1
0

public release of panzgb

This commit is contained in:
2025-11-24 13:37:49 +01:00
commit d08fbfe1aa
18 changed files with 3827 additions and 0 deletions

2
.gitignore vendored Executable file
View File

@@ -0,0 +1,2 @@
.idea
build/

22
CMakeLists.txt Normal file
View File

@@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 3.7)
project(panzgb)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED True)
# Make static library with pangb core
ADD_LIBRARY( panzgb-core STATIC
lib/gb-memory.c
lib/gb-opcodes.c
lib/gb-opcodes-impl.c
lib/gb-sound.c
lib/gb-video.c
lib/gc-imp.c
lib/gc-interrupts.c
lib/mbc1.c
lib/mbc3.c )
# PC clients
find_package(SDL2 REQUIRED)
include_directories(panzgb ${SDL2_INCLUDE_DIRS})
add_executable(panzgb panzgb.c)
target_link_libraries(panzgb ${SDL2_LIBRARIES} panzgb-core)

21
LICENSE Executable file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015-2025 panzone
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

25
README.md Executable file
View File

@@ -0,0 +1,25 @@
# panzgb
panzgb is an emulator for the Gameboy system.
## Build
The PC client requires SDL2 installed on the system. Then panzgb uses cmake for building
```
mkdir build && cd build
cmake ..
make
```
## Run
```
./panzgb ROM-FILE
```
## What is working
- Basic support for running games
- Support for MBC1 and MBC3 games
Most notably, the sound system is currently not working. Also, this is a pure Gameboy emulator and it doesn't support Gameboy Color games.

180
lib/gb-impl.h Executable file
View File

@@ -0,0 +1,180 @@
#ifndef GB_IMPL_H
#define GB_IMPL_H
#include "panzgb.h"
#define NAME_CART 0x134
#define CARTRIDGE_SIZE 0x200000
#define MEMORY_SIZE 0x10000
#define KEYMAP_ADDR 0xFF00
#define DIVIDER_TIMER 0xFF04
#define TIMER_ADDR 0xFF05
#define TIMER_DEFAULT 0xFF06
#define TIMER_CONTROLLER 0xFF07
#define INTERRUPT_ENABLED_ADDR 0xFFFF
#define INTERRUPT_REQUEST_ADDR 0xFF0F
#define LCD_SCANLINE_ADRR 0xFF44
#define LCD_REG_CONTROL 0xFF40
#define LCD_REG_STATUS 0xFF41
#define DMA_ADRR 0xFF46
#define SCROLLX 0xFF43
#define SCROLLY 0xFF42
#define WINDOWY 0xFF4A
#define WINDOWX 0xFF4B
#define getAF(cpu) ((cpu->A << 8) | (cpu->F))
#define getBC(cpu) ((cpu->B << 8) | (cpu->C))
#define getDE(cpu) ((cpu->D << 8) | (cpu->E))
#define getHL(cpu) ((cpu->H << 8) | (cpu->L))
#define SET_ZFLAG(cpu) (cpu->F |= 0x80)
#define SET_NFLAG(cpu) (cpu->F |= 0x40)
#define SET_HFLAG(cpu) (cpu->F |= 0x20)
#define SET_CFLAG(cpu) (cpu->F |= 0x10)
#define RESET_ZFLAG(cpu) (cpu->F &= ~(0x80))
#define RESET_NFLAG(cpu) (cpu->F &= ~(0x40))
#define RESET_HFLAG(cpu) (cpu->F &= ~(0x20))
#define RESET_CFLAG(cpu) (cpu->F &= ~(0x10))
#define PUSH(cpu, base, l) \
do { \
writeMemory(cpu, base - 2, l & 0xFF); \
writeMemory(cpu, base - 1, (l >> 8) & 0xFF); \
base -= 2; \
} while (0);
#define POP(cpu, base, l) \
do { \
BYTE _low = readMemory(cpu, base); \
l = (readMemory(cpu, base + 1) << 8) | _low; \
base += 2; \
} while (0);
#define GET_BYTE_PC(cpu, l) \
do { \
l = readMemory(cpu, cpu->progCounter); \
cpu->progCounter++; \
} while (0);
#define GET_WORD_PC(cpu, l) \
do { \
l = readMemory(cpu, cpu->progCounter); \
cpu->progCounter++; \
BYTE _hi = readMemory(cpu, cpu->progCounter); \
cpu->progCounter++; \
l = (_hi << 8) | l; \
} while (0);
struct audio_channel {
int pos;
int period_clock;
int length_timer;
int env_vol;
int env_dir;
int env_sweep_pace;
};
struct gameboy {
BYTE cartridge[CARTRIDGE_SIZE];
BYTE memory[MEMORY_SIZE];
// Now the CPU registers
WORD progCounter;
WORD stack;
BYTE A;
BYTE B;
BYTE C;
BYTE D;
BYTE E;
BYTE F;
BYTE H;
BYTE L;
/*This part represent the memory banking system*/
BYTE ROMType; /*This variable represents the ROM type MBCx*/
WORD currentROMBank;
BYTE ROMBankType;
BYTE RAMBank[0x10000]; /*This should contains all the RAM banks*/
BYTE currentRAMBank;
BYTE isRAMEnable;
/*This part helps handling the timers*/
int clockBeforeTimer;
int clockBeforeDividerTimer;
/*This is the master interrupt switch*/
BYTE master_interr_switch;
BYTE whenDisableInterrupts;
BYTE enable_interr;
/*This area is for handle the LCD timing*/
int clockScanline;
/*This is the screen*/
BYTE screenData[144][160];
BYTE cpuHalted;
BYTE keymap;
void (*changeBank)(gb *, WORD, BYTE);
struct audio_channel channel_1;
struct audio_channel channel_2;
struct audio_channel channel_3;
struct audio_channel channel_4;
BYTE soundData[2048];
WORD numerOfBytes;
SIGNED_WORD leftSoundBuffer, rightSoundBuffer;
int soundMasterClock;
int soundFrameClock;
int soundFramePos;
};
BYTE readMemory(gb *cpu, WORD addr);
void writeMemory(gb *cpu, WORD addr, BYTE data);
void increaseTimer(gb *cpu, BYTE clocks);
void setTimerFreq(gb *cpu);
void raiseInterrupt(gb *cpu, BYTE code);
void handleInterrupts(gb *cpu);
void handleGraphic(gb *cpu, BYTE cycles);
void DMATransfert(gb *cpu, BYTE data);
void mbc1_changeBank(gb *cpu, WORD addr, BYTE data);
void mbc3_changeBank(gb *cpu, WORD addr, BYTE data);
BYTE getKeypad(gb *cpu);
BYTE executeOpcode(gb *cpu, BYTE opcode);
unsigned int extendedOpcodes(gb *cpu, BYTE opcode);
void writeSaveRam(gb *cpu);
void loadSaveRam(gb *cpu);
void handleSound(gb *cpu, BYTE clocks);
void writeSoundRegistry(gb *cpu, WORD addr, BYTE data);
#endif

84
lib/gb-memory.c Executable file
View File

@@ -0,0 +1,84 @@
#include "gb-impl.h"
/*This function assumes that the ROM is already loaded*/
void setGbBanking(gb *cpu) {
switch (cpu->cartridge[0x147]) {
case 1:
case 2:
case 3:
cpu->ROMType = 1;
cpu->currentROMBank = 1;
cpu->changeBank = mbc1_changeBank;
break;
case 0x13:
cpu->ROMType = 3;
cpu->currentROMBank = 1;
cpu->changeBank = mbc3_changeBank;
break;
}
cpu->currentRAMBank = 0;
}
BYTE readMemory(gb *cpu, WORD addr) {
if ((addr >= 0x0000) && (addr <= 0x3FFF)) {
return cpu->cartridge[addr];
}
if ((addr >= 0x4000) && (addr <= 0x7FFF)) {
WORD t = addr - 0x4000;
return cpu->cartridge[t + (cpu->currentROMBank * 0x4000)];
}
else if ((addr >= 0xA000) && (addr <= 0xBFFF)) {
WORD t = addr - 0xA000;
return cpu->RAMBank[t + (cpu->currentRAMBank * 0x2000)];
} else if (addr == 0xFF00)
return getKeypad(cpu);
return cpu->memory[addr];
}
/*This function is necessary for replicate the ECHO (E000-FDFF) area*/
void writeMemory(gb *cpu, WORD addr, BYTE data) {
/*This part is mapped on the rom, so read-only*/
if (addr < 0x8000) {
cpu->changeBank(cpu, addr, data);
}
else if ((addr >= 0xA000) && (addr < 0xC000)) {
if (cpu->isRAMEnable != 0) {
WORD t = addr - 0xA000;
cpu->RAMBank[t + (cpu->currentRAMBank * 0x2000)] = data;
}
}
else if ((addr >= 0xE000) && (addr < 0xFE00)) {
cpu->memory[addr] = data;
writeMemory(cpu, addr - 0x2000, data);
}
/*Not usable */
else if ((addr >= 0xFEA0) && (addr < 0xFEFF)) {
}
else if (addr == TIMER_CONTROLLER) {
BYTE freq = readMemory(cpu, TIMER_CONTROLLER) & 0x3;
cpu->memory[TIMER_CONTROLLER] = data;
BYTE newfreq = readMemory(cpu, TIMER_CONTROLLER) & 0x3;
if (freq != newfreq)
setTimerFreq(cpu);
} else if (addr == DIVIDER_TIMER)
cpu->memory[DIVIDER_TIMER] = 0;
else if (addr == LCD_SCANLINE_ADRR)
cpu->memory[LCD_SCANLINE_ADRR] = 0;
else if (addr == DMA_ADRR)
DMATransfert(cpu, data);
else if((addr >= 0xFF10) && (addr <= 0xFF3F)){
writeSoundRegistry(cpu, addr, data);
}
else
cpu->memory[addr] = data;
}

47
lib/gb-opcode.h Executable file
View File

@@ -0,0 +1,47 @@
#ifndef GBOPCODE_H
#define GBOPCODE_H
void LOAD_8BIT(BYTE *dest, BYTE src);
void LOAD_16BIT(BYTE *destA, BYTE *destB, WORD src);
void LDHL(gb *cpu);
void ADD_8BIT(gb *cpu, BYTE *regA, BYTE regB);
void ADDC_8BIT(gb *cpu, BYTE *regA, BYTE regB);
void SUB_8BIT(gb *cpu, BYTE *regA, BYTE regB);
void SUBC_8BIT(gb *cpu, BYTE *regA, BYTE regB);
void AND_8BIT(gb *cpu, BYTE *regA, BYTE regB);
void OR_8BIT(gb *cpu, BYTE *regA, BYTE regB);
void XOR_8BIT(gb *cpu, BYTE *regA, BYTE regB);
void CP_8BIT(gb *cpu, BYTE regB);
void INC_8BIT(gb *cpu, BYTE *reg);
void DEC_8BIT(gb *cpu, BYTE *reg);
void ADD_16BIT(gb *cpu, BYTE *regA, BYTE *regB, WORD src);
void INC_16BIT(BYTE *regA, BYTE *regB);
void DEC_16BIT(BYTE *regA, BYTE *regB);
void JMP(gb *cpu, WORD addr);
void ROTATE_LEFT(gb *cpu, BYTE *reg);
void ROTATE_RIGHT(gb *cpu, BYTE *reg);
void ROTATE_LEFT_CARRY(gb *cpu, BYTE *reg);
void ROTATE_RIGHT_CARRY(gb *cpu, BYTE *reg);
void SHIFT_LEFT(gb *cpu, BYTE *reg);
void SHIFT_RIGHT_ARITH(gb *cpu, BYTE *reg);
void SHIFT_RIGHT(gb *cpu, BYTE *reg);
void SWAP_NIBBLES(gb *cpu, BYTE *reg);
void TEST_BIT(gb *cpu, BYTE val, BYTE numBit);
void RESET_BIT(gb *cpu, BYTE *val, BYTE numBit);
void SET_BIT(gb *cpu, BYTE *val, BYTE numBit);
void DAA(gb *cpu);
#endif

448
lib/gb-opcodes-impl.c Executable file
View File

@@ -0,0 +1,448 @@
#include "gb-impl.h"
#include "gb-opcode.h"
#include <stdint.h>
#define ROTATE_FLAG(cpu, bit, val) \
{ \
if (bit == 0) \
RESET_CFLAG(cpu); \
else \
SET_CFLAG(cpu); \
RESET_NFLAG(cpu); \
RESET_HFLAG(cpu); \
if (val == 0) \
SET_ZFLAG(cpu); \
else \
RESET_ZFLAG(cpu); \
} \
while (0)
void LOAD_8BIT(BYTE *dest, BYTE src) {
*dest = src;
}
void LOAD_16BIT(BYTE *destA, BYTE *destB, WORD src) {
*destB = src & 0xFF;
*destA = (src >> 8) & 0xFF;
}
void LDHL(gb *cpu) {
uint32_t val;
SIGNED_BYTE n;
n = (SIGNED_BYTE)readMemory(cpu, cpu->progCounter);
cpu->progCounter++;
// GET_BYTE_PC(cpu, n);
val = (SIGNED_WORD)cpu->stack + (SIGNED_WORD)n;
RESET_ZFLAG(cpu);
RESET_NFLAG(cpu);
if (val > 0xFFFF)
SET_CFLAG(cpu);
else
RESET_CFLAG(cpu);
WORD h = (val & 0x8FF);
h += (cpu->stack & 0x8FF);
if (h > 0x8FF)
SET_HFLAG(cpu);
else
RESET_HFLAG(cpu);
/*TODO check the flags*/
cpu->L = val & 0xFF;
cpu->H = (val >> 8) & 0xFF;
}
void JMP(gb *cpu, WORD addr) {
cpu->progCounter = addr;
}
void ADD_8BIT(gb *cpu, BYTE *regA, BYTE regB) {
WORD value = (*regA) + regB;
if (value > 255)
SET_CFLAG(cpu);
else
RESET_CFLAG(cpu);
value &= 0xFF;
RESET_NFLAG(cpu);
if (value == 0)
SET_ZFLAG(cpu);
else
RESET_ZFLAG(cpu);
WORD h = ((*regA) & 0xF);
h += (regB & 0xF);
if (h > 0xF)
SET_HFLAG(cpu);
else
RESET_HFLAG(cpu);
*regA = value & 0xFF;
}
void ADDC_8BIT(gb *cpu, BYTE *regA, BYTE regB) {
ADD_8BIT(cpu, regA, (BYTE)(regB + ((cpu->F & 0x10) ? 1 : 0)));
/* BYTE ret = regB + ((cpu->F &0x10)?1:0);
WORD value = (*regA) + ret;
//printf("(%x) %x +(%x) %x + %x (%x) = %x\n",i,*regA, (cpu->A),regB,
((cpu->F &0x10)?1:0), cpu->F, value&0xff );
if(value > 0xff)
SET_CFLAG(cpu);
else
RESET_CFLAG(cpu);
value &= 0xFF;
RESET_NFLAG(cpu);
if(value == 0)
SET_ZFLAG(cpu);
else
RESET_ZFLAG(cpu);
WORD h = ((*(regA)) & 0xF) ;
h += (ret & 0xF) ;
if (h > 0xF)
SET_HFLAG(cpu);
else
RESET_HFLAG(cpu);
*regA = value & 0xFF;*/
}
void SUB_8BIT(gb *cpu, BYTE *regA, BYTE regB) {
BYTE value = (*regA) - regB;
SET_NFLAG(cpu);
if ((*regA) < regB)
SET_CFLAG(cpu);
else
RESET_CFLAG(cpu);
value &= 0xFF;
if (value == 0)
SET_ZFLAG(cpu);
else
RESET_ZFLAG(cpu);
SIGNED_WORD h = ((*regA) & 0xF);
h -= (regB & 0xF);
if (h < 0)
SET_HFLAG(cpu);
else
RESET_HFLAG(cpu);
*regA = (BYTE)value;
}
void SUBC_8BIT(gb *cpu, BYTE *regA, BYTE regB) {
SUB_8BIT(cpu, regA, (BYTE)(regB + ((cpu->F & 0x10) ? 1 : 0)));
/*BYTE ret = regB + (SIGNED_BYTE)((cpu->F &0x10)?1:0);
int32_t value = (*regA) - ret;
// printf("%x - %x - %x = %x\n",*regA, regB, ((cpu->F &0x10)?1:0), value );
SET_NFLAG(cpu);
if(value <0 ){
SET_CFLAG(cpu);
value+=256;
}
else
RESET_CFLAG(cpu);
value &=0xFF;
if(value == 0)
SET_ZFLAG(cpu);
else
RESET_ZFLAG(cpu);
SIGNED_WORD h = (*regA & 0x0F) ;
h -= (ret & 0x0F) ;
if ((ret & 0x0F) > (*regA & 0x0F) )
SET_HFLAG(cpu);
else
RESET_HFLAG(cpu);
*regA = value&0xff;*/
}
void AND_8BIT(gb *cpu, BYTE *regA, BYTE regB) {
BYTE val = *regA & regB;
if (val == 0)
SET_ZFLAG(cpu);
else
RESET_ZFLAG(cpu);
RESET_NFLAG(cpu);
SET_HFLAG(cpu);
RESET_CFLAG(cpu);
*regA = val;
}
void OR_8BIT(gb *cpu, BYTE *regA, BYTE regB) {
BYTE val = (*regA) | regB;
if (val == 0)
SET_ZFLAG(cpu);
else
RESET_ZFLAG(cpu);
RESET_NFLAG(cpu);
RESET_HFLAG(cpu);
RESET_CFLAG(cpu);
*regA = val;
}
void XOR_8BIT(gb *cpu, BYTE *regA, BYTE regB) {
BYTE val = (*regA) ^ regB;
if (val == 0)
SET_ZFLAG(cpu);
else
RESET_ZFLAG(cpu);
RESET_NFLAG(cpu);
RESET_HFLAG(cpu);
RESET_CFLAG(cpu);
*regA = val;
}
void CP_8BIT(gb *cpu, BYTE regB) {
BYTE temp = cpu->A;
SUB_8BIT(cpu, &(cpu->A), regB);
cpu->A = temp;
}
void INC_8BIT(gb *cpu, BYTE *reg) {
BYTE val = *reg;
val++;
if (((*reg) & 0xF) == 0)
SET_HFLAG(cpu);
else
RESET_HFLAG(cpu);
RESET_NFLAG(cpu);
if (val == 0)
SET_ZFLAG(cpu);
else
RESET_ZFLAG(cpu);
*reg = val;
}
void DEC_8BIT(gb *cpu, BYTE *reg) {
BYTE val = *reg;
val--;
if (((*reg) & 0xF) == 0)
SET_HFLAG(cpu);
else
RESET_HFLAG(cpu);
SET_NFLAG(cpu);
if (val == 0)
SET_ZFLAG(cpu);
else
RESET_ZFLAG(cpu);
*reg = val;
}
void INC_16BIT(BYTE *regA, BYTE *regB) {
WORD value = ((*regA) << 8) | (*regB);
value++;
*regA = (value >> 8) & 0xFF;
*regB = value & 0xFF;
}
void DEC_16BIT(BYTE *regA, BYTE *regB) {
WORD value = ((*regA) << 8) | (*regB);
value--;
*regA = (value >> 8) & 0xFF;
*regB = value & 0xFF;
}
void SWAP_NIBBLES(gb *cpu, BYTE *reg) {
BYTE val = *reg;
val = ((val << 4) & 0xF0) | ((val >> 4) & 0xF);
if (val == 0)
SET_ZFLAG(cpu);
else
RESET_ZFLAG(cpu);
RESET_NFLAG(cpu);
RESET_HFLAG(cpu);
RESET_CFLAG(cpu);
*reg = val;
}
void TEST_BIT(gb *cpu, BYTE val, BYTE numBit) {
BYTE val2 = (val >> numBit) & 0x1;
if (val2 == 0)
SET_ZFLAG(cpu);
else
RESET_ZFLAG(cpu);
RESET_NFLAG(cpu);
SET_HFLAG(cpu);
}
void RESET_BIT(gb *cpu, BYTE *val, BYTE numBit) {
BYTE v = (*val) & (~(0x1 << numBit));
*val = v;
}
void SET_BIT(gb *cpu, BYTE *val, BYTE numBit) {
BYTE v = (*val) | (0x1 << numBit);
*val = v;
}
void ADD_16BIT(gb *cpu, BYTE *regA, BYTE *regB, WORD src) {
uint32_t val = (((*regA) << 8)) | ((*regB) & 0xFF);
// uint32_t val = (((*regA)<<8)) | ((*regB) &0xFF);
val += src;
RESET_NFLAG(cpu);
if (val > 0xFFFF)
SET_CFLAG(cpu);
else
RESET_CFLAG(cpu);
WORD h = (((((*regA) << 8)) | ((*regB) & 0xFF)) & 0x8FF);
h += (src & 0x8FF);
if (h > 0x8FF)
SET_HFLAG(cpu);
else
RESET_HFLAG(cpu);
*regA = (val >> 8) & 0xFF;
*regB = (val & 0xFF);
}
void ROTATE_LEFT(gb *cpu, BYTE *reg) {
BYTE val = *reg;
BYTE msb = (val >> 7) & 0x1;
val <<= 1;
val |= msb;
cpu->F = 0;
if (msb) {
SET_CFLAG(cpu);
} else {
RESET_CFLAG(cpu);
}
if (val == 0)
SET_ZFLAG(cpu);
else
RESET_ZFLAG(cpu);
*reg = val;
}
void ROTATE_RIGHT(gb *cpu, BYTE *reg) {
BYTE val = *reg;
BYTE msb = (val & 0x1);
cpu->F = 0;
val >>= 1;
if (msb) {
val |= 0x80;
SET_CFLAG(cpu);
} else {
val &= 0x7f;
RESET_CFLAG(cpu);
}
if (val == 0)
SET_ZFLAG(cpu);
else
RESET_ZFLAG(cpu);
*reg = val;
}
void ROTATE_LEFT_CARRY(gb *cpu, BYTE *reg) {
BYTE val = *reg;
BYTE msb = (val >> 7) & 0x1;
val <<= 1;
if (cpu->F & 0x10)
val |= 0x1;
if (msb)
SET_CFLAG(cpu);
else
RESET_CFLAG(cpu);
if (val == 0)
SET_ZFLAG(cpu);
else
RESET_ZFLAG(cpu);
*reg = val;
}
void ROTATE_RIGHT_CARRY(gb *cpu, BYTE *reg) {
BYTE val = *reg;
BYTE msb = val & 0x1;
val >>= 1;
if ((cpu->F & 0x10) != 0)
val |= 0x80;
else
val &= 0x7f;
if (msb)
SET_CFLAG(cpu);
else
RESET_CFLAG(cpu);
if (val == 0)
SET_ZFLAG(cpu);
else
RESET_ZFLAG(cpu);
*reg = val;
}
void SHIFT_LEFT(gb *cpu, BYTE *reg) {
BYTE val = *reg;
BYTE msb = (val >> 7) & 0x1;
val <<= 1;
ROTATE_FLAG(cpu, msb, val);
*reg = val;
}
void SHIFT_RIGHT_ARITH(gb *cpu, BYTE *reg) {
BYTE val = *reg;
BYTE lsb = (val)&0x1;
BYTE msb = (val >> 7) & 0x1;
val >>= 1;
val |= ((msb << 7));
ROTATE_FLAG(cpu, lsb, val);
*reg = val;
}
void SHIFT_RIGHT(gb *cpu, BYTE *reg) {
BYTE val = *reg;
BYTE lsb = (val)&0x1;
val >>= 1;
ROTATE_FLAG(cpu, lsb, val);
*reg = val;
}
void DAA(gb *cpu) {
uint32_t a = cpu->A;
if (!(cpu->F & 0x40)) {
if ((cpu->F & 0x20) || (a & 0xF) > 9)
a += 0x06;
if ((cpu->F & 0x10) || a > 0x9F)
a += 0x60;
} else {
if ((cpu->F & 0x20) != 0)
a -= 0x06;
if ((cpu->F & 0x10) != 0)
a -= 0x60;
}
cpu->F &= ~(0x20 | 0x80);
if ((a & 0x100) == 0x100)
SET_CFLAG(cpu);
a &= 0xFF;
if (a == 0)
SET_ZFLAG(cpu);
cpu->A = (BYTE)a;
}

1804
lib/gb-opcodes.c Executable file

File diff suppressed because it is too large Load Diff

277
lib/gb-sound.c Executable file
View File

@@ -0,0 +1,277 @@
#include "gb-impl.h"
#include "gb-sound.h"
void increase_sound_timers(gb *cpu) {
if (CHANNEL_1_LENGTH_ENABLED != 0) {
cpu->channel_1.length_timer++;
if (cpu->channel_1.length_timer >= 64) {
cpu->memory[0xFF26] = cpu->memory[0xFF26] & 0xFE;
}
}
if(CHANNEL_2_LENGTH_ENABLED != 0){
cpu->channel_2.length_timer++;
if(cpu->channel_2.length_timer >= 64) {
cpu->memory[0xFF26] = cpu->memory[0xFF26] & 0xFD;
}
}
if(CHANNEL_3_LENGTH_ENABLED != 0){
cpu->channel_3.length_timer++;
if(cpu->channel_3.length_timer >= 256) {
cpu->memory[0xFF26] = cpu->memory[0xFF26] & 0xFB;
}
}
if(CHANNEL_4_LENGTH_ENABLED != 0){
cpu->channel_4.length_timer++;
if(cpu->channel_4.length_timer >= 64) {
cpu->memory[0xFF26] = cpu->memory[0xFF26] & 0xF7;
}
}
}
void increase_vol_envelope(gb *cpu){
if (cpu->channel_1.env_sweep_pace != 0) {
cpu->channel_1.env_vol += (cpu->channel_1.env_sweep_pace == 0) ? -1 : 1;
if (cpu->channel_1.env_vol < 0) cpu->channel_1.env_vol = 0;
if (cpu->channel_1.env_vol > 15) cpu->channel_1.env_vol = 15;
}
if(cpu->channel_2.env_sweep_pace !=0) {
cpu->channel_2.env_vol += (cpu->channel_2.env_dir == 0) ? -1 : 1;
if (cpu->channel_2.env_vol < 0) cpu->channel_2.env_vol = 0;
if (cpu->channel_2.env_vol > 15) cpu->channel_2.env_vol = 15;
}
if(cpu->channel_4.env_sweep_pace !=0) {
cpu->channel_4.env_vol += (cpu->channel_4.env_dir == 0) ? -1 : 1;
if (cpu->channel_4.env_vol < 0) cpu->channel_4.env_vol = 0;
if (cpu->channel_4.env_vol > 15) cpu->channel_4.env_vol = 15;
}
}
void increase_sweep(gb *cpu){
if(CHANNEL_1_SWEEP_PACE !=0) {
WORD newPeriod = CHANNEL_1_PERIOD_CLOCK;
newPeriod = CHANNEL_1_SWEEP_DIR != 0? newPeriod - (CHANNEL_1_PERIOD_CLOCK >> CHANNEL_1_SWEEP_STEP) : newPeriod + (CHANNEL_1_PERIOD_CLOCK >> CHANNEL_1_SWEEP_STEP);
NR13 = newPeriod & 0xFF;
NR14 = (NR14 & 0xF8) | (newPeriod >> 8);
}
}
void handleSound(gb *cpu, BYTE clocks) {
BYTE s = 0;
if (AUDIO_OFF)
return;
cpu->channel_1.period_clock -= clocks;
if (cpu->channel_1.period_clock < 0) {
s = squareWave[CHANNEL_1_WAVE][cpu->channel_1.pos & 7];
cpu->channel_1.pos++;
cpu->channel_1.period_clock += (2048 - CHANNEL_1_PERIOD_CLOCK) *4;
s *= cpu->channel_1.env_vol;
if (CHANNEL_1_ON != 0 && CHANNEL_1_RIGHT_ACTIVE != 0) cpu->rightSoundBuffer += s;
if (CHANNEL_1_ON != 0 && CHANNEL_1_LEFT_ACTIVE != 0) cpu->leftSoundBuffer += s;
}
cpu->channel_2.period_clock -= clocks;
if(cpu->channel_2.period_clock < 0) {
s = squareWave[CHANNEL_2_WAVE][cpu->channel_2.pos & 7];
cpu->channel_2.pos++;
cpu->channel_2.period_clock += (2048 - CHANNEL_2_PERIOD_CLOCK) *4;
s *= cpu->channel_2.env_vol;
if (CHANNEL_2_ON != 0 && CHANNEL_2_RIGHT_ACTIVE != 0) cpu->rightSoundBuffer += s;
if (CHANNEL_2_ON != 0 && CHANNEL_2_LEFT_ACTIVE !=0) cpu->leftSoundBuffer += s;
}
cpu->channel_3.period_clock -= clocks;
if(cpu->channel_3.period_clock < 0) {
s = CHANNEL_3_WAVE[cpu->channel_3.pos / 2] &0xFF;
//If pos is even, we need the upper nibble, otherwise the lower one
s = cpu->channel_3.pos % 2 == 0 ? s >> 4 : s & 0xF;
cpu-> channel_3.pos = cpu->channel_3.pos++ &0x1F;
cpu->channel_3.period_clock += (2048 - CHANNEL_3_PERIOD_CLOCK) *2;
if(CHANNEL_3_OUTPUT_LEVEL == 0) {
s = 0;
} else {
s >>= CHANNEL_3_OUTPUT_LEVEL -1;
}
if (CHANNEL_3_ON != 0 && CHANNEL_3_RIGHT_ACTIVE != 0) cpu->rightSoundBuffer += s;
if (CHANNEL_3_ON != 0 && CHANNEL_3_LEFT_ACTIVE !=0) cpu->leftSoundBuffer += s;
}
cpu->channel_4.period_clock -= clocks;
if(cpu->channel_4.period_clock <= 0) {
s = noise[cpu->channel_4.pos & 0xF] & 0x1;
s *= cpu->channel_4.env_vol;
cpu->channel_4.pos++;
cpu->channel_4.period_clock += CHANNEL_4_FREQ_MULTIPLIER;
if (CHANNEL_4_ON != 0 && CHANNEL_4_RIGHT_ACTIVE != 0) cpu->rightSoundBuffer += s;
if (CHANNEL_4_ON != 0 && CHANNEL_4_LEFT_ACTIVE != 0) cpu->leftSoundBuffer += s;
}
cpu->soundFrameClock -= clocks;
if(cpu->soundFrameClock <= 0){
cpu->soundFrameClock += 8192; //512 hz
switch (cpu->soundFramePos) {
case 0:
case 4:
increase_sound_timers(cpu);
break;
case 2:
case 6:
increase_sound_timers(cpu);
//increase sweep
break;
case 7:
increase_vol_envelope(cpu);
break;
default:
break;
}
cpu->soundFramePos = cpu->soundFramePos++ & 0x7;
}
cpu->soundMasterClock -= clocks;
if (cpu->soundMasterClock <= 0) {
cpu->soundMasterClock += GB_CLOCK / 44100;
cpu->leftSoundBuffer *= VOLUME_LEFT;
cpu->rightSoundBuffer *= VOLUME_RIGHT;
if (cpu->leftSoundBuffer > 127) cpu->leftSoundBuffer = 127;
else if (cpu->leftSoundBuffer < -128) cpu->leftSoundBuffer = -128;
if (cpu->rightSoundBuffer > 127) cpu->rightSoundBuffer = 127;
else if (cpu->rightSoundBuffer < -128) cpu->rightSoundBuffer = -128;
cpu->soundData[cpu->numerOfBytes++] = cpu->leftSoundBuffer + 128;
cpu->soundData[cpu->numerOfBytes++] = cpu->rightSoundBuffer + 128;
cpu->leftSoundBuffer = 0;
cpu->rightSoundBuffer = 0;
}
}
void writeSoundRegistry(gb *cpu, WORD addr, BYTE data){
if(addr == 0xFF11) {
BYTE lengthTimer = data &0x3F;
cpu->channel_1.length_timer = lengthTimer;
cpu->memory[addr] = data;
}
else if(addr == 0xFF12) {
cpu->memory[addr] = data;
if((data & 0xF8) == 0){
cpu->memory[0xFF26] = cpu->memory[0xFF26] & 0xFE;
}
}
else if(addr == 0xFF13) {
cpu->memory[addr] = data;
}
else if(addr == 0xFF14) {
BYTE triggering = data &0x80;
if(triggering != 0) {
cpu->memory[0xFF26] = cpu->memory[0xFF26] | 0x1;
cpu->channel_1.pos = 0;
cpu->channel_1.period_clock = (2048 - CHANNEL_1_PERIOD_CLOCK) *4;
cpu->channel_1.length_timer = NR11 & 0x3F;
cpu->channel_1.env_vol = NR12 >> 4;
cpu->channel_1.env_dir = (NR12 & 0x8) >> 4;
cpu->channel_1.env_sweep_pace = NR12 & 0x7;
}
cpu->memory[addr] = data;
}
else if(addr == 0xFF16) {
BYTE lengthTimer = data &0x3F;
cpu->channel_2.length_timer = lengthTimer;
cpu->memory[addr] = data;
}
else if(addr == 0xFF17) {
cpu->memory[addr] = data;
if((data & 0xF8) == 0){
cpu->memory[0xFF26] = cpu->memory[0xFF26] & 0xFD;
}
}
else if(addr == 0xFF18) {
cpu->memory[addr] = data;
}
else if(addr == 0xFF19) {
BYTE triggering = data &0x80;
if(triggering != 0) {
cpu->channel_2.pos = 0;
cpu->channel_2.period_clock = (2048 - CHANNEL_2_PERIOD_CLOCK) *4;
cpu->channel_2.length_timer = NR21 & 0x3F;
cpu->channel_2.env_vol = NR22 >> 4;
cpu->channel_2.env_dir = (NR22 & 0x8) >> 4;
cpu->channel_2.env_sweep_pace = NR22 & 0x7;
cpu->memory[0xFF26] = cpu->memory[0xFF26] | 0x2;
}
cpu->memory[addr] = data;
}
else if(addr == 0xFF1A) {
BYTE on = data & 0x80;
if(on == 0) {
cpu->memory[0xFF26] = cpu->memory[0xFF26] & 0xFB;
}
cpu->memory[addr] = data;
}
else if(addr == 0xFF1B) {
cpu->channel_3.length_timer = data;
cpu->memory[addr] = data;
}
else if(addr == 0xFF1C) {
cpu->memory[addr] = data;
}
else if(addr == 0xFF1D) {
cpu->memory[addr] = data;
}
else if(addr == 0xFF1E) {
BYTE triggering = data &0x80;
if(triggering != 0) {
cpu->channel_3.pos = 1;
cpu->channel_3.period_clock = (2048 - CHANNEL_3_PERIOD_CLOCK) *2;
cpu->channel_3.length_timer = NR32;
cpu->memory[0xFF26] = cpu->memory[0xFF26] | 0x4;
}
cpu->memory[addr] = data;
}
else if(addr == 0xFF41) {
BYTE lengthTimer = data &0x3F;
cpu->channel_4.length_timer = lengthTimer;
cpu->memory[addr] = data;
}
else if(addr == 0xFF42) {
cpu->memory[addr] = data;
if((data & 0xF8) == 0){
cpu->memory[0xFF26] = cpu->memory[0xFF26] & 0xF7;
}
}
else if(addr == 0xFF43) {
cpu->memory[addr] = data;
}
else if(addr == 0xFF44) {
BYTE triggering = data &0x80;
if(triggering != 0) {
cpu->channel_4.env_vol = NR42 >> 4;
cpu->channel_4.length_timer = NR41 & 0x3F;
cpu->channel_4.pos = 0;
cpu->channel_4.env_dir = (NR42 & 0x8) >> 4;
cpu->channel_4.env_sweep_pace = NR42 & 0x7;
cpu->channel_4.period_clock = 0;
cpu->memory[0xFF26] = cpu->memory[0xFF26] | 0x8;
}
cpu->memory[addr] = data;
}
else if(addr == 0xFF26) {
BYTE val = data & 0x80;
cpu->memory[addr] = val | cpu->memory[addr];
}
else {
cpu->memory[addr] = data;
}
}

86
lib/gb-sound.h Executable file
View File

@@ -0,0 +1,86 @@
#ifndef __GB_SOUND_H__
#define __GB_SOUND_H__
#include "gb-impl.h"
const static BYTE squareWave[4][8] ={
{ 0, 0,0, 0, 0, 0, 0, 1 },
{ 1,0, 0, 0, 0, 0, 0, 1 },
{ 1,0,0,0, 0, 1, 1, 1 },
{ 0,1,1,1,1,1, 1, 0 }
};
const static BYTE noise[] = {
0xfb,0xe7,0xae,0x1b,0xa6,0x2b,0x05,0xe3,
0xb6,0x4a,0x42,0x72,0xd1,0x19,0xaa,0x03,
};
#define NR50 cpu->memory[0xFF24]
#define NR51 cpu->memory[0xFF25]
#define NR52 cpu->memory[0xFF26]
#define NR10 cpu->memory[0xFF10]
#define NR11 cpu->memory[0xFF11]
#define NR12 cpu->memory[0xFF12]
#define NR13 cpu->memory[0xFF13]
#define NR14 cpu->memory[0xFF14]
#define NR21 cpu->memory[0xFF16]
#define NR22 cpu->memory[0xFF17]
#define NR23 cpu->memory[0xFF18]
#define NR24 cpu->memory[0xFF19]
#define NR32 cpu->memory[0xFF1C]
#define NR33 cpu->memory[0xFF1D]
#define NR34 cpu->memory[0xFF1E]
#define NR41 cpu->memory[0xFF20]
#define NR42 cpu->memory[0xFF21]
#define NR43 cpu->memory[0xFF22]
#define NR44 cpu->memory[0xFF23]
#define AUDIO_OFF ((NR52 & 0x80) == 0)
#define VOLUME_LEFT ((NR50 & 0x70 )>> 4 )
#define VOLUME_RIGHT (NR50 & 0x7 )
#define CHANNEL_1_ON (NR52 & 0x1)
#define CHANNEL_1_PERIOD_CLOCK (((NR14 & 0x7) << 8) | NR13)
#define CHANNEL_1_WAVE ((NR11 & 0xC0) >> 6)
#define CHANNEL_1_LENGTH_ENABLED (NR14 & 0x40)
#define CHANNEL_1_ENVELOP_DIR (NR12 & 0x8)
#define CHANNEL_1_SWEEP_PACE ((NR11 & 0x70) >> 4)
#define CHANNEL_1_SWEEP_DIR ((NR11 & 0x08) >> 3)
#define CHANNEL_1_SWEEP_STEP (NR11 & 0x07)
#define CHANNEL_1_LEFT_ACTIVE (NR51 & 0x10 )
#define CHANNEL_1_RIGHT_ACTIVE (NR51 & 0x1 )
#define CHANNEL_2_ON (NR52 & 0x2)
#define CHANNEL_2_PERIOD_CLOCK (((NR24 & 0x7) << 8) | NR23)
#define CHANNEL_2_WAVE ((NR21 & 0xC0) >> 6)
#define CHANNEL_2_LENGTH_ENABLED (NR24 & 0x40)
#define CHANNEL_2_SWEEP_PACE (NR22 & 0x7)
#define CHANNEL_2_ENVELOP_DIR (NR22 & 0x8)
#define CHANNEL_2_LEFT_ACTIVE (NR51 & 0x20 )
#define CHANNEL_2_RIGHT_ACTIVE (NR51 & 0x2 )
#define CHANNEL_3_ON (NR52 & 0x4)
#define CHANNEL_3_OUTPUT_LEVEL ((NR32 >> 5) & 0x3)
#define CHANNEL_3_LENGTH_ENABLED (NR34 & 0x40)
#define CHANNEL_3_WAVE (cpu->memory + 0xFF30)
#define CHANNEL_3_PERIOD_CLOCK (((NR34 & 0x7) << 8) | NR33)
#define CHANNEL_3_LEFT_ACTIVE (NR51 & 0x40 )
#define CHANNEL_3_RIGHT_ACTIVE (NR51 & 0x4 )
#define CHANNEL_4_ON (NR52 & 0x4)
#define CHANNEL_4_LFSR (NR43 & 0x8)
#define CHANNEL_4_FREQ_MULTIPLIER ((NR43 & 0x7) << (NR43 >> 4))
#define CHANNEL_4_LENGTH_ENABLED (NR44 & 0x40)
#define CHANNEL_4_SWEEP_PACE (NR42 & 0x7)
#define CHANNEL_4_ENVELOP_DIR (NR42 & 0x8)
#define CHANNEL_4_LEFT_ACTIVE (NR51 & 0x80 )
#define CHANNEL_4_RIGHT_ACTIVE (NR51 & 0x8 )
#endif

328
lib/gb-video.c Executable file
View File

@@ -0,0 +1,328 @@
#include "gb-impl.h"
BYTE getColour(BYTE colourNum, BYTE palette) {
// use the palette to get the colour
return (palette >> (colourNum * 2)) & 0x3;
}
void renderTiles(gb *cpu) {
WORD tileData = 0;
WORD backgroundMemory = 0;
BYTE unsig = 1;
BYTE lcd_control = readMemory(cpu, LCD_REG_CONTROL);
BYTE scrollY = readMemory(cpu, SCROLLY);
BYTE scrollX = readMemory(cpu, SCROLLX);
BYTE windowY = readMemory(cpu, WINDOWY);
BYTE windowX = readMemory(cpu, WINDOWX);
if (windowX < 7) {
windowX = 7;
}
windowX -= 7;
BYTE currentLine = readMemory(cpu, LCD_SCANLINE_ADRR);
BYTE usingWindow = 0;
/*The window is enabled and visible?*/
if ((lcd_control & 0x20) != 0 && windowY <= currentLine)
usingWindow = 1;
// tile area ?
if ((lcd_control & 0x10) != 0)
tileData = 0x8000;
else {
// This memory region uses signed bytes
tileData = 0x8800;
unsig = 0;
}
BYTE yPos = 0;
// yPos is used to calculate which of 32 vertical tiles the
// current scanline is drawing
if (usingWindow == 0)
yPos = scrollY + currentLine;
else {
yPos = currentLine - windowY;
}
// which line of the tile is being rendered ?
WORD tileRow = (yPos / 8) * 32;
BYTE palette = readMemory(cpu, 0xFF47);
BYTE colorMap[4] = {getColour(0, palette), getColour(1, palette),
getColour(2, palette), getColour(3, palette)};
// Now I have to render the line, pixel by pixel
for (BYTE pixel = 0; pixel < 160;) {
BYTE xPos = pixel + scrollX;
if ((lcd_control & 0x08) != 0)
backgroundMemory = 0x9C00;
else
backgroundMemory = 0x9800;
// translate the current x pos to window space if necessary
if (usingWindow == 1) {
if (pixel >= windowX) {
// This means I'm in the window
xPos = pixel - windowX;
if ((lcd_control & 0x40) != 0)
backgroundMemory = 0x9c00;
else
backgroundMemory = 0x9800;
}
}
// which of the 32 horizontal tiles does this xPos fall within?
WORD tileCol = (xPos / 8);
WORD tileNum;
WORD tileAddrss = backgroundMemory + tileRow + tileCol;
if (unsig == 1)
tileNum = (BYTE)readMemory(cpu, tileAddrss);
else {
tileNum =
((SIGNED_WORD)readMemory(cpu, tileAddrss) + (SIGNED_WORD)128) &
0xff;
}
// deduce where this tile identifier is in memory.
WORD tileLocation = tileData + (tileNum * 16);
BYTE line = yPos % 8;
line *= 2; // each line takes two bytes
BYTE data1 = readMemory(cpu, tileLocation + line);
BYTE data2 = readMemory(cpu, tileLocation + line + 1);
SIGNED_BYTE colourBit = (xPos & 0x7);
colourBit -= 7;
colourBit *= -1;
for(; colourBit >= 0; colourBit--) {
BYTE colourNum = (data2 >> (colourBit)) & 0x1;
colourNum <<= 1;
colourNum |= ((data1 >> (colourBit)) & 0x1);
BYTE col = colorMap[colourNum];
cpu->screenData[currentLine][pixel] = col;
pixel++;
if(pixel >= 160) {
break;
}
}
}
}
void renderSprites(gb *cpu) {
BYTE ysize = 8;
BYTE currentLine = readMemory(cpu, 0xFF44);
BYTE lcd_control = readMemory(cpu, LCD_REG_CONTROL);
if ((lcd_control & 0x4) != 0)
ysize = 16;
/*Explore all the sprite table*/
/*Each sprite is formed by 4 bytes:
* 0 : y pos - 16
* 1 : x pos - 8
* 2 : data location in memory
* 3 : several flags
*/
for (BYTE index = 0; index < 40 * 4; index += 4) {
BYTE yPos = readMemory(cpu, 0xFE00 + index) - 16;
if ((currentLine < yPos) || (currentLine >= (yPos + ysize)))
continue;
BYTE xPos = readMemory(cpu, 0xFE00 + index + 1) - 8;
if (xPos > 160)
continue;
BYTE tileLocation = readMemory(cpu, 0xFE00 + index + 2);
BYTE attributes = readMemory(cpu, 0xFE00 + index + 3);
BYTE yFlip = attributes & 0x40;
BYTE xFlip = attributes & 0x20;
WORD colourAddress;
if ((attributes & 0x10) != 0)
colourAddress = 0xFF49;
else
colourAddress = 0xFF48;
// does this sprite intercept with the scanline?
BYTE line = currentLine - yPos;
// read the sprite in backwards in the y axis
if (yFlip != 0) {
line -= ysize;
line *= -1;
}
line *= 2; // same as for tiles
WORD dataAddress = (0x8000 + (tileLocation * 16)) + line;
BYTE data1 = readMemory(cpu, dataAddress);
BYTE data2 = readMemory(cpu, dataAddress + 1);
BYTE palette = readMemory(cpu, colourAddress);
// its easier to read in from right to left as pixel 0 is
// bit 7 in the colour data, pixel 1 is bit 6 etc...
for (SIGNED_BYTE tilePixel = 7; tilePixel >= 0; tilePixel--) {
SIGNED_BYTE xPix = 0 - tilePixel;
xPix += 7;
BYTE pixel = xPos + (BYTE)xPix;
if (pixel > 160 || currentLine > 144)
continue;
// check if pixel is hidden behind background
if ((attributes & 0x80) != 0 &&
cpu->screenData[currentLine][pixel] != 0) {
continue;
}
/*Should do some refactoring for avoiding this signed pixel*/
SIGNED_BYTE colourbit = tilePixel;
// read the sprite in backwards for the x axis
if (xFlip != 0) {
colourbit -= 7;
colourbit *= -1;
}
BYTE colourNum = (data2 >> (colourbit)) & 0x1;
colourNum <<= 1;
colourNum |= ((data1 >> (colourbit)) & 0x1);
/*Must ignore the alpha color*/
if (colourNum == 0)
continue;
BYTE col = getColour(colourNum, palette);
/*I can draw the pixel*/
cpu->screenData[currentLine][pixel] = col;
}
}
}
void drawScanline(gb *cpu) {
BYTE control = readMemory(cpu, LCD_REG_CONTROL);
if ((control & 0x1) != 0)
renderTiles(cpu);
if ((control & 0x2) != 0)
renderSprites(cpu);
}
void updateLCD(gb *cpu) {
BYTE status = readMemory(cpu, LCD_REG_STATUS);
BYTE lcd_control = readMemory(cpu, LCD_REG_CONTROL);
/*LCD is disabled*/
if ((lcd_control & 0x80) == 0) {
// set the mode to 1 during lcd disabled and reset scanline
cpu->clockScanline = 456;
cpu->memory[0xFF44] = 0;
status &= 252;
status |= 0x1;
writeMemory(cpu, LCD_REG_STATUS, status);
return;
}
BYTE currentline = readMemory(cpu, LCD_SCANLINE_ADRR);
BYTE mode = status & 0x3;
BYTE nextMode = 0;
BYTE reqInt = 0;
// in vblank so set mode to 1
if (currentline >= 144) {
nextMode = 1;
status &= ~(0x3);
status |= 0x1;
reqInt = status & 0x10;
} else {
// mode 2
if (cpu->clockScanline >= 376) {
nextMode = 2;
status &= ~(0x3);
status |= 0x2;
reqInt = status & 0x20;
}
// mode 3
else if (cpu->clockScanline >= 204) {
nextMode = 3;
status &= ~(0x3);
status |= 0x3;
}
// mode 0
else {
nextMode = 0;
status &= ~(0x3);
reqInt = status & 0x8;
}
}
if ((reqInt != 0) && (mode != nextMode))
raiseInterrupt(cpu, 1);
// check the conincidence flag
if (readMemory(cpu, 0xFF44) == readMemory(cpu, 0xFF45)) {
status |= 0x4;
if ((status & 0x40) != 0)
raiseInterrupt(cpu, 1);
} else {
status &= ~(0x4);
}
writeMemory(cpu, LCD_REG_STATUS, status);
}
void handleGraphic(gb *cpu, BYTE cycles) {
updateLCD(cpu);
BYTE lcd_control = cpu->memory[LCD_REG_CONTROL];
if ((lcd_control & 0x80) == 0) {
return;
}
cpu->clockScanline -= cycles;
if (cpu->clockScanline <= 0) {
BYTE currentline = cpu->memory[LCD_SCANLINE_ADRR];
cpu->clockScanline += 456;
/*V-blank interrupt*/
if (currentline == 144)
raiseInterrupt(cpu, 0);
else if (currentline > 153)
cpu->memory[LCD_SCANLINE_ADRR] = 0;
else if (currentline < 144)
drawScanline(cpu);
cpu->memory[LCD_SCANLINE_ADRR]++;
}
}
BYTE getPixelColor(gb *cpu, BYTE x, BYTE y) {
BYTE col = cpu->screenData[y][x];
BYTE color = 0;
switch (col) {
case 0:
color = BLACK_COL;
break;
case 1:
color = GRAY_COL;
break;
case 2:
color = LIGHTGRAY_COL;
break;
case 3:
color = WHITE_COL;
break;
}
return color;
}

172
lib/gc-imp.c Executable file
View File

@@ -0,0 +1,172 @@
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "gb-impl.h"
#include "gb-opcode.h"
void loadROM(gb *cpu, char *rom) {
FILE *f = fopen(rom, "rb");
if (!f) {
fprintf(stderr, "Can't load ROM\n");
exit(2);
}
fread(cpu->cartridge, 1, CARTRIDGE_SIZE, f);
fclose(f);
switch (cpu->cartridge[0x147]) {
case 0:
case 1:
case 2:
case 3:
case 0x13:
break;
default:
printf("Can't execute this rom (ROM code: %x)\n",
cpu->cartridge[0x147]);
exit(EXIT_FAILURE);
}
}
void no_changeBank(gb *cpu, WORD addr, BYTE data) {
}
void initGameBoy(gb *cpu) {
cpu->changeBank = no_changeBank;
cpu->progCounter = 0x100;
cpu->whenDisableInterrupts = 0;
cpu->enable_interr = 0;
cpu->stack = 0xFFFE;
memset(cpu->memory, 0, MEMORY_SIZE);
cpu->master_interr_switch = 0;
cpu->clockScanline = 456;
cpu->clockBeforeTimer = 1024;
cpu->clockBeforeDividerTimer = 256;
cpu->ROMType = 0;
cpu->currentROMBank = 1;
cpu->cpuHalted = 1;
cpu->whenDisableInterrupts = 0;
cpu->keymap = 0xFF;
cpu->soundMasterClock = GB_CLOCK / 44100;
setGbBanking(cpu);
}
void DMATransfert(gb *cpu, BYTE data) {
WORD addr = data * 0x100;
for (int i = 0; i < 0xA0; i++) {
writeMemory(cpu, 0xFE00 + i, readMemory(cpu, addr + i));
}
}
BYTE getKeypad(gb *cpu) {
BYTE mem = cpu->memory[0xFF00];
mem ^= 0xFF;
if ((mem & 0x10) == 0) {
BYTE currentPad = cpu->keymap >> 4;
currentPad |= 0xF0;
mem &= currentPad;
mem ^= 0x30;
} else if ((mem & 0x20) == 0) {
BYTE currentPad = cpu->keymap & 0xF;
currentPad |= 0xF0;
mem &= currentPad;
mem ^= 0x30;
}
return mem;
}
void keyReleased(gb *cpu, int key) {
cpu->keymap |= (0x1 << key);
}
void keyPressed(gb *cpu, int key) {
BYTE previouslyUnset = 0;
// if setting from 1 to 0 we may have to request an interupt
if (((cpu->keymap >> key) & 0x1) == 0)
previouslyUnset = 1;
// a keypressed is 0 not 1
cpu->keymap &= ~(0x1 << key);
// button pressed
BYTE button = 1;
// is this a standard button or a directional button?
if (key > 3)
button = 1;
else
button = 0;
BYTE keyReq = cpu->memory[0xFF00];
BYTE requestInterupt = 0;
// only request interupt if the button just pressed is
// the style of button the game is interested in
if (button && (((keyReq >> 5) & 0x1) == 0))
requestInterupt = 1;
// same as above but for directional button
else if (button && (((keyReq >> 4) & 0x1) == 0))
requestInterupt = 1;
// request interupt
if (requestInterupt && (previouslyUnset == 0))
raiseInterrupt(cpu, 4);
}
void changeKeyState(gb *cpu, BYTE key, BYTE state) {
if (state == GB_KEY_PRESSED) {
keyPressed(cpu, key);
} else
keyReleased(cpu, key);
}
BYTE executeGameBoy(gb *cpu) {
BYTE opcode;
BYTE numClock = 4;
if (cpu->cpuHalted != 0) {
GET_BYTE_PC(cpu, opcode);
numClock = executeOpcode(cpu, opcode);
if (cpu->whenDisableInterrupts > 0) {
cpu->whenDisableInterrupts--;
if (cpu->whenDisableInterrupts == 0)
cpu->master_interr_switch = cpu->enable_interr;
}
}
increaseTimer(cpu, numClock);
handleInterrupts(cpu);
handleGraphic(cpu, numClock);
//handleSound(cpu, numClock);
return numClock;
}
gb *newGameboy(char *rom) {
gb *game = malloc(sizeof(gb));
if (!game) {
return NULL;
}
loadROM(game, rom);
initGameBoy(game);
return game;
}
char *getGameName(gb *cpu) {
return (char *)cpu->cartridge + NAME_CART;
}
BYTE *getSoundData(gb *cpu, unsigned int *len) {
*len = cpu->numerOfBytes;
cpu->numerOfBytes = 0;
return cpu->soundData;
}

76
lib/gc-interrupts.c Executable file
View File

@@ -0,0 +1,76 @@
#include "gb-impl.h"
void setTimerFreq(gb *cpu) {
BYTE freq = readMemory(cpu, TIMER_CONTROLLER) & 0x3;
switch (freq) {
case 0:
cpu->clockBeforeTimer = 1024;
break; // freq 4096
case 1:
cpu->clockBeforeTimer = 16;
break; // freq 262144
case 2:
cpu->clockBeforeTimer = 64;
break; // freq 65536
case 3:
cpu->clockBeforeTimer = 256;
break; // freq 16382
}
}
void increaseTimer(gb *cpu, BYTE clocks) {
//Handle divider timer
cpu->clockBeforeDividerTimer -= clocks;
if (cpu->clockBeforeDividerTimer <= 0) {
cpu->clockBeforeDividerTimer += 255;
cpu->memory[DIVIDER_TIMER]++;
}
/*Timer is enabled ?*/
if ((readMemory(cpu, TIMER_CONTROLLER) & 0x4) != 0) {
cpu->clockBeforeTimer -= clocks;
if (cpu->clockBeforeTimer <= 0) {
BYTE currentTimer = readMemory(cpu, TIMER_ADDR);
setTimerFreq(cpu);
writeMemory(cpu, TIMER_ADDR, currentTimer + 1);
/*The timer should overflow ?*/
if (currentTimer == 255) {
writeMemory(cpu, TIMER_ADDR, readMemory(cpu, TIMER_DEFAULT));
raiseInterrupt(cpu, 2);
}
}
}
}
void raiseInterrupt(gb *cpu, BYTE code) {
BYTE interruptRequest = readMemory(cpu, INTERRUPT_REQUEST_ADDR);
interruptRequest |= (0x1 << code);
writeMemory(cpu, INTERRUPT_REQUEST_ADDR, interruptRequest);
cpu->cpuHalted = 1;
}
void handleInterrupts(gb *cpu) {
BYTE req = readMemory(cpu, INTERRUPT_REQUEST_ADDR);
BYTE enabled = readMemory(cpu, INTERRUPT_ENABLED_ADDR);
BYTE mask = req & enabled;
if (cpu->master_interr_switch != 0 && mask != 0) {
/*Interrupts are enabled and an interrupt is set, let's raise it */
for (BYTE i = 0; i < 5; i++) {
if ((mask & (0x1 << i)) != 0) {
//Disable other interrupts
cpu->master_interr_switch = 0;
//Mark interrupt as handled
req &= ~(0x1 << i);
writeMemory(cpu, INTERRUPT_REQUEST_ADDR, req);
WORD currentPC = cpu->progCounter;
PUSH(cpu, cpu->stack, currentPC);
//Which routine should we use?
cpu->progCounter = 0x40 + (i<<3);
return;
}
}
}
}

45
lib/mbc1.c Executable file
View File

@@ -0,0 +1,45 @@
#include "gb-impl.h"
void mbc1_changeBank(gb *cpu, WORD addr, BYTE data) {
// RAM enabling
if (addr < 0x2000) {
BYTE testData = data & 0xF;
if (testData == 0x0) {
cpu->isRAMEnable = 0;
} else {
cpu->isRAMEnable = 1;
}
}
/*Change ROM bank*/
else if ((addr >= 0x2000) && (addr < 0x4000)) {
BYTE lower5 = data & 31;
cpu->currentROMBank &= 224; // turn off the lower 5
cpu->currentROMBank |= lower5;
if (cpu->currentROMBank == 0 || cpu->currentROMBank == 0x20 ||
cpu->currentROMBank == 0x40 || cpu->currentROMBank == 0x60)
cpu->currentROMBank++;
}
/*Depending from the MBC1 type, I have to change something*/
else if ((addr >= 0x4000) && (addr < 0x6000)) {
if (cpu->ROMBankType == 1) {
cpu->currentROMBank &= 31;
data &= 0x3;
data <<= 5;
cpu->currentROMBank |= data;
if (cpu->currentROMBank == 0 || cpu->currentROMBank == 0x20 ||
cpu->currentROMBank == 0x40 || cpu->currentROMBank == 0x60)
cpu->currentROMBank++;
} else
cpu->currentRAMBank = data & 0x3;
}
/*Determine which type of MBC1 is*/
else if ((addr >= 0x6000) && (addr < 0x8000)) {
BYTE newData = data & 0x1;
cpu->ROMBankType = (newData == 0) ? 1 : 0;
if (cpu->ROMBankType == 1)
cpu->currentRAMBank = 0;
}
}

30
lib/mbc3.c Executable file
View File

@@ -0,0 +1,30 @@
#include "gb-impl.h"
void mbc3_changeBank(gb *cpu, WORD addr, BYTE data) {
// RAM enabling
if (addr < 0x2000) {
BYTE testData = data & 0xF;
if (testData == 0x0) {
cpu->isRAMEnable = 0;
} else
cpu->isRAMEnable = 1;
}
/*Change ROM bank*/
else if ((addr >= 0x2000) && (addr < 0x4000)) {
BYTE lower7 = data & 0x7F;
cpu->currentROMBank = lower7;
if (cpu->currentROMBank == 0x00)
cpu->currentROMBank = 1;
}
else if ((addr >= 0x4000) && (addr < 0x6000)) {
if (data <= 0x3)
cpu->currentRAMBank = data & 0x3;
}
/*Should handle the timer*/
else if ((addr >= 0x6000) && (addr < 0x8000)) {
}
}

53
lib/panzgb.h Executable file
View File

@@ -0,0 +1,53 @@
#ifndef PANZGB_H
#define PANZGB_H
/*
* BYTE must always be an unsigned, 8 bit value
* WORD must always be an unsigned, 16 bit value
*
* Must be changed, depending the architecture (if the compiler
* doesn't support C99 types)
*/
#include <stdint.h>
typedef uint8_t BYTE;
typedef int8_t SIGNED_BYTE;
typedef uint16_t WORD;
typedef int16_t SIGNED_WORD;
#define GB_CLOCK 4194304
#define GB_SCREEN_REFRESH_RATE 59.7
typedef struct gameboy gb;
gb *newGameboy(char *rom);
char *getGameName(gb *cpu);
BYTE executeGameBoy(gb *cpu);
/* Those macros are for handling the gameboy keymap*/
#define GB_K_RIGHT 0x0
#define GB_K_LEFT 0x1
#define GB_K_UP 0x2
#define GB_K_DOWN 0x3
#define GB_K_A 0x4
#define GB_K_B 0x5
#define GB_K_SELECT 0x6
#define GB_K_START 0x7
#define GB_KEY_PRESSED 0x0
#define GB_KEY_RELEASED 0x1
void changeKeyState(gb *cpu, BYTE key, BYTE state);
#define WHITE_COL 0x00
#define LIGHTGRAY_COL 0x77//0x55//0x77
#define GRAY_COL 0xcc//0xaa
#define BLACK_COL 0xff
BYTE getPixelColor(gb *cpu, BYTE x, BYTE y);
void setGbBanking(gb *cpu);
BYTE* getSoundData(gb *cpu, unsigned int* size);
#endif

127
panzgb.c Executable file
View File

@@ -0,0 +1,127 @@
#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "lib/panzgb.h"
#define NUM_OP_60HZ (GB_CLOCK / GB_SCREEN_REFRESH_RATE)
#define SCALE 4
void doScreenshoot(SDL_Renderer *renderer) {
time_t name;
name = time(NULL);
SDL_Surface *sshot =
SDL_CreateRGBSurface(0, 160 * SCALE, 144 * SCALE, 32, 0x00ff0000,
0x0000ff00, 0x000000ff, 0xff000000);
SDL_RenderReadPixels(renderer, NULL, SDL_PIXELFORMAT_ARGB8888,
sshot->pixels, sshot->pitch);
SDL_SaveBMP(sshot, ctime(&name));
SDL_FreeSurface(sshot);
}
void renderScreen(gb *cpu, SDL_Renderer *rend) {
int x, y;
int j;
SDL_Rect rectangle;
rectangle.h = SCALE;
SDL_SetRenderDrawColor(rend, 0, 0, 0, SDL_ALPHA_OPAQUE);
SDL_RenderClear(rend);
for (y = 0; y < 144; y++) {
for (x = 0; x < 160; x++) {
BYTE color = getPixelColor(cpu, x, y);
for (j = x + 1; color == getPixelColor(cpu, j, y) && j < 160; j++)
;
rectangle.w = SCALE * (j - x);
rectangle.x = x * SCALE;
rectangle.y = y * SCALE;
x = j - 1;
SDL_SetRenderDrawColor(rend, color, color, color, SDL_ALPHA_OPAQUE);
SDL_RenderFillRect(rend, &rectangle);
}
}
SDL_RenderPresent(rend);
}
void getInput(gb *cpu, SDL_Renderer *rend) {
SDL_Event event;
SIGNED_BYTE key = 0;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
exit(EXIT_SUCCESS);
}
if (event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) {
switch (event.key.keysym.sym) {
case SDLK_z:
key = GB_K_A;
break;
case SDLK_x:
key = GB_K_B;
break;
case SDLK_RETURN:
key = GB_K_START;
break;
case SDLK_SPACE:
key = GB_K_SELECT;
break;
case SDLK_UP:
key = GB_K_UP;
break;
case SDLK_LEFT:
key = GB_K_LEFT;
break;
case SDLK_RIGHT:
key = GB_K_RIGHT;
break;
case SDLK_DOWN:
key = GB_K_DOWN;
break;
case SDLK_f:
doScreenshoot(rend);
return;
default:
return;
}
if (event.type == SDL_KEYUP) {
changeKeyState(cpu, key, GB_KEY_RELEASED);
} else {
changeKeyState(cpu, key, GB_KEY_PRESSED);
}
}
}
}
gb *gameboy;
int main(int argc, char **argv) {
SDL_Init(SDL_INIT_EVERYTHING);
SDL_Window *window = SDL_CreateWindow("panz-gb", SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED, 160 * SCALE,
144 * SCALE, 0);
SDL_Renderer *renderer =
SDL_CreateRenderer(window, -1, 0);
gameboy = newGameboy(argv[1]);
if (!gameboy) {
fprintf(stderr, "Error on memory allocation\n");
exit(EXIT_FAILURE);
}
unsigned int numOperation = 0;
while (1) {
unsigned int timeStartFrame = SDL_GetTicks();
getInput(gameboy, renderer);
while (numOperation < NUM_OP_60HZ)
numOperation += executeGameBoy(gameboy);
numOperation -= NUM_OP_60HZ;
renderScreen(gameboy, renderer);
float deltaT =
(float)1000 / (59.7) - (float)(SDL_GetTicks() - timeStartFrame);
if (deltaT > 0)
SDL_Delay((unsigned int)deltaT);
}
}