Compare commits

..
1 Commits
4 changed files with 57 additions and 67 deletions
+23 -8
View File
@@ -10,18 +10,33 @@
- `tools/cslol-tools/` — cslol 工具链(`mod-tools.exe` / `cslol-dll.dll` / `wad-extract.exe` / `wad-make.exe`)。
- `runtime/` — 全部本地状态(git 忽略)。
## 注入器(cslol-dll.dll)本地自管
## 注入器(cslol-dll.dll)不更新
`tools/cslol-tools/` 下 `mod-tools.exe` / `cslol-dll.dll` / `wad-extract.exe` / `wad-make.exe` 都是**本地管理的二进制**,
其中 `cslol-dll.dll` 与游戏大版本强绑定,**不自动更新**。
其中 `cslol-dll.dll` **不更新**——updater 内不存在任何能写入它的代码路径(2026-10-02 起)。
原因:cslol-manager 上游 patcher 已换代(AeroHook,509344B),对当前游戏版本报
`ah_result != 0 / C0000229`(STATUS_ORDINAL_NOT_FOUND),换上即全盘失效;而可用的
MinHook 版(247128B,内部名 `League-of-Legends-Patch.dll`)不在上游 git 历史里,没有可 pin 的地址。
原因:cslol-manager 的新 patcher(AeroHook,509384B)被**故意硬绑定**为只能由
`cslol-manager.exe` / `ltk-manager` 启动,走 `mod-tools.exe` CLI 一律返回
`ah_result != 0 / C0000229`。上游 issue #437 维护者原话:
"this does mean cli tool is unfortunately unusable"。
即它不是"坏了",而是设计上不给 CLI 用,因此 pin 任何 commit 都无意义。
`npm run update -- check` / `auto` 只校验本地指纹(已验证 SHA256 记录在 `packages/core/src/sources.ts`),
不符会告警但**不会**自动下载替换。确需人工替换时才用
`sync --only toolchain`,且必须先设 `LOLSKIN_ALLOW_INJECTOR_SYNC=1`。
已于 2026-10-02 实际发生一次:`tools/cslol-tools/cslol-dll.dll.bak` 的**创建时间**
(`2026/10/02 00:11:17`)即旧 `syncToolchain()` 覆盖掉可用 DLL 的时刻。
可用的 MinHook 版(247128B,内部名 `League-of-Legends-Patch.dll`)对应源码为
`LeagueToolkit/cslol-patcher-legacy`,该仓库**只有源码、无预编译产物**,故无可自动更新的地址。
**要换 DLL 时人工放置文件**,然后用 `npm run update -- check` 核对指纹
(已验证 SHA256 记录在 `packages/core/src/sources.ts` 的 `INJECTOR.knownGoodSha256`)。
`check` / `auto` / `verify` 只读该文件,指纹不符会告警但绝不替换。
`git` 内也必须存着已验证版本 —— 曾因初始提交存的是坏的 509384B,一度靠工作区覆盖才可用。
游戏大版本更新后如换肤失效,看 `runtime/state/overlay.log`:
`Failed to find CRYPTO_free ptr.` / `Failed to write crypto free ptr:` 说明 patcher 需重编译;
**日志无任何报错但不生效** 说明 `cslol-patcher-legacy/src/cslol-dll.c` 中校验返回点的
机器码常量 `0x48C38B70247C8B48`(约 199 行)已变 —— 属静默失败,最难排查。
需重编译时只改 3 个常量:`pattern[16]`(约 266 行)、`offset + 32`(约 276 行)、以及上述机器码。
## 命令
+7 -5
View File
@@ -49,13 +49,15 @@ export const CDRAGON = {
* 与 mod-tools.exe / wad-extract.exe / wad-make.exe 一样按本地文件管理。
*/
export const INJECTOR = {
/** 仅供人工排查/取证;不要接进自动更新。 */
upstreamDlls: [
"https://cdn.jsdelivr.net/gh/LeagueToolkit/cslol-manager@master/cslol-tools/vendor/cslol-patcher/cslol-dll.dll",
"https://raw.githubusercontent.com/LeagueToolkit/cslol-manager/master/cslol-tools/vendor/cslol-patcher/cslol-dll.dll",
],
/** 已验证可用的注入器指纹(与 Aether 自带那份字节一致)。不符即说明被换掉了。 */
knownGoodSha256: "25EA29710A83C5B12126E3BB8E6EC1549EACD0DCD4A4A10DA105A7037CB310CD",
/** 重新编译/获取可用 patcher 的源码仓库(无预编译产物)。 */
legacySource: "https://github.com/LeagueToolkit/cslol-patcher-legacy",
/**
* 取证用地址,**刻意不以数据形式保留**,确保 updater 无法下载它。
* cslol-manager@cslol-tools/vendor/cslol-patcher/cslol-dll.dll
* (raw.githubusercontent 同路径)
* 该文件是 AeroHook 代(509384B),硬绑定只能由 cslol-manager.exe 启动,
* 走 mod-tools CLI 必然 `C0000229`。谁再想接自动更新,先读上面那段注释。
*/
};
+10 -2
View File
@@ -11,9 +11,9 @@ function usage(): void {
logInfo(
[
"lolskin-updater <命令>",
" auto | update 一键:检测 ->(有更新才)更新注入器 -> 校验",
" auto | update 一键:检测 -> 校验(注入器为本地自管,不更新)",
" check 只检测,不下载",
" sync [--only hashes|toolchain] 更新(默认注入器)",
" sync [--only hashes] 同步 hashes 库(注入器不参与更新)",
" fetch-skin <heroId> <skinId> 单皮肤全量下载(主程序委托用)",
" prefetch-skins [--hero <id>] 批量预下(占位)",
" verify [--fix] 完整性校验 / 修复",
@@ -65,6 +65,14 @@ async function main(argv: string[]): Promise<number> {
const only = rest.includes("--only")
? (rest[rest.indexOf("--only") + 1] as SyncOptions["only"])
: undefined;
if (only === ("toolchain" as SyncOptions["only"])) {
logError(
"sync --only toolchain 已移除:注入器不再由 updater 更新。" +
"请人工放置 cslol-dll.dll,再用 `npm run update -- check` 核对指纹。" +
"背景见 packages/core/src/sources.ts 的 INJECTOR 注释。"
);
return EXIT.USAGE;
}
return guarded(() => runSync({ only }));
}
+17 -52
View File
@@ -1,61 +1,28 @@
import * as fs from "node:fs";
import * as path from "node:path";
import { CDRAGON, CSLOL_DLL, HASH_DIR, INJECTOR, fileExists, logInfo, updateManifest } from "@lolskin/core";
import { downloadToFile, getBuffer, sha256File } from "./downloader";
import { CDRAGON, HASH_DIR, logInfo } from "@lolskin/core";
import { getBuffer } from "./downloader";
import { EXIT, type ExitCode } from "./exits";
export interface SyncOptions {
only?: "hashes" | "toolchain";
only?: "hashes";
}
/**
* 人工替换注入器 cslol-dll.dll(**仅显式调用**,自动流程不会触发)。
* 注入器 cslol-dll.dll:**已彻底移除更新路径**(2026-10-02)。
*
* 警告:上游 cslol-manager 的 patcher 已换代(AeroHook,509344B),对当前游戏版本
* 报 `C0000229`;本地可用的 MinHook 版(247128B)不在上游 git 历史里。
* 换之前请确认新 DLL 与本地 mod-tools.exe 同代,否则换肤会整体失效。
* 详见 packages/core/src/sources.ts 的 INJECTOR 注释。
* 原 `syncToolchain()` 会从 cslol-manager 的可变 `@master` 引用下载 patcher,
* 而上游该文件是**故意只允许 cslol-manager.exe 启动**的 AeroHook 版
* (509384B),走 mod-tools CLI 一律 `ah_result != 0 / C0000229`。
* 上游 issue LeagueToolkit/cslol-manager#437 维护者原话:
* "this does mean cli tool is unfortunately unusable"。
*
* 曾于 2026-10-02 00:11 实际发生:`cslol-dll.dll.bak` 的创建时间即该次覆盖时刻。
*
* 现在 updater 内**没有任何**能写入 CSLOL_DLL 的代码路径。
* 需要更换时由人工放置文件,并用 `npm run update -- check` 核对指纹。
* 背景见 packages/core/src/sources.ts 的 INJECTOR 注释。
*/
async function syncToolchain(): Promise<ExitCode> {
if (!process.env.LOLSKIN_ALLOW_INJECTOR_SYNC) {
throw new Error(
"注入器为本地自管二进制,自动更新已停用。" +
"如确认要替换,请设置环境变量 LOLSKIN_ALLOW_INJECTOR_SYNC=1 后重试," +
"并自行核对 SHA256 是否与已验证版本一致。"
);
}
const bak = `${CSLOL_DLL}.bak`;
if (fileExists(CSLOL_DLL)) {
fs.copyFileSync(CSLOL_DLL, bak);
logInfo(`已备份旧注入器 -> ${path.basename(bak)}`);
}
const tmp = `${CSLOL_DLL}.new`;
let ok = false;
let lastErr = "";
for (const url of INJECTOR.upstreamDlls) {
try {
await downloadToFile(url, tmp);
ok = true;
break;
} catch (e) {
lastErr = (e as Error).message;
fs.rmSync(tmp, { force: true });
}
}
if (!ok) throw new Error(`注入器下载失败(所有源均失败):${lastErr}`);
fs.renameSync(tmp, CSLOL_DLL);
const sha = sha256File(CSLOL_DLL);
if (sha.toUpperCase() !== INJECTOR.knownGoodSha256.toUpperCase()) {
logInfo(
`注意:新注入器指纹 ${sha.slice(0, 12)}… 与已验证版本不一致(上游已换代),换肤可能失效。`
);
}
updateManifest((m) => {
m.resources.toolchain = { ...m.resources.toolchain, present: true, injectorSha256: sha };
});
logInfo(`注入器已更新 (${fs.statSync(CSLOL_DLL).size}B, sha256=${sha.slice(0, 12)}…)`);
return EXIT.OK;
}
/**
* hashes 库(可选,仅 wad-extract 需要):
@@ -87,8 +54,6 @@ async function syncHashes(): Promise<ExitCode> {
}
export async function runSync(opts: SyncOptions = {}): Promise<ExitCode> {
if (opts.only === "hashes") return syncHashes();
if (opts.only === "toolchain") return syncToolchain();
// 默认:按需更新注入器;hashes 不主动拉(可选)
return syncToolchain();
void opts;
return syncHashes();
}