├── snes2asm ├── gui │ ├── __init__.py │ ├── application.py │ └── widget.py ├── template │ ├── __init__.py │ ├── linkfile │ ├── main.s │ ├── Makefile │ ├── hdr.asm │ └── snes.asm ├── test │ ├── __init__.py │ ├── compression │ │ ├── __init__.py │ │ ├── test_aplib.py │ │ ├── test_rle2.py │ │ ├── test_byte_rle.py │ │ ├── test_lz1.py │ │ ├── test_lz2.py │ │ ├── test_lz4.py │ │ ├── test_lz5.py │ │ ├── test_lz3.py │ │ ├── test_lz19.py │ │ └── test_rle1.py │ ├── test.bmp │ ├── classickong.smc │ ├── test_ordered_dict_range.py │ ├── test_rangetree_test.py │ ├── test_bitmap.py │ ├── test_project.py │ ├── test_tile.py │ ├── test_cartridge.py │ ├── test_brr.py │ └── classickong.yaml ├── compression │ ├── rle2.py │ ├── lz1.py │ ├── lz2.py │ ├── __init__.py │ ├── byte_rle.py │ ├── lz19.py │ ├── rle1.py │ ├── lz5.py │ ├── lz3.py │ ├── lz4.py │ ├── lz.py │ └── aplib.py ├── rangetree.py ├── project_maker.py ├── configurator.py ├── bitmap.py ├── cartridge.py ├── brr.py ├── __init__.py ├── tile.py └── decoder.py ├── .gitignore ├── bin ├── snes2asm ├── tile2bin ├── bmp2chr ├── brr ├── packer └── snes2asm_gui ├── tile2bin ├── tests │ └── application_test.py ├── __init__.py ├── application.py └── widget.py ├── setup.py ├── README.md └── LICENSE /snes2asm/gui/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /snes2asm/template/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /snes2asm/template/linkfile: -------------------------------------------------------------------------------- 1 | [objects] 2 | game.obj 3 | -------------------------------------------------------------------------------- /snes2asm/test/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /snes2asm/test/compression/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /snes2asm/test/test.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathancassano/snes2asm/HEAD/snes2asm/test/test.bmp -------------------------------------------------------------------------------- /snes2asm/test/classickong.smc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathancassano/snes2asm/HEAD/snes2asm/test/classickong.smc -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.py[cod] 3 | *$py.class 4 | .Python 5 | *.swp 6 | .DS_Store 7 | snes2asm.egg-info 8 | build 9 | -------------------------------------------------------------------------------- /bin/snes2asm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | from snes2asm import main 5 | 6 | if __name__ == '__main__': 7 | main(sys.argv) 8 | -------------------------------------------------------------------------------- /bin/tile2bin: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | from tile2bin import main 5 | 6 | if __name__ == '__main__': 7 | main(sys.argv) 8 | -------------------------------------------------------------------------------- /bin/bmp2chr: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | from snes2asm import bmp2chr 5 | 6 | if __name__ == '__main__': 7 | sys.exit(bmp2chr(sys.argv)) 8 | -------------------------------------------------------------------------------- /bin/brr: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | from snes2asm import brr_cli 5 | 6 | if __name__ == '__main__': 7 | sys.exit(brr_cli(sys.argv)) 8 | -------------------------------------------------------------------------------- /bin/packer: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | from snes2asm import packer 5 | 6 | if __name__ == '__main__': 7 | sys.exit(packer(sys.argv)) 8 | -------------------------------------------------------------------------------- /bin/snes2asm_gui: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | from snes2asm import main_gui 5 | 6 | if __name__ == '__main__': 7 | main_gui(sys.argv) 8 | -------------------------------------------------------------------------------- /snes2asm/template/main.s: -------------------------------------------------------------------------------- 1 | .ifdef WLA_VERSION 2 | .if WLA_VERSION < "10.0" 3 | .fail "Invalid version of wla-dx. Please ensure you are using version 10.0 or higher" 4 | .endif 5 | .else 6 | .fail "Invalid version of wla-dx. Please ensure you are using version 10.0 or higher" 7 | .endif 8 | 9 | .include "hdr.asm" 10 | .include "snes.asm" 11 | .include "constants.asm" 12 | %banks 13 | -------------------------------------------------------------------------------- /tile2bin/tests/application_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | from tile2bin.application import App 5 | 6 | class ApplicationTest(unittest.TestCase): 7 | 8 | def setUp(self): 9 | self.app = App({}) 10 | 11 | def test_headers(self): 12 | pass 13 | #self.assertEqual(0, self.cart.make_code) 14 | 15 | if __name__ == '__main__': 16 | unittest.main() 17 | -------------------------------------------------------------------------------- /snes2asm/test/compression/test_aplib.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | 5 | from snes2asm.compression import aplib 6 | 7 | class AplibTest(unittest.TestCase): 8 | 9 | def test_compression(self): 10 | 11 | singleByte = bytearray([53]) 12 | self.assertEqual(singleByte, aplib.decompress(aplib.compress(singleByte))) 13 | stringBytes = bytearray("aaaaaaaaaaaaaacaaaaaa".encode('utf-8')) 14 | self.assertEqual(stringBytes, aplib.decompress(aplib.compress(stringBytes))) 15 | 16 | if __name__ == '__main__': 17 | unittest.main() 18 | -------------------------------------------------------------------------------- /snes2asm/test/compression/test_rle2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | 5 | from snes2asm.compression import rle2 6 | 7 | class RLE2Test(unittest.TestCase): 8 | 9 | def test_compression(self): 10 | 11 | stringBytes = bytearray("aaaaaaaaaaaaaaca1aaaaa".encode('utf-8')) 12 | self.assertEqual(stringBytes, rle2.decompress(rle2.compress(stringBytes))) 13 | 14 | stringBytes = bytearray("azazaz12222234".encode('utf-8')) 15 | self.assertEqual(stringBytes, rle2.decompress(rle2.compress(stringBytes))) 16 | 17 | if __name__ == '__main__': 18 | unittest.main() 19 | -------------------------------------------------------------------------------- /snes2asm/test/compression/test_byte_rle.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | 5 | from snes2asm.compression import byte_rle 6 | 7 | class byte_rle_Test(unittest.TestCase): 8 | 9 | def test_compression(self): 10 | 11 | singleByte = bytearray([53]) 12 | self.assertEqual(singleByte, byte_rle.decompress(byte_rle.compress(singleByte))) 13 | stringBytes = bytearray("aaaaaaaaaaccaacccaaaa6ca7c712a6b2248dc409d34b82e58876".encode('utf-8')) 14 | self.assertEqual(stringBytes, byte_rle.decompress(byte_rle.compress(stringBytes))) 15 | 16 | if __name__ == '__main__': 17 | unittest.main() 18 | -------------------------------------------------------------------------------- /snes2asm/compression/rle2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from snes2asm.compression import rle1 4 | 5 | def compress(data): 6 | data1 = bytearray([data[i] for i in range(0,len(data),2)]) 7 | data2 = bytearray([data[i] for i in range(1,len(data),2)]) 8 | out1 = rle1.compress(data1,False) 9 | out2 = rle1.compress(data2,False) 10 | return out1 + out2 11 | 12 | def decompress(data): 13 | out = bytearray() 14 | decomp = rle1.decompress(data) 15 | half = len(decomp)/2 16 | data1 = decomp[:half] 17 | data2 = decomp[half:] 18 | for i in range(0,half): 19 | out.append(data1[i]) 20 | out.append(data2[i]) 21 | return out 22 | -------------------------------------------------------------------------------- /snes2asm/gui/application.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import sys 5 | import yaml 6 | 7 | class Document: 8 | 9 | def __init__(self): 10 | self.changed = True 11 | 12 | def loadYaml(self, filename): 13 | meta_format = yaml.load(filename) 14 | 15 | # Format validation and assignment 16 | for prop, value in meta_format.items(): 17 | 18 | def title(self): 19 | return "" 20 | 21 | def filepath(self): 22 | return os.path.join(self.working_dir, self.filename) 23 | 24 | class App: 25 | def __init__(self, args): 26 | self.docs = [] 27 | 28 | def addDoc(self, doc): 29 | self.docs.append(doc) 30 | 31 | def removeDoc(self, index): 32 | self.docs.remove(self.docs[index]) 33 | 34 | -------------------------------------------------------------------------------- /tile2bin/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import sys 5 | import argparse 6 | 7 | from PyQt5.QtWidgets import QApplication 8 | from tile2bin.application import App 9 | from tile2bin.widget import Window 10 | 11 | def main(argv=None): 12 | parser = argparse.ArgumentParser( prog="tile2bin", description='Tilemap editor', epilog='') 13 | parser.add_argument('input', metavar='map.tilemap', help="input tilemap file") 14 | 15 | args = parser.parse_args(argv[1:]) 16 | 17 | if args.input: 18 | run(args) 19 | else: 20 | parser.print_help() 21 | 22 | def run(options): 23 | qapp = QApplication(sys.argv) 24 | app = App(options) 25 | window = Window(app) 26 | window.show() 27 | sys.exit(qapp.exec_()) 28 | -------------------------------------------------------------------------------- /snes2asm/compression/lz1.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from snes2asm.compression.lz import lz_compress, lz_decompress 4 | 5 | def compress(data): 6 | return lz1_compress(data).do() 7 | 8 | def decompress(data): 9 | return lz1_decompress(data).do() 10 | 11 | class lz1_compress(lz_compress): 12 | def __init__(self, data): 13 | lz_compress.__init__(self, data) 14 | self._functions = [self._rle16,self._rle8,self._increment_fill,self._repeat_le] 15 | 16 | class lz1_decompress(lz_decompress): 17 | def __init__(self, data): 18 | lz_decompress.__init__(self, data) 19 | self._functions = [self._direct_copy, self._fill_byte, self._fill_word, self._inc_fill, self._repeat_le, self._noop, self._noop, self._long_command] 20 | 21 | -------------------------------------------------------------------------------- /snes2asm/compression/lz2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from snes2asm.compression.lz import lz_compress, lz_decompress 4 | 5 | def compress(data): 6 | return lz2_compress(data).do() 7 | 8 | def decompress(data): 9 | return lz2_decompress(data).do() 10 | 11 | class lz2_compress(lz_compress): 12 | def __init__(self, data): 13 | lz_compress.__init__(self, data) 14 | self._functions = [self._rle16,self._rle8,self._increment_fill,self._repeat_be] 15 | 16 | class lz2_decompress(lz_decompress): 17 | def __init__(self, data): 18 | lz_decompress.__init__(self, data) 19 | self._functions = [self._direct_copy, self._fill_byte, self._fill_word, self._inc_fill, self._repeat_be, self._noop, self._noop, self._long_command] 20 | 21 | -------------------------------------------------------------------------------- /snes2asm/test/test_ordered_dict_range.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | from snes2asm.disassembler import OrderedDictRange 5 | 6 | class OrderedDictRangeTest(unittest.TestCase): 7 | 8 | def setUp(self): 9 | self.dict = OrderedDictRange({1: 'A', 2: 'B', 3: 'C', 4: 'D'}) 10 | 11 | 12 | def test_search(self): 13 | 14 | self.assertEqual([(1, 'A')], self.dict.item_range(1,2) ) 15 | self.assertEqual([(2, 'B'), (3, 'C')], self.dict.item_range(2,4) ) 16 | self.assertEqual([], self.dict.item_range(10,20) ) 17 | 18 | def test_sort(self): 19 | 20 | self.dict[0] = '#' 21 | self.dict.sort_keys() 22 | self.assertEqual([(1, 'A')], self.dict.item_range(1,2) ) 23 | 24 | if __name__ == '__main__': 25 | unittest.main() 26 | -------------------------------------------------------------------------------- /snes2asm/test/test_rangetree_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | from snes2asm.rangetree import RangeTree 5 | 6 | class RangeTreeTest(unittest.TestCase): 7 | 8 | def setUp(self): 9 | self.tree = RangeTree() 10 | 11 | def test_search(self): 12 | 13 | self.tree.add(0, 5, 'A') 14 | self.tree.add(20, 25, 'B') 15 | self.tree.add(10, 15, 'M') 16 | self.tree.add(30, 40, 'C') 17 | 18 | self.assertEqual('A', self.tree.find(4) ) 19 | self.assertEqual('B', self.tree.find(22) ) 20 | self.assertEqual('M', self.tree.find(10) ) 21 | self.assertEqual('C', self.tree.find(31) ) 22 | self.assertEqual(None, self.tree.find(41) ) 23 | self.assertEqual('B', self.tree.intersects(17, 26)) 24 | 25 | self.assertEqual(['A','M','B','C'], self.tree.items() ) 26 | 27 | if __name__ == '__main__': 28 | unittest.main() 29 | -------------------------------------------------------------------------------- /snes2asm/test/compression/test_lz1.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | 5 | from snes2asm.compression import lz1 6 | 7 | class lz1_Test(unittest.TestCase): 8 | 9 | def test_compression(self): 10 | 11 | singleByte = bytearray([65]) 12 | self.assertEqual(singleByte, lz1.decompress(lz1.compress(singleByte))) 13 | 14 | repeatByte = bytearray([65]*14) 15 | self.assertEqual(repeatByte, lz1.decompress(lz1.compress(repeatByte))) 16 | 17 | stringBytes = bytearray("aaaaaaaaaaccaa".encode('utf-8')) 18 | self.assertEqual(stringBytes, lz1.decompress(lz1.compress(stringBytes))) 19 | 20 | stringBytes = bytearray("aaaaaaaaaa12345cacacacaaaa6ca7c712a6b2248dc409d34b82e58876123a".encode('utf-8')) 21 | self.assertEqual(stringBytes, lz1.decompress(lz1.compress(stringBytes))) 22 | 23 | 24 | if __name__ == '__main__': 25 | unittest.main() 26 | -------------------------------------------------------------------------------- /snes2asm/test/compression/test_lz2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | 5 | from snes2asm.compression import lz2 6 | 7 | class lz2_Test(unittest.TestCase): 8 | 9 | def test_compression(self): 10 | 11 | singleByte = bytearray([65]) 12 | self.assertEqual(singleByte, lz2.decompress(lz2.compress(singleByte))) 13 | 14 | repeatByte = bytearray([65]*14) 15 | self.assertEqual(repeatByte, lz2.decompress(lz2.compress(repeatByte))) 16 | 17 | stringBytes = bytearray("aaaaaaaaaaccaa".encode('utf-8')) 18 | self.assertEqual(stringBytes, lz2.decompress(lz2.compress(stringBytes))) 19 | 20 | stringBytes = bytearray("aaaaaaaaaa12345cacacacaaaa6ca7c712a6b2248dc409d34b82e58876123a".encode('utf-8')) 21 | self.assertEqual(stringBytes, lz2.decompress(lz2.compress(stringBytes))) 22 | 23 | 24 | if __name__ == '__main__': 25 | unittest.main() 26 | -------------------------------------------------------------------------------- /snes2asm/test/compression/test_lz4.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | 5 | from snes2asm.compression import lz4 6 | 7 | class lz4_Test(unittest.TestCase): 8 | 9 | def test_compression(self): 10 | 11 | singleByte = bytearray([65]) 12 | self.assertEqual(singleByte, lz4.decompress(lz4.compress(singleByte))) 13 | 14 | repeatByte = bytearray([64]*14) 15 | self.assertEqual(repeatByte, lz4.decompress(lz4.compress(repeatByte))) 16 | 17 | invertByte = bytearray([10,12,14,16,18,20,245,243,241,239,237,0,0]) 18 | self.assertEqual(invertByte, lz4.decompress(lz4.compress(invertByte))) 19 | 20 | stringBytes = bytearray("aaaaaaaaaa12345cacacacaaaa6ca7c712a6b2248dc409d34b82e58876123a".encode('utf-8')) 21 | self.assertEqual(stringBytes, lz4.decompress(lz4.compress(stringBytes))) 22 | 23 | if __name__ == '__main__': 24 | unittest.main() 25 | -------------------------------------------------------------------------------- /snes2asm/compression/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from snes2asm.compression import aplib 4 | from snes2asm.compression import byte_rle 5 | from snes2asm.compression import rle1 6 | from snes2asm.compression import rle2 7 | from snes2asm.compression import lz1 8 | from snes2asm.compression import lz2 9 | from snes2asm.compression import lz3 10 | from snes2asm.compression import lz4 11 | from snes2asm.compression import lz5 12 | from snes2asm.compression import lz19 13 | 14 | def get_names(): 15 | import sys 16 | from inspect import getmembers, ismodule 17 | return [m[0] for m in getmembers(sys.modules[__name__], ismodule)] 18 | 19 | def get_encoding(encoding): 20 | import sys 21 | return getattr(sys.modules[__name__], encoding) 22 | 23 | def compress(encoding, data): 24 | return get_encoding(encoding).compress(data) 25 | 26 | def decompress(encoding, data): 27 | return get_encoding(encoding).decompress(data) 28 | 29 | -------------------------------------------------------------------------------- /snes2asm/template/Makefile: -------------------------------------------------------------------------------- 1 | AS=wla-65816 2 | LD=wlalink 3 | BMP2CHR=bmp2chr 4 | PACKER=packer 5 | BRR=brr 6 | ROM=game 7 | 8 | COBJ=game.obj 9 | 10 | all: graphics encodings sound $(ROM).smc 11 | echo "Done" 12 | 13 | .PHONY: graphics 14 | graphics: *2bpp.chr *3bpp.chr *4bpp.chr *8bpp.chr 15 | 16 | %2bpp.chr: %2bpp.bmp 17 | $(BMP2CHR) -b2 -o $@ $< 18 | 19 | %3bpp.chr: %3bpp.bmp 20 | $(BMP2CHR) -b3 -o $@ $< 21 | 22 | %4bpp.chr: %4bpp.bmp 23 | $(BMP2CHR) -b4 -o $@ $< 24 | 25 | %8bpp.chr: %8bpp.bmp 26 | $(BMP2CHR) -b8 -o $@ $< 27 | 28 | .PHONY: sound 29 | sound: *.brr 30 | 31 | %.brr: %.wav 32 | $(BRR) encode -o $@ $< 33 | 34 | .PHONY: encodings 35 | encodings: %encode_files 36 | 37 | %endcode_targets 38 | %.s: *.asm 39 | touch $@ 40 | 41 | $(COBJ): *.s 42 | $(AS) -x -v -o $@ $< 43 | 44 | $(ROM).smc: $(COBJ) 45 | $(LD) -d -v -S linkfile $(ROM).smc 46 | 47 | %:: 48 | @echo -n 49 | 50 | clean: 51 | rm -f $(ROM).smc $(COBJ) 52 | -------------------------------------------------------------------------------- /snes2asm/compression/byte_rle.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from itertools import groupby 4 | 5 | def compress(data): 6 | # Find first unused byte value in data 7 | tag = next((d for d in range(0,255) if d not in data)) 8 | out = bytearray([tag]) 9 | 10 | # Process grouped data runs 11 | for val, item in groupby(data): 12 | count = len(list(item)) 13 | if count > 2: 14 | out += bytearray([val,tag,count-1]) 15 | else: 16 | out += bytearray([val]*count) 17 | 18 | # Mark end of rle 19 | out += bytearray([tag,0]) 20 | return out 21 | 22 | def decompress(data): 23 | out = bytearray() 24 | tag = data[0] 25 | last = tag 26 | i = 1 27 | while i < len(data): 28 | c = data[i] 29 | if c == tag: 30 | i += 1 31 | count = data[i] 32 | # End of rle found 33 | if count == 0: 34 | break 35 | out += bytearray([last] * count) 36 | else: 37 | out.append(c) 38 | last = c 39 | i += 1 40 | return out 41 | -------------------------------------------------------------------------------- /snes2asm/test/compression/test_lz5.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | 5 | from snes2asm.compression import lz5 6 | 7 | class lz5_Test(unittest.TestCase): 8 | 9 | def test_compression(self): 10 | 11 | singleByte = bytearray([65]) 12 | self.assertEqual(singleByte, lz5.decompress(lz5.compress(singleByte))) 13 | 14 | repeatByte = bytearray([65]*14) 15 | self.assertEqual(repeatByte, lz5.decompress(lz5.compress(repeatByte))) 16 | 17 | invertByte = bytearray([10,12,14,16,18,20,245,243,241,239,237,0,0]) 18 | self.assertEqual(invertByte, lz5.decompress(lz5.compress(invertByte))) 19 | 20 | stringBytes = bytearray("aaaaaaaaaaccaa".encode('utf-8')) 21 | self.assertEqual(stringBytes, lz5.decompress(lz5.compress(stringBytes))) 22 | 23 | stringBytes = bytearray("aaaaaaaaaa12345cacacacaaaa6ca7c712a6b2248dc409d34b82e58876123a".encode('utf-8')) 24 | self.assertEqual(stringBytes, lz5.decompress(lz5.compress(stringBytes))) 25 | 26 | 27 | if __name__ == '__main__': 28 | unittest.main() 29 | -------------------------------------------------------------------------------- /snes2asm/test/compression/test_lz3.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | 5 | from snes2asm.compression import lz3 6 | 7 | class lz3_Test(unittest.TestCase): 8 | 9 | def test_compression(self): 10 | 11 | singleByte = bytearray([65]) 12 | self.assertEqual(singleByte, lz3.decompress(lz3.compress(singleByte))) 13 | 14 | repeatByte = bytearray([65]*14) 15 | self.assertEqual(repeatByte, lz3.decompress(lz3.compress(repeatByte))) 16 | 17 | reverseBitByte = bytearray([0,0,0xF,0xF,0xF,0xF,20,0xF0,0xF0,0xF0,0xF0,45]) 18 | self.assertEqual(reverseBitByte, lz3.decompress(lz3.compress(reverseBitByte))) 19 | 20 | stringBytes = bytearray("aaaaaaaaaaccaa".encode('utf-8')) 21 | self.assertEqual(stringBytes, lz3.decompress(lz3.compress(stringBytes))) 22 | 23 | stringBytes = bytearray("aaaaaaaaaa12345cacacacaaaa6ca7c712a6b2248dc409d34b82e58876123a".encode('utf-8')) 24 | self.assertEqual(stringBytes, lz3.decompress(lz3.compress(stringBytes))) 25 | 26 | if __name__ == '__main__': 27 | unittest.main() 28 | -------------------------------------------------------------------------------- /snes2asm/test/compression/test_lz19.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | 5 | from snes2asm.compression import lz19 6 | 7 | class lz19_Test(unittest.TestCase): 8 | 9 | def test_compression(self): 10 | 11 | singleByte = bytearray([65]) 12 | self.assertEqual(singleByte, lz19.decompress(lz19.compress(singleByte))) 13 | 14 | repeatByte = bytearray([65]*14) 15 | self.assertEqual(repeatByte, lz19.decompress(lz19.compress(repeatByte))) 16 | 17 | reverseBitByte = bytearray([0,0,0xF,0xF,0xF,0xF,20,0xF0,0xF0,0xF0,0xF0,45]) 18 | self.assertEqual(reverseBitByte, lz19.decompress(lz19.compress(reverseBitByte))) 19 | 20 | stringBytes = bytearray("aaaaaaaaaaccaa".encode('utf-8')) 21 | self.assertEqual(stringBytes, lz19.decompress(lz19.compress(stringBytes))) 22 | 23 | stringBytes = bytearray("aaaaaaaaaa12345cacacacaaaa6ca7c712a6b2248dc409d34b82e58876123a".encode('utf-8')) 24 | self.assertEqual(stringBytes, lz19.decompress(lz19.compress(stringBytes))) 25 | 26 | if __name__ == '__main__': 27 | unittest.main() 28 | -------------------------------------------------------------------------------- /snes2asm/test/compression/test_rle1.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | 5 | from snes2asm.compression import rle1 6 | 7 | class RLE1Test(unittest.TestCase): 8 | 9 | def test_compression(self): 10 | 11 | stringBytes = bytearray("aaaaaaaaaaaaaaca1aaaaa".encode('utf-8')) 12 | self.assertEqual(bytearray([0x80,13,97,0,2,99,97,49,0x80,4,97,0xFF,0xFF]), rle1.compress(stringBytes)) 13 | self.assertEqual(stringBytes, rle1.decompress(rle1.compress(stringBytes))) 14 | 15 | stringBytes = bytearray("aaz".encode('utf-8')) 16 | self.assertEqual(bytearray([0x80,1,97,0,0,122,0xFF,0xFF]), rle1.compress(stringBytes)) 17 | 18 | stringBytes = bytearray("azz".encode('utf-8')) 19 | self.assertEqual(bytearray([0,0,97,0x80,1,122,0xFF,0xFF]), rle1.compress(stringBytes)) 20 | 21 | stringBytes = bytearray([0xFF] * (0xFF)) 22 | self.assertEqual(bytearray([0x80,0x7f,0xFF,0x80,0x7E,0xFF,0xFF,0xFF]), rle1.compress(stringBytes)) 23 | self.assertEqual(stringBytes, rle1.decompress(rle1.compress(stringBytes))) 24 | 25 | if __name__ == '__main__': 26 | unittest.main() 27 | -------------------------------------------------------------------------------- /snes2asm/compression/lz19.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from snes2asm.compression.lz import lz_compress, lz_decompress 4 | 5 | def compress(data): 6 | return lz19_compress(data).do() 7 | 8 | def decompress(data): 9 | return lz19_decompress(data).do() 10 | 11 | class lz19_compress(lz_compress): 12 | 13 | REPEAT_BITREV = 5 14 | REPEAT_REV = 6 15 | 16 | def __init__(self, data): 17 | lz_compress.__init__(self, data) 18 | self._functions = [self._rle16,self._rle8,self._increment_fill,self._repeat_be,self._repeat_reverse,self._repeat_bit_reverse] 19 | 20 | def _repeat_bit_reverse(self): 21 | length, index = self._search_bit_reverse() 22 | return (self.REPEAT_BITREV, length, bytearray([index & 0xFF, index >> 8])) 23 | 24 | def _repeat_reverse(self): 25 | length, index = self._search_reverse() 26 | return (self.REPEAT_REV, length, bytearray([index & 0xFF, index >> 8])) 27 | 28 | class lz19_decompress(lz_decompress): 29 | def __init__(self, data): 30 | lz_decompress.__init__(self, data) 31 | self._functions = [self._direct_copy, self._fill_byte, self._fill_word, self._inc_fill, self._repeat_be, self._repeat_bit_reverse, self._repeat_reverse, self._long_command] 32 | 33 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from setuptools import setup, find_packages 3 | from distutils.core import Command 4 | from os import path 5 | 6 | here = path.abspath(path.dirname(__file__)) 7 | 8 | with open(path.join(here, 'README.md')) as f: 9 | long_description = f.read() 10 | 11 | setup( 12 | name='snes2asm', 13 | version='0.0.4', 14 | description='Disassembles SNES cartridges into practical projects', 15 | long_description=long_description, 16 | author="Nathan Cassano", 17 | license="Apache 2", 18 | packages=find_packages(exclude=['snes2asm/tests']), 19 | include_package_data=True, 20 | package_data = {'snes2asm':['template/*'], 'images':['*']}, 21 | scripts=['bin/snes2asm','bin/bmp2chr', 'bin/brr'], 22 | install_requires=['PyYAML', 'PyQt5'], 23 | classifiers=[ 24 | 'Development Status :: 3 - Alpha', 25 | 'Environment :: Console', 26 | 'Programming Language :: Assembly', 27 | 'Topic :: Games/Entertainment', 28 | 'Topic :: Software Development :: Assemblers', 29 | 'Topic :: Software Development :: Build Tools', 30 | 'Topic :: Software Development :: Embedded Systems', 31 | ], 32 | url='https://github.com/nathancassano/snes2asm', 33 | ) 34 | -------------------------------------------------------------------------------- /snes2asm/test/test_bitmap.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | import os 5 | from snes2asm.bitmap import * 6 | 7 | class TileTest(unittest.TestCase): 8 | 9 | def test_readbmp(self): 10 | b = BitmapIndex.read(os.path.join(os.path.dirname(__file__), "test.bmp")) 11 | 12 | self.assertEqual(70, b._bfOffBits) 13 | self.assertEqual(4, b._bcWidth) 14 | self.assertEqual(4, b._bcHeight) 15 | self.assertEqual(4, b._bcBitCount) 16 | self.assertEqual(4, b._bcTotalColors) 17 | self.assertEqual(8, b._paddedWidth) 18 | 19 | self.assertEqual(0, b.getPixel(0,0)) 20 | self.assertEqual(1, b.getPixel(3,3)) 21 | 22 | b.setPixel(0,3,3) 23 | self.assertEqual(3, b.getPixel(0,3)) 24 | 25 | b.setPixel(1,2,3) 26 | self.assertEqual(3, b.getPixel(1,2)) 27 | 28 | def test_createbmp(self): 29 | b = BitmapIndex(8,8,4,[0xFF00, 0, 0, 0]*4) 30 | b.setPixel(0,0,1) 31 | b.output() 32 | 33 | def test_create4bmp(self): 34 | b = BitmapIndex(128,72,4,[0, 0xFF00, 0, 0]*4) 35 | for x in range(0,128): 36 | for y in range(0, 72): 37 | if x > 24 and y > 64: 38 | pass 39 | else: 40 | b.setPixel(x,y,1) 41 | 42 | b.write("output128x64.bmp") 43 | os.unlink("output128x64.bmp") 44 | 45 | 46 | if __name__ == '__main__': 47 | unittest.main() 48 | -------------------------------------------------------------------------------- /snes2asm/test/test_project.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | import os 5 | import shutil 6 | from snes2asm import main 7 | 8 | class ProjectTest(unittest.TestCase): 9 | 10 | def test_run(self): 11 | cwd = os.getcwd() 12 | directory = os.path.dirname(__file__) 13 | path = os.path.join(directory, 'classickong.smc') 14 | conf = os.path.join(directory, 'classickong.yaml') 15 | out = os.path.join(directory, 'project') 16 | game = os.path.join(out, 'game.smc') 17 | 18 | # ./snes2asm -o project -e 0 -c classickong.yaml classickong.smc 19 | main(['snes2asm', '-o', out, '-e', 0, '-c', conf, path]) 20 | 21 | # Run make to compile ROM 22 | os.chdir(out) 23 | self.assertEqual(0, os.system('make')) 24 | os.chdir(cwd) 25 | 26 | # Compare binary files 27 | with open(path, "r") as f: 28 | expected_lines = self.hexdump(f.read()) 29 | with open(game, "r") as f: 30 | actual_lines = self.hexdump(f.read()) 31 | 32 | self.assertListEqual(expected_lines, actual_lines) 33 | 34 | # Clean up 35 | shutil.rmtree(out) 36 | 37 | def hexdump(self, data): 38 | out = [] 39 | for h in range(0, len(data), 16): 40 | out.append(('%06X ' % h) + ' '.join(['%02x' % ord(c) for c in data[h:h+16]])) 41 | return out 42 | 43 | if __name__ == '__main__': 44 | unittest.main() 45 | -------------------------------------------------------------------------------- /snes2asm/compression/rle1.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from itertools import groupby 4 | 5 | def compress(data, terminator=True): 6 | compress.out = bytearray() 7 | compress.direct = bytearray() 8 | 9 | def write_direct_data(): 10 | if len(compress.direct) > 0: 11 | # Write direct data command 12 | dlen = len(compress.direct) - 1 13 | compress.out += bytearray([dlen >> 8, dlen & 0xFF]) + compress.direct 14 | compress.direct = bytearray() 15 | 16 | # Process grouped data runs 17 | for val, item in groupby(data): 18 | count = len(list(item)) 19 | if count > 1: 20 | write_direct_data() 21 | step = 0x80 if val == 0xFF else 0x8000 22 | # Write repeating command 23 | for i in range(count,0,-step): 24 | dlen = (step if i >= step else i) - 1 25 | compress.out += bytearray([0x80 | (dlen>> 8), dlen & 0xFF, val]) 26 | else: 27 | compress.direct.append(val) 28 | 29 | write_direct_data() 30 | 31 | # Write terminator 32 | if terminator: 33 | compress.out += bytearray([0xFF, 0xFF]) 34 | 35 | return compress.out 36 | 37 | def decompress(data): 38 | out = bytearray() 39 | stream = iter(data) 40 | for d in stream: 41 | header = d << 8 | next(stream) 42 | if header == 0xFFFF: 43 | break 44 | count = (0x7FFF & header) + 1 45 | # Repeat command 46 | if d & 0x80 != 0: 47 | out += bytearray([next(stream)] * count) 48 | # Direct copy coomand 49 | else: 50 | for i in range(0,count): 51 | out.append(next(stream)) 52 | return out 53 | -------------------------------------------------------------------------------- /snes2asm/compression/lz5.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from snes2asm.compression.lz import lz_compress, lz_decompress 4 | 5 | def compress(data): 6 | return lz5_compress(data).do() 7 | 8 | def decompress(data): 9 | return lz5_decompress(data).do() 10 | 11 | class lz5_compress(lz_compress): 12 | 13 | REPEAT_INV = 5 14 | REPEAT_REL = 6 15 | 16 | def __init__(self, data): 17 | lz_compress.__init__(self, data) 18 | self._functions = [self._rle16,self._rle8,self._increment_fill,self._repeat_le,self._repeat_inverse,self._repeat_rel] 19 | 20 | def _repeat_inverse(self): 21 | length, index = self._search_inverse() 22 | return (self.REPEAT_INV, length, bytearray([index >> 8, index & 0xFF])) 23 | 24 | def _repeat_rel(self): 25 | length, index = self._search() 26 | relative_index = self._offset - index 27 | if relative_index > 255: 28 | return (self.REPEAT_REL, 0, bytearray()) 29 | else: 30 | return (self.REPEAT_REL, length, bytearray([relative_index])) 31 | 32 | class lz5_decompress(lz_decompress): 33 | def __init__(self, data): 34 | lz_decompress.__init__(self, data) 35 | self._functions = [self._direct_copy, self._fill_byte, self._fill_word, self._inc_fill, self._repeat_le, self._repeat_inverse, self._repeat_rel, self._long_command] 36 | 37 | def _repeat_inverse(self): 38 | start = (self._in[self._offset] << 8) | self._in[self._offset+1] 39 | end = start + self._length 40 | self._out += bytearray([b ^ 0xFF for b in self._out[start:end]]) 41 | self._offset += 2 42 | 43 | def _repeat_rel(self): 44 | start = len(self._out) - self._in[self._offset] 45 | self._offset += 1 46 | end = start + self._length 47 | self._out += self._out[start:end] 48 | -------------------------------------------------------------------------------- /snes2asm/test/test_tile.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | from snes2asm.tile import * 5 | from snes2asm.bitmap import * 6 | 7 | class TileTest(unittest.TestCase): 8 | 9 | def test_tile2bpp(self): 10 | snes_tile_encoded = bytearray([0x00, 0x01, 0x01, 0x02, 0x03, 0x04, 0x07, 0x08, 0x0f, 0x10, 0x1f, 0x20, 0x3f, 0x40, 0xff, 0xff]) 11 | snes_tile_decoded = Decode2bppTile(snes_tile_encoded) 12 | self.assertEqual(snes_tile_encoded, Encode2bppTile(snes_tile_decoded)) 13 | 14 | def test_tile3bpp(self): 15 | snes_tile_encoded = bytearray([0xff, 0x00, 0x3f, 0xc0, 0xfe, 0x01, 0xfe, 0x01, 0xde, 0x21, 0x3e, 0xc1, 0xfe, 0x01, 0xc0, 0x3f, 0xc1, 0x21, 0x22, 0x22, 0xc4, 0x08, 0x30, 0xc0]) 16 | snes_tile_decoded = Decode3bppTile(snes_tile_encoded) 17 | self.assertEqual(snes_tile_encoded, Encode3bppTile(snes_tile_decoded)) 18 | 19 | def test_tile4bpp(self): 20 | snes_tile_encoded = bytearray([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x38, 0x7f, 0x47, 0xcc, 0xb8, 0x04, 0x11, 0x70, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x06, 0x3c, 0x04, 0x77, 0x04, 0x8d, 0x7a, 0x0f, 0x00]) 21 | snes_tile_decoded = Decode4bppTile(snes_tile_encoded) 22 | self.assertEqual(snes_tile_encoded, Encode4bppTile(snes_tile_decoded)) 23 | 24 | def test_tile8bpp(self): 25 | snes_tile_encoded = bytearray([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x38, 0x7f, 0x47, 0xcc, 0xb8, 0x04, 0x11, 0x70, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x06, 0x3c, 0x04, 0x77, 0x04, 0x8d, 0x7a, 0x0f, 0x00]*2) 26 | snes_tile_decoded = Decode8bppTile(snes_tile_encoded) 27 | self.assertEqual(snes_tile_encoded, Encode8bppTile(snes_tile_decoded)) 28 | 29 | if __name__ == '__main__': 30 | unittest.main() 31 | -------------------------------------------------------------------------------- /snes2asm/test/test_cartridge.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | import os 5 | from snes2asm.cartridge import Cartridge 6 | 7 | class CartridgeTest(unittest.TestCase): 8 | 9 | def setUp(self): 10 | self.cart = Cartridge() 11 | self.cart.open(os.path.join(os.path.dirname(__file__), 'classickong.smc')) 12 | 13 | def test_headers(self): 14 | 15 | self.assertEqual(0, self.cart.make_code) 16 | self.assertEqual(b"SNES", self.cart.game_code) 17 | self.assertEqual(b"\x00\x00\x00\x00\x00\x00\x00", self.cart.fixed) 18 | self.assertEqual(0, self.cart.expand_ram) 19 | self.assertEqual(0, self.cart.version) 20 | self.assertEqual(0, self.cart.sub_type) 21 | 22 | self.assertEqual(b"Classic Kong Complete", self.cart.title) 23 | self.assertEqual(0x30, self.cart.map_mode) 24 | self.assertEqual(0, self.cart.cart_type) 25 | self.assertEqual(0x8, self.cart.rom_size) 26 | self.assertEqual(0, self.cart.sram_size) 27 | self.assertEqual(1, self.cart.country) 28 | self.assertEqual(0, self.cart.license_code) 29 | self.assertEqual(0xA2CB, self.cart.comp_check) 30 | self.assertEqual(0x5D34, self.cart.check_sum) 31 | 32 | # Vectors 33 | self.assertEqual(0x0, self.cart.nvec_unused) 34 | self.assertEqual(0x8000, self.cart.nvec_cop) 35 | self.assertEqual(0x8000, self.cart.nvec_brk) 36 | self.assertEqual(0x8000, self.cart.nvec_abort) 37 | self.assertEqual(0x8091, self.cart.nvec_nmi) 38 | self.assertEqual(0x0, self.cart.nvec_reset) 39 | self.assertEqual(0x8000, self.cart.nvec_irq) 40 | self.assertEqual(0x0, self.cart.evec_unused) 41 | self.assertEqual(0x8000, self.cart.evec_cop) 42 | self.assertEqual(0x0, self.cart.evec_unused2) 43 | self.assertEqual(0x8000, self.cart.evec_abort) 44 | self.assertEqual(0x8000, self.cart.evec_nmi) 45 | self.assertEqual(0x80B7, self.cart.evec_reset) 46 | self.assertEqual(0x8000, self.cart.evec_irq) 47 | 48 | if __name__ == '__main__': 49 | unittest.main() 50 | -------------------------------------------------------------------------------- /snes2asm/template/hdr.asm: -------------------------------------------------------------------------------- 1 | .MEMORYMAP 2 | SLOTSIZE $%bank_size 3 | DEFAULTSLOT 0 4 | SLOT 0 $%slot_size 5 | .ENDME 6 | 7 | .ROMBANKSIZE $%bank_size 8 | .ROMBANKS %rom_banks 9 | .EMPTYFILL $%empty_fill 10 | 11 | .SNESHEADER 12 | ID "%game_code" ; 1-4 letter string 13 | 14 | NAME "%title" ; Program Title - can't be over 21 bytes, 15 | ; "123456789012345678901" ; use spaces for unused bytes of the name. 16 | %rom_speed 17 | %rom_map 18 | 19 | CARTRIDGETYPE $%cart_type 20 | ;$00 ROM only 21 | ;$01 ROM and RAM 22 | ;$02 ROM and Save RAM 23 | ;$03 ROM and DSP1 chip 24 | ;$04 ROM, RAM and DSP1 chip 25 | ;$05 ROM, Save RAM and DSP1 chip 26 | ;$13 ROM and Super FX chip 27 | ;$13 SuperFX with no battery 28 | ;$14 SuperFX with no battery 29 | ;$15 SuperFX with save-RAM 30 | ;$1a SuperFX with save-RAM 31 | ;$34 SA-1 32 | ;$35 SA-1 33 | 34 | ROMSIZE $%rom_size 35 | ;$08 - 2 Megabits (8x32K banks) 36 | ;$09 - 4 Megabits 37 | ;$0A - 8 Megabits 38 | ;$0B - 16 Megabits 39 | ;$0C - 32 Megabits 40 | 41 | SRAMSIZE $%sram_size 42 | ;$00 - No SRAM 43 | ;$01 - 16 kilobits 44 | ;$02 - 32 kilobits 45 | ;$03 - 64 kilobits 46 | 47 | COUNTRY $%country 48 | ;$00 NTSC Japan 49 | ;$01 NTSC US 50 | ;$02..$0c PAL 51 | ;$0d NTSC 52 | ;$0e..$ff invalid 53 | 54 | LICENSEECODE $%license_code ; Just use $00 55 | VERSION $%version ; $00 = 1.00, $01 = 1.01, etc. 56 | .ENDSNES 57 | 58 | .SNESNATIVEVECTOR ; Define Native Mode interrupt vector table 59 | COP %nvec_cop 60 | BRK %nvec_brk 61 | ABORT %nvec_abort 62 | NMI %nvec_nmi 63 | IRQ %nvec_irq 64 | UNUSED %nvec_reset 65 | .ENDNATIVEVECTOR 66 | 67 | .SNESEMUVECTOR ; Define Emulation Mode interrupt vector table 68 | COP %evec_cop 69 | ABORT %evec_abort 70 | NMI %evec_nmi 71 | RESET %evec_reset ; where execution starts 72 | IRQBRK %evec_irq 73 | UNUSED %evec_unused2 74 | .ENDEMUVECTOR 75 | -------------------------------------------------------------------------------- /snes2asm/compression/lz3.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from snes2asm.compression.lz import lz_compress, lz_decompress, bit_reverse 4 | 5 | def compress(data): 6 | return lz3_compress(data).do() 7 | 8 | def decompress(data): 9 | return lz3_decompress(data).do() 10 | 11 | class lz3_compress(lz_compress): 12 | 13 | REPEAT_BITREV = 5 14 | REPEAT_REV = 6 15 | 16 | def __init__(self, data): 17 | lz_compress.__init__(self, data) 18 | self._functions = [self._rle16,self._rle8,self._zero_fill,self._repeat_rel,self._repeat_reverse,self._repeat_bit_reverse] 19 | 20 | def _repeat_rel(self): 21 | return self._repeat_func(self.REPEAT, self._search) 22 | 23 | def _repeat_bit_reverse(self): 24 | return self._repeat_func(self.REPEAT_BITREV, self._search_bit_reverse) 25 | 26 | def _repeat_reverse(self): 27 | return self._repeat_func(self.REPEAT_REV, self._search_reverse) 28 | 29 | def _repeat_func(self, command, search_func): 30 | length, index = search_func() 31 | relative_index = self._offset - index 32 | if relative_index < 128: 33 | return (command, length, bytearray([relative_index | 0x80])) 34 | else: 35 | return (command, length, bytearray([(index >> 8) & 0x7F, index & 0xFF])) 36 | 37 | class lz3_decompress(lz_decompress): 38 | def __init__(self, data): 39 | lz_decompress.__init__(self, data) 40 | self._functions = [self._direct_copy, self._fill_byte, self._fill_word, self._fill_zero, self._repeat_rel, self._repeat_bit_reverse, self._repeat_reverse, self._long_command] 41 | 42 | def _repeat_data(self): 43 | index = self._in[self._offset] 44 | if index & 0x80 != 0: 45 | start = len(self._out) - (index & 0x7F) 46 | self._offset += 1 47 | else: 48 | start = index << 8 | self._in[self._offset+1] 49 | self._offset += 2 50 | end = start + self._length 51 | return self._out[start:end] 52 | 53 | def _repeat_rel(self): 54 | self._out += self._repeat_data() 55 | 56 | def _repeat_bit_reverse(self): 57 | self._out += bytearray([bit_reverse(b) for b in self._repeat_data()]) 58 | 59 | def _repeat_reverse(self): 60 | self._out += self._repeat_data()[::-1] 61 | -------------------------------------------------------------------------------- /snes2asm/compression/lz4.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | def compress(data): 4 | out = bytearray() 5 | i = 0 6 | anchor = 0 7 | end = len(data) - 5 8 | while i < len(data): 9 | # Find the longest match 10 | match_length = 0 11 | match_offset = 0 12 | for j in range(0, i): 13 | length = 0 14 | while i + length < end and data[j + length] == data[i + length]: 15 | length += 1 16 | if length > match_length: 17 | match_length = length 18 | match_offset = j + 1 19 | 20 | literal_length = i - anchor 21 | 22 | # If the match is too short 23 | if match_length < 4: 24 | i += 1 25 | if i < len(data): 26 | continue 27 | # End of data reached 28 | literal_length = len(data) - anchor 29 | token = (min(15, literal_length) << 4) 30 | match_length = -1 31 | else: 32 | match_length -= 4 33 | token = (min(15, literal_length) << 4) | min(15, match_length) 34 | 35 | out.append(token) 36 | 37 | # Encode literals 38 | if literal_length >= 15: 39 | n = literal_length - 15 40 | while n >= 0xFF: 41 | out.append(0xFF) 42 | n -= 0xFF 43 | out.append(n) 44 | 45 | out.extend(data[anchor:anchor + literal_length]) 46 | 47 | # Encode match 48 | if match_length >= 0: 49 | out.append(match_offset & 0xFF) 50 | out.append((match_offset >> 8) & 0xFF) 51 | 52 | if match_length >= 15: 53 | n = match_length - 15 54 | while n >= 0xFF: 55 | out.append(0xFF) 56 | n -= 0xFF 57 | out.append(n) 58 | i += match_length + 4 59 | anchor = i 60 | return out 61 | 62 | def decompress(data): 63 | out = bytearray() 64 | i = 0 65 | while i < len(data): 66 | token = data[i] 67 | i += 1 68 | 69 | literal_length = token >> 4 70 | match_length = token & 0xF 71 | 72 | if literal_length > 0: 73 | if literal_length == 0xF: 74 | while True: 75 | ext_direct = data[i] 76 | i += 1 77 | literal_length += ext_direct 78 | if data[offset] != 0xFF: break 79 | # Copy literal_length 80 | end = i + literal_length 81 | out += data[i:end] 82 | i = end 83 | 84 | if i >= len(data): 85 | break 86 | 87 | offset = (data[i] | (data[i+1] << 8)) - 1 88 | i += 2 89 | 90 | if match_length == 0xF: 91 | while True: 92 | ext_length = data[i] 93 | i += 1 94 | match_length += ext_length 95 | if ext_length != 0xFF: break 96 | match_length += 4 97 | for x in range(offset, offset + match_length): 98 | out.append(out[x]) 99 | return out -------------------------------------------------------------------------------- /snes2asm/test/test_brr.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | from snes2asm import brr 5 | from io import BytesIO 6 | import wave 7 | 8 | class BRRTest(unittest.TestCase): 9 | 10 | brr_sample = bytearray([0xb0,0x00,0x01,0x12,0x22,0x33,0x34,0x44,0x44,0x8c,0x6d,0x01,0x01,0x10,0x10,0x11,0x01,0x01,0x5c,0x72,0x20,0x11,0x01,0xff,0xca,0xad,0x47,0x74,0xfe,0xde,0xdf,0xf0,0x00,0xec,0x9b,0x07,0x78,0x51,0x1f,0x23,0x01,0xf1,0x12,0x20,0x03,0x5c,0x01,0xea,0xaf,0xdd,0xd0,0x75,0x30,0x00,0x6c,0x00,0xf1,0x35,0x20,0xf3,0x53,0xee,0xec,0x5f,0xb9,0xfd,0x54,0x5f,0x1f,0x10,0x0f,0x10]) 11 | 12 | wav_good_data = bytearray([0x52,0x49,0x46,0x46,0x24,0x01,0x00,0x00,0x57,0x41,0x56,0x45,0x66,0x6d,0x74,0x20,0x10,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0x00,0x7d,0x00,0x00,0x00,0xfa,0x00,0x00,0x02,0x00,0x10,0x00,0x64,0x61,0x74,0x61,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x08,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x80,0x25,0x62,0x26,0x80,0x26,0xfe,0x26,0xc8,0x26,0xfe,0x26,0x8c,0x27,0x5e,0x27,0x9a,0x27,0x2a,0x27,0x30,0x27,0x96,0x27,0x48,0x27,0x6a,0x27,0xe6,0x26,0xdc,0x26,0x16,0x27,0xe6,0x26,0x62,0x26,0x5c,0x25,0x10,0x24,0x90,0x22,0xcc,0x20,0xf8,0x1e,0xde,0x1c,0x94,0x1a,0xca,0x17,0x64,0x14,0x8c,0x10,0xc8,0x0c,0x04,0x0a,0x7a,0x08,0x74,0x07,0xfe,0x05,0x20,0x04,0xde,0x02,0x32,0x01,0xa0,0x00,0x16,0x00,0x16,0x00,0x16,0x00,0x16,0x00,0x16,0xff,0x26,0xfd,0xd4,0xf9,0xb8,0xf7,0x3e,0xf8,0x3c,0xfc,0x96,0x02,0xf2,0x08,0x1e,0x0f,0xec,0x13,0xca,0x18,0x12,0x1e,0x14,0x22,0x44,0x25,0x94,0x26,0x1a,0x27,0xdc,0x26,0x68,0x26,0xc6,0x25,0xfe,0x23,0x32,0x21,0x06,0x1f,0xc4,0x1c,0x9a,0x1a,0x2c,0x18,0x10,0x15,0x74,0x11,0x1e,0x0e,0xcc,0x0a,0x8c,0x07,0x68,0x04,0xc8,0x01,0x7c,0x00,0x0a,0x00,0x0a,0x00,0x06,0x00,0x00,0x00,0xfa,0xff,0xf4,0xff,0xee,0xff,0xa6,0xff,0xac,0xff,0x70,0x00,0x4c,0x02,0x44,0x04,0xcc,0x05,0xb2,0x06,0x10,0x08,0x4a,0x0a,0xae,0x0c,0xea,0x0d,0x32,0x0e,0xb0,0x0d,0x0e,0x0c,0xe8,0x09,0x20,0x07,0x9e,0x04,0x20,0x02,0xae,0x00,0xfe,0xff,0x0e,0x00,0xf8,0xff,0x04,0x00,0xec,0xff,0xf8,0xff,0x00,0x00,0x06,0x00,0xea,0xff,0xf2,0xff,0xf6,0xff]) 13 | 14 | def test_decode(self): 15 | 16 | wav_data = brr.decode(self.brr_sample) 17 | wav = wave.Wave_read(BytesIO(wav_data)) 18 | wav_good = wave.Wave_read(BytesIO(self.wav_good_data)) 19 | 20 | for x in range(0,64): 21 | self.assertEqual(wav_good.readframes(1), wav.readframes(1), "Decoded sample %d" % x) 22 | 23 | def test_encode(self): 24 | 25 | # Encode the WAV back to BRR 26 | encoded_brr = brr.encode(self.wav_good_data) 27 | 28 | # Verify BRR data was generated 29 | self.assertIsNotNone(encoded_brr) 30 | self.assertGreater(len(encoded_brr), 0) 31 | 32 | # Should be multiple of 9 bytes (BRR blocks) 33 | self.assertEqual(len(encoded_brr) % 9, 0) 34 | 35 | for x in range(0,64): 36 | self.assertEqual(self.brr_sample[x], encoded_brr[x], "Encoded sample %d" % x) 37 | 38 | 39 | if __name__ == '__main__': 40 | unittest.main() 41 | -------------------------------------------------------------------------------- /snes2asm/rangetree.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | RangeTree is a tree data structure for accessing values assigned to 5 | numerical ranges which are non-overlapping. 6 | 7 | tree = RangeTree() 8 | tree.add(0, 100, "A") 9 | tree.add(300,310, 42) 10 | 11 | tree.find(50) 12 | tree.intersects(200, 305) 13 | 14 | """ 15 | 16 | from copy import copy 17 | 18 | class RangeTree(): 19 | def __init__(self): 20 | self.root = None 21 | 22 | def find(self, index): 23 | """ 24 | Returns data value which index falls inside range entry 25 | """ 26 | node = self.root 27 | 28 | while node: 29 | if node.is_parent(): 30 | if node.left and node.left.contains(index): 31 | node = node.left 32 | elif node.right and node.right.contains(index): 33 | node = node.right 34 | else: 35 | return None 36 | elif node.contains(index): 37 | return node.val 38 | else: 39 | return None 40 | 41 | def items(self): 42 | """ 43 | Returns list of all data values in sequence 44 | """ 45 | items = [] 46 | path = [] 47 | stack = [self.root] 48 | while len(stack) != 0: 49 | node = stack.pop() 50 | if node not in path: 51 | path.append(node) 52 | 53 | if node.is_parent(): 54 | if node.right: 55 | stack.append(node.right) 56 | if node.left: 57 | stack.append(node.left) 58 | else: 59 | path.append(node) 60 | items.append(node.val) 61 | 62 | return items 63 | 64 | def intersects(self, start, end): 65 | """ 66 | Returns left most data value that intersects with first range entry 67 | """ 68 | node = self.root 69 | 70 | while node: 71 | if node.is_parent(): 72 | if node.left and node.left.intersects(start, end): 73 | node = node.left 74 | elif node.right and node.right.intersects(start, end): 75 | node = node.right 76 | else: 77 | return None 78 | elif node.intersects(start, end): 79 | return node.val 80 | else: 81 | return None 82 | 83 | def add(self, start, end, value): 84 | """ 85 | Add data entry assigned to numeric range 86 | """ 87 | new_node = _RangeNode(start, end, value) 88 | 89 | if self.root == None: 90 | self.root = new_node 91 | return 92 | 93 | if self.root.intersects(new_node.start, new_node.end): 94 | self._add_inner(self.root, new_node) 95 | else: 96 | if start < self.root.start: 97 | self._split_node(self.root, start, self.root.end, new_node, True) 98 | else: 99 | self._split_node(self.root, self.root.start, end, new_node, False ) 100 | 101 | def _add_inner(self, node, new_node): 102 | # Find tree node to insert new node into 103 | 104 | if not node.is_parent(): 105 | raise ValueError("Range conflict") 106 | 107 | if node.left.intersects(new_node.start, new_node.end): 108 | self._add_inner(node.left, new_node) 109 | elif node.right.intersects(new_node.start, new_node.end): 110 | self._add_inner(node.right, new_node) 111 | else: 112 | left = node.left.size() <= node.right.size() 113 | if left: 114 | self._split_node(node.left, node.start, new_node.end, new_node, False) 115 | else: 116 | self._split_node(node.right, new_node.start, node.end, new_node, True) 117 | 118 | def _split_node(self, node, range_start, range_end, new_node, left): 119 | # Create parent node to contain original node and new node 120 | tmp_node = copy(node) 121 | node.val = None # Mark as parent 122 | node.start = range_start 123 | node.end = range_end 124 | if left: 125 | node.left = new_node 126 | node.right = tmp_node 127 | else: 128 | node.left = tmp_node 129 | node.right = new_node 130 | 131 | def __str__(self): 132 | return str(self.root) 133 | 134 | class _RangeNode(): 135 | def __init__(self, start, end, val): 136 | if end < start: 137 | raise ValueError("Invalid range %d-%d" % (start, end)) 138 | self.start = start 139 | self.end = end 140 | self.left = None 141 | self.right = None 142 | self.val = val 143 | 144 | def contains(self, index): 145 | return self.start <= index and index < self.end 146 | 147 | def intersects(self, start, end): 148 | return min(self.end, end) - max(self.start, start) > 0 149 | 150 | def width(self): 151 | return self.end - self.start 152 | 153 | def size(self): 154 | c = 1 155 | if self.left: 156 | c = c + self.left.size() 157 | if self.right: 158 | c = c + self.right.size() 159 | return c 160 | 161 | def is_parent(self): 162 | return self.val == None 163 | 164 | def __str__(self): 165 | if self.is_parent(): 166 | s = "{%x-%x (%x)\n" % (self.start, self.end, self.size()) 167 | s = s + "[%s]\n[%s] }" % (str(self.left), str(self.right)) 168 | else: 169 | s = "N %x-%x => %s" % (self.start, self.end, str(self.val)) 170 | return s 171 | -------------------------------------------------------------------------------- /tile2bin/application.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import sys 5 | import argparse 6 | import struct 7 | import yaml 8 | 9 | class TileMapEntry: 10 | def __init__(self, pack): 11 | self.tile_index = pack & 0x3F 12 | self.palette_index = (pack & 0x1C00) >> 10 13 | self.vertical_flip = (pack & 0x8000) != 0 14 | self.horizontal_flip = (pack & 0x4000) != 0 15 | self.priority = (pack & 0x2000) != 0 16 | 17 | def pack(self): 18 | pack = self.tile_index & 0x3F 19 | pack = pack | (self.palette_index & 0x7 ) << 10 20 | pack = pack | 0x8000 if self.vertical_flip else 0 21 | pack = pack | 0x4000 if self.horizontal_flip else 0 22 | pack = pack | 0x2000 if self.priority else 0 23 | return struct.pack('> 7 | (bgr565 & 0x3e0) << 6 | (bgr565 & 0x1f) << 19 139 | self.palette_entry.append(rgbcolor) 140 | 141 | def savePalette(self): 142 | f = open(self._palFileName(), 'wb') 143 | 144 | for index in range(0, len(self.palette_entry)): 145 | rgbcolor = self.palette_entry[index] 146 | bgr565 = (rgbcolor << 7) & 0x7c00 | (rgbcolor >> 6) & 0x3e0 | (rgbcolor >> 19) & 0x1f 147 | f.write(struct.pack('= 0x8000 and self.disasm.valid_label(address - 0x8000) and not self.disasm.no_label: 135 | return self.disasm.label_name(address - 0x8000) 136 | else: 137 | return "$%04X" % address 138 | 139 | class PercentTemplate(Template): 140 | delimiter = '%' 141 | 142 | -------------------------------------------------------------------------------- /snes2asm/configurator.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import yaml 4 | import sys 5 | 6 | from snes2asm.decoder import * 7 | 8 | class Configurator: 9 | def __init__(self, file_path): 10 | fp = open(file_path, 'r') 11 | self.config = yaml.safe_load(fp) 12 | fp.close() 13 | self.decoders_enabled = {'data': Decoder, 'array': ArrayDecoder, 'text': TextDecoder, 'gfx': GraphicDecoder, 'palette': PaletteDecoder, 'bin': BinaryDecoder, 'translation': TranslationMap, 'index': IndexDecoder, 'tilemap': TileMapDecoder, 'sound': SoundDecoder} 14 | self._validate() 15 | self.label_lookup = {} 16 | 17 | def _validate(self): 18 | if 'memory' in self.config: 19 | memory = self.config['memory'] 20 | for variable in memory.keys(): 21 | addr = memory[variable] 22 | if addr > 0x2000 and addr < 0x7E0000: 23 | memory.pop(variable) 24 | print("Warning: Ignoring out of range memory entry '%s' at address 0x%X" % (variable, addr)) 25 | 26 | 27 | def apply(self, disasm): 28 | 29 | if 'banks' in self.config: 30 | banks = self.config['banks'] 31 | if type(banks) == list: 32 | disasm.code_banks = banks 33 | 34 | if 'structs' in self.config: 35 | pass 36 | 37 | if 'decoders' in self.config: 38 | for decode_conf in self.config['decoders']: 39 | if decode_conf['type'] not in self.decoders_enabled: 40 | print("Unknown decoder type %s. Skipping." % decode_conf['type']) 41 | continue 42 | if 'compress' in decode_conf and decode_conf['compress'] not in compression.get_names(): 43 | print("Unknown decoder compression %s. Skipping." % decode_conf['compress']) 44 | continue 45 | self.apply_decoder(disasm, decode_conf) 46 | 47 | if 'labels' in self.config: 48 | for label, index in self.config['labels'].items(): 49 | disasm.label_name(index, label) 50 | 51 | if 'memory' in self.config: 52 | for variable, index in self.config['memory'].items(): 53 | disasm.set_memory(index, variable) 54 | 55 | def apply_decoder(self, disasm, decode_conf): 56 | 57 | decoder_class = self.decoders_enabled[decode_conf['type']] 58 | if 'type' not in decode_conf: 59 | raise ValueError("Decoder missing type") 60 | del(decode_conf['type']) 61 | 62 | if 'label' not in decode_conf: 63 | raise ValueError("Decoder missing label") 64 | label = decode_conf['label'] 65 | 66 | if label in self.label_lookup: 67 | raise ValueError("Duplicate label %s" % label) 68 | 69 | # Check if decoder transgresses bank boundry 70 | if 'start' in decode_conf and 'end' in decode_conf: 71 | dec_start = decode_conf.get('start') 72 | dec_end = decode_conf.get('end') 73 | if type(dec_start) != int: 74 | raise ValueError("Decoder %s invalid start position %s" % (label, str(dec_start))) 75 | if type(dec_end) != int: 76 | raise ValueError("Decoder %s invalid end position %s" % (label, str(dec_end))) 77 | if dec_start > dec_end: 78 | raise ValueError("Decoder %s invalid start and end positions" % label) 79 | bank_size = disasm.cart.bank_size() 80 | if dec_start // bank_size != (dec_end-1) // bank_size: 81 | raise ValueError("Decoder %s crosses bank boundry at positions 0x%x to 0x%x" % (label, dec_start, dec_end)) 82 | 83 | # Replace decoder parameter references with actual object instances or sub decoder 84 | # {palette: 'sprites1_pal'} => {'palette: } 85 | # {palette: {'param': 'value'} } => {'palette: } 86 | for key, value in decode_conf.items(): 87 | # If the property of a decoder matches the name of a decoder class 88 | if key in self.decoders_enabled.keys(): 89 | # Is a label reference to another decoder 90 | if type(value) == str and value in self.label_lookup: 91 | decode_conf[key] = self.label_lookup[value] 92 | # Is a list of references 93 | elif type(value) == list: 94 | for i in range(0, len(value)): 95 | item = value[i] 96 | if type(item) == str and item in self.label_lookup: 97 | value[i] = self.label_lookup[item] 98 | else: 99 | raise ValueError("Could not find decoder label reference \"%s\" for decoder \"%s\"" % (item, str(decode_conf))) 100 | # Is a nested decoder with parameters 101 | elif type(value) == dict: 102 | value['type'] = key 103 | value['label'] = "%s_%s" % (label, key) 104 | decode_conf[key] = self.apply_decoder(disasm, value) 105 | else: 106 | raise ValueError("Could not find decoder label reference \"%s\" for decoder \"%s\"" % (value, str(decode_conf))) 107 | try: 108 | decoder_inst = decoder_class(**decode_conf) 109 | except TypeError as error: 110 | print("Error: Missing a required parameter from label: %s" % str(label)) 111 | print(error) 112 | sys.exit() 113 | 114 | try: 115 | disasm.add_decoder(decoder_inst) 116 | self.label_lookup[label] = decoder_inst 117 | 118 | except ValueError as error: 119 | print("Could not add decoder type: %s" % str(error)) 120 | 121 | return decoder_inst 122 | 123 | class DataStruct: 124 | def __init__(self, name, props): 125 | self.name = name 126 | self.props = props 127 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SNES2ASM 2 | ======== 3 | 4 | Overview 5 | -------- 6 | 7 | SNES2ASM is more than a disassembler. Generate assembly code with insights! Control the pipelines to assets like graphics, tile-maps, palettes, sound and text. Write your own configuration files describing the layouts of your ROMS. Makes developing your game modifications easy than ever. 8 | 9 | ### Features 10 | * Complete ROM disassembly and reassembly. 11 | * LoROM, HiROM with fast and slow ROM detection. 12 | * Extract and edit game assets like graphics, palettes and tile-maps. 13 | * Advanced code path detection and label generation. 14 | * SNES register symbol detection with code commentary. 15 | * Support for arrays, indices and encoded text 16 | * Integrated data decompression and recompression. 17 | * Custom configuration of game disassembly. 18 | 19 | 20 | Installation 21 | ------------ 22 | 23 | Clone this repo onto your computer, then: 24 | ``` 25 | cd snes2asm 26 | sudo python setup.py install 27 | ``` 28 | 29 | Requirements 30 | ------------ 31 | 32 | For compiling the output project you will need. 33 | 34 | * WLA DX Assembler - https://github.com/vhelin/wla-dx 35 | * GNU Make 36 | 37 | Usage 38 | ----- 39 | Provided is a command line interface tool `snes2asm` with the following options. 40 | ``` 41 | usage: snes2asm [-h] [-v] [-o OUTPUT_DIR] [-c CONFIG] [-b BANKS [BANKS ...]] 42 | [-hi] [-lo] [-f] [-s] [-nl] [-x] snes.sfc 43 | 44 | Disassembles snes cartridges into practical projects 45 | 46 | positional arguments: 47 | snes.sfc input snes file 48 | 49 | optional arguments: 50 | -h, --help show this help message and exit 51 | -v, --verbose Verbose output 52 | -o OUTPUT_DIR, --output_dir OUTPUT_DIR 53 | File path to output project 54 | -c CONFIG, --config CONFIG 55 | Path to decoding configuration yaml file 56 | -b BANKS [BANKS ...], --banks BANKS [BANKS ...] 57 | Code banks to disassemble. Default is auto-detect 58 | -hi, --hirom Force HiROM 59 | -lo, --lorom Force LoROM 60 | -f, --fastrom Force fast ROM addressing 61 | -s, --slowrom Force slow ROM addressing 62 | -nl, --nolabel Use addresses instead of labels 63 | -x, --hex Comments show instruction hex 64 | ``` 65 | ### Running: 66 | ``` 67 | snes2asm -b 0 1 -o output_dir snes2asm/tests/classickong.smc 68 | ``` 69 | 70 | Project Assembly 71 | ---------------- 72 | Once successfully disassembling your ROM into a project folder the next step is to test compilation. 73 | ``` 74 | cd output_dir 75 | # Edit Makefile PREFIX for the wlx-da install path 76 | make 77 | # Done! Next run output_dir/game.smc file is your emulator. 78 | ``` 79 | 80 | Configuration 81 | ------------- 82 | 83 | In addition to decompiling assembly code other data assets such as graphics, tilemaps and text can be decoded by specifying a YAML configuration file. 84 | 85 | ### banks: 86 | An array of bank numbers in the rom that hold machine code. Other banks will only be disassembled as data. 87 | ``` 88 | [0,1,2,3,4,5,8] 89 | ``` 90 | ### decoders: 91 | List of decoder objects and their parameters. 92 | | - | Example | Description 93 | |--|--|--| 94 | | **label:** | gradient1 | Unique name for code label 95 | | **type:** | data | Type of decoder being used (data,gfx,palette) 96 | | **compress:** | rle1 | Compression algorithm (aplib,rle1,lz2) 97 | | **start:** | 0x2fa90 | Hex address of starting point 98 | | **end:** | 0x2faf0 | Hex address of ending point 99 | | **(options):** | * | Other specific decoder options 100 | 101 | ### labels: 102 | Set of value key pairs which maps a code address to a named label. 103 | | Label | ROM Address 104 | |--|--| 105 | | **read_joy:** | 0x182EC | 106 | | **draw_oam:** | 0x13983 | 107 | 108 | ### memory: 109 | Set of value key pairs which maps a memory address to a named symbol. 110 | | Symbol | Memory Address 111 | |--|--| 112 | | **health:** | 0x701011 | 113 | | **lives:** | 0xDAA7 | 114 | 115 | ### structs: 116 | -- TODO 117 | 118 | **Example YAML configuration:** 119 | ``` 120 | --- 121 | banks: [0,3,4,5,7] 122 | decoders: 123 | - type: palette 124 | label: sprites1_pal 125 | start: 0x2f940 126 | end: 0x2f960 127 | - type: gfx 128 | label: sprites1_gfx 129 | start: 0x2bc60 130 | end: 0x2c860 131 | bit_depth: 4 132 | palette: sprites1_pal 133 | - type: translation 134 | label: default 135 | table: 136 | 0x0: "A" 137 | 0x1: "B" 138 | 0x2: "Long string" 139 | - type: text 140 | label: dialog 141 | translation: default 142 | start: 0x10000 143 | end: 0x10010 144 | - type: tilemap 145 | label: map1 146 | compress: byte_rle 147 | tilesize: 8x8 148 | width: 32 149 | start: 0x137AB 150 | end: 0x139B7 151 | - type: sound 152 | label: sample_brr 153 | start: 0x20800 154 | end: 0x20B84 155 | labels: 156 | read_joy: 0x182EC 157 | draw_oam: 0x13983 158 | memory: 159 | health: 0x701011 160 | lives: 0xDAA7 161 | ``` 162 | 163 | 164 | Sample ROM 165 | ========== 166 | If documenation makes you bored then try the provided sample! Seeing is believing. 167 | ``` 168 | snes2asm -c snes2asm/tests/classickong.yaml -o classickong snes2asm/tests/classickong.smc 169 | cd classickong 170 | make 171 | ``` 172 | -------------------------------------------------------------------------------- /snes2asm/bitmap.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import struct 4 | from io import BytesIO 5 | 6 | class BitmapIndex(): 7 | def __init__(self, width, height, bits, palette): 8 | if bits not in [1,2,4,8]: 9 | raise ValueError('Invalid index color bits') 10 | colors = 1 << bits 11 | palette = palette[0:colors] 12 | self._bfType = 19778 # Bitmap signature 13 | self._bfReserved1 = 0 14 | self._bfReserved2 = 0 15 | self._bfOffBits = 54 + len(palette)*4 16 | self._bcSize = 40 17 | self._bcWidth = width 18 | self._bcHeight = height 19 | self._bcPlanes = 1 20 | self._bcBitCount = bits 21 | if int((self._bcWidth * self._bcBitCount) / 8.0) & 0x3 == 0: 22 | self._paddedWidth = self._bcWidth 23 | else: 24 | self._paddedWidth = (self._bcWidth & ~0x3) + 4 25 | 26 | self._bfSize = 54 + self._graphicSize() + len(palette)*4 27 | 28 | self._palette = palette 29 | self._bcTotalColors = len(self._palette) 30 | self.clear() 31 | 32 | def _graphicSize(self): 33 | bytesPerRow = int(self._bcWidth * (self._bcBitCount / 8.0)) 34 | if bytesPerRow & 0x3 != 0: 35 | bytesPerRow = (bytesPerRow & ~0x3) + 4 36 | return int(bytesPerRow*self._bcHeight) 37 | 38 | def clear(self): 39 | self._graphics = bytearray(self._graphicSize()) 40 | 41 | def setPixel(self, x, y, index): 42 | if x < 0 or y < 0 or x >= self._bcWidth or y >= self._bcHeight: 43 | raise ValueError('Coords (%d,%d) out of range' % (x,y)) 44 | if index < 0 or index > self._bcTotalColors: 45 | raise ValueError('Color value %d must be inside index range of %d' % (index, self._bcTotalColors)) 46 | 47 | stride = int((self._bcHeight - 1 - y) * self._paddedWidth + x) 48 | # 1-Bit 49 | if self._bcBitCount == 1: 50 | offset = int(stride / 8) 51 | bitrow = stride & 0x7 52 | pixel = self._graphics[offset] 53 | pixel = pixel & ~(0x80 >> bitrow) | (index << 7) >> bitrow 54 | self._graphics[offset] = pixel & 0xFF 55 | # 2-Bit 56 | elif self._bcBitCount == 2: 57 | offset = int(stride / 4) 58 | bitrow = (stride & 0x3) << 1 59 | pixel = self._graphics[offset] 60 | pixel = pixel & ~(0xC >> bitrow) | (index << 6) >> bitrow 61 | self._graphics[offset] = pixel & 0xFF 62 | # 4-Bit 63 | elif self._bcBitCount == 4: 64 | offset = int(stride / 2) 65 | bitrow = (stride & 0x1) << 2 66 | pixel = self._graphics[offset] 67 | pixel = pixel & ~(0xF0 >> bitrow) | (index << 4) >> bitrow 68 | self._graphics[offset] = pixel & 0xFF 69 | # 8-Bit 70 | elif self._bcBitCount == 8: 71 | self._graphics[stride] = index 72 | 73 | def getPixel(self, x, y): 74 | if x < 0 or y < 0 or x >= self._bcWidth or y >= self._bcHeight: 75 | raise ValueError('Coords (%d,%d) out of range' % (x,y)) 76 | stride = (self._bcHeight - 1 - y) * self._paddedWidth + x 77 | # 1-Bit 78 | if self._bcBitCount == 1: 79 | offset = int(stride / 8) 80 | pixel = self._graphics[offset] 81 | return (pixel >> (~stride & 0x7)) & 0x1 82 | # 2-Bit 83 | elif self._bcBitCount == 2: 84 | offset = int(stride / 4) 85 | pixel = self._graphics[offset] 86 | return (pixel >> ((~stride & 0x3) << 1)) & 0x3 87 | # 4-Bit 88 | elif self._bcBitCount == 4: 89 | offset = int(stride / 2) 90 | pixel = self._graphics[offset] 91 | return (pixel >> ((~stride & 0x1) << 2)) & 0xF 92 | # 8-Bit 93 | elif self._bcBitCount == 8: 94 | return self._graphics[stride] 95 | 96 | def setGraphics(self, packedBytes): 97 | if type(packedBytes) != bytearray: 98 | raise ValueError('Packed bytes must be bytearray type') 99 | 100 | if len(packedBytes) != self._graphicSize(): 101 | raise ValueError('Packed bytes must %d bytes in size instead of %d' % (self._graphicSize(), len(packedBytes))) 102 | 103 | self._graphics = packedBytes 104 | 105 | def output(self): 106 | io = BytesIO() 107 | 108 | # Writing BITMAPFILEHEADER 109 | io.write(struct.pack(' 0x400000 49 | 50 | self.header = 0x400000 if self.extended else 0 51 | 52 | if self.options.get('hirom'): 53 | self.hirom = True 54 | elif self.options.get('lorom'): 55 | self.hirom = False 56 | else: 57 | hi_score = self.score_hirom() 58 | lo_score = self.score_lorom() 59 | 60 | self.hirom = hi_score > lo_score 61 | 62 | if self.options.get('fastrom'): 63 | self.fastrom = True 64 | elif self.options.get('slowrom'): 65 | self.fastrom = False 66 | else: 67 | # Auto-detect 68 | self.fastrom = (self[0xFFD5 if self.hirom else 0x7FD5] & 0x30) == 0x30 69 | 70 | self.header = self.header + (0x0ffb0 if self.hirom else 0x07fb0) 71 | 72 | self.parse_header() 73 | 74 | type = "Ext" if self.extended else "" 75 | type = type + ( "HiROM" if self.hirom else "LoROM") 76 | tv = "PAL" if self.country >= 2 and self.country <= 12 else "NTSC" 77 | print("Detected: %s[0x%x] ROMID:%s Type:%s TV:%s CheckSum:%04x" % (type, len(self.data), self.title, self.map_type(), tv, self.check_sum)) 78 | 79 | def parse_header(self): 80 | (self.make_code, self.game_code, self.fixed, self.expand_ram, self.special_version, self.sub_type, self.title, self.map_mode, self.cart_type, self.rom_size, self.sram_size, self.country, self.license_code, self.version, self.comp_check, self.check_sum) = struct.unpack("H4s7s3b21s7B2H", self.data[self.header:self.header+48]) 81 | self.game_code = self.game_code 82 | self.title = self.title 83 | (self.nvec_unused, self.nvec_cop, self.nvec_brk, self.nvec_abort, self.nvec_nmi, self.nvec_reset, self.nvec_irq, self.evec_unused, self.evec_cop, self.evec_unused2, self.evec_abort, self.evec_nmi, self.evec_reset, self.evec_irq) = struct.unpack("I6HI6H", self.data[self.header+48:self.header+80]) 84 | 85 | def map_type(self): 86 | type = ["ROM", "ROM+RAM", "ROM+RAM+BAT"] 87 | return type[self.cart_type] 88 | 89 | # Translate rom position to address 90 | def address(self, i): 91 | if self.extended: 92 | # TODO 93 | return 0x400000 + i 94 | else: 95 | if self.hirom: 96 | return 0x400000 + i 97 | else: 98 | return (((i & 0xFF8000) << 1) + (i & 0x7FFF)) + 0x8000 99 | 100 | # Translate address to rom position 101 | def index(self, address): 102 | 103 | if address < 0x8000 or ( address >= 0x7E0000 and address < 0x7FFFFF): 104 | return -1 105 | # HiROM 106 | if self.hirom: 107 | if address < 0x3FFFFF and address & 0x8000 == 0: 108 | return -1 109 | # LoROM 110 | else: 111 | if address & 0x8000 == 0: 112 | return -1 113 | address = (((address & 0x7F0000) >> 1) + (address & 0x7FFF)) 114 | 115 | mask = self.size() | self.size() - 1 116 | address = address & mask 117 | if address > self.size(): 118 | return -1 119 | return address 120 | 121 | def bank_size(self): 122 | return 0x10000 if self.hirom else 0x8000 123 | 124 | def bank_count(self): 125 | return int(len(self.data) / self.bank_size()) 126 | 127 | def size(self): 128 | return len(self.data) 129 | 130 | def bank_end(self, index): 131 | if self.hirom: 132 | return (index & 0xFF0000) + 0x10000 133 | else: 134 | return (index & 0xFF8000) + 0x8000 135 | 136 | def score_hirom(self): 137 | score = 0 138 | if (self[self.header + 0xFFDC] + self[self.header + 0xFFDD]*256 + self[self.header + 0xFFDE] + self[self.header + 0xFFDF]*256) == 0xFFFF: 139 | score = score + 2 140 | 141 | if self[self.header + 0xFFDA] == 0x33: 142 | score = score + 2 143 | 144 | if self[self.header + 0xFFD5] & 0xf < 4: 145 | score = score + 2 146 | 147 | if self[self.header + 0xFFFD] & 0x80 == 0: 148 | score = score - 4 149 | 150 | if (1 << abs(self[self.header + 0xFFD7] - 7)) > 48: 151 | score = score - 1 152 | 153 | if not all_ascii(self.data[self.header + 0xFFB0: self.header + 0xFFB6]): 154 | score = score - 1 155 | 156 | if not all_ascii(self.data[self.header + 0xFFC0:self.header + 0xFFD4]): 157 | score = score - 1 158 | 159 | return score 160 | 161 | def score_lorom(self): 162 | score = 0 163 | if (self[self.header + 0x7FDC] + self[self.header + 0x7FDD]*256 + self[self.header + 0x7FDE] + self[self.header + 0x7FDF]*256) == 0xFFFF: 164 | score = score + 2 165 | 166 | if self[self.header + 0x7FDA] == 0x33: 167 | score = score + 2 168 | 169 | if self[self.header + 0x7FD5] & 0xf < 4: 170 | score = score + 2 171 | 172 | if self[self.header + 0x7FFD] & 0x80 == 0: 173 | score = score - 4 174 | 175 | if 1 << abs(self[self.header + 0x7FD7] - 7) > 48: 176 | score = score - 1 177 | 178 | if not all_ascii(self.data[self.header + 0xFFB0:self.header + 0xFFB6]): 179 | score = score - 1 180 | 181 | if not all_ascii(self.data[self.header + 0xFFC0:self.header + 0xFFD4]): 182 | score = score - 1 183 | 184 | return score 185 | 186 | 187 | def all_ascii(text): 188 | for char in text: 189 | if char < 32 or char > 126: 190 | return False 191 | return True 192 | 193 | -------------------------------------------------------------------------------- /snes2asm/gui/widget.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import sys 5 | import argparse 6 | 7 | from PyQt5.QtCore import Qt 8 | from PyQt5.QtGui import QIcon, QKeySequence 9 | from PyQt5.QtWidgets import * 10 | 11 | from tile2bin.application import * 12 | 13 | class Window(QMainWindow): 14 | def __init__(self, app): 15 | super().__init__() 16 | 17 | self.app = app 18 | 19 | self.setWindowTitle('snes2asm') 20 | 21 | self.tabs = QTabWidget() 22 | self.tabs.setDocumentMode(True) 23 | self.tabs.setTabsClosable(True) 24 | self.tabs.tabCloseRequested.connect(self.closeTab) 25 | self.tabs.setMinimumSize(600, 400); 26 | 27 | self.setCentralWidget(self.tabs) 28 | 29 | self.createActions() 30 | self.createDockWindows() 31 | 32 | self.statusBar = QStatusBar() 33 | self.setStatusBar(self.statusBar) 34 | self.createMenus() 35 | self.createToolBars() 36 | 37 | self.panelHidden = False 38 | 39 | def createActions(self): 40 | self.newAct = QAction(QIcon('images/new.png'), "&New", self, shortcut=QKeySequence.New, statusTip="Create a new file", triggered=self.newFile) 41 | 42 | self.openAct = QAction(QIcon('images/open.png'), "&Open...", self, shortcut=QKeySequence.Open, statusTip="Open an existing file", triggered=self.open) 43 | 44 | self.saveAct = QAction(QIcon('images/save.png'), "&Save", self, shortcut=QKeySequence.Save, statusTip="Save the document to disk", triggered=self.save) 45 | 46 | self.saveAsAct = QAction("Save &As...", self, shortcut=QKeySequence.SaveAs, statusTip="Save the document under a new name", triggered=self.saveAs) 47 | 48 | self.exitAct = QAction("E&xit", self, shortcut=QKeySequence.Quit, statusTip="Exit the application", triggered=QApplication.instance().closeAllWindows) 49 | 50 | self.cutAct = QAction(QIcon('images/cut.png'), "Cu&t", self, shortcut=QKeySequence.Cut, statusTip="Cut the current selection's contents to the clipboard", triggered=self.cut) 51 | 52 | self.copyAct = QAction(QIcon('images/copy.png'), "&Copy", self, shortcut=QKeySequence.Copy, statusTip="Copy the current selection's contents to the clipboard", triggered=self.copy) 53 | 54 | self.pasteAct = QAction(QIcon('images/paste.png'), "&Paste", self, shortcut=QKeySequence.Paste, statusTip="Paste the clipboard's contents into the current selection", triggered=self.paste) 55 | 56 | self.aboutAct = QAction("&About", self, statusTip="Show the application's About box", triggered=self.about) 57 | 58 | def createMenus(self): 59 | self.fileMenu = self.menuBar().addMenu("&File") 60 | self.fileMenu.addAction(self.newAct) 61 | self.fileMenu.addAction(self.openAct) 62 | self.fileMenu.addAction(self.saveAct) 63 | self.fileMenu.addAction(self.saveAsAct) 64 | self.fileMenu.addSeparator() 65 | self.fileMenu.addAction(self.exitAct) 66 | 67 | self.editMenu = self.menuBar().addMenu("&Edit") 68 | self.editMenu.addAction(self.cutAct) 69 | self.editMenu.addAction(self.copyAct) 70 | self.editMenu.addAction(self.pasteAct) 71 | 72 | self.windowMenu = self.menuBar().addMenu("&Window") 73 | self.windowMenu.addAction(self.tileAct) 74 | 75 | self.helpMenu = self.menuBar().addMenu("&Help") 76 | self.helpMenu.addAction(self.aboutAct) 77 | 78 | def createToolBars(self): 79 | self.fileToolBar = self.addToolBar("File") 80 | self.fileToolBar.addAction(self.newAct) 81 | self.fileToolBar.addAction(self.openAct) 82 | self.fileToolBar.addAction(self.saveAct) 83 | 84 | def createDockWindows(self): 85 | dock = QDockWidget("Tiles") 86 | 87 | dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) 88 | 89 | docklayout = QVBoxLayout() 90 | self.tileSetView = TileSetView(128, 128) 91 | docklayout.addWidget(self.tileSetView) 92 | self.paletteView = PaletteView() 93 | docklayout.addWidget(self.paletteView) 94 | 95 | container = QWidget() 96 | container.setLayout(docklayout) 97 | 98 | self.tileAct = dock.toggleViewAction() 99 | dock.setWidget(container) 100 | self.addDockWidget(Qt.RightDockWidgetArea, dock) 101 | #self.viewMenu.addAction(dock.toggleViewAction()) 102 | 103 | def closeTab(self, index): 104 | answer = QMessageBox.StandardButton.Yes 105 | if self.app.docs[index].changed: 106 | answer = QMessageBox.question(self, 'Confirmation', 'Save changes?', 107 | QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No 108 | ) 109 | if answer == QMessageBox.StandardButton.Yes: 110 | self.app.docs[index].save() 111 | 112 | self.app.removeDoc(index) 113 | self.tabs.removeTab(index) 114 | self.setDocEnabled(len(self.app.docs) > 0) 115 | 116 | def setDocEnabled(self, enabled): 117 | self.saveAct.setEnabled(enabled) 118 | self.saveAsAct.setEnabled(enabled) 119 | self.cutAct.setEnabled(enabled) 120 | self.copyAct.setEnabled(enabled) 121 | self.pasteAct.setEnabled(enabled) 122 | 123 | def newFile(self): 124 | doc = Document() 125 | self.addDoc(doc) 126 | 127 | def addDoc(self, doc): 128 | self.app.addDoc(doc) 129 | self.tabs.addTab(TileMapView(doc.pixWidth(), doc.pixHeight()), doc.title()) 130 | self.setDocEnabled(True) 131 | 132 | def curDoc(self): 133 | index = self.tabs.currentIndex() 134 | return self.app.docs[index] 135 | 136 | def open(self): 137 | fileName, _ = QFileDialog.getOpenFileName(self, "Open Tile Map", '', "Tilemap (*.tilemap);;All Files (*)") 138 | if len(fileName) > 0: 139 | doc = TileDocument() 140 | try: 141 | doc.loadYaml(fileName) 142 | except Exception as e: 143 | QMessageBox.question(self, 'File', 'Error: %s' % e.message) 144 | return 145 | 146 | self.addDoc(doc) 147 | #self.statusBar().showMessage("File loaded", 2000) 148 | 149 | def save(self): 150 | self.curDoc().save() 151 | #self.statusBar().showMessage("File saved", 2000) 152 | 153 | def saveAs(self): 154 | fileName, _ = QFileDialog.getSaveFileName(self, "Save Tile Map", self.curDoc().filepath(), "Tilemap (*.tilemap);;All Files (*)") 155 | if len(fileName) > 0: 156 | self.app.working_dir = os.path.dirname(os.path.realpath(fileName)) 157 | self.app.filename = os.path.basename(fileName) 158 | self.save() 159 | 160 | def cut(self): 161 | pass 162 | 163 | def copy(self): 164 | pass 165 | 166 | def paste(self): 167 | pass 168 | 169 | def about(self): 170 | pass 171 | -------------------------------------------------------------------------------- /snes2asm/brr.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import wave 4 | from io import BytesIO 5 | 6 | def decode(brr_data, rate=32000): 7 | samples = bytearray() 8 | last_sample1 = 0 9 | last_sample2 = 0 10 | # Process blocks 11 | for h in range(0, len(brr_data), 9): 12 | header = brr_data[h] 13 | shift = header >> 4 14 | filt = (header >> 2) & 0x3 15 | h += 1 16 | # Read block samples 17 | for i in range(0, 16): 18 | low_nibble = (i & 1) != 0 19 | i = h + (i >> 1) 20 | sample = (brr_data[i] & 0xF) if low_nibble else brr_data[i] >> 4 21 | # Convert to 4-bit signed 22 | sample = sample - 16 if sample >= 8 else sample 23 | # Shifting 24 | if shift > 12: 25 | sample = ~0x7FF & sample 26 | else: 27 | sample = (sample << shift) >> 1 28 | sample = sample_filter(sample, filt, last_sample1, last_sample2) 29 | sample = clamp(sample) 30 | last_sample1 = last_sample2 31 | last_sample2 = sample 32 | sample = sample * 2 33 | samples.append(sample & 0xFF) 34 | samples.append((sample >> 8) & 0xFF) 35 | io = BytesIO() 36 | wav = wave.Wave_write(io) 37 | wav.setparams((1,2,rate,0,'NONE','not compressed')) 38 | wav.writeframes(samples) 39 | io.seek(0) 40 | return io.read() 41 | 42 | def sample_filter(sample, filt, last_sample1, last_sample2): 43 | # Filtering 44 | if filt == 0: 45 | pass 46 | elif filt == 1: 47 | sample += last_sample2 - (last_sample2 >> 4) 48 | elif filt == 2: 49 | sample += last_sample2 << 1 50 | sample += -(last_sample2 + (last_sample2 << 1)) >> 5 51 | sample += -last_sample1 52 | sample += last_sample1 >> 4 53 | elif filt == 3: 54 | sample += last_sample2 << 1 55 | sample += -(last_sample2 + (last_sample2 << 2) + (last_sample2 << 3)) >> 6 56 | sample += -last_sample1 57 | sample += (last_sample1 + (last_sample1 << 1)) >> 4 58 | return sample 59 | 60 | 61 | def encode(wav_data): 62 | io = BytesIO(wav_data) 63 | wav = wave.Wave_read(io) 64 | 65 | if wav.getnchannels() != 1: 66 | raise ValueError('Audio sample must be mono') 67 | 68 | if wav.getsampwidth() != 2: 69 | raise ValueError('Audio sample must be 16-bit') 70 | 71 | # Read all frames as raw bytes 72 | raw_samples = wav.readframes(wav.getnframes()) 73 | 74 | # Convert to 16-bit signed samples 75 | samples = [] 76 | for i in range(0, len(raw_samples), 2): 77 | sample = raw_samples[i] | (raw_samples[i+1] << 8) 78 | # Convert to signed 79 | if sample >= 0x8000: 80 | sample -= 0x10000 81 | # Scale down from 16-bit to 15-bit range 82 | samples.append(sample >> 1) 83 | 84 | brr_data = bytearray() 85 | last_samples = [[0, 0] for _ in range(4)] # [last_sample1, last_sample2] for each filter 86 | 87 | # Process 16 samples at a time (one BRR block) 88 | for block_start in range(0, len(samples), 16): 89 | block = samples[block_start:block_start+16] 90 | if len(block) < 16: 91 | # Pad final block with zeros 92 | block.extend([0] * (16 - len(block))) 93 | 94 | # Check if this is the last block 95 | is_last_block = (block_start + 16 >= len(samples)) 96 | 97 | # Find best filter and shift for this block 98 | best_error = float('inf') 99 | best_filter = 0 100 | best_shift = 0 101 | best_nibbles = None 102 | 103 | for filt in range(4): 104 | for shift in range(13): 105 | error = 0 106 | nibbles = [] 107 | test_last1 = last_samples[filt][0] 108 | test_last2 = last_samples[filt][1] 109 | 110 | for sample in block: 111 | # Predict sample using filter 112 | predicted = 0 113 | if filt == 1: 114 | predicted = test_last2 - (test_last2 >> 4) 115 | elif filt == 2: 116 | predicted = (test_last2 << 1) + ((-(test_last2 * 3)) >> 5) - test_last1 + (test_last1 >> 4) 117 | elif filt == 3: 118 | predicted = (test_last2 << 1) + ((-(test_last2 * 13)) >> 6) - test_last1 + ((test_last1 * 3) >> 4) 119 | 120 | # Calculate delta 121 | delta = sample - predicted 122 | 123 | # Quantize delta to 4-bit range with shift 124 | if shift > 12: 125 | quantized = 0 126 | else: 127 | quantized = (delta << 1) >> shift 128 | 129 | # Clamp to 4-bit signed range (-8 to 7) 130 | if quantized > 7: 131 | quantized = 7 132 | elif quantized < -8: 133 | quantized = -8 134 | 135 | nibbles.append(quantized & 0xF) 136 | 137 | # Reconstruct sample for filter prediction 138 | if shift > 12: 139 | reconstructed_delta = 0 140 | else: 141 | # Sign-extend 4-bit value 142 | signed_nibble = quantized if quantized < 8 else quantized - 16 143 | reconstructed_delta = (signed_nibble << shift) >> 1 144 | 145 | reconstructed = predicted + reconstructed_delta 146 | reconstructed = clamp(reconstructed) 147 | 148 | # Track error 149 | error += abs(sample - reconstructed) 150 | 151 | test_last1 = test_last2 152 | test_last2 = reconstructed 153 | 154 | if error < best_error: 155 | best_error = error 156 | best_filter = filt 157 | best_shift = shift 158 | best_nibbles = nibbles 159 | 160 | # Write BRR block header 161 | header = (best_shift << 4) | (best_filter << 2) 162 | # Set end and loop flags on the last block 163 | if is_last_block: 164 | header |= 0x03 # Set both loop (bit 1) and end (bit 0) flags 165 | brr_data.append(header) 166 | 167 | # Write BRR block samples (16 nibbles = 8 bytes) 168 | for i in range(0, 16, 2): 169 | byte = (best_nibbles[i] << 4) | best_nibbles[i+1] 170 | brr_data.append(byte) 171 | 172 | # Update last samples for chosen filter 173 | test_last1 = last_samples[best_filter][0] 174 | test_last2 = last_samples[best_filter][1] 175 | 176 | for i, nibble in enumerate(best_nibbles): 177 | # Reconstruct using chosen filter 178 | predicted = 0 179 | if best_filter == 1: 180 | predicted = test_last2 - (test_last2 >> 4) 181 | elif best_filter == 2: 182 | predicted = (test_last2 << 1) + ((-(test_last2 * 3)) >> 5) - test_last1 + (test_last1 >> 4) 183 | elif best_filter == 3: 184 | predicted = (test_last2 << 1) + ((-(test_last2 * 13)) >> 6) - test_last1 + ((test_last1 * 3) >> 4) 185 | 186 | # Decode nibble 187 | signed_nibble = nibble if nibble < 8 else nibble - 16 188 | if best_shift > 12: 189 | delta = 0 190 | else: 191 | delta = (signed_nibble << best_shift) >> 1 192 | 193 | reconstructed = clamp(predicted + delta) 194 | test_last1 = test_last2 195 | test_last2 = reconstructed 196 | 197 | # Update all filters' last samples 198 | for f in range(4): 199 | last_samples[f] = [test_last1, test_last2] 200 | 201 | return bytes(brr_data) 202 | 203 | def clamp(val): 204 | if val > 0x7FFF: return 0x7FFF 205 | if val < -0x7FFF: return -0x7FFF 206 | return val 207 | -------------------------------------------------------------------------------- /tile2bin/widget.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import sys 5 | import argparse 6 | 7 | from PyQt5.QtCore import Qt 8 | from PyQt5.QtGui import QIcon, QKeySequence 9 | from PyQt5.QtWidgets import * 10 | 11 | from tile2bin.application import * 12 | 13 | class Window(QMainWindow): 14 | def __init__(self, app): 15 | super().__init__() 16 | 17 | self.app = app 18 | 19 | self.setWindowTitle('tile2bin') 20 | 21 | self.tabs = QTabWidget() 22 | self.tabs.setDocumentMode(True) 23 | self.tabs.setTabsClosable(True) 24 | self.tabs.tabCloseRequested.connect(self.closeTab) 25 | self.tabs.setMinimumSize(600, 400); 26 | 27 | self.setCentralWidget(self.tabs) 28 | 29 | self.createActions() 30 | self.createDockWindows() 31 | 32 | self.statusBar = QStatusBar() 33 | self.setStatusBar(self.statusBar) 34 | self.createMenus() 35 | self.createToolBars() 36 | 37 | self.panelHidden = False 38 | 39 | def createActions(self): 40 | self.newAct = QAction(QIcon('images/new.png'), "&New", self, shortcut=QKeySequence.New, statusTip="Create a new file", triggered=self.newFile) 41 | 42 | self.openAct = QAction(QIcon('images/open.png'), "&Open...", self, shortcut=QKeySequence.Open, statusTip="Open an existing file", triggered=self.open) 43 | 44 | self.saveAct = QAction(QIcon('images/save.png'), "&Save", self, shortcut=QKeySequence.Save, statusTip="Save the document to disk", triggered=self.save) 45 | 46 | self.saveAsAct = QAction("Save &As...", self, shortcut=QKeySequence.SaveAs, statusTip="Save the document under a new name", triggered=self.saveAs) 47 | 48 | self.exitAct = QAction("E&xit", self, shortcut=QKeySequence.Quit, statusTip="Exit the application", triggered=QApplication.instance().closeAllWindows) 49 | 50 | self.cutAct = QAction(QIcon('images/cut.png'), "Cu&t", self, shortcut=QKeySequence.Cut, statusTip="Cut the current selection's contents to the clipboard", triggered=self.cut) 51 | 52 | self.copyAct = QAction(QIcon('images/copy.png'), "&Copy", self, shortcut=QKeySequence.Copy, statusTip="Copy the current selection's contents to the clipboard", triggered=self.copy) 53 | 54 | self.pasteAct = QAction(QIcon('images/paste.png'), "&Paste", self, shortcut=QKeySequence.Paste, statusTip="Paste the clipboard's contents into the current selection", triggered=self.paste) 55 | 56 | self.aboutAct = QAction("&About", self, statusTip="Show the application's About box", triggered=self.about) 57 | 58 | def createMenus(self): 59 | self.fileMenu = self.menuBar().addMenu("&File") 60 | self.fileMenu.addAction(self.newAct) 61 | self.fileMenu.addAction(self.openAct) 62 | self.fileMenu.addAction(self.saveAct) 63 | self.fileMenu.addAction(self.saveAsAct) 64 | self.fileMenu.addSeparator() 65 | self.fileMenu.addAction(self.exitAct) 66 | 67 | self.editMenu = self.menuBar().addMenu("&Edit") 68 | self.editMenu.addAction(self.cutAct) 69 | self.editMenu.addAction(self.copyAct) 70 | self.editMenu.addAction(self.pasteAct) 71 | 72 | self.windowMenu = self.menuBar().addMenu("&Window") 73 | self.windowMenu.addAction(self.tileAct) 74 | 75 | self.helpMenu = self.menuBar().addMenu("&Help") 76 | self.helpMenu.addAction(self.aboutAct) 77 | 78 | def createToolBars(self): 79 | self.fileToolBar = self.addToolBar("File") 80 | self.fileToolBar.addAction(self.newAct) 81 | self.fileToolBar.addAction(self.openAct) 82 | self.fileToolBar.addAction(self.saveAct) 83 | 84 | def createDockWindows(self): 85 | dock = QDockWidget("Tiles") 86 | 87 | dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) 88 | 89 | docklayout = QVBoxLayout() 90 | self.tileSetView = TileSetView(128, 128) 91 | docklayout.addWidget(self.tileSetView) 92 | self.paletteView = PaletteView() 93 | docklayout.addWidget(self.paletteView) 94 | 95 | container = QWidget() 96 | container.setLayout(docklayout) 97 | 98 | self.tileAct = dock.toggleViewAction() 99 | dock.setWidget(container) 100 | self.addDockWidget(Qt.RightDockWidgetArea, dock) 101 | #self.viewMenu.addAction(dock.toggleViewAction()) 102 | 103 | def closeTab(self, index): 104 | answer = QMessageBox.StandardButton.Yes 105 | if self.app.docs[index].changed: 106 | answer = QMessageBox.question(self, 'Confirmation', 'Save changes?', 107 | QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No 108 | ) 109 | if answer == QMessageBox.StandardButton.Yes: 110 | self.app.docs[index].save() 111 | 112 | self.app.removeDoc(index) 113 | self.tabs.removeTab(index) 114 | self.setDocEnabled(len(self.app.docs) > 0) 115 | 116 | def setDocEnabled(self, enabled): 117 | self.saveAct.setEnabled(enabled) 118 | self.saveAsAct.setEnabled(enabled) 119 | self.cutAct.setEnabled(enabled) 120 | self.copyAct.setEnabled(enabled) 121 | self.pasteAct.setEnabled(enabled) 122 | 123 | def newFile(self): 124 | # TODO prompt detail dialog 125 | doc = TileDocument() 126 | self.addDoc(doc) 127 | 128 | def addDoc(self, doc): 129 | self.app.addDoc(doc) 130 | self.tabs.addTab(TileMapView(doc.pixWidth(), doc.pixHeight()), doc.title()) 131 | self.setDocEnabled(True) 132 | 133 | def curDoc(self): 134 | index = self.tabs.currentIndex() 135 | return self.app.docs[index] 136 | 137 | def open(self): 138 | fileName, _ = QFileDialog.getOpenFileName(self, "Open Tile Map", '', "Tilemap (*.tilemap);;All Files (*)") 139 | if len(fileName) > 0: 140 | doc = TileDocument() 141 | try: 142 | doc.loadYaml(fileName) 143 | except Exception as e: 144 | QMessageBox.question(self, 'File', 'Error: %s' % e.message) 145 | return 146 | 147 | self.addDoc(doc) 148 | #self.statusBar().showMessage("File loaded", 2000) 149 | 150 | def save(self): 151 | self.curDoc().save() 152 | #self.statusBar().showMessage("File saved", 2000) 153 | 154 | def saveAs(self): 155 | fileName, _ = QFileDialog.getSaveFileName(self, "Save Tile Map", self.curDoc().filepath(), "Tilemap (*.tilemap);;All Files (*)") 156 | if len(fileName) > 0: 157 | self.app.working_dir = os.path.dirname(os.path.realpath(fileName)) 158 | self.app.filename = os.path.basename(fileName) 159 | self.save() 160 | 161 | def cut(self): 162 | pass 163 | 164 | def copy(self): 165 | pass 166 | 167 | def paste(self): 168 | pass 169 | 170 | def about(self): 171 | pass 172 | 173 | 174 | class TileView(QGraphicsView): 175 | def __init__(self, width, height): 176 | self.scene = QGraphicsScene(0, 0, width, height) 177 | super().__init__(self.scene) 178 | 179 | class TileSetView(TileView): 180 | def __init__(self, width, height): 181 | super().__init__(width, height) 182 | 183 | class TileMapView(TileView): 184 | def __init__(self, width, height): 185 | super().__init__(width, height) 186 | 187 | class PaletteView(QGraphicsView): 188 | def __init__(self): 189 | super().__init__() 190 | 191 | 192 | -------------------------------------------------------------------------------- /snes2asm/compression/lz.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from functools import reduce 3 | 4 | class lz_compress: 5 | 6 | DIRECT_COPY = 0 7 | FILL_BYTE = 1 8 | FILL_WORD = 2 9 | FILL_INC = 3 10 | FILL_ZERO = 3 11 | REPEAT = 4 12 | 13 | def __init__(self, data): 14 | self._in = data 15 | self._offset = 0 16 | self._direct = bytearray() 17 | self._out = bytearray() 18 | self._functions = [] 19 | 20 | def _rle8(self): 21 | val = self._in[self._offset] 22 | 23 | index = self._offset + 1 24 | while index < len(self._in) and val == self._in[index]: 25 | index += 1 26 | return (self.FILL_BYTE, index - self._offset, bytearray([val])) 27 | 28 | def _rle16(self): 29 | if self._offset + 4 >= len(self._in): 30 | return (self.FILL_WORD, 0, bytearray()) 31 | val1 = self._in[self._offset] 32 | val2 = self._in[self._offset+1] 33 | 34 | index = self._offset + 2 35 | while index < len(self._in) and val1 == self._in[index] and val2 == self._in[index+1]: 36 | index += 2 37 | length = index - self._offset 38 | length = length if length > 2 else 0 39 | return (self.FILL_WORD, length, bytearray([val1, val2])) 40 | 41 | def _increment_fill(self): 42 | val = self._in[self._offset] 43 | index = self._offset 44 | while index < len(self._in) and val == self._in[index]: 45 | val = (val + 1) & 0xFF 46 | index += 1 47 | return (self.FILL_INC, index - self._offset, bytearray([self._in[self._offset]])) 48 | 49 | def _zero_fill(self): 50 | index = self._offset 51 | while index < len(self._in) and self._in[index] == 0: 52 | index += 1 53 | return (self.FILL_ZERO, index - self._offset, bytearray()) 54 | 55 | def _search(self): 56 | max_length = 0 57 | max_index = 0 58 | 59 | for index in range(0, self._offset): 60 | offset = self._offset 61 | while offset < len(self._in) and self._in[index] == self._in[offset]: 62 | offset += 1 63 | index += 1 64 | length = offset - self._offset 65 | 66 | if length > max_length: 67 | max_length = length 68 | max_index = index - length 69 | 70 | return (max_length, max_index) 71 | 72 | def _search_inverse(self): 73 | max_length = 0 74 | max_index = 0 75 | 76 | for index in range(0, self._offset): 77 | offset = self._offset 78 | while offset < len(self._in) and self._in[index] == self._in[offset] ^ 0xFF: 79 | offset += 1 80 | index += 1 81 | length = offset - self._offset 82 | 83 | if length > max_length: 84 | max_length = length 85 | max_index = index - length 86 | 87 | return (max_length, max_index) 88 | 89 | def _search_bit_reverse(self): 90 | max_length = 0 91 | max_index = 0 92 | 93 | for index in range(0, self._offset): 94 | offset = self._offset 95 | while offset < len(self._in) and self._in[index] == bit_reverse(self._in[offset]): 96 | offset += 1 97 | index += 1 98 | length = offset - self._offset 99 | 100 | if length > max_length: 101 | max_length = length 102 | max_index = index - length 103 | 104 | return (max_length, max_index) 105 | 106 | 107 | def _search_reverse(self): 108 | max_length = 0 109 | max_index = 0 110 | for index in range(self._offset, -1, -1): 111 | offset = self._offset 112 | while offset < len(self._in) and index >= 0 and self._in[index] == self._in[offset]: 113 | offset += 1 114 | index -= 1 115 | length = offset - self._offset 116 | 117 | if length > max_length: 118 | max_length = length 119 | max_index = index + 1 120 | return (max_length, max_index) 121 | 122 | def _repeat_be(self): 123 | max_length, max_index = self._search(); 124 | return (self.REPEAT, max_length, bytearray([max_index & 0xFF, max_index >> 8])) 125 | 126 | def _repeat_le(self): 127 | max_length, max_index = self._search(); 128 | return (self.REPEAT, max_length, bytearray([max_index >> 8, max_index & 0xFF])) 129 | 130 | def _write_direct_copy(self): 131 | if len(self._direct) > 0: 132 | self._write_command(self.DIRECT_COPY, len(self._direct), self._direct) 133 | self._direct = bytearray() 134 | 135 | def _write_command(self, command, length, val): 136 | if command == self.FILL_WORD: 137 | length = length >> 1 138 | length -= 1 139 | # Long command 140 | if length > 0x1F: 141 | header = 0xE0 | (command << 2) | length >> 8 142 | self._out += bytearray([header, length & 0xFF]) + val 143 | else: 144 | header = command << 5 | length 145 | self._out += bytearray([header]) + val 146 | 147 | def do(self): 148 | while self._offset < len(self._in): 149 | # Run all encoding candidates 150 | algs = [f() for f in self._functions] 151 | # Select longest running algorithm 152 | (command, length, val) = reduce(lambda a, b: a if a[1] - len(a[2]) > b[1] - len(b[2]) else b, algs) 153 | if length > 2: 154 | self._write_direct_copy() 155 | self._write_command(command, length, val) 156 | else: 157 | self._direct.append(self._in[self._offset]) 158 | length = 1 159 | 160 | self._offset += length 161 | self._write_direct_copy() 162 | 163 | # Terminate 164 | self._out.append(0xFF) 165 | return self._out 166 | 167 | class lz_decompress: 168 | def __init__(self, data): 169 | self._in = data 170 | self._offset = 0 171 | self._length = 0 172 | self._out = bytearray() 173 | self._functions = [self._direct_copy, self._fill_byte, self._fill_word, self._inc_fill, self._repeat_be, self._noop, self._noop, self._long_command] 174 | 175 | def _direct_copy(self): 176 | end = self._offset + self._length 177 | self._out += self._in[self._offset:end] 178 | self._offset += self._length 179 | 180 | def _fill_byte(self): 181 | val = self._in[self._offset] 182 | self._out += bytearray([val] * self._length) 183 | self._offset += 1 184 | 185 | def _fill_zero(self): 186 | self._out += bytearray([0] * self._length) 187 | 188 | def _fill_word(self): 189 | val1 = self._in[self._offset] 190 | val2 = self._in[self._offset+1] 191 | self._out += bytearray([val1,val2] * self._length) 192 | self._offset += 2 193 | 194 | def _inc_fill(self): 195 | val = self._in[self._offset] 196 | self._out += bytearray([x & 0xFF for x in range(val, val + self._length)]) 197 | self._offset += 1 198 | 199 | def _repeat_be(self): 200 | start = (self._in[self._offset] | (self._in[self._offset+1] << 8)) 201 | end = start + self._length 202 | self._out += self._out[start:end] 203 | self._offset += 2 204 | 205 | def _repeat_le(self): 206 | start = ((self._in[self._offset] << 8) | self._in[self._offset+1]) 207 | end = start + self._length 208 | self._out += self._out[start:end] 209 | self._offset += 2 210 | 211 | def _repeat_reverse(self): 212 | start = (self._in[self._offset] | (self._in[self._offset+1] << 8)) - 1 213 | end = start + self._length 214 | self._out += self._out[end:start:-1] 215 | self._offset += 2 216 | 217 | def _repeat_bit_reverse(self): 218 | start = (self._in[self._offset] | (self._in[self._offset+1] << 8)) 219 | end = start + self._length 220 | self._out += bytearray([bit_reverse(b) for b in self._out[start:end]]) 221 | self._offset += 2 222 | 223 | def _noop(self): 224 | pass 225 | 226 | def _command(self): 227 | chunk = self._in[self._offset] 228 | # Terminate 229 | if chunk == 0xFF: 230 | self._offset = len(self._in) 231 | return 232 | # Run command 233 | command = (chunk & 0xE0) >> 5 234 | self._length = chunk & 0x1F 235 | if command != 0x7: 236 | self._length += 1 237 | self._offset += 1 238 | self._functions[command]() 239 | 240 | def _long_command(self): 241 | command = self._length >> 2 242 | ext_length = self._in[self._offset] 243 | self._length = ((self._length & 0x3) << 8 | ext_length) + 1 244 | self._offset += 1 245 | self._functions[command]() 246 | 247 | def do(self): 248 | while self._offset < len(self._in): 249 | self._command() 250 | return self._out 251 | 252 | def bit_reverse(val): 253 | val = (val & 0xF0) >> 4 | (val & 0x0F) << 4 254 | val = (val & 0xCC) >> 2 | (val & 0x33) << 2 255 | val = (val & 0xAA) >> 1 | (val & 0x55) << 1 256 | return val 257 | -------------------------------------------------------------------------------- /snes2asm/compression/aplib.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | def compress(data): 4 | return aplib_compress(data).do() 5 | 6 | def decompress(data): 7 | return aplib_decompress(data).do() 8 | 9 | class aplib_compress: 10 | """ 11 | aplib compression is based on lz77 12 | """ 13 | def __init__(self, data): 14 | self._in = data 15 | self._tagsize = 1 16 | self._tag = 0 17 | self._tagoffset = -1 18 | self._maxbit = (self._tagsize * 8) - 1 19 | self._curbit = 0 20 | self._isfirsttag = True 21 | self._offset = 0 22 | self._lastoffset = 0 23 | self._pair = True 24 | self.out = bytearray() 25 | 26 | def getdata(self): 27 | """builds an output array of what's currently compressed: 28 | currently output bit + current tag content""" 29 | tagstr = int2lebin(self._tag, self._tagsize) 30 | return modifybytearray(self.out, tagstr, self._tagoffset) 31 | 32 | def write_bit(self, value): 33 | """writes a bit, make space for the tag if necessary""" 34 | if self._curbit != 0: 35 | self._curbit -= 1 36 | else: 37 | if self._isfirsttag: 38 | self._isfirsttag = False 39 | else: 40 | self.out = self.getdata() 41 | self._tagoffset = len(self.out) 42 | self.out += bytearray([0] * self._tagsize) 43 | self._curbit = self._maxbit 44 | self._tag = 0 45 | 46 | if value: 47 | self._tag |= (1 << self._curbit) 48 | 49 | def write_bitstring(self, s): 50 | """write a string of bits""" 51 | for c in s: 52 | self.write_bit(0 if c == "0" else 1) 53 | 54 | def write_byte(self, b): 55 | self.out.append(b) 56 | 57 | def write_fixednumber(self, value, nbbit): 58 | """write a value on a fixed range of bits""" 59 | for i in range(nbbit - 1, -1, -1): 60 | self.write_bit( (value >> i) & 1) 61 | 62 | def write_variablenumber(self, value): 63 | length = getbinlen(value) - 2 # the highest bit is 1 64 | self.write_bit(value & (1 << length)) 65 | for i in range(length - 1, -1, -1): 66 | self.write_bit(1) 67 | self.write_bit(value & (1 << i)) 68 | self.write_bit(0) 69 | 70 | def _literal(self, marker=True): 71 | if marker: 72 | self.write_bit(0) 73 | self.write_byte(self._in[self._offset]) 74 | self._offset += 1 75 | self._pair = True 76 | 77 | def _block(self, offset, length): 78 | self.write_bitstring("10") 79 | 80 | # if the last operations were literal or single byte 81 | # and the offset is unchanged since the last block copy 82 | # we can just store a 'null' offset and the length 83 | if self._pair and self._lastoffset == offset: 84 | self.write_variablenumber(2) 85 | self.write_variablenumber(length) 86 | else: 87 | high = (offset >> 8) + 2 88 | if self._pair: 89 | high += 1 90 | self.write_variablenumber(high) 91 | low = offset & 0xFF 92 | self.write_byte(low) 93 | self.write_variablenumber(length - lengthdelta(offset)) 94 | self._offset += length 95 | self._lastoffset = offset 96 | self._pair = False 97 | 98 | def _shortblock(self, offset, length): 99 | self.write_bitstring("110") 100 | b = (offset << 1 ) + (length - 2) 101 | self.write_byte(b) 102 | self._offset += length 103 | self._lastoffset = offset 104 | self._pair = False 105 | 106 | def _singlebyte(self, offset): 107 | self.write_bitstring("111") 108 | self.write_fixednumber(offset, 4) 109 | self._offset += 1 110 | self._pair = True 111 | 112 | def _end(self): 113 | self.write_bitstring("110") 114 | self.write_byte(0) 115 | 116 | def do(self): 117 | self._literal(False) 118 | while self._offset < len(self._in): 119 | offset, length = find_longest_match(self._in[:self._offset], self._in[self._offset:]) 120 | if length == 0: 121 | c = self._in[self._offset] 122 | if c == 0: 123 | self._singlebyte(0) 124 | else: 125 | self._literal() 126 | elif length == 1 and 0 <= offset < 16: 127 | self._singlebyte(offset) 128 | elif 2 <= length <= 3 and 0 < offset <= 127: 129 | self._shortblock(offset, length) 130 | elif 3 <= length and 2 <= offset: 131 | self._block(offset, length) 132 | else: 133 | self._literal() 134 | self._end() 135 | return self.getdata() 136 | 137 | class aplib_decompress: 138 | def __init__(self, data): 139 | self._curbit = 0 140 | self._offset = 0 141 | self._tag = None 142 | self._tagsize = 1 143 | self._in = data 144 | self.out = bytearray() 145 | 146 | self._pair = True # paired sequence 147 | self._lastoffset = 0 148 | self._functions = [self._literal, self._block, self._shortblock, self._singlebyte] 149 | 150 | def read_bit(self): 151 | """read next bit from the stream, reloads the tag if necessary""" 152 | if self._curbit != 0: 153 | self._curbit -= 1 154 | else: 155 | self._curbit = (self._tagsize * 8) - 1 156 | self._tag = self.read_byte() 157 | for i in range(self._tagsize - 1): 158 | self._tag += self.read_byte() << (8 * (i + 1)) 159 | 160 | bit = (self._tag >> ((self._tagsize * 8) - 1)) & 0x01 161 | self._tag <<= 1 162 | return bit 163 | 164 | def is_end(self): 165 | return self._offset == len(self._in) and self._curbit == 1 166 | 167 | def read_byte(self): 168 | """read next byte from the stream""" 169 | result = self._in[self._offset] 170 | self._offset += 1 171 | return result 172 | 173 | def read_fixednumber(self, nbbit, init=0): 174 | """reads a fixed bit-length number""" 175 | result = init 176 | for i in range(nbbit): 177 | result = (result << 1) + self.read_bit() 178 | return result 179 | 180 | def read_variablenumber(self): 181 | """return a variable bit-length number x, x >= 2 182 | 183 | reads a bit until the next bit in the pair is not set""" 184 | result = 1 185 | result = (result << 1) + self.read_bit() 186 | while self.read_bit(): 187 | result = (result << 1) + self.read_bit() 188 | return result 189 | 190 | def read_setbits(self): 191 | """read bits as long as their set or a maximum is reached""" 192 | result = 0 193 | while result < 3 and self.read_bit() == 1: 194 | result += 1 195 | return result 196 | 197 | def back_copy(self, offset, length=1): 198 | for i in range(length): 199 | self.out.append(self.out[-offset]) 200 | 201 | def read_literal(self, value=None): 202 | if value is None: 203 | self.out.append(self.read_byte()) 204 | else: 205 | self.out.append(value) 206 | 207 | def _literal(self): 208 | self.read_literal() 209 | self._pair = True 210 | return False 211 | 212 | def _block(self): 213 | b = self.read_variablenumber() # 2- 214 | if b == 2 and self._pair : # reuse the same offset 215 | offset = self._lastoffset 216 | length = self.read_variablenumber() # 2- 217 | else: 218 | high = b - 2 # 0- 219 | if self._pair: 220 | high -= 1 221 | offset = (high << 8) + self.read_byte() 222 | length = self.read_variablenumber() # 2- 223 | length += lengthdelta(offset) 224 | self._lastoffset = offset 225 | self.back_copy(offset, length) 226 | self._pair = False 227 | return False 228 | 229 | def _shortblock(self): 230 | b = self.read_byte() 231 | if b <= 1: # likely 0 232 | return True 233 | length = 2 + (b & 0x01) # 2-3 234 | offset = b >> 1 # 1-127 235 | self.back_copy(offset, length) 236 | self._lastoffset = offset 237 | self._pair = False 238 | return False 239 | 240 | def _singlebyte(self): 241 | offset = self.read_fixednumber(4) # 0-15 242 | if offset: 243 | self.back_copy(offset) 244 | else: 245 | self.read_literal(0) 246 | self._pair = True 247 | 248 | def do(self): 249 | """returns decompressed buffer and consumed bytes counter""" 250 | self.read_literal() 251 | while True: 252 | if self._functions[self.read_setbits()](): 253 | break 254 | return self.out 255 | 256 | def find_longest_match(s, sub): 257 | """returns the number of byte to look backward and the length of byte to copy)""" 258 | if len(sub) == 0: 259 | return 0, 0 260 | limit = len(s) 261 | dic = bytearray(s) 262 | l = 0 263 | offset = 0 264 | length = 0 265 | first = 0 266 | word = bytearray() 267 | word.append(sub[l]) 268 | pos = dic.rfind(word, 0, limit + 1) 269 | if pos == -1: 270 | return offset, length 271 | 272 | offset = limit - pos 273 | length = len(word) 274 | dic.append(sub[l]) 275 | 276 | while l < len(sub) - 1: 277 | l += 1 278 | word.append(sub[l]) 279 | 280 | pos = dic.rfind(word, 0, limit + 1) 281 | if pos == -1: 282 | return offset, length 283 | offset = limit - pos 284 | length = len(word) 285 | dic.append(sub[l]) 286 | return offset, length 287 | 288 | def int2lebin(value, size): 289 | """ouputs value in binary, as little-endian""" 290 | result = bytearray() 291 | for i in range(size): 292 | result.append((value >> (8 * i)) & 0xFF) 293 | return result 294 | 295 | def modifybytearray(s, sub, offset): 296 | """overwrites 'sub' at 'offset' of 's'""" 297 | return s[:offset] + sub + s[offset + len(sub):] 298 | 299 | def getbinlen(value): 300 | """return the bit length of an integer""" 301 | result = 0 302 | if value == 0: 303 | return 1 304 | while value != 0: 305 | value >>= 1 306 | result += 1 307 | return result 308 | 309 | def lengthdelta(offset): 310 | l = 0 311 | if offset >= 32000: 312 | l += 1 313 | if offset >= 1280: 314 | l += 1 315 | if offset < 128: 316 | l += 2 317 | return l 318 | -------------------------------------------------------------------------------- /snes2asm/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import sys 5 | import re 6 | import logging 7 | import argparse 8 | 9 | from snes2asm.disassembler import Disassembler 10 | from snes2asm.cartridge import Cartridge 11 | from snes2asm.project_maker import ProjectMaker 12 | from snes2asm.configurator import Configurator 13 | from snes2asm.decoder import Headers 14 | from snes2asm.tile import * 15 | from snes2asm.bitmap import BitmapIndex 16 | from snes2asm import compression 17 | from snes2asm import brr 18 | 19 | # Depth needed for branch tracing 20 | sys.setrecursionlimit(3000) 21 | 22 | def main(argv=None): 23 | parser = argparse.ArgumentParser( prog="snes2asm", description='Disassembles snes cartridges into practical projects', epilog='') 24 | parser.add_argument('input', metavar='snes.sfc', help="input snes file") 25 | parser.add_argument('-v', '--verbose', action='store_true', default=None, help="Verbose output") 26 | parser.add_argument('-o', '--output-dir', default='.', help="File path to output project") 27 | parser.add_argument('-c', '--config', default=None, help="Path to decoding configuration yaml file") 28 | parser.add_argument('-b', '--banks', nargs='+', type=int, help='Code banks to disassemble. Default is auto-detect') 29 | parser.add_argument('-hi', '--hirom', action='store_true', default=None, help="Force HiROM") 30 | parser.add_argument('-lo', '--lorom', action='store_true', default=None, help="Force LoROM") 31 | parser.add_argument('-f', '--fastrom', action='store_true', default=None, help="Force fast ROM addressing") 32 | parser.add_argument('-s', '--slowrom', action='store_true', default=None, help="Force slow ROM addressing") 33 | parser.add_argument('-nl', '--nolabel', action='store_true', default=None, help="Use addresses instead of labels") 34 | parser.add_argument('-e', '--empty-fill', default=255, help="Default byte value for fill empty ROM space") 35 | parser.add_argument('-x', '--hex', action='store_true', default=None, help="Comments show instruction hex") 36 | 37 | args = parser.parse_args(argv[1:]) 38 | 39 | if args.input: 40 | exec_asm(args) 41 | else: 42 | parser.print_help() 43 | 44 | def exec_asm(options): 45 | cart = Cartridge(options.__dict__) 46 | cart.open(options.input) 47 | 48 | disasm = Disassembler(cart, options) 49 | disasm.add_decoder(Headers(cart.header,cart.header+80)) 50 | 51 | if options.banks: 52 | disasm.code_banks = options.banks 53 | 54 | if options.config: 55 | configurator = Configurator(options.config) 56 | try: 57 | configurator.apply(disasm) 58 | except ValueError as e: 59 | print("Error: %s" % str(e)) 60 | sys.exit(-1) 61 | 62 | disasm.run() 63 | 64 | project = ProjectMaker(cart, disasm) 65 | project.output(options.output_dir) 66 | 67 | def main_gui(argv=None): 68 | from PyQt5.QtWidgets import QApplication 69 | from snes2asm.gui.application import App 70 | from snes2asm.gui.widget import Window 71 | 72 | parser = argparse.ArgumentParser( prog="snes2asm_gui", description='Disassembles snes cartridges into practical projects', epilog='') 73 | 74 | args = parser.parse_args(argv[1:]) 75 | 76 | if args.input: 77 | qapp = QApplication(sys.argv) 78 | app = App(options) 79 | window = Window(app) 80 | window.show() 81 | sys.exit(qapp.exec_()) 82 | else: 83 | parser.print_help() 84 | 85 | def bmp2chr(argv=None): 86 | parser = argparse.ArgumentParser( prog="bmp2chr", description='Convert an indexed bitmap to SNES CHR data', epilog='') 87 | parser.add_argument('input', metavar='input.bmp', help="input bitmap file") 88 | parser.add_argument('-o', '--output', required=True, default=None, help="File path to output *.chr") 89 | parser.add_argument('-b2', '--b2pp', action='store_true', default=False, help="4 colors planar graphic output") 90 | parser.add_argument('-b3', '--b3pp', action='store_true', default=False, help="8 colors planar graphic output") 91 | parser.add_argument('-b4', '--b4pp', action='store_true', default=True, help="16 colors planar graphic output") 92 | parser.add_argument('-b8', '--b8pp', action='store_true', default=False, help="256 colors planar graphic output") 93 | parser.add_argument('-l2', '--linear2', action='store_true', default=False, help="4 colors linear graphic output") 94 | parser.add_argument('-l4', '--linear4', action='store_true', default=True, help="16 colors linear graphic output") 95 | parser.add_argument('-l8', '--linear8', action='store_true', default=False, help="256 colors linear graphic output") 96 | parser.add_argument('-p', '--palette', action='store_true', default=False, help="Output color *.pal file") 97 | parser.add_argument('-f', '--fullsize', action='store_true', default=False, help="Ignore destination CHR file size and write whole bitmap") 98 | args = parser.parse_args(argv[1:]) 99 | if args.input: 100 | try: 101 | b = BitmapIndex.read(args.input) 102 | except Exception as e: 103 | print("Error: %s" % str(e)) 104 | return -1 105 | 106 | if args.b2pp: 107 | encode = Encode2bppTile 108 | depth = 2 109 | elif args.b8pp: 110 | encode = Encode8bppTile 111 | depth = 8 112 | elif args.linear8: 113 | encode = EncodeLinear8Tile 114 | depth = 8 115 | elif args.linear4: 116 | encode = EncodeLinear4Tile 117 | depth = 4 118 | elif args.linear2: 119 | encode = EncodeLinear2Tile 120 | depth = 2 121 | elif args.b3pp: 122 | encode = Encode3bppTile 123 | depth = 3 124 | else: 125 | encode = Encode4bppTile 126 | depth = 4 127 | 128 | if depth != b._bcBitCount: 129 | print("Error: Bitmap file %s does not have a bit depth of %d" % (args.input, depth)) 130 | return -1 131 | 132 | if b._bcWidth % 8 != 0 or b._bcHeight % 8 != 0: 133 | print("Error: Bitmap file %s does not have multiple tile dimensions of 8x8" % args.input) 134 | return -1 135 | 136 | # For odd shaped bitmaps match the number of tiles in the destination chr file by limiting the size 137 | if os.path.isfile(args.output) and not args.fullsize: 138 | max_size = os.path.getsize(args.output) 139 | else: 140 | max_size = b._bcBitCount * b._bcWidth * b._bcHeight 141 | 142 | try: 143 | chr_fp = open(args.output, "wb") 144 | except Exception as e: 145 | print("Error: %s" % str(e)) 146 | return -1 147 | 148 | # Write tile data 149 | running = True 150 | for ty in range(0, b._bcHeight, 8): 151 | for tx in range(0, b._bcWidth, 8): 152 | tile = bytearray() 153 | for y in range(ty, ty+8): 154 | for x in range(tx, tx+8): 155 | tile.append(b.getPixel(x, y)) 156 | chr_fp.write(encode(tile)) 157 | if chr_fp.tell() >= max_size: 158 | running = False 159 | break 160 | 161 | if not running: 162 | break 163 | chr_fp.close() 164 | else: 165 | parser.print_help() 166 | return 0 167 | 168 | def packer(argv=None): 169 | parser = argparse.ArgumentParser( prog="packer", description='Encode and decode files with compression', epilog='') 170 | parser.add_argument('action', metavar='pack|unpack', help="Action type") 171 | parser.add_argument('input', metavar='input.bin', help="Input file") 172 | parser.add_argument('-o', '--output', required=True, metavar='outfile', default=None, help="File path to output") 173 | parser.add_argument('-x', '--encoding', metavar='|'.join(compression.get_names()), required=True, type=str, help='Encoding algorithm') 174 | parser.add_argument('-f', '--fullsize', action='store_true', default=False, help="Ignore destination file size and write full data") 175 | 176 | args = parser.parse_args(argv[1:]) 177 | 178 | if not args.action or args.action not in ['pack', 'unpack']: 179 | parser.print_help() 180 | return 0 181 | 182 | if args.input: 183 | try: 184 | in_fp = open(args.input, "rb") 185 | data = bytearray(in_fp.read()) 186 | in_fp.close() 187 | except Exception as e: 188 | print("Error: %s" % str(e)) 189 | return -1 190 | 191 | try: 192 | module = getattr(compression, args.encoding) 193 | except AttributeError: 194 | print("Unsupported encoding type: %s. Use following types %s." % (args.encoding, ",".join(compression.get_names()))) 195 | return -1 196 | 197 | if args.action == 'pack': 198 | output = module.compress(data) 199 | else: 200 | output = module.decompress(data) 201 | 202 | # If target file already exists then regulate output size 203 | if os.path.isfile(args.output): 204 | size = os.path.getsize(args.output) 205 | if not args.fullsize and size > 0: 206 | if size > len(output): 207 | output = output + bytes(size - len(output)) 208 | elif size < len(output): 209 | print("Warning: Truncating output of compression for file %s" % args.output) 210 | output = output[0:size] 211 | try: 212 | out_fp = open(args.output, "wb") 213 | out_fp.write(output) 214 | out_fp.close() 215 | except Exception as e: 216 | print("Error: %s" % str(e)) 217 | return -1 218 | else: 219 | parser.print_help() 220 | return 0 221 | 222 | 223 | def brr_cli(argv=None): 224 | parser = argparse.ArgumentParser( prog="brr", description='Encode and decode snes audio samples', epilog='') 225 | parser.add_argument('action', metavar='encode|decode', help="Action type") 226 | parser.add_argument('input', metavar='inputfile', help="Input file") 227 | parser.add_argument('-o', '--output', required=True, metavar='outfile', default=None, help="File path to output") 228 | 229 | args = parser.parse_args(argv[1:]) 230 | 231 | if not args.action or args.action not in ['encode', 'decode']: 232 | parser.print_help() 233 | return 0 234 | 235 | if args.action == "encode" and not args.input.endswith(".wav"): 236 | parser.print_help() 237 | print("Input file must be a wav") 238 | return 0 239 | 240 | if args.action == "decode" and not args.input.endswith(".brr"): 241 | parser.print_help() 242 | print("Input file must be a brr") 243 | return 0 244 | 245 | 246 | try: 247 | in_fp = open(args.input, "rb") 248 | data = bytearray(in_fp.read()) 249 | in_fp.close() 250 | 251 | # Decode 252 | if args.action == "decode": 253 | output = brr.decode(data) 254 | 255 | # Encode 256 | else: 257 | output = brr.encode(data) 258 | 259 | # Write output to file 260 | out_fp = open(args.output, "wb") 261 | out_fp.write(output) 262 | out_fp.close() 263 | 264 | print("Successfully %sd to output %s" % (args.action, args.output)) 265 | 266 | except Exception as e: 267 | print("Error: %s" % str(e)) 268 | return -1 269 | 270 | -------------------------------------------------------------------------------- /snes2asm/tile.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # 1111-0000 = 0101-0101 4 | # 0000-1111 = 1010-1010 5 | TileBitplaneDecodeLut = [ 6 | 0x00, 0x01, 0x04, 0x05, 0x10, 0x11, 0x14, 0x15, 0x40, 0x41, 0x44, 0x45, 0x50, 0x51, 0x54, 0x55, 7 | 0x02, 0x03, 0x06, 0x07, 0x12, 0x13, 0x16, 0x17, 0x42, 0x43, 0x46, 0x47, 0x52, 0x53, 0x56, 0x57, 8 | 0x08, 0x09, 0x0C, 0x0D, 0x18, 0x19, 0x1C, 0x1D, 0x48, 0x49, 0x4C, 0x4D, 0x58, 0x59, 0x5C, 0x5D, 9 | 0x0A, 0x0B, 0x0E, 0x0F, 0x1A, 0x1B, 0x1E, 0x1F, 0x4A, 0x4B, 0x4E, 0x4F, 0x5A, 0x5B, 0x5E, 0x5F, 10 | 0x20, 0x21, 0x24, 0x25, 0x30, 0x31, 0x34, 0x35, 0x60, 0x61, 0x64, 0x65, 0x70, 0x71, 0x74, 0x75, 11 | 0x22, 0x23, 0x26, 0x27, 0x32, 0x33, 0x36, 0x37, 0x62, 0x63, 0x66, 0x67, 0x72, 0x73, 0x76, 0x77, 12 | 0x28, 0x29, 0x2C, 0x2D, 0x38, 0x39, 0x3C, 0x3D, 0x68, 0x69, 0x6C, 0x6D, 0x78, 0x79, 0x7C, 0x7D, 13 | 0x2A, 0x2B, 0x2E, 0x2F, 0x3A, 0x3B, 0x3E, 0x3F, 0x6A, 0x6B, 0x6E, 0x6F, 0x7A, 0x7B, 0x7E, 0x7F, 14 | 0x80, 0x81, 0x84, 0x85, 0x90, 0x91, 0x94, 0x95, 0xC0, 0xC1, 0xC4, 0xC5, 0xD0, 0xD1, 0xD4, 0xD5, 15 | 0x82, 0x83, 0x86, 0x87, 0x92, 0x93, 0x96, 0x97, 0xC2, 0xC3, 0xC6, 0xC7, 0xD2, 0xD3, 0xD6, 0xD7, 16 | 0x88, 0x89, 0x8C, 0x8D, 0x98, 0x99, 0x9C, 0x9D, 0xC8, 0xC9, 0xCC, 0xCD, 0xD8, 0xD9, 0xDC, 0xDD, 17 | 0x8A, 0x8B, 0x8E, 0x8F, 0x9A, 0x9B, 0x9E, 0x9F, 0xCA, 0xCB, 0xCE, 0xCF, 0xDA, 0xDB, 0xDE, 0xDF, 18 | 0xA0, 0xA1, 0xA4, 0xA5, 0xB0, 0xB1, 0xB4, 0xB5, 0xE0, 0xE1, 0xE4, 0xE5, 0xF0, 0xF1, 0xF4, 0xF5, 19 | 0xA2, 0xA3, 0xA6, 0xA7, 0xB2, 0xB3, 0xB6, 0xB7, 0xE2, 0xE3, 0xE6, 0xE7, 0xF2, 0xF3, 0xF6, 0xF7, 20 | 0xA8, 0xA9, 0xAC, 0xAD, 0xB8, 0xB9, 0xBC, 0xBD, 0xE8, 0xE9, 0xEC, 0xED, 0xF8, 0xF9, 0xFC, 0xFD, 21 | 0xAA, 0xAB, 0xAE, 0xAF, 0xBA, 0xBB, 0xBE, 0xBF, 0xEA, 0xEB, 0xEE, 0xEF, 0xFA, 0xFB, 0xFE, 0xFF 22 | ] 23 | 24 | TileBitplaneEncodeLut = [ 25 | 0x00, 0x01, 0x10, 0x11, 0x02, 0x03, 0x12, 0x13, 0x20, 0x21, 0x30, 0x31, 0x22, 0x23, 0x32, 0x33, 26 | 0x04, 0x05, 0x14, 0x15, 0x06, 0x07, 0x16, 0x17, 0x24, 0x25, 0x34, 0x35, 0x26, 0x27, 0x36, 0x37, 27 | 0x40, 0x41, 0x50, 0x51, 0x42, 0x43, 0x52, 0x53, 0x60, 0x61, 0x70, 0x71, 0x62, 0x63, 0x72, 0x73, 28 | 0x44, 0x45, 0x54, 0x55, 0x46, 0x47, 0x56, 0x57, 0x64, 0x65, 0x74, 0x75, 0x66, 0x67, 0x76, 0x77, 29 | 0x08, 0x09, 0x18, 0x19, 0x0A, 0x0B, 0x1A, 0x1B, 0x28, 0x29, 0x38, 0x39, 0x2A, 0x2B, 0x3A, 0x3B, 30 | 0x0C, 0x0D, 0x1C, 0x1D, 0x0E, 0x0F, 0x1E, 0x1F, 0x2C, 0x2D, 0x3C, 0x3D, 0x2E, 0x2F, 0x3E, 0x3F, 31 | 0x48, 0x49, 0x58, 0x59, 0x4A, 0x4B, 0x5A, 0x5B, 0x68, 0x69, 0x78, 0x79, 0x6A, 0x6B, 0x7A, 0x7B, 32 | 0x4C, 0x4D, 0x5C, 0x5D, 0x4E, 0x4F, 0x5E, 0x5F, 0x6C, 0x6D, 0x7C, 0x7D, 0x6E, 0x6F, 0x7E, 0x7F, 33 | 0x80, 0x81, 0x90, 0x91, 0x82, 0x83, 0x92, 0x93, 0xA0, 0xA1, 0xB0, 0xB1, 0xA2, 0xA3, 0xB2, 0xB3, 34 | 0x84, 0x85, 0x94, 0x95, 0x86, 0x87, 0x96, 0x97, 0xA4, 0xA5, 0xB4, 0xB5, 0xA6, 0xA7, 0xB6, 0xB7, 35 | 0xC0, 0xC1, 0xD0, 0xD1, 0xC2, 0xC3, 0xD2, 0xD3, 0xE0, 0xE1, 0xF0, 0xF1, 0xE2, 0xE3, 0xF2, 0xF3, 36 | 0xC4, 0xC5, 0xD4, 0xD5, 0xC6, 0xC7, 0xD6, 0xD7, 0xE4, 0xE5, 0xF4, 0xF5, 0xE6, 0xE7, 0xF6, 0xF7, 37 | 0x88, 0x89, 0x98, 0x99, 0x8A, 0x8B, 0x9A, 0x9B, 0xA8, 0xA9, 0xB8, 0xB9, 0xAA, 0xAB, 0xBA, 0xBB, 38 | 0x8C, 0x8D, 0x9C, 0x9D, 0x8E, 0x8F, 0x9E, 0x9F, 0xAC, 0xAD, 0xBC, 0xBD, 0xAE, 0xAF, 0xBE, 0xBF, 39 | 0xC8, 0xC9, 0xD8, 0xD9, 0xCA, 0xCB, 0xDA, 0xDB, 0xE8, 0xE9, 0xF8, 0xF9, 0xEA, 0xEB, 0xFA, 0xFB, 40 | 0xCC, 0xCD, 0xDC, 0xDD, 0xCE, 0xCF, 0xDE, 0xDF, 0xEC, 0xED, 0xFC, 0xFD, 0xEE, 0xEF, 0xFE, 0xFF 41 | ] 42 | 43 | def Decode2Byte(b1, b2): 44 | t1 = ((b2 & 0x0F) << 4) | (b1 & 0x0F) 45 | t2 = ((b1 & 0xF0) >> 4) | (b2 & 0xF0) 46 | hi = TileBitplaneDecodeLut[t1] 47 | lo = TileBitplaneDecodeLut[t2] 48 | return hi,lo 49 | 50 | def Encode2Byte(b1, b2): 51 | t1 = TileBitplaneEncodeLut[b1] 52 | t2 = TileBitplaneEncodeLut[b2] 53 | hi = ((t1 & 0x0F) << 4) | (t2 & 0x0F) 54 | lo = ((t2 & 0xF0) >> 4) | (t1 & 0xF0) 55 | return hi,lo 56 | 57 | def Encode2bppTile(tile): 58 | data = bytearray(16) 59 | for line in range(0, 16, 2): 60 | offset = line * 4 61 | p1 = tile[offset+0] << 6 | tile[offset+1] << 4 | tile[offset+2] << 2 | tile[offset+3] 62 | p2 = tile[offset+4] << 6 | tile[offset+5] << 4 | tile[offset+6] << 2 | tile[offset+7] 63 | data[line], data[line+1] = Encode2Byte(p1, p2) 64 | return data 65 | 66 | def Encode3bppTile(tile): 67 | 68 | data = bytearray(24) 69 | for line in range(0, 16, 2): 70 | offset = line * 4 71 | p1 = (tile[offset+0] & 0x3) << 6 | (tile[offset+1] & 0x3) << 4 | (tile[offset+2] & 0x3) << 2 | (tile[offset+3] & 0x3) 72 | p2 = (tile[offset+4] & 0x3) << 6 | (tile[offset+5] & 0x3) << 4 | (tile[offset+6] & 0x3) << 2 | (tile[offset+7] & 0x3) 73 | hi, lo = Encode2Byte(p1, p2) 74 | data[line], data[line+1] = hi, lo 75 | 76 | # Add the extra 3rd bit 77 | for x in range(0,8): 78 | row = 0 79 | for y in range(0,8): 80 | pix = tile[x*8+y] 81 | row |= ((pix & 0x4) >> 2) << (7 - y) 82 | data[16+x] = row 83 | 84 | return data 85 | 86 | def Encode4bppTile(tile): 87 | data = bytearray(32) 88 | for line in range(0, 16, 2): 89 | offset = line * 4 90 | p1 = ((tile[offset+0] & 0x3) << 6) | ((tile[offset+1] & 0x3) << 4) | ((tile[offset+2] & 0x3) << 2) | (tile[offset+3] & 0x3) 91 | p2 = ((tile[offset+0] & 0xC) << 4) | ((tile[offset+1] & 0xC) << 2) | (tile[offset+2] & 0xC) | ((tile[offset+3] & 0xC) >> 2) 92 | p3 = (tile[offset+4] & 0x3) << 6 | (tile[offset+5] & 0x3) << 4 | (tile[offset+6] & 0x3) << 2 | tile[offset+7] & 0x3 93 | p4 = (tile[offset+4] & 0xC) << 4 | (tile[offset+5] & 0xC) << 2 | (tile[offset+6] & 0xC) | (tile[offset+7] & 0xC) >> 2 94 | data[line], data[line+1] = Encode2Byte(p1, p3) 95 | data[line+16], data[line+17] = Encode2Byte(p2, p4) 96 | return data 97 | 98 | def Encode8bppTile(tile): 99 | data = bytearray(64) 100 | for line in range(0, 16, 2): 101 | offset = line * 4 102 | p1 = (tile[offset+0] & 0x3) << 6 | (tile[offset+1] & 0x3) << 4 | (tile[offset+2] & 0x3) << 2 | tile[offset+3] & 0x3 103 | p2 = (tile[offset+0] & 0xC) << 4 | (tile[offset+1] & 0xC) << 2 | (tile[offset+2] & 0xC) | (tile[offset+3] & 0xC) >> 2 104 | p3 = (tile[offset+4] & 0x3) << 6 | (tile[offset+5] & 0x3) << 4 | (tile[offset+6] & 0x3) << 2 | tile[offset+7] & 0x3 105 | p4 = (tile[offset+4] & 0xC) << 4 | (tile[offset+5] & 0xC) << 2 | (tile[offset+6] & 0xC) | (tile[offset+7] & 0xC) >> 2 106 | data[line], data[line+1] = Encode2Byte(p1, p3) 107 | data[line+16], data[line+17] = Encode2Byte(p2, p4) 108 | 109 | p5 = (tile[offset+0] & 0x30) << 2 | (tile[offset+1] & 0x30) | (tile[offset+2] & 0x30) >> 2 | (tile[offset+3] & 0x30) >> 4 110 | p6 = (tile[offset+0] & 0xC0) | (tile[offset+1] & 0xC0) >> 2 | (tile[offset+2] & 0xC0) >> 4 | (tile[offset+3] & 0xC0) >> 6 111 | p7 = (tile[offset+4] & 0x30) << 2 | (tile[offset+5] & 0x30) | (tile[offset+6] & 0x30) >> 2 | (tile[offset+7] & 0x30) >> 4 112 | p8 = (tile[offset+4] & 0xC0) | (tile[offset+5] & 0xC0) >> 2 | (tile[offset+6] & 0xC0) >> 4 | (tile[offset+7] & 0xC0) >> 6 113 | data[line+32], data[line+33] = Encode2Byte(p5, p7) 114 | data[line+48], data[line+49] = Encode2Byte(p6, p8) 115 | return data 116 | 117 | def EncodeLinear2Tile(tile): 118 | data = bytearray(0) 119 | for i in range(0, 16): 120 | c = tile[i] 121 | tile.append(c & 0x3) 122 | tile.append((c & 0xC) >> 2) 123 | tile.append((c & 0x30) >> 4) 124 | tile.append((c & 0xC0) >> 6) 125 | return data 126 | 127 | def EncodeLinear4Tile(tile): 128 | return bytearray(tile) 129 | 130 | def EncodeLinear8Tile(tile): 131 | return bytearray(tile) 132 | 133 | def Decode2bppTile(data): 134 | tile = bytearray(0) 135 | for line in range(0, 16, 2): 136 | # Single bitplane 137 | a1, b1 = Decode2Byte(data[line], data[line+1]) 138 | tile.append((b1 & 0xC0) >> 6) 139 | tile.append((b1 & 0x30) >> 4) 140 | tile.append((b1 & 0x0C) >> 2) 141 | tile.append(b1 & 0x03) 142 | tile.append((a1 & 0xC0) >> 6) 143 | tile.append((a1 & 0x30) >> 4) 144 | tile.append((a1 & 0x0C) >> 2) 145 | tile.append(a1 & 0x03) 146 | return tile 147 | 148 | def Decode3bppTile(data): 149 | tile = Decode2bppTile(data) 150 | 151 | # Add the extra 3rd bit 152 | for x in range(0,8): 153 | b = data[16+x] 154 | for y in range(0,8): 155 | tile[x*8+y] |= ((b >> (7 - y)) & 1) << 2 156 | return tile 157 | 158 | def Decode4bppTile(data): 159 | tile = bytearray(0) 160 | for line in range(0, 16, 2): 161 | # Double bitplane 162 | a1, b1 = Decode2Byte(data[line], data[line+1]) 163 | a2, b2 = Decode2Byte(data[line+16], data[line+17]) 164 | 165 | tile.append( (b1 & 0xC0) >> 6 | (b2 & 0xC0) >> 4) 166 | tile.append( (b1 & 0x30) >> 4 | (b2 & 0x30) >> 2 ) 167 | tile.append( (b1 & 0x0C) >> 2 | (b2 & 0x0C) ) 168 | tile.append( (b1 & 0x03) | (b2 & 0x03) << 2 ) 169 | tile.append( (a1 & 0xC0) >> 6 | (a2 & 0xC0) >> 4) 170 | tile.append( (a1 & 0x30) >> 4 | (a2 & 0x30) >> 2 ) 171 | tile.append( (a1 & 0x0C) >> 2 | (a2 & 0x0C) ) 172 | tile.append( (a1 & 0x03) | (a2 & 0x03) << 2 ) 173 | return tile 174 | 175 | def Decode8bppTile(data): 176 | tile = bytearray(0) 177 | for line in range(0, 16, 2): 178 | # Quad bitplane 179 | a1, b1 = Decode2Byte(data[line], data[line+1]) 180 | a2, b2 = Decode2Byte(data[line+16], data[line+17]) 181 | a3, b3 = Decode2Byte(data[line+32], data[line+33]) 182 | a4, b4 = Decode2Byte(data[line+48], data[line+49]) 183 | 184 | tile.append( (b1 & 0xC0) >> 6 | (b2 & 0xC0) >> 4 | (b3 & 0xC0) >> 2 | (b4 & 0xC0) ) 185 | tile.append( (b1 & 0x30) >> 4 | (b2 & 0x30) >> 2 | (b3 & 0x30) | (b4 & 0x30) << 2 ) 186 | tile.append( (b1 & 0x0C) >> 2 | (b2 & 0x0C) | (b3 & 0x0C) << 2 | (b4 & 0x0C) << 4 ) 187 | tile.append( (b1 & 0x03) | (b2 & 0x03) << 2 | (b3 & 0x03) << 4 | (b4 & 0x03) << 6 ) 188 | 189 | tile.append( (a1 & 0xC0) >> 6 | (a2 & 0xC0) >> 4 | (a3 & 0xC0) >> 2 | (a4 & 0xC0) ) 190 | tile.append( (a1 & 0x30) >> 4 | (a2 & 0x30) >> 2 | (a3 & 0x30) | (a4 & 0x30) << 2 ) 191 | tile.append( (a1 & 0x0C) >> 2 | (a2 & 0x0C) | (a3 & 0x0C) << 2 | (a4 & 0x0C) << 4 ) 192 | tile.append( (a1 & 0x03) | (a2 & 0x03) << 2 | (a3 & 0x03) << 4 | (a4 & 0x03) << 6 ) 193 | 194 | return tile 195 | 196 | def DecodeLinear2Tile(data): 197 | tile = bytearray(0) 198 | for i in range(0, 16): 199 | c = data[i] 200 | tile.append(c & 0x3) 201 | tile.append((c & 0xC) >> 2) 202 | tile.append((c & 0x30) >> 4) 203 | tile.append((c & 0xC0) >> 6) 204 | return tile 205 | 206 | def DecodeLinear4Tile(data): 207 | tile = bytearray(0) 208 | for i in range(0, 32): 209 | c = data[i] 210 | tile.append(c & 0xF) 211 | tile.append((c & 0xF0) >> 4) 212 | return tile 213 | 214 | def DecodeLinear8Tile(data): 215 | return bytearray(data) 216 | 217 | def DecodeMode7Tile(data): 218 | #TODO 219 | return bytearray(data) 220 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021-2022 Nathan Cassano 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /snes2asm/template/snes.asm: -------------------------------------------------------------------------------- 1 | ;SNES register definitions 2 | 3 | ;PPU regs 4 | .define INIDSP $2100 ;Screen Display 5 | .define INIDSP_FORCE_BLANK 1 << 7 6 | .define INIDSP_BRIGHTNESS $f << 0 7 | 8 | .define OBJSEL $2101 ;Object Size and Chr Address 9 | .define OBJSEL_SPRITES_8X8_16X16 0 << 5 10 | .define OBJSEL_SPRITES_8X8_32X32 1 << 5 11 | .define OBJSEL_SPRITES_8X8_64X64 2 << 5 12 | .define OBJSEL_SPRITES_16X16_32X32 3 << 5 13 | .define OBJSEL_SPRITES_16X16_64X64 4 << 5 14 | .define OBJSEL_SPRITES_32X32_64X64 5 << 5 15 | .define OBJSEL_SPRITES_16X32_32X64 6 << 5 16 | .define OBJSEL_SPRITES_16X32_32X32 7 << 5 17 | .define OBJSEL_BASE_0000 0 18 | .define OBJSEL_BASE_4000 1 19 | .define OBJSEL_BASE_8000 2 20 | .define OBJSEL_BASE_C000 3 21 | .define OBJSEL_NAME_PLUS_1000 0 << 3 22 | .define OBJSEL_NAME_PLUS_2000 1 << 3 23 | .define OBJSEL_NAME_PLUS_3000 2 << 3 24 | .define OBJSEL_NAME_PLUS_4000 3 << 3 25 | 26 | .define OAMADDL $2102 ;OAM Address 27 | .define OAMADDH $2103 28 | .define OAMADDH_PRIORITY_ACTIVATE 1 << 7 29 | 30 | .define OAMDATA $2104 ;Data for OAM write 31 | 32 | .define BGMODE $2105 ;BG Mode and Character Size 33 | .define BGMODE_CHARSIZE_LAYER_4 1 << 7 34 | .define BGMODE_CHARSIZE_LAYER_3 1 << 6 35 | .define BGMODE_CHARSIZE_LAYER_2 1 << 5 36 | .define BGMODE_CHARSIZE_LAYER_1 1 << 4 37 | .define BGMODE_PRIORITY_LAYER_3 1 << 3 38 | .define BGMODE_MODE_7 7 39 | .define BGMODE_MODE_6 6 40 | .define BGMODE_MODE_5 5 41 | .define BGMODE_MODE_4 4 42 | .define BGMODE_MODE_3 3 43 | .define BGMODE_MODE_2 2 44 | .define BGMODE_MODE_1 1 45 | .define BGMODE_MODE_0 0 46 | 47 | .define MOSAIC $2106 ;Screen Pixelation 48 | .define MOSAIC_LAYER_4 1 << 3 49 | .define MOSAIC_LAYER_3 1 << 2 50 | .define MOSAIC_LAYER_2 1 << 1 51 | .define MOSAIC_LAYER_1 1 << 0 52 | 53 | .define BG1SC $2107 ;BG Tilemap Address and Size 54 | .define BG2SC $2108 55 | .define BG3SC $2109 56 | .define BG4SC $210a 57 | .define BGSC_Y_MIRROR 1 << 1 58 | .define BGSC_X_MIRROR 1 << 0 59 | 60 | .define BG12NBA $210b ;BG Chr(Tiles) Address 61 | .define BG1NBA_0000 0 << 0 62 | .define BG1NBA_2000 1 << 0 63 | .define BG1NBA_4000 2 << 0 64 | .define BG1NBA_6000 3 << 0 65 | .define BG1NBA_8000 4 << 0 66 | .define BG1NBA_A000 5 << 0 67 | .define BG1NBA_C000 6 << 0 68 | .define BG1NBA_E000 7 << 0 69 | 70 | .define BG2NBA_0000 0 << 4 71 | .define BG2NBA_2000 1 << 4 72 | .define BG2NBA_4000 2 << 4 73 | .define BG2NBA_6000 3 << 4 74 | .define BG2NBA_8000 4 << 4 75 | .define BG2NBA_A000 5 << 4 76 | .define BG2NBA_C000 6 << 4 77 | .define BG2NBA_E000 7 << 4 78 | 79 | .define BG34NBA $210c 80 | .define BG3NBA_0000 0 << 0 81 | .define BG3NBA_2000 1 << 0 82 | .define BG3NBA_4000 2 << 0 83 | .define BG3NBA_6000 3 << 0 84 | .define BG3NBA_8000 4 << 0 85 | .define BG3NBA_A000 5 << 0 86 | .define BG3NBA_C000 6 << 0 87 | .define BG3NBA_E000 7 << 0 88 | 89 | .define BG4NBA_0000 0 << 4 90 | .define BG4NBA_2000 1 << 4 91 | .define BG4NBA_4000 2 << 4 92 | .define BG4NBA_6000 3 << 4 93 | .define BG4NBA_8000 4 << 4 94 | .define BG4NBA_A000 5 << 4 95 | .define BG4NBA_C000 6 << 4 96 | .define BG4NBA_E000 7 << 4 97 | 98 | .define BG1HOFS $210d ;BG x-Scroll 99 | .define M7HOFS $210d 100 | .define BG1VOFS $210e ;BG y-Scroll 101 | .define M7VOFS $210e 102 | 103 | .define BG2HOFS $210f 104 | .define BG2VOFS $2110 105 | 106 | .define BG3HOFS $2111 107 | .define BG3VOFS $2112 108 | 109 | .define BG4HOFS $2113 110 | .define BG4VOFS $2114 111 | 112 | .define VMAIN $2115 ;Video Port Control 113 | .define VMAIN_INCREMENT_MODE 1 << 7 114 | .define VMAIN_INCREMENT_1 0 << 0 115 | .define VMAIN_INCREMENT_32 1 << 0 116 | .define VMAIN_INCREMENT_128 2 << 0 117 | 118 | .define VMADDL $2116 ;VRAM Address 119 | .define VMADDH $2117 120 | 121 | .define VMDATAL $2118 ;VRAM Data 122 | .define VMDATAH $2119 123 | 124 | .define M7SEL $211a ;Mode 7 Settings 125 | .define M7SEL_PLAYFIELD_SIZE 1 << 7 126 | .define M7SEL_EMPTY_FILL 1 << 6 127 | .define M7SEL_Y_MIRROR 1 << 1 128 | .define M7SEL_X_MIRROR 1 << 0 129 | 130 | .define M7A $211b ;Mode 7 Matrix 131 | .define M7B $211c 132 | .define M7C $211d 133 | .define M7D $211e 134 | .define M7X $211f ;Mode 7 Center 135 | .define M7Y $2120 136 | 137 | .define CGADD $2121 ;CGRAM Address 138 | .define CGDATA $2122 ;CGRAM Data 139 | 140 | .define W12SEL $2123 ;Window Mask Settings 141 | .define W12SEL_BG2_W2_ENABLE 1 << 7 142 | .define W12SEL_BG2_W2_INVERT 1 << 6 143 | .define W12SEL_BG2_W1_ENABLE 1 << 5 144 | .define W12SEL_BG2_W1_INVERT 1 << 4 145 | .define W12SEL_BG1_W2_ENABLE 1 << 3 146 | .define W12SEL_BG1_W2_INVERT 1 << 2 147 | .define W12SEL_BG1_W1_ENABLE 1 << 1 148 | .define W12SEL_BG1_W1_INVERT 1 << 0 149 | 150 | .define W34SEL $2124 151 | .define W34SEL_BG4_W2_ENABLE 1 << 7 152 | .define W34SEL_BG4_W2_INVERT 1 << 6 153 | .define W34SEL_BG4_W1_ENABLE 1 << 5 154 | .define W34SEL_BG4_W1_INVERT 1 << 4 155 | .define W34SEL_BG3_W2_ENABLE 1 << 3 156 | .define W34SEL_BG3_W2_INVERT 1 << 2 157 | .define W34SEL_BG3_W1_ENABLE 1 << 1 158 | .define W34SEL_BG3_W1_INVERT 1 << 0 159 | 160 | .define WOBJSEL $2125 161 | .define WOBJSEL_COL_W2_ENABLE 1 << 7 162 | .define WOBJSEL_COL_W2_INVERT 1 << 6 163 | .define WOBJSEL_COL_W1_ENABLE 1 << 5 164 | .define WOBJSEL_COL_W1_INVERT 1 << 4 165 | .define WOBJSEL_OBJ_W2_ENABLE 1 << 3 166 | .define WOBJSEL_OBJ_W2_INVERT 1 << 2 167 | .define WOBJSEL_OBJ_W1_ENABLE 1 << 1 168 | .define WOBJSEL_OBJ_W1_INVERT 1 << 0 169 | 170 | .define W1L $2126 ;WH0 ;Window Position 171 | .define W1R $2127 ;WH1 172 | .define W2L $2128 ;WH2 173 | .define W2R $2129 ;WH3 174 | 175 | .define WBGLOG $212a ;Window mask logic 176 | .define WBGLOG_BG4_OR 0 << 6 177 | .define WBGLOG_BG4_AND 1 << 6 178 | .define WBGLOG_BG4_XOR 2 << 6 179 | .define WBGLOG_BG4_XNOR 3 << 6 180 | 181 | .define WBGLOG_BG3_OR 0 << 4 182 | .define WBGLOG_BG3_AND 1 << 4 183 | .define WBGLOG_BG3_XOR 2 << 4 184 | .define WBGLOG_BG3_XNOR 3 << 4 185 | 186 | .define WBGLOG_BG2_OR 0 << 2 187 | .define WBGLOG_BG2_AND 1 << 2 188 | .define WBGLOG_BG2_XOR 2 << 2 189 | .define WBGLOG_BG2_XNOR 3 << 2 190 | 191 | .define WBGLOG_BG1_OR 0 << 0 192 | .define WBGLOG_BG1_AND 1 << 0 193 | .define WBGLOG_BG1_XOR 2 << 0 194 | .define WBGLOG_BG1_XNOR 3 << 0 195 | 196 | .define WOBJLOG $212b 197 | .define WOBJLOG_COL_OR 0 << 2 198 | .define WOBJLOG_COL_AND 1 << 2 199 | .define WOBJLOG_COL_XOR 2 << 2 200 | .define WOBJLOG_COL_XNOR 3 << 2 201 | 202 | .define WOBJLOG_OBJ_OR 0 << 0 203 | .define WOBJLOG_OBJ_AND 1 << 0 204 | .define WOBJLOG_OBJ_XOR 2 << 0 205 | .define WOBJLOG_OBJ_XNOR 3 << 0 206 | 207 | .define TMAIN $212c ;Mainscreen Designation 208 | .define TSUB $212d ;Subscreen Designation 209 | .define TMW $212e ;Mainscreen Window Mask Designation 210 | .define TSW $212f ;Subscreen Mask Designation 211 | .define T_OBJ_ENABLE 1 << 4 212 | .define T_BG4_ENABLE 1 << 3 213 | .define T_BG3_ENABLE 1 << 2 214 | .define T_BG2_ENABLE 1 << 1 215 | .define T_BG1_ENABLE 1 << 0 216 | 217 | .define CGWSEL $2130 ;Color Addition Select 218 | .define CGWSEL_CLIP_COL_NEVER 0 << 6 219 | .define CGWSEL_CLIP_COL_OUTSIDE 1 << 6 220 | .define CGWSEL_CLIP_COL_INSIDE 2 << 6 221 | .define CGWSEL_CLIP_COL_ALWAYS 3 << 6 222 | 223 | .define CGWSEL_NO_COL_MATH_NEVER 0 << 4 224 | .define CGWSEL_NO_COL_MATH_OUTSIDE 1 << 4 225 | .define CGWSEL_NO_COL_MATH_INSIDE 2 << 4 226 | .define CGWSEL_NO_COL_MATH_ALWAYS 3 << 4 227 | 228 | .define CGWSEL_ADD_SUBSCREEN 1 << 1 229 | .define CGWSEL_DIRECT_COLOR_MODE 1 << 0 230 | 231 | .define CGADSUB $2131 ;Color math designation 232 | .define CGADSUB_ADDSUB_COL 1 << 7 233 | .define CGADSUB_HALF_COL 1 << 6 234 | .define CGADSUB_BAC_ENABLE 1 << 5 235 | .define CGADSUB_OBJ_ENABLE 1 << 4 236 | .define CGADSUB_BG4_ENABLE 1 << 3 237 | .define CGADSUB_BG3_ENABLE 1 << 2 238 | .define CGADSUB_BG2_ENABLE 1 << 1 239 | .define CGADSUB_BG1_ENABLE 1 << 0 240 | 241 | .define COLDATA $2132 ;Fixed Color Data 242 | .define COLDATA_BLUE 1 << 7 243 | .define COLDATA_GREEN 1 << 6 244 | .define COLDATA_RED 1 << 5 245 | .define COLDATA_INTENSITY $1f << 0 246 | 247 | .define SETINI $2133 ;Screen Mode/Video Select 248 | .define SETINI_EXT_SYNC 1 << 7 249 | .define SETINI_MODE7_EXTBG 1 << 6 250 | .define SETINI_PSEUDO_HIRES 1 << 3 251 | .define SETINI_OVERSCAN 1 << 2 252 | .define SETINI_OBJ_INTERLACE 1 << 1 253 | .define SETINI_SCREEN_INTERLACE 1 << 0 254 | 255 | .define MPYL $2134 ;Multiplication Result 256 | .define MPYM $2135 257 | .define MPYH $2136 258 | 259 | .define SLHV $2137 ;Software Latch for H/V Counter 260 | 261 | .define OAMDATAREAD $2138 ;Data for OAM read 262 | 263 | .define VMDATALREAD $2139 ;VRAM Data Read 264 | .define VMDATAHREAD $213a 265 | 266 | .define CGDATAREAD $213b ;CGRAM Data read 267 | 268 | .define OPHCT $213c ;Scanline Location 269 | .define OPH_MASK $01ff 270 | .define OPVCT $213d 271 | .define OPV_MASK $01ff 272 | .define OPV_NMI 225 273 | 274 | .define STAT77 $213e ;5C77 PPU-1 Status Flag and Version 275 | .define STAT77_TIME_OVER 1 << 7 276 | .define STAT77_RANGE_OVER 1 << 6 277 | .define STAT77_PPU_SLAVE_MASTER 1 << 5 278 | 279 | .define STAT78 $213f ;5C78 PPU-2 Status Flag and Version 280 | .define STAT78_INTERLACE_FIELD 1 << 7 281 | .define STAT78_EXT_LATCH 1 << 6 282 | .define STAT78_NTSC_PAL 1 << 4 283 | 284 | .define APUIO0 $2140 ;APU I/O Ports 285 | .define APUIO1 $2141 286 | .define APUIO2 $2142 287 | .define APUIO3 $2143 288 | 289 | .define WMDATA $2180 ;WRAM Data Port 290 | .define WMADDL $2181 ;WRAM Address 291 | .define WMADDM $2182 292 | .define WMADDH $2183 293 | 294 | 295 | ;cpu regs: 296 | .define JOYSER0 $4016 ;Joypad Port 1 297 | .define JOYSER0_DATA2 1 << 1 298 | .define JOYSER0_DATA1 1 << 0 299 | .define JOYSER0_LATCH 1 << 0 300 | 301 | .define JOYSER1 $4017 ;Joypad Port 2 302 | .define JOYSER1_DATA2 1 << 1 303 | .define JOYSER1_DATA1 1 << 0 304 | 305 | .define NMITIMEN $4200 ;Interrupt Enable Flags 306 | .define NMITIMEN_NMI_ENABLE 1 << 7 307 | .define NMITIMEN_Y_IRQ_ENABLE 1 << 5 308 | .define NMITIMEN_X_IRQ_ENABLE 1 << 4 309 | .define NMITIMEN_AUTO_JOY_READ 1 << 0 310 | 311 | 312 | .define WRIO $4201 ;I/O port output/write 313 | .define WRIO_JOY2_IOBIT_LATCH 1 << 7 314 | .define WRIO_JOY1_IOBIT 1 << 6 315 | 316 | .define WRMPYA $4202 ;Multiplicand 317 | .define WRMPYB $4203 318 | .define WRDIVL $4204 ;Dividend 319 | .define WRDIVH $4205 320 | .define WRDIVB $4206 ;Divisor 321 | 322 | .define HTIMEL $4207 ;H/X Timer 323 | .define HTIMEH $4208 324 | .define VTIMEL $4209 ;V/Y Timer 325 | .define VTIMEH $420a 326 | .define TIMER_RANGE $1f 327 | 328 | .define MDMAEN $420b ;DMA Enable 329 | .define HDMAEN $420c ;HDMA Enable 330 | .define DMA_CHANNEL7_ENABLE 1 << 7 331 | .define DMA_CHANNEL6_ENABLE 1 << 6 332 | .define DMA_CHANNEL5_ENABLE 1 << 5 333 | .define DMA_CHANNEL4_ENABLE 1 << 4 334 | .define DMA_CHANNEL3_ENABLE 1 << 3 335 | .define DMA_CHANNEL2_ENABLE 1 << 2 336 | .define DMA_CHANNEL1_ENABLE 1 << 1 337 | .define DMA_CHANNEL0_ENABLE 1 << 0 338 | 339 | .define MEMSEL $420d ;ROM Access Speed 340 | .define MEMSEL_FASTROM_ENABLE 1 << 0 341 | 342 | .define RDNMI $4210 ;NMI Flag and 5A22 Version 343 | .define RDNMI_NMI_FLAG 1 << 7 344 | 345 | .define TIMEUP $4211 ;IRQ Flag 346 | .define TIMEUP_IRQ_FLAG 1 << 7 347 | 348 | .define HVBJOY $4212 ;PPU Status 349 | .define HVBJOY_VBLANK_FLAG 1 << 7 350 | .define HVBJOY_HBLANK_FLAG 1 << 6 351 | .define HVBJOY_AUTO_JOY_STATUS 1 << 0 352 | 353 | .define RDIO $4213 ;I/O port input/read 354 | .define RDIO_JOY2_IOBIT_LATCH 1 << 7 355 | .define RDIO_JOY1_IOBIT 1 << 6 356 | 357 | .define RDDIVL $4214 ;Quotient of Divide Result 358 | .define RDDIVH $4215 359 | .define RDMPYL $4216 ;Multiplication Product or Divide Remainder 360 | .define RDMPYH $4217 361 | 362 | .define JOY1L $4218 ;Joyport1 Data1 363 | .define JOY1H $4219 364 | .define JOY2L $421a ;Joyport2 Data1 365 | .define JOY2H $421b 366 | .define JOY3L $421c ;Joyport1 Data2 367 | .define JOY3H $421d 368 | .define JOY4L $421e ;Joyport2 Data2 369 | .define JOY4H $421f 370 | .define JOY_BUTTON_B 1 << 15 371 | .define JOY_BUTTON_Y 1 << 14 372 | .define JOY_BUTTON_SELECT 1 << 13 373 | .define JOY_BUTTON_START 1 << 12 374 | .define JOY_DIR_UP 1 << 11 375 | .define JOY_DIR_DOWN 1 << 10 376 | .define JOY_DIR_LEFT 1 << 9 377 | .define JOY_DIR_RIGHT 1 << 8 378 | .define JOY_BUTTON_A 1 << 7 379 | .define JOY_BUTTON_X 1 << 6 380 | .define JOY_BUTTON_L 1 << 5 381 | .define JOY_BUTTON_R 1 << 4 382 | 383 | .define JOY_BUTTON_SIGNATURE %1111 384 | 385 | .define DMAP0 $4300 ;DMA Control 386 | .define DMAP_TRANSFER_DIRECTION 1 << 7 387 | .define DMAP_HDMA_INDIRECT_MODE 1 << 6 388 | .define DMAP_ADRESS_INCREMENT 1 << 4 389 | .define DMAP_FIXED_TRANSFER 1 << 3 390 | .define DMAP_1_REG_WRITE_ONCE 0 << 0 391 | .define DMAP_2_REG_WRITE_ONCE 1 << 0 392 | .define DMAP_1_REG_WRITE_TWICE 2 << 0 393 | .define DMAP_2_REG_WRITE_TWICE_EACH 3 << 0 394 | .define DMAP_4_REG_WRITE_ONCE 4 << 0 395 | .define DMAP_2_REG_WRITE_TWICE_ALT 5 << 0 396 | 397 | .define DMADEST0 $4301 ;DMA Destination Register 398 | 399 | .define DMASRC0L $4302 ;DMA Source Adress 400 | .define DMASRC0H $4303 401 | .define DMASRC0B $4304 402 | 403 | .define DMALEN0L $4305 ;DMA Size/HDMA Indirect Adress 404 | .define DMALEN0H $4306 405 | .define DMALEN0B $4307 406 | 407 | .define HDMATBL0L $4308 ;HDMA Table Address 408 | .define HDMATBL0H $4309 409 | 410 | .define HDMACNT0 $430a ;HDMA Line Counter 411 | .define HDMACNT_REPEAT 1 << 7 412 | .define HDMACNT_RANGE $7f 413 | 414 | .define DMAP1 $4310 415 | .define DMADEST1 $4311 416 | .define DMASRC1L $4312 417 | .define DMASRC1H $4313 418 | .define DMASRC1B $4314 419 | .define DMALEN1L $4315 420 | .define DMALEN1H $4316 421 | .define DMALEN1B $4317 422 | .define HDMATBL1L $4318 423 | .define HDMATBL1H $4319 424 | .define HDMACNT1 $431a 425 | 426 | .define DMAP2 $4320 427 | .define DMADEST2 $4321 428 | .define DMASRC2L $4322 429 | .define DMASRC2H $4323 430 | .define DMASRC2B $4324 431 | .define DMALEN2L $4325 432 | .define DMALEN2H $4326 433 | .define DMALEN2B $4327 434 | .define HDMATBL2L $4328 435 | .define HDMATBL2H $4329 436 | .define HDMACNT2 $432a 437 | 438 | .define DMAP3 $4330 439 | .define DMADEST3 $4331 440 | .define DMASRC3L $4332 441 | .define DMASRC3H $4333 442 | .define DMASRC3B $4334 443 | .define DMALEN3L $4335 444 | .define DMALEN3H $4336 445 | .define DMALEN3B $4337 446 | .define HDMATBL3L $4338 447 | .define HDMATBL3H $4339 448 | .define HDMACNT3 $433a 449 | 450 | .define DMAP4 $4340 451 | .define DMADEST4 $4341 452 | .define DMASRC4L $4342 453 | .define DMASRC4H $4343 454 | .define DMASRC4B $4344 455 | .define DMALEN4L $4345 456 | .define DMALEN4H $4346 457 | .define DMALEN4B $4347 458 | .define HDMATBL4L $4348 459 | .define HDMATBL4H $4349 460 | .define HDMACNT4 $434a 461 | 462 | .define DMAP5 $4350 463 | .define DMADEST5 $4351 464 | .define DMASRC5L $4352 465 | .define DMASRC5H $4353 466 | .define DMASRC5B $4354 467 | .define DMALEN5L $4355 468 | .define DMALEN5H $4356 469 | .define DMALEN5B $4357 470 | .define HDMATBL5L $4358 471 | .define HDMATBL5H $4359 472 | .define HDMACNT5 $435a 473 | 474 | .define DMAP6 $4360 475 | .define DMADEST6 $4361 476 | .define DMASRC6L $4362 477 | .define DMASRC6H $4363 478 | .define DMASRC6B $4364 479 | .define DMALEN6L $4365 480 | .define DMALEN6H $4366 481 | .define DMALEN6B $4367 482 | .define HDMATBL6L $4368 483 | .define HDMATBL6H $4369 484 | .define HDMACNT6 $436a 485 | 486 | .define DMAP7 $4370 487 | .define DMADEST7 $4371 488 | .define DMASRC7L $4372 489 | .define DMASRC7H $4373 490 | .define DMASRC7B $4374 491 | .define DMALEN7L $4375 492 | .define DMALEN7H $4376 493 | .define DMALEN7B $4377 494 | .define HDMATBL7L $4378 495 | .define HDMATBL7H $4379 496 | .define HDMACNT7 $437a 497 | 498 | ;tilemap format in vram: 499 | .define BG.FORMAT.TILE $3ff 500 | .define BG.FORMAT.PALETTE $1C00 501 | .define BG.FORMAT.PALETTE.1 $400 502 | .define BG.FORMAT.PALETTE.2 $800 503 | .define BG.FORMAT.PRIORITY $2000 504 | .define BG.FORMAT.HFLIP $4000 505 | .define BG.FORMAT.VFLIP $8000 506 | 507 | ;sprite format in oamram: 508 | .define OAM.FORMAT.TILE $1ff 509 | .define OAM.FORMAT.PALETTE $0E00 510 | .define OAM.FORMAT.PRIORITY $3000 511 | .define OAM.FORMAT.HFLIP $4000 512 | .define OAM.FORMAT.VFLIP $8000 513 | -------------------------------------------------------------------------------- /snes2asm/decoder.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from snes2asm.disassembler import Instruction 4 | from snes2asm.tile import Decode8bppTile, Decode4bppTile, Decode3bppTile, Decode2bppTile, DecodeMode7Tile 5 | from snes2asm.bitmap import BitmapIndex 6 | from snes2asm import compression 7 | from snes2asm import brr 8 | 9 | import struct 10 | import yaml 11 | 12 | class Decoder: 13 | def __init__(self, label=None, start=0, end=0, compress=None): 14 | self.label = label 15 | self.start = start 16 | self.end = end 17 | self.files = {} 18 | self.file_ext = None 19 | self.compress = compress 20 | self.sub_decoders = [] 21 | self.processed = False 22 | 23 | if type(self.label) != str: 24 | raise TypeError("Invalid value for label parameter: %s" % str(self.label)) 25 | if type(self.start) != int: 26 | raise TypeError("Invalid value for start parameter: %s" % str(self.start)) 27 | if type(self.end) != int: 28 | raise TypeError("Invalid value for end parameter: %s" % str(self.end)) 29 | 30 | def name(self): 31 | return "%s%06x-%06x" % (self.__name__, self.start, self.end) 32 | 33 | def decode(self, data): 34 | show_label = self.label != None 35 | size = self.end - self.start 36 | if size <= 4: 37 | yield (0, Instruction(Decoder.data_directive(size) + ' ' + self.hex_fmt[size-1] % Decoder.val(data, 0, size), preamble=self.label+":")) 38 | else: 39 | for y in range(0, len(data), 16): 40 | line = '.db ' + ', '.join(("$%02X" % x) for x in data[y : min(y+16, len(data))]) 41 | if show_label: 42 | yield (y, Instruction(line, preamble=self.label+":")) 43 | show_label = False 44 | else: 45 | yield (y, Instruction(line)) 46 | 47 | def no_data(self): 48 | return self.start == self.end 49 | 50 | def set_output(self, name, ext, data): 51 | file_name = "%s.%s" % (name, ext) 52 | self.file_ext = ext 53 | 54 | # Decompress and output result 55 | if self.compress != None: 56 | self.files[file_name] = compression.decompress(self.compress, data) 57 | file_name = "%s.%s" % (name, self.compress) 58 | 59 | self.files[file_name] = data 60 | return file_name 61 | 62 | def add_extra_file(self, name, data): 63 | self.files[name] = data 64 | 65 | @staticmethod 66 | def val(data, pos, size=1): 67 | if size == 2: 68 | return data[pos] + (data[pos+1] << 8) 69 | elif size == 3: 70 | return data[pos] + (data[pos+1] << 8) + (data[pos+2] << 16) 71 | elif size == 4: 72 | return data[pos] + (data[pos+1] << 8) + (data[pos+2] << 16) + (data[pos+3] << 24) 73 | else: 74 | return data[pos] 75 | 76 | hex_fmt = ['$%02X', '$%04X', '$%06X', '$%08X'] 77 | 78 | @staticmethod 79 | def data_directive(size): 80 | return ['.db', '.dw', '.dl', '.dd'][(size-1) & 0x3] 81 | 82 | def decompress(self, data): 83 | try: 84 | module = getattrib(compression, self.compress) 85 | except AttributeError: 86 | raise ValueError("Decoder %s has an invalid compression type of %s" % (self.label, self.compress)) 87 | return module.decompress(data) 88 | 89 | 90 | class Headers(Decoder): 91 | def __init__(self, start, end): 92 | Decoder.__init__(self, label="Headers", start=start, end=end) 93 | 94 | def decode(self, data): 95 | yield (0, Instruction('; Auto-generated headers', preamble=self.label+":")) 96 | 97 | class BinaryDecoder(Decoder): 98 | def __init__(self, label, start, end, compress=None): 99 | Decoder.__init__(self, label, start, end, compress) 100 | 101 | def decode(self, data): 102 | file_name = self.set_output(self.label, 'bin', data) 103 | yield (0, Instruction(".INCBIN \"%s\"" % file_name, preamble=self.label+":")) 104 | 105 | class TextDecoder(Decoder): 106 | def __init__(self, label, start, end=0, compress=None, pack=None, index=None, translation=None): 107 | if pack != None: 108 | if type(pack) is not list: 109 | raise ValueError("TextDecoder: %s pack parameter must be an array" % label) 110 | # Calculate end from pack 111 | packsize = sum(pack) 112 | if end == 0: 113 | end = start + packsize 114 | elif start + packsize != end: 115 | raise ValueError("TextDecoder: %s pack lengths do not match end point" % label) 116 | elif index != None: 117 | index.parent = self 118 | if end == 0: 119 | raise ValueError("TextDecoder: %s missing end parameter" % label) 120 | 121 | Decoder.__init__(self, label, start, end, compress) 122 | self.translation = translation 123 | self.pack = pack 124 | self.index = index 125 | if self.index != None: 126 | self.sub_decoders.append(index) 127 | 128 | def decode(self, data): 129 | if self.pack: 130 | pos = 0 131 | for index in range(0, len(self.pack)): 132 | size = self.pack[index] 133 | label = "%s_%d:" % (self.label, index) 134 | end = pos + size 135 | yield self.text(pos, data[pos:end], label) 136 | pos = end 137 | elif self.index: 138 | pos = 0 139 | index = 0 140 | for offset in self.index.values: 141 | yield self.text(pos, data[pos:offset], "%s_%d:" % (self.label, index)) 142 | if pos != offset: 143 | index += 1 144 | pos = offset 145 | 146 | if pos < self.end: 147 | yield self.text(pos, data[pos:self.end], "%s_%d:" % (self.label, index)) 148 | 149 | else: 150 | yield self.text(0, data, self.label+":") 151 | 152 | def text(self, pos, vals, label): 153 | if self.translation: 154 | # Break STRINGMAP directives into multiple parts if needed 155 | # since there is a bug with large buffers in WLA-DX 156 | parts = [] 157 | for output_start in range(0, len(vals), 64): 158 | out = [] 159 | output_end = min(output_start+64,len(vals)) 160 | for char in vals[output_start:output_end]: 161 | if char in self.translation.table: 162 | out.append(self.translation.table[char]) 163 | else: 164 | out.append(_ESCAPE_CHARS[char]) 165 | parts.append('.STRINGMAP %s "%s"' % (self.translation.label, "".join(out))) 166 | return (pos, Instruction("\n".join(parts), preamble=label)) 167 | else: 168 | parts = ['.db "%s"' % ansi_escape(vals[i-128:i]) for i in range(128, len(vals) + 128, 128)] 169 | return (pos, Instruction("\n".join(parts), preamble=label)) 170 | 171 | class ArrayDecoder(Decoder): 172 | 173 | def __init__(self, label, start, end, compress=None, size=1, struct=None): 174 | Decoder.__init__(self, label, start, end, compress) 175 | self.size = size 176 | self.struct = struct 177 | 178 | if self.struct == None and (self.size > 4 or self.size < 1): 179 | raise ValueError("ArrayDecoder: Invalid array element size %d for label %s" % (self.size, label)) 180 | 181 | if (end - start) % size != 0: 182 | raise ValueError("ArrayDecoder: %s start and end do not align with size %i" % (label, size)) 183 | 184 | def decode(self, data): 185 | if self.struct != None: 186 | # TODO 187 | pass 188 | else: 189 | instr = Decoder.data_directive(self.size) + ' ' 190 | form = Decoder.hex_fmt[self.size-1] 191 | show_label = self.label != None 192 | for y in range(0, len(data), 16): 193 | parts = [form % Decoder.val(data, x, self.size) for x in range(y, min(y+16,len(data)), self.size)] 194 | line = instr + ', '.join(parts) 195 | if show_label: 196 | yield (y, Instruction(line, preamble=self.label+":")) 197 | show_label = False 198 | else: 199 | yield (y, Instruction(line)) 200 | 201 | class IndexDecoder(Decoder): 202 | def __init__(self, label, start, end, compress=None, size=2): 203 | Decoder.__init__(self, label, start, end, compress) 204 | if (end - start) % size != 0: 205 | raise ValueError("Index: %s start and end do not align with size %i" % (label, size)) 206 | self.size = size 207 | self.parent = None 208 | self.values = [] 209 | 210 | def decode(self, data): 211 | instr = Decoder.data_directive(self.size) 212 | index = 0 213 | for pos in range(0, len(data), self.size): 214 | offset = Decoder.val(data, pos, self.size) 215 | self.values.append(offset) 216 | if offset + self.parent.start > self.parent.end: 217 | yield(pos, Instruction('%s %i' % (instr, offset), comment='Invalid index')) 218 | else: 219 | yield(pos, Instruction('%s %s_%i - %s_0' % (instr, self.parent.label, index, self.parent.label), 220 | preamble="%s_%i:" % (self.label, index))) 221 | index = index + 1 222 | 223 | def size(self): 224 | return (self.end - self.start) // self.size 225 | 226 | class PaletteDecoder(Decoder): 227 | def __init__(self, start, end, compress=None, label=None): 228 | Decoder.__init__(self, label, start, end, compress) 229 | self.colors = [] 230 | if (self.end - self.start) & 0x1 != 0: 231 | raise ValueError("Palette %s start and end (0x%06X-0x%06X) do not align with 2-byte color entries" % (self.label, self.start, self.end)) 232 | 233 | def colorCount(self): 234 | return (self.end - self.start) / 2 235 | 236 | def decode(self, data): 237 | # Output pal file 238 | file_name = self.set_output(self.label, 'pal', data) 239 | 240 | lines = [] 241 | for i in range(0, len(data), 2): 242 | bgr565 = data[i] | data[i+1] << 8 243 | rgbcolor = ((bgr565 & 0x7c00) >> 7 | (bgr565 & 0x3e0) << 6 | (bgr565 & 0x1f) << 19) 244 | self.colors.append(rgbcolor) 245 | lines.append("#%06X" % rgbcolor) 246 | 247 | self.add_extra_file("%s.rgb" % self.label, "\n".join(lines) ) 248 | 249 | yield (0, Instruction(".INCBIN \"%s\"" % file_name, preamble=self.label+":")) 250 | 251 | class GraphicDecoder(Decoder): 252 | def __init__(self, label, start, end, compress=None, bit_depth=4, width=128, palette=None, palette_offset=0, mode7=False): 253 | Decoder.__init__(self, label, start, end, compress) 254 | self.bit_depth = bit_depth 255 | self.width = width 256 | self.palette = palette 257 | self.palette_offset = palette_offset 258 | 259 | if self.width & 0x7 != 0: 260 | raise ValueError("Tile value width must be a multiple of 8") 261 | 262 | if mode7: 263 | self.bit_depth = 8 264 | self.tile_decoder = DecodeMode7Tile 265 | if self.palette_offset != 0: 266 | raise ValueError("Tile %s not allowed palette_offset for mode 7" % (self.label)) 267 | elif self.bit_depth == 8: 268 | self.tile_decoder = Decode8bppTile 269 | self.tile_size = 64 270 | elif self.bit_depth == 2: 271 | self.tile_decoder = Decode2bppTile 272 | self.tile_size = 16 273 | elif self.bit_depth == 3: 274 | self.tile_decoder = Decode3bppTile 275 | self.tile_size = 24 276 | else: 277 | self.tile_decoder = Decode4bppTile 278 | self.tile_size = 32 279 | 280 | if self.palette != None: 281 | self.sub_decoders.append(self.palette) 282 | if (1 << self.bit_depth) > self.palette.colorCount() - self.palette_offset: 283 | raise ValueError("Palette %s does not provide enough colors for %d-bit graphic %s" % (self.palette.label, self.bit_depth, self.label)) 284 | 285 | if (self.end - self.start) % self.tile_size != 0: 286 | raise ValueError("Tile %s start and end (0x%06X-0x%06X) do not align with the %d-bit tile size" % (self.label, self.start, self.end, self.bit_depth)) 287 | 288 | def get_palette(self): 289 | if self.palette != None: 290 | return self.palette.colors[self.palette_offset:] 291 | else: 292 | # Gray scale palette 293 | step = 1 << (8 - self.bit_depth) 294 | pal = [ ((x + step - 1) << 8 | (x + step - 1) << 16 | (x + step - 1) << 0) for x in range(0, 256, step) ] 295 | pal[0] = 0xFF00FF # Transparent 296 | pal[1] = 0 # True black 297 | return pal 298 | 299 | def decode(self, data): 300 | # Output chr file 301 | file_name = self.set_output("%s_%dbpp" % (self.label, self.bit_depth), 'chr', data) 302 | 303 | # Output bitmap file 304 | tile_count = int((self.end - self.start) / self.tile_size) 305 | tiles_wide = int(self.width / 8.0) 306 | height = int((tile_count / tiles_wide) * 8) 307 | if tile_count % tiles_wide != 0: 308 | height = height + 8 309 | 310 | # TODO: RE 311 | # If palette hasn't been decoded yet then decode now 312 | if self.palette != None and len(self.palette.colors) == 0: 313 | next(self.palette.decode(data)) 314 | 315 | # Convert 3bpp to 4bpp for bitmap storage 316 | bitmap_depth = 4 if self.bit_depth == 3 else self.bit_depth 317 | bitmap = BitmapIndex(self.width, height, bitmap_depth, self.get_palette()) 318 | 319 | tile_index = 0 320 | for i in range(0, len(data), self.tile_size): 321 | tile = self.tile_decoder(data[i:i+self.tile_size]) 322 | 323 | tile_x = (tile_index % tiles_wide) * 8 324 | tile_y = (tile_index // tiles_wide) * 8 325 | for y in range(0,8): 326 | for x in range(0,8): 327 | pix = tile[y*8+x] 328 | bitmap.setPixel(tile_x+x,tile_y+y, pix) 329 | tile_index = tile_index + 1 330 | 331 | self.add_extra_file("%s_%dbpp.bmp" % (self.label, self.bit_depth), bitmap.output()) 332 | 333 | # Make binary chr file include 334 | yield (0, Instruction(".INCBIN \"%s\"" % file_name, preamble=self.label+":")) 335 | 336 | class TranslationMap(Decoder): 337 | def __init__(self, label, table): 338 | Decoder.__init__(self, label) 339 | self.table = table 340 | # Fill in escape characters 341 | for i, k in {0: '\\0', 10: '[n]', 13: '[r]'}.items(): 342 | if i not in self.table: 343 | self.table[i] = k 344 | 345 | self.table = { i: table[i] if i in table else chr(i) for i in range(0,256)} 346 | script = "\r\n".join(["%02x=%s" % (hex,text) for hex,text in self.table.items()]) 347 | self.add_extra_file("%s.tbl" % self.label, script.encode('utf-8')) 348 | 349 | def decode(self, data): 350 | yield (0, Instruction('.STRINGMAPTABLE %s "%s.tbl"' % (self.label, self.label))) 351 | 352 | class SoundDecoder(Decoder): 353 | def __init__(self, label, start, end, compress=None, rate=32000): 354 | Decoder.__init__(self, label, start, end, compress) 355 | self.rate = rate 356 | 357 | def decode(self, data): 358 | file_name = self.set_output(self.label, 'brr', data) 359 | wav_data = brr.decode(data, self.rate) 360 | self.add_extra_file("%s.wav" % self.label, wav_data) 361 | yield (0, Instruction(".INCBIN \"%s\"" % file_name, preamble=self.label+":")) 362 | 363 | class TileMapDecoder(Decoder): 364 | def __init__(self, label, start, end, gfx, compress=None, width=128, encoding=None): 365 | Decoder.__init__(self, label, start, end, compress) 366 | self.gfx = gfx 367 | self.width = width 368 | self.height = int((self.end - self.start) / (width * 2)) 369 | self.encoding = encoding 370 | 371 | if self.encoding != None and self.encoding not in ['rle', 'lzss']: 372 | raise ValueError("Unsupported encoding %s" % self.encoding) 373 | 374 | def decode(self, data): 375 | # Tilemap file 376 | tilebin = "%s.tilebin" % self.label 377 | if type(self.gfx) == list: 378 | gfx = [ g.filename() for g in self.gfx ] 379 | palette = [ g.palette.filename() for g in self.gfx ] 380 | else: 381 | gfx = self.gfx.filename() 382 | palette = self.gfx.palette.filename() 383 | 384 | tilemap = {'name': self.label, 'width': self.width, 'height': self.height, 'tilebin': tilebin, 'gfx': gfx, 'palette': palette} 385 | if self.encoding != None: 386 | tilemap['encoding'] = self.encoding 387 | self.add_extra_file("%s.tilemap" % self.label, yaml.dump(tilemap).encode('utf-8')) 388 | 389 | # Tile character map file 390 | file_name = self.set_output(self.label, "tilebin", data) 391 | yield (0, Instruction(".INCBIN \"%s\"" % file_name, preamble=self.label+":")) 392 | 393 | _ESCAPE_CHARS = ['\\' + '0', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\x07', '\\x08', '\\t', '\\n', '\\x0b', '\\x0c', '\\r', '\\x0e', '\\x0f', '\\x10', '\\x11', '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17', '\\x18', '\\x19', '\\x1a', '\\x1b', '\\x1c', '\\x1d', '\\x1e', '\\x1f', ' ', '!', '\\"', '#', '$', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', '\x7f', '\\x80', '\\x81', '\\x82', '\\x83', '\\x84', '\\x85', '\\x86', '\\x87', '\\x88', '\\x89', '\\x8a', '\\x8b', '\\x8c', '\\x8d', '\\x8e', '\\x8f', '\\x90', '\\x91', '\\x92', '\\x93', '\\x94', '\\x95', '\\x96', '\\x97', '\\x98', '\\x99', '\\x9a', '\\x9b', '\\x9c', '\\x9d', '\\x9e', '\\x9f', '\\xa0', '\\xa1', '\\xa2', '\\xa3', '\\xa4', '\\xa5', '\\xa6', '\\xa7', '\\xa8', '\\xa9', '\\xaa', '\\xab', '\\xac', '\\xad', '\\xae', '\\xaf', '\\xb0', '\\xb1', '\\xb2', '\\xb3', '\\xb4', '\\xb5', '\\xb6', '\\xb7', '\\xb8', '\\xb9', '\\xba', '\\xbb', '\\xbc', '\\xbd', '\\xbe', '\\xbf', '\\xc0', '\\xc1', '\\xc2', '\\xc3', '\\xc4', '\\xc5', '\\xc6', '\\xc7', '\\xc8', '\\xc9', '\\xca', '\\xcb', '\\xcc', '\\xcd', '\\xce', '\\xcf', '\\xd0', '\\xd1', '\\xd2', '\\xd3', '\\xd4', '\\xd5', '\\xd6', '\\xd7', '\\xd8', '\\xd9', '\\xda', '\\xdb', '\\xdc', '\\xdd', '\\xde', '\\xdf', '\\xe0', '\\xe1', '\\xe2', '\\xe3', '\\xe4', '\\xe5', '\\xe6', '\\xe7', '\\xe8', '\\xe9', '\\xea', '\\xeb', '\\xec', '\\xed', '\\xee', '\\xef', '\\xf0', '\\xf1', '\\xf2', '\\xf3', '\\xf4', '\\xf5', '\\xf6', '\\xf7', '\\xf8', '\\xf9', '\\xfa', '\\xfb', '\\xfc', '\\xfd', '\\xfe', '\\xff'] 394 | 395 | 396 | def ansi_escape(subject): 397 | if type(subject) == str: 398 | return ''.join([_ESCAPE_CHARS[ord(c)] for c in subject]) 399 | else: 400 | return ''.join([_ESCAPE_CHARS[c] for c in subject]) 401 | -------------------------------------------------------------------------------- /snes2asm/test/classickong.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | banks: [0,1,2] 3 | decoders: 4 | - type: text 5 | label: stub 6 | start: 0x15ac9 7 | end: 0x15c2e 8 | #pack: [11,6,3] 9 | - type: palette 10 | label: back1_pal 11 | start: 0x2f860 12 | end: 0x2f880 13 | - type: palette 14 | label: font_pal 15 | start: 0x2f880 16 | end: 0x2f8a0 17 | - type: palette 18 | label: tileset1_pal 19 | start: 0x2f8a0 20 | end: 0x2f8c0 21 | - type: palette 22 | label: tileset1alt1_pal 23 | start: 0x2f8c0 24 | end: 0x2f8e0 25 | - type: palette 26 | label: tileset1alt2_pal 27 | start: 0x2f8e0 28 | end: 0x2f900 29 | - type: palette 30 | label: tileset2_pal 31 | start: 0x2f900 32 | end: 0x2f920 33 | - type: palette 34 | label: tileset2alt_pal 35 | start: 0x2f920 36 | end: 0x2f940 37 | - type: palette 38 | label: sprites1_pal 39 | start: 0x2f940 40 | end: 0x2f960 41 | - type: palette 42 | label: sprites2_pal 43 | start: 0x2f960 44 | end: 0x2f980 45 | - type: palette 46 | label: sprites3_pal 47 | start: 0x2f980 48 | end: 0x2f9A0 49 | - type: palette 50 | label: sprites4_pal 51 | start: 0x2f9A0 52 | end: 0x2f9C0 53 | - type: palette 54 | label: sprites5_pal 55 | start: 0x2f9C0 56 | end: 0x2f9E0 57 | - type: palette 58 | label: sprites5alt_pal 59 | start: 0x2f9E0 60 | end: 0x2fa00 61 | - type: palette 62 | label: sprites6_pal 63 | start: 0x2fa00 64 | end: 0x2fa20 65 | - type: palette 66 | label: title_top_pal 67 | start: 0x2fa20 68 | end: 0x2fa40 69 | - type: palette 70 | label: title_bottom_pal 71 | start: 0x2fa40 72 | end: 0x2fa60 73 | - type: palette 74 | label: bzlogo_pal 75 | start: 0x2fa60 76 | end: 0x2fc60 77 | - type: gfx 78 | label: back1 79 | start: 0x18000 80 | end: 0x1E000 81 | bit_depth: 4 82 | palette: back1_pal 83 | width: 256 84 | - type: gfx 85 | label: bubblezaplogo 86 | start: 0x1E000 87 | end: 0x1FC00 88 | bit_depth: 8 89 | palette: bzlogo_pal 90 | width: 224 91 | - type: gfx 92 | label: sprites6 93 | start: 0x1FC00 94 | end: 0x20000 95 | bit_depth: 4 96 | palette: sprites6_pal 97 | width: 128 98 | - type: gfx 99 | label: title_top 100 | start: 0x28000 101 | end: 0x28960 102 | bit_depth: 4 103 | palette: title_top_pal 104 | width: 200 105 | - type: gfx 106 | label: title_bottom 107 | start: 0x28960 108 | end: 0x2A460 109 | bit_depth: 4 110 | palette: title_bottom_pal 111 | width: 192 112 | - type: gfx 113 | label: font 114 | start: 0x2A460 115 | end: 0x2AC60 116 | bit_depth: 4 117 | palette: font_pal 118 | width: 128 119 | - type: gfx 120 | label: tileset1 121 | start: 0x2AC60 122 | end: 0x2B460 123 | bit_depth: 4 124 | palette: tileset1_pal 125 | width: 128 126 | - type: gfx 127 | label: tileset2 128 | start: 0x2B460 129 | end: 0x2BC60 130 | bit_depth: 4 131 | palette: tileset2_pal 132 | width: 128 133 | - type: gfx 134 | label: sprites1 135 | start: 0x2BC60 136 | end: 0x2C860 137 | bit_depth: 4 138 | palette: sprites1_pal 139 | - type: gfx 140 | label: sprites2 141 | start: 0x2C860 142 | end: 0x2D060 143 | bit_depth: 4 144 | palette: sprites2_pal 145 | - type: gfx 146 | label: sprites3 147 | start: 0x2D060 148 | end: 0x2E460 149 | bit_depth: 4 150 | palette: sprites3_pal 151 | - type: gfx 152 | label: sprites4 153 | start: 0x2E460 154 | end: 0x2E860 155 | bit_depth: 4 156 | palette: sprites4_pal 157 | - type: gfx 158 | label: sprites5 159 | start: 0x2E860 160 | end: 0x2F860 161 | bit_depth: 4 162 | palette: sprites5_pal 163 | - type: bin 164 | label: map1 165 | compress: byte_rle 166 | start: 0x137AB 167 | end: 0x139B7 168 | - type: bin 169 | label: map2 170 | compress: byte_rle 171 | start: 0x139B7 172 | end: 0x13B5D 173 | - type: bin 174 | label: map3 175 | compress: byte_rle 176 | start: 0x13B5D 177 | end: 0x13D56 178 | - type: bin 179 | label: map4 180 | compress: byte_rle 181 | start: 0x13D56 182 | end: 0x13EC3 183 | - type: bin 184 | label: mape 185 | compress: byte_rle 186 | start: 0x13EC3 187 | end: 0x13F7D 188 | - type: bin 189 | label: mapi 190 | compress: byte_rle 191 | start: 0x13F7D 192 | end: 0x13FDD 193 | - type: data 194 | label: hdmaGradient0List0 195 | start: 0x13FDD 196 | end: 0x14063 197 | - type: data 198 | label: hdmaGradient0List1 199 | start: 0x14063 200 | end: 0x140E9 201 | - type: data 202 | label: hdmaGradient0List2 203 | start: 0x140E9 204 | end: 0x1416F 205 | - type: data 206 | label: hdmaGradient1List0 207 | start: 0x1416F 208 | end: 0x141FD 209 | - type: data 210 | label: hdmaGradient1List1 211 | start: 0x141FD 212 | end: 0x1428B 213 | - type: data 214 | label: hdmaGradient1List2 215 | start: 0x1428B 216 | end: 0x14319 217 | - type: data 218 | label: hdmaGradient2List0 219 | start: 0x14319 220 | end: 0x1439F 221 | - type: data 222 | label: hdmaGradient2List1 223 | start: 0x1439F 224 | end: 0x14425 225 | - type: data 226 | label: hdmaGradient2List2 227 | start: 0x14425 228 | end: 0x144AB 229 | - type: data 230 | label: hdmaGradient3List0 231 | start: 0x144AB 232 | end: 0x14535 233 | - type: data 234 | label: hdmaGradient3List1 235 | start: 0x14535 236 | end: 0x145BF 237 | - type: data 238 | label: hdmaGradient3List2 239 | start: 0x145BF 240 | end: 0x14649 241 | - type: data 242 | label: hdmaGradient4List0 243 | start: 0x14649 244 | end: 0x146D3 245 | - type: data 246 | label: hdmaGradient4List1 247 | start: 0x146D3 248 | end: 0x1475D 249 | - type: data 250 | label: hdmaGradient4List2 251 | start: 0x1475D 252 | end: 0x147E7 253 | - type: data 254 | label: hdmaGradient5List0 255 | start: 0x147E7 256 | end: 0x14875 257 | - type: data 258 | label: hdmaGradient5List1 259 | start: 0x14875 260 | end: 0x14903 261 | - type: data 262 | label: hdmaGradient5List2 263 | start: 0x14903 264 | end: 0x14991 265 | - type: data 266 | label: hdmaGradient7List0 267 | start: 0x14991 268 | end: 0x149BD 269 | - type: data 270 | label: hdmaGradient6List1 271 | start: 0x149BD 272 | end: 0x149E9 273 | - type: data 274 | label: hdmaGradient6List2 275 | start: 0x149E9 276 | end: 0x14A15 277 | - type: gfx 278 | label: tileset2alt_gfx 279 | start: 0x14A15 280 | end: 0x15215 281 | bit_depth: 4 282 | palette: tileset2alt_pal 283 | - type: bin 284 | label: music_title 285 | start: 0x15215 286 | end: 0x1542C 287 | - type: data 288 | label: music_title_size 289 | start: 0x1542C 290 | end: 0x1542E 291 | - type: bin 292 | label: music_game_start 293 | start: 0x1542E 294 | end: 0x1551A 295 | - type: data 296 | label: music_game_size 297 | start: 0x1551A 298 | end: 0x1551C 299 | - type: bin 300 | label: music_stage_clear 301 | start: 0x1551C 302 | end: 0x155A0 303 | - type: data 304 | label: music_stage_clear_size 305 | start: 0x155A0 306 | end: 0x155A2 307 | - type: bin 308 | label: music_hammer 309 | start: 0x155A2 310 | end: 0x15636 311 | - type: data 312 | label: music_hammer_size 313 | start: 0x15636 314 | end: 0x15638 315 | - type: bin 316 | label: music_level1 317 | start: 0x156a0 318 | end: 0x15742 319 | - type: data 320 | label: music_level1_size 321 | start: 0x15742 322 | end: 0x157AA 323 | - type: bin 324 | label: music_lose 325 | start: 0x157AA 326 | end: 0x158A0 327 | - type: data 328 | label: music_lose_size 329 | start: 0x158A0 330 | end: 0x158A2 331 | - type: bin 332 | label: music_time_out 333 | start: 0x158A2 334 | end: 0x15906 335 | - type: data 336 | label: music_time_out_size 337 | start: 0x15906 338 | end: 0x15908 339 | - type: bin 340 | label: music_victory 341 | start: 0x15908 342 | end: 0x15A4F 343 | - type: data 344 | label: music_victory_size 345 | start: 0x15A4F 346 | end: 0x15A51 347 | - type: text 348 | label: versionStr 349 | start: 0x15E29 350 | end: 0x15E2F 351 | - type: data 352 | label: tileAttribute 353 | start: 0x15E2F 354 | end: 0x1622F 355 | - type: array 356 | label: flipTable 357 | start: 0x16729 358 | end: 0x16829 359 | - type: array 360 | label: itemSpriteTable 361 | size: 2 362 | start: 0x1622F 363 | end: 0x16239 364 | - type: array 365 | label: fireBallJumpInAnimation 366 | start: 0x16239 367 | end: 0x16299 368 | - type: array 369 | label: fireBallSpawnAnim 370 | start: 0x16299 371 | end: 0x162C5 372 | size: 2 373 | - type: array 374 | label: bounce_speed 375 | start: 0x162C5 376 | end: 0x162D9 377 | size: 2 378 | - type: array 379 | label: playerWalkAnimLeft 380 | start: 0x162D9 381 | end: 0x162E5 382 | size: 2 383 | - type: array 384 | label: playerWalkAnimRight 385 | start: 0x162E5 386 | end: 0x162F1 387 | size: 2 388 | - type: sound 389 | label: sample0 390 | start: 0x20800 391 | end: 0x20B84 392 | structs: 393 | enemy_object: 394 | - name: x 395 | type: db 396 | - name: y 397 | type: db 398 | labels: 399 | EmptyHandler: 0x0 400 | EmptyNMI: 0x1 401 | tcc__snesinit: 0x2 402 | VBlank: 0x91 403 | tcc__start: 0xb7 404 | tcc__mul: 0x12c 405 | tcc__mull: 0x13f 406 | tcc__udiv: 0x161 407 | tcc__div: 0x180 408 | tcc__divl: 0x1b7 409 | tcc__udivl: 0x221 410 | _div2: 0x239 411 | tcc__divdi3: 0x25b 412 | tcc__moddi3: 0x273 413 | tcc__udivdi3: 0x28f 414 | tcc__umoddi3: 0x2a7 415 | tcc__shldi3: 0x2c3 416 | tcc__sardi3: 0x2d8 417 | _shr: 0x2f2 418 | tcc__shrdi3: 0x2fa 419 | tcc__jsl_r10: 0x30a 420 | tcc__jsl_ind_r9: 0x316 421 | set_pixelize: 0x324 422 | set_scroll: 0x366 423 | set_bright: 0x56A 424 | copy_mem: 0x59C 425 | fill_vram: 0x647 426 | copy_from_vram: 0x6AA 427 | copy_to_vram: 0x71E 428 | __malloc_init: 0x780 429 | update_palette: 0x801 430 | set_palette: 0x857 431 | main: 0x885 432 | show_pal_warning: 0xAE8 433 | show_logo: 0xBE0 434 | game_loop: 0xDF4 435 | cutscene_next_level: 0x430D 436 | cutscene_intro: 0x4BE7 437 | cutscene_intro_princess_hold: 0x56F2 438 | enemy_add: 0x57DA 439 | cutscene_level: 0x5BEB 440 | cutscene_levels_clear: 0x6456 441 | kong_stand_animation_both: 0x7096 442 | game_level4: 0x713B 443 | game_level3: 0x7470 444 | pad_poll_trigger: 0x7623 445 | game_show_ladders: 0x7641 446 | game_level1: 0x774B 447 | player_clip_right: 0x7F30 448 | title_screen_update: 0x7F95 449 | game_level2: 0x8000 450 | enemy_process: 0x8DF8 451 | enemy_add: 0xC35E 452 | enemy_clear: 0xC9E7 453 | player_clip_left: 0xCA84 454 | game_update_vram_animation: 0xCAEB 455 | game_wait_and_update_nametables: 0xCB79 456 | game_ladder_sound: 0xCDF3 457 | process_elevators: 0xCE6E 458 | process_items: 0xD459 459 | game_show_princess: 0xDA71 460 | pad_poll: 0xDC82 461 | enemy_remove: 0xC158 462 | enemy_check_object_jump: 0xC06B 463 | cutscene_update_platform: 0x59E4 464 | kong_stand_animation_level1: 0xDDFE 465 | kong_stand_animation_level2: 0xDCA0 466 | barrel_show_fire: 0xDEE2 467 | game_show_kong: 0xDFA4 468 | game_update_stats: 0xE300 469 | game_update_bonus: 0xE3B7 470 | game_add_score: 0xE51A 471 | game_update_lives: 0xE675 472 | game_item_update_oam: 0xE929 473 | game_item_add: 0xEB5F 474 | player_ladder_div: 0xEC0A 475 | player_horz_div: 0xEC7C 476 | game_update_score: 0xE8F7 477 | particle_process: 0xECDC 478 | particle_add: 0xF72D 479 | particles_clear: 0xFA62 480 | pad_read_ports: 0xFADC 481 | particle_clear: 0xFBAC 482 | title_set_rect: 0xFC83 483 | put_num: 0xFDCB 484 | title_screen_press_start_str: 0xFD8C 485 | update_nametables: 0xFFB4 486 | game_over: 0x10000 487 | title_screen: 0x10420 488 | sound_test: 0x10938 489 | palette_fade_to: 0x110CA 490 | setup_ingame_graphics: 0x11272 491 | setup_palettes: 0x1132B 492 | setup_hdma_gradient: 0x11453 493 | set_background: 0x115D9 494 | clear_nametables: 0x129C8 495 | put_str: 0x11040 496 | nmi_wait: 0x12A41 497 | fade_screen: 0x12A6A 498 | unrle: 0x12BAD 499 | music_stop: 0x12CFD 500 | music_play: 0x12D0E 501 | sfx_play: 0x12D96 502 | spc_load_music: 0x12E02 503 | spc_setup: 0x12E3E 504 | spc_load_data: 0x12E64 505 | spc_volume: 0x1301C 506 | spc_stereo: 0x13052 507 | spc_command: 0x13068 508 | init: 0x130F2 509 | delay: 0x13321 510 | oam_clear: 0x13339 511 | oam_size: 0x13393 512 | oam_spr: 0x134EF 513 | nmi_handler: 0x13716 514 | rand: 0x13782 515 | memcpy: 0x15C2E 516 | mempcpy: 0x15C4C 517 | memmove: 0x15C71 518 | memset: 0x15C9F 519 | bzero: 0x15CB8 520 | strcmp: 0x15CD2 521 | strncmp: 0x15D0E 522 | memcmp: 0x15D3D 523 | memory: 524 | r0: 0x0 525 | r0h: 0x2 526 | r1: 0x4 527 | r1h: 0x6 528 | r2: 0x8 529 | r2h: 0xa 530 | r3: 0xc 531 | r3h: 0xe 532 | r4: 0x10 533 | r4h: 0x12 534 | r5: 0x14 535 | r5h: 0x16 536 | r9: 0x18 537 | r9h: 0x1a 538 | r10: 0x1c 539 | r10h: 0x1e 540 | f2: 0x20 541 | f2h: 0x22 542 | f3: 0x24 543 | f3h: 0x26 544 | move_insn: 0x28 545 | move_backwards_insn: 0x2c 546 | func_nmi_handler: 0x30 547 | registers_irq: 0x34 548 | tccs_snes_joypad_state: 0x7E2008 549 | tccs_particle_x: 0x7ED80A 550 | tccs_snes_joypad_state_prev: 0x7E200C 551 | tccs_particle_y: 0x7ED80E 552 | tccs_snes_joypad_state_trigger: 0x7E2010 553 | tccs_particle_cnt1: 0x7ED812 554 | tccs_snes_frame_cnt: 0x7E2014 555 | tccs_snes_rand_seed1: 0x7E2016 556 | tccs_snes_rand_seed2: 0x7E2018 557 | tccs_snes_palette: 0x7E201A 558 | tccs_kong_x: 0x7ED822 559 | tccs_kong_y: 0x7ED823 560 | tccs_kong_frame_cnt: 0x7ED825 561 | tccs_kong_state: 0x7ED826 562 | tccs_kong_delay: 0x7ED827 563 | tccs_kong_frame: 0x7ED828 564 | tccs_kong_throw_wild_barrel: 0x7ED82C 565 | tccs_kong_wild_barrel_type: 0x7ED82D 566 | tccs_kong_start: 0x7ED82E 567 | tccs_splat_x: 0x7ED82F 568 | tccs_splat_y: 0x7ED830 569 | tccs_splat_cnt: 0x7ED831 570 | tccs_princess_x: 0x7ED832 571 | tccs_princess_y: 0x7ED833 572 | tccs_platformAnimCnt: 0x7ED835 573 | tccs_particle_cnt2: 0x7ED816 574 | tccs_particle_spr: 0x7ED81A 575 | tccs_walkmap: 0x7E4083 576 | tccs_ladders_x: 0x7ED8F5 577 | tccs_ladders_y: 0x7ED90B 578 | tccs_ladders_dir: 0x7ED921 579 | tccs_ladders_cnt: 0x7ED937 580 | tccs_ladders_delay: 0x7ED94D 581 | tccs_back_buffer: 0x7E5C83 582 | tccs_snes_spc_sync: 0x7ED963 583 | tccs_snes_spc_stereo: 0x7ED964 584 | tccs_snes_spc_volume: 0x7ED966 585 | tccs__FUNC_spc_load_data_cnt: 0x7ED968 586 | tccs__FUNC_spc_load_data_i: 0x7ED969 587 | tccs__FUNC_unrle_i: 0x7ED96B 588 | tccs__FUNC_unrle_tag: 0x7ED96C 589 | tccs__FUNC_unrle_byte: 0x7ED96D 590 | tccs__FUNC_fade_screen_i: 0x7ED96E 591 | tccs__FUNC_fade_screen_volume: 0x7ED96F 592 | tccs__FUNC_clear_nametables_i: 0x7ED970 593 | tccs__FUNC_set_background_off: 0x7ED972 594 | tccs__FUNC_set_background_ptr: 0x7ED974 595 | tccs__FUNC_set_background_tile: 0x7ED976 596 | tccs__FUNC_set_background_pp: 0x7ED978 597 | tccs__FUNC_set_background_tiles_all: 0x7ED97A 598 | tccs__FUNC_set_background_i: 0x7ED97C 599 | tccs__FUNC_set_background_j: 0x7ED97D 600 | tccs__FUNC_set_background_x: 0x7ED97E 601 | tccs__FUNC_set_background_x_off: 0x7ED97F 602 | tccs__FUNC_set_background_pal: 0x7ED980 603 | tccs__FUNC_palette_fade_to_i: 0x7ED984 604 | tccs__FUNC_palette_fade_to_src: 0x7ED986 605 | tccs__FUNC_palette_fade_to_dst: 0x7ED988 606 | tccs__FUNC_put_str_i: 0x7ED98A 607 | tccs__FUNC_sound_test_i: 0x7ED98B 608 | tccs__FUNC_sound_test_j: 0x7ED98D 609 | tccs__FUNC_sound_test_num: 0x7ED98F 610 | tccs__FUNC_sound_test_screen_bright: 0x7ED991 611 | tccs__FUNC_sound_test_off: 0x7ED993 612 | tccs__FUNC_sound_test_bright: 0x7ED995 613 | tccs__FUNC_sound_test_sound: 0x7ED997 614 | tccs__FUNC_sound_test_pan: 0x7ED999 615 | tccs__FUNC_sound_test_music: 0x7ED99B 616 | tccs__FUNC_sound_test_done: 0x7ED99D 617 | tccs__FUNC_sound_test_cur: 0x7ED99F 618 | tccs__FUNC_title_set_rect_i: 0x7ED9A1 619 | tccs__FUNC_title_set_rect_j: 0x7ED9A2 620 | tccs__FUNC_title_screen_i: 0x7ED9A3 621 | tccs__FUNC_title_screen_j: 0x7ED9A5 622 | tccs__FUNC_title_screen_cnt: 0x7ED9A7 623 | tccs__FUNC_title_screen_off: 0x7ED9A9 624 | tccs__FUNC_title_screen_bright: 0x7ED9AB 625 | tccs__FUNC_title_screen_codet: 0x7ED9AD 626 | tccs__FUNC_title_screen_codeh: 0x7ED9AF 627 | tccs__FUNC_game_over_i: 0x7ED9B1 628 | tccs__FUNC_game_over_j: 0x7ED9B3 629 | tccs__FUNC_game_over_color: 0x7ED9B5 630 | tccs__FUNC_game_over_r: 0x7ED9B7 631 | tccs__FUNC_game_over_g: 0x7ED9B9 632 | tccs__FUNC_game_over_b: 0x7ED9BB 633 | tccs__FUNC_game_over_x: 0x7ED9BD 634 | tccs__FUNC_game_over_y: 0x7ED9BE 635 | tccs__FUNC_particles_clear_i: 0x7ED9BF 636 | tccs__FUNC_particle_add_i: 0x7ED9C0 637 | tccs__FUNC_particle_add_j: 0x7ED9C1 638 | tccs__FUNC_particle_add_cnt1: 0x7ED9C2 639 | tccs__FUNC_particle_add_cnt2: 0x7ED9C3 640 | tccs__FUNC_particle_add_spr: 0x7ED9C4 641 | tccs__FUNC_particle_process_i: 0x7ED9C6 642 | tccs__FUNC_particle_process_j: 0x7ED9C7 643 | tccs__FUNC_particle_process_spr: 0x7ED9C8 644 | tccs__FUNC_game_item_update_oam_i: 0x7ED9C9 645 | tccs__FUNC_game_item_update_oam_j: 0x7ED9CA 646 | tccs__FUNC_game_item_update_oam_spr: 0x7ED9CB 647 | tccs__FUNC_game_update_lives_i: 0x7ED9CD 648 | tccs__FUNC_game_add_score_prev: 0x7ED9CE 649 | tccs__FUNC_game_update_bonus_i: 0x7ED9D0 650 | tccs__FUNC_game_update_bonus_x: 0x7ED9D1 651 | tccs__FUNC_game_show_kong_ky: 0x7ED9D2 652 | tccs__FUNC_game_show_kong_kx: 0x7ED9D4 653 | tccs__FUNC_game_show_kong_i: 0x7ED9D5 654 | tccs__FUNC_game_show_kong_pp: 0x7ED9D6 655 | tccs__FUNC_game_show_princess_i: 0x7ED9D7 656 | tccs__FUNC_game_show_princess_flip: 0x7ED9D8 657 | tccs__FUNC_process_items_i: 0x7ED9DA 658 | tccs__FUNC_process_items_anim: 0x7ED9DB 659 | tccs__FUNC_process_items_score: 0x7ED9DC 660 | tccs__FUNC_process_items_particle: 0x7ED9DE 661 | tccs__FUNC_process_elevators_i: 0x7ED9E0 662 | tccs__FUNC_process_elevators_elevator: 0x7ED9E1 663 | tccs__FUNC_process_elevators_floor1: 0x7ED9E2 664 | tccs__FUNC_process_elevators_off: 0x7ED9E3 665 | tccs__FUNC_process_elevators_spr: 0x7ED9E5 666 | tccs__FUNC_game_ladder_sound_off: 0x7ED9E7 667 | tccs__FUNC_game_wait_and_update_nametables_x: 0x7ED9E8 668 | tccs__FUNC_enemy_clear_i: 0x7ED9E9 669 | tccs__FUNC_enemy_add_i: 0x7ED9EA 670 | tccs__FUNC_enemy_add_j: 0x7ED9EB 671 | tccs__FUNC_enemy_add_off: 0x7ED9EC 672 | tccs__FUNC_enemy_add_take_ladder: 0x7ED9ED 673 | tccs__FUNC_enemy_remove_fire: 0x7ED9EE 674 | tccs__FUNC_enemy_process_i: 0x7ED9EF 675 | tccs__FUNC_enemy_process_frame: 0x7ED9F0 676 | tccs__FUNC_enemy_process_fire: 0x7ED9F1 677 | tccs__FUNC_enemy_process_type: 0x7ED9F2 678 | tccs__FUNC_enemy_process_particle: 0x7ED9F3 679 | tccs__FUNC_enemy_process_jump_over: 0x7ED9F4 680 | tccs__FUNC_enemy_process_barrels_jumped: 0x7ED9F5 681 | tccs__FUNC_enemy_process_hammer: 0x7ED9F6 682 | tccs__FUNC_enemy_process_hammer_off: 0x7ED9F7 683 | tccs__FUNC_enemy_process_spr: 0x7ED9F8 684 | tccs__FUNC_enemy_process_anim: 0x7ED9FA 685 | tccs__FUNC_enemy_process_score: 0x7ED9FC 686 | tccs__FUNC_enemy_process_val: 0x7ED9FE 687 | tccs__FUNC_enemy_process_ox: 0x7EDA00 688 | tccs__FUNC_enemy_process_oy: 0x7EDA01 689 | tccs__FUNC_enemy_process_sy: 0x7EDA02 690 | tccs__FUNC_enemy_process_hx: 0x7EDA03 691 | tccs__FUNC_enemy_process_hy: 0x7EDA04 692 | tccs__FUNC_enemy_process_off: 0x7EDA05 693 | tccs__FUNC_enemy_process_random: 0x7EDA06 694 | tccs__FUNC_enemy_process_difficulty: 0x7EDA07 695 | tccs__FUNC_enemy_process_barrel_dir: 0x7EDA08 696 | tccs__FUNC_enemy_process_take_ladder: 0x7EDA09 697 | tccs__FUNC_enemy_process_dx: 0x7EDA0A 698 | tccs__FUNC_game_show_ladders_dx: 0x7EDA0C 699 | tccs__FUNC_game_show_ladders_flip: 0x7EDA0D 700 | tccs__FUNC_game_level2_i: 0x7EDA0F 701 | tccs__FUNC_game_level2_x: 0x7EDA10 702 | tccs__FUNC_game_level2_y: 0x7EDA11 703 | tccs__FUNC_game_level2_val: 0x7EDA12 704 | tccs__FUNC_game_level3_val: 0x7EDA14 705 | tccs__FUNC_game_level4_i: 0x7EDA16 706 | tccs__FUNC_game_level4_x: 0x7EDA17 707 | tccs__FUNC_game_level4_y: 0x7EDA18 708 | tccs__FUNC_game_level4_val: 0x7EDA19 709 | tccs_snes_oam: 0x7E221A 710 | tccs__FUNC_cutscene_levels_clear_ptr: 0x7EDA1B 711 | tccs__FUNC_cutscene_levels_clear_frame: 0x7EDA1D 712 | tccs__FUNC_cutscene_levels_clear_spr: 0x7EDA1F 713 | tccs__FUNC_cutscene_levels_clear_i: 0x7EDA21 714 | tccs__FUNC_cutscene_levels_clear_j: 0x7EDA22 715 | tccs__FUNC_cutscene_levels_clear_x: 0x7EDA23 716 | tccs__FUNC_cutscene_levels_clear_bright: 0x7EDA24 717 | tccs__FUNC_cutscene_levels_clear_fall_start: 0x7EDA25 718 | tccs__FUNC_cutscene_levels_clear_fall_ground: 0x7EDA26 719 | tccs__FUNC_cutscene_levels_clear_princess_frame: 0x7EDA27 720 | tccs__FUNC_cutscene_levels_clear_bounce_cnt: 0x7EDA28 721 | tccs__FUNC_cutscene_levels_clear_shake_cnt: 0x7EDA29 722 | tccs__FUNC_cutscene_levels_clear_platform_y: 0x7EDA2A 723 | tccs__FUNC_cutscene_levels_clear_platform_hit: 0x7EDA2E 724 | tccs__FUNC_cutscene_levels_clear_ky: 0x7EDA32 725 | tccs__FUNC_cutscene_levels_clear_kdy: 0x7EDA34 726 | tccs__FUNC_cutscene_level_i: 0x7EDA36 727 | tccs__FUNC_cutscene_level_bright: 0x7EDA37 728 | tccs__FUNC_cutscene_level_delay: 0x7EDA38 729 | tccs__FUNC_cutscene_level_frame: 0x7EDA39 730 | tccs__FUNC_cutscene_level_ptr: 0x7EDA3B 731 | tccs__FUNC_cutscene_level_stack_y: 0x7EDA3D 732 | tccs__FUNC_cutscene_level_stack_ty: 0x7EDA45 733 | tccs__FUNC_cutscene_level_stack_dy: 0x7EDA4D 734 | tccs__FUNC_cutscene_level_stack_frame: 0x7EDA55 735 | tccs__FUNC_cutscene_level_stack_jump: 0x7EDA59 736 | tccs__FUNC_cutscene_level_stack_delay: 0x7EDA5D 737 | tccs__FUNC_cutscene_level_y: 0x7EDA61 738 | tccs__FUNC_cutscene_update_platform_i: 0x7EDA63 739 | tccs__FUNC_cutscene_update_platform_j: 0x7EDA64 740 | tccs__FUNC_cutscene_update_platform_cnt: 0x7EDA65 741 | tccs__FUNC_cutscene_update_platform_hgt: 0x7EDA66 742 | tccs__FUNC_cutscene_intro_show_ladders_i: 0x7EDA67 743 | tccs__FUNC_cutscene_intro_show_ladders_delay: 0x7EDA68 744 | tccs__FUNC_cutscene_intro_princess_hold_princess_frame: 0x7EDA69 745 | tccs__FUNC_cutscene_intro_i: 0x7EDA6A 746 | tccs__FUNC_cutscene_intro_j: 0x7EDA6B 747 | tccs__FUNC_cutscene_intro_y: 0x7EDA6C 748 | tccs__FUNC_cutscene_intro_bright: 0x7EDA6D 749 | tccs__FUNC_cutscene_intro_hold: 0x7EDA6E 750 | tccs__FUNC_cutscene_intro_jump_cnt: 0x7EDA6F 751 | tccs__FUNC_cutscene_intro_princess_frame: 0x7EDA70 752 | tccs__FUNC_cutscene_intro_frame: 0x7EDA71 753 | tccs__FUNC_cutscene_intro_ptr: 0x7EDA73 754 | tccs__FUNC_cutscene_intro_ky: 0x7EDA75 755 | tccs__FUNC_cutscene_intro_kdy: 0x7EDA77 756 | tccs__FUNC_cutscene_next_level_i: 0x7EDA79 757 | tccs__FUNC_cutscene_next_level_t1: 0x7EDA7A 758 | tccs__FUNC_cutscene_next_level_t2: 0x7EDA7B 759 | tccs__FUNC_cutscene_next_level_princess_frame: 0x7EDA7C 760 | tccs__FUNC_cutscene_next_level_hold: 0x7EDA7D 761 | tccs__FUNC_cutscene_next_level_side: 0x7EDA7E 762 | tccs__FUNC_cutscene_next_level_ky: 0x7EDA7F 763 | tccs__FUNC_cutscene_next_level_kdy: 0x7EDA81 764 | tccs__FUNC_cutscene_next_level_kong_tx: 0x7EDA83 765 | tccs__FUNC_cutscene_next_level_kong_oy: 0x7EDA85 766 | tccs__FUNC_cutscene_next_level_x_off: 0x7EDA87 767 | tccs__FUNC_cutscene_next_level_xp_off: 0x7EDA89 768 | tccs__FUNC_cutscene_next_level_heart_x: 0x7EDA8B 769 | tccs__FUNC_cutscene_next_level_heart_y: 0x7EDA8C 770 | tccs__FUNC_game_loop_i: 0x7EDA8D 771 | tccs__FUNC_game_loop_j: 0x7EDA8F 772 | tccs__FUNC_game_loop_k: 0x7EDA91 773 | tccs__FUNC_game_loop_ptr: 0x7EDA93 774 | tccs__FUNC_game_loop_off: 0x7EDA95 775 | tccs__FUNC_game_loop_bright: 0x7EDA97 776 | tccs__FUNC_game_loop_yoff: 0x7EDA99 777 | tccs__FUNC_game_loop_spr: 0x7EDA9B 778 | tccs__FUNC_game_loop_start_delay: 0x7EDA9D 779 | tccs__FUNC_game_loop_n: 0x7EDA9F 780 | tccs__FUNC_game_loop_floor1: 0x7EDAA0 781 | tccs__FUNC_game_loop_floor2: 0x7EDAA1 782 | tccs__FUNC_game_loop_clear: 0x7EDAA2 783 | tccs__FUNC_game_loop_anim: 0x7EDAA3 784 | tccs__FUNC_game_loop_pause: 0x7EDAA4 785 | tccs__FUNC_game_loop_elevator: 0x7EDAA5 786 | tccs__FUNC_game_loop_player_fall_sound: 0x7EDAA6 787 | tccs__FUNC_game_loop_x: 0x7EDAA7 788 | tccs__FUNC_game_loop_y: 0x7EDAA9 789 | tccs__FUNC_game_loop_dy: 0x7EDAAB 790 | tccs__FUNC_show_logo_i: 0x7EDAAD 791 | tccs__FUNC_show_logo_j: 0x7EDAAF 792 | tccs__FUNC_show_logo_n: 0x7EDAB1 793 | tccs__FUNC_show_logo_bright: 0x7EDAB3 794 | tccs__FUNC_show_pal_warning_i: 0x7EDAB5 795 | tccs__FUNC_show_pal_warning_bright: 0x7EDAB7 796 | tccs_map: 0x7E3C83 797 | tccs_snes_ntsc: 0x7E243A 798 | tccs_snes_skip_cnt: 0x7E243C 799 | tccs__FUNC_nmi_handler_i: 0x7E243E 800 | tccs__FUNC_nmi_wait_i: 0x7E2440 801 | tccs__FUNC_pad_read_ports_i: 0x7E2442 802 | tccs__FUNC_pad_read_ports_j: 0x7E2444 803 | tccs__FUNC_oam_spr_offx: 0x7E2446 804 | tccs__FUNC_oam_size_offx: 0x7E2448 805 | tccs__FUNC_oam_size_c: 0x7E244A 806 | tccs__FUNC_oam_clear_i: 0x7E244B 807 | tccs__FUNC_init_i: 0x7E244D 808 | tccs_global_stereo: 0x7E244F 809 | tccs_global_volume: 0x7E2451 810 | tccs_game_frame_cnt: 0x7E2453 811 | tccs_game_bg_anim: 0x7E2455 812 | tccs_game_level: 0x7E2457 813 | tccs_game_lives: 0x7E2458 814 | tccs_game_rivets: 0x7E2459 815 | tccs_game_score: 0x7E245A 816 | tccs_back_graphics: 0x7EBC83 817 | tccs_game_best_score: 0x7E245C 818 | tccs_game_loops: 0x7E245E 819 | tccs_game_bonus: 0x7E245F 820 | tccs_game_score_change: 0x7E2461 821 | tccs_game_bonus_change: 0x7E2462 822 | tccs_game_bonus_cnt: 0x7E2463 823 | tccs_game_level_difficulty: 0x7E2464 824 | tccs_game_level_difficulty_count: 0x7E2465 825 | tccs_game_object_jump: 0x7E2467 826 | tccs_game_bounce_delay: 0x7E2468 827 | tccs_game_bounce_speed: 0x7E2469 828 | tccs_game_fireballs: 0x7E246A 829 | tccs_game_fireballs_max: 0x7E246B 830 | tccs_game_test_mode: 0x7E246C 831 | tccs_game_hard_mode: 0x7E246D 832 | tccs_game_update_palette: 0x7E246E 833 | tccs_game_flip: 0x7E246F 834 | tccs_game_lives_update: 0x7E2470 835 | tccs_game_belts_update: 0x7E2471 836 | tccs_game_rivets_update: 0x7E2472 837 | tccs_barrel_fire: 0x7E2473 838 | tccs_barrel_fire_x: 0x7E2474 839 | tccs_barrel_fire_y: 0x7E2475 840 | tccs_barrel_fire_off: 0x7E2476 841 | tccs_conveyor_dir: 0x7E2478 842 | tccs_conveyor_cnt: 0x7E247B 843 | tccs_conveyor_items: 0x7E247E 844 | tccs_conveyor_cnt_middle: 0x7E2481 845 | tccs_nametable3: 0x7E3483 846 | tccs_snes_palette_to: 0x7ED483 847 | tccs_player_x: 0x7ED683 848 | tccs_player_y: 0x7ED685 849 | tccs_player_step: 0x7ED687 850 | tccs_player_ladder: 0x7ED688 851 | tccs_player_jump: 0x7ED689 852 | tccs_player_jump_cnt: 0x7ED68A 853 | tccs_player_jump_y: 0x7ED68B 854 | tccs_player_anim: 0x7ED68D 855 | tccs_player_dir: 0x7ED68F 856 | tccs_player_dir_prev: 0x7ED690 857 | tccs_player_fall: 0x7ED691 858 | tccs_player_speed_div: 0x7ED692 859 | tccs_player_rivet_delay: 0x7ED693 860 | tccs_player_hammer_time: 0x7ED694 861 | tccs_player_hammer_phase: 0x7ED696 862 | tccs_player_hammer_cnt: 0x7ED697 863 | tccs_items_all: 0x7ED698 864 | tccs_item_type: 0x7ED699 865 | tccs_item_x: 0x7ED69F 866 | tccs_item_y: 0x7ED6A5 867 | tccs_elevators_all: 0x7ED6AB 868 | tccs_elevator_x: 0x7ED6AC 869 | tccs_elevator_y: 0x7ED6B8 870 | tccs_elevator_dy: 0x7ED6C4 871 | tccs_elevator_top: 0x7ED6D0 872 | tccs_elevator_bottom: 0x7ED6D1 873 | tccs_enemy_free: 0x7ED6D2 874 | tccs_enemy_all: 0x7ED6D3 875 | tccs_enemy_type: 0x7ED6D4 876 | tccs_enemy_x: 0x7ED6E4 877 | tccs_enemy_y: 0x7ED6F4 878 | tccs_enemy_sy: 0x7ED704 879 | tccs_enemy_dx: 0x7ED714 880 | tccs_enemy_fall: 0x7ED724 881 | tccs_enemy_anim: 0x7ED734 882 | tccs_enemy_land: 0x7ED744 883 | tccs_enemy_ix: 0x7ED754 884 | tccs_enemy_iy: 0x7ED774 885 | tccs_enemy_idy: 0x7ED794 886 | tccs_nametable1: 0x7E2483 887 | tccs_enemy_cnt: 0x7ED7B4 888 | tccs_enemy_ladder: 0x7ED7C4 889 | tccs_enemy_spawn: 0x7ED7D4 890 | tccs_nametable2: 0x7E2C83 891 | tccs_enemy_speed: 0x7ED7E4 892 | tccs_fireball_spawn_all: 0x7ED7F4 893 | tccs_fireball_spawn_x: 0x7ED7F5 894 | tccs_fireball_spawn_y: 0x7ED7FD 895 | tccs_particle_free: 0x7ED805 896 | tccs_particle_type: 0x7ED806 897 | --------------------------------------------------------------------------------