def runBinary(self, pasfile, breakpointlabels, randoms, counters): # breakpointlabels is a list of labels to stop emulation on program counter hit # prepare binary file binfile = self.buildBinary(pasfile) binstart = int(self.config.get('params','binaryLocation'),16) memsize = int(self.config.get('params','memorySize'),16) # load into emulator memory with open(binfile, "rb") as filedata: self.m = MMU([ (0, binstart), (binstart, binstart + memsize, False, filedata) ]) # initialize cpu self.c = CPU(self.m, self.labels['START']) self.cmdCount = 0 # add user defined random registers if len(randoms) > 0: for item in randoms: self.randoms.append(item) # add user defined counters if len(counters) > 0: for item in counters: self.counters.append(item) # update randoms converting labels to direct addresses for value in self.randoms: if not isinstance(value, int): labelname = self.validateLabel(value.upper()) self.randoms.remove(value) self.randoms.append(self.labels[labelname]) # update counters converting labels to direct addresses for value in self.counters: if not isinstance(value, int): labelname = self.validateLabel(value.upper()) self.counters.remove(value) self.counters.append(self.labels[labelname]) # convert breakpoint labels into addresses self.breakpointadressess = [] if len(breakpointlabels) > 0: for blabel in breakpointlabels: labelname = self.validateLabel(blabel.upper()) self.breakpointadressess.append(self.labels[labelname]) # make sure all elements are unique self.randoms = list(set(self.randoms)) self.counters = list(set(self.counters)) self.breakpointadressess = list(set(self.breakpointadressess)) self.runEmu() return [self.c,self.m,self.labels]
def test_create(self): MMU([ (0, 128, False, None) ]) MMU([ (0, 128, False, None), (128, 128, True, None) ])
def test_index_error(self): m = MMU([(0, 128)]) with self.assertRaises(IndexError): m.write(-1, 0) with self.assertRaises(IndexError): m.write(128, 0) with self.assertRaises(IndexError): m.read(-1) with self.assertRaises(IndexError): m.read(128)
def test_reset(self): m = MMU([(0, 16, True), (16, 16, False)]) m.blocks[0]['memory'][0] = 5 m.write(16, 10) m.reset() self.assertEqual(m.read(0), 5) self.assertEqual(m.read(16), 0)
def test_addBlock_overlapping(self): m = MMU([]) m.addBlock(128, 128) with self.assertRaises(MemoryRangeError): m.addBlock(0, 129) with self.assertRaises(MemoryRangeError): m.addBlock(255, 128)
def test_write_readonly(self): m = MMU([(0, 16, True), (16, 16), (32, 16, True)]) with self.assertRaises(ReadOnlyError): m.write(8, 1) m.write(20, 1) with self.assertRaises(ReadOnlyError): m.write(40, 1)
def test_nestest(self): path = os.path.join( os.path.dirname(os.path.realpath(__file__)), "files", "nestest_mod.nes" ) with open(path, "rb") as f: mmu = MMU([ (0x0000, 0x800), # RAM (0x2000, 0x8), # PPU (0x4000, 0x18), (0x8000, 0xc000, True, f, 0x3ff0) # ROM ]) c = CPU(mmu, 0xc000) c.r.s = 0xfd # Not sure why the stack starts here. while c.r.pc != 0xc66e: try: c.step() except Exception as e: print(c.r) print(traceback.format_exc()) raise e self.assertEqual(c.mmu.read(0x2), 0x00, hex(c.mmu.read(0x2))) self.assertEqual(c.mmu.read(0x3), 0x00, hex(c.mmu.read(0x3)))
def init_cpu(mem, pc_value): mmu = MMU([(0x00, len(mem), False, mem) # readonly = False ]) c = CPU(mmu, pc_value) return c
def test_create_with_list(self): m = MMU([ (0, 128, False, [1, 2, 3]) ]) self.assertEqual(m.blocks[0]['memory'][0], 1) self.assertEqual(m.blocks[0]['memory'][2], 3) self.assertEqual(m.blocks[0]['memory'][3], 0)
def test_create_with_file(self): path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "files", "test_load_file.bin") with open(path, "rb") as f: m = MMU([(0, 128, True, f)]) self.assertEqual(m.blocks[0]['memory'][0], 0xa9)
out = a.assemble(open(f)) # Put the input into an array input_path = '../input.txt' with open(input_path, 'rb') as f: input_bytes = [] b = f.read(1) while b: input_bytes.append(ord(b)) b = f.read(1) # Add another new line to terminate input_bytes += [10] # Initialize memory and the CPU m = MMU([ (0x00, 0x200), # Create RAM with 512 bytes (0x1000, 0x1000 + len(input_bytes), True, input_bytes), # Input bytes (0xC000, 0xFFFF, True, out ) # Create ROM starting at 0xC000 with your program. ]) c = CPU(m, 0xC000) while not c.r.getFlag('B'): # Run until we break c.step() # Print out the 3-byte answer as decimal print(m.read(0x10) + (m.read(0x11) << 8) + (m.read(0x12) << 16))
def test_addBlock(self): m = MMU([]) m.addBlock(0, 128, False, None) m.addBlock(128, 128, True, None)
def test_read(self): m = MMU([(0, 128)]) m.write(16, 5) m.write(64, 111) self.assertEqual(5, m.read(16)) self.assertEqual(111, m.read(64))
def test_write_multiple_blocks(self): m = MMU([(0, 128), (1024, 128)]) m.write(16, 25) self.assertEqual(m.blocks[0]['memory'][16], 25) m.write(1056, 55) self.assertEqual(m.blocks[1]['memory'][32], 55, m.blocks[1]['memory'])
def test_write(self): m = MMU([(0, 128)]) m.write(16, 25) self.assertEqual(m.blocks[0]['memory'][16], 25)
def test_create_overlapping(self): with self.assertRaises(MemoryRangeError): MMU([(0, 129), (128, 128)])
def test_create_empty(self): MMU([])
class testUtils: validatedAttribs = ['START', 'MAIN.@EXIT'] def __init__(self): cfile = "config.ini" if not os.path.exists(cfile): raise FileNotFoundError('config.ini file not found') self.config = configparser.ConfigParser() self.config.read(cfile) if not os.path.exists(self.config.get('paths','mp')): raise FileNotFoundError('MadPascal compiler not found here: {}'.format(self.config.get('paths','mp'))) if not os.path.exists(self.config.get('paths','mads')): raise FileNotFoundError('MadAssembler not found here: {}'.format(self.config.get('paths','mads'))) if not os.path.exists(self.config.get('paths','base')): raise FileNotFoundError('base directory not found here: {}'.format(self.config.get('paths','base'))) self.clearRandoms() self.clearCounters() def validateLabel(self, label): labelname = "MAIN.{}".format(label.upper()) if not labelname in self.labels: raise IndexError("Label not found: {}".format(labelname)) return labelname def getLabels(self, tabfile): labels = {} with open(tabfile) as f: lines = f.readlines() if len(lines)>2: for linenum in range(2,len(lines)): elems = lines[linenum].strip().split('\t') labels[elems[2]]=int(elems[1],16) return labels def clearDir(self, dirname): if os.path.exists(dirname): shutil.rmtree(dirname) os.makedirs(dirname) def buildBinary(self, pasfile): # prepare paths dirname = os.path.dirname(pasfile) rawname = os.path.splitext(os.path.basename(pasfile))[0] srcasmfile = "{}/{}.a65".format(dirname, rawname) rawpath = "{}/{}".format(self.config.get('paths','tempdir'),rawname) asmfile = "{}.a65".format(rawpath) binfile = "{}.bin".format(rawpath) tabfile = "{}.tab".format(rawpath) # compile and validate rc = run("{} {} -t raw -o".format(self.config.get('paths','mp'), pasfile), shell = True) if rc.returncode != 0 : raise NotImplementedError("Mad-Pascal exit code = {}. Probably compilation error occured".format(rc.returncode)) if not os.path.exists(srcasmfile): raise NotImplementedError("File {} not found! Probably compilation error occured".format(srcasmfile)) # copy assembly file to tempdir if needed if srcasmfile != asmfile: rc = shutil.move(srcasmfile, asmfile) while not os.path.exists(asmfile): sleep(0.1) # assemby file and validate rc = run("{} {} -x -i:{} -t:{} -o:{}".format(self.config.get('paths','mads'), asmfile, self.config.get('paths','base'), tabfile, binfile), shell = True) if rc.returncode != 0: raise NotImplementedError("Mad-Assembler exit code = {}. Probably nasty assemblation error occured".format(rc.returncode)) # parse all labels self.labels = self.getLabels(tabfile) # check for required labels for attrib in self.validatedAttribs: if not attrib in self.labels: raise IndexError("Label not found: {}".format(attrib)) return binfile def runEmu(self): # this method executes code until reaches end of program, or hits breakpoint # it returns False on breakpoints and True on the end of code reached timeout = int(self.config.get('params','cpuTimeout'), 16) while self.c.r.pc != self.labels['MAIN.@EXIT']: self.c.step() self.cmdCount += 1 # check for timeout if self.cmdCount > timeout: raise TimeoutError("CPU timeouted after {} commands".format(self.cmdCount)) # check for breakpoints if len(self.breakpointadressess) > 0: if self.c.r.pc in self.breakpointadressess: return False # check if there is something to randomize if len(self.randoms) > 0: for address in self.randoms: self.m.write(address, random.randint(255)) # check if there are counters to increment if len(self.counters) > 0: for address in self.counters: curval = self.m.read(address) curval = (curval + 1) % 256 self.m.write(address, curval) return True def runBinary(self, pasfile, breakpointlabels, randoms, counters): # breakpointlabels is a list of labels to stop emulation on program counter hit # prepare binary file binfile = self.buildBinary(pasfile) binstart = int(self.config.get('params','binaryLocation'),16) memsize = int(self.config.get('params','memorySize'),16) # load into emulator memory with open(binfile, "rb") as filedata: self.m = MMU([ (0, binstart), (binstart, binstart + memsize, False, filedata) ]) # initialize cpu self.c = CPU(self.m, self.labels['START']) self.cmdCount = 0 # add user defined random registers if len(randoms) > 0: for item in randoms: self.randoms.append(item) # add user defined counters if len(counters) > 0: for item in counters: self.counters.append(item) # update randoms converting labels to direct addresses for value in self.randoms: if not isinstance(value, int): labelname = self.validateLabel(value.upper()) self.randoms.remove(value) self.randoms.append(self.labels[labelname]) # update counters converting labels to direct addresses for value in self.counters: if not isinstance(value, int): labelname = self.validateLabel(value.upper()) self.counters.remove(value) self.counters.append(self.labels[labelname]) # convert breakpoint labels into addresses self.breakpointadressess = [] if len(breakpointlabels) > 0: for blabel in breakpointlabels: labelname = self.validateLabel(blabel.upper()) self.breakpointadressess.append(self.labels[labelname]) # make sure all elements are unique self.randoms = list(set(self.randoms)) self.counters = list(set(self.counters)) self.breakpointadressess = list(set(self.breakpointadressess)) self.runEmu() return [self.c,self.m,self.labels] def setRandomByte(self, address): self.randoms.append(address) def setCounterByte(self, address): self.counters.append(address) def clearRandoms(self): self.randoms = [] def clearCounters(self): self.counters = [] ############################# # TEST RUNNER API # ############################# def runFile(self, pasfile, breakpoints = [], randoms = [], counters = []): # breakpointlabels is a list of labels to stop emulation on program counter hit self.clearDir(self.config.get('paths','tempdir')) return self.runBinary(pasfile, breakpoints, randoms, counters) def runCode(self, pascode, breakpoints = [], randoms = [], counters = []): # breakpointlabels is a list of labels to stop emulation on program counter hit self.clearDir(self.config.get('paths','tempdir')) pasfile = "{}/temp.pas".format(self.config.get('paths','tempdir')) with open(pasfile, 'w') as f: f.write(pascode) return self.runBinary(pasfile, breakpoints, randoms, counters) def resume(self): self.runEmu() return [self.c,self.m,self.labels] ############################# # VARIABLE AND DATA GETTERS # ############################# def varByte(self, varlabel): labelname = self.validateLabel(varlabel) return self.m.read(self.labels[labelname]) def varWord(self, varlabel): labelname = self.validateLabel(varlabel) return self.m.readWord(self.labels[labelname]) def varCardinal(self, varlabel): labelname = self.validateLabel(varlabel) upper = self.m.readWord(self.labels[labelname]) lower = self.m.readWord(self.labels[labelname] + 2) return (lower << 16) + upper def getByte(self, address): return self.m.read(address) def getWord(self, address): return self.m.readWord(address) def getCardinal(self, address): upper = self.m.readWord(address) lower = self.m.readWord(address + 2) return (lower << 16) + upper def getArray(self, address, size, element_size = 1): resultArray = [] elemAddress = address for i in range(size): byteshift = 0 elem = 0 for ebyte in range(element_size): elem += self.m.read(elemAddress) << byteshift byteshift += 8 elemAddress += 1 resultArray.append(elem) return resultArray def isVarTrue(self, varlabel): labelname = self.validateLabel(varlabel) return self.m.read(self.labels[labelname]) == self.labels['TRUE'] def isTrue(self, address): return self.m.read(address) == self.labels['TRUE']
def load_level(stage, prg_rom, chr_rom, sym_file, nes_palette): # Initialize the MMU / CPU mmu = MMU([(0x0, _WORKING_RAM_SIZE, False, []), (0x8000, 0x10000, True, list(prg_rom))]) cpu = CPU(mmu, 0x0) # Execute some preamble subroutines which set up variables used by the main subroutines. if isinstance(stage, tuple): world_num, area_num = stage mmu.write(sym_file['WORLDNUMBER'], world_num - 1) mmu.write(sym_file['AREANUMBER'], area_num - 1) _execute_subroutine(cpu, sym_file['LOADAREAPOINTER']) else: area_pointer = stage mmu.write(sym_file['AREAPOINTER'], area_pointer) mmu.write(sym_file['HALFWAYPAGE'], 0) mmu.write(sym_file['ALTENTRANCECONTROL'], 0) mmu.write(sym_file['PRIMARYHARDMODE'], 0) mmu.write(sym_file['OPERMODE_TASK'], 0) _execute_subroutine(cpu, sym_file['INITIALIZEAREA']) # Extract the palette. palette = _load_palette(mmu, sym_file, nes_palette) # Repeatedly extract meta-tile columns, until the level starts repeating. cols = [] for column_pos in range(1000): _execute_subroutine(cpu, sym_file['AREAPARSERCORE']) cols.append(_get_metatile_buffer(mmu, sym_file)) _execute_subroutine(cpu, sym_file['INCREMENTCOLUMNPOS']) if len(cols) >= 96 and cols[-48:] == cols[-96:-48]: cols = cols[:-80] break level = np.array(cols).T # Render a dict of metatiles. mtiles = { mtile: _render_metatile(mmu, chr_rom, mtile, palette) for mtile in set(level.flatten()) } return level, mtiles
def _cpu(self, ram=(0, 0x200, False), rom=(0x1000, 0x100), romInit=None, pc=0x1000): return CPU(MMU([ram, rom + (True, romInit)]), pc)