Files
yaze/src/app/emu/snes.cc
2023-08-26 04:51:50 -04:00

439 lines
12 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/clock.h"
#include "app/emu/cpu.h"
#include "app/emu/debug/debugger.h"
#include "app/emu/mem.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 = 0x7F;
offset = 0xFFC0;
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
void DMA::StartDMATransfer(uint8_t channelMask) {
for (int i = 0; i < 8; ++i) {
if ((channelMask & (1 << i)) != 0) {
Channel& ch = channels[i];
// Validate channel parameters (e.g., DMAPn, BBADn, A1Tn, DASn)
// ...
// Determine the transfer direction based on the DMAPn register
bool fromMemory = (ch.DMAPn & 0x80) != 0;
// Determine the transfer size based on the DMAPn register
bool transferTwoBytes = (ch.DMAPn & 0x40) != 0;
// Perform the DMA transfer based on the channel parameters
std::cout << "Starting DMA transfer for channel " << i << std::endl;
for (uint16_t j = 0; j < ch.DASn; ++j) {
// Read a byte or two bytes from memory based on the transfer size
// ...
// Write the data to the B-bus address (BBADn) if transferring from
// memory
// ...
// Update the A1Tn register based on the transfer direction
if (fromMemory) {
ch.A1Tn += transferTwoBytes ? 2 : 1;
} else {
ch.A1Tn -= transferTwoBytes ? 2 : 1;
}
}
// Update the channel registers after the transfer (e.g., A1Tn, DASn)
// ...
}
}
MDMAEN = channelMask; // Set the MDMAEN register to the channel mask
}
void DMA::EnableHDMATransfers(uint8_t channelMask) {
for (int i = 0; i < 8; ++i) {
if ((channelMask & (1 << i)) != 0) {
Channel& ch = channels[i];
// Validate channel parameters (e.g., DMAPn, BBADn, A1Tn, A2An, NLTRn)
// ...
// Perform the HDMA setup based on the channel parameters
std::cout << "Enabling HDMA transfer for channel " << i << std::endl;
// Read the HDMA table from memory starting at A1Tn
// ...
// Update the A2An register based on the HDMA table
// ...
// Update the NLTRn register based on the HDMA table
// ...
}
}
HDMAEN = channelMask; // Set the HDMAEN register to the channel mask
}
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) {
// Setup observers for the memory space
memory_.AddObserver(&apu);
memory_.AddObserver(&ppu);
// Load the ROM into memory and set up the memory mapping
memory_.Initialize(rom.vector());
// Read the ROM header
auto header_offset = GetHeaderOffset(memory_);
rom_info_ = ReadRomHeader(header_offset);
// Perform a long jump into a FastROM bank (if the ROM speed is FastROM)
// Disable the emulation flag (switch to 65816 native mode)
// Initialize CPU
cpu.Init();
cpu.PC = rom_info_.resetVector;
// 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
running_ = true;
scanline = 0;
}
void SNES::Run() {
running_ = true;
const double targetFPS = 60.0; // 60 frames per second
const double frameTime = 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();
// Update the PPU
ppu.UpdateClock(delta_time);
ppu.Update();
// Update the APU
apu.UpdateClock(delta_time);
apu.Update();
if (frame_accumulated_time >= frameTime) {
// renderer.Render();
frame_accumulated_time -= frameTime;
}
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() {
// Execute code that needs to run during VBlank, such as transferring data to
// the PPU
// ...
}
void SNES::BootAPUWithIPL() {
// 1. Waiting for the SPC700 to be ready
while (!apu.IsReadySignalReceived()) {
// Active waiting (this can be optimized)
}
// 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) {
// ...
}
void SNES::Debug() {
// ...
}
void SNES::Breakpoint(uint16_t address) {
// ...
}
} // namespace emu
} // namespace app
} // namespace yaze