diff --git a/src/Components/Modules/AssetInterfaces/IMenuList.cpp b/src/Components/Modules/AssetInterfaces/IMenuList.cpp index a66ddee3..f8a25f96 100644 --- a/src/Components/Modules/AssetInterfaces/IMenuList.cpp +++ b/src/Components/Modules/AssetInterfaces/IMenuList.cpp @@ -2,6 +2,16 @@ namespace Assets { + void IMenuList::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) + { + header->menuList = Components::Menus::LoadCustomMenuList(name, builder->getAllocator()); + + for (int i = 0; i < header->menuList->menuCount; i++) + { + ImenuDef_t::LoadedMenus[header->menuList->menus[i]->window.name] = header->menuList->menus[i]; + } + + } void IMenuList::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) { Game::MenuList *asset = header.menuList; diff --git a/src/Components/Modules/AssetInterfaces/IMenuList.hpp b/src/Components/Modules/AssetInterfaces/IMenuList.hpp index 005fa820..fd0a72f9 100644 --- a/src/Components/Modules/AssetInterfaces/IMenuList.hpp +++ b/src/Components/Modules/AssetInterfaces/IMenuList.hpp @@ -9,6 +9,6 @@ namespace Assets virtual void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; virtual void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; - // virtual void load(Game::XAssetHeader* header, std::string name, Components::ZoneBuilder::Zone* builder) override; + virtual void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override; }; } diff --git a/src/Components/Modules/AssetInterfaces/ImenuDef_t.cpp b/src/Components/Modules/AssetInterfaces/ImenuDef_t.cpp index e9bd1eb3..2aa142f4 100644 --- a/src/Components/Modules/AssetInterfaces/ImenuDef_t.cpp +++ b/src/Components/Modules/AssetInterfaces/ImenuDef_t.cpp @@ -2,6 +2,22 @@ namespace Assets { + + std::unordered_map ImenuDef_t::LoadedMenus; + + void ImenuDef_t::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) + { + // search menus loaded by a menufile + for (auto i = ImenuDef_t::LoadedMenus.begin(); i != ImenuDef_t::LoadedMenus.end(); ++i) + { + if (i->first == name) { + header->menu = i->second; + return; + } + } + } + + void ImenuDef_t::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) { Game::menuDef_t *asset = header.menu; diff --git a/src/Components/Modules/AssetInterfaces/ImenuDef_t.hpp b/src/Components/Modules/AssetInterfaces/ImenuDef_t.hpp index e62b15ea..b9e38cc4 100644 --- a/src/Components/Modules/AssetInterfaces/ImenuDef_t.hpp +++ b/src/Components/Modules/AssetInterfaces/ImenuDef_t.hpp @@ -9,7 +9,9 @@ namespace Assets virtual void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; virtual void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; - // virtual void load(Game::XAssetHeader* header, std::string name, Components::ZoneBuilder::Zone* builder) override; + virtual void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override; + + static std::unordered_map LoadedMenus; private: template void save_windowDef_t(Game::windowDef_t* asset, T* dest, Components::ZoneBuilder::Zone* builder) diff --git a/src/Components/Modules/Menus.cpp b/src/Components/Modules/Menus.cpp index cfd06bb3..9b9ff7e4 100644 --- a/src/Components/Modules/Menus.cpp +++ b/src/Components/Modules/Menus.cpp @@ -173,6 +173,69 @@ namespace Components return menu; } + Game::MenuList* Menus::LoadCustomMenuList(const std::string& menu, Utils::Memory::Allocator* allocator) + { + std::vector> menus; + FileSystem::File menuFile(menu); + + if (!menuFile.exists()) return nullptr; + + Game::pc_token_t token; + int handle = Menus::LoadMenuSource(menu, menuFile.getBuffer()); + + if (Menus::IsValidSourceHandle(handle)) + { + while (true) + { + ZeroMemory(&token, sizeof(token)); + + if (!Game::PC_ReadTokenHandle(handle, &token) || token.string[0] == '}') + { + break; + } + + if (!_stricmp(token.string, "loadmenu")) + { + Game::PC_ReadTokenHandle(handle, &token); + + Utils::Merge(&menus, Menus::LoadMenu(Utils::String::VA("ui_mp\\%s.menu", token.string))); + } + + if (!_stricmp(token.string, "menudef")) + { + Game::menuDef_t* menudef = Menus::ParseMenu(handle); + if (menudef) menus.push_back({ true, menudef }); // Custom menu + } + } + + Menus::FreeMenuSource(handle); + } + + if (menus.empty()) return nullptr; + + // Allocate new menu list + Game::MenuList* list = allocator->allocate(); + if (!list) return nullptr; + + list->menus = allocator->allocateArray(menus.size()); + if (!list->menus) + { + allocator->free(list); + return nullptr; + } + + list->name = allocator->duplicateString(menu); + list->menuCount = menus.size(); + + // Copy new menus + for (unsigned int i = 0; i < menus.size(); ++i) + { + list->menus[i] = menus[i].second; + } + + return list; + } + std::vector> Menus::LoadMenu(const std::string& menu) { std::vector> menus; @@ -580,12 +643,12 @@ namespace Components Menus::MenuList.clear(); } - Game::XAssetHeader Menus::MenuLoad(Game::XAssetType /*type*/, const std::string& filename) + Game::XAssetHeader Menus::MenuFindHook(Game::XAssetType /*type*/, const std::string& filename) { return { Game::Menus_FindByName(Game::uiContext, filename.data()) }; } - Game::XAssetHeader Menus::MenuFileLoad(Game::XAssetType type, const std::string& filename) + Game::XAssetHeader Menus::MenuListFindHook(Game::XAssetType type, const std::string& filename) { Game::XAssetHeader header = { nullptr }; @@ -694,6 +757,54 @@ namespace Components Menus::CustomMenus.push_back(menu); } + void Menus::RegisterCustomMenusHook() + { + Game::UiContext* uiInfoArray = (Game::UiContext*)0x62E2858; + Game::MenuList list; + + Utils::Hook::Call(0x401700)(); // call original load functions + Game::XAssetHeader header = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MENULIST, "ui_mp/iw4x.txt"); + if (header.data && !(header.menuList->menuCount == 1 && !_stricmp("default_menu", header.menuList->menus[0]->window.name))) + { + // Utils::Hook::Call(0x401700)(uiInfoArray, header.menuList, 1); // add loaded menus + std::memcpy(&list, header.data, sizeof(Game::MenuList)); + + for (int i = 0; i < uiInfoArray->menuCount; i++) + { + for (int j = 0; j < list.menuCount; j++) + { + if (!list.menus[j]) continue; // skip already used entries + if (!stricmp(list.menus[j]->window.name, uiInfoArray->Menus[i]->window.name)) + { + uiInfoArray->Menus[i] = list.menus[j]; // overwrite UiContext pointer + list.menus[j] = nullptr; // clear entries that already exist so we don't add them later + } + } + } + + for (int i = 0; i < list.menuCount; i++) + { + if (list.menus[i]) + { + uiInfoArray->Menus[uiInfoArray->menuCount++] = list.menus[i]; + } + } + } + + for (int i = 0; i < uiInfoArray->menuCount; i++) + { + OutputDebugStringA(Utils::String::VA("%s\n", uiInfoArray->Menus[i]->window.name)); + } + + /* + header = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MENULIST, "ui_mp/mod.txt"); + if (header.data && !(header.menuList->menuCount == 1 && !_stricmp("default_menu", header.menuList->menus[0]->window.name))) + { + Utils::Hook::Call(0x401700)(uiInfoArray, header.menuList, 1); // add loaded menus + } + */ + } + Menus::Menus() { if (Dedicated::IsEnabled()) return; @@ -702,12 +813,15 @@ namespace Components Menus::FreeEverything(); // Intercept asset finding - AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_MENU, Menus::MenuLoad); - AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_MENULIST, Menus::MenuFileLoad); + AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_MENU, Menus::MenuFindHook); + AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_MENULIST, Menus::MenuListFindHook); // Don't open connect menu //Utils::Hook::Nop(0x428E48, 5); + // register custom menufiles if they exist + Utils::Hook(0x4A58C3, Menus::RegisterCustomMenusHook, HOOK_CALL).install()->quick(); + // Use the connect menu open call to update server motds Utils::Hook(0x428E48, []() { @@ -779,6 +893,7 @@ namespace Components }); // Define custom menus here + /* Menus::Add("ui_mp/changelog.menu"); Menus::Add("ui_mp/theater_menu.menu"); Menus::Add("ui_mp/pc_options_multi.menu"); @@ -793,6 +908,7 @@ namespace Components Menus::Add("ui_mp/pc_store.menu"); Menus::Add("ui_mp/iw4x_credits.menu"); Menus::Add("ui_mp/resetclass.menu"); + */ } Menus::~Menus() diff --git a/src/Components/Modules/Menus.hpp b/src/Components/Modules/Menus.hpp index efc051e2..151b5e0a 100644 --- a/src/Components/Modules/Menus.hpp +++ b/src/Components/Modules/Menus.hpp @@ -15,13 +15,15 @@ namespace Components static void Add(const std::string& menu); + static Game::MenuList* Menus::LoadCustomMenuList(const std::string& menu, Utils::Memory::Allocator* allocator); + private: static std::unordered_map MenuList; static std::unordered_map MenuListList; static std::vector CustomMenus; - static Game::XAssetHeader MenuLoad(Game::XAssetType type, const std::string& filename); - static Game::XAssetHeader MenuFileLoad(Game::XAssetType type, const std::string& filename); + static Game::XAssetHeader MenuFindHook(Game::XAssetType type, const std::string& filename); + static Game::XAssetHeader MenuListFindHook(Game::XAssetType type, const std::string& filename); static Game::MenuList* LoadMenuList(Game::MenuList* menuList); static Game::MenuList* LoadScriptMenu(const char* menu); @@ -53,6 +55,8 @@ namespace Components static void RemoveMenuFromContext(Game::UiContext *dc, Game::menuDef_t *menu); + static void RegisterCustomMenusHook(); + // Ugly! static int KeywordHash(char* key); }; diff --git a/src/Components/Modules/ZoneBuilder.cpp b/src/Components/Modules/ZoneBuilder.cpp index dd07177c..ece47840 100644 --- a/src/Components/Modules/ZoneBuilder.cpp +++ b/src/Components/Modules/ZoneBuilder.cpp @@ -871,6 +871,10 @@ namespace Components // defaults need to load before we do this Utils::Hook::Call(0x4E1F30)(); // G_SetupWeaponDef + Utils::Hook::Call(0x4454C0)(); // Item_SetupKeywordHash (for loading menus) + Utils::Hook::Call(0x501BC0)(); // Menu_SetupKeywordHash (for loading menus) + Utils::Hook::Call(0x4A1280)(); // something related to uiInfoArray + Utils::Hook::Call(0x464A90)(GetCommandLineA()); // Com_ParseCommandLine Utils::Hook::Call(0x60C3D0)(); // Com_AddStartupCommands