Invoke on something like dump/*.cst. Needs regex postprocessing, as is common.
1 #!python
2
3 import os, sys, zlib
4 from struct import unpack
5
6 for fname in sys.argv[1:]:
7 with open(fname, "rb") as f:
8 fdata = f.read()
9
10 header = fdata[:8].decode("utf-8")
11 if header != "CatScene":
12 continue
13
14 compressed_len = unpack("<I", bytes(fdata[0x08:0x0C]))[0]
15 decompressed_len = unpack("<I", bytes(fdata[0x0C:0x10]))[0]
16
17 decompressed = zlib.decompress(bytes(fdata[0x10:compressed_len+0x10]))
18 fdata = decompressed
19
20 with open(fname.replace(".cst", "")+".bin", "wb") as f2:
21 f2.write(fdata)
22
23 flen = unpack("<I", bytes(fdata[0x00:0x04]))[0]
24 ranges = unpack("<I", bytes(fdata[0x04:0x08]))[0]
25 ranges_bytes = unpack("<I", bytes(fdata[0x08:0x0C]))[0]
26 headerlen = unpack("<I", bytes(fdata[0x0C:0x10]))[0]
27
28 # header structure is something like
29 # uint32 filelen
30 # uint32 num_ranges
31 # uint32 len_ranges_bytes
32 # uint32 len_header_bytes (excluding these first four words)
33
34 # struct[num_ranges] {uint32, uint32} // where first is length of range, second is index of range
35 # uint32[entries] // indexed into by ranges
36 # with this part (the two arrays together) being len_header_bytes bytes long
37
38 # we don't actually need any of that for just script ripping
39
40 code = fdata[headerlen+0x10:]
41
42 i = 0
43 while len(code[i:]) > 0:
44 # guess
45 oplen = code[i]
46 if oplen != 1:
47 print("oplen error")
48 exit()
49 i += 1
50
51 op = code[i]
52 i += 1
53 if op in [0x20, 0x21, 0x30]:
54 string = []
55 char = code[i]
56 while char != 0:
57 string += [char]
58 i += 1
59 char = code[i]
60 i += 1
61 string = bytes(string).decode("cp932")
62
63 if op == 0x20:
64 print(string.replace("\n", "").replace("\\n",""), end="")
65 elif op in [0x02]: # linefeed
66 print("")
67 i += 1
68 elif op in [0x03]: # some other kind of linefeed (grisaia)
69 print("")
70 i += 1
71 else:
72 print(f"\n\nunknown op {op:02X} at {i+headerlen+0x10:08X} in {fname}\n")
73 exit()
74 print("")