Task 762: .V64 File Format

Task 762: .V64 File Format

File Format Specifications for the .V64 File Format

The .V64 file format is a specific representation of Nintendo 64 (N64) ROM images, originating from dumps created using the Doctor V64 backup device. It is characterized by a byte-swapped order, where adjacent bytes are swapped in pairs compared to the native big-endian format (commonly associated with .Z64 files). This results in a "BADC" byte order, as opposed to the native "ABCD" order. The format consists of a 64-byte header followed by the IPL3 boot code (4032 bytes) and the main ROM data. To process a .V64 file, the byte order must be corrected by swapping every pair of bytes to convert it to the native big-endian format for accurate decoding. The header fields are then interpreted in big-endian byte order.

List of All Properties Intrinsic to This File Format

The properties refer to the structural fields within the standard N64 ROM header (offsets 0x00 to 0x3F), which are consistent across formats but require byte-swapping for .V64 files. These fields define the ROM's configuration, identification, and integrity. The list includes:

  • Reserved (offset 0x00, size 1 byte): Unused byte, typically set to 0x80 in commercial ROMs.
  • PI BSD DOM1 Configuration Flags (offset 0x01, size 3 bytes): Configures ROM access parameters, including release timing (bits 4-5 of byte 0x01), page size (bits 0-3 of byte 0x01), pulse width (byte 0x02), and latency (byte 0x03). Common values are 0x37, 0x12, 0x40 after byte-swapping.
  • Clock Rate (offset 0x04, size 4 bytes): Value used for timing calculations, typically 0x0000000F.
  • Boot Address (offset 0x08, size 4 bytes): Initial program counter address in RDRAM, commonly 0x80000400 (or adjusted for certain CIC versions).
  • Libultra Version (offset 0x0C, size 4 bytes): Indicates the SDK version, with bytes representing reserved/patch, major/minor (decimal), and revision (ASCII).
  • Check Code (offset 0x10, size 8 bytes): 64-bit integrity check value computed over the first 1 MiB of ROM data starting at offset 0x1000.
  • Reserved (offset 0x18, size 8 bytes): Unused by standard Nintendo ROMs, occasionally used by third-party developers.
  • Game Title (offset 0x20, size 20 bytes): ASCII or JIS X 0201 string representing the game name, padded with spaces (0x20).
  • Reserved (offset 0x34, size 7 bytes): Unused field.
  • Game Code (offset 0x3B, size 4 bytes): 4-character ASCII code specifying category, unique identifier, and destination/region.
  • ROM Version (offset 0x3F, size 1 byte): Version number, starting at 0 for the initial release.

Beyond the header, the format includes IPL3 boot code (offset 0x40, size 0xFC0 bytes) and the main ROM contents.

Two Direct Download Links for Files of Format .V64

Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .V64 File Dumping

The following is a self-contained HTML page with embedded JavaScript that can be embedded in a blog post (e.g., Ghost CMS). It allows users to drag and drop a .V64 file, performs byte-swapping to convert to native format, parses the header, and displays all properties on the screen.

.V64 File Property Dumper
Drag and drop a .V64 file here

Python Class for .V64 File Handling

The following Python class opens a .V64 file, decodes by byte-swapping, reads and prints the properties, and supports writing modified properties back to a new .V64 file.

import struct
import sys

class V64Handler:
    def __init__(self, filename):
        self.filename = filename
        self.data = None
        self.swapped_data = None
        self.properties = {}

    def read(self):
        with open(self.filename, 'rb') as f:
            self.data = bytearray(f.read())
        # Byte-swap to native
        for i in range(0, len(self.data), 2):
            self.data[i], self.data[i+1] = self.data[i+1], self.data[i]
        self.swapped_data = self.data
        self._parse_properties()

    def _parse_properties(self):
        self.properties = {
            'Reserved_0x00': hex(self.swapped_data[0]),
            'PI_BSD_DOM1_Flags_0x01': hex(struct.unpack('>I', b'\x00' + self.swapped_data[1:4])[0]),
            'Clock_Rate_0x04': hex(struct.unpack('>I', self.swapped_data[4:8])[0]),
            'Boot_Address_0x08': hex(struct.unpack('>I', self.swapped_data[8:12])[0]),
            'Libultra_Version_0x0C': hex(struct.unpack('>I', self.swapped_data[12:16])[0]),
            'Check_Code_0x10': hex(struct.unpack('>Q', self.swapped_data[16:24])[0]),
            'Reserved_0x18': hex(struct.unpack('>Q', self.swapped_data[24:32])[0]),
            'Game_Title_0x20': self.swapped_data[32:52].decode('utf-8', errors='ignore').strip(),
            'Reserved_0x34': hex(struct.unpack('>Q', self.swapped_data[52:59] + b'\x00')[0] >> 8),
            'Game_Code_0x3B': self.swapped_data[59:63].decode('ascii'),
            'ROM_Version_0x3F': hex(self.swapped_data[63])
        }

    def print_properties(self):
        for key, value in self.properties.items():
            print(f"{key}: {value}")

    def write(self, output_filename):
        # For demonstration, write the original swapped back
        output_data = bytearray(self.swapped_data)
        for i in range(0, len(output_data), 2):
            output_data[i], output_data[i+1] = output_data[i+1], output_data[i]
        with open(output_filename, 'wb') as f:
            f.write(output_data)

# Example usage
if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("Usage: python script.py input.v64")
        sys.exit(1)
    handler = V64Handler(sys.argv[1])
    handler.read()
    handler.print_properties()
    handler.write('output.v64')

Java Class for .V64 File Handling

The following Java class opens a .V64 file, decodes by byte-swapping, reads and prints the properties, and supports writing back to a new .V64 file.

import java.io.*;
import java.nio.*;
import java.util.*;

public class V64Handler {
    private String filename;
    private byte[] data;
    private byte[] swappedData;
    private Map<String, String> properties = new HashMap<>();

    public V64Handler(String filename) {
        this.filename = filename;
    }

    public void read() throws IOException {
        File file = new File(filename);
        data = new byte[(int) file.length()];
        try (FileInputStream fis = new FileInputStream(file)) {
            fis.read(data);
        }
        // Byte-swap to native
        for (int i = 0; i < data.length; i += 2) {
            byte temp = data[i];
            data[i] = data[i + 1];
            data[i + 1] = temp;
        }
        swappedData = data;
        parseProperties();
    }

    private void parseProperties() {
        ByteBuffer bb = ByteBuffer.wrap(swappedData).order(ByteOrder.BIG_ENDIAN);
        properties.put("Reserved_0x00", "0x" + Integer.toHexString(swappedData[0] & 0xFF));
        properties.put("PI_BSD_DOM1_Flags_0x01", "0x" + Integer.toHexString(bb.getInt(0) & 0x00FFFFFF));
        properties.put("Clock_Rate_0x04", "0x" + Integer.toHexString(bb.getInt(4)));
        properties.put("Boot_Address_0x08", "0x" + Integer.toHexString(bb.getInt(8)));
        properties.put("Libultra_Version_0x0C", "0x" + Integer.toHexString(bb.getInt(12)));
        properties.put("Check_Code_0x10", "0x" + Long.toHexString(bb.getLong(16)));
        properties.put("Reserved_0x18", "0x" + Long.toHexString(bb.getLong(24)));
        String title = new String(Arrays.copyOfRange(swappedData, 32, 52)).trim();
        properties.put("Game_Title_0x20", title);
        properties.put("Reserved_0x34", "0x" + Long.toHexString(bb.getLong(52) >>> 8));
        String code = new String(Arrays.copyOfRange(swappedData, 59, 63));
        properties.put("Game_Code_0x3B", code);
        properties.put("ROM_Version_0x3F", "0x" + Integer.toHexString(swappedData[63] & 0xFF));
    }

    public void printProperties() {
        for (Map.Entry<String, String> entry : properties.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }

    public void write(String outputFilename) throws IOException {
        byte[] outputData = Arrays.copyOf(swappedData, swappedData.length);
        for (int i = 0; i < outputData.length; i += 2) {
            byte temp = outputData[i];
            outputData[i] = outputData[i + 1];
            outputData[i + 1] = temp;
        }
        try (FileOutputStream fos = new FileOutputStream(outputFilename)) {
            fos.write(outputData);
        }
    }

    public static void main(String[] args) throws IOException {
        if (args.length < 1) {
            System.out.println("Usage: java V64Handler input.v64");
            return;
        }
        V64Handler handler = new V64Handler(args[0]);
        handler.read();
        handler.printProperties();
        handler.write("output.v64");
    }
}

JavaScript Class for .V64 File Handling

The following JavaScript class opens a .V64 file (using Node.js fs module), decodes by byte-swapping, reads and prints the properties to console, and supports writing back to a new .V64 file.

const fs = require('fs');

class V64Handler {
    constructor(filename) {
        this.filename = filename;
        this.data = null;
        this.swappedData = null;
        this.properties = {};
    }

    read() {
        this.data = fs.readFileSync(this.filename);
        // Byte-swap to native
        for (let i = 0; i < this.data.length; i += 2) {
            const temp = this.data[i];
            this.data[i] = this.data[i + 1];
            this.data[i + 1] = temp;
        }
        this.swappedData = this.data;
        this.parseProperties();
    }

    parseProperties() {
        const dv = new DataView(this.swappedData.buffer);
        this.properties = {
            'Reserved_0x00': `0x${this.swappedData[0].toString(16).padStart(2, '0')}`,
            'PI_BSD_DOM1_Flags_0x01': `0x${(dv.getUint32(0) & 0x00FFFFFF).toString(16).padStart(6, '0')}`,
            'Clock_Rate_0x04': `0x${dv.getUint32(4).toString(16).padStart(8, '0')}`,
            'Boot_Address_0x08': `0x${dv.getUint32(8).toString(16).padStart(8, '0')}`,
            'Libultra_Version_0x0C': `0x${dv.getUint32(12).toString(16).padStart(8, '0')}`,
            'Check_Code_0x10': `0x${dv.getBigUint64(16).toString(16).padStart(16, '0')}`,
            'Reserved_0x18': `0x${dv.getBigUint64(24).toString(16).padStart(16, '0')}`,
            'Game_Title_0x20': new TextDecoder('utf-8').decode(this.swappedData.slice(32, 52)).trim(),
            'Reserved_0x34': `0x${(dv.getBigUint64(52) >> 8n).toString(16)}`,
            'Game_Code_0x3B': new TextDecoder('ascii').decode(this.swappedData.slice(59, 63)),
            'ROM_Version_0x3F': `0x${this.swappedData[63].toString(16).padStart(2, '0')}`
        };
    }

    printProperties() {
        for (const [key, value] of Object.entries(this.properties)) {
            console.log(`${key}: ${value}`);
        }
    }

    write(outputFilename) {
        const outputData = Buffer.from(this.swappedData);
        for (let i = 0; i < outputData.length; i += 2) {
            const temp = outputData[i];
            outputData[i] = outputData[i + 1];
            outputData[i + 1] = temp;
        }
        fs.writeFileSync(outputFilename, outputData);
    }
}

// Example usage
if (process.argv.length < 3) {
    console.log('Usage: node script.js input.v64');
    process.exit(1);
}
const handler = new V64Handler(process.argv[2]);
handler.read();
handler.printProperties();
handler.write('output.v64');

C Class for .V64 File Handling

The following C code defines a struct-based "class" (using functions) to open a .V64 file, decode by byte-swapping, read and print the properties to console, and support writing back to a new .V64 file.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

typedef struct {
    char* filename;
    uint8_t* data;
    size_t size;
    uint8_t* swapped_data;
} V64Handler;

void v64_read(V64Handler* handler) {
    FILE* f = fopen(handler->filename, "rb");
    if (!f) {
        perror("Error opening file");
        exit(1);
    }
    fseek(f, 0, SEEK_END);
    handler->size = ftell(f);
    fseek(f, 0, SEEK_SET);
    handler->data = malloc(handler->size);
    fread(handler->data, 1, handler->size, f);
    fclose(f);
    // Byte-swap to native
    for (size_t i = 0; i < handler->size; i += 2) {
        uint8_t temp = handler->data[i];
        handler->data[i] = handler->data[i + 1];
        handler->data[i + 1] = temp;
    }
    handler->swapped_data = handler->data;
}

void v64_print_properties(V64Handler* handler) {
    printf("Reserved_0x00: 0x%02X\n", handler->swapped_data[0]);
    uint32_t flags = (handler->swapped_data[1] << 16) | (handler->swapped_data[2] << 8) | handler->swapped_data[3];
    printf("PI_BSD_DOM1_Flags_0x01: 0x%06X\n", flags);
    uint32_t clock = *(uint32_t*)(handler->swapped_data + 4);
    printf("Clock_Rate_0x04: 0x%08X\n", __builtin_bswap32(clock));
    uint32_t boot = *(uint32_t*)(handler->swapped_data + 8);
    printf("Boot_Address_0x08: 0x%08X\n", __builtin_bswap32(boot));
    uint32_t version = *(uint32_t*)(handler->swapped_data + 12);
    printf("Libultra_Version_0x0C: 0x%08X\n", __builtin_bswap32(version));
    uint64_t check = *(uint64_t*)(handler->swapped_data + 16);
    printf("Check_Code_0x10: 0x%016llX\n", __builtin_bswap64(check));
    uint64_t res1 = *(uint64_t*)(handler->swapped_data + 24);
    printf("Reserved_0x18: 0x%016llX\n", __builtin_bswap64(res1));
    char title[21];
    memcpy(title, handler->swapped_data + 32, 20);
    title[20] = '\0';
    printf("Game_Title_0x20: %s\n", title);
    uint64_t res2 = *(uint64_t*)(handler->swapped_data + 52) >> 8;
    printf("Reserved_0x34: 0x%014llX\n", __builtin_bswap64(res2 << 8));
    char code[5];
    memcpy(code, handler->swapped_data + 59, 4);
    code[4] = '\0';
    printf("Game_Code_0x3B: %s\n", code);
    printf("ROM_Version_0x3F: 0x%02X\n", handler->swapped_data[63]);
}

void v64_write(V64Handler* handler, const char* output_filename) {
    uint8_t* output_data = malloc(handler->size);
    memcpy(output_data, handler->swapped_data, handler->size);
    for (size_t i = 0; i < handler->size; i += 2) {
        uint8_t temp = output_data[i];
        output_data[i] = output_data[i + 1];
        output_data[i + 1] = temp;
    }
    FILE* f = fopen(output_filename, "wb");
    if (!f) {
        perror("Error writing file");
        exit(1);
    }
    fwrite(output_data, 1, handler->size, f);
    fclose(f);
    free(output_data);
}

void v64_free(V64Handler* handler) {
    free(handler->data);
}

int main(int argc, char** argv) {
    if (argc < 2) {
        printf("Usage: %s input.v64\n", argv[0]);
        return 1;
    }
    V64Handler handler = { .filename = argv[1] };
    v64_read(&handler);
    v64_print_properties(&handler);
    v64_write(&handler, "output.v64");
    v64_free(&handler);
    return 0;
}