From 99e0615212b566fca4ce5e9bbb06580130297594 Mon Sep 17 00:00:00 2001 From: Andreas Bielawski Date: Fri, 27 Jul 2018 00:17:53 +0200 Subject: [PATCH] Initial commit --- .gitignore | 9 +++++ LICENSE | 24 ++++++++++++ README.md | 19 ++++++++++ n64swap.py | 95 ++++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + 5 files changed, 148 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 n64swap.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7ea28aa --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# Byte-compiled / optimized / DLL files / pip / IDE +__pycache__/ +*.py[cod] +*$py.class +src/ +.idea/ +*.n64 +*.v64 +*.z64 \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fdddb29 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/README.md b/README.md new file mode 100644 index 0000000..86fa31a --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +N64Swap +======= +Swaps .n64, .v64 and .z64 roms. Written in Python 3. You must install `numpy` via e.g. pip. Code is kinda messy. + +## Usage +``` +n64swap.py [-h] input output +``` + +## Technical information +`.z64` is the correct format and should be used. This handy table shows the [difference](https://old.reddit.com/r/emulation/comments/7hrvzp/the_three_different_n64_rom_formats_explained_for/): + +| ROM format | Type | First 4 bytes | Game Title in ROM | +|------------|---------------|---------------|--------------------| +| .z64 | Big Endian | 80 37 12 40 | `SUPER MARIO 64 ` | +| .v64 | Byteswapped | 37 80 40 12 | `USEP RAMIR O64 ` | +| .n64 | Little Endian | 40 12 37 80 | `EPUSAM R OIR 46` | + +(Note that there are two spaces after both "64", they don't seem to show up here.) \ No newline at end of file diff --git a/n64swap.py b/n64swap.py new file mode 100644 index 0000000..58180cf --- /dev/null +++ b/n64swap.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +import os +from argparse import ArgumentParser + +import numpy + +valid_ext = [".n64", ".v64", ".z64"] +parser = ArgumentParser() +parser.add_argument('input', type=str, help="Input File") +parser.add_argument('output', type=str, help="Output File") +arguments = parser.parse_args() + + +def read_in_chunks(file_object, chunk_size=1024): + """Lazy function (generator) to read a file piece by piece. + Default chunk size: 1k.""" + while True: + data = file_object.read(chunk_size) + if not data: + break + yield data + + +def get_rom_format(rom_header): + if rom_header == b"\x40\x12\x37\x80": + return ".n64" + elif rom_header == b"\x80\x37\x12\x40": + return ".z64" + elif rom_header == b"\x37\x80\x40\x12": + return ".v64" + else: + return None + + +def swap_bytes(b): + chunk_bytearray = bytearray(b) + byteswapped = bytearray(len(b)) + byteswapped[0::2] = chunk_bytearray[1::2] + byteswapped[1::2] = chunk_bytearray[0::2] + return byteswapped + + +def main(infile, outfile): + # TODO: check if input == output + + output_ext = os.path.splitext(outfile)[1] + if output_ext.lower() not in valid_ext: + print("Output file must end with .n64, .v64 or .z64") + return + + with open(infile, "rb") as rom_file: + rom_header = rom_file.read(4) + rom_format = get_rom_format(rom_header) + if not rom_format: + print("Not an N64 rom.") + return + rom_file.seek(0) + + print("{0} -> {1}".format(rom_format, output_ext)) + with open(outfile, "wb") as out_file: + if rom_format == output_ext: # Just copy it + for chunk in read_in_chunks(rom_file): + out_file.write(chunk) + + if rom_format == ".z64": # Big Endian + if output_ext == ".n64": # Big Endian -> Little Endian + for chunk in read_in_chunks(rom_file): + out_file.write(numpy.frombuffer(chunk, numpy.float32).byteswap()) + elif output_ext == ".v64": # Big Endian -> Byteswapped + for chunk in read_in_chunks(rom_file): + out_file.write(swap_bytes(chunk)) + + elif rom_format == ".n64": # Little Endian + if output_ext == ".z64": # Little Endian -> Big Endian + for chunk in read_in_chunks(rom_file): + out_file.write(numpy.frombuffer(chunk, numpy.float32).byteswap()) + elif output_ext == ".v64": # Little Endian -> Big Endian -> Byteswapped + for chunk in read_in_chunks(rom_file): + endian_swapped = numpy.frombuffer(chunk, numpy.float32).byteswap() + out_file.write(swap_bytes(endian_swapped.tobytes())) + + elif rom_format == ".v64": # Byteswapped + if output_ext == ".n64": # Byteswapped -> Big Endian -> Little Endian + for chunk in read_in_chunks(rom_file): + endian_swapped = numpy.frombuffer(chunk, numpy.float32).byteswap() + out_file.write(swap_bytes(endian_swapped.tobytes())) + elif output_ext == ".z64": # Byteswapped -> Big Endian + for chunk in read_in_chunks(rom_file): + out_file.write(swap_bytes(chunk)) + + print("Conversion finished.") + + +if __name__ == "__main__": + main(infile=arguments.input, outfile=arguments.output) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..296d654 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +numpy \ No newline at end of file