wipeout icon indicating copy to clipboard operation
wipeout copied to clipboard

Support for WipEout 64 tracks.

Open tigrouind opened this issue 1 year ago • 6 comments

@aybe has been able to extract track data files from WipEout 64 rom. This add support to load those tracks in the viewer.

Wipeout64

tigrouind avatar Oct 13 '24 21:10 tigrouind

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

max-zilla avatar Feb 15 '25 01:02 max-zilla

image

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

image

good luck!

aybe avatar Feb 15 '25 06:02 aybe

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

image

max-zilla avatar Feb 15 '25 12:02 max-zilla

nice!

aybe avatar Feb 16 '25 09:02 aybe

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.

cshonegger avatar Mar 03 '25 23:03 cshonegger

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

aybe avatar Mar 04 '25 00:03 aybe