// Copyright (C) 2003 Dolphin Project. // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 2.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 2.0 for more details. // A copy of the GPL 2.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official SVN repository and contact information can be found at // http://code.google.com/p/dolphin-emu/ #include #include #include "Common.h" // Common #include "StringUtil.h" // for ArrayToString() #include "IniFile.h" #include "pluginspecs_wiimote.h" #include "EmuDefinitions.h" // Local #include "main.h" #include "wiimote_hid.h" #include "EmuSubroutines.h" #include "EmuMain.h" #include "Encryption.h" // for extension encryption #include "Config.h" // for g_Config extern SWiimoteInitialize g_WiimoteInitialize; namespace WiiMoteEmu { // Settings accel_cal g_wm; nu_cal g_nu; cc_cal g_ClassicContCalibration; gh3_cal g_GH3Calibration; bool g_EmulatedWiiMoteInitialized = false; /* Homebrew encryption for 16 byte zero keys. */ void CryptBuffer(u8* _buffer, u8 _size) { for (int i=0; i<_size; i++) { _buffer[i] = ((_buffer[i] - 0x17) ^ 0x17) & 0xFF; } } void WriteCrypted16(u8* _baseBlock, u16 _address, u16 _value) { u16 cryptedValue = _value; CryptBuffer((u8*)&cryptedValue, sizeof(u16)); *(u16*)(_baseBlock + _address) = cryptedValue; } /* Calculate Extenstion Regisister Calibration Checksum */ // This function is not currently used, it's just here to show how the values // in EmuDefinitions.h are calculated. void GetCalibrationChecksum() { u8 sum = 0; for (u32 i = 0; i < sizeof(nunchuck_calibration) - 2; i++) sum += nunchuck_calibration[i]; INFO_LOG(WIIMOTE, "0x%02x 0x%02x", (sum + 0x55), (sum + 0xaa)); } // Calculate checksum for the nunchuck calibration. The last two bytes. void ExtensionChecksum(u8 * Calibration) { u8 sum = 0; //u8 Byte15, Byte16; for (u32 i = 0; i < sizeof(Calibration) - 2; i++) { sum += Calibration[i]; //INFO_LOG(WIIMOTE, "Plus 0x%02x", Calibration[i]); } // Byte15 = sum + 0x55; // Byte 15 // Byte16 = sum + 0xaa; // Byte 16 } /* Bit shift conversions */ u32 convert24bit(const u8* src) { return (src[0] << 16) | (src[1] << 8) | src[2]; } u16 convert16bit(const u8* src) { return (src[0] << 8) | src[1]; } /* Load pre-recorded movements */ void LoadRecordedMovements() { INFO_LOG(WIIMOTE, "LoadRecordedMovements()"); IniFile file; file.Load((std::string(File::GetUserPath(D_CONFIG_IDX)) + "WiimoteMovement.ini").c_str()); for(int i = 0; i < RECORDING_ROWS; i++) { //INFO_LOG(WIIMOTE, "Recording%i ", i + 1); // Temporary storage int iTmp; std::string STmp; // First clear the list VRecording.at(i).Recording.clear(); // Get row name std::string SaveName = StringFromFormat("Recording%i", i + 1); // Get movement std::string TmpMovement; file.Get(SaveName.c_str(), "Movement", &TmpMovement, ""); // Get IR std::string TmpIR; file.Get(SaveName.c_str(), "IR", &TmpIR, ""); // Get time std::string TmpTime; file.Get(SaveName.c_str(), "Time", &TmpTime, ""); // Get IR bytes int TmpIRBytes; file.Get(SaveName.c_str(), "IRBytes", &TmpIRBytes, 0); VRecording.at(i).IRBytes = TmpIRBytes; SRecording Tmp; for (int j = 0, k = 0, l = 0; (u32)j < TmpMovement.length(); j+=13) { // Skip blank savings if (TmpMovement.length() < 3) continue; // Avoid going to far, this can only happen with modified ini files, but we check for it anyway if (TmpMovement.length() < (u32)j + 12) continue; // Skip old style recordings if (TmpMovement.substr(j, 1) != "-" && TmpMovement.substr(j, 1) != "+") continue; std::string StrX = TmpMovement.substr(j, 4); std::string StrY = TmpMovement.substr(j + 4, 4); std::string StrZ = TmpMovement.substr(j + 8, 4); Tmp.x = atoi(StrX.c_str()); Tmp.y = atoi(StrY.c_str()); Tmp.z = atoi(StrZ.c_str()); // Go to next set of IR values // If there is no IR data saving we fill the array with // zeroes. This should only be able to occur from manual ini // editing but we check for it anyway for(int ii = 0; ii < TmpIRBytes; ii++) { if(TmpIR.length() < (u32)(k + i + TmpIRBytes)) continue; // Safety check std::string TmpStr = TmpIR.substr(k + ii*2, 2); u32 TmpU32; AsciiToHex(TmpStr.c_str(), TmpU32); Tmp.IR[ii] = (u8)TmpU32; } if (TmpIRBytes == 10) k += (10*2 + 1); else k += (12*2 + 1); // Go to next set of time values double Time = (double)atoi(TmpTime.substr(l, 5).c_str()); Tmp.Time = (double)(Time/1000); l += 6; // Save the values VRecording.at(i).Recording.push_back(Tmp); // Log results /*INFO_LOG(WIIMOTE, "Time:%f", Tmp.Time); std::string TmpIRLog = ArrayToString(Tmp.IR, TmpIRBytes, 0, 30); INFO_LOG(WIIMOTE, "IR: %s", TmpIRLog.c_str()); INFO_LOG(WIIMOTE, "");*/ } // Get HotKey file.Get(SaveName.c_str(), "HotKeySwitch", &iTmp, 3); VRecording.at(i).HotKeySwitch = iTmp; file.Get(SaveName.c_str(), "HotKeyWiimote", &iTmp, 10); VRecording.at(i).HotKeyWiimote = iTmp; file.Get(SaveName.c_str(), "HotKeyNunchuck", &iTmp, 10); VRecording.at(i).HotKeyNunchuck = iTmp; file.Get(SaveName.c_str(), "HotKeyIR", &iTmp, 10); VRecording.at(i).HotKeyIR = iTmp; // Get Recording speed int TmpPlaybackSpeed; file.Get(SaveName.c_str(), "PlaybackSpeed", &TmpPlaybackSpeed, -1); VRecording.at(i).PlaybackSpeed = TmpPlaybackSpeed; // Logging /*std::string TmpIRLog; if(TmpIRBytes > 0 && VRecording.size() > i) TmpIRLog = ArrayToString(VRecording.at(i).Recording.at(0).IR, TmpIRBytes, 0, 30); else TmpIRLog = ""; INFO_LOG(WIIMOTE, "Size:%i HotKey:%i PlSpeed:%i IR:%s X:%i Y:%i Z:%i", VRecording.at(i).Recording.size(), VRecording.at(i).HotKeyWiimote, VRecording.at(i).PlaybackSpeed, TmpIRLog.c_str(), VRecording.at(i).Recording.at(0).x, VRecording.at(i).Recording.at(0).y, VRecording.at(i).Recording.at(0).z );*/ } } /* Calibrate the mouse position to the emulation window. g_WiimoteInitialize.hWnd is the rendering window handle. */ void GetMousePos(float& x, float& y) { #ifdef _WIN32 POINT point; // Get the cursor position for the entire screen GetCursorPos(&point); // Get the cursor position relative to the upper left corner of the rendering window ScreenToClient(g_WiimoteInitialize.hWnd, &point); // Get the size of the rendering window. (In my case Rect.top and Rect.left was zero.) RECT Rect; GetClientRect(g_WiimoteInitialize.hWnd, &Rect); // Width and height is the size of the rendering window float WinWidth = (float)(Rect.right - Rect.left); float WinHeight = (float)(Rect.bottom - Rect.top); float XOffset = 0, YOffset = 0; float PictureWidth = WinWidth, PictureHeight = WinHeight; #else #if defined(HAVE_X11) && HAVE_X11 float WinWidth = 0, WinHeight = 0; float XOffset = 0, YOffset = 0; int root_x, root_y, win_x, win_y; if (IsFocus()) { Window GLWin = *(Window *)g_WiimoteInitialize.pXWindow; XWindowAttributes WinAttribs; XGetWindowAttributes (WMdisplay, GLWin, &WinAttribs); WinWidth = (float)WinAttribs.width; WinHeight = (float)WinAttribs.height; Window rootDummy, childWin; unsigned int mask; XQueryPointer(WMdisplay, GLWin, &rootDummy, &childWin, &root_x, &root_y, &win_x, &win_y, &mask); } float PictureWidth = WinWidth, PictureHeight = WinHeight; #endif #endif #if defined(_WIN32) || (defined(HAVE_X11) && HAVE_X11) /* Calculate the actual picture size and location */ // Output: PictureWidth, PictureHeight, XOffset, YOffset if (g_Config.bKeepAR43 || g_Config.bKeepAR169) { // The rendering window aspect ratio as a proportion of the 4:3 or 16:9 ratio float Ratio = WinWidth / WinHeight / (g_Config.bKeepAR43 ? (4.0f / 3.0f) : (16.0f / 9.0f)); // Check if height or width is the limiting factor. If ratio > 1 the picture is to wide and have to limit the width. if (Ratio > 1) { // Calculate the new width and height for glViewport, this is not the actual size of either the picture or the screen PictureWidth = WinWidth / Ratio; // Calculate the new X offset // Move the left of the picture to the middle of the screen XOffset = XOffset + WinWidth / 2.0f; // Then remove half the picture height to move it to the horizontal center XOffset = XOffset - PictureWidth / 2.0f; } // The window is to high, we have to limit the height else { // Calculate the new width and height for glViewport, this is not the actual size of either the picture or the screen // Invert the ratio to make it > 1 Ratio = 1.0f / Ratio; PictureHeight = WinHeight / Ratio; // Calculate the new Y offset // Move the top of the picture to the middle of the screen YOffset = YOffset + WinHeight / 2.0f; // Then remove half the picture height to move it to the vertical center YOffset = YOffset - PictureHeight / 2.0f; } /* INFO_LOG(WIIMOTE, "Screen Width:%4.0f Height:%4.0f Ratio:%1.2f", WinWidth, WinHeight, Ratio); INFO_LOG(WIIMOTE, "Picture Width:%4.1f Height:%4.1f YOffset:%4.0f XOffset:%4.0f", PictureWidth, PictureHeight, YOffset, XOffset); */ } // Crop the picture from 4:3 to 5:4 or from 16:9 to 16:10. // Output: PictureWidth, PictureHeight, XOffset, YOffset if ((g_Config.bKeepAR43 || g_Config.bKeepAR169) && g_Config.bCrop) { float Ratio = g_Config.bKeepAR43 ? ((4.0f / 3.0f) / (5.0f / 4.0f)) : (((16.0f / 9.0f) / (16.0f / 10.0f))); // The width and height we will add (calculate this before PictureWidth and PictureHeight is adjusted) float IncreasedWidth = (Ratio - 1.0f) * PictureWidth; float IncreasedHeight = (Ratio - 1.0f) * PictureHeight; // The new width and height PictureWidth = PictureWidth * Ratio; PictureHeight = PictureHeight * Ratio; // Adjust the X and Y offset XOffset = float(XOffset - (IncreasedWidth / 2.0)); YOffset = float(YOffset - (IncreasedHeight / 2.0)); /* INFO_LOG(WIIMOTE, "Crop Ratio:%1.2f IncrWidth:%3.0f IncrHeight:%3.0f", Ratio, IncreasedWidth, IncreasedHeight); INFO_LOG(WIIMOTE, "Picture Width:%4.1f Height:%4.1f YOffset:%4.0f XOffset:%4.0f", PictureWidth, PictureHeight, YOffset, XOffset); */ } #endif // Return the mouse position as a fraction of one, inside the picture, with (0.0, 0.0) being the upper left corner of the picture #ifdef _WIN32 x = ((float)point.x - XOffset) / PictureWidth; y = ((float)point.y - YOffset) / PictureHeight; /* INFO_LOG(WIIMOTE, "GetCursorPos: %i %i", point.x, point.y); INFO_LOG(WIIMOTE, "GetClientRect: %i %i %i %i", Rect.left, Rect.right, Rect.top, Rect.bottom); INFO_LOG(WIIMOTE, "Position X:%1.2f Y:%1.2f", x, y); */ #else #if defined(HAVE_X11) && HAVE_X11 x = ((float)win_x - XOffset) / PictureWidth; y = ((float)win_y - YOffset) / PictureHeight; #endif #endif } /* This is not needed if we call FreeLibrary() when we stop a game, but if it's not called we need to reset these variables. */ void Shutdown() { INFO_LOG(WIIMOTE, "ShutDown"); ResetVariables(); // We can't close it here or it might crash // because the joystick could have been closed already in GCPad // But there is no easy way to know the situation of another DLL // So we just skip the close procedure here /* // Close joypads Close_Devices(); // Finally close SDL if (SDL_WasInit(0)) SDL_Quit(); */ for (int i=0; itype, hidp->param); switch(hidp->type) { case HID_TYPE_DATA: { switch(hidp->param) { case HID_PARAM_OUTPUT: { wm_report* sr = (wm_report*)hidp->data; HidOutputReport(_channelID, sr); /* This is the 0x22 answer to all Inputs.*/ // There are no 0x22 replys to these report from the real // wiimote from what I could see Report 0x10 that seems to // be only used for rumble, and we don't need to answer // that // The rumble report still needs more investigation } break; default: PanicAlert("HidInput: HID_TYPE_DATA - param 0x%02x", hidp->type, hidp->param); break; } } break; default: PanicAlert("HidInput: Unknown type 0x%02x and param 0x%02x", hidp->type, hidp->param); break; } } void ControlChannel(int _number, u16 _channelID, const void* _pData, u32 _Size) { g_ID = _number; hid_packet* hidp = (hid_packet*)_pData; INFO_LOG(WIIMOTE, "Emu ControlChannel (page: %i, type: 0x%02x, param: 0x%02x)", _number, hidp->type, hidp->param); switch(hidp->type) { case HID_TYPE_HANDSHAKE: PanicAlert("HID_TYPE_HANDSHAKE - %s", (hidp->param == HID_PARAM_INPUT) ? "INPUT" : "OUPUT"); break; case HID_TYPE_SET_REPORT: if (hidp->param == HID_PARAM_INPUT) { PanicAlert("HID_TYPE_SET_REPORT - INPUT"); } else { // AyuanX: My experiment shows Control Channel is never used // shuffle2: but homebrew uses this, so we'll do what we must :) HidOutputReport(_channelID, (wm_report*)hidp->data); u8 handshake = HID_HANDSHAKE_SUCCESS; g_WiimoteInitialize.pWiimoteInput(g_ID, _channelID, &handshake, 1); PanicAlert("HID_TYPE_DATA - OUTPUT: Ambiguous Control Channel Report!"); } break; case HID_TYPE_DATA: PanicAlert("HID_TYPE_DATA - %s", (hidp->param == HID_PARAM_INPUT) ? "INPUT" : "OUTPUT"); break; default: PanicAlert("HidControlChannel: Unknown type %x and param %x", hidp->type, hidp->param); break; } } /* This is called from Wiimote_Update(). See SystemTimers.cpp for a documentation. I'm not sure exactly how often this function is called but I think it's tied to the frame rate of the game rather than a certain amount of times per second. */ void Update(int _number) { if (g_ReportingAuto[_number] == false) return; g_ID = _number; // Read input or not if (WiiMapping[g_ID].Source == 1) { // Check if the pad state should be updated if (NumGoodPads > 0 && joyinfo.size() > (u32)WiiMapping[g_ID].ID) UpdatePadState(WiiMapping[g_ID]); } switch(g_ReportingMode[g_ID]) { case 0: break; case WM_REPORT_CORE: SendReportCore(g_ReportingChannel[g_ID]); break; case WM_REPORT_CORE_ACCEL: SendReportCoreAccel(g_ReportingChannel[g_ID]); break; case WM_REPORT_CORE_ACCEL_IR12: SendReportCoreAccelIr12(g_ReportingChannel[g_ID]); break; case WM_REPORT_CORE_ACCEL_EXT16: SendReportCoreAccelExt16(g_ReportingChannel[g_ID]); break; case WM_REPORT_CORE_ACCEL_IR10_EXT6: SendReportCoreAccelIr10Ext(g_ReportingChannel[g_ID]); break; } } } // end of namespace