public release of panzgb
This commit is contained in:
2
.gitignore
vendored
Executable file
2
.gitignore
vendored
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
.idea
|
||||||
|
build/
|
||||||
22
CMakeLists.txt
Normal file
22
CMakeLists.txt
Normal 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
21
LICENSE
Executable 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
25
README.md
Executable 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
180
lib/gb-impl.h
Executable 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
84
lib/gb-memory.c
Executable 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
47
lib/gb-opcode.h
Executable 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
448
lib/gb-opcodes-impl.c
Executable 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
1804
lib/gb-opcodes.c
Executable file
File diff suppressed because it is too large
Load Diff
277
lib/gb-sound.c
Executable file
277
lib/gb-sound.c
Executable 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
86
lib/gb-sound.h
Executable 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
328
lib/gb-video.c
Executable 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
172
lib/gc-imp.c
Executable 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
76
lib/gc-interrupts.c
Executable 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
45
lib/mbc1.c
Executable 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
30
lib/mbc3.c
Executable 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
53
lib/panzgb.h
Executable 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
127
panzgb.c
Executable 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user