diff --git a/Core/BaseCartridge.cpp b/Core/BaseCartridge.cpp index 23255038..b0f51a30 100644 --- a/Core/BaseCartridge.cpp +++ b/Core/BaseCartridge.cpp @@ -8,6 +8,8 @@ #include "../Utilities/HexUtilities.h" #include "../Utilities/VirtualFile.h" #include "../Utilities/FolderUtilities.h" +#include "../Utilities/Serializer.h" +#include "../Utilities/sha1.h" BaseCartridge::~BaseCartridge() { @@ -158,6 +160,11 @@ RomInfo BaseCartridge::GetRomInfo() return info; } +string BaseCartridge::GetSha1Hash() +{ + return SHA1::GetHash(_prgRom, _prgRomSize); +} + void BaseCartridge::LoadBattery() { if(_saveRamSize > 0) { @@ -240,6 +247,11 @@ void BaseCartridge::RegisterHandlers(MemoryManager &mm) } } +void BaseCartridge::Serialize(Serializer &s) +{ + s.StreamArray(_saveRam, _saveRamSize); +} + void BaseCartridge::DisplayCartInfo() { int nameLength = 21; diff --git a/Core/BaseCartridge.h b/Core/BaseCartridge.h index 5586eb76..eb67c29b 100644 --- a/Core/BaseCartridge.h +++ b/Core/BaseCartridge.h @@ -1,39 +1,12 @@ #pragma once #include "stdafx.h" #include "IMemoryHandler.h" +#include "CartTypes.h" +#include "../Utilities/ISerializable.h" class MemoryManager; class VirtualFile; -struct SnesCartInformation -{ - uint8_t MakerCode[2]; - uint8_t GameCode[4]; - uint8_t Reserved[7]; - uint8_t ExpansionRamSize; - uint8_t SpecialVersion; - uint8_t CartridgeType; - - char CartName[21]; - uint8_t MapMode; - uint8_t RomType; - uint8_t RomSize; - uint8_t SramSize; - - uint8_t DestinationCode; - uint8_t Reserved2; - uint8_t Version; - - uint8_t ChecksumComplement[2]; - uint8_t Checksum[2]; -}; - -struct RomInfo -{ - SnesCartInformation Header; - string RomPath; -}; - namespace CartFlags { enum CartFlags @@ -47,7 +20,7 @@ namespace CartFlags }; } -class BaseCartridge +class BaseCartridge : public ISerializable { private: vector> _prgRomHandlers; @@ -79,6 +52,7 @@ public: void Init(); RomInfo GetRomInfo(); + string GetSha1Hash(); void RegisterHandlers(MemoryManager &mm); @@ -86,4 +60,6 @@ public: uint8_t* DebugGetSaveRam() { return _saveRam; } uint32_t DebugGetPrgRomSize() { return _prgRomSize; } uint32_t DebugGetSaveRamSize() { return _saveRamSize; } + + void Serialize(Serializer &s) override; }; diff --git a/Core/BaseControlDevice.cpp b/Core/BaseControlDevice.cpp index a1cbbd16..26b6e7cb 100644 --- a/Core/BaseControlDevice.cpp +++ b/Core/BaseControlDevice.cpp @@ -1,9 +1,9 @@ #include "stdafx.h" #include "BaseControlDevice.h" +#include "Console.h" #include "KeyManager.h" #include "../Utilities/StringUtilities.h" -#include "Console.h" -//#include "EmulationSettings.h" +#include "../Utilities/Serializer.h" BaseControlDevice::BaseControlDevice(shared_ptr console, uint8_t port, KeyMappingSet keyMappingSet) { @@ -32,16 +32,6 @@ void BaseControlDevice::InternalSetStateFromInput() { } -//TODO -/* -void BaseControlDevice::StreamState(bool saving) -{ - auto lock = _stateLock.AcquireSafe(); - VectorInfo state{ &_state.State }; - Stream(_strobe, state); -} -*/ - bool BaseControlDevice::IsCurrentPort(uint16_t addr) { return _port == (addr - 0x4016); @@ -277,3 +267,10 @@ void BaseControlDevice::SwapButtons(shared_ptr state1, uint8_ state1->SetBit(button1); } } + +void BaseControlDevice::Serialize(Serializer &s) +{ + auto lock = _stateLock.AcquireSafe(); + s.Stream(_strobe); + s.StreamVector(_state.State); +} diff --git a/Core/BaseControlDevice.h b/Core/BaseControlDevice.h index 414d7fde..88b33438 100644 --- a/Core/BaseControlDevice.h +++ b/Core/BaseControlDevice.h @@ -4,10 +4,11 @@ #include "SettingTypes.h" #include "IKeyManager.h" #include "../Utilities/SimpleLock.h" +#include "../Utilities/ISerializable.h" class Console; -class BaseControlDevice +class BaseControlDevice : public ISerializable { private: ControlDeviceState _state; @@ -80,4 +81,6 @@ public: virtual void WriteRam(uint16_t addr, uint8_t value) = 0; void static SwapButtons(shared_ptr state1, uint8_t button1, shared_ptr state2, uint8_t button2); + + void Serialize(Serializer &s) override; }; diff --git a/Core/CartTypes.h b/Core/CartTypes.h new file mode 100644 index 00000000..fcaf3efd --- /dev/null +++ b/Core/CartTypes.h @@ -0,0 +1,31 @@ +#pragma once +#include "stdafx.h" + +struct SnesCartInformation +{ + uint8_t MakerCode[2]; + uint8_t GameCode[4]; + uint8_t Reserved[7]; + uint8_t ExpansionRamSize; + uint8_t SpecialVersion; + uint8_t CartridgeType; + + char CartName[21]; + uint8_t MapMode; + uint8_t RomType; + uint8_t RomSize; + uint8_t SramSize; + + uint8_t DestinationCode; + uint8_t Reserved2; + uint8_t Version; + + uint8_t ChecksumComplement[2]; + uint8_t Checksum[2]; +}; + +struct RomInfo +{ + SnesCartInformation Header; + string RomPath; +}; \ No newline at end of file diff --git a/Core/Console.cpp b/Core/Console.cpp index f41b928d..6ed28b55 100644 --- a/Core/Console.cpp +++ b/Core/Console.cpp @@ -21,7 +21,11 @@ #include "KeyManager.h" #include "EventType.h" #include "EmuSettings.h" +#include "SaveStateManager.h" #include "DebugStats.h" +#include "CartTypes.h" +#include "ConsoleLock.h" +#include "../Utilities/Serializer.h" #include "../Utilities/Timer.h" #include "../Utilities/VirtualFile.h" #include "../Utilities/PlatformUtilities.h" @@ -33,10 +37,13 @@ Console::~Console() void Console::Initialize() { + _lockCounter = 0; + _settings.reset(new EmuSettings()); _notificationManager.reset(new NotificationManager()); _videoDecoder.reset(new VideoDecoder(shared_from_this())); _videoRenderer.reset(new VideoRenderer(shared_from_this())); + _saveStateManager.reset(new SaveStateManager(shared_from_this())); _soundMixer.reset(new SoundMixer(this)); _debugHud.reset(new DebugHud()); @@ -55,6 +62,9 @@ void Console::Release() _videoRenderer.reset(); _debugHud.reset(); _notificationManager.reset(); + _saveStateManager.reset(); + _soundMixer.reset(); + _settings.reset(); } void Console::Run() @@ -81,6 +91,8 @@ void Console::Run() _cpu->Exec(); if(previousFrameCount != _ppu->GetFrameCount()) { + WaitForLock(); + frameLimiter.ProcessFrame(); frameLimiter.WaitForNextFrame(); @@ -169,6 +181,18 @@ void Console::LoadRom(VirtualFile romFile, VirtualFile patchFile) //_debugger.reset(); //GetDebugger(); //} + + _notificationManager->SendNotification(ConsoleNotificationType::GameLoaded); + } +} + +RomInfo Console::GetRomInfo() +{ + shared_ptr cart = _cart; + if(cart) { + return cart->GetRomInfo(); + } else { + return {}; } } @@ -184,6 +208,63 @@ double Console::GetFrameDelay() return frameDelay; } +ConsoleLock Console::AcquireLock() +{ + return ConsoleLock(this); +} + +void Console::Lock() +{ + _lockCounter++; + _runLock.Acquire(); +} + +void Console::Unlock() +{ + _runLock.Release(); + _lockCounter--; +} + +void Console::WaitForLock() +{ + if(_lockCounter > 0) { + //Need to temporarely pause the emu (to save/load a state, etc.) + _runLock.Release(); + + //Spin wait until we are allowed to start again + while(_lockCounter > 0) {} + + _runLock.Acquire(); + } +} + +void Console::Serialize(ostream &out) +{ + Serializer serializer(SaveStateManager::FileFormatVersion); + serializer.Stream(_cpu.get()); + serializer.Stream(_memoryManager.get()); + serializer.Stream(_ppu.get()); + serializer.Stream(_dmaController.get()); + serializer.Stream(_internalRegisters.get()); + serializer.Stream(_cart.get()); + serializer.Stream(_controlManager.get()); + serializer.Stream(_spc.get()); + serializer.Save(out); +} + +void Console::Deserialize(istream &in, uint32_t fileFormatVersion) +{ + Serializer serializer(in, fileFormatVersion); + serializer.Stream(_cpu.get()); + serializer.Stream(_memoryManager.get()); + serializer.Stream(_ppu.get()); + serializer.Stream(_dmaController.get()); + serializer.Stream(_internalRegisters.get()); + serializer.Stream(_cart.get()); + serializer.Stream(_controlManager.get()); + serializer.Stream(_spc.get()); +} + shared_ptr Console::GetSoundMixer() { return _soundMixer; @@ -209,6 +290,11 @@ shared_ptr Console::GetSettings() return _settings; } +shared_ptr Console::GetSaveStateManager() +{ + return _saveStateManager; +} + shared_ptr Console::GetDebugHud() { return _debugHud; diff --git a/Core/Console.h b/Core/Console.h index 0c95f213..173dddb5 100644 --- a/Core/Console.h +++ b/Core/Console.h @@ -1,5 +1,7 @@ #pragma once #include "stdafx.h" +#include "CartTypes.h" +#include "ConsoleLock.h" #include "../Utilities/VirtualFile.h" #include "../Utilities/SimpleLock.h" @@ -18,6 +20,7 @@ class VideoRenderer; class VideoDecoder; class NotificationManager; class EmuSettings; +class SaveStateManager; enum class MemoryOperationType; enum class SnesMemoryType; enum class EventType; @@ -42,14 +45,18 @@ private: shared_ptr _videoDecoder; shared_ptr _debugHud; shared_ptr _settings; + shared_ptr _saveStateManager; thread::id _emulationThreadId; - + + atomic _lockCounter; SimpleLock _runLock; + SimpleLock _debuggerLock; atomic _stopFlag; double GetFrameDelay(); + void WaitForLock(); public: ~Console(); @@ -61,12 +68,21 @@ public: void Stop(); void LoadRom(VirtualFile romFile, VirtualFile patchFile); + RomInfo GetRomInfo(); + + ConsoleLock AcquireLock(); + void Lock(); + void Unlock(); + + void Serialize(ostream &out); + void Deserialize(istream &in, uint32_t fileFormatVersion); shared_ptr GetSoundMixer(); shared_ptr GetVideoRenderer(); shared_ptr GetVideoDecoder(); shared_ptr GetNotificationManager(); shared_ptr GetSettings(); + shared_ptr GetSaveStateManager(); shared_ptr GetDebugHud(); diff --git a/Core/ConsoleLock.cpp b/Core/ConsoleLock.cpp new file mode 100644 index 00000000..ff8a4e2d --- /dev/null +++ b/Core/ConsoleLock.cpp @@ -0,0 +1,14 @@ +#include "stdafx.h" +#include "ConsoleLock.h" +#include "Console.h" + +ConsoleLock::ConsoleLock(Console *console) +{ + _console = console; + _console->Lock(); +} + +ConsoleLock::~ConsoleLock() +{ + _console->Unlock(); +} diff --git a/Core/ConsoleLock.h b/Core/ConsoleLock.h new file mode 100644 index 00000000..3d3fa6e0 --- /dev/null +++ b/Core/ConsoleLock.h @@ -0,0 +1,14 @@ +#pragma once +#include "stdafx.h" + +class Console; + +class ConsoleLock +{ +private: + Console *_console = nullptr; + +public: + ConsoleLock(Console *console); + ~ConsoleLock(); +}; \ No newline at end of file diff --git a/Core/ControlManager.cpp b/Core/ControlManager.cpp index 97446c13..370fe6fb 100644 --- a/Core/ControlManager.cpp +++ b/Core/ControlManager.cpp @@ -7,6 +7,7 @@ #include "IInputProvider.h" #include "IInputRecorder.h" #include "SnesController.h" +#include "../Utilities/Serializer.h" ControlManager::ControlManager(shared_ptr console) { @@ -216,4 +217,11 @@ void ControlManager::Write(uint16_t addr, uint8_t value) for(shared_ptr &device : _controlDevices) { device->WriteRam(addr, value); } -} \ No newline at end of file +} + +void ControlManager::Serialize(Serializer &s) +{ + for(shared_ptr &device : _controlDevices) { + s.Stream(device.get()); + } +} diff --git a/Core/ControlManager.h b/Core/ControlManager.h index ced4bf30..d3bc8be8 100644 --- a/Core/ControlManager.h +++ b/Core/ControlManager.h @@ -2,6 +2,7 @@ #include "stdafx.h" #include "../Utilities/SimpleLock.h" +#include "../Utilities/ISerializable.h" #include "IMemoryHandler.h" class BaseControlDevice; @@ -12,13 +13,13 @@ struct ControlDeviceState; enum class ControllerType; enum class ExpansionPortDevice; -class ControlManager +class ControlManager : public ISerializable { private: vector _inputRecorders; vector _inputProviders; - //Static so that power cycle does not reset its value + //TODO: Static so that power cycle does not reset its value uint32_t _pollCounter; protected: @@ -57,4 +58,6 @@ public: uint8_t Read(uint16_t addr); void Write(uint16_t addr, uint8_t value); + + void Serialize(Serializer &s) override; }; diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index fe296cd5..240b9f2f 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -54,8 +54,10 @@ + + @@ -104,6 +106,7 @@ + @@ -130,6 +133,7 @@ + @@ -152,6 +156,8 @@ + + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 41bb5cd7..c9819c5c 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -227,6 +227,15 @@ Misc + + Misc + + + SNES + + + Misc + @@ -362,6 +371,13 @@ Misc + + SNES + + + Misc + + diff --git a/Core/Cpu.cpp b/Core/Cpu.cpp index 40530b8b..39d28cc9 100644 --- a/Core/Cpu.cpp +++ b/Core/Cpu.cpp @@ -4,6 +4,7 @@ #include "Console.h" #include "MemoryManager.h" #include "EventType.h" +#include "../Utilities/Serializer.h" Cpu::Cpu(Console *console) { @@ -612,3 +613,12 @@ bool Cpu::CheckFlag(uint8_t flag) { return (_state.PS & flag) == flag; } + +void Cpu::Serialize(Serializer &s) +{ + s.Stream( + _state.A, _state.CycleCount, _state.D, _state.DBR, _state.EmulationMode, _state.IrqSource, _state.K, + _state.NmiFlag, _state.PC, _state.PrevIrqSource, _state.PrevNmiFlag, _state.PS, _state.SP, _state.StopState, + _state.X, _state.Y + ); +} diff --git a/Core/Cpu.h b/Core/Cpu.h index fbc95ddc..50399593 100644 --- a/Core/Cpu.h +++ b/Core/Cpu.h @@ -7,11 +7,12 @@ #include "stdafx.h" #include "CpuTypes.h" +#include "../Utilities/ISerializable.h" class MemoryManager; class Console; -class Cpu +class Cpu : public ISerializable { private: static constexpr uint32_t NmiVector = 0x00FFEA; @@ -300,6 +301,9 @@ public: bool CheckIrqSource(IrqSource source); void ClearIrqSource(IrqSource source); + // Inherited via ISerializable + void Serialize(Serializer &s) override; + #ifdef DUMMYCPU private: uint32_t _writeCounter = 0; diff --git a/Core/DmaController.cpp b/Core/DmaController.cpp index 0b4b2637..03010f68 100644 --- a/Core/DmaController.cpp +++ b/Core/DmaController.cpp @@ -2,6 +2,7 @@ #include "DmaController.h" #include "MemoryManager.h" #include "MessageManager.h" +#include "../Utilities/Serializer.h" DmaController::DmaController(MemoryManager *memoryManager) { @@ -428,5 +429,19 @@ uint8_t DmaController::Read(uint16_t addr) return channel.HdmaLineCounterAndRepeat; } } - return 0; //TODO : open bus -} \ No newline at end of file + return _memoryManager->GetOpenBus(); +} + +void DmaController::Serialize(Serializer &s) +{ + s.Stream(_hdmaPending, _hdmaChannels); + for(int i = 0; i < 8; i++) { + s.Stream( + _channel[i].Decrement, _channel[i].DestAddress, _channel[i].DoTransfer, _channel[i].FixedTransfer, + _channel[i].HdmaBank, _channel[i].HdmaFinished, _channel[i].HdmaIndirectAddressing, + _channel[i].HdmaLineCounterAndRepeat, _channel[i].HdmaTableAddress, _channel[i].InterruptedByHdma, + _channel[i].InvertDirection, _channel[i].SrcAddress, _channel[i].SrcBank, _channel[i].TransferMode, + _channel[i].TransferSize, _channel[i].UnusedFlag + ); + } +} diff --git a/Core/DmaController.h b/Core/DmaController.h index 48a9d8aa..072415de 100644 --- a/Core/DmaController.h +++ b/Core/DmaController.h @@ -1,6 +1,7 @@ #pragma once #include "stdafx.h" #include "CpuTypes.h" +#include "../Utilities/ISerializable.h" class MemoryManager; @@ -28,7 +29,7 @@ struct DmaChannelConfig bool UnusedFlag; }; -class DmaController +class DmaController : public ISerializable { private: static constexpr uint8_t _transferByteCount[8] = { 1, 2, 2, 4, 4, 4, 2, 4 }; @@ -57,4 +58,6 @@ public: void Write(uint16_t addr, uint8_t value); uint8_t Read(uint16_t addr); + + void Serialize(Serializer &s) override; }; \ No newline at end of file diff --git a/Core/EmuSettings.cpp b/Core/EmuSettings.cpp index 52b479bc..0e03e2d2 100644 --- a/Core/EmuSettings.cpp +++ b/Core/EmuSettings.cpp @@ -4,6 +4,12 @@ #include "MessageManager.h" #include "../Utilities/FolderUtilities.h" +uint32_t EmuSettings::GetVersion() +{ + //0.1.0 + return 0x00000100; +} + void EmuSettings::ProcessString(string & str, const char ** strPointer) { //Make a copy of the string and keep it (the original pointer will not be valid after the call is over) diff --git a/Core/EmuSettings.h b/Core/EmuSettings.h index 8983b6fe..093e2bb6 100644 --- a/Core/EmuSettings.h +++ b/Core/EmuSettings.h @@ -24,6 +24,8 @@ private: void SetShortcutKey(EmulatorShortcut shortcut, KeyCombination keyCombination, int keySetIndex); public: + uint32_t GetVersion(); + void SetVideoConfig(VideoConfig config); VideoConfig GetVideoConfig(); diff --git a/Core/InternalRegisters.cpp b/Core/InternalRegisters.cpp index c905d04b..2bbfa899 100644 --- a/Core/InternalRegisters.cpp +++ b/Core/InternalRegisters.cpp @@ -5,6 +5,7 @@ #include "Ppu.h" #include "ControlManager.h" #include "MessageManager.h" +#include "../Utilities/Serializer.h" #include "../Utilities/HexUtilities.h" InternalRegisters::InternalRegisters(shared_ptr console) @@ -101,7 +102,7 @@ uint8_t InternalRegisters::Read(uint16_t addr) return (uint8_t)(_controllerData[((addr & 0x0E) - 8) >> 1] >> 8); default: - MessageManager::DisplayMessage("Debug", "Unimplemented register read: " + HexUtilities::ToHex(addr)); + MessageManager::Log("[Debug] Unimplemented register read: " + HexUtilities::ToHex(addr)); return 0; } } @@ -159,7 +160,16 @@ void InternalRegisters::Write(uint16_t addr, uint8_t value) case 0x420D: _enableFastRom = (value & 0x01) != 0; break; default: - MessageManager::DisplayMessage("Debug", "Unimplemented register write: " + HexUtilities::ToHex(addr) + " = " + HexUtilities::ToHex(value)); + MessageManager::Log("[Debug] Unimplemented register write: " + HexUtilities::ToHex(addr) + " = " + HexUtilities::ToHex(value)); break; } } + +void InternalRegisters::Serialize(Serializer &s) +{ + s.Stream( + _multOperand1, _multOperand2, _multOrRemainderResult, _dividend, _divisor, _divResult, _enableAutoJoypadRead, + _enableFastRom, _nmiFlag, _enableNmi, _enableHorizontalIrq, _enableVerticalIrq, _horizontalTimer, + _verticalTimer, _ioPortOutput, _controllerData[0], _controllerData[1], _controllerData[2], _controllerData[3] + ); +} diff --git a/Core/InternalRegisters.h b/Core/InternalRegisters.h index 11971ed6..ca0ffa11 100644 --- a/Core/InternalRegisters.h +++ b/Core/InternalRegisters.h @@ -1,9 +1,10 @@ #pragma once #include "stdafx.h" +#include "../Utilities/ISerializable.h" class Console; -class InternalRegisters +class InternalRegisters : public ISerializable { private: shared_ptr _console; @@ -46,4 +47,6 @@ public: uint8_t Read(uint16_t addr); void Write(uint16_t addr, uint8_t value); + + void Serialize(Serializer &s) override; }; \ No newline at end of file diff --git a/Core/MemoryManager.cpp b/Core/MemoryManager.cpp index 958840b5..b6f54ccd 100644 --- a/Core/MemoryManager.cpp +++ b/Core/MemoryManager.cpp @@ -10,6 +10,7 @@ #include "RamHandler.h" #include "MessageManager.h" #include "DebugTypes.h" +#include "../Utilities/Serializer.h" #include "../Utilities/HexUtilities.h" void MemoryManager::Initialize(shared_ptr console) @@ -165,7 +166,7 @@ uint8_t MemoryManager::Read(uint32_t addr, MemoryOperationType type) } else { //open bus value = _openBus; - MessageManager::DisplayMessage("Debug", "Read - missing handler: $" + HexUtilities::ToHex(addr)); + MessageManager::Log("[Debug] Read - missing handler: $" + HexUtilities::ToHex(addr)); } _console->ProcessCpuRead(addr, value, type); return value; @@ -181,7 +182,7 @@ uint8_t MemoryManager::ReadDma(uint32_t addr) } else { //open bus value = _openBus; - MessageManager::DisplayMessage("Debug", "Read - missing handler: $" + HexUtilities::ToHex(addr)); + MessageManager::Log("[Debug] Read - missing handler: $" + HexUtilities::ToHex(addr)); } _console->ProcessCpuRead(addr, value, MemoryOperationType::DmaRead); return value; @@ -212,7 +213,7 @@ void MemoryManager::Write(uint32_t addr, uint8_t value, MemoryOperationType type if(_handlers[addr >> 12]) { return _handlers[addr >> 12]->Write(addr, value); } else { - MessageManager::DisplayMessage("Debug", "Write - missing handler: $" + HexUtilities::ToHex(addr) + " = " + HexUtilities::ToHex(value)); + MessageManager::Log("[Debug] Write - missing handler: $" + HexUtilities::ToHex(addr) + " = " + HexUtilities::ToHex(value)); } } @@ -224,7 +225,7 @@ void MemoryManager::WriteDma(uint32_t addr, uint8_t value) if(_handlers[addr >> 12]) { return _handlers[addr >> 12]->Write(addr, value); } else { - MessageManager::DisplayMessage("Debug", "Write - missing handler: $" + HexUtilities::ToHex(addr) + " = " + HexUtilities::ToHex(value)); + MessageManager::Log("[Debug] Write - missing handler: $" + HexUtilities::ToHex(addr) + " = " + HexUtilities::ToHex(value)); } } @@ -291,3 +292,9 @@ int MemoryManager::GetRelativeAddress(AddressInfo &address, int32_t cpuAddress) } return -1; } + +void MemoryManager::Serialize(Serializer &s) +{ + s.Stream(_masterClock, _openBus); + s.StreamArray(_workRam, MemoryManager::WorkRamSize); +} diff --git a/Core/MemoryManager.h b/Core/MemoryManager.h index 4209ee54..8548af8d 100644 --- a/Core/MemoryManager.h +++ b/Core/MemoryManager.h @@ -1,7 +1,7 @@ #pragma once #include "stdafx.h" #include "DebugTypes.h" -#include "Ppu.h" +#include "../Utilities/ISerializable.h" class IMemoryHandler; class RegisterHandlerA; @@ -12,7 +12,7 @@ class Console; class Ppu; enum class MemoryOperationType; -class MemoryManager +class MemoryManager : public ISerializable { public: constexpr static uint32_t WorkRamSize = 0x20000; @@ -65,6 +65,8 @@ public: bool IsWorkRam(uint32_t cpuAddress); AddressInfo GetAbsoluteAddress(uint32_t addr); int GetRelativeAddress(AddressInfo &address, int32_t cpuAddress = -1); + + void Serialize(Serializer &s) override; }; template diff --git a/Core/Ppu.cpp b/Core/Ppu.cpp index 95cc9c37..3ccb3f93 100644 --- a/Core/Ppu.cpp +++ b/Core/Ppu.cpp @@ -12,6 +12,7 @@ #include "MessageManager.h" #include "EventType.h" #include "../Utilities/HexUtilities.h" +#include "../Utilities/Serializer.h" Ppu::Ppu(shared_ptr console) { @@ -1270,7 +1271,7 @@ uint8_t Ppu::Read(uint16_t addr) } default: - MessageManager::DisplayMessage("Debug", "Unimplemented register read: " + HexUtilities::ToHex(addr)); + MessageManager::Log("[Debug] Unimplemented register read: " + HexUtilities::ToHex(addr)); break; } @@ -1600,11 +1601,50 @@ void Ppu::Write(uint32_t addr, uint8_t value) break; default: - MessageManager::DisplayMessage("Debug", "Unimplemented register write: " + HexUtilities::ToHex(addr) + " = " + HexUtilities::ToHex(value)); + MessageManager::Log("[Debug] Unimplemented register write: " + HexUtilities::ToHex(addr) + " = " + HexUtilities::ToHex(value)); break; } } +void Ppu::Serialize(Serializer &s) +{ + s.Stream( + _forcedVblank, _screenBrightness, _cycle, _scanline, _frameCount, _drawStartX, _drawEndX, _irqDelay, _bgMode, + _mode1Bg3Priority, _mainScreenLayers, _subScreenLayers, _vramAddress, _vramIncrementValue, _vramAddressRemapping, + _vramAddrIncrementOnSecondReg, _vramReadBuffer, _ppu1OpenBus, _ppu2OpenBus, _cgramAddress, _mosaicSize, _mosaicEnabled, + _mosaicStartScanline, _oamMode, _oamBaseAddress, _oamAddressOffset, _oamRamAddress, _enableOamPriority, + _internalOamAddress, _oamWriteBuffer, _timeOver, _rangeOver, _hiResMode, _screenInterlace, _objInterlace, + _overscanMode, _directColorMode, _colorMathClipMode, _colorMathPreventMode, _colorMathAddSubscreen, _colorMathEnabled, + _colorMathSubstractMode, _colorMathHalveResult, _fixedColor, _hvScrollLatchValue, _hScrollLatchValue, + _horizontalLocation, _horizontalLocToggle, _verticalLocation, _verticalLocationToggle, _locationLatched, + _maskLogic[0], _maskLogic[1], _maskLogic[2], _maskLogic[3], _maskLogic[4], _maskLogic[5], + _windowMaskMain[0], _windowMaskMain[1], _windowMaskMain[2], _windowMaskMain[3], _windowMaskMain[4], + _windowMaskSub[0], _windowMaskSub[1], _windowMaskSub[2], _windowMaskSub[3], _windowMaskSub[4], + _mode7.CenterX, _mode7.CenterY, _mode7.ExtBgEnabled, _mode7.FillWithTile0, _mode7.HorizontalMirroring, + _mode7.HScroll, _mode7.LargeMap, _mode7.Matrix[0], _mode7.Matrix[1], _mode7.Matrix[2], _mode7.Matrix[3], + _mode7.ValueLatch, _mode7.VerticalMirroring, _mode7.VScroll + ); + + for(int i = 0; i < 4; i++) { + s.Stream( + _layerConfig[i].ChrAddress, _layerConfig[i].DoubleHeight, _layerConfig[i].DoubleWidth, _layerConfig[i].HScroll, + _layerConfig[i].LargeTiles, _layerConfig[i].TilemapAddress, _layerConfig[i].VScroll + ); + } + + for(int i = 0; i < 2; i++) { + s.Stream( + _window[i].ActiveLayers[0], _window[i].ActiveLayers[1], _window[i].ActiveLayers[2], _window[i].ActiveLayers[3], _window[i].ActiveLayers[4], _window[i].ActiveLayers[5], + _window[i].InvertedLayers[0], _window[i].InvertedLayers[1], _window[i].InvertedLayers[2], _window[i].InvertedLayers[3], _window[i].InvertedLayers[4], _window[i].InvertedLayers[5], + _window[i].Left, _window[i].Right + ); + } + + s.StreamArray(_vram, Ppu::VideoRamSize); + s.StreamArray(_oamRam, Ppu::SpriteRamSize); + s.StreamArray(_cgram, Ppu::CgRamSize); +} + /* Everything below this point is used to select the proper arguments for templates */ template void Ppu::RenderTilemap() diff --git a/Core/Ppu.h b/Core/Ppu.h index 516436c6..6592569c 100644 --- a/Core/Ppu.h +++ b/Core/Ppu.h @@ -1,6 +1,7 @@ #pragma once #include "stdafx.h" #include "PpuTypes.h" +#include "../Utilities/ISerializable.h" class Console; class InternalRegisters; @@ -20,7 +21,7 @@ struct SpriteInfo uint8_t LargeSprite; }; -class Ppu +class Ppu : public ISerializable { public: constexpr static uint32_t SpriteRamSize = 544; @@ -242,4 +243,6 @@ public: uint8_t Read(uint16_t addr); void Write(uint32_t addr, uint8_t value); + + void Serialize(Serializer &s) override; }; \ No newline at end of file diff --git a/Core/RegisterHandlerB.cpp b/Core/RegisterHandlerB.cpp new file mode 100644 index 00000000..3e8d7c8c --- /dev/null +++ b/Core/RegisterHandlerB.cpp @@ -0,0 +1,69 @@ +#include "stdafx.h" +#include "RegisterHandlerB.h" +#include "Console.h" +#include "Ppu.h" +#include "Spc.h" +#include "../Utilities/Serializer.h" + +RegisterHandlerB::RegisterHandlerB(Console * console, Ppu * ppu, Spc * spc, uint8_t * workRam) +{ + _console = console; + _ppu = ppu; + _spc = spc; + _workRam = workRam; + _wramPosition = 0; + _memoryType = SnesMemoryType::Register; +} + +uint8_t RegisterHandlerB::Read(uint32_t addr) +{ + addr &= 0xFFFF; + if(addr >= 0x2140 && addr <= 0x217F) { + return _spc->Read(addr & 0x03); + } else if(addr == 0x2180) { + uint8_t value = _workRam[_wramPosition]; + _console->ProcessWorkRamRead(_wramPosition, value); + _wramPosition = (_wramPosition + 1) & 0x1FFFF; + return value; + } else { + return _ppu->Read(addr); + } +} + +uint8_t RegisterHandlerB::Peek(uint32_t addr) +{ + //Avoid side effects for now + return 0; +} + +void RegisterHandlerB::Write(uint32_t addr, uint8_t value) +{ + addr &= 0xFFFF; + if(addr >= 0x2140 && addr <= 0x217F) { + return _spc->Write(addr & 0x03, value); + } if(addr >= 0x2180 && addr <= 0x2183) { + switch(addr & 0xFFFF) { + case 0x2180: + _console->ProcessWorkRamWrite(_wramPosition, value); + _workRam[_wramPosition] = value; + _wramPosition = (_wramPosition + 1) & 0x1FFFF; + break; + + case 0x2181: _wramPosition = (_wramPosition & 0x1FF00) | value; break; + case 0x2182: _wramPosition = (_wramPosition & 0x100FF) | (value << 8); break; + case 0x2183: _wramPosition = (_wramPosition & 0xFFFF) | ((value & 0x01) << 16); break; + } + } else { + _ppu->Write(addr, value); + } +} + +AddressInfo RegisterHandlerB::GetAbsoluteAddress(uint32_t address) +{ + return { -1, SnesMemoryType::CpuMemory }; +} + +void RegisterHandlerB::Serialize(Serializer &s) +{ + s.Stream(_wramPosition); +} diff --git a/Core/RegisterHandlerB.h b/Core/RegisterHandlerB.h index f5948d42..88f138d7 100644 --- a/Core/RegisterHandlerB.h +++ b/Core/RegisterHandlerB.h @@ -1,75 +1,30 @@ #pragma once #include "stdafx.h" #include "IMemoryHandler.h" -#include "Console.h" -#include "Ppu.h" -#include "Spc.h" +#include "../Utilities/ISerializable.h" -class RegisterHandlerB : public IMemoryHandler +class Console; +class Ppu; +class Spc; + +class RegisterHandlerB : public IMemoryHandler, public ISerializable { private: Console *_console; Ppu *_ppu; Spc *_spc; + uint8_t *_workRam; uint32_t _wramPosition; public: - RegisterHandlerB(Console *console, Ppu *ppu, Spc *spc, uint8_t *workRam) - { - _console = console; - _ppu = ppu; - _spc = spc; - _workRam = workRam; - _wramPosition = 0; - _memoryType = SnesMemoryType::Register; - } + RegisterHandlerB(Console *console, Ppu *ppu, Spc *spc, uint8_t *workRam); - uint8_t Read(uint32_t addr) override - { - addr &= 0xFFFF; - if(addr >= 0x2140 && addr <= 0x217F) { - return _spc->Read(addr & 0x03); - } else if(addr == 0x2180) { - uint8_t value = _workRam[_wramPosition]; - _console->ProcessWorkRamRead(_wramPosition, value); - _wramPosition = (_wramPosition + 1) & 0x1FFFF; - return value; - } else { - return _ppu->Read(addr); - } - } + uint8_t Read(uint32_t addr) override; + uint8_t Peek(uint32_t addr) override; + void Write(uint32_t addr, uint8_t value) override; - uint8_t Peek(uint32_t addr) override - { - //Avoid side effects for now - return 0; - } + AddressInfo GetAbsoluteAddress(uint32_t address) override; - void Write(uint32_t addr, uint8_t value) override - { - addr &= 0xFFFF; - if(addr >= 0x2140 && addr <= 0x217F) { - return _spc->Write(addr & 0x03, value); - } if(addr >= 0x2180 && addr <= 0x2183) { - switch(addr & 0xFFFF) { - case 0x2180: - _console->ProcessWorkRamWrite(_wramPosition, value); - _workRam[_wramPosition] = value; - _wramPosition = (_wramPosition + 1) & 0x1FFFF; - break; - - case 0x2181: _wramPosition = (_wramPosition & 0x1FF00) | value; break; - case 0x2182: _wramPosition = (_wramPosition & 0x100FF) | (value << 8); break; - case 0x2183: _wramPosition = (_wramPosition & 0xFFFF) | ((value & 0x01) << 16); break; - } - } else { - _ppu->Write(addr, value); - } - } - - AddressInfo GetAbsoluteAddress(uint32_t address) override - { - return { -1, SnesMemoryType::CpuMemory }; - } + void Serialize(Serializer &s) override; }; \ No newline at end of file diff --git a/Core/SPC_DSP.cpp b/Core/SPC_DSP.cpp index 456b3068..679a415d 100644 --- a/Core/SPC_DSP.cpp +++ b/Core/SPC_DSP.cpp @@ -1,4 +1,5 @@ #include "stdafx.h" + // snes_spc 0.9.0. http://www.slack.net/~ant/ #include "SPC_DSP.h" @@ -27,7 +28,6 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #error "Requires that int type have at least 32 bits" #endif - // TODO: add to blargg_endian.h #define GET_LE16SA( addr ) ((BOOST::int16_t) GET_LE16( addr )) #define GET_LE16A( addr ) GET_LE16( addr ) @@ -90,555 +90,725 @@ void SPC_DSP::set_output( sample_t* out, int size ) // Volume registers and efb are signed! Easy to forget int8_t cast. // Prefixes are to avoid accidental use of locals with same names. -// Interleved gauss table (to improve cache coherency) -// interleved_gauss [i] = gauss [(i & 1) * 256 + 255 - (i >> 1 & 0xFF)] -static short const interleved_gauss [512] = +// Gaussian interpolation + +static short const gauss [512] = { - 370,1305, 366,1305, 362,1304, 358,1304, 354,1304, 351,1304, 347,1304, 343,1303, - 339,1303, 336,1303, 332,1302, 328,1302, 325,1301, 321,1300, 318,1300, 314,1299, - 311,1298, 307,1297, 304,1297, 300,1296, 297,1295, 293,1294, 290,1293, 286,1292, - 283,1291, 280,1290, 276,1288, 273,1287, 270,1286, 267,1284, 263,1283, 260,1282, - 257,1280, 254,1279, 251,1277, 248,1275, 245,1274, 242,1272, 239,1270, 236,1269, - 233,1267, 230,1265, 227,1263, 224,1261, 221,1259, 218,1257, 215,1255, 212,1253, - 210,1251, 207,1248, 204,1246, 201,1244, 199,1241, 196,1239, 193,1237, 191,1234, - 188,1232, 186,1229, 183,1227, 180,1224, 178,1221, 175,1219, 173,1216, 171,1213, - 168,1210, 166,1207, 163,1205, 161,1202, 159,1199, 156,1196, 154,1193, 152,1190, - 150,1186, 147,1183, 145,1180, 143,1177, 141,1174, 139,1170, 137,1167, 134,1164, - 132,1160, 130,1157, 128,1153, 126,1150, 124,1146, 122,1143, 120,1139, 118,1136, - 117,1132, 115,1128, 113,1125, 111,1121, 109,1117, 107,1113, 106,1109, 104,1106, - 102,1102, 100,1098, 99,1094, 97,1090, 95,1086, 94,1082, 92,1078, 90,1074, - 89,1070, 87,1066, 86,1061, 84,1057, 83,1053, 81,1049, 80,1045, 78,1040, - 77,1036, 76,1032, 74,1027, 73,1023, 71,1019, 70,1014, 69,1010, 67,1005, - 66,1001, 65, 997, 64, 992, 62, 988, 61, 983, 60, 978, 59, 974, 58, 969, - 56, 965, 55, 960, 54, 955, 53, 951, 52, 946, 51, 941, 50, 937, 49, 932, - 48, 927, 47, 923, 46, 918, 45, 913, 44, 908, 43, 904, 42, 899, 41, 894, - 40, 889, 39, 884, 38, 880, 37, 875, 36, 870, 36, 865, 35, 860, 34, 855, - 33, 851, 32, 846, 32, 841, 31, 836, 30, 831, 29, 826, 29, 821, 28, 816, - 27, 811, 27, 806, 26, 802, 25, 797, 24, 792, 24, 787, 23, 782, 23, 777, - 22, 772, 21, 767, 21, 762, 20, 757, 20, 752, 19, 747, 19, 742, 18, 737, - 17, 732, 17, 728, 16, 723, 16, 718, 15, 713, 15, 708, 15, 703, 14, 698, - 14, 693, 13, 688, 13, 683, 12, 678, 12, 674, 11, 669, 11, 664, 11, 659, - 10, 654, 10, 649, 10, 644, 9, 640, 9, 635, 9, 630, 8, 625, 8, 620, - 8, 615, 7, 611, 7, 606, 7, 601, 6, 596, 6, 592, 6, 587, 6, 582, - 5, 577, 5, 573, 5, 568, 5, 563, 4, 559, 4, 554, 4, 550, 4, 545, - 4, 540, 3, 536, 3, 531, 3, 527, 3, 522, 3, 517, 2, 513, 2, 508, - 2, 504, 2, 499, 2, 495, 2, 491, 2, 486, 1, 482, 1, 477, 1, 473, - 1, 469, 1, 464, 1, 460, 1, 456, 1, 451, 1, 447, 1, 443, 1, 439, - 0, 434, 0, 430, 0, 426, 0, 422, 0, 418, 0, 414, 0, 410, 0, 405, - 0, 401, 0, 397, 0, 393, 0, 389, 0, 385, 0, 381, 0, 378, 0, 374, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, + 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, + 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, + 11, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 15, 16, 16, 17, 17, + 18, 19, 19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 25, 26, 27, 27, + 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 36, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 58, 59, 60, 61, 62, 64, 65, 66, 67, 69, 70, 71, 73, 74, 76, 77, + 78, 80, 81, 83, 84, 86, 87, 89, 90, 92, 94, 95, 97, 99, 100, 102, + 104, 106, 107, 109, 111, 113, 115, 117, 118, 120, 122, 124, 126, 128, 130, 132, + 134, 137, 139, 141, 143, 145, 147, 150, 152, 154, 156, 159, 161, 163, 166, 168, + 171, 173, 175, 178, 180, 183, 186, 188, 191, 193, 196, 199, 201, 204, 207, 210, + 212, 215, 218, 221, 224, 227, 230, 233, 236, 239, 242, 245, 248, 251, 254, 257, + 260, 263, 267, 270, 273, 276, 280, 283, 286, 290, 293, 297, 300, 304, 307, 311, + 314, 318, 321, 325, 328, 332, 336, 339, 343, 347, 351, 354, 358, 362, 366, 370, + 374, 378, 381, 385, 389, 393, 397, 401, 405, 410, 414, 418, 422, 426, 430, 434, + 439, 443, 447, 451, 456, 460, 464, 469, 473, 477, 482, 486, 491, 495, 499, 504, + 508, 513, 517, 522, 527, 531, 536, 540, 545, 550, 554, 559, 563, 568, 573, 577, + 582, 587, 592, 596, 601, 606, 611, 615, 620, 625, 630, 635, 640, 644, 649, 654, + 659, 664, 669, 674, 678, 683, 688, 693, 698, 703, 708, 713, 718, 723, 728, 732, + 737, 742, 747, 752, 757, 762, 767, 772, 777, 782, 787, 792, 797, 802, 806, 811, + 816, 821, 826, 831, 836, 841, 846, 851, 855, 860, 865, 870, 875, 880, 884, 889, + 894, 899, 904, 908, 913, 918, 923, 927, 932, 937, 941, 946, 951, 955, 960, 965, + 969, 974, 978, 983, 988, 992, 997,1001,1005,1010,1014,1019,1023,1027,1032,1036, +1040,1045,1049,1053,1057,1061,1066,1070,1074,1078,1082,1086,1090,1094,1098,1102, +1106,1109,1113,1117,1121,1125,1128,1132,1136,1139,1143,1146,1150,1153,1157,1160, +1164,1167,1170,1174,1177,1180,1183,1186,1190,1193,1196,1199,1202,1205,1207,1210, +1213,1216,1219,1221,1224,1227,1229,1232,1234,1237,1239,1241,1244,1246,1248,1251, +1253,1255,1257,1259,1261,1263,1265,1267,1269,1270,1272,1274,1275,1277,1279,1280, +1282,1283,1284,1286,1287,1288,1290,1291,1292,1293,1294,1295,1296,1297,1297,1298, +1299,1300,1300,1301,1302,1302,1303,1303,1303,1304,1304,1304,1304,1304,1305,1305, }; +inline int SPC_DSP::interpolate( voice_t const* v ) +{ + // Make pointers into gaussian based on fractional position between samples + int offset = v->interp_pos >> 4 & 0xFF; + short const* fwd = gauss + 255 - offset; + short const* rev = gauss + offset; // mirror left half of gaussian + + int const* in = &v->buf [(v->interp_pos >> 12) + v->buf_pos]; + int out; + out = (fwd [ 0] * in [0]) >> 11; + out += (fwd [256] * in [1]) >> 11; + out += (rev [256] * in [2]) >> 11; + out = (int16_t) out; + out += (rev [ 0] * in [3]) >> 11; + + CLAMP16( out ); + out &= ~1; + return out; +} + //// Counters -#define RATE( rate, div )\ - (rate >= div ? rate / div * 8 - 1 : rate - 1) +int const simple_counter_range = 2048 * 5 * 3; // 30720 -static unsigned const counter_mask [32] = +static unsigned const counter_rates [32] = { - RATE( 2,2), RATE(2048,4), RATE(1536,3), - RATE(1280,5), RATE(1024,4), RATE( 768,3), - RATE( 640,5), RATE( 512,4), RATE( 384,3), - RATE( 320,5), RATE( 256,4), RATE( 192,3), - RATE( 160,5), RATE( 128,4), RATE( 96,3), - RATE( 80,5), RATE( 64,4), RATE( 48,3), - RATE( 40,5), RATE( 32,4), RATE( 24,3), - RATE( 20,5), RATE( 16,4), RATE( 12,3), - RATE( 10,5), RATE( 8,4), RATE( 6,3), - RATE( 5,5), RATE( 4,4), RATE( 3,3), - RATE( 2,4), - RATE( 1,4) + simple_counter_range + 1, // never fires + 2048, 1536, + 1280, 1024, 768, + 640, 512, 384, + 320, 256, 192, + 160, 128, 96, + 80, 64, 48, + 40, 32, 24, + 20, 16, 12, + 10, 8, 6, + 5, 4, 3, + 2, + 1 +}; + +static unsigned const counter_offsets [32] = +{ + 1, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 0, + 0 }; -#undef RATE inline void SPC_DSP::init_counter() { - // counters start out with this synchronization - m.counters [0] = 1; - m.counters [1] = 0; - m.counters [2] = (unsigned int)-0x20; - m.counters [3] = 0x0B; - - int n = 2; - for ( int i = 1; i < 32; i++ ) + m.counter = 0; +} + +inline void SPC_DSP::run_counters() +{ + if ( --m.counter < 0 ) + m.counter = simple_counter_range - 1; +} + +inline unsigned SPC_DSP::read_counter( int rate ) +{ + return ((unsigned) m.counter + counter_offsets [rate]) % counter_rates [rate]; +} + + +//// Envelope + +inline void SPC_DSP::run_envelope( voice_t* const v ) +{ + int env = v->env; + if ( v->env_mode == env_release ) // 60% { - m.counter_select [i] = &m.counters [n]; - if ( !--n ) - n = 3; + if ( (env -= 0x8) < 0 ) + env = 0; + v->env = env; } - m.counter_select [ 0] = &m.counters [0]; - m.counter_select [30] = &m.counters [2]; -} - -inline void SPC_DSP::run_counter( int i ) -{ - int n = m.counters [i]; - if ( !(n-- & 7) ) - n -= 6 - i; - m.counters [i] = n; -} - -#define READ_COUNTER( rate )\ - (*m.counter_select [rate] & counter_mask [rate]) - - -//// Emulation - -void SPC_DSP::run( int clock_count ) -{ - int new_phase = m.phase + clock_count; - int count = new_phase >> 5; - m.phase = new_phase & 31; - if ( !count ) - return; - - uint8_t* const ram = m.ram; - uint8_t const* const dir = &ram [REG(dir) * 0x100]; - int const slow_gaussian = (REG(pmon) >> 1) | REG(non); - int const noise_rate = REG(flg) & 0x1F; - - // Global volume - int mvoll = (int8_t) REG(mvoll); - int mvolr = (int8_t) REG(mvolr); - if ( mvoll * mvolr < m.surround_threshold ) - mvoll = -mvoll; // eliminate surround - - do + else { - // KON/KOFF reading - if ( (m.every_other_sample ^= 1) != 0 ) + int rate; + int env_data = VREG(v->regs,adsr1); + if ( m.t_adsr0 & 0x80 ) // 99% ADSR { - m.new_kon &= ~m.kon; - m.kon = m.new_kon; - m.t_koff = REG(koff); + if ( v->env_mode >= env_decay ) // 99% + { + env--; + env -= env >> 8; + rate = env_data & 0x1F; + if ( v->env_mode == env_decay ) // 1% + rate = (m.t_adsr0 >> 3 & 0x0E) + 0x10; + } + else // env_attack + { + rate = (m.t_adsr0 & 0x0F) * 2 + 1; + env += rate < 31 ? 0x20 : 0x400; + } + } + else // GAIN + { + int mode; + env_data = VREG(v->regs,gain); + mode = env_data >> 5; + if ( mode < 4 ) // direct + { + env = env_data * 0x10; + rate = 31; + } + else + { + rate = env_data & 0x1F; + if ( mode == 4 ) // 4: linear decrease + { + env -= 0x20; + } + else if ( mode < 6 ) // 5: exponential decrease + { + env--; + env -= env >> 8; + } + else // 6,7: linear increase + { + env += 0x20; + if ( mode > 6 && (unsigned) v->hidden_env >= 0x600 ) + env += 0x8 - 0x20; // 7: two-slope linear increase + } + } } - run_counter( 1 ); - run_counter( 2 ); - run_counter( 3 ); + // Sustain level + if ( (env >> 8) == (env_data >> 5) && v->env_mode == env_decay ) + v->env_mode = env_sustain; + + v->hidden_env = env; + + // unsigned cast because linear decrease going negative also triggers this + if ( (unsigned) env > 0x7FF ) + { + env = (env < 0 ? 0 : 0x7FF); + if ( v->env_mode == env_attack ) + v->env_mode = env_decay; + } + + if ( !read_counter( rate ) ) + v->env = env; // nothing else is controlled by the counter + } +} + + +//// BRR Decoding + +inline void SPC_DSP::decode_brr( voice_t* v ) +{ + // Arrange the four input nybbles in 0xABCD order for easy decoding + int nybbles = m.t_brr_byte * 0x100 + m.ram [(v->brr_addr + v->brr_offset + 1) & 0xFFFF]; + + int const header = m.t_brr_header; + + // Write to next four samples in circular buffer + int* pos = &v->buf [v->buf_pos]; + int* end; + if ( (v->buf_pos += 4) >= brr_buf_size ) + v->buf_pos = 0; + + // Decode four samples + for ( end = pos + 4; pos < end; pos++, nybbles <<= 4 ) + { + // Extract nybble and sign-extend + int s = (int16_t) nybbles >> 12; + + // Shift sample based on header + int const shift = header >> 4; + s = (s << shift) >> 1; + if ( shift >= 0xD ) // handle invalid range + s = (s >> 25) << 11; // same as: s = (s < 0 ? -0x800 : 0) + + // Apply IIR filter (8 is the most commonly used) + int const filter = header & 0x0C; + int const p1 = pos [brr_buf_size - 1]; + int const p2 = pos [brr_buf_size - 2] >> 1; + if ( filter >= 8 ) + { + s += p1; + s -= p2; + if ( filter == 8 ) // s += p1 * 0.953125 - p2 * 0.46875 + { + s += p2 >> 4; + s += (p1 * -3) >> 6; + } + else // s += p1 * 0.8984375 - p2 * 0.40625 + { + s += (p1 * -13) >> 7; + s += (p2 * 3) >> 4; + } + } + else if ( filter ) // s += p1 * 0.46875 + { + s += p1 >> 1; + s += (-p1) >> 5; + } + + // Adjust and write sample + CLAMP16( s ); + s = (int16_t) (s * 2); + pos [brr_buf_size] = pos [0] = s; // second copy simplifies wrap-around + } +} + + +//// Misc + +#define MISC_CLOCK( n ) inline void SPC_DSP::misc_##n() + +MISC_CLOCK( 27 ) +{ + m.t_pmon = REG(pmon) & 0xFE; // voice 0 doesn't support PMON +} +MISC_CLOCK( 28 ) +{ + m.t_non = REG(non); + m.t_eon = REG(eon); + m.t_dir = REG(dir); +} +MISC_CLOCK( 29 ) +{ + if ( (m.every_other_sample ^= 1) != 0 ) + m.new_kon &= ~m.kon; // clears KON 63 clocks after it was last read +} +MISC_CLOCK( 30 ) +{ + if ( m.every_other_sample ) + { + m.kon = m.new_kon; + m.t_koff = REG(koff) | m.mute_mask; + } + + run_counters(); + + // Noise + if ( !read_counter( REG(flg) & 0x1F ) ) + { + int feedback = (m.noise << 13) ^ (m.noise << 14); + m.noise = (feedback & 0x4000) ^ (m.noise >> 1); + } +} + + +//// Voices + +#define VOICE_CLOCK( n ) void SPC_DSP::voice_##n( voice_t* const v ) + +inline VOICE_CLOCK( V1 ) +{ + m.t_dir_addr = m.t_dir * 0x100 + m.t_srcn * 4; + m.t_srcn = VREG(v->regs,srcn); +} +inline VOICE_CLOCK( V2 ) +{ + // Read sample pointer (ignored if not needed) + uint8_t const* entry = &m.ram [m.t_dir_addr]; + if ( !v->kon_delay ) + entry += 2; + m.t_brr_next_addr = GET_LE16A( entry ); + + m.t_adsr0 = VREG(v->regs,adsr0); + + // Read pitch, spread over two clocks + m.t_pitch = VREG(v->regs,pitchl); +} +inline VOICE_CLOCK( V3a ) +{ + m.t_pitch += (VREG(v->regs,pitchh) & 0x3F) << 8; +} +inline VOICE_CLOCK( V3b ) +{ + // Read BRR header and byte + m.t_brr_byte = m.ram [(v->brr_addr + v->brr_offset) & 0xFFFF]; + m.t_brr_header = m.ram [v->brr_addr]; // brr_addr doesn't need masking +} +VOICE_CLOCK( V3c ) +{ + // Pitch modulation using previous voice's output + if ( m.t_pmon & v->vbit ) + m.t_pitch += ((m.t_output >> 5) * m.t_pitch) >> 10; + + if ( v->kon_delay ) + { + // Get ready to start BRR decoding on next sample + if ( v->kon_delay == 5 ) + { + v->brr_addr = m.t_brr_next_addr; + v->brr_offset = 1; + v->buf_pos = 0; + m.t_brr_header = 0; // header is ignored on this sample + m.kon_check = true; + } + + // Envelope is never run during KON + v->env = 0; + v->hidden_env = 0; + + // Disable BRR decoding until last three samples + v->interp_pos = 0; + if ( --v->kon_delay & 3 ) + v->interp_pos = 0x4000; + + // Pitch is never added during KON + m.t_pitch = 0; + } + + // Gaussian interpolation + { + int output = interpolate( v ); // Noise - if ( !READ_COUNTER( noise_rate ) ) + if ( m.t_non & v->vbit ) + output = (int16_t) (m.noise * 2); + + // Apply envelope + m.t_output = (output * v->env) >> 11 & ~1; + v->t_envx_out = (uint8_t) (v->env >> 4); + } + + // Immediate silence due to end of sample or soft reset + if ( REG(flg) & 0x80 || (m.t_brr_header & 3) == 1 ) + { + v->env_mode = env_release; + v->env = 0; + } + + if ( m.every_other_sample ) + { + // KOFF + if ( m.t_koff & v->vbit ) + v->env_mode = env_release; + + // KON + if ( m.kon & v->vbit ) { - int feedback = (m.noise << 13) ^ (m.noise << 14); - m.noise = (feedback & 0x4000) ^ (m.noise >> 1); + v->kon_delay = 5; + v->env_mode = env_attack; } + } + + // Run envelope for next sample + if ( !v->kon_delay ) + run_envelope( v ); +} +inline void SPC_DSP::voice_output( voice_t const* v, int ch ) +{ + // Apply left/right volume + int amp = (m.t_output * (int8_t) VREG(v->regs,voll + ch)) >> 7; + + // Add to output total + m.t_main_out [ch] += amp; + CLAMP16( m.t_main_out [ch] ); + + // Optionally add to echo total + if ( m.t_eon & v->vbit ) + { + m.t_echo_out [ch] += amp; + CLAMP16( m.t_echo_out [ch] ); + } +} +VOICE_CLOCK( V4 ) +{ + // Decode BRR + m.t_looped = 0; + if ( v->interp_pos >= 0x4000 ) + { + decode_brr( v ); - // Voices - int pmon_input = 0; - int main_out_l = 0; - int main_out_r = 0; - int echo_out_l = 0; - int echo_out_r = 0; - voice_t* v = m.voices; - uint8_t* v_regs = m.regs; - int vbit = 1; - do + if ( (v->brr_offset += 2) >= brr_block_size ) { - #define SAMPLE_PTR(i) GET_LE16A( &dir [VREG(v_regs,srcn) * 4 + i * 2] ) - - int brr_header = ram [v->brr_addr]; - int kon_delay = v->kon_delay; - - // Pitch - int pitch = GET_LE16A( &VREG(v_regs,pitchl) ) & 0x3FFF; - if ( REG(pmon) & vbit ) - pitch += ((pmon_input >> 5) * pitch) >> 10; - - // KON phases - if ( --kon_delay >= 0 ) + // Start decoding next BRR block + assert( v->brr_offset == brr_block_size ); + v->brr_addr = (v->brr_addr + brr_block_size) & 0xFFFF; + if ( m.t_brr_header & 1 ) { - v->kon_delay = kon_delay; - - // Get ready to start BRR decoding on next sample - if ( kon_delay == 4 ) - { - v->brr_addr = SAMPLE_PTR( 0 ); - v->brr_offset = 1; - v->buf_pos = v->buf; - brr_header = 0; // header is ignored on this sample - } - - // Envelope is never run during KON - v->env = 0; - v->hidden_env = 0; - - // Disable BRR decoding until last three samples - v->interp_pos = (kon_delay & 3 ? 0x4000 : 0); - - // Pitch is never added during KON - pitch = 0; + v->brr_addr = m.t_brr_next_addr; + m.t_looped = v->vbit; } - - int env = v->env; - - // Gaussian interpolation - { - int output = 0; - VREG(v_regs,envx) = (uint8_t) (env >> 4); - if ( env ) - { - // Make pointers into gaussian based on fractional position between samples - int offset = (unsigned) v->interp_pos >> 3 & 0x1FE; - short const* fwd = interleved_gauss + offset; - short const* rev = interleved_gauss + 510 - offset; // mirror left half of gaussian - - int const* in = &v->buf_pos [(unsigned) v->interp_pos >> 12]; - - if ( !(slow_gaussian & vbit) ) // 99% - { - // Faster approximation when exact sample value isn't necessary for pitch mod - output = (fwd [0] * in [0] + - fwd [1] * in [1] + - rev [1] * in [2] + - rev [0] * in [3]) >> 11; - output = (output * env) >> 11; - } - else - { - output = (int16_t) (m.noise * 2); - if ( !(REG(non) & vbit) ) - { - output = (fwd [0] * in [0]) >> 11; - output += (fwd [1] * in [1]) >> 11; - output += (rev [1] * in [2]) >> 11; - output = (int16_t) output; - output += (rev [0] * in [3]) >> 11; - - CLAMP16( output ); - output &= ~1; - } - output = (output * env) >> 11 & ~1; - } - - // Output - int l = output * v->volume [0]; - int r = output * v->volume [1]; - - main_out_l += l; - main_out_r += r; - - if ( REG(eon) & vbit ) - { - echo_out_l += l; - echo_out_r += r; - } - } - - pmon_input = output; - VREG(v_regs,outx) = (uint8_t) (output >> 8); - } - - // Soft reset or end of sample - if ( REG(flg) & 0x80 || (brr_header & 3) == 1 ) - { - v->env_mode = env_release; - env = 0; - } - - if ( m.every_other_sample ) - { - // KOFF - if ( m.t_koff & vbit ) - v->env_mode = env_release; - - // KON - if ( m.kon & vbit ) - { - v->kon_delay = 5; - v->env_mode = env_attack; - REG(endx) &= ~vbit; - } - } - - // Envelope - if ( !v->kon_delay ) - { - if ( v->env_mode == env_release ) // 97% - { - env -= 0x8; - v->env = env; - if ( env <= 0 ) - { - v->env = 0; - goto skip_brr; // no BRR decoding for you! - } - } - else // 3% - { - int rate; - int const adsr0 = VREG(v_regs,adsr0); - int env_data = VREG(v_regs,adsr1); - if ( adsr0 >= 0x80 ) // 97% ADSR - { - if ( v->env_mode > env_decay ) // 89% - { - env--; - env -= env >> 8; - rate = env_data & 0x1F; - - // optimized handling - v->hidden_env = env; - if ( READ_COUNTER( rate ) ) - goto exit_env; - v->env = env; - goto exit_env; - } - else if ( v->env_mode == env_decay ) - { - env--; - env -= env >> 8; - rate = (adsr0 >> 3 & 0x0E) + 0x10; - } - else // env_attack - { - rate = (adsr0 & 0x0F) * 2 + 1; - env += rate < 31 ? 0x20 : 0x400; - } - } - else // GAIN - { - int mode; - env_data = VREG(v_regs,gain); - mode = env_data >> 5; - if ( mode < 4 ) // direct - { - env = env_data * 0x10; - rate = 31; - } - else - { - rate = env_data & 0x1F; - if ( mode == 4 ) // 4: linear decrease - { - env -= 0x20; - } - else if ( mode < 6 ) // 5: exponential decrease - { - env--; - env -= env >> 8; - } - else // 6,7: linear increase - { - env += 0x20; - if ( mode > 6 && (unsigned) v->hidden_env >= 0x600 ) - env += 0x8 - 0x20; // 7: two-slope linear increase - } - } - } - - // Sustain level - if ( (env >> 8) == (env_data >> 5) && v->env_mode == env_decay ) - v->env_mode = env_sustain; - - v->hidden_env = env; - - // unsigned cast because linear decrease going negative also triggers this - if ( (unsigned) env > 0x7FF ) - { - env = (env < 0 ? 0 : 0x7FF); - if ( v->env_mode == env_attack ) - v->env_mode = env_decay; - } - - if ( !READ_COUNTER( rate ) ) - v->env = env; // nothing else is controlled by the counter - } - } - exit_env: - - { - // Apply pitch - int old_pos = v->interp_pos; - int interp_pos = (old_pos & 0x3FFF) + pitch; - if ( interp_pos > 0x7FFF ) - interp_pos = 0x7FFF; - v->interp_pos = interp_pos; - - // BRR decode if necessary - if ( old_pos >= 0x4000 ) - { - // Arrange the four input nybbles in 0xABCD order for easy decoding - int nybbles = ram [(v->brr_addr + v->brr_offset) & 0xFFFF] * 0x100 + - ram [(v->brr_addr + v->brr_offset + 1) & 0xFFFF]; - - // Advance read position - int const brr_block_size = 9; - int brr_offset = v->brr_offset; - if ( (brr_offset += 2) >= brr_block_size ) - { - // Next BRR block - int brr_addr = (v->brr_addr + brr_block_size) & 0xFFFF; - assert( brr_offset == brr_block_size ); - if ( brr_header & 1 ) - { - brr_addr = SAMPLE_PTR( 1 ); - if ( !v->kon_delay ) - REG(endx) |= vbit; - } - v->brr_addr = brr_addr; - brr_offset = 1; - } - v->brr_offset = brr_offset; - - // Decode - - // 0: >>1 1: <<0 2: <<1 ... 12: <<11 13-15: >>4 <<11 - static unsigned char const shifts [16 * 2] = { - 13,12,12,12,12,12,12,12,12,12,12, 12, 12, 16, 16, 16, - 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 11, 11, 11 - }; - int const scale = brr_header >> 4; - int const right_shift = shifts [scale]; - int const left_shift = shifts [scale + 16]; - - // Write to next four samples in circular buffer - int* pos = v->buf_pos; - int* end; - - // Decode four samples - for ( end = pos + 4; pos < end; pos++, nybbles <<= 4 ) - { - // Extract upper nybble and scale appropriately - int s = ((int16_t) nybbles >> right_shift) << left_shift; - - // Apply IIR filter (8 is the most commonly used) - int const filter = brr_header & 0x0C; - int const p1 = pos [brr_buf_size - 1]; - int const p2 = pos [brr_buf_size - 2] >> 1; - if ( filter >= 8 ) - { - s += p1; - s -= p2; - if ( filter == 8 ) // s += p1 * 0.953125 - p2 * 0.46875 - { - s += p2 >> 4; - s += (p1 * -3) >> 6; - } - else // s += p1 * 0.8984375 - p2 * 0.40625 - { - s += (p1 * -13) >> 7; - s += (p2 * 3) >> 4; - } - } - else if ( filter ) // s += p1 * 0.46875 - { - s += p1 >> 1; - s += (-p1) >> 5; - } - - // Adjust and write sample - CLAMP16( s ); - s = (int16_t) (s * 2); - pos [brr_buf_size] = pos [0] = s; // second copy simplifies wrap-around - } - - if ( pos >= &v->buf [brr_buf_size] ) - pos = v->buf; - v->buf_pos = pos; - } - } -skip_brr: - // Next voice - vbit <<= 1; - v_regs += 0x10; - v++; + v->brr_offset = 1; } - while ( vbit < 0x100 ); - - // Echo position - int echo_offset = m.echo_offset; - uint8_t* const echo_ptr = &ram [(REG(esa) * 0x100 + echo_offset) & 0xFFFF]; - if ( !echo_offset ) - m.echo_length = (REG(edl) & 0x0F) * 0x800; - echo_offset += 4; - if ( echo_offset >= m.echo_length ) - echo_offset = 0; - m.echo_offset = echo_offset; - - // FIR - int echo_in_l = GET_LE16SA( echo_ptr + 0 ); - int echo_in_r = GET_LE16SA( echo_ptr + 2 ); - - int (*echo_hist_pos) [2] = m.echo_hist_pos; - if ( ++echo_hist_pos >= &m.echo_hist [echo_hist_size] ) - echo_hist_pos = m.echo_hist; - m.echo_hist_pos = echo_hist_pos; - - echo_hist_pos [0] [0] = echo_hist_pos [8] [0] = echo_in_l; - echo_hist_pos [0] [1] = echo_hist_pos [8] [1] = echo_in_r; - - #define CALC_FIR_( i, in ) ((in) * (int8_t) REG(fir + i * 0x10)) - echo_in_l = CALC_FIR_( 7, echo_in_l ); - echo_in_r = CALC_FIR_( 7, echo_in_r ); - - #define CALC_FIR( i, ch ) CALC_FIR_( i, echo_hist_pos [i + 1] [ch] ) - #define DO_FIR( i )\ - echo_in_l += CALC_FIR( i, 0 );\ - echo_in_r += CALC_FIR( i, 1 ); - DO_FIR( 0 ); - DO_FIR( 1 ); - DO_FIR( 2 ); - #if defined (__MWERKS__) && __MWERKS__ < 0x3200 - __eieio(); // keeps compiler from stupidly "caching" things in memory - #endif - DO_FIR( 3 ); - DO_FIR( 4 ); - DO_FIR( 5 ); - DO_FIR( 6 ); - - // Echo out - if ( !(REG(flg) & 0x20) ) - { - int l = (echo_out_l >> 7) + ((echo_in_l * (int8_t) REG(efb)) >> 14); - int r = (echo_out_r >> 7) + ((echo_in_r * (int8_t) REG(efb)) >> 14); - - // just to help pass more validation tests - #if SPC_MORE_ACCURACY - l &= ~1; - r &= ~1; - #endif - - CLAMP16( l ); - CLAMP16( r ); - - SET_LE16A( echo_ptr + 0, l ); - SET_LE16A( echo_ptr + 2, r ); - } - - // Sound out - int l = (main_out_l * mvoll + echo_in_l * (int8_t) REG(evoll)) >> 14; - int r = (main_out_r * mvolr + echo_in_r * (int8_t) REG(evolr)) >> 14; - - CLAMP16( l ); - CLAMP16( r ); - - if ( (REG(flg) & 0x40) ) - { - l = 0; - r = 0; - } - + } + + // Apply pitch + v->interp_pos = (v->interp_pos & 0x3FFF) + m.t_pitch; + + // Keep from getting too far ahead (when using pitch modulation) + if ( v->interp_pos > 0x7FFF ) + v->interp_pos = 0x7FFF; + + // Output left + voice_output( v, 0 ); +} +inline VOICE_CLOCK( V5 ) +{ + // Output right + voice_output( v, 1 ); + + // ENDX, OUTX, and ENVX won't update if you wrote to them 1-2 clocks earlier + int endx_buf = REG(endx) | m.t_looped; + + // Clear bit in ENDX if KON just began + if ( v->kon_delay == 5 ) + endx_buf &= ~v->vbit; + m.endx_buf = (uint8_t) endx_buf; +} +inline VOICE_CLOCK( V6 ) +{ + (void) v; // avoid compiler warning about unused v + m.outx_buf = (uint8_t) (m.t_output >> 8); +} +inline VOICE_CLOCK( V7 ) +{ + // Update ENDX + REG(endx) = m.endx_buf; + + m.envx_buf = v->t_envx_out; +} +inline VOICE_CLOCK( V8 ) +{ + // Update OUTX + VREG(v->regs,outx) = m.outx_buf; +} +inline VOICE_CLOCK( V9 ) +{ + // Update ENVX + VREG(v->regs,envx) = m.envx_buf; +} + +// Most voices do all these in one clock, so make a handy composite +inline VOICE_CLOCK( V3 ) +{ + voice_V3a( v ); + voice_V3b( v ); + voice_V3c( v ); +} + +// Common combinations of voice steps on different voices. This greatly reduces +// code size and allows everything to be inlined in these functions. +VOICE_CLOCK(V7_V4_V1) { voice_V7(v); voice_V1(v+3); voice_V4(v+1); } +VOICE_CLOCK(V8_V5_V2) { voice_V8(v); voice_V5(v+1); voice_V2(v+2); } +VOICE_CLOCK(V9_V6_V3) { voice_V9(v); voice_V6(v+1); voice_V3(v+2); } + + +//// Echo + +// Current echo buffer pointer for left/right channel +#define ECHO_PTR( ch ) (&m.ram [m.t_echo_ptr + ch * 2]) + +// Sample in echo history buffer, where 0 is the oldest +#define ECHO_FIR( i ) (m.echo_hist_pos [i]) + +// Calculate FIR point for left/right channel +#define CALC_FIR( i, ch ) ((ECHO_FIR( i + 1 ) [ch] * (int8_t) REG(fir + i * 0x10)) >> 6) + +#define ECHO_CLOCK( n ) inline void SPC_DSP::echo_##n() + +inline void SPC_DSP::echo_read( int ch ) +{ + int s = GET_LE16SA( ECHO_PTR( ch ) ); + // second copy simplifies wrap-around handling + ECHO_FIR( 0 ) [ch] = ECHO_FIR( 8 ) [ch] = s >> 1; +} + +ECHO_CLOCK( 22 ) +{ + // History + if ( ++m.echo_hist_pos >= &m.echo_hist [echo_hist_size] ) + m.echo_hist_pos = m.echo_hist; + + m.t_echo_ptr = (m.t_esa * 0x100 + m.echo_offset) & 0xFFFF; + echo_read( 0 ); + + // FIR (using l and r temporaries below helps compiler optimize) + int l = CALC_FIR( 0, 0 ); + int r = CALC_FIR( 0, 1 ); + + m.t_echo_in [0] = l; + m.t_echo_in [1] = r; +} +ECHO_CLOCK( 23 ) +{ + int l = CALC_FIR( 1, 0 ) + CALC_FIR( 2, 0 ); + int r = CALC_FIR( 1, 1 ) + CALC_FIR( 2, 1 ); + + m.t_echo_in [0] += l; + m.t_echo_in [1] += r; + + echo_read( 1 ); +} +ECHO_CLOCK( 24 ) +{ + int l = CALC_FIR( 3, 0 ) + CALC_FIR( 4, 0 ) + CALC_FIR( 5, 0 ); + int r = CALC_FIR( 3, 1 ) + CALC_FIR( 4, 1 ) + CALC_FIR( 5, 1 ); + + m.t_echo_in [0] += l; + m.t_echo_in [1] += r; +} +ECHO_CLOCK( 25 ) +{ + int l = m.t_echo_in [0] + CALC_FIR( 6, 0 ); + int r = m.t_echo_in [1] + CALC_FIR( 6, 1 ); + + l = (int16_t) l; + r = (int16_t) r; + + l += (int16_t) CALC_FIR( 7, 0 ); + r += (int16_t) CALC_FIR( 7, 1 ); + + CLAMP16( l ); + CLAMP16( r ); + + m.t_echo_in [0] = l & ~1; + m.t_echo_in [1] = r & ~1; +} +inline int SPC_DSP::echo_output( int ch ) +{ + int out = (int16_t) ((m.t_main_out [ch] * (int8_t) REG(mvoll + ch * 0x10)) >> 7) + + (int16_t) ((m.t_echo_in [ch] * (int8_t) REG(evoll + ch * 0x10)) >> 7); + CLAMP16( out ); + return out; +} +ECHO_CLOCK( 26 ) +{ + // Left output volumes + // (save sample for next clock so we can output both together) + m.t_main_out [0] = echo_output( 0 ); + + // Echo feedback + int l = m.t_echo_out [0] + (int16_t) ((m.t_echo_in [0] * (int8_t) REG(efb)) >> 7); + int r = m.t_echo_out [1] + (int16_t) ((m.t_echo_in [1] * (int8_t) REG(efb)) >> 7); + + CLAMP16( l ); + CLAMP16( r ); + + m.t_echo_out [0] = l & ~1; + m.t_echo_out [1] = r & ~1; +} +ECHO_CLOCK( 27 ) +{ + // Output + int l = m.t_main_out [0]; + int r = echo_output( 1 ); + m.t_main_out [0] = 0; + m.t_main_out [1] = 0; + + // TODO: global muting isn't this simple (turns DAC on and off + // or something, causing small ~37-sample pulse when first muted) + if ( REG(flg) & 0x40 ) + { + l = 0; + r = 0; + } + + // Output sample to DAC + #ifdef SPC_DSP_OUT_HOOK + SPC_DSP_OUT_HOOK( l, r ); + #else sample_t* out = m.out; WRITE_SAMPLES( l, r, out ); m.out = out; - } - while ( --count ); + #endif } +ECHO_CLOCK( 28 ) +{ + m.t_echo_enabled = REG(flg); +} +inline void SPC_DSP::echo_write( int ch ) +{ + if ( !(m.t_echo_enabled & 0x20) ) + SET_LE16A( ECHO_PTR( ch ), m.t_echo_out [ch] ); + m.t_echo_out [ch] = 0; +} +ECHO_CLOCK( 29 ) +{ + m.t_esa = REG(esa); + + if ( !m.echo_offset ) + m.echo_length = (REG(edl) & 0x0F) * 0x800; + + m.echo_offset += 4; + if ( m.echo_offset >= m.echo_length ) + m.echo_offset = 0; + + // Write left echo + echo_write( 0 ); + + m.t_echo_enabled = REG(flg); +} +ECHO_CLOCK( 30 ) +{ + // Write right echo + echo_write( 1 ); +} + + +//// Timing + +// Execute clock for a particular voice +#define V( clock, voice ) voice_##clock( &m.voices [voice] ); + +/* The most common sequence of clocks uses composite operations +for efficiency. For example, the following are equivalent to the +individual steps on the right: + +V(V7_V4_V1,2) -> V(V7,2) V(V4,3) V(V1,5) +V(V8_V5_V2,2) -> V(V8,2) V(V5,3) V(V2,4) +V(V9_V6_V3,2) -> V(V9,2) V(V6,3) V(V3,4) */ + +// Voice 0 1 2 3 4 5 6 7 +#define GEN_DSP_TIMING \ +PHASE( 0) V(V5,0)V(V2,1)\ +PHASE( 1) V(V6,0)V(V3,1)\ +PHASE( 2) V(V7_V4_V1,0)\ +PHASE( 3) V(V8_V5_V2,0)\ +PHASE( 4) V(V9_V6_V3,0)\ +PHASE( 5) V(V7_V4_V1,1)\ +PHASE( 6) V(V8_V5_V2,1)\ +PHASE( 7) V(V9_V6_V3,1)\ +PHASE( 8) V(V7_V4_V1,2)\ +PHASE( 9) V(V8_V5_V2,2)\ +PHASE(10) V(V9_V6_V3,2)\ +PHASE(11) V(V7_V4_V1,3)\ +PHASE(12) V(V8_V5_V2,3)\ +PHASE(13) V(V9_V6_V3,3)\ +PHASE(14) V(V7_V4_V1,4)\ +PHASE(15) V(V8_V5_V2,4)\ +PHASE(16) V(V9_V6_V3,4)\ +PHASE(17) V(V1,0) V(V7,5)V(V4,6)\ +PHASE(18) V(V8_V5_V2,5)\ +PHASE(19) V(V9_V6_V3,5)\ +PHASE(20) V(V1,1) V(V7,6)V(V4,7)\ +PHASE(21) V(V8,6)V(V5,7) V(V2,0) /* t_brr_next_addr order dependency */\ +PHASE(22) V(V3a,0) V(V9,6)V(V6,7) echo_22();\ +PHASE(23) V(V7,7) echo_23();\ +PHASE(24) V(V8,7) echo_24();\ +PHASE(25) V(V3b,0) V(V9,7) echo_25();\ +PHASE(26) echo_26();\ +PHASE(27) misc_27(); echo_27();\ +PHASE(28) misc_28(); echo_28();\ +PHASE(29) misc_29(); echo_29();\ +PHASE(30) misc_30();V(V3c,0) echo_30();\ +PHASE(31) V(V4,0) V(V1,2)\ + +#if !SPC_DSP_CUSTOM_RUN + +void SPC_DSP::run( int clocks_remain ) +{ + require( clocks_remain > 0 ); + + int const phase = m.phase; + m.phase = (phase + clocks_remain) & 31; + switch ( phase ) + { + loop: + + #define PHASE( n ) if ( n && !--clocks_remain ) break; case n: + GEN_DSP_TIMING + #undef PHASE + + if ( --clocks_remain ) + goto loop; + } +} + +#endif //// Setup -void SPC_DSP::mute_voices( int mask ) -{ - m.mute_mask = mask; - for ( int i = 0; i < voice_count; i++ ) - { - m.voices [i].enabled = (mask >> i & 1) - 1; - update_voice_vol( i * 0x10 ); - } -} - void SPC_DSP::init( void* ram_64k ) { m.ram = (uint8_t*) ram_64k; @@ -688,17 +858,163 @@ void SPC_DSP::load( uint8_t const regs [register_count] ) memset( &m.regs [register_count], 0, offsetof (state_t,ram) - register_count ); // Internal state - int i; - for ( i = voice_count; --i >= 0; ) + for ( int i = voice_count; --i >= 0; ) { - voice_t& v = m.voices [i]; - v.brr_offset = 1; - v.buf_pos = v.buf; + voice_t* v = &m.voices [i]; + v->brr_offset = 1; + v->vbit = 1 << i; + v->regs = &m.regs [i * 0x10]; } m.new_kon = REG(kon); + m.t_dir = REG(dir); + m.t_esa = REG(esa); - mute_voices( m.mute_mask ); soft_reset_common(); } void SPC_DSP::reset() { load( initial_regs ); } + + +//// State save/load + +#if !SPC_NO_COPY_STATE_FUNCS + +void SPC_State_Copier::copy( void* state, size_t size ) +{ + func( buf, state, size ); +} + +int SPC_State_Copier::copy_int( int state, int size ) +{ + BOOST::uint8_t s [2]; + SET_LE16( s, state ); + func( buf, &s, size ); + return GET_LE16( s ); +} + +void SPC_State_Copier::skip( int count ) +{ + if ( count > 0 ) + { + char temp [64]; + memset( temp, 0, sizeof temp ); + do + { + int n = sizeof temp; + if ( n > count ) + n = count; + count -= n; + func( buf, temp, n ); + } + while ( count ); + } +} + +void SPC_State_Copier::extra() +{ + int n = 0; + SPC_State_Copier& copier = *this; + SPC_COPY( uint8_t, n ); + skip( n ); +} + +void SPC_DSP::copy_state( unsigned char** io, copy_func_t copy ) +{ + SPC_State_Copier copier( io, copy ); + + // DSP registers + copier.copy( m.regs, register_count ); + + // Internal state + + // Voices + int i; + for ( i = 0; i < voice_count; i++ ) + { + voice_t* v = &m.voices [i]; + + // BRR buffer + int i; + for ( i = 0; i < brr_buf_size; i++ ) + { + int s = v->buf [i]; + SPC_COPY( int16_t, s ); + v->buf [i] = v->buf [i + brr_buf_size] = s; + } + + SPC_COPY( uint16_t, v->interp_pos ); + SPC_COPY( uint16_t, v->brr_addr ); + SPC_COPY( uint16_t, v->env ); + SPC_COPY( int16_t, v->hidden_env ); + SPC_COPY( uint8_t, v->buf_pos ); + SPC_COPY( uint8_t, v->brr_offset ); + SPC_COPY( uint8_t, v->kon_delay ); + { + int m = v->env_mode; + SPC_COPY( uint8_t, m ); + v->env_mode = (enum env_mode_t) m; + } + SPC_COPY( uint8_t, v->t_envx_out ); + + copier.extra(); + } + + // Echo history + for ( i = 0; i < echo_hist_size; i++ ) + { + int j; + for ( j = 0; j < 2; j++ ) + { + int s = m.echo_hist_pos [i] [j]; + SPC_COPY( int16_t, s ); + m.echo_hist [i] [j] = s; // write back at offset 0 + } + } + m.echo_hist_pos = m.echo_hist; + memcpy( &m.echo_hist [echo_hist_size], m.echo_hist, echo_hist_size * sizeof m.echo_hist [0] ); + + // Misc + SPC_COPY( uint8_t, m.every_other_sample ); + SPC_COPY( uint8_t, m.kon ); + + SPC_COPY( uint16_t, m.noise ); + SPC_COPY( uint16_t, m.counter ); + SPC_COPY( uint16_t, m.echo_offset ); + SPC_COPY( uint16_t, m.echo_length ); + SPC_COPY( uint8_t, m.phase ); + + SPC_COPY( uint8_t, m.new_kon ); + SPC_COPY( uint8_t, m.endx_buf ); + SPC_COPY( uint8_t, m.envx_buf ); + SPC_COPY( uint8_t, m.outx_buf ); + + SPC_COPY( uint8_t, m.t_pmon ); + SPC_COPY( uint8_t, m.t_non ); + SPC_COPY( uint8_t, m.t_eon ); + SPC_COPY( uint8_t, m.t_dir ); + SPC_COPY( uint8_t, m.t_koff ); + + SPC_COPY( uint16_t, m.t_brr_next_addr ); + SPC_COPY( uint8_t, m.t_adsr0 ); + SPC_COPY( uint8_t, m.t_brr_header ); + SPC_COPY( uint8_t, m.t_brr_byte ); + SPC_COPY( uint8_t, m.t_srcn ); + SPC_COPY( uint8_t, m.t_esa ); + SPC_COPY( uint8_t, m.t_echo_enabled ); + + SPC_COPY( int16_t, m.t_main_out [0] ); + SPC_COPY( int16_t, m.t_main_out [1] ); + SPC_COPY( int16_t, m.t_echo_out [0] ); + SPC_COPY( int16_t, m.t_echo_out [1] ); + SPC_COPY( int16_t, m.t_echo_in [0] ); + SPC_COPY( int16_t, m.t_echo_in [1] ); + + SPC_COPY( uint16_t, m.t_dir_addr ); + SPC_COPY( uint16_t, m.t_pitch ); + SPC_COPY( int16_t, m.t_output ); + SPC_COPY( uint16_t, m.t_echo_ptr ); + SPC_COPY( uint8_t, m.t_looped ); + + copier.extra(); +} +#endif diff --git a/Core/SPC_DSP.h b/Core/SPC_DSP.h index a9c4142e..ee42ae08 100644 --- a/Core/SPC_DSP.h +++ b/Core/SPC_DSP.h @@ -1,4 +1,4 @@ -// Fast SNES SPC-700 DSP emulator (about 3x speed of accurate one) +// Highly accurate SNES SPC-700 DSP emulator // snes_spc 0.9.0 #ifndef SPC_DSP_H @@ -6,12 +6,14 @@ #include "blargg_common.h" +extern "C" { typedef void (*dsp_copy_func_t)( unsigned char** io, void* state, size_t ); } + class SPC_DSP { public: typedef BOOST::uint8_t uint8_t; // Setup - + // Initializes DSP and has it use the 64K RAM provided void init( void* ram_64k ); @@ -26,14 +28,14 @@ public: int sample_count() const; // Emulation - + // Resets DSP to power-on state void reset(); // Emulates pressing reset switch on SNES void soft_reset(); - // Reads/writes DSP registers. For accuracy, you must first call spc_run_dsp() + // Reads/writes DSP registers. For accuracy, you must first call run() // to catch the DSP up to present. int read ( int addr ) const; void write( int addr, int data ); @@ -41,23 +43,28 @@ public: // Runs DSP for specified number of clocks (~1024000 per second). Every 32 clocks // a pair of samples is be generated. void run( int clock_count ); - + // Sound control - // Mutes voices corresponding to non-zero bits in mask (overrides VxVOL with 0). + // Mutes voices corresponding to non-zero bits in mask (issues repeated KOFF events). // Reduces emulation accuracy. enum { voice_count = 8 }; void mute_voices( int mask ); - // If true, prevents channels and global volumes from being phase-negated - void disable_surround( bool disable = true ); - // State // Resets DSP and uses supplied values to initialize registers enum { register_count = 128 }; void load( uint8_t const regs [register_count] ); + // Saves/loads exact emulator state + enum { state_size = 640 }; // maximum space needed when saving + typedef dsp_copy_func_t copy_func_t; + void copy_state( unsigned char** io, copy_func_t ); + + // Returns non-zero if new key-on events occurred since last call + bool check_kon(); + // DSP register addresses // Global registers @@ -86,6 +93,7 @@ public: enum { extra_size = 16 }; sample_t* extra() { return m.extra; } sample_t const* out_pos() const { return m.out; } + void disable_surround( bool ) { } // not supported public: BLARGG_DISABLE_NOTHROW @@ -99,18 +107,21 @@ public: struct voice_t { int buf [brr_buf_size*2];// decoded samples (twice the size to simplify wrap handling) - int* buf_pos; // place in buffer where next samples will be decoded + int buf_pos; // place in buffer where next samples will be decoded int interp_pos; // relative fractional position in sample (0x1000 = 1.0) int brr_addr; // address of current BRR block int brr_offset; // current decoding offset in BRR block + uint8_t* regs; // pointer to voice's DSP registers + int vbit; // bitmask for voice: 0x01 for voice 0, 0x02 for voice 1, etc. int kon_delay; // KON delay/current setup phase env_mode_t env_mode; int env; // current envelope level int hidden_env; // used by GAIN mode 7, very obscure quirk - int volume [2]; // copy of volume from DSP registers, with surround disabled - int enabled; // -1 if enabled, 0 if muted + uint8_t t_envx_out; }; private: + enum { brr_block_size = 9 }; + struct state_t { uint8_t regs [register_count]; @@ -122,22 +133,53 @@ private: int every_other_sample; // toggles every sample int kon; // KON value when last checked int noise; + int counter; int echo_offset; // offset from ESA in echo buffer int echo_length; // number of bytes that echo_offset will stop at int phase; // next clock cycle to run (0-31) - unsigned counters [4]; + bool kon_check; // set when a new KON occurs + // Hidden registers also written to when main register is written to int new_kon; + uint8_t endx_buf; + uint8_t envx_buf; + uint8_t outx_buf; + + // Temporary state between clocks + + // read once per sample + int t_pmon; + int t_non; + int t_eon; + int t_dir; int t_koff; - voice_t voices [voice_count]; + // read a few clocks ahead then used + int t_brr_next_addr; + int t_adsr0; + int t_brr_header; + int t_brr_byte; + int t_srcn; + int t_esa; + int t_echo_enabled; - unsigned* counter_select [32]; + // internal state that is recalculated every sample + int t_dir_addr; + int t_pitch; + int t_output; + int t_looped; + int t_echo_ptr; + + // left/right sums + int t_main_out [2]; + int t_echo_out [2]; + int t_echo_in [2]; + + voice_t voices [voice_count]; // non-emulation state uint8_t* ram; // 64K shared RAM between DSP and SMP int mute_mask; - int surround_threshold; sample_t* out; sample_t* out_end; sample_t* out_begin; @@ -146,10 +188,49 @@ private: state_t m; void init_counter(); - void run_counter( int ); + void run_counters(); + unsigned read_counter( int rate ); + + int interpolate( voice_t const* v ); + void run_envelope( voice_t* const v ); + void decode_brr( voice_t* v ); + + void misc_27(); + void misc_28(); + void misc_29(); + void misc_30(); + + void voice_output( voice_t const* v, int ch ); + void voice_V1( voice_t* const ); + void voice_V2( voice_t* const ); + void voice_V3( voice_t* const ); + void voice_V3a( voice_t* const ); + void voice_V3b( voice_t* const ); + void voice_V3c( voice_t* const ); + void voice_V4( voice_t* const ); + void voice_V5( voice_t* const ); + void voice_V6( voice_t* const ); + void voice_V7( voice_t* const ); + void voice_V8( voice_t* const ); + void voice_V9( voice_t* const ); + void voice_V7_V4_V1( voice_t* const ); + void voice_V8_V5_V2( voice_t* const ); + void voice_V9_V6_V3( voice_t* const ); + + void echo_read( int ch ); + int echo_output( int ch ); + void echo_write( int ch ); + void echo_22(); + void echo_23(); + void echo_24(); + void echo_25(); + void echo_26(); + void echo_27(); + void echo_28(); + void echo_29(); + void echo_30(); + void soft_reset_common(); - void write_outline( int addr, int data ); - void update_voice_vol( int addr ); }; #include @@ -162,51 +243,62 @@ inline int SPC_DSP::read( int addr ) const return m.regs [addr]; } -inline void SPC_DSP::update_voice_vol( int addr ) -{ - int l = (int8_t) m.regs [addr + v_voll]; - int r = (int8_t) m.regs [addr + v_volr]; - - if ( l * r < m.surround_threshold ) - { - // signs differ, so negate those that are negative - l ^= l >> 7; - r ^= r >> 7; - } - - voice_t& v = m.voices [addr >> 4]; - int enabled = v.enabled; - v.volume [0] = l & enabled; - v.volume [1] = r & enabled; -} - inline void SPC_DSP::write( int addr, int data ) { assert( (unsigned) addr < register_count ); m.regs [addr] = (uint8_t) data; - int low = addr & 0x0F; - if ( low < 0x2 ) // voice volumes - { - update_voice_vol( low ^ addr ); - } - else if ( low == 0xC ) + switch ( addr & 0x0F ) { + case v_envx: + m.envx_buf = (uint8_t) data; + break; + + case v_outx: + m.outx_buf = (uint8_t) data; + break; + + case 0x0C: if ( addr == r_kon ) m.new_kon = (uint8_t) data; if ( addr == r_endx ) // always cleared, regardless of data written + { + m.endx_buf = 0; m.regs [r_endx] = 0; + } + break; } } -inline void SPC_DSP::disable_surround( bool disable ) +inline void SPC_DSP::mute_voices( int mask ) { m.mute_mask = mask; } + +inline bool SPC_DSP::check_kon() { - m.surround_threshold = disable ? 0 : -0x4000; + bool old = m.kon_check; + m.kon_check = 0; + return old; } -#define SPC_NO_COPY_STATE_FUNCS 1 +#if !SPC_NO_COPY_STATE_FUNCS -#define SPC_LESS_ACCURATE 1 +class SPC_State_Copier { + SPC_DSP::copy_func_t func; + unsigned char** buf; +public: + SPC_State_Copier( unsigned char** p, SPC_DSP::copy_func_t f ) { func = f; buf = p; } + void copy( void* state, size_t size ); + int copy_int( int state, int size ); + void skip( int count ); + void extra(); +}; + +#define SPC_COPY( type, state )\ +{\ + state = (BOOST::type) copier.copy_int( state, sizeof (BOOST::type) );\ + assert( (BOOST::type) state == state );\ +} + +#endif #endif diff --git a/Core/SaveStateManager.cpp b/Core/SaveStateManager.cpp new file mode 100644 index 00000000..08d4cf3f --- /dev/null +++ b/Core/SaveStateManager.cpp @@ -0,0 +1,250 @@ +#include "stdafx.h" +#include "../Utilities/FolderUtilities.h" +#include "../Utilities/ZipWriter.h" +#include "../Utilities/ZipReader.h" +#include "SaveStateManager.h" +#include "MessageManager.h" +#include "Console.h" +#include "EmuSettings.h" +#include "VideoDecoder.h" +#include "BaseCartridge.h" + +SaveStateManager::SaveStateManager(shared_ptr console) +{ + _console = console; + _lastIndex = 1; +} + +string SaveStateManager::GetStateFilepath(int stateIndex) +{ + string romPath = _console->GetRomInfo().RomPath; + string folder = FolderUtilities::GetSaveStateFolder(); + string filename = FolderUtilities::GetFilename(romPath, false) + "_" + std::to_string(stateIndex) + ".mst"; + return FolderUtilities::CombinePath(folder, filename); +} + +uint64_t SaveStateManager::GetStateInfo(int stateIndex) +{ + string filepath = SaveStateManager::GetStateFilepath(stateIndex); + ifstream file(filepath, ios::in | ios::binary); + + if(file) { + file.close(); + return FolderUtilities::GetFileModificationTime(filepath); + } + return 0; +} + +void SaveStateManager::MoveToNextSlot() +{ + _lastIndex = (_lastIndex % MaxIndex) + 1; + MessageManager::DisplayMessage("SaveStates", "SaveStateSlotSelected", std::to_string(_lastIndex)); +} + +void SaveStateManager::MoveToPreviousSlot() +{ + _lastIndex = (_lastIndex == 1 ? SaveStateManager::MaxIndex : (_lastIndex - 1)); + MessageManager::DisplayMessage("SaveStates", "SaveStateSlotSelected", std::to_string(_lastIndex)); +} + +void SaveStateManager::SaveState() +{ + SaveState(_lastIndex); +} + +bool SaveStateManager::LoadState() +{ + return LoadState(_lastIndex); +} + +void SaveStateManager::GetSaveStateHeader(ostream &stream) +{ + uint32_t emuVersion = _console->GetSettings()->GetVersion(); + uint32_t formatVersion = SaveStateManager::FileFormatVersion; + stream.write("MST", 3); + stream.write((char*)&emuVersion, sizeof(emuVersion)); + stream.write((char*)&formatVersion, sizeof(uint32_t)); + + string sha1Hash = _console->GetCartridge()->GetSha1Hash(); + stream.write(sha1Hash.c_str(), sha1Hash.size()); + + RomInfo romInfo = _console->GetCartridge()->GetRomInfo(); + string romName = FolderUtilities::GetFilename(romInfo.RomPath, true); + uint32_t nameLength = (uint32_t)romName.size(); + stream.write((char*)&nameLength, sizeof(uint32_t)); + stream.write(romName.c_str(), romName.size()); +} + +void SaveStateManager::SaveState(ostream &stream) +{ + GetSaveStateHeader(stream); + _console->Serialize(stream); +} + +bool SaveStateManager::SaveState(string filepath) +{ + ofstream file(filepath, ios::out | ios::binary); + + if(file) { + auto lock = _console->AcquireLock(); + SaveState(file); + file.close(); + + //TODO LUA + /*shared_ptr debugger = _console->GetDebugger(false); + if(debugger) { + debugger->ProcessEvent(EventType::StateSaved); + }*/ + return true; + } + return false; +} + +void SaveStateManager::SaveState(int stateIndex, bool displayMessage) +{ + string filepath = SaveStateManager::GetStateFilepath(stateIndex); + if(SaveState(filepath)) { + if(displayMessage) { + MessageManager::DisplayMessage("SaveStates", "SaveStateSaved", std::to_string(stateIndex)); + } + } +} + +bool SaveStateManager::LoadState(istream &stream, bool hashCheckRequired) +{ + char header[3]; + stream.read(header, 3); + if(memcmp(header, "MST", 3) == 0) { + uint32_t emuVersion, fileFormatVersion; + + stream.read((char*)&emuVersion, sizeof(emuVersion)); + if(emuVersion > _console->GetSettings()->GetVersion()) { + MessageManager::DisplayMessage("SaveStates", "SaveStateNewerVersion"); + return false; + } + + stream.read((char*)&fileFormatVersion, sizeof(fileFormatVersion)); + if(fileFormatVersion < 1) { + MessageManager::DisplayMessage("SaveStates", "SaveStateIncompatibleVersion"); + return false; + } else { + char hash[41] = {}; + stream.read(hash, 40); + + uint32_t nameLength = 0; + stream.read((char*)&nameLength, sizeof(uint32_t)); + + vector nameBuffer(nameLength); + stream.read(nameBuffer.data(), nameBuffer.size()); + string romName(nameBuffer.data(), nameLength); + + //TODO + /*shared_ptr cartridge = _console->GetCartridge(); + if(cartridge) { + string sha1Hash = cartridge->GetSha1Hash(); + bool gameLoaded = !sha1Hash.empty(); + if(sha1Hash != string(hash)) { + //CRC doesn't match + } + }*/ + } + + //Stop any movie that might have been playing/recording if a state is loaded + //(Note: Loading a state is disabled in the UI while a movie is playing/recording) + //TODO MovieManager::Stop(); + + _console->Deserialize(stream, fileFormatVersion); + + return true; + } + MessageManager::DisplayMessage("SaveStates", "SaveStateInvalidFile"); + return false; +} + +bool SaveStateManager::LoadState(string filepath, bool hashCheckRequired) +{ + ifstream file(filepath, ios::in | ios::binary); + bool result = false; + + if(file.good()) { + auto lock = _console->AcquireLock(); + if(LoadState(file, hashCheckRequired)) { + result = true; + } + file.close(); + + //TODO LUA + /*shared_ptr debugger = _console->GetDebugger(false); + if(debugger) { + debugger->ProcessEvent(EventType::StateLoaded); + }*/ + } else { + MessageManager::DisplayMessage("SaveStates", "SaveStateEmpty"); + } + + return result; +} + +bool SaveStateManager::LoadState(int stateIndex) +{ + string filepath = SaveStateManager::GetStateFilepath(stateIndex); + if(LoadState(filepath, false)) { + MessageManager::DisplayMessage("SaveStates", "SaveStateLoaded", std::to_string(stateIndex)); + return true; + } + return false; +} + +//TODO +/* +void SaveStateManager::SaveRecentGame(string romName, string romPath, string patchPath) +{ + if(!_console->GetSettings()->CheckFlag(EmulationFlags::ConsoleMode) && !_console->GetSettings()->CheckFlag(EmulationFlags::DisableGameSelectionScreen) && _console->GetRomInfo().Format != RomFormat::Nsf) { + string filename = FolderUtilities::GetFilename(_console->GetRomInfo().RomName, false) + ".rgd"; + ZipWriter writer; + writer.Initialize(FolderUtilities::CombinePath(FolderUtilities::GetRecentGamesFolder(), filename)); + + std::stringstream pngStream; + _console->GetVideoDecoder()->TakeScreenshot(pngStream); + writer.AddFile(pngStream, "Screenshot.png"); + + std::stringstream stateStream; + SaveStateManager::SaveState(stateStream); + writer.AddFile(stateStream, "Savestate.mst"); + + std::stringstream romInfoStream; + romInfoStream << romName << std::endl; + romInfoStream << romPath << std::endl; + romInfoStream << patchPath << std::endl; + writer.AddFile(romInfoStream, "RomInfo.txt"); + writer.Save(); + } +} + +void SaveStateManager::LoadRecentGame(string filename, bool resetGame) +{ + ZipReader reader; + reader.LoadArchive(filename); + + stringstream romInfoStream, stateStream; + reader.GetStream("RomInfo.txt", romInfoStream); + reader.GetStream("Savestate.mst", stateStream); + + string romName, romPath, patchPath; + std::getline(romInfoStream, romName); + std::getline(romInfoStream, romPath); + std::getline(romInfoStream, patchPath); + + _console->Pause(); + try { + if(_console->Initialize(romPath, patchPath)) { + if(!resetGame) { + SaveStateManager::LoadState(stateStream, false); + } + } + } catch(std::exception ex) { + _console->Stop(); + } + _console->Resume(); +} +*/ \ No newline at end of file diff --git a/Core/SaveStateManager.h b/Core/SaveStateManager.h new file mode 100644 index 00000000..f232c087 --- /dev/null +++ b/Core/SaveStateManager.h @@ -0,0 +1,40 @@ +#pragma once +#include "stdafx.h" + +class Console; + +class SaveStateManager +{ +private: + static constexpr uint32_t MaxIndex = 10; + + atomic _lastIndex; + shared_ptr _console; + + string GetStateFilepath(int stateIndex); + +public: + static constexpr uint32_t FileFormatVersion = 1; + + SaveStateManager(shared_ptr console); + + uint64_t GetStateInfo(int stateIndex); + + void SaveState(); + bool LoadState(); + + void GetSaveStateHeader(ostream & stream); + + void SaveState(ostream &stream); + bool SaveState(string filepath); + void SaveState(int stateIndex, bool displayMessage = true); + bool LoadState(istream &stream, bool hashCheckRequired = true); + bool LoadState(string filepath, bool hashCheckRequired = true); + bool LoadState(int stateIndex); + + //void SaveRecentGame(string romName, string romPath, string patchPath); + //void LoadRecentGame(string filename, bool resetGame); + + void MoveToNextSlot(); + void MoveToPreviousSlot(); +}; \ No newline at end of file diff --git a/Core/SettingTypes.h b/Core/SettingTypes.h index 32c2208e..622566b1 100644 --- a/Core/SettingTypes.h +++ b/Core/SettingTypes.h @@ -316,6 +316,7 @@ enum class EmulatorShortcut LoadStateSlot8, LoadStateSlot9, LoadStateSlot10, + LoadStateSlotAuto, LoadStateFromFile, OpenFile, diff --git a/Core/SnesController.h b/Core/SnesController.h index 0ab5cb29..19993ad5 100644 --- a/Core/SnesController.h +++ b/Core/SnesController.h @@ -1,6 +1,7 @@ #pragma once #include "stdafx.h" #include "BaseControlDevice.h" +#include "../Utilities/Serializer.h" class SnesController : public BaseControlDevice { @@ -52,13 +53,11 @@ protected: ((uint8_t)IsPressed(Buttons::R) << 11); } - //TODO - /* - void StreamState(bool saving) override + void Serialize(Serializer &s) override { - BaseControlDevice::StreamState(saving); - Stream(_stateBuffer); - }*/ + BaseControlDevice::Serialize(s); + s.Stream(_stateBuffer); + } void RefreshStateBuffer() override { diff --git a/Core/Spc.cpp b/Core/Spc.cpp index 027f3421..1899e2e2 100644 --- a/Core/Spc.cpp +++ b/Core/Spc.cpp @@ -4,6 +4,7 @@ #include "Console.h" #include "MemoryManager.h" #include "SoundMixer.h" +#include "../Utilities/Serializer.h" Spc::Spc(shared_ptr console, vector &spcRomData) { @@ -55,3 +56,30 @@ void Spc::ProcessEndFrame() uint64_t remainder = (_console->GetMemoryManager()->GetMasterClock() - _startFrameMasterClock) * 1024000 % 21477000 / 1024000; _startFrameMasterClock = _console->GetMemoryManager()->GetMasterClock() - remainder; } + +void Spc::Serialize(Serializer &s) +{ + s.Stream(_startFrameMasterClock); + + uint8_t state[SNES_SPC::state_size]; + memset(state, 0, SNES_SPC::state_size); + if(s.IsSaving()) { + uint8_t *out = state; + _spc->copy_state(&out, [](uint8_t** output, void* in, size_t size) { + memcpy(*output, in, size); + *output += size; + }); + + s.StreamArray(state, SNES_SPC::state_size); + } else { + s.StreamArray(state, SNES_SPC::state_size); + + uint8_t *in = state; + _spc->copy_state(&in, [](uint8_t** input, void* output, size_t size) { + memcpy(output, *input, size); + *input += size; + }); + + _spc->set_output(_soundBuffer, Spc::SampleBufferSize >> 1); + } +} diff --git a/Core/Spc.h b/Core/Spc.h index dc914697..173e95d0 100644 --- a/Core/Spc.h +++ b/Core/Spc.h @@ -1,10 +1,11 @@ #pragma once #include "stdafx.h" +#include "../Utilities/ISerializable.h" class Console; struct SNES_SPC; -class Spc +class Spc : public ISerializable { private: static constexpr int SampleBufferSize = 0x100000; @@ -25,4 +26,6 @@ public: void Write(uint32_t addr, uint8_t value); void ProcessEndFrame(); + + void Serialize(Serializer &s) override; }; \ No newline at end of file diff --git a/InteropDLL/EmuApiWrapper.cpp b/InteropDLL/EmuApiWrapper.cpp index 8b4fa8ed..1439c82f 100644 --- a/InteropDLL/EmuApiWrapper.cpp +++ b/InteropDLL/EmuApiWrapper.cpp @@ -1,7 +1,9 @@ #include "stdafx.h" #include "../Core/Console.h" +#include "../Core/EmuSettings.h" #include "../Core/VideoDecoder.h" #include "../Core/MessageManager.h" +#include "../Core/SaveStateManager.h" #include "../Core/INotificationListener.h" #include "../Core/KeyManager.h" #include "../Core/ShortcutKeyHandler.h" @@ -36,7 +38,7 @@ extern "C" { return true; } - DllExport uint32_t __stdcall GetMesenVersion() { return 0x00000100; } + DllExport uint32_t __stdcall GetMesenVersion() { return _console->GetSettings()->GetVersion(); } DllExport void __stdcall InitDll() { @@ -157,6 +159,12 @@ extern "C" { DllExport void __stdcall WriteLogEntry(char* message) { MessageManager::Log(message); } + DllExport void __stdcall SaveState(uint32_t stateIndex) { _console->GetSaveStateManager()->SaveState(stateIndex); } + DllExport void __stdcall LoadState(uint32_t stateIndex) { _console->GetSaveStateManager()->LoadState(stateIndex); } + DllExport void __stdcall SaveStateFile(char* filepath) { _console->GetSaveStateManager()->SaveState(filepath); } + DllExport void __stdcall LoadStateFile(char* filepath) { _console->GetSaveStateManager()->LoadState(filepath); } + DllExport int64_t __stdcall GetStateInfo(uint32_t stateIndex) { return _console->GetSaveStateManager()->GetStateInfo(stateIndex); } + DllExport void __stdcall PgoRunTest(vector testRoms, bool enableDebugger) { FolderUtilities::SetHomeFolder("../PGOMesenHome"); diff --git a/UI/Config/PreferencesConfig.cs b/UI/Config/PreferencesConfig.cs index 5973bf7f..63d68292 100644 --- a/UI/Config/PreferencesConfig.cs +++ b/UI/Config/PreferencesConfig.cs @@ -107,7 +107,7 @@ namespace Mesen.GUI.Config ShortcutKeys1.Add(new ShortcutKeyInfo(EmulatorShortcut.LoadStateSlot5, new KeyCombination() { Key1 = InputApi.GetKeyCode("F5") })); ShortcutKeys1.Add(new ShortcutKeyInfo(EmulatorShortcut.LoadStateSlot6, new KeyCombination() { Key1 = InputApi.GetKeyCode("F6") })); ShortcutKeys1.Add(new ShortcutKeyInfo(EmulatorShortcut.LoadStateSlot7, new KeyCombination() { Key1 = InputApi.GetKeyCode("F7") })); - //TODO ShortcutKeys1.Add(new ShortcutKeyInfo(EmulatorShortcut.LoadStateSlotAuto, new KeyCombination() { Key1 = InputApi.GetKeyCode("F8") })); + ShortcutKeys1.Add(new ShortcutKeyInfo(EmulatorShortcut.LoadStateSlotAuto, new KeyCombination() { Key1 = InputApi.GetKeyCode("F8") })); ShortcutKeys1.Add(new ShortcutKeyInfo(EmulatorShortcut.LoadStateFromFile, new KeyCombination() { Key1 = InputApi.GetKeyCode("Ctrl"), Key2 = InputApi.GetKeyCode("L") })); ShortcutKeys2 = new List(); diff --git a/UI/Config/RecentItems.cs b/UI/Config/RecentItems.cs index 5871b56e..b5f2b1a8 100644 --- a/UI/Config/RecentItems.cs +++ b/UI/Config/RecentItems.cs @@ -1,4 +1,5 @@ -using Mesen.GUI.Forms; +using Mesen.GUI.Emulation; +using Mesen.GUI.Forms; using System; using System.Collections.Generic; using System.IO; diff --git a/UI/Config/Shortcuts/EmulatorShortcut.cs b/UI/Config/Shortcuts/EmulatorShortcut.cs index 6aea53c8..de46038f 100644 --- a/UI/Config/Shortcuts/EmulatorShortcut.cs +++ b/UI/Config/Shortcuts/EmulatorShortcut.cs @@ -73,6 +73,7 @@ namespace Mesen.GUI.Config.Shortcuts LoadStateSlot8, LoadStateSlot9, LoadStateSlot10, + LoadStateSlotAuto, LoadStateFromFile, OpenFile diff --git a/UI/EmuRunner.cs b/UI/EmuRunner.cs deleted file mode 100644 index 66c2e10d..00000000 --- a/UI/EmuRunner.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Mesen.GUI.Config; -using Mesen.GUI.Forms; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Forms; - -namespace Mesen.GUI -{ - public static class EmuRunner - { - private static Thread _emuThread = null; - - public static void LoadRom(ResourcePath romPath, ResourcePath? patchPath = null) - { - if(!frmSelectRom.SelectRom(ref romPath)) { - return; - } - - EmuApi.LoadRom(romPath, patchPath); - - ConfigManager.Config.RecentFiles.AddRecentFile(romPath, patchPath); - - _emuThread = new Thread(() => { - EmuApi.Run(); - }); - _emuThread.Start(); - } - - public static bool IsRunning() - { - return _emuThread != null; - } - - public static void SaveState(uint slot) - { - if(_emuThread != null) { - //EmuApi.SaveState(slot); - } - } - - public static void LoadState(uint slot) - { - if(_emuThread != null) { - //EmuApi.LoadState(slot); - } - } - - public static void LoadStateFromFile() - { - if(_emuThread != null) { - using(OpenFileDialog ofd = new OpenFileDialog()) { - ofd.InitialDirectory = ConfigManager.SaveStateFolder; - ofd.SetFilter(ResourceHelper.GetMessage("FilterSavestate")); - if(ofd.ShowDialog(Application.OpenForms[0]) == DialogResult.OK) { - //EmuApi.LoadStateFile(ofd.FileName); - } - } - } - } - - public static void SaveStateToFile() - { - if(_emuThread != null) { - using(SaveFileDialog sfd = new SaveFileDialog()) { - sfd.InitialDirectory = ConfigManager.SaveStateFolder; - //TODO - //sfd.FileName = EmuApi.GetRomInfo().GetRomName() + ".mst"; - sfd.SetFilter(ResourceHelper.GetMessage("FilterSavestate")); - if(sfd.ShowDialog(Application.OpenForms[0]) == DialogResult.OK) { - //EmuApi.SaveStateFile(sfd.FileName); - } - } - } - } - } -} diff --git a/UI/Emulation/EmuRunner.cs b/UI/Emulation/EmuRunner.cs new file mode 100644 index 00000000..c6511ae5 --- /dev/null +++ b/UI/Emulation/EmuRunner.cs @@ -0,0 +1,38 @@ +using Mesen.GUI.Config; +using Mesen.GUI.Forms; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Mesen.GUI.Emulation +{ + public static class EmuRunner + { + private static Thread _emuThread = null; + + public static void LoadRom(ResourcePath romPath, ResourcePath? patchPath = null) + { + if(!frmSelectRom.SelectRom(ref romPath)) { + return; + } + + EmuApi.LoadRom(romPath, patchPath); + + ConfigManager.Config.RecentFiles.AddRecentFile(romPath, patchPath); + + _emuThread = new Thread(() => { + EmuApi.Run(); + }); + _emuThread.Start(); + } + + public static bool IsRunning() + { + return _emuThread != null; + } + } +} diff --git a/UI/Emulation/SaveStateManager.cs b/UI/Emulation/SaveStateManager.cs new file mode 100644 index 00000000..5f7223a4 --- /dev/null +++ b/UI/Emulation/SaveStateManager.cs @@ -0,0 +1,116 @@ +using Mesen.GUI.Config; +using Mesen.GUI.Config.Shortcuts; +using Mesen.GUI.Forms; +using Mesen.GUI.Properties; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Mesen.GUI.Emulation +{ + public static class SaveStateManager + { + private const int NumberOfSaveSlots = 10; + + public static void UpdateStateMenu(ToolStripMenuItem menu, bool forSave) + { + for(uint i = 1; i <= NumberOfSaveSlots + (forSave ? 0 : 1); i++) { + Int64 fileTime = EmuApi.GetStateInfo(i); + string label; + bool isAutoSaveSlot = i == NumberOfSaveSlots + 1; + string slotName = isAutoSaveSlot ? "Auto" : i.ToString(); + + if(fileTime == 0) { + label = slotName + ". " + ResourceHelper.GetMessage("EmptyState"); + } else { + DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(fileTime).ToLocalTime(); + label = slotName + ". " + dateTime.ToShortDateString() + " " + dateTime.ToShortTimeString(); + } + + if(i == NumberOfSaveSlots + 1) { + //Autosave slot (load only) + menu.DropDownItems[NumberOfSaveSlots + 1].Text = label; + } else { + menu.DropDownItems[(int)i - 1].Text = label; + } + } + } + + public static void InitializeStateMenu(ToolStripMenuItem menu, bool forSave, ShortcutHandler shortcutHandler) + { + Action addSaveStateInfo = (i) => { + ToolStripMenuItem item = new ToolStripMenuItem(); + menu.DropDownItems.Add(item); + + if(forSave) { + shortcutHandler.BindShortcut(item, (EmulatorShortcut)((int)EmulatorShortcut.SaveStateSlot1 + i - 1)); + } else { + shortcutHandler.BindShortcut(item, (EmulatorShortcut)((int)EmulatorShortcut.LoadStateSlot1 + i - 1)); + } + }; + + for(uint i = 1; i <= NumberOfSaveSlots; i++) { + addSaveStateInfo(i); + } + + if(!forSave) { + menu.DropDownItems.Add("-"); + addSaveStateInfo(NumberOfSaveSlots + 1); + menu.DropDownItems.Add("-"); + ToolStripMenuItem loadFromFile = new ToolStripMenuItem(ResourceHelper.GetMessage("LoadFromFile"), Resources.Folder); + menu.DropDownItems.Add(loadFromFile); + shortcutHandler.BindShortcut(loadFromFile, EmulatorShortcut.LoadStateFromFile); + } else { + menu.DropDownItems.Add("-"); + ToolStripMenuItem saveToFile = new ToolStripMenuItem(ResourceHelper.GetMessage("SaveToFile"), Resources.SaveFloppy); + menu.DropDownItems.Add(saveToFile); + shortcutHandler.BindShortcut(saveToFile, EmulatorShortcut.SaveStateToFile); + } + } + + public static void SaveState(uint slot) + { + if(EmuRunner.IsRunning()) { + EmuApi.SaveState(slot); + } + } + + public static void LoadState(uint slot) + { + if(EmuRunner.IsRunning()) { + EmuApi.LoadState(slot); + } + } + + public static void LoadStateFromFile() + { + if(EmuRunner.IsRunning()) { + using(OpenFileDialog ofd = new OpenFileDialog()) { + ofd.InitialDirectory = ConfigManager.SaveStateFolder; + ofd.SetFilter(ResourceHelper.GetMessage("FilterSavestate")); + if(ofd.ShowDialog(Application.OpenForms[0]) == DialogResult.OK) { + EmuApi.LoadStateFile(ofd.FileName); + } + } + } + } + + public static void SaveStateToFile() + { + if(EmuRunner.IsRunning()) { + using(SaveFileDialog sfd = new SaveFileDialog()) { + sfd.InitialDirectory = ConfigManager.SaveStateFolder; + //TODO + //sfd.FileName = EmuApi.GetRomInfo().GetRomName() + ".mst"; + sfd.SetFilter(ResourceHelper.GetMessage("FilterSavestate")); + if(sfd.ShowDialog(Application.OpenForms[0]) == DialogResult.OK) { + EmuApi.SaveStateFile(sfd.FileName); + } + } + } + } + } +} diff --git a/UI/Forms/ShortcutHandler.cs b/UI/Emulation/ShortcutHandler.cs similarity index 81% rename from UI/Forms/ShortcutHandler.cs rename to UI/Emulation/ShortcutHandler.cs index 27306e0c..9542c9de 100644 --- a/UI/Forms/ShortcutHandler.cs +++ b/UI/Emulation/ShortcutHandler.cs @@ -1,5 +1,6 @@ using Mesen.GUI.Config; using Mesen.GUI.Config.Shortcuts; +using Mesen.GUI.Forms; using System; using System.Collections.Generic; using System.IO; @@ -8,7 +9,7 @@ using System.Text; using System.Threading.Tasks; using System.Windows.Forms; -namespace Mesen.GUI.Forms +namespace Mesen.GUI.Emulation { public class ShortcutHandler { @@ -92,29 +93,30 @@ namespace Mesen.GUI.Forms case EmulatorShortcut.TakeScreenshot: EmuApi.TakeScreenshot(); break; - case EmulatorShortcut.LoadStateFromFile: EmuRunner.LoadStateFromFile(); break; - case EmulatorShortcut.SaveStateToFile: EmuRunner.SaveStateToFile(); break; + case EmulatorShortcut.LoadStateFromFile: SaveStateManager.LoadStateFromFile(); break; + case EmulatorShortcut.SaveStateToFile: SaveStateManager.SaveStateToFile(); break; - case EmulatorShortcut.SaveStateSlot1: EmuRunner.SaveState(1); break; - case EmulatorShortcut.SaveStateSlot2: EmuRunner.SaveState(2); break; - case EmulatorShortcut.SaveStateSlot3: EmuRunner.SaveState(3); break; - case EmulatorShortcut.SaveStateSlot4: EmuRunner.SaveState(4); break; - case EmulatorShortcut.SaveStateSlot5: EmuRunner.SaveState(5); break; - case EmulatorShortcut.SaveStateSlot6: EmuRunner.SaveState(6); break; - case EmulatorShortcut.SaveStateSlot7: EmuRunner.SaveState(7); break; - case EmulatorShortcut.SaveStateSlot8: EmuRunner.SaveState(8); break; - case EmulatorShortcut.SaveStateSlot9: EmuRunner.SaveState(9); break; - case EmulatorShortcut.SaveStateSlot10: EmuRunner.SaveState(10); break; - case EmulatorShortcut.LoadStateSlot1: EmuRunner.LoadState(1); break; - case EmulatorShortcut.LoadStateSlot2: EmuRunner.LoadState(2); break; - case EmulatorShortcut.LoadStateSlot3: EmuRunner.LoadState(3); break; - case EmulatorShortcut.LoadStateSlot4: EmuRunner.LoadState(4); break; - case EmulatorShortcut.LoadStateSlot5: EmuRunner.LoadState(5); break; - case EmulatorShortcut.LoadStateSlot6: EmuRunner.LoadState(6); break; - case EmulatorShortcut.LoadStateSlot7: EmuRunner.LoadState(7); break; - case EmulatorShortcut.LoadStateSlot8: EmuRunner.LoadState(8); break; - case EmulatorShortcut.LoadStateSlot9: EmuRunner.LoadState(9); break; - case EmulatorShortcut.LoadStateSlot10: EmuRunner.LoadState(10); break; + case EmulatorShortcut.SaveStateSlot1: SaveStateManager.SaveState(1); break; + case EmulatorShortcut.SaveStateSlot2: SaveStateManager.SaveState(2); break; + case EmulatorShortcut.SaveStateSlot3: SaveStateManager.SaveState(3); break; + case EmulatorShortcut.SaveStateSlot4: SaveStateManager.SaveState(4); break; + case EmulatorShortcut.SaveStateSlot5: SaveStateManager.SaveState(5); break; + case EmulatorShortcut.SaveStateSlot6: SaveStateManager.SaveState(6); break; + case EmulatorShortcut.SaveStateSlot7: SaveStateManager.SaveState(7); break; + case EmulatorShortcut.SaveStateSlot8: SaveStateManager.SaveState(8); break; + case EmulatorShortcut.SaveStateSlot9: SaveStateManager.SaveState(9); break; + case EmulatorShortcut.SaveStateSlot10: SaveStateManager.SaveState(10); break; + case EmulatorShortcut.LoadStateSlot1: SaveStateManager.LoadState(1); break; + case EmulatorShortcut.LoadStateSlot2: SaveStateManager.LoadState(2); break; + case EmulatorShortcut.LoadStateSlot3: SaveStateManager.LoadState(3); break; + case EmulatorShortcut.LoadStateSlot4: SaveStateManager.LoadState(4); break; + case EmulatorShortcut.LoadStateSlot5: SaveStateManager.LoadState(5); break; + case EmulatorShortcut.LoadStateSlot6: SaveStateManager.LoadState(6); break; + case EmulatorShortcut.LoadStateSlot7: SaveStateManager.LoadState(7); break; + case EmulatorShortcut.LoadStateSlot8: SaveStateManager.LoadState(8); break; + case EmulatorShortcut.LoadStateSlot9: SaveStateManager.LoadState(9); break; + case EmulatorShortcut.LoadStateSlot10: SaveStateManager.LoadState(10); break; + case EmulatorShortcut.LoadStateSlotAuto: SaveStateManager.LoadState(11); break; } //TODO diff --git a/UI/Forms/frmMain.Designer.cs b/UI/Forms/frmMain.Designer.cs index 81026c55..15c50b3f 100644 --- a/UI/Forms/frmMain.Designer.cs +++ b/UI/Forms/frmMain.Designer.cs @@ -32,6 +32,9 @@ this.mnuFile = new System.Windows.Forms.ToolStripMenuItem(); this.mnuOpen = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripMenuItem2 = new System.Windows.Forms.ToolStripSeparator(); + this.mnuSaveState = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuLoadState = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem10 = new System.Windows.Forms.ToolStripSeparator(); this.mnuRecentFiles = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripMenuItem6 = new System.Windows.Forms.ToolStripSeparator(); this.mnuExit = new System.Windows.Forms.ToolStripMenuItem(); @@ -152,6 +155,9 @@ this.mnuFile.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.mnuOpen, this.toolStripMenuItem2, + this.mnuSaveState, + this.mnuLoadState, + this.toolStripMenuItem10, this.mnuRecentFiles, this.toolStripMenuItem6, this.mnuExit}); @@ -165,30 +171,49 @@ this.mnuOpen.Image = global::Mesen.GUI.Properties.Resources.Folder; this.mnuOpen.Name = "mnuOpen"; this.mnuOpen.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.O))); - this.mnuOpen.Size = new System.Drawing.Size(146, 22); + this.mnuOpen.Size = new System.Drawing.Size(152, 22); this.mnuOpen.Text = "Open"; // // toolStripMenuItem2 // this.toolStripMenuItem2.Name = "toolStripMenuItem2"; - this.toolStripMenuItem2.Size = new System.Drawing.Size(143, 6); + this.toolStripMenuItem2.Size = new System.Drawing.Size(149, 6); + // + // mnuSaveState + // + this.mnuSaveState.Name = "mnuSaveState"; + this.mnuSaveState.Size = new System.Drawing.Size(152, 22); + this.mnuSaveState.Text = "Save State"; + this.mnuSaveState.DropDownOpening += new System.EventHandler(this.mnuSaveState_DropDownOpening); + // + // mnuLoadState + // + this.mnuLoadState.Name = "mnuLoadState"; + this.mnuLoadState.Size = new System.Drawing.Size(152, 22); + this.mnuLoadState.Text = "Load State"; + this.mnuLoadState.DropDownOpening += new System.EventHandler(this.mnuLoadState_DropDownOpening); + // + // toolStripMenuItem10 + // + this.toolStripMenuItem10.Name = "toolStripMenuItem10"; + this.toolStripMenuItem10.Size = new System.Drawing.Size(149, 6); // // mnuRecentFiles // this.mnuRecentFiles.Name = "mnuRecentFiles"; - this.mnuRecentFiles.Size = new System.Drawing.Size(146, 22); + this.mnuRecentFiles.Size = new System.Drawing.Size(152, 22); this.mnuRecentFiles.Text = "Recent Files"; // // toolStripMenuItem6 // this.toolStripMenuItem6.Name = "toolStripMenuItem6"; - this.toolStripMenuItem6.Size = new System.Drawing.Size(143, 6); + this.toolStripMenuItem6.Size = new System.Drawing.Size(149, 6); // // mnuExit // this.mnuExit.Image = global::Mesen.GUI.Properties.Resources.Exit; this.mnuExit.Name = "mnuExit"; - this.mnuExit.Size = new System.Drawing.Size(146, 22); + this.mnuExit.Size = new System.Drawing.Size(152, 22); this.mnuExit.Text = "Exit"; this.mnuExit.Click += new System.EventHandler(this.mnuExit_Click); // @@ -209,7 +234,7 @@ this.mnuPause.Enabled = false; this.mnuPause.Image = global::Mesen.GUI.Properties.Resources.MediaPause; this.mnuPause.Name = "mnuPause"; - this.mnuPause.Size = new System.Drawing.Size(139, 22); + this.mnuPause.Size = new System.Drawing.Size(152, 22); this.mnuPause.Text = "Pause"; // // mnuReset @@ -217,7 +242,7 @@ this.mnuReset.Enabled = false; this.mnuReset.Image = global::Mesen.GUI.Properties.Resources.Refresh; this.mnuReset.Name = "mnuReset"; - this.mnuReset.Size = new System.Drawing.Size(139, 22); + this.mnuReset.Size = new System.Drawing.Size(152, 22); this.mnuReset.Text = "Reset"; // // mnuPowerCycle @@ -225,19 +250,19 @@ this.mnuPowerCycle.Enabled = false; this.mnuPowerCycle.Image = global::Mesen.GUI.Properties.Resources.PowerCycle; this.mnuPowerCycle.Name = "mnuPowerCycle"; - this.mnuPowerCycle.Size = new System.Drawing.Size(139, 22); + this.mnuPowerCycle.Size = new System.Drawing.Size(152, 22); this.mnuPowerCycle.Text = "Power Cycle"; // // toolStripMenuItem24 // this.toolStripMenuItem24.Name = "toolStripMenuItem24"; - this.toolStripMenuItem24.Size = new System.Drawing.Size(136, 6); + this.toolStripMenuItem24.Size = new System.Drawing.Size(149, 6); // // mnuPowerOff // this.mnuPowerOff.Image = global::Mesen.GUI.Properties.Resources.MediaStop; this.mnuPowerOff.Name = "mnuPowerOff"; - this.mnuPowerOff.Size = new System.Drawing.Size(139, 22); + this.mnuPowerOff.Size = new System.Drawing.Size(152, 22); this.mnuPowerOff.Text = "Power Off"; // // optionsToolStripMenuItem @@ -272,7 +297,7 @@ this.mnuShowFPS}); this.mnuEmulationSpeed.Image = global::Mesen.GUI.Properties.Resources.Speed; this.mnuEmulationSpeed.Name = "mnuEmulationSpeed"; - this.mnuEmulationSpeed.Size = new System.Drawing.Size(152, 22); + this.mnuEmulationSpeed.Size = new System.Drawing.Size(135, 22); this.mnuEmulationSpeed.Text = "Speed"; this.mnuEmulationSpeed.DropDownOpening += new System.EventHandler(this.mnuEmulationSpeed_DropDownOpening); // @@ -360,7 +385,7 @@ this.mnuFullscreen}); this.mnuVideoScale.Image = global::Mesen.GUI.Properties.Resources.Fullscreen; this.mnuVideoScale.Name = "mnuVideoScale"; - this.mnuVideoScale.Size = new System.Drawing.Size(152, 22); + this.mnuVideoScale.Size = new System.Drawing.Size(135, 22); this.mnuVideoScale.Text = "Video Size"; this.mnuVideoScale.DropDownOpening += new System.EventHandler(this.mnuVideoScale_DropDownOpening); // @@ -446,7 +471,7 @@ this.mnuBilinearInterpolation}); this.mnuVideoFilter.Image = global::Mesen.GUI.Properties.Resources.VideoFilter; this.mnuVideoFilter.Name = "mnuVideoFilter"; - this.mnuVideoFilter.Size = new System.Drawing.Size(152, 22); + this.mnuVideoFilter.Size = new System.Drawing.Size(135, 22); this.mnuVideoFilter.Text = "Video Filter"; this.mnuVideoFilter.DropDownOpening += new System.EventHandler(this.mnuVideoFilter_DropDownOpening); // @@ -627,13 +652,13 @@ // toolStripMenuItem4 // this.toolStripMenuItem4.Name = "toolStripMenuItem4"; - this.toolStripMenuItem4.Size = new System.Drawing.Size(149, 6); + this.toolStripMenuItem4.Size = new System.Drawing.Size(132, 6); // // mnuAudioConfig // this.mnuAudioConfig.Image = global::Mesen.GUI.Properties.Resources.Audio; this.mnuAudioConfig.Name = "mnuAudioConfig"; - this.mnuAudioConfig.Size = new System.Drawing.Size(152, 22); + this.mnuAudioConfig.Size = new System.Drawing.Size(135, 22); this.mnuAudioConfig.Text = "Audio"; this.mnuAudioConfig.Click += new System.EventHandler(this.mnuAudioConfig_Click); // @@ -641,20 +666,20 @@ // this.mnuVideoConfig.Image = global::Mesen.GUI.Properties.Resources.VideoOptions; this.mnuVideoConfig.Name = "mnuVideoConfig"; - this.mnuVideoConfig.Size = new System.Drawing.Size(152, 22); + this.mnuVideoConfig.Size = new System.Drawing.Size(135, 22); this.mnuVideoConfig.Text = "Video"; this.mnuVideoConfig.Click += new System.EventHandler(this.mnuVideoConfig_Click); // // toolStripMenuItem3 // this.toolStripMenuItem3.Name = "toolStripMenuItem3"; - this.toolStripMenuItem3.Size = new System.Drawing.Size(149, 6); + this.toolStripMenuItem3.Size = new System.Drawing.Size(132, 6); // // mnuPreferences // this.mnuPreferences.Image = global::Mesen.GUI.Properties.Resources.Settings; this.mnuPreferences.Name = "mnuPreferences"; - this.mnuPreferences.Size = new System.Drawing.Size(152, 22); + this.mnuPreferences.Size = new System.Drawing.Size(135, 22); this.mnuPreferences.Text = "Preferences"; this.mnuPreferences.Click += new System.EventHandler(this.mnuPreferences_Click); // @@ -940,5 +965,8 @@ private System.Windows.Forms.ToolStripMenuItem mnuLogWindow; private System.Windows.Forms.ToolStripSeparator toolStripMenuItem7; private System.Windows.Forms.ToolStripMenuItem mnuTakeScreenshot; + private System.Windows.Forms.ToolStripMenuItem mnuSaveState; + private System.Windows.Forms.ToolStripMenuItem mnuLoadState; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem10; } } \ No newline at end of file diff --git a/UI/Forms/frmMain.cs b/UI/Forms/frmMain.cs index f2b824e4..f92aa3e0 100644 --- a/UI/Forms/frmMain.cs +++ b/UI/Forms/frmMain.cs @@ -1,6 +1,7 @@ using Mesen.GUI.Config; using Mesen.GUI.Config.Shortcuts; using Mesen.GUI.Debugger; +using Mesen.GUI.Emulation; using Mesen.GUI.Forms.Config; using System; using System.Collections.Generic; @@ -42,6 +43,9 @@ namespace Mesen.GUI.Forms _notifListener = new NotificationListener(); _notifListener.OnNotification += OnNotificationReceived; + SaveStateManager.InitializeStateMenu(mnuSaveState, true, _shortcuts); + SaveStateManager.InitializeStateMenu(mnuLoadState, false, _shortcuts); + BindShortcuts(); } @@ -64,6 +68,13 @@ namespace Mesen.GUI.Forms private void OnNotificationReceived(NotificationEventArgs e) { switch(e.NotificationType) { + case ConsoleNotificationType.GameLoaded: + this.BeginInvoke((Action)(() => { + SaveStateManager.UpdateStateMenu(mnuLoadState, false); + SaveStateManager.UpdateStateMenu(mnuSaveState, true); + })); + break; + case ConsoleNotificationType.ResolutionChanged: ScreenSize size = EmuApi.GetScreenSize(false); this.BeginInvoke((Action)(() => { @@ -327,5 +338,15 @@ namespace Mesen.GUI.Forms mnuEmuSpeedTriple.Checked = emulationSpeed == 300; mnuEmuSpeedMaximumSpeed.Checked = emulationSpeed == 0; } + + private void mnuLoadState_DropDownOpening(object sender, EventArgs e) + { + SaveStateManager.UpdateStateMenu(mnuLoadState, false); + } + + private void mnuSaveState_DropDownOpening(object sender, EventArgs e) + { + SaveStateManager.UpdateStateMenu(mnuSaveState, true); + } } } diff --git a/UI/Interop/EmuApi.cs b/UI/Interop/EmuApi.cs index 249c63b3..9133ca26 100644 --- a/UI/Interop/EmuApi.cs +++ b/UI/Interop/EmuApi.cs @@ -54,6 +54,12 @@ namespace Mesen.GUI [DllImport(DllPath)] public static extern void DisplayMessage([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))]string title, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))]string message, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))]string param1 = null); [DllImport(DllPath)] public static extern IntPtr GetArchiveRomList([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))]string filename); + + [DllImport(DllPath)] public static extern void SaveState(UInt32 stateIndex); + [DllImport(DllPath)] public static extern void LoadState(UInt32 stateIndex); + [DllImport(DllPath)] public static extern void SaveStateFile([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))]string filepath); + [DllImport(DllPath)] public static extern void LoadStateFile([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))]string filepath); + [DllImport(DllPath)] public static extern Int64 GetStateInfo(UInt32 stateIndex); } public struct ScreenSize diff --git a/UI/UI.csproj b/UI/UI.csproj index 29a6720a..f76566da 100644 --- a/UI/UI.csproj +++ b/UI/UI.csproj @@ -460,6 +460,7 @@ Form + BaseConfigForm.cs @@ -525,7 +526,7 @@ - + @@ -535,7 +536,7 @@ - + diff --git a/Utilities/ISerializable.h b/Utilities/ISerializable.h new file mode 100644 index 00000000..7d8138ee --- /dev/null +++ b/Utilities/ISerializable.h @@ -0,0 +1,10 @@ +#pragma once + +class Serializer; + +class ISerializable +{ +public: + virtual void Serialize(Serializer &s) = 0; +}; + diff --git a/Utilities/Serializer.cpp b/Utilities/Serializer.cpp new file mode 100644 index 00000000..9b61239d --- /dev/null +++ b/Utilities/Serializer.cpp @@ -0,0 +1,140 @@ +#include "stdafx.h" +#include +#include "Serializer.h" +#include "ISerializable.h" + +Serializer::Serializer(uint32_t version) +{ + _version = version; + + _streamSize = 0x50000; + _stream = new uint8_t[_streamSize]; + _position = 0; + _saving = true; +} + +Serializer::Serializer(istream &file, uint32_t version) +{ + _version = version; + + _position = 0; + _saving = false; + + file.read((char*)&_streamSize, sizeof(_streamSize)); + _stream = new uint8_t[_streamSize]; + file.read((char*)_stream, _streamSize); +} + +Serializer::~Serializer() +{ + delete[] _stream; +} + +void Serializer::EnsureCapacity(uint32_t typeSize) +{ + //Make sure the current block/stream is large enough to fit the next write + uint32_t oldSize; + uint32_t sizeRequired; + uint8_t *oldBuffer; + if(_inBlock) { + oldBuffer = _blockBuffer; + oldSize = _blockSize; + sizeRequired = _blockPosition + typeSize; + } else { + oldBuffer = _stream; + oldSize = _streamSize; + sizeRequired = _position + typeSize; + } + + uint8_t *newBuffer = nullptr; + uint32_t newSize = oldSize * 2; + if(oldSize < sizeRequired) { + while(newSize < sizeRequired) { + newSize *= 2; + } + + newBuffer = new uint8_t[newSize]; + memcpy(newBuffer, oldBuffer, oldSize); + delete[] oldBuffer; + } + + if(newBuffer) { + if(_inBlock) { + _blockBuffer = newBuffer; + _blockSize = newSize; + } else { + _stream = newBuffer; + _streamSize = newSize; + } + } +} + +void Serializer::RecursiveStream() +{ +} + +void Serializer::StreamStartBlock() +{ + if(_inBlock) { + throw new std::runtime_error("Cannot start a new block before ending the last block"); + } + + if(!_saving) { + InternalStream(_blockSize); + _blockSize = std::min(_blockSize, (uint32_t)0xFFFFF); + _blockBuffer = new uint8_t[_blockSize]; + ArrayInfo arrayInfo = { _blockBuffer, _blockSize }; + InternalStream(arrayInfo); + } else { + _blockSize = 0x100; + _blockBuffer = new uint8_t[_blockSize]; + } + _blockPosition = 0; + _inBlock = true; +} + +void Serializer::StreamEndBlock() +{ + _inBlock = false; + if(_saving) { + InternalStream(_blockPosition); + ArrayInfo arrayInfo = { _blockBuffer, _blockPosition }; + InternalStream(arrayInfo); + } + + delete[] _blockBuffer; + _blockBuffer = nullptr; +} + +void Serializer::Save(ostream& file) +{ + file.write((char*)&_position, sizeof(_position)); + file.write((char*)_stream, _position); +} + +void Serializer::WriteEmptyBlock(ostream* file) +{ + int blockSize = 0; + file->write((char*)&blockSize, sizeof(blockSize)); +} + +void Serializer::SkipBlock(istream* file) +{ + int blockSize = 0; + file->read((char*)&blockSize, sizeof(blockSize)); + file->seekg(blockSize, std::ios::cur); +} + +void Serializer::Stream(ISerializable &obj) +{ + //StreamStartBlock(); + obj.Serialize(*this); + //StreamEndBlock(); +} + +void Serializer::Stream(ISerializable *obj) +{ + //StreamStartBlock(); + obj->Serialize(*this); + //StreamEndBlock(); +} \ No newline at end of file diff --git a/Utilities/Serializer.h b/Utilities/Serializer.h new file mode 100644 index 00000000..48985b38 --- /dev/null +++ b/Utilities/Serializer.h @@ -0,0 +1,219 @@ +#pragma once + +#include "stdafx.h" + +class Serializer; +class ISerializable; + +template +struct ArrayInfo +{ + T* Array; + uint32_t ElementCount; +}; + +template +struct VectorInfo +{ + vector* Vector; +}; + +template +struct ValueInfo +{ + T* Value; + T DefaultValue; +}; + +template +struct EmptyInfo +{ + T Empty; +}; + +class Serializer +{ +private: + uint8_t* _stream = nullptr; + uint32_t _position = 0; + uint32_t _streamSize = 0; + uint32_t _version = 0; + + bool _inBlock = false; + uint8_t* _blockBuffer = nullptr; + uint32_t _blockSize = 0; + uint32_t _blockPosition = 0; + + bool _saving = false; + +private: + void EnsureCapacity(uint32_t typeSize); + + template void StreamElement(T &value, T defaultValue = T()); + template void InternalStream(EmptyInfo &info); + template void InternalStream(ArrayInfo &info); + template void InternalStream(VectorInfo &info); + template void InternalStream(ValueInfo &info); + template void InternalStream(T &value); + void RecursiveStream(); + + template void RecursiveStream(T &value, T2&... args); + + void StreamStartBlock(); + void StreamEndBlock(); + +public: + Serializer(uint32_t version); + Serializer(istream &file, uint32_t version); + ~Serializer(); + + uint32_t GetVersion() { return _version; } + bool IsSaving() { return _saving; } + + template void Stream(T&... args); + template void StreamArray(T *array, uint32_t size); + template void StreamVector(vector &list); + + void Save(ostream &file); + + void Stream(ISerializable &obj); + void Stream(ISerializable *obj); + + void WriteEmptyBlock(ostream* file); + void SkipBlock(istream* file); +}; + +template +void Serializer::StreamElement(T &value, T defaultValue) +{ + if(_saving) { + uint8_t* bytes = (uint8_t*)&value; + int typeSize = sizeof(T); + + EnsureCapacity(typeSize); + + for(int i = 0; i < typeSize; i++) { + if(_inBlock) { + _blockBuffer[_blockPosition++] = bytes[i]; + } else { + _stream[_position++] = bytes[i]; + } + } + } else { + if(_inBlock) { + if(_blockPosition + sizeof(T) <= _blockSize) { + memcpy(&value, _blockBuffer + _blockPosition, sizeof(T)); + _blockPosition += sizeof(T); + } else { + value = defaultValue; + _blockPosition = _blockSize; + } + } else { + if(_position + sizeof(T) <= _streamSize) { + memcpy(&value, _stream + _position, sizeof(T)); + _position += sizeof(T); + } else { + value = defaultValue; + _position = _streamSize; + } + } + } +} + +template +void Serializer::InternalStream(EmptyInfo &info) +{ + if(_inBlock) { + _blockPosition += sizeof(T); + } else { + _position += sizeof(T); + } +} + +template +void Serializer::InternalStream(ArrayInfo &info) +{ + T* pointer = info.Array; + + uint32_t count = info.ElementCount; + StreamElement(count); + + if(!_saving) { + //Reset array to 0 before loading from file + memset(info.Array, 0, sizeof(T) * info.ElementCount); + } + + //Load the number of elements requested, or the maximum possible (based on what is present in the save state) + for(uint32_t i = 0; i < info.ElementCount && i < count; i++) { + StreamElement(*pointer); + pointer++; + } +} + +template +void Serializer::InternalStream(VectorInfo &info) +{ + vector *vector = info.Vector; + + uint32_t count = (uint32_t)vector->size(); + StreamElement(count); + + if(!_saving) { + if(count > 0xFFFFFF) { + throw std::runtime_error("Invalid save state"); + } + vector->resize(count); + memset(vector->data(), 0, sizeof(T)*count); + } + + //Load the number of elements requested + T* pointer = vector->data(); + for(uint32_t i = 0; i < count; i++) { + StreamElement(*pointer); + pointer++; + } +} + +template +void Serializer::InternalStream(ValueInfo &info) +{ + StreamElement(*info.Value, info.DefaultValue); +} + +template +void Serializer::InternalStream(T &value) +{ + StreamElement(value); +} + +template +void Serializer::RecursiveStream(T &value, T2&... args) +{ + InternalStream(value); + RecursiveStream(args...); +} + +template +void Serializer::Stream(T&... args) +{ + //StreamStartBlock(); + RecursiveStream(args...); + //StreamEndBlock(); +} + +template +void Serializer::StreamArray(T *array, uint32_t size) +{ + ArrayInfo info; + info.Array = array; + info.ElementCount = size; + InternalStream(info); +} + +template +void Serializer::StreamVector(vector &list) +{ + VectorInfo info; + info.Vector = &list; + InternalStream(info); +} \ No newline at end of file diff --git a/Utilities/Utilities.vcxproj b/Utilities/Utilities.vcxproj index 31ad97b6..583d7c58 100644 --- a/Utilities/Utilities.vcxproj +++ b/Utilities/Utilities.vcxproj @@ -413,6 +413,7 @@ + @@ -426,6 +427,7 @@ + @@ -584,6 +586,7 @@ NotUsing NotUsing + diff --git a/Utilities/Utilities.vcxproj.filters b/Utilities/Utilities.vcxproj.filters index a37dc03c..71e652c7 100644 --- a/Utilities/Utilities.vcxproj.filters +++ b/Utilities/Utilities.vcxproj.filters @@ -1,117 +1,98 @@  - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {34df7dd9-5f1b-4aec-9212-1b70f1fada59} - - - {c29925fd-7698-4db8-a328-73ef7f8993a9} - - - {87329bd1-28ac-4ced-a4c2-b51777018d16} - - - {8e159744-fb91-4e16-aa82-8d8703ba2762} - {8b0e23bf-1bd9-4cc1-8046-784fd01e8688} {6d519bc1-7c40-448a-95d2-9ad94cd20644} + + {7b254092-7a93-4010-b203-ec6d48276d11} + + + {954e8e7b-1ee6-4ab4-a197-3154ce63061e} + + + {34df7dd9-5f1b-4aec-9212-1b70f1fada59} + + + {87329bd1-28ac-4ced-a4c2-b51777018d16} + + + {8e159744-fb91-4e16-aa82-8d8703ba2762} + + + {c29925fd-7698-4db8-a328-73ef7f8993a9} + + + {99c6afb9-49a2-4609-b3cd-3d26eb16d8a5} + + + {cde7b53f-be5e-4201-8194-0352d00216aa} + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + - Header Files + Misc - Header Files + Misc - Header Files + Misc - Header Files + Misc - Header Files + Misc - Header Files + Misc - Header Files - - - Header Files - - - Header Files + Misc - Header Files - - - Header Files + Misc - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files + Misc - xBRZ + Video\xBRZ - xBRZ + Video\xBRZ - HQX + Video\HQX - HQX + Video\HQX - Scale2x + Video\Scale2x - Scale2x + Video\Scale2x - Scale2x + Video\Scale2x - KreedSaiEagle - - - Header Files + Video\KreedSaiEagle - Header Files + Misc - Header Files + Misc Avi @@ -129,7 +110,7 @@ Avi - Header Files + Misc Patches @@ -140,130 +121,106 @@ Patches - - Header Files - - - Header Files - - - Header Files - - - Header Files - - Header Files + Misc - Header Files + Misc - Header Files + Misc - - Header Files + + Hashes - - Header Files + + Hashes + + + Hashes - Header Files + Video + + + Video + + + Video + + + Video + + + Archives + + + Archives + + + Archives + + + Archives + + + Archives + + + Audio + + + Audio + + + Audio - Header Files + Audio + + + Audio + + + Misc + + + Misc - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - xBRZ + Video\xBRZ - HQX + Video\HQX - HQX + Video\HQX - HQX + Video\HQX - HQX + Video\HQX - Scale2x + Video\Scale2x - Scale2x + Video\Scale2x - Scale2x + Video\Scale2x - KreedSaiEagle + Video\KreedSaiEagle - KreedSaiEagle + Video\KreedSaiEagle - KreedSaiEagle - - - Source Files - - - Source Files - - - Source Files - - - Source Files + Video\KreedSaiEagle Avi @@ -283,20 +240,80 @@ Patches + + Hashes + + + Hashes + - Source Files - - - Source Files - - - Source Files + Hashes - Source Files + Video + + + Video + + + Archives + + + Archives + + + Archives + + + Archives + + + Archives + + + Audio - Source Files + Audio + + + Audio + + + Misc + + + Misc + + + Misc + + + Misc + + + Misc + + + Misc + + + Misc + + + Misc + + + Misc + + + Misc + + + Misc + + + Misc \ No newline at end of file diff --git a/Utilities/sha1.cpp b/Utilities/sha1.cpp index dbae07bc..e794dab1 100644 --- a/Utilities/sha1.cpp +++ b/Utilities/sha1.cpp @@ -303,6 +303,16 @@ std::string SHA1::GetHash(vector &data) return checksum.final(); } +std::string SHA1::GetHash(uint8_t* data, size_t size) +{ + std::stringstream ss; + ss.write((char*)data, size); + + SHA1 checksum; + checksum.update(ss); + return checksum.final(); +} + std::string SHA1::GetHash(std::istream &stream) { SHA1 checksum; diff --git a/Utilities/sha1.h b/Utilities/sha1.h index 22b968f1..d25250c6 100644 --- a/Utilities/sha1.h +++ b/Utilities/sha1.h @@ -33,6 +33,7 @@ public: static std::string GetHash(const std::string &filename); static std::string GetHash(std::istream &stream); static std::string GetHash(vector &data); + static std::string GetHash(uint8_t* data, size_t size); private: uint32_t digest[5]; diff --git a/Utilities/stdafx.h b/Utilities/stdafx.h index 014e07e1..28964d07 100644 --- a/Utilities/stdafx.h +++ b/Utilities/stdafx.h @@ -14,6 +14,8 @@ using std::shared_ptr; using std::unique_ptr; using utf8::ifstream; using utf8::ofstream; +using std::ostream; +using std::istream; using std::string; using std::vector; using std::atomic;