Added support for AGP. Original patch by GreyRogue.

This commit is contained in:
skidau 2015-01-20 23:41:46 +11:00
parent 18cee2dcc7
commit a7299a7fff
11 changed files with 410 additions and 37 deletions

View File

@ -91,6 +91,7 @@ set(SRCS ActionReplay.cpp
HW/EXI.cpp
HW/EXI_Device.cpp
HW/EXI_DeviceAD16.cpp
HW/EXI_DeviceAGP.cpp
HW/EXI_DeviceAMBaseboard.cpp
HW/EXI_DeviceEthernet.cpp
HW/EXI_DeviceGecko.cpp

View File

@ -329,6 +329,8 @@ void SConfig::SaveCoreSettings(IniFile& ini)
core->Set("Latency", m_LocalCoreStartupParameter.iLatency);
core->Set("MemcardAPath", m_strMemoryCardA);
core->Set("MemcardBPath", m_strMemoryCardB);
core->Set("AgpCartAPath", m_strGbaCartA);
core->Set("AgpCartBPath", m_strGbaCartB);
core->Set("SlotA", m_EXIDevice[0]);
core->Set("SlotB", m_EXIDevice[1]);
core->Set("SerialPort1", m_EXIDevice[2]);
@ -559,6 +561,8 @@ void SConfig::LoadCoreSettings(IniFile& ini)
core->Get("Latency", &m_LocalCoreStartupParameter.iLatency, 2);
core->Get("MemcardAPath", &m_strMemoryCardA);
core->Get("MemcardBPath", &m_strMemoryCardB);
core->Get("AgpCartAPath", &m_strGbaCartA);
core->Get("AgpCartBPath", &m_strGbaCartB);
core->Get("SlotA", (int*)&m_EXIDevice[0], EXIDEVICE_MEMORYCARD);
core->Get("SlotB", (int*)&m_EXIDevice[1], EXIDEVICE_NONE);
core->Get("SerialPort1", (int*)&m_EXIDevice[2], EXIDEVICE_NONE);

View File

@ -41,6 +41,8 @@ struct SConfig : NonCopyable
std::string m_strMemoryCardA;
std::string m_strMemoryCardB;
std::string m_strGbaCartA;
std::string m_strGbaCartB;
TEXIDevices m_EXIDevice[3];
SIDevices m_SIDevice[4];
std::string m_bba_mac;

View File

@ -122,6 +122,7 @@
<ClCompile Include="HW\EXI_Channel.cpp" />
<ClCompile Include="HW\EXI_Device.cpp" />
<ClCompile Include="HW\EXI_DeviceAD16.cpp" />
<ClCompile Include="HW\EXI_DeviceAGP.cpp" />
<ClCompile Include="HW\EXI_DeviceAMBaseboard.cpp" />
<ClCompile Include="HW\EXI_DeviceEthernet.cpp" />
<ClCompile Include="HW\EXI_DeviceGecko.cpp" />
@ -330,6 +331,7 @@
<ClInclude Include="HW\EXI_Channel.h" />
<ClInclude Include="HW\EXI_Device.h" />
<ClInclude Include="HW\EXI_DeviceAD16.h" />
<ClInclude Include="HW\EXI_DeviceAGP.h" />
<ClInclude Include="HW\EXI_DeviceAMBaseboard.h" />
<ClInclude Include="HW\EXI_DeviceEthernet.h" />
<ClInclude Include="HW\EXI_DeviceGecko.h" />

View File

@ -385,6 +385,9 @@
<ClCompile Include="HW\EXI_DeviceAD16.cpp">
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
</ClCompile>
<ClCompile Include="HW\EXI_DeviceAGP.cpp">
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
</ClCompile>
<ClCompile Include="HW\EXI_DeviceAMBaseboard.cpp">
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
</ClCompile>
@ -914,6 +917,9 @@
<ClInclude Include="HW\EXI_DeviceAD16.h">
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
</ClInclude>
<ClInclude Include="HW\EXI_DeviceAGP.h">
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
</ClInclude>
<ClInclude Include="HW\EXI_DeviceAMBaseboard.h">
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
</ClInclude>
@ -1229,4 +1235,4 @@
<ItemGroup>
<Text Include="CMakeLists.txt" />
</ItemGroup>
</Project>
</Project>

View File

@ -7,6 +7,7 @@
#include "Core/Core.h"
#include "Core/HW/EXI_Device.h"
#include "Core/HW/EXI_DeviceAD16.h"
#include "Core/HW/EXI_DeviceAGP.h"
#include "Core/HW/EXI_DeviceAMBaseboard.h"
#include "Core/HW/EXI_DeviceEthernet.h"
#include "Core/HW/EXI_DeviceGecko.h"
@ -84,6 +85,7 @@ public:
u32 ImmRead (u32 size) override {INFO_LOG(EXPANSIONINTERFACE, "EXI DUMMY %s ImmRead", m_strName.c_str()); return 0;}
void DMAWrite(u32 addr, u32 size) override {INFO_LOG(EXPANSIONINTERFACE, "EXI DUMMY %s DMAWrite: %08x bytes, from %08x to device", m_strName.c_str(), size, addr);}
void DMARead (u32 addr, u32 size) override {INFO_LOG(EXPANSIONINTERFACE, "EXI DUMMY %s DMARead: %08x bytes, from device to %08x", m_strName.c_str(), size, addr);}
bool IsPresent() override { return true; }
};
@ -129,6 +131,10 @@ IEXIDevice* EXIDevice_Create(TEXIDevices device_type, const int channel_num)
result = new CEXIGecko();
break;
case EXIDEVICE_AGP:
result = new CEXIAgp(channel_num);
break;
case EXIDEVICE_NONE:
default:
result = new IEXIDevice();

View File

@ -19,6 +19,7 @@ enum TEXIDevices
EXIDEVICE_GECKO,
EXIDEVICE_MEMORYCARDFOLDER, // Only used when creating a device by EXIDevice_Create
// Converted to EXIDEVICE_MEMORYCARD internally
EXIDEVICE_AGP,
EXIDEVICE_NONE = (u8)-1
};

View File

@ -0,0 +1,253 @@
// Copyright 2013 Dolphin Emulator Project
// Licensed under GPLv2
// Refer to the license.txt file included.
#include "Common/FileUtil.h"
#include "Common/MemoryUtil.h"
#include "Common/StdMakeUnique.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/HW/EXI_Device.h"
#include "Core/HW/EXI_DeviceAGP.h"
#include "Core/HW/Memmap.h"
CEXIAgp::CEXIAgp(int index) :
m_slot(index)
{
// Create the ROM
m_pHashArray = (u8*)AllocateMemoryPages(HASH_SIZE);
m_rom_size = 0;
LoadRom();
m_address = 0;
m_rom_hash_loaded = false;
}
CEXIAgp::~CEXIAgp()
{
m_pROM = nullptr;
m_pHashArray = nullptr;
}
void CEXIAgp::DoHash(u8* data, u32 size)
{
for (u32 it = 0; it < size; it++)
{
m_hash = m_hash ^ data[it];
m_hash = m_pHashArray[m_hash];
}
}
void CEXIAgp::LoadRom()
{
// Load whole ROM dump
std::string path;
std::string filename;
std::string ext;
std::string gbapath;
SplitPath(m_slot == 0 ? SConfig::GetInstance().m_strGbaCartA : SConfig::GetInstance().m_strGbaCartB, &path, &filename, &ext);
gbapath = path + filename;
LoadFileToROM(gbapath + ext);
INFO_LOG(EXPANSIONINTERFACE, "Loaded gba rom: %s card: %d", gbapath.c_str(), m_slot);
LoadFileToEEPROM(gbapath + ".sav");
INFO_LOG(EXPANSIONINTERFACE, "Loaded gba sav: %s card: %d", gbapath.c_str(), m_slot);
}
void CEXIAgp::LoadFileToROM(std::string filename)
{
File::IOFile pStream(filename, "rb");
if (pStream)
{
u64 filesize = pStream.GetSize();
m_rom_size = filesize & 0xFFFFFFFF;
m_rom_mask = (m_rom_size - 1);
m_pROM = (u8*)AllocateMemoryPages(m_rom_size);
pStream.ReadBytes(m_pROM, filesize);
}
else
{
// dummy rom data
m_pROM = (u8*)AllocateMemoryPages(0x2000);
}
}
void CEXIAgp::LoadFileToEEPROM(std::string filename)
{
File::IOFile pStream(filename, "rb");
if (pStream)
{
u64 filesize = pStream.GetSize();
m_eeprom_size = filesize & 0xFFFFFFFF;
m_eeprom_mask = (m_eeprom_size - 1);
m_pEEPROM = (u8*)AllocateMemoryPages(m_eeprom_size);
pStream.ReadBytes(m_pEEPROM, filesize);
}
}
void CEXIAgp::LoadHash()
{
if (!m_rom_hash_loaded && m_rom_size > 0)
{
for (int i = 0; i < 0x100; i++)
{
m_pHashArray[i] = Memory::ReadUnchecked_U8(0x0017e908 + i);
}
m_rom_hash_loaded = true;
}
}
u32 CEXIAgp::ImmRead(u32 _uSize)
{
// We don't really care about _uSize
(void)_uSize;
u32 uData = 0;
u8 RomVal1, RomVal2, RomVal3, RomVal4;
switch (m_currrent_cmd)
{
case 0xAE000000:
uData = 0x5AAA5517; // 17 is precalculated hash
m_currrent_cmd = 0;
break;
case 0xAE010000:
uData = (m_return_pos == 0) ? 0x01020304 : 0xF0020304; // F0 is precalculated hash, 020304 is left over
if (m_return_pos == 1)
m_currrent_cmd = 0;
else
m_return_pos = 1;
break;
case 0xAE020000:
if (m_rw_offset == 0x8000000)
{
RomVal1 = 0x0;
RomVal2 = 0x1;
}
else
{
RomVal1 = m_pROM[m_rw_offset++];
RomVal2 = m_pROM[m_rw_offset++];
LoadHash();
}
DoHash(&RomVal2, 1);
DoHash(&RomVal1, 1);
uData = (RomVal2 << 24) | (RomVal1 << 16) | (m_hash << 8);
m_currrent_cmd = 0;
break;
case 0xAE030000:
if (_uSize == 1)
{
uData = 0xFF000000;
m_currrent_cmd = 0;
}
else
{
RomVal1 = m_pROM[m_rw_offset++];
RomVal2 = m_pROM[m_rw_offset++];
RomVal3 = m_pROM[m_rw_offset++];
RomVal4 = m_pROM[m_rw_offset++];
LoadHash();
DoHash(&RomVal2, 1);
DoHash(&RomVal1, 1);
DoHash(&RomVal4, 1);
DoHash(&RomVal3, 1);
uData = (RomVal2 << 24) | (RomVal1 << 16) | (RomVal4 << 8) | (RomVal3);
}
break;
case 0xAE0B0000:
RomVal1 = m_eeprom_pos < 4 ? 0xA : (((u64*)m_pEEPROM)[(m_eeprom_cmd >> 1) & 0x3F] >> (m_eeprom_pos - 4)) & 0x1;
RomVal2 = 0;
DoHash(&RomVal2, 1);
DoHash(&RomVal1, 1);
uData = (RomVal2 << 24) | (RomVal1 << 16) | (m_hash << 8);
m_eeprom_pos++;
m_currrent_cmd = 0;
break;
case 0xAE0C0000:
uData = m_hash << 24;
m_currrent_cmd = 0;
break;
default:
uData = 0x0;
m_currrent_cmd = 0;
break;
}
INFO_LOG(EXPANSIONINTERFACE, "AGP read %x", uData);
return uData;
}
void CEXIAgp::ImmWrite(u32 _uData, u32 _uSize)
{
if ((_uSize == 1) && ((_uData & 0xFF000000) == 0))
return;
u8 HashCmd;
u64 Mask;
INFO_LOG(EXPANSIONINTERFACE, "AGP command %x", _uData);
switch (m_currrent_cmd)
{
case 0xAE020000:
case 0xAE030000:
m_rw_offset = ((_uData & 0xFFFFFF00) >> 7) & m_rom_mask;
m_return_pos = 0;
HashCmd = (_uData & 0xFF000000) >> 24;
DoHash(&HashCmd, 1);
HashCmd = (_uData & 0x00FF0000) >> 16;
DoHash(&HashCmd, 1);
HashCmd = (_uData & 0x0000FF00) >> 8;
DoHash(&HashCmd, 1);
break;
case 0xAE0C0000:
if ((m_eeprom_pos < 0x8) || (m_eeprom_pos == ((m_eeprom_cmd & EE_READ) ? 0x8 : 0x48)))
{
Mask = (u64)(1 << (0x8-(m_eeprom_pos > 0x8 ? 0x8 : m_eeprom_pos)));
if ((_uData >> 16) & 0x1)
m_eeprom_cmd |= Mask;
else
m_eeprom_cmd &= ~Mask;
if (m_eeprom_pos == 0x48)
((u64*)(m_pEEPROM))[(m_eeprom_cmd >> 1) & 0x3F] = m_eeprom_data;
}
else
{
Mask = (u64)(1 << (0x47 - m_eeprom_pos));
if ((_uData >> 16) & 0x1)
m_eeprom_data |= Mask;
else
m_eeprom_data &= ~Mask;
}
m_eeprom_pos++;
m_return_pos = 0;
HashCmd = (_uData & 0xFF000000) >> 24;
DoHash(&HashCmd, 1);
HashCmd = (_uData & 0x00FF0000) >> 16;
DoHash(&HashCmd, 1);
break;
case 0xAE0B0000:
break;
case 0xAE000000:
case 0xAE010000:
case 0xAE090000:
case 0xAE0A0000:
default:
m_eeprom_pos = 0;
m_currrent_cmd = _uData;
m_return_pos = 0;
m_hash = 0xFF;
HashCmd = (_uData & 0x00FF0000) >> 16;
DoHash(&HashCmd, 1);
break;
}
}
void CEXIAgp::DoState(PointerWrap &p)
{
p.Do(m_position);
p.Do(m_address);
p.Do(m_rw_offset);
p.Do(m_hash);
}

View File

@ -0,0 +1,61 @@
// Copyright 2013 Dolphin Emulator Project
// Licensed under GPLv2
// Refer to the license.txt file included.
#pragma once
#include <deque>
#include <queue>
#include "Core/HW/EXI_Device.h"
class CEXIAgp
: public IEXIDevice
{
public:
CEXIAgp(const int index);
virtual ~CEXIAgp() override;
bool IsPresent() override { return true; }
void ImmWrite(u32 _uData, u32 _uSize) override;
u32 ImmRead(u32 _uSize) override;
void DoState(PointerWrap &p) override;
private:
enum
{
HASH_SIZE = 256,
HASH_MASK = (HASH_SIZE - 1),
EE_READ = 0x80
};
int m_slot;
//! ROM
u32 m_rom_size = 0;
u32 m_rom_mask = 0;
u32 m_eeprom_size = 0;
u32 m_eeprom_mask = 0;
u8* m_pROM;
u8* m_pEEPROM;
u8* m_pHashArray;
//! Helper
u32 m_position = 0;
u32 m_address = 0;
u32 m_rw_offset = 0;
u64 m_eeprom_data = 0;
u8 m_eeprom_pos = 0;
u16 m_eeprom_cmd = 0;
void LoadFileToROM(std::string filename);
void LoadFileToEEPROM(std::string filename);
void LoadHash();
void LoadRom();
void DoHash(u8* data, u32 size);
u8 m_hash = 0;
u32 m_currrent_cmd = 0;
u32 m_return_pos = 0;
bool m_rom_hash_loaded = false;
};

View File

@ -111,6 +111,7 @@ static const wxLanguage langIds[] =
#define EXIDEV_MEMDIR_STR _trans("GCI Folder")
#define EXIDEV_MIC_STR _trans("Mic")
#define EXIDEV_BBA_STR "BBA"
#define EXIDEV_AGP_STR "Advance Game Port"
#define EXIDEV_AM_BB_STR _trans("AM-Baseboard")
#define EXIDEV_GECKO_STR "USBGecko"
@ -382,8 +383,9 @@ void CConfigMain::InitializeGUIValues()
SlotDevices.Add(_(DEV_NONE_STR));
SlotDevices.Add(_(DEV_DUMMY_STR));
SlotDevices.Add(_(EXIDEV_MEMCARD_STR));
SlotDevices.Add(_(EXIDEV_GECKO_STR));
SlotDevices.Add(_(EXIDEV_MEMDIR_STR));
SlotDevices.Add(_(EXIDEV_GECKO_STR));
SlotDevices.Add(_(EXIDEV_AGP_STR));
#if HAVE_PORTAUDIO
SlotDevices.Add(_(EXIDEV_MIC_STR));
@ -415,10 +417,16 @@ void CConfigMain::InitializeGUIValues()
isMemcard = GCEXIDevice[i]->SetStringSelection(SlotDevices[2]);
break;
case EXIDEVICE_MEMORYCARDFOLDER:
GCEXIDevice[i]->SetStringSelection(SlotDevices[3]);
break;
case EXIDEVICE_GECKO:
GCEXIDevice[i]->SetStringSelection(SlotDevices[4]);
break;
case EXIDEVICE_AGP:
isMemcard = GCEXIDevice[i]->SetStringSelection(SlotDevices[5]);
break;
case EXIDEVICE_MIC:
GCEXIDevice[i]->SetStringSelection(SlotDevices[5]);
GCEXIDevice[i]->SetStringSelection(SlotDevices[6]);
break;
case EXIDEVICE_ETH:
GCEXIDevice[i]->SetStringSelection(SP1Devices[2]);
@ -426,9 +434,6 @@ void CConfigMain::InitializeGUIValues()
case EXIDEVICE_AM_BASEBOARD:
GCEXIDevice[i]->SetStringSelection(SP1Devices[3]);
break;
case EXIDEVICE_GECKO:
GCEXIDevice[i]->SetStringSelection(SlotDevices[3]);
break;
case EXIDEVICE_DUMMY:
default:
GCEXIDevice[i]->SetStringSelection(SlotDevices[1]);
@ -1015,68 +1020,98 @@ void CConfigMain::GCSettingsChanged(wxCommandEvent& event)
ChooseEXIDevice(event.GetString(), exidevice);
break;
case ID_GC_EXIDEVICE_SLOTA_PATH:
ChooseMemcardPath(SConfig::GetInstance().m_strMemoryCardA, true);
ChooseSlotPath(true, SConfig::GetInstance().m_EXIDevice[0]);
break;
case ID_GC_EXIDEVICE_SLOTB_PATH:
ChooseMemcardPath(SConfig::GetInstance().m_strMemoryCardB, false);
ChooseSlotPath(false, SConfig::GetInstance().m_EXIDevice[0]);
break;
}
}
void CConfigMain::ChooseMemcardPath(std::string& strMemcard, bool isSlotA)
void CConfigMain::ChooseSlotPath(bool isSlotA, TEXIDevices device_type)
{
bool memcard = (device_type == EXIDEVICE_MEMORYCARD);
std::string path;
std::string cardname;
std::string ext;
std::string pathA = SConfig::GetInstance().m_strMemoryCardA;
std::string pathB = SConfig::GetInstance().m_strMemoryCardB;
if (!memcard)
{
pathA = SConfig::GetInstance().m_strGbaCartA;
pathB = SConfig::GetInstance().m_strGbaCartB;
}
SplitPath(isSlotA ? pathA : pathB, &path, &cardname, &ext);
std::string filename = WxStrToStr(wxFileSelector(
_("Choose a file to open"),
StrToWxStr(File::GetUserPath(D_GCUSER_IDX)),
isSlotA ? GC_MEMCARDA : GC_MEMCARDB,
wxEmptyString,
_("GameCube Memory Cards (*.raw,*.gcp)") + "|*.raw;*.gcp"));
StrToWxStr(path),
StrToWxStr(cardname),
StrToWxStr(ext),
memcard ? _("GameCube Memory Cards (*.raw,*.gcp)") + "|*.raw;*.gcp" : _("Game Boy Advance Carts (*.gba)") + "|*.gba"));
if (!filename.empty())
{
if (File::Exists(filename))
{
GCMemcard memorycard(filename);
if (!memorycard.IsValid())
if (memcard)
{
GCMemcard memorycard(filename);
if (!memorycard.IsValid())
{
WxUtils::ShowErrorDialog(wxString::Format(_("Cannot use that file as a memory card.\n%s\n" \
"is not a valid gamecube memory card file"), filename.c_str()));
return;
}
}
else
{
WxUtils::ShowErrorDialog(wxString::Format(_("Cannot use that file as a memory card.\n%s\n" \
"is not a valid gamecube memory card file"), filename.c_str()));
return;
}
}
#ifdef _WIN32
if (!strncmp(File::GetExeDirectory().c_str(), filename.c_str(), File::GetExeDirectory().size()))
#ifdef _WIN32
if (!strncmp(File::GetExeDirectory().c_str(), filename.c_str(), File::GetExeDirectory().size()))
{
// If the Exe Directory Matches the prefix of the filename, we still need to verify
// that the next character is a directory separator character, otherwise we may create an invalid path
char next_char = filename.at(File::GetExeDirectory().size()) + 1;
if (next_char == '/' || next_char == '\\')
{
// If the Exe Directory Matches the prefix of the filename, we still need to verify
// that the next character is a directory separator character, otherwise we may create an invalid path
char next_char = filename.at(File::GetExeDirectory().size())+1;
if (next_char == '/' || next_char == '\\')
{
filename.erase(0, File::GetExeDirectory().size() +1);
filename = "./" + filename;
}
filename.erase(0, File::GetExeDirectory().size() + 1);
filename = "./" + filename;
}
#endif
}
#endif
// also check that the path isn't used for the other memcard...
if (filename.compare(isSlotA ? SConfig::GetInstance().m_strMemoryCardB
: SConfig::GetInstance().m_strMemoryCardA) != 0)
if (filename.compare(isSlotA ? pathB : pathA) != 0)
{
strMemcard = filename;
if (memcard)
{
if (isSlotA)
SConfig::GetInstance().m_strMemoryCardA = filename;
else
SConfig::GetInstance().m_strMemoryCardB = filename;
}
else
{
if (isSlotA)
SConfig::GetInstance().m_strGbaCartA = filename;
else
SConfig::GetInstance().m_strGbaCartB = filename;
}
if (Core::IsRunning())
{
// Change memcard to the new file
ExpansionInterface::ChangeDevice(
isSlotA ? 0 : 1, // SlotA: channel 0, SlotB channel 1
EXIDEVICE_MEMORYCARD,
device_type,
0); // SP1 is device 2, slots are device 0
}
}
else
{
WxUtils::ShowErrorDialog(_("Cannot use that file as a memory card.\n"
"Are you trying to use the same file in both slots?"));
WxUtils::ShowErrorDialog(_("Are you trying to use the same file in both slots?"));
}
}
}
@ -1093,6 +1128,8 @@ void CConfigMain::ChooseEXIDevice(wxString deviceName, int deviceNum)
tempType = EXIDEVICE_MIC;
else if (!deviceName.compare(EXIDEV_BBA_STR))
tempType = EXIDEVICE_ETH;
else if (!deviceName.compare(EXIDEV_AGP_STR))
tempType = EXIDEVICE_AGP;
else if (!deviceName.compare(_(EXIDEV_AM_BB_STR)))
tempType = EXIDEVICE_AM_BASEBOARD;
else if (!deviceName.compare(EXIDEV_GECKO_STR))
@ -1102,8 +1139,8 @@ void CConfigMain::ChooseEXIDevice(wxString deviceName, int deviceNum)
else
tempType = EXIDEVICE_DUMMY;
// Gray out the memcard path button if we're not on a memcard
if (tempType == EXIDEVICE_MEMORYCARD)
// Gray out the memcard path button if we're not on a memcard or AGP
if (tempType == EXIDEVICE_MEMORYCARD || tempType == EXIDEVICE_AGP)
GCMemcardPath[deviceNum]->Enable();
else if (deviceNum == 0 || deviceNum == 1)
GCMemcardPath[deviceNum]->Disable();

View File

@ -254,7 +254,7 @@ private:
void AddAudioBackends();
void GCSettingsChanged(wxCommandEvent& event);
void ChooseMemcardPath(std::string& strMemcard, bool isSlotA);
void ChooseSlotPath(bool isSlotA, TEXIDevices device_type);
void ChooseEXIDevice(wxString deviceName, int deviceNum);
void WiiSettingsChanged(wxCommandEvent& event);