Merge pull request #10364 from Pokechu22/exi-device-refactor

Create enum classes for EXI devices and slots
This commit is contained in:
Pokechu22 2022-01-16 15:10:55 -08:00 committed by GitHub
commit b7ac11080e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 717 additions and 525 deletions

View File

@ -95,7 +95,6 @@ protected:
constexpr explicit EnumFormatter(const array_type names) : m_names(std::move(names)) {} constexpr explicit EnumFormatter(const array_type names) : m_names(std::move(names)) {}
private:
const array_type m_names; const array_type m_names;
char format_type = 'u'; char format_type = 'u';
}; };

View File

@ -156,12 +156,13 @@ bool BootCore(std::unique_ptr<BootParameters> boot, const WindowSystemInfo& wsi)
// Movie settings // Movie settings
if (Movie::IsPlayingInput() && Movie::IsConfigSaved()) if (Movie::IsPlayingInput() && Movie::IsConfigSaved())
{ {
for (int i = 0; i < 2; ++i) for (ExpansionInterface::Slot slot : ExpansionInterface::MEMCARD_SLOTS)
{ {
if (Movie::IsUsingMemcard(i) && Movie::IsStartingFromClearSave() && !StartUp.bWii) if (Movie::IsUsingMemcard(slot) && Movie::IsStartingFromClearSave() && !StartUp.bWii)
{ {
const auto raw_path = const auto raw_path =
File::GetUserPath(D_GCUSER_IDX) + fmt::format("Movie{}.raw", (i == 0) ? 'A' : 'B'); File::GetUserPath(D_GCUSER_IDX) +
fmt::format("Movie{}.raw", slot == ExpansionInterface::Slot::A ? 'A' : 'B');
if (File::Exists(raw_path)) if (File::Exists(raw_path))
File::Delete(raw_path); File::Delete(raw_path);

View File

@ -8,13 +8,16 @@
#include <fmt/format.h> #include <fmt/format.h>
#include "AudioCommon/AudioCommon.h" #include "AudioCommon/AudioCommon.h"
#include "Common/Assert.h"
#include "Common/CommonPaths.h" #include "Common/CommonPaths.h"
#include "Common/Config/Config.h" #include "Common/Config/Config.h"
#include "Common/EnumMap.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Common/MathUtil.h" #include "Common/MathUtil.h"
#include "Common/StringUtil.h" #include "Common/StringUtil.h"
#include "Common/Version.h" #include "Common/Version.h"
#include "Core/Config/DefaultLocale.h" #include "Core/Config/DefaultLocale.h"
#include "Core/HW/EXI/EXI.h"
#include "Core/HW/EXI/EXI_Device.h" #include "Core/HW/EXI/EXI_Device.h"
#include "Core/HW/Memmap.h" #include "Core/HW/Memmap.h"
#include "Core/HW/SI/SI_Device.h" #include "Core/HW/SI/SI_Device.h"
@ -48,28 +51,60 @@ const Info<bool> MAIN_AUDIO_STRETCH{{System::Main, "Core", "AudioStretch"}, fals
const Info<int> MAIN_AUDIO_STRETCH_LATENCY{{System::Main, "Core", "AudioStretchMaxLatency"}, 80}; const Info<int> MAIN_AUDIO_STRETCH_LATENCY{{System::Main, "Core", "AudioStretchMaxLatency"}, 80};
const Info<std::string> MAIN_MEMCARD_A_PATH{{System::Main, "Core", "MemcardAPath"}, ""}; const Info<std::string> MAIN_MEMCARD_A_PATH{{System::Main, "Core", "MemcardAPath"}, ""};
const Info<std::string> MAIN_MEMCARD_B_PATH{{System::Main, "Core", "MemcardBPath"}, ""}; const Info<std::string> MAIN_MEMCARD_B_PATH{{System::Main, "Core", "MemcardBPath"}, ""};
const Info<std::string>& GetInfoForMemcardPath(ExpansionInterface::Slot slot)
{
ASSERT(ExpansionInterface::IsMemcardSlot(slot));
static constexpr Common::EnumMap<const Info<std::string>*, ExpansionInterface::MAX_MEMCARD_SLOT>
infos{
&MAIN_MEMCARD_A_PATH,
&MAIN_MEMCARD_B_PATH,
};
return *infos[slot];
}
const Info<std::string> MAIN_AGP_CART_A_PATH{{System::Main, "Core", "AgpCartAPath"}, ""}; const Info<std::string> MAIN_AGP_CART_A_PATH{{System::Main, "Core", "AgpCartAPath"}, ""};
const Info<std::string> MAIN_AGP_CART_B_PATH{{System::Main, "Core", "AgpCartBPath"}, ""}; const Info<std::string> MAIN_AGP_CART_B_PATH{{System::Main, "Core", "AgpCartBPath"}, ""};
const Info<std::string>& GetInfoForAGPCartPath(ExpansionInterface::Slot slot)
{
ASSERT(ExpansionInterface::IsMemcardSlot(slot));
static constexpr Common::EnumMap<const Info<std::string>*, ExpansionInterface::MAX_MEMCARD_SLOT>
infos{
&MAIN_AGP_CART_A_PATH,
&MAIN_AGP_CART_B_PATH,
};
return *infos[slot];
}
const Info<std::string> MAIN_GCI_FOLDER_A_PATH_OVERRIDE{ const Info<std::string> MAIN_GCI_FOLDER_A_PATH_OVERRIDE{
{System::Main, "Core", "GCIFolderAPathOverride"}, ""}; {System::Main, "Core", "GCIFolderAPathOverride"}, ""};
const Info<std::string> MAIN_GCI_FOLDER_B_PATH_OVERRIDE{ const Info<std::string> MAIN_GCI_FOLDER_B_PATH_OVERRIDE{
{System::Main, "Core", "GCIFolderBPathOverride"}, ""}; {System::Main, "Core", "GCIFolderBPathOverride"}, ""};
const Info<std::string>& GetInfoForGCIPathOverride(ExpansionInterface::Slot slot)
const Info<ExpansionInterface::TEXIDevices> MAIN_SLOT_A{
{System::Main, "Core", "SlotA"}, ExpansionInterface::EXIDEVICE_MEMORYCARDFOLDER};
const Info<ExpansionInterface::TEXIDevices> MAIN_SLOT_B{{System::Main, "Core", "SlotB"},
ExpansionInterface::EXIDEVICE_NONE};
const Info<ExpansionInterface::TEXIDevices> MAIN_SERIAL_PORT_1{
{System::Main, "Core", "SerialPort1"}, ExpansionInterface::EXIDEVICE_NONE};
const Info<ExpansionInterface::TEXIDevices>& GetInfoForEXIDevice(int channel)
{ {
static constexpr std::array<const Info<ExpansionInterface::TEXIDevices>*, 3> infos{ ASSERT(ExpansionInterface::IsMemcardSlot(slot));
&MAIN_SLOT_A, static constexpr Common::EnumMap<const Info<std::string>*, ExpansionInterface::MAX_MEMCARD_SLOT>
&MAIN_SLOT_B, infos{
&MAIN_SERIAL_PORT_1, &MAIN_GCI_FOLDER_A_PATH_OVERRIDE,
}; &MAIN_GCI_FOLDER_B_PATH_OVERRIDE,
return *infos[channel]; };
return *infos[slot];
}
const Info<ExpansionInterface::EXIDeviceType> MAIN_SLOT_A{
{System::Main, "Core", "SlotA"}, ExpansionInterface::EXIDeviceType::MemoryCardFolder};
const Info<ExpansionInterface::EXIDeviceType> MAIN_SLOT_B{{System::Main, "Core", "SlotB"},
ExpansionInterface::EXIDeviceType::None};
const Info<ExpansionInterface::EXIDeviceType> MAIN_SERIAL_PORT_1{
{System::Main, "Core", "SerialPort1"}, ExpansionInterface::EXIDeviceType::None};
const Info<ExpansionInterface::EXIDeviceType>& GetInfoForEXIDevice(ExpansionInterface::Slot slot)
{
static constexpr Common::EnumMap<const Info<ExpansionInterface::EXIDeviceType>*,
ExpansionInterface::MAX_SLOT>
infos{
&MAIN_SLOT_A,
&MAIN_SLOT_B,
&MAIN_SERIAL_PORT_1,
};
return *infos[slot];
} }
const Info<std::string> MAIN_BBA_MAC{{System::Main, "Core", "BBA_MAC"}, ""}; const Info<std::string> MAIN_BBA_MAC{{System::Main, "Core", "BBA_MAC"}, ""};

View File

@ -33,8 +33,9 @@ enum class DPL2Quality;
namespace ExpansionInterface namespace ExpansionInterface
{ {
enum TEXIDevices : int; enum class EXIDeviceType : int;
} enum class Slot : int;
} // namespace ExpansionInterface
namespace SerialInterface namespace SerialInterface
{ {
@ -65,14 +66,17 @@ extern const Info<bool> MAIN_AUDIO_STRETCH;
extern const Info<int> MAIN_AUDIO_STRETCH_LATENCY; extern const Info<int> MAIN_AUDIO_STRETCH_LATENCY;
extern const Info<std::string> MAIN_MEMCARD_A_PATH; extern const Info<std::string> MAIN_MEMCARD_A_PATH;
extern const Info<std::string> MAIN_MEMCARD_B_PATH; extern const Info<std::string> MAIN_MEMCARD_B_PATH;
const Info<std::string>& GetInfoForMemcardPath(ExpansionInterface::Slot slot);
extern const Info<std::string> MAIN_AGP_CART_A_PATH; extern const Info<std::string> MAIN_AGP_CART_A_PATH;
extern const Info<std::string> MAIN_AGP_CART_B_PATH; extern const Info<std::string> MAIN_AGP_CART_B_PATH;
const Info<std::string>& GetInfoForAGPCartPath(ExpansionInterface::Slot slot);
extern const Info<std::string> MAIN_GCI_FOLDER_A_PATH_OVERRIDE; extern const Info<std::string> MAIN_GCI_FOLDER_A_PATH_OVERRIDE;
extern const Info<std::string> MAIN_GCI_FOLDER_B_PATH_OVERRIDE; extern const Info<std::string> MAIN_GCI_FOLDER_B_PATH_OVERRIDE;
extern const Info<ExpansionInterface::TEXIDevices> MAIN_SLOT_A; const Info<std::string>& GetInfoForGCIPathOverride(ExpansionInterface::Slot slot);
extern const Info<ExpansionInterface::TEXIDevices> MAIN_SLOT_B; extern const Info<ExpansionInterface::EXIDeviceType> MAIN_SLOT_A;
extern const Info<ExpansionInterface::TEXIDevices> MAIN_SERIAL_PORT_1; extern const Info<ExpansionInterface::EXIDeviceType> MAIN_SLOT_B;
const Info<ExpansionInterface::TEXIDevices>& GetInfoForEXIDevice(int channel); extern const Info<ExpansionInterface::EXIDeviceType> MAIN_SERIAL_PORT_1;
const Info<ExpansionInterface::EXIDeviceType>& GetInfoForEXIDevice(ExpansionInterface::Slot slot);
extern const Info<std::string> MAIN_BBA_MAC; extern const Info<std::string> MAIN_BBA_MAC;
extern const Info<std::string> MAIN_BBA_XLINK_IP; extern const Info<std::string> MAIN_BBA_XLINK_IP;
extern const Info<bool> MAIN_BBA_XLINK_CHAT_OSD; extern const Info<bool> MAIN_BBA_XLINK_CHAT_OSD;

View File

@ -15,6 +15,7 @@
#include "Core/Config/MainSettings.h" #include "Core/Config/MainSettings.h"
#include "Core/Config/SYSCONFSettings.h" #include "Core/Config/SYSCONFSettings.h"
#include "Core/Config/SessionSettings.h" #include "Core/Config/SessionSettings.h"
#include "Core/HW/EXI/EXI.h"
#include "Core/NetPlayProto.h" #include "Core/NetPlayProto.h"
namespace ConfigLoaders namespace ConfigLoaders
@ -37,9 +38,8 @@ public:
layer->Set(Config::MAIN_DSP_HLE, m_settings.m_DSPHLE); layer->Set(Config::MAIN_DSP_HLE, m_settings.m_DSPHLE);
layer->Set(Config::MAIN_OVERCLOCK_ENABLE, m_settings.m_OCEnable); layer->Set(Config::MAIN_OVERCLOCK_ENABLE, m_settings.m_OCEnable);
layer->Set(Config::MAIN_OVERCLOCK, m_settings.m_OCFactor); layer->Set(Config::MAIN_OVERCLOCK, m_settings.m_OCFactor);
layer->Set(Config::MAIN_SLOT_A, m_settings.m_EXIDevice[0]); for (ExpansionInterface::Slot slot : ExpansionInterface::SLOTS)
layer->Set(Config::MAIN_SLOT_B, m_settings.m_EXIDevice[1]); layer->Set(Config::GetInfoForEXIDevice(slot), m_settings.m_EXIDevice[slot]);
layer->Set(Config::MAIN_SERIAL_PORT_1, m_settings.m_EXIDevice[2]);
layer->Set(Config::SESSION_SAVE_DATA_WRITABLE, m_settings.m_WriteToMemcard); layer->Set(Config::SESSION_SAVE_DATA_WRITABLE, m_settings.m_WriteToMemcard);
layer->Set(Config::MAIN_RAM_OVERRIDE_ENABLE, m_settings.m_RAMOverrideEnable); layer->Set(Config::MAIN_RAM_OVERRIDE_ENABLE, m_settings.m_RAMOverrideEnable);
layer->Set(Config::MAIN_MEM1_SIZE, m_settings.m_Mem1Size); layer->Set(Config::MAIN_MEM1_SIZE, m_settings.m_Mem1Size);

View File

@ -25,26 +25,11 @@ struct Partition;
class Volume; class Volume;
} // namespace DiscIO } // namespace DiscIO
namespace ExpansionInterface
{
enum TEXIDevices : int;
} // namespace ExpansionInterface
namespace IOS::ES namespace IOS::ES
{ {
class TMDReader; class TMDReader;
} // namespace IOS::ES } // namespace IOS::ES
namespace PowerPC
{
enum class CPUCore;
} // namespace PowerPC
namespace SerialInterface
{
enum SIDevices : int;
} // namespace SerialInterface
struct BootParameters; struct BootParameters;
struct SConfig struct SConfig

View File

@ -39,32 +39,70 @@ static void UpdateInterruptsCallback(u64 userdata, s64 cycles_late);
namespace namespace
{ {
void AddMemoryCards(int i) void AddMemoryCard(Slot slot)
{ {
TEXIDevices memorycard_device; EXIDeviceType memorycard_device;
if (Movie::IsPlayingInput() && Movie::IsConfigSaved()) if (Movie::IsPlayingInput() && Movie::IsConfigSaved())
{ {
if (Movie::IsUsingMemcard(i)) if (Movie::IsUsingMemcard(slot))
{ {
if (Config::Get(Config::GetInfoForEXIDevice(i)) == EXIDEVICE_MEMORYCARDFOLDER) memorycard_device = Config::Get(Config::GetInfoForEXIDevice(slot));
memorycard_device = EXIDEVICE_MEMORYCARDFOLDER; if (memorycard_device != EXIDeviceType::MemoryCardFolder &&
else memorycard_device != EXIDeviceType::MemoryCard)
memorycard_device = EXIDEVICE_MEMORYCARD; {
PanicAlertFmtT(
"The movie indicates that a memory card should be inserted into {0:n}, but one is not "
"currently inserted (instead, {1} is inserted). For the movie to sync properly, "
"please change the selected device to Memory Card or GCI Folder.",
slot, Common::GetStringT(fmt::format("{:n}", memorycard_device).c_str()));
}
} }
else else
{ {
memorycard_device = EXIDEVICE_NONE; memorycard_device = EXIDeviceType::None;
} }
} }
else else
{ {
memorycard_device = Config::Get(Config::GetInfoForEXIDevice(i)); memorycard_device = Config::Get(Config::GetInfoForEXIDevice(slot));
} }
g_Channels[i]->AddDevice(memorycard_device, 0); g_Channels[SlotToEXIChannel(slot)]->AddDevice(memorycard_device, SlotToEXIDevice(slot));
} }
} // namespace } // namespace
u8 SlotToEXIChannel(Slot slot)
{
switch (slot)
{
case Slot::A:
return 0;
case Slot::B:
return 1;
case Slot::SP1:
return 0;
default:
PanicAlertFmt("Unhandled slot {}", slot);
return 0;
}
}
u8 SlotToEXIDevice(Slot slot)
{
switch (slot)
{
case Slot::A:
return 0;
case Slot::B:
return 0;
case Slot::SP1:
return 2;
default:
PanicAlertFmt("Unhandled slot {}", slot);
return 0;
}
}
void Init() void Init()
{ {
if (!g_SRAM_netplay_initialized) if (!g_SRAM_netplay_initialized)
@ -98,12 +136,13 @@ void Init()
} }
} }
for (int i = 0; i < MAX_MEMORYCARD_SLOTS; i++) for (Slot slot : MEMCARD_SLOTS)
AddMemoryCards(i); AddMemoryCard(slot);
g_Channels[0]->AddDevice(EXIDEVICE_MASKROM, 1); g_Channels[0]->AddDevice(EXIDeviceType::MaskROM, 1);
g_Channels[0]->AddDevice(Config::Get(Config::MAIN_SERIAL_PORT_1), 2); g_Channels[SlotToEXIChannel(Slot::SP1)]->AddDevice(Config::Get(Config::MAIN_SERIAL_PORT_1),
g_Channels[2]->AddDevice(EXIDEVICE_AD16, 0); SlotToEXIDevice(Slot::SP1));
g_Channels[2]->AddDevice(EXIDeviceType::AD16, 0);
changeDevice = CoreTiming::RegisterEvent("ChangeEXIDevice", ChangeDeviceCallback); changeDevice = CoreTiming::RegisterEvent("ChangeEXIDevice", ChangeDeviceCallback);
updateInterrupts = CoreTiming::RegisterEvent("EXIUpdateInterrupts", UpdateInterruptsCallback); updateInterrupts = CoreTiming::RegisterEvent("EXIUpdateInterrupts", UpdateInterruptsCallback);
@ -149,15 +188,20 @@ static void ChangeDeviceCallback(u64 userdata, s64 cyclesLate)
u8 type = (u8)(userdata >> 16); u8 type = (u8)(userdata >> 16);
u8 num = (u8)userdata; u8 num = (u8)userdata;
g_Channels.at(channel)->AddDevice((TEXIDevices)type, num); g_Channels.at(channel)->AddDevice(static_cast<EXIDeviceType>(type), num);
} }
void ChangeDevice(const u8 channel, const TEXIDevices device_type, const u8 device_num, void ChangeDevice(Slot slot, EXIDeviceType device_type, CoreTiming::FromThread from_thread)
{
ChangeDevice(SlotToEXIChannel(slot), SlotToEXIDevice(slot), device_type, from_thread);
}
void ChangeDevice(u8 channel, u8 device_num, EXIDeviceType device_type,
CoreTiming::FromThread from_thread) CoreTiming::FromThread from_thread)
{ {
// Let the hardware see no device for 1 second // Let the hardware see no device for 1 second
CoreTiming::ScheduleEvent(0, changeDevice, CoreTiming::ScheduleEvent(0, changeDevice,
((u64)channel << 32) | ((u64)EXIDEVICE_NONE << 16) | device_num, ((u64)channel << 32) | ((u64)EXIDeviceType::None << 16) | device_num,
from_thread); from_thread);
CoreTiming::ScheduleEvent(SystemTimers::GetTicksPerSecond(), changeDevice, CoreTiming::ScheduleEvent(SystemTimers::GetTicksPerSecond(), changeDevice,
((u64)channel << 32) | ((u64)device_type << 16) | device_num, ((u64)channel << 32) | ((u64)device_type << 16) | device_num,
@ -169,15 +213,9 @@ CEXIChannel* GetChannel(u32 index)
return g_Channels.at(index).get(); return g_Channels.at(index).get();
} }
IEXIDevice* FindDevice(TEXIDevices device_type, int customIndex) IEXIDevice* GetDevice(Slot slot)
{ {
for (auto& channel : g_Channels) return g_Channels.at(SlotToEXIChannel(slot))->GetDevice(1 << SlotToEXIDevice(slot));
{
IEXIDevice* device = channel->FindDevice(device_type, customIndex);
if (device)
return device;
}
return nullptr;
} }
void UpdateInterrupts() void UpdateInterrupts()

View File

@ -3,7 +3,10 @@
#pragma once #pragma once
#include <initializer_list>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/EnumFormatter.h"
#include "Core/CoreTiming.h" #include "Core/CoreTiming.h"
class PointerWrap; class PointerWrap;
@ -21,14 +24,31 @@ namespace ExpansionInterface
{ {
class CEXIChannel; class CEXIChannel;
class IEXIDevice; class IEXIDevice;
enum TEXIDevices : int; enum class EXIDeviceType : int;
enum enum
{ {
MAX_MEMORYCARD_SLOTS = 2,
MAX_EXI_CHANNELS = 3 MAX_EXI_CHANNELS = 3
}; };
enum class Slot : int
{
A,
B,
SP1,
};
static constexpr auto SLOTS = {Slot::A, Slot::B, Slot::SP1};
static constexpr auto MAX_SLOT = Slot::SP1;
static constexpr auto MEMCARD_SLOTS = {Slot::A, Slot::B};
static constexpr auto MAX_MEMCARD_SLOT = Slot::B;
constexpr bool IsMemcardSlot(Slot slot)
{
return slot == Slot::A || slot == Slot::B;
}
u8 SlotToEXIChannel(Slot slot);
u8 SlotToEXIDevice(Slot slot);
void Init(); void Init();
void Shutdown(); void Shutdown();
void DoState(PointerWrap& p); void DoState(PointerWrap& p);
@ -39,11 +59,18 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base);
void UpdateInterrupts(); void UpdateInterrupts();
void ScheduleUpdateInterrupts(CoreTiming::FromThread from, int cycles_late); void ScheduleUpdateInterrupts(CoreTiming::FromThread from, int cycles_late);
void ChangeDevice(const u8 channel, const TEXIDevices device_type, const u8 device_num, void ChangeDevice(Slot slot, EXIDeviceType device_type,
CoreTiming::FromThread from_thread = CoreTiming::FromThread::NON_CPU);
void ChangeDevice(u8 channel, u8 device_num, EXIDeviceType device_type,
CoreTiming::FromThread from_thread = CoreTiming::FromThread::NON_CPU); CoreTiming::FromThread from_thread = CoreTiming::FromThread::NON_CPU);
CEXIChannel* GetChannel(u32 index); CEXIChannel* GetChannel(u32 index);
IEXIDevice* GetDevice(Slot slot);
IEXIDevice* FindDevice(TEXIDevices device_type, int customIndex = -1);
} // namespace ExpansionInterface } // namespace ExpansionInterface
template <>
struct fmt::formatter<ExpansionInterface::Slot> : EnumFormatter<ExpansionInterface::MAX_SLOT>
{
constexpr formatter() : EnumFormatter({"Slot A", "Slot B", "Serial Port 1"}) {}
};

View File

@ -34,7 +34,7 @@ CEXIChannel::CEXIChannel(u32 channel_id, const Memcard::HeaderData& memcard_head
m_status.CHIP_SELECT = 1; m_status.CHIP_SELECT = 1;
for (auto& device : m_devices) for (auto& device : m_devices)
device = EXIDevice_Create(EXIDEVICE_NONE, m_channel_id, m_memcard_header_data); device = EXIDevice_Create(EXIDeviceType::None, m_channel_id, m_memcard_header_data);
} }
CEXIChannel::~CEXIChannel() CEXIChannel::~CEXIChannel()
@ -168,7 +168,7 @@ void CEXIChannel::RemoveDevices()
device.reset(nullptr); device.reset(nullptr);
} }
void CEXIChannel::AddDevice(const TEXIDevices device_type, const int device_num) void CEXIChannel::AddDevice(const EXIDeviceType device_type, const int device_num)
{ {
AddDevice(EXIDevice_Create(device_type, m_channel_id, m_memcard_header_data), device_num); AddDevice(EXIDevice_Create(device_type, m_channel_id, m_memcard_header_data), device_num);
} }
@ -245,7 +245,7 @@ void CEXIChannel::DoState(PointerWrap& p)
for (int device_index = 0; device_index < NUM_DEVICES; ++device_index) for (int device_index = 0; device_index < NUM_DEVICES; ++device_index)
{ {
std::unique_ptr<IEXIDevice>& device = m_devices[device_index]; std::unique_ptr<IEXIDevice>& device = m_devices[device_index];
TEXIDevices type = device->m_device_type; EXIDeviceType type = device->m_device_type;
p.Do(type); p.Do(type);
if (type == device->m_device_type) if (type == device->m_device_type)
@ -260,7 +260,7 @@ void CEXIChannel::DoState(PointerWrap& p)
AddDevice(std::move(save_device), device_index, false); AddDevice(std::move(save_device), device_index, false);
} }
if (type == EXIDEVICE_MEMORYCARDFOLDER && old_header_data != m_memcard_header_data && if (type == EXIDeviceType::MemoryCardFolder && old_header_data != m_memcard_header_data &&
!Movie::IsMovieActive()) !Movie::IsMovieActive())
{ {
// We have loaded a savestate that has a GCI folder memcard that is different to the virtual // We have loaded a savestate that has a GCI folder memcard that is different to the virtual
@ -277,8 +277,8 @@ void CEXIChannel::DoState(PointerWrap& p)
// notify_presence_changed flag set to true? Not sure how software behaves if the previous and // notify_presence_changed flag set to true? Not sure how software behaves if the previous and
// the new device type are identical in this case. I assume there is a reason we have this // the new device type are identical in this case. I assume there is a reason we have this
// grace period when switching in the GUI. // grace period when switching in the GUI.
AddDevice(EXIDEVICE_NONE, device_index); AddDevice(EXIDeviceType::None, device_index);
ExpansionInterface::ChangeDevice(m_channel_id, EXIDEVICE_MEMORYCARDFOLDER, device_index, ExpansionInterface::ChangeDevice(m_channel_id, device_index, EXIDeviceType::MemoryCardFolder,
CoreTiming::FromThread::CPU); CoreTiming::FromThread::CPU);
} }
} }
@ -294,15 +294,4 @@ void CEXIChannel::SetEXIINT(bool exiint)
{ {
m_status.EXIINT = !!exiint; m_status.EXIINT = !!exiint;
} }
IEXIDevice* CEXIChannel::FindDevice(TEXIDevices device_type, int custom_index)
{
for (auto& sup : m_devices)
{
IEXIDevice* device = sup->FindDevice(device_type, custom_index);
if (device)
return device;
}
return nullptr;
}
} // namespace ExpansionInterface } // namespace ExpansionInterface

View File

@ -20,7 +20,7 @@ class Mapping;
namespace ExpansionInterface namespace ExpansionInterface
{ {
class IEXIDevice; class IEXIDevice;
enum TEXIDevices : int; enum class EXIDeviceType : int;
class CEXIChannel class CEXIChannel
{ {
@ -30,13 +30,12 @@ public:
// get device // get device
IEXIDevice* GetDevice(u8 chip_select); IEXIDevice* GetDevice(u8 chip_select);
IEXIDevice* FindDevice(TEXIDevices device_type, int custom_index = -1);
void RegisterMMIO(MMIO::Mapping* mmio, u32 base); void RegisterMMIO(MMIO::Mapping* mmio, u32 base);
void SendTransferComplete(); void SendTransferComplete();
void AddDevice(TEXIDevices device_type, int device_num); void AddDevice(EXIDeviceType device_type, int device_num);
void AddDevice(std::unique_ptr<IEXIDevice> device, int device_num, void AddDevice(std::unique_ptr<IEXIDevice> device, int device_num,
bool notify_presence_changed = true); bool notify_presence_changed = true);

View File

@ -64,11 +64,6 @@ void IEXIDevice::DMARead(u32 address, u32 size)
} }
} }
IEXIDevice* IEXIDevice::FindDevice(TEXIDevices device_type, int custom_index)
{
return (device_type == m_device_type) ? this : nullptr;
}
bool IEXIDevice::UseDelayedTransferCompletion() const bool IEXIDevice::UseDelayedTransferCompletion() const
{ {
return false; return false;
@ -101,60 +96,63 @@ void IEXIDevice::TransferByte(u8& byte)
} }
// F A C T O R Y // F A C T O R Y
std::unique_ptr<IEXIDevice> EXIDevice_Create(const TEXIDevices device_type, const int channel_num, std::unique_ptr<IEXIDevice> EXIDevice_Create(const EXIDeviceType device_type, const int channel_num,
const Memcard::HeaderData& memcard_header_data) const Memcard::HeaderData& memcard_header_data)
{ {
std::unique_ptr<IEXIDevice> result; std::unique_ptr<IEXIDevice> result;
// XXX This computation isn't necessarily right (it holds for A/B, but not SP1)
// However, the devices that care about slots currently only go in A/B.
const Slot slot = static_cast<Slot>(channel_num);
switch (device_type) switch (device_type)
{ {
case EXIDEVICE_DUMMY: case EXIDeviceType::Dummy:
result = std::make_unique<CEXIDummy>("Dummy"); result = std::make_unique<CEXIDummy>("Dummy");
break; break;
case EXIDEVICE_MEMORYCARD: case EXIDeviceType::MemoryCard:
case EXIDEVICE_MEMORYCARDFOLDER: case EXIDeviceType::MemoryCardFolder:
{ {
bool gci_folder = (device_type == EXIDEVICE_MEMORYCARDFOLDER); bool gci_folder = (device_type == EXIDeviceType::MemoryCardFolder);
result = std::make_unique<CEXIMemoryCard>(channel_num, gci_folder, memcard_header_data); result = std::make_unique<CEXIMemoryCard>(slot, gci_folder, memcard_header_data);
break; break;
} }
case EXIDEVICE_MASKROM: case EXIDeviceType::MaskROM:
result = std::make_unique<CEXIIPL>(); result = std::make_unique<CEXIIPL>();
break; break;
case EXIDEVICE_AD16: case EXIDeviceType::AD16:
result = std::make_unique<CEXIAD16>(); result = std::make_unique<CEXIAD16>();
break; break;
case EXIDEVICE_MIC: case EXIDeviceType::Microphone:
result = std::make_unique<CEXIMic>(channel_num); result = std::make_unique<CEXIMic>(channel_num);
break; break;
case EXIDEVICE_ETH: case EXIDeviceType::Ethernet:
result = std::make_unique<CEXIETHERNET>(BBADeviceType::TAP); result = std::make_unique<CEXIETHERNET>(BBADeviceType::TAP);
break; break;
#if defined(__APPLE__) #if defined(__APPLE__)
case EXIDEVICE_ETHTAPSERVER: case EXIDeviceType::EthernetTapServer:
result = std::make_unique<CEXIETHERNET>(BBADeviceType::TAPSERVER); result = std::make_unique<CEXIETHERNET>(BBADeviceType::TAPSERVER);
break; break;
#endif #endif
case EXIDEVICE_ETHXLINK: case EXIDeviceType::EthernetXLink:
result = std::make_unique<CEXIETHERNET>(BBADeviceType::XLINK); result = std::make_unique<CEXIETHERNET>(BBADeviceType::XLINK);
break; break;
case EXIDEVICE_GECKO: case EXIDeviceType::Gecko:
result = std::make_unique<CEXIGecko>(); result = std::make_unique<CEXIGecko>();
break; break;
case EXIDEVICE_AGP: case EXIDeviceType::AGP:
result = std::make_unique<CEXIAgp>(channel_num); result = std::make_unique<CEXIAgp>(slot);
break; break;
case EXIDEVICE_AM_BASEBOARD: case EXIDeviceType::AMBaseboard:
case EXIDEVICE_NONE: case EXIDeviceType::None:
default: default:
result = std::make_unique<IEXIDevice>(); result = std::make_unique<IEXIDevice>();
break; break;

View File

@ -4,7 +4,10 @@
#pragma once #pragma once
#include <memory> #include <memory>
#include "Common/Common.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/EnumFormatter.h"
class PointerWrap; class PointerWrap;
@ -15,26 +18,26 @@ struct HeaderData;
namespace ExpansionInterface namespace ExpansionInterface
{ {
enum TEXIDevices : int enum class EXIDeviceType : int
{ {
EXIDEVICE_DUMMY, Dummy,
EXIDEVICE_MEMORYCARD, MemoryCard,
EXIDEVICE_MASKROM, MaskROM,
EXIDEVICE_AD16, AD16,
EXIDEVICE_MIC, Microphone,
EXIDEVICE_ETH, Ethernet,
// Was used for Triforce in the past, but the implementation is no longer in Dolphin. // Was used for Triforce in the past, but the implementation is no longer in Dolphin.
// It's kept here so that values below will stay constant. // It's kept here so that values below will stay constant.
EXIDEVICE_AM_BASEBOARD, AMBaseboard,
EXIDEVICE_GECKO, Gecko,
// Only used when creating a device by EXIDevice_Create. // Only used when creating a device by EXIDevice_Create.
// Converted to EXIDEVICE_MEMORYCARD internally. // Converted to MemoryCard internally.
EXIDEVICE_MEMORYCARDFOLDER, MemoryCardFolder,
EXIDEVICE_AGP, AGP,
EXIDEVICE_ETHXLINK, EthernetXLink,
// Only used on Apple devices. // Only used on Apple devices.
EXIDEVICE_ETHTAPSERVER, EthernetTapServer,
EXIDEVICE_NONE = 0xFF None = 0xFF
}; };
class IEXIDevice class IEXIDevice
@ -51,8 +54,6 @@ public:
virtual void DMAWrite(u32 address, u32 size); virtual void DMAWrite(u32 address, u32 size);
virtual void DMARead(u32 address, u32 size); virtual void DMARead(u32 address, u32 size);
virtual IEXIDevice* FindDevice(TEXIDevices device_type, int custom_index = -1);
virtual bool UseDelayedTransferCompletion() const; virtual bool UseDelayedTransferCompletion() const;
virtual bool IsPresent() const; virtual bool IsPresent() const;
virtual void SetCS(int cs); virtual void SetCS(int cs);
@ -65,13 +66,60 @@ public:
// For savestates. storing it here seemed cleaner than requiring each implementation to report its // For savestates. storing it here seemed cleaner than requiring each implementation to report its
// type. I know this class is set up like an interface, but no code requires it to be strictly // type. I know this class is set up like an interface, but no code requires it to be strictly
// such. // such.
TEXIDevices m_device_type = TEXIDevices::EXIDEVICE_NONE; EXIDeviceType m_device_type = EXIDeviceType::None;
private: private:
// Byte transfer function for this device // Byte transfer function for this device
virtual void TransferByte(u8& byte); virtual void TransferByte(u8& byte);
}; };
std::unique_ptr<IEXIDevice> EXIDevice_Create(TEXIDevices device_type, int channel_num, std::unique_ptr<IEXIDevice> EXIDevice_Create(EXIDeviceType device_type, int channel_num,
const Memcard::HeaderData& memcard_header_data); const Memcard::HeaderData& memcard_header_data);
} // namespace ExpansionInterface } // namespace ExpansionInterface
template <>
struct fmt::formatter<ExpansionInterface::EXIDeviceType>
: EnumFormatter<ExpansionInterface::EXIDeviceType::EthernetTapServer>
{
static constexpr array_type names = {
_trans("Dummy"),
_trans("Memory Card"),
_trans("Mask ROM"),
// i18n: A mysterious debugging/diagnostics peripheral for the GameCube.
_trans("AD16"),
_trans("Microphone"),
_trans("Broadband Adapter (TAP)"),
_trans("Triforce AM Baseboard"),
_trans("USB Gecko"),
_trans("GCI Folder"),
_trans("Advance Game Port"),
_trans("Broadband Adapter (XLink Kai)"),
_trans("Broadband Adapter (tapserver)"),
};
constexpr formatter() : EnumFormatter(names) {}
template <typename FormatContext>
auto format(const ExpansionInterface::EXIDeviceType& e, FormatContext& ctx)
{
if (e != ExpansionInterface::EXIDeviceType::None)
{
return EnumFormatter::format(e, ctx);
}
else
{
// Special-case None since it has a fixed ID (0xff) that is much larger than the rest; we
// don't need 200 nullptr entries in names. We also want to format it specially in the UI.
switch (format_type)
{
default:
case 'u':
return fmt::format_to(ctx.out(), "None");
case 's':
return fmt::format_to(ctx.out(), "0xffu /* None */");
case 'n':
return fmt::format_to(ctx.out(), _trans("<Nothing>"));
}
}
}
};

View File

@ -8,18 +8,21 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "Common/Assert.h"
#include "Common/ChunkFile.h" #include "Common/ChunkFile.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/IOFile.h" #include "Common/IOFile.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Common/StringUtil.h" #include "Common/StringUtil.h"
#include "Core/Config/MainSettings.h" #include "Core/Config/MainSettings.h"
#include "Core/HW/EXI/EXI.h"
namespace ExpansionInterface namespace ExpansionInterface
{ {
CEXIAgp::CEXIAgp(int index) CEXIAgp::CEXIAgp(Slot slot)
{ {
m_slot = index; ASSERT(IsMemcardSlot(slot));
m_slot = slot;
// Create the ROM // Create the ROM
m_rom_size = 0; m_rom_size = 0;
@ -35,9 +38,7 @@ CEXIAgp::~CEXIAgp()
std::string filename; std::string filename;
std::string ext; std::string ext;
std::string gbapath; std::string gbapath;
SplitPath(m_slot == 0 ? Config::Get(Config::MAIN_AGP_CART_A_PATH) : SplitPath(Config::Get(Config::GetInfoForAGPCartPath(m_slot)), &path, &filename, &ext);
Config::Get(Config::MAIN_AGP_CART_B_PATH),
&path, &filename, &ext);
gbapath = path + filename; gbapath = path + filename;
SaveFileFromEEPROM(gbapath + ".sav"); SaveFileFromEEPROM(gbapath + ".sav");
@ -75,9 +76,7 @@ void CEXIAgp::LoadRom()
std::string path; std::string path;
std::string filename; std::string filename;
std::string ext; std::string ext;
SplitPath(m_slot == 0 ? Config::Get(Config::MAIN_AGP_CART_A_PATH) : SplitPath(Config::Get(Config::GetInfoForAGPCartPath(m_slot)), &path, &filename, &ext);
Config::Get(Config::MAIN_AGP_CART_B_PATH),
&path, &filename, &ext);
const std::string gbapath = path + filename; const std::string gbapath = path + filename;
LoadFileToROM(gbapath + ext); LoadFileToROM(gbapath + ext);
INFO_LOG_FMT(EXPANSIONINTERFACE, "Loaded GBA rom: {} card: {}", gbapath, m_slot); INFO_LOG_FMT(EXPANSIONINTERFACE, "Loaded GBA rom: {} card: {}", gbapath, m_slot);

View File

@ -12,10 +12,12 @@ class PointerWrap;
namespace ExpansionInterface namespace ExpansionInterface
{ {
enum class Slot : int;
class CEXIAgp : public IEXIDevice class CEXIAgp : public IEXIDevice
{ {
public: public:
CEXIAgp(const int index); CEXIAgp(const Slot slot);
virtual ~CEXIAgp() override; virtual ~CEXIAgp() override;
bool IsPresent() const override { return true; } bool IsPresent() const override { return true; }
void ImmWrite(u32 _uData, u32 _uSize) override; void ImmWrite(u32 _uData, u32 _uSize) override;
@ -31,7 +33,7 @@ private:
EE_READ_TRUE = 0xB, EE_READ_TRUE = 0xB,
}; };
int m_slot; Slot m_slot;
//! ROM //! ROM
u32 m_rom_size = 0; u32 m_rom_size = 0;

View File

@ -16,6 +16,7 @@
#include "Common/CommonPaths.h" #include "Common/CommonPaths.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/Config/Config.h" #include "Common/Config/Config.h"
#include "Common/EnumMap.h"
#include "Common/FileUtil.h" #include "Common/FileUtil.h"
#include "Common/IniFile.h" #include "Common/IniFile.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
@ -48,25 +49,24 @@ namespace ExpansionInterface
static const u32 MC_TRANSFER_RATE_READ = 512 * 1024; static const u32 MC_TRANSFER_RATE_READ = 512 * 1024;
static const auto MC_TRANSFER_RATE_WRITE = static_cast<u32>(96.125f * 1024.0f); static const auto MC_TRANSFER_RATE_WRITE = static_cast<u32>(96.125f * 1024.0f);
static std::array<CoreTiming::EventType*, 2> s_et_cmd_done; static Common::EnumMap<CoreTiming::EventType*, MAX_MEMCARD_SLOT> s_et_cmd_done;
static std::array<CoreTiming::EventType*, 2> s_et_transfer_complete; static Common::EnumMap<CoreTiming::EventType*, MAX_MEMCARD_SLOT> s_et_transfer_complete;
static Common::EnumMap<char, MAX_MEMCARD_SLOT> s_card_short_names{'A', 'B'};
// Takes care of the nasty recovery of the 'this' pointer from card_index, // Takes care of the nasty recovery of the 'this' pointer from card_slot,
// stored in the userdata parameter of the CoreTiming event. // stored in the userdata parameter of the CoreTiming event.
void CEXIMemoryCard::EventCompleteFindInstance(u64 userdata, void CEXIMemoryCard::EventCompleteFindInstance(u64 userdata,
std::function<void(CEXIMemoryCard*)> callback) std::function<void(CEXIMemoryCard*)> callback)
{ {
int card_index = (int)userdata; Slot card_slot = static_cast<Slot>(userdata);
auto* self = static_cast<CEXIMemoryCard*>( IEXIDevice* self = ExpansionInterface::GetDevice(card_slot);
ExpansionInterface::FindDevice(EXIDEVICE_MEMORYCARD, card_index)); if (self != nullptr)
if (self == nullptr)
{ {
self = static_cast<CEXIMemoryCard*>( if (self->m_device_type == EXIDeviceType::MemoryCard ||
ExpansionInterface::FindDevice(EXIDEVICE_MEMORYCARDFOLDER, card_index)); self->m_device_type == EXIDeviceType::MemoryCardFolder)
} {
if (self) callback(static_cast<CEXIMemoryCard*>(self));
{ }
callback(self);
} }
} }
@ -83,19 +83,15 @@ void CEXIMemoryCard::TransferCompleteCallback(u64 userdata, s64)
void CEXIMemoryCard::Init() void CEXIMemoryCard::Init()
{ {
static constexpr char DONE_PREFIX[] = "memcardDone";
static constexpr char TRANSFER_COMPLETE_PREFIX[] = "memcardTransferComplete";
static_assert(s_et_cmd_done.size() == s_et_transfer_complete.size(), "Event array size differs"); static_assert(s_et_cmd_done.size() == s_et_transfer_complete.size(), "Event array size differs");
for (unsigned int i = 0; i < s_et_cmd_done.size(); ++i) static_assert(s_et_cmd_done.size() == MEMCARD_SLOTS.size(), "Event array size differs");
for (Slot slot : MEMCARD_SLOTS)
{ {
std::string name = DONE_PREFIX; s_et_cmd_done[slot] = CoreTiming::RegisterEvent(
name += static_cast<char>('A' + i); fmt::format("memcardDone{}", s_card_short_names[slot]), CmdDoneCallback);
s_et_cmd_done[i] = CoreTiming::RegisterEvent(name, CmdDoneCallback); s_et_transfer_complete[slot] = CoreTiming::RegisterEvent(
fmt::format("memcardTransferComplete{}", s_card_short_names[slot]),
name = TRANSFER_COMPLETE_PREFIX; TransferCompleteCallback);
name += static_cast<char>('A' + i);
s_et_transfer_complete[i] = CoreTiming::RegisterEvent(name, TransferCompleteCallback);
} }
} }
@ -105,12 +101,12 @@ void CEXIMemoryCard::Shutdown()
s_et_transfer_complete.fill(nullptr); s_et_transfer_complete.fill(nullptr);
} }
CEXIMemoryCard::CEXIMemoryCard(const int index, bool gci_folder, CEXIMemoryCard::CEXIMemoryCard(const Slot slot, bool gci_folder,
const Memcard::HeaderData& header_data) const Memcard::HeaderData& header_data)
: m_card_index(index) : m_card_slot(slot)
{ {
ASSERT_MSG(EXPANSIONINTERFACE, static_cast<std::size_t>(index) < s_et_cmd_done.size(), ASSERT_MSG(EXPANSIONINTERFACE, IsMemcardSlot(slot), "Trying to create invalid memory card in {}.",
"Trying to create invalid memory card index {}.", index); slot);
// NOTE: When loading a save state, DMA completion callbacks (s_et_transfer_complete) and such // NOTE: When loading a save state, DMA completion callbacks (s_et_transfer_complete) and such
// may have been restored, we need to anticipate those arriving. // may have been restored, we need to anticipate those arriving.
@ -145,15 +141,13 @@ CEXIMemoryCard::CEXIMemoryCard(const int index, bool gci_folder,
m_memory_card_size = m_memory_card->GetCardId() * SIZE_TO_Mb; m_memory_card_size = m_memory_card->GetCardId() * SIZE_TO_Mb;
std::array<u8, 20> header{}; std::array<u8, 20> header{};
m_memory_card->Read(0, static_cast<s32>(header.size()), header.data()); m_memory_card->Read(0, static_cast<s32>(header.size()), header.data());
SetCardFlashID(header.data(), m_card_index); SetCardFlashID(header.data(), m_card_slot);
} }
std::pair<std::string /* path */, bool /* migrate */> std::pair<std::string /* path */, bool /* migrate */>
CEXIMemoryCard::GetGCIFolderPath(int card_index, AllowMovieFolder allow_movie_folder) CEXIMemoryCard::GetGCIFolderPath(Slot card_slot, AllowMovieFolder allow_movie_folder)
{ {
std::string path_override = std::string path_override = Config::Get(Config::GetInfoForGCIPathOverride(card_slot));
Config::Get(card_index == 0 ? Config::MAIN_GCI_FOLDER_A_PATH_OVERRIDE :
Config::MAIN_GCI_FOLDER_B_PATH_OVERRIDE);
if (!path_override.empty()) if (!path_override.empty())
return {std::move(path_override), false}; return {std::move(path_override), false};
@ -162,7 +156,7 @@ CEXIMemoryCard::GetGCIFolderPath(int card_index, AllowMovieFolder allow_movie_fo
const bool use_movie_folder = allow_movie_folder == AllowMovieFolder::Yes && const bool use_movie_folder = allow_movie_folder == AllowMovieFolder::Yes &&
Movie::IsPlayingInput() && Movie::IsConfigSaved() && Movie::IsPlayingInput() && Movie::IsConfigSaved() &&
Movie::IsUsingMemcard(card_index) && Movie::IsUsingMemcard(card_slot) &&
Movie::IsStartingFromClearSave(); Movie::IsStartingFromClearSave();
if (use_movie_folder) if (use_movie_folder)
@ -170,7 +164,7 @@ CEXIMemoryCard::GetGCIFolderPath(int card_index, AllowMovieFolder allow_movie_fo
const DiscIO::Region region = SConfig::ToGameCubeRegion(SConfig::GetInstance().m_region); const DiscIO::Region region = SConfig::ToGameCubeRegion(SConfig::GetInstance().m_region);
path = path + SConfig::GetDirectoryForRegion(region) + DIR_SEP + path = path + SConfig::GetDirectoryForRegion(region) + DIR_SEP +
fmt::format("Card {}", char('A' + card_index)); fmt::format("Card {}", s_card_short_names[card_slot]);
return {std::move(path), !use_movie_folder}; return {std::move(path), !use_movie_folder};
} }
@ -186,7 +180,7 @@ void CEXIMemoryCard::SetupGciFolder(const Memcard::HeaderData& header_data)
// TODO(C++20): Use structured bindings when we can use C++20 and refer to structured bindings // TODO(C++20): Use structured bindings when we can use C++20 and refer to structured bindings
// in lambda captures // in lambda captures
const auto folder_path_pair = GetGCIFolderPath(m_card_index, AllowMovieFolder::Yes); const auto folder_path_pair = GetGCIFolderPath(m_card_slot, AllowMovieFolder::Yes);
const std::string& dir_path = folder_path_pair.first; const std::string& dir_path = folder_path_pair.first;
const bool migrate = folder_path_pair.second; const bool migrate = folder_path_pair.second;
@ -194,7 +188,7 @@ void CEXIMemoryCard::SetupGciFolder(const Memcard::HeaderData& header_data)
if (!file_info.Exists()) if (!file_info.Exists())
{ {
if (migrate) // first use of memcard folder, migrate automatically if (migrate) // first use of memcard folder, migrate automatically
MigrateFromMemcardFile(dir_path + DIR_SEP, m_card_index); MigrateFromMemcardFile(dir_path + DIR_SEP, m_card_slot);
else else
File::CreateFullPath(dir_path + DIR_SEP); File::CreateFullPath(dir_path + DIR_SEP);
} }
@ -204,7 +198,7 @@ void CEXIMemoryCard::SetupGciFolder(const Memcard::HeaderData& header_data)
{ {
PanicAlertFmtT("{0} was not a directory, moved to *.original", dir_path); PanicAlertFmtT("{0} was not a directory, moved to *.original", dir_path);
if (migrate) if (migrate)
MigrateFromMemcardFile(dir_path + DIR_SEP, m_card_index); MigrateFromMemcardFile(dir_path + DIR_SEP, m_card_slot);
else else
File::CreateFullPath(dir_path + DIR_SEP); File::CreateFullPath(dir_path + DIR_SEP);
} }
@ -218,22 +212,23 @@ void CEXIMemoryCard::SetupGciFolder(const Memcard::HeaderData& header_data)
} }
} }
m_memory_card = std::make_unique<GCMemcardDirectory>(dir_path + DIR_SEP, m_card_index, m_memory_card = std::make_unique<GCMemcardDirectory>(dir_path + DIR_SEP, m_card_slot, header_data,
header_data, current_game_id); current_game_id);
} }
void CEXIMemoryCard::SetupRawMemcard(u16 size_mb) void CEXIMemoryCard::SetupRawMemcard(u16 size_mb)
{ {
const bool is_slot_a = m_card_index == 0; std::string filename = Config::Get(Config::GetInfoForMemcardPath(m_card_slot));
std::string filename = is_slot_a ? Config::Get(Config::MAIN_MEMCARD_A_PATH) : if (Movie::IsPlayingInput() && Movie::IsConfigSaved() && Movie::IsUsingMemcard(m_card_slot) &&
Config::Get(Config::MAIN_MEMCARD_B_PATH);
if (Movie::IsPlayingInput() && Movie::IsConfigSaved() && Movie::IsUsingMemcard(m_card_index) &&
Movie::IsStartingFromClearSave()) Movie::IsStartingFromClearSave())
filename = File::GetUserPath(D_GCUSER_IDX) + fmt::format("Movie{}.raw", is_slot_a ? 'A' : 'B'); {
filename = File::GetUserPath(D_GCUSER_IDX) +
fmt::format("Movie{}.raw", s_card_short_names[m_card_slot]);
}
const std::string region_dir = const std::string region_dir =
SConfig::GetDirectoryForRegion(SConfig::ToGameCubeRegion(SConfig::GetInstance().m_region)); SConfig::GetDirectoryForRegion(SConfig::ToGameCubeRegion(SConfig::GetInstance().m_region));
MemoryCard::CheckPath(filename, region_dir, is_slot_a); MemoryCard::CheckPath(filename, region_dir, m_card_slot);
if (size_mb < Memcard::MBIT_SIZE_MEMORY_CARD_2043) if (size_mb < Memcard::MBIT_SIZE_MEMORY_CARD_2043)
{ {
@ -241,13 +236,13 @@ void CEXIMemoryCard::SetupRawMemcard(u16 size_mb)
fmt::format(".{}", Memcard::MbitToFreeBlocks(size_mb))); fmt::format(".{}", Memcard::MbitToFreeBlocks(size_mb)));
} }
m_memory_card = std::make_unique<MemoryCard>(filename, m_card_index, size_mb); m_memory_card = std::make_unique<MemoryCard>(filename, m_card_slot, size_mb);
} }
CEXIMemoryCard::~CEXIMemoryCard() CEXIMemoryCard::~CEXIMemoryCard()
{ {
CoreTiming::RemoveEvent(s_et_cmd_done[m_card_index]); CoreTiming::RemoveEvent(s_et_cmd_done[m_card_slot]);
CoreTiming::RemoveEvent(s_et_transfer_complete[m_card_index]); CoreTiming::RemoveEvent(s_et_transfer_complete[m_card_slot]);
} }
bool CEXIMemoryCard::UseDelayedTransferCompletion() const bool CEXIMemoryCard::UseDelayedTransferCompletion() const
@ -272,13 +267,14 @@ void CEXIMemoryCard::CmdDone()
void CEXIMemoryCard::TransferComplete() void CEXIMemoryCard::TransferComplete()
{ {
// Transfer complete, send interrupt // Transfer complete, send interrupt
ExpansionInterface::GetChannel(m_card_index)->SendTransferComplete(); ExpansionInterface::GetChannel(ExpansionInterface::SlotToEXIChannel(m_card_slot))
->SendTransferComplete();
} }
void CEXIMemoryCard::CmdDoneLater(u64 cycles) void CEXIMemoryCard::CmdDoneLater(u64 cycles)
{ {
CoreTiming::RemoveEvent(s_et_cmd_done[m_card_index]); CoreTiming::RemoveEvent(s_et_cmd_done[m_card_slot]);
CoreTiming::ScheduleEvent(cycles, s_et_cmd_done[m_card_index], m_card_index); CoreTiming::ScheduleEvent(cycles, s_et_cmd_done[m_card_slot], static_cast<u64>(m_card_slot));
} }
void CEXIMemoryCard::SetCS(int cs) void CEXIMemoryCard::SetCS(int cs)
@ -522,19 +518,10 @@ void CEXIMemoryCard::DoState(PointerWrap& p)
p.Do(m_programming_buffer); p.Do(m_programming_buffer);
p.Do(m_address); p.Do(m_address);
m_memory_card->DoState(p); m_memory_card->DoState(p);
p.Do(m_card_index); p.Do(m_card_slot);
} }
} }
IEXIDevice* CEXIMemoryCard::FindDevice(TEXIDevices device_type, int custom_index)
{
if (device_type != m_device_type)
return nullptr;
if (custom_index != m_card_index)
return nullptr;
return this;
}
// DMA reads are preceded by all of the necessary setup via IMMRead // DMA reads are preceded by all of the necessary setup via IMMRead
// read all at once instead of single byte at a time as done by IEXIDevice::DMARead // read all at once instead of single byte at a time as done by IEXIDevice::DMARead
void CEXIMemoryCard::DMARead(u32 addr, u32 size) void CEXIMemoryCard::DMARead(u32 addr, u32 size)
@ -548,7 +535,7 @@ void CEXIMemoryCard::DMARead(u32 addr, u32 size)
// Schedule transfer complete later based on read speed // Schedule transfer complete later based on read speed
CoreTiming::ScheduleEvent(size * (SystemTimers::GetTicksPerSecond() / MC_TRANSFER_RATE_READ), CoreTiming::ScheduleEvent(size * (SystemTimers::GetTicksPerSecond() / MC_TRANSFER_RATE_READ),
s_et_transfer_complete[m_card_index], m_card_index); s_et_transfer_complete[m_card_slot], static_cast<u64>(m_card_slot));
} }
// DMA write are preceded by all of the necessary setup via IMMWrite // DMA write are preceded by all of the necessary setup via IMMWrite
@ -564,6 +551,6 @@ void CEXIMemoryCard::DMAWrite(u32 addr, u32 size)
// Schedule transfer complete later based on write speed // Schedule transfer complete later based on write speed
CoreTiming::ScheduleEvent(size * (SystemTimers::GetTicksPerSecond() / MC_TRANSFER_RATE_WRITE), CoreTiming::ScheduleEvent(size * (SystemTimers::GetTicksPerSecond() / MC_TRANSFER_RATE_WRITE),
s_et_transfer_complete[m_card_index], m_card_index); s_et_transfer_complete[m_card_slot], static_cast<u64>(m_card_slot));
} }
} // namespace ExpansionInterface } // namespace ExpansionInterface

View File

@ -21,6 +21,8 @@ struct HeaderData;
namespace ExpansionInterface namespace ExpansionInterface
{ {
enum class Slot : int;
enum class AllowMovieFolder enum class AllowMovieFolder
{ {
Yes, Yes,
@ -30,14 +32,13 @@ enum class AllowMovieFolder
class CEXIMemoryCard : public IEXIDevice class CEXIMemoryCard : public IEXIDevice
{ {
public: public:
CEXIMemoryCard(int index, bool gci_folder, const Memcard::HeaderData& header_data); CEXIMemoryCard(Slot slot, bool gci_folder, const Memcard::HeaderData& header_data);
~CEXIMemoryCard() override; ~CEXIMemoryCard() override;
void SetCS(int cs) override; void SetCS(int cs) override;
bool IsInterruptSet() override; bool IsInterruptSet() override;
bool UseDelayedTransferCompletion() const override; bool UseDelayedTransferCompletion() const override;
bool IsPresent() const override; bool IsPresent() const override;
void DoState(PointerWrap& p) override; void DoState(PointerWrap& p) override;
IEXIDevice* FindDevice(TEXIDevices device_type, int custom_index) override;
void DMARead(u32 addr, u32 size) override; void DMARead(u32 addr, u32 size) override;
void DMAWrite(u32 addr, u32 size) override; void DMAWrite(u32 addr, u32 size) override;
@ -48,7 +49,7 @@ public:
static void Shutdown(); static void Shutdown();
static std::pair<std::string /* path */, bool /* migrate */> static std::pair<std::string /* path */, bool /* migrate */>
GetGCIFolderPath(int card_index, AllowMovieFolder allow_movie_folder); GetGCIFolderPath(Slot card_slot, AllowMovieFolder allow_movie_folder);
private: private:
void SetupGciFolder(const Memcard::HeaderData& header_data); void SetupGciFolder(const Memcard::HeaderData& header_data);
@ -90,7 +91,7 @@ private:
ChipErase = 0xF4, ChipErase = 0xF4,
}; };
int m_card_index; Slot m_card_slot;
//! memory card state //! memory card state
// STATE_TO_SAVE // STATE_TO_SAVE

View File

@ -12,8 +12,9 @@ class PointerWrap;
class MemoryCardBase class MemoryCardBase
{ {
public: public:
explicit MemoryCardBase(int card_index = 0, int size_mbits = Memcard::MBIT_SIZE_MEMORY_CARD_2043) explicit MemoryCardBase(ExpansionInterface::Slot card_slot,
: m_card_index(card_index), m_nintendo_card_id(size_mbits) int size_mbits = Memcard::MBIT_SIZE_MEMORY_CARD_2043)
: m_card_slot(card_slot), m_nintendo_card_id(size_mbits)
{ {
} }
virtual ~MemoryCardBase() = default; virtual ~MemoryCardBase() = default;
@ -25,6 +26,6 @@ public:
u32 GetCardId() const { return m_nintendo_card_id; } u32 GetCardId() const { return m_nintendo_card_id; }
protected: protected:
int m_card_index; ExpansionInterface::Slot m_card_slot;
u16 m_nintendo_card_id; u16 m_nintendo_card_id;
}; };

View File

@ -152,7 +152,7 @@ std::vector<std::string> GCMemcardDirectory::GetFileNamesForGameID(const std::st
return filenames; return filenames;
} }
GCMemcardDirectory::GCMemcardDirectory(const std::string& directory, int slot, GCMemcardDirectory::GCMemcardDirectory(const std::string& directory, ExpansionInterface::Slot slot,
const Memcard::HeaderData& header_data, u32 game_id) const Memcard::HeaderData& header_data, u32 game_id)
: MemoryCardBase(slot, header_data.m_size_mb), m_game_id(game_id), m_last_block(-1), : MemoryCardBase(slot, header_data.m_size_mb), m_game_id(game_id), m_last_block(-1),
m_hdr(header_data), m_bat1(header_data.m_size_mb), m_saves(0), m_save_directory(directory), m_hdr(header_data), m_bat1(header_data.m_size_mb), m_saves(0), m_save_directory(directory),
@ -240,7 +240,7 @@ void GCMemcardDirectory::FlushThread()
return; return;
} }
Common::SetCurrentThreadName(fmt::format("Memcard {} flushing thread", m_card_index).c_str()); Common::SetCurrentThreadName(fmt::format("Memcard {} flushing thread", m_card_slot).c_str());
constexpr std::chrono::seconds flush_interval{1}; constexpr std::chrono::seconds flush_interval{1};
while (true) while (true)
@ -705,11 +705,10 @@ void GCMemcardDirectory::DoState(PointerWrap& p)
} }
} }
void MigrateFromMemcardFile(const std::string& directory_name, int card_index) void MigrateFromMemcardFile(const std::string& directory_name, ExpansionInterface::Slot card_slot)
{ {
File::CreateFullPath(directory_name); File::CreateFullPath(directory_name);
std::string ini_memcard = (card_index == 0) ? Config::Get(Config::MAIN_MEMCARD_A_PATH) : std::string ini_memcard = Config::Get(Config::GetInfoForMemcardPath(card_slot));
Config::Get(Config::MAIN_MEMCARD_B_PATH);
if (File::Exists(ini_memcard)) if (File::Exists(ini_memcard))
{ {
auto [error_code, memcard] = Memcard::GCMemcard::Open(ini_memcard.c_str()); auto [error_code, memcard] = Memcard::GCMemcard::Open(ini_memcard.c_str());

View File

@ -15,13 +15,13 @@
// Uncomment this to write the system data of the memorycard from directory to disc // Uncomment this to write the system data of the memorycard from directory to disc
//#define _WRITE_MC_HEADER 1 //#define _WRITE_MC_HEADER 1
void MigrateFromMemcardFile(const std::string& directory_name, int card_index); void MigrateFromMemcardFile(const std::string& directory_name, ExpansionInterface::Slot card_slot);
class GCMemcardDirectory : public MemoryCardBase class GCMemcardDirectory : public MemoryCardBase
{ {
public: public:
GCMemcardDirectory(const std::string& directory, int slot, const Memcard::HeaderData& header_data, GCMemcardDirectory(const std::string& directory, ExpansionInterface::Slot slot,
u32 game_id); const Memcard::HeaderData& header_data, u32 game_id);
~GCMemcardDirectory(); ~GCMemcardDirectory();
GCMemcardDirectory(const GCMemcardDirectory&) = delete; GCMemcardDirectory(const GCMemcardDirectory&) = delete;

View File

@ -26,6 +26,7 @@
#include "Core/Config/SessionSettings.h" #include "Core/Config/SessionSettings.h"
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
#include "Core/Core.h" #include "Core/Core.h"
#include "Core/HW/EXI/EXI.h"
#include "Core/HW/EXI/EXI_DeviceIPL.h" #include "Core/HW/EXI/EXI_DeviceIPL.h"
#include "Core/HW/GCMemcard/GCMemcard.h" #include "Core/HW/GCMemcard/GCMemcard.h"
#include "Core/HW/Sram.h" #include "Core/HW/Sram.h"
@ -33,8 +34,9 @@
#define SIZE_TO_Mb (1024 * 8 * 16) #define SIZE_TO_Mb (1024 * 8 * 16)
#define MC_HDR_SIZE 0xA000 #define MC_HDR_SIZE 0xA000
MemoryCard::MemoryCard(const std::string& filename, int card_index, u16 size_mbits) MemoryCard::MemoryCard(const std::string& filename, ExpansionInterface::Slot card_slot,
: MemoryCardBase(card_index, size_mbits), m_filename(filename) u16 size_mbits)
: MemoryCardBase(card_slot, size_mbits), m_filename(filename)
{ {
File::IOFile file(m_filename, "rb"); File::IOFile file(m_filename, "rb");
if (file) if (file)
@ -88,13 +90,15 @@ MemoryCard::~MemoryCard()
} }
} }
void MemoryCard::CheckPath(std::string& memcardPath, const std::string& gameRegion, bool isSlotA) void MemoryCard::CheckPath(std::string& memcardPath, const std::string& gameRegion,
ExpansionInterface::Slot card_slot)
{ {
bool is_slot_a = card_slot == ExpansionInterface::Slot::A;
std::string ext("." + gameRegion + ".raw"); std::string ext("." + gameRegion + ".raw");
if (memcardPath.empty()) if (memcardPath.empty())
{ {
// Use default memcard path if there is no user defined name // Use default memcard path if there is no user defined name
std::string defaultFilename = isSlotA ? GC_MEMCARDA : GC_MEMCARDB; std::string defaultFilename = is_slot_a ? GC_MEMCARDA : GC_MEMCARDB;
memcardPath = File::GetUserPath(D_GCUSER_IDX) + defaultFilename + ext; memcardPath = File::GetUserPath(D_GCUSER_IDX) + defaultFilename + ext;
} }
else else
@ -118,7 +122,7 @@ void MemoryCard::CheckPath(std::string& memcardPath, const std::string& gameRegi
"Slot {1} path was changed to\n" "Slot {1} path was changed to\n"
"{2}\n" "{2}\n"
"Would you like to copy the old file to this new location?\n", "Would you like to copy the old file to this new location?\n",
isSlotA ? 'A' : 'B', isSlotA ? 'A' : 'B', filename)) is_slot_a ? 'A' : 'B', is_slot_a ? 'A' : 'B', filename))
{ {
if (!File::Copy(oldFilename, filename)) if (!File::Copy(oldFilename, filename))
PanicAlertFmtT("Copy failed"); PanicAlertFmtT("Copy failed");
@ -142,7 +146,7 @@ void MemoryCard::FlushThread()
return; return;
} }
Common::SetCurrentThreadName(fmt::format("Memcard {} flushing thread", m_card_index).c_str()); Common::SetCurrentThreadName(fmt::format("Memcard {} flushing thread", m_card_slot).c_str());
const auto flush_interval = std::chrono::seconds(15); const auto flush_interval = std::chrono::seconds(15);
@ -199,9 +203,10 @@ void MemoryCard::FlushThread()
if (do_exit) if (do_exit)
return; return;
Core::DisplayMessage( Core::DisplayMessage(fmt::format("Wrote memory card {} contents to {}",
fmt::format("Wrote memory card {} contents to {}", m_card_index ? 'B' : 'A', m_filename), m_card_slot == ExpansionInterface::Slot::A ? 'A' : 'B',
4000); m_filename),
4000);
} }
} }
@ -265,7 +270,7 @@ void MemoryCard::ClearAll()
void MemoryCard::DoState(PointerWrap& p) void MemoryCard::DoState(PointerWrap& p)
{ {
p.Do(m_card_index); p.Do(m_card_slot);
p.Do(m_memory_card_size); p.Do(m_memory_card_size);
p.DoArray(&m_memcard_data[0], m_memory_card_size); p.DoArray(&m_memcard_data[0], m_memory_card_size);
} }

View File

@ -17,10 +17,11 @@ class PointerWrap;
class MemoryCard : public MemoryCardBase class MemoryCard : public MemoryCardBase
{ {
public: public:
MemoryCard(const std::string& filename, int card_index, MemoryCard(const std::string& filename, ExpansionInterface::Slot card_slot,
u16 size_mbits = Memcard::MBIT_SIZE_MEMORY_CARD_2043); u16 size_mbits = Memcard::MBIT_SIZE_MEMORY_CARD_2043);
~MemoryCard(); ~MemoryCard();
static void CheckPath(std::string& memcardPath, const std::string& gameRegion, bool isSlotA); static void CheckPath(std::string& memcardPath, const std::string& gameRegion,
ExpansionInterface::Slot slot);
void FlushThread(); void FlushThread();
void MakeDirty(); void MakeDirty();

View File

@ -6,8 +6,11 @@
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/IOFile.h" #include "Common/IOFile.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"
#include "Common/Swap.h" #include "Common/Swap.h"
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
#include "Core/HW/EXI/EXI.h"
// English // English
// This is just a template. Most/all fields are updated with sane(r) values at runtime. // This is just a template. Most/all fields are updated with sane(r) values at runtime.
@ -72,8 +75,22 @@ void InitSRAM()
} }
} }
void SetCardFlashID(const u8* buffer, u8 card_index) void SetCardFlashID(const u8* buffer, ExpansionInterface::Slot card_slot)
{ {
u8 card_index;
switch (card_slot)
{
case ExpansionInterface::Slot::A:
card_index = 0;
break;
case ExpansionInterface::Slot::B:
card_index = 1;
break;
default:
PanicAlertFmt("Invalid memcard slot {}", card_slot);
return;
}
u64 rand = Common::swap64(&buffer[12]); u64 rand = Common::swap64(&buffer[12]);
u8 csum = 0; u8 csum = 0;
for (int i = 0; i < 12; i++) for (int i = 0; i < 12; i++)

View File

@ -34,9 +34,15 @@ distribution.
#pragma once #pragma once
#include <array> #include <array>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/Swap.h" #include "Common/Swap.h"
namespace ExpansionInterface
{
enum class Slot : int;
};
using CardFlashId = std::array<u8, 12>; using CardFlashId = std::array<u8, 12>;
#pragma pack(push, 1) #pragma pack(push, 1)
@ -128,7 +134,7 @@ static_assert(sizeof(Sram) == 0x44);
#pragma pack(pop) #pragma pack(pop)
void InitSRAM(); void InitSRAM();
void SetCardFlashID(const u8* buffer, u8 card_index); void SetCardFlashID(const u8* buffer, ExpansionInterface::Slot card_slot);
void FixSRAMChecksums(); void FixSRAMChecksums();
extern Sram g_SRAM; extern Sram g_SRAM;

View File

@ -43,6 +43,7 @@
#include "Core/DSP/DSPCore.h" #include "Core/DSP/DSPCore.h"
#include "Core/HW/CPU.h" #include "Core/HW/CPU.h"
#include "Core/HW/DVD/DVDInterface.h" #include "Core/HW/DVD/DVDInterface.h"
#include "Core/HW/EXI/EXI.h"
#include "Core/HW/EXI/EXI_DeviceIPL.h" #include "Core/HW/EXI/EXI_DeviceIPL.h"
#include "Core/HW/EXI/EXI_DeviceMemoryCard.h" #include "Core/HW/EXI/EXI_DeviceMemoryCard.h"
#include "Core/HW/ProcessorInterface.h" #include "Core/HW/ProcessorInterface.h"
@ -433,9 +434,17 @@ bool IsStartingFromClearSave()
return s_bClearSave; return s_bClearSave;
} }
bool IsUsingMemcard(int memcard) bool IsUsingMemcard(ExpansionInterface::Slot slot)
{ {
return (s_memcards & (1 << memcard)) != 0; switch (slot)
{
case ExpansionInterface::Slot::A:
return (s_memcards & 1) != 0;
case ExpansionInterface::Slot::B:
return (s_memcards & 2) != 0;
default:
return false;
}
} }
bool IsNetPlayRecording() bool IsNetPlayRecording()
@ -1438,12 +1447,13 @@ void SetGraphicsConfig()
// NOTE: EmuThread / Host Thread // NOTE: EmuThread / Host Thread
void GetSettings() void GetSettings()
{ {
const ExpansionInterface::TEXIDevices slot_a_type = Config::Get(Config::MAIN_SLOT_A); using ExpansionInterface::EXIDeviceType;
const ExpansionInterface::TEXIDevices slot_b_type = Config::Get(Config::MAIN_SLOT_B); const EXIDeviceType slot_a_type = Config::Get(Config::MAIN_SLOT_A);
const bool slot_a_has_raw_memcard = slot_a_type == ExpansionInterface::EXIDEVICE_MEMORYCARD; const EXIDeviceType slot_b_type = Config::Get(Config::MAIN_SLOT_B);
const bool slot_a_has_gci_folder = slot_a_type == ExpansionInterface::EXIDEVICE_MEMORYCARDFOLDER; const bool slot_a_has_raw_memcard = slot_a_type == EXIDeviceType::MemoryCard;
const bool slot_b_has_raw_memcard = slot_b_type == ExpansionInterface::EXIDEVICE_MEMORYCARD; const bool slot_a_has_gci_folder = slot_a_type == EXIDeviceType::MemoryCardFolder;
const bool slot_b_has_gci_folder = slot_b_type == ExpansionInterface::EXIDEVICE_MEMORYCARDFOLDER; const bool slot_b_has_raw_memcard = slot_b_type == EXIDeviceType::MemoryCard;
const bool slot_b_has_gci_folder = slot_b_type == EXIDeviceType::MemoryCardFolder;
s_bSaveConfig = true; s_bSaveConfig = true;
s_bNetPlay = NetPlay::IsNetPlayRunning(); s_bNetPlay = NetPlay::IsNetPlayRunning();
@ -1455,9 +1465,9 @@ void GetSettings()
} }
else else
{ {
const auto gci_folder_has_saves = [](int card_index) { const auto gci_folder_has_saves = [](ExpansionInterface::Slot card_slot) {
const auto [path, migrate] = ExpansionInterface::CEXIMemoryCard::GetGCIFolderPath( const auto [path, migrate] = ExpansionInterface::CEXIMemoryCard::GetGCIFolderPath(
card_index, ExpansionInterface::AllowMovieFolder::No); card_slot, ExpansionInterface::AllowMovieFolder::No);
const u64 number_of_saves = File::ScanDirectoryTree(path, false).size; const u64 number_of_saves = File::ScanDirectoryTree(path, false).size;
return number_of_saves > 0; return number_of_saves > 0;
}; };
@ -1465,8 +1475,8 @@ void GetSettings()
s_bClearSave = s_bClearSave =
!(slot_a_has_raw_memcard && File::Exists(Config::Get(Config::MAIN_MEMCARD_A_PATH))) && !(slot_a_has_raw_memcard && File::Exists(Config::Get(Config::MAIN_MEMCARD_A_PATH))) &&
!(slot_b_has_raw_memcard && File::Exists(Config::Get(Config::MAIN_MEMCARD_B_PATH))) && !(slot_b_has_raw_memcard && File::Exists(Config::Get(Config::MAIN_MEMCARD_B_PATH))) &&
!(slot_a_has_gci_folder && gci_folder_has_saves(0)) && !(slot_a_has_gci_folder && gci_folder_has_saves(ExpansionInterface::Slot::A)) &&
!(slot_b_has_gci_folder && gci_folder_has_saves(1)); !(slot_b_has_gci_folder && gci_folder_has_saves(ExpansionInterface::Slot::B));
} }
s_memcards |= (slot_a_has_raw_memcard || slot_a_has_gci_folder) << 0; s_memcards |= (slot_a_has_raw_memcard || slot_a_has_gci_folder) << 0;
s_memcards |= (slot_b_has_raw_memcard || slot_b_has_gci_folder) << 1; s_memcards |= (slot_b_has_raw_memcard || slot_b_has_gci_folder) << 1;

View File

@ -17,6 +17,11 @@ struct BootParameters;
struct GCPadStatus; struct GCPadStatus;
class PointerWrap; class PointerWrap;
namespace ExpansionInterface
{
enum class Slot : int;
}
namespace WiimoteCommon namespace WiimoteCommon
{ {
class DataReportBuilder; class DataReportBuilder;
@ -166,7 +171,7 @@ void SetReset(bool reset);
bool IsConfigSaved(); bool IsConfigSaved();
bool IsStartingFromClearSave(); bool IsStartingFromClearSave();
bool IsUsingMemcard(int memcard); bool IsUsingMemcard(ExpansionInterface::Slot slot);
void SetGraphicsConfig(); void SetGraphicsConfig();
bool IsNetPlayRecording(); bool IsNetPlayRecording();

View File

@ -39,6 +39,7 @@
#include "Core/Config/SessionSettings.h" #include "Core/Config/SessionSettings.h"
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
#include "Core/GeckoCode.h" #include "Core/GeckoCode.h"
#include "Core/HW/EXI/EXI.h"
#include "Core/HW/EXI/EXI_DeviceIPL.h" #include "Core/HW/EXI/EXI_DeviceIPL.h"
#ifdef HAS_LIBMGBA #ifdef HAS_LIBMGBA
#include "Core/HW/GBACore.h" #include "Core/HW/GBACore.h"
@ -818,8 +819,8 @@ void NetPlayClient::OnStartGame(sf::Packet& packet)
packet >> m_net_settings.m_OCEnable; packet >> m_net_settings.m_OCEnable;
packet >> m_net_settings.m_OCFactor; packet >> m_net_settings.m_OCFactor;
for (auto& device : m_net_settings.m_EXIDevice) for (auto slot : ExpansionInterface::SLOTS)
packet >> device; packet >> m_net_settings.m_EXIDevice[slot];
for (u32& value : m_net_settings.m_SYSCONFSettings) for (u32& value : m_net_settings.m_SYSCONFSettings)
packet >> value; packet >> value;

View File

@ -8,7 +8,9 @@
#include <vector> #include <vector>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/EnumMap.h"
#include "Core/Config/SYSCONFSettings.h" #include "Core/Config/SYSCONFSettings.h"
#include "Core/HW/EXI/EXI.h"
#include "Core/HW/EXI/EXI_Device.h" #include "Core/HW/EXI/EXI_Device.h"
namespace DiscIO namespace DiscIO
@ -44,7 +46,7 @@ struct NetSettings
bool m_CopyWiiSave = false; bool m_CopyWiiSave = false;
bool m_OCEnable = false; bool m_OCEnable = false;
float m_OCFactor = 0; float m_OCFactor = 0;
std::array<ExpansionInterface::TEXIDevices, 3> m_EXIDevice{}; Common::EnumMap<ExpansionInterface::EXIDeviceType, ExpansionInterface::MAX_SLOT> m_EXIDevice{};
std::array<u32, Config::SYSCONF_SETTINGS.size()> m_SYSCONFSettings{}; std::array<u32, Config::SYSCONF_SETTINGS.size()> m_SYSCONFSettings{};

View File

@ -40,6 +40,8 @@
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
#include "Core/GeckoCode.h" #include "Core/GeckoCode.h"
#include "Core/GeckoCodeConfig.h" #include "Core/GeckoCodeConfig.h"
#include "Core/HW/EXI/EXI.h"
#include "Core/HW/EXI/EXI_Device.h"
#ifdef HAS_LIBMGBA #ifdef HAS_LIBMGBA
#include "Core/HW/GBACore.h" #include "Core/HW/GBACore.h"
#endif #endif
@ -1306,10 +1308,21 @@ bool NetPlayServer::SetupNetSettings()
settings.m_CopyWiiSave = Config::Get(Config::NETPLAY_LOAD_WII_SAVE); settings.m_CopyWiiSave = Config::Get(Config::NETPLAY_LOAD_WII_SAVE);
settings.m_OCEnable = Config::Get(Config::MAIN_OVERCLOCK_ENABLE); settings.m_OCEnable = Config::Get(Config::MAIN_OVERCLOCK_ENABLE);
settings.m_OCFactor = Config::Get(Config::MAIN_OVERCLOCK); settings.m_OCFactor = Config::Get(Config::MAIN_OVERCLOCK);
settings.m_EXIDevice[0] = Config::Get(Config::MAIN_SLOT_A);
settings.m_EXIDevice[1] = Config::Get(Config::MAIN_SLOT_B); for (ExpansionInterface::Slot slot : ExpansionInterface::SLOTS)
// There's no way the BBA is going to sync, disable it {
settings.m_EXIDevice[2] = ExpansionInterface::EXIDEVICE_NONE; ExpansionInterface::EXIDeviceType device;
if (slot == ExpansionInterface::Slot::SP1)
{
// There's no way the BBA is going to sync, disable it
device = ExpansionInterface::EXIDeviceType::None;
}
else
{
device = Config::Get(Config::GetInfoForEXIDevice(slot));
}
settings.m_EXIDevice[slot] = device;
}
for (size_t i = 0; i < Config::SYSCONF_SETTINGS.size(); ++i) for (size_t i = 0; i < Config::SYSCONF_SETTINGS.size(); ++i)
{ {
@ -1494,8 +1507,8 @@ bool NetPlayServer::StartGame()
spac << m_settings.m_OCEnable; spac << m_settings.m_OCEnable;
spac << m_settings.m_OCFactor; spac << m_settings.m_OCFactor;
for (auto& device : m_settings.m_EXIDevice) for (auto slot : ExpansionInterface::SLOTS)
spac << device; spac << static_cast<int>(m_settings.m_EXIDevice[slot]);
for (u32 value : m_settings.m_SYSCONFSettings) for (u32 value : m_settings.m_SYSCONFSettings)
spac << value; spac << value;
@ -1592,12 +1605,11 @@ bool NetPlayServer::SyncSaveData()
u8 save_count = 0; u8 save_count = 0;
constexpr int exi_device_count = 2; for (ExpansionInterface::Slot slot : ExpansionInterface::MEMCARD_SLOTS)
for (int i = 0; i < exi_device_count; ++i)
{ {
if (m_settings.m_EXIDevice[i] == ExpansionInterface::EXIDEVICE_MEMORYCARD || if (m_settings.m_EXIDevice[slot] == ExpansionInterface::EXIDeviceType::MemoryCard ||
Config::Get(Config::GetInfoForEXIDevice(i)) == Config::Get(Config::GetInfoForEXIDevice(slot)) ==
ExpansionInterface::EXIDEVICE_MEMORYCARDFOLDER) ExpansionInterface::EXIDeviceType::MemoryCardFolder)
{ {
save_count++; save_count++;
} }
@ -1652,16 +1664,15 @@ bool NetPlayServer::SyncSaveData()
const std::string region = const std::string region =
SConfig::GetDirectoryForRegion(SConfig::ToGameCubeRegion(game->GetRegion())); SConfig::GetDirectoryForRegion(SConfig::ToGameCubeRegion(game->GetRegion()));
for (int i = 0; i < exi_device_count; ++i) for (ExpansionInterface::Slot slot : ExpansionInterface::MEMCARD_SLOTS)
{ {
const bool is_slot_a = i == 0; const bool is_slot_a = slot == ExpansionInterface::Slot::A;
if (m_settings.m_EXIDevice[i] == ExpansionInterface::EXIDEVICE_MEMORYCARD) if (m_settings.m_EXIDevice[slot] == ExpansionInterface::EXIDeviceType::MemoryCard)
{ {
std::string path = is_slot_a ? Config::Get(Config::MAIN_MEMCARD_A_PATH) : std::string path = Config::Get(Config::GetInfoForMemcardPath(slot));
Config::Get(Config::MAIN_MEMCARD_B_PATH);
MemoryCard::CheckPath(path, region, is_slot_a); MemoryCard::CheckPath(path, region, slot);
int size_override; int size_override;
IniFile gameIni = SConfig::LoadGameIni(game->GetGameID(), game->GetRevision()); IniFile gameIni = SConfig::LoadGameIni(game->GetGameID(), game->GetRevision());
@ -1693,8 +1704,8 @@ bool NetPlayServer::SyncSaveData()
SendChunkedToClients(std::move(pac), 1, SendChunkedToClients(std::move(pac), 1,
fmt::format("Memory Card {} Synchronization", is_slot_a ? 'A' : 'B')); fmt::format("Memory Card {} Synchronization", is_slot_a ? 'A' : 'B'));
} }
else if (Config::Get(Config::GetInfoForEXIDevice(i)) == else if (Config::Get(Config::GetInfoForEXIDevice(slot)) ==
ExpansionInterface::EXIDEVICE_MEMORYCARDFOLDER) ExpansionInterface::EXIDeviceType::MemoryCardFolder)
{ {
const std::string path = File::GetUserPath(D_GCUSER_IDX) + region + DIR_SEP + const std::string path = File::GetUserPath(D_GCUSER_IDX) + region + DIR_SEP +
fmt::format("Card {}", is_slot_a ? 'A' : 'B'); fmt::format("Card {}", is_slot_a ? 'A' : 'B');

View File

@ -42,6 +42,8 @@
#include "DolphinQt/QtUtils/DolphinFileDialog.h" #include "DolphinQt/QtUtils/DolphinFileDialog.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h" #include "DolphinQt/QtUtils/ModalMessageBox.h"
using namespace ExpansionInterface;
constexpr int ROW_HEIGHT = 36; constexpr int ROW_HEIGHT = 36;
constexpr int COLUMN_WIDTH_FILENAME = 100; constexpr int COLUMN_WIDTH_FILENAME = 100;
constexpr int COLUMN_WIDTH_BANNER = Memcard::MEMORY_CARD_BANNER_WIDTH + 6; constexpr int COLUMN_WIDTH_BANNER = Memcard::MEMORY_CARD_BANNER_WIDTH + 6;
@ -55,6 +57,14 @@ constexpr int COLUMN_INDEX_ICON = 3;
constexpr int COLUMN_INDEX_BLOCKS = 4; constexpr int COLUMN_INDEX_BLOCKS = 4;
constexpr int COLUMN_COUNT = 5; constexpr int COLUMN_COUNT = 5;
namespace
{
Slot OtherSlot(Slot slot)
{
return slot == Slot::A ? Slot::B : Slot::A;
}
}; // namespace
struct GCMemcardManager::IconAnimationData struct GCMemcardManager::IconAnimationData
{ {
// the individual frames // the individual frames
@ -70,7 +80,7 @@ GCMemcardManager::GCMemcardManager(QWidget* parent) : QDialog(parent)
CreateWidgets(); CreateWidgets();
ConnectWidgets(); ConnectWidgets();
SetActiveSlot(0); SetActiveSlot(Slot::A);
UpdateActions(); UpdateActions();
m_timer = new QTimer(this); m_timer = new QTimer(this);
@ -117,52 +127,54 @@ void GCMemcardManager::CreateWidgets()
auto* layout = new QGridLayout; auto* layout = new QGridLayout;
for (int i = 0; i < SLOT_COUNT; i++) for (Slot slot : MEMCARD_SLOTS)
{ {
m_slot_group[i] = new QGroupBox(i == 0 ? tr("Slot A") : tr("Slot B")); m_slot_group[slot] = new QGroupBox(slot == Slot::A ? tr("Slot A") : tr("Slot B"));
m_slot_file_edit[i] = new QLineEdit; m_slot_file_edit[slot] = new QLineEdit;
m_slot_open_button[i] = new QPushButton(tr("&Open...")); m_slot_open_button[slot] = new QPushButton(tr("&Open..."));
m_slot_create_button[i] = new QPushButton(tr("&Create...")); m_slot_create_button[slot] = new QPushButton(tr("&Create..."));
m_slot_table[i] = new QTableWidget; m_slot_table[slot] = new QTableWidget;
m_slot_table[i]->setTabKeyNavigation(false); m_slot_table[slot]->setTabKeyNavigation(false);
m_slot_stat_label[i] = new QLabel; m_slot_stat_label[slot] = new QLabel;
m_slot_table[i]->setSelectionMode(QAbstractItemView::ExtendedSelection); m_slot_table[slot]->setSelectionMode(QAbstractItemView::ExtendedSelection);
m_slot_table[i]->setSelectionBehavior(QAbstractItemView::SelectRows); m_slot_table[slot]->setSelectionBehavior(QAbstractItemView::SelectRows);
m_slot_table[i]->setSortingEnabled(true); m_slot_table[slot]->setSortingEnabled(true);
m_slot_table[i]->horizontalHeader()->setHighlightSections(false); m_slot_table[slot]->horizontalHeader()->setHighlightSections(false);
m_slot_table[i]->horizontalHeader()->setMinimumSectionSize(0); m_slot_table[slot]->horizontalHeader()->setMinimumSectionSize(0);
m_slot_table[i]->horizontalHeader()->setSortIndicatorShown(true); m_slot_table[slot]->horizontalHeader()->setSortIndicatorShown(true);
m_slot_table[i]->setColumnCount(COLUMN_COUNT); m_slot_table[slot]->setColumnCount(COLUMN_COUNT);
m_slot_table[i]->setHorizontalHeaderItem(COLUMN_INDEX_FILENAME, m_slot_table[slot]->setHorizontalHeaderItem(COLUMN_INDEX_FILENAME,
new QTableWidgetItem(tr("Filename"))); new QTableWidgetItem(tr("Filename")));
m_slot_table[i]->setHorizontalHeaderItem(COLUMN_INDEX_BANNER, m_slot_table[slot]->setHorizontalHeaderItem(COLUMN_INDEX_BANNER,
new QTableWidgetItem(tr("Banner"))); new QTableWidgetItem(tr("Banner")));
m_slot_table[i]->setHorizontalHeaderItem(COLUMN_INDEX_TEXT, new QTableWidgetItem(tr("Title"))); m_slot_table[slot]->setHorizontalHeaderItem(COLUMN_INDEX_TEXT,
m_slot_table[i]->setHorizontalHeaderItem(COLUMN_INDEX_ICON, new QTableWidgetItem(tr("Icon"))); new QTableWidgetItem(tr("Title")));
m_slot_table[i]->setHorizontalHeaderItem(COLUMN_INDEX_BLOCKS, m_slot_table[slot]->setHorizontalHeaderItem(COLUMN_INDEX_ICON,
new QTableWidgetItem(tr("Blocks"))); new QTableWidgetItem(tr("Icon")));
m_slot_table[i]->setColumnWidth(COLUMN_INDEX_FILENAME, COLUMN_WIDTH_FILENAME); m_slot_table[slot]->setHorizontalHeaderItem(COLUMN_INDEX_BLOCKS,
m_slot_table[i]->setColumnWidth(COLUMN_INDEX_BANNER, COLUMN_WIDTH_BANNER); new QTableWidgetItem(tr("Blocks")));
m_slot_table[i]->setColumnWidth(COLUMN_INDEX_TEXT, COLUMN_WIDTH_TEXT); m_slot_table[slot]->setColumnWidth(COLUMN_INDEX_FILENAME, COLUMN_WIDTH_FILENAME);
m_slot_table[i]->setColumnWidth(COLUMN_INDEX_ICON, COLUMN_WIDTH_ICON); m_slot_table[slot]->setColumnWidth(COLUMN_INDEX_BANNER, COLUMN_WIDTH_BANNER);
m_slot_table[i]->setColumnWidth(COLUMN_INDEX_BLOCKS, COLUMN_WIDTH_BLOCKS); m_slot_table[slot]->setColumnWidth(COLUMN_INDEX_TEXT, COLUMN_WIDTH_TEXT);
m_slot_table[i]->verticalHeader()->setDefaultSectionSize(ROW_HEIGHT); m_slot_table[slot]->setColumnWidth(COLUMN_INDEX_ICON, COLUMN_WIDTH_ICON);
m_slot_table[i]->verticalHeader()->hide(); m_slot_table[slot]->setColumnWidth(COLUMN_INDEX_BLOCKS, COLUMN_WIDTH_BLOCKS);
m_slot_table[i]->setShowGrid(false); m_slot_table[slot]->verticalHeader()->setDefaultSectionSize(ROW_HEIGHT);
m_slot_table[slot]->verticalHeader()->hide();
m_slot_table[slot]->setShowGrid(false);
auto* slot_layout = new QGridLayout; auto* slot_layout = new QGridLayout;
m_slot_group[i]->setLayout(slot_layout); m_slot_group[slot]->setLayout(slot_layout);
slot_layout->addWidget(m_slot_file_edit[i], 0, 0); slot_layout->addWidget(m_slot_file_edit[slot], 0, 0);
slot_layout->addWidget(m_slot_open_button[i], 0, 1); slot_layout->addWidget(m_slot_open_button[slot], 0, 1);
slot_layout->addWidget(m_slot_create_button[i], 0, 2); slot_layout->addWidget(m_slot_create_button[slot], 0, 2);
slot_layout->addWidget(m_slot_table[i], 1, 0, 1, 3); slot_layout->addWidget(m_slot_table[slot], 1, 0, 1, 3);
slot_layout->addWidget(m_slot_stat_label[i], 2, 0); slot_layout->addWidget(m_slot_stat_label[slot], 2, 0);
layout->addWidget(m_slot_group[i], 0, i * 2, 8, 1); layout->addWidget(m_slot_group[slot], 0, slot == Slot::A ? 0 : 2, 8, 1);
UpdateSlotTable(i); UpdateSlotTable(slot);
} }
layout->addWidget(m_select_button, 1, 1); layout->addWidget(m_select_button, 1, 1);
@ -179,7 +191,8 @@ void GCMemcardManager::CreateWidgets()
void GCMemcardManager::ConnectWidgets() void GCMemcardManager::ConnectWidgets()
{ {
connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(m_select_button, &QPushButton::clicked, [this] { SetActiveSlot(!m_active_slot); }); connect(m_select_button, &QPushButton::clicked,
[this] { SetActiveSlot(OtherSlot(m_active_slot)); });
connect(m_export_gci_action, &QAction::triggered, connect(m_export_gci_action, &QAction::triggered,
[this] { ExportFiles(Memcard::SavefileFormat::GCI); }); [this] { ExportFiles(Memcard::SavefileFormat::GCI); });
connect(m_export_gcs_action, &QAction::triggered, connect(m_export_gcs_action, &QAction::triggered,
@ -191,7 +204,7 @@ void GCMemcardManager::ConnectWidgets()
connect(m_copy_button, &QPushButton::clicked, this, &GCMemcardManager::CopyFiles); connect(m_copy_button, &QPushButton::clicked, this, &GCMemcardManager::CopyFiles);
connect(m_fix_checksums_button, &QPushButton::clicked, this, &GCMemcardManager::FixChecksums); connect(m_fix_checksums_button, &QPushButton::clicked, this, &GCMemcardManager::FixChecksums);
for (int slot = 0; slot < SLOT_COUNT; slot++) for (Slot slot : MEMCARD_SLOTS)
{ {
connect(m_slot_file_edit[slot], &QLineEdit::textChanged, connect(m_slot_file_edit[slot], &QLineEdit::textChanged,
[this, slot](const QString& path) { SetSlotFile(slot, path); }); [this, slot](const QString& path) { SetSlotFile(slot, path); });
@ -206,27 +219,26 @@ void GCMemcardManager::ConnectWidgets()
void GCMemcardManager::LoadDefaultMemcards() void GCMemcardManager::LoadDefaultMemcards()
{ {
for (int i = 0; i < SLOT_COUNT; i++) for (ExpansionInterface::Slot slot : ExpansionInterface::MEMCARD_SLOTS)
{ {
if (Config::Get(i == 0 ? Config::MAIN_SLOT_A : Config::MAIN_SLOT_B) != if (Config::Get(Config::GetInfoForEXIDevice(slot)) !=
ExpansionInterface::EXIDEVICE_MEMORYCARD) ExpansionInterface::EXIDeviceType::MemoryCard)
{ {
continue; continue;
} }
const QString path = QString::fromStdString( const QString path = QString::fromStdString(Config::Get(Config::GetInfoForMemcardPath(slot)));
Config::Get(i == 0 ? Config::MAIN_MEMCARD_A_PATH : Config::MAIN_MEMCARD_B_PATH)); SetSlotFile(slot, path);
SetSlotFile(i, path);
} }
} }
void GCMemcardManager::SetActiveSlot(int slot) void GCMemcardManager::SetActiveSlot(Slot slot)
{ {
for (int i = 0; i < SLOT_COUNT; i++) for (Slot slot2 : MEMCARD_SLOTS)
m_slot_table[i]->setEnabled(i == slot); m_slot_table[slot2]->setEnabled(slot2 == slot);
m_select_button->setText(slot == 0 ? tr("Switch to B") : tr("Switch to A")); m_select_button->setText(slot == Slot::A ? tr("Switch to B") : tr("Switch to A"));
m_copy_button->setText(slot == 0 ? tr("Copy to B") : tr("Copy to A")); m_copy_button->setText(slot == Slot::A ? tr("Copy to B") : tr("Copy to A"));
m_active_slot = slot; m_active_slot = slot;
@ -234,7 +246,7 @@ void GCMemcardManager::SetActiveSlot(int slot)
UpdateActions(); UpdateActions();
} }
void GCMemcardManager::UpdateSlotTable(int slot) void GCMemcardManager::UpdateSlotTable(Slot slot)
{ {
m_slot_active_icons[slot].clear(); m_slot_active_icons[slot].clear();
@ -307,7 +319,7 @@ void GCMemcardManager::UpdateActions()
auto selection = m_slot_table[m_active_slot]->selectedItems(); auto selection = m_slot_table[m_active_slot]->selectedItems();
bool have_selection = selection.count(); bool have_selection = selection.count();
bool have_memcard = m_slot_memcard[m_active_slot] != nullptr; bool have_memcard = m_slot_memcard[m_active_slot] != nullptr;
bool have_memcard_other = m_slot_memcard[!m_active_slot] != nullptr; bool have_memcard_other = m_slot_memcard[OtherSlot(m_active_slot)] != nullptr;
m_copy_button->setEnabled(have_selection && have_memcard_other); m_copy_button->setEnabled(have_selection && have_memcard_other);
m_export_button->setEnabled(have_selection); m_export_button->setEnabled(have_selection);
@ -316,7 +328,7 @@ void GCMemcardManager::UpdateActions()
m_fix_checksums_button->setEnabled(have_memcard); m_fix_checksums_button->setEnabled(have_memcard);
} }
void GCMemcardManager::SetSlotFile(int slot, QString path) void GCMemcardManager::SetSlotFile(Slot slot, QString path)
{ {
auto [error_code, memcard] = Memcard::GCMemcard::Open(path.toStdString()); auto [error_code, memcard] = Memcard::GCMemcard::Open(path.toStdString());
@ -337,14 +349,15 @@ void GCMemcardManager::SetSlotFile(int slot, QString path)
UpdateActions(); UpdateActions();
} }
void GCMemcardManager::SetSlotFileInteractive(int slot) void GCMemcardManager::SetSlotFileInteractive(Slot slot)
{ {
QString path = QDir::toNativeSeparators(DolphinFileDialog::getOpenFileName( QString path = QDir::toNativeSeparators(
this, DolphinFileDialog::getOpenFileName(this,
slot == 0 ? tr("Set memory card file for Slot A") : tr("Set memory card file for Slot B"), slot == Slot::A ? tr("Set memory card file for Slot A") :
QString::fromStdString(File::GetUserPath(D_GCUSER_IDX)), tr("Set memory card file for Slot B"),
QStringLiteral("%1 (*.raw *.gcp);;%2 (*)") QString::fromStdString(File::GetUserPath(D_GCUSER_IDX)),
.arg(tr("GameCube Memory Cards"), tr("All Files")))); QStringLiteral("%1 (*.raw *.gcp);;%2 (*)")
.arg(tr("GameCube Memory Cards"), tr("All Files"))));
if (!path.isEmpty()) if (!path.isEmpty())
m_slot_file_edit[slot]->setText(path); m_slot_file_edit[slot]->setText(path);
} }
@ -487,7 +500,7 @@ void GCMemcardManager::ExportFiles(Memcard::SavefileFormat format)
} }
} }
void GCMemcardManager::ImportFiles(int slot, const std::vector<Memcard::Savefile>& savefiles) void GCMemcardManager::ImportFiles(Slot slot, const std::vector<Memcard::Savefile>& savefiles)
{ {
auto& card = m_slot_memcard[slot]; auto& card = m_slot_memcard[slot];
if (!card) if (!card)
@ -611,7 +624,7 @@ void GCMemcardManager::CopyFiles()
if (!source_card) if (!source_card)
return; return;
auto& target_card = m_slot_memcard[!m_active_slot]; auto& target_card = m_slot_memcard[OtherSlot(m_active_slot)];
if (!target_card) if (!target_card)
return; return;
@ -627,7 +640,7 @@ void GCMemcardManager::CopyFiles()
return; return;
} }
ImportFiles(!m_active_slot, savefiles); ImportFiles(OtherSlot(m_active_slot), savefiles);
} }
void GCMemcardManager::DeleteFiles() void GCMemcardManager::DeleteFiles()
@ -677,7 +690,7 @@ void GCMemcardManager::FixChecksums()
} }
} }
void GCMemcardManager::CreateNewCard(int slot) void GCMemcardManager::CreateNewCard(Slot slot)
{ {
GCMemcardCreateNewDialog dialog(this); GCMemcardCreateNewDialog dialog(this);
if (dialog.exec() == QDialog::Accepted) if (dialog.exec() == QDialog::Accepted)
@ -687,7 +700,7 @@ void GCMemcardManager::CreateNewCard(int slot)
void GCMemcardManager::DrawIcons() void GCMemcardManager::DrawIcons()
{ {
const int column = COLUMN_INDEX_ICON; const int column = COLUMN_INDEX_ICON;
for (int slot = 0; slot < SLOT_COUNT; slot++) for (Slot slot : MEMCARD_SLOTS)
{ {
QTableWidget* table = m_slot_table[slot]; QTableWidget* table = m_slot_table[slot];
const int row_count = table->rowCount(); const int row_count = table->rowCount();
@ -737,7 +750,7 @@ void GCMemcardManager::DrawIcons()
++m_current_frame; ++m_current_frame;
} }
QPixmap GCMemcardManager::GetBannerFromSaveFile(int file_index, int slot) QPixmap GCMemcardManager::GetBannerFromSaveFile(int file_index, Slot slot)
{ {
auto& memcard = m_slot_memcard[slot]; auto& memcard = m_slot_memcard[slot];
@ -753,7 +766,7 @@ QPixmap GCMemcardManager::GetBannerFromSaveFile(int file_index, int slot)
return QPixmap::fromImage(image); return QPixmap::fromImage(image);
} }
GCMemcardManager::IconAnimationData GCMemcardManager::GetIconFromSaveFile(int file_index, int slot) GCMemcardManager::IconAnimationData GCMemcardManager::GetIconFromSaveFile(int file_index, Slot slot)
{ {
auto& memcard = m_slot_memcard[slot]; auto& memcard = m_slot_memcard[slot];

View File

@ -12,6 +12,8 @@
#include <QDialog> #include <QDialog>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/EnumMap.h"
#include "Core/HW/EXI/EXI.h"
namespace Memcard namespace Memcard
{ {
@ -53,26 +55,26 @@ private:
void LoadDefaultMemcards(); void LoadDefaultMemcards();
void UpdateActions(); void UpdateActions();
void UpdateSlotTable(int slot); void UpdateSlotTable(ExpansionInterface::Slot slot);
void SetSlotFile(int slot, QString path); void SetSlotFile(ExpansionInterface::Slot slot, QString path);
void SetSlotFileInteractive(int slot); void SetSlotFileInteractive(ExpansionInterface::Slot slot);
void SetActiveSlot(int slot); void SetActiveSlot(ExpansionInterface::Slot slot);
std::vector<u8> GetSelectedFileIndices(); std::vector<u8> GetSelectedFileIndices();
void ImportFiles(int slot, const std::vector<Memcard::Savefile>& savefiles); void ImportFiles(ExpansionInterface::Slot slot, const std::vector<Memcard::Savefile>& savefiles);
void CopyFiles(); void CopyFiles();
void ImportFile(); void ImportFile();
void DeleteFiles(); void DeleteFiles();
void ExportFiles(Memcard::SavefileFormat format); void ExportFiles(Memcard::SavefileFormat format);
void FixChecksums(); void FixChecksums();
void CreateNewCard(int slot); void CreateNewCard(ExpansionInterface::Slot slot);
void DrawIcons(); void DrawIcons();
QPixmap GetBannerFromSaveFile(int file_index, int slot); QPixmap GetBannerFromSaveFile(int file_index, ExpansionInterface::Slot slot);
IconAnimationData GetIconFromSaveFile(int file_index, int slot); IconAnimationData GetIconFromSaveFile(int file_index, ExpansionInterface::Slot slot);
// Actions // Actions
QPushButton* m_select_button; QPushButton* m_select_button;
@ -87,17 +89,18 @@ private:
QPushButton* m_fix_checksums_button; QPushButton* m_fix_checksums_button;
// Slots // Slots
static constexpr int SLOT_COUNT = 2; Common::EnumMap<std::map<u8, IconAnimationData>, ExpansionInterface::MAX_MEMCARD_SLOT>
std::array<std::map<u8, IconAnimationData>, SLOT_COUNT> m_slot_active_icons; m_slot_active_icons;
std::array<std::unique_ptr<Memcard::GCMemcard>, SLOT_COUNT> m_slot_memcard; Common::EnumMap<std::unique_ptr<Memcard::GCMemcard>, ExpansionInterface::MAX_MEMCARD_SLOT>
std::array<QGroupBox*, SLOT_COUNT> m_slot_group; m_slot_memcard;
std::array<QLineEdit*, SLOT_COUNT> m_slot_file_edit; Common::EnumMap<QGroupBox*, ExpansionInterface::MAX_MEMCARD_SLOT> m_slot_group;
std::array<QPushButton*, SLOT_COUNT> m_slot_open_button; Common::EnumMap<QLineEdit*, ExpansionInterface::MAX_MEMCARD_SLOT> m_slot_file_edit;
std::array<QPushButton*, SLOT_COUNT> m_slot_create_button; Common::EnumMap<QPushButton*, ExpansionInterface::MAX_MEMCARD_SLOT> m_slot_open_button;
std::array<QTableWidget*, SLOT_COUNT> m_slot_table; Common::EnumMap<QPushButton*, ExpansionInterface::MAX_MEMCARD_SLOT> m_slot_create_button;
std::array<QLabel*, SLOT_COUNT> m_slot_stat_label; Common::EnumMap<QTableWidget*, ExpansionInterface::MAX_MEMCARD_SLOT> m_slot_table;
Common::EnumMap<QLabel*, ExpansionInterface::MAX_MEMCARD_SLOT> m_slot_stat_label;
int m_active_slot; ExpansionInterface::Slot m_active_slot;
u64 m_current_frame = 0; u64 m_current_frame = 0;
QDialogButtonBox* m_button_box; QDialogButtonBox* m_button_box;

View File

@ -47,6 +47,7 @@
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
#include "Core/Core.h" #include "Core/Core.h"
#include "Core/HW/DVD/DVDInterface.h" #include "Core/HW/DVD/DVDInterface.h"
#include "Core/HW/EXI/EXI.h"
#include "Core/HW/EXI/EXI_Device.h" #include "Core/HW/EXI/EXI_Device.h"
#include "Core/HW/WiiSave.h" #include "Core/HW/WiiSave.h"
#include "Core/WiiUtils.h" #include "Core/WiiUtils.h"
@ -661,21 +662,22 @@ void GameList::OpenGCSaveFolder()
bool found = false; bool found = false;
for (int i = 0; i < 2; i++) using ExpansionInterface::Slot;
for (Slot slot : ExpansionInterface::MEMCARD_SLOTS)
{ {
QUrl url; QUrl url;
const ExpansionInterface::TEXIDevices current_exi_device = const ExpansionInterface::EXIDeviceType current_exi_device =
Config::Get(Config::GetInfoForEXIDevice(i)); Config::Get(Config::GetInfoForEXIDevice(slot));
switch (current_exi_device) switch (current_exi_device)
{ {
case ExpansionInterface::EXIDEVICE_MEMORYCARDFOLDER: case ExpansionInterface::EXIDeviceType::MemoryCardFolder:
{ {
std::string path = StringFromFormat("%s/%s/%s", File::GetUserPath(D_GCUSER_IDX).c_str(), std::string path = StringFromFormat("%s/%s/%s", File::GetUserPath(D_GCUSER_IDX).c_str(),
SConfig::GetDirectoryForRegion(game->GetRegion()), SConfig::GetDirectoryForRegion(game->GetRegion()),
i == 0 ? "Card A" : "Card B"); slot == Slot::A ? "Card A" : "Card B");
std::string override_path = i == 0 ? Config::Get(Config::MAIN_GCI_FOLDER_A_PATH_OVERRIDE) : std::string override_path = Config::Get(Config::GetInfoForGCIPathOverride(slot));
Config::Get(Config::MAIN_GCI_FOLDER_B_PATH_OVERRIDE);
if (!override_path.empty()) if (!override_path.empty())
path = override_path; path = override_path;
@ -691,10 +693,9 @@ void GameList::OpenGCSaveFolder()
} }
break; break;
} }
case ExpansionInterface::EXIDEVICE_MEMORYCARD: case ExpansionInterface::EXIDeviceType::MemoryCard:
{ {
std::string memcard_path = i == 0 ? Config::Get(Config::MAIN_MEMCARD_A_PATH) : std::string memcard_path = Config::Get(Config::GetInfoForMemcardPath(slot));
Config::Get(Config::MAIN_MEMCARD_B_PATH);
std::string memcard_dir; std::string memcard_dir;

View File

@ -16,8 +16,10 @@
#include <QPushButton> #include <QPushButton>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <array>
#include <utility> #include <utility>
#include "Common/Assert.h"
#include "Common/CommonPaths.h" #include "Common/CommonPaths.h"
#include "Common/Config/Config.h" #include "Common/Config/Config.h"
#include "Common/FileUtil.h" #include "Common/FileUtil.h"
@ -54,6 +56,8 @@ GameCubePane::GameCubePane()
void GameCubePane::CreateWidgets() void GameCubePane::CreateWidgets()
{ {
using ExpansionInterface::EXIDeviceType;
QVBoxLayout* layout = new QVBoxLayout(this); QVBoxLayout* layout = new QVBoxLayout(this);
// IPL Settings // IPL Settings
@ -86,52 +90,49 @@ void GameCubePane::CreateWidgets()
QGridLayout* device_layout = new QGridLayout(device_box); QGridLayout* device_layout = new QGridLayout(device_box);
device_box->setLayout(device_layout); device_box->setLayout(device_layout);
for (int i = 0; i < SLOT_COUNT; i++) for (ExpansionInterface::Slot slot : ExpansionInterface::SLOTS)
{ {
m_slot_combos[i] = new QComboBox(device_box); m_slot_combos[slot] = new QComboBox(device_box);
m_slot_combos[i]->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); m_slot_combos[slot]->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
m_slot_buttons[i] = new QPushButton(tr("..."), device_box); m_slot_buttons[slot] = new QPushButton(tr("..."), device_box);
m_slot_buttons[i]->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_slot_buttons[slot]->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
} }
// Add slot devices // Add slot devices
for (const auto& entry : for (const auto device : {EXIDeviceType::None, EXIDeviceType::Dummy, EXIDeviceType::MemoryCard,
{std::make_pair(tr("<Nothing>"), ExpansionInterface::EXIDEVICE_NONE), EXIDeviceType::MemoryCardFolder, EXIDeviceType::Gecko,
std::make_pair(tr("Dummy"), ExpansionInterface::EXIDEVICE_DUMMY), EXIDeviceType::AGP, EXIDeviceType::Microphone})
std::make_pair(tr("Memory Card"), ExpansionInterface::EXIDEVICE_MEMORYCARD),
std::make_pair(tr("GCI Folder"), ExpansionInterface::EXIDEVICE_MEMORYCARDFOLDER),
std::make_pair(tr("USB Gecko"), ExpansionInterface::EXIDEVICE_GECKO),
std::make_pair(tr("Advance Game Port"), ExpansionInterface::EXIDEVICE_AGP),
std::make_pair(tr("Microphone"), ExpansionInterface::EXIDEVICE_MIC)})
{ {
m_slot_combos[0]->addItem(entry.first, entry.second); const QString name = tr(fmt::format("{:n}", device).c_str());
m_slot_combos[1]->addItem(entry.first, entry.second); const int value = static_cast<int>(device);
m_slot_combos[ExpansionInterface::Slot::A]->addItem(name, value);
m_slot_combos[ExpansionInterface::Slot::B]->addItem(name, value);
} }
// Add SP1 devices // Add SP1 devices
std::vector<std::pair<QString, ExpansionInterface::TEXIDevices>> sp1Entries{ for (const auto device : {
std::make_pair(tr("<Nothing>"), ExpansionInterface::EXIDEVICE_NONE), EXIDeviceType::None,
std::make_pair(tr("Dummy"), ExpansionInterface::EXIDEVICE_DUMMY), EXIDeviceType::Dummy,
std::make_pair(tr("Broadband Adapter (TAP)"), ExpansionInterface::EXIDEVICE_ETH), EXIDeviceType::Ethernet,
std::make_pair(tr("Broadband Adapter (XLink Kai)"), ExpansionInterface::EXIDEVICE_ETHXLINK)}; EXIDeviceType::EthernetXLink,
#if defined(__APPLE__) #ifdef __APPLE__
sp1Entries.emplace_back(std::make_pair(tr("Broadband Adapter (tapserver)"), EXIDeviceType::EthernetTapServer,
ExpansionInterface::EXIDEVICE_ETHTAPSERVER));
#endif #endif
for (const auto& entry : sp1Entries) })
{ {
m_slot_combos[2]->addItem(entry.first, entry.second); m_slot_combos[ExpansionInterface::Slot::SP1]->addItem(tr(fmt::format("{:n}", device).c_str()),
static_cast<int>(device));
} }
device_layout->addWidget(new QLabel(tr("Slot A:")), 0, 0); device_layout->addWidget(new QLabel(tr("Slot A:")), 0, 0);
device_layout->addWidget(m_slot_combos[0], 0, 1); device_layout->addWidget(m_slot_combos[ExpansionInterface::Slot::A], 0, 1);
device_layout->addWidget(m_slot_buttons[0], 0, 2); device_layout->addWidget(m_slot_buttons[ExpansionInterface::Slot::A], 0, 2);
device_layout->addWidget(new QLabel(tr("Slot B:")), 1, 0); device_layout->addWidget(new QLabel(tr("Slot B:")), 1, 0);
device_layout->addWidget(m_slot_combos[1], 1, 1); device_layout->addWidget(m_slot_combos[ExpansionInterface::Slot::B], 1, 1);
device_layout->addWidget(m_slot_buttons[1], 1, 2); device_layout->addWidget(m_slot_buttons[ExpansionInterface::Slot::B], 1, 2);
device_layout->addWidget(new QLabel(tr("SP1:")), 2, 0); device_layout->addWidget(new QLabel(tr("SP1:")), 2, 0);
device_layout->addWidget(m_slot_combos[2], 2, 1); device_layout->addWidget(m_slot_combos[ExpansionInterface::Slot::SP1], 2, 1);
device_layout->addWidget(m_slot_buttons[2], 2, 2); device_layout->addWidget(m_slot_buttons[ExpansionInterface::Slot::SP1], 2, 2);
#ifdef HAS_LIBMGBA #ifdef HAS_LIBMGBA
// GBA Settings // GBA Settings
@ -192,13 +193,13 @@ void GameCubePane::ConnectWidgets()
&GameCubePane::SaveSettings); &GameCubePane::SaveSettings);
// Device Settings // Device Settings
for (int i = 0; i < SLOT_COUNT; i++) for (ExpansionInterface::Slot slot : ExpansionInterface::SLOTS)
{ {
connect(m_slot_combos[i], qOverload<int>(&QComboBox::currentIndexChanged), this, connect(m_slot_combos[slot], qOverload<int>(&QComboBox::currentIndexChanged), this,
[this, i] { UpdateButton(i); }); [this, slot] { UpdateButton(slot); });
connect(m_slot_combos[i], qOverload<int>(&QComboBox::currentIndexChanged), this, connect(m_slot_combos[slot], qOverload<int>(&QComboBox::currentIndexChanged), this,
&GameCubePane::SaveSettings); &GameCubePane::SaveSettings);
connect(m_slot_buttons[i], &QPushButton::clicked, [this, i] { OnConfigPressed(i); }); connect(m_slot_buttons[slot], &QPushButton::clicked, [this, slot] { OnConfigPressed(slot); });
} }
#ifdef HAS_LIBMGBA #ifdef HAS_LIBMGBA
@ -240,62 +241,69 @@ void GameCubePane::OnEmulationStateChanged()
#endif #endif
} }
void GameCubePane::UpdateButton(int slot) void GameCubePane::UpdateButton(ExpansionInterface::Slot slot)
{ {
const auto value = m_slot_combos[slot]->currentData().toInt(); const auto device =
static_cast<ExpansionInterface::EXIDeviceType>(m_slot_combos[slot]->currentData().toInt());
bool has_config = false; bool has_config = false;
switch (slot) switch (slot)
{ {
case SLOT_A_INDEX: case ExpansionInterface::Slot::A:
case SLOT_B_INDEX: case ExpansionInterface::Slot::B:
has_config = has_config = (device == ExpansionInterface::EXIDeviceType::MemoryCard ||
(value == ExpansionInterface::EXIDEVICE_MEMORYCARD || device == ExpansionInterface::EXIDeviceType::AGP ||
value == ExpansionInterface::EXIDEVICE_AGP || value == ExpansionInterface::EXIDEVICE_MIC); device == ExpansionInterface::EXIDeviceType::Microphone);
break; break;
case SLOT_SP1_INDEX: case ExpansionInterface::Slot::SP1:
has_config = (value == ExpansionInterface::EXIDEVICE_ETH || has_config = (device == ExpansionInterface::EXIDeviceType::Ethernet ||
value == ExpansionInterface::EXIDEVICE_ETHXLINK); device == ExpansionInterface::EXIDeviceType::EthernetXLink);
break; break;
} }
m_slot_buttons[slot]->setEnabled(has_config); m_slot_buttons[slot]->setEnabled(has_config);
} }
void GameCubePane::OnConfigPressed(int slot) void GameCubePane::OnConfigPressed(ExpansionInterface::Slot slot)
{ {
QString filter; const ExpansionInterface::EXIDeviceType device =
bool memcard = false; static_cast<ExpansionInterface::EXIDeviceType>(m_slot_combos[slot]->currentData().toInt());
switch (m_slot_combos[slot]->currentData().toInt()) switch (device)
{ {
case ExpansionInterface::EXIDEVICE_MEMORYCARD: case ExpansionInterface::EXIDeviceType::MemoryCard:
filter = tr("GameCube Memory Cards (*.raw *.gcp)"); BrowseMemcard(slot);
memcard = true;
break;
case ExpansionInterface::EXIDEVICE_AGP:
filter = tr("Game Boy Advance Carts (*.gba)");
break;
case ExpansionInterface::EXIDEVICE_MIC:
MappingWindow(this, MappingWindow::Type::MAPPING_GC_MICROPHONE, slot).exec();
return; return;
case ExpansionInterface::EXIDEVICE_ETH: case ExpansionInterface::EXIDeviceType::AGP:
BrowseAGPRom(slot);
return;
case ExpansionInterface::EXIDeviceType::Microphone:
// TODO: convert MappingWindow to use Slot?
MappingWindow(this, MappingWindow::Type::MAPPING_GC_MICROPHONE, static_cast<int>(slot)).exec();
return;
case ExpansionInterface::EXIDeviceType::Ethernet:
{ {
BroadbandAdapterSettingsDialog(this, BroadbandAdapterSettingsDialog::Type::Ethernet).exec(); BroadbandAdapterSettingsDialog(this, BroadbandAdapterSettingsDialog::Type::Ethernet).exec();
return; return;
} }
case ExpansionInterface::EXIDEVICE_ETHXLINK: case ExpansionInterface::EXIDeviceType::EthernetXLink:
{ {
BroadbandAdapterSettingsDialog(this, BroadbandAdapterSettingsDialog::Type::XLinkKai).exec(); BroadbandAdapterSettingsDialog(this, BroadbandAdapterSettingsDialog::Type::XLinkKai).exec();
return; return;
} }
default: default:
qFatal("unknown settings pressed"); PanicAlertFmt("Unknown settings pressed for {}", device);
return;
} }
}
void GameCubePane::BrowseMemcard(ExpansionInterface::Slot slot)
{
ASSERT(ExpansionInterface::IsMemcardSlot(slot));
QString filename = DolphinFileDialog::getSaveFileName( QString filename = DolphinFileDialog::getSaveFileName(
this, tr("Choose a file to open"), QString::fromStdString(File::GetUserPath(D_GCUSER_IDX)), this, tr("Choose a file to open"), QString::fromStdString(File::GetUserPath(D_GCUSER_IDX)),
filter, 0, QFileDialog::DontConfirmOverwrite); tr("GameCube Memory Cards (*.raw *.gcp)"), 0, QFileDialog::DontConfirmOverwrite);
if (filename.isEmpty()) if (filename.isEmpty())
return; return;
@ -303,90 +311,85 @@ void GameCubePane::OnConfigPressed(int slot)
QString path_abs = QFileInfo(filename).absoluteFilePath(); QString path_abs = QFileInfo(filename).absoluteFilePath();
// Memcard validity checks // Memcard validity checks
if (memcard) if (File::Exists(filename.toStdString()))
{ {
if (File::Exists(filename.toStdString())) auto [error_code, mc] = Memcard::GCMemcard::Open(filename.toStdString());
{
auto [error_code, mc] = Memcard::GCMemcard::Open(filename.toStdString());
if (error_code.HasCriticalErrors() || !mc || !mc->IsValid()) if (error_code.HasCriticalErrors() || !mc || !mc->IsValid())
{
ModalMessageBox::critical(
this, tr("Error"),
tr("The file\n%1\nis either corrupted or not a GameCube memory card file.\n%2")
.arg(filename)
.arg(GCMemcardManager::GetErrorMessagesForErrorCode(error_code)));
return;
}
}
for (ExpansionInterface::Slot other_slot : ExpansionInterface::MEMCARD_SLOTS)
{
if (other_slot == slot)
continue;
bool other_slot_memcard = m_slot_combos[other_slot]->currentData().toInt() ==
static_cast<int>(ExpansionInterface::EXIDeviceType::MemoryCard);
if (other_slot_memcard)
{
QString path_other =
QFileInfo(QString::fromStdString(Config::Get(Config::GetInfoForMemcardPath(other_slot))))
.absoluteFilePath();
if (path_abs == path_other)
{ {
ModalMessageBox::critical( ModalMessageBox::critical(
this, tr("Error"), this, tr("Error"),
tr("The file\n%1\nis either corrupted or not a GameCube memory card file.\n%2") tr("The same file can't be used in multiple slots; it is already used by %1.")
.arg(filename) .arg(QString::fromStdString(fmt::to_string(other_slot))));
.arg(GCMemcardManager::GetErrorMessagesForErrorCode(error_code)));
return;
}
}
bool other_slot_memcard =
m_slot_combos[slot == SLOT_A_INDEX ? SLOT_B_INDEX : SLOT_A_INDEX]->currentData().toInt() ==
ExpansionInterface::EXIDEVICE_MEMORYCARD;
if (other_slot_memcard)
{
QString path_b =
QFileInfo(QString::fromStdString(slot == 0 ? Config::Get(Config::MAIN_MEMCARD_B_PATH) :
Config::Get(Config::MAIN_MEMCARD_A_PATH)))
.absoluteFilePath();
if (path_abs == path_b)
{
ModalMessageBox::critical(this, tr("Error"),
tr("The same file can't be used in both slots."));
return; return;
} }
} }
} }
QString path_old; QString path_old =
if (memcard) QFileInfo(QString::fromStdString(Config::Get(Config::GetInfoForMemcardPath(slot))))
{ .absoluteFilePath();
path_old =
QFileInfo(QString::fromStdString(slot == 0 ? Config::Get(Config::MAIN_MEMCARD_A_PATH) :
Config::Get(Config::MAIN_MEMCARD_B_PATH)))
.absoluteFilePath();
}
else
{
path_old =
QFileInfo(QString::fromStdString(slot == 0 ? Config::Get(Config::MAIN_AGP_CART_A_PATH) :
Config::Get(Config::MAIN_AGP_CART_B_PATH)))
.absoluteFilePath();
}
if (memcard) Config::SetBase(Config::GetInfoForMemcardPath(slot), path_abs.toStdString());
{
if (slot == SLOT_A_INDEX)
{
Config::SetBase(Config::MAIN_MEMCARD_A_PATH, path_abs.toStdString());
}
else
{
Config::SetBase(Config::MAIN_MEMCARD_B_PATH, path_abs.toStdString());
}
}
else
{
if (slot == SLOT_A_INDEX)
{
Config::SetBase(Config::MAIN_AGP_CART_A_PATH, path_abs.toStdString());
}
else
{
Config::SetBase(Config::MAIN_AGP_CART_B_PATH, path_abs.toStdString());
}
}
if (Core::IsRunning() && path_abs != path_old) if (Core::IsRunning() && path_abs != path_old)
{ {
ExpansionInterface::ChangeDevice( // ChangeDevice unplugs the device for 1 second, which means that games should notice that
// SlotB is on channel 1, slotA and SP1 are on 0 // the path has changed and thus the memory card contents have changed
slot, ExpansionInterface::ChangeDevice(slot, ExpansionInterface::EXIDeviceType::MemoryCard);
// The device enum to change to }
memcard ? ExpansionInterface::EXIDEVICE_MEMORYCARD : ExpansionInterface::EXIDEVICE_AGP, }
// SP1 is device 2, slots are device 0
0); void GameCubePane::BrowseAGPRom(ExpansionInterface::Slot slot)
{
ASSERT(ExpansionInterface::IsMemcardSlot(slot));
QString filename = DolphinFileDialog::getSaveFileName(
this, tr("Choose a file to open"), QString::fromStdString(File::GetUserPath(D_GCUSER_IDX)),
tr("Game Boy Advance Carts (*.gba)"), 0, QFileDialog::DontConfirmOverwrite);
if (filename.isEmpty())
return;
QString path_abs = QFileInfo(filename).absoluteFilePath();
QString path_old =
QFileInfo(QString::fromStdString(Config::Get(Config::GetInfoForAGPCartPath(slot))))
.absoluteFilePath();
Config::SetBase(Config::GetInfoForAGPCartPath(slot), path_abs.toStdString());
if (Core::IsRunning() && path_abs != path_old)
{
// ChangeDevice unplugs the device for 1 second. For an actual AGP, you can remove the
// cartridge without unplugging it, and it's not clear if the AGP software actually notices
// that it's been unplugged or the cartridge has changed, but this was done for memcards so
// we might as well do it for the AGP too.
ExpansionInterface::ChangeDevice(slot, ExpansionInterface::EXIDeviceType::AGP);
} }
} }
@ -455,12 +458,14 @@ void GameCubePane::LoadSettings()
m_skip_main_menu->setToolTip(have_menu ? QString{} : tr("Put IPL ROMs in User/GC/<region>.")); m_skip_main_menu->setToolTip(have_menu ? QString{} : tr("Put IPL ROMs in User/GC/<region>."));
// Device Settings // Device Settings
for (int i = 0; i < SLOT_COUNT; i++) for (ExpansionInterface::Slot slot : ExpansionInterface::SLOTS)
{ {
QSignalBlocker blocker(m_slot_combos[i]); QSignalBlocker blocker(m_slot_combos[slot]);
const ExpansionInterface::TEXIDevices exi_device = Config::Get(Config::GetInfoForEXIDevice(i)); const ExpansionInterface::EXIDeviceType exi_device =
m_slot_combos[i]->setCurrentIndex(m_slot_combos[i]->findData(exi_device)); Config::Get(Config::GetInfoForEXIDevice(slot));
UpdateButton(i); m_slot_combos[slot]->setCurrentIndex(
m_slot_combos[slot]->findData(static_cast<int>(exi_device)));
UpdateButton(slot);
} }
#ifdef HAS_LIBMGBA #ifdef HAS_LIBMGBA
@ -483,24 +488,19 @@ void GameCubePane::SaveSettings()
Config::SetBaseOrCurrent(Config::MAIN_GC_LANGUAGE, m_language_combo->currentData().toInt()); Config::SetBaseOrCurrent(Config::MAIN_GC_LANGUAGE, m_language_combo->currentData().toInt());
// Device Settings // Device Settings
for (int i = 0; i < SLOT_COUNT; i++) for (ExpansionInterface::Slot slot : ExpansionInterface::SLOTS)
{ {
const auto dev = ExpansionInterface::TEXIDevices(m_slot_combos[i]->currentData().toInt()); const auto dev =
const ExpansionInterface::TEXIDevices current_exi_device = static_cast<ExpansionInterface::EXIDeviceType>(m_slot_combos[slot]->currentData().toInt());
Config::Get(Config::GetInfoForEXIDevice(i)); const ExpansionInterface::EXIDeviceType current_exi_device =
Config::Get(Config::GetInfoForEXIDevice(slot));
if (Core::IsRunning() && current_exi_device != dev) if (Core::IsRunning() && current_exi_device != dev)
{ {
ExpansionInterface::ChangeDevice( ExpansionInterface::ChangeDevice(slot, dev);
// SlotB is on channel 1, slotA and SP1 are on 0
(i == 1) ? 1 : 0,
// The device enum to change to
dev,
// SP1 is device 2, slots are device 0
(i == 2) ? 2 : 0);
} }
Config::SetBaseOrCurrent(Config::GetInfoForEXIDevice(i), dev); Config::SetBaseOrCurrent(Config::GetInfoForEXIDevice(slot), dev);
} }
#ifdef HAS_LIBMGBA #ifdef HAS_LIBMGBA

View File

@ -9,6 +9,9 @@
#include <QWidget> #include <QWidget>
#include "Common/EnumMap.h"
#include "Core/HW/EXI/EXI.h"
class QCheckBox; class QCheckBox;
class QComboBox; class QComboBox;
class QLineEdit; class QLineEdit;
@ -31,9 +34,11 @@ private:
void OnEmulationStateChanged(); void OnEmulationStateChanged();
void UpdateButton(int slot); void UpdateButton(ExpansionInterface::Slot slot);
void OnConfigPressed(int slot); void OnConfigPressed(ExpansionInterface::Slot slot);
void BrowseMemcard(ExpansionInterface::Slot slot);
void BrowseAGPRom(ExpansionInterface::Slot slot);
void BrowseGBABios(); void BrowseGBABios();
void BrowseGBARom(size_t index); void BrowseGBARom(size_t index);
void SaveRomPathChanged(); void SaveRomPathChanged();
@ -42,8 +47,8 @@ private:
QCheckBox* m_skip_main_menu; QCheckBox* m_skip_main_menu;
QComboBox* m_language_combo; QComboBox* m_language_combo;
QPushButton* m_slot_buttons[3]; Common::EnumMap<QPushButton*, ExpansionInterface::MAX_SLOT> m_slot_buttons;
QComboBox* m_slot_combos[3]; Common::EnumMap<QComboBox*, ExpansionInterface::MAX_SLOT> m_slot_combos;
QCheckBox* m_gba_threads; QCheckBox* m_gba_threads;
QCheckBox* m_gba_save_rom_path; QCheckBox* m_gba_save_rom_path;