Skip to content

Conversation

@tigrouind
Copy link
Contributor

@tigrouind tigrouind commented Oct 13, 2024

@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 tigrouind force-pushed the wipeout64 branch 2 times, most recently from b814f22 to 2ec3ccc Compare October 16, 2024 19:03
@max-zilla
Copy link

max-zilla commented Feb 15, 2025

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

@aybe
Copy link

aybe commented Feb 15, 2025

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!

@max-zilla
Copy link

max-zilla commented Feb 15, 2025

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

@aybe
Copy link

aybe commented Feb 16, 2025

nice!

@cshonegger
Copy link

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.

@aybe
Copy link

aybe commented Mar 4, 2025

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants