37 changes: 32 additions & 5 deletions Source/Core/Core/HW/EXI/EXI.h
Expand Up @@ -3,7 +3,10 @@

#pragma once

#include <initializer_list>

#include "Common/CommonTypes.h"
#include "Common/EnumFormatter.h"
#include "Core/CoreTiming.h"

class PointerWrap;
Expand All @@ -21,14 +24,31 @@ namespace ExpansionInterface
{
class CEXIChannel;
class IEXIDevice;
enum TEXIDevices : int;
enum class EXIDeviceType : int;

enum
{
MAX_MEMORYCARD_SLOTS = 2,
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 Shutdown();
void DoState(PointerWrap& p);
Expand All @@ -39,11 +59,18 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base);
void UpdateInterrupts();
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);

CEXIChannel* GetChannel(u32 index);

IEXIDevice* FindDevice(TEXIDevices device_type, int customIndex = -1);
IEXIDevice* GetDevice(Slot slot);

} // namespace ExpansionInterface

template <>
struct fmt::formatter<ExpansionInterface::Slot> : EnumFormatter<ExpansionInterface::MAX_SLOT>
{
constexpr formatter() : EnumFormatter({"Slot A", "Slot B", "Serial Port 1"}) {}
};
23 changes: 6 additions & 17 deletions Source/Core/Core/HW/EXI/EXI_Channel.cpp
Expand Up @@ -34,7 +34,7 @@ CEXIChannel::CEXIChannel(u32 channel_id, const Memcard::HeaderData& memcard_head
m_status.CHIP_SELECT = 1;

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()
Expand Down Expand Up @@ -168,7 +168,7 @@ void CEXIChannel::RemoveDevices()
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);
}
Expand Down Expand Up @@ -245,7 +245,7 @@ void CEXIChannel::DoState(PointerWrap& p)
for (int device_index = 0; device_index < NUM_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);

if (type == device->m_device_type)
Expand All @@ -260,7 +260,7 @@ void CEXIChannel::DoState(PointerWrap& p)
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())
{
// We have loaded a savestate that has a GCI folder memcard that is different to the virtual
Expand All @@ -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
// 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.
AddDevice(EXIDEVICE_NONE, device_index);
ExpansionInterface::ChangeDevice(m_channel_id, EXIDEVICE_MEMORYCARDFOLDER, device_index,
AddDevice(EXIDeviceType::None, device_index);
ExpansionInterface::ChangeDevice(m_channel_id, device_index, EXIDeviceType::MemoryCardFolder,
CoreTiming::FromThread::CPU);
}
}
Expand All @@ -294,15 +294,4 @@ void CEXIChannel::SetEXIINT(bool 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
5 changes: 2 additions & 3 deletions Source/Core/Core/HW/EXI/EXI_Channel.h
Expand Up @@ -20,7 +20,7 @@ class Mapping;
namespace ExpansionInterface
{
class IEXIDevice;
enum TEXIDevices : int;
enum class EXIDeviceType : int;

class CEXIChannel
{
Expand All @@ -30,13 +30,12 @@ class CEXIChannel

// get device
IEXIDevice* GetDevice(u8 chip_select);
IEXIDevice* FindDevice(TEXIDevices device_type, int custom_index = -1);

void RegisterMMIO(MMIO::Mapping* mmio, u32 base);

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,
bool notify_presence_changed = true);

Expand Down
42 changes: 20 additions & 22 deletions Source/Core/Core/HW/EXI/EXI_Device.cpp
Expand Up @@ -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
{
return false;
Expand Down Expand Up @@ -101,60 +96,63 @@ void IEXIDevice::TransferByte(u8& byte)
}

// 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)
{
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)
{
case EXIDEVICE_DUMMY:
case EXIDeviceType::Dummy:
result = std::make_unique<CEXIDummy>("Dummy");
break;

case EXIDEVICE_MEMORYCARD:
case EXIDEVICE_MEMORYCARDFOLDER:
case EXIDeviceType::MemoryCard:
case EXIDeviceType::MemoryCardFolder:
{
bool gci_folder = (device_type == EXIDEVICE_MEMORYCARDFOLDER);
result = std::make_unique<CEXIMemoryCard>(channel_num, gci_folder, memcard_header_data);
bool gci_folder = (device_type == EXIDeviceType::MemoryCardFolder);
result = std::make_unique<CEXIMemoryCard>(slot, gci_folder, memcard_header_data);
break;
}
case EXIDEVICE_MASKROM:
case EXIDeviceType::MaskROM:
result = std::make_unique<CEXIIPL>();
break;

case EXIDEVICE_AD16:
case EXIDeviceType::AD16:
result = std::make_unique<CEXIAD16>();
break;

case EXIDEVICE_MIC:
case EXIDeviceType::Microphone:
result = std::make_unique<CEXIMic>(channel_num);
break;

case EXIDEVICE_ETH:
case EXIDeviceType::Ethernet:
result = std::make_unique<CEXIETHERNET>(BBADeviceType::TAP);
break;

#if defined(__APPLE__)
case EXIDEVICE_ETHTAPSERVER:
case EXIDeviceType::EthernetTapServer:
result = std::make_unique<CEXIETHERNET>(BBADeviceType::TAPSERVER);
break;
#endif

case EXIDEVICE_ETHXLINK:
case EXIDeviceType::EthernetXLink:
result = std::make_unique<CEXIETHERNET>(BBADeviceType::XLINK);
break;

case EXIDEVICE_GECKO:
case EXIDeviceType::Gecko:
result = std::make_unique<CEXIGecko>();
break;

case EXIDEVICE_AGP:
result = std::make_unique<CEXIAgp>(channel_num);
case EXIDeviceType::AGP:
result = std::make_unique<CEXIAgp>(slot);
break;

case EXIDEVICE_AM_BASEBOARD:
case EXIDEVICE_NONE:
case EXIDeviceType::AMBaseboard:
case EXIDeviceType::None:
default:
result = std::make_unique<IEXIDevice>();
break;
Expand Down
86 changes: 67 additions & 19 deletions Source/Core/Core/HW/EXI/EXI_Device.h
Expand Up @@ -4,7 +4,10 @@
#pragma once

#include <memory>

#include "Common/Common.h"
#include "Common/CommonTypes.h"
#include "Common/EnumFormatter.h"

class PointerWrap;

Expand All @@ -15,26 +18,26 @@ struct HeaderData;

namespace ExpansionInterface
{
enum TEXIDevices : int
enum class EXIDeviceType : int
{
EXIDEVICE_DUMMY,
EXIDEVICE_MEMORYCARD,
EXIDEVICE_MASKROM,
EXIDEVICE_AD16,
EXIDEVICE_MIC,
EXIDEVICE_ETH,
Dummy,
MemoryCard,
MaskROM,
AD16,
Microphone,
Ethernet,
// 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.
EXIDEVICE_AM_BASEBOARD,
EXIDEVICE_GECKO,
AMBaseboard,
Gecko,
// Only used when creating a device by EXIDevice_Create.
// Converted to EXIDEVICE_MEMORYCARD internally.
EXIDEVICE_MEMORYCARDFOLDER,
EXIDEVICE_AGP,
EXIDEVICE_ETHXLINK,
// Converted to MemoryCard internally.
MemoryCardFolder,
AGP,
EthernetXLink,
// Only used on Apple devices.
EXIDEVICE_ETHTAPSERVER,
EXIDEVICE_NONE = 0xFF
EthernetTapServer,
None = 0xFF
};

class IEXIDevice
Expand All @@ -51,8 +54,6 @@ class IEXIDevice
virtual void DMAWrite(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 IsPresent() const;
virtual void SetCS(int cs);
Expand All @@ -65,13 +66,60 @@ class IEXIDevice
// 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
// such.
TEXIDevices m_device_type = TEXIDevices::EXIDEVICE_NONE;
EXIDeviceType m_device_type = EXIDeviceType::None;

private:
// Byte transfer function for this device
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);
} // 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>"));
}
}
}
};
15 changes: 7 additions & 8 deletions Source/Core/Core/HW/EXI/EXI_DeviceAGP.cpp
Expand Up @@ -8,18 +8,21 @@
#include <string>
#include <vector>

#include "Common/Assert.h"
#include "Common/ChunkFile.h"
#include "Common/CommonTypes.h"
#include "Common/IOFile.h"
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
#include "Core/Config/MainSettings.h"
#include "Core/HW/EXI/EXI.h"

namespace ExpansionInterface
{
CEXIAgp::CEXIAgp(int index)
CEXIAgp::CEXIAgp(Slot slot)
{
m_slot = index;
ASSERT(IsMemcardSlot(slot));
m_slot = slot;

// Create the ROM
m_rom_size = 0;
Expand All @@ -35,9 +38,7 @@ CEXIAgp::~CEXIAgp()
std::string filename;
std::string ext;
std::string gbapath;
SplitPath(m_slot == 0 ? Config::Get(Config::MAIN_AGP_CART_A_PATH) :
Config::Get(Config::MAIN_AGP_CART_B_PATH),
&path, &filename, &ext);
SplitPath(Config::Get(Config::GetInfoForAGPCartPath(m_slot)), &path, &filename, &ext);
gbapath = path + filename;

SaveFileFromEEPROM(gbapath + ".sav");
Expand Down Expand Up @@ -75,9 +76,7 @@ void CEXIAgp::LoadRom()
std::string path;
std::string filename;
std::string ext;
SplitPath(m_slot == 0 ? Config::Get(Config::MAIN_AGP_CART_A_PATH) :
Config::Get(Config::MAIN_AGP_CART_B_PATH),
&path, &filename, &ext);
SplitPath(Config::Get(Config::GetInfoForAGPCartPath(m_slot)), &path, &filename, &ext);
const std::string gbapath = path + filename;
LoadFileToROM(gbapath + ext);
INFO_LOG_FMT(EXPANSIONINTERFACE, "Loaded GBA rom: {} card: {}", gbapath, m_slot);
Expand Down
6 changes: 4 additions & 2 deletions Source/Core/Core/HW/EXI/EXI_DeviceAGP.h
Expand Up @@ -12,10 +12,12 @@ class PointerWrap;

namespace ExpansionInterface
{
enum class Slot : int;

class CEXIAgp : public IEXIDevice
{
public:
CEXIAgp(const int index);
CEXIAgp(const Slot slot);
virtual ~CEXIAgp() override;
bool IsPresent() const override { return true; }
void ImmWrite(u32 _uData, u32 _uSize) override;
Expand All @@ -31,7 +33,7 @@ class CEXIAgp : public IEXIDevice
EE_READ_TRUE = 0xB,
};

int m_slot;
Slot m_slot;

//! ROM
u32 m_rom_size = 0;
Expand Down
115 changes: 51 additions & 64 deletions Source/Core/Core/HW/EXI/EXI_DeviceMemoryCard.cpp
Expand Up @@ -16,6 +16,7 @@
#include "Common/CommonPaths.h"
#include "Common/CommonTypes.h"
#include "Common/Config/Config.h"
#include "Common/EnumMap.h"
#include "Common/FileUtil.h"
#include "Common/IniFile.h"
#include "Common/Logging/Log.h"
Expand Down Expand Up @@ -48,25 +49,24 @@ namespace ExpansionInterface
static const u32 MC_TRANSFER_RATE_READ = 512 * 1024;
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 std::array<CoreTiming::EventType*, 2> s_et_transfer_complete;
static Common::EnumMap<CoreTiming::EventType*, MAX_MEMCARD_SLOT> s_et_cmd_done;
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.
void CEXIMemoryCard::EventCompleteFindInstance(u64 userdata,
std::function<void(CEXIMemoryCard*)> callback)
{
int card_index = (int)userdata;
auto* self = static_cast<CEXIMemoryCard*>(
ExpansionInterface::FindDevice(EXIDEVICE_MEMORYCARD, card_index));
if (self == nullptr)
Slot card_slot = static_cast<Slot>(userdata);
IEXIDevice* self = ExpansionInterface::GetDevice(card_slot);
if (self != nullptr)
{
self = static_cast<CEXIMemoryCard*>(
ExpansionInterface::FindDevice(EXIDEVICE_MEMORYCARDFOLDER, card_index));
}
if (self)
{
callback(self);
if (self->m_device_type == EXIDeviceType::MemoryCard ||
self->m_device_type == EXIDeviceType::MemoryCardFolder)
{
callback(static_cast<CEXIMemoryCard*>(self));
}
}
}

Expand All @@ -83,19 +83,15 @@ void CEXIMemoryCard::TransferCompleteCallback(u64 userdata, s64)

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");
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;
name += static_cast<char>('A' + i);
s_et_cmd_done[i] = CoreTiming::RegisterEvent(name, CmdDoneCallback);

name = TRANSFER_COMPLETE_PREFIX;
name += static_cast<char>('A' + i);
s_et_transfer_complete[i] = CoreTiming::RegisterEvent(name, TransferCompleteCallback);
s_et_cmd_done[slot] = CoreTiming::RegisterEvent(
fmt::format("memcardDone{}", s_card_short_names[slot]), CmdDoneCallback);
s_et_transfer_complete[slot] = CoreTiming::RegisterEvent(
fmt::format("memcardTransferComplete{}", s_card_short_names[slot]),
TransferCompleteCallback);
}
}

Expand All @@ -105,12 +101,12 @@ void CEXIMemoryCard::Shutdown()
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)
: m_card_index(index)
: m_card_slot(slot)
{
ASSERT_MSG(EXPANSIONINTERFACE, static_cast<std::size_t>(index) < s_et_cmd_done.size(),
"Trying to create invalid memory card index {}.", index);
ASSERT_MSG(EXPANSIONINTERFACE, IsMemcardSlot(slot), "Trying to create invalid memory card in {}.",
slot);

// 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.
Expand Down Expand Up @@ -145,15 +141,13 @@ CEXIMemoryCard::CEXIMemoryCard(const int index, bool gci_folder,
m_memory_card_size = m_memory_card->GetCardId() * SIZE_TO_Mb;
std::array<u8, 20> header{};
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 */>
CEXIMemoryCard::GetGCIFolderPath(int card_index, AllowMovieFolder allow_movie_folder)
CEXIMemoryCard::GetGCIFolderPath(Slot card_slot, AllowMovieFolder allow_movie_folder)
{
std::string path_override =
Config::Get(card_index == 0 ? Config::MAIN_GCI_FOLDER_A_PATH_OVERRIDE :
Config::MAIN_GCI_FOLDER_B_PATH_OVERRIDE);
std::string path_override = Config::Get(Config::GetInfoForGCIPathOverride(card_slot));

if (!path_override.empty())
return {std::move(path_override), false};
Expand All @@ -162,15 +156,15 @@ CEXIMemoryCard::GetGCIFolderPath(int card_index, AllowMovieFolder allow_movie_fo

const bool use_movie_folder = allow_movie_folder == AllowMovieFolder::Yes &&
Movie::IsPlayingInput() && Movie::IsConfigSaved() &&
Movie::IsUsingMemcard(card_index) &&
Movie::IsUsingMemcard(card_slot) &&
Movie::IsStartingFromClearSave();

if (use_movie_folder)
path += "Movie" DIR_SEP;

const DiscIO::Region region = SConfig::ToGameCubeRegion(SConfig::GetInstance().m_region);
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};
}

Expand All @@ -186,15 +180,15 @@ 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
// 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 bool migrate = folder_path_pair.second;

const File::FileInfo file_info(dir_path);
if (!file_info.Exists())
{
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
File::CreateFullPath(dir_path + DIR_SEP);
}
Expand All @@ -204,7 +198,7 @@ void CEXIMemoryCard::SetupGciFolder(const Memcard::HeaderData& header_data)
{
PanicAlertFmtT("{0} was not a directory, moved to *.original", dir_path);
if (migrate)
MigrateFromMemcardFile(dir_path + DIR_SEP, m_card_index);
MigrateFromMemcardFile(dir_path + DIR_SEP, m_card_slot);
else
File::CreateFullPath(dir_path + DIR_SEP);
}
Expand All @@ -218,36 +212,37 @@ void CEXIMemoryCard::SetupGciFolder(const Memcard::HeaderData& header_data)
}
}

m_memory_card = std::make_unique<GCMemcardDirectory>(dir_path + DIR_SEP, m_card_index,
header_data, current_game_id);
m_memory_card = std::make_unique<GCMemcardDirectory>(dir_path + DIR_SEP, m_card_slot, header_data,
current_game_id);
}

void CEXIMemoryCard::SetupRawMemcard(u16 size_mb)
{
const bool is_slot_a = m_card_index == 0;
std::string filename = is_slot_a ? Config::Get(Config::MAIN_MEMCARD_A_PATH) :
Config::Get(Config::MAIN_MEMCARD_B_PATH);
if (Movie::IsPlayingInput() && Movie::IsConfigSaved() && Movie::IsUsingMemcard(m_card_index) &&
std::string filename = Config::Get(Config::GetInfoForMemcardPath(m_card_slot));
if (Movie::IsPlayingInput() && Movie::IsConfigSaved() && Movie::IsUsingMemcard(m_card_slot) &&
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 =
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)
{
filename.insert(filename.find_last_of('.'),
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()
{
CoreTiming::RemoveEvent(s_et_cmd_done[m_card_index]);
CoreTiming::RemoveEvent(s_et_transfer_complete[m_card_index]);
CoreTiming::RemoveEvent(s_et_cmd_done[m_card_slot]);
CoreTiming::RemoveEvent(s_et_transfer_complete[m_card_slot]);
}

bool CEXIMemoryCard::UseDelayedTransferCompletion() const
Expand All @@ -272,13 +267,14 @@ void CEXIMemoryCard::CmdDone()
void CEXIMemoryCard::TransferComplete()
{
// Transfer complete, send interrupt
ExpansionInterface::GetChannel(m_card_index)->SendTransferComplete();
ExpansionInterface::GetChannel(ExpansionInterface::SlotToEXIChannel(m_card_slot))
->SendTransferComplete();
}

void CEXIMemoryCard::CmdDoneLater(u64 cycles)
{
CoreTiming::RemoveEvent(s_et_cmd_done[m_card_index]);
CoreTiming::ScheduleEvent(cycles, s_et_cmd_done[m_card_index], m_card_index);
CoreTiming::RemoveEvent(s_et_cmd_done[m_card_slot]);
CoreTiming::ScheduleEvent(cycles, s_et_cmd_done[m_card_slot], static_cast<u64>(m_card_slot));
}

void CEXIMemoryCard::SetCS(int cs)
Expand Down Expand Up @@ -522,19 +518,10 @@ void CEXIMemoryCard::DoState(PointerWrap& p)
p.Do(m_programming_buffer);
p.Do(m_address);
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
// read all at once instead of single byte at a time as done by IEXIDevice::DMARead
void CEXIMemoryCard::DMARead(u32 addr, u32 size)
Expand All @@ -548,7 +535,7 @@ void CEXIMemoryCard::DMARead(u32 addr, u32 size)

// Schedule transfer complete later based on read speed
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
Expand All @@ -564,6 +551,6 @@ void CEXIMemoryCard::DMAWrite(u32 addr, u32 size)

// Schedule transfer complete later based on write speed
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
9 changes: 5 additions & 4 deletions Source/Core/Core/HW/EXI/EXI_DeviceMemoryCard.h
Expand Up @@ -21,6 +21,8 @@ struct HeaderData;

namespace ExpansionInterface
{
enum class Slot : int;

enum class AllowMovieFolder
{
Yes,
Expand All @@ -30,14 +32,13 @@ enum class AllowMovieFolder
class CEXIMemoryCard : public IEXIDevice
{
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;
void SetCS(int cs) override;
bool IsInterruptSet() override;
bool UseDelayedTransferCompletion() const override;
bool IsPresent() const override;
void DoState(PointerWrap& p) override;
IEXIDevice* FindDevice(TEXIDevices device_type, int custom_index) override;
void DMARead(u32 addr, u32 size) override;
void DMAWrite(u32 addr, u32 size) override;

Expand All @@ -48,7 +49,7 @@ class CEXIMemoryCard : public IEXIDevice
static void Shutdown();

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:
void SetupGciFolder(const Memcard::HeaderData& header_data);
Expand Down Expand Up @@ -90,7 +91,7 @@ class CEXIMemoryCard : public IEXIDevice
ChipErase = 0xF4,
};

int m_card_index;
Slot m_card_slot;
//! memory card state

// STATE_TO_SAVE
Expand Down
7 changes: 4 additions & 3 deletions Source/Core/Core/HW/GCMemcard/GCMemcardBase.h
Expand Up @@ -12,8 +12,9 @@ class PointerWrap;
class MemoryCardBase
{
public:
explicit MemoryCardBase(int card_index = 0, int size_mbits = Memcard::MBIT_SIZE_MEMORY_CARD_2043)
: m_card_index(card_index), m_nintendo_card_id(size_mbits)
explicit MemoryCardBase(ExpansionInterface::Slot card_slot,
int size_mbits = Memcard::MBIT_SIZE_MEMORY_CARD_2043)
: m_card_slot(card_slot), m_nintendo_card_id(size_mbits)
{
}
virtual ~MemoryCardBase() = default;
Expand All @@ -25,6 +26,6 @@ class MemoryCardBase
u32 GetCardId() const { return m_nintendo_card_id; }

protected:
int m_card_index;
ExpansionInterface::Slot m_card_slot;
u16 m_nintendo_card_id;
};
9 changes: 4 additions & 5 deletions Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp
Expand Up @@ -152,7 +152,7 @@ std::vector<std::string> GCMemcardDirectory::GetFileNamesForGameID(const std::st
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)
: 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),
Expand Down Expand Up @@ -240,7 +240,7 @@ void GCMemcardDirectory::FlushThread()
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};
while (true)
Expand Down Expand Up @@ -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);
std::string ini_memcard = (card_index == 0) ? Config::Get(Config::MAIN_MEMCARD_A_PATH) :
Config::Get(Config::MAIN_MEMCARD_B_PATH);
std::string ini_memcard = Config::Get(Config::GetInfoForMemcardPath(card_slot));
if (File::Exists(ini_memcard))
{
auto [error_code, memcard] = Memcard::GCMemcard::Open(ini_memcard.c_str());
Expand Down
6 changes: 3 additions & 3 deletions Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h
Expand Up @@ -15,13 +15,13 @@

// Uncomment this to write the system data of the memorycard from directory to disc
//#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
{
public:
GCMemcardDirectory(const std::string& directory, int slot, const Memcard::HeaderData& header_data,
u32 game_id);
GCMemcardDirectory(const std::string& directory, ExpansionInterface::Slot slot,
const Memcard::HeaderData& header_data, u32 game_id);
~GCMemcardDirectory();

GCMemcardDirectory(const GCMemcardDirectory&) = delete;
Expand Down
25 changes: 15 additions & 10 deletions Source/Core/Core/HW/GCMemcard/GCMemcardRaw.cpp
Expand Up @@ -26,15 +26,17 @@
#include "Core/Config/SessionSettings.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/HW/EXI/EXI.h"
#include "Core/HW/EXI/EXI_DeviceIPL.h"
#include "Core/HW/GCMemcard/GCMemcard.h"
#include "Core/HW/Sram.h"

#define SIZE_TO_Mb (1024 * 8 * 16)
#define MC_HDR_SIZE 0xA000

MemoryCard::MemoryCard(const std::string& filename, int card_index, u16 size_mbits)
: MemoryCardBase(card_index, size_mbits), m_filename(filename)
MemoryCard::MemoryCard(const std::string& filename, ExpansionInterface::Slot card_slot,
u16 size_mbits)
: MemoryCardBase(card_slot, size_mbits), m_filename(filename)
{
File::IOFile file(m_filename, "rb");
if (file)
Expand Down Expand Up @@ -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");
if (memcardPath.empty())
{
// 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;
}
else
Expand All @@ -118,7 +122,7 @@ void MemoryCard::CheckPath(std::string& memcardPath, const std::string& gameRegi
"Slot {1} path was changed to\n"
"{2}\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))
PanicAlertFmtT("Copy failed");
Expand All @@ -142,7 +146,7 @@ void MemoryCard::FlushThread()
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);

Expand Down Expand Up @@ -199,9 +203,10 @@ void MemoryCard::FlushThread()
if (do_exit)
return;

Core::DisplayMessage(
fmt::format("Wrote memory card {} contents to {}", m_card_index ? 'B' : 'A', m_filename),
4000);
Core::DisplayMessage(fmt::format("Wrote memory card {} contents to {}",
m_card_slot == ExpansionInterface::Slot::A ? 'A' : 'B',
m_filename),
4000);
}
}

Expand Down Expand Up @@ -265,7 +270,7 @@ void MemoryCard::ClearAll()

void MemoryCard::DoState(PointerWrap& p)
{
p.Do(m_card_index);
p.Do(m_card_slot);
p.Do(m_memory_card_size);
p.DoArray(&m_memcard_data[0], m_memory_card_size);
}
5 changes: 3 additions & 2 deletions Source/Core/Core/HW/GCMemcard/GCMemcardRaw.h
Expand Up @@ -17,10 +17,11 @@ class PointerWrap;
class MemoryCard : public MemoryCardBase
{
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);
~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 MakeDirty();

Expand Down
19 changes: 18 additions & 1 deletion Source/Core/Core/HW/Sram.cpp
Expand Up @@ -6,8 +6,11 @@
#include "Common/CommonTypes.h"
#include "Common/IOFile.h"
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"
#include "Common/Swap.h"

#include "Core/ConfigManager.h"
#include "Core/HW/EXI/EXI.h"

// English
// This is just a template. Most/all fields are updated with sane(r) values at runtime.
Expand Down Expand Up @@ -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]);
u8 csum = 0;
for (int i = 0; i < 12; i++)
Expand Down
8 changes: 7 additions & 1 deletion Source/Core/Core/HW/Sram.h
Expand Up @@ -34,9 +34,15 @@ distribution.
#pragma once

#include <array>

#include "Common/CommonTypes.h"
#include "Common/Swap.h"

namespace ExpansionInterface
{
enum class Slot : int;
};

using CardFlashId = std::array<u8, 12>;

#pragma pack(push, 1)
Expand Down Expand Up @@ -128,7 +134,7 @@ static_assert(sizeof(Sram) == 0x44);
#pragma pack(pop)

void InitSRAM();
void SetCardFlashID(const u8* buffer, u8 card_index);
void SetCardFlashID(const u8* buffer, ExpansionInterface::Slot card_slot);
void FixSRAMChecksums();

extern Sram g_SRAM;
Expand Down
34 changes: 22 additions & 12 deletions Source/Core/Core/Movie.cpp
Expand Up @@ -43,6 +43,7 @@
#include "Core/DSP/DSPCore.h"
#include "Core/HW/CPU.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_DeviceMemoryCard.h"
#include "Core/HW/ProcessorInterface.h"
Expand Down Expand Up @@ -433,9 +434,17 @@ bool IsStartingFromClearSave()
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()
Expand Down Expand Up @@ -1438,12 +1447,13 @@ void SetGraphicsConfig()
// NOTE: EmuThread / Host Thread
void GetSettings()
{
const ExpansionInterface::TEXIDevices slot_a_type = Config::Get(Config::MAIN_SLOT_A);
const ExpansionInterface::TEXIDevices slot_b_type = Config::Get(Config::MAIN_SLOT_B);
const bool slot_a_has_raw_memcard = slot_a_type == ExpansionInterface::EXIDEVICE_MEMORYCARD;
const bool slot_a_has_gci_folder = slot_a_type == ExpansionInterface::EXIDEVICE_MEMORYCARDFOLDER;
const bool slot_b_has_raw_memcard = slot_b_type == ExpansionInterface::EXIDEVICE_MEMORYCARD;
const bool slot_b_has_gci_folder = slot_b_type == ExpansionInterface::EXIDEVICE_MEMORYCARDFOLDER;
using ExpansionInterface::EXIDeviceType;
const EXIDeviceType slot_a_type = Config::Get(Config::MAIN_SLOT_A);
const EXIDeviceType slot_b_type = Config::Get(Config::MAIN_SLOT_B);
const bool slot_a_has_raw_memcard = slot_a_type == EXIDeviceType::MemoryCard;
const bool slot_a_has_gci_folder = slot_a_type == EXIDeviceType::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_bNetPlay = NetPlay::IsNetPlayRunning();
Expand All @@ -1455,18 +1465,18 @@ void GetSettings()
}
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(
card_index, ExpansionInterface::AllowMovieFolder::No);
card_slot, ExpansionInterface::AllowMovieFolder::No);
const u64 number_of_saves = File::ScanDirectoryTree(path, false).size;
return number_of_saves > 0;
};

s_bClearSave =
!(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_a_has_gci_folder && gci_folder_has_saves(0)) &&
!(slot_b_has_gci_folder && gci_folder_has_saves(1));
!(slot_a_has_gci_folder && gci_folder_has_saves(ExpansionInterface::Slot::A)) &&
!(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_b_has_raw_memcard || slot_b_has_gci_folder) << 1;
Expand Down
7 changes: 6 additions & 1 deletion Source/Core/Core/Movie.h
Expand Up @@ -17,6 +17,11 @@ struct BootParameters;
struct GCPadStatus;
class PointerWrap;

namespace ExpansionInterface
{
enum class Slot : int;
}

namespace WiimoteCommon
{
class DataReportBuilder;
Expand Down Expand Up @@ -166,7 +171,7 @@ void SetReset(bool reset);

bool IsConfigSaved();
bool IsStartingFromClearSave();
bool IsUsingMemcard(int memcard);
bool IsUsingMemcard(ExpansionInterface::Slot slot);
void SetGraphicsConfig();
bool IsNetPlayRecording();

Expand Down
5 changes: 3 additions & 2 deletions Source/Core/Core/NetPlayClient.cpp
Expand Up @@ -39,6 +39,7 @@
#include "Core/Config/SessionSettings.h"
#include "Core/ConfigManager.h"
#include "Core/GeckoCode.h"
#include "Core/HW/EXI/EXI.h"
#include "Core/HW/EXI/EXI_DeviceIPL.h"
#ifdef HAS_LIBMGBA
#include "Core/HW/GBACore.h"
Expand Down Expand Up @@ -818,8 +819,8 @@ void NetPlayClient::OnStartGame(sf::Packet& packet)
packet >> m_net_settings.m_OCEnable;
packet >> m_net_settings.m_OCFactor;

for (auto& device : m_net_settings.m_EXIDevice)
packet >> device;
for (auto slot : ExpansionInterface::SLOTS)
packet >> m_net_settings.m_EXIDevice[slot];

for (u32& value : m_net_settings.m_SYSCONFSettings)
packet >> value;
Expand Down
4 changes: 3 additions & 1 deletion Source/Core/Core/NetPlayProto.h
Expand Up @@ -8,7 +8,9 @@
#include <vector>

#include "Common/CommonTypes.h"
#include "Common/EnumMap.h"
#include "Core/Config/SYSCONFSettings.h"
#include "Core/HW/EXI/EXI.h"
#include "Core/HW/EXI/EXI_Device.h"

namespace DiscIO
Expand Down Expand Up @@ -44,7 +46,7 @@ struct NetSettings
bool m_CopyWiiSave = false;
bool m_OCEnable = false;
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{};

Expand Down
49 changes: 30 additions & 19 deletions Source/Core/Core/NetPlayServer.cpp
Expand Up @@ -40,6 +40,8 @@
#include "Core/ConfigManager.h"
#include "Core/GeckoCode.h"
#include "Core/GeckoCodeConfig.h"
#include "Core/HW/EXI/EXI.h"
#include "Core/HW/EXI/EXI_Device.h"
#ifdef HAS_LIBMGBA
#include "Core/HW/GBACore.h"
#endif
Expand Down Expand Up @@ -1306,10 +1308,21 @@ bool NetPlayServer::SetupNetSettings()
settings.m_CopyWiiSave = Config::Get(Config::NETPLAY_LOAD_WII_SAVE);
settings.m_OCEnable = Config::Get(Config::MAIN_OVERCLOCK_ENABLE);
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);
// There's no way the BBA is going to sync, disable it
settings.m_EXIDevice[2] = ExpansionInterface::EXIDEVICE_NONE;

for (ExpansionInterface::Slot slot : ExpansionInterface::SLOTS)
{
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)
{
Expand Down Expand Up @@ -1494,8 +1507,8 @@ bool NetPlayServer::StartGame()
spac << m_settings.m_OCEnable;
spac << m_settings.m_OCFactor;

for (auto& device : m_settings.m_EXIDevice)
spac << device;
for (auto slot : ExpansionInterface::SLOTS)
spac << static_cast<int>(m_settings.m_EXIDevice[slot]);

for (u32 value : m_settings.m_SYSCONFSettings)
spac << value;
Expand Down Expand Up @@ -1592,12 +1605,11 @@ bool NetPlayServer::SyncSaveData()

u8 save_count = 0;

constexpr int exi_device_count = 2;
for (int i = 0; i < exi_device_count; ++i)
for (ExpansionInterface::Slot slot : ExpansionInterface::MEMCARD_SLOTS)
{
if (m_settings.m_EXIDevice[i] == ExpansionInterface::EXIDEVICE_MEMORYCARD ||
Config::Get(Config::GetInfoForEXIDevice(i)) ==
ExpansionInterface::EXIDEVICE_MEMORYCARDFOLDER)
if (m_settings.m_EXIDevice[slot] == ExpansionInterface::EXIDeviceType::MemoryCard ||
Config::Get(Config::GetInfoForEXIDevice(slot)) ==
ExpansionInterface::EXIDeviceType::MemoryCardFolder)
{
save_count++;
}
Expand Down Expand Up @@ -1652,16 +1664,15 @@ bool NetPlayServer::SyncSaveData()
const std::string region =
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) :
Config::Get(Config::MAIN_MEMCARD_B_PATH);
std::string path = Config::Get(Config::GetInfoForMemcardPath(slot));

MemoryCard::CheckPath(path, region, is_slot_a);
MemoryCard::CheckPath(path, region, slot);

int size_override;
IniFile gameIni = SConfig::LoadGameIni(game->GetGameID(), game->GetRevision());
Expand Down Expand Up @@ -1693,8 +1704,8 @@ bool NetPlayServer::SyncSaveData()
SendChunkedToClients(std::move(pac), 1,
fmt::format("Memory Card {} Synchronization", is_slot_a ? 'A' : 'B'));
}
else if (Config::Get(Config::GetInfoForEXIDevice(i)) ==
ExpansionInterface::EXIDEVICE_MEMORYCARDFOLDER)
else if (Config::Get(Config::GetInfoForEXIDevice(slot)) ==
ExpansionInterface::EXIDeviceType::MemoryCardFolder)
{
const std::string path = File::GetUserPath(D_GCUSER_IDX) + region + DIR_SEP +
fmt::format("Card {}", is_slot_a ? 'A' : 'B');
Expand Down
157 changes: 85 additions & 72 deletions Source/Core/DolphinQt/GCMemcardManager.cpp
Expand Up @@ -42,6 +42,8 @@
#include "DolphinQt/QtUtils/DolphinFileDialog.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h"

using namespace ExpansionInterface;

constexpr int ROW_HEIGHT = 36;
constexpr int COLUMN_WIDTH_FILENAME = 100;
constexpr int COLUMN_WIDTH_BANNER = Memcard::MEMORY_CARD_BANNER_WIDTH + 6;
Expand All @@ -55,6 +57,14 @@ constexpr int COLUMN_INDEX_ICON = 3;
constexpr int COLUMN_INDEX_BLOCKS = 4;
constexpr int COLUMN_COUNT = 5;

namespace
{
Slot OtherSlot(Slot slot)
{
return slot == Slot::A ? Slot::B : Slot::A;
}
}; // namespace

struct GCMemcardManager::IconAnimationData
{
// the individual frames
Expand All @@ -70,7 +80,7 @@ GCMemcardManager::GCMemcardManager(QWidget* parent) : QDialog(parent)
CreateWidgets();
ConnectWidgets();

SetActiveSlot(0);
SetActiveSlot(Slot::A);
UpdateActions();

m_timer = new QTimer(this);
Expand Down Expand Up @@ -117,52 +127,54 @@ void GCMemcardManager::CreateWidgets()

auto* layout = new QGridLayout;

for (int i = 0; i < SLOT_COUNT; i++)
{
m_slot_group[i] = new QGroupBox(i == 0 ? tr("Slot A") : tr("Slot B"));
m_slot_file_edit[i] = new QLineEdit;
m_slot_open_button[i] = new QPushButton(tr("&Open..."));
m_slot_create_button[i] = new QPushButton(tr("&Create..."));
m_slot_table[i] = new QTableWidget;
m_slot_table[i]->setTabKeyNavigation(false);
m_slot_stat_label[i] = new QLabel;

m_slot_table[i]->setSelectionMode(QAbstractItemView::ExtendedSelection);
m_slot_table[i]->setSelectionBehavior(QAbstractItemView::SelectRows);
m_slot_table[i]->setSortingEnabled(true);
m_slot_table[i]->horizontalHeader()->setHighlightSections(false);
m_slot_table[i]->horizontalHeader()->setMinimumSectionSize(0);
m_slot_table[i]->horizontalHeader()->setSortIndicatorShown(true);
m_slot_table[i]->setColumnCount(COLUMN_COUNT);
m_slot_table[i]->setHorizontalHeaderItem(COLUMN_INDEX_FILENAME,
new QTableWidgetItem(tr("Filename")));
m_slot_table[i]->setHorizontalHeaderItem(COLUMN_INDEX_BANNER,
new QTableWidgetItem(tr("Banner")));
m_slot_table[i]->setHorizontalHeaderItem(COLUMN_INDEX_TEXT, new QTableWidgetItem(tr("Title")));
m_slot_table[i]->setHorizontalHeaderItem(COLUMN_INDEX_ICON, new QTableWidgetItem(tr("Icon")));
m_slot_table[i]->setHorizontalHeaderItem(COLUMN_INDEX_BLOCKS,
new QTableWidgetItem(tr("Blocks")));
m_slot_table[i]->setColumnWidth(COLUMN_INDEX_FILENAME, COLUMN_WIDTH_FILENAME);
m_slot_table[i]->setColumnWidth(COLUMN_INDEX_BANNER, COLUMN_WIDTH_BANNER);
m_slot_table[i]->setColumnWidth(COLUMN_INDEX_TEXT, COLUMN_WIDTH_TEXT);
m_slot_table[i]->setColumnWidth(COLUMN_INDEX_ICON, COLUMN_WIDTH_ICON);
m_slot_table[i]->setColumnWidth(COLUMN_INDEX_BLOCKS, COLUMN_WIDTH_BLOCKS);
m_slot_table[i]->verticalHeader()->setDefaultSectionSize(ROW_HEIGHT);
m_slot_table[i]->verticalHeader()->hide();
m_slot_table[i]->setShowGrid(false);
for (Slot slot : MEMCARD_SLOTS)
{
m_slot_group[slot] = new QGroupBox(slot == Slot::A ? tr("Slot A") : tr("Slot B"));
m_slot_file_edit[slot] = new QLineEdit;
m_slot_open_button[slot] = new QPushButton(tr("&Open..."));
m_slot_create_button[slot] = new QPushButton(tr("&Create..."));
m_slot_table[slot] = new QTableWidget;
m_slot_table[slot]->setTabKeyNavigation(false);
m_slot_stat_label[slot] = new QLabel;

m_slot_table[slot]->setSelectionMode(QAbstractItemView::ExtendedSelection);
m_slot_table[slot]->setSelectionBehavior(QAbstractItemView::SelectRows);
m_slot_table[slot]->setSortingEnabled(true);
m_slot_table[slot]->horizontalHeader()->setHighlightSections(false);
m_slot_table[slot]->horizontalHeader()->setMinimumSectionSize(0);
m_slot_table[slot]->horizontalHeader()->setSortIndicatorShown(true);
m_slot_table[slot]->setColumnCount(COLUMN_COUNT);
m_slot_table[slot]->setHorizontalHeaderItem(COLUMN_INDEX_FILENAME,
new QTableWidgetItem(tr("Filename")));
m_slot_table[slot]->setHorizontalHeaderItem(COLUMN_INDEX_BANNER,
new QTableWidgetItem(tr("Banner")));
m_slot_table[slot]->setHorizontalHeaderItem(COLUMN_INDEX_TEXT,
new QTableWidgetItem(tr("Title")));
m_slot_table[slot]->setHorizontalHeaderItem(COLUMN_INDEX_ICON,
new QTableWidgetItem(tr("Icon")));
m_slot_table[slot]->setHorizontalHeaderItem(COLUMN_INDEX_BLOCKS,
new QTableWidgetItem(tr("Blocks")));
m_slot_table[slot]->setColumnWidth(COLUMN_INDEX_FILENAME, COLUMN_WIDTH_FILENAME);
m_slot_table[slot]->setColumnWidth(COLUMN_INDEX_BANNER, COLUMN_WIDTH_BANNER);
m_slot_table[slot]->setColumnWidth(COLUMN_INDEX_TEXT, COLUMN_WIDTH_TEXT);
m_slot_table[slot]->setColumnWidth(COLUMN_INDEX_ICON, COLUMN_WIDTH_ICON);
m_slot_table[slot]->setColumnWidth(COLUMN_INDEX_BLOCKS, COLUMN_WIDTH_BLOCKS);
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;
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_open_button[i], 0, 1);
slot_layout->addWidget(m_slot_create_button[i], 0, 2);
slot_layout->addWidget(m_slot_table[i], 1, 0, 1, 3);
slot_layout->addWidget(m_slot_stat_label[i], 2, 0);
slot_layout->addWidget(m_slot_file_edit[slot], 0, 0);
slot_layout->addWidget(m_slot_open_button[slot], 0, 1);
slot_layout->addWidget(m_slot_create_button[slot], 0, 2);
slot_layout->addWidget(m_slot_table[slot], 1, 0, 1, 3);
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);
Expand All @@ -179,7 +191,8 @@ void GCMemcardManager::CreateWidgets()
void GCMemcardManager::ConnectWidgets()
{
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,
[this] { ExportFiles(Memcard::SavefileFormat::GCI); });
connect(m_export_gcs_action, &QAction::triggered,
Expand All @@ -191,7 +204,7 @@ void GCMemcardManager::ConnectWidgets()
connect(m_copy_button, &QPushButton::clicked, this, &GCMemcardManager::CopyFiles);
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,
[this, slot](const QString& path) { SetSlotFile(slot, path); });
Expand All @@ -206,35 +219,34 @@ void GCMemcardManager::ConnectWidgets()

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) !=
ExpansionInterface::EXIDEVICE_MEMORYCARD)
if (Config::Get(Config::GetInfoForEXIDevice(slot)) !=
ExpansionInterface::EXIDeviceType::MemoryCard)
{
continue;
}

const QString path = QString::fromStdString(
Config::Get(i == 0 ? Config::MAIN_MEMCARD_A_PATH : Config::MAIN_MEMCARD_B_PATH));
SetSlotFile(i, path);
const QString path = QString::fromStdString(Config::Get(Config::GetInfoForMemcardPath(slot)));
SetSlotFile(slot, path);
}
}

void GCMemcardManager::SetActiveSlot(int slot)
void GCMemcardManager::SetActiveSlot(Slot slot)
{
for (int i = 0; i < SLOT_COUNT; i++)
m_slot_table[i]->setEnabled(i == slot);
for (Slot slot2 : MEMCARD_SLOTS)
m_slot_table[slot2]->setEnabled(slot2 == slot);

m_select_button->setText(slot == 0 ? tr("Switch to B") : tr("Switch to A"));
m_copy_button->setText(slot == 0 ? tr("Copy to B") : tr("Copy to A"));
m_select_button->setText(slot == Slot::A ? tr("Switch to B") : tr("Switch to A"));
m_copy_button->setText(slot == Slot::A ? tr("Copy to B") : tr("Copy to A"));

m_active_slot = slot;

UpdateSlotTable(slot);
UpdateActions();
}

void GCMemcardManager::UpdateSlotTable(int slot)
void GCMemcardManager::UpdateSlotTable(Slot slot)
{
m_slot_active_icons[slot].clear();

Expand Down Expand Up @@ -307,7 +319,7 @@ void GCMemcardManager::UpdateActions()
auto selection = m_slot_table[m_active_slot]->selectedItems();
bool have_selection = selection.count();
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_export_button->setEnabled(have_selection);
Expand All @@ -316,7 +328,7 @@ void GCMemcardManager::UpdateActions()
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());

Expand All @@ -337,14 +349,15 @@ void GCMemcardManager::SetSlotFile(int slot, QString path)
UpdateActions();
}

void GCMemcardManager::SetSlotFileInteractive(int slot)
void GCMemcardManager::SetSlotFileInteractive(Slot slot)
{
QString path = QDir::toNativeSeparators(DolphinFileDialog::getOpenFileName(
this,
slot == 0 ? tr("Set memory card file for Slot A") : tr("Set memory card file for Slot B"),
QString::fromStdString(File::GetUserPath(D_GCUSER_IDX)),
QStringLiteral("%1 (*.raw *.gcp);;%2 (*)")
.arg(tr("GameCube Memory Cards"), tr("All Files"))));
QString path = QDir::toNativeSeparators(
DolphinFileDialog::getOpenFileName(this,
slot == Slot::A ? tr("Set memory card file for Slot A") :
tr("Set memory card file for Slot B"),
QString::fromStdString(File::GetUserPath(D_GCUSER_IDX)),
QStringLiteral("%1 (*.raw *.gcp);;%2 (*)")
.arg(tr("GameCube Memory Cards"), tr("All Files"))));
if (!path.isEmpty())
m_slot_file_edit[slot]->setText(path);
}
Expand Down Expand Up @@ -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];
if (!card)
Expand Down Expand Up @@ -611,7 +624,7 @@ void GCMemcardManager::CopyFiles()
if (!source_card)
return;

auto& target_card = m_slot_memcard[!m_active_slot];
auto& target_card = m_slot_memcard[OtherSlot(m_active_slot)];
if (!target_card)
return;

Expand All @@ -627,7 +640,7 @@ void GCMemcardManager::CopyFiles()
return;
}

ImportFiles(!m_active_slot, savefiles);
ImportFiles(OtherSlot(m_active_slot), savefiles);
}

void GCMemcardManager::DeleteFiles()
Expand Down Expand Up @@ -677,7 +690,7 @@ void GCMemcardManager::FixChecksums()
}
}

void GCMemcardManager::CreateNewCard(int slot)
void GCMemcardManager::CreateNewCard(Slot slot)
{
GCMemcardCreateNewDialog dialog(this);
if (dialog.exec() == QDialog::Accepted)
Expand All @@ -687,7 +700,7 @@ void GCMemcardManager::CreateNewCard(int slot)
void GCMemcardManager::DrawIcons()
{
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];
const int row_count = table->rowCount();
Expand Down Expand Up @@ -737,7 +750,7 @@ void GCMemcardManager::DrawIcons()
++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];

Expand All @@ -753,7 +766,7 @@ QPixmap GCMemcardManager::GetBannerFromSaveFile(int file_index, int slot)
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];

Expand Down
41 changes: 22 additions & 19 deletions Source/Core/DolphinQt/GCMemcardManager.h
Expand Up @@ -12,6 +12,8 @@
#include <QDialog>

#include "Common/CommonTypes.h"
#include "Common/EnumMap.h"
#include "Core/HW/EXI/EXI.h"

namespace Memcard
{
Expand Down Expand Up @@ -53,26 +55,26 @@ class GCMemcardManager : public QDialog
void LoadDefaultMemcards();

void UpdateActions();
void UpdateSlotTable(int slot);
void SetSlotFile(int slot, QString path);
void SetSlotFileInteractive(int slot);
void SetActiveSlot(int slot);
void UpdateSlotTable(ExpansionInterface::Slot slot);
void SetSlotFile(ExpansionInterface::Slot slot, QString path);
void SetSlotFileInteractive(ExpansionInterface::Slot slot);
void SetActiveSlot(ExpansionInterface::Slot slot);

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 ImportFile();
void DeleteFiles();
void ExportFiles(Memcard::SavefileFormat format);
void FixChecksums();
void CreateNewCard(int slot);
void CreateNewCard(ExpansionInterface::Slot slot);
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
QPushButton* m_select_button;
Expand All @@ -87,17 +89,18 @@ class GCMemcardManager : public QDialog
QPushButton* m_fix_checksums_button;

// Slots
static constexpr int SLOT_COUNT = 2;
std::array<std::map<u8, IconAnimationData>, SLOT_COUNT> m_slot_active_icons;
std::array<std::unique_ptr<Memcard::GCMemcard>, SLOT_COUNT> m_slot_memcard;
std::array<QGroupBox*, SLOT_COUNT> m_slot_group;
std::array<QLineEdit*, SLOT_COUNT> m_slot_file_edit;
std::array<QPushButton*, SLOT_COUNT> m_slot_open_button;
std::array<QPushButton*, SLOT_COUNT> m_slot_create_button;
std::array<QTableWidget*, SLOT_COUNT> m_slot_table;
std::array<QLabel*, SLOT_COUNT> m_slot_stat_label;

int m_active_slot;
Common::EnumMap<std::map<u8, IconAnimationData>, ExpansionInterface::MAX_MEMCARD_SLOT>
m_slot_active_icons;
Common::EnumMap<std::unique_ptr<Memcard::GCMemcard>, ExpansionInterface::MAX_MEMCARD_SLOT>
m_slot_memcard;
Common::EnumMap<QGroupBox*, ExpansionInterface::MAX_MEMCARD_SLOT> m_slot_group;
Common::EnumMap<QLineEdit*, ExpansionInterface::MAX_MEMCARD_SLOT> m_slot_file_edit;
Common::EnumMap<QPushButton*, ExpansionInterface::MAX_MEMCARD_SLOT> m_slot_open_button;
Common::EnumMap<QPushButton*, ExpansionInterface::MAX_MEMCARD_SLOT> m_slot_create_button;
Common::EnumMap<QTableWidget*, ExpansionInterface::MAX_MEMCARD_SLOT> m_slot_table;
Common::EnumMap<QLabel*, ExpansionInterface::MAX_MEMCARD_SLOT> m_slot_stat_label;

ExpansionInterface::Slot m_active_slot;
u64 m_current_frame = 0;

QDialogButtonBox* m_button_box;
Expand Down
21 changes: 11 additions & 10 deletions Source/Core/DolphinQt/GameList/GameList.cpp
Expand Up @@ -47,6 +47,7 @@
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/HW/DVD/DVDInterface.h"
#include "Core/HW/EXI/EXI.h"
#include "Core/HW/EXI/EXI_Device.h"
#include "Core/HW/WiiSave.h"
#include "Core/WiiUtils.h"
Expand Down Expand Up @@ -661,21 +662,22 @@ void GameList::OpenGCSaveFolder()

bool found = false;

for (int i = 0; i < 2; i++)
using ExpansionInterface::Slot;

for (Slot slot : ExpansionInterface::MEMCARD_SLOTS)
{
QUrl url;
const ExpansionInterface::TEXIDevices current_exi_device =
Config::Get(Config::GetInfoForEXIDevice(i));
const ExpansionInterface::EXIDeviceType current_exi_device =
Config::Get(Config::GetInfoForEXIDevice(slot));
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(),
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) :
Config::Get(Config::MAIN_GCI_FOLDER_B_PATH_OVERRIDE);
std::string override_path = Config::Get(Config::GetInfoForGCIPathOverride(slot));

if (!override_path.empty())
path = override_path;
Expand All @@ -691,10 +693,9 @@ void GameList::OpenGCSaveFolder()
}
break;
}
case ExpansionInterface::EXIDEVICE_MEMORYCARD:
case ExpansionInterface::EXIDeviceType::MemoryCard:
{
std::string memcard_path = i == 0 ? Config::Get(Config::MAIN_MEMCARD_A_PATH) :
Config::Get(Config::MAIN_MEMCARD_B_PATH);
std::string memcard_path = Config::Get(Config::GetInfoForMemcardPath(slot));

std::string memcard_dir;

Expand Down
288 changes: 144 additions & 144 deletions Source/Core/DolphinQt/Settings/GameCubePane.cpp

Large diffs are not rendered by default.

13 changes: 9 additions & 4 deletions Source/Core/DolphinQt/Settings/GameCubePane.h
Expand Up @@ -9,6 +9,9 @@

#include <QWidget>

#include "Common/EnumMap.h"
#include "Core/HW/EXI/EXI.h"

class QCheckBox;
class QComboBox;
class QLineEdit;
Expand All @@ -31,9 +34,11 @@ class GameCubePane : public QWidget

void OnEmulationStateChanged();

void UpdateButton(int slot);
void OnConfigPressed(int slot);
void UpdateButton(ExpansionInterface::Slot slot);
void OnConfigPressed(ExpansionInterface::Slot slot);

void BrowseMemcard(ExpansionInterface::Slot slot);
void BrowseAGPRom(ExpansionInterface::Slot slot);
void BrowseGBABios();
void BrowseGBARom(size_t index);
void SaveRomPathChanged();
Expand All @@ -42,8 +47,8 @@ class GameCubePane : public QWidget
QCheckBox* m_skip_main_menu;
QComboBox* m_language_combo;

QPushButton* m_slot_buttons[3];
QComboBox* m_slot_combos[3];
Common::EnumMap<QPushButton*, ExpansionInterface::MAX_SLOT> m_slot_buttons;
Common::EnumMap<QComboBox*, ExpansionInterface::MAX_SLOT> m_slot_combos;

QCheckBox* m_gba_threads;
QCheckBox* m_gba_save_rom_path;
Expand Down