Created
April 20, 2013 16:32
-
-
Save NichtJens/5426532 to your computer and use it in GitHub Desktop.
PEP8 compliant modification of ZeldahackScrollText6.py from:
http://kennastuff.blogspot.de/2013/03/zelda-starring-zelda-python-code.html
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/python | |
# http://kennastuff.blogspot.de/2013/03/zelda-starring-zelda-python-code.html | |
# You'll need to have a copy of Legend of Zelda named "original.nes" | |
# in the same folder as the .py file. Run the .py file | |
# and it'll spit out a file called "hack.nes". | |
ORIGINAL_SCROLL_TEXT = [ | |
"""402024_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 242420""", | |
"""602024_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 242420""", | |
"""802024_ e6e4e5_ _T_H_E_ _L_E_G_E_N_D_ _O_F_ _Z_E_L_D_A_ e5e4e5e6242420""", | |
"""a02024_ e2_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ e3242420""", | |
"""c02024_ e3_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ e2242420""", | |
"""e02024_ e2_ _M_A_N_Y_ _ _Y_E_A_R_S_ _ _A_G_O_ _ _P_R_I_N_C_E_ e3242421""", | |
"""002024_ e3_ _ _ _ _ _ _ _ _ _ #"_ _ _ _ _ _ _ #"_ _ _ _ _ _ _ e2242421""", | |
"""202024_ e2_ _D_A_R_K_N_E_S_S_ _ _ _G_A_N_N_O_N_ _ _S_T_O_L_E_ e3242421""", | |
"""402024_ e3_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ e2242421""", | |
"""602024_ e2_ _O_N_E_ _O_F_ _T_H_E_ _T_R_I_F_O_R_C_E_ _W_I_T_H_ e3242421""", | |
"""802024_ e3_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ e2242421""", | |
"""a02024_ e2_ _P_O_W_E_R#._ _ _ _ _P_R_I_N_C_E_S_S_ _Z_E_L_D_A_ e3242421""", | |
"""c02024_ e3_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ e2242421""", | |
"""e02024_ e2_ _H_A_D_ _ _O_N_E_ _O_F_ _T_H_E_ _T_R_I_F_O_R_C_E_ e3242422""", | |
"""002024_ e3_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ e2242422""", | |
"""202024_ e2_ _W_I_T_H_ _W_I_S_D_O_M#._ _S_H_E_ _D_I_V_I_D_E_D_ e2242422""", | |
"""402024_ e3_ _ _ _ _ _ _ _ #"_ _ #"_ _ _ _ _ _ _ _ _ _ _ _ _ _ e3242422""", | |
"""602024_ e2_ _I_T_ _I_N_T_O_ _ _8_ _U_N_I_T_S_ _T_O_ _H_I_D_E_ e3242422""", | |
"""802024_ e3_ _ _ _ _ _ _ _ _ #"_ _ _ _ _ _ _ #"_ _ _ _ _ _ _ _ e2242422""", | |
"""a02024_ e2_ _I_T_ _F_R_O_M_ _ _ _G_A_N_N_O_N_ _ _B_E_F_O_R_E_ e3242422""", | |
"""c02024_ e3_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ e2242422""", | |
"""e02024_ e2_ _S_H_E_ _W_A_S_ _C_A_P_T_U_R_E_D#._ _ _ _ _ _ _ _ e3242423""", | |
"""002024_ e3_ _ _ _ _ _ _ _ _ _ _ _ _ _ #"_ _ #"_ _ _ _ _ _ _ _ e2242423""", | |
"""202024_ e2_ _ _ _G_O_ _F_I_N_D_ _T_H_E_ _ _8_ _U_N_I_T_S_ _ _ e3242423""", | |
"""402024_ e3_ _ _ #"_ _ _ _ _ #"_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ e2242423""", | |
"""602024_ e2_ _ _ _ _ _L_I_N_K_ _ _T_O_ _S_A_V_E_ _H_E_R#._ _ _ e3242423""", | |
"""802024_ e3_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ e2242423""", | |
"""a02024_ e6e4e5e4e5e4e5e4e5e4e5e4e5e4e5e4e5e4e5e4e5e4e5e4e5e4e5e6242423""", | |
] | |
COLOR_TEXT = [ | |
"""00ffffff0b0a0a0a0a0effff00004a5a5200ffff00000000585aff23e020ff00001000""", | |
"""0000ffff00000a0a0200fffffafabaaaaaaaffffffffffffffffff2bd002ffff2bd602""", | |
"""ffffff2000202424242424242424242424242424242424242424242424242424242424""", | |
] | |
NEW_SCROLL_TEXT = [ | |
"""402024_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 242420""", | |
"""602024_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 242420""", | |
"""802024_ e6e4e5_ _ _L_E_G_E_N_D_ _O_F_ _Z_E_L_D_A_!_ _ _ e5e4e5e6242420""", | |
"""a02024_ e2_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ e3242420""", | |
"""c02024_ e3_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ e2242420""", | |
"""e02024_ e2_ _M_A_N_Y_ _ _Y_E_A_R_S_ _ _A_G_O_ _ _P_R_I_N_C_E_ e3242421""", | |
"""002024_ e3_ _ _ _ _ _ _ _ _ _ #"_ _ _ _ _ _ _ #"_ _ _ _ _ _ _ e2242421""", | |
"""202024_ e2_ _D_A_R_K_N_E_S_S_ _ _ _G_A_N_N_O_N_ _ _S_T_O_L_E_ e3242421""", | |
"""402024_ e3_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ e2242421""", | |
"""602024_ e2_ _T_H_E_ _T_R_I_F_O_R_C_E_ _O_F_ _P_O_W_E_R#._ _ _ e3242421""", | |
"""802024_ e3_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ e2242421""", | |
"""a02024_ e2_ _L_I_N_K_ _D_I_V_I_D_E_D_ _H_Y_R_U_L_E_'_S_ _ _ _ e3242421""", | |
"""c02024_ e3_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ e2242421""", | |
"""e02024_ e2_ _T_R_I_F_O_R_C_E_ _O_F_ _W_I_S_D_O_M_ _I_N_T_O_ _ e3242422""", | |
"""002024_ e3#"#"_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ e2242422""", | |
"""202024_ e2_ _8_ _P_A_R_T_S_ _A_N_D_ _H_I_D_ _T_H_E_M_ _ _ _ _ e2242422""", | |
"""402024_ e3_ _ _ _ _ #"_ _ _ _ _ _ #"_ _ _ _ _ _ _ _ _ _ _ _ _ e3242422""", | |
"""602024_ e2_ _F_R_O_M_ _G_A_N_N_O_N#._ _B_U_T_ _N_O_W_,_ _ _ _ e3242422""", | |
"""802024_ e3_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ e2242422""", | |
"""a02024_ e2_ _L_I_N_K_ _H_A_S_ _B_E_E_N_ _C_A_P_T_U_R_E_D#._ _ e3242422""", | |
"""c02024_ e3_ _ _ _ _ _ _ _ _ _ _ _ _ #"#"_ _ _ _ _ _ _ _ _ _ _ e2242422""", | |
"""e02024_ e2_ _ _ _ _F_I_N_D_ _T_H_E_ _ _8_ _P_A_R_T_S#._ _ _ _ e3242423""", | |
"""002024_ e3_ _ _ _ _ _ _ _ _ _ _ #"_ _ _ _ _ _ #"_ _ _ _ _ _ _ e2242423""", | |
"""202024_ e2_ _ _ _ _ _D_E_F_E_A_T_ _G_A_N_N_O_N#._ _ _ _ _ _ _ e3242423""", | |
"""402024_ e3_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ e2242423""", | |
"""602024_ e2_ _ _ _ _ _ _S_A_V_E_ _H_Y_R_U_L_E#._ _ _ _ _ _ _ _ e3242423""", | |
"""802024_ e3_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ e2242423""", | |
"""a02024_ e6e4e5e4e5e4e5e4e5e4e5e4e5e4e5e4e5e4e5e4e5e4e5e4e5e4e5e6242423""", | |
] | |
NEW_COLOR_TEXT = [ | |
"""00fffffF0b0a0a0a0a0effff00505a0A0200ffff5F5000000000ff23e020ff01a0a000""", | |
"""0000ffff5f5f5f5f5f5ffffffa0a0a0a0aaaffffffffffffffffff2bd002ffff2bd602""", | |
"""ffffff2000202424242424242424242424242424242424242424242424242424242424""", | |
] | |
# rom memory locations for fun and pro$it | |
SCROLL_START, SCROLL_END = 0x1a455, 0x1A829 | |
COLOR_START, COLOR_END = 0x1A830, 0x1A85D + (60) # 0x1A876 | |
SKIP_QUEST_START, SKIP_QUEST_END = 0x9EFB, 0x9EFF | |
# 1A81B - 1A85D | |
# Use of colours for the storyboard - | |
# every 2 bits sets the colour for a 2x2 block of text | |
# slots for different tunic colors | |
TUNIC_SPOTS = [0xa297, 0xa298, 0xa299] | |
# colors that | |
GREEN = 0x05 | |
BLUE = 0x11 | |
RED = 0x16 | |
PURPL = 0x05 | |
# list of characters in the order they are in the rom | |
# note how nice the programmers were: | |
# 0 is at offset 0x0, 'A' is at offset '0xa' | |
chars = """0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ ~~~,!'&."?-""" | |
# don't know why but the scrolling text | |
# has different sprites for these two guys | |
alts = {0x63: '.', 0xf8: '"'} | |
def toRomRaw(text): | |
'''convert a simply human friendly string to rom friendly data | |
''' | |
x = [chr(chars.index(d)) for d in text] | |
return "".join(x) | |
OLD_SKIP_QUEST = toRomRaw("ZELDA") | |
NEW_SKIP_QUEST = toRomRaw("KENNA") | |
#print len(NEW_SKIP_QUEST), SKIP_QUEST_END - SKIP_QUEST_START + 1 | |
assert(len(NEW_SKIP_QUEST) == SKIP_QUEST_END - SKIP_QUEST_START + 1) | |
def hex(char): | |
'''convert a single byte(char) of rom data into a 2 digit hex number | |
''' | |
return "%2.2x" % ord(char) | |
def convert(char): | |
'''convert a single byte(char) of rom data into human friendly format | |
''' | |
charn = ord(char) | |
if charn in alts: | |
return "#" + alts[charn] | |
if charn in range(0, len(chars)): | |
return "_" + chars[charn] | |
return hex(char) | |
def convertString(string, begin=0, end=0): | |
'''convert a string of rom data into human friendly foramt | |
''' | |
if begin == end: | |
converted_chars = [hex(char) for char in string] | |
else: | |
converted_chars = \ | |
[hex(char) for char in string[0:begin]] + \ | |
[convert(char) for char in string[begin:end]] + \ | |
[hex(char) for char in string[end:]] | |
return "".join(converted_chars) | |
def yieldScrollLines(scrollData, numbersOnly=False): | |
'''generate a series of human friendly strings from the passed rom data | |
''' | |
lineWidth = 35 # lines appear to be 35 characters long | |
# split the big string into separate lines | |
for i in range(0, len(scrollData), lineWidth): | |
line = scrollData[i:i + lineWidth] | |
# the beginning and ending of the lines have special meaning | |
if numbersOnly: | |
yield convertString(line) | |
else: | |
yield convertString(line, 3, -3) | |
def dumpScrollText(scrollData, numbersOnly=False): | |
'''print all of the scrollData to the screen in human friendly format | |
''' | |
for line in yieldScrollLines(scrollData, numbersOnly): | |
print '"""' + line + '""",' | |
def yieldRomChars(textLines): | |
'''generate a series of rom friendly characters | |
from the passed human friendly strings | |
''' | |
for text in textLines: | |
for t in range(0, len(text), 2): | |
one, two = text[t:t + 2] | |
value = None | |
if one == '_': | |
value = chars.index(two) | |
elif one == '#': | |
for k, v in alts.iteritems(): | |
if two == v: | |
value = k | |
break | |
assert value is not None, "value starts with # but not in alts" | |
else: | |
value = int(one + two, 16) | |
yield chr(value) | |
def textToRomBlock(textLines): | |
'''convert a bunch of text lines into rom friendly format | |
''' | |
return "".join([v for v in yieldRomChars(textLines)]) | |
def verifyConversions(scrollData, scrollText=None): | |
'''read the scroll data, convert it to text | |
and back again, make sure it matches | |
''' | |
scrollText = scrollText or yieldScrollLines(scrollData) | |
newData = textToRomBlock(scrollText) # 945 | |
for i in range(0, len(newData)): | |
old = ord(scrollData[i]) | |
new = ord(newData[i]) | |
assert old == new, "FAILED char: %d, old value: %2x, new value: %2x" % (i, old, new) | |
if __name__ == '__main__': | |
# load the 'original.nes' file | |
org = None | |
with open("original.nes", "rb") as f: | |
org = f.read() | |
newData = org | |
### SCROLL | |
# extract the scroll data | |
scrollData = org[SCROLL_START:SCROLL_END] | |
#dumpScrollText(scrollData) | |
# create the new scroll data from the new scroll text | |
newScrollData = textToRomBlock(NEW_SCROLL_TEXT) | |
# create new rom data with the new scroll data in place of the old | |
newData = newData[:SCROLL_START] + newScrollData + newData[SCROLL_END:] | |
### COLOR | |
# extract color data | |
colorData = org[COLOR_START:COLOR_END] | |
#dumpScrollText(colorData, numbersOnly=True) | |
newColorData = textToRomBlock(NEW_COLOR_TEXT) | |
newData = newData[:COLOR_START] + newColorData + newData[COLOR_END:] | |
### QUEST SKIP | |
# replace the quest skip name | |
newData = newData[:SKIP_QUEST_START] + NEW_SKIP_QUEST + newData[SKIP_QUEST_END + 1:] | |
### tunic hacking | |
spot = TUNIC_SPOTS[0] | |
newData = newData[:spot] + chr(RED) + newData[spot + 1:] | |
spot = TUNIC_SPOTS[1] | |
newData = newData[:spot] + chr(BLUE) + newData[spot + 1:] | |
spot = TUNIC_SPOTS[2] | |
newData = newData[:spot] + chr(PURPL) + newData[spot + 1:] | |
QENDS = [0xA959 + 0xD, 0xAB07 + 0xD] | |
QEND_LENGTH = 56 | |
NEW_QENDS = [ | |
"""e6_J60_ _ _ _ _Z_E_L_D_A_!_ _ _Y_O_U_'_R8e64_T_H_E_ _H_E_R_O_ _O_F_ _H_Y_R_U_L_Eec_W95a9a5adf0_6a95085_!e6_J60_X""", | |
"""575859_F_I_N_A_L_L_Y_,_P_E_A_C_E_ _R_E_T_U_R_N_S_ _T_O_ _H_Y_R_U_L_E_._T_H_I_S_ _E_N_D_S_ _T_H_E_ _S_T_O_R_Y_.ff""" | |
] | |
for i in range(2): | |
text, begin = NEW_QENDS[i], QENDS[i] | |
data = textToRomBlock([text]) | |
assert len(data) == QEND_LENGTH, "%d != %d" % (len(data), QEND_LENGTH) | |
newData = newData[:begin] + data + newData[begin + QEND_LENGTH:] | |
### VERIFY AND WRITE | |
# verify | |
assert len(newData) == len(org) | |
# write the hack file to disk | |
with open("hack.nes", "wb") as hack: | |
hack.write(newData) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment