diff --git a/data/ui_scripts/frontend_menus/__init__.lua b/data/ui_scripts/frontend_menus/__init__.lua index 836045e8..7ee38ff2 100644 --- a/data/ui_scripts/frontend_menus/__init__.lua +++ b/data/ui_scripts/frontend_menus/__init__.lua @@ -2,9 +2,28 @@ if Engine.GetCurrentMap() ~= "core_frontend" then return end -local utils = require("utils") +local enableLobbyMapVote = true -- toggle map vote in public lobby +local enableLargeServerBrowserButton = true -- toggle large server browser button -CoD.LobbyButtons.MP_STATS = { +local utils = require("utils") +require("datasources_start_menu_tabs") +require("datasources_change_map_categories") +require("datasources_gamesettingsflyout_buttons") + +CoD.LobbyButtons.MP_PUBLIC_MATCH = { + stringRef = "MENU_PLAY_CAPS", + action = NavigateToLobby_SelectionList, + param = "MPLobbyOnline", + customId = "btnPublicMatch", +} + +CoD.LobbyButtons.MP_FIND_MATCH = { + stringRef = "MPUI_BASICTRAINING_CAPS", + action = OpenFindMatch, + customId = "btnFindMatch", +} + +CoD.LobbyButtons.STATS = { stringRef = "STATS", action = function(self, element, controller, param, menu) SetPerControllerTableProperty(controller, "disableGameSettingsOptions", true) @@ -31,17 +50,48 @@ CoD.LobbyButtons.SETTING_UP_BOTS = { customId = "btnSettingUpBots" } -CoD.LobbyButtons.MP_CUSTOM_SETUP_GAME = { +CoD.LobbyButtons.GameSettingsFlyoutArenas = { stringRef = "MPUI_SETUP_GAME_CAPS", - action = OpenSetupGameMP, - customId = "btnSetupGame", + action = function(self, element, controller, param, menu) + SetPerControllerTableProperty(controller, "disableGameSettingsOptions", true) + OpenPopup(menu, "GameSettingsFlyoutMP", controller) + end, + customId = "btnGameSettingsFlyoutMP" } -local shouldShowMapVote = false +CoD.LobbyButtons.GameSettingsFlyoutMP = { + stringRef = "MPUI_SETUP_GAME_CAPS", + action = function(self, element, controller, param, menu) + SetPerControllerTableProperty(controller, "disableGameSettingsOptions", true) + OpenPopup(menu, "GameSettingsFlyoutMPCustom", controller) + end, + customId = "btnGameSettingsFlyoutMPCustom" +} + +CoD.LobbyButtons.SERVER_BROWSER = { + stringRef = "MENU_SERVER_BROWSER_CAPS", + action = function(self, element, controller, param, menu) + SetPerControllerTableProperty(controller, "disableGameSettingsOptions", true) + OpenPopup(menu, "LobbyServerBrowserOnline", controller) + end, + customId = "btnDedicated" +} + +local shouldShowMapVote = enableLobbyMapVote +local lobbyMapVote = function(lobbyMapVoteIsEnabled) + if lobbyMapVoteIsEnabled == true then + Engine.Exec(nil, "LobbyStopDemo") + end +end + local addCustomButtons = function(controller, menuId, buttonTable, isLeader) + if menuId == LobbyData.UITargets.UI_MPLOBBYMAIN.id then + utils.RemoveSpaces(buttonTable) + utils.AddSpacer(buttonTable, utils.GetButtonIndex(buttonTable, CoD.LobbyButtons.THEATER_MP) - 1) + end + if menuId == LobbyData.UITargets.UI_MPLOBBYONLINE.id or menuId == LobbyData.UITargets.UI_ZMLOBBYONLINE.id then - utils.AddSpacer(buttonTable) - utils.AddSmallButton(controller, buttonTable, CoD.LobbyButtons.MP_STATS) + utils.AddSmallButton(controller, buttonTable, CoD.LobbyButtons.STATS) end if menuId == LobbyData.UITargets.UI_MPLOBBYONLINE.id or menuId == LobbyData.UITargets.UI_ZMLOBBYONLINE.id or menuId == LobbyData.UITargets.UI_MPLOBBYMAIN.id or menuId == LobbyData.UITargets.UI_MPLOBBYLANGAME.id then @@ -49,21 +99,34 @@ local addCustomButtons = function(controller, menuId, buttonTable, isLeader) end if menuId == LobbyData.UITargets.UI_MPLOBBYONLINE.id then - shouldShowMapVote = true - elseif menuId == LobbyData.UITargets.UI_MPLOBBYONLINEPUBLICGAME.id then - if shouldShowMapVote == true then - shouldShowMapVote = false - --Enable map vote at start lobby - Engine.Exec(nil, "LobbyStopDemo") + shouldShowMapVote = enableLobbyMapVote + if enableLargeServerBrowserButton then + utils.AddLargeButton(controller, buttonTable, CoD.LobbyButtons.SERVER_BROWSER, 1) end - utils.AddLargeButton(controller, buttonTable, CoD.LobbyButtons.MP_START_GAME, 1) --Launch match button - utils.AddSpacer(buttonTable, 1) + elseif menuId == LobbyData.UITargets.UI_MPLOBBYONLINEPUBLICGAME.id then + utils.RemoveButton(buttonTable, CoD.LobbyButtons.MP_PUBLIC_LOBBY_LEADERBOARD) - utils.AddSpacer(buttonTable) - utils.AddSmallButton(controller, buttonTable, CoD.LobbyButtons.MP_CUSTOM_SETUP_GAME) --Setup game in public lobby + utils.AddLargeButton(controller, buttonTable, CoD.LobbyButtons.MP_START_GAME, 1) + utils.AddSmallButton(controller, buttonTable, CoD.LobbyButtons.GameSettingsFlyoutMP, 2) + utils.AddSpacer(buttonTable, utils.GetButtonIndex(buttonTable, CoD.LobbyButtons.GameSettingsFlyoutMP)) + + lobbyMapVote(shouldShowMapVote) + shouldShowMapVote = false elseif menuId == LobbyData.UITargets.UI_MPLOBBYONLINEARENAGAME.id then - utils.AddSpacer(buttonTable) - utils.AddSmallButton(controller, buttonTable, CoD.LobbyButtons.SETTING_UP_BOTS) --Bot setting button in public lobby + utils.AddLargeButton(controller, buttonTable, CoD.LobbyButtons.MP_START_GAME, 1) + utils.AddSmallButton(controller, buttonTable, CoD.LobbyButtons.GameSettingsFlyoutArenas, 2) + + utils.AddSpacer(buttonTable, utils.GetButtonIndex(buttonTable, CoD.LobbyButtons.GameSettingsFlyoutArenas)) + end + + if menuId == LobbyData.UITargets.UI_ZMLOBBYONLINE.id then + utils.RemoveButton(buttonTable, CoD.LobbyButtons.THEATER_ZM) + utils.AddLargeButton(controller, buttonTable, CoD.LobbyButtons.THEATER_ZM, #buttonTable + 1) + + utils.RemoveSpaces(buttonTable) + utils.AddSpacer(buttonTable, utils.GetButtonIndex(buttonTable, CoD.LobbyButtons.SERVER_BROWSER)) + utils.AddSpacer(buttonTable, utils.GetButtonIndex(buttonTable, CoD.LobbyButtons.ZM_BUBBLEGUM_BUFFS) - 1) + utils.AddSpacer(buttonTable, utils.GetButtonIndex(buttonTable, CoD.LobbyButtons.STATS)) end end diff --git a/data/ui_scripts/frontend_menus/datasources_change_map_categories.lua b/data/ui_scripts/frontend_menus/datasources_change_map_categories.lua new file mode 100644 index 00000000..468c6bfe --- /dev/null +++ b/data/ui_scripts/frontend_menus/datasources_change_map_categories.lua @@ -0,0 +1,96 @@ +local f0_local0 = function(f1_arg0, f1_arg1) + if not CoD.useMouse then + return + else + LUI.OverrideFunction_CallOriginalFirst(f1_arg0, "setState", function(element, controller) + if IsSelfInState(f1_arg0, "SelectingMap") then + f1_arg0.mapList:setMouseDisabled(false) + f1_arg0.mapCategoriesList:setMouseDisabled(true) + f1_arg0.m_categorySet = false + else + f1_arg0.mapList:setMouseDisabled(true) + f1_arg0.mapCategoriesList:setMouseDisabled(false) + end + end) + f1_arg0.mapList:setMouseDisabled(true) + f1_arg0.mapList:registerEventHandler("leftclick_outside", function(element, event) + if IsSelfInState(f1_arg0, "SelectingMap") and f1_arg0.m_categorySet then + CoD.PCUtil.SimulateButtonPress(f1_arg1, Enum.LUIButton.LUI_KEY_XBB_PSCIRCLE) + end + f1_arg0.m_categorySet = true + return true + end) + end +end + +local PostLoadFunc = function(f4_arg0, f4_arg1) + f0_local0(f4_arg0, f4_arg1) +end + +local f0_local2 = 10000 +local f0_local3 = 10001 +local f0_local4 = function(f5_arg0) + local f5_local0 = CoD.mapsTable[f5_arg0] + if CoD.CONTENT_DLC6_INDEX <= f5_local0.dlc_pack or f5_arg0 == "mp_redwood_ice" or f5_arg0 == "mp_veiled_heyday" then + return f0_local3 + elseif f5_local0.dlc_pack > 0 then + return f0_local2 + else + return f5_local0.dlc_pack + end +end + +DataSources.ChangeMapCategories = DataSourceHelpers.ListSetup("ChangeMapCategories", function(f6_arg0) + local f6_local0 = {} + local f6_local1 = CoD.GetMapValue(Engine.DvarString(nil, "ui_mapname"), "dlc_pack", CoD.CONTENT_ORIGINAL_MAP_INDEX) + local f6_local2 = function(f7_arg0, f7_arg1) + return { + models = { + text = Engine.Localize("MPUI_MAP_CATEGORY_" .. f7_arg0 .. "_CAPS"), + buttonText = Engine.Localize("MPUI_MAP_CATEGORY_" .. f7_arg0 .. "_CAPS"), + image = "playlist_map", + description = Engine.Localize("MPUI_MAP_CATEGORY_" .. f7_arg0 .. "_DESC") + }, + properties = { + category = f7_arg1, + selectIndex = f6_local1 == f7_arg1 + } + } + end + + CoD.mapsTable = Engine.GetGDTMapsTable() + local f6_local3 = function(f8_arg0) + for f8_local3, f8_local4 in pairs(CoD.mapsTable) do + if f8_local4.session_mode == CoD.gameModeEnum and f0_local4(f8_local3) == f8_arg0 and (ShowPurchasableMap(f6_arg0, f8_local3) or Engine.IsMapValid(f8_local3)) then + return true + end + end + return false + end + + if CoD.isCampaign == true then + table.insert(f6_local0, f6_local2("missions", CoD.CONTENT_ORIGINAL_MAP_INDEX)) + table.insert(f6_local0, f6_local2("dev", CoD.CONTENT_DEV_MAP_INDEX)) + else + table.insert(f6_local0, f6_local2("standard", CoD.CONTENT_ORIGINAL_MAP_INDEX)) + if not Dvar.ui_execdemo:get() and f6_local3(f0_local2) then + table.insert(f6_local0, f6_local2("dlc", f0_local2)) + end + if not Dvar.ui_execdemo:get() and f6_local3(f0_local3) then + table.insert(f6_local0, f6_local2("dlc_bonus", f0_local3)) + end + if Mods_Enabled() then --and Engine.Mods_Lists_GetInfoEntries( LuaEnums.USERMAP_BASE_PATH, 0, Engine.Mods_Lists_GetInfoEntriesCount( LuaEnums.USERMAP_BASE_PATH ) ) ~= nil then + local f9_local11 = Engine.Mods_Lists_GetInfoEntries(LuaEnums.USERMAP_BASE_PATH, 0, + Engine.Mods_Lists_GetInfoEntriesCount(LuaEnums.USERMAP_BASE_PATH)) + if f9_local11 then + for f9_local12 = 0, #f9_local11, 1 do + local f9_local17 = f9_local11[f9_local12] + if LUI.startswith(f9_local17.internalName, "mp_") then + table.insert(f6_local0, f6_local2("mods", CoD.CONTENT_MODS_INDEX)) + end + end + end + end + end + return f6_local0 +end, true) diff --git a/data/ui_scripts/frontend_menus/datasources_gamesettingsflyout_buttons.lua b/data/ui_scripts/frontend_menus/datasources_gamesettingsflyout_buttons.lua new file mode 100644 index 00000000..497efefb --- /dev/null +++ b/data/ui_scripts/frontend_menus/datasources_gamesettingsflyout_buttons.lua @@ -0,0 +1,260 @@ +local f0_local0 = function(f1_arg0, f1_arg1) + if not CoD.useMouse then + return + else + f1_arg0.Options:setHandleMouse(true) + f1_arg0.Options:registerEventHandler("leftclick_outside", function(element, event) + CoD.PCUtil.SimulateButtonPress(event.controller, Enum.LUIButton.LUI_KEY_XBB_PSCIRCLE) + return true + end) + end +end + +local PostLoadFunc = function(f3_arg0, f3_arg1) + f0_local0(f3_arg0, f3_arg1) + f3_arg0.disableBlur = true + f3_arg0.disablePopupOpenCloseAnim = true + Engine.SetModelValue(Engine.CreateModel(Engine.GetGlobalModel(), "GameSettingsFlyoutOpen"), true) + LUI.OverrideFunction_CallOriginalSecond(f3_arg0, "close", function(element) + Engine.SetModelValue(Engine.CreateModel(Engine.GetGlobalModel(), "GameSettingsFlyoutOpen"), false) + end) + f3_arg0:registerEventHandler("occlusion_change", function(element, event) + local f5_local0 = element:getParent() + if f5_local0 then + local f5_local1 = f5_local0:getFirstChild() + while f5_local1 ~= nil do + if f5_local1.menuName == "Lobby" then + break + end + f5_local1 = f5_local1:getNextSibling() + end + if f5_local1 then + if event.occluded == true then + f5_local1:setAlpha(0) + end + f5_local1:setAlpha(1) + end + end + element:OcclusionChange(event) + end) + f3_arg0:subscribeToModel(Engine.CreateModel(Engine.GetGlobalModel(), "lobbyRoot.lobbyNav", true), function(model) + local f6_local0 = f3_arg0.occludedBy + while f6_local0 do + if f6_local0.occludedBy ~= nil then + f6_local0 = f6_local0.occludedBy + end + while f6_local0 and f6_local0.menuName ~= "Lobby" do + f6_local0 = GoBack(f6_local0, f3_arg1) + end + Engine.SendClientScriptNotify(f3_arg1, "menu_change" .. Engine.GetLocalClientNum(f3_arg1), "Main", + "closeToMenu") + return + end + GoBack(f3_arg0, f3_arg1) + end, false) +end + +DataSources.GameSettingsFlyoutButtonsCustom = DataSourceHelpers.ListSetup("GameSettingsFlyoutButtonsCustom", +function(f7_arg0) + local f7_local0 = { + { + optionDisplay = "MPUI_CHANGE_MAP_CAPS", + customId = "btnChangeMap", + action = OpenChangeMap + }, + -- { + -- optionDisplay = "MPUI_CHANGE_GAME_MODE_CAPS", + -- customId = "btnChangeGameMode", + -- action = OpenChangeGameMode + -- }, + { + optionDisplay = "MENU_SETUP_BOTS_CAPS", + customId = "btnSetupBots", + action = OpenBotSettings + }, + { + optionDisplay = "MPUI_EDIT_GAME_RULES_CAPS", + customId = "btnEditGameRules", + action = OpenEditGameRules + } + } + -- if CoD.isPC and IsServerBrowserEnabled() then + -- table.insert( f7_local0, { + -- optionDisplay = "PLATFORM_SERVER_SETTINGS_CAPS", + -- customID = "btnServerSettings", + -- action = OpenServerSettings + -- } ) + -- end + local f7_local1 = {} + for f7_local5, f7_local6 in ipairs(f7_local0) do + table.insert(f7_local1, { + models = { + displayText = Engine.Localize(f7_local6.optionDisplay), + customId = f7_local6.customId, + disabled = f7_local6.disabled + }, + properties = { + title = f7_local6.optionDisplay, + desc = f7_local6.desc, + action = f7_local6.action, + actionParam = f7_local6.actionParam + } + }) + end + return f7_local1 +end, nil, nil, nil) + +LUI.createMenu.GameSettingsFlyoutMPCustom = function(controller) + local self = CoD.Menu.NewForUIEditor("GameSettingsFlyoutMPCustom") + if PreLoadFunc then + PreLoadFunc(self, controller) + end + self.soundSet = "default" + self:setOwner(controller) + self:setLeftRight(true, true, 0, 0) + self:setTopBottom(true, true, 0, 0) + self:playSound("menu_open", controller) + self.buttonModel = Engine.CreateModel(Engine.GetModelForController(controller), "GameSettingsFlyoutMP.buttonPrompts") + self.anyChildUsesUpdateState = true + + local Options = LUI.UIList.new(self, controller, -2, 0, nil, false, false, 0, 0, false, false) + Options:makeFocusable() + Options:setLeftRight(true, false, 243.43, 523.43) + Options:setTopBottom(true, false, 177.56, 329.56) + Options:setYRot(25) + Options:setWidgetType(CoD.FE_List1ButtonLarge_PH) + Options:setVerticalCount(3) + Options:setSpacing(-2) + Options:setDataSource("GameSettingsFlyoutButtonsCustom") + Options:registerEventHandler("gain_focus", function(element, event) + local f9_local0 = nil + if element.gainFocus then + f9_local0 = element:gainFocus(event) + elseif element.super.gainFocus then + f9_local0 = element.super:gainFocus(event) + end + CoD.Menu.UpdateButtonShownState(element, self, controller, Enum.LUIButton.LUI_KEY_XBA_PSCROSS) + return f9_local0 + end) + Options:registerEventHandler("lose_focus", function(element, event) + local f10_local0 = nil + if element.loseFocus then + f10_local0 = element:loseFocus(event) + elseif element.super.loseFocus then + f10_local0 = element.super:loseFocus(event) + end + return f10_local0 + end) + self:AddButtonCallbackFunction(Options, controller, Enum.LUIButton.LUI_KEY_XBA_PSCROSS, "ENTER", + function(element, menu, controller, model) + ProcessListAction(self, element, controller) + return true + end, function(element, menu, controller) + CoD.Menu.SetButtonLabel(menu, Enum.LUIButton.LUI_KEY_XBA_PSCROSS, "MENU_SELECT") + return true + end, false) + self:addElement(Options) + self.Options = Options + + self:mergeStateConditions({ + { + stateName = "Local", + condition = function(menu, element, event) + return IsLobbyNetworkModeLAN() + end + } + }) + self:subscribeToModel(Engine.GetModel(Engine.GetGlobalModel(), "lobbyRoot.lobbyNetworkMode"), function(model) + local f14_local0 = self + local f14_local1 = { + controller = controller, + name = "model_validation", + modelValue = Engine.GetModelValue(model), + modelName = "lobbyRoot.lobbyNetworkMode" + } + CoD.Menu.UpdateButtonShownState(f14_local0, self, controller, Enum.LUIButton.LUI_KEY_XBY_PSTRIANGLE) + end) + self:subscribeToModel(Engine.GetModel(Engine.GetGlobalModel(), "lobbyRoot.lobbyNav"), function(model) + local f15_local0 = self + local f15_local1 = { + controller = controller, + name = "model_validation", + modelValue = Engine.GetModelValue(model), + modelName = "lobbyRoot.lobbyNav" + } + CoD.Menu.UpdateButtonShownState(f15_local0, self, controller, Enum.LUIButton.LUI_KEY_XBY_PSTRIANGLE) + end) + self:AddButtonCallbackFunction(self, controller, Enum.LUIButton.LUI_KEY_XBB_PSCIRCLE, nil, + function(element, menu, controller, model) + GoBack(self, controller) + ClearMenuSavedState(menu) + return true + end, function(element, menu, controller) + CoD.Menu.SetButtonLabel(menu, Enum.LUIButton.LUI_KEY_XBB_PSCIRCLE, "") + return false + end, false) + self:AddButtonCallbackFunction(self, controller, Enum.LUIButton.LUI_KEY_START, "M", + function(element, menu, controller, model) + GoBackAndOpenOverlayOnParent(self, "StartMenu_Main", controller) + return true + end, function(element, menu, controller) + CoD.Menu.SetButtonLabel(menu, Enum.LUIButton.LUI_KEY_START, "MENU_MENU") + return true + end, false) + self:AddButtonCallbackFunction(self, controller, Enum.LUIButton.LUI_KEY_XBY_PSTRIANGLE, "S", + function(element, menu, controller, model) + if not IsLAN() and not IsPlayerAGuest(controller) and IsPlayerAllowedToPlayOnline(controller) then + GoBackAndOpenOverlayOnParent(self, "Social_Main", controller) + return true + else + + end + end, function(element, menu, controller) + if not IsLAN() and not IsPlayerAGuest(controller) and IsPlayerAllowedToPlayOnline(controller) then + CoD.Menu.SetButtonLabel(menu, Enum.LUIButton.LUI_KEY_XBY_PSTRIANGLE, "") + return false + else + return false + end + end, false) + self:AddButtonCallbackFunction(self, controller, Enum.LUIButton.LUI_KEY_LB, nil, + function(element, menu, controller, model) + SendButtonPressToOccludedMenu(menu, controller, model, Enum.LUIButton.LUI_KEY_LB) + return true + end, function(element, menu, controller) + CoD.Menu.SetButtonLabel(menu, Enum.LUIButton.LUI_KEY_LB, "") + return false + end, false) + self:AddButtonCallbackFunction(self, controller, Enum.LUIButton.LUI_KEY_RB, nil, + function(element, menu, controller, model) + SendButtonPressToOccludedMenu(menu, controller, model, Enum.LUIButton.LUI_KEY_RB) + return true + end, function(element, menu, controller) + CoD.Menu.SetButtonLabel(menu, Enum.LUIButton.LUI_KEY_RB, "") + return false + end, false) + Options.id = "Options" + self:processEvent({ + name = "menu_loaded", + controller = controller + }) + self:processEvent({ + name = "update_state", + menu = self + }) + if not self:restoreState() then + self.Options:processEvent({ + name = "gain_focus", + controller = controller + }) + end + LUI.OverrideFunction_CallOriginalSecond(self, "close", function(element) + element.Options:close() + Engine.UnsubscribeAndFreeModel(Engine.GetModel(Engine.GetModelForController(controller), + "GameSettingsFlyoutMP.buttonPrompts")) + end) + if PostLoadFunc then + PostLoadFunc(self, controller) + end + return self +end diff --git a/data/ui_scripts/frontend_menus/datasources_start_menu_tabs.lua b/data/ui_scripts/frontend_menus/datasources_start_menu_tabs.lua new file mode 100644 index 00000000..77767260 --- /dev/null +++ b/data/ui_scripts/frontend_menus/datasources_start_menu_tabs.lua @@ -0,0 +1,217 @@ +DataSources.StartMenuTabs = ListHelper_SetupDataSource("StartMenuTabs", function(f44_arg0) + local f44_local0 = {} + table.insert(f44_local0, { + models = { + tabIcon = CoD.buttonStrings.shoulderl + }, + properties = { + m_mouseDisabled = true + } + }) + if Engine.IsDemoPlaying() then + local f44_local1 = "CoD.StartMenu_GameOptions" + if Engine.IsZombiesGame() then + f44_local1 = "CoD.StartMenu_GameOptions_ZM" + end + table.insert(f44_local0, { + models = { + tabName = Engine.Localize("MENU_THEATER_CAPS"), + tabWidget = f44_local1, + tabIcon = "" + }, + properties = { + tabId = "gameOptions" + } + }) + elseif Engine.IsInGame() then + if IsGameTypeDOA() and not InSafehouse() then + table.insert(f44_local0, { + models = { + tabName = "DOA", + tabWidget = "CoD.StartMenu_GameOptions_DOA", + tabIcon = "" + }, + properties = { + tabId = "gameOptions" + } + }) + elseif CoD.isCampaign then + table.insert(f44_local0, { + models = { + tabName = SessionModeToUnlocalizedSessionModeCaps(Engine.CurrentSessionMode()), + tabWidget = "CoD.StartMenu_GameOptions_CP", + tabIcon = "" + }, + properties = { + tabId = "gameOptions" + } + }) + if not Engine.IsCampaignModeZombies() then + if CoD.isSafehouse and CoD.isOnlineGame() and not IsInTrainingSim(f44_arg0) and Dvar.ui_safehousebarracks:get() and not IsPlayerAGuest(f44_arg0) then + table.insert(f44_local0, { + models = { + tabName = "CPUI_BARRACKS_CAPS", + tabWidget = "CoD.CombatRecordCP_Contents", + tabIcon = "" + }, + properties = { + tabId = "combatRecord" + } + }) + end + if HighestMapReachedGreaterThan(f44_arg0, 1) or LUI.DEV ~= nil then + table.insert(f44_local0, { + models = { + tabName = "CPUI_TACTICAL_MODE_CAPS", + tabWidget = "CoD.StartMenu_TacticalMode", + tabIcon = "" + }, + properties = { + tabId = "tacticalMode" + } + }) + end + if not CoD.isSafehouse and not IsPlayerAGuest(f44_arg0) then + table.insert(f44_local0, { + models = { + tabName = "CPUI_ACCOLADES", + tabWidget = "CoD.MissionRecordVault_Challenges", + tabIcon = "" + }, + properties = { + tabId = "accolades" + } + }) + end + end + elseif Engine.IsZombiesGame() then + table.insert(f44_local0, { + models = { + tabName = SessionModeToUnlocalizedSessionModeCaps(Engine.CurrentSessionMode()), + tabWidget = "CoD.StartMenu_GameOptions_ZM", + tabIcon = "" + }, + properties = { + tabId = "gameOptions" + } + }) + else + table.insert(f44_local0, { + models = { + tabName = SessionModeToUnlocalizedSessionModeCaps(Engine.CurrentSessionMode()), + tabWidget = "CoD.StartMenu_GameOptions", + tabIcon = "" + }, + properties = { + tabId = "gameOptions" + } + }) + end + else + if not IsPlayerAGuest(f44_arg0) then + table.insert(f44_local0, { + models = { + tabName = "MENU_TAB_IDENTITY_CAPS", + tabWidget = "CoD.StartMenu_Identity", + tabIcon = "" + }, + properties = { + tabId = "identity", + disabled = Dvar.ui_execdemo_gamescom:get() + } + }) + end + if not IsLobbyNetworkModeLAN() and not Dvar.ui_execdemo:get() and not Engine.IsCampaignModeZombies() and not IsPlayerAGuest(f44_arg0) then + table.insert(f44_local0, { + models = { + tabName = "MENU_TAB_CHALLENGES_CAPS", + tabWidget = "CoD.StartMenu_Challenges", + tabIcon = "" + }, + properties = { + tabId = "challenges" + } + }) + local f44_local1 = CoD.isPC + if f44_local1 then + f44_local1 = false --Mods_IsUsingMods() + end + table.insert(f44_local0, { + models = { + tabName = "MENU_TAB_BARRACKS_CAPS", + tabWidget = "CoD.StartMenu_Barracks", + tabIcon = "", + disabled = f44_local1 + }, + properties = { + tabId = "barracks" + } + }) + if CommunityOptionsEnabled() then + local f44_local2 = CoD.perController[f44_arg0].openMediaTabAfterClosingGroups + CoD.perController[f44_arg0].openMediaTabAfterClosingGroups = false + table.insert(f44_local0, { + models = { + tabName = "MENU_TAB_MEDIA_CAPS", + tabWidget = "CoD.StartMenu_Media", + tabIcon = "" + }, + properties = { + tabId = "media", + selectIndex = f44_local2 + } + }) + end + end + end + if IsGameTypeDOA() and Engine.IsInGame() and not InSafehouse() then + local f44_local1 = table.insert + local f44_local2 = f44_local0 + local f44_local3 = { + models = { + tabName = "MENU_TAB_OPTIONS_CAPS", + tabWidget = "CoD.StartMenu_Options_DOA", + tabIcon = "" + } + } + local f44_local4 = { + tabId = "options" + } + local f44_local5 = Dvar.ui_execdemo:get() + if f44_local5 then + f44_local5 = not Engine.IsInGame() + end + f44_local4.selectIndex = f44_local5 + f44_local3.properties = f44_local4 + f44_local1(f44_local2, f44_local3) + else + local f44_local1 = table.insert + local f44_local2 = f44_local0 + local f44_local3 = { + models = { + tabName = "MENU_TAB_OPTIONS_CAPS", + tabWidget = "CoD.StartMenu_Options", + tabIcon = "" + } + } + local f44_local4 = { + tabId = "options" + } + local f44_local5 = Dvar.ui_execdemo_gamescom:get() + if f44_local5 then + f44_local5 = not Engine.IsInGame() + end + f44_local4.selectIndex = f44_local5 + f44_local3.properties = f44_local4 + f44_local1(f44_local2, f44_local3) + end + table.insert(f44_local0, { + models = { + tabIcon = CoD.buttonStrings.shoulderr + }, + properties = { + m_mouseDisabled = true + } + }) + return f44_local0 +end, true) diff --git a/data/ui_scripts/frontend_menus/utils.lua b/data/ui_scripts/frontend_menus/utils.lua index d8190895..7abe1926 100644 --- a/data/ui_scripts/frontend_menus/utils.lua +++ b/data/ui_scripts/frontend_menus/utils.lua @@ -16,6 +16,28 @@ local SetButtonState = function(button, state) end end +local RemoveButton = function(buttonTable, button) + for id, v in pairs(buttonTable) do + if buttonTable[id].optionDisplay == button.stringRef then + table.remove(buttonTable, id) + end + end +end + +local RemoveSpaces = function(buttonTable) + for id, v in pairs(buttonTable) do + buttonTable[id].isLastButtonInGroup = false + end +end + +local GetButtonIndex = function(buttonTable, button) + for id, v in pairs(buttonTable) do + if buttonTable[id].optionDisplay == button.stringRef then + return id + end + end +end + local AddButton = function(controller, options, button, isLargeButton, index) if button == nil then return @@ -125,5 +147,8 @@ return { AddButton = AddButton, AddLargeButton = AddLargeButton, AddSmallButton = AddSmallButton, - AddSpacer = AddSpacer + AddSpacer = AddSpacer, + RemoveButton = RemoveButton, + RemoveSpaces = RemoveSpaces, + GetButtonIndex = GetButtonIndex } diff --git a/data/ui_scripts/party/__init__.lua b/data/ui_scripts/party/__init__.lua index 741d9378..cd6c5e3e 100644 --- a/data/ui_scripts/party/__init__.lua +++ b/data/ui_scripts/party/__init__.lua @@ -1,84 +1,27 @@ +if not Engine.IsInGame() then + return +end -- Removed check for public matches to allow team change in ranked matches CoD.IsTeamChangeAllowed = function() - if Engine.GetGametypeSetting( "allowInGameTeamChange" ) == 1 then + if Engine.GetGametypeSetting("allowInGameTeamChange") == 1 then return true else return false end end -DataSources.StartMenuGameOptions = ListHelper_SetupDataSource("StartMenuGameOptions", function (controller) - local options = {} - if Engine.IsDemoPlaying() then - if not IsDemoRestrictedBasicMode() then - table.insert(options, {models = {displayText = Engine.ToUpper(Engine.Localize("MENU_UPLOAD_CLIP", Engine.GetDemoSegmentCount())), action = StartMenuUploadClip, disabledFunction = IsUploadClipButtonDisabled}, properties = {hideHelpItemLabel = true}}) - end - if Engine.IsDemoHighlightReelMode() then - table.insert(options, {models = {displayText = Engine.ToUpper(Engine.Localize("MENU_DEMO_CUSTOMIZE_HIGHLIGHT_REEL")), action = StartMenuOpenCustomizeHighlightReel, disabledFunction = IsCustomizeHighlightReelButtonDisabled}}) - end - table.insert(options, {models = {displayText = Engine.ToUpper(Engine.ToUpper(Engine.Localize("MENU_JUMP_TO_START"))), action = StartMenuJumpToStart, disabledFunction = IsJumpToStartButtonDisabled}, properties = {hideHelpItemLabel = true}}) - local endDemoButtonText = nil - if Engine.IsDemoClipPlaying() then - endDemoButtonText = Engine.ToUpper(Engine.Localize("MENU_END_CLIP")) - else - endDemoButtonText = Engine.ToUpper(Engine.Localize("MENU_END_FILM")) - end - table.insert(options, {models = {displayText = Engine.ToUpper(endDemoButtonText), action = StartMenuEndDemo}}) - elseif CoD.isCampaign then - table.insert(options, {models = {displayText = "MENU_RESUMEGAME_CAPS", action = StartMenuGoBack_ListElement}}) - local inTrainingSim = CoD.SafeGetModelValue(Engine.GetModelForController(controller), "safehouse.inTrainingSim") - if not inTrainingSim then - inTrainingSim = 0 - end - if Engine.IsLobbyHost(Enum.LobbyType.LOBBY_TYPE_GAME) then - if not CoD.isSafehouse and controller == Engine.GetPrimaryController() then - table.insert(options, {models = {displayText = "MENU_RESTART_MISSION_CAPS", action = RestartMission}}) - if LUI.DEV ~= nil then - table.insert(options, {models = {displayText = "MENU_RESTART_CHECKPOINT_CAPS", action = RestartFromCheckpoint}}) - end - end - if controller == Engine.GetPrimaryController() then - table.insert(options, {models = {displayText = "MENU_CHANGE_DIFFICULTY_CAPS", action = OpenDifficultySelect}}) - end - if CoD.isSafehouse and inTrainingSim == 1 then - table.insert(options, {models = {displayText = "MENU_END_TRAINING_SIM", action = EndTrainingSim}}) - elseif controller == Engine.GetPrimaryController() then - if Engine.DvarBool(0, "ui_blocksaves") then - table.insert(options, {models = {displayText = "MENU_EXIT_CAPS", action = SaveAndQuitGame}}) - else - table.insert(options, {models = {displayText = "MENU_SAVE_AND_QUIT_CAPS", action = SaveAndQuitGame}}) - end - end - elseif CoD.isSafehouse and inTrainingSim == 1 then - table.insert(options, {models = {displayText = "MENU_END_TRAINING_SIM", action = EndTrainingSim}}) - else - table.insert(options, {models = {displayText = "MENU_LEAVE_PARTY_AND_EXIT_CAPS", action = QuitGame}}) - end - elseif CoD.isMultiplayer then - if Engine.Team(controller, "name") ~= "TEAM_SPECTATOR" and Engine.GetGametypeSetting("disableClassSelection") ~= 1 then - table.insert(options, {models = {displayText = "MPUI_CHOOSE_CLASS_BUTTON_CAPS", action = ChooseClass}}) - end - if not Engine.IsVisibilityBitSet(controller, Enum.UIVisibilityBit.BIT_ROUND_END_KILLCAM) and not Engine.IsVisibilityBitSet(controller, Enum.UIVisibilityBit.BIT_FINAL_KILLCAM) and CoD.IsTeamChangeAllowed() then - table.insert(options, {models = {displayText = "MPUI_CHANGE_TEAM_BUTTON_CAPS", action = ChooseTeam}}) - end - if controller == 0 then - local endGameText = "MENU_QUIT_GAME_CAPS" - if Engine.IsLobbyHost(Enum.LobbyType.LOBBY_TYPE_GAME) and not CoD.isOnlineGame() then - endGameText = "MENU_END_GAME_CAPS" - end - table.insert(options, {models = {displayText = endGameText, action = QuitGame_MP}}) - end - elseif CoD.isZombie then - table.insert(options, {models = {displayText = "MENU_RESUMEGAME_CAPS", action = StartMenuGoBack_ListElement}}) - if Engine.IsLobbyHost(Enum.LobbyType.LOBBY_TYPE_GAME) and (not Engine.SessionModeIsMode(CoD.SESSIONMODE_SYSTEMLINK) or Engine.SessionModeIsMode(CoD.SESSIONMODE_OFFLINE)) then - table.insert(options, {models = {displayText = "MENU_RESTART_LEVEL_CAPS", action = RestartGame}}) - end - if Engine.IsLobbyHost(Enum.LobbyType.LOBBY_TYPE_GAME) == true then - table.insert(options, {models = {displayText = "MENU_END_GAME_CAPS", action = QuitGame_MP}}) - else - table.insert(options, {models = {displayText = "MENU_QUIT_GAME_CAPS", action = QuitGame_MP}}) - end - end - return options -end, true) +local getModeInfo = function() + local id = Engine.GetLobbyUIScreen() + return LobbyData:UITargetFromId(id) +end + +local getMaxClients = function() + local modeInfo = getModeInfo() + return modeInfo.maxClients +end + +-- Set com_maxclients InGame so players can join via direct connect (default from lobbydata) +Engine.SetDvar("com_maxclients", getMaxClients()) + +require("datasources_start_menu_game_options") diff --git a/data/ui_scripts/party/datasources_start_menu_game_options.lua b/data/ui_scripts/party/datasources_start_menu_game_options.lua new file mode 100644 index 00000000..ae6eb3bb --- /dev/null +++ b/data/ui_scripts/party/datasources_start_menu_game_options.lua @@ -0,0 +1,75 @@ +DataSources.StartMenuGameOptions = ListHelper_SetupDataSource("StartMenuGameOptions", function (controller) + local options = {} + if Engine.IsDemoPlaying() then + if not IsDemoRestrictedBasicMode() then + table.insert(options, {models = {displayText = Engine.ToUpper(Engine.Localize("MENU_UPLOAD_CLIP", Engine.GetDemoSegmentCount())), action = StartMenuUploadClip, disabledFunction = IsUploadClipButtonDisabled}, properties = {hideHelpItemLabel = true}}) + end + if Engine.IsDemoHighlightReelMode() then + table.insert(options, {models = {displayText = Engine.ToUpper(Engine.Localize("MENU_DEMO_CUSTOMIZE_HIGHLIGHT_REEL")), action = StartMenuOpenCustomizeHighlightReel, disabledFunction = IsCustomizeHighlightReelButtonDisabled}}) + end + table.insert(options, {models = {displayText = Engine.ToUpper(Engine.ToUpper(Engine.Localize("MENU_JUMP_TO_START"))), action = StartMenuJumpToStart, disabledFunction = IsJumpToStartButtonDisabled}, properties = {hideHelpItemLabel = true}}) + local endDemoButtonText = nil + if Engine.IsDemoClipPlaying() then + endDemoButtonText = Engine.ToUpper(Engine.Localize("MENU_END_CLIP")) + else + endDemoButtonText = Engine.ToUpper(Engine.Localize("MENU_END_FILM")) + end + table.insert(options, {models = {displayText = Engine.ToUpper(endDemoButtonText), action = StartMenuEndDemo}}) + elseif CoD.isCampaign then + table.insert(options, {models = {displayText = "MENU_RESUMEGAME_CAPS", action = StartMenuGoBack_ListElement}}) + local inTrainingSim = CoD.SafeGetModelValue(Engine.GetModelForController(controller), "safehouse.inTrainingSim") + if not inTrainingSim then + inTrainingSim = 0 + end + if Engine.IsLobbyHost(Enum.LobbyType.LOBBY_TYPE_GAME) then + if not CoD.isSafehouse and controller == Engine.GetPrimaryController() then + table.insert(options, {models = {displayText = "MENU_RESTART_MISSION_CAPS", action = RestartMission}}) + if LUI.DEV ~= nil then + table.insert(options, {models = {displayText = "MENU_RESTART_CHECKPOINT_CAPS", action = RestartFromCheckpoint}}) + end + end + if controller == Engine.GetPrimaryController() then + table.insert(options, {models = {displayText = "MENU_CHANGE_DIFFICULTY_CAPS", action = OpenDifficultySelect}}) + end + if CoD.isSafehouse and inTrainingSim == 1 then + table.insert(options, {models = {displayText = "MENU_END_TRAINING_SIM", action = EndTrainingSim}}) + elseif controller == Engine.GetPrimaryController() then + if Engine.DvarBool(0, "ui_blocksaves") then + table.insert(options, {models = {displayText = "MENU_EXIT_CAPS", action = SaveAndQuitGame}}) + else + table.insert(options, {models = {displayText = "MENU_SAVE_AND_QUIT_CAPS", action = SaveAndQuitGame}}) + end + end + elseif CoD.isSafehouse and inTrainingSim == 1 then + table.insert(options, {models = {displayText = "MENU_END_TRAINING_SIM", action = EndTrainingSim}}) + else + table.insert(options, {models = {displayText = "MENU_LEAVE_PARTY_AND_EXIT_CAPS", action = QuitGame}}) + end + elseif CoD.isMultiplayer then + if Engine.Team(controller, "name") ~= "TEAM_SPECTATOR" and Engine.GetGametypeSetting("disableClassSelection") ~= 1 then + table.insert(options, {models = {displayText = "MPUI_CHOOSE_CLASS_BUTTON_CAPS", action = ChooseClass}}) + end + if not Engine.IsVisibilityBitSet(controller, Enum.UIVisibilityBit.BIT_ROUND_END_KILLCAM) and not Engine.IsVisibilityBitSet(controller, Enum.UIVisibilityBit.BIT_FINAL_KILLCAM) and CoD.IsTeamChangeAllowed() then + table.insert(options, {models = {displayText = "MPUI_CHANGE_TEAM_BUTTON_CAPS", action = ChooseTeam}}) + end + if controller == 0 then + local endGameText = "MENU_QUIT_GAME_CAPS" + if Engine.IsLobbyHost(Enum.LobbyType.LOBBY_TYPE_GAME) and not CoD.isOnlineGame() then + endGameText = "MENU_END_GAME_CAPS" + end + table.insert(options, {models = {displayText = endGameText, action = QuitGame_MP}}) + end + elseif CoD.isZombie then + table.insert(options, {models = {displayText = "MENU_RESUMEGAME_CAPS", action = StartMenuGoBack_ListElement}}) + if Engine.IsLobbyHost(Enum.LobbyType.LOBBY_TYPE_GAME) and (not Engine.SessionModeIsMode(CoD.SESSIONMODE_SYSTEMLINK) or Engine.SessionModeIsMode(CoD.SESSIONMODE_OFFLINE)) then + table.insert(options, {models = {displayText = "MENU_RESTART_LEVEL_CAPS", action = RestartGame}}) + end + if Engine.IsLobbyHost(Enum.LobbyType.LOBBY_TYPE_GAME) == true then + table.insert(options, {models = {displayText = "MENU_END_GAME_CAPS", action = QuitGame_MP}}) + else + table.insert(options, {models = {displayText = "MENU_QUIT_GAME_CAPS", action = QuitGame_MP}}) + end + end + table.insert(options, {models = {displayText = "QUIT TO DESKTOP", action = OpenPCQuit}}) + return options +end, true) diff --git a/data/ui_scripts/playlist/__init__.lua b/data/ui_scripts/playlist/__init__.lua new file mode 100644 index 00000000..f8080665 --- /dev/null +++ b/data/ui_scripts/playlist/__init__.lua @@ -0,0 +1,34 @@ +if Engine.GetCurrentMap() ~= "core_frontend" then + return +end + +if CoD.LobbyMember then + local oldLobbyMember = CoD.LobbyMember.new + function CoD.LobbyMember.new(menu, controller) + local self = oldLobbyMember(menu, controller) + + -- Hide the playlist count text + if self.SearchingForPlayer then + self.SearchingForPlayer:setAlpha(0) + end + if self.FEMemberBlurPanelContainer0 then + self.FEMemberBlurPanelContainer0:setAlpha(0) + end + + return self + end +end + +function IsLobbyStatusVisible() + return false +end + +Engine.SetDvar("lobbyMigrate_Enabled", 0) +Engine.SetDvar("lobbyTimerStatusVotingInterval", 11000) +Engine.SetDvar("lobbyTimerStatusBeginInterval", 10) +Engine.SetDvar("lobbyTimerStatusStartInterval", 10) +Engine.SetDvar("lobbyTimerStatusPostGameInterval", 10) +Engine.SetDvar("lobbyTimerStatusVotingInterval_Arena", 11000) + +require("widget_playlist_match_settings_info") +require("widget_playlist_category_match_settings_info") diff --git a/data/ui_scripts/playlist/widget_playlist_category_match_settings_info.lua b/data/ui_scripts/playlist/widget_playlist_category_match_settings_info.lua new file mode 100644 index 00000000..28c34d25 --- /dev/null +++ b/data/ui_scripts/playlist/widget_playlist_category_match_settings_info.lua @@ -0,0 +1,15 @@ +if not CoD.playlistCategoryMatchSettingsInfo then + return +end + +local oldPlaylistCategoryMatchSettingsInfo = CoD.playlistCategoryMatchSettingsInfo.new +function CoD.playlistCategoryMatchSettingsInfo.new(menu, controller) + local self = oldPlaylistCategoryMatchSettingsInfo(menu, controller) + + -- Hide the playlist count text + if self.playlistCount then + self.playlistCount:setAlpha(0) + end + + return self +end diff --git a/data/ui_scripts/playlist/widget_playlist_match_settings_info.lua b/data/ui_scripts/playlist/widget_playlist_match_settings_info.lua new file mode 100644 index 00000000..9b31390e --- /dev/null +++ b/data/ui_scripts/playlist/widget_playlist_match_settings_info.lua @@ -0,0 +1,15 @@ +if not CoD.playlistMatchSettingsInfo then + return +end + +local oldPlaylistMatchSettingsInfo = CoD.playlistMatchSettingsInfo.new +function CoD.playlistMatchSettingsInfo.new(menu, controller) + local self = oldPlaylistMatchSettingsInfo(menu, controller) + + -- Hide the playlist count text + if self.playlistCount then + self.playlistCount:setAlpha(0) + end + + return self +end diff --git a/deps/curl b/deps/curl index 1c5ed24e..6b1e4dc6 160000 --- a/deps/curl +++ b/deps/curl @@ -1 +1 @@ -Subproject commit 1c5ed24ee0e929a6f410fcc3729becfd2ee71211 +Subproject commit 6b1e4dc6cdd2c1fc1730dc64aa5e2d82615e5993 diff --git a/deps/zlib b/deps/zlib index 66588683..b8a8373e 160000 --- a/deps/zlib +++ b/deps/zlib @@ -1 +1 @@ -Subproject commit 66588683b36042154ad35140bf9fcbb60c5d573c +Subproject commit b8a8373ec195c8d286fe7e81e78b4a6d31bd859f diff --git a/src/client/component/auth.cpp b/src/client/component/auth.cpp index 1457ee8e..9d8108f8 100644 --- a/src/client/component/auth.cpp +++ b/src/client/component/auth.cpp @@ -382,6 +382,9 @@ namespace auth p(0x141EB74D2_g, 0x141EB7515_g); // ? utils::hook::call(0x14134BF7D_g, send_connect_data_stub); + + // Fix crash + utils::hook::nop(0x142249097_g, 5); } for (const auto& patch : patches) diff --git a/src/client/component/discord.cpp b/src/client/component/discord.cpp index 421fe889..e8cabdf1 100644 --- a/src/client/component/discord.cpp +++ b/src/client/component/discord.cpp @@ -9,9 +9,11 @@ namespace discord { namespace { - void ready(const DiscordUser* /*request*/) + void ready(const DiscordUser* request) { - printf("Discord: Ready\n"); + SetEnvironmentVariableA("discord_user", request->userId); + + printf("Discord: Ready: %s - %s\n", request->userId, request->username); DiscordRichPresence discord_presence{}; ZeroMemory(&discord_presence, sizeof(discord_presence)); diff --git a/src/client/component/getinfo.cpp b/src/client/component/getinfo.cpp index ae0ed794..95e3d10b 100644 --- a/src/client/component/getinfo.cpp +++ b/src/client/component/getinfo.cpp @@ -110,6 +110,7 @@ namespace getinfo info.set("bots", std::to_string(get_bot_count())); info.set("sv_maxclients", std::to_string(get_max_client_count())); info.set("protocol", std::to_string(PROTOCOL)); + info.set("sub_protocol", std::to_string(SUB_PROTOCOL)); info.set("playmode", std::to_string(game::Com_SessionMode_GetMode())); info.set("gamemode", std::to_string(Com_SessionMode_GetGameMode())); info.set("sv_running", std::to_string(game::is_server_running())); diff --git a/src/client/component/party.cpp b/src/client/component/party.cpp index 3a337c05..d0127989 100644 --- a/src/client/component/party.cpp +++ b/src/client/component/party.cpp @@ -157,6 +157,14 @@ namespace party return; } + const auto sub_protocol = atoi(info.get("sub_protocol").data()); + if (sub_protocol != SUB_PROTOCOL && sub_protocol != (SUB_PROTOCOL - 1)) + { + const auto str = "Invalid sub-protocol."; + printf("%s\n", str); + return; + } + const auto gamename = info.get("gamename"); if (gamename != "T7"s) { diff --git a/src/client/component/patches.cpp b/src/client/component/patches.cpp index b7a2824f..c442fa83 100644 --- a/src/client/component/patches.cpp +++ b/src/client/component/patches.cpp @@ -17,7 +17,7 @@ namespace patches void sv_execute_client_messages_stub(game::client_s* client, game::msg_t* msg) { - if (client->reliableAcknowledge < 0) + if ((client->reliableSequence - client->reliableAcknowledge) < 0) { client->reliableAcknowledge = client->reliableSequence; network::send(client->address, "error", "EXE_LOSTRELIABLECOMMANDS"); diff --git a/src/client/component/server_list.cpp b/src/client/component/server_list.cpp index 7c0ca6ad..9e3fcbfc 100644 --- a/src/client/component/server_list.cpp +++ b/src/client/component/server_list.cpp @@ -1,164 +1,164 @@ -#include -#include "loader/component_loader.hpp" -#include "server_list.hpp" - -#include "game/game.hpp" - -#include -#include -#include - -#include "network.hpp" -#include "scheduler.hpp" - -namespace server_list -{ - namespace - { +#include +#include "loader/component_loader.hpp" +#include "server_list.hpp" + +#include "game/game.hpp" + +#include +#include +#include + +#include "network.hpp" +#include "scheduler.hpp" + +namespace server_list +{ + namespace + { utils::hook::detour lua_serverinfo_to_table_hook; - - struct state - { - game::netadr_t address{}; - bool requesting{false}; - std::chrono::high_resolution_clock::time_point query_start{}; - callback callback{}; - }; - - utils::concurrency::container master_state; - - void handle_server_list_response(const game::netadr_t& target, - const network::data_view& data, state& s) - { - if (!s.requesting || s.address != target) - { - return; - } - - s.requesting = false; - const auto callback = std::move(s.callback); - - std::optional start{}; - - for (size_t i = 0; i + 6 < data.size(); ++i) - { - if (data[i + 6] == '\\') - { - start.emplace(i); - break; - } - } - - if (!start.has_value()) - { - callback(true, {}); - return; - } - - std::unordered_set result{}; - - for (auto i = start.value(); i + 6 < data.size(); i += 7) - { - if (data[i + 6] != '\\') - { - break; - } - - game::netadr_t address{}; - address.type = game::NA_RAWIP; - address.localNetID = game::NS_CLIENT1; - memcpy(&address.ipv4.a, data.data() + i + 0, 4); - memcpy(&address.port, data.data() + i + 4, 2); - address.port = ntohs(address.port); - - result.emplace(address); - } - - callback(true, result); + + struct state + { + game::netadr_t address{}; + bool requesting{false}; + std::chrono::high_resolution_clock::time_point query_start{}; + callback callback{}; + }; + + utils::concurrency::container master_state; + + void handle_server_list_response(const game::netadr_t& target, + const network::data_view& data, state& s) + { + if (!s.requesting || s.address != target) + { + return; + } + + s.requesting = false; + const auto callback = std::move(s.callback); + + std::optional start{}; + + for (size_t i = 0; i + 6 < data.size(); ++i) + { + if (data[i + 6] == '\\') + { + start.emplace(i); + break; + } + } + + if (!start.has_value()) + { + callback(true, {}); + return; + } + + std::unordered_set result{}; + + for (auto i = start.value(); i + 6 < data.size(); i += 7) + { + if (data[i + 6] != '\\') + { + break; + } + + game::netadr_t address{}; + address.type = game::NA_RAWIP; + address.localNetID = game::NS_CLIENT1; + memcpy(&address.ipv4.a, data.data() + i + 0, 4); + memcpy(&address.port, data.data() + i + 4, 2); + address.port = ntohs(address.port); + + result.emplace(address); + } + + callback(true, result); } void lua_serverinfo_to_table_stub(game::hks::lua_State* state, game::ServerInfo serverInfo, int index) - { + { lua_serverinfo_to_table_hook.invoke(state, serverInfo, index); if (state) { auto botCount = atoi(game::Info_ValueForKey(serverInfo.tags, "bots")); game::Lua_SetTableInt("botCount", botCount, state); - } - } - } - - bool get_master_server(game::netadr_t& address) - { - address = network::address_from_string("server.boiii.re:20810"); - return address.type != game::NA_BAD; - } - - void request_servers(callback callback) - { - master_state.access([&callback](state& s) - { - game::netadr_t addr{}; - if (!get_master_server(addr)) - { - return; - } - - s.requesting = true; - s.address = addr; - s.callback = std::move(callback); - s.query_start = std::chrono::high_resolution_clock::now(); - - network::send(s.address, "getservers", utils::string::va("T7 %i full empty", PROTOCOL)); - }); - } - - struct component final : client_component - { - void post_unpack() override - { - network::on("getServersResponse", [](const game::netadr_t& target, const network::data_view& data) - { - master_state.access([&](state& s) - { - handle_server_list_response(target, data, s); - }); - }); - - scheduler::loop([] - { - master_state.access([](state& s) - { - if (!s.requesting) - { - return; - } - - const auto now = std::chrono::high_resolution_clock::now(); - if ((now - s.query_start) < 2s) - { - return; - } - - s.requesting = false; - s.callback(false, {}); - s.callback = {}; - }); + } + } + } + + bool get_master_server(game::netadr_t& address) + { + address = network::address_from_string("server.boiii.re:20810"); + return address.type != game::NA_BAD; + } + + void request_servers(callback callback) + { + master_state.access([&callback](state& s) + { + game::netadr_t addr{}; + if (!get_master_server(addr)) + { + return; + } + + s.requesting = true; + s.address = addr; + s.callback = std::move(callback); + s.query_start = std::chrono::high_resolution_clock::now(); + + network::send(s.address, "getservers", utils::string::va("T7 %i full empty", PROTOCOL)); + }); + } + + struct component final : client_component + { + void post_unpack() override + { + network::on("getServersResponse", [](const game::netadr_t& target, const network::data_view& data) + { + master_state.access([&](state& s) + { + handle_server_list_response(target, data, s); + }); + }); + + scheduler::loop([] + { + master_state.access([](state& s) + { + if (!s.requesting) + { + return; + } + + const auto now = std::chrono::high_resolution_clock::now(); + if ((now - s.query_start) < 2s) + { + return; + } + + s.requesting = false; + s.callback(false, {}); + s.callback = {}; + }); }, scheduler::async, 200ms); - lua_serverinfo_to_table_hook.create(0x141F1FD10_g, lua_serverinfo_to_table_stub); - } - - void pre_destroy() override - { - master_state.access([](state& s) - { - s.requesting = false; - s.callback = {}; - }); - } - }; -} - -REGISTER_COMPONENT(server_list::component) + lua_serverinfo_to_table_hook.create(0x141F1FD10_g, lua_serverinfo_to_table_stub); + } + + void pre_destroy() override + { + master_state.access([](state& s) + { + s.requesting = false; + s.callback = {}; + }); + } + }; +} + +REGISTER_COMPONENT(server_list::component) diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 9876f2c4..2f72549a 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -1,6 +1,7 @@ #pragma once -#define PROTOCOL 3 +#define PROTOCOL 5 +#define SUB_PROTOCOL 1 #ifdef __cplusplus namespace game diff --git a/src/client/steam/interfaces/matchmaking_servers.cpp b/src/client/steam/interfaces/matchmaking_servers.cpp index 4973b1fb..2bc6127b 100644 --- a/src/client/steam/interfaces/matchmaking_servers.cpp +++ b/src/client/steam/interfaces/matchmaking_servers.cpp @@ -1,333 +1,345 @@ -#include -#include "../steam.hpp" - -#include "game/game.hpp" - -#include "component/party.hpp" -#include "component/network.hpp" -#include "component/server_list.hpp" - -#include -#include - -namespace steam -{ - namespace - { - struct server - { - bool handled{false}; - game::netadr_t address{}; - gameserveritem_t server_item{}; - }; - - auto* const internet_request = reinterpret_cast(1); - - using servers = std::vector; - - ::utils::concurrency::container queried_servers{}; - std::atomic current_response{}; - - gameserveritem_t create_server_item(const game::netadr_t& address, const ::utils::info_string& info, - const uint32_t ping, const bool success) - { - gameserveritem_t server{}; - server.m_NetAdr.m_usConnectionPort = address.port; - server.m_NetAdr.m_usQueryPort = address.port; - server.m_NetAdr.m_unIP = ntohl(address.addr); - server.m_nPing = static_cast(ping); - server.m_bHadSuccessfulResponse = success; - server.m_bDoNotRefresh = false; - ::utils::string::copy(server.m_szGameDir, ""); - ::utils::string::copy(server.m_szMap, info.get("mapname").data()); - ::utils::string::copy(server.m_szGameDescription, info.get("description").data()); - server.m_nAppID = 311210; - server.m_nPlayers = atoi(info.get("clients").data()); - server.m_nMaxPlayers = atoi(info.get("sv_maxclients").data()); - server.m_nBotPlayers = atoi(info.get("bots").data()); - server.m_bPassword = info.get("isPrivate") == "1"; - server.m_bSecure = true; - server.m_ulTimeLastPlayed = 0; - server.m_nServerVersion = 1000; - ::utils::string::copy(server.m_szServerName, info.get("hostname").data()); - - const auto playmode = info.get("playmode"); - const auto mode = game::eModes(std::atoi(playmode.data())); - - const auto* tags = ::utils::string::va( - R"(\gametype\%s\dedicated\%s\ranked\false\hardcore\%s\zombies\%s\playerCount\%d\bots\%d\modName\%s\)", - info.get("gametype").data(), +#include +#include "../steam.hpp" + +#include "game/game.hpp" + +#include "component/party.hpp" +#include "component/network.hpp" +#include "component/server_list.hpp" + +#include +#include + +namespace steam +{ + namespace + { + struct server + { + bool handled{false}; + game::netadr_t address{}; + gameserveritem_t server_item{}; + }; + + auto* const internet_request = reinterpret_cast(1); + + using servers = std::vector; + + ::utils::concurrency::container queried_servers{}; + std::atomic current_response{}; + + template + void copy_safe(T& dest, const char* in) + { + ::utils::string::copy(dest, in); + ::utils::string::strip_material(dest, dest, std::extent::value); + } + + gameserveritem_t create_server_item(const game::netadr_t& address, const ::utils::info_string& info, + const uint32_t ping, const bool success) + { + const auto sub_protocol = atoi(info.get("sub_protocol").data()); + + gameserveritem_t server{}; + server.m_NetAdr.m_usConnectionPort = address.port; + server.m_NetAdr.m_usQueryPort = address.port; + server.m_NetAdr.m_unIP = ntohl(address.addr); + server.m_nPing = static_cast(ping); + server.m_bHadSuccessfulResponse = success; + server.m_bDoNotRefresh = false; + + copy_safe(server.m_szGameDir, ""); + copy_safe(server.m_szMap, info.get("mapname").data()); + copy_safe(server.m_szGameDescription, info.get("description").data()); + + server.m_nAppID = (sub_protocol == SUB_PROTOCOL || sub_protocol == (SUB_PROTOCOL - 1)) ? 311210 : 0; + server.m_nPlayers = atoi(info.get("clients").data()); + server.m_nMaxPlayers = atoi(info.get("sv_maxclients").data()); + server.m_nBotPlayers = atoi(info.get("bots").data()); + server.m_bPassword = info.get("isPrivate") == "1"; + server.m_bSecure = true; + server.m_ulTimeLastPlayed = 0; + server.m_nServerVersion = 1000; + + copy_safe(server.m_szServerName, info.get("hostname").data()); + + const auto playmode = info.get("playmode"); + const auto mode = game::eModes(std::atoi(playmode.data())); + + const auto* tags = ::utils::string::va( + R"(\gametype\%s\dedicated\%s\ranked\false\hardcore\%s\zombies\%s\playerCount\%d\bots\%d\modName\%s\)", + info.get("gametype").data(), info.get("dedicated") == "1" ? "true" : "false", - info.get("hc") == "1" ? "true" : "false", + info.get("hc") == "1" ? "true" : "false", mode == game::MODE_ZOMBIES ? "true" : "false", server.m_nPlayers, atoi(info.get("bots").data()), - info.get("modName").data()); - - ::utils::string::copy(server.m_szGameTags, tags); - server.m_steamID.bits = strtoull(info.get("xuid").data(), nullptr, 16); - - return server; - } - - void handle_server_respone(const bool success, const game::netadr_t& host, const ::utils::info_string& info, - const uint32_t ping) - { - bool all_handled = false; - std::optional index{}; - queried_servers.access([&](servers& srvs) - { - size_t i = 0; - for (; i < srvs.size(); ++i) - { - if (srvs[i].address == host) - { - break; - } - } - - if (i >= srvs.size()) - { - return; - } - - index = static_cast(i); - - auto& srv = srvs[i]; - srv.handled = true; - srv.server_item = create_server_item(host, info, ping, success); - - - for (const auto& entry : srvs) - { - if (!entry.handled) - { - return; - } - } - - all_handled = true; - }); - - const auto res = current_response.load(); - if (!index || !res) - { - return; - } - - if (success) - { - res->ServerResponded(internet_request, *index); - } - else - { - res->ServerFailedToRespond(internet_request, *index); - } - - if (all_handled) - { - res->RefreshComplete(internet_request, eServerResponded); - } - } - - void ping_server(const game::netadr_t& server) - { - party::query_server(server, handle_server_respone); - } - } - - void* matchmaking_servers::RequestInternetServerList(unsigned int iApp, void** ppchFilters, unsigned int nFilters, - matchmaking_server_list_response* pRequestServersResponse) - { - current_response = pRequestServersResponse; - - server_list::request_servers([](const bool success, const std::unordered_set& s) - { - const auto res = current_response.load(); - if (!res) - { - return; - } - - if (!success) - { - res->RefreshComplete(internet_request, eServerFailedToRespond); - return; - } - - if (s.empty()) - { - res->RefreshComplete(internet_request, eNoServersListedOnMasterServer); - return; - } - - queried_servers.access([&s](servers& srvs) - { - srvs = {}; - srvs.reserve(s.size()); - - for (auto& address : s) - { - server new_server{}; - new_server.address = address; - new_server.server_item = create_server_item(address, {}, 0, false); - - srvs.push_back(new_server); - } - }); - - for (auto& srv : s) - { - ping_server(srv); - } - }); - - return internet_request; - } - - void* matchmaking_servers::RequestLANServerList(unsigned int iApp, - matchmaking_server_list_response* pRequestServersResponse) - { - return reinterpret_cast(2); - } - - void* matchmaking_servers::RequestFriendsServerList(unsigned int iApp, void** ppchFilters, unsigned int nFilters, - matchmaking_server_list_response* pRequestServersResponse) - { - return reinterpret_cast(3); - } - - void* matchmaking_servers::RequestFavoritesServerList(unsigned int iApp, void** ppchFilters, unsigned int nFilters, - matchmaking_server_list_response* pRequestServersResponse) - { - return reinterpret_cast(4); - } - - void* matchmaking_servers::RequestHistoryServerList(unsigned int iApp, void** ppchFilters, unsigned int nFilters, - matchmaking_server_list_response* pRequestServersResponse) - { - return reinterpret_cast(5); - } - - void* matchmaking_servers::RequestSpectatorServerList(unsigned int iApp, void** ppchFilters, unsigned int nFilters, - matchmaking_server_list_response* pRequestServersResponse) - { - return reinterpret_cast(6); - } - - void matchmaking_servers::ReleaseRequest(void* hServerListRequest) - { - if (internet_request == hServerListRequest) - { - current_response = nullptr; - } - } - - gameserveritem_t* matchmaking_servers::GetServerDetails(void* hRequest, int iServer) - { - if (internet_request != hRequest) - { - return nullptr; - } - - static thread_local gameserveritem_t server_item{}; - return queried_servers.access([iServer](const servers& s) -> gameserveritem_t* - { - if (iServer < 0 || static_cast(iServer) >= s.size()) - { - return nullptr; - } - - server_item = s[iServer].server_item; - return &server_item; - }); - } - - void matchmaking_servers::CancelQuery(void* hRequest) - { - } - - void matchmaking_servers::RefreshQuery(void* hRequest) - { - } - - bool matchmaking_servers::IsRefreshing(void* hRequest) - { - return false; - } - - int matchmaking_servers::GetServerCount(void* hRequest) - { - if (internet_request != hRequest) - { - return 0; - } - - return queried_servers.access([](const servers& s) - { - return static_cast(s.size()); - }); - } - - void matchmaking_servers::RefreshServer(void* hRequest, const int iServer) - { - if (internet_request != hRequest) - { - return; - } - - std::optional address{}; - queried_servers.access([&](const servers& s) - { - if (iServer < 0 || static_cast(iServer) >= s.size()) - { - return; - } - - address = s[iServer].address; - }); - - if (address) - { - ping_server(*address); - } - } - - void* matchmaking_servers::PingServer(const unsigned int unIP, const unsigned short usPort, - matchmaking_ping_response* pRequestServersResponse) - { - auto response = pRequestServersResponse; - const auto addr = network::address_from_ip(htonl(unIP), usPort); - - party::query_server( - addr, [response](const bool success, const game::netadr_t& host, const ::utils::info_string& info, - const uint32_t ping) - { - if (success) - { - auto server_item = create_server_item(host, info, ping, success); - response->ServerResponded(server_item); - } - else - { - response->ServerFailedToRespond(); - } - }); - - return reinterpret_cast(static_cast(7 + rand())); - } - - int matchmaking_servers::PlayerDetails(unsigned int unIP, unsigned short usPort, void* pRequestServersResponse) - { - return 0; - } - - int matchmaking_servers::ServerRules(unsigned int unIP, unsigned short usPort, void* pRequestServersResponse) - { - return 0; - } - - void matchmaking_servers::CancelServerQuery(int hServerQuery) - { - } -} + info.get("modName").data()); + + copy_safe(server.m_szGameTags, tags); + + server.m_steamID.bits = strtoull(info.get("xuid").data(), nullptr, 16); + + return server; + } + + void handle_server_respone(const bool success, const game::netadr_t& host, const ::utils::info_string& info, + const uint32_t ping) + { + bool all_handled = false; + std::optional index{}; + queried_servers.access([&](servers& srvs) + { + size_t i = 0; + for (; i < srvs.size(); ++i) + { + if (srvs[i].address == host) + { + break; + } + } + + if (i >= srvs.size()) + { + return; + } + + index = static_cast(i); + + auto& srv = srvs[i]; + srv.handled = true; + srv.server_item = create_server_item(host, info, ping, success); + + + for (const auto& entry : srvs) + { + if (!entry.handled) + { + return; + } + } + + all_handled = true; + }); + + const auto res = current_response.load(); + if (!index || !res) + { + return; + } + + if (success) + { + res->ServerResponded(internet_request, *index); + } + else + { + res->ServerFailedToRespond(internet_request, *index); + } + + if (all_handled) + { + res->RefreshComplete(internet_request, eServerResponded); + } + } + + void ping_server(const game::netadr_t& server) + { + party::query_server(server, handle_server_respone); + } + } + + void* matchmaking_servers::RequestInternetServerList(unsigned int iApp, void** ppchFilters, unsigned int nFilters, + matchmaking_server_list_response* pRequestServersResponse) + { + current_response = pRequestServersResponse; + + server_list::request_servers([](const bool success, const std::unordered_set& s) + { + const auto res = current_response.load(); + if (!res) + { + return; + } + + if (!success) + { + res->RefreshComplete(internet_request, eServerFailedToRespond); + return; + } + + if (s.empty()) + { + res->RefreshComplete(internet_request, eNoServersListedOnMasterServer); + return; + } + + queried_servers.access([&s](servers& srvs) + { + srvs = {}; + srvs.reserve(s.size()); + + for (auto& address : s) + { + server new_server{}; + new_server.address = address; + new_server.server_item = create_server_item(address, {}, 0, false); + + srvs.push_back(new_server); + } + }); + + for (auto& srv : s) + { + ping_server(srv); + } + }); + + return internet_request; + } + + void* matchmaking_servers::RequestLANServerList(unsigned int iApp, + matchmaking_server_list_response* pRequestServersResponse) + { + return reinterpret_cast(2); + } + + void* matchmaking_servers::RequestFriendsServerList(unsigned int iApp, void** ppchFilters, unsigned int nFilters, + matchmaking_server_list_response* pRequestServersResponse) + { + return reinterpret_cast(3); + } + + void* matchmaking_servers::RequestFavoritesServerList(unsigned int iApp, void** ppchFilters, unsigned int nFilters, + matchmaking_server_list_response* pRequestServersResponse) + { + return reinterpret_cast(4); + } + + void* matchmaking_servers::RequestHistoryServerList(unsigned int iApp, void** ppchFilters, unsigned int nFilters, + matchmaking_server_list_response* pRequestServersResponse) + { + return reinterpret_cast(5); + } + + void* matchmaking_servers::RequestSpectatorServerList(unsigned int iApp, void** ppchFilters, unsigned int nFilters, + matchmaking_server_list_response* pRequestServersResponse) + { + return reinterpret_cast(6); + } + + void matchmaking_servers::ReleaseRequest(void* hServerListRequest) + { + if (internet_request == hServerListRequest) + { + current_response = nullptr; + } + } + + gameserveritem_t* matchmaking_servers::GetServerDetails(void* hRequest, int iServer) + { + if (internet_request != hRequest) + { + return nullptr; + } + + static thread_local gameserveritem_t server_item{}; + return queried_servers.access([iServer](const servers& s) -> gameserveritem_t* { + if (iServer < 0 || static_cast(iServer) >= s.size()) + { + return nullptr; + } + + server_item = s[iServer].server_item; + return &server_item; + }); + } + + void matchmaking_servers::CancelQuery(void* hRequest) + { + } + + void matchmaking_servers::RefreshQuery(void* hRequest) + { + } + + bool matchmaking_servers::IsRefreshing(void* hRequest) + { + return false; + } + + int matchmaking_servers::GetServerCount(void* hRequest) + { + if (internet_request != hRequest) + { + return 0; + } + + return queried_servers.access([](const servers& s) + { + return static_cast(s.size()); + }); + } + + void matchmaking_servers::RefreshServer(void* hRequest, const int iServer) + { + if (internet_request != hRequest) + { + return; + } + + std::optional address{}; + queried_servers.access([&](const servers& s) + { + if (iServer < 0 || static_cast(iServer) >= s.size()) + { + return; + } + + address = s[iServer].address; + }); + + if (address) + { + ping_server(*address); + } + } + + void* matchmaking_servers::PingServer(const unsigned int unIP, const unsigned short usPort, + matchmaking_ping_response* pRequestServersResponse) + { + auto response = pRequestServersResponse; + const auto addr = network::address_from_ip(htonl(unIP), usPort); + + party::query_server( + addr, [response](const bool success, const game::netadr_t& host, const ::utils::info_string& info, + const uint32_t ping) + { + if (success) + { + auto server_item = create_server_item(host, info, ping, success); + response->ServerResponded(server_item); + } + else + { + response->ServerFailedToRespond(); + } + }); + + return reinterpret_cast(static_cast(7 + rand())); + } + + int matchmaking_servers::PlayerDetails(unsigned int unIP, unsigned short usPort, void* pRequestServersResponse) + { + return 0; + } + + int matchmaking_servers::ServerRules(unsigned int unIP, unsigned short usPort, void* pRequestServersResponse) + { + return 0; + } + + void matchmaking_servers::CancelServerQuery(int hServerQuery) + { + } +} diff --git a/src/common/utils/cryptography.cpp b/src/common/utils/cryptography.cpp index 98f61550..824cb9a7 100644 --- a/src/common/utils/cryptography.cpp +++ b/src/common/utils/cryptography.cpp @@ -229,7 +229,20 @@ namespace utils::cryptography if (ecc_export(buffer, &length, type, &this->key_storage_) == CRYPT_OK) { - return std::string(cs(buffer), length); + return {cs(buffer), length}; + } + + return ""; + } + + std::string ecc::key::get_openssl() const + { + uint8_t buffer[4096] = {0}; + unsigned long length = sizeof(buffer); + + if (ecc_export_openssl(buffer, &length, PK_PUBLIC, &this->key_storage_) == CRYPT_OK) + { + return {cs(buffer), length}; } return ""; diff --git a/src/common/utils/cryptography.hpp b/src/common/utils/cryptography.hpp index 9538c5eb..456a950b 100644 --- a/src/common/utils/cryptography.hpp +++ b/src/common/utils/cryptography.hpp @@ -31,6 +31,8 @@ namespace utils::cryptography std::string serialize(int type = PK_PRIVATE) const; + std::string get_openssl() const; + void free(); bool operator==(key& key) const; diff --git a/src/common/utils/string.cpp b/src/common/utils/string.cpp index 25e1c6bc..7e87fe0a 100644 --- a/src/common/utils/string.cpp +++ b/src/common/utils/string.cpp @@ -1,7 +1,8 @@ #include "string.hpp" -#include -#include #include +#include +#include +#include #include "nt.hpp" @@ -116,11 +117,12 @@ namespace utils::string void strip(const char* in, char* out, size_t max) { + assert(max); if (!in || !out) return; max--; size_t current = 0; - while (*in != 0 && current < max) + while (*in != '\0' && current < max) { const auto color_index = (*(in + 1) - 48) >= 0xC ? 7 : (*(in + 1) - 48); @@ -141,6 +143,25 @@ namespace utils::string *out = '\0'; } + void strip_material(const char* in, char* out, size_t max) + { + assert(max); + if (!in || !out) return; + + size_t i = 0; + while (*in != '\0' && i < max - 1) + { + if (*in != '$' && *in != '{' && *in != '}') + { + *out++ = *in; + i++; + } + in++; + } + + *out = '\0'; + } + std::string convert(const std::wstring& wstr) { std::string result; diff --git a/src/common/utils/string.hpp b/src/common/utils/string.hpp index 4b396ec7..fe2f5d4f 100644 --- a/src/common/utils/string.hpp +++ b/src/common/utils/string.hpp @@ -91,6 +91,7 @@ namespace utils::string std::string get_clipboard_data(); void strip(const char* in, char* out, size_t max); + void strip_material(const char* in, char* out, size_t max); std::string convert(const std::wstring& wstr); std::wstring convert(const std::string& str);