diff --git a/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp b/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp index 38f10babec..3d3a88460c 100644 --- a/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp @@ -354,6 +354,7 @@ bool Wiimote::ProcessReadDataRequest() rpt.report_id = RT_READ_DATA_REPLY; auto reply = &rpt.data; + reply->error = 0; reply->buttons = m_status.buttons; reply->address = Common::swap16(m_read_request.address); @@ -377,6 +378,7 @@ bool Wiimote::ProcessReadDataRequest() // read the calibration data at the beginning of Eeprom. I think this // error is supposed to occur when we try to read above the freely // usable space that ends at 0x16ff. + INFO_LOG(WIIMOTE, "Responding with read error 8."); reply->error = 0x08; } else @@ -414,6 +416,7 @@ bool Wiimote::ProcessReadDataRequest() if (bytes_read != bytes_to_read) { // generate read error, 7 == no such slave (no ack) + INFO_LOG(WIIMOTE, "Responding with read error 7."); reply->error = 0x07; } } @@ -464,6 +467,8 @@ void Wiimote::DoState(PointerWrap& p) if (p.GetMode() == PointerWrap::MODE_READ) RealState(); + + // TODO: rebuild i2c bus state after state-change } // load real Wiimote state diff --git a/Source/Core/Core/HW/WiimoteEmu/MatrixMath.h b/Source/Core/Core/HW/WiimoteEmu/MatrixMath.h index e4daaad923..c3fa0d498d 100644 --- a/Source/Core/Core/HW/WiimoteEmu/MatrixMath.h +++ b/Source/Core/Core/HW/WiimoteEmu/MatrixMath.h @@ -6,6 +6,8 @@ #include +// TODO: kill this whole file, we have Matrix functions in MathUtil.h + #ifndef M_PI #define M_PI 3.14159265358979323846 #endif diff --git a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp index cb80af1f4a..a121ced085 100644 --- a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp @@ -18,6 +18,7 @@ #include "Common/Config/Config.h" #include "Common/MathUtil.h" #include "Common/MsgHandler.h" +#include "Common/BitUtils.h" #include "Core/Config/SYSCONFSettings.h" #include "Core/Config/WiimoteInputSettings.h" @@ -330,9 +331,18 @@ void Wiimote::Reset() memset(&m_camera_logic.reg_data, 0, sizeof(m_camera_logic.reg_data)); memset(&m_ext_logic.reg_data, 0, sizeof(m_ext_logic.reg_data)); - memset(&m_motion_plus_logic.reg_data, 0, sizeof(m_motion_plus_logic.reg_data)); + memset(&m_motion_plus_logic.reg_data, 0x00, sizeof(m_motion_plus_logic.reg_data)); memcpy(&m_motion_plus_logic.reg_data.ext_identifier, motion_plus_id, sizeof(motion_plus_id)); + // calibration hackery + static const u8 c1[16] = {0x78, 0xd9, 0x78, 0x38, 0x77, 0x9d, 0x2f, 0x0c, 0xcf, 0xf0, 0x31, 0xad, + 0xc8, 0x0b, 0x5e, 0x39}; + static const u8 c2[16] = {0x6f, 0x81, 0x7b, 0x89, 0x78, 0x51, 0x33, 0x60, 0xc9, 0xf5, 0x37, 0xc1, + 0x2d, 0xe9, 0x15, 0x8d}; + + //std::copy(std::begin(c1), std::end(c1), m_motion_plus_logic.reg_data.calibration_data); + //std::copy(std::begin(c2), std::end(c2), m_motion_plus_logic.reg_data.calibration_data + 0x10); + // status memset(&m_status, 0, sizeof(m_status)); @@ -828,12 +838,6 @@ void Wiimote::UpdateIRData(bool use_accel) } } -void Wiimote::UpdateExtData() -{ - // Write extension data to addr 0x00 of extension register - m_extension->GetState(m_ext_logic.reg_data.controller_data); -} - void Wiimote::Update() { // no channel == not connected i guess @@ -896,26 +900,27 @@ void Wiimote::Update() } // IR Camera - // TODO: kill use_accel param - // TODO: call only if camera logic is enabled? - UpdateIRData(rptf.accel_size != 0); + // TODO: kill use_accel param, I think it exists for TAS reasons.. + if (m_status.ir) + UpdateIRData(rptf.accel_size != 0); + if (rptf.ir_size) { + if (!m_status.ir) + WARN_LOG(WIIMOTE, "Game is reading IR data without enabling IR logic first."); + m_i2c_bus.BusRead(IRCameraLogic::DEVICE_ADDR, offsetof(IRCameraLogic::RegData, camera_data), rptf.ir_size, feature_ptr); feature_ptr += rptf.ir_size; } - // motion plus - auto* mplus_data = - reinterpret_cast(m_motion_plus_logic.reg_data.controller_data); - *mplus_data = wm_motionplus_data(); - mplus_data->is_mp_data = true; - - // extension - UpdateExtData(); + // extension / motion-plus if (rptf.ext_size) { + // Update extension first as motion-plus will read from it. + m_ext_logic.Update(); + m_motion_plus_logic.Update(); + m_i2c_bus.BusRead(ExtensionLogic::DEVICE_ADDR, 0x00, rptf.ext_size, feature_ptr); feature_ptr += rptf.ext_size; } @@ -1115,4 +1120,145 @@ bool Wiimote::ExtensionLogic::ReadDeviceDetectPin() return extension->active_extension ? true : false; } +void Wiimote::ExtensionLogic::Update() +{ + // Update controller data from user input: + // Write data to addr 0x00 of extension register + extension->GetState(reg_data.controller_data); +} + +// TODO: move this to Common if it doesn't exist already? +template +void SetBit(T& value, u32 bit_number, bool bit_value) +{ + bit_value ? value |= (1 << bit_number) : value &= ~(1 << bit_number); +} + +void Wiimote::MotionPlusLogic::Update() +{ + if (!IsActive()) + { + return; + } + + // TODO: clean up this hackery: + // the value seems to increase based on time starting after the first read of 0x00 + if (IsActive() && times_updated_since_activation < 0xff) + { + ++times_updated_since_activation; + + // TODO: wtf is this value actually.. + if (times_updated_since_activation == 9) + reg_data.initialization_status = 0x4; + else if (times_updated_since_activation == 10) + reg_data.initialization_status = 0x8; + else if (times_updated_since_activation == 18) + reg_data.initialization_status = 0xc; + else if (times_updated_since_activation == 53) + reg_data.initialization_status = 0xe; + } + + auto& mplus_data = *reinterpret_cast(reg_data.controller_data); + auto& data = reg_data.controller_data; + + // TODO: make sure a motion plus report is sent first after init + + // On real mplus: + // For some reason the first read seems to have garbage data + // is_mp_data and extension_connected are set, but the data is junk + // it does seem to have some sort of pattern though, byte 5 is always 2 + // something like: d5, b0, 4e, 6e, fc, 2 + // When a passthrough mode is set: + // the second read is valid mplus data, which then triggers a read from the extension + // the third read is finally extension data + // If an extension is not attached the data is always mplus data + // even when passthrough is enabled + + switch (GetPassthroughMode()) + { + case PassthroughMode::PASSTHROUGH_DISABLED: + { + mplus_data.is_mp_data = true; + break; + } + case PassthroughMode::PASSTHROUGH_NUNCHUK: + { + // If we sent mplus data last time now we will try to send ext data. + if (mplus_data.is_mp_data) + { + // The real mplus seems to only ever read 6 bytes from the extension + // bytes after 6 seem to be zero filled + // The real hardware uses these 6 bytes for the next frame, + // but we aren't going to do that + if (6 == i2c_bus.BusRead(ACTIVE_DEVICE_ADDR, 0x00, 6, data)) + { + // Passthrough data modifications via wiibrew.org + // Data passing through drops the least significant bit of the three accelerometer values + // Bit 7 of byte 5 is moved to bit 6 of byte 5, overwriting it + SetBit(data[5], 6, Common::ExtractBit(data[5], 7)); + // Bit 0 of byte 4 is moved to bit 7 of byte 5 + SetBit(data[5], 7, Common::ExtractBit(data[4], 0)); + // Bit 3 of byte 5 is moved to bit 4 of byte 5, overwriting it + SetBit(data[5], 4, Common::ExtractBit(data[5], 3)); + // Bit 1 of byte 5 is moved to bit 3 of byte 5 + SetBit(data[5], 3, Common::ExtractBit(data[5], 1)); + // Bit 0 of byte 5 is moved to bit 2 of byte 5, overwriting it + SetBit(data[5], 2, Common::ExtractBit(data[5], 0)); + + mplus_data.is_mp_data = false; + } + } + break; + } + case PassthroughMode::PASSTHROUGH_CLASSIC: + { + // If we sent mplus data last time now we will try to send ext data. + if (mplus_data.is_mp_data) + { + if (6 == i2c_bus.BusRead(ACTIVE_DEVICE_ADDR, 0x00, 6, data)) + { + // Passthrough data modifications via wiibrew.org + // Data passing through drops the least significant bit of the axes of the left (or only) joystick + // Bit 0 of Byte 4 is overwritten [by the 'extension_connected' flag] + // Bits 0 and 1 of Byte 5 are moved to bit 0 of Bytes 0 and 1, overwriting what was there before + SetBit(data[0], 0, Common::ExtractBit(data[5], 0)); + SetBit(data[1], 0, Common::ExtractBit(data[5], 1)); + + mplus_data.is_mp_data = false; + } + } + break; + } + default: + PanicAlert("MotionPlus unknown passthrough-mode %d", GetPassthroughMode()); + break; + } + + // If the above logic determined this should be mp data, update it here + if (mplus_data.is_mp_data) + { + // Wiibrew: "While the Wiimote is still, the values will be about 0x1F7F (8,063)" + u16 yaw_value = 0x1F7F; + u16 roll_value = 0x1F7F; + u16 pitch_value = 0x1F7F; + + mplus_data.yaw_slow = 1; + mplus_data.roll_slow = 1; + mplus_data.pitch_slow = 1; + + // Bits 0-7 + mplus_data.yaw1 = yaw_value & 0xff; + mplus_data.roll1 = roll_value & 0xff; + mplus_data.pitch1 = pitch_value & 0xff; + + // Bits 8-13 + mplus_data.yaw1 = yaw_value >> 8; + mplus_data.roll1 = roll_value >> 8; + mplus_data.pitch1 = pitch_value >> 8; + } + + mplus_data.extension_connected = extension_port.IsDeviceConnected(); + mplus_data.zero = 0; +} + } // namespace WiimoteEmu diff --git a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h index 4b46e2ccbd..cd6a283d1b 100644 --- a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h +++ b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h @@ -4,9 +4,9 @@ #pragma once +#include #include #include -#include #include "Common/Logging/Log.h" #include "Core/HW/WiimoteCommon/WiimoteHid.h" @@ -266,9 +266,10 @@ public: void Reset() { m_slaves.clear(); } + // TODO: change int to u16 or something int BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) { - INFO_LOG(WIIMOTE, "i2c bus read: 0x%02x @ 0x%02x (%d)", slave_addr, addr, count); + // INFO_LOG(WIIMOTE, "i2c bus read: 0x%02x @ 0x%02x (%d)", slave_addr, addr, count); for (auto& slave : m_slaves) { auto const bytes_read = slave->BusRead(slave_addr, addr, count, data_out); @@ -281,9 +282,13 @@ public: return 0; } + // TODO: change int to u16 or something int BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) { - INFO_LOG(WIIMOTE, "i2c bus write: 0x%02x @ 0x%02x (%d)", slave_addr, addr, count); + // TODO: write in blocks of 6 to simulate the real bus + // this might trigger activation writes more accurately + + // INFO_LOG(WIIMOTE, "i2c bus write: 0x%02x @ 0x%02x (%d)", slave_addr, addr, count); for (auto& slave : m_slaves) { auto const bytes_written = slave->BusWrite(slave_addr, addr, count, data_in); @@ -386,7 +391,6 @@ protected: void GetButtonData(u8* data); void GetAccelData(u8* data); void UpdateIRData(bool use_accel); - void UpdateExtData(); private: I2CBus m_i2c_bus; @@ -438,18 +442,35 @@ private: static const u8 DEVICE_ADDR = 0x52; + void Update(); + private: int BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) override { if (DEVICE_ADDR != slave_addr) return 0; + if (0x00 == addr) + { + // Here we should sample user input and update controller data + // Moved into Update() function for TAS determinism + // TAS code fails to sync data reads and such.. + // extension->GetState(reg_data.controller_data); + } + auto const result = RawRead(®_data, addr, count, data_out); // Encrypt data read from extension register // Check if encrypted reads is on if (0xaa == reg_data.encryption) + { + INFO_LOG(WIIMOTE, "Encrypted read."); WiimoteEncrypt(&ext_key, data_out, addr, (u8)count); + } + else + { + INFO_LOG(WIIMOTE, "Unencrypted read."); + } return result; } @@ -538,19 +559,30 @@ private: // The port on the end of the motion plus: ExtensionPort extension_port{i2c_bus}; + void Update(); + #pragma pack(push, 1) struct MotionPlusRegister { - u8 controller_data[0x10]; - u8 unknown[0x10]; + u8 controller_data[21]; + u8 unknown[11]; + + // address 0x20 u8 calibration_data[0x20]; u8 unknown2[0xb0]; // address 0xF0 - // TODO: bad name - u8 activated; + u8 initialized; - u8 unknown3[9]; + u8 unknown3[6]; + + // address 0xf7 + // Wii Sports Resort reads regularly and claims mplus is disconnected if not to its liking + // Value starts at 0x00 and goes up after activation (not initialization) + // Immediately returns 0x02, even still after 15 and 30 seconds + u8 initialization_status; + + u8 unknown4[2]; // address 0xFA u8 ext_identifier[6]; @@ -559,19 +591,35 @@ private: static_assert(0x100 == sizeof(reg_data)); + private: static const u8 INACTIVE_DEVICE_ADDR = 0x53; static const u8 ACTIVE_DEVICE_ADDR = 0x52; + enum class PassthroughMode : u8 + { + PASSTHROUGH_DISABLED = 0x04, + PASSTHROUGH_NUNCHUK = 0x05, + PASSTHROUGH_CLASSIC = 0x07, + }; + + // TODO: savestate + u8 times_updated_since_activation = 0; + bool IsActive() const { return ACTIVE_DEVICE_ADDR << 1 == reg_data.ext_identifier[2]; } - u8 GetPassthroughMode() const { return reg_data.ext_identifier[4]; } + PassthroughMode GetPassthroughMode() const + { + return static_cast(reg_data.ext_identifier[4]); + } - // TODO: when activated it seems the motion plus continuously reads from the ext port even when not in passthrough mode - // 16 bytes it seems - - // It also sends the 0x55 to 0xf0 activation + // TODO: when activated it seems the motion plus reactivates the extension + // It sends 0x55 to 0xf0 // It also writes 0x00 to slave:0x52 addr:0xfa for some reason // And starts a write to 0xfa but never writes bytes.. + // It tries to read data at 0x00 for 3 times (failing) + // then it reads the 16 bytes of calibration at 0x20 and stops + + // TODO: if an extension is attached after activation, it also does this. int BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) override { @@ -605,15 +653,17 @@ private: auto const result = RawWrite(®_data, addr, count, data_in); return result; - // TODO: Should any write (of any value) trigger activation? + // It seems a write of any value triggers deactivation. if (0xf0 == addr) { // Deactivate motion plus: reg_data.ext_identifier[2] = INACTIVE_DEVICE_ADDR << 1; + times_updated_since_activation = 0; } } else { + // No i2c passthrough when activated. return 0; } } @@ -623,11 +673,18 @@ private: { auto const result = RawWrite(®_data, addr, count, data_in); - // TODO: Should any write (of any value) trigger activation? + // It seems a write of any value triggers activation. if (0xfe == addr) { + INFO_LOG(WIIMOTE, "Motion Plus has been activated with value: %d", data_in[0]); + // Activate motion plus: reg_data.ext_identifier[2] = ACTIVE_DEVICE_ADDR << 1; + times_updated_since_activation = 0x2; + + // Test some hax + std::array data = {0x55}; + i2c_bus.BusWrite(ACTIVE_DEVICE_ADDR, 0xf0, (int)data.size(), data.data()); } return result;