Compare commits

...

73 Commits

Author SHA1 Message Date
Rodrigo Cioletti
138425fdf4
Network: Fixed null string crash on sceNetResolverCreate (#3872)
Some checks failed
Build and Release / reuse (push) Has been cancelled
Build and Release / clang-format (push) Has been cancelled
Build and Release / get-info (push) Has been cancelled
Build and Release / windows-sdl (push) Has been cancelled
Build and Release / macos-sdl (push) Has been cancelled
Build and Release / linux-sdl (push) Has been cancelled
Build and Release / linux-sdl-gcc (push) Has been cancelled
Build and Release / pre-release (push) Has been cancelled
* Network: Fixed null string crash on sceNetResolverCreate

* Assigned an empty string for better code styling

* Fixed wrong commit
2025-12-21 15:50:49 +02:00
marecl
2bbb04ff55
ENAMETOOLONG, posix_rename fix (#3869)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
* ENAMETOOLONG on paths > 255 characters
Corrected posix_rename behaviour:
* handles errors only if dst exists (shouldn't error out if doesn't)
* recursively removes "old name" (would otherwise fail if it's a not-empty dir)
* actually creates target directory

* Updated detection

* destubbed unlink()

* proper error returned
2025-12-19 14:58:07 +02:00
kalaposfos13
eae5e0ad55
Initialize VK_EXT_shader_atomic_float before VK_EXT_shader_atomic_float2 (#3867)
Some checks failed
Build and Release / reuse (push) Has been cancelled
Build and Release / clang-format (push) Has been cancelled
Build and Release / get-info (push) Has been cancelled
Build and Release / windows-sdl (push) Has been cancelled
Build and Release / macos-sdl (push) Has been cancelled
Build and Release / linux-sdl (push) Has been cancelled
Build and Release / linux-sdl-gcc (push) Has been cancelled
Build and Release / pre-release (push) Has been cancelled
2025-12-15 16:50:14 -08:00
georgemoralis
9e287564ce
update submodules (ffmpeg,fmt,sdl3) (#3865)
Some checks failed
Build and Release / reuse (push) Has been cancelled
Build and Release / clang-format (push) Has been cancelled
Build and Release / get-info (push) Has been cancelled
Build and Release / windows-sdl (push) Has been cancelled
Build and Release / macos-sdl (push) Has been cancelled
Build and Release / linux-sdl (push) Has been cancelled
Build and Release / linux-sdl-gcc (push) Has been cancelled
Build and Release / pre-release (push) Has been cancelled
* update submodules (ffmpeg,fmt,sdl3)

* fixed linux builds?

* more linux issues

* let's see more linux...

* baby one more time
2025-12-12 23:40:17 +02:00
Stephen Miller
9e7df6ae54
Kernel.Vmm: Remove hack from #2726 (#3864)
Some checks failed
Build and Release / reuse (push) Has been cancelled
Build and Release / clang-format (push) Has been cancelled
Build and Release / get-info (push) Has been cancelled
Build and Release / windows-sdl (push) Has been cancelled
Build and Release / macos-sdl (push) Has been cancelled
Build and Release / linux-sdl (push) Has been cancelled
Build and Release / linux-sdl-gcc (push) Has been cancelled
Build and Release / pre-release (push) Has been cancelled
* Remove SceKernelInternalMemory mapping

Contrary to my initial beliefs, this is very much a hack.

* Unreachable for unpatched code

This will always infinitely loop, making logs extremely large.

* Update linker.cpp
2025-12-11 12:24:45 +02:00
AlpinDale
de6c5bbb83
cli: add --show-fps to the CLI launcher (#3860)
* cli: add `--show-fps` to the CLI launcher

* fix: clang-format

* nit: PascalCase -> camelCase
2025-12-08 21:02:54 +01:00
Stephen Miller
65f0b07c34
libkernel: Implement sceKernelEnableDmemAliasing, proper mapping type checks in posix_mmap (#3859)
* Basic handling for MAP_VOID, MAP_STACK, and MAP_ANON in mmap.

* Update memory.cpp

* Update memory.cpp

* Dmem aliasing check

* Oops
2025-12-08 13:46:32 +02:00
kalaposfos13
2a5910ed51
New translations en_us.ts (OpenOrbis) (#3858) 2025-12-07 23:56:51 +02:00
Stephen Miller
391d30cbb1
cpu_patches: Patch stack canary accesses (#3857)
* Patch stack checks done using fs:[0x28]

Additionally adds support for multiple patches per instruction, since this makes two separate patches we need to conditionally perform for mov instructions.

* Missing include

* Disable patches for Apple

Mac can use their native FS segment directly, so these patches aren't needed

* Oops
2025-12-06 22:47:39 -08:00
TheTurtle
d3ad728ac0
vector_alu: Handle -1 as src1 in v_cmp_u64 (#3855) 2025-12-06 15:11:29 -08:00
Odukoya Abdullahi Ademola
5183cbe686
sceHttpUriSweepPath (#3854)
Some checks failed
Build and Release / reuse (push) Has been cancelled
Build and Release / clang-format (push) Has been cancelled
Build and Release / get-info (push) Has been cancelled
Build and Release / windows-sdl (push) Has been cancelled
Build and Release / macos-sdl (push) Has been cancelled
Build and Release / linux-sdl (push) Has been cancelled
Build and Release / linux-sdl-gcc (push) Has been cancelled
Build and Release / pre-release (push) Has been cancelled
2025-12-04 10:50:24 +02:00
Odukoya Abdullahi Ademola
9e80cde60d
Implement http uri escape unescape (#3853)
* Implement sceHttpUriEscape and sceHttpUriUnescape

* Implement sceHttpUriEscape and sceHttpUriUnescape

* edge case

---------

Co-authored-by: Pirky10 <odukoyaabdullah@gmail.com>
2025-12-04 10:50:01 +02:00
kalaposfos13
98fd0689ac
Revert non-Linux parts of #3819 (#3852)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
* Revert non-Linux parts of #3819

* More OpenOrbis stuff that I couldn't be bothered to put in a new PR
2025-12-03 15:05:19 +02:00
Lander Gallastegi
9db4642f66
video_core: Scheduler priority pending operation queue (#3848)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
* Priority pending ops

* Use priority operations on image download

* clang-format

* Simplify thread

* I'm tired, it's too late :(
2025-12-02 22:27:01 +01:00
kalaposfos13
b135a056ba Remove debug logging
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
2025-12-02 09:50:11 +01:00
kalaposfos13
dc6013cf0e Block normal mouse inputs in mouse-to-touchpad mode
shadow sniped my PR. :(
2025-12-02 09:41:06 +01:00
Pirky
e5ea55e425
np: Add dialog state tracking for NpCommerce (#3841) 2025-12-02 10:22:41 +02:00
kalaposfos13
c3f7a4301c
Add basic mouse-to-touchpad emulation (#3842) 2025-12-02 10:21:01 +02:00
Stephen Miller
a5f9280841
Return CPU mode based on param.sfo attributes (#3846)
Some checks failed
Build and Release / reuse (push) Has been cancelled
Build and Release / clang-format (push) Has been cancelled
Build and Release / get-info (push) Has been cancelled
Build and Release / windows-sdl (push) Has been cancelled
Build and Release / macos-sdl (push) Has been cancelled
Build and Release / linux-sdl (push) Has been cancelled
Build and Release / linux-sdl-gcc (push) Has been cancelled
Build and Release / pre-release (push) Has been cancelled
Values are based on hardware observations.
2025-12-01 10:21:19 +02:00
kalaposfos13
cf866ab294
Don't bother trying to restart the emulator if sceSystemServiceLoadExec is called with an invalid path (#3845)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
2025-11-30 22:40:58 +02:00
Connor Garey
052f3260f3
Sdl message box when no args provided (#3843)
* Added a message box when no arguments are passed.

* clang-fix

* clang-fix episode 2

* Output message box error to stderr instead of stdout
2025-11-30 19:57:14 +02:00
TheThunderTurner
78e301c3db
libSceNpCommerce (#3839)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
* libSceNpCommerce

* copyright notice
2025-11-29 23:47:15 +02:00
psucien
a9f8eaf778
video_core: Initial implementation of pipeline cache (#3816)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
* Initial implementation

* Fix for crash caused by stale stages data; cosmetics applied

* Someone mentioned the assert

* Async blob writer

* Fix for memory leak

* Remain stuff

* Async changed to `packaged_task`
2025-11-29 11:52:08 +02:00
Quang Ngô
f9ef57f74b
Fix metainfo (#3834)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
2025-11-28 18:36:11 +02:00
squidbus
1394852791
renderer_vulkan: Remove primitive restart disable support check. (#3827)
Some checks failed
Build and Release / reuse (push) Has been cancelled
Build and Release / clang-format (push) Has been cancelled
Build and Release / get-info (push) Has been cancelled
Build and Release / windows-sdl (push) Has been cancelled
Build and Release / macos-sdl (push) Has been cancelled
Build and Release / linux-sdl (push) Has been cancelled
Build and Release / linux-sdl-gcc (push) Has been cancelled
Build and Release / pre-release (push) Has been cancelled
2025-11-24 23:51:39 -08:00
Stephen Miller
6295c32e5c
Render.Recompiler: Implement V_FLOOR_F64 (#3828)
* VectorFpRound64 decode table

Also fixed definition for V_TRUNC_F64, though I doubt that would change anything important.

* V_FLOOR_F64 implementation

Used by Just Cause 4

* Oops

Never forget your 64s
2025-11-24 23:51:06 -08:00
TheTurtle
14d71a155a
video_core: Reimplement inline data as buffer fill (#3825)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
2025-11-24 23:25:22 +02:00
kalaposfos13
2577dfde7e
Add assert on SFO file being empty (#3815)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
2025-11-23 17:30:09 -08:00
Missake
8123c44ad1
Make FSR off by default (#3801)
* Make FSR and RCAS off by default

* Update config.cpp
2025-11-23 17:28:48 -08:00
TheTurtle
f1a8b7d85e
vector_alu: Fix V_CMP_U64 (#3823)
* vector_alu: Fix V_CMP_U64

* vector_alu: Also handle vcc in V_CMP_U64
2025-11-23 17:26:34 -08:00
Stephen Miller
f6ae5544fd
Kernel.Vmm: Protect Fixes (#3822)
Some checks failed
Build and Release / reuse (push) Has been cancelled
Build and Release / clang-format (push) Has been cancelled
Build and Release / get-info (push) Has been cancelled
Build and Release / windows-sdl (push) Has been cancelled
Build and Release / macos-sdl (push) Has been cancelled
Build and Release / linux-sdl (push) Has been cancelled
Build and Release / linux-sdl-gcc (push) Has been cancelled
Build and Release / pre-release (push) Has been cancelled
* Some mprotect fixes

The biggest thing here is preventing mprotect on memory that isn't mapped in address space. This would cause exceptions before, but succeeds on real hardware.
I've also included a couple other minor fixes, mostly based around some tests I recently performed.

Note: All changes to memory pools in this PR are assumed. I have not yet tested memory pools with any of this logic, but I do at least want to prevent mprotect on pool reserved memory to avoid crashes.

* Update memory.cpp

* clang
2025-11-22 10:32:53 +02:00
oltolm
4922d526fe
msys2: fix build (#3818)
* cmake: fix mingw-w64 build

* time.cpp: fix build with Clang on Windows

* tls.h: include malloc.h for alloca
2025-11-22 10:32:29 +02:00
kalaposfos13
56109a1331
Avoid storing the Tcb pointer on the stack (#3819)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
* Avoid storing the Tcb pointer on the stack

* Just return the already stored pointer in GetTcbBase

* Replace uses of GetTcbBase with g_curthread->tcb

* copyright 2025

* sir clang offnir, the all-formatting
2025-11-21 00:42:49 -08:00
Osyotr
544a22a431
emulator: crash faster (#2360)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
By disabling Windows Error Reporting.
2025-11-20 22:35:35 +02:00
marecl
6612a32523
Prevent writing to directories (#3820)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
* Prevent writing to directories

* Prevent writing to directories
2025-11-20 19:41:01 +02:00
TheTurtle
3f86c2e94a
buffer_cache: Split DMA fault handling code from buffer cache (#3809)
Some checks failed
Build and Release / reuse (push) Has been cancelled
Build and Release / clang-format (push) Has been cancelled
Build and Release / get-info (push) Has been cancelled
Build and Release / windows-sdl (push) Has been cancelled
Build and Release / macos-sdl (push) Has been cancelled
Build and Release / linux-sdl (push) Has been cancelled
Build and Release / linux-sdl-gcc (push) Has been cancelled
Build and Release / pre-release (push) Has been cancelled
Its better not to have that raw code there
2025-11-18 08:46:51 +02:00
Alexandre Bouvier
5b699090e6
cmake: fix sdl3_mixer target name (#3811)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
2025-11-17 18:56:01 +02:00
TheTurtle
aa5c045555
logging: Format message after filter check (#3808)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
2025-11-16 17:34:23 +02:00
rainmakerv2
ed14359c87
Re-implement custom trophy sounds using sdl3 mixer (#3805)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
* re-implement custom trophy sounds using sdl3 mixer

* fix build vars

* Don't change SDL version
2025-11-16 10:36:54 +02:00
Missake
6a9f9abda0
Delete lines about Qt in building doc for Windows (#3800)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
2025-11-14 21:48:04 -08:00
TheTurtle
2f55636626
vk_rasterizer: Attempt to optimize compute clears (#3795) 2025-11-15 07:44:25 +02:00
Stephen Miller
94d0f2e7ed
Avoid initializing Shader::PsColorBuffer in RefreshGraphicsKey (#3799)
The bitfield in the struct is padded, which produces uninitialized memory on initialization.
To avoid modifying the struct while making our GraphicsPipelineKey struct properly hashable, set values directly instead of re-initializing.

This fixes pipeline compile spam, and the subsequent poor performance, on certain setups.
2025-11-14 19:50:14 -08:00
kalaposfos13
f557c6ac64
Update MoltenVK to the shadPS4 fork (#3797)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
2025-11-14 21:47:16 +02:00
Stephen Miller
93c340c6e1
calloc libusb_interface instead of pointer (#3793)
Some checks failed
Build and Release / reuse (push) Has been cancelled
Build and Release / clang-format (push) Has been cancelled
Build and Release / get-info (push) Has been cancelled
Build and Release / windows-sdl (push) Has been cancelled
Build and Release / macos-sdl (push) Has been cancelled
Build and Release / linux-sdl (push) Has been cancelled
Build and Release / linux-sdl-gcc (push) Has been cancelled
Build and Release / pre-release (push) Has been cancelled
interface->num_altsetting is oob
2025-11-12 17:26:26 +02:00
Joshua de Reeper
25344a3b89
calloc UsbDevice instead of pointer (#3790) 2025-11-12 12:40:39 +02:00
Missake
bbd985fe4b
Update building-windows.md (#3792)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
2025-11-11 16:56:03 +01:00
Stephen Miller
ee2bc97248
Windows: Limit address space maximum when higher addresses are not needed (#3775)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
* Earlier initialization of elf info.

Everything used for elf info initialization comes from the param.sfo, so we can initialize this earlier to have this information accessible during memory init.

* Extract compiled SDK version from pubtoolinfo string

Up until now, we've been using the game's reported "firmware version" as our compiled SDK version. This behavior is inaccurate, and is something that has come up in my hardware tests before.

For the actual compiled SDK version, we should use the SDK version in the PUBTOOLINFO string of the param.sfo, only falling back on the firmware version when that the sdk_ver component isn't present.

* Store compiled SDK version in ElfInfo

* Limit address space for compiled SDK version at or above FW 3

Sony placed a hard cap at 0xfc00000000, with a slight extension for stack mappings. For now, though stack mappings aren't implemented, there's no harm in keeping a slightly extended address space (since this cap is lower than our old user max).

Limiting the max through address space is necessary for Windows due to performance issues, in the future I plan to properly implement checks in memory manager code to properly handle this behavior for all platforms.

* Use compiled SDK version for sceKernelGetCompiledSdkVersion

I think this is pretty self explanatory.

* Log SDK version

Since this value is what most internal firmware version checks are against, logging the value will help with debugging.

* Update address_space.cpp

* Update emulator.cpp

* Backwards compatible logging

Because that's apparently an issue now
2025-11-10 17:07:17 +02:00
Connor Garey
bebfee58d6
Nix shell fixes for uuid (#3784)
Some checks failed
Build and Release / reuse (push) Has been cancelled
Build and Release / clang-format (push) Has been cancelled
Build and Release / get-info (push) Has been cancelled
Build and Release / windows-sdl (push) Has been cancelled
Build and Release / macos-sdl (push) Has been cancelled
Build and Release / linux-sdl (push) Has been cancelled
Build and Release / linux-sdl-gcc (push) Has been cancelled
Build and Release / pre-release (push) Has been cancelled
* added "with pkgs;" so pkgs does not need to be appended for all the buildInputs.

* Added util linux as missing uuid. Compiles successfully.
2025-11-08 19:08:18 -08:00
georgemoralis
5ddabda2b8 started 0.12.6 WIP
Some checks failed
Build and Release / reuse (push) Has been cancelled
Build and Release / clang-format (push) Has been cancelled
Build and Release / get-info (push) Has been cancelled
Build and Release / windows-sdl (push) Has been cancelled
Build and Release / macos-sdl (push) Has been cancelled
Build and Release / linux-sdl (push) Has been cancelled
Build and Release / linux-sdl-gcc (push) Has been cancelled
Build and Release / pre-release (push) Has been cancelled
2025-11-07 09:16:58 +02:00
georgemoralis
3ce1ac5e86 tagged 0.12.5 release 2025-11-07 08:59:26 +02:00
Emma
b4628b80e2
ImGui: keep drawing when there's a pending change_layer (#3782)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
2025-11-06 20:58:15 -03:00
Emma
2f022a462d
filesystem: return st_mtim in posix_stat (fixes RB4 / CUSA02901 DLC crash) (#3781)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
* filesystem: return st_mtim in posix_stat

* filesystem: stat - remove reliance on clock_cast

* filesystem: stat - remove reliance on to_time_t
2025-11-06 22:47:40 +02:00
Joshua de Reeper
f5505daaca
usbd: Emulate Dimensions Toypad (#3779)
* Dimensions Toypad

* update comment
2025-11-06 17:46:43 +02:00
Joshua de Reeper
8c1ec863da
Add Infinity Base Backend (#3778) 2025-11-06 16:34:52 +02:00
kalaposfos13
604f8d9398
More OpenOrbis stuff (#3776)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
2025-11-06 13:22:26 +02:00
Stephen Miller
19e974bf21
Libkernel: Implement/stub some functions (#3774)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
* Define latest released firmware version, use that for sceKernelGetSystemSwVersion

I feel this is less hacky and error-prone than just returning the game firmware.

* sceKernelGetAllowedSdkVersionOnSystem

* sceKernelHasNeoMode

* sceKernelGetAppInfo stub

* sceKernelGetCurrentCpu

* fixups

* sceKernelGetMainSocId

Used by libSceAvPlayer to determine if console is a pro or not.

* Update process.cpp

* Set has_param_sfo to true

* Clang
2025-11-05 16:58:15 -08:00
Stephen Miller
7031f5968e
Better return stub (#3773)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
Gets THE PLAYROOM (CUSA00001) further.
2025-11-05 18:32:24 +02:00
georgemoralis
ff8869262f
[Libs] Font lib HLE implementation (#2761)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
* dummy fontlib libs

* register font libs

* typo fix

* added font error file

* added sceFontCharacterGetBidiLevel (from RE)

* fixup

* sceFontCharacterGetTextOrder , sceFontCharacterLooksFormatCharacters , sceFontCharacterLooksWhiteSpace RE

* sceFontCharacterRefersTextBack,sceFontCharacterRefersTextNext,sceFontRenderSurfaceInit,sceFontRenderSurfaceSetScissor  RE

* sceFontRenderSurfaceSetStyleFrame ,sceFontStyleFrameGetEffectSlant RE added

* clang fix

* sceFontStyleFrameGetEffectWeight

* added sceFontStyleFrameGetScalePixel

* Update types.h

* fixup merge
2025-11-04 17:14:22 +02:00
Stephen Miller
683e5f3b04
Core: Simulate write-only file access with read-write access (#3360)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
* Swap write access mode for read write

Opening with access mode w will erase the opened file. We do not want this.

* Create mode

Opening with write access was previously the only way to create a file through open, so add a separate FileAccessMode that uses the write access mode to create files.

* Update file_system.cpp

Remove a hack added to posix_rename to bypass the file clearing behaviors of FileAccessMode::Write

* Check access mode in read functions

Write-only files cause the EBADF return on the various read functions. Now that we're opening files differently, properly handling this is necessary.

* Separate appends into proper modes

Fixes a potential regression from one of my prior PRs, and ensures the Write | Append flag combo also behaves properly in read-related functions.

* Move IsWriteOnly check after device/socket reads

file->f is only valid for files, so checking this before checking for sockets/devices will cause access violations.

* Fix issues

Now that Write is identical to ReadWrite, internal uses of Write need to be swapped to my new Create mode

* Fix remaining uses of FileAccessMode write to create files

Missed these before.

* Fix rebase

* Add stubbed get_authinfo (#3722)

* mostly stubbed get_authinfo

* Return value observed on console if get_authinfo was called for the current thread, esrch otherwise

---------

Co-authored-by: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com>
Co-authored-by: georgemoralis <giorgosmrls@gmail.com>
2025-11-04 10:57:26 +02:00
georgemoralis
08fe66a97f
[Libs] Http HLE (part2) (#2762)
* added sceHttpParseStatusLine

* added sceHttpUriMerge

* macOS fix

* more macOS fix?

* typo

* Update src/core/libraries/network/http.cpp

Co-authored-by: illusony <37698908+illusion0001@users.noreply.github.com>

* some work on sceHttpGetStatusCode

* more draft on GetStatusCode

* added some debug info

* fixup

---------

Co-authored-by: illusony <37698908+illusion0001@users.noreply.github.com>
2025-11-04 10:41:26 +02:00
Stephen Miller
bc44865cda
Implement sceGnmDrawInitToDefaultContextStateInternal functions (#3770)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
These are used by LLE libSceVideodec.
From decompiling the two GnmDriver libraries, it seems like sceGnmDrawInitToDefaultContextStateInternalCommand inlines a call to sceGnmDrawInitToDefaultContextState, so I've replaced that with an actual call to the function for readability.
sceGnmDrawInitToDefaultContextStateInternalSize is one to one with decomp.
2025-11-03 11:06:26 -08:00
kalaposfos13
a42ae46553
Fix game arguments not being passed under a certain condition (#3769)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
2025-11-02 23:16:11 +02:00
Vinicius Rangel
a4c3c665fe
add null gpu notice (#3768)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
2025-11-02 19:55:43 +02:00
Kyoskii
caccc05fb2
buffer_cache: smaller regions (#3764)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
* buffer_cache: smaller regions

this was a change back between v0.9.0 to v0.10.0
9f37ede336

reverting the TRACKER_HIGHER_PAGE_BITS from 24 to 22 gives a notable increase to performance.

* Update region_definitions.h

updated copyright
2025-11-01 16:20:45 +02:00
kalaposfos13
8238ecf88a
Fix patches being applied multiple times redundantly (#3763) 2025-11-01 13:47:38 +02:00
Joshua de Reeper
8bbb3956a2
Skylander Portal (#3762)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
2025-11-01 12:18:34 +02:00
georgemoralis
f466352dde revert controller change from #3750. Seems to cause issues in several games need to be reinvested 2025-11-01 11:40:26 +02:00
Joshua de Reeper
430f2e4700
Fix Typo in CMakeLists for MoltenVK MacOS (#3758)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
2025-10-31 09:48:34 -07:00
Pavel
6c7c5eb59c
get_authinfo (#3760) 2025-10-31 15:56:11 +02:00
oltolm
493cda07c0
fix divide by zero (#3759) 2025-10-31 15:36:27 +02:00
ElBread3
eda6be746f
usbd: Implement usb backend system (#3737)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
* initial impl

* reviews

* upstreamed deReaperJosh changes

* fixed config.cpp

---------

Co-authored-by: georgemoralis <giorgosmrls@gmail.com>
2025-10-31 11:11:14 +02:00
georgemoralis
ed9ffbfb64
Remove Qt from emulator (#3733)
* actions removal

* removed qt dir

# Conflicts:
#	src/qt_gui/check_update.cpp
#	src/qt_gui/translations/ar_SA.ts
#	src/qt_gui/translations/ca_ES.ts
#	src/qt_gui/translations/da_DK.ts
#	src/qt_gui/translations/de_DE.ts
#	src/qt_gui/translations/el_GR.ts
#	src/qt_gui/translations/en_US.ts
#	src/qt_gui/translations/es_ES.ts
#	src/qt_gui/translations/fa_IR.ts
#	src/qt_gui/translations/fi_FI.ts
#	src/qt_gui/translations/fr_FR.ts
#	src/qt_gui/translations/hu_HU.ts
#	src/qt_gui/translations/id_ID.ts
#	src/qt_gui/translations/it_IT.ts
#	src/qt_gui/translations/ja_JP.ts
#	src/qt_gui/translations/ko_KR.ts
#	src/qt_gui/translations/lt_LT.ts
#	src/qt_gui/translations/nb_NO.ts
#	src/qt_gui/translations/nl_NL.ts
#	src/qt_gui/translations/pl_PL.ts
#	src/qt_gui/translations/pt_BR.ts
#	src/qt_gui/translations/pt_PT.ts
#	src/qt_gui/translations/ro_RO.ts
#	src/qt_gui/translations/ru_RU.ts
#	src/qt_gui/translations/sl_SI.ts
#	src/qt_gui/translations/sq_AL.ts
#	src/qt_gui/translations/sr_CS.ts
#	src/qt_gui/translations/sv_SE.ts
#	src/qt_gui/translations/tr_TR.ts
#	src/qt_gui/translations/uk_UA.ts
#	src/qt_gui/translations/ur_PK.ts
#	src/qt_gui/translations/vi_VN.ts
#	src/qt_gui/translations/zh_CN.ts
#	src/qt_gui/translations/zh_TW.ts

* removed CMakePresets for qt builds

* clear cmakelists from qt

* sync config file with qtlauncher

* fixing review stuff

* Remove Qt code from memory patcher and add non-Qt fallback for automatic loading of patches
The second feature is disabled if IPC is present, to avoid conflicts with it.

* Add json submodule

* More Qt removal

* Documentation update

* fix build

* fix REUSE?

* removed qrc file

* fix clang

* Simplify Qt installation instructions for macOS

Removed instructions for installing x86_64 Qt on ARM and x86_64 Macs.

* Remove Qt installation instructions from guide

Removed instructions for downloading and configuring Qt.

---------

Co-authored-by: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com>
2025-10-31 10:28:39 +02:00
georgemoralis
5cabd6ddd8 started 0.12.1 WIP 2025-10-31 09:28:17 +02:00
241 changed files with 8234 additions and 104811 deletions

View File

@ -1,33 +0,0 @@
# SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
#!/bin/bash
if [[ -z $GITHUB_WORKSPACE ]]; then
GITHUB_WORKSPACE="${PWD%/*}"
fi
export Qt6_DIR="/usr/lib/qt6"
export PATH="$Qt6_DIR/bin:$PATH"
export EXTRA_QT_PLUGINS="waylandcompositor"
export EXTRA_PLATFORM_PLUGINS="libqwayland-egl.so;libqwayland-generic.so"
# Prepare Tools for building the AppImage
wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage
wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-checkrt/releases/download/continuous/linuxdeploy-plugin-checkrt-x86_64.sh
chmod a+x linuxdeploy-x86_64.AppImage
chmod a+x linuxdeploy-plugin-qt-x86_64.AppImage
chmod a+x linuxdeploy-plugin-checkrt-x86_64.sh
# Build AppImage
./linuxdeploy-x86_64.AppImage --appdir AppDir
./linuxdeploy-plugin-checkrt-x86_64.sh --appdir AppDir
cp -a "$GITHUB_WORKSPACE/build/translations" AppDir/usr/bin
./linuxdeploy-x86_64.AppImage --appdir AppDir -d "$GITHUB_WORKSPACE"/dist/net.shadps4.shadPS4.desktop -e "$GITHUB_WORKSPACE"/build/shadps4 -i "$GITHUB_WORKSPACE"/src/images/net.shadps4.shadPS4.svg --plugin qt
rm AppDir/usr/plugins/multimedia/libgstreamermediaplugin.so
./linuxdeploy-x86_64.AppImage --appdir AppDir --output appimage
mv shadPS4-x86_64.AppImage Shadps4-qt.AppImage

View File

@ -95,64 +95,6 @@ jobs:
name: shadps4-win64-sdl-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}
path: ${{github.workspace}}/build/shadPS4.exe
windows-qt:
runs-on: windows-2025
needs: get-info
steps:
- uses: actions/checkout@v5
with:
submodules: recursive
- name: Setup Qt
uses: jurplel/install-qt-action@v4
with:
version: 6.9.3
host: windows
target: desktop
arch: win64_msvc2022_64
archives: qtbase qttools
modules: qtmultimedia
- name: Cache CMake Configuration
uses: actions/cache@v4
env:
cache-name: ${{ runner.os }}-qt-ninja-cache-cmake-configuration
with:
path: |
${{github.workspace}}/build
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
restore-keys: |
${{ env.cache-name }}-
- name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.19
env:
cache-name: ${{ runner.os }}-qt-cache-cmake-build
with:
append-timestamp: false
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
- name: Configure CMake
run: cmake --fresh -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_QT_GUI=ON -DENABLE_UPDATER=ON -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $env:NUMBER_OF_PROCESSORS
- name: Deploy and Package
run: |
mkdir upload
mkdir upload/qtplugins
move build/shadPS4.exe upload
cp dist/qt.conf upload/qt.conf
windeployqt --plugindir upload/qtplugins --no-compiler-runtime --no-system-d3d-compiler --no-system-dxc-compiler --dir upload upload/shadPS4.exe
Compress-Archive -Path upload/* -DestinationPath shadps4-win64-qt-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}.zip
- name: Upload Windows Qt artifact
uses: actions/upload-artifact@v4
with:
name: shadps4-win64-qt-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}
path: upload/
macos-sdl:
runs-on: macos-15
needs: get-info
@ -204,67 +146,6 @@ jobs:
name: shadps4-macos-sdl-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}
path: upload/
macos-qt:
runs-on: macos-15
needs: get-info
steps:
- uses: actions/checkout@v5
with:
submodules: recursive
- name: Setup latest Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest
- name: Setup Qt
uses: jurplel/install-qt-action@v4
with:
version: 6.9.3
host: mac
target: desktop
arch: clang_64
archives: qtbase qttools
modules: qtmultimedia
- name: Cache CMake Configuration
uses: actions/cache@v4
env:
cache-name: ${{ runner.os }}-qt-cache-cmake-configuration
with:
path: |
${{github.workspace}}/build
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
restore-keys: |
${{ env.cache-name }}-
- name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.19
env:
cache-name: ${{runner.os}}-qt-cache-cmake-build
with:
append-timestamp: false
create-symlink: true
key: ${{env.cache-name}}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
variant: sccache
- name: Configure CMake
run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DENABLE_QT_GUI=ON -DENABLE_UPDATER=ON -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache
- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(sysctl -n hw.ncpu)
- name: Package and Upload macOS Qt artifact
run: |
mkdir upload
mv ${{github.workspace}}/build/shadps4.app upload
macdeployqt upload/shadps4.app
tar cf shadps4-macos-qt.tar.gz -C upload .
- uses: actions/upload-artifact@v4
with:
name: shadps4-macos-qt-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}
path: shadps4-macos-qt.tar.gz
linux-sdl:
runs-on: ubuntu-24.04
needs: get-info
@ -279,7 +160,7 @@ jobs:
sudo add-apt-repository 'deb http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main'
- name: Install dependencies
run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang-19 mold build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev
run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang-19 mold build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev libxcursor-dev libxi-dev libxss-dev libxtst-dev
- name: Cache CMake Configuration
uses: actions/cache@v4
@ -326,58 +207,6 @@ jobs:
name: shadps4-linux-sdl-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}
path: Shadps4-sdl.AppImage
linux-qt:
runs-on: ubuntu-24.04
needs: get-info
steps:
- uses: actions/checkout@v5
with:
submodules: recursive
- name: Add LLVM repository
run: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo add-apt-repository 'deb http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main'
- name: Install dependencies
run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang-19 mold build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-dev libasound2-dev libpulse-dev libopenal-dev libudev-dev
- name: Cache CMake Configuration
uses: actions/cache@v4
env:
cache-name: ${{ runner.os }}-qt-cache-cmake-configuration
with:
path: |
${{github.workspace}}/build
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
restore-keys: |
${{ env.cache-name }}-
- name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.19
env:
cache-name: ${{ runner.os }}-qt-cache-cmake-build
with:
append-timestamp: false
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
- name: Configure CMake
run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=clang-19 -DCMAKE_CXX_COMPILER=clang++-19 -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=mold" -DCMAKE_SHARED_LINKER_FLAGS="-fuse-ld=mold" -DENABLE_QT_GUI=ON -DENABLE_UPDATER=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(nproc)
- name: Run AppImage packaging script
run: ./.github/linux-appimage-qt.sh
- name: Package and Upload Linux Qt artifact
run: |
tar cf shadps4-linux-qt.tar.gz -C ${{github.workspace}}/build shadps4
- uses: actions/upload-artifact@v4
with:
name: shadps4-linux-qt-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}
path: Shadps4-qt.AppImage
linux-sdl-gcc:
runs-on: ubuntu-24.04
needs: get-info
@ -387,7 +216,7 @@ jobs:
submodules: recursive
- name: Install dependencies
run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 gcc-14 mold build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev
run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 gcc-14 mold build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev libxcursor-dev libxi-dev libxss-dev libxtst-dev
- name: Cache CMake Configuration
uses: actions/cache@v4
@ -414,45 +243,9 @@ jobs:
- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(nproc)
linux-qt-gcc:
runs-on: ubuntu-24.04
needs: get-info
steps:
- uses: actions/checkout@v5
with:
submodules: recursive
- name: Install dependencies
run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 gcc-14 mold build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-dev libasound2-dev libpulse-dev libopenal-dev libudev-dev
- name: Cache CMake Configuration
uses: actions/cache@v4
env:
cache-name: ${{ runner.os }}-qt-gcc-cache-cmake-configuration
with:
path: |
${{github.workspace}}/build
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
restore-keys: |
${{ env.cache-name }}-
- name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.19
env:
cache-name: ${{ runner.os }}-qt-gcc-cache-cmake-build
with:
append-timestamp: false
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
- name: Configure CMake
run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=mold" -DCMAKE_SHARED_LINKER_FLAGS="-fuse-ld=mold" -DENABLE_QT_GUI=ON -DENABLE_UPDATER=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(nproc)
pre-release:
if: github.ref == 'refs/heads/main' && github.repository == 'shadps4-emu/shadPS4' && github.event_name == 'push'
needs: [get-info, windows-sdl, windows-qt, macos-sdl, macos-qt, linux-sdl, linux-qt]
needs: [get-info, windows-sdl, macos-sdl, linux-sdl]
runs-on: ubuntu-latest
steps:
- name: Download all artifacts

12
.gitmodules vendored
View File

@ -108,5 +108,15 @@
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
url = https://github.com/nlohmann/json.git
[submodule "externals/sdl3_mixer"]
path = externals/sdl3_mixer
url = https://github.com/libsdl-org/SDL_mixer
shallow = true
[submodule "externals/miniz"]
path = externals/miniz
url = https://github.com/richgel999/miniz

View File

@ -31,7 +31,6 @@ if(UNIX AND NOT APPLE)
endif()
endif()
option(ENABLE_QT_GUI "Enable the Qt GUI. If not selected then the emulator uses a minimal SDL-based UI instead" OFF)
option(ENABLE_DISCORD_RPC "Enable the Discord RPC integration" ON)
option(ENABLE_UPDATER "Enables the options to updater" ON)
@ -204,12 +203,12 @@ execute_process(
# Set Version
set(EMULATOR_VERSION_MAJOR "0")
set(EMULATOR_VERSION_MINOR "12")
set(EMULATOR_VERSION_PATCH "0")
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}")
set(APP_VERSION "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH}")
set(APP_IS_RELEASE true)
set(APP_VERSION "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH} WIP")
set(APP_IS_RELEASE false)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/src/common/scm_rev.cpp" @ONLY)
message("-- end git things, remote: ${GIT_REMOTE_NAME}, branch: ${GIT_BRANCH}, link: ${GIT_REMOTE_URL}")
@ -220,10 +219,6 @@ if(NOT (GIT_REMOTE_URL_LOWER MATCHES "shadps4-emu/shadps4" AND (GIT_BRANCH STREQ
set(ENABLE_UPDATER OFF)
endif()
if(WIN32 AND ENABLE_QT_GUI AND NOT CMAKE_PREFIX_PATH)
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/DetectQtInstallation.cmake")
endif ()
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
find_package(Boost 1.84.0 CONFIG)
find_package(FFmpeg 5.1.2 MODULE)
@ -233,7 +228,10 @@ find_package(half 1.12.0 MODULE)
find_package(magic_enum 0.9.7 CONFIG)
find_package(PNG 1.6 MODULE)
find_package(RenderDoc 1.6.0 MODULE)
find_package(SDL3 3.1.2 CONFIG)
find_package(SDL3_mixer 2.8.1 CONFIG)
if (SDL3_mixer_FOUND)
find_package(SDL3 3.1.2 CONFIG)
endif()
find_package(stb MODULE)
find_package(toml11 4.2.0 CONFIG)
find_package(tsl-robin-map 1.3.0 CONFIG)
@ -262,30 +260,6 @@ endif()
add_subdirectory(externals)
include_directories(src)
if(ENABLE_QT_GUI)
find_package(Qt6 REQUIRED COMPONENTS Widgets Concurrent LinguistTools Network Multimedia)
qt_standard_project_setup()
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(QT_TRANSLATIONS "${PROJECT_SOURCE_DIR}/src/qt_gui/translations")
file(GLOB_RECURSE TRANSLATIONS_TS ${QT_TRANSLATIONS}/*.ts)
set_source_files_properties(${TRANSLATIONS_TS} PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/translations")
qt_add_translation(TRANSLATIONS_QM ${TRANSLATIONS_TS})
set(TRANSLATIONS_QRC ${CMAKE_CURRENT_BINARY_DIR}/translations/translations.qrc)
file(WRITE ${TRANSLATIONS_QRC} "<RCC><qresource prefix=\"translations\">\n")
foreach (QM ${TRANSLATIONS_QM})
get_filename_component(QM_FILE ${QM} NAME)
file(APPEND ${TRANSLATIONS_QRC} "<file>${QM_FILE}</file>\n")
endforeach (QM)
file(APPEND ${TRANSLATIONS_QRC} "</qresource></RCC>")
qt_add_resources(TRANSLATIONS ${TRANSLATIONS_QRC})
endif()
set(AJM_LIB src/core/libraries/ajm/ajm.cpp
src/core/libraries/ajm/ajm.h
src/core/libraries/ajm/ajm_at9.cpp
@ -490,6 +464,12 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp
src/core/libraries/mouse/mouse.h
src/core/libraries/web_browser_dialog/webbrowserdialog.cpp
src/core/libraries/web_browser_dialog/webbrowserdialog.h
src/core/libraries/font/font.cpp
src/core/libraries/font/font.h
src/core/libraries/font/fontft.cpp
src/core/libraries/font/fontft.h
src/core/libraries/font/font_error.h
)
set(VIDEOOUT_LIB src/core/libraries/videoout/buffer.h
@ -532,7 +512,7 @@ set(PAD_LIB src/core/libraries/pad/pad.cpp
src/core/libraries/pad/pad_errors.h
)
set(SYSTEM_GESTURE_LIB
set(SYSTEM_GESTURE_LIB
src/core/libraries/system_gesture/system_gesture.cpp
src/core/libraries/system_gesture/system_gesture.h
)
@ -561,6 +541,13 @@ 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
)
set(FIBER_LIB src/core/libraries/fiber/fiber_context.s
@ -569,6 +556,8 @@ set(FIBER_LIB src/core/libraries/fiber/fiber_context.s
src/core/libraries/fiber/fiber_error.h
)
set_source_files_properties(src/core/libraries/fiber/fiber_context.s PROPERTIES COMPILE_OPTIONS -Wno-unused-command-line-argument)
set(VDEC_LIB src/core/libraries/videodec/videodec2_impl.cpp
src/core/libraries/videodec/videodec2_impl.h
src/core/libraries/videodec/videodec2.cpp
@ -584,6 +573,8 @@ set(VDEC_LIB src/core/libraries/videodec/videodec2_impl.cpp
set(NP_LIBS src/core/libraries/np/np_error.h
src/core/libraries/np/np_common.cpp
src/core/libraries/np/np_common.h
src/core/libraries/np/np_commerce.cpp
src/core/libraries/np/np_commerce.h
src/core/libraries/np/np_manager.cpp
src/core/libraries/np/np_manager.h
src/core/libraries/np/np_score.cpp
@ -643,6 +634,7 @@ set(COMPANION_LIBS src/core/libraries/companion/companion_httpd.cpp
)
set(DEV_TOOLS src/core/devtools/layer.cpp
src/core/devtools/layer.h
src/core/devtools/layer_extra.cpp
src/core/devtools/options.cpp
src/core/devtools/options.h
src/core/devtools/gcn/gcn_context_regs.cpp
@ -703,7 +695,6 @@ set(COMMON src/common/logging/backend.cpp
src/common/lru_cache.h
src/common/error.cpp
src/common/error.h
src/common/scope_exit.h
src/common/fixed_value.h
src/common/func_traits.h
src/common/native_clock.cpp
@ -717,6 +708,8 @@ set(COMMON src/common/logging/backend.cpp
src/common/rdtsc.h
src/common/recursive_lock.cpp
src/common/recursive_lock.h
src/common/scope_exit.h
src/common/serdes.h
src/common/sha1.h
src/common/shared_first_mutex.h
src/common/signal_context.h
@ -974,6 +967,8 @@ set(VIDEO_CORE src/video_core/amdgpu/cb_db_extent.h
src/video_core/buffer_cache/buffer.h
src/video_core/buffer_cache/buffer_cache.cpp
src/video_core/buffer_cache/buffer_cache.h
src/video_core/buffer_cache/fault_manager.cpp
src/video_core/buffer_cache/fault_manager.h
src/video_core/buffer_cache/memory_tracker.h
src/video_core/buffer_cache/range_set.h
src/video_core/buffer_cache/region_definitions.h
@ -994,6 +989,8 @@ set(VIDEO_CORE src/video_core/amdgpu/cb_db_extent.h
src/video_core/renderer_vulkan/vk_pipeline_cache.h
src/video_core/renderer_vulkan/vk_pipeline_common.cpp
src/video_core/renderer_vulkan/vk_pipeline_common.h
src/video_core/renderer_vulkan/vk_pipeline_serialization.cpp
src/video_core/renderer_vulkan/vk_pipeline_serialization.h
src/video_core/renderer_vulkan/vk_platform.cpp
src/video_core/renderer_vulkan/vk_platform.h
src/video_core/renderer_vulkan/vk_presenter.cpp
@ -1031,6 +1028,8 @@ set(VIDEO_CORE src/video_core/amdgpu/cb_db_extent.h
src/video_core/texture_cache/tile_manager.cpp
src/video_core/texture_cache/tile_manager.h
src/video_core/texture_cache/types.h
src/video_core/cache_storage.cpp
src/video_core/cache_storage.h
src/video_core/page_manager.cpp
src/video_core/page_manager.h
src/video_core/multi_level_page_table.h
@ -1066,112 +1065,27 @@ set(EMULATOR src/emulator.cpp
src/sdl_window.cpp
)
# The above is shared in SDL and Qt version (TODO share them all)
if(ENABLE_QT_GUI)
qt_add_resources(RESOURCE_FILES src/shadps4.qrc)
if (ENABLE_UPDATER)
set(UPDATER src/qt_gui/check_update.cpp
src/qt_gui/check_update.h
)
endif()
set(QT_GUI src/qt_gui/about_dialog.cpp
src/qt_gui/about_dialog.h
src/qt_gui/about_dialog.ui
src/qt_gui/background_music_player.cpp
src/qt_gui/background_music_player.h
src/qt_gui/cheats_patches.cpp
src/qt_gui/cheats_patches.h
src/qt_gui/compatibility_info.cpp
src/qt_gui/compatibility_info.h
src/qt_gui/control_settings.cpp
src/qt_gui/control_settings.h
src/qt_gui/control_settings.ui
src/qt_gui/kbm_gui.cpp
src/qt_gui/kbm_gui.h
src/qt_gui/kbm_gui.ui
src/qt_gui/main_window_ui.h
src/qt_gui/main_window.cpp
src/qt_gui/main_window.h
src/qt_gui/gui_context_menus.h
src/qt_gui/game_list_utils.h
src/qt_gui/game_info.cpp
src/qt_gui/game_info.h
src/qt_gui/game_list_frame.cpp
src/qt_gui/game_list_frame.h
src/qt_gui/game_grid_frame.cpp
src/qt_gui/game_grid_frame.h
src/qt_gui/game_install_dialog.cpp
src/qt_gui/game_install_dialog.h
src/qt_gui/trophy_viewer.cpp
src/qt_gui/trophy_viewer.h
src/qt_gui/elf_viewer.cpp
src/qt_gui/elf_viewer.h
src/qt_gui/kbm_config_dialog.cpp
src/qt_gui/kbm_config_dialog.h
src/qt_gui/kbm_help_dialog.cpp
src/qt_gui/kbm_help_dialog.h
src/qt_gui/main_window_themes.cpp
src/qt_gui/main_window_themes.h
src/qt_gui/log_presets_dialog.cpp
src/qt_gui/log_presets_dialog.h
src/qt_gui/settings_dialog.cpp
src/qt_gui/settings_dialog.h
src/qt_gui/settings_dialog.ui
src/qt_gui/main.cpp
src/qt_gui/gui_settings.cpp
src/qt_gui/gui_settings.h
src/qt_gui/settings.cpp
src/qt_gui/settings.h
src/qt_gui/sdl_event_wrapper.cpp
src/qt_gui/sdl_event_wrapper.h
src/qt_gui/hotkeys.h
src/qt_gui/hotkeys.cpp
src/qt_gui/hotkeys.ui
${EMULATOR}
${RESOURCE_FILES}
${TRANSLATIONS}
${UPDATER}
add_executable(shadps4
${AUDIO_CORE}
${IMGUI}
${INPUT}
${COMMON}
${CORE}
${SHADER_RECOMPILER}
${VIDEO_CORE}
${EMULATOR}
src/main.cpp
src/emulator.cpp
src/emulator.h
src/sdl_window.h
src/sdl_window.cpp
)
endif()
if (ENABLE_QT_GUI)
qt_add_executable(shadps4
${AUDIO_CORE}
${IMGUI}
${INPUT}
${QT_GUI}
${COMMON}
${CORE}
${SHADER_RECOMPILER}
${VIDEO_CORE}
${EMULATOR}
src/images/shadPS4.icns
)
else()
add_executable(shadps4
${AUDIO_CORE}
${IMGUI}
${INPUT}
${COMMON}
${CORE}
${SHADER_RECOMPILER}
${VIDEO_CORE}
${EMULATOR}
src/main.cpp
src/emulator.cpp
src/emulator.h
src/sdl_window.h
src/sdl_window.cpp
)
endif()
create_target_directory_groups(shadps4)
target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG)
target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 pugixml::pugixml stb::headers libusb::usb lfreist-hwinfo::hwinfo)
target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 SDL3_mixer::SDL3_mixer pugixml::pugixml)
target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz)
target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h")
target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h")
@ -1191,19 +1105,8 @@ endif()
if (APPLE)
# Include MoltenVK, along with an ICD file so it can be found by the system Vulkan loader if used for loading layers.
if (ENABLE_QT_GUI)
set(MVK_BUNDLE_PATH "Resources/vulkan/icd.d")
set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path/../${MVK_BUNDLE_PATH}")
set(MVK_DST ${CMAKE_CURRENT_BINARY_DIR}/shadps4.app/Contents/${MVK_BUNDLE_PATH})
add_custom_command(
OUTPUT ${MVK_DST}
COMMAND ${CMAKE_COMMAND} -E make_directory ${MVK_DST})
else()
set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path")
set(MVK_DST ${CMAKE_CURRENT_BINARY_DIR})
endif()
set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path")
set(MVK_DST ${CMAKE_CURRENT_BINARY_DIR})
set(MVK_DYLIB_SRC ${CMAKE_CURRENT_BINARY_DIR}/externals/MoltenVK/MoltenVK/libMoltenVK.dylib)
set(MVK_DYLIB_DST ${MVK_DST}/libMoltenVK.dylib)
set(MVK_ICD_SRC ${CMAKE_CURRENT_SOURCE_DIR}/externals/MoltenVK/MoltenVK/icd/MoltenVK_icd.json)
@ -1230,31 +1133,23 @@ if (APPLE)
target_link_libraries(shadps4 PRIVATE date::date-tz epoll-shim)
endif()
if (ENABLE_QT_GUI)
target_link_libraries(shadps4 PRIVATE Qt6::Widgets Qt6::Concurrent Qt6::Network Qt6::Multimedia)
add_definitions(-DENABLE_QT_GUI)
if (ENABLE_UPDATER)
add_definitions(-DENABLE_UPDATER)
endif()
endif()
if (WIN32)
target_link_libraries(shadps4 PRIVATE mincore wepoll)
target_link_libraries(shadps4 PRIVATE mincore wepoll wbemuuid)
if (MSVC)
# MSVC likes putting opinions on what people can use, disable:
add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE -D_SCL_SECURE_NO_WARNINGS)
add_compile_definitions(_CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE _SCL_SECURE_NO_WARNINGS)
endif()
add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN)
add_compile_definitions(NOMINMAX WIN32_LEAN_AND_MEAN)
if (MSVC)
# Needed for conflicts with time.h of windows.h
add_definitions(-D_TIMESPEC_DEFINED)
add_compile_definitions(_TIMESPEC_DEFINED)
endif()
# Target Windows 10 RS5
add_definitions(-DNTDDI_VERSION=0x0A000006 -D_WIN32_WINNT=0x0A00 -DWINVER=0x0A00)
add_compile_definitions(NTDDI_VERSION=0x0A000006 _WIN32_WINNT=0x0A00 WINVER=0x0A00)
if (MSVC)
target_link_libraries(shadps4 PRIVATE clang_rt.builtins-x86_64.lib)
@ -1286,7 +1181,7 @@ if (WIN32)
target_sources(shadps4 PRIVATE src/shadps4.rc)
endif()
add_definitions(-DBOOST_ASIO_STANDALONE)
add_compile_definitions(BOOST_ASIO_STANDALONE)
target_include_directories(shadps4 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
@ -1315,25 +1210,6 @@ add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/imgui/renderer)
add_dependencies(shadps4 ImGui_Resources)
target_include_directories(shadps4 PRIVATE ${IMGUI_RESOURCES_INCLUDE})
if (ENABLE_QT_GUI)
set_target_properties(shadps4 PROPERTIES
# WIN32_EXECUTABLE ON
MACOSX_BUNDLE ON
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/dist/MacOSBundleInfo.plist.in"
MACOSX_BUNDLE_ICON_FILE "shadPS4.icns"
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APP_VERSION}"
)
set_source_files_properties(src/images/shadPS4.icns PROPERTIES
MACOSX_PACKAGE_LOCATION Resources)
endif()
if (UNIX AND NOT APPLE)
if (ENABLE_QT_GUI)
find_package(OpenSSL REQUIRED)
target_link_libraries(shadps4 PRIVATE ${OPENSSL_LIBRARIES})
endif()
endif()
# Discord RPC
if (ENABLE_DISCORD_RPC)
@ -1342,10 +1218,3 @@ endif()
# Install rules
install(TARGETS shadps4 BUNDLE DESTINATION .)
if (ENABLE_QT_GUI AND CMAKE_SYSTEM_NAME STREQUAL "Linux")
install(FILES "dist/net.shadps4.shadPS4.desktop" DESTINATION "share/applications")
install(FILES "dist/net.shadps4.shadPS4.metainfo.xml" DESTINATION "share/metainfo")
install(FILES ".github/shadps4.png" DESTINATION "share/icons/hicolor/512x512/apps" RENAME "net.shadps4.shadPS4.png")
install(FILES "src/images/net.shadps4.shadPS4.svg" DESTINATION "share/icons/hicolor/scalable/apps")
endif()

View File

@ -15,15 +15,6 @@
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "x64-Clang-Debug-Qt",
"displayName": "Clang x64 Debug with Qt",
"inherits": ["x64-Clang-Base"],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"ENABLE_QT_GUI": "ON"
}
},
{
"name": "x64-Clang-Release",
"displayName": "Clang x64 Release",
@ -32,15 +23,6 @@
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "x64-Clang-Release-Qt",
"displayName": "Clang x64 Release with Qt",
"inherits": ["x64-Clang-Base"],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"ENABLE_QT_GUI": "ON"
}
},
{
"name": "x64-Clang-RelWithDebInfo",
"displayName": "Clang x64 RelWithDebInfo",
@ -48,15 +30,6 @@
"cacheVariables": {
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
}
},
{
"name": "x64-Clang-RelWithDebInfo-Qt",
"displayName": "Clang x64 RelWithDebInfo with Qt",
"inherits": ["x64-Clang-Base"],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
"ENABLE_QT_GUI": "ON"
}
}
]
}

View File

@ -12,18 +12,6 @@
"inheritEnvironments": [ "clang_cl_x64_x64" ],
"intelliSenseMode": "windows-clang-x64"
},
{
"name": "x64-Clang-Release-Qt",
"generator": "Ninja",
"configurationType": "Release",
"buildRoot": "${projectDir}\\Build\\${name}",
"installRoot": "${projectDir}\\Install\\${name}",
"cmakeCommandArgs": "-DENABLE_QT_GUI=ON",
"buildCommandArgs": "",
"ctestCommandArgs": "",
"inheritEnvironments": [ "clang_cl_x64_x64" ],
"intelliSenseMode": "windows-clang-x64"
},
{
"name": "x64-Clang-Debug",
"generator": "Ninja",
@ -36,18 +24,6 @@
"inheritEnvironments": [ "clang_cl_x64_x64" ],
"intelliSenseMode": "windows-clang-x64"
},
{
"name": "x64-Clang-Debug-Qt",
"generator": "Ninja",
"configurationType": "Debug",
"buildRoot": "${projectDir}\\Build\\${name}",
"installRoot": "${projectDir}\\Install\\${name}",
"cmakeCommandArgs": "-DENABLE_QT_GUI=ON",
"buildCommandArgs": "",
"ctestCommandArgs": "",
"inheritEnvironments": [ "clang_cl_x64_x64" ],
"intelliSenseMode": "windows-clang-x64"
},
{
"name": "x64-Clang-RelWithDebInfo",
"generator": "Ninja",
@ -59,18 +35,6 @@
"ctestCommandArgs": "",
"inheritEnvironments": [ "clang_cl_x64_x64" ],
"intelliSenseMode": "windows-clang-x64"
},
{
"name": "x64-Clang-RelWithDebInfo-Qt",
"generator": "Ninja",
"configurationType": "RelWithDebInfo",
"buildRoot": "${projectDir}\\Build\\${name}",
"installRoot": "${projectDir}\\Install\\${name}",
"cmakeCommandArgs": "-DENABLE_QT_GUI=ON",
"buildCommandArgs": "",
"ctestCommandArgs": "",
"inheritEnvironments": [ "clang_cl_x64_x64" ],
"intelliSenseMode": "windows-clang-x64"
}
]
}

View File

@ -36,6 +36,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
**shadPS4** is an early **PlayStation 4** emulator for **Windows**, **Linux** and **macOS** written in C++.
> [!IMPORTANT]
> This is the emulator core, which does not include a GUI. If you just want to use the emulator as an end user, download the [**QtLauncher**](https://github.com/shadps4-emu/shadps4-qtlauncher/releases) instead.
If you encounter problems or have doubts, do not hesitate to look at the [**Quickstart**](https://github.com/shadps4-emu/shadPS4/wiki/I.-Quick-start-%5BUsers%5D).\
To verify that a game works, you can look at [**shadPS4 Game Compatibility**](https://github.com/shadps4-compatibility/shadps4-game-compatibility).\
To discuss shadPS4 development, suggest ideas or to ask for help, join our [**Discord server**](https://discord.gg/bFJxfftGW6).\
@ -55,9 +58,6 @@ This project began for fun. Given our limited free time, it may take some time b
# Building
> [!IMPORTANT]
> If you want to use shadPS4 to play your games, you don't have to follow the build instructions, you can simply download the emulator from either the [**release tab**](https://github.com/shadps4-emu/shadPS4/releases) or the [**action tab**](https://github.com/shadps4-emu/shadPS4/actions).
## Windows
Check the build instructions for [**Windows**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-windows.md).
@ -73,6 +73,22 @@ Check the build instructions for [**macOS**](https://github.com/shadps4-emu/shad
> [!IMPORTANT]
> macOS users need at least macOS 15.4 to run shadPS4. Due to GPU issues there are currently heavy bugs on Intel Macs.
# Usage examples
> [!IMPORTANT]
> For a user-friendly GUI, download the [**QtLauncher**](https://github.com/shadps4-emu/shadps4-qtlauncher/releases).
To get the list of all available commands and also a more detailed description of what each command does, please refer to the `--help` flag's output.
Below is a list of commonly used command patterns:
```sh
shadPS4 CUSA00001 # Searches for a game folder called CUSA00001 in the list of game install folders, and boots it.
shadPS4 --fullscreen true --config-clean CUSA00001 # the game argument is always the last one,
shadPS4 -g CUSA00001 --fullscreen true --config-clean # ...unless manually specified otherwise.
shadPS4 /path/to/game.elf # Boots a PS4 ELF file directly. Useful if you want to boot an executable that is not named eboot.bin.
shadPS4 CUSA00001 -- -flag1 -flag2 # Passes '-flag1' and '-flag2' to the game executable in argv.
```
# Debugging and reporting issues
For more information on how to test, debug and report issues with the emulator or games, read the [**Debugging documentation**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Debugging/Debugging.md).
@ -160,15 +176,6 @@ Logo is done by [**Xphalnos**](https://github.com/Xphalnos)
If you want to contribute, please read the [**CONTRIBUTING.md**](https://github.com/shadps4-emu/shadPS4/blob/main/CONTRIBUTING.md) file.\
Open a PR and we'll check it :)
# Translations
If you want to translate shadPS4 to your language we use [**Crowdin**](https://crowdin.com/project/shadps4-emulator).
# Contributors
<a href="https://github.com/shadps4-emu/shadPS4/graphs/contributors">
<img src="https://contrib.rocks/image?repo=shadps4-emu/shadPS4&max=24">
</a>
# Special Thanks

View File

@ -74,7 +74,6 @@ path = [
"src/images/trophy.wav",
"src/images/hotkey.png",
"src/images/game_settings.png",
"src/shadps4.qrc",
"src/shadps4.rc",
]
precedence = "aggregate"
@ -130,9 +129,4 @@ SPDX-License-Identifier = "MIT"
[[annotations]]
path = "src/video_core/host_shaders/fsr/*"
SPDX-FileCopyrightText = "Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved."
SPDX-License-Identifier = "MIT"
[[annotations]]
path = "dist/qt.conf"
SPDX-FileCopyrightText = "shadPS4 Emulator Project"
SPDX-License-Identifier = "GPL-2.0-or-later"
SPDX-License-Identifier = "MIT"

View File

@ -1,28 +0,0 @@
# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
set(highest_version "0")
set(CANDIDATE_DRIVES A B C D E F G H I J K L M N O P Q R S T U V W X Y Z)
foreach(drive ${CANDIDATE_DRIVES})
file(GLOB kits LIST_DIRECTORIES true CONFIGURE_DEPENDS "${drive}:/Qt/*/msvc*_64")
foreach(kit IN LISTS kits)
get_filename_component(version_dir "${kit}" DIRECTORY)
get_filename_component(kit_version "${version_dir}" NAME)
message(STATUS "DetectQtInstallation.cmake: Detected Qt: ${kit}")
if (kit_version VERSION_GREATER highest_version)
set(highest_version "${kit_version}")
set(QT_PREFIX "${kit}")
endif()
endforeach()
endforeach()
if(QT_PREFIX)
set(CMAKE_PREFIX_PATH "${QT_PREFIX}" CACHE PATH "Qt prefix autodetected" FORCE)
message(STATUS "DetectQtInstallation.cmake: Choose newest Qt: ${QT_PREFIX}")
else()
message(STATUS "DetectQtInstallation.cmake: No QtDirectory found in <drive>:/Qt please set CMAKE_PREFIX_PATH manually")
endif()

View File

@ -18,25 +18,28 @@
<screenshots>
<screenshot type="default">
<image type="source" translate="no" >https://cdn.jsdelivr.net/gh/shadps4-emu/shadps4@main/documents/Screenshots/1.png</image>
<caption>Bloodborne</caption>
<caption>Bloodborne by From Software</caption>
</screenshot>
<screenshot>
<image type="source" translate="no">https://cdn.jsdelivr.net/gh/shadps4-emu/shadps4@main/documents/Screenshots/2.png</image>
<caption>Hatsune Miku: Project DIVA Future Tone</caption>
<caption>Hatsune Miku Project DIVA Future Tone by SEGA</caption>
</screenshot>
<screenshot>
<image type="source" translate="no">https://cdn.jsdelivr.net/gh/shadps4-emu/shadps4@main/documents/Screenshots/3.png</image>
<caption>Yakuza 0</caption>
<caption>Yakuza 0 by SEGA</caption>
</screenshot>
<screenshot>
<image type="source" translate="no">https://cdn.jsdelivr.net/gh/shadps4-emu/shadps4@main/documents/Screenshots/4.png</image>
<caption>Persona 4 Golden</caption>
<caption>DRIVECLUB™ by Evolution Studios</caption>
</screenshot>
</screenshots>
<categories>
<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>

2
dist/qt.conf vendored
View File

@ -1,2 +0,0 @@
[Paths]
plugins = "./qtplugins"

View File

@ -17,8 +17,7 @@ First and foremost, Clang 18 is the **recommended compiler** as it is used for o
sudo apt install build-essential clang git cmake libasound2-dev \
libpulse-dev libopenal-dev libssl-dev zlib1g-dev libedit-dev \
libudev-dev libevdev-dev libsdl2-dev libjack-dev libsndio-dev \
qt6-base-dev qt6-tools-dev qt6-multimedia-dev libvulkan-dev \
vulkan-validationlayers libpng-dev
libvulkan-dev vulkan-validationlayers libpng-dev
```
#### Fedora
@ -27,8 +26,6 @@ sudo apt install build-essential clang git cmake libasound2-dev \
sudo dnf install clang git cmake libatomic alsa-lib-devel \
pipewire-jack-audio-connection-kit-devel openal-soft-devel \
openssl-devel libevdev-devel libudev-devel libXext-devel \
qt6-qtbase-devel qt6-qtbase-private-devel \
qt6-qtmultimedia-devel qt6-qtsvg-devel qt6-qttools-devel \
vulkan-devel vulkan-validation-layers libpng-devel libuuid-devel
```
@ -36,8 +33,7 @@ sudo dnf install clang git cmake libatomic alsa-lib-devel \
```bash
sudo pacman -S base-devel clang git cmake sndio jack2 openal \
qt6-base qt6-declarative qt6-multimedia qt6-tools sdl2 \
vulkan-validation-layers libpng
sdl2 vulkan-validation-layers libpng
```
**Note**: The `shadps4-git` AUR package is not maintained by any of the developers, and it uses the default compiler, which is often set to GCC. Use at your own discretion.
@ -48,9 +44,7 @@ sudo pacman -S base-devel clang git cmake sndio jack2 openal \
sudo zypper install clang git cmake libasound2 libpulse-devel \
libsndio7 libjack-devel openal-soft-devel libopenssl-devel \
zlib-devel libedit-devel systemd-devel libevdev-devel \
qt6-base-devel qt6-multimedia-devel qt6-svg-devel \
qt6-linguist-devel qt6-gui-private-devel vulkan-devel \
vulkan-validationlayers libpng-devel
vulkan-devel vulkan-validationlayers libpng-devel
```
#### NixOS
@ -90,12 +84,12 @@ There are 3 options you can choose from. Option 1 is **highly recommended**.
1. Generate the build directory in the shadPS4 directory.
```bash
cmake -S . -B build/ -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
cmake -S . -B build/ -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
```
To disable the Qt GUI, remove the `-DENABLE_QT_GUI=ON` flag. To change the build type (for debugging), add `-DCMAKE_BUILD_TYPE=Debug`.
To change the build type (for debugging), add `-DCMAKE_BUILD_TYPE=Debug`.
2. Use CMake to build the project:
1. Use CMake to build the project:
```bash
cmake --build ./build --parallel$(nproc)
@ -103,18 +97,12 @@ cmake --build ./build --parallel$(nproc)
If your computer freezes during this step, this could be caused by excessive system resource usage. In that case, remove `--parallel$(nproc)`.
Now run the emulator. If Qt was enabled at configure time:
Now run the emulator to get the list of options:
```bash
./build/shadps4
```
Otherwise, specify the path to your game's boot file:
```bash
./build/shadps4 /"PATH"/"TO"/"GAME"/"FOLDER"/eboot.bin
```
You can also specify the Game ID as an argument for which game to boot, as long as the folder containing the games is specified in config.toml (example: Bloodborne (US) is CUSA00900).
#### Option 2: Configuring with cmake-gui
@ -142,10 +130,6 @@ Go to Settings, filter by `@ext:ms-vscode.cmake-tools configure` and disable thi
![image](https://raw.githubusercontent.com/shadps4-emu/shadPS4/refs/heads/main/documents/Screenshots/Linux/1.png)
If you wish to build with the Qt GUI, add `-DENABLE_QT_GUI=ON` to the configure arguments:
![image](https://raw.githubusercontent.com/shadps4-emu/shadPS4/refs/heads/main/documents/Screenshots/Linux/2.png)
On the CMake tab, change the options as you wish, but make sure that it looks similar to or exactly like this:
![image](https://raw.githubusercontent.com/shadps4-emu/shadPS4/refs/heads/main/documents/Screenshots/Linux/4.png)

View File

@ -24,21 +24,6 @@ eval $(/opt/homebrew/bin/brew shellenv)
brew install clang-format cmake
```
Next, install x86_64 Qt. You can skip these steps and move on to **Cloning and compiling** if you do not intend to build the Qt GUI.
**If you are on an ARM Mac:**
```
# Installs x86_64 Homebrew to /usr/local
arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Installs libraries.
arch -x86_64 /usr/local/bin/brew install qt@6
```
**If you are on an x86_64 Mac:**
```
brew install qt@6
```
### Cloning and compiling:
Clone the repository recursively:
@ -52,8 +37,6 @@ Generate the build directory in the shadPS4 directory:
cmake -S . -B build/ -DCMAKE_OSX_ARCHITECTURES=x86_64
```
If you want to build the Qt GUI, add `-DENABLE_QT_GUI=ON` to the end of this command as well.
Enter the directory:
```
cd build/

View File

@ -22,23 +22,6 @@ Once you are within the installer:
2. Go to "Individual Components" tab then search and select both `C++ Clang Compiler for Windows` and `MSBuild support for LLVM`
3. Continue the installation
### (Prerequisite) Download [**Qt**](https://doc.qt.io/qt-6/get-and-install-qt.html)
Beware, this requires you to create a Qt account. If you do not want to do this, please follow the MSYS2/MinGW compilation method instead.
1. Under the current, non beta version of Qt, select the option `MSVC 2022 64-bit` or similar, as well as `QT Multimedia`.
If you are on Windows on ARM / Qualcomm Snapdragon Elite X, select `MSVC 2022 ARM64` instead.
Go through the installation normally. If you know what you are doing, you may unselect individual components that eat up too much disk space.
2. Download and install [Qt Visual Studio Tools](https://marketplace.visualstudio.com/items?itemName=TheQtCompany.QtVisualStudioTools2022)
Once you are finished, you will have to configure Qt within Visual Studio:
1. Tools -> Options -> Qt -> Versions
2. Add a new Qt version and navigate it to the correct folder. Should look like so: `C:\Qt\<QtVersion>\msvc2022_64`
3. Enable the default checkmark on the new version you just created.
### (Prerequisite) Download [**Git for Windows**](https://git-scm.com/download/win)
Go through the Git for Windows installation as normal
@ -53,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]
@ -79,13 +57,10 @@ Normal x86-based computers, follow:
1. Open "MSYS2 MINGW64" from your new applications
2. Run `pacman -Syu`, let it complete;
3. Run `pacman -S --needed git mingw-w64-x86_64-binutils mingw-w64-x86_64-clang mingw-w64-x86_64-cmake mingw-w64-x86_64-rapidjson mingw-w64-x86_64-ninja mingw-w64-x86_64-ffmpeg`
1. Optional (Qt only): run `pacman -S --needed mingw-w64-x86_64-qt6-base mingw-w64-x86_64-qt6-tools mingw-w64-x86_64-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"`
1. Optional (Qt only): add `-DENABLE_QT_GUI=ON`
7. Run `cmake --build build`
1. Optional (Qt only): run `windeployqt6 build/shadps4.exe`
8. To run the finished product, run `./build/shadPS4.exe`
ARM64-based computers, follow:
@ -93,13 +68,10 @@ 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"`
1. Optional (Qt only): add `-DENABLE_QT_GUI=ON`
7. Run `cmake --build build`
1. Optional (Qt only): run `windeployqt6 build/shadps4.exe`
8. To run the finished product, run `./build/shadPS4.exe`
## Note on MSYS2 builds

View File

@ -63,6 +63,18 @@ if (NOT TARGET SDL3::SDL3)
add_subdirectory(sdl3)
endif()
# SDL3_mixer
if (NOT TARGET SDL3_mixer::SDL3_mixer)
set(SDLMIXER_FLAC OFF)
set(SDLMIXER_OGG OFF)
set(SDLMIXER_MOD OFF)
set(SDLMIXER_MIDI OFF)
set(SDLMIXER_OPUS OFF)
set(SDLMIXER_WAVPACK OFF)
set(BUILD_SHARED_LIBS OFF)
add_subdirectory(sdl3_mixer)
endif()
# vulkan-headers
if (NOT TARGET Vulkan::Headers)
set(VULKAN_HEADERS_ENABLE_MODULE OFF)
@ -140,7 +152,7 @@ endif()
# sirit
add_subdirectory(sirit)
if (WIN32)
target_compile_options(sirit PUBLIC "-Wno-error=unused-command-line-argument")
target_compile_options(sirit PRIVATE "-Wno-error=unused-command-line-argument")
endif()
# half
@ -245,3 +257,10 @@ endif()
if (WIN32)
add_subdirectory(ext-wepoll)
endif()
#nlohmann json
set(JSON_BuildTests OFF CACHE INTERNAL "")
add_subdirectory(json)
# miniz
add_subdirectory(miniz)

2
externals/MoltenVK vendored

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

@ -1 +1 @@
Subproject commit b0de1dcca26c0ebfb8011b8e59dd17fc399db0ff
Subproject commit 94dde08c8a9e4271a93a2a7e4159e9fb05d30c0a

2
externals/fmt vendored

@ -1 +1 @@
Subproject commit 64db979e38ec644b1798e41610b28c8d2c8a2739
Subproject commit ec73fb72477d80926c758894a3ab2cb3994fd051

1
externals/json vendored Submodule

@ -0,0 +1 @@
Subproject commit 55f93686c01528224f448c19128836e7df245f72

1
externals/miniz vendored Submodule

@ -0,0 +1 @@
Subproject commit 174573d60290f447c13a2b1b3405de2b96e27d6c

2
externals/sdl3 vendored

@ -1 +1 @@
Subproject commit e9c2e9bfc3a6e1e70596f743fa9e1fc5fadabef7
Subproject commit bdb72bb3f051de32c91f5deb439a50bfd51499dc

1
externals/sdl3_mixer vendored Submodule

@ -0,0 +1 @@
Subproject commit 4182794ea45fe28568728670c6f1583855d0e85c

View File

@ -6,54 +6,47 @@ 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.openssl
pkgs.zlib
pkgs.libedit
pkgs.udev
pkgs.libevdev
pkgs.SDL2
pkgs.jack2
pkgs.sndio
pkgs.qt6.qtbase
pkgs.qt6.qttools
pkgs.qt6.qtmultimedia
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.qt6.qtwayland
pkgs.wayland-protocols
pkgs.libpng
ffmpeg
fmt
glslang
libxkbcommon
wayland
xorg.libxcb
xorg.xcbutil
xorg.xcbutilkeysyms
xorg.xcbutilwm
sdl3
stb
wayland-protocols
libpng
];
shellHook = ''
echo "Entering shadPS4 dev shell"
export QT_QPA_PLATFORM="wayland"
export QT_PLUGIN_PATH="${pkgs.qt6.qtwayland}/lib/qt-6/plugins:${pkgs.qt6.qtbase}/lib/qt-6/plugins"
export QML2_IMPORT_PATH="${pkgs.qt6.qtbase}/lib/qt-6/qml"
export CMAKE_PREFIX_PATH="${pkgs.vulkan-headers}:$CMAKE_PREFIX_PATH"
# OpenGL

View File

@ -139,13 +139,10 @@ static ConfigEntry<double> trophyNotificationDuration(6.0);
static ConfigEntry<string> logFilter("");
static ConfigEntry<string> logType("sync");
static ConfigEntry<string> userName("shadPS4");
static ConfigEntry<string> chooseHomeTab("General");
static ConfigEntry<bool> isShowSplash(false);
static ConfigEntry<string> isSideTrophy("right");
static ConfigEntry<bool> isConnectedToNetwork(false);
static bool enableDiscordRPC = false;
static bool checkCompatibilityOnStartup = false;
static bool compatibilityData = false;
static std::filesystem::path sys_modules_path = {};
// Input
@ -180,7 +177,7 @@ static ConfigEntry<bool> isFullscreen(false);
static ConfigEntry<string> fullscreenMode("Windowed");
static ConfigEntry<string> presentMode("Mailbox");
static ConfigEntry<bool> isHDRAllowed(false);
static ConfigEntry<bool> fsrEnabled(true);
static ConfigEntry<bool> fsrEnabled(false);
static ConfigEntry<bool> rcasEnabled(true);
static ConfigEntry<int> rcasAttenuation(250);
@ -194,16 +191,18 @@ static ConfigEntry<bool> vkCrashDiagnostic(false);
static ConfigEntry<bool> vkHostMarkers(false);
static ConfigEntry<bool> vkGuestMarkers(false);
static ConfigEntry<bool> rdocEnable(false);
static ConfigEntry<bool> pipelineCacheEnable(false);
static ConfigEntry<bool> pipelineCacheArchive(false);
// Debug
static ConfigEntry<bool> isDebugDump(false);
static ConfigEntry<bool> isShaderDebug(false);
static ConfigEntry<bool> isSeparateLogFilesEnabled(false);
static ConfigEntry<bool> isFpsColor(true);
static ConfigEntry<bool> showFpsCounter(false);
static ConfigEntry<bool> logEnabled(true);
// GUI
static bool load_game_size = true;
static std::vector<GameInstallDir> settings_install_dirs = {};
std::vector<bool> install_dirs_enabled = {};
std::filesystem::path settings_addon_install_dir = {};
@ -212,15 +211,28 @@ std::filesystem::path save_data_path = {};
// Settings
ConfigEntry<u32> m_language(1); // english
// USB Device
static ConfigEntry<int> usbDeviceBackend(UsbBackendType::Real);
// Keys
static string trophyKey = "";
// Config version, used to determine if a user's config file is outdated.
static string config_version = Common::g_scm_rev;
// These two entries aren't stored in the config
// These entries aren't stored in the config
static bool overrideControllerColor = false;
static int controllerCustomColorRGB[3] = {0, 0, 255};
static bool isGameRunning = false;
static bool load_auto_patches = true;
bool getGameRunning() {
return isGameRunning;
}
void setGameRunning(bool running) {
isGameRunning = running;
}
std::filesystem::path getSysModulesPath() {
if (sys_modules_path.empty()) {
@ -278,10 +290,6 @@ void setTrophyKey(string key) {
trophyKey = key;
}
bool GetLoadGameSizeEnabled() {
return load_game_size;
}
std::filesystem::path GetSaveDataPath() {
if (save_data_path.empty()) {
return Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "savedata";
@ -293,10 +301,6 @@ void setVolumeSlider(int volumeValue, bool is_game_specific) {
volumeSlider.set(volumeValue, is_game_specific);
}
void setLoadGameSizeEnabled(bool enable) {
load_game_size = enable;
}
bool isNeoModeConsole() {
return isNeo.get();
}
@ -309,8 +313,10 @@ int getExtraDmemInMbytes() {
return extraDmemInMbytes.get();
}
void setExtraDmemInMbytes(int value) {
extraDmemInMbytes.base_value = 0;
void setExtraDmemInMbytes(int value, bool is_game_specific) {
// Disable setting in global config
is_game_specific ? extraDmemInMbytes.game_specific_value = value
: extraDmemInMbytes.base_value = 0;
}
bool getIsFullscreen() {
@ -389,10 +395,6 @@ string getUserName() {
return userName.get();
}
string getChooseHomeTab() {
return chooseHomeTab.get();
}
bool getUseSpecialPad() {
return useSpecialPad.get();
}
@ -453,10 +455,26 @@ bool isRdocEnabled() {
return rdocEnable.get();
}
bool isPipelineCacheEnabled() {
return pipelineCacheEnable.get();
}
bool isPipelineCacheArchived() {
return pipelineCacheArchive.get();
}
bool fpsColor() {
return isFpsColor.get();
}
bool getShowFpsCounter() {
return showFpsCounter.get();
}
void setShowFpsCounter(bool enable, bool is_game_specific) {
showFpsCounter.set(enable, is_game_specific);
}
bool isLoggingEnabled() {
return logEnabled.get();
}
@ -508,14 +526,6 @@ void setVkGuestMarkersEnabled(bool enable, bool is_game_specific) {
vkGuestMarkers.set(enable, is_game_specific);
}
bool getCompatibilityEnabled() {
return compatibilityData;
}
bool getCheckCompatibilityOnStartup() {
return checkCompatibilityOnStartup;
}
bool getIsConnectedToNetwork() {
return isConnectedToNetwork.get();
}
@ -600,10 +610,26 @@ void setVkSyncValidation(bool enable, bool is_game_specific) {
vkValidationSync.set(enable, is_game_specific);
}
void setVkCoreValidation(bool enable, bool is_game_specific) {
vkValidationCore.set(enable, is_game_specific);
}
void setVkGpuValidation(bool enable, bool is_game_specific) {
vkValidationGpu.set(enable, is_game_specific);
}
void setRdocEnabled(bool enable, bool is_game_specific) {
rdocEnable.set(enable, is_game_specific);
}
void setPipelineCacheEnabled(bool enable, bool is_game_specific) {
pipelineCacheEnable.set(enable, is_game_specific);
}
void setPipelineCacheArchived(bool enable, bool is_game_specific) {
pipelineCacheArchive.set(enable, is_game_specific);
}
void setVblankFreq(u32 value, bool is_game_specific) {
vblankFrequency.set(value, is_game_specific);
}
@ -680,10 +706,6 @@ void setUserName(const string& name, bool is_game_specific) {
userName.set(name, is_game_specific);
}
void setChooseHomeTab(const string& type, bool is_game_specific) {
chooseHomeTab.set(type, is_game_specific);
}
void setUseSpecialPad(bool use) {
useSpecialPad.base_value = use;
}
@ -696,14 +718,6 @@ void setIsMotionControlsEnabled(bool use, bool is_game_specific) {
isMotionControlsEnabled.set(use, is_game_specific);
}
void setCompatibilityEnabled(bool use) {
compatibilityData = use;
}
void setCheckCompatibilityOnStartup(bool use) {
checkCompatibilityOnStartup = use;
}
bool addGameInstallDir(const std::filesystem::path& dir, bool enabled) {
for (const auto& install_dir : settings_install_dirs) {
if (install_dir.path == dir) {
@ -833,6 +847,21 @@ void setRcasAttenuation(int value, bool is_game_specific) {
rcasAttenuation.set(value, is_game_specific);
}
int getUsbDeviceBackend() {
return usbDeviceBackend.get();
}
void setUsbDeviceBackend(int value, bool is_game_specific) {
usbDeviceBackend.set(value, is_game_specific);
}
bool getLoadAutoPatches() {
return load_auto_patches;
}
void setLoadAutoPatches(bool enable) {
load_auto_patches = enable;
}
void load(const std::filesystem::path& path, bool is_game_specific) {
// If the configuration file does not exist, create it and return, unless it is game specific
std::error_code error;
@ -874,12 +903,8 @@ void load(const std::filesystem::path& path, bool is_game_specific) {
userName.setFromToml(general, "userName", is_game_specific);
isShowSplash.setFromToml(general, "showSplash", is_game_specific);
isSideTrophy.setFromToml(general, "sideTrophy", is_game_specific);
compatibilityData = toml::find_or<bool>(general, "compatibilityEnabled", compatibilityData);
checkCompatibilityOnStartup = toml::find_or<bool>(general, "checkCompatibilityOnStartup",
checkCompatibilityOnStartup);
isConnectedToNetwork.setFromToml(general, "isConnectedToNetwork", is_game_specific);
chooseHomeTab.setFromToml(general, "chooseHomeTab", is_game_specific);
defaultControllerID.setFromToml(general, "defaultControllerID", is_game_specific);
sys_modules_path = toml::find_fs_path_or(general, "sysModulesPath", sys_modules_path);
}
@ -894,6 +919,7 @@ void load(const std::filesystem::path& path, bool is_game_specific) {
isMotionControlsEnabled.setFromToml(input, "isMotionControlsEnabled", is_game_specific);
useUnifiedInputConfig.setFromToml(input, "useUnifiedInputConfig", is_game_specific);
backgroundControllerInput.setFromToml(input, "backgroundControllerInput", is_game_specific);
usbDeviceBackend.setFromToml(input, "usbDeviceBackend", is_game_specific);
}
if (data.contains("Audio")) {
@ -940,6 +966,8 @@ void load(const std::filesystem::path& path, bool is_game_specific) {
vkHostMarkers.setFromToml(vk, "hostMarkers", is_game_specific);
vkGuestMarkers.setFromToml(vk, "guestMarkers", is_game_specific);
rdocEnable.setFromToml(vk, "rdocEnable", is_game_specific);
pipelineCacheEnable.setFromToml(vk, "pipelineCacheEnable", is_game_specific);
pipelineCacheArchive.setFromToml(vk, "pipelineCacheArchive", is_game_specific);
}
string current_version = {};
@ -950,6 +978,7 @@ void load(const std::filesystem::path& path, bool is_game_specific) {
isSeparateLogFilesEnabled.setFromToml(debug, "isSeparateLogFilesEnabled", is_game_specific);
isShaderDebug.setFromToml(debug, "CollectShader", is_game_specific);
isFpsColor.setFromToml(debug, "FPSColor", is_game_specific);
showFpsCounter.setFromToml(debug, "showFpsCounter", is_game_specific);
logEnabled.setFromToml(debug, "logEnabled", is_game_specific);
current_version = toml::find_or<std::string>(debug, "ConfigVersion", current_version);
}
@ -957,8 +986,6 @@ void load(const std::filesystem::path& path, bool is_game_specific) {
if (data.contains("GUI")) {
const toml::value& gui = data.at("GUI");
load_game_size = toml::find_or<bool>(gui, "loadGameSizeEnabled", load_game_size);
const auto install_dir_array =
toml::find_or<std::vector<std::u8string>>(gui, "installDirs", {});
@ -1062,7 +1089,6 @@ void save(const std::filesystem::path& path, bool is_game_specific) {
logFilter.setTomlValue(data, "General", "logFilter", is_game_specific);
logType.setTomlValue(data, "General", "logType", is_game_specific);
userName.setTomlValue(data, "General", "userName", is_game_specific);
chooseHomeTab.setTomlValue(data, "General", "chooseHomeTab", is_game_specific);
isShowSplash.setTomlValue(data, "General", "showSplash", is_game_specific);
isSideTrophy.setTomlValue(data, "General", "sideTrophy", is_game_specific);
isNeo.setTomlValue(data, "General", "isPS4Pro", is_game_specific);
@ -1079,6 +1105,7 @@ void save(const std::filesystem::path& path, bool is_game_specific) {
is_game_specific);
backgroundControllerInput.setTomlValue(data, "Input", "backgroundControllerInput",
is_game_specific);
usbDeviceBackend.setTomlValue(data, "Input", "usbDeviceBackend", is_game_specific);
micDevice.setTomlValue(data, "Audio", "micDevice", is_game_specific);
mainOutputDevice.setTomlValue(data, "Audio", "mainOutputDevice", is_game_specific);
@ -1104,10 +1131,14 @@ void save(const std::filesystem::path& path, bool is_game_specific) {
gpuId.setTomlValue(data, "Vulkan", "gpuId", is_game_specific);
vkValidation.setTomlValue(data, "Vulkan", "validation", is_game_specific);
vkValidationSync.setTomlValue(data, "Vulkan", "validation_sync", is_game_specific);
vkValidationCore.setTomlValue(data, "Vulkan", "validation_core", is_game_specific);
vkValidationGpu.setTomlValue(data, "Vulkan", "validation_gpu", is_game_specific);
vkCrashDiagnostic.setTomlValue(data, "Vulkan", "crashDiagnostic", is_game_specific);
vkHostMarkers.setTomlValue(data, "Vulkan", "hostMarkers", is_game_specific);
vkGuestMarkers.setTomlValue(data, "Vulkan", "guestMarkers", is_game_specific);
rdocEnable.setTomlValue(data, "Vulkan", "rdocEnable", is_game_specific);
pipelineCacheEnable.setTomlValue(data, "Vulkan", "pipelineCacheEnable", is_game_specific);
pipelineCacheArchive.setTomlValue(data, "Vulkan", "pipelineCacheArchive", is_game_specific);
isDebugDump.setTomlValue(data, "Debug", "DebugDump", is_game_specific);
isShaderDebug.setTomlValue(data, "Debug", "CollectShader", is_game_specific);
@ -1149,13 +1180,10 @@ void save(const std::filesystem::path& path, bool is_game_specific) {
// Non game-specific entries
data["General"]["enableDiscordRPC"] = enableDiscordRPC;
data["General"]["compatibilityEnabled"] = compatibilityData;
data["General"]["checkCompatibilityOnStartup"] = checkCompatibilityOnStartup;
data["General"]["sysModulesPath"] = string{fmt::UTF(sys_modules_path.u8string()).data};
data["GUI"]["installDirs"] = install_dirs;
data["GUI"]["installDirsEnabled"] = install_dirs_enabled;
data["GUI"]["saveDataPath"] = string{fmt::UTF(save_data_path.u8string()).data};
data["GUI"]["loadGameSizeEnabled"] = load_game_size;
data["GUI"]["addonInstallDir"] =
string{fmt::UTF(settings_addon_install_dir.u8string()).data};
data["Debug"]["ConfigVersion"] = config_version;
@ -1169,9 +1197,8 @@ void save(const std::filesystem::path& path, bool is_game_specific) {
data["GPU"]["internalScreenWidth"] = internalScreenWidth.base_value;
data["GPU"]["internalScreenHeight"] = internalScreenHeight.base_value;
data["GPU"]["patchShaders"] = shouldPatchShaders.base_value;
data["Vulkan"]["validation_core"] = vkValidationCore.base_value;
data["Vulkan"]["validation_gpu"] = vkValidationGpu.base_value;
data["Debug"]["FPSColor"] = isFpsColor.base_value;
data["Debug"]["showFpsCounter"] = showFpsCounter.base_value;
}
// Sorting of TOML sections
@ -1205,7 +1232,6 @@ void setDefaultValues(bool is_game_specific) {
logFilter.set("", is_game_specific);
logType.set("sync", is_game_specific);
userName.set("shadPS4", is_game_specific);
chooseHomeTab.set("General", is_game_specific);
isShowSplash.set(false, is_game_specific);
isSideTrophy.set("right", is_game_specific);
@ -1214,6 +1240,7 @@ void setDefaultValues(bool is_game_specific) {
cursorHideTimeout.set(5, is_game_specific);
isMotionControlsEnabled.set(true, is_game_specific);
backgroundControllerInput.set(false, is_game_specific);
usbDeviceBackend.set(UsbBackendType::Real, is_game_specific);
// GS - Audio
micDevice.set("Default Device", is_game_specific);
@ -1243,6 +1270,8 @@ void setDefaultValues(bool is_game_specific) {
vkHostMarkers.set(false, is_game_specific);
vkGuestMarkers.set(false, is_game_specific);
rdocEnable.set(false, is_game_specific);
pipelineCacheEnable.set(false, is_game_specific);
pipelineCacheArchive.set(false, is_game_specific);
// GS - Debug
isDebugDump.set(false, is_game_specific);
@ -1258,8 +1287,6 @@ void setDefaultValues(bool is_game_specific) {
// General
enableDiscordRPC = false;
compatibilityData = false;
checkCompatibilityOnStartup = false;
// Input
useSpecialPad.base_value = false;
@ -1278,11 +1305,9 @@ void setDefaultValues(bool is_game_specific) {
internalScreenWidth.base_value = 1280;
internalScreenHeight.base_value = 720;
// GUI
load_game_size = true;
// Debug
isFpsColor.base_value = true;
showFpsCounter.base_value = false;
}
}
@ -1297,6 +1322,7 @@ hotkey_pause = f9
hotkey_reload_inputs = f8
hotkey_toggle_mouse_to_joystick = f7
hotkey_toggle_mouse_to_gyro = f6
hotkey_toggle_mouse_to_touchpad = delete
hotkey_quit = lctrl, lshift, end
)";
}

View File

@ -27,6 +27,8 @@ void load(const std::filesystem::path& path, bool is_game_specific = false);
void save(const std::filesystem::path& path, bool is_game_specific = false);
void resetGameSpecificValue(std::string entry);
bool getGameRunning();
void setGameRunning(bool running);
int getVolumeSlider();
void setVolumeSlider(int volumeValue, bool is_game_specific = false);
std::string getTrophyKey();
@ -79,6 +81,10 @@ bool vkValidationEnabled();
void setVkValidation(bool enable, bool is_game_specific = false);
bool vkValidationSyncEnabled();
void setVkSyncValidation(bool enable, bool is_game_specific = false);
bool vkValidationGpuEnabled();
void setVkGpuValidation(bool enable, bool is_game_specific = false);
bool vkValidationCoreEnabled();
void setVkCoreValidation(bool enable, bool is_game_specific = false);
bool getVkCrashDiagnosticEnabled();
void setVkCrashDiagnosticEnabled(bool enable, bool is_game_specific = false);
bool getVkHostMarkersEnabled();
@ -88,7 +94,11 @@ void setVkGuestMarkersEnabled(bool enable, bool is_game_specific = false);
bool getEnableDiscordRPC();
void setEnableDiscordRPC(bool enable);
bool isRdocEnabled();
bool isPipelineCacheEnabled();
bool isPipelineCacheArchived();
void setRdocEnabled(bool enable, bool is_game_specific = false);
void setPipelineCacheEnabled(bool enable, bool is_game_specific = false);
void setPipelineCacheArchived(bool enable, bool is_game_specific = false);
std::string getLogType();
void setLogType(const std::string& type, bool is_game_specific = false);
std::string getLogFilter();
@ -97,11 +107,10 @@ double getTrophyNotificationDuration();
void setTrophyNotificationDuration(double newTrophyNotificationDuration,
bool is_game_specific = false);
int getCursorHideTimeout();
void setCursorHideTimeout(int newcursorHideTimeout);
std::string getMainOutputDevice();
void setMainOutputDevice(std::string device);
void setMainOutputDevice(std::string device, bool is_game_specific = false);
std::string getPadSpkOutputDevice();
void setPadSpkOutputDevice(std::string device);
void setPadSpkOutputDevice(std::string device, bool is_game_specific = false);
std::string getMicDevice();
void setCursorHideTimeout(int newcursorHideTimeout, bool is_game_specific = false);
void setMicDevice(std::string device, bool is_game_specific = false);
@ -117,15 +126,15 @@ bool getPSNSignedIn();
void setPSNSignedIn(bool sign, bool is_game_specific = false);
bool patchShaders(); // no set
bool fpsColor(); // no set
bool getShowFpsCounter();
void setShowFpsCounter(bool enable, bool is_game_specific = false);
bool isNeoModeConsole();
void setNeoMode(bool enable, bool is_game_specific = false);
bool isDevKitConsole();
void setDevKitConsole(bool enable, bool is_game_specific = false);
bool vkValidationCoreEnabled(); // no set
bool vkValidationGpuEnabled(); // no set
int getExtraDmemInMbytes();
void setExtraDmemInMbytes(int value);
void setExtraDmemInMbytes(int value, bool is_game_specific = false);
bool getIsMotionControlsEnabled();
void setIsMotionControlsEnabled(bool use, bool is_game_specific = false);
std::string getDefaultControllerID();
@ -143,18 +152,18 @@ void setRcasAttenuation(int value, bool is_game_specific = false);
bool getIsConnectedToNetwork();
void setConnectedToNetwork(bool enable, bool is_game_specific = false);
void setUserName(const std::string& name, bool is_game_specific = false);
void setChooseHomeTab(const std::string& type, bool is_game_specific = false);
std::filesystem::path getSysModulesPath();
void setSysModulesPath(const std::filesystem::path& path);
bool getLoadAutoPatches();
void setLoadAutoPatches(bool enable);
enum UsbBackendType : int { Real, SkylandersPortal, InfinityBase, DimensionsToypad };
int getUsbDeviceBackend();
void setUsbDeviceBackend(int value, bool is_game_specific = false);
// TODO
bool GetLoadGameSizeEnabled();
std::filesystem::path GetSaveDataPath();
void setLoadGameSizeEnabled(bool enable);
bool getCompatibilityEnabled();
bool getCheckCompatibilityOnStartup();
std::string getUserName();
std::string getChooseHomeTab();
bool GetUseUnifiedInputConfig();
void SetUseUnifiedInputConfig(bool use);
bool GetOverrideControllerColor();
@ -164,8 +173,6 @@ void SetControllerCustomColor(int r, int b, int g);
void setGameInstallDirs(const std::vector<std::filesystem::path>& dirs_config);
void setAllGameInstallDirs(const std::vector<GameInstallDir>& dirs_config);
void setSaveDataPath(const std::filesystem::path& path);
void setCompatibilityEnabled(bool use);
void setCheckCompatibilityOnStartup(bool use);
// Gui
bool addGameInstallDir(const std::filesystem::path& dir, bool enabled = true);
void removeGameInstallDir(const std::filesystem::path& dir);

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

@ -40,28 +40,30 @@ namespace {
switch (mode) {
case FileAccessMode::Read:
return L"rb";
case FileAccessMode::Write:
return L"wb";
case FileAccessMode::Append:
return L"ab";
case FileAccessMode::Write:
case FileAccessMode::ReadWrite:
return L"r+b";
case FileAccessMode::ReadAppend:
return L"a+b";
case FileAccessMode::Create:
return L"wb";
}
break;
case FileType::TextFile:
switch (mode) {
case FileAccessMode::Read:
return L"r";
case FileAccessMode::Write:
return L"w";
case FileAccessMode::Append:
return L"a";
case FileAccessMode::Write:
case FileAccessMode::ReadWrite:
return L"r+";
case FileAccessMode::ReadAppend:
return L"a+";
case FileAccessMode::Create:
return L"w";
}
break;
}
@ -91,28 +93,30 @@ namespace {
switch (mode) {
case FileAccessMode::Read:
return "rb";
case FileAccessMode::Write:
return "wb";
case FileAccessMode::Append:
return "ab";
case FileAccessMode::Write:
case FileAccessMode::ReadWrite:
return "r+b";
case FileAccessMode::ReadAppend:
return "a+b";
case FileAccessMode::Create:
return "wb";
}
break;
case FileType::TextFile:
switch (mode) {
case FileAccessMode::Read:
return "r";
case FileAccessMode::Write:
return "w";
case FileAccessMode::Append:
return "a";
case FileAccessMode::Write:
case FileAccessMode::ReadWrite:
return "r+";
case FileAccessMode::ReadAppend:
return "a+";
case FileAccessMode::Create:
return "w";
}
break;
}

View File

@ -21,9 +21,8 @@ enum class FileAccessMode {
*/
Read = 1 << 0,
/**
* If the file at path exists, the existing contents of the file are erased.
* The empty file is then opened for writing.
* If the file at path does not exist, it creates and opens a new empty file for writing.
* If the file at path exists, it opens the file for writing.
* If the file at path does not exist, it fails to open the file.
*/
Write = 1 << 1,
/**
@ -42,6 +41,12 @@ enum class FileAccessMode {
* reading and appending.
*/
ReadAppend = Read | Append,
/**
* If the file at path exists, the existing contents of the file are erased.
* The empty file is then opened for writing.
* If the file at path does not exist, it creates and opens a new empty file for writing.
*/
Create = 1 << 3,
};
DECLARE_ENUM_FLAG_OPERATORS(FileAccessMode);
@ -102,6 +107,11 @@ public:
return file != nullptr;
}
bool IsWriteOnly() const {
return file_access_mode == FileAccessMode::Append ||
file_access_mode == FileAccessMode::Write;
}
uintptr_t GetFileMapping();
int Open(const std::filesystem::path& path, FileAccessMode mode,
@ -210,7 +220,7 @@ public:
}
static size_t WriteBytes(const std::filesystem::path path, const auto& data) {
IOFile out(path, FileAccessMode::Write);
IOFile out(path, FileAccessMode::Create);
return out.Write(data);
}
std::FILE* file = nullptr;

View File

@ -62,7 +62,7 @@ private:
class FileBackend {
public:
explicit FileBackend(const std::filesystem::path& filename, bool should_append = false)
: file{filename, should_append ? FS::FileAccessMode::Append : FS::FileAccessMode::Write,
: file{filename, should_append ? FS::FileAccessMode::Append : FS::FileAccessMode::Create,
FS::FileType::TextFile} {}
~FileBackend() = default;
@ -182,7 +182,13 @@ public:
}
void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num,
const char* function, std::string message) {
const char* function, const char* format, const fmt::format_args& args) {
if (!filter.CheckMessage(log_class, log_level) || !Config::getLoggingEnabled()) {
return;
}
const auto message = fmt::vformat(format, args);
// Propagate important log messages to the profiler
if (IsProfilerConnected()) {
const auto& msg_str = fmt::format("[{}] {}", GetLogClassName(log_class), message);
@ -201,10 +207,6 @@ public:
}
}
if (!filter.CheckMessage(log_class, log_level) || !Config::getLoggingEnabled()) {
return;
}
using std::chrono::duration_cast;
using std::chrono::microseconds;
using std::chrono::steady_clock;
@ -324,8 +326,8 @@ void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
unsigned int line_num, const char* function, const char* format,
const fmt::format_args& args) {
if (!initialization_in_progress_suppress_logging) [[likely]] {
Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function,
fmt::vformat(format, args));
Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function, format,
args);
}
}
} // namespace Common::Log

View File

@ -104,6 +104,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
SUB(Lib, Move) \
SUB(Lib, NpAuth) \
SUB(Lib, NpCommon) \
SUB(Lib, NpCommerce) \
SUB(Lib, NpManager) \
SUB(Lib, NpScore) \
SUB(Lib, NpTrophy) \
@ -140,6 +141,8 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
SUB(Lib, NpParty) \
SUB(Lib, Zlib) \
SUB(Lib, Hmd) \
SUB(Lib, Font) \
SUB(Lib, FontFt) \
SUB(Lib, HmdSetupDialog) \
SUB(Lib, SigninDialog) \
SUB(Lib, Camera) \

View File

@ -70,6 +70,7 @@ enum class Class : u8 {
Lib_Http2, ///< The LibSceHttp2 implementation.
Lib_SysModule, ///< The LibSceSysModule implementation
Lib_NpCommon, ///< The LibSceNpCommon implementation
Lib_NpCommerce, ///< The LibSceNpCommerce implementation
Lib_NpAuth, ///< The LibSceNpAuth implementation
Lib_NpManager, ///< The LibSceNpManager implementation
Lib_NpScore, ///< The LibSceNpScore implementation
@ -114,6 +115,8 @@ enum class Class : u8 {
Lib_CompanionHttpd, ///< The LibCompanionHttpd implementation.
Lib_CompanionUtil, ///< The LibCompanionUtil implementation.
Lib_VrTracker, ///< The LibSceVrTracker implementation.
Lib_Font, ///< The libSceFont implementation.
Lib_FontFt, ///< The libSceFontFt implementation.
Frontend, ///< Emulator UI
Render, ///< Video Core
Render_Vulkan, ///< Vulkan backend

View File

@ -3,20 +3,12 @@
#include <algorithm>
#include <codecvt>
#include <fstream>
#include <sstream>
#include <string>
#include <nlohmann/json.hpp>
#include <pugixml.hpp>
#ifdef ENABLE_QT_GUI
#include <QDir>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QListView>
#include <QMessageBox>
#include <QString>
#include <QXmlStreamReader>
#endif
#include "common/config.h"
#include "common/elf_info.h"
#include "common/logging/log.h"
#include "common/path_util.h"
@ -28,7 +20,7 @@ namespace MemoryPatcher {
EXPORT uintptr_t g_eboot_address;
uint64_t g_eboot_image_size;
std::string g_game_serial;
std::string patchFile;
std::string patch_file;
bool patches_applied = false;
std::vector<patchInfo> pending_patches;
@ -122,256 +114,104 @@ std::string convertValueToHex(const std::string type, const std::string valueStr
void ApplyPendingPatches();
void OnGameLoaded() {
if (!patchFile.empty()) {
std::filesystem::path patchDir = Common::FS::GetUserPath(Common::FS::PathType::PatchesDir);
void ApplyPatchesFromXML(std::filesystem::path path) {
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(path.c_str());
auto filePath = (patchDir / patchFile).native();
auto* param_sfo = Common::Singleton<PSF>::Instance();
auto app_version = param_sfo->GetString("APP_VER").value_or("Unknown version");
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(filePath.c_str());
if (result) {
auto patchXML = doc.child("Patch");
for (pugi::xml_node_iterator it = patchXML.children().begin();
it != patchXML.children().end(); ++it) {
auto* param_sfo = Common::Singleton<PSF>::Instance();
auto app_version = param_sfo->GetString("APP_VER").value_or("Unknown version");
if (std::string(it->name()) == "Metadata") {
if (std::string(it->attribute("isEnabled").value()) == "true") {
std::string currentPatchName = it->attribute("Name").value();
std::string metadataAppVer = it->attribute("AppVer").value();
bool versionMatches = metadataAppVer == app_version;
if (result) {
auto patchXML = doc.child("Patch");
for (pugi::xml_node_iterator it = patchXML.children().begin();
it != patchXML.children().end(); ++it) {
auto patchList = it->first_child();
for (pugi::xml_node_iterator patchLineIt = patchList.children().begin();
patchLineIt != patchList.children().end(); ++patchLineIt) {
if (std::string(it->name()) == "Metadata") {
if (std::string(it->attribute("isEnabled").value()) == "true") {
std::string currentPatchName = it->attribute("Name").value();
std::string metadataAppVer = it->attribute("AppVer").value();
bool versionMatches = metadataAppVer == app_version;
std::string type = patchLineIt->attribute("Type").value();
if (!versionMatches && type != "mask" && type != "mask_jump32")
continue;
auto patchList = it->first_child();
for (pugi::xml_node_iterator patchLineIt = patchList.children().begin();
patchLineIt != patchList.children().end(); ++patchLineIt) {
std::string type = patchLineIt->attribute("Type").value();
if (!versionMatches && type != "mask" && type != "mask_jump32")
continue;
std::string currentPatchName = it->attribute("Name").value();
for (pugi::xml_node_iterator patchLineIt = patchList.children().begin();
patchLineIt != patchList.children().end(); ++patchLineIt) {
std::string type = patchLineIt->attribute("Type").value();
std::string address = patchLineIt->attribute("Address").value();
std::string patchValue = patchLineIt->attribute("Value").value();
std::string maskOffsetStr =
patchLineIt->attribute("Offset").value();
std::string targetStr = "";
std::string sizeStr = "";
if (type == "mask_jump32") {
targetStr = patchLineIt->attribute("Target").value();
sizeStr = patchLineIt->attribute("Size").value();
} else {
patchValue = convertValueToHex(type, patchValue);
}
bool littleEndian = false;
if (type == "bytes16" || type == "bytes32" || type == "bytes64") {
littleEndian = true;
}
MemoryPatcher::PatchMask patchMask = MemoryPatcher::PatchMask::None;
int maskOffsetValue = 0;
if (type == "mask")
patchMask = MemoryPatcher::PatchMask::Mask;
if (type == "mask_jump32")
patchMask = MemoryPatcher::PatchMask::Mask_Jump32;
if ((type == "mask" || type == "mask_jump32") &&
!maskOffsetStr.empty()) {
maskOffsetValue = std::stoi(maskOffsetStr, 0, 10);
}
MemoryPatcher::PatchMemory(currentPatchName, address, patchValue,
targetStr, sizeStr, false, littleEndian,
patchMask, maskOffsetValue);
}
std::string address = patchLineIt->attribute("Address").value();
std::string patchValue = patchLineIt->attribute("Value").value();
std::string maskOffsetStr = patchLineIt->attribute("Offset").value();
std::string targetStr = "";
std::string sizeStr = "";
if (type == "mask_jump32") {
targetStr = patchLineIt->attribute("Target").value();
sizeStr = patchLineIt->attribute("Size").value();
} else {
patchValue = convertValueToHex(type, patchValue);
}
bool littleEndian = false;
if (type == "bytes16" || type == "bytes32" || type == "bytes64") {
littleEndian = true;
}
MemoryPatcher::PatchMask patchMask = MemoryPatcher::PatchMask::None;
int maskOffsetValue = 0;
if (type == "mask")
patchMask = MemoryPatcher::PatchMask::Mask;
if (type == "mask_jump32")
patchMask = MemoryPatcher::PatchMask::Mask_Jump32;
if ((type == "mask" || type == "mask_jump32") && !maskOffsetStr.empty()) {
maskOffsetValue = std::stoi(maskOffsetStr, 0, 10);
}
MemoryPatcher::PatchMemory(currentPatchName, address, patchValue, targetStr,
sizeStr, false, littleEndian, patchMask,
maskOffsetValue);
}
}
}
}
} else {
LOG_ERROR(Loader, "Could not parse patch XML: {}", result.description());
}
}
void OnGameLoaded() {
std::filesystem::path patch_dir = Common::FS::GetUserPath(Common::FS::PathType::PatchesDir);
if (!patch_file.empty()) {
auto file_path = (patch_dir / patch_file).native();
if (std::filesystem::exists(patch_file)) {
ApplyPatchesFromXML(patch_file);
} else {
LOG_ERROR(Loader, "couldnt patch parse xml : {}", result.description());
ApplyPatchesFromXML(file_path);
}
} else if (Config::getLoadAutoPatches()) {
for (auto const& repo : std::filesystem::directory_iterator(patch_dir)) {
if (!repo.is_directory()) {
continue;
}
std::ifstream json_file{repo.path() / "files.json"};
nlohmann::json available_patches = nlohmann::json::parse(json_file);
std::filesystem::path game_patch_file;
for (auto const& [filename, serials] : available_patches.items()) {
if (std::find(serials.begin(), serials.end(), g_game_serial) != serials.end()) {
game_patch_file = repo.path() / filename;
break;
}
}
if (std::filesystem::exists(game_patch_file)) {
ApplyPatchesFromXML(game_patch_file);
}
}
}
ApplyPendingPatches();
#ifdef ENABLE_QT_GUI
// We use the QT headers for the xml and json parsing, this define is only true on QT builds
QString patchDir;
Common::FS::PathToQString(patchDir, Common::FS::GetUserPath(Common::FS::PathType::PatchesDir));
QDir dir(patchDir);
QStringList folders = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
for (const QString& folder : folders) {
QString filesJsonPath = patchDir + "/" + folder + "/files.json";
QFile jsonFile(filesJsonPath);
if (!jsonFile.open(QIODevice::ReadOnly)) {
LOG_ERROR(Loader, "Unable to open files.json for reading in repository {}",
folder.toStdString());
continue;
}
QByteArray jsonData = jsonFile.readAll();
jsonFile.close();
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData);
QJsonObject jsonObject = jsonDoc.object();
QString selectedFileName;
QString serial = QString::fromStdString(g_game_serial);
for (auto it = jsonObject.constBegin(); it != jsonObject.constEnd(); ++it) {
QString filePath = it.key();
QJsonArray idsArray = it.value().toArray();
if (idsArray.contains(QJsonValue(serial))) {
selectedFileName = filePath;
break;
}
}
if (selectedFileName.isEmpty()) {
LOG_ERROR(Loader, "No patch file found for the current serial in repository {}",
folder.toStdString());
continue;
}
QString filePath = patchDir + "/" + folder + "/" + selectedFileName;
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
LOG_ERROR(Loader, "Unable to open the file for reading.");
continue;
}
QByteArray xmlData = file.readAll();
file.close();
QString newXmlData;
QXmlStreamReader xmlReader(xmlData);
bool isEnabled = false;
std::string currentPatchName;
auto* param_sfo = Common::Singleton<PSF>::Instance();
auto app_version = param_sfo->GetString("APP_VER").value_or("Unknown version");
bool versionMatches = true;
while (!xmlReader.atEnd()) {
xmlReader.readNext();
if (xmlReader.isStartElement()) {
QJsonArray patchLines;
if (xmlReader.name() == QStringLiteral("Metadata")) {
QString name = xmlReader.attributes().value("Name").toString();
currentPatchName = name.toStdString();
QString appVer = xmlReader.attributes().value("AppVer").toString();
// Check and update the isEnabled attribute
isEnabled = false;
for (const QXmlStreamAttribute& attr : xmlReader.attributes()) {
if (attr.name() == QStringLiteral("isEnabled")) {
isEnabled = (attr.value().toString() == "true");
}
}
versionMatches = (appVer.toStdString() == app_version);
} else if (xmlReader.name() == QStringLiteral("PatchList")) {
QJsonArray linesArray;
while (!xmlReader.atEnd() &&
!(xmlReader.tokenType() == QXmlStreamReader::EndElement &&
xmlReader.name() == QStringLiteral("PatchList"))) {
xmlReader.readNext();
if (xmlReader.tokenType() == QXmlStreamReader::StartElement &&
xmlReader.name() == QStringLiteral("Line")) {
QXmlStreamAttributes attributes = xmlReader.attributes();
QJsonObject lineObject;
lineObject["Type"] = attributes.value("Type").toString();
lineObject["Address"] = attributes.value("Address").toString();
lineObject["Value"] = attributes.value("Value").toString();
lineObject["Offset"] = attributes.value("Offset").toString();
if (lineObject["Type"].toString() == "mask_jump32") {
lineObject["Target"] = attributes.value("Target").toString();
lineObject["Size"] = attributes.value("Size").toString();
}
linesArray.append(lineObject);
}
}
patchLines = linesArray;
if (isEnabled) {
foreach (const QJsonValue& value, patchLines) {
QJsonObject lineObject = value.toObject();
QString type = lineObject["Type"].toString();
if ((type != "mask" && type != "mask_jump32") && !versionMatches)
continue;
QString address = lineObject["Address"].toString();
QString patchValue = lineObject["Value"].toString();
QString maskOffsetStr = lineObject["Offset"].toString();
QString targetStr;
QString sizeStr;
if (type == "mask_jump32") {
targetStr = lineObject["Target"].toString();
sizeStr = lineObject["Size"].toString();
} else {
patchValue = QString::fromStdString(convertValueToHex(
type.toStdString(), patchValue.toStdString()));
}
bool littleEndian = false;
if (type == "bytes16" || type == "bytes32" || type == "bytes64")
littleEndian = true;
MemoryPatcher::PatchMask patchMask = MemoryPatcher::PatchMask::None;
int maskOffsetValue = 0;
if (type == "mask")
patchMask = MemoryPatcher::PatchMask::Mask;
if (type == "mask_jump32")
patchMask = MemoryPatcher::PatchMask::Mask_Jump32;
if ((type == "mask" || type == "mask_jump32") &&
!maskOffsetStr.toStdString().empty()) {
maskOffsetValue = std::stoi(maskOffsetStr.toStdString(), 0, 10);
}
MemoryPatcher::PatchMemory(
currentPatchName, address.toStdString(), patchValue.toStdString(),
targetStr.toStdString(), sizeStr.toStdString(), false, littleEndian,
patchMask, maskOffsetValue);
}
}
}
}
}
if (xmlReader.hasError()) {
LOG_ERROR(Loader, "Failed to parse XML for {}", g_game_serial);
} else {
LOG_INFO(Loader, "Patches loaded successfully, repository: {}", folder.toStdString());
}
ApplyPendingPatches();
}
#endif
}
void AddPatchToQueue(patchInfo patchToAdd) {

View File

@ -17,7 +17,7 @@ namespace MemoryPatcher {
extern EXPORT uintptr_t g_eboot_address;
extern uint64_t g_eboot_image_size;
extern std::string g_game_serial;
extern std::string patchFile;
extern std::string patch_file;
enum PatchMask : uint8_t {
None,

View File

@ -25,10 +25,6 @@
#endif
#endif
#ifdef ENABLE_QT_GUI
#include <QString>
#endif
namespace Common::FS {
namespace fs = std::filesystem;
@ -88,13 +84,6 @@ static std::optional<std::filesystem::path> GetBundleParentDirectory() {
#endif
static auto UserPaths = [] {
#if defined(__APPLE__) && defined(ENABLE_QT_GUI)
// Set the current path to the directory containing the app bundle.
if (const auto bundle_dir = GetBundleParentDirectory()) {
std::filesystem::current_path(*bundle_dir);
}
#endif
// Try the portable user directory first.
auto user_dir = std::filesystem::current_path() / PORTABLE_DIR;
if (!std::filesystem::exists(user_dir)) {
@ -138,6 +127,7 @@ static auto UserPaths = [] {
create_path(PathType::MetaDataDir, user_dir / METADATA_DIR);
create_path(PathType::CustomTrophy, user_dir / CUSTOM_TROPHY);
create_path(PathType::CustomConfigs, user_dir / CUSTOM_CONFIGS);
create_path(PathType::CacheDir, user_dir / CACHE_DIR);
std::ofstream notice_file(user_dir / CUSTOM_TROPHY / "Notice.txt");
if (notice_file.is_open()) {
@ -229,22 +219,4 @@ std::optional<fs::path> FindGameByID(const fs::path& dir, const std::string& gam
return std::nullopt;
}
#ifdef ENABLE_QT_GUI
void PathToQString(QString& result, const std::filesystem::path& path) {
#ifdef _WIN32
result = QString::fromStdWString(path.wstring());
#else
result = QString::fromStdString(path.string());
#endif
}
std::filesystem::path PathFromQString(const QString& path) {
#ifdef _WIN32
return std::filesystem::path(path.toStdWString());
#else
return std::filesystem::path(path.toStdString());
#endif
}
#endif
} // namespace Common::FS

View File

@ -7,10 +7,6 @@
#include <optional>
#include <vector>
#ifdef ENABLE_QT_GUI
class QString; // to avoid including <QString> in this header
#endif
namespace Common::FS {
enum class PathType {
@ -28,6 +24,7 @@ enum class PathType {
MetaDataDir, // Where game metadata (e.g. trophies and menu backgrounds) is stored.
CustomTrophy, // Where custom files for trophies are stored.
CustomConfigs, // Where custom files for different games are stored.
CacheDir, // Where pipeline and shader cache is stored.
};
constexpr auto PORTABLE_DIR = "user";
@ -46,6 +43,7 @@ constexpr auto PATCHES_DIR = "patches";
constexpr auto METADATA_DIR = "game_data";
constexpr auto CUSTOM_TROPHY = "custom_trophy";
constexpr auto CUSTOM_CONFIGS = "custom_configs";
constexpr auto CACHE_DIR = "cache";
// Filenames
constexpr auto LOG_FILE = "shad_log.txt";
@ -99,25 +97,6 @@ constexpr auto LOG_FILE = "shad_log.txt";
*/
void SetUserPath(PathType user_path, const std::filesystem::path& new_path);
#ifdef ENABLE_QT_GUI
/**
* Converts an std::filesystem::path to a QString.
* The native underlying string of a path is wstring on Windows and string on POSIX.
*
* @param result The resulting QString
* @param path The path to convert
*/
void PathToQString(QString& result, const std::filesystem::path& path);
/**
* Converts a QString to an std::filesystem::path.
* The native underlying string of a path is wstring on Windows and string on POSIX.
*
* @param path The path to convert
*/
[[nodiscard]] std::filesystem::path PathFromQString(const QString& path);
#endif
/**
* Recursively searches for a game directory by its ID.
* Limits search depth to prevent excessive filesystem traversal.

140
src/common/serdes.h Normal file
View File

@ -0,0 +1,140 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/assert.h"
#include "common/types.h"
#include <cstddef>
namespace Serialization {
template <typename T>
concept Container = requires(T t) {
typename T::iterator;
{ t.begin() } -> std::same_as<typename T::iterator>;
{ t.end() } -> std::same_as<typename T::iterator>;
{ t.size() } -> std::convertible_to<std::size_t>;
};
struct Archive {
void Alloc(size_t size) {
container.resize(size);
}
void Grow(size_t size) {
container.resize(container.size() + size);
}
void Merge(const Archive& ar) {
container.insert(container.end(), ar.container.cbegin(), ar.container.cend());
offset = container.size();
}
[[nodiscard]] size_t SizeBytes() const {
return container.size();
}
u8* CurrPtr() {
return container.data() + offset;
}
void Advance(size_t size) {
ASSERT(offset + size <= container.size());
offset += size;
}
std::vector<u8>&& TakeOff() {
offset = 0;
return std::move(container);
}
[[nodiscard]] bool IsEoS() const {
return offset >= container.size();
}
Archive() = default;
explicit Archive(std::vector<u8>&& v) : container{v} {}
private:
u32 offset{};
std::vector<u8> container{};
friend struct Writer;
friend struct Reader;
};
struct Writer {
template <typename T>
void Write(const T* ptr, size_t size) {
if (ar.offset + size >= ar.container.size()) {
ar.Grow(size);
}
std::memcpy(ar.CurrPtr(), reinterpret_cast<const void*>(ptr), size);
ar.Advance(size);
}
template <typename T>
requires(!Container<T>)
void Write(const T& value) {
const auto size = sizeof(value);
Write(&value, size);
}
void Write(const auto& v) {
Write(v.size());
for (const auto& elem : v) {
Write(elem);
}
}
void Write(const std::string& s) {
Write(s.size());
Write(s.c_str(), s.size());
}
Writer() = delete;
explicit Writer(Archive& ar_) : ar{ar_} {}
Archive& ar;
};
struct Reader {
template <typename T>
void Read(T* ptr, size_t size) {
ASSERT(ar.offset + size <= ar.container.size());
std::memcpy(reinterpret_cast<void*>(ptr), ar.CurrPtr(), size);
ar.Advance(size);
}
template <typename T>
requires(!Container<T>)
void Read(T& value) {
const auto size = sizeof(value);
Read(&value, size);
}
void Read(auto& v) {
size_t num_elements{};
Read(num_elements);
for (int i = 0; i < num_elements; ++i) {
v.emplace_back();
Read(v.back());
}
}
void Read(std::string& s) {
size_t length{};
Read(length);
s.resize(length);
Read(s.data(), length);
}
Reader() = delete;
explicit Reader(Archive& ar_) : ar{ar_} {}
Archive& ar;
};
} // namespace Serialization

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

@ -5,6 +5,7 @@
#include <memory>
#include <mutex>
#include <set>
#include <vector>
#include <Zydis/Zydis.h>
#include <xbyak/xbyak.h>
#include <xbyak/xbyak_util.h>
@ -122,6 +123,30 @@ static void GenerateTcbAccess(void* /* address */, const ZydisDecodedOperand* op
#endif
}
static bool FilterStackCheck(const ZydisDecodedOperand* operands) {
const auto& dst_op = operands[0];
const auto& src_op = operands[1];
// Some compilers emit stack checks by starting a function with
// 'mov (64-bit register), fs:[0x28]', then checking with `xor (64-bit register), fs:[0x28]`
return src_op.type == ZYDIS_OPERAND_TYPE_MEMORY && src_op.mem.segment == ZYDIS_REGISTER_FS &&
src_op.mem.base == ZYDIS_REGISTER_NONE && src_op.mem.index == ZYDIS_REGISTER_NONE &&
src_op.mem.disp.value == 0x28 && dst_op.reg.value >= ZYDIS_REGISTER_RAX &&
dst_op.reg.value <= ZYDIS_REGISTER_R15;
}
static void GenerateStackCheck(void* /* address */, const ZydisDecodedOperand* operands,
Xbyak::CodeGenerator& c) {
const auto dst = ZydisToXbyakRegisterOperand(operands[0]);
c.xor_(dst, 0);
}
static void GenerateStackCanary(void* /* address */, const ZydisDecodedOperand* operands,
Xbyak::CodeGenerator& c) {
const auto dst = ZydisToXbyakRegisterOperand(operands[0]);
c.mov(dst, 0);
}
static bool FilterNoSSE4a(const ZydisDecodedOperand*) {
Cpu cpu;
return !cpu.has(Cpu::tSSE4a);
@ -440,18 +465,26 @@ struct PatchInfo {
bool trampoline;
};
static const std::unordered_map<ZydisMnemonic, PatchInfo> Patches = {
static const std::unordered_map<ZydisMnemonic, std::vector<PatchInfo>> Patches = {
// SSE4a
{ZYDIS_MNEMONIC_EXTRQ, {FilterNoSSE4a, GenerateEXTRQ, true}},
{ZYDIS_MNEMONIC_INSERTQ, {FilterNoSSE4a, GenerateINSERTQ, true}},
{ZYDIS_MNEMONIC_MOVNTSS, {FilterNoSSE4a, ReplaceMOVNTSS, false}},
{ZYDIS_MNEMONIC_MOVNTSD, {FilterNoSSE4a, ReplaceMOVNTSD, false}},
{ZYDIS_MNEMONIC_EXTRQ, {{FilterNoSSE4a, GenerateEXTRQ, true}}},
{ZYDIS_MNEMONIC_INSERTQ, {{FilterNoSSE4a, GenerateINSERTQ, true}}},
{ZYDIS_MNEMONIC_MOVNTSS, {{FilterNoSSE4a, ReplaceMOVNTSS, false}}},
{ZYDIS_MNEMONIC_MOVNTSD, {{FilterNoSSE4a, ReplaceMOVNTSD, false}}},
#if !defined(__APPLE__)
// FS segment patches
// These first two patches are for accesses to the stack canary, fs:[0x28]
{ZYDIS_MNEMONIC_XOR, {{FilterStackCheck, GenerateStackCheck, false}}},
{ZYDIS_MNEMONIC_MOV,
{{FilterStackCheck, GenerateStackCanary, false},
#if defined(_WIN32)
// Windows needs a trampoline.
{ZYDIS_MNEMONIC_MOV, {FilterTcbAccess, GenerateTcbAccess, true}},
#elif !defined(__APPLE__)
{ZYDIS_MNEMONIC_MOV, {FilterTcbAccess, GenerateTcbAccess, false}},
// Windows needs a trampoline for Tcb accesses.
{FilterTcbAccess, GenerateTcbAccess, true}
#else
{FilterTcbAccess, GenerateTcbAccess, false}
#endif
}},
#endif
};
@ -503,51 +536,53 @@ static std::pair<bool, u64> TryPatch(u8* code, PatchModule* module) {
}
if (Patches.contains(instruction.mnemonic)) {
const auto& patch_info = Patches.at(instruction.mnemonic);
bool needs_trampoline = patch_info.trampoline;
if (patch_info.filter(operands)) {
auto& patch_gen = module->patch_gen;
const auto& patches = Patches.at(instruction.mnemonic);
for (const auto& patch_info : patches) {
bool needs_trampoline = patch_info.trampoline;
if (patch_info.filter(operands)) {
auto& patch_gen = module->patch_gen;
if (needs_trampoline && instruction.length < 5) {
// Trampoline is needed but instruction is too short to patch.
// Return false and length to signal to AOT compilation that this instruction
// should be skipped and handled at runtime.
return std::make_pair(false, instruction.length);
}
if (needs_trampoline && instruction.length < 5) {
// Trampoline is needed but instruction is too short to patch.
// Return false and length to signal to AOT compilation that this instruction
// should be skipped and handled at runtime.
return std::make_pair(false, instruction.length);
}
// Reset state and move to current code position.
patch_gen.reset();
patch_gen.setSize(code - patch_gen.getCode());
// Reset state and move to current code position.
patch_gen.reset();
patch_gen.setSize(code - patch_gen.getCode());
if (needs_trampoline) {
auto& trampoline_gen = module->trampoline_gen;
const auto trampoline_ptr = trampoline_gen.getCurr();
if (needs_trampoline) {
auto& trampoline_gen = module->trampoline_gen;
const auto trampoline_ptr = trampoline_gen.getCurr();
patch_info.generator(code, operands, trampoline_gen);
patch_info.generator(code, operands, trampoline_gen);
// Return to the following instruction at the end of the trampoline.
trampoline_gen.jmp(code + instruction.length);
// Return to the following instruction at the end of the trampoline.
trampoline_gen.jmp(code + instruction.length);
// Replace instruction with near jump to the trampoline.
patch_gen.jmp(trampoline_ptr, Xbyak::CodeGenerator::LabelType::T_NEAR);
} else {
patch_info.generator(code, operands, patch_gen);
}
// Replace instruction with near jump to the trampoline.
patch_gen.jmp(trampoline_ptr, Xbyak::CodeGenerator::LabelType::T_NEAR);
} else {
patch_info.generator(code, operands, patch_gen);
}
const auto patch_size = patch_gen.getCurr() - code;
if (patch_size > 0) {
ASSERT_MSG(instruction.length >= patch_size,
"Instruction {} with length {} is too short to replace at: {}",
ZydisMnemonicGetString(instruction.mnemonic), instruction.length,
fmt::ptr(code));
const auto patch_size = patch_gen.getCurr() - code;
if (patch_size > 0) {
ASSERT_MSG(instruction.length >= patch_size,
"Instruction {} with length {} is too short to replace at: {}",
ZydisMnemonicGetString(instruction.mnemonic), instruction.length,
fmt::ptr(code));
// Fill remaining space with nops.
patch_gen.nop(instruction.length - patch_size);
// Fill remaining space with nops.
patch_gen.nop(instruction.length - patch_size);
module->patched.insert(code);
LOG_DEBUG(Core, "Patched instruction '{}' at: {}",
ZydisMnemonicGetString(instruction.mnemonic), fmt::ptr(code));
return std::make_pair(true, instruction.length);
module->patched.insert(code);
LOG_DEBUG(Core, "Patched instruction '{}' at: {}",
ZydisMnemonicGetString(instruction.mnemonic), fmt::ptr(code));
return std::make_pair(true, instruction.length);
}
}
}
}
@ -755,11 +790,12 @@ static bool PatchesIllegalInstructionHandler(void* context) {
Common::Decoder::Instance()->decodeInstruction(instruction, operands, code_address);
if (ZYAN_SUCCESS(status) && instruction.mnemonic == ZydisMnemonic::ZYDIS_MNEMONIC_UD2)
[[unlikely]] {
UNREACHABLE_MSG("ud2 at code address {:#x}", (u64)code_address);
UNREACHABLE_MSG("ud2 at code address {:#x}", reinterpret_cast<u64>(code_address));
}
LOG_ERROR(Core, "Failed to patch address {:x} -- mnemonic: {}", (u64)code_address,
ZYAN_SUCCESS(status) ? ZydisMnemonicGetString(instruction.mnemonic)
: "Failed to decode");
UNREACHABLE_MSG("Failed to patch address {:x} -- mnemonic: {}",
reinterpret_cast<u64>(code_address),
ZYAN_SUCCESS(status) ? ZydisMnemonicGetString(instruction.mnemonic)
: "Failed to decode");
}
}

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "layer.h"
@ -311,6 +311,7 @@ static void LoadSettings(const char* line) {
void L::SetupSettings() {
frame_graph.is_open = true;
show_simple_fps = Config::getShowFpsCounter();
using SettingLoader = void (*)(const char*);
@ -460,12 +461,12 @@ void L::Draw() {
}
void L::TextCentered(const std::string& text) {
float window_width = ImGui::GetWindowSize().x;
float text_width = ImGui::CalcTextSize(text.c_str()).x;
float window_width = GetWindowSize().x;
float text_width = CalcTextSize(text.c_str()).x;
float text_indentation = (window_width - text_width) * 0.5f;
ImGui::SameLine(text_indentation);
ImGui::Text("%s", text.c_str());
SameLine(text_indentation);
Text("%s", text.c_str());
}
namespace Overlay {
@ -475,6 +476,11 @@ void ToggleSimpleFps() {
visibility_toggled = true;
}
void SetSimpleFps(bool enabled) {
show_simple_fps = enabled;
visibility_toggled = true;
}
void ToggleQuitWindow() {
show_quit_window = !show_quit_window;
}

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@ -9,18 +9,20 @@
namespace Core::Devtools {
class Layer final : public ImGui::Layer {
static void DrawMenuBar();
static void DrawAdvanced();
static void DrawSimple();
public:
static void SetupSettings();
void Draw() override;
void TextCentered(const std::string& text);
// Must be inside a window
static void DrawNullGpuNotice();
private:
static void DrawMenuBar();
static void DrawAdvanced();
static void DrawSimple();
static void TextCentered(const std::string& text);
};
} // namespace Core::Devtools
@ -28,6 +30,7 @@ public:
namespace Overlay {
void ToggleSimpleFps();
void SetSimpleFps(bool enabled);
void ToggleQuitWindow();
} // namespace Overlay

View File

@ -0,0 +1,95 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "layer.h"
#include "imgui/imgui_std.h"
#include <imgui.h>
#include <imgui_internal.h>
#include <cmath>
#include <numbers>
namespace Core::Devtools {
void Layer::DrawNullGpuNotice() {
auto* window = ImGui::GetCurrentWindow();
if (window->SkipItems) {
return;
}
constexpr std::string_view mainNotice = "Null GPU is enabled";
constexpr std::string_view detailsNotice =
"Disable the nullGpu config to show the game display";
auto displaySize = window->Size;
ImVec2 targetSize = displaySize * 0.7f;
float minFontSize = 1.0f;
float maxFontSize = 200.0f;
float optimalFontSize = minFontSize;
static auto lastSize = ImVec2(-1, -1);
static float lastFontSize = -1.0f;
auto* font = ImGui::GetIO().Fonts->Fonts[IMGUI_FONT_TEXT_BIG];
if (lastSize != targetSize) {
while (maxFontSize - minFontSize > 0.1f) {
float testFontSize = (minFontSize + maxFontSize) / 2.0f;
ImVec2 textSize = font->CalcTextSizeA(testFontSize, FLT_MAX, 0.0f, &mainNotice.front(),
&mainNotice.back() + 1);
if (textSize.x <= targetSize.x && textSize.y <= targetSize.y) {
optimalFontSize = testFontSize;
minFontSize = testFontSize;
} else {
maxFontSize = testFontSize;
}
}
lastSize = targetSize;
lastFontSize = optimalFontSize;
} else {
optimalFontSize = lastFontSize;
}
ImVec2 textSize = font->CalcTextSizeA(optimalFontSize, FLT_MAX, 0.0f, &mainNotice.front(),
&mainNotice.back() + 1);
ImVec2 textPos = (displaySize - textSize) * 0.5f + window->Pos;
const float scale = optimalFontSize / font->FontSize;
double timeAnim = -std::numbers::pi * ImGui::GetTime();
int i = 0;
for (auto ch : mainNotice) {
double colorTime = sin(timeAnim + i * std::numbers::pi / 6.0) / 2.0 + 0.5;
int color = (int)(200 * colorTime) + 55;
double posTime = sin(timeAnim + i * std::numbers::pi / 15.0) / 2.0 + 0.5;
auto pos = textPos;
pos.y += 10.0 * (posTime < 0.5 ? std::pow(2.0, 20.0 * posTime - 10.0) / 2.0
: (2.0 - std::pow(2.0, -20.0 * posTime + 10.0)) / 2.0);
window->DrawList->AddText(font, optimalFontSize, pos, IM_COL32(color, color, color, 255),
&ch, &ch + 1);
textPos.x += font->FindGlyph(ch)->AdvanceX * scale;
i++;
}
font = ImGui::GetIO().Fonts->Fonts[IMGUI_FONT_TEXT];
textPos.y += textSize.y + 1.0;
optimalFontSize *= 0.2;
textSize = font->CalcTextSizeA(optimalFontSize, FLT_MAX, 0.0f, &detailsNotice.front(),
&detailsNotice.back() + 1);
textPos.x = window->Pos.x + (window->Size.x - textSize.x) * 0.5f;
window->DrawList->AddText(font, optimalFontSize, textPos, IM_COL32(200, 200, 200, 255),
&detailsNotice.front(), &detailsNotice.back() + 1);
}
} // namespace Core::Devtools

View File

@ -152,7 +152,7 @@ inline std::string RunDisassembler(const std::string& disassembler_cli, const T&
}
} else {
cli.replace(pos, src_arg.size(), "\"" + bin_path.string() + "\"");
Common::FS::IOFile file(bin_path, Common::FS::FileAccessMode::Write);
Common::FS::IOFile file(bin_path, Common::FS::FileAccessMode::Create);
file.Write(shader_code);
file.Close();

View File

@ -123,7 +123,7 @@ void FrameDumpViewer::Draw() {
const auto fname = fmt::format("{:%F %H-%M-%S} {}_{}_{}.bin", now_time,
magic_enum::enum_name(selected_queue_type),
selected_submit_num, selected_queue_num2);
Common::FS::IOFile file(fname, Common::FS::FileAccessMode::Write);
Common::FS::IOFile file(fname, Common::FS::FileAccessMode::Create);
const auto& data = frame_dump->queues[selected_cmd].data;
if (file.IsOpen()) {
DebugState.ShowDebugMessage(fmt::format("Dumping cmd as {}", fname));

View File

@ -36,6 +36,7 @@ bool PSF::Open(const std::filesystem::path& filepath) {
}
const u64 psfSize = file.GetSize();
ASSERT_MSG(psfSize != 0, "SFO file at {} is empty!", filepath.string());
std::vector<u8> psf(psfSize);
file.Seek(0);
file.Read(psf);
@ -99,7 +100,7 @@ bool PSF::Open(const std::vector<u8>& psf_buffer) {
}
bool PSF::Encode(const std::filesystem::path& filepath) const {
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Write);
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Create);
if (!file.IsOpen()) {
return false;
}

View File

@ -36,6 +36,18 @@ public:
return ORBIS_KERNEL_ERROR_EBADF;
}
virtual s64 write(const void* buf, u64 nbytes) {
return ORBIS_KERNEL_ERROR_EBADF;
}
virtual s64 writev(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) {
return ORBIS_KERNEL_ERROR_EBADF;
}
virtual s64 pwritev(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset) {
return ORBIS_KERNEL_ERROR_EBADF;
}
virtual s64 lseek(s64 offset, s32 whence) {
return ORBIS_KERNEL_ERROR_EBADF;
}

View File

@ -52,6 +52,9 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea
pos = corrected_path.find("//", pos + 1);
}
if (path.length() > 255)
return "";
const MntPair* mount = GetMount(corrected_path);
if (!mount) {
return "";

View File

@ -17,6 +17,7 @@
#include "core/libraries/audio/audioout.h"
#include "input/input_handler.h"
#include "sdl_window.h"
#include "src/core/libraries/usbd/usbd.h"
#include "video_core/renderer_vulkan/vk_presenter.h"
extern std::unique_ptr<Vulkan::Presenter> presenter;
@ -70,6 +71,8 @@ void IPC::Init() {
return;
}
Config::setLoadAutoPatches(false);
input_thread = std::jthread([this] {
Common::SetCurrentThreadName("IPC Read thread");
this->InputLoop();
@ -167,6 +170,37 @@ void IPC::InputLoop() {
presenter->GetFsrSettingsRef().rcas_attenuation =
static_cast<float>(value / 1000.0f);
}
} else if (cmd == "USB_LOAD_FIGURE") {
const auto ref = Libraries::Usbd::usb_backend->GetImplRef();
if (ref) {
ref->LoadFigure(next_str(), next_u64(), next_u64());
}
} else if (cmd == "USB_REMOVE_FIGURE") {
const auto ref = Libraries::Usbd::usb_backend->GetImplRef();
if (ref) {
ref->RemoveFigure(next_u64(), next_u64(), next_u64() != 0);
}
} else if (cmd == "USB_MOVE_FIGURE") {
const auto ref = Libraries::Usbd::usb_backend->GetImplRef();
if (ref) {
const u8 new_pad = next_u64();
const u8 new_index = next_u64();
const u8 old_pad = next_u64();
const u8 old_index = next_u64();
ref->MoveFigure(new_pad, new_index, old_pad, old_index);
}
} else if (cmd == "USB_TEMP_REMOVE_FIGURE") {
const auto ref = Libraries::Usbd::usb_backend->GetImplRef();
if (ref) {
const u8 index = next_u64();
ref->TempRemoveFigure(index);
}
} else if (cmd == "USB_CANCEL_REMOVE_FIGURE") {
const auto ref = Libraries::Usbd::usb_backend->GetImplRef();
if (ref) {
const u8 index = next_u64();
ref->CancelRemoveFigure(index);
}
} else if (cmd == "RELOAD_INPUTS") {
std::string config = next_str();
Input::ParseInputConfig(config);

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "fiber.h"

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,299 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Font {
struct OrbisFontTextCharacter {
// Other fields...
struct OrbisFontTextCharacter* next; // Pointer to the next node 0x00
struct OrbisFontTextCharacter* prev; // Pointer to the next node 0x08
void* textOrder; // Field at offset 0x10 (pointer to text order info)
u32 characterCode; // Field assumed at offset 0x28
u8 unkn_0x31; // Offset 0x31
u8 unkn_0x33; // Offset 0x33
u8 charType; // Field assumed at offset 0x39
u8 bidiLevel; // Field assumed at offset 0x3B stores the Bidi level
u8 formatFlags; // Field at offset 0x3D (stores format-related flags)
};
struct OrbisFontRenderSurface {
void* buffer;
s32 widthByte;
s8 pixelSizeByte;
u8 unkn_0xd;
u8 styleFlag;
u8 unkn_0xf;
s32 width, height;
u32 sc_x0;
u32 sc_y0;
u32 sc_x1;
u32 sc_y1;
void* unkn_28[3];
};
struct OrbisFontStyleFrame {
/*0x00*/ u16 magic; // Expected to be 0xF09
/*0x02*/ u16 flags;
/*0x04*/ s32 dpiX; // DPI scaling factor for width
/*0x08*/ s32 dpiY; // DPI scaling factor for height
/*0x0c*/ s32 scalingFlag; // Indicates whether scaling is enabled
/*0x10*/
/*0x14*/ float scaleWidth; // Width scaling factor
/*0x18*/ float scaleHeight; // Height scaling factor
/*0x1c*/ float weightXScale;
/*0x20*/ float weightYScale;
/*0x24*/ float slantRatio;
};
s32 PS4_SYSV_ABI sceFontAttachDeviceCacheBuffer();
s32 PS4_SYSV_ABI sceFontBindRenderer();
s32 PS4_SYSV_ABI sceFontCharacterGetBidiLevel(OrbisFontTextCharacter* textCharacter,
int* bidiLevel);
s32 PS4_SYSV_ABI sceFontCharacterGetSyllableStringState();
s32 PS4_SYSV_ABI sceFontCharacterGetTextFontCode();
s32 PS4_SYSV_ABI sceFontCharacterGetTextOrder(OrbisFontTextCharacter* textCharacter,
void** pTextOrder);
u32 PS4_SYSV_ABI sceFontCharacterLooksFormatCharacters(OrbisFontTextCharacter* textCharacter);
u32 PS4_SYSV_ABI sceFontCharacterLooksWhiteSpace(OrbisFontTextCharacter* textCharacter);
OrbisFontTextCharacter* PS4_SYSV_ABI
sceFontCharacterRefersTextBack(OrbisFontTextCharacter* textCharacter);
OrbisFontTextCharacter* PS4_SYSV_ABI
sceFontCharacterRefersTextNext(OrbisFontTextCharacter* textCharacter);
s32 PS4_SYSV_ABI sceFontCharactersRefersTextCodes();
s32 PS4_SYSV_ABI sceFontClearDeviceCache();
s32 PS4_SYSV_ABI sceFontCloseFont();
s32 PS4_SYSV_ABI sceFontControl();
s32 PS4_SYSV_ABI sceFontCreateGraphicsDevice();
s32 PS4_SYSV_ABI sceFontCreateGraphicsService();
s32 PS4_SYSV_ABI sceFontCreateGraphicsServiceWithEdition();
s32 PS4_SYSV_ABI sceFontCreateLibrary();
s32 PS4_SYSV_ABI sceFontCreateLibraryWithEdition();
s32 PS4_SYSV_ABI sceFontCreateRenderer();
s32 PS4_SYSV_ABI sceFontCreateRendererWithEdition();
s32 PS4_SYSV_ABI sceFontCreateString();
s32 PS4_SYSV_ABI sceFontCreateWords();
s32 PS4_SYSV_ABI sceFontCreateWritingLine();
s32 PS4_SYSV_ABI sceFontDefineAttribute();
s32 PS4_SYSV_ABI sceFontDeleteGlyph();
s32 PS4_SYSV_ABI sceFontDestroyGraphicsDevice();
s32 PS4_SYSV_ABI sceFontDestroyGraphicsService();
s32 PS4_SYSV_ABI sceFontDestroyLibrary();
s32 PS4_SYSV_ABI sceFontDestroyRenderer();
s32 PS4_SYSV_ABI sceFontDestroyString();
s32 PS4_SYSV_ABI sceFontDestroyWords();
s32 PS4_SYSV_ABI sceFontDestroyWritingLine();
s32 PS4_SYSV_ABI sceFontDettachDeviceCacheBuffer();
s32 PS4_SYSV_ABI sceFontGenerateCharGlyph();
s32 PS4_SYSV_ABI sceFontGetAttribute();
s32 PS4_SYSV_ABI sceFontGetCharGlyphCode();
s32 PS4_SYSV_ABI sceFontGetCharGlyphMetrics();
s32 PS4_SYSV_ABI sceFontGetEffectSlant();
s32 PS4_SYSV_ABI sceFontGetEffectWeight();
s32 PS4_SYSV_ABI sceFontGetFontGlyphsCount();
s32 PS4_SYSV_ABI sceFontGetFontGlyphsOutlineProfile();
s32 PS4_SYSV_ABI sceFontGetFontMetrics();
s32 PS4_SYSV_ABI sceFontGetFontResolution();
s32 PS4_SYSV_ABI sceFontGetFontStyleInformation();
s32 PS4_SYSV_ABI sceFontGetGlyphExpandBufferState();
s32 PS4_SYSV_ABI sceFontGetHorizontalLayout();
s32 PS4_SYSV_ABI sceFontGetKerning();
s32 PS4_SYSV_ABI sceFontGetLibrary();
s32 PS4_SYSV_ABI sceFontGetPixelResolution();
s32 PS4_SYSV_ABI sceFontGetRenderCharGlyphMetrics();
s32 PS4_SYSV_ABI sceFontGetRenderEffectSlant();
s32 PS4_SYSV_ABI sceFontGetRenderEffectWeight();
s32 PS4_SYSV_ABI sceFontGetRenderScaledKerning();
s32 PS4_SYSV_ABI sceFontGetRenderScalePixel();
s32 PS4_SYSV_ABI sceFontGetRenderScalePoint();
s32 PS4_SYSV_ABI sceFontGetResolutionDpi();
s32 PS4_SYSV_ABI sceFontGetScalePixel();
s32 PS4_SYSV_ABI sceFontGetScalePoint();
s32 PS4_SYSV_ABI sceFontGetScriptLanguage();
s32 PS4_SYSV_ABI sceFontGetTypographicDesign();
s32 PS4_SYSV_ABI sceFontGetVerticalLayout();
s32 PS4_SYSV_ABI sceFontGlyphDefineAttribute();
s32 PS4_SYSV_ABI sceFontGlyphGetAttribute();
s32 PS4_SYSV_ABI sceFontGlyphGetGlyphForm();
s32 PS4_SYSV_ABI sceFontGlyphGetMetricsForm();
s32 PS4_SYSV_ABI sceFontGlyphGetScalePixel();
s32 PS4_SYSV_ABI sceFontGlyphRefersMetrics();
s32 PS4_SYSV_ABI sceFontGlyphRefersMetricsHorizontal();
s32 PS4_SYSV_ABI sceFontGlyphRefersMetricsHorizontalAdvance();
s32 PS4_SYSV_ABI sceFontGlyphRefersMetricsHorizontalX();
s32 PS4_SYSV_ABI sceFontGlyphRefersOutline();
s32 PS4_SYSV_ABI sceFontGlyphRenderImage();
s32 PS4_SYSV_ABI sceFontGlyphRenderImageHorizontal();
s32 PS4_SYSV_ABI sceFontGlyphRenderImageVertical();
s32 PS4_SYSV_ABI sceFontGraphicsBeginFrame();
s32 PS4_SYSV_ABI sceFontGraphicsDrawingCancel();
s32 PS4_SYSV_ABI sceFontGraphicsDrawingFinish();
s32 PS4_SYSV_ABI sceFontGraphicsEndFrame();
s32 PS4_SYSV_ABI sceFontGraphicsExchangeResource();
s32 PS4_SYSV_ABI sceFontGraphicsFillMethodInit();
s32 PS4_SYSV_ABI sceFontGraphicsFillPlotInit();
s32 PS4_SYSV_ABI sceFontGraphicsFillPlotSetLayout();
s32 PS4_SYSV_ABI sceFontGraphicsFillPlotSetMapping();
s32 PS4_SYSV_ABI sceFontGraphicsFillRatesInit();
s32 PS4_SYSV_ABI sceFontGraphicsFillRatesSetFillEffect();
s32 PS4_SYSV_ABI sceFontGraphicsFillRatesSetLayout();
s32 PS4_SYSV_ABI sceFontGraphicsFillRatesSetMapping();
s32 PS4_SYSV_ABI sceFontGraphicsGetDeviceUsage();
s32 PS4_SYSV_ABI sceFontGraphicsRegionInit();
s32 PS4_SYSV_ABI sceFontGraphicsRegionInitCircular();
s32 PS4_SYSV_ABI sceFontGraphicsRegionInitRoundish();
s32 PS4_SYSV_ABI sceFontGraphicsRelease();
s32 PS4_SYSV_ABI sceFontGraphicsRenderResource();
s32 PS4_SYSV_ABI sceFontGraphicsSetFramePolicy();
s32 PS4_SYSV_ABI sceFontGraphicsSetupClipping();
s32 PS4_SYSV_ABI sceFontGraphicsSetupColorRates();
s32 PS4_SYSV_ABI sceFontGraphicsSetupFillMethod();
s32 PS4_SYSV_ABI sceFontGraphicsSetupFillRates();
s32 PS4_SYSV_ABI sceFontGraphicsSetupGlyphFill();
s32 PS4_SYSV_ABI sceFontGraphicsSetupGlyphFillPlot();
s32 PS4_SYSV_ABI sceFontGraphicsSetupHandleDefault();
s32 PS4_SYSV_ABI sceFontGraphicsSetupLocation();
s32 PS4_SYSV_ABI sceFontGraphicsSetupPositioning();
s32 PS4_SYSV_ABI sceFontGraphicsSetupRotation();
s32 PS4_SYSV_ABI sceFontGraphicsSetupScaling();
s32 PS4_SYSV_ABI sceFontGraphicsSetupShapeFill();
s32 PS4_SYSV_ABI sceFontGraphicsSetupShapeFillPlot();
s32 PS4_SYSV_ABI sceFontGraphicsStructureCanvas();
s32 PS4_SYSV_ABI sceFontGraphicsStructureCanvasSequence();
s32 PS4_SYSV_ABI sceFontGraphicsStructureDesign();
s32 PS4_SYSV_ABI sceFontGraphicsStructureDesignResource();
s32 PS4_SYSV_ABI sceFontGraphicsStructureSurfaceTexture();
s32 PS4_SYSV_ABI sceFontGraphicsUpdateClipping();
s32 PS4_SYSV_ABI sceFontGraphicsUpdateColorRates();
s32 PS4_SYSV_ABI sceFontGraphicsUpdateFillMethod();
s32 PS4_SYSV_ABI sceFontGraphicsUpdateFillRates();
s32 PS4_SYSV_ABI sceFontGraphicsUpdateGlyphFill();
s32 PS4_SYSV_ABI sceFontGraphicsUpdateGlyphFillPlot();
s32 PS4_SYSV_ABI sceFontGraphicsUpdateLocation();
s32 PS4_SYSV_ABI sceFontGraphicsUpdatePositioning();
s32 PS4_SYSV_ABI sceFontGraphicsUpdateRotation();
s32 PS4_SYSV_ABI sceFontGraphicsUpdateScaling();
s32 PS4_SYSV_ABI sceFontGraphicsUpdateShapeFill();
s32 PS4_SYSV_ABI sceFontGraphicsUpdateShapeFillPlot();
s32 PS4_SYSV_ABI sceFontMemoryInit();
s32 PS4_SYSV_ABI sceFontMemoryTerm();
s32 PS4_SYSV_ABI sceFontOpenFontFile();
s32 PS4_SYSV_ABI sceFontOpenFontInstance();
s32 PS4_SYSV_ABI sceFontOpenFontMemory();
s32 PS4_SYSV_ABI sceFontOpenFontSet();
s32 PS4_SYSV_ABI sceFontRebindRenderer();
s32 PS4_SYSV_ABI sceFontRenderCharGlyphImage();
s32 PS4_SYSV_ABI sceFontRenderCharGlyphImageHorizontal();
s32 PS4_SYSV_ABI sceFontRenderCharGlyphImageVertical();
s32 PS4_SYSV_ABI sceFontRendererGetOutlineBufferSize();
s32 PS4_SYSV_ABI sceFontRendererResetOutlineBuffer();
s32 PS4_SYSV_ABI sceFontRendererSetOutlineBufferPolicy();
void PS4_SYSV_ABI sceFontRenderSurfaceInit(OrbisFontRenderSurface* renderSurface, void* buffer,
int bufWidthByte, int pixelSizeByte, int widthPixel,
int heightPixel);
void PS4_SYSV_ABI sceFontRenderSurfaceSetScissor(OrbisFontRenderSurface* renderSurface, int x0,
int y0, int w, int h);
s32 PS4_SYSV_ABI sceFontRenderSurfaceSetStyleFrame(OrbisFontRenderSurface* renderSurface,
OrbisFontStyleFrame* styleFrame);
s32 PS4_SYSV_ABI sceFontSetEffectSlant();
s32 PS4_SYSV_ABI sceFontSetEffectWeight();
s32 PS4_SYSV_ABI sceFontSetFontsOpenMode();
s32 PS4_SYSV_ABI sceFontSetResolutionDpi();
s32 PS4_SYSV_ABI sceFontSetScalePixel();
s32 PS4_SYSV_ABI sceFontSetScalePoint();
s32 PS4_SYSV_ABI sceFontSetScriptLanguage();
s32 PS4_SYSV_ABI sceFontSetTypographicDesign();
s32 PS4_SYSV_ABI sceFontSetupRenderEffectSlant();
s32 PS4_SYSV_ABI sceFontSetupRenderEffectWeight();
s32 PS4_SYSV_ABI sceFontSetupRenderScalePixel();
s32 PS4_SYSV_ABI sceFontSetupRenderScalePoint();
s32 PS4_SYSV_ABI sceFontStringGetTerminateCode();
s32 PS4_SYSV_ABI sceFontStringGetTerminateOrder();
s32 PS4_SYSV_ABI sceFontStringGetWritingForm();
s32 PS4_SYSV_ABI sceFontStringRefersRenderCharacters();
s32 PS4_SYSV_ABI sceFontStringRefersTextCharacters();
s32 PS4_SYSV_ABI sceFontStyleFrameGetEffectSlant(OrbisFontStyleFrame* styleFrame,
float* slantRatio);
s32 PS4_SYSV_ABI sceFontStyleFrameGetEffectWeight(OrbisFontStyleFrame* fontStyleFrame,
float* weightXScale, float* weightYScale,
uint32_t* mode);
s32 PS4_SYSV_ABI sceFontStyleFrameGetResolutionDpi();
s32 PS4_SYSV_ABI sceFontStyleFrameGetScalePixel(OrbisFontStyleFrame* styleFrame, float* w,
float* h);
s32 PS4_SYSV_ABI sceFontStyleFrameGetScalePoint();
s32 PS4_SYSV_ABI sceFontStyleFrameInit();
s32 PS4_SYSV_ABI sceFontStyleFrameSetEffectSlant();
s32 PS4_SYSV_ABI sceFontStyleFrameSetEffectWeight();
s32 PS4_SYSV_ABI sceFontStyleFrameSetResolutionDpi();
s32 PS4_SYSV_ABI sceFontStyleFrameSetScalePixel();
s32 PS4_SYSV_ABI sceFontStyleFrameSetScalePoint();
s32 PS4_SYSV_ABI sceFontStyleFrameUnsetEffectSlant();
s32 PS4_SYSV_ABI sceFontStyleFrameUnsetEffectWeight();
s32 PS4_SYSV_ABI sceFontStyleFrameUnsetScale();
s32 PS4_SYSV_ABI sceFontSupportExternalFonts();
s32 PS4_SYSV_ABI sceFontSupportGlyphs();
s32 PS4_SYSV_ABI sceFontSupportSystemFonts();
s32 PS4_SYSV_ABI sceFontTextCodesStepBack();
s32 PS4_SYSV_ABI sceFontTextCodesStepNext();
s32 PS4_SYSV_ABI sceFontTextSourceInit();
s32 PS4_SYSV_ABI sceFontTextSourceRewind();
s32 PS4_SYSV_ABI sceFontTextSourceSetDefaultFont();
s32 PS4_SYSV_ABI sceFontTextSourceSetWritingForm();
s32 PS4_SYSV_ABI sceFontUnbindRenderer();
s32 PS4_SYSV_ABI sceFontWordsFindWordCharacters();
s32 PS4_SYSV_ABI sceFontWritingGetRenderMetrics();
s32 PS4_SYSV_ABI sceFontWritingInit();
s32 PS4_SYSV_ABI sceFontWritingLineClear();
s32 PS4_SYSV_ABI sceFontWritingLineGetOrderingSpace();
s32 PS4_SYSV_ABI sceFontWritingLineGetRenderMetrics();
s32 PS4_SYSV_ABI sceFontWritingLineRefersRenderStep();
s32 PS4_SYSV_ABI sceFontWritingLineWritesOrder();
s32 PS4_SYSV_ABI sceFontWritingRefersRenderStep();
s32 PS4_SYSV_ABI sceFontWritingRefersRenderStepCharacter();
s32 PS4_SYSV_ABI sceFontWritingSetMaskInvisible();
s32 PS4_SYSV_ABI Func_00F4D778F1C88CB3();
s32 PS4_SYSV_ABI Func_03C650025FBB0DE7();
s32 PS4_SYSV_ABI Func_07EAB8A163B27E1A();
s32 PS4_SYSV_ABI Func_09408E88E4F97CE3();
s32 PS4_SYSV_ABI Func_09F92905ED82A814();
s32 PS4_SYSV_ABI Func_0D142CEE1AB21ABE();
s32 PS4_SYSV_ABI Func_14BD2E9E119C16F2();
s32 PS4_SYSV_ABI Func_1AC53C9EDEAE8D75();
s32 PS4_SYSV_ABI Func_1D401185D5E24C3D();
s32 PS4_SYSV_ABI Func_1E83CD20C2CC996F();
s32 PS4_SYSV_ABI Func_314B1F765B9FE78A();
s32 PS4_SYSV_ABI Func_350E6725FEDE29E1();
s32 PS4_SYSV_ABI Func_3DB773F0A604BF39();
s32 PS4_SYSV_ABI Func_4FF49DD21E311B1C();
s32 PS4_SYSV_ABI Func_526287664A493981();
s32 PS4_SYSV_ABI Func_55CA718DBC84A6E9();
s32 PS4_SYSV_ABI Func_563FC5F0706A8B4D();
s32 PS4_SYSV_ABI Func_569E2ECD34290F45();
s32 PS4_SYSV_ABI Func_5A04775B6BE47685();
s32 PS4_SYSV_ABI Func_5FD93BCAB6F79750();
s32 PS4_SYSV_ABI Func_62B5398F864BD3B4();
s32 PS4_SYSV_ABI Func_6F9010294D822367();
s32 PS4_SYSV_ABI Func_7757E947423A7A67();
s32 PS4_SYSV_ABI Func_7E06BA52077F54FA();
s32 PS4_SYSV_ABI Func_93B36DEA021311D6();
s32 PS4_SYSV_ABI Func_94B0891E7111598A();
s32 PS4_SYSV_ABI Func_9785C9128C2FE7CD();
s32 PS4_SYSV_ABI Func_97DFBC9B65FBC0E1();
s32 PS4_SYSV_ABI Func_ACD9717405D7D3CA();
s32 PS4_SYSV_ABI Func_B19A8AEC3FD4F16F();
s32 PS4_SYSV_ABI Func_C10F488AD7CF103D();
s32 PS4_SYSV_ABI Func_D0C8B5FF4A6826C7();
s32 PS4_SYSV_ABI Func_E48D3CD01C342A33();
s32 PS4_SYSV_ABI Func_EAC96B2186B71E14();
s32 PS4_SYSV_ABI Func_FE4788A96EF46256();
s32 PS4_SYSV_ABI Func_FE7E5AE95D3058F5();
void RegisterlibSceFont(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Font

View File

@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/libraries/error_codes.h"
constexpr int ORBIS_FONT_ERROR_FATAL = 0x80460001;
constexpr int ORBIS_FONT_ERROR_INVALID_PARAMETER = 0x80460002;
constexpr int ORBIS_FONT_ERROR_INVALID_MEMORY = 0x80460003;
constexpr int ORBIS_FONT_ERROR_INVALID_LIBRARY = 0x80460004;
constexpr int ORBIS_FONT_ERROR_INVALID_FONT_HANDLE = 0x80460005;
constexpr int ORBIS_FONT_ERROR_INVALID_GLYPH = 0x80460006;
constexpr int ORBIS_FONT_ERROR_INVALID_RENDERER = 0x80460007;
constexpr int ORBIS_FONT_ERROR_INVALID_TEXT_SOURCE = 0x80460008;
constexpr int ORBIS_FONT_ERROR_INVALID_STRING = 0x80460009;
constexpr int ORBIS_FONT_ERROR_INVALID_WRITING = 0x8046000A;
constexpr int ORBIS_FONT_ERROR_INVALID_WORDS = 0x8046000B;
constexpr int ORBIS_FONT_ERROR_ALLOCATION_FAILED = 0x80460010;
constexpr int ORBIS_FONT_ERROR_FS_OPEN_FAILED = 0x80460011;
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_LIBRARY = 0x80460018;
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_FORMAT = 0x80460019;
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_FUNCTION = 0x80460020;
constexpr int ORBIS_FONT_ERROR_ALREADY_SPECIFIED = 0x80460021;
constexpr int ORBIS_FONT_ERROR_ALREADY_ATTACHED = 0x80460022;
constexpr int ORBIS_FONT_ERROR_ALREADY_OPENED = 0x80460023;
constexpr int ORBIS_FONT_ERROR_NOT_ATTACHED_CACHE_BUFFER = 0x80460025;
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_FONTSET = 0x80460031;
constexpr int ORBIS_FONT_ERROR_FONT_OPEN_MAX = 0x80460033;
constexpr int ORBIS_FONT_ERROR_FONT_OPEN_FAILED = 0x80460036;
constexpr int ORBIS_FONT_ERROR_FONT_CLOSE_FAILED = 0x80460037;
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_TYPOGRAPHY = 0x80460040;
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_CODE = 0x80460041;
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH = 0x80460042;
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_SCRIPT = 0x80460043;
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_LANGUAGE = 0x80460044;
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_SURFACE = 0x80460050;
constexpr int ORBIS_FONT_ERROR_UNSET_PARAMETER = 0x80460058;
constexpr int ORBIS_FONT_ERROR_FUNCTIONAL_LIMIT = 0x8046005C;
constexpr int ORBIS_FONT_ERROR_ALREADY_BOUND_RENDERER = 0x80460060;
constexpr int ORBIS_FONT_ERROR_NOT_BOUND_RENDERER = 0x80460061;
constexpr int ORBIS_FONT_ERROR_RENDERER_ALLOCATION_FAILED = 0x80460063;
constexpr int ORBIS_FONT_ERROR_RENDERER_ALLOCATION_LIMITED = 0x80460064;
constexpr int ORBIS_FONT_ERROR_RENDERER_RENDER_FAILED = 0x80460065;

View File

@ -0,0 +1,140 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/font/fontft.h"
#include "core/libraries/libs.h"
namespace Libraries::FontFt {
s32 PS4_SYSV_ABI sceFontFtInitAliases() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSetAliasFont() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSetAliasPath() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportBdf() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportCid() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportFontFormats() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportOpenType() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportOpenTypeOtf() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportOpenTypeTtf() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportPcf() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportPfr() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportSystemFonts() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportTrueType() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportTrueTypeGx() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportType1() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportType42() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportWinFonts() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtTermAliases() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontSelectGlyphsFt() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontSelectLibraryFt() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontSelectRendererFt() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
void RegisterlibSceFontFt(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("e60aorDdpB8", "libSceFontFt", 1, "libSceFontFt", sceFontFtInitAliases);
LIB_FUNCTION("BxcmiMc3UaA", "libSceFontFt", 1, "libSceFontFt", sceFontFtSetAliasFont);
LIB_FUNCTION("MEWjebIzDEI", "libSceFontFt", 1, "libSceFontFt", sceFontFtSetAliasPath);
LIB_FUNCTION("ZcQL0iSjvFw", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportBdf);
LIB_FUNCTION("LADHEyFTxRQ", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportCid);
LIB_FUNCTION("+jqQjsancTs", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportFontFormats);
LIB_FUNCTION("oakL15-mBtc", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportOpenType);
LIB_FUNCTION("dcQeaDr8UJc", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportOpenTypeOtf);
LIB_FUNCTION("2KXS-HkZT3c", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportOpenTypeTtf);
LIB_FUNCTION("H0mJnhKwV-s", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportPcf);
LIB_FUNCTION("S2mw3sYplAI", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportPfr);
LIB_FUNCTION("+ehNXJPUyhk", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportSystemFonts);
LIB_FUNCTION("4BAhDLdrzUI", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportTrueType);
LIB_FUNCTION("Utlzbdf+g9o", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportTrueTypeGx);
LIB_FUNCTION("nAfQ6qaL1fU", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportType1);
LIB_FUNCTION("X9+pzrGtBus", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportType42);
LIB_FUNCTION("w0hI3xsK-hc", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportWinFonts);
LIB_FUNCTION("w5sfH9r8ZJ4", "libSceFontFt", 1, "libSceFontFt", sceFontFtTermAliases);
LIB_FUNCTION("ojW+VKl4Ehs", "libSceFontFt", 1, "libSceFontFt", sceFontSelectGlyphsFt);
LIB_FUNCTION("oM+XCzVG3oM", "libSceFontFt", 1, "libSceFontFt", sceFontSelectLibraryFt);
LIB_FUNCTION("Xx974EW-QFY", "libSceFontFt", 1, "libSceFontFt", sceFontSelectRendererFt);
};
} // namespace Libraries::FontFt

View File

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::FontFt {
s32 PS4_SYSV_ABI sceFontFtInitAliases();
s32 PS4_SYSV_ABI sceFontFtSetAliasFont();
s32 PS4_SYSV_ABI sceFontFtSetAliasPath();
s32 PS4_SYSV_ABI sceFontFtSupportBdf();
s32 PS4_SYSV_ABI sceFontFtSupportCid();
s32 PS4_SYSV_ABI sceFontFtSupportFontFormats();
s32 PS4_SYSV_ABI sceFontFtSupportOpenType();
s32 PS4_SYSV_ABI sceFontFtSupportOpenTypeOtf();
s32 PS4_SYSV_ABI sceFontFtSupportOpenTypeTtf();
s32 PS4_SYSV_ABI sceFontFtSupportPcf();
s32 PS4_SYSV_ABI sceFontFtSupportPfr();
s32 PS4_SYSV_ABI sceFontFtSupportSystemFonts();
s32 PS4_SYSV_ABI sceFontFtSupportTrueType();
s32 PS4_SYSV_ABI sceFontFtSupportTrueTypeGx();
s32 PS4_SYSV_ABI sceFontFtSupportType1();
s32 PS4_SYSV_ABI sceFontFtSupportType42();
s32 PS4_SYSV_ABI sceFontFtSupportWinFonts();
s32 PS4_SYSV_ABI sceFontFtTermAliases();
s32 PS4_SYSV_ABI sceFontSelectGlyphsFt();
s32 PS4_SYSV_ABI sceFontSelectLibraryFt();
s32 PS4_SYSV_ABI sceFontSelectRendererFt();
void RegisterlibSceFontFt(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::FontFt

View File

@ -7,6 +7,7 @@
#include "common/assert.h"
#include "common/config.h"
#include "common/debug.h"
#include "common/elf_info.h"
#include "common/logging/log.h"
#include "common/slot_vector.h"
#include "core/address_space.h"
@ -68,7 +69,7 @@ std::condition_variable cv_lock{};
std::mutex m_submission{};
static u64 frames_submitted{}; // frame counter
static bool send_init_packet{true}; // initialize HW state before first game's submit in a frame
static int sdk_version{0};
static s32 sdk_version{0};
static u32 asc_next_offs_dw[Liverpool::NumComputeRings];
@ -2199,9 +2200,9 @@ int PS4_SYSV_ABI sceGnmSubmitCommandBuffersForWorkload(u32 workload, u32 count,
}
if (send_init_packet) {
if (sdk_version <= 0x1ffffffu) {
if (sdk_version < Common::ElfInfo::FW_20) {
liverpool->SubmitGfx(InitSequence, {});
} else if (sdk_version <= 0x3ffffffu) {
} else if (sdk_version < Common::ElfInfo::FW_40) {
if (sceKernelIsNeoMode()) {
if (!UseNeoCompatSequences) {
liverpool->SubmitGfx(InitSequence200Neo, {});
@ -2780,14 +2781,20 @@ int PS4_SYSV_ABI Func_C4C328B7CF3B4171() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceGnmDrawInitToDefaultContextStateInternalCommand() {
LOG_ERROR(Lib_GnmDriver, "(STUBBED) called");
return ORBIS_OK;
int PS4_SYSV_ABI sceGnmDrawInitToDefaultContextStateInternalCommand(u32* cmdbuf, u32 size) {
LOG_TRACE(Lib_GnmDriver, "called");
if (sdk_version >= Common::ElfInfo::FW_40) {
return sceGnmDrawInitToDefaultContextState400(cmdbuf, size);
}
return sceGnmDrawInitToDefaultContextState(cmdbuf, size);
}
int PS4_SYSV_ABI sceGnmDrawInitToDefaultContextStateInternalSize() {
LOG_ERROR(Lib_GnmDriver, "(STUBBED) called");
return ORBIS_OK;
LOG_TRACE(Lib_GnmDriver, "called");
if (sdk_version >= Common::ElfInfo::FW_40) {
return 0x100;
}
return 0x20;
}
int PS4_SYSV_ABI sceGnmFindResources() {

View File

@ -286,7 +286,7 @@ int PS4_SYSV_ABI Func_ECB4C6BA41FE3350();
int PS4_SYSV_ABI sceGnmDebugModuleReset();
int PS4_SYSV_ABI sceGnmDebugReset();
int PS4_SYSV_ABI Func_C4C328B7CF3B4171();
int PS4_SYSV_ABI sceGnmDrawInitToDefaultContextStateInternalCommand();
int PS4_SYSV_ABI sceGnmDrawInitToDefaultContextStateInternalCommand(u32* cmdbuf, u32 size);
int PS4_SYSV_ABI sceGnmDrawInitToDefaultContextStateInternalSize();
int PS4_SYSV_ABI sceGnmFindResources();
int PS4_SYSV_ABI sceGnmGetResourceRegistrationBuffers();

View File

@ -37,6 +37,7 @@
#endif
namespace D = Core::Devices;
namespace fs = std::filesystem;
using FactoryDevice = std::function<std::shared_ptr<D::BaseDevice>(u32, const char*, int, u16)>;
#define GET_DEVICE_FD(fd) \
@ -74,6 +75,7 @@ namespace Libraries::Kernel {
s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
LOG_INFO(Kernel_Fs, "path = {} flags = {:#x} mode = {:#o}", raw_path, flags, mode);
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
@ -87,6 +89,11 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
return -1;
}
if (strlen(raw_path) > 255) {
*__Error() = POSIX_ENAMETOOLONG;
return -1;
}
bool nonblock = (flags & ORBIS_KERNEL_O_NONBLOCK) != 0;
bool append = (flags & ORBIS_KERNEL_O_APPEND) != 0;
// Flags fsync and sync behave the same
@ -121,7 +128,7 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
bool read_only = false;
file->m_guest_name = path;
file->m_host_name = mnt->GetHostPath(file->m_guest_name, &read_only);
bool exists = std::filesystem::exists(file->m_host_name);
bool exists = fs::exists(file->m_host_name);
s32 e = 0;
if (create) {
@ -140,7 +147,7 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
return -1;
}
// Create a file if it doesn't exist
Common::FS::IOFile out(file->m_host_name, Common::FS::FileAccessMode::Write);
Common::FS::IOFile out(file->m_host_name, Common::FS::FileAccessMode::Create);
}
} else if (!exists) {
// If we're not creating a file, and it doesn't exist, return ENOENT
@ -149,14 +156,14 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
return -1;
}
if (std::filesystem::is_directory(file->m_host_name) || directory) {
if (fs::is_directory(file->m_host_name) || directory) {
// Directories can be opened even if the directory flag isn't set.
// In these cases, error behavior is identical to the directory code path.
directory = true;
}
if (directory) {
if (!std::filesystem::is_directory(file->m_host_name)) {
if (!fs::is_directory(file->m_host_name)) {
// If the opened file is not a directory, return ENOTDIR.
// This will trigger when create & directory is specified, this is expected.
h->DeleteHandle(handle);
@ -205,22 +212,30 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
}
if (read) {
// Read only
// Open exclusively for reading
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Read);
} else if (read_only) {
// Can't open files with write/read-write access in a read only directory
h->DeleteHandle(handle);
*__Error() = POSIX_EROFS;
return -1;
} else if (append) {
// Append can be specified with rdwr or write, but we treat it as a separate mode.
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Append);
} else if (write) {
// Write only
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Write);
if (append) {
// Open exclusively for appending
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Append);
} else {
// Open exclusively for writing
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Write);
}
} else if (rdwr) {
// Read and write
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::ReadWrite);
if (append) {
// Open for reading and appending
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::ReadAppend);
} else {
// Open for reading and writing
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::ReadWrite);
}
}
}
@ -303,6 +318,9 @@ s64 PS4_SYSV_ABI write(s32 fd, const void* buf, u64 nbytes) {
} else if (file->type == Core::FileSys::FileType::Socket) {
// Socket functions handle errnos internally.
return file->socket->SendPacket(buf, nbytes, 0, nullptr, 0);
} else if (file->type == Core::FileSys::FileType::Directory) {
*__Error() = POSIX_EBADF;
return -1;
}
return file->f.WriteRaw<u8>(buf, nbytes);
@ -354,6 +372,12 @@ s64 PS4_SYSV_ABI readv(s32 fd, const OrbisKernelIovec* iov, s32 iovcnt) {
}
return result;
}
if (file->f.IsWriteOnly()) {
*__Error() = POSIX_EBADF;
return -1;
}
s64 total_read = 0;
for (s32 i = 0; i < iovcnt; i++) {
total_read += ReadFile(file->f, iov[i].iov_base, iov[i].iov_len);
@ -391,7 +415,11 @@ s64 PS4_SYSV_ABI writev(s32 fd, const OrbisKernelIovec* iov, s32 iovcnt) {
return -1;
}
return result;
} else if (file->type == Core::FileSys::FileType::Directory) {
*__Error() = POSIX_EBADF;
return -1;
}
s64 total_written = 0;
for (s32 i = 0; i < iovcnt; i++) {
total_written += file->f.WriteRaw<u8>(iov[i].iov_base, iov[i].iov_len);
@ -509,6 +537,12 @@ s64 PS4_SYSV_ABI read(s32 fd, void* buf, u64 nbytes) {
// Socket functions handle errnos internally.
return file->socket->ReceivePacket(buf, nbytes, 0, nullptr, 0);
}
if (file->f.IsWriteOnly()) {
*__Error() = POSIX_EBADF;
return -1;
}
return ReadFile(file->f, buf, nbytes);
}
@ -527,6 +561,10 @@ s64 PS4_SYSV_ABI sceKernelRead(s32 fd, void* buf, u64 nbytes) {
s32 PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) {
LOG_INFO(Kernel_Fs, "path = {} mode = {:#o}", path, mode);
if (strlen(path) > 255) {
*__Error() = POSIX_ENAMETOOLONG;
return -1;
}
if (path == nullptr) {
*__Error() = POSIX_ENOTDIR;
return -1;
@ -536,7 +574,7 @@ s32 PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) {
bool ro = false;
const auto dir_name = mnt->GetHostPath(path, &ro);
if (std::filesystem::exists(dir_name)) {
if (fs::exists(dir_name)) {
*__Error() = POSIX_EEXIST;
return -1;
}
@ -548,12 +586,12 @@ s32 PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) {
// CUSA02456: path = /aotl after sceSaveDataMount(mode = 1)
std::error_code ec;
if (dir_name.empty() || !std::filesystem::create_directory(dir_name, ec)) {
if (dir_name.empty() || !fs::create_directory(dir_name, ec)) {
*__Error() = POSIX_EIO;
return -1;
}
if (!std::filesystem::exists(dir_name)) {
if (!fs::exists(dir_name)) {
*__Error() = POSIX_ENOENT;
return -1;
}
@ -570,28 +608,32 @@ s32 PS4_SYSV_ABI sceKernelMkdir(const char* path, u16 mode) {
}
s32 PS4_SYSV_ABI posix_rmdir(const char* path) {
if (strlen(path) > 255) {
*__Error() = POSIX_ENAMETOOLONG;
return -1;
}
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
bool ro = false;
const std::filesystem::path dir_name = mnt->GetHostPath(path, &ro);
const fs::path dir_name = mnt->GetHostPath(path, &ro);
if (ro) {
*__Error() = POSIX_EROFS;
return -1;
}
if (dir_name.empty() || !std::filesystem::is_directory(dir_name)) {
if (dir_name.empty() || !fs::is_directory(dir_name)) {
*__Error() = POSIX_ENOTDIR;
return -1;
}
if (!std::filesystem::exists(dir_name)) {
if (!fs::exists(dir_name)) {
*__Error() = POSIX_ENOENT;
return -1;
}
std::error_code ec;
s32 result = std::filesystem::remove_all(dir_name, ec);
s32 result = fs::remove_all(dir_name, ec);
if (ec) {
*__Error() = POSIX_EIO;
@ -611,26 +653,42 @@ s32 PS4_SYSV_ABI sceKernelRmdir(const char* path) {
s32 PS4_SYSV_ABI posix_stat(const char* path, OrbisKernelStat* sb) {
LOG_DEBUG(Kernel_Fs, "(PARTIAL) path = {}", path);
if (strlen(path) > 255) {
*__Error() = POSIX_ENAMETOOLONG;
return -1;
}
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
const auto path_name = mnt->GetHostPath(path);
std::memset(sb, 0, sizeof(OrbisKernelStat));
const bool is_dir = std::filesystem::is_directory(path_name);
const bool is_file = std::filesystem::is_regular_file(path_name);
const bool is_dir = fs::is_directory(path_name);
const bool is_file = fs::is_regular_file(path_name);
if (!is_dir && !is_file) {
*__Error() = POSIX_ENOENT;
return -1;
}
if (std::filesystem::is_directory(path_name)) {
// get the difference between file clock and system clock
const auto now_sys = std::chrono::system_clock::now();
const auto now_file = fs::file_time_type::clock::now();
// calculate the file modified time
const auto mtime = fs::last_write_time(path_name);
const auto mtimestamp = now_sys + (mtime - now_file);
if (fs::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_size = static_cast<s64>(fs::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
}
@ -647,6 +705,10 @@ s32 PS4_SYSV_ABI sceKernelStat(const char* path, OrbisKernelStat* sb) {
}
s32 PS4_SYSV_ABI sceKernelCheckReachability(const char* path) {
if (strlen(path) > 255) {
return ORBIS_KERNEL_ERROR_ENAMETOOLONG;
}
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
std::string_view guest_path{path};
for (const auto& prefix : available_device | std::views::keys) {
@ -655,7 +717,7 @@ s32 PS4_SYSV_ABI sceKernelCheckReachability(const char* path) {
}
}
const auto path_name = mnt->GetHostPath(guest_path);
if (!std::filesystem::exists(path_name)) {
if (!fs::exists(path_name)) {
return ORBIS_KERNEL_ERROR_ENOENT;
}
return ORBIS_OK;
@ -768,7 +830,15 @@ s32 PS4_SYSV_ABI posix_rename(const char* from, const char* to) {
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
bool ro = false;
const auto src_path = mnt->GetHostPath(from, &ro);
if (!std::filesystem::exists(src_path)) {
if (strlen(from) > 255) {
*__Error() = POSIX_ENAMETOOLONG;
return -1;
}
if (strlen(to) > 255) {
*__Error() = POSIX_ENAMETOOLONG;
return -1;
}
if (!fs::exists(src_path)) {
*__Error() = POSIX_ENOENT;
return -1;
}
@ -781,36 +851,36 @@ s32 PS4_SYSV_ABI posix_rename(const char* from, const char* to) {
*__Error() = POSIX_EROFS;
return -1;
}
const bool src_is_dir = std::filesystem::is_directory(src_path);
const bool dst_is_dir = std::filesystem::is_directory(dst_path);
if (src_is_dir && !dst_is_dir) {
*__Error() = POSIX_ENOTDIR;
return -1;
}
if (!src_is_dir && dst_is_dir) {
*__Error() = POSIX_EISDIR;
return -1;
}
if (dst_is_dir && !std::filesystem::is_empty(dst_path)) {
*__Error() = POSIX_ENOTEMPTY;
return -1;
const bool src_is_dir = fs::is_directory(src_path);
const bool dst_is_dir = fs::is_directory(dst_path);
if (fs::exists(dst_path)) {
if (src_is_dir && !dst_is_dir) {
*__Error() = POSIX_ENOTDIR;
return -1;
}
if (!src_is_dir && dst_is_dir) {
*__Error() = POSIX_EISDIR;
return -1;
}
if (dst_is_dir && !fs::is_empty(dst_path)) {
*__Error() = POSIX_ENOTEMPTY;
return -1;
}
}
// On Windows, std::filesystem::rename will error if the file has been opened before.
std::filesystem::copy(src_path, dst_path, std::filesystem::copy_options::overwrite_existing);
// On Windows, fs::rename will error if the file has been opened before.
fs::copy(src_path, dst_path,
fs::copy_options::overwrite_existing | fs::copy_options::recursive);
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto file = h->GetFile(src_path);
if (file) {
// We need to force ReadWrite if the file had Write access before
// Otherwise f.Open will clear the file contents.
auto access_mode = file->f.GetAccessMode() == Common::FS::FileAccessMode::Write
? Common::FS::FileAccessMode::ReadWrite
: file->f.GetAccessMode();
auto access_mode = file->f.GetAccessMode();
file->f.Close();
std::filesystem::remove(src_path);
fs::remove(src_path);
file->f.Open(dst_path, access_mode);
} else {
std::filesystem::remove(src_path);
fs::remove_all(src_path);
}
return ORBIS_OK;
@ -855,6 +925,11 @@ s64 PS4_SYSV_ABI posix_preadv(s32 fd, OrbisKernelIovec* iov, s32 iovcnt, s64 off
return result;
}
if (file->f.IsWriteOnly()) {
*__Error() = POSIX_EBADF;
return -1;
}
const s64 pos = file->f.Tell();
SCOPE_EXIT {
file->f.Seek(pos);
@ -1014,7 +1089,11 @@ s64 PS4_SYSV_ABI posix_pwritev(s32 fd, const OrbisKernelIovec* iov, s32 iovcnt,
return -1;
}
return result;
} else if (file->type == Core::FileSys::FileType::Directory) {
*__Error() = POSIX_EBADF;
return -1;
}
const s64 pos = file->f.Tell();
SCOPE_EXIT {
file->f.Seek(pos);
@ -1054,6 +1133,10 @@ s64 PS4_SYSV_ABI sceKernelPwritev(s32 fd, const OrbisKernelIovec* iov, s32 iovcn
}
s32 PS4_SYSV_ABI posix_unlink(const char* path) {
if (strlen(path) > 255) {
*__Error() = POSIX_ENAMETOOLONG;
return -1;
}
if (path == nullptr) {
*__Error() = POSIX_EINVAL;
return -1;
@ -1074,7 +1157,7 @@ s32 PS4_SYSV_ABI posix_unlink(const char* path) {
return -1;
}
if (std::filesystem::is_directory(host_path)) {
if (fs::is_directory(host_path)) {
*__Error() = POSIX_EPERM;
return -1;
}
@ -1447,6 +1530,7 @@ void RegisterFileSystem(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("FCcmRZhWtOk", "libkernel", 1, "libkernel", posix_pwritev);
LIB_FUNCTION("nKWi-N2HBV4", "libkernel", 1, "libkernel", sceKernelPwrite);
LIB_FUNCTION("mBd4AfLP+u8", "libkernel", 1, "libkernel", sceKernelPwritev);
LIB_FUNCTION("VAzswvTOCzI", "libkernel", 1, "libkernel", posix_unlink);
LIB_FUNCTION("AUXVxWeJU-A", "libkernel", 1, "libkernel", sceKernelUnlink);
LIB_FUNCTION("T8fER+tIGgk", "libScePosix", 1, "libkernel", posix_select);
LIB_FUNCTION("T8fER+tIGgk", "libkernel", 1, "libkernel", posix_select);

View File

@ -23,6 +23,7 @@
#include "core/libraries/kernel/process.h"
#include "core/libraries/kernel/threads.h"
#include "core/libraries/kernel/threads/exception.h"
#include "core/libraries/kernel/threads/pthread.h"
#include "core/libraries/kernel/time.h"
#include "core/libraries/libs.h"
#include "core/libraries/network/sys_net.h"
@ -235,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;
}
@ -256,6 +266,38 @@ const char** PS4_SYSV_ABI getargv() {
return entry_params.argv;
}
s32 PS4_SYSV_ABI get_authinfo(s32 pid, AuthInfoData* p2) {
LOG_WARNING(Lib_Kernel, "(STUBBED) called, pid: {}", pid);
if (p2 == nullptr) {
*Kernel::__Error() = POSIX_EPERM;
return -1;
}
if (pid != 0 && pid != GLOBAL_PID) {
*Kernel::__Error() = POSIX_ESRCH;
return -1;
}
*p2 = {};
p2->caps[0] = 0x2000000000000000;
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};
@ -272,7 +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 {
@ -44,6 +46,37 @@ struct SwVersionStruct {
u32 hex_representation;
};
struct AuthInfoData {
u64 paid;
u64 caps[4];
u64 attrs[4];
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

@ -5,24 +5,35 @@
#include "common/alignment.h"
#include "common/assert.h"
#include "common/elf_info.h"
#include "common/logging/log.h"
#include "common/scope_exit.h"
#include "common/singleton.h"
#include "core/libraries/kernel/kernel.h"
#include "core/libraries/kernel/memory.h"
#include "core/libraries/kernel/orbis_error.h"
#include "core/libraries/kernel/process.h"
#include "core/libraries/libs.h"
#include "core/linker.h"
#include "core/memory.h"
namespace Libraries::Kernel {
static s32 g_sdk_version = -1;
static bool g_alias_dmem = false;
u64 PS4_SYSV_ABI sceKernelGetDirectMemorySize() {
LOG_TRACE(Kernel_Vmm, "called");
const auto* memory = Core::Memory::Instance();
return memory->GetTotalDirectSize();
}
s32 PS4_SYSV_ABI sceKernelEnableDmemAliasing() {
LOG_DEBUG(Kernel_Vmm, "called");
g_alias_dmem = true;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u64 len,
u64 alignment, s32 memoryType, s64* physAddrOut) {
if (searchStart < 0 || searchEnd < 0) {
@ -197,8 +208,14 @@ s32 PS4_SYSV_ABI sceKernelMapNamedDirectMemory(void** addr, u64 len, s32 prot, s
const VAddr in_addr = reinterpret_cast<VAddr>(*addr);
auto* memory = Core::Memory::Instance();
const auto ret = memory->MapMemory(addr, in_addr, len, mem_prot, map_flags,
Core::VMAType::Direct, name, false, phys_addr, alignment);
bool should_check = false;
if (g_sdk_version >= Common::ElfInfo::FW_25 && False(map_flags & Core::MemoryMapFlags::Stack)) {
// Under these conditions, this would normally redirect to sceKernelMapDirectMemory2.
should_check = !g_alias_dmem;
}
const auto ret =
memory->MapMemory(addr, in_addr, len, mem_prot, map_flags, Core::VMAType::Direct, name,
should_check, phys_addr, alignment);
LOG_INFO(Kernel_Vmm, "out_addr = {}", fmt::ptr(*addr));
return ret;
@ -244,8 +261,9 @@ s32 PS4_SYSV_ABI sceKernelMapDirectMemory2(void** addr, u64 len, s32 type, s32 p
const VAddr in_addr = reinterpret_cast<VAddr>(*addr);
auto* memory = Core::Memory::Instance();
const auto ret = memory->MapMemory(addr, in_addr, len, mem_prot, map_flags,
Core::VMAType::Direct, "anon", true, phys_addr, alignment);
const auto ret =
memory->MapMemory(addr, in_addr, len, mem_prot, map_flags, Core::VMAType::Direct, "anon",
!g_alias_dmem, phys_addr, alignment);
if (ret == 0) {
// If the map call succeeds, set the direct memory type using the output address.
@ -668,10 +686,21 @@ void* PS4_SYSV_ABI posix_mmap(void* addr, u64 len, s32 prot, s32 flags, s32 fd,
}
s32 result = ORBIS_OK;
if (fd == -1) {
if (True(mem_flags & Core::MemoryMapFlags::Anon)) {
// Maps flexible memory
result = memory->MapMemory(&addr_out, aligned_addr, aligned_size, mem_prot, mem_flags,
Core::VMAType::Flexible, "anon", false);
} else if (True(mem_flags & Core::MemoryMapFlags::Stack)) {
// Maps stack memory
result = memory->MapMemory(&addr_out, aligned_addr, aligned_size, mem_prot, mem_flags,
Core::VMAType::Stack, "anon", false);
} else if (True(mem_flags & Core::MemoryMapFlags::Void)) {
// Reserves memory
result =
memory->MapMemory(&addr_out, aligned_addr, aligned_size, Core::MemoryProt::NoAccess,
mem_flags, Core::VMAType::Reserved, "anon", false);
} else {
// Default to file mapping
result = memory->MapFile(&addr_out, aligned_addr, aligned_size, mem_prot, mem_flags, fd,
phys_addr);
}
@ -769,6 +798,12 @@ s32 PS4_SYSV_ABI sceKernelGetPrtAperture(s32 id, VAddr* address, u64* size) {
}
void RegisterMemory(Core::Loader::SymbolsResolver* sym) {
ASSERT_MSG(sceKernelGetCompiledSdkVersion(&g_sdk_version) == ORBIS_OK,
"Failed to get compiled SDK verision.");
LIB_FUNCTION("usHTMoFoBTM", "libkernel_dmem_aliasing2", 1, "libkernel",
sceKernelEnableDmemAliasing);
LIB_FUNCTION("usHTMoFoBTM", "libkernel", 1, "libkernel", sceKernelEnableDmemAliasing);
LIB_FUNCTION("rTXw65xmLIA", "libkernel", 1, "libkernel", sceKernelAllocateDirectMemory);
LIB_FUNCTION("B+vc2AO2Zrc", "libkernel", 1, "libkernel", sceKernelAllocateMainDirectMemory);
LIB_FUNCTION("C0f7TJcbfac", "libkernel", 1, "libkernel", sceKernelAvailableDirectMemorySize);

View File

@ -21,13 +21,42 @@ 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;
}
s32 PS4_SYSV_ABI sceKernelGetCpumode() {
LOG_DEBUG(Lib_Kernel, "called");
auto& attrs = Common::ElfInfo::Instance().GetPSFAttributes();
u32 is_cpu6 = attrs.six_cpu_mode.Value();
u32 is_cpu7 = attrs.seven_cpu_mode.Value();
if (is_cpu6 == 1 && is_cpu7 == 1) {
return 2;
}
if (is_cpu7 == 1) {
return 5;
}
return 0;
}
s32 PS4_SYSV_ABI sceKernelGetCurrentCpu() {
LOG_DEBUG(Lib_Kernel, "called");
return 0;
}
@ -208,7 +237,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

@ -173,6 +173,8 @@ void RegisterException(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("OMDRKKAZ8I4", "libkernel", 1, "libkernel", sceKernelDebugRaiseException);
LIB_FUNCTION("zE-wXIZjLoM", "libkernel", 1, "libkernel",
sceKernelDebugRaiseExceptionOnReleaseMode);
LIB_FUNCTION("WkwEd3N7w0Y", "libkernel", 1, "libkernel", sceKernelInstallExceptionHandler);
LIB_FUNCTION("Qhv5ARAoOEc", "libkernel", 1, "libkernel", sceKernelRemoveExceptionHandler);
}
} // namespace Libraries::Kernel

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

@ -337,7 +337,7 @@ void PS4_SYSV_ABI sched_yield() {
}
int PS4_SYSV_ABI posix_getpid() {
return g_curthread->tid;
return GLOBAL_PID;
}
int PS4_SYSV_ABI posix_pthread_once(PthreadOnce* once_control,
@ -663,6 +663,13 @@ 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("lZzFeSxPl08", "libkernel", 1, "libkernel", posix_pthread_setcancelstate);
LIB_FUNCTION("CBNtXOoef-E", "libkernel", 1, "libkernel", posix_sched_get_priority_max);
LIB_FUNCTION("m0iS6jNsXds", "libkernel", 1, "libkernel", posix_sched_get_priority_min);
LIB_FUNCTION("Xs9hdiD7sAA", "libkernel", 1, "libkernel", posix_pthread_setschedparam);
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

@ -16,6 +16,8 @@
#include "core/thread.h"
#include "core/tls.h"
#define GLOBAL_PID 0xBAD1
namespace Core::Loader {
class SymbolsResolver;
}

View File

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <ctime>
#include <thread>
#include "common/assert.h"
@ -163,7 +164,7 @@ s32 PS4_SYSV_ABI posix_clock_gettime(u32 clock_id, OrbisKernelTimespec* ts) {
case ORBIS_CLOCK_MONOTONIC_FAST: {
static LARGE_INTEGER pf = [] {
LARGE_INTEGER res{};
QueryPerformanceFrequency(&pf);
QueryPerformanceFrequency(&res);
return res;
}();
@ -485,25 +486,41 @@ Common::NativeClock* GetClock() {
s32 PS4_SYSV_ABI sceKernelConvertUtcToLocaltime(time_t time, time_t* local_time,
struct OrbisTimesec* st, u64* dst_sec) {
LOG_TRACE(Kernel, "Called");
#ifdef _WIN32
TIME_ZONE_INFORMATION tz{};
DWORD res = GetTimeZoneInformation(&tz);
*local_time = time - tz.Bias;
if (st != nullptr) {
st->t = time;
st->west_sec = -tz.Bias * 60;
st->dst_sec = res == TIME_ZONE_ID_DAYLIGHT ? -_dstbias : 0;
}
if (dst_sec != nullptr) {
*dst_sec = res == TIME_ZONE_ID_DAYLIGHT ? -_dstbias : 0;
}
#else
#ifdef __APPLE__
// std::chrono::current_zone() not available yet.
const auto* time_zone = date::current_zone();
#else
const auto* time_zone = std::chrono::current_zone();
#endif
#endif // __APPLE__
auto info = time_zone->get_info(std::chrono::system_clock::now());
*local_time = info.offset.count() + info.save.count() * 60 + time;
if (st != nullptr) {
st->t = time;
st->west_sec = info.offset.count() * 60;
st->west_sec = info.offset.count();
st->dst_sec = info.save.count() * 60;
}
if (dst_sec != nullptr) {
*dst_sec = info.save.count() * 60;
}
#endif // _WIN32
return ORBIS_OK;
}
@ -565,4 +582,4 @@ void RegisterTime(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("-o5uEDpN+oY", "libkernel", 1, "libkernel", sceKernelConvertUtcToLocaltime);
}
} // namespace Libraries::Kernel
} // namespace Libraries::Kernel

View File

@ -32,6 +32,7 @@
#include "core/libraries/network/ssl.h"
#include "core/libraries/network/ssl2.h"
#include "core/libraries/np/np_auth.h"
#include "core/libraries/np/np_commerce.h"
#include "core/libraries/np/np_common.h"
#include "core/libraries/np/np_manager.h"
#include "core/libraries/np/np_party.h"
@ -93,6 +94,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
Libraries::SysModule::RegisterLib(sym);
Libraries::Posix::RegisterLib(sym);
Libraries::AudioIn::RegisterLib(sym);
Libraries::Np::NpCommerce::RegisterLib(sym);
Libraries::Np::NpCommon::RegisterLib(sym);
Libraries::Np::NpManager::RegisterLib(sym);
Libraries::Np::NpScore::RegisterLib(sym);

View File

@ -9,6 +9,35 @@
namespace Libraries::Http {
static bool g_isHttpInitialized = true; // TODO temp always inited
void NormalizeAndAppendPath(char* dest, char* src) {
char* lastSlash;
u64 length;
lastSlash = strrchr(dest, '/');
if (lastSlash == NULL) {
length = strlen(dest);
dest[length] = '/';
dest[length + 1] = '\0';
} else {
lastSlash[1] = '\0';
}
if (*src == '/') {
dest[0] = '\0';
}
length = strnlen(dest, 0x3fff);
strncat(dest, src, 0x3fff - length);
return;
}
int HttpRequestInternal_Acquire(HttpRequestInternal** outRequest, u32 requestId) {
return 0; // TODO dummy
}
int HttpRequestInternal_Release(HttpRequestInternal* request) {
return 0; // TODO dummy
}
int PS4_SYSV_ABI sceHttpAbortRequest() {
LOG_ERROR(Lib_Http, "(STUBBED) called");
return ORBIS_OK;
@ -34,8 +63,9 @@ int PS4_SYSV_ABI sceHttpAddQuery() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpAddRequestHeader() {
LOG_ERROR(Lib_Http, "(STUBBED) called");
int PS4_SYSV_ABI sceHttpAddRequestHeader(int id, const char* name, const char* value, s32 mode) {
LOG_ERROR(Lib_Http, "(STUBBED) called id= {} name = {} value = {} mode = {}", id,
std::string(name), std::string(value), mode);
return ORBIS_OK;
}
@ -84,8 +114,9 @@ int PS4_SYSV_ABI sceHttpCreateConnection() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpCreateConnectionWithURL() {
LOG_ERROR(Lib_Http, "(STUBBED) called");
int PS4_SYSV_ABI sceHttpCreateConnectionWithURL(int tmplId, const char* url, bool enableKeepalive) {
LOG_ERROR(Lib_Http, "(STUBBED) called tmpid = {} url = {} enableKeepalive = {}", tmplId,
std::string(url), enableKeepalive ? 1 : 0);
return ORBIS_OK;
}
@ -104,8 +135,10 @@ int PS4_SYSV_ABI sceHttpCreateRequest2() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpCreateRequestWithURL() {
LOG_ERROR(Lib_Http, "(STUBBED) called");
int PS4_SYSV_ABI sceHttpCreateRequestWithURL(int connId, s32 method, const char* url,
u64 contentLength) {
LOG_ERROR(Lib_Http, "(STUBBED) called connId = {} method = {} url={} contentLength={}", connId,
method, url, contentLength);
return ORBIS_OK;
}
@ -184,7 +217,7 @@ int PS4_SYSV_ABI sceHttpGetAcceptEncodingGZIPEnabled() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpGetAllResponseHeaders() {
int PS4_SYSV_ABI sceHttpGetAllResponseHeaders(int reqId, char** header, u64* headerSize) {
LOG_ERROR(Lib_Http, "(STUBBED) called");
return ORBIS_FAIL;
}
@ -254,12 +287,42 @@ int PS4_SYSV_ABI sceHttpGetResponseContentLength() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpGetStatusCode() {
LOG_ERROR(Lib_Http, "(STUBBED) called");
int PS4_SYSV_ABI sceHttpGetStatusCode(int reqId, int* statusCode) {
LOG_ERROR(Lib_Http, "(STUBBED) called reqId = {}", reqId);
#if 0
if (!g_isHttpInitialized)
return ORBIS_HTTP_ERROR_BEFORE_INIT;
if (statusCode == nullptr)
return ORBIS_HTTP_ERROR_INVALID_VALUE;
int ret = 0;
// Lookup HttpRequestInternal by reqId
HttpRequestInternal* request = nullptr;
ret = HttpRequestInternal_Acquire(&request, reqId);
if (ret < 0)
return ret;
request->m_mutex.lock();
if (request->state > 0x11) {
if (request->state == 0x16) {
ret = request->errorCode;
} else {
*statusCode = request->httpStatusCode;
ret = 0;
}
} else {
ret = ORBIS_HTTP_ERROR_BEFORE_SEND;
}
request->m_mutex.unlock();
HttpRequestInternal_Release(request);
return ret;
#else
return ORBIS_OK;
#endif
}
int PS4_SYSV_ABI sceHttpInit(int libnetMemId, int libsslCtxId, std::size_t poolSize) {
int PS4_SYSV_ABI sceHttpInit(int libnetMemId, int libsslCtxId, u64 poolSize) {
LOG_ERROR(Lib_Http, "(DUMMY) called libnetMemId = {} libsslCtxId = {} poolSize = {}",
libnetMemId, libsslCtxId, poolSize);
// return a value >1
@ -267,14 +330,104 @@ int PS4_SYSV_ABI sceHttpInit(int libnetMemId, int libsslCtxId, std::size_t poolS
return ++id;
}
int PS4_SYSV_ABI sceHttpParseResponseHeader() {
int PS4_SYSV_ABI sceHttpParseResponseHeader(const char* header, u64 headerLen, const char* fieldStr,
const char** fieldValue, u64* valueLen) {
LOG_ERROR(Lib_Http, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpParseStatusLine() {
LOG_ERROR(Lib_Http, "(STUBBED) called");
return ORBIS_OK;
int PS4_SYSV_ABI sceHttpParseStatusLine(const char* statusLine, u64 lineLen, int32_t* httpMajorVer,
int32_t* httpMinorVer, int32_t* responseCode,
const char** reasonPhrase, u64* phraseLen) {
if (!statusLine) {
LOG_ERROR(Lib_Http, "Invalid response");
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
}
if (!httpMajorVer || !httpMinorVer || !responseCode || !reasonPhrase || !phraseLen) {
LOG_ERROR(Lib_Http, "Invalid value");
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_VALUE;
}
*httpMajorVer = 0;
*httpMinorVer = 0;
if (lineLen < 8) {
LOG_ERROR(Lib_Http, "Linelen is smaller than 8");
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
}
if (strncmp(statusLine, "HTTP/", 5) != 0) {
LOG_ERROR(Lib_Http, "statusLine doesn't start with HTTP/");
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
}
u64 index = 5;
if (!isdigit(statusLine[index])) {
LOG_ERROR(Lib_Http, "Invalid response");
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
}
while (isdigit(statusLine[index])) {
*httpMajorVer = *httpMajorVer * 10 + (statusLine[index] - '0');
index++;
}
if (statusLine[index] != '.') {
LOG_ERROR(Lib_Http, "Invalid response");
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
}
index++;
if (!isdigit(statusLine[index])) {
LOG_ERROR(Lib_Http, "Invalid response");
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
}
while (isdigit(statusLine[index])) {
*httpMinorVer = *httpMinorVer * 10 + (statusLine[index] - '0');
index++;
}
if (statusLine[index] != ' ') {
LOG_ERROR(Lib_Http, "Invalid response");
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
}
index++;
// Validate and parse the 3-digit HTTP response code
if (lineLen - index < 3 || !isdigit(statusLine[index]) || !isdigit(statusLine[index + 1]) ||
!isdigit(statusLine[index + 2])) {
LOG_ERROR(Lib_Http, "Invalid response");
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
}
*responseCode = (statusLine[index] - '0') * 100 + (statusLine[index + 1] - '0') * 10 +
(statusLine[index + 2] - '0');
index += 3;
if (statusLine[index] != ' ') {
LOG_ERROR(Lib_Http, "Invalid response");
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
}
index++;
// Set the reason phrase start position
*reasonPhrase = &statusLine[index];
u64 phraseStart = index;
while (index < lineLen && statusLine[index] != '\n') {
index++;
}
// Determine the length of the reason phrase, excluding trailing \r if present
if (index == phraseStart) {
*phraseLen = 0;
} else {
*phraseLen =
(statusLine[index - 1] == '\r') ? (index - phraseStart - 1) : (index - phraseStart);
}
// Return the number of bytes processed
return index + 1;
}
int PS4_SYSV_ABI sceHttpReadData() {
@ -317,8 +470,8 @@ int PS4_SYSV_ABI sceHttpsEnableOptionPrivate() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpSendRequest() {
LOG_ERROR(Lib_Http, "(STUBBED) called");
int PS4_SYSV_ABI sceHttpSendRequest(int reqId, const void* postData, u64 size) {
LOG_ERROR(Lib_Http, "(STUBBED) called reqId = {} size = {}", reqId, size);
return ORBIS_OK;
}
@ -548,7 +701,8 @@ int PS4_SYSV_ABI sceHttpUnsetEpoll() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpUriBuild() {
int PS4_SYSV_ABI sceHttpUriBuild(char* out, u64* require, u64 prepare,
const OrbisHttpUriElement* srcElement, u32 option) {
LOG_ERROR(Lib_Http, "(STUBBED) called");
return ORBIS_OK;
}
@ -558,18 +712,155 @@ int PS4_SYSV_ABI sceHttpUriCopy() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpUriEscape() {
LOG_ERROR(Lib_Http, "(STUBBED) called");
int PS4_SYSV_ABI sceHttpUriEscape(char* out, u64* require, u64 prepare, const char* in) {
LOG_TRACE(Lib_Http, "called");
if (!in) {
LOG_ERROR(Lib_Http, "Invalid input string");
return ORBIS_HTTP_ERROR_INVALID_VALUE;
}
auto IsUnreserved = [](unsigned char c) -> bool {
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
c == '-' || c == '_' || c == '.' || c == '~';
};
u64 needed = 0;
const char* src = in;
while (*src) {
unsigned char c = static_cast<unsigned char>(*src);
if (IsUnreserved(c)) {
needed++;
} else {
needed += 3; // %XX format
}
src++;
}
needed++; // null terminator
if (require) {
*require = needed;
}
if (!out) {
return ORBIS_OK;
}
if (prepare < needed) {
LOG_ERROR(Lib_Http, "Buffer too small: need {} but only {} available", needed, prepare);
return ORBIS_HTTP_ERROR_OUT_OF_MEMORY;
}
static const char hex_chars[] = "0123456789ABCDEF";
src = in;
char* dst = out;
while (*src) {
unsigned char c = static_cast<unsigned char>(*src);
if (IsUnreserved(c)) {
*dst++ = *src;
} else {
*dst++ = '%';
*dst++ = hex_chars[(c >> 4) & 0x0F];
*dst++ = hex_chars[c & 0x0F];
}
src++;
}
*dst = '\0';
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpUriMerge() {
LOG_ERROR(Lib_Http, "(STUBBED) called");
return ORBIS_OK;
int PS4_SYSV_ABI sceHttpUriMerge(char* mergedUrl, char* url, char* relativeUri, u64* require,
u64 prepare, u32 option) {
u64 requiredLength;
int returnValue;
u64 baseUrlLength;
u64 relativeUriLength;
u64 totalLength;
u64 combinedLength;
int parseResult;
u64 localSizeRelativeUri;
u64 localSizeBaseUrl;
OrbisHttpUriElement parsedUriElement;
if (option != 0 || url == NULL || relativeUri == NULL) {
LOG_ERROR(Lib_Http, "Invalid value");
return ORBIS_HTTP_ERROR_INVALID_VALUE;
}
returnValue = sceHttpUriParse(NULL, url, NULL, &localSizeBaseUrl, 0);
if (returnValue < 0) {
LOG_ERROR(Lib_Http, "returning {:#x}", returnValue);
return returnValue;
}
returnValue = sceHttpUriParse(NULL, relativeUri, NULL, &localSizeRelativeUri, 0);
if (returnValue < 0) {
LOG_ERROR(Lib_Http, "returning {:#x}", returnValue);
return returnValue;
}
baseUrlLength = strnlen(url, 0x3fff);
relativeUriLength = strnlen(relativeUri, 0x3fff);
requiredLength = localSizeBaseUrl + 2 + (relativeUriLength + baseUrlLength) * 2;
if (require) {
*require = requiredLength;
}
if (mergedUrl == NULL) {
return ORBIS_OK;
}
if (prepare < requiredLength) {
LOG_ERROR(Lib_Http, "Error Out of memory");
return ORBIS_HTTP_ERROR_OUT_OF_MEMORY;
}
totalLength = strnlen(url, 0x3fff);
baseUrlLength = strnlen(relativeUri, 0x3fff);
combinedLength = totalLength + 1 + baseUrlLength;
relativeUriLength = prepare - combinedLength;
returnValue =
sceHttpUriParse(&parsedUriElement, relativeUri, mergedUrl + totalLength + baseUrlLength + 1,
&localSizeRelativeUri, relativeUriLength);
if (returnValue < 0) {
LOG_ERROR(Lib_Http, "returning {:#x}", returnValue);
return returnValue;
}
if (parsedUriElement.scheme == NULL) {
strncpy(mergedUrl, relativeUri, requiredLength);
if (require) {
*require = strnlen(relativeUri, 0x3fff) + 1;
}
return ORBIS_OK;
}
returnValue =
sceHttpUriParse(&parsedUriElement, url, mergedUrl + totalLength + baseUrlLength + 1,
&localSizeBaseUrl, relativeUriLength);
if (returnValue < 0) {
LOG_ERROR(Lib_Http, "returning {:#x}", returnValue);
return returnValue;
}
combinedLength += localSizeBaseUrl;
strncpy(mergedUrl + combinedLength, parsedUriElement.path, prepare - combinedLength);
NormalizeAndAppendPath(mergedUrl + combinedLength, relativeUri);
returnValue = sceHttpUriBuild(mergedUrl, 0, ~(baseUrlLength + totalLength) + prepare,
&parsedUriElement, 0x3f);
if (returnValue >= 0) {
return ORBIS_OK;
} else {
LOG_ERROR(Lib_Http, "returning {:#x}", returnValue);
return returnValue;
}
}
int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, void* pool,
size_t* require, size_t prepare) {
u64* require, u64 prepare) {
LOG_INFO(Lib_Http, "srcUri = {}", std::string(srcUri));
if (!srcUri) {
LOG_ERROR(Lib_Http, "invalid url");
@ -586,10 +877,10 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
}
// Track the total required buffer size
size_t requiredSize = 0;
u64 requiredSize = 0;
// Parse the scheme (e.g., "http:", "https:", "file:")
size_t schemeLength = 0;
u64 schemeLength = 0;
while (srcUri[schemeLength] && srcUri[schemeLength] != ':') {
if (!isalnum(srcUri[schemeLength])) {
LOG_ERROR(Lib_Http, "invalid url");
@ -611,7 +902,7 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
requiredSize += schemeLength + 1;
// Move past the scheme and ':' character
size_t offset = schemeLength + 1;
u64 offset = schemeLength + 1;
// Check if "//" appears after the scheme
if (strncmp(srcUri + offset, "//", 2) == 0) {
@ -638,7 +929,7 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
// Parse the path (everything after the slashes)
char* pathStart = (char*)srcUri + offset;
size_t pathLength = 0;
u64 pathLength = 0;
while (pathStart[pathLength] && pathStart[pathLength] != '?' &&
pathStart[pathLength] != '#') {
pathLength++;
@ -689,7 +980,7 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
hostStart++;
}
size_t hostLength = 0;
u64 hostLength = 0;
while (hostStart[hostLength] && hostStart[hostLength] != '/' &&
hostStart[hostLength] != '?' && hostStart[hostLength] != ':') {
hostLength++;
@ -714,7 +1005,7 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
// Parse the port (if present)
if (hostStart[hostLength] == ':') {
char* portStart = hostStart + hostLength + 1;
size_t portLength = 0;
u64 portLength = 0;
while (portStart[portLength] && isdigit(portStart[portLength])) {
portLength++;
}
@ -754,7 +1045,7 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
// Parse the path (if present)
if (srcUri[offset] == '/') {
char* pathStart = (char*)srcUri + offset;
size_t pathLength = 0;
u64 pathLength = 0;
while (pathStart[pathLength] && pathStart[pathLength] != '?' &&
pathStart[pathLength] != '#') {
pathLength++;
@ -780,7 +1071,7 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
// Parse the query (if present)
if (srcUri[offset] == '?') {
char* queryStart = (char*)srcUri + offset + 1;
size_t queryLength = 0;
u64 queryLength = 0;
while (queryStart[queryLength] && queryStart[queryLength] != '#') {
queryLength++;
}
@ -805,7 +1096,7 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
// Parse the fragment (if present)
if (srcUri[offset] == '#') {
char* fragmentStart = (char*)srcUri + offset + 1;
size_t fragmentLength = 0;
u64 fragmentLength = 0;
while (fragmentStart[fragmentLength]) {
fragmentLength++;
}
@ -833,13 +1124,164 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpUriSweepPath(char* dst, const char* src, size_t srcSize) {
LOG_ERROR(Lib_Http, "(STUBBED) called");
int PS4_SYSV_ABI sceHttpUriSweepPath(char* dst, const char* src, u64 srcSize) {
LOG_TRACE(Lib_Http, "called");
if (!dst || !src) {
LOG_ERROR(Lib_Http, "Invalid parameters");
return ORBIS_HTTP_ERROR_INVALID_VALUE;
}
if (srcSize == 0) {
dst[0] = '\0';
return ORBIS_OK;
}
u64 len = 0;
while (len < srcSize && src[len] != '\0') {
len++;
}
for (u64 i = 0; i < len; i++) {
dst[i] = src[i];
}
dst[len] = '\0';
char* read = dst;
char* write = dst;
while (*read) {
if (read[0] == '.' && read[1] == '.' && read[2] == '/') {
read += 3;
continue;
}
if (read[0] == '.' && read[1] == '/') {
read += 2;
continue;
}
if (read[0] == '/' && read[1] == '.' && read[2] == '/') {
read += 2;
continue;
}
if (read[0] == '/' && read[1] == '.' && read[2] == '\0') {
if (write == dst) {
*write++ = '/';
}
break;
}
bool is_dotdot_mid = (read[0] == '/' && read[1] == '.' && read[2] == '.' && read[3] == '/');
bool is_dotdot_end =
(read[0] == '/' && read[1] == '.' && read[2] == '.' && read[3] == '\0');
if (is_dotdot_mid || is_dotdot_end) {
if (write > dst) {
if (*(write - 1) == '/') {
write--;
}
while (write > dst && *(write - 1) != '/') {
write--;
}
if (is_dotdot_mid && write > dst) {
write--;
}
}
if (is_dotdot_mid) {
read += 3;
} else {
break;
}
continue;
}
if ((read[0] == '.' && read[1] == '\0') ||
(read[0] == '.' && read[1] == '.' && read[2] == '\0')) {
break;
}
if (read[0] == '/') {
*write++ = *read++;
}
while (*read && *read != '/') {
*write++ = *read++;
}
}
*write = '\0';
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpUriUnescape(char* out, size_t* require, size_t prepare, const char* in) {
LOG_ERROR(Lib_Http, "(STUBBED) called");
int PS4_SYSV_ABI sceHttpUriUnescape(char* out, u64* require, u64 prepare, const char* in) {
LOG_TRACE(Lib_Http, "called");
if (!in) {
LOG_ERROR(Lib_Http, "Invalid input string");
return ORBIS_HTTP_ERROR_INVALID_VALUE;
}
// Locale-independent hex digit check
auto IsHex = [](char c) -> bool {
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
};
// Convert hex char to int value
auto HexToInt = [](char c) -> int {
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
return 0;
};
// Check for valid percent-encoded sequence (%XX)
auto IsValidPercentSequence = [&](const char* s) -> bool {
return s[0] == '%' && s[1] != '\0' && s[2] != '\0' && IsHex(s[1]) && IsHex(s[2]);
};
u64 needed = 0;
const char* src = in;
while (*src) {
if (IsValidPercentSequence(src)) {
src += 3;
} else {
src++;
}
needed++;
}
needed++; // null terminator
if (require) {
*require = needed;
}
if (!out) {
return ORBIS_OK;
}
if (prepare < needed) {
LOG_ERROR(Lib_Http, "Buffer too small: need {} but only {} available", needed, prepare);
return ORBIS_HTTP_ERROR_OUT_OF_MEMORY;
}
src = in;
char* dst = out;
while (*src) {
if (IsValidPercentSequence(src)) {
*dst++ = static_cast<char>((HexToInt(src[1]) << 4) | HexToInt(src[2]));
src += 3;
} else {
*dst++ = *src++;
}
}
*dst = '\0';
return ORBIS_OK;
}

View File

@ -3,6 +3,7 @@
#pragma once
#include <mutex>
#include "common/types.h"
#include "core/libraries/network/ssl.h"
@ -25,6 +26,12 @@ struct OrbisHttpUriElement {
u8 reserved[10];
};
struct HttpRequestInternal {
int state; // +0x20
int errorCode; // +0x28
int httpStatusCode; // +0x20C
std::mutex m_mutex;
};
using OrbisHttpsCaList = Libraries::Ssl::OrbisSslCaList;
int PS4_SYSV_ABI sceHttpAbortRequest();
@ -32,7 +39,7 @@ int PS4_SYSV_ABI sceHttpAbortRequestForce();
int PS4_SYSV_ABI sceHttpAbortWaitRequest();
int PS4_SYSV_ABI sceHttpAddCookie();
int PS4_SYSV_ABI sceHttpAddQuery();
int PS4_SYSV_ABI sceHttpAddRequestHeader();
int PS4_SYSV_ABI sceHttpAddRequestHeader(int id, const char* name, const char* value, s32 mode);
int PS4_SYSV_ABI sceHttpAddRequestHeaderRaw();
int PS4_SYSV_ABI sceHttpAuthCacheExport();
int PS4_SYSV_ABI sceHttpAuthCacheFlush();
@ -42,11 +49,12 @@ int PS4_SYSV_ABI sceHttpCookieExport();
int PS4_SYSV_ABI sceHttpCookieFlush();
int PS4_SYSV_ABI sceHttpCookieImport();
int PS4_SYSV_ABI sceHttpCreateConnection();
int PS4_SYSV_ABI sceHttpCreateConnectionWithURL();
int PS4_SYSV_ABI sceHttpCreateConnectionWithURL(int tmplId, const char* url, bool enableKeepalive);
int PS4_SYSV_ABI sceHttpCreateEpoll();
int PS4_SYSV_ABI sceHttpCreateRequest();
int PS4_SYSV_ABI sceHttpCreateRequest2();
int PS4_SYSV_ABI sceHttpCreateRequestWithURL();
int PS4_SYSV_ABI sceHttpCreateRequestWithURL(int connId, s32 method, const char* url,
u64 contentLength);
int PS4_SYSV_ABI sceHttpCreateRequestWithURL2();
int PS4_SYSV_ABI sceHttpCreateTemplate();
int PS4_SYSV_ABI sceHttpDbgEnableProfile();
@ -62,7 +70,7 @@ int PS4_SYSV_ABI sceHttpDeleteRequest();
int PS4_SYSV_ABI sceHttpDeleteTemplate();
int PS4_SYSV_ABI sceHttpDestroyEpoll();
int PS4_SYSV_ABI sceHttpGetAcceptEncodingGZIPEnabled();
int PS4_SYSV_ABI sceHttpGetAllResponseHeaders();
int PS4_SYSV_ABI sceHttpGetAllResponseHeaders(int reqId, char** header, u64* headerSize);
int PS4_SYSV_ABI sceHttpGetAuthEnabled();
int PS4_SYSV_ABI sceHttpGetAutoRedirect();
int PS4_SYSV_ABI sceHttpGetConnectionStat();
@ -76,10 +84,13 @@ int PS4_SYSV_ABI sceHttpGetMemoryPoolStats();
int PS4_SYSV_ABI sceHttpGetNonblock();
int PS4_SYSV_ABI sceHttpGetRegisteredCtxIds();
int PS4_SYSV_ABI sceHttpGetResponseContentLength();
int PS4_SYSV_ABI sceHttpGetStatusCode();
int PS4_SYSV_ABI sceHttpInit(int libnetMemId, int libsslCtxId, std::size_t poolSize);
int PS4_SYSV_ABI sceHttpParseResponseHeader();
int PS4_SYSV_ABI sceHttpParseStatusLine();
int PS4_SYSV_ABI sceHttpGetStatusCode(int reqId, int* statusCode);
int PS4_SYSV_ABI sceHttpInit(int libnetMemId, int libsslCtxId, u64 poolSize);
int PS4_SYSV_ABI sceHttpParseResponseHeader(const char* header, u64 headerLen, const char* fieldStr,
const char** fieldValue, u64* valueLen);
int PS4_SYSV_ABI sceHttpParseStatusLine(const char* statusLine, u64 lineLen, int32_t* httpMajorVer,
int32_t* httpMinorVer, int32_t* responseCode,
const char** reasonPhrase, u64* phraseLen);
int PS4_SYSV_ABI sceHttpReadData();
int PS4_SYSV_ABI sceHttpRedirectCacheFlush();
int PS4_SYSV_ABI sceHttpRemoveRequestHeader();
@ -88,7 +99,7 @@ int PS4_SYSV_ABI sceHttpsDisableOption();
int PS4_SYSV_ABI sceHttpsDisableOptionPrivate();
int PS4_SYSV_ABI sceHttpsEnableOption();
int PS4_SYSV_ABI sceHttpsEnableOptionPrivate();
int PS4_SYSV_ABI sceHttpSendRequest();
int PS4_SYSV_ABI sceHttpSendRequest(int reqId, const void* postData, u64 size);
int PS4_SYSV_ABI sceHttpSetAcceptEncodingGZIPEnabled();
int PS4_SYSV_ABI sceHttpSetAuthEnabled();
int PS4_SYSV_ABI sceHttpSetAuthInfoCallback();
@ -134,14 +145,16 @@ int PS4_SYSV_ABI sceHttpTerm();
int PS4_SYSV_ABI sceHttpTryGetNonblock();
int PS4_SYSV_ABI sceHttpTrySetNonblock();
int PS4_SYSV_ABI sceHttpUnsetEpoll();
int PS4_SYSV_ABI sceHttpUriBuild();
int PS4_SYSV_ABI sceHttpUriBuild(char* out, u64* require, u64 prepare,
const OrbisHttpUriElement* srcElement, u32 option);
int PS4_SYSV_ABI sceHttpUriCopy();
int PS4_SYSV_ABI sceHttpUriEscape();
int PS4_SYSV_ABI sceHttpUriMerge();
int PS4_SYSV_ABI sceHttpUriEscape(char* out, u64* require, u64 prepare, const char* in);
int PS4_SYSV_ABI sceHttpUriMerge(char* mergedUrl, char* url, char* relativeUri, u64* require,
u64 prepare, u32 option);
int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, void* pool,
size_t* require, size_t prepare);
int PS4_SYSV_ABI sceHttpUriSweepPath(char* dst, const char* src, size_t srcSize);
int PS4_SYSV_ABI sceHttpUriUnescape(char* out, size_t* require, size_t prepare, const char* in);
u64* require, u64 prepare);
int PS4_SYSV_ABI sceHttpUriSweepPath(char* dst, const char* src, u64 srcSize);
int PS4_SYSV_ABI sceHttpUriUnescape(char* out, u64* require, u64 prepare, const char* in);
int PS4_SYSV_ABI sceHttpWaitRequest();
void RegisterLib(Core::Loader::SymbolsResolver* sym);

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() {
@ -1356,7 +1357,8 @@ int PS4_SYSV_ABI sceNetResolverConnectDestroy() {
}
int PS4_SYSV_ABI sceNetResolverCreate(const char* name, int poolid, int flags) {
LOG_INFO(Lib_Net, "name = {}, poolid = {}, flags = {}", name, poolid, flags);
const char* safe_name = name ? name : "";
LOG_INFO(Lib_Net, "name = {}, poolid = {}, flags = {}", safe_name, poolid, flags);
if (flags != 0) {
*sceNetErrnoLoc() = ORBIS_NET_EINVAL;
@ -1367,8 +1369,8 @@ int PS4_SYSV_ABI sceNetResolverCreate(const char* name, int poolid, int flags) {
auto* resolver = FDTable::Instance()->GetFile(fd);
resolver->is_opened = true;
resolver->type = Core::FileSys::FileType::Resolver;
resolver->resolver = std::make_shared<Resolver>(name, poolid, flags);
resolver->m_guest_name = name;
resolver->resolver = std::make_shared<Resolver>(safe_name, poolid, flags);
resolver->m_guest_name = safe_name;
return fd;
}

View File

@ -0,0 +1,129 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/system/commondialog.h"
namespace Libraries::Np::NpCommerce {
using CommonDialog::Error;
using CommonDialog::Result;
using CommonDialog::Status;
static Status g_dialog_status = Status::NONE;
static Result g_dialog_result = Result::OK;
s32 PS4_SYSV_ABI sceNpCommerceDialogClose() {
LOG_INFO(Lib_NpCommerce, "called");
if (g_dialog_status == Status::NONE) {
return static_cast<s32>(Error::NOT_INITIALIZED);
}
if (g_dialog_status != Status::FINISHED) {
return static_cast<s32>(Error::NOT_FINISHED);
}
g_dialog_status = Status::INITIALIZED;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpCommerceDialogGetResult(s32* result) {
LOG_INFO(Lib_NpCommerce, "called");
if (result == nullptr) {
return static_cast<s32>(Error::ARG_NULL);
}
if (g_dialog_status != Status::FINISHED) {
return static_cast<s32>(Error::NOT_FINISHED);
}
*result = static_cast<s32>(g_dialog_result);
return ORBIS_OK;
}
s8 PS4_SYSV_ABI sceNpCommerceDialogGetStatus() {
LOG_DEBUG(Lib_NpCommerce, "called, status = {}", static_cast<u32>(g_dialog_status));
return static_cast<s8>(g_dialog_status);
}
s32 PS4_SYSV_ABI sceNpCommerceDialogInitialize() {
LOG_INFO(Lib_NpCommerce, "called");
if (g_dialog_status != Status::NONE) {
return static_cast<s32>(Error::ALREADY_INITIALIZED);
}
g_dialog_status = Status::INITIALIZED;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpCommerceDialogInitializeInternal() {
LOG_INFO(Lib_NpCommerce, "called");
return sceNpCommerceDialogInitialize();
}
s16 PS4_SYSV_ABI sceNpCommerceDialogOpen(s64 check) {
LOG_INFO(Lib_NpCommerce, "called, check = {}", check);
if (g_dialog_status != Status::INITIALIZED) {
LOG_WARNING(Lib_NpCommerce, "Dialog not initialized");
return ORBIS_OK;
}
g_dialog_status = Status::FINISHED;
g_dialog_result = Result::USER_CANCELED;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpCommerceDialogTerminate() {
LOG_INFO(Lib_NpCommerce, "called");
if (g_dialog_status == Status::NONE) {
return static_cast<s32>(Error::NOT_INITIALIZED);
}
if (g_dialog_status == Status::RUNNING) {
return static_cast<s32>(Error::NOT_FINISHED);
}
g_dialog_status = Status::NONE;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpCommerceDialogUpdateStatus() {
LOG_DEBUG(Lib_NpCommerce, "called, status = {}", static_cast<u32>(g_dialog_status));
return static_cast<s32>(g_dialog_status);
}
s32 PS4_SYSV_ABI sceNpCommerceHidePsStoreIcon() {
LOG_ERROR(Lib_NpCommerce, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpCommerceSetPsStoreIconLayout(s32 layout) {
LOG_ERROR(Lib_NpCommerce, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpCommerceShowPsStoreIcon(s16 icon) {
LOG_ERROR(Lib_NpCommerce, "(STUBBED) called");
return ORBIS_OK;
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("NU3ckGHMFXo", "libSceNpCommerce", 1, "libSceNpCommerce",
sceNpCommerceDialogClose);
LIB_FUNCTION("r42bWcQbtZY", "libSceNpCommerce", 1, "libSceNpCommerce",
sceNpCommerceDialogGetResult);
LIB_FUNCTION("CCbC+lqqvF0", "libSceNpCommerce", 1, "libSceNpCommerce",
sceNpCommerceDialogGetStatus);
LIB_FUNCTION("0aR2aWmQal4", "libSceNpCommerce", 1, "libSceNpCommerce",
sceNpCommerceDialogInitialize);
LIB_FUNCTION("9ZiLXAGG5rg", "libSceNpCommerce", 1, "libSceNpCommerce",
sceNpCommerceDialogInitializeInternal);
LIB_FUNCTION("DfSCDRA3EjY", "libSceNpCommerce", 1, "libSceNpCommerce", sceNpCommerceDialogOpen);
LIB_FUNCTION("m-I92Ab50W8", "libSceNpCommerce", 1, "libSceNpCommerce",
sceNpCommerceDialogTerminate);
LIB_FUNCTION("LR5cwFMMCVE", "libSceNpCommerce", 1, "libSceNpCommerce",
sceNpCommerceDialogUpdateStatus);
LIB_FUNCTION("dsqCVsNM0Zg", "libSceNpCommerce", 1, "libSceNpCommerce",
sceNpCommerceHidePsStoreIcon);
LIB_FUNCTION("uKTDW8hk-ts", "libSceNpCommerce", 1, "libSceNpCommerce",
sceNpCommerceSetPsStoreIconLayout);
LIB_FUNCTION("DHmwsa6S8Tc", "libSceNpCommerce", 1, "libSceNpCommerce",
sceNpCommerceShowPsStoreIcon);
};
} // namespace Libraries::Np::NpCommerce

View File

@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Np::NpCommerce {
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpCommerce

View File

@ -5,7 +5,6 @@
#include <filesystem>
#include <fstream>
#include <mutex>
#include <SDL3/SDL_audio.h>
#include <cmrc/cmrc.hpp>
#include <imgui.h>
@ -92,59 +91,45 @@ TrophyUI::TrophyUI(const std::filesystem::path& trophyIconPath, const std::strin
AddLayer(this);
bool customsoundplayed = false;
#ifdef ENABLE_QT_GUI
QString musicPathWav = QString::fromStdString(CustomTrophy_Dir.string() + "/trophy.wav");
QString musicPathMp3 = QString::fromStdString(CustomTrophy_Dir.string() + "/trophy.mp3");
if (fs::exists(musicPathWav.toStdString())) {
BackgroundMusicPlayer::getInstance().setVolume(100);
BackgroundMusicPlayer::getInstance().playMusic(musicPathWav, false);
customsoundplayed = true;
} else if (fs::exists(musicPathMp3.toStdString())) {
BackgroundMusicPlayer::getInstance().setVolume(100);
BackgroundMusicPlayer::getInstance().playMusic(musicPathMp3, false);
customsoundplayed = true;
MIX_Init();
mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, NULL);
if (!mixer) {
LOG_ERROR(Lib_NpTrophy, "Could not initialize SDL Mixer, {}", SDL_GetError());
return;
}
#endif
if (!customsoundplayed) {
MIX_SetMasterGain(mixer, static_cast<float>(Config::getVolumeSlider() / 100.f));
auto musicPathMp3 = CustomTrophy_Dir / "trophy.mp3";
auto musicPathWav = CustomTrophy_Dir / "trophy.wav";
if (std::filesystem::exists(musicPathMp3)) {
audio = MIX_LoadAudio(mixer, musicPathMp3.string().c_str(), false);
} else if (std::filesystem::exists(musicPathWav)) {
audio = MIX_LoadAudio(mixer, musicPathWav.string().c_str(), false);
} else {
auto soundFile = resource.open("src/images/trophy.wav");
std::vector<u8> soundData = std::vector<u8>(soundFile.begin(), soundFile.end());
audio =
MIX_LoadAudio_IO(mixer, SDL_IOFromMem(soundData.data(), soundData.size()), false, true);
// due to low volume of default sound file
MIX_SetMasterGain(mixer, MIX_GetMasterGain(mixer) * 1.3f);
}
SDL_AudioSpec spec;
Uint8* audioBuf;
Uint32 audioLen;
if (!audio) {
LOG_ERROR(Lib_NpTrophy, "Could not loud audio file, {}", SDL_GetError());
return;
}
if (!SDL_LoadWAV_IO(SDL_IOFromMem(soundData.data(), soundData.size()), true, &spec,
&audioBuf, &audioLen)) {
LOG_ERROR(Lib_NpTrophy, "Cannot load trophy sound: {}", SDL_GetError());
SDL_free(audioBuf);
return;
}
SDL_AudioStream* stream =
SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, nullptr, nullptr);
if (!stream) {
LOG_ERROR(Lib_NpTrophy, "Cannot create audio stream for trophy sound: {}",
SDL_GetError());
SDL_free(audioBuf);
return;
}
if (!SDL_PutAudioStreamData(stream, audioBuf, audioLen)) {
LOG_ERROR(Lib_NpTrophy, "Cannot add trophy sound data to stream: {}", SDL_GetError());
SDL_free(audioBuf);
return;
}
// Set audio gain 20% higher since audio file itself is soft
SDL_SetAudioStreamGain(stream, Config::getVolumeSlider() / 100.0f * 1.2f);
SDL_ResumeAudioStreamDevice(stream);
SDL_free(audioBuf);
if (!MIX_PlayAudio(mixer, audio)) {
LOG_ERROR(Lib_NpTrophy, "Could not play audio file, {}", SDL_GetError());
}
}
TrophyUI::~TrophyUI() {
MIX_DestroyAudio(audio);
MIX_DestroyMixer(mixer);
MIX_Quit();
Finish();
}

View File

@ -5,6 +5,7 @@
#include <string>
#include <variant>
#include <SDL3_mixer/SDL_mixer.h>
#include <queue>
#include "common/fixed_value.h"
@ -30,6 +31,9 @@ private:
std::string_view trophy_type;
ImGui::RefCountedTexture trophy_icon;
ImGui::RefCountedTexture trophy_type_icon;
MIX_Mixer* mixer;
MIX_Audio* audio;
};
struct TrophyInfo {

View File

@ -180,7 +180,7 @@ void SaveInstance::SetupAndMount(bool read_only, bool copy_icon, bool ignore_cor
}
if (!ignore_corrupt && !read_only) {
Common::FS::IOFile f(corrupt_file_path, Common::FS::FileAccessMode::Write);
Common::FS::IOFile f(corrupt_file_path, Common::FS::FileAccessMode::Create);
f.Close();
}

View File

@ -59,7 +59,7 @@ void PersistMemory(u32 slot_id, bool lock) {
while (n++ < 10) {
try {
IOFile f;
int r = f.Open(memoryPath, Common::FS::FileAccessMode::Write);
int r = f.Open(memoryPath, Common::FS::FileAccessMode::Create);
if (f.IsOpen()) {
f.WriteRaw<u8>(data.memory_cache.data(), data.memory_cache.size());
f.Close();
@ -148,7 +148,7 @@ void SetIcon(u32 slot_id, void* buf, size_t buf_size) {
fs::copy_file(src_icon, icon_path);
}
} else {
IOFile file(icon_path, Common::FS::FileAccessMode::Write);
IOFile file(icon_path, Common::FS::FileAccessMode::Create);
file.WriteRaw<u8>(buf, buf_size);
file.Close();
}

View File

@ -1389,7 +1389,7 @@ Error PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint
}
try {
const Common::FS::IOFile file(path, Common::FS::FileAccessMode::Write);
const Common::FS::IOFile file(path, Common::FS::FileAccessMode::Create);
file.WriteRaw<u8>(icon->buf, std::min(icon->bufSize, icon->dataSize));
} catch (const fs::filesystem_error& e) {
LOG_ERROR(Lib_SaveData, "Failed to load icon: {}", e.what());

View File

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstdlib>
#include "common/config.h"
#include "common/logging/log.h"
#include "common/singleton.h"
@ -1874,6 +1875,10 @@ int PS4_SYSV_ABI sceSystemServiceLoadExec(const char* path, const char* argv[])
auto emu = Common::Singleton<Core::Emulator>::Instance();
auto mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
auto hostPath = mnt->GetHostPath(std::string_view(path));
if (hostPath.empty()) {
LOG_INFO(Lib_SystemService, "Restart called with invalid file '{}', exiting.", path);
std::quick_exit(0);
}
std::vector<std::string> args;
if (argv != nullptr) {
for (const char** ptr = argv; *ptr != nullptr; ptr++) {

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

@ -0,0 +1,503 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "skylander.h"
#include <mutex>
namespace Libraries::Usbd {
SkylanderPortal::SkylanderPortal() {}
void SkylanderPortal::LoadFigure(std::string file_name, u8 pad, u8 slot) {
Common::FS::IOFile file(file_name, Common::FS::FileAccessMode::ReadWrite);
std::array<u8, 0x40 * 0x10> data;
ASSERT(file.Read(data) == data.size());
ui_skylanders[slot] = LoadSkylander(data.data(), std::move(file));
}
void SkylanderPortal::RemoveFigure(u8 pad, u8 slot, bool full_remove) {
std::lock_guard lock(sky_mutex);
auto& thesky = skylanders[ui_skylanders[slot]];
if (thesky.status & 1) {
thesky.status = 2;
thesky.queued_status.push(Skylander::REMOVING);
thesky.queued_status.push(Skylander::REMOVED);
thesky.Save();
thesky.skyFile.Close();
}
}
u8 SkylanderPortal::LoadSkylander(u8* buf, Common::FS::IOFile file) {
std::lock_guard lock(sky_mutex);
u32 skySerial = 0;
for (int i = 3; i > -1; i--) {
skySerial <<= 8;
skySerial |= buf[i];
}
u8 foundSlot = 0xFF;
// mimics spot retaining on the portal
for (auto i = 0; i < MAX_SKYLANDERS; i++) {
if ((skylanders[i].status & 1) == 0) {
if (skylanders[i].last_id == skySerial) {
foundSlot = i;
break;
}
if (i < foundSlot) {
foundSlot = i;
}
}
}
if (foundSlot != 0xFF) {
auto& skylander = skylanders[foundSlot];
memcpy(skylander.data.data(), buf, skylander.data.size());
skylander.skyFile = std::move(file);
skylander.status = Skylander::ADDED;
skylander.queued_status.push(Skylander::ADDED);
skylander.queued_status.push(Skylander::READY);
skylander.last_id = skySerial;
}
return foundSlot;
}
void SkylanderPortal::Activate() {
const std::lock_guard lock(sky_mutex);
if (m_activated) {
// If the portal was already active no change is needed
return;
}
// If not we need to advertise change to all the figures present on the portal
for (auto& s : skylanders) {
if (s.status & 1) {
s.queued_status.push(Skylander::ADDED);
s.queued_status.push(Skylander::READY);
}
}
m_activated = true;
}
void SkylanderPortal::Deactivate() {
const std::lock_guard lock(sky_mutex);
for (auto& s : skylanders) {
// check if at the end of the updates there would be a figure on the portal
if (!s.queued_status.empty()) {
s.status = s.queued_status.back();
s.queued_status = std::queue<u8>();
}
s.status &= 1;
}
m_activated = false;
}
// Side:
// 0x00 = right
// 0x01 = left and right
// 0x02 = left
// 0x03 = trap
void SkylanderPortal::SetLEDs(u8 side, u8 red, u8 green, u8 blue) {
const std::lock_guard lock(sky_mutex);
if (side == 0x00) {
m_color_right.red = red;
m_color_right.green = green;
m_color_right.blue = blue;
} else if (side == 0x01) {
m_color_right.red = red;
m_color_right.green = green;
m_color_right.blue = blue;
m_color_left.red = red;
m_color_left.green = green;
m_color_left.blue = blue;
} else if (side == 0x02) {
m_color_left.red = red;
m_color_left.green = green;
m_color_left.blue = blue;
} else if (side == 0x03) {
m_color_trap.red = red;
m_color_trap.green = green;
m_color_trap.blue = blue;
}
}
std::array<u8, 64> SkylanderPortal::GetStatus() {
const std::lock_guard lock(sky_mutex);
u32 status = 0;
u8 active = 0x00;
if (m_activated) {
active = 0x01;
}
for (int i = MAX_SKYLANDERS - 1; i >= 0; i--) {
auto& s = skylanders[i];
if (!s.queued_status.empty()) {
s.status = s.queued_status.front();
s.queued_status.pop();
}
status <<= 2;
status |= s.status;
}
std::array<u8, 64> interrupt_response = {0x53, 0x00, 0x00, 0x00, 0x00, m_interrupt_counter++,
active, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00};
memcpy(&interrupt_response[1], &status, sizeof(status));
return interrupt_response;
}
void SkylanderPortal::QueryBlock(u8 sky_num, u8 block, u8* reply_buf) {
if (!IsSkylanderNumberValid(sky_num) || !IsBlockNumberValid(block))
return;
std::lock_guard lock(sky_mutex);
const auto& skylander = skylanders[sky_num];
reply_buf[0] = 'Q';
reply_buf[2] = block;
if (skylander.status & Skylander::READY) {
reply_buf[1] = (0x10 | sky_num);
memcpy(reply_buf + 3, skylander.data.data() + (16 * block), 16);
} else {
reply_buf[1] = 0x01;
}
}
void SkylanderPortal::WriteBlock(u8 sky_num, u8 block, const u8* to_write_buf, u8* reply_buf) {
if (!IsSkylanderNumberValid(sky_num) || !IsBlockNumberValid(block))
return;
std::lock_guard lock(sky_mutex);
auto& skylander = skylanders[sky_num];
reply_buf[0] = 'W';
reply_buf[2] = block;
if (skylander.status & 1) {
reply_buf[1] = (0x10 | sky_num);
memcpy(skylander.data.data() + (block * 16), to_write_buf, 16);
skylander.Save();
} else {
reply_buf[1] = 0x01;
}
}
bool SkylanderPortal::IsSkylanderNumberValid(u8 sky_num) {
return sky_num < MAX_SKYLANDERS;
}
bool SkylanderPortal::IsBlockNumberValid(u8 block) {
return block < 64;
}
libusb_endpoint_descriptor* SkylanderBackend::FillEndpointDescriptorPair() {
return m_endpoint_descriptors.data();
}
libusb_interface_descriptor* SkylanderBackend::FillInterfaceDescriptor(
libusb_endpoint_descriptor* descs) {
m_interface_descriptors[0].endpoint = descs;
return m_interface_descriptors.data();
}
libusb_config_descriptor* SkylanderBackend::FillConfigDescriptor(libusb_interface* inter) {
m_config_descriptors[0].interface = inter;
return m_config_descriptors.data();
}
libusb_device_descriptor* SkylanderBackend::FillDeviceDescriptor() {
return m_device_descriptors.data();
}
s32 SkylanderBackend::ControlTransfer(libusb_device_handle* dev_handle, u8 bmRequestType,
u8 bRequest, u16 wValue, u16 wIndex, u8* data, u16 wLength,
u32 timeout) {
if (bmRequestType != 0x21) {
return LIBUSB_ERROR_PIPE;
}
if (bRequest != 0x09) {
return 8;
}
// Data to be sent back via the control transfer immediately
std::array<u8, 64> control_response = {};
s32 expected_count = 0;
// Data to be queued to be sent back via the Interrupt Transfer (if needed)
std::array<u8, 64> interrupt_response = {};
switch (data[0]) {
case 'A': {
// Activation
// Command { 'A', (00 | 01), 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
// 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 }
// Response { 'A', (00 | 01),
// ff, 77, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
// 00, 00, 00, 00, 00, 00, 00, 00 }
// The 2nd byte of the command is whether to activate (0x01) or deactivate (0x00) the
// portal. The response echos back the activation byte as the 2nd byte of the response. The
// 3rd and 4th bytes of the response appear to vary from wired to wireless. On wired
// portals, the bytes appear to always be ff 77. On wireless portals, during activation the
// 3rd byte appears to count down from ff (possibly a battery power indication) and during
// deactivation ed and eb responses have been observed. The 4th byte appears to always be 00
// for wireless portals.
// Wii U Wireless: 41 01 f4 00 41 00 ed 00 41 01 f4 00 41 00 eb 00 41 01 f3 00 41 00 ed 00
if (wLength == 2) {
control_response = {data[0], data[1]};
interrupt_response = {0x41, data[1], 0xFF, 0x77, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
m_queries.push(interrupt_response);
expected_count = 10;
m_skylander_portal->Activate();
}
break;
}
case 'C': {
// Color
// Command { 'C', 12, 34, 56, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
// 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 }
// Response { 'C', 12, 34, 56, 00, 00,
// 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
// 00, 00, 00, 00 }
// The 3 bytes {12, 34, 56} are RGB values.
// This command should set the color of the LED in the portal, however this appears
// deprecated in most of the recent portals. On portals that do not have LEDs, this command
// is silently ignored and do not require a response.
if (wLength == 4) {
m_skylander_portal->SetLEDs(0x01, data[1], data[2], data[3]);
control_response = {0x43, data[1], data[2], data[3]};
expected_count = 12;
}
break;
}
case 'J': {
// Sided color
// The 2nd byte is the side
// 0x00: right
// 0x02: left
// The 3rd, 4th and 5th bytes are red, green and blue
// The 6th and 7th bytes form a little-endian short for how long the fade duration should be
// in milliseconds.
// For example, 500 milliseconds becomes 0xF4, 0x01
if (wLength == 7) {
control_response = {data[0], data[1], data[2], data[3], data[4], data[5], data[6]};
expected_count = 15;
interrupt_response = {data[0]};
m_queries.push(interrupt_response);
m_skylander_portal->SetLEDs(data[1], data[2], data[3], data[4]);
}
break;
}
case 'L': {
// Light
// This command is used while playing audio through the portal
// The 2nd bytes is the position
// 0x00: right
// 0x01: trap led
// 0x02: left
// The 3rd, 4th and 5th bytes are red, green and blue
// the trap led is white-only
// increasing or decreasing the values results in a brighter or dimmer light
if (wLength == 5) {
control_response = {data[0], data[1], data[2], data[3], data[4]};
expected_count = 13;
u8 side = data[1];
if (side == 0x02) {
side = 0x04;
}
m_skylander_portal->SetLEDs(side, data[2], data[3], data[4]);
}
break;
}
case 'M': {
// Audio Firmware version
// Respond with version obtained from Trap Team wired portal
if (wLength == 2) {
control_response = {data[0], data[1]};
expected_count = 10;
interrupt_response = {data[0], data[1], 0x00, 0x19};
m_queries.push(interrupt_response);
}
break;
}
// Query
// Command { 'Q', 10, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
// 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 }
// Response { 'Q', 10, 00, 00, 00, 00,
// 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
// 00, 00, 00, 00 }
// In the command the 2nd byte indicates which Skylander to query data
// from. Index starts at 0x10 for the 1st Skylander (as reported in the Status command.) The
// 16th Skylander indexed would be 0x20. The 3rd byte indicate which block to read from.
// A response with the 2nd byte of 0x01 indicates an error in the read. Otherwise, the
// response indicates the Skylander's index in the 2nd byte, the block read in the 3rd byte,
// data (16 bytes) is contained in bytes 4-19.
// A Skylander has 64 blocks of data indexed from 0x00 to 0x3f. SwapForce characters have 2
// character indexes, these may not be sequential.
case 'Q': {
if (wLength == 3) {
const u8 sky_num = data[1] & 0xF;
const u8 block = data[2];
m_skylander_portal->QueryBlock(sky_num, block, interrupt_response.data());
m_queries.push(interrupt_response);
control_response = {data[0], data[1], data[2]};
expected_count = 11;
}
break;
}
case 'R': {
// Ready
// Command { 'R', 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
// 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 }
// Response { 'R', 02, 0a, 03, 02, 00,
// 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
// 00, 00, 00, 00 }
// The 4 byte sequence after the R (0x52) is unknown, but appears consistent based on device
// type.
if (wLength == 2) {
control_response = {0x52, 0x00};
interrupt_response = {0x52, 0x02, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
m_queries.push(interrupt_response);
expected_count = 10;
}
break;
}
// Status
// Command { 'S', 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
// 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 }
// Response { 'S', 55, 00, 00, 55, 3e,
// (00|01), 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
// 00, 00, 00, 00, 00 }
// Status is the default command. If you open the HID device and
// activate the portal, you will get status outputs.
// The 4 bytes {55, 00, 00, 55} are the status of characters on the portal. The 4 bytes are
// treated as a 32-bit binary array. Each unique Skylander placed on a board is represented
// by 2 bits starting with the first Skylander in the least significant bit. This bit is
// present whenever the Skylander is added or present on the portal. When the Skylander is
// added to the board, both bits are set in the next status message as a one-time signal.
// When a Skylander is removed from the board, only the most significant bit of the 2 bits
// is set.
// Different portals can track a different number of RFID tags. The Wii Wireless portal
// tracks 4, the Wired portal can track 8. The maximum number of unique Skylanders tracked
// at any time is 16, after which new Skylanders appear to cycle unused bits.
// Certain Skylanders, e.g. SwapForce Skylanders, are represented as 2 ID in the bit array.
// This may be due to the presence of 2 RFIDs, one for each half of the Skylander.
// The 6th byte {3e} is a counter and increments by one. It will roll over when reaching
// {ff}.
// The purpose of the (00\|01) byte at the 7th position appear to indicate if the portal has
// been activated: {01} when active and {00} when deactivated.
case 'S': {
if (wLength == 1) {
// The Status interrupt responses are automatically handled via the GetStatus method
control_response = {data[0]};
expected_count = 9;
}
break;
}
case 'V': {
if (wLength == 4) {
control_response = {data[0], data[1], data[2], data[3]};
expected_count = 12;
}
break;
}
// Write
// Command { 'W', 10, 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f, 00,
// 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 }
// Response { 'W', 00, 00, 00, 00, 00,
// 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
// 00, 00, 00, 00 }
// In the command the 2nd byte indicates which Skylander to query data from. Index starts at
// 0x10 for the 1st Skylander (as reported in the Status command.) The 16th Skylander
// indexed would be 0x20.
// 3rd byte is the block to write to.
// Bytes 4 - 19 ({ 01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f }) are the
// data to write.
// The response does not appear to return the id of the Skylander being written, the 2nd
// byte is 0x00; however, the 3rd byte echos the block that was written (0x00 in example
// above.)
case 'W': {
if (wLength == 19) {
const u8 sky_num = data[1] & 0xF;
const u8 block = data[2];
m_skylander_portal->WriteBlock(sky_num, block, &data[3], interrupt_response.data());
m_queries.push(interrupt_response);
control_response = {data[0], data[1], data[2], data[3], data[4],
data[5], data[6], data[7], data[8], data[9],
data[10], data[11], data[12], data[13], data[14],
data[15], data[16], data[17], data[18]};
expected_count = 27;
}
break;
}
default:
LOG_ERROR(Lib_Usbd, "Unhandled Skylander Portal Query: {}", data[0]);
break;
}
return expected_count;
}
libusb_transfer_status SkylanderBackend::HandleAsyncTransfer(libusb_transfer* transfer) {
switch (transfer->endpoint) {
case 0x81:
if (m_queries.empty()) {
memcpy(transfer->buffer, m_skylander_portal->GetStatus().data(), 32);
} else {
memcpy(transfer->buffer, m_queries.front().data(), 32);
m_queries.pop();
}
transfer->length = 32;
break;
case 0x02:
LOG_INFO(Lib_Usbd, "OUT ENDPOINT");
break;
default:
break;
}
return LIBUSB_TRANSFER_COMPLETED;
}
void Skylander::Save() {
if (!skyFile.IsOpen())
return;
skyFile.Seek(0);
skyFile.Write(data);
}
} // namespace Libraries::Usbd

View File

@ -0,0 +1,111 @@
// 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 <queue>
#include "common/io_file.h"
#include "core/libraries/usbd/usb_backend.h"
namespace Libraries::Usbd {
constexpr u16 SKY_BLOCK_COUNT = 0x40;
constexpr u16 SKY_BLOCK_SIZE = 0x10;
constexpr u16 SKY_FIGURE_SIZE = SKY_BLOCK_COUNT * SKY_BLOCK_SIZE;
constexpr u8 MAX_SKYLANDERS = 16;
struct Skylander final {
Common::FS::IOFile skyFile;
u8 status = 0;
std::queue<u8> queued_status;
std::array<u8, SKY_FIGURE_SIZE> data{};
u32 last_id = 0;
void Save();
enum : u8 { REMOVED = 0, READY = 1, REMOVING = 2, ADDED = 3 };
};
struct SkylanderLEDColor final {
u8 red = 0;
u8 green = 0;
u8 blue = 0;
};
class SkylanderPortal final : public UsbEmulatedImpl {
public:
SkylanderPortal();
~SkylanderPortal() override = default;
void Activate();
void Deactivate();
void SetLEDs(u8 side, u8 r, u8 g, u8 b);
std::array<u8, 64> GetStatus();
void QueryBlock(u8 sky_num, u8 block, u8* reply_buf);
void WriteBlock(u8 sky_num, u8 block, const u8* to_write_buf, u8* reply_buf);
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 {}
u8 LoadSkylander(u8* buf, Common::FS::IOFile file);
protected:
std::mutex sky_mutex;
private:
static bool IsSkylanderNumberValid(u8 sky_num);
static bool IsBlockNumberValid(u8 block);
bool m_activated = true;
bool m_status_updated = false;
u8 m_interrupt_counter = 0;
SkylanderLEDColor m_color_right = {};
SkylanderLEDColor m_color_left = {};
SkylanderLEDColor m_color_trap = {};
std::array<Skylander, MAX_SKYLANDERS> skylanders;
std::array<u8, MAX_SKYLANDERS> ui_skylanders;
};
class SkylanderBackend 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;
libusb_transfer_status HandleAsyncTransfer(libusb_transfer* transfer) override;
std::shared_ptr<UsbEmulatedImpl> GetImplRef() override {
return m_skylander_portal;
}
private:
std::shared_ptr<SkylanderPortal> m_skylander_portal = std::make_shared<SkylanderPortal>();
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, 0x40, 0x1, 0x0, 0x0},
{0x7, 0x5, 0x2, 0x3, 0x40, 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, 0x40, 0x1430, 0x150, 0x100, 0x1, 0x2, 0x0, 0x1}};
std::queue<std::array<u8, 64>> m_queries;
};
} // namespace Libraries::Usbd

View File

@ -0,0 +1,477 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <list>
#include <libusb.h>
#include "common/assert.h"
#include "common/types.h"
namespace Libraries::Usbd {
#if defined(_WIN32)
typedef CRITICAL_SECTION usbi_mutex_t;
#else
typedef pthread_mutex_t usbi_mutex_t;
#endif
struct list_head {
struct list_head *prev, *next;
};
// Forward declared libusb structs
struct UsbDeviceHandle {
usbi_mutex_t lock;
unsigned long claimed_interfaces;
struct list_head list;
struct libusb_device* dev;
int auto_detach_kernel_driver;
};
struct UsbDevice {
volatile long refcnt;
struct libusb_context* ctx;
struct libusb_device* parent_dev;
u8 bus_number;
u8 port_number;
u8 device_address;
enum libusb_speed speed;
struct list_head list;
unsigned long session_data;
struct libusb_device_descriptor device_descriptor;
volatile long attached;
};
class UsbEmulatedImpl {
public:
UsbEmulatedImpl() = default;
virtual void LoadFigure(std::string file_name, u8 pad, u8 slot) = 0;
virtual void RemoveFigure(u8 pad, u8 slot, bool full_remove) = 0;
virtual void MoveFigure(u8 new_pad, u8 new_index, u8 old_pad, u8 old_index) = 0;
virtual void TempRemoveFigure(u8 index) = 0;
virtual void CancelRemoveFigure(u8 index) = 0;
protected:
virtual ~UsbEmulatedImpl() = default;
};
class UsbBackend {
public:
UsbBackend() = default;
virtual s32 Init() = 0;
virtual void Exit() = 0;
virtual s64 GetDeviceList(libusb_device*** list) = 0;
virtual void FreeDeviceList(libusb_device** list, s32 unref_devices) = 0;
virtual s32 GetConfiguration(libusb_device_handle* dev, s32* config) = 0;
virtual s32 GetDeviceDescriptor(libusb_device* dev, libusb_device_descriptor* desc) = 0;
virtual s32 GetActiveConfigDescriptor(libusb_device* dev,
libusb_config_descriptor** config) = 0;
virtual s32 GetConfigDescriptor(libusb_device* dev, u8 config_index,
libusb_config_descriptor** config) = 0;
virtual void FreeConfigDescriptor(libusb_config_descriptor* config) = 0;
virtual u8 GetBusNumber(libusb_device* dev) = 0;
virtual u8 GetDeviceAddress(libusb_device* dev) = 0;
virtual s32 GetMaxPacketSize(libusb_device* dev, u8 endpoint) = 0;
virtual s32 OpenDevice(libusb_device* dev, libusb_device_handle** dev_handle) = 0;
virtual void CloseDevice(libusb_device_handle* dev_handle) = 0;
virtual libusb_device* GetDevice(libusb_device_handle* dev_handle) = 0;
virtual s32 SetConfiguration(libusb_device_handle* dev_handle, s32 configuration) = 0;
virtual s32 ClaimInterface(libusb_device_handle* dev_handle, s32 interface_number) = 0;
virtual libusb_device_handle* OpenDeviceWithVidPid(u16 vendor_id, u16 product_id) = 0;
virtual s32 ResetDevice(libusb_device_handle* dev_handle) = 0;
virtual s32 KernelDriverActive(libusb_device_handle* dev_handle, s32 interface_number) = 0;
virtual s32 DetachKernelDriver(libusb_device_handle* dev_handle, s32 interface_number) = 0;
virtual s32 AttachKernelDriver(libusb_device_handle* dev_handle, s32 interface_number) = 0;
virtual s32 ControlTransfer(libusb_device_handle* dev_handle, u8 bmRequestType, u8 bRequest,
u16 wValue, u16 wIndex, u8* data, u16 wLength, u32 timeout) = 0;
virtual s32 SubmitTransfer(libusb_transfer* transfer) = 0;
virtual s32 TryLockEvents() = 0;
virtual void LockEvents() = 0;
virtual void UnlockEvents() = 0;
virtual s32 EventHandlingOk() = 0;
virtual s32 EventHandlerActive() = 0;
virtual void LockEventWaiters() = 0;
virtual void UnlockEventWaiters() = 0;
virtual s32 WaitForEvent(timeval* tv) = 0;
virtual s32 HandleEventsTimeout(timeval* tv) = 0;
virtual s32 HandleEvents() = 0;
virtual s32 HandleEventsLocked(timeval* tv) = 0;
virtual s32 CheckConnected(libusb_device_handle* dev) = 0;
virtual std::shared_ptr<UsbEmulatedImpl> GetImplRef() = 0;
protected:
virtual ~UsbBackend() = default;
};
class UsbRealBackend : public UsbBackend {
public:
s32 Init() override {
return libusb_init(&g_libusb_context);
}
void Exit() override {
libusb_exit(g_libusb_context);
}
s64 GetDeviceList(libusb_device*** list) override {
return libusb_get_device_list(g_libusb_context, list);
}
void FreeDeviceList(libusb_device** list, s32 unref_devices) override {
libusb_free_device_list(list, unref_devices);
}
s32 GetConfiguration(libusb_device_handle* dev, s32* config) override {
return libusb_get_configuration(dev, config);
}
s32 GetDeviceDescriptor(libusb_device* dev, libusb_device_descriptor* desc) override {
return libusb_get_device_descriptor(dev, desc);
}
s32 GetActiveConfigDescriptor(libusb_device* dev, libusb_config_descriptor** config) override {
return libusb_get_active_config_descriptor(dev, config);
}
s32 GetConfigDescriptor(libusb_device* dev, u8 config_index,
libusb_config_descriptor** config) override {
return libusb_get_config_descriptor(dev, config_index, config);
}
void FreeConfigDescriptor(libusb_config_descriptor* config) override {
libusb_free_config_descriptor(config);
}
u8 GetBusNumber(libusb_device* dev) override {
return libusb_get_bus_number(dev);
}
u8 GetDeviceAddress(libusb_device* dev) override {
return libusb_get_device_address(dev);
}
s32 GetMaxPacketSize(libusb_device* dev, u8 endpoint) override {
return libusb_get_max_packet_size(dev, endpoint);
}
s32 OpenDevice(libusb_device* dev, libusb_device_handle** dev_handle) override {
return libusb_open(dev, dev_handle);
}
void CloseDevice(libusb_device_handle* dev_handle) override {
libusb_close(dev_handle);
}
libusb_device* GetDevice(libusb_device_handle* dev_handle) override {
return libusb_get_device(dev_handle);
}
s32 SetConfiguration(libusb_device_handle* dev_handle, s32 configuration) override {
return libusb_set_configuration(dev_handle, configuration);
}
s32 ClaimInterface(libusb_device_handle* dev_handle, s32 interface_number) override {
return libusb_claim_interface(dev_handle, interface_number);
}
libusb_device_handle* OpenDeviceWithVidPid(u16 vendor_id, u16 product_id) override {
return libusb_open_device_with_vid_pid(g_libusb_context, vendor_id, product_id);
}
s32 ResetDevice(libusb_device_handle* dev_handle) override {
return libusb_reset_device(dev_handle);
}
s32 KernelDriverActive(libusb_device_handle* dev_handle, s32 interface_number) override {
#if defined(_WIN32) || defined(__APPLE__)
return s_has_removed_driver ? 0 : 1;
#else
return libusb_kernel_driver_active(dev_handle, interface_number);
#endif
}
s32 DetachKernelDriver(libusb_device_handle* dev_handle, s32 interface_number) override {
#if defined(_WIN32) || defined(__APPLE__)
s_has_removed_driver = true;
return LIBUSB_SUCCESS;
#else
return libusb_detach_kernel_driver(dev_handle, interface_number);
#endif
}
s32 AttachKernelDriver(libusb_device_handle* dev_handle, s32 interface_number) override {
#if defined(_WIN32) || defined(__APPLE__)
s_has_removed_driver = false;
return LIBUSB_SUCCESS;
#else
return libusb_attach_kernel_driver(dev_handle, interface_number);
#endif
}
s32 ControlTransfer(libusb_device_handle* dev_handle, u8 bmRequestType, u8 bRequest, u16 wValue,
u16 wIndex, u8* data, u16 wLength, u32 timeout) override {
return libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, wIndex, data,
wLength, timeout);
}
s32 SubmitTransfer(libusb_transfer* transfer) override {
return libusb_submit_transfer(transfer);
}
s32 TryLockEvents() override {
return libusb_try_lock_events(g_libusb_context);
}
void LockEvents() override {
return libusb_lock_events(g_libusb_context);
}
void UnlockEvents() override {
return libusb_unlock_events(g_libusb_context);
}
s32 EventHandlingOk() override {
return libusb_event_handling_ok(g_libusb_context);
}
s32 EventHandlerActive() override {
return libusb_event_handler_active(g_libusb_context);
}
void LockEventWaiters() override {
return libusb_lock_event_waiters(g_libusb_context);
}
void UnlockEventWaiters() override {
return libusb_unlock_event_waiters(g_libusb_context);
}
s32 WaitForEvent(timeval* tv) override {
return libusb_wait_for_event(g_libusb_context, tv);
}
s32 HandleEventsTimeout(timeval* tv) override {
return libusb_handle_events_timeout(g_libusb_context, tv);
}
s32 HandleEvents() override {
return libusb_handle_events(g_libusb_context);
}
s32 HandleEventsLocked(timeval* tv) override {
return libusb_handle_events_locked(g_libusb_context, tv);
}
s32 CheckConnected(libusb_device_handle* dev) override {
// There's no libusb version of this function.
// Simulate by querying data.
int config;
return libusb_get_configuration(dev, &config);
}
std::shared_ptr<UsbEmulatedImpl> GetImplRef() override {
return nullptr;
}
protected:
libusb_context* g_libusb_context = nullptr;
bool s_has_removed_driver = false;
};
class UsbEmulatedBackend : public UsbRealBackend {
public:
s64 GetDeviceList(libusb_device*** list) override {
auto** fake = static_cast<libusb_device**>(calloc(2, sizeof(libusb_device*)));
fake[0] = GetDevice(nullptr);
fake[1] = nullptr;
*list = fake;
return 1;
}
void FreeDeviceList(libusb_device** list, s32 unref_devices) override {
if (!list) {
return;
}
if (unref_devices) {
int i = 0;
libusb_device* dev;
while ((dev = list[i++]) != nullptr) {
free(dev);
}
}
free(list);
}
s32 GetConfiguration(libusb_device_handle* dev, s32* config) override {
config = nullptr;
return LIBUSB_SUCCESS;
}
s32 GetDeviceDescriptor(libusb_device* dev, libusb_device_descriptor* desc) override {
std::memcpy(desc, FillDeviceDescriptor(), sizeof(libusb_device_descriptor));
return LIBUSB_SUCCESS;
}
s32 GetActiveConfigDescriptor(libusb_device* dev, libusb_config_descriptor** config) override {
const auto endpoint_descs = FillEndpointDescriptorPair();
const auto interface_desc = FillInterfaceDescriptor(endpoint_descs);
const auto interface = static_cast<libusb_interface*>(calloc(1, sizeof(libusb_interface)));
interface->altsetting = interface_desc;
interface->num_altsetting = 1;
const auto new_config = FillConfigDescriptor(interface);
ASSERT(endpoint_descs && interface_desc && new_config);
*config = new_config;
return LIBUSB_SUCCESS;
}
s32 GetConfigDescriptor(libusb_device* dev, u8 config_index,
libusb_config_descriptor** config) override {
if (config_index >= 1) {
return LIBUSB_ERROR_NOT_FOUND;
}
return GetActiveConfigDescriptor(dev, config);
}
void FreeConfigDescriptor(libusb_config_descriptor* config) override {
// Member variable reference, don't free
}
u8 GetBusNumber(libusb_device* dev) override {
return 0;
}
u8 GetDeviceAddress(libusb_device* dev) override {
return 0;
}
s32 GetMaxPacketSize(libusb_device* dev, u8 endpoint) override {
libusb_device_descriptor* desc = nullptr;
int r = GetDeviceDescriptor(dev, desc);
if (r < LIBUSB_SUCCESS) {
return LIBUSB_ERROR_OTHER;
}
return desc->bMaxPacketSize0;
}
s32 OpenDevice(libusb_device* dev, libusb_device_handle** dev_handle) override {
auto* _dev_handle = static_cast<UsbDeviceHandle*>(calloc(1, sizeof(libusb_device_handle*)));
if (!_dev_handle) {
return LIBUSB_ERROR_NO_MEM;
}
_dev_handle->dev = dev;
*dev_handle = reinterpret_cast<libusb_device_handle*>(_dev_handle);
return LIBUSB_SUCCESS;
}
void CloseDevice(libusb_device_handle* dev_handle) override {
LOG_WARNING(Lib_Usbd, "Guest decided to close device, might be an implementation issue");
free(dev_handle);
}
libusb_device* GetDevice(libusb_device_handle* dev_handle) override {
const auto desc = FillDeviceDescriptor();
ASSERT(desc);
const auto fake = static_cast<UsbDevice*>(calloc(1, sizeof(UsbDevice)));
fake->bus_number = 0;
fake->port_number = 0;
fake->device_address = 0;
fake->device_descriptor = *desc;
fake->ctx = g_libusb_context;
return reinterpret_cast<libusb_device*>(fake);
}
s32 SetConfiguration(libusb_device_handle* dev_handle, s32 configuration) override {
return LIBUSB_SUCCESS;
}
s32 ClaimInterface(libusb_device_handle* dev_handle, s32 interface_number) override {
return LIBUSB_SUCCESS;
}
libusb_device_handle* OpenDeviceWithVidPid(u16 vendor_id, u16 product_id) override {
libusb_device_handle* dev_handle = nullptr;
OpenDevice(GetDevice(nullptr), &dev_handle);
return dev_handle;
}
s32 ResetDevice(libusb_device_handle* dev_handle) override {
LOG_WARNING(Lib_Usbd, "Guest decided to reset device, might be an implementation issue");
return LIBUSB_SUCCESS;
}
s32 KernelDriverActive(libusb_device_handle* dev_handle, s32 interface_number) override {
return s_has_removed_driver ? 0 : 1;
}
s32 DetachKernelDriver(libusb_device_handle* dev_handle, s32 interface_number) override {
s_has_removed_driver = true;
return LIBUSB_SUCCESS;
}
s32 AttachKernelDriver(libusb_device_handle* dev_handle, s32 interface_number) override {
s_has_removed_driver = false;
return LIBUSB_SUCCESS;
}
s32 ControlTransfer(libusb_device_handle* dev_handle, u8 bmRequestType, u8 bRequest, u16 wValue,
u16 wIndex, u8* data, u16 wLength, u32 timeout) override {
LOG_WARNING(Lib_Usbd, "Backend has not handled control transfers");
return LIBUSB_ERROR_PIPE;
}
s32 SubmitTransfer(libusb_transfer* transfer) override {
ASSERT(transfer->type == LIBUSB_TRANSFER_TYPE_INTERRUPT);
// if we have no other flying transfers, start the list with this one
if (flight_list.empty()) {
flight_list.push_front(transfer);
return LIBUSB_SUCCESS;
}
// if we have infinite timeout, append to end of list
if (transfer->timeout == 0) {
flight_list.push_back(transfer);
return LIBUSB_SUCCESS;
}
// otherwise, find appropriate place in list
for (auto it = flight_list.begin(); it != flight_list.end(); ++it) {
if ((*it)->timeout > transfer->timeout) {
flight_list.insert(it, transfer);
return LIBUSB_SUCCESS;
}
}
// otherwise we need to be inserted at the end
flight_list.push_back(transfer);
return LIBUSB_SUCCESS;
}
s32 HandleEventsTimeout(timeval* tv) override {
return HandleEvents();
}
s32 HandleEvents() override {
if (!flight_list.empty()) {
const auto transfer = flight_list.front();
const u8 flags = transfer->flags;
transfer->status = HandleAsyncTransfer(transfer);
transfer->actual_length = transfer->length;
if (transfer->callback) {
transfer->callback(transfer);
}
if (flags & LIBUSB_TRANSFER_FREE_TRANSFER) {
libusb_free_transfer(transfer);
}
flight_list.pop_front();
}
return LIBUSB_SUCCESS;
}
s32 CheckConnected(libusb_device_handle* dev) override {
return LIBUSB_SUCCESS;
}
protected:
virtual libusb_endpoint_descriptor* FillEndpointDescriptorPair() = 0;
virtual libusb_interface_descriptor* FillInterfaceDescriptor(
libusb_endpoint_descriptor* descs) = 0;
virtual libusb_config_descriptor* FillConfigDescriptor(libusb_interface* inter) = 0;
virtual libusb_device_descriptor* FillDeviceDescriptor() = 0;
virtual libusb_transfer_status HandleAsyncTransfer(libusb_transfer* transfer) = 0;
std::list<libusb_transfer*> flight_list;
};
} // namespace Libraries::Usbd

View File

@ -2,7 +2,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "common/singleton.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "usbd.h"
@ -10,6 +9,8 @@
#include <fmt/format.h>
#include <libusb.h>
#include "common/config.h"
namespace Libraries::Usbd {
s32 libusb_to_orbis_error(int retVal) {
@ -23,7 +24,7 @@ s32 libusb_to_orbis_error(int retVal) {
return retVal;
}
libusb_context* g_libusb_context;
std::shared_ptr<UsbBackend> usb_backend;
#if defined(_WIN32)
bool s_has_removed_driver = false;
@ -32,25 +33,25 @@ bool s_has_removed_driver = false;
s32 PS4_SYSV_ABI sceUsbdInit() {
LOG_DEBUG(Lib_Usbd, "called");
return libusb_to_orbis_error(libusb_init(&g_libusb_context));
return libusb_to_orbis_error(usb_backend->Init());
}
void PS4_SYSV_ABI sceUsbdExit() {
LOG_DEBUG(Lib_Usbd, "called");
libusb_exit(g_libusb_context);
usb_backend->Exit();
}
s64 PS4_SYSV_ABI sceUsbdGetDeviceList(SceUsbdDevice*** list) {
LOG_DEBUG(Lib_Usbd, "called");
return libusb_to_orbis_error(libusb_get_device_list(g_libusb_context, list));
return libusb_to_orbis_error(usb_backend->GetDeviceList(list));
}
void PS4_SYSV_ABI sceUsbdFreeDeviceList(SceUsbdDevice** list, s32 unref_devices) {
LOG_DEBUG(Lib_Usbd, "called");
libusb_free_device_list(list, unref_devices);
usb_backend->FreeDeviceList(list, unref_devices);
}
SceUsbdDevice* PS4_SYSV_ABI sceUsbdRefDevice(SceUsbdDevice* device) {
@ -68,27 +69,27 @@ void PS4_SYSV_ABI sceUsbdUnrefDevice(SceUsbdDevice* device) {
s32 PS4_SYSV_ABI sceUsbdGetConfiguration(SceUsbdDeviceHandle* dev_handle, s32* config) {
LOG_DEBUG(Lib_Usbd, "called");
return libusb_to_orbis_error(libusb_get_configuration(dev_handle, config));
return libusb_to_orbis_error(usb_backend->GetConfiguration(dev_handle, config));
}
s32 PS4_SYSV_ABI sceUsbdGetDeviceDescriptor(SceUsbdDevice* device, SceUsbdDeviceDescriptor* desc) {
LOG_DEBUG(Lib_Usbd, "called");
return libusb_to_orbis_error(libusb_get_device_descriptor(device, desc));
return libusb_to_orbis_error(usb_backend->GetDeviceDescriptor(device, desc));
}
s32 PS4_SYSV_ABI sceUsbdGetActiveConfigDescriptor(SceUsbdDevice* device,
SceUsbdConfigDescriptor** config) {
LOG_DEBUG(Lib_Usbd, "called");
return libusb_to_orbis_error(libusb_get_active_config_descriptor(device, config));
return libusb_to_orbis_error(usb_backend->GetActiveConfigDescriptor(device, config));
}
s32 PS4_SYSV_ABI sceUsbdGetConfigDescriptor(SceUsbdDevice* device, u8 config_index,
SceUsbdConfigDescriptor** config) {
LOG_DEBUG(Lib_Usbd, "called");
return libusb_to_orbis_error(libusb_get_config_descriptor(device, config_index, config));
return libusb_to_orbis_error(usb_backend->GetConfigDescriptor(device, config_index, config));
}
s32 PS4_SYSV_ABI sceUsbdGetConfigDescriptorByValue(SceUsbdDevice* device, u8 bConfigurationValue,
@ -102,19 +103,19 @@ s32 PS4_SYSV_ABI sceUsbdGetConfigDescriptorByValue(SceUsbdDevice* device, u8 bCo
void PS4_SYSV_ABI sceUsbdFreeConfigDescriptor(SceUsbdConfigDescriptor* config) {
LOG_DEBUG(Lib_Usbd, "called");
libusb_free_config_descriptor(config);
usb_backend->FreeConfigDescriptor(config);
}
u8 PS4_SYSV_ABI sceUsbdGetBusNumber(SceUsbdDevice* device) {
LOG_DEBUG(Lib_Usbd, "called");
return libusb_get_bus_number(device);
return usb_backend->GetBusNumber(device);
}
u8 PS4_SYSV_ABI sceUsbdGetDeviceAddress(SceUsbdDevice* device) {
LOG_DEBUG(Lib_Usbd, "called");
return libusb_get_device_address(device);
return usb_backend->GetDeviceAddress(device);
}
SceUsbdSpeed PS4_SYSV_ABI sceUsbdGetDeviceSpeed(SceUsbdDevice* device) {
@ -126,7 +127,7 @@ SceUsbdSpeed PS4_SYSV_ABI sceUsbdGetDeviceSpeed(SceUsbdDevice* device) {
s32 PS4_SYSV_ABI sceUsbdGetMaxPacketSize(SceUsbdDevice* device, u8 endpoint) {
LOG_DEBUG(Lib_Usbd, "called");
return libusb_to_orbis_error(libusb_get_max_packet_size(device, endpoint));
return libusb_to_orbis_error(usb_backend->GetMaxPacketSize(device, endpoint));
}
s32 PS4_SYSV_ABI sceUsbdGetMaxIsoPacketSize(SceUsbdDevice* device, u8 endpoint) {
@ -138,25 +139,25 @@ s32 PS4_SYSV_ABI sceUsbdGetMaxIsoPacketSize(SceUsbdDevice* device, u8 endpoint)
s32 PS4_SYSV_ABI sceUsbdOpen(SceUsbdDevice* device, SceUsbdDeviceHandle** dev_handle) {
LOG_DEBUG(Lib_Usbd, "called");
return libusb_to_orbis_error(libusb_open(device, dev_handle));
return libusb_to_orbis_error(usb_backend->OpenDevice(device, dev_handle));
}
void PS4_SYSV_ABI sceUsbdClose(SceUsbdDeviceHandle* dev_handle) {
LOG_DEBUG(Lib_Usbd, "called");
libusb_close(dev_handle);
usb_backend->CloseDevice(dev_handle);
}
SceUsbdDevice* PS4_SYSV_ABI sceUsbdGetDevice(SceUsbdDeviceHandle* dev_handle) {
LOG_DEBUG(Lib_Usbd, "called");
return libusb_get_device(dev_handle);
return usb_backend->GetDevice(dev_handle);
}
s32 PS4_SYSV_ABI sceUsbdSetConfiguration(SceUsbdDeviceHandle* dev_handle, s32 config) {
LOG_DEBUG(Lib_Usbd, "called");
return libusb_to_orbis_error(libusb_set_configuration(dev_handle, config));
return libusb_to_orbis_error(usb_backend->SetConfiguration(dev_handle, config));
}
s32 PS4_SYSV_ABI sceUsbdClaimInterface(SceUsbdDeviceHandle* dev_handle, s32 interface_number) {
@ -166,7 +167,7 @@ s32 PS4_SYSV_ABI sceUsbdClaimInterface(SceUsbdDeviceHandle* dev_handle, s32 inte
sceUsbdDetachKernelDriver(dev_handle, interface_number);
}
return libusb_to_orbis_error(libusb_claim_interface(dev_handle, interface_number));
return libusb_to_orbis_error(usb_backend->ClaimInterface(dev_handle, interface_number));
}
s32 PS4_SYSV_ABI sceUsbdReleaseInterface(SceUsbdDeviceHandle* dev_handle, s32 interface_number) {
@ -178,7 +179,7 @@ s32 PS4_SYSV_ABI sceUsbdReleaseInterface(SceUsbdDeviceHandle* dev_handle, s32 in
SceUsbdDeviceHandle* PS4_SYSV_ABI sceUsbdOpenDeviceWithVidPid(u16 vendor_id, u16 product_id) {
LOG_DEBUG(Lib_Usbd, "called");
return libusb_open_device_with_vid_pid(g_libusb_context, vendor_id, product_id);
return usb_backend->OpenDeviceWithVidPid(vendor_id, product_id);
}
s32 PS4_SYSV_ABI sceUsbdSetInterfaceAltSetting(SceUsbdDeviceHandle* dev_handle,
@ -198,42 +199,25 @@ s32 PS4_SYSV_ABI sceUsbdClearHalt(SceUsbdDeviceHandle* dev_handle, uint8_t endpo
s32 PS4_SYSV_ABI sceUsbdResetDevice(SceUsbdDeviceHandle* dev_handle) {
LOG_DEBUG(Lib_Usbd, "called");
return libusb_to_orbis_error(libusb_reset_device(dev_handle));
return libusb_to_orbis_error(usb_backend->ResetDevice(dev_handle));
}
s32 PS4_SYSV_ABI sceUsbdKernelDriverActive(SceUsbdDeviceHandle* dev_handle, int interface_number) {
LOG_DEBUG(Lib_Usbd, "called");
#if defined(_WIN32)
if (!s_has_removed_driver)
return 1;
else
return 0;
#endif
return libusb_to_orbis_error(libusb_kernel_driver_active(dev_handle, interface_number));
return libusb_to_orbis_error(usb_backend->KernelDriverActive(dev_handle, interface_number));
}
s32 PS4_SYSV_ABI sceUsbdDetachKernelDriver(SceUsbdDeviceHandle* dev_handle, int interface_number) {
LOG_DEBUG(Lib_Usbd, "called");
#if defined(_WIN32)
s_has_removed_driver = true;
return 0;
#endif
return libusb_to_orbis_error(libusb_detach_kernel_driver(dev_handle, interface_number));
return libusb_to_orbis_error(usb_backend->DetachKernelDriver(dev_handle, interface_number));
}
s32 PS4_SYSV_ABI sceUsbdAttachKernelDriver(SceUsbdDeviceHandle* dev_handle, int interface_number) {
LOG_DEBUG(Lib_Usbd, "called");
#if defined(_WIN32)
s_has_removed_driver = false;
return 0;
#endif
return libusb_to_orbis_error(libusb_attach_kernel_driver(dev_handle, interface_number));
return libusb_to_orbis_error(usb_backend->AttachKernelDriver(dev_handle, interface_number));
}
u8* PS4_SYSV_ABI sceUsbdControlTransferGetData(SceUsbdTransfer* transfer) {
@ -264,7 +248,7 @@ SceUsbdTransfer* PS4_SYSV_ABI sceUsbdAllocTransfer(int iso_packets) {
s32 PS4_SYSV_ABI sceUsbdSubmitTransfer(SceUsbdTransfer* transfer) {
LOG_DEBUG(Lib_Usbd, "called");
return libusb_to_orbis_error(libusb_submit_transfer(transfer));
return libusb_to_orbis_error(usb_backend->SubmitTransfer(transfer));
}
s32 PS4_SYSV_ABI sceUsbdCancelTransfer(SceUsbdTransfer* transfer) {
@ -336,8 +320,8 @@ s32 PS4_SYSV_ABI sceUsbdControlTransfer(SceUsbdDeviceHandle* dev_handle, u8 requ
u32 timeout) {
LOG_DEBUG(Lib_Usbd, "called");
return libusb_to_orbis_error(libusb_control_transfer(dev_handle, request_type, bRequest, wValue,
wIndex, data, wLength, timeout));
return libusb_to_orbis_error(usb_backend->ControlTransfer(
dev_handle, request_type, bRequest, wValue, wIndex, data, wLength, timeout));
}
s32 PS4_SYSV_ABI sceUsbdBulkTransfer(SceUsbdDeviceHandle* dev_handle, u8 endpoint, u8* data,
@ -383,79 +367,73 @@ s32 PS4_SYSV_ABI sceUsbdGetStringDescriptorAscii(SceUsbdDeviceHandle* dev_handle
s32 PS4_SYSV_ABI sceUsbdTryLockEvents() {
LOG_DEBUG(Lib_Usbd, "called");
return libusb_try_lock_events(g_libusb_context);
return usb_backend->TryLockEvents();
}
void PS4_SYSV_ABI sceUsbdLockEvents() {
LOG_DEBUG(Lib_Usbd, "called");
libusb_lock_events(g_libusb_context);
usb_backend->LockEvents();
}
void PS4_SYSV_ABI sceUsbdUnlockEvents() {
LOG_DEBUG(Lib_Usbd, "called");
libusb_unlock_events(g_libusb_context);
usb_backend->UnlockEvents();
}
s32 PS4_SYSV_ABI sceUsbdEventHandlingOk() {
LOG_DEBUG(Lib_Usbd, "called");
return libusb_event_handling_ok(g_libusb_context);
return usb_backend->EventHandlingOk();
}
s32 PS4_SYSV_ABI sceUsbdEventHandlerActive() {
LOG_DEBUG(Lib_Usbd, "called");
return libusb_event_handler_active(g_libusb_context);
return usb_backend->EventHandlerActive();
}
void PS4_SYSV_ABI sceUsbdLockEventWaiters() {
LOG_DEBUG(Lib_Usbd, "called");
libusb_lock_event_waiters(g_libusb_context);
usb_backend->LockEventWaiters();
}
void PS4_SYSV_ABI sceUsbdUnlockEventWaiters() {
LOG_DEBUG(Lib_Usbd, "called");
libusb_unlock_event_waiters(g_libusb_context);
usb_backend->UnlockEventWaiters();
}
s32 PS4_SYSV_ABI sceUsbdWaitForEvent(timeval* tv) {
LOG_DEBUG(Lib_Usbd, "called");
return libusb_to_orbis_error(libusb_wait_for_event(g_libusb_context, tv));
return libusb_to_orbis_error(usb_backend->WaitForEvent(tv));
}
s32 PS4_SYSV_ABI sceUsbdHandleEventsTimeout(timeval* tv) {
LOG_DEBUG(Lib_Usbd, "called");
return libusb_to_orbis_error(libusb_handle_events_timeout(g_libusb_context, tv));
return libusb_to_orbis_error(usb_backend->HandleEventsTimeout(tv));
}
s32 PS4_SYSV_ABI sceUsbdHandleEvents() {
LOG_DEBUG(Lib_Usbd, "called");
return libusb_to_orbis_error(libusb_handle_events(g_libusb_context));
return libusb_to_orbis_error(usb_backend->HandleEvents());
}
s32 PS4_SYSV_ABI sceUsbdHandleEventsLocked(timeval* tv) {
LOG_DEBUG(Lib_Usbd, "called");
return libusb_to_orbis_error(libusb_handle_events_locked(g_libusb_context, tv));
return libusb_to_orbis_error(usb_backend->HandleEventsTimeout(tv));
}
s32 PS4_SYSV_ABI sceUsbdCheckConnected(SceUsbdDeviceHandle* dev_handle) {
LOG_DEBUG(Lib_Usbd, "called");
// There's no libusb version of this function.
// Simulate by querying data.
int config;
int r = libusb_get_configuration(dev_handle, &config);
return libusb_to_orbis_error(r);
return libusb_to_orbis_error(usb_backend->CheckConnected(dev_handle));
}
int PS4_SYSV_ABI Func_65F6EF33E38FFF50() {
@ -479,6 +457,21 @@ int PS4_SYSV_ABI Func_D56B43060720B1E0() {
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
switch (Config::getUsbDeviceBackend()) {
case Config::SkylandersPortal:
usb_backend = std::make_shared<SkylandersPortalBackend>();
break;
case Config::InfinityBase:
usb_backend = std::make_shared<InfinityBaseBackend>();
break;
case Config::DimensionsToypad:
usb_backend = std::make_shared<DimensionsToypadBackend>();
break;
default:
usb_backend = std::make_shared<UsbRealBackend>();
break;
}
LIB_FUNCTION("0ktE1PhzGFU", "libSceUsbd", 1, "libSceUsbd", sceUsbdAllocTransfer);
LIB_FUNCTION("BKMEGvfCPyU", "libSceUsbd", 1, "libSceUsbd", sceUsbdAttachKernelDriver);
LIB_FUNCTION("fotb7DzeHYw", "libSceUsbd", 1, "libSceUsbd", sceUsbdBulkTransfer);

View File

@ -4,6 +4,10 @@
#pragma once
#include "common/types.h"
#include "emulated/dimensions.h"
#include "emulated/infinity.h"
#include "emulated/skylander.h"
#include "usb_backend.h"
extern "C" {
struct libusb_device;
@ -21,6 +25,8 @@ class SymbolsResolver;
namespace Libraries::Usbd {
extern std::shared_ptr<UsbBackend> usb_backend;
using SceUsbdDevice = libusb_device;
using SceUsbdDeviceHandle = libusb_device_handle;
using SceUsbdDeviceDescriptor = libusb_device_descriptor;
@ -29,6 +35,10 @@ using SceUsbdTransfer = libusb_transfer;
using SceUsbdControlSetup = libusb_control_setup;
using SceUsbdTransferCallback = void PS4_SYSV_ABI (*)(SceUsbdTransfer* transfer);
using SkylandersPortalBackend = SkylanderBackend;
using InfinityBaseBackend = InfinityBackend;
using DimensionsToypadBackend = DimensionsBackend;
enum class SceUsbdSpeed : u32 {
UNKNOWN = 0,
LOW = 1,

View File

@ -104,17 +104,6 @@ void Linker::Execute(const std::vector<std::string>& args) {
memory->SetupMemoryRegions(fmem_size, use_extended_mem1, use_extended_mem2);
// Simulate sceKernelInternalMemory mapping, a mapping usually performed during libkernel init.
// Due to the large size of this mapping, failing to emulate it causes issues in some titles.
// This mapping belongs in the system reserved area, which starts at address 0x880000000.
static constexpr VAddr KernelAllocBase = 0x880000000ULL;
static constexpr s64 InternalMemorySize = 0x1000000;
void* addr_out{reinterpret_cast<void*>(KernelAllocBase)};
s32 ret = Libraries::Kernel::sceKernelMapNamedFlexibleMemory(&addr_out, InternalMemorySize, 3,
0, "SceKernelInternalMemory");
ASSERT_MSG(ret == 0, "Unable to perform sceKernelInternalMemory mapping");
main_thread.Run([this, module, &args](std::stop_token) {
Common::SetCurrentThreadName("GAME_MainThread");
if (auto& ipc = IPC::Instance()) {

View File

@ -502,19 +502,19 @@ bool Elf::IsSharedLib() {
}
void Elf::ElfHeaderDebugDump(const std::filesystem::path& file_name) {
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Write,
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Create,
Common::FS::FileType::TextFile};
f.WriteString(ElfHeaderStr());
}
void Elf::SelfHeaderDebugDump(const std::filesystem::path& file_name) {
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Write,
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Create,
Common::FS::FileType::TextFile};
f.WriteString(SElfHeaderStr());
}
void Elf::SelfSegHeaderDebugDump(const std::filesystem::path& file_name) {
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Write,
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Create,
Common::FS::FileType::TextFile};
for (u16 i = 0; i < m_self.segment_count; i++) {
f.WriteString(SELFSegHeader(i));
@ -522,7 +522,7 @@ void Elf::SelfSegHeaderDebugDump(const std::filesystem::path& file_name) {
}
void Elf::PHeaderDebugDump(const std::filesystem::path& file_name) {
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Write,
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Create,
Common::FS::FileType::TextFile};
if (m_elf_header.e_phentsize > 0) {
for (u16 i = 0; i < m_elf_header.e_phnum; i++) {

View File

@ -32,7 +32,7 @@ const SymbolRecord* SymbolsResolver::FindSymbol(const SymbolResolver& s) const {
}
void SymbolsResolver::DebugDump(const std::filesystem::path& file_name) {
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Write,
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Create,
Common::FS::FileType::TextFile};
for (const auto& symbol : m_symbols) {
const auto ids = Common::SplitString(symbol.name, '#');

View File

@ -587,6 +587,10 @@ s32 MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, u64 size, Memory
// On real hardware, GPU file mmaps cause a full system crash due to an internal error.
ASSERT_MSG(false, "Files cannot be mapped to GPU memory");
}
if (True(prot & MemoryProt::CpuExec)) {
// On real hardware, execute permissions are silently removed.
prot &= ~MemoryProt::CpuExec;
}
// Add virtual memory area
auto& new_vma = CarveVMA(mapped_addr, size)->second;
@ -793,10 +797,9 @@ s32 MemoryManager::QueryProtection(VAddr addr, void** start, void** end, u32* pr
s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea& vma_base, u64 size,
MemoryProt prot) {
const auto start_in_vma = addr - vma_base.base;
const auto adjusted_size =
vma_base.size - start_in_vma < size ? vma_base.size - start_in_vma : size;
const auto adjusted_size = std::min<u64>(vma_base.size - start_in_vma, size);
if (vma_base.type == VMAType::Free) {
if (vma_base.type == VMAType::Free || vma_base.type == VMAType::PoolReserved) {
// On PS4, protecting freed memory does nothing.
return adjusted_size;
}
@ -828,8 +831,9 @@ s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea& vma_base, u64 siz
perms |= Core::MemoryPermission::ReadWrite;
}
if (vma_base.type == VMAType::Direct || vma_base.type == VMAType::Pooled) {
// On PS4, execute permissions are hidden from direct memory mappings.
if (vma_base.type == VMAType::Direct || vma_base.type == VMAType::Pooled ||
vma_base.type == VMAType::File) {
// On PS4, execute permissions are hidden from direct memory and file mappings.
// Tests show that execute permissions still apply, so handle this after reading perms.
prot &= ~MemoryProt::CpuExec;
}
@ -837,6 +841,12 @@ s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea& vma_base, u64 siz
// Change protection
vma_base.prot = prot;
if (vma_base.type == VMAType::Reserved) {
// On PS4, protections change vma_map, but don't apply.
// Return early to avoid protecting memory that isn't mapped in address space.
return adjusted_size;
}
impl.Protect(addr, size, perms);
return adjusted_size;
@ -853,22 +863,20 @@ s32 MemoryManager::Protect(VAddr addr, u64 size, MemoryProt prot) {
// Ensure the range to modify is valid
ASSERT_MSG(IsValidMapping(addr, size), "Attempted to access invalid address {:#x}", addr);
// Validate protection flags
constexpr static MemoryProt valid_flags =
MemoryProt::NoAccess | MemoryProt::CpuRead | MemoryProt::CpuWrite | MemoryProt::CpuExec |
MemoryProt::GpuRead | MemoryProt::GpuWrite | MemoryProt::GpuReadWrite;
MemoryProt invalid_flags = prot & ~valid_flags;
if (invalid_flags != MemoryProt::NoAccess) {
LOG_ERROR(Kernel_Vmm, "Invalid protection flags");
return ORBIS_KERNEL_ERROR_EINVAL;
}
// Appropriately restrict flags.
constexpr static MemoryProt flag_mask =
MemoryProt::CpuReadWrite | MemoryProt::CpuExec | MemoryProt::GpuReadWrite;
MemoryProt valid_flags = prot & flag_mask;
// Protect all VMAs between addr and addr + size.
s64 protected_bytes = 0;
while (protected_bytes < size) {
auto it = FindVMA(addr + protected_bytes);
auto& vma_base = it->second;
if (vma_base.base > addr + protected_bytes) {
// Account for potential gaps in memory map.
protected_bytes += vma_base.base - (addr + protected_bytes);
}
auto result = ProtectBytes(addr + protected_bytes, vma_base, size - protected_bytes, prot);
if (result < 0) {
// ProtectBytes returned an error, return it
@ -904,13 +912,21 @@ s32 MemoryManager::VirtualQuery(VAddr addr, s32 flags,
const auto& vma = it->second;
info->start = vma.base;
info->end = vma.base + vma.size;
info->offset = vma.type == VMAType::Flexible ? 0 : vma.phys_base;
info->offset = 0;
info->protection = static_cast<s32>(vma.prot);
info->is_flexible = vma.type == VMAType::Flexible ? 1 : 0;
info->is_direct = vma.type == VMAType::Direct ? 1 : 0;
info->is_stack = vma.type == VMAType::Stack ? 1 : 0;
info->is_pooled = vma.type == VMAType::PoolReserved || vma.type == VMAType::Pooled ? 1 : 0;
info->is_committed = vma.IsMapped() ? 1 : 0;
if (vma.type == VMAType::Direct || vma.type == VMAType::Pooled) {
// Offset is only assigned for direct and pooled mappings.
info->offset = vma.phys_base;
}
if (vma.type == VMAType::Reserved || vma.type == VMAType::PoolReserved) {
// Protection is hidden from reserved mappings.
info->protection = 0;
}
strncpy(info->name, vma.name.data(), ::Libraries::Kernel::ORBIS_KERNEL_MAXIMUM_NAME_LENGTH);

View File

@ -45,7 +45,10 @@ enum class MemoryMapFlags : u32 {
Private = 2,
Fixed = 0x10,
NoOverwrite = 0x80,
Void = 0x100,
Stack = 0x400,
NoSync = 0x800,
Anon = 0x1000,
NoCore = 0x20000,
NoCoalesce = 0x400000,
};

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <mutex>
@ -165,10 +165,7 @@ void SetTcbBase(void* image_address) {
}
Tcb* GetTcbBase() {
void* tcb = nullptr;
const int ret = syscall(SYS_arch_prctl, ARCH_GET_GS, &tcb);
ASSERT_MSG(ret == 0, "Failed to get GS base: errno {}", errno);
return static_cast<Tcb*>(tcb);
return Libraries::Kernel::g_curthread->tcb;
}
#else

View File

@ -1,10 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstring>
#include "common/types.h"
#ifdef _WIN32
#include <malloc.h>
#endif
namespace Xbyak {
class CodeGenerator;

View File

@ -15,10 +15,6 @@
#include "common/logging/backend.h"
#include "common/logging/log.h"
#include "core/ipc/ipc.h"
#ifdef ENABLE_QT_GUI
#include <QtCore>
#endif
#include "common/assert.h"
#ifdef ENABLE_DISCORD_RPC
#include "common/discord_rpc_handler.h"
#endif
@ -35,6 +31,8 @@
#include "core/file_format/trp.h"
#include "core/file_sys/fs.h"
#include "core/libraries/disc_map/disc_map.h"
#include "core/libraries/font/font.h"
#include "core/libraries/font/fontft.h"
#include "core/libraries/libc_internal/libc_internal.h"
#include "core/libraries/libs.h"
#include "core/libraries/ngs2/ngs2.h"
@ -44,6 +42,7 @@
#include "core/linker.h"
#include "core/memory.h"
#include "emulator.h"
#include "video_core/cache_storage.h"
#include "video_core/renderdoc.h"
#ifdef _WIN32
@ -60,10 +59,11 @@ Frontend::WindowSDL* g_window = nullptr;
namespace Core {
Emulator::Emulator() {
// Initialize NT API functions and set high priority
// Initialize NT API functions, set high priority and disable WER
#ifdef _WIN32
Common::NtApi::Initialize();
SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS);
SetErrorMode(SetErrorMode(0) | SEM_NOGPFAULTERRORBOX);
// need to init this in order for winsock2 to work
WORD versionWanted = MAKEWORD(2, 2);
WSADATA wsaData;
@ -115,6 +115,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) {
@ -134,8 +135,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);
@ -198,6 +239,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());
}
@ -237,22 +279,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);
@ -341,8 +367,6 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
}
#endif
// Start the timer (Play Time)
#ifdef ENABLE_QT_GUI
if (!id.empty()) {
start_time = std::chrono::steady_clock::now();
@ -354,7 +378,6 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
}
}).detach();
}
#endif
args.insert(args.begin(), eboot_name.generic_string());
linker->Execute(args);
@ -364,9 +387,8 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
window->WaitEvent();
}
#ifdef ENABLE_QT_GUI
UpdatePlayTime(id);
#endif
Storage::DataBase::Instance().Close();
std::quick_exit(0);
}
@ -389,9 +411,9 @@ void Emulator::Restart(std::filesystem::path eboot_path,
args.push_back("--ignore-game-patch");
}
if (!MemoryPatcher::patchFile.empty()) {
if (!MemoryPatcher::patch_file.empty()) {
args.push_back("--patch");
args.push_back(MemoryPatcher::patchFile);
args.push_back(MemoryPatcher::patch_file);
}
args.push_back("--wait-for-pid");
@ -482,8 +504,8 @@ void Emulator::LoadSystemModules(const std::string& game_serial) {
{"libSceJson2.sprx", nullptr},
{"libSceLibcInternal.sprx", &Libraries::LibcInternal::RegisterLib},
{"libSceCesCs.sprx", nullptr},
{"libSceFont.sprx", nullptr},
{"libSceFontFt.sprx", nullptr},
{"libSceFont.sprx", &Libraries::Font::RegisterlibSceFont},
{"libSceFontFt.sprx", &Libraries::FontFt::RegisterlibSceFontFt},
{"libSceFreeTypeOt.sprx", nullptr}});
std::vector<std::filesystem::path> found_modules;

View File

@ -3,6 +3,8 @@
#pragma once
#include "common/types.h"
#include <cmath>
#include <imgui.h>
@ -12,6 +14,7 @@
#define IMGUI_FONT_TEXT 0
#define IMGUI_FONT_MONO 1
#define IMGUI_FONT_TEXT_BIG 2
namespace ImGui {

View File

@ -76,10 +76,15 @@ void Initialize(const ::Vulkan::Instance& instance, const Frontend::WindowSDL& w
font_cfg.OversampleV = 1;
io.FontDefault = io.Fonts->AddFontFromMemoryCompressedTTF(
imgui_font_notosansjp_regular_compressed_data,
imgui_font_notosansjp_regular_compressed_size, 16.0f, &font_cfg, ranges.Data);
imgui_font_notosansjp_regular_compressed_size, 32.0f, &font_cfg, ranges.Data);
io.Fonts->AddFontFromMemoryCompressedTTF(imgui_font_proggyvector_regular_compressed_data,
imgui_font_proggyvector_regular_compressed_size,
16.0f);
32.0f);
io.Fonts->AddFontFromMemoryCompressedTTF(imgui_font_notosansjp_regular_compressed_data,
imgui_font_notosansjp_regular_compressed_size, 128.0f,
&font_cfg, ranges.Data);
io.FontGlobalScale = 0.5f;
StyleColorsDark();
@ -110,7 +115,7 @@ void Initialize(const ::Vulkan::Instance& instance, const Frontend::WindowSDL& w
dock_id = ImHashStr(label);
if (const auto dpi = SDL_GetWindowDisplayScale(window.GetSDLWindow()); dpi > 0.0f) {
GetIO().FontGlobalScale = dpi;
GetIO().FontGlobalScale *= dpi;
}
std::at_quick_exit([] { SaveIniSettingsToDisk(GetIO().IniFilename); });
@ -245,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

@ -114,69 +114,6 @@ State GameController::GetLastState() const {
}
void GameController::AddState(const State& state) {
// Don't add duplicate states - compare input values (ignoring timestamp)
if (m_states_num > 0) {
const State& last = GetLastState();
// Compare button states
bool buttons_match = (last.buttonsState == state.buttonsState);
// Compare axes with a LARGE threshold to maintain continuous state during spinning
// Use ±64 (~25% of 0-255 range) - only create new states for significant direction changes
// This treats analog movement as continuous rather than discrete state changes
bool axes_match = true;
for (int i = 0; i < static_cast<int>(Axis::AxisMax); i++) {
int diff = std::abs(last.axes[i] - state.axes[i]);
if (diff > 64) {
axes_match = false;
break;
}
}
// Compare touchpad states
bool touchpad_match = true;
for (int i = 0; i < 2; i++) {
if (last.touchpad[i].state != state.touchpad[i].state ||
last.touchpad[i].x != state.touchpad[i].x ||
last.touchpad[i].y != state.touchpad[i].y) {
touchpad_match = false;
break;
}
}
// Skip motion sensor comparison for duplicate detection
// Motion sensors (gyro/accelerometer) constantly fluctuate due to sensor noise
// and shouldn't affect whether we consider a state as "duplicate" for gameplay purposes
// If all gameplay-relevant input values are similar enough, just update the values
// in the most recent buffered state to track gradual movement without creating new entries
if (buttons_match && axes_match && touchpad_match) {
m_last_state.time = state.time;
// Update the axes to track continuous movement
for (int i = 0; i < static_cast<int>(Axis::AxisMax); i++) {
m_last_state.axes[i] = state.axes[i];
}
// Update motion sensor data
m_last_state.acceleration = state.acceleration;
m_last_state.angularVelocity = state.angularVelocity;
m_last_state.orientation = state.orientation;
// IMPORTANT: Also update the last buffered state (if any) with current axis values
// This ensures the game reads the current stick position, not old values
if (m_states_num > 0) {
const u32 last_index = (m_first_state + m_states_num - 1) % MAX_STATES;
m_states[last_index].time = state.time;
for (int i = 0; i < static_cast<int>(Axis::AxisMax); i++) {
m_states[last_index].axes[i] = state.axes[i];
}
m_states[last_index].acceleration = state.acceleration;
m_states[last_index].angularVelocity = state.angularVelocity;
m_states[last_index].orientation = state.orientation;
}
return;
}
}
if (m_states_num >= MAX_STATES) {
m_states_num = MAX_STATES - 1;
m_first_state = (m_first_state + 1) % MAX_STATES;

View File

@ -106,6 +106,7 @@ auto output_array = std::array{
ControllerOutput(HOTKEY_RELOAD_INPUTS),
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK),
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_GYRO),
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD),
ControllerOutput(HOTKEY_RENDERDOC),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID),
@ -579,6 +580,9 @@ void ControllerOutput::FinalizeUpdate() {
case HOTKEY_TOGGLE_MOUSE_TO_GYRO:
PushSDLEvent(SDL_EVENT_MOUSE_TO_GYRO);
break;
case HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD:
PushSDLEvent(SDL_EVENT_MOUSE_TO_TOUCHPAD);
break;
case HOTKEY_RENDERDOC:
PushSDLEvent(SDL_EVENT_RDOC_CAPTURE);
break;
@ -773,6 +777,9 @@ void ActivateOutputsFromInputs() {
it.ResetUpdate();
}
// Check for input blockers
ApplyMouseInputBlockers();
// Iterate over all inputs, and update their respecive outputs accordingly
for (auto& it : connections) {
it.output->AddUpdate(it.ProcessBinding());

View File

@ -34,9 +34,10 @@
#define SDL_EVENT_RELOAD_INPUTS SDL_EVENT_USER + 5
#define SDL_EVENT_MOUSE_TO_JOYSTICK SDL_EVENT_USER + 6
#define SDL_EVENT_MOUSE_TO_GYRO SDL_EVENT_USER + 7
#define SDL_EVENT_RDOC_CAPTURE SDL_EVENT_USER + 8
#define SDL_EVENT_QUIT_DIALOG SDL_EVENT_USER + 9
#define SDL_EVENT_MOUSE_WHEEL_OFF SDL_EVENT_USER + 10
#define SDL_EVENT_MOUSE_TO_TOUCHPAD SDL_EVENT_USER + 8
#define SDL_EVENT_RDOC_CAPTURE SDL_EVENT_USER + 9
#define SDL_EVENT_QUIT_DIALOG SDL_EVENT_USER + 10
#define SDL_EVENT_MOUSE_WHEEL_OFF SDL_EVENT_USER + 11
#define LEFTJOYSTICK_HALFMODE 0x00010000
#define RIGHTJOYSTICK_HALFMODE 0x00020000
@ -52,7 +53,8 @@
#define HOTKEY_RELOAD_INPUTS 0xf0000005
#define HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK 0xf0000006
#define HOTKEY_TOGGLE_MOUSE_TO_GYRO 0xf0000007
#define HOTKEY_RENDERDOC 0xf0000008
#define HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD 0xf0000008
#define HOTKEY_RENDERDOC 0xf0000009
#define SDL_UNMAPPED UINT32_MAX - 1
@ -141,6 +143,7 @@ const std::map<std::string, u32> string_to_cbutton_map = {
{"hotkey_reload_inputs", HOTKEY_RELOAD_INPUTS},
{"hotkey_toggle_mouse_to_joystick", HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK},
{"hotkey_toggle_mouse_to_gyro", HOTKEY_TOGGLE_MOUSE_TO_GYRO},
{"hotkey_toggle_mouse_to_touchpad", HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD},
{"hotkey_renderdoc_capture", HOTKEY_RENDERDOC},
};

Some files were not shown because too many files have changed in this diff Show More