diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..84d4140 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +games diff --git a/debug.lua b/debug.lua new file mode 100644 index 0000000..2a4cfe1 --- /dev/null +++ b/debug.lua @@ -0,0 +1,146 @@ +dofile("/gameboy/z80.lua") +dofile("/gameboy/rom_header.lua") +dofile("/gameboy/graphics.lua") +dofile("/gameboy/input.lua") + +args = {...} + +filename = args[1] +file = fs.open("/gameboy/games/"..filename, "rb") +if file then + print("Reading cartridge into memory...") + cart_data = read_file_into_byte_array(file) + print("Read " .. math.ceil(#cart_data / 1024) .. " kB") + print_cartridge_header(cart_data) +else + print("Couldn't open ", filename, " bailing.") + return +end + +write("Initializing main memory...") +initialize_memory() +print("Done!") + +initialize_graphics() + +write("Copying cart data into lower 0x7FFF of main memory...") +for i = 0, 0x7FFF do + memory[i] = cart_data[i] +end +print("Done!") + +function print_register_values() + local c = reg.flags.c == 1 and "c" or " " + local n = reg.flags.n == 1 and "n" or " " + local h = reg.flags.h == 1 and "h" or " " + local z = reg.flags.z == 1 and "z" or " " + write(string.format("AF: 0x%02X 0x%02X - ", reg.a, reg.f())) + print(string.format("BC: 0x%02X 0x%02X ", reg.b, reg.c)) + write(string.format("DE: 0x%02X 0x%02X - ", reg.d, reg.e)) + print(string.format("HL: 0x%02X 0x%02X", reg.h, reg.l)) + write(string.format("[%s %s %s %s] ", c, n, h, z)) + print(string.format("(HL): 0x%02X", read_byte(reg.hl()))) + write(string.format("PC: 0x%04X (PC): 0x%02X ", reg.pc, read_byte(reg.pc))) + write(string.format("NN: 0x%02X 0x%02X ", read_byte(reg.pc + 1), read_byte(reg.pc + 2))) + write(string.format("(0x%02X 0x%02X)", read_byte(reg.pc + 3), read_byte(reg.pc + 4))) + print(string.format("SP: 0x%04X (SP): 0x%02X 0x%02X %d %d", reg.sp, read_byte(reg.sp), read_byte(reg.sp + 1), read_byte(reg.sp), read_byte(reg.sp + 1))) + print(string.format("Clock: %d", clock)) + print(string.format("GPU: Mode: %d Scanline: %d", Status.Mode(), scanline())) + print(string.format("Halted: %d IME: %d IE: 0x%04X IF: 0x%04X", halted, interrupts_enabled, read_byte(0xFFFF), read_byte(0xFF0F))) + print("[Space] = Step | [K] = Run 1000") + print("[R] = Run Until Error or Breakpoint") + print("[V] = Run Until VBlank") + print("[H] = Run until HBlank") + print("Draw: [T] Tiles, [B] = BG, [W] = Window, [S] = Sprites, [D] = Entire Screen") +end + +print("Press [Enter] to begin execution.") +read() + +function run_one_opcode() + update_graphics() + update_input() + return process_instruction() +end + +term.clear() +term.setCursorPos(1,1) +print_register_values() +char = "" +while char ~= "q" do + event, char = os.pullEvent("char") + if char == " " then + run_one_opcode() + term.clear() + term.setCursorPos(1,1) + print_register_values() + end + if char == "k" then + for i = 1, 1000 do + process_instruction() + update_graphics() + end + term.clear() + term.setCursorPos(1,1) + print_register_values() + end + if char == "r" then + count = 0 + while run_one_opcode() do + count = count + 1 + if count >= 20000 then + term.clear() + term.setCursorPos(1,1) + print_register_values() + --draw_tiles() + draw_background() + os.sleep(0) + count = 0 + end + end + term.clear() + term.setCursorPos(1,1) + print_register_values() + end + if char == "h" then + old_scanline = scanline() + while old_scanline == scanline() do + run_one_opcode() + end + term.clear() + term.setCursorPos(1,1) + print_register_values() + end + if char == "v" then + while scanline() == 144 do + run_one_opcode() + end + while scanline() ~= 144 do + run_one_opcode() + end + term.clear() + term.setCursorPos(1,1) + print_register_values() + draw_tiles() + end + if char == "t" then + draw_tiles() + print("Drew Tiles") + end + if char == "b" then + draw_background() + print("Drew Main BG") + end + if char == "w" then + draw_window() + print("Drew Window") + end + if char == "s" then + draw_sprites() + print("Drew Sprites") + end + if char == "d" then + debug_draw_screen() + print("Drew entire screen!") + end +end diff --git a/graphics.lua b/graphics.lua new file mode 100644 index 0000000..c4bb577 --- /dev/null +++ b/graphics.lua @@ -0,0 +1,456 @@ +-- Various functions for manipulating IO in memory +local LCDC = function() + return memory[0xFF40] +end + +local STAT = function() + return memory[0xFF41] +end + +local setSTAT = function(value) + memory[0xFF41] = value +end + +LCD_Control = {} +LCD_Control.DisplayEnabled = function() + return bit32.band(0x80, LCDC()) ~= 0 +end + +LCD_Control.WindowTilemap = function() + if bit32.band(0x40, LCDC()) ~= 0 then + return 0x9C00 + else + return 0x9800 + end +end + +LCD_Control.WindowEnabled = function() + return bit32.band(0x20, LCDC()) ~= 0 +end + +LCD_Control.TileData = function() + if bit32.band(0x10, LCDC()) ~= 0 then + return 0x8000 + else + return 0x9000 + end +end + +LCD_Control.BackgroundTilemap = function() + if bit32.band(0x08, LCDC()) ~= 0 then + return 0x9C00 + else + return 0x9800 + end +end + +LCD_Control.LargeSprites = function() + return bit32.band(0x04, LCDC()) ~= 0 +end + +LCD_Control.SpritesEnabled = function() + return bit32.band(0x02, LCDC()) ~= 0 +end + +LCD_Control.BackgroundEnabled = function() + return bit32.band(0x01, LCDC()) ~= 0 +end + +Status = {} +Status.Coincidence_InterruptEnabled = function() + return bit32.band(0x20, STAT()) ~= 0 +end + +Status.OAM_InterruptEnabled = function() + return bit32.band(0x10, STAT()) ~= 0 +end + +Status.VBlank_InterruptEnabled = function() + return bit32.band(0x08, STAT()) ~= 0 +end + +Status.HBlank_InterruptEnabled = function() + return bit32.band(0x06, STAT()) ~= 0 +end + +Status.Mode = function() + return bit32.band(memory[0xFF41], 0x3) +end + +Status.SetMode = function(mode) + memory[0xFF41] = bit32.band(STAT(), 0xF8) + bit32.band(mode, 0x3) + if mode == 0 then + -- HBlank + --draw_scanline(scanline()) + end + if mode == 1 then + if LCD_Control.DisplayEnabled() then + -- VBlank + --draw_screen() + else + --clear_screen() + end + end +end + +local SCY = function() + return memory[0xFF42] +end + +local SCX = function() + return memory[0xFF43] +end + +local WY = function() + return memory[0xFF4A] +end + +local WX = function() + return memory[0xFF4B] +end + +scanline = function() + return memory[0xFF44] +end + +local set_scanline = function(value) + memory[0xFF44] = value +end + +local scanline_compare = function() + return memory[0xFF45] +end + +local last_edge = 0 +local handle_mode = {} + +time_at_this_mode = function() + return clock - last_edge +end + +-- HBlank: Period between scanlines +handle_mode[0] = function() + if clock - last_edge > 204 then + last_edge = last_edge + 204 + set_scanline(scanline() + 1) + -- If enabled, fire an HBlank interrupt + if bit32.btest(STAT(), 0x08) then + request_interrupt(Interrupt.LCDStat) + end + if scanline() == scanline_compare() then + -- set the LY compare bit + setSTAT(bit32.bor(STAT(), 0x4)) + if bit32.btest(STAT(), 0x40) then + request_interrupt(Interrupt.LCDStat) + end + else + -- clear the LY compare bit + setSTAT(bit32.band(STAT(), 0xFB)) + end + if scanline() >= 144 then + Status.SetMode(1) + request_interrupt(Interrupt.VBlank) + if bit32.btest(STAT(), 0x10) then + -- This is weird; LCDStat mirrors VBlank? + request_interrupt(Interrupt.LCDStat) + end + -- TODO: Draw the real screen here? + else + Status.SetMode(2) + if bit32.btest(STAT(), 0x20) then + request_interrupt(Interrupt.LCDStat) + end + end + end +end + +--VBlank: nothing to do except wait for the next frame +handle_mode[1] = function() + if clock - last_edge > 456 then + last_edge = last_edge + 456 + set_scanline(scanline() + 1) + end + if scanline() >= 154 then + set_scanline(0) + Status.SetMode(2) + if bit32.btest(STAT(), 0x20) then + request_interrupt(Interrupt.LCDStat) + end + end + if scanline() == scanline_compare() then + -- TODO: fire LCD STAT interrupt, and set appropriate flag + end +end + +-- OAM Read: OAM cannot be accessed +handle_mode[2] = function() + if clock - last_edge > 80 then + last_edge = last_edge + 80 + Status.SetMode(3) + end +end + +-- VRAM Read: Neither VRAM, OAM, nor CGB palettes can be read +handle_mode[3] = function() + if clock - last_edge > 172 then + last_edge = last_edge + 172 + Status.SetMode(0) + -- TODO: Fire HBlank interrupt here!! + -- TODO: Draw one scanline of graphics here! + end +end + +function initialize_graphics() + Status.SetMode(2) +end + +function update_graphics() + handle_mode[Status.Mode()]() +end + +colors = {} +colors[0] = {0, 0, 0} +colors[1] = {128, 128, 128} +colors[2] = {192, 192, 192} +colors[3] = {255, 255, 255} + +gpu = peripheral.wrap("left") +background_texture = gpu.createTexture(256,256) + +game_screen = {} +for y = 0, 143 do + game_screen[y] = {} + for x = 1, 160 * 4 + 4 do + game_screen[y][x] = 255 + end +end + +function plot_pixel(buffer, x, y, r, g, b) + local weird_offset = 4 + buffer[y][x + weird_offset ] = r + buffer[y][x + weird_offset + 1] = g + buffer[y][x + weird_offset + 2] = b + buffer[y][x + weird_offset + 3] = 255 +end + +function draw_buffer(buffer, width, height, x, y) + -- This is to work around an indexing bug in CCLights 2; when this is + -- eventually fixed, I believe the correct behavior will simply be to + -- remove the offsets on width and the x coordinate. + for dy = 0, height - 1 do + gpu.setPixels(width + 1, 1, x - 1, y + dy, buffer[dy]) + end +end + +function draw_screen() + draw_buffer(game_screen, 160, 144, 0, 0) +end + +function clear_screen() + gpu.setColor(255, 255, 255, 255) + gpu.filledRectangle(0, 0, 160, 144) +end + +function debug_draw_screen() + for i = 0, 143 do + draw_scanline(i) + end + print("Plotting screen to GPU") + draw_screen() + print("Didn't crash?") +end + +function getColorFromTilemap(map_address, x, y) + local tile_x = bit32.rshift(x, 3) + local tile_y = bit32.rshift(y, 3) + local tile_index = memory[map_address + (tile_y * 32) + (tile_x)] + if LCD_Control.TileData() == 0x8800 then + if tile_index > 127 then + tile_index = tile_index - 256 + end + end + tile_address = LCD_Control.TileData() + index * 16 + + local subpixel_x = x - (tile_x * 8) + local subpixel_y = y - (tile_y * 8) + -- move to the row we need this pixel from + while subpixel_y >= 0 do + tile_address = tile_address + 2 + subpixel_y = subpixel_y - 1 + end + -- grab the pixel color we need, and translate it into a palette index + local palette_index = 0 + if bit32.band(memory[tile_address], bit32.lshift(0x1, 7 - subpixel_x)) ~= 0 then + palette_index = palette_index + 1 + end + tile_address = tile_address + 1 + if bit32.band(memory[tile_address], bit32.lshift(0x1, 7 - subpixel_x)) ~= 0 then + palette_index = palette_index + 2 + end + -- finally, return the color from the table, based on this index + -- todo: allow specifying the palette? + return colors[palette_index] +end + +function draw_scanline(scanline) + local bg_y = scanline + SCY() + local bg_x = SCX() + -- wrap the map in the Y direction + if bg_y > 256 then + bg_y = bg_y - 256 + end + local w_y = scanline + WY() + local w_x = WX() + 7 + local window_visible = false + if w_x <= 166 and w_y <= 143 then + window_visible = true + end + + for x = 0, 159 do + if LCD_Control.BackgroundEnabled() then + local bg_color = getColorFromTilemap(LCD_Control.BackgroundTilemap(), bg_x, bg_y) + plot_pixel(game_screen, x, scanline, unpack(bg_color)) + end + if LCD_Control.WindowEnabled() and window_visible then + -- The window doesn't wrap, so make sure these coordinates are valid + -- (ie, inside the window map) before attempting to plot a pixel + if w_x > 0 and w_x < 256 and w_y > 0 and w_y < 256 then + local window_color = getColorFromTilemap(LCD_Control.WindowTilemap(), w_x, w_y) + plot_pixel(game_screen, x, scanline, unpack(window_color)) + end + end + bg_x = bg_x + 1 + if bg_x >= 256 then + bg_x = bg_x - 256 + end + w_x = w_x + 1 + end +end + +function draw_half_scale(destination, source, dx, dy) + gpu.bindTexture(source) + width, height = gpu.getSize() + for x = 0, width, 2 do + for y = 0, height, 2 do + gpu.bindTexture(source) + r, g, b = gpu.getPixels(x, y) + gpu.bindTexture(destination) + gpu.setColor(r, g, b) + gpu.plot(dx + (x / 2), dy + (y / 2)) + end + end +end + +function draw_tile(address, px, py) + local x = 0 + local y = 0 + for y = 0, 7 do + local low_bits = memory[address] + address = address + 1 + local high_bits = memory[address] + address = address + 1 + for x = 0, 7 do + local color_index = 0 + if bit32.btest(low_bits, bit32.lshift(0x1, 7 - x)) then + color_index = color_index + 1 + end + if bit32.btest(high_bits, bit32.lshift(0x1, 7 - x)) then + color_index = color_index + 2 + end + gpu.setColor(unpack(colors[color_index])) + gpu.plot(px + x, py + y) + end + end +end + +function draw_tiles() + gpu.bindTexture(0) + gpu.startFrame() + gpu.setColor(192,255,192) + gpu.fill() + local i = 0 + local x = 0 + local y = 0 + while i < 384 do + draw_tile(0x8000 + (i * 16), x, y) + x = x + 8 + if x >= 256 then + x = 0 + y = y + 8 + end + i = i + 1 + end + gpu.endFrame() +end + +function draw_background() + gpu.startFrame() + gpu.bindTexture(background_texture) + gpu.setColor(255,192,192) + gpu.fill() + + for x = 0, 32 do + for y = 0, 32 do + -- figure out the tile index + address = LCD_Control.BackgroundTilemap() + (y * 32) + (x) + index = memory[address] + if LCD_Control.TileData() == 0x8800 then + if index > 127 then + index = index - 256 + end + end + --write(index .. ",") + draw_tile(LCD_Control.TileData() + (index * 16), x * 8, y * 8) + end + end + gpu.bindTexture(0) + draw_half_scale(0, background_texture, 0, 0) + gpu.endFrame() +end + +function draw_window() + gpu.startFrame() + gpu.bindTexture(background_texture) + gpu.setColor(255,192,192) + gpu.fill() + + for x = 0, 32 do + for y = 0, 32 do + -- figure out the tile index + address = LCD_Control.WindowTilemap() + (y * 32) + (x) + index = memory[address] + if LCD_Control.TileData() == 0x8800 then + if index > 127 then + index = index - 256 + end + end + --write(index .. ",") + draw_tile(LCD_Control.TileData() + (index * 16), x * 8, y * 8) + end + end + gpu.bindTexture(0) + --gpu.drawTexture(background_texture, 0, 0) + draw_half_scale(0, background_texture, 128, 0) + gpu.endFrame() +end + +function draw_sprites() + gpu.startFrame() + gpu.bindTexture(0) + gpu.setColor(255,255,192) + gpu.fill() + local count = 0 + for i = 0, 39 do + local oam_entry = 0xFE00 + (i * 4) + local y = memory[oam_entry] - 16 + local x = memory[oam_entry + 1] - 8 + if y > -16 and y < 144 and x > -8 and x < 160 then + -- sprite is onscreen! + local sprite_tile = memory[oam_entry + 2] + draw_tile(0x8000 + (sprite_tile * 16), x, y) + count = count + 1 + end + end + print("Displayed " .. count .. " sprites") + gpu.endFrame() +end diff --git a/input.lua b/input.lua new file mode 100644 index 0000000..e54785e --- /dev/null +++ b/input.lua @@ -0,0 +1,6 @@ + +function update_input() + -- dummy: make sure all the keys are NOT pressed. (Later: handle real input + -- and update these bits accordingly) + memory[0xFF00] = bit32.bor(memory[0xFF00], 0x0F) +end diff --git a/rom_header.lua b/rom_header.lua new file mode 100644 index 0000000..7a55dc9 --- /dev/null +++ b/rom_header.lua @@ -0,0 +1,93 @@ +-- given an entire rom (as a string reference), +-- print out the various header data for debugging + +function read_file_into_byte_array(file) + byte_array = {} + byte = file:read() + i = 0 + while byte do + byte_array[i] = byte + byte = file:read() + i = i + 1 + end + return byte_array +end + +function extract_string(data, s, e) + str = "" + for i = s, e do + if data[i] ~= 0 then + str = str .. string.char(data[i]) + end + end + return str +end + +cartridge_types = {} +cartridge_types[0x00] = "ROM ONLY" +cartridge_types[0x01] = "MBC1" +cartridge_types[0x02] = "MBC1+RAM" +cartridge_types[0x03] = "MBC1+RAM+BATTERY" +cartridge_types[0x05] = "MBC2" +cartridge_types[0x06] = "MBC2+BATTERY" + +function print_cartridge_header(data) + --convert the title data into a lua string + title = extract_string(data, 0x134, 0x143) + manufacturer = extract_string(data, 0x13F, 0x142) + print("Title: ", title) + print("Manufacturer: ", manufacturer) + + cgb = bit32.band(data[0x143], 0x8) ~= 0 + if cgb then + print("Color: YES") + else + print("Color: NO") + end + + licencee = extract_string(data, 0x144, 0x145) + print("Licencee: ", licencee) + + sgb = data[0x146] == 0x3 + if sgb then + print("SuperGB: YES") + else + print("SuperGB: NO") + end + + cart_type = cartridge_types[data[0x147]] + if cart_type then + print("Type: " .. cart_type) + else + print("Type: UNKNOWN!!") + end + + rom_size = data[0x148] + if rom_size < 0x8 then + print("Rom Size: " .. bit32.lshift(32, rom_size)) + end + + ram_size = data[0x149] + if ram_size == 0 then + print("Ram Size: 0k") + end + if ram_size == 1 then + print("Ram Size: 2k") + end + if ram_size == 2 then + print("Ram Size: 8k") + end + if ram_size == 3 then + print("Ram Size: 32k (banked)") + end + + japanese = data[0x14A] + if japanese then + print("Japan Only: Iie") + else + print("Japan Only: Hai") + end + + licencee_code = data[0x14B] + print("Licencee Code: ", licencee_code) +end diff --git a/tetris.log b/tetris.log new file mode 100644 index 0000000..ec61ee6 --- /dev/null +++ b/tetris.log @@ -0,0 +1,21 @@ +Tetris Log +========== + +[before this] - zero out all of memory +448752 - start initializing high RAM from cart at 0x2AC7 +494272 - Finish writing 0x2F to all of BG 0x9800 (??? Why?) +494636 - Return from a routine doing fancy things to sound registers +494736 - We've just re-enabled the display (At this point, the hardware should re-start at scanline 0?) + +496328 - After comparing FFE3 with a lot of potential values (VBlank Interrupt) +496412 - Tetris starts a DMA transfer to OAM (not implemented!!) + +497320 - Return from first VBlank routine (which didn't accomplish much) +497360 - Starts initializing Timer register values +497452 - Starts reading (very, very wrong!) joypad input + +497852 - CB 87 +560188 - Return from VBlank during disabled display (oops? Not sure how this is supposed to work tbh) + +560356 - Roughly, this is a copy routine from cart RAM into VRAM, performing decompression from 1bpp to 2bpp +770696 - Tetris returns from its tile loading routines. (Check out those tiles!) diff --git a/z80.lua b/z80.lua new file mode 100644 index 0000000..7d3484a --- /dev/null +++ b/z80.lua @@ -0,0 +1,1511 @@ +-- Initialize registers to what the GB's +-- iternal state would be after executing +-- BIOS code +reg = {} +reg.a = 0x01 +reg.b = 0x00 +reg.c = 0x13 +reg.d = 0x00 +reg.e = 0xD8 +reg.flags = {z=1,n=0,h=1,c=1} +reg.h = 0x01 +reg.l = 0x4D +reg.pc = 0x100 --entrypoint for GB games +reg.sp = 0xFFFE + +memory = {} + +function read_byte(address) + -- todo: make this respect memory regions + -- and VRAM / OAM access limits. + -- Also, cart bank switching would be cool. + return memory[bit32.band(address, 0xFFFF)] +end + +function write_byte(address, byte) + if address <= 0x7FFF then + -- ROM area; we should not actually write data here, but should + -- handle some special case addresses + -- TODO: Handle bank switching logic here. + elseif address >= 0x8000 and address <= 0x9FFF and Status.Mode() == 3 then + -- silently discard this write; the GPU has exclusive access to VRAM + -- during this time + + --debug: or not + memory[bit32.band(address, 0xFFFF)] = bit32.band(byte, 0xFF) + elseif address >= 0xFE00 and address <= 0xFE9F and Status.Mode() >= 2 and Status.Mode() <= 3 then + -- silently discard this write; the GPU has exclusive access to OAM memory + + --debug: or not + memory[bit32.band(address, 0xFFFF)] = bit32.band(byte, 0xFF) + else + -- default case: simple write please. + memory[bit32.band(address, 0xFFFF)] = bit32.band(byte, 0xFF) + end +end + +function initialize_memory() + for i = 0, 0xFFFF do + memory[i] = 0 + end +end + +interrupts_enabled = 1 +halted = 0 + +clock = 0 + +reg.f = function() + value = bit32.lshift(reg.flags.z, 7) + + bit32.lshift(reg.flags.n, 6) + + bit32.lshift(reg.flags.h, 5) + + bit32.lshift(reg.flags.c, 4) + return value +end + +reg.set_f = function(value) + if bit32.band(value, 0x80) ~= 0 then + reg.flags.z = 1 + else + reg.flags.z = 0 + end + + if bit32.band(value, 0x40) ~= 0 then + reg.flags.n = 1 + else + reg.flags.n = 0 + end + + if bit32.band(value, 0x20) ~= 0 then + reg.flags.h = 1 + else + reg.flags.h = 0 + end + + if bit32.band(value, 0x10) ~= 0 then + reg.flags.c = 1 + else + reg.flags.c = 0 + end +end + +reg.af = function() + return bit32.lshift(reg.a, 8) + reg.f() +end + +reg.bc = function() + return bit32.lshift(reg.b, 8) + reg.c +end + +reg.de = function() + return bit32.lshift(reg.d, 8) + reg.e +end + +reg.hl = function() + return bit32.lshift(reg.h, 8) + reg.l +end + +reg.set_bc = function(value) + reg.b = bit32.rshift(bit32.band(value, 0xFF00), 8) + reg.c = bit32.band(value, 0xFF) +end + +reg.set_de = function(value) + reg.d = bit32.rshift(bit32.band(value, 0xFF00), 8) + reg.e = bit32.band(value, 0xFF) +end + +reg.set_hl = function(value) + reg.h = bit32.rshift(bit32.band(value, 0xFF00), 8) + reg.l = bit32.band(value, 0xFF) +end + +opcodes = {} + +function read_at_hl() + clock = clock + 4 + return read_byte(reg.hl()) +end + +function set_at_hl(value) + clock = clock + 4 + write_byte(reg.hl(), value) +end + +-- ====== GMB 8-bit load commands ====== + +-- ld r, r +opcodes[0x40] = function() reg.b = reg.b end +opcodes[0x41] = function() reg.b = reg.c end +opcodes[0x42] = function() reg.b = reg.d end +opcodes[0x43] = function() reg.b = reg.e end +opcodes[0x44] = function() reg.b = reg.h end +opcodes[0x45] = function() reg.b = reg.l end +opcodes[0x46] = function() reg.b = read_at_hl() end +opcodes[0x47] = function() reg.b = reg.a end + +opcodes[0x48] = function() reg.c = reg.b end +opcodes[0x49] = function() reg.c = reg.c end +opcodes[0x4A] = function() reg.c = reg.d end +opcodes[0x4B] = function() reg.c = reg.e end +opcodes[0x4C] = function() reg.c = reg.h end +opcodes[0x4D] = function() reg.c = reg.l end +opcodes[0x4E] = function() reg.c = read_at_hl() end +opcodes[0x4F] = function() reg.c = reg.a end + +opcodes[0x50] = function() reg.d = reg.b end +opcodes[0x51] = function() reg.d = reg.c end +opcodes[0x52] = function() reg.d = reg.d end +opcodes[0x53] = function() reg.d = reg.e end +opcodes[0x54] = function() reg.d = reg.h end +opcodes[0x55] = function() reg.d = reg.l end +opcodes[0x56] = function() reg.d = read_at_hl() end +opcodes[0x57] = function() reg.d = reg.a end + +opcodes[0x58] = function() reg.e = reg.b end +opcodes[0x59] = function() reg.e = reg.c end +opcodes[0x5A] = function() reg.e = reg.d end +opcodes[0x5B] = function() reg.e = reg.e end +opcodes[0x5C] = function() reg.e = reg.h end +opcodes[0x5D] = function() reg.e = reg.l end +opcodes[0x5E] = function() reg.e = read_at_hl() end +opcodes[0x5F] = function() reg.e = reg.a end + +opcodes[0x60] = function() reg.h = reg.b end +opcodes[0x61] = function() reg.h = reg.c end +opcodes[0x62] = function() reg.h = reg.d end +opcodes[0x63] = function() reg.h = reg.e end +opcodes[0x64] = function() reg.h = reg.h end +opcodes[0x65] = function() reg.h = reg.l end +opcodes[0x66] = function() reg.h = read_at_hl() end +opcodes[0x67] = function() reg.h = reg.a end + +opcodes[0x68] = function() reg.l = reg.b end +opcodes[0x69] = function() reg.l = reg.c end +opcodes[0x6A] = function() reg.l = reg.d end +opcodes[0x6B] = function() reg.l = reg.e end +opcodes[0x6C] = function() reg.l = reg.h end +opcodes[0x6D] = function() reg.l = reg.l end +opcodes[0x6E] = function() reg.l = read_at_hl() end +opcodes[0x6F] = function() reg.l = reg.a end + +opcodes[0x70] = function() set_at_hl(reg.b) end +opcodes[0x71] = function() set_at_hl(reg.c) end +opcodes[0x72] = function() set_at_hl(reg.d) end +opcodes[0x73] = function() set_at_hl(reg.e) end +opcodes[0x74] = function() set_at_hl(reg.h) end +opcodes[0x75] = function() set_at_hl(reg.l) end +-- 0x76 is HALT, we implement that later +opcodes[0x77] = function() set_at_hl(reg.a) end + +opcodes[0x78] = function() reg.a = reg.b end +opcodes[0x79] = function() reg.a = reg.c end +opcodes[0x7A] = function() reg.a = reg.d end +opcodes[0x7B] = function() reg.a = reg.e end +opcodes[0x7C] = function() reg.a = reg.h end +opcodes[0x7D] = function() reg.a = reg.l end +opcodes[0x7E] = function() reg.a = read_at_hl() end +opcodes[0x7F] = function() reg.a = reg.a end + +function read_nn() + nn = read_byte(reg.pc) + reg.pc = reg.pc + 1 + clock = clock + 4 + return nn +end + +-- ld r, n +opcodes[0x06] = function() reg.b = read_nn() end +opcodes[0x0E] = function() reg.c = read_nn() end +opcodes[0x16] = function() reg.d = read_nn() end +opcodes[0x1E] = function() reg.e = read_nn() end +opcodes[0x26] = function() reg.h = read_nn() end +opcodes[0x2E] = function() reg.l = read_nn() end +opcodes[0x36] = function() set_at_hl(read_nn()) end +opcodes[0x3E] = function() reg.a = read_nn() end + +-- ld A, (xx) +opcodes[0x0A] = function() + reg.a = read_byte(reg.bc()) + clock = clock + 4 +end + +opcodes[0x1A] = function() + reg.a = read_byte(reg.de()) + clock = clock + 4 +end + +opcodes[0xFA] = function() + lower = read_nn() + upper = bit32.lshift(read_nn(), 8) + reg.a = read_byte(upper + lower) + clock = clock + 4 +end + +-- ld (xx), A +opcodes[0x02] = function() + write_byte(reg.bc(), reg.a) + clock = clock + 4 +end + +opcodes[0x12] = function() + write_byte(reg.de(), reg.a) + clock = clock + 4 +end + +opcodes[0xEA] = function() + lower = read_nn() + upper = bit32.lshift(read_nn(), 8) + write_byte(upper + lower, reg.a) + clock = clock + 4 +end + +-- ld a, (FF00 + nn) +opcodes[0xF0] = function() + reg.a = read_byte(0xFF00 + read_nn()) + clock = clock + 4 +end + +-- ld (FF00 + nn), a +opcodes[0xE0] = function() + write_byte(0xFF00 + read_nn(), reg.a) + clock = clock + 4 +end + +-- ld a, (FF00 + C) +opcodes[0xF2] = function() + reg.a = read_byte(0xFF00 + reg.c) + clock = clock + 4 +end + +-- ld (FF00 + C), a +opcodes[0xE2] = function() + write_byte(0xFF00 + reg.c, reg.a) + clock = clock + 4 +end + +-- ldi (HL), a +opcodes[0x22] = function() + set_at_hl(reg.a) + reg.set_hl(reg.hl() + 1) +end + +-- ldi a, (HL) +opcodes[0x2A] = function() + reg.a = read_at_hl() + reg.set_hl(reg.hl() + 1) +end + +-- ldd (HL), a +opcodes[0x32] = function() + set_at_hl(reg.a) + reg.set_hl(reg.hl() - 1) +end + +-- ldd a, (HL) +opcodes[0x3A] = function() + reg.a = read_at_hl() + reg.set_hl(reg.hl() - 1) +end + +-- ====== GMB 16-bit load commands ====== +-- ld BC, nnnn +opcodes[0x01] = function() + reg.c = read_nn() + reg.b = read_nn() +end + +-- ld DE, nnnn +opcodes[0x11] = function() + reg.e = read_nn() + reg.d = read_nn() +end + +-- ld HL, nnnn +opcodes[0x21] = function() + reg.l = read_nn() + reg.h = read_nn() +end + +-- ld SP, nnnn +opcodes[0x31] = function() + lower = read_nn() + upper = bit32.lshift(read_nn(), 8) + reg.sp = upper + lower +end + +-- ld SP, HL +opcodes[0xF9] = function() + reg.sp = reg.hl() + clock = clock + 4 +end + +-- push BC +opcodes[0xC5] = function() + reg.sp = reg.sp - 1 + write_byte(reg.sp, reg.b) + reg.sp = reg.sp - 1 + write_byte(reg.sp, reg.c) + clock = clock + 12 +end + +-- push DE +opcodes[0xD5] = function() + reg.sp = reg.sp - 1 + write_byte(reg.sp, reg.d) + reg.sp = reg.sp - 1 + write_byte(reg.sp, reg.e) + clock = clock + 12 +end + +-- push HL +opcodes[0xE5] = function() + reg.sp = reg.sp - 1 + write_byte(reg.sp, reg.h) + reg.sp = reg.sp - 1 + write_byte(reg.sp, reg.l) + clock = clock + 12 +end + +-- push AF +opcodes[0xF5] = function() + reg.sp = reg.sp - 1 + write_byte(reg.sp, reg.a) + reg.sp = reg.sp - 1 + write_byte(reg.sp, reg.f()) + clock = clock + 12 +end + +-- pop BC +opcodes[0xC1] = function() + reg.c = read_byte(reg.sp) + reg.sp = reg.sp + 1 + reg.b = read_byte(reg.sp) + reg.sp = reg.sp + 1 + clock = clock + 8 +end + +-- pop DE +opcodes[0xD1] = function() + reg.e = read_byte(reg.sp) + reg.sp = reg.sp + 1 + reg.d = read_byte(reg.sp) + reg.sp = reg.sp + 1 + clock = clock + 8 +end + +-- pop HL +opcodes[0xE1] = function() + reg.l = read_byte(reg.sp) + reg.sp = reg.sp + 1 + reg.h = read_byte(reg.sp) + reg.sp = reg.sp + 1 + clock = clock + 8 +end + +-- pop AF +opcodes[0xF1] = function() + reg.set_f(read_byte(reg.sp)) + reg.sp = reg.sp + 1 + reg.a = read_byte(reg.sp) + reg.sp = reg.sp + 1 + clock = clock + 8 +end + +-- ====== GMB 8bit-Arithmetic/logical Commands ====== +add_to_a = function(value) + -- half-carry + if bit32.band(reg.a, 0xF) + bit32.band(value, 0xF) > 0xF then + reg.flags.h = 1 + else + reg.flags.h = 0 + end + + reg.a = reg.a + value + + -- carry (and overflow correction) + if reg.a > 0xFF or reg.a < 0x00 then + reg.a = bit32.band(reg.a, 0xFF) + reg.flags.c = 1 + else + reg.flags.c = 0 + end + + if reg.a == 0 then + reg.flags.z = 1 + else + reg.flags.z = 0 + end + + reg.flags.n = 0 +end + +-- add A, r +opcodes[0x80] = function() add_to_a(reg.b) end +opcodes[0x81] = function() add_to_a(reg.c) end +opcodes[0x82] = function() add_to_a(reg.d) end +opcodes[0x83] = function() add_to_a(reg.e) end +opcodes[0x84] = function() add_to_a(reg.h) end +opcodes[0x85] = function() add_to_a(reg.l) end +opcodes[0x86] = function() add_to_a(read_at_hl()) end +opcodes[0x87] = function() add_to_a(reg.a) end + +-- add A, nn +opcodes[0xC6] = function() add_to_a(read_nn()) end + +-- adc A, r +opcodes[0x88] = function() add_to_a(reg.b + reg.flags.c) end +opcodes[0x89] = function() add_to_a(reg.c + reg.flags.c) end +opcodes[0x8A] = function() add_to_a(reg.d + reg.flags.c) end +opcodes[0x8B] = function() add_to_a(reg.e + reg.flags.c) end +opcodes[0x8C] = function() add_to_a(reg.h + reg.flags.c) end +opcodes[0x8D] = function() add_to_a(reg.l + reg.flags.c) end +opcodes[0x8E] = function() add_to_a(read_at_hl() + reg.flags.c) end +opcodes[0x8F] = function() add_to_a(reg.a + reg.flags.c) end + +-- adc A, nn +opcodes[0xCE] = function() add_to_a(read_nn() + reg.flags.c) end + +sub_from_a = function(value) + -- half-carry + if bit32.band(reg.a, 0xF) - bit32.band(value, 0xF) < 0 then + reg.flags.h = 1 + else + reg.flags.h = 0 + end + + reg.a = reg.a - value + + -- carry (and overflow correction) + if reg.a < 0 or reg.a > 0xFF then + reg.a = bit32.band(reg.a, 0xFF) + reg.flags.c = 1 + else + reg.flags.c = 0 + end + + if reg.a == 0 then + reg.flags.z = 1 + else + reg.flags.z = 0 + end + + reg.flags.n = 1 +end + +-- sub A, r +opcodes[0x90] = function() sub_from_a(reg.b) end +opcodes[0x91] = function() sub_from_a(reg.c) end +opcodes[0x92] = function() sub_from_a(reg.d) end +opcodes[0x93] = function() sub_from_a(reg.e) end +opcodes[0x94] = function() sub_from_a(reg.h) end +opcodes[0x95] = function() sub_from_a(reg.l) end +opcodes[0x96] = function() sub_from_a(read_at_hl()) end +opcodes[0x97] = function() sub_from_a(reg.a) end + +-- sub A, nn +opcodes[0xD6] = function() sub_from_a(read_nn()) end + +-- sbc A, r +opcodes[0x98] = function() sub_from_a(reg.b + reg.flags.c) end +opcodes[0x99] = function() sub_from_a(reg.c + reg.flags.c) end +opcodes[0x9A] = function() sub_from_a(reg.d + reg.flags.c) end +opcodes[0x9B] = function() sub_from_a(reg.e + reg.flags.c) end +opcodes[0x9C] = function() sub_from_a(reg.h + reg.flags.c) end +opcodes[0x9D] = function() sub_from_a(reg.l + reg.flags.c) end +opcodes[0x9E] = function() sub_from_a(read_at_hl() + reg.flags.c) end +opcodes[0x9F] = function() sub_from_a(reg.a + reg.flags.c) end + +-- sbc A, nn +opcodes[0xDE] = function() sub_from_a(read_nn() + reg.flags.c) end + +and_a_with = function(value) + reg.a = bit32.band(reg.a, value) + if reg.a == 0 then + reg.flags.z = 1 + else + reg.flags.z = 0 + end + reg.flags.n = 0 + reg.flags.h = 1 + reg.flags.c = 0 +end + +-- and A, r +opcodes[0xA0] = function() and_a_with(reg.b) end +opcodes[0xA1] = function() and_a_with(reg.c) end +opcodes[0xA2] = function() and_a_with(reg.d) end +opcodes[0xA3] = function() and_a_with(reg.e) end +opcodes[0xA4] = function() and_a_with(reg.h) end +opcodes[0xA5] = function() and_a_with(reg.l) end +opcodes[0xA6] = function() and_a_with(read_at_hl()) end +opcodes[0xA7] = function() and_a_with(reg.a) end + +-- and A, nn +opcodes[0xE6] = function() and_a_with(read_nn()) end + +xor_a_with = function(value) + reg.a = bit32.bxor(reg.a, value) + if reg.a == 0 then + reg.flags.z = 1 + else + reg.flags.z = 0 + end + reg.flags.n = 0 + reg.flags.h = 0 + reg.flags.c = 0 +end + +-- xor A, r +opcodes[0xA8] = function() xor_a_with(reg.b) end +opcodes[0xA9] = function() xor_a_with(reg.c) end +opcodes[0xAA] = function() xor_a_with(reg.d) end +opcodes[0xAB] = function() xor_a_with(reg.e) end +opcodes[0xAC] = function() xor_a_with(reg.h) end +opcodes[0xAD] = function() xor_a_with(reg.l) end +opcodes[0xAE] = function() xor_a_with(read_at_hl()) end +opcodes[0xAF] = function() xor_a_with(reg.a) end + +-- xor A, nn +opcodes[0xEE] = function() xor_a_with(read_nn()) end + +or_a_with = function(value) + reg.a = bit32.bor(reg.a, value) + if reg.a == 0 then + reg.flags.z = 1 + else + reg.flags.z = 0 + end + reg.flags.n = 0 + reg.flags.h = 0 + reg.flags.c = 0 +end + +-- or A, r +opcodes[0xB0] = function() or_a_with(reg.b) end +opcodes[0xB1] = function() or_a_with(reg.c) end +opcodes[0xB2] = function() or_a_with(reg.d) end +opcodes[0xB3] = function() or_a_with(reg.e) end +opcodes[0xB4] = function() or_a_with(reg.h) end +opcodes[0xB5] = function() or_a_with(reg.l) end +opcodes[0xB6] = function() or_a_with(read_at_hl()) end +opcodes[0xB7] = function() or_a_with(reg.a) end + +-- or A, nn +opcodes[0xF6] = function() or_a_with(read_nn()) end + +cp_with_a = function(value) + -- half-carry + if bit32.band(reg.a, 0xF) - bit32.band(value, 0xF) < 0 then + reg.flags.h = 1 + else + reg.flags.h = 0 + end + + temp = reg.a - value + + -- carry (and overflow correction) + if temp < 0 or temp > 0xFF then + temp = bit32.band(temp, 0xFF) + reg.flags.c = 1 + else + reg.flags.c = 0 + end + + if temp == 0 then + reg.flags.z = 1 + else + reg.flags.z = 0 + end + + reg.flags.n = 1 +end + +-- cp A, r +opcodes[0xB8] = function() cp_with_a(reg.b) end +opcodes[0xB9] = function() cp_with_a(reg.c) end +opcodes[0xBA] = function() cp_with_a(reg.d) end +opcodes[0xBB] = function() cp_with_a(reg.e) end +opcodes[0xBC] = function() cp_with_a(reg.h) end +opcodes[0xBD] = function() cp_with_a(reg.l) end +opcodes[0xBE] = function() cp_with_a(read_at_hl()) end +opcodes[0xBF] = function() cp_with_a(reg.a) end + +-- cp A, nn +opcodes[0xFE] = function() cp_with_a(read_nn()) end + +set_inc_flags = function(value) + -- zero flag + if value == 0 then + reg.flags.z = 1 + else + reg.flags.z = 0 + end + + -- half-carry + if bit32.band(value, 0xF) == 0x0 then + reg.flags.h = 1 + else + reg.flags.h = 0 + end + + reg.flags.n = 1 +end + +set_dec_flags = function(value) + -- zero flag + if value == 0 then + reg.flags.z = 1 + else + reg.flags.z = 0 + end + + -- half-carry + if bit32.band(value, 0xF) == 0xF then + reg.flags.h = 1 + else + reg.flags.h = 0 + end + + reg.flags.n = 0 +end + +-- inc r +opcodes[0x04] = function() reg.b = bit32.band(reg.b + 1, 0xFF); set_inc_flags(reg.b) end +opcodes[0x0C] = function() reg.c = bit32.band(reg.c + 1, 0xFF); set_inc_flags(reg.c) end +opcodes[0x14] = function() reg.d = bit32.band(reg.d + 1, 0xFF); set_inc_flags(reg.d) end +opcodes[0x1C] = function() reg.e = bit32.band(reg.e + 1, 0xFF); set_inc_flags(reg.e) end +opcodes[0x24] = function() reg.h = bit32.band(reg.h + 1, 0xFF); set_inc_flags(reg.h) end +opcodes[0x2C] = function() reg.l = bit32.band(reg.l + 1, 0xFF); set_inc_flags(reg.l) end +opcodes[0x34] = function() + write_byte(reg.hl(), bit32.band(read_byte(reg.hl()) + 1, 0xFF)) + set_inc_flags(read_byte(reg.hl())) + clock = clock + 8 +end +opcodes[0x3C] = function() reg.a = bit32.band(reg.a + 1, 0xFF); set_inc_flags(reg.a) end + +-- dec r +opcodes[0x05] = function() reg.b = bit32.band(reg.b - 1, 0xFF); set_dec_flags(reg.b) end +opcodes[0x0D] = function() reg.c = bit32.band(reg.c - 1, 0xFF); set_dec_flags(reg.c) end +opcodes[0x15] = function() reg.d = bit32.band(reg.d - 1, 0xFF); set_dec_flags(reg.d) end +opcodes[0x1D] = function() reg.e = bit32.band(reg.e - 1, 0xFF); set_dec_flags(reg.e) end +opcodes[0x25] = function() reg.h = bit32.band(reg.h - 1, 0xFF); set_dec_flags(reg.h) end +opcodes[0x2D] = function() reg.l = bit32.band(reg.l - 1, 0xFF); set_dec_flags(reg.l) end +opcodes[0x35] = function() + write_byte(reg.hl(), bit32.band(read_byte(reg.hl()) - 1, 0xFF)) + set_dec_flags(read_byte(reg.hl())) + clock = clock + 8 +end +opcodes[0x3D] = function() reg.a = bit32.band(reg.a - 1, 0xFF); set_dec_flags(reg.a) end + +-- daa +opcodes[0x27] = function() + if bit32.band(0x0F, reg.a) > 0x09 or reg.flags.h == 1 then + reg.a = reg.a + 0x06 + end + if bit.band(0xF0, reg.a) > 0x90 or reg.flags.c == 1 then + reg.a = reg.a + 0x60 + reg.flags.c = 1 + else + reg.flags.c = 0 + end + reg.flags.h = 0 +end + +-- cpl +opcodes[0x2F] = function() + reg.a = bit32.bxor(reg.a, 0xFF) + reg.flags.n = 1 + reg.flags.h = 1 +end + +add_to_hl = function(value) + -- half carry + if bit32.band(reg.hl(), 0xFFF) + bit32.band(value, 0xFFF) > 0xFFF then + reg.flags.h = 1 + else + reg.flags.h = 0 + end + + reg.set_hl(reg.hl() + value) + + -- carry + if reg.hl() > 0xFFFF or reg.hl() < 0x0000 then + reg.set_hl(bit32.band(reg.hl, 0xFFFF)) + reg.flags.c = 1 + else + reg.flags.c = 0 + end + reg.flags.n = 0 + clock = clock + 4 +end + +-- add HL, rr +opcodes[0x09] = function() add_to_hl(reg.bc()) end +opcodes[0x19] = function() add_to_hl(reg.de()) end +opcodes[0x29] = function() add_to_hl(reg.hl()) end +opcodes[0x39] = function() add_to_hl(reg.sp) end + +-- inc rr +opcodes[0x03] = function() + reg.set_bc(bit32.band(reg.bc() + 1, 0xFFFF)) + clock = clock + 4 +end +opcodes[0x13] = function() + reg.set_de(bit32.band(reg.de() + 1, 0xFFFF)) + clock = clock + 4 +end +opcodes[0x23] = function() + reg.set_hl(bit32.band(reg.hl() + 1, 0xFFFF)) + clock = clock + 4 +end +opcodes[0x33] = function() + reg.sp = bit32.band(reg.sp + 1, 0xFFFF) + clock = clock + 4 +end + +-- dec rr +opcodes[0x0B] = function() + reg.set_bc(bit32.band(reg.bc() - 1, 0xFFFF)) + clock = clock + 4 +end +opcodes[0x1B] = function() + reg.set_de(bit32.band(reg.de() - 1, 0xFFFF)) + clock = clock + 4 +end +opcodes[0x2B] = function() + reg.set_hl(bit32.band(reg.hl() - 1, 0xFFFF)) + clock = clock + 4 +end +opcodes[0x3B] = function() + reg.sp = bit32.band(reg.sp - 1, 0xFFFF) + clock = clock + 4 +end + +-- add SP, dd +opcodes[0xE8] = function() + offset = read_nn() + -- offset comes in as unsigned 0-255, so convert it to signed -128 - 127 + if offset > 127 then + offset = offset - 256 + end + + -- half carry + if offset >= 0 then + if bit32.band(reg.sp, 0xFFF) + offset > 0xFFF then + reg.flags.h = 1 + else + reg.flags.h = 0 + end + else + -- (note: weird! not sure if this is right) + if bit32.band(bit32.band(reg.sp, 0xFFF) - offset, 0xF00) == 0xF00 then + reg.flags.h = 1 + else + reg.flags.h = 0 + end + end + + reg.sp = reg.sp + offset + + -- carry + if reg.sp > 0xFFFF or reg.sp < 0x0000 then + reg.sp = bit32.band(reg.sp, 0xFFFF) + reg.flags.c = 1 + else + reg.flags.c = 0 + end + + reg.flags.z = 0 + reg.flags.n = 0 + + clock = clock + 12 +end + +-- ld HL, SP + dd +opcodes[0xF8] = function() + -- cheat + old_sp = reg.sp + opcodes[0xE8]() + reg.set_hl(reg.sp) + reg.sp = old_sp + --op E8 is 16 clocks, this is 4 clocks less + clock = clock - 4 +end + +-- ====== GMB Rotate and Shift Commands ====== +local reg_rlc = function(value) + value = bit32.lshift(value, 1) + -- move what would be bit 8 into the carry + if bit32.band(value, 0x100) ~= 0 then + value = bit32.band(value, 0xFF) + reg.flags.c = 1 + else + reg.flags.c = 0 + end + -- also copy the carry into bit 0 + value = value + reg.flags.c + if value == 0 then + reg.flags.z = 1 + else + reg.flags.z = 0 + end + reg.flags.h = 0 + reg.flags.n = 0 + return value +end + +local reg_rl = function(value) + value = bit32.lshift(value, 1) + -- move the carry into bit 0 + value = value + reg.flags.c + -- now move what would be bit 8 into the carry + if bit32.band(value, 0x100) ~= 0 then + value = bit32.band(value, 0xFF) + reg.flags.c = 1 + else + reg.flags.c = 0 + end + if value == 0 then + reg.flags.z = 1 + else + reg.flags.z = 0 + end + reg.flags.h = 0 + reg.flags.n = 0 + return value +end + +local reg_rrc = function(value) + -- move bit 0 into the carry + if bit32.band(value, 0x1) ~= 0 then + reg.flags.c = 1 + else + reg.flags.c = 0 + end + value = bit32.rshift(value, 1) + -- also copy the carry into bit 7 + value = value + bit32.lshift(reg.flags.c, 7) + if value == 0 then + reg.flags.z = 1 + else + reg.flags.z = 0 + end + reg.flags.h = 0 + reg.flags.n = 0 + return value +end + +local reg_rr = function(value) + -- first, copy the carry into bit 8 (!!) + value = value + bit32.lshift(reg.flags.c, 8) + -- move bit 0 into the carry + if bit32.band(value, 0x1) ~= 0 then + reg.flags.c = 1 + else + reg.flags.c = 0 + end + value = bit32.rshift(value, 1) + -- for safety, this should be a nop? + value = bit32.band(value, 0xFF) + if value == 0 then + reg.flags.z = 1 + else + reg.flags.z = 0 + end + reg.flags.h = 0 + reg.flags.n = 0 + return value +end + +-- rlc a +opcodes[0x07] = function() reg.a = reg_rlc(reg.a); reg.flags.z = 0 end + +-- rl a +opcodes[0x17] = function() reg.a = reg_rl(reg.a); reg.flags.z = 0 end + +-- rrc a +opcodes[0x0F] = function() reg.a = reg_rrc(reg.a); reg.flags.z = 0 end + +-- rr a +opcodes[0x1F] = function() reg.a = reg_rr(reg.a); reg.flags.z = 0 end + +-- ====== CB: Extended Rotate and Shift ====== + +cb = {} + +-- rlc r +cb[0x00] = function() reg.b = reg_rlc(reg.b); clock = clock + 4 end +cb[0x01] = function() reg.c = reg_rlc(reg.c); clock = clock + 4 end +cb[0x02] = function() reg.d = reg_rlc(reg.d); clock = clock + 4 end +cb[0x03] = function() reg.e = reg_rlc(reg.e); clock = clock + 4 end +cb[0x04] = function() reg.h = reg_rlc(reg.h); clock = clock + 4 end +cb[0x05] = function() reg.l = reg_rlc(reg.l); clock = clock + 4 end +cb[0x06] = function() write_byte(reg.hl(), reg_rlc(read_byte(reg.hl()))); clock = clock + 8 end +cb[0x07] = function() reg.a = reg_rlc(reg.a); clock = clock + 4 end + +-- rl r +cb[0x10] = function() reg.b = reg_rl(reg.b); clock = clock + 4 end +cb[0x11] = function() reg.c = reg_rl(reg.c); clock = clock + 4 end +cb[0x12] = function() reg.d = reg_rl(reg.d); clock = clock + 4 end +cb[0x13] = function() reg.e = reg_rl(reg.e); clock = clock + 4 end +cb[0x14] = function() reg.h = reg_rl(reg.h); clock = clock + 4 end +cb[0x15] = function() reg.l = reg_rl(reg.l); clock = clock + 4 end +cb[0x16] = function() write_byte(reg.hl(), reg_rl(read_byte(reg.hl()))); clock = clock + 8 end +cb[0x17] = function() reg.a = reg_rl(reg.a); clock = clock + 4 end + +-- rrc r +cb[0x08] = function() reg.b = reg_rrc(reg.b); clock = clock + 4 end +cb[0x09] = function() reg.c = reg_rrc(reg.c); clock = clock + 4 end +cb[0x0A] = function() reg.d = reg_rrc(reg.d); clock = clock + 4 end +cb[0x0B] = function() reg.e = reg_rrc(reg.e); clock = clock + 4 end +cb[0x0C] = function() reg.h = reg_rrc(reg.h); clock = clock + 4 end +cb[0x0D] = function() reg.l = reg_rrc(reg.l); clock = clock + 4 end +cb[0x0E] = function() write_byte(reg.hl(), reg_rrc(read_byte(reg.hl()))); clock = clock + 8 end +cb[0x0F] = function() reg.a = reg_rrc(reg.a); clock = clock + 4 end + +-- rl r +cb[0x18] = function() reg.b = reg_rr(reg.b); clock = clock + 4 end +cb[0x19] = function() reg.c = reg_rr(reg.c); clock = clock + 4 end +cb[0x1A] = function() reg.d = reg_rr(reg.d); clock = clock + 4 end +cb[0x1B] = function() reg.e = reg_rr(reg.e); clock = clock + 4 end +cb[0x1C] = function() reg.h = reg_rr(reg.h); clock = clock + 4 end +cb[0x1D] = function() reg.l = reg_rr(reg.l); clock = clock + 4 end +cb[0x1E] = function() write_byte(reg.hl(), reg_rr(read_byte(reg.hl()))); clock = clock + 8 end +cb[0x1F] = function() reg.a = reg_rr(reg.a); clock = clock + 4 end + +reg_sla = function(value) + -- copy bit 7 into carry + if bit32.band(value, 0x80) == 1 then + reg.flags.c = 1 + else + reg.flags.c = 0 + end + value = bit32.band(bit32.lshift(value, 1), 0xFF) + if value == 0 then + reg.flags.z = 1 + else + reg.flags.z = 0 + end + reg.flags.h = 0 + reg.flags.n = 0 + clock = clock + 4 + return value +end + +reg_srl = function(value) + -- copy bit 0 into carry + if bit32.band(value, 0x1) == 1 then + reg.flags.c = 1 + else + reg.flags.c = 0 + end + value = bit32.rshift(value, 1) + if value == 0 then + reg.flags.z = 1 + else + reg.flags.z = 0 + end + reg.flags.h = 0 + reg.flags.n = 0 + clock = clock + 4 + return value +end + +reg_sra = function(value) + local arith_value = reg_srl(value) + -- if bit 6 is set, copy it to bit 7 + if bit32.band(arith_value, 0x40) ~= 0 then + arith_value = arith_value + 0x80 + end + clock = clock + 4 + return arith_value +end + +reg_swap = function(value) + value = bit32.rshift(bit32.band(value, 0xF0), 4) + bit32.lshift(bit32.band(value, 0xF), 4) + if value == 0 then + reg.flags.z = 1 + else + reg.flags.z = 0 + end + reg.flags.n = 0 + reg.flags.h = 0 + reg.flags.c = 0 + clock = clock + 4 + return value +end + +-- sla r +cb[0x20] = function() reg.b = reg_sla(reg.b) end +cb[0x21] = function() reg.c = reg_sla(reg.c) end +cb[0x22] = function() reg.d = reg_sla(reg.d) end +cb[0x23] = function() reg.e = reg_sla(reg.e) end +cb[0x24] = function() reg.h = reg_sla(reg.h) end +cb[0x25] = function() reg.l = reg_sla(reg.l) end +cb[0x26] = function() write_byte(reg.hl(), reg_sla(read_byte(reg.hl()))); clock = clock + 12 end +cb[0x27] = function() reg.a = reg_sla(reg.a) end + +-- swap r (high and low nybbles) +cb[0x30] = function() reg.b = reg_swap(reg.b) end +cb[0x31] = function() reg.c = reg_swap(reg.c) end +cb[0x32] = function() reg.d = reg_swap(reg.d) end +cb[0x33] = function() reg.e = reg_swap(reg.e) end +cb[0x34] = function() reg.h = reg_swap(reg.h) end +cb[0x35] = function() reg.l = reg_swap(reg.l) end +cb[0x36] = function() write_byte(reg.hl(), reg_swap(read_byte(reg.hl()))); clock = clock + 12 end +cb[0x37] = function() reg.a = reg_swap(reg.a) end + +-- sra r +cb[0x28] = function() reg.b = reg_sra(reg.b) end +cb[0x29] = function() reg.c = reg_sra(reg.c) end +cb[0x2A] = function() reg.d = reg_sra(reg.d) end +cb[0x2B] = function() reg.e = reg_sra(reg.e) end +cb[0x2C] = function() reg.h = reg_sra(reg.h) end +cb[0x2D] = function() reg.l = reg_sra(reg.l) end +cb[0x2E] = function() write_byte(reg.hl(), reg_sra(read_byte(reg.hl()))); clock = clock + 12 end +cb[0x2F] = function() reg.a = reg_sra(reg.a) end + +-- srl r +cb[0x38] = function() reg.b = reg_srl(reg.b) end +cb[0x39] = function() reg.c = reg_srl(reg.c) end +cb[0x3A] = function() reg.d = reg_srl(reg.d) end +cb[0x3B] = function() reg.e = reg_srl(reg.e) end +cb[0x3C] = function() reg.h = reg_srl(reg.h) end +cb[0x3D] = function() reg.l = reg_srl(reg.l) end +cb[0x3E] = function() write_byte(reg.hl(), reg_srl(read_byte(reg.hl()))); clock = clock + 12 end +cb[0x3F] = function() reg.a = reg_srl(reg.a) end + +-- ====== GMB Singlebit Operation Commands ====== +reg_bit = function(value, bit) + if bit32.band(value, bit32.lshift(0x1, bit)) ~= 0 then + reg.flags.z = 0 + else + reg.flags.z = 1 + end + reg.flags.n = 0 + reg.flags.h = 1 + return +end + +opcodes[0xCB] = function() + cb_op = read_nn() + if cb[cb_op] ~= nil then + --revert the timing; this is handled automatically by the various functions + clock = clock - 4 + cb[cb_op]() + return + end + high_half_nybble = bit32.rshift(bit32.band(cb_op, 0xC0), 6) + reg_index = bit32.band(cb_op, 0x7) + bit = bit32.rshift(bit32.band(cb_op, 0x38), 3) + if high_half_nybble == 0x1 then + -- bit n,r + if reg_index == 0 then reg_bit(reg.b, bit) end + if reg_index == 1 then reg_bit(reg.c, bit) end + if reg_index == 2 then reg_bit(reg.d, bit) end + if reg_index == 3 then reg_bit(reg.e, bit) end + if reg_index == 4 then reg_bit(reg.h, bit) end + if reg_index == 5 then reg_bit(reg.l, bit) end + if reg_index == 6 then reg_bit(read_byte(reg.hl()), bit); clock = clock + 4 end + if reg_index == 7 then reg_bit(reg.a, bit) end + clock = clock + 4 + end + if high_half_nybble == 0x2 then + -- res n, r + -- note: this is REALLY stupid, but it works around some floating point + -- limitations in Lua. + if reg_index == 0 then reg.b = bit32.band(reg.b, bit32.bxor(reg.b, bit32.lshift(0x1, bit))) end + if reg_index == 1 then reg.c = bit32.band(reg.c, bit32.bxor(reg.c, bit32.lshift(0x1, bit))) end + if reg_index == 2 then reg.d = bit32.band(reg.d, bit32.bxor(reg.d, bit32.lshift(0x1, bit))) end + if reg_index == 3 then reg.e = bit32.band(reg.e, bit32.bxor(reg.e, bit32.lshift(0x1, bit))) end + if reg_index == 4 then reg.h = bit32.band(reg.h, bit32.bxor(reg.h, bit32.lshift(0x1, bit))) end + if reg_index == 5 then reg.l = bit32.band(reg.l, bit32.bxor(reg.l, bit32.lshift(0x1, bit))) end + if reg_index == 6 then write_byte(reg.hl(), bit32.band(read_byte(reg.hl()), bit32.bxor(read_byte(reg.hl()), bit32.lshift(0x1, bit)))); clock = clock + 8 end + if reg_index == 7 then reg.a = bit32.band(reg.a, bit32.bxor(reg.a, bit32.lshift(0x1, bit))) end + clock = clock + 4 + end + + if high_half_nybble == 0x3 then + -- set n, r + if reg_index == 0 then reg.b = bit32.bor(bit32.lshift(0x1, bit), reg.b) end + if reg_index == 1 then reg.c = bit32.bor(bit32.lshift(0x1, bit), reg.c) end + if reg_index == 2 then reg.d = bit32.bor(bit32.lshift(0x1, bit), reg.d) end + if reg_index == 3 then reg.e = bit32.bor(bit32.lshift(0x1, bit), reg.e) end + if reg_index == 4 then reg.h = bit32.bor(bit32.lshift(0x1, bit), reg.h) end + if reg_index == 5 then reg.l = bit32.bor(bit32.lshift(0x1, bit), reg.l) end + if reg_index == 6 then write_byte(reg.hl(), bit32.bor(bit32.lshift(0x1, bit), read_byte(reg.hl()))); clock = clock + 8 end + if reg_index == 7 then reg.a = bit32.bor(bit32.lshift(0x1, bit), reg.a) end + clock = clock + 4 + end +end + +-- ====== GMB CPU-Controlcommands ====== +-- ccf +opcodes[0x3F] = function() + reg.flags.c = bit32.bnot(reg.flags.c) + reg.flags.n = 0 + reg.flags.h = 0 +end + +-- scf +opcodes[0x37] = function() + reg.flags.c = 1 + reg.flags.n = 0 + reg.flags.h = 0 +end + +-- nop +opcodes[0x00] = function() end + +-- halt +opcodes[0x76] = function() halted = 1 end + +-- stop +opcodes[0x10] = function() + value = read_nn() + if value == 0x00 then + print("Unimplemented STOP instruction, treating like HALT") + halted = 1 + else + print("Unimplemented WEIRDNESS after 0x10") + end +end + +-- di +opcodes[0xF3] = function() interrupts_enabled = 0 end +-- ei +opcodes[0xFB] = function() interrupts_enabled = 1 end + +-- ====== GMB Jumpcommands ====== +jump_to_nnnn = function() + lower = read_nn() + upper = bit32.lshift(read_nn(), 8) + reg.pc = upper + lower +end + +-- jp nnnn +opcodes[0xC3] = function() + jump_to_nnnn() + clock = clock + 4 +end + +-- jp HL +opcodes[0xE9] = function() + reg.pc = reg.hl() +end + +-- jp nz, nnnn +opcodes[0xC2] = function() + if reg.flags.z == 0 then + jump_to_nnnn() + clock = clock + 4 + else + reg.pc = reg.pc + 2 + clock = clock + 8 + end +end + +-- jp nc, nnnn +opcodes[0xD2] = function() + if reg.flags.c == 0 then + jump_to_nnnn() + clock = clock + 4 + else + reg.pc = reg.pc + 2 + clock = clock + 8 + end +end + +-- jp z, nnnn +opcodes[0xCA] = function() + if reg.flags.z == 1 then + jump_to_nnnn() + clock = clock + 4 + else + reg.pc = reg.pc + 2 + clock = clock + 8 + end +end + +-- jp c, nnnn +opcodes[0xCA] = function() + if reg.flags.c == 1 then + jump_to_nnnn() + clock = clock + 4 + else + reg.pc = reg.pc + 2 + clock = clock + 8 + end +end + +function jump_relative_to_nn() + offset = read_nn() + if offset > 127 then + offset = offset - 256 + end + reg.pc = bit32.band(reg.pc + offset, 0xFFFF) +end + +-- jr nn +opcodes[0x18] = function() + jump_relative_to_nn() + clock = clock + 4 +end + +-- jr nz, nn +opcodes[0x20] = function() + if reg.flags.z == 0 then + jump_relative_to_nn() + else + reg.pc = reg.pc + 1 + end + clock = clock + 4 +end + +-- jr nc, nn +opcodes[0x30] = function() + if reg.flags.c == 0 then + jump_relative_to_nn() + else + reg.pc = reg.pc + 1 + end + clock = clock + 4 +end + +-- jr z, nn +opcodes[0x28] = function() + if reg.flags.z == 1 then + jump_relative_to_nn() + else + reg.pc = reg.pc + 1 + end + clock = clock + 4 +end + +-- jr c, nn +opcodes[0x38] = function() + if reg.flags.c == 1 then + jump_relative_to_nn() + else + reg.pc = reg.pc + 1 + end + clock = clock + 4 +end + +call_nnnn = function() + lower = read_nn() + upper = bit32.lshift(read_nn(), 8) + -- at this point, reg.pc points at the next instruction after the call, + -- so store the current PC to the stack + reg.sp = reg.sp - 1 + write_byte(reg.sp, bit32.rshift(bit32.band(reg.pc, 0xFF00), 8)) + reg.sp = reg.sp - 1 + write_byte(reg.sp, bit32.band(reg.pc, 0xFF)) + + reg.pc = upper + lower +end + +-- call nn +opcodes[0xCD] = function() + call_nnnn() + clock = clock + 12 +end + +-- call nz, nnnn +opcodes[0xC4] = function() + if reg.flags.z == 0 then + call_nnnn() + clock = clock + 12 + else + reg.pc = reg.pc + 2 + clock = clock + 8 + end +end + +-- call nc, nnnn +opcodes[0xD4] = function() + if reg.flags.c == 0 then + call_nnnn() + clock = clock + 12 + else + reg.pc = reg.pc + 2 + clock = clock + 8 + end +end + +-- call z, nnnn +opcodes[0xCC] = function() + if reg.flags.z == 1 then + call_nnnn() + clock = clock + 12 + else + reg.pc = reg.pc + 2 + clock = clock + 8 + end +end + +-- call c, nnnn +opcodes[0xDC] = function() + if reg.flags.c == 1 then + call_nnnn() + clock = clock + 12 + else + reg.pc = reg.pc + 2 + clock = clock + 8 + end +end + +local ret = function() + lower = read_byte(reg.sp) + reg.sp = reg.sp + 1 + upper = bit32.lshift(read_byte(reg.sp), 8) + reg.sp = reg.sp + 1 + reg.pc = upper + lower + clock = clock + 12 +end + +-- ret +opcodes[0xC9] = function() ret() end + +-- ret nz +opcodes[0xC0] = function() + if reg.flags.z == 0 then + ret() + clock = clock + 16 + else + clock = clock + 4 + end +end + +-- ret nc +opcodes[0xD0] = function() + if reg.flags.c == 0 then + ret() + clock = clock + 16 + else + clock = clock + 4 + end +end + +-- ret nz +opcodes[0xC8] = function() + if reg.flags.z == 1 then + ret() + clock = clock + 16 + else + clock = clock + 4 + end +end + +-- ret nz +opcodes[0xD8] = function() + if reg.flags.c == 1 then + ret() + clock = clock + 16 + else + clock = clock + 4 + end +end + +-- reti +opcodes[0xD9] = function() + ret() + interrupts_enabled = 1 +end + +-- note: used only for the RST instructions below +function call_address(address) + -- reg.pc points at the next instruction after the call, + -- so store the current PC to the stack + reg.sp = reg.sp - 1 + write_byte(reg.sp, bit32.rshift(bit32.band(reg.pc, 0xFF00), 8)) + reg.sp = reg.sp - 1 + write_byte(reg.sp, bit32.band(reg.pc, 0xFF)) + + reg.pc = address + clock = clock + 12 +end + +-- rst N +opcodes[0xC7] = function() call_address(0x00) end +opcodes[0xCF] = function() call_address(0x08) end +opcodes[0xD7] = function() call_address(0x10) end +opcodes[0xDF] = function() call_address(0x18) end +opcodes[0xE7] = function() call_address(0x20) end +opcodes[0xEF] = function() call_address(0x28) end +opcodes[0xF7] = function() call_address(0x30) end +opcodes[0xFF] = function() call_address(0x38) end + +local break_on = {} +--break_on[0xFB] = 1 + +function process_interrupts() + if interrupts_enabled ~= 0 then + local fired = bit32.band(memory[0xFFFF], memory[0xFF0F]) + if fired ~= 0 then + -- an interrupt happened that we care about! How thoughtful + + -- First, disable interrupts so we don't have to pay royalties to Christopher Nolan + interrupts_enabled = 0 + + -- If the processor is halted / stopped, re-start it + halted = 0 + + -- Now, figure out which interrupt this is, and call the corresponding + -- interrupt vector + local vector = 0x40 + local count = 0 + while bit32.band(fired, 0x1) == 0 and count < 5 do + vector = vector + 0x08 + fired = bit32.rshift(fired, 1) + count = count + 1 + end + -- we need to clear the corresponding bit first, to avoid infinite loops + memory[0xFF0F] = bit32.bxor(bit32.lshift(0x1, count), memory[0xFF0F]) + call_address(vector) + return true + end + end +end + +function process_instruction() + process_interrupts() + + -- If the processor is currently halted, then do nothing. + if halted ~= 0 then + clock = clock + 4 + return true + end + + opcode = read_byte(reg.pc) + reg.pc = bit32.band(reg.pc + 1, 0xFFFF) + if opcodes[opcode] ~= nil then + -- run this instruction! + opcodes[opcode]() + -- add a base clock of 4 to every instruction + clock = clock + 4 + else + print(string.format("Unhandled opcode: %x", opcode)) + return false + end + if break_on[read_byte(reg.pc)] then + print("BREAK") + return false + end + return true +end + +Interrupt = {} +Interrupt.VBlank = 0x1 +Interrupt.LCDStat = 0x2 +Interrupt.Timer = 0x4 +Interrupt.Serial = 0x8 +Interrupt.Joypad = 0x16 + +function request_interrupt(bitmask) + memory[0xFF0F] = bit32.band(bit32.bor(memory[0xFF0F], bitmask), 0x1F) + if bit32.band(memory[0xFFFF], bitmask) ~= 0 then + halted = 0 + end +end