From 65861143bf02e6375231a391dcd43654e19562be Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Tue, 27 Aug 2024 11:25:53 +0800 Subject: [PATCH] fix: fix issues - restore some CKGlobals behavior because it will cause runtime exception. - move some classes from CmdHelper to UnvirtContext to make CmdHelper more common to use. - output warning string when fail to get utf8 or ordinary string. --- LibCmo/CK2/CKContext.cpp | 8 ++++-- LibCmo/CK2/CKContext.hpp | 6 ++-- LibCmo/CK2/CKFileReader.cpp | 6 ++-- LibCmo/CK2/CKFileWriter.cpp | 9 ++++-- LibCmo/CK2/CKGlobals.cpp | 14 ++++++---- LibCmo/CK2/CKGlobals.hpp | 16 +++++------ LibCmo/CK2/CKStateChunkReader.cpp | 8 +++++- LibCmo/CK2/CKStateChunkWriter.cpp | 3 +- LibCmo/CK2/MgrImpls/CKObjectManager.cpp | 6 +++- LibCmo/CK2/ObjImpls/CKTexture.cpp | 6 ++-- Unvirt/CmdHelper.cpp | 22 ++++----------- Unvirt/CmdHelper.hpp | 18 ++---------- Unvirt/StructFormatter.cpp | 2 +- Unvirt/UnvirtContext.cpp | 37 ++++++++++++++++++++----- 14 files changed, 91 insertions(+), 70 deletions(-) diff --git a/LibCmo/CK2/CKContext.cpp b/LibCmo/CK2/CKContext.cpp index 33cb2ad..a1eab2c 100644 --- a/LibCmo/CK2/CKContext.cpp +++ b/LibCmo/CK2/CKContext.cpp @@ -281,7 +281,7 @@ namespace LibCmo::CK2 { #pragma region Encoding utilities - void CKContext::GetUTF8String(const std::string& native_name, XContainer::XString& u8_name) { + bool CKContext::GetUTF8String(const std::string& native_name, XContainer::XString& u8_name) { bool conv_success = false, has_valid_token = false; for (const auto& token : this->m_NameEncoding) { if (token == EncodingHelper::INVALID_ENCODING_TOKEN) continue; @@ -298,9 +298,11 @@ namespace LibCmo::CK2 { this->OutputToConsole(u8"Error when converting to UTF8 string from ordinary string. The string will leave to blank."); } } + // return value + return conv_success; } - void CKContext::GetOrdinaryString(const XContainer::XString& u8_name, std::string& native_name) { + bool CKContext::GetOrdinaryString(const XContainer::XString& u8_name, std::string& native_name) { bool conv_success = false, has_valid_token = false; for (const auto& token : this->m_NameEncoding) { if (token == EncodingHelper::INVALID_ENCODING_TOKEN) continue; @@ -317,6 +319,8 @@ namespace LibCmo::CK2 { this->OutputToConsole(u8"Error when converting to ordinary string from UTF8 string. The string will leave to blank."); } } + // return value + return conv_success; } void CKContext::SetEncoding(const XContainer::XArray& encoding_seq) { diff --git a/LibCmo/CK2/CKContext.hpp b/LibCmo/CK2/CKContext.hpp index 8d8ee19..5a9a9c4 100644 --- a/LibCmo/CK2/CKContext.hpp +++ b/LibCmo/CK2/CKContext.hpp @@ -104,6 +104,7 @@ namespace LibCmo::CK2 { * @brief Convert given ordinary string to UTF8 string. * @param[in] native_name The input ordinary string. * @param[out] u8_name The output UTF8 string. + * @return True if convertion is success, otherwise false. * @exception RuntimeException Raised when perform this operation with a blank encoding sequence. * @remarks * The encoding of ordinary is specified by encoding sequence. @@ -111,11 +112,12 @@ namespace LibCmo::CK2 { * However, if you use this function with blank encoding sequence, it will raise exception. * So becore using this function, please make sure that you have checked by calling IsValidEncoding(). */ - void GetUTF8String(const std::string& native_name, XContainer::XString& u8_name); + bool GetUTF8String(const std::string& native_name, XContainer::XString& u8_name); /** * @brief Convert given UTF8 string to ordinary string. * @param[in] u8_name The input UTF8 string. * @param[out] native_name The output ordinary string. + * @return True if convertion is success, otherwise false. * @exception RuntimeException Raised when perform this operation with a blank encoding sequence. * @remarks * The encoding of ordinary is specified by encoding sequence. @@ -123,7 +125,7 @@ namespace LibCmo::CK2 { * However, if you use this function with blank encoding sequence, it will raise exception. * So becore using this function, please make sure that you have checked by calling IsValidEncoding(). */ - void GetOrdinaryString(const XContainer::XString& u8_name, std::string& native_name); + bool GetOrdinaryString(const XContainer::XString& u8_name, std::string& native_name); /** * @brief Set the encoding sequence. * @param[in] encoding_series The encoding name in this sequence. diff --git a/LibCmo/CK2/CKFileReader.cpp b/LibCmo/CK2/CKFileReader.cpp index 0b58376..772c5d9 100644 --- a/LibCmo/CK2/CKFileReader.cpp +++ b/LibCmo/CK2/CKFileReader.cpp @@ -133,7 +133,8 @@ namespace LibCmo::CK2 { if (namelen != 0) { name_conv.resize(namelen); parser->Read(name_conv.data(), namelen); - m_Ctx->GetUTF8String(name_conv, fileobj.Name); + if (!m_Ctx->GetUTF8String(name_conv, fileobj.Name)) + m_Ctx->OutputToConsole(u8"Fail to get UTF8 name for CKObject when reading file header. Some objects name will leave to blank."); } else { XContainer::NSXString::FromCKSTRING(fileobj.Name, nullptr); } @@ -311,7 +312,8 @@ namespace LibCmo::CK2 { // read filename if (filenamelen != 0) { parser->Read(name_conv.data(), filenamelen); - m_Ctx->GetUTF8String(name_conv, file); + if (!m_Ctx->GetUTF8String(name_conv, file)) + m_Ctx->OutputToConsole(u8"Fail to get UTF8 name for included file when reading file body. Some included files may be stripped."); } // read file body length diff --git a/LibCmo/CK2/CKFileWriter.cpp b/LibCmo/CK2/CKFileWriter.cpp index b652218..892687e 100644 --- a/LibCmo/CK2/CKFileWriter.cpp +++ b/LibCmo/CK2/CKFileWriter.cpp @@ -91,7 +91,8 @@ namespace LibCmo::CK2 { sumHdrObjSize += 4 * CKSizeof(CKDWORD); if (XContainer::NSXString::ToCKSTRING(obj.Name) != nullptr) { // += Name size - m_Ctx->GetOrdinaryString(obj.Name, name_conv); + if (!m_Ctx->GetOrdinaryString(obj.Name, name_conv)) + m_Ctx->OutputToConsole(u8"Fail to get ordinary string for CKObject name when computing the size of saved file. It may cause application crash or saved file has blank object name."); sumHdrObjSize += static_cast(name_conv.size()); } @@ -178,7 +179,8 @@ namespace LibCmo::CK2 { if (XContainer::NSXString::ToCKSTRING(obj.Name) != nullptr) { // if have name, write it - m_Ctx->GetOrdinaryString(obj.Name, name_conv); + if (!m_Ctx->GetOrdinaryString(obj.Name, name_conv)) + m_Ctx->OutputToConsole(u8"Fail to get ordinary string for CKObject name when saving file. Some objects may be saved with blank name."); CKDWORD namelen = static_cast(name_conv.size()); hdrparser->Write(&namelen); hdrparser->Write(name_conv.data(), namelen); @@ -318,7 +320,8 @@ namespace LibCmo::CK2 { m_Ctx->GetPathManager()->GetFileName(filename); // write filename - m_Ctx->GetOrdinaryString(filename, name_conv); + if (!m_Ctx->GetOrdinaryString(filename, name_conv)) + m_Ctx->OutputToConsole(u8"Fail to get ordinary string for included file when saving file. Some included files may not be saved correctly."); CKDWORD filenamelen = static_cast(name_conv.size()); std::fwrite(&filenamelen, sizeof(CKDWORD), 1, fs); std::fwrite(name_conv.data(), sizeof(CKBYTE), filenamelen, fs); diff --git a/LibCmo/CK2/CKGlobals.cpp b/LibCmo/CK2/CKGlobals.cpp index 47a8805..c19bb34 100644 --- a/LibCmo/CK2/CKGlobals.cpp +++ b/LibCmo/CK2/CKGlobals.cpp @@ -145,8 +145,7 @@ namespace LibCmo::CK2 { void CKClassNeedNotificationFrom(CK_CLASSID listener, CK_CLASSID listenTo) { size_t idxListener, idxListenTo; - if (!GetClassIdIndex(listener, idxListener) || !GetClassIdIndex(listenTo, idxListenTo)) - throw LogicException("Invalid CK_CLASSID in argument."); + if (!GetClassIdIndex(listener, idxListener) || !GetClassIdIndex(listenTo, idxListenTo)) return; XContainer::NSXBitArray::Set(g_CKClassInfo[idxListener].ToBeNotify, static_cast(idxListenTo)); } @@ -191,25 +190,25 @@ namespace LibCmo::CK2 { const CKClassDesc* CKGetClassDesc(CK_CLASSID cid) { size_t intcid; - if (!GetClassIdIndex(cid, intcid)) - throw LogicException("Invalid CK_CLASSID."); + if (!GetClassIdIndex(cid, intcid)) return nullptr; return &g_CKClassInfo[intcid]; } CKSTRING CKClassIDToString(CK_CLASSID cid) { const CKClassDesc* desc = CKGetClassDesc(cid); + if (desc == nullptr) return u8"Undefined Type"; return desc->NameFct(); } bool CKIsChildClassOf(CK_CLASSID child, CK_CLASSID parent) { size_t intchild, intparent; - if (!GetClassIdIndex(child, intchild) || !GetClassIdIndex(parent, intparent)) - throw LogicException("Invalid CK_CLASSID."); + if (!GetClassIdIndex(child, intchild) || !GetClassIdIndex(parent, intparent)) return false; return g_CKClassInfo[intchild].Parents[intparent]; } CK_CLASSID CKGetParentClassID(CK_CLASSID child) { const CKClassDesc* desc = CKGetClassDesc(child); + if (desc == nullptr) return CK_CLASSID::CKCID_OBJECT; return desc->Parent; } @@ -228,6 +227,7 @@ namespace LibCmo::CK2 { bool CKIsNeedNotify(CK_CLASSID listener, CK_CLASSID deletedObjCid) { const CKClassDesc* desc = CKGetClassDesc(listener); + if (desc == nullptr) return false; return XContainer::NSXBitArray::IsSet(desc->CommonToBeNotify, static_cast(deletedObjCid)); } @@ -238,6 +238,8 @@ namespace LibCmo::CK2 { if (!XContainer::NSXBitArray::IsSet(delObjCids, static_cast(i))) continue; const CKClassDesc* desc = CKGetClassDesc(static_cast(i)); + if (desc == nullptr) continue; + XContainer::NSXBitArray::Or(result, desc->ToNotify); } diff --git a/LibCmo/CK2/CKGlobals.hpp b/LibCmo/CK2/CKGlobals.hpp index b0aa492..a9df972 100644 --- a/LibCmo/CK2/CKGlobals.hpp +++ b/LibCmo/CK2/CKGlobals.hpp @@ -155,7 +155,7 @@ namespace LibCmo::CK2 { * @brief Order that first class id will be notified when deleting object whose class id is second argument * @param[in] listener The id of class will be notified. * @param[in] listenTo The id of class which first argument interested in. - * @exception LogicException Raised if given CK_CLASSID is invalid. + * @remarks If one of given class ids is invalid, this function simply return and do nothing. */ void CKClassNeedNotificationFrom(CK_CLASSID listener, CK_CLASSID listenTo); /** @@ -188,15 +188,15 @@ namespace LibCmo::CK2 { * @brief Get the class description struct by given class id. * @param[in] cid Class id for fetching. * @return The pointer to corresponding class description. - * We guaranteen that this pointer must not be nullptr. - * @exception LogicException Raised if given CK_CLASSID is invalid. + * If given class id is invalid, this function will return nullptr. + * According to this, caller can utilize this function to validate class id. */ const CKClassDesc* CKGetClassDesc(CK_CLASSID cid); /** * @brief Get the name representation of given class id. * @param[in] cid Class id for fetching. * @return The name of given class id. - * @exception LogicException Raised if given CK_CLASSID is invalid. + * If given class id is invalid, it return a predefined name. */ CKSTRING CKClassIDToString(CK_CLASSID cid); @@ -205,14 +205,14 @@ namespace LibCmo::CK2 { * @param[in] child The id of first class assumed as child class. * @param[in] parent The id of second class assumed as parent class. * @return True if relation is satisfied, otherwise false. - * @exception LogicException Raised if given CK_CLASSID is invalid. + * If one of given class ids is invalid, this function always return false. */ bool CKIsChildClassOf(CK_CLASSID child, CK_CLASSID parent); /** * @brief Get the parent class id of given class id. * @param[in] child The id to class which need to find parent class. * @return The parent class id. - * @exception LogicException Raised if given CK_CLASSID is invalid. + * If given class id is invalid, this function always return the class id of CKObject. */ CK_CLASSID CKGetParentClassID(CK_CLASSID child); /** @@ -220,7 +220,6 @@ namespace LibCmo::CK2 { * @param[in] cid1 The id of first class finding common parent. * @param[in] cid2 The id of second class finding common parent. * @return The cloest common parent class id. - * @exception LogicException Raised if given CK_CLASSID is invalid. */ CK_CLASSID CKGetCommonParent(CK_CLASSID cid1, CK_CLASSID cid2); @@ -229,14 +228,13 @@ namespace LibCmo::CK2 { * @param[in] listener The class id of checking whether need to be notified. * @param[in] deletedObjCid The class id of deleting object. * @return True if it need to be notified, otherwise false. - * @exception LogicException Raised if given CK_CLASSID is invalid. + * If the class id of checking is invalid, this function always return false. */ bool CKIsNeedNotify(CK_CLASSID listener, CK_CLASSID deletedObjCid); /** /** * @brief Get all class ids need to be notified when objects whose class id included in \c delObjCids are deleting. * @param[in] delObjCids The bit array representing class ids which need to be queried. * @return The queried bit array representing class ids need to be notified. - * @exception LogicException Raised if given CK_CLASSID is invalid. * @see CKIsNeedNotify() */ XContainer::XBitArray CKGetAllNotifyClassID(const XContainer::XBitArray& delObjCids); diff --git a/LibCmo/CK2/CKStateChunkReader.cpp b/LibCmo/CK2/CKStateChunkReader.cpp index d801cad..6ef9fd8 100644 --- a/LibCmo/CK2/CKStateChunkReader.cpp +++ b/LibCmo/CK2/CKStateChunkReader.cpp @@ -148,7 +148,13 @@ namespace LibCmo::CK2 { } // convert encoding - m_BindContext->GetUTF8String(cache, *strl); + if (!m_BindContext->GetUTF8String(cache, *strl)) { + m_BindContext->OutputToConsole(u8"Fail to get UTF8 string when reading CKStateChunk. Some objects may be loaded incorrectly."); + strl->clear(); + return false; + } + + // okey return true; } diff --git a/LibCmo/CK2/CKStateChunkWriter.cpp b/LibCmo/CK2/CKStateChunkWriter.cpp index 7bbd488..b4478e0 100644 --- a/LibCmo/CK2/CKStateChunkWriter.cpp +++ b/LibCmo/CK2/CKStateChunkWriter.cpp @@ -138,7 +138,8 @@ namespace LibCmo::CK2 { // convert encoding std::string cache; - m_BindContext->GetOrdinaryString(*strl, cache); + if (!m_BindContext->GetOrdinaryString(*strl, cache)) + m_BindContext->OutputToConsole(u8"Fail to get ordinary string when saving CKStateChunk. Some objects may be saved incorrectly."); if (cache.empty()) { // write zero string diff --git a/LibCmo/CK2/MgrImpls/CKObjectManager.cpp b/LibCmo/CK2/MgrImpls/CKObjectManager.cpp index 0b3947d..d282e16 100644 --- a/LibCmo/CK2/MgrImpls/CKObjectManager.cpp +++ b/LibCmo/CK2/MgrImpls/CKObjectManager.cpp @@ -20,6 +20,8 @@ namespace LibCmo::CK2::MgrImpls { // get description first const CKClassDesc* desc = CKGetClassDesc(cls); + // if no description, return directly to reject creating object. + if (desc == nullptr) return nullptr; // allocate a CK_ID first CKDWORD decided_off; @@ -60,7 +62,9 @@ namespace LibCmo::CK2::MgrImpls { void CKObjectManager::InternalDestroy(ObjImpls::CKObject* obj) { // find desc by classid const CKClassDesc* desc = CKGetClassDesc(obj->GetClassID()); - + // a create CKObject instance definitely can find corresponding desc. + // if not, throw exception. + if (desc == nullptr) throw LogicException("Invalid CK_CLASSID"); // free it desc->ReleaseFct(m_Context, obj); } diff --git a/LibCmo/CK2/ObjImpls/CKTexture.cpp b/LibCmo/CK2/ObjImpls/CKTexture.cpp index af6b544..b9bb2d0 100644 --- a/LibCmo/CK2/ObjImpls/CKTexture.cpp +++ b/LibCmo/CK2/ObjImpls/CKTexture.cpp @@ -187,7 +187,8 @@ namespace LibCmo::CK2::ObjImpls { props.m_ReaderGuid.d1 = realprops.m_ReaderGuid.d1; props.m_ReaderGuid.d2 = realprops.m_ReaderGuid.d2; std::string ext; - m_Context->GetOrdinaryString(realprops.m_Ext.GetExt(), ext); + if (!m_Context->GetOrdinaryString(realprops.m_Ext.GetExt(), ext)) + m_Context->OutputToConsole(u8"Fail to get ordinary string for the extension of bitmap properties when saving CKTexture. Some textures may be saved with blank extension."); std::memcpy( props.m_Ext.m_Data, ext.c_str(), @@ -314,7 +315,8 @@ namespace LibCmo::CK2::ObjImpls { // get utf8 extension XContainer::XString ext; - m_Context->GetUTF8String(props->m_Ext.m_Data, ext); + if (!m_Context->GetUTF8String(props->m_Ext.m_Data, ext)) + m_Context->OutputToConsole(u8"Fail to get UTF8 extension when loading CKTexture. Some textures may have blank extension in bitmap properties."); // get my bitmap prop CKBitmapProperties myprops( diff --git a/Unvirt/CmdHelper.cpp b/Unvirt/CmdHelper.cpp index 8b6e54c..30bae6e 100644 --- a/Unvirt/CmdHelper.cpp +++ b/Unvirt/CmdHelper.cpp @@ -522,6 +522,11 @@ if (!this->IsRootNode()) { \ #pragma region String Argument + StringArgument::StringArgument(const std::u8string_view& argname, Constraint_t constraint) : + AbstractArgument(argname), m_Constraint(constraint) {} + + StringArgument::~StringArgument() {} + bool StringArgument::BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) { // check constraint if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(cur_cmd)) @@ -531,23 +536,6 @@ if (!this->IsRootNode()) { \ return true; } -#pragma endregion - -#pragma region Encoding List Argument - - bool EncodingListArgument::BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) { - // split given argument - std::vector encs = YYCC::StringHelper::Split(cur_cmd, u8","); - // check each parts is a valid encoding name - for (const auto& item : encs) { - if (!LibCmo::EncodingHelper::IsValidEncodingName(item)) - return false; - } - // okey, add into map - am.Add(m_ArgumentName, encs); - return true; - } - #pragma endregion } diff --git a/Unvirt/CmdHelper.hpp b/Unvirt/CmdHelper.hpp index d9f211d..f68845a 100644 --- a/Unvirt/CmdHelper.hpp +++ b/Unvirt/CmdHelper.hpp @@ -498,9 +498,8 @@ namespace Unvirt::CmdHelper { using ArgValue_t = AMItems::StringItem; using Constraint_t = YYCC::Constraints::Constraint; public: - StringArgument(const std::u8string_view& argname, Constraint_t constraint = Constraint_t {}) : - AbstractArgument(argname), m_Constraint(constraint) {} - virtual ~StringArgument() {} + StringArgument(const std::u8string_view& argname, Constraint_t constraint = Constraint_t {}); + virtual ~StringArgument(); YYCC_DEF_CLS_COPY_MOVE(StringArgument); protected: @@ -508,19 +507,6 @@ namespace Unvirt::CmdHelper { Constraint_t m_Constraint; }; - class EncodingListArgument : public AbstractArgument { - public: - using ArgValue_t = AMItems::StringArrayItem; - public: - EncodingListArgument(const std::u8string_view& argname) : - AbstractArgument(argname) {} - virtual ~EncodingListArgument() {} - YYCC_DEF_CLS_COPY_MOVE(EncodingListArgument); - - protected: - virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override; - }; - } class CommandParser { diff --git a/Unvirt/StructFormatter.cpp b/Unvirt/StructFormatter.cpp index ff96a1d..07733bf 100644 --- a/Unvirt/StructFormatter.cpp +++ b/Unvirt/StructFormatter.cpp @@ -204,7 +204,7 @@ namespace Unvirt::StructFormatter { for (LibCmo::CKDWORD i = 0; i < slot_count; ++i) { auto desc = bd.GetImageDesc(i); - Console::FormatLine(u8"#%" PRIuCKDWORD "\t%" PRIuCKDWORD "\t%" PRIuCKDWORD "\t0x%" PRIxCKDWORD " bytes\t", + Console::FormatLine(u8"#%" PRIuCKDWORD "\t%" PRIuCKDWORD "\t%" PRIuCKDWORD "\t%s\t0x%" PRIxCKDWORD " bytes\t%s", i, desc->GetWidth(), desc->GetHeight(), diff --git a/Unvirt/UnvirtContext.cpp b/Unvirt/UnvirtContext.cpp index 2fbfbac..0c54340 100644 --- a/Unvirt/UnvirtContext.cpp +++ b/Unvirt/UnvirtContext.cpp @@ -7,7 +7,31 @@ namespace Unvirt::Context { -#pragma region Constraint Help Function +#pragma region Specialized for CmdHelper + + class EncodingListArgument : public CmdHelper::Nodes::AbstractArgument { + public: + using ArgValue_t = CmdHelper::AMItems::StringArrayItem; + public: + EncodingListArgument(const std::u8string_view& argname) : + AbstractArgument(argname) {} + virtual ~EncodingListArgument() {} + YYCC_DEF_CLS_COPY_MOVE(EncodingListArgument); + + protected: + virtual bool BeginConsume(const std::u8string& cur_cmd, CmdHelper::ArgumentsMap& am) override { + // split given argument + std::vector encs = YYCC::StringHelper::Split(cur_cmd, u8","); + // check each parts is a valid encoding name + for (const auto& item : encs) { + if (!LibCmo::EncodingHelper::IsValidEncodingName(item)) + return false; + } + // okey, add into map + am.Add(m_ArgumentName, encs); + return true; + } + }; static YYCC::Constraints::Constraint GetOneBasedIndexConstraint() { return YYCC::Constraints::Constraint { @@ -19,7 +43,6 @@ namespace Unvirt::Context { #pragma endregion - #pragma region UnvirtContext Misc UnvirtContext::UnvirtContext() : @@ -136,7 +159,7 @@ namespace Unvirt::Context { ) ) .Then(CmdHelper::Nodes::Literal(u8"encoding") - .Then(CmdHelper::Nodes::EncodingListArgument(u8"enc") + .Then(EncodingListArgument(u8"enc") .Comment(u8"CKContext used encoding splitted by ','. Support mutiple encoding.") .Executes( std::bind(&UnvirtContext::ProcEncoding, this, std::placeholders::_1), @@ -644,7 +667,7 @@ namespace Unvirt::Context { } void UnvirtContext::ProcEncoding(const CmdHelper::ArgumentsMap& amap) { - const auto& encodings = amap.Get(u8"enc").Get(); + const auto& encodings = amap.Get(u8"enc").Get(); m_Ctx->SetEncoding(encodings); } @@ -706,12 +729,12 @@ namespace Unvirt::Context { } } } - } + } #else PrintCommonError(u8"Test command only available in Debug mode."); #endif - } + } void Unvirt::Context::UnvirtContext::ProcHelp(const CmdHelper::ArgumentsMap&) { m_Help.Print(); @@ -723,4 +746,4 @@ namespace Unvirt::Context { #pragma endregion - } +}