├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── deobfuscated.cc ├── empty.sav ├── img ├── o1.gif ├── o10.gif ├── o11.gif ├── o2.gif ├── o3.gif ├── o4.gif ├── o5.gif ├── o6.gif ├── o7.gif ├── o8.gif ├── o9.gif └── pokegb.png └── pokegb.cc /.gitignore: -------------------------------------------------------------------------------- 1 | pokegb 2 | rom.gb 3 | rom.sav 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Ben Smith 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | pokegb: pokegb.cc 2 | $(CC) -O2 -Wall -Wno-return-type -Wno-misleading-indentation -Wno-parentheses -o $@ $< -lSDL2 3 | 4 | rom.sav: empty.sav 5 | cp $< $@ 6 | 7 | clean: 8 | rm -f pokegb rom.sav 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pokegb 2 | 3 | A gameboy emulator that only plays Pokemon Blue, in ~50 lines of c++. 4 | 5 | See the [technical write-up](https://binji.github.io/posts/pokegb/). 6 | 7 | ## Features 8 | 9 | Plays Pokemon Blue (and Red). 10 | 11 | ## Screenshots 12 | 13 | ![1](img/o1.gif) 14 | ![2](img/o2.gif) 15 | ![3](img/o3.gif) 16 | ![4](img/o4.gif) 17 | ![5](img/o5.gif) 18 | ![6](img/o6.gif) 19 | ![7](img/o7.gif) 20 | ![8](img/o8.gif) 21 | ![9](img/o9.gif) 22 | ![10](img/o10.gif) 23 | ![11](img/o11.gif) 24 | 25 | ![Source Code](img/pokegb.png) 26 | 27 | ## Building 28 | 29 | Only builds on Linux and macOS AFAIK. 30 | 31 | ``` 32 | $ make 33 | ``` 34 | 35 | On macOS, you'll need to create a save file too (just the first time): 36 | 37 | ``` 38 | $ make rom.sav 39 | ``` 40 | 41 | ## Running 42 | 43 | Get a Pokemon Blue (or Pokemon Red) ROM file. The files that are known to work have the following sha1s: 44 | 45 | | Name | sha1 | 46 | | - | - | 47 | | Pokemon - Blue Version (USA, Europe) (SGB Enhanced).gb | `d7037c83e1ae5b39bde3c30787637ba1d4c48ce2` | 48 | | Pokemon - Red Version (USA, Europe) (SGB Enhanced).gb | `ea9bcae617fdf159b045185467ae58b2e4a48b9a` | 49 | 50 | Others might work too, but these are the ones that I've tried. 51 | 52 | Rename the file to `rom.gb` and put it in the current directory. Then run: 53 | 54 | ``` 55 | $ ./pokegb 56 | ``` 57 | 58 | The save file is written to `rom.sav`. 59 | 60 | Keys: 61 | 62 | | Action | Key | 63 | | --- | --- | 64 | | DPAD-UP | | 65 | | DPAD-DOWN | | 66 | | DPAD-LEFT | | 67 | | DPAD-RIGHT | | 68 | | B | Z | 69 | | A | X | 70 | | START | Enter | 71 | | SELECT | Tab | 72 | 73 | ## Updating keys 74 | 75 | Look for [line 24](https://github.com/binji/pokegb/blob/5444936aa7f12cb8c5c9c78e3c0c391ca4102f9b/pokegb.cc#L24) the source. 76 | The following table shows which numbers map to which keyboard keys: 77 | 78 | | number | default key | gameboy button | 79 | | - | - | - | 80 | | 27 | X | A Button | 81 | | 29 | Z | B Button | 82 | | 43 | Tab | Select Button | 83 | | 40 | Return | Start Button | 84 | | 79 | Arrow Right | DPAD Right | 85 | | 80 | Arrow Left | DPAD Left | 86 | | 81 | Arrow Down | DPAD Down | 87 | | 82 | Arrow Up | DPAD Up | 88 | 89 | Replace the numbers on this line with one from the [SDL scancode list](https://www.libsdl.org/tmp/SDL/include/SDL_scancode.h). 90 | -------------------------------------------------------------------------------- /deobfuscated.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define OPCREL(_) opcrel = (opcode - _) / 8 8 | 9 | #define OP4_NX8(_,X) case _: case _ + 8*X: case _ + 16*X: case _ + 24*X: 10 | 11 | #define OP4_NX16_REL(_) OP4_NX8(_, 2) opcrel = (opcode - _) / 16; 12 | 13 | #define OP5_FLAG(_, always) \ 14 | OP4_NX8(_, 1) \ 15 | case always: \ 16 | OPCREL(_), carry = opcode == always || !(F & F_mask[opcrel]) ^ opcrel & 1; 17 | 18 | #define OP8_REL(_) \ 19 | case _ ... _ + 7: \ 20 | tmp8 = reg8_access(0, 0, opcrel = opcode); 21 | 22 | #define OP8_NX8_REL(_) \ 23 | OP4_NX8(_, 1) OP4_NX8(_ + 32, 1) tmp8 = reg8_access(0, 0, OPCREL(_)); 24 | 25 | #define OP64_REL(_) \ 26 | case _ ... _ + 55: OP8_REL(_ + 56) OPCREL(_); 27 | 28 | #define OP9_IMM_PTR(_) \ 29 | OP8_REL(_) case _ + 70 : operand = opcode & 64 ? mem8(PC++) : tmp8; 30 | 31 | uint8_t opcode, opcrel, tmp8, operand, carry, neg, *rom0, *rom1, io[512], 32 | video_ram[8192], work_ram[16384], *extram, *extrambank, 33 | reg8[] = {19, 0, 216, 0, 77, 1, 176, 1}, &F = reg8[6], &A = reg8[7], 34 | *reg8_group[] = {reg8 + 1, reg8, reg8 + 3, reg8 + 2, 35 | reg8 + 5, reg8 + 4, &F, &A}, 36 | &IF = io[271], &LCDC = io[320], &LY = io[324], IME, halt; 37 | 38 | uint8_t const *key_state; 39 | 40 | uint16_t PC = 256, *reg16 = (uint16_t *)reg8, &HL = reg16[2], SP = 65534, 41 | &DIV = (uint16_t &)io[259], ppu_dot = 32, 42 | *reg16_group1[] = {reg16, reg16 + 1, &HL, &SP}, 43 | *reg16_group2[] = {reg16, reg16 + 1, &HL, &HL}, prev_cycles, cycles; 44 | 45 | int tmp, tmp2, F_mask[] = {128, 128, 16, 16}, frame_buffer[23040], 46 | palette[] = {-1, -23197, -65536, -1 << 24, 47 | -1, -8092417, -12961132, -1 << 24}; 48 | 49 | void tick() { cycles += 4; } 50 | 51 | uint8_t mem8(uint16_t addr = HL, uint8_t val = 0, int write = 0) { 52 | tick(); 53 | switch (addr >> 13) { 54 | case 1: 55 | if (write) 56 | rom1 = rom0 + ((val ? val & 63 : 1) << 14); 57 | 58 | case 0: 59 | return rom0[addr]; 60 | 61 | case 2: 62 | if (write && val <= 3) 63 | extrambank = extram + (val << 13); 64 | 65 | case 3: 66 | return rom1[addr & 16383]; 67 | 68 | case 4: 69 | addr &= 8191; 70 | if (write) 71 | video_ram[addr] = val; 72 | return video_ram[addr]; 73 | 74 | case 5: 75 | addr &= 8191; 76 | if (write) 77 | extrambank[addr] = val; 78 | return extrambank[addr]; 79 | 80 | case 7: 81 | if (addr >= 65024) { 82 | if (write) { 83 | if (addr == 65350) 84 | for (int y = 160; --y >= 0;) 85 | io[y] = mem8(val << 8 | y); 86 | io[addr & 511] = val; 87 | } 88 | 89 | if (addr == 65280) { 90 | if (~io[256] & 16) 91 | return ~(16 + key_state[SDL_SCANCODE_DOWN] * 8 + 92 | key_state[SDL_SCANCODE_UP] * 4 + 93 | key_state[SDL_SCANCODE_LEFT] * 2 + 94 | key_state[SDL_SCANCODE_RIGHT]); 95 | if (~io[256] & 32) 96 | return ~(32 + key_state[SDL_SCANCODE_RETURN] * 8 + 97 | key_state[SDL_SCANCODE_TAB] * 4 + 98 | key_state[SDL_SCANCODE_Z] * 2 + 99 | key_state[SDL_SCANCODE_X]); 100 | return 255; 101 | } 102 | return io[addr & 511]; 103 | } 104 | 105 | case 6: 106 | addr &= 16383; 107 | if (write) 108 | work_ram[addr] = val; 109 | return work_ram[addr]; 110 | } 111 | } 112 | 113 | void set_flags(uint8_t mask, int Z, int N, int H, int C) { 114 | F = F & mask | !Z << 7 | N << 6 | H << 5 | C << 4; 115 | } 116 | 117 | uint16_t read16(uint16_t &addr = PC) { 118 | tmp8 = mem8(addr++); 119 | return mem8(addr++) << 8 | tmp8; 120 | } 121 | 122 | void push(uint16_t val) { 123 | mem8(--SP, val >> 8, 1); 124 | mem8(--SP, val, 1); 125 | tick(); 126 | } 127 | 128 | uint8_t reg8_access(uint8_t val, int write = 1, uint8_t o = opcrel) { 129 | return (o &= 7) == 6 ? mem8(HL, val, write) 130 | : write ? *reg8_group[o] = val 131 | : *reg8_group[o]; 132 | } 133 | 134 | uint8_t get_color(int tile, int y_offset, int x_offset) { 135 | uint8_t *tile_data = &video_ram[tile * 16 + y_offset * 2]; 136 | return (tile_data[1] >> x_offset) % 2 * 2 + (*tile_data >> x_offset) % 2; 137 | } 138 | 139 | int main() { 140 | rom1 = (rom0 = (uint8_t *)mmap(0, 1 << 20, PROT_READ, MAP_SHARED, 141 | open("rom.gb", O_RDONLY), 0)) + 142 | 32768; 143 | tmp = open("rom.sav", O_CREAT|O_RDWR, 0666); 144 | ftruncate(tmp, 32768); 145 | extrambank = extram = 146 | (uint8_t *)mmap(0, 32768, PROT_READ | PROT_WRITE, MAP_SHARED, tmp, 0); 147 | LCDC = 145; 148 | DIV = 44032; 149 | SDL_Init(SDL_INIT_VIDEO); 150 | SDL_Renderer *renderer = SDL_CreateRenderer( 151 | SDL_CreateWindow("pokegb", 0, 0, 800, 720, SDL_WINDOW_SHOWN), -1, 152 | SDL_RENDERER_PRESENTVSYNC); 153 | SDL_Texture *texture = SDL_CreateTexture( 154 | renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, 160, 144); 155 | key_state = SDL_GetKeyboardState(0); 156 | 157 | while (1) { 158 | prev_cycles = cycles; 159 | if (IME & IF & io[511]) { 160 | IF = halt = IME = 0; 161 | cycles += 8; 162 | push(PC); 163 | PC = 64; 164 | } else if (halt) 165 | tick(); 166 | else 167 | switch (opcode = mem8(PC++)) { 168 | OP4_NX16_REL(1) // LD r16, u16 169 | *reg16_group1[opcrel] = read16(); 170 | case 0: // NOP 171 | break; 172 | 173 | OP4_NX16_REL(10) // LD A, (r16) 174 | OP4_NX16_REL(2) // LD (r16), A 175 | tmp = opcode & 8; 176 | reg8_access(mem8(*reg16_group2[opcrel], A, !tmp), tmp, 7); 177 | HL += opcrel < 2 ? 0 : 5 - 2 * opcrel; 178 | break; 179 | 180 | OP4_NX16_REL(11) // DEC r16 181 | OP4_NX16_REL(3) // INC r16 182 | *reg16_group1[opcrel] += opcode & 8 ? -1 : 1; 183 | tick(); 184 | break; 185 | 186 | OP8_NX8_REL(5) // DEC r8 / DEC (HL) 187 | OP8_NX8_REL(4) // INC r8 / INC (HL) 188 | neg = opcode & 1; 189 | reg8_access(tmp8 += 1 - neg * 2); 190 | set_flags(16, tmp8, neg, !(tmp8 + neg & 15), 0); 191 | break; 192 | 193 | OP8_NX8_REL(6) // LD r8, u8 / LD (HL), u8 194 | reg8_access(mem8(PC++)); 195 | break; 196 | 197 | OP4_NX16_REL(9) // ADD HL, r16 198 | tmp = *reg16_group1[opcrel]; 199 | set_flags(128, 1, 0, HL % 4096 + tmp % 4096 > 4095, HL + tmp > 65535); 200 | HL += tmp; 201 | tick(); 202 | break; 203 | 204 | OP4_NX8(7,1) 205 | neg = 1; 206 | goto ROTATE; 207 | 208 | OP5_FLAG(32, 24) // JR i8 / JR , i8 209 | tmp8 = mem8(PC++); 210 | if (carry) 211 | PC += (int8_t)tmp8, tick(); 212 | break; 213 | 214 | case 39: // DAA 215 | carry = tmp8 = 0; 216 | if (F & 32 || ~F & 64 && A % 16 > 9) 217 | tmp8 = 6; 218 | if (F & 16 || ~F & 64 && A > 153) 219 | tmp8 |= 96, carry = 1; 220 | set_flags(65, A += F & 64 ? -tmp8 : tmp8, 0, 0, carry); 221 | break; 222 | 223 | case 47: // CPL 224 | A = ~A; 225 | set_flags(144, 1, 1, 1, 0); 226 | break; 227 | 228 | case 55: case 63: // SCF / CCF 229 | set_flags(128, 1, 0, 0, opcode & 8 ? !(F & 16) : 1); 230 | break; 231 | 232 | OP64_REL(64) // LD r8, r8 / LD r8, (HL) / LD (HL), r8 / HALT 233 | opcode == 118 ? halt = 1 : reg8_access(tmp8); 234 | break; 235 | 236 | OP9_IMM_PTR(128) // ADD A, r8 / ADD A, (HL) / ADD A, u8 237 | neg = carry = 0; 238 | goto ALU; 239 | 240 | OP9_IMM_PTR(136) // ADC A, r8 / ADC A, (HL) / ADC A, u8 241 | neg = 0; 242 | carry = F / 16 % 2; 243 | goto ALU; 244 | 245 | OP9_IMM_PTR(184) // CP A, r8 / CP A, (HL) / CP A, u8 246 | goto SUB; 247 | OP9_IMM_PTR(144) // SUB A, r8 / SUB A, (HL) / SUB A, u8 248 | SUB: 249 | carry = 1; 250 | goto SUBTRACT; 251 | 252 | OP9_IMM_PTR(152) // SBC A, r8 / SBC A, (HL) / SBC A, u8 253 | carry = !(F / 16 % 2); 254 | SUBTRACT: 255 | neg = 1; 256 | operand = ~operand; 257 | ALU: 258 | set_flags(0, tmp8 = A + operand + carry, neg, 259 | (A % 16 + operand % 16 + carry > 15) ^ neg, 260 | (A + operand + carry > 255) ^ neg); 261 | if (~(opcode / 8) & 7) 262 | A = tmp8; 263 | break; 264 | 265 | OP9_IMM_PTR(160) // AND A, r8 / AND A, (HL) / AND A, u8 266 | set_flags(0, A &= operand, 0, 1, 0); 267 | break; 268 | 269 | OP9_IMM_PTR(168) // XOR A, r8 / XOR A, (HL) / XOR A, u8 270 | set_flags(0, A ^= operand, 0, 0, 0); 271 | break; 272 | 273 | OP9_IMM_PTR(176) // OR A, r8 / OR A, (HL) / OR A, u8 274 | set_flags(0, A |= operand, 0, 0, 0); 275 | break; 276 | 277 | case 217: // RETI 278 | carry = IME = 1; 279 | goto RET; 280 | 281 | OP5_FLAG(192, 201) // RET / RET 282 | RET: 283 | tick(); 284 | if (carry) 285 | PC = read16(SP); 286 | break; 287 | 288 | OP4_NX16_REL(193) // POP r16 289 | reg16[opcrel] = read16(SP); 290 | break; 291 | 292 | OP5_FLAG(194, 195) // JP u16 / JP , u16 293 | goto CALL; 294 | OP5_FLAG(196, 205) // CALL u16 / CALL , u16 295 | CALL: 296 | tmp = read16(); 297 | if (carry) 298 | opcode & 4 ? push(PC) : tick(), PC = tmp; 299 | break; 300 | 301 | OP4_NX16_REL(197) // PUSH r16 302 | push(reg16[opcrel]); 303 | break; 304 | 305 | case 203: 306 | neg = 0; 307 | opcode = mem8(PC++); 308 | ROTATE: 309 | switch (opcode) { 310 | OP8_REL(0) // RLC r8 / RLC (HL) 311 | OP8_REL(16) // RL r8 / RL (HL) 312 | OP8_REL(32) // SLA r8 / SLA (HL) 313 | carry = tmp8 >> 7; 314 | tmp8 += tmp8 + (opcode & 16 ? F / 16 % 2 : opcode & 32 ? 0 : carry); 315 | goto CARRY_ZERO_FLAGS_U; 316 | 317 | OP8_REL(48) // SWAP r8 / SWAP (HL) 318 | carry = 0; 319 | tmp8 = tmp8 * 16 + tmp8 / 16; 320 | goto CARRY_ZERO_FLAGS_U; 321 | 322 | OP8_REL(8) // RRC r8 / RRC (HL) 323 | OP8_REL(24) // RR r8 / RR (HL) 324 | OP8_REL(40) // SRA r8 / SRA (HL) 325 | OP8_REL(56) // SRL r8 / SRL (HL) 326 | carry = tmp8 & 1; 327 | tmp8 = (opcode & 48) == 32 328 | ? (int8_t)tmp8 >> 1 329 | : tmp8 / 2 + (opcode & 32 ? 0 330 | : opcode & 16 ? (F * 8 & 128) 331 | : carry * 128); 332 | CARRY_ZERO_FLAGS_U: 333 | reg8_access(tmp8); 334 | set_flags(0, neg || tmp8, 0, 0, carry); 335 | break; 336 | 337 | OP64_REL(64) // BIT bit, r8 / BIT bit, (HL) 338 | set_flags(16, tmp8 & 1 << opcrel, 0, 1, 0); 339 | break; 340 | 341 | OP64_REL(128) // RES bit, r8 / RES bit, (HL) 342 | reg8_access(tmp8 & ~(1 << opcrel),1,opcode); 343 | break; 344 | 345 | OP64_REL(192) // SET bit, r8 / SET bit, (HL) 346 | reg8_access(tmp8 | 1 << opcrel,1,opcode); 347 | } 348 | break; 349 | 350 | case 224: case 226: case 234: 351 | case 240: case 242: case 250: 352 | // LD A, (FF00 + u8) / LD A, (FF00 + C) / LD A, (u16) 353 | // LD (FF00 + u8), A / LD (FF00 + C), A / LD (u16), A 354 | tmp = opcode & 16; 355 | reg8_access(mem8(opcode & 8 356 | ? read16() 357 | : 65280 + (opcode & 2 ? *reg8 : mem8(PC++)), 358 | A, !tmp), 359 | tmp, 7); 360 | break; 361 | 362 | case 233: // JP HL 363 | PC = HL; 364 | break; 365 | 366 | case 243: case 251: // DI / EI 367 | IME = opcode != 243; 368 | break; 369 | 370 | case 248: // LD HL, SP + i8 371 | HL = SP + (int8_t)(tmp8 = mem8(PC++)); 372 | set_flags(0, 1, 0, SP % 16 + tmp8 % 16 > 15, (uint8_t)SP + tmp8 > 255); 373 | tick(); 374 | break; 375 | 376 | case 249: // LD SP, HL 377 | SP = HL; 378 | tick(); 379 | } 380 | 381 | for (DIV += cycles - prev_cycles; prev_cycles++ != cycles;) 382 | if (LCDC & 128) { 383 | if (++ppu_dot == 456) { 384 | if (LY < 144) 385 | for (tmp = 160; --tmp >= 0;) { 386 | uint8_t is_window = 387 | LCDC & 32 && LY >= io[330] && tmp >= io[331] - 7, 388 | x_offset = is_window ? tmp - io[331] + 7 : tmp + io[323], 389 | y_offset = is_window ? LY - io[330] : LY + io[322]; 390 | uint16_t 391 | palette_index = 0, 392 | tile = video_ram[(LCDC & (is_window ? 64 : 8) ? 7 : 6) << 10 | 393 | y_offset / 8 * 32 + x_offset / 8], 394 | color = get_color(LCDC & 16 ? tile : 256 + (int8_t)tile, 395 | y_offset & 7, 7 - x_offset & 7); 396 | 397 | if (LCDC & 2) 398 | for (uint8_t *sprite = io; sprite < io + 160; sprite += 4) { 399 | uint8_t sprite_x = tmp - sprite[1] + 8, 400 | sprite_y = LY - *sprite + 16, 401 | sprite_color = get_color( 402 | sprite[2], sprite_y ^ (sprite[3] & 64 ? 7 : 0), 403 | sprite_x ^ (sprite[3] & 32 ? 0 : 7)); 404 | if (sprite_x < 8 && sprite_y < 8 && 405 | !(sprite[3] & 128 && color) && sprite_color) { 406 | color = sprite_color; 407 | palette_index = 1 + !!(sprite[3] & 16); 408 | break; 409 | } 410 | } 411 | 412 | frame_buffer[LY * 160 + tmp] = 413 | palette[(io[327 + palette_index] >> 2 * color) % 4 + 414 | palette_index * 4 & 415 | 7]; 416 | } 417 | 418 | if (LY == 143) { 419 | IF |= 1; 420 | SDL_UpdateTexture(texture, 0, frame_buffer, 640); 421 | SDL_RenderCopy(renderer, texture, 0, 0); 422 | SDL_RenderPresent(renderer); 423 | SDL_Event event; 424 | while (SDL_PollEvent(&event)) 425 | if (event.type == SDL_QUIT) 426 | return 0; 427 | } 428 | 429 | LY = (LY + 1) % 154; 430 | ppu_dot = 0; 431 | } 432 | } else 433 | LY = ppu_dot = 0; 434 | } 435 | } 436 | -------------------------------------------------------------------------------- /empty.sav: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/o1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binji/pokegb/0434c7687e75eb003cd77bd42c1ae0ffb2654a5d/img/o1.gif -------------------------------------------------------------------------------- /img/o10.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binji/pokegb/0434c7687e75eb003cd77bd42c1ae0ffb2654a5d/img/o10.gif -------------------------------------------------------------------------------- /img/o11.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binji/pokegb/0434c7687e75eb003cd77bd42c1ae0ffb2654a5d/img/o11.gif -------------------------------------------------------------------------------- /img/o2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binji/pokegb/0434c7687e75eb003cd77bd42c1ae0ffb2654a5d/img/o2.gif -------------------------------------------------------------------------------- /img/o3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binji/pokegb/0434c7687e75eb003cd77bd42c1ae0ffb2654a5d/img/o3.gif -------------------------------------------------------------------------------- /img/o4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binji/pokegb/0434c7687e75eb003cd77bd42c1ae0ffb2654a5d/img/o4.gif -------------------------------------------------------------------------------- /img/o5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binji/pokegb/0434c7687e75eb003cd77bd42c1ae0ffb2654a5d/img/o5.gif -------------------------------------------------------------------------------- /img/o6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binji/pokegb/0434c7687e75eb003cd77bd42c1ae0ffb2654a5d/img/o6.gif -------------------------------------------------------------------------------- /img/o7.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binji/pokegb/0434c7687e75eb003cd77bd42c1ae0ffb2654a5d/img/o7.gif -------------------------------------------------------------------------------- /img/o8.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binji/pokegb/0434c7687e75eb003cd77bd42c1ae0ffb2654a5d/img/o8.gif -------------------------------------------------------------------------------- /img/o9.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binji/pokegb/0434c7687e75eb003cd77bd42c1ae0ffb2654a5d/img/o9.gif -------------------------------------------------------------------------------- /img/pokegb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binji/pokegb/0434c7687e75eb003cd77bd42c1ae0ffb2654a5d/img/pokegb.png -------------------------------------------------------------------------------- /pokegb.cc: -------------------------------------------------------------------------------- 1 | #include /******************************************/ 2 | #include /* POKEGB by Ben Smith (June 2021) */ 3 | #include /* -------------------------------------- */ 4 | #include /* A GB emulator that can only play */ 5 | #include /* Pokemon Blue/Red, written in C++. */ 6 | #define P case /* Requires gcc/clang and Linux/macOS. */ 7 | #define O goto /* Many features are not implemented! */ 8 | #define K break; /* */ 9 | #define E return /* $ cc pokegb.cc -lSDL2 -o pokegb */ 10 | #define M(_) L(_,2)f=(b-_)/16; /* $ ./pokegb # reads from rom.gb */ 11 | #define o(_) P _..._+7:a=q(0,0,f=b); /* # writes to rom.sav */ 12 | #define N(_) o(_)P _+70:u=b&64?i(k++):a; /* */ 13 | #define B(_) P _..._+55:o(_+56)f=(b-_)/8; /* Controls: Up/Down/Left/Right=Arrow Keys*/ 14 | #define L(_,__) P _:P _+8*__:P _+16*__:P _+24*__: /* B Button=Z, A Button=X */ 15 | #define U(_) L(_,1)L(_+32,1)a=q(0,0,f=(b-_)/8); /* Start=Enter, Select=Tab */ 16 | #define e(_,__) L(_,1)P __:f=(b-_)/8,d=b==__||!(F&aj[f])^f&1; /******************************************/ 17 | using g=uint8_t ;g b,f,a,u,d,m ,*S,*T,h[512], 18 | Z[8192],ac[16384],*ad,*C ,r[]={19,0,216,0,77,1,176 ,1},&F=r[6],&j=r[7],*ae[ 19 | ]={r+1,r,r+3,r+2,r+5,r+4,&F,&j },&H=h[271],&z=h[320],&s=h[324 ],A,V,*v;using x=uint16_t;x k= 20 | 256,*y=(x*)r,&n=y[2],w=65534,&af=( x&)h[259],W=32,*X[]={y,y+1,&n,&w}, *ai[]={y,y+1,&n,&n},Y,I;using p=int 21 | ;p c,aj[]={128,128,16,16},ag[23040],ak []={-1,-23197,-65536,-1<<24,-1,-8092417 ,-12961132,-1<<24};g i(x a=n,g b=0,p c 22 | =0){I+=4;switch(a>>13){P 1:if(c)T=S+((b?b& 63:1)<<14);P 0:E S[a];P 2:if(c&&b<=3)C=ad+ (b<<13);P 3:E T[a&16383];P 4:a&=8191;if(c) 23 | Z[a]=b;E Z[a];P 5:a&=8191;if(c)C[a]=b;E C[a] ;P 7:if(a>=65024){if(c){if(a==65350)for(p y= 160;--y>=0;)h[y]=i(b<<8|y);h[a&511]=b;}if(a 24 | ==65280){if(~h[256]&16)E~(16+v[81]*8+v[82]*4+v [80]*2+v[79]);if(~h[256]&32)E~(32+v[40]*8+v[43 ]*4+v[29]*2+v[27]);E 255;}E h[a&511];}P 6:a&= 25 | 16383;if(c)ac[a]=b;E ac[a];}}g D(g a,p b,p c,p d ,p R){E F=F&a|!b<<7|c<<6|d<<5|R<<4;}x J(x&b=k){a =i(b++);E i(b++)<<8|a;}g t(x a){i(--w,a>>8,1);i( 26 | --w,a,1);E I+=4;}g q(g a,p b=1,g c=f){E(c&=7)==6?i (n,a,b):b?*ae[c]=a:*ae[c];}g ah(p a,p b,p c){g*d=& Z[a*16+b*2];E(d[1]>>c)%2*2+(*d>>c)%2;}p main(){T=( 27 | S=(g*)mmap(0,1<<20,1,1,open("rom.gb",0),0))+32768;c= open("rom.sav",66,438);ftruncate(c,32768);SDL_Init( 32+0+0);auto*aa=SDL_CreateRenderer(SDL_CreateWindow( 28 | "pokegb",0,0,800,720,4),-1,4);C=ad=(g*)mmap(0,32768,3, 1,c,0);v=(g*)SDL_GetKeyboardState(0);z=145;af=44032; auto*ab=SDL_CreateTexture(aa,376840196,1,160,144);for( 29 | ;;){Y=I;if(A&H&h[511])H=V=A=0,I+=8,t(k),k=64;else if(V )I+=4;else switch(b=i(k++)){M(1)*X[f]=J();P 0:K M(10)M (2)c=b&8;q(i(*ai[f],j,!c),c,7);n+=f<2?0:5-2*f;K M(11)M 30 | (3)*X[f]+=b&8?-1:1;I+=4; K U(5)U(4)m=b&1;q(a+=1-m*2);D(16,a,m,!(a+m&15),0 );K U(6)q(i(k++));K M(9)c=*X[f];D(128,1,0,n%4096 +c%4096>4095,n+c>65535); 31 | n+=c;I+=4;K L(7,1)m=1 ;O J;e(32,24)a=i(k++);if(d)k+=(int8_t)a,I+= 4;K P 39:d=a=0;if(F&32||~F&64&&j%16>9)a=6; if(F&16||~F&64&&j>153 32 | )a|=96,d=1;D(65,j+=F& 64?-a:a,0,0,d);K P 47:j=~j;D(144,1,1,1,0); K P 55:P 63:D(128,1,0,0,b&8?!(F&16):1);K B (64)b==118?V=1:q(a);K 33 | N(128)m=d=0;O F;N(136 )m=0;d=F/16%2;O F;N(184)O e;N(144)e:d=1;O B;N(152)d=!(F/16%2);B:m=1;u=~u;F:D(0,a=j+u +d,m,(j%16+u%16+d>15) 34 | ^m,(j+u+d>255)^m);if( ~(b/8)&7)j=a;K N(160)D(0,j&=u,0,1,0);K N( 168)D(0,j^=u,0,0,0);K N(176)D(0,j|=u,0,0,0 );K P 217:d=A=1;O Z;e( 35 | 192,201) Z:I +=4 ;if(d)k=J(w);K M( 193 )y[ f]=J(w);K e(194, 195 )O C ;e(196, 36 | 205)C:c= J() ;if (d)b&4?t(k),0:I+= 4,k =c; K M(197)t(y[f]); K P 203 :m=0;b=i( 37 | k++);J:;; switch (b){ o(0)o(16)o(32)d=a>> 7;a+= a+(b &16?F/16%2:b&32?0: d);O I;o( 48)d=0;a= 38 | a*16+a/ 16;O I;o(8)o(24 )o(40)o( 56)d=a&1 ;a=(b&48)==32? (int8_t) a>>1:a/2 +(b&32?0:b&16? (F*8&128 39 | ):d*128); I:q(a);D (0,m||a,0 ,0,d);K B (64)D(16 ,a&1<15,(g )w+a>255);I +=4;K P 249 44 | :w=n;I+=4;}for (af+=I-Y;Y++ !=I;)if(z&128 ){if(++W==456) {if(s<144)for (c=160;--c>= 45 | 0;){g b=z&32&&s >=h[330]&&c>=h[ 331]-7,d=b?c-h[ 331]+7:c+h[323] ,R=b?s-h[330]:s +h[322];x f=0,i 46 | =Z[(z&(b?64:8)?7:6)<<10|R/8*32+d/8],j= ah(z&16?i:256+(int8_t)i,R&7,7-d&7);if( z&2)for(g*a=h;a>2*j)%4+f*4&7];}if(s==143){H |=1;SDL_Event b;SDL_UpdateTexture 49 | (ab,0,ag,640);SDL_RenderCopy (aa,ab,0,0);SDL_RenderPresent (aa);for(;SDL_PollEvent(&b 50 | );)if(b.type==256 )E 0;}s=(s+1)%154; W=0;}}else s=W=0;}} 51 | --------------------------------------------------------------------------------