2021-11-24 21:59:48 +01:00
// v406 USIO emulator
// Responses may be specific to Taiko no Tatsujin
# include "stdafx.h"
# include "usio.h"
# include "Emu/Cell/lv2/sys_usbd.h"
# include "Input/pad_thread.h"
# include "Emu/System.h"
2022-09-16 18:35:24 +02:00
# include "Emu/IdManager.h"
2021-11-24 21:59:48 +01:00
LOG_CHANNEL ( usio_log ) ;
2022-09-16 18:35:24 +02:00
struct last_game_status
{
std : : vector < u8 > memory ;
last_game_status ( const last_game_status & ) = delete ;
last_game_status & operator = ( const last_game_status & ) = delete ;
} ;
constexpr u8 default_last_game_status [ 0x28 ] = { 0x4C , 0x41 , 0x53 , 0x54 , 0x47 , 0x41 , 0x4D , 0x45 , 0x53 , 0x54 , 0x41 , 0x54 , 0x55 , 0x53 , 0x20 , 0x76 , 0x65 , 0x72 , 0x2E , 0x33 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 } ; // "LASTGAMESTATUS ver.3"
2021-11-23 15:52:41 +01:00
usb_device_usio : : usb_device_usio ( const std : : array < u8 , 7 > & location )
: usb_device_emulated ( location )
2021-11-24 21:59:48 +01:00
{
device = UsbDescriptorNode ( USB_DESCRIPTOR_DEVICE ,
UsbDeviceDescriptor {
. bcdUSB = 0x0110 ,
. bDeviceClass = 0xff ,
. bDeviceSubClass = 0x00 ,
. bDeviceProtocol = 0xff ,
. bMaxPacketSize0 = 0x8 ,
. idVendor = 0x0b9a ,
. idProduct = 0x0910 ,
. bcdDevice = 0x0910 ,
. iManufacturer = 0x01 ,
. iProduct = 0x02 ,
. iSerialNumber = 0x00 ,
. bNumConfigurations = 0x01 } ) ;
auto & config0 = device . add_node ( UsbDescriptorNode ( USB_DESCRIPTOR_CONFIG ,
UsbDeviceConfiguration {
. wTotalLength = 39 ,
. bNumInterfaces = 0x01 ,
. bConfigurationValue = 0x01 ,
. iConfiguration = 0x00 ,
. bmAttributes = 0xc0 ,
. bMaxPower = 0x32 // ??? 100ma
} ) ) ;
config0 . add_node ( UsbDescriptorNode ( USB_DESCRIPTOR_INTERFACE ,
UsbDeviceInterface {
. bInterfaceNumber = 0x00 ,
. bAlternateSetting = 0x00 ,
. bNumEndpoints = 0x03 ,
. bInterfaceClass = 0x00 ,
. bInterfaceSubClass = 0x00 ,
. bInterfaceProtocol = 0x00 ,
. iInterface = 0x00 } ) ) ;
config0 . add_node ( UsbDescriptorNode ( USB_DESCRIPTOR_ENDPOINT ,
UsbDeviceEndpoint {
. bEndpointAddress = 0x01 ,
. bmAttributes = 0x02 ,
. wMaxPacketSize = 0x0040 ,
. bInterval = 0x00 } ) ) ;
config0 . add_node ( UsbDescriptorNode ( USB_DESCRIPTOR_ENDPOINT ,
UsbDeviceEndpoint {
. bEndpointAddress = 0x82 ,
. bmAttributes = 0x02 ,
. wMaxPacketSize = 0x0040 ,
. bInterval = 0x00 } ) ) ;
config0 . add_node ( UsbDescriptorNode ( USB_DESCRIPTOR_ENDPOINT ,
UsbDeviceEndpoint {
. bEndpointAddress = 0x83 ,
. bmAttributes = 0x03 ,
. wMaxPacketSize = 0x0008 ,
. bInterval = 16 } ) ) ;
2022-09-16 18:35:24 +02:00
g_fxo - > get < last_game_status > ( ) . memory . resize ( 0x28 ) ;
memcpy ( g_fxo - > get < last_game_status > ( ) . memory . data ( ) , default_last_game_status , 0x28 ) ;
2021-11-24 21:59:48 +01:00
}
usb_device_usio : : ~ usb_device_usio ( )
{
}
void usb_device_usio : : control_transfer ( u8 bmRequestType , u8 bRequest , u16 wValue , u16 wIndex , u16 wLength , u32 buf_size , u8 * buf , UsbTransfer * transfer )
{
transfer - > fake = true ;
// Control transfers are nearly instant
switch ( bmRequestType )
{
default :
// Follow to default emulated handler
usb_device_emulated : : control_transfer ( bmRequestType , bRequest , wValue , wIndex , wLength , buf_size , buf , transfer ) ;
break ;
}
}
2022-07-05 21:47:05 +02:00
extern bool is_input_allowed ( ) ;
2021-11-24 21:59:48 +01:00
void usb_device_usio : : translate_input ( )
{
std : : lock_guard lock ( pad : : g_pad_mutex ) ;
const auto handler = pad : : get_current_handler ( ) ;
std : : vector < u8 > input_buf = { 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x07 , 0x00 , 0x00 , 0x80 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x60 , 0x00 , 0x60 , 0x00 , 0x60 , 0x00 , 0x60 , 0x00 , 0x60 , 0x00 , 0x60 , 0x00 , 0x60 , 0x00 , 0x60 , 0x00 , 0x60 , 0x00 , 0xFF , 0xFF , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 } ;
2022-09-13 15:08:55 +02:00
constexpr le_t < u16 > c_small_hit = 0x4A0 ;
constexpr le_t < u16 > c_big_hit = 0xA40 ;
2021-11-24 21:59:48 +01:00
auto translate_from_pad = [ & ] ( u8 pad_number , u8 player )
{
2022-07-05 21:47:05 +02:00
if ( ! is_input_allowed ( ) )
{
return ;
}
2021-11-24 21:59:48 +01:00
const auto & pad = handler - > GetPads ( ) [ pad_number ] ;
if ( ! ( pad - > m_port_status & CELL_PAD_STATUS_CONNECTED ) )
{
return ;
}
const std : : size_t offset = ( player * 8 ) ;
for ( const Button & button : pad - > m_buttons )
{
if ( button . m_pressed )
{
if ( button . m_offset = = CELL_PAD_BTN_OFFSET_DIGITAL2 )
{
switch ( button . m_outKeyCode )
{
case CELL_PAD_CTRL_SQUARE :
// Strong hit side left
2022-09-13 15:08:55 +02:00
std : : memcpy ( input_buf . data ( ) + 32 + offset , & c_big_hit , sizeof ( u16 ) ) ;
2021-11-24 21:59:48 +01:00
break ;
case CELL_PAD_CTRL_CROSS :
// Strong hit center right
2022-09-13 15:08:55 +02:00
std : : memcpy ( input_buf . data ( ) + 36 + offset , & c_big_hit , sizeof ( u16 ) ) ;
2021-11-24 21:59:48 +01:00
break ;
case CELL_PAD_CTRL_CIRCLE :
// Strong hit side right
2022-09-13 15:08:55 +02:00
std : : memcpy ( input_buf . data ( ) + 38 + offset , & c_big_hit , sizeof ( u16 ) ) ;
2021-11-24 21:59:48 +01:00
break ;
case CELL_PAD_CTRL_TRIANGLE :
// Strong hit center left
2022-09-13 15:08:55 +02:00
std : : memcpy ( input_buf . data ( ) + 34 + offset , & c_big_hit , sizeof ( u16 ) ) ;
2021-11-24 21:59:48 +01:00
break ;
case CELL_PAD_CTRL_L1 :
// Small hit center left
2022-09-13 15:08:55 +02:00
std : : memcpy ( input_buf . data ( ) + 34 + offset , & c_small_hit , sizeof ( u16 ) ) ;
2021-11-24 21:59:48 +01:00
break ;
case CELL_PAD_CTRL_R1 :
// Small hit center right
2022-09-13 15:08:55 +02:00
std : : memcpy ( input_buf . data ( ) + 36 + offset , & c_small_hit , sizeof ( u16 ) ) ;
2021-11-24 21:59:48 +01:00
break ;
case CELL_PAD_CTRL_L2 :
// Small hit side left
2022-09-13 15:08:55 +02:00
std : : memcpy ( input_buf . data ( ) + 32 + offset , & c_small_hit , sizeof ( u16 ) ) ;
2021-11-24 21:59:48 +01:00
break ;
case CELL_PAD_CTRL_R2 :
// Small hit side right
2022-09-13 15:08:55 +02:00
std : : memcpy ( input_buf . data ( ) + 38 + offset , & c_small_hit , sizeof ( u16 ) ) ;
2021-11-24 21:59:48 +01:00
break ;
default :
break ;
}
}
}
}
} ;
translate_from_pad ( 0 , 0 ) ;
translate_from_pad ( 1 , 1 ) ;
q_replies . push ( input_buf ) ;
q_replies . push ( { 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 } ) ;
}
void usb_device_usio : : usio_write ( u8 channel , u16 reg , const std : : vector < u8 > & data )
{
const auto get_u16 = [ & ] ( std : : string_view usio_func ) - > u16
{
if ( data . size ( ) ! = 2 )
{
usio_log . fatal ( " data.size() is %d, expected 2 for get_u16 in %s " , data . size ( ) , usio_func ) ;
}
return * reinterpret_cast < const le_t < u16 > * > ( data . data ( ) ) ;
} ;
if ( channel = = 0 )
{
switch ( reg )
{
case 0x0002 :
{
usio_log . notice ( " SetSystemError: 0x%04X " , get_u16 ( " SetSystemError " ) ) ;
break ;
}
case 0x000A :
{
u16 command = get_u16 ( " ClearSram " ) ;
ensure ( command = = 0x6666 , " USIO: Unexpected Command instead of ClearSram " ) ;
usio_log . notice ( " ClearSram " ) ;
break ;
}
case 0x0028 :
{
usio_log . notice ( " SetExpansionMode: 0x%04X " , get_u16 ( " SetExpansionMode " ) ) ;
break ;
}
case 0x0048 :
case 0x0058 :
case 0x0068 :
case 0x0078 :
{
usio_log . notice ( " SetHopperRequest(Hopper: %d, Request: 0x%04X) " , ( reg - 0x48 ) / 0x10 , get_u16 ( " SetHopperRequest " ) ) ;
break ;
}
case 0x004A :
case 0x005A :
case 0x006A :
case 0x007A :
{
usio_log . notice ( " SetHopperRequest(Hopper: %d, Limit: 0x%04X) " , ( reg - 0x4A ) / 0x10 , get_u16 ( " SetHopperLimit " ) ) ;
break ;
}
default :
{
//usio_log.error("Unhandled channel 0 register write: 0x%04X", reg);
break ;
}
}
}
else if ( channel > = 2 )
{
usio_log . trace ( " Usio write of sram(chip: %d, addr: 0x%04X) " , channel - 2 , reg ) ;
2022-09-16 18:35:24 +02:00
if ( channel = = 2 )
{
switch ( reg )
{
case 0x0180 :
{
ensure ( data . size ( ) = = 0x28 ) ;
g_fxo - > get < last_game_status > ( ) . memory . resize ( 0x28 ) ;
memcpy ( g_fxo - > get < last_game_status > ( ) . memory . data ( ) , data . data ( ) , 0x28 ) ;
break ;
}
}
}
2021-11-24 21:59:48 +01:00
}
else
{
2022-09-05 04:55:19 +02:00
// Channel 1 is the endpoint for firmware update.
// We are not using any firmware since this is emulation.
usio_log . warning ( " Unsupported write operation(channel: 0x%02X, addr: 0x%04X) " , channel , reg ) ;
2021-11-24 21:59:48 +01:00
}
}
void usb_device_usio : : usio_read ( u8 channel , u16 reg , u16 size )
{
auto push_zeroes = [ & ] ( )
{
// Give it 00s
std : : vector < u8 > zeroes ;
u16 left = size ;
while ( left > 0 )
{
u16 to_push = std : : min ( left , static_cast < u16 > ( 64 ) ) ;
zeroes . resize ( to_push ) ;
q_replies . push ( zeroes ) ;
left - = to_push ;
}
} ;
if ( channel = = 0 )
{
switch ( reg )
{
case 0x0000 :
{
// Get Buffer, rarely gives a reply on real HW
// First U16 seems to be a timestamp of sort
// Purpose seems related to BananaPass
q_replies . push ( { 0x7E , 0xE4 , 0x00 , 0x00 , 0x74 , 0x01 , 0x01 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x7E , 0x00 , 0x7E , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x80 , 0x01 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x80 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x80 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 } ) ;
break ;
}
case 0x0080 :
{
// Purpose unknown
ensure ( size = = 0x10 ) ;
q_replies . push ( { 0x02 , 0x03 , 0x00 , 0x00 , 0xFF , 0x0F , 0x00 , 0x10 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x05 , 0x10 , 0x00 } ) ;
break ;
}
case 0x1080 :
{
// Often called, gets input from usio
translate_input ( ) ;
break ;
}
case 0x1800 :
{
usio_log . trace ( " Firmware Query on 0x1800 " ) ;
ensure ( size = = 0x70 ) ;
// Firmware
// "NBGI.;USIO01;Ver1.00;JPN,Multipurpose with PPG."
q_replies . push ( { 0x4E , 0x42 , 0x47 , 0x49 , 0x2E , 0x3B , 0x55 , 0x53 , 0x49 , 0x4F , 0x30 , 0x31 , 0x3B , 0x56 , 0x65 , 0x72 , 0x31 , 0x2E , 0x30 , 0x30 , 0x3B , 0x4A , 0x50 , 0x4E , 0x2C , 0x4D , 0x75 , 0x6C , 0x74 , 0x69 , 0x70 , 0x75 , 0x72 , 0x70 , 0x6F , 0x73 , 0x65 , 0x20 , 0x77 , 0x69 , 0x74 , 0x68 , 0x20 , 0x50 , 0x50 , 0x47 , 0x2E , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 } ) ;
q_replies . push ( { 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 } ) ;
break ;
}
case 0x1880 :
{
// Seems to contain a few extra bytes of info in addition to the firmware string
usio_log . trace ( " Firmware query on 0x1880 " ) ;
ensure ( size = = 0x70 ) ;
// Firmware
// "NBGI2;USIO01;Ver1.00;JPN,Multipurpose with PPG."
q_replies . push ( { 0x4E , 0x42 , 0x47 , 0x49 , 0x32 , 0x3B , 0x55 , 0x53 , 0x49 , 0x4F , 0x30 , 0x31 , 0x3B , 0x56 , 0x65 , 0x72 , 0x31 , 0x2E , 0x30 , 0x30 , 0x3B , 0x4A , 0x50 , 0x4E , 0x2C , 0x4D , 0x75 , 0x6C , 0x74 , 0x69 , 0x70 , 0x75 , 0x72 , 0x70 , 0x6F , 0x73 , 0x65 , 0x20 , 0x77 , 0x69 , 0x74 , 0x68 , 0x20 , 0x50 , 0x50 , 0x47 , 0x2E , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 } ) ;
q_replies . push ( { 0x01 , 0x00 , 0x13 , 0x00 , 0x30 , 0x00 , 0x01 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x01 , 0x03 , 0x02 , 0x00 , 0x08 , 0x00 , 0x01 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x03 , 0x00 , 0x03 , 0x00 , 0x08 , 0xE2 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 } ) ;
break ;
}
default :
{
usio_log . fatal ( " Unhandled channel 0 register read: 0x%04X " , reg ) ;
break ;
}
}
}
else if ( channel > = 2 )
{
u8 chip = channel - 2 ;
usio_log . trace ( " Usio read of sram(chip: %d, addr: 0x%04X) " , chip , reg ) ;
switch ( chip )
{
case 0 :
{
switch ( reg )
{
case 0x0000 :
{
ensure ( size = = 0xB8 ) ;
// No data returned
break ;
}
case 0x0180 :
{
ensure ( size = = 0x28 ) ;
2022-09-16 18:35:24 +02:00
q_replies . push ( g_fxo - > get < last_game_status > ( ) . memory ) ;
2021-11-24 21:59:48 +01:00
break ;
}
case 0x0200 :
{
ensure ( size = = 0x100 ) ;
// No data returned
break ;
}
case 0x1000 :
{
ensure ( size = = 0x1000 ) ;
push_zeroes ( ) ;
break ;
}
default :
{
usio_log . fatal ( " Unhandled read of sram(chip: %d, addr: 0x%04X) " , channel - 2 , reg ) ;
push_zeroes ( ) ;
break ;
}
}
break ;
}
default :
{
usio_log . fatal ( " Unhandled read of sram(chip: %d, addr: 0x%04X) " , channel - 2 , reg ) ;
push_zeroes ( ) ;
break ;
}
}
}
else
{
2022-09-05 04:55:19 +02:00
// Channel 1 is the endpoint for firmware update.
// We are not using any firmware since this is emulation.
usio_log . warning ( " Unsupported read operation(channel: 0x%02X, addr: 0x%04X) " , channel , reg ) ;
2021-11-24 21:59:48 +01:00
}
}
void usb_device_usio : : interrupt_transfer ( u32 buf_size , u8 * buf , u32 endpoint , UsbTransfer * transfer )
{
constexpr u8 USIO_COMMAND_WRITE = 0x90 ;
constexpr u8 USIO_COMMAND_READ = 0x10 ;
static bool expecting_data = false ;
static std : : vector < u8 > usio_data ;
static u8 usio_channel = 0 ;
static u16 usio_register = 0 ;
static u16 usio_length = 0 ;
transfer - > fake = true ;
transfer - > expected_result = HC_CC_NOERR ;
// The latency varies per operation but it doesn't seem to matter for this device so let's go fast!
transfer - > expected_time = get_timestamp ( ) ;
switch ( endpoint )
{
case 0x01 :
{
// Write endpoint
transfer - > expected_count = buf_size ;
if ( expecting_data )
{
usio_data . insert ( usio_data . end ( ) , buf , buf + buf_size ) ;
usio_length - = buf_size ;
if ( usio_length = = 0 )
{
expecting_data = false ;
usio_write ( usio_channel , usio_register , usio_data ) ;
}
return ;
}
// Commands
ensure ( buf_size = = 6 , " Expected a command but buf_size != 6 " ) ;
usio_channel = buf [ 0 ] & 0xF ;
usio_register = * reinterpret_cast < le_t < u16 > * > ( & buf [ 2 ] ) ;
usio_length = * reinterpret_cast < le_t < u16 > * > ( & buf [ 4 ] ) ;
if ( ( buf [ 0 ] & USIO_COMMAND_WRITE ) = = USIO_COMMAND_WRITE )
{
usio_log . trace ( " UsioWrite(Channel: 0x%02X, Register: 0x%04X, Length: 0x%04X) " , usio_channel , usio_register , usio_length ) ;
ensure ( ( ( ~ ( usio_register > > 8 ) ) & 0xF0 ) = = buf [ 1 ] ) ;
expecting_data = true ;
usio_data . clear ( ) ;
return ;
}
if ( ( buf [ 0 ] & USIO_COMMAND_READ ) = = USIO_COMMAND_READ )
{
usio_log . trace ( " UsioRead(Channel: 0x%02X, Register: 0x%04X, Length: 0x%04X) " , usio_channel , usio_register , usio_length ) ;
usio_read ( usio_channel , usio_register , usio_length ) ;
return ;
}
2022-09-05 04:55:19 +02:00
// Init and reset commands
2021-11-24 21:59:48 +01:00
if ( ( buf [ 0 ] & 0xA0 ) = = 0xA0 )
{
2022-09-05 04:55:19 +02:00
const std : : array < u8 , 2 > init_command = { 0xA0 , 0xF0 } ; // This kind of command starts with 0xA0, 0xF0 commonly. For example, {0xA0, 0xF0, 0x28, 0x00, 0x00, 0x80}
ensure ( memcmp ( buf , init_command . data ( ) , 2 ) = = 0 ) ;
2021-11-24 21:59:48 +01:00
return ;
}
fmt : : throw_exception ( " Received an unexpected command: 0x%02X " , buf [ 0 ] ) ;
}
case 0x82 :
{
// Read endpoint
if ( ! q_replies . empty ( ) )
{
// Sometimes software will outright ignore what usio sends and read with a buffer of 0
if ( buf_size = = 0 )
{
transfer - > expected_count = q_replies . front ( ) . size ( ) ;
q_replies . pop ( ) ;
break ;
}
// Otherwise we expect the buffer to be appropriately sized
ensure ( buf_size > = q_replies . front ( ) . size ( ) ) ;
memcpy ( buf , q_replies . front ( ) . data ( ) , q_replies . front ( ) . size ( ) ) ;
transfer - > expected_count = q_replies . front ( ) . size ( ) ;
q_replies . pop ( ) ;
}
else
{
transfer - > expected_count = 0 ;
}
break ;
}
default :
usio_log . fatal ( " Unhandled endpoint: 0x%x " , endpoint ) ;
break ;
}
}