Merge branch 'shadps4-emu:main' into gc2

This commit is contained in:
Lander Gallastegi 2025-11-16 00:05:18 +01:00 committed by GitHub
commit c21cce2a2f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 1568 additions and 91 deletions

2
.gitmodules vendored
View File

@ -108,7 +108,7 @@
branch = dist
[submodule "externals/MoltenVK"]
path = externals/MoltenVK
url = https://github.com/KhronosGroup/MoltenVK.git
url = https://github.com/shadPS4-emu/ext-MoltenVK.git
shallow = true
[submodule "externals/json"]
path = externals/json

View File

@ -203,7 +203,7 @@ execute_process(
# Set Version
set(EMULATOR_VERSION_MAJOR "0")
set(EMULATOR_VERSION_MINOR "12")
set(EMULATOR_VERSION_PATCH "1")
set(EMULATOR_VERSION_PATCH "6")
set_source_files_properties(src/shadps4.rc PROPERTIES COMPILE_DEFINITIONS "EMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR};EMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR};EMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}")
@ -539,6 +539,10 @@ set(RANDOM_LIB src/core/libraries/random/random.cpp
set(USBD_LIB src/core/libraries/usbd/usbd.cpp
src/core/libraries/usbd/usbd.h
src/core/libraries/usbd/usb_backend.h
src/core/libraries/usbd/emulated/dimensions.cpp
src/core/libraries/usbd/emulated/dimensions.h
src/core/libraries/usbd/emulated/infinity.cpp
src/core/libraries/usbd/emulated/infinity.h
src/core/libraries/usbd/emulated/skylander.cpp
src/core/libraries/usbd/emulated/skylander.h
)

View File

@ -37,6 +37,9 @@
<category translate="no">Game</category>
</categories>
<releases>
<release version="0.12.5" date="2025-11-07">
<url>https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.12.5</url>
</release>
<release version="0.12.0" date="2025-10-31">
<url>https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.12.0</url>
</release>

View File

@ -36,16 +36,11 @@ Go through the Git for Windows installation as normal
1. Open up Visual Studio, select `Open a local folder` and select the folder with the shadPS4 source code. The folder should contain `CMakeLists.txt`
2. Change Clang x64 Debug to Clang x64 Release if you want a regular, non-debug build.
3. If you want to build shadPS4 with the Qt Gui, simply select Clang x64 Release with Qt instead.
4. Change the project to build to shadps4.exe
5. Build -> Build All
3. Change the project to build to shadps4.exe
4. Build -> Build All
Your shadps4.exe will be in `C:\path\to\source\Build\x64-Clang-Release\`
To automatically populate the necessary files to run shadPS4.exe, run in a command prompt or terminal:
`C:\Qt\<QtVersion>\msvc2022_64\bin\windeployqt6.exe "C:\path\to\shadps4.exe"`
(Change Qt path if you've installed it to non-default path)
## Option 2: MSYS2/MinGW
> [!IMPORTANT]
@ -73,7 +68,6 @@ ARM64-based computers, follow:
1. Open "MSYS2 CLANGARM64" from your new applications
2. Run `pacman -Syu`, let it complete;
3. Run `pacman -S --needed git mingw-w64-clang-aarch64-binutils mingw-w64-clang-aarch64-clang mingw-w64-clang-aarch64-rapidjson mingw-w64-clang-aarch64-cmake mingw-w64-clang-aarch64-ninja mingw-w64-clang-aarch64-ffmpeg`
1. Optional (Qt only): run `pacman -S --needed mingw-w64-clang-aarch64-qt6-base mingw-w64-clang-aarch64-qt6-tools mingw-w64-clang-aarch64-qt6-multimedia`
4. Run `git clone --depth 1 --recursive https://github.com/shadps4-emu/shadPS4`
5. Run `cd shadPS4`
6. Run `cmake -S . -B build -DCMAKE_C_COMPILER="clang.exe" -DCMAKE_CXX_COMPILER="clang++.exe" -DCMAKE_CXX_FLAGS="-O2 -march=native"`

2
externals/MoltenVK vendored

@ -1 +1 @@
Subproject commit b23d42534622cd9926fe526fec1b7f8795a2853c
Subproject commit f168dec05998ab0ca09a400bab6831a95c0bdb2e

View File

@ -6,42 +6,43 @@ with import (fetchTarball "https://github.com/nixos/nixpkgs/archive/cfd19cdc5468
pkgs.mkShell {
name = "shadps4-build-env";
nativeBuildInputs = [
pkgs.llvmPackages_18.clang
pkgs.cmake
pkgs.pkg-config
pkgs.git
nativeBuildInputs = with pkgs; [
llvmPackages_18.clang
cmake
pkg-config
git
util-linux
];
buildInputs = [
pkgs.alsa-lib
pkgs.libpulseaudio
pkgs.openal
pkgs.zlib
pkgs.libedit
pkgs.udev
pkgs.libevdev
pkgs.SDL2
pkgs.jack2
pkgs.sndio
buildInputs = with pkgs; [
alsa-lib
libpulseaudio
openal
zlib
libedit
udev
libevdev
SDL2
jack2
sndio
pkgs.vulkan-headers
pkgs.vulkan-utility-libraries
pkgs.vulkan-tools
vulkan-headers
vulkan-utility-libraries
vulkan-tools
pkgs.ffmpeg
pkgs.fmt
pkgs.glslang
pkgs.libxkbcommon
pkgs.wayland
pkgs.xorg.libxcb
pkgs.xorg.xcbutil
pkgs.xorg.xcbutilkeysyms
pkgs.xorg.xcbutilwm
pkgs.sdl3
pkgs.stb
pkgs.wayland-protocols
pkgs.libpng
ffmpeg
fmt
glslang
libxkbcommon
wayland
xorg.libxcb
xorg.xcbutil
xorg.xcbutilkeysyms
xorg.xcbutilwm
sdl3
stb
wayland-protocols
libpng
];
shellHook = ''

View File

@ -68,6 +68,7 @@ class ElfInfo {
std::string app_ver{};
u32 firmware_ver = 0;
u32 raw_firmware_ver = 0;
u32 sdk_ver = 0;
PSFAttributes psf_attributes{};
std::filesystem::path splash_path{};
@ -117,6 +118,11 @@ public:
return raw_firmware_ver;
}
[[nodiscard]] u32 CompiledSdkVer() const {
ASSERT(initialized);
return sdk_ver;
}
[[nodiscard]] const PSFAttributes& GetPSFAttributes() const {
ASSERT(initialized);
return psf_attributes;

View File

@ -6,6 +6,7 @@
#include "common/arch.h"
#include "common/assert.h"
#include "common/config.h"
#include "common/elf_info.h"
#include "common/error.h"
#include "core/address_space.h"
#include "core/libraries/kernel/memory.h"
@ -103,8 +104,8 @@ struct AddressSpace::Impl {
GetSystemInfo(&sys_info);
u64 alignment = sys_info.dwAllocationGranularity;
// Determine the host OS build number
// Retrieve module handle for ntdll
// Older Windows builds have a severe performance issue with VirtualAlloc2.
// We need to get the host's Windows version, then determine if it needs a workaround.
auto ntdll_handle = GetModuleHandleW(L"ntdll.dll");
ASSERT_MSG(ntdll_handle, "Failed to retrieve ntdll handle");
@ -120,12 +121,20 @@ struct AddressSpace::Impl {
u64 supported_user_max = USER_MAX;
// This is the build number for Windows 11 22H2
static constexpr s32 AffectedBuildNumber = 22621;
if (os_version_info.dwBuildNumber <= AffectedBuildNumber) {
// Older Windows builds have an issue with VirtualAlloc2 on higher addresses.
// To prevent regressions, limit the maximum address we reserve for this platform.
supported_user_max = 0x11000000000ULL;
LOG_WARNING(Core, "Windows 10 detected, reducing user max to {:#x} to avoid problems",
supported_user_max);
// Higher PS4 firmware versions prevent higher address mappings too.
s32 sdk_ver = Common::ElfInfo::Instance().CompiledSdkVer();
if (os_version_info.dwBuildNumber <= AffectedBuildNumber ||
sdk_ver >= Common::ElfInfo::FW_30) {
supported_user_max = 0x10000000000ULL;
// Only log the message if we're restricting the user max due to operating system.
// Since higher compiled SDK versions also get reduced max, we don't need to log there.
if (sdk_ver < Common::ElfInfo::FW_30) {
LOG_WARNING(
Core,
"Older Windows version detected, reducing user max to {:#x} to avoid problems",
supported_user_max);
}
}
// Determine the free address ranges we can access.

View File

@ -640,17 +640,29 @@ s32 PS4_SYSV_ABI posix_stat(const char* path, OrbisKernelStat* sb) {
*__Error() = POSIX_ENOENT;
return -1;
}
// get the difference between file clock and system clock
const auto now_sys = std::chrono::system_clock::now();
const auto now_file = std::filesystem::file_time_type::clock::now();
// calculate the file modified time
const auto mtime = std::filesystem::last_write_time(path_name);
const auto mtimestamp = now_sys + (mtime - now_file);
if (std::filesystem::is_directory(path_name)) {
sb->st_mode = 0000777u | 0040000u;
sb->st_size = 65536;
sb->st_blksize = 65536;
sb->st_blocks = 128;
sb->st_mtim.tv_sec =
std::chrono::duration_cast<std::chrono::seconds>(mtimestamp.time_since_epoch()).count();
// TODO incomplete
} else {
sb->st_mode = 0000777u | 0100000u;
sb->st_size = static_cast<s64>(std::filesystem::file_size(path_name));
sb->st_blksize = 512;
sb->st_blocks = (sb->st_size + 511) / 512;
sb->st_mtim.tv_sec =
std::chrono::duration_cast<std::chrono::seconds>(mtimestamp.time_since_epoch()).count();
// TODO incomplete
}

View File

@ -236,15 +236,24 @@ s32 PS4_SYSV_ABI sceKernelSetGPO() {
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceKernelGetAllowedSdkVersionOnSystem(s32* ver) {
if (ver == nullptr) {
return ORBIS_KERNEL_ERROR_EINVAL;
}
// Returns the highest game SDK version this PS4 allows.
*ver = CURRENT_FIRMWARE_VERSION | 0xfff;
LOG_INFO(Lib_Kernel, "called, returned sw version: {}", *ver);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceKernelGetSystemSwVersion(SwVersionStruct* ret) {
if (ret == nullptr) {
return ORBIS_OK; // but why?
return ORBIS_OK;
}
ASSERT(ret->struct_size == 40);
u32 fake_fw = Common::ElfInfo::Instance().RawFirmwareVer();
u32 fake_fw = CURRENT_FIRMWARE_VERSION;
ret->hex_representation = fake_fw;
std::snprintf(ret->text_representation, 28, "%2x.%03x.%03x", fake_fw >> 0x18,
fake_fw >> 0xc & 0xfff, fake_fw & 0xfff); // why %2x?
fake_fw >> 0xc & 0xfff, fake_fw & 0xfff);
LOG_INFO(Lib_Kernel, "called, returned sw version: {}", ret->text_representation);
return ORBIS_OK;
}
@ -257,9 +266,13 @@ const char** PS4_SYSV_ABI getargv() {
return entry_params.argv;
}
s32 PS4_SYSV_ABI get_authinfo(int pid, AuthInfoData* p2) {
s32 PS4_SYSV_ABI get_authinfo(s32 pid, AuthInfoData* p2) {
LOG_WARNING(Lib_Kernel, "(STUBBED) called, pid: {}", pid);
if ((pid != 0) && (pid != GLOBAL_PID)) {
if (p2 == nullptr) {
*Kernel::__Error() = POSIX_EPERM;
return -1;
}
if (pid != 0 && pid != GLOBAL_PID) {
*Kernel::__Error() = POSIX_ESRCH;
return -1;
}
@ -269,6 +282,22 @@ s32 PS4_SYSV_ABI get_authinfo(int pid, AuthInfoData* p2) {
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceKernelGetAppInfo(s32 pid, OrbisKernelAppInfo* app_info) {
LOG_WARNING(Lib_Kernel, "(STUBBED) called, pid: {}", pid);
if (pid != GLOBAL_PID) {
return ORBIS_KERNEL_ERROR_EPERM;
}
if (app_info == nullptr) {
return ORBIS_OK;
}
auto& game_info = Common::ElfInfo::Instance();
*app_info = {};
app_info->has_param_sfo = 1;
strncpy(app_info->cusa_name, game_info.GameSerial().data(), 10);
return ORBIS_OK;
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
service_thread = std::jthread{KernelServiceThread};
@ -285,8 +314,10 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_OBJ("f7uOxY9mM1U", "libkernel", 1, "libkernel", &g_stack_chk_guard);
LIB_FUNCTION("D4yla3vx4tY", "libkernel", 1, "libkernel", sceKernelError);
LIB_FUNCTION("YeU23Szo3BM", "libkernel", 1, "libkernel", sceKernelGetAllowedSdkVersionOnSystem);
LIB_FUNCTION("Mv1zUObHvXI", "libkernel", 1, "libkernel", sceKernelGetSystemSwVersion);
LIB_FUNCTION("igMefp4SAv0", "libkernel", 1, "libkernel", get_authinfo);
LIB_FUNCTION("G-MYv5erXaU", "libkernel", 1, "libkernel", sceKernelGetAppInfo);
LIB_FUNCTION("PfccT7qURYE", "libkernel", 1, "libkernel", kernel_ioctl);
LIB_FUNCTION("wW+k21cmbwQ", "libkernel", 1, "libkernel", kernel_ioctl);
LIB_FUNCTION("JGfTMBOdUJo", "libkernel", 1, "libkernel", sceKernelGetFsSandboxRandomWord);

View File

@ -36,6 +36,8 @@ struct OrbisWrapperImpl<PS4_SYSV_ABI R (*)(Args...), f> {
#define ORBIS(func) (Libraries::Kernel::OrbisWrapperImpl<decltype(&(func)), func>::wrap)
#define CURRENT_FIRMWARE_VERSION 0x13020011
s32* PS4_SYSV_ABI __Error();
struct SwVersionStruct {
@ -51,6 +53,30 @@ struct AuthInfoData {
u64 ucred[8];
};
struct OrbisKernelTitleWorkaround {
s32 version;
s32 align;
u64 ids[2];
};
struct OrbisKernelAppInfo {
s32 app_id;
s32 mmap_flags;
s32 attribute_exe;
s32 attribute2;
char cusa_name[10];
u8 debug_level;
u8 slv_flags;
u8 mini_app_dmem_flags;
u8 render_mode;
u8 mdbg_out;
u8 required_hdcp_type;
u64 preload_prx_flags;
s32 attribute1;
s32 has_param_sfo;
OrbisKernelTitleWorkaround title_workaround;
};
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Kernel

View File

@ -21,8 +21,22 @@ s32 PS4_SYSV_ABI sceKernelIsNeoMode() {
Common::ElfInfo::Instance().GetPSFAttributes().support_neo_mode;
}
s32 PS4_SYSV_ABI sceKernelHasNeoMode() {
return Config::isNeoModeConsole();
}
s32 PS4_SYSV_ABI sceKernelGetMainSocId() {
// These hardcoded values are based on hardware observations.
// Different models of PS4/PS4 Pro likely return slightly different values.
LOG_DEBUG(Lib_Kernel, "called");
if (Config::isNeoModeConsole()) {
return 0x740f30;
}
return 0x710f10;
}
s32 PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(s32* ver) {
s32 version = Common::ElfInfo::Instance().RawFirmwareVer();
s32 version = Common::ElfInfo::Instance().CompiledSdkVer();
*ver = version;
return (version >= 0) ? ORBIS_OK : ORBIS_KERNEL_ERROR_EINVAL;
}
@ -31,6 +45,11 @@ s32 PS4_SYSV_ABI sceKernelGetCpumode() {
return 0;
}
s32 PS4_SYSV_ABI sceKernelGetCurrentCpu() {
LOG_DEBUG(Lib_Kernel, "called");
return 0;
}
void* PS4_SYSV_ABI sceKernelGetProcParam() {
auto* linker = Common::Singleton<Core::Linker>::Instance();
return linker->GetProcParam();
@ -208,7 +227,10 @@ void RegisterProcess(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("xeu-pV8wkKs", "libkernel", 1, "libkernel", sceKernelIsInSandbox);
LIB_FUNCTION("WB66evu8bsU", "libkernel", 1, "libkernel", sceKernelGetCompiledSdkVersion);
LIB_FUNCTION("WslcK1FQcGI", "libkernel", 1, "libkernel", sceKernelIsNeoMode);
LIB_FUNCTION("rNRtm1uioyY", "libkernel", 1, "libkernel", sceKernelHasNeoMode);
LIB_FUNCTION("0vTn5IDMU9A", "libkernel", 1, "libkernel", sceKernelGetMainSocId);
LIB_FUNCTION("VOx8NGmHXTs", "libkernel", 1, "libkernel", sceKernelGetCpumode);
LIB_FUNCTION("g0VTBxfJyu0", "libkernel", 1, "libkernel", sceKernelGetCurrentCpu);
LIB_FUNCTION("959qrazPIrg", "libkernel", 1, "libkernel", sceKernelGetProcParam);
LIB_FUNCTION("wzvqT4UqKX8", "libkernel", 1, "libkernel", sceKernelLoadStartModule);
LIB_FUNCTION("LwG8g3niqwA", "libkernel", 1, "libkernel", sceKernelDlsym);

View File

@ -354,6 +354,8 @@ void RegisterCond(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("Op8TBGY5KHg", "libkernel", 1, "libkernel", posix_pthread_cond_wait);
LIB_FUNCTION("mkx2fVhNMsg", "libkernel", 1, "libkernel", posix_pthread_cond_broadcast);
LIB_FUNCTION("2MOy+rUfuhQ", "libkernel", 1, "libkernel", posix_pthread_cond_signal);
LIB_FUNCTION("RXXqi4CtF8w", "libkernel", 1, "libkernel", posix_pthread_cond_destroy);
LIB_FUNCTION("27bAgiJmOh0", "libkernel", 1, "libkernel", posix_pthread_cond_timedwait);
LIB_FUNCTION("mKoTx03HRWA", "libkernel", 1, "libkernel", posix_pthread_condattr_init);
LIB_FUNCTION("dJcuQVn6-Iw", "libkernel", 1, "libkernel", posix_pthread_condattr_destroy);

View File

@ -442,6 +442,8 @@ void RegisterMutex(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("ltCfaGr2JGE", "libkernel", 1, "libkernel", posix_pthread_mutex_destroy);
LIB_FUNCTION("dQHWEsJtoE4", "libkernel", 1, "libkernel", posix_pthread_mutexattr_init);
LIB_FUNCTION("mDmgMOGVUqg", "libkernel", 1, "libkernel", posix_pthread_mutexattr_settype);
LIB_FUNCTION("HF7lK46xzjY", "libkernel", 1, "libkernel", posix_pthread_mutexattr_destroy);
LIB_FUNCTION("K-jXhbt2gn4", "libkernel", 1, "libkernel", posix_pthread_mutex_trylock);
// Orbis
LIB_FUNCTION("cmo1RIYva9o", "libkernel", 1, "libkernel", ORBIS(scePthreadMutexInit));

View File

@ -663,6 +663,9 @@ void RegisterThread(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("Z4QosVuAsA0", "libkernel", 1, "libkernel", posix_pthread_once);
LIB_FUNCTION("EotR8a3ASf4", "libkernel", 1, "libkernel", posix_pthread_self);
LIB_FUNCTION("OxhIB8LB-PQ", "libkernel", 1, "libkernel", posix_pthread_create);
LIB_FUNCTION("+U1R4WtXvoc", "libkernel", 1, "libkernel", posix_pthread_detach);
LIB_FUNCTION("7Xl257M4VNI", "libkernel", 1, "libkernel", posix_pthread_equal);
LIB_FUNCTION("h9CcP3J0oVM", "libkernel", 1, "libkernel", posix_pthread_join);
LIB_FUNCTION("Jb2uGFMr688", "libkernel", 1, "libkernel", posix_pthread_getaffinity_np);
LIB_FUNCTION("5KWrg7-ZqvE", "libkernel", 1, "libkernel", posix_pthread_setaffinity_np);
LIB_FUNCTION("3eqs37G74-s", "libkernel", 1, "libkernel", posix_pthread_getthreadid_np);

View File

@ -1285,7 +1285,8 @@ u16 PS4_SYSV_ABI sceNetNtohs(u16 net16) {
int PS4_SYSV_ABI sceNetPoolCreate(const char* name, int size, int flags) {
LOG_ERROR(Lib_Net, "(DUMMY) name = {} size = {} flags = {} ", std::string(name), size, flags);
return ORBIS_OK;
static s32 id = 1;
return id++;
}
int PS4_SYSV_ABI sceNetPoolDestroy() {

View File

@ -0,0 +1,651 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "dimensions.h"
#include <mutex>
#include <thread>
namespace Libraries::Usbd {
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};
DimensionsToypad::DimensionsToypad() {}
void DimensionsToypad::LoadFigure(std::string file_name, u8 pad, u8 index) {
Common::FS::IOFile file(file_name, Common::FS::FileAccessMode::ReadWrite);
std::array<u8, 0x2D * 0x04> data;
ASSERT(file.Read(data) == data.size());
LoadDimensionsFigure(data, std::move(file), pad, index);
}
u32 DimensionsToypad::LoadDimensionsFigure(const std::array<u8, 0x2D * 0x04>& buf,
Common::FS::IOFile file, u8 pad, u8 index) {
std::lock_guard lock(m_dimensions_mutex);
const u32 id = GetFigureId(buf);
DimensionsFigure& figure = GetFigureByIndex(index);
figure.dimFile = std::move(file);
figure.id = id;
figure.pad = pad;
figure.index = index + 1;
figure.data = buf;
// When a figure is added to the toypad, respond to the game with the pad they were added to,
// their index, the direction (0x00 in byte 6 for added) and their UID
std::array<u8, 32> figureChangeResponse = {0x56, 0x0b, figure.pad, 0x00, figure.index,
0x00, buf[0], buf[1], buf[2], buf[4],
buf[5], buf[6], buf[7]};
figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13);
m_figure_added_removed_responses.push(figureChangeResponse);
return id;
}
void DimensionsToypad::RemoveFigure(u8 pad, u8 index, bool fullRemove) {
std::lock_guard lock(m_dimensions_mutex);
DimensionsFigure& figure = GetFigureByIndex(index);
if (figure.index == 255)
return;
// When a figure is removed from the toypad, respond to the game with the pad they were removed
// from, their index, the direction (0x01 in byte 6 for removed) and their UID
if (fullRemove) {
std::array<u8, 32> figureChangeResponse = {
0x56, 0x0b, figure.pad, 0x00, figure.index,
0x01, figure.data[0], figure.data[1], figure.data[2], figure.data[4],
figure.data[5], figure.data[6], figure.data[7]};
figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13);
m_figure_added_removed_responses.push(figureChangeResponse);
figure.Save();
figure.dimFile.Close();
}
figure.index = 255;
figure.pad = 255;
figure.id = 0;
}
void DimensionsToypad::MoveFigure(u8 new_pad, u8 new_index, u8 old_pad, u8 old_index) {
if (old_index == new_index) {
// Don't bother removing and loading again, just send response to the game
CancelRemoveFigure(new_index);
return;
}
// When moving figures between spaces on the toypad, remove any figure from the space they are
// moving to, then remove them from their current space, then load them to the space they are
// moving to
RemoveFigure(new_pad, new_index, true);
DimensionsFigure& figure = GetFigureByIndex(old_index);
const std::array<u8, 0x2D * 0x04> data = figure.data;
Common::FS::IOFile inFile = std::move(figure.dimFile);
RemoveFigure(old_pad, old_index, false);
LoadDimensionsFigure(data, std::move(inFile), new_pad, new_index);
}
void DimensionsToypad::TempRemoveFigure(u8 index) {
std::lock_guard lock(m_dimensions_mutex);
DimensionsFigure& figure = GetFigureByIndex(index);
if (figure.index == 255)
return;
// Send a response to the game that the figure has been "Picked up" from existing slot,
// until either the movement is cancelled, or user chooses a space to move to
std::array<u8, 32> figureChangeResponse = {
0x56, 0x0b, figure.pad, 0x00, figure.index,
0x01, figure.data[0], figure.data[1], figure.data[2], figure.data[4],
figure.data[5], figure.data[6], figure.data[7]};
figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13);
m_figure_added_removed_responses.push(figureChangeResponse);
}
void DimensionsToypad::CancelRemoveFigure(u8 index) {
std::lock_guard lock(m_dimensions_mutex);
DimensionsFigure& figure = GetFigureByIndex(index);
if (figure.index == 255)
return;
// Cancel the previous movement of the figure
std::array<u8, 32> figureChangeResponse = {
0x56, 0x0b, figure.pad, 0x00, figure.index,
0x00, figure.data[0], figure.data[1], figure.data[2], figure.data[4],
figure.data[5], figure.data[6], figure.data[7]};
figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13);
m_figure_added_removed_responses.push(figureChangeResponse);
}
u8 DimensionsToypad::GenerateChecksum(const std::array<u8, 32>& data, u32 num_of_bytes) {
int checksum = 0;
ASSERT(num_of_bytes <= data.size());
for (u8 i = 0; i < num_of_bytes; i++) {
checksum += data[i];
}
return (checksum & 0xFF);
}
void DimensionsToypad::GetBlankResponse(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] = GenerateChecksum(reply_buf, 3);
}
void DimensionsToypad::GenerateRandomNumber(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 = (u32&)value[0];
// Confirmation is the second 4 bytes (big endian) of the decrypted payload
// const u32 conf = (u32be&)value[4];
// Initialize rng using the seed from decrypted payload
InitializeRNG(seed);
// Encrypt 8 bytes, first 4 bytes is the decrypted confirmation from payload, 2nd 4 bytes are
// blank
std::array<u8, 8> value_to_encrypt = {value[4], value[5], value[6], value[7], 0, 0, 0, 0};
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] = GenerateChecksum(reply_buf, 11);
}
void DimensionsToypad::InitializeRNG(u32 seed) {
m_random_a = 0xF1EA5EED;
m_random_b = seed;
m_random_c = seed;
m_random_d = seed;
for (int i = 0; i < 42; i++) {
GetNext();
}
}
u32 DimensionsToypad::GetNext() {
const u32 e = m_random_a - std::rotl(m_random_b, 21);
m_random_a = m_random_b ^ std::rotl(m_random_c, 19);
m_random_b = m_random_c + std::rotl(m_random_d, 6);
m_random_c = m_random_d + e;
m_random_d = e + m_random_a;
return m_random_d;
}
std::array<u8, 8> DimensionsToypad::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 = u32(buf[0]) | (u32(buf[1]) << 8) | (u32(buf[2]) << 16) | (u32(buf[3]) << 24);
u32 data_two = u32(buf[4]) | (u32(buf[5]) << 8) | (u32(buf[6]) << 16) | (u32(buf[7]) << 24);
// 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 = u32(key.value()[0]) | (u32(key.value()[1]) << 8) | (u32(key.value()[2]) << 16) |
(u32(key.value()[3]) << 24);
key_two = u32(key.value()[4]) | (u32(key.value()[5]) << 8) | (u32(key.value()[6]) << 16) |
(u32(key.value()[7]) << 24);
key_three = u32(key.value()[8]) | (u32(key.value()[9]) << 8) |
(u32(key.value()[10]) << 16) | (u32(key.value()[11]) << 24);
key_four = u32(key.value()[12]) | (u32(key.value()[13]) << 8) |
(u32(key.value()[14]) << 16) | (u32(key.value()[15]) << 24);
} else {
key_one = u32(COMMAND_KEY[0]) | (u32(COMMAND_KEY[1]) << 8) | (u32(COMMAND_KEY[2]) << 16) |
(u32(COMMAND_KEY[3]) << 24);
key_two = u32(COMMAND_KEY[4]) | (u32(COMMAND_KEY[5]) << 8) | (u32(COMMAND_KEY[6]) << 16) |
(u32(COMMAND_KEY[7]) << 24);
key_three = u32(COMMAND_KEY[8]) | (u32(COMMAND_KEY[9]) << 8) |
(u32(COMMAND_KEY[10]) << 16) | (u32(COMMAND_KEY[11]) << 24);
key_four = u32(COMMAND_KEY[12]) | (u32(COMMAND_KEY[13]) << 8) |
(u32(COMMAND_KEY[14]) << 16) | (u32(COMMAND_KEY[15]) << 24);
}
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;
}
ASSERT_MSG(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> DimensionsToypad::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 = u32(buf[0]) | (u32(buf[1]) << 8) | (u32(buf[2]) << 16) | (u32(buf[3]) << 24);
u32 data_two = u32(buf[4]) | (u32(buf[5]) << 8) | (u32(buf[6]) << 16) | (u32(buf[7]) << 24);
// 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 = u32(key.value()[0]) | (u32(key.value()[1]) << 8) | (u32(key.value()[2]) << 16) |
(u32(key.value()[3]) << 24);
key_two = u32(key.value()[4]) | (u32(key.value()[5]) << 8) | (u32(key.value()[6]) << 16) |
(u32(key.value()[7]) << 24);
key_three = u32(key.value()[8]) | (u32(key.value()[9]) << 8) |
(u32(key.value()[10]) << 16) | (u32(key.value()[11]) << 24);
key_four = u32(key.value()[12]) | (u32(key.value()[13]) << 8) |
(u32(key.value()[14]) << 16) | (u32(key.value()[15]) << 24);
} else {
key_one = u32(COMMAND_KEY[0]) | (u32(COMMAND_KEY[1]) << 8) | (u32(COMMAND_KEY[2]) << 16) |
(u32(COMMAND_KEY[3]) << 24);
key_two = u32(COMMAND_KEY[4]) | (u32(COMMAND_KEY[5]) << 8) | (u32(COMMAND_KEY[6]) << 16) |
(u32(COMMAND_KEY[7]) << 24);
key_three = u32(COMMAND_KEY[8]) | (u32(COMMAND_KEY[9]) << 8) |
(u32(COMMAND_KEY[10]) << 16) | (u32(COMMAND_KEY[11]) << 24);
key_four = u32(COMMAND_KEY[12]) | (u32(COMMAND_KEY[13]) << 8) |
(u32(COMMAND_KEY[14]) << 16) | (u32(COMMAND_KEY[15]) << 24);
}
u32 sum = 0;
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));
}
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> DimensionsToypad::GenerateFigureKey(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]};
u32 scrambleA = Scramble(uid, 3);
u32 scrambleB = Scramble(uid, 4);
u32 scrambleC = Scramble(uid, 5);
u32 scrambleD = Scramble(uid, 6);
return {
u8((scrambleA >> 24) & 0xFF), u8((scrambleA >> 16) & 0xFF), u8((scrambleA >> 8) & 0xFF),
u8(scrambleA & 0xFF), u8((scrambleB >> 24) & 0xFF), u8((scrambleB >> 16) & 0xFF),
u8((scrambleB >> 8) & 0xFF), u8(scrambleB & 0xFF), u8((scrambleC >> 24) & 0xFF),
u8((scrambleC >> 16) & 0xFF), u8((scrambleC >> 8) & 0xFF), u8(scrambleC & 0xFF),
u8((scrambleD >> 24) & 0xFF), u8((scrambleD >> 16) & 0xFF), u8((scrambleD >> 8) & 0xFF),
u8(scrambleD & 0xFF)};
}
u32 DimensionsToypad::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);
}
to_scramble[(count * 4) - 1] = 0xaa;
std::array<u8, 4> randomized = DimensionsRandomize(to_scramble, count);
return (u32(randomized[0]) << 24) | (u32(randomized[1]) << 16) | (u32(randomized[2]) << 8) |
u32(randomized[3]);
}
std::array<u8, 4> DimensionsToypad::PWDGenerate(const std::array<u8, 7>& uid) {
std::vector<u8> pwdCalc = {PWD_CONSTANT.begin(), PWD_CONSTANT.end() - 1};
for (u8 i = 0; i < uid.size(); i++) {
pwdCalc.insert(pwdCalc.begin() + i, uid[i]);
}
return DimensionsRandomize(pwdCalc, 8);
}
std::array<u8, 4> DimensionsToypad::DimensionsRandomize(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 = u32(key[i * 4]) | (u32(key[(i * 4) + 1]) << 8) |
(u32(key[(i * 4) + 2]) << 16) | (u32(key[(i * 4) + 3]) << 24);
scrambled = b + v4 + v5 - scrambled;
}
return {u8(scrambled & 0xFF), u8(scrambled >> 8 & 0xFF), u8(scrambled >> 16 & 0xFF),
u8(scrambled >> 24 & 0xFF)};
}
u32 DimensionsToypad::GetFigureId(const std::array<u8, 0x2D * 0x04>& buf) {
const std::array<u8, 16> figure_key = GenerateFigureKey(buf);
const std::array<u8, 8> decrypted = Decrypt(&buf[36 * 4], figure_key);
const u32 fig_num = u32(decrypted[0]) | (u32(decrypted[1]) << 8) | (u32(decrypted[2]) << 16) |
(u32(decrypted[3]) << 24);
// 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 u32(buf[36 * 4]) | (u32(buf[(36 * 4) + 1]) << 8) | (u32(buf[(36 * 4) + 2]) << 16) |
(u32(buf[(36 * 4) + 3]) << 24);
}
DimensionsFigure& DimensionsToypad::GetFigureByIndex(u8 index) {
return m_figures[index];
}
void DimensionsToypad::RandomUID(u8* uid_buffer) {
uid_buffer[0] = 0x04;
uid_buffer[7] = 0x80;
for (u8 i = 1; i < 7; i++) {
u8 random = rand() % 255;
uid_buffer[i] = random;
}
}
void DimensionsToypad::GetChallengeResponse(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 = GetNext();
// Encrypt an 8 byte array, first 4 bytes are the next random number (little endian)
// followed by the confirmation from the decrypted payload
std::array<u8, 8> value_to_encrypt = {u8(next_random & 0xFF),
u8((next_random >> 8) & 0xFF),
u8((next_random >> 16) & 0xFF),
u8((next_random >> 24) & 0xFF),
value[0],
value[1],
value[2],
value[3]};
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] = GenerateChecksum(reply_buf, 11);
}
void DimensionsToypad::QueryBlock(u8 index, u8 page, std::array<u8, 32>& reply_buf, u8 sequence) {
std::lock_guard lock(m_dimensions_mutex);
reply_buf[0] = 0x55;
reply_buf[1] = 0x12;
reply_buf[2] = sequence;
reply_buf[3] = 0x00;
// Index from game begins at 1 rather than 0, so minus 1 here
if (const u8 figure_index = index - 1; figure_index < MAX_DIMENSIONS_FIGURES) {
const DimensionsFigure& figure = GetFigureByIndex(figure_index);
// 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] = GenerateChecksum(reply_buf, 20);
}
void DimensionsToypad::WriteBlock(u8 index, u8 page, const u8* to_write_buf,
std::array<u8, 32>& reply_buf, u8 sequence) {
std::lock_guard lock(m_dimensions_mutex);
reply_buf[0] = 0x55;
reply_buf[1] = 0x02;
reply_buf[2] = sequence;
reply_buf[3] = 0x00;
// Index from game begins at 1 rather than 0, so minus 1 here
if (const u8 figure_index = index - 1; figure_index < MAX_DIMENSIONS_FIGURES) {
DimensionsFigure& figure = GetFigureByIndex(figure_index);
// 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 = u32(to_write_buf[0]) | (u32(to_write_buf[1]) << 8) |
(u32(to_write_buf[2]) << 16) | (u32(to_write_buf[3]) << 24);
}
std::memcpy(figure.data.data() + (page * 4), to_write_buf, 4);
figure.Save();
}
}
reply_buf[4] = GenerateChecksum(reply_buf, 4);
}
void DimensionsToypad::GetModel(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);
std::array<u8, 8> value_to_encrypt = {};
// Response is the figure's id (little endian) followed by the confirmation from payload
// Index from game begins at 1 rather than 0, so minus 1 here
if (const u8 figure_index = index - 1; figure_index < MAX_DIMENSIONS_FIGURES) {
const DimensionsFigure& figure = GetFigureByIndex(figure_index);
value_to_encrypt = {u8(figure.id & 0xFF),
u8((figure.id >> 8) & 0xFF),
u8((figure.id >> 16) & 0xFF),
u8((figure.id >> 24) & 0xFF),
value[4],
value[5],
value[6],
value[7]};
}
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] = GenerateChecksum(reply_buf, 12);
}
std::optional<std::array<u8, 32>> DimensionsToypad::PopAddedRemovedResponse() {
std::lock_guard lock(m_dimensions_mutex);
if (m_figure_added_removed_responses.empty()) {
return std::nullopt;
}
std::array<u8, 32> response = m_figure_added_removed_responses.front();
m_figure_added_removed_responses.pop();
return response;
}
libusb_endpoint_descriptor* DimensionsBackend::FillEndpointDescriptorPair() {
return m_endpoint_descriptors.data();
}
libusb_interface_descriptor* DimensionsBackend::FillInterfaceDescriptor(
libusb_endpoint_descriptor* descs) {
m_interface_descriptors[0].endpoint = descs;
return m_interface_descriptors.data();
}
libusb_config_descriptor* DimensionsBackend::FillConfigDescriptor(libusb_interface* inter) {
m_config_descriptors[0].interface = inter;
return m_config_descriptors.data();
}
libusb_device_descriptor* DimensionsBackend::FillDeviceDescriptor() {
return m_device_descriptors.data();
}
libusb_transfer_status DimensionsBackend::HandleAsyncTransfer(libusb_transfer* transfer) {
ASSERT(transfer->length == 32);
switch (transfer->endpoint) {
case 0x81: {
// Read Endpoint, wait to respond with either an added/removed figure response, or a queued
// response from a previous write
bool responded = false;
while (!responded) {
std::lock_guard lock(m_query_mutex);
std::optional<std::array<u8, 32>> response =
m_dimensions_toypad->PopAddedRemovedResponse();
if (response) {
std::memcpy(transfer->buffer, response.value().data(), 0x20);
transfer->length = 32;
responded = true;
} else if (!m_queries.empty()) {
std::memcpy(transfer->buffer, m_queries.front().data(), 0x20);
transfer->length = 32;
m_queries.pop();
responded = true;
}
}
break;
}
case 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 = transfer->buffer[2];
const u8 sequence = transfer->buffer[3];
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
m_dimensions_toypad->GenerateRandomNumber(&transfer->buffer[4], sequence, q_result);
break;
}
case 0xB3: // Challenge
{
// Get the next number in the sequence based on the RNG from 0xB1 command
m_dimensions_toypad->GetChallengeResponse(&transfer->buffer[4], sequence, q_result);
break;
}
case 0xC0: // Color
case 0xC1: // Get Pad Color
case 0xC2: // Fade
case 0xC3: // Flash
case 0xC4: // Fade Random
case 0xC6: // Fade All
case 0xC7: // Flash All
case 0xC8: // Color All
{
// Send a blank response to acknowledge color has been sent to toypad
m_dimensions_toypad->GetBlankResponse(0x01, sequence, q_result);
break;
}
case 0xD2: // Read
{
// Read 4 pages from the figure at index (buf[4]), starting with page buf[5]
m_dimensions_toypad->QueryBlock(transfer->buffer[4], transfer->buffer[5], q_result,
sequence);
break;
}
case 0xD3: // Write
{
// Write 4 bytes to page buf[5] to the figure at index buf[4]
m_dimensions_toypad->WriteBlock(transfer->buffer[4], transfer->buffer[5],
&transfer->buffer[6], q_result, sequence);
break;
}
case 0xD4: // Model
{
// Get the model id of the figure at index buf[4]
m_dimensions_toypad->GetModel(&transfer->buffer[4], sequence, q_result);
break;
}
case 0xD0: // Tag List
case 0xE1: // PWD
case 0xE5: // Active
case 0xFF: // LEDS Query
{
// Further investigation required
LOG_ERROR(Lib_Usbd, "Unimplemented LD Function: {:x}", command);
break;
}
default: {
LOG_ERROR(Lib_Usbd, "Unknown LD Function: {:x}", command);
break;
}
}
std::lock_guard lock(m_query_mutex);
m_queries.push(q_result);
break;
}
default:
break;
}
return LIBUSB_TRANSFER_COMPLETED;
}
s32 DimensionsBackend::SubmitTransfer(libusb_transfer* transfer) {
if (transfer->endpoint == 0x01) {
std::thread write_thread([this, transfer] {
HandleAsyncTransfer(transfer);
const u8 flags = transfer->flags;
transfer->status = LIBUSB_TRANSFER_COMPLETED;
transfer->actual_length = transfer->length;
if (transfer->callback) {
transfer->callback(transfer);
}
if (flags & LIBUSB_TRANSFER_FREE_TRANSFER) {
libusb_free_transfer(transfer);
}
});
write_thread.detach();
return LIBUSB_SUCCESS;
}
return UsbEmulatedBackend::SubmitTransfer(transfer);
}
void DimensionsFigure::Save() {
if (!dimFile.IsOpen())
return;
dimFile.Seek(0);
dimFile.Write(data);
}
} // namespace Libraries::Usbd

View File

@ -0,0 +1,118 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <queue>
#include "common/io_file.h"
#include "core/libraries/usbd/usb_backend.h"
namespace Libraries::Usbd {
constexpr u8 DIMEMSIONS_BLOCK_COUNT = 0x2D;
constexpr u8 DIMENSIONS_BLOCK_SIZE = 0x04;
constexpr u8 DIMENSIONS_FIGURE_SIZE = DIMEMSIONS_BLOCK_COUNT * DIMENSIONS_BLOCK_SIZE;
constexpr u8 MAX_DIMENSIONS_FIGURES = 7;
struct DimensionsFigure final {
Common::FS::IOFile dimFile;
std::array<u8, DIMENSIONS_FIGURE_SIZE> data{};
u8 index = 255;
u8 pad = 255;
u32 id = 0;
void Save();
};
class DimensionsToypad final : public UsbEmulatedImpl {
public:
DimensionsToypad();
~DimensionsToypad() override = default;
static void GetBlankResponse(u8 type, u8 sequence, std::array<u8, 32>& reply_buf);
void GenerateRandomNumber(const u8* buf, u8 sequence, std::array<u8, 32>& reply_buf);
void InitializeRNG(u32 seed);
void GetChallengeResponse(const u8* buf, u8 sequence, std::array<u8, 32>& reply_buf);
void QueryBlock(u8 index, u8 page, std::array<u8, 32>& reply_buf, u8 sequence);
void WriteBlock(u8 index, u8 page, const u8* to_write_buf, std::array<u8, 32>& reply_buf,
u8 sequence);
void GetModel(const u8* buf, u8 sequence, std::array<u8, 32>& reply_buf);
std::optional<std::array<u8, 32>> PopAddedRemovedResponse();
void LoadFigure(std::string file_name, u8 pad, u8 slot) override;
void RemoveFigure(u8 pad, u8 slot, bool full_remove) override;
void MoveFigure(u8 new_pad, u8 new_index, u8 old_pad, u8 old_index) override;
void TempRemoveFigure(u8 index) override;
void CancelRemoveFigure(u8 index) override;
u32 LoadDimensionsFigure(const std::array<u8, 0x2D * 0x04>& buf, Common::FS::IOFile file,
u8 pad, u8 index);
protected:
std::mutex m_dimensions_mutex;
std::array<DimensionsFigure, MAX_DIMENSIONS_FIGURES> m_figures{};
private:
static void RandomUID(u8* uid_buffer);
static u8 GenerateChecksum(const std::array<u8, 32>& data, u32 num_of_bytes);
static std::array<u8, 8> Decrypt(const u8* buf, std::optional<std::array<u8, 16>> key);
static std::array<u8, 8> Encrypt(const u8* buf, std::optional<std::array<u8, 16>> key);
static std::array<u8, 16> GenerateFigureKey(const std::array<u8, 0x2D * 0x04>& buf);
static u32 Scramble(const std::array<u8, 7>& uid, u8 count);
static std::array<u8, 4> PWDGenerate(const std::array<u8, 7>& uid);
static std::array<u8, 4> DimensionsRandomize(const std::vector<u8>& key, u8 count);
static u32 GetFigureId(const std::array<u8, 0x2D * 0x04>& buf);
u32 GetNext();
DimensionsFigure& GetFigureByIndex(u8 index);
u32 m_random_a{};
u32 m_random_b{};
u32 m_random_c{};
u32 m_random_d{};
u8 m_figure_order = 0;
std::queue<std::array<u8, 32>> m_figure_added_removed_responses;
};
class DimensionsBackend final : public UsbEmulatedBackend {
protected:
libusb_endpoint_descriptor* FillEndpointDescriptorPair() override;
libusb_interface_descriptor* FillInterfaceDescriptor(
libusb_endpoint_descriptor* descs) override;
libusb_config_descriptor* FillConfigDescriptor(libusb_interface* inter) override;
libusb_device_descriptor* FillDeviceDescriptor() override;
s32 GetMaxPacketSize(libusb_device* dev, u8 endpoint) override {
return 32;
}
s32 SubmitTransfer(libusb_transfer* transfer) override;
libusb_transfer_status HandleAsyncTransfer(libusb_transfer* transfer) override;
std::shared_ptr<UsbEmulatedImpl> GetImplRef() override {
return m_dimensions_toypad;
}
std::mutex m_query_mutex;
std::queue<std::array<u8, 32>> m_queries;
private:
std::shared_ptr<DimensionsToypad> m_dimensions_toypad = std::make_shared<DimensionsToypad>();
std::array<u8, 9> m_endpoint_out_extra = {0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22, 0x1d, 0x00};
std::vector<libusb_endpoint_descriptor> m_endpoint_descriptors = {
{0x7, 0x5, 0x81, 0x3, 0x20, 0x1, 0x0, 0x0}, {0x7, 0x5, 0x1, 0x3, 0x20, 0x1, 0x0, 0x0}};
std::vector<libusb_interface_descriptor> m_interface_descriptors = {
{0x9, 0x4, 0x0, 0x0, 0x2, 0x3, 0x0, 0x0, 0x0}};
std::vector<libusb_config_descriptor> m_config_descriptors = {
{0x9, 0x2, 0x29, 0x1, 0x1, 0x0, 0x80, 0xFA}};
std::vector<libusb_device_descriptor> m_device_descriptors = {
{0x12, 0x1, 0x200, 0x0, 0x0, 0x0, 0x20, 0x0E6F, 0x0241, 0x200, 0x1, 0x2, 0x0, 0x1}};
};
} // namespace Libraries::Usbd

View File

@ -0,0 +1,392 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "infinity.h"
#include <mutex>
namespace Libraries::Usbd {
InfinityBase::InfinityBase() {}
void InfinityBase::LoadFigure(std::string file_name, u8 pad, u8 slot) {
Common::FS::IOFile file(file_name, Common::FS::FileAccessMode::ReadWrite);
std::array<u8, INFINITY_FIGURE_SIZE> data;
ASSERT(file.Read(data) == data.size());
LoadInfinityFigure(data, std::move(file), slot);
}
void InfinityBase::RemoveFigure(u8 pad, u8 slot, bool full_remove) {
std::lock_guard lock(infinity_mutex);
InfinityFigure& figure = infinity_figures[slot];
if (!figure.present) {
return;
}
slot = DeriveFigurePosition(slot);
if (slot == 0) {
return;
}
figure.present = false;
std::array<u8, 32> figure_change_response = {0xab, 0x04, slot, 0x09, figure.order_added, 0x01};
figure_change_response[6] = GenerateChecksum(figure_change_response, 6);
m_figure_added_removed_responses.push(figure_change_response);
figure.Save();
figure.infFile.Close();
}
void InfinityBase::LoadInfinityFigure(const std::array<u8, INFINITY_FIGURE_SIZE>& buf,
Common::FS::IOFile file, u8 position) {
std::lock_guard lock(infinity_mutex);
u8 order_added;
InfinityFigure& figure = infinity_figures[position];
figure.infFile = std::move(file);
memcpy(figure.data.data(), buf.data(), figure.data.size());
figure.present = true;
if (figure.order_added == 255) {
figure.order_added = m_figure_order;
m_figure_order++;
}
order_added = figure.order_added;
position = DeriveFigurePosition(position);
if (position == 0) {
return;
}
std::array<u8, 32> figure_change_response = {0xab, 0x04, position, 0x09, order_added, 0x00};
figure_change_response[6] = GenerateChecksum(figure_change_response, 6);
m_figure_added_removed_responses.push(figure_change_response);
}
void InfinityBase::GetBlankResponse(u8 sequence, std::array<u8, 32>& reply_buf) {
reply_buf[0] = 0xaa;
reply_buf[1] = 0x01;
reply_buf[2] = sequence;
reply_buf[3] = GenerateChecksum(reply_buf, 3);
}
void InfinityBase::DescrambleAndSeed(u8* buf, u8 sequence, std::array<u8, 32>& reply_buf) {
u64 value = u64(buf[4]) << 56 | u64(buf[5]) << 48 | u64(buf[6]) << 40 | u64(buf[7]) << 32 |
u64(buf[8]) << 24 | u64(buf[9]) << 16 | u64(buf[10]) << 8 | u64(buf[11]);
u32 seed = Descramble(value);
GenerateSeed(seed);
GetBlankResponse(sequence, reply_buf);
}
void InfinityBase::GetNextAndScramble(u8 sequence, std::array<u8, 32>& reply_buf) {
const u32 next_random = GetNext();
const u64 scrambled_next_random = Scramble(next_random, 0);
reply_buf = {0xAA, 0x09, sequence};
reply_buf[3] = u8((scrambled_next_random >> 56) & 0xFF);
reply_buf[4] = u8((scrambled_next_random >> 48) & 0xFF);
reply_buf[5] = u8((scrambled_next_random >> 40) & 0xFF);
reply_buf[6] = u8((scrambled_next_random >> 32) & 0xFF);
reply_buf[7] = u8((scrambled_next_random >> 24) & 0xFF);
reply_buf[8] = u8((scrambled_next_random >> 16) & 0xFF);
reply_buf[9] = u8((scrambled_next_random >> 8) & 0xFF);
reply_buf[10] = u8(scrambled_next_random & 0xFF);
reply_buf[11] = GenerateChecksum(reply_buf, 11);
}
void InfinityBase::GetPresentFigures(u8 sequence, std::array<u8, 32>& reply_buf) {
int x = 3;
for (u8 i = 0; i < infinity_figures.size(); i++) {
u8 slot = i == 0 ? 0x10 : (i < 4) ? 0x20 : 0x30;
if (infinity_figures[i].present) {
reply_buf[x] = slot + infinity_figures[i].order_added;
reply_buf[x + 1] = 0x09;
x += 2;
}
}
reply_buf[0] = 0xaa;
reply_buf[1] = x - 2;
reply_buf[2] = sequence;
reply_buf[x] = GenerateChecksum(reply_buf, x);
}
void InfinityBase::QueryBlock(u8 fig_num, u8 block, std::array<u8, 32>& reply_buf, u8 sequence) {
std::lock_guard lock(infinity_mutex);
InfinityFigure& figure = GetFigureByOrder(fig_num);
reply_buf[0] = 0xaa;
reply_buf[1] = 0x12;
reply_buf[2] = sequence;
reply_buf[3] = 0x00;
const u8 file_block = (block == 0) ? 1 : (block * 4);
if (figure.present && file_block < 20) {
memcpy(&reply_buf[4], figure.data.data() + (16 * file_block), 16);
}
reply_buf[20] = GenerateChecksum(reply_buf, 20);
}
void InfinityBase::WriteBlock(u8 fig_num, u8 block, const u8* to_write_buf,
std::array<u8, 32>& reply_buf, u8 sequence) {
std::lock_guard lock(infinity_mutex);
InfinityFigure& figure = GetFigureByOrder(fig_num);
reply_buf[0] = 0xaa;
reply_buf[1] = 0x02;
reply_buf[2] = sequence;
reply_buf[3] = 0x00;
const u8 file_block = (block == 0) ? 1 : (block * 4);
if (figure.present && file_block < 20) {
memcpy(figure.data.data() + (file_block * 16), to_write_buf, 16);
figure.Save();
}
reply_buf[4] = GenerateChecksum(reply_buf, 4);
}
void InfinityBase::GetFigureIdentifier(u8 fig_num, u8 sequence, std::array<u8, 32>& reply_buf) {
std::lock_guard lock(infinity_mutex);
InfinityFigure& figure = GetFigureByOrder(fig_num);
reply_buf[0] = 0xaa;
reply_buf[1] = 0x09;
reply_buf[2] = sequence;
reply_buf[3] = 0x00;
if (figure.present) {
memcpy(&reply_buf[4], figure.data.data(), 7);
}
reply_buf[11] = GenerateChecksum(reply_buf, 11);
}
std::optional<std::array<u8, 32>> InfinityBase::PopAddedRemovedResponse() {
if (m_figure_added_removed_responses.empty())
return std::nullopt;
std::array<u8, 32> response = m_figure_added_removed_responses.front();
m_figure_added_removed_responses.pop();
return response;
}
u8 InfinityBase::GenerateChecksum(const std::array<u8, 32>& data, int num_of_bytes) const {
int checksum = 0;
for (int i = 0; i < num_of_bytes; i++) {
checksum += data[i];
}
return (checksum & 0xFF);
}
u32 InfinityBase::Descramble(u64 num_to_descramble) {
u64 mask = 0x8E55AA1B3999E8AA;
u32 ret = 0;
for (int i = 0; i < 64; i++) {
if (mask & 0x8000000000000000) {
ret = (ret << 1) | (num_to_descramble & 0x01);
}
num_to_descramble >>= 1;
mask <<= 1;
}
return ret;
}
u64 InfinityBase::Scramble(u32 num_to_scramble, u32 garbage) {
u64 mask = 0x8E55AA1B3999E8AA;
u64 ret = 0;
for (int i = 0; i < 64; i++) {
ret <<= 1;
if ((mask & 1) != 0) {
ret |= (num_to_scramble & 1);
num_to_scramble >>= 1;
} else {
ret |= (garbage & 1);
garbage >>= 1;
}
mask >>= 1;
}
return ret;
}
void InfinityBase::GenerateSeed(u32 seed) {
random_a = 0xF1EA5EED;
random_b = seed;
random_c = seed;
random_d = seed;
for (int i = 0; i < 23; i++) {
GetNext();
}
}
u32 InfinityBase::GetNext() {
u32 a = random_a;
u32 b = random_b;
u32 c = random_c;
u32 ret = std::rotl(random_b, 27);
const u32 temp = (a + ((ret ^ 0xFFFFFFFF) + 1));
b ^= std::rotl(c, 17);
a = random_d;
c += a;
ret = b + temp;
a += temp;
random_c = a;
random_a = b;
random_b = c;
random_d = ret;
return ret;
}
InfinityFigure& InfinityBase::GetFigureByOrder(u8 order_added) {
for (u8 i = 0; i < infinity_figures.size(); i++) {
if (infinity_figures[i].order_added == order_added) {
return infinity_figures[i];
}
}
return infinity_figures[0];
}
u8 InfinityBase::DeriveFigurePosition(u8 position) {
switch (position) {
case 0:
case 1:
case 2:
return 1;
case 3:
case 4:
case 5:
return 2;
case 6:
case 7:
case 8:
return 3;
default:
return 0;
}
}
libusb_endpoint_descriptor* InfinityBackend::FillEndpointDescriptorPair() {
return m_endpoint_descriptors.data();
}
libusb_interface_descriptor* InfinityBackend::FillInterfaceDescriptor(
libusb_endpoint_descriptor* descs) {
m_interface_descriptors[0].endpoint = descs;
return m_interface_descriptors.data();
}
libusb_config_descriptor* InfinityBackend::FillConfigDescriptor(libusb_interface* inter) {
m_config_descriptors[0].interface = inter;
return m_config_descriptors.data();
}
libusb_device_descriptor* InfinityBackend::FillDeviceDescriptor() {
return m_device_descriptors.data();
}
libusb_transfer_status InfinityBackend::HandleAsyncTransfer(libusb_transfer* transfer) {
switch (transfer->endpoint) {
case 0x81: {
// Respond after FF command
std::optional<std::array<u8, 32>> response = m_infinity_base->PopAddedRemovedResponse();
if (response) {
memcpy(transfer->buffer, response.value().data(), 0x20);
} else if (!m_queries.empty()) {
memcpy(transfer->buffer, m_queries.front().data(), 0x20);
m_queries.pop();
}
break;
}
case 0x01: {
const u8 command = transfer->buffer[2];
const u8 sequence = transfer->buffer[3];
LOG_INFO(Lib_Usbd, "Infinity Backend Transfer command: {:x}", command);
std::array<u8, 32> q_result{};
switch (command) {
case 0x80: {
q_result = {0xaa, 0x15, 0x00, 0x00, 0x0f, 0x01, 0x00, 0x03, 0x02, 0x09, 0x09, 0x43,
0x20, 0x32, 0x62, 0x36, 0x36, 0x4b, 0x34, 0x99, 0x67, 0x31, 0x93, 0x8c};
break;
}
case 0x81: {
// Initiate Challenge
m_infinity_base->DescrambleAndSeed(transfer->buffer, sequence, q_result);
break;
}
case 0x83: {
// Challenge Response
m_infinity_base->GetNextAndScramble(sequence, q_result);
break;
}
case 0x90:
case 0x92:
case 0x93:
case 0x95:
case 0x96: {
// Color commands
m_infinity_base->GetBlankResponse(sequence, q_result);
break;
}
case 0xA1: {
// Get Present Figures
m_infinity_base->GetPresentFigures(sequence, q_result);
break;
}
case 0xA2: {
// Read Block from Figure
m_infinity_base->QueryBlock(transfer->buffer[4], transfer->buffer[5], q_result,
sequence);
break;
}
case 0xA3: {
// Write block to figure
m_infinity_base->WriteBlock(transfer->buffer[4], transfer->buffer[5],
&transfer->buffer[7], q_result, sequence);
break;
}
case 0xB4: {
// Get figure ID
m_infinity_base->GetFigureIdentifier(transfer->buffer[4], sequence, q_result);
break;
}
case 0xB5: {
// Get status?
m_infinity_base->GetBlankResponse(sequence, q_result);
break;
}
default:
LOG_ERROR(Lib_Usbd, "Unhandled Infinity Query: {}", command);
break;
}
m_queries.push(q_result);
break;
}
default:
LOG_ERROR(Lib_Usbd, "Unhandled Infinity Endpoint: {}", transfer->endpoint);
break;
}
return LIBUSB_TRANSFER_COMPLETED;
}
void InfinityFigure::Save() {
if (!infFile.IsOpen())
return;
infFile.Seek(0);
infFile.Write(data);
}
} // namespace Libraries::Usbd

View File

@ -0,0 +1,112 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <queue>
#include "common/io_file.h"
#include "core/libraries/usbd/usb_backend.h"
namespace Libraries::Usbd {
constexpr u16 INFINITY_BLOCK_COUNT = 0x14;
constexpr u16 INFINITY_BLOCK_SIZE = 0x10;
constexpr u16 INFINITY_FIGURE_SIZE = INFINITY_BLOCK_COUNT * INFINITY_BLOCK_SIZE;
constexpr u8 MAX_INFINITY_FIGURES = 9;
struct InfinityFigure final {
Common::FS::IOFile infFile;
std::array<u8, INFINITY_FIGURE_SIZE> data{};
bool present = false;
u8 order_added = 255;
void Save();
};
class InfinityBase final : public UsbEmulatedImpl {
public:
InfinityBase();
~InfinityBase() override = default;
void GetBlankResponse(u8 sequence, std::array<u8, 32>& reply_buf);
void DescrambleAndSeed(u8* buf, u8 sequence, std::array<u8, 32>& reply_buf);
void GetNextAndScramble(u8 sequence, std::array<u8, 32>& reply_buf);
void GetPresentFigures(u8 sequence, std::array<u8, 32>& reply_buf);
void QueryBlock(u8 fig_num, u8 block, std::array<u8, 32>& reply_buf, u8 sequence);
void WriteBlock(u8 fig_num, u8 block, const u8* to_write_buf, std::array<u8, 32>& reply_buf,
u8 sequence);
void GetFigureIdentifier(u8 fig_num, u8 sequence, std::array<u8, 32>& reply_buf);
std::optional<std::array<u8, 32>> PopAddedRemovedResponse();
void LoadFigure(std::string file_name, u8 pad, u8 slot) override;
void RemoveFigure(u8 pad, u8 slot, bool full_remove) override;
void MoveFigure(u8 new_pad, u8 new_index, u8 old_pad, u8 old_index) override {}
void TempRemoveFigure(u8 index) override {}
void CancelRemoveFigure(u8 index) override {}
void LoadInfinityFigure(const std::array<u8, 0x14 * 0x10>& buf, Common::FS::IOFile file,
u8 position);
protected:
std::mutex infinity_mutex;
std::array<InfinityFigure, MAX_INFINITY_FIGURES> infinity_figures;
private:
u8 GenerateChecksum(const std::array<u8, 32>& data, int num_of_bytes) const;
u32 Descramble(u64 num_to_descramble);
u64 Scramble(u32 num_to_scramble, u32 garbage);
void GenerateSeed(u32 seed);
u32 GetNext();
InfinityFigure& GetFigureByOrder(u8 order_added);
u8 DeriveFigurePosition(u8 position);
u32 random_a = 0;
u32 random_b = 0;
u32 random_c = 0;
u32 random_d = 0;
u8 m_figure_order = 0;
std::queue<std::array<u8, 32>> m_figure_added_removed_responses;
};
class InfinityBackend final : public UsbEmulatedBackend {
protected:
libusb_endpoint_descriptor* FillEndpointDescriptorPair() override;
libusb_interface_descriptor* FillInterfaceDescriptor(
libusb_endpoint_descriptor* descs) override;
libusb_config_descriptor* FillConfigDescriptor(libusb_interface* inter) override;
libusb_device_descriptor* FillDeviceDescriptor() override;
s32 ControlTransfer(libusb_device_handle* dev_handle, u8 bmRequestType, u8 bRequest, u16 wValue,
u16 wIndex, u8* data, u16 wLength, u32 timeout) override {
return LIBUSB_SUCCESS;
}
libusb_transfer_status HandleAsyncTransfer(libusb_transfer* transfer) override;
std::shared_ptr<UsbEmulatedImpl> GetImplRef() override {
return m_infinity_base;
}
private:
std::shared_ptr<InfinityBase> m_infinity_base = std::make_shared<InfinityBase>();
std::array<u8, 9> m_endpoint_out_extra = {0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22, 0x1d, 0x00};
std::vector<libusb_endpoint_descriptor> m_endpoint_descriptors = {
{0x7, 0x5, 0x81, 0x3, 0x20, 0x1, 0x0, 0x0},
{0x7, 0x5, 0x1, 0x3, 0x20, 0x1, 0x0, 0x0, m_endpoint_out_extra.data(), 9}};
std::vector<libusb_interface_descriptor> m_interface_descriptors = {
{0x9, 0x4, 0x0, 0x0, 0x2, 0x3, 0x0, 0x0, 0x0}};
std::vector<libusb_config_descriptor> m_config_descriptors = {
{0x9, 0x2, 0x29, 0x1, 0x1, 0x0, 0x80, 0xFA}};
std::vector<libusb_device_descriptor> m_device_descriptors = {
{0x12, 0x1, 0x200, 0x0, 0x0, 0x0, 0x20, 0x0E6F, 0x0129, 0x200, 0x1, 0x2, 0x3, 0x1}};
std::queue<std::array<u8, 32>> m_queries;
};
} // namespace Libraries::Usbd

View File

@ -312,7 +312,7 @@ public:
const auto endpoint_descs = FillEndpointDescriptorPair();
const auto interface_desc = FillInterfaceDescriptor(endpoint_descs);
const auto interface = static_cast<libusb_interface*>(calloc(1, sizeof(libusb_interface*)));
const auto interface = static_cast<libusb_interface*>(calloc(1, sizeof(libusb_interface)));
interface->altsetting = interface_desc;
interface->num_altsetting = 1;
@ -366,7 +366,7 @@ public:
const auto desc = FillDeviceDescriptor();
ASSERT(desc);
const auto fake = static_cast<UsbDevice*>(calloc(1, sizeof(UsbDevice*)));
const auto fake = static_cast<UsbDevice*>(calloc(1, sizeof(UsbDevice)));
fake->bus_number = 0;
fake->port_number = 0;
fake->device_address = 0;

View File

@ -4,6 +4,8 @@
#pragma once
#include "common/types.h"
#include "emulated/dimensions.h"
#include "emulated/infinity.h"
#include "emulated/skylander.h"
#include "usb_backend.h"
@ -33,10 +35,9 @@ using SceUsbdTransfer = libusb_transfer;
using SceUsbdControlSetup = libusb_control_setup;
using SceUsbdTransferCallback = void PS4_SYSV_ABI (*)(SceUsbdTransfer* transfer);
// TODO: implement emulated devices
using SkylandersPortalBackend = SkylanderBackend;
using InfinityBaseBackend = UsbRealBackend;
using DimensionsToypadBackend = UsbRealBackend;
using InfinityBaseBackend = InfinityBackend;
using DimensionsToypadBackend = DimensionsBackend;
enum class SceUsbdSpeed : u32 {
UNKNOWN = 0,

View File

@ -113,6 +113,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
std::string id;
std::string title;
std::string app_version;
u32 sdk_version;
u32 fw_version;
Common::PSFAttributes psf_attributes{};
if (param_sfo_exists) {
@ -132,8 +133,48 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
if (const auto raw_attributes = param_sfo->GetInteger("ATTRIBUTE")) {
psf_attributes.raw = *raw_attributes;
}
// Extract sdk version from pubtool info.
std::string_view pubtool_info =
param_sfo->GetString("PUBTOOLINFO").value_or("Unknown value");
u64 sdk_ver_offset = pubtool_info.find("sdk_ver");
if (sdk_ver_offset == pubtool_info.npos) {
// Default to using firmware version if SDK version is not found.
sdk_version = fw_version;
} else {
// Increment offset to account for sdk_ver= part of string.
sdk_ver_offset += 8;
u64 sdk_ver_len = pubtool_info.find(",", sdk_ver_offset);
if (sdk_ver_len == pubtool_info.npos) {
// If there's no more commas, this is likely the last entry of pubtool info.
// Use string length instead.
sdk_ver_len = pubtool_info.size();
}
sdk_ver_len -= sdk_ver_offset;
std::string sdk_ver_string = pubtool_info.substr(sdk_ver_offset, sdk_ver_len).data();
// Number is stored in base 16.
sdk_version = std::stoi(sdk_ver_string, nullptr, 16);
}
}
auto& game_info = Common::ElfInfo::Instance();
game_info.initialized = true;
game_info.game_serial = id;
game_info.title = title;
game_info.app_ver = app_version;
game_info.firmware_ver = fw_version & 0xFFF00000;
game_info.raw_firmware_ver = fw_version;
game_info.sdk_ver = sdk_version;
game_info.psf_attributes = psf_attributes;
const auto pic1_path = mnt->GetHostPath("/app0/sce_sys/pic1.png");
if (std::filesystem::exists(pic1_path)) {
game_info.splash_path = pic1_path;
}
game_info.game_folder = game_folder;
Config::load(Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (id + ".toml"),
true);
@ -196,6 +237,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
if (param_sfo_exists) {
LOG_INFO(Loader, "Game id: {} Title: {}", id, title);
LOG_INFO(Loader, "Fw: {:#x} App Version: {}", fw_version, app_version);
LOG_INFO(Loader, "Compiled SDK version: {:#x}", sdk_version);
LOG_INFO(Loader, "PSVR Supported: {}", (bool)psf_attributes.support_ps_vr.Value());
LOG_INFO(Loader, "PSVR Required: {}", (bool)psf_attributes.require_ps_vr.Value());
}
@ -235,22 +277,6 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
}
}
auto& game_info = Common::ElfInfo::Instance();
game_info.initialized = true;
game_info.game_serial = id;
game_info.title = title;
game_info.app_ver = app_version;
game_info.firmware_ver = fw_version & 0xFFF00000;
game_info.raw_firmware_ver = fw_version;
game_info.psf_attributes = psf_attributes;
const auto pic1_path = mnt->GetHostPath("/app0/sce_sys/pic1.png");
if (std::filesystem::exists(pic1_path)) {
game_info.splash_path = pic1_path;
}
game_info.game_folder = game_folder;
std::string game_title = fmt::format("{} - {} <{}>", id, title, app_version);
std::string window_title = "";
std::string remote_url(Common::g_scm_remote_url);

View File

@ -250,7 +250,7 @@ void Render(const vk::CommandBuffer& cmdbuf, const vk::ImageView& image_view,
}
bool MustKeepDrawing() {
return layers.size() > 1 || DebugState.IsShowingDebugMenuBar();
return layers.size() > 1 || change_layers.size() > 1 || DebugState.IsShowingDebugMenuBar();
}
} // namespace Core

View File

@ -957,7 +957,7 @@ bool BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size,
.pBufferMemoryBarriers = &post_barrier,
});
}
if (is_texel_buffer) {
if (is_texel_buffer && !is_written) {
return SynchronizeBufferFromImage(buffer, device_addr, size);
}
return false;

View File

@ -355,13 +355,12 @@ bool PipelineCache::RefreshGraphicsKey() {
}
// Fill color target information
key.color_buffers[cb] = Shader::PsColorBuffer{
.data_format = col_buf.GetDataFmt(),
.num_format = col_buf.GetNumberFmt(),
.num_conversion = col_buf.GetNumberConversion(),
.export_format = regs.color_export_format.GetFormat(cb),
.swizzle = col_buf.Swizzle(),
};
auto& color_buffer = key.color_buffers[cb];
color_buffer.data_format = col_buf.GetDataFmt();
color_buffer.num_format = col_buf.GetNumberFmt();
color_buffer.num_conversion = col_buf.GetNumberConversion();
color_buffer.export_format = regs.color_export_format.GetFormat(cb);
color_buffer.swizzle = col_buf.Swizzle();
}
// Compile and bind shader stages
@ -379,7 +378,7 @@ bool PipelineCache::RefreshGraphicsKey() {
continue;
}
if ((key.mrt_mask & (1u << cb)) == 0) {
key.color_buffers[cb] = {};
std::memset(&key.color_buffers[cb], 0, sizeof(Shader::PsColorBuffer));
continue;
}

View File

@ -380,7 +380,8 @@ void Rasterizer::OnSubmit() {
}
bool Rasterizer::BindResources(const Pipeline* pipeline) {
if (IsComputeImageCopy(pipeline) || IsComputeMetaClear(pipeline)) {
if (IsComputeImageCopy(pipeline) || IsComputeMetaClear(pipeline) ||
IsComputeImageClear(pipeline)) {
return false;
}
@ -520,6 +521,66 @@ bool Rasterizer::IsComputeImageCopy(const Pipeline* pipeline) {
return true;
}
bool Rasterizer::IsComputeImageClear(const Pipeline* pipeline) {
if (!pipeline->IsCompute()) {
return false;
}
// Ensure shader only has 2 bound buffers
const auto& cs_pgm = liverpool->GetCsRegs();
const auto& info = pipeline->GetStage(Shader::LogicalStage::Compute);
if (cs_pgm.num_thread_x.full != 64 || info.buffers.size() != 2 || !info.images.empty()) {
return false;
}
// From those 2 buffers, first must hold the clear vector and second the image being cleared
const auto& desc0 = info.buffers[0];
const auto& desc1 = info.buffers[1];
if (desc0.is_formatted || !desc1.is_formatted || desc0.is_written || !desc1.is_written) {
return false;
}
// First buffer must have size of vec4 and second the size of a single layer
const AmdGpu::Buffer buf0 = desc0.GetSharp(info);
const AmdGpu::Buffer buf1 = desc1.GetSharp(info);
const u32 buf1_bpp = AmdGpu::NumBitsPerBlock(buf1.GetDataFmt());
if (buf0.GetSize() != 16 || (cs_pgm.dim_x * 128ULL * (buf1_bpp / 8)) != buf1.GetSize()) {
return false;
}
// Find image the buffer alias
const auto image1_id =
texture_cache.FindImageFromRange(buf1.base_address, buf1.GetSize(), false);
if (!image1_id) {
return false;
}
// Image clear must be valid
VideoCore::Image& image1 = texture_cache.GetImage(image1_id);
if (image1.info.guest_size != buf1.GetSize() || image1.info.num_bits != buf1_bpp ||
image1.info.props.is_depth) {
return false;
}
// Perform image clear
const float* values = reinterpret_cast<float*>(buf0.base_address);
const vk::ClearValue clear = {
.color = {.float32 = std::array<float, 4>{values[0], values[1], values[2], values[3]}},
};
const VideoCore::SubresourceRange range = {
.base =
{
.level = 0,
.layer = 0,
},
.extent = image1.info.resources,
};
image1.Clear(clear, range);
image1.flags |= VideoCore::ImageFlagBits::GpuModified;
image1.flags &= ~VideoCore::ImageFlagBits::Dirty;
return true;
}
void Rasterizer::BindBuffers(const Shader::Info& stage, Shader::Backend::Bindings& binding,
Shader::PushData& push_data) {
buffer_bindings.clear();

View File

@ -112,6 +112,7 @@ private:
bool IsComputeMetaClear(const Pipeline* pipeline);
bool IsComputeImageCopy(const Pipeline* pipeline);
bool IsComputeImageClear(const Pipeline* pipeline);
private:
friend class VideoCore::BufferCache;