Cemu/src/Cafe/OS/libs/nsyshid/Dimensions.cpp
2024-11-22 13:43:12 +01:00

1163 lines
No EOL
34 KiB
C++

#include "Dimensions.h"
#include "nsyshid.h"
#include "Backend.h"
#include "Common/FileStream.h"
#include <array>
#include <random>
namespace nsyshid
{
static constexpr std::array<uint8, 16> COMMAND_KEY = {0x55, 0xFE, 0xF6, 0xB0, 0x62, 0xBF, 0x0B, 0x41,
0xC9, 0xB3, 0x7C, 0xB4, 0x97, 0x3E, 0x29, 0x7B};
static constexpr std::array<uint8, 17> CHAR_CONSTANT = {0xB7, 0xD5, 0xD7, 0xE6, 0xE7, 0xBA, 0x3C, 0xA8,
0xD8, 0x75, 0x47, 0x68, 0xCF, 0x23, 0xE9, 0xFE, 0xAA};
static constexpr std::array<uint8, 25> PWD_CONSTANT = {0x28, 0x63, 0x29, 0x20, 0x43, 0x6F, 0x70, 0x79,
0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x4C, 0x45,
0x47, 0x4F, 0x20, 0x32, 0x30, 0x31, 0x34, 0xAA, 0xAA};
DimensionsUSB g_dimensionstoypad;
const std::map<const uint32, const char*> s_listMinis = {
{0, "Blank Tag"},
{1, "Batman"},
{2, "Gandalf"},
{3, "Wyldstyle"},
{4, "Aquaman"},
{5, "Bad Cop"},
{6, "Bane"},
{7, "Bart Simpson"},
{8, "Benny"},
{9, "Chell"},
{10, "Cole"},
{11, "Cragger"},
{12, "Cyborg"},
{13, "Cyberman"},
{14, "Doc Brown"},
{15, "The Doctor"},
{16, "Emmet"},
{17, "Eris"},
{18, "Gimli"},
{19, "Gollum"},
{20, "Harley Quinn"},
{21, "Homer Simpson"},
{22, "Jay"},
{23, "Joker"},
{24, "Kai"},
{25, "ACU Trooper"},
{26, "Gamer Kid"},
{27, "Krusty the Clown"},
{28, "Laval"},
{29, "Legolas"},
{30, "Lloyd"},
{31, "Marty McFly"},
{32, "Nya"},
{33, "Owen Grady"},
{34, "Peter Venkman"},
{35, "Slimer"},
{36, "Scooby-Doo"},
{37, "Sensei Wu"},
{38, "Shaggy"},
{39, "Stay Puft"},
{40, "Superman"},
{41, "Unikitty"},
{42, "Wicked Witch of the West"},
{43, "Wonder Woman"},
{44, "Zane"},
{45, "Green Arrow"},
{46, "Supergirl"},
{47, "Abby Yates"},
{48, "Finn the Human"},
{49, "Ethan Hunt"},
{50, "Lumpy Space Princess"},
{51, "Jake the Dog"},
{52, "Harry Potter"},
{53, "Lord Voldemort"},
{54, "Michael Knight"},
{55, "B.A. Baracus"},
{56, "Newt Scamander"},
{57, "Sonic the Hedgehog"},
{58, "Future Update (unreleased)"},
{59, "Gizmo"},
{60, "Stripe"},
{61, "E.T."},
{62, "Tina Goldstein"},
{63, "Marceline the Vampire Queen"},
{64, "Batgirl"},
{65, "Robin"},
{66, "Sloth"},
{67, "Hermione Granger"},
{68, "Chase McCain"},
{69, "Excalibur Batman"},
{70, "Raven"},
{71, "Beast Boy"},
{72, "Betelgeuse"},
{73, "Lord Vortech (unreleased)"},
{74, "Blossom"},
{75, "Bubbles"},
{76, "Buttercup"},
{77, "Starfire"},
{78, "World 15 (unreleased)"},
{79, "World 16 (unreleased)"},
{80, "World 17 (unreleased)"},
{81, "World 18 (unreleased)"},
{82, "World 19 (unreleased)"},
{83, "World 20 (unreleased)"},
{768, "Unknown 768"},
{769, "Supergirl Red Lantern"},
{770, "Unknown 770"}};
const std::map<const uint32, const char*> s_listTokens = {
{1000, "Police Car"},
{1001, "Aerial Squad Car"},
{1002, "Missile Striker"},
{1003, "Gravity Sprinter"},
{1004, "Street Shredder"},
{1005, "Sky Clobberer"},
{1006, "Batmobile"},
{1007, "Batblaster"},
{1008, "Sonic Batray"},
{1009, "Benny's Spaceship"},
{1010, "Lasercraft"},
{1011, "The Annihilator"},
{1012, "DeLorean Time Machine"},
{1013, "Electric Time Machine"},
{1014, "Ultra Time Machine"},
{1015, "Hoverboard"},
{1016, "Cyclone Board"},
{1017, "Ultimate Hoverjet"},
{1018, "Eagle Interceptor"},
{1019, "Eagle Sky Blazer"},
{1020, "Eagle Swoop Diver"},
{1021, "Swamp Skimmer"},
{1022, "Cragger's Fireship"},
{1023, "Croc Command Sub"},
{1024, "Cyber-Guard"},
{1025, "Cyber-Wrecker"},
{1026, "Laser Robot Walker"},
{1027, "K-9"},
{1028, "K-9 Ruff Rover"},
{1029, "K-9 Laser Cutter"},
{1030, "TARDIS"},
{1031, "Laser-Pulse TARDIS"},
{1032, "Energy-Burst TARDIS"},
{1033, "Emmet's Excavator"},
{1034, "Destroy Dozer"},
{1035, "Destruct-o-Mech"},
{1036, "Winged Monkey"},
{1037, "Battle Monkey"},
{1038, "Commander Monkey"},
{1039, "Axe Chariot"},
{1040, "Axe Hurler"},
{1041, "Soaring Chariot"},
{1042, "Shelob the Great"},
{1043, "8-Legged Stalker"},
{1044, "Poison Slinger"},
{1045, "Homer's Car"},
{1047, "The SubmaHomer"},
{1046, "The Homercraft"},
{1048, "Taunt-o-Vision"},
{1050, "The MechaHomer"},
{1049, "Blast Cam"},
{1051, "Velociraptor"},
{1053, "Venom Raptor"},
{1052, "Spike Attack Raptor"},
{1054, "Gyrosphere"},
{1055, "Sonic Beam Gyrosphere"},
{1056, " Gyrosphere"},
{1057, "Clown Bike"},
{1058, "Cannon Bike"},
{1059, "Anti-Gravity Rocket Bike"},
{1060, "Mighty Lion Rider"},
{1061, "Lion Blazer"},
{1062, "Fire Lion"},
{1063, "Arrow Launcher"},
{1064, "Seeking Shooter"},
{1065, "Triple Ballista"},
{1066, "Mystery Machine"},
{1067, "Mystery Tow & Go"},
{1068, "Mystery Monster"},
{1069, "Boulder Bomber"},
{1070, "Boulder Blaster"},
{1071, "Cyclone Jet"},
{1072, "Storm Fighter"},
{1073, "Lightning Jet"},
{1074, "Electro-Shooter"},
{1075, "Blade Bike"},
{1076, "Flight Fire Bike"},
{1077, "Blades of Fire"},
{1078, "Samurai Mech"},
{1079, "Samurai Shooter"},
{1080, "Soaring Samurai Mech"},
{1081, "Companion Cube"},
{1082, "Laser Deflector"},
{1083, "Gold Heart Emitter"},
{1084, "Sentry Turret"},
{1085, "Turret Striker"},
{1086, "Flight Turret Carrier"},
{1087, "Scooby Snack"},
{1088, "Scooby Fire Snack"},
{1089, "Scooby Ghost Snack"},
{1090, "Cloud Cuckoo Car"},
{1091, "X-Stream Soaker"},
{1092, "Rainbow Cannon"},
{1093, "Invisible Jet"},
{1094, "Laser Shooter"},
{1095, "Torpedo Bomber"},
{1096, "NinjaCopter"},
{1097, "Glaciator"},
{1098, "Freeze Fighter"},
{1099, "Travelling Time Train"},
{1100, "Flight Time Train"},
{1101, "Missile Blast Time Train"},
{1102, "Aqua Watercraft"},
{1103, "Seven Seas Speeder"},
{1104, "Trident of Fire"},
{1105, "Drill Driver"},
{1106, "Bane Dig 'n' Drill"},
{1107, "Bane Drill 'n' Blast"},
{1108, "Quinn Mobile"},
{1109, "Quinn Ultra Racer"},
{1110, "Missile Launcher"},
{1111, "The Joker's Chopper"},
{1112, "Mischievous Missile Blaster"},
{1113, "Lock 'n' Laser Jet"},
{1114, "Hover Pod"},
{1115, "Krypton Striker"},
{1116, "Super Stealth Pod"},
{1117, "Dalek"},
{1118, "Fire 'n' Ride Dalek"},
{1119, "Silver Shooter Dalek"},
{1120, "Ecto-1"},
{1121, "Ecto-1 Blaster"},
{1122, "Ecto-1 Water Diver"},
{1123, "Ghost Trap"},
{1124, "Ghost Stun 'n' Trap"},
{1125, "Proton Zapper"},
{1126, "Unknown"},
{1127, "Unknown"},
{1128, "Unknown"},
{1129, "Unknown"},
{1130, "Unknown"},
{1131, "Unknown"},
{1132, "Lloyd's Golden Dragon"},
{1133, "Sword Projector Dragon"},
{1134, "Unknown"},
{1135, "Unknown"},
{1136, "Unknown"},
{1137, "Unknown"},
{1138, "Unknown"},
{1139, "Unknown"},
{1140, "Unknown"},
{1141, "Unknown"},
{1142, "Unknown"},
{1143, "Unknown"},
{1144, "Mega Flight Dragon"},
{1145, "Unknown"},
{1146, "Unknown"},
{1147, "Unknown"},
{1148, "Unknown"},
{1149, "Unknown"},
{1150, "Unknown"},
{1151, "Unknown"},
{1152, "Unknown"},
{1153, "Unknown"},
{1154, "Unknown"},
{1155, "Flying White Dragon"},
{1156, "Golden Fire Dragon"},
{1157, "Ultra Destruction Dragon"},
{1158, "Arcade Machine"},
{1159, "8-Bit Shooter"},
{1160, "The Pixelator"},
{1161, "G-6155 Spy Hunter"},
{1162, "Interdiver"},
{1163, "Aerial Spyhunter"},
{1164, "Slime Shooter"},
{1165, "Slime Exploder"},
{1166, "Slime Streamer"},
{1167, "Terror Dog"},
{1168, "Terror Dog Destroyer"},
{1169, "Soaring Terror Dog"},
{1170, "Ancient Psychic Tandem War Elephant"},
{1171, "Cosmic Squid"},
{1172, "Psychic Submarine"},
{1173, "BMO"},
{1174, "DOGMO"},
{1175, "SNAKEMO"},
{1176, "Jakemobile"},
{1177, "Snail Dude Jake"},
{1178, "Hover Jake"},
{1179, "Lumpy Car"},
{1181, "Lumpy Land Whale"},
{1180, "Lumpy Truck"},
{1182, "Lunatic Amp"},
{1183, "Shadow Scorpion"},
{1184, "Heavy Metal Monster"},
{1185, "B.A.'s Van"},
{1186, "Fool Smasher"},
{1187, "Pain Plane"},
{1188, "Phone Home"},
{1189, "Mobile Uplink"},
{1190, "Super-Charged Satellite"},
{1191, "Niffler"},
{1192, "Sinister Scorpion"},
{1193, "Vicious Vulture"},
{1194, "Swooping Evil"},
{1195, "Brutal Bloom"},
{1196, "Crawling Creeper"},
{1197, "Ecto-1 (2016)"},
{1198, "Ectozer"},
{1199, "PerfEcto"},
{1200, "Flash 'n' Finish"},
{1201, "Rampage Record Player"},
{1202, "Stripe's Throne"},
{1203, "R.C. Racer"},
{1204, "Gadget-O-Matic"},
{1205, "Scarlet Scorpion"},
{1206, "Hogwarts Express"},
{1208, "Steam Warrior"},
{1207, "Soaring Steam Plane"},
{1209, "Enchanted Car"},
{1210, "Shark Sub"},
{1211, "Monstrous Mouth"},
{1212, "IMF Scrambler"},
{1213, "Shock Cycle"},
{1214, "IMF Covert Jet"},
{1215, "IMF Sports Car"},
{1216, "IMF Tank"},
{1217, "IMF Splorer"},
{1218, "Sonic Speedster"},
{1219, "Blue Typhoon"},
{1220, "Moto Bug"},
{1221, "The Tornado"},
{1222, "Crabmeat"},
{1223, "Eggcatcher"},
{1224, "K.I.T.T."},
{1225, "Goliath Armored Semi"},
{1226, "K.I.T.T. Jet"},
{1227, "Police Helicopter"},
{1228, "Police Hovercraft"},
{1229, "Police Plane"},
{1230, "Bionic Steed"},
{1231, "Bat-Raptor"},
{1232, "Ultrabat"},
{1233, "Batwing"},
{1234, "The Black Thunder"},
{1235, "Bat-Tank"},
{1236, "Skeleton Organ"},
{1237, "Skeleton Jukebox"},
{1238, "Skele-Turkey"},
{1239, "One-Eyed Willy's Pirate Ship"},
{1240, "Fanged Fortune"},
{1241, "Inferno Cannon"},
{1242, "Buckbeak"},
{1243, "Giant Owl"},
{1244, "Fierce Falcon"},
{1245, "Saturn's Sandworm"},
{1247, "Haunted Vacuum"},
{1246, "Spooky Spider"},
{1248, "PPG Smartphone"},
{1249, "PPG Hotline"},
{1250, "Powerpuff Mag-Net"},
{1253, "Mega Blast Bot"},
{1251, "Ka-Pow Cannon"},
{1252, "Slammin' Guitar"},
{1254, "Octi"},
{1255, "Super Skunk"},
{1256, "Sonic Squid"},
{1257, "T-Car"},
{1258, "T-Forklift"},
{1259, "T-Plane"},
{1260, "Spellbook of Azarath"},
{1261, "Raven Wings"},
{1262, "Giant Hand"},
{1263, "Titan Robot"},
{1264, "T-Rocket"},
{1265, "Robot Retriever"}};
DimensionsToypadDevice::DimensionsToypadDevice()
: Device(0x0E6F, 0x0241, 1, 2, 0)
{
m_IsOpened = false;
}
bool DimensionsToypadDevice::Open()
{
if (!IsOpened())
{
m_IsOpened = true;
}
return true;
}
void DimensionsToypadDevice::Close()
{
if (IsOpened())
{
m_IsOpened = false;
}
}
bool DimensionsToypadDevice::IsOpened()
{
return m_IsOpened;
}
Device::ReadResult DimensionsToypadDevice::Read(ReadMessage* message)
{
memcpy(message->data, g_dimensionstoypad.GetStatus().data(), message->length);
message->bytesRead = message->length;
return Device::ReadResult::Success;
}
Device::WriteResult DimensionsToypadDevice::Write(WriteMessage* message)
{
if (message->length != 32)
return Device::WriteResult::Error;
g_dimensionstoypad.SendCommand(std::span<const uint8, 32>{message->data, 32});
message->bytesWritten = message->length;
return Device::WriteResult::Success;
}
bool DimensionsToypadDevice::GetDescriptor(uint8 descType,
uint8 descIndex,
uint8 lang,
uint8* output,
uint32 outputMaxLength)
{
uint8 configurationDescriptor[0x29];
uint8* currentWritePtr;
// configuration descriptor
currentWritePtr = configurationDescriptor + 0;
*(uint8*)(currentWritePtr + 0) = 9; // bLength
*(uint8*)(currentWritePtr + 1) = 2; // bDescriptorType
*(uint16be*)(currentWritePtr + 2) = 0x0029; // wTotalLength
*(uint8*)(currentWritePtr + 4) = 1; // bNumInterfaces
*(uint8*)(currentWritePtr + 5) = 1; // bConfigurationValue
*(uint8*)(currentWritePtr + 6) = 0; // iConfiguration
*(uint8*)(currentWritePtr + 7) = 0x80; // bmAttributes
*(uint8*)(currentWritePtr + 8) = 0xFA; // MaxPower
currentWritePtr = currentWritePtr + 9;
// configuration descriptor
*(uint8*)(currentWritePtr + 0) = 9; // bLength
*(uint8*)(currentWritePtr + 1) = 0x04; // bDescriptorType
*(uint8*)(currentWritePtr + 2) = 0; // bInterfaceNumber
*(uint8*)(currentWritePtr + 3) = 0; // bAlternateSetting
*(uint8*)(currentWritePtr + 4) = 2; // bNumEndpoints
*(uint8*)(currentWritePtr + 5) = 3; // bInterfaceClass
*(uint8*)(currentWritePtr + 6) = 0; // bInterfaceSubClass
*(uint8*)(currentWritePtr + 7) = 0; // bInterfaceProtocol
*(uint8*)(currentWritePtr + 8) = 0; // iInterface
currentWritePtr = currentWritePtr + 9;
// configuration descriptor
*(uint8*)(currentWritePtr + 0) = 9; // bLength
*(uint8*)(currentWritePtr + 1) = 0x21; // bDescriptorType
*(uint16be*)(currentWritePtr + 2) = 0x0111; // bcdHID
*(uint8*)(currentWritePtr + 4) = 0x00; // bCountryCode
*(uint8*)(currentWritePtr + 5) = 0x01; // bNumDescriptors
*(uint8*)(currentWritePtr + 6) = 0x22; // bDescriptorType
*(uint16be*)(currentWritePtr + 7) = 0x001D; // wDescriptorLength
currentWritePtr = currentWritePtr + 9;
// endpoint descriptor 1
*(uint8*)(currentWritePtr + 0) = 7; // bLength
*(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType
*(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress
*(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes
*(uint16be*)(currentWritePtr + 4) = 0x40; // wMaxPacketSize
*(uint8*)(currentWritePtr + 6) = 0x01; // bInterval
currentWritePtr = currentWritePtr + 7;
// endpoint descriptor 2
*(uint8*)(currentWritePtr + 0) = 7; // bLength
*(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType
*(uint8*)(currentWritePtr + 1) = 0x02; // bEndpointAddress
*(uint8*)(currentWritePtr + 2) = 0x03; // bmAttributes
*(uint16be*)(currentWritePtr + 3) = 0x40; // wMaxPacketSize
*(uint8*)(currentWritePtr + 5) = 0x01; // bInterval
currentWritePtr = currentWritePtr + 7;
cemu_assert_debug((currentWritePtr - configurationDescriptor) == 0x29);
memcpy(output, configurationDescriptor,
std::min<uint32>(outputMaxLength, sizeof(configurationDescriptor)));
return true;
}
bool DimensionsToypadDevice::SetProtocol(uint8 ifIndex, uint8 protocol)
{
cemuLog_log(LogType::Force, "Toypad Protocol");
return true;
}
bool DimensionsToypadDevice::SetReport(ReportMessage* message)
{
cemuLog_log(LogType::Force, "Toypad Report");
return true;
}
std::array<uint8, 32> DimensionsUSB::GetStatus()
{
std::array<uint8, 32> response = {};
bool responded = false;
do
{
if (!m_queries.empty())
{
response = m_queries.front();
m_queries.pop();
responded = true;
}
else if (!m_figureAddedRemovedResponses.empty() && m_isAwake)
{
std::lock_guard lock(m_dimensionsMutex);
response = m_figureAddedRemovedResponses.front();
m_figureAddedRemovedResponses.pop();
responded = true;
}
else
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
while (responded == false);
return response;
}
void DimensionsUSB::SendCommand(std::span<const uint8, 32> buf)
{
const uint8 command = buf[2];
const uint8 sequence = buf[3];
std::array<uint8, 32> q_result{};
switch (command)
{
case 0xB0: // Wake
{
// Consistent device response to the wake command
q_result = {0x55, 0x0e, 0x01, 0x28, 0x63, 0x29,
0x20, 0x4c, 0x45, 0x47, 0x4f, 0x20,
0x32, 0x30, 0x31, 0x34, 0x46};
break;
}
case 0xB1: // Seed
{
// Initialise a random number generator using the seed provided
g_dimensionstoypad.GenerateRandomNumber(std::span<const uint8, 8>{buf.begin() + 4, 8}, sequence, q_result);
break;
}
case 0xB3: // Challenge
{
// Get the next number in the sequence based on the RNG from 0xB1 command
g_dimensionstoypad.GetChallengeResponse(std::span<const uint8, 8>{buf.begin() + 4, 8}, sequence, q_result);
break;
}
case 0xC0: // Color
case 0xC1: // Get Pad Color
case 0xC2: // Fade
case 0xC3: // Flash
case 0xC4: // Fade Random
case 0xC6: // Fade All
case 0xC7: // Flash All
case 0xC8: // Color All
{
// Send a blank response to acknowledge color has been sent to toypad
q_result = {0x55, 0x01, sequence};
q_result[3] = GenerateChecksum(q_result, 3);
break;
}
case 0xD2: // Read
{
// Read 4 pages from the figure at index (buf[4]), starting with page buf[5]
g_dimensionstoypad.QueryBlock(buf[4], buf[5], q_result, sequence);
break;
}
case 0xD3: // Write
{
// Write 4 bytes to page buf[5] to the figure at index buf[4]
g_dimensionstoypad.WriteBlock(buf[4], buf[5], std::span<const uint8, 4>{buf.begin() + 6, 4}, q_result, sequence);
break;
}
case 0xD4: // Model
{
// Get the model id of the figure at index buf[4]
g_dimensionstoypad.GetModel(std::span<const uint8, 8>{buf.begin() + 4, 8}, sequence, q_result);
break;
}
case 0xD0: // Tag List
case 0xE1: // PWD
case 0xE5: // Active
case 0xFF: // LEDS Query
{
// Further investigation required
cemuLog_log(LogType::Force, "Unimplemented LD Function: {:x}", command);
break;
}
default:
{
cemuLog_log(LogType::Force, "Unknown LD Function: {:x}", command);
break;
}
}
m_queries.push(q_result);
}
uint32 DimensionsUSB::LoadFigure(const std::array<uint8, 0x2D * 0x04>& buf, std::unique_ptr<FileStream> file, uint8 pad, uint8 index)
{
std::lock_guard lock(m_dimensionsMutex);
const uint32 id = GetFigureId(buf);
DimensionsMini& figure = GetFigureByIndex(index);
figure.dimFile = std::move(file);
figure.id = id;
figure.pad = pad;
figure.index = index + 1;
figure.data = buf;
// When a figure is added to the toypad, respond to the game with the pad they were added to, their index,
// the direction (0x00 in byte 6 for added) and their UID
std::array<uint8, 32> figureChangeResponse = {0x56, 0x0b, figure.pad, 0x00, figure.index, 0x00, buf[0], buf[1], buf[2], buf[4], buf[5], buf[6], buf[7]};
figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13);
m_figureAddedRemovedResponses.push(figureChangeResponse);
return id;
}
bool DimensionsUSB::RemoveFigure(uint8 pad, uint8 index, bool fullRemove)
{
std::lock_guard lock(m_dimensionsMutex);
DimensionsMini& figure = GetFigureByIndex(index);
if (figure.index == 255)
return false;
// When a figure is removed from the toypad, respond to the game with the pad they were removed from, their index,
// the direction (0x01 in byte 6 for removed) and their UID
if (fullRemove)
{
std::array<uint8, 32> figureChangeResponse = {0x56, 0x0b, figure.pad, 0x00, figure.index, 0x01,
figure.data[0], figure.data[1], figure.data[2],
figure.data[4], figure.data[5], figure.data[6], figure.data[7]};
figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13);
m_figureAddedRemovedResponses.push(figureChangeResponse);
figure.Save();
figure.dimFile.reset();
}
figure.index = 255;
figure.pad = 255;
figure.id = 0;
return true;
}
bool DimensionsUSB::TempRemove(uint8 index)
{
std::lock_guard lock(m_dimensionsMutex);
DimensionsMini& figure = GetFigureByIndex(index);
if (figure.index == 255)
return false;
// Send a response to the game that the figure has been "Picked up" from existing slot,
// until either the movement is cancelled, or user chooses a space to move to
std::array<uint8, 32> figureChangeResponse = {0x56, 0x0b, figure.pad, 0x00, figure.index, 0x01,
figure.data[0], figure.data[1], figure.data[2],
figure.data[4], figure.data[5], figure.data[6], figure.data[7]};
figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13);
m_figureAddedRemovedResponses.push(figureChangeResponse);
return true;
}
bool DimensionsUSB::CancelRemove(uint8 index)
{
std::lock_guard lock(m_dimensionsMutex);
DimensionsMini& figure = GetFigureByIndex(index);
if (figure.index == 255)
return false;
// Cancel the previous movement of the figure
std::array<uint8, 32> figureChangeResponse = {0x56, 0x0b, figure.pad, 0x00, figure.index, 0x00,
figure.data[0], figure.data[1], figure.data[2],
figure.data[4], figure.data[5], figure.data[6], figure.data[7]};
figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13);
m_figureAddedRemovedResponses.push(figureChangeResponse);
return true;
}
bool DimensionsUSB::CreateFigure(fs::path pathName, uint32 id)
{
FileStream* dimFile(FileStream::createFile2(pathName));
if (!dimFile)
return false;
std::array<uint8, 0x2D * 0x04> fileData{};
RandomUID(fileData);
fileData[3] = id & 0xFF;
std::array<uint8, 7> uid = {fileData[0], fileData[1], fileData[2], fileData[4], fileData[5], fileData[6], fileData[7]};
// Only characters are created with their ID encrypted and stored in pages 36 and 37,
// as well as a password stored in page 43. Blank tags have their information populated
// by the game when it calls the write_block command.
if (id != 0)
{
const std::array<uint8, 16> figureKey = GenerateFigureKey(fileData);
std::array<uint8, 8> valueToEncrypt = {uint8(id & 0xFF), uint8((id >> 8) & 0xFF), uint8((id >> 16) & 0xFF), uint8((id >> 24) & 0xFF),
uint8(id & 0xFF), uint8((id >> 8) & 0xFF), uint8((id >> 16) & 0xFF), uint8((id >> 24) & 0xFF)};
std::array<uint8, 8> encrypted = Encrypt(valueToEncrypt, figureKey);
std::memcpy(&fileData[36 * 4], &encrypted[0], 4);
std::memcpy(&fileData[37 * 4], &encrypted[4], 4);
std::memcpy(&fileData[43 * 4], PWDGenerate(fileData).data(), 4);
}
else
{
// Page 38 is used as verification for blank tags
fileData[(38 * 4) + 1] = 1;
}
if (fileData.size() != dimFile->writeData(fileData.data(), fileData.size()))
{
delete dimFile;
return false;
}
delete dimFile;
return true;
}
bool DimensionsUSB::MoveFigure(uint8 pad, uint8 index, uint8 oldPad, uint8 oldIndex)
{
if (oldIndex == index)
{
// Don't bother removing and loading again, just send response to the game
CancelRemove(index);
return true;
}
// When moving figures between spaces on the toypad, remove any figure from the space they are moving to,
// then remove them from their current space, then load them to the space they are moving to
RemoveFigure(pad, index, true);
DimensionsMini& figure = GetFigureByIndex(oldIndex);
const std::array<uint8, 0x2D * 0x04> data = figure.data;
std::unique_ptr<FileStream> inFile = std::move(figure.dimFile);
RemoveFigure(oldPad, oldIndex, false);
LoadFigure(data, std::move(inFile), pad, index);
return true;
}
void DimensionsUSB::GenerateRandomNumber(std::span<const uint8, 8> buf, uint8 sequence,
std::array<uint8, 32>& replyBuf)
{
// Decrypt payload into an 8 byte array
std::array<uint8, 8> value = Decrypt(buf, std::nullopt);
// Seed is the first 4 bytes (little endian) of the decrypted payload
uint32 seed = (uint32&)value[0];
// Confirmation is the second 4 bytes (big endian) of the decrypted payload
uint32 conf = (uint32be&)value[4];
// Initialize rng using the seed from decrypted payload
InitializeRNG(seed);
// Encrypt 8 bytes, first 4 bytes is the decrypted confirmation from payload, 2nd 4 bytes are blank
std::array<uint8, 8> valueToEncrypt = {value[4], value[5], value[6], value[7], 0, 0, 0, 0};
std::array<uint8, 8> encrypted = Encrypt(valueToEncrypt, std::nullopt);
replyBuf[0] = 0x55;
replyBuf[1] = 0x09;
replyBuf[2] = sequence;
// Copy encrypted value to response data
memcpy(&replyBuf[3], encrypted.data(), encrypted.size());
replyBuf[11] = GenerateChecksum(replyBuf, 11);
}
void DimensionsUSB::GetChallengeResponse(std::span<const uint8, 8> buf, uint8 sequence,
std::array<uint8, 32>& replyBuf)
{
// Decrypt payload into an 8 byte array
std::array<uint8, 8> value = Decrypt(buf, std::nullopt);
// Confirmation is the first 4 bytes of the decrypted payload
uint32 conf = (uint32be&)value[0];
// Generate next random number based on RNG
uint32 nextRandom = GetNext();
// Encrypt an 8 byte array, first 4 bytes are the next random number (little endian)
// followed by the confirmation from the decrypted payload
std::array<uint8, 8> valueToEncrypt = {uint8(nextRandom & 0xFF), uint8((nextRandom >> 8) & 0xFF),
uint8((nextRandom >> 16) & 0xFF), uint8((nextRandom >> 24) & 0xFF),
value[0], value[1], value[2], value[3]};
std::array<uint8, 8> encrypted = Encrypt(valueToEncrypt, std::nullopt);
replyBuf[0] = 0x55;
replyBuf[1] = 0x09;
replyBuf[2] = sequence;
// Copy encrypted value to response data
memcpy(&replyBuf[3], encrypted.data(), encrypted.size());
replyBuf[11] = GenerateChecksum(replyBuf, 11);
if (!m_isAwake)
m_isAwake = true;
}
void DimensionsUSB::InitializeRNG(uint32 seed)
{
m_randomA = 0xF1EA5EED;
m_randomB = seed;
m_randomC = seed;
m_randomD = seed;
for (int i = 0; i < 42; i++)
{
GetNext();
}
}
uint32 DimensionsUSB::GetNext()
{
uint32 e = m_randomA - std::rotl(m_randomB, 21);
m_randomA = m_randomB ^ std::rotl(m_randomC, 19);
m_randomB = m_randomC + std::rotl(m_randomD, 6);
m_randomC = m_randomD + e;
m_randomD = e + m_randomA;
return m_randomD;
}
std::array<uint8, 8> DimensionsUSB::Decrypt(std::span<const uint8, 8> buf, std::optional<std::array<uint8, 16>> key)
{
// Value to decrypt is separated in to two little endian 32 bit unsigned integers
uint32 dataOne = (uint32&)buf[0];
uint32 dataTwo = (uint32&)buf[4];
// Use the key as 4 32 bit little endian unsigned integers
uint32 keyOne;
uint32 keyTwo;
uint32 keyThree;
uint32 keyFour;
if (key)
{
keyOne = (uint32&)key.value()[0];
keyTwo = (uint32&)key.value()[4];
keyThree = (uint32&)key.value()[8];
keyFour = (uint32&)key.value()[12];
}
else
{
keyOne = (uint32&)COMMAND_KEY[0];
keyTwo = (uint32&)COMMAND_KEY[4];
keyThree = (uint32&)COMMAND_KEY[8];
keyFour = (uint32&)COMMAND_KEY[12];
}
uint32 sum = 0xC6EF3720;
uint32 delta = 0x9E3779B9;
for (int i = 0; i < 32; i++)
{
dataTwo -= (((dataOne << 4) + keyThree) ^ (dataOne + sum) ^ ((dataOne >> 5) + keyFour));
dataOne -= (((dataTwo << 4) + keyOne) ^ (dataTwo + sum) ^ ((dataTwo >> 5) + keyTwo));
sum -= delta;
}
cemu_assert(sum == 0);
std::array<uint8, 8> decrypted = {uint8(dataOne & 0xFF), uint8((dataOne >> 8) & 0xFF),
uint8((dataOne >> 16) & 0xFF), uint8((dataOne >> 24) & 0xFF),
uint8(dataTwo & 0xFF), uint8((dataTwo >> 8) & 0xFF),
uint8((dataTwo >> 16) & 0xFF), uint8((dataTwo >> 24) & 0xFF)};
return decrypted;
}
std::array<uint8, 8> DimensionsUSB::Encrypt(std::span<const uint8, 8> buf, std::optional<std::array<uint8, 16>> key)
{
// Value to encrypt is separated in to two little endian 32 bit unsigned integers
uint32 dataOne = (uint32&)buf[0];
uint32 dataTwo = (uint32&)buf[4];
// Use the key as 4 32 bit little endian unsigned integers
uint32 keyOne;
uint32 keyTwo;
uint32 keyThree;
uint32 keyFour;
if (key)
{
keyOne = (uint32&)key.value()[0];
keyTwo = (uint32&)key.value()[4];
keyThree = (uint32&)key.value()[8];
keyFour = (uint32&)key.value()[12];
}
else
{
keyOne = (uint32&)COMMAND_KEY[0];
keyTwo = (uint32&)COMMAND_KEY[4];
keyThree = (uint32&)COMMAND_KEY[8];
keyFour = (uint32&)COMMAND_KEY[12];
}
uint32 sum = 0;
uint32 delta = 0x9E3779B9;
for (int i = 0; i < 32; i++)
{
sum += delta;
dataOne += (((dataTwo << 4) + keyOne) ^ (dataTwo + sum) ^ ((dataTwo >> 5) + keyTwo));
dataTwo += (((dataOne << 4) + keyThree) ^ (dataOne + sum) ^ ((dataOne >> 5) + keyFour));
}
cemu_assert(sum == 0xC6EF3720);
std::array<uint8, 8> encrypted = {uint8(dataOne & 0xFF), uint8((dataOne >> 8) & 0xFF),
uint8((dataOne >> 16) & 0xFF), uint8((dataOne >> 24) & 0xFF),
uint8(dataTwo & 0xFF), uint8((dataTwo >> 8) & 0xFF),
uint8((dataTwo >> 16) & 0xFF), uint8((dataTwo >> 24) & 0xFF)};
return encrypted;
}
std::array<uint8, 16> DimensionsUSB::GenerateFigureKey(const std::array<uint8, 0x2D * 0x04>& buf)
{
std::array<uint8, 7> uid = {buf[0], buf[1], buf[2], buf[4], buf[5], buf[6], buf[7]};
uint32 scrambleA = Scramble(uid, 3);
uint32 scrambleB = Scramble(uid, 4);
uint32 scrambleC = Scramble(uid, 5);
uint32 scrambleD = Scramble(uid, 6);
return {uint8((scrambleA >> 24) & 0xFF), uint8((scrambleA >> 16) & 0xFF),
uint8((scrambleA >> 8) & 0xFF), uint8(scrambleA & 0xFF),
uint8((scrambleB >> 24) & 0xFF), uint8((scrambleB >> 16) & 0xFF),
uint8((scrambleB >> 8) & 0xFF), uint8(scrambleB & 0xFF),
uint8((scrambleC >> 24) & 0xFF), uint8((scrambleC >> 16) & 0xFF),
uint8((scrambleC >> 8) & 0xFF), uint8(scrambleC & 0xFF),
uint8((scrambleD >> 24) & 0xFF), uint8((scrambleD >> 16) & 0xFF),
uint8((scrambleD >> 8) & 0xFF), uint8(scrambleD & 0xFF)};
}
uint32 DimensionsUSB::Scramble(const std::array<uint8, 7>& uid, uint8 count)
{
std::vector<uint8> toScramble;
toScramble.reserve(uid.size() + CHAR_CONSTANT.size());
for (uint8 x : uid)
{
toScramble.push_back(x);
}
for (uint8 c : CHAR_CONSTANT)
{
toScramble.push_back(c);
}
toScramble[(count * 4) - 1] = 0xaa;
std::array<uint8, 4> randomized = DimensionsRandomize(toScramble, count);
return (uint32be&)randomized[0];
}
std::array<uint8, 4> DimensionsUSB::PWDGenerate(const std::array<uint8, 0x2D * 0x04>& buf)
{
std::array<uint8, 7> uid = {buf[0], buf[1], buf[2], buf[4], buf[5], buf[6], buf[7]};
std::vector<uint8> pwdCalc = {PWD_CONSTANT.begin(), PWD_CONSTANT.end() - 1};
for (uint8 i = 0; i < uid.size(); i++)
{
pwdCalc.insert(pwdCalc.begin() + i, uid[i]);
}
return DimensionsRandomize(pwdCalc, 8);
}
std::array<uint8, 4> DimensionsUSB::DimensionsRandomize(const std::vector<uint8> key, uint8 count)
{
uint32 scrambled = 0;
for (uint8 i = 0; i < count; i++)
{
const uint32 v4 = std::rotr(scrambled, 25);
const uint32 v5 = std::rotr(scrambled, 10);
const uint32 b = (uint32&)key[i * 4];
scrambled = b + v4 + v5 - scrambled;
}
return {uint8(scrambled & 0xFF), uint8(scrambled >> 8 & 0xFF), uint8(scrambled >> 16 & 0xFF), uint8(scrambled >> 24 & 0xFF)};
}
uint32 DimensionsUSB::GetFigureId(const std::array<uint8, 0x2D * 0x04>& buf)
{
const std::array<uint8, 16> figureKey = GenerateFigureKey(buf);
const std::span<const uint8, 8> modelNumber = std::span<const uint8, 8>{buf.begin() + (36 * 4), 8};
const std::array<uint8, 8> decrypted = Decrypt(modelNumber, figureKey);
const uint32 figNum = (uint32&)decrypted[0];
// Characters have their model number encrypted in page 36
if (figNum < 1000)
{
return figNum;
}
// Vehicles/Gadgets have their model number written as little endian in page 36
return (uint32&)modelNumber[0];
}
DimensionsUSB::DimensionsMini&
DimensionsUSB::GetFigureByIndex(uint8 index)
{
return m_figures[index];
}
void DimensionsUSB::QueryBlock(uint8 index, uint8 page,
std::array<uint8, 32>& replyBuf,
uint8 sequence)
{
std::lock_guard lock(m_dimensionsMutex);
replyBuf[0] = 0x55;
replyBuf[1] = 0x12;
replyBuf[2] = sequence;
replyBuf[3] = 0x00;
// Index from game begins at 1 rather than 0, so minus 1 here
if (const uint8 figureIndex = index - 1; figureIndex < 7)
{
const DimensionsMini& figure = GetFigureByIndex(figureIndex);
// Query 4 pages of 4 bytes from the figure, copy this to the response
if (figure.index != 255 && (4 * page) < ((0x2D * 4) - 16))
{
std::memcpy(&replyBuf[4], figure.data.data() + (4 * page), 16);
}
}
replyBuf[20] = GenerateChecksum(replyBuf, 20);
}
void DimensionsUSB::WriteBlock(uint8 index, uint8 page, std::span<const uint8, 4> toWriteBuf,
std::array<uint8, 32>& replyBuf, uint8 sequence)
{
std::lock_guard lock(m_dimensionsMutex);
replyBuf[0] = 0x55;
replyBuf[1] = 0x02;
replyBuf[2] = sequence;
replyBuf[3] = 0x00;
// Index from game begins at 1 rather than 0, so minus 1 here
if (const uint8 figureIndex = index - 1; figureIndex < 7)
{
DimensionsMini& figure = GetFigureByIndex(figureIndex);
// Copy 4 bytes to the page on the figure requested by the game
if (figure.index != 255 && page < 0x2D)
{
// Id is written to page 36
if (page == 36)
{
figure.id = (uint32&)toWriteBuf[0];
}
std::memcpy(figure.data.data() + (page * 4), toWriteBuf.data(), 4);
figure.Save();
}
}
replyBuf[4] = GenerateChecksum(replyBuf, 4);
}
void DimensionsUSB::GetModel(std::span<const uint8, 8> buf, uint8 sequence,
std::array<uint8, 32>& replyBuf)
{
// Decrypt payload to 8 byte array, byte 1 is the index, 4-7 are the confirmation
std::array<uint8, 8> value = Decrypt(buf, std::nullopt);
uint8 index = value[0];
uint32 conf = (uint32be&)value[4];
// Response is the figure's id (little endian) followed by the confirmation from payload
// Index from game begins at 1 rather than 0, so minus 1 here
std::array<uint8, 8> valueToEncrypt = {};
if (const uint8 figureIndex = index - 1; figureIndex < 7)
{
const DimensionsMini& figure = GetFigureByIndex(figureIndex);
valueToEncrypt = {uint8(figure.id & 0xFF), uint8((figure.id >> 8) & 0xFF),
uint8((figure.id >> 16) & 0xFF), uint8((figure.id >> 24) & 0xFF),
value[4], value[5], value[6], value[7]};
}
std::array<uint8, 8> encrypted = Encrypt(valueToEncrypt, std::nullopt);
replyBuf[0] = 0x55;
replyBuf[1] = 0x0a;
replyBuf[2] = sequence;
replyBuf[3] = 0x00;
memcpy(&replyBuf[4], encrypted.data(), encrypted.size());
replyBuf[12] = GenerateChecksum(replyBuf, 12);
}
void DimensionsUSB::RandomUID(std::array<uint8, 0x2D * 0x04>& uid_buffer)
{
uid_buffer[0] = 0x04;
uid_buffer[7] = 0x80;
std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<int> dist(0, 255);
uid_buffer[1] = dist(mt);
uid_buffer[2] = dist(mt);
uid_buffer[4] = dist(mt);
uid_buffer[5] = dist(mt);
uid_buffer[6] = dist(mt);
}
uint8 DimensionsUSB::GenerateChecksum(const std::array<uint8, 32>& data,
int num_of_bytes) const
{
int checksum = 0;
for (int i = 0; i < num_of_bytes; i++)
{
checksum += data[i];
}
return (checksum & 0xFF);
}
void DimensionsUSB::DimensionsMini::Save()
{
if (!dimFile)
return;
dimFile->SetPosition(0);
dimFile->writeData(data.data(), data.size());
}
std::map<const uint32, const char*> DimensionsUSB::GetListMinifigs()
{
return s_listMinis;
}
std::map<const uint32, const char*> DimensionsUSB::GetListTokens()
{
return s_listTokens;
}
std::string DimensionsUSB::FindFigure(uint32 figNum)
{
for (const auto& it : GetListMinifigs())
{
if (it.first == figNum)
{
return it.second;
}
}
for (const auto& it : GetListTokens())
{
if (it.first == figNum)
{
return it.second;
}
}
return fmt::format("Unknown ({})", figNum);
}
} // namespace nsyshid