From b409d5d139d5039986335a068356ea9203253cb4 Mon Sep 17 00:00:00 2001 From: deReeperJosh Date: Fri, 27 Oct 2023 22:47:27 +0100 Subject: [PATCH 1/9] nsyshid: Emulate Dimensions Toypad --- src/Cafe/CMakeLists.txt | 2 + src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp | 9 + src/Cafe/OS/libs/nsyshid/Dimensions.cpp | 1076 +++++++++++++++++ src/Cafe/OS/libs/nsyshid/Dimensions.h | 102 ++ src/config/CemuConfig.cpp | 2 + src/config/CemuConfig.h | 1 + .../EmulatedUSBDeviceFrame.cpp | 228 +++- .../EmulatedUSBDeviceFrame.h | 18 + 8 files changed, 1434 insertions(+), 4 deletions(-) create mode 100644 src/Cafe/OS/libs/nsyshid/Dimensions.cpp create mode 100644 src/Cafe/OS/libs/nsyshid/Dimensions.h diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 91d257b2e..0901fece9 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -465,6 +465,8 @@ add_library(CemuCafe OS/libs/nsyshid/BackendLibusb.h OS/libs/nsyshid/BackendWindowsHID.cpp OS/libs/nsyshid/BackendWindowsHID.h + OS/libs/nsyshid/Dimensions.cpp + OS/libs/nsyshid/Dimensions.h OS/libs/nsyshid/Infinity.cpp OS/libs/nsyshid/Infinity.h OS/libs/nsyshid/Skylander.cpp diff --git a/src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp b/src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp index 95eaf06ab..533d349e9 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp +++ b/src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp @@ -1,4 +1,6 @@ #include "BackendEmulated.h" + +#include "Dimensions.h" #include "Infinity.h" #include "Skylander.h" #include "config/CemuConfig.h" @@ -33,5 +35,12 @@ namespace nsyshid::backend::emulated auto device = std::make_shared(); AttachDevice(device); } + if (GetConfig().emulated_usb_devices.emulate_dimensions_toypad && !FindDeviceById(0x0E6F, 0x0241)) + { + cemuLog_logDebug(LogType::Force, "Attaching Emulated Toypad"); + // Add Dimensions Toypad + auto device = std::make_shared(); + AttachDevice(device); + } } } // namespace nsyshid::backend::emulated \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyshid/Dimensions.cpp b/src/Cafe/OS/libs/nsyshid/Dimensions.cpp new file mode 100644 index 000000000..5c4a97613 --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/Dimensions.cpp @@ -0,0 +1,1076 @@ +#include "Dimensions.h" + +#include "nsyshid.h" +#include "Backend.h" + +#include "Common/FileStream.h" + +#include +#include + +namespace nsyshid +{ + static constexpr std::array COMMAND_KEY = {0x55, 0xFE, 0xF6, 0xB0, 0x62, 0xBF, 0x0B, 0x41, + 0xC9, 0xB3, 0x7C, 0xB4, 0x97, 0x3E, 0x29, 0x7B}; + + static constexpr std::array CHAR_CONSTANT = {0xB7, 0xD5, 0xD7, 0xE6, 0xE7, 0xBA, 0x3C, 0xA8, + 0xD8, 0x75, 0x47, 0x68, 0xCF, 0x23, 0xE9, 0xFE, 0xAA}; + + static constexpr std::array 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 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 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) + { + g_dimensionstoypad.SendCommand(message->data, message->length); + 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(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 DimensionsUSB::GetStatus() + { + std::array response = {}; + + bool responded = false; + do + { + if (!m_figureAddedRemovedResponses.empty()) + { + memcpy(response.data(), m_figureAddedRemovedResponses.front().data(), + 0x20); + m_figureAddedRemovedResponses.pop(); + responded = true; + } + else if (!m_queries.empty()) + { + memcpy(response.data(), m_queries.front().data(), 0x20); + m_queries.pop(); + responded = true; + } + else + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + } + while (responded == false); + return response; + } + + void DimensionsUSB::SendCommand(uint8* buf, sint32 originalLength) + { + const uint8 command = buf[2]; + const uint8 sequence = buf[3]; + + std::array 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(&buf[4], sequence, q_result); + break; + } + case 0xB3: // Challenge + { + // Get the next number in the sequence based on the RNG from 0xB1 command + g_dimensionstoypad.GetChallengeResponse(&buf[4], 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], &buf[6], q_result, sequence); + break; + } + case 0xD4: // Model + { + // Get the model id of the figure at index buf[4] + g_dimensionstoypad.GetModel(&buf[4], 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& buf, std::unique_ptr 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; + memcpy(figure.data.data(), buf.data(), buf.size()); + // 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 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) + { + 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 + std::array 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]}; + figure.Save(); + figure.dimFile.reset(); + figure.index = 255; + figure.pad = 255; + figure.id = 0; + memcpy(&figureChangeResponse[6], figure.data.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 fileData{}; + RandomUID(fileData.data()); + fileData[7] = id & 0xFF; + + std::array 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 figureKey = GenerateFigureKey(fileData); + + std::array 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 encrypted = Encrypt(valueToEncrypt.data(), 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; + } + + void DimensionsUSB::GenerateRandomNumber(uint8* buf, uint8 sequence, + std::array& replyBuf) + { + // Decrypt payload into an 8 byte array + std::array value = Decrypt(buf, std::nullopt); + // Seed is the first 4 bytes (little endian) of the decrypted payload + uint32 seed = uint32(value[3]) << 24 | uint32(value[2]) << 16 | uint32(value[1]) << 8 | uint32(value[0]); + // Confirmation is the second 4 bytes (big endian) of the decrypted payload + uint32 conf = uint32(value[4]) << 24 | uint32(value[5]) << 16 | uint32(value[6]) << 8 | uint32(value[7]); + // 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 valueToEncrypt = {value[4], value[5], value[6], value[7], 0, 0, 0, 0}; + std::array encrypted = Encrypt(valueToEncrypt.data(), 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(uint8* buf, uint8 sequence, + std::array& replyBuf) + { + // Decrypt payload into an 8 byte array + std::array value = Decrypt(buf, std::nullopt); + // Confirmation is the first 4 bytes of the decrypted payload + uint32 conf = uint32(value[0]) << 24 | uint32(value[1]) << 16 | uint32(value[2]) << 8 | uint32(value[3]); + // 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 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 encrypted = Encrypt(valueToEncrypt.data(), 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::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 DimensionsUSB::Decrypt(const uint8* buf, std::optional> key) + { + // Value to decrypt is separated in to two little endian 32 bit unsigned integers + uint32 dataOne = uint32(buf[3]) << 24 | uint32(buf[2]) << 16 | uint32(buf[1]) << 8 | uint32(buf[0]); + uint32 dataTwo = uint32(buf[7]) << 24 | uint32(buf[6]) << 16 | uint32(buf[5]) << 8 | 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()[3]) << 24 | uint32(key.value()[2]) << 16 | uint32(key.value()[1]) << 8 | uint32(key.value()[0]); + keyTwo = uint32(key.value()[7]) << 24 | uint32(key.value()[6]) << 16 | uint32(key.value()[5]) << 8 | uint32(key.value()[4]); + keyThree = uint32(key.value()[11]) << 24 | uint32(key.value()[10]) << 16 | uint32(key.value()[9]) << 8 | uint32(key.value()[8]); + keyFour = uint32(key.value()[15]) << 24 | uint32(key.value()[14]) << 16 | uint32(key.value()[13]) << 8 | uint32(key.value()[12]); + } + else + { + keyOne = uint32(COMMAND_KEY[3]) << 24 | uint32(COMMAND_KEY[2]) << 16 | uint32(COMMAND_KEY[1]) << 8 | uint32(COMMAND_KEY[0]); + keyTwo = uint32(COMMAND_KEY[7]) << 24 | uint32(COMMAND_KEY[6]) << 16 | uint32(COMMAND_KEY[5]) << 8 | uint32(COMMAND_KEY[4]); + keyThree = uint32(COMMAND_KEY[11]) << 24 | uint32(COMMAND_KEY[10]) << 16 | uint32(COMMAND_KEY[9]) << 8 | uint32(COMMAND_KEY[8]); + keyFour = uint32(COMMAND_KEY[15]) << 24 | uint32(COMMAND_KEY[14]) << 16 | uint32(COMMAND_KEY[13]) << 8 | 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 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 DimensionsUSB::Encrypt(const uint8* buf, std::optional> key) + { + // Value to encrypt is separated in to two little endian 32 bit unsigned integers + uint32 dataOne = uint32(buf[3]) << 24 | uint32(buf[2]) << 16 | uint32(buf[1]) << 8 | uint32(buf[0]); + uint32 dataTwo = uint32(buf[7]) << 24 | uint32(buf[6]) << 16 | uint32(buf[5]) << 8 | 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()[3]) << 24 | uint32(key.value()[2]) << 16 | uint32(key.value()[1]) << 8 | uint32(key.value()[0]); + keyTwo = uint32(key.value()[7]) << 24 | uint32(key.value()[6]) << 16 | uint32(key.value()[5]) << 8 | uint32(key.value()[4]); + keyThree = uint32(key.value()[11]) << 24 | uint32(key.value()[10]) << 16 | uint32(key.value()[9]) << 8 | uint32(key.value()[8]); + keyFour = uint32(key.value()[15]) << 24 | uint32(key.value()[14]) << 16 | uint32(key.value()[13]) << 8 | uint32(key.value()[12]); + } + else + { + keyOne = uint32(COMMAND_KEY[3]) << 24 | uint32(COMMAND_KEY[2]) << 16 | uint32(COMMAND_KEY[1]) << 8 | uint32(COMMAND_KEY[0]); + keyTwo = uint32(COMMAND_KEY[7]) << 24 | uint32(COMMAND_KEY[6]) << 16 | uint32(COMMAND_KEY[5]) << 8 | uint32(COMMAND_KEY[4]); + keyThree = uint32(COMMAND_KEY[11]) << 24 | uint32(COMMAND_KEY[10]) << 16 | uint32(COMMAND_KEY[9]) << 8 | uint32(COMMAND_KEY[8]); + keyFour = uint32(COMMAND_KEY[15]) << 24 | uint32(COMMAND_KEY[14]) << 16 | uint32(COMMAND_KEY[13]) << 8 | 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 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 DimensionsUSB::GenerateFigureKey(const std::array& buf) + { + std::array 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& uid, uint8 count) + { + std::vector 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 randomized = DimensionsRandomize(toScramble, count); + + return uint32(randomized[0]) << 24 | uint32(randomized[1]) << 16 | uint32(randomized[2]) << 8 | uint32(randomized[3]); + } + + std::array DimensionsUSB::PWDGenerate(const std::array& buf) + { + std::array uid = {buf[0], buf[1], buf[2], buf[4], buf[5], buf[6], buf[7]}; + + std::vector 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 DimensionsUSB::DimensionsRandomize(const std::vector 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) + 3]) << 24 | uint32(key[(i * 4) + 2]) << 16 | uint32(key[(i * 4) + 1]) << 8 | 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& buf) + { + const std::array figureKey = GenerateFigureKey(buf); + + const std::array decrypted = Decrypt(&buf[36 * 4], figureKey); + + const uint32 figNum = uint32(decrypted[3]) << 24 | uint32(decrypted[2]) << 16 | uint32(decrypted[1]) << 8 | 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(buf[(36 * 4) + 3]) << 24 | uint32(buf[(36 * 4) + 2]) << 16 | uint32(buf[(36 * 4) + 1]) << 8 | uint32(buf[36 * 4]); + } + + DimensionsUSB::DimensionsMini& + DimensionsUSB::GetFigureByIndex(uint8 index) + { + return figures[index]; + } + + void DimensionsUSB::QueryBlock(uint8 index, uint8 page, + std::array& 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, const uint8* toWriteBuf, + std::array& 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[3]) << 24 | uint32(toWriteBuf[2]) << 16 | uint32(toWriteBuf[1]) << 8 | uint32(toWriteBuf[0]); + } + std::memcpy(figure.data.data() + (page * 4), toWriteBuf, 4); + figure.Save(); + } + } + replyBuf[4] = GenerateChecksum(replyBuf, 4); + } + + void DimensionsUSB::GetModel(uint8* buf, uint8 sequence, + std::array& replyBuf) + { + // Decrypt payload to 8 byte array, byte 1 is the index, 4-7 are the confirmation + std::array value = Decrypt(buf, std::nullopt); + uint8 index = value[0]; + uint32 conf = uint32(value[4]) << 24 | uint32(value[5]) << 16 | uint32(value[6]) << 8 | uint32(value[7]); + // 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 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 encrypted = Encrypt(valueToEncrypt.data(), 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(uint8* uid_buffer) + { + uid_buffer[0] = 0x04; + uid_buffer[6] = 0x80; + + std::random_device rd; + std::mt19937 mt(rd()); + std::uniform_int_distribution dist(0, 255); + + uid_buffer[1] = dist(mt); + uid_buffer[2] = dist(mt); + uid_buffer[3] = dist(mt); + uid_buffer[4] = dist(mt); + uid_buffer[5] = dist(mt); + } + + uint8 DimensionsUSB::GenerateChecksum(const std::array& 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 DimensionsUSB::GetListMinifigs() + { + return s_listMinis; + } + + std::string DimensionsUSB::FindFigure(uint32 figNum) + { + for (const auto& it : GetListMinifigs()) + { + if (it.first == figNum) + { + return it.second; + } + } + return fmt::format("Unknown ({})", figNum); + } +} // namespace nsyshid \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyshid/Dimensions.h b/src/Cafe/OS/libs/nsyshid/Dimensions.h new file mode 100644 index 000000000..17e2fd9c0 --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/Dimensions.h @@ -0,0 +1,102 @@ +#include + +#include "nsyshid.h" +#include "Backend.h" + +#include "Common/FileStream.h" + +namespace nsyshid +{ + class DimensionsToypadDevice final : public Device + { + public: + DimensionsToypadDevice(); + ~DimensionsToypadDevice() = default; + + bool Open() override; + + void Close() override; + + bool IsOpened() override; + + ReadResult Read(ReadMessage* message) override; + + WriteResult Write(WriteMessage* message) override; + + bool GetDescriptor(uint8 descType, + uint8 descIndex, + uint8 lang, + uint8* output, + uint32 outputMaxLength) override; + + bool SetProtocol(uint8 ifIndex, uint8 protocol) override; + + bool SetReport(ReportMessage* message) override; + + private: + bool m_IsOpened; + }; + + class DimensionsUSB + { + public: + struct DimensionsMini final + { + std::unique_ptr dimFile; + std::array data{}; + uint8 index = 255; + uint8 pad = 255; + uint32 id = 0; + void Save(); + }; + + void SendCommand(uint8* buf, sint32 originalLength); + std::array GetStatus(); + + void GenerateRandomNumber(uint8* buf, uint8 sequence, + std::array& replyBuf); + void InitializeRNG(uint32 seed); + void GetChallengeResponse(uint8* buf, uint8 sequence, + std::array& replyBuf); + void QueryBlock(uint8 index, uint8 page, std::array& replyBuf, + uint8 sequence); + void WriteBlock(uint8 index, uint8 page, const uint8* toWriteBuf, std::array& replyBuf, + uint8 sequence); + void GetModel(uint8* buf, uint8 sequence, + std::array& replyBuf); + + bool RemoveFigure(uint8 pad, uint8 index); + uint32 LoadFigure(const std::array& buf, std::unique_ptr file, uint8 pad, uint8 index); + bool CreateFigure(fs::path pathName, uint32 id); + static std::map GetListMinifigs(); + std::string FindFigure(uint32 figNum); + + protected: + std::mutex m_dimensionsMutex; + std::array figures; + + private: + void RandomUID(uint8* uidBuffer); + uint8 GenerateChecksum(const std::array& data, + int numOfBytes) const; + std::array Decrypt(const uint8* buf, std::optional> key); + std::array Encrypt(const uint8* buf, std::optional> key); + std::array GenerateFigureKey(const std::array& uid); + std::array PWDGenerate(const std::array& uid); + std::array DimensionsRandomize(const std::vector key, uint8 count); + uint32 GetFigureId(const std::array& buf); + uint32 Scramble(const std::array& uid, uint8 count); + uint32 GetNext(); + DimensionsMini& GetFigureByIndex(uint8 index); + + uint32 m_randomA; + uint32 m_randomB; + uint32 m_randomC; + uint32 m_randomD; + + std::queue> m_figureAddedRemovedResponses; + std::queue> m_queries; + }; + extern DimensionsUSB g_dimensionstoypad; + +} // namespace nsyshid \ No newline at end of file diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index e7920e84b..f5ee7ab41 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -346,6 +346,7 @@ void CemuConfig::Load(XMLConfigParser& parser) auto usbdevices = parser.get("EmulatedUsbDevices"); emulated_usb_devices.emulate_skylander_portal = usbdevices.get("EmulateSkylanderPortal", emulated_usb_devices.emulate_skylander_portal); emulated_usb_devices.emulate_infinity_base = usbdevices.get("EmulateInfinityBase", emulated_usb_devices.emulate_infinity_base); + emulated_usb_devices.emulate_dimensions_toypad = usbdevices.get("EmulateDimensionsToypad", emulated_usb_devices.emulate_dimensions_toypad); } void CemuConfig::Save(XMLConfigParser& parser) @@ -545,6 +546,7 @@ void CemuConfig::Save(XMLConfigParser& parser) auto usbdevices = config.set("EmulatedUsbDevices"); usbdevices.set("EmulateSkylanderPortal", emulated_usb_devices.emulate_skylander_portal.GetValue()); usbdevices.set("EmulateInfinityBase", emulated_usb_devices.emulate_infinity_base.GetValue()); + usbdevices.set("EmulateDimensionsToypad", emulated_usb_devices.emulate_dimensions_toypad.GetValue()); } GameEntry* CemuConfig::GetGameEntryByTitleId(uint64 titleId) diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index 2f22cd768..be1312661 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -521,6 +521,7 @@ struct CemuConfig { ConfigValue emulate_skylander_portal{false}; ConfigValue emulate_infinity_base{false}; + ConfigValue emulate_dimensions_toypad{false}; }emulated_usb_devices{}; private: diff --git a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp index 3a0f534a2..2fec5f5f4 100644 --- a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp +++ b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp @@ -1,4 +1,4 @@ -#include "gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h" +#include "EmulatedUSBDeviceFrame.h" #include @@ -8,14 +8,17 @@ #include "util/helpers/helpers.h" #include "Cafe/OS/libs/nsyshid/nsyshid.h" +#include "Cafe/OS/libs/nsyshid/Dimensions.h" #include "Common/FileStream.h" #include #include +#include #include #include #include +#include #include #include #include @@ -29,7 +32,6 @@ #include #include "resource/embedded/resources.h" -#include "EmulatedUSBDeviceFrame.h" EmulatedUSBDeviceFrame::EmulatedUSBDeviceFrame(wxWindow* parent) : wxFrame(parent, wxID_ANY, _("Emulated USB Devices"), wxDefaultPosition, @@ -44,6 +46,7 @@ EmulatedUSBDeviceFrame::EmulatedUSBDeviceFrame(wxWindow* parent) notebook->AddPage(AddSkylanderPage(notebook), _("Skylanders Portal")); notebook->AddPage(AddInfinityPage(notebook), _("Infinity Base")); + notebook->AddPage(AddDimensionsPage(notebook), _("Dimensions Toypad")); sizer->Add(notebook, 1, wxEXPAND | wxALL, 2); @@ -120,6 +123,51 @@ wxPanel* EmulatedUSBDeviceFrame::AddInfinityPage(wxNotebook* notebook) return panel; } +wxPanel* EmulatedUSBDeviceFrame::AddDimensionsPage(wxNotebook* notebook) +{ + auto* panel = new wxPanel(notebook); + auto* panel_sizer = new wxBoxSizer(wxVERTICAL); + auto* box = new wxStaticBox(panel, wxID_ANY, _("Dimensions Manager")); + auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); + + auto* row = new wxBoxSizer(wxHORIZONTAL); + + m_emulate_toypad = + new wxCheckBox(box, wxID_ANY, _("Emulate Dimensions Toypad")); + m_emulate_toypad->SetValue( + GetConfig().emulated_usb_devices.emulate_dimensions_toypad); + m_emulate_toypad->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { + GetConfig().emulated_usb_devices.emulate_dimensions_toypad = + m_emulate_toypad->IsChecked(); + g_config.Save(); + }); + row->Add(m_emulate_toypad, 1, wxEXPAND | wxALL, 2); + box_sizer->Add(row, 1, wxEXPAND | wxALL, 2); + auto* top_row = new wxBoxSizer(wxHORIZONTAL); + auto* bottom_row = new wxBoxSizer(wxHORIZONTAL); + + auto* dummy = new wxStaticText(box, wxID_ANY, ""); + + top_row->Add(AddDimensionPanel(2, 0, box), 1, wxEXPAND | wxALL, 2); + top_row->Add(dummy, 1, wxEXPAND | wxLEFT | wxRIGHT, 2); + top_row->Add(AddDimensionPanel(1, 1, box), 1, wxEXPAND | wxALL, 2); + top_row->Add(dummy, 1, wxEXPAND | wxLEFT | wxRIGHT, 2); + top_row->Add(AddDimensionPanel(3, 2, box), 1, wxEXPAND | wxALL, 2); + + bottom_row->Add(AddDimensionPanel(2, 3, box), 1, wxEXPAND | wxALL, 2); + bottom_row->Add(AddDimensionPanel(2, 4, box), 1, wxEXPAND | wxALL, 2); + bottom_row->Add(dummy, 1, wxEXPAND | wxLEFT | wxRIGHT, 0); + bottom_row->Add(AddDimensionPanel(3, 5, box), 1, wxEXPAND | wxALL, 2); + bottom_row->Add(AddDimensionPanel(3, 6, box), 1, wxEXPAND | wxALL, 2); + + box_sizer->Add(top_row, 1, wxEXPAND | wxALL, 2); + box_sizer->Add(bottom_row, 1, wxEXPAND | wxALL, 2); + panel_sizer->Add(box_sizer, 1, wxEXPAND | wxALL, 2); + panel->SetSizerAndFit(panel_sizer); + + return panel; +} + wxBoxSizer* EmulatedUSBDeviceFrame::AddSkylanderRow(uint8 rowNumber, wxStaticBox* box) { @@ -184,6 +232,41 @@ wxBoxSizer* EmulatedUSBDeviceFrame::AddInfinityRow(wxString name, uint8 rowNumbe return row; } +wxBoxSizer* EmulatedUSBDeviceFrame::AddDimensionPanel(uint8 pad, uint8 index, wxStaticBox* box) +{ + auto* panel = new wxBoxSizer(wxVERTICAL); + + auto* combo_row = new wxBoxSizer(wxHORIZONTAL); + m_dimension_slots[index] = new wxTextCtrl(box, wxID_ANY, _("None"), wxDefaultPosition, wxDefaultSize, + wxTE_READONLY); + combo_row->Add(m_dimension_slots[index], 1, wxEXPAND | wxALL, 2); + auto* move_button = new wxButton(box, wxID_ANY, _("Move")); + + combo_row->Add(move_button, 1, wxEXPAND | wxALL, 2); + + auto* button_row = new wxBoxSizer(wxHORIZONTAL); + auto* load_button = new wxButton(box, wxID_ANY, _("Load")); + load_button->Bind(wxEVT_BUTTON, [pad, index, this](wxCommandEvent&) { + LoadMinifig(pad, index); + }); + auto* clear_button = new wxButton(box, wxID_ANY, _("Clear")); + clear_button->Bind(wxEVT_BUTTON, [pad, index, this](wxCommandEvent&) { + ClearMinifig(pad, index); + }); + auto* create_button = new wxButton(box, wxID_ANY, _("Create")); + create_button->Bind(wxEVT_BUTTON, [pad, index, this](wxCommandEvent&) { + CreateMinifig(pad, index); + }); + button_row->Add(clear_button, 1, wxEXPAND | wxALL, 2); + button_row->Add(create_button, 1, wxEXPAND | wxALL, 2); + button_row->Add(load_button, 1, wxEXPAND | wxALL, 2); + + panel->Add(combo_row, 1, wxEXPAND | wxALL, 2); + panel->Add(button_row, 1, wxEXPAND | wxALL, 2); + + return panel; +} + void EmulatedUSBDeviceFrame::LoadSkylander(uint8 slot) { wxFileDialog openFileDialog(this, _("Open Skylander dump"), "", "", @@ -307,8 +390,8 @@ CreateSkylanderDialog::CreateSkylanderDialog(wxWindow* parent, uint8 slot) return; m_filePath = saveFileDialog.GetPath(); - - if(!nsyshid::g_skyportal.CreateSkylander(_utf8ToPath(m_filePath.utf8_string()), skyId, skyVar)) + + if (!nsyshid::g_skyportal.CreateSkylander(_utf8ToPath(m_filePath.utf8_string()), skyId, skyVar)) { wxMessageDialog errorMessage(this, "Failed to create file"); errorMessage.ShowModal(); @@ -447,6 +530,143 @@ wxString CreateInfinityFigureDialog::GetFilePath() const return m_filePath; } +CreateDimensionFigureDialog::CreateDimensionFigureDialog(wxWindow* parent) + : wxDialog(parent, wxID_ANY, _("Dimensions Figure Creator"), wxDefaultPosition, wxSize(500, 200)) +{ + auto* sizer = new wxBoxSizer(wxVERTICAL); + + auto* comboRow = new wxBoxSizer(wxHORIZONTAL); + + auto* comboBox = new wxComboBox(this, wxID_ANY); + comboBox->Append("---Select---", reinterpret_cast(0xFFFFFFFF)); + wxArrayString filterlist; + for (const auto& it : nsyshid::g_dimensionstoypad.GetListMinifigs()) + { + const uint32 figure = it.first; + comboBox->Append(it.second, reinterpret_cast(figure)); + filterlist.Add(it.second); + } + comboBox->SetSelection(0); + bool enabled = comboBox->AutoComplete(filterlist); + comboRow->Add(comboBox, 1, wxEXPAND | wxALL, 2); + + auto* figNumRow = new wxBoxSizer(wxHORIZONTAL); + + wxIntegerValidator validator; + + auto* labelFigNum = new wxStaticText(this, wxID_ANY, "Figure Number:"); + auto* editFigNum = new wxTextCtrl(this, wxID_ANY, _("0"), wxDefaultPosition, wxDefaultSize, 0, validator); + + figNumRow->Add(labelFigNum, 1, wxALL, 5); + figNumRow->Add(editFigNum, 1, wxALL, 5); + + auto* buttonRow = new wxBoxSizer(wxHORIZONTAL); + + auto* createButton = new wxButton(this, wxID_ANY, _("Create")); + createButton->Bind(wxEVT_BUTTON, [editFigNum, this](wxCommandEvent&) { + long longFigNum; + if (!editFigNum->GetValue().ToLong(&longFigNum) || longFigNum > 0xFFFF) + { + wxMessageDialog idError(this, "Error Converting Figure Number!", "Number Entered is Invalid"); + idError.ShowModal(); + this->EndModal(0); + } + uint16 figNum = longFigNum & 0xFFFF; + auto figure = nsyshid::g_dimensionstoypad.FindFigure(figNum); + wxString predefName = figure + ".bin"; + wxFileDialog + saveFileDialog(this, _("Create Dimensions Figure file"), "", predefName, + "BIN files (*.bin)|*.bin", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + + if (saveFileDialog.ShowModal() == wxID_CANCEL) + this->EndModal(0); + + m_filePath = saveFileDialog.GetPath(); + + nsyshid::g_dimensionstoypad.CreateFigure(_utf8ToPath(m_filePath.utf8_string()), figNum); + + this->EndModal(1); + }); + auto* cancelButton = new wxButton(this, wxID_ANY, _("Cancel")); + cancelButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { + this->EndModal(0); + }); + + comboBox->Bind(wxEVT_COMBOBOX, [comboBox, editFigNum, this](wxCommandEvent&) { + const uint64 fig_info = reinterpret_cast(comboBox->GetClientData(comboBox->GetSelection())); + if (fig_info != 0xFFFF) + { + const uint16 figNum = fig_info & 0xFFFF; + + editFigNum->SetValue(wxString::Format(wxT("%i"), figNum)); + } + }); + + buttonRow->Add(createButton, 1, wxALL, 5); + buttonRow->Add(cancelButton, 1, wxALL, 5); + + sizer->Add(comboRow, 1, wxEXPAND | wxALL, 2); + sizer->Add(figNumRow, 1, wxEXPAND | wxALL, 2); + sizer->Add(buttonRow, 1, wxEXPAND | wxALL, 2); + + this->SetSizer(sizer); + this->Centre(wxBOTH); +} + +wxString CreateDimensionFigureDialog::GetFilePath() const +{ + return m_filePath; +} + +void EmulatedUSBDeviceFrame::LoadMinifig(uint8 pad, uint8 index) +{ + wxFileDialog openFileDialog(this, _("Load Dimensions Figure"), "", "", + "Dimensions files (*.bin)|*.bin", + wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (openFileDialog.ShowModal() != wxID_OK || openFileDialog.GetPath().empty()) + return; + + LoadMinifigPath(openFileDialog.GetPath(), pad, index); +} +void EmulatedUSBDeviceFrame::LoadMinifigPath(wxString path_name, uint8 pad, uint8 index) +{ + std::unique_ptr dim_file(FileStream::openFile2(_utf8ToPath(path_name.utf8_string()), true)); + if (!dim_file) + { + wxMessageDialog errorMessage(this, "Failed to open minifig file"); + errorMessage.ShowModal(); + return; + } + + std::array file_data; + + if (dim_file->readData(file_data.data(), file_data.size()) != file_data.size()) + { + wxMessageDialog errorMessage(this, "Failed to read minifig file data"); + errorMessage.ShowModal(); + return; + } + + ClearMinifig(pad, index); + + uint32 id = nsyshid::g_dimensionstoypad.LoadFigure(file_data, std::move(dim_file), pad, index); + m_dimension_slots[index]->ChangeValue(nsyshid::g_dimensionstoypad.FindFigure(id)); +} +void EmulatedUSBDeviceFrame::ClearMinifig(uint8 pad, uint8 index) +{ + nsyshid::g_dimensionstoypad.RemoveFigure(pad, index); + m_dimension_slots[index]->ChangeValue("None"); +} +void EmulatedUSBDeviceFrame::CreateMinifig(uint8 pad, uint8 index) +{ + CreateDimensionFigureDialog create_dlg(this); + create_dlg.ShowModal(); + if (create_dlg.GetReturnCode() == 1) + { + LoadMinifigPath(create_dlg.GetFilePath(), pad, index); + } +} + void EmulatedUSBDeviceFrame::LoadFigure(uint8 slot) { wxFileDialog openFileDialog(this, _("Open Infinity Figure dump"), "", "", diff --git a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h index ae29a036e..26c1b53a3 100644 --- a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h +++ b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h @@ -25,18 +25,27 @@ class EmulatedUSBDeviceFrame : public wxFrame { private: wxCheckBox* m_emulatePortal; wxCheckBox* m_emulateBase; + wxCheckBox* m_emulate_toypad; std::array m_skylanderSlots; std::array m_infinitySlots; + std::array m_dimension_slots; std::array>, nsyshid::MAX_SKYLANDERS> m_skySlots; + std::array>, 7> dim_slots; wxPanel* AddSkylanderPage(wxNotebook* notebook); wxPanel* AddInfinityPage(wxNotebook* notebook); + wxPanel* AddDimensionsPage(wxNotebook* notebook); wxBoxSizer* AddSkylanderRow(uint8 row_number, wxStaticBox* box); wxBoxSizer* AddInfinityRow(wxString name, uint8 row_number, wxStaticBox* box); + wxBoxSizer* AddDimensionPanel(uint8 pad, uint8 index, wxStaticBox* box); void LoadSkylander(uint8 slot); void LoadSkylanderPath(uint8 slot, wxString path); void CreateSkylander(uint8 slot); void ClearSkylander(uint8 slot); + void LoadMinifigPath(wxString path_name, uint8 pad, uint8 index); + void LoadMinifig(uint8 pad, uint8 index); + void CreateMinifig(uint8 pad, uint8 index); + void ClearMinifig(uint8 pad, uint8 index); void LoadFigure(uint8 slot); void LoadFigurePath(uint8 slot, wxString path); void CreateFigure(uint8 slot); @@ -57,6 +66,15 @@ class CreateInfinityFigureDialog : public wxDialog { explicit CreateInfinityFigureDialog(wxWindow* parent, uint8 slot); wxString GetFilePath() const; + protected: + wxString m_filePath; +}; + +class CreateDimensionFigureDialog : public wxDialog { + public: + explicit CreateDimensionFigureDialog(wxWindow* parent); + wxString GetFilePath() const; + protected: wxString m_filePath; }; \ No newline at end of file From 922a858b5a765b86c7a08e57d555d4952f75b186 Mon Sep 17 00:00:00 2001 From: Joshua de Reeper Date: Mon, 7 Oct 2024 12:18:03 +0200 Subject: [PATCH 2/9] Use Span for Dimensions methods --- src/Cafe/OS/libs/nsyshid/Dimensions.cpp | 60 +++++++++++++------------ src/Cafe/OS/libs/nsyshid/Dimensions.h | 14 +++--- 2 files changed, 39 insertions(+), 35 deletions(-) diff --git a/src/Cafe/OS/libs/nsyshid/Dimensions.cpp b/src/Cafe/OS/libs/nsyshid/Dimensions.cpp index 5c4a97613..42e141830 100644 --- a/src/Cafe/OS/libs/nsyshid/Dimensions.cpp +++ b/src/Cafe/OS/libs/nsyshid/Dimensions.cpp @@ -416,7 +416,10 @@ namespace nsyshid Device::WriteResult DimensionsToypadDevice::Write(WriteMessage* message) { - g_dimensionstoypad.SendCommand(message->data, message->length); + if (message->length != 32) + return Device::WriteResult::Error; + + g_dimensionstoypad.SendCommand(std::span{message->data, 32}); message->bytesWritten = message->length; return Device::WriteResult::Success; } @@ -507,14 +510,13 @@ namespace nsyshid { if (!m_figureAddedRemovedResponses.empty()) { - memcpy(response.data(), m_figureAddedRemovedResponses.front().data(), - 0x20); + response = m_figureAddedRemovedResponses.front(); m_figureAddedRemovedResponses.pop(); responded = true; } else if (!m_queries.empty()) { - memcpy(response.data(), m_queries.front().data(), 0x20); + response = m_queries.front(); m_queries.pop(); responded = true; } @@ -527,7 +529,7 @@ namespace nsyshid return response; } - void DimensionsUSB::SendCommand(uint8* buf, sint32 originalLength) + void DimensionsUSB::SendCommand(std::span buf) { const uint8 command = buf[2]; const uint8 sequence = buf[3]; @@ -547,13 +549,13 @@ namespace nsyshid case 0xB1: // Seed { // Initialise a random number generator using the seed provided - g_dimensionstoypad.GenerateRandomNumber(&buf[4], sequence, q_result); + g_dimensionstoypad.GenerateRandomNumber(std::span{buf.data() + 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(&buf[4], sequence, q_result); + g_dimensionstoypad.GetChallengeResponse(std::span{buf.data() + 4, 8}, sequence, q_result); break; } case 0xC0: // Color @@ -579,13 +581,13 @@ namespace nsyshid case 0xD3: // Write { // Write 4 bytes to page buf[5] to the figure at index buf[4] - g_dimensionstoypad.WriteBlock(buf[4], buf[5], &buf[6], q_result, sequence); + g_dimensionstoypad.WriteBlock(buf[4], buf[5], std::span{buf.data() + 6, 16}, q_result, sequence); break; } case 0xD4: // Model { // Get the model id of the figure at index buf[4] - g_dimensionstoypad.GetModel(&buf[4], sequence, q_result); + g_dimensionstoypad.GetModel(std::span{buf.data() + 4, 8}, sequence, q_result); break; } case 0xD0: // Tag List @@ -673,7 +675,7 @@ namespace nsyshid std::array 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 encrypted = Encrypt(valueToEncrypt.data(), figureKey); + std::array encrypted = Encrypt(valueToEncrypt, figureKey); std::memcpy(&fileData[36 * 4], &encrypted[0], 4); std::memcpy(&fileData[37 * 4], &encrypted[4], 4); @@ -695,8 +697,8 @@ namespace nsyshid return true; } - void DimensionsUSB::GenerateRandomNumber(uint8* buf, uint8 sequence, - std::array& replyBuf) + void DimensionsUSB::GenerateRandomNumber(std::span buf, uint8 sequence, + std::array& replyBuf) { // Decrypt payload into an 8 byte array std::array value = Decrypt(buf, std::nullopt); @@ -708,7 +710,7 @@ namespace nsyshid InitializeRNG(seed); // Encrypt 8 bytes, first 4 bytes is the decrypted confirmation from payload, 2nd 4 bytes are blank std::array valueToEncrypt = {value[4], value[5], value[6], value[7], 0, 0, 0, 0}; - std::array encrypted = Encrypt(valueToEncrypt.data(), std::nullopt); + std::array encrypted = Encrypt(valueToEncrypt, std::nullopt); replyBuf[0] = 0x55; replyBuf[1] = 0x09; replyBuf[2] = sequence; @@ -717,8 +719,8 @@ namespace nsyshid replyBuf[11] = GenerateChecksum(replyBuf, 11); } - void DimensionsUSB::GetChallengeResponse(uint8* buf, uint8 sequence, - std::array& replyBuf) + void DimensionsUSB::GetChallengeResponse(std::span buf, uint8 sequence, + std::array& replyBuf) { // Decrypt payload into an 8 byte array std::array value = Decrypt(buf, std::nullopt); @@ -729,9 +731,9 @@ namespace nsyshid // 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 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 encrypted = Encrypt(valueToEncrypt.data(), std::nullopt); + uint8((nextRandom >> 16) & 0xFF), uint8((nextRandom >> 24) & 0xFF), + value[0], value[1], value[2], value[3]}; + std::array encrypted = Encrypt(valueToEncrypt, std::nullopt); replyBuf[0] = 0x55; replyBuf[1] = 0x09; replyBuf[2] = sequence; @@ -763,7 +765,7 @@ namespace nsyshid return m_randomD; } - std::array DimensionsUSB::Decrypt(const uint8* buf, std::optional> key) + std::array DimensionsUSB::Decrypt(std::span buf, std::optional> key) { // Value to decrypt is separated in to two little endian 32 bit unsigned integers uint32 dataOne = uint32(buf[3]) << 24 | uint32(buf[2]) << 16 | uint32(buf[1]) << 8 | uint32(buf[0]); @@ -808,7 +810,7 @@ namespace nsyshid uint8((dataTwo >> 16) & 0xFF), uint8((dataTwo >> 24) & 0xFF)}; return decrypted; } - std::array DimensionsUSB::Encrypt(const uint8* buf, std::optional> key) + std::array DimensionsUSB::Encrypt(std::span buf, std::optional> key) { // Value to encrypt is separated in to two little endian 32 bit unsigned integers uint32 dataOne = uint32(buf[3]) << 24 | uint32(buf[2]) << 16 | uint32(buf[1]) << 8 | uint32(buf[0]); @@ -922,7 +924,9 @@ namespace nsyshid { const std::array figureKey = GenerateFigureKey(buf); - const std::array decrypted = Decrypt(&buf[36 * 4], figureKey); + const std::span modelNumber = std::span{buf.data() + (36 * 4), 8}; + + const std::array decrypted = Decrypt(modelNumber, figureKey); const uint32 figNum = uint32(decrypted[3]) << 24 | uint32(decrypted[2]) << 16 | uint32(decrypted[1]) << 8 | uint32(decrypted[0]); // Characters have their model number encrypted in page 36 @@ -931,7 +935,7 @@ namespace nsyshid return figNum; } // Vehicles/Gadgets have their model number written as little endian in page 36 - return uint32(buf[(36 * 4) + 3]) << 24 | uint32(buf[(36 * 4) + 2]) << 16 | uint32(buf[(36 * 4) + 1]) << 8 | uint32(buf[36 * 4]); + return uint32(modelNumber[3]) << 24 | uint32(modelNumber[2]) << 16 | uint32(modelNumber[1]) << 8 | uint32(modelNumber[0]); } DimensionsUSB::DimensionsMini& @@ -965,8 +969,8 @@ namespace nsyshid replyBuf[20] = GenerateChecksum(replyBuf, 20); } - void DimensionsUSB::WriteBlock(uint8 index, uint8 page, const uint8* toWriteBuf, - std::array& replyBuf, uint8 sequence) + void DimensionsUSB::WriteBlock(uint8 index, uint8 page, std::span toWriteBuf, + std::array& replyBuf, uint8 sequence) { std::lock_guard lock(m_dimensionsMutex); @@ -988,15 +992,15 @@ namespace nsyshid { figure.id = uint32(toWriteBuf[3]) << 24 | uint32(toWriteBuf[2]) << 16 | uint32(toWriteBuf[1]) << 8 | uint32(toWriteBuf[0]); } - std::memcpy(figure.data.data() + (page * 4), toWriteBuf, 4); + std::memcpy(figure.data.data() + (page * 4), toWriteBuf.data(), 4); figure.Save(); } } replyBuf[4] = GenerateChecksum(replyBuf, 4); } - void DimensionsUSB::GetModel(uint8* buf, uint8 sequence, - std::array& replyBuf) + void DimensionsUSB::GetModel(std::span buf, uint8 sequence, + std::array& replyBuf) { // Decrypt payload to 8 byte array, byte 1 is the index, 4-7 are the confirmation std::array value = Decrypt(buf, std::nullopt); @@ -1012,7 +1016,7 @@ namespace nsyshid uint8((figure.id >> 16) & 0xFF), uint8((figure.id >> 24) & 0xFF), value[4], value[5], value[6], value[7]}; } - std::array encrypted = Encrypt(valueToEncrypt.data(), std::nullopt); + std::array encrypted = Encrypt(valueToEncrypt, std::nullopt); replyBuf[0] = 0x55; replyBuf[1] = 0x0a; replyBuf[2] = sequence; diff --git a/src/Cafe/OS/libs/nsyshid/Dimensions.h b/src/Cafe/OS/libs/nsyshid/Dimensions.h index 17e2fd9c0..a001a261d 100644 --- a/src/Cafe/OS/libs/nsyshid/Dimensions.h +++ b/src/Cafe/OS/libs/nsyshid/Dimensions.h @@ -50,19 +50,19 @@ namespace nsyshid void Save(); }; - void SendCommand(uint8* buf, sint32 originalLength); + void SendCommand(std::span buf); std::array GetStatus(); - void GenerateRandomNumber(uint8* buf, uint8 sequence, + void GenerateRandomNumber(std::span buf, uint8 sequence, std::array& replyBuf); void InitializeRNG(uint32 seed); - void GetChallengeResponse(uint8* buf, uint8 sequence, + void GetChallengeResponse(std::span buf, uint8 sequence, std::array& replyBuf); void QueryBlock(uint8 index, uint8 page, std::array& replyBuf, uint8 sequence); - void WriteBlock(uint8 index, uint8 page, const uint8* toWriteBuf, std::array& replyBuf, + void WriteBlock(uint8 index, uint8 page, std::span toWriteBuf, std::array& replyBuf, uint8 sequence); - void GetModel(uint8* buf, uint8 sequence, + void GetModel(std::span buf, uint8 sequence, std::array& replyBuf); bool RemoveFigure(uint8 pad, uint8 index); @@ -79,8 +79,8 @@ namespace nsyshid void RandomUID(uint8* uidBuffer); uint8 GenerateChecksum(const std::array& data, int numOfBytes) const; - std::array Decrypt(const uint8* buf, std::optional> key); - std::array Encrypt(const uint8* buf, std::optional> key); + std::array Decrypt(std::span buf, std::optional> key); + std::array Encrypt(std::span buf, std::optional> key); std::array GenerateFigureKey(const std::array& uid); std::array PWDGenerate(const std::array& uid); std::array DimensionsRandomize(const std::vector key, uint8 count); From 46796dad269a356b93977a08937f66290010d2db Mon Sep 17 00:00:00 2001 From: Joshua de Reeper Date: Mon, 7 Oct 2024 13:08:11 +0200 Subject: [PATCH 3/9] Use big endian utility and simple little endian cast --- src/Cafe/OS/libs/nsyshid/Dimensions.cpp | 74 ++++++++++++------------- src/Cafe/OS/libs/nsyshid/Dimensions.h | 4 +- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/Cafe/OS/libs/nsyshid/Dimensions.cpp b/src/Cafe/OS/libs/nsyshid/Dimensions.cpp index 42e141830..1150c1c71 100644 --- a/src/Cafe/OS/libs/nsyshid/Dimensions.cpp +++ b/src/Cafe/OS/libs/nsyshid/Dimensions.cpp @@ -549,13 +549,13 @@ namespace nsyshid case 0xB1: // Seed { // Initialise a random number generator using the seed provided - g_dimensionstoypad.GenerateRandomNumber(std::span{buf.data() + 4, 8}, sequence, q_result); + g_dimensionstoypad.GenerateRandomNumber(std::span{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{buf.data() + 4, 8}, sequence, q_result); + g_dimensionstoypad.GetChallengeResponse(std::span{buf.begin() + 4, 8}, sequence, q_result); break; } case 0xC0: // Color @@ -581,13 +581,13 @@ namespace nsyshid 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{buf.data() + 6, 16}, q_result, sequence); + g_dimensionstoypad.WriteBlock(buf[4], buf[5], std::span{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{buf.data() + 4, 8}, sequence, q_result); + g_dimensionstoypad.GetModel(std::span{buf.begin() + 4, 8}, sequence, q_result); break; } case 0xD0: // Tag List @@ -660,7 +660,7 @@ namespace nsyshid return false; } std::array fileData{}; - RandomUID(fileData.data()); + RandomUID(fileData); fileData[7] = id & 0xFF; std::array uid = {fileData[0], fileData[1], fileData[2], fileData[4], fileData[5], fileData[6], fileData[7]}; @@ -703,9 +703,9 @@ namespace nsyshid // Decrypt payload into an 8 byte array std::array value = Decrypt(buf, std::nullopt); // Seed is the first 4 bytes (little endian) of the decrypted payload - uint32 seed = uint32(value[3]) << 24 | uint32(value[2]) << 16 | uint32(value[1]) << 8 | uint32(value[0]); + uint32 seed = (uint32&)value[0]; // Confirmation is the second 4 bytes (big endian) of the decrypted payload - uint32 conf = uint32(value[4]) << 24 | uint32(value[5]) << 16 | uint32(value[6]) << 8 | uint32(value[7]); + uint32 conf = uint32be::from_bevalue((uint32&)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 @@ -725,7 +725,7 @@ namespace nsyshid // Decrypt payload into an 8 byte array std::array value = Decrypt(buf, std::nullopt); // Confirmation is the first 4 bytes of the decrypted payload - uint32 conf = uint32(value[0]) << 24 | uint32(value[1]) << 16 | uint32(value[2]) << 8 | uint32(value[3]); + uint32 conf = uint32be::from_bevalue((uint32&)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) @@ -768,8 +768,8 @@ namespace nsyshid std::array DimensionsUSB::Decrypt(std::span buf, std::optional> key) { // Value to decrypt is separated in to two little endian 32 bit unsigned integers - uint32 dataOne = uint32(buf[3]) << 24 | uint32(buf[2]) << 16 | uint32(buf[1]) << 8 | uint32(buf[0]); - uint32 dataTwo = uint32(buf[7]) << 24 | uint32(buf[6]) << 16 | uint32(buf[5]) << 8 | uint32(buf[4]); + uint32 dataOne = (uint32&)buf[0]; + uint32 dataTwo = (uint32&)buf[4]; // Use the key as 4 32 bit little endian unsigned integers uint32 keyOne; @@ -779,17 +779,17 @@ namespace nsyshid if (key) { - keyOne = uint32(key.value()[3]) << 24 | uint32(key.value()[2]) << 16 | uint32(key.value()[1]) << 8 | uint32(key.value()[0]); - keyTwo = uint32(key.value()[7]) << 24 | uint32(key.value()[6]) << 16 | uint32(key.value()[5]) << 8 | uint32(key.value()[4]); - keyThree = uint32(key.value()[11]) << 24 | uint32(key.value()[10]) << 16 | uint32(key.value()[9]) << 8 | uint32(key.value()[8]); - keyFour = uint32(key.value()[15]) << 24 | uint32(key.value()[14]) << 16 | uint32(key.value()[13]) << 8 | uint32(key.value()[12]); + 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[3]) << 24 | uint32(COMMAND_KEY[2]) << 16 | uint32(COMMAND_KEY[1]) << 8 | uint32(COMMAND_KEY[0]); - keyTwo = uint32(COMMAND_KEY[7]) << 24 | uint32(COMMAND_KEY[6]) << 16 | uint32(COMMAND_KEY[5]) << 8 | uint32(COMMAND_KEY[4]); - keyThree = uint32(COMMAND_KEY[11]) << 24 | uint32(COMMAND_KEY[10]) << 16 | uint32(COMMAND_KEY[9]) << 8 | uint32(COMMAND_KEY[8]); - keyFour = uint32(COMMAND_KEY[15]) << 24 | uint32(COMMAND_KEY[14]) << 16 | uint32(COMMAND_KEY[13]) << 8 | uint32(COMMAND_KEY[12]); + keyOne = (uint32&)COMMAND_KEY[0]; + keyTwo = (uint32&)COMMAND_KEY[4]; + keyThree = (uint32&)COMMAND_KEY[8]; + keyFour = (uint32&)COMMAND_KEY[12]; } uint32 sum = 0xC6EF3720; @@ -813,8 +813,8 @@ namespace nsyshid std::array DimensionsUSB::Encrypt(std::span buf, std::optional> key) { // Value to encrypt is separated in to two little endian 32 bit unsigned integers - uint32 dataOne = uint32(buf[3]) << 24 | uint32(buf[2]) << 16 | uint32(buf[1]) << 8 | uint32(buf[0]); - uint32 dataTwo = uint32(buf[7]) << 24 | uint32(buf[6]) << 16 | uint32(buf[5]) << 8 | uint32(buf[4]); + uint32 dataOne = (uint32&)buf[0]; + uint32 dataTwo = (uint32&)buf[4]; // Use the key as 4 32 bit little endian unsigned integers uint32 keyOne; @@ -824,17 +824,17 @@ namespace nsyshid if (key) { - keyOne = uint32(key.value()[3]) << 24 | uint32(key.value()[2]) << 16 | uint32(key.value()[1]) << 8 | uint32(key.value()[0]); - keyTwo = uint32(key.value()[7]) << 24 | uint32(key.value()[6]) << 16 | uint32(key.value()[5]) << 8 | uint32(key.value()[4]); - keyThree = uint32(key.value()[11]) << 24 | uint32(key.value()[10]) << 16 | uint32(key.value()[9]) << 8 | uint32(key.value()[8]); - keyFour = uint32(key.value()[15]) << 24 | uint32(key.value()[14]) << 16 | uint32(key.value()[13]) << 8 | uint32(key.value()[12]); + 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[3]) << 24 | uint32(COMMAND_KEY[2]) << 16 | uint32(COMMAND_KEY[1]) << 8 | uint32(COMMAND_KEY[0]); - keyTwo = uint32(COMMAND_KEY[7]) << 24 | uint32(COMMAND_KEY[6]) << 16 | uint32(COMMAND_KEY[5]) << 8 | uint32(COMMAND_KEY[4]); - keyThree = uint32(COMMAND_KEY[11]) << 24 | uint32(COMMAND_KEY[10]) << 16 | uint32(COMMAND_KEY[9]) << 8 | uint32(COMMAND_KEY[8]); - keyFour = uint32(COMMAND_KEY[15]) << 24 | uint32(COMMAND_KEY[14]) << 16 | uint32(COMMAND_KEY[13]) << 8 | uint32(COMMAND_KEY[12]); + keyOne = (uint32&)COMMAND_KEY[0]; + keyTwo = (uint32&)COMMAND_KEY[4]; + keyThree = (uint32&)COMMAND_KEY[8]; + keyFour = (uint32&)COMMAND_KEY[12]; } uint32 sum = 0; @@ -891,7 +891,7 @@ namespace nsyshid std::array randomized = DimensionsRandomize(toScramble, count); - return uint32(randomized[0]) << 24 | uint32(randomized[1]) << 16 | uint32(randomized[2]) << 8 | uint32(randomized[3]); + return uint32be::from_bevalue((uint32&)randomized[0]); } std::array DimensionsUSB::PWDGenerate(const std::array& buf) @@ -914,7 +914,7 @@ namespace nsyshid { const uint32 v4 = std::rotr(scrambled, 25); const uint32 v5 = std::rotr(scrambled, 10); - const uint32 b = uint32(key[(i * 4) + 3]) << 24 | uint32(key[(i * 4) + 2]) << 16 | uint32(key[(i * 4) + 1]) << 8 | uint32(key[(i * 4)]); + 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)}; @@ -924,18 +924,18 @@ namespace nsyshid { const std::array figureKey = GenerateFigureKey(buf); - const std::span modelNumber = std::span{buf.data() + (36 * 4), 8}; + const std::span modelNumber = std::span{buf.begin() + (36 * 4), 8}; const std::array decrypted = Decrypt(modelNumber, figureKey); - const uint32 figNum = uint32(decrypted[3]) << 24 | uint32(decrypted[2]) << 16 | uint32(decrypted[1]) << 8 | uint32(decrypted[0]); + 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[3]) << 24 | uint32(modelNumber[2]) << 16 | uint32(modelNumber[1]) << 8 | uint32(modelNumber[0]); + return (uint32&)modelNumber[0]; } DimensionsUSB::DimensionsMini& @@ -969,7 +969,7 @@ namespace nsyshid replyBuf[20] = GenerateChecksum(replyBuf, 20); } - void DimensionsUSB::WriteBlock(uint8 index, uint8 page, std::span toWriteBuf, + void DimensionsUSB::WriteBlock(uint8 index, uint8 page, std::span toWriteBuf, std::array& replyBuf, uint8 sequence) { std::lock_guard lock(m_dimensionsMutex); @@ -990,7 +990,7 @@ namespace nsyshid // Id is written to page 36 if (page == 36) { - figure.id = uint32(toWriteBuf[3]) << 24 | uint32(toWriteBuf[2]) << 16 | uint32(toWriteBuf[1]) << 8 | uint32(toWriteBuf[0]); + figure.id = (uint32&)toWriteBuf[0]; } std::memcpy(figure.data.data() + (page * 4), toWriteBuf.data(), 4); figure.Save(); @@ -1005,7 +1005,7 @@ namespace nsyshid // Decrypt payload to 8 byte array, byte 1 is the index, 4-7 are the confirmation std::array value = Decrypt(buf, std::nullopt); uint8 index = value[0]; - uint32 conf = uint32(value[4]) << 24 | uint32(value[5]) << 16 | uint32(value[6]) << 8 | uint32(value[7]); + uint32 conf = uint32be::from_bevalue((uint32&)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 valueToEncrypt = {}; @@ -1025,7 +1025,7 @@ namespace nsyshid replyBuf[12] = GenerateChecksum(replyBuf, 12); } - void DimensionsUSB::RandomUID(uint8* uid_buffer) + void DimensionsUSB::RandomUID(std::array& uid_buffer) { uid_buffer[0] = 0x04; uid_buffer[6] = 0x80; diff --git a/src/Cafe/OS/libs/nsyshid/Dimensions.h b/src/Cafe/OS/libs/nsyshid/Dimensions.h index a001a261d..22d89586e 100644 --- a/src/Cafe/OS/libs/nsyshid/Dimensions.h +++ b/src/Cafe/OS/libs/nsyshid/Dimensions.h @@ -60,7 +60,7 @@ namespace nsyshid std::array& replyBuf); void QueryBlock(uint8 index, uint8 page, std::array& replyBuf, uint8 sequence); - void WriteBlock(uint8 index, uint8 page, std::span toWriteBuf, std::array& replyBuf, + void WriteBlock(uint8 index, uint8 page, std::span toWriteBuf, std::array& replyBuf, uint8 sequence); void GetModel(std::span buf, uint8 sequence, std::array& replyBuf); @@ -76,7 +76,7 @@ namespace nsyshid std::array figures; private: - void RandomUID(uint8* uidBuffer); + void RandomUID(std::array& uidBuffer); uint8 GenerateChecksum(const std::array& data, int numOfBytes) const; std::array Decrypt(std::span buf, std::optional> key); From 08f789a313328b30a250d1aeddb24d7bfb922cd7 Mon Sep 17 00:00:00 2001 From: Joshua de Reeper Date: Mon, 7 Oct 2024 13:22:35 +0200 Subject: [PATCH 4/9] Simple assignment for figure data --- src/Cafe/OS/libs/nsyshid/Dimensions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cafe/OS/libs/nsyshid/Dimensions.cpp b/src/Cafe/OS/libs/nsyshid/Dimensions.cpp index 1150c1c71..788a80fb3 100644 --- a/src/Cafe/OS/libs/nsyshid/Dimensions.cpp +++ b/src/Cafe/OS/libs/nsyshid/Dimensions.cpp @@ -619,7 +619,7 @@ namespace nsyshid figure.id = id; figure.pad = pad; figure.index = index + 1; - memcpy(figure.data.data(), buf.data(), buf.size()); + 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 figureChangeResponse = {0x56, 0x0b, figure.pad, 0x00, figure.index, 0x00, buf[0], buf[1], buf[2], buf[4], buf[5], buf[6], buf[7]}; From 0829d33a483db30679dd7797e578f09f07ca0c48 Mon Sep 17 00:00:00 2001 From: Joshua de Reeper Date: Mon, 7 Oct 2024 17:52:04 +0200 Subject: [PATCH 5/9] Move Minifgure Dialog --- src/Cafe/OS/libs/nsyshid/Dimensions.cpp | 74 ++++- src/Cafe/OS/libs/nsyshid/Dimensions.h | 23 +- .../EmulatedUSBDeviceFrame.cpp | 296 +++++++++++------- .../EmulatedUSBDeviceFrame.h | 44 ++- 4 files changed, 297 insertions(+), 140 deletions(-) diff --git a/src/Cafe/OS/libs/nsyshid/Dimensions.cpp b/src/Cafe/OS/libs/nsyshid/Dimensions.cpp index 788a80fb3..25ab56ec4 100644 --- a/src/Cafe/OS/libs/nsyshid/Dimensions.cpp +++ b/src/Cafe/OS/libs/nsyshid/Dimensions.cpp @@ -609,9 +609,11 @@ namespace nsyshid m_queries.push(q_result); } - uint32 DimensionsUSB::LoadFigure(const std::array& buf, std::unique_ptr file, uint8 pad, uint8 index) + uint32 DimensionsUSB::LoadFigure(const std::array& buf, std::unique_ptr file, uint8 pad, uint8 index, bool lock) { - std::lock_guard lock(m_dimensionsMutex); + if (lock) + std::shared_lock lock(m_dimensionsMutex); + const uint32 id = GetFigureId(buf); DimensionsMini& figure = GetFigureByIndex(index); @@ -625,30 +627,44 @@ namespace nsyshid std::array 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); + + if (lock) + std::shared_lock unlock(m_dimensionsMutex); + return id; } - bool DimensionsUSB::RemoveFigure(uint8 pad, uint8 index) + bool DimensionsUSB::RemoveFigure(uint8 pad, uint8 index, bool save, bool lock) { - std::lock_guard lock(m_dimensionsMutex); DimensionsMini& figure = GetFigureByIndex(index); if (figure.index == 255) { return false; } + + if (lock) + std::shared_lock lock(m_dimensionsMutex); + // 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 std::array 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]}; - figure.Save(); - figure.dimFile.reset(); + if (save) + { + figure.Save(); + figure.dimFile.reset(); + } figure.index = 255; figure.pad = 255; figure.id = 0; memcpy(&figureChangeResponse[6], figure.data.data(), 7); figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13); m_figureAddedRemovedResponses.push(figureChangeResponse); + + if (lock) + std::shared_lock unlock(m_dimensionsMutex); + return true; } @@ -697,8 +713,40 @@ namespace nsyshid return true; } + bool DimensionsUSB::MoveFigure(uint8 pad, uint8 index, uint8 oldPad, uint8 oldIndex) + { + std::lock_guard lock(m_dimensionsMutex); + + if (oldIndex == index) + { + // Don't bother removing and loading again, just send response to the game + const DimensionsMini& figure = GetFigureByIndex(oldIndex); + std::array figureRemoveResponse = {0x56, 0x0b, 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]}; + figureRemoveResponse[13] = GenerateChecksum(figureRemoveResponse, 13); + std::array figureAddResponse = {0x56, 0x0b, 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]}; + figureAddResponse[13] = GenerateChecksum(figureAddResponse, 13); + m_figureAddedRemovedResponses.push(std::move(figureRemoveResponse)); + m_figureAddedRemovedResponses.push(std::move(figureAddResponse)); + 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, false); + + DimensionsMini& figure = GetFigureByIndex(oldIndex); + const std::array data = figure.data; + std::unique_ptr inFile = std::move(figure.dimFile); + + RemoveFigure(oldPad, oldIndex, false, false); + + LoadFigure(data, std::move(inFile), pad, index, false); + + return true; + } + void DimensionsUSB::GenerateRandomNumber(std::span buf, uint8 sequence, - std::array& replyBuf) + std::array& replyBuf) { // Decrypt payload into an 8 byte array std::array value = Decrypt(buf, std::nullopt); @@ -720,7 +768,7 @@ namespace nsyshid } void DimensionsUSB::GetChallengeResponse(std::span buf, uint8 sequence, - std::array& replyBuf) + std::array& replyBuf) { // Decrypt payload into an 8 byte array std::array value = Decrypt(buf, std::nullopt); @@ -731,8 +779,8 @@ namespace nsyshid // 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 valueToEncrypt = {uint8(nextRandom & 0xFF), uint8((nextRandom >> 8) & 0xFF), - uint8((nextRandom >> 16) & 0xFF), uint8((nextRandom >> 24) & 0xFF), - value[0], value[1], value[2], value[3]}; + uint8((nextRandom >> 16) & 0xFF), uint8((nextRandom >> 24) & 0xFF), + value[0], value[1], value[2], value[3]}; std::array encrypted = Encrypt(valueToEncrypt, std::nullopt); replyBuf[0] = 0x55; replyBuf[1] = 0x09; @@ -941,7 +989,7 @@ namespace nsyshid DimensionsUSB::DimensionsMini& DimensionsUSB::GetFigureByIndex(uint8 index) { - return figures[index]; + return m_figures[index]; } void DimensionsUSB::QueryBlock(uint8 index, uint8 page, @@ -970,7 +1018,7 @@ namespace nsyshid } void DimensionsUSB::WriteBlock(uint8 index, uint8 page, std::span toWriteBuf, - std::array& replyBuf, uint8 sequence) + std::array& replyBuf, uint8 sequence) { std::lock_guard lock(m_dimensionsMutex); @@ -1000,7 +1048,7 @@ namespace nsyshid } void DimensionsUSB::GetModel(std::span buf, uint8 sequence, - std::array& replyBuf) + std::array& replyBuf) { // Decrypt payload to 8 byte array, byte 1 is the index, 4-7 are the confirmation std::array value = Decrypt(buf, std::nullopt); diff --git a/src/Cafe/OS/libs/nsyshid/Dimensions.h b/src/Cafe/OS/libs/nsyshid/Dimensions.h index 22d89586e..a6c47924b 100644 --- a/src/Cafe/OS/libs/nsyshid/Dimensions.h +++ b/src/Cafe/OS/libs/nsyshid/Dimensions.h @@ -1,4 +1,4 @@ -#include +#include #include "nsyshid.h" #include "Backend.h" @@ -54,31 +54,32 @@ namespace nsyshid std::array GetStatus(); void GenerateRandomNumber(std::span buf, uint8 sequence, - std::array& replyBuf); + std::array& replyBuf); void InitializeRNG(uint32 seed); void GetChallengeResponse(std::span buf, uint8 sequence, - std::array& replyBuf); + std::array& replyBuf); void QueryBlock(uint8 index, uint8 page, std::array& replyBuf, - uint8 sequence); + uint8 sequence); void WriteBlock(uint8 index, uint8 page, std::span toWriteBuf, std::array& replyBuf, - uint8 sequence); + uint8 sequence); void GetModel(std::span buf, uint8 sequence, - std::array& replyBuf); + std::array& replyBuf); - bool RemoveFigure(uint8 pad, uint8 index); - uint32 LoadFigure(const std::array& buf, std::unique_ptr file, uint8 pad, uint8 index); + bool RemoveFigure(uint8 pad, uint8 index, bool save, bool lock); + uint32 LoadFigure(const std::array& buf, std::unique_ptr file, uint8 pad, uint8 index, bool lock); bool CreateFigure(fs::path pathName, uint32 id); + bool MoveFigure(uint8 pad, uint8 index, uint8 oldPad, uint8 oldIndex); static std::map GetListMinifigs(); std::string FindFigure(uint32 figNum); protected: - std::mutex m_dimensionsMutex; - std::array figures; + std::shared_mutex m_dimensionsMutex; + std::array m_figures{}; private: void RandomUID(std::array& uidBuffer); uint8 GenerateChecksum(const std::array& data, - int numOfBytes) const; + int numOfBytes) const; std::array Decrypt(std::span buf, std::optional> key); std::array Encrypt(std::span buf, std::optional> key); std::array GenerateFigureKey(const std::array& uid); diff --git a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp index 2fec5f5f4..d8ec111e3 100644 --- a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp +++ b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp @@ -168,8 +168,7 @@ wxPanel* EmulatedUSBDeviceFrame::AddDimensionsPage(wxNotebook* notebook) return panel; } -wxBoxSizer* EmulatedUSBDeviceFrame::AddSkylanderRow(uint8 rowNumber, - wxStaticBox* box) +wxBoxSizer* EmulatedUSBDeviceFrame::AddSkylanderRow(uint8 rowNumber, wxStaticBox* box) { auto* row = new wxBoxSizer(wxHORIZONTAL); @@ -237,10 +236,13 @@ wxBoxSizer* EmulatedUSBDeviceFrame::AddDimensionPanel(uint8 pad, uint8 index, wx auto* panel = new wxBoxSizer(wxVERTICAL); auto* combo_row = new wxBoxSizer(wxHORIZONTAL); - m_dimension_slots[index] = new wxTextCtrl(box, wxID_ANY, _("None"), wxDefaultPosition, wxDefaultSize, - wxTE_READONLY); - combo_row->Add(m_dimension_slots[index], 1, wxEXPAND | wxALL, 2); + m_dimensionSlots[index] = new wxTextCtrl(box, wxID_ANY, _("None"), wxDefaultPosition, wxDefaultSize, + wxTE_READONLY); + combo_row->Add(m_dimensionSlots[index], 1, wxEXPAND | wxALL, 2); auto* move_button = new wxButton(box, wxID_ANY, _("Move")); + move_button->Bind(wxEVT_BUTTON, [pad, index, this](wxCommandEvent&) { + MoveMinifig(pad, index); + }); combo_row->Add(move_button, 1, wxEXPAND | wxALL, 2); @@ -434,6 +436,80 @@ wxString CreateSkylanderDialog::GetFilePath() const return m_filePath; } +void EmulatedUSBDeviceFrame::UpdateSkylanderEdits() +{ + for (auto i = 0; i < nsyshid::MAX_SKYLANDERS; i++) + { + std::string displayString; + if (auto sd = m_skySlots[i]) + { + auto [portalSlot, skyId, skyVar] = sd.value(); + displayString = nsyshid::g_skyportal.FindSkylander(skyId, skyVar); + } + else + { + displayString = "None"; + } + + m_skylanderSlots[i]->ChangeValue(displayString); + } +} + +void EmulatedUSBDeviceFrame::LoadFigure(uint8 slot) +{ + wxFileDialog openFileDialog(this, _("Open Infinity Figure dump"), "", "", + "BIN files (*.bin)|*.bin", + wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (openFileDialog.ShowModal() != wxID_OK || openFileDialog.GetPath().empty()) + { + wxMessageDialog errorMessage(this, "File Okay Error"); + errorMessage.ShowModal(); + return; + } + + LoadFigurePath(slot, openFileDialog.GetPath()); +} + +void EmulatedUSBDeviceFrame::LoadFigurePath(uint8 slot, wxString path) +{ + std::unique_ptr infFile(FileStream::openFile2(_utf8ToPath(path.utf8_string()), true)); + if (!infFile) + { + wxMessageDialog errorMessage(this, "File Open Error"); + errorMessage.ShowModal(); + return; + } + + std::array fileData; + if (infFile->readData(fileData.data(), fileData.size()) != fileData.size()) + { + wxMessageDialog open_error(this, "Failed to read file! File was too small"); + open_error.ShowModal(); + return; + } + ClearFigure(slot); + + uint32 number = nsyshid::g_infinitybase.LoadFigure(fileData, std::move(infFile), slot); + m_infinitySlots[slot]->ChangeValue(nsyshid::g_infinitybase.FindFigure(number).second); +} + +void EmulatedUSBDeviceFrame::CreateFigure(uint8 slot) +{ + cemuLog_log(LogType::Force, "Create Figure: {}", slot); + CreateInfinityFigureDialog create_dlg(this, slot); + create_dlg.ShowModal(); + if (create_dlg.GetReturnCode() == 1) + { + LoadFigurePath(slot, create_dlg.GetFilePath()); + } +} + +void EmulatedUSBDeviceFrame::ClearFigure(uint8 slot) +{ + m_infinitySlots[slot]->ChangeValue("None"); + nsyshid::g_infinitybase.RemoveFigure(slot); +} + CreateInfinityFigureDialog::CreateInfinityFigureDialog(wxWindow* parent, uint8 slot) : wxDialog(parent, wxID_ANY, _("Infinity Figure Creator"), wxDefaultPosition, wxSize(500, 150)) { @@ -530,6 +606,77 @@ wxString CreateInfinityFigureDialog::GetFilePath() const return m_filePath; } +void EmulatedUSBDeviceFrame::LoadMinifig(uint8 pad, uint8 index) +{ + wxFileDialog openFileDialog(this, _("Load Dimensions Figure"), "", "", + "Dimensions files (*.bin)|*.bin", + wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (openFileDialog.ShowModal() != wxID_OK || openFileDialog.GetPath().empty()) + return; + + LoadMinifigPath(openFileDialog.GetPath(), pad, index); +} + +void EmulatedUSBDeviceFrame::LoadMinifigPath(wxString path_name, uint8 pad, uint8 index) +{ + std::unique_ptr dim_file(FileStream::openFile2(_utf8ToPath(path_name.utf8_string()), true)); + if (!dim_file) + { + wxMessageDialog errorMessage(this, "Failed to open minifig file"); + errorMessage.ShowModal(); + return; + } + + std::array file_data; + + if (dim_file->readData(file_data.data(), file_data.size()) != file_data.size()) + { + wxMessageDialog errorMessage(this, "Failed to read minifig file data"); + errorMessage.ShowModal(); + return; + } + + ClearMinifig(pad, index); + + uint32 id = nsyshid::g_dimensionstoypad.LoadFigure(file_data, std::move(dim_file), pad, index, true); + m_dimensionSlots[index]->ChangeValue(nsyshid::g_dimensionstoypad.FindFigure(id)); + m_dimSlots[index] = id; +} + +void EmulatedUSBDeviceFrame::ClearMinifig(uint8 pad, uint8 index) +{ + nsyshid::g_dimensionstoypad.RemoveFigure(pad, index, true, true); + m_dimensionSlots[index]->ChangeValue("None"); + m_dimSlots[index] = std::nullopt; +} + +void EmulatedUSBDeviceFrame::CreateMinifig(uint8 pad, uint8 index) +{ + CreateDimensionFigureDialog create_dlg(this); + create_dlg.ShowModal(); + if (create_dlg.GetReturnCode() == 1) + { + LoadMinifigPath(create_dlg.GetFilePath(), pad, index); + } +} + +void EmulatedUSBDeviceFrame::MoveMinifig(uint8 pad, uint8 index) +{ + MoveDimensionFigureDialog move_dlg(this, index); + move_dlg.ShowModal(); + if (move_dlg.GetReturnCode() == 1) + { + nsyshid::g_dimensionstoypad.MoveFigure(move_dlg.GetNewPad(), move_dlg.GetNewIndex(), pad, index); + if (index != move_dlg.GetNewIndex()) + { + m_dimSlots[move_dlg.GetNewIndex()] = m_dimSlots[index]; + m_dimensionSlots[move_dlg.GetNewIndex()]->ChangeValue(m_dimensionSlots[index]->GetValue()); + m_dimSlots[index] = std::nullopt; + m_dimensionSlots[index]->ChangeValue("None"); + } + } +} + CreateDimensionFigureDialog::CreateDimensionFigureDialog(wxWindow* parent) : wxDialog(parent, wxID_ANY, _("Dimensions Figure Creator"), wxDefaultPosition, wxSize(500, 200)) { @@ -618,125 +765,64 @@ wxString CreateDimensionFigureDialog::GetFilePath() const return m_filePath; } -void EmulatedUSBDeviceFrame::LoadMinifig(uint8 pad, uint8 index) +MoveDimensionFigureDialog::MoveDimensionFigureDialog(EmulatedUSBDeviceFrame* parent, uint8 currentIndex) + : wxDialog(parent, wxID_ANY, _("Dimensions Figure Mover"), wxDefaultPosition, wxSize(700, 300)) { - wxFileDialog openFileDialog(this, _("Load Dimensions Figure"), "", "", - "Dimensions files (*.bin)|*.bin", - wxFD_OPEN | wxFD_FILE_MUST_EXIST); - if (openFileDialog.ShowModal() != wxID_OK || openFileDialog.GetPath().empty()) - return; - - LoadMinifigPath(openFileDialog.GetPath(), pad, index); -} -void EmulatedUSBDeviceFrame::LoadMinifigPath(wxString path_name, uint8 pad, uint8 index) -{ - std::unique_ptr dim_file(FileStream::openFile2(_utf8ToPath(path_name.utf8_string()), true)); - if (!dim_file) - { - wxMessageDialog errorMessage(this, "Failed to open minifig file"); - errorMessage.ShowModal(); - return; - } + auto* sizer = new wxGridSizer(2, 5, 10, 10); - std::array file_data; + std::array, 7> ids = parent->GetCurrentMinifigs(); - if (dim_file->readData(file_data.data(), file_data.size()) != file_data.size()) - { - wxMessageDialog errorMessage(this, "Failed to read minifig file data"); - errorMessage.ShowModal(); - return; - } + sizer->Add(AddMinifigSlot(2, 0, currentIndex, ids[0]), 1, wxALL, 5); + sizer->Add(new wxStaticText(this, wxID_ANY, ""), 1, wxALL, 5); + sizer->Add(AddMinifigSlot(1, 1, currentIndex, ids[1]), 1, wxALL, 5); + sizer->Add(new wxStaticText(this, wxID_ANY, ""), 1, wxALL, 5); + sizer->Add(AddMinifigSlot(3, 2, currentIndex, ids[2]), 1, wxALL, 5); - ClearMinifig(pad, index); + sizer->Add(AddMinifigSlot(1, 3, currentIndex, ids[3]), 1, wxALL, 5); + sizer->Add(AddMinifigSlot(1, 4, currentIndex, ids[4]), 1, wxALL, 5); + sizer->Add(new wxStaticText(this, wxID_ANY, ""), 1, wxALL, 5); + sizer->Add(AddMinifigSlot(3, 5, currentIndex, ids[5]), 1, wxALL, 5); + sizer->Add(AddMinifigSlot(3, 6, currentIndex, ids[6]), 1, wxALL, 5); - uint32 id = nsyshid::g_dimensionstoypad.LoadFigure(file_data, std::move(dim_file), pad, index); - m_dimension_slots[index]->ChangeValue(nsyshid::g_dimensionstoypad.FindFigure(id)); -} -void EmulatedUSBDeviceFrame::ClearMinifig(uint8 pad, uint8 index) -{ - nsyshid::g_dimensionstoypad.RemoveFigure(pad, index); - m_dimension_slots[index]->ChangeValue("None"); -} -void EmulatedUSBDeviceFrame::CreateMinifig(uint8 pad, uint8 index) -{ - CreateDimensionFigureDialog create_dlg(this); - create_dlg.ShowModal(); - if (create_dlg.GetReturnCode() == 1) - { - LoadMinifigPath(create_dlg.GetFilePath(), pad, index); - } + this->SetSizer(sizer); + this->Centre(wxBOTH); } -void EmulatedUSBDeviceFrame::LoadFigure(uint8 slot) +wxBoxSizer* MoveDimensionFigureDialog::AddMinifigSlot(uint8 pad, uint8 index, uint8 currentIndex, std::optional currentId) { - wxFileDialog openFileDialog(this, _("Open Infinity Figure dump"), "", "", - "BIN files (*.bin)|*.bin", - wxFD_OPEN | wxFD_FILE_MUST_EXIST); - if (openFileDialog.ShowModal() != wxID_OK || openFileDialog.GetPath().empty()) - { - wxMessageDialog errorMessage(this, "File Okay Error"); - errorMessage.ShowModal(); - return; - } + auto* panel = new wxBoxSizer(wxVERTICAL); - LoadFigurePath(slot, openFileDialog.GetPath()); -} + auto* label = new wxStaticText(this, wxID_ANY, "None"); + if (currentId) + label->SetLabel(nsyshid::g_dimensionstoypad.FindFigure(currentId.value())); -void EmulatedUSBDeviceFrame::LoadFigurePath(uint8 slot, wxString path) -{ - std::unique_ptr infFile(FileStream::openFile2(_utf8ToPath(path.utf8_string()), true)); - if (!infFile) - { - wxMessageDialog errorMessage(this, "File Open Error"); - errorMessage.ShowModal(); - return; - } + auto* moveButton = new wxButton(this, wxID_ANY, _("Move Here")); + if (index == currentIndex) + moveButton->SetLabelText("Pick up and Place"); - std::array fileData; - if (infFile->readData(fileData.data(), fileData.size()) != fileData.size()) - { - wxMessageDialog open_error(this, "Failed to read file! File was too small"); - open_error.ShowModal(); - return; - } - ClearFigure(slot); + moveButton->Bind(wxEVT_BUTTON, [pad, index, this](wxCommandEvent&) { + m_newPad = pad; + m_newIndex = index; + this->EndModal(1); + }); - uint32 number = nsyshid::g_infinitybase.LoadFigure(fileData, std::move(infFile), slot); - m_infinitySlots[slot]->ChangeValue(nsyshid::g_infinitybase.FindFigure(number).second); + panel->Add(label, 1, wxALL, 5); + panel->Add(moveButton, 1, wxALL, 5); + + return panel; } -void EmulatedUSBDeviceFrame::CreateFigure(uint8 slot) +uint8 MoveDimensionFigureDialog::GetNewPad() const { - cemuLog_log(LogType::Force, "Create Figure: {}", slot); - CreateInfinityFigureDialog create_dlg(this, slot); - create_dlg.ShowModal(); - if (create_dlg.GetReturnCode() == 1) - { - LoadFigurePath(slot, create_dlg.GetFilePath()); - } + return m_newPad; } -void EmulatedUSBDeviceFrame::ClearFigure(uint8 slot) +uint8 MoveDimensionFigureDialog::GetNewIndex() const { - m_infinitySlots[slot]->ChangeValue("None"); - nsyshid::g_infinitybase.RemoveFigure(slot); + return m_newIndex; } -void EmulatedUSBDeviceFrame::UpdateSkylanderEdits() +std::array, 7> EmulatedUSBDeviceFrame::GetCurrentMinifigs() { - for (auto i = 0; i < nsyshid::MAX_SKYLANDERS; i++) - { - std::string displayString; - if (auto sd = m_skySlots[i]) - { - auto [portalSlot, skyId, skyVar] = sd.value(); - displayString = nsyshid::g_skyportal.FindSkylander(skyId, skyVar); - } - else - { - displayString = "None"; - } - - m_skylanderSlots[i]->ChangeValue(displayString); - } + return m_dimSlots; } \ No newline at end of file diff --git a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h index 26c1b53a3..0eeb5b2c4 100644 --- a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h +++ b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h @@ -17,10 +17,12 @@ class wxStaticBox; class wxString; class wxTextCtrl; -class EmulatedUSBDeviceFrame : public wxFrame { +class EmulatedUSBDeviceFrame : public wxFrame +{ public: EmulatedUSBDeviceFrame(wxWindow* parent); ~EmulatedUSBDeviceFrame(); + std::array, 7> GetCurrentMinifigs(); private: wxCheckBox* m_emulatePortal; @@ -28,9 +30,9 @@ class EmulatedUSBDeviceFrame : public wxFrame { wxCheckBox* m_emulate_toypad; std::array m_skylanderSlots; std::array m_infinitySlots; - std::array m_dimension_slots; + std::array m_dimensionSlots; std::array>, nsyshid::MAX_SKYLANDERS> m_skySlots; - std::array>, 7> dim_slots; + std::array, 7> m_dimSlots; wxPanel* AddSkylanderPage(wxNotebook* notebook); wxPanel* AddInfinityPage(wxNotebook* notebook); @@ -42,17 +44,20 @@ class EmulatedUSBDeviceFrame : public wxFrame { void LoadSkylanderPath(uint8 slot, wxString path); void CreateSkylander(uint8 slot); void ClearSkylander(uint8 slot); - void LoadMinifigPath(wxString path_name, uint8 pad, uint8 index); - void LoadMinifig(uint8 pad, uint8 index); - void CreateMinifig(uint8 pad, uint8 index); - void ClearMinifig(uint8 pad, uint8 index); + void UpdateSkylanderEdits(); void LoadFigure(uint8 slot); void LoadFigurePath(uint8 slot, wxString path); void CreateFigure(uint8 slot); void ClearFigure(uint8 slot); - void UpdateSkylanderEdits(); + void LoadMinifig(uint8 pad, uint8 index); + void LoadMinifigPath(wxString path_name, uint8 pad, uint8 index); + void CreateMinifig(uint8 pad, uint8 index); + void ClearMinifig(uint8 pad, uint8 index); + void MoveMinifig(uint8 pad, uint8 index); }; -class CreateSkylanderDialog : public wxDialog { + +class CreateSkylanderDialog : public wxDialog +{ public: explicit CreateSkylanderDialog(wxWindow* parent, uint8 slot); wxString GetFilePath() const; @@ -61,7 +66,8 @@ class CreateSkylanderDialog : public wxDialog { wxString m_filePath; }; -class CreateInfinityFigureDialog : public wxDialog { +class CreateInfinityFigureDialog : public wxDialog +{ public: explicit CreateInfinityFigureDialog(wxWindow* parent, uint8 slot); wxString GetFilePath() const; @@ -70,11 +76,27 @@ class CreateInfinityFigureDialog : public wxDialog { wxString m_filePath; }; -class CreateDimensionFigureDialog : public wxDialog { +class CreateDimensionFigureDialog : public wxDialog +{ public: explicit CreateDimensionFigureDialog(wxWindow* parent); wxString GetFilePath() const; protected: wxString m_filePath; +}; + +class MoveDimensionFigureDialog : public wxDialog +{ + public: + explicit MoveDimensionFigureDialog(EmulatedUSBDeviceFrame* parent, uint8 currentIndex); + uint8 GetNewPad() const; + uint8 GetNewIndex() const; + + protected: + uint8 m_newIndex = 0; + uint8 m_newPad = 0; + + private: + wxBoxSizer* AddMinifigSlot(uint8 pad, uint8 index, uint8 oldIndex, std::optional currentId); }; \ No newline at end of file From ec1e67e5921f8cdae0115f67b0c2f96b3b9efdd2 Mon Sep 17 00:00:00 2001 From: Joshua de Reeper Date: Mon, 7 Oct 2024 18:24:44 +0200 Subject: [PATCH 6/9] Return token name if not a minifig --- src/Cafe/OS/libs/nsyshid/Dimensions.cpp | 12 ++++++++++++ src/Cafe/OS/libs/nsyshid/Dimensions.h | 1 + 2 files changed, 13 insertions(+) diff --git a/src/Cafe/OS/libs/nsyshid/Dimensions.cpp b/src/Cafe/OS/libs/nsyshid/Dimensions.cpp index 25ab56ec4..7139fe6b8 100644 --- a/src/Cafe/OS/libs/nsyshid/Dimensions.cpp +++ b/src/Cafe/OS/libs/nsyshid/Dimensions.cpp @@ -1114,6 +1114,11 @@ namespace nsyshid return s_listMinis; } + std::map DimensionsUSB::GetListTokens() + { + return s_listTokens; + } + std::string DimensionsUSB::FindFigure(uint32 figNum) { for (const auto& it : GetListMinifigs()) @@ -1123,6 +1128,13 @@ namespace nsyshid return it.second; } } + for (const auto& it : GetListTokens()) + { + if (it.first == figNum) + { + return it.second; + } + } return fmt::format("Unknown ({})", figNum); } } // namespace nsyshid \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyshid/Dimensions.h b/src/Cafe/OS/libs/nsyshid/Dimensions.h index a6c47924b..0902c8327 100644 --- a/src/Cafe/OS/libs/nsyshid/Dimensions.h +++ b/src/Cafe/OS/libs/nsyshid/Dimensions.h @@ -70,6 +70,7 @@ namespace nsyshid bool CreateFigure(fs::path pathName, uint32 id); bool MoveFigure(uint8 pad, uint8 index, uint8 oldPad, uint8 oldIndex); static std::map GetListMinifigs(); + static std::map GetListTokens(); std::string FindFigure(uint32 figNum); protected: From a197c6c6aed050489a7eb5f6ca2b6f6124ad8459 Mon Sep 17 00:00:00 2001 From: Joshua de Reeper Date: Tue, 8 Oct 2024 12:31:38 +0200 Subject: [PATCH 7/9] Better mutexes and Move Methods --- src/Cafe/OS/libs/nsyshid/Dimensions.cpp | 105 +++++++++++------- src/Cafe/OS/libs/nsyshid/Dimensions.h | 12 +- .../EmulatedUSBDeviceFrame.cpp | 16 ++- 3 files changed, 83 insertions(+), 50 deletions(-) diff --git a/src/Cafe/OS/libs/nsyshid/Dimensions.cpp b/src/Cafe/OS/libs/nsyshid/Dimensions.cpp index 7139fe6b8..4547ce85c 100644 --- a/src/Cafe/OS/libs/nsyshid/Dimensions.cpp +++ b/src/Cafe/OS/libs/nsyshid/Dimensions.cpp @@ -508,16 +508,16 @@ namespace nsyshid bool responded = false; do { - if (!m_figureAddedRemovedResponses.empty()) + if (!m_queries.empty()) { - response = m_figureAddedRemovedResponses.front(); - m_figureAddedRemovedResponses.pop(); + response = m_queries.front(); + m_queries.pop(); responded = true; } - else if (!m_queries.empty()) + else if (!m_figureAddedRemovedResponses.empty() && m_isAwake) { - response = m_queries.front(); - m_queries.pop(); + response = m_figureAddedRemovedResponses.front(); + m_figureAddedRemovedResponses.pop(); responded = true; } else @@ -609,10 +609,9 @@ namespace nsyshid m_queries.push(q_result); } - uint32 DimensionsUSB::LoadFigure(const std::array& buf, std::unique_ptr file, uint8 pad, uint8 index, bool lock) + uint32 DimensionsUSB::LoadFigure(const std::array& buf, std::unique_ptr file, uint8 pad, uint8 index) { - if (lock) - std::shared_lock lock(m_dimensionsMutex); + std::lock_guard lock(m_dimensionsMutex); const uint32 id = GetFigureId(buf); @@ -628,42 +627,70 @@ namespace nsyshid figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13); m_figureAddedRemovedResponses.push(figureChangeResponse); - if (lock) - std::shared_lock unlock(m_dimensionsMutex); - return id; } - bool DimensionsUSB::RemoveFigure(uint8 pad, uint8 index, bool save, bool lock) + 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; - } - - if (lock) - std::shared_lock lock(m_dimensionsMutex); // 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 - std::array 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]}; - if (save) + if (fullRemove) { + std::array 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; - memcpy(&figureChangeResponse[6], figure.data.data(), 7); + + 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 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); + } + + bool DimensionsUSB::CancelRemove(uint8 index) + { + std::lock_guard lock(m_dimensionsMutex); + + DimensionsMini& figure = GetFigureByIndex(index); + if (figure.index == 255) + return false; - if (lock) - std::shared_lock unlock(m_dimensionsMutex); + // Cancel the previous movement of the figure + std::array 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; } @@ -672,12 +699,11 @@ namespace nsyshid { FileStream* dimFile(FileStream::createFile2(pathName)); if (!dimFile) - { return false; - } + std::array fileData{}; RandomUID(fileData); - fileData[7] = id & 0xFF; + fileData[3] = id & 0xFF; std::array uid = {fileData[0], fileData[1], fileData[2], fileData[4], fileData[5], fileData[6], fileData[7]}; @@ -715,32 +741,24 @@ namespace nsyshid bool DimensionsUSB::MoveFigure(uint8 pad, uint8 index, uint8 oldPad, uint8 oldIndex) { - std::lock_guard lock(m_dimensionsMutex); - if (oldIndex == index) { // Don't bother removing and loading again, just send response to the game - const DimensionsMini& figure = GetFigureByIndex(oldIndex); - std::array figureRemoveResponse = {0x56, 0x0b, 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]}; - figureRemoveResponse[13] = GenerateChecksum(figureRemoveResponse, 13); - std::array figureAddResponse = {0x56, 0x0b, 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]}; - figureAddResponse[13] = GenerateChecksum(figureAddResponse, 13); - m_figureAddedRemovedResponses.push(std::move(figureRemoveResponse)); - m_figureAddedRemovedResponses.push(std::move(figureAddResponse)); + 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, false); + RemoveFigure(pad, index, true); DimensionsMini& figure = GetFigureByIndex(oldIndex); const std::array data = figure.data; std::unique_ptr inFile = std::move(figure.dimFile); - RemoveFigure(oldPad, oldIndex, false, false); + RemoveFigure(oldPad, oldIndex, false); - LoadFigure(data, std::move(inFile), pad, index, false); + LoadFigure(data, std::move(inFile), pad, index); return true; } @@ -788,6 +806,9 @@ namespace nsyshid // 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) @@ -1076,7 +1097,7 @@ namespace nsyshid void DimensionsUSB::RandomUID(std::array& uid_buffer) { uid_buffer[0] = 0x04; - uid_buffer[6] = 0x80; + uid_buffer[7] = 0x80; std::random_device rd; std::mt19937 mt(rd()); @@ -1084,9 +1105,9 @@ namespace nsyshid uid_buffer[1] = dist(mt); uid_buffer[2] = dist(mt); - uid_buffer[3] = dist(mt); uid_buffer[4] = dist(mt); uid_buffer[5] = dist(mt); + uid_buffer[6] = dist(mt); } uint8 DimensionsUSB::GenerateChecksum(const std::array& data, diff --git a/src/Cafe/OS/libs/nsyshid/Dimensions.h b/src/Cafe/OS/libs/nsyshid/Dimensions.h index 0902c8327..d5a2a5292 100644 --- a/src/Cafe/OS/libs/nsyshid/Dimensions.h +++ b/src/Cafe/OS/libs/nsyshid/Dimensions.h @@ -1,4 +1,4 @@ -#include +#include #include "nsyshid.h" #include "Backend.h" @@ -65,8 +65,10 @@ namespace nsyshid void GetModel(std::span buf, uint8 sequence, std::array& replyBuf); - bool RemoveFigure(uint8 pad, uint8 index, bool save, bool lock); - uint32 LoadFigure(const std::array& buf, std::unique_ptr file, uint8 pad, uint8 index, bool lock); + bool RemoveFigure(uint8 pad, uint8 index, bool fullRemove); + bool TempRemove(uint8 index); + bool CancelRemove(uint8 index); + uint32 LoadFigure(const std::array& buf, std::unique_ptr file, uint8 pad, uint8 index); bool CreateFigure(fs::path pathName, uint32 id); bool MoveFigure(uint8 pad, uint8 index, uint8 oldPad, uint8 oldIndex); static std::map GetListMinifigs(); @@ -74,7 +76,7 @@ namespace nsyshid std::string FindFigure(uint32 figNum); protected: - std::shared_mutex m_dimensionsMutex; + std::mutex m_dimensionsMutex; std::array m_figures{}; private: @@ -96,6 +98,8 @@ namespace nsyshid uint32 m_randomC; uint32 m_randomD; + bool m_isAwake = false; + std::queue> m_figureAddedRemovedResponses; std::queue> m_queries; }; diff --git a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp index d8ec111e3..af1e4d153 100644 --- a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp +++ b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp @@ -638,14 +638,14 @@ void EmulatedUSBDeviceFrame::LoadMinifigPath(wxString path_name, uint8 pad, uint ClearMinifig(pad, index); - uint32 id = nsyshid::g_dimensionstoypad.LoadFigure(file_data, std::move(dim_file), pad, index, true); + uint32 id = nsyshid::g_dimensionstoypad.LoadFigure(file_data, std::move(dim_file), pad, index); m_dimensionSlots[index]->ChangeValue(nsyshid::g_dimensionstoypad.FindFigure(id)); m_dimSlots[index] = id; } void EmulatedUSBDeviceFrame::ClearMinifig(uint8 pad, uint8 index) { - nsyshid::g_dimensionstoypad.RemoveFigure(pad, index, true, true); + nsyshid::g_dimensionstoypad.RemoveFigure(pad, index, true); m_dimensionSlots[index]->ChangeValue("None"); m_dimSlots[index] = std::nullopt; } @@ -662,7 +662,11 @@ void EmulatedUSBDeviceFrame::CreateMinifig(uint8 pad, uint8 index) void EmulatedUSBDeviceFrame::MoveMinifig(uint8 pad, uint8 index) { + if (!m_dimSlots[index]) + return; + MoveDimensionFigureDialog move_dlg(this, index); + nsyshid::g_dimensionstoypad.TempRemove(index); move_dlg.ShowModal(); if (move_dlg.GetReturnCode() == 1) { @@ -675,6 +679,10 @@ void EmulatedUSBDeviceFrame::MoveMinifig(uint8 pad, uint8 index) m_dimensionSlots[index]->ChangeValue("None"); } } + else + { + nsyshid::g_dimensionstoypad.CancelRemove(index); + } } CreateDimensionFigureDialog::CreateDimensionFigureDialog(wxWindow* parent) @@ -778,8 +786,8 @@ MoveDimensionFigureDialog::MoveDimensionFigureDialog(EmulatedUSBDeviceFrame* par sizer->Add(new wxStaticText(this, wxID_ANY, ""), 1, wxALL, 5); sizer->Add(AddMinifigSlot(3, 2, currentIndex, ids[2]), 1, wxALL, 5); - sizer->Add(AddMinifigSlot(1, 3, currentIndex, ids[3]), 1, wxALL, 5); - sizer->Add(AddMinifigSlot(1, 4, currentIndex, ids[4]), 1, wxALL, 5); + sizer->Add(AddMinifigSlot(2, 3, currentIndex, ids[3]), 1, wxALL, 5); + sizer->Add(AddMinifigSlot(2, 4, currentIndex, ids[4]), 1, wxALL, 5); sizer->Add(new wxStaticText(this, wxID_ANY, ""), 1, wxALL, 5); sizer->Add(AddMinifigSlot(3, 5, currentIndex, ids[5]), 1, wxALL, 5); sizer->Add(AddMinifigSlot(3, 6, currentIndex, ids[6]), 1, wxALL, 5); From 453a4ff08e06ee92932a699c5d0f12906b8887c2 Mon Sep 17 00:00:00 2001 From: Joshua de Reeper Date: Tue, 8 Oct 2024 12:48:04 +0200 Subject: [PATCH 8/9] lock mutex in status method --- src/Cafe/OS/libs/nsyshid/Dimensions.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Cafe/OS/libs/nsyshid/Dimensions.cpp b/src/Cafe/OS/libs/nsyshid/Dimensions.cpp index 4547ce85c..2664338fe 100644 --- a/src/Cafe/OS/libs/nsyshid/Dimensions.cpp +++ b/src/Cafe/OS/libs/nsyshid/Dimensions.cpp @@ -516,6 +516,7 @@ namespace nsyshid } else if (!m_figureAddedRemovedResponses.empty() && m_isAwake) { + std::lock_guard lock(m_dimensionsMutex); response = m_figureAddedRemovedResponses.front(); m_figureAddedRemovedResponses.pop(); responded = true; From c5a1a6f27d8497933e0444f8d0e4d34d05e0217d Mon Sep 17 00:00:00 2001 From: Joshua de Reeper Date: Fri, 11 Oct 2024 11:39:00 +0200 Subject: [PATCH 9/9] use trivially copyable uint32be --- src/Cafe/OS/libs/nsyshid/Dimensions.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Cafe/OS/libs/nsyshid/Dimensions.cpp b/src/Cafe/OS/libs/nsyshid/Dimensions.cpp index 2664338fe..f328dde71 100644 --- a/src/Cafe/OS/libs/nsyshid/Dimensions.cpp +++ b/src/Cafe/OS/libs/nsyshid/Dimensions.cpp @@ -772,7 +772,7 @@ namespace nsyshid // 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::from_bevalue((uint32&)value[4]); + 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 @@ -792,7 +792,7 @@ namespace nsyshid // Decrypt payload into an 8 byte array std::array value = Decrypt(buf, std::nullopt); // Confirmation is the first 4 bytes of the decrypted payload - uint32 conf = uint32be::from_bevalue((uint32&)value[0]); + 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) @@ -961,7 +961,7 @@ namespace nsyshid std::array randomized = DimensionsRandomize(toScramble, count); - return uint32be::from_bevalue((uint32&)randomized[0]); + return (uint32be&)randomized[0]; } std::array DimensionsUSB::PWDGenerate(const std::array& buf) @@ -1075,7 +1075,7 @@ namespace nsyshid // Decrypt payload to 8 byte array, byte 1 is the index, 4-7 are the confirmation std::array value = Decrypt(buf, std::nullopt); uint8 index = value[0]; - uint32 conf = uint32be::from_bevalue((uint32&)value[4]); + 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 valueToEncrypt = {};