diff --git a/skymp5-client/src/services/services/remoteServer.ts b/skymp5-client/src/services/services/remoteServer.ts index 9b1e61670b..f3c2b8fc22 100644 --- a/skymp5-client/src/services/services/remoteServer.ts +++ b/skymp5-client/src/services/services/remoteServer.ts @@ -784,7 +784,7 @@ export class RemoteServer extends ClientListener { return this.worldModel.playerCharacterFormIdx; } - private getIdManager() { + getIdManager() { return this.idManager_; } diff --git a/skymp5-server/cpp/addon/ScampServer.cpp b/skymp5-server/cpp/addon/ScampServer.cpp index c4ad1aec18..f51d629000 100644 --- a/skymp5-server/cpp/addon/ScampServer.cpp +++ b/skymp5-server/cpp/addon/ScampServer.cpp @@ -1021,6 +1021,12 @@ Napi::Value ScampServer::GetIdFromDesc(const Napi::CallbackInfo& info) Napi::Value ScampServer::CallPapyrusFunction(const Napi::CallbackInfo& info) { + // This function throws exceptions in case of bad input + // But it also catches exceptions from the Papyrus VM functions + // This is because + // 1) they're rare and unexpected, and we don't want to crash the sever + // 2) in Papyrus (not JS) we catch them all. so it's consistent + // 3) we plan replacing all exceptions with logs in Papyrus VM functions try { auto callType = NapiHelper::ExtractString(info[0], "callType"); auto className = NapiHelper::ExtractString(info[1], "className"); @@ -1045,15 +1051,27 @@ Napi::Value ScampServer::CallPapyrusFunction(const Napi::CallbackInfo& info) auto& vm = partOne->worldState.GetPapyrusVm(); if (callType == "method") { if (self.GetType() == VarValue::Type::kType_Object) { - res = vm.CallMethod(static_cast(self), - functionName.data(), args); + try { + res = vm.CallMethod(static_cast(self), + functionName.data(), args); + } catch (std::exception& e) { + res = VarValue::None(); + spdlog::error("ScampServer::CallPapyrusFunction {} {} - {}", + self.ToString(), functionName, e.what()); + } } else { throw std::runtime_error( "Can't call Papyrus method on non-object self '" + self.ToString() + "'"); } } else if (callType == "global") { - res = vm.CallStatic(className, functionName, args); + try { + res = vm.CallStatic(className, functionName, args); + } catch (std::exception& e) { + res = VarValue::None(); + spdlog::error("ScampServer::CallPapyrusFunction {} {} - {}", className, + functionName, e.what()); + } } else { throw std::runtime_error("Unknown call type '" + callType + "', expected one of ['method', 'global']"); diff --git a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusObjectReference.cpp index ccf9959064..5a93d786da 100644 --- a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusObjectReference.cpp @@ -188,7 +188,9 @@ VarValue PapyrusObjectReference::RemoveItem( auto worldState = selfRefr->GetParent(); if (!worldState) { - throw std::runtime_error("RemoveItem - no WorldState attached"); + spdlog::error("RemoveItem {:x} - no WorldState attached", + selfRefr->GetFormId()); + return VarValue::None(); } if (!selfRefr || !item.rec) @@ -196,7 +198,10 @@ VarValue PapyrusObjectReference::RemoveItem( if (!espm::utils::Is(item.rec->GetType())) { if (!espm::utils::IsItem(item.rec->GetType())) { - throw std::runtime_error("RemoveItem - form is not an item"); + spdlog::error("RemoveItem {:x} - form {:x} is not an item, it is {}", + selfRefr->GetFormId(), item.ToGlobalId(item.rec->GetId()), + item.rec->GetType().ToString()); + return VarValue::None(); } } @@ -205,8 +210,10 @@ VarValue PapyrusObjectReference::RemoveItem( espm::Convert(item.rec)->GetData(worldState->GetEspmCache()); bool isTorch = res.data.flags & espm::LIGH::Flags::CanBeCarried; if (!isTorch) { - throw std::runtime_error( - "RemoveItem - form is LIGH without CanBeCarried flag"); + spdlog::error( + "RemoveItem {:x} - form {:x} is LIGH without CanBeCarried flag", + selfRefr->GetFormId(), item.ToGlobalId(item.rec->GetId())); + return VarValue::None(); } } diff --git a/skymp5-server/ts/index.ts b/skymp5-server/ts/index.ts index 4ec7c8f2c0..e5cf0ae0f2 100644 --- a/skymp5-server/ts/index.ts +++ b/skymp5-server/ts/index.ts @@ -303,6 +303,15 @@ const main = async () => { main(); // This is needed at least to handle axios errors in masterClient +// TODO: implement alerts process.on("unhandledRejection", (...args) => { + console.error("[!!!] unhandledRejection") + console.error(...args); +}); + +// setTimeout on gamemode should not be able to kill the entire server +// TODO: implement alerts +process.on("uncaughtException", (...args) => { + console.error("[!!!] uncaughtException") console.error(...args); }); diff --git a/skymp5-server/ts/systems/discordBanSystem.ts b/skymp5-server/ts/systems/discordBanSystem.ts index b49d753c40..2b88681733 100644 --- a/skymp5-server/ts/systems/discordBanSystem.ts +++ b/skymp5-server/ts/systems/discordBanSystem.ts @@ -19,16 +19,16 @@ export class DiscordBanSystem implements System { return console.log("discord ban system is disabled due to offline mode"); } if (!discordAuth) { - return console.error("discordAuth is missing, skipping Discord ban system"); + return console.warn("discordAuth is missing, skipping Discord ban system"); } if (!discordAuth.botToken) { - return console.error("discordAuth.botToken is missing, skipping Discord ban system"); + return console.warn("discordAuth.botToken is missing, skipping Discord ban system"); } if (!discordAuth.guildId) { - return console.error("discordAuth.guildId is missing, skipping Discord ban system"); + return console.warn("discordAuth.guildId is missing, skipping Discord ban system"); } if (!discordAuth.banRoleId) { - return console.error("discordAuth.banRoleId is missing, skipping Discord ban system"); + return console.warn("discordAuth.banRoleId is missing, skipping Discord ban system"); } const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers] }); diff --git a/vcpkg.json b/vcpkg.json index 5166218942..f263d02f38 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -43,6 +43,11 @@ { "name": "asio", "platform": "windows" + }, + { + "name": "libarchive", + "platform": "windows", + "features": [] } ], "features": {