From 12a48ffddf489e964bbea916c0df99a93e808900 Mon Sep 17 00:00:00 2001 From: Anonymous Maarten Date: Fri, 3 Oct 2025 03:23:11 +0200 Subject: [PATCH] Fix demo compatibility (#494) * Fix demo compatibility Regression introduced in 81116169cfefa549a70a57f3c7e616c57b697973 * Decode gDecode_string (use macro's to show its definition) * Fix IWANTTOFIDDLE cheat for demo * Add demo support to tools/decode_datatxt.py --- src/DETHRACE/common/loading.c | 23 +++- src/DETHRACE/common/racestrt.c | 2 +- src/DETHRACE/common/utility.c | 69 ++++++++++ src/DETHRACE/common/utility.h | 6 + tools/decode_datatxt.py | 235 ++++++++++++++++++++++++--------- 5 files changed, 269 insertions(+), 66 deletions(-) diff --git a/src/DETHRACE/common/loading.c b/src/DETHRACE/common/loading.c index a2b5e3b3..cfbef261 100644 --- a/src/DETHRACE/common/loading.c +++ b/src/DETHRACE/common/loading.c @@ -140,8 +140,25 @@ int gAllow_open_to_fail = 1; // GLOBAL: CARM95 0x0050a5c8 int gDecode_thing = '@'; +#define DECODE_STRING_SECRET 50 + // GLOBAL: CARM95 0x0050a5d0 -char gDecode_string[] = { 0x9B, 0x52, 0x93, 0x9F, 0x52, 0x98, 0x9B, 0x96, 0x96, 0x9E, 0x9B, 0xA0, 0x99, 0x0 }; +char gDecode_string[] = { + 'i' + DECODE_STRING_SECRET, + ' ' + DECODE_STRING_SECRET, + 'a' + DECODE_STRING_SECRET, + 'm' + DECODE_STRING_SECRET, + ' ' + DECODE_STRING_SECRET, + 'f' + DECODE_STRING_SECRET, + 'i' + DECODE_STRING_SECRET, + 'd' + DECODE_STRING_SECRET, + 'd' + DECODE_STRING_SECRET, + 'l' + DECODE_STRING_SECRET, + 'i' + DECODE_STRING_SECRET, + 'n' + DECODE_STRING_SECRET, + 'g' + DECODE_STRING_SECRET, + '\0' +}; // GLOBAL: CARM95 0x00531f00 int gFunk_groove_flags[30]; @@ -400,7 +417,7 @@ void LoadGeneralParameters(void) { fgets(s, sizeof(s) - 1, f); fclose(f); for (i = 0; i < strlen(gDecode_string); i++) { - gDecode_string[i] -= 50; + gDecode_string[i] -= DECODE_STRING_SECRET; } // trim trailing CRLF etc @@ -413,7 +430,7 @@ void LoadGeneralParameters(void) { } for (i = 0; i < strlen(gDecode_string); i++) { - gDecode_string[i] += 50; + gDecode_string[i] += DECODE_STRING_SECRET; } } PathCat(the_path, gApplication_path, "GENERAL.TXT"); diff --git a/src/DETHRACE/common/racestrt.c b/src/DETHRACE/common/racestrt.c index 607c25c6..7b48afdb 100644 --- a/src/DETHRACE/common/racestrt.c +++ b/src/DETHRACE/common/racestrt.c @@ -1595,7 +1595,7 @@ void SelectRaceDraw(int pCurrent_choice, int pCurrent_mode) { fputs("*************", f); } } - gDecode_thing ^= 0x40u; + gDecode_thing ^= '@'; fclose(f); EncodeAllFilesInDirectory(""); EncodeAllFilesInDirectory("CARS"); diff --git a/src/DETHRACE/common/utility.c b/src/DETHRACE/common/utility.c index 73432018..8a2d7390 100644 --- a/src/DETHRACE/common/utility.c +++ b/src/DETHRACE/common/utility.c @@ -98,6 +98,14 @@ void EncodeLine(char* pS) { FILE* test; unsigned char c; +#ifdef DETHRACE_FIX_BUGS + // Demo has its own decryption key + behavior + if (harness_game_info.mode == eGame_carmageddon_demo) { + EncodeLine_DEMO(pS); + return; + } +#endif + len = strlen(pS); key = (char*)gLong_key; if (gEncryption_method == 0) { @@ -1441,6 +1449,14 @@ void DecodeLine2(char* pS) { unsigned char c; char* key; +#ifdef DETHRACE_FIX_BUGS + // Demo has its own decryption key + behavior + if (harness_game_info.mode == eGame_carmageddon_demo) { + DecodeLine2_DEMO(pS); + return; + } +#endif + len = strlen(pS); key = (char*)gLong_key; #ifdef DETHRACE_FIX_BUGS @@ -1494,6 +1510,14 @@ void EncodeLine2(char* pS) { unsigned char c; char* key; +#ifdef DETHRACE_FIX_BUGS + // Demo has its own decryption key + behavior + if (harness_game_info.mode == eGame_carmageddon_demo) { + EncodeLine2_DEMO(pS); + return; + } +#endif + len = strlen(pS); count = 0; key = (char*)gLong_key; @@ -1913,3 +1937,48 @@ void EncodeLine_DEMO(char* pS) { pS[i] = c; } } + +void EncodeLine2_DEMO(char* pS) { + int len; + int seed; + int i; + const char* key; + unsigned char c; +#if BR_ENDIAN_BIG + const tU32 gLong_key_DEMO[] = { 0x58503A76, 0xCBB68565, 0x15CD5B07, 0xB168DE3A }; +#else + const tU32 gLong_key_DEMO[] = { 0x763A5058, 0x6585B6CB, 0x75BCD15, 0x3ADE68B1 }; +#endif + + len = strlen(pS); + key = (char*)gLong_key_DEMO; + + while (len != 0 && (pS[len - 1] == '\r' || pS[len - 1] == '\n')) { + pS[len - 1] = 0; + len--; + } + seed = len % 16; + for (i = 0; i < len; i++) { + c = pS[i]; + if (c == '\t') { + c = 0x9F; + } + + c -= 32; + c ^= key[seed]; + c &= 0x7f; + c += 32; + if (c == 0x9F) { + c = '\t'; + } + if (c == '\n' || c == '\r') { + c |= 0x80; + } + seed = (seed + 7) % 16; + pS[i] = c; + } +} + +void DecodeLine2_DEMO(char* pS) { + EncodeLine_DEMO(pS); +} diff --git a/src/DETHRACE/common/utility.h b/src/DETHRACE/common/utility.h index 0d409670..937a86bd 100644 --- a/src/DETHRACE/common/utility.h +++ b/src/DETHRACE/common/utility.h @@ -186,4 +186,10 @@ void BlendifyMaterialPrimitively(br_material* pMaterial, int pPercent); void BlendifyMaterial(br_material* pMaterial, int pPercent); +void EncodeLine_DEMO(char* pS); + +void EncodeLine2_DEMO(char* pS); + +void DecodeLine2_DEMO(char* pS); + #endif diff --git a/tools/decode_datatxt.py b/tools/decode_datatxt.py index f195a14b..42cc56d5 100755 --- a/tools/decode_datatxt.py +++ b/tools/decode_datatxt.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import argparse +import enum import sys LONG_KEY = ( @@ -11,7 +12,15 @@ OTHER_LONG_KEY = ( 0x67, 0xa8, 0xd6, 0x26, 0xb6, 0xdd, 0x45, 0x1b, 0x32, 0x7e, 0x22, 0x13, 0x15, 0xc2, 0x94, 0x37, ) +DEMO_KEY = ( + 0x58, 0x50, 0x3A, 0x76, 0xCB, 0xB6, 0x85, 0x65, + 0x15, 0xCD, 0x5B, 0x07, 0xB1, 0x68, 0xDE, 0x3A, +) +class Method(enum.Enum): + Method1 = "1" + Method2 = "2" + Demo = "demo" class Byte: def __init__(self, v: int): @@ -52,6 +61,13 @@ class Byte: self.v = (self.v & v) & 0xff return self + def __or__(self, v: int): + return Byte(self.v | v) + + def __ior__(self, v: int): + self.v = (self.v | v) & 0xff + return self + def __eq__(self, other): if isinstance(other, Byte): return self.v == other.v @@ -63,61 +79,20 @@ class Byte: return f"(byte 0x{self.v:02x})" -def decode_line(line: bytes, method: int) -> bytes: - line = line.rstrip(b"\r\n") - key = LONG_KEY - seed = len(line) % len(key) - dline = bytearray(len(line)) - for i, c in enumerate(line): - b = Byte(c) - if dline[i - 2:i] == b'//': - key = OTHER_LONG_KEY - if method == 1: - if b == ord(b'\t'): - b = Byte(0x9f) - - b -= 0x20 - b ^= key[seed] - b &= 0x7f - b += 0x20 - - seed += 7 - seed %= len(key) - - if b == 0x9f: - b = Byte(ord(b'\t')) - else: - if b == ord(b'\t'): - b = Byte(0x80) - - b -= 0x20 - if (b & 0x80) == 0: - b ^= key[seed] & 0x7f - b += 0x20 - - seed += 7 - seed %= len(key) - - if b == 0x80: - b = Byte(ord(b'\t')) - dline[i] = b.v - return dline - - -def encode_line(line: bytes, method: int) -> bytes: - line = line.rstrip(b"\r\n") - key = LONG_KEY - seed = len(line) % len(key) - count = 0 - eline = bytearray(len(line)) - for i, c in enumerate(line): - if count == 2: - key = OTHER_LONG_KEY - if c == ord('/'): - count += 1 - else: - count = 0 - if method == 1: +class Codec1: + def encode_line(self, line: bytes) -> bytes: + line = line.rstrip(b"\r\n") + key = LONG_KEY + seed = len(line) % len(key) + count = 0 + eline = bytearray(len(line)) + for i, c in enumerate(line): + if count == 2: + key = OTHER_LONG_KEY + if c == ord('/'): + count += 1 + else: + count = 0 if c == ord('\t'): c = 0x9f @@ -132,7 +107,51 @@ def encode_line(line: bytes, method: int) -> bytes: if b == 0x9f: b = Byte(ord('\t')) - else: + eline[i] = b.v + return bytes(eline) + + def decode_line(self, line: bytes) -> bytes: + line = line.rstrip(b"\r\n") + key = LONG_KEY + seed = len(line) % len(key) + dline = bytearray(len(line)) + for i, c in enumerate(line): + b = Byte(c) + if dline[i - 2:i] == b'//': + key = OTHER_LONG_KEY + + if b == ord(b'\t'): + b = Byte(0x9f) + + b -= 0x20 + b ^= key[seed] + b &= 0x7f + b += 0x20 + + seed += 7 + seed %= len(key) + + if b == 0x9f: + b = Byte(ord(b'\t')) + dline[i] = b.v + return dline + + +class Codec2: + def encode_line(self, line: bytes) -> bytes: + line = line.rstrip(b"\r\n") + key = LONG_KEY + seed = len(line) % len(key) + count = 0 + eline = bytearray(len(line)) + for i, c in enumerate(line): + if count == 2: + key = OTHER_LONG_KEY + if c == ord('/'): + count += 1 + else: + count = 0 + if c == ord('\t'): c = 0x80 b = Byte(c - 0x20) @@ -145,24 +164,116 @@ def encode_line(line: bytes, method: int) -> bytes: if b == 0x80: b = Byte(ord('\t')) - eline[i] = b.v - return bytes(eline) + eline[i] = b.v + return bytes(eline) + + def decode_line(self, line: bytes) -> bytes: + line = line.rstrip(b"\r\n") + key = LONG_KEY + seed = len(line) % len(key) + dline = bytearray(len(line)) + for i, c in enumerate(line): + b = Byte(c) + if dline[i - 2:i] == b'//': + key = OTHER_LONG_KEY + + if b == ord(b'\t'): + b = Byte(0x80) + + b -= 0x20 + if (b & 0x80) == 0: + b ^= key[seed] & 0x7f + b += 0x20 + + seed += 7 + seed %= len(key) + + if b == 0x80: + b = Byte(ord(b'\t')) + + dline[i] = b.v + return dline + + +class CodecDemo: + def encode_line(self, line: bytes) -> bytes: + line = line.rstrip(b"\r\n") + key = DEMO_KEY + seed = len(line) % len(key) + dline = bytearray(len(line)) + for i, c in enumerate(line): + b = Byte(c) + + if b == ord('\t'): + b = Byte(0x9f) + + b -= 0x20 + b ^= key[seed] + b &= 0x7f + b += 0x20 + if b == 0x9f: + b = Byte(ord('\t')) + if b == ord('\n') or b == ord('\r'): + b |= 0x80 + + seed += 7 + seed %= len(key) + + dline[i] = b.v + return dline + + def decode_line(self, line: bytes) -> bytes: + line = line.rstrip(b"\r\n") + key = DEMO_KEY + seed = len(line) % len(key) + dline = bytearray(len(line)) + for i, c in enumerate(line): + b = Byte(c) + + if b == ord('\t'): + b = Byte(0x9f) + + b -= 0x20 + b ^= key[seed] + b &= 0x7f + b += 0x20 + + seed += 7 + seed %= len(key) + + if b == 0x9f: + b = Byte(ord('\t')) + + dline[i] = b.v + return dline + + +CODECS = { + Method.Method1: Codec1, + Method.Method2: Codec2, + Method.Demo: CodecDemo, +} def main(): + method_choices = tuple(e.value for e in Method.__members__.values()) parser = argparse.ArgumentParser(allow_abbrev=False, description="Decode/encode a Carmageddon text file") parser.add_argument("file", metavar="FILE", nargs="?", help="input file (default=stdin)") - parser.add_argument("--method", choices=[1, 2], type=int, default=2, help="encryption method to use (default=2)") + parser.add_argument("--method", choices=method_choices, default=Method.Method2.value, + help=f"encryption method to use (default={Method.Method2.value}, choices={','.join(method_choices)})") args = parser.parse_args() + method = Method(args.method) + codec = CODECS[method]() + istream = open(args.file, "rb") if args.file else sys.stdin.buffer for line in istream.readlines(): - if line[0] == ord(b'@'): - dline = decode_line(line[1:], args.method) + if line[0] == ord(b"@"): + dline = codec.decode_line(line[1:]) sys.stdout.buffer.write(dline) else: - eline = b"@" + encode_line(line, args.method) + eline = b"@" + codec.encode_line(line) sys.stdout.buffer.write(eline) sys.stdout.buffer.write(b'\n')