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");
@ -56,6 +57,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(;;)
{ {
@ -80,7 +83,7 @@ function update()
SetDvar("ui_motd", level.motd); SetDvar("ui_motd", level.motd);
} }
g_allowvote = GetDvarString( "g_allowvote"); g_allowvote = GetDvarString( "g_allowvote" );
if(level.allowvote != g_allowvote) if(level.allowvote != g_allowvote)
{ {
level.allowvote = g_allowvote; level.allowvote = g_allowvote;
@ -193,4 +196,14 @@ 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
@ -81,4 +81,4 @@ DataSources.StartMenuGameOptions = ListHelper_SetupDataSource("StartMenuGameOpti
end end
end end
return options return options
end, true) end, true)

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

@ -2,101 +2,121 @@ if Engine.GetCurrentMap() ~= "core_frontend" then
return return
end end
DataSources.MPStatsSettings = DataSourceHelpers.ListSetup( "MPStatsSettings", function ( controller ) DataSources.MPStatsSettings = DataSourceHelpers.ListSetup("MPStatsSettings", function(controller)
local optionsTable = {} local optionsTable = {}
local updateDvar = function(f1_arg0, f1_arg1, f1_arg2, dvarName, f1_arg4) local updateDvar = function(f1_arg0, f1_arg1, f1_arg2, dvarName, f1_arg4)
local oldValue = Engine.DvarInt( nil, dvarName ) local oldValue = Engine.DvarInt(nil, dvarName)
local newValue = f1_arg1.value local newValue = f1_arg1.value
UpdateInfoModels( f1_arg1 ) UpdateInfoModels(f1_arg1)
if oldValue == newValue then if oldValue == newValue then
return return
end
Engine.SetDvar( dvarName, f1_arg1.value )
if dvarName == "cg_unlockall_loot" then
Engine.SetDvar( "ui_enableAllHeroes", f1_arg1.value )
end end
end Engine.SetDvar(dvarName, f1_arg1.value)
if dvarName == "cg_unlockall_loot" then
Engine.SetDvar("ui_enableAllHeroes", f1_arg1.value)
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",
option = "MENU_DISABLED", "Unlocks all Black Market loot.", "MPStatsSettings_unlock_loot",
value = 0, "cg_unlockall_loot", {
default = true {
}, option = "MENU_DISABLED",
{ value = 0,
option = "MENU_ENABLED", default = true
value = 1 },
}, {
}, nil, updateDvar )) option = "MENU_ENABLED",
value = 1
},
}, 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",
option = "MENU_DISABLED", "All items that need to be purchased with unlock tokens are unlocked.", "MPStatsSettings_purchase_all",
value = 0, "cg_unlockall_purchases", {
default = true {
}, option = "MENU_DISABLED",
{ value = 0,
option = "MENU_ENABLED", default = true
value = 1 },
}, {
}, nil, updateDvar )) option = "MENU_ENABLED",
end value = 1
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", { },
{ }, nil, updateDvar))
option = "MENU_DISABLED", table.insert(optionsTable,
value = 0, CoD.OptionsUtility.CreateDvarSettings(controller, "Unlock All Class Slots",
default = true "Unlocks all create-a-class slots and sets.", "MPStatsSettings_unlockall_cac_slots",
}, "cg_unlockall_cac_slots", {
{ {
option = "MENU_ENABLED", option = "MENU_DISABLED",
value = 1 value = 0,
}, default = true
}, nil, updateDvar )) },
table.insert( optionsTable, CoD.OptionsUtility.CreateDvarSettings( controller, "Unlock All Attachments", "All attachments on weapons are unlocked.", "MPStatsSettings_unlockall_attachments", "cg_unlockall_attachments", { {
{ option = "MENU_ENABLED",
option = "MENU_DISABLED", value = 1
value = 0, },
default = true }, nil, updateDvar))
}, end
{ table.insert(optionsTable,
option = "MENU_ENABLED", CoD.OptionsUtility.CreateDvarSettings(controller, "Unlock All Attachments",
value = 1 "All attachments on weapons are unlocked.",
}, "MPStatsSettings_unlockall_attachments", "cg_unlockall_attachments", {
}, 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", { option = "MENU_DISABLED",
{ value = 0,
option = "MENU_DISABLED", default = true
value = 0, },
default = true {
}, option = "MENU_ENABLED",
{ value = 1
option = "MENU_ENABLED", },
value = 1 }, nil, updateDvar))
}, table.insert(optionsTable,
}, nil, updateDvar )) CoD.OptionsUtility.CreateDvarSettings(controller, "Unlock all 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", { "All camos and reticles on weapons are unlocked.", "MPStatsSettings_unlockall_camos_and_reticles",
{ "cg_unlockall_camos_and_reticles", {
option = "MENU_DISABLED", {
value = 0, option = "MENU_DISABLED",
default = true value = 0,
}, default = true
{ },
option = "MENU_ENABLED", {
value = 1 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", { }, nil, updateDvar))
{ table.insert(optionsTable,
option = "MENU_DISABLED", CoD.OptionsUtility.CreateDvarSettings(controller, "Unlock all Calling Cards", "All calling cards are unlocked.",
value = 0, "MPStatsSettings_unlockall_calling_cards", "cg_unlockall_calling_cards", {
default = true {
}, option = "MENU_DISABLED",
{ value = 0,
option = "MENU_ENABLED", default = true
value = 1 },
}, {
}, nil, updateDvar )) 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",
value = 0,
default = true
},
{
option = "MENU_ENABLED",
value = 1
},
}, nil, updateDvar))
local rankLevels = {} local rankLevels = {}
if Engine.CurrentSessionMode() == Enum.eModes.MODE_MULTIPLAYER then if Engine.CurrentSessionMode() == Enum.eModes.MODE_MULTIPLAYER then
@ -106,9 +126,9 @@ DataSources.MPStatsSettings = DataSourceHelpers.ListSetup( "MPStatsSettings", fu
end end
local rankObjs = {} local rankObjs = {}
local hasDefault = false local hasDefault = false
local currentRank = CoD.BlackMarketUtility.GetCurrentRank( controller ) + 1 local currentRank = CoD.BlackMarketUtility.GetCurrentRank(controller) + 1
for index, value in ipairs(rankLevels) do for index, value in ipairs(rankLevels) do
table.insert( rankObjs, { table.insert(rankObjs, {
name = value, name = value,
value = value - 1, value = value - 1,
default = value == currentRank, default = value == currentRank,
@ -121,7 +141,7 @@ DataSources.MPStatsSettings = DataSourceHelpers.ListSetup( "MPStatsSettings", fu
end end
if not hasDefault then if not hasDefault then
table.insert( rankObjs, { table.insert(rankObjs, {
name = currentRank, name = currentRank,
value = currentRank - 1, value = currentRank - 1,
default = true, default = true,
@ -132,23 +152,23 @@ DataSources.MPStatsSettings = DataSourceHelpers.ListSetup( "MPStatsSettings", fu
local prestigeTable = {} local prestigeTable = {}
for i = 0, 10 do for i = 0, 10 do
table.insert( prestigeTable, { table.insert(prestigeTable, {
name = i == 0 and "None" or i, name = i == 0 and "None" or i,
value = i, value = i,
default = i == CoD.PrestigeUtility.GetCurrentPLevel( controller ), default = i == CoD.PrestigeUtility.GetCurrentPLevel(controller),
title = "Prestige", title = "Prestige",
desc = "" desc = ""
}) })
end end
local createSettingsDatasource = function ( controller, datasourceName, optionsTable, currentValue, loopEdges, action ) local createSettingsDatasource = function(controller, datasourceName, optionsTable, currentValue, loopEdges, action)
if currentValue == nil then if currentValue == nil then
currentValue = 0 currentValue = 0
end end
DataSources[datasourceName] = DataSourceHelpers.ListSetup( datasourceName, function ( f47_arg0 ) DataSources[datasourceName] = DataSourceHelpers.ListSetup(datasourceName, function(f47_arg0)
local f47_local0 = {} local f47_local0 = {}
for f47_local4, f47_local5 in ipairs( optionsTable ) do for f47_local4, f47_local5 in ipairs(optionsTable) do
table.insert( f47_local0, { table.insert(f47_local0, {
models = { models = {
text = optionsTable[f47_local4].name text = optionsTable[f47_local4].name
}, },
@ -161,418 +181,147 @@ DataSources.MPStatsSettings = DataSourceHelpers.ListSetup( "MPStatsSettings", fu
action = action, action = action,
selectIndex = optionsTable[f47_local4].value == currentValue, selectIndex = optionsTable[f47_local4].value == currentValue,
loopEdges = loopEdges, loopEdges = loopEdges,
showChangeIndicator = function ( f48_arg0, f48_arg1, f48_arg2 ) showChangeIndicator = function(f48_arg0, f48_arg1, f48_arg2)
return f48_arg0.default ~= true return f48_arg0.default ~= true
end end
} }
} ) })
end end
f47_local0[1].properties.first = true f47_local0[1].properties.first = true
f47_local0[#optionsTable].properties.last = true f47_local0[#optionsTable].properties.last = true
return f47_local0 return f47_local0
end, nil, nil, nil ) end, nil, nil, nil)
return datasourceName return datasourceName
end end
table.insert( optionsTable, { table.insert(optionsTable, {
models = { models = {
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,
UpdateInfoModels( f1_arg1 ) CoD.BlackMarketUtility.GetCurrentRank(controller), false, function(f1_arg0, f1_arg1, f1_arg2, dvarName, f1_arg4)
local rankTable = nil UpdateInfoModels(f1_arg1)
if Engine.CurrentSessionMode() == Enum.eModes.MODE_MULTIPLAYER then local rankTable = nil
rankTable = "gamedata/tables/mp/mp_ranktable.csv" if Engine.CurrentSessionMode() == Enum.eModes.MODE_MULTIPLAYER then
elseif Engine.CurrentSessionMode() == Enum.eModes.MODE_ZOMBIES then rankTable = "gamedata/tables/mp/mp_ranktable.csv"
rankTable = "gamedata/tables/zm/zm_ranktable.csv" elseif Engine.CurrentSessionMode() == Enum.eModes.MODE_ZOMBIES then
end rankTable = "gamedata/tables/zm/zm_ranktable.csv"
local skipLines = Engine.CurrentSessionMode() == Enum.eModes.MODE_MULTIPLAYER and 3 or 2 end
local maxXp = tonumber(Engine.TableLookupGetColumnValueForRow(rankTable, f1_arg1.value + skipLines, 7)) local skipLines = Engine.CurrentSessionMode() == Enum.eModes.MODE_MULTIPLAYER and 3 or 2
if maxXp == nil then local maxXp = tonumber(Engine.TableLookupGetColumnValueForRow(rankTable, f1_arg1.value + skipLines, 7))
maxXp = 9999999999 if maxXp == nil then
end maxXp = 9999999999
Engine.ExecNow(f1_arg0, "statsetbyname rankxp " .. maxXp - 1) end
Engine.ExecNow(f1_arg0, "statsetbyname rank " .. f1_arg1.value) Engine.ExecNow(f1_arg0, "statsetbyname rankxp " .. maxXp - 1)
Engine.Exec( f1_arg0, "uploadstats " .. tostring( Engine.CurrentSessionMode() ) ) Engine.ExecNow(f1_arg0, "statsetbyname rank " .. f1_arg1.value)
end ) Engine.Exec(f1_arg0, "uploadstats " .. tostring(Engine.CurrentSessionMode()))
end)
}, },
properties = { properties = {
revert = function ( f50_arg0 ) end revert = function(f50_arg0)
end
} }
}) })
table.insert( optionsTable, { table.insert(optionsTable, {
models = { models = {
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,
UpdateInfoModels( f1_arg1 ) CoD.PrestigeUtility.GetCurrentPLevel(controller), false, function(f1_arg0, f1_arg1, f1_arg2, dvarName, f1_arg4)
local newPrestige = f1_arg1.value UpdateInfoModels(f1_arg1)
Engine.ExecNow(f1_arg0, "statsetbyname plevel " .. newPrestige) local newPrestige = f1_arg1.value
Engine.ExecNow(f1_arg0, "statsetbyname hasprestiged " .. (newPrestige > 0 and 1 or 0)) Engine.ExecNow(f1_arg0, "statsetbyname plevel " .. newPrestige)
Engine.Exec( f1_arg0, "uploadstats " .. tostring( Engine.CurrentSessionMode() ) ) Engine.ExecNow(f1_arg0, "statsetbyname hasprestiged " .. (newPrestige > 0 and 1 or 0))
end ) Engine.Exec(f1_arg0, "uploadstats " .. tostring(Engine.CurrentSessionMode()))
end)
}, },
properties = { properties = {
revert = function ( f50_arg0 ) end revert = function(f50_arg0)
end
} }
}) })
return optionsTable return optionsTable
end) end)
if Dvar.cg_unlockall_loot:get() == true then if Dvar.cg_unlockall_loot:get() == true then
Engine.SetDvar( "ui_enableAllHeroes", 1 ) Engine.SetDvar("ui_enableAllHeroes", 1)
end end
LUI.createMenu.BoiiiStatsMenu = function ( controller ) LUI.createMenu.BoiiiStatsMenu = function(controller)
local self = CoD.Menu.NewForUIEditor( "BoiiiStatsMenu" ) local self = CoD.Menu.NewForUIEditor("BoiiiStatsMenu")
if PreLoadFunc then if PreLoadFunc then
PreLoadFunc( self, controller ) PreLoadFunc(self, controller)
end end
self.soundSet = "ChooseDecal" self.soundSet = "ChooseDecal"
self:setOwner( controller ) self:setOwner(controller)
self:setLeftRight( true, true, 0, 0 ) self:setLeftRight(true, true, 0, 0)
self:setTopBottom( true, true, 0, 0 ) self:setTopBottom(true, true, 0, 0)
self:playSound( "menu_open", controller ) self:playSound("menu_open", controller)
self.buttonModel = Engine.CreateModel( Engine.GetModelForController( controller ), "BoiiiStatsMenu.buttonPrompts" ) self.buttonModel = Engine.CreateModel(Engine.GetModelForController(controller), "BoiiiStatsMenu.buttonPrompts")
self.anyChildUsesUpdateState = true self.anyChildUsesUpdateState = true
local GameSettingsBackground = CoD.GameSettings_Background.new( self, controller ) local GameSettingsBackground = CoD.GameSettings_Background.new(self, 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
GameSettingsBackground.GameSettingsSelectedItemInfo.GameModeInfo:setAlpha( 0 ) .Localize("STATS SETTINGS"))
GameSettingsBackground.GameSettingsSelectedItemInfo.GameModeName:setAlpha( 0 ) GameSettingsBackground.GameSettingsSelectedItemInfo.GameModeInfo:setAlpha(0)
self:addElement( GameSettingsBackground ) GameSettingsBackground.GameSettingsSelectedItemInfo.GameModeName:setAlpha(0)
self:addElement(GameSettingsBackground)
self.GameSettingsBackground = GameSettingsBackground self.GameSettingsBackground = GameSettingsBackground
local Options = CoD.Competitive_SettingsList.new( self, controller ) local Options = CoD.Competitive_SettingsList.new(self, controller)
Options:setLeftRight( true, false, 26, 741 ) Options:setLeftRight(true, false, 26, 741)
Options:setTopBottom( true, false, 135, 720 ) Options:setTopBottom(true, false, 135, 720)
Options.Title.DescTitle:setText( Engine.Localize( "Stats" ) ) Options.Title.DescTitle:setText(Engine.Localize("Stats"))
Options.ButtonList:setVerticalCount( 15 ) Options.ButtonList:setVerticalCount(15)
Options.ButtonList:setDataSource( "MPStatsSettings" ) Options.ButtonList:setDataSource("MPStatsSettings")
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,
GoBack( self, controller ) function(element, menu, controller, model)
SetPerControllerTableProperty( controller, "disableGameSettingsOptions", nil ) GoBack(self, controller)
return true SetPerControllerTableProperty(controller, "disableGameSettingsOptions", nil)
end, function ( element, menu, controller ) return true
CoD.Menu.SetButtonLabel( menu, Enum.LUIButton.LUI_KEY_XBB_PSCIRCLE, "MENU_BACK" ) end, function(element, menu, controller)
return true CoD.Menu.SetButtonLabel(menu, Enum.LUIButton.LUI_KEY_XBB_PSCIRCLE, "MENU_BACK")
end, false ) return true
end, false)
GameSettingsBackground.MenuFrame:setModel( self.buttonModel, controller ) GameSettingsBackground.MenuFrame:setModel(self.buttonModel, controller)
Options.id = "Options" Options.id = "Options"
self:processEvent( { self:processEvent({
name = "menu_loaded", name = "menu_loaded",
controller = controller controller = controller
} ) })
self:processEvent( { self:processEvent({
name = "update_state", name = "update_state",
menu = self menu = self
} ) })
if not self:restoreState() then if not self:restoreState() then
self.Options:processEvent( { self.Options:processEvent({
name = "gain_focus", name = "gain_focus",
controller = controller controller = controller
} ) })
end end
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),
end ) "BoiiiStatsMenu.buttonPrompts"))
end)
if PostLoadFunc then if PostLoadFunc then
PostLoadFunc( self, controller ) PostLoadFunc(self, controller)
end end
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)
{
return res;
}
/*const char* get_gamer_tag_stub(const uint64_t num) 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)
{ {
return utils::string::va("^3%s", get_gamer_tag_hook.invoke<const char*>(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

@ -36,22 +36,22 @@ namespace dedicated_patches
{ {
const std::vector<uintptr_t> is_mod_loaded_addresses = const std::vector<uintptr_t> is_mod_loaded_addresses =
{ {
{ 0x14019CFC4_g }, {0x14019CFC4_g},
{ 0x14024D4A0_g }, {0x14024D4A0_g},
{ 0x14024D669_g }, {0x14024D669_g},
{ 0x14024D939_g }, {0x14024D939_g},
{ 0x14024DC64_g }, {0x14024DC64_g},
{ 0x14024E13A_g }, {0x14024E13A_g},
{ 0x14024E5A3_g }, {0x14024E5A3_g},
{ 0x14024FFB9_g }, {0x14024FFB9_g},
{ 0x140251E9E_g }, {0x140251E9E_g},
{ 0x140253680_g }, {0x140253680_g},
{ 0x140257BF6_g }, {0x140257BF6_g},
{ 0x1402D296D_g }, {0x1402D296D_g},
{ 0x1402D58E9_g }, {0x1402D58E9_g},
{ 0x140468374_g }, {0x140468374_g},
{ 0x14046B796_g }, {0x14046B796_g},
{ 0x14048003D_g }, {0x14048003D_g},
}; };
for (const auto& address : is_mod_loaded_addresses) for (const auto& address : is_mod_loaded_addresses)
@ -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

@ -1,122 +1,128 @@
#include <std_include.hpp> #include <std_include.hpp>
#include "loader/component_loader.hpp" #include "loader/component_loader.hpp"
#include "game/game.hpp" #include "game/game.hpp"
#include "steam/steam.hpp" #include "steam/steam.hpp"
#include "network.hpp" #include "network.hpp"
#include "workshop.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp> #include <utils/hook.hpp>
#include <utils/info_string.hpp> #include <utils/string.hpp>
#include <utils/info_string.hpp>
#include <version.hpp>
#include <version.hpp>
#include "game/utils.hpp"
#include "game/utils.hpp"
namespace getinfo
{ namespace getinfo
int get_max_client_count() {
{ int get_max_client_count()
return game::get_dvar_int("com_maxclients"); {
} return game::get_dvar_int("com_maxclients");
}
int get_client_count()
{ template <typename T>
int count = 0; int get_client_count(T* client_states)
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) {
{ if (client_states[i].client_state > 0)
const auto client_state = *reinterpret_cast<int*>(client_states + (i * object_length)); {
if (client_state > 0) ++count;
{ }
++count; }
}
} return count;
}
return count;
} size_t get_client_count()
{
int get_bot_count() size_t count = 0;
{ game::foreach_connected_client([&count](const game::client_s&)
const auto client_states = *reinterpret_cast<uint64_t*>(game::select(0x1576F9318, 0x14A178E98)); {
if (!client_states) ++count;
{ });
return 0;
} return count;
}
int count = 0;
size_t get_bot_count()
for (int i = 0; i < get_max_client_count(); ++i) {
{ size_t count = 0;
if (game::SV_IsTestClient(i))
{ game::foreach_connected_client([&count](const game::client_s&, const size_t index)
++count; {
} if (game::SV_IsTestClient(static_cast<int>(index)))
} {
++count;
return count; }
} });
namespace return count;
{ }
int Com_SessionMode_GetGameMode()
{ namespace
return *reinterpret_cast<int*>(game::select(0x1568ED7F4, 0x14948DB04)) << 14 >> 28; {
} int Com_SessionMode_GetGameMode()
} {
return *reinterpret_cast<int*>(game::select(0x1568ED7F4, 0x14948DB04)) << 14 >> 28;
int get_assigned_team() }
{ }
return (rand() % 2) + 1;
} int get_assigned_team()
{
bool is_host() return (rand() % 2) + 1;
{ }
return game::SV_Loaded() && (game::is_server() || !game::Com_IsRunningUILevel());
} bool is_host()
{
struct component final : generic_component return game::SV_Loaded() && (game::is_server() || !game::Com_IsRunningUILevel());
{ }
void post_unpack() override
{ struct component final : generic_component
//utils::hook::jump(game::select(0x142254EF0, 0x140537730), get_assigned_team); {
void post_unpack() override
network::on("getInfo", [](const game::netadr_t& target, const network::data_view& data) {
{ //utils::hook::jump(game::select(0x142254EF0, 0x140537730), get_assigned_team);
utils::info_string info{};
info.set("challenge", std::string(data.begin(), data.end())); network::on("getInfo", [](const game::netadr_t& target, const network::data_view& data)
info.set("gamename", "T7"); {
info.set("hostname", utils::info_string info{};
game::get_dvar_string(game::is_server() ? "live_steam_server_name" : "sv_hostname")); info.set("challenge", std::string{ data.begin(), data.end() });
info.set("gametype", game::get_dvar_string("g_gametype")); info.set("gamename", "T7");
//info.set("sv_motd", get_dvar_string("sv_motd")); info.set("hostname",
info.set("description", game::get_dvar_string(game::is_server() ? "live_steam_server_name" : "sv_hostname"));
game::is_server() ? game::get_dvar_string("live_steam_server_description") : ""); info.set("gametype", game::get_dvar_string("g_gametype"));
info.set("xuid", utils::string::va("%llX", steam::SteamUser()->GetSteamID().bits)); //info.set("sv_motd", get_dvar_string("sv_motd"));
info.set("mapname", game::get_dvar_string("mapname")); info.set("description",
info.set("isPrivate", game::get_dvar_string("g_password").empty() ? "0" : "1"); game::is_server() ? game::get_dvar_string("live_steam_server_description") : "");
info.set("clients", utils::string::va("%i", get_client_count())); info.set("xuid", utils::string::va("%llX", steam::SteamUser()->GetSteamID().bits));
info.set("bots", utils::string::va("%i", get_bot_count())); info.set("mapname", game::get_dvar_string("mapname"));
info.set("sv_maxclients", utils::string::va("%i", get_max_client_count())); info.set("isPrivate", game::get_dvar_string("g_password").empty() ? "0" : "1");
info.set("protocol", utils::string::va("%i", PROTOCOL)); info.set("clients", std::to_string(get_client_count()));
info.set("playmode", utils::string::va("%i", game::Com_SessionMode_GetMode())); info.set("bots", std::to_string(get_bot_count()));
info.set("gamemode", utils::string::va("%i", Com_SessionMode_GetGameMode())); info.set("sv_maxclients", std::to_string(get_max_client_count()));
//info.set("sv_running", utils::string::va("%i", get_dvar_bool("sv_running"))); info.set("protocol", std::to_string(PROTOCOL));
info.set("dedicated", utils::string::va("%i", game::is_server() ? 1 : 0)); info.set("playmode", std::to_string(game::Com_SessionMode_GetMode()));
info.set("shortversion", SHORTVERSION); info.set("gamemode", std::to_string(Com_SessionMode_GetGameMode()));
info.set("sv_running", std::to_string(game::is_server_running()));
network::send(target, "infoResponse", info.build(), '\n'); 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);
REGISTER_COMPONENT(getinfo::component) network::send(target, "infoResponse", info.build(), '\n');
});
}
};
}
REGISTER_COMPONENT(getinfo::component)

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);
handler->second(*address, data); try
{
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

@ -1,41 +1,68 @@
#include <std_include.hpp> #include <std_include.hpp>
#include "loader/component_loader.hpp" #include "loader/component_loader.hpp"
#include <game/game.hpp> #include <game/game.hpp>
#include <utils/hook.hpp> #include <game/utils.hpp>
namespace patches #include "network.hpp"
{ #include "scheduler.hpp"
namespace
{ #include <utils/hook.hpp>
void script_errors_stub(const char* file, int line, unsigned int code, const char* fmt, ...)
{ namespace patches
char buffer[0x1000]; {
namespace
{ {
va_list ap; utils::hook::detour sv_execute_client_messages_hook;
va_start(ap, fmt);
vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, fmt, ap); void sv_execute_client_messages_stub(game::client_s* client, game::msg_t* msg)
va_end(ap); {
} if (client->reliableAcknowledge < 0)
{
game::Com_Error(game::ERROR_SCRIPT_DROP, "%s", buffer); client->reliableAcknowledge = client->reliableSequence;
} network::send(client->address, "error", "EXE_LOSTRELIABLECOMMANDS");
} return;
}
struct component final : generic_component
{ sv_execute_client_messages_hook.invoke<void>(client, msg);
void post_unpack() override }
{
// don't make script errors fatal error void script_errors_stub(const char* file, int line, unsigned int code, const char* fmt, ...)
utils::hook::call(game::select(0x1412CAC4D, 0x140158EB2), script_errors_stub); {
char buffer[0x1000];
// Change 4 character name limit to 3 characters
utils::hook::set<uint8_t>(game::select(0x14224DA53, 0x140531143), 3); {
utils::hook::set<uint8_t>(game::select(0x14224DBB4, 0x1405312A8), 3); va_list ap;
utils::hook::set<uint8_t>(game::select(0x14224DF8C, 0x1405316DC), 3); va_start(ap, fmt);
} vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, fmt, ap);
}; va_end(ap);
} }
REGISTER_COMPONENT(patches::component) game::Com_Error(game::ERROR_SCRIPT_DROP, "%s", buffer);
}
}
struct component final : generic_component
{
void post_unpack() override
{
// don't make script errors fatal error
utils::hook::call(game::select(0x1412CAC4D, 0x140158EB2), script_errors_stub);
// Change 4 character name limit to 3 characters
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(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);
}
};
}
REGISTER_COMPONENT(patches::component)

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()
{ {
@ -29,7 +33,7 @@ namespace profile_infos
profile_info info{}; profile_info info{};
constexpr auto version_size = sizeof(info.version); constexpr auto version_size = sizeof(info.version);
if(data.size() < sizeof(version_size)) if (data.size() < sizeof(version_size))
{ {
return {}; return {};
} }
@ -37,15 +41,168 @@ namespace profile_infos
memcpy(&info.version, data.data(), version_size); memcpy(&info.version, data.data(), version_size);
info.ddl.assign(data.begin() + version_size, data.end()); info.ddl.assign(data.begin() + version_size, data.end());
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());
});
}
void distribute_profile_info(const uint64_t user_id, const profile_info& info)
{
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);
}
}
});
} }
} }
std::optional<profile_info> get_profile_info(uint64_t user_id) 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) if (user_id == steam::SteamUser()->GetSteamID().bits)
{ {
return load_profile_info(); 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();
}
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)
@ -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{};
@ -72,6 +75,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);
}
} }
} }
@ -131,7 +145,9 @@ namespace server_list
s.callback(false, {}); s.callback(false, {});
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);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,248 +1,278 @@
#pragma once #pragma once
#include "structs.hpp" #include "structs.hpp"
#define WEAK __declspec(selectany) #define WEAK __declspec(selectany)
namespace game namespace game
{ {
#define Com_Error(code, fmt, ...) Com_Error_(__FILE__, __LINE__, code, fmt, ##__VA_ARGS__) #define Com_Error(code, fmt, ...) Com_Error_(__FILE__, __LINE__, code, fmt, ##__VA_ARGS__)
// CL // CL
WEAK symbol<void(int controllerIndex, XSESSION_INFO* hostInfo, const netadr_t* addr, int numPublicSlots, WEAK symbol<void(int controllerIndex, XSESSION_INFO* hostInfo, const netadr_t* addr, int numPublicSlots,
int numPrivateSlots, const char* mapname, const char* gametype, int numPrivateSlots, const char* mapname, const char* gametype,
const char* somethingWithUserMaps)> CL_ConnectFromLobby const char* somethingWithUserMaps)> CL_ConnectFromLobby
{0x14134C570}; {0x14134C570};
WEAK symbol<bool(int localClientNum, int index, char* buf, int size, bool addClanName)> CL_GetClientName{ WEAK symbol<bool(int localClientNum, int index, char* buf, int size, bool addClanName)> CL_GetClientName{
0x1413E3140 0x1413E3140
}; };
// Game // Game
WEAK symbol<void(gentity_s* ent, gentity_s* target, int mode, const char* chatText)> G_Say{0x0, 0x140299170}; WEAK symbol<void(gentity_s* ent, gentity_s* target, int mode, const char* chatText)> G_Say{0x0, 0x140299170};
WEAK symbol<void(const char* fmt, ...)> G_LogPrintf{0x0, 0x1402A7BB0}; WEAK symbol<void(const char* fmt, ...)> G_LogPrintf{0x0, 0x1402A7BB0};
// Com // Com
WEAK symbol<void(int channel, unsigned int label, const char* fmt, ...)> Com_Printf{0x142148F60, 0x140505630}; WEAK symbol<void(int channel, unsigned int label, const char* fmt, ...)> Com_Printf{0x142148F60, 0x140505630};
WEAK symbol<void(const char* file, int line, int code, const char* fmt, ...)> Com_Error_{0x1420F8170, 0x140501470}; WEAK symbol<void(const char* file, int line, int code, const char* fmt, ...)> Com_Error_{0x1420F8170, 0x140501470};
WEAK symbol<bool(eModes mode)> Com_SessionMode_IsMode{0x1420F7370}; WEAK symbol<bool(eModes mode)> Com_SessionMode_IsMode{0x1420F7370};
WEAK symbol<void(eNetworkModes networkMode)> Com_SessionMode_SetNetworkMode{0x1420F75B0, 0x140500B80}; WEAK symbol<void(eNetworkModes networkMode)> Com_SessionMode_SetNetworkMode{0x1420F75B0, 0x140500B80};
WEAK symbol<eGameModes(eGameModes gameMode)> Com_SessionMode_SetGameMode{0x1420F7570, 0x140500B40}; WEAK symbol<eGameModes(eGameModes gameMode)> Com_SessionMode_SetGameMode{0x1420F7570, 0x140500B40};
WEAK symbol<eModes(eModes mode)> Com_SessionMode_SetMode{0x1420F7570}; WEAK symbol<eModes(eModes mode)> Com_SessionMode_SetMode{0x1420F7570};
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<bool()> Com_IsRunningUILevel{0x142148350}; WEAK symbol<unsigned int(const char* settingName, bool getDefault)> Com_GametypeSettings_GetUInt{
WEAK symbol<void(int localClientNum, eModes fromMode, eModes toMode, uint32_t flags)> Com_SwitchMode{ 0x1420F4E00, 0x1404FE5C0
0x14214A4D0 };
}; 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, const char* text)> Cbuf_AddText{0x1420EC010, 0x1404F75B0}; 0x14214A4D0
WEAK symbol<void(int localClientNum, ControllerIndex_t controllerIndex, const char* buffer)> Cbuf_ExecuteBuffer{ };
0x14133BE10, 0x1404F78D0 WEAK symbol<const char*(const char* fullpath)> Com_LoadRawTextFile{0x1420F61B0};
};
WEAK symbol<void(const char* cmdName, xcommand_t function, cmd_function_s* allocedCmd)> Cmd_AddCommandInternal{ WEAK symbol<void(int localClientNum, const char* text)> Cbuf_AddText{0x1420EC010, 0x1404F75B0};
0x1420ECC90, 0x1404F8210 WEAK symbol<void(int localClientNum, ControllerIndex_t controllerIndex, const char* buffer)> Cbuf_ExecuteBuffer{
}; 0x14133BE10, 0x1404F78D0
WEAK symbol<void()> Cbuf_AddServerText_f{0x0, 0x1407DB4C0}; };
WEAK symbol<void(const char* cmdName, xcommand_t function, cmd_function_s* allocedCmd)> Cmd_AddServerCommandInternal WEAK symbol<void(const char* cmdName, xcommand_t function, cmd_function_s* allocedCmd)> Cmd_AddCommandInternal{
{ 0x1420ECC90, 0x1404F8210
0x0, 0x1404F8280 };
}; WEAK symbol<void()> Cbuf_AddServerText_f{0x0, 0x1407DB4C0};
WEAK symbol<void(int localClientNum, ControllerIndex_t controllerIndex, const char* text, WEAK symbol<void(const char* cmdName, xcommand_t function, cmd_function_s* allocedCmd)> Cmd_AddServerCommandInternal
bool fromRemoteConsole)> Cmd_ExecuteSingleCommand{ {
0x1420ED380, 0x1404F8890 0x0, 0x1404F8280
}; };
WEAK symbol<void(int localClientNum, ControllerIndex_t localControllerIndex, const char* text_in, int max_tokens, WEAK symbol<void(int localClientNum, ControllerIndex_t controllerIndex, const char* text,
bool evalExpressions, CmdArgs* args)> Cmd_TokenizeStringKernel{0x1420EED60, 0x1404FA300}; bool fromRemoteConsole)> Cmd_ExecuteSingleCommand{
WEAK symbol<void()> Cmd_EndTokenizedString{0x1420ECED0, 0x1404F8420}; 0x1420ED380, 0x1404F8890
WEAK symbol<void(char* text, int maxSize)> Con_GetTextCopy{0x14133A7D0, 0x140182C40}; };
WEAK symbol<void(int localClientNum, ControllerIndex_t localControllerIndex, const char* text_in, int max_tokens,
// DB bool evalExpressions, CmdArgs* args)> Cmd_TokenizeStringKernel{0x1420EED60, 0x1404FA300};
WEAK symbol<void(XZoneInfo* zoneInfo, uint32_t zoneCount, bool sync, bool suppressSync)> DB_LoadXAssets{ WEAK symbol<void()> Cmd_EndTokenizedString{0x1420ECED0, 0x1404F8420};
0x1414236A0 WEAK symbol<void(char* text, int maxSize)> Con_GetTextCopy{0x14133A7D0, 0x140182C40};
};
WEAK symbol<void(XAssetType type, XAssetEnum* func, void* inData, bool includeOverride)> DB_EnumXAssets{ // DB
0x141420970, 0x1401D5A50 WEAK symbol<void(XZoneInfo* zoneInfo, uint32_t zoneCount, bool sync, bool suppressSync)> DB_LoadXAssets{
}; 0x1414236A0
WEAK symbol<XAssetHeader(XAssetType type, const char* name, bool errorIfMissing, int waitTime)> DB_FindXAssetHeader{ };
0x141420ED0, 0x1401D5FB0 WEAK symbol<void(XAssetType type, XAssetEnum* func, void* inData, bool includeOverride)> DB_EnumXAssets{
}; 0x141420970, 0x1401D5A50
WEAK symbol<const char*(const XAsset* asset)> DB_GetXAssetName{0x1413E9DA0, 0x14019F080}; };
WEAK symbol<bool(const char* zoneName, int source)> DB_FileExists{0x141420B40}; WEAK symbol<XAssetHeader(XAssetType type, const char* name, bool errorIfMissing, int waitTime)> DB_FindXAssetHeader{
WEAK symbol<void()> DB_ReleaseXAssets{0x1414247C0}; 0x141420ED0, 0x1401D5FB0
};
// Live WEAK symbol<const char*(const XAsset* asset)> DB_GetXAssetName{0x1413E9DA0, 0x14019F080};
WEAK symbol<bool(uint64_t, int*, bool)> Live_GetConnectivityInformation{0x141E0C380}; WEAK symbol<bool(const char* zoneName, int source)> DB_FileExists{0x141420B40};
WEAK symbol<void()> DB_ReleaseXAssets{0x1414247C0};
// MSG
WEAK symbol<uint8_t(msg_t* msg)> MSG_ReadByte{0x142155450, 0x14050D1B0}; // G
WEAK symbol<void()> G_ClearVehicleInputs{0x1423812E0, 0x1405C1200};
// NET
WEAK symbol<bool(netsrc_t sock, int length, const void* data, const netadr_t* to)> NET_SendPacket{ WEAK symbol<qboolean(void* ent)> StuckInClient{0x1415A8360, 0x14023BFE0};
0x1423323B0, 0x140596E40
}; // Live
WEAK symbol<bool(char const*, netadr_t*)> NET_StringToAdr{0x142172780, 0x140515110}; WEAK symbol<bool(uint64_t, int*, bool)> Live_GetConnectivityInformation{0x141E0C380};
// Sys // Info
WEAK symbol<int()> Sys_Milliseconds{0x142332870}; WEAK symbol<const char*(const char*, const char* key)> Info_ValueForKey{0x1422E87B0};
WEAK symbol<void()> Sys_ShowConsole{0x1423333C0, 0x140597E40};
WEAK symbol<TLSData*()> Sys_GetTLS{0x1421837B0, 0x140525EB0}; // MSG
WEAK symbol<TLSData*()> Sys_IsDatabaseReady{0x142183A60}; WEAK symbol<uint8_t(msg_t* msg)> MSG_ReadByte{0x142155450, 0x14050D1B0};
// Unnamed // NET
WEAK symbol<const char*(const char* name)> CopyString{0x1422AC220, 0x14056BD70}; 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()> isModLoaded{0x1420D5020}; 0x1423323B0, 0x140596E40
WEAK symbol<void(int, const char*, int)> loadMod{0x1420D6930}; };
WEAK symbol<bool(char const*, netadr_t*)> NET_StringToAdr{0x142172780, 0x140515110};
// Dvar
WEAK symbol<bool(const dvar_t* dvar)> Dvar_IsSessionModeBaseDvar{0x1422C23A0, 0x140576890}; // Sys
WEAK symbol<dvar_t*(const char* dvarName)> Dvar_FindVar{0x1422BCCD0, 0x140575540}; WEAK symbol<int()> Sys_Milliseconds{0x142332870};
WEAK symbol<unsigned int(const char* str)> Dvar_GenerateHash{0x14133DBF0, 0x140185800}; WEAK symbol<void()> Sys_ShowConsole{0x1423333C0, 0x140597E40};
WEAK symbol<dvar_t*(unsigned int hash)> Dvar_FindMalleableVar{0x1422BCC40}; WEAK symbol<TLSData*()> Sys_GetTLS{0x1421837B0, 0x140525EB0};
WEAK symbol<const char*(const dvar_t* dvar)> Dvar_GetDebugName{0x1422BD250}; WEAK symbol<TLSData*()> Sys_IsDatabaseReady{0x142183A60};
WEAK symbol<const char*(const dvar_t* dvar)> Dvar_GetString{0x1422BF590, 0x140575E30};
WEAK symbol<const char*(const dvar_t* dvar)> Dvar_DisplayableValue{0x1422BC080}; // Unnamed
WEAK symbol<bool(const dvar_t* dvar)> Dvar_GetBool{0x1422BCED0}; WEAK symbol<const char*(const char* name)> CopyString{0x1422AC220, 0x14056BD70};
WEAK symbol<int(const dvar_t* dvar)> Dvar_GetInt{0x0, 0x140575C20};
WEAK symbol<dvar_t*(dvarStrHash_t hash, const char* dvarName, bool value, int flags, WEAK symbol<bool()> isModLoaded{0x1420D5020};
const char* description)> Dvar_RegisterBool{ WEAK symbol<void(int, const char*, bool)> loadMod{0x1420D6930};
0x1422D0900, 0x14057B500
}; // Dvar
WEAK symbol<dvar_t*(dvarStrHash_t hash, const char* dvarName, bool value, int flags, WEAK symbol<bool(const dvar_t* dvar)> Dvar_IsSessionModeBaseDvar{0x1422C23A0, 0x140576890};
const char* description)> Dvar_SessionModeRegisterBool{ WEAK symbol<dvar_t*(const char* dvarName)> Dvar_FindVar{0x1422BCCD0, 0x140575540};
0x1422D0D40, 0x14057BAA0 WEAK symbol<unsigned int(const char* str)> Dvar_GenerateHash{0x14133DBF0, 0x140185800};
}; WEAK symbol<dvar_t*(unsigned int hash)> Dvar_FindMalleableVar{0x1422BCC40};
WEAK symbol<void(dvarStrHash_t hash, bool value, eModes mode)> Dvar_SessionModeSetDefaultBool{ WEAK symbol<const char*(const dvar_t* dvar)> Dvar_GetDebugName{0x1422BD250};
0x1422D0E90, 0x14057BCE0 WEAK symbol<const char*(const dvar_t* dvar)> Dvar_GetString{0x1422BF590, 0x140575E30};
}; WEAK symbol<const char*(const dvar_t* dvar)> Dvar_DisplayableValue{0x1422BC080};
WEAK symbol<dvar_t*(dvarStrHash_t hash, const char* dvarName, const char* value, int flags, WEAK symbol<bool(const dvar_t* dvar)> Dvar_GetBool{0x1422BCED0};
const char* description)> Dvar_RegisterString{ WEAK symbol<int(const dvar_t* dvar)> Dvar_GetInt{0x0, 0x140575C20};
0x1422D0B70, 0x14057B890 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<void(void (*callback)(const dvar_t*, void*), void* userData)> Dvar_ForEach{0x1422BCD00}; const char* description)> Dvar_RegisterBool{
WEAK symbol<void(const char* dvarName, const char* string, bool createIfMissing)> Dvar_SetFromStringByName{ 0x1422D0900, 0x14057B500
0x1422C7500 };
}; WEAK symbol<dvar_t*(dvarStrHash_t hash, const char* dvarName, float value, float min, float max, unsigned int flags,
WEAK symbol<dvar_t*(dvar_t* dvar, eModes mode)> Dvar_GetSessionModeSpecificDvar{ const char* description)> Dvar_RegisterFloat{
0x1422BF500, 0x140575D90 0x0, 0x14057B6B0
}; };
WEAK symbol<dvar_t*(dvarStrHash_t hash, const char* dvarName, bool value, int flags,
// UI const char* description)> Dvar_SessionModeRegisterBool{
WEAK symbol<void(bool frontend)> UI_CoD_Init{0x141F29010, 0x1404A0A50}; 0x1422D0D40, 0x14057BAA0
WEAK symbol<void()> UI_CoD_LobbyUI_Init{0x141F2BD80, 0x1404A1F50}; };
WEAK symbol<void()> UI_CoD_Shutdown{0x141F32E10, 0x0}; WEAK symbol<void(dvarStrHash_t hash, bool value, eModes mode)> Dvar_SessionModeSetDefaultBool{
WEAK symbol<void(const char*, const char*, int, game::hks::lua_State*)> UI_AddMenu{0x1427018F0, 0x0}; 0x1422D0E90, 0x14057BCE0
WEAK symbol<const char*(int)> UI_CoD_GetRootNameForController{0x141F28940, 0x0}; };
WEAK symbol<void(hks::lua_State*, const char*)> Lua_CoD_LoadLuaFile{0x141F11A20, 0x0}; WEAK symbol<dvar_t*(dvarStrHash_t hash, const char* dvarName, const char* value, int flags,
WEAK symbol<void(int localClientNum)> CG_LUIHUDRestart{0x140F7E970}; const char* description)> Dvar_RegisterString{
WEAK symbol<void(int localClientNum)> CL_CheckKeepDrawingConnectScreen{0x1413CCAE0}; 0x1422D0B70, 0x14057B890
};
// Scr WEAK symbol<void(void (*callback)(const dvar_t*, void*), void* userData)> Dvar_ForEach{0x1422BCD00};
WEAK symbol<void(scriptInstance_t inst, int value)> Scr_AddInt{0x0, 0x14016F160}; WEAK symbol<void(const char* dvarName, const char* string, bool createIfMissing)> Dvar_SetFromStringByName{
WEAK symbol<void(scriptInstance_t inst, const char* value)> Scr_AddString{0x0, 0x14016F320}; 0x1422C7500
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<dvar_t*(dvar_t* dvar, eModes mode)> Dvar_GetSessionModeSpecificDvar{
0x0, 0x1402F5FF0 0x1422BF500, 0x140575D90
}; };
WEAK symbol<unsigned int(scriptInstance_t inst)> Scr_GetNumParam{0x0, 0x140171320};
// UI
WEAK symbol<void(const char* name, const char* key, unsigned int playbackFlags, float volume, void* callbackInfo, WEAK symbol<void(int localClientNumber, int errorcode, const char* errorMessage)> UI_OpenErrorPopupWithMessage{0x14228DEE0};
int id)> Cinematic_StartPlayback{0x1412BE3A0}; WEAK symbol<void(bool frontend)> UI_CoD_Init{0x141F29010, 0x1404A0A50};
WEAK symbol<void(uint64_t id, bool cancelAll)> Cinematic_StopPlayback{0x1412BEA70}; WEAK symbol<void()> UI_CoD_LobbyUI_Init{0x141F2BD80, 0x1404A1F50};
WEAK symbol<void()> UI_CoD_Shutdown{0x141F32E10, 0x0};
// Rendering WEAK symbol<void(const char*, const char*, int, game::hks::lua_State*)> UI_AddMenu{0x1427018F0, 0x0};
WEAK symbol<void(const char*, int, const void*, float, float, float, float, float, const float*, int)> WEAK symbol<const char*(int)> UI_CoD_GetRootNameForController{0x141F28940, 0x0};
R_AddCmdDrawText{ WEAK symbol<void(hks::lua_State*, const char*)> Lua_CoD_LoadLuaFile{0x141F11A20, 0x0};
0x141CD98D0 WEAK symbol<void(int localClientNum)> CG_LUIHUDRestart{0x140F7E970};
}; WEAK symbol<void(int localClientNum)> CL_CheckKeepDrawingConnectScreen{0x1413CCAE0};
WEAK symbol<void(const char* key, int value, hks::lua_State* luaVM)> Lua_SetTableInt{0x141F066E0};
// SV
WEAK symbol<bool()> SV_Loaded{0x142252250, 0x140535460}; // Scr
WEAK symbol<void*()> SV_AddTestClient{0x142248F40, 0x14052E3E0}; WEAK symbol<void(scriptInstance_t inst, int value)> Scr_AddInt{0x1412E9870, 0x14016F160};
WEAK symbol<void (netadr_t from)> SV_DirectConnect{0x142249880, 0x14052EC60}; WEAK symbol<void(scriptInstance_t inst, const char* value)> Scr_AddString{0x0, 0x14016F320};
WEAK symbol<void(int clientNum, svscmd_type type, const char* text)> SV_GameSendServerCommand{ WEAK symbol<const char*(scriptInstance_t inst, unsigned int index)> Scr_GetString{0x0, 0x140171490};
0x14224F580, 0x140532CA0 WEAK symbol<void(gentity_s* ent, ScrVarCanonicalName_t stringValue, unsigned int paramcount)> Scr_Notify_Canon{
}; 0x0, 0x1402F5FF0
WEAK symbol<void(client_s* cl_0, svscmd_type type, const char* fmt, ...)> SV_SendServerCommand{0x0, 0x140537F10}; };
WEAK symbol<bool(int clientNum)> SV_IsTestClient{0x14224AB60, 0x14052FF40}; WEAK symbol<unsigned int(scriptInstance_t inst)> Scr_GetNumParam{0x0, 0x140171320};
WEAK symbol<void(int controllerIndex, const char* server, MapPreload preload, bool savegame)> SV_SpawnServer{
0x1422528C0, 0x140535B20 WEAK symbol<void(const char* name, const char* key, unsigned int playbackFlags, float volume, void* callbackInfo,
}; int id)> Cinematic_StartPlayback{0x1412BE3A0};
WEAK symbol<void(const char* text_in)> SV_Cmd_TokenizeString{0x1420EF130, 0x1404FA6C0}; WEAK symbol<void(uint64_t id, bool cancelAll)> Cinematic_StopPlayback{0x1412BEA70};
WEAK symbol<void()> SV_Cmd_EndTokenizedString{0x1420EF0E0, 0x1404FA670};
// Rendering
// Utils WEAK symbol<void(const char*, int, const void*, float, float, float, float, float, const float*, int)>
WEAK symbol<const char*(char* str)> I_CleanStr{0x1422E9050, 0x140580E80}; R_AddCmdDrawText{
0x141CD98D0
// Variables };
WEAK symbol<cmd_function_s> cmd_functions{0x15689DF58, 0x14946F860};
WEAK symbol<CmdArgs> sv_cmd_args{0x15689AE30, 0x14944C740}; // PCache
WEAK symbol<void(ControllerIndex_t controllerIndex)> PCache_DeleteEntries{0x141E8D710};
WEAK symbol<gentity_s> g_entities{0x0, 0x1471031B0};
// SV
WEAK symbol<int> level_time{0x0, 0x1474FDC94}; WEAK symbol<bool()> SV_Loaded{0x142252250, 0x140535460};
WEAK symbol<void*()> SV_AddTestClient{0x142248F40, 0x14052E3E0};
WEAK symbol<SOCKET> ip_socket{0x157E75818, 0x14A640988}; WEAK symbol<void (netadr_t from)> SV_DirectConnect{0x142249880, 0x14052EC60};
WEAK symbol<void(int clientNum, svscmd_type type, const char* text)> SV_GameSendServerCommand{
WEAK symbol<Join> s_join{0x15574A640}; 0x14224F580, 0x140532CA0
};
WEAK symbol<char> s_dvarPool{0x157AC6220, 0x14A3CB620}; WEAK symbol<void(client_s* cl_0, svscmd_type type, const char* fmt, ...)> SV_SendServerCommand{0x0, 0x140537F10};
WEAK symbol<int> g_dvarCount{0x157AC61CC, 0x14A3CB5FC}; WEAK symbol<bool(int clientNum)> SV_IsTestClient{0x14224AB60, 0x14052FF40};
WEAK symbol<void(int controllerIndex, const char* server, MapPreload preload, bool savegame)> SV_SpawnServer{
WEAK symbol<client_s> svs_clients{0x0, 0x14A178E98}; 0x1422528C0, 0x140535B20
};
// Dvar variables WEAK symbol<void(const char* text_in)> SV_Cmd_TokenizeString{0x1420EF130, 0x1404FA6C0};
WEAK symbol<dvar_t*> com_maxclients{0x0, 0x14948EE70}; WEAK symbol<void()> SV_Cmd_EndTokenizedString{0x1420EF0E0, 0x1404FA670};
namespace s_wcd // FS
{ WEAK symbol<char*(int bytes)> FS_AllocMem{0x1422AC9F0, 0x14056C340};
WEAK symbol<HWND> codLogo{0x157E75A50, 0x14A640BC0};
WEAK symbol<HFONT> hfBufferFont{0x157E75A58, 0x14A640BC8}; // Utils
WEAK symbol<HWND> hWnd{0x157E75A40, 0x14A640BB0}; WEAK symbol<const char*(char* str)> I_CleanStr{0x1422E9050, 0x140580E80};
WEAK symbol<HWND> hwndBuffer{0x157E75A48, 0x14A640BB8};
WEAK symbol<HWND> hwndInputLine{0x157E75A60, 0x14A640BD0}; // Variables
WEAK symbol<int> windowHeight{0x157E7606C, 0x14A6411DC}; WEAK symbol<cmd_function_s> cmd_functions{0x15689DF58, 0x14946F860};
WEAK symbol<int> windowWidth{0x157E76068, 0x14A6411D8}; WEAK symbol<CmdArgs> sv_cmd_args{0x15689AE30, 0x14944C740};
WEAK symbol<WNDPROC> SysInputLineWndProc{0x157E76070, 0x14A6411E0};
} WEAK symbol<gentity_s> g_entities{0x0, 0x1471031B0};
// Global game definitions WEAK symbol<int> level_time{0x0, 0x1474FDC94};
constexpr auto CMD_MAX_NESTING = 8;
WEAK symbol<SOCKET> ip_socket{0x157E75818, 0x14A640988};
// Re-implementations
eModes Com_SessionMode_GetMode(); WEAK symbol<Join> s_join{0x15574A640};
bool I_islower(int c); WEAK symbol<char> s_dvarPool{0x157AC6220, 0x14A3CB620};
bool I_isupper(int c); WEAK symbol<int> g_dvarCount{0x157AC61CC, 0x14A3CB5FC};
unsigned int Scr_CanonHash(const char* str); WEAK symbol<int> fs_loadStack{0x157A65310, 0x14A39C650};
namespace hks // Client and dedi struct size differs :(
{ WEAK symbol<client_s_cl*> svs_clients_cl{0x1576F9318, 0};
WEAK symbol<lua_State*> lua_state{0x159C76D88, 0x14858C408}; WEAK symbol<client_s*> svs_clients{0x0, 0x14A178E98};
WEAK symbol<void(lua_State* s, const char* str, unsigned int l)> hksi_lua_pushlstring{0x140A18430, 0x1401DE6F0};
// Dvar variables
WEAK symbol<void(lua_State* s, const HksObject* tbl, const HksObject* key, const HksObject* val)> WEAK symbol<dvar_t*> com_maxclients{0x0, 0x14948EE70};
hks_obj_settable{0x141D4B660, 0x1403F41B0};
WEAK symbol<HksObject*(HksObject* result, lua_State* s, const HksObject* table, const HksObject* key)> namespace s_wcd
hks_obj_gettable{0x141D4ABF0, 0x1403F3750}; {
WEAK symbol<void(lua_State* s, int nargs, int nresults, const unsigned int* pc)> vm_call_internal{ WEAK symbol<HWND> codLogo{0x157E75A50, 0x14A640BC0};
0x141D70FE0, 0x140418E40 WEAK symbol<HFONT> hfBufferFont{0x157E75A58, 0x14A640BC8};
}; WEAK symbol<HWND> hWnd{0x157E75A40, 0x14A640BB0};
WEAK symbol<HashTable*(lua_State* s, unsigned int arraySize, unsigned int hashSize)> Hashtable_Create{ WEAK symbol<HWND> hwndBuffer{0x157E75A48, 0x14A640BB8};
0x141D3B5F0, 0x1403E46D0 WEAK symbol<HWND> hwndInputLine{0x157E75A60, 0x14A640BD0};
}; WEAK symbol<int> windowHeight{0x157E7606C, 0x14A6411DC};
WEAK symbol<cclosure*(lua_State* s, lua_function function, int num_upvalues, int internal_, WEAK symbol<int> windowWidth{0x157E76068, 0x14A6411D8};
int profilerTreatClosureAsFunc)> cclosure_Create{0x141D3B7E0, 0x1403E48C0}; WEAK symbol<WNDPROC> SysInputLineWndProc{0x157E76070, 0x14A6411E0};
WEAK symbol<int(lua_State* s, int t)> hksi_luaL_ref{0x141D4D1A0, 0x1403F5CF0}; }
WEAK symbol<void(lua_State* s, int t, int ref)> hksi_luaL_unref{0x141D4D320, 0x1403F5E70};
// Global game definitions
WEAK symbol<int(lua_State* s, const HksCompilerSettings* options, const char* buff, unsigned __int64 sz, constexpr auto CMD_MAX_NESTING = 8;
const char* name)> hksi_hksL_loadbuffer{0x141D4BD80, 0x1403F48D0};
WEAK symbol<int(lua_State* s, const char* what, lua_Debug* ar)> hksi_lua_getinfo{0x141D4D8D0, 0x1403F64B0}; // Re-implementations
WEAK symbol<int(lua_State* s, int level, lua_Debug* ar)> hksi_lua_getstack{0x141D4DB90, 0x1403F6770}; eModes Com_SessionMode_GetMode();
WEAK symbol<void(lua_State* s, const char* fmt, ...)> hksi_luaL_error{0x141D4D050, 0x1403F5BA0};
WEAK symbol<const char*> s_compilerTypeName{0x140A18430}; bool I_islower(int c);
} bool I_isupper(int c);
}
unsigned int Scr_CanonHash(const char* str);
namespace hks
{
WEAK symbol<lua_State*> lua_state{0x159C76D88, 0x14858C408};
WEAK symbol<void(lua_State* s, const char* str, unsigned int l)> hksi_lua_pushlstring{0x140A18430, 0x1401DE6F0};
WEAK symbol<void(lua_State* s, const HksObject* tbl, const HksObject* key, const HksObject* val)>
hks_obj_settable{0x141D4B660, 0x1403F41B0};
WEAK symbol<HksObject*(HksObject* result, lua_State* s, const HksObject* table, const HksObject* key)>
hks_obj_gettable{0x141D4ABF0, 0x1403F3750};
WEAK symbol<void(lua_State* s, int nargs, int nresults, const unsigned int* pc)> vm_call_internal{
0x141D70FE0, 0x140418E40
};
WEAK symbol<HashTable*(lua_State* s, unsigned int arraySize, unsigned int hashSize)> Hashtable_Create{
0x141D3B5F0, 0x1403E46D0
};
WEAK symbol<cclosure*(lua_State* s, lua_function function, int num_upvalues, int internal_,
int profilerTreatClosureAsFunc)> cclosure_Create{0x141D3B7E0, 0x1403E48C0};
WEAK symbol<int(lua_State* s, int t)> hksi_luaL_ref{0x141D4D1A0, 0x1403F5CF0};
WEAK symbol<void(lua_State* s, int t, int ref)> hksi_luaL_unref{0x141D4D320, 0x1403F5E70};
WEAK symbol<int(lua_State* s, const HksCompilerSettings* options, const char* buff, unsigned __int64 sz,
const char* name)> hksi_hksL_loadbuffer{0x141D4BD80, 0x1403F48D0};
WEAK symbol<int(lua_State* s, const char* what, lua_Debug* ar)> hksi_lua_getinfo{0x141D4D8D0, 0x1403F64B0};
WEAK symbol<int(lua_State* s, int level, lua_Debug* ar)> hksi_lua_getstack{0x141D4DB90, 0x1403F6770};
WEAK symbol<void(lua_State* s, const char* fmt, ...)> hksi_luaL_error{0x141D4D050, 0x1403F5BA0};
WEAK symbol<const char*> s_compilerTypeName{0x140A18430};
}
}

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());
} }