Add updater
This commit is contained in:
parent
5dcc59f5fa
commit
e4344100fc
469
src/client/component/updater.cpp
Normal file
469
src/client/component/updater.cpp
Normal file
@ -0,0 +1,469 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "scheduler.hpp"
|
||||
#include "dvars.hpp"
|
||||
#include "updater.hpp"
|
||||
|
||||
#include "version.h"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include <utils/nt.hpp>
|
||||
#include <utils/concurrency.hpp>
|
||||
#include <utils/http.hpp>
|
||||
#include <utils/cryptography.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
#define MASTER "https://master.fed0001.xyz/h1-mod/"
|
||||
|
||||
#define FILES_PATH "files.json"
|
||||
#define FILES_PATH_DEV "files-dev.json"
|
||||
|
||||
#define DATA_PATH "data/"
|
||||
#define DATA_PATH_DEV "data-dev/"
|
||||
|
||||
#define ERR_UPDATE_CHECK_FAIL "Failed to check for updates"
|
||||
#define ERR_DOWNLOAD_FAIL "Failed to download file "
|
||||
#define ERR_WRITE_FAIL "Failed to write file "
|
||||
|
||||
#define BINARY_NAME "h1-mod.exe"
|
||||
|
||||
namespace updater
|
||||
{
|
||||
namespace
|
||||
{
|
||||
game::dvar_t* cl_auto_update;
|
||||
bool has_tried_update = false;
|
||||
|
||||
struct status
|
||||
{
|
||||
bool done;
|
||||
bool success;
|
||||
};
|
||||
|
||||
struct file_data
|
||||
{
|
||||
std::string name;
|
||||
std::string data;
|
||||
};
|
||||
|
||||
struct update_data_t
|
||||
{
|
||||
bool restart_required{};
|
||||
bool cancelled{};
|
||||
status check{};
|
||||
status download{};
|
||||
std::string error{};
|
||||
std::string current_file{};
|
||||
std::vector<std::string> required_files{};
|
||||
};
|
||||
|
||||
utils::concurrency::container<update_data_t> update_data;
|
||||
|
||||
std::string get_branch()
|
||||
{
|
||||
return GIT_BRANCH;
|
||||
}
|
||||
|
||||
std::string select(const std::string& main, const std::string& develop)
|
||||
{
|
||||
if (get_branch() == "develop")
|
||||
{
|
||||
return develop;
|
||||
}
|
||||
|
||||
return main;
|
||||
}
|
||||
|
||||
std::string get_data_path()
|
||||
{
|
||||
if (get_branch() == "develop")
|
||||
{
|
||||
return DATA_PATH_DEV;
|
||||
}
|
||||
|
||||
return DATA_PATH;
|
||||
}
|
||||
|
||||
void set_update_check_status(bool done, bool success, const std::string& error = {})
|
||||
{
|
||||
update_data.access([done, success, error](update_data_t& data_)
|
||||
{
|
||||
data_.check.done = done;
|
||||
data_.check.success = success;
|
||||
data_.error = error;
|
||||
});
|
||||
}
|
||||
|
||||
void set_update_download_status(bool done, bool success, const std::string& error = {})
|
||||
{
|
||||
update_data.access([done, success, error](update_data_t& data_)
|
||||
{
|
||||
data_.download.done = done;
|
||||
data_.download.success = success;
|
||||
data_.error = error;
|
||||
});
|
||||
}
|
||||
|
||||
bool check_file(const std::string& name, const std::string& sha)
|
||||
{
|
||||
std::string data;
|
||||
if (!utils::io::read_file(name, &data))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (utils::cryptography::sha1::compute(data, true) != sha)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string load_binary_name()
|
||||
{
|
||||
// utils::nt::library self;
|
||||
// return self.get_name();
|
||||
// returns the game's name and not the client's
|
||||
|
||||
return BINARY_NAME;
|
||||
}
|
||||
|
||||
std::string get_binary_name()
|
||||
{
|
||||
static const auto name = load_binary_name();
|
||||
return name;
|
||||
}
|
||||
|
||||
std::optional<std::string> download_file(const std::string& name)
|
||||
{
|
||||
return utils::http::get_data(MASTER + select(DATA_PATH, DATA_PATH_DEV) + name);
|
||||
}
|
||||
|
||||
bool is_update_cancelled()
|
||||
{
|
||||
return update_data.access<bool>([](update_data_t& data_)
|
||||
{
|
||||
return data_.cancelled;
|
||||
});
|
||||
}
|
||||
|
||||
bool write_file(const std::string& name, const std::string& data)
|
||||
{
|
||||
if (get_binary_name() == name &&
|
||||
utils::io::file_exists(name) &&
|
||||
!utils::io::move_file(name, name + ".old"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
return utils::io::write_file("update_test/" + name, data);
|
||||
#else
|
||||
return utils::io::write_file(name, data);
|
||||
#endif
|
||||
}
|
||||
|
||||
void delete_old_file()
|
||||
{
|
||||
utils::io::remove_file(get_binary_name() + ".old");
|
||||
}
|
||||
|
||||
void reset_data()
|
||||
{
|
||||
update_data.access([](update_data_t& data_)
|
||||
{
|
||||
data_ = {};
|
||||
});
|
||||
}
|
||||
|
||||
std::string get_mode_flag()
|
||||
{
|
||||
if (game::environment::is_mp())
|
||||
{
|
||||
return "-multiplayer";
|
||||
}
|
||||
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
return "-singleplayer";
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// workaround
|
||||
void relaunch()
|
||||
{
|
||||
if (!utils::io::file_exists(BINARY_NAME))
|
||||
{
|
||||
utils::nt::terminate(0);
|
||||
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)
|
||||
{
|
||||
has_tried_update = tried;
|
||||
}
|
||||
|
||||
bool get_has_tried_update()
|
||||
{
|
||||
return has_tried_update;
|
||||
}
|
||||
|
||||
bool auto_updates_enabled()
|
||||
{
|
||||
return cl_auto_update->current.enabled;
|
||||
}
|
||||
|
||||
bool is_update_check_done()
|
||||
{
|
||||
return update_data.access<bool>([](update_data_t& data_)
|
||||
{
|
||||
return data_.check.done;
|
||||
});
|
||||
}
|
||||
|
||||
bool is_update_download_done()
|
||||
{
|
||||
return update_data.access<bool>([](update_data_t& data_)
|
||||
{
|
||||
return data_.download.done;
|
||||
});
|
||||
}
|
||||
|
||||
bool get_update_check_status()
|
||||
{
|
||||
return update_data.access<bool>([](update_data_t& data_)
|
||||
{
|
||||
return data_.check.success;
|
||||
});
|
||||
}
|
||||
|
||||
bool get_update_download_status()
|
||||
{
|
||||
return update_data.access<bool>([](update_data_t& data_)
|
||||
{
|
||||
return data_.download.success;
|
||||
});
|
||||
}
|
||||
|
||||
bool is_update_available()
|
||||
{
|
||||
return update_data.access<bool>([](update_data_t& data_)
|
||||
{
|
||||
return data_.required_files.size() > 0;
|
||||
});
|
||||
}
|
||||
|
||||
bool is_restart_required()
|
||||
{
|
||||
return update_data.access<bool>([](update_data_t& data_)
|
||||
{
|
||||
return data_.restart_required;
|
||||
});
|
||||
}
|
||||
|
||||
std::string get_last_error()
|
||||
{
|
||||
return update_data.access<std::string>([](update_data_t& data_)
|
||||
{
|
||||
return data_.error;
|
||||
});
|
||||
}
|
||||
|
||||
std::string get_current_file()
|
||||
{
|
||||
return update_data.access<std::string>([](update_data_t& data_)
|
||||
{
|
||||
return data_.current_file;
|
||||
});
|
||||
}
|
||||
|
||||
void cancel_update()
|
||||
{
|
||||
#ifdef DEBUG
|
||||
printf("[Updater] Cancelling update\n");
|
||||
#endif
|
||||
|
||||
return update_data.access([](update_data_t& data_)
|
||||
{
|
||||
data_.cancelled = true;
|
||||
});
|
||||
}
|
||||
|
||||
void start_update_check()
|
||||
{
|
||||
cancel_update();
|
||||
reset_data();
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("[Updater] starting update check\n");
|
||||
#endif
|
||||
|
||||
scheduler::once([]()
|
||||
{
|
||||
const auto files_data = utils::http::get_data(MASTER + select(FILES_PATH, FILES_PATH_DEV));
|
||||
|
||||
if (is_update_cancelled())
|
||||
{
|
||||
reset_data();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!files_data.has_value())
|
||||
{
|
||||
set_update_check_status(true, false, ERR_UPDATE_CHECK_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
rapidjson::Document j;
|
||||
j.Parse(files_data.value().data());
|
||||
|
||||
if (!j.IsArray())
|
||||
{
|
||||
set_update_check_status(true, false, ERR_UPDATE_CHECK_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::string> required_files;
|
||||
|
||||
const auto files = j.GetArray();
|
||||
for (const auto& file : files)
|
||||
{
|
||||
if (!file.IsArray() || file.Size() != 3 || !file[0].IsString() || !file[2].IsString())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto name = file[0].GetString();
|
||||
const auto sha = file[2].GetString();
|
||||
|
||||
if (!check_file(name, sha))
|
||||
{
|
||||
if (get_binary_name() == name)
|
||||
{
|
||||
update_data.access([](update_data_t& data_)
|
||||
{
|
||||
data_.restart_required = true;
|
||||
});
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("[Updater] need file %s\n", name);
|
||||
#endif
|
||||
|
||||
required_files.push_back(name);
|
||||
}
|
||||
}
|
||||
|
||||
update_data.access([&required_files](update_data_t& data_)
|
||||
{
|
||||
data_.check.done = true;
|
||||
data_.check.success = true;
|
||||
data_.required_files = required_files;
|
||||
});
|
||||
}, scheduler::pipeline::async);
|
||||
}
|
||||
|
||||
void start_update_download()
|
||||
{
|
||||
#ifdef DEBUG
|
||||
printf("[Updater] starting update download\n");
|
||||
#endif
|
||||
|
||||
if (!is_update_check_done() || !get_update_check_status() || is_update_cancelled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler::once([]()
|
||||
{
|
||||
const auto required_files = update_data.access<std::vector<std::string>>([](update_data_t& data_)
|
||||
{
|
||||
return data_.required_files;
|
||||
});
|
||||
|
||||
std::vector<file_data> downloads;
|
||||
|
||||
for (const auto& file : required_files)
|
||||
{
|
||||
update_data.access([file](update_data_t& data_)
|
||||
{
|
||||
data_.current_file = file;
|
||||
});
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("[Updater] downloading file %s\n", file.data());
|
||||
#endif
|
||||
|
||||
const auto data = download_file(file);
|
||||
|
||||
if (is_update_cancelled())
|
||||
{
|
||||
reset_data();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data.has_value())
|
||||
{
|
||||
set_update_download_status(true, false, ERR_DOWNLOAD_FAIL + file);
|
||||
return;
|
||||
}
|
||||
|
||||
downloads.push_back({file, data.value()});
|
||||
}
|
||||
|
||||
for (const auto& download : downloads)
|
||||
{
|
||||
if (!write_file(download.name, download.data))
|
||||
{
|
||||
set_update_download_status(true, false, ERR_WRITE_FAIL + download.name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
set_update_download_status(true, true);
|
||||
}, scheduler::pipeline::async);
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
delete_old_file();
|
||||
cl_auto_update = dvars::register_bool("cg_auto_update", true, game::DVAR_FLAG_SAVED, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(updater::component)
|
26
src/client/component/updater.hpp
Normal file
26
src/client/component/updater.hpp
Normal file
@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
namespace updater
|
||||
{
|
||||
void relaunch();
|
||||
|
||||
void set_has_tried_update(bool tried);
|
||||
bool get_has_tried_update();
|
||||
bool auto_updates_enabled();
|
||||
|
||||
bool is_update_available();
|
||||
bool is_update_check_done();
|
||||
bool get_update_check_status();
|
||||
|
||||
bool is_update_download_done();
|
||||
bool get_update_download_status();
|
||||
|
||||
bool is_restart_required();
|
||||
|
||||
std::string get_last_error();
|
||||
std::string get_current_file();
|
||||
|
||||
void start_update_check();
|
||||
void start_update_download();
|
||||
void cancel_update();
|
||||
}
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include "../../../component/ui_scripting.hpp"
|
||||
#include "../../../component/command.hpp"
|
||||
#include "../../../component/updater.hpp"
|
||||
|
||||
#include "component/game_console.hpp"
|
||||
#include "component/scheduler.hpp"
|
||||
@ -141,12 +142,35 @@ namespace ui_scripting::lua
|
||||
state["LUI"] = state["luiglobals"]["LUI"];
|
||||
state["Engine"] = state["luiglobals"]["Engine"];
|
||||
state["Game"] = state["luiglobals"]["Game"];
|
||||
|
||||
auto updater_table = sol::table::create(state.lua_state());
|
||||
|
||||
updater_table["relaunch"] = updater::relaunch;
|
||||
|
||||
updater_table["sethastriedupdate"] = updater::set_has_tried_update;
|
||||
updater_table["gethastriedupdate"] = updater::get_has_tried_update;
|
||||
updater_table["autoupdatesenabled"] = updater::auto_updates_enabled;
|
||||
|
||||
updater_table["startupdatecheck"] = updater::start_update_check;
|
||||
updater_table["isupdatecheckdone"] = updater::is_update_check_done;
|
||||
updater_table["getupdatecheckstatus"] = updater::get_update_check_status;
|
||||
updater_table["isupdateavailable"] = updater::is_update_available;
|
||||
|
||||
updater_table["startupdatedownload"] = updater::start_update_download;
|
||||
updater_table["isupdatedownloaddone"] = updater::is_update_download_done;
|
||||
updater_table["getupdatedownloadstatus"] = updater::get_update_download_status;
|
||||
updater_table["cancelupdate"] = updater::cancel_update;
|
||||
updater_table["isrestartrequired"] = updater::is_restart_required;
|
||||
|
||||
updater_table["getlasterror"] = updater::get_last_error;
|
||||
updater_table["getcurrentfile"] = updater::get_current_file;
|
||||
|
||||
state["updater"] = updater_table;
|
||||
}
|
||||
}
|
||||
|
||||
context::context(std::string folder)
|
||||
: folder_(std::move(folder))
|
||||
, scheduler_(state_)
|
||||
context::context(std::string data, script_type type)
|
||||
: scheduler_(state_)
|
||||
{
|
||||
this->state_.open_libraries(sol::lib::base,
|
||||
sol::lib::package,
|
||||
@ -156,27 +180,37 @@ namespace ui_scripting::lua
|
||||
sol::lib::math,
|
||||
sol::lib::table);
|
||||
|
||||
this->state_["include"] = [this](const std::string& file)
|
||||
{
|
||||
this->load_script(file);
|
||||
};
|
||||
|
||||
sol::function old_require = this->state_["require"];
|
||||
auto base_path = utils::string::replace(this->folder_, "/", ".") + ".";
|
||||
this->state_["require"] = [base_path, old_require](const std::string& path)
|
||||
{
|
||||
return old_require(base_path + path);
|
||||
};
|
||||
|
||||
this->state_["scriptdir"] = [this]()
|
||||
{
|
||||
return this->folder_;
|
||||
};
|
||||
|
||||
setup_types(this->state_, this->scheduler_);
|
||||
|
||||
printf("Loading ui script '%s'\n", this->folder_.data());
|
||||
this->load_script("__init__");
|
||||
if (type == script_type::file)
|
||||
{
|
||||
this->folder_ = data;
|
||||
|
||||
this->state_["include"] = [this](const std::string& file)
|
||||
{
|
||||
this->load_script(file);
|
||||
};
|
||||
|
||||
sol::function old_require = this->state_["require"];
|
||||
auto base_path = utils::string::replace(this->folder_, "/", ".") + ".";
|
||||
this->state_["require"] = [base_path, old_require](const std::string& path)
|
||||
{
|
||||
return old_require(base_path + path);
|
||||
};
|
||||
|
||||
this->state_["scriptdir"] = [this]()
|
||||
{
|
||||
return this->folder_;
|
||||
};
|
||||
|
||||
printf("Loading ui script '%s'\n", this->folder_.data());
|
||||
this->load_script("__init__");
|
||||
}
|
||||
|
||||
if (type == script_type::code)
|
||||
{
|
||||
handle_error(this->state_.safe_script(data, &sol::script_pass_on_error));
|
||||
}
|
||||
}
|
||||
|
||||
context::~context()
|
||||
|
@ -10,10 +10,16 @@
|
||||
|
||||
namespace ui_scripting::lua
|
||||
{
|
||||
enum script_type
|
||||
{
|
||||
file,
|
||||
code
|
||||
};
|
||||
|
||||
class context
|
||||
{
|
||||
public:
|
||||
context(std::string folder);
|
||||
context(std::string folder, script_type type);
|
||||
~context();
|
||||
|
||||
context(context&&) noexcept = delete;
|
||||
|
@ -11,6 +11,9 @@ namespace ui_scripting::lua::engine
|
||||
{
|
||||
namespace
|
||||
{
|
||||
const auto lui_common = utils::nt::load_resource(LUI_COMMON);
|
||||
const auto lui_updater = utils::nt::load_resource(LUI_UPDATER);
|
||||
|
||||
auto& get_scripts()
|
||||
{
|
||||
static std::vector<std::unique_ptr<context>> scripts{};
|
||||
@ -30,16 +33,25 @@ namespace ui_scripting::lua::engine
|
||||
{
|
||||
if (std::filesystem::is_directory(script) && utils::io::file_exists(script + "/__init__.lua"))
|
||||
{
|
||||
get_scripts().push_back(std::make_unique<context>(script));
|
||||
get_scripts().push_back(std::make_unique<context>(script, script_type::file));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void load_code(const std::string& code)
|
||||
{
|
||||
get_scripts().push_back(std::make_unique<context>(code, script_type::code));
|
||||
}
|
||||
}
|
||||
|
||||
void start()
|
||||
{
|
||||
clear_converted_functions();
|
||||
get_scripts().clear();
|
||||
|
||||
load_code(lui_common);
|
||||
load_code(lui_updater);
|
||||
|
||||
load_scripts(game_module::get_host_module().get_folder() + "/data/ui_scripts/");
|
||||
load_scripts("h1-mod/ui_scripts/");
|
||||
load_scripts("data/ui_scripts/");
|
||||
|
@ -15,3 +15,6 @@
|
||||
#define RUNNER 307
|
||||
|
||||
#define ICON_IMAGE 308
|
||||
|
||||
#define LUI_COMMON 309
|
||||
#define LUI_UPDATER 310
|
||||
|
@ -118,6 +118,9 @@ RUNNER RCDATA "../../build/bin/x64/Release/runner.exe"
|
||||
|
||||
ICON_IMAGE RCDATA "resources/icon.png"
|
||||
|
||||
LUI_COMMON RCDATA "resources/ui_scripts/common.lua"
|
||||
LUI_UPDATER RCDATA "resources/ui_scripts/updater.lua"
|
||||
|
||||
#endif // English (United States) resources
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
164
src/client/resources/ui_scripts/common.lua
Normal file
164
src/client/resources/ui_scripts/common.lua
Normal file
@ -0,0 +1,164 @@
|
||||
menucallbacks = {}
|
||||
originalmenus = {}
|
||||
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.FlowManager.RequestLeaveMenu(args[1])
|
||||
end
|
||||
})
|
||||
|
||||
local listchildren = popup:getChildById("LUIHorizontalList"):getchildren()
|
||||
local children = listchildren[2]:getchildren()
|
||||
popup.text = children[2]
|
||||
|
||||
stack = {
|
||||
ret = popup
|
||||
}
|
||||
|
||||
return popup
|
||||
end
|
||||
|
||||
LUI.MenuBuilder.m_types_build["generic_yes_no_popup_"] = function()
|
||||
local callback = stack.callback
|
||||
local popup = LUI.MenuBuilder.BuildRegisteredType("generic_yesno_popup", {
|
||||
popup_title = stack.title,
|
||||
message_text = stack.text,
|
||||
yes_action = function()
|
||||
callback(true)
|
||||
end,
|
||||
no_action = function()
|
||||
callback(false)
|
||||
end
|
||||
})
|
||||
|
||||
stack = {
|
||||
ret = popup
|
||||
}
|
||||
|
||||
return popup
|
||||
end
|
||||
|
||||
LUI.MenuBuilder.m_types_build["generic_confirmation_popup_"] = function()
|
||||
local popup = LUI.MenuBuilder.BuildRegisteredType( "generic_confirmation_popup", {
|
||||
cancel_will_close = false,
|
||||
popup_title = stack.title,
|
||||
message_text = stack.text,
|
||||
button_text = stack.buttontext,
|
||||
confirmation_action = stack.callback
|
||||
})
|
||||
|
||||
stack = {
|
||||
ret = popup
|
||||
}
|
||||
|
||||
return stack.ret
|
||||
end
|
||||
|
||||
LUI.onmenuopen = function(name, callback)
|
||||
if (not LUI.MenuBuilder.m_types_build[name]) then
|
||||
return
|
||||
end
|
||||
|
||||
if (not menucallbacks[name]) then
|
||||
menucallbacks[name] = {}
|
||||
end
|
||||
|
||||
table.insert(menucallbacks[name], callback)
|
||||
|
||||
if (not originalmenus[name]) then
|
||||
originalmenus[name] = LUI.MenuBuilder.m_types_build[name]
|
||||
LUI.MenuBuilder.m_types_build[name] = function(...)
|
||||
local args = {...}
|
||||
local menu = originalmenus[name](table.unpack(args))
|
||||
|
||||
for k, v in luiglobals.next, 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
|
||||
|
||||
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.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
|
||||
|
||||
LUI.confirmationpopup = function(data)
|
||||
for k, v in luiglobals.next, data do
|
||||
stack[k] = v
|
||||
end
|
||||
LUI.FlowManager.RequestPopupMenu(nil, "generic_confirmation_popup_")
|
||||
return stack.ret
|
||||
end
|
||||
|
||||
function userdata_:getchildren()
|
||||
local children = {}
|
||||
local first = self:getFirstChild()
|
||||
|
||||
while (first) do
|
||||
table.insert(children, first)
|
||||
first = first:getNextSibling()
|
||||
end
|
||||
|
||||
return children
|
||||
end
|
156
src/client/resources/ui_scripts/updater.lua
Normal file
156
src/client/resources/ui_scripts/updater.lua
Normal file
@ -0,0 +1,156 @@
|
||||
updatecancelled = false
|
||||
taskinterval = 100
|
||||
|
||||
function startupdatecheck(popup, autoclose)
|
||||
updatecancelled = false
|
||||
|
||||
local callback = function()
|
||||
if (not updater.getupdatecheckstatus()) then
|
||||
if (autoclose) then
|
||||
LUI.FlowManager.RequestLeaveMenu(popup)
|
||||
return
|
||||
end
|
||||
|
||||
popup.text:setText("Error: " .. updater.getlasterror())
|
||||
return
|
||||
end
|
||||
|
||||
if (not updater.isupdateavailable()) then
|
||||
if (autoclose) then
|
||||
LUI.FlowManager.RequestLeaveMenu(popup)
|
||||
return
|
||||
end
|
||||
|
||||
popup.text:setText("No updates available")
|
||||
return
|
||||
end
|
||||
|
||||
LUI.yesnopopup({
|
||||
title = "NOTICE",
|
||||
text = "An update is available, proceed with installation?",
|
||||
callback = function(result)
|
||||
if (result) then
|
||||
startupdatedownload(popup, autoclose)
|
||||
else
|
||||
LUI.FlowManager.RequestLeaveMenu(popup)
|
||||
end
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
updater.startupdatecheck()
|
||||
createtask({
|
||||
done = updater.isupdatecheckdone,
|
||||
cancelled = isupdatecancelled,
|
||||
callback = callback,
|
||||
interval = taskinterval
|
||||
})
|
||||
end
|
||||
|
||||
function startupdatedownload(popup, autoclose)
|
||||
updater.startupdatedownload()
|
||||
|
||||
local textupdate = nil
|
||||
local previousfile = nil
|
||||
textupdate = game:oninterval(function()
|
||||
local file = updater.getcurrentfile()
|
||||
if (file == previousfile) then
|
||||
return
|
||||
end
|
||||
|
||||
file = previousfile
|
||||
popup.text:setText("Downloading file " .. updater.getcurrentfile() .. "...")
|
||||
end, 10)
|
||||
|
||||
local callback = function()
|
||||
textupdate:clear()
|
||||
|
||||
if (not updater.getupdatedownloadstatus()) then
|
||||
if (autoclose) then
|
||||
LUI.FlowManager.RequestLeaveMenu(popup)
|
||||
return
|
||||
end
|
||||
|
||||
popup.text:setText("Error: " .. updater.getlasterror())
|
||||
return
|
||||
end
|
||||
|
||||
popup.text:setText("Update successful")
|
||||
|
||||
if (updater.isrestartrequired()) then
|
||||
LUI.confirmationpopup({
|
||||
title = "RESTART REQUIRED",
|
||||
text = "Update requires restart",
|
||||
buttontext = "RESTART",
|
||||
callback = function()
|
||||
updater.relaunch()
|
||||
end
|
||||
})--
|
||||
end
|
||||
|
||||
if (autoclose) then
|
||||
LUI.FlowManager.RequestLeaveMenu(popup)
|
||||
end
|
||||
end
|
||||
|
||||
createtask({
|
||||
done = updater.isupdatedownloaddone,
|
||||
cancelled = isupdatecancelled,
|
||||
callback = callback,
|
||||
interval = taskinterval
|
||||
})
|
||||
end
|
||||
|
||||
function updaterpopup(oncancel)
|
||||
return LUI.openpopupmenu("generic_waiting_popup_", {
|
||||
oncancel = oncancel,
|
||||
withcancel = true,
|
||||
text = "Checking for updates..."
|
||||
})
|
||||
end
|
||||
|
||||
function createtask(data)
|
||||
local interval = nil
|
||||
interval = game:oninterval(function()
|
||||
if (data.cancelled()) then
|
||||
interval:clear()
|
||||
return
|
||||
end
|
||||
|
||||
if (data.done()) then
|
||||
interval:clear()
|
||||
data.callback()
|
||||
end
|
||||
end, data.interval)
|
||||
return interval
|
||||
end
|
||||
|
||||
function isupdatecancelled()
|
||||
return updatecancelled
|
||||
end
|
||||
|
||||
function tryupdate(autoclose)
|
||||
updatecancelled = false
|
||||
local popup = updaterpopup(function()
|
||||
updater.cancelupdate()
|
||||
updatecancelled = true
|
||||
end)
|
||||
|
||||
startupdatecheck(popup, autoclose)
|
||||
end
|
||||
|
||||
function tryautoupdate()
|
||||
if (not updater.autoupdatesenabled()) then
|
||||
return
|
||||
end
|
||||
|
||||
if (not updater.gethastriedupdate()) then
|
||||
game:ontimeout(function()
|
||||
updater.sethastriedupdate(true)
|
||||
tryupdate(true)
|
||||
end, 100)
|
||||
end
|
||||
end
|
||||
|
||||
LUI.onmenuopen("mp_main_menu", tryautoupdate)
|
||||
LUI.onmenuopen("main_lockout", tryautoupdate)
|
Loading…
Reference in New Issue
Block a user