forked from chinosk/gkms-localify-dmm
Compare commits
No commits in common. "main" and "main" have entirely different histories.
|
|
@ -157,5 +157,3 @@ resources/text_dumper/bin
|
|||
.vscode/
|
||||
/backend
|
||||
/utils/events.br
|
||||
|
||||
gakumas-texture-TL
|
||||
|
|
|
|||
|
|
@ -1,191 +0,0 @@
|
|||
# Gakumas纹理替换功能融合说明
|
||||
|
||||
## 概述
|
||||
|
||||
本项目已成功将 `gakumas-texture-TL` 项目的纹理替换功能完整集成到主项目中。现在只需要使用 `version.dll` 一个文件即可同时实现文字翻译和纹理替换功能。
|
||||
|
||||
## 主要改动
|
||||
|
||||
### 1. 新增模块
|
||||
|
||||
#### TextureReplace模块 (`src/GakumasLocalify/TextureReplace.h/cpp`)
|
||||
- 负责纹理替换的核心功能
|
||||
- 包含资源映射配置加载
|
||||
- 图片文件加载和Unity Texture2D对象创建
|
||||
- 待替换请求管理
|
||||
|
||||
### 2. Hook集成
|
||||
|
||||
#### 修改 `src/GakumasLocalify/Hook.cpp`
|
||||
- 在 `AssetBundle_LoadAssetAsync` Hook中添加纹理替换检测
|
||||
- 在 `AssetBundleRequest_GetResult` Hook中返回替换后的纹理
|
||||
- 在初始化流程中调用 `TextureReplace::Initialize()`
|
||||
|
||||
### 3. 配置系统
|
||||
|
||||
#### Config配置项 (`src/GakumasLocalify/config/`)
|
||||
- 添加 `enableTextureReplace` 配置项
|
||||
- 在 `Config.hpp` 中声明
|
||||
- 在 `Config.cpp` 中实现加载和保存
|
||||
|
||||
### 4. 资源目录结构
|
||||
|
||||
```
|
||||
resource/local-files/textures/
|
||||
├── asset_mapping.txt # 资源映射配置文件
|
||||
├── comic/ # 漫画图片
|
||||
│ ├── 1ko/ # 一格漫画
|
||||
│ └── 4ko/ # 四格漫画
|
||||
├── general_report/ # 好感度偶像档案
|
||||
├── ui/ # UI按钮图片
|
||||
└── tutorial/ # 教程图片
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 启用纹理替换功能
|
||||
|
||||
编辑 `resource/localizationConfig.json`,添加或修改:
|
||||
```json
|
||||
{
|
||||
"enableTextureReplace": true
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 配置资源映射
|
||||
|
||||
编辑 `resource/local-files/textures/asset_mapping.txt`:
|
||||
```
|
||||
# 格式: assetId=本地文件路径
|
||||
img_general_comic_0001=comic/1ko/img_general_comic_0001.png
|
||||
img_general_comic4_0000=comic/4ko/img_general_comic4_0000.png
|
||||
img_general_report_akapen_amao-001=general_report/amao/img_general_report_akapen_amao-001.png
|
||||
```
|
||||
|
||||
### 3. 放置图片文件
|
||||
|
||||
将替换用的图片文件放置到对应的子目录中:
|
||||
- `comic/1ko/` - 一格漫画
|
||||
- `comic/4ko/` - 四格漫画
|
||||
- `general_report/` - 好感度档案
|
||||
- `ui/` - UI按钮
|
||||
- `tutorial/` - 教程图片
|
||||
|
||||
### 4. 启动游戏
|
||||
|
||||
只需要将编译生成的 `version.dll` 放到游戏目录即可,无需其他DLL文件。
|
||||
|
||||
## 技术细节
|
||||
|
||||
### Hook工作流程
|
||||
|
||||
1. **AssetBundle_LoadAssetAsync Hook**
|
||||
- 拦截Unity的资源加载请求
|
||||
- 检查资源名称是否在映射表中
|
||||
- 如果需要替换,加载本地图片文件
|
||||
- 创建Unity Texture2D对象
|
||||
- 将替换信息存储到待处理映射表中
|
||||
|
||||
2. **AssetBundleRequest_GetResult Hook**
|
||||
- 拦截Unity获取加载结果的请求
|
||||
- 检查是否有待替换的纹理
|
||||
- 如果有,返回替换后的纹理对象
|
||||
- 否则返回原始资源
|
||||
|
||||
3. **自动清理机制**
|
||||
- 每30秒自动清理超时(60秒)的待处理请求
|
||||
- 防止内存泄漏
|
||||
|
||||
### IL2CPP API使用
|
||||
|
||||
通过动态获取IL2CPP函数指针实现:
|
||||
- `il2cpp_domain_get` - 获取应用域
|
||||
- `il2cpp_class_from_name` - 查找类
|
||||
- `il2cpp_object_new` - 创建对象
|
||||
- `il2cpp_runtime_invoke` - 调用方法
|
||||
|
||||
### 与xinput1_3.dll版本的区别
|
||||
|
||||
| 特性 | xinput1_3.dll版本 | version.dll集成版本 |
|
||||
|------|-------------------|---------------------|
|
||||
| DLL数量 | 2个 (version.dll + xinput1_3.dll) | 1个 (version.dll) |
|
||||
| 功能集成 | 分离 | 统一 |
|
||||
| 配置管理 | 独立配置 | 统一配置系统 |
|
||||
| 日志系统 | 独立日志 | 统一日志系统 |
|
||||
| 维护性 | 需要维护两套代码 | 单一代码库 |
|
||||
|
||||
## 迁移指南
|
||||
|
||||
如果你之前使用xinput1_3.dll版本:
|
||||
|
||||
1. 备份你的图片文件和 `asset_mapping.txt`
|
||||
2. 将 `asset_mapping.txt` 复制到 `resource/local-files/textures/`
|
||||
3. 将所有图片文件按目录结构复制到 `resource/local-files/textures/` 对应子目录
|
||||
4. 在 `resource/localizationConfig.json` 中设置 `"enableTextureReplace": true`
|
||||
5. 删除游戏目录中的 `xinput1_3.dll`
|
||||
6. 使用新编译的 `version.dll` 替换原有的文件
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **性能影响**: 纹理替换只对配置文件中列出的资源生效,不会影响其他资源的加载性能
|
||||
|
||||
2. **图片格式**: 支持PNG、JPG等Unity支持的常见图片格式
|
||||
|
||||
3. **路径分隔符**: 在 `asset_mapping.txt` 中,可以使用正斜杠(/)或反斜杠(\\)
|
||||
|
||||
4. **日志输出**: 纹理替换相关的日志会输出到控制台和日志文件中,前缀为 "TextureReplace:"
|
||||
|
||||
5. **错误处理**: 如果本地文件不存在或加载失败,会自动使用游戏原始资源
|
||||
|
||||
## 调试
|
||||
|
||||
启用调试模式查看详细日志:
|
||||
```json
|
||||
{
|
||||
"enableConsole": true,
|
||||
"dbgMode": true,
|
||||
"enableTextureReplace": true
|
||||
}
|
||||
```
|
||||
|
||||
日志会显示:
|
||||
- 初始化过程
|
||||
- 加载的映射数量
|
||||
- 每次资源替换的详细信息
|
||||
- 错误和警告信息
|
||||
|
||||
## 常见问题
|
||||
|
||||
**Q: 纹理没有被替换?**
|
||||
A: 检查以下几点:
|
||||
1. `enableTextureReplace` 是否设置为 `true`
|
||||
2. `asset_mapping.txt` 中的资源ID是否正确
|
||||
3. 图片文件是否存在于指定路径
|
||||
4. 查看日志输出是否有错误信息
|
||||
|
||||
**Q: 游戏启动后控制台显示什么?**
|
||||
A: 如果启用纹理替换,会看到:
|
||||
```
|
||||
TextureReplace: Initializing texture replacement system...
|
||||
TextureReplace: Created texture directories at: ...
|
||||
TextureReplace: IL2CPP API initialized successfully
|
||||
TextureReplace: Loaded X asset mappings
|
||||
TextureReplace: Texture replacement system initialized successfully
|
||||
```
|
||||
|
||||
**Q: 可以动态添加新的替换图片吗?**
|
||||
A: 可以,修改 `asset_mapping.txt` 并添加新图片后,重启游戏即可生效
|
||||
|
||||
## 开发者信息
|
||||
|
||||
- 原xinput1_3版本: gakumas-texture-TL项目
|
||||
- 集成版本: 本项目
|
||||
- 集成时间: 2025年11月26日
|
||||
|
||||
## 技术支持
|
||||
|
||||
如遇到问题,请:
|
||||
1. 检查日志输出
|
||||
2. 确认配置文件格式正确
|
||||
3. 验证图片文件可以正常打开
|
||||
4. 查看游戏目录权限设置
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
# 纹理替换功能已集成
|
||||
|
||||
## 摘要
|
||||
|
||||
**gakumas-texture-TL** 文件夹的图片替换功能已完全融合进主项目。现在只需使用 **version.dll** 一个文件即可同时实现:
|
||||
- ✅ 文字翻译
|
||||
- ✅ 纹理/图片替换
|
||||
|
||||
**不再需要** xinput1_3.dll!
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 启用功能
|
||||
|
||||
编辑 `resource/localizationConfig.json`:
|
||||
```json
|
||||
{
|
||||
"enableTextureReplace": true
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 配置资源映射
|
||||
|
||||
编辑 `resource/local-files/textures/asset_mapping.txt`:
|
||||
```
|
||||
img_general_comic_0001=comic/1ko/img_general_comic_0001.png
|
||||
img_general_comic4_0000=comic/4ko/img_general_comic4_0000.png
|
||||
```
|
||||
|
||||
### 3. 放置图片
|
||||
|
||||
将图片文件放到 `resource/local-files/textures/` 对应子目录:
|
||||
```
|
||||
textures/
|
||||
├── comic/1ko/ # 一格漫画
|
||||
├── comic/4ko/ # 四格漫画
|
||||
├── general_report/ # 好感度档案
|
||||
├── ui/ # UI按钮
|
||||
└── tutorial/ # 教程图片
|
||||
```
|
||||
|
||||
### 4. 使用
|
||||
|
||||
只需将 `version.dll` 放到游戏目录即可!
|
||||
|
||||
## 目录结构变化
|
||||
|
||||
```
|
||||
gkms-localify-dmm/
|
||||
├── gakumas-texture-TL/ # 可以保留作为参考,不再需要编译
|
||||
├── src/
|
||||
│ └── GakumasLocalify/
|
||||
│ ├── TextureReplace.h # 新增: 纹理替换模块
|
||||
│ ├── TextureReplace.cpp # 新增: 纹理替换实现
|
||||
│ ├── Hook.cpp # 修改: 集成纹理Hook
|
||||
│ └── config/
|
||||
│ ├── Config.hpp # 修改: 添加enableTextureReplace
|
||||
│ └── Config.cpp # 修改: 配置加载/保存
|
||||
├── resource/
|
||||
│ ├── localizationConfig.json # 修改: 添加enableTextureReplace配置项
|
||||
│ └── local-files/
|
||||
│ └── textures/ # 新增: 纹理替换资源目录
|
||||
│ ├── README.md # 使用说明
|
||||
│ ├── asset_mapping.txt # 资源映射配置
|
||||
│ ├── comic/
|
||||
│ ├── general_report/
|
||||
│ ├── ui/
|
||||
│ └── tutorial/
|
||||
└── docs/
|
||||
└── TEXTURE_REPLACE_INTEGRATION.md # 详细集成文档
|
||||
```
|
||||
|
||||
## 核心改动
|
||||
|
||||
### 新增文件
|
||||
1. `src/GakumasLocalify/TextureReplace.h/cpp` - 纹理替换核心模块
|
||||
2. `resource/local-files/textures/README.md` - 使用说明
|
||||
3. `docs/TEXTURE_REPLACE_INTEGRATION.md` - 详细集成文档
|
||||
|
||||
### 修改文件
|
||||
1. `src/GakumasLocalify/Hook.cpp` - 集成AssetBundle Hook
|
||||
2. `src/GakumasLocalify/config/Config.hpp` - 添加配置项声明
|
||||
3. `src/GakumasLocalify/config/Config.cpp` - 实现配置加载
|
||||
4. `resource/localizationConfig.json` - 添加enableTextureReplace
|
||||
|
||||
## 技术亮点
|
||||
|
||||
- **统一Hook系统**: 复用现有的AssetBundle Hook
|
||||
- **配置集成**: 纳入统一的配置管理系统
|
||||
- **日志集成**: 使用统一的日志输出
|
||||
- **目录规范**: 遵循项目现有的目录结构
|
||||
- **零依赖**: 无需额外的DLL文件
|
||||
|
||||
## 迁移建议
|
||||
|
||||
如果你之前使用 xinput1_3.dll:
|
||||
|
||||
1. 复制 `gakumas-texture-TL/gakumas-local-texture/asset_mapping.txt` 到 `resource/local-files/textures/`
|
||||
2. 复制所有图片文件到 `resource/local-files/textures/` 对应子目录
|
||||
3. 在配置文件中启用 `enableTextureReplace`
|
||||
4. 删除 `xinput1_3.dll`
|
||||
5. 只使用 `version.dll`
|
||||
|
||||
## 详细文档
|
||||
|
||||
查看 [docs/TEXTURE_REPLACE_INTEGRATION.md](docs/TEXTURE_REPLACE_INTEGRATION.md) 了解:
|
||||
- 完整技术细节
|
||||
- Hook工作流程
|
||||
- 调试方法
|
||||
- 常见问题解答
|
||||
|
||||
## 开发者备注
|
||||
|
||||
- 集成完成日期: 2025年11月26日
|
||||
- xinput1_3.dll 的所有功能已通过 version.dll 实现
|
||||
- gakumas-texture-TL 文件夹可以保留作为参考,但不再需要编译使用
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
# 纹理替换功能说明
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
resource/local-files/textures/
|
||||
├── asset_mapping.txt # 资源映射配置文件
|
||||
├── comic/ # 漫画图片
|
||||
│ ├── 1ko/ # 一格漫画
|
||||
│ └── 4ko/ # 四格漫画
|
||||
├── general_report/ # 好感度偶像档案
|
||||
│ ├── amao/ # 麻央
|
||||
│ └── hmsz/ # 美铃
|
||||
├── ui/ # UI按钮图片
|
||||
└── tutorial/ # 教程图片
|
||||
```
|
||||
|
||||
## 配置文件说明
|
||||
|
||||
`asset_mapping.txt` 文件用于配置资源ID与本地文件的映射关系。
|
||||
|
||||
格式: `assetId=本地文件路径`
|
||||
|
||||
示例:
|
||||
```
|
||||
# 一格漫画
|
||||
img_general_comic_0001=comic/1ko/img_general_comic_0001.png
|
||||
|
||||
# 四格漫画
|
||||
img_general_comic4_0000=comic/4ko/img_general_comic4_0000.png
|
||||
|
||||
# 好感度偶像档案
|
||||
img_general_report_akapen_amao-001=general_report/amao/img_general_report_akapen_amao-001.png
|
||||
```
|
||||
|
||||
## 启用方式
|
||||
|
||||
在 `resource/config.json` 中添加:
|
||||
```json
|
||||
{
|
||||
"enableTextureReplace": true
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 路径使用正斜杠 `/` 或反斜杠 `\` 均可
|
||||
2. 路径相对于 `resource/local-files/textures/` 目录
|
||||
3. 支持 PNG、JPG 等常见图片格式
|
||||
4. 只有在配置文件中映射的资源才会被替换
|
||||
5. 如果本地文件不存在,将使用游戏原始资源
|
||||
|
||||
## 迁移说明
|
||||
|
||||
如果你之前使用 xinput1_3.dll 版本的纹理替换插件:
|
||||
|
||||
1. 将 `gakumas-local-texture/asset_mapping.txt` 复制到此目录
|
||||
2. 将所有图片文件复制到对应的子目录中
|
||||
3. 在 `config.json` 中启用 `enableTextureReplace`
|
||||
4. 删除 xinput1_3.dll 文件
|
||||
5. 只保留 version.dll 即可
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
# Asset映射配置文件
|
||||
# 格式: assetId=本地文件路径
|
||||
# 注意: 路径使用正斜杠/,支持相对路径和绝对路径
|
||||
|
||||
# 一格漫画映射
|
||||
img_general_comic_0001=comic/1ko/img_general_comic_0001.png
|
||||
img_general_comic_0002=comic/1ko/img_general_comic_0002.png
|
||||
img_general_comic_0003=comic/1ko/img_general_comic_0003.png
|
||||
img_general_comic_0004=comic/1ko/img_general_comic_0004.png
|
||||
img_general_comic_0005=comic/1ko/img_general_comic_0005.png
|
||||
img_general_comic_0006=comic/1ko/img_general_comic_0006.png
|
||||
|
||||
|
||||
# 四格漫画映射
|
||||
img_general_comic4_0000=comic/4ko/img_general_comic4_0000.png
|
||||
img_general_comic4_0001=comic/4ko/img_general_comic4_0001.png
|
||||
img_general_comic4_0002=comic/4ko/img_general_comic4_0002.png
|
||||
img_general_comic4_0003=comic/4ko/img_general_comic4_0003.png
|
||||
img_general_comic4_0004=comic/4ko/img_general_comic4_0004.png
|
||||
img_general_comic4_0005=comic/4ko/img_general_comic4_0005.png
|
||||
img_general_comic4_0006=comic/4ko/img_general_comic4_0006.png
|
||||
img_general_comic4_0007=comic/4ko/img_general_comic4_0007.png
|
||||
img_general_comic4_0008=comic/4ko/img_general_comic4_0008.png
|
||||
|
||||
img_general_comic4_0000-thumb=comic/4ko/img_general_comic4_0000-thumb.png
|
||||
img_general_comic4_0001-thumb=comic/4ko/img_general_comic4_0001-thumb.png
|
||||
img_general_comic4_0002-thumb=comic/4ko/img_general_comic4_0002-thumb.png
|
||||
img_general_comic4_0003-thumb=comic/4ko/img_general_comic4_0003-thumb.png
|
||||
img_general_comic4_0004-thumb=comic/4ko/img_general_comic4_0004-thumb.png
|
||||
img_general_comic4_0005-thumb=comic/4ko/img_general_comic4_0005-thumb.png
|
||||
img_general_comic4_0006-thumb=comic/4ko/img_general_comic4_0006-thumb.png
|
||||
img_general_comic4_0007-thumb=comic/4ko/img_general_comic4_0007-thumb.png
|
||||
img_general_comic4_0008-thumb=comic/4ko/img_general_comic4_0008-thumb.png
|
||||
|
||||
|
||||
# 好感度偶像档案映射
|
||||
#麻央
|
||||
img_general_report_akapen_amao-001=general_report/amao/img_general_report_akapen_amao-001.png
|
||||
|
||||
# 美铃
|
||||
img_general_report_akapen_hmsz-001=general_report/hmsz/img_general_report_akapen_hmsz-001.png
|
||||
img_general_report_akapen_hmsz-002=general_report/hmsz/img_general_report_akapen_hmsz-002.png
|
||||
img_general_report_akapen_hmsz-003=general_report/hmsz/img_general_report_akapen_hmsz-003.png
|
||||
img_general_report_akapen_hmsz-004=general_report/hmsz/img_general_report_akapen_hmsz-004.png
|
||||
img_general_report_akapen_hmsz-005=general_report/hmsz/img_general_report_akapen_hmsz-005.png
|
||||
img_general_report_akapen_hmsz-006=general_report/hmsz/img_general_report_akapen_hmsz-006.png
|
||||
img_general_report_bg_graph_hmsz-001=general_report/hmsz/img_general_report_bg_graph_hmsz-001.png
|
||||
img_general_report_text_hmsz-001=general_report/hmsz/img_general_report_text_hmsz-001.png
|
||||
img_general_report_text_hmsz-002=general_report/hmsz/img_general_report_text_hmsz-002.png
|
||||
img_general_report_text_hmsz-003=general_report/hmsz/img_general_report_text_hmsz-003.png
|
||||
|
||||
|
||||
# 主页Produce按钮映射
|
||||
img_general_ui_produce-1_btn=ui/button/img_general_ui_produce-1_btn.png
|
||||
img_general_ui_produce-1_btn-small=ui/button/img_general_ui_produce-1_btn-small.png
|
||||
img_general_ui_produce-2_btn=ui/button/img_general_ui_produce-2_btn.png
|
||||
img_general_ui_produce-2_btn-small=ui/button/img_general_ui_produce-2_btn-small.png
|
||||
img_general_ui_produce-3_btn-small=ui/button/img_general_ui_produce-3_btn-small.png
|
||||
img_general_ui_produce-nia_btn=ui/button/img_general_ui_produce-nia_btn.png
|
||||
img_general_ui_produce-nia-2_btn=ui/button/img_general_ui_produce-nia-2_btn.png
|
||||
|
||||
|
||||
# 教程页面映射
|
||||
img_tutorial_produce_01_first-001=tutorial/img_tutorial_produce_01_first-001.png
|
||||
|
||||
|
||||
# 添加更多您需要替换的assetId
|
||||
# your_asset_id=path/to/your/custom/image.png
|
||||
|
||||
|
|
@ -9,7 +9,6 @@
|
|||
"gameOrientation": 0,
|
||||
"forceExportResource": false,
|
||||
"enableFreeCamera": false,
|
||||
"enableTextureReplace": false,
|
||||
"targetFrameRate": 0,
|
||||
"unlockAllLive": true,
|
||||
"unlockAllLiveCostume": true,
|
||||
|
|
@ -17,7 +16,6 @@
|
|||
"liveCustomeHeadId": "costume_head_fktn-cstm-0001",
|
||||
"liveCustomeCostumeId": "hume-othr-0000",
|
||||
"loginAsIOS": false,
|
||||
"skipPurchaseInit": true,
|
||||
"useCustomeGraphicSettings": false,
|
||||
"renderScale": 0.77,
|
||||
"qualitySettingsLevel": 3,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
#include "Il2cppUtils.hpp"
|
||||
#include "Local.h"
|
||||
#include "MasterLocal.h"
|
||||
#include "TextureReplace.h"
|
||||
#include <unordered_set>
|
||||
#include "camera/camera.hpp"
|
||||
#include "config/Config.hpp"
|
||||
|
|
@ -266,62 +265,10 @@ namespace GakumasLocal::HookMain {
|
|||
// Log::InfoFmt("AssetBundle_LoadAssetAsync: %s, type: %s", name->ToString().c_str());
|
||||
auto ret = AssetBundle_LoadAssetAsync_Orig(self, name, type);
|
||||
loadHistory.emplace(ret, name->ToString());
|
||||
|
||||
// 纹理替换功能: 检查是否需要替换此资源
|
||||
if (Config::enableTextureReplace) {
|
||||
std::string assetName = name->ToString();
|
||||
std::string replacementPath;
|
||||
|
||||
if (TextureReplace::ShouldReplaceAsset(assetName, replacementPath)) {
|
||||
Log::InfoFmt("TextureReplace: Detected asset for replacement: %s -> %s",
|
||||
assetName.c_str(), replacementPath.c_str());
|
||||
|
||||
// 尝试加载自定义纹理
|
||||
void* customTexture = TextureReplace::LoadCustomTexture(replacementPath);
|
||||
if (customTexture) {
|
||||
Log::InfoFmt("TextureReplace: Custom texture loaded, storing for request: 0x%p", ret);
|
||||
TextureReplace::PendingReplacement pending;
|
||||
pending.texture = customTexture;
|
||||
pending.timestamp = GetTickCount();
|
||||
TextureReplace::g_pendingReplacements[ret] = pending;
|
||||
} else {
|
||||
Log::InfoFmt("TextureReplace: Failed to load custom texture from: %s", replacementPath.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
DEFINE_HOOK(void*, AssetBundleRequest_GetResult, (void* self)) {
|
||||
// 定期清理过期的待替换请求
|
||||
if (Config::enableTextureReplace) {
|
||||
static DWORD lastCleanupTime = 0;
|
||||
DWORD currentTime = GetTickCount();
|
||||
if (currentTime - lastCleanupTime > 30000) { // 每30秒清理一次
|
||||
TextureReplace::CleanupPendingReplacements();
|
||||
lastCleanupTime = currentTime;
|
||||
}
|
||||
|
||||
// 检查此请求是否有待替换的纹理
|
||||
auto it = TextureReplace::g_pendingReplacements.find(self);
|
||||
if (it != TextureReplace::g_pendingReplacements.end()) {
|
||||
Log::InfoFmt("TextureReplace: Returning custom texture for request: 0x%p", self);
|
||||
void* customTexture = it->second.texture;
|
||||
|
||||
// 从待处理列表中移除
|
||||
TextureReplace::g_pendingReplacements.erase(it);
|
||||
|
||||
// 清理loadHistory
|
||||
if (const auto iter = loadHistory.find(self); iter != loadHistory.end()) {
|
||||
loadHistory.erase(iter);
|
||||
}
|
||||
|
||||
Log::InfoFmt("TextureReplace: Successfully returned custom texture: 0x%p", customTexture);
|
||||
return customTexture;
|
||||
}
|
||||
}
|
||||
|
||||
auto result = AssetBundleRequest_GetResult_Orig(self);
|
||||
if (const auto iter = loadHistory.find(self); iter != loadHistory.end()) {
|
||||
const auto name = iter->second;
|
||||
|
|
@ -1926,12 +1873,6 @@ namespace GakumasLocal::HookMain {
|
|||
|
||||
Local::LoadData();
|
||||
MasterLocal::LoadData();
|
||||
|
||||
// 初始化纹理替换系统
|
||||
if (Config::enableTextureReplace) {
|
||||
Log::Info("Initializing texture replacement system...");
|
||||
TextureReplace::Initialize();
|
||||
}
|
||||
|
||||
UnityResolveProgress::startInit = false;
|
||||
|
||||
|
|
@ -1956,11 +1897,5 @@ namespace GakumasLocal::Hook {
|
|||
|
||||
|
||||
Log::Info("Hook installed");
|
||||
|
||||
// 延迟启动自动资源更新检查
|
||||
std::thread([]() {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
GkmsResourceUpdate::AutoCheckUpdateFromAPI();
|
||||
}).detach();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,16 +2,6 @@
|
|||
#define GAKUMAS_LOCALIFY_HOOK_H
|
||||
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
namespace GakumasLocal {
|
||||
// 获取 AssetBundle 句柄(在 Hook.cpp 定义,TextureReplace.cpp 需要用)
|
||||
uint32_t GetBundleHandleByAssetName(std::string assetName);
|
||||
bool FindAssetBundleAssetByFilename(const std::string& filename,
|
||||
std::string& assetPath,
|
||||
uint32_t& handle);
|
||||
}
|
||||
|
||||
namespace GakumasLocal::Hook
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,372 +0,0 @@
|
|||
#include "TextureReplace.h"
|
||||
#include "Log.h"
|
||||
#include "Il2cppUtils.hpp"
|
||||
#include "../il2cpp/il2cpp_symbols.hpp"
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#include <windows.h>
|
||||
|
||||
extern std::filesystem::path gakumasLocalPath;
|
||||
|
||||
namespace GakumasLocal::TextureReplace {
|
||||
// IL2CPP string structure
|
||||
struct Il2CppString {
|
||||
void* klass;
|
||||
void* monitor;
|
||||
int length;
|
||||
wchar_t chars[1];
|
||||
};
|
||||
|
||||
// 资源映射表: assetId -> 本地文件路径
|
||||
std::map<std::string, std::string> g_assetMappings;
|
||||
|
||||
// 待替换的请求映射表
|
||||
std::map<void*, PendingReplacement> g_pendingReplacements;
|
||||
|
||||
// 纹理替换基础目录
|
||||
std::filesystem::path g_textureBaseDir;
|
||||
|
||||
// IL2CPP API函数指针
|
||||
static void* (*il2cpp_domain_get)() = nullptr;
|
||||
static void** (*il2cpp_domain_get_assemblies)(void*, size_t*) = nullptr;
|
||||
static void* (*il2cpp_assembly_get_image)(void*) = nullptr;
|
||||
static void* (*il2cpp_class_from_name)(void*, const char*, const char*) = nullptr;
|
||||
static void* (*il2cpp_object_new)(void*) = nullptr;
|
||||
static void* (*il2cpp_class_get_method_from_name)(void*, const char*, int) = nullptr;
|
||||
static void* (*il2cpp_runtime_invoke)(void*, void*, void**, void**) = nullptr;
|
||||
static void* (*il2cpp_array_new)(void*, size_t) = nullptr;
|
||||
|
||||
// 安全转换IL2CPP字符串为std::string
|
||||
std::string Il2CppStringToStdString(void* il2cppString) {
|
||||
if (!il2cppString) return "";
|
||||
|
||||
if (IsBadReadPtr(il2cppString, sizeof(Il2CppString))) {
|
||||
return "";
|
||||
}
|
||||
|
||||
Il2CppString* str = (Il2CppString*)il2cppString;
|
||||
|
||||
if (str->length <= 0 || str->length > 1000) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (IsBadReadPtr(str->chars, str->length * sizeof(wchar_t))) {
|
||||
return "";
|
||||
}
|
||||
|
||||
int bufferSize = WideCharToMultiByte(CP_UTF8, 0, str->chars, str->length, nullptr, 0, nullptr, nullptr);
|
||||
if (bufferSize <= 0) return "";
|
||||
|
||||
std::string result(bufferSize, 0);
|
||||
WideCharToMultiByte(CP_UTF8, 0, str->chars, str->length, &result[0], bufferSize, nullptr, nullptr);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 初始化IL2CPP API
|
||||
bool InitializeIL2CPPApi() {
|
||||
Log::Info("TextureReplace: Initializing IL2CPP API...");
|
||||
|
||||
HMODULE hGameAssembly = GetModuleHandleA("GameAssembly.dll");
|
||||
if (!hGameAssembly) {
|
||||
Log::Error("TextureReplace: Failed to get GameAssembly.dll handle");
|
||||
return false;
|
||||
}
|
||||
|
||||
il2cpp_domain_get = (void* (*)())GetProcAddress(hGameAssembly, "il2cpp_domain_get");
|
||||
il2cpp_domain_get_assemblies = (void** (*)(void*, size_t*))GetProcAddress(hGameAssembly, "il2cpp_domain_get_assemblies");
|
||||
il2cpp_assembly_get_image = (void* (*)(void*))GetProcAddress(hGameAssembly, "il2cpp_assembly_get_image");
|
||||
il2cpp_class_from_name = (void* (*)(void*, const char*, const char*))GetProcAddress(hGameAssembly, "il2cpp_class_from_name");
|
||||
il2cpp_object_new = (void* (*)(void*))GetProcAddress(hGameAssembly, "il2cpp_object_new");
|
||||
il2cpp_class_get_method_from_name = (void* (*)(void*, const char*, int))GetProcAddress(hGameAssembly, "il2cpp_class_get_method_from_name");
|
||||
il2cpp_runtime_invoke = (void* (*)(void*, void*, void**, void**))GetProcAddress(hGameAssembly, "il2cpp_runtime_invoke");
|
||||
il2cpp_array_new = (void* (*)(void*, size_t))GetProcAddress(hGameAssembly, "il2cpp_array_new");
|
||||
|
||||
if (!il2cpp_domain_get || !il2cpp_domain_get_assemblies || !il2cpp_assembly_get_image ||
|
||||
!il2cpp_class_from_name || !il2cpp_object_new || !il2cpp_class_get_method_from_name ||
|
||||
!il2cpp_runtime_invoke || !il2cpp_array_new) {
|
||||
Log::Error("TextureReplace: Failed to get IL2CPP API functions");
|
||||
return false;
|
||||
}
|
||||
|
||||
Log::Info("TextureReplace: IL2CPP API initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 初始化纹理替换系统
|
||||
bool Initialize() {
|
||||
Log::Info("TextureReplace: Initializing texture replacement system...");
|
||||
|
||||
// 设置基础目录
|
||||
g_textureBaseDir = gakumasLocalPath / "textures";
|
||||
|
||||
// 创建目录结构
|
||||
try {
|
||||
std::filesystem::create_directories(g_textureBaseDir);
|
||||
std::filesystem::create_directories(g_textureBaseDir / "comic" / "1ko");
|
||||
std::filesystem::create_directories(g_textureBaseDir / "comic" / "4ko");
|
||||
std::filesystem::create_directories(g_textureBaseDir / "general_report");
|
||||
std::filesystem::create_directories(g_textureBaseDir / "ui");
|
||||
std::filesystem::create_directories(g_textureBaseDir / "tutorial");
|
||||
Log::InfoFmt("TextureReplace: Created texture directories at: %s", g_textureBaseDir.string().c_str());
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
Log::ErrorFmt("TextureReplace: Failed to create directories: %s", e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 初始化IL2CPP API
|
||||
if (!InitializeIL2CPPApi()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 加载资源映射配置
|
||||
if (!LoadAssetMappings()) {
|
||||
Log::Info("TextureReplace: No asset mappings loaded, texture replacement will be disabled");
|
||||
return false;
|
||||
}
|
||||
|
||||
Log::Info("TextureReplace: Texture replacement system initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 加载资源映射配置
|
||||
bool LoadAssetMappings() {
|
||||
std::filesystem::path configPath = g_textureBaseDir / "asset_mapping.txt";
|
||||
|
||||
if (!std::filesystem::exists(configPath)) {
|
||||
Log::InfoFmt("TextureReplace: Config file not found: %s", configPath.string().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ifstream file(configPath);
|
||||
if (!file.is_open()) {
|
||||
Log::ErrorFmt("TextureReplace: Failed to open config file: %s", configPath.string().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
g_assetMappings.clear();
|
||||
std::string line;
|
||||
int count = 0;
|
||||
|
||||
while (std::getline(file, line)) {
|
||||
// 跳过空行和注释
|
||||
if (line.empty() || line[0] == '#') continue;
|
||||
|
||||
size_t equalPos = line.find('=');
|
||||
if (equalPos != std::string::npos) {
|
||||
std::string assetId = line.substr(0, equalPos);
|
||||
std::string localPath = line.substr(equalPos + 1);
|
||||
|
||||
// 移除首尾空格
|
||||
while (!assetId.empty() && (assetId.back() == ' ' || assetId.back() == '\r')) assetId.pop_back();
|
||||
while (!localPath.empty() && (localPath.front() == ' ')) localPath.erase(0, 1);
|
||||
while (!localPath.empty() && (localPath.back() == ' ' || localPath.back() == '\r')) localPath.pop_back();
|
||||
|
||||
// 转换为完整路径
|
||||
std::filesystem::path fullPath = g_textureBaseDir / localPath;
|
||||
g_assetMappings[assetId] = fullPath.string();
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
Log::InfoFmt("TextureReplace: Loaded %d asset mappings", count);
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
// 检查是否需要替换指定资源
|
||||
bool ShouldReplaceAsset(const std::string& assetName, std::string& replacementPath) {
|
||||
auto it = g_assetMappings.find(assetName);
|
||||
if (it != g_assetMappings.end()) {
|
||||
replacementPath = it->second;
|
||||
|
||||
// 检查文件是否存在
|
||||
if (std::filesystem::exists(replacementPath)) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
Log::InfoFmt("TextureReplace: Mapped file not found: %s", replacementPath.c_str());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 从文件加载图片数据
|
||||
bool LoadImageFile(const std::string& filePath, std::vector<unsigned char>& imageData) {
|
||||
FILE* file = nullptr;
|
||||
if (fopen_s(&file, filePath.c_str(), "rb") != 0 || !file) {
|
||||
Log::ErrorFmt("TextureReplace: Failed to open image file: %s", filePath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
long fileSize = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
|
||||
if (fileSize <= 0) {
|
||||
fclose(file);
|
||||
Log::ErrorFmt("TextureReplace: Invalid file size: %ld", fileSize);
|
||||
return false;
|
||||
}
|
||||
|
||||
imageData.resize(fileSize);
|
||||
size_t bytesRead = fread(imageData.data(), 1, fileSize, file);
|
||||
fclose(file);
|
||||
|
||||
if (bytesRead != fileSize) {
|
||||
Log::ErrorFmt("TextureReplace: Failed to read complete file. Expected: %ld, Read: %zu", fileSize, bytesRead);
|
||||
return false;
|
||||
}
|
||||
|
||||
Log::InfoFmt("TextureReplace: Loaded image file: %s (%ld bytes)", filePath.c_str(), fileSize);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 创建Unity Texture2D对象从图片数据
|
||||
void* CreateUnityTexture2DFromImage(const std::vector<unsigned char>& imageData, const std::string& fileName) {
|
||||
if (imageData.empty()) {
|
||||
Log::Error("TextureReplace: Empty image data");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Log::InfoFmt("TextureReplace: Creating Unity Texture2D from %s (%d bytes)", fileName.c_str(), (int)imageData.size());
|
||||
|
||||
void* domain = il2cpp_domain_get();
|
||||
if (!domain) {
|
||||
Log::Error("TextureReplace: Failed to get IL2CPP domain");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t assemblyCount = 0;
|
||||
void** assemblies = il2cpp_domain_get_assemblies(domain, &assemblyCount);
|
||||
if (!assemblies) {
|
||||
Log::Error("TextureReplace: Failed to get assemblies");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* texture2DClass = nullptr;
|
||||
void* imageConversionClass = nullptr;
|
||||
|
||||
// 查找所需的类
|
||||
for (size_t i = 0; i < assemblyCount; i++) {
|
||||
void* assembly = assemblies[i];
|
||||
if (!assembly) continue;
|
||||
|
||||
void* image = il2cpp_assembly_get_image(assembly);
|
||||
if (!image) continue;
|
||||
|
||||
if (!texture2DClass) {
|
||||
texture2DClass = il2cpp_class_from_name(image, "UnityEngine", "Texture2D");
|
||||
}
|
||||
if (!imageConversionClass) {
|
||||
imageConversionClass = il2cpp_class_from_name(image, "UnityEngine", "ImageConversion");
|
||||
}
|
||||
|
||||
if (texture2DClass && imageConversionClass) break;
|
||||
}
|
||||
|
||||
if (!texture2DClass) {
|
||||
Log::Error("TextureReplace: Failed to find Texture2D class");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 创建Texture2D对象
|
||||
void* texture2DObject = il2cpp_object_new(texture2DClass);
|
||||
if (!texture2DObject) {
|
||||
Log::Error("TextureReplace: Failed to create Texture2D object");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 调用构造函数 (2x2, RGBA32)
|
||||
void* ctorMethod = il2cpp_class_get_method_from_name(texture2DClass, ".ctor", 2);
|
||||
if (ctorMethod) {
|
||||
try {
|
||||
int width = 2;
|
||||
int height = 2;
|
||||
void* ctorParams[2] = { &width, &height };
|
||||
void* ctorException = nullptr;
|
||||
|
||||
il2cpp_runtime_invoke(ctorMethod, texture2DObject, ctorParams, &ctorException);
|
||||
if (ctorException) {
|
||||
Log::InfoFmt("TextureReplace: Constructor exception: 0x%p", ctorException);
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
Log::Info("TextureReplace: Exception during constructor call");
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有ImageConversion类,使用LoadImage方法加载实际图片数据
|
||||
if (imageConversionClass) {
|
||||
void* loadImageMethod = il2cpp_class_get_method_from_name(imageConversionClass, "LoadImage", 2);
|
||||
if (loadImageMethod) {
|
||||
try {
|
||||
// 创建byte数组
|
||||
void* byteArrayClass = nullptr;
|
||||
for (size_t i = 0; i < assemblyCount; i++) {
|
||||
void* assembly = assemblies[i];
|
||||
if (!assembly) continue;
|
||||
void* image = il2cpp_assembly_get_image(assembly);
|
||||
if (!image) continue;
|
||||
byteArrayClass = il2cpp_class_from_name(image, "System", "Byte");
|
||||
if (byteArrayClass) break;
|
||||
}
|
||||
|
||||
if (byteArrayClass) {
|
||||
void* byteArray = il2cpp_array_new(byteArrayClass, imageData.size());
|
||||
if (byteArray) {
|
||||
// 复制图片数据到数组
|
||||
memcpy((char*)byteArray + 4 * sizeof(void*), imageData.data(), imageData.size());
|
||||
|
||||
// 调用ImageConversion.LoadImage(texture2D, byteArray)
|
||||
void* params[2] = { texture2DObject, byteArray };
|
||||
void* exception = nullptr;
|
||||
il2cpp_runtime_invoke(loadImageMethod, nullptr, params, &exception);
|
||||
|
||||
if (exception) {
|
||||
Log::InfoFmt("TextureReplace: LoadImage exception: 0x%p", exception);
|
||||
}
|
||||
else {
|
||||
Log::InfoFmt("TextureReplace: Successfully loaded image into Texture2D: 0x%p", texture2DObject);
|
||||
return texture2DObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
Log::Info("TextureReplace: Exception during LoadImage call");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log::InfoFmt("TextureReplace: Created basic Texture2D object: 0x%p", texture2DObject);
|
||||
return texture2DObject;
|
||||
}
|
||||
|
||||
// 加载自定义纹理
|
||||
void* LoadCustomTexture(const std::string& filePath) {
|
||||
std::vector<unsigned char> imageData;
|
||||
if (!LoadImageFile(filePath, imageData)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::filesystem::path path(filePath);
|
||||
return CreateUnityTexture2DFromImage(imageData, path.filename().string());
|
||||
}
|
||||
|
||||
// 清理待处理的替换请求
|
||||
void CleanupPendingReplacements() {
|
||||
unsigned long currentTime = GetTickCount();
|
||||
|
||||
for (auto it = g_pendingReplacements.begin(); it != g_pendingReplacements.end();) {
|
||||
if (currentTime - it->second.timestamp > 60000) { // 超过60秒的请求
|
||||
Log::InfoFmt("TextureReplace: Cleaning up expired replacement request: 0x%p", it->first);
|
||||
it = g_pendingReplacements.erase(it);
|
||||
}
|
||||
else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
#ifndef GAKUMAS_LOCALIFY_TEXTURE_REPLACE_H
|
||||
#define GAKUMAS_LOCALIFY_TEXTURE_REPLACE_H
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace GakumasLocal::TextureReplace {
|
||||
// 初始化纹理替换系统
|
||||
bool Initialize();
|
||||
|
||||
// 加载配置映射
|
||||
bool LoadAssetMappings();
|
||||
|
||||
// 检查是否需要替换指定资源
|
||||
bool ShouldReplaceAsset(const std::string& assetName, std::string& replacementPath);
|
||||
|
||||
// 从文件加载图片数据
|
||||
bool LoadImageFile(const std::string& filePath, std::vector<unsigned char>& imageData);
|
||||
|
||||
// 创建Unity Texture2D对象从图片数据
|
||||
void* CreateUnityTexture2DFromImage(const std::vector<unsigned char>& imageData, const std::string& fileName);
|
||||
|
||||
// 加载自定义纹理
|
||||
void* LoadCustomTexture(const std::string& filePath);
|
||||
|
||||
// 清理待处理的替换请求
|
||||
void CleanupPendingReplacements();
|
||||
|
||||
// 待替换请求结构
|
||||
struct PendingReplacement {
|
||||
void* texture;
|
||||
unsigned long timestamp;
|
||||
};
|
||||
|
||||
// 待替换的请求映射表
|
||||
extern std::map<void*, PendingReplacement> g_pendingReplacements;
|
||||
}
|
||||
|
||||
#endif // GAKUMAS_LOCALIFY_TEXTURE_REPLACE_H
|
||||
|
|
@ -17,7 +17,6 @@ namespace GakumasLocal::Config {
|
|||
int gameOrientation = 0;
|
||||
bool dumpText = false;
|
||||
bool enableFreeCamera = false;
|
||||
bool enableTextureReplace = false;
|
||||
int targetFrameRate = 0;
|
||||
bool unlockAllLive = false;
|
||||
bool unlockAllLiveCostume = false;
|
||||
|
|
@ -27,7 +26,6 @@ namespace GakumasLocal::Config {
|
|||
std::string liveCustomeCostumeId = "";
|
||||
|
||||
bool loginAsIOS = false;
|
||||
bool skipPurchaseInit = true;
|
||||
|
||||
bool useCustomeGraphicSettings = false;
|
||||
float renderScale = 0.77f;
|
||||
|
|
@ -75,14 +73,12 @@ namespace GakumasLocal::Config {
|
|||
GetConfigItem(dumpText);
|
||||
GetConfigItem(targetFrameRate);
|
||||
GetConfigItem(enableFreeCamera);
|
||||
GetConfigItem(enableTextureReplace);
|
||||
GetConfigItem(unlockAllLive);
|
||||
GetConfigItem(unlockAllLiveCostume);
|
||||
GetConfigItem(enableLiveCustomeDress);
|
||||
GetConfigItem(liveCustomeHeadId);
|
||||
GetConfigItem(liveCustomeCostumeId);
|
||||
GetConfigItem(loginAsIOS);
|
||||
GetConfigItem(skipPurchaseInit);
|
||||
GetConfigItem(useCustomeGraphicSettings);
|
||||
GetConfigItem(renderScale);
|
||||
GetConfigItem(qualitySettingsLevel);
|
||||
|
|
@ -133,14 +129,12 @@ namespace GakumasLocal::Config {
|
|||
SetConfigItem(dumpText);
|
||||
SetConfigItem(targetFrameRate);
|
||||
SetConfigItem(enableFreeCamera);
|
||||
SetConfigItem(enableTextureReplace);
|
||||
SetConfigItem(unlockAllLive);
|
||||
SetConfigItem(unlockAllLiveCostume);
|
||||
SetConfigItem(enableLiveCustomeDress);
|
||||
SetConfigItem(liveCustomeHeadId);
|
||||
SetConfigItem(liveCustomeCostumeId);
|
||||
SetConfigItem(loginAsIOS);
|
||||
SetConfigItem(skipPurchaseInit);
|
||||
SetConfigItem(useCustomeGraphicSettings);
|
||||
SetConfigItem(renderScale);
|
||||
SetConfigItem(qualitySettingsLevel);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ namespace GakumasLocal::Config {
|
|||
extern bool useMasterTrans;
|
||||
extern bool dumpText;
|
||||
extern bool enableFreeCamera;
|
||||
extern bool enableTextureReplace;
|
||||
extern int targetFrameRate;
|
||||
extern bool unlockAllLive;
|
||||
extern bool unlockAllLiveCostume;
|
||||
|
|
@ -23,7 +22,6 @@ namespace GakumasLocal::Config {
|
|||
extern std::string liveCustomeCostumeId;
|
||||
|
||||
extern bool loginAsIOS;
|
||||
extern bool skipPurchaseInit;
|
||||
|
||||
extern bool useCustomeGraphicSettings;
|
||||
extern float renderScale;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include <functional>
|
||||
|
||||
// UnityEngine.Color
|
||||
struct Color_t
|
||||
|
|
|
|||
|
|
@ -47,18 +47,18 @@ namespace GkmsResourceUpdate {
|
|||
using namespace concurrency::streams;
|
||||
|
||||
try {
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͬ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ<EFBFBD><EFBFBD>
|
||||
// 打开输出文件流(同步方式)
|
||||
auto outTask = fstream::open_ostream(conversions::to_string_t(outputPath));
|
||||
outTask.wait();
|
||||
auto fileStream = outTask.get();
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD> HTTP <20>ͻ<EFBFBD><CDBB>ˣ<EFBFBD>ע<EFBFBD>⣺<EFBFBD><E2A3BA><EFBFBD> url <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7><EFBFBD><EFBFBD>cpprestsdk <20><><EFBFBD>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD><EFBFBD>
|
||||
// 创建 HTTP 客户端,注意:如果 url 包含完整路径,cpprestsdk 会自动解析
|
||||
http_client client(conversions::to_string_t(url));
|
||||
|
||||
downloading = true;
|
||||
downloadProgress = 0.0f;
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD> GET <20><><EFBFBD><EFBFBD>
|
||||
// 发起 GET 请求
|
||||
auto responseTask = client.request(methods::GET);
|
||||
responseTask.wait();
|
||||
http_response response = responseTask.get();
|
||||
|
|
@ -68,12 +68,12 @@ namespace GkmsResourceUpdate {
|
|||
return false;
|
||||
}
|
||||
|
||||
// <EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD>Ӧͷ<EFBFBD>е<EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD>С<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڣ<EFBFBD>
|
||||
// 获取响应头中的文件大小(如果存在)
|
||||
uint64_t contentLength = 0;
|
||||
if (response.headers().has(L"Content-Length"))
|
||||
contentLength = std::stoull(conversions::to_utf8string(response.headers().find(L"Content-Length")->second));
|
||||
|
||||
// <EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD>Ӧ<EFBFBD>壬<EFBFBD><EFBFBD><EFBFBD>д<EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD>ͬʱ<EFBFBD><EFBFBD><EFBFBD>½<EFBFBD><EFBFBD><EFBFBD>
|
||||
// 读取响应体,逐块写入文件,同时更新进度
|
||||
auto inStream = response.body();
|
||||
const size_t bufferSize = 8192;
|
||||
// std::vector<unsigned char> buffer(bufferSize);
|
||||
|
|
@ -118,13 +118,13 @@ namespace GkmsResourceUpdate {
|
|||
buffer << file.rdbuf();
|
||||
std::string content = buffer.str();
|
||||
|
||||
// ȥ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>β<EFBFBD>ո<EFBFBD>ͻ<EFBFBD><EFBFBD>з<EFBFBD>
|
||||
// 去除首尾空格和换行符
|
||||
auto is_not_space = [](unsigned char ch) {
|
||||
return !std::isspace(ch);
|
||||
};
|
||||
// ȥ<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD>հ<EFBFBD>
|
||||
// 去除前导空白
|
||||
content.erase(content.begin(), std::find_if(content.begin(), content.end(), is_not_space));
|
||||
// ȥ<EFBFBD><EFBFBD>β<EFBFBD><EFBFBD><EFBFBD>հ<EFBFBD>
|
||||
// 去除尾部空白
|
||||
content.erase(std::find_if(content.rbegin(), content.rend(), is_not_space).base(), content.end());
|
||||
resourceVersionCache = content;
|
||||
return content;
|
||||
|
|
@ -204,7 +204,7 @@ namespace GkmsResourceUpdate {
|
|||
g_reload_all_data();
|
||||
GakumasLocal::Log::Info("Update completed.");
|
||||
}
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѹһ<EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>
|
||||
// 仅解压一个文件
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -217,66 +217,6 @@ namespace GkmsResourceUpdate {
|
|||
}).detach();
|
||||
}
|
||||
|
||||
void AutoCheckUpdateFromAPI() {
|
||||
std::thread([]() {
|
||||
try {
|
||||
if (!g_useAPIAssets) {
|
||||
GakumasLocal::Log::Info("Auto update check skipped: API assets disabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
GakumasLocal::Log::Info("Auto checking update from API...");
|
||||
|
||||
auto response = send_get(g_useAPIAssetsURL, 30);
|
||||
if (response.status_code() != 200) {
|
||||
GakumasLocal::Log::ErrorFmt("Auto update check failed: HTTP %d\n", response.status_code());
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = nlohmann::json::parse(response.extract_utf8string().get());
|
||||
|
||||
std::string remoteVersion = data["tag_name"];
|
||||
const auto localVersion = GetCurrentResourceVersion(false);
|
||||
|
||||
if (localVersion == remoteVersion) {
|
||||
GakumasLocal::Log::InfoFmt("Local resource is already up to date (v%s).", localVersion.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
GakumasLocal::Log::InfoFmt("New resource version found: %s -> %s", localVersion.c_str(), remoteVersion.c_str());
|
||||
GakumasLocal::Log::Info("Starting automatic download...");
|
||||
|
||||
if (!data.contains("assets") || !data["assets"].is_array()) {
|
||||
GakumasLocal::Log::Error("API response doesn't contain assets array.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& asset : data["assets"]) {
|
||||
if (!asset.contains("name") || !asset.contains("browser_download_url"))
|
||||
continue;
|
||||
std::string name = asset["name"];
|
||||
if (name.ends_with(".zip")) {
|
||||
std::string downloadUrl = asset["browser_download_url"];
|
||||
|
||||
if (unzipFileFromURL(downloadUrl, gakumasLocalPath.string())) {
|
||||
g_reload_all_data();
|
||||
GakumasLocal::Log::Info("Auto update completed successfully.");
|
||||
}
|
||||
else {
|
||||
GakumasLocal::Log::Error("Auto update failed during download or extraction.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
GakumasLocal::Log::Error("No .zip file found in assets.");
|
||||
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
GakumasLocal::Log::ErrorFmt("Exception occurred in AutoCheckUpdateFromAPI: %s\n", e.what());
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
|
||||
void checkUpdateFromURL(const std::string& downloadUrl) {
|
||||
std::thread([downloadUrl]() {
|
||||
if (unzipFileFromURL(downloadUrl, gakumasLocalPath.string(), "local-files")) {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,5 @@ namespace GkmsResourceUpdate {
|
|||
void saveProgramConfig();
|
||||
std::string GetCurrentResourceVersion(bool useCache);
|
||||
void CheckUpdateFromAPI(bool isManual);
|
||||
void AutoCheckUpdateFromAPI();
|
||||
void checkUpdateFromURL(const std::string& downloadUrl);
|
||||
}
|
||||
Loading…
Reference in New Issue