Merge pull request #250 from h1-mod/filesystem-and-mods
H2-mod Filesystem and mods
This commit is contained in:
commit
11de734ab6
13
data/cdata/ui_scripts/custom_depot/mod_eula.lua
Normal file
13
data/cdata/ui_scripts/custom_depot/mod_eula.lua
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
local mod_eula = function(unk1, unk2)
|
||||||
|
return LUI.EULABase.new(CoD.CreateState(0, 0, 0, 0, CoD.AnchorTypes.All), {
|
||||||
|
textStrings = LUI.EULABase.CreateTextStrings("@CUSTOM_DEPOT_EULA_", 6),
|
||||||
|
declineCallback = function(unk3)
|
||||||
|
unk2.declineCallback(unk3)
|
||||||
|
end,
|
||||||
|
acceptCallback = function(unk4)
|
||||||
|
unk2.acceptCallback(unk4)
|
||||||
|
end
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
LUI.MenuBuilder.registerPopupType("mod_eula", mod_eula)
|
@ -1,16 +1,5 @@
|
|||||||
local pcdisplay = luiglobals.require("LUI.PCDisplay")
|
local pcdisplay = luiglobals.require("LUI.PCDisplay")
|
||||||
|
|
||||||
game:addlocalizedstring("LUA_MENU_FPS", "FPS Counter")
|
|
||||||
game:addlocalizedstring("LUA_MENU_FPS_DESC", "Show FPS Counter.")
|
|
||||||
|
|
||||||
game:addlocalizedstring("LUA_MENU_LATENCY", "Server Latency")
|
|
||||||
game:addlocalizedstring("LUA_MENU_LATENCY_DESC", "Show server latency.")
|
|
||||||
|
|
||||||
game:addlocalizedstring("LUA_MENU_RED_DOT_BRIGHTNESS", "Red dot Brightness")
|
|
||||||
game:addlocalizedstring("LUA_MENU_RED_DOT_BRIGHTNESS_DESC", "Adjust the brightness of red dot reticles.")
|
|
||||||
|
|
||||||
game:addlocalizedstring("MENU_SYSINFO_CUSTOMER_SUPPORT_URL", "https://h1.gg/")
|
|
||||||
|
|
||||||
function createdivider(menu, text)
|
function createdivider(menu, text)
|
||||||
local element = LUI.UIElement.new({
|
local element = LUI.UIElement.new({
|
||||||
leftAnchor = true,
|
leftAnchor = true,
|
5
data/cdata/ui_scripts/mods/__init__.lua
Normal file
5
data/cdata/ui_scripts/mods/__init__.lua
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
require("loading")
|
||||||
|
|
||||||
|
if (Engine.InFrontend()) then
|
||||||
|
require("download")
|
||||||
|
end
|
@ -1,12 +1,3 @@
|
|||||||
game:addlocalizedstring("MENU_MODS", "MODS")
|
|
||||||
game:addlocalizedstring("MENU_MODS_DESC", "Load installed mods.")
|
|
||||||
game:addlocalizedstring("LUA_MENU_MOD_DESC_DEFAULT", "Load &&1.")
|
|
||||||
game:addlocalizedstring("LUA_MENU_MOD_DESC", "&&1\nAuthor: &&2\nVersion: &&3")
|
|
||||||
game:addlocalizedstring("LUA_MENU_LOADED_MOD", "Loaded mod: ^2&&1")
|
|
||||||
game:addlocalizedstring("LUA_MENU_AVAILABLE_MODS", "Available mods")
|
|
||||||
game:addlocalizedstring("LUA_MENU_UNLOAD", "Unload")
|
|
||||||
game:addlocalizedstring("LUA_MENU_UNLOAD_DESC", "Unload the currently loaded mod.")
|
|
||||||
|
|
||||||
function createdivider(menu, text)
|
function createdivider(menu, text)
|
||||||
local element = LUI.UIElement.new({
|
local element = LUI.UIElement.new({
|
||||||
leftAnchor = true,
|
leftAnchor = true,
|
||||||
@ -38,19 +29,20 @@ function string:truncate(length)
|
|||||||
return self:sub(1, length - 3) .. "..."
|
return self:sub(1, length - 3) .. "..."
|
||||||
end
|
end
|
||||||
|
|
||||||
LUI.addmenubutton("main_campaign", {
|
if (game:issingleplayer()) then
|
||||||
index = 6,
|
LUI.addmenubutton("main_campaign", {
|
||||||
text = "@MENU_MODS",
|
index = 6,
|
||||||
description = Engine.Localize("@MENU_MODS_DESC"),
|
text = "@MENU_MODS",
|
||||||
callback = function()
|
description = Engine.Localize("@MENU_MODS_DESC"),
|
||||||
LUI.FlowManager.RequestAddMenu(nil, "mods_menu")
|
callback = function()
|
||||||
end
|
LUI.FlowManager.RequestAddMenu(nil, "mods_menu")
|
||||||
})
|
end
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
function getmodname(path)
|
function getmodname(path)
|
||||||
local name = path
|
local name = path
|
||||||
game:addlocalizedstring(name, name)
|
game:addlocalizedstring(name, name)
|
||||||
game:addlocalizedstring("LUA_MENU_MOD_DESC_DEFAULT", "Load &&1.")
|
|
||||||
local desc = Engine.Localize("LUA_MENU_MOD_DESC_DEFAULT", name)
|
local desc = Engine.Localize("LUA_MENU_MOD_DESC_DEFAULT", name)
|
||||||
local infofile = path .. "/info.json"
|
local infofile = path .. "/info.json"
|
||||||
|
|
@ -1,8 +1,5 @@
|
|||||||
LUI.MenuBuilder.registerPopupType("ShaderCacheDialog_original", LUI.ShaderCacheDialog.new)
|
LUI.MenuBuilder.registerPopupType("ShaderCacheDialog_original", LUI.ShaderCacheDialog.new)
|
||||||
|
|
||||||
game:addlocalizedstring("PLATFORM_SHADER_PRECACHE_ASK", "Would you like to populate the shader cache? It may cause crashes with certain GPUs (e.g. RTX cards) but will improve performance if successful.")
|
|
||||||
game:addlocalizedstring("MENU_NO_DONT_ASK", "No, don't ask me again")
|
|
||||||
|
|
||||||
local function dialog(...)
|
local function dialog(...)
|
||||||
if (game:sharedget("has_accepted_shader_caching") == "1") then
|
if (game:sharedget("has_accepted_shader_caching") == "1") then
|
||||||
return LUI.ShaderCacheDialog.new(...)
|
return LUI.ShaderCacheDialog.new(...)
|
@ -1,8 +1,6 @@
|
|||||||
local Lobby = luiglobals.Lobby
|
local Lobby = luiglobals.Lobby
|
||||||
local MPLobbyOnline = LUI.mp_menus.MPLobbyOnline
|
local MPLobbyOnline = LUI.mp_menus.MPLobbyOnline
|
||||||
|
|
||||||
game:addlocalizedstring("LUA_MENU_SERVERLIST", "SERVER LIST")
|
|
||||||
|
|
||||||
function LeaveLobby(f5_arg0)
|
function LeaveLobby(f5_arg0)
|
||||||
LeaveXboxLive()
|
LeaveXboxLive()
|
||||||
if Lobby.IsInPrivateParty() == false or Lobby.IsPrivatePartyHost() then
|
if Lobby.IsInPrivateParty() == false or Lobby.IsPrivatePartyHost() then
|
||||||
@ -28,6 +26,12 @@ function menu_xboxlive(f16_arg0, f16_arg1)
|
|||||||
menu:AddBarracksButton()
|
menu:AddBarracksButton()
|
||||||
menu:AddPersonalizationButton()
|
menu:AddPersonalizationButton()
|
||||||
menu:AddDepotButton()
|
menu:AddDepotButton()
|
||||||
|
|
||||||
|
-- kinda a weird place to do this, but it's whatever
|
||||||
|
-- add "MODS" button below depot button
|
||||||
|
local modsButton = menu:AddButton("@MENU_MODS", function(a1, a2)
|
||||||
|
LUI.FlowManager.RequestAddMenu(a1, "mods_menu", true, nil)
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
local privateMatchButton = menu:AddButton("@MENU_PRIVATE_MATCH", MPLobbyOnline.OnPrivateMatch,
|
local privateMatchButton = menu:AddButton("@MENU_PRIVATE_MATCH", MPLobbyOnline.OnPrivateMatch,
|
@ -5,11 +5,6 @@ if (not SystemLinkJoinMenu) then
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
game:addlocalizedstring("MENU_NUMPLAYERS", "Players")
|
|
||||||
game:addlocalizedstring("MENU_PING", "Ping")
|
|
||||||
game:addlocalizedstring("SERVERLIST_PLAYER_COUNT", "&&1 Players")
|
|
||||||
game:addlocalizedstring("SERVERLIST_SERVER_COUNT", "&&1 Servers")
|
|
||||||
|
|
||||||
local columns = {
|
local columns = {
|
||||||
{
|
{
|
||||||
offset = 40,
|
offset = 40,
|
@ -2,32 +2,6 @@ if (game:issingleplayer() or not Engine.InFrontend()) then
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
game:addlocalizedstring("LUA_MENU_STATS", "Stats")
|
|
||||||
game:addlocalizedstring("LUA_MENU_STATS_DESC", "Edit player stats settings.")
|
|
||||||
|
|
||||||
game:addlocalizedstring("LUA_MENU_UNLOCKALL_ITEMS", "Unlock all items")
|
|
||||||
game:addlocalizedstring("LUA_MENU_UNLOCKALL_ITEMS_DESC",
|
|
||||||
"Whether items should be locked based on the player's stats or always unlocked.")
|
|
||||||
|
|
||||||
game:addlocalizedstring("LUA_MENU_UNLOCKALL_LOOT", "Unlock all loot")
|
|
||||||
game:addlocalizedstring("LUA_MENU_UNLOCKALL_LOOT_DESC",
|
|
||||||
"Whether loot should be locked based on the player's stats or always unlocked.")
|
|
||||||
|
|
||||||
game:addlocalizedstring("LUA_MENU_UNLOCKALL_CLASSES", "Unlock all classes")
|
|
||||||
game:addlocalizedstring("LUA_MENU_UNLOCKALL_CLASSES_DESC",
|
|
||||||
"Whether classes should be locked based on the player's stats or always unlocked.")
|
|
||||||
|
|
||||||
game:addlocalizedstring("LUA_MENU_PRESTIGE", "Prestige")
|
|
||||||
game:addlocalizedstring("LUA_MENU_PRESTIGE_DESC", "Edit prestige level.")
|
|
||||||
game:addlocalizedstring("LUA_MENU_RANK", "Rank")
|
|
||||||
game:addlocalizedstring("LUA_MENU_RANK_DESC", "Edit rank.")
|
|
||||||
|
|
||||||
game:addlocalizedstring("LUA_MENU_UNSAVED_CHANGES", "You have unsaved changes, are you sure you want to exit?")
|
|
||||||
game:addlocalizedstring("LUA_MENU_SAVE", "Save changes")
|
|
||||||
game:addlocalizedstring("LUA_MENU_SAVE_DESC", "Save changes.")
|
|
||||||
game:addlocalizedstring("LUA_MENU_SETTINGS", "Settings")
|
|
||||||
game:addlocalizedstring("LUA_MENU_EDIT_STATS", "Edit Stats")
|
|
||||||
|
|
||||||
function createdivider(menu, text)
|
function createdivider(menu, text)
|
||||||
local element = LUI.UIElement.new({
|
local element = LUI.UIElement.new({
|
||||||
leftAnchor = true,
|
leftAnchor = true,
|
@ -1,117 +0,0 @@
|
|||||||
-- modified version of https://github.com/Joelrau/S1x-IW6x-g_log-script (permission to use by author)
|
|
||||||
|
|
||||||
if (game:getdvar("gamemode") ~= "mp") then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- setup dvars
|
|
||||||
game:setdvarifuninitialized("logfile", 1)
|
|
||||||
if (tonumber(game:getdvar("logfile")) < 1) then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
game:setdvarifuninitialized("g_log", "logs/games_mp.log")
|
|
||||||
|
|
||||||
start_time = 0
|
|
||||||
|
|
||||||
function get_time()
|
|
||||||
local seconds = math.floor((game:gettime() - start_time) / 1000)
|
|
||||||
local minutes = math.floor(seconds / 60)
|
|
||||||
time = string.format("%d:%02d", minutes, seconds - minutes * 60)
|
|
||||||
while (string.len(time) < 6) do
|
|
||||||
time = " " .. time
|
|
||||||
end
|
|
||||||
time = time .. " "
|
|
||||||
return time
|
|
||||||
end
|
|
||||||
|
|
||||||
function create_path(path)
|
|
||||||
local dir = path:gsub("%/", "\\"):match("(.*[\\])")
|
|
||||||
os.execute("if not exist " .. dir .. " mkdir " .. dir)
|
|
||||||
end
|
|
||||||
|
|
||||||
function log_print(message)
|
|
||||||
local path = game:getdvar("g_log")
|
|
||||||
local file = io.open(path, "a")
|
|
||||||
if (file == nil) then
|
|
||||||
create_path(path)
|
|
||||||
file = assert(io.open(path, "a"))
|
|
||||||
end
|
|
||||||
file:write(get_time() .. message .. "\n")
|
|
||||||
file:close()
|
|
||||||
end
|
|
||||||
|
|
||||||
function init()
|
|
||||||
start_time = game:gettime()
|
|
||||||
|
|
||||||
log_print("------------------------------------------------------------")
|
|
||||||
log_print("InitGame")
|
|
||||||
|
|
||||||
-- player callbacks
|
|
||||||
level:onnotify("connected", function(player)
|
|
||||||
player:player_connected()
|
|
||||||
end)
|
|
||||||
level:onnotify("say", function(player, message, hidden)
|
|
||||||
player:say(message)
|
|
||||||
end)
|
|
||||||
level:onnotify("say_team", function(player, message, hidden)
|
|
||||||
player:say(message, "say_team")
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- damage/killed hooks
|
|
||||||
game:onplayerdamage(player_damage)
|
|
||||||
game:onplayerkilled(player_killed)
|
|
||||||
|
|
||||||
-- other level notifies for log
|
|
||||||
level:onnotify("exitLevel_called", function()
|
|
||||||
log_print("ExitLevel: executed")
|
|
||||||
end)
|
|
||||||
level:onnotify("shutdownGame_called", function()
|
|
||||||
log_print("ShutdownGame:")
|
|
||||||
log_print("------------------------------------------------------------")
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function entity:player_connected()
|
|
||||||
log_print(string.format("J;%s;%i;%s", self:getguid(), self:getentitynumber(), self.name))
|
|
||||||
|
|
||||||
self:onnotifyonce("disconnect", function()
|
|
||||||
self:disconnect()
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function entity:disconnect()
|
|
||||||
log_print(string.format("Q;%s;%i;%s", self:getguid(), self:getentitynumber(), self.name))
|
|
||||||
end
|
|
||||||
|
|
||||||
function player_damage(self_, inflictor, attacker, damage, dflags, mod, weapon, vPoint, vDir, hitLoc)
|
|
||||||
if (game:isplayer(attacker) == 1) then
|
|
||||||
log_print(string.format("D;%s;%i;%s;%s;%s;%i;%s;%s;%s;%i;%s;%s", self_:getguid(), self_:getentitynumber(),
|
|
||||||
self_.team, self_.name, attacker:getguid(), attacker:getentitynumber(), attacker.team, attacker.name,
|
|
||||||
weapon, damage, mod, hitLoc))
|
|
||||||
else
|
|
||||||
log_print(string.format("D;%s;%i;%s;%s;%s;%i;%s;%s;%s;%i;%s;%s", self_:getguid(), self_:getentitynumber(),
|
|
||||||
self_.team, self_.name, "", "-1", "world", "", weapon, damage, mod, hitLoc))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function player_killed(self_, inflictor, attacker, damage, mod, weapon, vDir, hitLoc, psTimeOffset, deathAnimDuration)
|
|
||||||
if (game:isplayer(attacker) == 1) then
|
|
||||||
log_print(string.format("K;%s;%i;%s;%s;%s;%i;%s;%s;%s;%i;%s;%s", self_:getguid(), self_:getentitynumber(),
|
|
||||||
self_.team, self_.name, attacker:getguid(), attacker:getentitynumber(), attacker.team, attacker.name,
|
|
||||||
weapon, damage, mod, hitLoc))
|
|
||||||
else
|
|
||||||
log_print(string.format("K;%s;%i;%s;%s;%s;%i;%s;%s;%s;%i;%s;%s", self_:getguid(), self_:getentitynumber(),
|
|
||||||
self_.team, self_.name, "", "-1", "world", "", weapon, damage, mod, hitLoc))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- this function handles 'say' and 'say_team'
|
|
||||||
function entity:say(message, mode)
|
|
||||||
if (not mode) then
|
|
||||||
mode = "say"
|
|
||||||
end
|
|
||||||
|
|
||||||
log_print(string.format("%s;%s;%i;%s;%s", mode, self:getguid(), self:getentitynumber(), self.name, message))
|
|
||||||
end
|
|
||||||
|
|
||||||
init()
|
|
@ -1,23 +0,0 @@
|
|||||||
game:addlocalizedstring("CUSTOM_DEPOT_EULA_1", "Dear User,")
|
|
||||||
game:addlocalizedstring("CUSTOM_DEPOT_EULA_2",
|
|
||||||
"By using this feature, you acknowledge that you are over 18 years old, and that any sort of chance games / gambling are allowed in your country (even if they do not involve real money).")
|
|
||||||
game:addlocalizedstring("CUSTOM_DEPOT_EULA_3",
|
|
||||||
"The H1-Mod team is not responsible if you break the law within your country, and the sole responsibility will be upon you to respect the same.")
|
|
||||||
game:addlocalizedstring("CUSTOM_DEPOT_EULA_4",
|
|
||||||
"The H1-Mod team will never include real money transactions within the modified systems. The only way to get currency, should you wish to, is by playing the game.")
|
|
||||||
game:addlocalizedstring("CUSTOM_DEPOT_EULA_5", "Best Regards,")
|
|
||||||
game:addlocalizedstring("CUSTOM_DEPOT_EULA_6", "The H1-Mod Team.")
|
|
||||||
|
|
||||||
local mod_eula = function(unk1, unk2)
|
|
||||||
return LUI.EULABase.new(CoD.CreateState(0, 0, 0, 0, CoD.AnchorTypes.All), {
|
|
||||||
textStrings = LUI.EULABase.CreateTextStrings("@CUSTOM_DEPOT_EULA_", 6),
|
|
||||||
declineCallback = function(unk3)
|
|
||||||
unk2.declineCallback(unk3)
|
|
||||||
end,
|
|
||||||
acceptCallback = function(unk4)
|
|
||||||
unk2.acceptCallback(unk4)
|
|
||||||
end
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
LUI.MenuBuilder.registerPopupType("mod_eula", mod_eula)
|
|
@ -1 +0,0 @@
|
|||||||
-- this patch has been moved to ui_scripts/patches/gamemodes.lua
|
|
@ -1,3 +0,0 @@
|
|||||||
if (game:issingleplayer()) then
|
|
||||||
require("loading")
|
|
||||||
end
|
|
@ -1 +0,0 @@
|
|||||||
-- this patch has been moved to ui_scripts/patches/no_mode_switch.lua
|
|
2
data/zone_source/build.txt
Normal file
2
data/zone_source/build.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
eng_h1_mod_common
|
||||||
|
h1_mod_common
|
1
data/zone_source/eng_h1_mod_common.csv
Normal file
1
data/zone_source/eng_h1_mod_common.csv
Normal file
@ -0,0 +1 @@
|
|||||||
|
localize,english
|
|
1
data/zone_source/h1_mod_common.csv
Normal file
1
data/zone_source/h1_mod_common.csv
Normal file
@ -0,0 +1 @@
|
|||||||
|
localize,english
|
|
53
data/zonetool/localizedstrings/english.json
Normal file
53
data/zonetool/localizedstrings/english.json
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"CUSTOM_DEPOT_EULA_1": "Dear User,",
|
||||||
|
"CUSTOM_DEPOT_EULA_2": "By using this feature, you acknowledge that you are over the age of 18 years old, and that any sort of gambling is allowed in your country. (even if they do not involve real money)",
|
||||||
|
"CUSTOM_DEPOT_EULA_3": "The H1-mod team is not responsible if you break any law within your country, and the sole responsibility will be upon you to respect the same.",
|
||||||
|
"CUSTOM_DEPOT_EULA_4": "The H1-mod team will never include real money transactions within the modified systems. The only way to get currency, should you wish to, is by playing the game.",
|
||||||
|
"CUSTOM_DEPOT_EULA_5": "Best regards,",
|
||||||
|
"CUSTOM_DEPOT_EULA_6": "The H1-mod team.",
|
||||||
|
|
||||||
|
"LUA_MENU_FPS": "FPS Counter",
|
||||||
|
"LUA_MENU_FPS_DESC": "Show FPS counter.",
|
||||||
|
"LUA_MENU_LATENCY": "Server Latency",
|
||||||
|
"LUA_MENU_LATENCY_DESC": "Show server latency.",
|
||||||
|
"LUA_MENU_RED_DOT_BRIGHTNESS": "Red Dot Brightness",
|
||||||
|
"LUA_MENU_RED_DOT_BRIGHTNESS_DESC": "Adjust the brightness of red dot reticles.",
|
||||||
|
|
||||||
|
"MENU_SYSINFO_CUSTOMER_SUPPORT_URL": "https://h1.gg/",
|
||||||
|
|
||||||
|
"MENU_MODS": "MODS",
|
||||||
|
"MENU_MODS_DESC": "Load installed mods.",
|
||||||
|
"LUA_MENU_MOD_DESC_DEFAULT": "Load &&1.",
|
||||||
|
"LUA_MENU_MOD_DESC": "&&1\nAuthor: &&2\nVersion: &&3",
|
||||||
|
"LUA_MENU_LOADED_MOD": "Loaded mod: ^2&&1",
|
||||||
|
"LUA_MENU_AVAILABLE_MODS": "Available mods",
|
||||||
|
"LUA_MENU_UNLOAD": "Unload",
|
||||||
|
"LUA_MENU_UNLOAD_DESC": "Unload the currently loaded mod.",
|
||||||
|
|
||||||
|
"PLATFORM_SHADER_PRECACHE_ASK": "Would you like to populate the shader cache? It may cause crashes with certain GPUs (e.g. RTX cards) but will improve performance if successful.",
|
||||||
|
"MENU_NO_DONT_ASK": "No, don't ask me again",
|
||||||
|
|
||||||
|
"LUA_MENU_SERVERLIST": "SERVER LIST",
|
||||||
|
"MENU_NUMPLAYERS": "Players",
|
||||||
|
"MENU_PING": "Ping",
|
||||||
|
"SERVERLIST_PLAYER_COUNT": "&&1 Players",
|
||||||
|
"SERVERLIST_SERVER_COUNT": "&&1 Servers",
|
||||||
|
|
||||||
|
"LUA_MENU_STATS": "Stats",
|
||||||
|
"LUA_MENU_STATS_DESC": "Edit player stats settings.",
|
||||||
|
"LUA_MENU_UNLOCKALL_ITEMS": "Unlock all items",
|
||||||
|
"LUA_MENU_UNLOCKALL_ITEMS_DESC": "Whether items should be locked based on the player's stats or always unlocked.",
|
||||||
|
"LUA_MENU_UNLOCKALL_LOOT": "Unlock all loot",
|
||||||
|
"LUA_MENU_UNLOCKALL_LOOT_DESC": "Whether loot should be locked based on the player's stats or always unlocked.",
|
||||||
|
"LUA_MENU_UNLOCKALL_CLASSES": "Unlock all classes",
|
||||||
|
"LUA_MENU_UNLOCKALL_CLASSES_DESC": "Whether classes should be locked based on the player's stats or always unlocked.",
|
||||||
|
"LUA_MENU_PRESTIGE": "Prestige",
|
||||||
|
"LUA_MENU_PRESTIGE_DESC": "Edit prestige level.",
|
||||||
|
"LUA_MENU_RANK": "Rank",
|
||||||
|
"LUA_MENU_RANK_DESC": "Edit rank.",
|
||||||
|
"LUA_MENU_UNSAVED_CHANGES": "You have unsaved changes: are you sure you want to exit?",
|
||||||
|
"LUA_MENU_SAVE": "Save changes",
|
||||||
|
"LUA_MENU_SAVE_DESC": "Save changes.",
|
||||||
|
"LUA_MENU_SETTINGS": "Settings",
|
||||||
|
"LUA_MENU_EDIT_STATS": "Edit Stats"
|
||||||
|
}
|
@ -9,6 +9,7 @@
|
|||||||
#include "console.hpp"
|
#include "console.hpp"
|
||||||
#include "game_console.hpp"
|
#include "game_console.hpp"
|
||||||
#include "fastfiles.hpp"
|
#include "fastfiles.hpp"
|
||||||
|
#include "filesystem.hpp"
|
||||||
#include "scheduler.hpp"
|
#include "scheduler.hpp"
|
||||||
#include "logfile.hpp"
|
#include "logfile.hpp"
|
||||||
#include "dvars.hpp"
|
#include "dvars.hpp"
|
||||||
@ -27,6 +28,8 @@ namespace command
|
|||||||
std::unordered_map<std::string, std::function<void(params&)>> handlers;
|
std::unordered_map<std::string, std::function<void(params&)>> handlers;
|
||||||
std::unordered_map<std::string, std::function<void(int, params_sv&)>> handlers_sv;
|
std::unordered_map<std::string, std::function<void(int, params_sv&)>> handlers_sv;
|
||||||
|
|
||||||
|
std::string saved_fs_game;
|
||||||
|
|
||||||
void main_handler()
|
void main_handler()
|
||||||
{
|
{
|
||||||
params params = {};
|
params params = {};
|
||||||
@ -127,7 +130,7 @@ namespace command
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dvars::on_register(dvar_name, [dvar_name, value]()
|
dvars::callback::on_register(dvar_name, [dvar_name, value]()
|
||||||
{
|
{
|
||||||
game::Dvar_SetCommand(game::generateHashValue(dvar_name.data()), "", value.data());
|
game::Dvar_SetCommand(game::generateHashValue(dvar_name.data()), "", value.data());
|
||||||
});
|
});
|
||||||
@ -554,11 +557,43 @@ namespace command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void register_fs_game_path()
|
||||||
|
{
|
||||||
|
const auto* fs_game = game::Dvar_FindVar("fs_game");
|
||||||
|
const auto new_mod_path = fs_game->current.string;
|
||||||
|
|
||||||
|
// check if the last saved fs_game value isn't empty and if it doesn't equal the new fs_game
|
||||||
|
if (!saved_fs_game.empty() && saved_fs_game != new_mod_path)
|
||||||
|
{
|
||||||
|
// unregister path to be used as a fs directory
|
||||||
|
filesystem::unregister_path(saved_fs_game);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_mod_path && !new_mod_path[0])
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// register fs_game value as a fs directory used for many things
|
||||||
|
filesystem::register_path(new_mod_path);
|
||||||
|
saved_fs_game = new_mod_path;
|
||||||
|
}
|
||||||
|
|
||||||
class component final : public component_interface
|
class component final : public component_interface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void post_unpack() override
|
void post_unpack() override
|
||||||
{
|
{
|
||||||
|
// it might be overdone to change the filesystem path on every new value change, but to be fair,
|
||||||
|
// for the mods that don't need full restarts, this is good because it'll adjust and work like so
|
||||||
|
// in my opinion, this is fine. if a user tries to modify the dvar themselves, they'll have problems
|
||||||
|
// but i seriously doubt it'll be bad.
|
||||||
|
dvars::callback::on_new_value("fs_game", []()
|
||||||
|
{
|
||||||
|
console::warn("fs_game value changed, filesystem paths will be adjusted to new dvar value.");
|
||||||
|
register_fs_game_path();
|
||||||
|
});
|
||||||
|
|
||||||
if (game::environment::is_sp())
|
if (game::environment::is_sp())
|
||||||
{
|
{
|
||||||
add_commands_sp();
|
add_commands_sp();
|
||||||
|
@ -49,4 +49,6 @@ namespace command
|
|||||||
void add_sv(const char* name, std::function<void(int, const params_sv&)> callback);
|
void add_sv(const char* name, std::function<void(int, const params_sv&)> callback);
|
||||||
|
|
||||||
void execute(std::string command, bool sync = false);
|
void execute(std::string command, bool sync = false);
|
||||||
|
|
||||||
|
void register_fs_game_path();
|
||||||
}
|
}
|
@ -252,6 +252,23 @@ namespace dvars
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace callback
|
||||||
|
{
|
||||||
|
static std::unordered_map<int, std::function<void()>> new_value_callbacks;
|
||||||
|
|
||||||
|
static std::unordered_map<int, std::function<void()>> dvar_on_register_function_map;
|
||||||
|
|
||||||
|
void on_new_value(const std::string& name, const std::function<void()> callback)
|
||||||
|
{
|
||||||
|
new_value_callbacks[game::generateHashValue(name.data())] = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_register(const std::string& name, const std::function<void()>& callback)
|
||||||
|
{
|
||||||
|
dvar_on_register_function_map[game::generateHashValue(name.data())] = callback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
utils::hook::detour dvar_register_bool_hook;
|
utils::hook::detour dvar_register_bool_hook;
|
||||||
utils::hook::detour dvar_register_bool_hashed_hook;
|
utils::hook::detour dvar_register_bool_hashed_hook;
|
||||||
utils::hook::detour dvar_register_float_hook;
|
utils::hook::detour dvar_register_float_hook;
|
||||||
@ -271,6 +288,8 @@ namespace dvars
|
|||||||
utils::hook::detour dvar_set_string_hook;
|
utils::hook::detour dvar_set_string_hook;
|
||||||
utils::hook::detour dvar_set_from_string_hook;
|
utils::hook::detour dvar_set_from_string_hook;
|
||||||
|
|
||||||
|
utils::hook::detour dvar_set_variant_hook;
|
||||||
|
|
||||||
game::dvar_t* dvar_register_bool(const int hash, const char* name, bool value, unsigned int flags)
|
game::dvar_t* dvar_register_bool(const int hash, const char* name, bool value, unsigned int flags)
|
||||||
{
|
{
|
||||||
auto* var = find_dvar(override::register_bool_overrides, hash);
|
auto* var = find_dvar(override::register_bool_overrides, hash);
|
||||||
@ -409,21 +428,15 @@ namespace dvars
|
|||||||
return dvar_register_enum_hook.invoke<game::dvar_t*>(hash, name, value_list, default_index, flags);
|
return dvar_register_enum_hook.invoke<game::dvar_t*>(hash, name, value_list, default_index, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unordered_map<int, std::function<void()>> dvar_on_register_function_map;
|
|
||||||
void on_register(const std::string& name, const std::function<void()>& callback)
|
|
||||||
{
|
|
||||||
dvar_on_register_function_map[game::generateHashValue(name.data())] = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
game::dvar_t* dvar_register_new(const int hash, const char* name, game::dvar_type type, unsigned int flags,
|
game::dvar_t* dvar_register_new(const int hash, const char* name, game::dvar_type type, unsigned int flags,
|
||||||
game::dvar_value* value, game::dvar_limits* domain, const char* description)
|
game::dvar_value* value, game::dvar_limits* domain, const char* description)
|
||||||
{
|
{
|
||||||
auto* dvar = dvar_register_new_hook.invoke<game::dvar_t*>(hash, name, type, flags, value, domain, description);
|
auto* dvar = dvar_register_new_hook.invoke<game::dvar_t*>(hash, name, type, flags, value, domain, description);
|
||||||
|
|
||||||
if (dvar && dvar_on_register_function_map.find(hash) != dvar_on_register_function_map.end())
|
if (dvar && callback::dvar_on_register_function_map.find(hash) != callback::dvar_on_register_function_map.end())
|
||||||
{
|
{
|
||||||
dvar_on_register_function_map[hash]();
|
callback::dvar_on_register_function_map[hash]();
|
||||||
dvar_on_register_function_map.erase(hash);
|
callback::dvar_on_register_function_map.erase(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
return dvar;
|
return dvar;
|
||||||
@ -514,6 +527,16 @@ namespace dvars
|
|||||||
return dvar_set_from_string_hook.invoke<void>(dvar, string, source);
|
return dvar_set_from_string_hook.invoke<void>(dvar, string, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void dvar_set_variant(game::dvar_t* dvar, game::dvar_value* value, game::DvarSetSource source)
|
||||||
|
{
|
||||||
|
dvar_set_variant_hook.invoke<void>(dvar, value, source);
|
||||||
|
|
||||||
|
if (callback::new_value_callbacks.find(dvar->hash) != callback::new_value_callbacks.end())
|
||||||
|
{
|
||||||
|
callback::new_value_callbacks[dvar->hash]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class component final : public component_interface
|
class component final : public component_interface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -541,6 +564,8 @@ namespace dvars
|
|||||||
dvar_set_int_hook.create(SELECT_VALUE(0x41BEE0_b, 0x185D10_b), &dvar_set_int);
|
dvar_set_int_hook.create(SELECT_VALUE(0x41BEE0_b, 0x185D10_b), &dvar_set_int);
|
||||||
dvar_set_string_hook.create(SELECT_VALUE(0x41C0F0_b, 0x186080_b), &dvar_set_string);
|
dvar_set_string_hook.create(SELECT_VALUE(0x41C0F0_b, 0x186080_b), &dvar_set_string);
|
||||||
dvar_set_from_string_hook.create(SELECT_VALUE(0x41BE20_b, 0x185C60_b), &dvar_set_from_string);
|
dvar_set_from_string_hook.create(SELECT_VALUE(0x41BE20_b, 0x185C60_b), &dvar_set_from_string);
|
||||||
|
|
||||||
|
dvar_set_variant_hook.create(SELECT_VALUE(0x41C190_b, 0x186120_b), &dvar_set_variant);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -27,5 +27,10 @@ namespace dvars
|
|||||||
void set_from_string(const std::string& name, const std::string& value);
|
void set_from_string(const std::string& name, const std::string& value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_register(const std::string& name, const std::function<void()>& callback);
|
namespace callback
|
||||||
|
{
|
||||||
|
void on_new_value(const std::string& name, const std::function<void()> callback);
|
||||||
|
|
||||||
|
void on_register(const std::string& name, const std::function<void()>& callback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include "fastfiles.hpp"
|
#include "fastfiles.hpp"
|
||||||
#include "command.hpp"
|
#include "command.hpp"
|
||||||
#include "console.hpp"
|
#include "console.hpp"
|
||||||
|
#include "filesystem.hpp"
|
||||||
|
|
||||||
#include <utils/hook.hpp>
|
#include <utils/hook.hpp>
|
||||||
#include <utils/concurrency.hpp>
|
#include <utils/concurrency.hpp>
|
||||||
@ -143,32 +144,94 @@ namespace fastfiles
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool try_load_zone(std::string name, bool localized, bool game = false)
|
||||||
|
{
|
||||||
|
if (localized)
|
||||||
|
{
|
||||||
|
const auto language = game::SEH_GetCurrentLanguageCode();
|
||||||
|
try_load_zone(language + "_"s + name, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fastfiles::exists(name))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
game::XZoneInfo info{};
|
||||||
|
info.name = name.data();
|
||||||
|
info.allocFlags = (game ? game::DB_ZONE_GAME : game::DB_ZONE_COMMON) | game::DB_ZONE_CUSTOM;
|
||||||
|
info.freeFlags = 0;
|
||||||
|
game::DB_LoadXAssets(&info, 1u, game::DBSyncMode::DB_LOAD_ASYNC);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
HANDLE find_fastfile(const std::string& filename, bool check_loc_folder)
|
||||||
|
{
|
||||||
|
std::string path{};
|
||||||
|
std::string loc_folder{};
|
||||||
|
|
||||||
|
if (check_loc_folder && game::DB_IsLocalized(filename.data()))
|
||||||
|
{
|
||||||
|
const auto handle = find_fastfile(filename, false);
|
||||||
|
if (handle != reinterpret_cast<HANDLE>(-1))
|
||||||
|
{
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
loc_folder = game::SEH_GetCurrentLanguageName() + "/"s;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!filesystem::find_file(loc_folder + filename, &path))
|
||||||
|
{
|
||||||
|
if (!filesystem::find_file("zone/"s + loc_folder + filename, &path))
|
||||||
|
{
|
||||||
|
return reinterpret_cast<HANDLE>(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateFileA(path.data(), 0x80000000, 1u, 0, 3u, 0x60000000u, 0);
|
||||||
|
}
|
||||||
|
|
||||||
utils::hook::detour sys_createfile_hook;
|
utils::hook::detour sys_createfile_hook;
|
||||||
HANDLE sys_create_file_stub(game::Sys_Folder folder, const char* base_filename)
|
HANDLE sys_create_file_stub(game::Sys_Folder folder, const char* base_filename)
|
||||||
{
|
{
|
||||||
auto* fs_basepath = game::Dvar_FindVar("fs_basepath");
|
const auto* fs_basepath = game::Dvar_FindVar("fs_basepath");
|
||||||
auto* fs_game = game::Dvar_FindVar("fs_game");
|
const auto* fs_game = game::Dvar_FindVar("fs_game");
|
||||||
|
|
||||||
std::string dir = fs_basepath ? fs_basepath->current.string : "";
|
const std::string dir = fs_basepath ? fs_basepath->current.string : "";
|
||||||
std::string mod_dir = fs_game ? fs_game->current.string : "";
|
const std::string mod_dir = fs_game ? fs_game->current.string : "";
|
||||||
|
const std::string name = base_filename;
|
||||||
|
|
||||||
if (base_filename == "mod.ff"s)
|
if (name == "mod.ff")
|
||||||
{
|
{
|
||||||
if (!mod_dir.empty())
|
if (!mod_dir.empty())
|
||||||
{
|
{
|
||||||
auto path = utils::string::va("%s\\%s\\%s", dir.data(), mod_dir.data(), base_filename);
|
const auto path = utils::string::va("%s\\%s\\%s",
|
||||||
|
dir.data(), mod_dir.data(), base_filename);
|
||||||
|
|
||||||
if (utils::io::file_exists(path))
|
if (utils::io::file_exists(path))
|
||||||
{
|
{
|
||||||
return CreateFileA(path, 0x80000000, 1u, 0, 3u, 0x60000000u, 0);
|
return CreateFileA(path, 0x80000000, 1u, 0, 3u, 0x60000000u, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (HANDLE)-1;
|
|
||||||
|
return reinterpret_cast<HANDLE>(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.ends_with(".ff"))
|
||||||
|
{
|
||||||
|
const auto handle = find_fastfile(name, true);
|
||||||
|
if (handle != reinterpret_cast<HANDLE>(-1))
|
||||||
|
{
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sys_createfile_hook.invoke<HANDLE>(folder, base_filename);
|
return sys_createfile_hook.invoke<HANDLE>(folder, base_filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> inline void merge(std::vector<T>* target, T* source, size_t length)
|
template <typename T>
|
||||||
|
inline void merge(std::vector<T>* target, T* source, size_t length)
|
||||||
{
|
{
|
||||||
if (source)
|
if (source)
|
||||||
{
|
{
|
||||||
@ -179,7 +242,8 @@ namespace fastfiles
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> inline void merge(std::vector<T>* target, std::vector<T> source)
|
template <typename T>
|
||||||
|
inline void merge(std::vector<T>* target, std::vector<T> source)
|
||||||
{
|
{
|
||||||
for (auto& entry : source)
|
for (auto& entry : source)
|
||||||
{
|
{
|
||||||
@ -206,12 +270,11 @@ namespace fastfiles
|
|||||||
// ui
|
// ui
|
||||||
// common
|
// common
|
||||||
|
|
||||||
if (fastfiles::exists("mod"))
|
try_load_zone("h1_mod_common", true);
|
||||||
{
|
|
||||||
data.push_back({ "mod", game::DB_ZONE_COMMON | game::DB_ZONE_CUSTOM, 0 });
|
|
||||||
}
|
|
||||||
|
|
||||||
game::DB_LoadXAssets(data.data(), static_cast<std::uint32_t>(data.size()), syncMode);
|
game::DB_LoadXAssets(data.data(), static_cast<std::uint32_t>(data.size()), syncMode);
|
||||||
|
|
||||||
|
try_load_zone("mod", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void load_ui_zones(game::XZoneInfo* zoneInfo, unsigned int zoneCount, game::DBSyncMode syncMode)
|
void load_ui_zones(game::XZoneInfo* zoneInfo, unsigned int zoneCount, game::DBSyncMode syncMode)
|
||||||
@ -227,13 +290,16 @@ namespace fastfiles
|
|||||||
|
|
||||||
bool exists(const std::string& zone)
|
bool exists(const std::string& zone)
|
||||||
{
|
{
|
||||||
auto is_localized = game::DB_IsLocalized(zone.data());
|
const auto is_localized = game::DB_IsLocalized(zone.data());
|
||||||
auto handle = game::Sys_CreateFile((is_localized ? game::SF_ZONE_LOC : game::SF_ZONE), utils::string::va("%s.ff", zone.data()));
|
const auto handle = game::Sys_CreateFile((is_localized ? game::SF_ZONE_LOC : game::SF_ZONE),
|
||||||
if (handle != (HANDLE)-1)
|
utils::string::va("%s.ff", zone.data()));
|
||||||
|
|
||||||
|
if (handle != reinterpret_cast<HANDLE>(-1))
|
||||||
{
|
{
|
||||||
CloseHandle(handle);
|
CloseHandle(handle);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,7 +328,6 @@ namespace fastfiles
|
|||||||
{
|
{
|
||||||
db_try_load_x_file_internal_hook.create(
|
db_try_load_x_file_internal_hook.create(
|
||||||
SELECT_VALUE(0x1F5700_b, 0x39A620_b), &db_try_load_x_file_internal);
|
SELECT_VALUE(0x1F5700_b, 0x39A620_b), &db_try_load_x_file_internal);
|
||||||
|
|
||||||
db_find_xasset_header_hook.create(game::DB_FindXAssetHeader, db_find_xasset_header_stub);
|
db_find_xasset_header_hook.create(game::DB_FindXAssetHeader, db_find_xasset_header_stub);
|
||||||
|
|
||||||
g_dump_scripts = dvars::register_bool("g_dumpScripts", false, game::DVAR_FLAG_NONE, "Dump GSC scripts");
|
g_dump_scripts = dvars::register_bool("g_dumpScripts", false, game::DVAR_FLAG_NONE, "Dump GSC scripts");
|
||||||
@ -312,22 +377,11 @@ namespace fastfiles
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* name = params.get(1);
|
const auto name = params.get(1);
|
||||||
|
if (!try_load_zone(name, false))
|
||||||
if (!fastfiles::exists(name))
|
|
||||||
{
|
{
|
||||||
console::warn("loadzone: zone \"%s\" could not be found!\n", name);
|
console::warn("loadzone: zone \"%s\" could not be found!\n", name);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
game::XZoneInfo info;
|
|
||||||
info.name = name;
|
|
||||||
info.allocFlags = game::DB_ZONE_GAME;
|
|
||||||
info.freeFlags = 0;
|
|
||||||
|
|
||||||
info.allocFlags |= game::DB_ZONE_CUSTOM; // skip extra zones with this flag!
|
|
||||||
|
|
||||||
game::DB_LoadXAssets(&info, 1, game::DBSyncMode::DB_LOAD_ASYNC);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,72 +1,148 @@
|
|||||||
#include <std_include.hpp>
|
#include <std_include.hpp>
|
||||||
#include "loader/component_loader.hpp"
|
#include "loader/component_loader.hpp"
|
||||||
|
|
||||||
|
#include "command.hpp"
|
||||||
|
#include "console.hpp"
|
||||||
#include "filesystem.hpp"
|
#include "filesystem.hpp"
|
||||||
#include "game_module.hpp"
|
#include "localized_strings.hpp"
|
||||||
|
#include "updater.hpp"
|
||||||
|
|
||||||
#include "game/game.hpp"
|
#include "game/game.hpp"
|
||||||
#include "dvars.hpp"
|
|
||||||
|
|
||||||
#include <utils/hook.hpp>
|
|
||||||
#include <utils/string.hpp>
|
|
||||||
#include <utils/io.hpp>
|
#include <utils/io.hpp>
|
||||||
|
#include <utils/flags.hpp>
|
||||||
|
#include <utils/hook.hpp>
|
||||||
|
#include <utils/properties.hpp>
|
||||||
|
|
||||||
namespace filesystem
|
namespace filesystem
|
||||||
{
|
{
|
||||||
file::file(std::string name)
|
namespace
|
||||||
: name_(std::move(name))
|
|
||||||
{
|
{
|
||||||
char* buffer{};
|
utils::hook::detour fs_startup_hook;
|
||||||
const auto size = game::FS_ReadFile(this->name_.data(), &buffer);
|
|
||||||
|
|
||||||
if (size >= 0 && buffer)
|
bool initialized = false;
|
||||||
|
|
||||||
|
std::deque<std::filesystem::path>& get_search_paths_internal()
|
||||||
{
|
{
|
||||||
this->valid_ = true;
|
static std::deque<std::filesystem::path> search_paths{};
|
||||||
this->buffer_.append(buffer, size);
|
return search_paths;
|
||||||
game::FS_FreeFile(buffer);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
bool file::exists() const
|
bool is_fallback_lang()
|
||||||
{
|
{
|
||||||
return this->valid_;
|
static const auto* loc_language = game::Dvar_FindVar("loc_language");
|
||||||
}
|
const auto id = loc_language->current.integer;
|
||||||
|
return id == 5 || id == 6 || id == 8 || id == 9 || id == 10 || id == 11 || id == 12 || id == 13 || id == 15;
|
||||||
|
}
|
||||||
|
|
||||||
const std::string& file::get_buffer() const
|
void fs_startup_stub(const char* name)
|
||||||
{
|
{
|
||||||
return this->buffer_;
|
console::debug("[FS] Startup\n");
|
||||||
}
|
|
||||||
|
|
||||||
const std::string& file::get_name() const
|
initialized = true;
|
||||||
{
|
|
||||||
return this->name_;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unordered_set<std::string>& get_search_paths()
|
// hardcoded paths
|
||||||
{
|
filesystem::register_path(utils::properties::get_appdata_path() / CLIENT_DATA_FOLDER);
|
||||||
static std::unordered_set<std::string> search_paths{};
|
filesystem::register_path(L".");
|
||||||
return search_paths;
|
filesystem::register_path(L"h1-mod");
|
||||||
|
|
||||||
|
fs_startup_hook.invoke<void>(name);
|
||||||
|
|
||||||
|
command::register_fs_game_path();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::filesystem::path> get_paths(const std::filesystem::path& path)
|
||||||
|
{
|
||||||
|
std::vector<std::filesystem::path> paths{};
|
||||||
|
|
||||||
|
const auto code = game::SEH_GetCurrentLanguageName();
|
||||||
|
|
||||||
|
paths.push_back(path);
|
||||||
|
|
||||||
|
if (is_fallback_lang())
|
||||||
|
{
|
||||||
|
paths.push_back(path / "fallback");
|
||||||
|
}
|
||||||
|
|
||||||
|
paths.push_back(path / code);
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool can_insert_path(const std::filesystem::path& path)
|
||||||
|
{
|
||||||
|
for (const auto& path_ : get_search_paths_internal())
|
||||||
|
{
|
||||||
|
if (path_ == path)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* sys_default_install_path_stub()
|
||||||
|
{
|
||||||
|
static auto current_path = std::filesystem::current_path().string();
|
||||||
|
return current_path.data();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string read_file(const std::string& path)
|
std::string read_file(const std::string& path)
|
||||||
{
|
{
|
||||||
for (const auto& search_path : get_search_paths())
|
for (const auto& search_path : get_search_paths_internal())
|
||||||
{
|
{
|
||||||
const auto path_ = search_path + "/" + path;
|
const auto path_ = search_path / path;
|
||||||
if (utils::io::file_exists(path_))
|
if (utils::io::file_exists(path_.generic_string()))
|
||||||
{
|
{
|
||||||
return utils::io::read_file(path_);
|
return utils::io::read_file(path_.generic_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool read_file(const std::string& path, std::string* data)
|
bool read_file(const std::string& path, std::string* data, std::string* real_path)
|
||||||
{
|
{
|
||||||
for (const auto& search_path : get_search_paths())
|
for (const auto& search_path : get_search_paths_internal())
|
||||||
{
|
{
|
||||||
const auto path_ = search_path + "/" + path;
|
const auto path_ = search_path / path;
|
||||||
if (utils::io::read_file(path_, data))
|
if (utils::io::read_file(path_.generic_string(), data))
|
||||||
|
{
|
||||||
|
if (real_path != nullptr)
|
||||||
|
{
|
||||||
|
*real_path = path_.generic_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool find_file(const std::string& path, std::string* real_path)
|
||||||
|
{
|
||||||
|
for (const auto& search_path : get_search_paths_internal())
|
||||||
|
{
|
||||||
|
const auto path_ = search_path / path;
|
||||||
|
if (utils::io::file_exists(path_.generic_string()))
|
||||||
|
{
|
||||||
|
*real_path = path_.generic_string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool exists(const std::string& path)
|
||||||
|
{
|
||||||
|
for (const auto& search_path : get_search_paths_internal())
|
||||||
|
{
|
||||||
|
const auto path_ = search_path / path;
|
||||||
|
if (utils::io::file_exists(path_.generic_string()))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -75,14 +151,83 @@ namespace filesystem
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void register_path(const std::filesystem::path& path)
|
||||||
|
{
|
||||||
|
if (!initialized)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto paths = get_paths(path);
|
||||||
|
for (const auto& path_ : paths)
|
||||||
|
{
|
||||||
|
if (can_insert_path(path_))
|
||||||
|
{
|
||||||
|
console::debug("[FS] Registering path '%s'\n", path_.generic_string().data());
|
||||||
|
get_search_paths_internal().push_front(path_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void unregister_path(const std::filesystem::path& path)
|
||||||
|
{
|
||||||
|
if (!initialized)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto paths = get_paths(path);
|
||||||
|
for (const auto& path_ : paths)
|
||||||
|
{
|
||||||
|
auto& search_paths = get_search_paths_internal();
|
||||||
|
for (auto i = search_paths.begin(); i != search_paths.end();)
|
||||||
|
{
|
||||||
|
if (*i == path_)
|
||||||
|
{
|
||||||
|
console::debug("[FS] Unregistering path '%s'\n", path_.generic_string().data());
|
||||||
|
i = search_paths.erase(i);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> get_search_paths()
|
||||||
|
{
|
||||||
|
std::vector<std::string> paths{};
|
||||||
|
|
||||||
|
for (const auto& path : get_search_paths_internal())
|
||||||
|
{
|
||||||
|
paths.push_back(path.generic_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> get_search_paths_rev()
|
||||||
|
{
|
||||||
|
std::vector<std::string> paths{};
|
||||||
|
const auto& search_paths = get_search_paths_internal();
|
||||||
|
|
||||||
|
for (auto i = search_paths.rbegin(); i != search_paths.rend(); ++i)
|
||||||
|
{
|
||||||
|
paths.push_back(i->generic_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
class component final : public component_interface
|
class component final : public component_interface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void post_unpack() override
|
void post_unpack() override
|
||||||
{
|
{
|
||||||
get_search_paths().insert(".");
|
fs_startup_hook.create(SELECT_VALUE(0x40D890_b, 0x189A40_b), fs_startup_stub);
|
||||||
get_search_paths().insert("h1-mod");
|
|
||||||
get_search_paths().insert("data");
|
utils::hook::jump(SELECT_VALUE(0x42CE00_b, 0x5B3440_b), sys_default_install_path_stub);
|
||||||
|
|
||||||
// fs_game flags
|
// fs_game flags
|
||||||
utils::hook::set<uint32_t>(SELECT_VALUE(0x40D2A5_b, 0x189275_b), 0);
|
utils::hook::set<uint32_t>(SELECT_VALUE(0x40D2A5_b, 0x189275_b), 0);
|
||||||
@ -90,4 +235,4 @@ namespace filesystem
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
REGISTER_COMPONENT(filesystem::component)
|
REGISTER_COMPONENT(filesystem::component)
|
||||||
|
@ -2,22 +2,14 @@
|
|||||||
|
|
||||||
namespace filesystem
|
namespace filesystem
|
||||||
{
|
{
|
||||||
class file
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
file(std::string name);
|
|
||||||
|
|
||||||
bool exists() const;
|
|
||||||
const std::string& get_buffer() const;
|
|
||||||
const std::string& get_name() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool valid_ = false;
|
|
||||||
std::string name_;
|
|
||||||
std::string buffer_;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unordered_set<std::string>& get_search_paths();
|
|
||||||
std::string read_file(const std::string& path);
|
std::string read_file(const std::string& path);
|
||||||
bool read_file(const std::string& path, std::string* data);
|
bool read_file(const std::string& path, std::string* data, std::string* real_path = nullptr);
|
||||||
}
|
bool find_file(const std::string& path, std::string* real_path);
|
||||||
|
bool exists(const std::string& path);
|
||||||
|
|
||||||
|
void register_path(const std::filesystem::path& path);
|
||||||
|
void unregister_path(const std::filesystem::path& path);
|
||||||
|
|
||||||
|
std::vector<std::string> get_search_paths();
|
||||||
|
std::vector<std::string> get_search_paths_rev();
|
||||||
|
}
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
#include <std_include.hpp>
|
#include <std_include.hpp>
|
||||||
#include "loader/component_loader.hpp"
|
#include "loader/component_loader.hpp"
|
||||||
|
|
||||||
|
#include "console.hpp"
|
||||||
|
#include "filesystem.hpp"
|
||||||
#include "localized_strings.hpp"
|
#include "localized_strings.hpp"
|
||||||
|
|
||||||
|
#include "game/game.hpp"
|
||||||
|
|
||||||
#include <utils/hook.hpp>
|
#include <utils/hook.hpp>
|
||||||
#include <utils/string.hpp>
|
#include <utils/string.hpp>
|
||||||
#include <utils/concurrency.hpp>
|
#include <utils/concurrency.hpp>
|
||||||
#include "game/game.hpp"
|
#include <utils/io.hpp>
|
||||||
|
|
||||||
namespace localized_strings
|
namespace localized_strings
|
||||||
{
|
{
|
||||||
|
@ -2,15 +2,15 @@
|
|||||||
#include "loader/component_loader.hpp"
|
#include "loader/component_loader.hpp"
|
||||||
|
|
||||||
#include "game/game.hpp"
|
#include "game/game.hpp"
|
||||||
#include "game/dvars.hpp"
|
|
||||||
|
|
||||||
#include "command.hpp"
|
#include "command.hpp"
|
||||||
#include "console.hpp"
|
#include "console.hpp"
|
||||||
#include "scheduler.hpp"
|
|
||||||
#include "filesystem.hpp"
|
#include "filesystem.hpp"
|
||||||
#include "materials.hpp"
|
|
||||||
#include "fonts.hpp"
|
#include "fonts.hpp"
|
||||||
|
#include "localized_strings.hpp"
|
||||||
|
#include "materials.hpp"
|
||||||
#include "mods.hpp"
|
#include "mods.hpp"
|
||||||
|
#include "scheduler.hpp"
|
||||||
|
|
||||||
#include <utils/hook.hpp>
|
#include <utils/hook.hpp>
|
||||||
#include <utils/io.hpp>
|
#include <utils/io.hpp>
|
||||||
@ -40,10 +40,27 @@ namespace mods
|
|||||||
scheduler::once([]()
|
scheduler::once([]()
|
||||||
{
|
{
|
||||||
release_assets = true;
|
release_assets = true;
|
||||||
|
const auto _0 = gsl::finally([]()
|
||||||
|
{
|
||||||
|
release_assets = false;
|
||||||
|
});
|
||||||
|
|
||||||
game::Com_Shutdown("");
|
game::Com_Shutdown("");
|
||||||
release_assets = false;
|
|
||||||
}, scheduler::pipeline::main);
|
}, scheduler::pipeline::main);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void full_restart(const std::string& arg)
|
||||||
|
{
|
||||||
|
auto mode = game::environment::is_mp() ? " -multiplayer "s : " -singleplayer "s;
|
||||||
|
|
||||||
|
utils::nt::relaunch_self(mode.append(arg), true);
|
||||||
|
utils::nt::terminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mod_requires_restart(const std::string& path)
|
||||||
|
{
|
||||||
|
return utils::io::file_exists(path + "/mod.ff") || utils::io::file_exists(path + "/zone/mod.ff");
|
||||||
}
|
}
|
||||||
|
|
||||||
class component final : public component_interface
|
class component final : public component_interface
|
||||||
@ -51,11 +68,6 @@ namespace mods
|
|||||||
public:
|
public:
|
||||||
void post_unpack() override
|
void post_unpack() override
|
||||||
{
|
{
|
||||||
if (!game::environment::is_sp())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!utils::io::directory_exists("mods"))
|
if (!utils::io::directory_exists("mods"))
|
||||||
{
|
{
|
||||||
utils::io::create_directory("mods");
|
utils::io::create_directory("mods");
|
||||||
@ -71,10 +83,10 @@ namespace mods
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!game::Com_InFrontend())
|
if (!game::Com_InFrontend() && (game::environment::is_mp() && !game::VirtualLobby_Loaded()))
|
||||||
{
|
{
|
||||||
console::info("Cannot load mod while in-game!\n");
|
console::info("Cannot load mod while in-game!\n");
|
||||||
game::CG_GameMessage(0, "^1Cannot unload mod while in-game!");
|
game::CG_GameMessage(0, "^1Cannot load mod while in-game!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,10 +98,19 @@ namespace mods
|
|||||||
}
|
}
|
||||||
|
|
||||||
console::info("Loading mod %s\n", path);
|
console::info("Loading mod %s\n", path);
|
||||||
filesystem::get_search_paths().erase(mod_path);
|
|
||||||
filesystem::get_search_paths().insert(path);
|
if (mod_requires_restart(mod_path) || mod_requires_restart(path))
|
||||||
mod_path = path;
|
{
|
||||||
restart();
|
console::info("Restarting...\n");
|
||||||
|
full_restart("+set fs_game \""s + path + "\"");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
filesystem::unregister_path(mod_path);
|
||||||
|
filesystem::register_path(path);
|
||||||
|
mod_path = path;
|
||||||
|
restart();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
command::add("unloadmod", [](const command::params& params)
|
command::add("unloadmod", [](const command::params& params)
|
||||||
@ -100,7 +121,7 @@ namespace mods
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!game::Com_InFrontend())
|
if (!game::Com_InFrontend() && (game::environment::is_mp() && !game::VirtualLobby_Loaded()))
|
||||||
{
|
{
|
||||||
console::info("Cannot unload mod while in-game!\n");
|
console::info("Cannot unload mod while in-game!\n");
|
||||||
game::CG_GameMessage(0, "^1Cannot unload mod while in-game!");
|
game::CG_GameMessage(0, "^1Cannot unload mod while in-game!");
|
||||||
@ -108,8 +129,27 @@ namespace mods
|
|||||||
}
|
}
|
||||||
|
|
||||||
console::info("Unloading mod %s\n", mod_path.data());
|
console::info("Unloading mod %s\n", mod_path.data());
|
||||||
filesystem::get_search_paths().erase(mod_path);
|
|
||||||
mod_path.clear();
|
if (mod_requires_restart(mod_path))
|
||||||
|
{
|
||||||
|
console::info("Restarting...\n");
|
||||||
|
full_restart("");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
filesystem::unregister_path(mod_path);
|
||||||
|
mod_path.clear();
|
||||||
|
restart();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
command::add("com_restart", []()
|
||||||
|
{
|
||||||
|
if (!game::Com_InFrontend() && (game::environment::is_mp() && !game::VirtualLobby_Loaded()))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
restart();
|
restart();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -134,10 +134,10 @@ namespace patches
|
|||||||
file_name.append(".cfg");
|
file_name.append(".cfg");
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto file = filesystem::file(file_name);
|
std::string buffer{};
|
||||||
if (file.exists())
|
if (filesystem::read_file(file_name, &buffer))
|
||||||
{
|
{
|
||||||
snprintf(buf, size, "%s\n", file.get_buffer().data());
|
snprintf(buf, size, "%s\n", buffer.data());
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -390,7 +390,7 @@ namespace ui_scripting
|
|||||||
load_script("lui_updater", lui_updater);
|
load_script("lui_updater", lui_updater);
|
||||||
load_script("lua_json", lua_json);
|
load_script("lua_json", lua_json);
|
||||||
|
|
||||||
for (const auto& path : filesystem::get_search_paths())
|
for (const auto& path : filesystem::get_search_paths_rev())
|
||||||
{
|
{
|
||||||
load_scripts(path + "/ui_scripts/");
|
load_scripts(path + "/ui_scripts/");
|
||||||
if (game::environment::is_sp())
|
if (game::environment::is_sp())
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include <std_include.hpp>
|
#include <std_include.hpp>
|
||||||
#include "loader/component_loader.hpp"
|
#include "loader/component_loader.hpp"
|
||||||
|
|
||||||
|
#include "console.hpp"
|
||||||
#include "scheduler.hpp"
|
#include "scheduler.hpp"
|
||||||
#include "dvars.hpp"
|
#include "dvars.hpp"
|
||||||
#include "updater.hpp"
|
#include "updater.hpp"
|
||||||
@ -11,11 +12,12 @@
|
|||||||
#include "game/game.hpp"
|
#include "game/game.hpp"
|
||||||
#include "game/dvars.hpp"
|
#include "game/dvars.hpp"
|
||||||
|
|
||||||
#include <utils/nt.hpp>
|
|
||||||
#include <utils/concurrency.hpp>
|
#include <utils/concurrency.hpp>
|
||||||
#include <utils/http.hpp>
|
|
||||||
#include <utils/cryptography.hpp>
|
#include <utils/cryptography.hpp>
|
||||||
|
#include <utils/http.hpp>
|
||||||
#include <utils/io.hpp>
|
#include <utils/io.hpp>
|
||||||
|
#include <utils/nt.hpp>
|
||||||
|
#include <utils/properties.hpp>
|
||||||
#include <utils/string.hpp>
|
#include <utils/string.hpp>
|
||||||
|
|
||||||
#define MASTER "https://master.fed0001.xyz/h1-mod/"
|
#define MASTER "https://master.fed0001.xyz/h1-mod/"
|
||||||
@ -61,6 +63,14 @@ namespace updater
|
|||||||
std::string error{};
|
std::string error{};
|
||||||
std::string current_file{};
|
std::string current_file{};
|
||||||
std::vector<std::string> required_files{};
|
std::vector<std::string> required_files{};
|
||||||
|
std::vector<std::string> garbage_files{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// remove this at some point
|
||||||
|
std::vector<std::string> old_data_files =
|
||||||
|
{
|
||||||
|
{"./data"},
|
||||||
|
{"./cdata"},
|
||||||
};
|
};
|
||||||
|
|
||||||
utils::concurrency::container<update_data_t> update_data;
|
utils::concurrency::container<update_data_t> update_data;
|
||||||
@ -75,6 +85,18 @@ namespace updater
|
|||||||
return main;
|
return main;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string load_binary_name()
|
||||||
|
{
|
||||||
|
utils::nt::library self;
|
||||||
|
return self.get_name();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_binary_name()
|
||||||
|
{
|
||||||
|
static const auto name = load_binary_name();
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
void notify(const std::string& name)
|
void notify(const std::string& name)
|
||||||
{
|
{
|
||||||
scheduler::once([=]()
|
scheduler::once([=]()
|
||||||
@ -109,9 +131,22 @@ namespace updater
|
|||||||
bool check_file(const std::string& name, const std::string& sha)
|
bool check_file(const std::string& name, const std::string& sha)
|
||||||
{
|
{
|
||||||
std::string data;
|
std::string data;
|
||||||
if (!utils::io::read_file(name, &data))
|
|
||||||
|
if (get_binary_name() == name)
|
||||||
{
|
{
|
||||||
return false;
|
if (!utils::io::read_file(name, &data))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto appdata_folder = utils::properties::get_appdata_path();
|
||||||
|
const auto path = (appdata_folder / name).generic_string();
|
||||||
|
if (!utils::io::read_file(path, &data))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (utils::cryptography::sha1::compute(data, true) != sha)
|
if (utils::cryptography::sha1::compute(data, true) != sha)
|
||||||
@ -122,18 +157,6 @@ namespace updater
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string load_binary_name()
|
|
||||||
{
|
|
||||||
utils::nt::library self;
|
|
||||||
return self.get_name();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string get_binary_name()
|
|
||||||
{
|
|
||||||
static const auto name = load_binary_name();
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string get_time_str()
|
std::string get_time_str()
|
||||||
{
|
{
|
||||||
return utils::string::va("%i", uint32_t(time(nullptr)));
|
return utils::string::va("%i", uint32_t(time(nullptr)));
|
||||||
@ -144,6 +167,28 @@ namespace updater
|
|||||||
return utils::http::get_data(MASTER + select(DATA_PATH, DATA_PATH_DEV) + name + "?" + get_time_str());
|
return utils::http::get_data(MASTER + select(DATA_PATH, DATA_PATH_DEV) + name + "?" + get_time_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool has_old_data_files()
|
||||||
|
{
|
||||||
|
bool has = false;
|
||||||
|
for (const auto& file : old_data_files)
|
||||||
|
{
|
||||||
|
if (utils::io::directory_exists(file))
|
||||||
|
{
|
||||||
|
has = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return has;
|
||||||
|
}
|
||||||
|
|
||||||
|
void delete_old_data_files()
|
||||||
|
{
|
||||||
|
for (const auto& file : old_data_files)
|
||||||
|
{
|
||||||
|
std::filesystem::remove_all(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool is_update_cancelled()
|
bool is_update_cancelled()
|
||||||
{
|
{
|
||||||
return update_data.access<bool>([](update_data_t& data_)
|
return update_data.access<bool>([](update_data_t& data_)
|
||||||
@ -161,7 +206,16 @@ namespace updater
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return utils::io::write_file(name, data);
|
if (get_binary_name() == name)
|
||||||
|
{
|
||||||
|
return utils::io::write_file(name, data);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto appdata_folder = utils::properties::get_appdata_path();
|
||||||
|
const auto path = (appdata_folder / name).generic_string();
|
||||||
|
return utils::io::write_file(path, data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void delete_old_file()
|
void delete_old_file()
|
||||||
@ -177,6 +231,54 @@ namespace updater
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> find_garbage_files(const std::vector<std::string>& update_files)
|
||||||
|
{
|
||||||
|
std::vector<std::string> garbage_files{};
|
||||||
|
|
||||||
|
const auto appdata_folder = utils::properties::get_appdata_path();
|
||||||
|
const auto path = (appdata_folder / CLIENT_DATA_FOLDER).generic_string();
|
||||||
|
if (!utils::io::directory_exists(path))
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto current_files = utils::io::list_files_recursively(path);
|
||||||
|
for (const auto& file : current_files)
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
for (const auto& update_file : update_files)
|
||||||
|
{
|
||||||
|
const auto update_file_ = (appdata_folder / update_file).generic_string();
|
||||||
|
const auto path_a = std::filesystem::path(file);
|
||||||
|
const auto path_b = std::filesystem::path(update_file_);
|
||||||
|
const auto is_directory = utils::io::directory_exists(file);
|
||||||
|
const auto compare = path_a.compare(path_b);
|
||||||
|
|
||||||
|
if ((is_directory && compare == -1) || compare == 0)
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
console::debug("[Updater] Found extra file %s\n", file.data());
|
||||||
|
if (file.ends_with(".ff"))
|
||||||
|
{
|
||||||
|
update_data.access([](update_data_t& data_)
|
||||||
|
{
|
||||||
|
data_.restart_required = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
garbage_files.push_back(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return garbage_files;
|
||||||
|
}
|
||||||
|
|
||||||
std::string get_mode_flag()
|
std::string get_mode_flag()
|
||||||
{
|
{
|
||||||
if (game::environment::is_mp())
|
if (game::environment::is_mp())
|
||||||
@ -199,36 +301,11 @@ namespace updater
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// workaround
|
|
||||||
void relaunch()
|
void relaunch()
|
||||||
{
|
{
|
||||||
if (!utils::io::file_exists(BINARY_NAME))
|
const auto mode = game::environment::is_mp() ? "-multiplayer" : "-singleplayer";
|
||||||
{
|
utils::nt::relaunch_self(mode);
|
||||||
utils::nt::terminate(0);
|
utils::nt::terminate();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
STARTUPINFOA startup_info;
|
|
||||||
PROCESS_INFORMATION process_info;
|
|
||||||
|
|
||||||
ZeroMemory(&startup_info, sizeof(startup_info));
|
|
||||||
ZeroMemory(&process_info, sizeof(process_info));
|
|
||||||
startup_info.cb = sizeof(startup_info);
|
|
||||||
|
|
||||||
char current_dir[MAX_PATH];
|
|
||||||
GetCurrentDirectoryA(sizeof(current_dir), current_dir);
|
|
||||||
|
|
||||||
char buf[1024] = {0};
|
|
||||||
const auto command_line = utils::string::va("%s %s", GetCommandLineA(), get_mode_flag().data());
|
|
||||||
strcpy_s(buf, 1024, command_line);
|
|
||||||
|
|
||||||
CreateProcess(BINARY_NAME, buf, nullptr, nullptr, false, NULL, nullptr, current_dir,
|
|
||||||
&startup_info, &process_info);
|
|
||||||
|
|
||||||
if (process_info.hThread && process_info.hThread != INVALID_HANDLE_VALUE) CloseHandle(process_info.hThread);
|
|
||||||
if (process_info.hProcess && process_info.hProcess != INVALID_HANDLE_VALUE) CloseHandle(process_info.hProcess);
|
|
||||||
|
|
||||||
utils::nt::terminate(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_has_tried_update(bool tried)
|
void set_has_tried_update(bool tried)
|
||||||
@ -282,7 +359,7 @@ namespace updater
|
|||||||
{
|
{
|
||||||
return update_data.access<bool>([](update_data_t& data_)
|
return update_data.access<bool>([](update_data_t& data_)
|
||||||
{
|
{
|
||||||
return data_.required_files.size() > 0;
|
return data_.required_files.size() > 0 || data_.garbage_files.size() > 0 || has_old_data_files();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -312,9 +389,7 @@ namespace updater
|
|||||||
|
|
||||||
void cancel_update()
|
void cancel_update()
|
||||||
{
|
{
|
||||||
#ifdef DEBUG
|
console::debug("[Updater] Cancelling update\n");
|
||||||
printf("[Updater] Cancelling update\n");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return update_data.access([](update_data_t& data_)
|
return update_data.access([](update_data_t& data_)
|
||||||
{
|
{
|
||||||
@ -327,9 +402,7 @@ namespace updater
|
|||||||
cancel_update();
|
cancel_update();
|
||||||
reset_data();
|
reset_data();
|
||||||
|
|
||||||
#ifdef DEBUG
|
console::debug("[Updater] starting update check\n");
|
||||||
printf("[Updater] starting update check\n");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
scheduler::once([]()
|
scheduler::once([]()
|
||||||
{
|
{
|
||||||
@ -365,6 +438,7 @@ namespace updater
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> required_files;
|
std::vector<std::string> required_files;
|
||||||
|
std::vector<std::string> update_files;
|
||||||
|
|
||||||
const auto files = j.GetArray();
|
const auto files = j.GetArray();
|
||||||
for (const auto& file : files)
|
for (const auto& file : files)
|
||||||
@ -377,6 +451,8 @@ namespace updater
|
|||||||
const auto name = file[0].GetString();
|
const auto name = file[0].GetString();
|
||||||
const auto sha = file[2].GetString();
|
const auto sha = file[2].GetString();
|
||||||
|
|
||||||
|
update_files.push_back(name);
|
||||||
|
|
||||||
if (!check_file(name, sha))
|
if (!check_file(name, sha))
|
||||||
{
|
{
|
||||||
if (get_binary_name() == name)
|
if (get_binary_name() == name)
|
||||||
@ -387,19 +463,29 @@ namespace updater
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DEBUG
|
std::string name_ = name;
|
||||||
printf("[Updater] need file %s\n", name);
|
if (name_.ends_with(".ff"))
|
||||||
#endif
|
{
|
||||||
|
update_data.access([](update_data_t& data_)
|
||||||
|
{
|
||||||
|
data_.restart_required = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console::debug("[Updater] need file %s\n", name);
|
||||||
|
|
||||||
required_files.push_back(name);
|
required_files.push_back(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
update_data.access([&required_files](update_data_t& data_)
|
const auto garbage_files = find_garbage_files(update_files);
|
||||||
|
|
||||||
|
update_data.access([&](update_data_t& data_)
|
||||||
{
|
{
|
||||||
data_.check.done = true;
|
data_.check.done = true;
|
||||||
data_.check.success = true;
|
data_.check.success = true;
|
||||||
data_.required_files = required_files;
|
data_.required_files = required_files;
|
||||||
|
data_.garbage_files = garbage_files;
|
||||||
});
|
});
|
||||||
|
|
||||||
notify("update_check_done");
|
notify("update_check_done");
|
||||||
@ -408,15 +494,32 @@ namespace updater
|
|||||||
|
|
||||||
void start_update_download()
|
void start_update_download()
|
||||||
{
|
{
|
||||||
#ifdef DEBUG
|
console::debug("[Updater] starting update download\n");
|
||||||
printf("[Updater] starting update download\n");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!is_update_check_done() || !get_update_check_status() || is_update_cancelled())
|
if (!is_update_check_done() || !get_update_check_status() || is_update_cancelled())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete_old_data_files();
|
||||||
|
|
||||||
|
const auto garbage_files = update_data.access<std::vector<std::string>>([](update_data_t& data_)
|
||||||
|
{
|
||||||
|
return data_.garbage_files;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const auto& file : garbage_files)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::filesystem::remove_all(file);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
console::error("Failed to delete %s\n", file.data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
scheduler::once([]()
|
scheduler::once([]()
|
||||||
{
|
{
|
||||||
const auto required_files = update_data.access<std::vector<std::string>>([](update_data_t& data_)
|
const auto required_files = update_data.access<std::vector<std::string>>([](update_data_t& data_)
|
||||||
@ -433,9 +536,7 @@ namespace updater
|
|||||||
data_.current_file = file;
|
data_.current_file = file;
|
||||||
});
|
});
|
||||||
|
|
||||||
#ifdef DEBUG
|
console::debug("[Updater] downloading file %s\n", file.data());
|
||||||
printf("[Updater] downloading file %s\n", file.data());
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const auto data = download_file(file);
|
const auto data = download_file(file);
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#define CLIENT_DATA_FOLDER "cdata"
|
||||||
|
|
||||||
namespace updater
|
namespace updater
|
||||||
{
|
{
|
||||||
void relaunch();
|
void relaunch();
|
||||||
|
@ -50,7 +50,7 @@ namespace scripting::lua::engine
|
|||||||
{
|
{
|
||||||
stop();
|
stop();
|
||||||
running = true;
|
running = true;
|
||||||
for (const auto& path : filesystem::get_search_paths())
|
for (const auto& path : filesystem::get_search_paths_rev())
|
||||||
{
|
{
|
||||||
load_scripts(path + "/scripts/");
|
load_scripts(path + "/scripts/");
|
||||||
if (game::environment::is_sp())
|
if (game::environment::is_sp())
|
||||||
|
@ -1422,6 +1422,12 @@ namespace game
|
|||||||
const char* name;
|
const char* name;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct LocalizeEntry
|
||||||
|
{
|
||||||
|
const char* value;
|
||||||
|
const char* name;
|
||||||
|
};
|
||||||
|
|
||||||
union XAssetHeader
|
union XAssetHeader
|
||||||
{
|
{
|
||||||
void* data;
|
void* data;
|
||||||
@ -1435,6 +1441,7 @@ namespace game
|
|||||||
TTF* ttf;
|
TTF* ttf;
|
||||||
XModel* model;
|
XModel* model;
|
||||||
WeaponDef* weapon;
|
WeaponDef* weapon;
|
||||||
|
LocalizeEntry* localize;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct XAsset
|
struct XAsset
|
||||||
|
@ -221,6 +221,9 @@ namespace game
|
|||||||
WEAK symbol<bool(const char* path)> Sys_FileExists{0x0, 0x0};
|
WEAK symbol<bool(const char* path)> Sys_FileExists{0x0, 0x0};
|
||||||
WEAK symbol<HANDLE(Sys_Folder, const char* baseFilename)> Sys_CreateFile{0x42C430, 0x5B2860};
|
WEAK symbol<HANDLE(Sys_Folder, const char* baseFilename)> Sys_CreateFile{0x42C430, 0x5B2860};
|
||||||
|
|
||||||
|
WEAK symbol<const char*()> SEH_GetCurrentLanguageCode{0x3E5FB0, 0x585090};
|
||||||
|
WEAK symbol<const char*()> SEH_GetCurrentLanguageName{0x3E6030, 0x5850F0};
|
||||||
|
|
||||||
WEAK symbol<const char*(const char*)> UI_GetMapDisplayName{0x0, 0x4DDEE0};
|
WEAK symbol<const char*(const char*)> UI_GetMapDisplayName{0x0, 0x4DDEE0};
|
||||||
WEAK symbol<const char*(const char*)> UI_GetGameTypeDisplayName{0x0, 0x4DD8C0};
|
WEAK symbol<const char*(const char*)> UI_GetGameTypeDisplayName{0x0, 0x4DD8C0};
|
||||||
WEAK symbol<void(unsigned int localClientNum, const char** args)> UI_RunMenuScript{0x3F3AA0, 0x1E35B0};
|
WEAK symbol<void(unsigned int localClientNum, const char** args)> UI_RunMenuScript{0x3F3AA0, 0x1E35B0};
|
||||||
|
@ -6,9 +6,10 @@
|
|||||||
|
|
||||||
#include "component/arxan.hpp"
|
#include "component/arxan.hpp"
|
||||||
|
|
||||||
#include <utils/string.hpp>
|
|
||||||
#include <utils/flags.hpp>
|
#include <utils/flags.hpp>
|
||||||
#include <utils/io.hpp>
|
#include <utils/io.hpp>
|
||||||
|
#include <utils/string.hpp>
|
||||||
|
#include <utils/properties.hpp>
|
||||||
|
|
||||||
DECLSPEC_NORETURN void WINAPI exit_hook(const int code)
|
DECLSPEC_NORETURN void WINAPI exit_hook(const int code)
|
||||||
{
|
{
|
||||||
@ -62,7 +63,7 @@ void apply_aslr_patch(std::string* data)
|
|||||||
|
|
||||||
void get_aslr_patched_binary(std::string* binary, std::string* data)
|
void get_aslr_patched_binary(std::string* binary, std::string* data)
|
||||||
{
|
{
|
||||||
const auto patched_binary = "h1-mod\\"s + *binary;
|
const auto patched_binary = (utils::properties::get_appdata_path() / "bin" / *binary).generic_string();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -159,6 +160,7 @@ FARPROC load_binary(const launcher::mode mode, uint64_t* base_address)
|
|||||||
void remove_crash_file()
|
void remove_crash_file()
|
||||||
{
|
{
|
||||||
utils::io::remove_file("__h1Exe");
|
utils::io::remove_file("__h1Exe");
|
||||||
|
utils::io::remove_file("h1-mod\\h1_mp64_ship.exe"); // remove this at some point
|
||||||
}
|
}
|
||||||
|
|
||||||
void enable_dpi_awareness()
|
void enable_dpi_awareness()
|
||||||
|
@ -89,6 +89,9 @@
|
|||||||
#include <MinHook.h>
|
#include <MinHook.h>
|
||||||
#include <tomcrypt.h>
|
#include <tomcrypt.h>
|
||||||
|
|
||||||
|
#define RAPIDJSON_NOEXCEPT
|
||||||
|
#define RAPIDJSON_ASSERT(cond) if(cond); else throw std::runtime_error("rapidjson assert fail");
|
||||||
|
|
||||||
#include <rapidjson/document.h>
|
#include <rapidjson/document.h>
|
||||||
#include <rapidjson/prettywriter.h>
|
#include <rapidjson/prettywriter.h>
|
||||||
#include <rapidjson/stringbuffer.h>
|
#include <rapidjson/stringbuffer.h>
|
||||||
|
@ -121,6 +121,18 @@ namespace utils::io
|
|||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> list_files_recursively(const std::string& directory)
|
||||||
|
{
|
||||||
|
std::vector<std::string> files;
|
||||||
|
|
||||||
|
for (auto& file : std::filesystem::recursive_directory_iterator(directory))
|
||||||
|
{
|
||||||
|
files.push_back(file.path().generic_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
void copy_folder(const std::filesystem::path& src, const std::filesystem::path& target)
|
void copy_folder(const std::filesystem::path& src, const std::filesystem::path& target)
|
||||||
{
|
{
|
||||||
std::filesystem::copy(src, target,
|
std::filesystem::copy(src, target,
|
||||||
|
@ -18,5 +18,6 @@ namespace utils::io
|
|||||||
bool directory_is_empty(const std::string& directory);
|
bool directory_is_empty(const std::string& directory);
|
||||||
bool remove_directory(const std::string& directory);
|
bool remove_directory(const std::string& directory);
|
||||||
std::vector<std::string> list_files(const std::string& directory);
|
std::vector<std::string> list_files(const std::string& directory);
|
||||||
|
std::vector<std::string> list_files_recursively(const std::string& directory);
|
||||||
void copy_folder(const std::filesystem::path& src, const std::filesystem::path& target);
|
void copy_folder(const std::filesystem::path& src, const std::filesystem::path& target);
|
||||||
}
|
}
|
||||||
|
@ -225,7 +225,7 @@ namespace utils::nt
|
|||||||
return std::string(LPSTR(LockResource(handle)), SizeofResource(nullptr, res));
|
return std::string(LPSTR(LockResource(handle)), SizeofResource(nullptr, res));
|
||||||
}
|
}
|
||||||
|
|
||||||
void relaunch_self()
|
void relaunch_self(const std::string& extra_command_line, bool override_command_line)
|
||||||
{
|
{
|
||||||
const utils::nt::library self;
|
const utils::nt::library self;
|
||||||
|
|
||||||
@ -238,9 +238,21 @@ namespace utils::nt
|
|||||||
|
|
||||||
char current_dir[MAX_PATH];
|
char current_dir[MAX_PATH];
|
||||||
GetCurrentDirectoryA(sizeof(current_dir), current_dir);
|
GetCurrentDirectoryA(sizeof(current_dir), current_dir);
|
||||||
auto* const command_line = GetCommandLineA();
|
|
||||||
|
|
||||||
CreateProcessA(self.get_path().data(), command_line, nullptr, nullptr, false, NULL, nullptr, current_dir,
|
std::string command_line = GetCommandLineA();
|
||||||
|
if (!extra_command_line.empty())
|
||||||
|
{
|
||||||
|
if (override_command_line)
|
||||||
|
{
|
||||||
|
command_line = extra_command_line;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
command_line += " " + extra_command_line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateProcessA(self.get_path().data(), command_line.data(), nullptr, nullptr, false, NULL, nullptr, current_dir,
|
||||||
&startup_info, &process_info);
|
&startup_info, &process_info);
|
||||||
|
|
||||||
if (process_info.hThread && process_info.hThread != INVALID_HANDLE_VALUE) CloseHandle(process_info.hThread);
|
if (process_info.hThread && process_info.hThread != INVALID_HANDLE_VALUE) CloseHandle(process_info.hThread);
|
||||||
|
@ -105,6 +105,6 @@ namespace utils::nt
|
|||||||
__declspec(noreturn) void raise_hard_exception();
|
__declspec(noreturn) void raise_hard_exception();
|
||||||
std::string load_resource(int id);
|
std::string load_resource(int id);
|
||||||
|
|
||||||
void relaunch_self();
|
void relaunch_self(const std::string& extra_command_line = "", bool override_command_line = false);
|
||||||
__declspec(noreturn) void terminate(uint32_t code = 0);
|
__declspec(noreturn) void terminate(uint32_t code = 0);
|
||||||
}
|
}
|
||||||
|
24
src/common/utils/properties.cpp
Normal file
24
src/common/utils/properties.cpp
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#include "io.hpp"
|
||||||
|
#include "properties.hpp"
|
||||||
|
#include <gsl/gsl>
|
||||||
|
#include <ShlObj.h>
|
||||||
|
|
||||||
|
namespace utils::properties
|
||||||
|
{
|
||||||
|
std::filesystem::path get_appdata_path()
|
||||||
|
{
|
||||||
|
PWSTR path;
|
||||||
|
if (!SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &path)))
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to read APPDATA path!");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto _ = gsl::finally([&path]
|
||||||
|
{
|
||||||
|
CoTaskMemFree(path);
|
||||||
|
});
|
||||||
|
|
||||||
|
static auto appdata = std::filesystem::path(path) / "h1-mod";
|
||||||
|
return appdata;
|
||||||
|
}
|
||||||
|
}
|
6
src/common/utils/properties.hpp
Normal file
6
src/common/utils/properties.hpp
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace utils::properties
|
||||||
|
{
|
||||||
|
std::filesystem::path get_appdata_path();
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user