376 lines
9.5 KiB
C++
376 lines
9.5 KiB
C++
#include "app/emu/snes.h"
|
|
|
|
#include <SDL_mixer.h>
|
|
|
|
#include <cstdint>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <thread>
|
|
|
|
#include "app/emu/audio/apu.h"
|
|
#include "app/emu/audio/spc700.h"
|
|
#include "app/emu/cpu/clock.h"
|
|
#include "app/emu/cpu/cpu.h"
|
|
#include "app/emu/debug/debugger.h"
|
|
#include "app/emu/memory/memory.h"
|
|
#include "app/emu/video/ppu.h"
|
|
#include "app/rom.h"
|
|
|
|
namespace yaze {
|
|
namespace app {
|
|
namespace emu {
|
|
|
|
namespace {
|
|
|
|
uint16_t GetHeaderOffset(const Memory& memory) {
|
|
uint8_t mapMode = memory[(0x00 << 16) + 0xFFD5];
|
|
uint16_t offset;
|
|
|
|
switch (mapMode & 0x07) {
|
|
case 0: // LoROM
|
|
offset = 0x7FC0;
|
|
break;
|
|
case 1: // HiROM
|
|
offset = 0xFFC0;
|
|
break;
|
|
case 5: // ExHiROM
|
|
offset = 0x40;
|
|
break;
|
|
default:
|
|
throw std::invalid_argument(
|
|
"Unable to locate supported ROM mapping mode in the provided ROM "
|
|
"file. Please try another ROM file.");
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
void audio_callback(void* userdata, uint8_t* stream, int len) {
|
|
auto* apu = static_cast<APU*>(userdata);
|
|
auto* buffer = reinterpret_cast<int16_t*>(stream);
|
|
|
|
for (int i = 0; i < len / 2; i++) { // Assuming 16-bit samples
|
|
buffer[i] = apu->GetNextSample(); // This function should be implemented in
|
|
// APU to fetch the next sample
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
ROMInfo SNES::ReadRomHeader(uint32_t offset) {
|
|
ROMInfo romInfo;
|
|
|
|
// Read cartridge title
|
|
char title[22];
|
|
for (int i = 0; i < 21; ++i) {
|
|
title[i] = cpu_.ReadByte(offset + i);
|
|
}
|
|
title[21] = '\0'; // Null-terminate the string
|
|
romInfo.title = std::string(title);
|
|
|
|
// Read ROM speed and memory map mode
|
|
uint8_t romSpeedAndMapMode = cpu_.ReadByte(offset + 0x15);
|
|
romInfo.romSpeed = (ROMSpeed)(romSpeedAndMapMode & 0x07);
|
|
romInfo.bankSize = (BankSize)((romSpeedAndMapMode >> 5) & 0x01);
|
|
|
|
// Read ROM type
|
|
romInfo.romType = (ROMType)cpu_.ReadByte(offset + 0x16);
|
|
|
|
// Read ROM size
|
|
romInfo.romSize = (ROMSize)cpu_.ReadByte(offset + 0x17);
|
|
|
|
// Read RAM size
|
|
romInfo.sramSize = (SRAMSize)cpu_.ReadByte(offset + 0x18);
|
|
|
|
// Read country code
|
|
romInfo.countryCode = (CountryCode)cpu_.ReadByte(offset + 0x19);
|
|
|
|
// Read license
|
|
romInfo.license = (License)cpu_.ReadByte(offset + 0x1A);
|
|
|
|
// Read ROM version
|
|
romInfo.version = cpu_.ReadByte(offset + 0x1B);
|
|
|
|
// Read checksum complement
|
|
romInfo.checksumComplement = cpu_.ReadWord(offset + 0x1E);
|
|
|
|
// Read checksum
|
|
romInfo.checksum = cpu_.ReadWord(offset + 0x1C);
|
|
|
|
// Read NMI VBL vector
|
|
romInfo.nmiVblVector = cpu_.ReadWord(offset + 0x3E);
|
|
|
|
// Read reset vector
|
|
romInfo.resetVector = cpu_.ReadWord(offset + 0x3C);
|
|
|
|
return romInfo;
|
|
}
|
|
|
|
void SNES::Init(ROM& rom) {
|
|
// Perform a long jump into a FastROM bank (if the ROM speed is FastROM)
|
|
// Disable the emulation flag (switch to 65816 native mode)
|
|
cpu_.E = 0;
|
|
|
|
// Initialize CPU
|
|
cpu_.Init();
|
|
|
|
// Read the ROM header
|
|
auto header_offset = GetHeaderOffset(memory_);
|
|
rom_info_ = ReadRomHeader((0x00 << 16) + header_offset);
|
|
cpu_.PB = 0x00;
|
|
cpu_.PC = 0x8000;
|
|
|
|
// Initialize PPU
|
|
ppu_.Init();
|
|
|
|
// Initialize APU
|
|
apu_.Init();
|
|
|
|
// Initialize SDL_Mixer to play the audio samples
|
|
// Mix_HookMusic(audio_callback, &apu);
|
|
|
|
// Disable interrupts and rendering
|
|
memory_.WriteByte(0x4200, 0x00); // NMITIMEN
|
|
memory_.WriteByte(0x420C, 0x00); // HDMAEN
|
|
|
|
// Disable screen
|
|
memory_.WriteByte(0x2100, 0x8F); // INIDISP
|
|
|
|
// Fill Work-RAM with zeros using two 64KiB fixed address DMA transfers to
|
|
// WMDATA
|
|
// TODO: Make this load from work ram, potentially in Memory class
|
|
std::memset((void*)memory_.ram_.data(), 0, sizeof(memory_.ram_));
|
|
|
|
// Reset PPU registers to a known good state
|
|
memory_.WriteByte(0x4201, 0xFF); // WRIO
|
|
|
|
// Objects
|
|
memory_.WriteByte(0x2101, 0x00); // OBSEL
|
|
memory_.WriteByte(0x2102, 0x00); // OAMADDL
|
|
memory_.WriteByte(0x2103, 0x00); // OAMADDH
|
|
|
|
// Backgrounds
|
|
memory_.WriteByte(0x2105, 0x00); // BGMODE
|
|
memory_.WriteByte(0x2106, 0x00); // MOSAIC
|
|
|
|
memory_.WriteByte(0x2107, 0x00); // BG1SC
|
|
memory_.WriteByte(0x2108, 0x00); // BG2SC
|
|
memory_.WriteByte(0x2109, 0x00); // BG3SC
|
|
memory_.WriteByte(0x210A, 0x00); // BG4SC
|
|
|
|
memory_.WriteByte(0x210B, 0x00); // BG12NBA
|
|
memory_.WriteByte(0x210C, 0x00); // BG34NBA
|
|
|
|
// Scroll Registers
|
|
memory_.WriteByte(0x210D, 0x00); // BG1HOFS
|
|
memory_.WriteByte(0x210E, 0xFF); // BG1VOFS
|
|
|
|
memory_.WriteByte(0x210F, 0x00); // BG2HOFS
|
|
memory_.WriteByte(0x2110, 0xFF); // BG2VOFS
|
|
|
|
memory_.WriteByte(0x2111, 0x00); // BG3HOFS
|
|
memory_.WriteByte(0x2112, 0xFF); // BG3VOFS
|
|
|
|
memory_.WriteByte(0x2113, 0x00); // BG4HOFS
|
|
memory_.WriteByte(0x2114, 0xFF); // BG4VOFS
|
|
|
|
// VRAM Registers
|
|
memory_.WriteByte(0x2115, 0x80); // VMAIN
|
|
|
|
// Mode 7
|
|
memory_.WriteByte(0x211A, 0x00); // M7SEL
|
|
memory_.WriteByte(0x211B, 0x01); // M7A
|
|
memory_.WriteByte(0x211C, 0x00); // M7B
|
|
memory_.WriteByte(0x211D, 0x00); // M7C
|
|
memory_.WriteByte(0x211E, 0x01); // M7D
|
|
memory_.WriteByte(0x211F, 0x00); // M7X
|
|
memory_.WriteByte(0x2120, 0x00); // M7Y
|
|
|
|
// Windows
|
|
memory_.WriteByte(0x2123, 0x00); // W12SEL
|
|
memory_.WriteByte(0x2124, 0x00); // W34SEL
|
|
memory_.WriteByte(0x2125, 0x00); // WOBJSEL
|
|
memory_.WriteByte(0x2126, 0x00); // WH0
|
|
memory_.WriteByte(0x2127, 0x00); // WH1
|
|
memory_.WriteByte(0x2128, 0x00); // WH2
|
|
memory_.WriteByte(0x2129, 0x00); // WH3
|
|
memory_.WriteByte(0x212A, 0x00); // WBGLOG
|
|
memory_.WriteByte(0x212B, 0x00); // WOBJLOG
|
|
|
|
// Layer Enable
|
|
memory_.WriteByte(0x212C, 0x00); // TM
|
|
memory_.WriteByte(0x212D, 0x00); // TS
|
|
memory_.WriteByte(0x212E, 0x00); // TMW
|
|
memory_.WriteByte(0x212F, 0x00); // TSW
|
|
|
|
// Color Math
|
|
memory_.WriteByte(0x2130, 0x30); // CGWSEL
|
|
memory_.WriteByte(0x2131, 0x00); // CGADSUB
|
|
memory_.WriteByte(0x2132, 0xE0); // COLDATA
|
|
|
|
// Misc
|
|
memory_.WriteByte(0x2133, 0x00); // SETINI
|
|
|
|
// Psuedo-Init
|
|
memory_.WriteWord(0x2140, 0xBBAA);
|
|
|
|
running_ = true;
|
|
scanline = 0;
|
|
}
|
|
|
|
void SNES::Run() {
|
|
const double targetFPS = 60.0; // 60 frames per second
|
|
const double frame_time = 1.0 / targetFPS;
|
|
double frame_accumulated_time = 0.0;
|
|
|
|
auto last_time = std::chrono::high_resolution_clock::now();
|
|
|
|
if (running_) {
|
|
auto current_time = std::chrono::high_resolution_clock::now();
|
|
double delta_time =
|
|
std::chrono::duration<double>(current_time - last_time).count();
|
|
last_time = current_time;
|
|
|
|
frame_accumulated_time += delta_time;
|
|
|
|
// Update the CPU
|
|
cpu_.UpdateClock(delta_time);
|
|
cpu_.Update(GetCpuMode());
|
|
|
|
// Update the PPU
|
|
ppu_.UpdateClock(delta_time);
|
|
ppu_.Update();
|
|
|
|
// Update the APU
|
|
apu_.UpdateClock(delta_time);
|
|
apu_.Update();
|
|
|
|
if (frame_accumulated_time >= frame_time) {
|
|
// renderer.Render();
|
|
frame_accumulated_time -= frame_time;
|
|
}
|
|
|
|
HandleInput();
|
|
}
|
|
}
|
|
|
|
void SNES::StepRun() {
|
|
// Update the CPU
|
|
cpu_.UpdateClock(0.0);
|
|
cpu_.Update(CPU::UpdateMode::Step);
|
|
|
|
// Update the PPU
|
|
ppu_.UpdateClock(0.0);
|
|
ppu_.Update();
|
|
|
|
// Update the APU
|
|
apu_.UpdateClock(0.0);
|
|
apu_.Update();
|
|
|
|
HandleInput();
|
|
}
|
|
|
|
// Enable NMI Interrupts
|
|
void SNES::EnableVBlankInterrupts() {
|
|
v_blank_flag_ = false;
|
|
|
|
// Clear the RDNMI VBlank flag
|
|
memory_.ReadByte(0x4210); // RDNMI
|
|
|
|
// Enable vblank NMI interrupts and Joypad auto-read
|
|
memory_.WriteByte(0x4200, 0x81); // NMITIMEN
|
|
}
|
|
|
|
// Wait until the VBlank routine has been processed
|
|
void SNES::WaitForVBlank() {
|
|
v_blank_flag_ = true;
|
|
|
|
// Loop until `v_blank_flag_` is clear
|
|
while (v_blank_flag_) {
|
|
std::this_thread::yield();
|
|
}
|
|
}
|
|
|
|
// NMI Interrupt Service Routine
|
|
void SNES::NmiIsr() {
|
|
// Switch to a FastROM bank (assuming NmiIsr is in bank 0x80)
|
|
// ...
|
|
|
|
// Push CPU registers to stack
|
|
cpu_.PHP();
|
|
|
|
// Reset DB and DP registers
|
|
cpu_.DB = 0x80; // Assuming bank 0x80, can be changed to 0x00
|
|
cpu_.D = 0;
|
|
|
|
if (v_blank_flag_) {
|
|
VBlankRoutine();
|
|
|
|
// Clear `v_blank_flag_`
|
|
v_blank_flag_ = false;
|
|
}
|
|
|
|
// Increment 32-bit frame_counter_
|
|
frame_counter_++;
|
|
|
|
// Restore CPU registers
|
|
cpu_.PHB();
|
|
}
|
|
|
|
// VBlank routine
|
|
void SNES::VBlankRoutine() {
|
|
// Read the joypad state
|
|
// ...
|
|
|
|
// Update the PPU
|
|
// ...
|
|
|
|
// Update the APU
|
|
// ...
|
|
}
|
|
|
|
void SNES::StartApuDataTransfer() {
|
|
// 2. Setting the starting address
|
|
const uint16_t startAddress = 0x0200;
|
|
memory_.WriteByte(0x2142, startAddress & 0xFF); // Lower byte
|
|
memory_.WriteByte(0x2143, startAddress >> 8); // Upper byte
|
|
memory_.WriteByte(0x2141, 0xCC); // Any non-zero value
|
|
memory_.WriteByte(0x2140, 0xCC); // Signal to start
|
|
|
|
const int DATA_SIZE = 0x1000; // 4 KiB
|
|
|
|
// 3. Sending data (simplified)
|
|
// Assuming a buffer `audioData` containing the audio program/data
|
|
uint8_t audioData[DATA_SIZE]; // Define DATA_SIZE and populate audioData as
|
|
// needed
|
|
for (int i = 0; i < DATA_SIZE; ++i) {
|
|
memory_.WriteByte(0x2141, audioData[i]);
|
|
memory_.WriteByte(0x2140, i & 0xFF);
|
|
while (memory_.ReadByte(0x2140) != (i & 0xFF))
|
|
; // Wait for acknowledgment
|
|
}
|
|
|
|
// 4. Running the SPC700 program
|
|
memory_.WriteByte(0x2142, startAddress & 0xFF); // Lower byte
|
|
memory_.WriteByte(0x2143, startAddress >> 8); // Upper byte
|
|
memory_.WriteByte(0x2141, 0x00); // Zero to start the program
|
|
memory_.WriteByte(0x2140, 0xCE); // Increment by 2
|
|
while (memory_.ReadByte(0x2140) != 0xCE)
|
|
; // Wait for acknowledgment
|
|
}
|
|
|
|
void SNES::HandleInput() {
|
|
// ...
|
|
}
|
|
|
|
void SNES::SaveState(const std::string& path) {
|
|
// ...
|
|
}
|
|
|
|
void SNES::LoadState(const std::string& path) {
|
|
// ...
|
|
}
|
|
|
|
} // namespace emu
|
|
} // namespace app
|
|
} // namespace yaze
|