505 lines
14 KiB
C++
505 lines
14 KiB
C++
// 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/
|
|
|
|
|
|
/* HID reports access guide. */
|
|
|
|
/* 0x10 - 0x1a Output EmuMain.cpp: HidOutputReport()
|
|
0x10 - 0x14: General
|
|
0x15: Status report request from the Wii
|
|
0x16 and 0x17: Write and read memory or registers
|
|
0x19 and 0x1a: General
|
|
0x20 - 0x22 Input EmuMain.cpp: HidOutputReport() to the destination
|
|
0x15 leads to a 0x20 Input report
|
|
0x17 leads to a 0x21 Input report
|
|
0x10 - 0x1a leads to a 0x22 Input report
|
|
0x30 - 0x3f Input This file: Update() */
|
|
|
|
#include <vector>
|
|
#include <string>
|
|
#include <fstream>
|
|
|
|
#include "Common.h" // Common
|
|
#include "FileUtil.h"
|
|
#include "pluginspecs_wiimote.h"
|
|
|
|
#include "WiimoteEmu.h"
|
|
#include "Attachment/Attachment.h"
|
|
|
|
/* 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];
|
|
}
|
|
|
|
namespace WiimoteEmu
|
|
{
|
|
|
|
void Wiimote::ReportMode(const u16 _channelID, wm_report_mode* dr)
|
|
{
|
|
//INFO_LOG(WIIMOTE, "Set data report mode");
|
|
//DEBUG_LOG(WIIMOTE, " Rumble: %x", dr->rumble);
|
|
//DEBUG_LOG(WIIMOTE, " Continuous: %x", dr->continuous);
|
|
//DEBUG_LOG(WIIMOTE, " All The Time: %x", dr->all_the_time);
|
|
//DEBUG_LOG(WIIMOTE, " Mode: 0x%02x", dr->mode);
|
|
|
|
m_reporting_auto = dr->all_the_time;
|
|
m_reporting_mode = dr->mode;
|
|
m_reporting_channel = _channelID;
|
|
|
|
// reset IR camera
|
|
//memset(m_reg_ir, 0, sizeof(*m_reg_ir)); //ugly hack
|
|
|
|
if (0 == dr->all_the_time)
|
|
PanicAlert("Wiimote: Reporting Always is set to OFF! Everything should be fine, but games never do this.");
|
|
|
|
if (dr->mode >= WM_REPORT_INTERLEAVE1)
|
|
PanicAlert("Wiimote: Unsupported Reporting mode.");
|
|
else if (dr->mode < WM_REPORT_CORE)
|
|
PanicAlert("Wiimote: Reporting mode < 0x30.");
|
|
}
|
|
|
|
/* Here we process the Output Reports that the Wii sends. Our response will be
|
|
an Input Report back to the Wii. Input and Output is from the Wii's
|
|
perspective, Output means data to the Wiimote (from the Wii), Input means
|
|
data from the Wiimote.
|
|
|
|
The call browser:
|
|
|
|
1. Wiimote_InterruptChannel > InterruptChannel > HidOutputReport
|
|
2. Wiimote_ControlChannel > ControlChannel > HidOutputReport
|
|
|
|
The IR enable/disable and speaker enable/disable and mute/unmute values are
|
|
bit2: 0 = Disable (0x02), 1 = Enable (0x06)
|
|
*/
|
|
void Wiimote::HidOutputReport(const u16 _channelID, wm_report* sr)
|
|
{
|
|
INFO_LOG(WIIMOTE, "HidOutputReport (page: %i, cid: 0x%02x, wm: 0x%02x)", m_index, _channelID, sr->wm);
|
|
|
|
// wiibrew:
|
|
// In every single Output Report, bit 0 (0x01) of the first byte controls the Rumble feature.
|
|
m_rumble_on = (sr->data[0] & 0x01) != 0;
|
|
|
|
switch (sr->wm)
|
|
{
|
|
case WM_RUMBLE : // 0x10
|
|
// this is handled above
|
|
return; // no ack
|
|
break;
|
|
|
|
case WM_LEDS : // 0x11
|
|
//INFO_LOG(WIIMOTE, "Set LEDs: 0x%02x", sr->data[0]);
|
|
m_status.leds = sr->data[0] >> 4;
|
|
break;
|
|
|
|
case WM_REPORT_MODE : // 0x12
|
|
ReportMode(_channelID, (wm_report_mode*)sr->data);
|
|
break;
|
|
|
|
case WM_IR_PIXEL_CLOCK : // 0x13
|
|
//INFO_LOG(WIIMOTE, "WM IR Clock: 0x%02x", sr->data[0]);
|
|
//m_ir_clock = (sr->data[0] & 0x04) ? 1 : 0;
|
|
break;
|
|
|
|
case WM_SPEAKER_ENABLE : // 0x14
|
|
//INFO_LOG(WIIMOTE, "WM Speaker Enable: 0x%02x", sr->data[0]);
|
|
//PanicAlert( "WM Speaker Enable: %d", sr->data[0] );
|
|
m_status.speaker = (sr->data[0] & 0x04) ? 1 : 0;
|
|
break;
|
|
|
|
case WM_REQUEST_STATUS : // 0x15
|
|
RequestStatus(_channelID, (wm_request_status*)sr->data);
|
|
return; // sends its own ack
|
|
break;
|
|
|
|
case WM_WRITE_DATA : // 0x16
|
|
WriteData(_channelID, (wm_write_data*)sr->data);
|
|
break;
|
|
|
|
case WM_READ_DATA : // 0x17
|
|
ReadData(_channelID, (wm_read_data*)sr->data);
|
|
return; // sends its own ack
|
|
break;
|
|
|
|
case WM_WRITE_SPEAKER_DATA : // 0x18
|
|
#ifdef USE_WIIMOTE_EMU_SPEAKER
|
|
SpeakerData((wm_speaker_data*)sr->data);
|
|
#endif
|
|
// TODO: Does this need an ack?
|
|
return; // no ack
|
|
break;
|
|
|
|
case WM_SPEAKER_MUTE : // 0x19
|
|
//INFO_LOG(WIIMOTE, "WM Speaker Mute: 0x%02x", sr->data[0]);
|
|
//PanicAlert( "WM Speaker Mute: %d", sr->data[0] & 0x04 );
|
|
#ifdef USE_WIIMOTE_EMU_SPEAKER
|
|
// testing
|
|
if (sr->data[0] & 0x04)
|
|
memset(&m_channel_status, 0, sizeof(m_channel_status));
|
|
#endif
|
|
m_speaker_mute = (sr->data[0] & 0x04) ? 1 : 0;
|
|
break;
|
|
|
|
case WM_IR_LOGIC: // 0x1a
|
|
// comment from old plugin:
|
|
// This enables or disables the IR lights, we update the global variable g_IR
|
|
// so that WmRequestStatus() knows about it
|
|
//INFO_LOG(WIIMOTE, "WM IR Enable: 0x%02x", sr->data[0]);
|
|
m_status.ir = (sr->data[0] & 0x04) ? 1 : 0;
|
|
break;
|
|
|
|
default:
|
|
PanicAlert("HidOutputReport: Unknown channel 0x%02x", sr->wm);
|
|
return; // no ack
|
|
break;
|
|
}
|
|
|
|
// send ack
|
|
SendAck(_channelID, sr->wm);
|
|
}
|
|
|
|
/* This will generate the 0x22 acknowledgement for most Input reports.
|
|
It has the form of "a1 22 00 00 _reportID 00".
|
|
The first two bytes are the core buttons data,
|
|
00 00 means nothing is pressed.
|
|
The last byte is the success code 00. */
|
|
void Wiimote::SendAck(const u16 _channelID, u8 _reportID)
|
|
{
|
|
u8 data[6];
|
|
|
|
data[0] = 0xA1;
|
|
data[1] = WM_ACK_DATA;
|
|
|
|
wm_acknowledge* const ack = (wm_acknowledge*)(data + 2);
|
|
|
|
ack->buttons = m_status.buttons;
|
|
ack->reportID = _reportID;
|
|
ack->errorID = 0;
|
|
|
|
g_WiimoteInitialize.pWiimoteInterruptChannel( m_index, _channelID, data, sizeof(data));
|
|
}
|
|
|
|
// old comment
|
|
/* Here we produce a 0x20 status report to send to the Wii. We currently ignore
|
|
the status request rs and all its eventual instructions it may include (for
|
|
example turn off rumble or something else) and just send the status
|
|
report. */
|
|
void Wiimote::RequestStatus(const u16 _channelID, wm_request_status* rs)
|
|
{
|
|
//if (rs)
|
|
|
|
// handle switch extension
|
|
if ( m_extension->active_extension != m_extension->switch_extension )
|
|
{
|
|
// if an extension is currently connected and we want to switch to a different extension
|
|
if ( (m_extension->active_extension > 0) && m_extension->switch_extension )
|
|
// detach extension first, wait til next Update() or RequestStatus() call to change to the new extension
|
|
m_extension->active_extension = 0;
|
|
else
|
|
// set the wanted extension
|
|
m_extension->active_extension = m_extension->switch_extension;
|
|
|
|
// update status struct
|
|
m_status.extension = m_extension->active_extension ? 1 : 0;
|
|
|
|
// set register, I hate this line
|
|
m_register[ 0xa40000 ] = ((WiimoteEmu::Attachment*)m_extension->attachments[ m_extension->active_extension ])->reg;
|
|
}
|
|
|
|
// set up report
|
|
u8 data[8];
|
|
data[0] = 0xA1;
|
|
data[1] = WM_STATUS_REPORT;
|
|
|
|
// status values
|
|
*(wm_status_report*)(data + 2) = m_status;
|
|
|
|
// send report
|
|
g_WiimoteInitialize.pWiimoteInterruptChannel(m_index, _channelID, data, sizeof(data));
|
|
}
|
|
|
|
/* Write data to Wiimote and Extensions registers. */
|
|
void Wiimote::WriteData(const u16 _channelID, wm_write_data* wd)
|
|
{
|
|
u32 address = convert24bit(wd->address);
|
|
|
|
// ignore the 0x010000 bit
|
|
address &= 0xFEFFFF;
|
|
|
|
if (wd->size > 16)
|
|
{
|
|
PanicAlert("WriteData: size is > 16 bytes");
|
|
return;
|
|
}
|
|
|
|
switch (wd->space)
|
|
{
|
|
case WM_SPACE_EEPROM :
|
|
{
|
|
// Write to EEPROM
|
|
|
|
if (address + wd->size > WIIMOTE_EEPROM_SIZE)
|
|
{
|
|
ERROR_LOG(WIIMOTE, "WriteData: address + size out of bounds!");
|
|
PanicAlert("WriteData: address + size out of bounds!");
|
|
return;
|
|
}
|
|
memcpy(m_eeprom + address, wd->data, wd->size);
|
|
|
|
// write mii data to file
|
|
// i need to improve this greatly
|
|
if (address >= 0x0FCA && address < 0x12C0)
|
|
{
|
|
// writing the whole mii block each write :/
|
|
std::ofstream file;
|
|
file.open( (std::string(File::GetUserPath(D_WIIUSER_IDX)) + "mii.bin").c_str(), std::ios::binary | std::ios::out);
|
|
file.write((char*)m_eeprom + 0x0FCA, 0x02f0);
|
|
file.close();
|
|
}
|
|
}
|
|
break;
|
|
case WM_SPACE_REGS1 :
|
|
case WM_SPACE_REGS2 :
|
|
{
|
|
// Write to Control Register
|
|
|
|
// ignore second byte for extension area
|
|
if (0xA4 == (address >> 16))
|
|
address &= 0xFF00FF;
|
|
|
|
// write to the register
|
|
m_register.Write(address, wd->data, wd->size);
|
|
|
|
switch (address >> 16)
|
|
{
|
|
// speaker
|
|
case 0xa2 :
|
|
//PanicAlert("Write to speaker!!");
|
|
break;
|
|
// extension register
|
|
case 0xa4 :
|
|
{
|
|
// Run the key generation on all writes in the key area, it doesn't matter
|
|
// that we send it parts of a key, only the last full key will have an effect
|
|
// I might have f'ed this up
|
|
if ( address >= 0xa40040 && address <= 0xa4004c )
|
|
wiimote_gen_key(&m_ext_key, m_reg_ext->encryption_key);
|
|
//else if ( address >= 0xa40020 && address < 0xa40040 )
|
|
// PanicAlert("Writing to extension calibration data! Extension may misbehave");
|
|
}
|
|
break;
|
|
// ir
|
|
case 0xB0 :
|
|
if (5 == m_reg_ir->mode)
|
|
PanicAlert("IR Full Mode is Unsupported!");
|
|
break;
|
|
}
|
|
|
|
}
|
|
break;
|
|
default:
|
|
PanicAlert("WriteData: unimplemented parameters!");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Read data from Wiimote and Extensions registers. */
|
|
void Wiimote::ReadData(const u16 _channelID, wm_read_data* rd)
|
|
{
|
|
u32 address = convert24bit(rd->address);
|
|
u16 size = convert16bit(rd->size);
|
|
|
|
// ignore the 0x010000 bit
|
|
address &= 0xFEFFFF;
|
|
|
|
ReadRequest rr;
|
|
u8* block = new u8[size];
|
|
|
|
switch (rd->space)
|
|
{
|
|
case WM_SPACE_EEPROM :
|
|
{
|
|
//PanicAlert("ReadData: reading from EEPROM: address: 0x%x size: 0x%x", address, size);
|
|
// Read from EEPROM
|
|
if (address + size >= WIIMOTE_EEPROM_FREE_SIZE)
|
|
{
|
|
if (address + size > WIIMOTE_EEPROM_SIZE)
|
|
{
|
|
PanicAlert("ReadData: address + size out of bounds");
|
|
return;
|
|
}
|
|
// generate a read error
|
|
size = 0;
|
|
}
|
|
|
|
// read mii data from file
|
|
// i need to improve this greatly
|
|
if (address >= 0x0FCA && address < 0x12C0)
|
|
{
|
|
// reading the whole mii block :/
|
|
std::ifstream file;
|
|
file.open( (std::string(File::GetUserPath(D_WIIUSER_IDX)) + "mii.bin").c_str(), std::ios::binary | std::ios::in);
|
|
file.read((char*)m_eeprom + 0x0FCA, 0x02f0);
|
|
file.close();
|
|
}
|
|
|
|
// read mem to be sent to wii
|
|
memcpy( block, m_eeprom + address, size);
|
|
}
|
|
break;
|
|
case WM_SPACE_REGS1 :
|
|
case WM_SPACE_REGS2 :
|
|
{
|
|
// Read from Control Register
|
|
|
|
// ignore second byte for extension area
|
|
if (0xA4 == (address >> 16))
|
|
address &= 0xFF00FF;
|
|
|
|
// read block to send to wii
|
|
m_register.Read( address, block, size );
|
|
|
|
switch (address >> 16)
|
|
{
|
|
// speaker
|
|
case 0xa2 :
|
|
//PanicAlert("read from speaker!!");
|
|
break;
|
|
// extension
|
|
case 0xa4 :
|
|
{
|
|
// Encrypt data read from extension register
|
|
// Check if encrypted reads is on
|
|
if (0xaa == m_reg_ext->encryption)
|
|
wiimote_encrypt(&m_ext_key, block, address & 0xffff, (u8)size);
|
|
|
|
//if ( address >= 0xa40008 && address < 0xa40020 )
|
|
// PanicAlert("Reading extension data from register");
|
|
}
|
|
break;
|
|
// motion plus
|
|
case 0xa6 :
|
|
{
|
|
// motion plus crap copied from old wiimote plugin
|
|
//block[0xFC] = 0xA6;
|
|
//block[0xFD] = 0x20;
|
|
//block[0xFE] = 0x00;
|
|
//block[0xFF] = 0x05;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
default :
|
|
PanicAlert("WmReadData: unimplemented parameters (size: %i, addr: 0x%x)!", size, rd->space);
|
|
break;
|
|
}
|
|
|
|
// want the requested address, not the above modified one
|
|
rr.address = convert24bit(rd->address);
|
|
rr.size = size;
|
|
//rr.channel = _channelID;
|
|
rr.position = 0;
|
|
rr.data = block;
|
|
|
|
// send up to 16 bytes
|
|
SendReadDataReply( _channelID, rr );
|
|
|
|
// if there is more data to be sent, add it to the queue
|
|
if (rr.size)
|
|
m_read_requests.push( rr );
|
|
else
|
|
delete[] rr.data;
|
|
}
|
|
|
|
// old comment
|
|
/* Here we produce the actual 0x21 Input report that we send to the Wii. The
|
|
message is divided into 16 bytes pieces and sent piece by piece. There will
|
|
be five formatting bytes at the begging of all reports. A common format is
|
|
00 00 f0 00 20, the 00 00 means that no buttons are pressed, the f means 16
|
|
bytes in the message, the 0 means no error, the 00 20 means that the message
|
|
is at the 00 20 offest in the registry that was read.
|
|
*/
|
|
void Wiimote::SendReadDataReply(const u16 _channelID, ReadRequest& _request)
|
|
{
|
|
u8 data[23];
|
|
data[0] = 0xA1;
|
|
data[1] = WM_READ_DATA_REPLY;
|
|
|
|
wm_read_data_reply* const reply = (wm_read_data_reply*)(data + 2);
|
|
reply->buttons = m_status.buttons;
|
|
reply->address = Common::swap16(_request.address);
|
|
|
|
// generate a read error
|
|
// Out of bounds. The real Wiimote generate an error for the first
|
|
// request to 0x1770 if we dont't replicate that the game will never
|
|
// 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.
|
|
if (0 == _request.size)
|
|
{
|
|
reply->size = 0x0f;
|
|
reply->error = 0x08;
|
|
|
|
memset(reply->data, 0, sizeof(reply->data));
|
|
}
|
|
else
|
|
{
|
|
// Limit the amt to 16 bytes
|
|
// AyuanX: the MTU is 640B though... what a waste!
|
|
const int amt = std::min( (unsigned int)16, _request.size );
|
|
|
|
// no error
|
|
reply->error = 0;
|
|
|
|
// 0x1 means two bytes, 0xf means 16 bytes
|
|
reply->size = amt - 1;
|
|
|
|
// Clear the mem first
|
|
memset(reply->data, 0, sizeof(reply->data));
|
|
|
|
// copy piece of mem
|
|
memcpy(reply->data, _request.data + _request.position, amt);
|
|
|
|
// update request struct
|
|
_request.size -= amt;
|
|
_request.position += amt;
|
|
_request.address += amt;
|
|
}
|
|
|
|
// Send a piece
|
|
g_WiimoteInitialize.pWiimoteInterruptChannel(m_index, _channelID, data, sizeof(data));
|
|
}
|
|
|
|
void Wiimote::DoState(PointerWrap& p)
|
|
{
|
|
// not working :(
|
|
//if (p.MODE_READ == p.GetMode())
|
|
//{
|
|
// // LOAD
|
|
// Reset(); // should cause a status report to be sent, then wii should re-setup wiimote
|
|
//}
|
|
//p.Do(m_reporting_channel);
|
|
}
|
|
|
|
}
|