2020-12-05 13:08:24 +01:00
|
|
|
#include "stdafx.h"
|
2019-02-25 11:23:15 +01:00
|
|
|
#include "Skylander.h"
|
|
|
|
|
#include "Emu/Cell/lv2/sys_usbd.h"
|
|
|
|
|
|
2021-03-13 16:02:37 +01:00
|
|
|
#include "util/asm.hpp"
|
|
|
|
|
|
2020-02-01 08:43:43 +01:00
|
|
|
LOG_CHANNEL(skylander_log, "skylander");
|
2019-02-25 11:23:15 +01:00
|
|
|
|
2020-12-18 04:04:41 +01:00
|
|
|
sky_portal g_skyportal;
|
2019-02-25 11:23:15 +01:00
|
|
|
|
2020-12-18 04:04:41 +01:00
|
|
|
void skylander::save()
|
2019-02-25 11:23:15 +01:00
|
|
|
{
|
|
|
|
|
if (!sky_file)
|
|
|
|
|
{
|
2020-12-18 04:04:41 +01:00
|
|
|
skylander_log.error("Tried to save skylander to file but no skylander is active!");
|
2019-02-25 11:23:15 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
sky_file.seek(0, fs::seek_set);
|
2020-12-18 04:04:41 +01:00
|
|
|
sky_file.write(data.data(), 0x40 * 0x10);
|
2019-02-25 11:23:15 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-18 04:04:41 +01:00
|
|
|
void sky_portal::activate()
|
2019-02-25 11:23:15 +01:00
|
|
|
{
|
2020-12-18 04:04:41 +01:00
|
|
|
std::lock_guard lock(sky_mutex);
|
|
|
|
|
if (activated)
|
2019-02-25 11:23:15 +01:00
|
|
|
{
|
2020-12-18 04:04:41 +01:00
|
|
|
// If the portal was already active no change is needed
|
2019-02-25 11:23:15 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-18 04:04:41 +01:00
|
|
|
// If not we need to advertise change to all the figures present on the portal
|
|
|
|
|
for (auto& s : skylanders)
|
2019-02-25 11:23:15 +01:00
|
|
|
{
|
2020-12-18 04:04:41 +01:00
|
|
|
if (s.status & 1)
|
|
|
|
|
{
|
|
|
|
|
s.queued_status.push(3);
|
|
|
|
|
s.queued_status.push(1);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-02-25 11:23:15 +01:00
|
|
|
|
2020-12-18 04:04:41 +01:00
|
|
|
activated = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sky_portal::deactivate()
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard lock(sky_mutex);
|
|
|
|
|
|
|
|
|
|
for (auto& s : skylanders)
|
|
|
|
|
{
|
|
|
|
|
// check if at the end of the updates there would be a figure on the portal
|
|
|
|
|
if (!s.queued_status.empty())
|
|
|
|
|
{
|
|
|
|
|
s.status = s.queued_status.back();
|
|
|
|
|
s.queued_status = std::queue<u8>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s.status &= 1;
|
2019-02-25 11:23:15 +01:00
|
|
|
}
|
2020-12-18 04:04:41 +01:00
|
|
|
|
|
|
|
|
activated = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sky_portal::set_leds(u8 r, u8 g, u8 b)
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard lock(sky_mutex);
|
|
|
|
|
this->r = r;
|
|
|
|
|
this->g = g;
|
|
|
|
|
this->b = b;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sky_portal::get_status(u8* reply_buf)
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard lock(sky_mutex);
|
|
|
|
|
|
|
|
|
|
u16 status = 0;
|
|
|
|
|
|
|
|
|
|
for (int i = 7; i >= 0; i--)
|
|
|
|
|
{
|
|
|
|
|
auto& s = skylanders[i];
|
|
|
|
|
|
|
|
|
|
if (!s.queued_status.empty())
|
|
|
|
|
{
|
|
|
|
|
s.status = s.queued_status.front();
|
|
|
|
|
s.queued_status.pop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
status <<= 2;
|
|
|
|
|
status |= s.status;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-13 16:02:37 +01:00
|
|
|
std::memset(reply_buf, 0, 0x20);
|
2020-12-18 04:04:41 +01:00
|
|
|
reply_buf[0] = 0x53;
|
2021-03-13 16:02:37 +01:00
|
|
|
*utils::bless<le_t<u16>>(reply_buf + 1) = status;
|
2020-12-18 04:04:41 +01:00
|
|
|
reply_buf[5] = interrupt_counter++;
|
|
|
|
|
reply_buf[6] = 0x01;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sky_portal::query_block(u8 sky_num, u8 block, u8* reply_buf)
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard lock(sky_mutex);
|
|
|
|
|
|
|
|
|
|
const auto& thesky = skylanders[sky_num];
|
|
|
|
|
|
|
|
|
|
reply_buf[0] = 'Q';
|
|
|
|
|
reply_buf[2] = block;
|
|
|
|
|
if (thesky.status & 1)
|
|
|
|
|
{
|
|
|
|
|
reply_buf[1] = (0x10 | sky_num);
|
|
|
|
|
memcpy(reply_buf + 3, thesky.data.data() + (16 * block), 16);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
reply_buf[1] = sky_num;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sky_portal::write_block(u8 sky_num, u8 block, const u8* to_write_buf, u8* reply_buf)
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard lock(sky_mutex);
|
|
|
|
|
|
|
|
|
|
auto& thesky = skylanders[sky_num];
|
|
|
|
|
|
|
|
|
|
reply_buf[0] = 'W';
|
|
|
|
|
reply_buf[2] = block;
|
|
|
|
|
|
|
|
|
|
if (thesky.status & 1)
|
|
|
|
|
{
|
|
|
|
|
reply_buf[1] = (0x10 | sky_num);
|
|
|
|
|
memcpy(thesky.data.data() + (block * 16), to_write_buf, 16);
|
|
|
|
|
thesky.save();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
reply_buf[1] = sky_num;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool sky_portal::remove_skylander(u8 sky_num)
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard lock(sky_mutex);
|
|
|
|
|
auto& thesky = skylanders[sky_num];
|
|
|
|
|
|
|
|
|
|
if (thesky.status & 1)
|
|
|
|
|
{
|
|
|
|
|
thesky.status = 2;
|
|
|
|
|
thesky.queued_status.push(2);
|
|
|
|
|
thesky.queued_status.push(0);
|
|
|
|
|
thesky.sky_file.close();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
u8 sky_portal::load_skylander(u8* buf, fs::file in_file)
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard lock(sky_mutex);
|
|
|
|
|
|
2021-03-13 16:02:37 +01:00
|
|
|
u32 sky_serial = *utils::bless<le_t<u32>>(buf);
|
2020-12-18 04:04:41 +01:00
|
|
|
u8 found_slot = 0xFF;
|
|
|
|
|
|
|
|
|
|
// mimics spot retaining on the portal
|
|
|
|
|
for (auto i = 0; i < 8; i++)
|
|
|
|
|
{
|
|
|
|
|
if ((skylanders[i].status & 1) == 0)
|
|
|
|
|
{
|
|
|
|
|
if (skylanders[i].last_id == sky_serial)
|
|
|
|
|
{
|
|
|
|
|
found_slot = i;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (i < found_slot)
|
|
|
|
|
{
|
|
|
|
|
found_slot = i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ensure(found_slot != 0xFF);
|
|
|
|
|
|
|
|
|
|
auto& thesky = skylanders[found_slot];
|
|
|
|
|
memcpy(thesky.data.data(), buf, thesky.data.size());
|
|
|
|
|
thesky.sky_file = std::move(in_file);
|
|
|
|
|
thesky.status = 3;
|
|
|
|
|
thesky.queued_status.push(3);
|
|
|
|
|
thesky.queued_status.push(1);
|
|
|
|
|
thesky.last_id = sky_serial;
|
|
|
|
|
|
|
|
|
|
return found_slot;
|
2019-02-25 11:23:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
usb_device_skylander::usb_device_skylander()
|
|
|
|
|
{
|
|
|
|
|
device = UsbDescriptorNode(USB_DESCRIPTOR_DEVICE, UsbDeviceDescriptor{0x0200, 0x00, 0x00, 0x00, 0x20, 0x1430, 0x0150, 0x0100, 0x01, 0x02, 0x00, 0x01});
|
|
|
|
|
auto& config0 = device.add_node(UsbDescriptorNode(USB_DESCRIPTOR_CONFIG, UsbDeviceConfiguration{0x0029, 0x01, 0x01, 0x00, 0x80, 0x96}));
|
|
|
|
|
config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_INTERFACE, UsbDeviceInterface{0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00}));
|
|
|
|
|
config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_HID, UsbDeviceHID{0x0111, 0x00, 0x01, 0x22, 0x001d}));
|
|
|
|
|
config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, UsbDeviceEndpoint{0x81, 0x03, 0x0020, 0x01}));
|
|
|
|
|
config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, UsbDeviceEndpoint{0x01, 0x03, 0x0020, 0x01}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
usb_device_skylander::~usb_device_skylander()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void usb_device_skylander::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)
|
|
|
|
|
{
|
|
|
|
|
// HID Host 2 Device
|
|
|
|
|
case 0x21:
|
|
|
|
|
switch (bRequest)
|
|
|
|
|
{
|
|
|
|
|
case 0x09:
|
|
|
|
|
transfer->expected_count = buf_size;
|
|
|
|
|
transfer->expected_result = HC_CC_NOERR;
|
|
|
|
|
// 100 usec, control transfers are very fast
|
|
|
|
|
transfer->expected_time = get_timestamp() + 100;
|
|
|
|
|
|
|
|
|
|
std::array<u8, 32> q_result = {};
|
|
|
|
|
|
|
|
|
|
switch (buf[0])
|
|
|
|
|
{
|
|
|
|
|
case 'A':
|
2020-12-18 04:04:41 +01:00
|
|
|
{
|
2019-02-25 11:23:15 +01:00
|
|
|
// Activate command
|
2020-12-18 04:04:41 +01:00
|
|
|
ensure(buf_size == 2 || buf_size == 32);
|
2019-02-25 11:23:15 +01:00
|
|
|
q_result = {0x41, buf[1], 0xFF, 0x77, 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};
|
|
|
|
|
q_queries.push(q_result);
|
2020-12-18 04:04:41 +01:00
|
|
|
g_skyportal.activate();
|
2019-02-25 11:23:15 +01:00
|
|
|
break;
|
2020-12-18 04:04:41 +01:00
|
|
|
}
|
2019-02-25 11:23:15 +01:00
|
|
|
case 'C':
|
2020-12-18 04:04:41 +01:00
|
|
|
{
|
2019-02-25 11:23:15 +01:00
|
|
|
// Set LEDs colour
|
2020-12-18 04:04:41 +01:00
|
|
|
ensure(buf_size == 4 || buf_size == 32);
|
|
|
|
|
g_skyportal.set_leds(buf[1], buf[2], buf[3]);
|
2019-02-25 11:23:15 +01:00
|
|
|
break;
|
2020-12-18 04:04:41 +01:00
|
|
|
}
|
2020-05-11 12:57:13 +02:00
|
|
|
case 'M':
|
2020-12-18 04:04:41 +01:00
|
|
|
{
|
|
|
|
|
// ?
|
2020-05-11 12:57:13 +02:00
|
|
|
q_result[0] = 0x4D;
|
|
|
|
|
q_result[1] = buf[1];
|
|
|
|
|
q_queries.push(q_result);
|
|
|
|
|
break;
|
2020-12-18 04:04:41 +01:00
|
|
|
}
|
2019-02-25 11:23:15 +01:00
|
|
|
case 'Q':
|
2020-12-18 04:04:41 +01:00
|
|
|
{
|
2019-02-25 11:23:15 +01:00
|
|
|
// Queries a block
|
2020-12-18 04:04:41 +01:00
|
|
|
ensure(buf_size == 3 || buf_size == 32);
|
2019-02-25 11:23:15 +01:00
|
|
|
|
2020-12-18 04:04:41 +01:00
|
|
|
const u8 sky_num = buf[1] & 0xF;
|
|
|
|
|
ensure(sky_num < 8);
|
|
|
|
|
const u8 block = buf[2];
|
|
|
|
|
ensure(block < 0x40);
|
2019-02-25 11:23:15 +01:00
|
|
|
|
2020-12-18 04:04:41 +01:00
|
|
|
g_skyportal.query_block(sky_num, block, q_result.data());
|
2019-02-25 11:23:15 +01:00
|
|
|
q_queries.push(q_result);
|
|
|
|
|
break;
|
2020-12-18 04:04:41 +01:00
|
|
|
}
|
2019-02-25 11:23:15 +01:00
|
|
|
case 'R':
|
2020-12-18 04:04:41 +01:00
|
|
|
{
|
|
|
|
|
// Shutdowns the portal
|
|
|
|
|
ensure(buf_size == 2 || buf_size == 32);
|
2019-02-25 11:23:15 +01:00
|
|
|
q_result = {
|
|
|
|
|
0x52, 0x02, 0x0A, 0x03, 0x02, 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};
|
|
|
|
|
q_queries.push(q_result);
|
2020-12-18 04:04:41 +01:00
|
|
|
g_skyportal.deactivate();
|
2019-02-25 11:23:15 +01:00
|
|
|
break;
|
2020-12-18 04:04:41 +01:00
|
|
|
}
|
2019-02-25 11:23:15 +01:00
|
|
|
case 'S':
|
2020-12-18 04:04:41 +01:00
|
|
|
{
|
2019-02-25 11:23:15 +01:00
|
|
|
// ?
|
2020-12-18 04:04:41 +01:00
|
|
|
ensure(buf_size == 1 || buf_size == 32);
|
2019-02-25 11:23:15 +01:00
|
|
|
break;
|
2020-12-18 04:04:41 +01:00
|
|
|
}
|
2019-02-25 11:23:15 +01:00
|
|
|
case 'W':
|
2020-12-18 04:04:41 +01:00
|
|
|
{
|
|
|
|
|
// Writes a block
|
|
|
|
|
ensure(buf_size == 19 || buf_size == 32);
|
2019-02-25 11:23:15 +01:00
|
|
|
|
2020-12-18 04:04:41 +01:00
|
|
|
const u8 sky_num = buf[1] & 0xF;
|
|
|
|
|
ensure(sky_num < 8);
|
|
|
|
|
const u8 block = buf[2];
|
|
|
|
|
ensure(block < 0x40);
|
2019-02-25 11:23:15 +01:00
|
|
|
|
2020-12-18 04:04:41 +01:00
|
|
|
g_skyportal.write_block(sky_num, block, &buf[3], q_result.data());
|
2019-02-25 11:23:15 +01:00
|
|
|
q_queries.push(q_result);
|
|
|
|
|
break;
|
2020-12-18 04:04:41 +01:00
|
|
|
}
|
2019-02-25 11:23:15 +01:00
|
|
|
default: skylander_log.error("Unhandled Query Type: 0x%02X", buf[0]); break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
// Follow to default emulated handler
|
|
|
|
|
usb_device_emulated::control_transfer(bmRequestType, bRequest, wValue, wIndex, wLength, buf_size, buf, transfer);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-05 20:05:37 +01:00
|
|
|
void usb_device_skylander::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endpoint*/, UsbTransfer* transfer)
|
2019-02-25 11:23:15 +01:00
|
|
|
{
|
2020-12-09 08:47:45 +01:00
|
|
|
ensure(buf_size == 0x20);
|
2019-02-25 11:23:15 +01:00
|
|
|
|
|
|
|
|
transfer->fake = true;
|
|
|
|
|
transfer->expected_count = buf_size;
|
|
|
|
|
transfer->expected_result = HC_CC_NOERR;
|
2020-12-18 04:04:41 +01:00
|
|
|
// Interrupt transfers are slow(~22ms)
|
|
|
|
|
transfer->expected_time = get_timestamp() + 22000;
|
2019-02-25 11:23:15 +01:00
|
|
|
|
2020-02-26 21:13:54 +01:00
|
|
|
if (!q_queries.empty())
|
2019-02-25 11:23:15 +01:00
|
|
|
{
|
|
|
|
|
memcpy(buf, q_queries.front().data(), 0x20);
|
|
|
|
|
q_queries.pop();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-12-18 04:04:41 +01:00
|
|
|
g_skyportal.get_status(buf);
|
2019-02-25 11:23:15 +01:00
|
|
|
}
|
|
|
|
|
}
|