前言
对照参考表制作需要在论坛的搜索游戏处,拿一份模拟器版本的xp3,garbro解包它
而不是把xp3汇总扔游戏撞库
模拟器版本实时dump出来整理的打包xp3
包含了游戏的全部文件,且未加密,用于恢复电脑版本的参考,免得二次重复性读取游戏还不全(ctrl的情况)
一个个点跑游戏跑断腿吧
如果没有现成的参考副本那就老老实实跑游戏吧
如果你愿意自己手动分类模拟器文件也没关系,模拟器版本大多都是一个包下一堆文件全塞在一个包的目录
慢慢分?耗费时间太长了
所以重点是制作适用于Garbro的通用dat拿到映射表格以快速提取游戏文件
其基本阐述了如何恢复的过程
目的是制作详细教学方便小白入手
说来容易而实际上你要做的事情可不止这么简单
本文补充了大量的具体技术细节
注:本文代码等内容都遵循MIT协议,仅供逆向汉化学习参考,不得用于其他用途
1.使用Python汇总模拟器版本解包后游戏文件
源码如下——豆包生成,
import os
import sys
import time
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
# 自然排序函数
def natural_sort_key(s):
import re
return [int(text) if text.isdigit() else text.lower() for text in re.split(r'(\d+)', s)]
# 核心文件提取逻辑
def extract_files(root_dir, output_path, recursive=True):
skip_files = {'.DS_Store', 'Thumbs.db', 'desktop.ini'}
skip_prefixes = ('~$', '.tmp', 'temp_')
total_count = 0
start_time = time.time()
try:
with open(output_path, "w", encoding="utf-16-le", newline="\n") as f:
for root, dirs, files in os.walk(root_dir):
sorted_files = sorted(files, key=natural_sort_key)
for file in sorted_files:
if file in skip_files or file.startswith(skip_prefixes):
continue
f.write(file + "\n")
total_count += 1
total_time = time.time() - start_time
result_msg = f"成功提取 {total_count} 个文件\n"
result_msg += f"TXT保存路径:{output_path}\n"
result_msg += f"总耗时:{total_time:.1f} 秒"
return True, result_msg
except Exception as e:
error_msg = f"提取失败:{str(e)}\n请检查路径权限或文件名是否有特殊字符"
return False, error_msg
# 主GUI界面
def main_gui():
# 创建主窗口
root = tk.Tk()
root.title("文件列表提取工具")
root.geometry("550x220")
root.resizable(False, False) # 禁止调整窗口大小
# 界面组件
# 标题标签
title_label = ttk.Label(root, text="文件列表提取工具", font=("微软雅黑", 14, "bold"))
title_label.pack(pady=10)
# 进度提示
progress_label = ttk.Label(root, text="点击下方按钮选择文件夹开始提取", font=("微软雅黑", 10))
progress_label.pack(pady=5)
# 进度条
progress_bar = ttk.Progressbar(root, mode='indeterminate', length=450)
progress_bar.pack(pady=5, padx=20)
progress_bar.pack_forget() # 初始隐藏
# 开始提取按钮
def start_extraction():
# 选择目标文件夹
target_dir = filedialog.askdirectory(title="选择要提取文件列表的文件夹")
if not target_dir:
return
# 选择保存路径
save_path = filedialog.asksaveasfilename(
title="保存文件列表",
defaultextension=".txt",
filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")],
initialfile="文件列表.txt"
)
if not save_path:
return
# 更新界面状态
progress_label.config(text="正在提取文件...请耐心等待")
progress_bar.pack(pady=5, padx=20)
progress_bar.start()
root.update_idletasks() # 立即刷新界面
# 执行提取
success, msg = extract_files(target_dir, save_path)
# 恢复界面状态并显示结果
progress_bar.stop()
progress_bar.pack_forget()
if success:
progress_label.config(text="提取完成!可点击按钮继续操作")
messagebox.showinfo("操作成功", msg)
else:
progress_label.config(text="提取失败!请检查错误信息")
messagebox.showerror("操作失败", msg)
start_btn = ttk.Button(
root,
text="选择文件夹并开始提取",
command=start_extraction,
width=30
)
start_btn.pack(pady=15)
# 退出按钮
exit_btn = ttk.Button(
root,
text="退出程序",
command=root.quit,
width=15
)
exit_btn.pack(pady=5)
# 运行主循环
root.mainloop()
# 主程序入口
if __name__ == "__main__":
# 命令行模式:如果传入路径参数,直接执行不显示GUI
if len(sys.argv) == 2:
target_dir = os.path.abspath(sys.argv[1])
if not os.path.exists(target_dir) or not os.path.isdir(target_dir):
print(f"错误:路径无效或不是文件夹 → {target_dir}")
sys.exit(1)
script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
save_path = os.path.join(script_dir, "文件列表.txt")
print(f"命令行模式:开始提取 {target_dir}")
success, msg = extract_files(target_dir, save_path)
print(msg)
sys.exit(0 if success else 1)
# GUI模式:双击运行直接显示主界面
else:
try:
main_gui()
except Exception as e:
print(f"GUI启动失败:{str(e)}")
messagebox.showerror("错误", f"无法启动程序:{str(e)}")
sys.exit(1)
使用方法双击打开py用gui选择路径或
命令行python 程序.py 文件夹路径
创建汇总文件的txt是以utf-16le形式储存,若想查看系统txt直接打开会出现问题,系统自带txt的是以utf-8格式读取,建议subline text来查看
utf-16l采取的原因是游戏内部分文件是以日文来命名的,当使用utf-8格式txt输入cxdec撞库会以乱码的形式扔进去,无法制作获取映射表的
CXDEC只负责把字符串转变为哈希
你即使输入个1145141919810.png也会有hash
2.dump游戏解密
dumpkey源码如下
如果懒得自己保存了,翻到最下面的github下源码js
或者自己愿意动一下手的话复制源码创建txt粘贴内容改名保存为krkr_hxv4_dumpkey.js
/**
* dump wamsoft hxv4 keys (hx decrypt index, cx decrypt index)
* v0.1.1, developed by devseed
*
* usage:
* npm i @types/frida-gum --save
* frida -l krkr_hxv4_dumpkey.js -f dc5ph.exe # frida version 17.2.4
* (the key will show on console, block will dump to control_block.bin)
*
* tested games:
* D.C.5 Plus Happiness ~ダ・カーポ5~プラスハピネス
* エッチで一途なド田舎兄さまと、古式ゆかしい病弱妹 (dlsite, steam)
* KANADE
* 花束を君に贈ろう-Kinsenka-
*/
'use strict'
/**
* @param {ArrayBuffer} buf
*/
function buf2hexstr(buf, sep="") {
const arr = new Uint8Array(buf);
const hexs = [];
for(let i=0; i<arr.length; i++) {
let hex = arr[i].toString(16);
hex = ('00' + hex).slice(-2);
hexs.push(hex);
}
return hexs.join(sep);
}
var dllpath;
var cxtpm_load_flag = false;
// change this to frida breaking change in 17.0
// const LoadLibraryW = Module.getExportByName('kernel32.dll', 'LoadLibraryW');
const LoadLibraryW = Process.getModuleByName('kernel32.dll').getExportByName('LoadLibraryW')
Interceptor.attach(LoadLibraryW, {
onEnter(args) {
dllpath = args[0].readUtf16String();
if(dllpath.search("krkr_") > 0) cxtpm_load_flag = true;
},
onLeave(retval) {
if(cxtpm_load_flag==false) return;
cxtpm_load_flag = false;
let m; // change this to frida breaking change in 17.0
var hmod = Process.findModuleByAddress(ptr(retval.toUInt32()));
console.log(`load ${dllpath} at 0x${hmod.base.toString(16)}`);
// .text:1001F0B0 55 push ebp
// .text:1001F0B1 8B EC mov ebp, esp
// .text:1001F0B3 81 EC D4 00 00 00 sub esp, 0D4h
// .text:1001F0B9 A1 48 B2 0A 10 mov eax, ___security_cookie
// .text:1001F0BE 33 C5 xor eax, ebp
// .text:1001F0C0 89 45 FC mov [ebp+var_4], eax
// .text:1001F0C3 8B 45 14 mov eax, [ebp+key] // [ebp+14h] key, [ebp+18h] nonce
// .text:1001F0C6 53 push ebx
// .text:1001F0C7 56 push esi
// .text:1001F0C8 8B 75 08 mov esi, [ebp+this]
// .text:1001F0CB 57 push edi
// .text:1001F0CC 50 push eax
// .text:1001F0CD 8D 85 7C FF FF FF lea eax, [ebp+state0]
var hxpoint = 0; // decrypt hx index
m = Memory.scanSync(hmod.base, hmod.size, "8B 45 14 53 56 8B 75 08 57 50");
if(m.length == 1) hxpoint = m[0].address;
console.log(`hxpoint at 0x${hxpoint.toUInt32().toString(16)}`);
Interceptor.attach(hxpoint, {
onEnter(args){
if(!hxpoint) return;
let key = this.context.ebp.add(0x14).readPointer().readByteArray(32);
let nonce = this.context.ebp.add(0x18).readPointer().readByteArray(16);
console.log(`* key : ${buf2hexstr(key)}`);
console.log(`* nonce : ${buf2hexstr(nonce)}`);
hxpoint = 0;
}});
// 7B5B3C60 | 55 | push ebp |
// 7B5B3C61 | 8BEC | mov ebp,esp |
// 7B5B3C63 | 83EC 34 | sub esp,34 |
// 7B5B3C66 | A1 48B2647B | mov eax,dword ptr ds:[7B64B248] |
// 7B5B3C6B | 33C5 | xor eax,ebp |
// 7B5B3C6D | 8945 FC | mov dword ptr ss:[ebp-4],eax |
// 7B5B3C70 | 807D 10 00 | cmp byte ptr ss:[ebp+10],0 |
// 7B5B3C74 | 53 | push ebx |
// 7B5B3C75 | 56 | push esi |
// 7B5B3C76 | 8B75 08 | mov esi,dword ptr ss:[ebp+8] |
// 7B5B3C79 | 57 | push edi |
// 7B5B3C7A | 8B7D 0C | mov edi,dword ptr ss:[ebp+C] |
// 7B5B3C7D | 8BD9 | mov ebx,ecx | ecx:"ツ0"
var cxpoint = 0; // decrypt cx content
m = Memory.scanSync(hmod.base, hmod.size, "89 45 fc 80 7D 10 00");
if(m.length == 1) cxpoint = m[0].address;
console.log(`cxpoint at 0x${cxpoint.toUInt32().toString(16)}`);
Interceptor.attach(cxpoint, {
onEnter(args){
if(!cxpoint) return;
let filterkey = this.context.ecx.add(0x8).readByteArray(8);
let mask = this.context.ecx.add(0x10).readU32();
let offset = this.context.ecx.add(0x14).readU32();
let randtype = this.context.ecx.add(0x18).readU8();
let block = this.context.ecx.add(0x20).readByteArray(4096);
let order = this.context.ecx.add(0x3020).readByteArray(0x11);
console.log(`* filterkey : ${buf2hexstr(filterkey)}`);
console.log(`* mask : 0x${mask.toString(16)}`);
console.log(`* offset : 0x${offset.toString(16)}`);
console.log(`* randtype : ${randtype.toString()}`);
console.log(`* order : ${buf2hexstr(order, " ")}`);
File.writeAllBytes("control_block.bin", block);
// order compatible for garbro
const O = new Uint8Array(order);
const S3 = [0, 1, 2];
const S6 = [2, 5, 3, 4, 1, 0];
const S8 = [0, 2, 3, 1, 5, 6, 7, 4];
let O3 = [0, 1, 2];
let O6 = [0, 1, 2, 3, 4, 5];
let O8 = [0, 1, 2, 3, 4, 5, 6, 7];
for (let i=0; i<3; i++) O3[O[14+i]]=S3[i];
for (let i=0; i<6; i++) O6[O[8+i]]=S6[i];
for (let i=0; i<8; i++) O8[O[i]]=S8[i];
console.log(`* PrologOrder (garbro) : ${O3[0]}, ${O3[1]}, ${O3[2]}`);
console.log(`* OddBranchOrder (garbro) : ${O6[0]}, ${O6[1]}, ${O6[2]}, ${O6[3]}, ${O6[4]}, ${O6[5]}`);
console.log(`* EvenBranchOrder (garbro) : ${O8[0]}, ${O8[1]}, ${O8[2]}, ${O8[3]}, ${O8[4]}, ${O8[5]}, ${O8[6]}, ${O8[7]}`);
cxpoint = 0;
}});
}
});
源码:https://github.com/YuriSizuku/GalgameReverse/blob/master/project/krkr/src/krkr_hxv4_dumpkey.js
3.环境配置
电脑需有python
pip install frida-tools
4.将krkr_hxv4_dumpkey.js复制到游戏目录内
Steam版本的游戏提取需去除DRM后使用
这里就拿魔女的夜宴官中来演示吧
附带讲讲
Steamless打开游戏exe

选1257项
点Unpack File
出现绿色字,提取成功
游戏目录会有一个叫SabbatOfTheWitch.exe.unpacked.exe
将Steam通用api复制到游戏目录替换

请注意测试游戏打开时显示这个
覆盖后仍
报错这个然后点确定后弹

或者直接报Malformed exe/dll detected
通常情况下Steam官中脱壳后或HIKARI FIELD CLIENT下载的游戏程序拖入到提取程序会提示
原因是没绕过程序startup与bootstart的验证
例如说柚子社组乐队的新作,有软电池验证,通常用winHex删除KrKr2前面的软电池,直接删了没事,KrKrZ删除了前面的软电池程序的字节,从而让bootstart检测到程序损坏
所以遇到这种情况使用反编译器xdg32那些或者改bootstart也行,工具箱的讲解用32xdg可以快速解决
实在不会做就拿网盘内提供steamapi加经xdg32处理过的游戏程序补丁,打上覆盖即可继续下一步
针对柚子社还有部分krrkZ的程序,下下来的补丁不放心可使用火绒等杀毒软件扫描
若盘内无支持游戏,请评论发issue
5.提取
先提取key再提取游戏哈希目录
命令行运行命令,注意记得改一下参数的exe名字!!!
frida -l krkr_hxv4_dumpkey.js -f 游戏.exe
如果想要保存为txt请使用:frida -l krkr_hxv4_dumpkey.js -f 游戏.exe > output.txt 2>&1
部分系统无法使用具体原因未知?
如遇报错用最上面的指令命令行显示后复制到txt也行
运行后cmd会有如下信息,这就是魔女的夜宴官中的解密参数
不要傻到拿文章得到的这个参数去套别的游戏,那肯定行不通......
然后把窗口的信息全部复制保存到txt里头
____
/ _ | Frida 17.3.2 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://frida.re/docs/home/
. . . .
. . . . Connected to Local System (id=local)
Spawning `SabbatOfTheWitch.exe`...
Spawned `SabbatOfTheWitch.exe`. Resuming main thread!
[Local::SabbatOfTheWitch.exe ]-> load c:\users\用户名\appdata\local\temp\krkr_24b154f5970d_584099171_10304\38a43677e8d5.dll at 0x7c000000
hxpoint at 0x7c01f0c3
cxpoint at 0x7c013c6d
* key : e6662ea4b50ccd083d56e13e0bd52ef3a75048052ccc77d57d1bc5a873e0bf14
* nonce : fefe820b57060e50b7cc2580db04d993
* filterkey : d99230e02623f4a0
* mask : 0x226
* offset : 0x1c8
* randtype : 1
* order : 04 06 02 00 07 01 03 05 03 00 05 04 02 01 01 02 00
* PrologOrder (garbro) : 2, 0, 1
* OddBranchOrder (garbro) : 5, 0, 1, 2, 4, 3
* EvenBranchOrder (garbro) : 1, 6, 3, 7, 0, 4, 2, 5
那么这个参数回保存在游戏目录下的key_output.txt,并还有一个文件是control_block.bin会自动生成在游戏目录下
control_block.bin用于放在编译后的文件夹的GameData\Formats里头
这两个文件Garbro制作参数时会使用到
接下来使用CxdecExtractorLoader拿到加密的文件夹映射表
将游戏程序拖到CxdecExtractorLoader.exe
点第二个加载字符串Hash
游戏目录下的
StringHashDumper_Output会有DirectoryHash.log这个就是我们要的
有部分是空目录对于Windows系统无意义
%EmptyString%##YSig##94D4A97C61498621 %EmptyString%##YSig##94D4A97C61498621 %EmptyString%##YSig##94D4A97C61498621 %EmptyString%##YSig##94D4A97C61498621 %EmptyString%##YSig##94D4A97C61498621 %EmptyString%##YSig##94D4A97C61498621
这几个没用的%EmptyString%带这些的行可以删除掉
游戏开久了就会有这个,反选复制前面的内容覆盖掉即可
Garbro读取HxNames.lst映射表格式要求
文件:
CF9D48435E0122C5CFB2BB9ACA41DAE27996035E5374D2A6B8BDA5ABF9C2FEBC:ama_102_0030.ogg
文件夹:
A174FE004F5BC2DD:bgimage/
说人话就是
经CXDEC加密的文件名:正确文件.png
经CXDEC加密的文件夹名:正确的文件夹名字/
6.撞库
前面提到的用python汇总到的文件名集合也就是文件列表.txt
将其改名为files.txt
https://github.com/YuriSizuku/GalgameReverse/blob/master/project/krkr/src/krkr_hxv4_dumphash.cpp
已编译好dll,要下的看到最后面的网盘工具箱
将version.dll与前面汇总拿到的files.txt复制到游戏目录
然后打开游戏后出现这个弹窗就是在撞库了
等待一会后
[src/krkr_hxv4_dumphash.cpp,216,calc_thread,I] try to calc names in dirs.txt [src/krkr_hxv4_dumphash.cpp,218,calc_thread,I] calculate finish, results in files_match.txt, dirs_match.txt
直接关掉命令窗口就可以快速关掉游戏了
即可在游戏目录找到files_match.txt与dirs_match.txt
files_match.txt使用的是utf-16的编码文本编辑器重新保存txt然后编码改成utf-8编码即可

撞库得到的信息是
001.共通-オナニーマスター.ks.scn,CAF2630573E58914755A99B3444995F3B7FF681F98D2220C7AF9370E5FA29F56
文件,哈希
需要将其转换为“哈希:文件名"的形式方便制作lst映射
文件映射格式转换处理
读取的files.txt编码应当改为utf-8,utf-16le读取报错
import os
import sys
def main():
# 检查命令行参数是否正确
if len(sys.argv) != 2:
print("用法错误!正确格式:python 1.py 输入文件.txt")
print("示例:python 1.py 1.txt")
sys.exit(1)
# 获取命令行传入的输入文件路径
input_file_path = sys.argv[1]
# 输出文件保存到与脚本同一目录,命名为"转换结果.txt"
output_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "转换结果.txt")
try:
# 读取输入文件(支持中文/日文等特殊字符)
with open(input_file_path, "r", encoding="utf-8") as f:
lines = f.readlines()
result = []
for line_num, line in enumerate(lines, 1):
line = line.strip() # 去掉换行符、前后空格
if not line:
continue # 跳过空行
# 分割文件名和哈希值(按逗号分隔,确保只分割1次)
if "," not in line:
print(f"警告:第{line_num}行格式错误(无逗号分隔),已跳过:{line}")
continue
filename, hash_val = line.split(",", 1)
# 替换后缀:将.ks.scn改为.ogg
ogg_filename = filename.replace(".ks.scn", ".ogg")
# 生成目标格式:哈希值:文件名.ogg
target_line = f"{hash_val}:{ogg_filename}"
result.append(target_line)
# 保存结果(覆盖已有文件,编码为utf-8确保兼容性)
with open(output_file_path, "w", encoding="utf-8") as f:
f.write("\n".join(result))
print("转换完成!")
print(f"读取文件:{os.path.abspath(input_file_path)}")
print(f"保存文件:{output_file_path}")
print(f"成功处理 {len(result)} 条数据")
except FileNotFoundError:
print(f"错误:未找到输入文件 {input_file_path}")
print("请检查文件路径是否正确,或文件是否存在")
except Exception as e:
print(f"转换失败:{str(e)}")
if __name__ == "__main__":
main()
使用方法:python 程序.py files_match.txt
与脚本在同一目录下有转换结果.txt

还有一个文件夹路径要处理
文件夹路径格式映射处理
.log的编码是ANSI,应当改成utf-8
import re
import sys
import os
def convert_ysig_to_colon(input_str: str) -> str | None:
"""
核心转换逻辑:
- 正常格式(如 video/##YSig##01E3087E0C79CE02)→ 转换为 签名:资源类型
- 空类型格式(%EmptyString%##YSig##xxx)→ 直接删除
- 非法格式 → 跳过(控制台提示)
"""
input_str = input_str.strip()
if not input_str:
return None
# 匹配正常YSig格式:[资源类型]##YSig##[十六进制签名]
normal_pattern = r'^([^#%]+)##YSig##([0-9A-Fa-f]+)$'
normal_match = re.match(normal_pattern, input_str)
if normal_match:
resource_type = normal_match.group(1)
signature = normal_match.group(2).upper() # 签名统一转大写
return f"{signature}:{resource_type}"
# 匹配空类型格式:直接返回None(删除)
empty_pattern = r'^%EmptyString%##YSig##[0-9A-Fa-f]+$'
if re.match(empty_pattern, input_str):
return None
# 非法格式:返回None并提示
print(f"警告:跳过非法格式行:{input_str}")
return None
def main():
# 检查命令行参数
if len(sys.argv) != 2:
print("错误:用法错误!正确用法:python 1.py 路径.txt")
sys.exit(1)
# 获取输入文件路径
input_file_path = sys.argv[1]
# 检查文件是否存在
if not os.path.exists(input_file_path):
print(f"错误:文件 {input_file_path} 不存在!")
sys.exit(1)
# 读取输入文件(UTF-16LE编码,适配DirectoryHash.log)
try:
with open(input_file_path, 'r', encoding='utf-16le') as f:
input_lines = [line.strip() for line in f] # 读取所有行并去除首尾空格
except Exception as e:
print(f"错误:读取文件失败:{str(e)}")
sys.exit(1)
# 批量转换(过滤空类型和非法格式)
result_lines = []
for line in input_lines:
converted = convert_ysig_to_colon(line)
if converted:
result_lines.append(converted)
# 生成输出文件路径:强制保存到1.py所在目录
py_file_dir = os.path.dirname(os.path.abspath(__file__)) # 1.py的绝对路径目录
input_file_name = os.path.basename(input_file_path) # 输入文件的文件名(含后缀)
name_without_ext, ext = os.path.splitext(input_file_name)
output_file_path = os.path.join(py_file_dir, f"{name_without_ext}_output{ext}") # 输出文件在1.py目录下
# 写入输出文件(UTF-8编码,兼容大多数场景)
try:
with open(output_file_path, 'w', encoding='utf-8') as f:
f.write('\n'.join(result_lines))
except Exception as e:
print(f"错误:写入文件失败:{str(e)}")
sys.exit(1)
# 输出统计信息
total_input = len(input_lines)
total_valid = len(result_lines)
total_deleted = total_input - total_valid
print("处理完成!")
print(f"统计:输入 {total_input} 行 → 有效输出 {total_valid} 行 → 删除 {total_deleted} 行(空类型/非法格式)")
print(f"输出文件:{output_file_path}")
if __name__ == "__main__":
main()
使用方法:python 程序.py DirectoryHash.log
那么把文件夹路径复制到文件映射表下面加回去复制粘贴,改一下名字HxNames.lst
往下添加就是
7.Garbro-Mod的制作dat编写
Github自行搜索Garbro-Mod下源码编译修改
项目遵循MIT
https://github.com/nanami5270/GARbro-Mod/blob/master/LICENSE
在源码下的GARbro-Mod/SchemeTool/Program.cs
此cs正是要编辑的用于创建dat的如果新游戏的dat数据没有,那么可以自己动手手动创建,维护这个lst的过程十分caodan,要是发布了新的patch,就只能跟踪TJS2100这个恶心玩意了
本代码个人理解范围编写部分内容,如写得像屎山免责,问怎么写得就是我问ai瞎写,免责
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Remoting.Metadata.W3cXsd2001;
using System.Text;
using System.Threading.Tasks;
namespace SchemeTool
{
class Program
{
static void Main(string[] args)
{
// Load database
using (Stream stream = File.OpenRead(".\\GameData\\Formats.dat"))
{
GameRes.FormatCatalog.Instance.DeserializeScheme(stream);
}
#if false
using (Stream stream = File.Create(".\\GameData\\Formats.json"))
{
GameRes.FormatCatalog.Instance.SerializeSchemeJson(stream);
return;
}
#endif
GameRes.Formats.KiriKiri.Xp3Opener format = GameRes.FormatCatalog.Instance.ArcFormats
.FirstOrDefault(a => a is GameRes.Formats.KiriKiri.Xp3Opener) as GameRes.Formats.KiriKiri.Xp3Opener;
if (format != null)
{
GameRes.Formats.KiriKiri.Xp3Scheme scheme = format.Scheme as GameRes.Formats.KiriKiri.Xp3Scheme;
// Add scheme information here
#if true
byte[] cb = File.ReadAllBytes(@"control_block.bin");//这里control_block.bin需放入编译后的文件夹GameData\Formats里头
uint[] cb2 = new uint[cb.Length / 4];
Buffer.BlockCopy(cb, 0, cb2, 0, cb.Length);
for (int i = 0; i < cb2.Length; i++)
cb2[i] = ~cb2[i];
var cs = new GameRes.Formats.KiriKiri.CxScheme
{ //将提取到的东西选中并替换掉输入二字
Mask = 输入,//输入mask其定位于第四个* 方便快速定位内容
Offset = 输入,//输入Offset其定位于第五个*
PrologOrder = new byte[] { 输入 },//如下以此类推
OddBranchOrder = new byte[] { 输入 },
EvenBranchOrder = new byte[] { 输入 },
ControlBlock = cb2.ToArray()
};
var crypt = new GameRes.Formats.KiriKiri.HxCrypt(cs);
crypt.RandomType = 0;
crypt.FilterKey = 0x0090d997b400a9b5;
crypt.NamesFile = "HxNames.lst";//这个文件需放入编译后的文件夹GameData\Formats里头
var keyA1 = SoapHexBinary.Parse("输入key").Value;//第一个*key
var keyA2 = SoapHexBinary.Parse("输入nonce").Value;//第二个* nonce
var keyB1 = SoapHexBinary.Parse("输入key").Value;//第一个*key
var keyB2 = SoapHexBinary.Parse("输入nonce").Value;//第二个* nonce
crypt.IndexKeyDict = new Dictionary<string, GameRes.Formats.KiriKiri.HxIndexKey>()
{
{ "data.xp3", new GameRes.Formats.KiriKiri.HxIndexKey { Key1 = keyA1, Key2 = keyA2 } },
{ "update.xp3", new GameRes.Formats.KiriKiri.HxIndexKey { Key1 = keyB1, Key2 = keyB2 } },
};
#else
GameRes.Formats.KiriKiri.ICrypt crypt = new GameRes.Formats.KiriKiri.XorCrypt(0x00);
#endif
// scheme.KnownSchemes.Add("game title", crypt);
}
var gameMap = typeof(GameRes.FormatCatalog).GetField("m_game_map", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)
.GetValue(GameRes.FormatCatalog.Instance) as Dictionary<string, string>;
if (gameMap != null)
{
// Add file name here
gameMap.Add("游戏.exe", "游戏标题");
}
// Save database
using (Stream stream = File.Create(".\\GameData\\Formats.dat"))
{
GameRes.FormatCatalog.Instance.SerializeScheme(stream);
}
}
}
}
直接全选复制粘贴到cs后编辑一下得到的密钥
直接文本编辑器打开cs全选覆盖粘贴
然后使用VS2026编译,在这之前可以阅览一下推荐安装的东西,还有一个较大的硬盘用于储存依赖头文件等
8.编译方法
下一个VScode2026,下好头文件依赖,不过我也顺带更新一下记录一下过程吧
选择
版本Community2026
勾选
C++的桌面开发
VisualStudio扩展开发
双击打开sln或打开VS2026拖入sln
版本选Release不要选预览版还有Debug版本,适用处理器也无需修改
ctrl+shift+B快捷生成解决方案
9.文件补丁工具箱
☁️全部文件/
└── 📂软件/
├── 📂xdg32处理/
│ ├── 📂Yuzusoft
│ │ ├── 星光咖啡馆📦️
│ │ ├── 新作-柠檬即兴曲(组一辈子的神人乐队)名字待定📦️
│ │ ├── 天使纷扰📦️
│ │ ├── 千态万花📦️
│ │ ├── 魔女的夜宴📦️
│ │ └── 谜语小丑📦️
│ └── xdg32如上操作即可去除.mp4 ▶︎
├── 📂Steamless/
│ ├── 配置勾选1257.png 🖻
│ └── Steamless.v3.1.0.5...by.atom0s.zip📦️
└── 📂注入/
├── 获取文件哈希对应/
└── 获取解密密钥/
https://pan.baidu.com/s/12Q-cQh9v3eZjlhQ7O4-YFQ
提取码: 4btt


大佬我是少东西了吗?为啥说tee不是命令?

更改了version.dll好像无法运行游戏了#
files_match.txt只有60条是正常的吗
dirs_match.txt里面是空的
这些都是正常的吗?~