Skip to content

Instantly share code, notes, and snippets.

@CookiePLMonster
Created January 21, 2022 20:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CookiePLMonster/9847b4427741b538c8a9ae67f2634667 to your computer and use it in GitHub Desktop.
Save CookiePLMonster/9847b4427741b538c8a9ae67f2634667 to your computer and use it in GitHub Desktop.
Unpacker for NES ROMs included in Memorial Series games
import sys
import os
if len(sys.argv) < 2:
exit()
# Dump all files specified in the command line
for arg in sys.argv[1:]:
decrypted = []
GAME_HEADER_SIZE = 0x18 # Header specific to the Memorial Series
with open(arg, 'rb') as f:
f.seek(0x800)
def toSigned16(n):
n = n & 0xffff
return (n ^ 0x8000) - 0x8000
def readByte(file):
return int.from_bytes(file.read(1), 'little')
try:
while True: # Read until EOF, or until control termination
control = readByte(f)
typeMaybe = control >> 6
if typeMaybe == 1:
count = (control & 0x3F) + 1
byte = readByte(f)
while count != -1:
decrypted.append(byte)
count -= 1
elif typeMaybe == 2:
count = (control & 0x3F) + 2
offsetHigh = readByte(f)
offsetLow = readByte(f)
offset = toSigned16((offsetHigh << 8) | offsetLow)
index = count + offset + 1
while count != -1:
decrypted.append(decrypted[-index])
count -= 1
elif typeMaybe == 3:
break # Control termination
elif typeMaybe == 0:
count = control & 0x3F
while count != -1:
decrypted.append(readByte(f))
count -= 1
except EOFError:
pass
# Verify header, then strip it
if ''.join([chr(i) for i in decrypted[:4]]) == 'CODE':
decrypted = decrypted[GAME_HEADER_SIZE:]
filename = os.path.splitext(os.path.basename(arg))[0]
with open(filename + '.bin', 'wb') as f:
f.write(bytes(decrypted))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment