diff --git a/src/GakumasLocalify/Hook.cpp b/src/GakumasLocalify/Hook.cpp index 4d5073f..ec201bb 100644 --- a/src/GakumasLocalify/Hook.cpp +++ b/src/GakumasLocalify/Hook.cpp @@ -1240,6 +1240,27 @@ namespace GakumasLocal::HookMain { return ret; } + DEFINE_HOOK(void*, WindowHandle_SetWindowLong, (int32_t nIndex, intptr_t dwNewLong, void* mtd)) { + if (nIndex == GWL_STYLE) { + // printf("GWL_STYLE\n"); + + HWND hwnd = FindWindowW(L"UnityWndClass", L"gakumas"); + LONG_PTR style = GetWindowLongPtr(hwnd, GWL_STYLE); + style |= WS_OVERLAPPEDWINDOW; + SetWindowLongPtr(hwnd, GWL_STYLE, style); + dwNewLong = style; + } + return WindowHandle_SetWindowLong_Orig(nIndex, dwNewLong, mtd); + } + + DEFINE_HOOK(void*, AspectRatioHandler_WindowProc, (intptr_t hWnd, + uint32_t msg, + intptr_t wParam, + intptr_t lParam, void* mtd)) { + + return AspectRatioHandler_WindowProc_Orig(hWnd, msg, wParam, lParam, mtd); + } + void UpdateSwingBreastBonesData(void* initializeData) { if (!Config::enableBreastParam) return; @@ -1634,6 +1655,11 @@ namespace GakumasLocal::HookMain { "RenderPipeline", "EndCameraRendering")); #ifdef GKMS_WINDOWS + ADD_HOOK(WindowHandle_SetWindowLong, Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.Common.StandAloneWindow", + "WindowHandle", "SetWindowLong")); + ADD_HOOK(AspectRatioHandler_WindowProc, Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.Common.StandAloneWindow", + "AspectRatioHandler", "WindowProc")); + g_extra_assetbundle_paths.push_back((gakumasLocalPath / "local-files/gakumasassets").string()); LoadExtraAssetBundle(); GkmsResourceUpdate::CheckUpdateFromAPI(false); diff --git a/src/gkmsGUI/i18nData/strings_en.hpp b/src/gkmsGUI/i18nData/strings_en.hpp index 00c4ba9..407d4ce 100644 --- a/src/gkmsGUI/i18nData/strings_en.hpp +++ b/src/gkmsGUI/i18nData/strings_en.hpp @@ -56,7 +56,7 @@ namespace I18nData { { "axisx_y", "axisX.y" }, { "axisy_y", "axisY.y" }, { "axisz_y", "axisZ.y" }, - { "basic_settings", "Basic Ssettings" }, + { "basic_settings", "Basic Settings" }, { "graphic_settings", "Graphic Settings" }, { "camera_settings", "Camera Settings" }, { "test_mode_live", "Test Mode - LIVE" }, diff --git a/src/resourceUpdate/resourceUpdate.cpp b/src/resourceUpdate/resourceUpdate.cpp index b3b4a45..25b0b46 100644 --- a/src/resourceUpdate/resourceUpdate.cpp +++ b/src/resourceUpdate/resourceUpdate.cpp @@ -130,13 +130,16 @@ namespace GkmsResourceUpdate { return content; } - bool unzipFileFromURL(std::string downloadUrl, const std::string& unzipPath) { + bool unzipFileFromURL(std::string downloadUrl, const std::string& unzipPath, const std::string& targetDir = "") { std::string tempZipFile = (gakumasLocalPath / "temp_download.zip").string(); + if (std::filesystem::exists(tempZipFile)) { + std::filesystem::remove(tempZipFile); + } if (!DownloadFile(downloadUrl, tempZipFile)) { GakumasLocal::Log::Error("Download zip file failed."); return false; } - if (!UnzipFile(tempZipFile, unzipPath)) { + if (!UnzipFile(tempZipFile, unzipPath, targetDir)) { GakumasLocal::Log::Error("Unzip file failed."); return false; } @@ -214,9 +217,9 @@ namespace GkmsResourceUpdate { }).detach(); } - void checkUpdateFromURL(std::string downloadUrl) { - std::thread([&downloadUrl]() { - if (unzipFileFromURL(downloadUrl, gakumasLocalPath.string())) { + void checkUpdateFromURL(const std::string& downloadUrl) { + std::thread([downloadUrl]() { + if (unzipFileFromURL(downloadUrl, gakumasLocalPath.string(), "local-files")) { g_reload_all_data(); GakumasLocal::Log::Info("Update completed."); } diff --git a/src/resourceUpdate/resourceUpdate.hpp b/src/resourceUpdate/resourceUpdate.hpp index ca8e13c..d9c81b4 100644 --- a/src/resourceUpdate/resourceUpdate.hpp +++ b/src/resourceUpdate/resourceUpdate.hpp @@ -6,5 +6,5 @@ namespace GkmsResourceUpdate { void saveProgramConfig(); std::string GetCurrentResourceVersion(bool useCache); void CheckUpdateFromAPI(bool isManual); - void checkUpdateFromURL(std::string downloadUrl); + void checkUpdateFromURL(const std::string& downloadUrl); } \ No newline at end of file diff --git a/src/resourceUpdate/unzip.hpp b/src/resourceUpdate/unzip.hpp index 1911257..c41c348 100644 --- a/src/resourceUpdate/unzip.hpp +++ b/src/resourceUpdate/unzip.hpp @@ -147,3 +147,181 @@ static bool UnzipFile(const std::string& zipPath, const std::string& destination unzClose(zipfile); return true; } + + +static bool UnzipFile(const std::string& zipPath, const std::string& destinationFolder, std::string_view targetDir) { + if (targetDir.empty()) { + return UnzipFile(zipPath, destinationFolder); + } + + // 打开 zip 文件 + unzFile zipfile = unzOpen(zipPath.c_str()); + if (zipfile == nullptr) { + GakumasLocal::Log::ErrorFmt("Can't open zip file: %s", zipPath.c_str()); + return false; + } + + // 用于标识:ZIP 根目录是否直接包含 targetDir 文件夹 + bool targetAtRoot = false; + // 如果在根目录下没找到,再尝试记录哪个根目录下包含 targetDir(只检查一层) + std::string candidateRoot; + + // 第一遍扫描:遍历所有条目,判断目标文件夹所在位置 + int ret = unzGoToFirstFile(zipfile); + if (ret != UNZ_OK) { + GakumasLocal::Log::ErrorFmt("Can't read first file: %s", zipPath.c_str()); + unzClose(zipfile); + return false; + } + do { + char filename[512] = { 0 }; + unz_file_info fileInfo{}; + ret = unzGetCurrentFileInfo(zipfile, &fileInfo, + filename, sizeof(filename), + nullptr, 0, nullptr, 0); + if (ret != UNZ_OK) { + GakumasLocal::Log::ErrorFmt("Read ZIP File Info Error"); + unzClose(zipfile); + return false; + } + std::string entryName = filename; + // 将 '\' 统一替换为 '/' + std::replace(entryName.begin(), entryName.end(), '\\', '/'); + + // 若条目全名等于 targetDir(可以带或不带结尾 '/'),且没有前缀,则认为在根目录 + if ((entryName == targetDir || entryName == std::string(targetDir) + "/") && + entryName.find('/') == entryName.size() - 1) { + targetAtRoot = true; + } + else { + // 对于含有路径分隔符的条目,提取第一级目录 + if (auto pos = entryName.find('/'); pos != std::string::npos) { + std::string root = entryName.substr(0, pos); + // remainder 为 root 后面的路径 + std::string remainder = entryName.substr(pos + 1); + // 仅检查第一层目录 + auto pos2 = remainder.find('/'); + std::string firstSubDir = (pos2 != std::string::npos) ? remainder.substr(0, pos2) : remainder; + if (firstSubDir == targetDir) { + candidateRoot = root; + } + } + } + ret = unzGoToNextFile(zipfile); + } while (ret == UNZ_OK); + + // 根据扫描结果确定 extractionPrefix + // 若目标文件夹在根目录,则仅解压出 targetDir 内的条目(写入时去掉 targetDir 前缀) + // 否则,若 candidateRoot 不为空,则解压 candidateRoot 内的所有条目(写入时去除 candidateRoot 前缀) + std::string extractionPrefix; + if (targetAtRoot) { + extractionPrefix = std::string(targetDir) + "/"; + } + else if (!candidateRoot.empty()) { + extractionPrefix = candidateRoot + "/"; + } + else { + GakumasLocal::Log::ErrorFmt("Target directory '%.*s' not found in zip", (int)targetDir.size(), targetDir.data()); + unzClose(zipfile); + return false; + } + + // 重新打开 zip 文件进行解压(或重置扫描位置) + unzClose(zipfile); + zipfile = unzOpen(zipPath.c_str()); + if (zipfile == nullptr) { + GakumasLocal::Log::ErrorFmt("Can't reopen zip file: %s", zipPath.c_str()); + return false; + } + ret = unzGoToFirstFile(zipfile); + if (ret != UNZ_OK) { + GakumasLocal::Log::ErrorFmt("Can't read first file: %s", zipPath.c_str()); + unzClose(zipfile); + return false; + } + + // 第二遍扫描:仅解压属于指定根目录内的条目 + do { + char filename[512] = { 0 }; + unz_file_info fileInfo{}; + ret = unzGetCurrentFileInfo(zipfile, &fileInfo, + filename, sizeof(filename), + nullptr, 0, nullptr, 0); + if (ret != UNZ_OK) { + GakumasLocal::Log::ErrorFmt("Read ZIP File Info Error"); + unzClose(zipfile); + return false; + } + std::string entryName = filename; + std::replace(entryName.begin(), entryName.end(), '\\', '/'); + + // 仅处理以 extractionPrefix 开头的条目 + if (entryName.rfind(extractionPrefix, 0) == 0) { + // 去除 extractionPrefix 得到相对路径 + std::string relativePath = entryName.substr(extractionPrefix.size()); + + // 构造输出全路径 + std::string fullPath = destinationFolder; + if (!fullPath.empty() && fullPath.back() != '/' && fullPath.back() != '\\') + fullPath += "/"; + fullPath += relativePath; + + // 如果 relativePath 为空或以 '/' 结尾,认为是目录 + if (relativePath.empty() || relativePath.back() == '/') { + if (!CreateDirectoryRecursively(fullPath)) { + GakumasLocal::Log::ErrorFmt("Create Dir Failed: %s", fullPath.c_str()); + unzClose(zipfile); + return false; + } + } + else { + // 确保上级目录存在 + if (auto pos = fullPath.find_last_of("/\\"); pos != std::string::npos) { + std::string directory = fullPath.substr(0, pos); + if (!CreateDirectoryRecursively(directory)) { + GakumasLocal::Log::ErrorFmt("Create Dir Failed: %s", directory.c_str()); + unzClose(zipfile); + return false; + } + } + ret = unzOpenCurrentFile(zipfile); + if (ret != UNZ_OK) { + GakumasLocal::Log::ErrorFmt("Open file in zip failed: %s", entryName.c_str()); + unzClose(zipfile); + return false; + } + FILE* outFile = fopen(fullPath.c_str(), "wb"); + if (outFile == nullptr) { + GakumasLocal::Log::ErrorFmt("Can't create output file: %s", fullPath.c_str()); + unzCloseCurrentFile(zipfile); + unzClose(zipfile); + return false; + } + constexpr int bufferSize = 8192; + std::vector buffer(bufferSize); + int bytesRead = 0; + while ((bytesRead = unzReadCurrentFile(zipfile, buffer.data(), bufferSize)) > 0) { + if (fwrite(buffer.data(), 1, bytesRead, outFile) != static_cast(bytesRead)) { + GakumasLocal::Log::ErrorFmt("Write File Error: %s", fullPath.c_str()); + fclose(outFile); + unzCloseCurrentFile(zipfile); + unzClose(zipfile); + return false; + } + } + if (bytesRead < 0) { + GakumasLocal::Log::ErrorFmt("Read File Error: %s", entryName.c_str()); + fclose(outFile); + unzCloseCurrentFile(zipfile); + unzClose(zipfile); + return false; + } + fclose(outFile); + unzCloseCurrentFile(zipfile); + } + } + ret = unzGoToNextFile(zipfile); + } while (ret == UNZ_OK); + unzClose(zipfile); + return true; +}