PIO: Add basic flash cartridge support
This commit is contained in:
parent
2f6eaa1d43
commit
5687dd22bd
|
@ -50,6 +50,7 @@
|
|||
X(PlatformMisc) \
|
||||
X(PostProcessing) \
|
||||
X(ProgressCallback) \
|
||||
X(PIO) \
|
||||
X(ReShadeFXShader) \
|
||||
X(Recompiler) \
|
||||
X(SDL) \
|
||||
|
|
|
@ -106,6 +106,8 @@ add_library(core
|
|||
pcdrv.h
|
||||
performance_counters.cpp
|
||||
performance_counters.h
|
||||
pio.cpp
|
||||
pio.h
|
||||
playstation_mouse.cpp
|
||||
playstation_mouse.h
|
||||
psf_loader.cpp
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "interrupt_controller.h"
|
||||
#include "mdec.h"
|
||||
#include "pad.h"
|
||||
#include "pio.h"
|
||||
#include "psf_loader.h"
|
||||
#include "settings.h"
|
||||
#include "sio.h"
|
||||
|
@ -43,8 +44,6 @@
|
|||
|
||||
LOG_CHANNEL(Bus);
|
||||
|
||||
// TODO: Get rid of page code bits, instead use page faults to track SMC.
|
||||
|
||||
// Exports for external debugger access
|
||||
#ifndef __ANDROID__
|
||||
namespace Exports {
|
||||
|
@ -139,8 +138,6 @@ std::array<TickCount, 3> g_bios_access_time = {};
|
|||
std::array<TickCount, 3> g_cdrom_access_time = {};
|
||||
std::array<TickCount, 3> g_spu_access_time = {};
|
||||
|
||||
static std::vector<u8> s_exp1_rom;
|
||||
|
||||
static MEMCTRL s_MEMCTRL = {};
|
||||
static RAM_SIZE_REG s_RAM_SIZE = {};
|
||||
|
||||
|
@ -518,6 +515,8 @@ void Bus::RecalculateMemoryTimings()
|
|||
CalculateMemoryTiming(s_MEMCTRL.cdrom_delay_size, s_MEMCTRL.common_delay);
|
||||
std::tie(g_spu_access_time[0], g_spu_access_time[1], g_spu_access_time[2]) =
|
||||
CalculateMemoryTiming(s_MEMCTRL.spu_delay_size, s_MEMCTRL.common_delay);
|
||||
std::tie(g_exp1_access_time[0], g_exp1_access_time[1], g_exp1_access_time[2]) =
|
||||
CalculateMemoryTiming(s_MEMCTRL.exp1_delay_size, s_MEMCTRL.common_delay);
|
||||
|
||||
TRACE_LOG("BIOS Memory Timing: {} bit bus, byte={}, halfword={}, word={}",
|
||||
s_MEMCTRL.bios_delay_size.data_bus_16bit ? 16 : 8, g_bios_access_time[0] + 1, g_bios_access_time[1] + 1,
|
||||
|
@ -528,6 +527,9 @@ void Bus::RecalculateMemoryTimings()
|
|||
TRACE_LOG("SPU Memory Timing: {} bit bus, byte={}, halfword={}, word={}",
|
||||
s_MEMCTRL.spu_delay_size.data_bus_16bit ? 16 : 8, g_spu_access_time[0] + 1, g_spu_access_time[1] + 1,
|
||||
g_spu_access_time[2] + 1);
|
||||
TRACE_LOG("EXP1 Memory Timing: {} bit bus, byte={}, halfword={}, word={}",
|
||||
s_MEMCTRL.spu_delay_size.data_bus_16bit ? 16 : 8, g_spu_access_time[0] + 1, g_spu_access_time[1] + 1,
|
||||
g_spu_access_time[2] + 1);
|
||||
}
|
||||
|
||||
void* Bus::GetFastmemBase(bool isc)
|
||||
|
@ -926,11 +928,6 @@ std::optional<PhysicalMemoryAddress> Bus::SearchMemory(PhysicalMemoryAddress sta
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
void Bus::SetExpansionROM(std::vector<u8> data)
|
||||
{
|
||||
s_exp1_rom = std::move(data);
|
||||
}
|
||||
|
||||
void Bus::AddTTYCharacter(char ch)
|
||||
{
|
||||
if (ch == '\r')
|
||||
|
@ -1503,53 +1500,49 @@ u32 Bus::EXP1ReadHandler(VirtualMemoryAddress address)
|
|||
{
|
||||
BUS_CYCLES(g_exp1_access_time[static_cast<u32>(size)]);
|
||||
|
||||
// TODO: auto-increment should be handled elsewhere...
|
||||
|
||||
const u32 offset = address & EXP1_MASK;
|
||||
u32 value;
|
||||
if (s_exp1_rom.empty())
|
||||
u32 ret;
|
||||
|
||||
if constexpr (size >= MemoryAccessSize::HalfWord)
|
||||
{
|
||||
// EXP1 not present.
|
||||
value = UINT32_C(0xFFFFFFFF);
|
||||
}
|
||||
else if (offset == 0x20018)
|
||||
{
|
||||
// Bit 0 - Action Replay On/Off
|
||||
value = UINT32_C(1);
|
||||
ret = g_pio_device->ReadHandler(offset);
|
||||
ret |= ZeroExtend32(g_pio_device->ReadHandler(offset + 1)) << 8;
|
||||
if constexpr (size == MemoryAccessSize::Word)
|
||||
{
|
||||
ret |= ZeroExtend32(g_pio_device->ReadHandler(offset + 2)) << 16;
|
||||
ret |= ZeroExtend32(g_pio_device->ReadHandler(offset + 3)) << 24;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const u32 transfer_size = u32(1) << static_cast<u32>(size);
|
||||
if ((offset + transfer_size) > s_exp1_rom.size())
|
||||
{
|
||||
value = UINT32_C(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if constexpr (size == MemoryAccessSize::Byte)
|
||||
{
|
||||
value = ZeroExtend32(s_exp1_rom[offset]);
|
||||
}
|
||||
else if constexpr (size == MemoryAccessSize::HalfWord)
|
||||
{
|
||||
u16 halfword;
|
||||
std::memcpy(&halfword, &s_exp1_rom[offset], sizeof(halfword));
|
||||
value = ZeroExtend32(halfword);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::memcpy(&value, &s_exp1_rom[offset], sizeof(value));
|
||||
}
|
||||
|
||||
// Log_DevPrintf("EXP1 read: 0x%08X -> 0x%08X", address, value);
|
||||
}
|
||||
ret = ZeroExtend32(g_pio_device->ReadHandler(offset));
|
||||
}
|
||||
|
||||
return value;
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<MemoryAccessSize size>
|
||||
void Bus::EXP1WriteHandler(VirtualMemoryAddress address, u32 value)
|
||||
{
|
||||
WARNING_LOG("EXP1 write: 0x{:08X} <- 0x{:08X}", address, value);
|
||||
// TODO: auto-increment should be handled elsewhere...
|
||||
|
||||
const u32 offset = address & EXP1_MASK;
|
||||
if constexpr (size >= MemoryAccessSize::HalfWord)
|
||||
{
|
||||
g_pio_device->WriteHandler(offset, Truncate8(value));
|
||||
g_pio_device->WriteHandler(offset + 1, Truncate8(value >> 8));
|
||||
if constexpr (size == MemoryAccessSize::Word)
|
||||
{
|
||||
g_pio_device->WriteHandler(offset + 2, Truncate8(value >> 16));
|
||||
g_pio_device->WriteHandler(offset + 3, Truncate8(value >> 24));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
g_pio_device->WriteHandler(offset, Truncate8(value));
|
||||
}
|
||||
}
|
||||
|
||||
template<MemoryAccessSize size>
|
||||
|
|
|
@ -5,15 +5,11 @@
|
|||
|
||||
#include "types.h"
|
||||
|
||||
#include "common/bitfield.h"
|
||||
|
||||
#include <array>
|
||||
#include <bitset>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
class Error;
|
||||
|
||||
|
@ -148,8 +144,6 @@ void* GetFastmemBase(bool isc);
|
|||
void RemapFastmemViews();
|
||||
bool CanUseFastmemForAddress(VirtualMemoryAddress address);
|
||||
|
||||
void SetExpansionROM(std::vector<u8> data);
|
||||
|
||||
extern std::bitset<RAM_8MB_CODE_PAGE_COUNT> g_ram_code_bits;
|
||||
extern u8* g_ram; // 2MB-8MB RAM
|
||||
extern u8* g_unprotected_ram; // RAM without page protection, use for debugger access.
|
||||
|
|
|
@ -69,6 +69,7 @@
|
|||
</ClCompile>
|
||||
<ClCompile Include="cpu_pgxp.cpp" />
|
||||
<ClCompile Include="performance_counters.cpp" />
|
||||
<ClCompile Include="pio.cpp" />
|
||||
<ClCompile Include="playstation_mouse.cpp" />
|
||||
<ClCompile Include="psf_loader.cpp" />
|
||||
<ClCompile Include="settings.cpp" />
|
||||
|
@ -148,6 +149,7 @@
|
|||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="cpu_pgxp.h" />
|
||||
<ClInclude Include="performance_counters.h" />
|
||||
<ClInclude Include="pio.h" />
|
||||
<ClInclude Include="playstation_mouse.h" />
|
||||
<ClInclude Include="psf_loader.h" />
|
||||
<ClInclude Include="save_state_version.h" />
|
||||
|
|
|
@ -66,6 +66,7 @@
|
|||
<ClCompile Include="cdrom_subq_replacement.cpp" />
|
||||
<ClCompile Include="performance_counters.cpp" />
|
||||
<ClCompile Include="jogcon.cpp" />
|
||||
<ClCompile Include="pio.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="types.h" />
|
||||
|
@ -141,6 +142,7 @@
|
|||
<ClInclude Include="performance_counters.h" />
|
||||
<ClInclude Include="system_private.h" />
|
||||
<ClInclude Include="jogcon.h" />
|
||||
<ClInclude Include="pio.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="gpu_sw_rasterizer.inl" />
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "gte.h"
|
||||
#include "host.h"
|
||||
#include "pcdrv.h"
|
||||
#include "pio.h"
|
||||
#include "settings.h"
|
||||
#include "system.h"
|
||||
#include "timing_event.h"
|
||||
|
@ -2693,6 +2694,14 @@ ALWAYS_INLINE_RELEASE bool CPU::DoInstructionRead(PhysicalMemoryAddress address,
|
|||
|
||||
return true;
|
||||
}
|
||||
else if (address >= EXP1_BASE && address < (EXP1_BASE + EXP1_SIZE))
|
||||
{
|
||||
g_pio_device->CodeReadHandler(address & EXP1_MASK, data, word_count);
|
||||
if constexpr (add_ticks)
|
||||
g_state.pending_ticks += g_exp1_access_time[static_cast<u32>(MemoryAccessSize::Word)] * word_count;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (raise_exceptions)
|
||||
|
|
|
@ -0,0 +1,863 @@
|
|||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#include "pio.h"
|
||||
#include "bus.h"
|
||||
#include "settings.h"
|
||||
#include "system.h"
|
||||
#include "types.h"
|
||||
|
||||
#include "util/state_wrapper.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/bitfield.h"
|
||||
#include "common/bitutils.h"
|
||||
#include "common/error.h"
|
||||
#include "common/file_system.h"
|
||||
#include "common/heap_array.h"
|
||||
#include "common/log.h"
|
||||
#include "common/path.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
LOG_CHANNEL(PIO);
|
||||
|
||||
namespace PIO {
|
||||
|
||||
// Atmel AT29C040A
|
||||
class Flash
|
||||
{
|
||||
public:
|
||||
static constexpr u32 TOTAL_SIZE = 512 * 1024;
|
||||
|
||||
Flash();
|
||||
~Flash();
|
||||
|
||||
ALWAYS_INLINE bool IsImageModified() const { return m_image_modified; }
|
||||
|
||||
bool LoadImage(const char* path, Error* error);
|
||||
bool SaveImage(const char* path, Error* error);
|
||||
|
||||
void Reset();
|
||||
bool DoState(StateWrapper& sw);
|
||||
|
||||
u8 Read(u32 offset);
|
||||
void CodeRead(u32 offset, u32* words, u32 word_count);
|
||||
void Write(u32 offset, u8 value);
|
||||
|
||||
private:
|
||||
static constexpr u32 SECTOR_SIZE = 256;
|
||||
static constexpr u32 SECTOR_COUNT = TOTAL_SIZE / SECTOR_SIZE;
|
||||
static constexpr TickCount PROGRAM_TIMER_CYCLES = 5080; // ~150us
|
||||
|
||||
enum : u8
|
||||
{
|
||||
// 3 byte commands
|
||||
FLASH_CMD_ENTER_ID_MODE = 0x90,
|
||||
FLASH_CMD_EXIT_ID_MODE = 0xF0,
|
||||
FLASH_CMD_WRITE_SECTOR_WITH_SDP = 0xA0,
|
||||
FLASH_CMD_BEGIN_5_BYTE_COMMAND = 0x80,
|
||||
|
||||
// 5 byte commands
|
||||
FLASH_CMD_WRITE_SECTOR_WITHOUT_SDP = 0x20,
|
||||
FLASH_CMD_ALT_ENTER_ID_MODE = 0x60,
|
||||
};
|
||||
|
||||
u8* SectorPtr(u32 sector);
|
||||
void PushCommandByte(u8 value);
|
||||
|
||||
void ProgramWrite(u32 offset, u8 value);
|
||||
bool CheckForProgramTimeout();
|
||||
void EndProgramming();
|
||||
|
||||
DynamicHeapArray<u8> m_data;
|
||||
GlobalTicks m_program_write_timeout = 0;
|
||||
std::array<u8, 6> m_command_buffer = {};
|
||||
bool m_flash_id_mode = false;
|
||||
bool m_write_enable = false;
|
||||
bool m_image_modified = false;
|
||||
u8 m_write_toggle_result = 0;
|
||||
u16 m_write_position = 0;
|
||||
u32 m_sector_address = 0;
|
||||
u32 m_max_data_address = 0;
|
||||
};
|
||||
|
||||
} // namespace PIO
|
||||
|
||||
PIO::Flash::Flash() = default;
|
||||
|
||||
PIO::Flash::~Flash() = default;
|
||||
|
||||
u8 PIO::Flash::Read(u32 offset)
|
||||
{
|
||||
if (m_flash_id_mode) [[unlikely]]
|
||||
{
|
||||
// Atmel AT29C040A
|
||||
static constexpr std::array<u8, 2> flash_id = {0x1F, 0xA4};
|
||||
return flash_id[offset & 1];
|
||||
}
|
||||
|
||||
// WARNING_LOG("FLASH READ 0x{:X} 0x{:X} @ {}", offset, g_exp1_rom[offset], System::GetGlobalTickCounter());
|
||||
|
||||
if (m_write_enable && !CheckForProgramTimeout()) [[unlikely]]
|
||||
{
|
||||
m_write_toggle_result ^= 0x40;
|
||||
WARNING_LOG("read while programming 0x{:02X}", m_write_toggle_result);
|
||||
EndProgramming();
|
||||
return m_write_toggle_result | 0x80;
|
||||
}
|
||||
|
||||
return (offset >= TOTAL_SIZE) ? 0xFFu : m_data[offset];
|
||||
}
|
||||
|
||||
void PIO::Flash::CodeRead(u32 offset, u32* words, u32 word_count)
|
||||
{
|
||||
DebugAssert((offset + (word_count * sizeof(u32))) < TOTAL_SIZE);
|
||||
std::memcpy(words, m_data.data() + offset, word_count * sizeof(u32));
|
||||
}
|
||||
|
||||
void PIO::Flash::PushCommandByte(u8 value)
|
||||
{
|
||||
for (u32 i = static_cast<u32>(std::size(m_command_buffer) - 1); i > 0; i--)
|
||||
m_command_buffer[i] = m_command_buffer[i - 1];
|
||||
m_command_buffer[0] = value;
|
||||
}
|
||||
|
||||
void PIO::Flash::Write(u32 offset, u8 value)
|
||||
{
|
||||
if (m_write_enable && !CheckForProgramTimeout())
|
||||
{
|
||||
ProgramWrite(offset, value);
|
||||
return;
|
||||
}
|
||||
|
||||
DEV_LOG("FLASH WRITE 0x{:X} 0x{:X}", offset, value);
|
||||
|
||||
// Ignore banked addresses
|
||||
offset &= 0x3FFFF;
|
||||
if (offset == 0x2AAA || offset == 0x5555)
|
||||
{
|
||||
PushCommandByte(value);
|
||||
|
||||
const auto& buf = m_command_buffer;
|
||||
if (buf[2] == 0xAA && buf[1] == 0x55)
|
||||
{
|
||||
if (value == FLASH_CMD_ENTER_ID_MODE)
|
||||
{
|
||||
DEV_LOG("Flash enter ID mode");
|
||||
m_flash_id_mode = true;
|
||||
}
|
||||
else if (value == FLASH_CMD_EXIT_ID_MODE)
|
||||
{
|
||||
DEV_LOG("Flash exit ID mode");
|
||||
m_flash_id_mode = false;
|
||||
}
|
||||
else if (value == FLASH_CMD_WRITE_SECTOR_WITH_SDP)
|
||||
{
|
||||
DEV_LOG("Flash write sector with SDP @ {}", System::GetGlobalTickCounter());
|
||||
m_write_enable = true;
|
||||
m_program_write_timeout = System::GetGlobalTickCounter() + PROGRAM_TIMER_CYCLES;
|
||||
}
|
||||
else if (buf[5] == 0xAA && buf[4] == 0x55 && buf[3] == 0x80)
|
||||
{
|
||||
if (value == FLASH_CMD_ALT_ENTER_ID_MODE)
|
||||
{
|
||||
DEV_LOG("Flash Alt Enter ID mode");
|
||||
m_flash_id_mode = true;
|
||||
}
|
||||
if (value == FLASH_CMD_WRITE_SECTOR_WITHOUT_SDP)
|
||||
{
|
||||
DEV_LOG("Flash Write sector WITHOUT SDP");
|
||||
m_write_enable = true;
|
||||
m_program_write_timeout = std::numeric_limits<GlobalTicks>::max();
|
||||
}
|
||||
else
|
||||
{
|
||||
ERROR_LOG("Unhandled 5-cycle flash command 0x{:02X}", value);
|
||||
}
|
||||
}
|
||||
else if (value != 0x80)
|
||||
{
|
||||
ERROR_LOG("Unhandled 3-cycle flash command 0x{:02X}", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PIO::Flash::ProgramWrite(u32 offset, u8 value)
|
||||
{
|
||||
// reset the timeout.. event system suckage, we need it from _this_ cycle, not the first
|
||||
m_program_write_timeout = std::max(m_program_write_timeout, System::GetGlobalTickCounter() + PROGRAM_TIMER_CYCLES);
|
||||
|
||||
static_assert((0x800 * 0x100) == TOTAL_SIZE);
|
||||
const u32 byte_address = (offset & 0xFFu);
|
||||
const u32 sector_address = (offset >> 8) & 0x7FFu;
|
||||
if (m_write_position == 0)
|
||||
{
|
||||
DEV_LOG("Writing to flash sector {} (offset 0x{:06X})", sector_address, sector_address * SECTOR_SIZE);
|
||||
m_sector_address = sector_address;
|
||||
|
||||
const u32 sector_data_end = (sector_address * SECTOR_SIZE) + SECTOR_SIZE;
|
||||
if (sector_data_end > m_max_data_address)
|
||||
{
|
||||
m_max_data_address = sector_data_end;
|
||||
m_image_modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (sector_address == m_sector_address) [[likely]]
|
||||
{
|
||||
u8* byte_ptr = SectorPtr(sector_address) + byte_address;
|
||||
m_image_modified |= (*byte_ptr != value);
|
||||
*byte_ptr = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
WARNING_LOG("Flash write: unexpected sector address of {}, expected {} (addr 0x{:05X}", sector_address,
|
||||
m_sector_address, offset);
|
||||
}
|
||||
|
||||
m_write_position++;
|
||||
if (m_write_position == SECTOR_SIZE)
|
||||
{
|
||||
// end of flash write
|
||||
EndProgramming();
|
||||
}
|
||||
}
|
||||
|
||||
bool PIO::Flash::CheckForProgramTimeout()
|
||||
{
|
||||
DebugAssert(m_write_enable);
|
||||
if (System::GetGlobalTickCounter() < m_program_write_timeout)
|
||||
return false;
|
||||
|
||||
WARNING_LOG("Flash program timeout at byte {}", m_write_position);
|
||||
|
||||
// kinda cheating here, the sector would normally get buffered and then written
|
||||
// but the flash isn't supposed to be readable during programming anyway...
|
||||
if (m_write_position > 0)
|
||||
{
|
||||
const u32 bytes_to_erase = SECTOR_SIZE - m_write_position;
|
||||
if (bytes_to_erase > 0)
|
||||
{
|
||||
WARNING_LOG("Erasing {} unwritten bytes in sector {} (0x{:05X})", bytes_to_erase, m_write_position,
|
||||
m_sector_address * SECTOR_SIZE);
|
||||
|
||||
u8* sector = SectorPtr(m_sector_address) + m_write_position;
|
||||
bool image_modified = false;
|
||||
for (u32 i = 0; i < bytes_to_erase; i++)
|
||||
{
|
||||
image_modified |= (sector[i] != 0xFF);
|
||||
sector[i] = 0xFF;
|
||||
}
|
||||
m_image_modified |= image_modified;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WARNING_LOG("No sector address set, skipping programming.");
|
||||
}
|
||||
|
||||
EndProgramming();
|
||||
return true;
|
||||
}
|
||||
|
||||
void PIO::Flash::EndProgramming()
|
||||
{
|
||||
m_write_enable = false;
|
||||
m_write_position = 0;
|
||||
m_sector_address = 0;
|
||||
m_write_toggle_result = 0;
|
||||
}
|
||||
|
||||
u8* PIO::Flash::SectorPtr(u32 sector)
|
||||
{
|
||||
DebugAssert(sector < SECTOR_COUNT);
|
||||
return (m_data.data() + sector * SECTOR_SIZE);
|
||||
}
|
||||
|
||||
bool PIO::Flash::LoadImage(const char* path, Error* error)
|
||||
{
|
||||
const FileSystem::ManagedCFilePtr fp = FileSystem::OpenManagedCFile(path, "rb", error);
|
||||
if (!fp)
|
||||
{
|
||||
Error::AddPrefixFmt(error, "Failed to open PIO flash image '{}': ", Path::GetFileName(path));
|
||||
return false;
|
||||
}
|
||||
|
||||
const s64 file_size = std::max<s64>(FileSystem::FSize64(fp.get(), error), 0);
|
||||
if (file_size > TOTAL_SIZE)
|
||||
{
|
||||
WARNING_LOG("PIO flash image is too large ({} bytes), only {} bytes will be read.", file_size, TOTAL_SIZE);
|
||||
}
|
||||
else if (file_size < TOTAL_SIZE)
|
||||
{
|
||||
DEV_LOG("PIO flash image is too small ({} bytes), {} bytes of padding will be added.", file_size,
|
||||
TOTAL_SIZE - file_size);
|
||||
}
|
||||
|
||||
const u32 read_size = static_cast<u32>(std::min<s64>(file_size, TOTAL_SIZE));
|
||||
m_data.resize(TOTAL_SIZE);
|
||||
if (read_size > 0 && std::fread(m_data.data(), read_size, 1, fp.get()) != 1)
|
||||
{
|
||||
Error::SetErrno(error, "Failed to read PIO flash image: ", errno);
|
||||
m_data.deallocate();
|
||||
return false;
|
||||
}
|
||||
|
||||
const u32 padding_size = TOTAL_SIZE - read_size;
|
||||
if (padding_size > 0)
|
||||
std::memset(m_data.data() + read_size, 0, padding_size);
|
||||
|
||||
m_max_data_address = read_size;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PIO::Flash::SaveImage(const char* path, Error* error)
|
||||
{
|
||||
WARNING_LOG("Writing PIO flash image '{}'", Path::GetFileName(path));
|
||||
|
||||
if (!FileSystem::WriteBinaryFile(path, m_data.cspan(0, m_max_data_address), error))
|
||||
{
|
||||
Error::AddPrefixFmt(error, "Failed to write PIO flash image '{}': ", Path::GetFileName(path));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PIO::Flash::Reset()
|
||||
{
|
||||
m_command_buffer.fill(0);
|
||||
m_flash_id_mode = false;
|
||||
m_write_enable = false;
|
||||
m_write_position = 0;
|
||||
m_sector_address = 0;
|
||||
m_program_write_timeout = 0;
|
||||
m_write_toggle_result = 0;
|
||||
}
|
||||
|
||||
bool PIO::Flash::DoState(StateWrapper& sw)
|
||||
{
|
||||
sw.DoBytes(m_data.data(), m_data.size());
|
||||
sw.DoBytes(m_command_buffer.data(), m_command_buffer.size());
|
||||
sw.Do(&m_flash_id_mode);
|
||||
sw.Do(&m_write_enable);
|
||||
sw.Do(&m_write_position);
|
||||
sw.Do(&m_sector_address);
|
||||
sw.Do(&m_program_write_timeout);
|
||||
sw.Do(&m_write_toggle_result);
|
||||
sw.Do(&m_image_modified);
|
||||
sw.Do(&m_max_data_address);
|
||||
return !sw.HasError();
|
||||
}
|
||||
|
||||
namespace PIO {
|
||||
|
||||
class NullDevice : public Device
|
||||
{
|
||||
public:
|
||||
NullDevice();
|
||||
~NullDevice() override;
|
||||
|
||||
bool Initialize(Error* error) override;
|
||||
|
||||
void Reset() override;
|
||||
void UpdateSettings(const Settings& old_settings) override;
|
||||
bool DoState(StateWrapper& sw) override;
|
||||
|
||||
u8 ReadHandler(u32 offset) override;
|
||||
void CodeReadHandler(u32 offset, u32* words, u32 word_count) override;
|
||||
void WriteHandler(u32 offset, u8 value) override;
|
||||
};
|
||||
|
||||
} // namespace PIO
|
||||
|
||||
PIO::NullDevice::NullDevice() = default;
|
||||
|
||||
PIO::NullDevice::~NullDevice() = default;
|
||||
|
||||
bool PIO::NullDevice::Initialize(Error* error)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void PIO::NullDevice::Reset()
|
||||
{
|
||||
}
|
||||
|
||||
void PIO::NullDevice::UpdateSettings(const Settings& old_settings)
|
||||
{
|
||||
}
|
||||
|
||||
bool PIO::NullDevice::DoState(StateWrapper& sw)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
u8 PIO::NullDevice::ReadHandler(u32 offset)
|
||||
{
|
||||
return 0xFFu;
|
||||
}
|
||||
|
||||
void PIO::NullDevice::CodeReadHandler(u32 offset, u32* words, u32 word_count)
|
||||
{
|
||||
std::memset(words, 0xFF, sizeof(u32) * word_count);
|
||||
}
|
||||
|
||||
void PIO::NullDevice::WriteHandler(u32 offset, u8 value)
|
||||
{
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
namespace PIO {
|
||||
|
||||
namespace {
|
||||
|
||||
class DatelCartDevice : public Device
|
||||
{
|
||||
public:
|
||||
DatelCartDevice();
|
||||
~DatelCartDevice() override;
|
||||
|
||||
bool Initialize(Error* error) override;
|
||||
|
||||
void Reset() override;
|
||||
void UpdateSettings(const Settings& old_settings) override;
|
||||
bool DoState(StateWrapper& sw) override;
|
||||
|
||||
u8 ReadHandler(u32 offset) override;
|
||||
void CodeReadHandler(u32 offset, u32* words, u32 word_count) override;
|
||||
void WriteHandler(u32 offset, u8 value) override;
|
||||
|
||||
private:
|
||||
Flash m_flash;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
} // namespace PIO
|
||||
|
||||
PIO::DatelCartDevice::DatelCartDevice() = default;
|
||||
|
||||
PIO::DatelCartDevice::~DatelCartDevice() = default;
|
||||
|
||||
bool PIO::DatelCartDevice::Initialize(Error* error)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void PIO::DatelCartDevice::Reset()
|
||||
{
|
||||
}
|
||||
|
||||
void PIO::DatelCartDevice::UpdateSettings(const Settings& old_settings)
|
||||
{
|
||||
}
|
||||
|
||||
bool PIO::DatelCartDevice::DoState(StateWrapper& sw)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
u8 PIO::DatelCartDevice::ReadHandler(u32 offset)
|
||||
{
|
||||
WARNING_LOG("Datel EXP1 read 0x{:08X}", offset);
|
||||
|
||||
if (offset < 0x20000)
|
||||
{
|
||||
// first 128KB of flash
|
||||
return m_flash.Read(offset);
|
||||
}
|
||||
else if (offset >= 0x40000 && offset < 0x60000) // 1F040000->1F05FFFF
|
||||
{
|
||||
// second 128KB of flash
|
||||
return m_flash.Read((offset - 0x40000) + 0x20000);
|
||||
}
|
||||
else if (offset == 0x20018)
|
||||
{
|
||||
// switch setting
|
||||
return 1u;
|
||||
}
|
||||
else if (offset == 0x20010)
|
||||
{
|
||||
// comms link STB pin state (bit 0)
|
||||
return 0u;
|
||||
}
|
||||
else if (offset == 0x60000)
|
||||
{
|
||||
// comms link data in
|
||||
return 0u;
|
||||
}
|
||||
else
|
||||
{
|
||||
WARNING_LOG("Unhandled Datel EXP1 read: 0x{:08X}", offset);
|
||||
return 0xFFu;
|
||||
}
|
||||
}
|
||||
|
||||
void PIO::DatelCartDevice::CodeReadHandler(u32 offset, u32* words, u32 word_count)
|
||||
{
|
||||
if (offset < 0x20000)
|
||||
m_flash.CodeRead(offset, words, word_count);
|
||||
else if (offset >= 0x40000 && offset < 0x60000) // 1F040000->1F05FFFF
|
||||
m_flash.CodeRead((offset - 0x40000) + 0x20000, words, word_count);
|
||||
else
|
||||
std::memset(words, 0xFF, sizeof(u32) * word_count);
|
||||
}
|
||||
|
||||
void PIO::DatelCartDevice::WriteHandler(u32 offset, u8 value)
|
||||
{
|
||||
WARNING_LOG("DATEL WRITE 0x{:08X} 0x{:08X}", offset, value);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Xplorer/Xploder
|
||||
namespace PIO {
|
||||
|
||||
namespace {
|
||||
|
||||
class XplorerCart : public Device
|
||||
{
|
||||
public:
|
||||
static constexpr u32 SRAM_SIZE = 128 * 1024;
|
||||
|
||||
XplorerCart();
|
||||
~XplorerCart() override;
|
||||
|
||||
bool Initialize(Error* error) override;
|
||||
void UpdateSettings(const Settings& old_settings) override;
|
||||
|
||||
void Reset() override;
|
||||
bool DoState(StateWrapper& sw) override;
|
||||
|
||||
u8 ReadHandler(u32 offset) override;
|
||||
void CodeReadHandler(u32 offset, u32* words, u32 word_count) override;
|
||||
void WriteHandler(u32 offset, u8 value) override;
|
||||
|
||||
private:
|
||||
ALWAYS_INLINE u32 GetFlashUpperBank() const { return m_memory_map.sram_bank ? (384 * 1024) : (256 * 1024); }
|
||||
|
||||
union MemoryMappingRegister
|
||||
{
|
||||
u8 bits;
|
||||
|
||||
BitField<u8, bool, 0, 1> pc_slct;
|
||||
BitField<u8, bool, 1, 1> pc_pe;
|
||||
BitField<u8, bool, 2, 1> pc_busy;
|
||||
BitField<u8, bool, 3, 1> pc_ack;
|
||||
BitField<u8, bool, 4, 1> sram_select;
|
||||
BitField<u8, bool, 5, 1> flash_bank;
|
||||
BitField<u8, bool, 6, 1> sram_bank;
|
||||
BitField<u8, bool, 7, 1> sram_bank_2;
|
||||
};
|
||||
|
||||
DynamicHeapArray<u8> m_sram;
|
||||
Flash m_flash;
|
||||
MemoryMappingRegister m_memory_map = {};
|
||||
bool m_switch_state = false;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace PIO
|
||||
|
||||
PIO::XplorerCart::XplorerCart()
|
||||
{
|
||||
m_sram.resize(SRAM_SIZE);
|
||||
}
|
||||
|
||||
PIO::XplorerCart::~XplorerCart()
|
||||
{
|
||||
if (g_settings.pio_flash_write_enable && m_flash.IsImageModified())
|
||||
{
|
||||
Error error;
|
||||
if (!m_flash.SaveImage(g_settings.pio_flash_image_path.c_str(), &error))
|
||||
ERROR_LOG("Failed to update Xplorer flash image: {}", error.GetDescription());
|
||||
}
|
||||
}
|
||||
|
||||
bool PIO::XplorerCart::Initialize(Error* error)
|
||||
{
|
||||
if (!m_flash.LoadImage(g_settings.pio_flash_image_path.c_str(), error))
|
||||
return false;
|
||||
|
||||
m_switch_state = g_settings.pio_switch_active;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PIO::XplorerCart::UpdateSettings(const Settings& old_settings)
|
||||
{
|
||||
m_switch_state = g_settings.pio_switch_active;
|
||||
}
|
||||
|
||||
void PIO::XplorerCart::Reset()
|
||||
{
|
||||
m_flash.Reset();
|
||||
std::memset(m_sram.data(), 0, m_sram.size());
|
||||
m_memory_map.bits = 0;
|
||||
}
|
||||
|
||||
bool PIO::XplorerCart::DoState(StateWrapper& sw)
|
||||
{
|
||||
m_flash.DoState(sw);
|
||||
sw.DoBytes(m_sram.data(), m_sram.size());
|
||||
sw.Do(&m_memory_map.bits);
|
||||
return !sw.HasError();
|
||||
}
|
||||
|
||||
u8 PIO::XplorerCart::ReadHandler(u32 offset)
|
||||
{
|
||||
// WARNING_LOG("Xplorer EXP1 read size {}: 0x{:08X}", 1u << (u32)size, address);
|
||||
|
||||
if (offset < 0x40000) // 1F000000->1F03FFFF
|
||||
{
|
||||
// first 256KB of flash
|
||||
return m_flash.Read(offset);
|
||||
}
|
||||
else if (offset < 0x60000) // 1F040000->1F05FFFF
|
||||
{
|
||||
// second 256KB of flash or SRAM
|
||||
offset &= 0x3FFFF;
|
||||
if (m_memory_map.sram_select)
|
||||
{
|
||||
DebugAssert(offset < SRAM_SIZE);
|
||||
return m_sram[offset];
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_flash.Read(offset | GetFlashUpperBank());
|
||||
}
|
||||
}
|
||||
else if (offset >= 0x60000 && offset < 0x70000)
|
||||
{
|
||||
// I/O, mirrored
|
||||
switch (offset & 0x07)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
// switch setting
|
||||
return 0xFEu | BoolToUInt8(m_switch_state);
|
||||
}
|
||||
|
||||
case 1:
|
||||
{
|
||||
// data from PC
|
||||
return 0u;
|
||||
}
|
||||
|
||||
case 2:
|
||||
{
|
||||
// handshake from PC
|
||||
return 0xFEu;
|
||||
}
|
||||
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
{
|
||||
// unknown
|
||||
WARNING_LOG("Unhandled Xplorer I/O register read: 0x{:08X}", offset);
|
||||
return 0xFFu;
|
||||
}
|
||||
|
||||
DefaultCaseIsUnreachable()
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WARNING_LOG("Unhandled Xplorer EXP1 read: 0x{:08X}", offset);
|
||||
return 0xFFu;
|
||||
}
|
||||
}
|
||||
|
||||
void PIO::XplorerCart::CodeReadHandler(u32 offset, u32* words, u32 word_count)
|
||||
{
|
||||
if ((offset + word_count) <= 0x40000)
|
||||
{
|
||||
m_flash.CodeRead(offset, words, word_count);
|
||||
}
|
||||
else if (offset >= 0x40000 && (offset + word_count) <= 0x60000)
|
||||
{
|
||||
// second 256KB of flash or SRAM
|
||||
const u32 bank_offset = offset - 0x40000;
|
||||
if (m_memory_map.sram_select)
|
||||
{
|
||||
DebugAssert(bank_offset < SRAM_SIZE);
|
||||
std::memcpy(words, &m_sram[bank_offset], word_count * sizeof(u32));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_flash.CodeRead(bank_offset, words, word_count * sizeof(u32));
|
||||
}
|
||||
}
|
||||
else if (offset < 0x60000) [[unlikely]]
|
||||
{
|
||||
// partial read of both banks
|
||||
if (offset < 0x40000)
|
||||
{
|
||||
const u32 words_from_first = (0x40000 - offset) / sizeof(u32);
|
||||
m_flash.CodeRead(offset, words, words_from_first);
|
||||
words += words_from_first;
|
||||
word_count -= words_from_first;
|
||||
offset += words_from_first * sizeof(u32);
|
||||
}
|
||||
|
||||
const u32 words_from_second = std::min(0x60000 - offset, word_count);
|
||||
const u32 second_bank_offset = offset - 0x40000;
|
||||
if (m_memory_map.sram_bank)
|
||||
{
|
||||
std::memcpy(words, &m_sram[second_bank_offset], words_from_second * sizeof(u32));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_flash.CodeRead(second_bank_offset + GetFlashUpperBank(), words, words_from_second);
|
||||
}
|
||||
|
||||
words += words_from_second;
|
||||
word_count -= words_from_second;
|
||||
if (word_count > 0)
|
||||
std::memset(words, 0xFF, sizeof(u32) * word_count);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::memset(words, 0xFF, sizeof(u32) * word_count);
|
||||
}
|
||||
}
|
||||
|
||||
void PIO::XplorerCart::WriteHandler(u32 offset, u8 value)
|
||||
{
|
||||
if (offset < 0x40000)
|
||||
{
|
||||
m_flash.Write(offset, value);
|
||||
}
|
||||
else if (offset < 0x60000)
|
||||
{
|
||||
const u32 bank_offset = offset - 0x40000;
|
||||
if (m_memory_map.sram_bank)
|
||||
{
|
||||
m_sram[bank_offset] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
const u32 flash_offset = m_memory_map.sram_bank ? (384 * 1024) : (256 * 1024);
|
||||
m_flash.Write(flash_offset + bank_offset, value);
|
||||
}
|
||||
}
|
||||
else if (offset == 0x60001)
|
||||
{
|
||||
DEV_LOG("Memory map <- 0x{:02X}", value);
|
||||
m_memory_map.bits = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
WARNING_LOG("Unhandled Xplorer WRITE 0x{:08X} 0x{:08X}", offset, value);
|
||||
}
|
||||
}
|
||||
|
||||
namespace PIO {
|
||||
|
||||
static std::unique_ptr<PIO::Device> CreateDevice(PIODeviceType type);
|
||||
|
||||
} // namespace PIO
|
||||
|
||||
std::unique_ptr<PIO::Device> g_pio_device;
|
||||
|
||||
PIO::Device::~Device() = default;
|
||||
|
||||
bool PIO::Initialize(Error* error)
|
||||
{
|
||||
g_pio_device = CreateDevice(g_settings.pio_device_type);
|
||||
Assert(g_pio_device);
|
||||
|
||||
if (!g_pio_device->Initialize(error))
|
||||
{
|
||||
g_pio_device.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PIO::UpdateSettings(const Settings& old_settings)
|
||||
{
|
||||
if (g_settings.pio_device_type != old_settings.pio_device_type)
|
||||
{
|
||||
Error error;
|
||||
g_pio_device.reset();
|
||||
g_pio_device = CreateDevice(g_settings.pio_device_type);
|
||||
if (!g_pio_device->Initialize(&error))
|
||||
{
|
||||
ERROR_LOG("Failed to create new PIO device: {}", error.GetDescription());
|
||||
g_pio_device = CreateDevice(PIODeviceType::None);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
g_pio_device->UpdateSettings(old_settings);
|
||||
}
|
||||
}
|
||||
|
||||
void PIO::Shutdown()
|
||||
{
|
||||
g_pio_device.reset();
|
||||
}
|
||||
|
||||
std::unique_ptr<PIO::Device> PIO::CreateDevice(PIODeviceType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case PIODeviceType::None:
|
||||
return std::make_unique<NullDevice>();
|
||||
|
||||
case PIODeviceType::XplorerCart:
|
||||
return std::make_unique<XplorerCart>();
|
||||
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void PIO::Reset()
|
||||
{
|
||||
g_pio_device->Reset();
|
||||
}
|
||||
|
||||
bool PIO::DoState(StateWrapper& sw)
|
||||
{
|
||||
PIODeviceType device_type = g_settings.pio_device_type;
|
||||
sw.Do(&device_type);
|
||||
|
||||
const size_t pio_state_pos = sw.GetPosition();
|
||||
u32 pio_state_size = 0;
|
||||
sw.Do(&pio_state_size);
|
||||
|
||||
if (device_type == g_settings.pio_device_type) [[likely]]
|
||||
{
|
||||
if (!g_pio_device->DoState(sw))
|
||||
return false;
|
||||
|
||||
// rewrite size field
|
||||
if (sw.IsWriting())
|
||||
{
|
||||
const size_t new_pos = sw.GetPosition();
|
||||
sw.SetPosition(pio_state_pos);
|
||||
pio_state_size = static_cast<u32>(new_pos - pio_state_pos);
|
||||
sw.Do(&pio_state_size);
|
||||
sw.SetPosition(new_pos);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WARNING_LOG("State contains PIO device {}, expected {}", Settings::GetPIODeviceTypeModeName(device_type),
|
||||
Settings::GetPIODeviceTypeModeName(g_settings.pio_device_type));
|
||||
g_pio_device->Reset();
|
||||
sw.SkipBytes(pio_state_size);
|
||||
}
|
||||
|
||||
return !sw.HasError();
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
class Error;
|
||||
|
||||
class StateWrapper;
|
||||
|
||||
struct Settings;
|
||||
|
||||
enum class PIODevice : u8;
|
||||
|
||||
namespace PIO {
|
||||
|
||||
// Exposed so Bus can call the handlers directly. Otherwise calls should go through the functions below.
|
||||
|
||||
class Device
|
||||
{
|
||||
public:
|
||||
virtual ~Device();
|
||||
|
||||
virtual bool Initialize(Error* error) = 0;
|
||||
virtual void UpdateSettings(const Settings& old_settings) = 0;
|
||||
|
||||
virtual void Reset() = 0;
|
||||
|
||||
virtual bool DoState(StateWrapper& sw) = 0;
|
||||
|
||||
virtual u8 ReadHandler(u32 offset) = 0;
|
||||
virtual void CodeReadHandler(u32 offset, u32* words, u32 word_count) = 0;
|
||||
virtual void WriteHandler(u32 offset, u8 value) = 0;
|
||||
};
|
||||
|
||||
extern bool Initialize(Error* error);
|
||||
extern void UpdateSettings(const Settings& old_settings);
|
||||
extern void Shutdown();
|
||||
|
||||
extern void Reset();
|
||||
extern bool DoState(StateWrapper& sw);
|
||||
|
||||
} // namespace PIO
|
||||
|
||||
extern std::unique_ptr<PIO::Device> g_pio_device;
|
|
@ -6,7 +6,7 @@
|
|||
#include "common/types.h"
|
||||
|
||||
static constexpr u32 SAVE_STATE_MAGIC = 0x43435544;
|
||||
static constexpr u32 SAVE_STATE_VERSION = 76;
|
||||
static constexpr u32 SAVE_STATE_VERSION = 77;
|
||||
static constexpr u32 SAVE_STATE_MINIMUM_VERSION = 42;
|
||||
|
||||
static_assert(SAVE_STATE_VERSION >= SAVE_STATE_MINIMUM_VERSION);
|
||||
|
|
|
@ -361,9 +361,6 @@ void Settings::Load(const SettingsInterface& si, const SettingsInterface& contro
|
|||
|
||||
use_old_mdec_routines = si.GetBoolValue("Hacks", "UseOldMDECRoutines", false);
|
||||
export_shared_memory = si.GetBoolValue("Hacks", "ExportSharedMemory", false);
|
||||
pcdrv_enable = si.GetBoolValue("PCDrv", "Enabled", false);
|
||||
pcdrv_enable_writes = si.GetBoolValue("PCDrv", "EnableWrites", false);
|
||||
pcdrv_root = si.GetStringValue("PCDrv", "Root");
|
||||
|
||||
dma_max_slice_ticks = si.GetIntValue("Hacks", "DMAMaxSliceTicks", DEFAULT_DMA_MAX_SLICE_TICKS);
|
||||
dma_halt_ticks = si.GetIntValue("Hacks", "DMAHaltTicks", DEFAULT_DMA_HALT_TICKS);
|
||||
|
@ -481,6 +478,17 @@ void Settings::Load(const SettingsInterface& si, const SettingsInterface& contro
|
|||
texture_replacements.config.vram_write_dump_height_threshold =
|
||||
si.GetUIntValue("TextureReplacements", "DumpVRAMWriteHeightThreshold", 128);
|
||||
|
||||
pio_device_type = ParsePIODeviceTypeName(
|
||||
si.GetTinyStringValue("PIO", "DeviceType", GetPIODeviceTypeModeName(DEFAULT_PIO_DEVICE_TYPE)))
|
||||
.value_or(DEFAULT_PIO_DEVICE_TYPE);
|
||||
pio_flash_image_path = si.GetStringValue("PIO", "FlashImagePath");
|
||||
pio_flash_write_enable = si.GetBoolValue("PIO", "FlashImageWriteEnable", false);
|
||||
pio_switch_active = si.GetBoolValue("PIO", "SwitchActive", true);
|
||||
|
||||
pcdrv_enable = si.GetBoolValue("PCDrv", "Enabled", false);
|
||||
pcdrv_enable_writes = si.GetBoolValue("PCDrv", "EnableWrites", false);
|
||||
pcdrv_root = si.GetStringValue("PCDrv", "Root");
|
||||
|
||||
#ifdef __ANDROID__
|
||||
// Android users are incredibly silly and don't understand that stretch is in the aspect ratio list...
|
||||
if (si.GetBoolValue("Display", "Stretch", false))
|
||||
|
@ -649,10 +657,6 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
|
|||
si.SetIntValue("Hacks", "GPUMaxRunAhead", gpu_max_run_ahead);
|
||||
}
|
||||
|
||||
si.SetBoolValue("PCDrv", "Enabled", pcdrv_enable);
|
||||
si.SetBoolValue("PCDrv", "EnableWrites", pcdrv_enable_writes);
|
||||
si.SetStringValue("PCDrv", "Root", pcdrv_root.c_str());
|
||||
|
||||
si.SetBoolValue("BIOS", "TTYLogging", bios_tty_logging);
|
||||
si.SetBoolValue("BIOS", "PatchFastBoot", bios_patch_fast_boot);
|
||||
si.SetBoolValue("BIOS", "FastForwardBoot", bios_fast_forward_boot);
|
||||
|
@ -746,6 +750,15 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
|
|||
texture_replacements.config.vram_write_dump_width_threshold);
|
||||
si.SetUIntValue("TextureReplacements", "DumpVRAMWriteHeightThreshold",
|
||||
texture_replacements.config.vram_write_dump_height_threshold);
|
||||
|
||||
si.SetStringValue("PIO", "DeviceType", GetPIODeviceTypeModeName(pio_device_type));
|
||||
si.SetStringValue("PIO", "FlashImagePath", pio_flash_image_path.c_str());
|
||||
si.SetBoolValue("PIO", "FlashImageWriteEnable", pio_flash_write_enable);
|
||||
si.SetBoolValue("PIO", "SwitchActive", pio_switch_active);
|
||||
|
||||
si.SetBoolValue("PCDrv", "Enabled", pcdrv_enable);
|
||||
si.SetBoolValue("PCDrv", "EnableWrites", pcdrv_enable_writes);
|
||||
si.SetStringValue("PCDrv", "Root", pcdrv_root.c_str());
|
||||
}
|
||||
|
||||
void Settings::Clear(SettingsInterface& si)
|
||||
|
@ -964,10 +977,11 @@ void Settings::FixIncompatibleSettings(bool display_osd_messages)
|
|||
g_settings.cdrom_mute_cd_audio = false;
|
||||
g_settings.texture_replacements.enable_vram_write_replacements = false;
|
||||
g_settings.use_old_mdec_routines = false;
|
||||
g_settings.pcdrv_enable = false;
|
||||
g_settings.bios_patch_fast_boot = false;
|
||||
g_settings.runahead_frames = 0;
|
||||
g_settings.rewind_enable = false;
|
||||
g_settings.pio_device_type = PIODeviceType::None;
|
||||
g_settings.pcdrv_enable = false;
|
||||
}
|
||||
|
||||
// fast forward boot requires fast boot
|
||||
|
@ -2176,6 +2190,42 @@ const char* Settings::GetSaveStateCompressionModeDisplayName(SaveStateCompressio
|
|||
"SaveStateCompressionMode");
|
||||
}
|
||||
|
||||
static constexpr const std::array s_pio_device_type_names = {
|
||||
"None",
|
||||
"XplorerCart",
|
||||
};
|
||||
static constexpr const std::array s_pio_device_type_display_names = {
|
||||
TRANSLATE_DISAMBIG_NOOP("Settings", "None", "PIODeviceType"),
|
||||
TRANSLATE_DISAMBIG_NOOP("Settings", "Xplorer/Xploder Cartridge", "PIODeviceType"),
|
||||
};
|
||||
static_assert(s_pio_device_type_names.size() == static_cast<size_t>(PIODeviceType::MaxCount));
|
||||
static_assert(s_pio_device_type_display_names.size() == static_cast<size_t>(PIODeviceType::MaxCount));
|
||||
|
||||
std::optional<PIODeviceType> Settings::ParsePIODeviceTypeName(const char* str)
|
||||
{
|
||||
u32 index = 0;
|
||||
for (const char* name : s_pio_device_type_names)
|
||||
{
|
||||
if (StringUtil::Strcasecmp(name, str) == 0)
|
||||
return static_cast<PIODeviceType>(index);
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const char* Settings::GetPIODeviceTypeModeName(PIODeviceType type)
|
||||
{
|
||||
return s_pio_device_type_names[static_cast<size_t>(type)];
|
||||
}
|
||||
|
||||
const char* Settings::GetPIODeviceTypeModeDisplayName(PIODeviceType type)
|
||||
{
|
||||
return Host::TranslateToCString("Settings", s_pio_device_type_display_names[static_cast<size_t>(type)],
|
||||
"PIODeviceType");
|
||||
}
|
||||
|
||||
std::string EmuFolders::AppRoot;
|
||||
std::string EmuFolders::DataRoot;
|
||||
std::string EmuFolders::Bios;
|
||||
|
|
|
@ -297,6 +297,11 @@ struct Settings
|
|||
|
||||
MultitapMode multitap_mode = DEFAULT_MULTITAP_MODE;
|
||||
|
||||
PIODeviceType pio_device_type = DEFAULT_PIO_DEVICE_TYPE;
|
||||
std::string pio_flash_image_path;
|
||||
bool pio_switch_active = true;
|
||||
bool pio_flash_write_enable = false;
|
||||
|
||||
std::string pcdrv_root;
|
||||
bool pcdrv_enable_writes = false;
|
||||
|
||||
|
@ -486,6 +491,10 @@ struct Settings
|
|||
static const char* GetSaveStateCompressionModeName(SaveStateCompressionMode mode);
|
||||
static const char* GetSaveStateCompressionModeDisplayName(SaveStateCompressionMode mode);
|
||||
|
||||
static std::optional<PIODeviceType> ParsePIODeviceTypeName(const char* str);
|
||||
static const char* GetPIODeviceTypeModeName(PIODeviceType type);
|
||||
static const char* GetPIODeviceTypeModeDisplayName(PIODeviceType type);
|
||||
|
||||
static constexpr GPURenderer DEFAULT_GPU_RENDERER = GPURenderer::Automatic;
|
||||
static constexpr GPUTextureFilter DEFAULT_GPU_TEXTURE_FILTER = GPUTextureFilter::Nearest;
|
||||
static constexpr GPULineDetectMode DEFAULT_GPU_LINE_DETECT_MODE = GPULineDetectMode::Disabled;
|
||||
|
@ -535,6 +544,7 @@ struct Settings
|
|||
static constexpr MemoryCardType DEFAULT_MEMORY_CARD_1_TYPE = MemoryCardType::PerGameTitle;
|
||||
static constexpr MemoryCardType DEFAULT_MEMORY_CARD_2_TYPE = MemoryCardType::None;
|
||||
static constexpr MultitapMode DEFAULT_MULTITAP_MODE = MultitapMode::Disabled;
|
||||
static constexpr PIODeviceType DEFAULT_PIO_DEVICE_TYPE = PIODeviceType::None;
|
||||
|
||||
static constexpr s32 DEFAULT_ACHIEVEMENT_NOTIFICATION_TIME = 5;
|
||||
static constexpr s32 DEFAULT_LEADERBOARD_NOTIFICATION_TIME = 10;
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "pad.h"
|
||||
#include "pcdrv.h"
|
||||
#include "performance_counters.h"
|
||||
#include "pio.h"
|
||||
#include "psf_loader.h"
|
||||
#include "save_state_version.h"
|
||||
#include "sio.h"
|
||||
|
@ -1926,6 +1927,9 @@ bool System::Initialize(std::unique_ptr<CDImage> disc, DiscRegion disc_region, b
|
|||
CPU::Initialize();
|
||||
CDROM::Initialize();
|
||||
|
||||
if (!PIO::Initialize(error))
|
||||
return false;
|
||||
|
||||
// CDROM before GPU, that way we don't modeswitch.
|
||||
if (disc &&
|
||||
!CDROM::InsertMedia(std::move(disc), disc_region, s_state.running_game_serial, s_state.running_game_title, error))
|
||||
|
@ -2011,6 +2015,7 @@ void System::DestroySystem()
|
|||
CDROM::Shutdown();
|
||||
g_gpu.reset();
|
||||
DMA::Shutdown();
|
||||
PIO::Shutdown();
|
||||
CPU::CodeCache::Shutdown();
|
||||
CPU::PGXP::Shutdown();
|
||||
CPU::Shutdown();
|
||||
|
@ -2548,6 +2553,12 @@ bool System::DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_di
|
|||
if (!sw.DoMarker("SIO") || !SIO::DoState(sw))
|
||||
return false;
|
||||
|
||||
if (sw.GetVersion() >= 77)
|
||||
{
|
||||
if (!sw.DoMarker("PIO") || !PIO::DoState(sw))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sw.DoMarker("Events") || !TimingEvents::DoState(sw))
|
||||
return false;
|
||||
|
||||
|
@ -2628,6 +2639,7 @@ void System::InternalReset()
|
|||
CPU::PGXP::Initialize();
|
||||
|
||||
Bus::Reset();
|
||||
PIO::Reset();
|
||||
DMA::Reset();
|
||||
InterruptController::Reset();
|
||||
g_gpu->Reset(true);
|
||||
|
@ -4491,6 +4503,17 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
|
|||
UpdateSpeedLimiterState();
|
||||
}
|
||||
|
||||
if (g_settings.multitap_mode != old_settings.multitap_mode)
|
||||
UpdateMultitaps();
|
||||
|
||||
if (g_settings.pio_device_type != old_settings.pio_device_type ||
|
||||
g_settings.pio_flash_image_path != old_settings.pio_flash_image_path ||
|
||||
g_settings.pio_flash_write_enable != old_settings.pio_flash_write_enable ||
|
||||
g_settings.pio_switch_active != old_settings.pio_switch_active)
|
||||
{
|
||||
PIO::UpdateSettings(old_settings);
|
||||
}
|
||||
|
||||
if (g_settings.display_show_gpu_usage != old_settings.display_show_gpu_usage)
|
||||
g_gpu_device->SetGPUTimingEnabled(g_settings.display_show_gpu_usage);
|
||||
|
||||
|
@ -4537,9 +4560,6 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
|
|||
ImGuiManager::SetScreenMargin(g_settings.display_osd_margin);
|
||||
}
|
||||
|
||||
if (g_settings.multitap_mode != old_settings.multitap_mode)
|
||||
UpdateMultitaps();
|
||||
|
||||
Achievements::UpdateSettings(old_settings);
|
||||
|
||||
FullscreenUI::CheckForConfigChanges(old_settings);
|
||||
|
@ -4739,6 +4759,8 @@ void System::WarnAboutUnsafeSettings()
|
|||
APPEND_SUBMESSAGE(TRANSLATE_SV("System", "VRAM write texture replacements disabled."));
|
||||
if (g_settings.use_old_mdec_routines)
|
||||
APPEND_SUBMESSAGE(TRANSLATE_SV("System", "Use old MDEC routines disabled."));
|
||||
if (g_settings.pio_device_type != PIODeviceType::None)
|
||||
APPEND_SUBMESSAGE(TRANSLATE_SV("System", "PIO device removed."));
|
||||
if (g_settings.pcdrv_enable)
|
||||
APPEND_SUBMESSAGE(TRANSLATE_SV("System", "PCDrv disabled."));
|
||||
if (g_settings.bios_patch_fast_boot)
|
||||
|
|
|
@ -303,3 +303,10 @@ enum class ForceVideoTimingMode : u8
|
|||
|
||||
Count,
|
||||
};
|
||||
|
||||
enum class PIODeviceType : u8
|
||||
{
|
||||
None,
|
||||
XplorerCart,
|
||||
MaxCount,
|
||||
};
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "core/bios.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
#include <QtCore/QDir>
|
||||
#include <QtWidgets/QFileDialog>
|
||||
#include <algorithm>
|
||||
|
||||
|
@ -25,13 +26,37 @@ BIOSSettingsWidget::BIOSSettingsWidget(SettingsWindow* dialog, QWidget* parent)
|
|||
|
||||
connect(m_ui.fastBoot, &QCheckBox::checkStateChanged, this, &BIOSSettingsWidget::onFastBootChanged);
|
||||
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.pioDeviceType, "PIO", "DeviceType",
|
||||
&Settings::ParsePIODeviceTypeName, &Settings::GetPIODeviceTypeModeName,
|
||||
&Settings::GetPIODeviceTypeModeDisplayName,
|
||||
Settings::DEFAULT_PIO_DEVICE_TYPE, PIODeviceType::MaxCount);
|
||||
SettingWidgetBinder::BindWidgetToStringSetting(sif, m_ui.pioImagePath, "PIO", "FlashImagePath");
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pioSwitchActive, "PIO", "SwitchActive", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pioImageWrites, "PIO", "FlashImageWriteEnable", false);
|
||||
connect(m_ui.pioDeviceType, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&BIOSSettingsWidget::onPIODeviceTypeChanged);
|
||||
connect(m_ui.pioImagePathBrowse, &QPushButton::clicked, this, &BIOSSettingsWidget::onPIOImagePathBrowseClicked);
|
||||
|
||||
onFastBootChanged();
|
||||
onPIODeviceTypeChanged();
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.fastBoot, tr("Fast Boot"), tr("Unchecked"),
|
||||
tr("Patches the BIOS to skip the console's boot animation. Does not work with all games, "
|
||||
"but usually safe to enable."));
|
||||
dialog->registerWidgetHelp(m_ui.enableTTYLogging, tr("Enable TTY Logging"), tr("Unchecked"),
|
||||
tr("Logs BIOS calls to printf(). Not all games contain debugging messages."));
|
||||
dialog->registerWidgetHelp(m_ui.pioDeviceType, tr("Device Type"), tr("None"),
|
||||
tr("Simulates a device plugged into the console's parallel port. Usually these are flash "
|
||||
"cartridges, and require some sort of image dump to function."));
|
||||
dialog->registerWidgetHelp(m_ui.pioImagePath, tr("Image Path"), tr("Empty"),
|
||||
tr("Sets the path to the image used for flash cartridges."));
|
||||
dialog->registerWidgetHelp(m_ui.pioSwitchActive, tr("Cartridge Switch On"), tr("Checked"),
|
||||
tr("Simulates the position of the switch on the cartridge. Most cartridges require the "
|
||||
"switch to be on for it to activate on startup."));
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.pioImageWrites, tr("Allow Image Writes"), tr("None"),
|
||||
tr("Stores any images made to the cartridge's flash storage back to the host's file system. <strong>This will "
|
||||
"overwrite your cartridge dump,</strong> you should ensure you have a backup first."));
|
||||
|
||||
connect(m_ui.imageNTSCJ, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) {
|
||||
if (m_dialog->isPerGameSettings() && index == 0)
|
||||
|
@ -180,3 +205,31 @@ void BIOSSettingsWidget::setDropDownValue(QComboBox* cb, const std::optional<std
|
|||
cb->addItem(qname, QVariant(qname));
|
||||
cb->setCurrentIndex(cb->count() - 1);
|
||||
}
|
||||
|
||||
void BIOSSettingsWidget::onPIODeviceTypeChanged()
|
||||
{
|
||||
const PIODeviceType type =
|
||||
Settings::ParsePIODeviceTypeName(
|
||||
m_dialog
|
||||
->getEffectiveStringValue("PIO", "DeviceType",
|
||||
Settings::GetPIODeviceTypeModeName(Settings::DEFAULT_PIO_DEVICE_TYPE))
|
||||
.c_str())
|
||||
.value_or(Settings::DEFAULT_PIO_DEVICE_TYPE);
|
||||
const bool has_image = (type == PIODeviceType::XplorerCart);
|
||||
const bool has_switch = (type == PIODeviceType::XplorerCart);
|
||||
m_ui.pioImagePathLabel->setEnabled(has_image);
|
||||
m_ui.pioImagePath->setEnabled(has_image);
|
||||
m_ui.pioImagePathBrowse->setEnabled(has_image);
|
||||
m_ui.pioImageWrites->setEnabled(has_image);
|
||||
m_ui.pioSwitchActive->setEnabled(has_switch);
|
||||
}
|
||||
|
||||
void BIOSSettingsWidget::onPIOImagePathBrowseClicked()
|
||||
{
|
||||
const QString path = QDir::toNativeSeparators(
|
||||
QFileDialog::getOpenFileName(QtUtils::GetRootWidget(this), tr("Select PIO Image"), m_ui.pioImagePath->text()));
|
||||
if (path.isEmpty())
|
||||
return;
|
||||
|
||||
m_ui.pioImagePath->setText(path);
|
||||
}
|
||||
|
|
|
@ -30,6 +30,8 @@ public:
|
|||
private Q_SLOTS:
|
||||
void refreshList();
|
||||
void onFastBootChanged();
|
||||
void onPIODeviceTypeChanged();
|
||||
void onPIOImagePathBrowseClicked();
|
||||
|
||||
private:
|
||||
Ui::BIOSSettingsWidget m_ui;
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>529</width>
|
||||
<height>330</height>
|
||||
<width>732</width>
|
||||
<height>470</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="mainLayout">
|
||||
|
@ -177,6 +177,64 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="pioGroupBox">
|
||||
<property name="title">
|
||||
<string>Parallel Port</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Device Type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="pioDeviceType"/>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="0,1,0">
|
||||
<item>
|
||||
<widget class="QLabel" name="pioImagePathLabel">
|
||||
<property name="text">
|
||||
<string>Image Path:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="pioImagePath"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pioImagePathBrowse">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="pioSwitchActive">
|
||||
<property name="text">
|
||||
<string>Cartridge Switch On</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="pioImageWrites">
|
||||
<property name="text">
|
||||
<string>Allow Image Writes</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
|
@ -185,7 +243,7 @@
|
|||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
<height>1</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
|
|
|
@ -35,6 +35,7 @@ public:
|
|||
ALWAYS_INLINE bool IsWriting() const { return (m_mode == Mode::Write); }
|
||||
ALWAYS_INLINE u32 GetVersion() const { return m_version; }
|
||||
ALWAYS_INLINE size_t GetPosition() const { return m_pos; }
|
||||
ALWAYS_INLINE void SetPosition(size_t pos) { m_pos = pos; }
|
||||
|
||||
/// Overload for integral or floating-point types. Writes bytes as-is.
|
||||
template<typename T, std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>, int> = 0>
|
||||
|
|
Loading…
Reference in New Issue