HW: Rewrite MagCard features of SI_DeviceAMBaseboard in new MagneticCardReader class.

A huge thank you goes to GXTX and https://github.com/GXTX/YACardEmu which this code is based on.
This commit is contained in:
Jordan Woyak 2026-02-10 01:56:00 -06:00
parent d893565b78
commit f657b32521
11 changed files with 1462 additions and 529 deletions

View File

@ -76,6 +76,8 @@ Dolphin includes or links code of the following third-party software projects:
[MIT](https://github.com/microsoft/wil/blob/master/LICENSE)
- [xxHash](https://github.com/Cyan4973/xxHash):
[BSD 2-Clause](https://github.com/Cyan4973/xxHash/blob/master/LICENSE)
- [YACardEmu](https://github.com/GXTX/YACardEmu)
[GPLv2+](https://github.com/GXTX/YACardEmu/blob/master/license.txt)
- [zlib-ng](https://github.com/zlib-ng/zlib-ng):
[zlib license](https://github.com/zlib-ng/zlib-ng/blob/develop/LICENSE.md)
- [Zstandard](https://facebook.github.io/zstd/):

View File

@ -254,6 +254,12 @@ add_library(core
HW/HSP/HSP_DeviceNull.h
HW/HW.cpp
HW/HW.h
HW/MagCard/C1231BR.cpp
HW/MagCard/C1231BR.h
HW/MagCard/C1231LR.cpp
HW/MagCard/C1231LR.h
HW/MagCard/MagneticCardReader.cpp
HW/MagCard/MagneticCardReader.h
HW/Memmap.cpp
HW/Memmap.h
HW/MemoryInterface.cpp

View File

@ -0,0 +1,82 @@
// Copyright 2026 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// Based on: https://github.com/GXTX/YACardEmu
// Copyright (C) 2020-2023 wutno (https://github.com/GXTX)
// Copyright (C) 2022-2023 tugpoat (https://github.com/tugpoat)
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Core/HW/MagCard/C1231BR.h"
namespace MagCard
{
void C1231BR::Command_10_Initialize()
{
// TODO: Not hardware tested.
if (!m_current_packet.empty())
{
if (InitMode(m_current_packet[0]) == InitMode::EjectAfter)
{
m_is_shutter_open = true;
MoveCard(R::ENTRY_SLOT);
}
}
// m_status.SoftReset(); // TODO: Needed ?
FinishCommand();
}
void C1231BR::Command_D0_ControlShutter()
{
enum class Action : u8
{
Close = 0x30,
Open = 0x31,
};
if (m_current_packet.empty())
{
SetPError(P::SYSTEM_ERR);
FinishCommand();
return;
}
m_is_shutter_open = Action(m_current_packet[0]) == Action::Open;
INFO_LOG_FMT(SERIALINTERFACE_CARD, "Shutter: {}", m_is_shutter_open ? "Open" : "Close");
FinishCommand();
}
u8 C1231BR::GetPositionValue()
{
// True "R" (Card Position) values.
// FYI: I see a 0xd0 command transition from: 11000 -> 01110 -> 00111
// Are these 5 bits individual sensors placed within the machine ?
static constexpr std::array<u8, std::size_t(R::MAX_POSITIONS)> POSITION_VALUES{
0b00000, // NO_CARD
0b11000, // READ_WRITE_HEAD
0b00111, // THERMAL_HEAD
0b11100, // DISPENSER_THERMAL
0b00001, // ENTRY_SLOT
};
return (m_is_shutter_open ? 0x80u : 0x40u) |
(m_card_settings->report_dispenser_empty ? 0x00u : 0x20u) |
POSITION_VALUES[u8(m_status.r)];
}
void C1231BR::DoState(PointerWrap& p)
{
MagneticCardReader::DoState(p);
p.Do(m_is_shutter_open);
}
bool C1231BR::IsReadyForCard()
{
return !IsCardPresent() && m_is_shutter_open;
}
} // namespace MagCard

View File

@ -0,0 +1,37 @@
// Copyright 2026 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// Based on: https://github.com/GXTX/YACardEmu
// Copyright (C) 2020-2023 wutno (https://github.com/GXTX)
// Copyright (C) 2022-2023 tugpoat (https://github.com/tugpoat)
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Core/HW/MagCard/MagneticCardReader.h"
namespace MagCard
{
// CRP-1231BR-10
// Used by F-Zero AX
class C1231BR final : public MagneticCardReader
{
public:
using MagneticCardReader::MagneticCardReader;
protected:
// Specific to this model.
bool m_is_shutter_open = true;
bool IsReadyForCard() override;
u8 GetPositionValue() override;
void Command_10_Initialize() override;
void Command_D0_ControlShutter() override;
void DoState(PointerWrap& p) override;
};
} // namespace MagCard

View File

@ -0,0 +1,27 @@
// Copyright 2026 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// Based on: https://github.com/GXTX/YACardEmu
// Copyright (C) 2020-2023 wutno (https://github.com/GXTX)
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Core/HW/MagCard/C1231LR.h"
namespace MagCard
{
u8 C1231LR::GetPositionValue()
{
// True "R" (Card Position) values.
static constexpr std::array<u8, std::size_t(R::MAX_POSITIONS)> POSITION_VALUES{
0x30, // NO_CARD
0x31, // READ_WRITE_HEAD
0x32, // THERMAL_HEAD
0x33, // DISPENSER_THERMAL
0x34, // ENTRY_SLOT
};
return POSITION_VALUES[u8(m_status.r)];
}
} // namespace MagCard

View File

@ -0,0 +1,26 @@
// Copyright 2026 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// Based on: https://github.com/GXTX/YACardEmu
// Copyright (C) 2020-2023 wutno (https://github.com/GXTX)
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Core/HW/MagCard/MagneticCardReader.h"
namespace MagCard
{
// CRP-1231LR-10NAB
// Used by Mario Kart Arcade GP + GP2
class C1231LR final : public MagneticCardReader
{
public:
using MagneticCardReader::MagneticCardReader;
protected:
u8 GetPositionValue() override;
};
} // namespace MagCard

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,207 @@
// Copyright 2026 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// Based on: https://github.com/GXTX/YACardEmu
// Copyright (C) 2020-2023 wutno (https://github.com/GXTX)
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <optional>
#include <span>
#include <string>
#include <vector>
#include "Common/ChunkFile.h"
#include "Common/CommonTypes.h"
namespace MagCard
{
class MagneticCardReader
{
public:
static constexpr std::size_t TRACK_SIZE = 69; // A nice amount of data.
static constexpr std::size_t NUM_TRACKS = 3;
// This data is not part of the card reader state.
struct Settings
{
std::string card_name;
std::string card_path;
bool insert_card_when_possible = true;
bool report_dispenser_empty = false;
};
explicit MagneticCardReader(Settings* settings);
virtual ~MagneticCardReader();
MagneticCardReader(const MagneticCardReader&) = delete;
MagneticCardReader& operator=(const MagneticCardReader&) = delete;
MagneticCardReader(MagneticCardReader&&) = delete;
MagneticCardReader& operator=(MagneticCardReader&&) = delete;
// TODO: This std:vector buffer interface is a bit funky..
// read = MagCard <-- Baseboard.
// write = Magcard --> Baseboard.
void Process(std::vector<u8>* read, std::vector<u8>* write);
virtual void DoState(PointerWrap& p);
protected:
// Status bytes:
enum class R : u8
{
// Note: Derived classes define the real values in GetPositionValue().
NO_CARD,
READ_WRITE_HEAD,
THERMAL_HEAD,
DISPENSER_THERMAL,
ENTRY_SLOT,
MAX_POSITIONS,
};
enum class P : u8
{
NO_ERR = 0x30,
READ_ERR = 0x31,
WRITE_ERR = 0x32,
CARD_JAM = 0x33,
MOTOR_ERR = 0x34, // transport system motor error
PRINT_ERR = 0x35,
ILLEGAL_ERR = 0x38, // generic error
BATTERY_ERR = 0x40, // low battery voltage
SYSTEM_ERR = 0x41, // reader/writer system err
TRACK_1_READ_ERR = 0x51,
TRACK_2_READ_ERR = 0x52,
TRACK_3_READ_ERR = 0x53,
TRACK_1_AND_2_READ_ERR = 0x54,
TRACK_1_AND_3_READ_ERR = 0x55,
TRACK_2_AND_3_READ_ERR = 0x56,
};
enum class S : u8
{
NO_JOB = 0x30,
ILLEGAL_COMMAND = 0x32,
RUNNING_COMMAND = 0x33,
WAITING_FOR_CARD = 0x34,
DISPENSER_EMPTY = 0x35,
NO_DISPENSER = 0x36,
CARD_FULL = 0x37,
};
struct Status
{
R r = R::NO_CARD;
P p = P::NO_ERR;
S s = S::NO_JOB;
void SoftReset()
{
p = P::NO_ERR;
s = S::NO_JOB;
}
};
enum class InitMode : u8
{
Standard = 0x30,
EjectAfter = 0x31,
ResetSpecifications = 0x32,
};
bool ReceivePacket(std::span<const u8> packet);
void BuildPacket(std::vector<u8>& write_buffer);
void StepStateMachine(DT elapsed_time);
void StepStatePerson(DT elapsed_time);
// This is what actually maps the "R" position value to a number.
virtual u8 GetPositionValue() = 0;
void SetPError(P error_code);
void SetSError(S error_code);
void ClearCardInMemory();
// Read/Write with host filesystem.
void ReadCardFile();
void WriteCardFile();
bool IsRunningCommand() const;
void FinishCommand();
u8 GetCurrentCommand() const;
// Is a card anywhere the machine can sense it ?
bool IsCardPresent() const;
// Is a card beyond the "ENTRY_SLOT" position ?
bool IsCardLoaded() const;
virtual bool IsReadyForCard();
// Move the card through the machine.
void MoveCard(R new_position);
// Unloads the card and moves it to the "ENTRY_SLOT" position.
void EjectCard();
// Feeds a new card into the machine from an internal hopper.
void DispenseCard();
// Commands
virtual void Command_10_Initialize();
void Command_20_ReadStatus();
void Command_33_ReadData(); // Multi-track read.
void Command_35_GetData(); // Read the entire card.
void Command_40_Cancel();
void Command_53_WriteData(); // Multi-track write.
void Command_78_PrintSettings();
void Command_7A_RegisterFont();
void Command_7B_PrintImage();
void Command_7C_PrintLine();
void Command_7D_Erase(); // Erase the printed image.
void Command_7E_PrintBarcode();
void Command_80_EjectCard();
void Command_A0_Clean();
void Command_B0_DispenseCard();
void Command_C0_ControlLED();
void Command_C1_SetPrintRetry();
virtual void Command_D0_ControlShutter();
void Command_F0_GetVersion();
Settings* const m_card_settings;
// R P S values that are sent in every ENQUIRY response.
Status m_status{};
// A byte sent in every ENQUIRY response.
// TODO: What's the default value on just powered up hardware ?
u8 m_current_command = 0;
// The data included in every ENQUIRY response.
std::vector<u8> m_command_payload;
// The card track data in memory.
using TrackData = std::optional<std::array<u8, TRACK_SIZE>>;
std::array<TrackData, NUM_TRACKS> m_card_data;
// The parameter data of the command being processed.
std::vector<u8> m_current_packet;
// The progress state of the current command.
// 0 == not running a command.
// TODO: Change this to emulated milliseconds or something.
s32 m_current_step = 0;
// Used to delay re-insertion of cards after ejection.
// This avoids having to expose card "Take"/"Insert" actions.
DT m_time_since_machine_moved_card{};
DT m_time_since_person_moved_card{};
};
} // namespace MagCard

View File

@ -9,10 +9,8 @@
#include <fmt/format.h>
#include "Common/Buffer.h"
#include "Common/CommonTypes.h"
#include "Common/FileUtil.h"
#include "Common/IOFile.h"
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"
#include "Common/Swap.h"
@ -28,6 +26,8 @@
#include "Core/HW/EXI/EXI.h"
#include "Core/HW/GCPad.h"
#include "Core/HW/MMIO.h"
#include "Core/HW/MagCard/C1231BR.h"
#include "Core/HW/MagCard/C1231LR.h"
#include "Core/HW/Memmap.h"
#include "Core/HW/ProcessorInterface.h"
#include "Core/HW/SI/SI.h"
@ -160,6 +160,26 @@ CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices dev
// Use count
m_ic_card_data[0x28] = 0xFF;
m_ic_card_data[0x29] = 0xFF;
// Magnetic Card Reader
m_mag_card_settings.card_path = File::GetUserPath(D_TRIUSER_IDX);
m_mag_card_settings.card_name = fmt::format("tricard_{}.bin", SConfig::GetInstance().GetGameID());
// TODO: Do any other games use the Magnetic Card Reader ?
switch (AMMediaboard::GetGameType())
{
case FZeroAX:
m_mag_card_reader = std::make_unique<MagCard::C1231BR>(&m_mag_card_settings);
break;
case MarioKartGP:
case MarioKartGP2:
m_mag_card_reader = std::make_unique<MagCard::C1231LR>(&m_mag_card_settings);
break;
default:
break;
}
}
constexpr u32 SI_XFER_LENGTH_MASK = 0x7f;
@ -1147,506 +1167,43 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)
case GCAMCommand::SerialB:
{
DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 32 (CARD-Interface)");
if (!validate_data_in_out(1, 0, "SerialB"))
break;
const u32 length = *data_in++;
if (!validate_data_in_out(length, 0, "SerialB"))
const u32 in_length = *data_in++;
static constexpr u32 max_packet_size = 0x2f;
// Also accounting for the 2-byte header.
if (!validate_data_in_out(in_length, max_packet_size + 2, "SerialB"))
break;
if (length)
if (m_mag_card_reader)
{
// Send Card Reply
if (length == 1 && data_in[0] == 0x05)
{
if (m_card_read_length)
{
if (!validate_data_in_out(0, 1, "SerialB"))
break;
data_out[data_offset++] = gcam_command;
u32 read_length = m_card_read_length - m_card_read;
// Append the data to our buffer.
const auto prev_size = m_mag_card_in_buffer.size();
m_mag_card_in_buffer.resize(prev_size + in_length);
std::ranges::copy(std::span{data_in, in_length},
m_mag_card_in_buffer.data() + prev_size);
if (AMMediaboard::GetGameType() == FZeroAX)
{
read_length = std::min<u32>(read_length, 0x2F);
}
if (!validate_data_in_out(0, 1, "SerialB"))
break;
data_out[data_offset++] = read_length; // 0x2F (max size per packet)
if (!validate_data_in_out(0, read_length, "SerialB"))
break;
if (u64{m_card_read} + read_length > sizeof(m_card_read_packet))
{
ERROR_LOG_FMT(SERIALINTERFACE_AMBB,
"GC-AM: Command SerialB, m_card_read_packet overflow:\n"
" - m_card_read_packet = {}\n"
" - m_card_read = {}\n"
" - read_length = {}",
fmt::ptr(m_card_read_packet), m_card_read, read_length);
data_in = data_in_end;
break;
}
memcpy(data_out.data() + data_offset, m_card_read_packet + m_card_read,
read_length);
data_offset += read_length;
m_card_read += read_length;
if (m_card_read >= m_card_read_length)
m_card_read_length = 0;
data_in += length;
break;
}
if (!validate_data_in_out(0, 5, "SerialB"))
break;
data_out[data_offset++] = gcam_command;
const u32 command_length_offset = data_offset;
data_out[data_offset++] = 0x00; // len
data_out[data_offset++] = 0x02;
const u32 checksum_start = data_offset;
data_out[data_offset++] = 0x00; // 0x00 len
data_out[data_offset++] = m_card_command; // 0x01 command
switch (CARDCommand(m_card_command))
{
case CARDCommand::Init:
if (!validate_data_in_out(0, 2, "SerialB"))
break;
data_out[data_offset++] = 0x00; // 0x02
data_out[data_offset++] = 0x30; // 0x03
break;
case CARDCommand::GetState:
if (!validate_data_in_out(0, 2, "SerialB"))
break;
data_out[data_offset++] = 0x20 | m_card_bit; // 0x02
// bit 0: Please take your card
// bit 1: Endless waiting causes UNK_E to be called
data_out[data_offset++] = 0x00; // 0x03
break;
case CARDCommand::Read:
if (!validate_data_in_out(0, 2, "SerialB"))
break;
data_out[data_offset++] = 0x02; // 0x02
data_out[data_offset++] = 0x53; // 0x03
break;
case CARDCommand::IsPresent:
if (!validate_data_in_out(0, 2, "SerialB"))
break;
data_out[data_offset++] = 0x22; // 0x02
data_out[data_offset++] = 0x30; // 0x03
break;
case CARDCommand::Write:
if (!validate_data_in_out(0, 2, "SerialB"))
break;
data_out[data_offset++] = 0x02; // 0x02
data_out[data_offset++] = 0x00; // 0x03
break;
case CARDCommand::SetPrintParam:
case CARDCommand::RegisterFont:
if (!validate_data_in_out(0, 2, "SerialB"))
break;
data_out[data_offset++] = 0x00; // 0x02
data_out[data_offset++] = 0x00; // 0x03
break;
case CARDCommand::WriteInfo:
if (!validate_data_in_out(0, 2, "SerialB"))
break;
data_out[data_offset++] = 0x02; // 0x02
data_out[data_offset++] = 0x00; // 0x03
break;
case CARDCommand::Erase:
// TODO: CARDCommand::Erase is not handled.
ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "CARDCommand::Erase is not handled.");
break;
case CARDCommand::Eject:
if (!validate_data_in_out(0, 2, "SerialB"))
break;
if (AMMediaboard::GetGameType() == FZeroAX)
{
data_out[data_offset++] = 0x01; // 0x02
}
else
{
data_out[data_offset++] = 0x31; // 0x02
}
data_out[data_offset++] = 0x30; // 0x03
break;
case CARDCommand::Clean:
if (!validate_data_in_out(0, 2, "SerialB"))
break;
data_out[data_offset++] = 0x02; // 0x02
data_out[data_offset++] = 0x00; // 0x03
break;
case CARDCommand::Load:
if (!validate_data_in_out(0, 2, "SerialB"))
break;
data_out[data_offset++] = 0x02; // 0x02
data_out[data_offset++] = 0x30; // 0x03
break;
case CARDCommand::SetShutter:
if (!validate_data_in_out(0, 2, "SerialB"))
break;
data_out[data_offset++] = 0x00; // 0x02
data_out[data_offset++] = 0x00; // 0x03
break;
}
if (!validate_data_in_out(0, 3, "SerialB"))
break;
data_out[data_offset++] = 0x30; // 0x04
data_out[data_offset++] = 0x00; // 0x05
data_out[data_offset++] = 0x03; // 0x06
data_out[checksum_start] = data_offset - checksum_start; // 0x00 len
if (!validate_data_in_out(0, 1, "SerialB"))
break;
data_out[data_offset] = 0; // 0x07
for (u32 i = 0; i < data_out[checksum_start]; ++i)
data_out[data_offset] ^= data_out[checksum_start + i];
if (!validate_data_in_out(0, 1, "SerialB"))
break;
data_offset++;
data_out[command_length_offset] = data_out[checksum_start] + 2;
}
else
{
if (!validate_data_in_out(length, 0, "SerialB"))
break;
if (u64{m_card_offset} + length > std::size(m_card_buffer))
{
ERROR_LOG_FMT(SERIALINTERFACE_AMBB,
"GC-AM: Command SerialB, m_card_buffer overflow:\n"
" - m_card_buffer = {}\n"
" - m_card_offset = {}\n"
" - length = {}",
fmt::ptr(m_card_buffer), m_card_offset, length);
data_in = data_in_end;
break;
}
for (u32 i = 0; i < length; ++i)
m_card_buffer[m_card_offset + i] = data_in[i];
m_card_offset += length;
// Check if we got a complete command
if (m_card_buffer[0] == 0x02)
{
if (m_card_offset < 2)
{
ERROR_LOG_FMT(
SERIALINTERFACE_AMBB,
"GC-AM: Command SerialB, m_card_buffer overflow (m_card_offset < 2):\n"
" - m_card_buffer = {}\n"
" - m_card_offset = {}\n"
" - length = {}",
fmt::ptr(m_card_buffer), m_card_offset, length);
data_in = data_in_end;
break;
}
if (const u32 offset = m_card_offset - 2;
m_card_buffer[1] == offset && m_card_buffer[offset] == 0x03)
{
m_card_command = m_card_buffer[2];
switch (CARDCommand(m_card_command))
{
case CARDCommand::Init:
NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Init");
m_card_write_length = 0;
m_card_bit = 0;
m_card_memory_size = 0;
m_card_state_call_count = 0;
break;
case CARDCommand::GetState:
{
NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD GetState({:02X})",
m_card_bit);
if (m_card_memory_size == 0)
{
const std::string card_filename(
fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),
SConfig::GetInstance().GetGameID()));
if (File::Exists(card_filename))
{
File::IOFile card(card_filename, "rb+");
m_card_memory_size = static_cast<u32>(card.GetSize());
if (m_card_memory_size > sizeof(m_card_memory))
{
ERROR_LOG_FMT(SERIALINTERFACE_CARD,
"GC-AM: Command CARD GetState overflow:\n"
" - file name = {}\n"
" - file size = {}\n"
" - card size = {}",
card_filename, m_card_memory_size, sizeof(m_card_memory));
data_in = data_in_end;
break;
}
card.ReadBytes(m_card_memory, m_card_memory_size);
card.Close();
m_card_is_inserted = true;
}
}
if (AMMediaboard::GetGameType() == FZeroAX && m_card_memory_size)
{
m_card_state_call_count++;
if (m_card_state_call_count > 10)
{
if (m_card_bit & 2)
m_card_bit &= ~2u;
else
m_card_bit |= 2;
m_card_state_call_count = 0;
}
}
if (m_card_clean == 1)
{
m_card_clean = 2;
}
else if (m_card_clean == 2)
{
const std::string card_filename(
fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),
SConfig::GetInstance().GetGameID()));
if (File::Exists(card_filename))
{
m_card_memory_size = static_cast<u32>(File::GetSize(card_filename));
if (m_card_memory_size)
{
if (AMMediaboard::GetGameType() == FZeroAX)
{
m_card_bit = 2;
}
else
{
m_card_bit = 1;
}
}
}
m_card_clean = 0;
}
break;
}
case CARDCommand::IsPresent:
NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD IsPresent");
break;
case CARDCommand::RegisterFont:
NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD RegisterFont");
break;
case CARDCommand::Load:
{
const u8 mode = m_card_buffer[6];
NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Load({:02X})", mode);
break;
}
case CARDCommand::Clean:
NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Clean");
m_card_clean = 1;
break;
case CARDCommand::Read:
{
const u8 mode = m_card_buffer[6];
const u8 bitmode = m_card_buffer[7];
const u8 track = m_card_buffer[8];
NOTICE_LOG_FMT(SERIALINTERFACE_CARD,
"GC-AM: Command CARD Read({:02X},{:02X},{:02X})", mode, bitmode,
track);
// Prepare read packet
memset(m_card_read_packet, 0, 0xDB);
const std::string card_filename(
fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),
SConfig::GetInstance().GetGameID()));
if (File::Exists(card_filename))
{
File::IOFile card(card_filename, "rb+");
if (m_card_memory_size == 0)
{
m_card_memory_size = static_cast<u32>(card.GetSize());
}
if (m_card_memory_size > sizeof(m_card_memory))
{
ERROR_LOG_FMT(SERIALINTERFACE_CARD,
"GC-AM: Command CARD Read overflow:\n"
" - file name = {}\n"
" - file size = {}\n"
" - card size = {}",
card_filename, m_card_memory_size, sizeof(m_card_memory));
data_in = data_in_end;
break;
}
card.ReadBytes(m_card_memory, m_card_memory_size);
card.Close();
m_card_is_inserted = true;
}
else if (m_card_memory_size > sizeof(m_card_memory))
{
ERROR_LOG_FMT(SERIALINTERFACE_CARD,
"GC-AM: Command CARD Read overflow:\n"
" - requested size = {}\n"
" - card size = {}",
m_card_memory_size, sizeof(m_card_memory));
data_in = data_in_end;
break;
}
m_card_read_packet[0] = 0x02; // SUB CMD
m_card_read_packet[1] = 0x00; // SUB CMDLen
m_card_read_packet[2] = 0x33; // CARD CMD
if (m_card_is_inserted) // CARD Status
{
m_card_read_packet[3] = 0x31;
}
else
{
m_card_read_packet[3] = 0x30;
}
m_card_read_packet[4] = 0x30;
m_card_read_packet[5] = 0x30;
u32 packet_offset = 6;
// Data reply
static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 6);
memcpy(m_card_read_packet + packet_offset, m_card_memory, m_card_memory_size);
packet_offset += m_card_memory_size;
static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 7);
m_card_read_packet[packet_offset++] = 0x03;
m_card_read_packet[1] = packet_offset - 1; // SUB CMDLen
static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 8);
for (u32 i = 0; i < packet_offset - 1; ++i)
m_card_read_packet[packet_offset] ^= m_card_read_packet[1 + i];
static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 9);
packet_offset++;
m_card_read_length = packet_offset;
m_card_read = 0;
break;
}
case CARDCommand::Write:
{
const u8 mode = m_card_buffer[6];
const u8 bitmode = m_card_buffer[7];
const u8 track = m_card_buffer[8];
m_card_memory_size = m_card_buffer[1] - 9;
if (m_card_memory_size > sizeof(m_card_memory))
{
ERROR_LOG_FMT(SERIALINTERFACE_CARD,
"GC-AM: Command CARD Write overflow:\n"
" - write size = {}\n"
" - card size = {}",
m_card_memory_size, sizeof(m_card_memory));
data_in = data_in_end;
break;
}
static_assert(sizeof(m_card_buffer) >= sizeof(m_card_memory) + 9);
memcpy(m_card_memory, m_card_buffer + 9, m_card_memory_size);
NOTICE_LOG_FMT(SERIALINTERFACE_CARD,
"GC-AM: Command CARD Write: {:02X} {:02X} {:02X} {}", mode,
bitmode, track, m_card_memory_size);
const std::string card_filename(File::GetUserPath(D_TRIUSER_IDX) + "tricard_" +
SConfig::GetInstance().GetGameID() + ".bin");
File::IOFile card(card_filename, "wb+");
card.WriteBytes(m_card_memory, m_card_memory_size);
card.Close();
m_card_bit = 2;
m_card_state_call_count = 0;
break;
}
case CARDCommand::SetPrintParam:
NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD SetPrintParam");
break;
case CARDCommand::WriteInfo:
NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD WriteInfo");
break;
case CARDCommand::Erase:
NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Erase");
break;
case CARDCommand::Eject:
NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Eject");
if (AMMediaboard::GetGameType() != FZeroAX)
{
m_card_bit = 0;
}
break;
case CARDCommand::SetShutter:
NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD SetShutter");
if (AMMediaboard::GetGameType() != FZeroAX)
{
m_card_bit = 0;
}
// Close
if (m_card_buffer[6] == 0x30)
{
m_card_shutter = false;
}
// Open
else if (m_card_buffer[6] == 0x31)
{
m_card_shutter = true;
}
break;
default:
ERROR_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: CARD:Unhandled command!");
ERROR_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: CARD:[{:08X}]", m_card_command);
// hexdump( m_card_buffer, m_card_offset );
break;
}
m_card_offset = 0;
}
}
if (!validate_data_in_out(0, 3, "SerialB"))
break;
data_out[data_offset++] = 0x32;
data_out[data_offset++] = 0x01; // len
data_out[data_offset++] = 0x06; // OK
}
// Send and receive data with the magnetic card reader.
m_mag_card_reader->Process(&m_mag_card_in_buffer, &m_mag_card_out_buffer);
}
else
{
if (!validate_data_in_out(0, 2, "SerialB"))
break;
data_out[data_offset++] = gcam_command;
data_out[data_offset++] = 0x00; // len
}
data_in += length;
data_in += in_length;
const auto out_length = std::min(u32(m_mag_card_out_buffer.size()), max_packet_size);
// Write the 2-byte header.
data_out[data_offset++] = gcam_command;
data_out[data_offset++] = u8(out_length);
// Write the data.
std::copy_n(m_mag_card_out_buffer.data(), out_length, data_out.data() + data_offset);
data_offset += out_length;
// Remove the data from our buffer.
m_mag_card_out_buffer.erase(m_mag_card_out_buffer.begin(),
m_mag_card_out_buffer.begin() + s32(out_length));
break;
}
case GCAMCommand::JVSIOA:
@ -2762,24 +2319,14 @@ void CSIDevice_AMBaseboard::DoState(PointerWrap& p)
p.Do(m_ic_write_offset);
p.Do(m_ic_write_size);
p.Do(m_card_memory);
p.Do(m_card_read_packet);
p.Do(m_card_buffer);
// Magnetic Card Reader
if (m_mag_card_reader)
{
m_mag_card_reader->DoState(p);
// Setup CARD
p.Do(m_card_memory_size);
p.Do(m_card_is_inserted);
p.Do(m_card_command);
p.Do(m_card_clean);
p.Do(m_card_write_length);
p.Do(m_card_wrote);
p.Do(m_card_read_length);
p.Do(m_card_read);
p.Do(m_card_bit);
p.Do(m_card_shutter);
p.Do(m_card_state_call_count);
p.Do(m_card_offset);
p.Do(m_mag_card_in_buffer);
p.Do(m_mag_card_out_buffer);
}
// Serial
p.Do(m_wheel_init);

View File

@ -4,7 +4,9 @@
#pragma once
#include "Core/HW/GCPad.h"
#include "Core/HW/MagCard/MagneticCardReader.h"
#include "Core/HW/SI/SI_Device.h"
#include "InputCommon/GCPadStatus.h"
namespace Movie
@ -274,24 +276,13 @@ private:
u32 m_ic_write_offset = 0;
u32 m_ic_write_size = 0;
u8 m_card_memory[0xD0] = {};
u8 m_card_read_packet[0xDB] = {};
u8 m_card_buffer[0x100] = {};
// Magnetic Card Reader
MagCard::MagneticCardReader::Settings m_mag_card_settings;
// Setup CARD
u32 m_card_memory_size = 0;
bool m_card_is_inserted = false;
std::vector<u8> m_mag_card_in_buffer;
std::vector<u8> m_mag_card_out_buffer;
u32 m_card_command = 0;
u32 m_card_clean = 0;
u32 m_card_write_length = 0;
u32 m_card_wrote = 0;
u32 m_card_read_length = 0;
u32 m_card_read = 0;
u32 m_card_bit = 0;
bool m_card_shutter = true; // Open
u32 m_card_state_call_count = 0;
u8 m_card_offset = 0;
std::unique_ptr<MagCard::MagneticCardReader> m_mag_card_reader;
// Serial
u32 m_wheel_init = 0;

View File

@ -317,6 +317,9 @@
<ClInclude Include="Core\HW\HSP\HSP_DeviceARAMExpansion.h" />
<ClInclude Include="Core\HW\HSP\HSP_DeviceNull.h" />
<ClInclude Include="Core\HW\HW.h" />
<ClInclude Include="Core\HW\MagCard\C1231BR.h" />
<ClInclude Include="Core\HW\MagCard\C1231LR.h" />
<ClInclude Include="Core\HW\MagCard\MagneticCardReader.h" />
<ClInclude Include="Core\HW\Memmap.h" />
<ClInclude Include="Core\HW\MemoryInterface.h" />
<ClInclude Include="Core\HW\MMIO.h" />
@ -1015,6 +1018,9 @@
<ClCompile Include="Core\HW\HSP\HSP_DeviceARAMExpansion.cpp" />
<ClCompile Include="Core\HW\HSP\HSP_DeviceNull.cpp" />
<ClCompile Include="Core\HW\HW.cpp" />
<ClCompile Include="Core\HW\MagCard\C1231BR.cpp" />
<ClCompile Include="Core\HW\MagCard\C1231LR.cpp" />
<ClCompile Include="Core\HW\MagCard\MagneticCardReader.cpp" />
<ClCompile Include="Core\HW\Memmap.cpp" />
<ClCompile Include="Core\HW\MemoryInterface.cpp" />
<ClCompile Include="Core\HW\MMIO.cpp" />