Version 0.2:
// Project: GameBoy
// Created: 2021-07-16
// visit thread at: https://forum.thegamecreators.com/thread/227727#msg2665310
// set AGK window properties
SetWindowTitle( "AGK GameBoy 0.2" )
SetWindowSize( 1024, 768, 0 )
SetWindowAllowResize( 1 ) // allow the user to resize the window
SetVirtualResolution( 1024, 768 ) // doesn't have to match the window
SetOrientationAllowed( 1, 1, 1, 1 ) // allow both portrait and landscape on mobile devices
SetSyncRate( 60, 0 ) // 30fps instead of 60 to save battery
SetScissor( 0,0,0,0 ) // use the maximum available screen space, no black borders
UseNewDefaultFonts( 0 ) // since version 2.0.22 we can use nicer default fonts
SetErrorMode(2) // show all errors
SetPrintSize(16)
/*
Sources:
https://github.com/gbdev/awesome-gbdev
https://mattbruv.github.io/gameboy-crust/
http://bgb.bircd.org/pandocs.htm
https://www.linkedin.com/pulse/creating-gameboy-emulator-part-1-bruno-croci/
https://bgb.bircd.org/pandocs.htm#lcdcontrolregister
https://github.com/bouk/gameboy-emu/blob/master/lr35902/lr35902.go
Cartrige Memory Layout: ( Source: https://www.youtube.com/watch?v=gYQMdox5gzI )
0x4000 - 0x7FFF Switchable ROM Bank ( BANK X )
0x0000 - 0x3FFF ROM (BANK 0)
Main Memory Layout:
0xFFFF Interrupt Enable Flag
0xFF80 - 0xFFFE Zero Page 127 bytes
0xFF00 - 0xFF7F Hardware I/O Registers
0xFEA0 - 0xFeFF Unusable Memory
0xFE00 - 0xFE9F OAM Object Attribute Memory
0xE000 - 0xFDFF Echo RAM - Reserved Do not use
0xD000 - 0xDFFF Internal RAM Bank 1-7 ( switchable - CGB only)
0xC000 - 0xCFFF Internal RAM Bank 0 (fixed)
0xA000 - 0xBFFF Cartridge RAM (If Available)
0x9C00 - 0x9FFF BG Map Data 2
0x9800 - 0x9BFF BG Map data 1
0x8000 - 0x97FF Character RAM
0x4000 - 0x7FFF Cartridge ROM Switchable Banks 1-xx
0x0150 - 0x3FFF Cartridge ROM - Bank 0 (fixed) |
0x0100 - 0x014F Cartridge Herader Area |- Copied from game cartridge
0x0000 - 0x00FF Restart and Interrupt Vectors |
Boot Process:
1. copy bios to address 0x0 - 0x100
2.
....
*/
// AGK USER INPUT KEYS (Controls)
#CONSTANT KEY_LEFT 37 // LEFT
#CONSTANT KEY_UP 38 // UP
#CONSTANT KEY_RIGHT 39 // RIGHT
#CONSTANT KEY_DOWN 40 // DOWN
#CONSTANT KEY_NUMPAD_0 96 // A
#CONSTANT KEY_NUMPAD_3 99 // B
#CONSTANT KEY_SHIFT 16 // SELECT
#CONSTANT KEY_CONTROL 17 // START
// gameboy constants
#CONSTANT GameBoy_ResX = 160
#CONSTANT GameBoy_ResY = 144
#CONSTANT GameBoy_MemorySize = 0xFFFF // 65535 bytes
#CONSTANT Default_Cartridge = "Tetris.gb" // default debug cart
// temporary gameboy globals
Global Gameboy_ScreenOffsetX = 0x0
Global Gameboy_ScreenOffsetY = 0x0
// gameboy bios code
Dim BIOS_ARRAY[0x100] = [
0x31, 0xFE, 0xFF, 0xAF, 0x21, 0xFF, 0x9F, 0x32, 0xCB, 0x7C, 0x20, 0xFB, 0x21, 0x26, 0xFF, 0x0E,
0x11, 0x3E, 0x80, 0x32, 0xE2, 0x0C, 0x3E, 0xF3, 0xE2, 0x32, 0x3E, 0x77, 0x77, 0x3E, 0xFC, 0xE0,
0x47, 0x11, 0x04, 0x01, 0x21, 0x10, 0x80, 0x1A, 0xCD, 0x95, 0x00, 0xCD, 0x96, 0x00, 0x13, 0x7B,
0xFE, 0x34, 0x20, 0xF3, 0x11, 0xD8, 0x00, 0x06, 0x08, 0x1A, 0x13, 0x22, 0x23, 0x05, 0x20, 0xF9,
0x3E, 0x19, 0xEA, 0x10, 0x99, 0x21, 0x2F, 0x99, 0x0E, 0x0C, 0x3D, 0x28, 0x08, 0x32, 0x0D, 0x20,
0xF9, 0x2E, 0x0F, 0x18, 0xF3, 0x67, 0x3E, 0x64, 0x57, 0xE0, 0x42, 0x3E, 0x91, 0xE0, 0x40, 0x04,
0x1E, 0x02, 0x0E, 0x0C, 0xF0, 0x44, 0xFE, 0x90, 0x20, 0xFA, 0x0D, 0x20, 0xF7, 0x1D, 0x20, 0xF2,
0x0E, 0x13, 0x24, 0x7C, 0x1E, 0x83, 0xFE, 0x62, 0x28, 0x06, 0x1E, 0xC1, 0xFE, 0x64, 0x20, 0x06,
0x7B, 0xE2, 0x0C, 0x3E, 0x87, 0xF2, 0xF0, 0x42, 0x90, 0xE0, 0x42, 0x15, 0x20, 0xD2, 0x05, 0x20,
0x4F, 0x16, 0x20, 0x18, 0xCB, 0x4F, 0x06, 0x04, 0xC5, 0xCB, 0x11, 0x17, 0xC1, 0xCB, 0x11, 0x17,
0x05, 0x20, 0xF5, 0x22, 0x23, 0x22, 0x23, 0xC9, 0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B,
0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E,
0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99, 0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC,
0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E, 0x3c, 0x42, 0xB9, 0xA5, 0xB9, 0xA5, 0x42, 0x4C,
0x21, 0x04, 0x01, 0x11, 0xA8, 0x00, 0x1A, 0x13, 0xBE, 0x20, 0xFE, 0x23, 0x7D, 0xFE, 0x34, 0x20,
0xF5, 0x06, 0x19, 0x78, 0x86, 0x23, 0x05, 0x20, 0xFB, 0x86, 0x20, 0xFE, 0x3E, 0x01, 0xE0, 0x50 ]
// define main gameboy registers
/*
This is where the problems actually begin. It may be smarter
to group a and f to AF 'two bytes integer' and others aswell
*/
type registers_def
a as integer // 1 byte accumulator
b as integer // 1 byte
c as integer // 1 byte
d as integer // 1 byte
e as integer // 1 byte
h as integer // 1 byte
l as integer // 1 byte
flags as integer // 1 byte flags
sp as integer // 2 bytes Stack Pointer It points to a special area in memory called the stack
pc as integer // 2 bytes Program Counter It stands for program counter and it’s actually a pointer to the next instruction in memory. And that’s how the CPU knows where to fetch the next operation to be executed.
endtype
// define gameboy cpu
type CPU_def
Registers as registers_def
m as integer // Clock for last instruction // https://github.com/mattrubin/Gambit/blob/master/CPU.h
t as integer
Stopped as integer // bool https://github.com/bouk/gameboy-emu/blob/master/lr35902/lr35902.go
Halted as integer // bool
finished_bios as integer // BIOS will store the 256 bytes startup code. When the gameboy starts, the MMU will map it in the first 256 bytes of the memory, so that’s why we need the finished_bios flag.
endtype
// define gameboy cartridge
type CARTRIDGE_def
mem as integer // memblock containing whole cartridge
// header data source https://gbdev.io/pandocs/The_Cartridge_Header.html
EntryPoint as integer // 0x100-0x103
Logo as integer[] // 0x104-0x133
title$ as String // 0x134-0x143
ManufacturerCode$ as string // 0x13F-0x142
CGBFlag as integer // 0x143
NewLicenseeCode$ as string // 0x144-0x145
SGBFlag as integer // 0x146
CartridgeType as integer // 0x147
ROMSize as integer // 0x148
RAMSize as integer // 0x149
DestinationCode as integer // 0x14A
OldLicenseeCode as integer // 0x14B
MaskROMVersionnumber as integer // 0x14C
HeaderChecksum as integer // 0x14D
GlobalChecksum as integer // 0x14E-0x14F
endtype
Global CPU as CPU_def
Global RAM as integer
Global CARTRIDGE as CARTRIDGE_Def
CPU.Stopped = 1
// create debug buttons
imgBup = CreateImageColor(55,55,55,255)
imgBdown = CreateImageColor(155,155,155,255)
CreateButton(1,490,50,"Reset",imgBup,imgBdown) // resets the cpu to default state
CreateButton(2,490,140,"Run",imgBup,imgBdown) // starts / stops cpu
CreateButton(3,400,50,"Load",imgBup,imgBdown) // loads default cartrige
global AGKBUTTON_START as integer
global AGKBUTTON_SELECT as integer
global AGKBUTTON_A as integer
global AGKBUTTON_B as integer
global AGKBUTTON_UP as integer
global AGKBUTTON_DOWN as integer
global AGKBUTTON_LEFT as integer
global AGKBUTTON_RIGHT as integer
AGKBUTTON_START = CreateScreenText( "Start [CTRL]",540,460)
AGKBUTTON_SELECT = CreateScreenText("Select [Rshift]",540,472)
AGKBUTTON_A = CreateScreenText("A - [Num0]",700,460)
AGKBUTTON_B = CreateScreenText("B - [Num3]",700,472)
AGKBUTTON_UP = CreateScreenText("UP",860,460)
AGKBUTTON_DOWN = CreateScreenText("DOWN",860,472)
AGKBUTTON_LEFT = CreateScreenText("LEFT",960,460)
AGKBUTTON_RIGHT = CreateScreenText("RIGHT",960,472)
// for sake of debugging
// create tileset buffer
BufferDat as integer[1]
// create gameboy display screen buffer 160x144 pixel
Global ScreenBufferIMG as integer
Global ScreenBufferSPR as integer
BufferDat = CreateRenderBuffer("Gameboy Screen Buffer 160x144", 160,144,GetVirtualWidth()-480-5,25,3)
ScreenBufferIMG = BufferDat[0]
ScreenBufferSPR = BufferDat[1]
// create complete screen buffer 256x256 pixel
Global FullScreenBufferIMG as integer
Global FullScreenBufferSPR as integer
BufferDat = CreateRenderBuffer("Full Screen Buffer 256x256", 256,256,GetVirtualWidth()-256*3.1,GetVirtualHeight()-256,1)
FullScreenBufferIMG = BufferDat[0]
FullScreenBufferSPR = BufferDat[1]
// create tileset 1 buffer 256x256 pixel
Global TilesetBufferIMG as integer
Global titlebufspr as integer
BufferDat = CreateRenderBuffer("BANK (0)", 256,256,GetVirtualWidth()-256*2,GetVirtualHeight()-256,16)
TilesetBufferIMG = BufferDat[0]
titlebufspr = BufferDat[1]
// create tileset 2 buffer 256x256 pixel
Global TilesetBuffer2IMG as integer
Global titlebuf2spr as integer
BufferDat = CreateRenderBuffer("BANK (X)", 256,256,GetVirtualWidth()-256*1,GetVirtualHeight()-256,16)
TilesetBuffer2IMG = BufferDat[0]
titlebuf2spr = BufferDat[1]
// create tile buffer 8x8 pixel
Global TileBufferIMG as integer
BufferDat = CreateRenderBuffer("tile", 8,8,GetVirtualWidth()-960,GetVirtualHeight()-8*16,16)
TileBufferIMG = BufferDat[0]
// start gameboy
RAM = CreateMemblock( GameBoy_MemorySize ) // create ram memblock
CopyArrayToMemblock( BIOS_ARRAY,RAM,0,0,0x100) // copy bios into ram adress: 0x0 - 0x100
finished_bios = 0xFF50 // set finished bios flag ( questionable )
// Debug Dump RAM for hex editor inspection
/*
SetRawWritePath(GetReadPath())
CreateFileFromMemblock("ramdump.hex",RAM) // dump complete raw ram for debug
*/
Global GetTime# as float
// Main loop
do
Gameboy_ScreenOffsetX = Gameboy_ScreenOffsetX+ GetRawMouseWheelDelta()
if not CPU.Stopped then GetTime# = timer()
print("FPS: "+str(ScreenFPS(),2))
print("Runtime: "+str(GetTime# ,2))
Print("")
Print("CPU:")
Print("====")
Print("Program Counter: 0x"+ hex(CPU.Registers.pc) )
Print("Stack Pointer: 0x"+ hex(CPU.Registers.sp) )
Print("")
if (GetMemblockExists(CARTRIDGE.mem))
Print("Cartridge Header:")
Print("=================")
PrintCartridgeInfo()
endif
HandleInput()
if not CPU.Stopped then RunCPU()
UpdateScreen()
HandleVirtualButtons()
HandleVirtualBuffers()
Render2DFront()
swap()
loop
function HandleInput()
SetTextColor(AGKBUTTON_START,255,255,255,255)
SetTextColor(AGKBUTTON_SELECT,255,255,255,255)
SetTextColor(AGKBUTTON_A,255,255,255,255)
SetTextColor(AGKBUTTON_B,255,255,255,255)
SetTextColor(AGKBUTTON_UP,255,255,255,255)
SetTextColor(AGKBUTTON_DOWN,255,255,255,255)
SetTextColor(AGKBUTTON_LEFT,255,255,255,255)
SetTextColor(AGKBUTTON_RIGHT,255,255,255,255)
if GetRawKeyState(KEY_CONTROL)
SetTextColor(AGKBUTTON_START,255,128,0,255)
endif
if GetRawKeyState(KEY_SHIFT)
SetTextColor(AGKBUTTON_SELECT,255,128,0,255)
endif
if GetRawKeyState(KEY_NUMPAD_0)
SetTextColor(AGKBUTTON_A,255,128,0,255)
endif
if GetRawKeyState(KEY_NUMPAD_3)
SetTextColor(AGKBUTTON_B,255,128,0,255)
endif
if GetRawKeyState(KEY_UP)
SetTextColor(AGKBUTTON_UP,255,128,0,255)
endif
if GetRawKeyState(KEY_DOWN)
SetTextColor(AGKBUTTON_DOWN,255,128,0,255)
endif
if GetRawKeyState(KEY_LEFT)
SetTextColor(AGKBUTTON_LEFT,255,128,0,255)
endif
if GetRawKeyState(KEY_RIGHT)
SetTextColor(AGKBUTTON_RIGHT,255,128,0,255)
endif
endfunction
// helps to show a bigger preview on mouse hover over tilesets
function HandleVirtualBuffers()
hit = GetSpriteHit(getrawmousex(),getrawmousey())
if titlebufspr = hit
SetSpriteScale(titlebufspr,2.2,2.2)
SetSpritePosition(titlebufspr,GetVirtualWidth()-GetSpriteWidth(titlebufspr),GetVirtualHeight()-GetSpriteHeight(titlebufspr))
SetSpriteDepth(titlebufspr,0)
else
SetSpriteDepth(titlebufspr,1)
SetSpriteScale(titlebufspr,1,1)
px = GetVirtualWidth()-GetSpriteWidth(titlebufspr)*2.05-5
py = GetVirtualHeight()-GetSpriteHeight(titlebufspr)-5
SetSpritePosition(titlebufspr,px,py)
endif
if titlebuf2spr = hit
SetSpriteScale(titlebuf2spr,2.2,2.2)
SetSpritePosition(titlebuf2spr,GetVirtualWidth()-GetSpriteWidth(titlebuf2spr),GetVirtualHeight()-GetSpriteHeight(titlebuf2spr))
SetSpriteDepth(titlebuf2spr,0)
else
SetSpriteDepth(titlebuf2spr,1)
SetSpriteScale(titlebuf2spr,1,1)
px = GetVirtualWidth()-GetSpriteWidth(titlebuf2spr)*1.0-5
py = GetVirtualHeight()-GetSpriteHeight(titlebuf2spr)-5
SetSpritePosition(titlebuf2spr,px,py)
endif
endfunction
// function that renders a 16 byte tile from a specific position in RAM memory into the tile buffer
function SetTileBuffer(mempos)
ox = GetVirtualWidth()
oy = GetVirtualHeight()
SetRenderToImage(TileBufferIMG,0) // render to image buffer
SetVirtualResolution(8,8)
SetClearColor(0,50,0)
ClearScreen()
// draw
DrawTileHex(mempos, 0,0)
SetRenderToScreen() // go back to screen
SetClearColor(0,0,0)
SetVirtualResolution(ox,oy)
endfunction
function DrawTileHex(mempos, spx,spy)
px = spx
py = spy
for byte = 0 to 15 step 2 // two bytes per tile row
for pixel = 0 to 7
// aquire pixel color (2bpp)
c = 255-( (((GetMemblockByte(RAM, mempos+byte+1) >> (7-pixel)) && 0x01) << 1)+ ((GetMemblockByte(RAM, mempos+byte) >> (7-pixel)) && 0x01))*(256/3)
color = MakeColor(c,c,c)
DrawLine(px,py,px+1,py+1,color,color)
inc px
if px-spx>7
px=spx
inc py
endif
next pixel
next byte
endfunction
function GetCartridgeTileMap( bufferimage, startadress, endadressadress)
ox = GetVirtualWidth()
oy = GetVirtualHeight()
SetRenderToImage(bufferimage,0) // set render to image buffer
SetVirtualResolution(256,256)
SetClearColor(0,50,0)
ClearScreen()
// draw tile map
px = 0
py = 0
for tilespos = startadress to endadressadress step 16
DrawTileHex( tilespos , px,py)
inc px,8
if px>8*32
px=0
inc py,8
endif
next tilespos
SetRenderToScreen() // go back to screen buffer
SetClearColor(0,0,0)
SetVirtualResolution(ox,oy)
endfunction
function UpdateScreen()
UpdateFullScreenBuffer() // update whole 256x256 FullScreenBuffer
UpdateScreenBuffer( Gameboy_ScreenOffsetX,Gameboy_ScreenOffsetY) // copies portion of FullScreenBuffer to ScreenBuffer with a certain offset
endfunction
function UpdateFullScreenBuffer()
// display "camera"
colorred = MakeColor(255,0,0)
sx = Gameboy_ScreenOffsetX
sy = Gameboy_ScreenOffsetY
ox = GetVirtualWidth()
oy = GetVirtualHeight()
SetRenderToImage(FullScreenBufferIMG,0) // render to screen buffer
SetVirtualResolution(256,256)
SetClearColor(0,50,0)
ClearScreen()
// create screen fuzzle in for performance testing
for y=0 to 256-1
for x=0 to 256-1
// update each pixel with a random color
cval = random(0,255)
color = MakeColor(cval,cval,cval)
DrawLine(x,y,x+1,y+1,color,color)
next x
next y
DrawBox(sx,sy,sx+160-1,sy+144-1,colorred,colorred,colorred,colorred,0)
// go back to screen
SetRenderToScreen()
SetClearColor(0,0,0)
SetVirtualResolution(ox,oy)
endfunction
function UpdateScreenBuffer(OffsetX,OffsetY)
ox = GetVirtualWidth()
oy = GetVirtualHeight()
SetRenderToImage(ScreenBufferIMG,0) // render to screen buffer
SetVirtualResolution(GameBoy_ResX,GameBoy_ResY)
SetClearColor(0,50,0)
ClearScreen()
// draw (right now I stick to buffer pasting for convenience
// Its Cheating! Later we try to create this from portion of RAM
spr = CreateSprite(FullScreenBufferIMG)
SetSpritePosition(spr,-OffsetX,-OffsetY)
DrawSprite(spr)
DeleteSprite(spr)
// add a debug tiles above screen fuzzle, just render some from cartridge memory
if GetMemblockExists(CARTRIDGE.mem)
for y=0 to 17 step 2
for x = 0 to 19 step 2
if y<4 or y>17-4
DrawTileHex(0x37A1, 8*x,8*y)
endif
next x
next y
endif
// go back to screen
SetRenderToScreen()
SetClearColor(0,0,0)
SetVirtualResolution(ox,oy)
endfunction
function RunCPU()
ReadPC = CPU.Registers.pc // aquire current cprogram counter
opcode = read8_incPC( ReadPC ) // read opcode
select opcode // cpu opcode state machine
case 0x0
// NOP
endcase
case 0x21 // LD HL,u16 - 0x21
CPU.Registers.h = read8_incPC( CPU.Registers.pc )
CPU.Registers.l = read8_incPC( CPU.Registers.pc )
endcase
/* 12. LDD (HL), A */
/* Description: Put A into memory address HL. Decrement HL */
/* Same as: LD (HL), A - DEC HL */
case 0x32 // LD (HL-),A - 0x32
CPU.Registers.h = 0x0
CPU.Registers.l = CPU.Registers.a
hl = ((CPU.Registers.h << 8) || (CPU.Registers.l && 0xFF))&& 0xFFFF
endcase
case 0x76
// HALT
endcase
// ....
case 0xC6
value1 = read8_incPC( CPU.Registers.pc + 1 ) // wrong?? already added??
endcase
case 0x31 // LD SP,u16 - 0x31
CPU.Registers.sp = read16_incPC( CPU.Registers.pc )
endcase
/* 7. XOR n */
/* Description: */
/* Logically exclusive OR n with A, result in A. */
/* Use with: */
/* n = A, B, C, D, E, H, L, (HL), # */
/* Flags affected: */
/* Z - Set if result is zero. */
/* N - Reset. */
/* H - Reset. */
/* C - Reset. */
case 0xAF // XOR A
CPU.Registers.a = CPU.Registers.a ~~ CPU.Registers.a
endcase
case default // Debug Error message
Message("Invaild opcode: 0x"+hex(opcode)+" at adress: 0x"+hex(ReadPC)+chr(10)+"Insert missing opcode in RunCPU() function.") // drop a debug message if there are problems
CPU.Stopped = 1
endcase
endselect
endfunction
function ResetCPU()
newcpu as CPU_def
CPU = newcpu
CPU.Stopped = 1
CopyArrayToMemblock( BIOS_ARRAY,RAM,0,0,0x100) // copy bios into ram adress: 0x0 - 0x100
ResetTimer()
GetTime# = 0.0
endfunction
// cpu reading and writing functions
// =================================
/*
WARNING AGK GetMemblockShort may have little / big endian issues! Maybe bytes have to be flipped
*/
// 8 bit
function read8( pos )
value = GetMemblockByte(RAM,pos)
endfunction value
function read8_incPC( pos ) // incPC variation - increase program counter aswell, lets see if this is more used than the simple one
value = GetMemblockByte(RAM,pos)
CPU.Registers.pc = CPU.Registers.pc + 1
endfunction value
function write8( pos, data )
SetMemblockByte(RAM,pos,data)
endfunction
function write8_incPC( pos, data ) // increase program counter aswell
SetMemblockByte(RAM,pos,data)
CPU.Registers.pc = CPU.Registers.pc + 1
endfunction
// 16 bit
function read16( pos )
value = GetMemblockShort(RAM,pos)
value = ((value && 0xFF) << 8) || ((value && 0xFF00) >> 8) // 16 bit change endian FFAA => AAFF
endfunction value
function read16_incPC( pos ) // increase program counter aswell
value = GetMemblockShort(RAM,pos)
value = ((value && 0xFF) << 8) || ((value && 0xFF00) >> 8) // 16 bit change endian FFAA => AAFF
CPU.Registers.pc = CPU.Registers.pc + 2
endfunction value
function write16( pos, data )
data = ((data && 0xFF) << 8) || ((data && 0xFF00) >> 8) // 16 bit change endian FFAA => AAFF
SetMemblockShort(RAM,pos,data)
endfunction
function write16_incPC( pos, data ) // increase program counter aswell
data = ((data && 0xFF) << 8) || ((data && 0xFF00) >> 8) // 16 bit change endian FFAA => AAFF
SetMemblockShort(RAM,pos,data)
CPU.Registers.pc = CPU.Registers.pc + 2
endfunction
/*
Cartridge manangement functions
===============================
*/
// loads a file into cardtrige memblock, then aquires some header data
function LoadCartridge( file$ )
CARTRIDGE.mem = CreateMemblockFromFile( file$ ) // load file into memblock
CARTRIDGE.EntryPoint = ( GetMemblockByte(CARTRIDGE.mem, 0x103) || (GetMemblockByte(CARTRIDGE.mem, 0x102) << 8) || (GetMemblockByte(CARTRIDGE.mem, 0x101) << 16) || (GetMemblockByte(CARTRIDGE.mem, 0x100) << 24) )
for mempos = 0x104 to 0x133
CARTRIDGE.Logo.insert(GetMemblockByte(CARTRIDGE.mem,mempos))
next mempos
CARTRIDGE.title$ = GetMemblockString(CARTRIDGE.mem, 0x134, 16 )
CARTRIDGE.ManufacturerCode$ = GetMemblockString(CARTRIDGE.mem, 0x013F, 4 ) // 0x13F-0x142
CARTRIDGE.CGBFlag = GetMemblockByte(CARTRIDGE.mem,0x143) && 0xFF
CARTRIDGE.NewLicenseeCode$ = GetMemblockString(CARTRIDGE.mem, 0x144, 2 ) // 0x144-0x145
CARTRIDGE.SGBFlag = GetMemblockByte(CARTRIDGE.mem,0x146) && 0xFF
CARTRIDGE.CartridgeType = GetMemblockByte(CARTRIDGE.mem,0x147) && 0xFF
CARTRIDGE.ROMSize = GetMemblockByte(CARTRIDGE.mem,0x148) && 0xFF
CARTRIDGE.RAMSize = GetMemblockByte(CARTRIDGE.mem,0x149) && 0xFF
CARTRIDGE.DestinationCode = GetMemblockByte(CARTRIDGE.mem,0x14A) && 0xFF
CARTRIDGE.OldLicenseeCode = GetMemblockByte(CARTRIDGE.mem,0x14B) && 0xFF
CARTRIDGE.MaskROMVersionnumber = GetMemblockByte(CARTRIDGE.mem,0x14C) && 0xFF
CARTRIDGE.HeaderChecksum = GetMemblockByte(CARTRIDGE.mem,0x14D) && 0xFF
CARTRIDGE.GlobalChecksum = ( GetMemblockByte(CARTRIDGE.mem, 0x14F) || (GetMemblockByte(CARTRIDGE.mem, 0x14E) << 8) ) // 0x14E-0x14F
endfunction
function ResetCartridge()
if GetMemblockExists(CARTRIDGE.mem) then DeleteMemblock(CARTRIDGE.mem)
newcard as CARTRIDGE_def
CARTRIDGE = newcard
endfunction
// just print some cartridge data to screen
function PrintCartridgeInfo()
Print("EntryPoint: 0x"+hex(CARTRIDGE.EntryPoint))
logo$=chr(10)
for i=0 to CARTRIDGE.Logo.length
nl$=""
if mod(i,16)=15 and i<CARTRIDGE.Logo.length then nl$=chr(10)
value$ = hex(CARTRIDGE.Logo[i])
if len(value$)<2 then value$="0"+value$
logo$=logo$+""+upper(value$)+" "+nl$
next i
Print("Logo: "+logo$)
Print("Title$: '"+CARTRIDGE.title$+"'")
Print("ManufacturerCode$: '"+CARTRIDGE.ManufacturerCode$+"'")
Print("CGBFlag: 0x"+hex(CARTRIDGE.CGBFlag && 0xFF))
Print("NewLicenseeCode$: '"+CARTRIDGE.NewLicenseeCode$+"'")
Print("SGBFlag: 0x"+hex(CARTRIDGE.SGBFlag && 0xFF))
Print("CartridgeType: 0x"+hex(CARTRIDGE.CartridgeType && 0xFF))
Print("ROMSize: 0x"+hex(CARTRIDGE.ROMSize && 0xFF))
Print("RAMSize: 0x"+hex(CARTRIDGE.RAMSize && 0xFF))
Print("DestinationCode: 0x"+hex(CARTRIDGE.DestinationCode))
Print("OldLicenseeCode: 0x"+hex(CARTRIDGE.OldLicenseeCode && 0xFF))
Print("MaskROMVersionnumber: 0x"+hex(CARTRIDGE.MaskROMVersionnumber && 0xFF))
Print("HeaderChecksum: 0x"+hex(CARTRIDGE.HeaderChecksum && 0xFF))
Print("GlobalChecksum: 0x"+hex(CARTRIDGE.GlobalChecksum && 0xFFFF))
endfunction
/*
Misc functions, helpers
=======================
*/
// create a virtual button with specific colors
function CreateButton(num,px,py,text$,imgup,imgdown)
AddVirtualButton(num,px,py,60)
SetVirtualButtonText(num,text$)
SetVirtualButtonSize(num,80,60)
SetVirtualButtonImageUp(num,imgup)
SetVirtualButtonImageDown(num,imgdown)
endfunction
// a function to copy an integer array into a memblock,each int in array represents a byte value
function CopyArrayToMemblock( srcArray as integer[],dstMem,srcOffset,dstOffset,size ) // copy bios into ram
for i = srcOffset to srcOffset + size-1
dstpos = dstOffset + i-srcOffset
SetMemblockByte(dstMem,dstpos,srcArray[i])
next i
endfunction
// create screen text (debug)
function CreateScreenText(text$,px,py)
txt = CreateText(text$)
SetTextSize(txt,16)
SetTextPosition(txt,px,py)
endfunction txt
// debug helper function
function HandleVirtualButtons()
// reset
if GetVirtualButtonPressed(1)
ResetCPU()
ResetCartridge()
endif
// stop / run functionality
if GetVirtualButtonPressed(2)
CPU.Stopped = 1- CPU.Stopped
endif
if CPU.Stopped
SetVirtualButtonText(2,"Run")
else
SetVirtualButtonText(2,"Stop")
endif
// load default cartrige and update tilemap buffers
if GetVirtualButtonPressed(3)
ResetCartridge()
// load game file
LoadCartridge(Default_Cartridge)
CopyMemblock( CARTRIDGE.mem,ram,0,0, 0x8000 ) // copy 0x8000 bytes to ram - (will work for 16kb games?)
GetCartridgeTileMap(TilesetBufferIMG,0x150,0x3FFF) // display some cartridge memory (improve values) BANK 0 STATIC
GetCartridgeTileMap(TilesetBuffer2IMG,0x4000,0x7FFF) // display some cartridge memory (improve values) BANK X CHANGEABLE
SetTileBuffer(0x37A1) // just display some tile
endif
endfunction
// creates a render buffer and a sprite to render it aswell as some title text
function CreateRenderBuffer(name$, sx,sy,px,py,scale#)
arr as integer[1] // array in order to output multiple references
text = CreateScreenText(name$,px,py-20)
image = CreateRenderImage( sx, sy, 0,0)
sprite = CreateSprite(image)
SetSpriteScale(sprite,scale#,scale#)
SetImageMinFilter(image,0)
SetImageMagFilter(image,0)
SetSpritePosition(sprite,px,py)
arr[0] = image
arr[1] = sprite
endfunction arr
notes on code:
I have unified the buffer creation process to a single function and added a simple Interface for all gameboy buttons.
Some functions where renamed.
The biggest change is the new 256x256 pixel full screen buffer as mentioned in the gameboy specifications and
the change from tileset buffers to the actual complete bank (0) and bank (X) of the cardrige, including tilesets.
As I thought, per pixel control of a 256x256 pixel buffer will drop the fps under 60. But this is right now no problem at all. the gameboy has no per pixel control just something like a primitive fullscreen shader.
This version is a good base to start any 8 bit emulator project, as it has all common implementations like input / output and processing.