New updater
This commit is contained in:
parent
2245fd20c1
commit
0b50ac734d
418
src/client/component/updater.cpp
Normal file
418
src/client/component/updater.cpp
Normal file
@ -0,0 +1,418 @@
|
|||||||
|
#include <std_include.hpp>
|
||||||
|
#include "loader/component_loader.hpp"
|
||||||
|
|
||||||
|
#include "scheduler.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/"
|
||||||
|
|
||||||
|
#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 "
|
||||||
|
|
||||||
|
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 select(const std::string& main, const std::string& develop)
|
||||||
|
{
|
||||||
|
if (GIT_BRANCH == "develop"s)
|
||||||
|
{
|
||||||
|
return develop;
|
||||||
|
}
|
||||||
|
|
||||||
|
return main;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_data_path()
|
||||||
|
{
|
||||||
|
if (GIT_BRANCH == "develop"s)
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_binary_name()
|
||||||
|
{
|
||||||
|
static const auto name = load_binary_name();
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_time_str()
|
||||||
|
{
|
||||||
|
return utils::string::va("%i", uint32_t(time(nullptr)));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> download_file(const std::string& name)
|
||||||
|
{
|
||||||
|
return utils::http::get_data(MASTER + select(DATA_PATH, DATA_PATH_DEV) + name + "?" + get_time_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils::io::write_file(name, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void delete_old_file()
|
||||||
|
{
|
||||||
|
utils::io::remove_file(get_binary_name() + ".old");
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset_data()
|
||||||
|
{
|
||||||
|
update_data.access([](update_data_t& data_)
|
||||||
|
{
|
||||||
|
data_ = {};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void relaunch()
|
||||||
|
{
|
||||||
|
utils::nt::relaunch_self("-singleplayer");
|
||||||
|
utils::nt::terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
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) + "?" + get_time_str());
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
@ -10,6 +10,8 @@
|
|||||||
#include "../../../component/scripting.hpp"
|
#include "../../../component/scripting.hpp"
|
||||||
#include "../../../component/command.hpp"
|
#include "../../../component/command.hpp"
|
||||||
#include "../../../component/fastfiles.hpp"
|
#include "../../../component/fastfiles.hpp"
|
||||||
|
#include "../../../component/updater.hpp"
|
||||||
|
#include "../../../component/localized_strings.hpp"
|
||||||
|
|
||||||
#include "component/game_console.hpp"
|
#include "component/game_console.hpp"
|
||||||
#include "component/scheduler.hpp"
|
#include "component/scheduler.hpp"
|
||||||
@ -25,8 +27,6 @@ namespace ui_scripting::lua
|
|||||||
{
|
{
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
const auto json_script = utils::nt::load_resource(LUA_JSON_SCRIPT);
|
|
||||||
|
|
||||||
scripting::script_value script_convert(const sol::lua_value& value)
|
scripting::script_value script_convert(const sol::lua_value& value)
|
||||||
{
|
{
|
||||||
if (value.is<int>())
|
if (value.is<int>())
|
||||||
@ -95,13 +95,6 @@ namespace ui_scripting::lua
|
|||||||
state["io"]["readfile"] = static_cast<std::string(*)(const std::string&)>(utils::io::read_file);
|
state["io"]["readfile"] = static_cast<std::string(*)(const std::string&)>(utils::io::read_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup_json(sol::state& state)
|
|
||||||
{
|
|
||||||
const auto json = state.safe_script(json_script, &sol::script_pass_on_error);
|
|
||||||
handle_error(json);
|
|
||||||
state["json"] = json;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup_vector_type(sol::state& state)
|
void setup_vector_type(sol::state& state)
|
||||||
{
|
{
|
||||||
auto vector_type = state.new_usertype<scripting::vector>("vector", sol::constructors<scripting::vector(float, float, float)>());
|
auto vector_type = state.new_usertype<scripting::vector>("vector", sol::constructors<scripting::vector(float, float, float)>());
|
||||||
@ -435,121 +428,10 @@ namespace ui_scripting::lua
|
|||||||
return ::game::mod_folder;
|
return ::game::mod_folder;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int request_id{};
|
game_type["addlocalizedstring"] = [](const game&, const std::string& string,
|
||||||
game_type["httpget"] = [](const game&, const std::string& url)
|
const std::string& value)
|
||||||
{
|
{
|
||||||
const auto id = request_id++;
|
localized_strings::override(string, value);
|
||||||
::scheduler::once([url, id]()
|
|
||||||
{
|
|
||||||
const auto result = utils::http::get_data(url);
|
|
||||||
::scheduler::once([result, id]
|
|
||||||
{
|
|
||||||
event event;
|
|
||||||
event.name = "http_request_done";
|
|
||||||
|
|
||||||
if (result.has_value())
|
|
||||||
{
|
|
||||||
event.arguments = {id, true, result.value()};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
event.arguments = {id, false};
|
|
||||||
}
|
|
||||||
|
|
||||||
notify(event);
|
|
||||||
}, ::scheduler::pipeline::lui);
|
|
||||||
}, ::scheduler::pipeline::async);
|
|
||||||
return id;
|
|
||||||
};
|
|
||||||
|
|
||||||
game_type["httpgettofile"] = [](const game&, const std::string& url,
|
|
||||||
const std::string& dest)
|
|
||||||
{
|
|
||||||
const auto id = request_id++;
|
|
||||||
::scheduler::once([url, id, dest]()
|
|
||||||
{
|
|
||||||
auto last_report = std::chrono::high_resolution_clock::now();
|
|
||||||
const auto result = utils::http::get_data(url, {}, [&last_report, id](size_t progress, size_t total, size_t speed)
|
|
||||||
{
|
|
||||||
const auto now = std::chrono::high_resolution_clock::now();
|
|
||||||
if (now - last_report < 100ms && progress < total)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
last_report = now;
|
|
||||||
|
|
||||||
::scheduler::once([id, progress, total, speed]
|
|
||||||
{
|
|
||||||
event event;
|
|
||||||
event.name = "http_request_progress";
|
|
||||||
event.arguments = {
|
|
||||||
id,
|
|
||||||
static_cast<int>(progress),
|
|
||||||
static_cast<int>(total),
|
|
||||||
static_cast<int>(speed)
|
|
||||||
};
|
|
||||||
|
|
||||||
notify(event);
|
|
||||||
}, ::scheduler::pipeline::lui);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.has_value())
|
|
||||||
{
|
|
||||||
const auto write = utils::io::write_file(dest, result.value(), false);
|
|
||||||
::scheduler::once([result, id, write]()
|
|
||||||
{
|
|
||||||
event event;
|
|
||||||
event.name = "http_request_done";
|
|
||||||
event.arguments = {id, true, write};
|
|
||||||
|
|
||||||
notify(event);
|
|
||||||
}, ::scheduler::pipeline::lui);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
::scheduler::once([result, id]()
|
|
||||||
{
|
|
||||||
event event;
|
|
||||||
event.name = "http_request_done";
|
|
||||||
event.arguments = {id, false};
|
|
||||||
|
|
||||||
notify(event);
|
|
||||||
}, ::scheduler::pipeline::lui);
|
|
||||||
}
|
|
||||||
}, ::scheduler::pipeline::async);
|
|
||||||
return id;
|
|
||||||
};
|
|
||||||
|
|
||||||
game_type["sha"] = [](const game&, const std::string& data)
|
|
||||||
{
|
|
||||||
return utils::string::to_upper(utils::cryptography::sha1::compute(data, true));
|
|
||||||
};
|
|
||||||
|
|
||||||
game_type["environment"] = [](const game&)
|
|
||||||
{
|
|
||||||
return GIT_BRANCH;
|
|
||||||
};
|
|
||||||
|
|
||||||
game_type["binaryname"] = [](const game&)
|
|
||||||
{
|
|
||||||
utils::nt::library self;
|
|
||||||
return self.get_name();
|
|
||||||
};
|
|
||||||
|
|
||||||
game_type["relaunch"] = [](const game&)
|
|
||||||
{
|
|
||||||
utils::nt::relaunch_self("-singleplayer");
|
|
||||||
utils::nt::terminate();
|
|
||||||
};
|
|
||||||
|
|
||||||
game_type["isdebugbuild"] = [](const game&)
|
|
||||||
{
|
|
||||||
#ifdef DEBUG
|
|
||||||
return true;
|
|
||||||
#else
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct player
|
struct player
|
||||||
@ -677,6 +559,30 @@ namespace ui_scripting::lua
|
|||||||
state["LUI"] = state["luiglobals"]["LUI"];
|
state["LUI"] = state["luiglobals"]["LUI"];
|
||||||
state["Engine"] = state["luiglobals"]["Engine"];
|
state["Engine"] = state["luiglobals"]["Engine"];
|
||||||
state["Game"] = state["luiglobals"]["Game"];
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -694,7 +600,6 @@ namespace ui_scripting::lua
|
|||||||
sol::lib::table);
|
sol::lib::table);
|
||||||
|
|
||||||
setup_io(this->state_);
|
setup_io(this->state_);
|
||||||
setup_json(this->state_);
|
|
||||||
setup_vector_type(this->state_);
|
setup_vector_type(this->state_);
|
||||||
setup_game_type(this->state_, this->event_handler_, this->scheduler_);
|
setup_game_type(this->state_, this->event_handler_, this->scheduler_);
|
||||||
setup_lui_types(this->state_, this->event_handler_, this->scheduler_);
|
setup_lui_types(this->state_, this->event_handler_, this->scheduler_);
|
||||||
|
@ -14,7 +14,8 @@ namespace ui_scripting::lua::engine
|
|||||||
{
|
{
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
const auto updater_script = utils::nt::load_resource(LUI_UPDATER_MENU);
|
const auto lui_common = utils::nt::load_resource(LUI_COMMON);
|
||||||
|
const auto lui_updater = utils::nt::load_resource(LUI_UPDATER);
|
||||||
|
|
||||||
void handle_key_event(const int key, const int down)
|
void handle_key_event(const int key, const int down)
|
||||||
{
|
{
|
||||||
@ -72,7 +73,8 @@ namespace ui_scripting::lua::engine
|
|||||||
clear_converted_functions();
|
clear_converted_functions();
|
||||||
get_scripts().clear();
|
get_scripts().clear();
|
||||||
|
|
||||||
load_code(updater_script);
|
load_code(lui_common);
|
||||||
|
// load_code(lui_updater);
|
||||||
|
|
||||||
for (const auto& path : filesystem::get_search_paths())
|
for (const auto& path : filesystem::get_search_paths())
|
||||||
{
|
{
|
||||||
|
@ -2,23 +2,11 @@
|
|||||||
|
|
||||||
#define ID_ICON 102
|
#define ID_ICON 102
|
||||||
|
|
||||||
#define IMAGE_SPLASH 300
|
#define MENU_MAIN 300
|
||||||
#define IMAGE_LOGO 301
|
|
||||||
|
|
||||||
#define DW_ENTITLEMENT_CONFIG 302
|
#define TLS_DLL 301
|
||||||
#define DW_SOCIAL_CONFIG 303
|
|
||||||
#define DW_MM_CONFIG 304
|
|
||||||
#define DW_LOOT_CONFIG 305
|
|
||||||
#define DW_STORE_CONFIG 306
|
|
||||||
#define DW_MOTD 307
|
|
||||||
#define DW_FASTFILE 308
|
|
||||||
#define DW_PLAYLISTS 309
|
|
||||||
|
|
||||||
#define MENU_MAIN 310
|
#define ICON_IMAGE 302
|
||||||
|
|
||||||
#define TLS_DLL 311
|
#define LUI_COMMON 303
|
||||||
|
#define LUI_UPDATER 304
|
||||||
#define ICON_IMAGE 312
|
|
||||||
|
|
||||||
#define LUA_JSON_SCRIPT 313
|
|
||||||
#define LUI_UPDATER_MENU 314
|
|
||||||
|
@ -97,8 +97,8 @@ ID_ICON ICON "resources/icon.ico"
|
|||||||
|
|
||||||
MENU_MAIN RCDATA "resources/main.html"
|
MENU_MAIN RCDATA "resources/main.html"
|
||||||
|
|
||||||
LUA_JSON_SCRIPT RCDATA "resources/json.lua"
|
LUI_COMMON RCDATA "resources/ui_scripts/common.lua"
|
||||||
LUI_UPDATER_MENU RCDATA "resources/updater.lua"
|
LUI_UPDATER RCDATA "resources/ui_scripts/updater.lua"
|
||||||
|
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
TLS_DLL RCDATA "../../build/bin/x64/Debug/tlsdll.dll"
|
TLS_DLL RCDATA "../../build/bin/x64/Debug/tlsdll.dll"
|
||||||
|
@ -1,388 +0,0 @@
|
|||||||
--
|
|
||||||
-- json.lua
|
|
||||||
--
|
|
||||||
-- Copyright (c) 2020 rxi
|
|
||||||
--
|
|
||||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
-- this software and associated documentation files (the "Software"), to deal in
|
|
||||||
-- the Software without restriction, including without limitation the rights to
|
|
||||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
||||||
-- of the Software, and to permit persons to whom the Software is furnished to do
|
|
||||||
-- so, subject to the following conditions:
|
|
||||||
--
|
|
||||||
-- The above copyright notice and this permission notice shall be included in all
|
|
||||||
-- copies or substantial portions of the Software.
|
|
||||||
--
|
|
||||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
-- SOFTWARE.
|
|
||||||
--
|
|
||||||
|
|
||||||
local json = { _version = "0.1.2" }
|
|
||||||
|
|
||||||
-------------------------------------------------------------------------------
|
|
||||||
-- Encode
|
|
||||||
-------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
local encode
|
|
||||||
|
|
||||||
local escape_char_map = {
|
|
||||||
[ "\\" ] = "\\",
|
|
||||||
[ "\"" ] = "\"",
|
|
||||||
[ "\b" ] = "b",
|
|
||||||
[ "\f" ] = "f",
|
|
||||||
[ "\n" ] = "n",
|
|
||||||
[ "\r" ] = "r",
|
|
||||||
[ "\t" ] = "t",
|
|
||||||
}
|
|
||||||
|
|
||||||
local escape_char_map_inv = { [ "/" ] = "/" }
|
|
||||||
for k, v in pairs(escape_char_map) do
|
|
||||||
escape_char_map_inv[v] = k
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function escape_char(c)
|
|
||||||
return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function encode_nil(val)
|
|
||||||
return "null"
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function encode_table(val, stack)
|
|
||||||
local res = {}
|
|
||||||
stack = stack or {}
|
|
||||||
|
|
||||||
-- Circular reference?
|
|
||||||
if stack[val] then error("circular reference") end
|
|
||||||
|
|
||||||
stack[val] = true
|
|
||||||
|
|
||||||
if rawget(val, 1) ~= nil or next(val) == nil then
|
|
||||||
-- Treat as array -- check keys are valid and it is not sparse
|
|
||||||
local n = 0
|
|
||||||
for k in pairs(val) do
|
|
||||||
if type(k) ~= "number" then
|
|
||||||
error("invalid table: mixed or invalid key types")
|
|
||||||
end
|
|
||||||
n = n + 1
|
|
||||||
end
|
|
||||||
if n ~= #val then
|
|
||||||
error("invalid table: sparse array")
|
|
||||||
end
|
|
||||||
-- Encode
|
|
||||||
for i, v in ipairs(val) do
|
|
||||||
table.insert(res, encode(v, stack))
|
|
||||||
end
|
|
||||||
stack[val] = nil
|
|
||||||
return "[" .. table.concat(res, ",") .. "]"
|
|
||||||
|
|
||||||
else
|
|
||||||
-- Treat as an object
|
|
||||||
for k, v in pairs(val) do
|
|
||||||
if type(k) ~= "string" then
|
|
||||||
error("invalid table: mixed or invalid key types")
|
|
||||||
end
|
|
||||||
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
|
|
||||||
end
|
|
||||||
stack[val] = nil
|
|
||||||
return "{" .. table.concat(res, ",") .. "}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function encode_string(val)
|
|
||||||
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function encode_number(val)
|
|
||||||
-- Check for NaN, -inf and inf
|
|
||||||
if val ~= val or val <= -math.huge or val >= math.huge then
|
|
||||||
error("unexpected number value '" .. tostring(val) .. "'")
|
|
||||||
end
|
|
||||||
return string.format("%.14g", val)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local type_func_map = {
|
|
||||||
[ "nil" ] = encode_nil,
|
|
||||||
[ "table" ] = encode_table,
|
|
||||||
[ "string" ] = encode_string,
|
|
||||||
[ "number" ] = encode_number,
|
|
||||||
[ "boolean" ] = tostring,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
encode = function(val, stack)
|
|
||||||
local t = type(val)
|
|
||||||
local f = type_func_map[t]
|
|
||||||
if f then
|
|
||||||
return f(val, stack)
|
|
||||||
end
|
|
||||||
error("unexpected type '" .. t .. "'")
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function json.encode(val)
|
|
||||||
return ( encode(val) )
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-------------------------------------------------------------------------------
|
|
||||||
-- Decode
|
|
||||||
-------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
local parse
|
|
||||||
|
|
||||||
local function create_set(...)
|
|
||||||
local res = {}
|
|
||||||
for i = 1, select("#", ...) do
|
|
||||||
res[ select(i, ...) ] = true
|
|
||||||
end
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
local space_chars = create_set(" ", "\t", "\r", "\n")
|
|
||||||
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
|
|
||||||
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
|
|
||||||
local literals = create_set("true", "false", "null")
|
|
||||||
|
|
||||||
local literal_map = {
|
|
||||||
[ "true" ] = true,
|
|
||||||
[ "false" ] = false,
|
|
||||||
[ "null" ] = nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
local function next_char(str, idx, set, negate)
|
|
||||||
for i = idx, #str do
|
|
||||||
if set[str:sub(i, i)] ~= negate then
|
|
||||||
return i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return #str + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function decode_error(str, idx, msg)
|
|
||||||
local line_count = 1
|
|
||||||
local col_count = 1
|
|
||||||
for i = 1, idx - 1 do
|
|
||||||
col_count = col_count + 1
|
|
||||||
if str:sub(i, i) == "\n" then
|
|
||||||
line_count = line_count + 1
|
|
||||||
col_count = 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function codepoint_to_utf8(n)
|
|
||||||
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
|
|
||||||
local f = math.floor
|
|
||||||
if n <= 0x7f then
|
|
||||||
return string.char(n)
|
|
||||||
elseif n <= 0x7ff then
|
|
||||||
return string.char(f(n / 64) + 192, n % 64 + 128)
|
|
||||||
elseif n <= 0xffff then
|
|
||||||
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
|
|
||||||
elseif n <= 0x10ffff then
|
|
||||||
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
|
|
||||||
f(n % 4096 / 64) + 128, n % 64 + 128)
|
|
||||||
end
|
|
||||||
error( string.format("invalid unicode codepoint '%x'", n) )
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function parse_unicode_escape(s)
|
|
||||||
local n1 = tonumber( s:sub(1, 4), 16 )
|
|
||||||
local n2 = tonumber( s:sub(7, 10), 16 )
|
|
||||||
-- Surrogate pair?
|
|
||||||
if n2 then
|
|
||||||
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
|
|
||||||
else
|
|
||||||
return codepoint_to_utf8(n1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function parse_string(str, i)
|
|
||||||
local res = ""
|
|
||||||
local j = i + 1
|
|
||||||
local k = j
|
|
||||||
|
|
||||||
while j <= #str do
|
|
||||||
local x = str:byte(j)
|
|
||||||
|
|
||||||
if x < 32 then
|
|
||||||
decode_error(str, j, "control character in string")
|
|
||||||
|
|
||||||
elseif x == 92 then -- `\`: Escape
|
|
||||||
res = res .. str:sub(k, j - 1)
|
|
||||||
j = j + 1
|
|
||||||
local c = str:sub(j, j)
|
|
||||||
if c == "u" then
|
|
||||||
local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
|
|
||||||
or str:match("^%x%x%x%x", j + 1)
|
|
||||||
or decode_error(str, j - 1, "invalid unicode escape in string")
|
|
||||||
res = res .. parse_unicode_escape(hex)
|
|
||||||
j = j + #hex
|
|
||||||
else
|
|
||||||
if not escape_chars[c] then
|
|
||||||
decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
|
|
||||||
end
|
|
||||||
res = res .. escape_char_map_inv[c]
|
|
||||||
end
|
|
||||||
k = j + 1
|
|
||||||
|
|
||||||
elseif x == 34 then -- `"`: End of string
|
|
||||||
res = res .. str:sub(k, j - 1)
|
|
||||||
return res, j + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
j = j + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
decode_error(str, i, "expected closing quote for string")
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function parse_number(str, i)
|
|
||||||
local x = next_char(str, i, delim_chars)
|
|
||||||
local s = str:sub(i, x - 1)
|
|
||||||
local n = tonumber(s)
|
|
||||||
if not n then
|
|
||||||
decode_error(str, i, "invalid number '" .. s .. "'")
|
|
||||||
end
|
|
||||||
return n, x
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function parse_literal(str, i)
|
|
||||||
local x = next_char(str, i, delim_chars)
|
|
||||||
local word = str:sub(i, x - 1)
|
|
||||||
if not literals[word] then
|
|
||||||
decode_error(str, i, "invalid literal '" .. word .. "'")
|
|
||||||
end
|
|
||||||
return literal_map[word], x
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function parse_array(str, i)
|
|
||||||
local res = {}
|
|
||||||
local n = 1
|
|
||||||
i = i + 1
|
|
||||||
while 1 do
|
|
||||||
local x
|
|
||||||
i = next_char(str, i, space_chars, true)
|
|
||||||
-- Empty / end of array?
|
|
||||||
if str:sub(i, i) == "]" then
|
|
||||||
i = i + 1
|
|
||||||
break
|
|
||||||
end
|
|
||||||
-- Read token
|
|
||||||
x, i = parse(str, i)
|
|
||||||
res[n] = x
|
|
||||||
n = n + 1
|
|
||||||
-- Next token
|
|
||||||
i = next_char(str, i, space_chars, true)
|
|
||||||
local chr = str:sub(i, i)
|
|
||||||
i = i + 1
|
|
||||||
if chr == "]" then break end
|
|
||||||
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
|
|
||||||
end
|
|
||||||
return res, i
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function parse_object(str, i)
|
|
||||||
local res = {}
|
|
||||||
i = i + 1
|
|
||||||
while 1 do
|
|
||||||
local key, val
|
|
||||||
i = next_char(str, i, space_chars, true)
|
|
||||||
-- Empty / end of object?
|
|
||||||
if str:sub(i, i) == "}" then
|
|
||||||
i = i + 1
|
|
||||||
break
|
|
||||||
end
|
|
||||||
-- Read key
|
|
||||||
if str:sub(i, i) ~= '"' then
|
|
||||||
decode_error(str, i, "expected string for key")
|
|
||||||
end
|
|
||||||
key, i = parse(str, i)
|
|
||||||
-- Read ':' delimiter
|
|
||||||
i = next_char(str, i, space_chars, true)
|
|
||||||
if str:sub(i, i) ~= ":" then
|
|
||||||
decode_error(str, i, "expected ':' after key")
|
|
||||||
end
|
|
||||||
i = next_char(str, i + 1, space_chars, true)
|
|
||||||
-- Read value
|
|
||||||
val, i = parse(str, i)
|
|
||||||
-- Set
|
|
||||||
res[key] = val
|
|
||||||
-- Next token
|
|
||||||
i = next_char(str, i, space_chars, true)
|
|
||||||
local chr = str:sub(i, i)
|
|
||||||
i = i + 1
|
|
||||||
if chr == "}" then break end
|
|
||||||
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
|
|
||||||
end
|
|
||||||
return res, i
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local char_func_map = {
|
|
||||||
[ '"' ] = parse_string,
|
|
||||||
[ "0" ] = parse_number,
|
|
||||||
[ "1" ] = parse_number,
|
|
||||||
[ "2" ] = parse_number,
|
|
||||||
[ "3" ] = parse_number,
|
|
||||||
[ "4" ] = parse_number,
|
|
||||||
[ "5" ] = parse_number,
|
|
||||||
[ "6" ] = parse_number,
|
|
||||||
[ "7" ] = parse_number,
|
|
||||||
[ "8" ] = parse_number,
|
|
||||||
[ "9" ] = parse_number,
|
|
||||||
[ "-" ] = parse_number,
|
|
||||||
[ "t" ] = parse_literal,
|
|
||||||
[ "f" ] = parse_literal,
|
|
||||||
[ "n" ] = parse_literal,
|
|
||||||
[ "[" ] = parse_array,
|
|
||||||
[ "{" ] = parse_object,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
parse = function(str, idx)
|
|
||||||
local chr = str:sub(idx, idx)
|
|
||||||
local f = char_func_map[chr]
|
|
||||||
if f then
|
|
||||||
return f(str, idx)
|
|
||||||
end
|
|
||||||
decode_error(str, idx, "unexpected character '" .. chr .. "'")
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function json.decode(str)
|
|
||||||
if type(str) ~= "string" then
|
|
||||||
error("expected argument of type string, got " .. type(str))
|
|
||||||
end
|
|
||||||
local res, idx = parse(str, next_char(str, 1, space_chars, true))
|
|
||||||
idx = next_char(str, idx, space_chars, true)
|
|
||||||
if idx <= #str then
|
|
||||||
decode_error(str, idx, "trailing garbage")
|
|
||||||
end
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
return json
|
|
162
src/client/resources/ui_scripts/common.lua
Normal file
162
src/client/resources/ui_scripts/common.lua
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
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
|
||||||
|
})
|
||||||
|
|
||||||
|
popup.text = popup:getchildren()[7]
|
||||||
|
|
||||||
|
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
|
164
src/client/resources/ui_scripts/updater.lua
Normal file
164
src/client/resources/ui_scripts/updater.lua
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
updatecancelled = false
|
||||||
|
taskinterval = 100
|
||||||
|
|
||||||
|
updater.cancelupdate()
|
||||||
|
|
||||||
|
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
|
||||||
|
})
|
||||||
|
else
|
||||||
|
if (LUI.mp_menus) then
|
||||||
|
Engine.Exec("lui_restart; lui_open mp_main_menu")
|
||||||
|
else
|
||||||
|
Engine.Exec("lui_restart")
|
||||||
|
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)
|
@ -1,411 +0,0 @@
|
|||||||
menucallbacks = {}
|
|
||||||
originalmenus = {}
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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:isdebugbuild() or not Engine.GetDvarBool("cg_autoUpdate")) then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if (game:sharedget("has_tried_updating") == "") then
|
|
||||||
game:ontimeout(function()
|
|
||||||
game:sharedset("has_tried_updating", "1")
|
|
||||||
tryupdate(true)
|
|
||||||
end, 0)
|
|
||||||
end
|
|
||||||
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 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.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 updaterpopup(oncancel)
|
|
||||||
return LUI.openpopupmenu("generic_waiting_popup_", {
|
|
||||||
oncancel = oncancel,
|
|
||||||
withcancel = true,
|
|
||||||
text = "Checking for updates..."
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
function deleteoldfile()
|
|
||||||
io.removefile(game:binaryname() .. ".old")
|
|
||||||
end
|
|
||||||
|
|
||||||
deleteoldfile()
|
|
||||||
|
|
||||||
function verifyfiles(files)
|
|
||||||
local needed = {}
|
|
||||||
local updatebinary = false
|
|
||||||
|
|
||||||
if (game:isdebugbuild()) then
|
|
||||||
return needed, updatebinary
|
|
||||||
end
|
|
||||||
|
|
||||||
local binaryname = game:binaryname()
|
|
||||||
|
|
||||||
for i = 1, #files do
|
|
||||||
local name = files[i][1]
|
|
||||||
|
|
||||||
if (io.fileexists(name) and game:sha(io.readfile(name)) == files[i][3]) then
|
|
||||||
goto continue
|
|
||||||
end
|
|
||||||
|
|
||||||
if (name == binaryname) then
|
|
||||||
updatebinary = true
|
|
||||||
end
|
|
||||||
|
|
||||||
table.insert(needed, files[i])
|
|
||||||
|
|
||||||
::continue::
|
|
||||||
end
|
|
||||||
|
|
||||||
return needed, updatebinary
|
|
||||||
end
|
|
||||||
|
|
||||||
local canceled = false
|
|
||||||
|
|
||||||
function downloadfiles(popup, files, callback)
|
|
||||||
deleteoldfile()
|
|
||||||
|
|
||||||
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 filename = files[index][1]
|
|
||||||
|
|
||||||
local url = "https://master.fed0001.xyz/" .. folder .. "/" .. filename .. "?" .. os.time()
|
|
||||||
text:setText(string.format("Downloading file [%i/%i]\n%s", index, #files, filename))
|
|
||||||
|
|
||||||
if (filename == game:binaryname()) then
|
|
||||||
io.movefile(filename, filename .. ".old")
|
|
||||||
end
|
|
||||||
|
|
||||||
httprequesttofile(url, filename, function(valid, success)
|
|
||||||
if (not valid) then
|
|
||||||
callback(false, "Invalid server response")
|
|
||||||
stop = true
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if (not success) then
|
|
||||||
callback(false, "Failed to write file " .. filename)
|
|
||||||
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"
|
|
||||||
local url = "https://master.fed0001.xyz/" .. file .. "?" .. os.time()
|
|
||||||
|
|
||||||
httprequest(url, 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, updatebinary = 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 (not result) then
|
|
||||||
text:setText("Update failed: " .. error)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if (updatebinary) then
|
|
||||||
LUI.confirmationpopup({
|
|
||||||
title = "RESTART REQUIRED",
|
|
||||||
text = "Update requires restart",
|
|
||||||
buttontext = "RESTART",
|
|
||||||
callback = function()
|
|
||||||
game:relaunch()
|
|
||||||
end
|
|
||||||
})
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if (result and #needed > 0) then
|
|
||||||
game:executecommand("lui_restart")
|
|
||||||
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
|
|
||||||
|
|
||||||
function httprequesttofile(url, dest, callback)
|
|
||||||
local request = game:httpgettofile(url, dest)
|
|
||||||
local listener = nil
|
|
||||||
listener = game:onnotify("http_request_done", function(id, valid, success)
|
|
||||||
if (id ~= request) then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
listener:clear()
|
|
||||||
callback(valid, success)
|
|
||||||
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
|
|
Loading…
x
Reference in New Issue
Block a user