Support for WipEout 64 tracks.
@aybe has been able to extract track data files from WipEout 64 rom. This add support to load those tracks in the viewer.
@tigrouind would love to try this out, can you say more about extracting tracks from the n64 rom? I found where @aybe provided offset addresses for the track WADs, but not sure how much data to read from those locations to extract the WADs or if there's a common tool I should be using. thanks!
header:
u16 entries;
entry:
char name[16]; // null-terminated
u32 size1, size2; // compressed/uncompressed size (WADs aren't)
char padding;
*mul.prm // multi-player scene, low-res models
*sin.prm // single-player scene, hi-res models
*com.prm // common scene objects, always used
library.ttf // useless, library.cmp has only one 64x64 texture
sky.cmp // stitch yourself, notice green pixel
good luck!
thanks so much for taking the time to reply! I got it working with that header spec!
for anyone else wanting to test this, you can use this python script on the correct romfile to generate the WIPEOUT64 folder and track structure, then put it in this repo and it just works! great work everyone!
import os
import sys
import hashlib
def check_sha1(file_path, expected_sha1="54e4795aba4fc326f79d0703ffdb51a212dd4b5c"):
sha1 = hashlib.sha1()
with open(file_path, "rb") as f:
while chunk := f.read(8192):
sha1.update(chunk)
file_hash = sha1.hexdigest()
return file_hash == expected_sha1
def extract_entries(file_path, start_address, out_folder):
with open(file_path, "rb") as f:
f.seek(start_address)
entries_count = struct.unpack("<H", f.read(2))[0]
# get entries from headers
entry_list = []
for _ in range(entries_count):
raw_name = f.read(16)
name = raw_name.split(b"\x00", 1)[0].decode("utf-8")
sizeCmp, sizeUncmp = struct.unpack("<II", f.read(8))
padding = f.read(1)
entry_list.append({"name": name, "size": sizeUncmp})
# extract contents next
for entry in entry_list:
name = entry["name"]
sizeUncmp = entry["size"]
print(f"Extracting: {name} (size: {sizeUncmp})")
file_data = f.read(sizeUncmp)
output_path = os.path.join(out_folder, name)
with open(output_path, "wb") as out_file:
out_file.write(file_data)
print("Extraction complete.")
rom = "Wipeout 64 (USA).z64" # ROM (little-endian dump)
if check_sha1(rom):
trackno = 1
for offset in [4152976, 4515360, 4976240, 5548976, 6007760, 6506736, 6945968]:
track_folder = os.path.join("WIPEOUT64", f"TRACK{trackno:02}")
os.makedirs(track_folder, exist_ok=True)
extract_entries(file_path, offset, track_folder)
trackno += 1
nice!
For anyone using @max-zilla's Python, I had to make a few changes to get it to work on my end, but it works:
- The second to last line I modified to: extract_entries(rom, offset, track_folder)
- The script requires import struct at the start
And your Wipeout 64 ROM needs to be big-endian, not little-endian. You can download Tool64 to verify and convert an existing file.
I am also not sure how to handle the skyboxes - I see @aybe has ripped and converted PNGs from them, but I'm not sure where to start with stitching them together. So for now your skyboxes will look strange.
These are the notes I wrote a while ago:
24 textured polygons
18 textures 56*36
607.4 295.6 607.4
textures
1 3 5 7 9 11 | 13 15 17 (default?)
0 2 4 6 8 10 | 12 14 16
polygons
(5.0, 3.0), (5.0, 121.0), (122.0, 3.0), (122.0, 121.0)
(5.0, 3.0), (5.0, 121.0), (122.0, 3.0), (122.0, 121.0)
(3.0, 3.0), (3.0, 121.0), (121.0, 3.0), (121.0, 121.0)
(5.0, 3.0), (5.0, 121.0), (122.0, 3.0), (122.0, 121.0)
(5.0, 4.0), (5.0, 121.0), (123.0, 4.0), (123.0, 121.0)
(5.0, 3.0), (5.0, 121.0), (122.0, 3.0), (122.0, 121.0)
(4.0, 4.0), (121.0, 4.0), (4.0, 122.0), (121.0, 122.0)
(2.0, 3.0), (119.0, 3.0), (2.0, 121.0), (119.0, 121.0)
(3.0, 4.0), (120.0, 4.0), (3.0, 122.0), (120.0, 122.0)
(3.0, 4.0), (120.0, 4.0), (3.0, 122.0), (120.0, 122.0)
(2.0, 4.0), (119.0, 4.0), (2.0, 121.0), (119.0, 121.0)
(3.0, 4.0), (120.0, 4.0), (3.0, 121.0), (120.0, 121.0)
(3.0, 4.0), (120.0, 4.0), (3.0, 122.0), (120.0, 122.0)
(3.0, 5.0), (121.0, 5.0), (3.0, 123.0), (121.0, 123.0)
(1.0, 4.0), (119.0, 4.0), (1.0, 122.0), (119.0, 122.0)
(1.0, 3.0), (119.0, 3.0), (1.0, 121.0), (119.0, 121.0)
(5.0, 4.0), (5.0, 122.0), (122.0, 4.0), (122.0, 122.0)
(5.0, 3.0), (5.0, 121.0), (122.0, 3.0), (122.0, 121.0)
(4.0, 3.0), (4.0, 120.0), (121.0, 3.0), (121.0, 120.0)
(5.0, 4.0), (5.0, 122.0), (122.0, 4.0), (122.0, 122.0)
(3.0, 4.0), (120.0, 4.0), (3.0, 122.0), (120.0, 122.0)
(4.0, 3.0), (4.0, 120.0), (121.0, 3.0), (121.0, 120.0)
(5.0, 4.0), (5.0, 122.0), (122.0, 4.0), (122.0, 122.0)
(3.0, 4.0), (120.0, 4.0), (3.0, 122.0), (120.0, 122.0)
triangles
back left front right (from inside)
1 9 35 18 20 22 42 36 4 47 41 25
0 8 34 19 21 23 43 37 5 46 40 24
3 11 33 12 14 16 44 38 6 27 29 31
2 10 32 13 15 17 45 39 7 26 28 30
12
03
large
small
UVs are somewhat incorrect in models, maybe you can try figure out the logic I didn't understand...