iw4x-client/src/Components/Modules/Localization.cpp

454 lines
10 KiB
C++
Raw Normal View History

2022-02-27 07:53:44 -05:00
#include <STDInclude.hpp>
2017-01-19 16:23:59 -05:00
namespace Components
{
std::recursive_mutex Localization::LocalizeMutex;
Dvar::Var Localization::UseLocalization;
std::unordered_map<std::string, Game::LocalizeEntry*> Localization::LocalizeMap;
std::unordered_map<std::string, Game::LocalizeEntry*> Localization::TempLocalizeMap;
2018-12-17 08:29:18 -05:00
void Localization::Set(const std::string& key, const std::string& value)
2017-01-19 16:23:59 -05:00
{
2022-07-07 09:32:18 -04:00
std::lock_guard _(Localization::LocalizeMutex);
2017-06-02 09:36:20 -04:00
Utils::Memory::Allocator* allocator = Utils::Memory::GetAllocator();
2017-01-19 16:23:59 -05:00
2022-05-31 12:38:09 -04:00
if (Localization::LocalizeMap.contains(key))
2017-01-19 16:23:59 -05:00
{
Game::LocalizeEntry* entry = Localization::LocalizeMap[key];
char* newStaticValue = allocator->duplicateString(value);
2017-01-19 16:23:59 -05:00
if (!newStaticValue) return;
if (entry->value) allocator->free(entry->value);
2017-01-19 16:23:59 -05:00
entry->value = newStaticValue;
return;
}
Game::LocalizeEntry* entry = allocator->allocate<Game::LocalizeEntry>();
2017-01-19 16:23:59 -05:00
if (!entry) return;
entry->name = allocator->duplicateString(key);
2017-01-19 16:23:59 -05:00
if (!entry->name)
{
allocator->free(entry);
2017-01-19 16:23:59 -05:00
return;
}
entry->value = allocator->duplicateString(value);
2017-01-19 16:23:59 -05:00
if (!entry->value)
{
allocator->free(entry->name);
allocator->free(entry);
2017-01-19 16:23:59 -05:00
return;
}
Localization::LocalizeMap[key] = entry;
}
const char* Localization::Get(const char* key)
{
if (!Localization::UseLocalization.get<bool>()) return key;
Game::LocalizeEntry* entry = nullptr;
{
2022-07-07 09:32:18 -04:00
std::lock_guard _(Localization::LocalizeMutex);
2022-05-31 12:38:09 -04:00
if (Localization::TempLocalizeMap.contains(key))
{
entry = Localization::TempLocalizeMap[key];
}
2022-05-31 12:38:09 -04:00
else if (Localization::LocalizeMap.contains(key))
{
entry = Localization::LocalizeMap[key];
}
2017-01-19 16:23:59 -05:00
}
if (!entry || !entry->value)
{
entry = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_LOCALIZE_ENTRY, key).localize;
}
if (entry && entry->value)
{
return entry->value;
}
return key;
}
2018-12-17 08:29:18 -05:00
void Localization::SetTemp(const std::string& key, const std::string& value)
2017-01-19 16:23:59 -05:00
{
2022-07-07 09:32:18 -04:00
std::lock_guard _(Localization::LocalizeMutex);
2017-06-02 09:36:20 -04:00
Utils::Memory::Allocator* allocator = Utils::Memory::GetAllocator();
2017-01-19 16:23:59 -05:00
2022-05-31 12:38:09 -04:00
if (Localization::TempLocalizeMap.contains(key))
2017-01-19 16:23:59 -05:00
{
Game::LocalizeEntry* entry = Localization::TempLocalizeMap[key];
if (entry->value) allocator->free(entry->value);
entry->value = allocator->duplicateString(value);
2017-01-19 16:23:59 -05:00
}
else
{
Game::LocalizeEntry* entry = allocator->allocate<Game::LocalizeEntry>();
2017-01-19 16:23:59 -05:00
if (!entry) return;
entry->name = allocator->duplicateString(key);
2017-01-19 16:23:59 -05:00
if (!entry->name)
{
allocator->free(entry);
2017-01-19 16:23:59 -05:00
return;
}
entry->value = allocator->duplicateString(value);
2017-01-19 16:23:59 -05:00
if (!entry->value)
{
allocator->free(entry->name);
allocator->free(entry);
2017-01-19 16:23:59 -05:00
return;
}
Localization::TempLocalizeMap[key] = entry;
}
}
void Localization::ClearTemp()
{
2022-07-07 09:32:18 -04:00
std::lock_guard _(Localization::LocalizeMutex);
2017-06-02 09:36:20 -04:00
Utils::Memory::Allocator* allocator = Utils::Memory::GetAllocator();
2017-01-19 16:23:59 -05:00
for (auto i = Localization::TempLocalizeMap.begin(); i != Localization::TempLocalizeMap.end(); ++i)
{
if (i->second)
{
if (i->second->name) allocator->free(i->second->name);
if (i->second->value) allocator->free(i->second->value);
allocator->free(i->second);
2017-01-19 16:23:59 -05:00
}
}
Localization::TempLocalizeMap.clear();
}
void __stdcall Localization::SetStringStub(const char* key, const char* value, bool /*isEnglish*/)
{
Localization::Set(key, value);
}
void Localization::LoadLanguageStrings()
{
//if (ZoneBuilder::IsEnabled())
{
if (FileSystem::File(Utils::String::VA("localizedstrings/iw4x_%s.str", Game::Win_GetLanguage())).exists())
{
Game::SE_Load(Utils::String::VA("localizedstrings/iw4x_%s.str", Game::Win_GetLanguage()), 0);
}
else if (FileSystem::File("localizedstrings/iw4x_english.str").exists())
{
Game::SE_Load("localizedstrings/iw4x_english.str", 0);
}
}
}
__declspec(naked) void Localization::SELoadLanguageStub()
{
__asm
{
pushad
call Localization::LoadLanguageStrings
popad
push 629E20h
retn
}
}
2017-02-08 10:25:48 -05:00
void Localization::SetCredits()
{
static const char* staff[] =
{
2022-07-07 09:32:18 -04:00
"Snake",
2017-02-08 10:25:48 -05:00
"/dev/../",
"/dev/console",
"/dev/full",
"/dev/sdb",
"/dev/sr0",
2021-02-07 17:26:47 -05:00
"/dev/tty0",
2017-02-08 10:25:48 -05:00
"/dev/urandom",
2022-07-07 09:32:18 -04:00
"Dss0",
"FutureRave",
"H3X1C",
"Homura",
"Laupetin",
"Louvenarde",
"lsb_release -a",
"quaK",
2017-02-08 10:25:48 -05:00
};
static const char* contributors[] =
{
"a231",
2017-02-08 10:25:48 -05:00
"AmateurHailbut",
"Aoki",
2021-10-16 16:21:32 -04:00
"Chase",
2017-02-08 10:25:48 -05:00
"civil",
"Dasfonia",
"Deity",
2017-02-08 10:25:48 -05:00
"Dizzy",
"HardNougat",
2020-12-22 15:12:23 -05:00
"INeedGames",
2022-07-07 09:32:18 -04:00
"JTAG",
2017-02-08 10:25:48 -05:00
"Killera",
"Lithium",
2020-12-22 15:12:23 -05:00
"OneFourOne",
"RaidMax",
2017-02-08 10:25:48 -05:00
"Revo",
"RezTech",
"Shadow the Hedgehog",
"Slykuiper",
2017-02-08 10:25:48 -05:00
"st0rm",
"VVLNT",
2022-07-07 09:32:18 -04:00
"X3RX35",
2017-02-08 10:25:48 -05:00
};
static const char* specials[] =
{
"NTAuthority",
"aerosoul94",
2017-02-08 13:27:35 -05:00
"ReactIW4",
"IW4Play",
2020-12-22 15:12:23 -05:00
"V2",
2017-07-05 15:08:34 -04:00
"luckyy"
2017-02-08 10:25:48 -05:00
};
std::string credits = "^2The IW4x Team:^7\n";
2022-07-07 09:32:18 -04:00
for (std::size_t i = 0; i < ARRAYSIZE(staff); ++i)
2017-02-08 10:25:48 -05:00
{
credits.append(staff[i]);
credits.append("\n");
}
credits.append("\n^3Contributors:^7\n");
2022-07-07 09:32:18 -04:00
for (std::size_t i = 0; i < ARRAYSIZE(contributors); ++i)
2017-02-08 10:25:48 -05:00
{
credits.append(contributors[i]);
credits.append("\n");
}
credits.append("\n^5Special thanks to:^7\n");
2022-07-07 09:32:18 -04:00
for (std::size_t i = 0; i < ARRAYSIZE(specials); ++i)
2017-02-08 10:25:48 -05:00
{
credits.append(specials[i]);
credits.append("\n");
}
// I have no idea why, but the last 2 lines are invisible!
credits.append("-\n-");
Localization::Set("IW4X_CREDITS", credits);
}
2022-08-31 16:21:26 -04:00
const char* Localization::SEH_LocalizeTextMessageStub(const char* pszInputBuffer, const char* pszMessageType, Game::msgLocErrType_t errType)
{
constexpr auto szStringCount = 10;
constexpr auto szStringSize = 1024;
char szInsertBuf[szStringSize];
char szTokenBuf[szStringSize];
static thread_local int iCurrString;
static thread_local char szStrings[szStringCount][szStringSize];
iCurrString = (iCurrString + 1) % szStringCount;
std::memset(szStrings[iCurrString], 0, sizeof(szStrings[0]));
auto* pszString = szStrings[iCurrString];
auto iLen = 0;
auto bLocOn = 1;
auto bInsertEnabled = 1;
auto iInsertLevel = 0;
auto insertIndex = 1;
auto bLocSkipped = 0;
const auto* pszTokenStart = pszInputBuffer;
const auto* pszIn = pszInputBuffer;
auto i = 0;
while (*pszTokenStart)
{
if (*pszIn && *pszIn != '\x14' && *pszIn != '\x15' && *pszIn != '\x16')
{
++pszIn;
continue;
}
if (pszIn > pszTokenStart)
{
auto iTokenLen = pszIn - pszTokenStart;
strncpy_s(szTokenBuf, pszTokenStart, pszIn - pszTokenStart);
if (bLocOn)
{
if (!Game::SEH_GetLocalizedTokenReference(szTokenBuf, szTokenBuf, pszMessageType, errType))
{
return nullptr;
}
iTokenLen = &szTokenBuf[std::strlen(szTokenBuf) + 1] - &szTokenBuf[1];
}
if (iTokenLen + iLen >= szStringSize)
{
return nullptr;
}
for (i = 0; i < iTokenLen - 2; ++i)
{
if (!std::strncmp(&szTokenBuf[i], "&&", 2) && std::isdigit(szTokenBuf[i + 2]))
{
if (bInsertEnabled)
{
++iInsertLevel;
}
else
{
szTokenBuf[i] = '\x16';
bLocSkipped = 1;
}
}
}
if (iInsertLevel <= 0 || iLen <= 0)
{
strncpy_s(&pszString[iLen], szStringSize - iLen, szTokenBuf, sizeof(szTokenBuf));
}
else
{
for (i = 0; i < iLen - 2; ++i)
{
if (!std::strncmp(&pszString[i], "&&", 2) && std::isdigit(pszString[i + 2]))
{
const auto digit = pszString[i + 2] - 48;
if (!digit)
{
Game::Com_Printf(Game::CON_CHANNEL_SYSTEM, "%s cannot have &&0 as conversion format: \"%s\"\n", pszMessageType, pszInputBuffer);
}
if (digit == insertIndex)
{
strncpy_s(szInsertBuf, &pszString[i + 3], szStringSize - (i + 3));
pszString[i] = 0;
++insertIndex;
break;
}
}
}
strncpy_s(&pszString[i], szStringSize - i, szTokenBuf, sizeof(szTokenBuf));
if ((iTokenLen + i) < szStringSize)
{
strncpy_s(&pszString[iTokenLen + i], szStringSize - (iTokenLen + i), szInsertBuf, sizeof(szInsertBuf));
}
iLen -= 3;
--iInsertLevel;
}
iLen += iTokenLen;
}
bInsertEnabled = 1;
if (*pszIn == '\x14')
{
bLocOn = 1;
++pszIn;
}
else if (*pszIn == '\x15')
{
bLocOn = 0;
++pszIn;
}
if (*pszIn == '\x16')
{
bInsertEnabled = 0;
++pszIn;
}
pszTokenStart = pszIn;
}
if (bLocSkipped)
{
for (i = 0; i < iLen; ++i)
{
if (pszString[i] == '\x16')
{
pszString[i] = '%';
}
}
}
return pszString;
}
2017-01-19 16:23:59 -05:00
Localization::Localization()
{
2017-02-08 10:25:48 -05:00
Localization::SetCredits();
2018-12-17 08:29:18 -05:00
AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_LOCALIZE_ENTRY, [](Game::XAssetType, const std::string& filename)
2017-01-19 16:23:59 -05:00
{
Game::XAssetHeader header = { nullptr };
2022-07-07 09:32:18 -04:00
std::lock_guard _(Localization::LocalizeMutex);
2017-01-19 16:23:59 -05:00
2022-05-31 12:38:09 -04:00
if (Localization::TempLocalizeMap.contains(filename))
2017-01-19 16:23:59 -05:00
{
header.localize = Localization::TempLocalizeMap[filename];
}
2022-05-31 12:38:09 -04:00
else if (Localization::LocalizeMap.contains(filename))
2017-01-19 16:23:59 -05:00
{
header.localize = Localization::LocalizeMap[filename];
}
return header;
});
// Resolving hook
Utils::Hook(0x629B90, Localization::Get, HOOK_JUMP).install()->quick();
// Set loading entry point
Utils::Hook(0x41D859, Localization::SELoadLanguageStub, HOOK_CALL).install()->quick();
// Overwrite SetString
Utils::Hook(0x4CE5EE, Localization::SetStringStub, HOOK_CALL).install()->quick();
2022-08-31 16:21:26 -04:00
Utils::Hook(0x49D4A0, Localization::SEH_LocalizeTextMessageStub, HOOK_JUMP).install()->quick();
Utils::Hook::Nop(0x49D4A5, 1);
Localization::UseLocalization = Dvar::Register<bool>("ui_localize", true, Game::DVAR_NONE, "Use localization strings");
// Generate localized entries for custom classes above 10
2018-12-17 08:29:18 -05:00
AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader asset, const std::string& name, bool* /*restrict*/)
{
if (type != Game::XAssetType::ASSET_TYPE_LOCALIZE_ENTRY) return;
if (name == "CLASS_SLOT1"s)
{
for (int i = 11; i <= NUM_CUSTOM_CLASSES; ++i)
{
std::string key = Utils::String::VA("CLASS_SLOT%i", i);
std::string value = asset.localize->value;
Utils::String::Replace(value, "1", Utils::String::VA("%i", i)); // Pretty ugly, but it should work
Localization::Set(key, value);
}
}
});
2017-01-19 16:23:59 -05:00
}
Localization::~Localization()
{
Localization::ClearTemp();
Localization::LocalizeMap.clear();
}
}