1
0
mirror of https://github.com/RPCS3/rpcs3.git synced 2024-11-26 04:32:35 +01:00

sys_usbd: Emulate Dimensions Toypad

This commit is contained in:
Joshua de Reeper 2024-07-01 16:43:07 +01:00 committed by Megamouse
parent a2534263fe
commit 500bf0f3f5
13 changed files with 1593 additions and 1 deletions

View File

@ -397,6 +397,7 @@ target_link_libraries(rpcs3_emu
target_sources(rpcs3_emu PRIVATE target_sources(rpcs3_emu PRIVATE
Io/Buzz.cpp Io/Buzz.cpp
Io/camera_config.cpp Io/camera_config.cpp
Io/Dimensions.cpp
Io/GameTablet.cpp Io/GameTablet.cpp
Io/GHLtar.cpp Io/GHLtar.cpp
Io/GunCon3.cpp Io/GunCon3.cpp

View File

@ -17,6 +17,7 @@
#include "Emu/Io/usb_vfs.h" #include "Emu/Io/usb_vfs.h"
#include "Emu/Io/Skylander.h" #include "Emu/Io/Skylander.h"
#include "Emu/Io/Infinity.h" #include "Emu/Io/Infinity.h"
#include "Emu/Io/Dimensions.h"
#include "Emu/Io/GHLtar.h" #include "Emu/Io/GHLtar.h"
#include "Emu/Io/ghltar_config.h" #include "Emu/Io/ghltar_config.h"
#include "Emu/Io/guncon3_config.h" #include "Emu/Io/guncon3_config.h"
@ -239,6 +240,7 @@ usb_handler_thread::usb_handler_thread()
bool found_skylander = false; bool found_skylander = false;
bool found_infinity = false; bool found_infinity = false;
bool found_dimension = false;
bool found_usj = false; bool found_usj = false;
for (ssize_t index = 0; index < ndev; index++) for (ssize_t index = 0; index < ndev; index++)
@ -274,7 +276,11 @@ usb_handler_thread::usb_handler_thread()
found_infinity = true; found_infinity = true;
} }
check_device(0x0E6F, 0x0241, 0x0241, "Lego Dimensions Portal"); if (check_device(0x0E6F, 0x0241, 0x0241, "Lego Dimensions Portal"))
{
found_dimension = true;
}
check_device(0x0E6F, 0x200A, 0x200A, "Kamen Rider Summonride Portal"); check_device(0x0E6F, 0x200A, 0x200A, "Kamen Rider Summonride Portal");
// Cameras // Cameras
@ -394,6 +400,12 @@ usb_handler_thread::usb_handler_thread()
usb_devices.push_back(std::make_shared<usb_device_infinity>(get_new_location())); usb_devices.push_back(std::make_shared<usb_device_infinity>(get_new_location()));
} }
if (!found_dimension)
{
sys_usbd.notice("Adding emulated dimension toypad");
usb_devices.push_back(std::make_shared<usb_device_dimensions>(get_new_location()));
}
if (!found_usj) if (!found_usj)
{ {
if (!g_cfg_usio.load()) if (!g_cfg_usio.load())

618
rpcs3/Emu/Io/Dimensions.cpp Normal file
View File

@ -0,0 +1,618 @@
#include "stdafx.h"
#include "Dimensions.h"
#include <bit>
#include <thread>
#include "Crypto/aes.h"
#include "Crypto/sha1.h"
#include "util/asm.hpp"
#include "Emu/Cell/lv2/sys_usbd.h"
LOG_CHANNEL(dimensions_log, "dimensions");
dimensions_toypad g_dimensionstoypad;
static constexpr std::array<u8, 16> COMMAND_KEY = {0x55, 0xFE, 0xF6, 0xB0, 0x62, 0xBF, 0x0B, 0x41,
0xC9, 0xB3, 0x7C, 0xB4, 0x97, 0x3E, 0x29, 0x7B};
static constexpr std::array<u8, 17> CHAR_CONSTANT = {0xB7, 0xD5, 0xD7, 0xE6, 0xE7, 0xBA, 0x3C,
0xA8, 0xD8, 0x75, 0x47, 0x68, 0xCF, 0x23, 0xE9, 0xFE, 0xAA};
static constexpr std::array<u8, 25> PWD_CONSTANT = {0x28, 0x63, 0x29, 0x20, 0x43, 0x6F, 0x70, 0x79,
0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x4C, 0x45, 0x47, 0x4F, 0x20, 0x32, 0x30, 0x31, 0x34, 0xAA, 0xAA};
void dimensions_figure::save()
{
if (!dim_file)
{
dimensions_log.error("Tried to save infinity figure to file but no infinity figure is active!");
return;
}
dim_file.seek(0, fs::seek_set);
dim_file.write(data.data(), 0x2D * 0x04);
}
u8 dimensions_toypad::generate_checksum(const std::array<u8, 32>& data, u32 num_of_bytes) const
{
int checksum = 0;
ensure(num_of_bytes <= data.size());
for (u8 i = 0; i < num_of_bytes; i++)
{
checksum += data[i];
}
return (checksum & 0xFF);
}
void dimensions_toypad::get_blank_response(u8 type, u8 sequence, std::array<u8, 32>& reply_buf)
{
reply_buf[0] = 0x55;
reply_buf[1] = type;
reply_buf[2] = sequence;
reply_buf[3] = generate_checksum(reply_buf, 3);
}
void dimensions_toypad::generate_random_number(const u8* buf, u8 sequence, std::array<u8, 32>& reply_buf)
{
// Decrypt payload into an 8 byte array
const std::array<u8, 8> value = decrypt(buf, std::nullopt);
// Seed is the first 4 bytes (little endian) of the decrypted payload
const u32 seed = read_from_ptr<le_t<u32>>(value);
// Confirmation is the second 4 bytes (big endian) of the decrypted payload
const u32 conf = read_from_ptr<be_t<u32>>(value, 4);
// Initialize rng using the seed from decrypted payload
initialize_rng(seed);
std::array<u8, 8> value_to_encrypt = {};
// Encrypt 8 bytes, first 4 bytes is the decrypted confirmation from payload, 2nd 4 bytes are blank
write_to_ptr<be_t<u32>>(value_to_encrypt, conf);
const std::array<u8, 8> encrypted = encrypt(value_to_encrypt.data(), std::nullopt);
reply_buf[0] = 0x55;
reply_buf[1] = 0x09;
reply_buf[2] = sequence;
// Copy encrypted value to response data
std::memcpy(&reply_buf[3], encrypted.data(), encrypted.size());
reply_buf[11] = generate_checksum(reply_buf, 11);
}
void dimensions_toypad::initialize_rng(u32 seed)
{
random_a = 0xF1EA5EED;
random_b = seed;
random_c = seed;
random_d = seed;
for (int i = 0; i < 42; i++)
{
get_next();
}
}
u32 dimensions_toypad::get_next()
{
u32 e = random_a - std::rotl(random_b, 21);
random_a = random_b ^ std::rotl(random_c, 19);
random_b = random_c + std::rotl(random_d, 6);
random_c = random_d + e;
random_d = e + random_a;
return random_d;
}
std::array<u8, 8> dimensions_toypad::decrypt(const u8* buf, std::optional<std::array<u8, 16>> key)
{
// Value to decrypt is separated in to two little endian 32 bit unsigned integers
u32 data_one = read_from_ptr<le_t<u32>>(buf);
u32 data_two = read_from_ptr<le_t<u32>>(buf, 4);
// Use the key as 4 32 bit little endian unsigned integers
u32 key_one;
u32 key_two;
u32 key_three;
u32 key_four;
if (key)
{
key_one = read_from_ptr<le_t<u32>>(key.value());
key_two = read_from_ptr<le_t<u32>>(key.value(), 4);
key_three = read_from_ptr<le_t<u32>>(key.value(), 8);
key_four = read_from_ptr<le_t<u32>>(key.value(), 12);
}
else
{
key_one = read_from_ptr<le_t<u32>>(COMMAND_KEY);
key_two = read_from_ptr<le_t<u32>>(COMMAND_KEY, 4);
key_three = read_from_ptr<le_t<u32>>(COMMAND_KEY, 8);
key_four = read_from_ptr<le_t<u32>>(COMMAND_KEY, 12);
}
u32 sum = 0xC6EF3720;
constexpr u32 delta = 0x9E3779B9;
for (int i = 0; i < 32; i++)
{
data_two -= (((data_one << 4) + key_three) ^ (data_one + sum) ^ ((data_one >> 5) + key_four));
data_one -= (((data_two << 4) + key_one) ^ (data_two + sum) ^ ((data_two >> 5) + key_two));
sum -= delta;
}
ensure(sum == 0, "Decryption failed, sum inequal to 0");
std::array<u8, 8> decrypted = {u8(data_one & 0xFF), u8((data_one >> 8) & 0xFF),
u8((data_one >> 16) & 0xFF), u8((data_one >> 24) & 0xFF),
u8(data_two & 0xFF), u8((data_two >> 8) & 0xFF),
u8((data_two >> 16) & 0xFF), u8((data_two >> 24) & 0xFF)};
return decrypted;
}
std::array<u8, 8> dimensions_toypad::encrypt(const u8* buf, std::optional<std::array<u8, 16>> key)
{
// Value to encrypt is separated in to two little endian 32 bit unsigned integers
u32 data_one = read_from_ptr<le_t<u32>>(buf);
u32 data_two = read_from_ptr<le_t<u32>>(buf, 4);
// Use the key as 4 32 bit little endian unsigned integers
u32 key_one;
u32 key_two;
u32 key_three;
u32 key_four;
if (key)
{
key_one = read_from_ptr<le_t<u32>>(key.value());
key_two = read_from_ptr<le_t<u32>>(key.value(), 4);
key_three = read_from_ptr<le_t<u32>>(key.value(), 8);
key_four = read_from_ptr<le_t<u32>>(key.value(), 12);
}
else
{
key_one = read_from_ptr<le_t<u32>>(COMMAND_KEY);
key_two = read_from_ptr<le_t<u32>>(COMMAND_KEY, 4);
key_three = read_from_ptr<le_t<u32>>(COMMAND_KEY, 8);
key_four = read_from_ptr<le_t<u32>>(COMMAND_KEY, 12);
}
u32 sum = 0;
constexpr u32 delta = 0x9E3779B9;
for (int i = 0; i < 32; i++)
{
sum += delta;
data_one += (((data_two << 4) + key_one) ^ (data_two + sum) ^ ((data_two >> 5) + key_two));
data_two += (((data_one << 4) + key_three) ^ (data_one + sum) ^ ((data_one >> 5) + key_four));
}
ensure(sum == 0xC6EF3720, "Encryption failed, sum inequal to 0xC6EF3720");
std::array<u8, 8> encrypted = {u8(data_one & 0xFF), u8((data_one >> 8) & 0xFF),
u8((data_one >> 16) & 0xFF), u8((data_one >> 24) & 0xFF),
u8(data_two & 0xFF), u8((data_two >> 8) & 0xFF),
u8((data_two >> 16) & 0xFF), u8((data_two >> 24) & 0xFF)};
return encrypted;
}
std::array<u8, 16> dimensions_toypad::generate_figure_key(const std::array<u8, 0x2D * 0x04>& buf)
{
std::array<u8, 7> uid = {buf[0], buf[1], buf[2], buf[4], buf[5], buf[6], buf[7]};
std::array<u8, 16> figure_key = {};
write_to_ptr<be_t<u32>>(figure_key, scramble(uid, 3));
write_to_ptr<be_t<u32>>(figure_key, 4, scramble(uid, 4));
write_to_ptr<be_t<u32>>(figure_key, 8, scramble(uid, 5));
write_to_ptr<be_t<u32>>(figure_key, 12, scramble(uid, 6));
return figure_key;
}
u32 dimensions_toypad::scramble(const std::array<u8, 7>& uid, u8 count)
{
std::vector<u8> to_scramble;
to_scramble.reserve(uid.size() + CHAR_CONSTANT.size());
for (u8 x : uid)
{
to_scramble.push_back(x);
}
for (u8 c : CHAR_CONSTANT)
{
to_scramble.push_back(c);
}
::at32(to_scramble, count * 4 - 1) = 0xaa;
return read_from_ptr<be_t<u32>>(dimensions_randomize(to_scramble, count).data());
}
std::array<u8, 4> dimensions_toypad::dimensions_randomize(const std::vector<u8> key, u8 count)
{
u32 scrambled = 0;
for (u8 i = 0; i < count; i++)
{
const u32 v4 = std::rotr(scrambled, 25);
const u32 v5 = std::rotr(scrambled, 10);
const u32 b = read_from_ptr<le_t<u32>>(key, i * 4);
scrambled = b + v4 + v5 - scrambled;
}
return {u8(scrambled & 0xFF), u8(scrambled >> 8 & 0xFF), u8(scrambled >> 16 & 0xFF), u8(scrambled >> 24 & 0xFF)};
}
u32 dimensions_toypad::get_figure_id(const std::array<u8, 0x2D * 0x04>& buf)
{
std::array<u8, 16> figure_key = generate_figure_key(buf);
std::array<u8, 8> decrypted = decrypt(&buf[36 * 4], figure_key);
const u32 fig_num = read_from_ptr<le_t<u32>>(decrypted);
// Characters have their model number encrypted in page 36
if (fig_num < 1000)
{
return fig_num;
}
// Vehicles/Gadgets have their model number written as little endian in page 36
return read_from_ptr<le_t<u32>>(buf, 36 * 4);
}
dimensions_figure& dimensions_toypad::get_figure_by_index(u8 index)
{
return ::at32(m_figures, index);
}
void dimensions_toypad::random_uid(u8* uid_buffer)
{
uid_buffer[0] = 0x04;
uid_buffer[6] = 0x80;
for (u8 i = 1; i < 6; i++)
{
u8 random = rand() % 255;
uid_buffer[i] = random;
}
}
void dimensions_toypad::get_challenge_response(const u8* buf, u8 sequence, std::array<u8, 32>& reply_buf)
{
// Decrypt payload into an 8 byte array
const std::array<u8, 8> value = decrypt(buf, std::nullopt);
// Confirmation is the first 4 bytes of the decrypted payload
const u32 conf = read_from_ptr<be_t<u32>>(value);
// Generate next random number based on RNG
const u32 next_random = get_next();
std::array<u8, 8> value_to_encrypt = {};
// Encrypt an 8 byte array, first 4 bytes are the next random number (little endian)
// followed by the confirmation from the decrypted payload
write_to_ptr<le_t<u32>>(value_to_encrypt, next_random);
write_to_ptr<be_t<u32>>(value_to_encrypt, 4, conf);
const std::array<u8, 8> encrypted = encrypt(value_to_encrypt.data(), std::nullopt);
reply_buf[0] = 0x55;
reply_buf[1] = 0x09;
reply_buf[2] = sequence;
// Copy encrypted value to response data
std::memcpy(&reply_buf[3], encrypted.data(), encrypted.size());
reply_buf[11] = generate_checksum(reply_buf, 11);
}
void dimensions_toypad::query_block(u8 index, u8 page, std::array<u8, 32>& reply_buf, u8 sequence)
{
std::lock_guard lock(dimensions_mutex);
// Index from game begins at 1 rather than 0, so minus 1 here
dimensions_figure& figure = get_figure_by_index(index - 1);
reply_buf[0] = 0x55;
reply_buf[1] = 0x12;
reply_buf[2] = sequence;
reply_buf[3] = 0x00;
// 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(&reply_buf[4], figure.data.data() + (4 * page), 16);
}
reply_buf[20] = generate_checksum(reply_buf, 20);
}
void dimensions_toypad::write_block(u8 index, u8 page, const u8* to_write_buf, std::array<u8, 32>& reply_buf, u8 sequence)
{
std::lock_guard lock(dimensions_mutex);
// Index from game begins at 1 rather than 0, so minus 1 here
dimensions_figure& figure = get_figure_by_index(index - 1);
reply_buf[0] = 0x55;
reply_buf[1] = 0x02;
reply_buf[2] = sequence;
reply_buf[3] = 0x00;
// 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 = read_from_ptr<le_t<u32>>(to_write_buf);
}
std::memcpy(figure.data.data() + (page * 4), to_write_buf, 4);
figure.save();
}
reply_buf[4] = generate_checksum(reply_buf, 4);
}
void dimensions_toypad::get_model(const u8* buf, u8 sequence, std::array<u8, 32>& reply_buf)
{
// Decrypt payload to 8 byte array, byte 1 is the index, 4-7 are the confirmation
const std::array<u8, 8> value = decrypt(buf, std::nullopt);
const u8 index = value[0];
const u32 conf = read_from_ptr<be_t<u32>>(value, 4);
// Index from game begins at 1 rather than 0, so minus 1 here
dimensions_figure& figure = get_figure_by_index(index - 1);
std::array<u8, 8> value_to_encrypt = {};
// Response is the figure's id (little endian) followed by the confirmation from payload
write_to_ptr<le_t<u32>>(value_to_encrypt, figure.id);
write_to_ptr<be_t<u32>>(value_to_encrypt, 4, conf);
const std::array<u8, 8> encrypted = encrypt(value_to_encrypt.data(), std::nullopt);
reply_buf[0] = 0x55;
reply_buf[1] = 0x0a;
reply_buf[2] = sequence;
reply_buf[3] = 0x00;
// Copy encrypted message to response
std::memcpy(&reply_buf[4], encrypted.data(), encrypted.size());
reply_buf[12] = generate_checksum(reply_buf, 12);
}
u32 dimensions_toypad::load_figure(const std::array<u8, 0x2D * 0x04>& buf, fs::file in_file, u8 pad, u8 index)
{
std::lock_guard lock(dimensions_mutex);
const u32 id = get_figure_id(buf);
dimensions_figure& figure = get_figure_by_index(index);
figure.dim_file = std::move(in_file);
figure.id = id;
figure.pad = pad;
figure.index = index + 1;
std::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<u8, 32> figure_change_response = {0x56, 0x0b, figure.pad, 0x00, figure.index, 0x00};
std::memcpy(&figure_change_response[6], buf.data(), 7);
figure_change_response[13] = generate_checksum(figure_change_response, 13);
m_figure_added_removed_responses.push(figure_change_response);
return id;
}
bool dimensions_toypad::remove_figure(u8 pad, u8 index, bool save)
{
std::lock_guard lock(dimensions_mutex);
dimensions_figure& figure = get_figure_by_index(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<u8, 32> figure_change_response = {0x56, 0x0b, pad, 0x00, figure.index, 0x01};
std::memcpy(&figure_change_response[6], figure.data.data(), 7);
if (save)
{
figure.save();
figure.dim_file.close();
}
figure.index = 255;
figure.pad = 255;
figure_change_response[13] = generate_checksum(figure_change_response, 13);
m_figure_added_removed_responses.push(figure_change_response);
return true;
}
bool dimensions_toypad::move_figure(u8 pad, u8 index, u8 old_pad, u8 old_index)
{
// When moving figures between spaces on the portal, 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
remove_figure(pad, index, true);
dimensions_figure& figure = get_figure_by_index(old_index);
const std::array<u8, 0x2D * 0x04> data = figure.data;
fs::file in_file = std::move(figure.dim_file);
remove_figure(old_pad, old_index, false);
load_figure(data, std::move(in_file), pad, index);
return true;
}
bool dimensions_toypad::create_blank_character(std::array<u8, 0x2D * 0x04>& buf, u16 id)
{
random_uid(buf.data());
buf[7] = id & 0xFF;
std::array<u8, 7> uid = {buf[0], buf[1], buf[2], buf[4], buf[5], buf[6], buf[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<u8, 16> figure_key = generate_figure_key(buf);
std::array<u8, 8> value_to_encrypt = {};
write_to_ptr<le_t<u32>>(value_to_encrypt, id);
write_to_ptr<le_t<u32>>(value_to_encrypt, 4, id);
std::array<u8, 8> encrypted = encrypt(value_to_encrypt.data(), figure_key);
std::memcpy(&buf[36 * 4], &encrypted[0], 4);
std::memcpy(&buf[37 * 4], &encrypted[4], 4);
std::memcpy(&buf[43 * 4], pwd_generate(uid).data(), 4);
}
else
{
// Page 38 is used as verification for blank tags
write_to_ptr<be_t<u16>>(buf.data(), 38 * 4, 1);
}
return true;
}
std::array<u8, 4> dimensions_toypad::pwd_generate(const std::array<u8, 7>& uid)
{
std::vector<u8> pwd_calc = {PWD_CONSTANT.begin(), PWD_CONSTANT.end() - 1};
for (u8 i = 0; i < uid.size(); i++)
{
pwd_calc.insert(pwd_calc.begin() + i, uid[i]);
}
return dimensions_randomize(pwd_calc, 8);
}
std::optional<std::array<u8, 32>> dimensions_toypad::pop_added_removed_response()
{
if (m_figure_added_removed_responses.empty())
{
return std::nullopt;
}
else
{
std::array<u8, 32> response = m_figure_added_removed_responses.front();
m_figure_added_removed_responses.pop();
return response;
}
}
usb_device_dimensions::usb_device_dimensions(const std::array<u8, 7>& location)
: usb_device_emulated(location)
{
device = UsbDescriptorNode(USB_DESCRIPTOR_DEVICE, UsbDeviceDescriptor{0x200, 0x0, 0x0, 0x0, 0x20, 0x0E6F, 0x0241, 0x200, 0x1, 0x2, 0x3, 0x1});
auto& config0 = device.add_node(UsbDescriptorNode(USB_DESCRIPTOR_CONFIG, UsbDeviceConfiguration{0x29, 0x1, 0x1, 0x0, 0x80, 0xFA}));
config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_INTERFACE, UsbDeviceInterface{0x0, 0x0, 0x2, 0x3, 0x0, 0x0, 0x0}));
config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_HID, UsbDeviceHID{0x0111, 0x00, 0x01, 0x22, 0x001d}));
config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, UsbDeviceEndpoint{0x81, 0x03, 0x20, 0x1}));
config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, UsbDeviceEndpoint{0x01, 0x03, 0x20, 0x1}));
}
usb_device_dimensions::~usb_device_dimensions()
{
}
void usb_device_dimensions::control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer)
{
usb_device_emulated::control_transfer(bmRequestType, bRequest, wValue, wIndex, wLength, buf_size, buf, transfer);
}
void usb_device_dimensions::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint, UsbTransfer* transfer)
{
ensure(buf_size == 0x20);
transfer->fake = true;
transfer->expected_count = buf_size;
transfer->expected_result = HC_CC_NOERR;
if (endpoint == 0x81)
{
// Read Endpoint, if a request has not been sent via the write endpoint, set expected result as
// EHCI_CC_HALTED so the game doesn't report the Toypad as being disconnected.
std::unique_lock lock(query_mutex);
std::optional<std::array<u8, 32>> response = g_dimensionstoypad.pop_added_removed_response();
if (response)
{
std::memcpy(buf, response.value().data(), 0x20);
}
else if (!m_queries.empty())
{
std::memcpy(buf, m_queries.front().data(), 0x20);
m_queries.pop();
}
else
{
transfer->expected_count = 0;
transfer->expected_result = EHCI_CC_HALTED;
}
lock.unlock();
}
else if (endpoint == 0x01)
{
// Write endpoint, similar structure of request to the Infinity Base with a command for byte 3,
// sequence for byte 4, the payload after that, then a checksum for the final byte.
const u8 command = buf[2];
const u8 sequence = buf[3];
transfer->expected_time = get_timestamp() + 100;
std::array<u8, 32> q_result{};
switch (command)
{
case 0xB0: // Wake
{
// Consistent device response to the wake command
q_result = {0x55, 0x0e, 0x01, 0x28, 0x63, 0x29,
0x20, 0x4c, 0x45, 0x47, 0x4f, 0x20,
0x32, 0x30, 0x31, 0x34, 0x46};
break;
}
case 0xB1: // Seed
{
// Initialise a random number generator using the seed provided
g_dimensionstoypad.generate_random_number(&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.get_challenge_response(&buf[4], sequence, q_result);
break;
}
case 0xC0: // Color
case 0xC2: // Fade
case 0xC3: // Flash
case 0xC6: // Fade All
{
// Send a blank response to acknowledge color has been sent to toypad
g_dimensionstoypad.get_blank_response(0x01, sequence, q_result);
break;
}
case 0xD2: // Read
{
// Read 4 pages from the figure at index (buf[4]), starting with page buf[5]
g_dimensionstoypad.query_block(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.write_block(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.get_model(&buf[4], sequence, q_result);
break;
}
case 0xC1: // Get Pad Color
case 0xC4: // Fade Random
case 0xC7: // Flash All
case 0xC8: // Color All
case 0xD0: // Tag List
case 0xE1: // PWD
case 0xE5: // Active
case 0xFF: // LEDS Query
{
// Further investigation required
dimensions_log.error("Unimplemented LD Function: 0x%x", command);
dimensions_log.error("Request: %s", fmt::buf_to_hexstring(buf, buf_size));
break;
}
default:
{
dimensions_log.error("Unknown LD Function: 0x%x", command);
dimensions_log.error("Request: %s", fmt::buf_to_hexstring(buf, buf_size));
break;
}
}
std::lock_guard lock(query_mutex);
m_queries.push(q_result);
}
}
void usb_device_dimensions::isochronous_transfer(UsbTransfer* transfer)
{
usb_device_emulated::isochronous_transfer(transfer);
}

76
rpcs3/Emu/Io/Dimensions.h Normal file
View File

@ -0,0 +1,76 @@
#pragma once
#include "Emu/Io/usb_device.h"
#include "Utilities/mutex.h"
#include <array>
#include <queue>
struct dimensions_figure
{
fs::file dim_file;
std::array<u8, 0x2D * 0x04> data{};
u8 index = 255;
u8 pad = 255;
u32 id = 0;
void save();
};
class dimensions_toypad
{
public:
void get_blank_response(u8 type, u8 sequence, std::array<u8, 32>& reply_buf);
void generate_random_number(const u8* buf, u8 sequence, std::array<u8, 32>& reply_buf);
void initialize_rng(u32 seed);
void get_challenge_response(const u8* buf, u8 sequence, std::array<u8, 32>& reply_buf);
void query_block(u8 index, u8 page, std::array<u8, 32>& reply_buf, u8 sequence);
void write_block(u8 index, u8 page, const u8* to_write_buf, std::array<u8, 32>& reply_buf, u8 sequence);
void get_model(const u8* buf, u8 sequence, std::array<u8, 32>& reply_buf);
std::optional<std::array<u8, 32>> pop_added_removed_response();
bool remove_figure(u8 pad, u8 index, bool save);
u32 load_figure(const std::array<u8, 0x2D * 0x04>& buf, fs::file in_file, u8 pad, u8 index);
bool move_figure(u8 pad, u8 index, u8 old_pad, u8 old_index);
bool create_blank_character(std::array<u8, 0x2D * 0x04>& buf, u16 id);
protected:
shared_mutex dimensions_mutex;
std::array<dimensions_figure, 7> m_figures;
private:
void random_uid(u8* uid_buffer);
u8 generate_checksum(const std::array<u8, 32>& data, u32 num_of_bytes) const;
std::array<u8, 8> decrypt(const u8* buf, std::optional<std::array<u8, 16>> key);
std::array<u8, 8> encrypt(const u8* buf, std::optional<std::array<u8, 16>> key);
std::array<u8, 16> generate_figure_key(const std::array<u8, 0x2D * 0x04>& buf);
u32 scramble(const std::array<u8, 7>& uid, u8 count);
std::array<u8, 4> pwd_generate(const std::array<u8, 7>& uid);
std::array<u8, 4> dimensions_randomize(const std::vector<u8> key, u8 count);
u32 get_figure_id(const std::array<u8, 0x2D * 0x04>& buf);
u32 get_next();
dimensions_figure& get_figure_by_index(u8 index);
u32 random_a;
u32 random_b;
u32 random_c;
u32 random_d;
u8 m_figure_order = 0;
std::queue<std::array<u8, 32>> m_figure_added_removed_responses;
};
extern dimensions_toypad g_dimensionstoypad;
class usb_device_dimensions : public usb_device_emulated
{
public:
usb_device_dimensions(const std::array<u8, 7>& location);
~usb_device_dimensions();
void control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer) override;
void interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint, UsbTransfer* transfer) override;
void isochronous_transfer(UsbTransfer* transfer) override;
protected:
shared_mutex query_mutex;
std::queue<std::array<u8, 32>> m_queries;
};

View File

@ -412,6 +412,7 @@
<ClCompile Include="Emu\Cell\SPUDisAsm.cpp" /> <ClCompile Include="Emu\Cell\SPUDisAsm.cpp" />
<ClCompile Include="Emu\Cell\SPUInterpreter.cpp" /> <ClCompile Include="Emu\Cell\SPUInterpreter.cpp" />
<ClCompile Include="Emu\IdManager.cpp" /> <ClCompile Include="Emu\IdManager.cpp" />
<ClCompile Include="Emu\Io\Dimensions.cpp" />
<ClCompile Include="Emu\Io\Infinity.cpp" /> <ClCompile Include="Emu\Io\Infinity.cpp" />
<ClCompile Include="Emu\Io\Skylander.cpp" /> <ClCompile Include="Emu\Io\Skylander.cpp" />
<ClCompile Include="Emu\Io\usb_device.cpp" /> <ClCompile Include="Emu\Io\usb_device.cpp" />
@ -741,6 +742,7 @@
<ClInclude Include="Emu\Cell\PPUAnalyser.h" /> <ClInclude Include="Emu\Cell\PPUAnalyser.h" />
<ClInclude Include="Emu\Cell\PPUTranslator.h" /> <ClInclude Include="Emu\Cell\PPUTranslator.h" />
<ClInclude Include="Emu\CPU\CPUTranslator.h" /> <ClInclude Include="Emu\CPU\CPUTranslator.h" />
<ClInclude Include="Emu\Io\Dimensions.h" />
<ClInclude Include="Emu\Io\Infinity.h" /> <ClInclude Include="Emu\Io\Infinity.h" />
<ClInclude Include="Emu\Io\Skylander.h" /> <ClInclude Include="Emu\Io\Skylander.h" />
<ClInclude Include="Emu\Io\usb_device.h" /> <ClInclude Include="Emu\Io\usb_device.h" />

View File

@ -861,6 +861,9 @@
<ClCompile Include="Emu\Cell\lv2\sys_config.cpp"> <ClCompile Include="Emu\Cell\lv2\sys_config.cpp">
<Filter>Emu\Cell\lv2</Filter> <Filter>Emu\Cell\lv2</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="Emu\Io\Dimensions.cpp">
<Filter>Emu\Io</Filter>
</ClCompile>
<ClCompile Include="Emu\Io\Infinity.cpp"> <ClCompile Include="Emu\Io\Infinity.cpp">
<Filter>Emu\Io</Filter> <Filter>Emu\Io</Filter>
</ClCompile> </ClCompile>
@ -1986,6 +1989,9 @@
<ClInclude Include="Emu\Io\Keyboard.h"> <ClInclude Include="Emu\Io\Keyboard.h">
<Filter>Emu\Io</Filter> <Filter>Emu\Io</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="Emu\Io\Dimensions.h">
<Filter>Emu\Io</Filter>
</ClInclude>
<ClInclude Include="Emu\Io\Infinity.h"> <ClInclude Include="Emu\Io\Infinity.h">
<Filter>Emu\Io</Filter> <Filter>Emu\Io</Filter>
</ClInclude> </ClInclude>

View File

@ -229,6 +229,9 @@
<ClCompile Include="QTGeneratedFiles\Debug\moc_debugger_list.cpp"> <ClCompile Include="QTGeneratedFiles\Debug\moc_debugger_list.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile> </ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_dimensions_dialog.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_downloader.cpp"> <ClCompile Include="QTGeneratedFiles\Debug\moc_downloader.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile> </ClCompile>
@ -496,6 +499,9 @@
<ClCompile Include="QTGeneratedFiles\Release\moc_debugger_list.cpp"> <ClCompile Include="QTGeneratedFiles\Release\moc_debugger_list.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile> </ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_dimensions_dialog.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_downloader.cpp"> <ClCompile Include="QTGeneratedFiles\Release\moc_downloader.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile> </ClCompile>
@ -734,6 +740,7 @@
<ClCompile Include="rpcs3qt\custom_dialog.cpp" /> <ClCompile Include="rpcs3qt\custom_dialog.cpp" />
<ClCompile Include="rpcs3qt\custom_table_widget_item.cpp" /> <ClCompile Include="rpcs3qt\custom_table_widget_item.cpp" />
<ClCompile Include="rpcs3qt\debugger_list.cpp" /> <ClCompile Include="rpcs3qt\debugger_list.cpp" />
<ClCompile Include="rpcs3qt\dimensions_dialog.cpp" />
<ClCompile Include="rpcs3qt\downloader.cpp" /> <ClCompile Include="rpcs3qt\downloader.cpp" />
<ClCompile Include="rpcs3qt\elf_memory_dumping_dialog.cpp" /> <ClCompile Include="rpcs3qt\elf_memory_dumping_dialog.cpp" />
<ClCompile Include="rpcs3qt\emulated_pad_settings_dialog.cpp" /> <ClCompile Include="rpcs3qt\emulated_pad_settings_dialog.cpp" />
@ -1516,6 +1523,16 @@
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs> <Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent"</Command> <Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent"</Command>
</CustomBuild> </CustomBuild>
<CustomBuild Include="rpcs3qt\dimensions_dialog.h">
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing %(Identity)...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Moc%27ing %(Identity)...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent"</Command>
</CustomBuild>
<CustomBuild Include="rpcs3qt\update_manager.h"> <CustomBuild Include="rpcs3qt\update_manager.h">
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing %(Identity)...</Message> <Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing %(Identity)...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs> <Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>

View File

@ -101,6 +101,9 @@
<Filter Include="Io\DS3"> <Filter Include="Io\DS3">
<UniqueIdentifier>{66e6027b-d3dd-4894-814c-cc4444a4c7df}</UniqueIdentifier> <UniqueIdentifier>{66e6027b-d3dd-4894-814c-cc4444a4c7df}</UniqueIdentifier>
</Filter> </Filter>
<Filter Include="Gui\dimensions">
<UniqueIdentifier>{d138e7e2-861a-4078-badf-80ecb10b0d04}</UniqueIdentifier>
</Filter>
<Filter Include="Gui\infinity"> <Filter Include="Gui\infinity">
<UniqueIdentifier>{f5fcca0d-918b-46ba-bb91-2f2f9d9ddbba}</UniqueIdentifier> <UniqueIdentifier>{f5fcca0d-918b-46ba-bb91-2f2f9d9ddbba}</UniqueIdentifier>
</Filter> </Filter>
@ -576,6 +579,9 @@
<ClCompile Include="headless_application.cpp"> <ClCompile Include="headless_application.cpp">
<Filter>rpcs3</Filter> <Filter>rpcs3</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="rpcs3qt\dimensions_dialog.cpp">
<Filter>Gui\dimensions</Filter>
</ClCompile>
<ClCompile Include="rpcs3qt\infinity_dialog.cpp"> <ClCompile Include="rpcs3qt\infinity_dialog.cpp">
<Filter>Gui\infinity</Filter> <Filter>Gui\infinity</Filter>
</ClCompile> </ClCompile>
@ -681,6 +687,12 @@
<ClCompile Include="QTGeneratedFiles\Release\moc_screenshot_preview.cpp"> <ClCompile Include="QTGeneratedFiles\Release\moc_screenshot_preview.cpp">
<Filter>Generated Files\Release</Filter> <Filter>Generated Files\Release</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_dimensions_dialog.cpp">
<Filter>Generated Files\Debug</Filter>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_dimensions_dialog.cpp">
<Filter>Generated Files\Release</Filter>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_infinity_dialog.cpp"> <ClCompile Include="QTGeneratedFiles\Debug\moc_infinity_dialog.cpp">
<Filter>Generated Files\Debug</Filter> <Filter>Generated Files\Debug</Filter>
</ClCompile> </ClCompile>
@ -1447,6 +1459,9 @@
<CustomBuild Include="rpcs3qt\screenshot_preview.h"> <CustomBuild Include="rpcs3qt\screenshot_preview.h">
<Filter>Gui\screenshot manager</Filter> <Filter>Gui\screenshot manager</Filter>
</CustomBuild> </CustomBuild>
<CustomBuild Include="rpcs3qt\dimensions_dialog.h">
<Filter>Gui\dimensions</Filter>
</CustomBuild>
<CustomBuild Include="rpcs3qt\infinity_dialog.h"> <CustomBuild Include="rpcs3qt\infinity_dialog.h">
<Filter>Gui\infinity</Filter> <Filter>Gui\infinity</Filter>
</CustomBuild> </CustomBuild>

View File

@ -16,6 +16,7 @@ add_library(rpcs3_ui STATIC
debugger_frame.cpp debugger_frame.cpp
debugger_list.cpp debugger_list.cpp
downloader.cpp downloader.cpp
dimensions_dialog.cpp
_discord_utils.cpp _discord_utils.cpp
emu_settings.cpp emu_settings.cpp
elf_memory_dumping_dialog.cpp elf_memory_dumping_dialog.cpp

View File

@ -0,0 +1,768 @@
#include "stdafx.h"
#include "Utilities/File.h"
#include "dimensions_dialog.h"
#include "Emu/Io/Dimensions.h"
#include "util/asm.hpp"
#include <locale>
#include <QLabel>
#include <QGroupBox>
#include <QFileDialog>
#include <QVBoxLayout>
#include <QMessageBox>
#include <QComboBox>
#include <QPushButton>
#include <QStringList>
#include <QCompleter>
#include <QGridLayout>
dimensions_dialog* dimensions_dialog::inst = nullptr;
std::array<std::optional<u32>, 7> figure_slots = {};
static QString s_last_figure_path;
LOG_CHANNEL(dimensions_log, "dimensions");
const std::map<const u32, const std::string> list_minifigs = {
{0, "Blank Tag"},
{1, "Batman"},
{2, "Gandalf"},
{3, "Wyldstyle"},
{4, "Aquaman"},
{5, "Bad Cop"},
{6, "Bane"},
{7, "Bart Simpson"},
{8, "Benny"},
{9, "Chell"},
{10, "Cole"},
{11, "Cragger"},
{12, "Cyborg"},
{13, "Cyberman"},
{14, "Doc Brown"},
{15, "The Doctor"},
{16, "Emmet"},
{17, "Eris"},
{18, "Gimli"},
{19, "Gollum"},
{20, "Harley Quinn"},
{21, "Homer Simpson"},
{22, "Jay"},
{23, "Joker"},
{24, "Kai"},
{25, "ACU Trooper"},
{26, "Gamer Kid"},
{27, "Krusty the Clown"},
{28, "Laval"},
{29, "Legolas"},
{30, "Lloyd"},
{31, "Marty McFly"},
{32, "Nya"},
{33, "Owen Grady"},
{34, "Peter Venkman"},
{35, "Slimer"},
{36, "Scooby-Doo"},
{37, "Sensei Wu"},
{38, "Shaggy"},
{39, "Stay Puft"},
{40, "Superman"},
{41, "Unikitty"},
{42, "Wicked Witch of the West"},
{43, "Wonder Woman"},
{44, "Zane"},
{45, "Green Arrow"},
{46, "Supergirl"},
{47, "Abby Yates"},
{48, "Finn the Human"},
{49, "Ethan Hunt"},
{50, "Lumpy Space Princess"},
{51, "Jake the Dog"},
{52, "Harry Potter"},
{53, "Lord Voldemort"},
{54, "Michael Knight"},
{55, "B.A. Baracus"},
{56, "Newt Scamander"},
{57, "Sonic the Hedgehog"},
{58, "Future Update (unreleased)"},
{59, "Gizmo"},
{60, "Stripe"},
{61, "E.T."},
{62, "Tina Goldstein"},
{63, "Marceline the Vampire Queen"},
{64, "Batgirl"},
{65, "Robin"},
{66, "Sloth"},
{67, "Hermione Granger"},
{68, "Chase McCain"},
{69, "Excalibur Batman"},
{70, "Raven"},
{71, "Beast Boy"},
{72, "Betelgeuse"},
{73, "Lord Vortech (unreleased)"},
{74, "Blossom"},
{75, "Bubbles"},
{76, "Buttercup"},
{77, "Starfire"},
{78, "World 15 (unreleased)"},
{79, "World 16 (unreleased)"},
{80, "World 17 (unreleased)"},
{81, "World 18 (unreleased)"},
{82, "World 19 (unreleased)"},
{83, "World 20 (unreleased)"},
{768, "Unknown 768"},
{769, "Supergirl Red Lantern"},
{770, "Unknown 770"}};
const std::map<const u32, const std::string> list_tokens = {
{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"}};
minifig_creator_dialog::minifig_creator_dialog(QWidget* parent)
: QDialog(parent)
{
setWindowTitle(tr("Figure Creator"));
setObjectName("figure_creator");
setMinimumSize(QSize(500, 150));
QVBoxLayout* vbox_panel = new QVBoxLayout();
QComboBox* combo_figlist = new QComboBox();
QStringList filterlist;
for (const auto& [figure, figure_name] : list_minifigs)
{
const QString name = QString::fromStdString(figure_name);
combo_figlist->addItem(name, QVariant(figure));
filterlist << name;
}
combo_figlist->addItem(tr("--Unknown--"), QVariant(0xFFFF));
combo_figlist->setEditable(true);
combo_figlist->setInsertPolicy(QComboBox::NoInsert);
QCompleter* co_compl = new QCompleter(filterlist, this);
co_compl->setCaseSensitivity(Qt::CaseInsensitive);
co_compl->setCompletionMode(QCompleter::PopupCompletion);
co_compl->setFilterMode(Qt::MatchContains);
combo_figlist->setCompleter(co_compl);
vbox_panel->addWidget(combo_figlist);
QFrame* line = new QFrame();
line->setFrameShape(QFrame::HLine);
line->setFrameShadow(QFrame::Sunken);
vbox_panel->addWidget(line);
QHBoxLayout* hbox_number = new QHBoxLayout();
QLabel* label_number = new QLabel(tr("Figure Number:"));
QLineEdit* edit_number = new QLineEdit(QString::fromStdString(std::to_string(0)));
QRegularExpressionValidator* rxv = new QRegularExpressionValidator(QRegularExpression("\\d*"), this);
edit_number->setValidator(rxv);
hbox_number->addWidget(label_number);
hbox_number->addWidget(edit_number);
vbox_panel->addLayout(hbox_number);
QHBoxLayout* hbox_buttons = new QHBoxLayout();
QPushButton* btn_create = new QPushButton(tr("Create"), this);
QPushButton* btn_cancel = new QPushButton(tr("Cancel"), this);
hbox_buttons->addStretch();
hbox_buttons->addWidget(btn_create);
hbox_buttons->addWidget(btn_cancel);
vbox_panel->addLayout(hbox_buttons);
setLayout(vbox_panel);
connect(combo_figlist, QOverload<int>::of(&QComboBox::currentIndexChanged), [=](int index)
{
const u16 fig_info = combo_figlist->itemData(index).toUInt();
if (fig_info != 0xFFFF)
{
edit_number->setText(QString::number(fig_info));
}
});
connect(btn_create, &QAbstractButton::clicked, this, [=, this]()
{
bool ok_num = false;
const u16 fig_num = edit_number->text().toUInt(&ok_num) & 0xFFFF;
if (!ok_num)
{
QMessageBox::warning(this, tr("Error converting value"), tr("Figure number entered is invalid!"), QMessageBox::Ok);
return;
}
const auto found_figure = list_minifigs.find(fig_num);
if (found_figure != list_minifigs.end())
{
s_last_figure_path += QString::fromStdString(found_figure->second + ".bin");
}
else
{
s_last_figure_path += QString("Unknown(%1).bin").arg(fig_num);
}
m_file_path = QFileDialog::getSaveFileName(this, tr("Create Figure File"), s_last_figure_path, tr("Dimensions Figure (*.bin);;"));
if (m_file_path.isEmpty())
{
return;
}
fs::file dim_file(m_file_path.toStdString(), fs::read + fs::write + fs::create);
if (!dim_file)
{
QMessageBox::warning(this, tr("Failed to create minifig file!"), tr("Failed to create minifig file:\n%1").arg(m_file_path), QMessageBox::Ok);
return;
}
std::array<u8, 0x2D * 0x04> file_data{};
g_dimensionstoypad.create_blank_character(file_data, fig_num);
dim_file.write(file_data.data(), file_data.size());
dim_file.close();
s_last_figure_path = QFileInfo(m_file_path).absolutePath() + "/";
accept();
});
connect(btn_cancel, &QAbstractButton::clicked, this, &QDialog::reject);
connect(co_compl, QOverload<const QString&>::of(&QCompleter::activated), [=](const QString& text)
{
combo_figlist->setCurrentIndex(combo_figlist->findText(text));
});
}
QString minifig_creator_dialog::get_file_path() const
{
return m_file_path;
}
minifig_move_dialog::minifig_move_dialog(QWidget* parent, u8 old_index)
: QDialog(parent)
{
setWindowTitle(tr("Figure Mover"));
setObjectName("figure_mover");
setMinimumSize(QSize(500, 150));
auto* grid_panel = new QGridLayout();
add_minifig_position(grid_panel, 0, 0, 0, old_index);
grid_panel->addWidget(new QLabel(tr("")), 0, 1);
add_minifig_position(grid_panel, 1, 0, 2, old_index);
grid_panel->addWidget(new QLabel(tr(""), this), 0, 3);
add_minifig_position(grid_panel, 2, 0, 4, old_index);
add_minifig_position(grid_panel, 3, 1, 0, old_index);
add_minifig_position(grid_panel, 4, 1, 1, old_index);
grid_panel->addWidget(new QLabel(tr("")), 1, 2);
add_minifig_position(grid_panel, 5, 1, 3, old_index);
add_minifig_position(grid_panel, 6, 1, 4, old_index);
setLayout(grid_panel);
}
void minifig_move_dialog::add_minifig_position(QGridLayout* grid_panel, u8 index, u8 row, u8 column, u8 old_index)
{
ensure(index < figure_slots.size());
auto* vbox_panel = new QVBoxLayout();
if (figure_slots[index])
{
const auto found_figure = list_minifigs.find(figure_slots[index].value());
if (found_figure != list_minifigs.end())
{
vbox_panel->addWidget(new QLabel(tr(found_figure->second.c_str())));
}
}
else
{
vbox_panel->addWidget(new QLabel(tr("None")));
}
if (old_index != index)
{
auto* btn_move = new QPushButton(tr("Move Here"), this);
vbox_panel->addWidget(btn_move);
connect(btn_move, &QAbstractButton::clicked, this, [this, index]
{
m_index = index;
m_pad = index == 1 ? 1 :
index == 0 || index == 3 || index == 4 ? 2 :
3;
accept();
});
}
grid_panel->addLayout(vbox_panel, row, column);
}
u8 minifig_move_dialog::get_new_pad() const
{
return m_pad;
}
u8 minifig_move_dialog::get_new_index() const
{
return m_index;
}
dimensions_dialog::dimensions_dialog(QWidget* parent)
: QDialog(parent)
{
setWindowTitle(tr("Dimensions Manager"));
setObjectName("dimensions_manager");
setAttribute(Qt::WA_DeleteOnClose);
setMinimumSize(QSize(1200, 500));
QVBoxLayout* vbox_panel = new QVBoxLayout();
QGroupBox* group_figures = new QGroupBox(tr("Active Dimensions Figures:"));
QGridLayout* grid_group = new QGridLayout();
add_minifig_slot(grid_group, 2, 0, 0, 0);
grid_group->addWidget(new QLabel(tr("")), 0, 1);
add_minifig_slot(grid_group, 1, 1, 0, 2);
grid_group->addWidget(new QLabel(tr("")), 0, 1);
add_minifig_slot(grid_group, 3, 2, 0, 4);
add_minifig_slot(grid_group, 2, 3, 1, 0);
add_minifig_slot(grid_group, 2, 4, 1, 1);
grid_group->addWidget(new QLabel(tr("")), 0, 1);
add_minifig_slot(grid_group, 3, 5, 1, 3);
add_minifig_slot(grid_group, 3, 6, 1, 4);
group_figures->setLayout(grid_group);
vbox_panel->addWidget(group_figures);
setLayout(vbox_panel);
}
dimensions_dialog::~dimensions_dialog()
{
inst = nullptr;
}
dimensions_dialog* dimensions_dialog::get_dlg(QWidget* parent)
{
if (inst == nullptr)
inst = new dimensions_dialog(parent);
return inst;
}
void dimensions_dialog::add_minifig_slot(QGridLayout* grid_group, u8 pad, u8 index, u8 row, u8 column)
{
ensure(index < figure_slots.size());
QVBoxLayout* vbox_layout = new QVBoxLayout();
QHBoxLayout* hbox_name_move = new QHBoxLayout();
QHBoxLayout* hbox_actions = new QHBoxLayout();
QPushButton* clear_btn = new QPushButton(tr("Clear"));
QPushButton* create_btn = new QPushButton(tr("Create"));
QPushButton* load_btn = new QPushButton(tr("Load"));
QPushButton* move_btn = new QPushButton(tr("Move"));
m_edit_figures[index] = new QLineEdit();
m_edit_figures[index]->setEnabled(false);
if (figure_slots[index])
{
const auto found_figure = list_minifigs.find(figure_slots[index].value());
if (found_figure != list_minifigs.end())
{
m_edit_figures[index]->setText(QString::fromStdString(found_figure->second));
}
else
{
m_edit_figures[index]->setText(tr("Unknown Figure"));
}
}
else
{
m_edit_figures[index]->setText(tr("None"));
}
connect(clear_btn, &QAbstractButton::clicked, this, [this, pad, index]
{
clear_figure(pad, index);
});
connect(create_btn, &QAbstractButton::clicked, this, [this, pad, index]
{
create_figure(pad, index);
});
connect(load_btn, &QAbstractButton::clicked, this, [this, pad, index]
{
load_figure(pad, index);
});
connect(move_btn, &QAbstractButton::clicked, this, [this, pad, index]
{
if (figure_slots[index])
{
move_figure(pad, index);
}
});
hbox_name_move->addWidget(m_edit_figures[index]);
hbox_name_move->addWidget(move_btn);
hbox_actions->addWidget(clear_btn);
hbox_actions->addWidget(create_btn);
hbox_actions->addWidget(load_btn);
vbox_layout->addLayout(hbox_name_move);
vbox_layout->addLayout(hbox_actions);
grid_group->addLayout(vbox_layout, row, column);
}
void dimensions_dialog::clear_figure(u8 pad, u8 index)
{
ensure(index < figure_slots.size());
if (figure_slots[index])
{
g_dimensionstoypad.remove_figure(pad, index, true);
figure_slots[index] = std::nullopt;
m_edit_figures[index]->setText(tr("None"));
}
}
void dimensions_dialog::create_figure(u8 pad, u8 index)
{
ensure(index < figure_slots.size());
minifig_creator_dialog create_dlg(this);
if (create_dlg.exec() == Accepted)
{
load_figure_path(pad, index, create_dlg.get_file_path());
}
}
void dimensions_dialog::load_figure(u8 pad, u8 index)
{
ensure(index < figure_slots.size());
const QString file_path = QFileDialog::getOpenFileName(this, tr("Select Dimensions File"), s_last_figure_path, tr("Dimensions Figure (*.bin);;"));
if (file_path.isEmpty())
{
return;
}
s_last_figure_path = QFileInfo(file_path).absolutePath() + "/";
load_figure_path(pad, index, file_path);
}
void dimensions_dialog::move_figure(u8 pad, u8 index)
{
ensure(index < figure_slots.size());
minifig_move_dialog move_dlg(this, index);
if (move_dlg.exec() == Accepted)
{
g_dimensionstoypad.move_figure(move_dlg.get_new_pad(), move_dlg.get_new_index(), pad, index);
figure_slots[move_dlg.get_new_index()] = figure_slots[index];
m_edit_figures[move_dlg.get_new_index()]->setText(m_edit_figures[index]->text());
figure_slots[index] = std::nullopt;
m_edit_figures[index]->setText(tr("None"));
}
}
void dimensions_dialog::load_figure_path(u8 pad, u8 index, const QString& path)
{
fs::file dim_file(path.toStdString(), fs::read + fs::write + fs::lock);
if (!dim_file)
{
QMessageBox::warning(this, tr("Failed to open the figure file!"), tr("Failed to open the figure file(%1)!\nFile may already be in use on the base.").arg(path), QMessageBox::Ok);
return;
}
std::array<u8, 0x2D * 0x04> data;
if (dim_file.read(data.data(), data.size()) != data.size())
{
QMessageBox::warning(this, tr("Failed to read the figure file!"), tr("Failed to read the figure file(%1)!\nFile was too small.").arg(path), QMessageBox::Ok);
return;
}
clear_figure(pad, index);
const u32 fig_num = g_dimensionstoypad.load_figure(data, std::move(dim_file), pad, index);
figure_slots[index] = fig_num;
auto name = list_minifigs.find(fig_num);
if (name != list_minifigs.end())
{
m_edit_figures[index]->setText(QString::fromStdString(name->second));
}
else
{
auto blank_name = list_tokens.find(fig_num);
if (blank_name != list_tokens.end())
{
m_edit_figures[index]->setText(QString::fromStdString(blank_name->second));
}
else
{
m_edit_figures[index]->setText(tr("Blank Tag"));
}
}
}

View File

@ -0,0 +1,63 @@
#pragma once
#include "util/types.hpp"
#include <QDialog>
#include <QLineEdit>
#include <QGridLayout>
class minifig_creator_dialog : public QDialog
{
Q_OBJECT
public:
explicit minifig_creator_dialog(QWidget* parent);
QString get_file_path() const;
protected:
QString m_file_path;
};
class minifig_move_dialog : public QDialog
{
Q_OBJECT
public:
explicit minifig_move_dialog(QWidget* parent, u8 old_index);
u8 get_new_pad() const;
u8 get_new_index() const;
protected:
u8 m_pad = 0;
u8 m_index = 0;
private:
void add_minifig_position(QGridLayout* grid_panel, u8 index, u8 row, u8 column, u8 old_index);
};
class dimensions_dialog : public QDialog
{
Q_OBJECT
public:
explicit dimensions_dialog(QWidget* parent);
~dimensions_dialog();
static dimensions_dialog* get_dlg(QWidget* parent);
dimensions_dialog(dimensions_dialog const&) = delete;
void operator=(dimensions_dialog const&) = delete;
protected:
void clear_figure(u8 pad, u8 index);
void create_figure(u8 pad, u8 index);
void load_figure(u8 pad, u8 index);
void move_figure(u8 pad, u8 index);
void load_figure_path(u8 pad, u8 index, const QString& path);
protected:
std::array<QLineEdit*, 7> m_edit_figures{};
private:
void add_minifig_slot(QGridLayout* grid_group, u8 pad, u8 index, u8 row, u8 column);
static dimensions_dialog* inst;
};

View File

@ -21,6 +21,7 @@
#include "progress_dialog.h" #include "progress_dialog.h"
#include "skylander_dialog.h" #include "skylander_dialog.h"
#include "infinity_dialog.h" #include "infinity_dialog.h"
#include "dimensions_dialog.h"
#include "cheat_manager.h" #include "cheat_manager.h"
#include "patch_manager_dialog.h" #include "patch_manager_dialog.h"
#include "patch_creator_dialog.h" #include "patch_creator_dialog.h"
@ -2851,6 +2852,12 @@ void main_window::CreateConnects()
inf_dlg->show(); inf_dlg->show();
}); });
connect(ui->actionManage_Dimensions_ToyPad, &QAction::triggered, this, [this]
{
dimensions_dialog* dim_dlg = dimensions_dialog::get_dlg(this);
dim_dlg->show();
});
connect(ui->actionManage_Cheats, &QAction::triggered, this, [this] connect(ui->actionManage_Cheats, &QAction::triggered, this, [this]
{ {
cheat_manager_dialog* cheat_manager = cheat_manager_dialog::get_dlg(this); cheat_manager_dialog* cheat_manager = cheat_manager_dialog::get_dlg(this);

View File

@ -282,6 +282,7 @@
<addaction name="actionManage_Trophy_Data"/> <addaction name="actionManage_Trophy_Data"/>
<addaction name="actionManage_Skylanders_Portal"/> <addaction name="actionManage_Skylanders_Portal"/>
<addaction name="actionManage_Infinity_Base"/> <addaction name="actionManage_Infinity_Base"/>
<addaction name="actionManage_Dimensions_ToyPad"/>
<addaction name="actionManage_Cheats"/> <addaction name="actionManage_Cheats"/>
<addaction name="actionManage_Game_Patches"/> <addaction name="actionManage_Game_Patches"/>
<addaction name="actionManage_Screenshots"/> <addaction name="actionManage_Screenshots"/>
@ -1159,6 +1160,11 @@
<string>Infinity Base</string> <string>Infinity Base</string>
</property> </property>
</action> </action>
<action name="actionManage_Dimensions_ToyPad">
<property name="text">
<string>Dimensions Toypad</string>
</property>
</action>
<action name="actionManage_Cheats"> <action name="actionManage_Cheats">
<property name="text"> <property name="text">
<string>Cheats</string> <string>Cheats</string>