From 85f0ebdc5dbaf8a2842ef136220700b814922cf3 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Thu, 24 Dec 2015 12:00:29 +0100 Subject: [PATCH] Implement custom menus (with support for multiple menus in one menufile!) Still contains some memory leaks! --- iw4/Components/Menus.cpp | 200 +++++++++++++++++++++++++++++++++++---- iw4/Components/Menus.hpp | 15 ++- iw4/Game/Functions.cpp | 3 + iw4/Game/Functions.hpp | 6 ++ 4 files changed, 202 insertions(+), 22 deletions(-) diff --git a/iw4/Components/Menus.cpp b/iw4/Components/Menus.cpp index 05d48cd8..750f940e 100644 --- a/iw4/Components/Menus.cpp +++ b/iw4/Components/Menus.cpp @@ -24,11 +24,11 @@ namespace Components return i; } - Game::script_t* Menus::LoadMenuScript(std::string buffer) + Game::script_t* Menus::LoadMenuScript(const char* name, std::string buffer) { Game::script_t* script = Game::Script_Alloc(sizeof(Game::script_t) + 1 + buffer.length()); - strcpy_s(script->filename, sizeof(script->filename), "script_t"); + strcpy_s(script->filename, sizeof(script->filename), name); script->buffer = (char*)(script + 1); *((char*)(script + 1) + buffer.length()) = '\0'; @@ -51,13 +51,13 @@ namespace Components return script; } - int Menus::LoadMenuSource(std::string buffer) + int Menus::LoadMenuSource(const char* name, std::string buffer) { int handle = Menus::ReserveSourceHandle(); - if (!handle) return 0; // No free source slot! + if (!Menus::IsValidSourceHandle(handle)) return 0; // No free source slot! Game::source_t *source = nullptr; - Game::script_t *script = Menus::LoadMenuScript(buffer); + Game::script_t *script = Menus::LoadMenuScript(name, buffer); if (!script) { @@ -82,38 +82,193 @@ namespace Components return handle; } - Game::menuDef_t* Menus::LoadMenu(Game::menuDef_t* menudef) + bool Menus::IsValidSourceHandle(int handle) { + return (handle > 0 && handle < MAX_SOURCEFILES && Game::sourceFiles[handle]); + } + + int Menus::KeywordHash(char* key) + { + // patch this function on-the-fly, as it's some ugly C. + Utils::Hook::Set(0x63FE9E, 3523); + Utils::Hook::Set(0x63FECB, 0x7F); + + int var = 0x63FE90; + __asm + { + mov eax, key + call var + mov var, eax + } + + Utils::Hook::Set(0x63FE9E, 531); + Utils::Hook::Set(0x63FECB, 0x1FF); + + return var; + } + + Game::menuDef_t* Menus::ParseMenu(int handle) + { + Game::menuDef_t* menu = (Game::menuDef_t*)calloc(1, 2048); // FIXME: tentative size + menu->items = (Game::itemDef_t**)calloc(512, sizeof(Game::itemDef_t*)); + Menus::MenuList.push_back(menu); + + Game::pc_token_t token; + Game::keywordHash_t *key; + + if (!Menus::ReadToken(handle, &token) || token.string[0] != '{') + { + return menu; + } + + while (true) + { + ZeroMemory(&token, sizeof(token)); + + if (!Menus::ReadToken(handle, &token)) + { + Game::PC_SourceError(handle, "end of file inside menu\n"); + break; // Fail + } + + if (*token.string == '}') + { + break; // Success + } + + int idx = Menus::KeywordHash(token.string); + + key = Game::menuParseKeywordHash[idx]; + + if (!key) + { + Game::PC_SourceError(handle, "unknown menu keyword %s", token.string); + continue; + } + + if (!key->func((Game::itemDef_t*)menu, handle)) + { + Game::PC_SourceError(handle, "couldn't parse menu keyword %s", token.string); + break; // Fail + } + } + + return menu; + } + + int Menus::ReadToken(int handle, Game::pc_token_t *pc_token) + { + Game::token_t token; + int ret; + + if (!Menus::IsValidSourceHandle(handle)) return 0; + + ret = Game::PC_ReadToken(Game::sourceFiles[handle], &token); + strcpy(pc_token->string, token.string); + pc_token->type = token.type; + pc_token->subtype = token.subtype; + pc_token->intvalue = token.intvalue; + pc_token->floatvalue = (float)token.floatvalue; + + if (pc_token->type == TT_STRING) + { + // StripDoubleQuotes + char *string = pc_token->string; + if (*string == '\"') + { + strcpy(string, string + 1); + } + if (string[strlen(string) - 1] == '\"') + { + string[strlen(string) - 1] = '\0'; + } + } + + return ret; + } + + std::vector Menus::LoadMenu(Game::menuDef_t* menudef) + { + std::vector menus; FileSystem::File menuFile(Utils::VA("ui_mp\\%s.menu", menudef->window.name)); if (menuFile.Exists()) { - int handle = Menus::LoadMenuSource(menuFile.GetBuffer()); + Game::pc_token_t token; + int handle = Menus::LoadMenuSource(menudef->window.name, menuFile.GetBuffer()); + if (!Menus::IsValidSourceHandle(handle)) + { + menus.push_back(menudef); + return menus; + } - // TODO: Parse menu! + while (true) + { + ZeroMemory(&token, sizeof(token)); + + if (!Menus::ReadToken(handle, &token) || token.string[0] == '}') + { + break; + } + + if (!_stricmp(token.string, "loadmenu")) + { + Menus::ReadToken(handle, &token); + + // Ugly, but does the job ;) + Game::menuDef_t _temp; + _temp.window.name = token.string; + + std::vector newMenus = Menus::LoadMenu(&_temp); + + for (auto newMenu : newMenus) + { + menus.push_back(newMenu); + } + } + + if (!_stricmp(token.string, "menudef")) + { + menus.push_back(Menus::ParseMenu(handle)); + } + } Menus::FreeMenuSource(handle); } - return menudef; + if (!menus.size()) + { + menus.push_back(menudef); + } + + return menus; } Game::MenuList* Menus::LoadMenuList(Game::MenuList* menuList) { - bool NewMenuLoaded = false; + std::vector menus; + for (int i = 0; i < menuList->menuCount; i++) + { + std::vector newMenus = Menus::LoadMenu(menuList->menus[i]); + + for (auto newMenu : newMenus) + { + menus.push_back(newMenu); + } + } + + // Allocate new menu list Game::MenuList* newList = (Game::MenuList*)calloc(1, sizeof(Game::MenuList)); newList->name = _strdup(menuList->name); - newList->menus = (Game::menuDef_t **)calloc(menuList->menuCount, sizeof(Game::menuDef_t *)); - newList->menuCount = menuList->menuCount; + newList->menus = (Game::menuDef_t **)calloc(menus.size(), sizeof(Game::menuDef_t *)); + newList->menuCount = menus.size(); + + // Copy new menus + memcpy(newList->menus, menus.data(), menus.size() * sizeof(Game::menuDef_t *)); Menus::MenuListList.push_back(newList); - for (int i = 0; i < newList->menuCount; i++) - { - newList->menus[i] = Menus::LoadMenu(menuList->menus[i]); - } - return newList; } @@ -124,7 +279,7 @@ namespace Components void Menus::FreeMenuSource(int handle) { - if (!Game::sourceFiles[handle]) return; + if (!Menus::IsValidSourceHandle(handle)) return; Game::script_t *script; // Game::token_t *token; @@ -155,7 +310,8 @@ namespace Components void Menus::FreeMenu(Game::menuDef_t* menudef) { - // + if (menudef->items) free(menudef->items); + free(menudef); } void Menus::FreeMenuList(Game::MenuList* menuList) @@ -222,6 +378,12 @@ namespace Components { AssetHandler::On(Game::XAssetType::ASSET_TYPE_MENUFILE, Menus::MenuFileLoad); + // disable the 2 new tokens in ItemParse_rect + Utils::Hook::Set(0x640693, 0xEB); + + // don't load ASSET_TYPE_MENU assets for every menu (might cause patch menus to fail) + Utils::Hook::Nop(0x453406, 5); + Command::Add("openmenu", [] (Command::Params params) { if (params.Length() != 2) diff --git a/iw4/Components/Menus.hpp b/iw4/Components/Menus.hpp index 391e93fc..2bd50ce6 100644 --- a/iw4/Components/Menus.hpp +++ b/iw4/Components/Menus.hpp @@ -15,11 +15,17 @@ namespace Components static Game::XAssetHeader MenuFileLoad(Game::XAssetType type, const char* filename); static Game::MenuList* LoadMenuList(Game::MenuList* menuList); - static Game::menuDef_t* LoadMenu(Game::menuDef_t* menudef); + static std::vector LoadMenu(Game::menuDef_t* menudef); + + static Game::script_t* LoadMenuScript(const char* name, std::string buffer); + static int LoadMenuSource(const char* name, std::string buffer); - static Game::script_t* LoadMenuScript(std::string buffer); - static int LoadMenuSource(std::string buffer); static int ReserveSourceHandle(); + static bool IsValidSourceHandle(int handle); + + static int ReadToken(int handle, Game::pc_token_t *pc_token); + + static Game::menuDef_t* ParseMenu(int handle); static void FreeMenuScript(Game::script_t* script); static void FreeMenuSource(int handle); @@ -28,5 +34,8 @@ namespace Components static void FreeMenu(Game::menuDef_t* menudef); static void FreeEverything(); + + // Ugly! + static int KeywordHash(char* key); }; } diff --git a/iw4/Game/Functions.cpp b/iw4/Game/Functions.cpp index c49aa5ba..ca15dd6c 100644 --- a/iw4/Game/Functions.cpp +++ b/iw4/Game/Functions.cpp @@ -36,6 +36,9 @@ namespace Game LoadModdableRawfile_t LoadModdableRawfile = (LoadModdableRawfile_t)0x61ABC0; + PC_ReadToken_t PC_ReadToken = (PC_ReadToken_t)0x4ACCD0; + PC_SourceError_t PC_SourceError = (PC_SourceError_t)0x467A00; + Script_Alloc_t Script_Alloc = (Script_Alloc_t)0x422E70; Script_SetupTokens_t Script_SetupTokens = (Script_SetupTokens_t)0x4E6950; Script_CleanString_t Script_CleanString = (Script_CleanString_t)0x498220; diff --git a/iw4/Game/Functions.hpp b/iw4/Game/Functions.hpp index 524ab083..3b6cd311 100644 --- a/iw4/Game/Functions.hpp +++ b/iw4/Game/Functions.hpp @@ -72,6 +72,12 @@ namespace Game typedef void* (__cdecl * LoadModdableRawfile_t)(int a1, const char* filename); extern LoadModdableRawfile_t LoadModdableRawfile; + typedef int(__cdecl * PC_ReadToken_t)(source_t*, token_t*); + extern PC_ReadToken_t PC_ReadToken; + + typedef void(__cdecl * PC_SourceError_t)(int, const char*, ...); + extern PC_SourceError_t PC_SourceError; + typedef script_t* (__cdecl * Script_Alloc_t)(int length); extern Script_Alloc_t Script_Alloc;