#include "pch.h" #include "WS/WsConsole.h" #include "WS/WsCpu.h" #include "WS/WsPpu.h" #include "WS/WsTimer.h" #include "WS/Carts/WsCart.h" #include "WS/Carts/WsCartBandai2001.h" #include "WS/Carts/WsCartBandai2003.h" #include "WS/Carts/WsCartWonderWitch.h" #include "WS/Carts/WsRtc.h" #include "WS/WsControlManager.h" #include "WS/WsMemoryManager.h" #include "WS/WsDmaController.h" #include "WS/WsEeprom.h" #include "WS/WsSerial.h" #include "WS/WsDefaultVideoFilter.h" #include "WS/APU/WsApu.h" #include "Shared/CpuType.h" #include "Shared/Emulator.h" #include "Shared/SettingTypes.h" #include "Shared/FirmwareHelper.h" #include "Shared/BatteryManager.h" WsConsole::WsConsole(Emulator* emu) { _emu = emu; } WsConsole::~WsConsole() { delete[] _workRam; delete[] _saveRam; delete[] _prgRom; delete[] _bootRom; delete[] _internalEepromData; delete[] _cartEepromData; } LoadRomResult WsConsole::LoadRom(VirtualFile& romFile) { vector romData; romFile.ReadFile(romData); if(romData.size() < 0x10000) { return LoadRomResult::Failure; } uint32_t power = (uint32_t)std::log2(romData.size()); if(romData.size() > ((uint64_t)1 << power)) { //If size isn't a power of 2, pad the beginning of the ROM to the next power of 2 uint32_t newSize = 1 << (power + 1); romData.insert(romData.begin(), newSize - romData.size(), 0); } MessageManager::Log("------------------------------"); MessageManager::Log("File: " + romFile.GetFileName()); _prgRomSize = (uint32_t)romData.size(); _prgRom = new uint8_t[_prgRomSize]; memcpy(_prgRom, romData.data(), _prgRomSize); _emu->RegisterMemory(MemoryType::WsPrgRom, _prgRom, _prgRomSize); uint8_t sramType = _prgRom[_prgRomSize - 5]; switch(sramType) { case 0x00: _saveRamSize = 0; break; case 0x01: _saveRamSize = 32 * 1024; break; case 0x02: _saveRamSize = 32 * 1024; break; case 0x03: _saveRamSize = 128 * 1024; break; case 0x04: _saveRamSize = 256 * 1024; break; case 0x05: _saveRamSize = 512 * 1024; break; case 0x10: _cartEepromSize = 0x80; break; case 0x20: _cartEepromSize = 0x800; break; case 0x50: _cartEepromSize = 0x400; break; default: MessageManager::Log("Save RAM: unrecognized value"); break; } bool hasColorSupport = (_prgRom[_prgRomSize - 9] & 0x01) || romFile.GetFileExtension() == ".wsc"; uint8_t mapperType = _prgRom[_prgRomSize - 3]; MessageManager::Log(string("Color supported: ") + (hasColorSupport ? "Yes" : "No")); MessageManager::Log("Save RAM size: " + std::to_string(_saveRamSize / 1024) + " KB"); string cartName; WsCartType cartType = WsCartType::Unknown; if(IsWonderWitchCart()) { cartName = "WonderWitch (Bandai 2003 + NOR flash)"; _cartRtc.reset(new WsRtc(_emu, this)); _cart.reset(new WsCartWonderWitch(_cartRtc.get())); cartType = WsCartType::WonderWitch; } else { switch(mapperType) { case 0: MessageManager::Log("Cart EEPROM size: " + std::to_string(_cartEepromSize) + " bytes"); cartName = "Bandai 2001"; if(_cartEepromSize > 0) { _cartEepromData = new uint8_t[_cartEepromSize]; memset(_cartEepromData, 0, _cartEepromSize); _emu->RegisterMemory(MemoryType::WsCartEeprom, _cartEepromData, _cartEepromSize); _cartEeprom.reset(new WsEeprom(_emu, this, (WsEepromSize)_cartEepromSize, _cartEepromData, false)); } _cart.reset(new WsCartBandai2001(_cartEeprom.get())); cartType = WsCartType::Bandai2001; break; case 1: cartName = "Bandai 2003"; _cartRtc.reset(new WsRtc(_emu, this)); _cart.reset(new WsCartBandai2003(_cartRtc.get())); cartType = WsCartType::Bandai2003; break; default: cartName = "Unknown: " + std::to_string(mapperType); cartType = WsCartType::Unknown; _cart.reset(new WsCart()); break; } } MessageManager::Log("Mapper: " + cartName); MessageManager::Log("------------------------------"); if(_saveRamSize > 0) { _saveRam = new uint8_t[_saveRamSize]; memset(_saveRam, 0, _saveRamSize); _emu->RegisterMemory(MemoryType::WsCartRam, _saveRam, _saveRamSize); } _model = _emu->GetSettings()->GetWsConfig().Model; if(_model == WsModel::Auto) { _model = romFile.GetFileExtension() == ".pc2" ? WsModel::PocketChallenge : (hasColorSupport ? WsModel::Color : WsModel::Monochrome); } _colorModel = _model == WsModel::Color || _model == WsModel::SwanCrystal; _workRamSize = _colorModel ? 0x10000 : 0x4000; _workRam = new uint8_t[_workRamSize]; memset(_workRam, 0, _workRamSize); _emu->RegisterMemory(MemoryType::WsWorkRam, _workRam, _workRamSize); vector bootRom; if(_emu->GetSettings()->GetWsConfig().UseBootRom && FirmwareHelper::LoadWsBootRom(_emu, bootRom, _model)) { _bootRomSize = (uint32_t)bootRom.size(); _bootRom = new uint8_t[_bootRomSize]; memcpy(_bootRom, bootRom.data(), _bootRomSize); _emu->RegisterMemory(MemoryType::WsBootRom, _bootRom, _bootRomSize); } switch(_model) { case WsModel::PocketChallenge: _internalEepromSize = (uint32_t)WsEepromSize::Size0; break; case WsModel::Monochrome: _internalEepromSize = (uint32_t)WsEepromSize::Size128; break; default: _internalEepromSize = (uint32_t)WsEepromSize::Size2kb; break; } if(_internalEepromSize) { _internalEepromData = new uint8_t[_internalEepromSize]; memset(_internalEepromData, 0, _internalEepromSize); _emu->RegisterMemory(MemoryType::WsInternalEeprom, _internalEepromData, _internalEepromSize); } _internalEeprom.reset(new WsEeprom(_emu, this, (WsEepromSize)_internalEepromSize, _internalEepromData, true)); _controlManager.reset(new WsControlManager(_emu, this)); _memoryManager.reset(new WsMemoryManager()); _cpu.reset(new WsCpu(_emu, this, _memoryManager.get())); _timer.reset(new WsTimer()); _serial.reset(new WsSerial(this)); _dmaController.reset(new WsDmaController()); _ppu.reset(new WsPpu(_emu, this, _memoryManager.get(), _timer.get(), _workRam)); _apu.reset(new WsApu(_emu, this, _memoryManager.get(), _dmaController.get())); _cart->Init(_emu, cartType, _memoryManager.get(), _prgRom, _prgRomSize, _saveRam, _saveRamSize); _memoryManager->Init(_emu, this, _cpu.get(), _ppu.get(), _controlManager.get(), _cart.get(), _timer.get(), _dmaController.get(), _internalEeprom.get(), _apu.get(), _serial.get()); _timer->Init(_memoryManager.get()); _dmaController->Init(_memoryManager.get(), _apu.get()); if(!_bootRom) { InitPostBootRomState(); } LoadBattery(); return LoadRomResult::Success; } void WsConsole::RunFrame() { uint32_t frameCount = _ppu->GetFrameCount(); while(frameCount == _ppu->GetFrameCount()) { _cpu->Exec(); } _apu->PlayQueuedAudio(); //Make sure to process writes to EEPROM once a frame at least (so that the update is visible in the memory viewer) _internalEeprom->Run(); if(_cartEeprom) { _cartEeprom->Run(); } _verticalMode |= _ppu->GetState().Icons.Vertical; if(_ppu->GetState().Icons.Horizontal) { _verticalMode = false; } } void WsConsole::GetScreenRotationOverride(uint32_t& rotation) { if(_emu->GetSettings()->GetWsConfig().AutoRotate) { if(_verticalMode) { rotation = 270; } else { rotation = 0; } } } bool WsConsole::IsColorMode() { return _memoryManager->GetState().ColorEnabled; } bool WsConsole::IsColorModel() { return _colorModel; } bool WsConsole::IsPowerOff() { return _cpu->IsPowerOff(); } bool WsConsole::IsVerticalMode() { return _verticalMode; } bool WsConsole::IsWonderWitchCart() { //TODOWS exclude static FreyaBIOS cartridges return _prgRomSize == 0x80000 && memcmp(_prgRom + 0x70000, "ELISA", 5) == 0 // FreyaBIOS header && _prgRom[0x7FFF6] == 0x00 // Publisher ID && _prgRom[0x7FFF8] == 0x00 // Game ID && _prgRom[0x7FFFb] == 0x04 // Save format && _prgRom[0x7FFFd] == 0x01; // Mapper } WsAudioMode WsConsole::GetAudioMode() { // PCv2 always uses the headphone port for sound output if(_model == WsModel::PocketChallenge) { return WsAudioMode::Headphones; } return _emu->GetSettings()->GetWsConfig().AudioMode; } WsModel WsConsole::GetModel() { return _model; } void WsConsole::ProcessEndOfFrame() { _controlManager->UpdateControlDevices(); _controlManager->UpdateInputState(); } void WsConsole::Reset() { //The WS has no reset button, behave like power cycle _emu->ReloadRom(true); } void WsConsole::InitPostBootRomState() { //Init work ram if(_colorModel) { memset(_workRam, 0, 0xFE00); memset(_workRam + 0xFE00, 0xFF, 0x200); } else { memset(_workRam, 0, _workRamSize); } //Init port state WsCpuState& cpu = _cpu->GetState(); cpu.CS = 0xFFFF; cpu.SP = 0x2000; if(_model == WsModel::PocketChallenge) { // The Pocket Challenge skips most of the boot ROM. // TODOWS: Figure out scanline, cycle, any missing port writes. cpu.CS = 0x4000; cpu.IP = 0x0010; return; } else if(_model == WsModel::Monochrome) { cpu.AX = 0xFF85; cpu.BX = 0x0040; cpu.DX = 0x0005; cpu.SI = 0x023D; cpu.DI = 0x040D; cpu.DS = 0xFF00; cpu.Flags.Sign = true; } else { cpu.AX = 0xFF87; cpu.BX = 0x0043; cpu.DX = 0x0001; cpu.SI = 0x0435; cpu.DI = 0x040B; cpu.DS = 0xFE00; cpu.Flags.Sign = true; cpu.Flags.Parity = true; } WsPpuState& ppu = _ppu->GetState(); ppu.LcdEnabled = true; ppu.LcdControl = 0x01; if(_colorModel) { ppu.Scanline = 127; ppu.Cycle = 232; } else { ppu.Scanline = 26; ppu.Cycle = 53; // clang-format off constexpr static uint8_t defaultBwPalette[] = { 0,0,0,7,0,0,0,5,0,0,0,4,0,0,0,3, 0,0,0,2,0,0,0,1,0,0,0,0,0,0,0,0, 0,3,5,7,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, }; // clang-format on constexpr static uint8_t defaultShades[] = { 0, 2, 4, 6, 8, 10, 12, 15 }; memcpy(ppu.BwPalettes, defaultBwPalette, sizeof(defaultBwPalette)); memcpy(ppu.BwShades, defaultShades, sizeof(defaultShades)); } _ppu->SetOutputToBgColor(); _controlManager->Write(0x40); WsMemoryManagerState& mm = _memoryManager->GetState(); mm.BootRomDisabled = true; //Apply flags from rom header _verticalMode = (_prgRom[_prgRomSize - 4] & 0x01) != 0; mm.CartWordBus = (_prgRom[_prgRomSize - 4] & 0x04) != 0; mm.SlowRom = (_prgRom[_prgRomSize - 4] & 0x08) != 0; WsApuState& apu = _apu->GetState(); if(_colorModel) { apu.InternalMasterVolume = _internalEepromData[0x83] & 0x03; ppu.HighContrast = (_internalEepromData[0x83] & 0x40); } else { apu.Ch1.Frequency = 0x7E6; apu.Ch2.Frequency = 0x7E6; } WsEepromState& eeprom = _internalEeprom->GetState(); eeprom.ReadDone = true; eeprom.Idle = true; if(!(_prgRom[_prgRomSize - 0x10 + 9] & 0x80)) { eeprom.InternalEepromWriteProtected = true; } //Copy data from rom header to internal eeprom, like the boot rom would for(int i = 0; i < 3; i++) { _internalEepromData[0x76 + i] = _prgRom[_prgRomSize - 0x10 + 6 + i]; bool supportsColor = _prgRom[_prgRomSize - 0x10 + 7] & 0x01; if(_colorModel && supportsColor) { //For games that support color & color models, copy 76-78 to 80-82 _internalEepromData[0x80 + i] = _internalEepromData[0x76 + i]; } } _memoryManager->RefreshMappings(); } void WsConsole::LoadBattery() { _internalEeprom->LoadBattery(); _cart->LoadBattery(); } void WsConsole::SaveBattery() { _internalEeprom->SaveBattery(); _cart->SaveBattery(); } BaseControlManager* WsConsole::GetControlManager() { return _controlManager.get(); } ConsoleRegion WsConsole::GetRegion() { return ConsoleRegion::Ntsc; } ConsoleType WsConsole::GetConsoleType() { return ConsoleType::Ws; } vector WsConsole::GetCpuTypes() { return { CpuType::Ws }; } uint64_t WsConsole::GetCartridgeClock() { return _cpu->GetCycleCount() / 8; } uint64_t WsConsole::GetMasterClock() { return _cpu->GetCycleCount(); } uint32_t WsConsole::GetMasterClockRate() { return 12288000 / 4; } double WsConsole::GetFps() { return 12000.0 / _ppu->GetScanlineCount(); } BaseVideoFilter* WsConsole::GetVideoFilter(bool getDefaultFilter) { if(getDefaultFilter) { return new WsDefaultVideoFilter(_emu, this, false); } VideoFilterType filterType = _emu->GetSettings()->GetVideoConfig().VideoFilter; switch(filterType) { case VideoFilterType::NtscBisqwit: case VideoFilterType::NtscBlargg: return new WsDefaultVideoFilter(_emu, this, true); default: return new WsDefaultVideoFilter(_emu, this, false); } } PpuFrameInfo WsConsole::GetPpuFrame() { PpuFrameInfo frame = {}; frame.FirstScanline = 0; frame.FrameCount = _ppu->GetFrameCount(); frame.Width = _ppu->GetScreenWidth(); frame.Height = _ppu->GetScreenHeight(); frame.ScanlineCount = WsConstants::ScanlineCount; frame.CycleCount = WsConstants::ClocksPerScanline; frame.FrameBufferSize = frame.Width * frame.Height * sizeof(uint16_t); frame.FrameBuffer = (uint8_t*)_ppu->GetScreenBuffer(false); return frame; } uint32_t WsConsole::GetFrameCount() { return _ppu->GetFrameCount(); } RomFormat WsConsole::GetRomFormat() { return RomFormat::Ws; } AudioTrackInfo WsConsole::GetAudioTrackInfo() { //TODOWS return AudioTrackInfo(); } void WsConsole::ProcessAudioPlayerAction(AudioPlayerActionParams p) { //TODOWS } AddressInfo WsConsole::GetAbsoluteAddress(uint32_t relAddress) { return _memoryManager->GetAbsoluteAddress(relAddress); } AddressInfo WsConsole::GetAbsoluteAddress(AddressInfo& relAddress) { if(relAddress.Type == MemoryType::WsPort) { return { relAddress.Address & 0xFFFF, MemoryType::WsPort }; } else { return GetAbsoluteAddress(relAddress.Address); } } AddressInfo WsConsole::GetRelativeAddress(AddressInfo& absAddress, CpuType cpuType) { return { _memoryManager->GetRelativeAddress(absAddress), MemoryType::WsMemory }; } WsState WsConsole::GetState() { WsState state = {}; state.Model = _model; state.Cpu = _cpu->GetState(); state.Ppu = _ppu->GetState(); state.Apu = _apu->GetState(); state.MemoryManager = _memoryManager->GetState(); state.ControlManager = _controlManager->GetState(); state.DmaController = _dmaController->GetState(); state.Timer = _timer->GetState(); state.Serial = _serial->GetState(); state.InternalEeprom = _internalEeprom->GetState(); state.Cart = _cart->GetState(); if(_cartEeprom) { state.CartEeprom = _cartEeprom->GetState(); } if(_cartRtc) { state.CartRtc = _cartRtc->GetState(); } return state; } void WsConsole::GetConsoleState(BaseState& state, ConsoleType consoleType) { (WsState&)state = GetState(); } static WsModel GetModelForCompatibilityCheck(WsModel model) { if(model == WsModel::SwanCrystal) { //WSC and SC save states are currently interchangeable. return WsModel::Color; } return model; } static string GetModelShortName(WsModel model) { switch(model) { case WsModel::Color: case WsModel::SwanCrystal: return "WSC"; case WsModel::PocketChallenge: return "PCv2"; default: return "WS"; } } void WsConsole::Serialize(Serializer& s) { WsModel model = _model; SV(model); if(!s.IsSaving() && GetModelForCompatibilityCheck(model) != GetModelForCompatibilityCheck(_model)) { MessageManager::DisplayMessage("SaveStates", "Can't load " + GetModelShortName(model) + " state in " + GetModelShortName(_model) + " mode."); s.SetErrorFlag(); return; } SV(_cpu); SV(_ppu); SV(_apu); //Process cart before memory manager to ensure mappings are updated properly SV(_cart); SV(_memoryManager); SV(_dmaController); SV(_controlManager); SV(_timer); SV(_serial); SV(_internalEeprom); SV(_verticalMode); SVArray(_workRam, _workRamSize); if(_saveRam && _saveRamSize) { SVArray(_saveRam, _saveRamSize); } if(_internalEepromData && _internalEepromSize) { SVArray(_internalEepromData, _internalEepromSize); } if(_cartEepromData && _cartEepromSize) { SVArray(_cartEepromData, _cartEepromSize); } if(_cartEeprom) { SV(_cartEeprom); } if(_cartRtc) { SV(_cartRtc); } }