恋爱,我就借走了steam版本是如何变为学习版本的

开发技术
21
kinotern

KiriKiri 引擎 Steam 插件加载失败的事件分析与去 Steam 化改造

摘要

一台 Windows 主机上的 Krkrz 游戏《恋爱,我就借走咯》在启动时弹出两个错误:

一是"无法定位程序输入点 SteamAPI_RestartAppIfNecessary 于动态链接库 krkrsteam.dll 上"

二是脚本层抛出 Cannot load Plugin krkrsteam.dll。

本文记录了从日志、脚本、PE 导入表三层定位故障的过程。

分析表明,故障源是游戏根目录的 steam_api.dll 被替换成了一个基于 Detours 的注入器,该文件不导出任何 Steam API 符号,导致插件在加载阶段就失败,

根本未进入插件代码。原版 Valve 库仍以 steam_api.dll.bak 的形式留在同一目录。

在此基础上,本文给出并实现了一套脱离 Steam 客户端的改造方案:

停用两个 Steam 相关插件,用纯 TJS 桩对象顶替原生 Steam 类

删除硬编码的客户端校验段,并通过新增归档包覆盖脚本层。

改造不触碰游戏本体与原有补丁包。

关键词:KiriKiri;Krkrz;PE 导入表;Steamworks;XP3 归档;逆向分析


1 问题的提出

1.1 现象

用户描述的操作是"打了 steamapi 补丁"。之后启动 KoiKari.exe,出现两个先后不同的对话框。

第一次是加载器报错:无法定位程序输入点 SteamAPI_RestartAppIfNecessary 于插件 krkrsteam.dll 上。第二次启动则变成脚本异常,提示 script exception : Cannot load Plugin krkrsteam.dll。

两个错误都来自同一个加载阶段:Windows 加载器的导入解析。一个由加载器直接报出,另一个由脚本层包装后报出。游戏本体与补丁包是否损坏,要用数据判断。

1.2 现场清单

分析开始前记录目录状态,作为后续比对的基准。

文件字节数时间戳初步判断
Koikari.exe4,807,1682026-11-11 21:39PE32,Krkrz 1.3.3.7
steam_api.dll392,1922025-05-29 23:50学习补丁
steam_api.dll.bak278,1122026-11-11 21:39原版补丁
krkrsteam.dll(插件目录)161,792无Steam 插件
patch.xp32,727,9992026-11-11 21:39补丁归档
data.xp32,899,952,4712026-11-11 21:50主数据
adult.xp32,040,430,6492026-11-11 21:48成人内容包

目录中另有 Koikari.exe.asm(86 MB)、Koikari.exe.c(18 MB)、Koikari.exe.i64(47 MB)三个文件,是 IDA 既有产物,可直接用于交叉验证。krkr.console.log 记录了崩溃当次的完整调用栈。


2 背景:Krkrz 的 Steam 支持放在哪里

KiriKiri 是吉里吉里系列的脚本引擎。

Krkrz 为其 32 位 Windows 延续分支,脚本语言为 TJS2,图像与音频由插件提供。

游戏的 Steam 集成由两层组成:原生插件 krkrsteam.dll,以及一个 TJS 类 Steam。

krkrsteam.dll 是一个标准的 TVP 插件,导出 V2Link 与 V2Unlink。

引擎调用 V2Link 时传入 iTVPFunctionExporter 指针,插件再通过它从宿主取到 TVPRegisterNativeClass 一类函数,注册 Steam 原生类。

一切 Steamworks 调用都发生在插件内部,因此插件自身必须静态导入 steam_api.dll 的若干导出。

Koikari.exe 与 Steam 无关。

用 pefile 解析其导入表,共 16 个 DLL,从 KERNEL32 到 MFPlat,没有一项涉及 steam。

在 86 MB 的 .asm 全文检索 steam,零命中。Steam 逻辑全部集中在插件里。

Steamworks SDK 提供两套入口。旧版为 SteamAPI_Init,新版(1.57 之后)改为 SteamInternal_SteamAPI_Init,并配套 SteamInternal_ContextInit、SteamInternal_FindOrCreateUserInterface 等内部函数。krkrsteam.dll 导入的正是新版集合,这一点决定了后续所有兼容性判断。


3 分析方法

工具链分三块,分别管 PE 结构、反汇编与日志对照。

导入导出表解析使用 Python 3.14 与 pefile 2024.8.26。反汇编使用 MSYS2 提供的 binutils(objdump -d -M intel)。

字符串提取分两路:ASCII 用 Encoding.ASCII,UTF-16LE 用 Encoding.Unicode,因为 TJS 类成员名、错误文本多按宽字符存放。

IDA 的两个既有产物承担了交叉验证。

Koikari.exe.asm 头部记录了输入 SHA256、编译器版本与 tvpwin32.pdb 的构建路径,可以确认 exe 的构建来源。steam_api.dll.bak.asm 则用于比对导出集合。

日志分析放在最前面,因为它给出最直接的失败位置。


4 定位过程

4.1 日志给出调用点

krkr.console.log 末尾的异常段记录了寄存器状态与调用链:

22:05:38 ==== An exception occured at k2compat.tjs(118)[(function) hookPluginLink], VM ip = 104 ====
22:05:38 #(118) 			r = orig(dll, *);
22:05:38 %-6=(string)"krkrsteam"  %-4=(string)"krkrsteam.dll"
22:05:38 Script exception raised
script exception : Cannot load Plugin krkrsteam.dll
22:05:38 trace : k2compat.tjs(121)[(function) hookPluginLink] <-- ... <-- override.tjs(22)[(top level script) global] <-- initialize.tjs(312) ...

override.tjs 第 22 行是 Plugins.link("krkrsteam.dll");。k2compat.tjs 的 hookPluginLink 只是包装层,它把底层失败翻译成脚本异常。真正失败的位置在 Windows 加载器里。

4.2 脚本层暴露全部校验逻辑

反编译得到的 override.tjs 把 Steam 相关代码集中在一处,共 136 行。关键片段:

tjs
if (true) {
    Plugins.link("cconv.dll");
    Plugins.link("krkrsteam.dll");
    Plugins.link("DrawDeviceForSteam.dll");
}

if(Steam.restartAppIfNecessary(4845020)) {
	System.exit(0);
}
if(System.getArgument("-steam") != "no" && !Steam.initializeSteamAPI()) {
	System.inform("请先启动 Steam 并登录后再运行游戏。");
	System.exit(1);
}

脚本给出三条关键信息。AppID 是 4845020,写死在脚本里而不是二进制里。启动参数 -steam no 只能跳过 initializeSteamAPI(),跳不过 Plugins.link,也跳不过 restartAppIfNecessary,所以加参数无效。客户端不在线时,restartAppIfNecessary 返回真会直接 System.exit(0),由 Steam 客户端重新拉起游戏。

4.3 插件需要哪些符号

用 pefile 读 krkrsteam.dll 的导入表,来自 steam_api.dll 的条目共 9 个:

IAT 地址符号
0x1001c138SteamAPI_RestartAppIfNecessary
0x1001c13cSteamAPI_RegisterCallback
0x1001c140SteamInternal_FindOrCreateUserInterface
0x1001c144SteamAPI_RunCallbacks
0x1001c148SteamAPI_GetHSteamUser
0x1001c14cSteamAPI_Shutdown
0x1001c150SteamInternal_ContextInit
0x1001c154SteamAPI_UnregisterCallback
0x1001c158SteamInternal_SteamAPI_Init

九个符号都在,缺一个都不行。Windows 加载器解析导入表时按序进行,第一个缺失的符号就会终止加载。

4.4 三个 steam_api.dll 的对照

把目录里的两个 DLL 一起解析,差异列在下表。

文件字节数导出数量SteamAPI_RestartAppIfNecessarySteamInternal_SteamAPI_Init
steam_api.dll(当前)392,1920 个 Steam 符号无无
steam_api.dll.bak278,1121,096有有

当前 DLL 的字符串表里搜不到任何一个 SteamAPI_ 前缀的符号。它的导入表是:

kernel32.dll / user32.dll / version.dll / winhttp.dll / winmm.dll
steamapicheckbypass.dll

节表包含 .detourc、.detourd 两个非标准节,这是 Microsoft Detours 的编译特征。‘字符串表里的 SteamAPICheckBypass 确认了它的身份:

一个注入器,作用是把别的模块挂进目标进程,本身不实现 Steam 接口。

它依赖的 steamapicheckbypass.dll 在本目录里并不存在。

把 .bak 拆开看,1,096 个导出里包含 RestartAppIfNecessary、SteamInternal_SteamAPI_Init、SteamInternal_ContextInit、SteamInternal_CreateInterface、SteamInternal_FindOrCreateUserInterface 等全套符号。这正是 32 位 Valve 原版库。

4.5 插件是否被改过

需要排除"插件被打过补丁"的可能。krkrsteam.dll 只有 4 个标准节(.text、.rdata、.data、.reloc),没有附加节,没有加壳特征。导出表为标准的四个条目:V2Link、V2Unlink 及两个 stdcall 别名。PDB 路径保留为 krkrsteam.pdb。

字符串表里保留着官方的错误文本 Steam must be running to play this game (SteamAPI_Init() failed),以及 Received stats and achievements from Steam、Stored Achievement for Steam 等成就模块文案。反汇编 0x100031e0 处的转发函数,内容是 jmp DWORD PTR ds:0x1001c138,直通导入槽,没有被改成 xor eax,eax; ret。真要绕过授权,改这里最省事,而它保持原样。

结论是插件为官方原版,构建时间 2026-03-25。


5 根因

排除法与比对法的结果一致:故障落在用户替换进去的 steam_api.dll 上。

游戏本体、补丁包、插件三项都排除了。

加载 krkrsteam.dll 时,Windows 的导入解析器需要从 steam_api.dll 的导出表里找到那 9 个名字。当前文件一个都没有,解析在第一个符号处失败,弹出"无法定位程序输入点"。插件代码从未执行,V2Link 从未被调用,因此也没有任何日志来自插件本身。

脚本层的 Cannot load Plugin krkrsteam.dll 是这次失败的二次表现:Plugins.link 抛出异常,hookPluginLink 未做容错,异常一路上抛到 startup,脚本初始化中断。

补充一个与故障无关但影响修复路径的事实:本机 6 个 Steam 库目录中都没有 appmanifest_4845020.acf。也就是说,即便换回原版库,RestartAppIfNecessary 仍会返回真并退出。用户的目标是离线运行,这一点必须在方案里体现。


6 改造方案

6.1 三条路的取舍

第一条是换回 steam_api.dll.bak。这能消掉报错,但会立刻转入授权检查,对没有该游戏的账号没有意义。

第二条是使用 Steam 模拟器(Goldberg 一类)。它提供完整的导出集合,RestartAppIfNecessary 返回假,SteamInternal_SteamAPI_Init 返回真,成就写入本地文件。这条路能保住成就系统,代价是要引入第三方二进制,且必须选带 SteamInternal_SteamAPI_Init 的新版构建,旧构建没有这个符号,同样会失败。

第三条是脚本层剥离。停用两个插件,删掉校验段,让 Steam 相关调用落空或静默。游戏本体、补丁包、steam_api.dll 三者都不动。用户明确表示不需要云存档与成就,这条路代价最小、可回滚、不引入外部二进制。

本文实现第三条。

6.2 改造点

原始 override.tjs 136 行,改造后 121 行,编码保持 UTF-8 with BOM,换行保持 CRLF,字节数从 4,697 变为 4,834。改动集中在五处。

插件加载。注释掉 krkrsteam.dll 与 DrawDeviceForSteam.dll 两行,保留 cconv.dll。这两个插件不被 link,就不会有加载动作,steam_api.dll 是好是坏都不再影响启动。

校验段。整段删除 restartAppIfNecessary 与 initializeSteamAPI 的调用。没有原生类,这两行会直接报"成员不存在",必须删。

Steam 对象。脚本别处仍有 Steam. 调用。systembutton.ks 第 199 行有 Steam.triggerScreenshot();,MS_LayCre.tjs 第 103 行同样如此。两处都包在 try/catch 里,异常会被吞掉,但会弹出"截图失败,请确认 Steam 正在运行"。为了让按钮安静失效,用一个 TJS 类顶替原生类:

tjs
class SteamStubClass
{
    var initialized = true;
    var appId = 4845020;
    function restartAppIfNecessary(appId) { return false; }
    function setAchievement(id) { return true; }
    function triggerScreenshot() { }
    ...
}
global.Steam = new SteamStubClass();

initialized 置真,restartAppIfNecessary 返回假,setAchievement 返回真,截图函数留空,其余方法按签名补齐。

这样 typeof global.Steam != "Object" 的检查全部通过,调用不抛异常。

成就函数。SteamSetAchievement 与三个队列函数保留签名,内部改为直接返回。保留签名是为了避免别处调用时报"成员不存在",改成空实现是为了让成就逻辑静默。SteamCheckAllRouteClear 原样保留,它内部只调用 SteamSetAchievement。

渲染设备。KAGWindow_config_override 原本是 drawDevice = new DrawDeviceForSteam()。改成空实现后,KAG 使用自带的默认设备。ECPlugin_Window.ks 第 320 行的 recreateDrawDevice 在调用前检查 typeof dd.recreate != "undefined",默认设备没有该成员,函数直接跳过,不会报错。

括号配平校验结果为 {} 26 比 26,() 71 比 71。

6.3 覆盖机制

改造后的 override.tjs 需要作为 system/override.tjs 这个条目写入归档。该名字是归档内的条目名,与磁盘目录无关。它的优先级要高于 data.xp3 里的同名文件。有两种做法。

整体重打包 patch.xp3 更保险,但原包里有 20 个条目(ECPlugin_Window.ks、systembutton.ks、initialize.tjs、ev703_1.png 等),漏掉任何一个都会丢失补丁层修复。

新增 patch2.xp3 只装这一个文件,不动原包。这条做法依赖引擎的归档枚举行为。检查 Koikari.exe 的字符串表,存在 *.* 与 data.xp3 两个宽字符常量,说明加载逻辑靠 FindFirstFile 枚举目录,再按扩展名筛选。固定文件名不在判断条件里。枚举按名称排序,patch2.xp3 排在 patch.xp3 之后(第 6 个字符 '2' 的码位 0x32 大于 '.' 的 0x2E),后加入的归档在查找链中优先。目录内没有 patch.xp3.sig 一类的签名文件,加包不会触发校验。

两种做法的回滚都简单:前者恢复备份,后者删除新增的 xp3。


7 结果与残余风险

改造后游戏路径上不再出现 steam_api.dll 的任何导入解析,与 Steam 客户端的交互为零。

存档仍写入 savedata,云同步不存在,因此换机器需要手动拷贝。

成就系统失效,SteamSetAchievement 调用全部落空,游戏内的成就界面可能显示为空。截图按钮点击后无响应,不再弹出错误提示。

-steam no 参数失去意义,可加可不加。

风险集中在归档层。若该 Krkrz 构建的查找顺序为"先加入优先",patch2.xp3 不会覆盖 patch.xp3 里的同名文件。判别方法简单:启动一次,查看 krkr.console.log 是否仍出现 hookPluginLink 异常。仍有异常就退回整体重打包方案。

另一个需要避开的操作是把改造后的文件放到其他 Krkrz 游戏的目录。文件里写死 4845020、路线判定与中文标题,落到别的游戏会直接报错。


8 安全模型假设

本节列出上述结论依赖的前提,并说明改造动过哪些信任边界。


权限与范围。分析对象是一台主机上的单机游戏目录,操作者对该目录有完全控制权。

整个过程以只读方式解析二进制,唯一写入的是 override.tjs 的副本,原文留在解包目录内。没有改动系统目录、注册表与 Steam 客户端安装目录。


工具可信性。pefile、objdump 与 IDA 的既有产物被当作可信输入,但不作为唯一依据。导入表结论由 pefile 解析与 objdump 反汇编互证,导出表结论由 pefile 读数与 .asm 文本比对。四条证据链里只要有一条来自运行期,就优先采信运行期。本例中 krkr.console.log 的异常栈与静态分析结果一致,三方才没有分歧。


未执行假设。注入器 steam_api.dll 只做了静态解析,没有加载进任何进程。Detours 类模块在 DllMain 里执行初始化,静态分析覆盖不到那部分行为,因此本报告不对它的实际功能作运行期断言,只依据导入表、节表与字符串判定它不是 Steam API 实现。要坐实它的行为,需要放进隔离环境跑一次 API 监视,这超出本文范围。


归档完整性。目录内没有 .sig 签名文件,XP3 归档不携带完整性校验。本方案靠这一点实现覆盖,同一个事实也意味着任何对游戏目录有写权限的本地进程都能替换脚本、改变游戏行为。信任模型上,游戏目录的写权限等价于本地任意 TJS 执行。这是 Krkrz 一类引擎的既有属性,改造前后没有变化。若目录被暴露给低权限进程或网络共享,风险来自那个暴露面,与补丁无关。


信任边界的变动。换回原版库不引入新组件,信任面保持原样。Steam 模拟器方案引入第三方二进制,该文件在游戏进程内执行任意代码,等同于信任其作者与构建链,需要独立校验来源与导出表。脚本层剥离写入的是纯文本 TJS,由引擎解释执行,不引入外部二进制,不访问网络。


安全属性的增减。改造绕开的是客户端授权检查,属于本地 DRM 层,不触碰系统权限、内核对象与网络监听。游戏进程不提权,不开端口,不写注册表。减项是成就与云存档,两者本来就依赖客户端在线,离线场景下等于不存在。


假设失效的情形。若引擎对归档做签名校验,patch2.xp3 会被拒绝;若本体被 Steam DRM 包装,替换脚本可能触发完整性检查。实测两条都不成立:目录内没有签名文件,本体导入表里也没有 DRM 包装模块。换到其他 Krkrz 作品时,这两条要重新验证一遍再动手。

这封包没加密,sig也没有,exe没加壳,我喜欢,良心厂商!


9 结论

本例的故障由一次文件替换错误引起。引擎、本体、补丁三项都能排除。

定位路径可以复现:日志给出脚本行号,脚本文本给出 AppID 与校验逻辑,PE 导入表给出所需符号集合,导出表比对则确认替换文件不具备这些符号。

四步之内根因确定,不需要动态调试。

由此得到一条对同类问题通用的判据:当报错文本为"无法定位程序输入点 X 于 Y.dll 上"时,要查的是 Y 的依赖。逐个核对 Y 的导入表中每个符号是否存在于依赖模块的导出表里,就能定位。本例中 Y 依赖的那个文件被换成了一个注入器,它与 Steam 没有任何关系。

改造本身只动了脚本层 121 行文本,未触及 4.8 MB 的本体、2.7 MB 的补丁包与 161 KB 的插件。删除一个 xp3 就能回到改造前的状态,这是该方案的主要价值。


附录 A 关键地址与数据

krkrsteam.dll 分析数据,ImageBase 为 0x10000000:

项目值
Machine0x14C(i386)
TimeDateStamp1774436200(2026-03-25 10:56:40 UTC)
节.text 0x1A638、.rdata 0x9388、.data 0x2224、.reloc 0x1E78
V2Link0x10009300
V2Unlink0x10009340
Steam.initializeSteamAPI 实现0x10002530
调用 SteamInternal_SteamAPI_Init0x10002565
注册三个回调0x100025ac(0x44D、0x44E、0x44F)
初始化失败分支0x10002705
restartAppIfNecessary 转发0x100031e0
统计与成就同步0x10006740

回调 ID 0x44D、0x44E、0x44F 分别对应 UserStatsReceived_t、UserStatsStored_t、UserAchievementStored_t(基值 1101)。

附录 B 复现步骤

  1. 记录目录内 steam_api.dll 与 steam_api.dll.bak 的字节数、导出数量。
  2. 解析 krkrsteam.dll 导入表,提取 steam_api.dll 相关条目。
  3. 对比两个候选库是否包含上述符号,选择缺失项最少的版本。
  4. 检查 override.tjs 的 AppID 与校验段。
  5. 按 6.2 节修改脚本,按 6.3 节覆盖,启动后读取 krkr.console.log 验证。

成果展示


// ■DLLプラグイン追加 ==================================================================
Plugins.link("krglhwebp.dll");
Plugins.link("layerExDraw.dll");
Plugins.link("varfile.dll");
Plugins.link("KaichoTrans.dll");
Plugins.link("addFont.dll");
Plugins.link("scriptsEx.dll");
Plugins.link("dirlist.dll");
Plugins.link("extNagano.dll");
Plugins.link("windowEx.dll");
Plugins.link("layerExImage.dll");
Plugins.link("ExtKAGParser.dll");
Plugins.link("csvParser.dll");
Plugins.link("kropus.dll");
Plugins.link("wuvorbis.dll");
Plugins.link("extrans.dll");
Plugins.link("fstat.dll");
if (true) {
    Plugins.link("cconv.dll");
    // ■■■ Steam 非依存版:以下 2 行已停用 ■■■
    // Plugins.link("krkrsteam.dll");
    // Plugins.link("DrawDeviceForSteam.dll");
}

// =====================================================================================
// Steam 非依存(离线)版补丁
// 原版在此处把 krkrsteam.dll 挂进引擎,并用 Steam.restartAppIfNecessary(4845020) +
// Steam.initializeSteamAPI() 强制要求 Steam 客户端在线,否则直接 System.exit()。
// 本版删掉这两段,并用纯 TJS 桩对象替代 native 的 Steam 类,
// 让脚本层残留的 Steam.xxx() 调用(systembutton.ks / MS_LayCre.tjs 的
// Steam.triggerScreenshot())不抛异常、不弹"截图失败"提示。
// =====================================================================================
class SteamStubClass
{
    var initialized      = true;
    var appId            = 4845020;
    var available        = false;
    var achievementsCount = 0;

    function restartAppIfNecessary(appId) { return false; }
    function requestInitialize()          { return true; }
    function initializeSteamAPI()         { return true; }

    function setAchievement(id)   { return true; }   // 离线版:成就视为已提交,不落盘
    function getAchievement(id)   { return false; }
    function clearAchievement(id) { }
    function getLanguage()        { return "schinese"; }

    function triggerScreenshot()  { }                // 离线版不截图(Steam overlay 专用)
    function writeScreenshot()    { return false; }
    function hookScreenshots(b)   { return false; }
    function hookBroadcasting(b)  { return false; }
    function isBroadcasting()     { return false; }

    function isDlcInstalled(id)   { return true; }
    function getDLCCount()        { return 0; }
    function getDLCData(i)        { return null; }
}
global.Steam = new SteamStubClass();

function ensureSteamInitialized() {
	// 离线版:桩对象永远可用
	return true;
}

// 以下成就队列函数保留签名,防止别处调用时报 "成员不存在"
function SteamQueueAchievement(id) { return; }
function SteamRemovePendingAchievement(id) { return; }
function SteamRetryPendingAchievements() { return; }

function SteamSetAchievement(id) {
    // 离线版:不写 Steam、不入队,直接忽略
    return;
}

// 2=絵未 3=八純 4=ちなこな 5=椿 6=咲希 7=桃子
// depot(全年龄)版には咲希ルートが無いため、RouteClear6は要求しない
function SteamCheckAllRouteClear() {
    var routes = [sf.RouteClear2, sf.RouteClear3, sf.RouteClear4,
                  sf.RouteClear5];
    if (!lf.DM_DepotVer)
        routes.add(sf.RouteClear6);
    routes.add(sf.RouteClear7);
    for (var i = 0; i < routes.count; i++)
        if (routes[i] !== true) return;

    // CG鑑賞: 現在の構成に実在するCGグループをすべて閲覧済みか
    // (depot版は成人版専用CGが存在しないため、自動的にその分は除外される)
    var CGList = CalcPL.Calc_CG_Num();
    for (var i = 0; i < CGList.count; i++)
        if (!sf.CGDB[CGList[i]]) return;

    // H回想: depot版は回想自体が無い(lf.EX_SCENEInfoが空)ので判定しない
    if (!lf.DM_DepotVer) {
        if (lf.EX_SCENEInfo === void || lf.EX_SCENEInfo.count == 0) return;
        for (var i = 0; i < lf.EX_SCENEInfo.count; i++)
            if (sf.SCENEDB[i] === void || !sf.SCENEDB[i][0]) return;
    }

    SteamSetAchievement("acv45");
}

function KAGWindow_config_override()
{
	// 原実装: if (isMain) drawDevice = new DrawDeviceForSteam();
	// Steam 非依存版では KAG 既定の drawDevice をそのまま使うので何もしない
}

if (!bSimpChineseEnabled) {
	global.cConv = new ChineseConverter("s2t.json");
}

// ■TJSプラグイン追加 ==================================================================

// BoxBlurプラグイン
Scripts.execStorage("BoxBlurRestore.tjs");
// エラー抑制
Scripts.evalStorage("ignoreMissing.tjs");

System.title = "恋爱,我就借走咯" + (lf.DM_DepotVer ? "" : " - R18");

最后使用kirikiritools封包为patch2.xp3即可


参考文献

  1. Valve Corporation. Steamworks SDK 文档与头文件(steam_api.h、steam_api_internal.h).
  2. uyjulian. Krkrz(KiriKiri Z)源代码,tvpwin32 与 Archive 相关模块.
  3. pefile 项目文档,PE 导入导出表解析接口.
  4. GNU Binutils 文档,objdump 的 Intel 语法反汇编选项.

本文版权遵循 CC BY-NC 协议 和 本站版权政策

(。>︿<。) 已经一滴回复都不剩了哦~