mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-04-09 11:11:29 -06:00
Merge branch 'shadps4-emu:main' into gc2
This commit is contained in:
commit
c21cce2a2f
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -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
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
3
dist/net.shadps4.shadPS4.metainfo.xml
vendored
3
dist/net.shadps4.shadPS4.metainfo.xml
vendored
@ -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>
|
||||
|
||||
@ -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
2
externals/MoltenVK
vendored
@ -1 +1 @@
|
||||
Subproject commit b23d42534622cd9926fe526fec1b7f8795a2853c
|
||||
Subproject commit f168dec05998ab0ca09a400bab6831a95c0bdb2e
|
||||
65
shell.nix
65
shell.nix
@ -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 = ''
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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() {
|
||||
|
||||
651
src/core/libraries/usbd/emulated/dimensions.cpp
Normal file
651
src/core/libraries/usbd/emulated/dimensions.cpp
Normal 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
|
||||
118
src/core/libraries/usbd/emulated/dimensions.h
Normal file
118
src/core/libraries/usbd/emulated/dimensions.h
Normal 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
|
||||
392
src/core/libraries/usbd/emulated/infinity.cpp
Normal file
392
src/core/libraries/usbd/emulated/infinity.cpp
Normal 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
|
||||
112
src/core/libraries/usbd/emulated/infinity.h
Normal file
112
src/core/libraries/usbd/emulated/infinity.h
Normal 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
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user