Fix demo compatibility (#494)

* Fix demo compatibility

Regression introduced in 81116169cf

* Decode gDecode_string (use macro's to show its definition)

* Fix IWANTTOFIDDLE cheat for demo

* Add demo support to tools/decode_datatxt.py
This commit is contained in:
Anonymous Maarten 2025-10-03 03:23:11 +02:00 committed by GitHub
parent 9c82a405bd
commit 12a48ffddf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 269 additions and 66 deletions

View File

@ -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");

View File

@ -1595,7 +1595,7 @@ void SelectRaceDraw(int pCurrent_choice, int pCurrent_mode) {
fputs("*************", f);
}
}
gDecode_thing ^= 0x40u;
gDecode_thing ^= '@';
fclose(f);
EncodeAllFilesInDirectory("");
EncodeAllFilesInDirectory("CARS");

View File

@ -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);
}

View File

@ -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

View File

@ -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')