From 09ae55a556a389d396ad66f33cca69e7439429d9 Mon Sep 17 00:00:00 2001 From: Adrian Siekierka Date: Fri, 8 May 2026 23:21:40 +0200 Subject: [PATCH 1/7] WS: Initial definition of Pocket Challenge V2 subtype. The Pocket Challenge V2 is an education handheld based on the ASWAN SoC, as used in the WonderSwan. It uses the same boot ROM, but has some implementation changes. This commit only implements one of them (the display panel has no icon bar); further distinctions are implemented in later commits. --- Core/Shared/FirmwareHelper.h | 3 ++- Core/Shared/SettingTypes.h | 3 +++ Core/WS/APU/WsApu.cpp | 10 ++++----- Core/WS/WsConsole.cpp | 20 ++++++++--------- Core/WS/WsCpu.cpp | 2 +- Core/WS/WsDefaultVideoFilter.cpp | 2 +- Core/WS/WsMemoryManager.cpp | 22 +++++++++---------- Core/WS/WsPpu.cpp | 20 ++++++++--------- UI/Config/WsConfig.cs | 1 + .../RegisterViewer/WsRegisterViewer.cs | 4 ++-- UI/Localization/resources.en.xml | 1 + UI/ViewModels/MainMenuViewModel.cs | 1 + 12 files changed, 48 insertions(+), 41 deletions(-) diff --git a/Core/Shared/FirmwareHelper.h b/Core/Shared/FirmwareHelper.h index 07fe20108..c6baea45b 100644 --- a/Core/Shared/FirmwareHelper.h +++ b/Core/Shared/FirmwareHelper.h @@ -403,6 +403,7 @@ class FirmwareHelper FirmwareType firmwareType; switch(model) { default: + case WsModel::PocketChallenge: case WsModel::Monochrome: filename = "bootrom.ws"; firmwareType = FirmwareType::WonderSwan; @@ -418,7 +419,7 @@ class FirmwareHelper firmwareType = FirmwareType::SwanCrystal; break; } - uint32_t size = model == WsModel::Monochrome ? 0x1000 : 0x2000; + uint32_t size = model <= WsModel::Monochrome ? 0x1000 : 0x2000; string path = FolderUtilities::CombinePath(FolderUtilities::GetFirmwareFolder(), filename); if(AttemptLoadFirmware(bootRom, filename, size)) { return true; diff --git a/Core/Shared/SettingTypes.h b/Core/Shared/SettingTypes.h index 3403dac13..3d011728d 100644 --- a/Core/Shared/SettingTypes.h +++ b/Core/Shared/SettingTypes.h @@ -745,7 +745,10 @@ struct CvConfig enum class WsModel : uint8_t { Auto, + // Monochrome models + PocketChallenge, Monochrome, + // Color models Color, SwanCrystal }; diff --git a/Core/WS/APU/WsApu.cpp b/Core/WS/APU/WsApu.cpp index 21401b361..df92b6ebc 100644 --- a/Core/WS/APU/WsApu.cpp +++ b/Core/WS/APU/WsApu.cpp @@ -23,7 +23,7 @@ WsApu::WsApu(Emulator* emu, WsConsole* console, WsMemoryManager* memoryManager, _dmaController = dmaController; _soundMixer = emu->GetSoundMixer(); - _state.MasterVolume = _console->GetModel() == WsModel::Monochrome ? 2 : 3; + _state.MasterVolume = _console->GetModel() <= WsModel::Monochrome ? 2 : 3; _state.InternalMasterVolume = _state.MasterVolume; _ch1.reset(new WsApuCh1(this, _state.Ch1)); @@ -48,7 +48,7 @@ void WsApu::ChangeMasterVolume() { if(_emu->GetSettings()->GetWsConfig().AudioMode == WsAudioMode::Speakers) { if(_state.InternalMasterVolume == 0) { - _state.InternalMasterVolume = _console->GetModel() == WsModel::Monochrome ? 2 : 3; + _state.InternalMasterVolume = _console->GetModel() <= WsModel::Monochrome ? 2 : 3; } else { _state.InternalMasterVolume--; } @@ -132,7 +132,7 @@ void WsApu::UpdateOutput() leftOutput = out; rightOutput = out; - if(_console->GetModel() == WsModel::Monochrome) { + if(_console->GetModel() <= WsModel::Monochrome) { switch(_state.InternalMasterVolume) { case 0: leftOutput = 0; @@ -275,7 +275,7 @@ uint8_t WsApu::Read(uint16_t port) case 0x9B: return (GetApuOutput(false) + GetApuOutput(true)) >> 8; case 0x9E: - if(_console->GetModel() != WsModel::Monochrome) { + if(_console->GetModel() > WsModel::Monochrome) { return _state.MasterVolume; } break; @@ -366,7 +366,7 @@ void WsApu::Write(uint16_t port, uint8_t value) break; case 0x9E: - if(_console->GetModel() != WsModel::Monochrome) { + if(_console->GetModel() > WsModel::Monochrome) { _state.InternalMasterVolume = value & 0x03; _state.MasterVolume = value & 0x03; } diff --git a/Core/WS/WsConsole.cpp b/Core/WS/WsConsole.cpp index b6445985b..92f9f2432 100644 --- a/Core/WS/WsConsole.cpp +++ b/Core/WS/WsConsole.cpp @@ -96,10 +96,10 @@ LoadRomResult WsConsole::LoadRom(VirtualFile& romFile) _model = _emu->GetSettings()->GetWsConfig().Model; if(_model == WsModel::Auto) { - _model = hasColorSupport ? WsModel::Color : WsModel::Monochrome; + _model = romFile.GetFileExtension() == ".pc2" ? WsModel::PocketChallenge : (hasColorSupport ? WsModel::Color : WsModel::Monochrome); } - _workRamSize = _model == WsModel::Monochrome ? 0x4000 : 0x10000; + _workRamSize = _model <= WsModel::Monochrome ? 0x4000 : 0x10000; _workRam = new uint8_t[_workRamSize]; memset(_workRam, 0, _workRamSize); _emu->RegisterMemory(MemoryType::WsWorkRam, _workRam, _workRamSize); @@ -112,7 +112,7 @@ LoadRomResult WsConsole::LoadRom(VirtualFile& romFile) _emu->RegisterMemory(MemoryType::WsBootRom, _bootRom, _bootRomSize); } - _internalEepromSize = (uint32_t)(_model == WsModel::Monochrome ? WsEepromSize::Size128 : WsEepromSize::Size2kb); + _internalEepromSize = (uint32_t)(_model <= WsModel::Monochrome ? WsEepromSize::Size128 : WsEepromSize::Size2kb); _internalEepromData = new uint8_t[_internalEepromSize]; memset(_internalEepromData, 0, _internalEepromSize); _emu->RegisterMemory(MemoryType::WsInternalEeprom, _internalEepromData, _internalEepromSize); @@ -210,7 +210,7 @@ void WsConsole::Reset() void WsConsole::InitPostBootRomState() { //Init work ram - if(_model == WsModel::Monochrome) { + if(_model <= WsModel::Monochrome) { memset(_workRam, 0, _workRamSize); } else { memset(_workRam, 0, 0xFE00); @@ -222,7 +222,7 @@ void WsConsole::InitPostBootRomState() cpu.CS = 0xFFFF; cpu.SP = 0x2000; - if(_model == WsModel::Monochrome) { + if(_model <= WsModel::Monochrome) { cpu.AX = 0xFF85; cpu.BX = 0x0040; cpu.DX = 0x0005; @@ -243,7 +243,7 @@ void WsConsole::InitPostBootRomState() WsPpuState& ppu = _ppu->GetState(); ppu.LcdEnabled = true; - if(_model == WsModel::Monochrome) { + if(_model <= WsModel::Monochrome) { ppu.Scanline = 26; ppu.Cycle = 53; @@ -278,7 +278,7 @@ void WsConsole::InitPostBootRomState() mm.SlowRom = (_prgRom[_prgRomSize - 4] & 0x08) != 0; WsApuState& apu = _apu->GetState(); - if(_model == WsModel::Monochrome) { + if(_model <= WsModel::Monochrome) { apu.Ch1.Frequency = 0x7E6; apu.Ch2.Frequency = 0x7E6; } else { @@ -296,7 +296,7 @@ void WsConsole::InitPostBootRomState() _internalEepromData[0x76 + i] = _prgRom[_prgRomSize - 0x10 + 6 + i]; bool supportsColor = _prgRom[_prgRomSize - 0x10 + 7] & 0x01; - if(_model != WsModel::Monochrome && supportsColor) { + if(_model > WsModel::Monochrome && supportsColor) { //For games that support color & color models, copy 76-78 to 80-82 _internalEepromData[0x80 + i] = _internalEepromData[0x76 + i]; } @@ -459,8 +459,8 @@ void WsConsole::Serialize(Serializer& s) { WsModel model = _model; SV(model); - if(!s.IsSaving() && (model == WsModel::Monochrome) != (_model == WsModel::Monochrome)) { - bool isMono = _model == WsModel::Monochrome; + if(!s.IsSaving() && (model <= WsModel::Monochrome) != (_model <= WsModel::Monochrome)) { + bool isMono = _model <= WsModel::Monochrome; MessageManager::DisplayMessage("SaveStates", isMono ? "Can't load WSC state in WS mode." : "Can't load WS state in WSC mode."); s.SetErrorFlag(); return; diff --git a/Core/WS/WsCpu.cpp b/Core/WS/WsCpu.cpp index f3b3e94a4..32426369e 100644 --- a/Core/WS/WsCpu.cpp +++ b/Core/WS/WsCpu.cpp @@ -1393,7 +1393,7 @@ void WsCpu::MulUnsigned(T x, T y) _state.Flags.AuxCarry = false; _state.Flags.Parity = false; _state.Flags.Sign = false; - _state.Flags.Zero = _console->GetModel() != WsModel::Monochrome; + _state.Flags.Zero = _console->GetModel() > WsModel::Monochrome; _mulOverflow = overflow; diff --git a/Core/WS/WsDefaultVideoFilter.cpp b/Core/WS/WsDefaultVideoFilter.cpp index 08771017e..2cf7fd086 100644 --- a/Core/WS/WsDefaultVideoFilter.cpp +++ b/Core/WS/WsDefaultVideoFilter.cpp @@ -37,7 +37,7 @@ void WsDefaultVideoFilter::InitLookupTable() uint8_t b = rgb444 & 0xF; if(_adjustColors) { - if(_console->GetModel() == WsModel::Monochrome) { + if(_console->GetModel() <= WsModel::Monochrome) { //Same formula as what asie recently implemented for ares auto applyGamma = [](uint32_t color, uint32_t min, uint32_t max) { return min + (uint32_t)(pow(color / 15.0, 1 / 2.2) * (max - min)); diff --git a/Core/WS/WsMemoryManager.cpp b/Core/WS/WsMemoryManager.cpp index db1a38643..8f3f49c3b 100644 --- a/Core/WS/WsMemoryManager.cpp +++ b/Core/WS/WsMemoryManager.cpp @@ -217,7 +217,7 @@ bool WsMemoryManager::IsUnmappedPort(uint16_t port) uint8_t WsMemoryManager::GetUnmappedPort() { - return _console->GetModel() == WsModel::Monochrome ? 0x90 : 0; + return _console->GetModel() <= WsModel::Monochrome ? 0x90 : 0; } uint8_t WsMemoryManager::InternalReadPort(uint16_t port, bool isWordAccess) @@ -226,9 +226,9 @@ uint8_t WsMemoryManager::InternalReadPort(uint16_t port, bool isWordAccess) if(port <= 0x3F) { return _ppu->ReadPort(port); - } else if(port >= 0x40 && port <= 0x53 && _console->GetModel() != WsModel::Monochrome) { + } else if(port >= 0x40 && port <= 0x53 && _console->GetModel() > WsModel::Monochrome) { return _state.ColorEnabled ? _dmaController->ReadPort(port) : 0; - } else if(port >= 0x6A && port <= 0x6B && _console->GetModel() != WsModel::Monochrome) { + } else if(port >= 0x6A && port <= 0x6B && _console->GetModel() > WsModel::Monochrome) { return _apu->Read(port); //HyperVoice } else if(port >= 0x70 && port <= 0x77 && _console->GetModel() == WsModel::SwanCrystal) { return _ppu->ReadLcdConfigPort(port); @@ -237,7 +237,7 @@ uint8_t WsMemoryManager::InternalReadPort(uint16_t port, bool isWordAccess) } else if(port >= 0xA2 && port <= 0xAB && port != 0xA3) { return _timer->ReadPort(port); } else if(port >= 0xBA && port <= 0xBF) { - if(_console->GetModel() != WsModel::Monochrome || isWordAccess || !(port & 0x01)) { + if(_console->GetModel() > WsModel::Monochrome || isWordAccess || !(port & 0x01)) { return _eeprom->ReadPort(port - 0xBA); } else { return GetUnmappedPort(); @@ -246,10 +246,10 @@ uint8_t WsMemoryManager::InternalReadPort(uint16_t port, bool isWordAccess) return _cart->ReadPort(port); } else { switch(port) { - case 0x60: return _console->GetModel() == WsModel::Monochrome ? GetUnmappedPort() : _state.SystemControl2; + case 0x60: return _console->GetModel() <= WsModel::Monochrome ? GetUnmappedPort() : _state.SystemControl2; case 0x62: - if(_console->GetModel() == WsModel::Monochrome) { + if(_console->GetModel() <= WsModel::Monochrome) { return GetUnmappedPort(); } return ( @@ -259,7 +259,7 @@ uint8_t WsMemoryManager::InternalReadPort(uint16_t port, bool isWordAccess) case 0xA0: return ( (_state.BootRomDisabled ? 0x01 : 0) | - (_console->GetModel() == WsModel::Monochrome ? 0 : 0x02) | + (_console->GetModel() <= WsModel::Monochrome ? 0 : 0x02) | (_state.CartWordBus ? 0x04 : 0) | (_state.SlowRom ? 0x08 : 0) | 0x80 //mbc authentication? @@ -294,7 +294,7 @@ void WsMemoryManager::InternalWritePort(uint16_t port, uint8_t value, bool isWor _ppu->WritePort(port, value); } else if(port >= 0x40 && port <= 0x53 && _state.ColorEnabled) { return _dmaController->WritePort(port, value); - } else if(port >= 0x64 && port <= 0x6B && _console->GetModel() != WsModel::Monochrome) { + } else if(port >= 0x64 && port <= 0x6B && _console->GetModel() > WsModel::Monochrome) { _apu->Write(port, value); //HyperVoice } else if(port >= 0x70 && port <= 0x77 && !_state.BootRomDisabled && _console->GetModel() == WsModel::SwanCrystal) { _ppu->WriteLcdConfigPort(port, value); @@ -303,7 +303,7 @@ void WsMemoryManager::InternalWritePort(uint16_t port, uint8_t value, bool isWor } else if(port >= 0xA2 && port <= 0xAB && port != 0xA3) { _timer->WritePort(port, value); } else if(port >= 0xBA && port <= 0xBF) { - if(_console->GetModel() != WsModel::Monochrome || isWordAccess || !(port & 0x01)) { + if(_console->GetModel() > WsModel::Monochrome || isWordAccess || !(port & 0x01)) { _eeprom->WritePort(port - 0xBA, value); } } else if(port >= 0xC0) { @@ -311,7 +311,7 @@ void WsMemoryManager::InternalWritePort(uint16_t port, uint8_t value, bool isWor } else { switch(port) { case 0x60: - if(_console->GetModel() != WsModel::Monochrome) { + if(_console->GetModel() > WsModel::Monochrome) { _state.SystemControl2 = value & 0xEF; _state.ColorEnabled = value & 0x80; _state.Enable4bpp = value & 0x40; @@ -334,7 +334,7 @@ void WsMemoryManager::InternalWritePort(uint16_t port, uint8_t value, bool isWor break; case 0x62: - if(_console->GetModel() != WsModel::Monochrome) { + if(_console->GetModel() > WsModel::Monochrome) { _state.PowerOffRequested = value & 0x01; } break; diff --git a/Core/WS/WsPpu.cpp b/Core/WS/WsPpu.cpp index 2bf62ff5e..3e6aa782f 100644 --- a/Core/WS/WsPpu.cpp +++ b/Core/WS/WsPpu.cpp @@ -32,7 +32,7 @@ WsPpu::WsPpu(Emulator* emu, WsConsole* console, WsMemoryManager* memoryManager, _screenHeight = WsConstants::ScreenHeight; if(console->GetModel() == WsModel::Monochrome) { _screenHeight += 13; - } else { + } else if(console->GetModel() >= WsModel::Color) { _screenWidth += 13; } @@ -201,7 +201,7 @@ void WsPpu::DrawBackground() } uint16_t scanline = _state.Scanline; - uint16_t layerAddr = (uint16_t)(layer.MapAddressLatch & (_console->GetModel() == WsModel::Monochrome ? 0x3FFF : 0x7FFF)); + uint16_t layerAddr = (uint16_t)(layer.MapAddressLatch & (_console->GetModel() <= WsModel::Monochrome ? 0x3FFF : 0x7FFF)); for(int cycle = 0; cycle < WsConstants::ScreenWidth; cycle++) { int y = (scanline + layer.ScrollYLatch) & 0xFF; @@ -299,7 +299,7 @@ void WsPpu::ProcessSpriteCopy() _state.SpriteCountLatch = _state.SpriteCount; } - uint16_t baseAddr = _state.SpriteTableAddress & (_console->GetModel() == WsModel::Monochrome ? 0x3FFF : 0x7FFF); + uint16_t baseAddr = _state.SpriteTableAddress & (_console->GetModel() <= WsModel::Monochrome ? 0x3FFF : 0x7FFF); int i = _state.Cycle << 1; _spriteRam[i] = _vram[baseAddr + (((_state.FirstSpriteIndex * 4) + i) & 0x1FF)]; @@ -327,7 +327,7 @@ void WsPpu::DrawIcons() static constexpr uint16_t aux2[11] = { 0, 0, 0x70, 0xF8, 0x1FC, 0x1FC, 0x1FC, 0xF8, 0x70, 0, 0 }; static constexpr uint16_t aux1[11] = { 0, 0, 0, 0, 0x70, 0x70, 0x70, 0, 0, 0, 0 }; - if(_console->GetModel() == WsModel::Monochrome) { + if(_console->GetModel() <= WsModel::Monochrome) { uint16_t* start = _currentBuffer + (WsConstants::ScreenWidth * WsConstants::ScreenHeight); std::fill(start, start + WsConstants::ScreenWidth * 13, 0xFFF); } else { @@ -346,7 +346,7 @@ void WsPpu::DrawIcons() if(_emu->GetSettings()->GetWsConfig().AudioMode == WsAudioMode::Headphones) { DrawIcon(true, headphones, 65); } else { - if(_console->GetModel() == WsModel::Monochrome) { + if(_console->GetModel() <= WsModel::Monochrome) { switch(_console->GetApu()->GetMasterVolume()) { default: case 0: DrawIcon(true, volumeWsOff, 52); break; @@ -374,7 +374,7 @@ void WsPpu::DrawIcons() void WsPpu::DrawIcon(bool visible, const uint16_t icon[11], uint8_t position) { - if(!visible) { + if(!visible || _console->GetModel() == WsModel::PocketChallenge) { return; } @@ -441,7 +441,7 @@ uint8_t WsPpu::GetLcdStatus() uint8_t masterVolume = _console->GetApu()->GetMasterVolume(); uint8_t volumeLevel; - if(_console->GetModel() == WsModel::Monochrome) { + if(_console->GetModel() <= WsModel::Monochrome) { switch(masterVolume) { default: case 0: volumeLevel = 0; break; @@ -570,15 +570,15 @@ void WsPpu::WritePort(uint16_t port, uint8_t value) _state.BgWindow.Enabled = value & 0x20; break; - case 0x01: _state.BgColor = value & (_console->GetModel() == WsModel::Monochrome ? 0x07 : 0xFF); break; + case 0x01: _state.BgColor = value & (_console->GetModel() <= WsModel::Monochrome ? 0x07 : 0xFF); break; case 0x03: _state.IrqScanline = value; break; - case 0x04: _state.SpriteTableAddress = (value & (_console->GetModel() == WsModel::Monochrome ? 0x1F : 0x3F)) << 9; break; + case 0x04: _state.SpriteTableAddress = (value & (_console->GetModel() <= WsModel::Monochrome ? 0x1F : 0x3F)) << 9; break; case 0x05: _state.FirstSpriteIndex = value & 0x7F; break; case 0x06: _state.SpriteCount = value; break; case 0x07: - _state.ScreenAddress = value & (_console->GetModel() == WsModel::Monochrome ? 0x77 : 0xFF); + _state.ScreenAddress = value & (_console->GetModel() <= WsModel::Monochrome ? 0x77 : 0xFF); _state.BgLayers[0].MapAddress = (_state.ScreenAddress & 0x0F) << 11; _state.BgLayers[1].MapAddress = (_state.ScreenAddress & 0xF0) << 7; break; diff --git a/UI/Config/WsConfig.cs b/UI/Config/WsConfig.cs index 985b60a13..16812b31d 100644 --- a/UI/Config/WsConfig.cs +++ b/UI/Config/WsConfig.cs @@ -106,6 +106,7 @@ public struct InteropWsConfig public enum WsModel : byte { Auto, + PocketChallenge, Monochrome, Color, SwanCrystal diff --git a/UI/Debugger/RegisterViewer/WsRegisterViewer.cs b/UI/Debugger/RegisterViewer/WsRegisterViewer.cs index 40ac369a9..0043ad48b 100644 --- a/UI/Debugger/RegisterViewer/WsRegisterViewer.cs +++ b/UI/Debugger/RegisterViewer/WsRegisterViewer.cs @@ -30,7 +30,7 @@ private static RegisterViewerTab GetPpuTab(ref WsState ws) WsPpuState ppu = ws.Ppu; byte volumeLevel = 0; - if(ws.Model == WsModel.Monochrome) { + if(ws.Model <= WsModel.Monochrome) { switch(ws.Apu.InternalMasterVolume) { default: case 0: volumeLevel = 0; break; case 1: volumeLevel = 2; break; @@ -406,7 +406,7 @@ private static RegisterViewerTab GetMiscTab(ref WsState ws) new RegEntry("$A0", "System Control"), new RegEntry("$A0.0", "Boot ROM Disabled", mm.BootRomDisabled), - new RegEntry("$A0.1", "Color System", ws.Model != WsModel.Monochrome), + new RegEntry("$A0.1", "Color System", ws.Model > WsModel.Monochrome), new RegEntry("$A0.2", "16-bit ROM Bus", mm.CartWordBus), new RegEntry("$A0.3", "ROM Wait State", mm.SlowRom), diff --git a/UI/Localization/resources.en.xml b/UI/Localization/resources.en.xml index cadb77149..6f115db92 100644 --- a/UI/Localization/resources.en.xml +++ b/UI/Localization/resources.en.xml @@ -2873,6 +2873,7 @@ E Auto + Pocket Challenge V2 WonderSwan WonderSwan Color SwanCrystal diff --git a/UI/ViewModels/MainMenuViewModel.cs b/UI/ViewModels/MainMenuViewModel.cs index 6dbb553d6..c8f49de61 100644 --- a/UI/ViewModels/MainMenuViewModel.cs +++ b/UI/ViewModels/MainMenuViewModel.cs @@ -428,6 +428,7 @@ private void InitOptionsMenu(MainWindow wnd) GetPcEngineModelMenuItem(PceConsoleType.PcEngine), GetPcEngineModelMenuItem(PceConsoleType.SuperGrafx), GetPcEngineModelMenuItem(PceConsoleType.TurboGrafx), + GetWsModelMenuItem(WsModel.PocketChallenge), GetWsModelMenuItem(WsModel.Monochrome), GetWsModelMenuItem(WsModel.Color), GetWsModelMenuItem(WsModel.SwanCrystal), From ff3c6c428c889980611b76fff70f9869567bb73e Mon Sep 17 00:00:00 2001 From: Adrian Siekierka Date: Fri, 8 May 2026 23:52:08 +0200 Subject: [PATCH 2/7] WS: Emulate N/C EEPROM for Pocket Challenge V2 subtype. --- Core/WS/WsConsole.cpp | 22 +++++++++++++++++----- Core/WS/WsEeprom.cpp | 37 ++++++++++++++++++++++++++++--------- 2 files changed, 45 insertions(+), 14 deletions(-) diff --git a/Core/WS/WsConsole.cpp b/Core/WS/WsConsole.cpp index 92f9f2432..1b74c3a03 100644 --- a/Core/WS/WsConsole.cpp +++ b/Core/WS/WsConsole.cpp @@ -112,10 +112,16 @@ LoadRomResult WsConsole::LoadRom(VirtualFile& romFile) _emu->RegisterMemory(MemoryType::WsBootRom, _bootRom, _bootRomSize); } - _internalEepromSize = (uint32_t)(_model <= WsModel::Monochrome ? WsEepromSize::Size128 : WsEepromSize::Size2kb); - _internalEepromData = new uint8_t[_internalEepromSize]; - memset(_internalEepromData, 0, _internalEepromSize); - _emu->RegisterMemory(MemoryType::WsInternalEeprom, _internalEepromData, _internalEepromSize); + 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)); @@ -222,7 +228,13 @@ void WsConsole::InitPostBootRomState() cpu.CS = 0xFFFF; cpu.SP = 0x2000; - if(_model <= WsModel::Monochrome) { + 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; diff --git a/Core/WS/WsEeprom.cpp b/Core/WS/WsEeprom.cpp index a510694e6..d280bec37 100644 --- a/Core/WS/WsEeprom.cpp +++ b/Core/WS/WsEeprom.cpp @@ -53,8 +53,12 @@ WsEepromCommand WsEeprom::GetCommand() WsEepromSize WsEeprom::GetSize() { + if(_state.Size == WsEepromSize::Size0) { + //Process commands as if the eeprom was the monochrome model's 128-byte eeprom on PCv2 + return WsEepromSize::Size128; + } if(_isInternal && _state.Size == WsEepromSize::Size2kb && !_console->IsColorMode()) { - //Process commands as is the eeprom was the monochrome model's 128-byte eeprom when color mode is disabled + //Process commands as if the eeprom was the monochrome model's 128-byte eeprom when color mode is disabled return WsEepromSize::Size128; } return _state.Size; @@ -73,7 +77,7 @@ uint16_t WsEeprom::GetCommandAddress() void WsEeprom::WriteValue(uint16_t addr, uint16_t value) { - if(!_state.WriteDisabled && (!_state.InternalEepromWriteProtected || addr < 0x30)) { + if((uint32_t)_state.Size && !_state.WriteDisabled && (!_state.InternalEepromWriteProtected || addr < 0x30)) { if(_isInternal) { _emu->ProcessMemoryAccess(addr << 1, value); } else { @@ -94,6 +98,10 @@ string WsEeprom::ConvertToEepromString(string in) void WsEeprom::InitInternalEepromData() { + if(!(uint32_t)_state.Size) { + return; + } + memset(_data, 0, (uint32_t)_state.Size); string name; @@ -132,7 +140,12 @@ void WsEeprom::Run() switch(cmd) { case WsEepromCommand::Read: - _state.ReadBuffer = _data[addr << 1] | (_data[(addr << 1) + 1] << 8); + if(!(uint32_t)_state.Size) { + //TODOWS does this return all ones when EEPROM N/C? + _state.ReadBuffer = 0xFFFF; + } else { + _state.ReadBuffer = _data[addr << 1] | (_data[(addr << 1) + 1] << 8); + } _state.ReadDone = true; break; @@ -207,10 +220,12 @@ void WsEeprom::WritePort(uint8_t port, uint8_t value) } _state.Idle = false; - if(_isInternal) { - _emu->ProcessMemoryAccess(addr << 1, _state.ReadBuffer); - } else { - _emu->ProcessMemoryAccess(addr << 1, _state.ReadBuffer); + if((uint32_t)_state.Size) { + if(_isInternal) { + _emu->ProcessMemoryAccess(addr << 1, _state.ReadBuffer); + } else { + _emu->ProcessMemoryAccess(addr << 1, _state.ReadBuffer); + } } } } else if(write) { @@ -263,12 +278,16 @@ uint8_t WsEeprom::ReadPort(uint8_t port) void WsEeprom::LoadBattery() { - _emu->GetBatteryManager()->LoadBattery(_isInternal ? ".ieeprom" : ".eeprom", _data, (uint32_t)_state.Size); + if((uint32_t)_state.Size) { + _emu->GetBatteryManager()->LoadBattery(_isInternal ? ".ieeprom" : ".eeprom", _data, (uint32_t)_state.Size); + } } void WsEeprom::SaveBattery() { - _emu->GetBatteryManager()->SaveBattery(_isInternal ? ".ieeprom" : ".eeprom", _data, (uint32_t)_state.Size); + if((uint32_t)_state.Size) { + _emu->GetBatteryManager()->SaveBattery(_isInternal ? ".ieeprom" : ".eeprom", _data, (uint32_t)_state.Size); + } } void WsEeprom::Serialize(Serializer& s) From 620e7f072cc31341b07cda4d1d7298e560e724d8 Mon Sep 17 00:00:00 2001 From: Adrian Siekierka Date: Sat, 9 May 2026 09:20:08 +0200 Subject: [PATCH 3/7] WS: Support .pc2 ROM file extension. --- Core/WS/WsConsole.h | 2 +- PGOHelper/PGOHelper.cpp | 2 +- UI/Config/FileAssociationHelper.cs | 2 ++ UI/Utilities/FileDialogHelper.cs | 4 ++-- UI/Utilities/FolderHelper.cs | 2 +- Utilities/VirtualFile.cpp | 2 +- 6 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Core/WS/WsConsole.h b/Core/WS/WsConsole.h index 628016f73..d8fb87188 100644 --- a/Core/WS/WsConsole.h +++ b/Core/WS/WsConsole.h @@ -59,7 +59,7 @@ class WsConsole final : public IConsole WsConsole(Emulator* emu); ~WsConsole(); - static vector GetSupportedExtensions() { return { ".ws", ".wsc" }; } + static vector GetSupportedExtensions() { return { ".ws", ".wsc", ".pc2" }; } static vector GetSupportedSignatures() { return {}; } LoadRomResult LoadRom(VirtualFile& romFile) override; diff --git a/PGOHelper/PGOHelper.cpp b/PGOHelper/PGOHelper.cpp index 5a28f3928..9a9740768 100644 --- a/PGOHelper/PGOHelper.cpp +++ b/PGOHelper/PGOHelper.cpp @@ -48,7 +48,7 @@ int main(int argc, char* argv[]) romFolder = argv[1]; } - vector testRoms = GetFilesInFolder(romFolder, { ".sfc", ".gb", ".gbc", ".gbx", ".nes", ".pce", ".cue", ".sms", ".gg", ".sg", ".gba", ".col", ".ws", ".wsc" }); + vector testRoms = GetFilesInFolder(romFolder, { ".sfc", ".gb", ".gbc", ".gbx", ".nes", ".pce", ".cue", ".sms", ".gg", ".sg", ".gba", ".col", ".ws", ".wsc", ".pc2" }); PgoRunTest(testRoms, true); return 0; } diff --git a/UI/Config/FileAssociationHelper.cs b/UI/Config/FileAssociationHelper.cs index d01e030c4..b65eed216 100644 --- a/UI/Config/FileAssociationHelper.cs +++ b/UI/Config/FileAssociationHelper.cs @@ -94,6 +94,7 @@ static public void UpdateLinuxFileAssociations() CreateMimeType("x-mesen-ws", "ws", "WonderSwan ROM", mimeTypes, cfg.AssociateWsRomFiles); CreateMimeType("x-mesen-wsc", "wsc", "WonderSwan Color ROM", mimeTypes, cfg.AssociateWsRomFiles); + CreateMimeType("x-mesen-pc2", "pc2", "Pocket Challenge V2 ROM", mimeTypes, cfg.AssociateWsRomFiles); //Icon used for shortcuts ImageUtilities.BitmapFromAsset("Assets/MesenIcon.png").Save(Path.Combine(iconFolder, "MesenIcon.png")); @@ -217,6 +218,7 @@ static public void UpdateWindowsFileAssociations() FileAssociationHelper.UpdateFileAssociation("ws", cfg.AssociateWsRomFiles); FileAssociationHelper.UpdateFileAssociation("wsc", cfg.AssociateWsRomFiles); + FileAssociationHelper.UpdateFileAssociation("pc2", cfg.AssociateWsRomFiles); } static private void UpdateFileAssociation(string extension, bool associate) diff --git a/UI/Utilities/FileDialogHelper.cs b/UI/Utilities/FileDialogHelper.cs index 88a965f13..d6102a6fc 100644 --- a/UI/Utilities/FileDialogHelper.cs +++ b/UI/Utilities/FileDialogHelper.cs @@ -55,7 +55,7 @@ public class FileDialogHelper "*.pce", "*.sgx", "*.cue", "*.hes", "*.sms", "*.gg", "*.sg", "*.col", "*.gba", - "*.ws", "*.wsc", + "*.ws", "*.wsc", "*.pc2", "*.zip", "*.7z", "*.ips", "*.bps" } @@ -68,7 +68,7 @@ public class FileDialogHelper filter.Add(new FilePickerFileType("SMS / GG ROM files") { Patterns = new List() { "*.sms", "*.gg" } }); filter.Add(new FilePickerFileType("SG-1000 ROM files") { Patterns = new List() { "*.sg" } }); filter.Add(new FilePickerFileType("ColecoVision ROM files") { Patterns = new List() { "*.col" } }); - filter.Add(new FilePickerFileType("WonderSwan ROM files") { Patterns = new List() { "*.ws", "*.wsc" } }); + filter.Add(new FilePickerFileType("WonderSwan ROM files") { Patterns = new List() { "*.ws", "*.wsc", "*.pc2" } }); filter.Add(new FilePickerFileType("Patch files (IPS/BPS)") { Patterns = new List() { "*.ips", "*.bps" } }); } else if(ext == FileDialogHelper.FirmwareExt) { filter.Add(new FilePickerFileType("All firmware files") { Patterns = new List() { "*.sfc", "*.pce", "*.nes", "*.bin", "*.rom", "*.col", "*.sms", "*.gg", "*.gba", "*.ws", "*.wsc" } }); diff --git a/UI/Utilities/FolderHelper.cs b/UI/Utilities/FolderHelper.cs index 0d64c644c..704651f4f 100644 --- a/UI/Utilities/FolderHelper.cs +++ b/UI/Utilities/FolderHelper.cs @@ -17,7 +17,7 @@ public static class FolderHelper ".pce", ".sgx", ".cue", ".sms", ".gg", ".sg", ".col", ".gba", - ".ws", ".wsc" + ".ws", ".wsc", ".pc2" }; public static bool IsRomFile(string path) diff --git a/Utilities/VirtualFile.cpp b/Utilities/VirtualFile.cpp index 838bb8ad6..e1644bc24 100644 --- a/Utilities/VirtualFile.cpp +++ b/Utilities/VirtualFile.cpp @@ -19,7 +19,7 @@ const std::initializer_list VirtualFile::RomExtensions = { ".pce", ".sgx", ".cue", ".hes", ".sms", ".gg", ".sg", ".col", ".gba", - ".ws", ".wsc" + ".ws", ".wsc", ".pc2" }; // clang-format on From 18105c67ec89c91e78262bf47b48303fae94e7f5 Mon Sep 17 00:00:00 2001 From: Adrian Siekierka Date: Sat, 9 May 2026 09:26:47 +0200 Subject: [PATCH 4/7] WS: Support Pocket Challenge V2 keypad layout. --- Core/Core.vcxproj | 1 + Core/Core.vcxproj.filters | 3 + Core/Shared/SettingTypes.h | 4 +- Core/WS/Debugger/WsDebugger.cpp | 38 +++++---- Core/WS/Pcv2Controller.h | 81 +++++++++++++++++++ Core/WS/WsControlManager.cpp | 24 +++++- UI/Config/InputConfig.cs | 2 + UI/Config/WsConfig.cs | 9 ++- UI/Config/WsControllerConfig.cs | 125 +++++++++++++++++++++++++++++ UI/Localization/resources.en.xml | 12 +++ UI/ViewModels/WsConfigViewModel.cs | 13 ++- UI/Views/WsConfigView.axaml | 12 ++- 12 files changed, 301 insertions(+), 23 deletions(-) create mode 100644 Core/WS/Pcv2Controller.h create mode 100644 UI/Config/WsControllerConfig.cs diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 29fd33122..e194fa3f6 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -789,6 +789,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index d196c703e..4b6017526 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -2880,6 +2880,9 @@ WS + + WS + WS diff --git a/Core/Shared/SettingTypes.h b/Core/Shared/SettingTypes.h index 3d011728d..33170a713 100644 --- a/Core/Shared/SettingTypes.h +++ b/Core/Shared/SettingTypes.h @@ -239,7 +239,8 @@ enum class ControllerType //WS WsController, - WsControllerVertical + WsControllerVertical, + Pcv2Controller }; struct KeyMapping @@ -763,6 +764,7 @@ struct WsConfig { ControllerConfig ControllerHorizontal; ControllerConfig ControllerVertical; + ControllerConfig ControllerPcv2; WsModel Model = WsModel::Auto; bool UseBootRom = false; diff --git a/Core/WS/Debugger/WsDebugger.cpp b/Core/WS/Debugger/WsDebugger.cpp index 62f9e194d..2058564fe 100644 --- a/Core/WS/Debugger/WsDebugger.cpp +++ b/Core/WS/Debugger/WsDebugger.cpp @@ -7,6 +7,7 @@ #include "WS/Debugger/WsTraceLogger.h" #include "WS/Debugger/WsPpuTools.h" #include "WS/WsController.h" +#include "WS/Pcv2Controller.h" #include "WS/WsCpu.h" #include "WS/WsPpu.h" #include "WS/WsConsole.h" @@ -497,20 +498,29 @@ void WsDebugger::ProcessInputOverrides(DebugControllerState inputOverrides[8]) { BaseControlManager* controlManager = _console->GetControlManager(); for(int i = 0; i < 8; i++) { - shared_ptr controller = std::dynamic_pointer_cast(controlManager->GetControlDeviceByIndex(i)); - if(controller && inputOverrides[i].HasPressedButton()) { - controller->SetBitValue(WsController::Buttons::A, inputOverrides[i].A); - controller->SetBitValue(WsController::Buttons::B, inputOverrides[i].B); - controller->SetBitValue(WsController::Buttons::Start, inputOverrides[i].Start); - controller->SetBitValue(WsController::Buttons::Sound, inputOverrides[i].Select); - controller->SetBitValue(WsController::Buttons::Up, inputOverrides[i].Up); - controller->SetBitValue(WsController::Buttons::Down, inputOverrides[i].Down); - controller->SetBitValue(WsController::Buttons::Left, inputOverrides[i].Left); - controller->SetBitValue(WsController::Buttons::Right, inputOverrides[i].Right); - controller->SetBitValue(WsController::Buttons::Up2, inputOverrides[i].U); - controller->SetBitValue(WsController::Buttons::Down2, inputOverrides[i].D); - controller->SetBitValue(WsController::Buttons::Left2, inputOverrides[i].L); - controller->SetBitValue(WsController::Buttons::Right2, inputOverrides[i].R); + shared_ptr wsController = std::dynamic_pointer_cast(controlManager->GetControlDeviceByIndex(i)); + if(wsController && inputOverrides[i].HasPressedButton()) { + wsController->SetBitValue(WsController::Buttons::A, inputOverrides[i].A); + wsController->SetBitValue(WsController::Buttons::B, inputOverrides[i].B); + wsController->SetBitValue(WsController::Buttons::Start, inputOverrides[i].Start); + wsController->SetBitValue(WsController::Buttons::Sound, inputOverrides[i].Select); + wsController->SetBitValue(WsController::Buttons::Up, inputOverrides[i].Up); + wsController->SetBitValue(WsController::Buttons::Down, inputOverrides[i].Down); + wsController->SetBitValue(WsController::Buttons::Left, inputOverrides[i].Left); + wsController->SetBitValue(WsController::Buttons::Right, inputOverrides[i].Right); + wsController->SetBitValue(WsController::Buttons::Up2, inputOverrides[i].U); + wsController->SetBitValue(WsController::Buttons::Down2, inputOverrides[i].D); + wsController->SetBitValue(WsController::Buttons::Left2, inputOverrides[i].L); + wsController->SetBitValue(WsController::Buttons::Right2, inputOverrides[i].R); + } + + shared_ptr pcv2Controller = std::dynamic_pointer_cast(controlManager->GetControlDeviceByIndex(i)); + if(pcv2Controller && inputOverrides[i].HasPressedButton()) { + pcv2Controller->SetBitValue(Pcv2Controller::Buttons::Up, inputOverrides[i].Up); + pcv2Controller->SetBitValue(Pcv2Controller::Buttons::Down, inputOverrides[i].Down); + pcv2Controller->SetBitValue(Pcv2Controller::Buttons::Left, inputOverrides[i].Left); + pcv2Controller->SetBitValue(Pcv2Controller::Buttons::Right, inputOverrides[i].Right); + //TODOWS other buttons } } controlManager->RefreshHubState(); diff --git a/Core/WS/Pcv2Controller.h b/Core/WS/Pcv2Controller.h new file mode 100644 index 000000000..f6ca2d4a2 --- /dev/null +++ b/Core/WS/Pcv2Controller.h @@ -0,0 +1,81 @@ +#pragma once +#include "pch.h" +#include "WS/WsConsole.h" +#include "Shared/BaseControlDevice.h" +#include "Shared/Emulator.h" +#include "Shared/EmuSettings.h" +#include "Shared/InputHud.h" +#include "Utilities/Serializer.h" + +class Pcv2Controller : public BaseControlDevice +{ +private: + WsConsole* _console = nullptr; + +protected: + string GetKeyNames() override + { + return "udlrepcCv"; + } + + void InternalSetStateFromInput() override + { + for(KeyMapping& keyMapping : _keyMappings) { + for(int i = Buttons::Up; i <= Buttons::View; i++) { + SetPressedState(i, keyMapping.CustomKeys[i]); + } + } + } + + void RefreshStateBuffer() override + { + } + +public: + enum Buttons + { + Up = 0, + Down, + Left, + Right, + Esc, + Pass, + Circle, + Clear, + View + }; + + Pcv2Controller(Emulator* emu, WsConsole* console, uint8_t port, KeyMappingSet mappings) : BaseControlDevice(emu, ControllerType::Pcv2Controller, port, mappings) + { + _console = console; + } + + uint8_t ReadRam(uint16_t addr) override + { + return 0; + } + + void WriteRam(uint16_t addr, uint8_t value) override + { + } + + void InternalDrawController(InputHud& hud) override + { + // TODOWS + } + + vector GetKeyNameAssociations() override + { + return { + { "up", Buttons::Up }, + { "down", Buttons::Down }, + { "left", Buttons::Left }, + { "right", Buttons::Right }, + { "esc", Buttons::Esc }, + { "pass", Buttons::Pass }, + { "circle", Buttons::Circle }, + { "clear", Buttons::Clear }, + { "view", Buttons::View }, + }; + } +}; \ No newline at end of file diff --git a/Core/WS/WsControlManager.cpp b/Core/WS/WsControlManager.cpp index 5759cbfcd..181c4c019 100644 --- a/Core/WS/WsControlManager.cpp +++ b/Core/WS/WsControlManager.cpp @@ -3,6 +3,7 @@ #include "WS/WsControlManager.h" #include "WS/WsMemoryManager.h" #include "WS/WsController.h" +#include "WS/Pcv2Controller.h" #include "Shared/KeyManager.h" WsControlManager::WsControlManager(Emulator* emu, WsConsole* console) : BaseControlManager(emu, CpuType::Ws) @@ -24,6 +25,10 @@ shared_ptr WsControlManager::CreateControllerDevice(Controlle case ControllerType::WsControllerVertical: device.reset(new WsController(_emu, _console, port, cfg.ControllerHorizontal.Keys, cfg.ControllerVertical.Keys)); break; + + case ControllerType::Pcv2Controller: + device.reset(new Pcv2Controller(_emu, _console, port, cfg.ControllerPcv2.Keys)); + break; } return device; @@ -41,7 +46,7 @@ void WsControlManager::UpdateControlDevices() ClearDevices(); - shared_ptr device(CreateControllerDevice(ControllerType::WsController, 0)); + shared_ptr device(CreateControllerDevice(_console->GetModel() == WsModel::PocketChallenge ? ControllerType::Pcv2Controller : ControllerType::WsController, 0)); if(device) { RegisterControlDevice(device); } @@ -70,6 +75,23 @@ uint8_t WsControlManager::Read() result |= controller->IsPressed(WsController::A) ? 0x04 : 0; result |= controller->IsPressed(WsController::B) ? 0x08 : 0; } + } else if(controller->GetPort() == 0 && controller->GetControllerType() == ControllerType::Pcv2Controller) { + result |= 0x02; + if(_state.InputSelect & 0x10) { + result |= controller->IsPressed(Pcv2Controller::Clear) ? 0x01 : 0; + result |= controller->IsPressed(Pcv2Controller::Circle) ? 0x04 : 0; + result |= controller->IsPressed(Pcv2Controller::Pass) ? 0x08 : 0; + } + if(_state.InputSelect & 0x20) { + result |= controller->IsPressed(Pcv2Controller::View) ? 0x01 : 0; + result |= controller->IsPressed(Pcv2Controller::Esc) ? 0x04 : 0; + result |= controller->IsPressed(Pcv2Controller::Right) ? 0x08 : 0; + } + if(_state.InputSelect & 0x40) { + result |= controller->IsPressed(Pcv2Controller::Left) ? 0x01 : 0; + result |= controller->IsPressed(Pcv2Controller::Down) ? 0x04 : 0; + result |= controller->IsPressed(Pcv2Controller::Up) ? 0x08 : 0; + } } } diff --git a/UI/Config/InputConfig.cs b/UI/Config/InputConfig.cs index f9f867fe9..522bae829 100644 --- a/UI/Config/InputConfig.cs +++ b/UI/Config/InputConfig.cs @@ -410,6 +410,7 @@ public enum ControllerType //WonderSwan WsController, WsControllerVertical, + Pcv2Controller, } public static class ControllerTypeExtensions @@ -500,6 +501,7 @@ public static bool CanConfigure(this ControllerType type) case ControllerType.ColecoVisionController: case ControllerType.WsController: case ControllerType.WsControllerVertical: + case ControllerType.Pcv2Controller: return true; } diff --git a/UI/Config/WsConfig.cs b/UI/Config/WsConfig.cs index 16812b31d..760faeb0d 100644 --- a/UI/Config/WsConfig.cs +++ b/UI/Config/WsConfig.cs @@ -13,8 +13,9 @@ public class WsConfig : BaseConfig { [Reactive] public ConsoleOverrideConfig ConfigOverrides { get; set; } = new(); - [Reactive] public ControllerConfig ControllerHorizontal { get; set; } = new(); - [Reactive] public ControllerConfig ControllerVertical { get; set; } = new(); + [Reactive] public WsControllerConfig ControllerHorizontal { get; set; } = new(); + [Reactive] public WsControllerConfig ControllerVertical { get; set; } = new(); + [Reactive] public WsControllerConfig ControllerPcv2 { get; set; } = new(); [Reactive] public WsModel Model { get; set; } = WsModel.Auto; [Reactive] public bool UseBootRom { get; set; } = false; @@ -40,12 +41,14 @@ public void ApplyConfig() { ControllerHorizontal.Type = ControllerType.WsController; ControllerVertical.Type = ControllerType.WsControllerVertical; + ControllerPcv2.Type = ControllerType.Pcv2Controller; ConfigManager.Config.Video.ApplyConfig(); ConfigApi.SetWsConfig(new InteropWsConfig() { ControllerHorizontal = ControllerHorizontal.ToInterop(), ControllerVertical = ControllerVertical.ToInterop(), + ControllerPcv2 = ControllerPcv2.ToInterop(), Model = Model, UseBootRom = UseBootRom, @@ -73,6 +76,7 @@ internal void InitializeDefaults(DefaultKeyMappingType defaultMappings) { ControllerHorizontal.InitDefaults(defaultMappings, ControllerType.WsController); ControllerVertical.InitDefaults(defaultMappings, ControllerType.WsControllerVertical); + ControllerPcv2.InitDefaults(defaultMappings, ControllerType.Pcv2Controller); } } @@ -81,6 +85,7 @@ public struct InteropWsConfig { public InteropControllerConfig ControllerHorizontal; public InteropControllerConfig ControllerVertical; + public InteropControllerConfig ControllerPcv2; public WsModel Model; [MarshalAs(UnmanagedType.I1)] public bool UseBootRom; diff --git a/UI/Config/WsControllerConfig.cs b/UI/Config/WsControllerConfig.cs new file mode 100644 index 000000000..8812ded79 --- /dev/null +++ b/UI/Config/WsControllerConfig.cs @@ -0,0 +1,125 @@ +using Mesen.Interop; +using Mesen.Localization; +using Mesen.ViewModels; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Mesen.Config +{ + public class WsControllerConfig : ControllerConfig + { + public new WsKeyMapping Mapping1 { get => (WsKeyMapping)_mapping1; set => _mapping1 = value; } + public new WsKeyMapping Mapping2 { get => (WsKeyMapping)_mapping2; set => _mapping2 = value; } + public new WsKeyMapping Mapping3 { get => (WsKeyMapping)_mapping3; set => _mapping3 = value; } + public new WsKeyMapping Mapping4 { get => (WsKeyMapping)_mapping4; set => _mapping4 = value; } + + public WsControllerConfig() + { + _mapping1 = new WsKeyMapping(); + _mapping2 = new WsKeyMapping(); + _mapping3 = new WsKeyMapping(); + _mapping4 = new WsKeyMapping(); + } + } + + public class WsKeyMapping : KeyMapping + { + public UInt16[]? Pcv2Buttons { get; set; } = null; + + protected override UInt16[]? GetCustomButtons(ControllerType type) + { + return type switch { + ControllerType.Pcv2Controller => Pcv2Buttons, + _ => null + }; + } + + public override List ToCustomKeys(ControllerType type, int mappingIndex) + { + UInt16[]? buttonMappings = GetCustomButtons(type); + if(buttonMappings == null) { + if(GetDefaultCustomKeys(type, null) != null) { + if(mappingIndex == 0) { + SetDefaultKeys(type, null); + } else { + ClearKeys(type); + } + } + + buttonMappings = GetCustomButtons(type); + if(buttonMappings == null) { + return new List(); + } + } + + List keys = type switch { + ControllerType.Pcv2Controller => Enum.GetValues().Select(val => new CustomKeyMapping(ResourceHelper.GetEnumText(val), buttonMappings, (int)val)).ToList(), + _ => new() + }; + + keys.Sort((a, b) => a.Name.CompareTo(b.Name)); + + return keys; + } + + public override void ClearKeys(ControllerType type) + { + switch(type) { + case ControllerType.Pcv2Controller: + Pcv2Buttons = new UInt16[9]; + break; + + case ControllerType.WsController: + case ControllerType.WsControllerVertical: + base.ClearKeys(type); + break; + } + } + + public override UInt16[]? GetDefaultCustomKeys(ControllerType type, KeyPresetType? preset) + { + switch(type) { + case ControllerType.Pcv2Controller: + return new UInt16[9] { + InputApi.GetKeyCode("Up Arrow"), + InputApi.GetKeyCode("Down Arrow"), + InputApi.GetKeyCode("Left Arrow"), + InputApi.GetKeyCode("Right Arrow"), + InputApi.GetKeyCode("S"), + InputApi.GetKeyCode("Z"), + InputApi.GetKeyCode("X"), + InputApi.GetKeyCode("C"), + InputApi.GetKeyCode("A") + }; + + default: + return null; + } + } + + public override void SetDefaultKeys(ControllerType type, KeyPresetType? preset) + { + switch(type) { + case ControllerType.Pcv2Controller: Pcv2Buttons = GetDefaultCustomKeys(type, preset); break; + + default: + base.SetDefaultKeys(type, preset); + break; + } + } + } + + public enum Pcv2Buttons + { + Up, + Down, + Left, + Right, + Esc, + Pass, + Circle, + Clear, + View + }; +} diff --git a/UI/Localization/resources.en.xml b/UI/Localization/resources.en.xml index 6f115db92..aa7cf4a28 100644 --- a/UI/Localization/resources.en.xml +++ b/UI/Localization/resources.en.xml @@ -644,6 +644,7 @@ Controllers Controller (Horizontal) Controller (Vertical) + Controller (Pocket Challenge V2) Setup @@ -2373,6 +2374,17 @@ E Button - Right Button - Left + + Up + Down + Left + Right + Esc + View + Circle + Clear + Pass + NES - Front loader (NES-001) NES - Top loader (NES-101) diff --git a/UI/ViewModels/WsConfigViewModel.cs b/UI/ViewModels/WsConfigViewModel.cs index 49389a0e1..343b42f9b 100644 --- a/UI/ViewModels/WsConfigViewModel.cs +++ b/UI/ViewModels/WsConfigViewModel.cs @@ -19,6 +19,7 @@ public class WsConfigViewModel : DisposableViewModel public ReactiveCommand SetupPlayerHorizontal { get; } public ReactiveCommand SetupPlayerVertical { get; } + public ReactiveCommand SetupPlayerPcv2 { get; } public WsConfigViewModel() { @@ -27,8 +28,10 @@ public WsConfigViewModel() IObservable button1Enabled = this.WhenAnyValue(x => x.Config.ControllerHorizontal.Type, x => x.CanConfigure()); IObservable button2Enabled = this.WhenAnyValue(x => x.Config.ControllerVertical.Type, x => x.CanConfigure()); + IObservable button3Enabled = this.WhenAnyValue(x => x.Config.ControllerPcv2.Type, x => x.CanConfigure()); SetupPlayerHorizontal = ReactiveCommand.Create