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

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