diff --git a/README.md b/README.md index 511e2c0..d0bd9e8 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,16 @@ # fixNES -Yet Another NES Emulator +This is yet another NES Emulator which was written so I can learn about the NES, right now it is not in the most "complete" or cleanest state. +If you want to check it out for some reason I do include a windows binary in the "Releases" tab, if you want to compile it go check out the "build" files. +NTSC NES ROMs and Mappers 0,1,2,3,4 and 7 are supported right now, it also creates .sav files if the chosen game supports saving. + +Controls right now are keyboard only and do the following: +Y/Z is A +X is B +A is Start +S is select +Arrow Keys is DPad +Keys 1-9 integer-scale the window to number +P is Pause +O is Enable/Disable vertical Overscan + +That is all I can say about it right now, who knows if I will write some more on it. \ No newline at end of file diff --git a/alhelpers.c b/alhelpers.c new file mode 100644 index 0000000..db3fa55 --- /dev/null +++ b/alhelpers.c @@ -0,0 +1,328 @@ +/* + * OpenAL Helpers + * + * Copyright (c) 2011 by Chris Robinson + * + * 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 + +#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); +} diff --git a/alhelpers.h b/alhelpers.h new file mode 100644 index 0000000..dc57203 --- /dev/null +++ b/alhelpers.h @@ -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 */ diff --git a/apu.c b/apu.c new file mode 100644 index 0000000..bc96a7d --- /dev/null +++ b/apu.c @@ -0,0 +1,636 @@ +/* + * 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 +#include +#include +#include +#include "apu.h" +#include "audio.h" +#include "mem.h" +#include "cpu.h" + +//LP at 22kHz for 893415Hz input +static const float lpVal = 0.13398995f; + +//HP at 40Hz for 893415Hz input +static const float hpVal = 0.99971876f; + +#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[0x18]; + +static float *apuOutBuf; +static uint32_t curBufPos; +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 triLengthCtr, triLinearCtr, triCurLinearCtr; +static uint8_t dmcVol, dmcCurVol; +static uint8_t dmcSampleRemain; +static bool mode5 = false; +static int modePos = 0; +static uint16_t p1freqCtr, p2freqCtr, triFreqCtr, noiseFreqCtr, dmcFreqCtr; +static uint8_t p1Cycle, p2Cycle, triCycle, dmcCycle; +static bool p1haltloop, p2haltloop, trihaltloop, noisehaltloop, dmchaltloop; +static bool dmcstart; +static bool dmcirqenable; +static bool trireload; +static bool noiseMode1; +static bool apu_enable_irq; + +typedef struct _envelope_t { + bool start; + bool constant; + bool loop; + uint8_t vol; + //uint8_t envelope; + uint8_t divider; + uint8_t decay; +} envelope_t; + +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]; + +static 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 +}; + +static 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 uint8_t triSeq[32] = { + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, +}; + +static const uint16_t noisePeriod[16] = { + 4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068, +}; + +static const uint16_t dmcPeriod[16] = { + 428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54, +}; + +static const uint8_t *p1seq = pulseSeqs[0], + *p2seq = pulseSeqs[1]; +extern bool dmc_interrupt; + +void apuInit() +{ + memset(APU_IO_Reg,0,0x18); + apuOutBuf = (float*)malloc(APU_BUF_SIZE_BYTES); + memset(apuOutBuf, 0, APU_BUF_SIZE_BYTES); + 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, dmcFreqCtr = 0; + p1Cycle = 0; p2Cycle = 0; triCycle = 0; dmcCycle = 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; + /* https://wiki.nesdev.com/w/index.php/APU_Mixer#Lookup_Table */ + int 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 apuDeinit() +{ + if(apuOutBuf) + free(apuOutBuf); + apuOutBuf = NULL; +} + +void apuClockTimers() +{ + if(p1LengthCtr && (APU_IO_Reg[0x15] & P1_ENABLE)) + { + if(p1freqCtr/2 > freq1) + { + p1freqCtr = 0; + p1Cycle++; + } + if(p1Cycle >= 8) + p1Cycle = 0; + p1freqCtr++; + } + if(p2LengthCtr && (APU_IO_Reg[0x15] & P2_ENABLE)) + { + if(p2freqCtr/2 > freq2) + { + p2freqCtr = 0; + p2Cycle++; + } + if(p2Cycle >= 8) + p2Cycle = 0; + p2freqCtr++; + } + if(triLengthCtr && triCurLinearCtr && (APU_IO_Reg[0x15] & TRI_ENABLE)) + { + if(triFreqCtr > triFreq) + { + triFreqCtr = 0; + triCycle++; + } + if(triCycle >= 32) + triCycle = 0; + triFreqCtr++; + } + if(noiseLengthCtr && (APU_IO_Reg[0x15] & NOISE_ENABLE)) + { + if(noiseFreqCtr > noiseFreq) + { + noiseFreqCtr = 0; + uint8_t cmpBit = noiseMode1 ? (noiseShiftReg>>6)&1 : (noiseShiftReg>>1)&1; + uint8_t cmpRes = (noiseShiftReg&1)^cmpBit; + noiseShiftReg>>=1; + noiseShiftReg|=cmpRes<<14; + } + noiseFreqCtr++; + } + if(dmcLen && (APU_IO_Reg[0x15] & DMC_ENABLE)) + { + if(dmcstart) + { + dmcCurAddr = dmcAddr; + dmcCurLen = dmcLen; + //dmcCurVol = dmcVol; + if(!dmcSampleRemain) + dmcCycle = 8; + dmcstart = false; + } + if(dmcFreqCtr > dmcFreq) + { + dmcFreqCtr = 0; + dmcCycle++; + if(dmcSampleRemain) + { + if(dmcSampleBuf&1) + { + if(dmcVol <= 125) + dmcVol += 2; + } + else if(dmcVol >= 2) + dmcVol -= 2; + dmcSampleBuf>>=1; + dmcSampleRemain--; + } + } + if(dmcCycle >= 8) + { + if(dmcCurLen) + { + dmcSampleBuf = memGet8(dmcCurAddr); + cpuIncWaitCycles(4); + dmcCurAddr++; + if(dmcCurAddr < 0x8000) + dmcCurAddr |= 0x8000; + dmcCurLen--; + dmcSampleRemain = 8; + } + else if(dmchaltloop) + dmcstart = true; + else if(dmcirqenable) + dmc_interrupt = true; + dmcCycle = 0; + } + dmcFreqCtr++; + } +} + +static float lastHPOut = 0, lastLPOut = 0; +static uint8_t lastP1Out = 0, lastP2Out = 0, lastTriOut = 0, lastNoiseOut = 0; + +extern bool emuSkipVsync; + +int apuCycle() +{ + if(curBufPos == APU_BUF_SIZE) + { + int updateRes = audioUpdate(); + if(updateRes == 0) + { + emuSkipVsync = false; + return 0; + } + if(updateRes > 4) + emuSkipVsync = true; + else + emuSkipVsync = false; + curBufPos = 0; + } + uint8_t p1Out = lastP1Out, p2Out = lastP2Out, + triOut = lastTriOut, noiseOut = lastNoiseOut; + if(p1LengthCtr && (APU_IO_Reg[0x15] & P1_ENABLE)) + { + if(p1seq[p1Cycle] && !p1Sweep.mute && freq1 >= 8 && freq1 < 0x7FF) + lastP1Out = p1Out = (p1Env.constant ? p1Env.vol : p1Env.decay); + else + p1Out = 0; + } + if(p2LengthCtr && (APU_IO_Reg[0x15] & P2_ENABLE)) + { + if(p2seq[p2Cycle] && !p2Sweep.mute && freq2 >= 8 && freq2 < 0x7FF) + lastP2Out = p2Out = (p2Env.constant ? p2Env.vol : p2Env.decay); + else + p2Out = 0; + } + if(triLengthCtr && triCurLinearCtr && (APU_IO_Reg[0x15] & TRI_ENABLE)) + { + if(triSeq[triCycle] && triFreq >= 2) + lastTriOut = triOut = triSeq[triCycle]; + else + triOut = 0; + } + if(noiseLengthCtr && (APU_IO_Reg[0x15] & NOISE_ENABLE)) + { + if((noiseShiftReg&1) == 0 && noiseFreq > 0) + lastNoiseOut = noiseOut = (noiseEnv.constant ? noiseEnv.vol : noiseEnv.decay); + 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 1; +} + +static void doEnvelopeLogic(envelope_t *env) +{ + if(env->start) + { + env->start = false; + env->divider = env->vol; + env->decay = 15; + } + else + { + if(env->divider == 0) + { + env->divider = env->vol; + if(env->decay == 0) + { + if(env->loop) + env->decay = 15; + } + else + env->decay--; + } + 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->negative) + { + inFreq -= (inFreq >> sw->shift); + if(sw->chan1 == true) inFreq--; + } + else + inFreq += (inFreq >> sw->shift); + //if(inFreq > 8 && (inFreq < 0x7FF)) + { + if(sw->enabled && sw->shift) + *freq = inFreq; + } +} + +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) + { + doSweepLogic(&p2Sweep, &freq2); + if(!p2haltloop) + p2LengthCtr--; + } + if(triLengthCtr && !trihaltloop) + triLengthCtr--; + if(noiseLengthCtr && !noisehaltloop) + noiseLengthCtr--; +} + +void apuClockB() +{ + if(p1LengthCtr) + doEnvelopeLogic(&p1Env); + if(p2LengthCtr) + doEnvelopeLogic(&p2Env); + if(noiseLengthCtr) + doEnvelopeLogic(&noiseEnv); + if(trireload) + triCurLinearCtr = triLinearCtr; + else if(triCurLinearCtr) + triCurLinearCtr--; + if(!trihaltloop) + trireload = false; +} + +extern bool apu_interrupt; +extern void nesEmuResetApuClock(void); + +void apuLenCycle() +{ + if(mode5 == false) + { + if(modePos >= 4) + modePos = 0; + if(modePos == 1) + apuClockA(); + if(modePos == 3) + { + apuClockA(); + if(apu_enable_irq) + apu_interrupt = true; + } + apuClockB(); + } + else + { + if(modePos >= 5) + modePos = 0; + if(modePos == 0 || modePos == 2) + apuClockA(); + if(modePos != 4) + apuClockB(); + } + modePos++; +} + +void apuSet8(uint8_t reg, uint8_t val) +{ + //printf("%02x %02x %04x\n", reg, val, cpuGetPc()); + APU_IO_Reg[reg] = val; + if(reg == 0) + { + p1Env.vol = val&0xF; + p1seq = pulseSeqs[val>>6]; + p1Env.constant = ((val&PULSE_CONST_V) != 0); + p1Env.loop = p1haltloop = ((val&PULSE_HALT_LOOP) != 0); + } + else if(reg == 1) + { + //printf("P1 sweep %02x\n", val); + p1Sweep.enabled = ((val&0x80) != 0); + p1Sweep.shift = val&7; + p1Sweep.period = (val>>4)&7; + p1Sweep.negative = ((val&0x8) != 0); + p1Sweep.start = true; + doSweepLogic(&p1Sweep, &freq1); + } + else if(reg == 2) + { + //printf("P1 time low %02x\n", val); + freq1 = ((freq1&~0xFF) | val); + } + else if(reg == 3) + { + //p1Cycle = 0; + if(APU_IO_Reg[0x15] & P1_ENABLE) + p1LengthCtr = lengthLookupTbl[val>>3]; + freq1 = (freq1&0xFF) | ((val&7)<<8); + //printf("P1 new freq %04x\n", freq2); + p1Env.start = true; + } + else if(reg == 4) + { + p2Env.vol = val&0xF; + p2seq = pulseSeqs[val>>6]; + p2Env.constant = ((val&PULSE_CONST_V) != 0); + p2Env.loop = p2haltloop = ((val&PULSE_HALT_LOOP) != 0); + } + else if(reg == 5) + { + //printf("P2 sweep %02x\n", val); + p2Sweep.enabled = ((val&0x80) != 0); + p2Sweep.shift = val&7; + p2Sweep.period = (val>>4)&7; + p2Sweep.negative = ((val&0x8) != 0); + p2Sweep.start = true; + doSweepLogic(&p2Sweep, &freq2); + } + else if(reg == 6) + { + //printf("P2 time low %02x\n", val); + freq2 = ((freq2&~0xFF) | val); + } + else if(reg == 7) + { + //p2Cycle = 0; + if(APU_IO_Reg[0x15] & P2_ENABLE) + p2LengthCtr = lengthLookupTbl[val>>3]; + freq2 = (freq2&0xFF) | ((val&7)<<8); + //printf("P2 new freq %04x\n", freq2); + p2Env.start = true; + } + else if(reg == 8) + { + triLinearCtr = val&0x7F; + trihaltloop = ((val&TRI_HALT_LOOP) != 0); + } + else if(reg == 0xA) + { + triFreq = ((triFreq&~0xFF) | val); + //if(triFreq < 2) + // triLengthCtr = 0; + } + else if(reg == 0xB) + { + if(APU_IO_Reg[0x15] & TRI_ENABLE) + triLengthCtr = lengthLookupTbl[val>>3]; + triFreq = (triFreq&0xFF) | ((val&7)<<8); + //printf("Tri new freq %04x\n", triFreq); + //if(triFreq < 2) + // triLengthCtr = 0; + trireload = true; + } + else if(reg == 0xC) + { + noiseEnv.vol = val&0xF; + noiseEnv.constant = ((val&PULSE_CONST_V) != 0); + noiseEnv.loop = noisehaltloop = ((val&PULSE_HALT_LOOP) != 0); + } + else if(reg == 0xE) + { + noiseMode1 = ((val&0x80) != 0); + noiseFreq = noisePeriod[val&0xF]; + } + else if(reg == 0xF) + { + if(APU_IO_Reg[0x15] & NOISE_ENABLE) + noiseLengthCtr = lengthLookupTbl[val>>3]; + noiseEnv.start = true; + } + else if(reg == 0x10) + { + dmcFreq = dmcPeriod[val&0xF]; + dmchaltloop = ((val&DMC_HALT_LOOP) != 0); + dmcirqenable = ((val&DMC_IRQ_ENABLE) != 0); + //printf("%d\n", dmcirqenable); + if(!dmcirqenable) + dmc_interrupt = false; + } + else if(reg == 0x11) + dmcVol = val&0x7F; + else if(reg == 0x12) + dmcAddr = 0xC000+(val*64); + else if(reg == 0x13) + dmcLen = (val*16)+1; + else if(reg == 0x15) + { + //printf("Set 0x15 %02x\n",val); + if(!(val & P1_ENABLE)) + p1LengthCtr = 0; + if(!(val & P2_ENABLE)) + p2LengthCtr = 0; + if(!(val & TRI_ENABLE)) + triLengthCtr = 0; + if(!(val & NOISE_ENABLE)) + noiseLengthCtr = 0; + if(!(val & DMC_ENABLE)) + dmcCurLen = 0; + else if(dmcCurLen == 0) + dmcstart = true; + dmc_interrupt = false; + } + else if(reg == 0x17) + { + apu_enable_irq = ((val&(1<<6)) == 0); + if(!apu_enable_irq) + apu_interrupt = false; + mode5 = ((val&(1<<7)) != 0); + //printf("Set 0x17 %d %d\n", apu_enable_irq, mode5); + modePos = 0; + nesEmuResetApuClock(); + if(mode5) + apuLenCycle(); + } +} + +uint8_t apuGet8(uint8_t reg) +{ + //printf("%08x\n", reg); + if(reg == 0x15) + { + uint8_t intrflags = ((apu_interrupt<<6) | (dmc_interrupt<<7)); + //printf("Get 0x15 %02x\n",intrflags); + apu_interrupt = false; + return ((p1LengthCtr > 0) | ((p2LengthCtr > 0)<<1) | ((triLengthCtr > 0)<<2) | ((noiseLengthCtr > 0)<<3) | ((dmcCurLen > 0)<<4) | intrflags); + } + return APU_IO_Reg[reg]; +} + +uint8_t *apuGetBuf() +{ + return (uint8_t*)apuOutBuf; +} diff --git a/apu.h b/apu.h new file mode 100644 index 0000000..081f5f7 --- /dev/null +++ b/apu.h @@ -0,0 +1,27 @@ +/* + * 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 APU_FREQUENCY 1789773.f/2.f +//our actually effective output at 60hz +#define APU_FREQUENCY 893415.f +#define APU_BUF_SIZE ((int)(APU_FREQUENCY/60.f)) +#define APU_BUF_SIZE_BYTES APU_BUF_SIZE*sizeof(float) +#define NUM_BUFFERS 8 + +void apuInit(); +void apuDeinit(); +int apuCycle(); +void apuClockTimers(); +uint8_t *apuGetBuf(); +void apuSet8(uint8_t reg, uint8_t val); +uint8_t apuGet8(uint8_t reg); +void apuLenCycle(); + +#endif diff --git a/audio.c b/audio.c new file mode 100644 index 0000000..b97ecbb --- /dev/null +++ b/audio.c @@ -0,0 +1,209 @@ +#include +#include +#include +#include +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" +#include "alhelpers.h" +#include "apu.h" + +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(APU_BUF_SIZE_BYTES, 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 = APU_FREQUENCY; + 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(APU_BUF_SIZE_BYTES, 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(); +} diff --git a/audio.h b/audio.h new file mode 100644 index 0000000..b4ca619 --- /dev/null +++ b/audio.h @@ -0,0 +1,9 @@ + +#ifndef _audio_h_ +#define _audio_h_ + +int audioInit(); +int audioUpdate(); +void audioDeinit(); + +#endif diff --git a/build_console.bat b/build_console.bat new file mode 100644 index 0000000..fb4983e --- /dev/null +++ b/build_console.bat @@ -0,0 +1,2 @@ +gcc -DWINDOWS_BUILD main.c apu.c audio.c alhelpers.c cpu.c ppu.c mem.c input.c mapper.c fm2play.c mapper/*.c -DFREEGLUT_STATIC -lfreeglut_static -lopenal32 -lopengl32 -lglu32 -lgdi32 -lwinmm -Wall -Wextra -O3 -s -o fixNES +pause \ No newline at end of file diff --git a/build_noconsole.bat b/build_noconsole.bat new file mode 100644 index 0000000..adb4a60 --- /dev/null +++ b/build_noconsole.bat @@ -0,0 +1,2 @@ +gcc -DWINDOWS_BUILD main.c apu.c audio.c alhelpers.c cpu.c ppu.c mem.c input.c mapper.c fm2play.c mapper/*.c -DFREEGLUT_STATIC -lfreeglut_static -lopenal32 -lopengl32 -lglu32 -lgdi32 -lwinmm -Wall -Wextra -O3 -s -o fixNES -Wl,--subsystem,windows +pause diff --git a/cpu.c b/cpu.c new file mode 100644 index 0000000..bc0fe0c --- /dev/null +++ b/cpu.c @@ -0,0 +1,1915 @@ +/* + * 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 +#include +#include +#include "mem.h" +#include "ppu.h" + +#define P_FLAG_CARRY (1<<0) +#define P_FLAG_ZERO (1<<1) +#define P_FLAG_IRQ_DISABLE (1<<2) +#define P_FLAG_DECIMAL (1<<3) +#define P_FLAG_S1 (1<<4) +#define P_FLAG_S2 (1<<5) +#define P_FLAG_OVERFLOW (1<<6) +#define P_FLAG_NEGATIVE (1<<7) + +static uint16_t pc; +static uint8_t p,a,x,y,s; +static uint32_t waitCycles; +static bool reset; +static bool interrupt; +//used externally +bool dmc_interrupt; +bool apu_interrupt; +extern bool nesPause; + +void cpuInit() +{ + reset = true; + interrupt = false; + dmc_interrupt = false; + apu_interrupt = false; + p = (P_FLAG_IRQ_DISABLE | P_FLAG_S1 | P_FLAG_S2); + a = 0; + x = 0; + y = 0; + s = 0xFD; + waitCycles = 0; +} + +static void setRegStats(uint8_t reg) +{ + if(reg == 0) + { + p |= P_FLAG_ZERO; + p &= ~P_FLAG_NEGATIVE; + } + else + { + if(reg & (1<<7)) + p |= P_FLAG_NEGATIVE; + else + p &= ~P_FLAG_NEGATIVE; + p &= ~P_FLAG_ZERO; + } +} + +static void cpuSetARRRegs() +{ + if((a & ((1<<5) | (1<<6))) == ((1<<5) | (1<<6))) + { + p |= P_FLAG_CARRY; + p &= ~P_FLAG_OVERFLOW; + } + else if((a & ((1<<5) | (1<<6))) == 0) + { + p &= ~P_FLAG_CARRY; + p &= ~P_FLAG_OVERFLOW; + } + else if(a & (1<<5)) + { + p &= ~P_FLAG_CARRY; + p |= P_FLAG_OVERFLOW; + } + else if(a & (1<<6)) + { + p |= P_FLAG_CARRY; + p |= P_FLAG_OVERFLOW; + } +} + +static void cpuRelativeBranch() +{ + int8_t loc = memGet8(pc++); + uint16_t oldPc = pc; + pc += loc; + waitCycles++; + if((oldPc&0xFF00) != (pc&0xFF00)) + waitCycles++; +} + +static void cpuDummyRead(uint16_t addr, uint8_t val, bool alwaysAddCycle) +{ + if(alwaysAddCycle || (addr>>8) != ((addr+val)>>8)) + { //dummy read + memGet8((addr&0xFF00)|((addr+val)&0xFF)); + waitCycles++; + } +} + +/* Helper functions for updating reg sets */ + +static inline void cpuSetA(uint8_t val) +{ + a = val; + setRegStats(a); +} + +static inline void cpuSetX(uint8_t val) +{ + x = val; + setRegStats(x); +} + +static inline void cpuSetY(uint8_t val) +{ + y = val; + setRegStats(y); +} + +static inline uint8_t cpuSetTMP(uint8_t val) +{ + setRegStats(val); + return val; +} + + +/* Various functions for different memory access instructions */ + +static inline uint8_t getImmediate() { return memGet8(pc); }; +static inline uint8_t getZeroPage() { return memGet8(pc); } +static inline uint8_t getZeroPagePlus(uint8_t tmp) { return (getZeroPage()+tmp)&0xFF; } +static inline uint16_t getAbsoluteAddr() { return memGet8(pc)|(memGet8(pc+1)<<8); } + +static inline uint16_t getAbsolutePlusAddr(uint8_t val, bool alwaysAddCycle) +{ + uint16_t addr = getAbsoluteAddr(); + cpuDummyRead(addr, val, alwaysAddCycle); + return addr+val; +} + +static inline uint16_t getIndirectPlusXaddr() { uint8_t p = getZeroPagePlus(x); return (memGet8(p))|(memGet8((p+1)&0xFF)<<8); } +static inline uint16_t getIndirectPlusYaddr(bool alwaysAddCycle) +{ + uint8_t p = getZeroPage(); + uint16_t addr = (memGet8(p)|memGet8((p+1)&0xFF)<<8); + cpuDummyRead(addr, y, alwaysAddCycle); + return addr+y; +} + +static inline void cpuImmediateEnd() { pc++; } +static inline void cpuZeroPageEnd() { pc++; waitCycles++; } +static inline void cpuZeroPagePlusEnd() { pc++; waitCycles+=2; } +static inline void cpuAbsoluteEnd() { pc+=2; waitCycles+=2; } +static inline void cpuIndirectPlusXEnd() { pc++; waitCycles+=4; } +static inline void cpuIndirectPlusYEnd() { pc++; waitCycles+=3; } + +//used externally +bool cpuWriteTMP = false; + +static inline void cpuSaveTMPStart(uint16_t addr, uint8_t val) +{ + memSet8(addr, val); + waitCycles++; + cpuWriteTMP = true; +} + +static inline void cpuSaveTMPEnd(uint16_t addr, uint8_t val) +{ + memSet8(addr, val); + waitCycles++; + cpuWriteTMP = false; +} + + +/* Various Instructions used multiple times */ + +static inline void cpuAND(uint8_t val) +{ + a &= val; + setRegStats(a); +} + +static inline void cpuORA(uint8_t val) +{ + a |= val; + setRegStats(a); +} + +static inline void cpuEOR(uint8_t val) +{ + a ^= val; + setRegStats(a); +} + +static inline uint8_t cpuASL(uint8_t val) +{ + if(val & (1<<7)) + p |= P_FLAG_CARRY; + else + p &= ~P_FLAG_CARRY; + val <<= 1; + setRegStats(val); + return val; +} + +static inline uint8_t cpuLSR(uint8_t val) +{ + if(val & (1<<0)) + p |= P_FLAG_CARRY; + else + p &= ~P_FLAG_CARRY; + val >>= 1; + setRegStats(val); + return val; +} + +static inline uint8_t cpuROL(uint8_t val) +{ + uint8_t oldP = p; + if(val & (1<<7)) + p |= P_FLAG_CARRY; + else + p &= ~P_FLAG_CARRY; + val<<=1; + if(oldP & P_FLAG_CARRY) + val |= (1<<0); //bit 0 + setRegStats(val); + return val; +} + +static inline uint8_t cpuROR(uint8_t val) +{ + uint8_t oldP = p; + if(val & (1<<0)) + p |= P_FLAG_CARRY; + else + p &= ~P_FLAG_CARRY; + val>>=1; + if(oldP & P_FLAG_CARRY) + val |= (1<<7); //bit 7 + setRegStats(val); + return val; +} + +static void cpuKIL() +{ + printf("Processor Requested Lock-Up at %04x\n", pc-1); + nesPause = true; +} + +static void cpuADC(uint8_t tmp) +{ + //use uint16_t here to easly detect carry + uint16_t res = a + tmp; + + if(p & P_FLAG_CARRY) + res++; + + if(res > 0xFF) + p |= P_FLAG_CARRY; + else + p &= ~P_FLAG_CARRY; + + if(!(a & (1<<7)) && !(tmp & (1<<7)) && (res & (1<<7))) + p |= P_FLAG_OVERFLOW; + else if((a & (1<<7)) && (tmp & (1<<7)) && !(res & (1<<7))) + p |= P_FLAG_OVERFLOW; + else + p &= ~P_FLAG_OVERFLOW; + + cpuSetA(res); +} + +static inline void cpuSBC(uint8_t tmp) { cpuADC(~tmp); } + +static uint8_t cpuCMP(uint8_t v1, uint8_t v2) +{ + if(v1 >= v2) + p |= P_FLAG_CARRY; + else + p &= ~P_FLAG_CARRY; + + uint8_t cmpVal = (v1 - v2); + setRegStats(cmpVal); + return cmpVal; +} + +static void cpuBIT(uint8_t tmp) +{ + if((a & tmp) == 0) + p |= P_FLAG_ZERO; + else + p &= ~P_FLAG_ZERO; + + if(tmp & P_FLAG_OVERFLOW) + p |= P_FLAG_OVERFLOW; + else + p &= ~P_FLAG_OVERFLOW; + + if(tmp & P_FLAG_NEGATIVE) + p |= P_FLAG_NEGATIVE; + else + p &= ~P_FLAG_NEGATIVE; +} + +/* For Interrupt Handling */ + +#define DEBUG_INTR 0 +#define DEBUG_JSR 0 + +static void intrBackup() +{ + uint16_t tmp16 = pc;//+1; + //back up pc on stack + memSet8(0x100+s,tmp16>>8); + s--; + memSet8(0x100+s,tmp16&0xFF); + s--; + //back up p on stack + memSet8(0x100+s,p); + s--; +} + + +/* Main CPU Interpreter */ +//static int intrPrintUpdate = 0; +static int p_irq_req = 0; +static bool cpu_interrupt_req = false; +static bool ppu_nmi_handler_req = false; +//set externally +bool mapper_interrupt = false; +bool cpuCycle() +{ + //make sure to wait if needed + if(waitCycles) + { + waitCycles--; + return true; + } + //grab reset from vector + if(reset) + { + pc = memGet8(0xFFFC)|(memGet8(0xFFFD)<<8); + #if DEBUG_INTR + printf("Reset at %04x\n",pc); + #endif + reset = false; + return true; + } + //update irq flag if requested + if(p_irq_req) + { + if(p_irq_req == 1) + { + #if DEBUG_INTR + printf("Setting irq disable %02x %02x %d %d %d %d line %i dot %i\n", p, P_FLAG_IRQ_DISABLE, cpu_interrupt_req, apu_interrupt, + !(p & P_FLAG_IRQ_DISABLE), (p & P_FLAG_IRQ_DISABLE) == 0, curLine, curDot); + #endif + p |= P_FLAG_IRQ_DISABLE; + } + else + { + #if DEBUG_INTR + printf("Clearing irq disable %02x %02x %d %d %d %d line %i dot %i\n", p, P_FLAG_IRQ_DISABLE, cpu_interrupt_req, apu_interrupt, + !(p & P_FLAG_IRQ_DISABLE), (p & P_FLAG_IRQ_DISABLE) == 0, curLine, curDot); + #endif + p &= ~P_FLAG_IRQ_DISABLE; + } + p_irq_req = 0; + } + if(ppu_nmi_handler_req) + { + #if DEBUG_INTR + printf("NMI from p %02x pc %04x line %i dot %i ",p,pc, curLine, curDot); + #endif + p |= P_FLAG_S2; + p &= ~P_FLAG_S1; + intrBackup(); + p |= P_FLAG_IRQ_DISABLE; + //jump to NMI + pc = memGet8(0xFFFA)|(memGet8(0xFFFB)<<8); + #if DEBUG_INTR + printf("to pc %04x\n",pc); + #endif + waitCycles+=5; + ppu_nmi_handler_req = false; + } + else if(cpu_interrupt_req) + { + #if DEBUG_INTR + printf("INTR %d %d %d from p %02x pc %04x line %i dot %i ",interrupt,dmc_interrupt,apu_interrupt,p,pc, curLine, curDot); + #endif + intrBackup(); + p |= P_FLAG_IRQ_DISABLE; + pc = memGet8(0xFFFE)|(memGet8(0xFFFF)<<8); + #if DEBUG_INTR + printf("to pc %04x\n",pc); + #endif + if(interrupt) + interrupt = false; + waitCycles+=5; + } + uint16_t instrPtr = pc; + //printf("%04x\n", instrPtr); + /*if(intrPrintUpdate == 100) + { + printf("%04x\n", instrPtr); + intrPrintUpdate = 0; + } + else + intrPrintUpdate++;*/ + uint8_t instr = memGet8(instrPtr); + uint8_t tmp, zPage; + uint16_t absAddr, val; + pc++; waitCycles++; + switch(instr) + { + case 0x00: //BRK + memGet8(pc); + #if DEBUG_INTR + printf("BRK\n"); + #endif + interrupt = true; + p |= (P_FLAG_S1 | P_FLAG_S2); + pc++; + break; + case 0x01: //ORA (Indirect, X) + cpuORA(memGet8(getIndirectPlusXaddr())); + cpuIndirectPlusXEnd(); + break; + case 0x02: //KIL + cpuKIL(); + break; + case 0x03: //SLO (Indirect, X) + absAddr = getIndirectPlusXaddr(); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuASL(tmp); + cpuSaveTMPEnd(absAddr, tmp); + cpuORA(tmp); + cpuIndirectPlusXEnd(); + break; + case 0x04: //DOP Zero Page + cpuZeroPageEnd(); + break; + case 0x05: //ORA Zero Page + cpuORA(memGet8(getZeroPage())); + cpuZeroPageEnd(); + break; + case 0x06: //ASL Zero Page + zPage = getZeroPage(); + tmp = memGet8(zPage); + cpuSaveTMPStart(zPage, tmp); + tmp = cpuASL(tmp); + cpuSaveTMPEnd(zPage, tmp); + cpuZeroPageEnd(); + break; + case 0x07: //SLO Zero Page + zPage = getZeroPage(); + tmp = memGet8(zPage); + cpuSaveTMPStart(zPage, tmp); + tmp = cpuASL(tmp); + cpuSaveTMPEnd(zPage, tmp); + cpuORA(tmp); + cpuZeroPageEnd(); + break; + case 0x08: //PHP + memGet8(pc); + p |= (P_FLAG_S1 | P_FLAG_S2); + memSet8(0x100+s,p); + s--; + waitCycles++; + break; + case 0x09: //ORA Immediate + cpuORA(getImmediate()); + cpuImmediateEnd(); + break; + case 0x0A: //ASL A + memGet8(pc); + cpuSetA(cpuASL(a)); + break; + case 0x0B: //AAC Immediate + cpuAND(getImmediate()); + if(p & P_FLAG_NEGATIVE) + p |= P_FLAG_CARRY; + else + p &= ~P_FLAG_CARRY; + cpuImmediateEnd(); + break; + case 0x0C: //TOP Absolute + cpuAbsoluteEnd(); + break; + case 0x0D: //ORA Absolute + cpuORA(memGet8(getAbsoluteAddr())); + cpuAbsoluteEnd(); + break; + case 0x0E: //ASL Absolute + absAddr = getAbsoluteAddr(); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuASL(tmp); + cpuSaveTMPEnd(absAddr,tmp); + cpuAbsoluteEnd(); + break; + case 0x0F: //SLO Absolute + absAddr = getAbsoluteAddr(); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuASL(tmp); + cpuSaveTMPEnd(absAddr,tmp); + cpuORA(tmp); + cpuAbsoluteEnd(); + break; + case 0x10: //BPL + if(p & P_FLAG_NEGATIVE) + pc++; + else + cpuRelativeBranch(); + break; + case 0x11: //ORA (Indirect), Y + absAddr = getIndirectPlusYaddr(false); + cpuORA(memGet8(absAddr)); + cpuIndirectPlusYEnd(); + break; + case 0x12: //KIL + cpuKIL(); + break; + case 0x13: //SLO (Indirect), Y + absAddr = getIndirectPlusYaddr(true); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuASL(tmp); + cpuSaveTMPEnd(absAddr,tmp); + cpuORA(tmp); + cpuIndirectPlusYEnd(); + break; + case 0x14: //DOP Zero Page, X + cpuZeroPagePlusEnd(); + break; + case 0x15: //ORA Zero Page, X + cpuORA(memGet8(getZeroPagePlus(x))); + cpuZeroPagePlusEnd(); + break; + case 0x16: //ASL Zero Page, X + zPage = getZeroPagePlus(x); + tmp = memGet8(zPage); + cpuSaveTMPStart(zPage, tmp); + tmp = cpuASL(tmp); + cpuSaveTMPEnd(zPage,tmp); + cpuZeroPagePlusEnd(); + break; + case 0x17: //SLO Zero Page, X + zPage = getZeroPagePlus(x); + tmp = memGet8(zPage); + cpuSaveTMPStart(zPage, tmp); + tmp = cpuASL(tmp); + cpuSaveTMPEnd(zPage, tmp); + cpuORA(tmp); + cpuZeroPagePlusEnd(); + break; + case 0x18: //CLC + memGet8(pc); + p &= ~P_FLAG_CARRY; + break; + case 0x19: //ORA Absolute, Y + absAddr = getAbsolutePlusAddr(y,false); + cpuORA(memGet8(absAddr)); + cpuAbsoluteEnd(); + break; + case 0x1A: //NOP + memGet8(pc); + break; + case 0x1B: //SLO Absolute, Y + absAddr = getAbsolutePlusAddr(y,true); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuASL(tmp); + cpuSaveTMPEnd(absAddr, tmp); + cpuORA(tmp); + cpuAbsoluteEnd(); + break; + case 0x1C: //TOP Absolute, X + absAddr = getAbsolutePlusAddr(x,false); + cpuAbsoluteEnd(); + break; + case 0x1D: //ORA Absolute, X + absAddr = getAbsolutePlusAddr(x,false); + cpuORA(memGet8(absAddr)); + cpuAbsoluteEnd(); + break; + case 0x1E: //ASL Absolute, X + absAddr = getAbsolutePlusAddr(x,true); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuASL(tmp); + cpuSaveTMPEnd(absAddr, tmp); + cpuAbsoluteEnd(); + break; + case 0x1F: //SLO Absolute, X + absAddr = getAbsolutePlusAddr(x,true); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuASL(tmp); + cpuSaveTMPEnd(absAddr, tmp); + cpuORA(tmp); + cpuAbsoluteEnd(); + break; + case 0x20: //JSR Absolute + absAddr = (pc+1); + #if DEBUG_JSR + printf("JSR Abs from s %04x pc %04x to ",s,pc); + #endif + memSet8(0x100+s,absAddr>>8); + s--; + memSet8(0x100+s,absAddr&0xFF); + s--; + pc = memGet8(pc)|(memGet8(pc+1)<<8); + #if DEBUG_JSR + printf("%04x\n",pc); + #endif + waitCycles+=4; + break; + case 0x21: //AND (Indirect, X) + cpuAND(memGet8(getIndirectPlusXaddr())); + cpuIndirectPlusXEnd(); + break; + case 0x22: //KIL + cpuKIL(); + break; + case 0x23: //RLA (Indirect, X) + absAddr = getIndirectPlusXaddr(); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuROL(tmp); + cpuSaveTMPEnd(absAddr, tmp); + cpuAND(tmp); + cpuIndirectPlusXEnd(); + break; + case 0x24: //BIT Zero Page + cpuBIT(memGet8(getZeroPage())); + cpuZeroPageEnd(); + break; + case 0x25: //AND Zero Page + cpuAND(memGet8(getZeroPage())); + cpuZeroPageEnd(); + break; + case 0x26: //ROL Zero Page + zPage = getZeroPage(); + tmp = memGet8(zPage); + cpuSaveTMPStart(zPage, tmp); + tmp = cpuROL(tmp); + cpuSaveTMPEnd(zPage, tmp); + cpuZeroPageEnd(); + break; + case 0x27: //RLA Zero Page + zPage = getZeroPage(); + tmp = memGet8(zPage); + cpuSaveTMPStart(zPage, tmp); + tmp = cpuROL(tmp); + cpuSaveTMPEnd(zPage, tmp); + cpuAND(tmp); + cpuZeroPageEnd(); + break; + case 0x28: //PLP + memGet8(pc); + s++; + tmp = memGet8(0x100+s); + p &= P_FLAG_IRQ_DISABLE; + p |= (tmp & ~P_FLAG_IRQ_DISABLE); + //will do it for us after + if(tmp & P_FLAG_IRQ_DISABLE) + { + //printf("PLP IRQ Set 1\n"); + p_irq_req = 1; + } + else + { + //printf("PLP IRQ Set 2\n"); + p_irq_req = 2; + } + waitCycles+=2; + break; + case 0x29: //AND Immediate + cpuAND(getImmediate()); + cpuImmediateEnd(); + break; + case 0x2A: //ROL A + memGet8(pc); + cpuSetA(cpuROL(a)); + break; + case 0x2B: //AAC Immediate + cpuAND(getImmediate()); + if(p & P_FLAG_NEGATIVE) + p |= P_FLAG_CARRY; + else + p &= ~P_FLAG_CARRY; + cpuImmediateEnd(); + break; + case 0x2C: //BIT Absolute + cpuBIT(memGet8(getAbsoluteAddr())); + cpuAbsoluteEnd(); + break; + case 0x2D: //AND Absolute + cpuAND(memGet8(getAbsoluteAddr())); + cpuAbsoluteEnd(); + break; + case 0x2E: //ROL Absolute + absAddr = getAbsoluteAddr(); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuROL(tmp); + cpuSaveTMPEnd(absAddr, tmp); + cpuAbsoluteEnd(); + break; + case 0x2F: //RLA Absolute + absAddr = getAbsoluteAddr(); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuROL(tmp); + cpuSaveTMPEnd(absAddr, tmp); + cpuAND(tmp); + cpuAbsoluteEnd(); + break; + case 0x30: //BMI + if(!(p & P_FLAG_NEGATIVE)) + pc++; + else + cpuRelativeBranch(); + break; + case 0x31: //AND (Indirect), Y + absAddr = getIndirectPlusYaddr(false); + cpuAND(memGet8(absAddr)); + cpuIndirectPlusYEnd(); + break; + case 0x32: //KIL + cpuKIL(); + break; + case 0x33: //RLA (Indirect), Y + absAddr = getIndirectPlusYaddr(true); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuROL(tmp); + cpuSaveTMPEnd(absAddr, tmp); + cpuAND(tmp); + cpuIndirectPlusYEnd(); + break; + case 0x34: //DOP Zero Page, X + cpuZeroPagePlusEnd(); + break; + case 0x35: //AND Zero Page, X + cpuAND(memGet8(getZeroPagePlus(x))); + cpuZeroPagePlusEnd(); + break; + case 0x36: //ROL Zero Page, X + zPage = getZeroPagePlus(x); + tmp = memGet8(zPage); + cpuSaveTMPStart(zPage, tmp); + tmp = cpuROL(tmp); + cpuSaveTMPEnd(zPage, tmp); + cpuZeroPagePlusEnd(); + break; + case 0x37: //RLA Zero Page, X + zPage = getZeroPagePlus(x); + tmp = memGet8(zPage); + cpuSaveTMPStart(zPage, tmp); + tmp = cpuROL(tmp); + cpuSaveTMPEnd(zPage, tmp); + cpuAND(tmp); + cpuZeroPagePlusEnd(); + break; + case 0x38: //SEC + memGet8(pc); + p |= P_FLAG_CARRY; + break; + case 0x39: //AND Absolute, Y + absAddr = getAbsolutePlusAddr(y,false); + cpuAND(memGet8(absAddr)); + cpuAbsoluteEnd(); + break; + case 0x3A: //NOP + memGet8(pc); + break; + case 0x3B: //RLA Absolute, Y + absAddr = getAbsolutePlusAddr(y,true); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuROL(tmp); + cpuSaveTMPEnd(absAddr, tmp); + cpuAND(tmp); + cpuAbsoluteEnd(); + break; + case 0x3C: //TOP Absolute, X + absAddr = getAbsolutePlusAddr(x,false); + cpuAbsoluteEnd(); + break; + case 0x3D: //AND Absolute, X + absAddr = getAbsolutePlusAddr(x,false); + cpuAND(memGet8(absAddr)); + cpuAbsoluteEnd(); + break; + case 0x3E: //ROL Absolute, X + absAddr = getAbsolutePlusAddr(x,true); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuROL(tmp); + cpuSaveTMPEnd(absAddr, tmp); + cpuAbsoluteEnd(); + break; + case 0x3F: //RLA Absolute, X + absAddr = getAbsolutePlusAddr(x,true); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuROL(tmp); + cpuSaveTMPEnd(absAddr, tmp); + cpuAND(tmp); + cpuAbsoluteEnd(); + break; + case 0x40: //RTI + memGet8(pc); + #if DEBUG_INTR + printf("RTI from p %02x pc %04x line %i dot %i ",p,pc, curLine, curDot); + #endif + //get back p from stack + s++; + p = memGet8(0x100+s); + //get back pc from stack + s++; + pc = memGet8(0x100+s); + s++; + pc |= memGet8(0x100+s)<<8; + //jump back + #if DEBUG_INTR + printf("to p %02x pc %04x\n",p,pc); + #endif + waitCycles+=4; + break; + case 0x41: //EOR (Indirect, X) + cpuEOR(memGet8(getIndirectPlusXaddr())); + cpuIndirectPlusXEnd(); + break; + case 0x42: //KIL + cpuKIL(); + break; + case 0x43: //SRE (Indirect, X) + absAddr = getIndirectPlusXaddr(); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuLSR(tmp); + cpuSaveTMPEnd(absAddr, tmp); + cpuEOR(tmp); + cpuIndirectPlusXEnd(); + break; + case 0x44: //DOP Zero Page + cpuZeroPageEnd(); + break; + case 0x45: //EOR Zero Page + cpuEOR(memGet8(getZeroPage())); + cpuZeroPageEnd(); + break; + case 0x46: //LSR Zero Page + zPage = getZeroPage(); + tmp = memGet8(zPage); + cpuSaveTMPStart(zPage, tmp); + tmp = cpuLSR(tmp); + cpuSaveTMPEnd(zPage, tmp); + cpuZeroPageEnd(); + break; + case 0x47: //SRE Zero Page + zPage = getZeroPage(); + tmp = memGet8(zPage); + cpuSaveTMPStart(zPage, tmp); + tmp = cpuLSR(tmp); + cpuSaveTMPEnd(zPage, tmp); + cpuEOR(tmp); + cpuZeroPageEnd(); + break; + case 0x48: //PHA + memGet8(pc); + memSet8(0x100+s,a); + s--; + waitCycles++; + break; + case 0x49: //EOR Immediate + cpuEOR(getImmediate()); + cpuImmediateEnd(); + break; + case 0x4A: //LSR A + memGet8(pc); + cpuSetA(cpuLSR(a)); + break; + case 0x4B: //ASR Immediate + cpuAND(getImmediate()); + cpuSetA(cpuLSR(a)); + cpuImmediateEnd(); + break; + case 0x4C: //JMP Absolute + #if DEBUG_JSR + printf("JMP from %04x to ",pc); + #endif + pc = getAbsoluteAddr(); + #if DEBUG_JSR + printf("%04x\n",pc); + #endif + waitCycles++; + break; + case 0x4D: //EOR Absolute + cpuEOR(memGet8(getAbsoluteAddr())); + cpuAbsoluteEnd(); + break; + case 0x4E: //LSR Absolute + absAddr = getAbsoluteAddr(); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuLSR(tmp); + cpuSaveTMPEnd(absAddr, tmp); + cpuAbsoluteEnd(); + break; + case 0x4F: //SRE Absolute + absAddr = getAbsoluteAddr(); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuLSR(tmp); + cpuSaveTMPEnd(absAddr, tmp); + cpuEOR(tmp); + cpuAbsoluteEnd(); + break; + case 0x50: //BVC + if(p & P_FLAG_OVERFLOW) + pc++; + else + cpuRelativeBranch(); + break; + case 0x51: //EOR (Indirect), Y + absAddr = getIndirectPlusYaddr(false); + cpuEOR(memGet8(absAddr)); + cpuIndirectPlusYEnd(); + break; + case 0x52: //KIL + cpuKIL(); + break; + case 0x53: //SRE (Indirect), Y + absAddr = getIndirectPlusYaddr(true); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuLSR(tmp); + cpuSaveTMPEnd(absAddr, tmp); + cpuEOR(tmp); + cpuIndirectPlusYEnd(); + break; + case 0x54: //DOP Zero Page, X + cpuZeroPagePlusEnd(); + break; + case 0x55: //EOR Zero Page, X + cpuEOR(memGet8(getZeroPagePlus(x))); + cpuZeroPagePlusEnd(); + break; + case 0x56: //LSR Zero Page, X + zPage = getZeroPagePlus(x); + tmp = memGet8(zPage); + cpuSaveTMPStart(zPage, tmp); + tmp = cpuLSR(tmp); + cpuSaveTMPEnd(zPage, tmp); + cpuZeroPagePlusEnd(); + break; + case 0x57: //SRE Zero Page, X + zPage = getZeroPagePlus(x); + tmp = memGet8(zPage); + cpuSaveTMPStart(zPage, tmp); + tmp = cpuLSR(tmp); + cpuSaveTMPEnd(zPage, tmp); + cpuEOR(tmp); + cpuZeroPagePlusEnd(); + break; + case 0x58: //CLI + memGet8(pc); + #if DEBUG_INTR + printf("CLI\n"); + #endif + p_irq_req = 2; //will do it for us later + break; + case 0x59: //EOR Absolute, Y + absAddr = getAbsolutePlusAddr(y,false); + cpuEOR(memGet8(absAddr)); + cpuAbsoluteEnd(); + break; + case 0x5A: //NOP + memGet8(pc); + break; + case 0x5B: //SRE Absolute, Y + absAddr = getAbsolutePlusAddr(y,true); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuLSR(tmp); + cpuSaveTMPEnd(absAddr, tmp); + cpuEOR(tmp); + cpuAbsoluteEnd(); + break; + case 0x5C: //TOP Absolute, X + absAddr = getAbsolutePlusAddr(x,false); + cpuAbsoluteEnd(); + break; + case 0x5D: //EOR Absolute, X + absAddr = getAbsolutePlusAddr(x,false); + cpuEOR(memGet8(absAddr)); + cpuAbsoluteEnd(); + break; + case 0x5E: //LSR Absolute, X + absAddr = getAbsolutePlusAddr(x,true); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuLSR(tmp); + cpuSaveTMPEnd(absAddr, tmp); + cpuAbsoluteEnd(); + break; + case 0x5F: //SRE Absolute, X + absAddr = getAbsolutePlusAddr(x,true); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuLSR(tmp); + cpuSaveTMPEnd(absAddr, tmp); + cpuEOR(tmp); + cpuAbsoluteEnd(); + break; + case 0x60: //RTS + memGet8(pc); + #if DEBUG_JSR + printf("RTS from s %04x pc %04x to ",s,pc); + #endif + s++; + absAddr = memGet8(0x100+s); + s++; + absAddr |= (memGet8(0x100+s) << 8); + pc = (absAddr+1); + #if DEBUG_JSR + printf("%04x\n",pc); + #endif + waitCycles+=4; + break; + case 0x61: //ADC (Indirect, X) + cpuADC(memGet8(getIndirectPlusXaddr())); + cpuIndirectPlusXEnd(); + break; + case 0x62: //KIL + cpuKIL(); + break; + case 0x63: //RRA (Indirect, X) + absAddr = getIndirectPlusXaddr(); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuROR(tmp); + cpuSaveTMPEnd(absAddr, tmp); + cpuADC(tmp); + cpuIndirectPlusXEnd(); + break; + case 0x64: //DOP Zero Page + cpuZeroPageEnd(); + break; + case 0x65: //ADC Zero Page + cpuADC(memGet8(getZeroPage())); + cpuZeroPageEnd(); + break; + case 0x66: //ROR Zero Page + zPage = getZeroPage(); + tmp = memGet8(zPage); + cpuSaveTMPStart(zPage, tmp); + tmp = cpuROR(tmp); + cpuSaveTMPEnd(zPage, tmp); + cpuZeroPageEnd(); + break; + case 0x67: //RRA Zero Page + zPage = getZeroPage(); + tmp = memGet8(zPage); + cpuSaveTMPStart(zPage, tmp); + tmp = cpuROR(tmp); + cpuSaveTMPEnd(zPage, tmp); + cpuADC(tmp); + cpuZeroPageEnd(); + break; + case 0x68: //PLA + memGet8(pc); + s++; + cpuSetA(memGet8(0x100+s)); + waitCycles+=2; + break; + case 0x69: //ADC Immediate + cpuADC(getImmediate()); + cpuImmediateEnd(); + break; + case 0x6A: //ROR A + memGet8(pc); + cpuSetA(cpuROR(a)); + break; + case 0x6B: //ARR Immediate + cpuAND(getImmediate()); + cpuSetA(cpuROR(a)); + cpuSetARRRegs(); + cpuImmediateEnd(); + break; + case 0x6C: //JMP Indirect + #if DEBUG_JSR + printf("JMP from %04x to ",pc); + #endif + tmp = memGet8(pc++); + absAddr = (memGet8(pc)<<8); + pc = memGet8(absAddr+tmp); + //emulate 6502 jmp wrap bug + tmp++; //possibly FF->00 without page increase! + pc |= (memGet8(absAddr+tmp)<<8); + #if DEBUG_JSR + printf("%04x\n",pc); + #endif + waitCycles+=3; + break; + case 0x6D: //ADC Absolute + cpuADC(memGet8(getAbsoluteAddr())); + cpuAbsoluteEnd(); + break; + case 0x6E: //ROR Absolute + absAddr = getAbsoluteAddr(); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuROR(tmp); + cpuSaveTMPEnd(absAddr, tmp); + cpuAbsoluteEnd(); + break; + case 0x6F: //RRA Absolute + absAddr = getAbsoluteAddr(); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuROR(tmp); + cpuSaveTMPEnd(absAddr, tmp); + cpuADC(tmp); + cpuAbsoluteEnd(); + break; + case 0x70: //BVS + if(!(p & P_FLAG_OVERFLOW)) + pc++; + else + cpuRelativeBranch(); + break; + case 0x71: //ADC (Indirect), Y + absAddr = getIndirectPlusYaddr(false); + cpuADC(memGet8(absAddr)); + cpuIndirectPlusYEnd(); + break; + case 0x72: //KIL + cpuKIL(); + break; + case 0x73: //RRA (Indirect), Y + absAddr = getIndirectPlusYaddr(true); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuROR(tmp); + cpuSaveTMPEnd(absAddr, tmp); + cpuADC(tmp); + cpuIndirectPlusYEnd(); + break; + case 0x74: //DOP Zero Page, X + cpuZeroPagePlusEnd(); + break; + case 0x75: //ADC Zero Page, X + cpuADC(memGet8(getZeroPagePlus(x))); + cpuZeroPagePlusEnd(); + break; + case 0x76: //ROR Zero Page, X + zPage = getZeroPagePlus(x); + tmp = memGet8(zPage); + cpuSaveTMPStart(zPage, tmp); + tmp = cpuROR(tmp); + cpuSaveTMPEnd(zPage, tmp); + cpuZeroPagePlusEnd(); + break; + case 0x77: //RRA Zero Page, X + zPage = getZeroPagePlus(x); + tmp = memGet8(zPage); + cpuSaveTMPStart(zPage, tmp); + tmp = cpuROR(tmp); + cpuSaveTMPEnd(zPage, tmp); + cpuADC(tmp); + cpuZeroPagePlusEnd(); + break; + case 0x78: //SEI + memGet8(pc); + #if DEBUG_INTR + printf("SEI\n"); + #endif + p_irq_req = 1; //will do it for us after + break; + case 0x79: //ADC Absolute, Y + absAddr = getAbsolutePlusAddr(y,false); + cpuADC(memGet8(absAddr)); + cpuAbsoluteEnd(); + break; + case 0x7A: //NOP + memGet8(pc); + break; + case 0x7B: //RRA Absolute, Y + absAddr = getAbsolutePlusAddr(y,true); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuROR(tmp); + cpuSaveTMPEnd(absAddr, tmp); + cpuADC(tmp); + cpuAbsoluteEnd(); + break; + case 0x7C: //TOP Absolute, X + absAddr = getAbsolutePlusAddr(x,false); + cpuAbsoluteEnd(); + break; + case 0x7D: //ADC Absolute, X + absAddr = getAbsolutePlusAddr(x,false); + cpuADC(memGet8(absAddr)); + cpuAbsoluteEnd(); + break; + case 0x7E: //ROR Absolute, X + absAddr = getAbsolutePlusAddr(x,true); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuROR(tmp); + cpuSaveTMPEnd(absAddr, tmp); + cpuAbsoluteEnd(); + break; + case 0x7F: //RRA Absolute, X + absAddr = getAbsolutePlusAddr(x,true); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuROR(tmp); + cpuSaveTMPEnd(absAddr, tmp); + cpuADC(tmp); + cpuAbsoluteEnd(); + break; + case 0x80: //DOP Immediate + cpuImmediateEnd(); + break; + case 0x81: //STA (Indirect, X) + memSet8(getIndirectPlusXaddr(), a); + cpuIndirectPlusXEnd(); + break; + case 0x82: //DOP Immediate + cpuImmediateEnd(); + break; + case 0x83: //AAX (Indirect, X) + memSet8(getIndirectPlusXaddr(), a&x); + cpuIndirectPlusXEnd(); + break; + case 0x84: //STY Zero Page + memSet8(getZeroPage(), y); + cpuZeroPageEnd(); + break; + case 0x85: //STA Zero Page + memSet8(getZeroPage(), a); + cpuZeroPageEnd(); + break; + case 0x86: //STX Zero Page + memSet8(getZeroPage(), x); + cpuZeroPageEnd(); + break; + case 0x87: //AAX Zero Page + memSet8(getZeroPage(), a&x); + cpuZeroPageEnd(); + break; + case 0x88: //DEY + memGet8(pc); + cpuSetY(y-1); + break; + case 0x89: //DOP Immediate + cpuImmediateEnd(); + break; + case 0x8A: //TXA + memGet8(pc); + cpuSetA(x); + break; + case 0x8B: //XAA Immediate + cpuSetA(x&getImmediate()); + cpuImmediateEnd(); + break; + case 0x8C: //STY Absolute + memSet8(getAbsoluteAddr(), y); + cpuAbsoluteEnd(); + break; + case 0x8D: //STA Absolute + memSet8(getAbsoluteAddr(), a); + cpuAbsoluteEnd(); + break; + case 0x8E: //STX Absolute + memSet8(getAbsoluteAddr(), x); + cpuAbsoluteEnd(); + break; + case 0x8F: //AAX Absolute + memSet8(getAbsoluteAddr(), a&x); + cpuAbsoluteEnd(); + break; + case 0x90: //BCC + if(p & P_FLAG_CARRY) + pc++; + else + cpuRelativeBranch(); + break; + case 0x91: //STA (Indirect), Y + absAddr = getIndirectPlusYaddr(true); + memSet8(absAddr, a); + cpuIndirectPlusYEnd(); + break; + case 0x92: //KIL + cpuKIL(); + break; + case 0x93: //AXA (Indirect), Y + absAddr = getIndirectPlusYaddr(true); + memSet8(absAddr, a&x&(absAddr>>8)); + cpuIndirectPlusYEnd(); + break; + case 0x94: //STY Zero Page, X + memSet8(getZeroPagePlus(x), y); + cpuZeroPagePlusEnd(); + break; + case 0x95: //STA Zero Page, X + memSet8(getZeroPagePlus(x), a); + cpuZeroPagePlusEnd(); + break; + case 0x96: //STX Zero Page, Y + memSet8(getZeroPagePlus(y), x); + cpuZeroPagePlusEnd(); + break; + case 0x97: //AAX Zero Page, Y + memSet8(getZeroPagePlus(y), a&x); + cpuZeroPagePlusEnd(); + break; + case 0x98: //TYA + memGet8(pc); + cpuSetA(y); + break; + case 0x99: //STA Absolute, Y + absAddr = getAbsolutePlusAddr(y,true); + memSet8(absAddr, a); + cpuAbsoluteEnd(); + break; + case 0x9A: //TXS + memGet8(pc); + s = x; + break; + case 0x9B: //XAS Absolute, Y + val = getAbsolutePlusAddr(y,true); + absAddr = (val+x)&0xFFFF; + val = ((val&0xFF00)|(absAddr&0x00FF)); + if((val >> 8) != (absAddr >> 8)) + val &= y << 8; + s = a & x; + memSet8(val, a & x & ((val >> 8) + 1)); + cpuAbsoluteEnd(); + break; + case 0x9C: //SYA Absolute, X + val = getAbsoluteAddr(); + absAddr = (val+x)&0xFFFF; + val = ((val&0xFF00)|(absAddr&0x00FF)); + if((val >> 8) != (absAddr >> 8)) + val &= y << 8; + memSet8(val, y & ((val >> 8) + 1)); + waitCycles++; + cpuAbsoluteEnd(); + break; + case 0x9D: //STA Absolute, X + absAddr = getAbsolutePlusAddr(x,true); + memSet8(absAddr, a); + cpuAbsoluteEnd(); + break; + case 0x9E: //SXA Absolute, Y + val = getAbsoluteAddr(); + absAddr = (val+y)&0xFFFF; + val = ((val&0xFF00)|(absAddr&0x00FF)); + if((val >> 8) != (absAddr >> 8)) + val &= x << 8; + memSet8(val, x & ((val >> 8) + 1)); + waitCycles++; + cpuAbsoluteEnd(); + break; + case 0x9F: //AXA Absolute, Y + absAddr = getAbsolutePlusAddr(y,true); + memSet8(absAddr, a&x&(absAddr>>8)); + cpuAbsoluteEnd(); + break; + case 0xA0: //LDY Immediate + cpuSetY(getImmediate()); + cpuImmediateEnd(); + break; + case 0xA1: //LDA (Indirect, X) + cpuSetA(memGet8(getIndirectPlusXaddr())); + cpuIndirectPlusXEnd(); + break; + case 0xA2: //LDX Immediate + cpuSetX(getImmediate()); + cpuImmediateEnd(); + break; + case 0xA3: //LAX (Indirect, X) + tmp = memGet8(getIndirectPlusXaddr()); + cpuSetA(tmp); cpuSetX(tmp); + cpuIndirectPlusXEnd(); + break; + case 0xA4: //LDY Zero Page + cpuSetY(memGet8(getZeroPage())); + cpuZeroPageEnd(); + break; + case 0xA5: //LDA Zero Page + cpuSetA(memGet8(getZeroPage())); + cpuZeroPageEnd(); + break; + case 0xA6: //LDX Zero Page + cpuSetX(memGet8(getZeroPage())); + cpuZeroPageEnd(); + break; + case 0xA7: //LAX Zero Page + tmp = memGet8(getZeroPage()); + cpuSetA(tmp); cpuSetX(tmp); + cpuZeroPageEnd(); + break; + case 0xA8: //TAY + memGet8(pc); + cpuSetY(a); + break; + case 0xA9: //LDA Immediate + cpuSetA(getImmediate()); + cpuImmediateEnd(); + break; + case 0xAA: //TAX + memGet8(pc); + cpuSetX(a); + break; + case 0xAB: //AXT Immediate + cpuSetA(getImmediate()); + cpuSetX(a); + cpuImmediateEnd(); + break; + case 0xAC: //LDY Absolute + cpuSetY(memGet8(getAbsoluteAddr())); + cpuAbsoluteEnd(); + break; + case 0xAD: //LDA Absolute + cpuSetA(memGet8(getAbsoluteAddr())); + cpuAbsoluteEnd(); + break; + case 0xAE: //LDX Absolute + cpuSetX(memGet8(getAbsoluteAddr())); + cpuAbsoluteEnd(); + break; + case 0xAF: //LAX Absolute + tmp = memGet8(getAbsoluteAddr()); + cpuSetA(tmp); cpuSetX(tmp); + cpuAbsoluteEnd(); + break; + case 0xB0: //BCS + if(!(p & P_FLAG_CARRY)) + pc++; + else + cpuRelativeBranch(); + break; + case 0xB1: //LDA (Indirect), Y + absAddr = getIndirectPlusYaddr(false); + cpuSetA(memGet8(absAddr)); + cpuIndirectPlusYEnd(); + break; + case 0xB2: //KIL + cpuKIL(); + break; + case 0xB3: //LAX (Indirect), Y + absAddr = getIndirectPlusYaddr(false); + tmp = memGet8(absAddr); + cpuSetA(tmp); cpuSetX(tmp); + cpuIndirectPlusYEnd(); + break; + case 0xB4: //LDY Zero Page, X + cpuSetY(memGet8(getZeroPagePlus(x))); + cpuZeroPagePlusEnd(); + break; + case 0xB5: //LDA Zero Page, X + cpuSetA(memGet8(getZeroPagePlus(x))); + cpuZeroPagePlusEnd(); + break; + case 0xB6: //LDX Zero Page, Y + cpuSetX(memGet8(getZeroPagePlus(y))); + cpuZeroPagePlusEnd(); + break; + case 0xB7: //LAX Zero Page, Y + tmp = memGet8(getZeroPagePlus(y)); + cpuSetA(tmp); cpuSetX(tmp); + cpuZeroPagePlusEnd(); + break; + case 0xB8: //CLV + memGet8(pc); + p &= ~P_FLAG_OVERFLOW; + break; + case 0xB9: //LDA Absolute, Y + absAddr = getAbsolutePlusAddr(y,false); + cpuSetA(memGet8(absAddr)); + cpuAbsoluteEnd(); + break; + case 0xBA: //TSX + memGet8(pc); + cpuSetX(s); + break; + case 0xBB: //LAR Absolute, Y + absAddr = getAbsolutePlusAddr(y,false); + cpuSetA(memGet8(absAddr)); + cpuAND(s); cpuSetX(a); s = a; + cpuAbsoluteEnd(); + break; + case 0xBC: //LDY Absolute, X + absAddr = getAbsolutePlusAddr(x,false); + cpuSetY(memGet8(absAddr)); + cpuAbsoluteEnd(); + break; + case 0xBD: //LDA Absolute, X + absAddr = getAbsolutePlusAddr(x,false); + cpuSetA(memGet8(absAddr)); + cpuAbsoluteEnd(); + break; + case 0xBE: //LDX Absolute, Y + absAddr = getAbsolutePlusAddr(y,false); + cpuSetX(memGet8(absAddr)); + cpuAbsoluteEnd(); + break; + case 0xBF: //LAX Absolute, Y + absAddr = getAbsolutePlusAddr(y,false); + tmp = memGet8(absAddr); + cpuSetA(tmp); cpuSetX(tmp); + cpuAbsoluteEnd(); + break; + case 0xC0: //CPY Immediate + cpuCMP(y,getImmediate()); + cpuImmediateEnd(); + break; + case 0xC1: //CMP (Indirect, X) + cpuCMP(a,memGet8(getIndirectPlusXaddr())); + cpuIndirectPlusXEnd(); + break; + case 0xC2: //DOP Immediate + cpuImmediateEnd(); + break; + case 0xC3: //DCP (Indirect, X) + absAddr = getIndirectPlusXaddr(); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuSetTMP(tmp-1); + cpuSaveTMPEnd(absAddr, tmp); + cpuCMP(a,tmp); + cpuIndirectPlusXEnd(); + break; + case 0xC4: //CPY Zero Page + cpuCMP(y,memGet8(getZeroPage())); + cpuZeroPageEnd(); + break; + case 0xC5: //CMP Zero Page + cpuCMP(a,memGet8(getZeroPage())); + cpuZeroPageEnd(); + break; + case 0xC6: //DEC Zero Page + zPage = getZeroPage(); + tmp = memGet8(zPage); + cpuSaveTMPStart(zPage, tmp); + tmp = cpuSetTMP(tmp-1); + cpuSaveTMPEnd(zPage, tmp); + cpuZeroPageEnd(); + break; + case 0xC7: //DCP Zero Page + zPage = getZeroPage(); + tmp = memGet8(zPage); + cpuSaveTMPStart(zPage, tmp); + tmp = cpuSetTMP(tmp-1); + cpuSaveTMPEnd(zPage, tmp); + cpuCMP(a,tmp); + cpuZeroPageEnd(); + break; + case 0xC8: //INY + memGet8(pc); + cpuSetY(y+1); + break; + case 0xC9: //CMP Immediate + cpuCMP(a,getImmediate()); + cpuImmediateEnd(); + break; + case 0xCA: //DEX + memGet8(pc); + cpuSetX(x-1); + break; + case 0xCB: //AXS Immediate + x = cpuCMP(a&x,getImmediate()); + cpuImmediateEnd(); + break; + case 0xCC: //CPY Absolute + cpuCMP(y,memGet8(getAbsoluteAddr())); + cpuAbsoluteEnd(); + break; + case 0xCD: //CMP Absolute + cpuCMP(a,memGet8(getAbsoluteAddr())); + cpuAbsoluteEnd(); + break; + case 0xCE: //DEC Absolute + absAddr = getAbsoluteAddr(); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuSetTMP(tmp-1); + cpuSaveTMPEnd(absAddr, tmp); + cpuAbsoluteEnd(); + break; + case 0xCF: //DCP Absolute + absAddr = getAbsoluteAddr(); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuSetTMP(tmp-1); + cpuSaveTMPEnd(absAddr, tmp); + cpuCMP(a,tmp); + cpuAbsoluteEnd(); + break; + case 0xD0: //BNE + if(p & P_FLAG_ZERO) + pc++; + else + cpuRelativeBranch(); + break; + case 0xD1: //CMP (Indirect), Y + absAddr = getIndirectPlusYaddr(false); + cpuCMP(a,memGet8(absAddr)); + cpuIndirectPlusYEnd(); + break; + case 0xD2: //KIL + cpuKIL(); + break; + case 0xD3: //DCP (Indirect), Y + absAddr = getIndirectPlusYaddr(true); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuSetTMP(tmp-1); + cpuSaveTMPEnd(absAddr, tmp); + cpuCMP(a,tmp); + cpuIndirectPlusYEnd(); + break; + case 0xD4: //DOP Zero Page, X + cpuZeroPagePlusEnd(); + break; + case 0xD5: //CMP Zero Page, X + cpuCMP(a,memGet8(getZeroPagePlus(x))); + cpuZeroPagePlusEnd(); + break; + case 0xD6: //DEC Zero Page, X + zPage = getZeroPagePlus(x); + tmp = memGet8(zPage); + cpuSaveTMPStart(zPage, tmp); + tmp = cpuSetTMP(tmp-1); + cpuSaveTMPEnd(zPage, tmp); + cpuZeroPagePlusEnd(); + break; + case 0xD7: //DCP Zero Page, X + zPage = getZeroPagePlus(x); + tmp = memGet8(zPage); + cpuSaveTMPStart(zPage, tmp); + tmp = cpuSetTMP(tmp-1); + cpuSaveTMPEnd(zPage, tmp); + cpuCMP(a,tmp); + cpuZeroPagePlusEnd(); + break; + case 0xD8: //CLD + memGet8(pc); + p &= ~P_FLAG_DECIMAL; + break; + case 0xD9: //CMP Absolute, Y + absAddr = getAbsolutePlusAddr(y,false); + cpuCMP(a,memGet8(absAddr)); + cpuAbsoluteEnd(); + break; + case 0xDA: //NOP + memGet8(pc); + break; + case 0xDB: //DCP Absolute, Y + absAddr = getAbsolutePlusAddr(y,true); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuSetTMP(tmp-1); + cpuSaveTMPEnd(absAddr ,tmp); + cpuCMP(a,tmp); + cpuAbsoluteEnd(); + break; + case 0xDC: //TOP Absolute, X + absAddr = getAbsolutePlusAddr(x,false); + cpuAbsoluteEnd(); + break; + case 0xDD: //CMP Absolute, X + absAddr = getAbsolutePlusAddr(x,false); + cpuCMP(a,memGet8(absAddr)); + cpuAbsoluteEnd(); + break; + case 0xDE: //DEC Absolute, X + absAddr = getAbsolutePlusAddr(x,true); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuSetTMP(tmp-1); + cpuSaveTMPEnd(absAddr, tmp); + cpuAbsoluteEnd(); + break; + case 0xDF: //DCP Absolute, X + absAddr = getAbsolutePlusAddr(x,true); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuSetTMP(tmp-1); + cpuSaveTMPEnd(absAddr, tmp); + cpuCMP(a,tmp); + cpuAbsoluteEnd(); + break; + case 0xE0: //CPX Immediate + cpuCMP(x,getImmediate()); + cpuImmediateEnd(); + break; + case 0xE1: //SBC (Indirect, X) + cpuSBC(memGet8(getIndirectPlusXaddr())); + cpuIndirectPlusXEnd(); + break; + case 0xE2: //DOP Immediate + cpuImmediateEnd(); + break; + case 0xE3: //ISC (Indirect, X) + absAddr = getIndirectPlusXaddr(); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuSetTMP(tmp+1); + cpuSaveTMPEnd(absAddr, tmp); + cpuSBC(tmp); + cpuIndirectPlusXEnd(); + break; + case 0xE4: //CPX Zero Page + cpuCMP(x,memGet8(getZeroPage())); + cpuZeroPageEnd(); + break; + case 0xE5: //SBC Zero Page + cpuSBC(memGet8(getZeroPage())); + cpuZeroPageEnd(); + break; + case 0xE6: //INC Zero Page + zPage = getZeroPage(); + tmp = memGet8(zPage); + cpuSaveTMPStart(zPage, tmp); + tmp = cpuSetTMP(tmp+1); + cpuSaveTMPEnd(zPage, tmp); + cpuZeroPageEnd(); + break; + case 0xE7: //ISC Zero Page + zPage = getZeroPage(); + tmp = memGet8(zPage); + cpuSaveTMPStart(zPage, tmp); + tmp = cpuSetTMP(tmp+1); + cpuSaveTMPEnd(zPage, tmp); + cpuSBC(tmp); + cpuZeroPageEnd(); + break; + case 0xE8: //INX + memGet8(pc); + cpuSetX(x+1); + break; + case 0xE9: //SBC Immediate + cpuSBC(getImmediate()); + cpuImmediateEnd(); + break; + case 0xEA: //NOP + memGet8(pc); + break; + case 0xEB: //SBC Immediate + cpuSBC(getImmediate()); + cpuImmediateEnd(); + break; + case 0xEC: //CPX Absolute + cpuCMP(x,memGet8(getAbsoluteAddr())); + cpuAbsoluteEnd(); + break; + case 0xED: //SBC Absolute + cpuSBC(memGet8(getAbsoluteAddr())); + cpuAbsoluteEnd(); + break; + case 0xEE: //INC Absolute + absAddr = getAbsoluteAddr(); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuSetTMP(tmp+1); + cpuSaveTMPEnd(absAddr, tmp); + cpuAbsoluteEnd(); + break; + case 0xEF: //ISC Absolute + absAddr = getAbsoluteAddr(); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuSetTMP(tmp+1); + cpuSaveTMPEnd(absAddr, tmp); + cpuSBC(tmp); + cpuAbsoluteEnd(); + break; + case 0xF0: //BEQ + if(!(p & P_FLAG_ZERO)) + pc++; + else + cpuRelativeBranch(); + break; + case 0xF1: //SBC (Indirect), Y + absAddr = getIndirectPlusYaddr(false); + cpuSBC(memGet8(absAddr)); + cpuIndirectPlusYEnd(); + break; + case 0xF2: //KIL + cpuKIL(); + break; + case 0xF3: //ISC (Indirect), Y + absAddr = getIndirectPlusYaddr(true); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuSetTMP(tmp+1); + cpuSaveTMPEnd(absAddr, tmp); + cpuSBC(tmp); + cpuIndirectPlusYEnd(); + break; + case 0xF4: //DOP Zero Page, X + cpuZeroPagePlusEnd(); + break; + case 0xF5: //SBC Zero Page, X + cpuSBC(memGet8(getZeroPagePlus(x))); + cpuZeroPagePlusEnd(); + break; + case 0xF6: //INC Zero Page, X + zPage = getZeroPagePlus(x); + tmp = memGet8(zPage); + cpuSaveTMPStart(zPage, tmp); + tmp = cpuSetTMP(tmp+1); + cpuSaveTMPEnd(zPage, tmp); + cpuZeroPagePlusEnd(); + break; + case 0xF7: //ISC Zero Page, X + zPage = getZeroPagePlus(x); + tmp = memGet8(zPage); + cpuSaveTMPStart(zPage, tmp); + tmp = cpuSetTMP(tmp+1); + cpuSaveTMPEnd(zPage, tmp); + cpuSBC(tmp); + cpuZeroPagePlusEnd(); + break; + case 0xF8: //SED + memGet8(pc); + p |= P_FLAG_DECIMAL; + break; + case 0xF9: //SBC Absolute, Y + absAddr = getAbsolutePlusAddr(y,false); + cpuSBC(memGet8(absAddr)); + cpuAbsoluteEnd(); + break; + case 0xFA: //NOP + memGet8(pc); + break; + case 0xFB: //ISC Absolute, Y + absAddr = getAbsolutePlusAddr(y,true); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuSetTMP(tmp+1); + cpuSaveTMPEnd(absAddr, tmp); + cpuSBC(tmp); + cpuAbsoluteEnd(); + break; + case 0xFC: //TOP Absolute, X + absAddr = getAbsolutePlusAddr(x,false); + cpuAbsoluteEnd(); + break; + case 0xFD: //SBC Absolute, X + absAddr = getAbsolutePlusAddr(x,false); + cpuSBC(memGet8(absAddr)); + cpuAbsoluteEnd(); + break; + case 0xFE: //INC Absolute, X + absAddr = getAbsolutePlusAddr(x,true); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuSetTMP(tmp+1); + cpuSaveTMPEnd(absAddr, tmp); + cpuAbsoluteEnd(); + break; + case 0xFF: //ISC Absolute, X + absAddr = getAbsolutePlusAddr(x,true); + tmp = memGet8(absAddr); + cpuSaveTMPStart(absAddr, tmp); + tmp = cpuSetTMP(tmp+1); + cpuSaveTMPEnd(absAddr, tmp); + cpuSBC(tmp); + cpuAbsoluteEnd(); + break; + default: //should never happen + printf("Unknown instruction at %04x: %02x\n", instrPtr, instr); + memDumpMainMem(); + return false; + } + //update interrupt values + ppu_nmi_handler_req = ppuNMI(); + cpu_interrupt_req = (interrupt || ((mapper_interrupt || dmc_interrupt || apu_interrupt) && !(p & P_FLAG_IRQ_DISABLE))); + //if(instrPtr > 0xa980 && instrPtr < 0xa9C0) printf("%d %d %d %04x %04x\n",a,x,y,instrPtr,memGet8(instrPtr)|(memGet8(instrPtr+1)<<8)); + return true; +} + + +/* Access for other .c files */ + +void cpuIncWaitCycles(uint32_t inc) +{ + waitCycles += inc; +} + +uint16_t cpuGetPc() +{ + return pc; +} diff --git a/cpu.h b/cpu.h new file mode 100644 index 0000000..07e7c7c --- /dev/null +++ b/cpu.h @@ -0,0 +1,16 @@ +/* + * 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(); +void cpuIncWaitCycles(uint32_t inc); +uint16_t cpuGetPc(); + +#endif diff --git a/fm2play.c b/fm2play.c new file mode 100644 index 0000000..bbdd130 --- /dev/null +++ b/fm2play.c @@ -0,0 +1,88 @@ +/* + * 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 +#include +#include +#include +#include "fm2play.h" + +static char *fm2playIn = NULL; +static char *fm2playCur = NULL; +static size_t fm2playSize = 0; +bool waitDMAcycles = true; + +bool fm2playInit(char *fName, int fstart, bool wait) +{ + FILE *f = fopen(fName,"rb"); + if(!f) + { + printf("Huh\n"); + return false; + } + fseek(f,0,SEEK_END); + fm2playSize = ftell(f); + fm2playIn = (char*)malloc(fm2playSize+1); + fm2playIn[fm2playSize] = '\0'; + rewind(f); + fread(fm2playIn,1,fm2playSize,f); + fclose(f); + fm2playCur = fm2playIn; + + if(!wait) + waitDMAcycles = false; + + int i; + for(i = 0; i < fstart; i++) + fm2playUpdate(); + + return true; +} + +//from input.c +extern uint8_t inValReads[8]; + +void fm2playUpdate() +{ + if(!fm2playIn) return; + if(fm2playCur == '\0') + { + free(fm2playIn); + fm2playIn = NULL; + return; + } + fm2playCur = strstr(fm2playCur,"|0|"); + if(!fm2playCur) + { + free(fm2playIn); + fm2playIn = NULL; + return; + } + fm2playCur+=3; + //printf("%.8s\n",fm2playCur); + int i; + for(i = 0; i < 8; i++) + { + char cChar = *fm2playCur; + fm2playCur++; + if(cChar != 0x20 && cChar != 0x2E) + inValReads[7-i] = 1; + else + inValReads[7-i] = 0; + } +} + +bool fm2playRunning() +{ + return (fm2playIn != NULL); +} + +bool fm2playWaitDMAcycles() +{ + return waitDMAcycles; +} + diff --git a/fm2play.h b/fm2play.h new file mode 100644 index 0000000..dc7d68f --- /dev/null +++ b/fm2play.h @@ -0,0 +1,16 @@ +/* + * 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 _fm2play_h_ +#define _fm2play_h_ + +bool fm2playInit(char *fName, int fstart, bool wait); +void fm2playUpdate(); +bool fm2playRunning(); +bool fm2playWaitDMAcycles(); + +#endif diff --git a/input.c b/input.c new file mode 100644 index 0000000..c38a978 --- /dev/null +++ b/input.c @@ -0,0 +1,58 @@ +/* + * 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 +#include +#include +#include "input.h" + +//used externally by main.c +uint8_t inValReads[8]; +uint8_t inPollMode = 0; +uint8_t inPos = 0; + +#define DEBUG_INPUT 0 + +void inputInit() +{ + memset(inValReads, 0, 8); +} + +void inputSet(uint8_t in) +{ + inPollMode = in; + #if DEBUG_INPUT + printf("Set %02x\n",in); + #endif + if(!(in&1)) + inPos = 0; +} + +uint8_t inputGet() +{ + uint8_t ret = 1; + if(inPollMode&1) + ret = inValReads[BUTTON_A]; + else if(inPos < 8) + { + if((inPos == BUTTON_DOWN && inValReads[BUTTON_UP]) || (inPos == BUTTON_RIGHT && inValReads[BUTTON_LEFT])) + ret = 0; //dont allow up+down/left+right + else + ret = inValReads[inPos]; + #if DEBUG_INPUT + printf("%d:%d\n",ret,inPos); + #endif + inPos++; + } + else + { + #if DEBUG_INPUT + printf("keeps reading\n"); + #endif + } + return (ret&1); +} diff --git a/input.h b/input.h new file mode 100644 index 0000000..3a10ce9 --- /dev/null +++ b/input.h @@ -0,0 +1,24 @@ +/* + * 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 inputGet(); +void inputSet(uint8_t in); + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..7d31368 --- /dev/null +++ b/main.c @@ -0,0 +1,617 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include "mapper.h" +#include "cpu.h" +#include "ppu.h" +#include "mem.h" +#include "input.h" +#include "fm2play.h" +#include "apu.h" +#include "audio.h" + +#define DEBUG_HZ 0 +#define DEBUG_KEY 0 +#define DEBUG_LOAD_INFO 1 + +static const char *VERSION_STRING = "fixNES Alpha v0.1"; + +static void nesEmuDisplayFrame(void); +static void nesEmuMainLoop(void); +static void nesEmuDeinit(void); + +static void nesEmuHandleKeyDown(unsigned char key, int x, int y); +static void nesEmuHandleKeyUp(unsigned char key, int x, int y); +static void nesEmuHandleSpecialDown(int key, int x, int y); +static void nesEmuHandleSpecialUp(int key, int x, int y); + +static uint8_t *emuNesROM = 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; + +static bool inPause = false; +static bool inOverscanToggle = false; +static bool inResize = false; + +#if WINDOWS_BUILD +#include +typedef bool (APIENTRY *PFNWGLSWAPINTERVALEXTPROC) (int interval); +PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL; +static DWORD emuFrameStart = 0; +static DWORD emuTimesCalled = 0; +static DWORD emuTotalElapsed = 0; +#endif + +#define DOTS 341 +#define LINES 262 + +#define VISIBLE_DOTS 256 +#define VISIBLE_LINES 240 + +static const int visibleImg = VISIBLE_DOTS*VISIBLE_LINES*4; +static int scaleFactor = 2; +static bool emuSaveEnabled = false; + +//from input.c +extern uint8_t inValReads[8]; + +int main(int argc, char** argv) +{ + puts(VERSION_STRING); + if(argc >= 2 && (strstr(argv[1],".nes") != NULL || strstr(argv[1],".NES") != NULL)) + { + FILE *nesF = fopen(argv[1],"rb"); + if(!nesF) return EXIT_SUCCESS; + fseek(nesF,0,SEEK_END); + size_t fsize = ftell(nesF); + rewind(nesF); + emuNesROM = malloc(fsize); + fread(emuNesROM,1,fsize,nesF); + fclose(nesF); + uint8_t mapper = ((emuNesROM[6] & 0xF0) >> 4) | ((emuNesROM[7] & 0xF0)); + emuSaveEnabled = (emuNesROM[6] & (1<<1)) != 0; + bool trainer = (emuNesROM[6] & (1<<2)) != 0; + if(emuNesROM[6] & 4) + ppuScreenMode = PPU_MODE_FOURSCREEN; + if(emuNesROM[6] & 1) + ppuScreenMode = PPU_MODE_VERTICAL; + else + ppuScreenMode = PPU_MODE_HORIZONTAL; + uint32_t prgROMsize = emuNesROM[4] * 0x4000; + uint32_t chrROMsize = emuNesROM[5] * 0x2000; + emuPrgRAMsize = emuNesROM[8] * 0x2000; + if(emuPrgRAMsize == 0) emuPrgRAMsize = 0x2000; + emuPrgRAM = malloc(emuPrgRAMsize); + #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); + printf("Trainer: %i Saving: %i VRAM Mode: %s\n", trainer, emuSaveEnabled, (ppuScreenMode == PPU_MODE_VERTICAL) ? "Vertical" : + ((ppuScreenMode == PPU_MODE_HORIZONTAL) ? "Horizontal" : "4-Screen")); + #endif + uint8_t *prgROM = emuNesROM+16; + if(trainer) prgROM += 512; + uint8_t *chrROM = NULL; + if(chrROMsize) + { + chrROM = emuNesROM+16+prgROMsize; + if(trainer) chrROM += 512; + } + cpuInit(); + ppuInit(); + memInit(); + apuInit(); + inputInit(); + if(!mapperInit(mapper, prgROM, prgROMsize, emuPrgRAM, emuPrgRAMsize, chrROM, chrROMsize)) + { + printf("Mapper init failed!\n"); + free(emuNesROM); + return EXIT_SUCCESS; + } + 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); + } + } + if(argc == 5 && (strstr(argv[2],".fm2") != NULL || strstr(argv[2],".FM2") != NULL)) + fm2playInit(argv[2], atoi(argv[3]), !!atoi(argv[4])); + #if WINDOWS_BUILD + emuFrameStart = GetTickCount(); + #endif + textureImage = malloc(visibleImg); + memset(textureImage,0,visibleImg); + //make sure image is visible + int i; + for(i = 0; i < visibleImg; i+=4) + textureImage[i+3] = 0xFF; + } + if(emuNesROM == NULL) + return EXIT_SUCCESS; + glutInit(&argc, argv); + glutInitWindowSize(VISIBLE_DOTS*scaleFactor, VISIBLE_LINES*scaleFactor); + glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA); + glutCreateWindow(VERSION_STRING); + audioInit(); + atexit(&nesEmuDeinit); + glutKeyboardFunc(&nesEmuHandleKeyDown); + glutKeyboardUpFunc(&nesEmuHandleKeyUp); + glutSpecialFunc(&nesEmuHandleSpecialDown); + glutSpecialUpFunc(&nesEmuHandleSpecialUp); + glutDisplayFunc(&nesEmuDisplayFrame); + glutIdleFunc(&nesEmuMainLoop); + #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 nesEmuDeinit(void) +{ + //printf("\n"); + emuRenderFrame = false; + audioDeinit(); + apuDeinit(); + if(emuNesROM != NULL) + free(emuNesROM); + emuNesROM = 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; + +//static int mCycles = 0; +static bool emuApuDoCycle = false; +static int emuApuClock = 0; +//do one scanline per idle loop +#define MAIN_LOOP_RUNS 341 +static int mainClock = 0, mainLoopPos = MAIN_LOOP_RUNS; + +static void nesEmuMainLoop(void) +{ + do + { + if((!emuSkipVsync && emuRenderFrame) || nesPause) + return; + if(mainClock == 2) + { + //runs every second cpu clock + if(emuApuDoCycle && !apuCycle()) + return; + emuApuDoCycle ^= true; + //runs every cpu cycle + apuClockTimers(); + //main CPU clock + if(!cpuCycle()) + exit(EXIT_SUCCESS); + //mapper related irqs + if(mapperCycle != NULL) + mapperCycle(); + //mCycles++; + //channel timer updates + if(emuApuClock == 7457) + { + apuLenCycle(); + emuApuClock = 0; + } + else + emuApuClock++; + mainClock = 0; + } + else + mainClock++; + //3 PPU dots per CPU cycle + if(!ppuCycle()) + exit(EXIT_SUCCESS); + if(ppuDrawDone()) + { + //printf("%i\n",mCycles); + //mCycles = 0; + emuRenderFrame = true; + if(fm2playRunning()) + fm2playUpdate(); + #if WINDOWS_BUILD + emuTimesCalled++; + DWORD end = GetTickCount(); + emuTotalElapsed += end - emuFrameStart; + if(emuTotalElapsed >= 1000) + { + #if DEBUG_HZ + printf("\r%iHz ", emuTimesCalled); + #endif + emuTimesCalled = 0; + emuTotalElapsed = 0; + } + emuFrameStart = end; + #endif + glutPostRedisplay(); + if(ppuDebugPauseFrame) + nesPause = true; + } + } + while(mainLoopPos--); + mainLoopPos = MAIN_LOOP_RUNS; +} + +void nesEmuResetApuClock(void) +{ + emuApuClock = 0; +} + +static void nesEmuHandleKeyDown(unsigned char key, int x, int y) +{ + (void)x; + (void)y; + switch (key) + { + case 'y': + case 'z': + case 'Y': + case 'Z': + if(fm2playRunning()) + break; + #if DEBUG_KEY + if(inValReads[BUTTON_A]==0) + printf("a\n"); + #endif + inValReads[BUTTON_A]=1; + break; + case 'x': + case 'X': + if(fm2playRunning()) + break; + #if DEBUG_KEY + if(inValReads[BUTTON_B]==0) + printf("b\n"); + #endif + inValReads[BUTTON_B]=1; + break; + case 's': + case 'S': + if(fm2playRunning()) + break; + #if DEBUG_KEY + if(inValReads[BUTTON_SELECT]==0) + printf("sel\n"); + #endif + inValReads[BUTTON_SELECT]=1; + break; + case 'a': + case 'A': + if(fm2playRunning()) + break; + #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; + } + default: + break; + } +} + +static void nesEmuHandleKeyUp(unsigned char key, int x, int y) +{ + (void)x; + (void)y; + switch (key) + { + case 'y': + case 'z': + case 'Y': + case 'Z': + if(fm2playRunning()) + break; + #if DEBUG_KEY + printf("a up\n"); + #endif + inValReads[BUTTON_A]=0; + break; + case 'x': + case 'X': + if(fm2playRunning()) + break; + #if DEBUG_KEY + printf("b up\n"); + #endif + inValReads[BUTTON_B]=0; + break; + case 's': + case 'S': + if(fm2playRunning()) + break; + #if DEBUG_KEY + printf("sel up\n"); + #endif + inValReads[BUTTON_SELECT]=0; + break; + case 'a': + case 'A': + if(fm2playRunning()) + break; + #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; + default: + break; + } +} + +static void nesEmuHandleSpecialDown(int key, int x, int y) +{ + (void)x; + (void)y; + switch(key) + { + case GLUT_KEY_UP: + if(fm2playRunning()) + break; + #if DEBUG_KEY + if(inValReads[BUTTON_UP]==0) + printf("up\n"); + #endif + inValReads[BUTTON_UP]=1; + break; + case GLUT_KEY_DOWN: + if(fm2playRunning()) + break; + #if DEBUG_KEY + if(inValReads[BUTTON_DOWN]==0) + printf("down\n"); + #endif + inValReads[BUTTON_DOWN]=1; + break; + case GLUT_KEY_LEFT: + if(fm2playRunning()) + break; + #if DEBUG_KEY + if(inValReads[BUTTON_LEFT]==0) + printf("left\n"); + #endif + inValReads[BUTTON_LEFT]=1; + break; + case GLUT_KEY_RIGHT: + if(fm2playRunning()) + break; + #if DEBUG_KEY + if(inValReads[BUTTON_RIGHT]==0) + printf("right\n"); + #endif + inValReads[BUTTON_RIGHT]=1; + break; + default: + break; + } +} + +static void nesEmuHandleSpecialUp(int key, int x, int y) +{ + (void)x; + (void)y; + switch(key) + { + case GLUT_KEY_UP: + if(fm2playRunning()) + break; + #if DEBUG_KEY + printf("up up\n"); + #endif + inValReads[BUTTON_UP]=0; + break; + case GLUT_KEY_DOWN: + if(fm2playRunning()) + break; + #if DEBUG_KEY + printf("down up\n"); + #endif + inValReads[BUTTON_DOWN]=0; + break; + case GLUT_KEY_LEFT: + if(fm2playRunning()) + break; + #if DEBUG_KEY + printf("left up\n"); + #endif + inValReads[BUTTON_LEFT]=0; + break; + case GLUT_KEY_RIGHT: + if(fm2playRunning()) + break; + #if DEBUG_KEY + printf("right up\n"); + #endif + inValReads[BUTTON_RIGHT]=0; + break; + default: + break; + } +} + +static void nesEmuDisplayFrame() +{ + if(emuRenderFrame) + { + 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(); +} diff --git a/mapper.c b/mapper.c new file mode 100644 index 0000000..a03f1c3 --- /dev/null +++ b/mapper.c @@ -0,0 +1,51 @@ +/* + * 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 +#include +#include +#include "mapper/m0.h" +#include "mapper/m1.h" +#include "mapper/m2.h" +#include "mapper/m3.h" +#include "mapper/m4.h" +#include "mapper/m7.h" +#include "mapper.h" + +get8FuncT mapperGet8; +set8FuncT mapperSet8; +getChrFuncT mapperGetChr; +chrGet8FuncT mapperChrGet8; +chrSet8FuncT mapperChrSet8; +cycleFuncT mapperCycle; + +mapperList_t mapperList[8] = { + { m0init, m0get8, m0set8, m0chrGet8, m0chrSet8, NULL }, + { m1init, m1get8, m1set8, m1chrGet8, m1chrSet8, NULL }, + { m2init, m2get8, m2set8, m2chrGet8, m2chrSet8, NULL }, + { m3init, m3get8, m3set8, m3chrGet8, m3chrSet8, NULL }, + { m4init, m4get8, m4set8, m4chrGet8, m4chrSet8, m4cycle }, + { NULL, NULL, NULL, NULL, NULL, NULL }, + { NULL, NULL, NULL, NULL, NULL, NULL }, + { m7init, m7get8, m7set8, m7chrGet8, m7chrSet8, NULL }, +}; + +bool mapperInit(uint8_t mapper, uint8_t *prgROM, uint32_t prgROMsize, uint8_t *prgRAM, uint32_t prgRAMsize, uint8_t *chrROM, uint32_t chrROMsize) +{ + if(mapper > 4 && mapper != 7) + { + printf("Unsupported Mapper!\n"); + return false; + } + mapperList[mapper].initF(prgROM, prgROMsize, prgRAM, prgRAMsize, chrROM, chrROMsize); + mapperGet8 = mapperList[mapper].get8F; + mapperSet8 = mapperList[mapper].set8F; + mapperChrGet8 = mapperList[mapper].chrGet8F; + mapperChrSet8 = mapperList[mapper].chrSet8F; + mapperCycle = mapperList[mapper].cycleFuncF; + return true; +} diff --git a/mapper.h b/mapper.h new file mode 100644 index 0000000..748c08d --- /dev/null +++ b/mapper.h @@ -0,0 +1,36 @@ +/* + * 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 _mapper_h_ +#define _mapper_h_ + +typedef void (*initFuncT)(uint8_t*, uint32_t, uint8_t*, uint32_t, uint8_t*, uint32_t); +typedef uint8_t (*get8FuncT)(uint16_t); +typedef void (*set8FuncT)(uint16_t, uint8_t); +typedef uint8_t (*chrGet8FuncT)(uint16_t); +typedef void (*chrSet8FuncT)(uint16_t, uint8_t); +typedef uint8_t* (*getChrFuncT)(); +typedef void (*cycleFuncT)(); + +typedef struct _mapperList_t { + initFuncT initF; + get8FuncT get8F; + set8FuncT set8F; + chrGet8FuncT chrGet8F; + chrSet8FuncT chrSet8F; + cycleFuncT cycleFuncF; +} mapperList_t; + +bool mapperInit(uint8_t mapper, uint8_t *prgROM, uint32_t prgROMsize, uint8_t *prgRAM, uint32_t prgRAMsize, uint8_t *chrROM, uint32_t chrROMsize); + +extern get8FuncT mapperGet8; +extern set8FuncT mapperSet8; +extern chrGet8FuncT mapperChrGet8; +extern chrSet8FuncT mapperChrSet8; +extern cycleFuncT mapperCycle; + +#endif diff --git a/mapper/m0.c b/mapper/m0.c new file mode 100644 index 0000000..c995198 --- /dev/null +++ b/mapper/m0.c @@ -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 +#include + +static uint8_t *m0_prgROM; +static uint8_t *m0_chrROM; +static uint32_t m0_prgROMsize; +static uint32_t m0_chrROMsize; +static uint8_t m0_chrRAM[0x2000]; + +void m0init(uint8_t *prgROMin, uint32_t prgROMsizeIn, + uint8_t *prgRAMin, uint32_t prgRAMsizeIn, + uint8_t *chrROMin, uint32_t chrROMsizeIn) +{ + m0_prgROM = prgROMin; + m0_prgROMsize = prgROMsizeIn; + (void)prgRAMin; + (void)prgRAMsizeIn; + if(chrROMsizeIn > 0) + { + m0_chrROM = chrROMin; + m0_chrROMsize = chrROMsizeIn; + } + else + { + m0_chrROM = m0_chrRAM; + m0_chrROMsize = 0x2000; + } + memset(m0_chrRAM,0,0x2000); + printf("Mapper 0 inited\n"); +} + +uint8_t m0get8(uint16_t addr) +{ + if(addr < 0x8000) + return m0_chrRAM[addr&0x1FFF]; + if(m0_prgROMsize == 0x8000) + return m0_prgROM[addr&0x7FFF]; + return m0_prgROM[addr&0x3FFF]; +} + + +void m0set8(uint16_t addr, uint8_t val) +{ + (void)addr; + (void)val; +} + +uint8_t m0chrGet8(uint16_t addr) +{ + return m0_chrROM[addr&0x1FFF]; +} + +void m0chrSet8(uint16_t addr, uint8_t val) +{ + if(m0_chrROM == m0_chrRAM) + m0_chrROM[addr&0x1FFF] = val; +} diff --git a/mapper/m0.h b/mapper/m0.h new file mode 100644 index 0000000..6a0a34a --- /dev/null +++ b/mapper/m0.h @@ -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 m0_h_ +#define m0_h_ + +void m0init(uint8_t *prgROM, uint32_t prgROMsize, + uint8_t *prgRAM, uint32_t prgRAMsize, + uint8_t *chrROM, uint32_t chrROMsize); +uint8_t m0get8(uint16_t addr); +void m0set8(uint16_t addr, uint8_t val); +uint8_t m0chrGet8(uint16_t addr); +void m0chrSet8(uint16_t addr, uint8_t val); + +#endif diff --git a/mapper/m1.c b/mapper/m1.c new file mode 100644 index 0000000..2fbfc75 --- /dev/null +++ b/mapper/m1.c @@ -0,0 +1,230 @@ +/* + * 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 +#include +#include +#include "../ppu.h" + +static uint8_t *m1_prgROM; +static uint8_t *m1_prgRAM; +static uint8_t *m1_chrROM; +static uint32_t m1_prgROMsize; +static uint32_t m1_prgRAMsize; +static uint32_t m1_chrROMsize; +static uint8_t m1_chrRAM[0x2000]; +static uint32_t m1_256KPRGBank; +static uint32_t m1_curPRGBank; +static uint32_t m1_firstPRGBank; +static uint32_t m1_lastPRGBank; +static uint32_t m1_curCHRBank0; +static uint32_t m1_curCHRBank1; +static uint8_t m1_sr; +static bool m1_single_prg_bank; +static bool m1_last_bank_fixed; +static bool m1_single_chr_bank; + +extern bool ppuForceTable; +extern uint16_t ppuForceTableAddr; + +void m1init(uint8_t *prgROMin, uint32_t prgROMsizeIn, + uint8_t *prgRAMin, uint32_t prgRAMsizeIn, + uint8_t *chrROMin, uint32_t chrROMsizeIn) +{ + m1_prgROM = prgROMin; + m1_prgROMsize = prgROMsizeIn; + m1_prgRAM = prgRAMin; + m1_prgRAMsize = prgRAMsizeIn; + m1_firstPRGBank = 0; + m1_256KPRGBank = 0; + m1_curPRGBank = m1_firstPRGBank; + m1_lastPRGBank = (prgROMsizeIn - 0x4000)&0x3FFFF; + if(chrROMsizeIn > 0) + { + m1_chrROM = chrROMin; + m1_chrROMsize = chrROMsizeIn; + } + else + { + m1_chrROM = m1_chrRAM; + m1_chrROMsize = 0x2000; + } + m1_curCHRBank0 = 0; + m1_curCHRBank1 = 0; + memset(m1_chrRAM,0,0x2000); + m1_sr = (1<<4);//0; + m1_single_prg_bank = false; + m1_last_bank_fixed = true; + m1_single_chr_bank = false; + printf("Mapper 1 inited, last bank=%04x sr=%02x\n", m1_lastPRGBank, m1_sr); +} + +uint8_t m1get8(uint16_t addr) +{ + if(addr < 0x8000) + return m1_prgRAM[addr&0x1FFF]; + else + { + if(m1_single_prg_bank) + { + return m1_prgROM[(m1_curPRGBank&~0x7FFF)+(addr&0x7FFF)+m1_256KPRGBank]; + } + else if(m1_last_bank_fixed) + { + if(addr < 0xC000) + return m1_prgROM[(m1_curPRGBank&~0x3FFF)+(addr&0x3FFF)+m1_256KPRGBank]; + return m1_prgROM[m1_lastPRGBank+(addr&0x3FFF)+m1_256KPRGBank]; + } + else //first bank fixed + { + if(addr < 0xC000) + return m1_prgROM[m1_firstPRGBank+(addr&0x3FFF)+m1_256KPRGBank]; + return m1_prgROM[(m1_curPRGBank&~0x3FFF)+(addr&0x3FFF)+m1_256KPRGBank]; + } + } +} + +extern bool cpuWriteTMP; +void m1set8(uint16_t addr, uint8_t val) +{ + if(addr < 0x8000) + { + //printf("m1set8 %04x %02x\n", addr, val); + m1_prgRAM[addr&0x1FFF] = val; + } + else + { + // mmc1 regs cant be written to + // with just 1 cpu cycle delay + if(cpuWriteTMP) + return; + //printf("m1set8 %04x %02x\n", addr, val); + if(val&(1<<7)) + { + //printf("Reset (???)\n"); + m1_sr = (1<<4);//0; + m1_last_bank_fixed = true; + m1_single_prg_bank = false; + } + else if(m1_sr & 1) + { + m1_sr >>= 1; + m1_sr |= ((val&1)<<4); + //printf("m1 sr full, addr %04x sr %02x\n", addr, m1_sr); + if(addr < 0xA000) + { + if((m1_sr & 3) == 2) + { + //printf("Vertical mode\n"); + ppuScreenMode = PPU_MODE_VERTICAL; + ppuForceTable = false; + } + else if((m1_sr & 3) == 3) + { + //printf("Horizontal mode\n"); + ppuScreenMode = PPU_MODE_HORIZONTAL; + ppuForceTable = false; + } + else + { + ppuScreenMode = PPU_MODE_SINGLE; + ppuForceTable = true; + if((m1_sr & 3) == 1) + { + //printf("Upper CHR Mode\n"); + //m1_small_upper_chr = true; + ppuForceTableAddr = 0x400; + } + else + { + + //printf("Lower CHR Mode\n"); + //m1_small_upper_chr = false; + ppuForceTableAddr = 0; + } + } + if(((m1_sr>>2) & 3) == 2) + { + //printf("First bank fixed\n"); + m1_last_bank_fixed = false; + m1_single_prg_bank = false; + } + else if(((m1_sr>>2) & 3) == 3) + { + //printf("Last bank fixed\n"); + m1_last_bank_fixed = true; + m1_single_prg_bank = false; + } + else + { + //printf("Some other PRG Bank mode\n"); + m1_single_prg_bank = true; + } + + if(m1_sr & (1<<4)) + { + //printf("2 CHR Banks\n"); + m1_single_chr_bank = false; + } + else + { + //printf("Single CHR Bank\n"); + m1_single_chr_bank = true; + } + } + else if(addr < 0xC000) + { + if(m1_single_chr_bank) m1_sr &= ~1; + m1_curCHRBank0 = (m1_sr*0x1000)&(m1_chrROMsize-1); + //printf("chr bank 0 now %04x\n", m1_curCHRBank0); + if(m1_prgROMsize > 0x40000) + m1_256KPRGBank = (m1_sr&0x10)<<14; + } + else if(addr < 0xE000) + { + if(!m1_single_chr_bank) + { + m1_curCHRBank1 = (m1_sr*0x1000)&(m1_chrROMsize-1); + //printf("chr bank 1 now %04x\n", m1_curCHRBank1); + if(m1_prgROMsize > 0x40000) + m1_256KPRGBank = (m1_sr&0x10)<<14; + } + } + else + { + if(m1_single_prg_bank) m1_sr &= ~1; + m1_curPRGBank = ((m1_sr&15)*0x4000)&(m1_prgROMsize-1); + //printf("switchable bank now %04x\n", m1_curPRGBank); + } + //m1_sr = 0;//(m1_sr<<4); + m1_sr = (1<<4); + } + else + { + //printf("%02x\n", m1_sr); + m1_sr >>= 1; + m1_sr |= ((val&1)<<4); + //printf("%02x\n", m1_sr); + } + } +} + +uint8_t m1chrGet8(uint16_t addr) +{ + if(m1_single_chr_bank) + return m1_chrROM[(m1_curCHRBank0&~0x1FFF)+(addr&0x1FFF)]; + else if(addr < 0x1000) + return m1_chrROM[(m1_curCHRBank0&~0xFFF)+(addr&0xFFF)]; + return m1_chrROM[(m1_curCHRBank1&~0xFFF)+(addr&0xFFF)]; +} + +void m1chrSet8(uint16_t addr, uint8_t val) +{ + //printf("m1chrSet8 %04x %02x\n", addr, val); + if(m1_chrROM == m1_chrRAM) //Writable + m1_chrROM[addr&0x1FFF] = val; +} diff --git a/mapper/m1.h b/mapper/m1.h new file mode 100644 index 0000000..1b345b9 --- /dev/null +++ b/mapper/m1.h @@ -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 m1_h_ +#define m1_h_ + +void m1init(uint8_t *prgROM, uint32_t prgROMsize, + uint8_t *prgRAM, uint32_t prgRAMsize, + uint8_t *chrROM, uint32_t chrROMsize); +uint8_t m1get8(uint16_t addr); +void m1set8(uint16_t addr, uint8_t val); +uint8_t m1chrGet8(uint16_t addr); +void m1chrSet8(uint16_t addr, uint8_t val); + +#endif diff --git a/mapper/m2.c b/mapper/m2.c new file mode 100644 index 0000000..dd8a343 --- /dev/null +++ b/mapper/m2.c @@ -0,0 +1,68 @@ +/* + * 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 +#include +#include +#include "../ppu.h" + +static uint8_t *m2_prgROM; +static uint8_t *m2_chrROM; +static uint32_t m2_prgROMsize; +static uint32_t m2_chrROMsize; +static uint32_t m2_curPRGBank; +static uint32_t m2_lastPRGBank; + +static uint8_t m2_chrRAM[0x2000]; +extern bool ppuForceTable; +extern uint16_t ppuForceTableAddr; + +void m2init(uint8_t *prgROMin, uint32_t prgROMsizeIn, + uint8_t *prgRAMin, uint32_t prgRAMsizeIn, + uint8_t *chrROMin, uint32_t chrROMsizeIn) +{ + m2_prgROM = prgROMin; + m2_prgROMsize = prgROMsizeIn; + (void)prgRAMin; + (void)prgRAMsizeIn; + m2_curPRGBank = 0; + m2_lastPRGBank = prgROMsizeIn - 0x4000; + if(chrROMsizeIn > 0) + { + m2_chrROM = chrROMin; + m2_chrROMsize = chrROMsizeIn; + printf("m2 ???\n"); + } + memset(m2_chrRAM,0,0x2000); + printf("Mapper 2 inited\n"); +} + +uint8_t m2get8(uint16_t addr) +{ + if(addr < 0x8000) + return 0; + if(addr < 0xC000) + return m2_prgROM[(m2_curPRGBank&~0x3FFF)+(addr&0x3FFF)]; + return m2_prgROM[(m2_lastPRGBank&~0x3FFF)+(addr&0x3FFF)]; +} + +void m2set8(uint16_t addr, uint8_t val) +{ + //printf("m1set8 %04x %02x\n", addr, val); + (void)addr; + m2_curPRGBank = ((val & 15) * 0x4000)&(m2_prgROMsize-1); +} + +uint8_t m2chrGet8(uint16_t addr) +{ + return m2_chrRAM[addr&0x1FFF]; +} + +void m2chrSet8(uint16_t addr, uint8_t val) +{ + m2_chrRAM[addr&0x1FFF] = val; +} diff --git a/mapper/m2.h b/mapper/m2.h new file mode 100644 index 0000000..9b3fd3d --- /dev/null +++ b/mapper/m2.h @@ -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 m2_h_ +#define m2_h_ + +void m2init(uint8_t *prgROM, uint32_t prgROMsize, + uint8_t *prgRAM, uint32_t prgRAMsize, + uint8_t *chrROM, uint32_t chrROMsize); +uint8_t m2get8(uint16_t addr); +void m2set8(uint16_t addr, uint8_t val); +uint8_t m2chrGet8(uint16_t addr); +void m2chrSet8(uint16_t addr, uint8_t val); + +#endif diff --git a/mapper/m3.c b/mapper/m3.c new file mode 100644 index 0000000..a1aad34 --- /dev/null +++ b/mapper/m3.c @@ -0,0 +1,59 @@ +/* + * 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 +#include +#include +#include "../ppu.h" + +static uint8_t *m3_prgROM; +static uint8_t *m3_chrROM; +static uint32_t m3_prgROMsize; +static uint32_t m3_chrROMsize; +static uint32_t m3_curCHRBank; + +void m3init(uint8_t *prgROMin, uint32_t prgROMsizeIn, + uint8_t *prgRAMin, uint32_t prgRAMsizeIn, + uint8_t *chrROMin, uint32_t chrROMsizeIn) +{ + m3_prgROM = prgROMin; + m3_prgROMsize = prgROMsizeIn; + (void)prgRAMin; + (void)prgRAMsizeIn; + m3_chrROM = chrROMin; + m3_chrROMsize = chrROMsizeIn; + m3_curCHRBank = 0; + printf("Mapper 3 inited\n"); +} + +uint8_t m3get8(uint16_t addr) +{ + if(addr < 0x8000) + return 0; + if(m3_prgROMsize == 0x8000) + return m3_prgROM[addr&0x7FFF]; + return m3_prgROM[addr&0x3FFF]; +} + +void m3set8(uint16_t addr, uint8_t val) +{ + //printf("%04x %02x\n", addr, val); + if(addr < 0x8000) + return; + m3_curCHRBank = ((val * 0x2000)&(m3_chrROMsize-1)); +} + +uint8_t m3chrGet8(uint16_t addr) +{ + return m3_chrROM[m3_curCHRBank+(addr&0x1FFF)]; +} + +void m3chrSet8(uint16_t addr, uint8_t val) +{ + (void)addr; + (void)val; +} diff --git a/mapper/m3.h b/mapper/m3.h new file mode 100644 index 0000000..48b8cf3 --- /dev/null +++ b/mapper/m3.h @@ -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 m3_h_ +#define m3_h_ + +void m3init(uint8_t *prgROM, uint32_t prgROMsize, + uint8_t *prgRAM, uint32_t prgRAMsize, + uint8_t *chrROM, uint32_t chrROMsize); +uint8_t m3get8(uint16_t addr); +void m3set8(uint16_t addr, uint8_t val); +uint8_t m3chrGet8(uint16_t addr); +void m3chrSet8(uint16_t addr, uint8_t val); + +#endif diff --git a/mapper/m4.c b/mapper/m4.c new file mode 100644 index 0000000..a35fb75 --- /dev/null +++ b/mapper/m4.c @@ -0,0 +1,281 @@ +/* + * 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 +#include +#include +#include "../ppu.h" + +static uint8_t *m4_prgROM; +static uint8_t *m4_prgRAM; +static uint8_t *m4_chrROM; +static uint32_t m4_prgROMsize; +static uint32_t m4_prgRAMsize; +static uint32_t m4_chrROMsize; +static uint32_t m4_prgROMand; +static uint32_t m4_chrROMand; +static uint8_t m4_chrRAM[0x2000]; +static uint32_t m4_curPRGBank0; +static uint32_t m4_curPRGBank1; +static uint32_t m4_lastM1PRGBank; +static uint32_t m4_lastPRGBank; +static uint32_t m4_CHRBank[6]; +static uint8_t m4_writeAddr; +static uint8_t m4_tmpAddr; +static bool m4_chr_bank_flip; +static bool m4_prg_bank_flip; +static uint8_t m4_irqCtr; +static bool m4_irqEnable; +static uint8_t m4_irqReloadVal; +static uint8_t m4_irqCooldown; +static uint8_t m4_irqStart; +extern bool mapper_interrupt; +extern bool ppuForceTable; +extern uint16_t ppuForceTableAddr; +static uint16_t m4_prevAddr; + +void m4init(uint8_t *prgROMin, uint32_t prgROMsizeIn, + uint8_t *prgRAMin, uint32_t prgRAMsizeIn, + uint8_t *chrROMin, uint32_t chrROMsizeIn) +{ + m4_prgROM = prgROMin; + m4_prgROMsize = prgROMsizeIn; + m4_prgRAM = prgRAMin; + m4_prgRAMsize = prgRAMsizeIn; + m4_prgROMand = prgROMsizeIn-1; + m4_curPRGBank0 = 0; + m4_curPRGBank1 = 0x2000; + m4_lastPRGBank = (prgROMsizeIn - 0x2000); + m4_lastM1PRGBank = m4_lastPRGBank - 0x2000; + if(chrROMsizeIn > 0) + { + m4_chrROM = chrROMin; + m4_chrROMsize = chrROMsizeIn; + m4_chrROMand = chrROMsizeIn-1; + } + else + { + m4_chrROM = m4_chrRAM; + m4_chrROMsize = 0x2000; + m4_chrROMand = 0x1FFF; + } + memset(m4_chrRAM,0,0x2000); + memset(m4_CHRBank,0,6*sizeof(uint32_t)); + m4_tmpAddr = 0; + m4_irqCtr = 0; + m4_irqStart = 0; + m4_writeAddr = 0; + m4_irqEnable = false; + m4_irqReloadVal = 0xFF; + m4_irqCooldown = 0; + m4_chr_bank_flip = false; + m4_prg_bank_flip = false; + m4_prevAddr = 0; + printf("Mapper 4 inited\n"); +} + +uint8_t m4get8(uint16_t addr) +{ + if(addr < 0x8000) + return m4_prgRAM[addr&0x1FFF]; + else + { + if(addr < 0xA000) + { + if(m4_prg_bank_flip) + return m4_prgROM[(m4_lastM1PRGBank+(addr&0x1FFF))&m4_prgROMand]; + else + return m4_prgROM[((m4_curPRGBank0<<13)+(addr&0x1FFF))&m4_prgROMand]; + } + else if(addr < 0xC000) + return m4_prgROM[((m4_curPRGBank1<<13)+(addr&0x1FFF))&m4_prgROMand]; + else if(addr < 0xE000) + { + if(m4_prg_bank_flip) + return m4_prgROM[((m4_curPRGBank0<<13)+(addr&0x1FFF))&m4_prgROMand]; + else + return m4_prgROM[(m4_lastM1PRGBank+(addr&0x1FFF))&m4_prgROMand]; + } + return m4_prgROM[(m4_lastPRGBank+(addr&0x1FFF))&m4_prgROMand]; + } +} + +extern bool cpuWriteTMP; +void m4set8(uint16_t addr, uint8_t val) +{ + if(addr < 0x8000) + { + //printf("m4set8 %04x %02x\n", addr, val); + m4_prgRAM[addr&0x1FFF] = val; + } + else + { + // mmc1 regs cant be written to + // with just 1 cpu cycle delay + //if(cpuWriteTMP) + // return; + //printf("m4set8 %04x %02x\n", addr, val); + if(addr < 0xA000) + { + if((addr&1) == 0) + { + m4_chr_bank_flip = ((val&(1<<7)) != 0); + m4_prg_bank_flip = ((val&(1<<6)) != 0); + m4_writeAddr = (val&7); + } + else + { + switch(m4_writeAddr) + { + case 0: + m4_CHRBank[0] = val; + break; + case 1: + m4_CHRBank[1] = val; + break; + case 2: + m4_CHRBank[2] = val; + break; + case 3: + m4_CHRBank[3] = val; + break; + case 4: + m4_CHRBank[4] = val; + break; + case 5: + m4_CHRBank[5] = val; + break; + case 6: + m4_curPRGBank0 = val; + break; + case 7: + m4_curPRGBank1 = val; + break; + } + } + } + else if(addr < 0xC000) + { + if((addr&1) == 0) + { + if((val&1) == 0) + { + //printf("Vertical mode\n"); + ppuScreenMode = PPU_MODE_VERTICAL; + ppuForceTable = false; + } + else + { + //printf("Horizontal mode\n"); + ppuScreenMode = PPU_MODE_HORIZONTAL; + ppuForceTable = false; + } + } + } + else if(addr < 0xE000) + { + if((addr&1) == 0) + { + //printf("Reload value set to %i\n", val); + m4_irqReloadVal = val; + } + else + { + //printf("Reset counter\n"); + m4_irqCtr = 0; + } + } + else + { + if((addr&1) == 0) + { + m4_irqEnable = false; + mapper_interrupt = false; + m4_irqStart = 0; + //printf("Interrupt disabled\n"); + } + else + { + m4_irqStart = 0; + m4_irqEnable = true; + //printf("Interrupt enabled\n"); + } + } + } +} + +void m4clock(uint16_t addr) +{ + if(addr & 0x1000) + { + if(m4_irqCooldown == 0) + { + //printf("MMC3 Beep at %i %i\n", curLine, curDot); + if(m4_irqCtr == 0) + m4_irqCtr = m4_irqReloadVal; + else + m4_irqCtr--; + if(m4_irqCtr == 0) + { + if(m4_irqEnable) + { + //printf("MMC3 Tick at %i %i\n", curLine, curDot); + m4_irqStart = 5; //takes a bit before trigger + m4_irqEnable = false; + } + } + } + //make sure to pass all other sprites + //before counting up again + m4_irqCooldown = 20; + } +} + +uint8_t m4chrGet8(uint16_t addr) +{ + //printf("%04x\n",addr); + m4clock(addr); + addr &= 0x1FFF; + if(m4_chr_bank_flip) + addr ^= 0x1000; + if(addr < 0x800) + return m4_chrROM[((m4_CHRBank[0]<<10)+(addr&0x7FF))&m4_chrROMand]; + else if(addr < 0x1000) + return m4_chrROM[((m4_CHRBank[1]<<10)+(addr&0x7FF))&m4_chrROMand]; + else if(addr < 0x1400) + return m4_chrROM[((m4_CHRBank[2]<<10)+(addr&0x3FF))&m4_chrROMand]; + else if(addr < 0x1800) + return m4_chrROM[((m4_CHRBank[3]<<10)+(addr&0x3FF))&m4_chrROMand]; + else if(addr < 0x1C00) + return m4_chrROM[((m4_CHRBank[4]<<10)+(addr&0x3FF))&m4_chrROMand]; + return m4_chrROM[((m4_CHRBank[5]<<10)+(addr&0x3FF))&m4_chrROMand]; +} + +void m4chrSet8(uint16_t addr, uint8_t val) +{ + //printf("m4chrSet8 %04x %02x\n", addr, val); + if(m4_chrROM == m4_chrRAM) //Writable + m4_chrROM[addr&0x1FFF] = val; +} + +void m4cycle() +{ + uint16_t curAddr = ppuGetCurVramAddr(); + if((curAddr & 0x1000) && !(m4_prevAddr & 0x1000)) + m4clock(curAddr); + m4_prevAddr = curAddr; + if(m4_irqCooldown) + m4_irqCooldown--; + if(m4_irqStart == 1) + { + mapper_interrupt = true; + m4_irqStart = 0; + } + else if(m4_irqStart > 1) + m4_irqStart--; +} + diff --git a/mapper/m4.h b/mapper/m4.h new file mode 100644 index 0000000..412630a --- /dev/null +++ b/mapper/m4.h @@ -0,0 +1,20 @@ +/* + * 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 m4_h_ +#define m4_h_ + +void m4init(uint8_t *prgROM, uint32_t prgROMsize, + uint8_t *prgRAM, uint32_t prgRAMsize, + uint8_t *chrROM, uint32_t chrROMsize); +uint8_t m4get8(uint16_t addr); +void m4set8(uint16_t addr, uint8_t val); +uint8_t m4chrGet8(uint16_t addr); +void m4chrSet8(uint16_t addr, uint8_t val); +void m4cycle(); + +#endif diff --git a/mapper/m7.c b/mapper/m7.c new file mode 100644 index 0000000..1695b5b --- /dev/null +++ b/mapper/m7.c @@ -0,0 +1,77 @@ +/* + * 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 +#include +#include +#include "../ppu.h" + +static uint8_t *m7_prgROM; +static uint8_t *m7_chrROM; +static uint32_t m7_prgROMsize; +static uint32_t m7_chrROMsize; +static uint32_t m7_curPRGBank; + +static uint8_t m7_chrRAM[0x2000]; +extern bool ppuForceTable; +extern uint16_t ppuForceTableAddr; + +void m7init(uint8_t *prgROMin, uint32_t prgROMsizeIn, + uint8_t *prgRAMin, uint32_t prgRAMsizeIn, + uint8_t *chrROMin, uint32_t chrROMsizeIn) +{ + m7_prgROM = prgROMin; + m7_prgROMsize = prgROMsizeIn; + (void)prgRAMin; + (void)prgRAMsizeIn; + m7_curPRGBank = prgROMsizeIn - 0x8000; + if(chrROMsizeIn > 0) + { + m7_chrROM = chrROMin; + m7_chrROMsize = chrROMsizeIn; + printf("M7 ???\n"); + } + memset(m7_chrRAM,0,0x2000); + ppuScreenMode = PPU_MODE_SINGLE; + ppuForceTable= true; + ppuForceTableAddr = 0; + printf("Mapper 7 inited\n"); +} + +uint8_t m7get8(uint16_t addr) +{ + if(addr < 0x8000) + return 0; + return m7_prgROM[(m7_curPRGBank&~0x7FFF)+(addr&0x7FFF)]; +} + +void m7set8(uint16_t addr, uint8_t val) +{ + //printf("m1set8 %04x %02x\n", addr, val); + (void)addr; + if(val & (1<<4)) + { + //printf("400\n"); + ppuForceTableAddr = 0x400; + } + else + { + //printf("000\n"); + ppuForceTableAddr = 0; + } + m7_curPRGBank = (val & 7) * 0x8000; +} + +uint8_t m7chrGet8(uint16_t addr) +{ + return m7_chrRAM[addr&0x1FFF]; +} + +void m7chrSet8(uint16_t addr, uint8_t val) +{ + m7_chrRAM[addr&0x1FFF] = val; +} diff --git a/mapper/m7.h b/mapper/m7.h new file mode 100644 index 0000000..439f286 --- /dev/null +++ b/mapper/m7.h @@ -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 m7_h_ +#define m7_h_ + +void m7init(uint8_t *prgROM, uint32_t prgROMsize, + uint8_t *prgRAM, uint32_t prgRAMsize, + uint8_t *chrROM, uint32_t chrROMsize); +uint8_t m7get8(uint16_t addr); +void m7set8(uint16_t addr, uint8_t val); +uint8_t m7chrGet8(uint16_t addr); +void m7chrSet8(uint16_t addr, uint8_t val); + +#endif diff --git a/mem.c b/mem.c new file mode 100644 index 0000000..2392eb1 --- /dev/null +++ b/mem.c @@ -0,0 +1,107 @@ +/* + * 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 +#include +#include +#include "mapper.h" +#include "ppu.h" +#include "cpu.h" +#include "input.h" +#include "fm2play.h" +#include "apu.h" + +static uint8_t Main_Mem[0x800]; +static uint8_t memLastVal; + +void memInit() +{ + memset(Main_Mem,0,0x800); + memLastVal = 0; +} + +uint8_t memGet8(uint16_t addr) +{ + uint8_t val = memLastVal; + //printf("memGet8 %04x\n", addr); + if(addr >= 0x6000) + val = mapperGet8(addr); + else if(addr & 0x4000) + { + if(addr == 0x4015) + val = apuGet8(0x15); + else if(addr == 0x4016) + { + val &= ~0x1F; //player 1 + val |= inputGet(); + } + else if(addr == 0x4017) + val &= ~0x1F; //player 2 + } + else if(addr & 0x2000) + val = ppuGet8(addr&7); + else + val = Main_Mem[addr&0x7FF]; + + memLastVal = val; + return val; +} + +void memSet8(uint16_t addr, uint8_t val) +{ + //printf("memSet8 %04x %02x\n", addr, val); + if(addr >= 0x6000) + mapperSet8(addr, val); + else if(addr & 0x4000) + { + if(addr == 0x4014) + { + uint16_t dmaAddr = (val<<8); + //printf("ppuLoadOAM %04x\n", dmaAddr); + if(!fm2playRunning() || (fm2playRunning() && fm2playWaitDMAcycles())) + cpuIncWaitCycles(513); + int i; + if(dmaAddr < 0x2000) + { + for(i = 0; i < 0x100; i++) + ppuLoadOAM(Main_Mem[(dmaAddr+i)&0x7FF]); + } + else if(dmaAddr >= 0x6000) + { + for(i = 0; i < 0x100; i++) + ppuLoadOAM(mapperGet8(dmaAddr+i)); + } + else + printf("WARNING: Invalid ppuLoadOAM at %04x!\n", dmaAddr); + } + else if(addr == 0x4016) + inputSet(val); + else if(addr < 0x4018) + apuSet8(addr&0x1F, val); + } + else if(addr & 0x2000) + ppuSet8(addr&7, val); + else + Main_Mem[addr&0x7FF] = val; + memLastVal = val; +} + +#define DEBUG_MEM_DUMP 0 + +void memDumpMainMem() +{ + #if DEBUG_MEM_DUMP + FILE *f = fopen("MainMem.bin","wb"); + if(f) + { + fwrite(Main_Mem,1,0x800,f); + fclose(f); + } + ppuDumpMem(); + #endif +} + diff --git a/mem.h b/mem.h new file mode 100644 index 0000000..e2bbd8d --- /dev/null +++ b/mem.h @@ -0,0 +1,17 @@ +/* + * 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(); + +#endif \ No newline at end of file diff --git a/ppu.c b/ppu.c new file mode 100644 index 0000000..cb637f2 --- /dev/null +++ b/ppu.c @@ -0,0 +1,731 @@ +/* + * 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 +#include +#include +#include "mapper.h" +#include "ppu.h" + +//sprite byte 2 +#define PPU_SPRITE_PRIO (1<<5) +#define PPU_SPRITE_FLIP_X (1<<6) +#define PPU_SPRITE_FLIP_Y (1<<7) + +//2000 +#define PPU_INC_AMOUNT (1<<2) +#define PPU_SPRITE_ADDR (1<<3) +#define PPU_BACKGROUND_ADDR (1<<4) +#define PPU_SPRITE_8_16 (1<<5) +#define PPU_FLAG_NMI (1<<7) + +//2001 +#define PPU_BG_8PX (1<<1) +#define PPU_SPRITE_8PX (1<<2) +#define PPU_BG_ENABLE (1<<3) +#define PPU_SPRITE_ENABLE (1<<4) + +//2002 +#define PPU_FLAG_OVERFLOW (1<<5) +#define PPU_FLAG_SPRITEZERO (1<<6) +#define PPU_FLAG_VBLANK (1<<7) + +#define DOTS 341 +#define LINES 262 + +#define VISIBLE_DOTS 256 +#define VISIBLE_LINES 240 + +#define PPU_VRAM_HORIZONTAL_MASK 0x41F +#define PPU_VRAM_VERTICAL_MASK (~PPU_VRAM_HORIZONTAL_MASK) + +#define PPU_DEBUG_ULTRA 0 + +#define PPU_DEBUG_VSYNC 0 + +//set or used externally +bool ppuForceTable = false; +uint16_t ppuForceTableAddr = 0; +uint8_t ppuScreenMode = PPU_MODE_SINGLE; + +//from main.c +extern uint8_t *textureImage; +extern bool nesPause; +extern bool ppuDebugPauseFrame; +extern bool doOverscan; + +static uint8_t ppuDoBackground(uint8_t color, uint8_t dot); +static uint8_t ppuDoSprites(uint8_t color, uint8_t dot); + +// BMF Final 2 +static const uint8_t PPU_Pal[192] = +{ + 0x52, 0x52, 0x52, 0x00, 0x00, 0x80, 0x08, 0x00, 0x8A, 0x2C, 0x00, 0x7E, 0x4A, 0x00, 0x4E, 0x50, 0x00, 0x06, 0x44, 0x00, 0x00, 0x26, 0x08, 0x00, + 0x0A, 0x20, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x32, 0x00, 0x00, 0x26, 0x0A, 0x00, 0x1C, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xA4, 0xA4, 0xA4, 0x00, 0x38, 0xCE, 0x34, 0x16, 0xEC, 0x5E, 0x04, 0xDC, 0x8C, 0x00, 0xB0, 0x9A, 0x00, 0x4C, 0x90, 0x18, 0x00, 0x70, 0x36, 0x00, + 0x4C, 0x54, 0x00, 0x0E, 0x6C, 0x00, 0x00, 0x74, 0x00, 0x00, 0x6C, 0x2C, 0x00, 0x5E, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0x4C, 0x9C, 0xFF, 0x7C, 0x78, 0xFF, 0xA6, 0x64, 0xFF, 0xDA, 0x5A, 0xFF, 0xF0, 0x54, 0xC0, 0xF0, 0x6A, 0x56, 0xD6, 0x86, 0x10, + 0xBA, 0xA4, 0x00, 0x76, 0xC0, 0x00, 0x46, 0xCC, 0x1A, 0x2E, 0xC8, 0x66, 0x34, 0xC2, 0xBE, 0x3A, 0x3A, 0x3A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xB6, 0xDA, 0xFF, 0xC8, 0xCA, 0xFF, 0xDA, 0xC2, 0xFF, 0xF0, 0xBE, 0xFF, 0xFC, 0xBC, 0xEE, 0xFA, 0xC2, 0xC0, 0xF2, 0xCC, 0xA2, + 0xE6, 0xDA, 0x92, 0xCC, 0xE6, 0x8E, 0xB8, 0xEE, 0xA2, 0xAE, 0xEA, 0xBE, 0xAE, 0xE8, 0xE2, 0xB0, 0xB0, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +} ; + +static uint8_t PPU_Reg[8]; +static uint8_t PPU_VRAM[0x2000]; + +static uint8_t PPU_OAM[0x100]; +static uint8_t PPU_OAM2[0x20]; + +static uint8_t PPU_PALRAM[0x20]; +//internally processed, ready to draw +static uint8_t PPU_Sprites[0x20]; + +//static uint32_t ppuCycles; +static uint16_t curLine; +static uint16_t curDot; +static uint16_t ppuVramAddr; +static uint16_t ppuTmpVramAddr; +static uint8_t ppuToDraw; +static uint8_t ppuVramReadBuf; +static uint8_t ppuOAMpos; +static uint8_t ppuOAM2pos; +static uint8_t ppuFineXScroll; +static uint8_t ppuSpriteTilePos; +static uint16_t ppuBGRegA; +static uint16_t ppuBGRegB; +static uint8_t ppuBGAttribRegA; +static uint8_t ppuBGAttribRegB; +static uint8_t ppuCurOverflowAdd; +static uint8_t ppulastVal; + +static bool ppuHasZSprite; +static bool ppuNextHasZSprite; +static bool ppuFrameDone; +static bool ppuTmpWrite; +static bool ppuNMITriggered; +static bool ppuSpriteSearch; +static bool ppuCurVBlankStat; +static bool ppuCurNMIStat; +static bool ppuOddFrame; + +void ppuInit() +{ + memset(PPU_Reg,0,8); + memset(PPU_VRAM,0,0x2000); + memset(PPU_OAM,0,0x100); + memset(PPU_PALRAM,0,0x20); + memset(PPU_OAM2,0xFF,0x20); + memset(PPU_Sprites,0xFF,0x20); + //ppuCycles = 0; + //start out being in vblank + PPU_Reg[2] |= PPU_FLAG_VBLANK; + curLine = 251; + curDot = 0; + ppuVramAddr = 0; + ppuToDraw = 0; + ppuVramReadBuf = 0; + ppuOAMpos = 0; + ppuOAM2pos = 0; + ppuFineXScroll = 0; + ppuSpriteTilePos = 0; + ppuBGRegA = 0; + ppuBGRegB = 0; + ppuBGAttribRegA = 0; + ppuBGAttribRegB = 0; + ppuCurOverflowAdd = 0; + ppulastVal = 0; + ppuHasZSprite = false; + ppuNextHasZSprite = false; + ppuFrameDone = false; + ppuTmpWrite = false; + ppuNMITriggered = false; + ppuSpriteSearch = false; + ppuCurVBlankStat = false; + ppuCurNMIStat = false; + ppuOddFrame = false; +} + +static uint16_t ppuGetVramTbl(uint16_t tblStart) +{ + //printf("%d %04x\n", ppuForceTable, ppuForceTableAddr); + //take mapper input unless specified 4-screen ram + if(ppuForceTable && ppuScreenMode != PPU_MODE_FOURSCREEN) + return ppuForceTableAddr; + //no mapper input, check requested address + if(ppuScreenMode == PPU_MODE_SINGLE) + return 0; + tblStart &= 0xFFF; + if(tblStart < 0x400) + tblStart = 0; + else if(tblStart < 0x800) + { + if(ppuScreenMode == PPU_MODE_VERTICAL || ppuScreenMode == PPU_MODE_FOURSCREEN) + tblStart = 0x400; + else //horizontal + tblStart = 0; + } + else if(tblStart < 0xC00) + { + if(ppuScreenMode == PPU_MODE_FOURSCREEN) + tblStart = 0x800; + else if(ppuScreenMode == PPU_MODE_VERTICAL) + tblStart = 0; + else //horizontal + tblStart = 0x400; + } + else // <= 0xFFF + { + if(ppuScreenMode == PPU_MODE_FOURSCREEN) + tblStart = 0xC00; + else //horizontal and vertical + tblStart = 0x400; + } + return tblStart; +} + +bool ppuCycle() +{ + ppuCurVBlankStat = !!(PPU_Reg[2] & PPU_FLAG_VBLANK); + ppuCurNMIStat = !!(PPU_Reg[0] & PPU_FLAG_NMI); + + /* VBlank ends at first dot of the pre-render line */ + /* Though results are better when clearing it a bit later */ + if(curDot == 12 && curLine == 261) + { + #if PPU_DEBUG_VSYNC + printf("PPU End VBlank\n"); + #endif + PPU_Reg[2] &= ~(PPU_FLAG_VBLANK | PPU_FLAG_SPRITEZERO | PPU_FLAG_OVERFLOW); + } + bool pictureOutput = (PPU_Reg[1] & (PPU_BG_ENABLE | PPU_SPRITE_ENABLE)) != 0; + + /* Do Background Updates */ + if(pictureOutput && (curLine == 261 || curLine < VISIBLE_LINES)) + { + /* Update tile address if needed */ + if((curDot <= VISIBLE_DOTS) && (curDot & 7) == 0) + { + if((ppuVramAddr & 0x1F) == 0x1F) + { + ppuVramAddr &= ~0x1F; + ppuVramAddr ^= 0x400; + } + else + ppuVramAddr++; + } + /* update Y position for writes */ + if(curDot == VISIBLE_DOTS) + { + if(((ppuVramAddr>>12)&7) != 7) + ppuVramAddr += (1<<12); + else + { + ppuVramAddr &= ~0x7000; + uint8_t coarseY = (ppuVramAddr&0x3E0)>>5; + if(coarseY == 29) + { + coarseY = 0; + ppuVramAddr ^= 0x800; + } + else if(coarseY == 31) + coarseY = 0; + else + coarseY++; + ppuVramAddr &= ~0x3E0; + ppuVramAddr |= (coarseY<<5); + } + } /* Update horizontal values after scanline */ + else if(curDot == VISIBLE_DOTS+1) + { + ppuVramAddr &= ~PPU_VRAM_HORIZONTAL_MASK; + ppuVramAddr |= (ppuTmpVramAddr & PPU_VRAM_HORIZONTAL_MASK); + } + /* Do BG Reg Updates */ + if((curDot >= 319 || curDot <= VISIBLE_DOTS) && (curDot & 7) == 0) + { + uint16_t cPpuTbl = ppuGetVramTbl(ppuVramAddr & 0xC00); + /* Select new BG Tiles */ + uint16_t chrROMBG = (PPU_Reg[0] & PPU_BACKGROUND_ADDR) ? 0x1000 : 0; + uint16_t workAddr = cPpuTbl | (ppuVramAddr & 0x3FF); + uint8_t curBGtile = PPU_VRAM[workAddr]; + uint8_t curTileY = (ppuVramAddr>>12)&7; + uint16_t curBGTile = chrROMBG+(curBGtile<<4)+curTileY; + /* Select new BG Background Attribute */ + uint8_t cAttrib = ((ppuVramAddr>>4)&0x38) | ((ppuVramAddr>>2)&7); + uint16_t attributeAddr = cPpuTbl | (0x3C0 | cAttrib); + uint8_t cPalByte = PPU_VRAM[attributeAddr]; + /* Update BG Tile Regs */ + ppuBGRegA >>= 8; + ppuBGRegB >>= 8; + ppuBGRegA |= mapperChrGet8(curBGTile)<<8; + ppuBGRegB |= mapperChrGet8(curBGTile+8)<<8; + /* Update BG Background Attribute Regs */ + ppuBGAttribRegA = ppuBGAttribRegB; + uint8_t coarseX = (ppuVramAddr & 0x1F); + uint8_t coarseY = ((ppuVramAddr & 0x3E0)>>5); + bool left = ((coarseX&2) == 0); + bool top = ((coarseY&2) == 0); + if(top) + { + if(left) + ppuBGAttribRegB = (cPalByte&3)<<2; + else + ppuBGAttribRegB = (cPalByte&12); + } + else + { + if(left) + ppuBGAttribRegB = (cPalByte&48)>>2; + else + ppuBGAttribRegB = (cPalByte&192)>>4; + } + } + } + + /* Only render visible dots after pre-render line and before post-render line */ + if(curDot < VISIBLE_DOTS && curLine < VISIBLE_LINES) + { + /* Grab color to render from BG and Sprites */ + uint8_t curCol = ppuDoBackground(0, curDot); + curCol = ppuDoSprites(curCol, curDot); + if((curCol&3) == 0) //0,4,8 and C wrap + curCol &= ~0x10; //down to 0x00 + /* make sure to to clip top and bottom if requested */ + if(doOverscan && (curLine < 8 || curLine >= 232)) + curCol = 0xFF; + /* If left is clipped, also clip right */ + if((!(PPU_Reg[1] & PPU_BG_8PX) || !(PPU_Reg[1] & PPU_SPRITE_8PX)) && (curDot < 8 || curDot > 248)) + curCol = 0xFF; + /* Draw current dot on screen */ + size_t drawPos = (curDot<<2)+(curLine<<10); + if(curCol != 0xFF && pictureOutput) + { + uint8_t cPalIdx = (PPU_PALRAM[curCol]&0x3F)*3; + textureImage[drawPos] = PPU_Pal[cPalIdx+2]; + textureImage[drawPos+1] = PPU_Pal[cPalIdx+1]; + textureImage[drawPos+2] = PPU_Pal[cPalIdx]; + } + else /* Draw clipped area as black */ + { + textureImage[drawPos] = 0; + textureImage[drawPos+1] = 0; + textureImage[drawPos+2] = 0; + } + } + + /* set to 0 during not visible dots up to post-render line */ + if(pictureOutput && curDot > 257 && curLine < VISIBLE_LINES) + { + ppuOAMpos = 0; + ppuSpriteSearch = true; + ppuCurOverflowAdd = 0; + } + /* Update vertical values on pre-render scanline */ + if(pictureOutput && curLine == 261 && curDot >= 280 && curDot <= 304) + { + ppuVramAddr &= ~PPU_VRAM_VERTICAL_MASK; + ppuVramAddr |= (ppuTmpVramAddr & PPU_VRAM_VERTICAL_MASK); + } + /* Clear out OAM2 for next eval */ + if(curDot < 64 && ((curDot&1) == 0) && (curLine == 261 || curLine < VISIBLE_LINES)) + { + PPU_OAM2[ppuOAM2pos] = 0xFF; + ppuOAM2pos++; ppuOAM2pos &= 0x1F; + } /* Start sprite eval for next line */ + else if(curDot >= 64 && curDot < 192 && ((curDot&1) == 0) && curLine < VISIBLE_LINES && ppuSpriteSearch && pictureOutput) + { + uint8_t cSpriteLn = PPU_OAM[(ppuOAMpos+ppuCurOverflowAdd)&0xFF]; + /* nes sprite overflow bug */ + if(ppuOAM2pos == 0x20) + { + if(ppuCurOverflowAdd < 3) + ppuCurOverflowAdd++; + else + ppuCurOverflowAdd = 0; + } + uint8_t cSpriteAdd = (PPU_Reg[0] & PPU_SPRITE_8_16) ? 16 : 8; + if(ppuSpriteSearch && cSpriteLn <= curLine && (cSpriteLn+cSpriteAdd) > curLine) + { + if(ppuOAM2pos != 0x20) + { + if(ppuOAM2pos == 0 && ppuOAMpos == 0) + ppuNextHasZSprite = true; + *(uint32_t*)(PPU_OAM2+ppuOAM2pos) = *(uint32_t*)(PPU_OAM+ppuOAMpos); + //printf("Copying sprite with line %i at line %i pos oam %i oam2 %i\n", cSpriteLn, curLine, ppuOAMpos, ppuOAM2pos); + ppuOAM2pos += 4; + } + else + { + //if(!(PPU_Reg[2] & PPU_FLAG_OVERFLOW)) + // printf("Overflow on line %i\n", curLine); + PPU_Reg[2] |= PPU_FLAG_OVERFLOW; + ppuSpriteSearch = false; + } + } + ppuOAMpos += 4; + //if(ppuOAMpos == 0) + // ppuSpriteSearch = false; + //printf("%i\n", ppuOAMpos); + } + /* Grab Sprite Tiles to be drawn next line */ + if(curDot >= 260 && curDot <= 316 && (curDot&7) == 4 && (curLine == 261 || curLine < VISIBLE_LINES) && pictureOutput) + { + uint8_t cSpriteLn = PPU_OAM2[ppuSpriteTilePos]; + uint8_t cSpriteIndex = PPU_OAM2[ppuSpriteTilePos+1]; + uint8_t cSpriteByte2 = PPU_OAM2[ppuSpriteTilePos+2]; + uint8_t cSpriteByte3 = PPU_OAM2[ppuSpriteTilePos+3]; + uint8_t cSpriteAnd = (PPU_Reg[0] & PPU_SPRITE_8_16) ? 15 : 7; + uint8_t cSpriteAdd = 0; //used to select which 8 by 16 tile + uint8_t cSpriteY = (curLine - cSpriteLn)&cSpriteAnd; + if(cSpriteY > 7) //8 by 16 select + { + cSpriteAdd = 16; + cSpriteY &= 7; + } + uint16_t chrROMSpriteAdd = 0; + if(PPU_Reg[0] & PPU_SPRITE_8_16) + { + chrROMSpriteAdd = ((cSpriteIndex & 1) << 12); + cSpriteIndex &= ~1; + } + else if(PPU_Reg[0] & PPU_SPRITE_ADDR) + chrROMSpriteAdd = 0x1000; + if(cSpriteByte2 & PPU_SPRITE_FLIP_Y) + { + cSpriteY ^= 7; + if(PPU_Reg[0] & PPU_SPRITE_8_16) + cSpriteAdd ^= 16; //8 by 16 select + } + /* write processed values into internal draw buffer */ + PPU_Sprites[ppuSpriteTilePos] = mapperChrGet8(((chrROMSpriteAdd+(cSpriteIndex<<4)+cSpriteY+cSpriteAdd)&0xFFF) | chrROMSpriteAdd); + PPU_Sprites[ppuSpriteTilePos+1] = mapperChrGet8(((chrROMSpriteAdd+(cSpriteIndex<<4)+cSpriteY+8+cSpriteAdd)&0xFFF) | chrROMSpriteAdd); + PPU_Sprites[ppuSpriteTilePos+2] = cSpriteByte2; + PPU_Sprites[ppuSpriteTilePos+3] = cSpriteByte3; + ppuSpriteTilePos+=4; + } + + /* increase pos */ + curDot++; + if(curDot == 340 && curLine == 261) + { + ppuOddFrame ^= true; + if(ppuOddFrame && (PPU_Reg[1] & PPU_BG_ENABLE)) + curDot++; + } + if(curDot == DOTS) + { + curDot = 0; + curLine++; + ppuHasZSprite = ppuNextHasZSprite; + ppuNextHasZSprite = false; //reset + ppuSpriteTilePos = 0; //reset + ppuToDraw = ppuOAM2pos; + ppuOAM2pos = 0; + //printf("Line done\n"); + } + /* VBlank start at first dot after post-render line */ + /* Though results are better when starting it a bit later */ + if(curDot == 12 && curLine == 241) + { + ppuNMITriggered = false; + PPU_Reg[2] |= PPU_FLAG_VBLANK; + #if PPU_DEBUG_VSYNC + printf("PPU Start VBlank\n"); + #endif + } + /* Wrap back down after pre-render line */ + if(curLine == LINES) + { + ppuFrameDone = true; + curLine = 0; + } + //ppuCycles++; + return true; +} + +static uint8_t ppuDoBackground(uint8_t color, uint8_t dot) +{ + if((dot < 8 && !(PPU_Reg[1] & PPU_BG_8PX)) || !(PPU_Reg[1] & PPU_BG_ENABLE)) + return color; + + uint8_t curTileX = (dot+ppuFineXScroll)&7; + if((dot&7)+ppuFineXScroll <= 7) + { + if(ppuBGRegA & (0x80>>curTileX)) + color |= 1; + if(ppuBGRegB & (0x80>>curTileX)) + color |= 2; + if(color != 0) + color |= ppuBGAttribRegA; + } + else + { + if(ppuBGRegA & (0x8000>>curTileX)) + color |= 1; + if(ppuBGRegB & (0x8000>>curTileX)) + color |= 2; + if(color != 0) + color |= ppuBGAttribRegB; + } + + #if PPU_DEBUG_ULTRA + //if(dot == 90 && color){ printf("%02x\n", color);/* color = 0xD;*/ } + #endif + return color; +} + +static uint8_t ppuDoSprites(uint8_t color, uint8_t dot) +{ + if((dot < 8 && !(PPU_Reg[1] & PPU_SPRITE_8PX)) || !(PPU_Reg[1] & PPU_SPRITE_ENABLE)) + return color; + + uint8_t i; + for(i = 0; i < ppuToDraw; i+=4) + { + uint8_t cSpriteDot = PPU_Sprites[i+3]; + if(cSpriteDot <= dot && (cSpriteDot+8) > dot) + { + uint8_t cSpriteByte2 = PPU_Sprites[i+2]; + uint8_t cSpriteColor = cSpriteByte2&3; + uint8_t cSpriteX = (dot - cSpriteDot)&7; + if(cSpriteByte2 & PPU_SPRITE_FLIP_X) + cSpriteX ^= 7; + uint8_t sprCol = 0; + if(PPU_Sprites[i] & (0x80>>cSpriteX)) + sprCol |= 1; + if(PPU_Sprites[i+1] & (0x80>>cSpriteX)) + sprCol |= 2; + if(i == 0 && ppuHasZSprite && dot < 255 && ((color&3) != 0) && (sprCol != 0) && !(PPU_Reg[2] & PPU_FLAG_SPRITEZERO)) + { + PPU_Reg[2] |= PPU_FLAG_SPRITEZERO; + #if PPU_DEBUG_ULTRA + printf("Zero sprite hit at x %i y %i cSpriteDot %i " + "table %04x color %02x sprCol %02x\n", dot, curLine, cSpriteDot, ppuGetVramTbl((PPU_Reg[0]&3)<<10), color, sprCol); + #endif + //if(line != 30) + // ppuDebugPauseFrame = true; + } + //done looking at sprites, we have to + //always return the first one we find + if(sprCol != 0) + { + //sprite has highest priority, return sprite + if((cSpriteByte2 & PPU_SPRITE_PRIO) == 0) + { + color = (sprCol | cSpriteColor<<2 | 0x10); + break; + } //sprite has low priority and BG is not 0, return BG + else if((color&3) != 0) + break; + //background is 0 so return sprite + color = (sprCol | cSpriteColor<<2 | 0x10); + break; + } + //Sprite is 0, keep looking for sprites + } + } + return color; +} + +bool ppuDrawDone() +{ + if(ppuFrameDone) + { + //printf("%i\n",ppuCycles); + //ppuCycles = 0; + ppuFrameDone = false; + return true; + } + return false; +} + +void ppuSet8(uint8_t reg, uint8_t val) +{ + ppulastVal = val; + if(reg == 0) + { + PPU_Reg[reg] = val; + ppuTmpVramAddr &= ~0xC00; + ppuTmpVramAddr |= ((val&3)<<10); + //printf("%d %d %d\n", (PPU_Reg[0] & PPU_BACKGROUND_ADDR) != 0, (PPU_Reg[0] & PPU_SPRITE_ADDR) != 0, (PPU_Reg[0] & PPU_SPRITE_8_16) != 0); + } + else if(reg == 3) + { + #if PPU_DEBUG_ULTRA + printf("ppuOAMpos was %02x set to %02x\n", ppuOAMpos, val); + #endif + ppuOAMpos = val; + } + else if(reg == 4) + { + #if PPU_DEBUG_ULTRA + printf("Setting OAM at %02x to %02x\n", ppuOAMpos, val); + #endif + PPU_OAM[ppuOAMpos++] = val; + } + else if(reg == 5) + { + #if PPU_DEBUG_ULTRA + printf("ppuScrollWrite (%d) %02x pc %04x\n", ppuTmpWrite, val, cpuGetPc()); + #endif + if(!ppuTmpWrite) + { + ppuTmpWrite = true; + ppuFineXScroll = val&7; + ppuTmpVramAddr &= ~0x1F; + ppuTmpVramAddr |= ((val>>3)&0x1F); + } + else + { + ppuTmpWrite = false; + ppuTmpVramAddr &= ~0x73E0; + ppuTmpVramAddr |= ((val&7)<<12) | ((val>>3)<<5); + } + } + else if(reg == 6) + { + #if PPU_DEBUG_ULTRA + printf("ppuVramAddrWrite (%d) %02x pc %04x\n", ppuTmpWrite, val, cpuGetPc()); + #endif + if(!ppuTmpWrite) + { + ppuTmpWrite = true; + ppuTmpVramAddr &= 0xFF; + ppuTmpVramAddr |= ((val&0x3F)<<8); + } + else + { + ppuTmpWrite = false; + ppuTmpVramAddr &= ~0xFF; + ppuTmpVramAddr |= val; + ppuVramAddr = ppuTmpVramAddr; + } + } + else if(reg == 7) + { + uint16_t writeAddr = (ppuVramAddr & 0x3FFF); + if(writeAddr < 0x2000) + { + mapperChrSet8(writeAddr, val); + } + else if(writeAddr < 0x3F00) + { + uint16_t workAddr = ppuGetVramTbl(writeAddr) | (writeAddr & 0x3FF); + //printf("ppuVRAMwrite %04x %02x\n", workAddr, val); + PPU_VRAM[workAddr] = val; + } + else + { + uint8_t palRamAddr = (writeAddr&0x1F); + if((palRamAddr&3) == 0) + palRamAddr &= ~0x10; + PPU_PALRAM[palRamAddr] = val; + } + ppuVramAddr += (PPU_Reg[0] & PPU_INC_AMOUNT) ? 32 : 1; + } + else if(reg != 2) + { + PPU_Reg[reg] = val; + //printf("ppuSet8 %04x %02x\n", reg, val); + } +} + +uint8_t ppuGet8(uint8_t reg) +{ + uint8_t ret = ppulastVal; + //if(ret & PPU_FLAG_VBLANK) + //printf("ppuGet8 %04x:%02x\n",reg,ret); + if(reg == 2) + { + ret = PPU_Reg[reg]; + PPU_Reg[reg] &= ~PPU_FLAG_VBLANK; + ppuTmpWrite = false; + } + else if(reg == 4) + ret = PPU_OAM[ppuOAMpos]; + else if(reg == 7) + { + uint16_t writeAddr = (ppuVramAddr & 0x3FFF); + if(writeAddr < 0x2000) + { + ret = ppuVramReadBuf; + ppuVramReadBuf = mapperChrGet8(writeAddr); + } + else if(writeAddr < 0x3F00) + { + ret = ppuVramReadBuf; + uint16_t workAddr = ppuGetVramTbl(writeAddr) | (writeAddr & 0x3FF); + ppuVramReadBuf = PPU_VRAM[workAddr]; + //printf("ppuVRAMread pc %04x addr %04x ret %02x\n", cpuGetPc(), workAddr, ret); + } + else + { + uint8_t palRamAddr = (writeAddr&0x1F); + if((palRamAddr&3) == 0) + palRamAddr &= ~0x10; + ret = PPU_PALRAM[palRamAddr]; + //shadow read + uint16_t workAddr = ppuGetVramTbl(writeAddr) | (writeAddr & 0x3FF); + ppuVramReadBuf = PPU_VRAM[workAddr]; + } + ppuVramAddr += (PPU_Reg[0] & PPU_INC_AMOUNT) ? 32 : 1; + } + ppulastVal = ret; + return ret; +} + +bool ppuNMI() +{ + if(ppuCurVBlankStat && ppuCurNMIStat) + { + if(ppuNMITriggered == false) + { + ppuNMITriggered = true; + return true; + } + else + return false; + } + ppuNMITriggered = false; + return false; +} + +void ppuLoadOAM(uint8_t val) +{ + #if PPU_DEBUG_ULTRA + printf("ppuLoadOAM\n"); + #endif + PPU_OAM[ppuOAMpos] = val; + ppuOAMpos++; +} + +void ppuDumpMem() +{ + FILE *f = fopen("PPU_VRAM.bin","wb"); + if(f) + { + fwrite(PPU_VRAM,1,0x800,f); + fclose(f); + } + f = fopen("PPU_OAM.bin","wb"); + if(f) + { + fwrite(PPU_OAM,1,0x100,f); + fclose(f); + } + f = fopen("PPU_Sprites.bin","wb"); + if(f) + { + fwrite(PPU_Sprites,1,0x20,f); + fclose(f); + } +} + +uint16_t ppuGetCurVramAddr() +{ + return ppuVramAddr; +} diff --git a/ppu.h b/ppu.h new file mode 100644 index 0000000..bac22c8 --- /dev/null +++ b/ppu.h @@ -0,0 +1,28 @@ +/* + * 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(uint8_t reg); +void ppuSet8(uint8_t reg, uint8_t val); +void ppuLoadOAM(uint8_t val); +bool ppuNMI(); +void ppuDumpMem(); +uint16_t ppuGetCurVramAddr(); + +extern uint8_t ppuScreenMode; + +#define PPU_MODE_SINGLE 0 +#define PPU_MODE_VERTICAL 1 +#define PPU_MODE_HORIZONTAL 2 +#define PPU_MODE_FOURSCREEN 3 + +#endif \ No newline at end of file