diff --git a/binaries/data/mods/public/gui/common/functions_utility_error.js b/binaries/data/mods/public/gui/common/functions_utility_error.js index 7ab9796037..17254b3870 100644 --- a/binaries/data/mods/public/gui/common/functions_utility_error.js +++ b/binaries/data/mods/public/gui/common/functions_utility_error.js @@ -16,7 +16,7 @@ function cancelOnError(msg) if (msg) { Engine.PushGuiPage("page_msgbox.xml", { - width: 400, + width: 500, height: 200, message: '[font="serif-bold-18"]' + msg + '[/font]', title: "Loading Aborted", diff --git a/binaries/data/mods/public/simulation/ai/common-api/base.js b/binaries/data/mods/public/simulation/ai/common-api/base.js index dd03ee9370..08031f09a1 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/base.js +++ b/binaries/data/mods/public/simulation/ai/common-api/base.js @@ -3,16 +3,41 @@ function BaseAI(settings) if (!settings) return; - // Make some properties non-enumerable, so they won't be serialised - Object.defineProperty(this, "_player", {value: settings.player, enumerable: false}); - Object.defineProperty(this, "_templates", {value: settings.templates, enumerable: false}); - Object.defineProperty(this, "_derivedTemplates", {value: {}, enumerable: false}); + // Copies of static engine data (not serialized) + this._player = settings.player; + this._templates = settings.templates; + this._derivedTemplates = {}; + // Representation of the current world state (requires serialization) + this._rawEntities = null; this._ownEntities = {}; - this._entityMetadata = {}; } +// Return a simple object (using no classes etc) that will be serialized +// into saved games +BaseAI.prototype.Serialize = function() +{ + return { + _rawEntities: this._rawEntities, + _ownEntities: this._ownEntities, + _entityMetadata: this._entityMetadata, + }; + + // TODO: ought to get the AI script subclass to serialize its own state +}; + +// Called after the constructor when loading a saved game, with 'data' being +// whatever Serialize() returned +BaseAI.prototype.Deserialize = function(data) +{ + this._rawEntities = data._rawEntities; + this._ownEntities = data._ownEntities; + this._entityMetadata = data._entityMetadata; + + // TODO: ought to get the AI script subclass to deserialize its own state +}; + // Components that will be disabled in foundation entity templates. // (This is a bit yucky and fragile since it's the inverse of // CCmpTemplateManager::CopyFoundationSubset and only includes components @@ -160,7 +185,8 @@ BaseAI.prototype.ApplyEntitiesDelta = function(state) }; BaseAI.prototype.OnUpdate = function() -{ // AIs override this function +{ + // AIs override this function }; BaseAI.prototype.chat = function(message) diff --git a/binaries/data/mods/public/simulation/helpers/InitGame.js b/binaries/data/mods/public/simulation/helpers/InitGame.js index 1e572f17fe..6c6556547a 100644 --- a/binaries/data/mods/public/simulation/helpers/InitGame.js +++ b/binaries/data/mods/public/simulation/helpers/InitGame.js @@ -2,6 +2,8 @@ function InitGame(settings) { // This will be called after the map settings have been loaded, // before the simulation has started. + // This is only called at the start of a new game, not when loading + // a saved game. // No settings when loading a map in Atlas, so do nothing if (!settings) diff --git a/source/ps/Game.cpp b/source/ps/Game.cpp index 1b833f62a1..afcab01f7a 100644 --- a/source/ps/Game.cpp +++ b/source/ps/Game.cpp @@ -36,6 +36,7 @@ #include "ps/Profile.h" #include "ps/Replay.h" #include "ps/World.h" +#include "ps/GameSetup/GameSetup.h" #include "renderer/Renderer.h" #include "scripting/ScriptingHost.h" #include "scriptinterface/ScriptInterface.h" @@ -63,7 +64,8 @@ CGame::CGame(bool disableGraphics): m_GameStarted(false), m_Paused(false), m_SimRate(1.0f), - m_PlayerID(-1) + m_PlayerID(-1), + m_IsSavedGame(false) { m_ReplayLogger = new CReplayLogger(m_Simulation2->GetScriptInterface()); // TODO: should use CDummyReplayLogger unless activated by cmd-line arg, perhaps? @@ -115,6 +117,7 @@ void CGame::SetTurnManager(CNetTurnManager* turnManager) void CGame::RegisterInit(const CScriptValRooted& attribs, const std::string& savedState) { m_InitialSavedState = savedState; + m_IsSavedGame = !savedState.empty(); m_Simulation2->SetInitAttributes(attribs); @@ -153,7 +156,7 @@ void CGame::RegisterInit(const CScriptValRooted& attribs, const std::string& sav m_World->RegisterInitRMS(scriptFile, settings, m_PlayerID); } - if (!m_InitialSavedState.empty()) + if (m_IsSavedGame) RegMemFun(this, &CGame::LoadInitialState, L"Loading game", 1000); LDR_EndRegistering(); @@ -161,6 +164,7 @@ void CGame::RegisterInit(const CScriptValRooted& attribs, const std::string& sav int CGame::LoadInitialState() { + ENSURE(m_IsSavedGame); ENSURE(!m_InitialSavedState.empty()); std::string state; @@ -169,7 +173,11 @@ int CGame::LoadInitialState() std::stringstream stream(state); bool ok = m_Simulation2->DeserializeState(stream); - ENSURE(ok); + if (!ok) + { + CancelLoad(L"Failed to load saved game state. It might have been\nsaved with an incompatible version of the game."); + return 0; + } return 0; } @@ -181,9 +189,13 @@ int CGame::LoadInitialState() **/ PSRETURN CGame::ReallyStartGame() { - CScriptVal settings; - m_Simulation2->GetScriptInterface().GetProperty(m_Simulation2->GetInitAttributes().get(), "settings", settings); - m_Simulation2->InitGame(settings); + // Call the script function InitGame only for new games, not saved games + if (!m_IsSavedGame) + { + CScriptVal settings; + m_Simulation2->GetScriptInterface().GetProperty(m_Simulation2->GetInitAttributes().get(), "settings", settings); + m_Simulation2->InitGame(settings); + } // Call the reallyStartGame GUI function, but only if it exists if (g_GUI && g_GUI->HasPages()) diff --git a/source/ps/Game.h b/source/ps/Game.h index 8568c8c9a3..d6a1d18613 100644 --- a/source/ps/Game.h +++ b/source/ps/Game.h @@ -161,6 +161,7 @@ private: int LoadInitialState(); std::string m_InitialSavedState; // valid between RegisterInit and LoadInitialState + bool m_IsSavedGame; // true if loading a saved game; false for a new game }; extern CGame *g_Game; diff --git a/source/ps/Loader.cpp b/source/ps/Loader.cpp index 06c239011f..ce4404a64d 100644 --- a/source/ps/Loader.cpp +++ b/source/ps/Loader.cpp @@ -202,7 +202,7 @@ Status LDR_ProgressiveLoad(double time_budget, wchar_t* description, size_t max_ { state = LOADING; - ret = ERR::TIMED_OUT; // make caller think we did something + ret = ERR::TIMED_OUT; // make caller think we did something // progress already set to 0.0; that'll be passed back. goto done; } @@ -219,7 +219,7 @@ Status LDR_ProgressiveLoad(double time_budget, wchar_t* description, size_t max_ const double estimated_duration = lr.estimated_duration_ms*1e-3; if(!HaveTimeForNextTask(time_left, time_budget, lr.estimated_duration_ms)) { - ret = ERR::TIMED_OUT; + ret = ERR::TIMED_OUT; goto done; } @@ -258,7 +258,7 @@ Status LDR_ProgressiveLoad(double time_budget, wchar_t* description, size_t max_ // .. function interrupted itself, i.e. timed out; abort. if(timed_out) { - ret = ERR::TIMED_OUT; + ret = ERR::TIMED_OUT; goto done; } // .. failed; abort. loading will continue when we're called in @@ -270,6 +270,13 @@ Status LDR_ProgressiveLoad(double time_budget, wchar_t* description, size_t max_ ret = (Status)status; goto done; } + // .. function called LDR_Cancel; abort. return OK since this is an + // intentional cancellation, not an error. + else if(state != LOADING) + { + ret = INFO::OK; + goto done; + } // .. succeeded; continue and process next queued task. } diff --git a/source/ps/SavedGame.cpp b/source/ps/SavedGame.cpp index ec3ee58f0a..dfb13af990 100644 --- a/source/ps/SavedGame.cpp +++ b/source/ps/SavedGame.cpp @@ -75,15 +75,9 @@ Status SavedGames::Save(const std::wstring& prefix, CSimulation2& simulation, CG std::string metadataString = simulation.GetScriptInterface().StringifyJSON(metadata.get(), true); // Write the saved game as zip file containing the various components - PIArchiveWriter archiveWriter; - try - { - archiveWriter = CreateArchiveWriter_Zip(tempSaveFileRealPath, false); - } - catch (Status err) - { - WARN_RETURN(err); - } + PIArchiveWriter archiveWriter = CreateArchiveWriter_Zip(tempSaveFileRealPath, false); + if (!archiveWriter) + WARN_RETURN(ERR::FAIL); WARN_RETURN_STATUS_IF_ERR(archiveWriter->AddMemory((const u8*)metadataString.c_str(), metadataString.length(), now, "metadata.json")); WARN_RETURN_STATUS_IF_ERR(archiveWriter->AddMemory((const u8*)simStateStream.str().c_str(), simStateStream.str().length(), now, "simulation.dat")); @@ -147,15 +141,9 @@ Status SavedGames::Load(const std::wstring& name, ScriptInterface& scriptInterfa OsPath realPath; WARN_RETURN_STATUS_IF_ERR(g_VFS->GetRealPath(filename, realPath)); - PIArchiveReader archiveReader; - try - { - archiveReader = CreateArchiveReader_Zip(realPath); - } - catch (Status err) - { - WARN_RETURN(err); - } + PIArchiveReader archiveReader = CreateArchiveReader_Zip(realPath); + if (!archiveReader) + WARN_RETURN(ERR::FAIL); CGameLoader loader(scriptInterface, &metadata, &savedState); WARN_RETURN_STATUS_IF_ERR(archiveReader->ReadEntries(CGameLoader::ReadEntryCallback, (uintptr_t)&loader)); @@ -185,14 +173,11 @@ std::vector SavedGames::GetSavedGames(ScriptInterface& scriptI continue; // skip this file } - PIArchiveReader archiveReader; - try + PIArchiveReader archiveReader = CreateArchiveReader_Zip(realPath); + if (!archiveReader) { - archiveReader = CreateArchiveReader_Zip(realPath); - } - catch (Status err) - { - DEBUG_WARN_ERR(err); + // Triggered by e.g. the file being open in another program + LOGWARNING(L"Failed to read saved game '%ls'", realPath.string().c_str()); continue; // skip this file } diff --git a/source/simulation2/Simulation2.cpp b/source/simulation2/Simulation2.cpp index acad2019c4..55868051e7 100644 --- a/source/simulation2/Simulation2.cpp +++ b/source/simulation2/Simulation2.cpp @@ -638,8 +638,7 @@ ScriptInterface& CSimulation2::GetScriptInterface() const void CSimulation2::InitGame(const CScriptVal& data) { - CScriptVal ret; // ignored - GetScriptInterface().CallFunction(GetScriptInterface().GetGlobalObject(), "InitGame", data, ret); + GetScriptInterface().CallFunctionVoid(GetScriptInterface().GetGlobalObject(), "InitGame", data); } void CSimulation2::Update(int turnLength) diff --git a/source/simulation2/components/CCmpAIManager.cpp b/source/simulation2/components/CCmpAIManager.cpp index 5c522b7645..e555d9e7eb 100644 --- a/source/simulation2/components/CCmpAIManager.cpp +++ b/source/simulation2/components/CCmpAIManager.cpp @@ -210,6 +210,7 @@ private: CScriptVal settings; m_ScriptInterface.Eval(L"({})", settings); m_ScriptInterface.SetProperty(settings.get(), "player", m_Player, false); + ENSURE(m_Worker.m_HasLoadedEntityTemplates); m_ScriptInterface.SetProperty(settings.get(), "templates", m_Worker.m_EntityTemplates, false); obj = m_ScriptInterface.CallConstructor(ctor.get(), settings.get()); @@ -218,6 +219,7 @@ private: { // For deserialization, we want to create the object with the correct prototype // but don't want to actually run the constructor again + // XXX: actually we don't currently use this path for deserialization - maybe delete it? obj = m_ScriptInterface.NewObjectFromConstructor(ctor.get()); } @@ -258,7 +260,8 @@ public: m_ScriptRuntime(ScriptInterface::CreateRuntime()), m_ScriptInterface("Engine", "AI", m_ScriptRuntime), m_TurnNum(0), - m_CommandsComputed(true) + m_CommandsComputed(true), + m_HasLoadedEntityTemplates(false) { m_ScriptInterface.SetCallbackData(static_cast (this)); @@ -337,6 +340,8 @@ public: void LoadEntityTemplates(const std::vector >& templates) { + m_HasLoadedEntityTemplates = true; + m_ScriptInterface.Eval("({})", m_EntityTemplates); for (size_t i = 0; i < templates.size(); ++i) @@ -369,13 +374,18 @@ public: void SerializeState(ISerializer& serializer) { + std::stringstream rngStream; + rngStream << m_RNG; + serializer.StringASCII("rng", rngStream.str(), 0, 32); + + serializer.NumberU32_Unbounded("turn", m_TurnNum); + serializer.NumberU32_Unbounded("num ais", (u32)m_Players.size()); for (size_t i = 0; i < m_Players.size(); ++i) { - serializer.String("name", m_Players[i]->m_AIName, 0, 256); + serializer.String("name", m_Players[i]->m_AIName, 1, 256); serializer.NumberI32_Unbounded("player", m_Players[i]->m_Player); - serializer.ScriptVal("data", m_Players[i]->m_Obj); serializer.NumberU32_Unbounded("num commands", (u32)m_Players[i]->m_Commands.size()); for (size_t j = 0; j < m_Players[i]->m_Commands.size(); ++j) @@ -383,6 +393,11 @@ public: CScriptVal val = m_ScriptInterface.ReadStructuredClone(m_Players[i]->m_Commands[j]); serializer.ScriptVal("command", val); } + + CScriptVal scriptData; + if (!m_ScriptInterface.CallFunction(m_Players[i]->m_Obj.get(), "Serialize", scriptData)) + LOGERROR(L"AI script Serialize call failed"); + serializer.ScriptVal("data", scriptData); } } @@ -395,6 +410,14 @@ public: m_PlayerMetadata.clear(); m_Players.clear(); + std::string rngString; + std::stringstream rngStream; + deserializer.StringASCII("rng", rngString, 0, 32); + rngStream << rngString; + rngStream >> m_RNG; + + deserializer.NumberU32_Unbounded("turn", m_TurnNum); + uint32_t numAis; deserializer.NumberU32_Unbounded("num ais", numAis); @@ -402,15 +425,11 @@ public: { std::wstring name; player_id_t player; - deserializer.String("name", name, 0, 256); + deserializer.String("name", name, 1, 256); deserializer.NumberI32_Unbounded("player", player); - if (!AddPlayer(name, player, false)) + if (!AddPlayer(name, player, true)) throw PSERROR_Deserialize_ScriptError(); - // Use ScriptObjectAppend so we don't lose the carefully-constructed - // prototype/parent of this object - deserializer.ScriptObjectAppend("data", m_Players.back()->m_Obj.getRef()); - uint32_t numCommands; deserializer.NumberU32_Unbounded("num commands", numCommands); m_Players.back()->m_Commands.reserve(numCommands); @@ -420,6 +439,11 @@ public: deserializer.ScriptVal("command", val); m_Players.back()->m_Commands.push_back(m_ScriptInterface.WriteStructuredClone(val.get())); } + + CScriptVal scriptData; + deserializer.ScriptVal("data", scriptData); + if (!m_ScriptInterface.CallFunctionVoid(m_Players.back()->m_Obj.get(), "Deserialize", scriptData)) + LOGERROR(L"AI script Deserialize call failed"); } } @@ -473,9 +497,11 @@ private: shared_ptr m_ScriptRuntime; ScriptInterface m_ScriptInterface; boost::rand48 m_RNG; - size_t m_TurnNum; + u32 m_TurnNum; CScriptValRooted m_EntityTemplates; + bool m_HasLoadedEntityTemplates; + std::map m_PlayerMetadata; std::vector > m_Players; // use shared_ptr just to avoid copying @@ -523,17 +549,16 @@ public: // directly. So we'll just grab the ISerializer's stream and write to it // with an independent serializer. - // TODO: make the serialization/deserialization actually work, and not really slowly -// m_Worker.Serialize(serialize.GetStream(), serialize.IsDebug()); - UNUSED2(serialize); + m_Worker.Serialize(serialize.GetStream(), serialize.IsDebug()); } virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) { Init(paramNode); -// m_Worker.Deserialize(deserialize.GetStream()); - UNUSED2(deserialize); + ForceLoadEntityTemplates(); + + m_Worker.Deserialize(deserialize.GetStream()); } virtual void HandleMessage(const CMessage& msg, bool UNUSED(global)) diff --git a/source/simulation2/serialization/IDeserializer.cpp b/source/simulation2/serialization/IDeserializer.cpp index 9543756bce..6d8d93aabd 100644 --- a/source/simulation2/serialization/IDeserializer.cpp +++ b/source/simulation2/serialization/IDeserializer.cpp @@ -163,7 +163,9 @@ void IDeserializer::StringASCII(const char* name, std::string& out, uint32_t min { uint32_t len; NumberU32("string length", len, minlength, maxlength); - out.resize(len); // TODO: should check len <= bytes remaining in stream + + RequireBytesInStream(len); + out.resize(len); Get(name, (u8*)out.data(), len); for (size_t i = 0; i < out.length(); ++i) @@ -176,7 +178,9 @@ void IDeserializer::String(const char* name, std::wstring& out, uint32_t minleng std::string str; uint32_t len; NumberU32_Unbounded("string length", len); - str.resize(len); // TODO: should check len <= bytes remaining in stream + + RequireBytesInStream(len); + str.resize(len); Get(name, (u8*)str.data(), len); Status err; diff --git a/source/simulation2/serialization/IDeserializer.h b/source/simulation2/serialization/IDeserializer.h index 6ec7559916..2d1e336f0c 100644 --- a/source/simulation2/serialization/IDeserializer.h +++ b/source/simulation2/serialization/IDeserializer.h @@ -78,6 +78,14 @@ public: */ virtual std::istream& GetStream() = 0; + /** + * Throws an exception if the stream cannot provide the required number of + * bytes. (This should be used when allocating memory based on data in the + * stream, e.g. reading strings, to avoid dangerously large allocations + * when the data is invalid.) + */ + virtual void RequireBytesInStream(size_t numBytes) = 0; + protected: virtual void Get(const char* name, u8* data, size_t len) = 0; }; diff --git a/source/simulation2/serialization/StdDeserializer.cpp b/source/simulation2/serialization/StdDeserializer.cpp index 1774225c74..2c42fb1bae 100644 --- a/source/simulation2/serialization/StdDeserializer.cpp +++ b/source/simulation2/serialization/StdDeserializer.cpp @@ -67,6 +67,12 @@ std::istream& CStdDeserializer::GetStream() return m_Stream; } +void CStdDeserializer::RequireBytesInStream(size_t numBytes) +{ + if (numBytes >= m_Stream.rdbuf()->in_avail()) + throw PSERROR_Deserialize_OutOfBounds("RequireBytesInStream"); +} + void CStdDeserializer::AddScriptBackref(JSObject* obj) { std::pair::iterator, bool> it = m_ScriptBackrefs.insert(std::make_pair((u32)m_ScriptBackrefs.size()+1, obj)); @@ -197,7 +203,8 @@ void CStdDeserializer::ReadStringUTF16(const char* name, utf16string& str) { uint32_t len; NumberU32_Unbounded("string length", len); - str.resize(len); // TODO: should check len*2 <= bytes remaining in stream, before resizing + RequireBytesInStream(len*2); + str.resize(len); Get(name, (u8*)str.data(), len*2); } diff --git a/source/simulation2/serialization/StdDeserializer.h b/source/simulation2/serialization/StdDeserializer.h index 8a24b69b85..9f88d02867 100644 --- a/source/simulation2/serialization/StdDeserializer.h +++ b/source/simulation2/serialization/StdDeserializer.h @@ -38,7 +38,8 @@ public: virtual void ScriptString(const char* name, JSString*& out); virtual std::istream& GetStream(); - + virtual void RequireBytesInStream(size_t numBytes); + protected: virtual void Get(const char* name, u8* data, size_t len); diff --git a/source/simulation2/system/ComponentManagerSerialization.cpp b/source/simulation2/system/ComponentManagerSerialization.cpp index bda8bff6af..abfd08c489 100644 --- a/source/simulation2/system/ComponentManagerSerialization.cpp +++ b/source/simulation2/system/ComponentManagerSerialization.cpp @@ -261,69 +261,77 @@ bool CComponentManager::SerializeState(std::ostream& stream) bool CComponentManager::DeserializeState(std::istream& stream) { - CStdDeserializer deserializer(m_ScriptInterface, stream); - - ResetState(); - - std::string rng; - deserializer.StringASCII("rng", rng, 0, 32); - DeserializeRNG(rng, m_RNG); - - deserializer.NumberU32_Unbounded("next entity id", m_NextEntityId); // TODO: use sensible bounds - - uint32_t numComponentTypes; - deserializer.NumberU32_Unbounded("num component types", numComponentTypes); - - ICmpTemplateManager* templateManager = NULL; - CParamNode noParam; - - for (size_t i = 0; i < numComponentTypes; ++i) + try { - std::string ctname; - deserializer.StringASCII("name", ctname, 0, 255); - ComponentTypeId ctid = LookupCID(ctname); - if (ctid == CID__Invalid) + CStdDeserializer deserializer(m_ScriptInterface, stream); + + ResetState(); + + std::string rng; + deserializer.StringASCII("rng", rng, 0, 32); + DeserializeRNG(rng, m_RNG); + + deserializer.NumberU32_Unbounded("next entity id", m_NextEntityId); // TODO: use sensible bounds + + uint32_t numComponentTypes; + deserializer.NumberU32_Unbounded("num component types", numComponentTypes); + + ICmpTemplateManager* templateManager = NULL; + CParamNode noParam; + + for (size_t i = 0; i < numComponentTypes; ++i) { - LOGERROR(L"Deserialization saw unrecognised component type '%hs'", ctname.c_str()); + std::string ctname; + deserializer.StringASCII("name", ctname, 0, 255); + + ComponentTypeId ctid = LookupCID(ctname); + if (ctid == CID__Invalid) + { + LOGERROR(L"Deserialization saw unrecognised component type '%hs'", ctname.c_str()); + return false; + } + + uint32_t numComponents; + deserializer.NumberU32_Unbounded("num components", numComponents); + + for (size_t j = 0; j < numComponents; ++j) + { + entity_id_t ent; + deserializer.NumberU32_Unbounded("entity id", ent); + IComponent* component = ConstructComponent(ent, ctid); + if (!component) + return false; + + // Try to find the template for this entity + const CParamNode* entTemplate = NULL; + if (templateManager && ent != SYSTEM_ENTITY) // (system entities don't use templates) + entTemplate = templateManager->LoadLatestTemplate(ent); + + // Deserialize, with the appropriate template for this component + if (entTemplate) + component->Deserialize(entTemplate->GetChild(ctname.c_str()), deserializer); + else + component->Deserialize(noParam, deserializer); + + // If this was the template manager, remember it so we can use it when + // deserializing any further non-system entities + if (ent == SYSTEM_ENTITY && ctid == CID_TemplateManager) + templateManager = static_cast (component); + } + } + + if (stream.peek() != EOF) + { + LOGERROR(L"Deserialization didn't reach EOF"); return false; } - uint32_t numComponents; - deserializer.NumberU32_Unbounded("num components", numComponents); - - for (size_t j = 0; j < numComponents; ++j) - { - entity_id_t ent; - deserializer.NumberU32_Unbounded("entity id", ent); - IComponent* component = ConstructComponent(ent, ctid); - if (!component) - return false; - - // Try to find the template for this entity - const CParamNode* entTemplate = NULL; - if (templateManager && ent != SYSTEM_ENTITY) // (system entities don't use templates) - entTemplate = templateManager->LoadLatestTemplate(ent); - - // Deserialize, with the appropriate template for this component - if (entTemplate) - component->Deserialize(entTemplate->GetChild(ctname.c_str()), deserializer); - else - component->Deserialize(noParam, deserializer); - - // If this was the template manager, remember it so we can use it when - // deserializing any further non-system entities - if (ent == SYSTEM_ENTITY && ctid == CID_TemplateManager) - templateManager = static_cast (component); - } + return true; } - - if (stream.peek() != EOF) + catch (PSERROR_Deserialize& e) { - LOGERROR(L"Deserialization didn't reach EOF"); + LOGERROR(L"Deserialization failed: %hs", e.what()); return false; } - - // TODO: catch exceptions - return true; }