#include #include #include #include #include #define OP4_NX8(_) case _: case _ + 8: case _ + 16: case _ + 24: #define OP4_NX16_REL(_) \ case _: \ case _ + 16: \ case _ + 32: \ case _ + 48: \ opcode_rel = opcode - _; #define OP5_FLAG(_, always) \ OP4_NX8(_) \ case always: \ carry = (opcode == always) || \ (F & F_mask[(opcode - _) / 8]) == F_equals[(opcode - _) / 8]; #define OP8_NX8_REL(_) OP4_NX8(_) OP4_NX8(_ + 32) opcode_rel = opcode - _; #define OP7_PTR(_) \ case _: \ case _ + 1: \ case _ + 2: \ case _ + 3: \ case _ + 4: \ case _ + 5: \ case _ + 7: \ ptr8 = reg8_group[opcode & 7]; #define OP7_NX8(_) OP4_NX8(_) case _ + 32 : case _ + 40 : case _ + 56: #define OP7_NX8_PTR(_) OP7_NX8(_) ptr8 = reg8_group[(opcode - _) / 8]; #define OP49_REL(_) \ OP7_NX8(_) \ OP7_NX8(_ + 1) \ OP7_NX8(_ + 2) \ OP7_NX8(_ + 3) \ OP7_NX8(_ + 4) \ OP7_NX8(_ + 5) \ OP7_NX8(_ + 7) \ opcode_rel = opcode - _; #define OP56_PTR_REL(_) OP49_REL(_) OP7_PTR(_ + 48) opcode_rel = opcode - _; #define OP9_IMM_PTR(_) \ case _ + 6: \ case _ + 70: \ OP7_PTR(_) \ operand = (opcode == _ + 6) ? read8() \ : (opcode == _ + 70) ? read8_pc() \ : *ptr8; uint8_t opcode, opcode_rel, tmp8, operand, carry, neg, *rom0, *rom1, io[512], video_ram[8192], work_ram[16384], *extram, *extrambank, reg8[] = {19, 0, 216, 0, 77, 1, 176, 1, 254, 255}, &F = reg8[6], &A = reg8[7], *reg8_group[] = {reg8 + 1, reg8, reg8 + 3, reg8 + 2, reg8 + 5, reg8 + 4, &F, &A}, &IF = io[271], &LCDC = io[320], &LY = io[324], IME, halt, *ptr8; uint8_t const *key_state; uint16_t PC = 256, temp16, *reg16 = (uint16_t *)reg8, &HL = reg16[2], &SP = reg16[4], &DIV = (uint16_t &)io[259], ppu_dot = 32, *ptr16, *reg16_group1[] = {reg16, reg16 + 1, &HL, &SP}, *reg16_group2[] = {reg16, reg16 + 1, &HL, &HL}, prev_cycles, cycles; int tmp, tmp2, HL_add[] = {0, 0, 1, -1}, F_mask[] = {128, 128, 16, 16}, F_equals[] = {0, 128, 0, 16}, frame_buffer[23040], palette[] = {-1, -23197, -65536, -16777216, -1, -8092417, -12961132, -16777216, -1, -23197, -65536, -16777216}; void tick() { cycles += 4; } uint8_t mem_access(uint16_t addr, uint8_t val, int write) { tick(); switch (addr >> 12) { case 2: case 3: if (write) rom1 = rom0 + ((val ? val & 63 : 1) << 14); case 0: case 1: return rom0[addr]; case 4: case 5: if (write && val <= 3) extrambank = extram + (val << 13); case 6: case 7: return rom1[addr & 16383]; case 8: case 9: addr &= 8191; if (write) video_ram[addr] = val; return video_ram[addr]; case 10: case 11: addr &= 8191; if (write) extrambank[addr] = val; return extrambank[addr]; case 15: if (addr >= 65024) { if (write) { if (addr == 65350) for (int y = 160; --y >= 0;) io[y] = mem_access(val * 256 + y, 0, 0); io[addr & 511] = val; } if (addr == 65280) { if (!(io[256] & 16)) return ~(16 + key_state[81] * 8 + key_state[82] * 4 + key_state[80] * 2 + key_state[79]); if (!(io[256] & 32)) return ~(32 + key_state[40] * 8 + key_state[43] * 4 + key_state[29] * 2 + key_state[27]); return 255; } return io[addr & 511]; } case 12 ... 14: addr &= 16383; if (write) work_ram[addr] = val; return work_ram[addr]; } } uint8_t read8(uint16_t addr = HL) { return mem_access(addr, 0, 0); } void write8(uint8_t val, uint16_t addr = HL) { mem_access(addr, val, 1); } void set_flags(uint8_t mask, int Z, int N, int H, int C) { F = (F & mask) | (!Z * 128 + N * 64 + H * 32 + C * 16); } uint8_t read8_pc() { return read8(PC++); } uint16_t read16(uint16_t &addr = PC) { tmp8 = read8(addr++); return read8(addr++) * 256 + tmp8; } void push(uint16_t addr) { write8(addr >> 8, --SP); write8(addr, --SP); tick(); } int main() { rom1 = (rom0 = (uint8_t *)mmap(0, 1048576, PROT_READ, MAP_SHARED, open("rom.gb", O_RDONLY), 0)) + 32768; tmp = open("rom.sav", O_CREAT|O_RDWR, 0666); ftruncate(tmp, 32768); extrambank = extram = (uint8_t *)mmap(0, 32768, PROT_READ | PROT_WRITE, MAP_SHARED, tmp, 0); LCDC = 145; DIV = 44032; SDL_Init(SDL_INIT_VIDEO); SDL_Renderer *Sr = SDL_CreateRenderer( SDL_CreateWindow("pokegb", 0, 0, 800, 720, SDL_WINDOW_SHOWN), -1, SDL_RENDERER_PRESENTVSYNC); SDL_Texture *St = SDL_CreateTexture(Sr, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, 160, 144); key_state = SDL_GetKeyboardState(0); while (1) { prev_cycles = cycles; if (IME & IF & io[511]) { IF = halt = IME = 0; cycles += 8; carry = 1; temp16 = 64; goto CALL; } else if (halt) tick(); else switch (opcode = read8_pc()) { case 0: break; OP4_NX16_REL(1) *reg16_group1[opcode >> 4] = read16(); break; OP4_NX16_REL(2) write8(A, *reg16_group2[opcode >> 4]); HL += HL_add[opcode_rel / 16]; break; OP4_NX16_REL(3) (*reg16_group1[opcode >> 4])++; tick(); break; OP7_NX8_PTR(4) tmp8 = ++(*ptr8); goto INC_FLAGS; case 52: write8(tmp8 = read8() + 1); INC_FLAGS: set_flags(16, tmp8, 0, !(tmp8 & 15), 0); break; OP7_NX8_PTR(5) tmp8 = --(*ptr8); goto DEC_FLAGS; case 53: write8(tmp8 = read8() - 1); DEC_FLAGS: set_flags(16, tmp8, 1, tmp8 % 16 == 15, 0); break; OP7_NX8_PTR(6) *ptr8 = read8_pc(); break; case 7: A += A + (carry = A >> 7); goto CARRY_FLAG; OP4_NX16_REL(9) ptr16 = reg16_group1[opcode >> 4]; set_flags(128, 1, 0, HL % 4096 + *ptr16 % 4096 > 4095, HL + *ptr16 > 65535); HL += *ptr16; tick(); break; OP4_NX16_REL(10) A = read8(*reg16_group2[opcode >> 4]); HL += HL_add[opcode_rel / 16]; break; OP4_NX16_REL(11) (*reg16_group1[opcode >> 4])--; tick(); break; case 15: A = (carry = A & 1) * 128 + A / 2; goto CARRY_FLAG; case 23: carry = A >> 7; A += A + F / 16 % 2; CARRY_FLAG: set_flags(0, 1, 0, 0, carry); break; OP5_FLAG(32, 24) tmp8 = read8_pc(); if (carry) PC += (int8_t)tmp8, tick(); break; case 39: carry = tmp8 = 0; if (F & 32 || (!(F & 64) && A % 15 > 9)) tmp8 = 6; if (F & 16 || (!(F & 64) && A > 153)) tmp8 |= 96, carry = 1; set_flags(65, A += (F & 64) ? -tmp8 : tmp8, 0, 0, carry); break; case 47: A = ~A; set_flags(129, 1, 1, 1, 0); break; case 54: write8(read8_pc()); break; case 55: case 63: set_flags(128, 1, 0, 0, opcode == 55 ? 1 : !(F & 16)); break; OP7_NX8_PTR(70) *ptr8 = read8(); break; OP49_REL(64) *reg8_group[opcode_rel / 8] = *reg8_group[opcode & 7]; break; OP7_PTR(112) write8(*ptr8); break; case 118: halt = 1; break; OP9_IMM_PTR(128) neg = carry = 0; goto ALU; OP9_IMM_PTR(136) neg = 0; carry = F / 16 % 2; goto ALU; OP9_IMM_PTR(144) carry = 1; goto SUBTRACT; OP9_IMM_PTR(152) carry = !(F / 16 % 2); SUBTRACT: neg = 1; operand = ~operand; ALU: set_flags(0, tmp8 = A + operand + carry, neg, (A % 16 + operand % 16 + carry > 15) ^ neg, (A + operand + carry > 255) ^ neg); A = tmp8; break; OP9_IMM_PTR(160) set_flags(0, A &= operand, 0, 1, 0); break; OP9_IMM_PTR(168) set_flags(0, A ^= operand, 0, 0, 0); break; OP9_IMM_PTR(176) set_flags(0, A |= operand, 0, 0, 0); break; OP9_IMM_PTR(184) set_flags(0, A != operand, 1, A % 16 < operand % 16, A < operand); break; case 217: carry = IME = 1; goto RET; OP5_FLAG(192, 201) RET: tick(); if (carry) PC = read16(SP); break; OP4_NX16_REL(193) reg16[opcode_rel >> 4] = read16(SP); break; OP5_FLAG(194, 195) temp16 = read16(); if (carry) PC = temp16, tick(); break; OP5_FLAG(196, 205) temp16 = read16(); CALL: if (carry) push(PC), PC = temp16; break; OP4_NX16_REL(197) push(reg16[opcode_rel >> 4]); break; case 203: switch (opcode = read8_pc()) { OP7_PTR(0) *ptr8 += *ptr8 + (carry = *ptr8 / 128 % 2); goto CARRY_ZERO_FLAGS_PTR; OP7_PTR(8) *ptr8 = (carry = *ptr8 & 1) * 128 + *ptr8 / 2; goto CARRY_ZERO_FLAGS_PTR; case 14: tmp8 = read8(); carry = tmp8 & 1; write8(tmp8 = carry * 128 + tmp8 / 2); goto CARRY_ZERO_FLAGS_U; OP7_PTR(16) carry = *ptr8 >> 7; *ptr8 = *ptr8 * 2 + F / 16 % 2; goto CARRY_ZERO_FLAGS_PTR; OP7_PTR(24) carry = *ptr8 & 1; *ptr8 = *ptr8 / 2 + ((F * 8) & 128); goto CARRY_ZERO_FLAGS_PTR; OP7_PTR(32) carry = *ptr8 / 128 % 2; *ptr8 *= 2; goto CARRY_ZERO_FLAGS_PTR; OP7_PTR(40) carry = *ptr8 & 1; *ptr8 = (int8_t)*ptr8 >> 1; goto CARRY_ZERO_FLAGS_PTR; OP7_PTR(48) set_flags(0, *ptr8 = *ptr8 * 16 + *ptr8 / 16, 0, 0, 0); break; case 54: tmp8 = read8(); carry = 0; write8(tmp8 = tmp8 * 16 + tmp8 / 16); CARRY_ZERO_FLAGS_U: set_flags(0, tmp8, 0, 0, carry); break; OP7_PTR(56) carry = *ptr8 & 1; *ptr8 /= 2; CARRY_ZERO_FLAGS_PTR: set_flags(0, *ptr8, 0, 0, carry); break; OP8_NX8_REL(70) tmp8 = read8(); goto BIT_FLAGS; OP56_PTR_REL(64) tmp8 = *ptr8; BIT_FLAGS: set_flags(16, tmp8 & (1 << opcode_rel / 8), 0, 1, 0); break; OP8_NX8_REL(134) write8(read8() & ~(1 << opcode_rel / 8)); break; OP56_PTR_REL(128) *ptr8 &= ~(1 << opcode_rel / 8); break; OP8_NX8_REL(198) write8(read8() | (1 << opcode_rel / 8)); break; OP56_PTR_REL(192) *ptr8 |= 1 << opcode_rel / 8; break; } break; case 224: case 226: write8(A, 65280 + (opcode == 224 ? read8_pc() : *reg8)); break; case 233: PC = HL; break; case 234: write8(A, read16()); break; case 240: case 250: A = read8(opcode == 240 ? 65280 | read8_pc() : read16()); break; case 243: case 251: IME = opcode != 243; break; case 248: tmp8 = read8_pc(); set_flags(0, 1, 0, (uint8_t)SP + tmp8 > 255, SP % 16 + tmp8 % 16 > 15); HL = SP + (int8_t)tmp8; tick(); break; case 249: SP = HL; tick(); break; } for (DIV += cycles - prev_cycles; prev_cycles++ != cycles;) if (LCDC & 128) { if (++ppu_dot == 1 && LY == 144) IF |= 1; if (ppu_dot == 456) { if (LY < 144) for (tmp = 160; --tmp >= 0;) { uint8_t is_window = LCDC & 32 && LY >= io[330] && tmp >= io[331] - 7, x_offset = is_window ? tmp - io[331] + 7 : tmp + io[323], y_offset = is_window ? LY - io[330] : LY + io[322]; uint16_t tile = video_ram[((LCDC & (is_window ? 64 : 8) ? 7 : 6) << 10) + y_offset / 8 * 32 + x_offset / 8], palette_index = 0; x_offset = (x_offset ^ 7) & 7; uint8_t *tile_data = &video_ram[(LCDC & 16 ? tile : 256 + (int8_t)tile) * 16 + y_offset % 8 * 2], color = (tile_data[1] >> x_offset) % 2 * 2 + (*tile_data >> x_offset) % 2; if (LCDC & 2) for (uint8_t *sprite = io; sprite < io + 160; sprite += 4) { uint8_t sprite_x = tmp - sprite[1] + 8, sprite_y = LY - *sprite + 16; if (sprite_x < 8 && sprite_y < 8) { sprite_x ^= sprite[3] & 32 ? 0 : 7; tile_data = &video_ram[sprite[2] * 16 + (sprite_y ^ (sprite[3] & 64 ? 7 : 0)) * 2]; uint8_t sprite_color = (tile_data[1] >> sprite_x) % 2 * 2 + (*tile_data >> sprite_x) % 2; if (!((sprite[3] & 128) && color) && sprite_color) { color = sprite_color; palette_index += 1 + !!(sprite[3] & 8); break; } } } frame_buffer[LY * 160 + tmp] = palette[(io[327 + palette_index] >> (2 * color)) % 4 + palette_index * 4]; } if (LY == 144) { void *pixels; SDL_LockTexture(St, 0, &pixels, &tmp); for (tmp2 = 144; --tmp2 >= 0;) memcpy((uint8_t *)pixels + tmp2 * tmp, frame_buffer + tmp2 * 160, 640); SDL_UnlockTexture(St); SDL_RenderCopy(Sr, St, 0, 0); SDL_RenderPresent(Sr); SDL_Event e; while (SDL_PollEvent(&e)) if (e.type == SDL_QUIT) return 0; } LY = (LY + 1) % 154; ppu_dot = 0; } } else LY = ppu_dot = 0; } }