修复 DLL 自校验 CRC 算法并加固槽位定位(频道无法连接问题)
根因:重写的 patch_dll_crc.py 用 binascii.crc32(data, 0xFFFFFFFF) 计算 CRC,
该参数是"末值异或之后的运行值",再喂回去等于重复施加了条件化,得到的是
CRC32 的另一个变体,与 90CN.cpp 里 Crc32Update 的实现不一致。结果是补丁写
入的 expected 永远校验不过,客户端在 VerifySelfIntegrity 失败后安装流程中断,
transport 钩子(codec/decode/route/dproto)全部没装上,表现为"无法连接频道"。
改动:
- tools/patch_dll_crc.py
* CRC 改为标准 zlib.crc32(init 默认 0),与 C 端 Crc32Update 完全一致;
已用旧版本 DLL 反向验证(10-20、11-03、10-28 三份的存储值均能复算一致)
* 槽位改为按 PE 段表定位,只接受 .data/.rdata 内的候选,拒绝把补丁写进
.text 机器码(此前曾命中 cmp ..., 0C0DEC0DEh 的立即数导致访问违例)
* 新增 info/verify 子命令,patch 后自动回读校验
- 90CN.cpp
* SelfCrcSlot 三个字段加 volatile,避免 /O2 把 offset/expected 折叠成常量
* VerifySelfIntegrity 增加分步诊断日志,便于定位安装线程中断点
- client_build_id.go
* 指纹重绑为本次构建产物:1215488 / C7BE377C…8247071A
验证:客户端日志 self crc check computed==expected==0x80AFC116 ok=1,
transport DLL installed codec=1 decode=1 route=1 dproto=1,
channel connect result=1,first-login watchdog server_seen=1 client_seen=1。
This commit is contained in:
1 parent
2ca06e113a
commit
6e570fe820
3 files changed
+162
-43
No files matched your search
+14
-3
@@ -13169,9 +13169,11 @@ static bool VerifyBoundMoviePresent()
|
|||||||
// expected = 整个文件的 CRC32(计算时跳过 [offset, offset+4),避免自指)
|
// expected = 整个文件的 CRC32(计算时跳过 [offset, offset+4),避免自指)
|
||||||
// 运行时重新读盘、用同样规则重算并比对;任何字节被改动 → 不符 → 客户端无法启动。
|
// 运行时重新读盘、用同样规则重算并比对;任何字节被改动 → 不符 → 客户端无法启动。
|
||||||
static struct SelfCrcSlot {
|
static struct SelfCrcSlot {
|
||||||
unsigned int magic; // 固定 0xC0DEC0DE
|
// volatile:这三个字段只由构建后脚本改文件字节,代码里从不写。
|
||||||
unsigned int offset; // expected 字段的文件偏移(脚本写入)
|
// 不加 volatile 时 /O2 会把 offset/expected 当常量折叠成 0,补丁写对了也读不到。
|
||||||
unsigned int expected; // 文件 CRC32(脚本写入)
|
volatile unsigned int magic; // 固定 0xC0DEC0DE
|
||||||
|
volatile unsigned int offset; // expected 字段的文件偏移(脚本写入)
|
||||||
|
volatile unsigned int expected; // 文件 CRC32(脚本写入)
|
||||||
} g_selfCrcSlot = { 0xC0DEC0DEu, 0u, 0u };
|
} g_selfCrcSlot = { 0xC0DEC0DEu, 0u, 0u };
|
||||||
|
|
||||||
static unsigned int Crc32Update(unsigned int crc, const unsigned char* data, size_t length)
|
static unsigned int Crc32Update(unsigned int crc, const unsigned char* data, size_t length)
|
||||||
@@ -13189,6 +13191,8 @@ static unsigned int Crc32Update(unsigned int crc, const unsigned char* data, siz
|
|||||||
static bool VerifySelfIntegrity()
|
static bool VerifySelfIntegrity()
|
||||||
{
|
{
|
||||||
wchar_t path[MAX_PATH] = {};
|
wchar_t path[MAX_PATH] = {};
|
||||||
|
// 崩溃定位断点:worker 在 TCLS 之后踩 AV 时靠这些行判断停在哪一步。
|
||||||
|
LogLine("self crc step=enter slot=%p", (void*)&g_selfCrcSlot);
|
||||||
// 用数据变量地址解析自身模块;函数地址在某些构建/加载方式下解析不到(实测 err=126)。
|
// 用数据变量地址解析自身模块;函数地址在某些构建/加载方式下解析不到(实测 err=126)。
|
||||||
HMODULE selfModule = nullptr;
|
HMODULE selfModule = nullptr;
|
||||||
if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
|
if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
|
||||||
@@ -13197,10 +13201,12 @@ static bool VerifySelfIntegrity()
|
|||||||
LogLine("self crc diag: GetModuleHandleEx failed err=%u", GetLastError());
|
LogLine("self crc diag: GetModuleHandleEx failed err=%u", GetLastError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
LogLine("self crc step=module module=0x%p", (void*)selfModule);
|
||||||
if (!GetModuleFileNameW(selfModule, path, MAX_PATH)) {
|
if (!GetModuleFileNameW(selfModule, path, MAX_PATH)) {
|
||||||
LogLine("self crc diag: GetModuleFileName failed err=%u", GetLastError());
|
LogLine("self crc diag: GetModuleFileName failed err=%u", GetLastError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
LogLine("self crc step=path");
|
||||||
FILE* file = nullptr;
|
FILE* file = nullptr;
|
||||||
if (_wfopen_s(&file, path, L"rb") != 0 || !file) {
|
if (_wfopen_s(&file, path, L"rb") != 0 || !file) {
|
||||||
LogLine("self crc diag: open failed");
|
LogLine("self crc diag: open failed");
|
||||||
@@ -13209,6 +13215,7 @@ static bool VerifySelfIntegrity()
|
|||||||
fseek(file, 0, SEEK_END);
|
fseek(file, 0, SEEK_END);
|
||||||
const long size = ftell(file);
|
const long size = ftell(file);
|
||||||
fseek(file, 0, SEEK_SET);
|
fseek(file, 0, SEEK_SET);
|
||||||
|
LogLine("self crc step=size size=%ld", size);
|
||||||
if (size < 4096) {
|
if (size < 4096) {
|
||||||
LogLine("self crc diag: file too small size=%ld", size);
|
LogLine("self crc diag: file too small size=%ld", size);
|
||||||
fclose(file);
|
fclose(file);
|
||||||
@@ -13221,6 +13228,7 @@ static bool VerifySelfIntegrity()
|
|||||||
}
|
}
|
||||||
const size_t read = fread(image, 1, static_cast<size_t>(size), file);
|
const size_t read = fread(image, 1, static_cast<size_t>(size), file);
|
||||||
fclose(file);
|
fclose(file);
|
||||||
|
LogLine("self crc step=read read=%u image=%p", (unsigned int)read, (void*)image);
|
||||||
if (read != static_cast<size_t>(size)) {
|
if (read != static_cast<size_t>(size)) {
|
||||||
free(image);
|
free(image);
|
||||||
return false;
|
return false;
|
||||||
@@ -13228,6 +13236,7 @@ static bool VerifySelfIntegrity()
|
|||||||
|
|
||||||
const unsigned int slotOffset = g_selfCrcSlot.offset;
|
const unsigned int slotOffset = g_selfCrcSlot.offset;
|
||||||
const unsigned int expected = g_selfCrcSlot.expected;
|
const unsigned int expected = g_selfCrcSlot.expected;
|
||||||
|
LogLine("self crc step=slot offset=0x%X expected=0x%08X", slotOffset, expected);
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
if (g_selfCrcSlot.magic != 0xC0DEC0DEu || slotOffset < 16 ||
|
if (g_selfCrcSlot.magic != 0xC0DEC0DEu || slotOffset < 16 ||
|
||||||
static_cast<size_t>(slotOffset) + 4 > static_cast<size_t>(size)) {
|
static_cast<size_t>(slotOffset) + 4 > static_cast<size_t>(size)) {
|
||||||
@@ -14995,7 +15004,9 @@ DWORD WINAPI InstallTransportWorkerInner(void*)
|
|||||||
TerminateProcess(GetCurrentProcess(), 1);
|
TerminateProcess(GetCurrentProcess(), 1);
|
||||||
}
|
}
|
||||||
// 影片绑定校验:文件缺失/损坏直接终止,客户端无法启动。
|
// 影片绑定校验:文件缺失/损坏直接终止,客户端无法启动。
|
||||||
|
LogLine("self crc verified, entering movie binding check");
|
||||||
EnforceBoundMovieOrExit();
|
EnforceBoundMovieOrExit();
|
||||||
|
LogLine("movie binding check passed");
|
||||||
// 攻坚队创建入口默认写死 owner 0x76(RaidCreateWindow),把该立即数改成
|
// 攻坚队创建入口默认写死 owner 0x76(RaidCreateWindow),把该立即数改成
|
||||||
// 0x7A 后打开原生 RaidSelectSingleModeWindow(团队/单人选择),单人按钮
|
// 0x7A 后打开原生 RaidSelectSingleModeWindow(团队/单人选择),单人按钮
|
||||||
// 再把 mode 0 传回 0x76 的创建窗口。窗口与按钮回调全部是客户端原生实现。
|
// 再把 mode 0 传回 0x76 的创建窗口。窗口与按钮回调全部是客户端原生实现。
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""90CN.dll self-integrity CRC32 patcher (reimplementation of patch_dll_crc.py).
|
"""90CN.dll self-integrity CRC32 patcher.
|
||||||
|
|
||||||
The client patch embeds this struct (see client-patch/90CN.cpp, VerifySelfIntegrity):
|
The client patch embeds this struct (see client-patch/90CN.cpp, VerifySelfIntegrity):
|
||||||
|
|
||||||
@@ -12,10 +12,19 @@ The client patch embeds this struct (see client-patch/90CN.cpp, VerifySelfIntegr
|
|||||||
At runtime the DLL re-reads itself, recomputes that CRC and compares. Any byte
|
At runtime the DLL re-reads itself, recomputes that CRC and compares. Any byte
|
||||||
change (e.g. patching out the check) makes the client refuse to start.
|
change (e.g. patching out the check) makes the client refuse to start.
|
||||||
|
|
||||||
|
IMPORTANT — the magic is NOT unique in the image. MSVC encodes the comparison
|
||||||
|
`magic != 0xC0DEC0DEu` as an immediate inside .text, so a naive "find the first
|
||||||
|
0xC0DEC0DE" scan lands on machine code and overwrites instructions, which shows
|
||||||
|
up at runtime as an access violation right after the slot read. This tool
|
||||||
|
therefore resolves the slot through the PE section table and only accepts a
|
||||||
|
candidate that lives in a data section (.data/.rdata) and whose offset/expected
|
||||||
|
fields are either zero (fresh build) or already self-consistent.
|
||||||
|
|
||||||
The CRC is the standard reflected CRC-32 (poly 0xEDB88320, init 0xFFFFFFFF,
|
The CRC is the standard reflected CRC-32 (poly 0xEDB88320, init 0xFFFFFFFF,
|
||||||
final XOR 0xFFFFFFFF) == zlib.crc32 / binascii.crc32.
|
final XOR 0xFFFFFFFF) == zlib.crc32 / binascii.crc32.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
python patch_dll_crc.py info <dll> # show slot + section layout
|
||||||
python patch_dll_crc.py verify <dll> # check the current stored CRC
|
python patch_dll_crc.py verify <dll> # check the current stored CRC
|
||||||
python patch_dll_crc.py patch <dll> # rewrite offset+expected in place
|
python patch_dll_crc.py patch <dll> # rewrite offset+expected in place
|
||||||
python patch_dll_crc.py patch <dll> -o <out.dll> # write to a copy instead
|
python patch_dll_crc.py patch <dll> -o <out.dll> # write to a copy instead
|
||||||
@@ -29,75 +38,171 @@ import argparse
|
|||||||
import binascii
|
import binascii
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
|
import zlib
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
MAGIC = 0xC0DEC0DE
|
MAGIC = 0xC0DEC0DE
|
||||||
MAGIC_BYTES = struct.pack("<I", MAGIC)
|
MAGIC_BYTES = struct.pack("<I", MAGIC)
|
||||||
# struct layout is three consecutive little-endian uint32
|
|
||||||
SLOT_SIZE = 12
|
SLOT_SIZE = 12
|
||||||
OFFSET_FIELD = 4
|
OFFSET_FIELD = 4
|
||||||
EXPECTED_FIELD = 8
|
EXPECTED_FIELD = 8
|
||||||
|
|
||||||
|
# Section names whose raw data may hold the slot. MSVC puts a zero-initialised
|
||||||
|
# (but non-const) static object in .data; if it is truly constant it can end up
|
||||||
|
# in .rdata. Anything executable is rejected outright.
|
||||||
|
SLOT_SECTIONS = (".data", ".rdata")
|
||||||
|
|
||||||
|
|
||||||
class SlotError(RuntimeError):
|
class SlotError(RuntimeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def find_slot(blob: bytes) -> int:
|
def parse_sections(blob: bytes) -> list[dict]:
|
||||||
"""Return the file offset of the SelfCrcSlot struct.
|
if len(blob) < 0x40 or blob[:2] != b"MZ":
|
||||||
|
raise SlotError("not a PE image (missing MZ header)")
|
||||||
The magic must be followed by a plausible `offset` field pointing at the
|
pe = struct.unpack_from("<I", blob, 0x3C)[0]
|
||||||
`expected` field it lives in; that rejects accidental magic collisions in
|
if blob[pe : pe + 4] != b"PE\0\0":
|
||||||
code or data.
|
raise SlotError("not a PE image (missing PE signature)")
|
||||||
"""
|
nsec = struct.unpack_from("<H", blob, pe + 6)[0]
|
||||||
start = 0
|
opt_size = struct.unpack_from("<H", blob, pe + 20)[0]
|
||||||
candidates: list[int] = []
|
sh = pe + 24 + opt_size
|
||||||
while True:
|
sections = []
|
||||||
pos = blob.find(MAGIC_BYTES, start)
|
for i in range(nsec):
|
||||||
if pos < 0:
|
base = sh + i * 40
|
||||||
|
if base + 40 > len(blob):
|
||||||
break
|
break
|
||||||
candidates.append(pos)
|
name = blob[base : base + 8].rstrip(b"\0").decode("latin1")
|
||||||
start = pos + 1
|
vsize, va, rsize, roff = struct.unpack_from("<IIII", blob, base + 8)
|
||||||
|
chars = struct.unpack_from("<I", blob, base + 36)[0]
|
||||||
|
sections.append(
|
||||||
|
{
|
||||||
|
"name": name,
|
||||||
|
"vsize": vsize,
|
||||||
|
"rva": va,
|
||||||
|
"rawsize": rsize,
|
||||||
|
"rawoff": roff,
|
||||||
|
"executable": bool(chars & 0x20000000),
|
||||||
|
"writable": bool(chars & 0x80000000),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if not sections:
|
||||||
|
raise SlotError("PE has no section table")
|
||||||
|
return sections
|
||||||
|
|
||||||
|
|
||||||
|
def section_of(sections: list[dict], file_offset: int) -> dict | None:
|
||||||
|
for sec in sections:
|
||||||
|
if sec["rawoff"] <= file_offset < sec["rawoff"] + sec["rawsize"]:
|
||||||
|
return sec
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def find_slot(blob: bytes) -> tuple[int, dict]:
|
||||||
|
"""Return (file offset, section) of the SelfCrcSlot struct."""
|
||||||
|
sections = parse_sections(blob)
|
||||||
|
candidates: list[tuple[int, dict]] = []
|
||||||
|
pos = blob.find(MAGIC_BYTES)
|
||||||
|
while pos >= 0:
|
||||||
|
sec = section_of(sections, pos)
|
||||||
|
if sec is not None and sec["name"] in SLOT_SECTIONS:
|
||||||
|
candidates.append((pos, sec))
|
||||||
|
pos = blob.find(MAGIC_BYTES, pos + 1)
|
||||||
if not candidates:
|
if not candidates:
|
||||||
|
# Distinguish "magic only in code" from "magic nowhere": give a useful hint.
|
||||||
|
if blob.find(MAGIC_BYTES) >= 0:
|
||||||
|
raise SlotError(
|
||||||
|
"0xC0DEC0DE only found in an executable section — refusing to "
|
||||||
|
"patch machine code (the real slot lives in .data/.rdata)"
|
||||||
|
)
|
||||||
raise SlotError("0xC0DEC0DE slot magic not found")
|
raise SlotError("0xC0DEC0DE slot magic not found")
|
||||||
|
|
||||||
for pos in candidates:
|
def score(item: tuple[int, dict]) -> tuple[int, int]:
|
||||||
if pos + SLOT_SIZE > len(blob):
|
p, sec = item
|
||||||
continue
|
offset = struct.unpack_from("<I", blob, p + OFFSET_FIELD)[0]
|
||||||
offset = struct.unpack_from("<I", blob, pos + OFFSET_FIELD)[0]
|
fresh = offset == 0
|
||||||
if offset == pos + EXPECTED_FIELD and offset + 4 <= len(blob):
|
patched = offset == p + EXPECTED_FIELD
|
||||||
return pos
|
# Prefer a patched slot, then a fresh one, then .data over .rdata.
|
||||||
# Fall back to the first candidate so `patch` can still repair an unpatched
|
return (2 if patched else 1 if fresh else 0, 1 if sec["name"] == ".data" else 0)
|
||||||
# or corrupted slot (fresh build has offset == 0).
|
|
||||||
return candidates[0]
|
candidates.sort(key=score, reverse=True)
|
||||||
|
best = candidates[0]
|
||||||
|
if score(best)[0] == 0:
|
||||||
|
raise SlotError(
|
||||||
|
"slot magic found in data but offset field is neither 0 nor self-referential"
|
||||||
|
)
|
||||||
|
return best
|
||||||
|
|
||||||
|
|
||||||
def compute_crc(blob: bytes, expected_offset: int) -> int:
|
def compute_crc(blob: bytes, expected_offset: int) -> int:
|
||||||
"""CRC32 of the whole file, skipping the 4 `expected` bytes."""
|
"""CRC32 of the whole file, skipping the 4 `expected` bytes.
|
||||||
|
|
||||||
|
Must match 90CN.cpp VerifySelfIntegrity() exactly:
|
||||||
|
|
||||||
|
unsigned int crc = 0xFFFFFFFF;
|
||||||
|
crc = Crc32Update(crc, image, slotOffset);
|
||||||
|
crc = Crc32Update(crc, image + slotOffset + 4, size - slotOffset - 4);
|
||||||
|
crc ^= 0xFFFFFFFF;
|
||||||
|
|
||||||
|
Crc32Update is the reflected CRC-32 (poly 0xEDB88320) with no internal
|
||||||
|
conditioning, so "init 0xFFFFFFFF ... final XOR 0xFFFFFFFF" is exactly
|
||||||
|
zlib.crc32()/binascii.crc32() with the DEFAULT seed 0.
|
||||||
|
|
||||||
|
Do NOT seed binascii.crc32 with 0xFFFFFFFF: that argument is the running
|
||||||
|
CRC *after* the final XOR, so seeding it re-applies the conditioning and
|
||||||
|
yields a different, non-matching variant (this bug shipped once and made
|
||||||
|
the client reject its own DLL).
|
||||||
|
"""
|
||||||
if expected_offset < 16 or expected_offset + 4 > len(blob):
|
if expected_offset < 16 or expected_offset + 4 > len(blob):
|
||||||
raise SlotError(
|
raise SlotError(
|
||||||
f"slot offset 0x{expected_offset:X} out of range (size 0x{len(blob):X})"
|
f"slot offset 0x{expected_offset:X} out of range (size 0x{len(blob):X})"
|
||||||
)
|
)
|
||||||
crc = 0xFFFFFFFF
|
crc = zlib.crc32(blob[:expected_offset])
|
||||||
crc = binascii.crc32(blob[:expected_offset], crc)
|
crc = zlib.crc32(blob[expected_offset + 4 :], crc)
|
||||||
crc = binascii.crc32(blob[expected_offset + 4 :], crc)
|
|
||||||
return crc & 0xFFFFFFFF
|
return crc & 0xFFFFFFFF
|
||||||
|
|
||||||
|
|
||||||
def read_slot(blob: bytes) -> tuple[int, int, int]:
|
def read_slot(blob: bytes) -> tuple[int, dict, int, int]:
|
||||||
pos = find_slot(blob)
|
pos, sec = find_slot(blob)
|
||||||
magic, offset, expected = struct.unpack_from("<III", blob, pos)
|
magic, offset, expected = struct.unpack_from("<III", blob, pos)
|
||||||
return pos, offset, expected
|
if magic != MAGIC:
|
||||||
|
raise SlotError("slot magic mismatch after read")
|
||||||
|
return pos, sec, offset, expected
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_info(path: Path) -> int:
|
||||||
|
blob = path.read_bytes()
|
||||||
|
sections = parse_sections(blob)
|
||||||
|
print(f"file : {path} ({len(blob)} bytes)")
|
||||||
|
for sec in sections:
|
||||||
|
print(
|
||||||
|
f" {sec['name']:9s} rva=0x{sec['rva']:08X} vsize={sec['vsize']:8d} "
|
||||||
|
f"raw=0x{sec['rawoff']:08X} rawsize={sec['rawsize']:8d}"
|
||||||
|
f"{' EXEC' if sec['executable'] else ''}"
|
||||||
|
f"{' WRITABLE' if sec['writable'] else ''}"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
pos, sec, offset, expected = read_slot(blob)
|
||||||
|
except SlotError as exc:
|
||||||
|
print(f"slot : NOT FOUND ({exc})")
|
||||||
|
return 1
|
||||||
|
print(
|
||||||
|
f"slot : file 0x{pos:08X} in {sec['name']} "
|
||||||
|
f"offset=0x{offset:08X} expected=0x{expected:08X}"
|
||||||
|
)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def cmd_verify(path: Path) -> int:
|
def cmd_verify(path: Path) -> int:
|
||||||
blob = path.read_bytes()
|
blob = path.read_bytes()
|
||||||
try:
|
try:
|
||||||
pos, offset, expected = read_slot(blob)
|
pos, sec, offset, expected = read_slot(blob)
|
||||||
except SlotError as exc:
|
except SlotError as exc:
|
||||||
print(f"FAIL {path.name}: {exc}")
|
print(f"FAIL {path.name}: {exc}")
|
||||||
return 1
|
return 1
|
||||||
print(f"slot : file 0x{pos:08X} offset=0x{offset:08X} expected=0x{expected:08X}")
|
print(
|
||||||
|
f"slot : file 0x{pos:08X} in {sec['name']} "
|
||||||
|
f"offset=0x{offset:08X} expected=0x{expected:08X}"
|
||||||
|
)
|
||||||
if offset != pos + EXPECTED_FIELD:
|
if offset != pos + EXPECTED_FIELD:
|
||||||
print(f"FAIL {path.name}: slot offset does not point at itself")
|
print(f"FAIL {path.name}: slot offset does not point at itself")
|
||||||
return 1
|
return 1
|
||||||
@@ -113,7 +218,7 @@ def cmd_verify(path: Path) -> int:
|
|||||||
def cmd_patch(src: Path, dst: Path | None) -> int:
|
def cmd_patch(src: Path, dst: Path | None) -> int:
|
||||||
blob = bytearray(src.read_bytes())
|
blob = bytearray(src.read_bytes())
|
||||||
try:
|
try:
|
||||||
pos = find_slot(blob)
|
pos, sec = find_slot(blob)
|
||||||
except SlotError as exc:
|
except SlotError as exc:
|
||||||
print(f"FAIL {src.name}: {exc}")
|
print(f"FAIL {src.name}: {exc}")
|
||||||
return 1
|
return 1
|
||||||
@@ -126,16 +231,17 @@ def cmd_patch(src: Path, dst: Path | None) -> int:
|
|||||||
|
|
||||||
out = dst or src
|
out = dst or src
|
||||||
out.write_bytes(bytes(blob))
|
out.write_bytes(bytes(blob))
|
||||||
print(f"slot : file 0x{pos:08X} offset=0x{expected_offset:08X} expected=0x{crc:08X}")
|
print(
|
||||||
|
f"slot : file 0x{pos:08X} in {sec['name']} "
|
||||||
|
f"offset=0x{expected_offset:08X} expected=0x{crc:08X}"
|
||||||
|
)
|
||||||
print(f"OK {out.name}: patched ({len(blob)} bytes)")
|
print(f"OK {out.name}: patched ({len(blob)} bytes)")
|
||||||
|
|
||||||
# Re-read and verify what we just wrote.
|
|
||||||
return cmd_verify(out)
|
return cmd_verify(out)
|
||||||
|
|
||||||
|
|
||||||
def main(argv: list[str]) -> int:
|
def main(argv: list[str]) -> int:
|
||||||
parser = argparse.ArgumentParser(description="90CN.dll self-integrity CRC patcher")
|
parser = argparse.ArgumentParser(description="90CN.dll self-integrity CRC patcher")
|
||||||
parser.add_argument("action", choices=["verify", "patch"])
|
parser.add_argument("action", choices=["info", "verify", "patch"])
|
||||||
parser.add_argument("dll", type=Path)
|
parser.add_argument("dll", type=Path)
|
||||||
parser.add_argument("-o", "--out", type=Path, help="output path for patch (default: in place)")
|
parser.add_argument("-o", "--out", type=Path, help="output path for patch (default: in place)")
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
@@ -143,6 +249,8 @@ def main(argv: list[str]) -> int:
|
|||||||
if not args.dll.is_file():
|
if not args.dll.is_file():
|
||||||
print(f"FAIL {args.dll}: not a file")
|
print(f"FAIL {args.dll}: not a file")
|
||||||
return 2
|
return 2
|
||||||
|
if args.action == "info":
|
||||||
|
return cmd_info(args.dll)
|
||||||
if args.action == "verify":
|
if args.action == "verify":
|
||||||
return cmd_verify(args.dll)
|
return cmd_verify(args.dll)
|
||||||
return cmd_patch(args.dll, args.out)
|
return cmd_patch(args.dll, args.out)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const (
|
|||||||
boundMovieSHA256 = "D62E3623C39CD9FCED5F6C4C7F5E6E18BEB69EB269ABF5D713EDE08EE8C926A1"
|
boundMovieSHA256 = "D62E3623C39CD9FCED5F6C4C7F5E6E18BEB69EB269ABF5D713EDE08EE8C926A1"
|
||||||
boundMovieBytes = int64(22846532)
|
boundMovieBytes = int64(22846532)
|
||||||
|
|
||||||
boundDllSHA256 = "30E41FF583893E611E6975DF96246E1320B042F7B33A32503C987F211D1776F5"
|
boundDllSHA256 = "C7BE377CC2795612074CAE70F071523E19E32964CED355762A2B59F48247071A"
|
||||||
boundDllBytes = int64(1215488)
|
boundDllBytes = int64(1215488)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user