From 43f5603aa42481f9ccb3cbcc9d459ca907a0e587 Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Sun, 30 Jan 2022 22:55:21 +0100 Subject: [PATCH] Add updater --- .../{mods_menus => mods}/__init__.lua | 0 .../{mods_menus => mods}/loading.lua | 46 +-- .../ui_scripts/{mods_menus => mods}/store.lua | 0 src/client/game/ui_scripting/lua/context.cpp | 13 +- src/client/game/ui_scripting/lua/engine.cpp | 4 +- src/client/resource.hpp | 2 +- src/client/resource.rc | 2 +- src/client/resources/mods_menu.lua | 121 ------- src/client/resources/updater.lua | 339 ++++++++++++++++++ 9 files changed, 365 insertions(+), 162 deletions(-) rename data/ui_scripts/{mods_menus => mods}/__init__.lua (100%) rename data/ui_scripts/{mods_menus => mods}/loading.lua (71%) rename data/ui_scripts/{mods_menus => mods}/store.lua (100%) delete mode 100644 src/client/resources/mods_menu.lua create mode 100644 src/client/resources/updater.lua diff --git a/data/ui_scripts/mods_menus/__init__.lua b/data/ui_scripts/mods/__init__.lua similarity index 100% rename from data/ui_scripts/mods_menus/__init__.lua rename to data/ui_scripts/mods/__init__.lua diff --git a/data/ui_scripts/mods_menus/loading.lua b/data/ui_scripts/mods/loading.lua similarity index 71% rename from data/ui_scripts/mods_menus/loading.lua rename to data/ui_scripts/mods/loading.lua index 23d57980..31c85425 100644 --- a/data/ui_scripts/mods_menus/loading.lua +++ b/data/ui_scripts/mods/loading.lua @@ -26,39 +26,15 @@ function string:truncate(length) return self:sub(1, length - 3) .. "..." end -local maincampaign = LUI.MenuBuilder.m_types_build["main_campaign"] -LUI.MenuBuilder.m_types_build["main_campaign"] = function(a1, a2) - local menu = maincampaign(a1, a2) - local buttonlist = menu:getChildById("main_campaign_list") - - local button = menu:AddButton("$_MODS", function() +LUI.addmenubutton("main_campaign", { + index = 6, + id = "mods_menu-button", + text = "$_MODS", + description = "Load installed mods.", + callback = function() LUI.FlowManager.RequestAddMenu(nil, "mods_menu") - end, nil, true, nil, { - desc_text = "Open mods menu" - }) - - buttonlist:removeElement(button) - buttonlist:insertElement(button, 6) - button.id = "mods_menu-button" - - local hintbox = menu.optionTextInfo - local firstbutton = buttonlist:getFirstChild() - hintbox:dispatchEventToRoot({ - name = "set_button_info_text", - text = firstbutton.properties.desc_text, - immediate = true - }) - - menu:CreateBottomDivider() - menu:AddBottomDividerToList(buttonlist:getLastChild()) - menu:removeElement(menu.optionTextInfo) - - LUI.Options.InitScrollingList(menu.list, nil) - menu:CreateBottomDivider() - menu.optionTextInfo = LUI.Options.AddOptionTextInfo(menu) - - return menu -end + end +}) LUI.MenuBuilder.m_types_build["mods_menu"] = function(a1) local menu = LUI.MenuTemplate.new(a1, { @@ -70,9 +46,11 @@ LUI.MenuBuilder.m_types_build["mods_menu"] = function(a1) }) menu:AddButton("$_OPEN STORE", function() - LUI.FlowManager.RequestAddMenu(nil, "mod_store_menu") + if (LUI.MenuBuilder.m_types_build["mod_store_menu"]) then + LUI.FlowManager.RequestAddMenu(nil, "mod_store_menu") + end end, nil, true, nil, { - desc_text = "Open the menu store" + desc_text = "Download and install mods." }) local modfolder = game:getloadedmod() diff --git a/data/ui_scripts/mods_menus/store.lua b/data/ui_scripts/mods/store.lua similarity index 100% rename from data/ui_scripts/mods_menus/store.lua rename to data/ui_scripts/mods/store.lua diff --git a/src/client/game/ui_scripting/lua/context.cpp b/src/client/game/ui_scripting/lua/context.cpp index 830ae314..9717ff64 100644 --- a/src/client/game/ui_scripting/lua/context.cpp +++ b/src/client/game/ui_scripting/lua/context.cpp @@ -1119,11 +1119,11 @@ namespace ui_scripting::lua event.name = "http_request_done"; if (result.has_value()) { - event.arguments = { id, true, result.value() }; + event.arguments = {id, true, result.value()}; } else { - event.arguments = { id, false }; + event.arguments = {id, false}; } notify(event); @@ -1134,7 +1134,7 @@ namespace ui_scripting::lua game_type["sha"] = [](const game&, const std::string& data) { - return utils::cryptography::sha1::compute(data, true); + return utils::string::to_upper(utils::cryptography::sha1::compute(data, true)); }; game_type["environment"] = [](const game&) @@ -1142,6 +1142,13 @@ namespace ui_scripting::lua return GIT_BRANCH; }; + game_type["binaryname"] = [](const game&) + { + char name[MAX_PATH] = {0}; + GetModuleBaseName(GetCurrentProcess(), GetModuleHandle(NULL), name, MAX_PATH); + return std::string(name); + }; + struct player { }; diff --git a/src/client/game/ui_scripting/lua/engine.cpp b/src/client/game/ui_scripting/lua/engine.cpp index b3cefc35..e7450a72 100644 --- a/src/client/game/ui_scripting/lua/engine.cpp +++ b/src/client/game/ui_scripting/lua/engine.cpp @@ -13,7 +13,7 @@ namespace ui_scripting::lua::engine { namespace { - const auto mods_menu_script = utils::nt::load_resource(LUI_MODS_MENU); + const auto updater_script = utils::nt::load_resource(LUI_UPDATER_MENU); float screen_max[2]; @@ -413,7 +413,7 @@ namespace ui_scripting::lua::engine get_scripts().clear(); clear_menus(); - //load_code(mods_menu_script); + load_code(updater_script); load_scripts("ui_scripts/"); load_scripts("h2-mod/ui_scripts/"); diff --git a/src/client/resource.hpp b/src/client/resource.hpp index 3b68dedb..2005d4f1 100644 --- a/src/client/resource.hpp +++ b/src/client/resource.hpp @@ -23,4 +23,4 @@ #define LUA_ANIMATION_SCRIPT 314 #define LUA_JSON_SCRIPT 315 -#define LUI_MODS_MENU 316 +#define LUI_UPDATER_MENU 316 diff --git a/src/client/resource.rc b/src/client/resource.rc index 02339d31..65467a1d 100644 --- a/src/client/resource.rc +++ b/src/client/resource.rc @@ -100,7 +100,7 @@ MENU_MAIN RCDATA "resources/main.html" LUA_ANIMATION_SCRIPT RCDATA "resources/animation.lua" LUA_JSON_SCRIPT RCDATA "resources/json.lua" -LUI_MODS_MENU RCDATA "resources/mods_menu.lua" +LUI_UPDATER_MENU RCDATA "resources/updater.lua" #ifdef _DEBUG TLS_DLL RCDATA "../../build/bin/x64/Debug/tlsdll.dll" diff --git a/src/client/resources/mods_menu.lua b/src/client/resources/mods_menu.lua deleted file mode 100644 index f8743713..00000000 --- a/src/client/resources/mods_menu.lua +++ /dev/null @@ -1,121 +0,0 @@ -function createdivider(menu, text) - local element = LUI.UIElement.new( { - leftAnchor = true, - rightAnchor = true, - left = 0, - right = 0, - topAnchor = true, - bottomAnchor = false, - top = 0, - bottom = 33.33 - }) - - element.scrollingToNext = true - element:addElement(LUI.MenuBuilder.BuildRegisteredType("h1_option_menu_titlebar", { - title_bar_text = Engine.ToUpperCase(Engine.Localize(text)) - })) - - menu.list:addElement(element) -end - -local maincampaign = LUI.MenuBuilder.m_types_build["main_campaign"] -LUI.MenuBuilder.m_types_build["main_campaign"] = function(a1, a2) - local menu = maincampaign(a1, a2) - local buttonlist = menu:getChildById("main_campaign_list") - - local button = menu:AddButton("$_MODS", function() - LUI.FlowManager.RequestAddMenu(nil, "mods_menu") - end, nil, true, nil, { - desc_text = "Open mods menu" - }) - - buttonlist:removeElement(button) - buttonlist:insertElement(button, 6) - button.id = "mods_menu-button" - - local hintbox = menu.optionTextInfo - local firstbutton = buttonlist:getFirstChild() - hintbox:dispatchEventToRoot({ - name = "set_button_info_text", - text = firstbutton.properties.desc_text, - immediate = true - }) - - menu:CreateBottomDivider() - menu:AddBottomDividerToList(buttonlist:getLastChild()) - menu:removeElement(menu.optionTextInfo) - - LUI.Options.InitScrollingList(menu.list, nil) - menu:CreateBottomDivider() - menu.optionTextInfo = LUI.Options.AddOptionTextInfo(menu) - - return menu -end - -function modsmenu(a1) - local menu = LUI.MenuTemplate.new(a1, { - menu_title = "$_MODS", - exclusiveController = 0, - menu_width = 400, - menu_top_indent = LUI.MenuTemplate.spMenuOffset, - showTopRightSmallBar = true - }) - - local modfolder = game:getloadedmod() - if (modfolder ~= "") then - createdivider(menu, "$_Loaded mod: " .. modfolder) - - menu:AddButton("$_UNLOAD", function() - game:executecommand("unloadmod") - end, nil, true, nil, { - desc_text = "Unload the currently loaded mod" - }) - end - - createdivider(menu, "$_Available mods") - - if (io.directoryexists("mods")) then - local mods = io.listfiles("mods/") - for i = 1, #mods do - local desc = "Load " .. mods[i] - local infofile = mods[i] .. "/mod.txt" - local exists = io.fileexists(infofile) - - if (exists) then - desc = io.readfile(infofile) - end - - if (mods[i] ~= modfolder) then - menu:AddButton("$_" .. mods[i], function() - game:executecommand("loadmod " .. mods[i]) - end, nil, true, nil, { - desc_text = desc - }) - end - end - end - - menu:AddBackButton(function(a1) - Engine.PlaySound(CoD.SFX.MenuBack) - LUI.FlowManager.RequestLeaveMenu(a1) - end) - - LUI.Options.InitScrollingList(menu.list, nil) - menu:CreateBottomDivider() - menu.optionTextInfo = LUI.Options.AddOptionTextInfo(menu) - - return menu -end - -LUI.MenuBuilder.m_types_build["mods_menu"] = modsmenu - -local localize = Engine.Localize -Engine.Localize = function(...) - local args = {...} - - if (type(args[1]) == "string" and args[1]:sub(1, 2) == "$_") then - return args[1]:sub(3, -1) - end - - return localize(table.unpack(args)) -end \ No newline at end of file diff --git a/src/client/resources/updater.lua b/src/client/resources/updater.lua new file mode 100644 index 00000000..074dd7c5 --- /dev/null +++ b/src/client/resources/updater.lua @@ -0,0 +1,339 @@ +LUI.menucallbacks = {} +LUI.originalmenus = {} + +LUI.onmenuopen = function(name, callback) + if (not LUI.MenuBuilder.m_types_build[name]) then + return + end + + if (not LUI.menucallbacks[name]) then + LUI.menucallbacks[name] = {} + end + + luiglobals.table.insert(LUI.menucallbacks[name], callback) + + if (not LUI.originalmenus[name]) then + LUI.originalmenus[name] = LUI.MenuBuilder.m_types_build[name] + LUI.MenuBuilder.m_types_build[name] = function(...) + local args = {...} + local menu = LUI.originalmenus[name](table.unpack(args)) + + for k, v in luiglobals.next, LUI.menucallbacks[name] do + v(menu, table.unpack(args)) + end + + return menu + end + end +end + +local addoptionstextinfo = LUI.Options.AddOptionTextInfo +LUI.Options.AddOptionTextInfo = function(menu) + local result = addoptionstextinfo(menu) + menu.optionTextInfo = result + return result +end + +LUI.addmenubutton = function(name, data) + LUI.onmenuopen(name, function(menu) + if (not menu.list) then + return + end + + local button = menu:AddButton(data.text, data.callback, nil, true, nil, { + desc_text = data.description + }) + + local buttonlist = menu:getChildById(menu.type .. "_list") + + if (data.id) then + button.id = data.id + end + + if (data.index) then + buttonlist:removeElement(button) + buttonlist:insertElement(button, data.index) + end + + local hintbox = menu.optionTextInfo + menu:removeElement(hintbox) + + LUI.Options.InitScrollingList(menu.list, nil) + menu.optionTextInfo = LUI.Options.AddOptionTextInfo(menu) + end) +end + +stack = {} +LUI.openmenu = function(menu, args) + stack = args + LUI.FlowManager.RequestAddMenu(nil, menu) + return stack.ret +end + +LUI.openpopupmenu = function(menu, args) + stack = args + LUI.FlowManager.RequestPopupMenu(nil, menu) + return stack.ret +end + +LUI.onmenuopen("main_lockout", function() + if (game:sharedget("has_tried_updating") == "") then + game:ontimeout(function() + game:sharedset("has_tried_updating", "1") + tryupdate(true) + end, 0) + end +end) + +LUI.addmenubutton("pc_controls", { + text = "$_CHECK FOR UPDATES", + description = "Check for updates.", + callback = function() + tryupdate(false) + end +}) + +stack = {} +LUI.MenuBuilder.m_types_build["generic_waiting_popup_"] = function (menu, event) + local oncancel = stack.oncancel + + local popup = LUI.MenuBuilder.BuildRegisteredType("waiting_popup", { + message_text = stack.text, + isLiveWithCancel = true, + cancel_func = function(...) + local args = {...} + oncancel() + LUI.common_menus.CommonPopups.CancelCSSDownload(table.unpack(args)) + end + }) + + stack = { + ret = popup + } + + return popup +end + +LUI.MenuBuilder.m_types_build["generic_yes_no_popup_"] = function() + local title = stack.title + local text = stack.text + local callback = stack.callback + + local popup = LUI.MenuBuilder.BuildRegisteredType("generic_yesno_popup", { + popup_title = title, + message_text = text, + yes_action = function() + callback(true) + end, + no_action = function() + callback(false) + end + }) + + stack = { + ret = popup + } + + return popup +end + +LUI.yesnopopup = function(data) + for k, v in luiglobals.next, data do + stack[k] = v + end + LUI.FlowManager.RequestPopupMenu(nil, "generic_yes_no_popup_") + return stack.ret +end + +function updaterpopup(oncancel) + return LUI.openpopupmenu("generic_waiting_popup_", { + oncancel = oncancel, + withcancel = true, + text = "Checking for updates..." + }) +end + +function verifyfiles(files) + local needed = {} + local binaryname = game:binaryname() + + for i = 1, #files do + local name = files[i][1] + if (name ~= binaryname and (not io.fileexists(files[i][1]) or game:sha(io.readfile(files[i][1])) ~= files[i][3])) then + table.insert(needed, files[i]) + end + end + + return needed +end + +local canceled = false + +function downloadfiles(popup, files, callback) + local text = popup:getchildren()[7] + local folder = game:environment() == "develop" and "data-dev" or "data" + + if (#files == 0) then + callback(true) + return + end + + local total = 0 + local filedownloaded = function() + total = total + 1 + if (total == #files) then + callback(true) + end + end + + local download = nil + local stop = false + download = function(index) + if (canceled or stop) then + return + end + + local url = "https://master.fed0001.xyz/" .. folder .. "/" .. files[index][1] + text:setText(string.format("Downloading file [%i/%i]\n%s", index, #files, files[index][1])) + + httprequest(url, function(valid, data) + if (not valid) then + callback(false, "Invalid server response") + stop = true + return + end + + if (not io.writefile(files[index][1], data, false)) then + callback(false, "Failed to write file " .. files[index][1]) + stop = true + return + end + + filedownloaded() + + if (files[index + 1]) then + download(index + 1) + else + callback(true) + end + end) + end + + download(1) +end + +function userdata_:getchildren() + local children = {} + local first = self:getFirstChild() + + while (first) do + table.insert(children, first) + first = first:getNextSibling() + end + + return children +end + +function tryupdate(autoclose) + canceled = false + local popup = updaterpopup(function() + canceled = true + end) + + local text = popup:getchildren()[7] + local file = game:environment() == "develop" and "files-dev.json" or "files.json" + + httprequest("https://master.fed0001.xyz/" .. file, function(valid, data) + if (not valid) then + text:setText("Update check failed: Invalid server response") + return + end + + local valid = pcall(function() + local files = json.decode(data) + local needed = verifyfiles(files) + + if (#needed == 0) then + text:setText("No updates available") + if (autoclose) then + LUI.common_menus.CommonPopups.CancelCSSDownload(popup, {}) + end + return + end + + local download = function() + local gotresult = false + downloadfiles(popup, needed, function(result, error) + if (gotresult or canceled) then + return + end + + gotresult = true + + if (result and #needed > 0) then + game:executecommand("lui_restart") + end + + if (not result) then + text:setText("Update failed: " .. error) + return + end + + text:setText("Update successful!") + + if (autoclose) then + LUI.common_menus.CommonPopups.CancelCSSDownload(popup, {}) + end + end) + end + + if (autoclose) then + download() + else + LUI.yesnopopup({ + title = "NOTICE", + text = "An update is available, proceed with installation?", + callback = function(result) + if (not result) then + LUI.common_menus.CommonPopups.CancelCSSDownload(popup, {}) + else + download() + end + end + }) + end + end) + + if (not valid) then + text:setText("Update failed: unknown error") + end + end) +end + +LUI.tryupdating = function(autoclose) + tryupdate(autoclose) +end + +function httprequest(url, callback) + local request = game:httpget(url) + local listener = nil + listener = game:onnotify("http_request_done", function(id, valid, data) + if (id ~= request) then + return + end + + listener:clear() + callback(valid, data) + end) +end + +local localize = Engine.Localize +Engine.Localize = function(...) + local args = {...} + + if (type(args[1]) == "string" and args[1]:sub(1, 2) == "$_") then + return args[1]:sub(3, -1) + end + + return localize(table.unpack(args)) +end \ No newline at end of file