early push to at least have most things secured

This commit is contained in:
FIX94 2017-05-12 05:22:59 +02:00
parent 84bb28dbad
commit 42df24cb69
No known key found for this signature in database
GPG Key ID: CE39016A19D8EADA
17 changed files with 4250 additions and 1 deletions

View File

@ -1,2 +1,2 @@
# fixGB
yet another GB Emulator
This is still quite early in development so it cant do all that much, its essentially the GB version of my NES Emulator fixNES.

328
alhelpers.c Normal file
View File

@ -0,0 +1,328 @@
/*
* OpenAL Helpers
*
* Copyright (c) 2011 by Chris Robinson <chris.kcat@gmail.com>
*
* 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.
*/
/* This file contains routines to help with some menial OpenAL-related tasks,
* such as opening a device and setting up a context, closing the device and
* destroying its context, converting between frame counts and byte lengths,
* finding an appropriate buffer format, and getting readable strings for
* channel configs and sample types. */
#include <stdio.h>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/alext.h"
#include "alhelpers.h"
/* InitAL opens the default device and sets up a context using default
* attributes, making the program ready to call OpenAL functions. */
int InitAL(void)
{
ALCdevice *device;
ALCcontext *ctx;
/* Open and initialize a device with default settings */
device = alcOpenDevice(NULL);
if(!device)
{
fprintf(stderr, "Could not open a device!\n");
return 1;
}
ctx = alcCreateContext(device, NULL);
if(ctx == NULL || alcMakeContextCurrent(ctx) == ALC_FALSE)
{
if(ctx != NULL)
alcDestroyContext(ctx);
alcCloseDevice(device);
fprintf(stderr, "Could not set a context!\n");
return 1;
}
//printf("Opened \"%s\"\n", alcGetString(device, ALC_DEVICE_SPECIFIER));
return 0;
}
/* CloseAL closes the device belonging to the current context, and destroys the
* context. */
void CloseAL(void)
{
ALCdevice *device;
ALCcontext *ctx;
ctx = alcGetCurrentContext();
if(ctx == NULL)
return;
device = alcGetContextsDevice(ctx);
//printf("Closed \"%s\"\n", alcGetString(device, ALC_DEVICE_SPECIFIER));
alcMakeContextCurrent(NULL);
alcDestroyContext(ctx);
alcCloseDevice(device);
}
/* GetFormat retrieves a compatible buffer format given the channel config and
* sample type. If an alIsBufferFormatSupportedSOFT-compatible function is
* provided, it will be called to find the closest-matching format from
* AL_SOFT_buffer_samples. Returns AL_NONE (0) if no supported format can be
* found. */
ALenum GetFormat(ALenum channels, ALenum type, LPALISBUFFERFORMATSUPPORTEDSOFT palIsBufferFormatSupportedSOFT)
{
ALenum format = AL_NONE;
/* If using AL_SOFT_buffer_samples, try looking through its formats */
if(palIsBufferFormatSupportedSOFT)
{
/* AL_SOFT_buffer_samples is more lenient with matching formats. The
* specified sample type does not need to match the returned format,
* but it is nice to try to get something close. */
if(type == AL_UNSIGNED_BYTE_SOFT || type == AL_BYTE_SOFT)
{
if(channels == AL_MONO_SOFT) format = AL_MONO8_SOFT;
else if(channels == AL_STEREO_SOFT) format = AL_STEREO8_SOFT;
else if(channels == AL_QUAD_SOFT) format = AL_QUAD8_SOFT;
else if(channels == AL_5POINT1_SOFT) format = AL_5POINT1_8_SOFT;
else if(channels == AL_6POINT1_SOFT) format = AL_6POINT1_8_SOFT;
else if(channels == AL_7POINT1_SOFT) format = AL_7POINT1_8_SOFT;
}
else if(type == AL_UNSIGNED_SHORT_SOFT || type == AL_SHORT_SOFT)
{
if(channels == AL_MONO_SOFT) format = AL_MONO16_SOFT;
else if(channels == AL_STEREO_SOFT) format = AL_STEREO16_SOFT;
else if(channels == AL_QUAD_SOFT) format = AL_QUAD16_SOFT;
else if(channels == AL_5POINT1_SOFT) format = AL_5POINT1_16_SOFT;
else if(channels == AL_6POINT1_SOFT) format = AL_6POINT1_16_SOFT;
else if(channels == AL_7POINT1_SOFT) format = AL_7POINT1_16_SOFT;
}
else if(type == AL_UNSIGNED_BYTE3_SOFT || type == AL_BYTE3_SOFT ||
type == AL_UNSIGNED_INT_SOFT || type == AL_INT_SOFT ||
type == AL_FLOAT_SOFT || type == AL_DOUBLE_SOFT)
{
if(channels == AL_MONO_SOFT) format = AL_MONO32F_SOFT;
else if(channels == AL_STEREO_SOFT) format = AL_STEREO32F_SOFT;
else if(channels == AL_QUAD_SOFT) format = AL_QUAD32F_SOFT;
else if(channels == AL_5POINT1_SOFT) format = AL_5POINT1_32F_SOFT;
else if(channels == AL_6POINT1_SOFT) format = AL_6POINT1_32F_SOFT;
else if(channels == AL_7POINT1_SOFT) format = AL_7POINT1_32F_SOFT;
}
if(format != AL_NONE && !palIsBufferFormatSupportedSOFT(format))
format = AL_NONE;
/* A matching format was not found or supported. Try 32-bit float. */
if(format == AL_NONE)
{
if(channels == AL_MONO_SOFT) format = AL_MONO32F_SOFT;
else if(channels == AL_STEREO_SOFT) format = AL_STEREO32F_SOFT;
else if(channels == AL_QUAD_SOFT) format = AL_QUAD32F_SOFT;
else if(channels == AL_5POINT1_SOFT) format = AL_5POINT1_32F_SOFT;
else if(channels == AL_6POINT1_SOFT) format = AL_6POINT1_32F_SOFT;
else if(channels == AL_7POINT1_SOFT) format = AL_7POINT1_32F_SOFT;
if(format != AL_NONE && !palIsBufferFormatSupportedSOFT(format))
format = AL_NONE;
}
/* 32-bit float not supported. Try 16-bit int. */
if(format == AL_NONE)
{
if(channels == AL_MONO_SOFT) format = AL_MONO16_SOFT;
else if(channels == AL_STEREO_SOFT) format = AL_STEREO16_SOFT;
else if(channels == AL_QUAD_SOFT) format = AL_QUAD16_SOFT;
else if(channels == AL_5POINT1_SOFT) format = AL_5POINT1_16_SOFT;
else if(channels == AL_6POINT1_SOFT) format = AL_6POINT1_16_SOFT;
else if(channels == AL_7POINT1_SOFT) format = AL_7POINT1_16_SOFT;
if(format != AL_NONE && !palIsBufferFormatSupportedSOFT(format))
format = AL_NONE;
}
/* 16-bit int not supported. Try 8-bit int. */
if(format == AL_NONE)
{
if(channels == AL_MONO_SOFT) format = AL_MONO8_SOFT;
else if(channels == AL_STEREO_SOFT) format = AL_STEREO8_SOFT;
else if(channels == AL_QUAD_SOFT) format = AL_QUAD8_SOFT;
else if(channels == AL_5POINT1_SOFT) format = AL_5POINT1_8_SOFT;
else if(channels == AL_6POINT1_SOFT) format = AL_6POINT1_8_SOFT;
else if(channels == AL_7POINT1_SOFT) format = AL_7POINT1_8_SOFT;
if(format != AL_NONE && !palIsBufferFormatSupportedSOFT(format))
format = AL_NONE;
}
return format;
}
/* We use the AL_EXT_MCFORMATS extension to provide output of Quad, 5.1,
* and 7.1 channel configs, AL_EXT_FLOAT32 for 32-bit float samples, and
* AL_EXT_DOUBLE for 64-bit float samples. */
if(type == AL_UNSIGNED_BYTE_SOFT)
{
if(channels == AL_MONO_SOFT)
format = AL_FORMAT_MONO8;
else if(channels == AL_STEREO_SOFT)
format = AL_FORMAT_STEREO8;
else if(alIsExtensionPresent("AL_EXT_MCFORMATS"))
{
if(channels == AL_QUAD_SOFT)
format = alGetEnumValue("AL_FORMAT_QUAD8");
else if(channels == AL_5POINT1_SOFT)
format = alGetEnumValue("AL_FORMAT_51CHN8");
else if(channels == AL_6POINT1_SOFT)
format = alGetEnumValue("AL_FORMAT_61CHN8");
else if(channels == AL_7POINT1_SOFT)
format = alGetEnumValue("AL_FORMAT_71CHN8");
}
}
else if(type == AL_SHORT_SOFT)
{
if(channels == AL_MONO_SOFT)
format = AL_FORMAT_MONO16;
else if(channels == AL_STEREO_SOFT)
format = AL_FORMAT_STEREO16;
else if(alIsExtensionPresent("AL_EXT_MCFORMATS"))
{
if(channels == AL_QUAD_SOFT)
format = alGetEnumValue("AL_FORMAT_QUAD16");
else if(channels == AL_5POINT1_SOFT)
format = alGetEnumValue("AL_FORMAT_51CHN16");
else if(channels == AL_6POINT1_SOFT)
format = alGetEnumValue("AL_FORMAT_61CHN16");
else if(channels == AL_7POINT1_SOFT)
format = alGetEnumValue("AL_FORMAT_71CHN16");
}
}
else if(type == AL_FLOAT_SOFT && alIsExtensionPresent("AL_EXT_FLOAT32"))
{
if(channels == AL_MONO_SOFT)
format = alGetEnumValue("AL_FORMAT_MONO_FLOAT32");
else if(channels == AL_STEREO_SOFT)
format = alGetEnumValue("AL_FORMAT_STEREO_FLOAT32");
else if(alIsExtensionPresent("AL_EXT_MCFORMATS"))
{
if(channels == AL_QUAD_SOFT)
format = alGetEnumValue("AL_FORMAT_QUAD32");
else if(channels == AL_5POINT1_SOFT)
format = alGetEnumValue("AL_FORMAT_51CHN32");
else if(channels == AL_6POINT1_SOFT)
format = alGetEnumValue("AL_FORMAT_61CHN32");
else if(channels == AL_7POINT1_SOFT)
format = alGetEnumValue("AL_FORMAT_71CHN32");
}
}
else if(type == AL_DOUBLE_SOFT && alIsExtensionPresent("AL_EXT_DOUBLE"))
{
if(channels == AL_MONO_SOFT)
format = alGetEnumValue("AL_FORMAT_MONO_DOUBLE");
else if(channels == AL_STEREO_SOFT)
format = alGetEnumValue("AL_FORMAT_STEREO_DOUBLE");
}
/* NOTE: It seems OSX returns -1 from alGetEnumValue for unknown enums, as
* opposed to 0. Correct it. */
if(format == -1)
format = 0;
return format;
}
void AL_APIENTRY wrap_BufferSamples(ALuint buffer, ALuint samplerate,
ALenum internalformat, ALsizei samples,
ALenum channels, ALenum type,
const ALvoid *data)
{
alBufferData(buffer, internalformat, data,
FramesToBytes(samples, channels, type),
samplerate);
}
const char *ChannelsName(ALenum chans)
{
switch(chans)
{
case AL_MONO_SOFT: return "Mono";
case AL_STEREO_SOFT: return "Stereo";
case AL_REAR_SOFT: return "Rear";
case AL_QUAD_SOFT: return "Quadraphonic";
case AL_5POINT1_SOFT: return "5.1 Surround";
case AL_6POINT1_SOFT: return "6.1 Surround";
case AL_7POINT1_SOFT: return "7.1 Surround";
}
return "Unknown Channels";
}
const char *TypeName(ALenum type)
{
switch(type)
{
case AL_BYTE_SOFT: return "S8";
case AL_UNSIGNED_BYTE_SOFT: return "U8";
case AL_SHORT_SOFT: return "S16";
case AL_UNSIGNED_SHORT_SOFT: return "U16";
case AL_INT_SOFT: return "S32";
case AL_UNSIGNED_INT_SOFT: return "U32";
case AL_FLOAT_SOFT: return "Float32";
case AL_DOUBLE_SOFT: return "Float64";
}
return "Unknown Type";
}
ALsizei FramesToBytes(ALsizei size, ALenum channels, ALenum type)
{
switch(channels)
{
case AL_MONO_SOFT: size *= 1; break;
case AL_STEREO_SOFT: size *= 2; break;
case AL_REAR_SOFT: size *= 2; break;
case AL_QUAD_SOFT: size *= 4; break;
case AL_5POINT1_SOFT: size *= 6; break;
case AL_6POINT1_SOFT: size *= 7; break;
case AL_7POINT1_SOFT: size *= 8; break;
}
switch(type)
{
case AL_BYTE_SOFT: size *= sizeof(ALbyte); break;
case AL_UNSIGNED_BYTE_SOFT: size *= sizeof(ALubyte); break;
case AL_SHORT_SOFT: size *= sizeof(ALshort); break;
case AL_UNSIGNED_SHORT_SOFT: size *= sizeof(ALushort); break;
case AL_INT_SOFT: size *= sizeof(ALint); break;
case AL_UNSIGNED_INT_SOFT: size *= sizeof(ALuint); break;
case AL_FLOAT_SOFT: size *= sizeof(ALfloat); break;
case AL_DOUBLE_SOFT: size *= sizeof(ALdouble); break;
}
return size;
}
ALsizei BytesToFrames(ALsizei size, ALenum channels, ALenum type)
{
return size / FramesToBytes(1, channels, type);
}

43
alhelpers.h Normal file
View File

@ -0,0 +1,43 @@
#ifndef ALHELPERS_H
#define ALHELPERS_H
#include "AL/alc.h"
#include "AL/al.h"
#include "AL/alext.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/* Some helper functions to get the name from the channel and type enums. */
const char *ChannelsName(ALenum chans);
const char *TypeName(ALenum type);
/* Helpers to convert frame counts and byte lengths. */
ALsizei FramesToBytes(ALsizei size, ALenum channels, ALenum type);
ALsizei BytesToFrames(ALsizei size, ALenum channels, ALenum type);
/* Retrieves a compatible buffer format given the channel configuration and
* sample type. If an alIsBufferFormatSupportedSOFT-compatible function is
* provided, it will be called to find the closest-matching format from
* AL_SOFT_buffer_samples. Returns AL_NONE (0) if no supported format can be
* found. */
ALenum GetFormat(ALenum channels, ALenum type, LPALISBUFFERFORMATSUPPORTEDSOFT palIsBufferFormatSupportedSOFT);
/* Loads samples into a buffer using the standard alBufferData call, but with a
* LPALBUFFERSAMPLESSOFT-compatible prototype. Assumes internalformat is valid
* for alBufferData, and that channels and type match it. */
void AL_APIENTRY wrap_BufferSamples(ALuint buffer, ALuint samplerate,
ALenum internalformat, ALsizei samples,
ALenum channels, ALenum type,
const ALvoid *data);
/* Easy device init/deinit functions. InitAL returns 0 on success. */
int InitAL(void);
void CloseAL(void);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* ALHELPERS_H */

613
apu.c Normal file
View File

@ -0,0 +1,613 @@
/*
* Copyright (C) 2017 FIX94
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include <stdio.h>
#include <stdbool.h>
#include <inttypes.h>
#include <malloc.h>
#include "apu.h"
#include "audio.h"
#include "mem.h"
#include "cpu.h"
#define P1_ENABLE (1<<0)
#define P2_ENABLE (1<<1)
#define TRI_ENABLE (1<<2)
#define NOISE_ENABLE (1<<3)
#define DMC_ENABLE (1<<4)
#define PULSE_CONST_V (1<<4)
#define PULSE_HALT_LOOP (1<<5)
#define TRI_HALT_LOOP (1<<7)
#define DMC_HALT_LOOP (1<<6)
#define DMC_IRQ_ENABLE (1<<7)
static uint8_t APU_IO_Reg[0x40];
static float lpVal;
static float hpVal;
static float *apuOutBuf;
static uint32_t apuBufSize;
static uint32_t apuBufSizeBytes;
static uint32_t curBufPos;
static uint32_t apuFrequency;
static uint16_t freq1;
static uint16_t freq2;
static uint16_t triFreq;
static uint16_t noiseFreq;
static uint16_t noiseShiftReg;
static uint16_t dmcFreq;
static uint16_t dmcAddr, dmcLen, dmcSampleBuf;
static uint16_t dmcCurAddr, dmcCurLen;
static uint8_t p1LengthCtr, p2LengthCtr, noiseLengthCtr;
static uint8_t triLinearCtr, triCurLinearCtr;
static uint16_t triLengthCtr;
static uint8_t dmcVol, dmcCurVol;
static uint8_t dmcSampleRemain;
static uint8_t triVolShift;
static uint16_t modeCurCtr = 0;
static uint16_t p1freqCtr, p2freqCtr, triFreqCtr, noiseFreqCtr;
static uint8_t p1Cycle, p2Cycle, triCycle;
static uint8_t modePos = 0;
static bool p1haltloop, p2haltloop, trihaltloop, noisehaltloop;
static bool dmcstart;
static bool dmcirqenable;
static bool trireload;
static bool noiseMode1;
static bool apu_enable_irq;
static envelope_t p1Env, p2Env, noiseEnv;
typedef struct _sweep_t {
bool enabled;
bool start;
bool negative;
bool mute;
bool chan1;
uint8_t period;
uint8_t divider;
uint8_t shift;
} sweep_t;
static sweep_t p1Sweep, p2Sweep;
static float pulseLookupTbl[32];
static float tndLookupTbl[204];
//used externally
const uint8_t lengthLookupTbl[0x20] = {
10,254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14,
12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30
};
//used externally
const uint8_t pulseSeqs[4][8] = {
{ 0, 1, 0, 0, 0, 0, 0, 0 },
{ 0, 1, 1, 0, 0, 0, 0, 0 },
{ 0, 1, 1, 1, 1, 0, 0, 0 },
{ 1, 0, 0, 1, 1, 1, 1, 1 },
};
static const uint16_t noisePeriodNtsc[8] = {
8, 16, 32, 48, 64, 80, 96, 112,
};
//used externally
const uint16_t *noisePeriod;
static const uint8_t *p1seq = pulseSeqs[0],
*p2seq = pulseSeqs[1];
//extern bool dmc_interrupt;
#define M_2_PI 6.28318530717958647692
void apuInitBufs()
{
noisePeriod = noisePeriodNtsc;
//effective frequency for 60.000Hz Video out
apuFrequency = 1053360;
double dt = 1.0/((double)apuFrequency);
//LP at 22kHz
double rc = 1.0/(M_2_PI * 22000.0);
lpVal = dt / (rc + dt);
//HP at 40Hz
rc = 1.0/(M_2_PI * 40.0);
hpVal = rc / (rc + dt);
apuBufSize = apuFrequency/60;
apuBufSizeBytes = apuBufSize*sizeof(float);
apuOutBuf = (float*)malloc(apuBufSizeBytes);
/* https://wiki.nesdev.com/w/index.php/APU_Mixer#Lookup_Table */
uint8_t i;
for(i = 0; i < 32; i++)
pulseLookupTbl[i] = 95.52 / ((8128.0 / i) + 100);
for(i = 0; i < 204; i++)
tndLookupTbl[i] = 163.67 / ((24329.0 / i) + 100);
}
void apuDeinitBufs()
{
if(apuOutBuf)
free(apuOutBuf);
apuOutBuf = NULL;
}
void apuInit()
{
memset(APU_IO_Reg,0,0x40);
memset(apuOutBuf, 0, apuBufSizeBytes);
curBufPos = 0;
freq1 = 0; freq2 = 0; triFreq = 0; noiseFreq = 0, dmcFreq = 0;
noiseShiftReg = 1;
p1LengthCtr = 0; p2LengthCtr = 0;
noiseLengthCtr = 0; triLengthCtr = 0;
triLinearCtr = 0; triCurLinearCtr = 0;
dmcAddr = 0, dmcLen = 0, dmcVol = 0; dmcSampleBuf = 0;
dmcCurAddr = 0, dmcCurLen = 0; dmcCurVol = 0;
dmcSampleRemain = 0;
p1freqCtr = 0; p2freqCtr = 0; triFreqCtr = 0, noiseFreqCtr = 0;
p1Cycle = 0; p2Cycle = 0; triCycle = 0;
triVolShift = 0;
memset(&p1Env,0,sizeof(envelope_t));
memset(&p2Env,0,sizeof(envelope_t));
memset(&noiseEnv,0,sizeof(envelope_t));
memset(&p1Sweep,0,sizeof(sweep_t));
p1Sweep.chan1 = true; //for negative sweep
memset(&p2Sweep,0,sizeof(sweep_t));
p2Sweep.chan1 = false;
p1haltloop = false; p2haltloop = false;
trihaltloop = false; noisehaltloop = false;
dmcstart = false;
dmcirqenable = false;
trireload = false;
noiseMode1 = false;
//4017 starts out as 0, so enable
apu_enable_irq = true;
}
extern uint32_t cpu_oam_dma;
void apuClockTimers()
{
if(p1freqCtr)
p1freqCtr--;
if(p1freqCtr == 0)
{
if(freq1)
p1freqCtr = (2048-freq1)*4;
p1Cycle++;
if(p1Cycle >= 8)
p1Cycle = 0;
}
if(p2freqCtr)
p2freqCtr--;
if(p2freqCtr == 0)
{
if(freq2)
p2freqCtr = (2048-freq2)*4;
p2Cycle++;
if(p2Cycle >= 8)
p2Cycle = 0;
}
if(triFreqCtr)
triFreqCtr--;
if(triFreqCtr == 0)
{
triFreqCtr = (2048-triFreq)*2;
triCycle++;
if(triCycle >= 32)
triCycle = 0;
}
if(noiseFreqCtr)
noiseFreqCtr--;
if(noiseFreqCtr == 0)
{
noiseFreqCtr = noiseFreq;
uint8_t cmpBit = noiseMode1 ? (noiseShiftReg>>6)&1 : (noiseShiftReg>>1)&1;
uint8_t cmpRes = (noiseShiftReg&1)^cmpBit;
noiseShiftReg >>= 1;
noiseShiftReg |= cmpRes<<14;
}
}
static float lastHPOut = 0, lastLPOut = 0;
static uint8_t lastP1Out = 0, lastP2Out = 0, lastTriOut = 0, lastNoiseOut = 0;
extern bool emuSkipVsync, emuSkipFrame;
bool apuCycle()
{
if(curBufPos == apuBufSize)
{
int updateRes = audioUpdate();
if(updateRes == 0)
{
emuSkipFrame = false;
emuSkipVsync = false;
return false;
}
if(updateRes > 6)
{
emuSkipVsync = true;
emuSkipFrame = true;
}
else
{
emuSkipFrame = false;
if(updateRes > 2)
emuSkipVsync = true;
else
emuSkipVsync = false;
}
curBufPos = 0;
}
uint8_t p1Out = lastP1Out, p2Out = lastP2Out,
triOut = lastTriOut, noiseOut = lastNoiseOut;
if(p1LengthCtr && ((APU_IO_Reg[0x25]|(APU_IO_Reg[0x25]>>4)) & P1_ENABLE))
{
if(p1seq[p1Cycle] && !p1Sweep.mute && freq1 >= 0x100 && freq1 < 0x7FF)
lastP1Out = p1Out = p1Env.vol;
else
p1Out = 0;
}
if(p2LengthCtr && ((APU_IO_Reg[0x25]|(APU_IO_Reg[0x25]>>4)) & P2_ENABLE))
{
if(p2seq[p2Cycle] && freq2 >= 0x100 && freq2 < 0x7FF)
lastP2Out = p2Out = p2Env.vol;
else
p2Out = 0;
}
if(triLengthCtr && triCurLinearCtr && ((APU_IO_Reg[0x25]|(APU_IO_Reg[0x25]>>4)) & TRI_ENABLE))
{
uint8_t v = APU_IO_Reg[0x30+(triCycle>>1)];
if((triCycle&1)==0)
v >>= 4;
else
v &= 0xF;
v>>=triVolShift;
if(v)// && triFreq >= 2)
lastTriOut = triOut = v;
else
triOut = 0;
}
if(noiseLengthCtr && ((APU_IO_Reg[0x25]|(APU_IO_Reg[0x25]>>4)) & NOISE_ENABLE))
{
if((noiseShiftReg&1) == 0 && noiseFreq > 0)
lastNoiseOut = noiseOut = noiseEnv.vol;
else
noiseOut = 0;
}
float curIn = pulseLookupTbl[p1Out + p2Out] + tndLookupTbl[(3*triOut) + (2*noiseOut) + dmcVol];
float curLPout = lastLPOut+(lpVal*(curIn-lastLPOut));
float curHPOut = hpVal*(lastHPOut+curLPout-curIn);
//set output
apuOutBuf[curBufPos] = -curHPOut;
lastLPOut = curLPout;
lastHPOut = curHPOut;
curBufPos++;
return true;
}
void doEnvelopeLogic(envelope_t *env)
{
if(env->divider == 0)
{
if(env->period)
{
if(env->modeadd)
{
if(env->vol < 15)
env->vol++;
}
else
{
if(env->vol > 0)
env->vol--;
}
}
env->divider = env->period;
}
else
env->divider--;
//too slow on its own?
//env->envelope = (env->constant ? env->vol : env->decay);
}
void sweepUpdateFreq(sweep_t *sw, uint16_t *freq)
{
uint16_t inFreq = *freq;
if(sw->shift > 0)
{
if(sw->negative)
{
inFreq -= (inFreq >> sw->shift);
if(sw->chan1 == true) inFreq--;
}
else
inFreq += (inFreq >> sw->shift);
}
if(inFreq > 0x100 && (inFreq < 0x7FF))
{
sw->mute = false;
if(sw->enabled && sw->shift)
*freq = inFreq;
}
else
sw->mute = true;
}
void doSweepLogic(sweep_t *sw, uint16_t *freq)
{
if(sw->start)
{
uint8_t prevDiv = sw->divider;
sw->divider = sw->period;
sw->start = false;
if(prevDiv == 0)
sweepUpdateFreq(sw, freq);
}
else
{
if(sw->divider == 0)
{
sweepUpdateFreq(sw, freq);
sw->divider = sw->period;
}
else
sw->divider--;
}
//gets clocked too little on its own?
/*if(inFreq < 8 || (inFreq >= 0x7FF))
sw->mute = true;
else
sw->mute = false;*/
}
void apuClockA()
{
if(p1LengthCtr)
{
doSweepLogic(&p1Sweep, &freq1);
if(!p1haltloop)
p1LengthCtr--;
}
if(p2LengthCtr && !p2haltloop)
p2LengthCtr--;
if(triLengthCtr && !trihaltloop)
triLengthCtr--;
if(noiseLengthCtr && !noisehaltloop)
noiseLengthCtr--;
}
void apuClockB()
{
if(p1LengthCtr)
doEnvelopeLogic(&p1Env);
if(p2LengthCtr)
doEnvelopeLogic(&p2Env);
if(noiseLengthCtr)
doEnvelopeLogic(&noiseEnv);
}
//extern bool apu_interrupt;
void apuLenCycle()
{
if(modeCurCtr)
modeCurCtr--;
if(modeCurCtr == 0)
{
apuClockA();
modePos++;
if(modePos >= 4)
{
apuClockB();
modePos = 0;
}
modeCurCtr = 4096;
}
}
void apuSet8(uint8_t reg, uint8_t val)
{
//printf("APU %02x %02x\n", reg, val);
APU_IO_Reg[reg] = val;
if(reg == 0x10)
{
//printf("P1 sweep %02x\n", val);
p1Sweep.enabled = true;//((val&0x80) != 0);
p1Sweep.shift = val&7;
p1Sweep.period = (val>>4)&7;
//if(p1Sweep.period == 0)
// p1Sweep.period = 8;
p1Sweep.negative = ((val&0x8) != 0);
p1Sweep.start = true;
if(freq1 > 0x100 && (freq1 < 0x7FF))
p1Sweep.mute = false; //to be safe
doSweepLogic(&p1Sweep, &freq1);
}
else if(reg == 0x11)
{
p1seq = pulseSeqs[val>>6];
p1LengthCtr = 64-(val&0x3F);
if(freq1 > 0x100 && (freq1 < 0x7FF))
p1Sweep.mute = false; //to be safe
}
else if(reg == 0x12)
{
p1Env.vol = (val>>4)&0xF;
p1Env.modeadd = (val&8)!=0;
p1Env.period = val&7;
//if(p1Env.period==0)
// p1Env.period=8;
p1Env.divider = p1Env.period;
if(freq1 > 0x100 && (freq1 < 0x7FF))
p1Sweep.mute = false; //to be safe
}
else if(reg == 0x13)
{
freq1 = ((freq1&~0xFF) | val);
if(freq1 > 0x100 && (freq1 < 0x7FF))
p1Sweep.mute = false; //to be safe
}
else if(reg == 0x14)
{
p1haltloop = ((val&(1<<6)) == 0);
if(val&(1<<7))
{
if(p1LengthCtr == 0)
p1LengthCtr = 64;
if(freq1 > 0x100 && (freq1 < 0x7FF))
p1Sweep.mute = false; //to be safe
doSweepLogic(&p1Sweep, &freq1);
}
freq1 = (freq1&0xFF) | ((val&7)<<8);
//printf("P1 new freq %04x\n", freq1);
}
else if(reg == 0x16)
{
p2seq = pulseSeqs[val>>6];
p2LengthCtr = 64-(val&0x3F);
}
else if(reg == 0x17)
{
p2Env.vol = (val>>4)&0xF;
p2Env.modeadd = (val&8)!=0;
p2Env.period = val&7;
//if(p2Env.period==0)
// p2Env.period=8;
p2Env.divider = p2Env.period;
}
else if(reg == 0x18)
{
freq2 = ((freq2&~0xFF) | val);
}
else if(reg == 0x19)
{
p2haltloop = ((val&(1<<6)) == 0);
if(val&(1<<7))
{
if(p2LengthCtr == 0)
p2LengthCtr = 64;
}
freq2 = (freq2&0xFF) | ((val&7)<<8);
//printf("P2 new freq %04x\n", freq2);
}
else if(reg == 0x1A)
triCurLinearCtr = ((val&0x80)!=0);
else if(reg == 0x1B)
triLengthCtr = 256-val;
else if(reg == 0x1C)
{
//printf("TRIVolShift %i\n", (val>>5)&3);
switch((val>>5)&3)
{
case 0:
triVolShift=4;
break;
case 1:
triVolShift=0;
break;
case 2:
triVolShift=1;
break;
case 3:
triVolShift=2;
break;
}
}
else if(reg == 0x1D)
{
//printf("TRI time low %02x\n", val);
triFreq = ((triFreq&~0xFF) | val);
}
else if(reg == 0x1E)
{
trihaltloop = ((val&(1<<6)) == 0);
if(val&(1<<7))
{
if(triLengthCtr == 0)
triLengthCtr = 256;
}
triFreq = (triFreq&0xFF) | ((val&7)<<8);
//printf("TRI new freq %04x\n", triFreq);
}
else if(reg == 0x20)
{
noiseLengthCtr = 64-(val&0x3F);
}
else if(reg == 0x21)
{
noiseEnv.vol = (val>>4)&0xF;
noiseEnv.modeadd = (val&8)!=0;
noiseEnv.period=val&7;
//if(noiseEnv.period==0)
// noiseEnv.period=8;
noiseEnv.divider = noiseEnv.period;
}
else if(reg == 0x22)
{
if((val>>4)<14)
noiseFreq = noisePeriod[val&0x7]<<(val>>4);
else
noiseFreq = 0;
noiseMode1 = ((val&0x8) != 0);
}
else if(reg == 0x23)
{
noisehaltloop = ((val&(1<<6)) == 0);
if(val&(1<<7))
{
if(noiseLengthCtr == 0)
noiseLengthCtr = 64;
}
}
else if(reg == 0x25)
{
}
}
uint8_t apuGet8(uint8_t reg)
{
//printf("%08x\n", reg);
/*if(reg == 0x15)
{
//uint8_t intrflags = ((apu_interrupt<<6) | (dmc_interrupt<<7));
//uint8_t apuretval = ((p1LengthCtr > 0) | ((p2LengthCtr > 0)<<1) | ((triLengthCtr > 0)<<2) | ((noiseLengthCtr > 0)<<3) | ((dmcCurLen > 0)<<4) | intrflags);
//printf("Get 0x15 %02x\n",apuretval);
//apu_interrupt = false;
return 0;//apuretval;
}*/
return APU_IO_Reg[reg];
}
uint8_t *apuGetBuf()
{
return (uint8_t*)apuOutBuf;
}
uint32_t apuGetBufSize()
{
return apuBufSizeBytes;
}
uint32_t apuGetFrequency()
{
return apuFrequency;
}

35
apu.h Normal file
View File

@ -0,0 +1,35 @@
/*
* Copyright (C) 2017 FIX94
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#ifndef _apu_h_
#define _apu_h_
#define NUM_BUFFERS 10
void apuInitBufs();
void apuDeinitBufs();
void apuInit();
bool apuCycle();
void apuClockTimers();
uint8_t *apuGetBuf();
uint32_t apuGetBufSize();
uint32_t apuGetFrequency();
void apuSet8(uint8_t reg, uint8_t val);
uint8_t apuGet8(uint8_t reg);
void apuLenCycle();
typedef struct _envelope_t {
bool modeadd;
uint8_t vol;
uint8_t period;
uint8_t divider;
} envelope_t;
void doEnvelopeLogic(envelope_t *env);
#endif

224
audio.c Normal file
View File

@ -0,0 +1,224 @@
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include <malloc.h>
#include <math.h>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/alext.h"
#include "alhelpers.h"
#include "apu.h"
#if WINDOWS_BUILD
#include <windows.h>
#else
#include <unistd.h>
#endif
static LPALBUFFERSAMPLESSOFT alBufferSamplesSOFT = wrap_BufferSamples;
static LPALISBUFFERFORMATSUPPORTEDSOFT alIsBufferFormatSupportedSOFT;
typedef struct StreamPlayer {
/* These are the buffers and source to play out through OpenAL with */
ALuint buffers[NUM_BUFFERS];
ALuint source;
/* Handle for the audio file */
//FilePtr file;
/* The format of the output stream */
ALenum format;
ALenum channels;
ALenum type;
ALuint rate;
} StreamPlayer;
static StreamPlayer *NewPlayer(void);
static void DeletePlayer(StreamPlayer *player);
/* Creates a new player object, and allocates the needed OpenAL source and
* buffer objects. Error checking is simplified for the purposes of this
* example, and will cause an abort if needed. */
static StreamPlayer *NewPlayer(void)
{
StreamPlayer *player;
player = malloc(sizeof(*player));
//assert(player != NULL);
memset(player, 0, sizeof(*player));
/* Generate the buffers and source */
alGenBuffers(NUM_BUFFERS, player->buffers);
//assert(alGetError() == AL_NO_ERROR && "Could not create buffers");
alGenSources(1, &player->source);
//assert(alGetError() == AL_NO_ERROR && "Could not create source");
/* Set parameters so mono sources play out the front-center speaker and
* won't distance attenuate. */
alSource3i(player->source, AL_POSITION, 0, 0, -1);
alSourcei(player->source, AL_SOURCE_RELATIVE, AL_TRUE);
alSourcei(player->source, AL_ROLLOFF_FACTOR, 0);
//assert(alGetError() == AL_NO_ERROR && "Could not set source parameters");
return player;
}
/* Destroys a player object, deleting the source and buffers. No error handling
* since these calls shouldn't fail with a properly-made player object. */
static void DeletePlayer(StreamPlayer *player)
{
// ClosePlayerFile(player);
alDeleteSources(1, &player->source);
alDeleteBuffers(NUM_BUFFERS, player->buffers);
if(alGetError() != AL_NO_ERROR)
fprintf(stderr, "Failed to delete object IDs\n");
memset(player, 0, sizeof(*player));
free(player);
}
static int StartPlayer(StreamPlayer *player)
{
size_t i;
/* Rewind the source position and clear the buffer queue */
alSourceRewind(player->source);
alSourcei(player->source, AL_BUFFER, 0);
/* Fill the buffer queue with empty data */
for(i = 0;i < NUM_BUFFERS;i++)
{
uint8_t *data;
/* Get some data to give it to the buffer */
data = apuGetBuf();
if(!data) break;
alBufferSamplesSOFT(player->buffers[i], player->rate, player->format,
BytesToFrames(apuGetBufSize(), player->channels, player->type),
player->channels, player->type, data);
}
if(alGetError() != AL_NO_ERROR)
{
fprintf(stderr, "Error buffering for playback\n");
return 0;
}
/* Now queue and start playback! */
alSourceQueueBuffers(player->source, i, player->buffers);
alSourcePlay(player->source);
if(alGetError() != AL_NO_ERROR)
{
fprintf(stderr, "Error starting playback\n");
return 0;
}
return 1;
}
StreamPlayer *player = NULL;
int audioInit()
{
if(InitAL() != 0)
goto error;
if(alIsExtensionPresent("AL_SOFT_buffer_samples"))
{
alBufferSamplesSOFT = alGetProcAddress("alBufferSamplesSOFT");
alIsBufferFormatSupportedSOFT = alGetProcAddress("alIsBufferFormatSupportedSOFT");
}
player = NewPlayer();
player->channels = AL_MONO_SOFT;
player->rate = apuGetFrequency();
player->type = AL_FLOAT_SOFT;
player->format = GetFormat(player->channels, player->type, alIsBufferFormatSupportedSOFT);
if(player->format == 0)
{
fprintf(stderr, "Unsupported format (%s, %s)\n",
ChannelsName(player->channels), TypeName(player->type));
goto error;
}
StartPlayer(player);
return 0;
error:
return 1;
}
int audioUpdate()
{
ALint processed = 0, state;
/* Get relevant source info */
alGetSourcei(player->source, AL_SOURCE_STATE, &state);
alGetSourcei(player->source, AL_BUFFERS_PROCESSED, &processed);
if(alGetError() != AL_NO_ERROR)
{
fprintf(stderr, "Error checking source state\n");
return 0;
}
if(!processed)
return 0;
/* Unqueue and handle processed buffer */
ALuint bufid;
uint8_t *data;
alSourceUnqueueBuffers(player->source, 1, &bufid);
/* Read the next chunk of data, refill the buffer, and queue it
* back on the source */
data = apuGetBuf();
if(data != NULL)
{
alBufferSamplesSOFT(bufid, player->rate, player->format,
BytesToFrames(apuGetBufSize(), player->channels, player->type),
player->channels, player->type, data);
alSourceQueueBuffers(player->source, 1, &bufid);
}
if(alGetError() != AL_NO_ERROR)
{
fprintf(stderr, "Error buffering data\n");
return 0;
}
/* Make sure the source hasn't underrun */
if(state != AL_PLAYING && state != AL_PAUSED)
{
//ALint queued;
alSourcePlay(player->source);
if(alGetError() != AL_NO_ERROR)
{
fprintf(stderr, "Error restarting playback\n");
return 0;
}
}
return processed;
}
void audioDeinit()
{
if(player)
{
DeletePlayer(player);
player = NULL;
}
CloseAL();
}
void audioSleep()
{
#if WINDOWS_BUILD
Sleep(1);
#else
usleep(1000);
#endif
}

10
audio.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef _audio_h_
#define _audio_h_
int audioInit();
int audioUpdate();
void audioDeinit();
void audioSleep();
#endif

View File

@ -0,0 +1,2 @@
gcc -DWINDOWS_BUILD main.c apu.c audio.c alhelpers.c cpu.c mem.c ppu.c input.c -DFREEGLUT_STATIC -lfreeglut_static -lopenal32 -lopengl32 -lglu32 -lgdi32 -lwinmm -Wall -Wextra -O3 -msse -mfpmath=sse -ffast-math -s -o fixGB
pause

1557
cpu.c Normal file

File diff suppressed because it is too large Load Diff

15
cpu.h Normal file
View File

@ -0,0 +1,15 @@
/*
* Copyright (C) 2017 FIX94
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#ifndef _cpu_c_
#define _cpu_h_
void cpuInit();
bool cpuCycle();
uint16_t cpuCurPC();
#endif

64
input.c Normal file
View File

@ -0,0 +1,64 @@
/*
* Copyright (C) 2017 FIX94
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include <stdio.h>
#include <stdbool.h>
#include <inttypes.h>
#include "input.h"
//used externally by main.c
uint8_t inValReads[8];
uint8_t modeSelect = 0;
#define DEBUG_INPUT 0
void inputInit()
{
memset(inValReads, 0, 8);
}
void inputSet8(uint8_t in)
{
modeSelect = ((~in)>>4)&0x3;
#if DEBUG_INPUT
printf("Set %02x->%02x\n",in,modeSelect);
#endif
}
uint8_t inputGet8()
{
uint8_t outVal = 0;
if(modeSelect == 2)
{
if(inValReads[BUTTON_A])
outVal |= 1;
if(inValReads[BUTTON_B])
outVal |= 2;
if(inValReads[BUTTON_SELECT])
outVal |= 4;
if(inValReads[BUTTON_START])
outVal |= 8;
}
else if(modeSelect == 1)
{
if(inValReads[BUTTON_RIGHT])
outVal |= 1;
if(inValReads[BUTTON_LEFT])
outVal |= 2;
if(inValReads[BUTTON_UP])
outVal |= 4;
if(inValReads[BUTTON_DOWN])
outVal |= 8;
}
return ~outVal;
}
bool inputAny()
{
return !!(inValReads[BUTTON_A]|inValReads[BUTTON_B]|inValReads[BUTTON_SELECT]|inValReads[BUTTON_START]
|inValReads[BUTTON_RIGHT]|inValReads[BUTTON_LEFT]|inValReads[BUTTON_UP]|inValReads[BUTTON_DOWN]);
}

25
input.h Normal file
View File

@ -0,0 +1,25 @@
/*
* Copyright (C) 2017 FIX94
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#ifndef _INPUT_H_
#define _INPUT_H_
#define BUTTON_A 0
#define BUTTON_B 1
#define BUTTON_SELECT 2
#define BUTTON_START 3
#define BUTTON_UP 4
#define BUTTON_DOWN 5
#define BUTTON_LEFT 6
#define BUTTON_RIGHT 7
void inputInit();
uint8_t inputGet8();
void inputSet8(uint8_t in);
bool inputAny();
#endif

661
main.c Normal file
View File

@ -0,0 +1,661 @@
/*
* Copyright (C) 2017 FIX94
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <malloc.h>
#include <inttypes.h>
#include <GL/glut.h>
#include <GL/glext.h>
#include <time.h>
#include <math.h>
//#include "mapper.h"
#include "cpu.h"
#include "input.h"
#include "ppu.h"
#include "mem.h"
//#include "fm2play.h"
#include "apu.h"
#include "audio.h"
/*#include "audio_fds.h"
#include "audio_vrc7.h"*/
#define DEBUG_HZ 0
#define DEBUG_MAIN_CALLS 0
#define DEBUG_KEY 0
#define DEBUG_LOAD_INFO 1
static const char *VERSION_STRING = "fixGB";
static void gbEmuDisplayFrame(void);
static void gbEmuMainLoop(void);
static void gbEmuDeinit(void);
static void gbEmuHandleKeyDown(unsigned char key, int x, int y);
static void gbEmuHandleKeyUp(unsigned char key, int x, int y);
static void gbEmuHandleSpecialDown(int key, int x, int y);
static void gbEmuHandleSpecialUp(int key, int x, int y);
/*static */uint8_t *emuGBROM = NULL;
static char *emuSaveName = NULL;
static uint8_t *emuPrgRAM = NULL;
static uint32_t emuPrgRAMsize = 0;
//used externally
uint8_t *textureImage = NULL;
bool nesPause = false;
bool ppuDebugPauseFrame = false;
bool doOverscan = true;
bool gbEmuGBSPlayback = false;
static bool inPause = false;
static bool inOverscanToggle = false;
static bool inResize = false;
#if WINDOWS_BUILD
#include <windows.h>
typedef bool (APIENTRY *PFNWGLSWAPINTERVALEXTPROC) (int interval);
PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL;
#if DEBUG_HZ
static DWORD emuFrameStart = 0;
static DWORD emuTimesCalled = 0;
static DWORD emuTotalElapsed = 0;
#endif
#if DEBUG_MAIN_CALLS
static DWORD emuMainFrameStart = 0;
static DWORD emuMainTimesCalled = 0;
static DWORD emuMainTimesSkipped = 0;
static DWORD emuMainTotalElapsed = 0;
#endif
#endif
#define VISIBLE_DOTS 160
#define VISIBLE_LINES 144
static const uint32_t visibleImg = VISIBLE_DOTS*VISIBLE_LINES*4;
static uint8_t scaleFactor = 2;
static bool emuSaveEnabled = false;
static uint32_t mainLoopRuns;
static uint16_t mainLoopPos;
//from input.c
extern uint8_t inValReads[8];
int main(int argc, char** argv)
{
puts(VERSION_STRING);
if(argc >= 2 && (strstr(argv[1],".gb") != NULL || strstr(argv[1],".GB") != NULL))
{
FILE *gbF = fopen(argv[1],"rb");
if(!gbF) return EXIT_SUCCESS;
fseek(gbF,0,SEEK_END);
size_t fsize = ftell(gbF);
rewind(gbF);
emuGBROM = malloc(fsize);
fread(emuGBROM,1,fsize,gbF);
fclose(gbF);
//uint8_t mapper = ((emuGBROM[6] & 0xF0) >> 4) | ((emuGBROM[7] & 0xF0));
emuSaveEnabled = (emuGBROM[6] & (1<<1)) != 0;
bool trainer = (emuGBROM[6] & (1<<2)) != 0;
uint32_t prgROMsize = emuGBROM[4] * 0x4000;
uint32_t chrROMsize = emuGBROM[5] * 0x2000;
emuPrgRAMsize = emuGBROM[8] * 0x2000;
if(emuPrgRAMsize == 0) emuPrgRAMsize = 0x2000;
emuPrgRAM = malloc(emuPrgRAMsize);
uint8_t *prgROM = emuGBROM+16;
if(trainer)
{
memcpy(emuPrgRAM+0x1000,prgROM,0x200);
prgROM += 512;
}
uint8_t *chrROM = NULL;
if(chrROMsize)
{
chrROM = emuGBROM+16+prgROMsize;
if(trainer) chrROM += 512;
}
apuInitBufs();
cpuInit();
ppuInit();
memInit();
apuInit();
inputInit();
#if DEBUG_LOAD_INFO
printf("Read in %s\n", argv[1]);
//printf("Used Mapper: %i\n", mapper);
//printf("PRG: 0x%x bytes PRG RAM: 0x%x bytes CHR: 0x%x bytes\n", prgROMsize, emuPrgRAMsize, chrROMsize);
#endif
/*if(!mapperInit(mapper, prgROM, prgROMsize, emuPrgRAM, emuPrgRAMsize, chrROM, chrROMsize))
{
printf("Mapper init failed!\n");
free(emuGBROM);
emuGBROM = NULL;
return EXIT_SUCCESS;
}
if(emuGBROM[6] & 8)
ppuSetNameTbl4Screen();
else if(emuGBROM[6] & 1)
ppuSetNameTblVertical();
else
ppuSetNameTblHorizontal();
#if DEBUG_LOAD_INFO
printf("Trainer: %i Saving: %i VRAM Mode: %s\n", trainer, emuSaveEnabled, (emuGBROM[6] & 1) ? "Vertical" :
((!(emuGBROM[6] & 1)) ? "Horizontal" : "4-Screen"));
#endif
if(emuSaveEnabled)
{
emuSaveName = strdup(argv[1]);
memcpy(emuSaveName+strlen(emuSaveName)-3,"sav",3);
FILE *save = fopen(emuSaveName, "rb");
if(save)
{
fread(emuPrgRAM,1,emuPrgRAMsize,save);
fclose(save);
}
}*/
}
/*else if(argc >= 2 && (strstr(argv[1],".gbs") != NULL || strstr(argv[1],".GBS") != NULL))
{
FILE *gbF = fopen(argv[1],"rb");
if(!gbF) return EXIT_SUCCESS;
fseek(gbF,0,SEEK_END);
size_t fsize = ftell(gbF);
rewind(gbF);
emuGBROM = malloc(fsize);
fread(emuGBROM,1,fsize,gbF);
fclose(gbF);
emuPrgRAMsize = 0x2000;
emuPrgRAM = malloc(emuPrgRAMsize);
if(!mapperInitGBS(emuGBROM, fsize, emuPrgRAM, emuPrgRAMsize))
{
printf("GBS init failed!\n");
free(emuGBROM);
return EXIT_SUCCESS;
}
gbEmuGBSPlayback = true;
}*/
if(emuGBROM == NULL)
return EXIT_SUCCESS;
#if WINDOWS_BUILD
#if DEBUG_HZ
emuFrameStart = GetTickCount();
#endif
#if DEBUG_MAIN_CALLS
emuMainFrameStart = GetTickCount();
#endif
#endif
textureImage = malloc(visibleImg);
memset(textureImage,0,visibleImg);
//make sure image is visible
uint32_t i;
for(i = 0; i < visibleImg; i+=4)
textureImage[i+3] = 0xFF;
/*cpuCycleTimer = nesPAL ? 16 : 12;
//do one scanline per idle loop
ppuCycleTimer = nesPAL ? 5 : 4;
mainLoopRuns = nesPAL ? DOTS*ppuCycleTimer : DOTS*ppuCycleTimer;
mainLoopPos = mainLoopRuns;*/
//do one scanline per idle loop
mainLoopRuns = 70224;
mainLoopPos = mainLoopRuns;
glutInit(&argc, argv);
glutInitWindowSize(VISIBLE_DOTS*scaleFactor, VISIBLE_LINES*scaleFactor);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutCreateWindow(VERSION_STRING);
audioInit();
atexit(&gbEmuDeinit);
glutKeyboardFunc(&gbEmuHandleKeyDown);
glutKeyboardUpFunc(&gbEmuHandleKeyUp);
glutSpecialFunc(&gbEmuHandleSpecialDown);
glutSpecialUpFunc(&gbEmuHandleSpecialUp);
glutDisplayFunc(&gbEmuDisplayFrame);
glutIdleFunc(&gbEmuMainLoop);
#if WINDOWS_BUILD
/* Enable OpenGL VSync */
wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT");
wglSwapIntervalEXT(1);
#endif
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(GL_TEXTURE_2D, 0, 4, VISIBLE_DOTS, VISIBLE_LINES, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, textureImage);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glEnable(GL_TEXTURE_2D);
glShadeModel(GL_FLAT);
glutMainLoop();
return EXIT_SUCCESS;
}
static volatile bool emuRenderFrame = false;
static void gbEmuDeinit(void)
{
//printf("\n");
emuRenderFrame = false;
audioDeinit();
apuDeinitBufs();
if(emuGBROM != NULL)
free(emuGBROM);
emuGBROM = NULL;
if(emuPrgRAM != NULL)
{
if(emuSaveEnabled)
{
FILE *save = fopen(emuSaveName, "wb");
if(save)
{
fwrite(emuPrgRAM,1,emuPrgRAMsize,save);
fclose(save);
}
}
free(emuPrgRAM);
}
emuPrgRAM = NULL;
if(textureImage != NULL)
free(textureImage);
textureImage = NULL;
//printf("Bye!\n");
}
//used externally
bool emuSkipVsync = false;
bool emuSkipFrame = false;
//static uint32_t mCycles = 0;
//static bool emuApuDoCycle = false;
static uint16_t mainClock = 1;
static uint16_t memClock = 1;
//static uint16_t vrc7Clock = 1;
static void gbEmuMainLoop(void)
{
do
{
if((!emuSkipVsync && emuRenderFrame) || nesPause)
{
#if (WINDOWS_BUILD && DEBUG_MAIN_CALLS)
emuMainTimesSkipped++;
#endif
audioSleep();
return;
}
if(mainClock == 4)
{
if(!apuCycle())
{
#if (WINDOWS_BUILD && DEBUG_MAIN_CALLS)
emuMainTimesSkipped++;
#endif
audioSleep();
return;
}
//main CPU clock
if(!cpuCycle())
{
memDumpMainMem();
exit(EXIT_SUCCESS);
}
if(memClock == 4)
{
memClockTimers();
memClock = 1;
}
else
memClock++;
//channel timer updates
apuLenCycle();
/*//mapper related irqs
if(mapperCycle != NULL)
mapperCycle();*/
//mCycles++;
mainClock = 1;
}
else
mainClock++;
apuClockTimers();
if(!ppuCycle())
exit(EXIT_SUCCESS);
if(!gbEmuGBSPlayback && ppuDrawDone())
{
//printf("%i\n",mCycles);
//mCycles = 0;
emuRenderFrame = true;
#if (WINDOWS_BUILD && DEBUG_HZ)
emuTimesCalled++;
DWORD end = GetTickCount();
emuTotalElapsed += end - emuFrameStart;
if(emuTotalElapsed >= 1000)
{
printf("\r%iHz ", emuTimesCalled);
emuTimesCalled = 0;
emuTotalElapsed = 0;
}
emuFrameStart = end;
#endif
glutPostRedisplay();
if(ppuDebugPauseFrame)
nesPause = true;
}
}
while(mainLoopPos--);
mainLoopPos = mainLoopRuns;
#if (WINDOWS_BUILD && DEBUG_MAIN_CALLS)
emuMainTimesCalled++;
DWORD end = GetTickCount();
emuMainTotalElapsed += end - emuMainFrameStart;
if(emuMainTotalElapsed >= 1000)
{
printf("\r%i calls, %i skips ", emuMainTimesCalled, emuMainTimesSkipped);
emuMainTimesCalled = 0;
emuMainTimesSkipped = 0;
emuMainTotalElapsed = 0;
}
emuMainFrameStart = end;
#endif
}
static void gbEmuHandleKeyDown(unsigned char key, int x, int y)
{
(void)x;
(void)y;
switch (key)
{
case 'y':
case 'z':
case 'Y':
case 'Z':
#if DEBUG_KEY
if(inValReads[BUTTON_A]==0)
printf("a\n");
#endif
inValReads[BUTTON_A]=1;
break;
case 'x':
case 'X':
#if DEBUG_KEY
if(inValReads[BUTTON_B]==0)
printf("b\n");
#endif
inValReads[BUTTON_B]=1;
break;
case 's':
case 'S':
#if DEBUG_KEY
if(inValReads[BUTTON_SELECT]==0)
printf("sel\n");
#endif
inValReads[BUTTON_SELECT]=1;
break;
case 'a':
case 'A':
#if DEBUG_KEY
if(inValReads[BUTTON_START]==0)
printf("start\n");
#endif
inValReads[BUTTON_START]=1;
break;
case '\x1B': //Escape
memDumpMainMem();
exit(EXIT_SUCCESS);
break;
case 'p':
case 'P':
if(!inPause)
{
#if DEBUG_KEY
printf("pause\n");
#endif
inPause = true;
nesPause ^= true;
}
break;
case '1':
if(!inResize)
{
inResize = true;
glutReshapeWindow(VISIBLE_DOTS*1, VISIBLE_LINES*1);
}
break;
case '2':
if(!inResize)
{
inResize = true;
glutReshapeWindow(VISIBLE_DOTS*2, VISIBLE_LINES*2);
}
break;
case '3':
if(!inResize)
{
inResize = true;
glutReshapeWindow(VISIBLE_DOTS*3, VISIBLE_LINES*3);
}
break;
case '4':
if(!inResize)
{
inResize = true;
glutReshapeWindow(VISIBLE_DOTS*4, VISIBLE_LINES*4);
}
break;
case '5':
if(!inResize)
{
inResize = true;
glutReshapeWindow(VISIBLE_DOTS*5, VISIBLE_LINES*5);
}
break;
case '6':
if(!inResize)
{
inResize = true;
glutReshapeWindow(VISIBLE_DOTS*6, VISIBLE_LINES*6);
}
break;
case '7':
if(!inResize)
{
inResize = true;
glutReshapeWindow(VISIBLE_DOTS*7, VISIBLE_LINES*7);
}
break;
case '8':
if(!inResize)
{
inResize = true;
glutReshapeWindow(VISIBLE_DOTS*8, VISIBLE_LINES*8);
}
break;
case '9':
if(!inResize)
{
inResize = true;
glutReshapeWindow(VISIBLE_DOTS*9, VISIBLE_LINES*9);
}
break;
case 'o':
case 'O':
if(!inOverscanToggle)
{
inOverscanToggle = true;
doOverscan ^= true;
}
break;
default:
break;
}
}
static void gbEmuHandleKeyUp(unsigned char key, int x, int y)
{
(void)x;
(void)y;
switch (key)
{
case 'y':
case 'z':
case 'Y':
case 'Z':
#if DEBUG_KEY
printf("a up\n");
#endif
inValReads[BUTTON_A]=0;
break;
case 'x':
case 'X':
#if DEBUG_KEY
printf("b up\n");
#endif
inValReads[BUTTON_B]=0;
break;
case 's':
case 'S':
#if DEBUG_KEY
printf("sel up\n");
#endif
inValReads[BUTTON_SELECT]=0;
break;
case 'a':
case 'A':
#if DEBUG_KEY
printf("start up\n");
#endif
inValReads[BUTTON_START]=0;
break;
case 'p':
case 'P':
#if DEBUG_KEY
printf("pause up\n");
#endif
inPause=false;
break;
case '1': case '2': case '3':
case '4': case '5': case '6':
case '7': case '8': case '9':
inResize = false;
break;
case 'o':
case 'O':
inOverscanToggle = false;
break;
default:
break;
}
}
static void gbEmuHandleSpecialDown(int key, int x, int y)
{
(void)x;
(void)y;
switch(key)
{
case GLUT_KEY_UP:
#if DEBUG_KEY
if(inValReads[BUTTON_UP]==0)
printf("up\n");
#endif
inValReads[BUTTON_UP]=1;
break;
case GLUT_KEY_DOWN:
#if DEBUG_KEY
if(inValReads[BUTTON_DOWN]==0)
printf("down\n");
#endif
inValReads[BUTTON_DOWN]=1;
break;
case GLUT_KEY_LEFT:
#if DEBUG_KEY
if(inValReads[BUTTON_LEFT]==0)
printf("left\n");
#endif
inValReads[BUTTON_LEFT]=1;
break;
case GLUT_KEY_RIGHT:
#if DEBUG_KEY
if(inValReads[BUTTON_RIGHT]==0)
printf("right\n");
#endif
inValReads[BUTTON_RIGHT]=1;
break;
default:
break;
}
}
static void gbEmuHandleSpecialUp(int key, int x, int y)
{
(void)x;
(void)y;
switch(key)
{
case GLUT_KEY_UP:
#if DEBUG_KEY
printf("up up\n");
#endif
inValReads[BUTTON_UP]=0;
break;
case GLUT_KEY_DOWN:
#if DEBUG_KEY
printf("down up\n");
#endif
inValReads[BUTTON_DOWN]=0;
break;
case GLUT_KEY_LEFT:
#if DEBUG_KEY
printf("left up\n");
#endif
inValReads[BUTTON_LEFT]=0;
break;
case GLUT_KEY_RIGHT:
#if DEBUG_KEY
printf("right up\n");
#endif
inValReads[BUTTON_RIGHT]=0;
break;
default:
break;
}
}
static void gbEmuDisplayFrame()
{
if(emuRenderFrame)
{
if(emuSkipFrame)
{
emuRenderFrame = false;
return;
}
if(textureImage != NULL)
glTexImage2D(GL_TEXTURE_2D, 0, 4, VISIBLE_DOTS, VISIBLE_LINES, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, textureImage);
emuRenderFrame = false;
}
glClear(GL_COLOR_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, glutGet(GLUT_WINDOW_WIDTH), 0, glutGet(GLUT_WINDOW_HEIGHT), -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
double upscaleVal = round((((double)glutGet(GLUT_WINDOW_HEIGHT))/((double)VISIBLE_LINES))*20.0)/20.0;
double windowMiddle = ((double)glutGet(GLUT_WINDOW_WIDTH))/2.0;
double drawMiddle = (((double)VISIBLE_DOTS)*upscaleVal)/2.0;
double drawHeight = ((double)VISIBLE_LINES)*upscaleVal;
glBegin(GL_QUADS);
glTexCoord2f(0,0); glVertex2f(windowMiddle-drawMiddle,drawHeight);
glTexCoord2f(1,0); glVertex2f(windowMiddle+drawMiddle,drawHeight);
glTexCoord2f(1,1); glVertex2f(windowMiddle+drawMiddle,0);
glTexCoord2f(0,1); glVertex2f(windowMiddle-drawMiddle,0);
glEnd();
glutSwapBuffers();
}

223
mem.c Normal file
View File

@ -0,0 +1,223 @@
/*
* Copyright (C) 2017 FIX94
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include <stdio.h>
#include <stdbool.h>
#include <inttypes.h>
#include "cpu.h"
#include "ppu.h"
#include "apu.h"
#include "input.h"
static uint8_t Main_Mem[0x2000];
static uint8_t High_Mem[0x80];
static uint8_t memLastVal;
static uint8_t irqEnableReg;
static uint8_t irqFlagsReg;
static uint8_t divRegVal;
static uint8_t divRegClock;
static uint8_t timerReg;
static uint8_t timerRegVal;
static uint8_t timerResetVal;
static uint8_t timerRegClock;
static uint8_t timerRegTimer;
static uint8_t cBank;
static uint32_t bankMask;
static bool timerRegEnable;
extern uint8_t *emuGBROM;
void memInit()
{
bankMask = (0x8000<<(emuGBROM[0x148]&0xF))-1;
memset(Main_Mem,0,0x2000);
memset(High_Mem,0,0x80);
memLastVal = 0;
irqEnableReg = 0;
irqFlagsReg = 0;
cBank = 1;
divRegVal = 0;
divRegClock = 1;
timerReg = 0;
timerRegVal = 0;
timerResetVal = 0;
timerRegClock = 1;
timerRegTimer = 64; //262144 / 64 = 4096
timerRegEnable = false;
}
uint8_t memGet8(uint16_t addr)
{
uint8_t val = memLastVal;
//printf("memGet8 %04x\n", addr);
if(addr < 0x4000)
val = emuGBROM[addr];
else if(addr < 0x8000)
val = emuGBROM[((cBank<<14)+(addr&0x3FFF))&bankMask];
else if(addr >= 0x8000 && addr < 0xA000)
val = ppuGet8(addr);
else if(addr >= 0xC000 && addr < 0xFE00)
val = Main_Mem[addr&0x1FFF];
else if(addr >= 0xFE00 && addr < 0xFEA0)
val = ppuGet8(addr);
else if(addr == 0xFF00)
val = inputGet8();
else if(addr == 0xFF04)
val = divRegVal;
else if(addr == 0xFF05)
val = timerRegVal;
else if(addr == 0xFF06)
val = timerResetVal;
else if(addr == 0xFF07)
val = timerReg;
else if(addr == 0xFF0F)
{
val = irqFlagsReg;
//printf("memGet8 %04x %02x\n", addr, val);
}
else if(addr >= 0xFF10 && addr < 0xFF40)
val = apuGet8(addr&0xFF);
else if(addr >= 0xFF40 && addr < 0xFF70)
val = ppuGet8(addr);
else if(addr >= 0xFF80 && addr < 0xFFFF)
val = High_Mem[addr&0x7F];
else if(addr == 0xFFFF)
val = irqEnableReg;
memLastVal = val;
return val;
}
extern uint32_t cpu_oam_dma;
extern bool cpu_odd_cycle;
void memSet8(uint16_t addr, uint8_t val)
{
if(addr >= 0x2000 && addr < 0x4000)
{
//printf("%02x\n",val);
cBank = val;
if(cBank == 0)
cBank = 1;
}
if(addr >= 0x8000 && addr < 0xA000)
ppuSet8(addr, val);
else if(addr >= 0xC000 && addr < 0xFE00)
Main_Mem[addr&0x1FFF] = val;
else if(addr >= 0xFE00 && addr < 0xFEA0)
ppuSet8(addr, val);
else if(addr == 0xFF00)
inputSet8(val);
else if(addr == 0xFF04)
divRegVal = 0; //writing any val resets to 0
else if(addr == 0xFF05)
timerRegVal = 0; //not sure
else if(addr == 0xFF06)
timerResetVal = val;
else if(addr == 0xFF07)
{
//if(val != 0)
// printf("memSet8 %04x %02x\n", addr, val);
timerReg = val; //for readback
timerRegEnable = ((val&4)!=0);
if((val&3)==0) //0 for 4096 Hz
timerRegTimer = 64; //262144 / 64 = 4096
else if((val&3)==1) //1 for 262144 Hz
timerRegTimer = 1; //262144 / 1 = 262144
else if((val&3)==2) //2 for 65536 Hz
timerRegTimer = 4; //262144 / 4 = 65536
else if((val&3)==3) //3 for 16384 Hz
timerRegTimer = 16; //262144 / 16 = 16384
}
else if(addr == 0xFF0F)
{
//printf("memSet8 %04x %02x\n", addr, val);
irqFlagsReg = val;
}
else if(addr >= 0xFF10 && addr < 0xFF40)
apuSet8(addr&0xFF, val);
else if(addr >= 0xFF40 && addr < 0xFF70)
ppuSet8(addr, val);
else if(addr >= 0xFF80 && addr < 0xFFFF)
High_Mem[addr&0x7F] = val;
else if(addr == 0xFFFF)
{
//printf("memSet8 %04x %02x\n", addr, val);
irqEnableReg = val;
}
memLastVal = val;
}
uint8_t memGetCurIrqList()
{
return (irqEnableReg & irqFlagsReg);
}
void memClearCurIrqList(uint8_t num)
{
irqFlagsReg &= ~num;
}
void memEnableVBlankIrq()
{
irqFlagsReg |= 1;
}
void memEnableStatIrq()
{
irqFlagsReg |= 2;
}
#define DEBUG_MEM_DUMP 1
void memDumpMainMem()
{
#if DEBUG_MEM_DUMP
FILE *f = fopen("MainMem.bin","wb");
if(f)
{
fwrite(Main_Mem,1,0x2000,f);
fclose(f);
}
f = fopen("HighMem.bin","wb");
if(f)
{
fwrite(High_Mem,1,0x80,f);
fclose(f);
}
ppuDumpMem();
#endif
}
//clocked at 262144 Hz
void memClockTimers()
{
//clocked at 16384 Hz (262144 / 16 = 16384)
if(divRegClock == 16)
{
divRegVal++;
divRegClock = 1;
}
else
divRegClock++;
if(!timerRegEnable)
return;
//clocked at specified rate
if(timerRegClock == timerRegTimer)
{
timerRegVal++;
if(timerRegVal == 0) //set on overflow
{
//printf("Timer interrupt\n");
timerRegVal = timerResetVal;
irqFlagsReg |= 4;
}
timerRegClock = 1;
}
else
timerRegClock++;
}

23
mem.h Normal file
View File

@ -0,0 +1,23 @@
/*
* Copyright (C) 2017 FIX94
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#ifndef _mem_h_
#define _mem_h_
void memInit();
uint8_t memGet8(uint16_t addr);
void memSet8(uint16_t addr, uint8_t val);
void memSet16(uint16_t addr, uint16_t val);
void memDumpMainMem();
void memClockTimers();
uint8_t memGetCurIrqList();
void memClearCurIrqList(uint8_t num);
void memEnableVBlankIrq();
void memEnableStatIrq();
#endif

407
ppu.c Normal file
View File

@ -0,0 +1,407 @@
/*
* Copyright (C) 2017 FIX94
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include <stdio.h>
#include <stdbool.h>
#include <inttypes.h>
#include "cpu.h"
#include "ppu.h"
#include "mem.h"
//FF40
#define PPU_BG_ENABLE (1<<0)
#define PPU_SPRITE_ENABLE (1<<1)
#define PPU_SPRITE_8_16 (1<<2)
#define PPU_BG_TILEMAP_UP (1<<3)
#define PPU_BG_TILEDAT_LOW (1<<4)
#define PPU_WINDOW_ENABLE (1<<5)
#define PPU_WINDOW_TILEMAP_UP (1<<6)
#define PPU_ENABLE (1<<7)
//FF41
#define PPU_LINEMATCH (1<<2)
#define PPU_HBLANK_IRQ (1<<3)
#define PPU_VBLANK_IRQ (1<<4)
#define PPU_OAM_IRQ (1<<5)
#define PPU_LINEMATCH_IRQ (1<<6)
//sprite byte 3
#define PPU_SPRITE_PAL (1<<4)
#define PPU_SPRITE_FLIP_X (1<<5)
#define PPU_SPRITE_FLIP_Y (1<<6)
#define PPU_SPRITE_PRIO (1<<7)
extern uint8_t *textureImage;
static uint32_t ppuClock;
static uint32_t ppuTestClock;
static uint8_t ppuMode;
static uint8_t ppuDots;
static uint8_t ppuOAMpos;
static uint8_t ppuOAM2pos;
static uint8_t PPU_Reg[12];
static uint8_t PPU_OAM[0xA0];
static uint8_t PPU_OAM2[0x28];
static uint8_t PPU_VRAM[0x2000];
static bool ppuFrameDone;
static bool ppuVBlank;
static bool ppuVBlankTriggered;
void ppuInit()
{
ppuClock = 0;
ppuTestClock = 0;
ppuMode = 0;
ppuDots = 0;
ppuOAMpos = 0;
ppuOAM2pos = 0;
ppuFrameDone = false;
ppuVBlank = false;
ppuVBlankTriggered = false;
memset(PPU_Reg,0,12);
memset(PPU_OAM,0,0xA0);
memset(PPU_OAM2,0,0x28);
memset(PPU_VRAM,0,0x2000);
//From GB Bootrom
PPU_Reg[0] = 0x91;
PPU_Reg[7] = 0xFC;
PPU_Reg[8] = 0xFF;
PPU_Reg[9] = 0xFF;
}
bool ppuCycle()
{
if(!(PPU_Reg[0] & PPU_ENABLE))
return true;
if(PPU_Reg[4] < 144)
{
if(ppuClock == 0)
{
ppuOAMpos = 0; //Reset check pos
ppuOAM2pos = 0; //Reset array pos
ppuMode = 2; //OAM
if(PPU_Reg[1]&PPU_OAM_IRQ)
{
//printf("OAM STAT IRQ\n");
memEnableStatIrq();
}
}
if(ppuClock == 80)
{
ppuDots = 0; //Reset Draw Pos
ppuMode = 3; //Main Mode
}
if(ppuClock == 252)
{
ppuMode = 0; //HBlank
if(PPU_Reg[1]&PPU_HBLANK_IRQ)
{
//printf("HBlank STAT IRQ\n");
memEnableStatIrq();
}
}
//do OAM updates?
if(ppuClock < 80 && ((ppuClock&1) == 0) && ppuOAM2pos < 10)
{
uint8_t OAMcYpos = PPU_OAM[(ppuOAMpos<<2)];
if(OAMcYpos < 160)
{
int16_t cmpPos = ((int16_t)OAMcYpos)-16;
uint8_t cSpriteAdd = (PPU_Reg[0] & PPU_SPRITE_8_16) ? 16 : 8;
if(cmpPos <= PPU_Reg[4] && (cmpPos+cSpriteAdd) > PPU_Reg[4])
{
memcpy(PPU_OAM2+(ppuOAM2pos<<2), PPU_OAM+(ppuOAMpos<<2), 4);
ppuOAM2pos++;
}
}
ppuOAMpos++;
}
//draw point?
if(ppuClock >= 80 && ppuClock < 240)
{
//makes it possible to draw 160x144 in here :)
uint8_t ChrRegA = 0, ChrRegB = 0, color = 0, tCol = 0;
if(PPU_Reg[0]&PPU_BG_ENABLE)
{
uint8_t bgXPos = ppuDots+PPU_Reg[3];
uint8_t bgYPos = PPU_Reg[4]+PPU_Reg[2];
uint16_t vramTilePos = ((PPU_Reg[0]&PPU_BG_TILEMAP_UP)?0x1C00:0x1800)+(((bgXPos/8)+(bgYPos/8*32))&0x3FFF);
if(PPU_Reg[0]&PPU_BG_TILEDAT_LOW)
{
uint8_t tVal = PPU_VRAM[vramTilePos&0x1FFF];
uint16_t tPos = tVal*16;
tPos+=(bgYPos&7)*2;
ChrRegA = PPU_VRAM[(tPos)&0x1FFF];
ChrRegB = PPU_VRAM[(tPos+1)&0x1FFF];
}
else
{
int8_t tVal = (int8_t)PPU_VRAM[vramTilePos&0x1FFF];
int16_t tPos = tVal*16;
tPos+=(bgYPos&7)*2;
ChrRegA = PPU_VRAM[(0x1000+tPos)&0x1FFF];
ChrRegB = PPU_VRAM[(0x1000+tPos+1)&0x1FFF];
}
if(ChrRegA & (0x80>>(bgXPos&7)))
color |= 1;
if(ChrRegB & (0x80>>(bgXPos&7)))
color |= 2;
//again, not sure
tCol = (~(PPU_Reg[7]>>(color*2)))&3;
}
if(PPU_Reg[0]&PPU_WINDOW_ENABLE && (PPU_Reg[0xB]) <= ppuDots+7 && PPU_Reg[0xA] <= PPU_Reg[4])
{
uint8_t windowXPos = ppuDots+7-PPU_Reg[0xB];
uint8_t windowYPos = PPU_Reg[4]-PPU_Reg[0xA];
uint16_t vramTilePos = ((PPU_Reg[0]&PPU_WINDOW_TILEMAP_UP)?0x1C00:0x1800)+(((windowXPos/8)+(windowYPos/8*32))&0x3FF);
if(PPU_Reg[0]&PPU_BG_TILEDAT_LOW)
{
uint8_t tVal = PPU_VRAM[vramTilePos&0x1FFF];
uint16_t tPos = tVal*16;
tPos+=(windowYPos&7)*2;
ChrRegA = PPU_VRAM[(tPos)&0x1FFF];
ChrRegB = PPU_VRAM[(tPos+1)&0x1FFF];
}
else
{
int8_t tVal = (int8_t)PPU_VRAM[vramTilePos&0x1FFF];
int16_t tPos = tVal*16;
tPos+=(windowYPos&7)*2;
ChrRegA = PPU_VRAM[(0x1000+tPos)&0x1FFF];
ChrRegB = PPU_VRAM[(0x1000+tPos+1)&0x1FFF];
}
color = 0;
if(ChrRegA & (0x80>>(windowXPos&7)))
color |= 1;
if(ChrRegB & (0x80>>(windowXPos&7)))
color |= 2;
//again, not sure
tCol = (~(PPU_Reg[7]>>(color*2)))&3;
}
if(PPU_Reg[0]&PPU_SPRITE_ENABLE)
{
uint8_t i;
uint8_t cSpriteAnd = (PPU_Reg[0] & PPU_SPRITE_8_16) ? 15 : 7;
for(i = 0; i < ppuOAM2pos; i++)
{
uint8_t OAMcXpos = PPU_OAM2[(i<<2)+1];
if(OAMcXpos >= 168)
continue;
int16_t cmpPos = ((int16_t)OAMcXpos)-8;
if(cmpPos <= ppuDots && (cmpPos+8) > ppuDots)
{
uint8_t cSpriteByte3 = PPU_OAM2[(i<<2)+3];
uint8_t tVal = PPU_OAM2[(i<<2)+2];
uint16_t tPos = tVal*16;
uint8_t OAMcYpos = PPU_OAM2[(i<<2)];
uint8_t cmpYPos = OAMcYpos-16;
uint8_t cSpriteY = (PPU_Reg[4] - cmpYPos)&cSpriteAnd;
uint8_t cSpriteAdd = 0; //used to select which 8 by 16 tile
if(cSpriteY > 7) //8 by 16 select
{
cSpriteAdd = 16;
cSpriteY &= 7;
}
if(cSpriteByte3 & PPU_SPRITE_FLIP_Y)
{
cSpriteY ^= 7;
if(PPU_Reg[0] & PPU_SPRITE_8_16)
cSpriteAdd ^= 16; //8 by 16 select
}
tPos+=(cSpriteY)*2;
ChrRegA = PPU_VRAM[(tPos+cSpriteAdd)&0x1FFF];
ChrRegB = PPU_VRAM[(tPos+cSpriteAdd+1)&0x1FFF];
uint8_t cSpriteX = (ppuDots - OAMcXpos)&7;
if(cSpriteByte3 & PPU_SPRITE_FLIP_X)
cSpriteX ^= 7;
uint8_t sprCol = 0;
if(ChrRegA & (0x80>>cSpriteX))
sprCol |= 1;
if(ChrRegB & (0x80>>cSpriteX))
sprCol |= 2;
//done looking at sprites, we have to
//always return the first one we find
if(sprCol != 0)
{
//sprite has highest priority, return sprite
if((cSpriteByte3 & PPU_SPRITE_PRIO) == 0)
{
if(cSpriteByte3 & PPU_SPRITE_PAL)
tCol = (~(PPU_Reg[9]>>(sprCol*2)))&3;
else
tCol = (~(PPU_Reg[8]>>(sprCol*2)))&3;
break;
} //sprite has low priority and BG is not 0, return BG
else if((color&3) != 0)
break;
//background is 0 so return sprite
if(cSpriteByte3 & PPU_SPRITE_PAL)
tCol = (~(PPU_Reg[9]>>(sprCol*2)))&3;
else
tCol = (~(PPU_Reg[8]>>(sprCol*2)))&3;
break;
}
//Sprite is 0, keep looking for sprites
}
}
}
uint8_t draw = (tCol == 0) ? 0 : (tCol == 1) ? 0x55 : (tCol == 2) ? 0xAA : 0xFF;
{
size_t drawPos = (ppuDots*4)+(PPU_Reg[4]*160*4);
textureImage[drawPos] = draw;
textureImage[drawPos+1] = draw;
textureImage[drawPos+2] = draw;
}
ppuDots++;
}
}
ppuTestClock++;
ppuClock++;
if(ppuClock == 456)
{
ppuClock = 0;
PPU_Reg[4]++;
if(PPU_Reg[4]==PPU_Reg[5])
{
if(PPU_Reg[1]&PPU_LINEMATCH_IRQ)
{
//printf("Line STAT IRQ at %i\n",PPU_Reg[4]);
memEnableStatIrq();
}
}
if(PPU_Reg[4] == 144)
{
ppuMode = 1; //VBlank
ppuVBlank = true;
memEnableVBlankIrq();
if(PPU_Reg[1]&PPU_VBLANK_IRQ)
{
//printf("VBlank STAT IRQ\n");
memEnableStatIrq();
}
//printf("VBlank Start\n");
}
if(PPU_Reg[4] == 154)
{
PPU_Reg[4] = 0; //Draw Done!
extern int testCounter;
//printf("Draw Done %i %i\n",testCounter,ppuTestClock);
testCounter = 0;
ppuTestClock = 0;
ppuFrameDone = true;
ppuVBlank = false;
}
}
return true;
}
bool ppuDrawDone()
{
if(ppuFrameDone)
{
//printf("%i\n",ppuCycles);
//ppuCycles = 0;
ppuFrameDone = false;
return true;
}
return false;
}
uint8_t ppuGet8(uint16_t addr)
{
uint8_t val = 0;
if(addr >= 0x8000 && addr < 0xA000)
val = PPU_VRAM[addr&0x1FFF];
else if(addr >= 0xFE00 && addr < 0xFEA0)
val = PPU_OAM[addr&0xFF];
else if(addr >= 0xFF40 && addr < 0xFF4C)
{
if(addr == 0xFF41)
{
if(!(PPU_Reg[0] & PPU_ENABLE))
val = 0; //This is not all that clear anywhere...
else
val = (PPU_Reg[addr&0xF]&(~7))|(ppuMode&3)|((PPU_Reg[4]==PPU_Reg[5])?PPU_LINEMATCH:0);
}
else
val = PPU_Reg[addr&0xF];
//if(addr != 0xFF44)
// printf("at instr %04x:ppuGet8(%04x, %02x)\n",cpuCurPC(),addr,val);
}
return val;
}
void ppuSet8(uint16_t addr, uint8_t val)
{
if(addr >= 0x8000 && addr < 0xA000)
PPU_VRAM[addr&0x1FFF] = val;
else if(addr >= 0xFE00 && addr < 0xFEA0)
PPU_OAM[addr&0xFF] = val;
else if(addr >= 0xFF40 && addr < 0xFF4C)
{
if(addr == 0xFF46) //OAM DMA
{
if(val < 0xFE)
{
uint8_t i;
for(i = 0; i < 0xA0; i++)
PPU_OAM[i] = memGet8((val<<8) | i);
}
}
else if(addr != 0xFF44) //line count fully RO
{
if(addr == 0xFF41) //STAT RO Regs
PPU_Reg[addr&0xF] = (val&(~7));
else //other R/W regs
PPU_Reg[addr&0xF] = val;
//if(addr == 0xFF40)
// printf("ppuSet8(%04x, %02x)\n",addr,val);
}
}
}
void ppuDumpMem()
{
FILE *f = fopen("PPU_VRAM.bin","wb");
if(f)
{
fwrite(PPU_VRAM,1,0x2000,f);
fclose(f);
}
f = fopen("PPU_OAM.bin","wb");
if(f)
{
fwrite(PPU_OAM,1,0xA0,f);
fclose(f);
}
/*f = fopen("PPU_Sprites.bin","wb");
if(f)
{
fwrite(PPU_Sprites,1,0x20,f);
fclose(f);
}*/
}
bool ppuInVBlank()
{
if(ppuVBlank)
{
if(ppuVBlankTriggered == false)
{
ppuVBlankTriggered = true;
return true;
}
else
return false;
}
ppuVBlankTriggered = false;
return false;
}

19
ppu.h Normal file
View File

@ -0,0 +1,19 @@
/*
* Copyright (C) 2017 FIX94
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#ifndef _ppu_h_
#define _ppu_h_
void ppuInit();
bool ppuCycle();
bool ppuDrawDone();
uint8_t ppuGet8(uint16_t addr);
void ppuSet8(uint16_t addr, uint8_t val);
bool ppuInVBlank();
void ppuDumpMem();
#endif