mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2024-11-15 05:47:56 -07:00
Stage1: Introducing MotionPlus as emulated extension, allowing us to boot games that require the MotionPlus itself.
I also already implemented most of the needed data structures and datareport handling for the motionplus-nunchuk passthrough mode, which I'll add up next as well. Wii Sports Resort is now bootable by just using an emulated wiimote (only working under the old wiimote plugin atm, I asked billiard to port my commit into his plugin since he knows his plugin better than me and its less work for him than for me, I kept most parts of my code in modules to simplify that task). Upcoming stage2: Faking the motionplus on the fly on real wiimotes, that means you will be able to play, e.g. Redsteel2 with a real wiimote and nunchuk or wii sports resort with just your real wiimote. and nunchuk, but w/o the need of having a real motionpluscontroller connected. The new Zelda 2010 will benefit by that, since it will require a motionplus controller. git-svn-id: https://dolphin-emu.googlecode.com/svn/trunk@5438 8ced0084-cf51-0410-be5f-012b33b47a6e
This commit is contained in:
parent
f6ce87765f
commit
7742e1a6dd
@ -167,7 +167,6 @@ void WiimoteBasicConfigDialog::CreateGUIControls()
|
|||||||
m_UprightWiimote[i]->SetToolTip(wxT("Treat the upright position as neutral"));
|
m_UprightWiimote[i]->SetToolTip(wxT("Treat the upright position as neutral"));
|
||||||
|
|
||||||
m_WiiMotionPlusConnected[i] = new wxCheckBox(m_Controller[i], IDC_MOTIONPLUSCONNECTED, wxT("Wii Motion Plus Connected"));
|
m_WiiMotionPlusConnected[i] = new wxCheckBox(m_Controller[i], IDC_MOTIONPLUSCONNECTED, wxT("Wii Motion Plus Connected"));
|
||||||
m_WiiMotionPlusConnected[i]->Enable(false);
|
|
||||||
|
|
||||||
m_Extension[i] = new wxChoice(m_Controller[i], IDC_EXTCONNECTED, wxDefaultPosition, wxDefaultSize, arrayStringFor_extension, 0, wxDefaultValidator);
|
m_Extension[i] = new wxChoice(m_Controller[i], IDC_EXTCONNECTED, wxDefaultPosition, wxDefaultSize, arrayStringFor_extension, 0, wxDefaultValidator);
|
||||||
|
|
||||||
@ -382,7 +381,7 @@ void WiimoteBasicConfigDialog::DoUseReal()
|
|||||||
|
|
||||||
// Are we using an extension now? The report that it's removed, then reconnected.
|
// Are we using an extension now? The report that it's removed, then reconnected.
|
||||||
bool UsingExtension = false;
|
bool UsingExtension = false;
|
||||||
if (WiiMoteEmu::WiiMapping[m_Page].iExtensionConnected != WiiMoteEmu::EXT_NONE)
|
if ((WiiMoteEmu::WiiMapping[m_Page].iExtensionConnected != WiiMoteEmu::EXT_NONE) || ( WiiMoteEmu::WiiMapping[m_Page].bMotionPlusConnected))
|
||||||
UsingExtension = true;
|
UsingExtension = true;
|
||||||
|
|
||||||
DEBUG_LOG(WIIMOTE, "DoUseReal() Connect extension: %i", !UsingExtension);
|
DEBUG_LOG(WIIMOTE, "DoUseReal() Connect extension: %i", !UsingExtension);
|
||||||
@ -466,6 +465,11 @@ void WiimoteBasicConfigDialog::GeneralSettingsChanged(wxCommandEvent& event)
|
|||||||
break;
|
break;
|
||||||
case IDC_MOTIONPLUSCONNECTED:
|
case IDC_MOTIONPLUSCONNECTED:
|
||||||
WiiMoteEmu::WiiMapping[m_Page].bMotionPlusConnected = m_WiiMotionPlusConnected[m_Page]->IsChecked();
|
WiiMoteEmu::WiiMapping[m_Page].bMotionPlusConnected = m_WiiMotionPlusConnected[m_Page]->IsChecked();
|
||||||
|
DoExtensionConnectedDisconnected(WiiMoteEmu::EXT_NONE);
|
||||||
|
if (g_EmulatorRunning)
|
||||||
|
SLEEP(25);
|
||||||
|
WiiMoteEmu::UpdateExtRegisterBlocks(m_Page);
|
||||||
|
DoExtensionConnectedDisconnected();
|
||||||
break;
|
break;
|
||||||
case IDC_WIIAUTORECONNECT:
|
case IDC_WIIAUTORECONNECT:
|
||||||
WiiMoteEmu::WiiMapping[m_Page].bWiiAutoReconnect = m_WiiAutoReconnect[m_Page]->IsChecked();
|
WiiMoteEmu::WiiMapping[m_Page].bWiiAutoReconnect = m_WiiAutoReconnect[m_Page]->IsChecked();
|
||||||
|
@ -755,7 +755,7 @@ void WiimotePadConfigDialog::CreatePadGUIControls()
|
|||||||
m_gWiimote[i]->Add(m_sWmVertRight[i], 0, (wxLEFT | wxRIGHT | wxDOWN), 1);
|
m_gWiimote[i]->Add(m_sWmVertRight[i], 0, (wxLEFT | wxRIGHT | wxDOWN), 1);
|
||||||
|
|
||||||
// Extension Mapping
|
// Extension Mapping
|
||||||
if(WiiMoteEmu::WiiMapping[i].iExtensionConnected == WiiMoteEmu::EXT_NUNCHUCK)
|
if(WiiMoteEmu::WiiMapping[i].iExtensionConnected == WiiMoteEmu::EXT_NUNCHUK)
|
||||||
{
|
{
|
||||||
// Stick controls
|
// Stick controls
|
||||||
m_NunchuckTextStick[i] = new wxStaticText(m_Controller[i], wxID_ANY, wxT("Stick"));
|
m_NunchuckTextStick[i] = new wxStaticText(m_Controller[i], wxID_ANY, wxT("Stick"));
|
||||||
@ -763,11 +763,11 @@ void WiimotePadConfigDialog::CreatePadGUIControls()
|
|||||||
|
|
||||||
for (int x = 0; x <= IDB_NC_SHAKE - IDB_NC_Z; x++)
|
for (int x = 0; x <= IDB_NC_SHAKE - IDB_NC_Z; x++)
|
||||||
{
|
{
|
||||||
m_statictext_NunChuck[x][i] = new wxStaticText(m_Controller[i], wxID_ANY, ncText[x]);
|
m_statictext_Nunchuk[x][i] = new wxStaticText(m_Controller[i], wxID_ANY, ncText[x]);
|
||||||
m_Button_NunChuck[x][i] = new wxButton(m_Controller[i], x + IDB_NC_Z, wxEmptyString, wxDefaultPosition, wxSize(BtW, BtH));
|
m_Button_NunChuck[x][i] = new wxButton(m_Controller[i], x + IDB_NC_Z, wxEmptyString, wxDefaultPosition, wxSize(BtW, BtH));
|
||||||
m_Button_NunChuck[x][i]->SetFont(m_SmallFont);
|
m_Button_NunChuck[x][i]->SetFont(m_SmallFont);
|
||||||
m_Sizer_NunChuck[x][i] = new wxBoxSizer(wxHORIZONTAL);
|
m_Sizer_NunChuck[x][i] = new wxBoxSizer(wxHORIZONTAL);
|
||||||
m_Sizer_NunChuck[x][i]->Add(m_statictext_NunChuck[x][i], 0, (wxUP), 4);
|
m_Sizer_NunChuck[x][i]->Add(m_statictext_Nunchuk[x][i], 0, (wxUP), 4);
|
||||||
m_Sizer_NunChuck[x][i]->Add(m_Button_NunChuck[x][i], 0, (wxLEFT), 2);
|
m_Sizer_NunChuck[x][i]->Add(m_Button_NunChuck[x][i], 0, (wxLEFT), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -895,7 +895,7 @@ void WiimotePadConfigDialog::CreatePadGUIControls()
|
|||||||
m_sHorizControllerMapping[i]->Add(m_gWiimote[i], 0, (wxLEFT), 5);
|
m_sHorizControllerMapping[i]->Add(m_gWiimote[i], 0, (wxLEFT), 5);
|
||||||
switch(WiiMoteEmu::WiiMapping[i].iExtensionConnected)
|
switch(WiiMoteEmu::WiiMapping[i].iExtensionConnected)
|
||||||
{
|
{
|
||||||
case WiiMoteEmu::EXT_NUNCHUCK:
|
case WiiMoteEmu::EXT_NUNCHUK:
|
||||||
m_sHorizControllerMapping[i]->Add(m_gNunchuck[i], 0, (wxLEFT), 5);
|
m_sHorizControllerMapping[i]->Add(m_gNunchuck[i], 0, (wxLEFT), 5);
|
||||||
break;
|
break;
|
||||||
case WiiMoteEmu::EXT_CLASSIC_CONTROLLER:
|
case WiiMoteEmu::EXT_CLASSIC_CONTROLLER:
|
||||||
@ -1074,7 +1074,7 @@ void WiimotePadConfigDialog::UpdateGUI()
|
|||||||
m_Button_Wiimote[x][m_Page]->SetLabel(wxString::FromAscii(
|
m_Button_Wiimote[x][m_Page]->SetLabel(wxString::FromAscii(
|
||||||
InputCommon::VKToString(WiiMoteEmu::WiiMapping[m_Page].Button[x + WiiMoteEmu::EWM_A]).c_str()));
|
InputCommon::VKToString(WiiMoteEmu::WiiMapping[m_Page].Button[x + WiiMoteEmu::EWM_A]).c_str()));
|
||||||
}
|
}
|
||||||
if(WiiMoteEmu::WiiMapping[m_Page].iExtensionConnected == WiiMoteEmu::EXT_NUNCHUCK)
|
if(WiiMoteEmu::WiiMapping[m_Page].iExtensionConnected == WiiMoteEmu::EXT_NUNCHUK)
|
||||||
{
|
{
|
||||||
m_NunchuckComboStick[m_Page]->SetSelection(WiiMoteEmu::WiiMapping[m_Page].Stick.NC);
|
m_NunchuckComboStick[m_Page]->SetSelection(WiiMoteEmu::WiiMapping[m_Page].Stick.NC);
|
||||||
for (int x = 0; x <= IDB_NC_SHAKE - IDB_NC_Z; x++)
|
for (int x = 0; x <= IDB_NC_SHAKE - IDB_NC_Z; x++)
|
||||||
@ -1104,7 +1104,7 @@ void WiimotePadConfigDialog::UpdateGUI()
|
|||||||
InputCommon::XKeyToString(WiiMoteEmu::WiiMapping[m_Page].Button[x + WiiMoteEmu::EWM_A], keyStr);
|
InputCommon::XKeyToString(WiiMoteEmu::WiiMapping[m_Page].Button[x + WiiMoteEmu::EWM_A], keyStr);
|
||||||
m_Button_Wiimote[x][m_Page]->SetLabel(wxString::FromAscii(keyStr));
|
m_Button_Wiimote[x][m_Page]->SetLabel(wxString::FromAscii(keyStr));
|
||||||
}
|
}
|
||||||
if(WiiMoteEmu::WiiMapping[m_Page].iExtensionConnected == WiiMoteEmu::EXT_NUNCHUCK)
|
if(WiiMoteEmu::WiiMapping[m_Page].iExtensionConnected == WiiMoteEmu::EXT_NUNCHUK)
|
||||||
{
|
{
|
||||||
m_NunchuckComboStick[m_Page]->SetSelection(WiiMoteEmu::WiiMapping[m_Page].Stick.NC);
|
m_NunchuckComboStick[m_Page]->SetSelection(WiiMoteEmu::WiiMapping[m_Page].Stick.NC);
|
||||||
for (int x = 0; x <= IDB_NC_SHAKE - IDB_NC_Z; x++)
|
for (int x = 0; x <= IDB_NC_SHAKE - IDB_NC_Z; x++)
|
||||||
|
@ -257,7 +257,7 @@ class WiimotePadConfigDialog : public wxDialog
|
|||||||
*m_tTriggerSource[MAX_WIIMOTES],
|
*m_tTriggerSource[MAX_WIIMOTES],
|
||||||
*m_statictext_Analog[IDB_TRIGGER_R - IDB_ANALOG_LEFT_X + 1][MAX_WIIMOTES],
|
*m_statictext_Analog[IDB_TRIGGER_R - IDB_ANALOG_LEFT_X + 1][MAX_WIIMOTES],
|
||||||
*m_statictext_Wiimote[IDB_WM_SHAKE - IDB_WM_A + 1][MAX_WIIMOTES],
|
*m_statictext_Wiimote[IDB_WM_SHAKE - IDB_WM_A + 1][MAX_WIIMOTES],
|
||||||
*m_statictext_NunChuck[IDB_NC_SHAKE - IDB_NC_Z + 1][MAX_WIIMOTES],
|
*m_statictext_Nunchuk[IDB_NC_SHAKE - IDB_NC_Z + 1][MAX_WIIMOTES],
|
||||||
*m_statictext_Classic[IDB_CC_RD - IDB_CC_A + 1][MAX_WIIMOTES],
|
*m_statictext_Classic[IDB_CC_RD - IDB_CC_A + 1][MAX_WIIMOTES],
|
||||||
*m_statictext_GH3[IDB_GH3_STRUM_DOWN - IDB_GH3_GREEN + 1][MAX_WIIMOTES],
|
*m_statictext_GH3[IDB_GH3_STRUM_DOWN - IDB_GH3_GREEN + 1][MAX_WIIMOTES],
|
||||||
*m_NunchuckTextStick[5],
|
*m_NunchuckTextStick[5],
|
||||||
|
@ -196,7 +196,7 @@ void SendReportCoreAccelExt16(u16 _channelID)
|
|||||||
FillReportAcc(pReport->a);
|
FillReportAcc(pReport->a);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if(WiiMapping[g_ID].iExtensionConnected == EXT_NUNCHUCK)
|
if(WiiMapping[g_ID].iExtensionConnected == EXT_NUNCHUK)
|
||||||
{
|
{
|
||||||
#if defined(HAVE_WX) && HAVE_WX
|
#if defined(HAVE_WX) && HAVE_WX
|
||||||
FillReportExtension(pReport->ext);
|
FillReportExtension(pReport->ext);
|
||||||
@ -233,38 +233,53 @@ void SendReportCoreAccelIr10Ext(u16 _channelID)
|
|||||||
memset(pReport, 0, sizeof(wm_report_core_accel_ir10_ext6));
|
memset(pReport, 0, sizeof(wm_report_core_accel_ir10_ext6));
|
||||||
|
|
||||||
// Make a classic extension struct
|
// Make a classic extension struct
|
||||||
wm_classic_extension _ext;
|
|
||||||
wm_GH3_extension _GH3_ext;
|
|
||||||
memset(&_ext, 0, sizeof(wm_classic_extension));
|
|
||||||
memset(&_GH3_ext, 0, sizeof(wm_GH3_extension));
|
|
||||||
|
|
||||||
#if defined(HAVE_WX) && HAVE_WX
|
#if defined(HAVE_WX) && HAVE_WX
|
||||||
FillReportInfo(pReport->c);
|
FillReportInfo(pReport->c);
|
||||||
FillReportAcc(pReport->a);
|
FillReportAcc(pReport->a);
|
||||||
FillReportIRBasic(pReport->ir[0], pReport->ir[1]);
|
FillReportIRBasic(pReport->ir[0], pReport->ir[1]);
|
||||||
#endif
|
if ((WiiMapping[g_ID].bMotionPlusConnected) && (( WiiMapping[g_ID].iExtensionConnected == EXT_NUNCHUK ) || (WiiMapping[g_ID].iExtensionConnected == EXT_NONE)) )
|
||||||
if(WiiMapping[g_ID].iExtensionConnected == EXT_NUNCHUCK)
|
{
|
||||||
|
if(WiiMapping[g_ID].iExtensionConnected == EXT_NUNCHUK)
|
||||||
|
{
|
||||||
|
FillReportMotionPlus(pReport->ext, true);
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (WiiMapping[g_ID].iExtensionConnected == EXT_NONE)
|
||||||
|
{
|
||||||
|
FillReportMotionPlus(pReport->ext, false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if(WiiMapping[g_ID].iExtensionConnected == EXT_NUNCHUK)
|
||||||
{
|
{
|
||||||
#if defined(HAVE_WX) && HAVE_WX
|
|
||||||
FillReportExtension(pReport->ext);
|
FillReportExtension(pReport->ext);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
else if(WiiMapping[g_ID].iExtensionConnected == EXT_CLASSIC_CONTROLLER)
|
else if(WiiMapping[g_ID].iExtensionConnected == EXT_CLASSIC_CONTROLLER)
|
||||||
{
|
{
|
||||||
#if defined(HAVE_WX) && HAVE_WX
|
wm_classic_extension _ext;
|
||||||
|
memset(&_ext, 0, sizeof(wm_classic_extension));
|
||||||
|
|
||||||
FillReportClassicExtension(_ext);
|
FillReportClassicExtension(_ext);
|
||||||
#endif
|
|
||||||
// Copy _ext to pReport->ext
|
// Copy _ext to pReport->ext
|
||||||
memcpy(&pReport->ext, &_ext, sizeof(_ext));
|
memcpy(&pReport->ext, &_ext, sizeof(_ext));
|
||||||
}
|
}
|
||||||
else if(WiiMapping[g_ID].iExtensionConnected == EXT_GUITARHERO)
|
else if(WiiMapping[g_ID].iExtensionConnected == EXT_GUITARHERO)
|
||||||
{
|
{
|
||||||
#if defined(HAVE_WX) && HAVE_WX
|
|
||||||
|
wm_GH3_extension _GH3_ext;
|
||||||
|
memset(&_GH3_ext, 0, sizeof(wm_GH3_extension));
|
||||||
FillReportGuitarHero3Extension(_GH3_ext);
|
FillReportGuitarHero3Extension(_GH3_ext);
|
||||||
#endif
|
|
||||||
memcpy(&pReport->ext, &_GH3_ext, sizeof(_GH3_ext));
|
memcpy(&pReport->ext, &_GH3_ext, sizeof(_GH3_ext));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
||||||
INFO_LOG(WIIMOTE, " SendReportCoreAccelIr10Ext(0x37)");
|
INFO_LOG(WIIMOTE, " SendReportCoreAccelIr10Ext(0x37)");
|
||||||
DEBUG_LOG(WIIMOTE, " Channel: %04x", _channelID);
|
DEBUG_LOG(WIIMOTE, " Channel: %04x", _channelID);
|
||||||
DEBUG_LOG(WIIMOTE, " Size: %08x", Offset);
|
DEBUG_LOG(WIIMOTE, " Size: %08x", Offset);
|
||||||
|
@ -44,12 +44,16 @@ u8 g_IRClock[MAX_WIIMOTES];
|
|||||||
u8 g_IR[MAX_WIIMOTES];
|
u8 g_IR[MAX_WIIMOTES];
|
||||||
u8 g_Leds[MAX_WIIMOTES];
|
u8 g_Leds[MAX_WIIMOTES];
|
||||||
u8 g_Speaker[MAX_WIIMOTES];
|
u8 g_Speaker[MAX_WIIMOTES];
|
||||||
|
u8 g_MotionPlus[MAX_WIIMOTES];
|
||||||
u8 g_SpeakerMute[MAX_WIIMOTES];
|
u8 g_SpeakerMute[MAX_WIIMOTES];
|
||||||
|
|
||||||
|
int g_MotionPlusReadError[MAX_WIIMOTES];
|
||||||
u8 g_RegExtTmp[WIIMOTE_REG_EXT_SIZE];
|
u8 g_RegExtTmp[WIIMOTE_REG_EXT_SIZE];
|
||||||
|
|
||||||
int g_ID; // Current refreshing Wiimote
|
int g_ID; // Current refreshing Wiimote
|
||||||
bool g_ReportingAuto[MAX_WIIMOTES]; // Auto report or passive report
|
bool g_ReportingAuto[MAX_WIIMOTES]; // Auto report or passive report
|
||||||
|
bool g_MotionPlusConnected[MAX_WIIMOTES]; //MotionPlusinitiated
|
||||||
|
bool g_InterleavedData[MAX_WIIMOTES]; //sending alternated data packets from the nunchuk/motionplus
|
||||||
u8 g_ReportingMode[MAX_WIIMOTES]; // The reporting mode and channel id
|
u8 g_ReportingMode[MAX_WIIMOTES]; // The reporting mode and channel id
|
||||||
u16 g_ReportingChannel[MAX_WIIMOTES];
|
u16 g_ReportingChannel[MAX_WIIMOTES];
|
||||||
|
|
||||||
|
@ -86,14 +86,17 @@ extern u8 g_IRClock[MAX_WIIMOTES];
|
|||||||
extern u8 g_IR[MAX_WIIMOTES];
|
extern u8 g_IR[MAX_WIIMOTES];
|
||||||
extern u8 g_Leds[MAX_WIIMOTES];
|
extern u8 g_Leds[MAX_WIIMOTES];
|
||||||
extern u8 g_Speaker[MAX_WIIMOTES];
|
extern u8 g_Speaker[MAX_WIIMOTES];
|
||||||
|
extern u8 g_MotionPlus[MAX_WIIMOTES];
|
||||||
extern u8 g_SpeakerMute[MAX_WIIMOTES];
|
extern u8 g_SpeakerMute[MAX_WIIMOTES];
|
||||||
|
|
||||||
|
extern int g_MotionPlusReadError[MAX_WIIMOTES];
|
||||||
extern u8 g_RegExtTmp[WIIMOTE_REG_EXT_SIZE];
|
extern u8 g_RegExtTmp[WIIMOTE_REG_EXT_SIZE];
|
||||||
|
|
||||||
extern int g_ID;
|
extern int g_ID;
|
||||||
extern bool g_ReportingAuto[MAX_WIIMOTES];
|
extern bool g_ReportingAuto[MAX_WIIMOTES];
|
||||||
extern u8 g_ReportingMode[MAX_WIIMOTES];
|
extern u8 g_ReportingMode[MAX_WIIMOTES];
|
||||||
extern u16 g_ReportingChannel[MAX_WIIMOTES];
|
extern u16 g_ReportingChannel[MAX_WIIMOTES];
|
||||||
|
extern bool g_InterleavedData[MAX_WIIMOTES];
|
||||||
|
|
||||||
extern wiimote_key g_ExtKey[MAX_WIIMOTES]; // extension encryption key
|
extern wiimote_key g_ExtKey[MAX_WIIMOTES]; // extension encryption key
|
||||||
extern bool g_Encryption;
|
extern bool g_Encryption;
|
||||||
@ -113,7 +116,24 @@ static const u8 EepromData_16D0[] = {
|
|||||||
0x33, 0xCC, 0x44, 0xBB, 0x00, 0x00, 0x66, 0x99,
|
0x33, 0xCC, 0x44, 0xBB, 0x00, 0x00, 0x66, 0x99,
|
||||||
0x77, 0x88, 0x00, 0x00, 0x2B, 0x01, 0xE8, 0x13
|
0x77, 0x88, 0x00, 0x00, 0x2B, 0x01, 0xE8, 0x13
|
||||||
};
|
};
|
||||||
|
static const u8 motionplus_register[] = {
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0x79, 0x83, 0x73, 0x54, 0x72, 0xE8, 0x30, 0xC3, 0xCC, 0x4A, 0x34, 0xFC, 0xC8, 0x4F, 0xCC, 0x5B,
|
||||||
|
0x77, 0x49, 0x75, 0xA4, 0x73, 0x9A, 0x35, 0x52, 0xCA, 0x22, 0x37, 0x26, 0x2D, 0xE5, 0xB5, 0xA2,
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0x78, 0x76, 0xDD, 0xF5, 0x6A, 0x3C, 0xCF, 0xF7, 0x2A, 0x0E, 0x32, 0xEE, 0x82, 0xFE, 0x2E, 0xFD,
|
||||||
|
0x19, 0xE7, 0x0A, 0xCA, 0x67, 0x3B, 0x3A, 0x75, 0xF6, 0x45, 0x55, 0x8E, 0x9D, 0x33, 0xCC, 0xEA,
|
||||||
|
0x6E, 0x52, 0xC6, 0xC6, 0x16, 0x9B, 0xEE, 0x12, 0x2E, 0x3F, 0x77, 0xB1, 0xA1, 0x80, 0x0B, 0x0E,
|
||||||
|
0xC2, 0x25, 0x05, 0xEA, 0xC3, 0x2F, 0x85, 0x1E, 0x31, 0x53, 0x74, 0xC7, 0xF1, 0x93, 0xF1, 0x2D,
|
||||||
|
0xC1, 0x6D, 0x84, 0x2A, 0xD8, 0x6F, 0x8A, 0xE5, 0x2D, 0x3B, 0x7B, 0xCC, 0xD2, 0x59, 0xD5, 0xD1,
|
||||||
|
0x9F, 0x5B, 0x6F, 0xAE, 0x82, 0xDE, 0xEA, 0xC3, 0x73, 0x42, 0x06, 0xA9, 0x77, 0xFF, 0x61, 0xA8,
|
||||||
|
0x1A, 0x70, 0xE4, 0x16, 0x90, 0x7A, 0x80, 0xF7, 0x79, 0x4B, 0x41, 0x18, 0x82, 0x6C, 0x62, 0x1A,
|
||||||
|
0x3B, 0xBF, 0xFC, 0xFF, 0x2C, 0xF2, 0x32, 0x97, 0xB8, 0x2F, 0x17, 0xE7, 0xBD, 0x35, 0x1D, 0x0A,
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0x55, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x10, 0xff, 0xff, 0x00, 0x00, 0xa6, 0x20, 0x00, 0x05
|
||||||
|
};
|
||||||
|
|
||||||
/* Default calibration for the nunchuck. It should be written to 0x20 - 0x3f of the
|
/* Default calibration for the nunchuck. It should be written to 0x20 - 0x3f of the
|
||||||
extension register. 0x80 is the neutral x and y accelerators and 0xb3 is the
|
extension register. 0x80 is the neutral x and y accelerators and 0xb3 is the
|
||||||
@ -133,6 +153,12 @@ static const u8 wireless_nunchuck_calibration[] =
|
|||||||
255, 0, 125, 255,
|
255, 0, 125, 255,
|
||||||
0, 126, 0xed, 0x43
|
0, 126, 0xed, 0x43
|
||||||
};
|
};
|
||||||
|
/* Default calibration for the motion plus*/
|
||||||
|
static const u8 motion_plus_calibration[] =
|
||||||
|
{
|
||||||
|
0x79, 0xbe, 0x77, 0x5a, 0x77, 0x38, 0x2f, 0x90, 0xcd, 0x3b, 0x2f, 0xfd, 0xc8, 0x29, 0x9c, 0x75,
|
||||||
|
0x7d, 0xd4, 0x78, 0xef, 0x78, 0x8a, 0x35, 0xa6, 0xc9, 0x9b, 0x33, 0x50, 0x2d, 0x00, 0xbd, 0x23
|
||||||
|
};
|
||||||
|
|
||||||
/* Classic Controller calibration */
|
/* Classic Controller calibration */
|
||||||
static const u8 classic_calibration[] =
|
static const u8 classic_calibration[] =
|
||||||
@ -146,6 +172,11 @@ static const u8 nunchuck_id[] = { 0x00, 0x00, 0xa4, 0x20, 0x00, 0x00 };
|
|||||||
static const u8 classic_id[] = { 0x00, 0x00, 0xa4, 0x20, 0x01, 0x01 };
|
static const u8 classic_id[] = { 0x00, 0x00, 0xa4, 0x20, 0x01, 0x01 };
|
||||||
static const u8 gh3glp_id[] = { 0x00, 0x00, 0xa4, 0x20, 0x01, 0x03 };
|
static const u8 gh3glp_id[] = { 0x00, 0x00, 0xa4, 0x20, 0x01, 0x03 };
|
||||||
static const u8 ghwtdrums_id[] = { 0x01, 0x00, 0xa4, 0x20, 0x01, 0x03 };
|
static const u8 ghwtdrums_id[] = { 0x01, 0x00, 0xa4, 0x20, 0x01, 0x03 };
|
||||||
|
static const u8 wbb_id[] = { 0x00, 0x00, 0xa4, 0x20, 0x4, 0x02 };
|
||||||
|
static const u8 motionplus_id[] = { 0x00, 0x00, 0xa4, 0x20, 0x04, 0x05 };
|
||||||
|
static const u8 motionplusnunchuk_id[] = { 0x00, 0x00, 0xa4, 0x20, 0x05, 0x05 };
|
||||||
|
//initial control packet for datatransfers over 0x37 reports
|
||||||
|
static const u8 motionpluscheck_id[] = { 0xa3, 0x62, 0x45, 0xaa, 0x04, 0x02};
|
||||||
// The id for nothing inserted
|
// The id for nothing inserted
|
||||||
static const u8 nothing_id[] = { 0x00, 0x00, 0x00, 0x00, 0x2e, 0x2e };
|
static const u8 nothing_id[] = { 0x00, 0x00, 0x00, 0x00, 0x2e, 0x2e };
|
||||||
// The id for a partially inserted extension
|
// The id for a partially inserted extension
|
||||||
@ -155,9 +186,10 @@ static const u8 partially_id[] = { 0x00, 0x00, 0x00, 0x00, 0xff, 0xff };
|
|||||||
enum EExtensionType
|
enum EExtensionType
|
||||||
{
|
{
|
||||||
EXT_NONE = 0,
|
EXT_NONE = 0,
|
||||||
EXT_NUNCHUCK,
|
EXT_NUNCHUK,
|
||||||
EXT_CLASSIC_CONTROLLER,
|
EXT_CLASSIC_CONTROLLER,
|
||||||
EXT_GUITARHERO,
|
EXT_GUITARHERO,
|
||||||
|
EXT_WBB,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum EInputType
|
enum EInputType
|
||||||
|
@ -416,6 +416,8 @@ void ResetVariables()
|
|||||||
for (int i = 0; i < MAX_WIIMOTES; i++)
|
for (int i = 0; i < MAX_WIIMOTES; i++)
|
||||||
{
|
{
|
||||||
g_ReportingAuto[i] = false;
|
g_ReportingAuto[i] = false;
|
||||||
|
g_MotionPlusReadError[i] = 0;
|
||||||
|
g_InterleavedData[i] = false;
|
||||||
g_ReportingMode[i] = 0;
|
g_ReportingMode[i] = 0;
|
||||||
g_ReportingChannel[i] = 0;
|
g_ReportingChannel[i] = 0;
|
||||||
WiiMapping[i].Motion.TiltWM.Shake = 0;
|
WiiMapping[i].Motion.TiltWM.Shake = 0;
|
||||||
@ -491,9 +493,35 @@ void InitCalibration()
|
|||||||
|
|
||||||
// Update the extension calibration values with our default values
|
// Update the extension calibration values with our default values
|
||||||
void UpdateExtRegisterBlocks(int Slot)
|
void UpdateExtRegisterBlocks(int Slot)
|
||||||
|
{
|
||||||
|
if (WiiMapping[Slot].bMotionPlusConnected)
|
||||||
{
|
{
|
||||||
// Copy extension id and calibration to its register
|
// Copy extension id and calibration to its register
|
||||||
if(WiiMapping[Slot].iExtensionConnected == EXT_NUNCHUCK)
|
if (WiiMapping[Slot].iExtensionConnected == EXT_NONE)
|
||||||
|
{
|
||||||
|
memset(g_RegExt[Slot],0,sizeof(g_RegExt[0]));
|
||||||
|
memcpy(g_RegMotionPlus[Slot], motionplus_register, sizeof(motionplus_register));
|
||||||
|
memcpy(g_RegMotionPlus[Slot] + 0x20, motion_plus_calibration, sizeof(motion_plus_calibration)); //reg 32bytes 0x20-3f;
|
||||||
|
g_MotionPlus[Slot] = 0;
|
||||||
|
//memcpy(g_RegMotionPlus[Slot] + 0xfa, motionplus_id, sizeof(motionplus_id));
|
||||||
|
}
|
||||||
|
else if(WiiMapping[Slot].iExtensionConnected == EXT_NUNCHUK)
|
||||||
|
{
|
||||||
|
memset(g_RegMotionPlus[Slot],0,sizeof(g_RegExt[0]));
|
||||||
|
memcpy(g_RegMotionPlus[Slot], motionplus_register, sizeof(motionplus_register));
|
||||||
|
memcpy(g_RegMotionPlus[Slot] + 0x20, motion_plus_calibration, sizeof(motion_plus_calibration)); //reg 32bytes 0x20-3f;
|
||||||
|
memcpy(g_RegExt[Slot] + 0x20, nunchuck_calibration, sizeof(nunchuck_calibration));
|
||||||
|
memcpy(g_RegExt[Slot] + 0x30, nunchuck_calibration, sizeof(nunchuck_calibration));
|
||||||
|
memcpy(g_RegExt[Slot] + 0xfa, nunchuck_id, sizeof(nunchuck_id));
|
||||||
|
g_MotionPlus[Slot] = 1;
|
||||||
|
}
|
||||||
|
g_MotionPlusReadError[Slot] = 0;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
|
||||||
|
// Copy extension id and calibration to its register
|
||||||
|
if(WiiMapping[Slot].iExtensionConnected == EXT_NUNCHUK)
|
||||||
{
|
{
|
||||||
memcpy(g_RegExt[Slot] + 0x20, nunchuck_calibration, sizeof(nunchuck_calibration));
|
memcpy(g_RegExt[Slot] + 0x20, nunchuck_calibration, sizeof(nunchuck_calibration));
|
||||||
memcpy(g_RegExt[Slot] + 0x30, nunchuck_calibration, sizeof(nunchuck_calibration));
|
memcpy(g_RegExt[Slot] + 0x30, nunchuck_calibration, sizeof(nunchuck_calibration));
|
||||||
@ -512,6 +540,11 @@ void UpdateExtRegisterBlocks(int Slot)
|
|||||||
memcpy(g_RegExt[Slot] + 0x30, classic_calibration, sizeof(classic_calibration));
|
memcpy(g_RegExt[Slot] + 0x30, classic_calibration, sizeof(classic_calibration));
|
||||||
memcpy(g_RegExt[Slot] + 0xfa, gh3glp_id, sizeof(gh3glp_id));
|
memcpy(g_RegExt[Slot] + 0xfa, gh3glp_id, sizeof(gh3glp_id));
|
||||||
}
|
}
|
||||||
|
else if(WiiMapping[Slot].iExtensionConnected == EXT_WBB)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
INFO_LOG(WIIMOTE, "UpdateExtRegisterBlocks()");
|
INFO_LOG(WIIMOTE, "UpdateExtRegisterBlocks()");
|
||||||
}
|
}
|
||||||
@ -546,6 +579,7 @@ void DoState(PointerWrap &p)
|
|||||||
p.Do(g_IR[i]);
|
p.Do(g_IR[i]);
|
||||||
p.Do(g_Leds[i]);
|
p.Do(g_Leds[i]);
|
||||||
p.Do(g_Speaker[i]);
|
p.Do(g_Speaker[i]);
|
||||||
|
p.Do(g_MotionPlus[i]);
|
||||||
//p.Do(g_SpeakerMute[i]);
|
//p.Do(g_SpeakerMute[i]);
|
||||||
p.Do(g_ExtKey[i]);
|
p.Do(g_ExtKey[i]);
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,6 @@ extern SWiimoteInitialize g_WiimoteInitialize;
|
|||||||
|
|
||||||
namespace WiiMoteEmu
|
namespace WiiMoteEmu
|
||||||
{
|
{
|
||||||
|
|
||||||
extern void PAD_Rumble(u8 _numPAD, unsigned int _uType);
|
extern void PAD_Rumble(u8 _numPAD, unsigned int _uType);
|
||||||
|
|
||||||
/* Here we process the Output Reports that the Wii sends. Our response will be
|
/* Here we process the Output Reports that the Wii sends. Our response will be
|
||||||
@ -190,7 +189,7 @@ void WmReadData(u16 _channelID, wm_read_data* rd)
|
|||||||
{
|
{
|
||||||
u32 address = convert24bit(rd->address);
|
u32 address = convert24bit(rd->address);
|
||||||
u16 size = convert16bit(rd->size);
|
u16 size = convert16bit(rd->size);
|
||||||
|
u8 addressHI = (address >> 16) & 0xFE;
|
||||||
INFO_LOG(WIIMOTE, "Read data");
|
INFO_LOG(WIIMOTE, "Read data");
|
||||||
DEBUG_LOG(WIIMOTE, " Read data Space: %x", rd->space);
|
DEBUG_LOG(WIIMOTE, " Read data Space: %x", rd->space);
|
||||||
DEBUG_LOG(WIIMOTE, " Read data Address: 0x%06x", address);
|
DEBUG_LOG(WIIMOTE, " Read data Address: 0x%06x", address);
|
||||||
@ -205,7 +204,7 @@ void WmReadData(u16 _channelID, wm_read_data* rd)
|
|||||||
PanicAlert("WmReadData: address + size out of bounds");
|
PanicAlert("WmReadData: address + size out of bounds");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SendReadDataReply(_channelID, g_Eeprom[g_ID] + address, address, (int)size);
|
SendReadDataReply(_channelID, g_Eeprom[g_ID] + address, address, addressHI, (int)size);
|
||||||
/*DEBUG_LOG(WIIMOTE, "Read RegEeprom: Size: %i, Address: %08x, Offset: %08x",
|
/*DEBUG_LOG(WIIMOTE, "Read RegEeprom: Size: %i, Address: %08x, Offset: %08x",
|
||||||
size, address, (address & 0xffff));*/
|
size, address, (address & 0xffff));*/
|
||||||
}
|
}
|
||||||
@ -213,7 +212,7 @@ void WmReadData(u16 _channelID, wm_read_data* rd)
|
|||||||
{
|
{
|
||||||
u8* block;
|
u8* block;
|
||||||
u32 blockSize;
|
u32 blockSize;
|
||||||
switch((address >> 16) & 0xFE)
|
switch(addressHI)
|
||||||
{
|
{
|
||||||
case 0xA2:
|
case 0xA2:
|
||||||
block = g_RegSpeaker[g_ID];
|
block = g_RegSpeaker[g_ID];
|
||||||
@ -230,10 +229,6 @@ void WmReadData(u16 _channelID, wm_read_data* rd)
|
|||||||
// MotionPlus is pretty much just a dummy atm :p
|
// MotionPlus is pretty much just a dummy atm :p
|
||||||
case 0xA6:
|
case 0xA6:
|
||||||
block = g_RegMotionPlus[g_ID];
|
block = g_RegMotionPlus[g_ID];
|
||||||
block[0xFC] = 0xA6;
|
|
||||||
block[0xFD] = 0x20;
|
|
||||||
block[0xFE] = 0x00;
|
|
||||||
block[0xFF] = 0x05;
|
|
||||||
blockSize = WIIMOTE_REG_EXT_SIZE;
|
blockSize = WIIMOTE_REG_EXT_SIZE;
|
||||||
DEBUG_LOG(WIIMOTE, " Case 0xa6: MotionPlusReg [%x]", address);
|
DEBUG_LOG(WIIMOTE, " Case 0xa6: MotionPlusReg [%x]", address);
|
||||||
break;
|
break;
|
||||||
@ -251,7 +246,7 @@ void WmReadData(u16 _channelID, wm_read_data* rd)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Encrypt data that is read from the Wiimote Extension Register
|
// Encrypt data that is read from the Wiimote Extension Register
|
||||||
if(((address >> 16) & 0xfe) == 0xa4)
|
if(addressHI == 0xa4)
|
||||||
{
|
{
|
||||||
// Check if encrypted reads is on
|
// Check if encrypted reads is on
|
||||||
if(g_RegExt[g_ID][0xf0] == 0xaa)
|
if(g_RegExt[g_ID][0xf0] == 0xaa)
|
||||||
@ -274,9 +269,13 @@ void WmReadData(u16 _channelID, wm_read_data* rd)
|
|||||||
PanicAlert("WmReadData: address + size out of bounds! [%d %d %d]", address, size, blockSize);
|
PanicAlert("WmReadData: address + size out of bounds! [%d %d %d]", address, size, blockSize);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
//3x read error due activated(or not present device, which is not the case), WII will await a status report after init and attempting to read from write-only area @A600FE/FF
|
||||||
|
if ((g_MotionPlusReadError[g_ID] == 2) && (g_RegExt[g_ID][0xFF]== 0x05)) { //if motionplus is active, its in the current ExtReg, 0xFF will be always 0x05
|
||||||
|
WmRequestStatus(_channelID, (wm_request_status*) rd, 1);
|
||||||
|
}
|
||||||
// Let this function process the message and send it to the Wii
|
// Let this function process the message and send it to the Wii
|
||||||
SendReadDataReply(_channelID, block + address, address, (u8)size);
|
SendReadDataReply(_channelID, block + address, address, addressHI, (u8)size);
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -295,7 +294,7 @@ void WmReadData(u16 _channelID, wm_read_data* rd)
|
|||||||
_Address: The starting address inside the registry, this is used to check for out of bounds reading
|
_Address: The starting address inside the registry, this is used to check for out of bounds reading
|
||||||
_Size: The total size to send
|
_Size: The total size to send
|
||||||
*/
|
*/
|
||||||
void SendReadDataReply(u16 _channelID, void* _Base, u16 _Address, int _Size)
|
void SendReadDataReply(u16 _channelID, void* _Base, u16 _Address, u8 _AddressHI, int _Size)
|
||||||
{
|
{
|
||||||
int dataOffset = 0;
|
int dataOffset = 0;
|
||||||
const u8* data = (const u8*)_Base;
|
const u8* data = (const u8*)_Base;
|
||||||
@ -340,6 +339,24 @@ void SendReadDataReply(u16 _channelID, void* _Base, u16 _Address, int _Size)
|
|||||||
pReply->size = 0x0f;
|
pReply->size = 0x0f;
|
||||||
pReply->error = 0x08;
|
pReply->error = 0x08;
|
||||||
}
|
}
|
||||||
|
if (WiiMapping[g_ID].bMotionPlusConnected)
|
||||||
|
{
|
||||||
|
//MP+ will try to read from this Registeraddress, expecting an error if a previous WM+ activation has been succesful
|
||||||
|
//it also returns an error if there was no WM+ present at all
|
||||||
|
if (((_Address == 0x00FE ) || (_Address == 0x00FF )) && (_AddressHI == 0xA6))
|
||||||
|
{
|
||||||
|
//Check if MP+ is activated, resp. if its in the current RegExt.
|
||||||
|
if ((g_RegExt[g_ID][0xFF] == 0x05) && (g_RegMotionPlus[g_ID][0xFF] != 0x05))
|
||||||
|
{
|
||||||
|
pReply->size = 0x0f;
|
||||||
|
pReply->error = 0x07; //error: write-only area when activated/or not present
|
||||||
|
// we use the read error at the same time as an indicator whether we need to send a faked 0x37 report or not
|
||||||
|
g_MotionPlusReadError[g_ID]++;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
DEBUG_LOG(WIIMOTE, "SendReadDataReply");
|
DEBUG_LOG(WIIMOTE, "SendReadDataReply");
|
||||||
@ -369,12 +386,11 @@ void SendReadDataReply(u16 _channelID, void* _Base, u16 _Address, int _Size)
|
|||||||
void WmWriteData(u16 _channelID, wm_write_data* wd)
|
void WmWriteData(u16 _channelID, wm_write_data* wd)
|
||||||
{
|
{
|
||||||
u32 address = convert24bit(wd->address);
|
u32 address = convert24bit(wd->address);
|
||||||
|
u8 addressHI = (address >> 16) & 0xFE;
|
||||||
INFO_LOG(WIIMOTE, "Write data");
|
INFO_LOG(WIIMOTE, "Write data");
|
||||||
DEBUG_LOG(WIIMOTE, " Space: %x", wd->space);
|
DEBUG_LOG(WIIMOTE, " Space: %x", wd->space);
|
||||||
DEBUG_LOG(WIIMOTE, " Address: 0x%06x", address);
|
DEBUG_LOG(WIIMOTE, " Address: 0x%06x", address);
|
||||||
DEBUG_LOG(WIIMOTE, " Size: 0x%02x", wd->size);
|
DEBUG_LOG(WIIMOTE, " Size: 0x%02x", wd->size);
|
||||||
|
|
||||||
// Write to EEPROM
|
// Write to EEPROM
|
||||||
if(wd->size <= 16 && wd->space == WM_SPACE_EEPROM)
|
if(wd->size <= 16 && wd->space == WM_SPACE_EEPROM)
|
||||||
{
|
{
|
||||||
@ -390,7 +406,7 @@ void WmWriteData(u16 _channelID, wm_write_data* wd)
|
|||||||
{
|
{
|
||||||
u8* block;
|
u8* block;
|
||||||
u32 blockSize;
|
u32 blockSize;
|
||||||
switch((address >> 16) & 0xFE)
|
switch(addressHI)
|
||||||
{
|
{
|
||||||
case 0xA2:
|
case 0xA2:
|
||||||
block = g_RegSpeaker[g_ID];
|
block = g_RegSpeaker[g_ID];
|
||||||
@ -422,7 +438,6 @@ void WmWriteData(u16 _channelID, wm_write_data* wd)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove for example 0xa40000 from the address
|
|
||||||
address &= 0xFFFF;
|
address &= 0xFFFF;
|
||||||
|
|
||||||
// Check if the address is within bounds
|
// Check if the address is within bounds
|
||||||
@ -442,6 +457,9 @@ void WmWriteData(u16 _channelID, wm_write_data* wd)
|
|||||||
if(address >= 0x40 && address <= 0x4c)
|
if(address >= 0x40 && address <= 0x4c)
|
||||||
wiimote_gen_key(&g_ExtKey[g_ID], &g_RegExt[g_ID][0x40]);
|
wiimote_gen_key(&g_ExtKey[g_ID], &g_RegExt[g_ID][0x40]);
|
||||||
}
|
}
|
||||||
|
if (WiiMapping[g_ID].bMotionPlusConnected) {
|
||||||
|
HandlingMotionPlusWrites(wd->data, addressHI, address);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -487,7 +505,7 @@ void WmRequestStatus(u16 _channelID, wm_request_status* rs, int Extension)
|
|||||||
if (Extension == -1)
|
if (Extension == -1)
|
||||||
{
|
{
|
||||||
// Read config value for the first time
|
// Read config value for the first time
|
||||||
pStatus->extension = (WiiMapping[g_ID].iExtensionConnected == EXT_NONE) ? 0 : 1;
|
pStatus->extension = ((g_MotionPlus[g_ID]) || (WiiMapping[g_ID].iExtensionConnected != EXT_NONE)) ? 1 : 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -501,10 +519,121 @@ void WmRequestStatus(u16 _channelID, wm_request_status* rs, int Extension)
|
|||||||
DEBUG_LOG(WIIMOTE, " IR: %x", pStatus->ir);
|
DEBUG_LOG(WIIMOTE, " IR: %x", pStatus->ir);
|
||||||
DEBUG_LOG(WIIMOTE, " LEDs: %x", pStatus->leds);
|
DEBUG_LOG(WIIMOTE, " LEDs: %x", pStatus->leds);
|
||||||
|
|
||||||
|
|
||||||
g_WiimoteInitialize.pWiimoteInput(g_ID, _channelID, DataFrame, Offset);
|
g_WiimoteInitialize.pWiimoteInput(g_ID, _channelID, DataFrame, Offset);
|
||||||
|
|
||||||
// Debugging
|
// Debugging
|
||||||
//ReadDebugging(true, DataFrame, Offset);
|
//ReadDebugging(true, DataFrame, Offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void HandlingMotionPlusWrites(u8* data, u8 addressHI, u32 address){
|
||||||
|
switch (addressHI)
|
||||||
|
{
|
||||||
|
case 0xA4:
|
||||||
|
switch (address)
|
||||||
|
{
|
||||||
|
case 0x00FE:
|
||||||
|
if (data[0] == 0x00) {
|
||||||
|
if ((g_RegExt[g_ID][0xFF] == 0x05) && (g_RegMotionPlus[g_ID][0xFF] != 0x05))
|
||||||
|
{
|
||||||
|
SwapExtRegisters();
|
||||||
|
DEBUG_LOG(WIIMOTE, "Writing [0x%02x] to [0x%02x:%04x]: Disabling WM+ and swapping registers back", data[0], addressHI, address);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
DEBUG_LOG(WIIMOTE, "Writing [0x%02x] to [0x%02x:%04x]: WM+ already inactive", data[0], addressHI, address);
|
||||||
|
}
|
||||||
|
g_MotionPlus[g_ID] = 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x00FB:
|
||||||
|
//1. Initializing the extension: writing 0x55 ->0xA400F0 and then 0x00 to 0xA400FB.
|
||||||
|
//2. Disables an active wiimote; ext disconnect.
|
||||||
|
if (data[0] == 0x00) {
|
||||||
|
//1. connecting extension
|
||||||
|
if ((g_RegExt[g_ID][0xFF] != 0x05) && (g_RegMotionPlus[g_ID][0xFF] == 0x05))
|
||||||
|
{
|
||||||
|
g_RegMotionPlus[g_ID][0xFE] = 0x04;
|
||||||
|
g_RegMotionPlus[g_ID][0xF7] = 0x08; //control init byte, ingame check
|
||||||
|
SwapExtRegisters();
|
||||||
|
DEBUG_LOG(WIIMOTE, "Writing [0x%02x] to [0x%02x:%04x]: Enabling WM+ and swapping rgisters", data[0], addressHI, address);
|
||||||
|
g_MotionPlus[g_ID] = 0;
|
||||||
|
} //2. disconnecting extension
|
||||||
|
else if ((g_RegExt[g_ID][0xFF] == 0x05) && (g_RegMotionPlus[g_ID][0xFF] != 0x05)){
|
||||||
|
g_RegExt[g_ID][0xFE] = 0x04;
|
||||||
|
g_MotionPlus[g_ID] = 1;
|
||||||
|
DEBUG_LOG(WIIMOTE, "Writing [0x%02x] to [0x%02x:%04x]: Disabling WM+ and swapping registers back", data[0], addressHI, address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xA6:
|
||||||
|
switch (address)
|
||||||
|
{
|
||||||
|
case 0x00FE:
|
||||||
|
//Enabling WM+, swapping extension registers
|
||||||
|
if (data[0] == 0x05) {
|
||||||
|
if ((g_RegExt[g_ID][0xFF] != 0x05) && (g_RegMotionPlus[g_ID][0xFF] == 0x05) ) {
|
||||||
|
//The WII will try to read from the readprotected A6 WM+ register now, we need to reply with an error each time,
|
||||||
|
//plus sent an statusreport 0x20(depending on if nunchuk inserted or not)
|
||||||
|
g_MotionPlusReadError[g_ID] = 0;
|
||||||
|
g_MotionPlus[g_ID] = 1;
|
||||||
|
SwapExtRegisters();
|
||||||
|
DEBUG_LOG(WIIMOTE, "Writing [0x%02x] to [0x%02x:%04x]: Enabling WM+ and swapping rgisters back", data[0], addressHI, address);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
DEBUG_LOG(WIIMOTE, "Writing [0x%02x] to [0x%02x:%04x]: WM already enabled no register swapping", data[0], addressHI, address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x00F0: //init() WM+, this will change some control bits in the WM+ register
|
||||||
|
if (data[0] == 0x55) { //enables passthroug, convert to 0405* A6 swap
|
||||||
|
if ((g_RegMotionPlus[g_ID][0xFF] == 0x05))
|
||||||
|
{
|
||||||
|
//control init byte, ingame check
|
||||||
|
g_RegMotionPlus[g_ID][0xF7] = 0x08;
|
||||||
|
|
||||||
|
//motion plus id
|
||||||
|
g_RegMotionPlus[g_ID][0xFE] = 0x05;
|
||||||
|
|
||||||
|
//we will swap the register on write to 0x00FE
|
||||||
|
}
|
||||||
|
else if (g_RegExt[g_ID][0xFF] == 0x05) { //if the wiimote is already active, we will init() the WM+ directly in the ExtReg
|
||||||
|
g_RegExt[g_ID][0xFE] = 0x05;
|
||||||
|
g_RegExt[g_ID][0xF7] = 0x08;
|
||||||
|
}
|
||||||
|
g_MotionPlus[g_ID] = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
DEBUG_LOG(WIIMOTE, "Writing [0x%02x] to [0x%02x:%04x]: unknown reason", data[0], addressHI, address);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Swapping Ext/WM+-registers
|
||||||
|
void SwapExtRegisters(){
|
||||||
|
|
||||||
|
memset(g_RegExtTmp, 0, sizeof(g_RegExtTmp));
|
||||||
|
memcpy(g_RegExtTmp, g_RegExt[g_ID], sizeof(g_RegExt[0]));
|
||||||
|
memset(g_RegExt[0], 0, sizeof(g_RegExt[0]));
|
||||||
|
memcpy(g_RegExt[g_ID], g_RegMotionPlus[g_ID], sizeof(g_RegMotionPlus[0]));
|
||||||
|
memset(g_RegMotionPlus[0], 0, sizeof(g_RegMotionPlus[0]));
|
||||||
|
memcpy(g_RegMotionPlus[g_ID], g_RegExtTmp, sizeof(g_RegExtTmp));
|
||||||
|
|
||||||
|
if (g_RegMotionPlus[g_ID][0xFC]) {
|
||||||
|
g_RegMotionPlus[g_ID][0xFC] = 0xa6;
|
||||||
|
}
|
||||||
|
if (g_RegExt[g_ID][0xFC]) {
|
||||||
|
g_RegExt[g_ID][0xFC] = 0xa4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // WiiMoteEmu
|
} // WiiMoteEmu
|
||||||
|
@ -51,7 +51,9 @@ void SendReportCoreAccelIr10Ext(u16 _channelID);
|
|||||||
|
|
||||||
int WriteWmReportHdr(u8* dst, u8 wm);
|
int WriteWmReportHdr(u8* dst, u8 wm);
|
||||||
void WmSendAck(u16 _channelID, u8 _reportID);
|
void WmSendAck(u16 _channelID, u8 _reportID);
|
||||||
void SendReadDataReply(u16 _channelID, void* _Base, u16 _Address, int _Size);
|
void SendReadDataReply(u16 _channelID, void* _Base, u16 _Address, u8 _AddressHI, int _Size);
|
||||||
|
void SwapExtRegisters();
|
||||||
|
void HandlingMotionPlusWrites(u8* data, u8 addressHI, u32 address);
|
||||||
|
|
||||||
void FillReportAcc(wm_accel& _acc);
|
void FillReportAcc(wm_accel& _acc);
|
||||||
void FillReportInfo(wm_core& _core);
|
void FillReportInfo(wm_core& _core);
|
||||||
@ -60,7 +62,9 @@ void FillReportIRBasic(wm_ir_basic& _ir0, wm_ir_basic& _ir1);
|
|||||||
void FillReportExtension(wm_extension& _ext);
|
void FillReportExtension(wm_extension& _ext);
|
||||||
void FillReportClassicExtension(wm_classic_extension& _ext);
|
void FillReportClassicExtension(wm_classic_extension& _ext);
|
||||||
void FillReportGuitarHero3Extension(wm_GH3_extension& _ext);
|
void FillReportGuitarHero3Extension(wm_GH3_extension& _ext);
|
||||||
|
void FillReportMotionPlusNunchukExtension(wm_extension& _ext);
|
||||||
|
void FillReportMotionPlusNoExtension(wm_extension& _ext);
|
||||||
|
void FillReportMotionPlus(wm_extension& ext, bool extension);
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
#endif //_EMU_DECLARATIONS_
|
#endif //_EMU_DECLARATIONS_
|
||||||
|
@ -710,7 +710,8 @@ void FillReportIRBasic(wm_ir_basic& _ir0, wm_ir_basic& _ir1)
|
|||||||
);*/
|
);*/
|
||||||
// ------------------
|
// ------------------
|
||||||
}
|
}
|
||||||
|
/* Generate the 6 byte extension report for the motionplus&nunchuk, encrypted. The bytes
|
||||||
|
void FillReportExtension(wm_extension& _ext)
|
||||||
|
|
||||||
/* Generate the 6 byte extension report for the Nunchuck, encrypted. The bytes
|
/* Generate the 6 byte extension report for the Nunchuck, encrypted. The bytes
|
||||||
are JX JY AX AY AZ BT. */
|
are JX JY AX AY AZ BT. */
|
||||||
@ -1247,4 +1248,201 @@ void FillReportGuitarHero3Extension(wm_GH3_extension& _ext)
|
|||||||
memcpy(&_ext, Tmp, sizeof(_ext));
|
memcpy(&_ext, Tmp, sizeof(_ext));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Generate the 6 byte extension report for the MotionPlus Controller.
|
||||||
|
pass-through mode supported for MotionPlus+Nunchuk */
|
||||||
|
void FillReportMotionPlus(wm_extension& ext, bool extension){
|
||||||
|
|
||||||
|
//sending initial control packet, this must be sent first, its some kind of verifiation, all control bits are set to 0!
|
||||||
|
if (g_MotionPlusReadError[g_ID]) {
|
||||||
|
|
||||||
|
memcpy(&ext, motionpluscheck_id, sizeof(motionpluscheck_id));
|
||||||
|
g_MotionPlus[g_ID] = (extension) ? 1 : 0;
|
||||||
|
g_MotionPlusReadError[g_ID] = 0;
|
||||||
|
|
||||||
|
} //nunchuk inserted
|
||||||
|
else if (extension == 1) {
|
||||||
|
|
||||||
|
switch (g_InterleavedData[g_ID])
|
||||||
|
{
|
||||||
|
case false://MPlus
|
||||||
|
FillReportMotionPlusNoExtension(ext);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case true: //Nunchuk
|
||||||
|
FillReportMotionPlusNunchukExtension(ext);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
//alternate between nunchuk and wm+ interleaved reports
|
||||||
|
g_InterleavedData[g_ID] = g_InterleavedData[g_ID] ? false : true;
|
||||||
|
|
||||||
|
}//no additional extension inserted, no interleaving, always sending mp+ data
|
||||||
|
else if (extension == 0) {
|
||||||
|
|
||||||
|
FillReportMotionPlusNoExtension(ext);
|
||||||
|
g_InterleavedData[g_ID] = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FillReportMotionPlusNoExtension(wm_extension& _ext)
|
||||||
|
{
|
||||||
|
wm_mp_nc_0 ext;
|
||||||
|
memset(&ext, 0, sizeof(wm_mp_nc_0));
|
||||||
|
ext.YawLeftLS = 0x00; //dummy data atm
|
||||||
|
ext.RollLeftLS = 0x00;
|
||||||
|
ext.PitchDownLS = 0x00;
|
||||||
|
ext.pitchfast = 0x00;
|
||||||
|
ext.yawfast = 0x00;
|
||||||
|
ext.YawLeftHI = 0x00;
|
||||||
|
ext.ExtCon = 0x00; // 0 (important, since we don't use a nunchuk here)
|
||||||
|
ext.rollfast = 0x00;
|
||||||
|
ext.RollLeftHI = 0x00;
|
||||||
|
ext.dummy = 0x00; // 0 (important)
|
||||||
|
ext.mpdata = 0x01; //1 (important, using MP+ report instead of NC report)
|
||||||
|
ext.PitchDownHI = 0x00;
|
||||||
|
memcpy(&_ext, &ext, sizeof(ext));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void FillReportMotionPlusNunchukExtension(wm_extension& _ext)
|
||||||
|
{
|
||||||
|
wm_mp_nc_1 ext;
|
||||||
|
memset(&ext, 0, sizeof(wm_mp_nc_1));
|
||||||
|
ext.jx = g_nu.jx.center;
|
||||||
|
ext.jy = g_nu.jy.center;
|
||||||
|
|
||||||
|
// Use the neutral values
|
||||||
|
ext.ax = g_nu.cal_zero.x;
|
||||||
|
ext.ay = g_nu.cal_zero.y;
|
||||||
|
ext.az = g_nu.cal_zero.z + g_nu.cal_g.z;
|
||||||
|
ext.axLS = 0x00;
|
||||||
|
ext.ayLS = 0x00;
|
||||||
|
ext.azLS = 0x00;
|
||||||
|
|
||||||
|
ext.bz = 0x01;
|
||||||
|
ext.bc = 0x01;
|
||||||
|
ext.dummy = 0; //0 (important)
|
||||||
|
ext.mpdata = 0; //0 NC report, interleaved data (important)
|
||||||
|
ext.ExtCon = 1; // must be 1 when in NC-MP+ Mode
|
||||||
|
|
||||||
|
|
||||||
|
if (IsFocus())
|
||||||
|
{
|
||||||
|
int acc_x = g_nu.cal_zero.x;
|
||||||
|
int acc_y = g_nu.cal_zero.y;
|
||||||
|
int acc_z = g_nu.cal_zero.z + g_nu.cal_g.z;
|
||||||
|
|
||||||
|
if (IsKey(ENC_SHAKE) && !WiiMapping[g_ID].Motion.TiltNC.Shake)
|
||||||
|
WiiMapping[g_ID].Motion.TiltNC.Shake = 1;
|
||||||
|
|
||||||
|
// Step the shake simulation one step
|
||||||
|
ShakeToAccelerometer(acc_x, acc_y, acc_z, WiiMapping[g_ID].Motion.TiltNC);
|
||||||
|
|
||||||
|
// Tilt Nunchuck, allow the shake function to interrupt it
|
||||||
|
if (!WiiMapping[g_ID].Motion.TiltNC.Shake)
|
||||||
|
TiltNunchuck(acc_x, acc_y, acc_z);
|
||||||
|
|
||||||
|
// Boundary check
|
||||||
|
if (acc_x > 0xFF) acc_x = 0xFF;
|
||||||
|
else if (acc_x < 0x00) acc_x = 0x00;
|
||||||
|
if (acc_y > 0xFF) acc_y = 0xFF;
|
||||||
|
else if (acc_y < 0x00) acc_y = 0x00;
|
||||||
|
if (acc_z > 0xFF) acc_z = 0xFF;
|
||||||
|
else if (acc_z < 0x00) acc_z = 0x00;
|
||||||
|
|
||||||
|
ext.ax = acc_x;
|
||||||
|
ext.ay = acc_y;
|
||||||
|
ext.az = acc_z>>1;
|
||||||
|
ext.azLS = acc_z<<1; //LS0=0
|
||||||
|
|
||||||
|
|
||||||
|
// Update the analog stick
|
||||||
|
if (WiiMapping[g_ID].Stick.NC == FROM_KEYBOARD)
|
||||||
|
{
|
||||||
|
// Set the max values to the current calibration values
|
||||||
|
if(IsKey(ENC_L)) // x
|
||||||
|
ext.jx = g_nu.jx.min;
|
||||||
|
if(IsKey(ENC_R))
|
||||||
|
ext.jx = g_nu.jx.max;
|
||||||
|
|
||||||
|
if(IsKey(ENC_D)) // y
|
||||||
|
ext.jy = g_nu.jy.min;
|
||||||
|
if(IsKey(ENC_U))
|
||||||
|
ext.jy = g_nu.jy.max;
|
||||||
|
|
||||||
|
// On a real stick, the initialization value of center is 0x80,
|
||||||
|
// but after a first time touch, the center value automatically changes to 0x7F
|
||||||
|
if(ext.jx != g_nu.jx.center)
|
||||||
|
g_nu.jx.center = 0x7F;
|
||||||
|
if(ext.jy != g_nu.jy.center)
|
||||||
|
g_nu.jy.center = 0x7F;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Get adjusted pad state values
|
||||||
|
int _Lx = WiiMapping[g_ID].AxisState.Lx;
|
||||||
|
int _Ly = WiiMapping[g_ID].AxisState.Ly;
|
||||||
|
int _Rx = WiiMapping[g_ID].AxisState.Rx;
|
||||||
|
int _Ry = WiiMapping[g_ID].AxisState.Ry;
|
||||||
|
|
||||||
|
// The Y-axis is inverted
|
||||||
|
_Ly = 0xff - _Ly;
|
||||||
|
_Ry = 0xff - _Ry;
|
||||||
|
|
||||||
|
/* This is if we are also using a real Nunchuck that we are sharing the
|
||||||
|
calibration with. It's not needed if we are using our default
|
||||||
|
values. We adjust the values to the configured range, we even allow
|
||||||
|
the center to not be 0x80. */
|
||||||
|
if(g_nu.jx.max != 0xff || g_nu.jy.max != 0xff
|
||||||
|
|| g_nu.jx.min != 0 || g_nu.jy.min != 0
|
||||||
|
|| g_nu.jx.center != 0x80 || g_nu.jy.center != 0x80)
|
||||||
|
{
|
||||||
|
float Lx = (float)_Lx;
|
||||||
|
float Ly = (float)_Ly;
|
||||||
|
float Rx = (float)_Rx;
|
||||||
|
float Ry = (float)_Ry;
|
||||||
|
//float Tl = (float)_Tl;
|
||||||
|
//float Tr = (float)_Tr;
|
||||||
|
|
||||||
|
float XRangePos = (float) (g_nu.jx.max - g_nu.jx.center);
|
||||||
|
float XRangeNeg = (float) (g_nu.jx.center - g_nu.jx.min);
|
||||||
|
float YRangePos = (float) (g_nu.jy.max - g_nu.jy.center);
|
||||||
|
float YRangeNeg = (float) (g_nu.jy.center - g_nu.jy.min);
|
||||||
|
if (Lx > 0x80) Lx = Lx * (XRangePos / 128.0);
|
||||||
|
if (Lx < 0x80) Lx = Lx * (XRangeNeg / 128.0);
|
||||||
|
if (Lx == 0x80) Lx = (float)g_nu.jx.center;
|
||||||
|
if (Ly > 0x80) Ly = Ly * (YRangePos / 128.0);
|
||||||
|
if (Ly < 0x80) Ly = Ly * (YRangeNeg / 128.0);
|
||||||
|
if (Ly == 0x80) Lx = (float)g_nu.jy.center;
|
||||||
|
// Boundaries
|
||||||
|
_Lx = (int)Lx;
|
||||||
|
_Ly = (int)Ly;
|
||||||
|
_Rx = (int)Rx;
|
||||||
|
_Ry = (int)Ry;
|
||||||
|
if (_Lx > 0xff) _Lx = 0xff; if (_Lx < 0) _Lx = 0;
|
||||||
|
if (_Rx > 0xff) _Rx = 0xff; if (_Rx < 0) _Rx = 0;
|
||||||
|
if (_Ly > 0xff) _Ly = 0xff; if (_Ly < 0) _Ly = 0;
|
||||||
|
if (_Ry > 0xff) _Ry = 0xff; if (_Ry < 0) _Ry = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WiiMapping[g_ID].Stick.NC == FROM_ANALOG1)
|
||||||
|
{
|
||||||
|
ext.jx = _Lx;
|
||||||
|
ext.jy = _Ly;
|
||||||
|
}
|
||||||
|
else // ANALOG2
|
||||||
|
{
|
||||||
|
ext.jx = _Rx;
|
||||||
|
ext.jy = _Ry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(IsKey(ENC_C)) ext.bc = 0x00; ///////////////
|
||||||
|
if(IsKey(ENC_Z)) ext.bz = 0x00;
|
||||||
|
|
||||||
|
}
|
||||||
|
memcpy(&_ext, &ext, sizeof(ext));
|
||||||
|
}
|
||||||
|
|
||||||
} // end of namespace
|
} // end of namespace
|
||||||
|
@ -138,6 +138,39 @@ struct wm_classic_extension
|
|||||||
wm_cc_5 b2; // byte 5
|
wm_cc_5 b2; // byte 5
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct wm_mp_nc_0 //motionplus+nunchuk_pass-through
|
||||||
|
{
|
||||||
|
u8 YawLeftLS; //7e
|
||||||
|
u8 RollLeftLS; //82
|
||||||
|
u8 PitchDownLS; //83
|
||||||
|
u8 pitchfast : 1; //1
|
||||||
|
u8 yawfast : 1; //0
|
||||||
|
u8 YawLeftHI : 6; //01 1010 /1a
|
||||||
|
u8 ExtCon : 1; // 1 usually
|
||||||
|
u8 rollfast : 1; //0
|
||||||
|
u8 RollLeftHI : 6; //00 1010
|
||||||
|
u8 dummy : 1; // 0 usually. 1 in dem fall mhh
|
||||||
|
u8 mpdata : 1; //1 in this case, interleaved motion+ data
|
||||||
|
u8 PitchDownHI : 6;//01 1100
|
||||||
|
}; // default for yaw/roll/pitch around 0x1F7F
|
||||||
|
|
||||||
|
struct wm_mp_nc_1 //motionplus+nunchuk_pass-through
|
||||||
|
{
|
||||||
|
u8 jx;
|
||||||
|
u8 jy;
|
||||||
|
u8 ax;
|
||||||
|
u8 ay;
|
||||||
|
u8 ExtCon : 1; // 1 usually
|
||||||
|
u8 az : 7;
|
||||||
|
u8 dummy : 1; //0 always
|
||||||
|
u8 mpdata : 1; //0 when nunchuk interleaved data
|
||||||
|
u8 bz : 1;
|
||||||
|
u8 bc : 1;
|
||||||
|
u8 axLS : 1; // ls 1, ls0 = 0 by default,
|
||||||
|
u8 ayLS : 1;
|
||||||
|
u8 azLS : 2;
|
||||||
|
};
|
||||||
|
|
||||||
struct wm_GH3_extension
|
struct wm_GH3_extension
|
||||||
{
|
{
|
||||||
u8 SX : 6;
|
u8 SX : 6;
|
||||||
|
Loading…
Reference in New Issue
Block a user