Posted: 20th Jul 2021 22:47
My previous and first emulation project was a Chip8 that run quite well based
on the unclear instruction set that a chip8 has. It was actually quite fun,
and teached me the basics of emulation. Seeing games come to life on an own
emulator was a powerful experience.

If you do not know where to start, check out this wonderful AppGameKit Chip8 emulator made by Cliff Mellangard:
https://forum.thegamecreators.com/thread/217664



This project is a chance to get a deeper glimpse into emulation.



Project Idea:
- It has to be fun
- Creating a working and portable gameboy emulator in AppGameKit Tier 1
- Audio Processing would be nice to have

Project Paradigms:
- single main.agc file only
- no external media (besides cartridge bytecode)
- keep it as near as possible to the original hardware (pixel rendering is okay but no tile to agk sprite draw call improvements)
- No Plugins, it should run out of the box
- Implement aquired knowlege first, thoughts second

Sources:
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
https://www.huderlem.com/demos/gameboy2bpp.html





Version 0.1:
+ Code Snippet
// Project: GameBoy 
// Created: 2021-07-16



// set window properties
SetWindowTitle( "GameBoy" )
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(18)

/*

Sources:

	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








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. 

*/


// gameboy constants
#Constant GameBoy_ResX = 160
#Constant GameBoy_ResY = 144
#Constant GameBoy_MemorySize = 0xFFFF	// 65535 bytes

#Constant Default_Cartridge = "Tetris.gb"	// default debug cart



// 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.
	// virtual registers:
	/*
		af		// 2 bytes
		bc		// 2 bytes
		de		// 2 bytes
		hl		// 2 bytes
	*/
	
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
	
	// source https://gbdev.io/pandocs/The_Cartridge_Header.html
	
	// header data
	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 screen buffer
Global SCREENBUFFERMEM as integer
Global ScreenBufferIMG as integer
SCREENBUFFERMEM = CreateMemblock(GameBoy_ResX*GameBoy_ResY)
ScreenBufferIMG = CreateRenderImage( GameBoy_ResX, GameBoy_ResY, 0,0)
SetImageMinFilter(ScreenBufferIMG,0)
SetImageMagFilter(ScreenBufferIMG,0)
screenbufspr = CreateSprite( ScreenBufferIMG )	// create debug renderview
SetSpritePosition(screenbufspr,GetVirtualWidth()-GetSpriteWidth(screenbufspr)*3-5,25)
SetSpriteScale(screenbufspr,3,3)
CreateScreenText("160x144 Display",getspritex(screenbufspr),getspritey(screenbufspr)-20)






imgBup = CreateImageColor(55,55,55,255)
imgBdown = CreateImageColor(155,155,155,255)

// create debug buttons

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,380,50,"Load",imgBup,imgBdown)		// loads default cartrige

CreateScreenText("Start - [CTRL]"+chr(10)+"Select - [Rshift]",540,460)


CreateScreenText("A - [Num0]"+chr(10)+"B - [Num3]",740,460)

CreateScreenText("Arrowkeys",880,460)


// for sake of debugging
// create tileset buffer
global titlebufspr
global titlebuf2spr

Global TilesetBufferIMG as integer
TilesetBufferIMG = CreateRenderImage( GameBoy_ResX, GameBoy_ResY, 0,0)
titlebufspr = CreateSprite(TilesetBufferIMG)
SetImageMinFilter(TilesetBufferIMG,0)
SetImageMagFilter(TilesetBufferIMG,0)
px = GetVirtualWidth()-GetSpriteWidth(titlebufspr)*2.05-5
py = GetVirtualHeight()-GetSpriteHeight(titlebufspr)-5
SetSpritePosition(titlebufspr,px,py)
CreateScreenText("tilemap (1)",px,py-20)

// create tileset 2 buffer
Global TilesetBuffer2IMG as integer
TilesetBuffer2IMG = CreateRenderImage( GameBoy_ResX, GameBoy_ResY, 0,0)
titlebuf2spr = CreateSprite(TilesetBuffer2IMG)
SetImageMinFilter(TilesetBuffer2IMG,0)
SetImageMagFilter(TilesetBuffer2IMG,0)
px = GetVirtualWidth()-GetSpriteWidth(titlebuf2spr)*1.0-5
py = GetVirtualHeight()-GetSpriteHeight(titlebuf2spr)-5
SetSpritePosition(titlebuf2spr,px,py)
CreateScreenText("tilemap (2)",px,py-20)


// create tile buffer
Global TileBufferIMG as integer
TileBufferIMG = CreateRenderImage( 8, 8, 0,0)
tilebufspr = CreateSprite(TileBufferIMG)
SetSpriteScale(tilebufspr,16,16)
SetImageMinFilter(TileBufferIMG,0)
SetImageMagFilter(TileBufferIMG,0)
px = GetVirtualWidth()-500
py = GetVirtualHeight()-GetSpriteHeight(tilebufspr)-5
SetSpritePosition(tilebufspr,px,py)
CreateScreenText("tile",px,py-20)







// 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 )


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
endfunction






// Dim testtile[15] as integer = [0xFF, 0x00, 0x7E, 0xFF, 0x85, 0x81, 0x89, 0x83, 0x93, 0x85, 0xA5, 0x8B, 0xC9, 0x97, 0x7E, 0xFF]
SetTileBuffer(0x37A1)




/*
SetRawWritePath(GetReadPath())
CreateFileFromMemblock("ramdump.hex",RAM) // dump complete raw ram for debug
*/












Global GetTime# as float


do
	GetTime# = timer()*10

	print("FPS: "+str(ScreenFPS(),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:")
		Print("==========")
		PrintCartridgeInfo()
	endif
	
	if not CPU.Stopped then UpdateGameBoy()
	UpdateScreen()
	
	HandleVirtualButtons()
	HandleVirtualBuffers()

	Render2DFront()
	Render2DBack()
	swap()
    // Sync()
loop










// helps to show a bigger preview on mouse hover
function HandleVirtualBuffers()
	
hit = GetSpriteHit(getrawmousex(),getrawmousey())

	if titlebufspr = hit

		SetSpriteScale(titlebufspr,4,4)
		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,4,4)
		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 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,0x3200,0x4800) // display some cartridge memory (improve values)
	GetCartridgeTileMap(TilesetBuffer2IMG,0x4800,0x6000) // display some cartridge memory (improve values)
	SetTileBuffer(0x37A1) // just display some tile
endif


endfunction


function SetTileBuffer(mempos)

	ox = GetVirtualWidth()
	oy = GetVirtualHeight()

	SetRenderToImage(TileBufferIMG,0)
	SetVirtualResolution(8,8)

	// render to screen buffer
	SetClearColor(0,50,0)
	ClearScreen()
	
	DrawTileHex(mempos, 0,0)
	

		
	/*
	for y=0 to 7
	for x=0 to 7
		
		
	cval = 255
		color = MakeColor(cval,cval,cval) 
		DrawLine(x,y,x+1,y+1,color,color)
		
		
	next x
	next y
	*/
	
	
	// go back to screen
	SetRenderToScreen()
	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 color
				c = 255-( (((GetMemblockByte(RAM, mempos+byte+1) >> (7-pixel))  && 0x01) << 1)+ ((GetMemblockByte(RAM, mempos+byte) >> (7-pixel)) && 0x01))*(256/3) // lower
				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)
	SetVirtualResolution(GameBoy_ResX,GameBoy_ResY)

	// render to screen buffer
	SetClearColor(0,50,0)
	ClearScreen()
	

	px = 0
	py = 0
	for tilespos = startadress to endadressadress step 16
		
		

		
		DrawTileHex( tilespos , px,py)
		
		inc px,8
		if px>8*19
			px=0
			inc py,8
		endif
		
		
		
	next tilespos
	
	/*
	for y=0 to GameBoy_ResY-1
	for x=0 to GameBoy_ResX-1
		
		
		
		// update each pixel with a random two bit color

		SetMemblockByte(SCREENBUFFERMEM,x*(y+1),random(0,255))
		
		cval = GetMemblockByte(SCREENBUFFERMEM,x*(y+1))
		
		color = MakeColor(cval,cval,cval) 
		
		
		DrawLine(x,y,x+1,y+1,color,color)
	next x
	next y
	*/

	
	// go back to screen
	SetRenderToScreen()
	SetClearColor(0,0,0)
	SetVirtualResolution(ox,oy)
endfunction





function UpdateScreen()

	ox = GetVirtualWidth()
	oy = GetVirtualHeight()

	SetRenderToImage(ScreenBufferIMG,0)
	SetVirtualResolution(GameBoy_ResX,GameBoy_ResY)

	// render to screen buffer
	SetClearColor(0,50,0)
	ClearScreen()
	
	
	// create screen fuzzle in for performance testing
	for y=0 to GameBoy_ResY-1
	for x=0 to GameBoy_ResX-1
		// update each pixel with a random color
		SetMemblockByte(SCREENBUFFERMEM,x*(y+1),random(0,255))		
		cval = GetMemblockByte(SCREENBUFFERMEM,x*(y+1))
		color = MakeColor(cval,cval,cval) 
		DrawLine(x,y,x+1,y+1,color,color)
	next x
	next y
	
	
	// add a debug tiles above screen fuzzle, just render some from cartridge memory
	if GetMemblockExists(CARTRIDGE.mem)
		
		for y=0 to 17
		 for x = 0 to 19
			 if y<2 or y>17-2
			   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 UpdateGameBoy()


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 UpdateGameBoy() function.") // drop a debug message if there are problems
		CPU.Stopped = 1
	endcase
	
endselect



/*
// debug update screen
for y=1 to GameBoy_ResY
	for x=1 to GameBoy_ResX
	color = MakeColor(random(0,sin(GetTime#*3)*255),random(0,cos(GetTime#*2)*255),random(0,sin(GetTime#)*255)) // aquire random pixel
		DrawBox(x*PixelWidth+ScreenX,y*PixelWidth+ScreenY,x*PixelWidth+PixelWidth+ScreenX,y*PixelWidth+PixelWidth+ScreenY,color,color,color,color,1)
	next x
next y
*/


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.Logo = 				 [0,0,1,1,2]					// 0x104-0x133
	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 ) // ( GetMemblockByte(CARTRIDGE.mem, 0x145) || (GetMemblockByte(CARTRIDGE.mem, 0x144) << 8)  )			// 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
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




comment on the code:
The first code sets a basic structure based on the gameboy specifications, aswell
as some early screen buffer rendering stages. I have aswell implemented a 2bpp renderer labeled as "tile", "tileset 1" and "tileset 2"
There are three screen buttons in order to control the cpu (reset; run/stop) and a button to load the default cartridge from the /media/ folder.
There is a lot of debug code and there might be some wrong values in the tileset.
But all in all a basic emulation layout even with some basic opcode examples.
I do not expect this project to get way bigger, as we can already read and write 8 or 16 bytes to or from memory abd chunks of memory can already be drawn to a screen buffer, even if I don't have a clue right now how the screen update works on the gameboy exactly.

I've used a file called "Tetris.gb" inside the /media/ folder, representing a backup copy of the Tetris cardrige, but there are some fun public domain games, that may be used in later development stages.



Errors/ WIP:
- Gameboy has a screen size of 160x144 pixel but a virtual draw buffer of 256x256 pixel. The screen buffer of 160x144 should be a chunk of the 256x256 pixel buffer, and this can be used as a virtual camera offset aswell.
- Actually no real opcodes are implemented right now, as there might be a change in the "CPU registers" functionality. It may be even smart to create a memblock for this registers, as opcodes require a decision, no work was done on this side.
- A random tile is selected and drawn multiple times on screen in order to demonstrate rendering 2bpp 8x8 pixel gameboy tile system




Any help is appreciated as I am learning aswell
Posted: 21st Jul 2021 11:59


Version 0.2:
+ Code Snippet
// 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.
Posted: 25th Jul 2021 14:18
[size=large]0xCB Opcode Table[/size]

After implementing the first opcodes, I realized the huge problem of implementing about 512 opcodes - it would take some time.
So I thought there may be a logical algorythm that can sort out the opcodes faster and reduce them to some single functions.

In order to move on, I decided to test this idea on the second 256 opcode table the gameboy has, (that can be accessed by a 0xCB byte Prefix, and can be used on any opcode)

You can see this opcode table on this image:

Or: Check out the second table on this side, the Prefix CB table in cyan:
https://www.pastraiser.com/cpu/gameboy/gameboy_opcodes.html


This table consists of 256 opcodes that are based on just eleven operations, but with diffrent registers, that are repeated in an uniform order. ( B C D E H L (HL) A - two times in a row)
By careful inspection, there is some fun thing to notice. After x7, the function type changes. It looks like there is a x0 - x7 function "left" side and a x8 - xF function "right" side.

So Ive split the opcode (0x13 for example) in two nibbles.

1. the operation nibble 0x1 (represents function RL, when operand bigger than 0x7 it would represent RRC)
2. the operand nibble 0x3 ( represents register E)




I've made a seperate program shell to demonstrate the simple, yet time saving algorithm:
It does even display the operation and operand as a human readable string, in order to understand quicker.

+ Code Snippet
// Project: GB_0xCB_Codes 
// All 256 commands from the Prefix CB Opcode table (Source: https://www.pastraiser.com/cpu/gameboy/gameboy_opcodes.html)
// Created: 2021-07-24
// show all errors
SetErrorMode(2)

// set window properties
SetWindowTitle( "GB_0xCB_Codes" )
SetWindowSize( 1024, 768, 0 )
SetWindowAllowResize( 1 ) // allow the user to resize the window

// set display properties
SetVirtualResolution( 1024, 768 ) // doesn't have to match the window
SetOrientationAllowed( 1, 1, 1, 1 ) // allow both portrait and landscape on mobile devices
SetSyncRate( 30, 0 ) // 30fps instead of 60 to save battery
SetScissor( 0,0,0,0 ) // use the maximum available screen space, no black borders
UseNewDefaultFonts( 1 ) // since version 2.0.22 we can use nicer default fonts


opcode = 0x13	// default opcode

Global operands as string[0x7] = ["B","C","D","E","H","L","(HL)","A"] // repeated twice so actually 0xF
Global operationLeft as string[0xF] =  ["RLC","RL","SLA","SWAP","BIT 0","BIT 2","BIT 4","BIT 6","RES 0","RES 2","RES 4","RES 6","SET 0","SET 2","SET 4","SET 6"]
Global operationRight as string[0xF] = ["RRC","RR","SRA", "SRL","BIT 1","BIT 3","BIT 5","BIT 7","RES 1","RES 3","RES 5","RES 7","SET 1","SET 3","SET 5","SET 7"]


do
	Print("Gameboy 0xCB Opcode parser")
	Print("Use mousewheel to change opcode")
	Print("")
	
	inc opcode,GetRawMouseWheelDelta()/3 // control opcode with mousewheel
	FenchCB(opcode)
	
    Sync()
loop



function FenchCB(opcode)
	
	// aquire operand and operation from opcode nibbles
	operand = opcode && 0xF
	operation = (opcode >> 4) && 0xF
	
	
	if operand>0x7 // checks side on the opcode table, each side has different commands
		// RIGHT SIDE	
		// ["RRC","RR","SRA", "SRL","BIT 1","BIT 3","BIT 5","BIT 7","RES 1","RES 3","RES 5","RES 7","SET 1","SET 3","SET 5","SET 7"]
		select operation
			case 0x0
				CB_RRC( operand )
			endcase
			case 0x1
				CB_RR( operand )
			endcase
			case 0x2
				CB_SRA( operand )
			endcase
			case 0x3
				CB_SRL( operand )
			endcase
			case 0x4
				CB_BIT( 1, operand )
			endcase
			case 0x5
				CB_BIT( 3, operand ) 
			endcase
			case 0x6
				CB_BIT( 5, operand )
			endcase
			case 0x7
				CB_BIT( 7, operand )
			endcase
			case 0x8
				CB_RES( 1, operand )
			endcase
			case 0x9
				CB_RES( 3, operand )
			endcase
			case 0xA
				CB_RES( 5, operand )
			endcase
			case 0xB
				CB_RES( 7, operand )
			endcase
			case 0xC
				CB_SET( 1, operand )
			endcase
			case 0xD
				CB_SET( 3, operand )
			endcase
			case 0xE
				CB_SET( 5, operand )
			endcase
			case 0xF
				CB_SET( 7, operand )
			endcase
			
		endselect	
	
	
	else
		// LEFT SIDE
		// ["RLC","RL","SLA","SWAP","BIT 0","BIT 2","BIT 4","BIT 6","RES 0","RES 2","RES 4","RES 6","SET 0","SET 2","SET 4","SET 6"]
		select operation
			case 0x0
				CB_RLC( operand )
			endcase
			case 0x1
				CB_RL( operand )
			endcase
			case 0x2
				CB_SLA( operand )
			endcase
			case 0x3
				CB_SWAP( operand )
			endcase
			case 0x4
				CB_BIT( 0, operand )
			endcase
			case 0x5
				CB_BIT( 2, operand ) 
			endcase
			case 0x6
				CB_BIT( 4, operand )
			endcase
			case 0x7
				CB_BIT( 6, operand )
			endcase
			case 0x8
				CB_RES( 0, operand )
			endcase
			case 0x9
				CB_RES( 2, operand )
			endcase
			case 0xA
				CB_RES( 4, operand )
			endcase
			case 0xB
				CB_RES( 6, operand )
			endcase
			case 0xC
				CB_SET( 0, operand )
			endcase
			case 0xD
				CB_SET( 2, operand )
			endcase
			case 0xE
				CB_SET( 4, operand )
			endcase
			case 0xF
				CB_SET( 6, operand )
			endcase
			
		endselect
	
	
	endif	
	
	
	
	
	// aquire a string representation aswell
	operand$ = operands[ mod(operand,0x8) ]
	operation$ = operationLeft[ operation ]

	// 0xCB opcodes operands do repeat themself including diffrent operations
	if operand>0x7 then operation$ = operationRight[operation] // right side operations

	// print debug data
	Print("Opcode: 0x"+hex(opcode && 0xFFFF))
	Print("Operation: 0x"+hex(operation)+"  "+bin(operation))
	Print("Operand: 0x"+hex(operand)+"  "+bin(operand))
	Print("")
	Print("OP: "+operation$+" "+operand$)
endfunction



/*
	CPU 0xCB 'extended' function set, also known as "not the z80 part of the dmg chip"
	==================================================================================
	
	CB_RLC(n as integer)
	CB_RRC(n as integer)
	CB_SET( bit as integer, r as integer )
	CB_RES( bit as integer, r as integer )
	CB_BIT( bit as integer, r as integer )
	CB_SWAP(n as integer)
	CB_SRL(n as integer)
	CB_SLA(n as integer)
	CB_SRA(n as integer)
	CB_RL(n as integer)
	CB_RR(n as integer)
	
*/
function CB_RLC(n as integer)
/*
 Description:
  Rotate n left. Old bit 7 to Carry flag.
 Use with:    
  n = A,B,C,D,E,H,L,(HL)
 Flags affected:
  Z - Set if result is zero.
  N - Reset.
  H - Reset.
  C - Contains old bit 7 data
*/
endfunction

function CB_RRC(n as integer)
/*
Description:
  Rotate n right. Old bit 0 to Carry flag.
 
 Use with:    
  n = A,B,C,D,E,H,L,(HL)
 Flags affected:
  Z - Set if result is zero.
  N - Reset.
  H - Reset.
  C - Contains old bit 0 data
*/
endfunction


function CB_SET( bit as integer, r as integer )
/*
Description:
  Set bit b in register r.
 Use with:    
  b = 0 - 7, r = A,B,C,D,E,H,L,(HL)
 Flags affected:
  None.
*/	
endfunction

function CB_RES( bit as integer, r as integer )
/*
Description:
  Reset bit b in register r.
 Use with:    
  b = 0 - 7, r = A,B,C,D,E,H,L,(HL)
 Flags affected:
  None.
*/	
endfunction

function CB_BIT( bit as integer, r as integer )
/*
 Description:
  Test bit b in register r.
 Use with:    
  b = 0 - 7, r = A,B,C,D,E,H,L,(HL)
 Flags affected:
  Z - Set if bit b of register r is 0.
  N - Reset.
  H - Set.
  C - Not affected.
*/		
endfunction

function CB_SWAP(n as integer)
/*
Description:
  Swap upper & lower nibles of n.
 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.
*/
endfunction

function CB_SRL(n as integer)
/*
 Description:
  Shift n right into Carry. MSB set to 0.
 Use with:    
  n = A,B,C,D,E,H,L,(HL)
 Flags affected:
  Z - Set if result is zero.
  N - Reset.
  H - Reset.
  C - Contains old bit 0 data.
*/
// n = (n >> 1) && 0xFF
endfunction

function CB_SLA(n as integer)
/*
Description:
  Shift n left into Carry. LSB of n set to 0.
 Use with:    
  n = A,B,C,D,E,H,L,(HL)
 Flags affected:
  Z - Set if result is zero.
  N - Reset.
  H - Reset.
  C - Contains old bit 7 data
*/	
endfunction

function CB_SRA(n as integer)
/*
Description:
  Shift n right into Carry. MSB doesn't change.
 Use with:    
  n = A,B,C,D,E,H,L,(HL)
 Flags affected:
  Z - Set if result is zero.
  N - Reset.
  H - Reset.
  C - Contains old bit 0 data
*/
endfunction
	
function CB_RL(n as integer)
/*
Description:
  Rotate n left through Carry flag.
 Use with:    
  n = A,B,C,D,E,H,L,(HL)
 Flags affected:
  Z - Set if result is zero.
  N - Reset.
  H - Reset.
  C - Contains old bit 7 data.	
*/
endfunction
	
function CB_RR(n as integer)
/*
Description:
  Rotate n right through Carry flag.
 Use with:    
  n = A,B,C,D,E,H,L,(HL)
 Flags affected:
  Z - Set if result is zero.
  N - Reset.
  H - Reset.
  C - Contains old bit 0 data.
*/

endfunction



Be aware, that CPU timing is not represented at all and the
resulting cpu instruction per function structure will require a function that sets a value to a register based on "n"

There may be better approaches, as some functions are mirrored (left shift / right shift) but this method is capable of solving 256 opcodes in about 256 lines of code, so I am fine with the result,
but I am thankful for each suggestion in order to recude some line of codes, as long as it is not using out of "basic" scope commands



[size=large]Default Opcode Table[/size]

Once I did this to the 0xCB opcode table, it was time to look for a similar solution for the default z80 opcode table. The approach to just select/case all 256 opcodes, as I've done in the beginning, is too bare bone.
After some time spend for research, I found this, it was what I've been looking for:


Source: http://goldencrystal.free.fr/GBZ80Opcodes.pdf

The information presented in this list allowed us to reduce 256 opcodes to just 57 CPU functions
Well at least I am way better off implementing all 57 functions and fenching / parsing them like the 0xCB ones.
Posted: 22nd Aug 2021 3:51


this is way beyond light afternoon tea coding for which i feel AppGameKit is extremely well suited on days with excessive sunlight, but hella-cool . I always felt like it would be awesome to come up with a virtual maching like a pico8, grant it a virtual circuitboard, memory limit, cpu with specially designed set of opcodes and then allow cartridges of set sizes to be entered to see how far people can push it. C64 nostalgia ... getting to the doing is another thing, this is awesome