Merge branch 'main' into create-a-class

This commit is contained in:
WantedDV 2023-04-18 10:51:03 -02:30
commit ca1ce5aba0
56 changed files with 4715 additions and 2850 deletions

View File

@ -7,11 +7,11 @@
# BOIII ☄️ # BOIII ☄️
Reverse engineering and analysis of Call of Duty: Black Ops 3. An attempt at reverse engineering and analyzing Call of Duty: Black Ops 3.
<img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQeSXYzQITJrcjiifN1nqX1fsVE7VwLZ3vl2g&usqp=CAU"> <img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQeSXYzQITJrcjiifN1nqX1fsVE7VwLZ3vl2g&usqp=CAU">
## Roadmap ## Technical Features
- [x] Steam API Emulation - [x] Steam API Emulation
- [x] Steam Integrity Bypass - [x] Steam Integrity Bypass
@ -24,6 +24,8 @@ Reverse engineering and analysis of Call of Duty: Black Ops 3.
- [x] P2P multiplayer - [x] P2P multiplayer
- [x] Dedicated Servers - [x] Dedicated Servers
Check out the <a href="https://github.com/momo5502/boiii/issues?q=is%3Aissue+is%3Aclosed+reason%3Acompleted">closed issues</a> for more gameplay related features and fixes that have been added!
## Writeups & Articles ## Writeups & Articles
- <a href="https://momo5502.com/posts/2022-11-17-reverse-engineering-integrity-checks-in-black-ops-3/">Reverse engineering integrity checks in Black Ops 3</a> - <a href="https://momo5502.com/posts/2022-11-17-reverse-engineering-integrity-checks-in-black-ops-3/">Reverse engineering integrity checks in Black Ops 3</a>

View File

@ -0,0 +1,26 @@
gametype_setting timelimit 5
gametype_setting scorelimit 0
gametype_setting roundscorelimit 1
gametype_setting roundwinlimit 2
gametype_setting roundlimit 2
gametype_setting preroundperiod 10
gametype_setting teamCount 2
gametype_setting shutdownDamage 3
gametype_setting bootTime 5
gametype_setting rebootTime 15
gametype_setting rebootPlayers 0
gametype_setting movePlayers 1
gametype_setting robotSpeed 1
gametype_setting robotShield 0
gametype_setting scoreHeroPowerGainFactor 0.788 //Score earned towards Hero Weapons and Abilities are multiplied by this factor
gametype_setting scoreHeroPowerTimeFactor 0.788
gametype_setting spawntraptriggertime 5
gametype_setting disableVehicleSpawners 1
gametype_setting gameAdvertisementRuleTimeLeft 3.5
gametype_setting gameAdvertisementRuleRound 3

View File

@ -3,11 +3,9 @@
#using scripts\shared\callbacks_shared; #using scripts\shared\callbacks_shared;
#using scripts\shared\system_shared; #using scripts\shared\system_shared;
#insert scripts\shared\shared.gsh;
#namespace serversettings; #namespace serversettings;
REGISTER_SYSTEM( "serversettings", &__init__, undefined ) function autoexec __init__sytem__() { system::register("serversettings",&__init__,undefined,undefined); }
function __init__() function __init__()
{ {
@ -21,26 +19,29 @@ function init()
level.hostname = "CoDHost"; level.hostname = "CoDHost";
SetDvar("sv_hostname", level.hostname); SetDvar("sv_hostname", level.hostname);
SetDvar("ui_hostname", level.hostname); SetDvar("ui_hostname", level.hostname);
//makeDvarServerInfo("ui_hostname", "CoDHost");
level.motd = GetDvarString( "scr_motd" ); level.motd = GetDvarString( "scr_motd" );
if(level.motd == "") if(level.motd == "")
level.motd = ""; level.motd = "";
SetDvar("scr_motd", level.motd); SetDvar("scr_motd", level.motd);
SetDvar("ui_motd", level.motd); SetDvar("ui_motd", level.motd);
//makeDvarServerInfo("ui_motd", "");
level.allowvote = GetDvarString( "g_allowvote" ); level.allowvote = GetDvarString( "g_allowvote" );
if(level.allowvote == "") if(level.allowvote == "")
level.allowvote = "1"; level.allowvote = "1";
SetDvar("g_allowvote", level.allowvote); SetDvar("g_allowvote", level.allowvote);
SetDvar("ui_allowvote", level.allowvote); SetDvar("ui_allowvote", level.allowvote);
//makeDvarServerInfo("ui_allowvote", "1");
level.allow_teamchange = "1"; level.allow_teamchange = "1";
SetDvar("ui_allow_teamchange", level.allow_teamchange); SetDvar("ui_allow_teamchange", level.allow_teamchange);
level.friendlyfire = GetGametypeSetting( "friendlyfiretype" ); level.friendlyfire = GetGametypeSetting( "friendlyfiretype" );
SetDvar("ui_friendlyfire", level.friendlyfire); SetDvar("ui_friendlyfire", level.friendlyfire);
//makeDvarServerInfo("ui_friendlyfire", "0");
if(GetDvarString( "scr_mapsize") == "") if(GetDvarString( "scr_mapsize") == "")
SetDvar("scr_mapsize", "64"); SetDvar("scr_mapsize", "64");
@ -57,6 +58,8 @@ function init()
constrain_gametype(GetDvarString( "g_gametype")); constrain_gametype(GetDvarString( "g_gametype"));
constrain_map_size(level.mapsize); constrain_map_size(level.mapsize);
thread setup_callbacks();
for(;;) for(;;)
{ {
update(); update();
@ -194,3 +197,13 @@ function constrain_map_size(mapsize)
} }
} }
} }
function setup_callbacks()
{
level.onForfeit = &default_onForfeit;
}
function default_onForfeit()
{
level.gameForfeited = false;
}

View File

@ -0,0 +1,87 @@
if Engine.GetCurrentMap() ~= "core_frontend" then
return
end
local utils = require("utils")
CoD.LobbyButtons.MP_STATS = {
stringRef = "STATS",
action = function(self, element, controller, param, menu)
SetPerControllerTableProperty(controller, "disableGameSettingsOptions", true)
OpenPopup(menu, "BoiiiStatsMenu", controller)
end,
customId = "btnMPStats"
}
CoD.LobbyButtons.MP_START_GAME = {
stringRef = "MENU_START_GAME_CAPS",
action = function(self, element, controller, param, menu)
Engine.SetDvar("party_minplayers", 1)
Engine.Exec(nil, "launchgame")
end,
customId = "btnStartGame"
}
CoD.LobbyButtons.SETTING_UP_BOTS = {
stringRef = "MENU_SETUP_BOTS_CAPS",
action = function(self, element, controller, param, menu)
SetPerControllerTableProperty(controller, "disableGameSettingsOptions", true)
OpenPopup(menu, "GameSettings_Bots", controller)
end,
customId = "btnSettingUpBots"
}
CoD.LobbyButtons.MP_CUSTOM_SETUP_GAME = {
stringRef = "MPUI_SETUP_GAME_CAPS",
action = OpenSetupGameMP,
customId = "btnSetupGame",
}
local shouldShowMapVote = false
local addCustomButtons = function(controller, menuId, buttonTable, isLeader)
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)
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
Engine.Mods_Lists_UpdateUsermaps()
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")
end
utils.AddLargeButton(controller, buttonTable, CoD.LobbyButtons.MP_START_GAME, 1) --Launch match button
utils.AddSpacer(buttonTable, 1)
utils.AddSpacer(buttonTable)
utils.AddSmallButton(controller, buttonTable, CoD.LobbyButtons.MP_CUSTOM_SETUP_GAME) --Setup game in public lobby
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
end
end
local oldAddButtonsForTarget = CoD.LobbyMenus.AddButtonsForTarget
CoD.LobbyMenus.AddButtonsForTarget = function(controller, id)
local model = nil
if Engine.IsLobbyActive(Enum.LobbyType.LOBBY_TYPE_GAME) then
model = Engine.GetModel(DataSources.LobbyRoot.getModel(controller), "gameClient.isHost")
else
model = Engine.GetModel(DataSources.LobbyRoot.getModel(controller), "privateClient.isHost")
end
local isLeader = nil
if model ~= nil then
isLeader = Engine.GetModelValue(model)
else
isLeader = 1
end
local result = oldAddButtonsForTarget(controller, id)
addCustomButtons(controller, id, result, isLeader)
return result
end

View File

@ -0,0 +1,129 @@
local IsGamescomDemo = function()
return Dvar.ui_execdemo_gamescom:get()
end
local IsBetaDemo = function()
return Dvar.ui_execdemo_beta:get()
end
local SetButtonState = function(button, state)
if state == nil then
return
elseif state == CoD.LobbyButtons.DISABLED then
button.disabled = true
elseif state == CoD.LobbyButtons.HIDDEN then
button.hidden = true
end
end
local AddButton = function(controller, options, button, isLargeButton, index)
if button == nil then
return
end
button.disabled = false
button.hidden = false
button.selected = false
button.warning = false
if button.defaultState ~= nil then
if button.defaultState == CoD.LobbyButtons.DISABLED then
button.disabled = true
elseif button.defaultState == CoD.LobbyButtons.HIDDEN then
button.hidden = true
end
end
if button.disabledFunc ~= nil then
button.disabled = button.disabledFunc(controller)
end
if button.visibleFunc ~= nil then
button.hidden = not button.visibleFunc(controller)
end
if IsBetaDemo() then
SetButtonState(button, button.demo_beta)
elseif IsGamescomDemo() then
SetButtonState(button, button.demo_gamescom)
end
if button.hidden then
return
end
local lobbyNav = LobbyData.GetLobbyNav()
if button.selectedFunc ~= nil then
button.selected = button.selectedFunc(button.selectedParam)
elseif CoD.LobbyMenus.History[lobbyNav] ~= nil then
button.selected = CoD.LobbyMenus.History[lobbyNav] == button.customId
end
if button.newBreadcrumbFunc then
local f8_local1 = button.newBreadcrumbFunc
if type(f8_local1) == "string" then
f8_local1 = LUI.getTableFromPath(f8_local1)
end
if f8_local1 then
button.isBreadcrumbNew = f8_local1(controller)
end
end
if button.warningFunc ~= nil then
button.warning = button.warningFunc(controller)
end
if button.starterPack == CoD.LobbyButtons.STARTERPACK_UPGRADE then
button.starterPackUpgrade = true
if IsStarterPack() then
button.disabled = false
end
end
if index ~= nil then
table.insert(options, index, {
optionDisplay = button.stringRef,
action = button.action,
param = button.param,
customId = button.customId,
isLargeButton = isLargeButton,
isLastButtonInGroup = false,
disabled = button.disabled,
selected = button.selected,
isBreadcrumbNew = button.isBreadcrumbNew,
warning = button.warning,
requiredChunk = button.selectedParam,
starterPackUpgrade = button.starterPackUpgrade,
unloadMod = button.unloadMod
})
else
table.insert(options, {
optionDisplay = button.stringRef,
action = button.action,
param = button.param,
customId = button.customId,
isLargeButton = isLargeButton,
isLastButtonInGroup = false,
disabled = button.disabled,
selected = button.selected,
isBreadcrumbNew = button.isBreadcrumbNew,
warning = button.warning,
requiredChunk = button.selectedParam,
starterPackUpgrade = button.starterPackUpgrade,
unloadMod = button.unloadMod
})
end
end
local AddLargeButton = function(controller, options, button, index)
AddButton(controller, options, button, true, index)
end
local AddSmallButton = function(controller, options, button, index)
AddButton(controller, options, button, false, index)
end
local AddSpacer = function(options, index)
if index ~= nil then
options[index].isLastButtonInGroup = true
elseif 0 < #options then
options[#options].isLastButtonInGroup = true
end
end
return {
AddButton = AddButton,
AddLargeButton = AddLargeButton,
AddSmallButton = AddSmallButton,
AddSpacer = AddSpacer
}

View File

@ -71,7 +71,7 @@ DataSources.StartMenuGameOptions = ListHelper_SetupDataSource("StartMenuGameOpti
end end
elseif CoD.isZombie then elseif CoD.isZombie then
table.insert(options, {models = {displayText = "MENU_RESUMEGAME_CAPS", action = StartMenuGoBack_ListElement}}) table.insert(options, {models = {displayText = "MENU_RESUMEGAME_CAPS", action = StartMenuGoBack_ListElement}})
if Engine.IsLobbyHost(Enum.LobbyType.LOBBY_TYPE_GAME) and (not not (Engine.SessionModeIsMode(CoD.SESSIONMODE_SYSTEMLINK) == true) or Engine.SessionModeIsMode(CoD.SESSIONMODE_OFFLINE) == true) then 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}}) table.insert(options, {models = {displayText = "MENU_RESTART_LEVEL_CAPS", action = RestartGame}})
end end
if Engine.IsLobbyHost(Enum.LobbyType.LOBBY_TYPE_GAME) == true then if Engine.IsLobbyHost(Enum.LobbyType.LOBBY_TYPE_GAME) == true then

View File

@ -0,0 +1,378 @@
if Engine.GetCurrentMap() ~= "core_frontend" then
return
end
function IsServerBrowserEnabled()
return true
end
DataSources.LobbyServer = {
prepare = function(controller, list, filter)
list.numElementsInList = list.vCount
list.controller = controller
list.serverBrowserRootModel = Engine.CreateModel(Engine.GetGlobalModel(), "serverBrowser")
local serverListCountModel = Engine.GetModel(list.serverBrowserRootModel, "serverListCount")
if serverListCountModel then
list.serverCount = Engine.GetModelValue(serverListCountModel)
else
list.serverCount = 0
end
list.servers = {}
local serversModel = Engine.CreateModel(list.serverBrowserRootModel, "servers")
for i = 1, list.numElementsInList, 1 do
list.servers[i] = {}
list.servers[i].root = Engine.CreateModel(serversModel, "server_" .. i)
list.servers[i].model = Engine.CreateModel(list.servers[i].root, "model")
end
list.updateModels = function(controller, list, offset)
local serverInfo = Engine.SteamServerBrowser_GetServerInfo(offset)
if serverInfo then
local SetModelValue = function(model, key, value)
local model = Engine.CreateModel(model, key)
if model then
Engine.SetModelValue(model, value)
end
end
local elementIndex = offset % list.numElementsInList + 1
local serverModel = list.servers[elementIndex].model
SetModelValue(serverModel, "serverIndex", serverInfo.serverIndex)
SetModelValue(serverModel, "connectAddr", serverInfo.connectAddr)
SetModelValue(serverModel, "ping", serverInfo.ping)
SetModelValue(serverModel, "modName", serverInfo.modName)
SetModelValue(serverModel, "mapName", serverInfo.map)
SetModelValue(serverModel, "desc", serverInfo.desc)
-- Changed the client count to be the actual player count
local clientCount = serverInfo.playerCount - serverInfo.botCount
SetModelValue(serverModel, "clientCount", clientCount)
SetModelValue(serverModel, "maxClients", serverInfo.maxPlayers)
SetModelValue(serverModel, "passwordProtected", serverInfo.password)
SetModelValue(serverModel, "secure", serverInfo.secure)
SetModelValue(serverModel, "name", serverInfo.name)
SetModelValue(serverModel, "gameType", serverInfo.gametype)
SetModelValue(serverModel, "dedicated", serverInfo.dedicated)
SetModelValue(serverModel, "ranked", serverInfo.ranked)
SetModelValue(serverModel, "hardcore", serverInfo.hardcore)
SetModelValue(serverModel, "zombies", serverInfo.zombies)
-- Added the bot count
SetModelValue(serverModel, "botCount", serverInfo.botCount)
return serverModel
else
return nil
end
end
if list.serverListUpdateSubscription then
list:removeSubscription(list.serverListUpdateSubscription)
end
local serverListUpdateModel = Engine.CreateModel(list.serverBrowserRootModel, "serverListCount")
list.serverListUpdateSubscription = list:subscribeToModel(serverListUpdateModel, function(model)
list:updateDataSource(false, false)
end, false)
if list.serverListSortTypeSubscription then
list:removeSubscription(list.serverListSortTypeSubscription)
end
local serverListSortTypeModel = Engine.CreateModel(list.serverBrowserRootModel, "serverListSortType")
list.serverListSortTypeSubscription = list:subscribeToModel(serverListSortTypeModel, function(model)
list:updateDataSource(false, false)
end, false)
end,
getCount = function(list)
return list.serverCount
end,
getItem = function(controller, list, index)
local offset = index - 1
return list.updateModels(controller, list, offset)
end,
cleanup = function(list)
if list.serverBrowserRootModel then
Engine.UnsubscribeAndFreeModel(list.serverBrowserRootModel)
list.serverBrowserRootModel = nil
end
end
}
CoD.ServerBrowserRowInternal.new = function(menu, controller)
local self = LUI.UIHorizontalList.new({
left = 0,
top = 0,
right = 0,
bottom = 0,
leftAnchor = true,
topAnchor = true,
rightAnchor = true,
bottomAnchor = true,
spacing = 2
})
self:setAlignment(LUI.Alignment.Left)
if PreLoadFunc then
PreLoadFunc(self, controller)
end
self:setUseStencil(false)
self:setClass(CoD.ServerBrowserRowInternal)
self.id = "ServerBrowserRowInternal"
self.soundSet = "default"
self:setLeftRight(true, false, 0, 700)
self:setTopBottom(true, false, 0, 22)
self:makeFocusable()
self.onlyChildrenFocusable = true
self.anyChildUsesUpdateState = true
local passwordFlag = CoD.ServerBrowserFlag.new(menu, controller)
passwordFlag:setLeftRight(true, false, 0, 28)
passwordFlag:setTopBottom(true, true, 0, 0)
passwordFlag.icon:setImage(RegisterImage("uie_t7_icon_serverbrowser_protected"))
passwordFlag:linkToElementModel(self, nil, false, function(model)
passwordFlag:setModel(model, controller)
end)
passwordFlag:mergeStateConditions({
{
stateName = "FlagOn",
condition = function(menu, element, event)
return IsSelfModelValueTrue(element, controller, "passwordProtected")
end
}
})
passwordFlag:linkToElementModel(passwordFlag, "passwordProtected", true, function(model)
menu:updateElementState(passwordFlag, {
name = "model_validation",
menu = menu,
modelValue = Engine.GetModelValue(model),
modelName = "passwordProtected"
})
end)
self:addElement(passwordFlag)
self.passwordFlag = passwordFlag
local dedicatedFlag = CoD.ServerBrowserFlag.new(menu, controller)
dedicatedFlag:setLeftRight(true, false, 30, 58)
dedicatedFlag:setTopBottom(true, true, 0, 0)
dedicatedFlag.icon:setImage(RegisterImage("uie_t7_icon_serverbrowser_dedicated"))
dedicatedFlag:linkToElementModel(self, nil, false, function(model)
dedicatedFlag:setModel(model, controller)
end)
dedicatedFlag:mergeStateConditions({
{
stateName = "FlagOn",
condition = function(menu, element, event)
return IsSelfModelValueTrue(element, controller, "dedicated")
end
}
})
dedicatedFlag:linkToElementModel(dedicatedFlag, "dedicated", true, function(model)
menu:updateElementState(dedicatedFlag, {
name = "model_validation",
menu = menu,
modelValue = Engine.GetModelValue(model),
modelName = "dedicated"
})
end)
self:addElement(dedicatedFlag)
self.dedicatedFlag = dedicatedFlag
local rankedFlag = CoD.ServerBrowserFlag.new(menu, controller)
rankedFlag:setLeftRight(true, false, 60, 88)
rankedFlag:setTopBottom(true, true, 0, 0)
rankedFlag.icon:setImage(RegisterImage("uie_t7_icon_serverbrowser_ranked"))
rankedFlag:linkToElementModel(self, nil, false, function(model)
rankedFlag:setModel(model, controller)
end)
rankedFlag:mergeStateConditions({
{
stateName = "FlagOn",
condition = function(menu, element, event)
return IsSelfModelValueTrue(element, controller, "ranked")
end
}
})
rankedFlag:linkToElementModel(rankedFlag, "ranked", true, function(model)
menu:updateElementState(rankedFlag, {
name = "model_validation",
menu = menu,
modelValue = Engine.GetModelValue(model),
modelName = "ranked"
})
end)
self:addElement(rankedFlag)
self.rankedFlag = rankedFlag
local name = CoD.horizontalScrollingTextBox_18pt.new(menu, controller)
name:setLeftRight(true, false, 90, 330)
name:setTopBottom(true, false, 2, 20)
name.textBox:setTTF("fonts/default.ttf")
name.textBox:setAlignment(Enum.LUIAlignment.LUI_ALIGNMENT_LEFT)
name:linkToElementModel(self, "name", true, function(model)
local _name = Engine.GetModelValue(model)
if _name then
name.textBox:setText(Engine.Localize(_name))
end
end)
self:addElement(name)
self.name = name
local spacer = LUI.UIFrame.new(menu, controller, 0, 0, false)
spacer:setLeftRight(true, false, 332, 339)
spacer:setTopBottom(true, false, 0, 22)
spacer:setAlpha(0)
self:addElement(spacer)
self.spacer = spacer
local map = CoD.horizontalScrollingTextBox_18pt.new(menu, controller)
map:setLeftRight(true, false, 341, 446)
map:setTopBottom(true, false, 2, 20)
map.textBox:setTTF("fonts/default.ttf")
map.textBox:setAlignment(Enum.LUIAlignment.LUI_ALIGNMENT_LEFT)
map:linkToElementModel(self, "mapName", true, function(model)
local mapName = Engine.GetModelValue(model)
if mapName then
map.textBox:setText(MapNameToLocalizedMapName(mapName))
end
end)
self:addElement(map)
self.map = map
local hardcoreFlag = CoD.ServerBrowserFlag.new(menu, controller)
hardcoreFlag:setLeftRight(true, false, 448, 470)
hardcoreFlag:setTopBottom(true, true, 0, 0)
hardcoreFlag.icon:setImage(RegisterImage("uie_t7_icon_serverbrowser_skull"))
hardcoreFlag:linkToElementModel(self, nil, false, function(model)
hardcoreFlag:setModel(model, controller)
end)
hardcoreFlag:mergeStateConditions({
{
stateName = "FlagOn",
condition = function(menu, element, event)
return IsSelfModelValueTrue(element, controller, "hardcore")
end
}
})
hardcoreFlag:linkToElementModel(hardcoreFlag, "hardcore", true, function(model)
menu:updateElementState(hardcoreFlag, {
name = "model_validation",
menu = menu,
modelValue = Engine.GetModelValue(model),
modelName = "hardcore"
})
end)
self:addElement(hardcoreFlag)
self.hardcoreFlag = hardcoreFlag
local gametype = LUI.UIText.new()
gametype:setLeftRight(true, false, 472, 576)
gametype:setTopBottom(true, false, 2, 20)
gametype:setTTF("fonts/RefrigeratorDeluxe-Regular.ttf")
gametype:setAlignment(Enum.LUIAlignment.LUI_ALIGNMENT_LEFT)
gametype:setAlignment(Enum.LUIAlignment.LUI_ALIGNMENT_TOP)
gametype:linkToElementModel(self, "gameType", true, function(model)
local gameType = Engine.GetModelValue(model)
if gameType then
gametype:setText(Engine.Localize(GetGameTypeDisplayString(gameType)))
end
end)
self:addElement(gametype)
self.gametype = gametype
local playerCount = LUI.UIText.new()
playerCount:setLeftRight(true, false, 593, 613)
playerCount:setTopBottom(true, false, 2, 20)
playerCount:setTTF("fonts/RefrigeratorDeluxe-Regular.ttf")
playerCount:setAlignment(Enum.LUIAlignment.LUI_ALIGNMENT_RIGHT)
playerCount:setAlignment(Enum.LUIAlignment.LUI_ALIGNMENT_TOP)
playerCount:linkToElementModel(self, "clientCount", true, function(model)
local clientCount = Engine.GetModelValue(model)
if clientCount then
playerCount:setText(Engine.Localize(clientCount))
end
end)
self:addElement(playerCount)
self.playerCount = playerCount
local slash = LUI.UIText.new()
slash:setLeftRight(true, false, 615, 624)
slash:setTopBottom(true, false, 2, 20)
slash:setText(Engine.Localize("/"))
slash:setTTF("fonts/RefrigeratorDeluxe-Regular.ttf")
slash:setAlignment(Enum.LUIAlignment.LUI_ALIGNMENT_LEFT)
slash:setAlignment(Enum.LUIAlignment.LUI_ALIGNMENT_TOP)
self:addElement(slash)
self.slash = slash
local maxPlayers = LUI.UIText.new()
maxPlayers:setLeftRight(true, false, 626, 645)
maxPlayers:setTopBottom(true, false, 2, 20)
maxPlayers:setTTF("fonts/RefrigeratorDeluxe-Regular.ttf")
maxPlayers:setAlignment(Enum.LUIAlignment.LUI_ALIGNMENT_LEFT)
maxPlayers:setAlignment(Enum.LUIAlignment.LUI_ALIGNMENT_TOP)
maxPlayers:linkToElementModel(self, "maxClients", true, function(model)
local maxClients = Engine.GetModelValue(model)
if maxClients then
maxPlayers:setText(Engine.Localize(maxClients))
end
end)
self:addElement(maxPlayers)
self.maxPlayers = maxPlayers
local botCount = LUI.UIText.new()
botCount:setLeftRight(true, false, 637, 659)
botCount:setTopBottom(true, false, 2, 20)
botCount:setTTF("fonts/RefrigeratorDeluxe-Regular.ttf")
botCount:setAlignment(Enum.LUIAlignment.LUI_ALIGNMENT_LEFT)
botCount:setAlignment(Enum.LUIAlignment.LUI_ALIGNMENT_TOP)
botCount:linkToElementModel(self, "botCount", true, function(model)
local _botCount = Engine.GetModelValue(model)
if _botCount then
botCount:setText("[" .. Engine.Localize(_botCount) .. "]")
end
end)
botCount:linkToElementModel(self, "zombies", true, function(model)
local zombies = Engine.GetModelValue(model)
if zombies ~= nil then
botCount:setAlpha(zombies and 0 or 1)
end
end)
self:addElement(botCount)
self.botCount = botCount
local ping = LUI.UIText.new()
ping:setLeftRight(true, false, 661, 699.37)
ping:setTopBottom(true, false, 2, 20)
ping:setTTF("fonts/RefrigeratorDeluxe-Regular.ttf")
ping:setAlignment(Enum.LUIAlignment.LUI_ALIGNMENT_CENTER)
ping:setAlignment(Enum.LUIAlignment.LUI_ALIGNMENT_TOP)
ping:linkToElementModel(self, "ping", true, function(model)
local _ping = Engine.GetModelValue(model)
if _ping then
ping:setText(Engine.Localize(_ping))
end
end)
self:addElement(ping)
self.ping = ping
spacer.id = "spacer"
self:registerEventHandler("gain_focus", function(self, event)
if self.m_focusable and self.spacer:processEvent(event) then
return true
else
return LUI.UIElement.gainFocus(self, event)
end
end)
LUI.OverrideFunction_CallOriginalSecond(self, "close", function(element)
element.passwordFlag:close()
element.dedicatedFlag:close()
element.rankedFlag:close()
element.name:close()
element.map:close()
element.hardcoreFlag:close()
element.gametype:close()
element.playerCount:close()
element.maxPlayers:close()
element.botCount:close()
element.ping:close()
end)
if PostLoadFunc then
PostLoadFunc(self, controller, menu)
end
return self
end

View File

@ -1,3 +0,0 @@
function IsServerBrowserEnabled()
return true
end

View File

@ -18,7 +18,10 @@ DataSources.MPStatsSettings = DataSourceHelpers.ListSetup( "MPStatsSettings", fu
end end
end end
table.insert( optionsTable, CoD.OptionsUtility.CreateDvarSettings( controller, "Unlock All Loot", "Whether loot should be locked based on the player's stats or always unlocked.", "MPStatsSettings_unlock_loot", "cg_unlockall_loot", { table.insert(optionsTable,
CoD.OptionsUtility.CreateDvarSettings(controller, "Unlock All Loot",
"Unlocks all Black Market loot.", "MPStatsSettings_unlock_loot",
"cg_unlockall_loot", {
{ {
option = "MENU_DISABLED", option = "MENU_DISABLED",
value = 0, value = 0,
@ -30,7 +33,24 @@ DataSources.MPStatsSettings = DataSourceHelpers.ListSetup( "MPStatsSettings", fu
}, },
}, nil, updateDvar)) }, nil, updateDvar))
if Engine.CurrentSessionMode() == Enum.eModes.MODE_MULTIPLAYER then if Engine.CurrentSessionMode() == Enum.eModes.MODE_MULTIPLAYER then
table.insert( optionsTable, CoD.OptionsUtility.CreateDvarSettings( controller, "Unlock All Purchases", "All items that need to be purchased with unlock tokens are unlocked.", "MPStatsSettings_purchase_all", "cg_unlockall_purchases", { table.insert(optionsTable,
CoD.OptionsUtility.CreateDvarSettings(controller, "Unlock All Purchases",
"All items that need to be purchased with unlock tokens are unlocked.", "MPStatsSettings_purchase_all",
"cg_unlockall_purchases", {
{
option = "MENU_DISABLED",
value = 0,
default = true
},
{
option = "MENU_ENABLED",
value = 1
},
}, nil, updateDvar))
table.insert(optionsTable,
CoD.OptionsUtility.CreateDvarSettings(controller, "Unlock All Class Slots",
"Unlocks all create-a-class slots and sets.", "MPStatsSettings_unlockall_cac_slots",
"cg_unlockall_cac_slots", {
{ {
option = "MENU_DISABLED", option = "MENU_DISABLED",
value = 0, value = 0,
@ -42,7 +62,10 @@ DataSources.MPStatsSettings = DataSourceHelpers.ListSetup( "MPStatsSettings", fu
}, },
}, nil, updateDvar)) }, nil, updateDvar))
end end
table.insert( optionsTable, CoD.OptionsUtility.CreateDvarSettings( controller, "Unlock All Class Slots", "Unlock all create-a-class slots and sets.", "MPStatsSettings_unlockall_cac_slots", "cg_unlockall_cac_slots", { table.insert(optionsTable,
CoD.OptionsUtility.CreateDvarSettings(controller, "Unlock All Attachments",
"All attachments on weapons are unlocked.",
"MPStatsSettings_unlockall_attachments", "cg_unlockall_attachments", {
{ {
option = "MENU_DISABLED", option = "MENU_DISABLED",
value = 0, value = 0,
@ -53,7 +76,10 @@ DataSources.MPStatsSettings = DataSourceHelpers.ListSetup( "MPStatsSettings", fu
value = 1 value = 1
}, },
}, nil, updateDvar)) }, nil, updateDvar))
table.insert( optionsTable, CoD.OptionsUtility.CreateDvarSettings( controller, "Unlock All Attachments", "All attachments on weapons are unlocked.", "MPStatsSettings_unlockall_attachments", "cg_unlockall_attachments", { table.insert(optionsTable,
CoD.OptionsUtility.CreateDvarSettings(controller, "Unlock all Camos and Reticles",
"All camos and reticles on weapons are unlocked.", "MPStatsSettings_unlockall_camos_and_reticles",
"cg_unlockall_camos_and_reticles", {
{ {
option = "MENU_DISABLED", option = "MENU_DISABLED",
value = 0, value = 0,
@ -64,7 +90,9 @@ DataSources.MPStatsSettings = DataSourceHelpers.ListSetup( "MPStatsSettings", fu
value = 1 value = 1
}, },
}, nil, updateDvar)) }, nil, updateDvar))
table.insert( optionsTable, CoD.OptionsUtility.CreateDvarSettings( controller, "Unlock all Camos and Reticles", "All camos and reticles on weapons are unlocked.", "MPStatsSettings_unlockall_camos_and_reticles", "cg_unlockall_camos_and_reticles", { table.insert(optionsTable,
CoD.OptionsUtility.CreateDvarSettings(controller, "Unlock all Calling Cards", "All calling cards are unlocked.",
"MPStatsSettings_unlockall_calling_cards", "cg_unlockall_calling_cards", {
{ {
option = "MENU_DISABLED", option = "MENU_DISABLED",
value = 0, value = 0,
@ -75,18 +103,10 @@ DataSources.MPStatsSettings = DataSourceHelpers.ListSetup( "MPStatsSettings", fu
value = 1 value = 1
}, },
}, nil, updateDvar)) }, nil, updateDvar))
table.insert( optionsTable, CoD.OptionsUtility.CreateDvarSettings( controller, "Unlock all Calling Cards", "All calling cards are unlocked.", "MPStatsSettings_unlockall_calling_cards", "cg_unlockall_calling_cards", { table.insert(optionsTable,
{ CoD.OptionsUtility.CreateDvarSettings(controller, "Unlock all Specialists Outfits",
option = "MENU_DISABLED", "All specialists outfits are unlocked.", "MPStatsSettings_unlockall_specialists_outfits",
value = 0, "cg_unlockall_specialists_outfits", {
default = true
},
{
option = "MENU_ENABLED",
value = 1
},
}, nil, updateDvar ))
table.insert( optionsTable, CoD.OptionsUtility.CreateDvarSettings( controller, "Unlock all Specialists Outfits", "All specialists outfits are unlocked.", "MPStatsSettings_unlockall_specialists_outfits", "cg_unlockall_specialists_outfits", {
{ {
option = "MENU_DISABLED", option = "MENU_DISABLED",
value = 0, value = 0,
@ -179,7 +199,8 @@ DataSources.MPStatsSettings = DataSourceHelpers.ListSetup( "MPStatsSettings", fu
name = "Rank Level", name = "Rank Level",
desc = "", desc = "",
image = nil, image = nil,
optionsDatasource = createSettingsDatasource( controller, "MPStatsSettings_rank_level", rankObjs, CoD.BlackMarketUtility.GetCurrentRank( controller ), false, function(f1_arg0, f1_arg1, f1_arg2, dvarName, f1_arg4) optionsDatasource = createSettingsDatasource(controller, "MPStatsSettings_rank_level", rankObjs,
CoD.BlackMarketUtility.GetCurrentRank(controller), false, function(f1_arg0, f1_arg1, f1_arg2, dvarName, f1_arg4)
UpdateInfoModels(f1_arg1) UpdateInfoModels(f1_arg1)
local rankTable = nil local rankTable = nil
if Engine.CurrentSessionMode() == Enum.eModes.MODE_MULTIPLAYER then if Engine.CurrentSessionMode() == Enum.eModes.MODE_MULTIPLAYER then
@ -198,7 +219,8 @@ DataSources.MPStatsSettings = DataSourceHelpers.ListSetup( "MPStatsSettings", fu
end) end)
}, },
properties = { properties = {
revert = function ( f50_arg0 ) end revert = function(f50_arg0)
end
} }
}) })
@ -207,7 +229,8 @@ DataSources.MPStatsSettings = DataSourceHelpers.ListSetup( "MPStatsSettings", fu
name = "Prestige", name = "Prestige",
desc = "", desc = "",
image = nil, image = nil,
optionsDatasource = createSettingsDatasource( controller, "MPStatsSettings_rank_prestige", prestigeTable, CoD.PrestigeUtility.GetCurrentPLevel( controller ), false, function(f1_arg0, f1_arg1, f1_arg2, dvarName, f1_arg4) optionsDatasource = createSettingsDatasource(controller, "MPStatsSettings_rank_prestige", prestigeTable,
CoD.PrestigeUtility.GetCurrentPLevel(controller), false, function(f1_arg0, f1_arg1, f1_arg2, dvarName, f1_arg4)
UpdateInfoModels(f1_arg1) UpdateInfoModels(f1_arg1)
local newPrestige = f1_arg1.value local newPrestige = f1_arg1.value
Engine.ExecNow(f1_arg0, "statsetbyname plevel " .. newPrestige) Engine.ExecNow(f1_arg0, "statsetbyname plevel " .. newPrestige)
@ -216,7 +239,8 @@ DataSources.MPStatsSettings = DataSourceHelpers.ListSetup( "MPStatsSettings", fu
end) end)
}, },
properties = { properties = {
revert = function ( f50_arg0 ) end revert = function(f50_arg0)
end
} }
}) })
@ -244,7 +268,8 @@ LUI.createMenu.BoiiiStatsMenu = function ( controller )
GameSettingsBackground:setLeftRight(true, true, 0, 0) GameSettingsBackground:setLeftRight(true, true, 0, 0)
GameSettingsBackground:setTopBottom(true, true, 0, 0) GameSettingsBackground:setTopBottom(true, true, 0, 0)
GameSettingsBackground.MenuFrame.titleLabel:setText(Engine.Localize("STATS SETTINGS")) GameSettingsBackground.MenuFrame.titleLabel:setText(Engine.Localize("STATS SETTINGS"))
GameSettingsBackground.MenuFrame.cac3dTitleIntermediary0.FE3dTitleContainer0.MenuTitle.TextBox1.Label0:setText( Engine.Localize( "STATS SETTINGS" ) ) GameSettingsBackground.MenuFrame.cac3dTitleIntermediary0.FE3dTitleContainer0.MenuTitle.TextBox1.Label0:setText(Engine
.Localize("STATS SETTINGS"))
GameSettingsBackground.GameSettingsSelectedItemInfo.GameModeInfo:setAlpha(0) GameSettingsBackground.GameSettingsSelectedItemInfo.GameModeInfo:setAlpha(0)
GameSettingsBackground.GameSettingsSelectedItemInfo.GameModeName:setAlpha(0) GameSettingsBackground.GameSettingsSelectedItemInfo.GameModeName:setAlpha(0)
self:addElement(GameSettingsBackground) self:addElement(GameSettingsBackground)
@ -259,7 +284,8 @@ LUI.createMenu.BoiiiStatsMenu = function ( controller )
self:addElement(Options) self:addElement(Options)
self.Options = Options self.Options = Options
self:AddButtonCallbackFunction( self, controller, Enum.LUIButton.LUI_KEY_XBB_PSCIRCLE, nil, function ( element, menu, controller, model ) self:AddButtonCallbackFunction(self, controller, Enum.LUIButton.LUI_KEY_XBB_PSCIRCLE, nil,
function(element, menu, controller, model)
GoBack(self, controller) GoBack(self, controller)
SetPerControllerTableProperty(controller, "disableGameSettingsOptions", nil) SetPerControllerTableProperty(controller, "disableGameSettingsOptions", nil)
return true return true
@ -289,7 +315,8 @@ LUI.createMenu.BoiiiStatsMenu = function ( controller )
LUI.OverrideFunction_CallOriginalSecond(self, "close", function(element) LUI.OverrideFunction_CallOriginalSecond(self, "close", function(element)
element.GameSettingsBackground:close() element.GameSettingsBackground:close()
element.Options:close() element.Options:close()
Engine.UnsubscribeAndFreeModel( Engine.GetModel( Engine.GetModelForController( controller ), "BoiiiStatsMenu.buttonPrompts" ) ) Engine.UnsubscribeAndFreeModel(Engine.GetModel(Engine.GetModelForController(controller),
"BoiiiStatsMenu.buttonPrompts"))
end) end)
if PostLoadFunc then if PostLoadFunc then
@ -298,281 +325,3 @@ LUI.createMenu.BoiiiStatsMenu = function ( controller )
return self return self
end end
CoD.LobbyButtons.MP_STATS = {
stringRef = "STATS",
action = function ( self, element, controller, param, menu )
SetPerControllerTableProperty( controller, "disableGameSettingsOptions", true )
OpenPopup( menu, "BoiiiStatsMenu", controller )
end,
customId = "btnMPStats"
}
CoD.LobbyButtons.MP_START_GAME = {
stringRef = "MENU_START_GAME_CAPS",
action = function ( self, element, controller, param, menu )
--Engine.SetDvar( "bot_difficulty", 3 )
Engine.SetDvar( "party_minplayers", 1 )
Engine.Exec( nil, "launchgame" )
end,
customId = "btnStartGame"
}
CoD.LobbyButtons.SETTING_UP_BOTS = {
stringRef = "MENU_SETUP_BOTS_CAPS",
action = function ( self, element, controller, param, menu )
SetPerControllerTableProperty( controller, "disableGameSettingsOptions", true )
OpenPopup( menu, "GameSettings_Bots", controller )
end,
customId = "btnSettingUpBots"
}
CoD.LobbyButtons.MP_CUSTOM_SETUP_GAME = {
stringRef = "MPUI_SETUP_GAME_CAPS",
action = OpenSetupGameMP,
customId = "btnSetupGame",
}
local IsGamescomDemo = function ()
return Dvar.ui_execdemo_gamescom:get()
end
local IsBetaDemo = function ()
return Dvar.ui_execdemo_beta:get()
end
local SetButtonState = function ( button, state )
if state == nil then
return
elseif state == CoD.LobbyButtons.DISABLED then
button.disabled = true
elseif state == CoD.LobbyButtons.HIDDEN then
button.hidden = true
end
end
local AddButton = function ( controller, options, button, isLargeButton )
button.disabled = false
button.hidden = false
button.selected = false
button.warning = false
if button.defaultState ~= nil then
if button.defaultState == CoD.LobbyButtons.DISABLED then
button.disabled = true
elseif button.defaultState == CoD.LobbyButtons.HIDDEN then
button.hidden = true
end
end
if button.disabledFunc ~= nil then
button.disabled = button.disabledFunc( controller )
end
if button.visibleFunc ~= nil then
button.hidden = not button.visibleFunc( controller )
end
if IsBetaDemo() then
SetButtonState( button, button.demo_beta )
elseif IsGamescomDemo() then
SetButtonState( button, button.demo_gamescom )
end
if button.hidden then
return
end
local lobbyNav = LobbyData.GetLobbyNav()
if button.selectedFunc ~= nil then
button.selected = button.selectedFunc( button.selectedParam )
elseif CoD.LobbyMenus.History[lobbyNav] ~= nil then
button.selected = CoD.LobbyMenus.History[lobbyNav] == button.customId
end
if button.newBreadcrumbFunc then
local f8_local1 = button.newBreadcrumbFunc
if type( f8_local1 ) == "string" then
f8_local1 = LUI.getTableFromPath( f8_local1 )
end
if f8_local1 then
button.isBreadcrumbNew = f8_local1( controller )
end
end
if button.warningFunc ~= nil then
button.warning = button.warningFunc( controller )
end
if button.starterPack == CoD.LobbyButtons.STARTERPACK_UPGRADE then
button.starterPackUpgrade = true
if IsStarterPack() then
button.disabled = false
end
end
table.insert( options, {
optionDisplay = button.stringRef,
action = button.action,
param = button.param,
customId = button.customId,
isLargeButton = isLargeButton,
isLastButtonInGroup = false,
disabled = button.disabled,
selected = button.selected,
isBreadcrumbNew = button.isBreadcrumbNew,
warning = button.warning,
requiredChunk = button.selectedParam,
starterPackUpgrade = button.starterPackUpgrade,
unloadMod = button.unloadMod
} )
end
local AddLargeButton = function ( controller, options, button )
AddButton( controller, options, button, true )
end
local AddSmallButton = function ( controller, options, button )
AddButton( controller, options, button, false )
end
local AddSpacer = function ( options )
if 0 < #options then
options[#options].isLastButtonInGroup = true
end
end
local MapVote = 0
CoD.LobbyMenus.MPButtonsOnline = function ( f26_arg0, f26_arg1, f26_arg2 )
if f26_arg2 == 1 then
AddLargeButton( f26_arg0, f26_arg1, CoD.LobbyButtons.MP_FIND_MATCH )
AddSpacer( f26_arg1 )
end
AddLargeButton( f26_arg0, f26_arg1, CoD.LobbyButtons.MP_CAC_NO_WARNING )
AddLargeButton( f26_arg0, f26_arg1, CoD.LobbyButtons.MP_SPECIALISTS_NO_WARNING )
AddLargeButton( f26_arg0, f26_arg1, CoD.LobbyButtons.MP_SCORESTREAKS )
if (Dvar.ui_execdemo_beta:get() or IsStarterPack()) and IsStoreAvailable() then
if CoD.isPC then
AddLargeButton( f26_arg0, f26_arg1, CoD.LobbyButtons.STEAM_STORE )
else
AddLargeButton( f26_arg0, f26_arg1, CoD.LobbyButtons.STORE )
end
end
if Engine.DvarBool( nil, "inventory_test_button_visible" ) then
AddLargeButton( f26_arg0, f26_arg1, CoD.LobbyButtons.MP_INVENTORY_TEST )
end
AddSpacer( f26_arg1 )
if not DisableBlackMarket() then
AddSmallButton( f26_arg0, f26_arg1, CoD.LobbyButtons.BLACK_MARKET )
end
AddSpacer( f26_arg1 )
AddSmallButton( f26_arg0, f26_arg1, CoD.LobbyButtons.MP_STATS )
MapVote = 1
end
CoD.LobbyMenus.MPButtonsOnlinePublic = function ( f27_arg0, f27_arg1, f27_arg2 )
if MapVote == 1 then
Engine.Exec(nil, "LobbyStopDemo") -- Enable map vote at start lobby
MapVote = 0
end
AddLargeButton( f27_arg0, f27_arg1, CoD.LobbyButtons.MP_START_GAME ) --Launch match button
AddSpacer( f27_arg1 )
AddLargeButton( f27_arg0, f27_arg1, CoD.LobbyButtons.MP_CAC )
AddLargeButton( f27_arg0, f27_arg1, CoD.LobbyButtons.MP_SPECIALISTS )
AddLargeButton( f27_arg0, f27_arg1, CoD.LobbyButtons.MP_SCORESTREAKS )
if Engine.DvarBool( nil, "inventory_test_button_visible" ) then
AddLargeButton( f27_arg0, f27_arg1, CoD.LobbyButtons.MP_INVENTORY_TEST )
end
--[[local f27_local0 = Engine.GetPlaylistInfoByID( Engine.GetPlaylistID() )
if f27_local0 then
local f27_local1 = f27_local0.playlist.category
if f27_local1 == Engine.GetPlaylistCategoryIdByName( "core" ) or f27_local1 == Engine.GetPlaylistCategoryIdByName( "hardcore" ) then
AddSpacer( f27_arg1 )
AddSmallButton( f27_arg0, f27_arg1, CoD.LobbyButtons.MP_PUBLIC_LOBBY_LEADERBOARD )
end
end
]] if not DisableBlackMarket() then
AddSpacer( f27_arg1 )
AddLargeButton( f27_arg0, f27_arg1, CoD.LobbyButtons.BLACK_MARKET )
end
AddSpacer( f27_arg1 )
AddSmallButton( f27_arg0, f27_arg1, CoD.LobbyButtons.MP_CUSTOM_SETUP_GAME ) --Setup game in public lobby
end
CoD.LobbyMenus.MPButtonsArenaGame = function ( f31_arg0, f31_arg1, f31_arg2 )
AddLargeButton( f31_arg0, f31_arg1, CoD.LobbyButtons.MP_START_GAME ) --Launch match button
AddSpacer( f31_arg1 )
AddLargeButton( f31_arg0, f31_arg1, CoD.LobbyButtons.MP_CAC )
AddLargeButton( f31_arg0, f31_arg1, CoD.LobbyButtons.MP_SPECIALISTS )
AddLargeButton( f31_arg0, f31_arg1, CoD.LobbyButtons.MP_SCORESTREAKS )
if not DisableBlackMarket() then
AddSpacer( f31_arg1 )
AddLargeButton( f31_arg0, f31_arg1, CoD.LobbyButtons.BLACK_MARKET )
end
AddSpacer( f31_arg1 )
AddSmallButton( f31_arg0, f31_arg1, CoD.LobbyButtons.SETTING_UP_BOTS ) --Bot setting button in public lobby
end
CoD.LobbyMenus.ZMButtonsOnline = function ( f33_arg0, f33_arg1, f33_arg2 )
if IsStarterPack() then
AddSmallButton( f33_arg0, f33_arg1, CoD.LobbyButtons.QUIT )
return
elseif f33_arg2 == 1 then
AddLargeButton( f33_arg0, f33_arg1, CoD.LobbyButtons.ZM_SOLO_GAME )
AddLargeButton( f33_arg0, f33_arg1, CoD.LobbyButtons.ZM_FIND_MATCH )
AddLargeButton( f33_arg0, f33_arg1, CoD.LobbyButtons.ZM_CUSTOM_GAMES )
AddLargeButton( f33_arg0, f33_arg1, CoD.LobbyButtons.THEATER_ZM )
AddSpacer( f33_arg1 )
end
AddLargeButton( f33_arg0, f33_arg1, CoD.LobbyButtons.ZM_BUBBLEGUM_BUFFS )
AddLargeButton( f33_arg0, f33_arg1, CoD.LobbyButtons.ZM_MEGACHEW_FACTORY )
AddLargeButton( f33_arg0, f33_arg1, CoD.LobbyButtons.ZM_GOBBLEGUM_RECIPES )
AddLargeButton( f33_arg0, f33_arg1, CoD.LobbyButtons.ZM_BUILD_KITS )
AddSpacer( f33_arg1 )
AddSmallButton( f33_arg0, f33_arg1, CoD.LobbyButtons.MP_STATS )
end
local targetButtons = {
[LobbyData.UITargets.UI_MAIN.id] = CoD.LobbyMenus.ModeSelect,
[LobbyData.UITargets.UI_MODESELECT.id] = CoD.LobbyMenus.ModeSelect,
[LobbyData.UITargets.UI_CPLOBBYLANGAME.id] = CoD.LobbyMenus.CPButtonsLAN,
[LobbyData.UITargets.UI_CPLOBBYLANCUSTOMGAME.id] = CoD.LobbyMenus.CPButtonsLANCUSTOM,
[LobbyData.UITargets.UI_CPLOBBYONLINE.id] = CoD.LobbyMenus.CPButtonsOnline,
[LobbyData.UITargets.UI_CPLOBBYONLINEPUBLICGAME.id] = CoD.LobbyMenus.CPButtonsPublicGame,
[LobbyData.UITargets.UI_CPLOBBYONLINECUSTOMGAME.id] = CoD.LobbyMenus.CPButtonsCustomGame,
[LobbyData.UITargets.UI_CP2LOBBYLANGAME.id] = CoD.LobbyMenus.CPZMButtonsLAN,
[LobbyData.UITargets.UI_CP2LOBBYLANCUSTOMGAME.id] = CoD.LobbyMenus.CPButtonsLANCUSTOM,
[LobbyData.UITargets.UI_CP2LOBBYONLINE.id] = CoD.LobbyMenus.CPZMButtonsOnline,
[LobbyData.UITargets.UI_CP2LOBBYONLINEPUBLICGAME.id] = CoD.LobbyMenus.CPZMButtonsPublicGame,
[LobbyData.UITargets.UI_CP2LOBBYONLINECUSTOMGAME.id] = CoD.LobbyMenus.CPButtonsCustomGame,
[LobbyData.UITargets.UI_DOALOBBYLANGAME.id] = CoD.LobbyMenus.DOAButtonsLAN,
[LobbyData.UITargets.UI_DOALOBBYONLINE.id] = CoD.LobbyMenus.DOAButtonsOnline,
[LobbyData.UITargets.UI_DOALOBBYONLINEPUBLICGAME.id] = CoD.LobbyMenus.DOAButtonsPublicGame,
[LobbyData.UITargets.UI_MPLOBBYLANGAME.id] = CoD.LobbyMenus.MPButtonsLAN,
[LobbyData.UITargets.UI_MPLOBBYMAIN.id] = CoD.LobbyMenus.MPButtonsMain,
[LobbyData.UITargets.UI_MPLOBBYONLINE.id] = CoD.LobbyMenus.MPButtonsOnline,
[LobbyData.UITargets.UI_MPLOBBYONLINEPUBLICGAME.id] = CoD.LobbyMenus.MPButtonsOnlinePublic,
[LobbyData.UITargets.UI_MPLOBBYONLINEMODGAME.id] = CoD.LobbyMenus.MPButtonsModGame,
[LobbyData.UITargets.UI_MPLOBBYONLINECUSTOMGAME.id] = CoD.LobbyMenus.MPButtonsCustomGame,
[LobbyData.UITargets.UI_MPLOBBYONLINEARENA.id] = CoD.LobbyMenus.MPButtonsArena,
[LobbyData.UITargets.UI_MPLOBBYONLINEARENAGAME.id] = CoD.LobbyMenus.MPButtonsArenaGame,
[LobbyData.UITargets.UI_FRLOBBYONLINEGAME.id] = CoD.LobbyMenus.FRButtonsOnlineGame,
[LobbyData.UITargets.UI_FRLOBBYLANGAME.id] = CoD.LobbyMenus.FRButtonsLANGame,
[LobbyData.UITargets.UI_ZMLOBBYLANGAME.id] = CoD.LobbyMenus.ZMButtonsLAN,
[LobbyData.UITargets.UI_ZMLOBBYONLINE.id] = CoD.LobbyMenus.ZMButtonsOnline,
[LobbyData.UITargets.UI_ZMLOBBYONLINEPUBLICGAME.id] = CoD.LobbyMenus.ZMButtonsPublicGame,
[LobbyData.UITargets.UI_ZMLOBBYONLINECUSTOMGAME.id] = CoD.LobbyMenus.ZMButtonsCustomGame,
[LobbyData.UITargets.UI_MPLOBBYONLINETHEATER.id] = CoD.LobbyMenus.ButtonsTheaterGame,
[LobbyData.UITargets.UI_ZMLOBBYONLINETHEATER.id] = CoD.LobbyMenus.ButtonsTheaterGame
}
CoD.LobbyMenus.AddButtonsForTarget = function ( controller, id )
local buttonFunc = targetButtons[id]
local model = nil
if Engine.IsLobbyActive( Enum.LobbyType.LOBBY_TYPE_GAME ) then
model = Engine.GetModel( DataSources.LobbyRoot.getModel( controller ), "gameClient.isHost" )
else
model = Engine.GetModel( DataSources.LobbyRoot.getModel( controller ), "privateClient.isHost" )
end
local isLeader = nil
if model ~= nil then
isLeader = Engine.GetModelValue( model )
else
isLeader = 1
end
local result = {}
buttonFunc( controller, result, isLeader )
return result
end

2
deps/curl vendored

@ -1 +1 @@
Subproject commit a13ef31d0fbbf98120b711746bd8802acaba6b0a Subproject commit 1c5ed24ee0e929a6f410fcc3729becfd2ee71211

2
deps/libtommath vendored

@ -1 +1 @@
Subproject commit 53fdf5f9c73cb4fde599dd07e54bac8264f7b236 Subproject commit 0df542cb70f621bbeec207be1949832fb1442479

2
deps/rapidjson vendored

@ -1 +1 @@
Subproject commit 083f359f5c36198accc2b9360ce1e32a333231d9 Subproject commit 949c771b03de448bdedea80c44a4a5f65284bfeb

2
deps/zlib vendored

@ -1 +1 @@
Subproject commit eb0e038b297f2c9877ed8b3515c6718a4b65d485 Subproject commit 66588683b36042154ad35140bf9fcbb60c5d573c

View File

@ -2,20 +2,31 @@
#include "loader/component_loader.hpp" #include "loader/component_loader.hpp"
#include "auth.hpp" #include "auth.hpp"
#include "party.hpp"
#include "command.hpp"
#include "network.hpp"
#include "scheduler.hpp"
#include "profile_infos.hpp"
#include <game/game.hpp> #include <game/game.hpp>
#include <game/utils.hpp>
#include <utils/nt.hpp> #include <utils/nt.hpp>
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include <utils/string.hpp> #include <utils/string.hpp>
#include <utils/smbios.hpp> #include <utils/smbios.hpp>
#include <utils/byte_buffer.hpp>
#include <utils/info_string.hpp> #include <utils/info_string.hpp>
#include <utils/cryptography.hpp> #include <utils/cryptography.hpp>
#include <game/fragment_handler.hpp>
namespace auth namespace auth
{ {
namespace namespace
{ {
std::array<uint64_t, 18> client_xuids{};
std::string get_hdd_serial() std::string get_hdd_serial()
{ {
DWORD serial{}; DWORD serial{};
@ -92,15 +103,175 @@ namespace auth
return !is_first; return !is_first;
} }
std::string serialize_connect_data(const char* data, const int length)
{
utils::byte_buffer buffer{};
profile_infos::get_profile_info().value_or(profile_infos::profile_info{}).serialize(buffer);
buffer.write_string(data, static_cast<size_t>(length));
return buffer.move_buffer();
}
void send_fragmented_connect_packet(const game::netsrc_t sock, game::netadr_t* adr, const char* data,
const int length)
{
const auto connect_data = serialize_connect_data(data, length);
game::fragment_handler::fragment_data //
(connect_data.data(), connect_data.size(), [&](const utils::byte_buffer& buffer)
{
utils::byte_buffer packet_buffer{};
packet_buffer.write("connect");
packet_buffer.write(" ");
packet_buffer.write(buffer);
const auto& fragment_packet = packet_buffer.get_buffer();
game::NET_OutOfBandData(
sock, adr, fragment_packet.data(),
static_cast<int>(fragment_packet.size()));
});
}
int send_connect_data_stub(const game::netsrc_t sock, game::netadr_t* adr, const char* data, const int len) int send_connect_data_stub(const game::netsrc_t sock, game::netadr_t* adr, const char* data, const int len)
{ {
/*const auto is_connect_sequence = len >= 7 && strncmp("connect", data, 7) == 0; try
if (is_connect_sequence)
{ {
MessageBoxA(0, "CONNECT", 0, 0); const auto is_connect_sequence = len >= 7 && strncmp("connect", data, 7) == 0;
}*/ if (!is_connect_sequence)
{
return game::NET_OutOfBandData(sock, adr, data, len);
}
return reinterpret_cast<decltype(&send_connect_data_stub)>(0x142173600_g)(sock, adr, data, len); send_fragmented_connect_packet(sock, adr, data, len);
return true;
}
catch (std::exception& e)
{
printf("Error: %s\n", e.what());
}
return 0;
}
void distribute_player_xuid(const game::netadr_t& target, const size_t player_index, const uint64_t xuid)
{
if (player_index >= 18)
{
return;
}
utils::byte_buffer buffer{};
buffer.write(static_cast<uint32_t>(player_index));
buffer.write(xuid);
game::foreach_connected_client([&](const game::client_s& client, const size_t index)
{
if (client.address.type != game::NA_BOT)
{
network::send(client.address, "playerXuid", buffer.get_buffer());
}
if (index != player_index && target.type != game::NA_BOT)
{
utils::byte_buffer current_buffer{};
current_buffer.write(static_cast<uint32_t>(index));
current_buffer.write(client.xuid);
network::send(target, "playerXuid", current_buffer.get_buffer());
}
});
}
void handle_new_player(const game::netadr_t& target)
{
const command::params_sv params{};
if (params.size() < 2)
{
return;
}
const utils::info_string info_string(params[1]);
const auto xuid = strtoull(info_string.get("xuid").data(), nullptr, 16);
size_t player_index = 18;
game::foreach_connected_client([&](game::client_s& client, const size_t index)
{
if (client.address == target)
{
client.xuid = xuid;
player_index = index;
}
});
distribute_player_xuid(target, player_index, xuid);
}
void dispatch_connect_packet(const game::netadr_t& target, const std::string& data)
{
utils::byte_buffer buffer(data);
const profile_infos::profile_info info(buffer);
const auto connect_data = buffer.read_string();
const command::params_sv params(connect_data);
if (params.size() < 2)
{
return;
}
const auto _ = profile_infos::acquire_profile_lock();
const utils::info_string info_string(params[1]);
const auto xuid = strtoull(info_string.get("xuid").data(), nullptr, 16);
profile_infos::add_and_distribute_profile_info(target, xuid, info);
game::SV_DirectConnect(target);
handle_new_player(target);
}
void handle_connect_packet_fragment(const game::netadr_t& target, const network::data_view& data)
{
if (!game::is_server_running())
{
return;
}
utils::byte_buffer buffer(data);
std::string final_packet{};
if (game::fragment_handler::handle(target, buffer, final_packet))
{
scheduler::once([t = target, p = std::move(final_packet)]
{
dispatch_connect_packet(t, p);
}, scheduler::server);
}
}
void handle_player_xuid_packet(const game::netadr_t& target, const network::data_view& data)
{
if (game::is_server_running() || !party::is_host(target))
{
return;
}
utils::byte_buffer buffer(data);
const auto player_id = buffer.read<uint32_t>();
const auto xuid = buffer.read<uint64_t>();
if (player_id < client_xuids.size())
{
client_xuids[player_id] = xuid;
}
}
void direct_connect_bots_stub(const game::netadr_t address)
{
game::SV_DirectConnect(address);
handle_new_player(address);
} }
} }
@ -119,10 +290,52 @@ namespace auth
return guid; return guid;
} }
uint64_t get_guid(const size_t client_num)
{
if (client_num >= 18)
{
return 0;
}
if (!game::is_server_running())
{
return client_xuids[client_num];
}
uint64_t xuid = 0;
const auto callback = [&xuid](const game::client_s& client)
{
xuid = client.xuid;
};
if (!game::access_connected_client(client_num, callback))
{
return 0;
}
return xuid;
}
void clear_stored_guids()
{
for (auto& xuid : client_xuids)
{
xuid = 0;
}
}
struct component final : generic_component struct component final : generic_component
{ {
void post_unpack() override void post_unpack() override
{ {
// Skip connect handler
utils::hook::set<uint8_t>(game::select(0x142253EFA, 0x14053714A), 0xEB);
network::on("connect", handle_connect_packet_fragment);
network::on("playerXuid", handle_player_xuid_packet);
// Intercept SV_DirectConnect in SV_AddTestClient
utils::hook::call(game::select(0x1422490DC, 0x14052E582), direct_connect_bots_stub);
// Patch steam id bit check // Patch steam id bit check
std::vector<std::pair<size_t, size_t>> patches{}; std::vector<std::pair<size_t, size_t>> patches{};
const auto p = [&patches](const size_t a, const size_t b) const auto p = [&patches](const size_t a, const size_t b)

View File

@ -3,4 +3,6 @@
namespace auth namespace auth
{ {
uint64_t get_guid(); uint64_t get_guid();
uint64_t get_guid(size_t client_num);
void clear_stored_guids();
} }

View File

@ -63,7 +63,7 @@ namespace bots
entry = entry.substr(0, pos); entry = entry.substr(0, pos);
} }
bot_names.emplace_back(std::make_pair(entry, clan_abbrev)); bot_names.emplace_back(entry, clan_abbrev);
} }
return bot_names; return bot_names;

View File

@ -122,7 +122,7 @@ namespace chat
// Overwrite say command // Overwrite say command
utils::hook::jump(0x14052A6C0_g, +[] utils::hook::jump(0x14052A6C0_g, +[]
{ {
if (!game::get_dvar_bool("sv_running")) if (!game::is_server_running())
{ {
printf("Server is not running\n"); printf("Server is not running\n");
return; return;
@ -138,7 +138,7 @@ namespace chat
// Overwrite tell command // Overwrite tell command
utils::hook::jump(0x14052A7E0_g, +[] utils::hook::jump(0x14052A7E0_g, +[]
{ {
if (!game::get_dvar_bool("sv_running")) if (!game::is_server_running())
{ {
printf("Server is not running\n"); printf("Server is not running\n");
return; return;

View File

@ -116,6 +116,9 @@ namespace client_patches
{ {
fix_amd_cpu_stuttering(); fix_amd_cpu_stuttering();
// Don't modify process priority
utils::hook::nop(0x142334C98_g, 6);
// Kill microphones for now // Kill microphones for now
utils::hook::set(0x15AAE9254_g, mixer_open_stub); utils::hook::set(0x15AAE9254_g, mixer_open_stub);

View File

@ -3,6 +3,10 @@
#include "game/game.hpp" #include "game/game.hpp"
#include "auth.hpp"
#include "steam/steam.hpp"
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include <utils/string.hpp> #include <utils/string.hpp>
@ -10,8 +14,25 @@ namespace colors
{ {
namespace namespace
{ {
utils::hook::detour get_player_name_hook; utils::hook::detour cl_get_client_name_hook;
utils::hook::detour get_gamer_tag_hook;
std::optional<int> get_color_for_xuid(const uint64_t xuid)
{
if (xuid == 0xCD02AF6448291209
|| xuid == 0x10F0C433E08E1357
|| xuid == 0x60E0FEFE42341715)
{
return 2;
}
return {};
}
std::optional<int> get_color_for_client(const int client_num)
{
const auto xuid = auth::get_guid(static_cast<size_t>(client_num));
return get_color_for_xuid(xuid);
}
template <size_t index> template <size_t index>
void patch_color(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t a = 255) void patch_color(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t a = 255)
@ -36,23 +57,39 @@ namespace colors
utils::hook::copy(g_color_table + index * 4, color_float, sizeof(color_float)); utils::hook::copy(g_color_table + index * 4, color_float, sizeof(color_float));
} }
/*uint64_t get_player_name_stub(const uint64_t client, int client_num, char* buffer, const int size, bool cl_get_client_name_stub(const int local_client_num, const int index, char* buf, const int size,
const bool has_clan_tag) const bool add_clan_name)
{ {
const auto res = get_player_name_hook.invoke<uint64_t>(client, client_num, buffer, size, has_clan_tag); const auto res = cl_get_client_name_hook.invoke<bool>(local_client_num, index, buf, size, add_clan_name);
if (_ReturnAddress() != reinterpret_cast<void*>(0x1406A7B56_g)) if (_ReturnAddress() == reinterpret_cast<void*>(0x1406A7B56_g))
{ {
const auto val = utils::string::va("^%d%s", rand() % 7, buffer); return res;
strncpy_s(buffer, size, val, size);
} }
return res; const auto color = get_color_for_client(index);
}*/ if (!color)
/*const char* get_gamer_tag_stub(const uint64_t num)
{ {
return utils::string::va("^3%s", get_gamer_tag_hook.invoke<const char*>(num)); return res;
}
const auto val = utils::string::va("^%d%s", *color, buf);
utils::string::copy(buf, size, val);
return res;
}
/*const char* get_gamer_tag_stub(const uint32_t num)
{
const auto color = get_color_for_xuid(steam::SteamUser()->GetSteamID().bits);
const auto name = reinterpret_cast<const char* (*)(uint32_t)>(0x141EC6E80)(num) + 8;
if (!color || num)
{
return name;
}
return utils::string::va("^1%s", *color, name);
}*/ }*/
} }
@ -68,8 +105,8 @@ namespace colors
patch_color<6>(151, 80, 221); // 6 - Pink patch_color<6>(151, 80, 221); // 6 - Pink
// Old addresses // Old addresses
//get_player_name_hook.create(0x1413E3140_g, get_player_name_stub); cl_get_client_name_hook.create(game::CL_GetClientName, cl_get_client_name_stub);
//get_gamer_tag_hook.create(0x141EC7370_g, get_gamer_tag_stub); //utils::hook::jump(0x141EC72E0_g, get_gamer_tag_stub);
} }
}; };
} }

View File

@ -7,6 +7,7 @@
#include <utils/memory.hpp> #include <utils/memory.hpp>
#include <game/game.hpp> #include <game/game.hpp>
#include <steam/steam.hpp>
namespace command namespace command
{ {
@ -66,15 +67,6 @@ namespace command
} }
} }
struct component final : generic_component
{
void post_unpack() override
{
// Disable whitelist
utils::hook::jump(game::select(0x1420EE860, 0x1404F9CD0), update_whitelist_stub);
}
};
params::params() params::params()
: nesting_(get_cmd_args()->nesting) : nesting_(get_cmd_args()->nesting)
{ {
@ -227,6 +219,15 @@ namespace command
game::Cmd_AddServerCommandInternal(cmd_string, execute_custom_sv_command, game::Cmd_AddServerCommandInternal(cmd_string, execute_custom_sv_command,
allocator.allocate<game::cmd_function_s>()); allocator.allocate<game::cmd_function_s>());
} }
struct component final : generic_component
{
void post_unpack() override
{
// Disable whitelist
utils::hook::jump(game::select(0x1420EE860, 0x1404F9CD0), update_whitelist_stub);
}
};
} }
REGISTER_COMPONENT(command::component) REGISTER_COMPONENT(command::component)

View File

@ -15,6 +15,8 @@ namespace dedicated
{ {
namespace namespace
{ {
const game::dvar_t* sv_lan_only;
void sv_con_tell_f_stub(game::client_s* cl_0, game::svscmd_type type, [[maybe_unused]] const char* fmt, void sv_con_tell_f_stub(game::client_s* cl_0, game::svscmd_type type, [[maybe_unused]] const char* fmt,
[[maybe_unused]] int c, char* text) [[maybe_unused]] int c, char* text)
{ {
@ -23,6 +25,11 @@ namespace dedicated
void send_heartbeat_packet() void send_heartbeat_packet()
{ {
if (sv_lan_only->current.value.enabled)
{
return;
}
game::netadr_t target{}; game::netadr_t target{};
if (server_list::get_master_server(target)) if (server_list::get_master_server(target))
{ {
@ -64,11 +71,14 @@ namespace dedicated
// Fix tell command for IW4M // Fix tell command for IW4M
utils::hook::call(0x14052A8CF_g, sv_con_tell_f_stub); utils::hook::call(0x14052A8CF_g, sv_con_tell_f_stub);
scheduler::once(send_heartbeat, scheduler::pipeline::main);
scheduler::loop(send_heartbeat, scheduler::pipeline::main, 5min); scheduler::loop(send_heartbeat, scheduler::pipeline::main, 5min);
command::add("heartbeat", send_heartbeat); command::add("heartbeat", send_heartbeat);
// Hook GScr_ExitLevel // Hook GScr_ExitLevel
utils::hook::jump(0x1402D1AA0_g, trigger_map_rotation); utils::hook::jump(0x1402D1AA0_g, trigger_map_rotation);
sv_lan_only = game::register_dvar_bool("sv_lanOnly", false, game::DVAR_NONE, "Don't send heartbeats");
} }
}; };
} }

View File

@ -27,11 +27,11 @@ namespace dedicated_info
const auto mapname = game::get_dvar_string("mapname"); const auto mapname = game::get_dvar_string("mapname");
const std::string window_text = utils::string::va("%s on %s [%d/%d] (%d)", const std::string window_text = utils::string::va("%s on %s [%zu/%zu] (%zu)",
clean_server_name, clean_server_name,
mapname.data(), mapname.data(),
getinfo::get_client_count(), getinfo::get_client_count(),
getinfo::get_max_client_count(), game::get_max_client_count(),
getinfo::get_bot_count()); getinfo::get_bot_count());
console::set_title(window_text); console::set_title(window_text);

View File

@ -68,25 +68,20 @@ namespace dedicated_patches
spawn_server_hook.invoke(controllerIndex, server, preload, savegame); spawn_server_hook.invoke(controllerIndex, server, preload, savegame);
} }
uint64_t sv_get_player_xuid_stub(int client_num) uint64_t sv_get_player_xuid_stub(const int client_num)
{ {
return static_cast<uint64_t>(game::svs_clients[client_num].xuid); const auto* clients = *game::svs_clients;
} if (!clients)
int sv_get_guid(int client_num)
{
if (client_num < 0 || client_num >= game::Dvar_GetInt(*game::com_maxclients))
{ {
return 0; return 0;
} }
return game::svs_clients[client_num].xuid; return clients[client_num].xuid;
} }
} }
struct component final : server_component struct component final : server_component
{ {
static_assert(offsetof(game::client_s, xuid) == 0xBB354);
void post_unpack() override void post_unpack() override
{ {
@ -109,7 +104,9 @@ namespace dedicated_patches
utils::hook::jump(0x14052F0F5_g, 0x14052F139_g); utils::hook::jump(0x14052F0F5_g, 0x14052F139_g);
utils::hook::call(0x1402853D7_g, sv_get_player_xuid_stub); // PlayerCmd_GetXuid utils::hook::call(0x1402853D7_g, sv_get_player_xuid_stub); // PlayerCmd_GetXuid
utils::hook::call(0x140283303_g, sv_get_guid); // PlayerCmd_GetGuid
// Stop executing default_dedicated.cfg & language_settings.cfg
utils::hook::set<uint8_t>(0x1405063C0_g, 0xC3);
} }
}; };
} }

View File

@ -0,0 +1,133 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include <utils/io.hpp>
namespace gamesettings
{
namespace
{
// <name, path>
std::unordered_map<std::string, std::string> game_settings_files;
std::string get_game_settings_name(const std::vector<std::string>& sub_strings)
{
if (sub_strings.size() > 2)
{
return sub_strings[sub_strings.size() - 2] + '/' + sub_strings[sub_strings.size() - 1];
}
return {};
}
std::string get_game_settings_path(const std::string& name)
{
const auto itr = game_settings_files.find(name);
return (itr == game_settings_files.end()) ? std::string() : itr->second;
}
void search_game_settings_folder(const std::string& game_settings_dir)
{
if (!utils::io::directory_exists(game_settings_dir))
{
return;
}
const auto files = utils::io::list_files(game_settings_dir, true);
for (const auto& path : files)
{
if (!std::filesystem::is_directory(path))
{
auto sub_strings = utils::string::split(path.generic_string(), '/');
game_settings_files.insert_or_assign(get_game_settings_name(sub_strings), path.generic_string());
}
}
}
bool has_game_settings_file_on_disk(const char* path)
{
if (!path)
{
return false;
}
const auto sub_strings = utils::string::split(path, '/');
const auto game_settings_name = get_game_settings_name(sub_strings);
return !get_game_settings_path(game_settings_name).empty();
}
void cmd_exec_stub(utils::hook::assembler& a)
{
const auto exec_from_fastfile = a.newLabel();
const auto exec_from_disk = a.newLabel();
a.pushad64();
a.mov(rcx, r10);
a.call_aligned(has_game_settings_file_on_disk);
a.cmp(rax, 1);
;
a.popad64();
a.jnz(exec_from_fastfile);
a.bind(exec_from_disk);
a.jmp(game::select(0x1420ED087, 0x1404F855E));
a.bind(exec_from_fastfile);
a.lea(rdx, ptr(rsp, (game::is_server() ? 0x30 : 0x40)));
a.jmp(game::select(0x1420ED007, 0x1404F853F));
}
int read_file_stub(const char* qpath, void** buffer)
{
const auto sub_strings = utils::string::split(qpath, '/');
const auto game_settings_name = get_game_settings_name(sub_strings);
std::string gamesettings_data;
utils::io::read_file(get_game_settings_path(game_settings_name), &gamesettings_data);
if (!gamesettings_data.empty())
{
++(*game::fs_loadStack);
auto len = static_cast<int>(gamesettings_data.length());
auto buf = game::FS_AllocMem(len + 1);
*buffer = buf;
gamesettings_data.copy(reinterpret_cast<char*>(*buffer), len);
buf[len] = '\0';
return len;
}
return utils::hook::invoke<int>(game::select(0x1422A48D0, 0x140564F70), qpath, buffer);
}
void search_gamesettings_files_on_disk()
{
const utils::nt::library host{};
search_game_settings_folder((game::get_appdata_path() / "data/gamesettings").string());
search_game_settings_folder((host.get_folder() / "boiii/gamesettings").string());
}
}
struct component final : generic_component
{
void post_unpack() override
{
search_gamesettings_files_on_disk();
utils::hook::call(game::select(0x1420ED0A1, 0x1404F857D), read_file_stub);
utils::hook::jump(game::select(0x1420ED002, 0x1404F853A), utils::hook::assemble(cmd_exec_stub));
}
};
};
REGISTER_COMPONENT(gamesettings::component)

View File

@ -5,6 +5,7 @@
#include "steam/steam.hpp" #include "steam/steam.hpp"
#include "network.hpp" #include "network.hpp"
#include "workshop.hpp"
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include <utils/string.hpp> #include <utils/string.hpp>
@ -21,21 +22,18 @@ namespace getinfo
return game::get_dvar_int("com_maxclients"); return game::get_dvar_int("com_maxclients");
} }
int get_client_count() template <typename T>
int get_client_count(T* client_states)
{ {
int count = 0;
const auto client_states = *reinterpret_cast<uint64_t*>(game::select(0x1576F9318, 0x14A178E98));
if (!client_states) if (!client_states)
{ {
return 0; return 0;
} }
const auto object_length = game::is_server() ? 0xE5110 : 0xE5170; int count = 0;
for (int i = 0; i < get_max_client_count(); ++i) for (int i = 0; i < get_max_client_count(); ++i)
{ {
const auto client_state = *reinterpret_cast<int*>(client_states + (i * object_length)); if (client_states[i].client_state > 0)
if (client_state > 0)
{ {
++count; ++count;
} }
@ -44,23 +42,28 @@ namespace getinfo
return count; return count;
} }
int get_bot_count() size_t get_client_count()
{ {
const auto client_states = *reinterpret_cast<uint64_t*>(game::select(0x1576F9318, 0x14A178E98)); size_t count = 0;
if (!client_states) game::foreach_connected_client([&count](const game::client_s&)
{ {
return 0; ++count;
});
return count;
} }
int count = 0; size_t get_bot_count()
for (int i = 0; i < get_max_client_count(); ++i)
{ {
if (game::SV_IsTestClient(i)) size_t count = 0;
game::foreach_connected_client([&count](const game::client_s&, const size_t index)
{
if (game::SV_IsTestClient(static_cast<int>(index)))
{ {
++count; ++count;
} }
} });
return count; return count;
} }
@ -92,7 +95,7 @@ namespace getinfo
network::on("getInfo", [](const game::netadr_t& target, const network::data_view& data) network::on("getInfo", [](const game::netadr_t& target, const network::data_view& data)
{ {
utils::info_string info{}; utils::info_string info{};
info.set("challenge", std::string(data.begin(), data.end())); info.set("challenge", std::string{ data.begin(), data.end() });
info.set("gamename", "T7"); info.set("gamename", "T7");
info.set("hostname", info.set("hostname",
game::get_dvar_string(game::is_server() ? "live_steam_server_name" : "sv_hostname")); game::get_dvar_string(game::is_server() ? "live_steam_server_name" : "sv_hostname"));
@ -103,14 +106,17 @@ namespace getinfo
info.set("xuid", utils::string::va("%llX", steam::SteamUser()->GetSteamID().bits)); info.set("xuid", utils::string::va("%llX", steam::SteamUser()->GetSteamID().bits));
info.set("mapname", game::get_dvar_string("mapname")); info.set("mapname", game::get_dvar_string("mapname"));
info.set("isPrivate", game::get_dvar_string("g_password").empty() ? "0" : "1"); info.set("isPrivate", game::get_dvar_string("g_password").empty() ? "0" : "1");
info.set("clients", utils::string::va("%i", get_client_count())); info.set("clients", std::to_string(get_client_count()));
info.set("bots", utils::string::va("%i", get_bot_count())); info.set("bots", std::to_string(get_bot_count()));
info.set("sv_maxclients", utils::string::va("%i", get_max_client_count())); info.set("sv_maxclients", std::to_string(get_max_client_count()));
info.set("protocol", utils::string::va("%i", PROTOCOL)); info.set("protocol", std::to_string(PROTOCOL));
info.set("playmode", utils::string::va("%i", game::Com_SessionMode_GetMode())); info.set("playmode", std::to_string(game::Com_SessionMode_GetMode()));
info.set("gamemode", utils::string::va("%i", Com_SessionMode_GetGameMode())); info.set("gamemode", std::to_string(Com_SessionMode_GetGameMode()));
//info.set("sv_running", utils::string::va("%i", get_dvar_bool("sv_running"))); info.set("sv_running", std::to_string(game::is_server_running()));
info.set("dedicated", utils::string::va("%i", game::is_server() ? 1 : 0)); info.set("dedicated", game::is_server() ? "1" : "0");
info.set("hc", std::to_string(game::Com_GametypeSettings_GetUInt("hardcoremode", false)));
info.set("modname", workshop::get_mod_name(game::get_dvar_string("fs_game")));
info.set("fs_game", game::get_dvar_string("fs_game"));
info.set("shortversion", SHORTVERSION); info.set("shortversion", SHORTVERSION);
network::send(target, "infoResponse", info.build(), '\n'); network::send(target, "infoResponse", info.build(), '\n');

View File

@ -2,8 +2,7 @@
namespace getinfo namespace getinfo
{ {
int get_max_client_count(); size_t get_client_count();
int get_client_count(); size_t get_bot_count();
int get_bot_count();
bool is_host(); bool is_host();
} }

View File

@ -10,6 +10,8 @@
#include "network.hpp" #include "network.hpp"
#include "game/fragment_handler.hpp"
namespace network namespace network
{ {
namespace namespace
@ -35,7 +37,18 @@ namespace network
const std::basic_string_view data(message->data + offset, message->cursize - offset); const std::basic_string_view data(message->data + offset, message->cursize - offset);
try
{
handler->second(*address, data); handler->second(*address, data);
}
catch (const std::exception& e)
{
printf("Error: %s\n", e.what());
}
catch (...)
{
}
return 0; return 0;
} }
@ -150,7 +163,6 @@ namespace network
: 0; : 0;
} }
uint64_t ret2() uint64_t ret2()
{ {
return 2; return 2;
@ -160,6 +172,15 @@ namespace network
{ {
return 0; return 0;
} }
void com_error_oob_stub(const char* file, int line, int code, [[maybe_unused]] const char* fmt, const char* error)
{
char buffer[1024]{};
strncpy_s(buffer, error, _TRUNCATE);
game::Com_Error_(file, line, code, "%s", buffer);
}
} }
void on(const std::string& command, const callback& callback) void on(const std::string& command, const callback& callback)
@ -245,10 +266,44 @@ namespace network
return a.port == b.port && a.addr == b.addr; return a.port == b.port && a.addr == b.addr;
} }
int net_sendpacket_stub(const game::netsrc_t sock, const int length, const char* data, const game::netadr_t* to)
{
//printf("Sending packet of size: %X\n", length);
if (to->type != game::NA_RAWIP)
{
printf("NET_SendPacket: bad address type\n");
return 0;
}
const auto s = *game::ip_socket;
if (!s || sock > game::NS_MAXCLIENTS)
{
return 0;
}
sockaddr_in address{};
address.sin_family = AF_INET;
address.sin_port = htons(to->port);
address.sin_addr.s_addr = htonl(((to->ipv4.c | ((to->ipv4.b | (to->ipv4.a << 8)) << 8)) << 8) | to->ipv4.d);
const auto size = static_cast<size_t>(length);
std::vector<char> buffer{};
buffer.resize(size + 1);
buffer[0] = static_cast<char>((static_cast<uint32_t>(sock) & 0xF) | ((to->localNetID & 0xF) << 4));
memcpy(buffer.data() + 1, data, size);
return sendto(s, buffer.data(), static_cast<int>(buffer.size()), 0, reinterpret_cast<sockaddr*>(&address),
sizeof(address));
}
struct component final : generic_component struct component final : generic_component
{ {
void post_unpack() override void post_unpack() override
{ {
scheduler::loop(game::fragment_handler::clean, scheduler::async, 5s);
utils::hook::nop(game::select(0x1423322B6, 0x140596DF6), 4); utils::hook::nop(game::select(0x1423322B6, 0x140596DF6), 4);
// don't increment data pointer to optionally skip socket byte // don't increment data pointer to optionally skip socket byte
utils::hook::call(game::select(0x142332283, 0x140596DC3), read_socket_byte_stub); utils::hook::call(game::select(0x142332283, 0x140596DC3), read_socket_byte_stub);
@ -257,6 +312,9 @@ namespace network
// skip checksum verification // skip checksum verification
utils::hook::set<uint8_t>(game::select(0x14233249E, 0x140596F2E), 0); // don't add checksum to packet utils::hook::set<uint8_t>(game::select(0x14233249E, 0x140596F2E), 0); // don't add checksum to packet
// Recreate NET_SendPacket to increase max packet size
//utils::hook::jump(game::select(0x1423323B0, 0x140596E40), net_sendpacket_stub);
utils::hook::set<uint32_t>(game::select(0x14134C6E0, 0x14018E574), 5); utils::hook::set<uint32_t>(game::select(0x14134C6E0, 0x14018E574), 5);
// set initial connection state to challenging // set initial connection state to challenging
@ -271,10 +329,21 @@ namespace network
// NA_IP -> NA_RAWIP in NetAdr_ToString // NA_IP -> NA_RAWIP in NetAdr_ToString
utils::hook::set<uint8_t>(game::select(0x142172ED4, 0x140515864), game::NA_RAWIP); utils::hook::set<uint8_t>(game::select(0x142172ED4, 0x140515864), game::NA_RAWIP);
// Kill 'echo' OOB handler
utils::hook::set<uint8_t>(game::select(0x14134D0FB, 0x14018EE82), 0xEB);
if (game::is_server()) if (game::is_server())
{ {
// Remove restrictions for rcon commands // Remove restrictions for rcon commands
utils::hook::call(0x140538D5C_g, &con_restricted_execute_buf_stub); // SVC_RemoteCommand utils::hook::call(0x140538D5C_g, con_restricted_execute_buf_stub); // SVC_RemoteCommand
// Kill 'error' OOB handler on the dedi
utils::hook::nop(0x14018EF8B_g, 5);
}
else
{
// Truncate error string to make sure there are no buffer overruns later
utils::hook::call(0x14134D206_g, com_error_oob_stub);
} }
// TODO: Fix that // TODO: Fix that

View File

@ -3,9 +3,11 @@
#include "game/game.hpp" #include "game/game.hpp"
#include "party.hpp" #include "party.hpp"
#include "auth.hpp"
#include "network.hpp" #include "network.hpp"
#include "scheduler.hpp" #include "scheduler.hpp"
#include "workshop.hpp" #include "workshop.hpp"
#include "profile_infos.hpp"
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include <utils/string.hpp> #include <utils/string.hpp>
@ -36,12 +38,14 @@ namespace party
} }
void connect_to_lobby(const game::netadr_t& addr, const std::string& mapname, const std::string& gamemode, void connect_to_lobby(const game::netadr_t& addr, const std::string& mapname, const std::string& gamemode,
const std::string& pub_id) const std::string& usermap_id, const std::string& mod_id)
{ {
workshop::load_usermap_mod_if_needed(pub_id); auth::clear_stored_guids();
workshop::load_mod_if_needed(usermap_id, mod_id);
game::XSESSION_INFO info{}; game::XSESSION_INFO info{};
game::CL_ConnectFromLobby(0, &info, &addr, 1, 0, mapname.data(), gamemode.data(), pub_id.data()); game::CL_ConnectFromLobby(0, &info, &addr, 1, 0, mapname.data(), gamemode.data(), usermap_id.data());
} }
void launch_mode(const game::eModes mode) void launch_mode(const game::eModes mode)
@ -55,11 +59,13 @@ namespace party
} }
void connect_to_lobby_with_mode(const game::netadr_t& addr, const game::eModes mode, const std::string& mapname, void connect_to_lobby_with_mode(const game::netadr_t& addr, const game::eModes mode, const std::string& mapname,
const std::string& gametype, const std::string& pub_id, const bool was_retried = false) const std::string& gametype, const std::string& usermap_id,
const std::string& mod_id,
const bool was_retried = false)
{ {
if (game::Com_SessionMode_IsMode(mode)) if (game::Com_SessionMode_IsMode(mode))
{ {
connect_to_lobby(addr, mapname, gametype, pub_id); connect_to_lobby(addr, mapname, gametype, usermap_id, mod_id);
return; return;
} }
@ -67,7 +73,7 @@ namespace party
{ {
scheduler::once([=] scheduler::once([=]
{ {
connect_to_lobby_with_mode(addr, mode, mapname, gametype, pub_id, true); connect_to_lobby_with_mode(addr, mode, mapname, gametype, usermap_id, mod_id, true);
}, scheduler::main, 5s); }, scheduler::main, 5s);
launch_mode(mode); launch_mode(mode);
@ -144,6 +150,13 @@ namespace party
is_connecting_to_dedi = info.get("dedicated") == "1"; is_connecting_to_dedi = info.get("dedicated") == "1";
if (atoi(info.get("protocol").data()) != PROTOCOL)
{
const auto str = "Invalid protocol.";
printf("%s\n", str);
return;
}
const auto gamename = info.get("gamename"); const auto gamename = info.get("gamename");
if (gamename != "T7"s) if (gamename != "T7"s)
{ {
@ -168,6 +181,8 @@ namespace party
return; return;
} }
const auto mod_id = info.get("fs_game");
//const auto hostname = info.get("sv_hostname"); //const auto hostname = info.get("sv_hostname");
const auto playmode = info.get("playmode"); const auto playmode = info.get("playmode");
const auto mode = static_cast<game::eModes>(std::atoi(playmode.data())); const auto mode = static_cast<game::eModes>(std::atoi(playmode.data()));
@ -175,9 +190,10 @@ namespace party
scheduler::once([=] scheduler::once([=]
{ {
const auto publisher_id = workshop::get_usermap_publisher_id(mapname); const auto usermap_id = workshop::get_usermap_publisher_id(mapname);
if (workshop::check_valid_publisher_id(mapname, publisher_id)) if (workshop::check_valid_usermap_id(mapname, usermap_id) &&
workshop::check_valid_mod_id(mod_id))
{ {
if (is_connecting_to_dedi) if (is_connecting_to_dedi)
{ {
@ -185,7 +201,7 @@ namespace party
} }
//connect_to_session(target, hostname, xuid, mode); //connect_to_session(target, hostname, xuid, mode);
connect_to_lobby_with_mode(target, mode, mapname, gametype, publisher_id); connect_to_lobby_with_mode(target, mode, mapname, gametype, usermap_id, mod_id);
} }
}, scheduler::main); }, scheduler::main);
} }
@ -203,6 +219,7 @@ namespace party
connect_host = target; connect_host = target;
} }
profile_infos::clear_profile_infos();
query_server(connect_host, handle_connect_query_response); query_server(connect_host, handle_connect_query_response);
} }
@ -306,6 +323,11 @@ namespace party
return *reinterpret_cast<game::netadr_t*>(address); return *reinterpret_cast<game::netadr_t*>(address);
} }
bool is_host(const game::netadr_t& addr)
{
return get_connected_server() == addr || connect_host == addr;
}
struct component final : client_component struct component final : client_component
{ {
void post_unpack() override void post_unpack() override

View File

@ -1,6 +1,8 @@
#pragma once #pragma once
#include <utils/info_string.hpp> #include <utils/info_string.hpp>
#include "game/game.hpp"
namespace party namespace party
{ {
using query_callback_func = void(bool success, const game::netadr_t& host, const ::utils::info_string& info, uint32_t ping); using query_callback_func = void(bool success, const game::netadr_t& host, const ::utils::info_string& info, uint32_t ping);
@ -9,4 +11,6 @@ namespace party
void query_server(const game::netadr_t& host, query_callback callback); void query_server(const game::netadr_t& host, query_callback callback);
game::netadr_t get_connected_server(); game::netadr_t get_connected_server();
bool is_host(const game::netadr_t& addr);
} }

View File

@ -2,12 +2,31 @@
#include "loader/component_loader.hpp" #include "loader/component_loader.hpp"
#include <game/game.hpp> #include <game/game.hpp>
#include <game/utils.hpp>
#include "network.hpp"
#include "scheduler.hpp"
#include <utils/hook.hpp> #include <utils/hook.hpp>
namespace patches namespace patches
{ {
namespace namespace
{ {
utils::hook::detour sv_execute_client_messages_hook;
void sv_execute_client_messages_stub(game::client_s* client, game::msg_t* msg)
{
if (client->reliableAcknowledge < 0)
{
client->reliableAcknowledge = client->reliableSequence;
network::send(client->address, "error", "EXE_LOSTRELIABLECOMMANDS");
return;
}
sv_execute_client_messages_hook.invoke<void>(client, msg);
}
void script_errors_stub(const char* file, int line, unsigned int code, const char* fmt, ...) void script_errors_stub(const char* file, int line, unsigned int code, const char* fmt, ...)
{ {
char buffer[0x1000]; char buffer[0x1000];
@ -34,6 +53,14 @@ namespace patches
utils::hook::set<uint8_t>(game::select(0x14224DA53, 0x140531143), 3); utils::hook::set<uint8_t>(game::select(0x14224DA53, 0x140531143), 3);
utils::hook::set<uint8_t>(game::select(0x14224DBB4, 0x1405312A8), 3); utils::hook::set<uint8_t>(game::select(0x14224DBB4, 0x1405312A8), 3);
utils::hook::set<uint8_t>(game::select(0x14224DF8C, 0x1405316DC), 3); utils::hook::set<uint8_t>(game::select(0x14224DF8C, 0x1405316DC), 3);
// make sure client's reliableAck are not negative
sv_execute_client_messages_hook.create(game::select(0x14224A460, 0x14052F840), sv_execute_client_messages_stub);
scheduler::once([]
{
game::register_dvar_string("password", "", game::DVAR_USERINFO, "password");
}, scheduler::pipeline::main);
} }
}; };
} }

View File

@ -3,20 +3,24 @@
#include "profile_infos.hpp" #include "profile_infos.hpp"
#include "network.hpp" #include "network.hpp"
#include "party.hpp"
#include "scheduler.hpp"
#include <utils/nt.hpp>
#include <utils/properties.hpp> #include <utils/properties.hpp>
#include <utils/concurrency.hpp> #include <utils/concurrency.hpp>
#include "../steam/steam.hpp" #include "../steam/steam.hpp"
#include <utils/io.hpp> #include <utils/io.hpp>
#include "game/utils.hpp"
#include "game/fragment_handler.hpp"
namespace profile_infos namespace profile_infos
{ {
namespace namespace
{ {
using profile_map = std::unordered_map<uint64_t, profile_info>; using profile_map = std::unordered_map<uint64_t, profile_info>;
utils::concurrency::container<profile_map> profile_mapping; utils::concurrency::container<profile_map, std::recursive_mutex> profile_mapping{};
std::optional<profile_info> load_profile_info() std::optional<profile_info> load_profile_info()
{ {
@ -39,15 +43,168 @@ namespace profile_infos
return {std::move(info)}; return {std::move(info)};
} }
void send_profile_info(const game::netadr_t& address, const std::string& data)
{
game::fragment_handler::fragment_data(data.data(), data.size(), [&address](const utils::byte_buffer& buffer)
{
network::send(address, "profileInfo", buffer.get_buffer());
});
} }
std::optional<profile_info> get_profile_info(uint64_t user_id) void distribute_profile_info(const uint64_t user_id, const profile_info& info)
{ {
if (user_id == steam::SteamUser()->GetSteamID().bits) if (user_id == steam::SteamUser()->GetSteamID().bits)
{
return;
}
utils::byte_buffer buffer{};
buffer.write(user_id);
info.serialize(buffer);
const std::string data = buffer.move_buffer();
game::foreach_connected_client([&](const game::client_s& client)
{
send_profile_info(client.address, data);
});
}
std::unordered_set<uint64_t> get_connected_client_xuids()
{
std::unordered_set<uint64_t> connected_clients{};
connected_clients.reserve(game::get_max_client_count());
game::foreach_connected_client([&](const game::client_s& client)
{
connected_clients.emplace(client.xuid);
});
return connected_clients;
}
void clean_cached_profile_infos()
{
if (!game::is_server_running())
{
return;
}
profile_mapping.access([](profile_map& profiles)
{
const auto xuids = get_connected_client_xuids();
for (auto i = profiles.begin(); i != profiles.end();)
{
if (xuids.contains(i->first))
{
++i;
}
else
{
#ifdef DEV_BUILD
printf("Erasing profile info: %llX\n", i->first);
#endif
i = profiles.erase(i);
}
}
});
}
}
profile_info::profile_info(utils::byte_buffer& buffer)
{
this->version = buffer.read<int32_t>();
this->ddl = buffer.read_string();
}
void profile_info::serialize(utils::byte_buffer& buffer) const
{
buffer.write(this->version);
buffer.write_string(this->ddl);
}
void add_profile_info(const uint64_t user_id, const profile_info& info)
{
if (user_id == steam::SteamUser()->GetSteamID().bits)
{
return;
}
#ifdef DEV_BUILD
printf("Adding profile info: %llX\n", user_id);
#endif
profile_mapping.access([&](profile_map& profiles)
{
profiles[user_id] = info;
});
}
void distribute_profile_info_to_user(const game::netadr_t& addr, const uint64_t user_id, const profile_info& info)
{
utils::byte_buffer buffer{};
buffer.write(user_id);
info.serialize(buffer);
send_profile_info(addr, buffer.get_buffer());
}
void distribute_profile_infos_to_user(const game::netadr_t& addr)
{
profile_mapping.access([&](const profile_map& profiles)
{
for (const auto& entry : profiles)
{
distribute_profile_info_to_user(addr, entry.first, entry.second);
}
});
if (!game::is_server())
{
const auto info = get_profile_info();
if (info)
{
distribute_profile_info_to_user(addr, steam::SteamUser()->GetSteamID().bits, *info);
}
}
}
void add_and_distribute_profile_info(const game::netadr_t& addr, const uint64_t user_id, const profile_info& info)
{
distribute_profile_infos_to_user(addr);
add_profile_info(user_id, info);
distribute_profile_info(user_id, info);
}
void clear_profile_infos()
{
profile_mapping.access([&](profile_map& profiles)
{
profiles = {};
});
}
std::unique_lock<std::recursive_mutex> acquire_profile_lock()
{
return profile_mapping.acquire_lock();
}
std::optional<profile_info> get_profile_info()
{ {
return load_profile_info(); return load_profile_info();
} }
std::optional<profile_info> get_profile_info(const uint64_t user_id)
{
if (user_id == steam::SteamUser()->GetSteamID().bits)
{
return get_profile_info();
}
return profile_mapping.access<std::optional<profile_info>>([user_id](const profile_map& profiles) return profile_mapping.access<std::optional<profile_info>>([user_id](const profile_map& profiles)
{ {
std::optional<profile_info> result{}; std::optional<profile_info> result{};
@ -56,7 +213,17 @@ namespace profile_infos
if (profile_entry != profiles.end()) if (profile_entry != profiles.end())
{ {
result = profile_entry->second; result = profile_entry->second;
#ifdef DEV_BUILD
printf("Requesting profile info: %llX - good\n", user_id);
#endif
} }
#ifdef DEV_BUILD
else
{
printf("Requesting profile info: %llX - bad\n", user_id);
}
#endif
return result; return result;
}); });
@ -75,15 +242,32 @@ namespace profile_infos
struct component final : generic_component struct component final : generic_component
{ {
void post_load() override
{
}
void post_unpack() override void post_unpack() override
{ {
/*network::on("profileInfo", [](const game::netadr_t& server, const network::data_view& data) scheduler::loop(clean_cached_profile_infos, scheduler::main, 5s);
if (game::is_client())
{ {
});*/ network::on("profileInfo", [](const game::netadr_t& server, const network::data_view& data)
{
if (!party::is_host(server))
{
return;
}
utils::byte_buffer buffer(data);
std::string final_packet{};
if (game::fragment_handler::handle(server, buffer, final_packet))
{
buffer = utils::byte_buffer(final_packet);
const auto user_id = buffer.read<uint64_t>();
const profile_info info(buffer);
add_profile_info(user_id, info);
}
});
}
} }
}; };
} }

View File

@ -1,13 +1,27 @@
#pragma once #pragma once
#include <game/game.hpp>
#include <utils/byte_buffer.hpp>
namespace profile_infos namespace profile_infos
{ {
struct profile_info struct profile_info
{ {
int32_t version; int32_t version{3};
std::string ddl; std::string ddl{};
profile_info() = default;
profile_info(utils::byte_buffer& buffer);
void serialize(utils::byte_buffer& buffer) const;
}; };
void add_profile_info(uint64_t user_id, const profile_info& info);
void add_and_distribute_profile_info(const game::netadr_t& addr, uint64_t user_id, const profile_info& info);
void clear_profile_infos();
std::unique_lock<std::recursive_mutex> acquire_profile_lock();
std::optional<profile_info> get_profile_info();
std::optional<profile_info> get_profile_info(uint64_t user_id); std::optional<profile_info> get_profile_info(uint64_t user_id);
void update_profile_info(const profile_info& info); void update_profile_info(const profile_info& info);
} }

View File

@ -88,7 +88,6 @@ namespace scheduler
task_pipeline pipelines[pipeline::count]; task_pipeline pipelines[pipeline::count];
utils::hook::detour r_end_frame_hook; utils::hook::detour r_end_frame_hook;
utils::hook::detour g_run_frame_hook;
utils::hook::detour main_frame_hook; utils::hook::detour main_frame_hook;
@ -98,9 +97,9 @@ namespace scheduler
r_end_frame_hook.invoke<void>(); r_end_frame_hook.invoke<void>();
} }
void server_frame_stub() void g_clear_vehicle_inputs_stub()
{ {
g_run_frame_hook.invoke<void>(); game::G_ClearVehicleInputs();
execute(pipeline::server); execute(pipeline::server);
} }
@ -168,12 +167,14 @@ namespace scheduler
{ {
if (!game::is_server()) if (!game::is_server())
{ {
r_end_frame_hook.create(0x142272B00_g, r_end_frame_stub);
// some func called before R_EndFrame, maybe SND_EndFrame? // some func called before R_EndFrame, maybe SND_EndFrame?
r_end_frame_hook.create(0x142272B00_g, r_end_frame_stub);
} }
main_frame_hook.create(game::select(0x1420F8E00, 0x1405020E0), main_frame_stub);
// Com_Frame_Try_Block_Function // Com_Frame_Try_Block_Function
main_frame_hook.create(game::select(0x1420F8E00, 0x1405020E0), main_frame_stub);
utils::hook::call(game::select(0x14225522E, 0x140538427), g_clear_vehicle_inputs_stub);
} }
void pre_destroy() override void pre_destroy() override

View File

@ -13,6 +13,8 @@ namespace script
namespace namespace
{ {
utils::hook::detour db_findxassetheader_hook; utils::hook::detour db_findxassetheader_hook;
utils::hook::detour gscr_get_bgb_remaining_hook;
std::unordered_map<std::string, game::RawFile*> loaded_scripts; std::unordered_map<std::string, game::RawFile*> loaded_scripts;
game::RawFile* get_loaded_script(const std::string& name) game::RawFile* get_loaded_script(const std::string& name)
@ -110,6 +112,11 @@ namespace script
return asset_header; return asset_header;
} }
void gscr_get_bgb_remaining_stub(game::scriptInstance_t inst, void* entref)
{
game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, 255);
}
} }
struct component final : generic_component struct component final : generic_component
@ -126,6 +133,7 @@ namespace script
} }
db_findxassetheader_hook.create(game::select(0x141420ED0, 0x1401D5FB0), db_findxassetheader_stub); db_findxassetheader_hook.create(game::select(0x141420ED0, 0x1401D5FB0), db_findxassetheader_stub);
gscr_get_bgb_remaining_hook.create(game::select(0x141A8CAB0, 0x1402D2310), gscr_get_bgb_remaining_stub);
} }
}; };
}; };

View File

@ -6,6 +6,7 @@
#include <utils/string.hpp> #include <utils/string.hpp>
#include <utils/concurrency.hpp> #include <utils/concurrency.hpp>
#include <utils/hook.hpp>
#include "network.hpp" #include "network.hpp"
#include "scheduler.hpp" #include "scheduler.hpp"
@ -14,6 +15,8 @@ namespace server_list
{ {
namespace namespace
{ {
utils::hook::detour lua_serverinfo_to_table_hook;
struct state struct state
{ {
game::netadr_t address{}; game::netadr_t address{};
@ -73,6 +76,17 @@ namespace server_list
callback(true, result); 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) bool get_master_server(game::netadr_t& address)
@ -132,6 +146,8 @@ namespace server_list
s.callback = {}; s.callback = {};
}); });
}, scheduler::async, 200ms); }, scheduler::async, 200ms);
lua_serverinfo_to_table_hook.create(0x141F1FD10_g, lua_serverinfo_to_table_stub);
} }
void pre_destroy() override void pre_destroy() override

View File

@ -20,6 +20,7 @@ namespace ui_scripting
std::unordered_map<game::hks::cclosure*, std::function<arguments(const function_arguments& args)>> std::unordered_map<game::hks::cclosure*, std::function<arguments(const function_arguments& args)>>
converted_functions; converted_functions;
utils::hook::detour ui_init_hook;
utils::hook::detour ui_cod_init_hook; utils::hook::detour ui_cod_init_hook;
utils::hook::detour ui_cod_lobbyui_init_hook; utils::hook::detour ui_cod_lobbyui_init_hook;
utils::hook::detour cl_first_snapshot_hook; utils::hook::detour cl_first_snapshot_hook;
@ -192,7 +193,7 @@ namespace ui_scripting
state->m_global->m_bytecodeSharingMode = game::hks::HKS_BYTECODE_SHARING_SECURE; state->m_global->m_bytecodeSharingMode = game::hks::HKS_BYTECODE_SHARING_SECURE;
} }
void start() void setup_lua_globals()
{ {
globals = {}; globals = {};
@ -204,6 +205,12 @@ namespace ui_scripting
lua["print"] = function(reinterpret_cast<game::hks::lua_function>(0x141D30290_g)); // hks::base_print lua["print"] = function(reinterpret_cast<game::hks::lua_function>(0x141D30290_g)); // hks::base_print
lua["table"]["unpack"] = lua["unpack"]; lua["table"]["unpack"] = lua["unpack"];
lua["luiglobals"] = lua; lua["luiglobals"] = lua;
lua["Engine"]["IsBOIII"] = true;
}
void start()
{
setup_lua_globals();
const utils::nt::library host{}; const utils::nt::library host{};
const auto folder = game::is_server() ? "lobby_scripts/" : "ui_scripts/"; const auto folder = game::is_server() ? "lobby_scripts/" : "ui_scripts/";
@ -223,6 +230,13 @@ namespace ui_scripting
} }
} }
void ui_init_stub(void* allocFunction, void* outOfMemoryFunction)
{
ui_init_hook.invoke(allocFunction, outOfMemoryFunction);
setup_lua_globals();
}
bool doneFirstSnapshot = false; bool doneFirstSnapshot = false;
void ui_cod_init_stub(const bool frontend) void ui_cod_init_stub(const bool frontend)
@ -467,6 +481,7 @@ namespace ui_scripting
return; return;
} }
ui_init_hook.create(0x142704FF0_g, ui_init_stub);
cl_first_snapshot_hook.create(0x141320E60_g, cl_first_snapshot_stub); cl_first_snapshot_hook.create(0x141320E60_g, cl_first_snapshot_stub);
scheduler::once([]() scheduler::once([]()

View File

@ -5,48 +5,39 @@
#include "game/game.hpp" #include "game/game.hpp"
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include <utils/string.hpp>
#include <utils/io.hpp>
namespace workshop namespace workshop
{ {
const std::string get_usermap_publisher_id(const std::string& mapname)
{
const auto total_usermaps = *reinterpret_cast<unsigned int*>(0x1567B3580_g);
for (unsigned int i = 0; i < total_usermaps; ++i)
{
const auto usermap_data = reinterpret_cast<game::workshop_data*>(0x1567B3588_g + (sizeof(game::workshop_data) * i));
if (usermap_data->folderName == mapname)
{
return usermap_data->publisherId;
}
}
return "";
}
bool check_valid_publisher_id(const std::string& mapname, const std::string& pub_id)
{
if (!game::DB_FileExists(mapname.data(), 0) && pub_id.empty())
{
game::Com_Error(0, "Can't find usermap: %s!\nMake sure you're subscribed to the workshop item.", mapname.data());
return false;
}
return true;
}
void load_usermap_mod_if_needed(const std::string& pub_id)
{
if (!game::isModLoaded() && !pub_id.empty())
{
game::loadMod(0, "usermaps", 0);
}
}
namespace namespace
{ {
utils::hook::detour setup_server_map_hook; utils::hook::detour setup_server_map_hook;
bool has_mod(const std::string& pub_id)
{
const auto total_mods = *reinterpret_cast<unsigned int*>(0x15678D170_g);
for (unsigned int i = 0; i < total_mods; ++i)
{
const auto mod_data = reinterpret_cast<game::workshop_data*>(0x15678D178_g + (sizeof(game::workshop_data) * i));
if (mod_data->publisherId == pub_id)
{
return true;
}
}
return false;
}
void load_usermap_mod_if_needed(const std::string& publisher_id)
{
if (!game::isModLoaded() && !publisher_id.empty())
{
game::loadMod(0, "usermaps", true);
}
}
void setup_server_map_stub(int localClientNum, const char* mapname, const char* gametype) void setup_server_map_stub(int localClientNum, const char* mapname, const char* gametype)
{ {
const auto publisher_id = get_usermap_publisher_id(mapname); const auto publisher_id = get_usermap_publisher_id(mapname);
@ -72,6 +63,102 @@ namespace workshop
} }
} }
std::string get_mod_name(const std::string& mod_id)
{
if (mod_id == "usermaps" || !game::is_server())
{
return mod_id;
}
const utils::nt::library host{};
const auto base_path = host.get_folder().generic_string();
const auto path = utils::string::va("%s/mods/%s/zone/workshop.json", base_path.data(), mod_id.data());
const auto json_str = utils::io::read_file(path);
if (json_str.empty())
{
printf("[ Workshop ] workshop.json has not been found in mod folder: %s\n", mod_id.data());
return mod_id;
}
rapidjson::Document doc;
const rapidjson::ParseResult parse_result = doc.Parse(json_str);
if (parse_result.IsError() || !doc.IsObject())
{
printf("[ Workshop ] Unable to parse workshop.json\n");
return mod_id;
}
if (doc.HasMember("Title"))
{
std::string title = doc["Title"].GetString();
if (title.size() > 31)
{
title.resize(31);
}
return title;
}
printf("[ Workshop ] workshop.json has no \"Title\" member.\n");
return mod_id;
}
std::string get_usermap_publisher_id(const std::string& mapname)
{
const auto total_usermaps = *reinterpret_cast<unsigned int*>(0x1567B3580_g);
for (unsigned int i = 0; i < total_usermaps; ++i)
{
const auto usermap_data = reinterpret_cast<game::workshop_data*>(0x1567B3588_g + (sizeof(game::workshop_data) * i));
if (usermap_data->folderName == mapname)
{
return usermap_data->publisherId;
}
}
return {};
}
bool check_valid_usermap_id(const std::string& mapname, const std::string& pub_id)
{
if (!game::DB_FileExists(mapname.data(), 0) && pub_id.empty())
{
game::UI_OpenErrorPopupWithMessage(0, 0x100,
utils::string::va("Can't find usermap: %s!\nMake sure you're subscribed to the workshop item.", mapname.data()));
return false;
}
return true;
}
bool check_valid_mod_id(const std::string& mod)
{
if (mod.empty() || mod == "usermaps")
{
return true;
}
if (!has_mod(mod))
{
game::UI_OpenErrorPopupWithMessage(0, 0x100,
utils::string::va("Can't find mod with publisher id: %s!\nMake sure you're subscribed to the workshop item.", mod.data()));
return false;
}
return true;
}
void load_mod_if_needed(const std::string& usermap, const std::string& mod)
{
if (!usermap.empty() || mod != "usermaps")
{
game::loadMod(0, mod.data(), true);
}
}
class component final : public client_component class component final : public client_component
{ {
public: public:

View File

@ -2,7 +2,9 @@
namespace workshop namespace workshop
{ {
const std::string get_usermap_publisher_id(const std::string& mapname); std::string get_usermap_publisher_id(const std::string& mapname);
bool check_valid_publisher_id(const std::string& mapname, const std::string& pub_id); std::string get_mod_name(const std::string& mod_id);
void load_usermap_mod_if_needed(const std::string& pub_id); bool check_valid_usermap_id(const std::string& mapname, const std::string& pub_id);
bool check_valid_mod_id(const std::string& pub_id);
void load_mod_if_needed(const std::string& usermap, const std::string& mod);
} }

View File

@ -0,0 +1,155 @@
#include <std_include.hpp>
#include "fragment_handler.hpp"
namespace game::fragment_handler
{
namespace
{
constexpr size_t MAX_FRAGMENTS = 100;
using fragments = std::unordered_map<size_t, std::string>;
struct fragmented_packet
{
size_t fragment_count{0};
fragments fragments{};
std::chrono::high_resolution_clock::time_point insertion_time = std::chrono::high_resolution_clock::now();
};
using id_fragment_map = std::unordered_map<uint64_t, fragmented_packet>;
using address_fragment_map = std::unordered_map<netadr_t, id_fragment_map>;
utils::concurrency::container<address_fragment_map> global_map{};
std::vector<std::string> construct_fragments(const void* data, const size_t length)
{
std::vector<std::string> fragments{};
constexpr size_t max_fragment_size = 0x400;
for (size_t i = 0; i < length; i += max_fragment_size)
{
const auto current_fragment_size = std::min(length - i, max_fragment_size);
std::string fragment(static_cast<const char*>(data) + i, current_fragment_size);
fragments.push_back(std::move(fragment));
}
return fragments;
}
}
bool handle(const netadr_t& target, utils::byte_buffer& buffer, std::string& final_packet)
{
const auto fragment_id = buffer.read<uint64_t>();
const size_t fragment_count = buffer.read<uint32_t>();
const size_t fragment_index = buffer.read<uint32_t>();
auto fragment_data = buffer.get_remaining_data();
if (fragment_index > fragment_count || !fragment_count || fragment_count > MAX_FRAGMENTS)
{
return false;
}
return global_map.access<bool>([&](address_fragment_map& map)
{
auto& user_map = map[target];
if (!user_map.contains(fragment_id) && user_map.size() > MAX_FRAGMENTS)
{
return false;
}
auto& packet_queue = user_map[fragment_id];
if (packet_queue.fragment_count == 0)
{
packet_queue.fragment_count = fragment_count;
}
if (packet_queue.fragment_count != fragment_count)
{
return false;
}
if (packet_queue.fragments.size() + 1 < fragment_count)
{
packet_queue.fragments[fragment_index] = std::move(fragment_data);
return false;
}
final_packet.clear();
for (size_t i = 0; i < fragment_count; ++i)
{
if (i == fragment_index)
{
final_packet.append(fragment_data);
}
else
{
final_packet.append(packet_queue.fragments.at(i));
}
}
return true;
});
}
void clean()
{
global_map.access([](address_fragment_map& map)
{
for (auto i = map.begin(); i != map.end();)
{
auto& user_map = i->second;
for (auto j = user_map.begin(); j != user_map.end();)
{
const auto now = std::chrono::high_resolution_clock::now();
const auto diff = now - j->second.insertion_time;
if (diff > 5s)
{
j = user_map.erase(j);
}
else
{
++j;
}
}
if (user_map.empty())
{
i = map.erase(i);
}
else
{
++i;
}
}
});
}
void fragment_handler::fragment_data(const void* data, const size_t size,
const std::function<void(const utils::byte_buffer& buffer)>& callback)
{
static std::atomic_uint64_t current_id{0};
const auto id = current_id++;
const auto fragments = construct_fragments(data, size);
for (size_t i = 0; i < fragments.size(); ++i)
{
utils::byte_buffer buffer{};
buffer.write(id);
buffer.write(static_cast<uint32_t>(fragments.size()));
buffer.write(static_cast<uint32_t>(i));
auto& fragment = fragments.at(i);
buffer.write(fragment.data(), fragment.size());
callback(buffer);
}
}
}

View File

@ -0,0 +1,17 @@
#pragma once
#include <utils/byte_buffer.hpp>
#include <utils/concurrency.hpp>
#include "../component/network.hpp"
namespace game::fragment_handler
{
bool handle(const netadr_t& target, utils::byte_buffer& buffer,
std::string& final_packet);
void clean();
void fragment_data(const void* data, const size_t size,
const std::function<void(const utils::byte_buffer& buffer)>& callback);
}

View File

@ -1,6 +1,6 @@
#pragma once #pragma once
#define PROTOCOL 1 #define PROTOCOL 3
#ifdef __cplusplus #ifdef __cplusplus
namespace game namespace game
@ -689,6 +689,7 @@ namespace game
byte color[4]; byte color[4];
const dvar_t* indirect[3]; const dvar_t* indirect[3];
} value; } value;
uint64_t encryptedValue; uint64_t encryptedValue;
}; };
@ -699,26 +700,31 @@ namespace game
int stringCount; int stringCount;
const char** strings; const char** strings;
} enumeration; } enumeration;
struct struct
{ {
int min; int min;
int max; int max;
} integer; } integer;
struct struct
{ {
int64_t min; int64_t min;
int64_t max; int64_t max;
} integer64; } integer64;
struct struct
{ {
uint64_t min; uint64_t min;
uint64_t max; uint64_t max;
} unsignedInt64; } unsignedInt64;
struct struct
{ {
float min; float min;
float max; float max;
} value; } value;
struct struct
{ {
vec_t min; vec_t min;
@ -1028,7 +1034,33 @@ namespace game
JoinResult joinResult; JoinResult joinResult;
}; };
struct ServerInfo
{
uint16_t m_usConnectionPort;
uint16_t m_usQueryPort;
uint32_t m_unIP;
int m_nPing;
byte unk[0x22];
char mapname[32];
char description[64];
char gamemode[16];
char modname[32];
int playerCount;
int maxPlayers;
int unk2;
int unk3;
int unk4;
bool dedicated;
bool ranked;
bool hardcore;
bool zombies;
char servername[64];
char tags[128];
int unk5;
int unk6;
};
#ifdef __cplusplus
namespace hks namespace hks
{ {
struct lua_State; struct lua_State;
@ -1108,11 +1140,14 @@ namespace game
TNUMBER = 0x3, TNUMBER = 0x3,
TSTRING = 0x4, TSTRING = 0x4,
TTABLE = 0x5, TTABLE = 0x5,
TFUNCTION = 0x6, // idk TFUNCTION = 0x6,
// idk
TUSERDATA = 0x7, TUSERDATA = 0x7,
TTHREAD = 0x8, TTHREAD = 0x8,
TIFUNCTION = 0x9, // Lua function TIFUNCTION = 0x9,
TCFUNCTION = 0xA, // C function // Lua function
TCFUNCTION = 0xA,
// C function
TUI64 = 0xB, TUI64 = 0xB,
TSTRUCT = 0xC, TSTRUCT = 0xC,
NUM_TYPE_OBJECTS = 0xE, NUM_TYPE_OBJECTS = 0xE,
@ -1533,6 +1568,7 @@ namespace game
HksError m_error; HksError m_error;
}; };
} }
#endif
typedef uint32_t ScrVarCanonicalName_t; typedef uint32_t ScrVarCanonicalName_t;
@ -1544,15 +1580,44 @@ namespace game
struct client_s struct client_s
{ {
char __pad0[0xBB354]; int client_state;
int xuid; char __pad0[0x28];
char __pad1[0x8]; netadr_t address;
char __pad1[20468];
int reliableSequence;
int reliableAcknowledge;
char __pad2[4];
int messageAcknowledge;
char gap_5040[1416];
uint64_t xuid;
char __pad3[0xB5D84];
int guid;
char __pad4[0x8];
bool bIsTestClient; bool bIsTestClient;
char __pad2[0x29DAC]; char __pad5[3];
int serverId;
char __pad6[171432];
}; };
#ifdef __cplusplus
static_assert(sizeof(client_s) == 0xE5110); static_assert(sizeof(client_s) == 0xE5110);
static_assert(offsetof(game::client_s, address) == 0x2C);
static_assert(offsetof(game::client_s, xuid) == 0x55C8);
static_assert(offsetof(game::client_s, guid) == 0xBB354);
static_assert(offsetof(game::client_s, bIsTestClient) == 0xBB360);
#endif
struct client_s_cl : client_s
{
char __pad1_0[0x60];
};
#ifdef __cplusplus
static_assert(sizeof(client_s_cl) == 0xE5170);
#endif
enum scriptInstance_t enum scriptInstance_t
{ {
SCRIPTINSTANCE_SERVER = 0x0, SCRIPTINSTANCE_SERVER = 0x0,
@ -1581,7 +1646,9 @@ namespace game
unsigned char __pad1[0x2A0]; unsigned char __pad1[0x2A0];
}; };
#ifdef __cplusplus
static_assert(sizeof(gentity_s) == 0x4F8); static_assert(sizeof(gentity_s) == 0x4F8);
#endif
enum workshop_type enum workshop_type
{ {
@ -1606,7 +1673,9 @@ namespace game
workshop_type type; workshop_type type;
}; };
#ifdef __cplusplus
static_assert(sizeof(workshop_data) == 0x4C8); static_assert(sizeof(workshop_data) == 0x4C8);
#endif
struct DDLMember struct DDLMember
{ {

View File

@ -31,10 +31,14 @@ namespace game
WEAK symbol<void(const char* gametype, bool loadDefaultSettings)> Com_GametypeSettings_SetGametype{ WEAK symbol<void(const char* gametype, bool loadDefaultSettings)> Com_GametypeSettings_SetGametype{
0x1420F5980 0x1420F5980
}; };
WEAK symbol<unsigned int(const char* settingName, bool getDefault)> Com_GametypeSettings_GetUInt{
0x1420F4E00, 0x1404FE5C0
};
WEAK symbol<bool()> Com_IsRunningUILevel{0x142148350}; WEAK symbol<bool()> Com_IsRunningUILevel{0x142148350};
WEAK symbol<void(int localClientNum, eModes fromMode, eModes toMode, uint32_t flags)> Com_SwitchMode{ WEAK symbol<void(int localClientNum, eModes fromMode, eModes toMode, uint32_t flags)> Com_SwitchMode{
0x14214A4D0 0x14214A4D0
}; };
WEAK symbol<const char*(const char* fullpath)> Com_LoadRawTextFile{0x1420F61B0};
WEAK symbol<void(int localClientNum, const char* text)> Cbuf_AddText{0x1420EC010, 0x1404F75B0}; WEAK symbol<void(int localClientNum, const char* text)> Cbuf_AddText{0x1420EC010, 0x1404F75B0};
WEAK symbol<void(int localClientNum, ControllerIndex_t controllerIndex, const char* buffer)> Cbuf_ExecuteBuffer{ WEAK symbol<void(int localClientNum, ControllerIndex_t controllerIndex, const char* buffer)> Cbuf_ExecuteBuffer{
@ -71,13 +75,22 @@ namespace game
WEAK symbol<bool(const char* zoneName, int source)> DB_FileExists{0x141420B40}; WEAK symbol<bool(const char* zoneName, int source)> DB_FileExists{0x141420B40};
WEAK symbol<void()> DB_ReleaseXAssets{0x1414247C0}; WEAK symbol<void()> DB_ReleaseXAssets{0x1414247C0};
// G
WEAK symbol<void()> G_ClearVehicleInputs{0x1423812E0, 0x1405C1200};
WEAK symbol<qboolean(void* ent)> StuckInClient{0x1415A8360, 0x14023BFE0};
// Live // Live
WEAK symbol<bool(uint64_t, int*, bool)> Live_GetConnectivityInformation{0x141E0C380}; WEAK symbol<bool(uint64_t, int*, bool)> Live_GetConnectivityInformation{0x141E0C380};
// Info
WEAK symbol<const char*(const char*, const char* key)> Info_ValueForKey{0x1422E87B0};
// MSG // MSG
WEAK symbol<uint8_t(msg_t* msg)> MSG_ReadByte{0x142155450, 0x14050D1B0}; WEAK symbol<uint8_t(msg_t* msg)> MSG_ReadByte{0x142155450, 0x14050D1B0};
// NET // NET
WEAK symbol<bool(netsrc_t sock, netadr_t* adr, const void* data, int len)> NET_OutOfBandData{0x142173600};
WEAK symbol<bool(netsrc_t sock, int length, const void* data, const netadr_t* to)> NET_SendPacket{ WEAK symbol<bool(netsrc_t sock, int length, const void* data, const netadr_t* to)> NET_SendPacket{
0x1423323B0, 0x140596E40 0x1423323B0, 0x140596E40
}; };
@ -93,7 +106,7 @@ namespace game
WEAK symbol<const char*(const char* name)> CopyString{0x1422AC220, 0x14056BD70}; WEAK symbol<const char*(const char* name)> CopyString{0x1422AC220, 0x14056BD70};
WEAK symbol<bool()> isModLoaded{0x1420D5020}; WEAK symbol<bool()> isModLoaded{0x1420D5020};
WEAK symbol<void(int, const char*, int)> loadMod{0x1420D6930}; WEAK symbol<void(int, const char*, bool)> loadMod{0x1420D6930};
// Dvar // Dvar
WEAK symbol<bool(const dvar_t* dvar)> Dvar_IsSessionModeBaseDvar{0x1422C23A0, 0x140576890}; WEAK symbol<bool(const dvar_t* dvar)> Dvar_IsSessionModeBaseDvar{0x1422C23A0, 0x140576890};
@ -105,10 +118,15 @@ namespace game
WEAK symbol<const char*(const dvar_t* dvar)> Dvar_DisplayableValue{0x1422BC080}; WEAK symbol<const char*(const dvar_t* dvar)> Dvar_DisplayableValue{0x1422BC080};
WEAK symbol<bool(const dvar_t* dvar)> Dvar_GetBool{0x1422BCED0}; WEAK symbol<bool(const dvar_t* dvar)> Dvar_GetBool{0x1422BCED0};
WEAK symbol<int(const dvar_t* dvar)> Dvar_GetInt{0x0, 0x140575C20}; WEAK symbol<int(const dvar_t* dvar)> Dvar_GetInt{0x0, 0x140575C20};
WEAK symbol<float(const dvar_t* dvar)> Dvar_GetFLoat{0x0, 0x140575B20};
WEAK symbol<dvar_t*(dvarStrHash_t hash, const char* dvarName, bool value, int flags, WEAK symbol<dvar_t*(dvarStrHash_t hash, const char* dvarName, bool value, int flags,
const char* description)> Dvar_RegisterBool{ const char* description)> Dvar_RegisterBool{
0x1422D0900, 0x14057B500 0x1422D0900, 0x14057B500
}; };
WEAK symbol<dvar_t*(dvarStrHash_t hash, const char* dvarName, float value, float min, float max, unsigned int flags,
const char* description)> Dvar_RegisterFloat{
0x0, 0x14057B6B0
};
WEAK symbol<dvar_t*(dvarStrHash_t hash, const char* dvarName, bool value, int flags, WEAK symbol<dvar_t*(dvarStrHash_t hash, const char* dvarName, bool value, int flags,
const char* description)> Dvar_SessionModeRegisterBool{ const char* description)> Dvar_SessionModeRegisterBool{
0x1422D0D40, 0x14057BAA0 0x1422D0D40, 0x14057BAA0
@ -129,6 +147,7 @@ namespace game
}; };
// UI // UI
WEAK symbol<void(int localClientNumber, int errorcode, const char* errorMessage)> UI_OpenErrorPopupWithMessage{0x14228DEE0};
WEAK symbol<void(bool frontend)> UI_CoD_Init{0x141F29010, 0x1404A0A50}; WEAK symbol<void(bool frontend)> UI_CoD_Init{0x141F29010, 0x1404A0A50};
WEAK symbol<void()> UI_CoD_LobbyUI_Init{0x141F2BD80, 0x1404A1F50}; WEAK symbol<void()> UI_CoD_LobbyUI_Init{0x141F2BD80, 0x1404A1F50};
WEAK symbol<void()> UI_CoD_Shutdown{0x141F32E10, 0x0}; WEAK symbol<void()> UI_CoD_Shutdown{0x141F32E10, 0x0};
@ -137,9 +156,10 @@ namespace game
WEAK symbol<void(hks::lua_State*, const char*)> Lua_CoD_LoadLuaFile{0x141F11A20, 0x0}; WEAK symbol<void(hks::lua_State*, const char*)> Lua_CoD_LoadLuaFile{0x141F11A20, 0x0};
WEAK symbol<void(int localClientNum)> CG_LUIHUDRestart{0x140F7E970}; WEAK symbol<void(int localClientNum)> CG_LUIHUDRestart{0x140F7E970};
WEAK symbol<void(int localClientNum)> CL_CheckKeepDrawingConnectScreen{0x1413CCAE0}; WEAK symbol<void(int localClientNum)> CL_CheckKeepDrawingConnectScreen{0x1413CCAE0};
WEAK symbol<void(const char* key, int value, hks::lua_State* luaVM)> Lua_SetTableInt{0x141F066E0};
// Scr // Scr
WEAK symbol<void(scriptInstance_t inst, int value)> Scr_AddInt{0x0, 0x14016F160}; WEAK symbol<void(scriptInstance_t inst, int value)> Scr_AddInt{0x1412E9870, 0x14016F160};
WEAK symbol<void(scriptInstance_t inst, const char* value)> Scr_AddString{0x0, 0x14016F320}; WEAK symbol<void(scriptInstance_t inst, const char* value)> Scr_AddString{0x0, 0x14016F320};
WEAK symbol<const char*(scriptInstance_t inst, unsigned int index)> Scr_GetString{0x0, 0x140171490}; WEAK symbol<const char*(scriptInstance_t inst, unsigned int index)> Scr_GetString{0x0, 0x140171490};
WEAK symbol<void(gentity_s* ent, ScrVarCanonicalName_t stringValue, unsigned int paramcount)> Scr_Notify_Canon{ WEAK symbol<void(gentity_s* ent, ScrVarCanonicalName_t stringValue, unsigned int paramcount)> Scr_Notify_Canon{
@ -157,6 +177,9 @@ namespace game
0x141CD98D0 0x141CD98D0
}; };
// PCache
WEAK symbol<void(ControllerIndex_t controllerIndex)> PCache_DeleteEntries{0x141E8D710};
// SV // SV
WEAK symbol<bool()> SV_Loaded{0x142252250, 0x140535460}; WEAK symbol<bool()> SV_Loaded{0x142252250, 0x140535460};
WEAK symbol<void*()> SV_AddTestClient{0x142248F40, 0x14052E3E0}; WEAK symbol<void*()> SV_AddTestClient{0x142248F40, 0x14052E3E0};
@ -172,6 +195,9 @@ namespace game
WEAK symbol<void(const char* text_in)> SV_Cmd_TokenizeString{0x1420EF130, 0x1404FA6C0}; WEAK symbol<void(const char* text_in)> SV_Cmd_TokenizeString{0x1420EF130, 0x1404FA6C0};
WEAK symbol<void()> SV_Cmd_EndTokenizedString{0x1420EF0E0, 0x1404FA670}; WEAK symbol<void()> SV_Cmd_EndTokenizedString{0x1420EF0E0, 0x1404FA670};
// FS
WEAK symbol<char*(int bytes)> FS_AllocMem{0x1422AC9F0, 0x14056C340};
// Utils // Utils
WEAK symbol<const char*(char* str)> I_CleanStr{0x1422E9050, 0x140580E80}; WEAK symbol<const char*(char* str)> I_CleanStr{0x1422E9050, 0x140580E80};
@ -190,7 +216,11 @@ namespace game
WEAK symbol<char> s_dvarPool{0x157AC6220, 0x14A3CB620}; WEAK symbol<char> s_dvarPool{0x157AC6220, 0x14A3CB620};
WEAK symbol<int> g_dvarCount{0x157AC61CC, 0x14A3CB5FC}; WEAK symbol<int> g_dvarCount{0x157AC61CC, 0x14A3CB5FC};
WEAK symbol<client_s> svs_clients{0x0, 0x14A178E98}; WEAK symbol<int> fs_loadStack{0x157A65310, 0x14A39C650};
// Client and dedi struct size differs :(
WEAK symbol<client_s_cl*> svs_clients_cl{0x1576F9318, 0};
WEAK symbol<client_s*> svs_clients{0x0, 0x14A178E98};
// Dvar variables // Dvar variables
WEAK symbol<dvar_t*> com_maxclients{0x0, 0x14948EE70}; WEAK symbol<dvar_t*> com_maxclients{0x0, 0x14948EE70};

View File

@ -45,7 +45,8 @@ namespace game
return dvar->current.value.enabled; return dvar->current.value.enabled;
} }
const dvar_t* register_sessionmode_dvar_bool(const char* dvar_name, const bool value, const int flags, const char* description, const eModes mode) const dvar_t* register_sessionmode_dvar_bool(const char* dvar_name, const bool value, const int flags,
const char* description, const eModes mode)
{ {
const auto hash = Dvar_GenerateHash(dvar_name); const auto hash = Dvar_GenerateHash(dvar_name);
auto* registered_dvar = Dvar_SessionModeRegisterBool(hash, dvar_name, value, flags, description); auto* registered_dvar = Dvar_SessionModeRegisterBool(hash, dvar_name, value, flags, description);
@ -83,7 +84,22 @@ namespace game
return registered_dvar; return registered_dvar;
} }
const dvar_t* register_dvar_string(const char* dvar_name, const char* value, const int flags, const char* description) const dvar_t* register_dvar_float(const char* dvar_name, float value, float min, float max, const int flags,
const char* description)
{
const auto hash = Dvar_GenerateHash(dvar_name);
auto* registered_dvar = Dvar_RegisterFloat(hash, dvar_name, value, min, max, flags, description);
if (registered_dvar)
{
registered_dvar->debugName = dvar_name;
}
return registered_dvar;
}
const dvar_t* register_dvar_string(const char* dvar_name, const char* value, const int flags,
const char* description)
{ {
const auto hash = Dvar_GenerateHash(dvar_name); const auto hash = Dvar_GenerateHash(dvar_name);
auto* registered_dvar = Dvar_RegisterString(hash, dvar_name, value, flags, description); auto* registered_dvar = Dvar_RegisterString(hash, dvar_name, value, flags, description);
@ -135,4 +151,101 @@ namespace game
dvar_to_change->flags = flags; dvar_to_change->flags = flags;
} }
bool is_server_running()
{
return get_dvar_bool("sv_running");
}
size_t get_max_client_count()
{
return static_cast<size_t>(get_dvar_int("com_maxclients"));
}
template <typename T>
static void foreach_client(T* client_states, const std::function<void(client_s&, size_t index)>& callback)
{
if (!client_states || !callback)
{
return;
}
for (size_t i = 0; i < get_max_client_count(); ++i)
{
callback(client_states[i], i);
}
}
template <typename T>
static bool access_client(T* client_states, const size_t index, const std::function<void(client_s&)>& callback)
{
if (!client_states || !callback)
{
return false;
}
if (index >= get_max_client_count())
{
return false;
}
auto& client = client_states[index];
if (client.client_state <= 0)
{
return false;
}
callback(client);
return true;
}
void foreach_client(const std::function<void(client_s&, size_t index)>& callback)
{
if (is_server())
{
foreach_client(*svs_clients, callback);
}
else
{
foreach_client(*svs_clients_cl, callback);
}
}
void foreach_client(const std::function<void(client_s&)>& callback)
{
foreach_client([&](client_s& client, size_t)
{
callback(client);
});
}
void foreach_connected_client(const std::function<void(client_s&, size_t index)>& callback)
{
foreach_client([&](client_s& client, const size_t index)
{
if (client.client_state > 0)
{
callback(client, index);
}
});
}
void foreach_connected_client(const std::function<void(client_s&)>& callback)
{
foreach_connected_client([&](client_s& client, size_t)
{
callback(client);
});
}
bool access_connected_client(const size_t index, const std::function<void(client_s&)>& callback)
{
if (is_server())
{
return access_client(*svs_clients, index, callback);
}
return access_client(*svs_clients_cl, index, callback);
}
} }

View File

@ -9,8 +9,21 @@ namespace game
bool get_dvar_bool(const char* dvar_name); bool get_dvar_bool(const char* dvar_name);
const dvar_t* register_dvar_bool(const char* dvar_name, bool value, int flags, const char* description); const dvar_t* register_dvar_bool(const char* dvar_name, bool value, int flags, const char* description);
const dvar_t* register_dvar_float(const char* dvar_name, float value, float min, float max, const int flags, const char* description);
const dvar_t* register_sessionmode_dvar_bool(const char* dvar_name, bool value, int flags, const char* description, eModes mode = MODE_COUNT); const dvar_t* register_sessionmode_dvar_bool(const char* dvar_name, bool value, int flags, const char* description, eModes mode = MODE_COUNT);
const dvar_t* register_dvar_string(const char* dvar_name, const char* value, int flags, const char* description); const dvar_t* register_dvar_string(const char* dvar_name, const char* value, int flags, const char* description);
void dvar_add_flags(const char* dvar, dvarFlags_e flags); void dvar_add_flags(const char* dvar, dvarFlags_e flags);
void dvar_set_flags(const char* dvar_name, dvarFlags_e flags); void dvar_set_flags(const char* dvar_name, dvarFlags_e flags);
bool is_server_running();
size_t get_max_client_count();
void foreach_client(const std::function<void(client_s&, size_t index)>& callback);
void foreach_client(const std::function<void(client_s&)>& callback);
void foreach_connected_client(const std::function<void(client_s&, size_t index)>& callback);
void foreach_connected_client(const std::function<void(client_s&)>& callback);
bool access_connected_client(size_t index, const std::function<void(client_s&)>& callback);
} }

View File

@ -1,9 +1,28 @@
#include <std_include.hpp> #include <std_include.hpp>
#include "window.hpp" #include "window.hpp"
#include <utils/nt.hpp>
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
#endif
namespace namespace
{ {
thread_local uint32_t window_count = 0; thread_local uint32_t window_count = 0;
uint32_t get_dpi_for_window(const HWND window)
{
const utils::nt::library user32{"user32.dll"};
const auto get_dpi = user32 ? user32.get_proc<UINT(WINAPI *)(HWND)>("GetDpiForWindow") : nullptr;
if (!get_dpi)
{
return USER_DEFAULT_SCREEN_DPI;
}
return get_dpi(window);
}
} }
window::window(const std::string& title, const int width, const int height, window::window(const std::string& title, const int width, const int height,
@ -34,6 +53,10 @@ window::window(const std::string& title, const int width, const int height,
this->handle_ = CreateWindowExA(NULL, this->wc_.lpszClassName, title.data(), flags, x, y, width, height, nullptr, this->handle_ = CreateWindowExA(NULL, this->wc_.lpszClassName, title.data(), flags, x, y, width, height, nullptr,
nullptr, this->wc_.hInstance, this); nullptr, this->wc_.hInstance, this);
BOOL value = TRUE;
DwmSetWindowAttribute(this->handle_,
DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value));
SendMessageA(this->handle_, WM_DPICHANGED, 0, 0); SendMessageA(this->handle_, WM_DPICHANGED, 0, 0);
ShowWindow(this->handle_, SW_SHOW); ShowWindow(this->handle_, SW_SHOW);
SetForegroundWindow(this->handle_); SetForegroundWindow(this->handle_);
@ -67,7 +90,7 @@ LRESULT window::processor(const UINT message, const WPARAM w_param, const LPARAM
{ {
if (message == WM_DPICHANGED) if (message == WM_DPICHANGED)
{ {
const auto dpi = GetDpiForWindow(*this); const auto dpi = get_dpi_for_window(*this);
if (dpi != this->last_dpi_) if (dpi != this->last_dpi_)
{ {
RECT rect; RECT rect;

View File

@ -1,4 +1,5 @@
#pragma once #pragma once
#pragma comment (lib, "dwmapi.lib")
class window class window
{ {

View File

@ -50,6 +50,7 @@
#include <atlsafe.h> #include <atlsafe.h>
#include <iphlpapi.h> #include <iphlpapi.h>
#include <wincrypt.h> #include <wincrypt.h>
#include <dwmapi.h>
#include <shellscalingapi.h> #include <shellscalingapi.h>
#include <d3d11.h> #include <d3d11.h>
#include <dxgi1_6.h> #include <dxgi1_6.h>
@ -64,6 +65,7 @@
#endif #endif
#include <map> #include <map>
#include <array>
#include <atomic> #include <atomic>
#include <vector> #include <vector>
#include <mutex> #include <mutex>

View File

@ -55,10 +55,14 @@ namespace steam
const auto mode = game::eModes(std::atoi(playmode.data())); const auto mode = game::eModes(std::atoi(playmode.data()));
const auto* tags = ::utils::string::va( const auto* tags = ::utils::string::va(
R"(\gametype\%s\dedicated\%s\ranked\false\hardcore\false\zombies\%s\modName\\playerCount\%d)", R"(\gametype\%s\dedicated\%s\ranked\false\hardcore\%s\zombies\%s\playerCount\%d\bots\%d\modName\%s\)",
info.get("gametype").data(), info.get("gametype").data(),
info.get("dedicated") == "1" ? "true" : "false", info.get("dedicated") == "1" ? "true" : "false",
mode == game::MODE_ZOMBIES ? "true" : "false", server.m_nPlayers); 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); ::utils::string::copy(server.m_szGameTags, tags);
server.m_steamID.bits = strtoull(info.get("xuid").data(), nullptr, 16); server.m_steamID.bits = strtoull(info.get("xuid").data(), nullptr, 16);

View File

@ -5,6 +5,7 @@
#include "file_updater.hpp" #include "file_updater.hpp"
#include <utils/cryptography.hpp> #include <utils/cryptography.hpp>
#include <utils/flags.hpp>
#include <utils/http.hpp> #include <utils/http.hpp>
#include <utils/io.hpp> #include <utils/io.hpp>
#include <utils/compression.hpp> #include <utils/compression.hpp>
@ -271,7 +272,7 @@ namespace updater
bool file_updater::is_outdated_file(const file_info& file) const bool file_updater::is_outdated_file(const file_info& file) const
{ {
#if !defined(NDEBUG) || !defined(CI) #if !defined(NDEBUG) || !defined(CI)
if (file.name == UPDATE_HOST_BINARY) if (file.name == UPDATE_HOST_BINARY && !utils::flags::has_flag("update"))
{ {
return false; return false;
} }

View File

@ -0,0 +1,53 @@
#include "byte_buffer.hpp"
#include <cstring>
namespace utils
{
byte_buffer::byte_buffer()
: writing_(true)
{
}
byte_buffer::byte_buffer(std::string buffer)
: writing_(false)
, buffer_(std::move(buffer))
{
}
void byte_buffer::write(const void* buffer, const size_t length)
{
if (!this->writing_)
{
throw std::runtime_error("Writing to readable byte buffer");
}
this->buffer_.append(static_cast<const char*>(buffer), length);
}
void byte_buffer::read(void* data, const size_t length)
{
if (this->writing_)
{
throw std::runtime_error("Reading from writable byte buffer");
}
if (this->offset_ + length > this->buffer_.size())
{
throw std::runtime_error("Out of bounds read from byte buffer");
}
memcpy(data, this->buffer_.data() + this->offset_, length);
this->offset_ += length;
}
std::string byte_buffer::read_data(const size_t length)
{
std::string result{};
result.resize(length);
this->read(result.data(), result.size());
return result;
}
}

View File

@ -0,0 +1,141 @@
#pragma once
#include <string>
#include <vector>
#include <stdexcept>
namespace utils
{
class byte_buffer
{
public:
byte_buffer();
byte_buffer(std::string buffer);
template <typename T>
byte_buffer(const std::basic_string_view<T>& buffer)
: byte_buffer(std::string(reinterpret_cast<const char*>(buffer.data()), buffer.size() * sizeof(T)))
{
}
void write(const void* buffer, size_t length);
void write(const char* text)
{
this->write(text, strlen(text));
}
void write_string(const char* str, const size_t length)
{
this->write<uint32_t>(static_cast<uint32_t>(length));
this->write(str, length);
}
void write_string(const std::string& str)
{
this->write_string(str.data(), str.size());
}
void write_string(const char* str)
{
this->write_string(str, strlen(str));
}
template <typename T>
void write(const T& object)
{
this->write(&object, sizeof(object));
}
template<>
void write<byte_buffer>(const byte_buffer& object)
{
const auto& buffer = object.get_buffer();
this->write(buffer.data(), buffer.size());
}
template <typename T>
void write(const std::vector<T>& vec)
{
this->write(vec.data(), vec.size() * sizeof(T));
}
template <typename T>
void write_vector(const std::vector<T>& vec)
{
this->write(static_cast<uint32_t>(vec.size()));
this->write(vec);
}
const std::string& get_buffer() const
{
return this->buffer_;
}
std::string move_buffer()
{
return std::move(this->buffer_);
}
void read(void* data, size_t length);
template <typename T>
T read()
{
T object{};
this->read(&object, sizeof(object));
return object;
}
template <typename T>
std::vector<T> read_vector()
{
std::vector<T> result{};
const auto size = this->read<uint32_t>();
const auto totalSize = size * sizeof(T);
if (this->offset_ + totalSize > this->buffer_.size())
{
throw std::runtime_error("Out of bounds read from byte buffer");
}
result.resize(size);
this->read(result.data(), totalSize);
return result;
}
std::string read_string()
{
std::string result{};
const auto size = this->read<uint32_t>();
if (this->offset_ + size > this->buffer_.size())
{
throw std::runtime_error("Out of bounds read from byte buffer");
}
result.resize(size);
this->read(result.data(), size);
return result;
}
size_t get_remaining_size() const
{
return this->buffer_.size() - offset_;
}
std::string get_remaining_data()
{
return this->read_data(this->get_remaining_size());
}
std::string read_data(size_t length);
private:
bool writing_{false};
size_t offset_{0};
std::string buffer_{};
};
}

View File

@ -45,6 +45,11 @@ namespace utils::concurrency
return object_; return object_;
} }
std::unique_lock<MutexType> acquire_lock()
{
return std::unique_lock<MutexType>{mutex_};
}
private: private:
mutable MutexType mutex_{}; mutable MutexType mutex_{};
T object_{}; T object_{};

View File

@ -8,6 +8,11 @@ namespace utils
this->parse(buffer); this->parse(buffer);
} }
info_string::info_string(const char* buffer)
: info_string(std::string{buffer})
{
}
info_string::info_string(const std::string_view& buffer) info_string::info_string(const std::string_view& buffer)
: info_string(std::string{buffer}) : info_string(std::string{buffer})
{ {

View File

@ -9,8 +9,9 @@ namespace utils
{ {
public: public:
info_string() = default; info_string() = default;
info_string(const std::string& buffer); explicit info_string(const std::string& buffer);
info_string(const std::string_view& buffer); explicit info_string(const char* buffer);
explicit info_string(const std::string_view& buffer);
info_string(const std::basic_string_view<uint8_t>& buffer); info_string(const std::basic_string_view<uint8_t>& buffer);
void set(const std::string& key, const std::string& value); void set(const std::string& key, const std::string& value);

View File

@ -194,18 +194,19 @@ namespace utils::io
std::vector<std::filesystem::path> list_files(const std::filesystem::path& directory, const bool recursive) std::vector<std::filesystem::path> list_files(const std::filesystem::path& directory, const bool recursive)
{ {
std::error_code code{};
std::vector<std::filesystem::path> files; std::vector<std::filesystem::path> files;
if (recursive) if (recursive)
{ {
for (auto& file : std::filesystem::recursive_directory_iterator(directory)) for (auto& file : std::filesystem::recursive_directory_iterator(directory, code))
{ {
files.push_back(file.path()); files.push_back(file.path());
} }
} }
else else
{ {
for (auto& file : std::filesystem::directory_iterator(directory)) for (auto& file : std::filesystem::directory_iterator(directory, code))
{ {
files.push_back(file.path()); files.push_back(file.path());
} }