-
Notifications
You must be signed in to change notification settings - Fork 0
/
pw64_taskmod_json_poc.py
executable file
·496 lines (404 loc) · 24 KB
/
pw64_taskmod_json_poc.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
#!/usr/bin/env python3
# Pilotwings 64 Mission Editor: Millenial Edition 2000 XP - Now with JSON cleaning power!
# by roto
import binascii
import io
import itertools
import json
import os
import struct
from subprocess import call
import sys
import pw64_lib
# NOTE:
# Even if you feed a JSON that is compiled from an unmodified (stock) ROM and don't modify a task, the TABL chunk
# is rebuilt/re-compressed and ROM checksum is recalculated. Thus you won't get a 1:1 ROM back out of this script.
#
# Q: What is this?
# A: An evolution of "pw64_taskmod_poc.py" (which only did one thing: inject BALS object) but now with the ability to do more via JSON.
# Check the README on GitHub.
# Fill me in
PW64_ROM = ""
def main():
test_id = "E_GC_1" # All JSON data ingestion functions default to reading "E_GC_1.json"
pw64_lib.build_fs_table(PW64_ROM)
# Print full FS Table
if len(sys.argv) == 2 and sys.argv[1] == "-l":
pw64_lib.show_fs_table()
sys.exit(0)
# Print list of specified file type
elif len(sys.argv) == 3 and sys.argv[1] == "-l":
pw64_lib.show_fs_table(sys.argv[2].upper())
sys.exit(0)
###
# FIXME: There's an issue with modifying/resizing the ROM multiple times prior to patching and re-reading TABL from ROM.
# If we don't run this change_some_text() function early on (or later on after writing the TABL patch to ROM!), there's a problem
# with incorrect with offsets and build_fs_table blows up when trying to do ADAT lookup... fix later.
#change_some_text()
###
# Get the original (pre-mod) size of the UPWT in question
old_upwt_size = pw64_lib.get_fs_index_and_size_of_task(test_id)[1] #0x6b8 # Best way to get this?
# Do the actual modifications and get our new UPWT size in return
# This should really be split into multiple functions.
new_upwt_size = modify_upwt(test_id)
# Update specified Task ID with new size in TABL.
pw64_lib.update_task_size_in_tabl(test_id, new_upwt_size)
# Does what it says.
pw64_lib.rebuild_TABL()
# Inject our updated and compressed TABL back into the ROM.
pw64_lib.inject_TABL(PW64_ROM)
###
# See FIXME above. Doing this *after* rebuilding/injecting the TABL seems to not blow anything up...
# EDIT: Now I know why: build_adat_layout opens the ROM on disk, if that ROM was not updated, it won't have the new TABL with updates.
# So we have to make this change BEFORE or AFTER modifying any other data on the ROM... Hmm :thinkingface:
#
# If we are making further changes, and the changes require lookups for files offsets then
# we have to re-read the TABL from ROM and clear+rebuild FS table after our previous UPWT mods.
# This MUST be done because the "fs_table" lives in memory and is not updated after TABL is rebuilt.
pw64_lib.fs_table = pw64_lib.build_fs_table(PW64_ROM)
change_some_text(test_id)
###
# Patch all our addresses that deal with FileSystem sizes
pw64_lib.patch_fs_addrs(new_upwt_size - old_upwt_size, PW64_ROM)
pw64_lib.fix_rom_checksum(PW64_ROM) # Does what it says on the label.
pw64_lib.show_fs_table("UPWT") # Show the listing of UPWT's at the end for whatever reason
print("Done: If your ROM diff does not match what you expect, make sure your E_GC_1.json is correct.")
def modify_upwt(test_id):
# Read in the base (unmodified) COMM chunk and also address/offset of said chunk
original_comm_data_and_addr = pw64_lib.read_comm_from_rom(test_id, PW64_ROM)
original_comm_data = original_comm_data_and_addr[0]
original_comm_addr = original_comm_data_and_addr[1]
print("- Original COMM Data Length from %s: %s" % (test_id, hex(len(original_comm_data))))
# Loads in original COMM and returns modified COMM data chunk
modified_comm_data = rebuild_comm(test_id, original_comm_data)
# Send modified COMM and work on the rest of the data chunks in the UPWT
final_upwt_data = assemble_final_upwt(test_id, modified_comm_data)
print("- Modified COMM + UPWT Data Length: %s (Minus 0x88 bytes for rest of UPWT chunks/headers)" % hex(len(final_upwt_data)))
new_comm_data_chunk_size = len(final_upwt_data)
# Write our new COMM+DATA to the ROM at the specified address
write_final_upwt(test_id, final_upwt_data, original_comm_addr, new_comm_data_chunk_size, PW64_ROM)
# This one needs some re-thinking (see later "update_upwt_size" call)
final_upwt_size = len(final_upwt_data)+0x8+0x80 # 0x8 = COMM header + size, 0x80 = FORM+PAD+JPTX+etc UPWT data+headers
print("- Full UPWT (Headers+COMM+Data) Chunk Length: %s" % hex(final_upwt_size))
# Modify the size of the new UPWT chunk in the FORM
FORM_size_offset = original_comm_addr - 0x82 # What is 0x82!? Crap I don't remember why this is here...
# Patches the FORM chunk of the UPWT (minus 0x8 bytes for FORM Maker+Size)
pw64_lib.update_upwt_size(FORM_size_offset, final_upwt_size-0x8, PW64_ROM)
print("* All UPWT modifications completed.")
return final_upwt_size
def assemble_final_upwt(filename, modified_comm_data):
###
# Chunks that can vary in size should not use these static headers (e.g. BALS/RNGS)
###
# TPAD is 0x30 bytes + Marker (0x4) + Size (0x4)
# From: E_GC_1
TPAD_header = bytes.fromhex('5450414400000030')
# RNGS is 0x84 bytes (per ring) + Marker (0x4) + Size of ALL RNGS (0x4)
# From: E_GC_1
RNGS_header = bytes.fromhex('524E475300000190') # 0x190 is 3 rings's worth of data chunks
# LSTP is 0x28 bytes + Marker (0x4) + Size (0x4)
# From: E_GC_1
LSTP_header = bytes.fromhex('4C53545000000028')
# BALS is 0x68 bytes + Marker (0x4) + Size (0x4)
# From: E_RP_1 (balloon on top of castle)
BALS_header = bytes.fromhex('42414C5300000068')
# Read in the JSON file
with open(filename + ".json", 'r') as json_in:
data = json_in.read()
decoded_data = json.loads(data)
json_in.close()
# We'll be doing some reads/writes in memory
upwt_assembler = io.BytesIO()
# We're going to build our new UPWT container. Write our pre-modified COMM chunk forst
upwt_assembler.write(modified_comm_data)
# Assemble the various parts of the UPWT
# The order of where these chunks are placed doesn't appear to be important (which is why we append BALS at the end and it just works)
if decoded_data["COMM"]["TPAD"] >= 1:
upwt_assembler.seek(0, 2) # SEEK_END
TPAD_template = rebuild_upwt_chunk("E_GC_1", "TPAD")
upwt_assembler.write(TPAD_header)
upwt_assembler.write(TPAD_template)
if decoded_data["COMM"]["RNGS"] >= 1:
upwt_assembler.seek(0, 2) # SEEK_END
RNGS_template = rebuild_upwt_chunk("E_GC_1", "RNGS")
RNGS_size = 0x84 * decoded_data["COMM"]["RNGS"] + 0x4 # Needs to account for the odd 0x4 bytes at the end of the RNGS chunk
#print(hex(RNGS_size))
RNGS_header = b'\x52\x4e\x47\x53' + b'%s' % bytes.fromhex(hex(RNGS_size)[2:].zfill(8)) # 0190... hmm do this better.
upwt_assembler.write(RNGS_header)
upwt_assembler.write(RNGS_template)
if decoded_data["COMM"]["LSTP"] >= 1:
upwt_assembler.seek(0, 2) # SEEK_END
LSTP_template = rebuild_upwt_chunk("E_GC_1", "LSTP")
upwt_assembler.write(LSTP_header)
upwt_assembler.write(LSTP_template)
if decoded_data["COMM"]["BALS"] >= 1:
upwt_assembler.seek(0, 2) # SEEK_END
BALS_template = rebuild_upwt_chunk("E_GC_1", "BALS")
BALS_size = 0x68 * decoded_data["COMM"]["BALS"] # If we have >1 BALS need to update size in chunk
BALS_header = b'\x42\x41\x4C\x53' + b'%s' % bytes.fromhex(hex(BALS_size)[2:].zfill(8)) # 68
upwt_assembler.write(BALS_header)
upwt_assembler.write(BALS_template)
# Go back to the beginning and read it all again.
upwt_assembler.seek(0)
upwt_data = upwt_assembler.read(upwt_assembler.getbuffer().nbytes) # A way to size up our BytesIO object
upwt_assembler.close()
# Send it back for writing to ROM
return upwt_data
def rebuild_upwt_chunk(filename, chunk):
# This function does the actual "rebuilding" of objects from JSON data.
# If an object doesn't exist in the JSON input, it won't get added to the UPWT container. Related to COMM counts.
# Read in the JSON file and start plugging in data
with open(filename + ".json", 'r') as json_in:
data = json_in.read()
decoded_data = json.loads(data)
json_in.close()
# Stock chunks from ROM, to be modified if JSON data differs
# This _mostly_ should be OK in terms of "unknown" bytes in the data stream... seems to work out fine at least for E_GC_1
TPAD_template = bytes.fromhex('C222EB85C3CADCAC4120000041C00000000000000000000000000000000000000000000000000000000000003F800000')
RNGS_template = bytes.fromhex('C392E937432F03D7425C000041B8000000000000000000000000000000010000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000201000000000000000000006E0000004000000000000000000000007901000000000000000000000000000000000000')
LSTP_template = bytes.fromhex('C1E68F5CC3D80E3541200000C316D74CC31E0C4A412000000000000001000000420C000000000000')
BALS_template = bytes.fromhex('C290210643A74D71433600008000000000000000000000000000000041200000000000003C23D70A0000000000000000402000000000001E0000000000000000411CCCCD0000000000000000000000000000000000000000000000003F80000000000000411CCCCD')
upwt_io = io.BytesIO()
if chunk == "TPAD":
XPAD_layout = pw64_lib.TPAD_layout
upwt_io.write(TPAD_template) # Lay down the default, unmodified chunk
elif chunk == "LSTP":
XPAD_layout = pw64_lib.LSTP_layout
upwt_io.write(LSTP_template)
elif chunk == "BALS":
BALS_layout = pw64_lib.BALS_layout
upwt_io.write(BALS_template)
# Set up a stream object just in case we have >1 BALS chunk
bals_stack = io.BytesIO()
if decoded_data["COMM"]["BALS"] >= 1:
for ball_num in range(1, decoded_data["COMM"]["BALS"] + 1):
#print(binascii.hexlify(BALS_template))
ball_num = str(ball_num)
upwt_io.seek(0)
# Go through our structure and populate the template, convert all floats back to hex
upwt_io.seek(BALS_layout["x"], 0)
upwt_io.write(pw64_lib.float_to_hex(decoded_data[chunk][ball_num]["x"]))
upwt_io.seek(BALS_layout["z"], 0)
upwt_io.write(pw64_lib.float_to_hex(decoded_data[chunk][ball_num]["z"]))
upwt_io.seek(BALS_layout["y"], 0)
upwt_io.write(pw64_lib.float_to_hex(decoded_data[chunk][ball_num]["y"]))
upwt_io.seek(BALS_layout["scale"], 0)
upwt_io.write(pw64_lib.float_to_hex(decoded_data[chunk][ball_num]["scale"]))
# Things that don't get converted to float or decoded are straight ASCII hex
upwt_io.seek(BALS_layout["color"], 0)
upwt_io.write(bytes.fromhex(decoded_data[chunk][ball_num]["color"]))
upwt_io.seek(BALS_layout["type"], 0)
upwt_io.write(bytes.fromhex(decoded_data[chunk][ball_num]["type"]))
upwt_io.seek(BALS_layout["solidity"], 0)
upwt_io.write(bytes.fromhex(decoded_data[chunk][ball_num]["solidity"]))
upwt_io.seek(BALS_layout["weight"], 0)
upwt_io.write(bytes.fromhex(decoded_data[chunk][ball_num]["weight"]))
upwt_io.seek(BALS_layout["popforce"], 0)
upwt_io.write(bytes.fromhex(decoded_data[chunk][ball_num]["popforce"]))
# Go to the end of the BALS stack, add current ring
bals_stack.seek(0, 2)
upwt_io.seek(0, 0)
bals_stack.write(upwt_io.read())
# Seek to start of bals_stack then return the ring stack (containing the X number of BALS)
bals_stack.seek(0, 0)
bals_data = bals_stack.read()
bals_stack.close()
print("* UPWT data chunk(s) rebuilt: %s" % chunk)
return bals_data
elif chunk == "RNGS":
RNGS_layout = pw64_lib.RNGS_layout
upwt_io.write(RNGS_template)
# We likely have more than one ring so lets make a buffer for it
rngs_stack = io.BytesIO()
ring_count = decoded_data["COMM"]["RNGS"]
if ring_count >= 1:
for ring_num in range(1, ring_count + 1):
ring_num = str(ring_num)
upwt_io.seek(0)
# Go through our structure and populate the template, convert all floats back to hex
upwt_io.seek(RNGS_layout["x"], 0)
upwt_io.write(pw64_lib.float_to_hex(decoded_data[chunk][ring_num]["x"]))
upwt_io.seek(RNGS_layout["z"], 0)
upwt_io.write(pw64_lib.float_to_hex(decoded_data[chunk][ring_num]["z"]))
upwt_io.seek(RNGS_layout["y"], 0)
upwt_io.write(pw64_lib.float_to_hex(decoded_data[chunk][ring_num]["y"]))
upwt_io.seek(RNGS_layout["yaw"], 0)
upwt_io.write(pw64_lib.float_to_hex(decoded_data[chunk][ring_num]["yaw"]))
upwt_io.seek(RNGS_layout["pitch"], 0)
upwt_io.write(pw64_lib.float_to_hex(decoded_data[chunk][ring_num]["pitch"]))
upwt_io.seek(RNGS_layout["roll"], 0)
upwt_io.write(pw64_lib.float_to_hex(decoded_data[chunk][ring_num]["roll"]))
upwt_io.seek(RNGS_layout["size"], 0)
upwt_io.write(bytes.fromhex(decoded_data[chunk][ring_num]["size"]))
upwt_io.seek(RNGS_layout["state"], 0)
upwt_io.write(bytes.fromhex(decoded_data[chunk][ring_num]["state"]))
upwt_io.seek(RNGS_layout["motion_axis"], 0)
upwt_io.write(bytes.fromhex(decoded_data[chunk][ring_num]["motion_axis"]))
upwt_io.seek(RNGS_layout["motion_rad_start"], 0)
upwt_io.write(bytes.fromhex(decoded_data[chunk][ring_num]["motion_rad_start"]))
upwt_io.seek(RNGS_layout["motion_rad_end"], 0)
upwt_io.write(bytes.fromhex(decoded_data[chunk][ring_num]["motion_rad_end"]))
upwt_io.seek(RNGS_layout["rotation"], 0)
upwt_io.write(bytes.fromhex(decoded_data[chunk][ring_num]["rotation"]))
upwt_io.seek(RNGS_layout["rotation_speed"], 0)
upwt_io.write(bytes.fromhex(decoded_data[chunk][ring_num]["rotation_speed"]))
upwt_io.seek(RNGS_layout["ring_special"], 0)
upwt_io.write(bytes.fromhex(decoded_data[chunk][ring_num]["ring_special"]))
upwt_io.seek(RNGS_layout["next_ring_unknown"], 0)
upwt_io.write(bytes.fromhex(decoded_data[chunk][ring_num]["next_ring_unknown"]))
upwt_io.seek(RNGS_layout["next_ring_order_count"], 0)
upwt_io.write(bytes.fromhex(decoded_data[chunk][ring_num]["next_ring_order_count"]))
if int(decoded_data[chunk][ring_num]["next_ring_order_count"], 16) >= 1:
for ring_index in range(0, int(decoded_data[chunk][ring_num]["next_ring_order_count"], 16)):
upwt_io.seek(RNGS_layout["next_ring_index"][ring_index], 0)
upwt_io.write(bytes.fromhex(decoded_data[chunk][ring_num]["next_ring_index"][str(ring_index)]))
else:
# This is a new one I found out while writing this tool...
# There seems to be form of list of rings to clear in order in some tasks. Sometimes its linear, sometimes there's choices.
# Prior to reading the next ring we have to clear the current/old index data of the "available" rings.
# Otherwise the next ring (even if it has 0 for the count) will re-use the RNGS_template with the previous ring's ring count.
# Im so tired and not sure why this works... but it will probably break for any level other than E_GC_1
# This will need attention later for sure.
upwt_io.seek(RNGS_layout["next_ring_index"][ring_index], 0)
upwt_io.write(bytes.fromhex("00000000"))
# Add to our stack
rngs_stack.seek(0, 2)
upwt_io.seek(0, 0)
rngs_stack.write(upwt_io.read())
# For some reason theres another 0x4 bytes at the end of the RNGS chunk...
rngs_stack.seek(0, 2)
rngs_stack.write(b'\x00\x00\x00\x00')
# Seek to start of rngs_stack, then dump the whole ring stack to a var for return
rngs_stack.seek(0, 0)
#upwt_io.seek(0, 0)
ring_data = rngs_stack.read()
rngs_stack.close()
print("* UPWT data chunk(s) rebuilt: %s" % chunk)
return ring_data
if chunk == "TPAD" or chunk == "LSTP":
upwt_io.seek(0)
# Go through our structure and populate the template, convert all floats back to hex
upwt_io.seek(XPAD_layout["x"], 0)
upwt_io.write(pw64_lib.float_to_hex(decoded_data[chunk]["x"]))
upwt_io.seek(XPAD_layout["z"], 0)
upwt_io.write(pw64_lib.float_to_hex(decoded_data[chunk]["z"]))
upwt_io.seek(XPAD_layout["y"], 0)
upwt_io.write(pw64_lib.float_to_hex(decoded_data[chunk]["y"]))
upwt_io.seek(XPAD_layout["yaw"], 0)
upwt_io.write(pw64_lib.float_to_hex(decoded_data[chunk]["yaw"]))
upwt_io.seek(XPAD_layout["roll"], 0)
upwt_io.write(pw64_lib.float_to_hex(decoded_data[chunk]["roll"]))
upwt_io.seek(XPAD_layout["pitch"], 0)
upwt_io.write(pw64_lib.float_to_hex(decoded_data[chunk]["pitch"]))
if chunk == "TPAD":
upwt_io.seek(XPAD_layout["vehicle_fuel"], 0)
upwt_io.write(pw64_lib.float_to_hex(decoded_data[chunk]["vehicle_fuel"]))
# If we reach here, we return one of the singular objects like TakeOff Pad or Landing Strip.
upwt_io.seek(0)
upwt_output_chunk = upwt_io.read()
upwt_io.close()
print("* UPWT data chunk(s) rebuilt: %s" % chunk)
return upwt_output_chunk
def rebuild_comm(filename, comm_data):
# COMM chunk is *always* 0x430 bytes in size
# Read in the JSON file and start plugging in data
with open(filename + ".json", 'r') as json_in:
data = json_in.read()
decoded_data = json.loads(data)
json_in.close()
COMM_layout = pw64_lib.COMM_layout
comm_assembler = io.BytesIO()
comm_assembler.write(comm_data)
comm_assembler.seek(COMM_layout["pilot_class"], 0)
comm_assembler.write(bytes.fromhex(decoded_data["COMM"]["pilot_class"]))
comm_assembler.seek(COMM_layout["vehicle"], 0)
comm_assembler.write(bytes.fromhex(decoded_data["COMM"]["vehicle"]))
comm_assembler.seek(COMM_layout["test_number"], 0)
comm_assembler.write(bytes.fromhex(decoded_data["COMM"]["test_number"]))
comm_assembler.seek(COMM_layout["level"], 0)
comm_assembler.write(bytes.fromhex(decoded_data["COMM"]["level"]))
comm_assembler.seek(COMM_layout["skybox"], 0)
comm_assembler.write(bytes.fromhex(decoded_data["COMM"]["skybox"]))
comm_assembler.seek(COMM_layout["snow"], 0)
comm_assembler.write(bytes.fromhex(decoded_data["COMM"]["snow"]))
comm_assembler.seek(COMM_layout["wind_WE"], 0)
comm_assembler.write(pw64_lib.float_to_hex(decoded_data["COMM"]["wind_WE"]))
comm_assembler.seek(COMM_layout["wind_SN"], 0)
comm_assembler.write(pw64_lib.float_to_hex(decoded_data["COMM"]["wind_SN"]))
comm_assembler.seek(COMM_layout["wind_UD"], 0)
comm_assembler.write(pw64_lib.float_to_hex(decoded_data["COMM"]["wind_UD"]))
# These get converted from int() to bytes([])
comm_assembler.seek(COMM_layout["THER"], 0)
comm_assembler.write(bytes([decoded_data["COMM"]["THER"]]))
comm_assembler.seek(COMM_layout["LWND"], 0)
comm_assembler.write(bytes([decoded_data["COMM"]["LWND"]]))
comm_assembler.seek(COMM_layout["TPAD"], 0)
comm_assembler.write(bytes([decoded_data["COMM"]["TPAD"]]))
comm_assembler.seek(COMM_layout["LPAD"], 0)
comm_assembler.write(bytes([decoded_data["COMM"]["LPAD"]]))
comm_assembler.seek(COMM_layout["LSTP"], 0)
comm_assembler.write(bytes([decoded_data["COMM"]["LSTP"]]))
comm_assembler.seek(COMM_layout["RNGS"], 0)
comm_assembler.write(bytes([decoded_data["COMM"]["RNGS"]]))
comm_assembler.seek(COMM_layout["BALS"], 0)
comm_assembler.write(bytes([decoded_data["COMM"]["BALS"]]))
comm_assembler.seek(COMM_layout["TARG"], 0)
comm_assembler.write(bytes([decoded_data["COMM"]["TARG"]]))
comm_assembler.seek(COMM_layout["HPAD"], 0)
comm_assembler.write(bytes([decoded_data["COMM"]["HPAD"]]))
comm_assembler.seek(COMM_layout["BTGT"], 0)
comm_assembler.write(bytes([decoded_data["COMM"]["BTGT"]]))
comm_assembler.seek(COMM_layout["PHTS"], 0)
comm_assembler.write(bytes([decoded_data["COMM"]["PHTS"]]))
comm_assembler.seek(COMM_layout["FALC"], 0)
comm_assembler.write(bytes([decoded_data["COMM"]["FALC"]]))
comm_assembler.seek(COMM_layout["UNKN"], 0)
comm_assembler.write(bytes([decoded_data["COMM"]["UNKN"]]))
comm_assembler.seek(COMM_layout["CNTG"], 0)
comm_assembler.write(bytes([decoded_data["COMM"]["CNTG"]]))
comm_assembler.seek(COMM_layout["HOPD"], 0)
comm_assembler.write(bytes([decoded_data["COMM"]["HOPD"]]))
# Rewind and populate the data to return. Yeah I know this can be optimized. Bite me.
comm_assembler.seek(0)
final_comm_data = comm_assembler.read()
print("* COMM data rebuilt.")
return final_comm_data
def write_final_upwt(test_id, final_upwt_data, addr, new_comm_data_chunk_size, output_rom):
# We open the ROM in read-only mode, read in the original data from the ROM up to where we want to insert our new data.
# Open a temp file in write mode, read the ROM up to the insertion address and then insert the BALS bytes we read in previously.
# Then read in (from old ROM) and write (to temp file) the rest of the ROM.
# This is a form of "injecting" data into a binary file. Python doesn't have an "insert" mode for writing files...
# When done writing out this newly munged crap into a temp file, remove the original and rename our temp file back to the OG ROM name.
# There's probably a better way to do this.
old_upwt_size = pw64_lib.get_fs_index_and_size_of_task(test_id)[1] - 0x8 - 0x80 # 0x6b8 - FORM Header/Size - Other shit
with open(PW64_ROM, 'rb') as pw64_rom_first, open("PW64_ROM_TMP.z64", 'wb') as pw64_rom_second:
pw64_rom_second.write(pw64_rom_first.read(addr)) # Read all the bytes up to where we need to insert our data
pw64_rom_second.write(final_upwt_data) # Add our newly recompiled UPWT container
# This is where it gets kinda weird.
# In the unmodified ROM we seek to the offset at the end of the original UPWT container's address to read then write the rest of the old ROM data to the new ROM.
pw64_rom_first.seek(old_upwt_size, 1) # OLD: 0x630 == OLD COMM+DATA chunk! 0x6A0 = NEW (with BALS) COMM+DATA. 0x6a0 - 0x630 = 0x70!
pw64_rom_second.write(pw64_rom_first.read()) # Read in the rest of the data from original ROM and write to temp file
fs_size_change = len(final_upwt_data) - old_upwt_size
if fs_size_change > 0:
pw64_rom_second.flush()
pw64_rom_second.seek(-fs_size_change, os.SEEK_END)
pw64_rom_second.truncate()
pw64_rom_second.close()
pw64_rom_first.close()
# Switch our files ... ooh scary.
os.remove(PW64_ROM)
os.rename('PW64_ROM_TMP.z64', PW64_ROM)
print("* New UPWT container chunk injected and ROM resized by %s bytes." % hex(fs_size_change))
def change_some_text(test_id):
# Yay one more ROM open/write operation!
# Let's modify the Mission Objective text!
# NOTE: Right now this only accepts a hardcoded size (0x70 bytes) because I haven't written the support code to modify TABL if you write a longass string.
pw64_lib.build_adat_layout(PW64_ROM)
task_message_offset = pw64_lib.adat_layout[test_id + "_M"][1] # 1: Offset
with open(PW64_ROM, 'r+b') as pw64_rom:
pw64_rom.seek(task_message_offset, 0)
data = pw64_lib.encode_adat("Fly through 3 rings and...\nooh look a balloon!", 0x70)
pw64_rom.write(data)
pw64_rom.close()
if __name__== "__main__":
main()