mirror of
https://github.com/RPCS3/rpcs3.git
synced 2024-11-22 10:42:36 +01:00
sys_usbd: Emulate Dimensions Toypad
This commit is contained in:
parent
a2534263fe
commit
500bf0f3f5
@ -397,6 +397,7 @@ target_link_libraries(rpcs3_emu
|
||||
target_sources(rpcs3_emu PRIVATE
|
||||
Io/Buzz.cpp
|
||||
Io/camera_config.cpp
|
||||
Io/Dimensions.cpp
|
||||
Io/GameTablet.cpp
|
||||
Io/GHLtar.cpp
|
||||
Io/GunCon3.cpp
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "Emu/Io/usb_vfs.h"
|
||||
#include "Emu/Io/Skylander.h"
|
||||
#include "Emu/Io/Infinity.h"
|
||||
#include "Emu/Io/Dimensions.h"
|
||||
#include "Emu/Io/GHLtar.h"
|
||||
#include "Emu/Io/ghltar_config.h"
|
||||
#include "Emu/Io/guncon3_config.h"
|
||||
@ -239,6 +240,7 @@ usb_handler_thread::usb_handler_thread()
|
||||
|
||||
bool found_skylander = false;
|
||||
bool found_infinity = false;
|
||||
bool found_dimension = false;
|
||||
bool found_usj = false;
|
||||
|
||||
for (ssize_t index = 0; index < ndev; index++)
|
||||
@ -274,7 +276,11 @@ usb_handler_thread::usb_handler_thread()
|
||||
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");
|
||||
|
||||
// Cameras
|
||||
@ -394,6 +400,12 @@ usb_handler_thread::usb_handler_thread()
|
||||
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 (!g_cfg_usio.load())
|
||||
|
618
rpcs3/Emu/Io/Dimensions.cpp
Normal file
618
rpcs3/Emu/Io/Dimensions.cpp
Normal 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
76
rpcs3/Emu/Io/Dimensions.h
Normal 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;
|
||||
};
|
@ -412,6 +412,7 @@
|
||||
<ClCompile Include="Emu\Cell\SPUDisAsm.cpp" />
|
||||
<ClCompile Include="Emu\Cell\SPUInterpreter.cpp" />
|
||||
<ClCompile Include="Emu\IdManager.cpp" />
|
||||
<ClCompile Include="Emu\Io\Dimensions.cpp" />
|
||||
<ClCompile Include="Emu\Io\Infinity.cpp" />
|
||||
<ClCompile Include="Emu\Io\Skylander.cpp" />
|
||||
<ClCompile Include="Emu\Io\usb_device.cpp" />
|
||||
@ -741,6 +742,7 @@
|
||||
<ClInclude Include="Emu\Cell\PPUAnalyser.h" />
|
||||
<ClInclude Include="Emu\Cell\PPUTranslator.h" />
|
||||
<ClInclude Include="Emu\CPU\CPUTranslator.h" />
|
||||
<ClInclude Include="Emu\Io\Dimensions.h" />
|
||||
<ClInclude Include="Emu\Io\Infinity.h" />
|
||||
<ClInclude Include="Emu\Io\Skylander.h" />
|
||||
<ClInclude Include="Emu\Io\usb_device.h" />
|
||||
|
@ -861,6 +861,9 @@
|
||||
<ClCompile Include="Emu\Cell\lv2\sys_config.cpp">
|
||||
<Filter>Emu\Cell\lv2</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Emu\Io\Dimensions.cpp">
|
||||
<Filter>Emu\Io</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Emu\Io\Infinity.cpp">
|
||||
<Filter>Emu\Io</Filter>
|
||||
</ClCompile>
|
||||
@ -1986,6 +1989,9 @@
|
||||
<ClInclude Include="Emu\Io\Keyboard.h">
|
||||
<Filter>Emu\Io</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Emu\Io\Dimensions.h">
|
||||
<Filter>Emu\Io</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Emu\Io\Infinity.h">
|
||||
<Filter>Emu\Io</Filter>
|
||||
</ClInclude>
|
||||
|
@ -229,6 +229,9 @@
|
||||
<ClCompile Include="QTGeneratedFiles\Debug\moc_debugger_list.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</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">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
@ -496,6 +499,9 @@
|
||||
<ClCompile Include="QTGeneratedFiles\Release\moc_debugger_list.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
</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">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
@ -734,6 +740,7 @@
|
||||
<ClCompile Include="rpcs3qt\custom_dialog.cpp" />
|
||||
<ClCompile Include="rpcs3qt\custom_table_widget_item.cpp" />
|
||||
<ClCompile Include="rpcs3qt\debugger_list.cpp" />
|
||||
<ClCompile Include="rpcs3qt\dimensions_dialog.cpp" />
|
||||
<ClCompile Include="rpcs3qt\downloader.cpp" />
|
||||
<ClCompile Include="rpcs3qt\elf_memory_dumping_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>
|
||||
<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\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">
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing %(Identity)...</Message>
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
|
||||
|
@ -101,6 +101,9 @@
|
||||
<Filter Include="Io\DS3">
|
||||
<UniqueIdentifier>{66e6027b-d3dd-4894-814c-cc4444a4c7df}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Gui\dimensions">
|
||||
<UniqueIdentifier>{d138e7e2-861a-4078-badf-80ecb10b0d04}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Gui\infinity">
|
||||
<UniqueIdentifier>{f5fcca0d-918b-46ba-bb91-2f2f9d9ddbba}</UniqueIdentifier>
|
||||
</Filter>
|
||||
@ -576,6 +579,9 @@
|
||||
<ClCompile Include="headless_application.cpp">
|
||||
<Filter>rpcs3</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rpcs3qt\dimensions_dialog.cpp">
|
||||
<Filter>Gui\dimensions</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rpcs3qt\infinity_dialog.cpp">
|
||||
<Filter>Gui\infinity</Filter>
|
||||
</ClCompile>
|
||||
@ -681,6 +687,12 @@
|
||||
<ClCompile Include="QTGeneratedFiles\Release\moc_screenshot_preview.cpp">
|
||||
<Filter>Generated Files\Release</Filter>
|
||||
</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">
|
||||
<Filter>Generated Files\Debug</Filter>
|
||||
</ClCompile>
|
||||
@ -1447,6 +1459,9 @@
|
||||
<CustomBuild Include="rpcs3qt\screenshot_preview.h">
|
||||
<Filter>Gui\screenshot manager</Filter>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="rpcs3qt\dimensions_dialog.h">
|
||||
<Filter>Gui\dimensions</Filter>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="rpcs3qt\infinity_dialog.h">
|
||||
<Filter>Gui\infinity</Filter>
|
||||
</CustomBuild>
|
||||
|
@ -16,6 +16,7 @@ add_library(rpcs3_ui STATIC
|
||||
debugger_frame.cpp
|
||||
debugger_list.cpp
|
||||
downloader.cpp
|
||||
dimensions_dialog.cpp
|
||||
_discord_utils.cpp
|
||||
emu_settings.cpp
|
||||
elf_memory_dumping_dialog.cpp
|
||||
|
768
rpcs3/rpcs3qt/dimensions_dialog.cpp
Normal file
768
rpcs3/rpcs3qt/dimensions_dialog.cpp
Normal 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"));
|
||||
}
|
||||
}
|
||||
}
|
63
rpcs3/rpcs3qt/dimensions_dialog.h
Normal file
63
rpcs3/rpcs3qt/dimensions_dialog.h
Normal 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;
|
||||
};
|
@ -21,6 +21,7 @@
|
||||
#include "progress_dialog.h"
|
||||
#include "skylander_dialog.h"
|
||||
#include "infinity_dialog.h"
|
||||
#include "dimensions_dialog.h"
|
||||
#include "cheat_manager.h"
|
||||
#include "patch_manager_dialog.h"
|
||||
#include "patch_creator_dialog.h"
|
||||
@ -2851,6 +2852,12 @@ void main_window::CreateConnects()
|
||||
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]
|
||||
{
|
||||
cheat_manager_dialog* cheat_manager = cheat_manager_dialog::get_dlg(this);
|
||||
|
@ -282,6 +282,7 @@
|
||||
<addaction name="actionManage_Trophy_Data"/>
|
||||
<addaction name="actionManage_Skylanders_Portal"/>
|
||||
<addaction name="actionManage_Infinity_Base"/>
|
||||
<addaction name="actionManage_Dimensions_ToyPad"/>
|
||||
<addaction name="actionManage_Cheats"/>
|
||||
<addaction name="actionManage_Game_Patches"/>
|
||||
<addaction name="actionManage_Screenshots"/>
|
||||
@ -1159,6 +1160,11 @@
|
||||
<string>Infinity Base</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionManage_Dimensions_ToyPad">
|
||||
<property name="text">
|
||||
<string>Dimensions Toypad</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionManage_Cheats">
|
||||
<property name="text">
|
||||
<string>Cheats</string>
|
||||
|
Loading…
Reference in New Issue
Block a user