diff --git a/blcrack/CMakeLists.txt b/blcrack/CMakeLists.txt index 59e2424db..ead3c3b66 100644 --- a/blcrack/CMakeLists.txt +++ b/blcrack/CMakeLists.txt @@ -17,6 +17,12 @@ endif () set(VCPKG_ROOT ${CMAKE_CURRENT_LIST_DIR}/vcpkgs) +function(set_output_dir out_dir) + string(TOUPPER ${CMAKE_BUILD_TYPE} build_type) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${build_type} ${out_dir} PARENT_SCOPE) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${build_type} ${out_dir} PARENT_SCOPE) +endfunction() + if (WIN32) include(platform/win32.cmake) elseif (LINUX) diff --git a/blcrack/http/updater.http b/blcrack/http/updater.http new file mode 100644 index 000000000..157f51c2e --- /dev/null +++ b/blcrack/http/updater.http @@ -0,0 +1,20 @@ +### +POST http://127.0.0.1:7541/files + +{ + "arch": "x86", + "file": "libtolua.so" +} + +### +POST http://127.0.0.1:7541/get_hash + +{ + "arch": "x86", + "file": "libtolua.so" +} + +### +POST http://127.0.0.1:7541/reload + +BlCrackUpdater \ No newline at end of file diff --git a/blcrack/installvcpkgs.bat b/blcrack/installvcpkgs.bat index 52a6d28d5..efd2db0e2 100644 --- a/blcrack/installvcpkgs.bat +++ b/blcrack/installvcpkgs.bat @@ -9,6 +9,12 @@ call :install_all_platforms jsoncpp call :install_all_platforms spdlog call :install_all_platforms cxxopts +call :install cpp-httplib x64-windows-static +call :install jsoncpp x64-windows-static +call :install spdlog x64-windows-static +call :install cxxopts x64-windows-static +call :install cryptopp x64-windows-static + goto :EOF :install_all_platforms diff --git a/blcrack/platform/android.cmake b/blcrack/platform/android.cmake index 9a41a26bf..774973c2a 100644 --- a/blcrack/platform/android.cmake +++ b/blcrack/platform/android.cmake @@ -1,5 +1,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/android.vcpkgs.cmake) +set_output_dir(${CMAKE_CURRENT_LIST_DIR}/../bin/${CMAKE_BUILD_TYPE}-Android-${CMAKE_ANDROID_ARCH_ABI}) + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../cracker) add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../logger) add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../patchelf) diff --git a/blcrack/platform/linux.cmake b/blcrack/platform/linux.cmake index ea2446b18..290301efa 100644 --- a/blcrack/platform/linux.cmake +++ b/blcrack/platform/linux.cmake @@ -1 +1,5 @@ -include(${CMAKE_CURRENT_LIST_DIR}/linux.vcpkgs.cmake) \ No newline at end of file +include(${CMAKE_CURRENT_LIST_DIR}/linux.vcpkgs.cmake) + +set_output_dir(${CMAKE_CURRENT_LIST_DIR}/../bin/${CMAKE_BUILD_TYPE}-Linux) + +add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../updater) \ No newline at end of file diff --git a/blcrack/platform/linux.vcpkgs.cmake b/blcrack/platform/linux.vcpkgs.cmake index e69de29bb..d8dfd7cf7 100644 --- a/blcrack/platform/linux.vcpkgs.cmake +++ b/blcrack/platform/linux.vcpkgs.cmake @@ -0,0 +1,19 @@ +set(VCPKGS_SHARE ${VCPKG_ROOT}/x64-linux-static/share) + +set(unofficial-brotli_DIR ${VCPKGS_SHARE}/unofficial-brotli) +set(httplib_DIR ${VCPKGS_SHARE}/httplib) +find_package(httplib REQUIRED) + +set(jsoncpp_DIR ${VCPKGS_SHARE}/jsoncpp) +find_package(jsoncpp CONFIG REQUIRED) + +set(fmt_DIR ${VCPKGS_SHARE}/fmt) +set(spdlog_DIR ${VCPKGS_SHARE}/spdlog) +find_package(spdlog CONFIG REQUIRED) +set(SPDLOG_DEFINES SPDLOG_COMPILED_LIB) + +set(cxxopts_DIR ${VCPKGS_SHARE}/cxxopts) +find_package(cxxopts CONFIG REQUIRED) + +set(cryptopp_DIR ${VCPKGS_SHARE}/cryptopp) +find_package(cryptopp CONFIG REQUIRED) \ No newline at end of file diff --git a/blcrack/platform/win32.cmake b/blcrack/platform/win32.cmake index 93948e220..722d37a5f 100644 --- a/blcrack/platform/win32.cmake +++ b/blcrack/platform/win32.cmake @@ -1 +1,5 @@ -include(${CMAKE_CURRENT_LIST_DIR}/win32.vcpkgs.cmake) \ No newline at end of file +include(${CMAKE_CURRENT_LIST_DIR}/win32.vcpkgs.cmake) + +set_output_dir(${CMAKE_CURRENT_LIST_DIR}/../bin/${CMAKE_BUILD_TYPE}-Windows) + +add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../updater) \ No newline at end of file diff --git a/blcrack/platform/win32.vcpkgs.cmake b/blcrack/platform/win32.vcpkgs.cmake index e69de29bb..9369ca5e9 100644 --- a/blcrack/platform/win32.vcpkgs.cmake +++ b/blcrack/platform/win32.vcpkgs.cmake @@ -0,0 +1,19 @@ +set(VCPKGS_SHARE ${VCPKG_ROOT}/x64-windows-static/share) + +set(unofficial-brotli_DIR ${VCPKGS_SHARE}/unofficial-brotli) +set(httplib_DIR ${VCPKGS_SHARE}/httplib) +find_package(httplib REQUIRED) + +set(jsoncpp_DIR ${VCPKGS_SHARE}/jsoncpp) +find_package(jsoncpp CONFIG REQUIRED) + +set(fmt_DIR ${VCPKGS_SHARE}/fmt) +set(spdlog_DIR ${VCPKGS_SHARE}/spdlog) +find_package(spdlog CONFIG REQUIRED) +set(SPDLOG_DEFINES SPDLOG_COMPILED_LIB) + +set(cxxopts_DIR ${VCPKGS_SHARE}/cxxopts) +find_package(cxxopts CONFIG REQUIRED) + +set(cryptopp_DIR ${VCPKGS_SHARE}/cryptopp) +find_package(cryptopp CONFIG REQUIRED) \ No newline at end of file diff --git a/blcrack/updater/CMakeLists.txt b/blcrack/updater/CMakeLists.txt new file mode 100644 index 000000000..72d25cacb --- /dev/null +++ b/blcrack/updater/CMakeLists.txt @@ -0,0 +1,23 @@ +set(UPDATER updater) + +file(GLOB_RECURSE UPDATER_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/*.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp +) + +add_executable(${UPDATER} ${UPDATER_SOURCES}) +target_link_libraries(${UPDATER} PRIVATE + httplib::httplib + spdlog::spdlog + JsonCpp::JsonCpp + cryptopp::cryptopp + cxxopts::cxxopts +) +target_compile_definitions(${UPDATER} PRIVATE ${SPDLOG_DEFINES} SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE) +if (WIN32) + if (DEBUG_MODE) + target_compile_options(${UPDATER} PRIVATE /MTd) + else () + target_compile_options(${UPDATER} PRIVATE /MT) + endif () +endif () \ No newline at end of file diff --git a/blcrack/updater/main.cpp b/blcrack/updater/main.cpp new file mode 100644 index 000000000..ca3d6e857 --- /dev/null +++ b/blcrack/updater/main.cpp @@ -0,0 +1,39 @@ +#include "server.hpp" + +#include +#include +#include +#include +#include + +#define LOGGER_FORMAT "[%Y-%m-%d %H:%M:%S.%e] [%n] [%^%l%$] %v" + +void init_logger() { + auto console_logger = std::make_shared(); + console_logger->set_pattern(LOGGER_FORMAT); + + auto file_logger = std::make_shared("server.log", true); + file_logger->set_pattern(LOGGER_FORMAT); + + std::vector server_sinks{console_logger, file_logger}; + auto server_logger = std::make_shared("Server", server_sinks.begin(), server_sinks.end()); + server_logger->set_pattern(LOGGER_FORMAT); + + spdlog::set_default_logger(server_logger); +} + +int main(int argc, char* argv[]) { + init_logger(); + + cxxopts::Options options("BlCrackUpdater"); + options.add_options() + ("p,port", "Port to listen on", cxxopts::value()->default_value("7541")) + ("k,reload-key", "Reload key for the server", cxxopts::value()->default_value("BlCrackUpdater")); + auto result = options.parse(argc, argv); + + int port = result["port"].as(); + std::string reload_key = result["reload-key"].as(); + + UpdateServer::Instance().start(port, reload_key).join(); + return 0; +} diff --git a/blcrack/updater/server.cpp b/blcrack/updater/server.cpp new file mode 100644 index 000000000..5a24c55c4 --- /dev/null +++ b/blcrack/updater/server.cpp @@ -0,0 +1,161 @@ +#include "server.hpp" + +#define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1 + +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +UpdateServer::UpdateServer() { + update_files(); + + Post("/files", [this](const httplib::Request& req, httplib::Response& res) { + Json::Reader reader; + Json::Value j; + if (!reader.parse(req.body, j)) { + SPDLOG_WARN("Invalid JSON request: {}", req.body); + res.status = 400; + return; + } + if (!j.isObject() || !j.isMember("arch") || !j.isMember("file")) { + SPDLOG_WARN("Invalid JSON request: {}", req.body); + res.status = 400; + return; + } + + auto arch = j["arch"].asString(); + auto file_name = j["file"].asString(); + SPDLOG_INFO("{} requested download {}/{}", req.remote_addr, arch, file_name); + + if (!is_file_exists(arch, file_name)) { + SPDLOG_WARN("File {}/{} not exists", arch, file_name); + res.status = 400; + return; + } + + auto& file_data = m_files[arch][file_name].data; + res.set_content(file_data.data(), file_data.size(), "application/octet-stream"); + + res.status = 200; + }); + + Post("/get_hash", [this](const httplib::Request& req, httplib::Response& res) { + Json::Reader reader; + Json::Value j; + if (!reader.parse(req.body, j)) { + SPDLOG_WARN("Invalid JSON request: {}", req.body); + res.status = 400; + return; + } + if (!j.isObject() || !j.isMember("arch") || !j.isMember("file")) { + SPDLOG_WARN("Invalid JSON request: {}", req.body); + res.status = 400; + return; + } + + auto arch = j["arch"].asString(); + auto file_name = j["file"].asString(); + SPDLOG_INFO("{} requested {}/{} hash", req.remote_addr, arch, file_name); + + if (!is_file_exists(arch, file_name)) { + SPDLOG_WARN("File {}/{} not exists", arch, file_name); + res.status = 400; + return; + } + + auto& hash = m_files[arch][file_name].hash; + res.set_content(hash, "text/plain"); + + res.status = 200; + }); + + Post("/reload", [this](const httplib::Request& req, httplib::Response& res) { + SPDLOG_INFO("{} requested reload", req.remote_addr); + if (req.body == m_reload_key) { + update_files(); + res.status = 200; + return; + } + SPDLOG_WARN("Invalid reload key: {}", req.body); + res.status = 400; + }); +} + +UpdateServer& UpdateServer::start(int port, const std::string& reload_key) { + m_reload_key = reload_key; + m_server_thread = std::thread([this, port] { + SPDLOG_INFO("Update server on port {}, reload key: {}", port, m_reload_key); + listen("0.0.0.0", port); + }); + return *this; +} + +void UpdateServer::join() { + m_server_thread.join(); +} + +UpdateServer& UpdateServer::Instance() { + static UpdateServer instance; + return instance; +} + +void UpdateServer::update_files() { + m_files.clear(); + fs::path assets_path = fs::current_path() / "assets"; + if (!exists(assets_path)) { + return; + } + for (auto& arch : fs::directory_iterator(assets_path)) { + if (!fs::is_directory(arch)) { + continue; + } + for (auto& file : fs::directory_iterator(arch)) { + if (!fs::is_regular_file(file)) { + continue; + } + auto arch_name = arch.path().filename().string(); + auto file_name = file.path().filename().string(); + + auto size = file.file_size(); + FileData data(size); + std::ifstream f(file.path().string(), std::ios::binary); + f.read(data.data(), size); + auto md5 = hash_file(data); + SPDLOG_INFO("Load {}/{}, hash: {}", arch_name, file_name, md5); + + m_files[arch_name][file_name] = { + .data = std::move(data), + .hash = std::move(md5) + }; + } + } +} + +UpdateServer::FileHash UpdateServer::hash_file(const FileData& data) { + FileHash hash; + CryptoPP::Weak::MD5 md5; + + CryptoPP::StringSource s( + reinterpret_cast(data.data()), data.size(), true, + new CryptoPP::HashFilter(md5, + new CryptoPP::HexEncoder( + new CryptoPP::StringSink(hash) + ) + ) + ); + return hash; +} + +bool UpdateServer::is_file_exists(const std::string& arch, const std::string& file) const { + if (!m_files.contains(arch)) { + return false; + } + auto& files = m_files.at(arch); + return files.contains(file); +} diff --git a/blcrack/updater/server.hpp b/blcrack/updater/server.hpp new file mode 100644 index 000000000..e723e4773 --- /dev/null +++ b/blcrack/updater/server.hpp @@ -0,0 +1,38 @@ +#ifndef SERVER_HPP +#define SERVER_HPP + +#include +#include +#include +#include +#include + +class UpdateServer : protected httplib::Server { + using ArchName = std::string; + using FileName = std::string; + using FileData = std::vector; + using FileHash = std::string; + struct FileInfo { + FileData data; + FileHash hash; + }; +public: + UpdateServer(); + ~UpdateServer() override = default; + + UpdateServer& start(int port = 7541, const std::string& reload_key = ""); + void join(); + + static UpdateServer& Instance(); + +private: + void update_files(); + static FileHash hash_file(const FileData& data); + bool is_file_exists(const std::string& arch, const std::string& file) const; + + std::string m_reload_key; + std::thread m_server_thread; + std::map> m_files; +}; + +#endif //SERVER_HPP diff --git a/config/template.json b/config/template.json index c56435619..db79edea4 100644 --- a/config/template.json +++ b/config/template.json @@ -98,6 +98,7 @@ "Enable": false, "InjectMethod": "local_patch", "RequestTimeLimit": 10, + "UpdateServer": null, "GameLibDir": null }, "Misc": { diff --git a/module/config/argument/args.json b/module/config/argument/args.json index 4692e2d51..5caae2c9f 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -495,6 +495,10 @@ "type": "input", "value": 10 }, + "UpdateServer": { + "type": "textarea", + "value": "" + }, "GameLibDir": { "type": "textarea", "value": "" diff --git a/module/config/argument/argument.yaml b/module/config/argument/argument.yaml index 79ababac6..e061afda6 100644 --- a/module/config/argument/argument.yaml +++ b/module/config/argument/argument.yaml @@ -155,6 +155,9 @@ HookGeneral: value: local_patch option: [local_patch, global_patch, outer_inject] RequestTimeLimit: 10 + UpdateServer: + value: "" + type: textarea GameLibDir: value: "" type: textarea diff --git a/module/config/config_generated.py b/module/config/config_generated.py index 13186ada3..8722016ab 100644 --- a/module/config/config_generated.py +++ b/module/config/config_generated.py @@ -81,6 +81,7 @@ class GeneratedConfig: HookGeneral_Enable = False HookGeneral_InjectMethod = 'local_patch' # local_patch, global_patch, outer_inject HookGeneral_RequestTimeLimit = 10 + HookGeneral_UpdateServer = None HookGeneral_GameLibDir = None # Group `ShipProperty` diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index c41fd0012..07bab4ef4 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -742,6 +742,10 @@ "name": "HookGeneral.RequestTimeLimit.name", "help": "HookGeneral.RequestTimeLimit.help" }, + "UpdateServer": { + "name": "HookGeneral.UpdateServer.name", + "help": "HookGeneral.UpdateServer.help" + }, "GameLibDir": { "name": "HookGeneral.GameLibDir.name", "help": "HookGeneral.GameLibDir.help" diff --git a/module/config/i18n/ja-JP.json b/module/config/i18n/ja-JP.json index 8e977d7bb..e8b03041f 100644 --- a/module/config/i18n/ja-JP.json +++ b/module/config/i18n/ja-JP.json @@ -742,6 +742,10 @@ "name": "HookGeneral.RequestTimeLimit.name", "help": "HookGeneral.RequestTimeLimit.help" }, + "UpdateServer": { + "name": "HookGeneral.UpdateServer.name", + "help": "HookGeneral.UpdateServer.help" + }, "GameLibDir": { "name": "HookGeneral.GameLibDir.name", "help": "HookGeneral.GameLibDir.help" diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index ea4fc47f9..903e7213b 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -742,6 +742,10 @@ "name": "请求时限(s)", "help": "" }, + "UpdateServer": { + "name": "资源更新服务器", + "help": "更新LuaHook资源的服务器,不填则表示不使用,将会使用本地资源" + }, "GameLibDir": { "name": "游戏库目录", "help": "以x86架构为例,目录一般为/data/app/<一串乱码>/<一串乱码>/lib/x86" diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index 46f960b3b..429a46ca8 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -742,6 +742,10 @@ "name": "HookGeneral.RequestTimeLimit.name", "help": "HookGeneral.RequestTimeLimit.help" }, + "UpdateServer": { + "name": "HookGeneral.UpdateServer.name", + "help": "HookGeneral.UpdateServer.help" + }, "GameLibDir": { "name": "HookGeneral.GameLibDir.name", "help": "HookGeneral.GameLibDir.help"