From 7414ceef4476fa8bad9b899c79f9fac9f24f618a Mon Sep 17 00:00:00 2001 From: FutureRave Date: Wed, 16 Nov 2022 17:25:21 +0000 Subject: [PATCH] [Zone:b:uilder]: Add .str parsing --- .../AssetInterfaces/ILocalizeEntry.cpp | 29 +++++++ .../AssetInterfaces/ILocalizeEntry.hpp | 4 +- src/Components/Modules/Localization.cpp | 80 +++++++++++++------ src/Components/Modules/Localization.hpp | 12 ++- src/Components/Modules/Network.cpp | 11 +-- src/Components/Modules/Network.hpp | 2 +- src/Components/Modules/RCon.cpp | 4 +- src/Components/Modules/ZoneBuilder.cpp | 79 +++++++++++------- src/Components/Modules/ZoneBuilder.hpp | 4 +- src/Game/Functions.hpp | 2 +- 10 files changed, 156 insertions(+), 71 deletions(-) diff --git a/src/Components/Modules/AssetInterfaces/ILocalizeEntry.cpp b/src/Components/Modules/AssetInterfaces/ILocalizeEntry.cpp index b3546388..5b5eb82a 100644 --- a/src/Components/Modules/AssetInterfaces/ILocalizeEntry.cpp +++ b/src/Components/Modules/AssetInterfaces/ILocalizeEntry.cpp @@ -52,4 +52,33 @@ namespace Assets buffer->popBlock(); } + + void ILocalizeEntry::ParseLocalizedStringsFile(Components::ZoneBuilder::Zone* builder, const std::string& name, const std::string& filename) + { + std::vector assets; + const auto _0 = gsl::finally([] + { + Components::Localization::ParseOutput(nullptr); + Components::Localization::PrefixOverride = {}; + }); + + Components::Localization::PrefixOverride = Utils::String::ToUpper(name) + "_"; + Components::Localization::ParseOutput([&assets](Game::LocalizeEntry* asset) + { + assets.push_back(asset); + }); + + const auto* psLoadedFile = Game::SE_Load(filename.data(), false); + if (psLoadedFile) + { + Game::Com_PrintError(Game::CON_CHANNEL_SYSTEM, "^1Localization ERROR: %s\n", psLoadedFile); + return; + } + + auto type = Game::DB_GetXAssetNameType("localize"); + for (const auto& entry : assets) + { + builder->addRawAsset(type, entry); + } + } } diff --git a/src/Components/Modules/AssetInterfaces/ILocalizeEntry.hpp b/src/Components/Modules/AssetInterfaces/ILocalizeEntry.hpp index 4b2d6f6d..b01b9c81 100644 --- a/src/Components/Modules/AssetInterfaces/ILocalizeEntry.hpp +++ b/src/Components/Modules/AssetInterfaces/ILocalizeEntry.hpp @@ -5,9 +5,11 @@ namespace Assets class ILocalizeEntry : public Components::AssetHandler::IAsset { public: - Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_LOCALIZE_ENTRY; }; + Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_LOCALIZE_ENTRY; } void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override; void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; + + static void ParseLocalizedStringsFile(Components::ZoneBuilder::Zone* builder, const std::string& name, const std::string& filename); }; } diff --git a/src/Components/Modules/Localization.cpp b/src/Components/Modules/Localization.cpp index a860440a..b5d9719f 100644 --- a/src/Components/Modules/Localization.cpp +++ b/src/Components/Modules/Localization.cpp @@ -6,23 +6,36 @@ namespace Components Dvar::Var Localization::UseLocalization; std::unordered_map Localization::LocalizeMap; - void Localization::Set(const std::string& key, const std::string& value) + std::optional Localization::PrefixOverride; + std::function Localization::ParseCallback = nullptr; + + void Localization::Set(const std::string& psLocalReference, const std::string& psNewString) { - std::lock_guard _(Localization::LocalizeMutex); + std::lock_guard _(LocalizeMutex); Utils::Memory::Allocator* allocator = Utils::Memory::GetAllocator(); - if (Localization::LocalizeMap.contains(key)) + auto key = psLocalReference; + if (PrefixOverride.has_value()) { - Game::LocalizeEntry* entry = Localization::LocalizeMap[key]; + key.insert(0, PrefixOverride.value()); + } - char* newStaticValue = allocator->duplicateString(value); + if (LocalizeMap.contains(key)) + { + auto* entry = LocalizeMap[key]; + + const auto* newStaticValue = allocator->duplicateString(psNewString); if (!newStaticValue) return; + if (entry->value) allocator->free(entry->value); entry->value = newStaticValue; + + SaveParseOutput(entry); + return; } - Game::LocalizeEntry* entry = allocator->allocate(); + auto* entry = allocator->allocate(); if (!entry) return; entry->name = allocator->duplicateString(key); @@ -32,7 +45,7 @@ namespace Components return; } - entry->value = allocator->duplicateString(value); + entry->value = allocator->duplicateString(psNewString); if (!entry->value) { allocator->free(entry->name); @@ -40,21 +53,23 @@ namespace Components return; } - Localization::LocalizeMap[key] = entry; + SaveParseOutput(entry); + + LocalizeMap[key] = entry; } const char* Localization::Get(const char* key) { - if (!Localization::UseLocalization.get()) return key; + if (!UseLocalization.get()) return key; Game::LocalizeEntry* entry = nullptr; { - std::lock_guard _(Localization::LocalizeMutex); + std::lock_guard _(LocalizeMutex); - if (Localization::LocalizeMap.contains(key)) + if (LocalizeMap.contains(key)) { - entry = Localization::LocalizeMap[key]; + entry = LocalizeMap[key]; } } @@ -71,9 +86,22 @@ namespace Components return key; } - void __stdcall Localization::SetStringStub(const char* key, const char* value, bool /*isEnglish*/) + void __stdcall Localization::SetStringStub(const char* psLocalReference, const char* psNewString, [[maybe_unused]] int bSentenceIsEnglish) { - Localization::Set(key, value); + Set(psLocalReference, psNewString); + } + + void Localization::ParseOutput(const std::function& callback) + { + ParseCallback = callback; + } + + void Localization::SaveParseOutput(Game::LocalizeEntry* asset) + { + if (Localization::ParseCallback) + { + Localization::ParseCallback(asset); + } } void Localization::SetCredits() @@ -161,7 +189,7 @@ namespace Components // I have no idea why, but the last 2 lines are invisible! credits.append("-\n-"); - Localization::Set("IW4X_CREDITS", credits); + Set("IW4X_CREDITS", credits); } const char* Localization::SEH_LocalizeTextMessageStub(const char* pszInputBuffer, const char* pszMessageType, Game::msgLocErrType_t errType) @@ -304,31 +332,31 @@ namespace Components Localization::Localization() { - Localization::SetCredits(); + SetCredits(); - AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_LOCALIZE_ENTRY, [](Game::XAssetType, const std::string& filename) + AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_LOCALIZE_ENTRY, [](Game::XAssetType, const std::string& name) { Game::XAssetHeader header = { nullptr }; - std::lock_guard _(Localization::LocalizeMutex); + std::lock_guard _(LocalizeMutex); - if (Localization::LocalizeMap.contains(filename)) + if (const auto itr = LocalizeMap.find(name); itr != LocalizeMap.end()) { - header.localize = Localization::LocalizeMap[filename]; + header.localize = itr->second; } return header; }); // Resolving hook - Utils::Hook(0x629B90, Localization::Get, HOOK_JUMP).install()->quick(); + Utils::Hook(0x629B90, Get, HOOK_JUMP).install()->quick(); // Overwrite SetString - Utils::Hook(0x4CE5EE, Localization::SetStringStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x4CE5EE, SetStringStub, HOOK_CALL).install()->quick(); - Utils::Hook(0x49D4A0, Localization::SEH_LocalizeTextMessageStub, HOOK_JUMP).install()->quick(); + Utils::Hook(0x49D4A0, SEH_LocalizeTextMessageStub, HOOK_JUMP).install()->quick(); Utils::Hook::Nop(0x49D4A5, 1); - Localization::UseLocalization = Dvar::Register("ui_localize", true, Game::DVAR_NONE, "Use localization strings"); + UseLocalization = Dvar::Register("ui_localize", true, Game::DVAR_NONE, "Use localization strings"); // Generate localized entries for custom classes above 10 AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader asset, const std::string& name, bool* /*restrict*/) @@ -344,7 +372,7 @@ namespace Components std::string value = asset.localize->value; Utils::String::Replace(value, "1", std::to_string(i)); // Pretty ugly, but it should work - Localization::Set(key, value); + Set(key, value); } } }); @@ -352,6 +380,6 @@ namespace Components Localization::~Localization() { - Localization::LocalizeMap.clear(); + LocalizeMap.clear(); } } diff --git a/src/Components/Modules/Localization.hpp b/src/Components/Modules/Localization.hpp index 2da7ba8e..c4c6951e 100644 --- a/src/Components/Modules/Localization.hpp +++ b/src/Components/Modules/Localization.hpp @@ -8,15 +8,23 @@ namespace Components Localization(); ~Localization(); - static void Set(const std::string& key, const std::string& value); + static void Set(const std::string& psLocalReference, const std::string& psNewString); static const char* Get(const char* key); + static std::optional PrefixOverride; + static void ParseOutput(const std::function& callback); + private: static std::recursive_mutex LocalizeMutex; static std::unordered_map LocalizeMap; static Dvar::Var UseLocalization; - static void __stdcall SetStringStub(const char* key, const char* value, bool isEnglish); + static std::function ParseCallback; + + static void __stdcall SetStringStub(const char* psLocalReference, const char* psNewString, int bSentenceIsEnglish); + + static void SaveParseOutput(Game::LocalizeEntry* asset); + static void SetCredits(); static const char* SEH_LocalizeTextMessageStub(const char* pszInputBuffer, const char* pszMessageType, Game::msgLocErrType_t errType); diff --git a/src/Components/Modules/Network.cpp b/src/Components/Modules/Network.cpp index 98d3ed0d..9b198743 100644 --- a/src/Components/Modules/Network.cpp +++ b/src/Components/Modules/Network.cpp @@ -16,7 +16,7 @@ namespace Components Game::SockadrToNetadr(addr, &this->address); } - bool Network::Address::operator==(const Network::Address& obj) const + bool Network::Address::operator==(const Address& obj) const { return Game::NET_CompareAdr(this->address, obj.address); } @@ -150,13 +150,11 @@ namespace Components void Network::Send(Game::netsrc_t type, Address target, const std::string& data) { - // NET_OutOfBandPrint only supports non-binary data! - //Game::NET_OutOfBandPrint(type, *target.Get(), data.data()); + // Do not use NET_OutOfBandPrint. It only supports non-binary data! std::string rawData; rawData.append("\xFF\xFF\xFF\xFF", 4); rawData.append(data); - //rawData.append("\0", 1); SendRaw(type, target, rawData); } @@ -170,8 +168,7 @@ namespace Components { if (!target.isValid()) return; - // NET_OutOfBandData doesn't seem to work properly - //Game::NET_OutOfBandData(type, *target.Get(), data.data(), data.size()); + // NET_OutOfBandData doesn't seem to work properly. Do not use it Game::Sys_SendPacket(type, data.size(), data.data(), *target.get()); } @@ -294,7 +291,7 @@ namespace Components const std::string data(reinterpret_cast(message->data) + offset, message->cursize - offset); - Address address_ = address; + auto address_ = Address(address); handler->second(address_, data); return true; } diff --git a/src/Components/Modules/Network.hpp b/src/Components/Modules/Network.hpp index 764170bd..388ba28c 100644 --- a/src/Components/Modules/Network.hpp +++ b/src/Components/Modules/Network.hpp @@ -8,7 +8,7 @@ namespace Components class Address { public: - Address() { setType(Game::netadrtype_t::NA_BAD); } + Address() { setType(Game::NA_BAD); } Address(const std::string& addrString); Address(sockaddr* addr); Address(sockaddr addr) : Address(&addr) {} diff --git a/src/Components/Modules/RCon.cpp b/src/Components/Modules/RCon.cpp index 8023114d..57fa3324 100644 --- a/src/Components/Modules/RCon.cpp +++ b/src/Components/Modules/RCon.cpp @@ -30,7 +30,7 @@ namespace Components return; } - const auto addr = reinterpret_cast(0xA5EA44); + auto* addr = reinterpret_cast(0xA5EA44); if (Password.empty()) { Logger::Print("You need to be logged in and connected to a server!\n"); @@ -57,7 +57,7 @@ namespace Components RconContainer.command = params->get(1); - const auto addr = reinterpret_cast(0xA5EA44); + auto* addr = reinterpret_cast(0xA5EA44); Network::Address target(addr); if (!target.isValid() || target.getIP().full == 0) { diff --git a/src/Components/Modules/ZoneBuilder.cpp b/src/Components/Modules/ZoneBuilder.cpp index a6ddfee0..8b55e5f8 100644 --- a/src/Components/Modules/ZoneBuilder.cpp +++ b/src/Components/Modules/ZoneBuilder.cpp @@ -1,4 +1,5 @@ #include +#include "AssetInterfaces/ILocalizeEntry.hpp" namespace Components { @@ -146,30 +147,44 @@ namespace Components { for (std::size_t i = 0; i < this->dataMap.getRows(); ++i) { - if (this->dataMap.getElementAt(i, 0) != "require") + if (this->dataMap.getElementAt(i, 0) == "require"s) { - if (this->dataMap.getColumns(i) > 2) - { - std::string oldName = this->dataMap.getElementAt(i, 1); - std::string newName = this->dataMap.getElementAt(i, 2); - std::string typeName = this->dataMap.getElementAt(i, 0); - Game::XAssetType type = Game::DB_GetXAssetNameType(typeName.data()); + continue; + } - if (type < Game::XAssetType::ASSET_TYPE_COUNT && type >= 0) - { - this->renameAsset(type, oldName, newName); - } - else - { - Logger::Error(Game::ERR_FATAL, "Unable to rename '{}' to '{}' as the asset type '{}' is invalid!", oldName, newName, typeName); - } - } - - if (!this->loadAssetByName(this->dataMap.getElementAt(i, 0), this->dataMap.getElementAt(i, 1), false)) + if (this->dataMap.getElementAt(i, 0) == "localize"s) + { + const auto filename = this->dataMap.getElementAt(i, 1); + FileSystem::File file(std::format("localizedstrings/{}.str", filename)); + if (file.exists()) { - return false; + Assets::ILocalizeEntry::ParseLocalizedStringsFile(this, filename, file.getName()); + continue; } } + + if (this->dataMap.getColumns(i) > 2) + { + auto oldName = this->dataMap.getElementAt(i, 1); + auto newName = this->dataMap.getElementAt(i, 2); + auto typeName = this->dataMap.getElementAt(i, 0); + auto type = Game::DB_GetXAssetNameType(typeName.data()); + + if (type < Game::XAssetType::ASSET_TYPE_COUNT && type >= 0) + { + this->renameAsset(type, oldName, newName); + } + else + { + Logger::Error(Game::ERR_FATAL, "Unable to rename '{}' to '{}' as the asset type '{}' is invalid!", oldName, newName, typeName); + } + } + + if (!this->loadAssetByName(this->dataMap.getElementAt(i, 0), this->dataMap.getElementAt(i, 1), false)) + { + return false; + } + } return true; @@ -194,10 +209,9 @@ namespace Components { Game::XAssetType type = Game::DB_GetXAssetNameType(typeName.data()); - if (name.find(" ", 0) != std::string::npos) + if (name.find(' ', 0) != std::string::npos) { - Logger::Warning(Game::CON_CHANNEL_DONT_FILTER, - "Asset with name '{}' contains spaces. Check your zone source file to ensure this is correct!\n", name); + Logger::Warning(Game::CON_CHANNEL_DONT_FILTER, "Asset with name '{}' contains spaces. Check your zone source file to ensure this is correct!\n", name); } // Sanitize name for empty assets @@ -408,8 +422,7 @@ namespace Components Utils::IO::WriteFile(outFile, outBuffer); Logger::Print("done.\n"); - Logger::Print("Zone '{}' written with {} assets and {} script strings\n", - outFile, (this->aliasList.size() + this->loadedAssets.size()), this->scriptStrings.size()); + Logger::Print("Zone '{}' written with {} assets and {} script strings\n", outFile, (this->aliasList.size() + this->loadedAssets.size()), this->scriptStrings.size()); } void ZoneBuilder::Zone::saveData() @@ -440,7 +453,7 @@ namespace Components // That's the reason why the count is incremented by 1, if scriptStrings are available. // Write ScriptString pointer table - for (size_t i = 0; i < this->scriptStrings.size(); ++i) + for (std::size_t i = 0; i < this->scriptStrings.size(); ++i) { this->buffer.saveMax(4); } @@ -606,6 +619,11 @@ namespace Components return -1; } + void ZoneBuilder::Zone::addRawAsset(Game::XAssetType type, void* ptr) + { + this->loadedAssets.push_back({type, {ptr}}); + } + // Remap a scriptString to it's corresponding value in the local scriptString table. void ZoneBuilder::Zone::mapScriptString(unsigned short* gameIndex) { @@ -907,11 +925,12 @@ namespace Components void ZoneBuilder::HandleError(Game::errorParm_t code, const char* fmt, ...) { - char buffer[4096] = {0}; - va_list args; - va_start(args, fmt); - vsnprintf_s(buffer, _TRUNCATE, fmt, args); - va_end(args); + char buffer[0x1000]{}; + va_list ap; + + va_start(ap, fmt); + vsnprintf_s(buffer, _TRUNCATE, fmt, ap); + va_end(ap); if (!Flags::HasFlag("stdout")) { diff --git a/src/Components/Modules/ZoneBuilder.hpp b/src/Components/Modules/ZoneBuilder.hpp index 40cc8abd..ea18ed1a 100644 --- a/src/Components/Modules/ZoneBuilder.hpp +++ b/src/Components/Modules/ZoneBuilder.hpp @@ -43,7 +43,7 @@ namespace Components void storePointer(const void* pointer); template - inline T* getPointer(const T* pointer) { return reinterpret_cast(this->safeGetPointer(pointer)); } + T* getPointer(const T* pointer) { return reinterpret_cast(this->safeGetPointer(pointer)); } int findAsset(Game::XAssetType type, std::string name); Game::XAssetHeader findSubAsset(Game::XAssetType type, std::string name); @@ -58,6 +58,7 @@ namespace Components int addScriptString(unsigned short gameIndex); int addScriptString(const std::string& str); int findScriptString(const std::string& str); + void addRawAsset(Game::XAssetType type, void* ptr); void mapScriptString(unsigned short* gameIndex); @@ -101,6 +102,7 @@ namespace Components std::vector markedAssets; std::vector loadedSubAssets; std::vector scriptStrings; + std::map scriptStringMap; std::map renameMap[Game::XAssetType::ASSET_TYPE_COUNT]; diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 9e72deed..6c52b815 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -377,7 +377,7 @@ namespace Game typedef int(*Script_CleanString_t)(char* buffer); extern Script_CleanString_t Script_CleanString; - typedef char*(*SE_Load_t)(const char* file, int Unk); + typedef char*(*SE_Load_t)(const char* psFileName, bool forceEnglish); extern SE_Load_t SE_Load; typedef char*(*SEH_StringEd_GetString_t)(const char* string);