Skip to content

Commit 102b740

Browse files
committed
extract and/or replace eyeball frames
1 parent eb8e828 commit 102b740

File tree

1 file changed

+92
-55
lines changed

1 file changed

+92
-55
lines changed

Teddy_Ruxpin/ruxalyzer.py

Lines changed: 92 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
#!/usr/local/bin/python3
22

33
import struct
4+
import PIL.Image
45

5-
FILENAME = "Intro.bin"
6+
FILENAME = "Idle.bin"
67

7-
storyfile = open(FILENAME, "rb")
8+
storyfile = open(FILENAME, "rb+")
89

910
SNXROM = {}
1011

@@ -32,82 +33,116 @@
3233
assetTablePointers = [0] * SNXROM['assetTableLengthWords']
3334
for i in range(SNXROM['assetTableLengthWords']):
3435
assetTablePointers[i] = struct.unpack('I', storyfile.read(4))[0]
35-
#print("Asset table pointers:", [hex(i) for i in assetTablePointers])
36+
print("Asset table pointers:", [hex(i) for i in assetTablePointers])
3637

3738

3839

3940
# Go to the metadata table
4041
SNXROM['metaOffset'] = assetTablePointers[0]
4142
storyfile.seek(SNXROM['metaOffset'])
4243

43-
if 0x0000 != struct.unpack('h', storyfile.read(2))[0]:
44-
raise RuntimeError("Didn't find 0x0000 magic bytes")
45-
SNXROM['storyId'] = struct.unpack('h', storyfile.read(2))[0]
46-
SNXROM['numberOfEyeAnimations'] = struct.unpack('h', storyfile.read(2))[0]
47-
SNXROM['numberOfEyeBitmaps'] = struct.unpack('h', storyfile.read(2))[0]
48-
SNXROM['numberOfVideoSequences'] = struct.unpack('h', storyfile.read(2))[0]
49-
SNXROM['numberOfAudioBlocks'] = struct.unpack('h', storyfile.read(2))[0]
50-
print("Story ID: ", SNXROM['storyId'])
51-
print("Eye animations:", SNXROM['numberOfEyeAnimations'])
52-
print("Eye bitmaps:", SNXROM['numberOfEyeBitmaps'])
53-
print("Video seqs:", SNXROM['numberOfVideoSequences'])
54-
print("Audio blocks:", SNXROM['numberOfAudioBlocks'])
44+
if 0x0000 == struct.unpack('h', storyfile.read(2))[0]:
45+
SNXROM['storyId'] = struct.unpack('h', storyfile.read(2))[0]
46+
SNXROM['numberOfEyeAnimations'] = struct.unpack('h', storyfile.read(2))[0]
47+
SNXROM['numberOfEyeBitmaps'] = struct.unpack('h', storyfile.read(2))[0]
48+
SNXROM['numberOfVideoSequences'] = struct.unpack('h', storyfile.read(2))[0]
49+
SNXROM['numberOfAudioBlocks'] = struct.unpack('h', storyfile.read(2))[0]
50+
print("Story ID: ", SNXROM['storyId'])
51+
print("Eye animations:", SNXROM['numberOfEyeAnimations'])
52+
print("Eye bitmaps:", SNXROM['numberOfEyeBitmaps'])
53+
print("Video seqs:", SNXROM['numberOfVideoSequences'])
54+
print("Audio blocks:", SNXROM['numberOfAudioBlocks'])
55+
else:
56+
print("Didn't find 0x0000 magic bytes, skipping meta!")
57+
SNXROM['metaOffset'] = 0
58+
5559

5660
SNXROM['ROMfilesize'] = struct.unpack('i', storyfile.read(4))[0]
5761
print(SNXROM['ROMfilesize'])
5862

5963
##############
6064

6165
SNXROM['audioOffset'] = assetTablePointers[-1]
62-
66+
SNXROM['marktable'] = []
67+
6368
# verify audio offset
6469
print("Looking for audio at", hex(SNXROM['audioOffset']))
6570
storyfile.seek(SNXROM['audioOffset'])
6671

67-
if "AU" != storyfile.read(2).decode('utf-8'):
68-
raise RuntimeError("Didn't find AU at beginning of audio")
69-
print("Found beginning of audio")
70-
(samplerate, bitrate, channels, frame_count, file_len, mark_flag, silence_flag, \
71-
mbf, pcs, rec, header_len) = \
72-
struct.unpack('<HHHIIHHHHHH', storyfile.read(26))
73-
74-
print("Header Size (16b words):", hex(header_len))
75-
76-
print("Samplerate:", samplerate)
77-
print("Bitrate:", bitrate)
78-
print("Channels:", channels)
79-
print("Frame count:", frame_count)
80-
print("File len (16b words):", file_len)
81-
if (file_len * 2) / 80 != frame_count:
82-
print("Should be %d frames * 80 bytes per frame = %d total size" % (frame_count, file_len*2))
83-
print("Mark flag:", mark_flag)
84-
print("Silence flag:", silence_flag)
85-
print("Header Size (16b words):", header_len)
72+
if b'AU' == storyfile.read(2):
73+
print("Found beginning of audio")
74+
(samplerate, bitrate, channels, frame_count, file_len, mark_flag, silence_flag, \
75+
mbf, pcs, rec, header_len) = \
76+
struct.unpack('<HHHIIHHHHHH', storyfile.read(26))
77+
78+
print("Header Size (16b words):", hex(header_len))
79+
80+
print("Samplerate:", samplerate)
81+
print("Bitrate:", bitrate)
82+
print("Channels:", channels)
83+
print("Frame count:", frame_count)
84+
print("File len (16b words):", file_len)
85+
if (file_len * 2) / 80 != frame_count:
86+
print("Should be %d frames * 80 bytes per frame = %d total size" % (frame_count, file_len*2))
87+
print("Mark flag:", mark_flag)
88+
print("Silence flag:", silence_flag)
89+
print("Header Size (16b words):", header_len)
90+
91+
# toss 0xFFFFFFFF
92+
storyfile.read(4)
93+
94+
table_size = struct.unpack('<H', storyfile.read(2))[0]
95+
#print(table_size * 2)
96+
mark_entries = header_len*2 - (storyfile.tell() - SNXROM['audioOffset']) - 4
97+
print("entries:", mark_entries)
98+
99+
SNXROM['marktable'] = []
100+
for i in range(mark_entries // 2):
101+
SNXROM['marktable'].append(struct.unpack('<H', storyfile.read(2))[0])
102+
totaltime = 0;
103+
for i in range(len(SNXROM['marktable']) // 2):
104+
totaltime+= SNXROM['marktable'][2*i+0]
105+
print( SNXROM['marktable'][2*i+0], SNXROM['marktable'][2*i+1])
106+
print("total length of motion mark table (s):", totaltime / 1000.0)
107+
108+
print("Actual audio data starts at", hex(SNXROM['audioOffset'] + header_len*2))
109+
print("and ends at ", hex(SNXROM['audioOffset'] + header_len*2 + file_len*2))
110+
111+
else:
112+
print("Didn't find AU at beginning of audio, no mark table!")
113+
114+
# The rest are eyeball frames, extract them!
115+
116+
for i,pointer in enumerate(assetTablePointers):
117+
if SNXROM['metaOffset'] and (i == 0):
118+
continue # skip metadata table if it exists
119+
if SNXROM['marktable'] and (i == len(assetTablePointers)-1):
120+
continue # skip audio table if it exists
121+
print(i,pointer)
122+
storyfile.seek(pointer)
123+
image_data = storyfile.read(128*128*2)
124+
125+
print(len(image_data))
126+
PIL.Image.frombytes('RGB', (128,128), image_data, 'raw', 'BGR;16').save(f'eye{i}.png')
127+
continue
128+
129+
logobytes = bytearray(b'')
130+
for pix in PIL.Image.open("logo.png").convert('RGB').getdata():
131+
r = (pix[0] >> 3) & 0x1F
132+
g = (pix[1] >> 2) & 0x3F
133+
b = (pix[2] >> 3) & 0x1F
134+
logobytes = logobytes + struct.pack('H', (r << 11) + (g << 5) + b)
135+
#print(len(logobytes))
136+
storyfile.write(logobytes)
137+
86138

87139
#print(storyfile.tell() - SNXROM['audioOffset'])
88140

89-
# toss 0xFFFFFFFF
90-
storyfile.read(4)
91-
92-
table_size = struct.unpack('<H', storyfile.read(2))[0]
93-
#print(table_size * 2)
94-
mark_entries = header_len*2 - (storyfile.tell() - SNXROM['audioOffset']) - 4
95-
print("entries:", mark_entries)
96-
97-
marktable = []
98-
for i in range(mark_entries // 2):
99-
marktable.append(struct.unpack('<H', storyfile.read(2))[0])
100-
totaltime = 0;
101-
for i in range(len(marktable) // 2):
102-
totaltime+=marktable[2*i+0]
103-
print(marktable[2*i+0], marktable[2*i+1])
104-
print("total length of motion mark table (s):", totaltime / 1000.0)
105-
106-
print("Actual audio data starts at", hex(SNXROM['audioOffset'] + header_len*2))
107-
print("and ends at ", hex(SNXROM['audioOffset'] + header_len*2 + file_len*2))
108141
"""
109142
110-
143+
seen.add(i)
144+
asset = (c_uint16 * (128*128)).from_buffer(content, assetTablePointers[i])
145+
PIL.Image.frombytes('RGB', (128,128), string_at(asset, 128*128*2), 'raw', 'RGB;16').save(f'eye{i}.png')
111146
112147
SNXROM['leftEyeOffset'] = struct.unpack('I', storyfile.read(4))[0]
113148
SNXROM['rightEyeOffset'] = struct.unpack('I', storyfile.read(4))[0]
@@ -122,3 +157,5 @@
122157
print(storyfile.tell())
123158
124159
"""
160+
161+
storyfile.close()

0 commit comments

Comments
 (0)