Guest User

Untitled

a guest
Mar 18th, 2018
88
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 13.17 KB | None | 0 0
  1. ########################################
  2. # Utility functions
  3. ########################################
  4.  
  5. def cpu_to_rom(addr: int) -> int:
  6. return addr - 0x8000 + 0x10
  7.  
  8. def rom_to_cpu(addr: int) -> int:
  9. return addr + 0x8000 - 0x10
  10.  
  11. def text_chr(s: str) -> bytes:
  12. return bytes(map('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ -× !'.index, s))
  13.  
  14. # Return the amount of bytes written.
  15. def write(b: bytearray, index: int, length: int, data) -> int:
  16. assert len(data) == length
  17. b[index:index+length] = data
  18. return length
  19.  
  20. def write_padding(b: bytearray, index: int, length: int, data) -> int:
  21. assert len(data) <= length
  22. return write(b, index, length, data + bytes(length - len(data)))
  23.  
  24. ########################################
  25. # Color patches
  26. ########################################
  27.  
  28. # Background color addresses:
  29. water = 0x5Df
  30. day_sky = 0x5E0
  31. night_sky = 0x5E1
  32.  
  33. # Object color addresses:
  34. ow_bushes_bright = 0xCDC
  35. ow_bushes_dark = 0xCDD
  36. ow_bushes_outline = 0xCDE
  37. ow_brick_bright = 0xCE0
  38. ow_brick_dark = 0xCE1
  39.  
  40. mario_hat = 0x5E8
  41. mario_skin = 0x5E9
  42. mario_hair = 0x5EA
  43.  
  44. def patch_colors(rom: bytearray) -> None:
  45. rom[water] = 0x14
  46. rom[day_sky] = 0x2B
  47. # rom[night_sky] = navy = 0x01
  48. rom[ow_bushes_dark] = 0x15
  49. rom[ow_bushes_bright] = 0x25
  50. # rom[ow_brick_dark] = teal = 0x17
  51. # rom[ow_brick_bright] = bright_teal = 0x27
  52.  
  53. rom[mario_skin] = 0x27
  54. rom[mario_hair] = 0x01
  55.  
  56. ########################################
  57. # Music patches!
  58. # We use strings like "C-5" or "C#5" to represent notes,
  59. # strings "4.", "4", "8.", "8", "8t", "16", "16t", "32t" for note duration opcodes,
  60. # "0" for stop, "." for rest
  61. ########################################
  62.  
  63. song_table = 0x791D
  64. song_table_size = 49
  65. overworld_pattern_list = 0x792D
  66. song_headers = song_table + song_table_size
  67. music_data = 0x79C8 # Adjust as needed.
  68. music_data_size = 1352
  69.  
  70. # Speed values.
  71. bpm150 = 0x20
  72. bpm100 = 0x18
  73.  
  74. melody_to_byte = {
  75. 'q.': 0x80, 'q': 0x86, 'i.': 0x85, 'i': 0x84, 'it': 0x87, 's': 0x82, 'st': 0x83, 'z': 0x81, # (From ZZT #PLAY! ^v^)
  76. 'G-6': 0x58, 'E-6': 0x56, 'D-6': 0x02, 'C-6': 0x54, 'Bb5': 0x52, 'A#5': 0x52, 'Ab5': 0x50, 'G#5': 0x50, 'G-5': 0x4E,
  77. 'F-5': 0x4C, 'E-5': 0x44, 'D#5': 0x4A, 'D-5': 0x48, 'Db5': 0x46, 'C#5': 0x46, 'C-5': 0x64, 'B-4': 0x42, 'Bb4': 0x3E,
  78. 'A#4': 0x3E, 'A-4': 0x40, 'Ab4': 0x3C, 'G#4': 0x3C, 'G-4': 0x3A, 'Gb4': 0x38, 'F#4': 0x38, 'F-4': 0x36, 'E-4': 0x34,
  79. 'Eb4': 0x32, 'D#4': 0x32, 'D-4': 0x30, 'Db4': 0x2E, 'C#4': 0x2E, 'C-4': 0x2C, 'B-3': 0x2A, 'Bb3': 0x28, 'A#3': 0x28,
  80. 'A-3': 0x26, 'Ab3': 0x24, 'G#3': 0x24, 'G-3': 0x22, 'Gb3': 0x20, 'F#3': 0x20, 'F-3': 0x1E, 'E-3': 0x1C, 'Eb3': 0x1A,
  81. 'D#3': 0x1A, 'D-3': 0x18, 'C#3': 0x16, 'C-3': 0x14, 'B-2': 0x12, 'Bb2': 0x10, 'A#2': 0x10, 'A-2': 0x62, 'Ab2': 0x0E,
  82. 'G#2': 0x0E, 'G-2': 0x0C, 'Gb2': 0x0A, 'F#2': 0x0A, 'F-2': 0x08, 'E-2': 0x06, 'Eb2': 0x60, 'D#2': 0x60, 'D-2': 0x5E,
  83. 'C-2': 0x5C, 'G-2': 0x5A, 'x': 0x04, '0': 0x00,
  84. }
  85.  
  86. harmony_to_byte = {
  87. # Drums (Open hat, Kick, Closed hat):
  88. 'q.O': 0x30, 'qO': 0xB1, 'i.O': 0x71, 'iO': 0x31, 'itO': 0xF1, 'sO': 0xB0, 'stO': 0xF0, 'zO': 0x70,
  89. 'q.K': 0x20, 'qK': 0xA1, 'i.K': 0x61, 'iK': 0x21, 'itK': 0xE1, 'sK': 0xA0, 'stK': 0xE0, 'zK': 0x60,
  90. 'q.C': 0x10, 'qC': 0x91, 'i.C': 0x51, 'iC': 0x11, 'itC': 0xD1, 'sC': 0x90, 'stC': 0xD0, 'zC': 0x50,
  91.  
  92. # Square 1 notes:
  93. 'q.Bb4': 0x3E, 'qBb4': 0xBF, 'i.Bb4': 0x7F, 'iBb4': 0x3F, 'itBb4': 0xFF, 'sBb4': 0xBE, 'stBb4': 0xFE, 'zBb4': 0x7E,
  94. 'q.A#4': 0x3E, 'qA#4': 0xBF, 'i.A#4': 0x7F, 'iA#4': 0x3F, 'itA#4': 0xFF, 'sA#4': 0xBE, 'stA#4': 0xFE, 'zA#4': 0x7E,
  95. 'q.Ab4': 0x3C, 'qAb4': 0xBD, 'i.Ab4': 0x7D, 'iAb4': 0x3D, 'itAb4': 0xFD, 'sAb4': 0xBC, 'stAb4': 0xFC, 'zAb4': 0x7C,
  96. 'q.G#4': 0x3C, 'qG#4': 0xBD, 'i.G#4': 0x7D, 'iG#4': 0x3D, 'itG#4': 0xFD, 'sG#4': 0xBC, 'stG#4': 0xFC, 'zG#4': 0x7C,
  97. 'q.G-4': 0x3A, 'qG-4': 0xBB, 'i.G-4': 0x7B, 'iG-4': 0x3B, 'itG-4': 0xFB, 'sG-4': 0xBA, 'stG-4': 0xFA, 'zG-4': 0x7A,
  98. 'q.Gb4': 0x38, 'qGb4': 0xB9, 'i.Gb4': 0x79, 'iGb4': 0x39, 'itGb4': 0xF9, 'sGb4': 0xB8, 'stGb4': 0xF8, 'zGb4': 0x78,
  99. 'q.F#4': 0x38, 'qF#4': 0xB9, 'i.F#4': 0x79, 'iF#4': 0x39, 'itF#4': 0xF9, 'sF#4': 0xB8, 'stF#4': 0xF8, 'zF#4': 0x78,
  100. 'q.F-4': 0x36, 'qF-4': 0xB7, 'i.F-4': 0x77, 'iF-4': 0x37, 'itF-4': 0xF7, 'sF-4': 0xB6, 'stF-4': 0xF6, 'zF-4': 0x76,
  101. 'q.E-4': 0x34, 'qE-4': 0xB5, 'i.E-4': 0x75, 'iE-4': 0x35, 'itE-4': 0xF5, 'sE-4': 0xB4, 'stE-4': 0xF4, 'zE-4': 0x74,
  102. 'q.Eb4': 0x32, 'qEb4': 0xB3, 'i.Eb4': 0x73, 'iEb4': 0x33, 'itEb4': 0xF3, 'sEb4': 0xB2, 'stEb4': 0xF2, 'zEb4': 0x72,
  103. 'q.D#4': 0x32, 'qD#4': 0xB3, 'i.D#4': 0x73, 'iD#4': 0x33, 'itD#4': 0xF3, 'sD#4': 0xB2, 'stD#4': 0xF2, 'zD#4': 0x72,
  104. 'q.D-4': 0x30, 'qD-4': 0xB1, 'i.D-4': 0x71, 'iD-4': 0x31, 'itD-4': 0xF1, 'sD-4': 0xB0, 'stD-4': 0xF0, 'zD-4': 0x70,
  105. 'q.C#4': 0x2E, 'qC#4': 0xAF, 'i.C#4': 0x6F, 'iC#4': 0x2F, 'itC#4': 0xEF, 'sC#4': 0xAE, 'stC#4': 0xEE, 'zC#4': 0x6E,
  106. 'q.C-4': 0x2C, 'qC-4': 0xAD, 'i.C-4': 0x6D, 'iC-4': 0x2D, 'itC-4': 0xED, 'sC-4': 0xAC, 'stC-4': 0xEC, 'zC-4': 0x6C,
  107. 'q.B-3': 0x2A, 'qB-3': 0xAB, 'i.B-3': 0x6B, 'iB-3': 0x2B, 'itB-3': 0xEB, 'sB-3': 0xAA, 'stB-3': 0xEA, 'zB-3': 0x6A,
  108. 'q.Bb3': 0x28, 'qBb3': 0xA9, 'i.Bb3': 0x69, 'iBb3': 0x29, 'itBb3': 0xE9, 'sBb3': 0xA8, 'stBb3': 0xE8, 'zBb3': 0x68,
  109. 'q.A#3': 0x28, 'qA#3': 0xA9, 'i.A#3': 0x69, 'iA#3': 0x29, 'itA#3': 0xE9, 'sA#3': 0xA8, 'stA#3': 0xE8, 'zA#3': 0x68,
  110. 'q.A-3': 0x26, 'qA-3': 0xA7, 'i.A-3': 0x67, 'iA-3': 0x27, 'itA-3': 0xE7, 'sA-3': 0xA6, 'stA-3': 0xE6, 'zA-3': 0x66,
  111. 'q.Ab3': 0x24, 'qAb3': 0xA5, 'i.Ab3': 0x65, 'iAb3': 0x25, 'itAb3': 0xE5, 'sAb3': 0xA4, 'stAb3': 0xE4, 'zAb3': 0x64,
  112. 'q.G#3': 0x24, 'qG#3': 0xA5, 'i.G#3': 0x65, 'iG#3': 0x25, 'itG#3': 0xE5, 'sG#3': 0xA4, 'stG#3': 0xE4, 'zG#3': 0x64,
  113. 'q.G-3': 0x22, 'qG-3': 0xA3, 'i.G-3': 0x63, 'iG-3': 0x23, 'itG-3': 0xE3, 'sG-3': 0xA2, 'stG-3': 0xE2, 'zG-3': 0x62,
  114. 'q.Gb3': 0x20, 'qGb3': 0xA1, 'i.Gb3': 0x61, 'iGb3': 0x21, 'itGb3': 0xE1, 'sGb3': 0xA0, 'stGb3': 0xE0, 'zGb3': 0x60,
  115. 'q.F#3': 0x20, 'qF#3': 0xA1, 'i.F#3': 0x61, 'iF#3': 0x21, 'itF#3': 0xE1, 'sF#3': 0xA0, 'stF#3': 0xE0, 'zF#3': 0x60,
  116. 'q.F-3': 0x1E, 'qF-3': 0x9F, 'i.F-3': 0x5F, 'iF-3': 0x1F, 'itF-3': 0xDF, 'sF-3': 0x9E, 'stF-3': 0xDE, 'zF-3': 0x5E,
  117. 'q.E-3': 0x1C, 'qE-3': 0x9D, 'i.E-3': 0x5D, 'iE-3': 0x1D, 'itE-3': 0xDD, 'sE-3': 0x9C, 'stE-3': 0xDC, 'zE-3': 0x5C,
  118. 'q.Eb3': 0x1A, 'qEb3': 0x9B, 'i.Eb3': 0x5B, 'iEb3': 0x1B, 'itEb3': 0xDB, 'sEb3': 0x9A, 'stEb3': 0xDA, 'zEb3': 0x5A,
  119. 'q.D#3': 0x1A, 'qD#3': 0x9B, 'i.D#3': 0x5B, 'iD#3': 0x1B, 'itD#3': 0xDB, 'sD#3': 0x9A, 'stD#3': 0xDA, 'zD#3': 0x5A,
  120. 'q.D-3': 0x18, 'qD-3': 0x99, 'i.D-3': 0x59, 'iD-3': 0x19, 'itD-3': 0xD9, 'sD-3': 0x98, 'stD-3': 0xD8, 'zD-3': 0x58,
  121. 'q.Db3': 0x16, 'qDb3': 0x97, 'i.Db3': 0x57, 'iDb3': 0x17, 'itDb3': 0xD7, 'sDb3': 0x96, 'stDb3': 0xD6, 'zDb3': 0x56,
  122. 'q.C#3': 0x16, 'qC#3': 0x97, 'i.C#3': 0x57, 'iC#3': 0x17, 'itC#3': 0xD7, 'sC#3': 0x96, 'stC#3': 0xD6, 'zC#3': 0x56,
  123. 'q.C-3': 0x14, 'qC-3': 0x95, 'i.C-3': 0x55, 'iC-3': 0x15, 'itC-3': 0xD5, 'sC-3': 0x94, 'stC-3': 0xD4, 'zC-3': 0x54,
  124. 'q.B-2': 0x12, 'qB-2': 0x93, 'i.B-2': 0x53, 'iB-2': 0x13, 'itB-2': 0xD3, 'sB-2': 0x92, 'stB-2': 0xD2, 'zB-2': 0x52,
  125. 'q.Bb2': 0x10, 'qBb2': 0x91, 'i.Bb2': 0x51, 'iBb2': 0x11, 'itBb2': 0xD1, 'sBb2': 0x90, 'stBb2': 0xD0, 'zBb2': 0x50,
  126. 'q.A#2': 0x10, 'qA#2': 0x91, 'i.A#2': 0x51, 'iA#2': 0x11, 'itA#2': 0xD1, 'sA#2': 0x90, 'stA#2': 0xD0, 'zA#2': 0x50,
  127. 'q.Ab2': 0x0E, 'qAb2': 0x8F, 'i.Ab2': 0x4F, 'iAb2': 0x0F, 'itAb2': 0xCF, 'sAb2': 0x8E, 'stAb2': 0xCE, 'zAb2': 0x4E,
  128. 'q.G#2': 0x0E, 'qG#2': 0x8F, 'i.G#2': 0x4F, 'iG#2': 0x0F, 'itG#2': 0xCF, 'sG#2': 0x8E, 'stG#2': 0xCE, 'zG#2': 0x4E,
  129. 'q.G-2': 0x0C, 'qG-2': 0x8D, 'i.G-2': 0x4D, 'iG-2': 0x0D, 'itG-2': 0xCD, 'sG-2': 0x8C, 'stG-2': 0xCC, 'zG-2': 0x4C,
  130. 'q.Gb2': 0x0A, 'qGb2': 0x8B, 'i.Gb2': 0x4B, 'iGb2': 0x0B, 'itGb2': 0xCB, 'sGb2': 0x8A, 'stGb2': 0xCA, 'zGb2': 0x4A,
  131. 'q.F#2': 0x0A, 'qF#2': 0x8B, 'i.F#2': 0x4B, 'iF#2': 0x0B, 'itF#2': 0xCB, 'sF#2': 0x8A, 'stF#2': 0xCA, 'zF#2': 0x4A,
  132. 'q.F-2': 0x08, 'qF-2': 0x89, 'i.F-2': 0x49, 'iF-2': 0x09, 'itF-2': 0xC9, 'sF-2': 0x88, 'stF-2': 0xC8, 'zF-2': 0x48,
  133. 'q.E-2': 0x06, 'qE-2': 0x87, 'i.E-2': 0x47, 'iE-2': 0x07, 'itE-2': 0xC7, 'sE-2': 0x86, 'stE-2': 0xC6, 'zE-2': 0x46,
  134. 'q.x': 0x04, 'qx': 0x85, 'i.x': 0x45, 'ix': 0x05, 'itx': 0xC5, 'sx': 0x84, 'stx': 0xC4, 'zx': 0x44,
  135. 'q.D-6': 0x02, 'qD-6': 0x83, 'i.D-6': 0x43, 'iD-6': 0x03, 'itD-6': 0xC3, 'sD-6': 0x82, 'stD-6': 0xC2, 'zD-6': 0x42,
  136.  
  137. '0': 0x00,
  138. }
  139.  
  140. byte_to_melody = {v: k for k,v in melody_to_byte.items()}
  141. byte_to_harmony = {v: k for k,v in harmony_to_byte.items()}
  142.  
  143. def melody(phrase: str) -> bytes:
  144. return bytes(melody_to_byte[s] for s in phrase.split())
  145.  
  146. def harmony(phrase: str) -> bytes:
  147. return bytes(harmony_to_byte[s] for s in phrase.split())
  148.  
  149. bass = melody
  150. noise = harmony
  151.  
  152. class Music:
  153. def __init__(self, rom):
  154. self.rom = rom
  155. self.hp = song_headers # Header pointer
  156. self.dp = music_data # Data pointer
  157.  
  158. def write(self, index, length, data) -> int:
  159. return write(self.rom, index, length, data)
  160.  
  161. def write_data(self, buf) -> int:
  162. '''Write data, advance dp, return old dp.'''
  163. where = self.dp
  164. self.dp += self.write(where, len(buf), buf)
  165. return where
  166.  
  167. def write_header(self, buf) -> int:
  168. '''Write data, advance hp, return old hp.'''
  169. where = self.hp
  170. print(hex(where))
  171. self.hp += self.write(where, len(buf), buf)
  172. return where
  173.  
  174. def song(self, name: str, speed: int, melody: bytes, harmony: bytes, bass: bytes, noise: bytes) -> int:
  175. '''Write a song header + data. Return (header - song table), to write to the song table.'''
  176. print('=== Writing song: %r' % name)
  177.  
  178. # Write song data.
  179. noise = noise or b'\4' # Avoid crash.
  180. parts = {}
  181. m = self.write_data(melody + b'\0'); parts[melody] = m
  182. b = parts.get(bass) or self.write_data(bass); parts[bass] = b
  183. h = parts.get(harmony) or self.write_data(harmony); parts[harmony] = h
  184. n = parts.get(noise) or self.write_data(noise + b'\0'); parts[noise] = n
  185. print('(Mel, Bass, Harm, Noise) at (%04x, %04x, %04x, %04x)' % (m, b, h, n))
  186.  
  187. # Compute the header.
  188. cpu_m = rom_to_cpu(m)
  189. lo = cpu_m & 0xFF
  190. hi = cpu_m >> 8
  191. tr = b - m
  192. s1 = h - m
  193. ns = n - m
  194.  
  195. # Write header.
  196. header = bytes([speed, lo, hi, tr, s1] + ([] if ns is None else [ns]))
  197. addr = self.write_header(header)
  198. print('Header:', ' '.join('%02x' % c for c in header), 'at', addr)
  199. print('Use as %02x in the song table.' % (addr - song_table))
  200. return addr - song_table
  201.  
  202. def clear(self) -> None:
  203. silence = self.song('Silence', 0x18, b'\4', b'\4', b'\4', b'\4')
  204. # Point everything towards silence.
  205. self.write(song_table, song_table_size, bytes([silence] * song_table_size))
  206.  
  207. # Overworld music!
  208. ow = overworld_pattern_list
  209. self.rom[ow] = \
  210. self.song('Overworld Intro', bpm150,
  211. melody('q E-4 F-4 G-4'),
  212. harmony('qC-4 qD-4 qE-4'),
  213. bass('q G-3 A-3 C-4'),
  214. noise('qK qK qO'))
  215.  
  216. self.rom[ow+1] = self.rom[ow+2] = self.rom[ow+3] = self.rom[ow+4] = \
  217. self.song('Overworld Main', bpm150,
  218. melody('q. Bb4 i G-4 x Bb4 q A-4 G-4 F-4 q. G-4 i E-4 x F-4 q E-4 D-4 C-4'),
  219. harmony('q.G-4 q.E-4 qF-4 qE-4 qD-4 q.C-4 q.G-3 q.Bb3 q.G-3'),
  220. bass('q. Bb3 F-4 A-3 E-4 G-3 C-4 i Bb3 C-4 Bb3 A-3 Bb3 C-4'),
  221. noise('qK iC qO iC iK iK iO iK qC'))
  222. print(self.rom[ow])
  223.  
  224.  
  225. def patch(self):
  226. self.clear()
  227.  
  228. # def patch_overworld_music(rom: bytearray) -> None:
  229. # intro_pattern = rom[overworld_pattern_list + 0]
  230. # intro_header = song_table + intro_pattern
  231. # sp, lo, hi, tr, s1, ns = rom[intro_header : intro_header+6]
  232. # # print([hex(x) for x in [sp,lo,hi,tr,s1,ns]])
  233. #
  234. # ima = intro_melody_address = cpu_to_rom(256*hi + lo)
  235. # iba = intro_bass_address = ima + tr
  236. # iha = intro_harmony_address = ima + s1
  237. # ina = intro_noise_address = ima + ns
  238. # print(ima, iba, iha, ina)
  239. #
  240. # i = ima = intro_melody_address
  241. # while rom[i] != 0: print(byte_to_melody[rom[i]], end=' '); i += 1
  242. # print()
  243. #
  244. # i = iba
  245. # while i != ina: print(byte_to_melody[rom[i]], end=' '); i += 1
  246. # print()
  247. #
  248. # i = iha
  249. # while i != iba: print(byte_to_harmony[rom[i]], end=' '); i += 1
  250. # print()
  251. #
  252. # write(rom, ima, 12, melody('s F-4 i E-4 D-4 s C-4 i A-3 q A-4 G-4'))
  253. # write(rom, iha, 1, harmony('sF-3'))
  254. # write(rom, ina, 3, harmony('qK qC qO'))
  255.  
  256. def patch_music(rom: bytearray) -> None:
  257. Music(rom).patch()
  258.  
  259. ########################################
  260. # Main
  261. ########################################
  262.  
  263. def patch(rom: bytearray) -> None:
  264. patch_colors(rom)
  265. patch_music(rom)
  266. # Credits (14 bytes):
  267. # rom[0x09FB5 : 0x09FC3] = text_chr('LYNN VERSION'.center(14))
  268.  
  269. def main(output_path: str, input_path: str='Super Mario Bros. (JU) (PRG0) [!].nes') -> None:
  270. with open(input_path, 'rb') as f:
  271. rom = bytearray(f.read())
  272. assert bytes(rom[0:3]) == b'NES'
  273. patch(rom)
  274. with open(output_path, 'wb') as f:
  275. f.write(rom)
  276.  
  277. if __name__ == '__main__':
  278. main('lynnsmb.nes')
Add Comment
Please, Sign In to add comment