diff --git a/src/Common/Game/IW3/IW3_Assets.h b/src/Common/Game/IW3/IW3_Assets.h index 4b7be821..02a0753f 100644 --- a/src/Common/Game/IW3/IW3_Assets.h +++ b/src/Common/Game/IW3/IW3_Assets.h @@ -2528,18 +2528,18 @@ namespace IW3 enum WindowDefDynamicFlag : unsigned int { - WINDOW_FLAG_1 = 0x1, + WINDOW_FLAG_MOUSE_OVER = 0x1, WINDOW_FLAG_FOCUSED = 0x2, WINDOW_FLAG_VISIBLE = 0x4, WINDOW_FLAG_FADING_OUT = 0x10, WINDOW_FLAG_FADING_IN = 0x20, - WINDOW_FLAG_HOVERED = 0x40, - WINDOW_FLAG_LISTBOX_HOVER_100 = 0x100, - WINDOW_FLAG_LISTBOX_HOVER_200 = 0x200, - WINDOW_FLAG_LISTBOX_HOVER_400 = 0x400, - WINDOW_FLAG_LISTBOX_HOVER_800 = 0x800, - WINDOW_FLAG_LISTBOX_HOVER_1000 = 0x1000, - WINDOW_FLAG_4000 = 0x4000, + WINDOW_FLAG_MOUSE_OVER_TEXT = 0x40, + WINDOW_FLAG_LISTBOX_PREV_ARROW = 0x100, + WINDOW_FLAG_LISTBOX_NEXT_ARROW = 0x200, + WINDOW_FLAG_LISTBOX_THUMB = 0x400, + WINDOW_FLAG_LISTBOX_PREV_PAGE = 0x800, + WINDOW_FLAG_LISTBOX_NEXT_PAGE = 0x1000, + WINDOW_FLAG_FORCED_VISIBLE = 0x4000, WINDOW_FLAG_NON_DEFAULT_BACKCOLOR = 0x8000, WINDOW_FLAG_NON_DEFAULT_FORECOLOR = 0x10000, }; @@ -2787,6 +2787,12 @@ namespace IW3 ITEM_TYPE_GAME_MESSAGE_WINDOW = 0x13 }; + enum ItemDefFlag : unsigned int + { + ITEM_FLAG_SAVE_GAME_INFO = 0x1, + ITEM_FLAG_CINEMATIC_SUBTITLE = 0x2, + }; + enum ItemDefDvarFlag { ITEM_DVAR_FLAG_ENABLE = 0x1, diff --git a/src/Common/Game/IW4/IW4_Assets.h b/src/Common/Game/IW4/IW4_Assets.h index 0fed8149..48313908 100644 --- a/src/Common/Game/IW4/IW4_Assets.h +++ b/src/Common/Game/IW4/IW4_Assets.h @@ -2294,12 +2294,18 @@ namespace IW4 enum WindowDefDynamicFlag : unsigned int { - WINDOW_FLAG_HOVERED = 0x1, // guessed + WINDOW_FLAG_MOUSE_OVER = 0x1, WINDOW_FLAG_FOCUSED = 0x2, WINDOW_FLAG_VISIBLE = 0x4, WINDOW_FLAG_FADING_OUT = 0x10, WINDOW_FLAG_FADING_IN = 0x20, + WINDOW_FLAG_MOUSE_OVER_TEXT = 0x40, WINDOW_FLAG_80 = 0x80, + WINDOW_FLAG_LISTBOX_PREV_ARROW = 0x100, + WINDOW_FLAG_LISTBOX_NEXT_ARROW = 0x200, + WINDOW_FLAG_LISTBOX_THUMB = 0x400, + WINDOW_FLAG_LISTBOX_PREV_PAGE = 0x800, + WINDOW_FLAG_LISTBOX_NEXT_PAGE = 0x1000, WINDOW_FLAG_NON_DEFAULT_BACKCOLOR = 0x8000, WINDOW_FLAG_NON_DEFAULT_FORECOLOR = 0x10000 }; diff --git a/src/Common/Game/IW5/IW5_Assets.h b/src/Common/Game/IW5/IW5_Assets.h index 301cac70..a7967c47 100644 --- a/src/Common/Game/IW5/IW5_Assets.h +++ b/src/Common/Game/IW5/IW5_Assets.h @@ -3214,12 +3214,18 @@ namespace IW5 // This is data from IW4, could be different for IW5, to be investigated enum WindowDefDynamicFlag : unsigned int { - WINDOW_FLAG_HOVERED = 0x1, // guessed + WINDOW_FLAG_MOUSE_OVER = 0x1, WINDOW_FLAG_FOCUSED = 0x2, WINDOW_FLAG_VISIBLE = 0x4, // confirmed WINDOW_FLAG_FADING_OUT = 0x10, WINDOW_FLAG_FADING_IN = 0x20, + WINDOW_FLAG_MOUSE_OVER_TEXT = 0x40, WINDOW_FLAG_80 = 0x80, + WINDOW_FLAG_LISTBOX_PREV_ARROW = 0x100, + WINDOW_FLAG_LISTBOX_NEXT_ARROW = 0x200, + WINDOW_FLAG_LISTBOX_THUMB = 0x400, + WINDOW_FLAG_LISTBOX_PREV_PAGE = 0x800, + WINDOW_FLAG_LISTBOX_NEXT_PAGE = 0x1000, WINDOW_FLAG_NON_DEFAULT_BACKCOLOR = 0x8000, WINDOW_FLAG_NON_DEFAULT_FORECOLOR = 0x10000 }; diff --git a/src/Common/Game/T4/T4_Assets.h b/src/Common/Game/T4/T4_Assets.h index efaf5c5c..72a364c1 100644 --- a/src/Common/Game/T4/T4_Assets.h +++ b/src/Common/Game/T4/T4_Assets.h @@ -2790,7 +2790,18 @@ namespace T4 enum WindowDefDynamicFlag : unsigned int { + WINDOW_FLAG_MOUSE_OVER = 0x1, + WINDOW_FLAG_FOCUSED = 0x2, WINDOW_FLAG_VISIBLE = 0x4, + WINDOW_FLAG_FADING_OUT = 0x10, + WINDOW_FLAG_FADING_IN = 0x20, + WINDOW_FLAG_MOUSE_OVER_TEXT = 0x40, + WINDOW_FLAG_LISTBOX_PREV_ARROW = 0x100, + WINDOW_FLAG_LISTBOX_NEXT_ARROW = 0x200, + WINDOW_FLAG_LISTBOX_THUMB = 0x400, + WINDOW_FLAG_LISTBOX_PREV_PAGE = 0x800, + WINDOW_FLAG_LISTBOX_NEXT_PAGE = 0x1000, + WINDOW_FLAG_FORCED_VISIBLE = 0x4000, WINDOW_FLAG_NON_DEFAULT_BACKCOLOR = 0x8000, WINDOW_FLAG_NON_DEFAULT_FORECOLOR = 0x10000, }; @@ -3057,6 +3068,12 @@ namespace T4 ITEM_TYPE_GAME_MESSAGE_WINDOW = 0x13 }; + enum ItemDefFlag : unsigned int + { + ITEM_FLAG_SAVE_GAME_INFO = 0x1, + ITEM_FLAG_CINEMATIC_SUBTITLE = 0x2, + }; + enum ItemDefDvarFlag { ITEM_DVAR_FLAG_ENABLE = 0x1, diff --git a/src/ObjLoading/Game/IW3/Menu/MenuConverterIW3.cpp b/src/ObjLoading/Game/IW3/Menu/MenuConverterIW3.cpp index 5cfbca11..17a46a9a 100644 --- a/src/ObjLoading/Game/IW3/Menu/MenuConverterIW3.cpp +++ b/src/ObjLoading/Game/IW3/Menu/MenuConverterIW3.cpp @@ -562,6 +562,16 @@ namespace item->window.name = ConvertString(commonItem.m_name); item->text = commonItem.m_text ? m_memory.Dup(commonItem.m_text->c_str()) : nullptr; + if (commonItem.m_text_save_game) + { + item->text = m_memory.Dup("savegameinfo"); + item->itemFlags |= ITEM_FLAG_SAVE_GAME_INFO; + } + if (commonItem.m_text_cinematic_subtitle) + { + item->text = m_memory.Dup("cinematicsubtitle"); + item->itemFlags |= ITEM_FLAG_CINEMATIC_SUBTITLE; + } item->window.group = ConvertString(commonItem.m_group); item->window.rectClient = ConvertRectDef(commonItem.m_rect); item->window.style = commonItem.m_style; diff --git a/src/ObjLoading/Game/T4/Menu/MenuConverterT4.cpp b/src/ObjLoading/Game/T4/Menu/MenuConverterT4.cpp index 479706db..408b1174 100644 --- a/src/ObjLoading/Game/T4/Menu/MenuConverterT4.cpp +++ b/src/ObjLoading/Game/T4/Menu/MenuConverterT4.cpp @@ -566,6 +566,16 @@ namespace item->window.name = ConvertString(commonItem.m_name); item->text = commonItem.m_text ? m_memory.Dup(commonItem.m_text->c_str()) : nullptr; + if (commonItem.m_text_save_game) + { + item->text = m_memory.Dup("savegameinfo"); + item->itemFlags |= ITEM_FLAG_SAVE_GAME_INFO; + } + if (commonItem.m_text_cinematic_subtitle) + { + item->text = m_memory.Dup("cinematicsubtitle"); + item->itemFlags |= ITEM_FLAG_CINEMATIC_SUBTITLE; + } item->window.group = ConvertString(commonItem.m_group); item->window.rectClient = ConvertRectDef(commonItem.m_rect); item->window.style = commonItem.m_style; diff --git a/src/ObjWriting/Game/IW3/Menu/MenuWriterIW3.cpp b/src/ObjWriting/Game/IW3/Menu/MenuWriterIW3.cpp index e9a2667d..b00c8d69 100644 --- a/src/ObjWriting/Game/IW3/Menu/MenuWriterIW3.cpp +++ b/src/ObjWriting/Game/IW3/Menu/MenuWriterIW3.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -535,10 +536,29 @@ namespace m_stream << "\n"; } + void WriteItemTextProperties(const itemDef_s& item) const + { + const char* implicitText = nullptr; + + if (item.itemFlags & ITEM_FLAG_SAVE_GAME_INFO) + { + WriteKeywordProperty("textsavegame", true); + implicitText = "savegameinfo"; + } + if (item.itemFlags & ITEM_FLAG_CINEMATIC_SUBTITLE) + { + WriteKeywordProperty("textcinematicsubtitle", true); + implicitText = "cinematicsubtitle"; + } + + if (!implicitText || (item.text && std::strcmp(item.text, implicitText) != 0)) + WriteItemTextProperty(item.text); + } + void WriteItemData(const itemDef_s& item) { WriteStringProperty("name", item.window.name); - WriteItemTextProperty(item.text); + WriteItemTextProperties(item); WriteStringProperty("group", item.window.group); WriteRectProperty("rect", item.window.rectClient); WriteIntProperty("style", item.window.style, 0); diff --git a/src/ObjWriting/Game/T4/Menu/MenuWriterT4.cpp b/src/ObjWriting/Game/T4/Menu/MenuWriterT4.cpp index 262e156b..123c986e 100644 --- a/src/ObjWriting/Game/T4/Menu/MenuWriterT4.cpp +++ b/src/ObjWriting/Game/T4/Menu/MenuWriterT4.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -539,10 +540,29 @@ namespace m_stream << "\n"; } + void WriteItemTextProperties(const itemDef_s& item) const + { + const char* implicitText = nullptr; + + if (item.itemFlags & ITEM_FLAG_SAVE_GAME_INFO) + { + WriteKeywordProperty("textsavegame", true); + implicitText = "savegameinfo"; + } + if (item.itemFlags & ITEM_FLAG_CINEMATIC_SUBTITLE) + { + WriteKeywordProperty("textcinematicsubtitle", true); + implicitText = "cinematicsubtitle"; + } + + if (!implicitText || (item.text && std::strcmp(item.text, implicitText) != 0)) + WriteItemTextProperty(item.text); + } + void WriteItemData(const itemDef_s& item) { WriteStringProperty("name", item.window.name); - WriteItemTextProperty(item.text); + WriteItemTextProperties(item); WriteStringProperty("group", item.window.group); WriteRectProperty("rect", item.window.rectClient); WriteIntProperty("style", item.window.style, 0); diff --git a/test/ObjLoadingTests/Game/IW3/Menu/LoaderMenuListIW3Test.cpp b/test/ObjLoadingTests/Game/IW3/Menu/LoaderMenuListIW3Test.cpp index e4a8009c..88368cb3 100644 --- a/test/ObjLoadingTests/Game/IW3/Menu/LoaderMenuListIW3Test.cpp +++ b/test/ObjLoadingTests/Game/IW3/Menu/LoaderMenuListIW3Test.cpp @@ -104,6 +104,16 @@ namespace exp material (localvarstring("ui_material")); } itemDef + { + name "save_game_info" + textsavegame + } + itemDef + { + name "cinematic_subtitle" + textcinematicsubtitle + } + itemDef { name "server_list" rect 20 60 300 200 0 0 @@ -171,7 +181,7 @@ namespace REQUIRE(mainMenu->visibleExp.entries[2]->data.op == OP_RIGHTPAREN); REQUIRE(mainMenu->onOpen == R"("play" "menu_open" ; "setLocalVarInt" "ui_highlight" "5" ; )"s); - REQUIRE(mainMenu->itemCount == 3); + REQUIRE(mainMenu->itemCount == 5); const auto* emptyTextItem = mainMenu->items[0]; REQUIRE(emptyTextItem->parent == mainMenu); REQUIRE(emptyTextItem->text != nullptr); @@ -185,7 +195,15 @@ namespace REQUIRE(emptyTextItem->materialExp.numEntries == 3); REQUIRE(emptyTextItem->materialExp.entries[0]->data.op == OP_LOCALVARSTRING); - const auto* listItem = mainMenu->items[1]; + const auto* saveGameInfoItem = mainMenu->items[1]; + REQUIRE(saveGameInfoItem->text == "savegameinfo"s); + REQUIRE(saveGameInfoItem->itemFlags == ITEM_FLAG_SAVE_GAME_INFO); + + const auto* cinematicSubtitleItem = mainMenu->items[2]; + REQUIRE(cinematicSubtitleItem->text == "cinematicsubtitle"s); + REQUIRE(cinematicSubtitleItem->itemFlags == ITEM_FLAG_CINEMATIC_SUBTITLE); + + const auto* listItem = mainMenu->items[3]; REQUIRE(listItem->typeData.listBox); REQUIRE(listItem->typeData.listBox->numColumns == 2); REQUIRE(listItem->typeData.listBox->columnInfo[1].pos == 120); @@ -197,7 +215,7 @@ namespace REQUIRE(listItem->typeData.listBox->selectIcon == selectMaterial); REQUIRE(listItem->typeData.listBox->onDoubleClick == R"("play" "mouse_click" ; )"s); - const auto* defaultTextItem = mainMenu->items[2]; + const auto* defaultTextItem = mainMenu->items[4]; REQUIRE(defaultTextItem->type == 0); REQUIRE(defaultTextItem->typeData.editField); REQUIRE(defaultTextItem->typeData.editField->maxChars == 2); diff --git a/test/ObjLoadingTests/Game/T4/Menu/LoaderMenuListT4Test.cpp b/test/ObjLoadingTests/Game/T4/Menu/LoaderMenuListT4Test.cpp index b84b401a..a5428a0b 100644 --- a/test/ObjLoadingTests/Game/T4/Menu/LoaderMenuListT4Test.cpp +++ b/test/ObjLoadingTests/Game/T4/Menu/LoaderMenuListT4Test.cpp @@ -110,6 +110,16 @@ namespace exp material (localvarstring("ui_material")); } itemDef + { + name "save_game_info" + textsavegame + } + itemDef + { + name "cinematic_subtitle" + textcinematicsubtitle + } + itemDef { name "server_list" rect 20 60 300 200 0 0 @@ -186,7 +196,7 @@ namespace REQUIRE(mainMenu->onOpen == R"("play" "menu_open" ; "setLocalVarInt" "ui_highlight" "5" ; )"s); REQUIRE(mainMenu->onFocus == R"("setdvar" "ui_menu_focused" "1" ; )"s); - REQUIRE(mainMenu->itemCount == 3); + REQUIRE(mainMenu->itemCount == 5); const auto* emptyTextItem = mainMenu->items[0]; REQUIRE(emptyTextItem->parent == mainMenu); REQUIRE(emptyTextItem->text != nullptr); @@ -200,7 +210,15 @@ namespace REQUIRE(emptyTextItem->materialExp.numEntries == 3); REQUIRE(emptyTextItem->materialExp.entries[0]->data.op == OP_LOCALVARSTRING); - const auto* listItem = mainMenu->items[1]; + const auto* saveGameInfoItem = mainMenu->items[1]; + REQUIRE(saveGameInfoItem->text == "savegameinfo"s); + REQUIRE(saveGameInfoItem->itemFlags == ITEM_FLAG_SAVE_GAME_INFO); + + const auto* cinematicSubtitleItem = mainMenu->items[2]; + REQUIRE(cinematicSubtitleItem->text == "cinematicsubtitle"s); + REQUIRE(cinematicSubtitleItem->itemFlags == ITEM_FLAG_CINEMATIC_SUBTITLE); + + const auto* listItem = mainMenu->items[3]; REQUIRE(listItem->typeData.listBox); REQUIRE(listItem->typeData.listBox->numColumns == 2); REQUIRE(listItem->typeData.listBox->columnInfo[1].pos == 120); @@ -216,7 +234,7 @@ namespace REQUIRE(listItem->typeData.listBox->onDoubleClick == R"("play" "mouse_click" ; )"s); REQUIRE(listItem->onListboxSelectionChange == R"("setdvar" "ui_server_selected" "1" ; )"s); - const auto* defaultTextItem = mainMenu->items[2]; + const auto* defaultTextItem = mainMenu->items[4]; REQUIRE(defaultTextItem->type == 0); REQUIRE(defaultTextItem->typeData.editField); REQUIRE(defaultTextItem->typeData.editField->maxChars == 2); diff --git a/test/ObjWritingTests/Game/IW3/Menu/MenuDumperIW3Test.cpp b/test/ObjWritingTests/Game/IW3/Menu/MenuDumperIW3Test.cpp index c2365384..a6b61b28 100644 --- a/test/ObjWritingTests/Game/IW3/Menu/MenuDumperIW3Test.cpp +++ b/test/ObjWritingTests/Game/IW3/Menu/MenuDumperIW3Test.cpp @@ -353,6 +353,67 @@ namespace REQUIRE(parsed->m_menus[0]->m_items[1]->m_dvar == "player_name"); } + TEST_CASE("MenuDumperIW3: Dumps special item text flags", "[iw3][menu][assetdumper]") + { + itemDef_s saveGameInfo{}; + saveGameInfo.window.name = "save_game_info"; + saveGameInfo.text = "savegameinfo"; + saveGameInfo.itemFlags = ITEM_FLAG_SAVE_GAME_INFO; + + itemDef_s cinematicSubtitle{}; + cinematicSubtitle.window.name = "cinematic_subtitle"; + cinematicSubtitle.text = "cinematicsubtitle"; + cinematicSubtitle.itemFlags = ITEM_FLAG_CINEMATIC_SUBTITLE; + + itemDef_s customSaveGameInfo{}; + customSaveGameInfo.window.name = "custom_save_game_info"; + customSaveGameInfo.text = "custom save game text"; + customSaveGameInfo.itemFlags = ITEM_FLAG_SAVE_GAME_INFO; + + itemDef_s* items[]{&saveGameInfo, &cinematicSubtitle, &customSaveGameInfo}; + + menuDef_t menu{}; + menu.window.name = "special_item_text"; + menu.itemCount = static_cast(std::size(items)); + menu.items = items; + + Zone zone("MockZone", 0, GameId::IW3, GamePlatform::PC); + zone.m_pools.AddAsset(std::make_unique>(ASSET_TYPE_MENU, menu.window.name, &menu)); + + MockSearchPath mockObjPath; + MockOutputPath mockOutput; + AssetDumpingContext context(zone, "", mockOutput, mockObjPath, std::nullopt); + + menu::MenuDumperIW3 dumper; + dumper.Dump(context); + + const auto* file = mockOutput.GetMockedFile("ui_mp/special_item_text.menu"); + REQUIRE(file); + + const auto output = file->AsString(); + const auto saveGameInfoPosition = output.find("save_game_info"); + const auto cinematicSubtitlePosition = output.find("cinematic_subtitle", saveGameInfoPosition); + const auto customSaveGameInfoPosition = output.find("custom_save_game_info", cinematicSubtitlePosition); + REQUIRE(saveGameInfoPosition != std::string::npos); + REQUIRE(cinematicSubtitlePosition != std::string::npos); + REQUIRE(customSaveGameInfoPosition != std::string::npos); + + const auto saveGameInfoOutput = output.substr(saveGameInfoPosition, cinematicSubtitlePosition - saveGameInfoPosition); + REQUIRE(saveGameInfoOutput.find("textsavegame") != std::string::npos); + REQUIRE(saveGameInfoOutput.find("\"savegameinfo\"") == std::string::npos); + + const auto cinematicSubtitleOutput = output.substr(cinematicSubtitlePosition, customSaveGameInfoPosition - cinematicSubtitlePosition); + REQUIRE(cinematicSubtitleOutput.find("textcinematicsubtitle") != std::string::npos); + REQUIRE(cinematicSubtitleOutput.find("\"cinematicsubtitle\"") == std::string::npos); + + const auto customSaveGameInfoOutput = output.substr(customSaveGameInfoPosition); + const auto customKeywordPosition = customSaveGameInfoOutput.find("textsavegame"); + const auto customTextPosition = customSaveGameInfoOutput.find("\"custom save game text\""); + REQUIRE(customKeywordPosition != std::string::npos); + REQUIRE(customTextPosition != std::string::npos); + REQUIRE(customKeywordPosition < customTextPosition); + } + TEST_CASE("MenuDumperIW3: Prefers parent menu list path over ui_mp fallback", "[iw3][menu][assetdumper]") { menuDef_t menu{}; diff --git a/test/ObjWritingTests/Game/T4/Menu/MenuDumperT4Test.cpp b/test/ObjWritingTests/Game/T4/Menu/MenuDumperT4Test.cpp index 554c6874..25a15769 100644 --- a/test/ObjWritingTests/Game/T4/Menu/MenuDumperT4Test.cpp +++ b/test/ObjWritingTests/Game/T4/Menu/MenuDumperT4Test.cpp @@ -353,6 +353,67 @@ namespace REQUIRE(parsed->m_menus[0]->m_items[1]->m_dvar == "player_name"); } + TEST_CASE("MenuDumperT4: Dumps special item text flags", "[t4][menu][assetdumper]") + { + itemDef_s saveGameInfo{}; + saveGameInfo.window.name = "save_game_info"; + saveGameInfo.text = "savegameinfo"; + saveGameInfo.itemFlags = ITEM_FLAG_SAVE_GAME_INFO; + + itemDef_s cinematicSubtitle{}; + cinematicSubtitle.window.name = "cinematic_subtitle"; + cinematicSubtitle.text = "cinematicsubtitle"; + cinematicSubtitle.itemFlags = ITEM_FLAG_CINEMATIC_SUBTITLE; + + itemDef_s customSaveGameInfo{}; + customSaveGameInfo.window.name = "custom_save_game_info"; + customSaveGameInfo.text = "custom save game text"; + customSaveGameInfo.itemFlags = ITEM_FLAG_SAVE_GAME_INFO; + + itemDef_s* items[]{&saveGameInfo, &cinematicSubtitle, &customSaveGameInfo}; + + menuDef_t menu{}; + menu.window.name = "special_item_text"; + menu.itemCount = static_cast(std::size(items)); + menu.items = items; + + Zone zone("MockZone", 0, GameId::T4, GamePlatform::PC); + zone.m_pools.AddAsset(std::make_unique>(ASSET_TYPE_MENU, menu.window.name, &menu)); + + MockSearchPath mockObjPath; + MockOutputPath mockOutput; + AssetDumpingContext context(zone, "", mockOutput, mockObjPath, std::nullopt); + + menu::MenuDumperT4 dumper; + dumper.Dump(context); + + const auto* file = mockOutput.GetMockedFile("ui_mp/special_item_text.menu"); + REQUIRE(file); + + const auto output = file->AsString(); + const auto saveGameInfoPosition = output.find("save_game_info"); + const auto cinematicSubtitlePosition = output.find("cinematic_subtitle", saveGameInfoPosition); + const auto customSaveGameInfoPosition = output.find("custom_save_game_info", cinematicSubtitlePosition); + REQUIRE(saveGameInfoPosition != std::string::npos); + REQUIRE(cinematicSubtitlePosition != std::string::npos); + REQUIRE(customSaveGameInfoPosition != std::string::npos); + + const auto saveGameInfoOutput = output.substr(saveGameInfoPosition, cinematicSubtitlePosition - saveGameInfoPosition); + REQUIRE(saveGameInfoOutput.find("textsavegame") != std::string::npos); + REQUIRE(saveGameInfoOutput.find("\"savegameinfo\"") == std::string::npos); + + const auto cinematicSubtitleOutput = output.substr(cinematicSubtitlePosition, customSaveGameInfoPosition - cinematicSubtitlePosition); + REQUIRE(cinematicSubtitleOutput.find("textcinematicsubtitle") != std::string::npos); + REQUIRE(cinematicSubtitleOutput.find("\"cinematicsubtitle\"") == std::string::npos); + + const auto customSaveGameInfoOutput = output.substr(customSaveGameInfoPosition); + const auto customKeywordPosition = customSaveGameInfoOutput.find("textsavegame"); + const auto customTextPosition = customSaveGameInfoOutput.find("\"custom save game text\""); + REQUIRE(customKeywordPosition != std::string::npos); + REQUIRE(customTextPosition != std::string::npos); + REQUIRE(customKeywordPosition < customTextPosition); + } + TEST_CASE("MenuDumperT4: Prefers parent menu list path over ui_mp fallback", "[t4][menu][assetdumper]") { menuDef_t menu{};