#!/usr/bin/perl #use SDL; #use SDL::App; #use SDL::Event; use File::Basename; my $ticks; $SIG{TSTP} = "dump_regs"; our @RAM; # an array of 8192 bytes, 1d our @VRAM; # see above our @SPRITES; # 40 maximum sprites at one time our @SNDBUF; # 4 8192*4 audio buffers our $CLK_SGB = "4.194304"; # MHz our $CLK_GBC = "8.388"; # MHz our $NINTENDO_LOGO = chr(hex(0xCE)).chr(hex(0xED)).chr(hex(0x66)).chr(hex(0x66)).chr(hex(0xCC)).chr(hex(0x0D)).chr(hex(0x00)).chr(hex(0x0B)).chr(hex(0x03)).chr(hex(0x73)).chr(hex(0x00)).chr(hex(0x83)).chr(hex(0x00)).chr(hex(0x0C)).chr(hex(0x00)).chr(hex(0x0D)); $NINTENDO_LOGO .= chr(hex(0x00)).chr(hex(0x08)).chr(hex(0x11)).chr(hex(0x1F)).chr(hex(0x88)).chr(hex(0x89)).chr(hex(0x00)).chr(hex(0x0E)).chr(hex(0xDC)).chr(hex(0xCC)).chr(hex(0x6E)).chr(hex(0xE6)).chr(hex(0xDD)).chr(hex(0xDD)).chr(hex(0xD9)).chr(hex(0x99)); $NINTENDO_LOGO .= chr(hex(0xBB)).chr(hex(0xBB)).chr(hex(0x67)).chr(hex(0x63)).chr(hex(0x6E)).chr(hex(0x0E)).chr(hex(0xEC)).chr(hex(0xCC)).chr(hex(0xDD)).chr(hex(0xDC)).chr(hex(0x99)).chr(hex(0x9F)).chr(hex(0xBB)).chr(hex(0xB9)).chr(hex(0x33)).chr(hex(0x3E)); # Generate the opcode table. our %OPCODES; # The gameboy color's screen is 160x144. #my $app = SDL::App->new(-width => 160, -height => 144, -depth => 16) or die("Failed to create screen\n"); #my $event = SDL::Event->new(); my $filename = shift || die("Parameters: ".basename($0)." \n"); open(ROM, $filename) or die("Failed to open $filename: $!\n"); my @rom_array = ; my $rom = join('', @rom_array); close(ROM); my $rom_size = length($rom)/1024; my $rom_raw_size = length($rom); print "*** ROM Size: ".$rom_size."kb\n"; my $title = substr($rom, 0x134, 16); # title is at 0x134-0x142 (16 chars) print "*** Game: $title\n"; our %cart_types; $cart_types{0x0} = "ROM only"; $cart_types{0x1} = "ROM and MBC1"; $cart_types{0x2} = "ROM and MBC1 with RAM"; $cart_types{0x3} = "ROM and MBC1 with RAM and BATTERY"; $cart_types{0x5} = "ROM and MBC2"; $cart_types{0x6} = "ROM and MBC2 with BATTERY"; $cart_types{0x8} = "ROM with RAM"; $cart_types{0x9} = "ROM with RAM and BATTERY"; $cart_types{0xB} = "ROM with MMM01"; $cart_types{0xC} = "ROM with MMM01 and SRAM"; $cart_types{0xD} = "ROM with MMM01 and SRAM with BATTERY"; $cart_types{0xF} = "ROM with MBC3 and TIMER with BATTERY"; $cart_types{0x10} = "ROM with MBC3 and TIMER with RAM and BATTERY"; $cart_types{0x11} = "ROM with MBC3"; $cart_types{0x12} = "ROM with MBC3 and RAM"; $cart_types{0x13} = "ROM with MBC3 and RAM and BATTERY"; $cart_types{0x19} = "ROM with MBC5"; $cart_types{0x1A} = "ROM with MBC5 and RAM"; $cart_types{0x1B} = "ROM with MBC5 and RAM with BATTERY"; $cart_types{0x1C} = "ROM with MBC5 and RUMBLE"; $cart_types{0x1D} = "ROM with MBC5 and RUMBLE with SRAM"; $cart_types{0x1E} = "ROM with MBC5 and RUMBLE with SRAM and BATTERY"; $cart_types{0x1F} = "Pocket Camera ROM"; $cart_types{0xFD} = "Bandai TAMA-5"; $cart_types{0xFE} = "Hudson HuC-3"; $cart_types{0xFF} = "Hudson HuC-1"; my $cart_type = substr($rom, 0x147, 1); print "*** Cart type: ".$cart_types{ord($cart_type)}."\n"; my $rom_size_ = substr($rom, 0x148, 1); my $ram_size_ = substr($rom, 0x149, 1); our @stack; our %regs; # 8-bit registers $regs{A} = 0x0; $regs{B} = 0x0; $regs{C} = 0x0; $regs{D} = 0x0; $regs{E} = 0x0; $regs{F} = 0x0; $regs{H} = 0x0; $regs{L} = 0x0; # 16-bit registers $regs{AF} = 0x0; $regs{BC} = 0x0; $regs{DE} = 0x0; $regs{HL} = 0x0; # Special registers $regs{PC} = 0x0; # Program counter $regs{SP} = 0x100; # Stack pointer sub is_spacebar_down { while ($event->wait()) { my $key = $event->type(); if ($key == SDLK_SPACE) { return(1); } if ($key == SDLK_q) { exit(0); } else { return(0); } } } sub RCLA { $regs{F} = (($regs{A} & 0x80) >> 3); $regs{A} = (($regs{A} << 1) | ($regs{A} >> 7)); } sub RLA { $regs{F} |= $regs{A} >> 7; $regs{A} <<= 1; $regs{A} |= ($regs{F} & 0x10) >> 4; $regs{F} = ($regs{F} & 0x01) << 4; } sub RRA { $regs{F} |= $regs{A} & 0x01; $regs{A} >>= 1; $regs{A} |= ($regs{F} & 0x10) << 3; $regs{F} = ($regs{F} & 0x01) << 4; } sub DAA { } for ($x=0;$x> 4) | (($regs{A} = ($regs{A} + $$reg)) == 0) ? 0x80 : 0; } sub ADC { my $reg = shift; my $DUMMY_F = ($regs{F} & 0x10) > 0 ? 1 :0; $regs{F} = (((($regs{A} & 0x0F) + ($$reg & 0x0F) + $DUMMY_F) & 0x10) << 1) | ((($regs{A} + $$reg + $DUMMY_F) & 0x100 ) >> 4) | (($regs{A} = ($regs{A} + $$reg + $DUMMY_F))) == 0 ? 0x80 : 0; } while (1) { $ticks++; my $opcode = substr($rom, $regs{PC}, 1); if ($opcode == 0x00) { } # NOP # elsif ($opcode != 0x00) { printf(" 0x%.2lX", $opcode); } elsif ($opcode == 0x01) { $regs{BC} = $regs{PC}; $regs{PC}+=2; } elsif ($opcode == 0x02) { $regs{BC} = $regs{A}; } # LD (BC),A elsif ($opcode == 0x03) { $regs{BC}++; } # INC BC elsif ($opcode == 0x04) { $regs{B}++; } # INC B elsif ($opcode == 0x05) { $regs{B}--; } # DEC B elsif ($opcode == 0x06) { $regs{B} = substr($rom, ++$regs{PC}, 2); $regs{PC}++; } # LD B,N elsif ($opcode == 0x07) { &RCLA(); } # RLCA elsif ($opcode == 0x08) { # LD (NN),SP $rom[$regs{PC}] = $regs{SP}; $rom[++$regs{PC}] = $regs{SP}; } elsif ($opcode == 0x09) { $regs{HL} += $regs{BC}; } # ADD HL,BC elsif ($opcode == 0x0A) { $regs{A} = $regs{BC}; } # LD A,(BC) elsif ($opcode == 0x0B) { $regs{BC}--; } # DEC BC elsif ($opcode == 0x0C) { $regs{C}++; } # INC C elsif ($opcode == 0x0D) { $regs{C}--; } # DEC C elsif ($opcode == 0x0E) { $regs{C} = substr($rom, ++$regs{PC}, 1); } # LD C,N elsif ($opcode == 0x0F) { &RRCA(); } # RRCA elsif ($opcode == 0x10) { print "0x10 (STOP) unimplimented\n"; } # STOP elsif ($opcode == 0x11) { # LD DE,NN $regs{DE} = substr($rom, ++$regs{PC}, 1); $regs{PE} += 2; if ($regs{DE} == 0x1B58) { if (substr($rom, $regs{PC}, 1).substr($rom, $regs{PC}+2, 1) == 0) { $regs{DE} = 0x0001; } } } elsif ($opcode == 0x12) { $regs{DE} = $regs{A}; } # LD (DE),A elsif ($opcode == 0x13) { $regs{DE}++; } # INC DE elsif ($opcode == 0x14) { $regs{D}++; } # INC D elsif ($opcode == 0x15) { $regs{D}--; } # DEC D elsif ($opcode == 0x16) { $regs{D} = substr($rom, ++$regs{PC}, 1); } # LD D,N elsif ($opcode == 0x17) { &RLA(); } # RLA elsif ($opcode == 0x18) { $regs{PC} = substr($rom, ++$regs{PC}, 1); } # JR N elsif ($opcode == 0x19) { $regs{HL} = $regs{DE}; } # ADD HL,DE elsif ($opcode == 0x1A) { $regs{A} = $regs{DE}; } # LD A,(DE) elsif ($opcode == 0x1B) { $regs{DE}--; } # DEC DE elsif ($opcode == 0x1C) { $regs{E}++; } # INC E elsif ($opcode == 0x1D) { $regs{E}--; } # DEC E elsif ($opcode == 0x1E) { $regs{E} = substr($rom, ++$regs{PC}, 1); } # LD E,N elsif ($opcode == 0x1F) { &RRA(); } # RRA elsif ($opcode == 0x20) { # JR NZ,N if (($regs{F} & 0x80) == 0) { $regs{PC} = substr($rom, ++$regs{PC}, 1); } else { $regs{PC}++ } } elsif ($opcode == 0x21) { $regs{HL} = substr($rom, ++$regs{PC}, 2); $regs{PC}++; } # LD HL,NN elsif ($opcode == 0x22) { $regs{HL}++; $regs{HL} = $regs{A}; } # LDI (HL),A elsif ($opcode == 0x23) { $regs{HL}++; } # INC HL elsif ($opcode == 0x24) { $regs{H}++; } # INC H elsif ($opcode == 0x25) { $regs{H}--; } # DEC H elsif ($opcode == 0x26) { $regs{H} = substr($rom, ++$regs{PC}, 1); } # LD H,N elsif ($opcode == 0x27) { &DAA(); } # DAA elsif ($opcode == 0x28) { # JR Z,N if (($regs{F} & 0x80) != 0) { print "JR Z,N\n"; } # impliment else { $regs{PC}++; } } elsif ($opcode == 0x29) { $regs{HL} += $regs{HL}; } # ADD HL,HL elsif ($opcode == 0x2A) { $regs{A}=++$regs{HL}; } # LDI A,(HL) elsif ($opcode == 0x2B) { $regs{HL}--; } # DEC HL elsif ($opcode == 0x2C) { $regs{L}++; } # INC L elsif ($opcode == 0x2D) { $regs{L}--; } # DEC L elsif ($opcode == 0x2E) { $regs{L} = substr($rom, ++$regs{PC}, 1); } # LD L,N elsif ($opcode == 0x2F) { # CPL $regs{A} ^= 0xFF; $regs{F} = ($reg{F} & (0x80 | 0x10)) | 0x40 | 0x20; print "CPL: possibly misimpliented\n"; } elsif ($opcode == 0x30) { # JR NC,N if (($regs{F} & 0x10) == 0) { print "JR NC, N\n"; } else { $regs{PC}++; } } elsif ($opcode == 0x31) { $regs{SP} = substr($rom, ++$regs{PC}, 2); # LD SP,NN $regs{PC}++; } elsif ($opcode == 0x32) { $regs{HL}--; $regs{HL}=$regs{A}; } # LDD (HL),A elsif ($opcode == 0x33) { $regs{SP}++; } # INC SP elsif ($opcode == 0x34) { $regs{HL}++; } # INC (HL) elsif ($opcode == 0x35) { $regs{HL}--; } # DEC (HL) elsif ($opcode == 0x36) { $regs{HL} = substr($rom, ++$regs{PC}, 1); } # LD (HL),N elsif ($opcode == 0x37) { $regs{F} = ($regs{F} & 0x80) | 0x10; } elsif ($opcode == 0x38) { # JR C,N if (($regs{F} & 0x10) != 0) { print "JR C,N\n"; } else { $regs{SP}++; } } elsif ($opcode == 0x39) { $regs{HL} += $regs{SP}; } # ADD HL,SP elsif ($opcode == 0x3A) { $regs{A} = substr($rom, --$regs{HL}, 1); } # LDD A,(HL) elsif ($opcode == 0x3B) { $regs{SP}--; } # DEC SP elsif ($opcode == 0x3C) { $regs{A}++; } # INC A elsif ($opcode == 0x3D) { $regs{A}--; } # DEC A elsif ($opcode == 0x3E) { $regs{A} = substr($rom, ++$regs{PC}, 1); } # LD A,N elsif ($opcode == 0x3F) { # CCF $regs{F} = ($regs{F} ^ 0x10) & (0x10 | 0x80); print "CCF: possibly misplimented\n"; } elsif ($opcode == 0x40) { $regs{B} = $regs{B}; } # LD B,B elsif ($opcode == 0x41) { $regs{B} = $regs{C}; } # LD B,C elsif ($opcode == 0x42) { $regs{B} = $regs{D}; } # LD B,D elsif ($opcode == 0x43) { $regs{B} = $regs{E}; } # LD B,E elsif ($opcode == 0x44) { $regs{B} = $regs{H}; } # LD B,H elsif ($opcode == 0x45) { $regs{B} = $regs{L}; } # LD B,L elsif ($opcode == 0x46) { $regs{B} = substr($rom, $regs{HL}, 1); } # LD B,(HL) elsif ($opcode == 0x47) { $regs{B} = $regs{A}; } # LD B,A elsif ($opcode == 0x48) { $regs{C} = $regs{B}; } # LD C,B elsif ($opcode == 0x49) { $regs{C} = $regs{C}; } # LD C,C elsif ($opcode == 0x4A) { $regs{C} = $regs{D}; } # LD C,D elsif ($opcode == 0x4B) { $regs{C} = $regs{E}; } # LD C,E elsif ($opcode == 0x4C) { $regs{C} = $regs{H}; } # LD C,H elsif ($opcode == 0x4D) { $regs{C} = $regs{L}; } # LD C,L elsif ($opcode == 0x4E) { $regs{C} = substr($rom, $regs{HL}, 1); } # LD C,(HL) elsif ($opcode == 0x4F) { $regs{C} = $regs{A}; } # LD C,A elsif ($opcode == 0x50) { $regs{D} = $regs{B}; } # LD D,B elsif ($opcode == 0x51) { $regs{D} = $regs{C}; } # LD D,C elsif ($opcode == 0x52) { $regs{D} = $regs{D}; } # LD D,D elsif ($opcode == 0x53) { $regs{D} = $regs{E}; } # LD D,E elsif ($opcode == 0x54) { $regs{D} = $regs{H}; } # LD D,H elsif ($opcode == 0x55) { $regs{D} = $regs{L}; } # LD D,L elsif ($opcode == 0x56) { $regs{D} = substr($rom, $regs{HL}, 1); } # LD D,(HL) elsif ($opcode == 0x57) { $regs{D} = $regs{A}; } # LD D,A elsif ($opcode == 0x58) { $regs{E} = $regs{B}; } # LD E,B elsif ($opcode == 0x59) { $regs{E} = $regs{C}; } # LD E,C elsif ($opcode == 0x5A) { $regs{E} = $regs{D}; } # LD E,D elsif ($opcode == 0x5B) { $regs{E} = $regs{E}; } # LD E,E elsif ($opcode == 0x5C) { $regs{E} = $regs{H}; } # LD E,H elsif ($opcode == 0x5D) { $regs{E} = $regs{L}; } # LD E,L elsif ($opcode == 0x5E) { $regs{E} = substr($rom, $regs{HL}, 1); } # LD E,(HL) elsif ($opcode == 0x5F) { $regs{E} = $regs{A}; } # LD E,A elsif ($opcode == 0x60) { $regs{H} = $regs{B}; } # LD H,B elsif ($opcode == 0x61) { $regs{H} = $regs{C}; } # LD H,C elsif ($opcode == 0x62) { $regs{H} = $regs{D}; } # LD H,D elsif ($opcode == 0x63) { $regs{H} = $regs{E}; } # LD H,E elsif ($opcode == 0x64) { $regs{H} = $regs{H}; } # LD H,H elsif ($opcode == 0x65) { $regs{H} = $regs{L}; } # LD H,L elsif ($opcode == 0x66) { $regs{H} = substr($rom, $regs{HL}, 1); } # LD H,(HL) elsif ($opcode == 0x67) { $regs{H} = $regs{A}; } # LD H,A elsif ($opcode == 0x68) { $regs{L} = $regs{B}; } # LD L,B elsif ($opcode == 0x69) { $regs{L} = $regs{C}; } # LD L,C elsif ($opcode == 0x6A) { $regs{L} = $regs{D}; } # LD L,D elsif ($opcode == 0x6B) { $regs{L} = $regs{E}; } # LD L,E elsif ($opcode == 0x6C) { $regs{L} = $regs{H}; } # LD L,H elsif ($opcode == 0x6D) { $regs{L} = $regs{L}; } # LD L,L elsif ($opcode == 0x6E) { $regs{L} = substr($rom, $regs{HL}, 1); } # LD L,(HL) elsif ($opcode == 0x6F) { $regs{L} = $regs{A}; } # LD L,A elsif ($opcode == 0x70) { # LD (HL),B $rom[$regs{HL}] = $regs{B}; $rom[++$regs{HL}] = $regs{B}; } elsif ($opcode == 0x71) { # LD (HL),C $rom[$regs{HL}] = $regs{C}; $rom[++$regs{HL}] = $regs{C}; } elsif ($opcode == 0x72) { # LD (HL),D $rom[$regs{HL}] = $regs{D}; $rom[++$regs{HL}] = $regs{D}; } elsif ($opcode == 0x73) { # LD (HL),E $rom[$regs{HL}] = $regs{E}; $rom[++$regs{HL}] = $regs{E}; } elsif ($opcode == 0x74) { # LD (HL),H $rom[$regs{HL}] = $regs{H}; $rom[++$regs{HL}] = $regs{H}; } elsif ($opcode == 0x75) { # LD (HL),L $rom[$regs{HL}] = $regs{L}; $rom[++$regs{HL}] = $regs{L}; } elsif ($opcode == 0x76) { $cpu_running = 0; safe_reg_dump(); } # HALT elsif ($opcode == 0x77) { # LD (HL),A $rom[$regs{HL}] = $regs{A}; $rom[++$regs{HL}] = $regs{A}; } elsif ($opcode == 0x78) { $regs{A} = $regs{B}; } # LD A,B elsif ($opcode == 0x79) { $regs{A} = $regs{C}; } # LD A,C elsif ($opcode == 0x7A) { $regs{A} = $regs{D}; } # LD A,D elsif ($opcode == 0x7B) { $regs{A} = $regs{E}; } # LD A,E elsif ($opcode == 0x7C) { $regs{A} = $regs{H}; } # LD A,H elsif ($opcode == 0x7D) { $regs{A} = $regs{L}; } # LD A,L elsif ($opcode == 0x7E) { $regs{A} = substr($rom, $regs{HL}, 1); } # LD A,(HL) elsif ($opcode == 0x7F) { $regs{A} = $regs{A}; } # LD A,A elsif ($opcode == 0x80) { $regs{A} += $regs{B} } # ADD A,B elsif ($opcode == 0x81) { $regs{A} += $regs{C}; } # ADD A,C elsif ($opcode == 0x82) { $regs{A} += $regs{D}; } # ADD A,D elsif ($opcode == 0x83) { $regs{A} += $regs{E}; } # ADD A,E elsif ($opcode == 0x84) { $regs{A} += $regs{H}; } # ADD A,H elsif ($opcode == 0x85) { $regs{A} += $regs{L}; } # ADD A,L elsif ($opcode == 0x86) { $regs{A} += substr($rom, ++$regs{HL}, 1); } # ADD A,(HL) elsif ($opcode == 0x87) { $regs{A} += $regs{A}; } # ADD A,A elsif ($opcode == 0x88) { ADC(\$regs{B}); } # ADC A,B elsif ($opcode == 0x89) { ADC(\$regs{C}); } # ADC A,C elsif ($opcode == 0x8A) { ADC(\$regs{D}); } # ADC A,D elsif ($opcode == 0x8B) { ADC(\$regs{E}); } # ADC A,E elsif ($opcode == 0x8C) { ADC(\$regs{H}); } # ADC A,H elsif ($opcode == 0x8D) { ADC(\$regs{L}); } # ADC A,L elsif ($opcode == 0x8E) { ADC(\$regs{HL}); } # ADC A,(HL) elsif ($opcode == 0x8F) { ADC(\$regs{A}); } # ADC A,A else { printf("Invalid opcode 0x%.2lX at $regs{PC}\n", $opcode); dump_regs(); } $regs{PC}++; $secs = time()-$starttime; $clock = ($ticks/$secs)/256; # print "\rMHz: ".sprintf("%.3f", $clock)."/$CLK_GBC ($ticks)"; #sleep(1) if ($clock > $CLK_GBC && is_spacebar_down()); } sub dump_regs { $endttime = time(); printf("\n8-bit registers\n"); printf("A: 0x%.2lX B: 0x%.2lX C: 0x%.2lX D: 0x%.2lX\n", $regs{A}, $regs{B}, $regs{C}, $regs{D}); printf("E: 0x%.2lX F: 0x%.2lX H: 0x%.2lX L: 0x%.2lX\n", $regs{E}, $regs{F}, $regs{H}, $regs{L}); print "16-bit registers\n"; printf("AF: 0x%.4lX BC: 0x%.4lX DE: 0x%.4lX HL: 0x%.4lX\n", $regs{AF}, $regs{BC}, $regs{DE}, $regs{HL}); printf("Stack pointer: 0x%.4lX Program counter: 0x%.4lX Cycles: %d\n", $regs{SP}, $regs{PC}, $ticks); $secs = $endttime-$starttime; $mhz = ($ticks/$secs)/256; printf("Average CPU speed: %dMHz (%dGHz)\n", int($mhz), int($mhz/1024)); exit(1); # Reset registers in case we want to restart $regs{A} = 0x00; $regs{B} = 0x00; $regs{C} = 0x00; $regs{D} = 0x00; $regs{E} = 0x00; $regs{F} = 0x00; $regs{H} = 0x00; $regs{L} = 0x00; # 16-bit registers $regs{AF} = 0x0000; $regs{BC} = 0x0000; $regs{DE} = 0x0000; $regs{HL} = 0x0000; # Special registers $regs{PC} = 0x100; # Program counter $regs{SP} = 0xFFFE; # Stack pointer } sub safe_reg_dump { $endttime = time(); printf("\n8-bit registers\n"); printf("A: 0x%.2lX B: 0x%.2lX C: 0x%.2lX D: 0x%.2lX\n", $regs{A}, $regs{B}, $regs{C}, $regs{D}); printf("E: 0x%.2lX F: 0x%.2lX H: 0x%.2lX L: 0x%.2lX\n", $regs{E}, $regs{F}, $regs{H}, $regs{L}); print "16-bit registers\n"; printf("AF: 0x%.4lX BC: 0x%.4lX DE: 0x%.4lX HL: 0x%.4lX\n", $regs{AF}, $regs{BC}, $regs{DE}, $regs{HL}); printf("Stack pointer: 0x%.4lX Program counter: 0x%.4lX Cycles: %d\n", $regs{SP}, $regs{PC}, $ticks); $secs = $endttime-$starttime; $mhz = ($ticks/$secs); printf("Average CPU speed: %dMHz (%dGHz)\n", int($mhz), int($mhz/1024)); }