Emulating USB Devices within Dolphin

For any fan of Skylanders, you'll remember the Portal of Power, a USB device that you plugged in to bring toys to life within the game. But how does this work within an Emulated Wii device?

Skylander's Spyro's Adventure within Dolphin

For anyone unfamiliar with the Dolphin project - a group of geniuses have managed to reverse-engineer both a Wii and a Gamecube into a unified application, able to be run on Windows, Linux, MacOS and Android. It works fantastically, but for niche games like Skylanders, you need extra hardware in order to get the game to run. In the case of Skylanders, the games require a USB device, known as a "Portal of Power", which, once broken down, is a very juiced up RFID reader, which started out being able to read and write to 4 separate RFID tags and flash lights, and then, once the games grew in complexity, were able to read/write up to 16 different tags, play audio through a mini speaker and flash RGB lights in 3 separate areas on the device. On top of that, players (or more likely, their parents) also needed to buy the toys themselves, which were cool looking plastic figures with a 1kb RFID tag embedded within their base.

The Skylander's starter pack for the Wii

The Why

When I had my old Windows gaming laptop, I used to run a different emulator, called RPCS3, a PS3 emulator. I already had most of the PS3 Skylander Disc versions, and so it was easy enough to set up and get started. The games were pretty intensive to run, and so performance was definitely an issue, and my machine wasn't suitable for RPCS3 unfortunately, which is when I started hunting for the Wii version of the Skylander games. I had used Dolphin in the past, but no longer had access to my parents' old Wii machine so I wasn't able to dump my physical copies of the games, which is why I started using RPCS3 in the first place. The Wii's architecture allowed it to be emulated more efficiently, and I was able to run my games at 100% speed.

When I made the switch to MacOS - I quickly discovered that I was no longer able to play Skylanders, because when any device is plugged in, MacOS's kernel drivers take control of the device, which prevents applications such as Dolphin and RPCS3 from gaining access to the device, and so I was again, stuck without a way to play my niche games. The one thing that RPCS3 does have over Dolphin, is some in-built USB device emulation, which includes an emulated Skylander Portal. It was then that I was inspired to try to replicate the same within Dolphin, and luckily for me both projects were open source, so I was able to see what was going on internally in both.

The How

A good place to start would be how the Portal itself works, and it has been dissected a few times online, but the gist of the functionality comes from a couple of different types of USB protocols, control transfers and interrupt transfers. A control transfer is usually used for control or status operations, sent from the host to the USB device, instructing it on what to do next. In the context of the Portal of Power, these control transfers can include anything from activation, changing colour, playing sound, reading and writing to the RFID tags and a couple of other commands. While working on the project, someone much more knowledgeable than myself offered to lend a hand, and his blog was a huge help in my understanding of commands that weren't handled by other articles I had read.

An interrupt transfer is device generated - meaning the device replies with new information to the host, either with responses to the control transfer request sent out by the host, or the current operational status of the device itself. For the Portal of Power, it begins replying with interrupt information such as whether it has been activated, the data expected from a query command, the status of skylanders on the Portal, plus a few more. In terms of translating that to Dolphin - it was challenging at first, but once I understood how and when I should be replying to these transfers, I was able to migrate the code within RPCS3 and translate that to Dolphin.

An example of the layout of a Skylander's Binary File

Another important structure to understand is the Skylander toys themselves, and the layout of their RFID information. The most crucial part of how the games know what toy is on the Portal is due to 3 key pieces of information, their ID code, their variant code, and a checksum value, all written within the first 2 blocks of data on the toy. The IDs and Variants of all Skylanders have been discovered, as people have algorithmically cracked the Skylander code, using their own programs to read, write and restore Skylander data, and the clever people at RPCS3 have this info already, with a handy checksum generation method as well, so I just needed to include this info with Dolphin's existing binary file IO to not just load existing Skylander dumps, but also to create brand new ones. As an aside, the RFID tags within Skylanders have a suspected battery life of 10 years, so they might be dead very soon...

The Code

Dolphin's codebase is super pretty and well organised - which made getting started a breeze. I only needed to decide where the best location would be to insert the "Portal" and I was off to the races. The main thing was coding in the correct ID values for the Portal, which was easy enough with a physical Portal handy, I just needed to find and insert the Device's ID values within the code.

SkylanderUsb::SkylanderUsb(Kernel& ios, const std::string& device_name)
    : EmulatedUsbDevice(ios, device_name)
{
  m_vid = 0x1430;
  m_pid = 0x150;
  m_id = (static_cast<u64>(m_vid) << 32 | static_cast<u64>(m_pid) << 16 | static_cast<u64>(9) << 8 |
          static_cast<u64>(1));
  deviceDesc = DeviceDescriptor{18, 1, 512, 0, 0, 0, 64, 5168, 336, 256, 1, 2, 0, 1};
  configDesc.emplace_back(ConfigDescriptor{9, 2, 41, 1, 1, 0, 128, 250});
  interfaceDesc.emplace_back(InterfaceDescriptor{9, 4, 0, 0, 2, 3, 0, 0, 0});
  endpointDesc.emplace_back(EndpointDescriptor{7, 5, 129, 3, 64, 1});
  endpointDesc.emplace_back(EndpointDescriptor{7, 5, 2, 3, 64, 1});
}

The code snippet above is the constructor of my emulated Portal, with a few different values gained from the real Portal. The VID and PID are consistent across all Skylander Portals and versions, and the device, config, interface and endpoint descriptors slightly different across different devices, but they communicate back to the Host (Dolphin in this case) the different capabilities and locations at which it can send commands to.

Luckily for me, with both RPCS3 and Dolphin being written in C++, the bulk of the code mapped across, but I just needed to modify different utility classes that Dolphin had used to incorporate little endianness and File IO. I won't include all of the code that handles the physical transfers, but as a high level here is what happens:

  1. Portal is Added during the USB Scan Thread
  2. Dolphin is notified of the Device change
  3. Dolphin sends control/interrupt transfers to the emulated Portal
  4. Respond to request
    • For control requests, the request is echoed back to Dolphin, and queues up data for the following Interrupt message
    • For Interrupt requests, a response is sent back after approx. 22ms, with either the status of the Portal or the requested data
  5. Dolphin sees this response, and the game is notified of changes.

As an example, here's a look at the activate control request being handled:

if (cmd->length == 2 || cmd->length == 32)
{
  q_data = {buf[0], buf[1]};
  q_result = {buf[0], 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);
  cmd->expected_count = 10;
  g_skyPortal.Activate();
}

Through research online, it is known that the Portal expects some data back from the interrupt request, which is whether the Portal is activated or deactivated. In the above snippet, the buf array is the data being sent from the control request, and for activation commands, buf[0] is always the char 'A', and buf[1] is whether to activate or deactivate the Portal. We add the result, an array of 32 bytes, to the queue of data to return to dolphin, the "Portal" is activated, and the rest of the method runs before finishing up by responding with an echo of the request. The expected count for control transfers is the size of the data (2 for Activate requests) + 8, which is the size of the Control Transfer's setup packet.

The Now

At the time of writing this, I have opened a pull request to the main Dolphin repository, after completing the work for this project in a forked repository. There's been a fair amount of interest from the user base, and there's been some super helpful people helping the little piece of code I wrote (close to 2000 lines) hopefully succeed, and make its way into the latest developer builds of the project.

Just to show you what the window looks like, here is a screengrab of the Emulated Portal's window (copied from the RPCS3 project, but with a checkbox)

The new Skylander Portal Window tool working within dolphin

And the Skylander Create Dialog, also taken from the RPCS3 project, is able to create brand new Skylander dumps so people are able to play with any known Skylander, or even enter an ID and Variant Number to see if it's recognised.

A window where you can create Skylander binary files to use within Dolphin

I've also uploaded a video to Youtube to show it in action, but the video is one of the original builds and so a few bits and pieces have changed, including better control handling and response threading.

The Future

I hope to see this merged in to mainline Dolphin as a starting place, but I've also begun working on implementing the same for Android (which is already working and just needs a few tweaks), have begun work on a Netplay version, which aims to let users who want to play Dolphin online have access to the same features. That will prove tricky, as the deterministic nature of Netplay requires any requests to be handled on the main CPU thread, and doesn't scan for new devices (so the Portal is not recognised), and the Thread which controls responses to Fake transfers is unable to start. I will try to work on it in the new year, but I'll update my page if so!

All in all, this has been a whole heap of fun to work on, and even if this doesn't go anywhere, I can still claim this as a great project to have been involved in, and completing this before the end of the year has certainly been a huge win for me. Let me know what you think, and send me an email with any feedback, or even have a look at my forked repository