Last active
August 8, 2024 17:09
-
-
Save einstein95/6545066905680466cdf200c4cc8ca4f0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
Converts between QD and FDS disk images | |
""" | |
import struct | |
def create_fds_header(side_count): | |
return b"FDS\x1A" + bytes([side_count & 0xFF]) + bytes(11) | |
def fds_crc(data): | |
# http://forums.nesdev.com/viewtopic.php?p=194867 | |
# Do not include any existing checksum, not even the blank checksums 00 00 or FF FF. | |
# The formula will automatically count 2 0x00 bytes without the programmer adding them manually. | |
# Also, do not include the gap terminator (0x80) in the data. | |
# If you wish to do so, change sum to 0x0000. | |
s = 0x8000 | |
for byte in data + bytes(2): | |
s |= byte << 16 | |
for _ in range(8): | |
if (s & 1): | |
s ^= 0x8408 << 1 | |
s >>= 1 | |
return s | |
def convert_to_fds(disk, null): | |
""" | |
Convert a .qd to .fds | |
""" | |
del disk[0x38:0x3A] # 01 block chksum | |
# fn = disk[0x39] # get num of files from 02 block | |
pos = 0x3A | |
del disk[pos: pos + 2] # 02 block chksum | |
try: | |
while disk[pos] == 3: # if there's any more files, like in Doki Doki Panic | |
print(f"{pos:05X}\t{disk[pos + 0x3:pos + 0xB].decode('latin_1')}") | |
filesize, = struct.unpack('<H', disk[pos + 0xD: pos + 0xF]) | |
del disk[pos + 0x10: pos + 0x12] # 03 block chksum | |
pos = pos + 0x10 + 1 + filesize | |
del disk[pos: pos + 2] # 04 block chksum | |
except IndexError: | |
pass | |
if len(disk) > 65500: | |
disk = disk[:65500] | |
else: | |
disk = disk.ljust(65500, b"\0") | |
return disk | |
def convert_to_qd(disk, null): | |
""" | |
Convert a .fds to .qd | |
""" | |
def insert_crc(start, end, null): | |
if not null: | |
crc = fds_crc(disk[start:end]) | |
disk.insert(end + 0, crc & 0xFF) | |
disk.insert(end + 1, crc >> 0x8) | |
else: | |
disk.insert(end + 0, 0) | |
disk.insert(end + 1, 0) | |
if disk[0] != 0x01: | |
return bytearray() | |
insert_crc(0, 0x38, null) # 01 block chksum | |
# fn = disk[0x3B] # get num of files from 02 block | |
pos = 0x3A | |
insert_crc(pos, pos + 2, null) # 02 block chksum | |
pos = 0x3E | |
try: | |
while disk[pos] == 3: # if there's any more files, like in Doki Doki Panic | |
print(disk[pos + 0x3: pos + 0xB].decode('latin_1')) | |
filesize, = struct.unpack('<H', disk[pos + 0xD: pos + 0xF]) | |
insert_crc(pos, pos + 0x10, null) # 03 block chksum | |
pos += 0x10 + 2 | |
insert_crc(pos, pos + 1 + filesize, null) # 04 block chksum | |
pos += 1 + filesize + 2 | |
except IndexError: | |
pass | |
if len(disk) > 0x10000: | |
disk = disk[:0x10000] | |
else: | |
disk = disk.ljust(0x10000, b"\0") | |
return disk | |
if __name__ == '__main__': | |
import sys | |
import argparse | |
from pathlib import Path | |
parser = argparse.ArgumentParser(description='Converts between QD and FDS disk images') | |
parser.add_argument('rom', metavar='ROM', help='QD or FDS file') | |
parser.add_argument('-o', '--output') | |
parser.add_argument('-f', '--fds-header', action='store_true', help='add FDS header') | |
parser.add_argument('-n', '--null-checksum', action='store_true', help='use null bytes instead of calculated checksum (like in some VC games)') | |
args = parser.parse_args() | |
path = Path(args.rom) | |
try: | |
size = path.stat().st_size | |
except FileNotFoundError as err: | |
print("File Not Found: {0}".format(err)) | |
sys.exit(1) | |
if not path.is_file(): | |
print("Error: It's not file: '{0}'".format(args.rom)) | |
sys.exit(1) | |
side_size = 0 | |
start_pos = 0 | |
convert_func = None | |
ext = '' | |
if size % 0x10000 == 0: | |
side_size = 0x10000 | |
convert_func = convert_to_fds | |
ext = '.fds' | |
elif size % 65500 == 0 or size % 65500 == 16: | |
side_size = 65500 | |
start_pos = size % 65500 | |
convert_func = convert_to_qd | |
ext = '.qd' | |
if convert_func: | |
in_bytes = bytearray(path.read_bytes()) | |
out_bytes = bytearray() | |
if ext == '.fds' and args.fds_header: | |
out_bytes += create_fds_header(size // side_size) | |
for i in range(start_pos, size, side_size): | |
out_bytes += convert_func(in_bytes[i: i + side_size], null=args.null_checksum) | |
path = path.with_suffix(ext) | |
if args.output: | |
path = Path(args.output) | |
path.write_bytes(out_bytes) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for the quick reply!
Interesting. That SHA1 hash does in fact match the Lost Levels QD rom I have. It would make sense then that my Bubble Bobble QD wouldn't match the Wii U QD dump, since my FDS rom matches this one:
https://datomatic.no-intro.org/?page=show_record&s=31&n=0208