├── nespy ├── __init__.py ├── apu.pxd ├── util.pxd ├── apu.py ├── ppu.pxd ├── cpu.pxd ├── exceptions.py ├── nes.pxd ├── clock.pxd ├── util.py ├── clock.py ├── ppu.py ├── nes.py ├── enum.py └── cpu.py ├── requirements.txt ├── .gitignore ├── test.py ├── setup.py ├── README.md └── LICENSE /nespy/__init__.py: -------------------------------------------------------------------------------- 1 | from nespy.nes import NES 2 | -------------------------------------------------------------------------------- /nespy/apu.pxd: -------------------------------------------------------------------------------- 1 | cdef class APU: 2 | cdef list memory -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Cython==3.0.0a9 2 | PyOpenGL 3 | PyOpenGL_accelerate -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.pyc 3 | *.so 4 | build 5 | *.html 6 | *.pyd 7 | .mypy_cache 8 | venv 9 | tmp*/ -------------------------------------------------------------------------------- /nespy/util.pxd: -------------------------------------------------------------------------------- 1 | #cpdef int to_signed_int(int value) 2 | 3 | #cpdef int to_uint16(list values, bint reverse=*) 4 | -------------------------------------------------------------------------------- /nespy/apu.py: -------------------------------------------------------------------------------- 1 | class APU: 2 | def __init__(self, memory: list[int]) -> None: 3 | self.memory = memory 4 | 5 | def emulate_cycle(self): 6 | pass 7 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | from nespy import NES 2 | import cProfile 3 | from sys import argv 4 | 5 | 6 | if __name__ == '__main__': 7 | nes = NES(resolution=None, disassemble=False) 8 | nes.load_rom(argv[1]) 9 | #nes._cpu.pc = 0xc000 10 | #cProfile.run("nes.run()") 11 | nes.run() 12 | -------------------------------------------------------------------------------- /nespy/ppu.pxd: -------------------------------------------------------------------------------- 1 | cdef class PPU: 2 | cdef list _cpu_memory 3 | cdef list _memory 4 | cdef dict _sprites 5 | cdef dict _debug_sprites 6 | cdef int _scan_line 7 | cdef int _cycle 8 | cdef bint _odd_frame 9 | cdef int _max_cycle_count 10 | cdef int _max_scan_line 11 | 12 | #cpdef set_memory(self, location, value) -------------------------------------------------------------------------------- /nespy/cpu.pxd: -------------------------------------------------------------------------------- 1 | cdef class CPU: 2 | cdef: 3 | list memory 4 | bint disassemble, nmi_low, irq_low 5 | dict opcodes 6 | int c, z, i, d, b, u, v, n, a, x, y, pc, sp, opcode 7 | #cpdef int fetch_uint16(self, int length=*, int address=*) 8 | #cpdef list fetch_memory(self, int length=*, int address=*) 9 | #cpdef void write_memory(self, int address, int value) 10 | #cpdef emulate_cycle(self) 11 | -------------------------------------------------------------------------------- /nespy/exceptions.py: -------------------------------------------------------------------------------- 1 | class InvalidOpcode(Exception): 2 | """ 3 | Raised when the emulator encounters an opcode it is unable to parse 4 | """ 5 | pass 6 | 7 | 8 | class InvalidROM(Exception): 9 | """ 10 | Raised when the emulator is unable to parse a ROM file 11 | """ 12 | pass 13 | 14 | 15 | class UnsupportedMapper(Exception): 16 | """ 17 | Raised when the emulator attempts to load a ROM that uses an unsupported mapper 18 | """ 19 | pass 20 | -------------------------------------------------------------------------------- /nespy/nes.pxd: -------------------------------------------------------------------------------- 1 | from nespy.apu cimport APU 2 | from nespy.clock cimport Clock 3 | from nespy.cpu cimport CPU 4 | from nespy.ppu cimport PPU 5 | 6 | 7 | cdef class NES: 8 | cdef int _memory[0x10000] 9 | cdef list memory 10 | cdef double _width_scale, _height_scale 11 | cdef object _main_window, _debug_window_ppu 12 | cdef CPU _cpu 13 | cdef PPU _ppu 14 | cdef APU _apu 15 | cdef Clock _master_clock 16 | cdef str _rom 17 | cdef int _rom_format 18 | cdef int _mapper -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | from Cython.Build import cythonize 3 | 4 | 5 | # python3 setup.py build_ext --inplace 6 | # cythonize -3 -a -i .\nespy\util.py .\nespy\cpu.py .\nespy\ppu.py .\nespy\clock.py .\nespy\nes.py .\nespy\apu.py .\nespy\enum.py 7 | setup(ext_modules=cythonize(['nespy/nes.py', 'nespy/apu.py', 'nespy/clock.py', 8 | 'nespy/cpu.py', 'nespy/ppu.py', 'nespy/enum.py', 9 | 'nespy/util.py', 'nespy/exceptions.py'], 10 | language_level='3', annotate=True)) 11 | -------------------------------------------------------------------------------- /nespy/clock.pxd: -------------------------------------------------------------------------------- 1 | # cython: cdivision=True 2 | # cython: boundscheck=False 3 | 4 | import cython 5 | 6 | 7 | #ctypedef void (*EMULATE_CYCLE_FUNCTION)() 8 | 9 | cdef class Clock: 10 | cdef int frequency 11 | cdef int cycle 12 | cdef bint ticking 13 | cdef double nanoseconds_per_tick 14 | cdef double last_cycle_time 15 | cdef double last_print_time 16 | cdef list children 17 | cdef int num_children 18 | cdef int speed 19 | cdef double start_time 20 | 21 | #cpdef void start(self) 22 | #cpdef void stop(self) 23 | 24 | @cython.locals(delta=cython.double, ii=cython.int) 25 | cdef void tick(self) 26 | 27 | #cdef void add_child(self, int divisor, void (*func)()) 28 | 29 | cdef class ChildClock: 30 | cdef int divisor 31 | cdef object func 32 | cdef void tick(self) -------------------------------------------------------------------------------- /nespy/util.py: -------------------------------------------------------------------------------- 1 | def to_signed_int(value: int) -> int: 2 | """ 3 | Converts an unsigned integer to a signed integer 4 | 5 | 6502 uses 2's complement for some instructions, (e.g. relative branches), 6 | so some values will have to be converted to signed. 7 | 8 | Args: 9 | value(int): one hex byte (example: "ff") 10 | 11 | Returns: 12 | int: signed integer 13 | """ 14 | if value >= 128: 15 | return -128 + (value-128) 16 | return value 17 | 18 | 19 | def to_hex(value: int) -> str: 20 | """ 21 | Args: 22 | value(int): any integer 23 | 24 | Returns: 25 | str: hex without leading '0x'. Example: to_hex(255) -> "ff" 26 | """ 27 | return format(int(value), '02x') 28 | 29 | 30 | def to_uint16(values: list[int], reverse: bool = True) -> int: 31 | """ 32 | Args: 33 | values(list[int]): two bytes in little-endian order (MSB, LSB) 34 | reverse(bool): if True, reverses the order of the bytes 35 | 36 | Returns: 37 | int: a 16-bit value 38 | """ 39 | if len(values) == 1: 40 | return values[0] 41 | if reverse: 42 | values = values[::-1] 43 | return values[0] << 8 | values[1] 44 | -------------------------------------------------------------------------------- /nespy/clock.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | from multiprocessing import Process 3 | from typing import Callable 4 | 5 | 6 | EMULATE_CYCLE_FUNCTION = Callable[[], None] 7 | 8 | 9 | class Clock: 10 | """ 11 | A Clock that ticks at a given frequency. 12 | ChildClocks whose frequencies are derived from this parent Clock's frequency can be added using `.add_child(...)` 13 | """ 14 | def __init__(self, frequency: int) -> None: 15 | self.frequency = frequency 16 | self.cycle = 0 17 | self.ticking = False 18 | self.nanoseconds_per_tick = 1 * 1000 * 1000 * 1000 / self.frequency 19 | self.last_cycle_time = 0.0 20 | self.last_print_time = 0.0 21 | self.children: list[ChildClock] = [] 22 | self.num_children = 0 23 | self.speed = 0 24 | self.start_time = 0.0 25 | 26 | def start(self) -> None: 27 | self.start_time = time() 28 | self.ticking = True 29 | # Process(target=self.tick).start() 30 | Process(target=self._tick_cwrapper).start() 31 | 32 | def stop(self) -> None: 33 | self.ticking = False 34 | 35 | def add_child(self, divisor: int, func: EMULATE_CYCLE_FUNCTION) -> None: 36 | """ 37 | Creates a clock whose clock rate is derived from this clock. 38 | This child clock runs the given callable every time it ticks. 39 | """ 40 | self.children.append(ChildClock(divisor, func)) 41 | self.num_children += 1 42 | 43 | def _tick_cwrapper(self) -> None: 44 | self.tick() 45 | 46 | def tick(self) -> None: 47 | """ 48 | Requests that the clock tick once. 49 | If enough time has passed, ticks any child clocks derived from this clock as well. 50 | Blocks until all child clocks have finished executing their cycle. 51 | """ 52 | while self.ticking: 53 | self.cycle += 1 54 | ii = 0 55 | # while loop instead of for loop over the list for greater Cython speedup 56 | while ii < self.num_children: 57 | child: ChildClock = self.children[ii] 58 | if self.cycle % child.divisor == 0: 59 | child.tick() 60 | ii += 1 61 | #while time_ns() - self._last_cycle_time_ns < self._nanoseconds_per_tick: 62 | # # wait until enough time has passed to move onto the next tick 63 | # pass 64 | # DEBUG 65 | if self.cycle % 10000000 == 0: # print every 10 million cycles 66 | self.last_print_time = time() 67 | delta = self.last_print_time - self.start_time 68 | print(f"{'{:,}'.format(self.cycle // delta)} c/s avg over {delta}s") 69 | 70 | 71 | class ChildClock: 72 | """ 73 | A Clock whose frequency is calculated by dividing its parent's frequency by `divisor`. 74 | When this ChildClock ticks, it runs the callback function given when this clock is instantiated. 75 | """ 76 | def __init__(self, divisor: int, func: EMULATE_CYCLE_FUNCTION) -> None: 77 | self.divisor = divisor 78 | self.func = func 79 | 80 | def tick(self) -> None: 81 | self.func() 82 | -------------------------------------------------------------------------------- /nespy/ppu.py: -------------------------------------------------------------------------------- 1 | # import pyglet 2 | from nespy.enum import PPURegister 3 | 4 | """ 5 | TODO: clear vblank any time 0x2002 (PPUSTATUS) is read 6 | """ 7 | 8 | 9 | class PPU: 10 | # TODO: rename cpu_memory? 11 | def __init__(self, cpu_memory: list[int]) -> None: 12 | self._cpu_memory = cpu_memory 13 | # TODO: verify PPU startup and reset states 14 | self._memory = [0] * 0x4000 # 16KiB of RAM 15 | self._sprites = {} 16 | self._debug_sprites = {} 17 | self._scan_line = 261 18 | self._cycle = 0 19 | self._odd_frame = False 20 | self._max_cycle_count = 340 21 | self._max_scan_line = 261 22 | self.reset() 23 | 24 | def reset(self) -> None: 25 | self._memory = [0] * 0x4000 # 16KiB of RAM 26 | self._sprites = {} 27 | self._debug_sprites = {} 28 | self._scan_line = 261 29 | self._cycle = 0 30 | self._odd_frame = False 31 | self._max_cycle_count = 340 32 | self._max_scan_line = 261 33 | self._cpu_memory[PPURegister.PPUCTRL] = 0 34 | self._cpu_memory[PPURegister.PPUMASK] = 0 35 | self._cpu_memory[PPURegister.PPUSTATUS] = 0b10100000 36 | self._cpu_memory[PPURegister.PPUSCROLL] = 0 37 | self._cpu_memory[PPURegister.PPUDATA] = 0 38 | 39 | def set_memory(self, location: int, value: int) -> None: 40 | self._memory[location] = value 41 | 42 | """ 43 | def generate_debug_sprites(self) -> None: 44 | # temp palette until palettes are implemented 45 | color_map = [(0, 0, 0), 46 | (255, 0, 0), 47 | (0, 255, 0), 48 | (255, 255, 255)] 49 | if self._debug_sprites != {}: 50 | return 51 | # pattern tables stored in PPU memory from 0x0 to 0x2000 52 | for tile_offset in range(0, 0x2000, 0x10): 53 | tile1 = self._memory[0x0 + tile_offset: 0x8 + tile_offset] 54 | tile2 = self._memory[0x8 + tile_offset: 0x10 + tile_offset] 55 | imagemap = [[0 for x in range(8)] for y in range(8)] 56 | for row, byte in enumerate(tile1): 57 | for column in range(8): 58 | if byte & 2**column: 59 | imagemap[row][column] |= 0b00000001 60 | for row, byte in enumerate(tile2): 61 | for column in range(8): 62 | if byte & 2**column: 63 | imagemap[row][column] |= 0b00000010 64 | 65 | encoded_image_data = b"" 66 | for y, row in enumerate(imagemap): 67 | for x, pixel in enumerate(row): 68 | encoded_image_data = bytes(color_map[pixel]) + encoded_image_data 69 | image = pyglet.image.ImageData(8, 8, "RGB", encoded_image_data, pitch=24) 70 | sprite = pyglet.sprite.Sprite(image, batch=self._nes._render_batch_ppu_debug) 71 | self._debug_sprites[tile_offset] = sprite 72 | 73 | sprite_height = 8 * self._nes._height_scale 74 | sprite_width = 8 * self._nes._width_scale 75 | sprites_per_line = 16 # also sprites_per_column (it's a square) 76 | total_sprites = 512 77 | for sprite_number, sprite in enumerate(self._debug_sprites.values()): 78 | sprite.update(scale_x=self._nes._width_scale, scale_y=self._nes._height_scale) 79 | sprite.x = sprite_width * (sprite_number % sprites_per_line) 80 | sprite.y = (self._nes._debug_window_ppu.height - sprite_height) - sprite_height * (sprite_number // sprites_per_line) 81 | if sprite_number >= total_sprites / 2: 82 | # halfway through the sprite list, start drawing at the top of the right half of the window 83 | sprite.x += sprite_width * sprites_per_line 84 | sprite.y += self._nes._debug_window_ppu.height 85 | """ 86 | 87 | def emulate_cycle(self) -> None: 88 | self._tick() 89 | 90 | def _tick(self) -> None: 91 | if 0 <= self._scan_line <= 239: 92 | pass 93 | elif self._scan_line == 240: 94 | pass 95 | elif self._scan_line == 241 and self._cycle == 1: 96 | # set the vblank flag 97 | self._cpu_memory[PPURegister.PPUSTATUS] |= 0b10000000 98 | elif self._scan_line == 261 and self._cycle == 1: 99 | # unset the vblank, sprite 0 hit, and overflow flags 100 | self._cpu_memory[PPURegister.PPUSTATUS] = 0 101 | 102 | self._cycle += 1 103 | if self._cycle > self._max_cycle_count: 104 | self._cycle = 0 105 | self._scan_line += 1 106 | if self._scan_line > self._max_scan_line: 107 | self._scan_line = 0 108 | self._odd_frame = not self._odd_frame 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | nespy is a Nintendo Entertainment System (NES) emulator written in Python (v3.9+). 2 | 3 | This emulator features a simple API to interact with the system's internals while software 4 | is running. This is useful for things such as AI development, reverse engineering, 5 | game modifications, memory editing, tool-assisted speedruns (TAS), and more. 6 | 7 | This is a work in progress. The CPU implementation is completed (except for unofficial opcodes) 8 | and passes the nestest ROM. I have started working on the PPU. 9 | 10 | The standard CPython interpreter is not fast enough to run my implementation of an NES emulator at full speed. 11 | For this reason, I have included Cython .pxd files, as well as the C source files automatically generated by Cython 12 | so that the components that make up nespy can optionally be compiled into native binaries and run at much higher 13 | speeds than the standard Python interpreter would allow. 14 | 15 | Utilizing Cython allows this project to run fast while keeping the code written in 100% pure Python. As mentioned above, 16 | compiling the project using Cython is completely optional and simply allows the emulator to run faster. nespy can be 17 | run, slower, using the standard Python interpreter. 18 | 19 | Usage 20 | ===== 21 | 22 | ``` 23 | from nespy.nes import NES 24 | 25 | 26 | nes = NES() 27 | nes.load_rom("roms/Donkey Kong.nes") 28 | nes.run() 29 | ``` 30 | 31 | Keys 32 | ==== 33 | 34 | * F11 - Open PPU debug window 35 | 36 | Future Usage 37 | ============ 38 | In its final form, I want this emulator to have features that support the following use cases. 39 | None of these things currently exist in the emulator, this is a wishlist. 40 | 41 | Reverse Engineering 42 | ------------------- 43 | This emulator includes a 6502 disassembler and debugger, as well as an interface for 44 | altering a ROM. 45 | 46 | ``` 47 | nes = NES(debug=True) 48 | nes.load_rom("roms/Donkey_Kong.nes") 49 | nes.run() 50 | ``` 51 | 52 | Disassembler output: 53 | ``` 54 | 0xc000 4c f5 c5 JMP $c5f5 A=00 X=00 Y=00 flags=0b110100 55 | 0xc5f5 a2 00 LDX #$00 A=00 X=00 Y=00 flags=0b110100 56 | 0xc5f7 86 00 STX $00; = 00 A=00 X=00 Y=00 flags=0b110110 57 | 0xc5f9 86 10 STX $10; = 00 A=00 X=00 Y=00 flags=0b110110 58 | 0xc5fb 86 11 STX $11; = 00 A=00 X=00 Y=00 flags=0b110110 59 | 0xc5fd 20 2d c7 JSR $c72d A=00 X=00 Y=00 flags=0b110110 60 | 0xc72d ea NOP A=00 X=00 Y=00 flags=0b110110 61 | 0xc72e 38 SEC A=00 X=00 Y=00 flags=0b110110 62 | 0xc72f b0 04 BCS $04; = $c735 A=00 X=00 Y=00 flags=0b110111 63 | ... 64 | ``` 65 | 66 | Modifications 67 | ------------- 68 | Create and load patches and modifications to ROMs that can affect the behavior of any part 69 | of the NES and the emulator itself, including Game Genie-style cheats, ROM hacks, 70 | CPU instruction overrides, and even custom OpenGL shaders. 71 | 72 | ``` 73 | nes.load_rom("roms/Donkey Kong.nes") 74 | nes.load_patch("patches/nude_dk.patch") 75 | nes.load_patch("patches/rtx_shader_dk.patch") 76 | ``` 77 | 78 | ``` 79 | def my_jmp_handler(nes): 80 | # always just set PC to 0x1000 whenever a JMP instruction is reached 81 | nes.cpu.set_pc(0x1000) 82 | 83 | nes.cpu.jmp = my_jmp_handler 84 | ``` 85 | 86 | AI 87 | -- 88 | Retrieve information directly from the system's RAM and use it to make decisions in real time. 89 | ``` 90 | SCORE_ADDRESS = 0x25 91 | ENEMY_LOC_ADDRESS = 0x78 92 | MY_LOC_ADDRESS = 0x7A 93 | 94 | def get_score(): 95 | return nes.cpu.fetch_memory(length=2, address=SCORE_ADDRESS) 96 | 97 | def get_enemy_position(): 98 | return nes.cpu.fetch_memory(length=2, address=ENEMY_LOC_ADDRESS) 99 | 100 | def get_my_position(): 101 | return nes.cpu.fetch_memory(length=2, address=MY_LOC_ADDRESS) 102 | 103 | 104 | score = get_score() 105 | 106 | if get_enemy_position() == get_my_position(): 107 | player1_controller.hold_a(frames=1) 108 | 109 | nes.wait_frames(60) # block script for 60 frames 110 | 111 | new_score = get_score() 112 | if new_score > score: 113 | # the enemy was killed, continue moving right 114 | player1_controller.press_right() 115 | ``` 116 | 117 | TAS 118 | --- 119 | Allow creation and playback of TAS input recordings. 120 | ``` 121 | with nes.start_recording_inputs("mario_tas.fm2"): 122 | nes.advance_frames(10) # advance the system by 10 frames 123 | player1_controller.press_a() 124 | nes.advance_frames(1) 125 | player1_controller.release_a() 126 | # ... 127 | 128 | nes.import_recorded_inputs("mario_tas.fm2") 129 | nes.play_inputs() 130 | ``` 131 | 132 | Resources 133 | ========= 134 | 135 | * http://nesdev.com/6502_cpu.txt -- Extremely detailed information about the CPU 136 | * http://blog.alexanderdickson.com/javascript-nes-emulator-part-1 -- tons of useful information about the NES 137 | * http://www.obelisk.me.uk/6502/reference.html -- opcode reference 138 | * http://wiki.nesdev.com/w/index.php/CPU_unofficial_opcodes -- opcode grid 139 | * http://wiki.nesdev.com/w/index.php/INES -- parsing a NES rom 140 | * http://nemulator.com/files/nes_emu.txt -- more info about parsing ROMs 141 | * https://www.reddit.com/r/pygame/comments/5ifplr/ive_updated_the_pyglet_procedural_audio_module/ -- playing raw waveforms with pyglet 142 | * https://wiki.nesdev.com/w/index.php/Emulator_tests -- NES test roms 143 | * https://cython.readthedocs.io/en/latest/src/tutorial/pure.html -- Cython - Pure Python Mode -------------------------------------------------------------------------------- /nespy/nes.py: -------------------------------------------------------------------------------- 1 | from OpenGL.GL import glBegin, glVertex2f, glEnd, glViewport, glMatrixMode, glLoadIdentity, glOrtho, glClear, \ 2 | glColor3f, glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, \ 3 | GL_TEXTURE_MAG_FILTER, GL_NEAREST, GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT, GL_QUADS, GL_PROJECTION, GL_MODELVIEW 4 | from OpenGL.GLUT import glutInit, glutInitDisplayMode, glutInitWindowSize, glutInitWindowPosition, glutCreateWindow, \ 5 | glutSwapBuffers, glutSetOption, glutHideWindow, GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS, \ 6 | GLUT_RGBA 7 | from typing import Optional 8 | 9 | from nespy.apu import APU 10 | from nespy.clock import Clock 11 | from nespy.cpu import CPU 12 | from nespy.enum import ROMFormat, Mapper 13 | from nespy.exceptions import InvalidROM, UnsupportedMapper 14 | from nespy.ppu import PPU 15 | 16 | 17 | def square(): 18 | glBegin(GL_QUADS) 19 | glVertex2f(100, 100) 20 | glVertex2f(200, 100) 21 | glVertex2f(200, 200) 22 | glVertex2f(100, 200) 23 | glEnd() 24 | 25 | 26 | def iterate(): 27 | glViewport(0, 0, 500, 500) 28 | glMatrixMode(GL_PROJECTION) 29 | glLoadIdentity() 30 | glOrtho(0.0, 500, 0.0, 500, 0.0, 1.0) 31 | glMatrixMode(GL_MODELVIEW) 32 | glLoadIdentity() 33 | 34 | 35 | def show_screen(): 36 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) 37 | glLoadIdentity() 38 | iterate() 39 | glColor3f(1.0, 0.0, 3.0) 40 | square() 41 | glutSwapBuffers() 42 | 43 | 44 | class NES: 45 | """ 46 | Args: 47 | resolution(tuple): in the form (int, int). Render resolution. 48 | if set to None, the emulator can still run, but no video will be drawn to the screen. 49 | Controller inputs and other manipulations can be performed via the API. 50 | """ 51 | def __init__(self, resolution: Optional[tuple[int, int]] = (512, 480), disassemble: bool = False) -> None: 52 | if resolution: 53 | self._width_scale = resolution[0] / 256 # NES native width is 256 pixels 54 | self._height_scale = resolution[1] / 240 # NES native height is 240 pixels 55 | # overrides opengl's default filtering behavior from GL_LINEAR to GL_NEAREST 56 | # GL_LINEAR creates really bad blurriness 57 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) 58 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) 59 | # initialize graphics 60 | glutInit() 61 | glutInitDisplayMode(GLUT_RGBA) 62 | glutInitWindowSize(*resolution) 63 | glutInitWindowPosition(0, 0) 64 | self._main_window = glutCreateWindow("NESpy - Main Window") 65 | glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS) 66 | glutInitWindowSize(int(256 * self._width_scale), int(128 * self._height_scale)) 67 | self._debug_window_ppu = glutCreateWindow("NESpy - Debug (PPU)") 68 | glutHideWindow() 69 | #glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_CONTINUE_EXECUTION) 70 | #self._render_batch_ppu_debug = pyglet.graphics.Batch() 71 | self._memory = [0] * 0x10000 # 64KiB 72 | self._cpu = CPU(self._memory, disassemble=disassemble) 73 | self._ppu = PPU(self._memory) 74 | self._apu = APU(self._memory) 75 | self._master_clock = Clock(21477272) # NTSC NES master clock frequency is ~21.477272 MHz 76 | self._master_clock.add_child(12, self._cpu.emulate_cycle) # CPU clock frequency is master / 12 77 | self._master_clock.add_child(4, self._ppu.emulate_cycle) # PPU clock frequency is master / 4 78 | 79 | self._mapper = -1 80 | self._rom_format = -1 81 | 82 | def load_rom(self, path: str, reset: bool = True) -> None: 83 | # read rom into memory 84 | rom = open(path, "rb") 85 | rom_bytes = [] 86 | while True: 87 | byte = rom.read(1) 88 | if not byte: 89 | break 90 | rom_bytes.append(int.from_bytes(byte, byteorder='little')) 91 | rom_header = rom_bytes[0:16] 92 | # parse ROM header 93 | if rom_header[0:4] != [0x4E, 0x45, 0x53, 0x1A]: # first 4 bytes should be 'NES\x1A' 94 | raise InvalidROM(f'Invalid ROM header: {rom_header}') 95 | prg_banks = rom_header[4] 96 | chr_banks = rom_header[5] 97 | flags6 = rom_header[6] 98 | flags7 = rom_header[7] 99 | 100 | # ROM is NES2.0 if 3rd bit of flags7 is set, and 2nd bit is clear 101 | if flags7 & 0b1100 == 0b1000: 102 | self._rom_format = ROMFormat.INES2_0 103 | flags8 = rom_header[8] 104 | self._mapper = ((flags6 & 0b11110000) >> 4) | (flags7 & 0b11110000) | ((flags8 & 0b11110000) << 4) 105 | else: 106 | self._rom_format = ROMFormat.INES 107 | self._mapper = ((flags6 & 0b11110000) >> 4) | (flags7 & 0b11110000) 108 | prg_ram_size = rom_header[8] 109 | flags9 = rom_header[9] 110 | flags10 = rom_header[10] 111 | # bits 11-15 of the header are unused 112 | 113 | rom_has_trainer = (flags6 & 0b00000100 != 0) # bit 2 of flags6 indicates whether a trainer is present 114 | 115 | if self._mapper == Mapper.NROM: 116 | rom_prg_start = 0x10 + (0x200 * rom_has_trainer) # trainer is 0x200 bytes long 117 | rom_prg_end = rom_prg_start + (0x4000 * prg_banks) # each prgBank is 0x4000 bytes long 118 | memory_pointer = 0x8000 # prg_banks get written to memory starting at 0x8000 119 | rom_prg_bytes = rom_bytes[rom_prg_start:rom_prg_end] 120 | for byte in rom_prg_bytes: 121 | self._memory[memory_pointer] = byte 122 | if prg_banks == 1: # if there's only one PRG bank on the ROM, it gets duplicated to the 2nd area in memory 123 | self._memory[memory_pointer + 0x4000] = byte 124 | memory_pointer += 1 125 | 126 | rom_chr_bytes = rom_bytes[rom_prg_end:rom_prg_end + 0x2000] # CHR ROM is 0x2000 bytes long 127 | memory_pointer = 0 128 | for byte in rom_chr_bytes: 129 | self._ppu.set_memory(memory_pointer, byte) 130 | memory_pointer += 1 131 | else: 132 | raise UnsupportedMapper(f"The mapper {self._mapper} for ROM {path} is not supported.") 133 | 134 | if reset: 135 | self._cpu.reset() 136 | self._ppu.reset() 137 | 138 | self._rom = path 139 | 140 | def update_display(self) -> None: 141 | pass 142 | 143 | def keyboard_handler(self) -> None: 144 | pass 145 | 146 | def run(self) -> None: 147 | """ 148 | glutDisplayFunc(self.update_display) 149 | glutKeyboardFunc(self.keyboard_handler) 150 | glutIdleFunc(self._master_clock.tick) 151 | 152 | glutMainLoop() 153 | """ 154 | self._master_clock.start() 155 | -------------------------------------------------------------------------------- /nespy/enum.py: -------------------------------------------------------------------------------- 1 | class ROMFormat: 2 | INES = 0 3 | INES2_0 = 1 4 | 5 | 6 | class Mapper: 7 | """ 8 | Maps mapper names to mapper values found in iNES ROMs 9 | """ 10 | NROM = 0 11 | MMC1 = 1 12 | UXROM = 2 13 | CNROM = 3 14 | MMC3 = 4 15 | AXROM = 7 16 | MMC2 = 9 17 | 18 | 19 | class PPURegister: 20 | PPUCTRL = 0x2000 21 | PPUMASK = 0x2001 22 | PPUSTATUS = 0x2002 23 | OAMADDR = 0x2003 24 | OAMDATA = 0x2004 25 | PPUSCROLL = 0x2005 26 | PPUADDR = 0x2006 27 | PPUDATA = 0x2007 28 | 29 | 30 | InstructionMnemonicMap = { 31 | 0x69: "ADC", 0x65: "ADC", 0x75: "ADC", 0x6D: "ADC", 32 | 0x7D: "ADC", 0x79: "ADC", 0x61: "ADC", 0x71: "ADC", 33 | 0x29: "AND", 0x25: "AND", 0x35: "AND", 0x2D: "AND", 34 | 0x3D: "AND", 0x39: "AND", 0x21: "AND", 0x31: "AND", 35 | 0x0A: "ASL", 0x06: "ASL", 0x16: "ASL", 0x0E: "ASL", 36 | 0x1E: "ASL", 37 | 0x90: "BCC", 38 | 0xB0: "BCS", 39 | 0xF0: "BEQ", 40 | 0x24: "BIT", 0x2C: "BIT", 41 | 0x30: "BMI", 42 | 0xD0: "BNE", 43 | 0x10: "BPL", 44 | 0x0: "BRK", 45 | 0x50: "BVC", 46 | 0x70: "BVS", 47 | 0x18: "CLC", 48 | 0xD8: "CLD", 49 | 0x58: "CLI", 50 | 0xB8: "CLV", 51 | 0xC9: "CMP", 0xC5: "CMP", 0xD5: "CMP", 0xCD: "CMP", 52 | 0xDD: "CMP", 0xD9: "CMP", 0xC1: "CMP", 0xD1: "CMP", 53 | 0xE0: "CPX", 0xE4: "CPX", 0xEC: "CPX", 54 | 0xC0: "CPY", 0xC4: "CPY", 0xCC: "CPY", 55 | 0xC6: "DEC", 0xD6: "DEC", 0xCE: "DEC", 0xDE: "DEC", 56 | 0xCA: "DEX", 57 | 0x88: "DEY", 58 | 0x49: "EOR", 0x45: "EOR", 0x55: "EOR", 0x4D: "EOR", 59 | 0x5D: "EOR", 0x59: "EOR", 0x41: "EOR", 0x51: "EOR", 60 | 0xE6: "INC", 0xF6: "INC", 0xEE: "INC", 0xFE: "INC", 61 | 0xE8: "INX", 62 | 0xC8: "INY", 63 | 0x4C: "JMP", 0x6C: "JMP", 64 | 0x20: "JSR", 65 | 0xA9: "LDA", 0xA5: "LDA", 0xB5: "LDA", 0xAD: "LDA", 66 | 0xBD: "LDA", 0xB9: "LDA", 0xA1: "LDA", 0xB1: "LDA", 67 | 0xA2: "LDX", 0xA6: "LDX", 0xB6: "LDX", 0xAE: "LDX", 68 | 0xBE: "LDX", 69 | 0xA0: "LDY", 0xA4: "LDY", 0xB4: "LDY", 0xAC: "LDY", 70 | 0xBC: "LDY", 71 | 0x4A: "LSR", 0x46: "LSR", 0x56: "LSR", 0x4E: "LSR", 72 | 0x5E: "LSR", 73 | 0xEA: "NOP", 0x1A: "NOP", 0x3A: "NOP", 0x5A: "NOP", 74 | 0x7A: "NOP", 0xDA: "NOP", 0xFA: "NOP", 75 | 0x80: "NOP", # _immediate, 76 | 0x82: "NOP", # _immediate, 77 | 0x89: "NOP", # _immediate, 78 | 0xC2: "NOP", # _immediate, 79 | 0xE2: "NOP", # _immediate, 80 | 0x09: "ORA", 0x05: "ORA", 0x15: "ORA", 0x0D: "ORA", 81 | 0x1D: "ORA", 0x19: "ORA", 0x01: "ORA", 0x11: "ORA", 82 | 0x48: "PHA", 83 | 0x08: "PHP", 84 | 0x68: "PLA", 85 | 0x28: "PLP", 86 | 0x2A: "ROL", 0x26: "ROL", 0x36: "ROL", 0x2E: "ROL", 87 | 0x3E: "ROL", 88 | 0x6A: "ROR", 0x66: "ROR", 0x76: "ROR", 0x6E: "ROR", 89 | 0x7E: "ROR", 90 | 0x40: "RTI", 91 | 0x60: "RTS", 92 | 0xE9: "SBC", 0xE5: "SBC", 0xF5: "SBC", 0xED: "SBC", 93 | 0xFD: "SBC", 0xF9: "SBC", 0xE1: "SBC", 0xF1: "SBC", 94 | 0x38: "SEC", 95 | 0xF8: "SED", 96 | 0x78: "SEI", 97 | 0x85: "STA", 0x95: "STA", 0x8D: "STA", 0x9D: "STA", 98 | 0x99: "STA", 0x81: "STA", 0x91: "STA", 99 | 0x86: "STX", 0x96: "STX", 0x8E: "STX", 100 | 0x84: "STY", 0x94: "STY", 0x8C: "STY", 101 | 0xAA: "TAX", 102 | 0xA8: "TAY", 103 | 0xBA: "TSX", 104 | 0x8A: "TXA", 105 | 0x9A: "TXS", 106 | 0x98: "TYA" 107 | } 108 | 109 | 110 | class AddressingMode: 111 | Absolute = "absolute" 112 | AbsoluteX = "absolute,x" 113 | AbsoluteY = "absolute,y" 114 | ZeroPage = "zeropage" 115 | ZeroPageX = "zeropage,x" 116 | ZeroPageY = "zeropage,y" 117 | Immediate = "immediate" 118 | Relative = "relative" 119 | Implicit = "implicit" 120 | Indirect = "indirect" 121 | IndirectX = "indirect,x" 122 | IndirectY = "indirect,y" 123 | 124 | 125 | InstructionAddressingModeMap = { # addressing mode used by the instruction 126 | **dict.fromkeys([0x6D, 0x2D, 0x0E, 0x2C, 127 | 0xCD, 0xEC, 0xCC, 0xCE, 128 | 0x4D, 0xEE, 0x4C, 0x20, 129 | 0xAD, 0xAC, 0x4E, 0x0D, 130 | 0x2E, 0x6E, 0xED, 0x8D, 131 | 0x8E, 0x8C, 0xAE], 132 | AddressingMode.Absolute), 133 | **dict.fromkeys([0x7D, 0x3D, 0x1E, 0xDD, 134 | 0xDE, 0x5D, 0xFE, 0xBD, 135 | 0xBC, 0x5E, 0x1D, 0x3E, 136 | 0x7E, 0xFD, 0x9D], 137 | AddressingMode.AbsoluteX), 138 | **dict.fromkeys([0x79, 0x39, 0xD9, 0x59, 139 | 0xB9, 0x19, 0xF9, 0x99, 140 | 0xBE], 141 | AddressingMode.AbsoluteY), 142 | **dict.fromkeys([0x65, 0x25, 0x06, 0x24, 143 | 0xC5, 0xE4, 0xC4, 0xC6, 144 | 0x45, 0xE6, 0xA5, 0xA4, 145 | 0x46, 0x05, 0x26, 0x66, 146 | 0xE5, 0x85, 0x86, 0x84, 147 | 0xA6], 148 | AddressingMode.ZeroPage), 149 | **dict.fromkeys([0x75, 0x35, 0x16, 0xD5, 150 | 0xD6, 0x55, 0xF6, 0xB5, 151 | 0xB4, 0x56, 0x15, 0x36, 152 | 0x76, 0xF5, 0x95, 0x94, 153 | 0xB6], 154 | AddressingMode.ZeroPageX), 155 | **dict.fromkeys([0x96], 156 | AddressingMode.ZeroPageY), 157 | **dict.fromkeys([0x69, 0x29, 0xC9, 0xE0, 158 | 0xC0, 0x49, 0xA9, 0xA2, 159 | 0x09, 0xE9, 0xA0], 160 | AddressingMode.Immediate), 161 | **dict.fromkeys([0x90, 0xB0, 0xF0, 0x30, 162 | 0xD0, 0x10, 0x50, 0x70], 163 | AddressingMode.Relative), 164 | **dict.fromkeys([0x0A, 0x00, 0x18, 0xD8, 165 | 0x58, 0xB8, 0xCA, 0x88, 166 | 0xE8, 0xC8, 0x4A, 0xEA, 167 | 0x48, 0x08, 0x68, 0x28, 168 | 0x2A, 0x6A, 0x40, 0x60, 169 | 0x38, 0xF8, 0x78, 0xAA, 170 | 0xA8, 0xBA, 0x8A, 0x9A, 171 | 0x98], 172 | AddressingMode.Implicit), 173 | **dict.fromkeys([0x6C], 174 | AddressingMode.Indirect), 175 | **dict.fromkeys([0x61, 0x21, 0xC1, 0x41, 176 | 0xA1, 0x01, 0xE1, 0x81], 177 | AddressingMode.IndirectX), 178 | **dict.fromkeys([0x71, 0x31, 0xD1, 0x51, 179 | 0xB1, 0x11, 0xF1, 0x91], 180 | AddressingMode.IndirectY), 181 | } 182 | 183 | 184 | InstructionLengthMap = { # number of bytes each instruction takes up in memory 185 | **dict.fromkeys([0x0A, 0x00, 0x18, 0xD8, 186 | 0x58, 0xB8, 0xCA, 0x88, 187 | 0xE8, 0xC8, 0x4A, 0xEA, 188 | 0x48, 0x08, 0x68, 0x28, 189 | 0x2A, 0x6A, 0x40, 0x60, 190 | 0x38, 0xF8, 0x78, 0xAA, 191 | 0xA8, 0xBA, 0x8A, 0x9A, 192 | 0x98], 193 | 1), 194 | **dict.fromkeys([0x69, 0x65, 0x75, 0x61, 195 | 0x71, 0x29, 0x25, 0x35, 196 | 0x21, 0x31, 0x06, 0x16, 197 | 0x90, 0xB0, 0xF0, 0x24, 198 | 0x30, 0xD0, 0x10, 0x50, 199 | 0x70, 0xC9, 0xC5, 0xD5, 200 | 0xC1, 0xD1, 0xE0, 0xE4, 201 | 0xC0, 0xC4, 0xC6, 0xD6, 202 | 0x49, 0x45, 0x55, 0x41, 203 | 0x51, 0xE6, 0xF6, 0xA9, 204 | 0xA5, 0xB5, 0xA1, 0xB1, 205 | 0xA2, 0xA6, 0xB6, 0xA0, 206 | 0xA4, 0xB4, 0x46, 0x56, 207 | 0x09, 0x05, 0x15, 0x01, 208 | 0x11, 0x26, 0x36, 0x66, 209 | 0x76, 0xE9, 0xE5, 0xF5, 210 | 0xE1, 0xF1, 0x85, 0x95, 211 | 0x81, 0x91, 0x86, 0x96, 212 | 0x84, 0x94], 213 | 2), 214 | **dict.fromkeys([0x6D, 0x7D, 0x79, 0x2D, 215 | 0x3D, 0x39, 0x0E, 0x1E, 216 | 0x2C, 0xCD, 0xDD, 0xD9, 217 | 0xEC, 0xCC, 0xCE, 0xDE, 218 | 0x4D, 0x5D, 0x59, 0xEE, 219 | 0xFE, 0x4C, 0x6C, 0x20, 220 | 0xAD, 0xBD, 0xB9, 0xAE, 221 | 0xBE, 0xAC, 0xBC, 0x4E, 222 | 0x5E, 0x0D, 0x1D, 0x19, 223 | 0x2E, 0x3E, 0x6E, 0x7E, 224 | 0xED, 0xFD, 0xF9, 0x8D, 225 | 0x9D, 0x99, 0x8E, 0x8C], 226 | 3) 227 | } 228 | 229 | 230 | AddressingModeDisasmFormat = { # the format string printed by the disassembler for each addressing mode 231 | AddressingMode.Absolute: "${operand}", 232 | AddressingMode.AbsoluteX: "${operand},X", 233 | AddressingMode.AbsoluteY: "${operand},Y", 234 | AddressingMode.ZeroPage: "${operand}", 235 | AddressingMode.ZeroPageX: "${operand},X", 236 | AddressingMode.ZeroPageY: "${operand},Y", 237 | AddressingMode.Immediate: "#${operand}", 238 | AddressingMode.Relative: "${operand}; = ${value2}", 239 | AddressingMode.Implicit: "", 240 | AddressingMode.Indirect: "(${operand})", 241 | AddressingMode.IndirectX: "(${operand},X)", 242 | AddressingMode.IndirectY: "(${operand}),Y" 243 | } 244 | 245 | 246 | InstructionDisasmExtras = { 247 | # any additional information to show when disassembling certain instructions 248 | # example: "STX $20 ;= FF", meaning X was stored in memory at 0x20, and the value of X was 0xFF 249 | **dict.fromkeys([0x85, 0x95, 0x8D, 0x9D, 250 | 0x99, 0x81, 0x91], "; = {a}"), # STA 251 | **dict.fromkeys([0x86, 0x96, 0x8E], "; = {x}"), # STX 252 | **dict.fromkeys([0x84, 0x94, 0x8C], "; = {y}"), # STY 253 | **dict.fromkeys([0x24, 0x2C], "; = {value2}"), # BIT 254 | } 255 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /nespy/cpu.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from nespy.util import to_signed_int, to_uint16, to_hex 4 | from nespy.enum import AddressingMode, AddressingModeDisasmFormat, InstructionAddressingModeMap,\ 5 | InstructionMnemonicMap, InstructionDisasmExtras, InstructionLengthMap 6 | 7 | 8 | class CPU: 9 | def __init__(self, memory: list[int], disassemble: bool = False) -> None: 10 | self.memory = memory 11 | self.disassemble = disassemble 12 | self.opcodes = {0x69: self.adc, 0x65: self.adc, 0x75: self.adc, 0x6D: self.adc, 13 | 0x7D: self.adc, 0x79: self.adc, 0x61: self.adc, 0x71: self.adc, 14 | 0x29: self._and, 0x25: self._and, 0x35: self._and, 0x2D: self._and, 15 | 0x3D: self._and, 0x39: self._and, 0x21: self._and, 0x31: self._and, 16 | 0x0A: self.asl, 0x06: self.asl, 0x16: self.asl, 0x0E: self.asl, 17 | 0x1E: self.asl, 18 | 0x90: self.bcc, 19 | 0xB0: self.bcs, 20 | 0xF0: self.beq, 21 | 0x24: self.bit, 0x2C: self.bit, 22 | 0x30: self.bmi, 23 | 0xD0: self.bne, 24 | 0x10: self.bpl, 25 | 0x0: self.brk, 26 | 0x50: self.bvc, 27 | 0x70: self.bvs, 28 | 0x18: self.clc, 29 | 0xD8: self.cld, 30 | 0x58: self.cli, 31 | 0xB8: self.clv, 32 | 0xC9: self.cmp, 0xC5: self.cmp, 0xD5: self.cmp, 0xCD: self.cmp, 33 | 0xDD: self.cmp, 0xD9: self.cmp, 0xC1: self.cmp, 0xD1: self.cmp, 34 | 0xE0: self.cpx, 0xE4: self.cpx, 0xEC: self.cpx, 35 | 0xC0: self.cpy, 0xC4: self.cpy, 0xCC: self.cpy, 36 | 0xC6: self.dec, 0xD6: self.dec, 0xCE: self.dec, 0xDE: self.dec, 37 | 0xCA: self.dex, 38 | 0x88: self.dey, 39 | 0x49: self.eor, 0x45: self.eor, 0x55: self.eor, 0x4D: self.eor, 40 | 0x5D: self.eor, 0x59: self.eor, 0x41: self.eor, 0x51: self.eor, 41 | 0xE6: self.inc, 0xF6: self.inc, 0xEE: self.inc, 0xFE: self.inc, 42 | 0xE8: self.inx, 43 | 0xC8: self.iny, 44 | 0x4C: self.jmp, 0x6C: self.jmp, 45 | 0x20: self.jsr, 46 | 0xA9: self.lda, 0xA5: self.lda, 0xB5: self.lda, 0xAD: self.lda, 47 | 0xBD: self.lda, 0xB9: self.lda, 0xA1: self.lda, 0xB1: self.lda, 48 | 0xA2: self.ldx, 0xA6: self.ldx, 0xB6: self.ldx, 0xAE: self.ldx, 49 | 0xBE: self.ldx, 50 | 0xA0: self.ldy, 0xA4: self.ldy, 0xB4: self.ldy, 0xAC: self.ldy, 51 | 0xBC: self.ldy, 52 | 0x4A: self.lsr, 0x46: self.lsr, 0x56: self.lsr, 0x4E: self.lsr, 53 | 0x5E: self.lsr, 54 | 0xEA: self.nop, 0x1A: self.nop, 0x3A: self.nop, 0x5A: self.nop, 55 | 0x7A: self.nop, 0xDA: self.nop, 0xFA: self.nop, 56 | 0x80: self.nop_immediate, 0x82: self.nop_immediate, 0x89: self.nop_immediate, 57 | 0xC2: self.nop_immediate, 0xE2: self.nop_immediate, 58 | 0x09: self.ora, 0x05: self.ora, 0x15: self.ora, 0x0D: self.ora, 59 | 0x1D: self.ora, 0x19: self.ora, 0x01: self.ora, 0x11: self.ora, 60 | 0x48: self.pha, 61 | 0x08: self.php, 62 | 0x68: self.pla, 63 | 0x28: self.plp, 64 | 0x2A: self.rol, 0x26: self.rol, 0x36: self.rol, 0x2E: self.rol, 65 | 0x3E: self.rol, 66 | 0x6A: self.ror, 0x66: self.ror, 0x76: self.ror, 0x6E: self.ror, 67 | 0x7E: self.ror, 68 | 0x40: self.rti, 69 | 0x60: self.rts, 70 | 0xE9: self.sbc, 0xE5: self.sbc, 0xF5: self.sbc, 0xED: self.sbc, 71 | 0xFD: self.sbc, 0xF9: self.sbc, 0xE1: self.sbc, 0xF1: self.sbc, 72 | 0x38: self.sec, 73 | 0xF8: self.sed, 74 | 0x78: self.sei, 75 | 0x85: self.sta, 0x95: self.sta, 0x8D: self.sta, 0x9D: self.sta, 76 | 0x99: self.sta, 0x81: self.sta, 0x91: self.sta, 77 | 0x86: self.stx, 0x96: self.stx, 0x8E: self.stx, 78 | 0x84: self.sty, 0x94: self.sty, 0x8C: self.sty, 79 | 0xAA: self.tax, 80 | 0xA8: self.tay, 81 | 0xBA: self.tsx, 82 | 0x8A: self.txa, 83 | 0x9A: self.txs, 84 | 0x98: self.tya} 85 | self.sp = 0xFD # stack pointer, 0xFD at power-on 86 | self.c = 0 # carry 87 | self.z = 0 # zero 88 | self.i = 1 # interrupt disable, set at power-on 89 | self.d = 0 # decimal mode 90 | self.b = 1 # break command, set at power-on 91 | self.u = 1 # unused, set at power-on 92 | self.v = 0 # overflow 93 | self.n = 0 # negative 94 | self.a = 0 # accumulator 95 | self.x = 0 # x and y are general purpose registers 96 | self.y = 0 97 | self.pc = 0x8000 # program counter 98 | self.opcode = 0x00 # the current instruction being executed 99 | self.irq_low = False 100 | self.nmi_low = False 101 | 102 | self.reset() 103 | 104 | def reset(self) -> None: 105 | self.sp = self.sp - 3 & 0xFF # 3 is subtracted from SP on reset 106 | self.c = 0 107 | self.z = 0 108 | self.i = 1 # set on reset 109 | self.d = 0 110 | self.b = 1 # set on reset 111 | self.u = 1 # set on reset 112 | self.v = 0 113 | self.n = 0 114 | self.a = 0 115 | self.x = 0 116 | self.y = 0 117 | self.pc = 0x8000 118 | self.opcode = 0x00 119 | self.irq_low = False 120 | self.nmi_low = False 121 | 122 | # set pc to RESET vector (0xFFFC-0xFFFD) 123 | reset_interrupt = self.fetch_uint16(2, address=0xFFFC) 124 | self.pc = reset_interrupt 125 | 126 | # the 6502 has a very particular order that the flags are arranged in: (little endian) 127 | # N V U B D I Z C 128 | def get_flags(self) -> int: 129 | flags = 0 130 | flags |= self.c << 0 131 | flags |= self.z << 1 132 | flags |= self.i << 2 133 | flags |= self.d << 3 134 | flags |= 1 << 4 # the b flag will always be set to 1 when the flags are being pushed to the stack 135 | # flags |= self.b << 4 136 | flags |= self.u << 5 137 | flags |= self.v << 6 138 | flags |= self.n << 7 139 | return flags 140 | 141 | def set_flags(self, flags: int) -> None: 142 | self.c = flags >> 0 & 1 143 | self.z = flags >> 1 & 1 144 | self.i = flags >> 2 & 1 145 | self.d = flags >> 3 & 1 146 | # bits 4 and 5 do not get set when flags are being pulled from the stack 147 | # self.b = flags >> 4 & 1 148 | # self.u = flags >> 5 & 1 149 | self.v = flags >> 6 & 1 150 | self.n = flags >> 7 & 1 151 | 152 | def push(self, byte: int) -> None: 153 | """ 154 | Pushes one byte onto the stack 155 | 156 | Args: 157 | byte(int): one byte of data 158 | """ 159 | self.memory[0x100 | self.sp] = byte 160 | self.sp = self.sp - 1 & 0xFF 161 | 162 | def pop(self) -> int: 163 | """ 164 | Returns: 165 | int: one byte from the top of the stack 166 | """ 167 | self.sp = self.sp + 1 & 0xFF 168 | value = self.fetch_uint16(1, address=0x100 | self.sp) 169 | return value 170 | 171 | def push16(self, data: int) -> None: 172 | """ 173 | Pushes two bytes onto the stack 174 | 175 | Args: 176 | data(int): two bytes of data 177 | """ 178 | lsb = data & 0xFF 179 | msb = data >> 8 180 | self.push(msb) 181 | self.push(lsb) 182 | 183 | def pop16(self) -> int: 184 | """ 185 | Returns: 186 | int: two bytes from the top of the stack 187 | """ 188 | lsb = self.pop() 189 | msb = self.pop() 190 | return msb << 8 | lsb 191 | 192 | def push_pc(self) -> None: 193 | self.push16(self.pc) 194 | 195 | def fetch_uint16(self, length: int = 2, address: int = -1) -> int: 196 | data = self.fetch_memory(length, address) 197 | if length > 1: 198 | return to_uint16(data) 199 | else: 200 | return data[0] 201 | 202 | def fetch_memory(self, length: int = 2, address: int = -1) -> list[int]: 203 | """ 204 | Args: 205 | length(int): number of bytes to retrieve. default=2 206 | address(int): address to start reading from. uses self.pc if address=-1. default=-1 207 | 208 | Returns: 209 | list or int: if return_int, returns an integer. Otherwise, a list of bytes 210 | """ 211 | if address == -1: 212 | address = self.pc 213 | # handle mirrored memory accesses 214 | if 0x2008 <= address <= 0x3FFF: 215 | address = (address % 0x8) + 0x2000 216 | # if this is a zero-page lookup, we have to handle potential overflows 217 | if address <= 0xFF < address + length: 218 | overflow_amount = address + length & 0xFF 219 | data = self.memory[address:address+length-overflow_amount] 220 | data += self.memory[0:overflow_amount] 221 | else: 222 | # TODO: handle 16-bit overflow? 223 | data = self.memory[address:address+length] 224 | 225 | return data 226 | 227 | def write_memory(self, address: int, value: int) -> None: 228 | self.memory[address] = value 229 | 230 | def emulate_cycle(self) -> None: 231 | self.opcode = self.fetch_uint16(length=1) 232 | 233 | # build disassembly output 234 | if self.disassemble: 235 | disassembly_values = { 236 | 'x': to_hex(self.x), 237 | 'y': to_hex(self.y), 238 | 'a': to_hex(self.a), 239 | } 240 | location = to_hex(self.pc) 241 | instruction = InstructionMnemonicMap[self.opcode] 242 | addressing_mode = InstructionAddressingModeMap[self.opcode] 243 | instruction_length = InstructionLengthMap[self.opcode] 244 | instruction_bytes = self.fetch_memory(length=instruction_length) # fetch whole instruction, with opcode 245 | raw_bytes = ' '.join([to_hex(x) for x in instruction_bytes]) 246 | if instruction_length > 1: 247 | disassembly_values['operand'] = ''.join([to_hex(x) for x in instruction_bytes[1:][::-1]]) 248 | # also show the value of any indirect memory accesses 249 | if addressing_mode in (AddressingMode.Indirect, AddressingMode.IndirectX, AddressingMode.IndirectY): 250 | pass # TODO - show memory indirection during indirect memory accesses 251 | elif addressing_mode == AddressingMode.Relative: # show the actual memory address for relative operations 252 | disassembly_values['value2'] = to_hex(self.pc + instruction_length + to_signed_int(instruction_bytes[1])) 253 | elif instruction in ("BIT",): # BIT uses indirect memory access in Zero Page and Absolute modes 254 | disassembly_values['value2'] = to_hex(self.fetch_uint16(instruction_length-1, 255 | address=to_uint16(instruction_bytes[1:]))) 256 | disassembly = AddressingModeDisasmFormat[addressing_mode] 257 | disassembly_extra = InstructionDisasmExtras.get(self.opcode, "") 258 | disassembly = (disassembly + disassembly_extra).format(**disassembly_values) 259 | registers = f"A={to_hex(self.a)} X={to_hex(self.x)} Y={to_hex(self.y)} flags={bin(self.get_flags())}" 260 | print(f"0x{location:<5}{raw_bytes:<9}{instruction:<4}{disassembly:<32}{registers}") 261 | 262 | self.pc += 1 263 | 264 | try: 265 | self.opcodes[self.opcode]() 266 | except KeyError: 267 | logging.warning(f"Invalid opcode: {to_hex(self.opcode)} at 0x{to_hex(self.pc)}") 268 | 269 | # handles IRQ and NMI 270 | def handle_interrupt(self) -> None: 271 | # cycle 1 272 | # cycle 2 273 | self.push_pc() 274 | flags = self.get_flags() 275 | # IRQ and NMI set bit 5 and clear bit 4 of the flags 276 | flags |= 1 << 5 277 | if self.irq_low: 278 | address = 0xFFFE 279 | elif self.nmi_low: 280 | address = 0xFFFA 281 | else: 282 | # TODO: can this even happen? interrupt cleared by something else? 283 | return 284 | self.pc = self.fetch_uint16(2, address=address) 285 | self.i = 1 286 | 287 | # ADC - Add with Carry 288 | # Flags: carry, zero, overflow, negative 289 | def adc(self) -> None: 290 | # immediate 291 | if self.opcode == 0x69: 292 | value = self.fetch_uint16(1) 293 | self.pc += 1 294 | # zero page 295 | elif self.opcode == 0x65: 296 | value_location = self.fetch_uint16(1) 297 | value = self.fetch_uint16(1, address=value_location) 298 | self.pc += 1 299 | # zero page, x 300 | elif self.opcode == 0x75: 301 | value_location = self.fetch_uint16(1) + self.x & 0xFF 302 | value = self.fetch_uint16(1, address=value_location) 303 | self.pc += 1 304 | # absolute 305 | elif self.opcode == 0x6D: 306 | value_location = self.fetch_uint16(2) 307 | value = self.fetch_uint16(1, address=value_location) 308 | self.pc += 2 309 | # absolute, x 310 | elif self.opcode == 0x7D: 311 | value_location = self.fetch_uint16(2) + self.x & 0xFFFF 312 | value = self.fetch_uint16(1, address=value_location) 313 | self.pc += 2 314 | # absolute, y 315 | elif self.opcode == 0x79: 316 | value_location = self.fetch_uint16(2) + self.y & 0xFFFF 317 | value = self.fetch_uint16(1, address=value_location) 318 | self.pc += 2 319 | # indirect, x 320 | elif self.opcode == 0x61: 321 | indirect_address = self.fetch_uint16(1) + self.x & 0xFF 322 | value_location = self.fetch_uint16(2, address=indirect_address) 323 | value = self.fetch_uint16(1, address=value_location) 324 | self.pc += 1 325 | # indirect, y (opcode 71) 326 | else: 327 | indirect_address = self.fetch_uint16(1) 328 | value_location = self.fetch_uint16(2, address=indirect_address) + self.y & 0xFFFF 329 | value = self.fetch_uint16(1, address=value_location) 330 | self.pc += 1 331 | self.z = 0 332 | self.n = 0 333 | self.v = 0 334 | old_accumulator = self.a 335 | self.a = self.a + value + self.c & 0xFF 336 | if self.a < old_accumulator: 337 | # accumulator overflowed and wrapped 338 | self.c = 1 339 | else: 340 | self.c = 0 341 | if self.a == 0: 342 | self.z = 1 343 | # if the old accumulator and value had the same sign (+ or -), AND the accumulator and sum have different signs 344 | # then set overflow 345 | # ^0xFF is used because python doesn't have a built-in unsigned bitwise NOT 346 | if ((old_accumulator ^ value) ^ 0xFF) & (old_accumulator ^ self.a) & 0x80: 347 | self.v = 1 348 | if self.a > 127: 349 | self.n = 1 350 | 351 | # AND - Logical AND 352 | # Flags: zero, negative 353 | def _and(self) -> None: 354 | # immediate 355 | if self.opcode == 0x29: 356 | value = self.fetch_uint16(1) 357 | self.pc += 1 358 | # zero page 359 | elif self.opcode == 0x25: 360 | value_location = self.fetch_uint16(1) 361 | value = self.fetch_uint16(1, address=value_location) 362 | self.pc += 1 363 | # zero page, x 364 | elif self.opcode == 0x35: 365 | value_location = self.fetch_uint16(1) + self.x & 0xFF 366 | value = self.fetch_uint16(1, address=value_location) 367 | self.pc += 1 368 | # absolute 369 | elif self.opcode == 0x2D: 370 | value_location = self.fetch_uint16(2) 371 | value = self.fetch_uint16(1, address=value_location) 372 | self.pc += 2 373 | # absolute, x 374 | elif self.opcode == 0x3D: 375 | value_location = self.fetch_uint16(2) + self.x & 0xFFFF 376 | value = self.fetch_uint16(1, address=value_location) 377 | self.pc += 2 378 | # absolute, y 379 | elif self.opcode == 0x39: 380 | value_location = self.fetch_uint16(2) + self.y & 0xFFFF 381 | value = self.fetch_uint16(1, address=value_location) 382 | self.pc += 2 383 | # indirect, x 384 | elif self.opcode == 0x21: 385 | indirect_address = self.fetch_uint16(1) + self.x & 0xFF 386 | value_location = self.fetch_uint16(2, address=indirect_address) 387 | value = self.fetch_uint16(1, address=value_location) 388 | self.pc += 1 389 | # indirect, y (opcode 31) 390 | else: 391 | indirect_address = self.fetch_uint16(1) 392 | value_location = self.fetch_uint16(2, address=indirect_address) + self.y & 0xFFFF 393 | value = self.fetch_uint16(1, address=value_location) 394 | self.pc += 1 395 | self.z = 0 396 | self.n = 0 397 | self.a = self.a & value 398 | if self.a == 0: 399 | self.z = 1 400 | elif self.a > 127: 401 | self.n = 1 402 | 403 | # ASL - Arithmetic Shift Left 404 | # Bitwise shift left by one bit. Bit 7 is shifted into the Carry flag. Bit 0 is set to zero. 405 | # Flags: carry, zero, negative 406 | def asl(self) -> None: 407 | # implicit (accumulator) 408 | if self.opcode == 0x0A: 409 | old_value = self.a 410 | value = old_value << 1 & 0xFF 411 | self.a = value 412 | # zero page 413 | elif self.opcode == 0x06: 414 | memory_location = self.fetch_uint16(1) 415 | old_value = self.fetch_uint16(1, address=memory_location) 416 | value = old_value << 1 & 0xFF 417 | self.memory[memory_location] = value 418 | self.pc += 1 419 | # zero page, x 420 | elif self.opcode == 0x16: 421 | memory_location = self.fetch_uint16(1) + self.x & 0xFF 422 | old_value = self.fetch_uint16(1, address=memory_location) 423 | value = old_value << 1 & 0xFF 424 | self.memory[memory_location] = value 425 | self.pc += 1 426 | # absolute 427 | elif self.opcode == 0x0E: 428 | memory_location = self.fetch_uint16(2) 429 | old_value = self.fetch_uint16(1, address=memory_location) 430 | value = old_value << 1 & 0xFF 431 | self.memory[memory_location] = value 432 | self.pc += 2 433 | # absolute, x (opcode 1E) 434 | else: 435 | memory_location = self.fetch_uint16(2) + self.x & 0xFFFF 436 | old_value = self.fetch_uint16(1, address=memory_location) 437 | value = old_value << 1 & 0xFF 438 | self.memory[memory_location] = value 439 | self.pc += 2 440 | self.c = old_value >> 7 & 1 441 | self.z = 0 442 | self.n = value >> 7 & 1 443 | if value == 0: 444 | self.z = 1 445 | 446 | # BCC - Branch if Carry Clear (90) 447 | # Branch to relative offset if carry flag is not set 448 | def bcc(self) -> None: 449 | offset = to_signed_int(self.fetch_uint16(1)) 450 | self.pc += 1 451 | if self.c == 0: 452 | self.pc += offset 453 | 454 | # BCS - Branch if Carry Set (B0) 455 | # Branch to relative offset if carry flag is not set 456 | def bcs(self) -> None: 457 | offset = to_signed_int(self.fetch_uint16(1)) 458 | self.pc += 1 459 | if self.c == 1: 460 | self.pc += offset 461 | 462 | # BEQ - Branch if Equal (F0) 463 | # Branch to relative offset if zero flag is set 464 | def beq(self) -> None: 465 | offset = to_signed_int(self.fetch_uint16(1)) 466 | self.pc += 1 467 | if self.z == 1: 468 | self.pc += offset 469 | 470 | # BIT - Bit Test 471 | # The value in memory is AND'd with the Accumulator to set the Zero flag, and then the result is discarded. 472 | # Bit 6 of that same value in memory is used to set the Overflow flag, and bit 7 is used for the Negative flag 473 | # Flags: zero, overflow, negative 474 | def bit(self) -> None: 475 | # zero page 476 | if self.opcode == 0x24: 477 | value_location = self.fetch_uint16(1) 478 | value = self.fetch_uint16(1, value_location) 479 | self.pc += 1 480 | # absolute (opcode 2C) 481 | else: 482 | value_location = self.fetch_uint16(2) 483 | value = self.fetch_uint16(1, value_location) 484 | self.pc += 2 485 | self.z = 0 486 | self.v = value >> 6 & 1 487 | self.n = value >> 7 & 1 488 | if self.a & value == 0: 489 | self.z = 1 490 | 491 | # BMI - Branch if Minus (30) 492 | # Branch to relative offset if negative flag is set 493 | def bmi(self) -> None: 494 | offset = to_signed_int(self.fetch_uint16(1)) 495 | self.pc += 1 496 | if self.n == 1: 497 | self.pc += offset 498 | 499 | # BNE - Branch if Not Equal (D0) 500 | # Branch to relative offset if zero flag is not set 501 | def bne(self) -> None: 502 | offset = to_signed_int(self.fetch_uint16(1)) 503 | self.pc += 1 504 | if self.z == 0: 505 | self.pc += offset 506 | 507 | # BPL - Branch if Positive (10) 508 | # Branch to relative offset if negative flag is not set 509 | def bpl(self) -> None: 510 | offset = to_signed_int(self.fetch_uint16(1)) 511 | self.pc += 1 512 | if self.n == 0: 513 | self.pc += offset 514 | 515 | # BRK - Force Interrupt (00) 516 | # Push PC onto stack. Set Break flag. Push processor flags onto stack. Set Interrupt flag. 517 | # Jump to IRQ Interrupt (0xFFFE-0xFFFF) 518 | def brk(self) -> None: 519 | self.pc += 1 # when we return from the interrupt, we want to go to the next instruction, not repeat the BRK 520 | self.push_pc() 521 | self.b = 1 522 | flags = self.get_flags() 523 | # BRK sets bits 5 and 4 of flags 524 | flags |= 0b11 << 4 525 | self.push(flags) 526 | self.i = 1 527 | irq_interrupt_location = self.fetch_uint16(2, address=0xFFFE) 528 | self.pc = irq_interrupt_location 529 | 530 | # BVC - Branch if Overflow Clear (50) 531 | # Branch to relative offset if overflow flag is not set 532 | def bvc(self) -> None: 533 | offset = to_signed_int(self.fetch_uint16(1)) 534 | self.pc += 1 535 | if self.v == 0: 536 | self.pc += offset 537 | 538 | # BVS - Branch if Overflow Set (70) 539 | # Branch to relative offset if overflow flag is set 540 | def bvs(self) -> None: 541 | offset = to_signed_int(self.fetch_uint16(1)) 542 | self.pc += 1 543 | if self.v == 1: 544 | self.pc += offset 545 | 546 | # CLC - Clear Carry Flag (18) 547 | def clc(self) -> None: 548 | self.c = 0 549 | 550 | # CLD - Clear Decimal Mode Flag (D8) 551 | def cld(self) -> None: 552 | self.d = 0 553 | 554 | # CLI - Clear Interrupt Disable Flag (58) 555 | def cli(self) -> None: 556 | self.i = 0 557 | 558 | # CLV - Clear Overflow Flag (B8) 559 | def clv(self) -> None: 560 | self.v = 0 561 | 562 | # CMP - Compare 563 | # Compares A with a value in memory. Set Carry if A>=M, set Zero if A==M, set Negative if A-M<0 564 | # Flags: carry, zero, negative 565 | def cmp(self) -> None: 566 | # immediate 567 | if self.opcode == 0xC9: 568 | value = self.fetch_uint16(1) 569 | self.pc += 1 570 | # zero page 571 | elif self.opcode == 0xC5: 572 | value_location = self.fetch_uint16(1) 573 | value = self.fetch_uint16(1, address=value_location) 574 | self.pc += 1 575 | # zero page, x 576 | elif self.opcode == 0xD5: 577 | value_location = self.fetch_uint16(1) + self.x & 0xFF 578 | value = self.fetch_uint16(1, address=value_location) 579 | self.pc += 1 580 | # absolute 581 | elif self.opcode == 0xCD: 582 | value_location = self.fetch_uint16(2) 583 | value = self.fetch_uint16(1, address=value_location) 584 | self.pc += 2 585 | # absolute, x 586 | elif self.opcode == 0xDD: 587 | value_location = self.fetch_uint16(2) + self.x & 0xFFFF 588 | value = self.fetch_uint16(1, address=value_location) 589 | self.pc += 2 590 | # absolute, y 591 | elif self.opcode == 0xD9: 592 | value_location = self.fetch_uint16(2) + self.y & 0xFFFF 593 | value = self.fetch_uint16(1, address=value_location) 594 | self.pc += 2 595 | # indirect, x 596 | elif self.opcode == 0xC1: 597 | indirect_address = self.fetch_uint16(1) + self.x & 0xFF 598 | value_location = self.fetch_uint16(2, address=indirect_address) 599 | value = self.fetch_uint16(1, address=value_location) 600 | self.pc += 1 601 | # indirect y (opcode D1) 602 | else: 603 | indirect_address = self.fetch_uint16(1) 604 | value_location = self.fetch_uint16(2, address=indirect_address) + self.y & 0xFFFF 605 | value = self.fetch_uint16(1, address=value_location) 606 | self.pc += 1 607 | self.c = 0 608 | self.z = 0 609 | self.n = 0 610 | if self.a >= value: 611 | self.c = 1 612 | result = self.a - value 613 | if result == 0: 614 | self.z = 1 615 | elif result < 0: 616 | self.n = 1 617 | 618 | # CPX - Compare X Register 619 | # Compares X with a value in memory. Set Carry if X>=M, set Zero if X==M, set Negative if X-M<0 620 | # Flags: carry, zero, negative 621 | def cpx(self) -> None: 622 | # immediate 623 | if self.opcode == 0xE0: 624 | value = self.fetch_uint16(1) 625 | self.pc += 1 626 | # zero page 627 | elif self.opcode == 0xE4: 628 | value_location = self.fetch_uint16(1) 629 | value = self.fetch_uint16(1, address=value_location) 630 | self.pc += 1 631 | # absolute (opcode EC) 632 | else: 633 | value_location = self.fetch_uint16(2) 634 | value = self.fetch_uint16(1, address=value_location) 635 | self.pc += 2 636 | self.c = 0 637 | self.z = 0 638 | self.n = 0 639 | if self.x >= value: 640 | self.c = 1 641 | result = self.x - value 642 | if result == 0: 643 | self.z = 1 644 | elif result < 0: 645 | self.n = 1 646 | 647 | # CPY - Compare Y Register 648 | # Compares Y with a value in memory. Set Carry if Y>=M, set Zero if Y==M, set Negative if Y-M<0 649 | # Flags: carry, zero, negative 650 | def cpy(self) -> None: 651 | # immediate 652 | if self.opcode == 0xC0: 653 | value = self.fetch_uint16(1) 654 | self.pc += 1 655 | # zero page 656 | elif self.opcode == 0xC4: 657 | value_location = self.fetch_uint16(1) 658 | value = self.fetch_uint16(1, address=value_location) 659 | self.pc += 1 660 | # absolute (opcode CC) 661 | else: 662 | value_location = self.fetch_uint16(2) 663 | value = self.fetch_uint16(1, address=value_location) 664 | self.pc += 2 665 | self.c = 0 666 | self.z = 0 667 | self.n = 0 668 | if self.y >= value: 669 | self.c = 1 670 | result = self.y - value 671 | if result == 0: 672 | self.z = 1 673 | elif result < 0: 674 | self.n = 1 675 | 676 | # DEC - Decrement Memory 677 | # Flags: zero, negative 678 | def dec(self) -> None: 679 | # zero page 680 | if self.opcode == 0xC6: 681 | value_location = self.fetch_uint16(1) 682 | self.pc += 1 683 | # zero page, x 684 | elif self.opcode == 0xD6: 685 | value_location = self.fetch_uint16(1) + self.x & 0xff 686 | self.pc += 1 687 | # absolute 688 | elif self.opcode == 0xCE: 689 | value_location = self.fetch_uint16(2) 690 | self.pc += 2 691 | # absolute, x (opcode DE) 692 | else: 693 | value_location = self.fetch_uint16(2) + self.x & 0xffff 694 | self.pc += 2 695 | self.z = 0 696 | self.n = 0 697 | value = self.fetch_uint16(1, address=value_location) - 1 & 0xFF 698 | self.memory[value_location] = value 699 | if value == 0: 700 | self.z = 1 701 | elif value > 127: 702 | self.n = 1 703 | 704 | # DEX - Decrement X Register (CA) 705 | # Flags: negative, zero 706 | def dex(self) -> None: 707 | self.n = 0 708 | self.z = 0 709 | self.x = self.x - 1 & 0xFF 710 | if self.x == 0: 711 | self.z = 1 712 | elif self.x > 127: 713 | self.n = 1 714 | 715 | # DEY - Decrement Y Register (88) 716 | # Flags: negative, zero 717 | def dey(self) -> None: 718 | self.n = 0 719 | self.z = 0 720 | self.y = self.y - 1 & 0xFF 721 | if self.y == 0: 722 | self.z = 1 723 | elif self.y > 127: 724 | self.n = 1 725 | 726 | # EOR - Exclusive OR 727 | # Performs an Exclusive OR on the Accumulator using a byte from memory 728 | # Flags: zero, negative 729 | def eor(self) -> None: 730 | # immediate 731 | if self.opcode == 0x49: 732 | value = self.fetch_uint16(1) 733 | self.pc += 1 734 | # zero page 735 | elif self.opcode == 0x45: 736 | value_location = self.fetch_uint16(1) 737 | value = self.fetch_uint16(1, address=value_location) 738 | self.pc += 1 739 | # zero page, x 740 | elif self.opcode == 0x55: 741 | value_location = self.fetch_uint16(1) + self.x & 0xFF 742 | value = self.fetch_uint16(1, address=value_location) 743 | self.pc += 1 744 | # absolute 745 | elif self.opcode == 0x4D: 746 | value_location = self.fetch_uint16(2) 747 | value = self.fetch_uint16(1, address=value_location) 748 | self.pc += 2 749 | # absolute, x 750 | elif self.opcode == 0x5D: 751 | value_location = self.fetch_uint16(2) + self.x & 0xffff 752 | value = self.fetch_uint16(1, address=value_location) 753 | self.pc += 2 754 | # absolute, y 755 | elif self.opcode == 0x59: 756 | value_location = self.fetch_uint16(2) + self.y & 0xffff 757 | value = self.fetch_uint16(1, address=value_location) 758 | self.pc += 2 759 | # indirect, x 760 | elif self.opcode == 0x41: 761 | indirect_address = self.fetch_uint16(1) + self.x & 0xFF 762 | value_location = self.fetch_uint16(2, address=indirect_address) 763 | value = self.fetch_uint16(1, address=value_location) 764 | self.pc += 1 765 | # indirect, y (opcode 51) 766 | else: 767 | indirect_address = self.fetch_uint16(1) 768 | value_location = self.fetch_uint16(2, address=indirect_address) + self.y & 0xFFFF 769 | value = self.fetch_uint16(1, address=value_location) 770 | self.pc += 1 771 | self.z = 0 772 | self.n = 0 773 | self.a = self.a ^ value 774 | if self.a == 0: 775 | self.z = 1 776 | elif self.a > 127: 777 | self.n = 1 778 | 779 | # INC - Increment Memory 780 | # Flags: zero, negative 781 | def inc(self) -> None: 782 | # zero page 783 | if self.opcode == 0xE6: 784 | value_location = self.fetch_uint16(1) 785 | self.pc += 1 786 | # zero page, x 787 | elif self.opcode == 0xF6: 788 | value_location = self.fetch_uint16(1) + self.x & 0xFF 789 | self.pc += 1 790 | # absolute 791 | elif self.opcode == 0xEE: 792 | value_location = self.fetch_uint16(2) 793 | self.pc += 2 794 | # absolute, x (opcode FE) 795 | else: 796 | value_location = self.fetch_uint16(2) + self.x & 0xffff 797 | self.pc += 2 798 | self.z = 0 799 | self.n = 0 800 | value = self.fetch_uint16(1, address=value_location) + 1 & 0xFF 801 | self.memory[value_location] = value 802 | if value == 0: 803 | self.z = 1 804 | elif value > 127: 805 | self.n = 1 806 | 807 | # INX - Increment X Register (E8) 808 | # Flags: negative, zero 809 | def inx(self) -> None: 810 | self.n = 0 811 | self.z = 0 812 | self.x = self.x + 1 & 0xFF 813 | if self.x == 0: 814 | self.z = 1 815 | elif self.x > 127: 816 | self.n = 1 817 | 818 | # INY - Increment Y Register (C8) 819 | # Flags: negative, zero 820 | def iny(self) -> None: 821 | self.n = 0 822 | self.z = 0 823 | self.y = self.y + 1 & 0xFF 824 | if self.y == 0: 825 | self.z = 1 826 | elif self.y > 127: 827 | self.n = 1 828 | 829 | # JMP - Jump 830 | # Set PC to specified address 831 | def jmp(self) -> None: 832 | location = self.fetch_uint16(2) 833 | # absolute (opcode 4C) -- no special logic 834 | # indirect (opcode 6C) 835 | if self.opcode == 0x6C: 836 | # indirect JMP on the 6502 has a bug where it wraps around to grab the MSB from $xx00 if the LSB is on $xxFF 837 | if location & 0xFF == 0xFF: 838 | msb_location = location & 0xFF00 839 | jump_to = self.fetch_memory(length=1, address=location) 840 | jump_to += self.fetch_memory(length=1, address=msb_location) 841 | location = to_uint16(jump_to) 842 | else: 843 | location = self.fetch_uint16(length=2, address=location) 844 | self.pc = location 845 | 846 | # JSR - Jump to Subroutine (20) 847 | # Store PC-1 in stack (RTS adds 1 when it returns), then jump to absolute address of subroutine 848 | def jsr(self) -> None: 849 | subroutine_loc = self.fetch_uint16(2) 850 | self.pc += 1 # set PC to last byte of current instruction 851 | self.push_pc() 852 | self.pc = subroutine_loc 853 | 854 | # LDA - Load Accumulator (A9, A5, B5, AD, BD, B9, A1, B1) 855 | # Load a byte into the accumulator. 856 | # Flags: negative, zero 857 | def lda(self) -> None: 858 | self.n = 0 859 | self.z = 0 860 | # immediate 861 | if self.opcode == 0xA9: 862 | value = self.fetch_uint16(1) 863 | self.pc += 1 864 | # zero page 865 | elif self.opcode == 0xA5: 866 | value_location = self.fetch_uint16(1) 867 | value = self.fetch_uint16(1, address=value_location) 868 | self.pc += 1 869 | # zero page,x 870 | elif self.opcode == 0xB5: 871 | value_location = self.fetch_uint16(1) + self.x & 0xFF 872 | value = self.fetch_uint16(1, address=value_location) 873 | self.pc += 1 874 | # absolute 875 | elif self.opcode == 0xAD: 876 | value_location = self.fetch_uint16(2) 877 | value = self.fetch_uint16(1, address=value_location) 878 | self.pc += 2 879 | # absolute,x 880 | elif self.opcode == 0xBD: 881 | value_location = self.fetch_uint16(2) + self.x & 0xffff 882 | value = self.fetch_uint16(1, address=value_location) 883 | self.pc += 2 884 | # absolute,y 885 | elif self.opcode == 0xB9: 886 | value_location = self.fetch_uint16(2) + self.y & 0xffff 887 | value = self.fetch_uint16(1, address=value_location) 888 | self.pc += 2 889 | # indirect,x 890 | elif self.opcode == 0xA1: 891 | indirect_address = self.fetch_uint16(1) + self.x & 0xFF 892 | value_location = self.fetch_uint16(2, address=indirect_address) 893 | value = self.fetch_uint16(1, address=value_location) 894 | self.pc += 1 895 | # indirect,y (opcode B1) 896 | else: 897 | indirect_address = self.fetch_uint16(1) 898 | value_location = self.fetch_uint16(2, address=indirect_address) + self.y & 0xFFFF 899 | value = self.fetch_uint16(1, address=value_location) 900 | self.pc += 1 901 | self.a = value 902 | if value > 127: 903 | self.n = 1 904 | elif value == 0: 905 | self.z = 1 906 | 907 | # LDX - Load X Register 908 | # Load a byte into the X register. 909 | # Flags: negative, zero 910 | def ldx(self) -> None: 911 | self.n = 0 912 | self.z = 0 913 | # immediate 914 | if self.opcode == 0xA2: 915 | data_to_load = self.fetch_uint16(1) 916 | self.pc += 1 917 | # zero page 918 | elif self.opcode == 0xA6: 919 | memory_location = self.fetch_uint16(1) 920 | data_to_load = self.fetch_uint16(1, address=memory_location) 921 | self.pc += 1 922 | # zero page,y 923 | elif self.opcode == 0xB6: 924 | memory_location = self.fetch_uint16(1) + self.y & 0xFF 925 | data_to_load = self.fetch_uint16(1, address=memory_location) 926 | self.pc += 1 927 | # absolute 928 | elif self.opcode == 0xAE: 929 | memory_location = self.fetch_uint16(2) 930 | data_to_load = self.fetch_uint16(1, address=memory_location) 931 | self.pc += 2 932 | # absolute,y (opcode BE) 933 | else: 934 | memory_location = self.fetch_uint16(2) + self.y & 0xFFFF 935 | data_to_load = self.fetch_uint16(1, address=memory_location) 936 | self.pc += 2 937 | self.x = data_to_load 938 | if data_to_load == 0: 939 | self.z = 1 940 | elif data_to_load > 127: 941 | self.n = 1 942 | 943 | # LDY - Load Y Register 944 | # Load a byte into the Y register. 945 | # Flags: negative, zero 946 | def ldy(self) -> None: 947 | self.n = 0 948 | self.z = 0 949 | # immediate 950 | if self.opcode == 0xA0: 951 | data_to_load = self.fetch_uint16(1) 952 | self.pc += 1 953 | # zero page 954 | elif self.opcode == 0xA4: 955 | memory_location = self.fetch_uint16(1) 956 | data_to_load = self.fetch_uint16(1, address=memory_location) 957 | self.pc += 1 958 | # zero page, x 959 | elif self.opcode == 0xB4: 960 | memory_location = self.fetch_uint16(1) + self.x & 0xFF 961 | data_to_load = self.fetch_uint16(1, address=memory_location) 962 | self.pc += 1 963 | # absolute 964 | elif self.opcode == 0xAC: 965 | memory_location = self.fetch_uint16(2) 966 | data_to_load = self.fetch_uint16(1, address=memory_location) 967 | self.pc += 2 968 | # absolute, x (opcode BC) 969 | else: 970 | memory_location = self.fetch_uint16(2) + self.x & 0xFFFF 971 | data_to_load = self.fetch_uint16(1, address=memory_location) 972 | self.pc += 2 973 | self.y = data_to_load 974 | if data_to_load == 0: 975 | self.z = 1 976 | elif data_to_load > 127: 977 | self.n = 1 978 | 979 | # LSR - Logical Shift Right 980 | # Bitwise shift right by one bit. Bit 0 is shifted into the Carry flag. Bit 7 is set to zero. 981 | # Flags: carry, zero, negative 982 | def lsr(self) -> None: 983 | # implicit (accumulator) 984 | if self.opcode == 0x4A: 985 | old_value = self.a 986 | value = old_value >> 1 987 | self.a = value 988 | # zero page 989 | elif self.opcode == 0x46: 990 | memory_location = self.fetch_uint16(1) 991 | old_value = self.fetch_uint16(1, address=memory_location) 992 | value = old_value >> 1 993 | self.memory[memory_location] = value 994 | self.pc += 1 995 | # zero page, x 996 | elif self.opcode == 0x56: 997 | memory_location = self.fetch_uint16(1) + self.x & 0xFF 998 | old_value = self.fetch_uint16(1, address=memory_location) 999 | value = old_value >> 1 1000 | self.memory[memory_location] = value 1001 | self.pc += 1 1002 | # absolute 1003 | elif self.opcode == 0x4E: 1004 | memory_location = self.fetch_uint16(2) 1005 | old_value = self.fetch_uint16(1, address=memory_location) 1006 | value = old_value >> 1 1007 | self.memory[memory_location] = value 1008 | self.pc += 2 1009 | # absolute, x (opcode 5E) 1010 | else: 1011 | memory_location = self.fetch_uint16(2) + self.x & 0xFFFF 1012 | old_value = self.fetch_uint16(1, address=memory_location) 1013 | value = old_value >> 1 1014 | self.memory[memory_location] = value 1015 | self.pc += 2 1016 | self.c = old_value & 1 1017 | self.z = 0 1018 | self.n = 0 1019 | if value == 0: 1020 | self.z = 1 1021 | 1022 | # NOP - No Operation (1 byte) 1023 | def nop(self) -> None: 1024 | pass 1025 | 1026 | def nop_immediate(self) -> None: 1027 | """ 1028 | Unofficial opcode for NOP. Reads an immediate byte and ignores the value. 1029 | """ 1030 | self.pc += 1 1031 | 1032 | # ORA - Logical Inclusive OR 1033 | # Performs a bitwise OR on the Accumulator using a byte from memory 1034 | # Flags: zero, negative 1035 | def ora(self) -> None: 1036 | # immediate 1037 | if self.opcode == 0x09: 1038 | value = self.fetch_uint16(1) 1039 | self.pc += 1 1040 | # zero page 1041 | elif self.opcode == 0x05: 1042 | value_location = self.fetch_uint16(1) 1043 | value = self.fetch_uint16(1, address=value_location) 1044 | self.pc += 1 1045 | # zero page, x 1046 | elif self.opcode == 0x15: 1047 | value_location = self.fetch_uint16(1) + self.x & 0xFF 1048 | value = self.fetch_uint16(1, address=value_location) 1049 | self.pc += 1 1050 | # absolute 1051 | elif self.opcode == 0x0D: 1052 | value_location = self.fetch_uint16(2) 1053 | value = self.fetch_uint16(1, address=value_location) 1054 | self.pc += 2 1055 | # absolute, x 1056 | elif self.opcode == 0x1D: 1057 | value_location = self.fetch_uint16(2) + self.x & 0xFFFF 1058 | value = self.fetch_uint16(1, address=value_location) 1059 | self.pc += 2 1060 | # absolute, y 1061 | elif self.opcode == 0x19: 1062 | value_location = self.fetch_uint16(2) + self.y & 0xFFFF 1063 | value = self.fetch_uint16(1, address=value_location) 1064 | self.pc += 2 1065 | # indirect, x 1066 | elif self.opcode == 0x01: 1067 | indirect_address = self.fetch_uint16(1) + self.x & 0xFF 1068 | value_location = self.fetch_uint16(2, address=indirect_address) 1069 | value = self.fetch_uint16(1, address=value_location) 1070 | self.pc += 1 1071 | # indirect, y (opcode 11) 1072 | else: 1073 | indirect_address = self.fetch_uint16(1) 1074 | value_location = self.fetch_uint16(2, address=indirect_address) + self.y & 0xFFFF 1075 | value = self.fetch_uint16(1, address=value_location) 1076 | self.pc += 1 1077 | self.z = 0 1078 | self.n = 0 1079 | self.a = self.a | value 1080 | if self.a == 0: 1081 | self.z = 1 1082 | elif self.a > 127: 1083 | self.n = 1 1084 | 1085 | # PHA - Push Accumulator (48) 1086 | def pha(self) -> None: 1087 | self.push(self.a) 1088 | 1089 | # PHP - Push Processor Status (08) 1090 | def php(self) -> None: 1091 | flags = self.get_flags() 1092 | # PHP sets bits 5 and 4 when pushing the flags, but does not actually change the state of the flags 1093 | flags |= 0b11 << 4 1094 | self.push(flags) 1095 | 1096 | # PLA - Pull Accumulator (68) 1097 | # Flags: negative, zero 1098 | def pla(self) -> None: 1099 | self.n = 0 1100 | self.z = 0 1101 | self.a = self.pop() 1102 | if self.a == 0: 1103 | self.z = 1 1104 | elif self.a > 127: 1105 | self.n = 1 1106 | 1107 | # PLP - Pull Processor Status (28) 1108 | # Flags: all 1109 | def plp(self) -> None: 1110 | self.set_flags(self.pop()) 1111 | 1112 | # ROL - Rotate Left 1113 | # Flags: carry, zero, negative 1114 | def rol(self) -> None: 1115 | # implicit (accumulator) 1116 | if self.opcode == 0x2A: 1117 | old_value = self.a 1118 | value = old_value << 1 & 0xFF | self.c 1119 | self.a = value 1120 | # zero page 1121 | elif self.opcode == 0x26: 1122 | memory_location = self.fetch_uint16(1) 1123 | old_value = self.fetch_uint16(1, address=memory_location) 1124 | value = old_value << 1 & 0xFF | self.c 1125 | self.memory[memory_location] = value 1126 | self.pc += 1 1127 | # zero page, x 1128 | elif self.opcode == 0x36: 1129 | memory_location = self.fetch_uint16(1) + self.x & 0xFF 1130 | old_value = self.fetch_uint16(1, address=memory_location) 1131 | value = old_value << 1 & 0xFF | self.c 1132 | self.memory[memory_location] = value 1133 | self.pc += 1 1134 | # absolute 1135 | elif self.opcode == 0x2E: 1136 | memory_location = self.fetch_uint16(2) 1137 | old_value = self.fetch_uint16(1, address=memory_location) 1138 | value = old_value << 1 & 0xFF | self.c 1139 | self.memory[memory_location] = value 1140 | self.pc += 2 1141 | # absolute, x (opcode 3E) 1142 | else: 1143 | memory_location = self.fetch_uint16(2) + self.x & 0xFFFF 1144 | old_value = self.fetch_uint16(1, address=memory_location) 1145 | value = old_value << 1 & 0xFF | self.c 1146 | self.memory[memory_location] = value 1147 | self.pc += 2 1148 | self.c = 0 1149 | self.z = 0 1150 | self.n = 0 1151 | if value == 0: 1152 | self.z = 1 1153 | elif value > 127: 1154 | self.n = 1 1155 | if old_value > 127: 1156 | self.c = 1 1157 | 1158 | # ROR - Rotate Right 1159 | # Flags: carry, zero, negative 1160 | def ror(self) -> None: 1161 | # implicit (accumulator) 1162 | if self.opcode == 0x6A: 1163 | old_value = self.a 1164 | value = old_value >> 1 | self.c << 7 1165 | self.a = value 1166 | # zero page 1167 | elif self.opcode == 0x66: 1168 | memory_location = self.fetch_uint16(1) 1169 | old_value = self.fetch_uint16(1, address=memory_location) 1170 | value = old_value >> 1 | self.c << 7 1171 | self.memory[memory_location] = value 1172 | self.pc += 1 1173 | # zero page, x 1174 | elif self.opcode == 0x76: 1175 | memory_location = self.fetch_uint16(1) + self.x & 0xFF 1176 | old_value = self.fetch_uint16(1, address=memory_location) 1177 | value = old_value >> 1 | self.c << 7 1178 | self.memory[memory_location] = value 1179 | self.pc += 1 1180 | # absolute 1181 | elif self.opcode == 0x6E: 1182 | memory_location = self.fetch_uint16(2) 1183 | old_value = self.fetch_uint16(1, address=memory_location) 1184 | value = old_value >> 1 | self.c << 7 1185 | self.memory[memory_location] = value 1186 | self.pc += 2 1187 | # absolute, x (opcode 7E) 1188 | else: 1189 | memory_location = self.fetch_uint16(2) + self.x & 0xFFFF 1190 | old_value = self.fetch_uint16(1, address=memory_location) 1191 | value = old_value >> 1 | self.c << 7 1192 | self.memory[memory_location] = value 1193 | self.pc += 2 1194 | self.c = old_value & 1 1195 | self.z = 0 1196 | self.n = 0 1197 | if value == 0: 1198 | self.z = 1 1199 | elif value > 127: 1200 | self.n = 1 1201 | 1202 | # RTI - Return from Interrupt (40) 1203 | # Pop processor flags from stack, followed by the program counter 1204 | def rti(self) -> None: 1205 | self.set_flags(self.pop()) 1206 | self.pc = self.pop16() 1207 | 1208 | # RTS - Return from Subroutine (60) 1209 | # Pop return address from stack and jump to it 1210 | def rts(self) -> None: 1211 | self.pc = self.pop16() 1212 | self.pc += 1 1213 | 1214 | # SBC - Subtract with Carry 1215 | # Flags: carry, zero, overflow, negative 1216 | def sbc(self) -> None: 1217 | # immediate 1218 | if self.opcode == 0xE9: 1219 | value = self.fetch_uint16(1) 1220 | self.pc += 1 1221 | # zero page 1222 | elif self.opcode == 0xE5: 1223 | value_location = self.fetch_uint16(1) 1224 | value = self.fetch_uint16(1, address=value_location) 1225 | self.pc += 1 1226 | # zero page, x 1227 | elif self.opcode == 0xF5: 1228 | value_location = self.fetch_uint16(1) + self.x & 0xFF 1229 | value = self.fetch_uint16(1, address=value_location) 1230 | self.pc += 1 1231 | # absolute 1232 | elif self.opcode == 0xED: 1233 | value_location = self.fetch_uint16(2) 1234 | value = self.fetch_uint16(1, address=value_location) 1235 | self.pc += 2 1236 | # absolute, x 1237 | elif self.opcode == 0xFD: 1238 | value_location = self.fetch_uint16(2) + self.x & 0xFFFF 1239 | value = self.fetch_uint16(1, address=value_location) 1240 | self.pc += 2 1241 | # absolute, y 1242 | elif self.opcode == 0xF9: 1243 | value_location = self.fetch_uint16(2) + self.y & 0xFFFF 1244 | value = self.fetch_uint16(1, address=value_location) 1245 | self.pc += 2 1246 | # indirect, x 1247 | elif self.opcode == 0xE1: 1248 | indirect_address = self.fetch_uint16(1) + self.x & 0xFF 1249 | value_location = self.fetch_uint16(2, address=indirect_address) 1250 | value = self.fetch_uint16(1, address=value_location) 1251 | self.pc += 1 1252 | # indirect, y (opcode F1) 1253 | else: 1254 | indirect_address = self.fetch_uint16(1) 1255 | value_location = self.fetch_uint16(2, address=indirect_address) + self.y & 0xFFFF 1256 | value = self.fetch_uint16(1, address=value_location) 1257 | self.pc += 1 1258 | self.z = 0 1259 | self.n = 0 1260 | self.v = 0 1261 | old_accumulator = self.a 1262 | self.a = self.a - value - (1 - self.c) & 0xFF 1263 | self.c = 1 1264 | if self.a > old_accumulator: 1265 | self.c = 0 1266 | if self.a == 0: 1267 | self.z = 1 1268 | # if the sign (+ or -) of both inputs is different from the sign of the result, overflow is set 1269 | # in the hardware, SBC is more like adding a negative number than subtracting a positive number. 1270 | # with that in mind, overflow works a little differently. invert the bits on value before doing the below check. 1271 | value = value ^ 0xFF 1272 | if ((old_accumulator ^ value) ^ 0xFF) & (old_accumulator ^ self.a) & 0x80: 1273 | self.v = 1 1274 | if self.a > 127: 1275 | self.n = 1 1276 | 1277 | # SEC - Set Carry (38) 1278 | def sec(self) -> None: 1279 | self.c = 1 1280 | 1281 | # SED - Set Decimal (F8) 1282 | def sed(self) -> None: 1283 | self.d = 1 1284 | 1285 | # SEI - Set Interrupt Disable (78) 1286 | def sei(self) -> None: 1287 | self.i = 1 1288 | 1289 | # STA - Store Accumulator 1290 | # Store the accumulator at the specified location in memory 1291 | def sta(self) -> None: 1292 | # zero page 1293 | if self.opcode == 0x85: 1294 | memory_location = self.fetch_uint16(1) 1295 | self.pc += 1 1296 | # zero page,x 1297 | elif self.opcode == 0x95: 1298 | memory_location = self.fetch_uint16(1) + self.x & 0xFF 1299 | self.pc += 1 1300 | # absolute 1301 | elif self.opcode == 0x8D: 1302 | memory_location = self.fetch_uint16(2) 1303 | self.pc += 2 1304 | # absolute,x 1305 | elif self.opcode == 0x9D: 1306 | memory_location = self.fetch_uint16(2) + self.x & 0xFFFF 1307 | self.pc += 2 1308 | # absolute,y 1309 | elif self.opcode == 0x99: 1310 | memory_location = self.fetch_uint16(2) + self.y & 0xFFFF 1311 | self.pc += 2 1312 | # indirect,x 1313 | elif self.opcode == 0x81: 1314 | indirect_address = self.fetch_uint16(1) + self.x & 0xFF 1315 | memory_location = self.fetch_uint16(2, address=indirect_address) 1316 | self.pc += 1 1317 | # indirect,y (opcode 91) 1318 | else: 1319 | indirect_address = self.fetch_uint16(1) 1320 | memory_location = self.fetch_uint16(2, address=indirect_address) + self.y & 0xFFFF 1321 | self.pc += 1 1322 | self.memory[memory_location] = self.a 1323 | 1324 | # STX - Store X Register 1325 | # Store the X register at the specified location in memory 1326 | def stx(self) -> None: 1327 | # zero page 1328 | if self.opcode == 0x86: 1329 | memory_location = self.fetch_uint16(1) 1330 | self.pc += 1 1331 | # zero page, y 1332 | elif self.opcode == 0x96: 1333 | memory_location = self.fetch_uint16(1) + self.y & 0xFF 1334 | self.pc += 1 1335 | # absolute (opcode 8E) 1336 | else: 1337 | memory_location = self.fetch_uint16(2) 1338 | self.pc += 2 1339 | self.memory[memory_location] = self.x 1340 | 1341 | # STY - Store Y Register 1342 | # Store the Y register at the specified location in memory 1343 | def sty(self) -> None: 1344 | # zero page 1345 | if self.opcode == 0x84: 1346 | memory_location = self.fetch_uint16(1) 1347 | self.pc += 1 1348 | # zero page, x 1349 | elif self.opcode == 0x94: 1350 | memory_location = self.fetch_uint16(1) + self.x & 0xFF 1351 | self.pc += 1 1352 | # absolute (opcode 8C) 1353 | else: 1354 | memory_location = self.fetch_uint16(2) 1355 | self.pc += 2 1356 | self.memory[memory_location] = self.y 1357 | 1358 | # TAX - Transfer Accumulator to X (AA) 1359 | # Flags: zero, negative 1360 | def tax(self) -> None: 1361 | self.z = 0 1362 | self.n = 0 1363 | self.x = self.a 1364 | if self.x == 0: 1365 | self.z = 1 1366 | elif self.x > 127: 1367 | self.n = 1 1368 | 1369 | # TAY - Transfer Accumulator to Y (A8) 1370 | # Flags: zero, negative 1371 | def tay(self) -> None: 1372 | self.z = 0 1373 | self.n = 0 1374 | self.y = self.a 1375 | if self.y == 0: 1376 | self.z = 1 1377 | elif self.y > 127: 1378 | self.n = 1 1379 | 1380 | # TSX - Transfer Stack Pointer to X (BA) 1381 | # Flags: zero, negative 1382 | def tsx(self) -> None: 1383 | self.z = 0 1384 | self.n = 0 1385 | self.x = self.sp 1386 | if self.x == 0: 1387 | self.z = 1 1388 | elif self.x > 127: 1389 | self.n = 1 1390 | 1391 | # TXA - Transfer X to Accumulator (8A) 1392 | # Flags: zero, negative 1393 | def txa(self) -> None: 1394 | self.z = 0 1395 | self.n = 0 1396 | self.a = self.x 1397 | if self.a == 0: 1398 | self.z = 1 1399 | elif self.a > 127: 1400 | self.n = 1 1401 | 1402 | # TXS - Transfer X to Stack Pointer (9A) 1403 | def txs(self) -> None: 1404 | self.sp = self.x 1405 | 1406 | # TYA - Transfer Y to Accumulator(98) 1407 | # Flags: zero, negative 1408 | def tya(self) -> None: 1409 | self.z = 0 1410 | self.n = 0 1411 | self.a = self.y 1412 | if self.a == 0: 1413 | self.z = 1 1414 | elif self.a > 127: 1415 | self.n = 1 1416 | --------------------------------------------------------------------------------