Duckstation/src/core/memory_card.cpp

322 lines
10 KiB
C++
Raw Normal View History

2019-09-29 15:07:38 +00:00
#include "memory_card.h"
2020-01-10 03:31:12 +00:00
#include "common/byte_stream.h"
#include "common/file_system.h"
#include "common/log.h"
2019-09-29 15:59:35 +00:00
#include "common/state_wrapper.h"
JIT optimizations and refactoring (#675) * CPU/Recompiler: Use rel32 call where possible for no-args * JitCodeBuffer: Support using preallocated buffer * CPU/Recompiler/AArch64: Use bl instead of blr for short branches * CPU/CodeCache: Allocate recompiler buffer in program space This means we don't need 64-bit moves for every call out of the recompiler. * GTE: Don't store as u16 and load as u32 * CPU/Recompiler: Add methods to emit global load/stores * GTE: Convert class to namespace * CPU/Recompiler: Call GTE functions directly * Settings: Turn into a global variable * GPU: Replace local pointers with global * InterruptController: Turn into a global pointer * System: Replace local pointers with global * Timers: Turn into a global instance * DMA: Turn into a global instance * SPU: Turn into a global instance * CDROM: Turn into a global instance * MDEC: Turn into a global instance * Pad: Turn into a global instance * SIO: Turn into a global instance * CDROM: Move audio FIFO to the heap * CPU/Recompiler: Drop ASMFunctions No longer needed since we have code in the same 4GB window. * CPUCodeCache: Turn class into namespace * Bus: Local pointer -> global pointers * CPU: Turn class into namespace * Bus: Turn into namespace * GTE: Store registers in CPU state struct Allows relative addressing on ARM. * CPU/Recompiler: Align code storage to page size * CPU/Recompiler: Fix relative branches on A64 * HostInterface: Local references to global * System: Turn into a namespace, move events out * Add guard pages * Android: Fix build
2020-07-31 07:09:18 +00:00
#include "common/string_util.h"
2019-10-27 06:45:23 +00:00
#include "host_interface.h"
#include "system.h"
#include <cstdio>
2019-09-29 15:07:38 +00:00
Log_SetChannel(MemoryCard);
JIT optimizations and refactoring (#675) * CPU/Recompiler: Use rel32 call where possible for no-args * JitCodeBuffer: Support using preallocated buffer * CPU/Recompiler/AArch64: Use bl instead of blr for short branches * CPU/CodeCache: Allocate recompiler buffer in program space This means we don't need 64-bit moves for every call out of the recompiler. * GTE: Don't store as u16 and load as u32 * CPU/Recompiler: Add methods to emit global load/stores * GTE: Convert class to namespace * CPU/Recompiler: Call GTE functions directly * Settings: Turn into a global variable * GPU: Replace local pointers with global * InterruptController: Turn into a global pointer * System: Replace local pointers with global * Timers: Turn into a global instance * DMA: Turn into a global instance * SPU: Turn into a global instance * CDROM: Turn into a global instance * MDEC: Turn into a global instance * Pad: Turn into a global instance * SIO: Turn into a global instance * CDROM: Move audio FIFO to the heap * CPU/Recompiler: Drop ASMFunctions No longer needed since we have code in the same 4GB window. * CPUCodeCache: Turn class into namespace * Bus: Local pointer -> global pointers * CPU: Turn class into namespace * Bus: Turn into namespace * GTE: Store registers in CPU state struct Allows relative addressing on ARM. * CPU/Recompiler: Align code storage to page size * CPU/Recompiler: Fix relative branches on A64 * HostInterface: Local references to global * System: Turn into a namespace, move events out * Add guard pages * Android: Fix build
2020-07-31 07:09:18 +00:00
MemoryCard::MemoryCard()
2019-09-29 15:07:38 +00:00
{
m_FLAG.no_write_yet = true;
m_save_event = TimingEvents::CreateTimingEvent("Memory Card Host Flush", GetSaveDelayInTicks(), GetSaveDelayInTicks(),
std::bind(&MemoryCard::SaveIfChanged, this, true), false);
2019-09-29 15:07:38 +00:00
}
MemoryCard::~MemoryCard()
{
SaveIfChanged(false);
}
2019-09-29 15:07:38 +00:00
TickCount MemoryCard::GetSaveDelayInTicks()
{
return System::GetTicksPerSecond() * SAVE_DELAY_IN_SECONDS;
}
2019-09-29 15:59:35 +00:00
void MemoryCard::Reset()
{
ResetTransferState();
SaveIfChanged(true);
2020-04-25 04:58:19 +00:00
m_FLAG.no_write_yet = true;
2019-09-29 15:59:35 +00:00
}
bool MemoryCard::DoState(StateWrapper& sw)
{
if (sw.IsReading())
SaveIfChanged(true);
2019-09-29 15:59:35 +00:00
sw.Do(&m_state);
2020-04-25 04:58:19 +00:00
sw.Do(&m_FLAG.bits);
2019-09-29 15:59:35 +00:00
sw.Do(&m_address);
sw.Do(&m_sector_offset);
sw.Do(&m_checksum);
sw.Do(&m_last_byte);
sw.Do(&m_data);
2019-10-27 06:45:23 +00:00
sw.Do(&m_changed);
2019-09-30 04:22:57 +00:00
2019-09-29 15:59:35 +00:00
return !sw.HasError();
}
2019-09-29 15:07:38 +00:00
void MemoryCard::ResetTransferState()
{
m_state = State::Idle;
m_address = 0;
m_sector_offset = 0;
m_checksum = 0;
m_last_byte = 0;
}
bool MemoryCard::Transfer(const u8 data_in, u8* data_out)
{
bool ack = false;
2020-10-11 02:14:29 +00:00
#ifdef _DEBUG
2019-09-29 15:07:38 +00:00
const State old_state = m_state;
2020-10-11 02:14:29 +00:00
#endif
2019-09-29 15:07:38 +00:00
switch (m_state)
{
#define FIXED_REPLY_STATE(state, reply, ack_value, next_state) \
case state: \
{ \
*data_out = reply; \
ack = ack_value; \
m_state = next_state; \
} \
break;
#define ADDRESS_STATE_MSB(state, next_state) \
case state: \
{ \
*data_out = 0x00; \
ack = true; \
m_address = ((m_address & u16(0x00FF)) | (ZeroExtend16(data_in) << 8)) & 0x3FF; \
m_state = next_state; \
} \
break;
#define ADDRESS_STATE_LSB(state, next_state) \
case state: \
{ \
*data_out = m_last_byte; \
ack = true; \
m_address = ((m_address & u16(0xFF00)) | ZeroExtend16(data_in)) & 0x3FF; \
m_sector_offset = 0; \
m_state = next_state; \
} \
break;
// read state
FIXED_REPLY_STATE(State::ReadCardID1, 0x5A, true, State::ReadCardID2);
FIXED_REPLY_STATE(State::ReadCardID2, 0x5D, true, State::ReadAddressMSB);
ADDRESS_STATE_MSB(State::ReadAddressMSB, State::ReadAddressLSB);
ADDRESS_STATE_LSB(State::ReadAddressLSB, State::ReadACK1);
FIXED_REPLY_STATE(State::ReadACK1, 0x5C, true, State::ReadACK2);
FIXED_REPLY_STATE(State::ReadACK2, 0x5D, true, State::ReadConfirmAddressMSB);
FIXED_REPLY_STATE(State::ReadConfirmAddressMSB, Truncate8(m_address >> 8), true, State::ReadConfirmAddressLSB);
FIXED_REPLY_STATE(State::ReadConfirmAddressLSB, Truncate8(m_address), true, State::ReadData);
case State::ReadData:
{
const u8 bits = m_data[ZeroExtend32(m_address) * MemoryCardImage::FRAME_SIZE + m_sector_offset];
2019-09-29 15:07:38 +00:00
if (m_sector_offset == 0)
{
2019-09-29 15:53:47 +00:00
Log_DevPrintf("Reading memory card sector %u", ZeroExtend32(m_address));
2019-09-29 15:07:38 +00:00
m_checksum = Truncate8(m_address >> 8) ^ Truncate8(m_address) ^ bits;
}
else
{
m_checksum ^= bits;
}
*data_out = bits;
ack = true;
m_sector_offset++;
if (m_sector_offset == MemoryCardImage::FRAME_SIZE)
2019-09-29 15:07:38 +00:00
{
m_state = State::ReadChecksum;
m_sector_offset = 0;
}
}
break;
FIXED_REPLY_STATE(State::ReadChecksum, m_checksum, true, State::ReadEnd);
2019-09-30 04:22:57 +00:00
FIXED_REPLY_STATE(State::ReadEnd, 0x47, true, State::Idle);
2019-09-29 15:07:38 +00:00
// write state
FIXED_REPLY_STATE(State::WriteCardID1, 0x5A, true, State::WriteCardID2);
FIXED_REPLY_STATE(State::WriteCardID2, 0x5D, true, State::WriteAddressMSB);
ADDRESS_STATE_MSB(State::WriteAddressMSB, State::WriteAddressLSB);
ADDRESS_STATE_LSB(State::WriteAddressLSB, State::WriteData);
case State::WriteData:
{
if (m_sector_offset == 0)
{
Log_InfoPrintf("Writing memory card sector %u", ZeroExtend32(m_address));
2019-09-29 15:07:38 +00:00
m_checksum = Truncate8(m_address >> 8) ^ Truncate8(m_address) ^ data_in;
2019-09-30 04:22:57 +00:00
m_FLAG.no_write_yet = false;
2019-09-29 15:07:38 +00:00
}
else
{
m_checksum ^= data_in;
}
const u32 offset = ZeroExtend32(m_address) * MemoryCardImage::FRAME_SIZE + m_sector_offset;
m_changed |= (m_data[offset] != data_in);
2019-10-27 06:45:23 +00:00
m_data[offset] = data_in;
2019-09-29 15:07:38 +00:00
*data_out = m_last_byte;
ack = true;
m_sector_offset++;
if (m_sector_offset == MemoryCardImage::FRAME_SIZE)
2019-09-29 15:07:38 +00:00
{
m_state = State::WriteChecksum;
m_sector_offset = 0;
2019-10-27 06:45:23 +00:00
if (m_changed)
QueueFileSave();
2019-09-29 15:07:38 +00:00
}
}
break;
FIXED_REPLY_STATE(State::WriteChecksum, m_checksum, true, State::WriteACK1);
FIXED_REPLY_STATE(State::WriteACK1, 0x5C, true, State::WriteACK2);
FIXED_REPLY_STATE(State::WriteACK2, 0x5D, true, State::WriteEnd);
FIXED_REPLY_STATE(State::WriteEnd, 0x47, false, State::Idle);
2019-09-29 15:07:38 +00:00
// new command
case State::Idle:
{
2019-09-30 04:22:57 +00:00
// select device
if (data_in == 0x81)
2019-09-29 15:07:38 +00:00
{
2019-09-30 04:22:57 +00:00
*data_out = 0xFF;
ack = true;
m_state = State::Command;
}
}
break;
2019-09-29 15:07:38 +00:00
2019-09-30 04:22:57 +00:00
case State::Command:
{
switch (data_in)
{
2019-09-29 15:07:38 +00:00
case 0x52: // read data
{
*data_out = m_FLAG.bits;
ack = true;
m_state = State::ReadCardID1;
}
break;
case 0x57: // write data
{
*data_out = m_FLAG.bits;
ack = true;
m_state = State::WriteCardID1;
}
break;
case 0x53: // get id
{
Panic("implement me");
}
break;
default:
{
2019-09-30 04:22:57 +00:00
Log_ErrorPrintf("Invalid command 0x%02X", ZeroExtend32(data_in));
2019-09-29 15:07:38 +00:00
*data_out = m_FLAG.bits;
ack = false;
2019-09-30 04:22:57 +00:00
m_state = State::Idle;
2019-09-29 15:07:38 +00:00
}
}
}
break;
default:
UnreachableCode();
break;
}
Log_DebugPrintf("Transfer, old_state=%u, new_state=%u, data_in=0x%02X, data_out=0x%02X, ack=%s",
static_cast<u32>(old_state), static_cast<u32>(m_state), data_in, *data_out, ack ? "true" : "false");
m_last_byte = data_in;
return ack;
}
JIT optimizations and refactoring (#675) * CPU/Recompiler: Use rel32 call where possible for no-args * JitCodeBuffer: Support using preallocated buffer * CPU/Recompiler/AArch64: Use bl instead of blr for short branches * CPU/CodeCache: Allocate recompiler buffer in program space This means we don't need 64-bit moves for every call out of the recompiler. * GTE: Don't store as u16 and load as u32 * CPU/Recompiler: Add methods to emit global load/stores * GTE: Convert class to namespace * CPU/Recompiler: Call GTE functions directly * Settings: Turn into a global variable * GPU: Replace local pointers with global * InterruptController: Turn into a global pointer * System: Replace local pointers with global * Timers: Turn into a global instance * DMA: Turn into a global instance * SPU: Turn into a global instance * CDROM: Turn into a global instance * MDEC: Turn into a global instance * Pad: Turn into a global instance * SIO: Turn into a global instance * CDROM: Move audio FIFO to the heap * CPU/Recompiler: Drop ASMFunctions No longer needed since we have code in the same 4GB window. * CPUCodeCache: Turn class into namespace * Bus: Local pointer -> global pointers * CPU: Turn class into namespace * Bus: Turn into namespace * GTE: Store registers in CPU state struct Allows relative addressing on ARM. * CPU/Recompiler: Align code storage to page size * CPU/Recompiler: Fix relative branches on A64 * HostInterface: Local references to global * System: Turn into a namespace, move events out * Add guard pages * Android: Fix build
2020-07-31 07:09:18 +00:00
std::unique_ptr<MemoryCard> MemoryCard::Create()
2019-09-29 15:07:38 +00:00
{
JIT optimizations and refactoring (#675) * CPU/Recompiler: Use rel32 call where possible for no-args * JitCodeBuffer: Support using preallocated buffer * CPU/Recompiler/AArch64: Use bl instead of blr for short branches * CPU/CodeCache: Allocate recompiler buffer in program space This means we don't need 64-bit moves for every call out of the recompiler. * GTE: Don't store as u16 and load as u32 * CPU/Recompiler: Add methods to emit global load/stores * GTE: Convert class to namespace * CPU/Recompiler: Call GTE functions directly * Settings: Turn into a global variable * GPU: Replace local pointers with global * InterruptController: Turn into a global pointer * System: Replace local pointers with global * Timers: Turn into a global instance * DMA: Turn into a global instance * SPU: Turn into a global instance * CDROM: Turn into a global instance * MDEC: Turn into a global instance * Pad: Turn into a global instance * SIO: Turn into a global instance * CDROM: Move audio FIFO to the heap * CPU/Recompiler: Drop ASMFunctions No longer needed since we have code in the same 4GB window. * CPUCodeCache: Turn class into namespace * Bus: Local pointer -> global pointers * CPU: Turn class into namespace * Bus: Turn into namespace * GTE: Store registers in CPU state struct Allows relative addressing on ARM. * CPU/Recompiler: Align code storage to page size * CPU/Recompiler: Fix relative branches on A64 * HostInterface: Local references to global * System: Turn into a namespace, move events out * Add guard pages * Android: Fix build
2020-07-31 07:09:18 +00:00
std::unique_ptr<MemoryCard> mc = std::make_unique<MemoryCard>();
2019-10-27 06:45:23 +00:00
mc->Format();
return mc;
}
JIT optimizations and refactoring (#675) * CPU/Recompiler: Use rel32 call where possible for no-args * JitCodeBuffer: Support using preallocated buffer * CPU/Recompiler/AArch64: Use bl instead of blr for short branches * CPU/CodeCache: Allocate recompiler buffer in program space This means we don't need 64-bit moves for every call out of the recompiler. * GTE: Don't store as u16 and load as u32 * CPU/Recompiler: Add methods to emit global load/stores * GTE: Convert class to namespace * CPU/Recompiler: Call GTE functions directly * Settings: Turn into a global variable * GPU: Replace local pointers with global * InterruptController: Turn into a global pointer * System: Replace local pointers with global * Timers: Turn into a global instance * DMA: Turn into a global instance * SPU: Turn into a global instance * CDROM: Turn into a global instance * MDEC: Turn into a global instance * Pad: Turn into a global instance * SIO: Turn into a global instance * CDROM: Move audio FIFO to the heap * CPU/Recompiler: Drop ASMFunctions No longer needed since we have code in the same 4GB window. * CPUCodeCache: Turn class into namespace * Bus: Local pointer -> global pointers * CPU: Turn class into namespace * Bus: Turn into namespace * GTE: Store registers in CPU state struct Allows relative addressing on ARM. * CPU/Recompiler: Align code storage to page size * CPU/Recompiler: Fix relative branches on A64 * HostInterface: Local references to global * System: Turn into a namespace, move events out * Add guard pages * Android: Fix build
2020-07-31 07:09:18 +00:00
std::unique_ptr<MemoryCard> MemoryCard::Open(std::string_view filename)
2019-10-27 06:45:23 +00:00
{
JIT optimizations and refactoring (#675) * CPU/Recompiler: Use rel32 call where possible for no-args * JitCodeBuffer: Support using preallocated buffer * CPU/Recompiler/AArch64: Use bl instead of blr for short branches * CPU/CodeCache: Allocate recompiler buffer in program space This means we don't need 64-bit moves for every call out of the recompiler. * GTE: Don't store as u16 and load as u32 * CPU/Recompiler: Add methods to emit global load/stores * GTE: Convert class to namespace * CPU/Recompiler: Call GTE functions directly * Settings: Turn into a global variable * GPU: Replace local pointers with global * InterruptController: Turn into a global pointer * System: Replace local pointers with global * Timers: Turn into a global instance * DMA: Turn into a global instance * SPU: Turn into a global instance * CDROM: Turn into a global instance * MDEC: Turn into a global instance * Pad: Turn into a global instance * SIO: Turn into a global instance * CDROM: Move audio FIFO to the heap * CPU/Recompiler: Drop ASMFunctions No longer needed since we have code in the same 4GB window. * CPUCodeCache: Turn class into namespace * Bus: Local pointer -> global pointers * CPU: Turn class into namespace * Bus: Turn into namespace * GTE: Store registers in CPU state struct Allows relative addressing on ARM. * CPU/Recompiler: Align code storage to page size * CPU/Recompiler: Fix relative branches on A64 * HostInterface: Local references to global * System: Turn into a namespace, move events out * Add guard pages * Android: Fix build
2020-07-31 07:09:18 +00:00
std::unique_ptr<MemoryCard> mc = std::make_unique<MemoryCard>();
2019-10-27 06:45:23 +00:00
mc->m_filename = filename;
if (!mc->LoadFromFile())
{
2019-11-02 14:15:42 +00:00
SmallString message;
message.AppendString("Memory card at '");
message.AppendString(filename.data(), static_cast<u32>(filename.length()));
2019-11-02 14:15:42 +00:00
message.AppendString("' could not be read, formatting.");
Log_ErrorPrint(message);
JIT optimizations and refactoring (#675) * CPU/Recompiler: Use rel32 call where possible for no-args * JitCodeBuffer: Support using preallocated buffer * CPU/Recompiler/AArch64: Use bl instead of blr for short branches * CPU/CodeCache: Allocate recompiler buffer in program space This means we don't need 64-bit moves for every call out of the recompiler. * GTE: Don't store as u16 and load as u32 * CPU/Recompiler: Add methods to emit global load/stores * GTE: Convert class to namespace * CPU/Recompiler: Call GTE functions directly * Settings: Turn into a global variable * GPU: Replace local pointers with global * InterruptController: Turn into a global pointer * System: Replace local pointers with global * Timers: Turn into a global instance * DMA: Turn into a global instance * SPU: Turn into a global instance * CDROM: Turn into a global instance * MDEC: Turn into a global instance * Pad: Turn into a global instance * SIO: Turn into a global instance * CDROM: Move audio FIFO to the heap * CPU/Recompiler: Drop ASMFunctions No longer needed since we have code in the same 4GB window. * CPUCodeCache: Turn class into namespace * Bus: Local pointer -> global pointers * CPU: Turn class into namespace * Bus: Turn into namespace * GTE: Store registers in CPU state struct Allows relative addressing on ARM. * CPU/Recompiler: Align code storage to page size * CPU/Recompiler: Fix relative branches on A64 * HostInterface: Local references to global * System: Turn into a namespace, move events out * Add guard pages * Android: Fix build
2020-07-31 07:09:18 +00:00
g_host_interface->AddOSDMessage(message.GetCharArray(), 5.0f);
2019-10-27 06:45:23 +00:00
mc->Format();
}
return mc;
2019-09-29 15:07:38 +00:00
}
2019-09-29 15:53:47 +00:00
void MemoryCard::Format()
{
MemoryCardImage::Format(&m_data);
m_changed = true;
2019-09-29 15:53:47 +00:00
}
2019-10-27 06:45:23 +00:00
bool MemoryCard::LoadFromFile()
{
return MemoryCardImage::LoadFromFile(&m_data, m_filename.c_str());
2019-10-27 06:45:23 +00:00
}
bool MemoryCard::SaveIfChanged(bool display_osd_message)
2019-10-27 06:45:23 +00:00
{
m_save_event->Deactivate();
if (!m_changed)
return true;
m_changed = false;
2019-10-27 06:45:23 +00:00
if (m_filename.empty())
return false;
if (!MemoryCardImage::SaveToFile(m_data, m_filename.c_str()))
2019-10-27 06:45:23 +00:00
{
if (display_osd_message)
{
g_host_interface->AddFormattedOSDMessage(
20.0f, g_host_interface->TranslateString("OSDMessage", "Failed to save memory card to '%s'"),
m_filename.c_str());
}
2019-10-27 06:45:23 +00:00
return false;
}
if (display_osd_message)
g_host_interface->AddFormattedOSDMessage(
2.0f, g_host_interface->TranslateString("OSDMessage", "Saved memory card to '%s'"), m_filename.c_str());
2019-10-27 06:45:23 +00:00
return true;
2019-11-02 14:15:42 +00:00
}
void MemoryCard::QueueFileSave()
{
// skip if the event is already pending, or we don't have a backing file
if (m_save_event->IsActive() || m_filename.empty())
return;
// save in one second, that should be long enough for everything to finish writing
m_save_event->Schedule(GetSaveDelayInTicks());
}