mirror of
https://github.com/ayozetr/bloodborne_linux.git
synced 2026-11-08 01:08:25 -10:00
Compare commits
21
Commits
main
...
0.4-linux.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5227432b1b | ||
|
|
81db68f552 | ||
|
|
f723f6a972 | ||
|
|
82a96e0d25 | ||
|
|
71717863d5 | ||
|
|
c4281227da | ||
|
|
ee20c8b147 | ||
|
|
a39a0073de | ||
|
|
09a1e4981a | ||
|
|
992fc95ed1 | ||
|
|
851712537b | ||
|
|
e289b722b5 | ||
|
|
c7cae2a585 | ||
|
|
7d53c5e9d6 | ||
|
|
7dfbf49367 | ||
|
|
52a6daf288 | ||
|
|
912641c298 | ||
|
|
dca76e386f | ||
|
|
0b465f8ee1 | ||
|
|
82782d8155 | ||
|
|
89ea3deb70 |
No files matched your search
@@ -23,6 +23,8 @@ perf.data
|
||||
/bloodborne_upscalers_*.md
|
||||
mods.json
|
||||
patches.json
|
||||
# The mods folder of runs from source (the launcher's default, DATA_DIR/mods)
|
||||
/mods/
|
||||
# Local: game logs, messages and archives from users, internal plans
|
||||
/logs/
|
||||
/message*.txt
|
||||
@@ -30,3 +32,10 @@ patches.json
|
||||
/docs/ISSUE_ACTION_PLAN_*.md
|
||||
# AMD FidelityFX DLLs (FSR 4.1.1 is built from the user's own): never in the repository
|
||||
*.dll
|
||||
# Game files and dumps: never in the repository
|
||||
eboot.bin
|
||||
param.sfo
|
||||
*.pkg
|
||||
*.dcx
|
||||
*.7z
|
||||
/CUSA*/
|
||||
@@ -1,6 +1,8 @@
|
||||
[submodule "gpu/third_party/fsr-vulkan"]
|
||||
path = gpu/third_party/fsr-vulkan
|
||||
url = https://github.com/FireBurn/FSR-Vulkan.git
|
||||
# build.sh applies gpu/patches/fsr-vulkan to its working tree
|
||||
ignore = dirty
|
||||
[submodule "gpu/third_party/imgui"]
|
||||
path = gpu/third_party/imgui
|
||||
url = https://github.com/ocornut/imgui.git
|
||||
|
||||
@@ -0,0 +1,370 @@
|
||||
THIS PROJECT IS NOT RELATED TO SHADPS4. ALL QUESTIONS RELATED TO THIS PROJECT SHOULD BE SENT TO THE DISCORD SERVER https://discord.gg/KYZRKk9CB, NOT TO THE SHADPS4 SERVER.
|
||||
|
||||
|
||||
# bbport — a native Linux port of Bloodborne
|
||||
|
||||
> This is the README of the original project (deadinside28/bloodborne_pc), kept for its
|
||||
> technical details. The fork's own README is [README.md](README.md).
|
||||
|
||||
**English** · [Русский](BBPORT.ru.md)
|
||||
|
||||
bbport is the counterpart of Wine + DXVK for a single game: *Bloodborne* for PlayStation 4
|
||||
(CUSA03173, game version 1.09) on an x86-64 Linux PC. The game's original executable runs
|
||||
directly on the PC:
|
||||
|
||||
- **as in Wine**, the game's x86-64 code runs on the CPU directly, and a runtime written for
|
||||
this one game replaces the PS4 system libraries;
|
||||
- **as in DXVK**, the game's graphics are translated to Vulkan — by a renderer derived from
|
||||
[shadPS4](https://github.com/shadps4-emu/shadPS4) and heavily extended for this game, including
|
||||
temporal upscaling with AMD FSR 3.1, FSR 4 and FSR 4.1.1;
|
||||
- memory and the GPU's work are moving to the PC model: the launcher has an experimental
|
||||
*New memory and translation model* mode (AMD GPUs only); without it the game runs on the old
|
||||
memory model, as in 0.3.
|
||||
|
||||
Two steps remain to the full Wine + DXVK model: make the new mode the default, and move the
|
||||
reading of resource descriptors (textures, buffers) from the CPU to the GPU (see below).
|
||||
|
||||
> **No game files are included.** You need your own dump of Bloodborne (CUSA03173, v1.09).
|
||||
> This project is not affiliated with Sony Interactive Entertainment, FromSoftware or AMD.
|
||||
|
||||
**Status: experimental, playable.** The game boots, loads saves and plays (the Hunter's Dream
|
||||
and several areas of Yharnam were played with it) with sound, gamepad and saving.
|
||||
A full play-through has not been verified, and only one machine (Linux, AMD Radeon RX 7800 XT,
|
||||
Mesa/RADV) has been tested thoroughly.
|
||||
|
||||
## Highlights
|
||||
|
||||
- **Native execution.** The eboot is converted offline into a flat memory image; PS4 libc and
|
||||
libSceFios2 are linked into it as native code. No CPU emulation and no per-instruction
|
||||
translation: the game code runs at full speed.
|
||||
- **Two modes** (launcher → *Mode*):
|
||||
- **Switch off (the default) — as in 0.3.** The old memory model: VRAM copies of the game's
|
||||
memory, writes tracked through page protection. With every fix made since 0.3 (motion
|
||||
vectors and the upscaler, flicker with DoF on, a damaged shader cache).
|
||||
- **New memory and translation model — experimental, AMD GPUs only.** The game's memory lives
|
||||
in system RAM and the GPU reads it where it is, as a PC game's buffers; data it reads often
|
||||
is kept in VRAM and given back when unused (textures after 20 s, buffers after 60 s). No
|
||||
write tracking, no copies of the whole GPU-visible memory. The PS4 command processor's work
|
||||
is translated into Vulkan commands rather than emulated on the CPU: memory writes
|
||||
(`WRITE_DATA`, DMA) are done by the GPU in command-stream order, and fences are written once
|
||||
the GPU has really finished the work. Where the game is CPU-bound it runs 20–25% faster
|
||||
(measured standing in the Hunter's Dream), with fewer stutters. **It may crash**, and has
|
||||
been tested thoroughly only on the author's PC (RX 7800 XT). On NVIDIA and Intel the switch
|
||||
is unavailable: NVIDIA's driver cannot map the game's memory as needed, Intel is untested.
|
||||
Without the launcher: `BB_PC_MODEL=1`; to try it on another GPU: `BB_PC_MODEL_ANY_GPU=1`.
|
||||
|
||||
Unused textures are freed in both modes, so VRAM no longer grows with every area visited.
|
||||
- **Unlocked frame rate.** Community patches (`patches/Bloodborne.xml`) make the simulation
|
||||
use the real frame time; ~90 FPS at 4K with FSR 4 Balanced on an RX 7800 XT, ~150 FPS at
|
||||
1440p with FSR 4 Quality. Also 30/60/90 FPS modes.
|
||||
- **Temporal upscaling built for this game.** Bloodborne has no velocity buffer, so bbport
|
||||
computes motion vectors itself: camera motion from depth and the scene matrices, and object
|
||||
motion (characters, cloth, weapons) from the vertex positions of the previous frame. The
|
||||
scene is jittered sub-pixel (Halton) and rendered at a reduced resolution; the upscaler fills
|
||||
the output (720p for the Steam Deck, 1080p, 1440p or 2160p) and the UI is drawn natively at the output resolution.
|
||||
- **FSR 3.1** (FireBurn/FSR-Vulkan).
|
||||
- **FSR 4 (INT8, model v07)** on GPUs exposing the required Vulkan shader features —
|
||||
RDNA2/3 included (see Requirements).
|
||||
- **FSR 4.1.1**: AMD's 4.1.1 DLL is recorded once under vkd3d-proton and its passes are
|
||||
replayed natively on Vulkan; the output is **bit-exact** with the DLL. Two variants, as in
|
||||
the DLL: INT8 on any GPU with the required shader features, and FP8 matrices on RDNA4 (RX
|
||||
9000; picked automatically). The assets are built on your machine from one DLL of your own
|
||||
(4.1.x): the launcher's *FSR 4.1.1 from your own AMD DLL → Choose DLL…* button (2–5 minutes,
|
||||
twice that on RDNA4; needs a recent Proton), or `tools/fsr4cap`.
|
||||
- Faster than AMD's own shaders on RDNA3: the final passes of FSR 4 and 4.1.1 were rewritten
|
||||
to store through workgroup memory (3.5× and 2.3× faster, bit-exact); FSR 4 costs ~4 ms at
|
||||
4K on an RX 7800 XT instead of ~6 ms.
|
||||
- **Multi-threaded GPU command processing.** The PS4 command stream is decoded on one thread
|
||||
and draws are bound and recorded on another (two-stage pipeline), with a Vulkan recording
|
||||
thread and helper threads for memory copies. Early on the single GPU thread capped the game
|
||||
at ~26 FPS; now it runs at 90–150 FPS depending on resolution and scene.
|
||||
- **In-game menu** (Insert or L3+R3): upscaler, preset, sharpness, output resolution, game
|
||||
effects (chromatic aberration, DoF, motion blur, SSAO, the game's own AA, SSR, model LOD).
|
||||
It opens where it was left, with the mouse cursor shown over it.
|
||||
- **GTK4 launcher** and an **AppImage** for the Steam Deck.
|
||||
|
||||
## How it differs from shadPS4
|
||||
|
||||
| | shadPS4 | bbport |
|
||||
|---|---|---|
|
||||
| Scope | General PS4 emulator, many games | One game: Bloodborne v1.09 |
|
||||
| Loading | Its own ELF loader and kernel emulation at run time | The eboot is converted offline (`scripts/`) into an image with PS4 libc/Fios2 linked in; a C loader maps it and jumps into the game (loader and runtime: ~5k lines) |
|
||||
| Memory | The GPU's view of PS4 memory is kept in VRAM copies, synchronized through page-protection write tracking | By default the same model (as in 0.3); in the *New memory and translation model* mode (AMD): the game's memory in system RAM, used by the GPU in place, frequently read data in VRAM, freed when unused |
|
||||
| Command processor | Emulated: memory writes, DMA and fences are done by the CPU while decoding | By default the same; in the new mode translated into Vulkan commands that the GPU runs in stream order, fences written after the work has really finished |
|
||||
| System libraries | Broad HLE of the PS4 OS | A small runtime (`src/runtime_*.c`) that implements exactly what Bloodborne calls: memory, threads, sync, files, audio (incl. ATRAC9), pad, saves, AppContent |
|
||||
| GPU | shadPS4 video core and shader recompiler | The same core (vendored, GPL) with ~200 marked changes (`bbport:`) plus new modules: two-stage draw pipeline, render-state and texture-set memoization, render-scale proxies, motion vectors, FSR 3.1/4/4.1.1, frame capture and GPU profiler |
|
||||
| GPU thread | One thread processes the whole command stream (the bottleneck in Bloodborne) | Decode and draw recording run on separate threads; the work scales with the hardware threads (Steam Deck included) |
|
||||
| Upscaling | — | Temporal (FSR 3.1, FSR 4, FSR 4.1.1) with the game's own motion vectors and jitter |
|
||||
| Game patches | Patch files applied by the emulator | The same community patches, compiled at start (`scripts/patches.py`); render resolution, effects and FPS from the launcher |
|
||||
|
||||
Without shadPS4 there would be no bbport: its renderer and shader recompiler are the base of
|
||||
the graphics side.
|
||||
|
||||
### Wine + DXVK for one game
|
||||
|
||||
- **CPU.** The PS4 CPU is x86-64, so the game's code runs directly on the PC's CPU, with no
|
||||
emulation and no instruction translation.
|
||||
- **System libraries.** As Wine replaces the Windows API, the bbport runtime (`src/runtime_*.c`)
|
||||
implements exactly the PS4 OS functions Bloodborne calls: memory, threads, files, audio, pad,
|
||||
saves.
|
||||
- **Memory.** In the new mode, as in a PC game: the game's data in system RAM, VRAM used the way
|
||||
a PC game uses it. The mode is still experimental and runs on AMD GPUs only.
|
||||
- **Graphics.** The PS4 GPU's command stream (PM4) is a recording of the game's graphics API
|
||||
calls: it is written by 99 functions of the statically linked libGnm, so decoding the stream
|
||||
and translating the calls themselves come to the same thing. GCN shaders are translated to
|
||||
SPIR-V. In the new mode the command processor's work (memory writes, DMA, fences) is
|
||||
translated into Vulkan commands as well. One part still works the old way: the translator
|
||||
reads resource descriptors (textures, buffers) from the game's memory on the CPU, and
|
||||
recognises textures by address. The next big step is the GPU reading the descriptors itself
|
||||
(bindless), with textures as objects created at load time.
|
||||
|
||||
"Port" here means a build for this one game, not a rewrite of its source code, which the project
|
||||
neither has nor includes.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Linux x86-64, a Vulkan 1.3 GPU. Tested: AMD RX 7800 XT with Mesa 26 (RADV).
|
||||
The *New memory and translation model* mode needs an AMD GPU.
|
||||
FSR 4 / 4.1.1 require shader Float16, Int8/Int16, integer dot products, linear compute
|
||||
derivatives and extended storage image formats; FSR 4.1.1 additionally requires
|
||||
`VK_VALVE_shader_mixed_float_dot_product`. Unsupported choices fall back to FSR 3.1
|
||||
before the first frame and are disabled in the in-game menu.
|
||||
- Your decrypted game dump: the `CUSA03173` folder (eboot.bin, sce_module, ...), version 1.09.
|
||||
The EU release (`CUSA00900`) runs too: its 1.09 executable is the same as the US one.
|
||||
A dumped update is a separate folder: copy it over the base game, replacing files. The base
|
||||
game alone (1.00) crashes at start (guest offset 0x20348b8); the launcher and `run.sh` check
|
||||
the executable and say what is missing (`BB_SKIP_GAME_CHECK=1` skips the check). Some dumps
|
||||
ship the 1.09 eboot.bin with Lance McDonald's 60 fps patch baked in; that one crashes when the
|
||||
gestures menu opens, and the check asks for a clean eboot.bin (bbport has its own 60 fps).
|
||||
- To build: GCC, CMake, Ninja, Python 3, glslang, SDL3, Vulkan headers and the libraries in
|
||||
`shell.nix`. With [Nix](https://nixos.org) everything comes from `shell.nix` automatically.
|
||||
|
||||
## Build and run
|
||||
|
||||
```bash
|
||||
git clone --recursive https://github.com/deadinside28/bloodborne_pc.git bbport && cd bbport
|
||||
bash build.sh # builds out/bb-probe and out/gpu/libbbgpu.so
|
||||
BB_GAME_DIR=/path/to/CUSA03173 bash run.sh
|
||||
```
|
||||
|
||||
or the launcher (pick the game folder, settings, *Start*):
|
||||
|
||||
```bash
|
||||
bash launcher/bb-launcher.sh # launcher/install-desktop.sh adds it to the app menu
|
||||
```
|
||||
|
||||
By default the game folder is expected next to the repository (`../CUSA03173`). Saves and the
|
||||
shader cache go to `user/` (the launcher lets you choose another folder); settings to
|
||||
`bbport.ini`. A gamepad is used through SDL3 (the launcher's *Controls → Controller* picks one
|
||||
when several are connected; `BB_GAMEPAD=<GUID or part of the name>`). The keyboard works too,
|
||||
also next to a connected gamepad (the Steam Deck always has one); both are remapped in the
|
||||
launcher (*Controls*). The character name is typed on the keyboard in a box over the game.
|
||||
The touchpad: its left half (Tab, Back/Select) opens the gestures, the right half (Backspace)
|
||||
the key items.
|
||||
|
||||
Add-ons: `BB_DLC_DIR=/path/to/dlc` (the launcher's *DLC folder*) holds the dumped add-ons of
|
||||
the game, one folder each (`SPEXPANSIONDLC03/sce_sys/param.sfo`, ...). Their data ships with
|
||||
the 1.09 update, so a folder with only `sce_sys` is enough: `scripts/content_profile.py` lists
|
||||
the add-ons of this title into `out/content.bin` and `sceAppContentGetAddcontInfoList` reports
|
||||
them to the game. The GOTY edition (CUSA03173) needs none.
|
||||
|
||||
**Resolution and preset changes:** for outputs other than 1080p (720p on the Steam Deck,
|
||||
1440p, 4K) the whole game renders at the preset's resolution, set by a patch at start — the
|
||||
fastest path. Changing the output or the preset in the in-game menu then needs *Apply and
|
||||
restart the game*. The *Live resolution changes* setting (launcher, in-game menu,
|
||||
`bbport.ini` `live_resolution=0|1|auto`; **off by default**) instead keeps the game at
|
||||
1080p internally and scales its render targets at run time, so 720p/1080p/1440p/4K and the
|
||||
presets switch without a restart. It costs more: the game then believes it renders 1080p
|
||||
and draws more (e.g. ~8× more small lights), and some targets are copied between sizes —
|
||||
use it on strong desktop GPUs only (`auto` turns it on for discrete GPUs with 8+ GB that are
|
||||
not pre-Turing NVIDIA). 1080p output and TAA always use the live path.
|
||||
|
||||
**TAA:** a separate native-resolution temporal AA mode in the launcher and overlay,
|
||||
switchable live without an FSR model. The saved FSR preset is restored when returning
|
||||
to FSR. FSR Native AA adds reconstruction on top of full-resolution rendering and can
|
||||
be slower than disabling AA. TAA also adds work compared with no temporal AA.
|
||||
The RCAS switch and the 0–2 sharpness control also work with TAA. Sharpening runs after
|
||||
temporal accumulation and leaves its history and HUD unchanged.
|
||||
|
||||
**Mods:** the launcher accepts separate loose-file mod folders (with `dvdroot_ps4/`, an extra
|
||||
wrapper folder, or the game folders such as `chr/` directly; file name case does not matter),
|
||||
with enable switches and load order. A sibling `CUSA03173-mods/` overlay also works.
|
||||
The original game is preserved; later mods override conflicting files.
|
||||
**Third-party patches:** shadPS4-format XML patch files in the data directory's `patches/`,
|
||||
switched on and off in the launcher. See [mods and patches](docs/MODS.md).
|
||||
|
||||
**Launcher language:** Russian, English or Brazilian Portuguese (follows the system language by default).
|
||||
**Game language:** the PS4 system language, *System language* in the launcher; by default it
|
||||
follows the desktop's locale (Japanese, English, French, Spanish, German, Italian or Russian,
|
||||
English for the rest). `run.sh` takes it as `BB_LANGUAGE` (PS4 code: 0 Japanese, 1 English,
|
||||
2 French, 3 Spanish, 4 German, 5 Italian, 8 Russian; without it, the desktop's locale as above).
|
||||
|
||||
**Free camera and game debug menu** (v1.09): enable the corresponding switches in the
|
||||
launcher or in-game menu and restart. Free camera uses Lance McDonald's
|
||||
[GoldHEN patch](https://github.com/GoldHEN/GoldHEN_Patch_Repository/blob/main/patches/xml/Bloodborne-Orbis.xml):
|
||||
hold Cross and press L3 to cycle modes (keyboard: hold Space and press Z). It needs no fonts
|
||||
and conflicts with *Enemy Control*.
|
||||
For the game debug menu, install `DbgFont14h.ccm` and `DbgFont14h.tpf` from
|
||||
[Debug Menu and XML Patch](https://www.nexusmods.com/bloodborne/mods/253) into the game's
|
||||
`dvdroot_ps4/font/` first. Startup rejects missing or empty font files instead of launching
|
||||
the unsafe patch. Open it with the left touchpad / Tab (with the debug menu on, the left half
|
||||
no longer opens the gestures); Backspace is the right touchpad.
|
||||
Touch coordinates are forwarded from SDL gamepads; Back/Select emulates a left click on
|
||||
pads without a touch surface. The port's settings menu remains Insert / L3+R3.
|
||||
|
||||
GPU occlusion queries still use synthetic pixel counters (`PixelPipeStatDump`), and
|
||||
`IT_SET_PREDICATION` is unimplemented. Free camera allows visual investigation; it does
|
||||
not implement GPU occlusion culling.
|
||||
|
||||
**Upscaler assets** (not included; FSR 3.1 needs none):
|
||||
|
||||
```bash
|
||||
bash tools/fetch_fsr4_assets.sh # FSR 4 v07 (MIT, built from AMD's source by Q2RTX)
|
||||
# FSR 4.1.1, from your own AMD DLLs (e.g. OptiScaler's FSR4_LATEST), needs GE-Proton 10 or newer:
|
||||
bash tools/fsr4cap/build_assets.sh <amd_fidelityfx_upscaler_dx12.dll>
|
||||
```
|
||||
|
||||
**FSR 4.1.1 from your own DLL.** The easiest way is the launcher: *Upscaler → FSR 4.1.1 from your
|
||||
own AMD DLL → Choose DLL…*, then pick `amd_fidelityfx_upscaler_dx12.dll` version 4.1.x: from
|
||||
OptiScaler's `FSR4_LATEST` folder or from a game with FSR 4.1. That one file is enough: AMD's DLL
|
||||
exports the FidelityFX API itself, so no loader is needed (only a DLL without these exports would
|
||||
be recorded through `amd_fidelityfx_loader_dx12.dll`, and the launcher would ask for it). The DLL is checked at once,
|
||||
without a long capture; these do not fit: AMD's official FSR 4.0.x (e.g. Pragmata's 4.0.3 —
|
||||
AMD enables it on RDNA4 only, and under Proton it does not start on other GPUs) and community
|
||||
builds (4.0.2b, 4.1.1b and similar OptiScaler INT8 builds: another model and pass count). Then
|
||||
the DLL runs under Proton and is recorded at 20 sizes (progress in the row and in the *Log* tab;
|
||||
on RDNA4 a second time, for the FP8 variant),
|
||||
the passes are translated to SPIR-V, and FSR 4.1.1 is selected. The Proton build is picked
|
||||
automatically — the first under which the DLL enables FSR 4.1: GE-Proton 10 or newer,
|
||||
Proton-CachyOS, Proton Experimental (tested: GE-Proton 11, Proton-CachyOS 11, Experimental of
|
||||
October 2026; Steam's Proton 11.0 and GE-Proton 9 do not). It runs in the Steam runtime it
|
||||
requires (Steam installs it the first time any game runs with that Proton), or through
|
||||
umu-launcher when installed. On NixOS umu-launcher comes from `nix-shell` (downloaded the first
|
||||
time, ~1.7 GB); the AppImage runs the recording on the system itself, through the user's systemd
|
||||
(`systemd-run --user`), since the system's `/nix` is out of its sight. Recording on the Steam Deck is not verified yet; if the DLL does not
|
||||
enable FSR 4.1 there, build on a PC and copy the `fsr4_411` folder. The same from the command
|
||||
line: the launcher's (and the AppImage's) `--build-fsr411 <DLL>`.
|
||||
|
||||
From source the script takes its tools from the system (MinGW GCC, CMake, Ninja, Python 3,
|
||||
SPIRV-Tools, Git) or from Nix; the AppImage has them prebuilt.
|
||||
|
||||
**FP8 on RDNA4.** When vkd3d-proton offers FP8 cooperative matrices (RDNA4), the DLL runs
|
||||
another variant of its passes: other shaders and weights, other dispatch sizes. The build records
|
||||
both, INT8 into `fsr4_411/` and FP8 into `fsr4_411/fp8/`, and the game picks FP8 itself when the
|
||||
GPU has FP8 matrices (`VK_EXT_shader_float8`); the log says `Upscaler: FSR 4.1.1 replay, FP8 …`.
|
||||
`BB_FSR411_VARIANT=int8` forces INT8. Checked on an RX 7800 XT through vkd3d-proton's FP16
|
||||
emulation of FP8 (`BB_FSR4CAP_FP8=1` records it there too, `fp8emu/`): bit-exact with the DLL;
|
||||
**not yet run on RDNA4 hardware.**
|
||||
|
||||
**AppImage** (Steam Deck): `bash build.sh && bash packaging/appimage.sh` →
|
||||
`dist/Bloodborne-bbport-x86_64.AppImage`; data in `~/.local/share/bbport`, `--play` starts the
|
||||
game without the launcher window (Game Mode). FSR 4.1.1 models are not packaged: build them with
|
||||
the launcher's button (see above); they go to `~/.local/share/bbport/fsr4_411`
|
||||
(`BB_PACKAGE_FSR411=1` bundles a local `fsr4_411` into an AppImage for your own devices). On the Steam Deck pick the 1280×720 output (the
|
||||
game is 16:9; on the 1280×800 screen it gets thin bars).
|
||||
|
||||
**Adding the AppImage to Steam** (*Add a Non-Steam Game*) needs no options; the compatibility tool
|
||||
does not matter. (Steam preloads its overlay into every non-Steam game; the AppImage removes it
|
||||
before its own programs start, so the Steam overlay is not shown in the game.) Where Steam
|
||||
runs games without FUSE (NixOS: Steam's FHS sandbox; the AppImage then exits with *Cannot mount
|
||||
AppImage*), set the launch options to
|
||||
|
||||
```
|
||||
TMPDIR=$HOME/.cache APPIMAGE_EXTRACT_AND_RUN=1 NO_CLEANUP=1 %command%
|
||||
```
|
||||
|
||||
The AppImage then unpacks itself (~2 GB, `~/.cache/appimage_extracted_*`) on the first start
|
||||
(~10 s) and reuses that copy afterwards; a new AppImage version gets a new copy, the old one can
|
||||
be deleted. Without `TMPDIR` it would unpack into Steam's `/tmp`, which is in RAM there. Add
|
||||
` --play` after `%command%` to skip the launcher.
|
||||
|
||||
**NVIDIA in the AppImage:** startup discovers the host's installed 64-bit NVIDIA Vulkan ICD
|
||||
and exposes its vendor libraries alongside the bundled AMD/Intel drivers. This keeps the
|
||||
NVIDIA userspace driver matched to the host kernel module. Standard Linux distributions keep
|
||||
these libraries under `/usr/lib*`; on NixOS the AppImage's internal `/nix/store` may hide them.
|
||||
In that case copy the NVIDIA libraries into an accessible directory and set
|
||||
`BB_NVIDIA_LIB_DIR=/path/to/libraries` (the NVIDIA manifest must also be accessible).
|
||||
Explicit `VK_DRIVER_FILES`/`VK_ICD_FILENAMES` overrides are preserved. Diagnose drivers inside
|
||||
the package with:
|
||||
|
||||
```bash
|
||||
./Bloodborne-bbport-x86_64.AppImage --vulkan-info 2>&1 | tee bbport-vulkan.log
|
||||
```
|
||||
|
||||
A user reported successful startup with FSR 3 on a GTX 1060 6GB (Fedora 44, NVIDIA
|
||||
580.178.04); selecting FSR 4 caused a black window. Use FSR 3 on this configuration.
|
||||
|
||||
MangoHud is bundled in the AppImage; enable its checkbox in the launcher. If MangoHud is also
|
||||
installed system-wide, or Steam's performance overlay is on (Steam Deck game mode), only one
|
||||
overlay is drawn (two drew doubled, offset text).
|
||||
When running from source, install MangoHud separately. A diagnostic launch with
|
||||
`VK_LOADER_LAYERS_DISABLE=~implicit~` also disables MangoHud.
|
||||
|
||||
Useful variables: `BB_FRAME_STATS=1` (frame statistics, including a `Memory:` line: VRAM, GTT,
|
||||
RSS, images and guest blocks in VRAM), `BB_PC_MODEL=1` (the new memory and translation model,
|
||||
AMD only; 0, the old model as in 0.3, is the default), `BB_ANISO=N` (anisotropic filtering of scene textures; 16 by
|
||||
default, 0 = the game's own),
|
||||
`BB_GC_IDLE_SECONDS=N` / `BB_VRAM_IDLE_SECONDS=N` (how long unused textures / buffers stay in VRAM;
|
||||
20 / 60), `BB_BREADCRUMBS=0` (no GPU breadcrumbs; with them a GPU hang names the draw or dispatch
|
||||
it is stuck in), `BB_GPU_PROFILE=1` (GPU time per
|
||||
pass), `BB_FSR4_PROFILE=1` (GPU time per FSR 4 pass), `BB_UPSCALER=taa|fsr3|fsr4|fsr411|off|none`,
|
||||
`BB_FRAMES_AHEAD=N` (how many frames the GPU command thread may run ahead of the GPU; 1 by default,
|
||||
0 = unbounded), `BB_PRESENT_THREAD=0` (present on the vblank thread, as before),
|
||||
`BB_LIVE_RES=1` (live resolution changes instead of the startup patch for outputs other than 1080p),
|
||||
`BB_PAD_RECORD=file` / `BB_PAD_REPLAY=file` (record a route with F9, replay it in scripted tests),
|
||||
`BB_GC_BUDGET_MB=N` (texture cache budget, as on integrated GPUs), `BB_PRESENT_DUMP_TRIGGER=file`
|
||||
with `BB_PRESENT_DUMP_COUNT=N` (dump N consecutive presented frames),
|
||||
`BB_FSR411_VARIANT=int8|fp8|fp8emu` (FSR 4.1.1 variant; by default FP8 where the GPU has FP8
|
||||
matrices), `BB_READBACKS=0|1|2` (reads of GPU-written memory by the game: 1 by default, 2
|
||||
precise and slow, 0 off).
|
||||
More in [docs/](docs); recent changes: [docs/CHANGES_0.4.md](docs/CHANGES_0.4.md) (in Russian),
|
||||
[docs/CHANGES_2026-10-06.md](docs/CHANGES_2026-10-06.md).
|
||||
|
||||
## Repository layout
|
||||
|
||||
| Path | Contents |
|
||||
|---|---|
|
||||
| `src/` | Loader (`probe.c`) and the HLE runtime |
|
||||
| `scripts/` | Offline preparation of the game image, module linking, patch compiler |
|
||||
| `gpu/` | Renderer library: vendored shadPS4 video core with this port's changes (`gpu/VENDOR.txt`), shims, ImGui menu, FSR 4.1.1 runtime (`gpu/shadps4/video_core/renderer_vulkan/fsr411`) |
|
||||
| `launcher/`, `packaging/` | GTK4 launcher; Nix package and AppImage |
|
||||
| `patches/` | Community patches for Bloodborne |
|
||||
| `tools/` | Developer tools: scripted runs, A/B toggles, FSR benchmark helpers, FSR 4 shader rewrites, `fsr4cap` (FSR 4.1.1 recording/extraction) |
|
||||
| `tests/` | Loader, runtime, patch and renderer tests |
|
||||
| `docs/` | Design notes and measurements ([upscaler](docs/upscaler.md), [parallel GPU](docs/parallel_gpu.md), [motion vectors](docs/motion_vectors.md), [roadmap](docs/ROADMAP.md)) |
|
||||
|
||||
Tests: `bash build.sh --test`, `python3 -m unittest discover -s tests`, and
|
||||
`ninja -C out/gpu motion-history-test ui-composition-test scene-resolution-test motion-shader-test settings-test`.
|
||||
|
||||
## Roadmap
|
||||
|
||||
- The new memory and translation model on by default once it is stable, NVIDIA included; the
|
||||
old model is removed after that.
|
||||
- The GPU reads resource descriptors itself (bindless), textures are objects created at load
|
||||
time: no shader code or constant engine executed on the CPU, no texture cache keyed by
|
||||
address.
|
||||
- Shaders translated ahead of time, at install, not during play.
|
||||
- More CPU parallelism in GPU command processing (split the draw-recording stage further),
|
||||
scaling to all hardware threads — most important for the Steam Deck.
|
||||
- Async compute for the upscaler (the frame is GPU-bound at 4K).
|
||||
- XeSS (super resolution) and XeFG frame generation through a Wine helper sharing Vulkan
|
||||
memory (a memory-bridge prototype is in `tools/bridge_helper`); DLSS for NVIDIA users;
|
||||
inputs exposed so that OptiScaler-style mapping works.
|
||||
- Frame generation (FSR 3.1 FG first), reactive and transparency masks for particles and fog.
|
||||
- Fix the races in AMD's FSR 4.1.1 shaders at output widths that are not multiples of 64
|
||||
(e.g. 1600×900), as already done for the left-edge race in FSR 4 v07 at 1080p.
|
||||
- Steam Deck validation of the AppImage; HDR output.
|
||||
|
||||
## Credits and licenses
|
||||
|
||||
bbport is licensed under the **GNU GPL v2 or later** ([LICENSE](LICENSE)) — it contains code
|
||||
from shadPS4 (GPL-2.0-or-later). Third-party components keep their licenses:
|
||||
[shadPS4](https://github.com/shadps4-emu/shadPS4) video core and shader recompiler (GPL-2.0+),
|
||||
[sirit](https://github.com/shadps4-emu/sirit), [half](https://half.sourceforge.net/),
|
||||
[FSR-Vulkan](https://github.com/FireBurn/FSR-Vulkan) by FireBurn (MIT; FSR 3.1 on Vulkan and the
|
||||
FSR 4 v07 provider), AMD FidelityFX SDK (MIT), [LibAtrac9](https://github.com/Thealexbarney/LibAtrac9)
|
||||
(MIT), [Dear ImGui](https://github.com/ocornut/imgui) (MIT), DejaVu fonts,
|
||||
[dxil-spirv](https://github.com/HansKristian-Work/dxil-spirv) (MIT, used to build the
|
||||
FSR 4.1.1 assets). Game patches by Kyo, Lance McDonald, auser1337, illusion, emoose and other
|
||||
community members (`patches/Bloodborne.xml`). AMD's FSR 4 DLLs and model data are not
|
||||
distributed here.
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
# bbport — нативный порт Bloodborne для Linux
|
||||
|
||||
[English](README.md) · **Русский**
|
||||
> README оригинального проекта (deadinside28/bloodborne_pc), сохранён ради технических
|
||||
> подробностей. README этого форка — [README.md](README.md) (на английском).
|
||||
|
||||
[English](BBPORT.md) · **Русский**
|
||||
|
||||
bbport — это аналог Wine + DXVK, только для одной игры: *Bloodborne* для PlayStation 4
|
||||
(CUSA03173, версия игры 1.09) на ПК с Linux x86-64. Оригинальный исполняемый файл игры
|
||||
@@ -1,354 +1,128 @@
|
||||
THIS PROJECT IS NOT RELATED TO SHADPS4. ALL QUESTIONS RELATED TO THIS PROJECT SHOULD BE SENT TO THE DISCORD SERVER https://discord.gg/KYZRKk9CB, NOT TO THE SHADPS4 SERVER.
|
||||
# Bloodborne for Linux
|
||||
|
||||
The PlayStation 4 version of *Bloodborne* running natively on Linux. The game's own x86-64 code
|
||||
runs directly on the CPU and its graphics are translated to Vulkan. It is not an emulator.
|
||||
|
||||
# bbport — a native Linux port of Bloodborne
|
||||
This fork builds on [bbport](https://github.com/deadinside28/bloodborne_pc), the native Linux port
|
||||
by deadinside28. It adds the features of [Supermedo's Windows port](https://github.com/Supermedo/bloodborne_pc)
|
||||
(online play, DLSS, cheats) brought to Linux, plus a few of its own.
|
||||
|
||||
**English** · [Русский](README.ru.md)
|
||||
> **No game files are included.** You need your own decrypted dump of Bloodborne, US
|
||||
> (`CUSA03173`) or EU (`CUSA00900`), game version **1.09**. This project is not affiliated with
|
||||
> Sony Interactive Entertainment or FromSoftware.
|
||||
|
||||
bbport is the counterpart of Wine + DXVK for a single game: *Bloodborne* for PlayStation 4
|
||||
(CUSA03173, game version 1.09) on an x86-64 Linux PC. The game's original executable runs
|
||||
directly on the PC:
|
||||
## Features
|
||||
|
||||
- **as in Wine**, the game's x86-64 code runs on the CPU directly, and a runtime written for
|
||||
this one game replaces the PS4 system libraries;
|
||||
- **as in DXVK**, the game's graphics are translated to Vulkan — by a renderer derived from
|
||||
[shadPS4](https://github.com/shadps4-emu/shadPS4) and heavily extended for this game, including
|
||||
temporal upscaling with AMD FSR 3.1, FSR 4 and FSR 4.1.1;
|
||||
- memory and the GPU's work are moving to the PC model: the launcher has an experimental
|
||||
*New memory and translation model* mode (AMD GPUs only); without it the game runs on the old
|
||||
memory model, as in 0.3.
|
||||
|
||||
Two steps remain to the full Wine + DXVK model: make the new mode the default, and move the
|
||||
reading of resource descriptors (textures, buffers) from the CPU to the GPU (see below).
|
||||
|
||||
> **No game files are included.** You need your own dump of Bloodborne (CUSA03173, v1.09).
|
||||
> This project is not affiliated with Sony Interactive Entertainment, FromSoftware or AMD.
|
||||
|
||||
**Status: experimental, playable.** The game boots, loads saves and plays (the Hunter's Dream
|
||||
and several areas of Yharnam were played with it) with sound, gamepad and saving.
|
||||
A full play-through has not been verified, and only one machine (Linux, AMD Radeon RX 7800 XT,
|
||||
Mesa/RADV) has been tested thoroughly.
|
||||
|
||||
## Highlights
|
||||
|
||||
- **Native execution.** The eboot is converted offline into a flat memory image; PS4 libc and
|
||||
libSceFios2 are linked into it as native code. No CPU emulation and no per-instruction
|
||||
translation: the game code runs at full speed.
|
||||
- **Two modes** (launcher → *Mode*):
|
||||
- **Switch off (the default) — as in 0.3.** The old memory model: VRAM copies of the game's
|
||||
memory, writes tracked through page protection. With every fix made since 0.3 (motion
|
||||
vectors and the upscaler, flicker with DoF on, a damaged shader cache).
|
||||
- **New memory and translation model — experimental, AMD GPUs only.** The game's memory lives
|
||||
in system RAM and the GPU reads it where it is, as a PC game's buffers; data it reads often
|
||||
is kept in VRAM and given back when unused (textures after 20 s, buffers after 60 s). No
|
||||
write tracking, no copies of the whole GPU-visible memory. The PS4 command processor's work
|
||||
is translated into Vulkan commands rather than emulated on the CPU: memory writes
|
||||
(`WRITE_DATA`, DMA) are done by the GPU in command-stream order, and fences are written once
|
||||
the GPU has really finished the work. Where the game is CPU-bound it runs 20–25% faster
|
||||
(measured standing in the Hunter's Dream), with fewer stutters. **It may crash**, and has
|
||||
been tested thoroughly only on the author's PC (RX 7800 XT). On NVIDIA and Intel the switch
|
||||
is unavailable: NVIDIA's driver cannot map the game's memory as needed, Intel is untested.
|
||||
Without the launcher: `BB_PC_MODEL=1`; to try it on another GPU: `BB_PC_MODEL_ANY_GPU=1`.
|
||||
|
||||
Unused textures are freed in both modes, so VRAM no longer grows with every area visited.
|
||||
- **Unlocked frame rate.** Community patches (`patches/Bloodborne.xml`) make the simulation
|
||||
use the real frame time; ~90 FPS at 4K with FSR 4 Balanced on an RX 7800 XT, ~150 FPS at
|
||||
1440p with FSR 4 Quality. Also 30/60/90 FPS modes.
|
||||
- **Temporal upscaling built for this game.** Bloodborne has no velocity buffer, so bbport
|
||||
computes motion vectors itself: camera motion from depth and the scene matrices, and object
|
||||
motion (characters, cloth, weapons) from the vertex positions of the previous frame. The
|
||||
scene is jittered sub-pixel (Halton) and rendered at a reduced resolution; the upscaler fills
|
||||
the output (720p for the Steam Deck, 1080p, 1440p or 2160p) and the UI is drawn natively at the output resolution.
|
||||
- **FSR 3.1** (FireBurn/FSR-Vulkan).
|
||||
- **FSR 4 (INT8, model v07)** on GPUs exposing the required Vulkan shader features —
|
||||
RDNA2/3 included (see Requirements).
|
||||
- **FSR 4.1.1**: AMD's 4.1.1 DLL is recorded once under vkd3d-proton and its passes are
|
||||
replayed natively on Vulkan; the output is **bit-exact** with the DLL. Two variants, as in
|
||||
the DLL: INT8 on any GPU with the required shader features, and FP8 matrices on RDNA4 (RX
|
||||
9000; picked automatically). The assets are built on your machine from one DLL of your own
|
||||
(4.1.x): the launcher's *FSR 4.1.1 from your own AMD DLL → Choose DLL…* button (2–5 minutes,
|
||||
twice that on RDNA4; needs a recent Proton), or `tools/fsr4cap`.
|
||||
- Faster than AMD's own shaders on RDNA3: the final passes of FSR 4 and 4.1.1 were rewritten
|
||||
to store through workgroup memory (3.5× and 2.3× faster, bit-exact); FSR 4 costs ~4 ms at
|
||||
4K on an RX 7800 XT instead of ~6 ms.
|
||||
- **Multi-threaded GPU command processing.** The PS4 command stream is decoded on one thread
|
||||
and draws are bound and recorded on another (two-stage pipeline), with a Vulkan recording
|
||||
thread and helper threads for memory copies. Early on the single GPU thread capped the game
|
||||
at ~26 FPS; now it runs at 90–150 FPS depending on resolution and scene.
|
||||
- **In-game menu** (Insert or L3+R3): upscaler, preset, sharpness, output resolution, game
|
||||
effects (chromatic aberration, DoF, motion blur, SSAO, the game's own AA, SSR, model LOD).
|
||||
It opens where it was left, with the mouse cursor shown over it.
|
||||
- **GTK4 launcher** and an **AppImage** for the Steam Deck.
|
||||
|
||||
## How it differs from shadPS4
|
||||
|
||||
| | shadPS4 | bbport |
|
||||
|---|---|---|
|
||||
| Scope | General PS4 emulator, many games | One game: Bloodborne v1.09 |
|
||||
| Loading | Its own ELF loader and kernel emulation at run time | The eboot is converted offline (`scripts/`) into an image with PS4 libc/Fios2 linked in; a C loader maps it and jumps into the game (loader and runtime: ~5k lines) |
|
||||
| Memory | The GPU's view of PS4 memory is kept in VRAM copies, synchronized through page-protection write tracking | By default the same model (as in 0.3); in the *New memory and translation model* mode (AMD): the game's memory in system RAM, used by the GPU in place, frequently read data in VRAM, freed when unused |
|
||||
| Command processor | Emulated: memory writes, DMA and fences are done by the CPU while decoding | By default the same; in the new mode translated into Vulkan commands that the GPU runs in stream order, fences written after the work has really finished |
|
||||
| System libraries | Broad HLE of the PS4 OS | A small runtime (`src/runtime_*.c`) that implements exactly what Bloodborne calls: memory, threads, sync, files, audio (incl. ATRAC9), pad, saves, AppContent |
|
||||
| GPU | shadPS4 video core and shader recompiler | The same core (vendored, GPL) with ~200 marked changes (`bbport:`) plus new modules: two-stage draw pipeline, render-state and texture-set memoization, render-scale proxies, motion vectors, FSR 3.1/4/4.1.1, frame capture and GPU profiler |
|
||||
| GPU thread | One thread processes the whole command stream (the bottleneck in Bloodborne) | Decode and draw recording run on separate threads; the work scales with the hardware threads (Steam Deck included) |
|
||||
| Upscaling | — | Temporal (FSR 3.1, FSR 4, FSR 4.1.1) with the game's own motion vectors and jitter |
|
||||
| Game patches | Patch files applied by the emulator | The same community patches, compiled at start (`scripts/patches.py`); render resolution, effects and FPS from the launcher |
|
||||
|
||||
Without shadPS4 there would be no bbport: its renderer and shader recompiler are the base of
|
||||
the graphics side.
|
||||
|
||||
### Wine + DXVK for one game
|
||||
|
||||
- **CPU.** The PS4 CPU is x86-64, so the game's code runs directly on the PC's CPU, with no
|
||||
emulation and no instruction translation.
|
||||
- **System libraries.** As Wine replaces the Windows API, the bbport runtime (`src/runtime_*.c`)
|
||||
implements exactly the PS4 OS functions Bloodborne calls: memory, threads, files, audio, pad,
|
||||
saves.
|
||||
- **Memory.** In the new mode, as in a PC game: the game's data in system RAM, VRAM used the way
|
||||
a PC game uses it. The mode is still experimental and runs on AMD GPUs only.
|
||||
- **Graphics.** The PS4 GPU's command stream (PM4) is a recording of the game's graphics API
|
||||
calls: it is written by 99 functions of the statically linked libGnm, so decoding the stream
|
||||
and translating the calls themselves come to the same thing. GCN shaders are translated to
|
||||
SPIR-V. In the new mode the command processor's work (memory writes, DMA, fences) is
|
||||
translated into Vulkan commands as well. One part still works the old way: the translator
|
||||
reads resource descriptors (textures, buffers) from the game's memory on the CPU, and
|
||||
recognises textures by address. The next big step is the GPU reading the descriptors itself
|
||||
(bindless), with textures as objects created at load time.
|
||||
|
||||
"Port" here means a build for this one game, not a rewrite of its source code, which the project
|
||||
neither has nor includes.
|
||||
- **Online play (beta)** through [The Hunter's Dream](https://thehuntersdream.com) and shadNet:
|
||||
messages, bloodstains, ghosts, bells and summons. Linux and Windows players can play together.
|
||||
- **Upscaling:** AMD FSR 3.1, FSR 4 and FSR 4.1.1, **NVIDIA DLSS** on GeForce RTX, and
|
||||
native-resolution TAA.
|
||||
- **Unlocked frame rate**, capped at your display's refresh rate up to 120 FPS (above that the
|
||||
game's own movement timing breaks), or 30/60/90 FPS. Output from 720p to 4K.
|
||||
- **Launcher** with every setting in one window, in English, Spanish, Italian, French, German,
|
||||
Portuguese and Russian, like the in-game menu (Insert or L3+R3).
|
||||
- **Xbox, Switch and keyboard button prompts**, picked by the controller in use, and a gestures
|
||||
menu without the motion-control icons.
|
||||
- **Cheats and gameplay tweaks:** never die, stealth, silent steps, enemy control, camera
|
||||
distance, ragdoll physics and more.
|
||||
- **Game effects** on and off, **mods** and community **patches**, loaded without changing the
|
||||
game files.
|
||||
- **Add to Steam** in one click, and **Discord Rich Presence**.
|
||||
- **AppImage:** one file with everything inside, for any distribution and the Steam Deck.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Linux x86-64, a Vulkan 1.3 GPU. Tested: AMD RX 7800 XT with Mesa 26 (RADV).
|
||||
The *New memory and translation model* mode needs an AMD GPU.
|
||||
FSR 4 / 4.1.1 require shader Float16, Int8/Int16, integer dot products, linear compute
|
||||
derivatives and extended storage image formats; FSR 4.1.1 additionally requires
|
||||
`VK_VALVE_shader_mixed_float_dot_product`. Unsupported choices fall back to FSR 3.1
|
||||
before the first frame and are disabled in the in-game menu.
|
||||
- Your decrypted game dump: the `CUSA03173` folder (eboot.bin, sce_module, ...), version 1.09.
|
||||
A dumped update is a separate folder: copy it over the base game, replacing files. The base
|
||||
game alone (1.00) crashes at start (guest offset 0x20348b8); the launcher and `run.sh` check
|
||||
the executable and say what is missing (`BB_SKIP_GAME_CHECK=1` skips the check).
|
||||
- To build: GCC, CMake, Ninja, Python 3, glslang, SDL3, Vulkan headers and the libraries in
|
||||
`shell.nix`. With [Nix](https://nixos.org) everything comes from `shell.nix` automatically.
|
||||
- Linux x86-64 and a GPU with Vulkan 1.3: AMD or Intel with Mesa, or NVIDIA with NVIDIA's driver
|
||||
(open kernel modules included). Mesa's NVK has not been tested.
|
||||
- DLSS needs a GeForce RTX (20 series or newer) and NVIDIA's driver; it does not work with NVK.
|
||||
- About 6 GB of free memory, 10 GB for 1440p or 4K output.
|
||||
|
||||
## Build and run
|
||||
## Getting started
|
||||
|
||||
### AppImage
|
||||
|
||||
Build it with `bash build.sh && bash packaging/appimage-sharun.sh`, then run
|
||||
`dist/Bloodborne-bbport-x86_64.AppImage`. Choose your game folder (the one with `eboot.bin`) and
|
||||
press **Play**. Saves and settings go to `~/.local/share/bbport`.
|
||||
|
||||
### From source
|
||||
|
||||
On Arch Linux (or CachyOS, Manjaro…):
|
||||
|
||||
```bash
|
||||
git clone --recursive https://github.com/deadinside28/bloodborne_pc.git bbport && cd bbport
|
||||
bash build.sh # builds out/bb-probe and out/gpu/libbbgpu.so
|
||||
BB_GAME_DIR=/path/to/CUSA03173 bash run.sh
|
||||
sudo pacman -S --needed base-devel cmake ninja python python-gobject gtk4 libadwaita sdl3 \
|
||||
vulkan-headers vulkan-icd-loader glslang spirv-headers spirv-cross ffmpeg boost fmt \
|
||||
robin-map xxhash zydis miniz protobuf nlohmann-json openssl miniupnpc python-pillow ttf-dejavu
|
||||
paru -S magic_enum vulkan-memory-allocator xbyak # from the AUR
|
||||
|
||||
git clone --recursive https://github.com/ayozetr/bloodborne_linux
|
||||
cd bloodborne_linux
|
||||
bash build.sh
|
||||
python3 launcher/bbport_launcher.py # or launcher/install-desktop.sh for a menu entry
|
||||
```
|
||||
|
||||
or the launcher (pick the game folder, settings, *Start*):
|
||||
With [Nix](https://nixos.org), `bash build.sh` gets every dependency from `shell.nix` by itself.
|
||||
|
||||
```bash
|
||||
bash launcher/bb-launcher.sh # launcher/install-desktop.sh adds it to the app menu
|
||||
```
|
||||
### Online play
|
||||
|
||||
By default the game folder is expected next to the repository (`../CUSA03173`). Saves and the
|
||||
shader cache go to `user/` (the launcher lets you choose another folder); settings to
|
||||
`bbport.ini`. A gamepad is used through SDL3 (the launcher's *Controls → Controller* picks one
|
||||
when several are connected; `BB_GAMEPAD=<GUID or part of the name>`). The keyboard works too,
|
||||
also next to a connected gamepad (the Steam Deck always has one); both are remapped in the
|
||||
launcher (*Controls*). The character name is typed on the keyboard in a box over the game.
|
||||
The touchpad: its left half (Tab, Back/Select) opens the gestures, the right half (Backspace)
|
||||
the key items.
|
||||
1. Register a shadNet account at [shadnet.shadps4.net](https://shadnet.shadps4.net).
|
||||
2. In the launcher, open **Online**, turn on **Play online** and enter your account name (not
|
||||
your email) and password. **Test the connection**: all three servers should be reachable.
|
||||
3. Start the game and choose online at the title screen.
|
||||
|
||||
**Resolution and preset changes:** for outputs other than 1080p (720p on the Steam Deck,
|
||||
1440p, 4K) the whole game renders at the preset's resolution, set by a patch at start — the
|
||||
fastest path. Changing the output or the preset in the in-game menu then needs *Apply and
|
||||
restart the game*. The *Live resolution changes* setting (launcher, in-game menu,
|
||||
`bbport.ini` `live_resolution=0|1|auto`; **off by default**) instead keeps the game at
|
||||
1080p internally and scales its render targets at run time, so 720p/1080p/1440p/4K and the
|
||||
presets switch without a restart. It costs more: the game then believes it renders 1080p
|
||||
and draws more (e.g. ~8× more small lights), and some targets are copied between sizes —
|
||||
use it on strong desktop GPUs only (`auto` turns it on for discrete GPUs with 8+ GB that are
|
||||
not pre-Turing NVIDIA). 1080p output and TAA always use the live path.
|
||||
Everyone in a session needs game version 1.09 and the same servers. Turn cheats off when you
|
||||
play with others.
|
||||
|
||||
**TAA:** a separate native-resolution temporal AA mode in the launcher and overlay,
|
||||
switchable live without an FSR model. The saved FSR preset is restored when returning
|
||||
to FSR. FSR Native AA adds reconstruction on top of full-resolution rendering and can
|
||||
be slower than disabling AA. TAA also adds work compared with no temporal AA.
|
||||
The RCAS switch and the 0–2 sharpness control also work with TAA. Sharpening runs after
|
||||
temporal accumulation and leaves its history and HUD unchanged.
|
||||
### DLSS
|
||||
|
||||
**Mods:** the launcher accepts separate loose-file mod folders (with `dvdroot_ps4/`, an extra
|
||||
wrapper folder, or the game folders such as `chr/` directly; file name case does not matter),
|
||||
with enable switches and load order. A sibling `CUSA03173-mods/` overlay also works.
|
||||
The original game is preserved; later mods override conflicting files.
|
||||
**Third-party patches:** shadPS4-format XML patch files in the data directory's `patches/`,
|
||||
switched on and off in the launcher. See [mods and patches](docs/MODS.md).
|
||||
On a PC with a GeForce RTX, run `bash tools/build_dlss.sh` once after `build.sh`. It downloads
|
||||
what it needs from the NVIDIA DLSS SDK and builds the bridge. Then choose **DLSS** as the
|
||||
upscaler.
|
||||
|
||||
**Launcher language:** Russian, English or Brazilian Portuguese (follows the system language by default).
|
||||
### DLC
|
||||
|
||||
**Free camera and game debug menu** (v1.09): enable the corresponding switches in the
|
||||
launcher or in-game menu and restart. Free camera uses Lance McDonald's
|
||||
[GoldHEN patch](https://github.com/GoldHEN/GoldHEN_Patch_Repository/blob/main/patches/xml/Bloodborne-Orbis.xml):
|
||||
hold Cross and press L3 to cycle modes (keyboard: hold Space and press Z). It needs no fonts
|
||||
and conflicts with *Enemy Control*.
|
||||
For the game debug menu, install `DbgFont14h.ccm` and `DbgFont14h.tpf` from
|
||||
[Debug Menu and XML Patch](https://www.nexusmods.com/bloodborne/mods/253) into the game's
|
||||
`dvdroot_ps4/font/` first. Startup rejects missing or empty font files instead of launching
|
||||
the unsafe patch. Open it with the left touchpad / Tab (with the debug menu on, the left half
|
||||
no longer opens the gestures); Backspace is the right touchpad.
|
||||
Touch coordinates are forwarded from SDL gamepads; Back/Select emulates a left click on
|
||||
pads without a touch surface. The port's settings menu remains Insert / L3+R3.
|
||||
The Game of the Year edition (`CUSA03173`) already includes The Old Hunters. With the base game
|
||||
(such as the EU `CUSA00900`), dump your add-ons too: each one is a folder (`SPEXPANSIONDLC03`,
|
||||
`SPDLCMESSENGER00`…) with only `sce_sys` inside, because their data ships with the 1.09 update.
|
||||
Put them in one folder and pick it in the launcher (**Game → DLC folder**), or set
|
||||
`BB_DLC_DIR=/path/to/dlc` for `run.sh`. The launcher shows how many it found.
|
||||
|
||||
GPU occlusion queries still use synthetic pixel counters (`PixelPipeStatDump`), and
|
||||
`IT_SET_PREDICATION` is unimplemented. Free camera allows visual investigation; it does
|
||||
not implement GPU occlusion culling.
|
||||
### Button prompts
|
||||
|
||||
**Upscaler assets** (not included; FSR 3.1 needs none):
|
||||
The game shows PlayStation buttons. In the launcher, **Controls → Button prompts** switches them to
|
||||
Xbox, Nintendo Switch or keyboard ones; *Automatic* follows the controller in use, and the
|
||||
keyboard's when none is connected. Keyboard prompts show the keys bound in **Controls →
|
||||
Keyboard**. **Gestures menu without motion-control icons** cleans up the gestures menu. The
|
||||
prompts are put into your own game's menu files at each start, and nothing in the game folder
|
||||
changes.
|
||||
|
||||
```bash
|
||||
bash tools/fetch_fsr4_assets.sh # FSR 4 v07 (MIT, built from AMD's source by Q2RTX)
|
||||
# FSR 4.1.1, from your own AMD DLLs (e.g. OptiScaler's FSR4_LATEST), needs GE-Proton 10 or newer:
|
||||
bash tools/fsr4cap/build_assets.sh <amd_fidelityfx_upscaler_dx12.dll>
|
||||
```
|
||||
## Credits
|
||||
|
||||
**FSR 4.1.1 from your own DLL.** The easiest way is the launcher: *Upscaler → FSR 4.1.1 from your
|
||||
own AMD DLL → Choose DLL…*, then pick `amd_fidelityfx_upscaler_dx12.dll` version 4.1.x: from
|
||||
OptiScaler's `FSR4_LATEST` folder or from a game with FSR 4.1. That one file is enough: AMD's DLL
|
||||
exports the FidelityFX API itself, so no loader is needed (only a DLL without these exports would
|
||||
be recorded through `amd_fidelityfx_loader_dx12.dll`, and the launcher would ask for it). The DLL is checked at once,
|
||||
without a long capture; these do not fit: AMD's official FSR 4.0.x (e.g. Pragmata's 4.0.3 —
|
||||
AMD enables it on RDNA4 only, and under Proton it does not start on other GPUs) and community
|
||||
builds (4.0.2b, 4.1.1b and similar OptiScaler INT8 builds: another model and pass count). Then
|
||||
the DLL runs under Proton and is recorded at 20 sizes (progress in the row and in the *Log* tab;
|
||||
on RDNA4 a second time, for the FP8 variant),
|
||||
the passes are translated to SPIR-V, and FSR 4.1.1 is selected. The Proton build is picked
|
||||
automatically — the first under which the DLL enables FSR 4.1: GE-Proton 10 or newer,
|
||||
Proton-CachyOS, Proton Experimental (tested: GE-Proton 11, Proton-CachyOS 11, Experimental of
|
||||
October 2026; Steam's Proton 11.0 and GE-Proton 9 do not). It runs in the Steam runtime it
|
||||
requires (Steam installs it the first time any game runs with that Proton), or through
|
||||
umu-launcher when installed. On NixOS umu-launcher comes from `nix-shell` (downloaded the first
|
||||
time, ~1.7 GB); the AppImage runs the recording on the system itself, through the user's systemd
|
||||
(`systemd-run --user`), since the system's `/nix` is out of its sight. Recording on the Steam Deck is not verified yet; if the DLL does not
|
||||
enable FSR 4.1 there, build on a PC and copy the `fsr4_411` folder. The same from the command
|
||||
line: the launcher's (and the AppImage's) `--build-fsr411 <DLL>`.
|
||||
- [deadinside28/bloodborne_pc](https://github.com/deadinside28/bloodborne_pc): **bbport**, the
|
||||
native Linux port this fork is built on, and almost all of the work behind it. Its README, with
|
||||
the technical details, is in [BBPORT.md](BBPORT.md).
|
||||
- [Supermedo/bloodborne_pc](https://github.com/Supermedo/bloodborne_pc): the Windows port, whose
|
||||
online play, DLSS support, cheats page, Add to Steam and icon were brought to Linux here.
|
||||
- [shadPS4](https://github.com/shadps4-emu/shadPS4): the renderer and shader recompiler.
|
||||
- [shadp2p](https://github.com/Wozzardman/shadp2p) by Wozzardman: the network and PSN libraries
|
||||
for online play. Servers by the [shadPS4](https://shadps4.net) team and
|
||||
[The Hunter's Dream](https://thehuntersdream.com) by droogie and its team.
|
||||
- [IFreemz/shadPS4-Bloodborne-DLSS-FSR](https://github.com/IFreemz/shadPS4-Bloodborne-DLSS-FSR):
|
||||
the DLSS bridge, and the [NVIDIA DLSS SDK](https://github.com/NVIDIA/DLSS).
|
||||
- [FSR-Vulkan](https://github.com/FireBurn/FSR-Vulkan) by FireBurn and the AMD FidelityFX SDK.
|
||||
- **Dommo**: the Xbox and Switch button prompts (*DS3 Style - Modern Xbox prompts*, *DS3 Style -
|
||||
Nintendo Switch prompts*) and the *Stripped down gesture menu*, from
|
||||
[Nexus Mods (Bloodborne, mod 30)](https://www.nexusmods.com/bloodborne/mods/30), uploaded by
|
||||
goomab. Only their drawn prompts are in this repository ([assets/menu_prompts](assets/menu_prompts)).
|
||||
- **Abken**: the [KBM Icons Generator](https://www.nexusmods.com/bloodborne/mods/537), which showed
|
||||
which textures keyboard prompts need (after the idea of
|
||||
[mod 460](https://www.nexusmods.com/bloodborne/mods/460)); bbport draws its own.
|
||||
- [quick-sharun](https://github.com/pkgforge-dev/Anylinux-AppImages) for the AppImage.
|
||||
- Game patches by Kyo, Lance McDonald, auser1337, illusion, emoose and the Bloodborne community.
|
||||
|
||||
From source the script takes its tools from the system (MinGW GCC, CMake, Ninja, Python 3,
|
||||
SPIRV-Tools, Git) or from Nix; the AppImage has them prebuilt.
|
||||
Full list of components and licenses: [BBPORT.md](BBPORT.md#credits-and-licenses).
|
||||
|
||||
**FP8 on RDNA4.** When vkd3d-proton offers FP8 cooperative matrices (RDNA4), the DLL runs
|
||||
another variant of its passes: other shaders and weights, other dispatch sizes. The build records
|
||||
both, INT8 into `fsr4_411/` and FP8 into `fsr4_411/fp8/`, and the game picks FP8 itself when the
|
||||
GPU has FP8 matrices (`VK_EXT_shader_float8`); the log says `Upscaler: FSR 4.1.1 replay, FP8 …`.
|
||||
`BB_FSR411_VARIANT=int8` forces INT8. Checked on an RX 7800 XT through vkd3d-proton's FP16
|
||||
emulation of FP8 (`BB_FSR4CAP_FP8=1` records it there too, `fp8emu/`): bit-exact with the DLL;
|
||||
**not yet run on RDNA4 hardware.**
|
||||
## License
|
||||
|
||||
**AppImage** (Steam Deck): `bash build.sh && bash packaging/appimage.sh` →
|
||||
`dist/Bloodborne-bbport-x86_64.AppImage`; data in `~/.local/share/bbport`, `--play` starts the
|
||||
game without the launcher window (Game Mode). FSR 4.1.1 models are not packaged: build them with
|
||||
the launcher's button (see above); they go to `~/.local/share/bbport/fsr4_411`
|
||||
(`BB_PACKAGE_FSR411=1` bundles a local `fsr4_411` into an AppImage for your own devices). On the Steam Deck pick the 1280×720 output (the
|
||||
game is 16:9; on the 1280×800 screen it gets thin bars).
|
||||
|
||||
**Adding the AppImage to Steam** (*Add a Non-Steam Game*) needs no options; the compatibility tool
|
||||
does not matter. (Steam preloads its overlay into every non-Steam game; the AppImage removes it
|
||||
before its own programs start, so the Steam overlay is not shown in the game.) Where Steam
|
||||
runs games without FUSE (NixOS: Steam's FHS sandbox; the AppImage then exits with *Cannot mount
|
||||
AppImage*), set the launch options to
|
||||
|
||||
```
|
||||
TMPDIR=$HOME/.cache APPIMAGE_EXTRACT_AND_RUN=1 NO_CLEANUP=1 %command%
|
||||
```
|
||||
|
||||
The AppImage then unpacks itself (~2 GB, `~/.cache/appimage_extracted_*`) on the first start
|
||||
(~10 s) and reuses that copy afterwards; a new AppImage version gets a new copy, the old one can
|
||||
be deleted. Without `TMPDIR` it would unpack into Steam's `/tmp`, which is in RAM there. Add
|
||||
` --play` after `%command%` to skip the launcher.
|
||||
|
||||
**NVIDIA in the AppImage:** startup discovers the host's installed 64-bit NVIDIA Vulkan ICD
|
||||
and exposes its vendor libraries alongside the bundled AMD/Intel drivers. This keeps the
|
||||
NVIDIA userspace driver matched to the host kernel module. Standard Linux distributions keep
|
||||
these libraries under `/usr/lib*`; on NixOS the AppImage's internal `/nix/store` may hide them.
|
||||
In that case copy the NVIDIA libraries into an accessible directory and set
|
||||
`BB_NVIDIA_LIB_DIR=/path/to/libraries` (the NVIDIA manifest must also be accessible).
|
||||
Explicit `VK_DRIVER_FILES`/`VK_ICD_FILENAMES` overrides are preserved. Diagnose drivers inside
|
||||
the package with:
|
||||
|
||||
```bash
|
||||
./Bloodborne-bbport-x86_64.AppImage --vulkan-info 2>&1 | tee bbport-vulkan.log
|
||||
```
|
||||
|
||||
A user reported successful startup with FSR 3 on a GTX 1060 6GB (Fedora 44, NVIDIA
|
||||
580.178.04); selecting FSR 4 caused a black window. Use FSR 3 on this configuration.
|
||||
|
||||
MangoHud is bundled in the AppImage; enable its checkbox in the launcher. If MangoHud is also
|
||||
installed system-wide, or Steam's performance overlay is on (Steam Deck game mode), only one
|
||||
overlay is drawn (two drew doubled, offset text).
|
||||
When running from source, install MangoHud separately. A diagnostic launch with
|
||||
`VK_LOADER_LAYERS_DISABLE=~implicit~` also disables MangoHud.
|
||||
|
||||
Useful variables: `BB_FRAME_STATS=1` (frame statistics, including a `Memory:` line: VRAM, GTT,
|
||||
RSS, images and guest blocks in VRAM), `BB_PC_MODEL=1` (the new memory and translation model,
|
||||
AMD only; 0, the old model as in 0.3, is the default), `BB_ANISO=N` (anisotropic filtering of scene textures; 16 by
|
||||
default, 0 = the game's own),
|
||||
`BB_GC_IDLE_SECONDS=N` / `BB_VRAM_IDLE_SECONDS=N` (how long unused textures / buffers stay in VRAM;
|
||||
20 / 60), `BB_BREADCRUMBS=0` (no GPU breadcrumbs; with them a GPU hang names the draw or dispatch
|
||||
it is stuck in), `BB_GPU_PROFILE=1` (GPU time per
|
||||
pass), `BB_FSR4_PROFILE=1` (GPU time per FSR 4 pass), `BB_UPSCALER=taa|fsr3|fsr4|fsr411|off|none`,
|
||||
`BB_FRAMES_AHEAD=N` (how many frames the GPU command thread may run ahead of the GPU; 1 by default,
|
||||
0 = unbounded), `BB_PRESENT_THREAD=0` (present on the vblank thread, as before),
|
||||
`BB_LIVE_RES=1` (live resolution changes instead of the startup patch for outputs other than 1080p),
|
||||
`BB_PAD_RECORD=file` / `BB_PAD_REPLAY=file` (record a route with F9, replay it in scripted tests),
|
||||
`BB_GC_BUDGET_MB=N` (texture cache budget, as on integrated GPUs), `BB_PRESENT_DUMP_TRIGGER=file`
|
||||
with `BB_PRESENT_DUMP_COUNT=N` (dump N consecutive presented frames),
|
||||
`BB_FSR411_VARIANT=int8|fp8|fp8emu` (FSR 4.1.1 variant; by default FP8 where the GPU has FP8
|
||||
matrices), `BB_READBACKS=0|1|2` (reads of GPU-written memory by the game: 1 by default, 2
|
||||
precise and slow, 0 off).
|
||||
More in [docs/](docs); recent changes: [docs/CHANGES_0.4.md](docs/CHANGES_0.4.md) (in Russian),
|
||||
[docs/CHANGES_2026-10-06.md](docs/CHANGES_2026-10-06.md).
|
||||
|
||||
## Repository layout
|
||||
|
||||
| Path | Contents |
|
||||
|---|---|
|
||||
| `src/` | Loader (`probe.c`) and the HLE runtime |
|
||||
| `scripts/` | Offline preparation of the game image, module linking, patch compiler |
|
||||
| `gpu/` | Renderer library: vendored shadPS4 video core with this port's changes (`gpu/VENDOR.txt`), shims, ImGui menu, FSR 4.1.1 runtime (`gpu/shadps4/video_core/renderer_vulkan/fsr411`) |
|
||||
| `launcher/`, `packaging/` | GTK4 launcher; Nix package and AppImage |
|
||||
| `patches/` | Community patches for Bloodborne |
|
||||
| `tools/` | Developer tools: scripted runs, A/B toggles, FSR benchmark helpers, FSR 4 shader rewrites, `fsr4cap` (FSR 4.1.1 recording/extraction) |
|
||||
| `tests/` | Loader, runtime, patch and renderer tests |
|
||||
| `docs/` | Design notes and measurements ([upscaler](docs/upscaler.md), [parallel GPU](docs/parallel_gpu.md), [motion vectors](docs/motion_vectors.md), [roadmap](docs/ROADMAP.md)) |
|
||||
|
||||
Tests: `bash build.sh --test`, `python3 -m unittest discover -s tests`, and
|
||||
`ninja -C out/gpu motion-history-test ui-composition-test scene-resolution-test motion-shader-test settings-test`.
|
||||
|
||||
## Roadmap
|
||||
|
||||
- The new memory and translation model on by default once it is stable, NVIDIA included; the
|
||||
old model is removed after that.
|
||||
- The GPU reads resource descriptors itself (bindless), textures are objects created at load
|
||||
time: no shader code or constant engine executed on the CPU, no texture cache keyed by
|
||||
address.
|
||||
- Shaders translated ahead of time, at install, not during play.
|
||||
- More CPU parallelism in GPU command processing (split the draw-recording stage further),
|
||||
scaling to all hardware threads — most important for the Steam Deck.
|
||||
- Async compute for the upscaler (the frame is GPU-bound at 4K).
|
||||
- XeSS (super resolution) and XeFG frame generation through a Wine helper sharing Vulkan
|
||||
memory (a memory-bridge prototype is in `tools/bridge_helper`); DLSS for NVIDIA users;
|
||||
inputs exposed so that OptiScaler-style mapping works.
|
||||
- Frame generation (FSR 3.1 FG first), reactive and transparency masks for particles and fog.
|
||||
- Fix the races in AMD's FSR 4.1.1 shaders at output widths that are not multiples of 64
|
||||
(e.g. 1600×900), as already done for the left-edge race in FSR 4 v07 at 1080p.
|
||||
- Steam Deck validation of the AppImage; HDR output.
|
||||
|
||||
## Credits and licenses
|
||||
|
||||
bbport is licensed under the **GNU GPL v2 or later** ([LICENSE](LICENSE)) — it contains code
|
||||
from shadPS4 (GPL-2.0-or-later). Third-party components keep their licenses:
|
||||
[shadPS4](https://github.com/shadps4-emu/shadPS4) video core and shader recompiler (GPL-2.0+),
|
||||
[sirit](https://github.com/shadps4-emu/sirit), [half](https://half.sourceforge.net/),
|
||||
[FSR-Vulkan](https://github.com/FireBurn/FSR-Vulkan) by FireBurn (MIT; FSR 3.1 on Vulkan and the
|
||||
FSR 4 v07 provider), AMD FidelityFX SDK (MIT), [LibAtrac9](https://github.com/Thealexbarney/LibAtrac9)
|
||||
(MIT), [Dear ImGui](https://github.com/ocornut/imgui) (MIT), DejaVu fonts,
|
||||
[dxil-spirv](https://github.com/HansKristian-Work/dxil-spirv) (MIT, used to build the
|
||||
FSR 4.1.1 assets). Game patches by Kyo, Lance McDonald, auser1337, illusion, emoose and other
|
||||
community members (`patches/Bloodborne.xml`). AMD's FSR 4 DLLs and model data are not
|
||||
distributed here.
|
||||
GNU GPL v2 or later ([LICENSE](LICENSE)). Third-party components keep their own licenses. NVIDIA
|
||||
and DLSS are trademarks of NVIDIA Corporation.
|
||||
@@ -0,0 +1,16 @@
|
||||
# Button prompts and gestures menu
|
||||
|
||||
By **Dommo**, from [Nexus Mods: Bloodborne, mod 30](https://www.nexusmods.com/bloodborne/mods/30)
|
||||
(uploaded by goomab): *DS3 Style - Modern Xbox prompts*, *DS3 Style - Nintendo Switch prompts* and
|
||||
*Stripped down gesture menu*. The mod page allows using its assets and uploading them elsewhere
|
||||
with credit to the author, but not in anything sold.
|
||||
|
||||
Only what the mods drew is here, never the game's own files:
|
||||
|
||||
- `xbox/`, `switch/`: the 20 button icons (`KG_*.dds`, BC7 exactly as in the mods) and the strip
|
||||
of big prompts from the shared UI atlas (`atlas.dds`, scaled down to the game's atlas size).
|
||||
- `gesturetop.json`: the edits that turn the game's `menu/gesturetop.gfx` into the mod's.
|
||||
|
||||
`scripts/menu_prompts.py` puts them into the player's own `menu/common.tpf.dcx` and
|
||||
`menu/gesturetop.gfx` at each start; `tools/extract_prompt_assets.py` rebuilds this folder from
|
||||
the mods' ZIPs. Keyboard prompts are drawn by `scripts/menu_prompts.py` itself.
|
||||
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"original_sha256": "5d7bae1451d97296e88a6add62b1fd9d1425901acc5c907e90f52354bc0b624e",
|
||||
"result_sha256": "c77f7398c02613bd3d1a9613ce773fa8b6b74143053c6092b52a3c8d959dd868",
|
||||
"edits": [
|
||||
[
|
||||
4,
|
||||
1,
|
||||
"2f"
|
||||
],
|
||||
[
|
||||
2548,
|
||||
1,
|
||||
"18"
|
||||
],
|
||||
[
|
||||
2556,
|
||||
1,
|
||||
"8e"
|
||||
],
|
||||
[
|
||||
2563,
|
||||
0,
|
||||
"c9"
|
||||
],
|
||||
[
|
||||
2564,
|
||||
0,
|
||||
"0034bca0480238"
|
||||
],
|
||||
[
|
||||
3189,
|
||||
1,
|
||||
"92"
|
||||
],
|
||||
[
|
||||
3229,
|
||||
1,
|
||||
"92"
|
||||
],
|
||||
[
|
||||
3242,
|
||||
5,
|
||||
"207b2673a5e0"
|
||||
],
|
||||
[
|
||||
3268,
|
||||
1,
|
||||
"98"
|
||||
],
|
||||
[
|
||||
3281,
|
||||
5,
|
||||
"2077b30a7b80"
|
||||
],
|
||||
[
|
||||
3519,
|
||||
1,
|
||||
"41"
|
||||
],
|
||||
[
|
||||
3529,
|
||||
1,
|
||||
"33"
|
||||
],
|
||||
[
|
||||
3539,
|
||||
5,
|
||||
"c900004000207d11920000"
|
||||
],
|
||||
[
|
||||
5795,
|
||||
1,
|
||||
"ab"
|
||||
],
|
||||
[
|
||||
5817,
|
||||
1,
|
||||
"1a"
|
||||
],
|
||||
[
|
||||
5826,
|
||||
5,
|
||||
"c900004000207a015deba0"
|
||||
],
|
||||
[
|
||||
6076,
|
||||
12,
|
||||
""
|
||||
]
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -70,6 +70,9 @@ if [[ ${1:-} == --test ]]; then
|
||||
out/file-mods-test
|
||||
"$CC" -std=c11 -O2 -g -Wall -Wextra -Werror -pthread -I. -Isrc tests/test_sema.c "${runtime[@]}" out/libatrac9.a -lm "${gpu[@]}" "${libraries[@]}" -o out/sema-test
|
||||
out/sema-test
|
||||
"$CC" -std=c11 -O2 -g -Wall -Wextra -Werror -pthread -I. -Isrc tests/test_online.c "${runtime[@]}" out/libatrac9.a -lm "${gpu[@]}" "${libraries[@]}" -o out/online-test
|
||||
out/online-test
|
||||
"$CC" -std=c11 -D_GNU_SOURCE -O2 -g -Wall -Wextra -Werror -I. -Isrc tests/test_content.c src/runtime_content.c -o out/content-test
|
||||
out/content-test
|
||||
out/content-test --addons
|
||||
fi
|
||||
@@ -0,0 +1,45 @@
|
||||
# Release notes template
|
||||
|
||||
Every release uses this format: same sections, same order. A section with nothing to say keeps
|
||||
its heading and says "None.". Notes are in English. "New", "Improved" and "Fixed" cover the
|
||||
changes since the previous release. Third-party work is credited in the line that uses it.
|
||||
|
||||
- Tag: `<upstream bbport version>-linux.<n>` (e.g. `0.4-linux.1`).
|
||||
- Title: `bbport Linux <tag>`.
|
||||
- Asset: `dist/Bloodborne-bbport-x86_64.AppImage` (`bash build.sh && bash packaging/appimage-sharun.sh`).
|
||||
|
||||
```markdown
|
||||
<One or two sentences: what this release is and which upstream bbport it is based on.>
|
||||
|
||||
**Requirements:** your own decrypted dump of Bloodborne 1.09, US (`CUSA03173`) or EU
|
||||
(`CUSA00900`). No game files are included. Linux x86-64 and a Vulkan 1.3 GPU.
|
||||
|
||||
## Download
|
||||
|
||||
| File | For |
|
||||
|---|---|
|
||||
| `Bloodborne-bbport-x86_64.AppImage` | Any x86-64 distribution and the Steam Deck |
|
||||
|
||||
Make it executable, run it, choose your game folder and press **Play**. Saves and settings go
|
||||
to `~/.local/share/bbport`.
|
||||
|
||||
SHA-256: `<sha256sum of the AppImage>`
|
||||
|
||||
## New
|
||||
- **<Feature>**: <what it does>.
|
||||
|
||||
## Improved
|
||||
- <Change to something that existed>.
|
||||
|
||||
## Fixed
|
||||
- <Bug fixed>.
|
||||
|
||||
## Upgrading
|
||||
- <What someone coming from the previous release has to do, or "None.">
|
||||
|
||||
## Known issues
|
||||
- <Limitations and known bugs.>
|
||||
|
||||
**Full changes:** https://github.com/ayozetr/bloodborne_linux/compare/<previous tag>...<tag>
|
||||
**Credits and licenses:** https://github.com/ayozetr/bloodborne_linux#credits
|
||||
```
|
||||
@@ -125,6 +125,42 @@ add_library(bbgpu SHARED ${VIDEO_CORE} ${RECOMPILER} ${COMMON} ${SHIM_SOURCES}
|
||||
shadps4/core/libraries/kernel/equeue.cpp ${AVPLAYER}
|
||||
shadps4/core/libraries/videodec/video_utils.cpp)
|
||||
add_dependencies(bbgpu host_shaders)
|
||||
# bbport co-op: PS4 network and PSN libraries with the shadNet client, from shadp2p
|
||||
# (github.com/Wozzardman/shadp2p, GPL-2.0), for private Bloodborne co-op servers.
|
||||
find_package(Protobuf CONFIG REQUIRED)
|
||||
set(SHADNET_PROTO_OUT "${CMAKE_CURRENT_BINARY_DIR}/shadnet_proto_gen")
|
||||
file(MAKE_DIRECTORY "${SHADNET_PROTO_OUT}")
|
||||
add_custom_command(
|
||||
OUTPUT "${SHADNET_PROTO_OUT}/shadnet.pb.cc" "${SHADNET_PROTO_OUT}/shadnet.pb.h"
|
||||
COMMAND protobuf::protoc "--proto_path=${CMAKE_CURRENT_SOURCE_DIR}/shadps4/shadnet"
|
||||
"--cpp_out=${SHADNET_PROTO_OUT}" "${CMAKE_CURRENT_SOURCE_DIR}/shadps4/shadnet/shadnet.proto"
|
||||
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/shadps4/shadnet/shadnet.proto")
|
||||
file(GLOB_RECURSE BBNET_SOURCES CONFIGURE_DEPENDS shadps4/core/libraries/network/*.cpp
|
||||
shadps4/core/libraries/np/*.cpp shadps4/shadnet/*.cpp)
|
||||
# Trophies and the system dialogs stay in the C runtime; their shadPS4 versions need the
|
||||
# emulator's UI layers.
|
||||
list(FILTER BBNET_SOURCES EXCLUDE REGEX
|
||||
"np_trophy|trophy_ui|np_sns_facebook_dialog|np_profile_dialog/|np_commerce/")
|
||||
find_package(nlohmann_json CONFIG REQUIRED)
|
||||
add_library(bbnet STATIC ${BBNET_SOURCES} "${SHADNET_PROTO_OUT}/shadnet.pb.cc" bbnet/bbnet_glue.cpp)
|
||||
target_include_directories(bbnet PRIVATE shim shadps4 shadps4/shadnet "${SHADNET_PROTO_OUT}"
|
||||
third_party/imgui third_party/httplib)
|
||||
target_link_libraries(bbnet PRIVATE nlohmann_json::nlohmann_json)
|
||||
# HTTPS (the community game server only speaks TLS) and gzip replies in cpp-httplib.
|
||||
find_package(OpenSSL REQUIRED)
|
||||
find_package(ZLIB REQUIRED)
|
||||
target_compile_definitions(bbnet PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT CPPHTTPLIB_ZLIB_SUPPORT)
|
||||
target_link_libraries(bbnet PRIVATE OpenSSL::SSL OpenSSL::Crypto ZLIB::ZLIB)
|
||||
# UPnP: opens the co-op port on the router (net_upnp.cpp).
|
||||
find_path(MINIUPNPC_INCLUDE miniupnpc.h PATH_SUFFIXES miniupnpc REQUIRED)
|
||||
find_library(MINIUPNPC_LIBRARY NAMES miniupnpc REQUIRED)
|
||||
target_include_directories(bbnet PRIVATE "${MINIUPNPC_INCLUDE}")
|
||||
target_link_libraries(bbnet PRIVATE "${MINIUPNPC_LIBRARY}")
|
||||
target_compile_definitions(bbnet PRIVATE BOOST_ASIO_STANDALONE NDEBUG)
|
||||
target_compile_options(bbnet PRIVATE -Wno-deprecated-declarations -Wno-missing-field-initializers)
|
||||
target_link_libraries(bbnet PUBLIC Vulkan::Headers fmt::fmt Boost::headers magic_enum::magic_enum
|
||||
protobuf::libprotobuf)
|
||||
target_link_libraries(bbgpu PUBLIC bbnet)
|
||||
# In-game settings menu: Dear ImGui (third_party/imgui, MIT) with its Vulkan backend, and an
|
||||
# embedded Cyrillic font (third_party/fonts).
|
||||
target_sources(bbgpu PRIVATE third_party/imgui/imgui.cpp third_party/imgui/imgui_draw.cpp
|
||||
|
||||
@@ -16,6 +16,8 @@ typedef struct {
|
||||
} BbGpuConfig;
|
||||
/* Registers kernel event queues (needed with or without graphics). */
|
||||
void bbgpu_register_kernel(void);
|
||||
/* Co-op: the game folder and param.sfo identity for the network and PSN libraries. */
|
||||
void bbnet_configure(const char *app0, const char *serial, const char *title, const char *app_ver);
|
||||
/* Creates window, Vulkan device, presenter and GPU command processor. */
|
||||
int bbgpu_init(const BbGpuConfig *config);
|
||||
/* Function for an imported NID ("NID#lib#mod"), or 0 when the GPU library does not provide it. */
|
||||
|
||||
@@ -0,0 +1,476 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// bbport co-op: what the network and NP libraries from shadp2p need from the rest of
|
||||
// shadPS4, implemented on top of the bbport runtime, and their registration.
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "common/elf_info.h"
|
||||
#include "common/types.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/bloodborne_re.h"
|
||||
#include "core/debugger.h"
|
||||
#include "core/libraries/kernel/kernel.h"
|
||||
#include "core/libraries/kernel/memory.h"
|
||||
#include "core/libraries/kernel/threads/pthread.h"
|
||||
#include "core/libraries/kernel/time.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/system/systemservice.h"
|
||||
#include "core/libraries/system/userservice.h"
|
||||
#include "core/user_manager.h"
|
||||
#include "imgui/invitation_prompt_layer.h"
|
||||
#include "imgui/shadnet_notifications_layer.h"
|
||||
|
||||
#include "core/libraries/network/http.h"
|
||||
#include "core/libraries/network/http2.h"
|
||||
#include "core/libraries/network/net.h"
|
||||
#include "core/libraries/network/netctl.h"
|
||||
#include "core/libraries/network/ssl.h"
|
||||
#include "core/libraries/network/ssl2.h"
|
||||
#include "core/libraries/np/np_auth.h"
|
||||
#include "core/libraries/np/np_common.h"
|
||||
#include "core/libraries/np/np_manager.h"
|
||||
#include "core/libraries/np/np_matching2/np_matching2.h"
|
||||
#include "core/libraries/np/np_partner.h"
|
||||
#include "core/libraries/np/np_party.h"
|
||||
#include "core/libraries/np/np_score/np_score.h"
|
||||
#include "core/libraries/np/np_signaling/np_signaling.h"
|
||||
#include "core/libraries/np/np_tus.h"
|
||||
#include "core/libraries/np/np_web_api/np_web_api.h"
|
||||
#include "core/libraries/np/np_web_api2/np_web_api2.h"
|
||||
|
||||
extern "C" {
|
||||
int32_t runtime_thread_spawn(void** out, void* PS4_SYSV_ABI (*entry)(void*), void* argument,
|
||||
uint64_t stack, const char* name);
|
||||
int32_t runtime_thread_join_spawned(void* thread, void** result);
|
||||
}
|
||||
|
||||
// ---- Kernel ---------------------------------------------------------------------------------
|
||||
|
||||
namespace Libraries::Kernel {
|
||||
|
||||
struct BbNetMutex {
|
||||
std::recursive_mutex mutex;
|
||||
bool recursive{};
|
||||
int depth{};
|
||||
};
|
||||
struct BbNetMutexAttr {
|
||||
PthreadMutexType type{PthreadMutexType::ErrorCheck};
|
||||
};
|
||||
struct BbNetCond {
|
||||
std::condition_variable_any cond;
|
||||
};
|
||||
struct BbNetCondAttr {};
|
||||
struct BbNetThreadAttr {
|
||||
u64 stack{};
|
||||
};
|
||||
|
||||
s32 ErrnoToSceKernelError(s32 e) {
|
||||
return e > 0 && e <= 0xffff ? s32(0x80020000u | u32(e)) : e;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelGetSystemSwVersion(SwVersionStruct* ret) {
|
||||
if (!ret) {
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
std::memset(ret->text_representation, 0, sizeof(ret->text_representation));
|
||||
std::snprintf(ret->text_representation, sizeof(ret->text_representation), "%s", "11.000.000");
|
||||
ret->hex_representation = 0x11000001;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelClockGettime(u32 clock_id, OrbisKernelTimespec* tp) {
|
||||
if (!tp) {
|
||||
return ORBIS_KERNEL_ERROR_EFAULT;
|
||||
}
|
||||
s64 ns;
|
||||
if (clock_id == ORBIS_CLOCK_REALTIME || clock_id == ORBIS_CLOCK_REALTIME_PRECISE ||
|
||||
clock_id == ORBIS_CLOCK_REALTIME_FAST || clock_id == ORBIS_CLOCK_SECOND) {
|
||||
ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
} else {
|
||||
ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch())
|
||||
.count();
|
||||
}
|
||||
tp->tv_sec = ns / 1000000000;
|
||||
tp->tv_nsec = ns % 1000000000;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, u64 len, s32, s32,
|
||||
const char*) {
|
||||
if (!addr_in_out || !len) {
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
void* memory = std::calloc(1, len);
|
||||
if (!memory) {
|
||||
return ORBIS_KERNEL_ERROR_ENOMEM;
|
||||
}
|
||||
*addr_in_out = memory;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelMunmap(void* addr, u64) {
|
||||
std::free(addr);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int posix_pthread_mutexattr_init(PthreadMutexAttrT* attr) {
|
||||
if (!attr) return 22;
|
||||
*attr = new BbNetMutexAttr;
|
||||
return 0;
|
||||
}
|
||||
int posix_pthread_mutexattr_settype(PthreadMutexAttrT* attr, PthreadMutexType type) {
|
||||
if (!attr || !*attr) return 22;
|
||||
(*attr)->type = type;
|
||||
return 0;
|
||||
}
|
||||
int posix_pthread_mutexattr_destroy(PthreadMutexAttrT* attr) {
|
||||
if (!attr) return 22;
|
||||
delete *attr;
|
||||
*attr = nullptr;
|
||||
return 0;
|
||||
}
|
||||
int scePthreadMutexInit(PthreadMutexT* mutex, const PthreadMutexAttrT* attr, const char*) {
|
||||
if (!mutex) return 22;
|
||||
*mutex = new BbNetMutex;
|
||||
(*mutex)->recursive = attr && *attr && (*attr)->type == PthreadMutexType::Recursive;
|
||||
return 0;
|
||||
}
|
||||
int posix_pthread_mutex_destroy(PthreadMutexT* mutex) {
|
||||
if (!mutex || !*mutex) return 22;
|
||||
delete *mutex;
|
||||
*mutex = nullptr;
|
||||
return 0;
|
||||
}
|
||||
int posix_pthread_mutex_lock(PthreadMutexT* mutex) {
|
||||
if (!mutex || !*mutex) return 22;
|
||||
(*mutex)->mutex.lock();
|
||||
if (++(*mutex)->depth > 1 && !(*mutex)->recursive) {
|
||||
--(*mutex)->depth;
|
||||
(*mutex)->mutex.unlock();
|
||||
return 11; // EDEADLK
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int posix_pthread_mutex_trylock(PthreadMutexT* mutex) {
|
||||
if (!mutex || !*mutex) return 22;
|
||||
if (!(*mutex)->mutex.try_lock()) return 16; // EBUSY
|
||||
if (++(*mutex)->depth > 1 && !(*mutex)->recursive) {
|
||||
--(*mutex)->depth;
|
||||
(*mutex)->mutex.unlock();
|
||||
return 16;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int posix_pthread_mutex_unlock(PthreadMutexT* mutex) {
|
||||
if (!mutex || !*mutex) return 22;
|
||||
--(*mutex)->depth;
|
||||
(*mutex)->mutex.unlock();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int posix_pthread_condattr_init(PthreadCondAttrT* attr) {
|
||||
if (!attr) return 22;
|
||||
*attr = new BbNetCondAttr;
|
||||
return 0;
|
||||
}
|
||||
int posix_pthread_condattr_destroy(PthreadCondAttrT* attr) {
|
||||
if (!attr) return 22;
|
||||
delete *attr;
|
||||
*attr = nullptr;
|
||||
return 0;
|
||||
}
|
||||
int scePthreadCondInit(PthreadCondT* cond, const PthreadCondAttrT*, const char*) {
|
||||
if (!cond) return 22;
|
||||
*cond = new BbNetCond;
|
||||
return 0;
|
||||
}
|
||||
int posix_pthread_cond_destroy(PthreadCondT* cond) {
|
||||
if (!cond || !*cond) return 22;
|
||||
delete *cond;
|
||||
*cond = nullptr;
|
||||
return 0;
|
||||
}
|
||||
int posix_pthread_cond_signal(PthreadCondT* cond) {
|
||||
if (!cond || !*cond) return 22;
|
||||
(*cond)->cond.notify_one();
|
||||
return 0;
|
||||
}
|
||||
namespace {
|
||||
// The caller holds the mutex once; the wait releases and retakes it like pthread_cond_wait.
|
||||
struct HeldMutex {
|
||||
BbNetMutex* mutex;
|
||||
void lock() {
|
||||
mutex->mutex.lock();
|
||||
++mutex->depth;
|
||||
}
|
||||
void unlock() {
|
||||
--mutex->depth;
|
||||
mutex->mutex.unlock();
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
int posix_pthread_cond_wait(PthreadCondT* cond, PthreadMutexT* mutex) {
|
||||
if (!cond || !*cond || !mutex || !*mutex) return 22;
|
||||
HeldMutex held{*mutex};
|
||||
(*cond)->cond.wait(held);
|
||||
return 0;
|
||||
}
|
||||
int posix_pthread_cond_reltimedwait_np(PthreadCondT* cond, PthreadMutexT* mutex, u64 usec) {
|
||||
if (!cond || !*cond || !mutex || !*mutex) return 22;
|
||||
HeldMutex held{*mutex};
|
||||
const auto status = (*cond)->cond.wait_for(held, std::chrono::microseconds(usec));
|
||||
return status == std::cv_status::timeout ? 60 : 0; // ETIMEDOUT
|
||||
}
|
||||
|
||||
int posix_pthread_attr_init(PthreadAttrT* attr) {
|
||||
if (!attr) return 22;
|
||||
*attr = new BbNetThreadAttr;
|
||||
return 0;
|
||||
}
|
||||
int posix_pthread_attr_destroy(PthreadAttrT* attr) {
|
||||
if (!attr) return 22;
|
||||
delete *attr;
|
||||
*attr = nullptr;
|
||||
return 0;
|
||||
}
|
||||
int posix_pthread_attr_setstacksize(PthreadAttrT* attr, size_t stack_size) {
|
||||
if (!attr || !*attr) return 22;
|
||||
(*attr)->stack = stack_size;
|
||||
return 0;
|
||||
}
|
||||
int posix_pthread_attr_setinheritsched(PthreadAttrT*, int) {
|
||||
return 0;
|
||||
}
|
||||
int posix_pthread_attr_setschedpolicy(PthreadAttrT*, SchedPolicy) {
|
||||
return 0;
|
||||
}
|
||||
int posix_pthread_attr_setschedparam(PthreadAttrT*, const SchedParam*) {
|
||||
return 0;
|
||||
}
|
||||
int scePthreadAttrSetaffinity(PthreadAttrT*, u64) {
|
||||
return 0;
|
||||
}
|
||||
int posix_pthread_create_name_np(PthreadT* thread, const PthreadAttrT* attr,
|
||||
PthreadEntryFunc start, void* arg, const char* name) {
|
||||
if (!thread || !start) return 22;
|
||||
const u64 stack = attr && *attr ? (*attr)->stack : 0;
|
||||
return runtime_thread_spawn(thread, start, arg, stack, name ? name : "np");
|
||||
}
|
||||
int posix_pthread_join(PthreadT thread, void** ret) {
|
||||
return runtime_thread_join_spawned(thread, ret);
|
||||
}
|
||||
|
||||
} // namespace Libraries::Kernel
|
||||
|
||||
// ---- User and system service, debugger, Bloodborne helpers -------------------------------------
|
||||
|
||||
namespace Libraries::UserService {
|
||||
s32 PS4_SYSV_ABI sceUserServiceGetInitialUser(int* user_id) {
|
||||
if (!user_id) {
|
||||
return s32(0x80960009); // SCE_USER_SERVICE_ERROR_INVALID_ARGUMENT
|
||||
}
|
||||
*user_id = UserManager::RuntimeUserId;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
} // namespace Libraries::UserService
|
||||
|
||||
namespace Libraries::SystemService {
|
||||
void PushSystemServiceEvent(const OrbisSystemServiceEvent& event) {
|
||||
std::printf("Co-op: system service event %#x not delivered (invitations are not ported)\n",
|
||||
unsigned(event.event_type));
|
||||
}
|
||||
} // namespace Libraries::SystemService
|
||||
|
||||
namespace Core::Debugger {
|
||||
s32 GetCurrentPid() {
|
||||
#ifdef _WIN32
|
||||
return s32(GetCurrentProcessId());
|
||||
#else
|
||||
return s32(getpid());
|
||||
#endif
|
||||
}
|
||||
} // namespace Core::Debugger
|
||||
|
||||
namespace Core::Bloodborne {
|
||||
namespace {
|
||||
std::mutex placement_mutex;
|
||||
std::optional<std::string> placement;
|
||||
} // namespace
|
||||
std::optional<std::string> GetSeamlessHostPlacementHeader() {
|
||||
std::scoped_lock lock{placement_mutex};
|
||||
return placement;
|
||||
}
|
||||
bool SetSeamlessHostPlacementHeader(std::string_view value) {
|
||||
if (value.empty() || value.size() > 4096) {
|
||||
return false;
|
||||
}
|
||||
std::scoped_lock lock{placement_mutex};
|
||||
placement = std::string{value};
|
||||
return true;
|
||||
}
|
||||
void ClearSeamlessHostPlacementHeader() {
|
||||
std::scoped_lock lock{placement_mutex};
|
||||
placement.reset();
|
||||
}
|
||||
void TraceMatching2LeaveRoom(std::uintptr_t, std::uint64_t) {}
|
||||
} // namespace Core::Bloodborne
|
||||
|
||||
// ---- Notifications (log only for now) ------------------------------------------------------------
|
||||
|
||||
namespace ImGui::ShadNetNotify {
|
||||
namespace {
|
||||
std::mutex history_mutex;
|
||||
std::vector<HistoryEntry> history;
|
||||
std::size_t unread{};
|
||||
} // namespace
|
||||
KindDisplay DisplayOf(Kind kind) {
|
||||
switch (kind) {
|
||||
case Kind::FriendRequest:
|
||||
return {"Friend request", {0.9f, 0.8f, 0.3f, 1.0f}};
|
||||
case Kind::FriendNew:
|
||||
return {"Friend", {0.4f, 0.9f, 0.4f, 1.0f}};
|
||||
case Kind::FriendLost:
|
||||
return {"Friend", {0.9f, 0.4f, 0.4f, 1.0f}};
|
||||
case Kind::Online:
|
||||
return {"Online", {0.4f, 0.8f, 0.9f, 1.0f}};
|
||||
default:
|
||||
return {"Info", {0.8f, 0.8f, 0.8f, 1.0f}};
|
||||
}
|
||||
}
|
||||
void Push(Kind kind, std::string text) {
|
||||
std::printf("Co-op: %s: %s\n", DisplayOf(kind).tag, text.c_str());
|
||||
std::scoped_lock lock{history_mutex};
|
||||
history.push_back({kind, std::move(text), {}});
|
||||
++unread;
|
||||
}
|
||||
std::vector<HistoryEntry> GetHistory() {
|
||||
std::scoped_lock lock{history_mutex};
|
||||
return history;
|
||||
}
|
||||
void ClearHistory() {
|
||||
std::scoped_lock lock{history_mutex};
|
||||
history.clear();
|
||||
unread = 0;
|
||||
}
|
||||
std::size_t UnreadCount() {
|
||||
std::scoped_lock lock{history_mutex};
|
||||
return unread;
|
||||
}
|
||||
void MarkRead() {
|
||||
std::scoped_lock lock{history_mutex};
|
||||
unread = 0;
|
||||
}
|
||||
void Register() {}
|
||||
void Unregister() {}
|
||||
} // namespace ImGui::ShadNetNotify
|
||||
|
||||
namespace ImGui::InvitationPrompt {
|
||||
void Push(s32, std::string invitation_id, std::string, std::string from_npid) {
|
||||
std::printf("Co-op: invitation %s from %s (invitations are not supported yet)\n",
|
||||
invitation_id.c_str(), from_npid.c_str());
|
||||
}
|
||||
void Dismiss(const std::string&) {}
|
||||
void Register() {}
|
||||
void Unregister() {}
|
||||
} // namespace ImGui::InvitationPrompt
|
||||
|
||||
// ---- Game identity -------------------------------------------------------------------------------
|
||||
|
||||
// ElfInfo lets shadPS4's Core::Emulator fill it in; this port has no emulator class, so the
|
||||
// same name is used here for that one job.
|
||||
namespace Core {
|
||||
class Emulator {
|
||||
public:
|
||||
static void SetGame(const char* app0, const char* serial, const char* title,
|
||||
const char* app_ver) {
|
||||
auto& info = Common::ElfInfo::Instance();
|
||||
info.game_serial = serial ? serial : "";
|
||||
info.title = title ? title : "";
|
||||
info.app_ver = app_ver ? app_ver : "";
|
||||
info.game_folder = app0 ? app0 : "";
|
||||
info.initialized = true;
|
||||
info.npCommIds = ReadNpCommIds(info.game_folder / "sce_sys" / "npbind.dat");
|
||||
}
|
||||
|
||||
private:
|
||||
// npbind.dat: a 0x80-byte header, then tagged records (big-endian u16 tag and length);
|
||||
// tag 0x10 holds an NP communication id such as NPWR05818_00.
|
||||
static std::vector<std::string> ReadNpCommIds(const std::filesystem::path& path) {
|
||||
std::vector<std::string> ids;
|
||||
std::ifstream file{path, std::ios::binary};
|
||||
const std::vector<u8> data{std::istreambuf_iterator<char>(file), {}};
|
||||
for (size_t at = 0x80; at + 4 <= data.size();) {
|
||||
const u16 tag = u16(data[at] << 8 | data[at + 1]);
|
||||
const u16 size = u16(data[at + 2] << 8 | data[at + 3]);
|
||||
at += 4;
|
||||
if (at + size > data.size()) {
|
||||
break;
|
||||
}
|
||||
if (tag == 0x10 && size) {
|
||||
std::string id(reinterpret_cast<const char*>(&data[at]), size);
|
||||
id.resize(std::strlen(id.c_str()));
|
||||
ids.push_back(id);
|
||||
}
|
||||
at += size;
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
};
|
||||
} // namespace Core
|
||||
|
||||
extern "C" void bbnet_configure(const char* app0, const char* serial, const char* title,
|
||||
const char* app_ver) {
|
||||
Core::Emulator::SetGame(app0, serial, title, app_ver);
|
||||
const auto& ids = Common::ElfInfo::Instance().GetNpCommIds();
|
||||
std::printf("Co-op: %s, NP communication id %s%s\n", serial ? serial : "?",
|
||||
ids.empty() ? "missing" : ids.front().c_str(),
|
||||
EmulatorSettings.IsShadNetEnabled() ? "; online play on" : "");
|
||||
}
|
||||
|
||||
// ---- Registration ------------------------------------------------------------------------------
|
||||
|
||||
extern "C" void bbnet_register(void) {
|
||||
Core::Loader::SymbolsResolver resolver;
|
||||
Core::Loader::SymbolsResolver* sym = &resolver;
|
||||
Libraries::Net::RegisterLib(sym);
|
||||
Libraries::NetCtl::RegisterLib(sym);
|
||||
Libraries::Http::RegisterLib(sym);
|
||||
Libraries::Http2::RegisterLib(sym);
|
||||
Libraries::Ssl::RegisterLib(sym);
|
||||
Libraries::Ssl2::RegisterLib(sym);
|
||||
Libraries::Np::NpAuth::RegisterLib(sym);
|
||||
Libraries::Np::NpCommon::RegisterLib(sym);
|
||||
Libraries::Np::NpManager::RegisterLib(sym);
|
||||
Libraries::Np::NpMatching2::RegisterLib(sym);
|
||||
Libraries::Np::NpPartner::RegisterLib(sym);
|
||||
Libraries::Np::NpParty::RegisterLib(sym);
|
||||
Libraries::Np::NpScore::RegisterLib(sym);
|
||||
Libraries::Np::NpSignaling::RegisterLib(sym);
|
||||
Libraries::Np::NpTus::RegisterLib(sym);
|
||||
Libraries::Np::NpWebApi::RegisterLib(sym);
|
||||
Libraries::Np::NpWebApi2::RegisterLib(sym);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
# SPDX-FileCopyrightText: Copyright 2026 IFreemz (shadps4_dlss bridge), bbport contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
#
|
||||
# The bridge links the NVIDIA DLSS SDK and is built separately from the port:
|
||||
# Linux: bash tools/build_dlss.sh (fetches the SDK files, builds out/gpu/libbbport_dlss.so)
|
||||
# Windows: cmake -S gpu/dlss_bridge -B out/dlss-bridge -G "Visual Studio 17 2022" -A x64 \
|
||||
# -DDLSS_SDK_ROOT=<github.com/NVIDIA/DLSS checkout> -DVULKAN_INCLUDE=<Vulkan-Headers include>
|
||||
# cmake --build out/dlss-bridge --config Release
|
||||
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(bbport_dlss LANGUAGES CXX)
|
||||
|
||||
set(DLSS_SDK_ROOT "" CACHE PATH "NVIDIA DLSS SDK root (contains include/ and lib/)")
|
||||
set(VULKAN_INCLUDE "" CACHE PATH "Vulkan-Headers include directory")
|
||||
|
||||
if (NOT CMAKE_SIZEOF_VOID_P EQUAL 8 OR NOT (WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux"))
|
||||
message(FATAL_ERROR "The DLSS bridge is for Windows x64 and Linux x86-64")
|
||||
endif()
|
||||
if (NOT EXISTS "${DLSS_SDK_ROOT}/include/nvsdk_ngx_vk.h")
|
||||
message(FATAL_ERROR "Set DLSS_SDK_ROOT to the NVIDIA DLSS SDK (github.com/NVIDIA/DLSS)")
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
add_library(bbport_dlss SHARED bbport_dlss_bridge.cpp bbport_dlss_bridge.h)
|
||||
target_include_directories(bbport_dlss PRIVATE "${DLSS_SDK_ROOT}/include")
|
||||
if (VULKAN_INCLUDE)
|
||||
target_include_directories(bbport_dlss PRIVATE "${VULKAN_INCLUDE}")
|
||||
endif()
|
||||
if (WIN32)
|
||||
# nvsdk_ngx_s.lib is the static-CRT (/MT) variant: no Visual C++ runtime install needed.
|
||||
set_property(TARGET bbport_dlss PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded")
|
||||
target_link_libraries(bbport_dlss PRIVATE "${DLSS_SDK_ROOT}/lib/Windows_x86_64/x64/nvsdk_ngx_s.lib")
|
||||
target_compile_definitions(bbport_dlss PRIVATE NOMINMAX WIN32_LEAN_AND_MEAN)
|
||||
else()
|
||||
# libnvsdk_ngx.a loads the driver's NGX core (libnvidia-ngx.so.1) at run time.
|
||||
set_target_properties(bbport_dlss PROPERTIES CXX_VISIBILITY_PRESET hidden)
|
||||
target_link_libraries(bbport_dlss PRIVATE "${DLSS_SDK_ROOT}/lib/Linux_x86_64/libnvsdk_ngx.a"
|
||||
${CMAKE_DL_LIBS})
|
||||
# Only BbDlssGetApi is exported, not the SDK's own functions.
|
||||
target_link_options(bbport_dlss PRIVATE -Wl,--exclude-libs,ALL)
|
||||
endif()
|
||||
@@ -0,0 +1,27 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 IFreemz
|
||||
Copyright (c) 2026 bbport contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
This license covers the files in this directory only. The bridge is adapted from
|
||||
the shadps4_dlss bridge of https://github.com/IFreemz/shadPS4-Bloodborne-DLSS-FSR.
|
||||
Building it requires the NVIDIA DLSS SDK (https://github.com/NVIDIA/DLSS), which is
|
||||
licensed separately by NVIDIA Corporation and is not included in this repository.
|
||||
@@ -0,0 +1,255 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 IFreemz (shadps4_dlss bridge), bbport contributors
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// bbport_dlss.dll: the only part of the project that uses the NVIDIA DLSS (NGX) SDK. Built with
|
||||
// MSVC (the SDK's libraries are MSVC only); the port loads it at run time.
|
||||
// Adapted from IFreemz/shadPS4-Bloodborne-DLSS-FSR (dlss_bridge).
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include "bbport_dlss_bridge.h" // Vulkan first: the NGX headers expect its types
|
||||
|
||||
#include <nvsdk_ngx_helpers.h>
|
||||
#include <nvsdk_ngx_vk.h>
|
||||
#include <nvsdk_ngx_helpers_vk.h>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char ProjectId[] = "42359704-c9f3-4806-9fb5-d469000fdb8a";
|
||||
constexpr char EngineVersion[] = "bbport";
|
||||
|
||||
struct State {
|
||||
BbDlssLogFn log{};
|
||||
std::wstring dll_directory;
|
||||
std::wstring data_directory;
|
||||
const wchar_t* dll_path{};
|
||||
NVSDK_NGX_FeatureCommonInfo common{};
|
||||
NVSDK_NGX_FeatureDiscoveryInfo discovery{};
|
||||
NVSDK_NGX_Parameter* capabilities{};
|
||||
NVSDK_NGX_Parameter* parameters{};
|
||||
NVSDK_NGX_Handle* feature{};
|
||||
BbDlssFeature feature_desc{};
|
||||
VkDevice device{};
|
||||
bool configured{};
|
||||
bool initialized{};
|
||||
} state;
|
||||
|
||||
template <typename... Args>
|
||||
void Log(int warning, const char* format, Args... args) {
|
||||
if (!state.log)
|
||||
return;
|
||||
std::array<char, 1024> text{};
|
||||
std::snprintf(text.data(), text.size(), format, args...);
|
||||
state.log(warning, text.data());
|
||||
}
|
||||
|
||||
bool Check(const char* operation, NVSDK_NGX_Result result) {
|
||||
if (NVSDK_NGX_FAILED(result)) {
|
||||
Log(1, "%s failed: 0x%08x", operation, static_cast<unsigned>(result));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void NVSDK_CONV NgxLog(const char* message, NVSDK_NGX_Logging_Level, NVSDK_NGX_Feature) {
|
||||
Log(0, "NGX: %s", message ? message : "");
|
||||
}
|
||||
|
||||
int32_t Configure(const wchar_t* dll_directory, const wchar_t* data_directory, BbDlssLogFn log) {
|
||||
state.log = log;
|
||||
state.dll_directory = dll_directory ? dll_directory : L".";
|
||||
state.data_directory = data_directory ? data_directory : L".";
|
||||
state.dll_path = state.dll_directory.c_str();
|
||||
state.common.PathListInfo = {&state.dll_path, 1};
|
||||
state.common.LoggingInfo = {NgxLog, NVSDK_NGX_LOGGING_LEVEL_OFF, false};
|
||||
state.discovery.SDKVersion = NVSDK_NGX_Version_API;
|
||||
state.discovery.FeatureID = NVSDK_NGX_Feature_SuperSampling;
|
||||
state.discovery.Identifier.IdentifierType = NVSDK_NGX_Application_Identifier_Type_Project_Id;
|
||||
state.discovery.Identifier.v.ProjectDesc = {ProjectId, NVSDK_NGX_ENGINE_TYPE_CUSTOM,
|
||||
EngineVersion};
|
||||
state.discovery.ApplicationDataPath = state.data_directory.c_str();
|
||||
state.discovery.FeatureInfo = &state.common;
|
||||
state.configured = true;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int32_t InstanceExtensions(uint32_t* count, const VkExtensionProperties** extensions) {
|
||||
VkExtensionProperties* required{};
|
||||
if (!state.configured ||
|
||||
!Check("Instance extension query",
|
||||
NVSDK_NGX_VULKAN_GetFeatureInstanceExtensionRequirements(&state.discovery, count,
|
||||
&required)))
|
||||
return 0;
|
||||
*extensions = required;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int32_t DeviceExtensions(VkInstance instance, VkPhysicalDevice physical, uint32_t* count,
|
||||
const VkExtensionProperties** extensions) {
|
||||
if (!state.configured)
|
||||
return 0;
|
||||
NVSDK_NGX_FeatureRequirement support{};
|
||||
if (!Check("Feature requirement query",
|
||||
NVSDK_NGX_VULKAN_GetFeatureRequirements(instance, physical, &state.discovery,
|
||||
&support)))
|
||||
return 0;
|
||||
if (support.FeatureSupported != NVSDK_NGX_FeatureSupportResult_Supported) {
|
||||
Log(1, "DLSS is not supported on this GPU or driver (code 0x%x)",
|
||||
static_cast<unsigned>(support.FeatureSupported));
|
||||
return 0;
|
||||
}
|
||||
VkExtensionProperties* required{};
|
||||
if (!Check("Device extension query",
|
||||
NVSDK_NGX_VULKAN_GetFeatureDeviceExtensionRequirements(
|
||||
instance, physical, &state.discovery, count, &required)))
|
||||
return 0;
|
||||
*extensions = required;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int32_t Initialize(VkInstance instance, VkPhysicalDevice physical, VkDevice device,
|
||||
PFN_vkGetInstanceProcAddr get_instance_proc,
|
||||
PFN_vkGetDeviceProcAddr get_device_proc) {
|
||||
if (!state.configured || state.initialized)
|
||||
return 0;
|
||||
if (!Check("NGX initialization",
|
||||
NVSDK_NGX_VULKAN_Init_with_ProjectID(
|
||||
ProjectId, NVSDK_NGX_ENGINE_TYPE_CUSTOM, EngineVersion,
|
||||
state.data_directory.c_str(), instance, physical, device, get_instance_proc,
|
||||
get_device_proc, &state.common)))
|
||||
return 0;
|
||||
state.initialized = true;
|
||||
state.device = device;
|
||||
if (!Check("Capability query", NVSDK_NGX_VULKAN_GetCapabilityParameters(&state.capabilities)))
|
||||
return 0;
|
||||
int available{}, needs_driver{};
|
||||
NVSDK_NGX_Parameter_GetI(state.capabilities, NVSDK_NGX_Parameter_SuperSampling_Available,
|
||||
&available);
|
||||
NVSDK_NGX_Parameter_GetI(state.capabilities,
|
||||
NVSDK_NGX_Parameter_SuperSampling_NeedsUpdatedDriver, &needs_driver);
|
||||
if (needs_driver) {
|
||||
Log(1, "DLSS needs a newer NVIDIA driver");
|
||||
return 0;
|
||||
}
|
||||
if (!available) {
|
||||
Log(1, "DLSS Super Resolution is not available on this system");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ReleaseFeature() {
|
||||
if (state.feature) {
|
||||
NVSDK_NGX_VULKAN_ReleaseFeature(state.feature);
|
||||
state.feature = nullptr;
|
||||
}
|
||||
if (state.parameters) {
|
||||
NVSDK_NGX_VULKAN_DestroyParameters(state.parameters);
|
||||
state.parameters = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t CreateFeature(VkCommandBuffer command, const BbDlssFeature* desc) {
|
||||
if (!state.initialized || !state.capabilities || !desc)
|
||||
return 0;
|
||||
ReleaseFeature();
|
||||
if (!Check("Parameter allocation", NVSDK_NGX_VULKAN_AllocateParameters(&state.parameters)))
|
||||
return 0;
|
||||
for (const char* key : {NVSDK_NGX_Parameter_DLSS_Hint_Render_Preset_DLAA,
|
||||
NVSDK_NGX_Parameter_DLSS_Hint_Render_Preset_Quality,
|
||||
NVSDK_NGX_Parameter_DLSS_Hint_Render_Preset_Balanced,
|
||||
NVSDK_NGX_Parameter_DLSS_Hint_Render_Preset_Performance,
|
||||
NVSDK_NGX_Parameter_DLSS_Hint_Render_Preset_UltraPerformance,
|
||||
NVSDK_NGX_Parameter_DLSS_Hint_Render_Preset_UltraQuality})
|
||||
NVSDK_NGX_Parameter_SetUI(state.parameters, key, desc->preset);
|
||||
static constexpr std::array qualities{
|
||||
NVSDK_NGX_PerfQuality_Value_DLAA, NVSDK_NGX_PerfQuality_Value_MaxQuality,
|
||||
NVSDK_NGX_PerfQuality_Value_Balanced, NVSDK_NGX_PerfQuality_Value_MaxPerf,
|
||||
NVSDK_NGX_PerfQuality_Value_UltraPerformance};
|
||||
NVSDK_NGX_DLSS_Create_Params settings{};
|
||||
settings.Feature.InWidth = desc->input_width;
|
||||
settings.Feature.InHeight = desc->input_height;
|
||||
settings.Feature.InTargetWidth = desc->output_width;
|
||||
settings.Feature.InTargetHeight = desc->output_height;
|
||||
settings.Feature.InPerfQualityValue = qualities[std::clamp(desc->quality, 0, 4)];
|
||||
settings.InFeatureCreateFlags =
|
||||
NVSDK_NGX_DLSS_Feature_Flags_MVLowRes |
|
||||
(desc->depth_inverted ? NVSDK_NGX_DLSS_Feature_Flags_DepthInverted : 0) |
|
||||
(desc->hdr ? NVSDK_NGX_DLSS_Feature_Flags_IsHDR | NVSDK_NGX_DLSS_Feature_Flags_AutoExposure
|
||||
: 0);
|
||||
const auto result = NGX_VULKAN_CREATE_DLSS_EXT1(state.device, command, 1, 1, &state.feature,
|
||||
state.parameters, &settings);
|
||||
if (!Check("DLSS feature creation", result) || !state.feature) {
|
||||
state.feature = nullptr;
|
||||
return 0;
|
||||
}
|
||||
state.feature_desc = *desc;
|
||||
Log(0, "DLSS %ux%u -> %ux%u, quality %d, preset %u%s", desc->input_width, desc->input_height,
|
||||
desc->output_width, desc->output_height, desc->quality, desc->preset,
|
||||
desc->hdr ? ", HDR input" : "");
|
||||
return 1;
|
||||
}
|
||||
|
||||
NVSDK_NGX_Resource_VK Wrap(const BbDlssImage& image, bool writable) {
|
||||
return NVSDK_NGX_Create_ImageView_Resource_VK(image.view, image.image, image.range,
|
||||
image.format, image.width, image.height,
|
||||
writable);
|
||||
}
|
||||
|
||||
int32_t Evaluate(VkCommandBuffer command, const BbDlssEvaluate* evaluate) {
|
||||
if (!state.feature || !state.parameters || !evaluate)
|
||||
return 0;
|
||||
auto color = Wrap(evaluate->color, false);
|
||||
auto depth = Wrap(evaluate->depth, false);
|
||||
auto motion = Wrap(evaluate->motion, false);
|
||||
auto output = Wrap(evaluate->output, true);
|
||||
NVSDK_NGX_VK_DLSS_Eval_Params parameters{};
|
||||
parameters.Feature.pInColor = &color;
|
||||
parameters.Feature.pInOutput = &output;
|
||||
parameters.Feature.InSharpness = evaluate->sharpness;
|
||||
parameters.pInDepth = &depth;
|
||||
parameters.pInMotionVectors = &motion;
|
||||
parameters.InRenderSubrectDimensions = {state.feature_desc.input_width,
|
||||
state.feature_desc.input_height};
|
||||
parameters.InJitterOffsetX = evaluate->jitter_x;
|
||||
parameters.InJitterOffsetY = evaluate->jitter_y;
|
||||
parameters.InReset = evaluate->reset;
|
||||
parameters.InMVScaleX = parameters.InMVScaleY = 1.0f;
|
||||
parameters.InPreExposure = parameters.InExposureScale = 1.0f;
|
||||
parameters.InFrameTimeDeltaInMsec = evaluate->frame_ms;
|
||||
return Check("DLSS evaluation", NGX_VULKAN_EVALUATE_DLSS_EXT(command, state.feature,
|
||||
state.parameters, ¶meters))
|
||||
? 1
|
||||
: 0;
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
ReleaseFeature();
|
||||
if (state.capabilities) {
|
||||
NVSDK_NGX_VULKAN_DestroyParameters(state.capabilities);
|
||||
state.capabilities = nullptr;
|
||||
}
|
||||
if (state.initialized) {
|
||||
NVSDK_NGX_VULKAN_Shutdown1(state.device);
|
||||
state.initialized = false;
|
||||
state.device = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
const BbDlssApi api{BBPORT_DLSS_BRIDGE_ABI, Configure, InstanceExtensions, DeviceExtensions,
|
||||
Initialize, CreateFeature, Evaluate, ReleaseFeature,
|
||||
Shutdown};
|
||||
|
||||
} // namespace
|
||||
|
||||
#ifdef _WIN32
|
||||
#define BBPORT_DLSS_EXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define BBPORT_DLSS_EXPORT __attribute__((visibility("default")))
|
||||
#endif
|
||||
extern "C" BBPORT_DLSS_EXPORT const BbDlssApi* BbDlssGetApi() {
|
||||
return &api;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 IFreemz (shadps4_dlss bridge), bbport contributors
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// C interface between bbport and bbport_dlss.dll. The port contains no NVIDIA code: it loads the
|
||||
// bridge at run time when it is present (next to bb-probe.exe, with nvngx_dlss.dll) and keeps
|
||||
// its other upscalers when it is absent. Adapted from IFreemz/shadPS4-Bloodborne-DLSS-FSR.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define BBPORT_DLSS_BRIDGE_ABI 1
|
||||
|
||||
typedef void (*BbDlssLogFn)(int warning, const char* message);
|
||||
|
||||
typedef struct BbDlssImage {
|
||||
VkImage image;
|
||||
VkImageView view;
|
||||
VkImageSubresourceRange range;
|
||||
VkFormat format;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
} BbDlssImage;
|
||||
|
||||
typedef struct BbDlssFeature {
|
||||
uint32_t input_width;
|
||||
uint32_t input_height;
|
||||
uint32_t output_width;
|
||||
uint32_t output_height;
|
||||
int32_t quality; // 0 DLAA, 1 Quality, 2 Balanced, 3 Performance, 4 Ultra Performance
|
||||
int32_t depth_inverted;
|
||||
uint32_t preset; // NVSDK_NGX_DLSS_Hint_Render_Preset value, 0 = driver default
|
||||
int32_t hdr; // 1: linear HDR colour with automatic exposure, 0: tonemapped colour
|
||||
} BbDlssFeature;
|
||||
|
||||
typedef struct BbDlssEvaluate {
|
||||
BbDlssImage color; // sampled
|
||||
BbDlssImage depth; // sampled, hardware depth
|
||||
BbDlssImage motion; // sampled, render-resolution pixels, current to previous
|
||||
BbDlssImage output; // storage, general layout
|
||||
float jitter_x;
|
||||
float jitter_y;
|
||||
int32_t reset;
|
||||
float frame_ms;
|
||||
float sharpness; // 0..1; 0 leaves the output unsharpened
|
||||
} BbDlssEvaluate;
|
||||
|
||||
typedef struct BbDlssApi {
|
||||
uint32_t abi;
|
||||
// Wide paths (wchar_t, as NGX takes them): the directory with NVIDIA's DLSS library, and a
|
||||
// writable data directory.
|
||||
int32_t (*Configure)(const wchar_t* dll_directory, const wchar_t* data_directory,
|
||||
BbDlssLogFn log);
|
||||
// Vulkan extensions NGX needs. The arrays stay valid until Shutdown.
|
||||
int32_t (*InstanceExtensions)(uint32_t* count, const VkExtensionProperties** extensions);
|
||||
int32_t (*DeviceExtensions)(VkInstance instance, VkPhysicalDevice physical, uint32_t* count,
|
||||
const VkExtensionProperties** extensions);
|
||||
int32_t (*Initialize)(VkInstance instance, VkPhysicalDevice physical, VkDevice device,
|
||||
PFN_vkGetInstanceProcAddr get_instance_proc,
|
||||
PFN_vkGetDeviceProcAddr get_device_proc);
|
||||
int32_t (*CreateFeature)(VkCommandBuffer command, const BbDlssFeature* feature);
|
||||
int32_t (*Evaluate)(VkCommandBuffer command, const BbDlssEvaluate* evaluate);
|
||||
// The caller must have waited for the GPU work that used the feature.
|
||||
void (*ReleaseFeature)(void);
|
||||
void (*Shutdown)(void);
|
||||
} BbDlssApi;
|
||||
|
||||
typedef const BbDlssApi* (*BbDlssGetApiFn)(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,332 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/network/ssl.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
namespace Libraries::Http {
|
||||
|
||||
enum OrbisHttpNBEvents : u32 {
|
||||
ORBIS_HTTP_NB_EVENT_IN = 0x00000001U, // Ready to receive (response data available).
|
||||
ORBIS_HTTP_NB_EVENT_OUT = 0x00000002U, // Ready to send.
|
||||
ORBIS_HTTP_NB_EVENT_SOCK_ERR = 0x00000008U, // Socket-level error during send/recv.
|
||||
ORBIS_HTTP_NB_EVENT_HUP = 0x00000010U, // Request interrupted by the application.
|
||||
ORBIS_HTTP_NB_EVENT_RESOLVED = 0x00010000U, // DNS resolution completed.
|
||||
ORBIS_HTTP_NB_EVENT_RESOLVER_ERR = 0x00020000U, // DNS resolution failed.
|
||||
};
|
||||
|
||||
enum OrbisHttpsFlags : u32 {
|
||||
ORBIS_HTTPS_FLAG_SERVER_VERIFY = 0x01,
|
||||
ORBIS_HTTPS_FLAG_CLIENT_VERIFY = 0x02,
|
||||
ORBIS_HTTPS_FLAG_CN_CHECK = 0x04,
|
||||
ORBIS_HTTPS_FLAG_NOT_AFTER_CHECK = 0x08,
|
||||
ORBIS_HTTPS_FLAG_NOT_BEFORE_CHECK = 0x10,
|
||||
ORBIS_HTTPS_FLAG_KNOWN_CA_CHECK = 0x20,
|
||||
ORBIS_HTTPS_FLAG_SESSION_REUSE = 0x40,
|
||||
ORBIS_HTTPS_FLAG_SNI = 0x80,
|
||||
};
|
||||
|
||||
// Used as the initial value of HttpSettings::ssl_flags.
|
||||
constexpr u32 ORBIS_HTTPS_FLAG_SDK_DEFAULT = ORBIS_HTTPS_FLAG_SERVER_VERIFY |
|
||||
ORBIS_HTTPS_FLAG_CN_CHECK |
|
||||
ORBIS_HTTPS_FLAG_KNOWN_CA_CHECK | ORBIS_HTTPS_FLAG_SNI;
|
||||
|
||||
// Validation masks consumed by sceHttpsEnableOption / sceHttpsDisableOption
|
||||
constexpr u32 ORBIS_HTTPS_FLAG_PUBLIC_VALID = 0x000020ff;
|
||||
constexpr u32 ORBIS_HTTPS_FLAG_PRIVATE_VALID = 0x00002dff;
|
||||
|
||||
enum OrbisHttpMethod : s32 {
|
||||
ORBIS_HTTP_METHOD_GET = 0,
|
||||
ORBIS_HTTP_METHOD_POST = 1,
|
||||
ORBIS_HTTP_METHOD_HEAD = 2,
|
||||
ORBIS_HTTP_METHOD_OPTIONS = 3,
|
||||
ORBIS_HTTP_METHOD_PUT = 4,
|
||||
ORBIS_HTTP_METHOD_DELETE = 5,
|
||||
ORBIS_HTTP_METHOD_TRACE = 6,
|
||||
ORBIS_HTTP_METHOD_CONNECT = 7,
|
||||
ORBIS_HTTP_METHOD_CUSTOM = 8,
|
||||
};
|
||||
|
||||
// mode argument for sceHttpAddRequestHeader.
|
||||
enum OrbisHttpHeaderModes : s32 {
|
||||
ORBIS_HTTP_HEADER_OVERWRITE = 0,
|
||||
ORBIS_HTTP_HEADER_ADD = 1,
|
||||
};
|
||||
|
||||
enum OrbisUriBuild : s32 {
|
||||
ORBIS_HTTP_URI_BUILD_WITH_SCHEME = 0x01,
|
||||
ORBIS_HTTP_URI_BUILD_WITH_HOSTNAME = 0x02,
|
||||
ORBIS_HTTP_URI_BUILD_WITH_PORT = 0x04,
|
||||
ORBIS_HTTP_URI_BUILD_WITH_PATH = 0x08,
|
||||
ORBIS_HTTP_URI_BUILD_WITH_USERNAME = 0x10,
|
||||
ORBIS_HTTP_URI_BUILD_WITH_PASSWORD = 0x20,
|
||||
ORBIS_HTTP_URI_BUILD_WITH_QUERY = 0x40,
|
||||
ORBIS_HTTP_URI_BUILD_WITH_FRAGMENT = 0x80
|
||||
};
|
||||
|
||||
struct OrbisHttpUriElement {
|
||||
bool opaque;
|
||||
char* scheme;
|
||||
char* username;
|
||||
char* password;
|
||||
char* hostname;
|
||||
char* path;
|
||||
char* query;
|
||||
char* fragment;
|
||||
u16 port;
|
||||
u8 reserved[10];
|
||||
};
|
||||
|
||||
struct HttpRequestInternal {
|
||||
int state; // +0x20
|
||||
int errorCode; // +0x28
|
||||
int httpStatusCode; // +0x20C
|
||||
std::mutex m_mutex;
|
||||
};
|
||||
|
||||
// Memory pool statistics
|
||||
struct OrbisHttpMemoryPoolStats {
|
||||
u64 poolSize;
|
||||
u64 maxInuseSize;
|
||||
u64 currentInuseSize;
|
||||
s32 reserved;
|
||||
};
|
||||
|
||||
// Cookie statistics
|
||||
struct OrbisHttpCookieStats {
|
||||
u64 currentInuseSize;
|
||||
u32 currentInuseNum;
|
||||
u64 maxInuseSize;
|
||||
u32 maxInuseNum;
|
||||
u32 removedNum;
|
||||
s32 reserved;
|
||||
};
|
||||
|
||||
using OrbisHttpEpollHandle = void*;
|
||||
|
||||
// Non-blocking event reported by sceHttpWaitRequest.
|
||||
struct OrbisHttpNBEvent {
|
||||
u32 events;
|
||||
u32 eventDetail;
|
||||
int id;
|
||||
void* userArg;
|
||||
};
|
||||
|
||||
// Callbacks
|
||||
using OrbisHttpAuthInfoCallback = int(PS4_SYSV_ABI*)(int request, int authType, const char* realm,
|
||||
char* username, char* password,
|
||||
int isNeedEntity, u8** entityBody,
|
||||
u64* entitySize, int* isSave, void* userArg);
|
||||
|
||||
using OrbisHttpRedirectCallback = int(PS4_SYSV_ABI*)(int request, s32 statusCode, s32* method,
|
||||
const char* location, void* userArg);
|
||||
|
||||
using OrbisHttpRequestStatusCallback = void(PS4_SYSV_ABI*)(int request, int requestStatus,
|
||||
void* userArg);
|
||||
|
||||
using OrbisHttpCookieRecvCallback = int(PS4_SYSV_ABI*)(int request, const char* url,
|
||||
const char* cookieHeader, u64 headerLen,
|
||||
void* userArg);
|
||||
|
||||
using OrbisHttpCookieSendCallback = int(PS4_SYSV_ABI*)(int request, const char* url,
|
||||
const char* cookieHeader, void* userArg);
|
||||
|
||||
using OrbisHttpsCallback = int(PS4_SYSV_ABI*)(int libsslCtxId, u32 verifyErr, void* const sslCert[],
|
||||
int certNum, void* userArg);
|
||||
|
||||
using OrbisHttpsCaList = Libraries::Ssl::OrbisSslCaList;
|
||||
|
||||
// Functions
|
||||
int PS4_SYSV_ABI sceHttpAddCookie(int libhttpCtxId, const char* url, const char* cookie,
|
||||
u64 cookieLength);
|
||||
int PS4_SYSV_ABI sceHttpAddQuery();
|
||||
int PS4_SYSV_ABI sceHttpAddRequestHeaderRaw();
|
||||
int PS4_SYSV_ABI sceHttpAuthCacheExport();
|
||||
int PS4_SYSV_ABI sceHttpAuthCacheFlush(int libhttpCtxId);
|
||||
int PS4_SYSV_ABI sceHttpAuthCacheImport();
|
||||
int PS4_SYSV_ABI sceHttpCacheRedirectedConnectionEnabled(int id, int isEnable);
|
||||
int PS4_SYSV_ABI sceHttpCookieExport(int libhttpCtxId, void* buffer, u64 bufferSize,
|
||||
u64* exportSize);
|
||||
int PS4_SYSV_ABI sceHttpCookieFlush(int libhttpCtxId);
|
||||
int PS4_SYSV_ABI sceHttpCookieImport(int libhttpCtxId, const void* buffer, u64 bufferSize);
|
||||
int PS4_SYSV_ABI sceHttpCreateConnection(int tmplId, const char* serverName, const char* scheme,
|
||||
u16 port, int isEnableKeepalive);
|
||||
int PS4_SYSV_ABI sceHttpCreateConnectionWithURL(int tmplId, const char* url, bool enableKeepalive);
|
||||
int PS4_SYSV_ABI sceHttpCreateRequest(int connId, int method, const char* path, u64 contentLength);
|
||||
int PS4_SYSV_ABI sceHttpCreateRequestWithURL(int connId, s32 method, const char* url,
|
||||
u64 contentLength);
|
||||
int PS4_SYSV_ABI sceHttpCreateTemplate(int libhttpCtxId, const char* userAgent, int httpVer,
|
||||
int isAutoProxyConf);
|
||||
int PS4_SYSV_ABI sceHttpDbgEnableProfile();
|
||||
int PS4_SYSV_ABI sceHttpDbgGetConnectionStat();
|
||||
int PS4_SYSV_ABI sceHttpDbgGetRequestStat();
|
||||
int PS4_SYSV_ABI sceHttpDbgSetPrintf();
|
||||
int PS4_SYSV_ABI sceHttpDbgShowConnectionStat();
|
||||
int PS4_SYSV_ABI sceHttpDbgShowMemoryPoolStat();
|
||||
int PS4_SYSV_ABI sceHttpDbgShowRequestStat();
|
||||
int PS4_SYSV_ABI sceHttpDbgShowStat();
|
||||
int PS4_SYSV_ABI sceHttpGetAuthEnabled(int id, int* isEnable);
|
||||
int PS4_SYSV_ABI sceHttpGetConnectionStat();
|
||||
int PS4_SYSV_ABI sceHttpGetCookie(int libhttpCtxId, const char* url, char* cookie, u64* required,
|
||||
u64 prepared, int isSecure);
|
||||
int PS4_SYSV_ABI sceHttpGetCookieEnabled(int id, int* isEnable);
|
||||
int PS4_SYSV_ABI sceHttpGetCookieStats(int libhttpCtxId, OrbisHttpCookieStats* stats);
|
||||
int PS4_SYSV_ABI sceHttpGetEpollId();
|
||||
int PS4_SYSV_ABI sceHttpGetMemoryPoolStats(int libhttpCtxId, OrbisHttpMemoryPoolStats* currentStat);
|
||||
int PS4_SYSV_ABI sceHttpGetRegisteredCtxIds();
|
||||
int PS4_SYSV_ABI sceHttpInit(int libnetMemId, int libsslCtxId, u64 poolSize);
|
||||
int PS4_SYSV_ABI sceHttpRedirectCacheFlush(int libhttpCtxId);
|
||||
int PS4_SYSV_ABI sceHttpRequestGetAllHeaders();
|
||||
int PS4_SYSV_ABI sceHttpSendRequest(int reqId, const void* postData, u64 size);
|
||||
int PS4_SYSV_ABI sceHttpSetAuthEnabled(int id, int isEnable);
|
||||
int PS4_SYSV_ABI sceHttpSetAuthInfoCallback(int id, OrbisHttpAuthInfoCallback cbfunc,
|
||||
void* userArg);
|
||||
int PS4_SYSV_ABI sceHttpSetChunkedTransferEnabled(int id, int isEnable);
|
||||
int PS4_SYSV_ABI sceHttpSetCookieEnabled(int id, int isEnable);
|
||||
int PS4_SYSV_ABI sceHttpSetCookieMaxNum(int libhttpCtxId, u32 num);
|
||||
int PS4_SYSV_ABI sceHttpSetCookieMaxNumPerDomain(int libhttpCtxId, u32 num);
|
||||
int PS4_SYSV_ABI sceHttpSetCookieMaxSize(int libhttpCtxId, u32 size);
|
||||
int PS4_SYSV_ABI sceHttpSetCookieRecvCallback(int id, OrbisHttpCookieRecvCallback cbfunc,
|
||||
void* userArg);
|
||||
int PS4_SYSV_ABI sceHttpSetCookieSendCallback(int id, OrbisHttpCookieSendCallback cbfunc,
|
||||
void* userArg);
|
||||
int PS4_SYSV_ABI sceHttpSetCookieTotalMaxSize(int libhttpCtxId, u32 size);
|
||||
int PS4_SYSV_ABI sceHttpSetDefaultAcceptEncodingGZIPEnabled(int libhttpCtxId, int isEnable);
|
||||
int PS4_SYSV_ABI sceHttpSetDelayBuildRequestEnabled(int id, int isEnable);
|
||||
int PS4_SYSV_ABI sceHttpSetEpoll(int id, OrbisHttpEpollHandle eh, void* userArg);
|
||||
int PS4_SYSV_ABI sceHttpSetEpollId();
|
||||
int PS4_SYSV_ABI sceHttpSetHttp09Enabled(int id, int isEnable);
|
||||
int PS4_SYSV_ABI sceHttpSetPolicyOption();
|
||||
int PS4_SYSV_ABI sceHttpSetPriorityOption();
|
||||
int PS4_SYSV_ABI sceHttpSetRedirectCallback(int id, OrbisHttpRedirectCallback cbfunc,
|
||||
void* userArg);
|
||||
int PS4_SYSV_ABI sceHttpSetRequestStatusCallback(int id, OrbisHttpRequestStatusCallback cbfunc,
|
||||
void* userArg);
|
||||
int PS4_SYSV_ABI sceHttpSetSocketCreationCallback();
|
||||
int PS4_SYSV_ABI sceHttpsFreeCaList(int libhttpCtxId, OrbisHttpsCaList* caList);
|
||||
int PS4_SYSV_ABI sceHttpsGetCaList(int httpCtxId, OrbisHttpsCaList* list);
|
||||
int PS4_SYSV_ABI sceHttpsGetSslError(int id, int* errNum, u32* detail);
|
||||
int PS4_SYSV_ABI sceHttpsLoadCert(int libhttpCtxId, int caCertNum, const void** caList,
|
||||
const void* cert, const void* privKey);
|
||||
int PS4_SYSV_ABI sceHttpsSetMinSslVersion(int id, int version);
|
||||
int PS4_SYSV_ABI sceHttpsSetSslCallback(int id, OrbisHttpsCallback cbfunc, void* userArg);
|
||||
int PS4_SYSV_ABI sceHttpsSetSslVersion(int id, int version);
|
||||
int PS4_SYSV_ABI sceHttpsUnloadCert(int libhttpCtxId);
|
||||
int PS4_SYSV_ABI sceHttpWaitRequest(OrbisHttpEpollHandle eh, OrbisHttpNBEvent* nbev, int maxevents,
|
||||
int timeout);
|
||||
int PS4_SYSV_ABI sceHttpUriCopy();
|
||||
//***********************************
|
||||
// Init/Terminate functions
|
||||
//***********************************
|
||||
int PS4_SYSV_ABI sceHttpTerm(int libhttpCtxId);
|
||||
//***********************************
|
||||
// Misc functions
|
||||
//***********************************
|
||||
int PS4_SYSV_ABI sceHttpSetRecvBlockSize(int id, u32 blockSize);
|
||||
int PS4_SYSV_ABI sceHttpSetProxy(int id, int httpProxyConf, int wlanProxyConf, const char* host,
|
||||
u16 port);
|
||||
int PS4_SYSV_ABI sceHttpGetAcceptEncodingGZIPEnabled(int id, int* isEnable);
|
||||
int PS4_SYSV_ABI sceHttpSetDefaultAcceptEncodingGZIPEnabled(int libhttpCtxId, int isEnable);
|
||||
int PS4_SYSV_ABI sceHttpSetAcceptEncodingGZIPEnabled(int id, int isEnable);
|
||||
//***********************************
|
||||
// Non-blocking processing functions
|
||||
//***********************************
|
||||
int PS4_SYSV_ABI sceHttpCreateEpoll(int libhttpCtxId, OrbisHttpEpollHandle* eh);
|
||||
int PS4_SYSV_ABI sceHttpDestroyEpoll(int libhttpCtxId, OrbisHttpEpollHandle eh);
|
||||
int PS4_SYSV_ABI sceHttpGetEpoll(int id, OrbisHttpEpollHandle* eh, void** userArg);
|
||||
int PS4_SYSV_ABI sceHttpUnsetEpoll(int id);
|
||||
int PS4_SYSV_ABI sceHttpAbortWaitRequest(OrbisHttpEpollHandle eh);
|
||||
int PS4_SYSV_ABI sceHttpGetNonblock(int id, int* isEnable);
|
||||
int PS4_SYSV_ABI sceHttpSetNonblock(int id, int isEnable);
|
||||
int PS4_SYSV_ABI sceHttpTryGetNonblock(int id, int* isEnable);
|
||||
int PS4_SYSV_ABI sceHttpTrySetNonblock(int id, int isEnable);
|
||||
//***********************************
|
||||
// Http Communication functions
|
||||
//***********************************
|
||||
int PS4_SYSV_ABI sceHttpReadData(s32 reqId, void* data, u64 size);
|
||||
int PS4_SYSV_ABI sceHttpAbortRequest(int reqId);
|
||||
int PS4_SYSV_ABI sceHttpAbortRequestForce(int reqId);
|
||||
//***********************************
|
||||
// Https Option setting functions
|
||||
//***********************************
|
||||
int PS4_SYSV_ABI sceHttpsDisableOption(int id, u32 sslFlags);
|
||||
int PS4_SYSV_ABI sceHttpsDisableOptionPrivate(int id, u32 sslFlags);
|
||||
int PS4_SYSV_ABI sceHttpsEnableOption(int id, u32 sslFlags);
|
||||
int PS4_SYSV_ABI sceHttpsEnableOptionPrivate(int id, u32 sslFlags);
|
||||
//***********************************
|
||||
// Response Information functions
|
||||
//***********************************
|
||||
int PS4_SYSV_ABI sceHttpSetResolveTimeOut(int id, u32 usec);
|
||||
int PS4_SYSV_ABI sceHttpSetResponseHeaderMaxSize(int id, u64 headerSize);
|
||||
int PS4_SYSV_ABI sceHttpGetAllResponseHeaders(int reqId, char** header, u64* headerSize);
|
||||
int PS4_SYSV_ABI sceHttpGetResponseContentLength(int reqId, int* result, u64* contentLength);
|
||||
int PS4_SYSV_ABI sceHttpGetStatusCode(int reqId, int* statusCode);
|
||||
int PS4_SYSV_ABI sceHttpSetInflateGZIPEnabled(int id, int isEnable);
|
||||
//***********************************
|
||||
// Http Header setting functions
|
||||
//***********************************
|
||||
int PS4_SYSV_ABI sceHttpAddRequestHeader(int id, const char* name, const char* value, s32 mode);
|
||||
int PS4_SYSV_ABI sceHttpRemoveRequestHeader(int id, const char* name);
|
||||
int PS4_SYSV_ABI sceHttpSetRequestContentLength(int id, u64 contentLength);
|
||||
//***********************************
|
||||
// Redirection setting functions
|
||||
//***********************************
|
||||
int PS4_SYSV_ABI sceHttpGetAutoRedirect(int id, int* isEnable);
|
||||
int PS4_SYSV_ABI sceHttpSetAutoRedirect(int id, int isEnable);
|
||||
//***********************************
|
||||
// Timeout settting functions
|
||||
//***********************************
|
||||
int PS4_SYSV_ABI sceHttpSetResolveRetry(int id, int retry);
|
||||
int PS4_SYSV_ABI sceHttpSetConnectTimeOut(int id, u32 usec);
|
||||
int PS4_SYSV_ABI sceHttpSetSendTimeOut(int id, u32 usec);
|
||||
int PS4_SYSV_ABI sceHttpSetRecvTimeOut(int id, u32 usec);
|
||||
//***********************************
|
||||
// Connection functions
|
||||
//***********************************
|
||||
int PS4_SYSV_ABI sceHttpDeleteConnection(int connId);
|
||||
//***********************************
|
||||
// Template functions
|
||||
//***********************************
|
||||
int PS4_SYSV_ABI sceHttpDeleteTemplate(int tmplId);
|
||||
//***********************************
|
||||
// Request functions
|
||||
//***********************************
|
||||
int PS4_SYSV_ABI sceHttpDeleteRequest(int reqId);
|
||||
int PS4_SYSV_ABI sceHttpCreateRequest2(int connId, const char* method, const char* path,
|
||||
u64 contentLength);
|
||||
int PS4_SYSV_ABI sceHttpCreateRequestWithURL2(int connId, const char* method, const char* url,
|
||||
u64 contentLength);
|
||||
//***********************************
|
||||
// Error Obtainment functions
|
||||
//***********************************
|
||||
int PS4_SYSV_ABI sceHttpGetLastErrno(int reqId, int* errNum);
|
||||
//***********************************
|
||||
// HTTP Header Parsing functions
|
||||
//***********************************
|
||||
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 sceHttpParseResponseHeader(const char* header, u64 headerLen, const char* fieldStr,
|
||||
const char** fieldValue, u64* valueLen);
|
||||
//***********************************
|
||||
// URI functions
|
||||
//***********************************
|
||||
int PS4_SYSV_ABI sceHttpUriBuild(char* out, u64* require, u64 prepare,
|
||||
const OrbisHttpUriElement* srcElement, u32 option);
|
||||
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,
|
||||
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);
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::Http
|
||||
@@ -0,0 +1,417 @@
|
||||
// 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/libs.h"
|
||||
#include "core/libraries/network/http.h"
|
||||
#include "core/libraries/network/http2.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace Libraries::Http2 {
|
||||
|
||||
std::map<s32, s32> requests_to_connections{};
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2AbortRequest(s32 req_id) {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
s32 result = Libraries::Http::sceHttpAbortRequest(req_id);
|
||||
if (result < 0) {
|
||||
LOG_ERROR(Lib_Http2, "Failed to get abort HTTP request, error = {:#x}", result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2AddCookie() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2AddRequestHeader(s32 template_or_req_id, const char* name,
|
||||
const char* value, u32 mode) {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
s32 result = Libraries::Http::sceHttpAddRequestHeader(template_or_req_id, name, value, mode);
|
||||
if (result < 0) {
|
||||
LOG_ERROR(Lib_Http2, "Failed to add HTTP response header, error = {:#x}", result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2AuthCacheFlush() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2CookieExport() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2CookieFlush() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2CookieImport() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2CreateCookieBox() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2CreateRequestWithURL(s32 tmpl_id, const char* method, const char* url,
|
||||
u64 content_length) {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
// Http1 has a connection thing to go through first
|
||||
s32 conn_id = Libraries::Http::sceHttpCreateConnectionWithURL(tmpl_id, url, true);
|
||||
if (conn_id < 0) {
|
||||
LOG_ERROR(Lib_Http2, "Failed to create HTTP connection, error = {:#x}", conn_id);
|
||||
return conn_id;
|
||||
}
|
||||
s32 req_id =
|
||||
Libraries::Http::sceHttpCreateRequestWithURL2(conn_id, method, url, content_length);
|
||||
if (req_id < 0) {
|
||||
LOG_ERROR(Lib_Http2, "Failed to create HTTP request, error = {:#x}", req_id);
|
||||
return req_id;
|
||||
}
|
||||
requests_to_connections[req_id] = conn_id;
|
||||
return req_id;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2CreateTemplate(s32 ctx_id, const char* user_agent, s32 http_ver,
|
||||
s32 auto_proxy_conf) {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
s32 tmpl_id =
|
||||
Libraries::Http::sceHttpCreateTemplate(ctx_id, user_agent, http_ver, auto_proxy_conf);
|
||||
if (tmpl_id < 0) {
|
||||
LOG_ERROR(Lib_Http2, "Failed to create HTTP template, error = {:#x}", tmpl_id);
|
||||
}
|
||||
return tmpl_id;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2DeleteCookieBox() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2DeleteRequest(s32 req_id) {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
s32 result = Libraries::Http::sceHttpDeleteRequest(req_id);
|
||||
if (result < 0) {
|
||||
LOG_ERROR(Lib_Http2, "Failed to delete HTTP request, error = {:#x}", result);
|
||||
return result;
|
||||
}
|
||||
s32 conn_id = requests_to_connections[req_id];
|
||||
result = Libraries::Http::sceHttpDeleteConnection(conn_id);
|
||||
if (result < 0) {
|
||||
LOG_ERROR(Lib_Http2, "Failed to delete HTTP connection, error = {:#x}", result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2DeleteTemplate(s32 tmpl_id) {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
s32 result = Libraries::Http::sceHttpDeleteTemplate(tmpl_id);
|
||||
if (result < 0) {
|
||||
LOG_ERROR(Lib_Http2, "Failed to delete HTTP template, error = {:#x}", result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2GetAllResponseHeaders(s32 req_id, char** header, u64* header_size) {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
s32 result = Libraries::Http::sceHttpGetAllResponseHeaders(req_id, header, header_size);
|
||||
if (result < 0) {
|
||||
LOG_ERROR(Lib_Http2, "Failed to get HTTP response headers, error = {:#x}", result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2GetAuthEnabled() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2GetAutoRedirect() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2GetCookie() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2GetCookieBox() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2GetCookieStats() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2GetMemoryPoolStats() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2GetResponseContentLength() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2GetStatusCode(s32 req_id, s32* status_code) {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
s32 result = Libraries::Http::sceHttpGetStatusCode(req_id, status_code);
|
||||
if (result < 0) {
|
||||
LOG_ERROR(Lib_Http2, "Failed to get HTTP status code, error = {:#x}", result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2Init(s32 net_id, s32 ssl_id, u64 pool_size, s32 max_requests) {
|
||||
LOG_ERROR(Lib_Http2, "(DUMMY) called");
|
||||
s32 ctx_id = Libraries::Http::sceHttpInit(net_id, ssl_id, pool_size);
|
||||
if (ctx_id < 0) {
|
||||
LOG_ERROR(Lib_Http2, "Failed to init HTTP context, error = {:#x}", ctx_id);
|
||||
}
|
||||
return ctx_id;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2ReadData(s32 req_id, void* data, u64 size) {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
s32 result = Libraries::Http::sceHttpReadData(req_id, data, size);
|
||||
if (result < 0) {
|
||||
LOG_ERROR(Lib_Http2, "Failed to read HTTP data, error = {:#x}", result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2ReadDataAsync() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2RedirectCacheFlush() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2RemoveRequestHeader() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2SendRequest(s32 req_id, const void* data, u64 size) {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
s32 result = Libraries::Http::sceHttpSendRequest(req_id, data, size);
|
||||
if (result < 0) {
|
||||
LOG_ERROR(Lib_Http2, "Failed to send HTTP request, error = {:#x}", result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2SendRequestAsync() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2SetAuthEnabled() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2SetAuthInfoCallback() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2SetAutoRedirect() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2SetConnectionWaitTimeOut() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2SetConnectTimeOut() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2SetCookieBox() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2SetCookieMaxNum() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2SetCookieMaxNumPerDomain() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2SetCookieMaxSize() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2SetCookieRecvCallback() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2SetCookieSendCallback() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2SetInflateGZIPEnabled() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2SetMinSslVersion() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2SetPreSendCallback(s32 template_id, OrbisHttp2PreSendCallback cb_func,
|
||||
void* user_arg) {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2SetRecvTimeOut() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2SetRedirectCallback() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2SetRequestContentLength(s32 req_id, u64 content_length) {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
s32 result = Libraries::Http::sceHttpSetRequestContentLength(req_id, content_length);
|
||||
if (result < 0) {
|
||||
LOG_ERROR(Lib_Http2, "Failed to set HTTP request content length, error = {:#x}", result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2SetResolveRetry() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2SetResolveTimeOut() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2SetSendTimeOut() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2SetSslCallback() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2SetTimeOut() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2SslDisableOption() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2SslEnableOption() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2Term() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2WaitAsync() {
|
||||
LOG_ERROR(Lib_Http2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("IZ-qjhRqvjk", "libSceHttp2", 1, "libSceHttp2", sceHttp2AbortRequest);
|
||||
LIB_FUNCTION("flPxnowtvWY", "libSceHttp2", 1, "libSceHttp2", sceHttp2AddCookie);
|
||||
LIB_FUNCTION("nrPfOE8TQu0", "libSceHttp2", 1, "libSceHttp2", sceHttp2AddRequestHeader);
|
||||
LIB_FUNCTION("WeuDjj5m4YU", "libSceHttp2", 1, "libSceHttp2", sceHttp2AuthCacheFlush);
|
||||
LIB_FUNCTION("JlFGR4v50Kw", "libSceHttp2", 1, "libSceHttp2", sceHttp2CookieExport);
|
||||
LIB_FUNCTION("5VlQSzXW-SQ", "libSceHttp2", 1, "libSceHttp2", sceHttp2CookieFlush);
|
||||
LIB_FUNCTION("B5ibZI5UlzU", "libSceHttp2", 1, "libSceHttp2", sceHttp2CookieImport);
|
||||
LIB_FUNCTION("N4UfjvWJsMw", "libSceHttp2", 1, "libSceHttp2", sceHttp2CreateCookieBox);
|
||||
LIB_FUNCTION("mmyOCxQMVYQ", "libSceHttp2", 1, "libSceHttp2", sceHttp2CreateRequestWithURL);
|
||||
LIB_FUNCTION("+wCt7fCijgk", "libSceHttp2", 1, "libSceHttp2", sceHttp2CreateTemplate);
|
||||
LIB_FUNCTION("O9ync3F-JVI", "libSceHttp2", 1, "libSceHttp2", sceHttp2DeleteCookieBox);
|
||||
LIB_FUNCTION("c8D9qIjo8EY", "libSceHttp2", 1, "libSceHttp2", sceHttp2DeleteRequest);
|
||||
LIB_FUNCTION("pDom5-078DA", "libSceHttp2", 1, "libSceHttp2", sceHttp2DeleteTemplate);
|
||||
LIB_FUNCTION("-rdXUi2XW90", "libSceHttp2", 1, "libSceHttp2", sceHttp2GetAllResponseHeaders);
|
||||
LIB_FUNCTION("m-OL13q8AI8", "libSceHttp2", 1, "libSceHttp2", sceHttp2GetAuthEnabled);
|
||||
LIB_FUNCTION("od5QCZhZSfw", "libSceHttp2", 1, "libSceHttp2", sceHttp2GetAutoRedirect);
|
||||
LIB_FUNCTION("GQFGj0rYX+A", "libSceHttp2", 1, "libSceHttp2", sceHttp2GetCookie);
|
||||
LIB_FUNCTION("IX23slKvtQI", "libSceHttp2", 1, "libSceHttp2", sceHttp2GetCookieBox);
|
||||
LIB_FUNCTION("eij7UzkUqK8", "libSceHttp2", 1, "libSceHttp2", sceHttp2GetCookieStats);
|
||||
LIB_FUNCTION("otUQuZa-mv0", "libSceHttp2", 1, "libSceHttp2", sceHttp2GetMemoryPoolStats);
|
||||
LIB_FUNCTION("o0DBQpFE13o", "libSceHttp2", 1, "libSceHttp2", sceHttp2GetResponseContentLength);
|
||||
LIB_FUNCTION("9XYJwCf3lEA", "libSceHttp2", 1, "libSceHttp2", sceHttp2GetStatusCode);
|
||||
LIB_FUNCTION("3JCe3lCbQ8A", "libSceHttp2", 1, "libSceHttp2", sceHttp2Init);
|
||||
LIB_FUNCTION("QygCNNmbGss", "libSceHttp2", 1, "libSceHttp2", sceHttp2ReadData);
|
||||
LIB_FUNCTION("bGN-6zbo7ms", "libSceHttp2", 1, "libSceHttp2", sceHttp2ReadDataAsync);
|
||||
LIB_FUNCTION("klwUy2Wg+q8", "libSceHttp2", 1, "libSceHttp2", sceHttp2RedirectCacheFlush);
|
||||
LIB_FUNCTION("jHdP0CS4ZlA", "libSceHttp2", 1, "libSceHttp2", sceHttp2RemoveRequestHeader);
|
||||
LIB_FUNCTION("rbqZig38AT8", "libSceHttp2", 1, "libSceHttp2", sceHttp2SendRequest);
|
||||
LIB_FUNCTION("A+NVAFu4eCg", "libSceHttp2", 1, "libSceHttp2", sceHttp2SendRequestAsync);
|
||||
LIB_FUNCTION("jjFahkBPCYs", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetAuthEnabled);
|
||||
LIB_FUNCTION("Wwj6HbB2mOo", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetAuthInfoCallback);
|
||||
LIB_FUNCTION("b9AvoIaOuHI", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetAutoRedirect);
|
||||
LIB_FUNCTION("n8hMLe31OPA", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetConnectionWaitTimeOut);
|
||||
LIB_FUNCTION("-HIO4VT87v8", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetConnectTimeOut);
|
||||
LIB_FUNCTION("jrVHsKCXA0g", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetCookieBox);
|
||||
LIB_FUNCTION("mPKVhQqh2Es", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetCookieMaxNum);
|
||||
LIB_FUNCTION("o7+WXe4WadE", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetCookieMaxNumPerDomain);
|
||||
LIB_FUNCTION("6a0N6GPD7RM", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetCookieMaxSize);
|
||||
LIB_FUNCTION("zdtXKn9X7no", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetCookieRecvCallback);
|
||||
LIB_FUNCTION("McYmUpQ3-DY", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetCookieSendCallback);
|
||||
LIB_FUNCTION("uRosf8GQbHQ", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetInflateGZIPEnabled);
|
||||
LIB_FUNCTION("09tk+kIA1Ns", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetMinSslVersion);
|
||||
LIB_FUNCTION("UL4Fviw+IAM", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetPreSendCallback);
|
||||
LIB_FUNCTION("izvHhqgDt44", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetRecvTimeOut);
|
||||
LIB_FUNCTION("BJgi0CH7al4", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetRedirectCallback);
|
||||
LIB_FUNCTION("FSAFOzi0FpM", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetRequestContentLength);
|
||||
LIB_FUNCTION("Gcjh+CisAZM", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetResolveRetry);
|
||||
LIB_FUNCTION("ACjtE27aErY", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetResolveTimeOut);
|
||||
LIB_FUNCTION("XPtW45xiLHk", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetSendTimeOut);
|
||||
LIB_FUNCTION("YrWX+DhPHQY", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetSslCallback);
|
||||
LIB_FUNCTION("VYMxTcBqSE0", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetTimeOut);
|
||||
LIB_FUNCTION("B37SruheQ5Y", "libSceHttp2", 1, "libSceHttp2", sceHttp2SslDisableOption);
|
||||
LIB_FUNCTION("EWcwMpbr5F8", "libSceHttp2", 1, "libSceHttp2", sceHttp2SslEnableOption);
|
||||
LIB_FUNCTION("YiBUtz-pGkc", "libSceHttp2", 1, "libSceHttp2", sceHttp2Term);
|
||||
LIB_FUNCTION("MOp-AUhdfi8", "libSceHttp2", 1, "libSceHttp2", sceHttp2WaitAsync);
|
||||
};
|
||||
|
||||
} // namespace Libraries::Http2
|
||||
@@ -0,0 +1,49 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/network/ssl2.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
namespace Libraries::Http2 {
|
||||
|
||||
enum OrbisHttp2HttpVersion : u32 {
|
||||
ORBIS_HTTP2_VERSION_1_0 = 1,
|
||||
ORBIS_HTTP2_VERSION_1_1 = 2,
|
||||
ORBIS_HTTP2_VERSION_2_0 = 3,
|
||||
};
|
||||
|
||||
// TODO: Figure out the whole struct. What's here is based on libSceNpWebApi2 use.
|
||||
struct OrbisHttp2PreSendCallbackData {
|
||||
void* unk;
|
||||
void* cert;
|
||||
};
|
||||
using OrbisHttp2PreSendCallback = PS4_SYSV_ABI s32 (*)(s32 request_id, s32 ssl_id,
|
||||
OrbisHttp2PreSendCallbackData* data,
|
||||
void* user_arg);
|
||||
|
||||
s32 PS4_SYSV_ABI sceHttp2AbortRequest(s32 req_id);
|
||||
s32 PS4_SYSV_ABI sceHttp2AddRequestHeader(s32 template_or_req_id, const char* name,
|
||||
const char* value, u32 mode);
|
||||
s32 PS4_SYSV_ABI sceHttp2CreateRequestWithURL(s32 tmpl_id, const char* method, const char* url,
|
||||
u64 content_length);
|
||||
s32 PS4_SYSV_ABI sceHttp2CreateTemplate(s32 ctx_id, const char* user_agent, s32 http_ver,
|
||||
s32 auto_proxy_conf);
|
||||
s32 PS4_SYSV_ABI sceHttp2DeleteRequest(s32 req_id);
|
||||
s32 PS4_SYSV_ABI sceHttp2DeleteTemplate(s32 tmpl_id);
|
||||
s32 PS4_SYSV_ABI sceHttp2GetAllResponseHeaders(s32 req_id, char** header, u64* header_size);
|
||||
s32 PS4_SYSV_ABI sceHttp2GetStatusCode(s32 request_id, s32* status_code);
|
||||
s32 PS4_SYSV_ABI sceHttp2Init(s32 net_id, s32 ssl_id, u64 pool_size, s32 max_requests);
|
||||
s32 PS4_SYSV_ABI sceHttp2ReadData(s32 req_id, void* data, u64 size);
|
||||
s32 PS4_SYSV_ABI sceHttp2SendRequest(s32 req_id, const void* data, u64 size);
|
||||
s32 PS4_SYSV_ABI sceHttp2SetPreSendCallback(s32 template_id, OrbisHttp2PreSendCallback cb_func,
|
||||
void* user_arg);
|
||||
s32 PS4_SYSV_ABI sceHttp2SetRequestContentLength(s32 req_id, u64 content_length);
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::Http2
|
||||
@@ -0,0 +1,66 @@
|
||||
// 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_HTTP_ERROR_BEFORE_INIT = 0x80431001;
|
||||
constexpr int ORBIS_HTTP_ERROR_ALREADY_INITED = 0x80431020;
|
||||
constexpr int ORBIS_HTTP_ERROR_BUSY = 0x80431021;
|
||||
constexpr int ORBIS_HTTP_ERROR_OUT_OF_MEMORY = 0x80431022;
|
||||
constexpr int ORBIS_HTTP_ERROR_NOT_FOUND = 0x80431025;
|
||||
constexpr int ORBIS_HTTP_ERROR_INVALID_VERSION = 0x8043106a;
|
||||
constexpr int ORBIS_HTTP_ERROR_INVALID_ID = 0x80431100;
|
||||
constexpr int ORBIS_HTTP_ERROR_OUT_OF_SIZE = 0x80431104;
|
||||
constexpr int ORBIS_HTTP_ERROR_INVALID_VALUE = 0x804311fe;
|
||||
|
||||
constexpr int ORBIS_HTTP_ERROR_INVALID_URL = 0x80433060;
|
||||
constexpr int ORBIS_HTTP_ERROR_UNKNOWN_SCHEME = 0x80431061;
|
||||
constexpr int ORBIS_HTTP_ERROR_NETWORK = 0x80431063;
|
||||
constexpr int ORBIS_HTTP_ERROR_BAD_RESPONSE = 0x80431064;
|
||||
constexpr int ORBIS_HTTP_ERROR_BEFORE_SEND = 0x80431065;
|
||||
constexpr int ORBIS_HTTP_ERROR_AFTER_SEND = 0x80431066;
|
||||
constexpr int ORBIS_HTTP_ERROR_TIMEOUT = 0x80431068;
|
||||
constexpr int ORBIS_HTTP_ERROR_UNKNOWN_AUTH_TYPE = 0x80431069;
|
||||
constexpr int ORBIS_HTTP_ERROR_UNKNOWN_METHOD = 0x8043106b;
|
||||
constexpr int ORBIS_HTTP_ERROR_READ_BY_HEAD_METHOD = 0x8043106f;
|
||||
constexpr int ORBIS_HTTP_ERROR_NOT_IN_COM = 0x80431070;
|
||||
constexpr int ORBIS_HTTP_ERROR_NO_CONTENT_LENGTH = 0x80431071;
|
||||
constexpr int ORBIS_HTTP_ERROR_CHUNK_ENC = 0x80431072;
|
||||
constexpr int ORBIS_HTTP_ERROR_TOO_LARGE_RESPONSE_HEADER = 0x80431073;
|
||||
constexpr int ORBIS_HTTP_ERROR_SSL = 0x80431075;
|
||||
constexpr int ORBIS_HTTP_ERROR_INSUFFICIENT_STACKSIZE = 0x80431076;
|
||||
constexpr int ORBIS_HTTP_ERROR_ABORTED = 0x80431080;
|
||||
constexpr int ORBIS_HTTP_ERROR_UNKNOWN = 0x80431081;
|
||||
constexpr int ORBIS_HTTP_ERROR_EAGAIN = 0x80431082;
|
||||
constexpr int ORBIS_HTTP_ERROR_PROXY = 0x80431084;
|
||||
constexpr int ORBIS_HTTP_ERROR_BROKEN = 0x80431085;
|
||||
|
||||
constexpr int ORBIS_HTTP_ERROR_PARSE_HTTP_NOT_FOUND = 0x80432025;
|
||||
constexpr int ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE = 0x80432060;
|
||||
constexpr int ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_VALUE = 0x804321fe;
|
||||
|
||||
constexpr int ORBIS_HTTP_ERROR_RESOLVER_EPACKET = 0x80436001;
|
||||
constexpr int ORBIS_HTTP_ERROR_RESOLVER_ENODNS = 0x80436002;
|
||||
constexpr int ORBIS_HTTP_ERROR_RESOLVER_ETIMEDOUT = 0x80436003;
|
||||
constexpr int ORBIS_HTTP_ERROR_RESOLVER_ENOSUPPORT = 0x80436004;
|
||||
constexpr int ORBIS_HTTP_ERROR_RESOLVER_EFORMAT = 0x80436005;
|
||||
constexpr int ORBIS_HTTP_ERROR_RESOLVER_ESERVERFAILURE = 0x80436006;
|
||||
constexpr int ORBIS_HTTP_ERROR_RESOLVER_ENOHOST = 0x80436007;
|
||||
constexpr int ORBIS_HTTP_ERROR_RESOLVER_ENOTIMPLEMENTED = 0x80436008;
|
||||
constexpr int ORBIS_HTTP_ERROR_RESOLVER_ESERVERREFUSED = 0x80436009;
|
||||
constexpr int ORBIS_HTTP_ERROR_RESOLVER_ENORECORD = 0x8043600a;
|
||||
|
||||
constexpr int ORBIS_HTTPS_ERROR_CERT = 0x80435060;
|
||||
constexpr int ORBIS_HTTPS_ERROR_HANDSHAKE = 0x80435061;
|
||||
constexpr int ORBIS_HTTPS_ERROR_IO = 0x80435062;
|
||||
constexpr int ORBIS_HTTPS_ERROR_INTERNAL = 0x80435063;
|
||||
constexpr int ORBIS_HTTPS_ERROR_PROXY = 0x80435064;
|
||||
|
||||
constexpr int ORBIS_HTTPS_ERROR_SSL_INTERNAL = 0x01;
|
||||
constexpr int ORBIS_HTTPS_ERROR_SSL_INVALID_CERT = 0x02;
|
||||
constexpr int ORBIS_HTTPS_ERROR_SSL_CN_CHECK = 0x04;
|
||||
constexpr int ORBIS_HTTPS_ERROR_SSL_NOT_AFTER_CHECK = 0x08;
|
||||
constexpr int ORBIS_HTTPS_ERROR_SSL_NOT_BEFORE_CHECK = 0x10;
|
||||
constexpr int ORBIS_HTTPS_ERROR_SSL_UNKNOWN_CA = 0x20;
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,495 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/types.h"
|
||||
#include "netctl.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
// Define our own htonll and ntohll because its not available in some systems/platforms
|
||||
#ifndef HTONLL
|
||||
#define HTONLL(x) (((uint64_t)htonl((x) & 0xFFFFFFFFUL)) << 32) | htonl((uint32_t)((x) >> 32))
|
||||
#endif
|
||||
#ifndef NTOHLL
|
||||
#define NTOHLL(x) (((uint64_t)ntohl((x) & 0xFFFFFFFFUL)) << 32) | ntohl((uint32_t)((x) >> 32))
|
||||
#endif
|
||||
|
||||
namespace Libraries::Net {
|
||||
|
||||
static int ConvertFamilies(int family);
|
||||
|
||||
enum OrbisNetFamily : u32 {
|
||||
ORBIS_NET_AF_UNIX = 1,
|
||||
ORBIS_NET_AF_INET = 2,
|
||||
ORBIS_NET_AF_INET6 = 28,
|
||||
};
|
||||
enum OrbisNetSocketType : u32 {
|
||||
ORBIS_NET_SOCK_STREAM = 1,
|
||||
ORBIS_NET_SOCK_DGRAM = 2,
|
||||
ORBIS_NET_SOCK_RAW = 3,
|
||||
ORBIS_NET_SOCK_DGRAM_P2P = 6,
|
||||
ORBIS_NET_SOCK_STREAM_P2P = 10
|
||||
};
|
||||
|
||||
enum OrbisNetProtocol : u32 {
|
||||
ORBIS_NET_IPPROTO_IP = 0,
|
||||
ORBIS_NET_IPPROTO_ICMP = 1,
|
||||
ORBIS_NET_IPPROTO_IGMP = 2,
|
||||
ORBIS_NET_IPPROTO_TCP = 6,
|
||||
ORBIS_NET_IPPROTO_UDP = 17,
|
||||
ORBIS_NET_IPPROTO_IPV6 = 41,
|
||||
ORBIS_NET_SOL_SOCKET = 0xFFFF
|
||||
};
|
||||
|
||||
constexpr std::string_view NameOf(OrbisNetProtocol p) {
|
||||
switch (p) {
|
||||
case ORBIS_NET_IPPROTO_IP:
|
||||
return "ORBIS_NET_IPPROTO_IP";
|
||||
case ORBIS_NET_IPPROTO_ICMP:
|
||||
return "ORBIS_NET_IPPROTO_ICMP";
|
||||
case ORBIS_NET_IPPROTO_IGMP:
|
||||
return "ORBIS_NET_IPPROTO_IGMP";
|
||||
case ORBIS_NET_IPPROTO_TCP:
|
||||
return "ORBIS_NET_IPPROTO_TCP";
|
||||
case ORBIS_NET_IPPROTO_UDP:
|
||||
return "ORBIS_NET_IPPROTO_UDP";
|
||||
case ORBIS_NET_IPPROTO_IPV6:
|
||||
return "ORBIS_NET_IPPROTO_IPV6";
|
||||
case ORBIS_NET_SOL_SOCKET:
|
||||
return "ORBIS_NET_SOL_SOCKET";
|
||||
default:
|
||||
UNREACHABLE_MSG("{}", (u32)p);
|
||||
}
|
||||
}
|
||||
|
||||
enum OrbisNetSocketIpOption : u32 {
|
||||
ORBIS_NET_IP_HDRINCL = 2,
|
||||
ORBIS_NET_IP_TOS = 3,
|
||||
ORBIS_NET_IP_TTL = 4,
|
||||
ORBIS_NET_IP_MULTICAST_IF = 9,
|
||||
ORBIS_NET_IP_MULTICAST_TTL = 10,
|
||||
ORBIS_NET_IP_MULTICAST_LOOP = 11,
|
||||
ORBIS_NET_IP_ADD_MEMBERSHIP = 12,
|
||||
ORBIS_NET_IP_DROP_MEMBERSHIP = 13,
|
||||
ORBIS_NET_IP_TTLCHK = 23,
|
||||
ORBIS_NET_IP_MAXTTL = 24,
|
||||
};
|
||||
|
||||
enum OrbisNetSocketTcpOption : u32 {
|
||||
ORBIS_NET_TCP_NODELAY = 1,
|
||||
ORBIS_NET_TCP_MAXSEG = 2,
|
||||
ORBIS_NET_TCP_MSS_TO_ADVERTISE = 3,
|
||||
};
|
||||
|
||||
enum OrbisNetSocketSoOption : u32 {
|
||||
ORBIS_NET_SO_REUSEADDR = 0x00000004,
|
||||
ORBIS_NET_SO_KEEPALIVE = 0x00000008,
|
||||
ORBIS_NET_SO_BROADCAST = 0x00000020,
|
||||
ORBIS_NET_SO_LINGER = 0x00000080,
|
||||
ORBIS_NET_SO_REUSEPORT = 0x00000200,
|
||||
ORBIS_NET_SO_ONESBCAST = 0x00010000,
|
||||
ORBIS_NET_SO_USECRYPTO = 0x00020000,
|
||||
ORBIS_NET_SO_USESIGNATURE = 0x00040000,
|
||||
ORBIS_NET_SO_SNDBUF = 0x1001,
|
||||
ORBIS_NET_SO_RCVBUF = 0x1002,
|
||||
ORBIS_NET_SO_ERROR = 0x1007,
|
||||
ORBIS_NET_SO_TYPE = 0x1008,
|
||||
ORBIS_NET_SO_SNDTIMEO = 0x1105,
|
||||
ORBIS_NET_SO_RCVTIMEO = 0x1106,
|
||||
ORBIS_NET_SO_ERROR_EX = 0x1107,
|
||||
ORBIS_NET_SO_ACCEPTTIMEO = 0x1108,
|
||||
ORBIS_NET_SO_CONNECTTIMEO = 0x1109,
|
||||
ORBIS_NET_SO_NBIO = 0x1200,
|
||||
ORBIS_NET_SO_POLICY = 0x1201,
|
||||
ORBIS_NET_SO_NAME = 0x1202,
|
||||
ORBIS_NET_SO_PRIORITY = 0x1203
|
||||
};
|
||||
|
||||
enum OrbisNetFlags : u32 {
|
||||
ORBIS_NET_MSG_PEEK = 0x00000002,
|
||||
ORBIS_NET_MSG_WAITALL = 0x00000040,
|
||||
ORBIS_NET_MSG_DONTWAIT = 0x00000080,
|
||||
ORBIS_NET_MSG_USECRYPTO = 0x00100000,
|
||||
ORBIS_NET_MSG_USESIGNATURE = 0x00200000,
|
||||
ORBIS_NET_MSG_PEEKLEN = (0x00400000 | ORBIS_NET_MSG_PEEK)
|
||||
};
|
||||
|
||||
constexpr std::string_view NameOf(OrbisNetSocketSoOption o) {
|
||||
switch (o) {
|
||||
case ORBIS_NET_SO_REUSEADDR:
|
||||
return "ORBIS_NET_SO_REUSEADDR";
|
||||
case ORBIS_NET_SO_KEEPALIVE:
|
||||
return "ORBIS_NET_SO_KEEPALIVE";
|
||||
case ORBIS_NET_SO_BROADCAST:
|
||||
return "ORBIS_NET_SO_BROADCAST";
|
||||
case ORBIS_NET_SO_LINGER:
|
||||
return "ORBIS_NET_SO_LINGER";
|
||||
case ORBIS_NET_SO_REUSEPORT:
|
||||
return "ORBIS_NET_SO_REUSEPORT";
|
||||
case ORBIS_NET_SO_ONESBCAST:
|
||||
return "ORBIS_NET_SO_ONESBCAST";
|
||||
case ORBIS_NET_SO_USECRYPTO:
|
||||
return "ORBIS_NET_SO_USECRYPTO";
|
||||
case ORBIS_NET_SO_USESIGNATURE:
|
||||
return "ORBIS_NET_SO_USESIGNATURE";
|
||||
case ORBIS_NET_SO_SNDBUF:
|
||||
return "ORBIS_NET_SO_SNDBUF";
|
||||
case ORBIS_NET_SO_RCVBUF:
|
||||
return "ORBIS_NET_SO_RCVBUF";
|
||||
case ORBIS_NET_SO_ERROR:
|
||||
return "ORBIS_NET_SO_ERROR";
|
||||
case ORBIS_NET_SO_TYPE:
|
||||
return "ORBIS_NET_SO_TYPE";
|
||||
case ORBIS_NET_SO_SNDTIMEO:
|
||||
return "ORBIS_NET_SO_SNDTIMEO";
|
||||
case ORBIS_NET_SO_RCVTIMEO:
|
||||
return "ORBIS_NET_SO_RCVTIMEO";
|
||||
case ORBIS_NET_SO_ERROR_EX:
|
||||
return "ORBIS_NET_SO_ERROR_EX";
|
||||
case ORBIS_NET_SO_ACCEPTTIMEO:
|
||||
return "ORBIS_NET_SO_ACCEPTTIMEO";
|
||||
case ORBIS_NET_SO_CONNECTTIMEO:
|
||||
return "ORBIS_NET_SO_CONNECTTIMEO";
|
||||
case ORBIS_NET_SO_NBIO:
|
||||
return "ORBIS_NET_SO_NBIO";
|
||||
case ORBIS_NET_SO_POLICY:
|
||||
return "ORBIS_NET_SO_POLICY";
|
||||
case ORBIS_NET_SO_NAME:
|
||||
return "ORBIS_NET_SO_NAME";
|
||||
case ORBIS_NET_SO_PRIORITY:
|
||||
return "ORBIS_NET_SO_PRIORITY";
|
||||
default:
|
||||
UNREACHABLE_MSG("{}", (u32)o);
|
||||
}
|
||||
}
|
||||
|
||||
enum OrbisNetEpollFlag : u32 {
|
||||
ORBIS_NET_EPOLL_CTL_ADD = 1,
|
||||
ORBIS_NET_EPOLL_CTL_MOD = 2,
|
||||
ORBIS_NET_EPOLL_CTL_DEL = 3,
|
||||
};
|
||||
|
||||
enum OrbisNetEpollEvents : u32 {
|
||||
ORBIS_NET_EPOLLIN = 0x1,
|
||||
ORBIS_NET_EPOLLOUT = 0x2,
|
||||
ORBIS_NET_EPOLLERR = 0x8,
|
||||
ORBIS_NET_EPOLLHUP = 0x10,
|
||||
ORBIS_NET_EPOLLDESCID = 0x10000,
|
||||
};
|
||||
|
||||
enum OrbisNetResolverFlag : u32 {
|
||||
ORBIS_NET_RESOLVER_ASYNC = 0x1,
|
||||
ORBIS_NET_RESOLVER_START_NTOA_DISABLE_IPADDRESS = 0x10000,
|
||||
};
|
||||
|
||||
using OrbisNetId = s32;
|
||||
|
||||
struct OrbisNetSockaddr {
|
||||
u8 sa_len;
|
||||
u8 sa_family;
|
||||
char sa_data[14];
|
||||
};
|
||||
|
||||
struct OrbisNetSockaddrIn {
|
||||
u8 sin_len;
|
||||
u8 sin_family;
|
||||
u16 sin_port;
|
||||
u32 sin_addr;
|
||||
u16 sin_vport;
|
||||
char sin_zero[6];
|
||||
};
|
||||
|
||||
using OrbisNetInAddr_t = u32;
|
||||
|
||||
struct OrbisNetInAddr {
|
||||
OrbisNetInAddr_t inaddr_addr;
|
||||
};
|
||||
|
||||
struct OrbisNetSockaddrUn {
|
||||
u8 sun_len;
|
||||
u8 sun_family; // AF_UNIX
|
||||
char sun_path[104];
|
||||
};
|
||||
|
||||
struct OrbisNetIovec {
|
||||
void* iov_base;
|
||||
u64 iov_len;
|
||||
};
|
||||
|
||||
struct OrbisNetMsghdr {
|
||||
void* msg_name;
|
||||
u32 msg_namelen;
|
||||
OrbisNetIovec* msg_iov;
|
||||
int msg_iovlen;
|
||||
void* msg_control;
|
||||
u32 msg_controllen;
|
||||
int msg_flags;
|
||||
};
|
||||
|
||||
union OrbisNetEpollData {
|
||||
void* ptr;
|
||||
u32 data_u32;
|
||||
int fd;
|
||||
u64 data_u64;
|
||||
};
|
||||
|
||||
struct OrbisNetEpollEvent {
|
||||
u32 events;
|
||||
u32 pad;
|
||||
u64 ident;
|
||||
OrbisNetEpollData data;
|
||||
};
|
||||
|
||||
union OrbisNetAddrUnion {
|
||||
OrbisNetInAddr addr;
|
||||
u8 addr6[16];
|
||||
};
|
||||
|
||||
struct OrbisNetResolverAddr {
|
||||
OrbisNetAddrUnion u;
|
||||
u32 af;
|
||||
u32 pad[3];
|
||||
};
|
||||
|
||||
struct OrbisNetResolverInfo {
|
||||
OrbisNetResolverAddr addrs[10];
|
||||
u32 records;
|
||||
u32 recordsv4;
|
||||
u32 pad[14];
|
||||
};
|
||||
|
||||
int PS4_SYSV_ABI in6addr_any();
|
||||
int PS4_SYSV_ABI in6addr_loopback();
|
||||
int PS4_SYSV_ABI sce_net_dummy();
|
||||
int PS4_SYSV_ABI sce_net_in6addr_any();
|
||||
int PS4_SYSV_ABI sce_net_in6addr_linklocal_allnodes();
|
||||
int PS4_SYSV_ABI sce_net_in6addr_linklocal_allrouters();
|
||||
int PS4_SYSV_ABI sce_net_in6addr_loopback();
|
||||
int PS4_SYSV_ABI sce_net_in6addr_nodelocal_allnodes();
|
||||
OrbisNetId PS4_SYSV_ABI sceNetAccept(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen);
|
||||
int PS4_SYSV_ABI sceNetAddrConfig6GetInfo();
|
||||
int PS4_SYSV_ABI sceNetAddrConfig6Start();
|
||||
int PS4_SYSV_ABI sceNetAddrConfig6Stop();
|
||||
int PS4_SYSV_ABI sceNetAllocateAllRouteInfo();
|
||||
int PS4_SYSV_ABI sceNetBandwidthControlGetDataTraffic();
|
||||
int PS4_SYSV_ABI sceNetBandwidthControlGetDefaultParam();
|
||||
int PS4_SYSV_ABI sceNetBandwidthControlGetIfParam();
|
||||
int PS4_SYSV_ABI sceNetBandwidthControlGetPolicy();
|
||||
int PS4_SYSV_ABI sceNetBandwidthControlSetDefaultParam();
|
||||
int PS4_SYSV_ABI sceNetBandwidthControlSetIfParam();
|
||||
int PS4_SYSV_ABI sceNetBandwidthControlSetPolicy();
|
||||
int PS4_SYSV_ABI sceNetBind(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen);
|
||||
int PS4_SYSV_ABI sceNetClearDnsCache();
|
||||
int PS4_SYSV_ABI sceNetConfigAddArp();
|
||||
int PS4_SYSV_ABI sceNetConfigAddArpWithInterface();
|
||||
int PS4_SYSV_ABI sceNetConfigAddIfaddr();
|
||||
int PS4_SYSV_ABI sceNetConfigAddMRoute();
|
||||
int PS4_SYSV_ABI sceNetConfigAddRoute();
|
||||
int PS4_SYSV_ABI sceNetConfigAddRoute6();
|
||||
int PS4_SYSV_ABI sceNetConfigAddRouteWithInterface();
|
||||
int PS4_SYSV_ABI sceNetConfigCleanUpAllInterfaces();
|
||||
int PS4_SYSV_ABI sceNetConfigDelArp();
|
||||
int PS4_SYSV_ABI sceNetConfigDelArpWithInterface();
|
||||
int PS4_SYSV_ABI sceNetConfigDelDefaultRoute();
|
||||
int PS4_SYSV_ABI sceNetConfigDelDefaultRoute6();
|
||||
int PS4_SYSV_ABI sceNetConfigDelIfaddr();
|
||||
int PS4_SYSV_ABI sceNetConfigDelIfaddr6();
|
||||
int PS4_SYSV_ABI sceNetConfigDelMRoute();
|
||||
int PS4_SYSV_ABI sceNetConfigDelRoute();
|
||||
int PS4_SYSV_ABI sceNetConfigDelRoute6();
|
||||
int PS4_SYSV_ABI sceNetConfigDownInterface();
|
||||
int PS4_SYSV_ABI sceNetConfigEtherGetLinkMode();
|
||||
int PS4_SYSV_ABI sceNetConfigEtherPostPlugInOutEvent();
|
||||
int PS4_SYSV_ABI sceNetConfigEtherSetLinkMode();
|
||||
int PS4_SYSV_ABI sceNetConfigFlushRoute();
|
||||
int PS4_SYSV_ABI sceNetConfigGetDefaultRoute();
|
||||
int PS4_SYSV_ABI sceNetConfigGetDefaultRoute6();
|
||||
int PS4_SYSV_ABI sceNetConfigGetIfaddr();
|
||||
int PS4_SYSV_ABI sceNetConfigGetIfaddr6();
|
||||
int PS4_SYSV_ABI sceNetConfigRoutingShowRoutingConfig();
|
||||
int PS4_SYSV_ABI sceNetConfigRoutingShowtCtlVar();
|
||||
int PS4_SYSV_ABI sceNetConfigRoutingStart();
|
||||
int PS4_SYSV_ABI sceNetConfigRoutingStop();
|
||||
int PS4_SYSV_ABI sceNetConfigSetDefaultRoute();
|
||||
int PS4_SYSV_ABI sceNetConfigSetDefaultRoute6();
|
||||
int PS4_SYSV_ABI sceNetConfigSetDefaultScope();
|
||||
int PS4_SYSV_ABI sceNetConfigSetIfaddr();
|
||||
int PS4_SYSV_ABI sceNetConfigSetIfaddr6();
|
||||
int PS4_SYSV_ABI sceNetConfigSetIfaddr6WithFlags();
|
||||
int PS4_SYSV_ABI sceNetConfigSetIfFlags();
|
||||
int PS4_SYSV_ABI sceNetConfigSetIfLinkLocalAddr6();
|
||||
int PS4_SYSV_ABI sceNetConfigSetIfmtu();
|
||||
int PS4_SYSV_ABI sceNetConfigUnsetIfFlags();
|
||||
int PS4_SYSV_ABI sceNetConfigUpInterface();
|
||||
int PS4_SYSV_ABI sceNetConfigUpInterfaceWithFlags();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanAdhocClearWakeOnWlan();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanAdhocCreate();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanAdhocGetWakeOnWlanInfo();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanAdhocJoin();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanAdhocLeave();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuClearWakeOnWlan();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuGetWakeOnWlanInfo();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuSetWakeOnWlan();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanAdhocScanJoin();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanAdhocSetExtInfoElement();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanAdhocSetWakeOnWlan();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanApStart();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanApStop();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanBackgroundScanQuery();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanBackgroundScanStart();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanBackgroundScanStop();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanDiagGetDeviceInfo();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanDiagSetAntenna();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanDiagSetTxFixedRate();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanGetDeviceConfig();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanInfraGetRssiInfo();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanInfraLeave();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanInfraScanJoin();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanScan();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanSetDeviceConfig();
|
||||
int PS4_SYSV_ABI sceNetConnect(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen);
|
||||
int PS4_SYSV_ABI sceNetControl();
|
||||
int PS4_SYSV_ABI sceNetDhcpdStart();
|
||||
int PS4_SYSV_ABI sceNetDhcpdStop();
|
||||
int PS4_SYSV_ABI sceNetDhcpGetAutoipInfo();
|
||||
int PS4_SYSV_ABI sceNetDhcpGetInfo();
|
||||
int PS4_SYSV_ABI sceNetDhcpGetInfoEx();
|
||||
int PS4_SYSV_ABI sceNetDhcpStart();
|
||||
int PS4_SYSV_ABI sceNetDhcpStop();
|
||||
int PS4_SYSV_ABI sceNetDumpAbort();
|
||||
int PS4_SYSV_ABI sceNetDumpCreate();
|
||||
int PS4_SYSV_ABI sceNetDumpDestroy();
|
||||
int PS4_SYSV_ABI sceNetDumpRead();
|
||||
int PS4_SYSV_ABI sceNetDuplicateIpStart();
|
||||
int PS4_SYSV_ABI sceNetDuplicateIpStop();
|
||||
int PS4_SYSV_ABI sceNetEpollAbort();
|
||||
int PS4_SYSV_ABI sceNetEpollControl(OrbisNetId epollid, OrbisNetEpollFlag op, OrbisNetId id,
|
||||
OrbisNetEpollEvent* event);
|
||||
int PS4_SYSV_ABI sceNetEpollCreate(const char* name, int flags);
|
||||
int PS4_SYSV_ABI sceNetEpollDestroy(OrbisNetId epollid);
|
||||
int PS4_SYSV_ABI sceNetEpollWait(OrbisNetId epollid, OrbisNetEpollEvent* events, int maxevents,
|
||||
int timeout);
|
||||
int* PS4_SYSV_ABI sceNetErrnoLoc();
|
||||
int PS4_SYSV_ABI sceNetEtherNtostr();
|
||||
int PS4_SYSV_ABI sceNetEtherStrton();
|
||||
int PS4_SYSV_ABI sceNetEventCallbackCreate();
|
||||
int PS4_SYSV_ABI sceNetEventCallbackDestroy();
|
||||
int PS4_SYSV_ABI sceNetEventCallbackGetError();
|
||||
int PS4_SYSV_ABI sceNetEventCallbackWaitCB();
|
||||
int PS4_SYSV_ABI sceNetFreeAllRouteInfo();
|
||||
int PS4_SYSV_ABI sceNetGetArpInfo();
|
||||
int PS4_SYSV_ABI sceNetGetDns6Info();
|
||||
int PS4_SYSV_ABI sceNetGetDnsInfo();
|
||||
int PS4_SYSV_ABI sceNetGetIfList();
|
||||
int PS4_SYSV_ABI sceNetGetIfListOnce();
|
||||
int PS4_SYSV_ABI sceNetGetIfName();
|
||||
int PS4_SYSV_ABI sceNetGetIfnameNumList();
|
||||
int PS4_SYSV_ABI sceNetGetMacAddress(Libraries::NetCtl::OrbisNetEtherAddr* addr, int flags);
|
||||
int PS4_SYSV_ABI sceNetGetMemoryPoolStats();
|
||||
int PS4_SYSV_ABI sceNetGetNameToIndex();
|
||||
int PS4_SYSV_ABI sceNetGetpeername(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen);
|
||||
int PS4_SYSV_ABI sceNetGetRandom();
|
||||
int PS4_SYSV_ABI sceNetGetRouteInfo();
|
||||
int PS4_SYSV_ABI sceNetGetSockInfo();
|
||||
int PS4_SYSV_ABI sceNetGetSockInfo6();
|
||||
int PS4_SYSV_ABI sceNetGetsockname(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen);
|
||||
int PS4_SYSV_ABI sceNetGetsockopt(OrbisNetId s, int level, int optname, void* optval, u32* optlen);
|
||||
int PS4_SYSV_ABI sceNetGetStatisticsInfo();
|
||||
int PS4_SYSV_ABI sceNetGetStatisticsInfoInternal();
|
||||
int PS4_SYSV_ABI sceNetGetSystemTime();
|
||||
u32 PS4_SYSV_ABI sceNetHtonl(u32 host32);
|
||||
u64 PS4_SYSV_ABI sceNetHtonll(u64 host64);
|
||||
u16 PS4_SYSV_ABI sceNetHtons(u16 host16);
|
||||
const char* PS4_SYSV_ABI sceNetInetNtop(int af, const void* src, char* dst, u32 size);
|
||||
int PS4_SYSV_ABI sceNetInetNtopWithScopeId();
|
||||
int PS4_SYSV_ABI sceNetInetPton(int af, const char* src, void* dst);
|
||||
int PS4_SYSV_ABI sceNetInetPtonEx(int af, const char* src, void* dst, int flags);
|
||||
int PS4_SYSV_ABI sceNetInetPtonWithScopeId();
|
||||
int PS4_SYSV_ABI sceNetInfoDumpStart();
|
||||
int PS4_SYSV_ABI sceNetInfoDumpStop();
|
||||
int PS4_SYSV_ABI sceNetInit();
|
||||
int PS4_SYSV_ABI sceNetInitParam();
|
||||
int PS4_SYSV_ABI sceNetIoctl();
|
||||
int PS4_SYSV_ABI sceNetListen(OrbisNetId s, int backlog);
|
||||
int PS4_SYSV_ABI sceNetMemoryAllocate();
|
||||
int PS4_SYSV_ABI sceNetMemoryFree();
|
||||
u32 PS4_SYSV_ABI sceNetNtohl(u32 net32);
|
||||
int PS4_SYSV_ABI sceNetNtohll();
|
||||
u16 PS4_SYSV_ABI sceNetNtohs(u16 net16);
|
||||
int PS4_SYSV_ABI sceNetPoolCreate(const char* name, int size, int flags);
|
||||
int PS4_SYSV_ABI sceNetPoolDestroy();
|
||||
int PS4_SYSV_ABI sceNetPppoeStart();
|
||||
int PS4_SYSV_ABI sceNetPppoeStop();
|
||||
int PS4_SYSV_ABI sceNetRecv(OrbisNetId s, void* buf, u64 len, int flags);
|
||||
int PS4_SYSV_ABI sceNetRecvfrom(OrbisNetId s, void* buf, u64 len, int flags, OrbisNetSockaddr* addr,
|
||||
u32* paddrlen);
|
||||
int PS4_SYSV_ABI sceNetRecvmsg(OrbisNetId s, OrbisNetMsghdr* msg, int flags);
|
||||
int PS4_SYSV_ABI sceNetResolverAbort();
|
||||
int PS4_SYSV_ABI sceNetResolverConnect();
|
||||
int PS4_SYSV_ABI sceNetResolverConnectAbort();
|
||||
int PS4_SYSV_ABI sceNetResolverConnectCreate();
|
||||
int PS4_SYSV_ABI sceNetResolverConnectDestroy();
|
||||
int PS4_SYSV_ABI sceNetResolverCreate(const char* name, int poolid, int flags);
|
||||
int PS4_SYSV_ABI sceNetResolverDestroy(OrbisNetId resolverid);
|
||||
int PS4_SYSV_ABI sceNetResolverGetError(OrbisNetId resolverid, s32* status);
|
||||
int PS4_SYSV_ABI sceNetResolverStartAton();
|
||||
int PS4_SYSV_ABI sceNetResolverStartAton6();
|
||||
int PS4_SYSV_ABI sceNetResolverStartNtoa(OrbisNetId resolverid, const char* hostname,
|
||||
OrbisNetInAddr* addr, int timeout, int retry, int flags);
|
||||
int PS4_SYSV_ABI sceNetResolverStartNtoa6();
|
||||
int PS4_SYSV_ABI sceNetResolverStartNtoaMultipleRecords(OrbisNetId resolverid, const char* hostname,
|
||||
OrbisNetResolverInfo* info, int timeout,
|
||||
int retry, int flags);
|
||||
int PS4_SYSV_ABI sceNetResolverStartNtoaMultipleRecordsEx();
|
||||
int PS4_SYSV_ABI sceNetSend(OrbisNetId s, const void* buf, u64 len, int flags);
|
||||
int PS4_SYSV_ABI sceNetSendmsg(OrbisNetId s, const OrbisNetMsghdr* msg, int flags);
|
||||
int PS4_SYSV_ABI sceNetSendto(OrbisNetId s, const void* buf, u64 len, int flags,
|
||||
const OrbisNetSockaddr* addr, u32 addrlen);
|
||||
int PS4_SYSV_ABI sceNetSetDns6Info();
|
||||
int PS4_SYSV_ABI sceNetSetDns6InfoToKernel();
|
||||
int PS4_SYSV_ABI sceNetSetDnsInfo();
|
||||
int PS4_SYSV_ABI sceNetSetDnsInfoToKernel();
|
||||
int PS4_SYSV_ABI sceNetSetsockopt(OrbisNetId s, int level, int optname, const void* optval,
|
||||
u32 optlen);
|
||||
int PS4_SYSV_ABI sceNetShowIfconfig();
|
||||
int PS4_SYSV_ABI sceNetShowIfconfigForBuffer();
|
||||
int PS4_SYSV_ABI sceNetShowIfconfigWithMemory();
|
||||
int PS4_SYSV_ABI sceNetShowNetstat();
|
||||
int PS4_SYSV_ABI sceNetShowNetstatEx();
|
||||
int PS4_SYSV_ABI sceNetShowNetstatExForBuffer();
|
||||
int PS4_SYSV_ABI sceNetShowNetstatForBuffer();
|
||||
int PS4_SYSV_ABI sceNetShowNetstatWithMemory();
|
||||
int PS4_SYSV_ABI sceNetShowPolicy();
|
||||
int PS4_SYSV_ABI sceNetShowPolicyWithMemory();
|
||||
int PS4_SYSV_ABI sceNetShowRoute();
|
||||
int PS4_SYSV_ABI sceNetShowRoute6();
|
||||
int PS4_SYSV_ABI sceNetShowRoute6ForBuffer();
|
||||
int PS4_SYSV_ABI sceNetShowRoute6WithMemory();
|
||||
int PS4_SYSV_ABI sceNetShowRouteForBuffer();
|
||||
int PS4_SYSV_ABI sceNetShowRouteWithMemory();
|
||||
int PS4_SYSV_ABI sceNetShutdown(OrbisNetId s, int how);
|
||||
OrbisNetId PS4_SYSV_ABI sceNetSocket(const char* name, int family, int type, int protocol);
|
||||
int PS4_SYSV_ABI sceNetSocketAbort(OrbisNetId s, int flags);
|
||||
int PS4_SYSV_ABI sceNetSocketClose(OrbisNetId s);
|
||||
int PS4_SYSV_ABI sceNetSyncCreate();
|
||||
int PS4_SYSV_ABI sceNetSyncDestroy();
|
||||
int PS4_SYSV_ABI sceNetSyncGet();
|
||||
int PS4_SYSV_ABI sceNetSyncSignal();
|
||||
int PS4_SYSV_ABI sceNetSyncWait();
|
||||
int PS4_SYSV_ABI sceNetSysctl();
|
||||
int PS4_SYSV_ABI sceNetTerm();
|
||||
int PS4_SYSV_ABI sceNetThreadCreate();
|
||||
int PS4_SYSV_ABI sceNetThreadExit();
|
||||
int PS4_SYSV_ABI sceNetThreadJoin();
|
||||
int PS4_SYSV_ABI sceNetUsleep();
|
||||
int PS4_SYSV_ABI Func_0E707A589F751C68();
|
||||
int PS4_SYSV_ABI sceNetEmulationGet();
|
||||
int PS4_SYSV_ABI sceNetEmulationSet();
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::Net
|
||||
@@ -0,0 +1,37 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
// error codes
|
||||
constexpr int ORBIS_NET_CTL_ERROR_CALLBACK_MAX = 0x80412103;
|
||||
constexpr int ORBIS_NET_CTL_ERROR_ID_NOT_FOUND = 0x80412104;
|
||||
constexpr int ORBIS_NET_CTL_ERROR_INVALID_ID = 0x80412105;
|
||||
constexpr int ORBIS_NET_CTL_ERROR_INVALID_ADDR = 0x80412107;
|
||||
constexpr int ORBIS_NET_CTL_ERROR_INVALID_SIZE = 0x80412111;
|
||||
constexpr int ORBIS_NET_CTL_ERROR_NOT_CONNECTED = 0x80412108;
|
||||
constexpr int ORBIS_NET_CTL_ERROR_NOT_AVAIL = 0x80412109;
|
||||
constexpr int ORBIS_NET_CTL_ERROR_NETWORK_DISABLED = 0x8041210D;
|
||||
constexpr int ORBIS_NET_CTL_ERROR_DISCONNECT_REQ = 0x8041210E;
|
||||
constexpr int ORBIS_NET_CTL_ERROR_ETHERNET_PLUGOUT = 0x80412115;
|
||||
constexpr int ORBIS_NET_CTL_ERROR_WIFI_DEAUTHED = 0x80412116;
|
||||
constexpr int ORBIS_NET_CTL_ERROR_WIFI_BEACON_LOST = 0x80412117;
|
||||
|
||||
// state codes
|
||||
constexpr int ORBIS_NET_CTL_STATE_DISCONNECTED = 0;
|
||||
constexpr int ORBIS_NET_CTL_STATE_CONNECTING = 1;
|
||||
constexpr int ORBIS_NET_CTL_STATE_IPOBTAINING = 2;
|
||||
constexpr int ORBIS_NET_CTL_STATE_IPOBTAINED = 3;
|
||||
|
||||
// event type
|
||||
constexpr int ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED = 1;
|
||||
constexpr int ORBIS_SCE_NET_CTL_EVENT_TYPE_DISCONNECT_REQ_FINISHED = 2;
|
||||
constexpr int ORBIS_NET_CTL_EVENT_TYPE_IPOBTAINED = 3;
|
||||
|
||||
// get info codes
|
||||
// device
|
||||
constexpr int ORBIS_NET_CTL_DEVICE_WIRED = 0;
|
||||
constexpr int ORBIS_NET_CTL_DEVICE_WIRELESS = 1;
|
||||
// link
|
||||
constexpr int ORBIS_NET_CTL_LINK_DISCONNECTED = 0;
|
||||
constexpr int ORBIS_NET_CTL_LINK_CONNECTED = 1;
|
||||
@@ -0,0 +1,71 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/network/net_ctl_codes.h"
|
||||
#include "core/libraries/network/net_ctl_obj.h"
|
||||
#include "core/tls.h"
|
||||
|
||||
namespace Libraries::NetCtl {
|
||||
|
||||
NetCtlInternal::NetCtlInternal() = default;
|
||||
|
||||
NetCtlInternal::~NetCtlInternal() = default;
|
||||
|
||||
s32 NetCtlInternal::RegisterCallback(OrbisNetCtlCallback func, void* arg) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
// Find the next available slot
|
||||
const auto it = std::ranges::find(callbacks, nullptr, &NetCtlCallback::func);
|
||||
if (it == callbacks.end()) {
|
||||
return ORBIS_NET_CTL_ERROR_CALLBACK_MAX;
|
||||
}
|
||||
|
||||
const int next_id = std::distance(callbacks.begin(), it);
|
||||
callbacks[next_id].func = func;
|
||||
callbacks[next_id].arg = arg;
|
||||
return next_id;
|
||||
}
|
||||
|
||||
s32 NetCtlInternal::RegisterNpToolkitCallback(OrbisNetCtlCallbackForNpToolkit func, void* arg) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
// Find the next available slot
|
||||
const auto it = std::ranges::find(nptool_callbacks, nullptr, &NetCtlCallbackForNpToolkit::func);
|
||||
if (it == nptool_callbacks.end()) {
|
||||
return ORBIS_NET_CTL_ERROR_CALLBACK_MAX;
|
||||
}
|
||||
|
||||
const int next_id = std::distance(nptool_callbacks.begin(), it);
|
||||
nptool_callbacks[next_id].func = func;
|
||||
nptool_callbacks[next_id].arg = arg;
|
||||
return next_id;
|
||||
}
|
||||
|
||||
void NetCtlInternal::CheckCallback() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
const auto event = EmulatorSettings.IsConnectedToNetwork()
|
||||
? ORBIS_NET_CTL_EVENT_TYPE_IPOBTAINED
|
||||
: ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED;
|
||||
for (const auto [func, arg] : callbacks) {
|
||||
if (func != nullptr) {
|
||||
func(event, arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetCtlInternal::CheckNpToolkitCallback() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
const auto event = EmulatorSettings.IsConnectedToNetwork()
|
||||
? ORBIS_NET_CTL_EVENT_TYPE_IPOBTAINED
|
||||
: ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED;
|
||||
for (const auto [func, arg] : nptool_callbacks) {
|
||||
if (func != nullptr) {
|
||||
func(event, arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libraries::NetCtl
|
||||
@@ -0,0 +1,39 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Libraries::NetCtl {
|
||||
|
||||
using OrbisNetCtlCallback = PS4_SYSV_ABI void (*)(int eventType, void* arg);
|
||||
using OrbisNetCtlCallbackForNpToolkit = PS4_SYSV_ABI void (*)(int eventType, void* arg);
|
||||
|
||||
struct NetCtlCallback {
|
||||
OrbisNetCtlCallback func;
|
||||
void* arg;
|
||||
};
|
||||
|
||||
struct NetCtlCallbackForNpToolkit {
|
||||
OrbisNetCtlCallbackForNpToolkit func;
|
||||
void* arg;
|
||||
};
|
||||
|
||||
class NetCtlInternal {
|
||||
public:
|
||||
explicit NetCtlInternal();
|
||||
~NetCtlInternal();
|
||||
|
||||
s32 RegisterCallback(OrbisNetCtlCallback func, void* arg);
|
||||
s32 RegisterNpToolkitCallback(OrbisNetCtlCallbackForNpToolkit func, void* arg);
|
||||
void CheckCallback();
|
||||
void CheckNpToolkitCallback();
|
||||
|
||||
public:
|
||||
std::array<NetCtlCallbackForNpToolkit, 8> nptool_callbacks{};
|
||||
std::array<NetCtlCallback, 8> callbacks{};
|
||||
std::mutex m_mutex;
|
||||
};
|
||||
} // namespace Libraries::NetCtl
|
||||
@@ -0,0 +1,79 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/types.h"
|
||||
#include "net_epoll.h"
|
||||
|
||||
namespace Libraries::Net {
|
||||
|
||||
u32 ConvertEpollEventsIn(u32 orbis_events) {
|
||||
u32 ret = 0;
|
||||
|
||||
#ifndef __FreeBSD__
|
||||
if ((orbis_events & ORBIS_NET_EPOLLIN) != 0) {
|
||||
ret |= EPOLLIN;
|
||||
}
|
||||
if ((orbis_events & ORBIS_NET_EPOLLOUT) != 0) {
|
||||
ret |= EPOLLOUT;
|
||||
}
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
u32 ConvertEpollEventsOut(u32 epoll_events) {
|
||||
u32 ret = 0;
|
||||
|
||||
#ifndef __FreeBSD__
|
||||
if ((epoll_events & EPOLLIN) != 0) {
|
||||
ret |= ORBIS_NET_EPOLLIN;
|
||||
}
|
||||
if ((epoll_events & EPOLLOUT) != 0) {
|
||||
ret |= ORBIS_NET_EPOLLOUT;
|
||||
}
|
||||
if ((epoll_events & EPOLLERR) != 0) {
|
||||
ret |= ORBIS_NET_EPOLLERR;
|
||||
}
|
||||
if ((epoll_events & EPOLLHUP) != 0) {
|
||||
ret |= ORBIS_NET_EPOLLHUP;
|
||||
}
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int EpollTable::CreateHandle(const char* name) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
if (auto it = std::ranges::find_if(epolls, [](const Epoll& e) { return e.Destroyed(); });
|
||||
it != epolls.end()) {
|
||||
*it = Epoll(name);
|
||||
const auto ret = std::distance(epolls.begin(), it);
|
||||
LOG_DEBUG(Lib_Net, "epollid = {}", ret);
|
||||
return ret;
|
||||
} else {
|
||||
epolls.emplace_back(name);
|
||||
const auto ret = epolls.size() - 1;
|
||||
LOG_DEBUG(Lib_Net, "epollid = {}", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
void EpollTable::DeleteHandle(int d) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
Epoll* EpollTable::GetEpoll(int epollid) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (epollid >= epolls.size() || epolls[epollid].Destroyed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &epolls[epollid];
|
||||
}
|
||||
|
||||
} // namespace Libraries::Net
|
||||
@@ -0,0 +1,86 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/network/net.h"
|
||||
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <wepoll.h>
|
||||
#endif
|
||||
|
||||
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
|
||||
// ADD libepoll-shim if using freebsd!
|
||||
#include <sys/epoll.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace Libraries::Net {
|
||||
|
||||
#ifdef _WIN32
|
||||
using epoll_handle = HANDLE;
|
||||
#else
|
||||
using epoll_handle = int;
|
||||
#endif
|
||||
|
||||
struct Epoll {
|
||||
std::vector<std::pair<u32 /*netId*/, OrbisNetEpollEvent>> events{};
|
||||
std::string name;
|
||||
epoll_handle epoll_fd;
|
||||
std::deque<u32> async_resolutions{};
|
||||
|
||||
explicit Epoll(const char* name_) : name(name_), epoll_fd(epoll_create1(0)) {
|
||||
#ifdef _WIN32
|
||||
ASSERT(epoll_fd != nullptr);
|
||||
#else
|
||||
ASSERT(epoll_fd != -1);
|
||||
#endif
|
||||
if (name_ == nullptr) {
|
||||
name = "anon";
|
||||
}
|
||||
}
|
||||
|
||||
bool Destroyed() const noexcept {
|
||||
return destroyed;
|
||||
}
|
||||
|
||||
void Destroy() noexcept {
|
||||
events.clear();
|
||||
#ifdef _WIN32
|
||||
epoll_close(epoll_fd);
|
||||
epoll_fd = nullptr;
|
||||
#else
|
||||
close(epoll_fd);
|
||||
epoll_fd = -1;
|
||||
#endif
|
||||
name = "";
|
||||
destroyed = true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool destroyed{};
|
||||
};
|
||||
|
||||
u32 ConvertEpollEventsIn(u32 orbis_events);
|
||||
u32 ConvertEpollEventsOut(u32 epoll_events);
|
||||
|
||||
class EpollTable {
|
||||
public:
|
||||
EpollTable() = default;
|
||||
virtual ~EpollTable() = default;
|
||||
|
||||
int CreateHandle(const char* name);
|
||||
void DeleteHandle(int d);
|
||||
Epoll* GetEpoll(int d);
|
||||
|
||||
private:
|
||||
std::vector<Epoll> epolls;
|
||||
std::mutex m_mutex;
|
||||
};
|
||||
|
||||
} // namespace Libraries::Net
|
||||
@@ -0,0 +1,163 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
// net errno codes
|
||||
constexpr int ORBIS_NET_EPERM = 1;
|
||||
constexpr int ORBIS_NET_ENOENT = 2;
|
||||
constexpr int ORBIS_NET_EINTR = 4;
|
||||
constexpr int ORBIS_NET_EBADF = 9;
|
||||
constexpr int ORBIS_NET_ENOMEM = 12;
|
||||
constexpr int ORBIS_NET_EACCES = 13;
|
||||
constexpr int ORBIS_NET_EFAULT = 14;
|
||||
constexpr int ORBIS_NET_ENOTBLK = 15;
|
||||
constexpr int ORBIS_NET_EBUSY = 16;
|
||||
constexpr int ORBIS_NET_EEXIST = 17;
|
||||
constexpr int ORBIS_NET_ENODEV = 19;
|
||||
constexpr int ORBIS_NET_EINVAL = 22;
|
||||
constexpr int ORBIS_NET_ENFILE = 23;
|
||||
constexpr int ORBIS_NET_EMFILE = 24;
|
||||
constexpr int ORBIS_NET_ENOSPC = 28;
|
||||
constexpr int ORBIS_NET_EPIPE = 32;
|
||||
constexpr int ORBIS_NET_EAGAIN = 35;
|
||||
constexpr int ORBIS_NET_EWOULDBLOCK = 35;
|
||||
constexpr int ORBIS_NET_EINPROGRESS = 36;
|
||||
constexpr int ORBIS_NET_EALREADY = 37;
|
||||
constexpr int ORBIS_NET_ENOTSOCK = 38;
|
||||
constexpr int ORBIS_NET_EDESTADDRREQ = 39;
|
||||
constexpr int ORBIS_NET_EMSGSIZE = 40;
|
||||
constexpr int ORBIS_NET_EPROTOTYPE = 41;
|
||||
constexpr int ORBIS_NET_ENOPROTOOPT = 42;
|
||||
constexpr int ORBIS_NET_EPROTONOSUPPORT = 43;
|
||||
constexpr int ORBIS_NET_EOPNOTSUPP = 45;
|
||||
constexpr int ORBIS_NET_EAFNOSUPPORT = 47;
|
||||
constexpr int ORBIS_NET_EADDRINUSE = 48;
|
||||
constexpr int ORBIS_NET_EADDRNOTAVAIL = 49;
|
||||
constexpr int ORBIS_NET_ENETDOWN = 50;
|
||||
constexpr int ORBIS_NET_ENETUNREACH = 51;
|
||||
constexpr int ORBIS_NET_ENETRESET = 52;
|
||||
constexpr int ORBIS_NET_ECONNABORTED = 53;
|
||||
constexpr int ORBIS_NET_ECONNRESET = 54;
|
||||
constexpr int ORBIS_NET_ENOBUFS = 55;
|
||||
constexpr int ORBIS_NET_EISCONN = 56;
|
||||
constexpr int ORBIS_NET_ENOTCONN = 57;
|
||||
constexpr int ORBIS_NET_ETOOMANYREFS = 59;
|
||||
constexpr int ORBIS_NET_ETIMEDOUT = 60;
|
||||
constexpr int ORBIS_NET_ECONNREFUSED = 61;
|
||||
constexpr int ORBIS_NET_ELOOP = 62;
|
||||
constexpr int ORBIS_NET_ENAMETOOLONG = 63;
|
||||
constexpr int ORBIS_NET_EHOSTDOWN = 64;
|
||||
constexpr int ORBIS_NET_EHOSTUNREACH = 65;
|
||||
constexpr int ORBIS_NET_ENOTEMPTY = 66;
|
||||
constexpr int ORBIS_NET_EPROCUNAVAIL = 76;
|
||||
constexpr int ORBIS_NET_ECANCELED = 87;
|
||||
constexpr int ORBIS_NET_EPROTO = 92;
|
||||
constexpr int ORBIS_NET_EADHOC = 160;
|
||||
constexpr int ORBIS_NET_EINACTIVEDISABLED = 163;
|
||||
constexpr int ORBIS_NET_ENODATA = 164;
|
||||
constexpr int ORBIS_NET_EDESC = 165;
|
||||
constexpr int ORBIS_NET_EDESCTIMEDOUT = 166;
|
||||
constexpr int ORBIS_NET_ENETINTR = 167;
|
||||
constexpr int ORBIS_NET_ENOTINIT = 200;
|
||||
constexpr int ORBIS_NET_ENOLIBMEM = 201;
|
||||
constexpr int ORBIS_NET_ECALLBACK = 203;
|
||||
constexpr int ORBIS_NET_EINTERNAL = 204;
|
||||
constexpr int ORBIS_NET_ERETURN = 205;
|
||||
constexpr int ORBIS_NET_ENOALLOCMEM = 206;
|
||||
|
||||
// errno for dns resolver
|
||||
constexpr int ORBIS_NET_RESOLVER_EINTERNAL = 220;
|
||||
constexpr int ORBIS_NET_RESOLVER_EBUSY = 221;
|
||||
constexpr int ORBIS_NET_RESOLVER_ENOSPACE = 222;
|
||||
constexpr int ORBIS_NET_RESOLVER_EPACKET = 223;
|
||||
constexpr int ORBIS_NET_RESOLVER_ENODNS = 225;
|
||||
constexpr int ORBIS_NET_RESOLVER_ETIMEDOUT = 226;
|
||||
constexpr int ORBIS_NET_RESOLVER_ENOSUPPORT = 227;
|
||||
constexpr int ORBIS_NET_RESOLVER_EFORMAT = 228;
|
||||
constexpr int ORBIS_NET_RESOLVER_ESERVERFAILURE = 229;
|
||||
constexpr int ORBIS_NET_RESOLVER_ENOHOST = 230;
|
||||
constexpr int ORBIS_NET_RESOLVER_ENOTIMPLEMENTED = 231;
|
||||
constexpr int ORBIS_NET_RESOLVER_ESERVERREFUSED = 232;
|
||||
constexpr int ORBIS_NET_RESOLVER_ENORECORD = 233;
|
||||
constexpr int ORBIS_NET_RESOLVER_EALIGNMENT = 234;
|
||||
|
||||
// error codes
|
||||
constexpr int ORBIS_NET_ERROR_BASE = 0x80410100; // not existed used for calculation
|
||||
constexpr int ORBIS_NET_ERROR_EPERM = 0x80410101;
|
||||
constexpr int ORBIS_NET_ERROR_ENOENT = 0x80410102;
|
||||
constexpr int ORBIS_NET_ERROR_EINTR = 0x80410104;
|
||||
constexpr int ORBIS_NET_ERROR_EBADF = 0x80410109;
|
||||
constexpr int ORBIS_NET_ERROR_ENOMEM = 0x8041010c;
|
||||
constexpr int ORBIS_NET_ERROR_EACCES = 0x8041010d;
|
||||
constexpr int ORBIS_NET_ERROR_EFAULT = 0x8041010e;
|
||||
constexpr int ORBIS_NET_ERROR_ENOTBLK = 0x8041010f;
|
||||
constexpr int ORBIS_NET_ERROR_EEXIST = 0x80410111;
|
||||
constexpr int ORBIS_NET_ERROR_ENODEV = 0x80410113;
|
||||
constexpr int ORBIS_NET_ERROR_EINVAL = 0x80410116;
|
||||
constexpr int ORBIS_NET_ERROR_ENFILE = 0x80410117;
|
||||
constexpr int ORBIS_NET_ERROR_EMFILE = 0x80410118;
|
||||
constexpr int ORBIS_NET_ERROR_ENOSPC = 0x8041011c;
|
||||
constexpr int ORBIS_NET_ERROR_EPIPE = 0x80410120;
|
||||
constexpr int ORBIS_NET_ERROR_EAGAIN = 0x80410123;
|
||||
constexpr int ORBIS_NET_ERROR_EWOULDBLOCK = 0x80410123;
|
||||
constexpr int ORBIS_NET_ERROR_EINPROGRESS = 0x80410124;
|
||||
constexpr int ORBIS_NET_ERROR_EALREADY = 0x80410125;
|
||||
constexpr int ORBIS_NET_ERROR_ENOTSOCK = 0x80410126;
|
||||
constexpr int ORBIS_NET_ERROR_EDESTADDRREQ = 0x80410127;
|
||||
constexpr int ORBIS_NET_ERROR_EMSGSIZE = 0x80410128;
|
||||
constexpr int ORBIS_NET_ERROR_EPROTOTYPE = 0x80410129;
|
||||
constexpr int ORBIS_NET_ERROR_ENOPROTOOPT = 0x8041012a;
|
||||
constexpr int ORBIS_NET_ERROR_EPROTONOSUPPORT = 0x8041012b;
|
||||
constexpr int ORBIS_NET_ERROR_EOPNOTSUPP = 0x8041012d;
|
||||
constexpr int ORBIS_NET_ERROR_EPFNOSUPPORT = 0x8041012e;
|
||||
constexpr int ORBIS_NET_ERROR_EAFNOSUPPORT = 0x8041012f;
|
||||
constexpr int ORBIS_NET_ERROR_EADDRINUSE = 0x80410130;
|
||||
constexpr int ORBIS_NET_ERROR_EADDRNOTAVAIL = 0x80410131;
|
||||
constexpr int ORBIS_NET_ERROR_ENETDOWN = 0x80410132;
|
||||
constexpr int ORBIS_NET_ERROR_ENETUNREACH = 0x80410133;
|
||||
constexpr int ORBIS_NET_ERROR_ENETRESET = 0x80410134;
|
||||
constexpr int ORBIS_NET_ERROR_ECONNABORTED = 0x80410135;
|
||||
constexpr int ORBIS_NET_ERROR_ECONNRESET = 0x80410136;
|
||||
constexpr int ORBIS_NET_ERROR_ENOBUFS = 0x80410137;
|
||||
constexpr int ORBIS_NET_ERROR_EISCONN = 0x80410138;
|
||||
constexpr int ORBIS_NET_ERROR_ENOTCONN = 0x80410139;
|
||||
constexpr int ORBIS_NET_ERROR_ESHUTDOWN = 0x8041013a;
|
||||
constexpr int ORBIS_NET_ERROR_ETOOMANYREFS = 0x8041013b;
|
||||
constexpr int ORBIS_NET_ERROR_ETIMEDOUT = 0x8041013c;
|
||||
constexpr int ORBIS_NET_ERROR_ECONNREFUSED = 0x8041013d;
|
||||
constexpr int ORBIS_NET_ERROR_ELOOP = 0x8041013e;
|
||||
constexpr int ORBIS_NET_ERROR_ENAMETOOLONG = 0x8041013f;
|
||||
constexpr int ORBIS_NET_ERROR_EHOSTDOWN = 0x80410140;
|
||||
constexpr int ORBIS_NET_ERROR_EHOSTUNREACH = 0x80410141;
|
||||
constexpr int ORBIS_NET_ERROR_ENOTEMPTY = 0x80410142;
|
||||
constexpr int ORBIS_NET_ERROR_EPROCUNAVAIL = 0x8041014C;
|
||||
constexpr int ORBIS_NET_ERROR_ECANCELED = 0x80410157;
|
||||
constexpr int ORBIS_NET_ERROR_EPROTO = 0x8041015C;
|
||||
constexpr int ORBIS_NET_ERROR_EADHOC = 0x804101a0;
|
||||
constexpr int ORBIS_NET_ERROR_ERESERVED161 = 0x804101a1;
|
||||
constexpr int ORBIS_NET_ERROR_ERESERVED162 = 0x804101a2;
|
||||
constexpr int ORBIS_NET_ERROR_EINACTIVEDISABLED = 0x804101a3;
|
||||
constexpr int ORBIS_NET_ERROR_ENODATA = 0x804101a4;
|
||||
constexpr int ORBIS_NET_ERROR_EDESC = 0x804101a5;
|
||||
constexpr int ORBIS_NET_ERROR_EDESCTIMEDOUT = 0x804101a6;
|
||||
constexpr int ORBIS_NET_ERROR_ENOTINIT = 0x804101c8;
|
||||
constexpr int ORBIS_NET_ERROR_ENOLIBMEM = 0x804101c9;
|
||||
constexpr int ORBIS_NET_ERROR_ECALLBACK = 0x804101cb;
|
||||
constexpr int ORBIS_NET_ERROR_EINTERNAL = 0x804101cc;
|
||||
constexpr int ORBIS_NET_ERROR_ERETURN = 0x804101cd;
|
||||
constexpr int ORBIS_NET_ERROR_ENOALLOCMEM = 0x804101ce;
|
||||
constexpr int ORBIS_NET_ERROR_RESOLVER_EINTERNAL = 0x804101dc;
|
||||
constexpr int ORBIS_NET_ERROR_RESOLVER_EBUSY = 0x804101dd;
|
||||
constexpr int ORBIS_NET_ERROR_RESOLVER_ENOSPACE = 0x804101de;
|
||||
constexpr int ORBIS_NET_ERROR_RESOLVER_EPACKET = 0x804101df;
|
||||
constexpr int ORBIS_NET_ERROR_RESOLVER_ENODNS = 0x804101e1;
|
||||
constexpr int ORBIS_NET_ERROR_RESOLVER_ETIMEDOUT = 0x804101e2;
|
||||
constexpr int ORBIS_NET_ERROR_RESOLVER_ENOSUPPORT = 0x804101e3;
|
||||
constexpr int ORBIS_NET_ERROR_RESOLVER_EFORMAT = 0x804101e4;
|
||||
constexpr int ORBIS_NET_ERROR_RESOLVER_ESERVERFAILURE = 0x804101e5;
|
||||
constexpr int ORBIS_NET_ERROR_RESOLVER_ENOHOST = 0x804101e6;
|
||||
constexpr int ORBIS_NET_ERROR_RESOLVER_ENOTIMPLEMENTED = 0x804101e7;
|
||||
constexpr int ORBIS_NET_ERROR_RESOLVER_ESERVERREFUSED = 0x804101e8;
|
||||
constexpr int ORBIS_NET_ERROR_RESOLVER_ENORECORD = 0x804101e9;
|
||||
constexpr int ORBIS_NET_ERROR_RESOLVER_EALIGNMENT = 0x804101ea;
|
||||
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,48 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/singleton.h"
|
||||
#include "common/types.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "net_error.h"
|
||||
#include "net_resolver.h"
|
||||
#include "net_util.h"
|
||||
|
||||
namespace Libraries::Net {
|
||||
|
||||
int Resolver::ResolveAsync(const char* hostname, OrbisNetInAddr* addr, int timeout, int retry,
|
||||
int flags) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
if (async_resolution) {
|
||||
*sceNetErrnoLoc() = ORBIS_NET_RESOLVER_EBUSY;
|
||||
return ORBIS_NET_ERROR_RESOLVER_EBUSY;
|
||||
}
|
||||
|
||||
async_resolution = AsyncResolution{hostname, addr, timeout, retry, flags};
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
void Resolver::Resolve() {
|
||||
if (!EmulatorSettings.IsConnectedToNetwork()) {
|
||||
resolution_error = ORBIS_NET_ERROR_RESOLVER_ENODNS;
|
||||
return;
|
||||
}
|
||||
|
||||
if (async_resolution) {
|
||||
auto* netinfo = Common::Singleton<NetUtil::NetUtilInternal>::Instance();
|
||||
auto ret = netinfo->ResolveHostname(async_resolution->hostname, async_resolution->addr);
|
||||
resolution_error = ret;
|
||||
if (ret != ORBIS_OK) {
|
||||
// Resolver errors are stored as ORBIS_NET_ERROR values.
|
||||
resolution_error = -ret | ORBIS_NET_ERROR_BASE;
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(Lib_Net, "async resolution has not been set-up");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libraries::Net
|
||||
@@ -0,0 +1,39 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/network/net.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace Libraries::Net {
|
||||
|
||||
struct Resolver {
|
||||
public:
|
||||
Resolver(const char* name, int poolid, int flags) : name(name), poolid(poolid), flags(flags) {}
|
||||
|
||||
int ResolveAsync(const char* hostname, OrbisNetInAddr* addr, int timeout, int retry, int flags);
|
||||
void Resolve();
|
||||
|
||||
int resolution_error = ORBIS_OK;
|
||||
|
||||
private:
|
||||
struct AsyncResolution {
|
||||
const char* hostname;
|
||||
OrbisNetInAddr* addr;
|
||||
int timeout;
|
||||
int retry;
|
||||
int flags;
|
||||
};
|
||||
|
||||
std::string name;
|
||||
int poolid;
|
||||
int flags;
|
||||
std::optional<AsyncResolution> async_resolution{};
|
||||
std::mutex m_mutex;
|
||||
};
|
||||
|
||||
} // namespace Libraries::Net
|
||||
@@ -0,0 +1,150 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <upnperrors.h>
|
||||
#include "common/logging/log.h"
|
||||
#include "net_upnp.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#include <net/if.h>
|
||||
#endif
|
||||
|
||||
namespace Libraries::Net {
|
||||
|
||||
UPnPClient& UPnPClient::Instance() {
|
||||
static UPnPClient instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
UPnPClient::~UPnPClient() {
|
||||
if (m_thread.joinable())
|
||||
m_thread.join();
|
||||
if (m_available.load())
|
||||
FreeUPNPUrls(&m_urls);
|
||||
}
|
||||
|
||||
void UPnPClient::Start() {
|
||||
if (!m_p2p_features_enabled.load()) {
|
||||
LOG_INFO(Lib_Net, "UPNP: skipped because Matching2 is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
bool expected = false;
|
||||
if (!m_started.compare_exchange_strong(expected, true))
|
||||
return;
|
||||
LOG_INFO(Lib_Net, "UPNP: starting discovery thread");
|
||||
m_thread = std::thread(&UPnPClient::DiscoverThread, this);
|
||||
}
|
||||
|
||||
void UPnPClient::SetP2PFeaturesEnabled(bool enabled) {
|
||||
m_p2p_features_enabled.store(enabled);
|
||||
}
|
||||
|
||||
bool UPnPClient::WaitReady(int timeout_ms) {
|
||||
std::unique_lock lock(m_mutex);
|
||||
m_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), [this] { return m_done.load(); });
|
||||
return m_available.load();
|
||||
}
|
||||
|
||||
bool UPnPClient::IsAvailable() const {
|
||||
return m_available.load();
|
||||
}
|
||||
|
||||
u32 UPnPClient::GetExternalIp() const {
|
||||
return m_external_ip_nbo.load();
|
||||
}
|
||||
|
||||
std::string UPnPClient::GetExternalIpString() const {
|
||||
return m_available.load() ? std::string(m_external_ip_str) : std::string{};
|
||||
}
|
||||
|
||||
u16 UPnPClient::GetExternalPort() const {
|
||||
return m_external_port.load();
|
||||
}
|
||||
|
||||
void UPnPClient::DiscoverThread() {
|
||||
int error = 0;
|
||||
UPNPDev* devlist = upnpDiscover(2000, nullptr, nullptr, UPNP_LOCAL_PORT_ANY, 0, 2, &error);
|
||||
|
||||
if (!devlist) {
|
||||
LOG_INFO(Lib_Net, "UPNP: no IGD found (error={})", error);
|
||||
m_done = true;
|
||||
m_cv.notify_all();
|
||||
return;
|
||||
}
|
||||
|
||||
char wan_addr[40]{};
|
||||
const int ret = UPNP_GetValidIGD(devlist, &m_urls, &m_data, m_lan_addr, sizeof(m_lan_addr),
|
||||
wan_addr, sizeof(wan_addr));
|
||||
freeUPNPDevlist(devlist);
|
||||
|
||||
if (ret != 1) {
|
||||
LOG_INFO(Lib_Net, "UPNP: no valid IGD (ret={})", ret);
|
||||
m_done = true;
|
||||
m_cv.notify_all();
|
||||
return;
|
||||
}
|
||||
|
||||
char external_ip[40]{};
|
||||
if (UPNP_GetExternalIPAddress(m_urls.controlURL, m_data.first.servicetype, external_ip) !=
|
||||
UPNPCOMMAND_SUCCESS) {
|
||||
LOG_WARNING(Lib_Net, "UPNP: IGD found but failed to get external IP");
|
||||
FreeUPNPUrls(&m_urls);
|
||||
m_done = true;
|
||||
m_cv.notify_all();
|
||||
return;
|
||||
}
|
||||
|
||||
u32 ip_nbo = 0;
|
||||
inet_pton(AF_INET, external_ip, &ip_nbo);
|
||||
m_external_ip_nbo.store(ip_nbo);
|
||||
std::strncpy(m_external_ip_str, external_ip, sizeof(m_external_ip_str) - 1);
|
||||
m_available = true;
|
||||
|
||||
LOG_INFO(Lib_Net, "UPNP: IGD found - external IP={} lan={} (port mapping available)",
|
||||
external_ip, m_lan_addr);
|
||||
|
||||
m_done = true;
|
||||
m_cv.notify_all();
|
||||
}
|
||||
|
||||
void UPnPClient::AddMapping(u16 port) {
|
||||
if (!m_p2p_features_enabled.load())
|
||||
return;
|
||||
|
||||
if (!m_available.load())
|
||||
return;
|
||||
|
||||
const std::string port_str = std::to_string(port);
|
||||
const int ret =
|
||||
UPNP_AddPortMapping(m_urls.controlURL, m_data.first.servicetype, port_str.c_str(),
|
||||
port_str.c_str(), m_lan_addr, "shadPS4", "UDP", nullptr, "0");
|
||||
|
||||
if (ret == UPNPCOMMAND_SUCCESS) {
|
||||
m_external_port.store(port);
|
||||
LOG_INFO(Lib_Net, "UPNP: mapped UDP port {}", port);
|
||||
} else {
|
||||
// Store the requested port as best-guess even on failure so callers still get a value.
|
||||
m_external_port.store(port);
|
||||
LOG_WARNING(Lib_Net, "UPNP: failed to map port {} ({})", port, strupnperror(ret));
|
||||
}
|
||||
}
|
||||
|
||||
void UPnPClient::RemoveMapping(u16 port) {
|
||||
if (!m_available.load())
|
||||
return;
|
||||
|
||||
const std::string port_str = std::to_string(port);
|
||||
UPNP_DeletePortMapping(m_urls.controlURL, m_data.first.servicetype, port_str.c_str(), "UDP",
|
||||
nullptr);
|
||||
|
||||
LOG_INFO(Lib_Net, "UPNP: removed UDP port mapping {}", port);
|
||||
}
|
||||
|
||||
} // namespace Libraries::Net
|
||||
@@ -0,0 +1,68 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include "common/types.h"
|
||||
|
||||
#include <miniupnpc.h>
|
||||
#include <upnpcommands.h>
|
||||
|
||||
namespace Libraries::Net {
|
||||
|
||||
class UPnPClient {
|
||||
public:
|
||||
static UPnPClient& Instance();
|
||||
|
||||
// Kick off async IGD discovery. Safe to call multiple times — only the first call has effect.
|
||||
void Start();
|
||||
void SetP2PFeaturesEnabled(bool enabled);
|
||||
|
||||
// Block until discovery completes or timeout_ms elapses. Returns true if IGD was found.
|
||||
bool WaitReady(int timeout_ms);
|
||||
|
||||
bool IsAvailable() const;
|
||||
|
||||
// External IP in network byte order as reported by the IGD. 0 if not yet known.
|
||||
u32 GetExternalIp() const;
|
||||
|
||||
// External IP as a dotted-decimal string. Empty if not yet known.
|
||||
std::string GetExternalIpString() const;
|
||||
|
||||
// External port the IGD mapped for us. Matches the internal port unless the router remapped it.
|
||||
u16 GetExternalPort() const;
|
||||
|
||||
void AddMapping(u16 port);
|
||||
void RemoveMapping(u16 port);
|
||||
|
||||
private:
|
||||
UPnPClient() = default;
|
||||
~UPnPClient();
|
||||
UPnPClient(const UPnPClient&) = delete;
|
||||
UPnPClient& operator=(const UPnPClient&) = delete;
|
||||
|
||||
void DiscoverThread();
|
||||
|
||||
std::atomic<bool> m_started{false};
|
||||
std::atomic<bool> m_p2p_features_enabled{true};
|
||||
std::atomic<bool> m_done{false};
|
||||
std::atomic<bool> m_available{false};
|
||||
std::atomic<u32> m_external_ip_nbo{0};
|
||||
std::atomic<u16> m_external_port{0};
|
||||
char m_external_ip_str[16]{};
|
||||
|
||||
char m_lan_addr[64]{};
|
||||
UPNPUrls m_urls{};
|
||||
IGDdatas m_data{};
|
||||
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_cv;
|
||||
std::thread m_thread;
|
||||
};
|
||||
|
||||
} // namespace Libraries::Net
|
||||
@@ -0,0 +1,416 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#ifdef _WIN32
|
||||
#define _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||
#include <Ws2tcpip.h>
|
||||
#include <iphlpapi.h>
|
||||
#include <winsock2.h>
|
||||
typedef SOCKET net_socket;
|
||||
typedef int socklen_t;
|
||||
#else
|
||||
#include <cerrno>
|
||||
#include <arpa/inet.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <net/if.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
typedef int net_socket;
|
||||
#endif
|
||||
#if defined(__APPLE__)
|
||||
#include <net/if_dl.h>
|
||||
#include <net/route.h>
|
||||
#endif
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#endif
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <string.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "net.h"
|
||||
#include "net_error.h"
|
||||
#include "net_util.h"
|
||||
|
||||
namespace NetUtil {
|
||||
|
||||
const std::array<u8, 6>& NetUtilInternal::GetEthernetAddr() const {
|
||||
return ether_address;
|
||||
}
|
||||
|
||||
bool NetUtilInternal::RetrieveEthernetAddr() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
#ifdef _WIN32
|
||||
std::vector<u8> adapter_infos(sizeof(IP_ADAPTER_INFO));
|
||||
ULONG size_infos = sizeof(IP_ADAPTER_INFO);
|
||||
|
||||
if (GetAdaptersInfo(reinterpret_cast<PIP_ADAPTER_INFO>(adapter_infos.data()), &size_infos) ==
|
||||
ERROR_BUFFER_OVERFLOW)
|
||||
adapter_infos.resize(size_infos);
|
||||
|
||||
if (GetAdaptersInfo(reinterpret_cast<PIP_ADAPTER_INFO>(adapter_infos.data()), &size_infos) ==
|
||||
NO_ERROR &&
|
||||
size_infos) {
|
||||
PIP_ADAPTER_INFO info = reinterpret_cast<PIP_ADAPTER_INFO>(adapter_infos.data());
|
||||
memcpy(ether_address.data(), info[0].Address, 6);
|
||||
return true;
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
ifaddrs* ifap;
|
||||
|
||||
if (getifaddrs(&ifap) == 0) {
|
||||
ifaddrs* p;
|
||||
for (p = ifap; p; p = p->ifa_next) {
|
||||
if (p->ifa_addr->sa_family == AF_LINK) {
|
||||
sockaddr_dl* sdp = reinterpret_cast<sockaddr_dl*>(p->ifa_addr);
|
||||
memcpy(ether_address.data(), sdp->sdl_data + sdp->sdl_nlen, 6);
|
||||
freeifaddrs(ifap);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
freeifaddrs(ifap);
|
||||
}
|
||||
#elif defined(__FreeBSD__)
|
||||
// todo
|
||||
#else
|
||||
ifreq ifr;
|
||||
ifconf ifc;
|
||||
char buf[1024];
|
||||
int success = 0;
|
||||
|
||||
int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (sock == -1)
|
||||
return false;
|
||||
|
||||
ifc.ifc_len = sizeof(buf);
|
||||
ifc.ifc_buf = buf;
|
||||
if (ioctl(sock, SIOCGIFCONF, &ifc) == -1)
|
||||
return false;
|
||||
|
||||
ifreq* it = ifc.ifc_req;
|
||||
const ifreq* const end = it + (ifc.ifc_len / sizeof(ifreq));
|
||||
|
||||
for (; it != end; ++it) {
|
||||
strcpy(ifr.ifr_name, it->ifr_name);
|
||||
if (ioctl(sock, SIOCGIFFLAGS, &ifr) == 0) {
|
||||
if (!(ifr.ifr_flags & IFF_LOOPBACK)) {
|
||||
if (ioctl(sock, SIOCGIFHWADDR, &ifr) == 0) {
|
||||
success = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(sock);
|
||||
|
||||
if (success) {
|
||||
memcpy(ether_address.data(), ifr.ifr_hwaddr.sa_data, 6);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string& NetUtilInternal::GetDefaultGateway() const {
|
||||
return default_gateway;
|
||||
}
|
||||
|
||||
bool NetUtilInternal::RetrieveDefaultGateway() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
#ifdef _WIN32
|
||||
ULONG flags = GAA_FLAG_INCLUDE_GATEWAYS;
|
||||
ULONG family = AF_INET; // Only IPv4
|
||||
ULONG buffer_size = 15000;
|
||||
|
||||
std::vector<BYTE> buffer(buffer_size);
|
||||
PIP_ADAPTER_ADDRESSES adapter_addresses =
|
||||
reinterpret_cast<PIP_ADAPTER_ADDRESSES>(buffer.data());
|
||||
|
||||
DWORD result = GetAdaptersAddresses(family, flags, nullptr, adapter_addresses, &buffer_size);
|
||||
if (result != NO_ERROR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (PIP_ADAPTER_ADDRESSES adapter = adapter_addresses; adapter != nullptr;
|
||||
adapter = adapter->Next) {
|
||||
if (adapter->OperStatus != IfOperStatusUp)
|
||||
continue;
|
||||
|
||||
IP_ADAPTER_GATEWAY_ADDRESS_LH* gateway = adapter->FirstGatewayAddress;
|
||||
while (gateway) {
|
||||
sockaddr* sa = gateway->Address.lpSockaddr;
|
||||
if (sa->sa_family == AF_INET) {
|
||||
char str[INET_ADDRSTRLEN];
|
||||
sockaddr_in* sa_in = reinterpret_cast<sockaddr_in*>(sa);
|
||||
if (inet_ntop(AF_INET, &sa_in->sin_addr, str, sizeof(str))) {
|
||||
this->default_gateway = str;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
gateway = gateway->Next;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
#elif defined(__APPLE__)
|
||||
// adapted from
|
||||
// https://github.com/seladb/PcapPlusPlus/blob/a49a79e0b67b402ad75ffa96c1795def36df75c8/Pcap%2B%2B/src/PcapLiveDevice.cpp#L1236
|
||||
// route message struct for communication in APPLE device
|
||||
struct BSDRoutingMessage {
|
||||
struct rt_msghdr header;
|
||||
char messageSpace[512];
|
||||
};
|
||||
|
||||
struct BSDRoutingMessage routingMessage;
|
||||
// It creates a raw socket that can be used for routing-related operations
|
||||
int sockfd = socket(PF_ROUTE, SOCK_RAW, 0);
|
||||
if (sockfd < 0) {
|
||||
return false;
|
||||
}
|
||||
memset(reinterpret_cast<char*>(&routingMessage), 0, sizeof(routingMessage));
|
||||
routingMessage.header.rtm_msglen = sizeof(struct rt_msghdr);
|
||||
routingMessage.header.rtm_version = RTM_VERSION;
|
||||
routingMessage.header.rtm_type = RTM_GET;
|
||||
routingMessage.header.rtm_addrs = RTA_DST | RTA_NETMASK;
|
||||
routingMessage.header.rtm_flags = RTF_UP | RTF_GATEWAY | RTF_STATIC;
|
||||
routingMessage.header.rtm_msglen += 2 * sizeof(sockaddr_in);
|
||||
|
||||
if (write(sockfd, reinterpret_cast<char*>(&routingMessage), routingMessage.header.rtm_msglen) <
|
||||
0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the response from the route socket
|
||||
if (read(sockfd, reinterpret_cast<char*>(&routingMessage), sizeof(routingMessage)) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct in_addr* gateAddr = nullptr;
|
||||
struct sockaddr* sa = nullptr;
|
||||
char* spacePtr = (reinterpret_cast<char*>(&routingMessage.header + 1));
|
||||
auto rtmAddrs = routingMessage.header.rtm_addrs;
|
||||
int index = 1;
|
||||
auto roundUpClosestMultiple = [](int multiple, int num) {
|
||||
return ((num + multiple - 1) / multiple) * multiple;
|
||||
};
|
||||
while (rtmAddrs) {
|
||||
if (rtmAddrs & 1) {
|
||||
sa = reinterpret_cast<sockaddr*>(spacePtr);
|
||||
if (index == RTA_GATEWAY) {
|
||||
gateAddr = &((sockaddr_in*)sa)->sin_addr;
|
||||
break;
|
||||
}
|
||||
spacePtr += sa->sa_len > 0 ? roundUpClosestMultiple(sizeof(uint32_t), sa->sa_len)
|
||||
: sizeof(uint32_t);
|
||||
}
|
||||
index++;
|
||||
rtmAddrs >>= 1;
|
||||
}
|
||||
|
||||
if (gateAddr == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char str[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, gateAddr, str, sizeof(str));
|
||||
this->default_gateway = str;
|
||||
return true;
|
||||
#elif defined(__FreeBSD__)
|
||||
return true;
|
||||
#else
|
||||
std::ifstream route{"/proc/net/route"};
|
||||
std::string line;
|
||||
|
||||
std::getline(route, line);
|
||||
while (std::getline(route, line)) {
|
||||
std::istringstream iss{line};
|
||||
std::string iface, destination, gateway;
|
||||
int flags;
|
||||
|
||||
iss >> iface >> destination >> gateway >> std::hex >> flags;
|
||||
|
||||
if (destination == "00000000") {
|
||||
u64 default_gateway{};
|
||||
std::stringstream ss;
|
||||
ss << std::hex << gateway;
|
||||
ss >> default_gateway;
|
||||
|
||||
char str[INET_ADDRSTRLEN];
|
||||
in_addr addr;
|
||||
addr.s_addr = default_gateway;
|
||||
inet_ntop(AF_INET, &addr, str, sizeof(str));
|
||||
this->default_gateway = str;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string& NetUtilInternal::GetNetmask() const {
|
||||
return netmask;
|
||||
}
|
||||
|
||||
bool NetUtilInternal::RetrieveNetmask() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
char netmaskStr[INET_ADDRSTRLEN];
|
||||
auto success = false;
|
||||
|
||||
#ifdef _WIN32
|
||||
ULONG flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER;
|
||||
ULONG family = AF_INET; // Only IPv4
|
||||
ULONG buffer_size = 15000;
|
||||
|
||||
std::vector<BYTE> buffer(buffer_size);
|
||||
auto adapter_addresses = reinterpret_cast<PIP_ADAPTER_ADDRESSES>(buffer.data());
|
||||
|
||||
DWORD result = GetAdaptersAddresses(family, flags, nullptr, adapter_addresses, &buffer_size);
|
||||
if (result == ERROR_BUFFER_OVERFLOW) {
|
||||
buffer.resize(buffer_size);
|
||||
adapter_addresses = reinterpret_cast<PIP_ADAPTER_ADDRESSES>(buffer.data());
|
||||
result = GetAdaptersAddresses(family, flags, nullptr, adapter_addresses, &buffer_size);
|
||||
}
|
||||
|
||||
if (result != NO_ERROR)
|
||||
return false;
|
||||
|
||||
for (auto adapter = adapter_addresses; adapter != nullptr; adapter = adapter->Next) {
|
||||
// Skip loopback and down interfaces
|
||||
if (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK || adapter->OperStatus != IfOperStatusUp)
|
||||
continue;
|
||||
|
||||
for (auto unicast = adapter->FirstUnicastAddress; unicast != nullptr;
|
||||
unicast = unicast->Next) {
|
||||
if (unicast->Address.lpSockaddr->sa_family == AF_INET) {
|
||||
ULONG prefix_length = unicast->OnLinkPrefixLength;
|
||||
ULONG mask = prefix_length == 0 ? 0 : 0xFFFFFFFF << (32 - prefix_length);
|
||||
|
||||
in_addr mask_addr{};
|
||||
mask_addr.S_un.S_addr = htonl(mask);
|
||||
|
||||
if (inet_ntop(AF_INET, &mask_addr, netmaskStr, INET_ADDRSTRLEN)) {
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
ifaddrs* ifap;
|
||||
|
||||
if (getifaddrs(&ifap) == 0) {
|
||||
ifaddrs* p;
|
||||
for (p = ifap; p; p = p->ifa_next) {
|
||||
if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET) {
|
||||
auto sa = reinterpret_cast<sockaddr_in*>(p->ifa_netmask);
|
||||
inet_ntop(AF_INET, &sa->sin_addr, netmaskStr, INET_ADDRSTRLEN);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freeifaddrs(ifap);
|
||||
#endif
|
||||
|
||||
if (success) {
|
||||
netmask = netmaskStr;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
const std::string& NetUtilInternal::GetIp() const {
|
||||
return ip;
|
||||
}
|
||||
|
||||
u32 NetUtilInternal::GetExternalIp() const {
|
||||
return external_ip;
|
||||
}
|
||||
|
||||
void NetUtilInternal::SetExternalIp(u32 addr) {
|
||||
external_ip = addr;
|
||||
}
|
||||
|
||||
u32 NetUtilInternal::GetNatType() const {
|
||||
return nat_type;
|
||||
}
|
||||
|
||||
bool NetUtilInternal::RetrieveIp() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
auto sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sockfd == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sockaddr_in sa{};
|
||||
socklen_t sa_len{sizeof(sa)};
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_addr.s_addr = inet_addr("1.1.1.1");
|
||||
sa.sin_port = htons(80);
|
||||
|
||||
#ifdef _WIN32
|
||||
#define close closesocket
|
||||
#endif
|
||||
|
||||
if (connect(sockfd, (sockaddr*)&sa, sa_len) == -1) {
|
||||
close(sockfd);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getsockname(sockfd, (struct sockaddr*)&sa, &sa_len) == -1) {
|
||||
close(sockfd);
|
||||
return false;
|
||||
}
|
||||
|
||||
char netmaskStr[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, &sa.sin_addr, netmaskStr, INET_ADDRSTRLEN);
|
||||
ip = netmaskStr;
|
||||
|
||||
close(sockfd);
|
||||
|
||||
#ifdef _WIN32
|
||||
#undef close
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int NetUtilInternal::ResolveHostname(const char* hostname, Libraries::Net::OrbisNetInAddr* addr) {
|
||||
const addrinfo hints = {
|
||||
.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG,
|
||||
.ai_family = AF_INET,
|
||||
};
|
||||
|
||||
addrinfo* info = nullptr;
|
||||
auto gai_result = getaddrinfo(hostname, nullptr, &hints, &info);
|
||||
|
||||
auto ret = ORBIS_OK;
|
||||
if (gai_result != 0) {
|
||||
// handle more errors
|
||||
LOG_ERROR(Lib_Net, "address resolution for {} failed: {}", hostname, gai_result);
|
||||
ret = ORBIS_NET_ERETURN;
|
||||
} else {
|
||||
ASSERT(info && info->ai_addr);
|
||||
in_addr resolved_addr = ((sockaddr_in*)info->ai_addr)->sin_addr;
|
||||
LOG_DEBUG(Lib_Net, "resolved address for {}: {}", hostname, inet_ntoa(resolved_addr));
|
||||
addr->inaddr_addr = resolved_addr.s_addr;
|
||||
}
|
||||
|
||||
freeaddrinfo(info);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace NetUtil
|
||||
@@ -0,0 +1,43 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Libraries::Net {
|
||||
struct OrbisNetInAddr;
|
||||
}
|
||||
|
||||
namespace NetUtil {
|
||||
|
||||
class NetUtilInternal {
|
||||
public:
|
||||
explicit NetUtilInternal() = default;
|
||||
~NetUtilInternal() = default;
|
||||
|
||||
private:
|
||||
std::array<u8, 6> ether_address{};
|
||||
std::string default_gateway{};
|
||||
std::string netmask{};
|
||||
std::string ip{};
|
||||
u32 external_ip{0};
|
||||
u32 nat_type{0};
|
||||
std::mutex m_mutex;
|
||||
|
||||
public:
|
||||
const std::array<u8, 6>& GetEthernetAddr() const;
|
||||
const std::string& GetDefaultGateway() const;
|
||||
const std::string& GetNetmask() const;
|
||||
const std::string& GetIp() const;
|
||||
u32 GetExternalIp() const;
|
||||
void SetExternalIp(u32 addr);
|
||||
u32 GetNatType() const;
|
||||
bool RetrieveEthernetAddr();
|
||||
bool RetrieveDefaultGateway();
|
||||
bool RetrieveNetmask();
|
||||
bool RetrieveIp();
|
||||
int ResolveHostname(const char* hostname, Libraries::Net::OrbisNetInAddr* addr);
|
||||
};
|
||||
} // namespace NetUtil
|
||||
@@ -0,0 +1,758 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#ifdef WIN32
|
||||
#define _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||
#include <Ws2tcpip.h>
|
||||
#include <iphlpapi.h>
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <common/singleton.h>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/network/net_ctl_codes.h"
|
||||
#include "core/libraries/network/netctl.h"
|
||||
#include "net_util.h"
|
||||
|
||||
namespace Libraries::NetCtl {
|
||||
|
||||
static NetCtlInternal netctl;
|
||||
|
||||
int PS4_SYSV_ABI sceNetBweCheckCallbackIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetBweClearEventIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetBweFinishInternetConnectionTestIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetBweGetInfoIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetBweRegisterCallbackIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetBweSetInternetConnectionTestResultIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetBweStartInternetConnectionTestBandwidthTestIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetBweStartInternetConnectionTestIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetBweUnregisterCallbackIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetInfoV6() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetResultV6() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetStateV6() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlRegisterCallbackV6() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlUnregisterCallbackV6() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlCheckCallback() {
|
||||
LOG_DEBUG(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlCheckCallbackForLibIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlClearEventForLibIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlClearEventIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlConnectConfIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlConnectIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlConnectWithRetryIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlDisableBandwidthManagementIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlDisconnectIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlEnableBandwidthManagementIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetBandwidthInfoIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetEtherLinkMode() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetIfStat() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetInfo(int code, OrbisNetCtlInfo* info) {
|
||||
LOG_DEBUG(Lib_NetCtl, "code = {}", code);
|
||||
if (!EmulatorSettings.IsConnectedToNetwork()) {
|
||||
return ORBIS_NET_CTL_ERROR_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
auto* netinfo = Common::Singleton<NetUtil::NetUtilInternal>::Instance();
|
||||
|
||||
switch (code) {
|
||||
case ORBIS_NET_CTL_INFO_DEVICE:
|
||||
info->device = ORBIS_NET_CTL_DEVICE_WIRED;
|
||||
break;
|
||||
case ORBIS_NET_CTL_INFO_ETHER_ADDR: {
|
||||
netinfo->RetrieveEthernetAddr();
|
||||
memcpy(info->ether_addr.data, netinfo->GetEthernetAddr().data(), 6);
|
||||
} break;
|
||||
case ORBIS_NET_CTL_INFO_MTU:
|
||||
info->mtu = 1500; // default value
|
||||
break;
|
||||
case ORBIS_NET_CTL_INFO_LINK:
|
||||
info->link = EmulatorSettings.IsConnectedToNetwork() ? ORBIS_NET_CTL_LINK_CONNECTED
|
||||
: ORBIS_NET_CTL_LINK_DISCONNECTED;
|
||||
break;
|
||||
case ORBIS_NET_CTL_INFO_IP_ADDRESS: {
|
||||
strcpy(info->ip_address,
|
||||
"127.0.0.1"); // placeholder in case ip retrieval failed
|
||||
auto success = netinfo->RetrieveIp();
|
||||
if (success) {
|
||||
strncpy(info->ip_address, netinfo->GetIp().data(), sizeof(info->ip_address));
|
||||
} else {
|
||||
LOG_WARNING(Lib_NetCtl, "local ip: failed to retrieve");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ORBIS_NET_CTL_INFO_NETMASK: {
|
||||
auto success = netinfo->RetrieveNetmask();
|
||||
if (success) {
|
||||
strncpy(info->netmask, netinfo->GetNetmask().data(), sizeof(info->netmask));
|
||||
LOG_DEBUG(Lib_NetCtl, "netmask: {}", info->netmask);
|
||||
} else {
|
||||
LOG_WARNING(Lib_NetCtl, "netmask: failed to retrieve");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ORBIS_NET_CTL_INFO_DEFAULT_ROUTE: {
|
||||
auto success = netinfo->RetrieveDefaultGateway();
|
||||
if (success) {
|
||||
strncpy(info->default_route, netinfo->GetDefaultGateway().data(),
|
||||
sizeof(info->default_route));
|
||||
LOG_DEBUG(Lib_NetCtl, "default gateway: {}", info->default_route);
|
||||
} else {
|
||||
LOG_WARNING(Lib_NetCtl, "default gateway: failed to retrieve");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ORBIS_NET_CTL_INFO_HTTP_PROXY_CONFIG:
|
||||
info->http_proxy_config = 0; // off
|
||||
LOG_DEBUG(Lib_NetCtl, "http proxy config: {}", info->http_proxy_config);
|
||||
break;
|
||||
case ORBIS_NET_CTL_INFO_PRIMARY_DNS:
|
||||
strcpy(info->primary_dns, "1.1.1.1");
|
||||
LOG_DEBUG(Lib_NetCtl, "http primary dns: {}", info->primary_dns);
|
||||
break;
|
||||
case ORBIS_NET_CTL_INFO_SECONDARY_DNS:
|
||||
strcpy(info->secondary_dns, "1.1.1.1");
|
||||
LOG_DEBUG(Lib_NetCtl, "http secondary dns: {}", info->secondary_dns);
|
||||
break;
|
||||
case ORBIS_NET_CTL_INFO_HTTP_PROXY_SERVER:
|
||||
info->http_proxy_server[0] = '\0';
|
||||
LOG_DEBUG(Lib_NetCtl, "http proxy server: none");
|
||||
break;
|
||||
case ORBIS_NET_CTL_INFO_HTTP_PROXY_PORT:
|
||||
info->http_proxy_port = 0;
|
||||
LOG_DEBUG(Lib_NetCtl, "http proxy config: {}", info->http_proxy_port);
|
||||
break;
|
||||
case ORBIS_NET_CTL_INFO_IP_CONFIG:
|
||||
info->ip_config = 1; // static
|
||||
LOG_DEBUG(Lib_NetCtl, "ip config: {}", info->ip_config);
|
||||
break;
|
||||
case ORBIS_NET_CTL_INFO_DHCP_HOSTNAME:
|
||||
LOG_DEBUG(Lib_NetCtl, "dhcp hostname: none");
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Lib_NetCtl, "{} unsupported code", code);
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetInfoIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetInfoV6IpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetNatInfo(OrbisNetCtlNatInfo* nat_info) {
|
||||
if (!nat_info) {
|
||||
return ORBIS_NET_CTL_ERROR_INVALID_ADDR;
|
||||
}
|
||||
if (nat_info->size != sizeof(OrbisNetCtlNatInfo)) {
|
||||
return ORBIS_NET_CTL_ERROR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
auto* netinfo = Common::Singleton<NetUtil::NetUtilInternal>::Instance();
|
||||
nat_info->nat_type = netinfo->GetNatType();
|
||||
const u32 ext_ip = netinfo->GetExternalIp();
|
||||
if (ext_ip != 0) {
|
||||
nat_info->stun_status = 1;
|
||||
nat_info->mapped_addr = ext_ip;
|
||||
} else {
|
||||
nat_info->stun_status = 0;
|
||||
nat_info->mapped_addr = inet_addr("127.0.0.1");
|
||||
if (netinfo->RetrieveIp()) {
|
||||
nat_info->mapped_addr = inet_addr(netinfo->GetIp().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DEBUG(Lib_NetCtl, "stun_status={} nat_type={} mapped_addr={:#x}", nat_info->stun_status,
|
||||
nat_info->nat_type, nat_info->mapped_addr);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetNatInfoIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetNetEvConfigInfoIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetResult(int eventType, int* errorCode) {
|
||||
if (!errorCode) {
|
||||
return ORBIS_NET_CTL_ERROR_INVALID_ADDR;
|
||||
}
|
||||
LOG_DEBUG(Lib_NetCtl, "(STUBBED) called eventType = {} ", eventType);
|
||||
*errorCode = 0;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetResultIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetResultV6IpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetScanInfoBssidForSsidListScanIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetScanInfoBssidIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetScanInfoByBssidIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetScanInfoForSsidListScanIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetScanInfoForSsidScanIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetState(int* state) {
|
||||
const auto connected = EmulatorSettings.IsConnectedToNetwork();
|
||||
LOG_DEBUG(Lib_NetCtl, "connected = {}", connected);
|
||||
const auto current_state =
|
||||
connected ? ORBIS_NET_CTL_STATE_IPOBTAINED : ORBIS_NET_CTL_STATE_DISCONNECTED;
|
||||
*state = current_state;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetState2IpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetStateIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetStateV6IpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetWifiType() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlInit() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlIsBandwidthManagementEnabledIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlRegisterCallback(OrbisNetCtlCallback func, void* arg, int* cid) {
|
||||
if (!func || !cid) {
|
||||
return ORBIS_NET_CTL_ERROR_INVALID_ADDR;
|
||||
}
|
||||
s32 result = netctl.RegisterCallback(func, arg);
|
||||
if (result < 0) {
|
||||
return result;
|
||||
} else {
|
||||
*cid = result;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlRegisterCallbackForLibIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlRegisterCallbackIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlRegisterCallbackV6IpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlScanIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlSetErrorNotificationEnabledIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlSetStunWithPaddingFlagIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlTerm() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlUnregisterCallback() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlUnregisterCallbackForLibIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlUnregisterCallbackIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlUnregisterCallbackV6IpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlUnsetStunWithPaddingFlagIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI Func_D8DCB6973537A3DC() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlCheckCallbackForNpToolkit() {
|
||||
LOG_DEBUG(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlClearEventForNpToolkit() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlRegisterCallbackForNpToolkit(OrbisNetCtlCallbackForNpToolkit func,
|
||||
void* arg, int* cid) {
|
||||
if (!func || !cid) {
|
||||
return ORBIS_NET_CTL_ERROR_INVALID_ADDR;
|
||||
}
|
||||
s32 result = netctl.RegisterNpToolkitCallback(func, arg);
|
||||
if (result < 0) {
|
||||
return result;
|
||||
} else {
|
||||
*cid = result;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlUnregisterCallbackForNpToolkit() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApCheckCallback() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApClearEvent() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApGetConnectInfo() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApGetInfo() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApGetResult() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApGetState() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApInit() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApRegisterCallback() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApStop() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApTerm() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApUnregisterCallback() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApAppInitWpaKey() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApAppInitWpaKeyForQa() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApAppStartWithRetry() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApAppStartWithRetryPid() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApRestart() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApRpCheckCallback() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApRpClearEvent() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApRpGetInfo() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApRpGetResult() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApRpGetState() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApRpRegisterCallback() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApRpStart() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApRpStartConf() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApRpStartWithRetry() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApRpStop() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlApRpUnregisterCallback() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("XtClSOC1xcU", "libSceNetBwe", 1, "libSceNetCtl", sceNetBweCheckCallbackIpcInt);
|
||||
LIB_FUNCTION("YALqoY4aeY0", "libSceNetBwe", 1, "libSceNetCtl", sceNetBweClearEventIpcInt);
|
||||
LIB_FUNCTION("ouyROWhGUbM", "libSceNetBwe", 1, "libSceNetCtl",
|
||||
sceNetBweFinishInternetConnectionTestIpcInt);
|
||||
LIB_FUNCTION("G4vltQ0Vs+0", "libSceNetBwe", 1, "libSceNetCtl", sceNetBweGetInfoIpcInt);
|
||||
LIB_FUNCTION("GqETL5+INhU", "libSceNetBwe", 1, "libSceNetCtl", sceNetBweRegisterCallbackIpcInt);
|
||||
LIB_FUNCTION("mEUt-phGd5E", "libSceNetBwe", 1, "libSceNetCtl",
|
||||
sceNetBweSetInternetConnectionTestResultIpcInt);
|
||||
LIB_FUNCTION("pQLJV5SEAqk", "libSceNetBwe", 1, "libSceNetCtl",
|
||||
sceNetBweStartInternetConnectionTestBandwidthTestIpcInt);
|
||||
LIB_FUNCTION("c+aYh130SV0", "libSceNetBwe", 1, "libSceNetCtl",
|
||||
sceNetBweStartInternetConnectionTestIpcInt);
|
||||
LIB_FUNCTION("0lViPaTB-R8", "libSceNetBwe", 1, "libSceNetCtl",
|
||||
sceNetBweUnregisterCallbackIpcInt);
|
||||
LIB_FUNCTION("Jy1EO5GdlcM", "libSceNetCtlV6", 1, "libSceNetCtl", sceNetCtlGetInfoV6);
|
||||
LIB_FUNCTION("H5yARg37U5g", "libSceNetCtlV6", 1, "libSceNetCtl", sceNetCtlGetResultV6);
|
||||
LIB_FUNCTION("+lxqIKeU9UY", "libSceNetCtlV6", 1, "libSceNetCtl", sceNetCtlGetStateV6);
|
||||
LIB_FUNCTION("1NE9OWdBIww", "libSceNetCtlV6", 1, "libSceNetCtl", sceNetCtlRegisterCallbackV6);
|
||||
LIB_FUNCTION("hIUVeUNxAwc", "libSceNetCtlV6", 1, "libSceNetCtl", sceNetCtlUnregisterCallbackV6);
|
||||
LIB_FUNCTION("iQw3iQPhvUQ", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlCheckCallback);
|
||||
LIB_FUNCTION("UF6H6+kjyQs", "libSceNetCtl", 1, "libSceNetCtl",
|
||||
sceNetCtlCheckCallbackForLibIpcInt);
|
||||
LIB_FUNCTION("vv6g8zoanL4", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlClearEventForLibIpcInt);
|
||||
LIB_FUNCTION("8OJ86vFucfo", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlClearEventIpcInt);
|
||||
LIB_FUNCTION("HCD46HVTyQg", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlConnectConfIpcInt);
|
||||
LIB_FUNCTION("ID+Gq3Ddzbg", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlConnectIpcInt);
|
||||
LIB_FUNCTION("aPpic8K75YA", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlConnectWithRetryIpcInt);
|
||||
LIB_FUNCTION("9y4IcsJdTCc", "libSceNetCtl", 1, "libSceNetCtl",
|
||||
sceNetCtlDisableBandwidthManagementIpcInt);
|
||||
LIB_FUNCTION("qOefcpoSs0k", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlDisconnectIpcInt);
|
||||
LIB_FUNCTION("x9bSmRSE+hc", "libSceNetCtl", 1, "libSceNetCtl",
|
||||
sceNetCtlEnableBandwidthManagementIpcInt);
|
||||
LIB_FUNCTION("eCUIlA2t5CE", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetBandwidthInfoIpcInt);
|
||||
LIB_FUNCTION("2EfjPXVPk3s", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetEtherLinkMode);
|
||||
LIB_FUNCTION("teuK4QnJTGg", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetIfStat);
|
||||
LIB_FUNCTION("obuxdTiwkF8", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetInfo);
|
||||
LIB_FUNCTION("xstcTqAhTys", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetInfoIpcInt);
|
||||
LIB_FUNCTION("Jy1EO5GdlcM", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetInfoV6);
|
||||
LIB_FUNCTION("arAQRFlwqaA", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetInfoV6IpcInt);
|
||||
LIB_FUNCTION("JO4yuTuMoKI", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetNatInfo);
|
||||
LIB_FUNCTION("x+cnsAxKSHo", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetNatInfoIpcInt);
|
||||
LIB_FUNCTION("hhTsdv99azU", "libSceNetCtl", 1, "libSceNetCtl",
|
||||
sceNetCtlGetNetEvConfigInfoIpcInt);
|
||||
LIB_FUNCTION("0cBgduPRR+M", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetResult);
|
||||
LIB_FUNCTION("NEtnusbZyAs", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetResultIpcInt);
|
||||
LIB_FUNCTION("H5yARg37U5g", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetResultV6);
|
||||
LIB_FUNCTION("vdsTa93atXY", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetResultV6IpcInt);
|
||||
LIB_FUNCTION("wP0Ab2maR1Y", "libSceNetCtl", 1, "libSceNetCtl",
|
||||
sceNetCtlGetScanInfoBssidForSsidListScanIpcInt);
|
||||
LIB_FUNCTION("Wn-+887Lt2s", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetScanInfoBssidIpcInt);
|
||||
LIB_FUNCTION("FEdkOG1VbQo", "libSceNetCtl", 1, "libSceNetCtl",
|
||||
sceNetCtlGetScanInfoByBssidIpcInt);
|
||||
LIB_FUNCTION("irV8voIAHDw", "libSceNetCtl", 1, "libSceNetCtl",
|
||||
sceNetCtlGetScanInfoForSsidListScanIpcInt);
|
||||
LIB_FUNCTION("L97eAHI0xxs", "libSceNetCtl", 1, "libSceNetCtl",
|
||||
sceNetCtlGetScanInfoForSsidScanIpcInt);
|
||||
LIB_FUNCTION("uBPlr0lbuiI", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetState);
|
||||
LIB_FUNCTION("JXlI9EZVjf4", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetState2IpcInt);
|
||||
LIB_FUNCTION("gvnJPMkSoAY", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetStateIpcInt);
|
||||
LIB_FUNCTION("+lxqIKeU9UY", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetStateV6);
|
||||
LIB_FUNCTION("O8Fk4w5MWss", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetStateV6IpcInt);
|
||||
LIB_FUNCTION("BXW9b3R1Nw4", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetWifiType);
|
||||
LIB_FUNCTION("gky0+oaNM4k", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlInit);
|
||||
LIB_FUNCTION("YtAnCkTR0K4", "libSceNetCtl", 1, "libSceNetCtl",
|
||||
sceNetCtlIsBandwidthManagementEnabledIpcInt);
|
||||
LIB_FUNCTION("UJ+Z7Q+4ck0", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlRegisterCallback);
|
||||
LIB_FUNCTION("WRvDk2syatE", "libSceNetCtl", 1, "libSceNetCtl",
|
||||
sceNetCtlRegisterCallbackForLibIpcInt);
|
||||
LIB_FUNCTION("rqkh2kXvLSw", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlRegisterCallbackIpcInt);
|
||||
LIB_FUNCTION("1NE9OWdBIww", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlRegisterCallbackV6);
|
||||
LIB_FUNCTION("ipqlpcIqRsQ", "libSceNetCtl", 1, "libSceNetCtl",
|
||||
sceNetCtlRegisterCallbackV6IpcInt);
|
||||
LIB_FUNCTION("reIsHryCDx4", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlScanIpcInt);
|
||||
LIB_FUNCTION("LJYiiIS4HB0", "libSceNetCtl", 1, "libSceNetCtl",
|
||||
sceNetCtlSetErrorNotificationEnabledIpcInt);
|
||||
LIB_FUNCTION("DjuqqqV08Nk", "libSceNetCtl", 1, "libSceNetCtl",
|
||||
sceNetCtlSetStunWithPaddingFlagIpcInt);
|
||||
LIB_FUNCTION("Z4wwCFiBELQ", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlTerm);
|
||||
LIB_FUNCTION("Rqm2OnZMCz0", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlUnregisterCallback);
|
||||
LIB_FUNCTION("urWaUWkEGZg", "libSceNetCtl", 1, "libSceNetCtl",
|
||||
sceNetCtlUnregisterCallbackForLibIpcInt);
|
||||
LIB_FUNCTION("by9cbB7JGJE", "libSceNetCtl", 1, "libSceNetCtl",
|
||||
sceNetCtlUnregisterCallbackIpcInt);
|
||||
LIB_FUNCTION("hIUVeUNxAwc", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlUnregisterCallbackV6);
|
||||
LIB_FUNCTION("Hjxpy28aID8", "libSceNetCtl", 1, "libSceNetCtl",
|
||||
sceNetCtlUnregisterCallbackV6IpcInt);
|
||||
LIB_FUNCTION("1HSvkN9oxO4", "libSceNetCtl", 1, "libSceNetCtl",
|
||||
sceNetCtlUnsetStunWithPaddingFlagIpcInt);
|
||||
LIB_FUNCTION("2Ny2lzU3o9w", "libSceNetCtl", 1, "libSceNetCtl", Func_D8DCB6973537A3DC);
|
||||
LIB_FUNCTION("u5oqtlIP+Fw", "libSceNetCtlForNpToolkit", 1, "libSceNetCtl",
|
||||
sceNetCtlCheckCallbackForNpToolkit);
|
||||
LIB_FUNCTION("saYB0b2ZWtI", "libSceNetCtlForNpToolkit", 1, "libSceNetCtl",
|
||||
sceNetCtlClearEventForNpToolkit);
|
||||
LIB_FUNCTION("wIsKy+TfeLs", "libSceNetCtlForNpToolkit", 1, "libSceNetCtl",
|
||||
sceNetCtlRegisterCallbackForNpToolkit);
|
||||
LIB_FUNCTION("2oUqKR5odGc", "libSceNetCtlForNpToolkit", 1, "libSceNetCtl",
|
||||
sceNetCtlUnregisterCallbackForNpToolkit);
|
||||
LIB_FUNCTION("19Ec7WkMFfQ", "libSceNetCtlAp", 1, "libSceNetCtl", sceNetCtlApCheckCallback);
|
||||
LIB_FUNCTION("meFMaDpdsVI", "libSceNetCtlAp", 1, "libSceNetCtl", sceNetCtlApClearEvent);
|
||||
LIB_FUNCTION("hfkLVdXmfnU", "libSceNetCtlAp", 1, "libSceNetCtl", sceNetCtlApGetConnectInfo);
|
||||
LIB_FUNCTION("LXADzTIzM9I", "libSceNetCtlAp", 1, "libSceNetCtl", sceNetCtlApGetInfo);
|
||||
LIB_FUNCTION("4jkLJc954+Q", "libSceNetCtlAp", 1, "libSceNetCtl", sceNetCtlApGetResult);
|
||||
LIB_FUNCTION("AKZOzsb9whc", "libSceNetCtlAp", 1, "libSceNetCtl", sceNetCtlApGetState);
|
||||
LIB_FUNCTION("FdN+edNRtiw", "libSceNetCtlAp", 1, "libSceNetCtl", sceNetCtlApInit);
|
||||
LIB_FUNCTION("pmjobSVHuY0", "libSceNetCtlAp", 1, "libSceNetCtl", sceNetCtlApRegisterCallback);
|
||||
LIB_FUNCTION("r-pOyN6AhsM", "libSceNetCtlAp", 1, "libSceNetCtl", sceNetCtlApStop);
|
||||
LIB_FUNCTION("cv5Y2efOTeg", "libSceNetCtlAp", 1, "libSceNetCtl", sceNetCtlApTerm);
|
||||
LIB_FUNCTION("NpTcFtaQ-0E", "libSceNetCtlAp", 1, "libSceNetCtl", sceNetCtlApUnregisterCallback);
|
||||
LIB_FUNCTION("R-4a9Yh4tG8", "libSceNetCtlApIpcInt", 1, "libSceNetCtl",
|
||||
sceNetCtlApAppInitWpaKey);
|
||||
LIB_FUNCTION("5oLJoOVBbGU", "libSceNetCtlApIpcInt", 1, "libSceNetCtl",
|
||||
sceNetCtlApAppInitWpaKeyForQa);
|
||||
LIB_FUNCTION("YtTwZ3pa4aQ", "libSceNetCtlApIpcInt", 1, "libSceNetCtl",
|
||||
sceNetCtlApAppStartWithRetry);
|
||||
LIB_FUNCTION("sgWeDrEt24U", "libSceNetCtlApIpcInt", 1, "libSceNetCtl",
|
||||
sceNetCtlApAppStartWithRetryPid);
|
||||
LIB_FUNCTION("amqSGH8l--s", "libSceNetCtlApIpcInt", 1, "libSceNetCtl", sceNetCtlApRestart);
|
||||
LIB_FUNCTION("DufQZgH5ISc", "libSceNetCtlApIpcInt", 1, "libSceNetCtl",
|
||||
sceNetCtlApRpCheckCallback);
|
||||
LIB_FUNCTION("qhZbOi+2qLY", "libSceNetCtlApIpcInt", 1, "libSceNetCtl", sceNetCtlApRpClearEvent);
|
||||
LIB_FUNCTION("VQl16Q+qXeY", "libSceNetCtlApIpcInt", 1, "libSceNetCtl", sceNetCtlApRpGetInfo);
|
||||
LIB_FUNCTION("3pxwYqHzGcw", "libSceNetCtlApIpcInt", 1, "libSceNetCtl", sceNetCtlApRpGetResult);
|
||||
LIB_FUNCTION("LEn8FGztKWc", "libSceNetCtlApIpcInt", 1, "libSceNetCtl", sceNetCtlApRpGetState);
|
||||
LIB_FUNCTION("ofGsK+xoAaM", "libSceNetCtlApIpcInt", 1, "libSceNetCtl",
|
||||
sceNetCtlApRpRegisterCallback);
|
||||
LIB_FUNCTION("mjFgpqNavHg", "libSceNetCtlApIpcInt", 1, "libSceNetCtl", sceNetCtlApRpStart);
|
||||
LIB_FUNCTION("HMvaHoZWsn8", "libSceNetCtlApIpcInt", 1, "libSceNetCtl", sceNetCtlApRpStartConf);
|
||||
LIB_FUNCTION("9Dxg7XSlr2s", "libSceNetCtlApIpcInt", 1, "libSceNetCtl",
|
||||
sceNetCtlApRpStartWithRetry);
|
||||
LIB_FUNCTION("6uvAl4RlEyk", "libSceNetCtlApIpcInt", 1, "libSceNetCtl", sceNetCtlApRpStop);
|
||||
LIB_FUNCTION("8eyH37Ns8tk", "libSceNetCtlApIpcInt", 1, "libSceNetCtl",
|
||||
sceNetCtlApRpUnregisterCallback);
|
||||
};
|
||||
|
||||
} // namespace Libraries::NetCtl
|
||||
@@ -0,0 +1,177 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/network/net_ctl_obj.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
namespace Libraries::NetCtl {
|
||||
|
||||
constexpr int ORBIS_NET_ETHER_ADDR_LEN = 6;
|
||||
|
||||
struct OrbisNetEtherAddr {
|
||||
u8 data[ORBIS_NET_ETHER_ADDR_LEN];
|
||||
};
|
||||
|
||||
constexpr int ORBIS_NET_CTL_SSID_LEN = 32 + 1;
|
||||
constexpr int ORBIS_NET_CTL_HOSTNAME_LEN = 255 + 1;
|
||||
constexpr int ORBIS_NET_CTL_AUTH_NAME_LEN = 127 + 1;
|
||||
constexpr int ORBIS_NET_CTL_IPV4_ADDR_STR_LEN = 16;
|
||||
|
||||
union OrbisNetCtlInfo {
|
||||
u32 device;
|
||||
OrbisNetEtherAddr ether_addr;
|
||||
u32 mtu;
|
||||
u32 link;
|
||||
OrbisNetEtherAddr bssid;
|
||||
char ssid[ORBIS_NET_CTL_SSID_LEN];
|
||||
u32 wifi_security;
|
||||
u8 rssi_dbm;
|
||||
uint8_t rssi_percentage;
|
||||
u8 channel;
|
||||
u32 ip_config;
|
||||
char dhcp_hostname[ORBIS_NET_CTL_HOSTNAME_LEN];
|
||||
char pppoe_auth_name[ORBIS_NET_CTL_AUTH_NAME_LEN];
|
||||
char ip_address[ORBIS_NET_CTL_IPV4_ADDR_STR_LEN];
|
||||
char netmask[ORBIS_NET_CTL_IPV4_ADDR_STR_LEN];
|
||||
char default_route[ORBIS_NET_CTL_IPV4_ADDR_STR_LEN];
|
||||
char primary_dns[ORBIS_NET_CTL_IPV4_ADDR_STR_LEN];
|
||||
char secondary_dns[ORBIS_NET_CTL_IPV4_ADDR_STR_LEN];
|
||||
u32 http_proxy_config;
|
||||
char http_proxy_server[ORBIS_NET_CTL_HOSTNAME_LEN];
|
||||
u16 http_proxy_port;
|
||||
};
|
||||
|
||||
struct OrbisNetCtlNatInfo {
|
||||
u32 size;
|
||||
s32 stun_status;
|
||||
s32 nat_type;
|
||||
u32 mapped_addr;
|
||||
};
|
||||
static_assert(sizeof(OrbisNetCtlNatInfo) == 0x10);
|
||||
|
||||
// GetInfo codes
|
||||
constexpr int ORBIS_NET_CTL_INFO_DEVICE = 1;
|
||||
constexpr int ORBIS_NET_CTL_INFO_ETHER_ADDR = 2;
|
||||
constexpr int ORBIS_NET_CTL_INFO_MTU = 3;
|
||||
constexpr int ORBIS_NET_CTL_INFO_LINK = 4;
|
||||
constexpr int ORBIS_NET_CTL_INFO_BSSID = 5;
|
||||
constexpr int ORBIS_NET_CTL_INFO_SSID = 6;
|
||||
constexpr int ORBIS_NET_CTL_INFO_WIFI_SECURITY = 7;
|
||||
constexpr int ORBIS_NET_CTL_INFO_RSSI_DBM = 8;
|
||||
constexpr int ORBIS_NET_CTL_INFO_RSSI_PERCENTAGE = 9;
|
||||
constexpr int ORBIS_NET_CTL_INFO_CHANNEL = 10;
|
||||
constexpr int ORBIS_NET_CTL_INFO_IP_CONFIG = 11;
|
||||
constexpr int ORBIS_NET_CTL_INFO_DHCP_HOSTNAME = 12;
|
||||
constexpr int ORBIS_NET_CTL_INFO_PPPOE_AUTH_NAME = 13;
|
||||
constexpr int ORBIS_NET_CTL_INFO_IP_ADDRESS = 14;
|
||||
constexpr int ORBIS_NET_CTL_INFO_NETMASK = 15;
|
||||
constexpr int ORBIS_NET_CTL_INFO_DEFAULT_ROUTE = 16;
|
||||
constexpr int ORBIS_NET_CTL_INFO_PRIMARY_DNS = 17;
|
||||
constexpr int ORBIS_NET_CTL_INFO_SECONDARY_DNS = 18;
|
||||
constexpr int ORBIS_NET_CTL_INFO_HTTP_PROXY_CONFIG = 19;
|
||||
constexpr int ORBIS_NET_CTL_INFO_HTTP_PROXY_SERVER = 20;
|
||||
constexpr int ORBIS_NET_CTL_INFO_HTTP_PROXY_PORT = 21;
|
||||
|
||||
int PS4_SYSV_ABI sceNetBweCheckCallbackIpcInt();
|
||||
int PS4_SYSV_ABI sceNetBweClearEventIpcInt();
|
||||
int PS4_SYSV_ABI sceNetBweFinishInternetConnectionTestIpcInt();
|
||||
int PS4_SYSV_ABI sceNetBweGetInfoIpcInt();
|
||||
int PS4_SYSV_ABI sceNetBweRegisterCallbackIpcInt();
|
||||
int PS4_SYSV_ABI sceNetBweSetInternetConnectionTestResultIpcInt();
|
||||
int PS4_SYSV_ABI sceNetBweStartInternetConnectionTestBandwidthTestIpcInt();
|
||||
int PS4_SYSV_ABI sceNetBweStartInternetConnectionTestIpcInt();
|
||||
int PS4_SYSV_ABI sceNetBweUnregisterCallbackIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlGetInfoV6();
|
||||
int PS4_SYSV_ABI sceNetCtlGetResultV6();
|
||||
int PS4_SYSV_ABI sceNetCtlGetStateV6();
|
||||
int PS4_SYSV_ABI sceNetCtlRegisterCallbackV6();
|
||||
int PS4_SYSV_ABI sceNetCtlUnregisterCallbackV6();
|
||||
int PS4_SYSV_ABI sceNetCtlCheckCallback();
|
||||
int PS4_SYSV_ABI sceNetCtlCheckCallbackForLibIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlClearEventForLibIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlClearEventIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlConnectConfIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlConnectIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlConnectWithRetryIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlDisableBandwidthManagementIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlDisconnectIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlEnableBandwidthManagementIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlGetBandwidthInfoIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlGetEtherLinkMode();
|
||||
int PS4_SYSV_ABI sceNetCtlGetIfStat();
|
||||
int PS4_SYSV_ABI sceNetCtlGetInfo(int code, OrbisNetCtlInfo* info);
|
||||
int PS4_SYSV_ABI sceNetCtlGetInfoIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlGetInfoV6IpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlGetNatInfo(OrbisNetCtlNatInfo* nat_info);
|
||||
int PS4_SYSV_ABI sceNetCtlGetNatInfoIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlGetNetEvConfigInfoIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlGetResult(int eventType, int* errorCode);
|
||||
int PS4_SYSV_ABI sceNetCtlGetResultIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlGetResultV6IpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlGetScanInfoBssidForSsidListScanIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlGetScanInfoBssidIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlGetScanInfoByBssidIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlGetScanInfoForSsidListScanIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlGetScanInfoForSsidScanIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlGetState(int* state);
|
||||
int PS4_SYSV_ABI sceNetCtlGetState2IpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlGetStateIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlGetStateV6IpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlGetWifiType();
|
||||
int PS4_SYSV_ABI sceNetCtlInit();
|
||||
int PS4_SYSV_ABI sceNetCtlIsBandwidthManagementEnabledIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlRegisterCallback(OrbisNetCtlCallback func, void* arg, int* cid);
|
||||
int PS4_SYSV_ABI sceNetCtlRegisterCallbackForLibIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlRegisterCallbackIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlRegisterCallbackV6IpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlScanIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlSetErrorNotificationEnabledIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlSetStunWithPaddingFlagIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlTerm();
|
||||
int PS4_SYSV_ABI sceNetCtlUnregisterCallback();
|
||||
int PS4_SYSV_ABI sceNetCtlUnregisterCallbackForLibIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlUnregisterCallbackIpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlUnregisterCallbackV6IpcInt();
|
||||
int PS4_SYSV_ABI sceNetCtlUnsetStunWithPaddingFlagIpcInt();
|
||||
int PS4_SYSV_ABI Func_D8DCB6973537A3DC();
|
||||
int PS4_SYSV_ABI sceNetCtlCheckCallbackForNpToolkit();
|
||||
int PS4_SYSV_ABI sceNetCtlClearEventForNpToolkit();
|
||||
int PS4_SYSV_ABI sceNetCtlRegisterCallbackForNpToolkit(OrbisNetCtlCallbackForNpToolkit func,
|
||||
void* arg, int* cid);
|
||||
int PS4_SYSV_ABI sceNetCtlUnregisterCallbackForNpToolkit();
|
||||
int PS4_SYSV_ABI sceNetCtlApCheckCallback();
|
||||
int PS4_SYSV_ABI sceNetCtlApClearEvent();
|
||||
int PS4_SYSV_ABI sceNetCtlApGetConnectInfo();
|
||||
int PS4_SYSV_ABI sceNetCtlApGetInfo();
|
||||
int PS4_SYSV_ABI sceNetCtlApGetResult();
|
||||
int PS4_SYSV_ABI sceNetCtlApGetState();
|
||||
int PS4_SYSV_ABI sceNetCtlApInit();
|
||||
int PS4_SYSV_ABI sceNetCtlApRegisterCallback();
|
||||
int PS4_SYSV_ABI sceNetCtlApStop();
|
||||
int PS4_SYSV_ABI sceNetCtlApTerm();
|
||||
int PS4_SYSV_ABI sceNetCtlApUnregisterCallback();
|
||||
int PS4_SYSV_ABI sceNetCtlApAppInitWpaKey();
|
||||
int PS4_SYSV_ABI sceNetCtlApAppInitWpaKeyForQa();
|
||||
int PS4_SYSV_ABI sceNetCtlApAppStartWithRetry();
|
||||
int PS4_SYSV_ABI sceNetCtlApAppStartWithRetryPid();
|
||||
int PS4_SYSV_ABI sceNetCtlApRestart();
|
||||
int PS4_SYSV_ABI sceNetCtlApRpCheckCallback();
|
||||
int PS4_SYSV_ABI sceNetCtlApRpClearEvent();
|
||||
int PS4_SYSV_ABI sceNetCtlApRpGetInfo();
|
||||
int PS4_SYSV_ABI sceNetCtlApRpGetResult();
|
||||
int PS4_SYSV_ABI sceNetCtlApRpGetState();
|
||||
int PS4_SYSV_ABI sceNetCtlApRpRegisterCallback();
|
||||
int PS4_SYSV_ABI sceNetCtlApRpStart();
|
||||
int PS4_SYSV_ABI sceNetCtlApRpStartConf();
|
||||
int PS4_SYSV_ABI sceNetCtlApRpStartWithRetry();
|
||||
int PS4_SYSV_ABI sceNetCtlApRpStop();
|
||||
int PS4_SYSV_ABI sceNetCtlApRpUnregisterCallback();
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::NetCtl
|
||||
@@ -0,0 +1,996 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <limits>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#ifdef __linux__
|
||||
#include <sys/eventfd.h>
|
||||
#endif
|
||||
|
||||
// clang-format off
|
||||
#include "sockets.h"
|
||||
// clang-format on
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/singleton.h"
|
||||
#include "core/libraries/kernel/kernel.h"
|
||||
#include "core/libraries/network/net_util.h"
|
||||
#include "net.h"
|
||||
#include "net_error.h"
|
||||
|
||||
namespace Libraries::Net {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr u16 kSignalingVPortNbo = 0xffff;
|
||||
constexpr size_t kVPortHeaderSize = 4;
|
||||
constexpr size_t kMaxUdpPayload = 65507;
|
||||
constexpr size_t kMaxP2PPayload = kMaxUdpPayload - kVPortHeaderSize;
|
||||
|
||||
#ifdef _WIN32
|
||||
constexpr net_socket kInvalidSocket = INVALID_SOCKET;
|
||||
#else
|
||||
constexpr net_socket kInvalidSocket = -1;
|
||||
#endif
|
||||
|
||||
enum class InternalChannel {
|
||||
Signaling,
|
||||
Control,
|
||||
Matching2,
|
||||
};
|
||||
|
||||
struct QueuedPacket {
|
||||
u32 source_addr{};
|
||||
u16 source_port{};
|
||||
std::vector<u8> payload;
|
||||
};
|
||||
|
||||
int FailWith(int error) {
|
||||
*Libraries::Kernel::__Error() = error;
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool IsValidSocket(net_socket socket) {
|
||||
return socket != kInvalidSocket;
|
||||
}
|
||||
|
||||
int LastSocketError() {
|
||||
#ifdef _WIN32
|
||||
return WSAGetLastError();
|
||||
#else
|
||||
return errno;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool IsRetryableSocketError(int error) {
|
||||
#ifdef _WIN32
|
||||
return error == WSAEWOULDBLOCK || error == WSAEINTR;
|
||||
#else
|
||||
return error == EAGAIN || error == EWOULDBLOCK || error == EINTR;
|
||||
#endif
|
||||
}
|
||||
|
||||
void CloseSocket(net_socket& socket) {
|
||||
if (!IsValidSocket(socket)) {
|
||||
return;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
::closesocket(socket);
|
||||
#else
|
||||
::close(socket);
|
||||
#endif
|
||||
socket = kInvalidSocket;
|
||||
}
|
||||
|
||||
bool SetSocketNonBlocking(net_socket socket) {
|
||||
#ifdef _WIN32
|
||||
u_long enabled = 1;
|
||||
return ::ioctlsocket(socket, FIONBIO, &enabled) == 0;
|
||||
#else
|
||||
int enabled = 1;
|
||||
return ::ioctl(socket, FIONBIO, &enabled) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
bool CreateReadinessPair(net_socket* read_socket, net_socket* signal_socket) {
|
||||
*read_socket = kInvalidSocket;
|
||||
*signal_socket = kInvalidSocket;
|
||||
|
||||
net_socket reader = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (!IsValidSocket(reader)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sockaddr_in address{};
|
||||
address.sin_family = AF_INET;
|
||||
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
address.sin_port = 0;
|
||||
if (::bind(reader, reinterpret_cast<const sockaddr*>(&address), sizeof(address)) < 0) {
|
||||
CloseSocket(reader);
|
||||
return false;
|
||||
}
|
||||
|
||||
socklen_t address_len = sizeof(address);
|
||||
if (::getsockname(reader, reinterpret_cast<sockaddr*>(&address), &address_len) < 0) {
|
||||
CloseSocket(reader);
|
||||
return false;
|
||||
}
|
||||
|
||||
net_socket signaler = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (!IsValidSocket(signaler) ||
|
||||
::connect(signaler, reinterpret_cast<const sockaddr*>(&address), sizeof(address)) < 0 ||
|
||||
!SetSocketNonBlocking(reader) || !SetSocketNonBlocking(signaler)) {
|
||||
CloseSocket(signaler);
|
||||
CloseSocket(reader);
|
||||
return false;
|
||||
}
|
||||
|
||||
*read_socket = reader;
|
||||
*signal_socket = signaler;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
class P2PTransport {
|
||||
public:
|
||||
static P2PTransport& Instance() {
|
||||
static P2PTransport transport;
|
||||
return transport;
|
||||
}
|
||||
|
||||
~P2PTransport() {
|
||||
Stop();
|
||||
}
|
||||
|
||||
bool Start() {
|
||||
#if !defined(__linux__) && !defined(_WIN32)
|
||||
LOG_ERROR(Lib_Net, "P2P transport is currently supported on Linux and Windows");
|
||||
return false;
|
||||
#else
|
||||
std::lock_guard lock(start_mutex);
|
||||
if (running.load(std::memory_order_acquire)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
host_socket = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (!IsValidSocket(host_socket)) {
|
||||
LOG_ERROR(Lib_Net, "P2P transport socket() failed error={}", LastSocketError());
|
||||
return false;
|
||||
}
|
||||
|
||||
int one = 1;
|
||||
(void)::setsockopt(host_socket, SOL_SOCKET, SO_REUSEADDR,
|
||||
reinterpret_cast<const char*>(&one), sizeof(one));
|
||||
if (!SetSocketNonBlocking(host_socket)) {
|
||||
LOG_ERROR(Lib_Net, "P2P transport failed to make socket nonblocking error={}",
|
||||
LastSocketError());
|
||||
CloseSocket(host_socket);
|
||||
return false;
|
||||
}
|
||||
|
||||
u16 requested_port = 0;
|
||||
if (const char* env = std::getenv("SHADPS4_P2P_PORT"); env != nullptr && *env != '\0') {
|
||||
char* end = nullptr;
|
||||
const long parsed = std::strtol(env, &end, 10);
|
||||
if (end == env || *end != '\0' || parsed < 1 || parsed > 65535) {
|
||||
LOG_ERROR(Lib_Net, "Invalid SHADPS4_P2P_PORT='{}'", env);
|
||||
CloseSocket(host_socket);
|
||||
return false;
|
||||
}
|
||||
requested_port = static_cast<u16>(parsed);
|
||||
}
|
||||
|
||||
sockaddr_in bind_addr{};
|
||||
bind_addr.sin_family = AF_INET;
|
||||
bind_addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
bind_addr.sin_port = htons(requested_port);
|
||||
|
||||
if (::bind(host_socket, reinterpret_cast<sockaddr*>(&bind_addr), sizeof(bind_addr)) < 0) {
|
||||
LOG_ERROR(Lib_Net, "P2P transport bind(port={}) failed error={}", requested_port,
|
||||
LastSocketError());
|
||||
CloseSocket(host_socket);
|
||||
return false;
|
||||
}
|
||||
|
||||
sockaddr_in actual{};
|
||||
socklen_t actual_len = sizeof(actual);
|
||||
if (::getsockname(host_socket, reinterpret_cast<sockaddr*>(&actual), &actual_len) < 0) {
|
||||
LOG_ERROR(Lib_Net, "P2P transport getsockname() failed error={}", LastSocketError());
|
||||
CloseSocket(host_socket);
|
||||
return false;
|
||||
}
|
||||
|
||||
physical_port = ntohs(actual.sin_port);
|
||||
running.store(true, std::memory_order_release);
|
||||
receive_thread = std::thread(&P2PTransport::ReceiveMain, this);
|
||||
|
||||
LOG_INFO(Lib_Net,
|
||||
"P2P transport ready: UDP physical_port={} (set SHADPS4_P2P_PORT to pin it)",
|
||||
physical_port);
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Stop() {
|
||||
#if defined(__linux__) || defined(_WIN32)
|
||||
std::lock_guard lock(start_mutex);
|
||||
if (!running.exchange(false, std::memory_order_acq_rel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (receive_thread.joinable()) {
|
||||
receive_thread.join();
|
||||
}
|
||||
|
||||
CloseSocket(host_socket);
|
||||
physical_port = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool IsReady() const {
|
||||
return running.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
u16 PhysicalPort() const {
|
||||
return physical_port;
|
||||
}
|
||||
|
||||
bool RegisterSocket(P2PSocket* socket, u16 requested_vport_nbo, u16* actual_vport_nbo) {
|
||||
if (socket == nullptr || actual_vport_nbo == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard lock(registry_mutex);
|
||||
u16 vport = requested_vport_nbo;
|
||||
|
||||
// vport 0 acts as an ephemeral request in this PoC.
|
||||
if (vport == 0) {
|
||||
for (u32 attempt = 0; attempt < 35000; ++attempt) {
|
||||
if (next_ephemeral_vport >= 65535) {
|
||||
next_ephemeral_vport = 30000;
|
||||
}
|
||||
const u16 candidate_host = next_ephemeral_vport++;
|
||||
const u16 candidate_nbo = htons(candidate_host);
|
||||
if (candidate_nbo == kSignalingVPortNbo || game_sockets.contains(candidate_nbo)) {
|
||||
continue;
|
||||
}
|
||||
vport = candidate_nbo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (vport == 0 || vport == kSignalingVPortNbo || game_sockets.contains(vport)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
game_sockets.emplace(vport, socket);
|
||||
*actual_vport_nbo = vport;
|
||||
return true;
|
||||
}
|
||||
|
||||
void UnregisterSocket(P2PSocket* socket, u16 vport_nbo) {
|
||||
std::lock_guard lock(registry_mutex);
|
||||
const auto it = game_sockets.find(vport_nbo);
|
||||
if (it != game_sockets.end() && it->second == socket) {
|
||||
game_sockets.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
int SendFrame(u16 source_vport_nbo, u16 dest_vport_nbo, const void* data, u32 len,
|
||||
u32 dest_addr_nbo, u16 dest_port_nbo) {
|
||||
#if !defined(__linux__) && !defined(_WIN32)
|
||||
return -1;
|
||||
#else
|
||||
if (!Start() || !IsValidSocket(host_socket) || (data == nullptr && len != 0) ||
|
||||
len > kMaxP2PPayload || dest_addr_nbo == 0 || dest_port_nbo == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::vector<u8> framed(kVPortHeaderSize + len);
|
||||
std::memcpy(framed.data(), &source_vport_nbo, sizeof(source_vport_nbo));
|
||||
std::memcpy(framed.data() + sizeof(source_vport_nbo), &dest_vport_nbo,
|
||||
sizeof(dest_vport_nbo));
|
||||
if (len != 0) {
|
||||
std::memcpy(framed.data() + kVPortHeaderSize, data, len);
|
||||
}
|
||||
|
||||
sockaddr_in dest{};
|
||||
dest.sin_family = AF_INET;
|
||||
dest.sin_addr.s_addr = dest_addr_nbo;
|
||||
dest.sin_port = dest_port_nbo;
|
||||
|
||||
const auto rc = ::sendto(host_socket, reinterpret_cast<const char*>(framed.data()),
|
||||
static_cast<int>(framed.size()), 0,
|
||||
reinterpret_cast<const sockaddr*>(&dest), sizeof(dest));
|
||||
if (rc < 0) {
|
||||
LOG_DEBUG(Lib_Net, "P2P sendto failed error={} dst={:#x}:{}", LastSocketError(),
|
||||
dest_addr_nbo, ntohs(dest_port_nbo));
|
||||
return -1;
|
||||
}
|
||||
if (static_cast<size_t>(rc) != framed.size()) {
|
||||
LOG_WARNING(Lib_Net, "P2P short UDP send: {} of {} bytes", rc, framed.size());
|
||||
return -1;
|
||||
}
|
||||
return static_cast<int>(len);
|
||||
#endif
|
||||
}
|
||||
|
||||
int SendInternal(const void* data, u32 len, u32 dest_addr_nbo, u16 dest_port_nbo) {
|
||||
return SendFrame(kSignalingVPortNbo, kSignalingVPortNbo, data, len, dest_addr_nbo,
|
||||
dest_port_nbo);
|
||||
}
|
||||
|
||||
int ReceiveInternal(InternalChannel channel, void* buf, u32 len, u32* from_addr,
|
||||
u16* from_port) {
|
||||
std::lock_guard lock(internal_mutex);
|
||||
auto& queue = QueueFor(channel);
|
||||
if (queue.empty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
QueuedPacket packet = std::move(queue.front());
|
||||
queue.pop_front();
|
||||
|
||||
const u32 copy_len = static_cast<u32>(std::min<size_t>(len, packet.payload.size()));
|
||||
if (copy_len != 0 && buf != nullptr) {
|
||||
std::memcpy(buf, packet.payload.data(), copy_len);
|
||||
}
|
||||
if (from_addr != nullptr) {
|
||||
*from_addr = packet.source_addr;
|
||||
}
|
||||
if (from_port != nullptr) {
|
||||
*from_port = packet.source_port;
|
||||
}
|
||||
return static_cast<int>(copy_len);
|
||||
}
|
||||
|
||||
private:
|
||||
P2PTransport() = default;
|
||||
|
||||
std::deque<QueuedPacket>& QueueFor(InternalChannel channel) {
|
||||
switch (channel) {
|
||||
case InternalChannel::Signaling:
|
||||
return signaling_queue;
|
||||
case InternalChannel::Control:
|
||||
return control_queue;
|
||||
case InternalChannel::Matching2:
|
||||
return matching2_queue;
|
||||
}
|
||||
return signaling_queue;
|
||||
}
|
||||
|
||||
static InternalChannel ClassifyInternal(const u8* payload, size_t size) {
|
||||
if (size >= 5 && std::memcmp(payload, "SHAD", 4) == 0) {
|
||||
if (payload[4] == 0x21) {
|
||||
return InternalChannel::Matching2;
|
||||
}
|
||||
if (payload[4] == 0x10) {
|
||||
return InternalChannel::Control;
|
||||
}
|
||||
}
|
||||
// STUN echo (6 bytes) and generic signaling 0x05/0x06/0x07 land here.
|
||||
return InternalChannel::Signaling;
|
||||
}
|
||||
|
||||
void PushInternal(InternalChannel channel, u32 from_addr, u16 from_port, const u8* payload,
|
||||
size_t size) {
|
||||
QueuedPacket packet{};
|
||||
packet.source_addr = from_addr;
|
||||
packet.source_port = from_port;
|
||||
packet.payload.assign(payload, payload + size);
|
||||
|
||||
std::lock_guard lock(internal_mutex);
|
||||
QueueFor(channel).push_back(std::move(packet));
|
||||
}
|
||||
|
||||
void ReceiveMain() {
|
||||
#if defined(__linux__) || defined(_WIN32)
|
||||
std::vector<u8> buffer(65536);
|
||||
while (running.load(std::memory_order_acquire)) {
|
||||
sockaddr_in from{};
|
||||
socklen_t from_len = sizeof(from);
|
||||
#ifdef _WIN32
|
||||
constexpr int receive_flags = 0;
|
||||
#else
|
||||
constexpr int receive_flags = MSG_DONTWAIT;
|
||||
#endif
|
||||
const auto rc = ::recvfrom(host_socket, reinterpret_cast<char*>(buffer.data()),
|
||||
static_cast<int>(buffer.size()), receive_flags,
|
||||
reinterpret_cast<sockaddr*>(&from), &from_len);
|
||||
|
||||
if (rc < 0) {
|
||||
const int error = LastSocketError();
|
||||
if (IsRetryableSocketError(error)) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
||||
continue;
|
||||
}
|
||||
LOG_WARNING(Lib_Net, "P2P recvfrom failed error={}", error);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rc < static_cast<decltype(rc)>(kVPortHeaderSize)) {
|
||||
LOG_DEBUG(Lib_Net, "P2P dropping too-short UDP frame size={}", rc);
|
||||
continue;
|
||||
}
|
||||
|
||||
u16 source_vport_nbo = 0;
|
||||
u16 dest_vport_nbo = 0;
|
||||
std::memcpy(&source_vport_nbo, buffer.data(), sizeof(source_vport_nbo));
|
||||
std::memcpy(&dest_vport_nbo, buffer.data() + sizeof(source_vport_nbo),
|
||||
sizeof(dest_vport_nbo));
|
||||
|
||||
const u8* payload = buffer.data() + kVPortHeaderSize;
|
||||
const size_t payload_size = static_cast<size_t>(rc) - kVPortHeaderSize;
|
||||
|
||||
if (dest_vport_nbo == kSignalingVPortNbo) {
|
||||
PushInternal(ClassifyInternal(payload, payload_size), from.sin_addr.s_addr,
|
||||
from.sin_port, payload, payload_size);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::lock_guard lock(registry_mutex);
|
||||
const auto it = game_sockets.find(dest_vport_nbo);
|
||||
if (it == game_sockets.end() || it->second == nullptr) {
|
||||
LOG_TRACE(Lib_Net, "P2P RX no socket for dst_vport={}", ntohs(dest_vport_nbo));
|
||||
continue;
|
||||
}
|
||||
|
||||
P2PDatagram packet{};
|
||||
packet.source_addr = from.sin_addr.s_addr;
|
||||
packet.source_port = from.sin_port;
|
||||
packet.source_vport = source_vport_nbo;
|
||||
packet.payload.assign(payload, payload + payload_size);
|
||||
it->second->EnqueuePacket(std::move(packet));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
mutable std::mutex start_mutex;
|
||||
std::atomic<bool> running{false};
|
||||
std::thread receive_thread;
|
||||
|
||||
net_socket host_socket{kInvalidSocket};
|
||||
u16 physical_port{0};
|
||||
|
||||
std::mutex registry_mutex;
|
||||
std::unordered_map<u16, P2PSocket*> game_sockets;
|
||||
u16 next_ephemeral_vport{30000};
|
||||
|
||||
std::mutex internal_mutex;
|
||||
std::deque<QueuedPacket> signaling_queue;
|
||||
std::deque<QueuedPacket> control_queue;
|
||||
std::deque<QueuedPacket> matching2_queue;
|
||||
};
|
||||
|
||||
int ReadIntegerOption(const void* optval, u32 optlen, int* out) {
|
||||
if (optval == nullptr || out == nullptr) {
|
||||
return FailWith(ORBIS_NET_EFAULT);
|
||||
}
|
||||
if (optlen < sizeof(int)) {
|
||||
return FailWith(ORBIS_NET_EINVAL);
|
||||
}
|
||||
std::memcpy(out, optval, sizeof(int));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WriteIntegerOption(void* optval, u32* optlen, int value) {
|
||||
if (optval == nullptr || optlen == nullptr) {
|
||||
return FailWith(ORBIS_NET_EFAULT);
|
||||
}
|
||||
if (*optlen < sizeof(int)) {
|
||||
return FailWith(ORBIS_NET_EINVAL);
|
||||
}
|
||||
std::memcpy(optval, &value, sizeof(int));
|
||||
*optlen = sizeof(int);
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
P2PSocket::P2PSocket(int domain, int type, int protocol) : Socket(domain, type, protocol) {
|
||||
#ifdef _WIN32
|
||||
if (!CreateReadinessPair(&readiness_fd, &readiness_signal_fd)) {
|
||||
LOG_ERROR(Lib_Net, "P2P readiness socket pair failed error={}", LastSocketError());
|
||||
valid = false;
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
readiness_fd = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
|
||||
if (readiness_fd < 0) {
|
||||
LOG_ERROR(Lib_Net, "P2P eventfd() failed errno={}", errno);
|
||||
valid = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
P2PSocket::~P2PSocket() {
|
||||
Close();
|
||||
}
|
||||
|
||||
int P2PSocket::Close() {
|
||||
std::lock_guard lock(m_mutex);
|
||||
if (!valid) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (bound) {
|
||||
P2PTransport::Instance().UnregisterSocket(this, local_addr.sin_vport);
|
||||
bound = false;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard receive_lock(receive_mutex);
|
||||
valid = false;
|
||||
}
|
||||
connected = false;
|
||||
receive_cv.notify_all();
|
||||
|
||||
#ifdef _WIN32
|
||||
CloseSocket(readiness_signal_fd);
|
||||
CloseSocket(readiness_fd);
|
||||
#elif defined(__linux__)
|
||||
if (readiness_fd >= 0) {
|
||||
::close(readiness_fd);
|
||||
readiness_fd = -1;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int P2PSocket::SetSocketOptions(int level, int optname, const void* optval, u32 optlen) {
|
||||
if (level != ORBIS_NET_SOL_SOCKET) {
|
||||
LOG_WARNING(Lib_Net, "P2P setsockopt ignoring non-SOL_SOCKET level={} opt={}", level,
|
||||
optname);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int value = 0;
|
||||
switch (optname) {
|
||||
case ORBIS_NET_SO_NBIO:
|
||||
case ORBIS_NET_SO_USECRYPTO:
|
||||
case ORBIS_NET_SO_USESIGNATURE:
|
||||
case ORBIS_NET_SO_REUSEADDR:
|
||||
case ORBIS_NET_SO_REUSEPORT:
|
||||
case ORBIS_NET_SO_KEEPALIVE:
|
||||
case ORBIS_NET_SO_BROADCAST:
|
||||
case ORBIS_NET_SO_ONESBCAST:
|
||||
case ORBIS_NET_SO_SNDBUF:
|
||||
case ORBIS_NET_SO_RCVBUF:
|
||||
case ORBIS_NET_SO_PRIORITY:
|
||||
if (ReadIntegerOption(optval, optlen, &value) < 0) {
|
||||
return -1;
|
||||
}
|
||||
integer_socket_options[optname] = value;
|
||||
if (optname == ORBIS_NET_SO_NBIO) {
|
||||
std::lock_guard receive_lock(receive_mutex);
|
||||
sockopt_so_nbio = value;
|
||||
} else if (optname == ORBIS_NET_SO_USECRYPTO) {
|
||||
sockopt_so_usecrypto = value;
|
||||
} else if (optname == ORBIS_NET_SO_USESIGNATURE) {
|
||||
sockopt_so_usesignature = value;
|
||||
}
|
||||
return 0;
|
||||
default:
|
||||
// For the first emulator-to-emulator PoC, remember that an option was requested
|
||||
// but do not fail the title merely because the Sony-specific behavior is absent.
|
||||
LOG_WARNING(Lib_Net, "P2P setsockopt opt={:#x} accepted as no-op", optname);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int P2PSocket::GetSocketOptions(int level, int optname, void* optval, u32* optlen) {
|
||||
if (level != ORBIS_NET_SOL_SOCKET) {
|
||||
return FailWith(ORBIS_NET_ENOPROTOOPT);
|
||||
}
|
||||
|
||||
switch (optname) {
|
||||
case ORBIS_NET_SO_NBIO:
|
||||
return WriteIntegerOption(optval, optlen, sockopt_so_nbio);
|
||||
case ORBIS_NET_SO_USECRYPTO:
|
||||
return WriteIntegerOption(optval, optlen, sockopt_so_usecrypto);
|
||||
case ORBIS_NET_SO_USESIGNATURE:
|
||||
return WriteIntegerOption(optval, optlen, sockopt_so_usesignature);
|
||||
case ORBIS_NET_SO_TYPE:
|
||||
return WriteIntegerOption(optval, optlen, socket_type);
|
||||
case ORBIS_NET_SO_ERROR:
|
||||
case ORBIS_NET_SO_ERROR_EX:
|
||||
return WriteIntegerOption(optval, optlen, 0);
|
||||
default:
|
||||
if (const auto it = integer_socket_options.find(optname);
|
||||
it != integer_socket_options.end()) {
|
||||
return WriteIntegerOption(optval, optlen, it->second);
|
||||
}
|
||||
return FailWith(ORBIS_NET_ENOPROTOOPT);
|
||||
}
|
||||
}
|
||||
|
||||
int P2PSocket::Bind(const OrbisNetSockaddr* addr, u32 addrlen) {
|
||||
if (socket_type != ORBIS_NET_SOCK_DGRAM_P2P) {
|
||||
return FailWith(ORBIS_NET_EOPNOTSUPP);
|
||||
}
|
||||
if (addr == nullptr) {
|
||||
return FailWith(ORBIS_NET_EFAULT);
|
||||
}
|
||||
if (addrlen < sizeof(OrbisNetSockaddrIn)) {
|
||||
return FailWith(ORBIS_NET_EINVAL);
|
||||
}
|
||||
|
||||
const auto* in = reinterpret_cast<const OrbisNetSockaddrIn*>(addr);
|
||||
if (in->sin_family != ORBIS_NET_AF_INET) {
|
||||
return FailWith(ORBIS_NET_EAFNOSUPPORT);
|
||||
}
|
||||
if (!EnsureP2PTransport()) {
|
||||
return FailWith(ORBIS_NET_ENETDOWN);
|
||||
}
|
||||
|
||||
std::lock_guard lock(m_mutex);
|
||||
if (!valid) {
|
||||
return FailWith(ORBIS_NET_EBADF);
|
||||
}
|
||||
if (bound) {
|
||||
return FailWith(ORBIS_NET_EINVAL);
|
||||
}
|
||||
|
||||
u16 actual_vport = 0;
|
||||
if (!P2PTransport::Instance().RegisterSocket(this, in->sin_vport, &actual_vport)) {
|
||||
return FailWith(ORBIS_NET_EADDRINUSE);
|
||||
}
|
||||
|
||||
local_addr = *in;
|
||||
local_addr.sin_len = sizeof(OrbisNetSockaddrIn);
|
||||
local_addr.sin_family = ORBIS_NET_AF_INET;
|
||||
local_addr.sin_port = htons(P2PTransport::Instance().PhysicalPort());
|
||||
local_addr.sin_vport = actual_vport;
|
||||
bound = true;
|
||||
|
||||
LOG_INFO(Lib_Net, "P2P bind: physical_port={} virtual_port={}",
|
||||
P2PTransport::Instance().PhysicalPort(), ntohs(local_addr.sin_vport));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int P2PSocket::Listen(int backlog) {
|
||||
LOG_WARNING(Lib_Net, "P2P STREAM listen not implemented (backlog={})", backlog);
|
||||
return FailWith(ORBIS_NET_EOPNOTSUPP);
|
||||
}
|
||||
|
||||
int P2PSocket::SendMessage(const OrbisNetMsghdr* msg, int flags) {
|
||||
if (msg == nullptr || msg->msg_iovlen < 0) {
|
||||
return FailWith(ORBIS_NET_EINVAL);
|
||||
}
|
||||
|
||||
size_t total = 0;
|
||||
for (int i = 0; i < msg->msg_iovlen; ++i) {
|
||||
total += static_cast<size_t>(msg->msg_iov[i].iov_len);
|
||||
if (total > kMaxP2PPayload || total > std::numeric_limits<u32>::max()) {
|
||||
return FailWith(ORBIS_NET_EMSGSIZE);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<u8> payload(total);
|
||||
size_t offset = 0;
|
||||
for (int i = 0; i < msg->msg_iovlen; ++i) {
|
||||
const auto len = static_cast<size_t>(msg->msg_iov[i].iov_len);
|
||||
if (len != 0) {
|
||||
std::memcpy(payload.data() + offset, msg->msg_iov[i].iov_base, len);
|
||||
}
|
||||
offset += len;
|
||||
}
|
||||
|
||||
return SendPacket(payload.data(), static_cast<u32>(payload.size()), flags,
|
||||
reinterpret_cast<const OrbisNetSockaddr*>(msg->msg_name), msg->msg_namelen);
|
||||
}
|
||||
|
||||
int P2PSocket::SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
|
||||
u32 tolen) {
|
||||
if (socket_type != ORBIS_NET_SOCK_DGRAM_P2P) {
|
||||
return FailWith(ORBIS_NET_EOPNOTSUPP);
|
||||
}
|
||||
if (msg == nullptr && len != 0) {
|
||||
return FailWith(ORBIS_NET_EFAULT);
|
||||
}
|
||||
if (len > kMaxP2PPayload) {
|
||||
return FailWith(ORBIS_NET_EMSGSIZE);
|
||||
}
|
||||
if (!EnsureP2PTransport()) {
|
||||
return FailWith(ORBIS_NET_ENETDOWN);
|
||||
}
|
||||
|
||||
std::lock_guard lock(m_mutex);
|
||||
if (!valid) {
|
||||
return FailWith(ORBIS_NET_EBADF);
|
||||
}
|
||||
|
||||
// UDP permits implicit local binding. Give an unbound guest socket an ephemeral vport.
|
||||
if (!bound) {
|
||||
u16 actual_vport = 0;
|
||||
if (!P2PTransport::Instance().RegisterSocket(this, 0, &actual_vport)) {
|
||||
return FailWith(ORBIS_NET_EADDRINUSE);
|
||||
}
|
||||
local_addr = {};
|
||||
local_addr.sin_len = sizeof(local_addr);
|
||||
local_addr.sin_family = ORBIS_NET_AF_INET;
|
||||
local_addr.sin_port = htons(P2PTransport::Instance().PhysicalPort());
|
||||
local_addr.sin_vport = actual_vport;
|
||||
bound = true;
|
||||
}
|
||||
|
||||
OrbisNetSockaddrIn target{};
|
||||
if (to != nullptr) {
|
||||
if (tolen < sizeof(OrbisNetSockaddrIn)) {
|
||||
return FailWith(ORBIS_NET_EINVAL);
|
||||
}
|
||||
target = *reinterpret_cast<const OrbisNetSockaddrIn*>(to);
|
||||
if (target.sin_family != ORBIS_NET_AF_INET) {
|
||||
return FailWith(ORBIS_NET_EAFNOSUPPORT);
|
||||
}
|
||||
} else {
|
||||
if (!connected) {
|
||||
return FailWith(ORBIS_NET_ENOTCONN);
|
||||
}
|
||||
target = peer_addr;
|
||||
}
|
||||
|
||||
const int rc = P2PTransport::Instance().SendFrame(local_addr.sin_vport, target.sin_vport, msg,
|
||||
len, target.sin_addr, target.sin_port);
|
||||
if (rc < 0) {
|
||||
return FailWith(ORBIS_NET_EHOSTUNREACH);
|
||||
}
|
||||
|
||||
LOG_TRACE(Lib_Net, "P2P TX {} bytes vport {} -> {} dst={:#x}:{}", len,
|
||||
ntohs(local_addr.sin_vport), ntohs(target.sin_vport), target.sin_addr,
|
||||
ntohs(target.sin_port));
|
||||
return rc;
|
||||
}
|
||||
|
||||
int P2PSocket::ReceiveMessage(OrbisNetMsghdr* msg, int flags) {
|
||||
if (msg == nullptr || msg->msg_iovlen < 0) {
|
||||
return FailWith(ORBIS_NET_EINVAL);
|
||||
}
|
||||
|
||||
size_t capacity = 0;
|
||||
for (int i = 0; i < msg->msg_iovlen; ++i) {
|
||||
capacity += static_cast<size_t>(msg->msg_iov[i].iov_len);
|
||||
if (capacity > std::numeric_limits<u32>::max()) {
|
||||
return FailWith(ORBIS_NET_EMSGSIZE);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<u8> payload(capacity);
|
||||
u32 name_len = msg->msg_namelen;
|
||||
const int rc = ReceivePacket(payload.data(), static_cast<u32>(payload.size()), flags,
|
||||
reinterpret_cast<OrbisNetSockaddr*>(msg->msg_name),
|
||||
msg->msg_name != nullptr ? &name_len : nullptr);
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
msg->msg_namelen = name_len;
|
||||
msg->msg_flags = 0;
|
||||
|
||||
size_t remaining = static_cast<size_t>(rc);
|
||||
size_t offset = 0;
|
||||
for (int i = 0; i < msg->msg_iovlen && remaining != 0; ++i) {
|
||||
const size_t amount =
|
||||
std::min<size_t>(remaining, static_cast<size_t>(msg->msg_iov[i].iov_len));
|
||||
if (amount != 0) {
|
||||
std::memcpy(msg->msg_iov[i].iov_base, payload.data() + offset, amount);
|
||||
}
|
||||
offset += amount;
|
||||
remaining -= amount;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int P2PSocket::ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, u32* fromlen) {
|
||||
if (buf == nullptr && len != 0) {
|
||||
return FailWith(ORBIS_NET_EFAULT);
|
||||
}
|
||||
|
||||
std::unique_lock lock(receive_mutex);
|
||||
while (receive_queue.empty() && valid) {
|
||||
if (sockopt_so_nbio != 0 || (flags & ORBIS_NET_MSG_DONTWAIT) != 0) {
|
||||
return FailWith(ORBIS_NET_EWOULDBLOCK);
|
||||
}
|
||||
receive_cv.wait(lock);
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
return FailWith(ORBIS_NET_EBADF);
|
||||
}
|
||||
|
||||
const P2PDatagram& packet = receive_queue.front();
|
||||
const u32 amount = static_cast<u32>(std::min<size_t>(len, packet.payload.size()));
|
||||
if (amount != 0) {
|
||||
std::memcpy(buf, packet.payload.data(), amount);
|
||||
}
|
||||
|
||||
if (from != nullptr && fromlen != nullptr) {
|
||||
if (*fromlen < sizeof(OrbisNetSockaddrIn)) {
|
||||
return FailWith(ORBIS_NET_EINVAL);
|
||||
}
|
||||
auto* out = reinterpret_cast<OrbisNetSockaddrIn*>(from);
|
||||
*out = {};
|
||||
out->sin_len = sizeof(OrbisNetSockaddrIn);
|
||||
out->sin_family = ORBIS_NET_AF_INET;
|
||||
out->sin_addr = packet.source_addr;
|
||||
out->sin_port = packet.source_port;
|
||||
out->sin_vport = packet.source_vport;
|
||||
*fromlen = sizeof(OrbisNetSockaddrIn);
|
||||
}
|
||||
|
||||
const bool peek = (flags & ORBIS_NET_MSG_PEEK) != 0;
|
||||
const bool peek_len =
|
||||
(flags & ORBIS_NET_MSG_PEEKLEN) == static_cast<int>(ORBIS_NET_MSG_PEEKLEN);
|
||||
const int result =
|
||||
peek_len ? static_cast<int>(packet.payload.size()) : static_cast<int>(amount);
|
||||
|
||||
if (!peek) {
|
||||
receive_queue.pop_front();
|
||||
#ifdef _WIN32
|
||||
if (receive_queue.empty() && IsValidSocket(readiness_fd)) {
|
||||
char signal = 0;
|
||||
(void)::recv(readiness_fd, &signal, sizeof(signal), 0);
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
if (receive_queue.empty() && readiness_fd >= 0) {
|
||||
u64 event_count = 0;
|
||||
(void)::read(readiness_fd, &event_count, sizeof(event_count));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
SocketPtr P2PSocket::Accept(OrbisNetSockaddr* addr, u32* addrlen) {
|
||||
LOG_WARNING(Lib_Net, "P2P STREAM accept not implemented");
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EOPNOTSUPP;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int P2PSocket::Connect(const OrbisNetSockaddr* addr, u32 namelen) {
|
||||
if (socket_type != ORBIS_NET_SOCK_DGRAM_P2P) {
|
||||
return FailWith(ORBIS_NET_EOPNOTSUPP);
|
||||
}
|
||||
if (addr == nullptr) {
|
||||
return FailWith(ORBIS_NET_EFAULT);
|
||||
}
|
||||
if (namelen < sizeof(OrbisNetSockaddrIn)) {
|
||||
return FailWith(ORBIS_NET_EINVAL);
|
||||
}
|
||||
|
||||
const auto* in = reinterpret_cast<const OrbisNetSockaddrIn*>(addr);
|
||||
if (in->sin_family != ORBIS_NET_AF_INET) {
|
||||
return FailWith(ORBIS_NET_EAFNOSUPPORT);
|
||||
}
|
||||
if (!EnsureP2PTransport()) {
|
||||
return FailWith(ORBIS_NET_ENETDOWN);
|
||||
}
|
||||
|
||||
std::lock_guard lock(m_mutex);
|
||||
peer_addr = *in;
|
||||
connected = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int P2PSocket::GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) {
|
||||
if (name == nullptr || namelen == nullptr) {
|
||||
return FailWith(ORBIS_NET_EFAULT);
|
||||
}
|
||||
if (*namelen < sizeof(OrbisNetSockaddrIn)) {
|
||||
return FailWith(ORBIS_NET_EINVAL);
|
||||
}
|
||||
|
||||
auto out = local_addr;
|
||||
out.sin_len = sizeof(out);
|
||||
out.sin_family = ORBIS_NET_AF_INET;
|
||||
if (!bound && EnsureP2PTransport()) {
|
||||
out.sin_port = htons(GetP2PConfiguredPort());
|
||||
}
|
||||
std::memcpy(name, &out, sizeof(out));
|
||||
*namelen = sizeof(out);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int P2PSocket::GetPeerName(OrbisNetSockaddr* addr, u32* namelen) {
|
||||
if (!connected) {
|
||||
return FailWith(ORBIS_NET_ENOTCONN);
|
||||
}
|
||||
if (addr == nullptr || namelen == nullptr) {
|
||||
return FailWith(ORBIS_NET_EFAULT);
|
||||
}
|
||||
if (*namelen < sizeof(OrbisNetSockaddrIn)) {
|
||||
return FailWith(ORBIS_NET_EINVAL);
|
||||
}
|
||||
std::memcpy(addr, &peer_addr, sizeof(peer_addr));
|
||||
*namelen = sizeof(peer_addr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int P2PSocket::fstat(Libraries::Kernel::OrbisKernelStat* stat) {
|
||||
LOG_DEBUG(Lib_Net, "P2P fstat called");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void P2PSocket::EnqueuePacket(P2PDatagram packet) {
|
||||
std::lock_guard lock(receive_mutex);
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool was_empty = receive_queue.empty();
|
||||
LOG_TRACE(Lib_Net, "P2P RX {} bytes vport {} <- {} src={:#x}:{}", packet.payload.size(),
|
||||
ntohs(local_addr.sin_vport), ntohs(packet.source_vport), packet.source_addr,
|
||||
ntohs(packet.source_port));
|
||||
receive_queue.push_back(std::move(packet));
|
||||
|
||||
#ifdef _WIN32
|
||||
if (was_empty && IsValidSocket(readiness_signal_fd)) {
|
||||
constexpr char signal = 1;
|
||||
if (::send(readiness_signal_fd, &signal, sizeof(signal), 0) < 0) {
|
||||
LOG_WARNING(Lib_Net, "P2P readiness signal failed error={}", LastSocketError());
|
||||
}
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
if (was_empty && readiness_fd >= 0) {
|
||||
const u64 one = 1;
|
||||
(void)::write(readiness_fd, &one, sizeof(one));
|
||||
}
|
||||
#endif
|
||||
receive_cv.notify_all();
|
||||
}
|
||||
|
||||
u16 GetP2PConfiguredPort() {
|
||||
return P2PTransport::Instance().PhysicalPort();
|
||||
}
|
||||
|
||||
u32 GetP2PAdvertisedAddr() {
|
||||
auto* netinfo = Common::Singleton<NetUtil::NetUtilInternal>::Instance();
|
||||
return netinfo->GetExternalIp();
|
||||
}
|
||||
|
||||
bool EnsureP2PTransport() {
|
||||
return P2PTransport::Instance().Start();
|
||||
}
|
||||
|
||||
bool P2PTransportIsReady() {
|
||||
return P2PTransport::Instance().IsReady();
|
||||
}
|
||||
|
||||
int P2PSignalingSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port) {
|
||||
return P2PTransport::Instance().SendInternal(data, len, dest_addr, dest_port);
|
||||
}
|
||||
|
||||
int P2PSignalingRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port) {
|
||||
return P2PTransport::Instance().ReceiveInternal(InternalChannel::Signaling, buf, len, from_addr,
|
||||
from_port);
|
||||
}
|
||||
|
||||
int P2PControlSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port) {
|
||||
return P2PTransport::Instance().SendInternal(data, len, dest_addr, dest_port);
|
||||
}
|
||||
|
||||
int P2PControlRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port) {
|
||||
return P2PTransport::Instance().ReceiveInternal(InternalChannel::Control, buf, len, from_addr,
|
||||
from_port);
|
||||
}
|
||||
|
||||
int P2PMatching2SendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port) {
|
||||
return P2PTransport::Instance().SendInternal(data, len, dest_addr, dest_port);
|
||||
}
|
||||
|
||||
int P2PMatching2RecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port) {
|
||||
return P2PTransport::Instance().ReceiveInternal(InternalChannel::Matching2, buf, len, from_addr,
|
||||
from_port);
|
||||
}
|
||||
|
||||
} // namespace Libraries::Net
|
||||
@@ -0,0 +1,712 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <vector>
|
||||
#include <common/assert.h>
|
||||
#include "common/error.h"
|
||||
#include "core/libraries/kernel/file_system.h"
|
||||
#include "core/libraries/kernel/kernel.h"
|
||||
#include "net.h"
|
||||
#ifndef _WIN32
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
#include "net_error.h"
|
||||
#include "sockets.h"
|
||||
|
||||
namespace Libraries::Net {
|
||||
|
||||
#ifdef _WIN32
|
||||
#define ERROR_CASE(errname) \
|
||||
case (WSA##errname): \
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_##errname; \
|
||||
return -1;
|
||||
#else
|
||||
#define ERROR_CASE(errname) \
|
||||
case (errname): \
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_##errname; \
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
static int ConvertReturnErrorCode(int retval) {
|
||||
if (retval < 0) {
|
||||
#ifdef _WIN32
|
||||
switch (WSAGetLastError()) {
|
||||
#else
|
||||
switch (errno) {
|
||||
#endif
|
||||
#ifndef _WIN32 // These errorcodes don't exist in WinSock
|
||||
ERROR_CASE(EPERM)
|
||||
ERROR_CASE(ENOENT)
|
||||
// ERROR_CASE(ESRCH)
|
||||
// ERROR_CASE(EIO)
|
||||
// ERROR_CASE(ENXIO)
|
||||
// ERROR_CASE(E2BIG)
|
||||
// ERROR_CASE(ENOEXEC)
|
||||
// ERROR_CASE(EDEADLK)
|
||||
ERROR_CASE(ENOMEM)
|
||||
// ERROR_CASE(ECHILD)
|
||||
// ERROR_CASE(EBUSY)
|
||||
ERROR_CASE(EEXIST)
|
||||
// ERROR_CASE(EXDEV)
|
||||
ERROR_CASE(ENODEV)
|
||||
// ERROR_CASE(ENOTDIR)
|
||||
// ERROR_CASE(EISDIR)
|
||||
ERROR_CASE(ENFILE)
|
||||
// ERROR_CASE(ENOTTY)
|
||||
// ERROR_CASE(ETXTBSY)
|
||||
// ERROR_CASE(EFBIG)
|
||||
ERROR_CASE(ENOSPC)
|
||||
// ERROR_CASE(ESPIPE)
|
||||
// ERROR_CASE(EROFS)
|
||||
// ERROR_CASE(EMLINK)
|
||||
ERROR_CASE(EPIPE)
|
||||
// ERROR_CASE(EDOM)
|
||||
// ERROR_CASE(ERANGE)
|
||||
// ERROR_CASE(ENOLCK)
|
||||
// ERROR_CASE(ENOSYS)
|
||||
// ERROR_CASE(EIDRM)
|
||||
// ERROR_CASE(EOVERFLOW)
|
||||
// ERROR_CASE(EILSEQ)
|
||||
// ERROR_CASE(ENOTSUP)
|
||||
ERROR_CASE(ECANCELED)
|
||||
// ERROR_CASE(EBADMSG)
|
||||
ERROR_CASE(ENODATA)
|
||||
// ERROR_CASE(ENOSR)
|
||||
// ERROR_CASE(ENOSTR)
|
||||
// ERROR_CASE(ETIME)
|
||||
#endif
|
||||
ERROR_CASE(EINTR)
|
||||
ERROR_CASE(EBADF)
|
||||
ERROR_CASE(EACCES)
|
||||
ERROR_CASE(EFAULT)
|
||||
ERROR_CASE(EINVAL)
|
||||
ERROR_CASE(EMFILE)
|
||||
ERROR_CASE(EWOULDBLOCK)
|
||||
ERROR_CASE(EINPROGRESS)
|
||||
ERROR_CASE(EALREADY)
|
||||
ERROR_CASE(ENOTSOCK)
|
||||
ERROR_CASE(EDESTADDRREQ)
|
||||
ERROR_CASE(EMSGSIZE)
|
||||
ERROR_CASE(EPROTOTYPE)
|
||||
ERROR_CASE(ENOPROTOOPT)
|
||||
ERROR_CASE(EPROTONOSUPPORT)
|
||||
#if defined(__APPLE__) || defined(_WIN32)
|
||||
ERROR_CASE(EOPNOTSUPP)
|
||||
#endif
|
||||
ERROR_CASE(EAFNOSUPPORT)
|
||||
ERROR_CASE(EADDRINUSE)
|
||||
ERROR_CASE(EADDRNOTAVAIL)
|
||||
ERROR_CASE(ENETDOWN)
|
||||
ERROR_CASE(ENETUNREACH)
|
||||
ERROR_CASE(ENETRESET)
|
||||
ERROR_CASE(ECONNABORTED)
|
||||
ERROR_CASE(ECONNRESET)
|
||||
ERROR_CASE(ENOBUFS)
|
||||
ERROR_CASE(EISCONN)
|
||||
ERROR_CASE(ENOTCONN)
|
||||
ERROR_CASE(ETIMEDOUT)
|
||||
ERROR_CASE(ECONNREFUSED)
|
||||
ERROR_CASE(ELOOP)
|
||||
ERROR_CASE(ENAMETOOLONG)
|
||||
ERROR_CASE(EHOSTUNREACH)
|
||||
ERROR_CASE(ENOTEMPTY)
|
||||
}
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EINTERNAL;
|
||||
return -1;
|
||||
}
|
||||
// if it is 0 or positive return it as it is
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int ConvertLevels(int level) {
|
||||
switch (level) {
|
||||
case ORBIS_NET_SOL_SOCKET:
|
||||
return SOL_SOCKET;
|
||||
case ORBIS_NET_IPPROTO_IP:
|
||||
return IPPROTO_IP;
|
||||
case ORBIS_NET_IPPROTO_TCP:
|
||||
return IPPROTO_TCP;
|
||||
case ORBIS_NET_IPPROTO_IPV6:
|
||||
return IPPROTO_IPV6;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void convertOrbisNetSockaddrToPosix(const OrbisNetSockaddr* src, sockaddr* dst) {
|
||||
if (src == nullptr || dst == nullptr)
|
||||
return;
|
||||
memset(dst, 0, sizeof(sockaddr));
|
||||
const OrbisNetSockaddrIn* src_in = (const OrbisNetSockaddrIn*)src;
|
||||
sockaddr_in* dst_in = (sockaddr_in*)dst;
|
||||
dst_in->sin_family = src_in->sin_family;
|
||||
dst_in->sin_port = src_in->sin_port;
|
||||
memcpy(&dst_in->sin_addr, &src_in->sin_addr, 4);
|
||||
}
|
||||
|
||||
static void convertPosixSockaddrToOrbis(sockaddr* src, OrbisNetSockaddr* dst) {
|
||||
if (src == nullptr || dst == nullptr)
|
||||
return;
|
||||
memset(dst, 0, sizeof(OrbisNetSockaddr));
|
||||
OrbisNetSockaddrIn* dst_in = (OrbisNetSockaddrIn*)dst;
|
||||
sockaddr_in* src_in = (sockaddr_in*)src;
|
||||
dst_in->sin_family = static_cast<unsigned char>(src_in->sin_family);
|
||||
dst_in->sin_port = src_in->sin_port;
|
||||
memcpy(&dst_in->sin_addr, &src_in->sin_addr, 4);
|
||||
}
|
||||
|
||||
bool PosixSocket::IsValid() const {
|
||||
#ifdef _WIN32
|
||||
return sock != INVALID_SOCKET;
|
||||
#else
|
||||
return sock != -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
int PosixSocket::Close() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
#ifdef _WIN32
|
||||
auto out = closesocket(sock);
|
||||
#else
|
||||
auto out = ::close(sock);
|
||||
#endif
|
||||
return ConvertReturnErrorCode(out);
|
||||
}
|
||||
|
||||
int PosixSocket::Bind(const OrbisNetSockaddr* addr, u32 addrlen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
sockaddr addr2;
|
||||
convertOrbisNetSockaddrToPosix(addr, &addr2);
|
||||
return ConvertReturnErrorCode(::bind(sock, &addr2, sizeof(sockaddr_in)));
|
||||
}
|
||||
|
||||
int PosixSocket::Listen(int backlog) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
return ConvertReturnErrorCode(::listen(sock, backlog));
|
||||
}
|
||||
|
||||
static int convertOrbisFlagsToPosix(int sock_type, int sce_flags) {
|
||||
int posix_flags = 0;
|
||||
|
||||
if (sce_flags & ORBIS_NET_MSG_PEEK)
|
||||
posix_flags |= MSG_PEEK;
|
||||
#ifndef _WIN32
|
||||
if (sce_flags & ORBIS_NET_MSG_DONTWAIT)
|
||||
posix_flags |= MSG_DONTWAIT;
|
||||
#endif
|
||||
// MSG_WAITALL is only valid for stream sockets
|
||||
if ((sce_flags & ORBIS_NET_MSG_WAITALL) &&
|
||||
((sock_type == ORBIS_NET_SOCK_STREAM) || (sock_type == ORBIS_NET_SOCK_STREAM_P2P)))
|
||||
posix_flags |= MSG_WAITALL;
|
||||
|
||||
return posix_flags;
|
||||
}
|
||||
|
||||
// On Windows, MSG_DONTWAIT is not handled natively by recv/send.
|
||||
// This function uses select() with zero timeout to simulate non-blocking behavior.
|
||||
static int socket_is_ready(int sock, bool is_read = true) {
|
||||
fd_set fds{};
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(sock, &fds);
|
||||
timeval timeout{0, 0};
|
||||
int res =
|
||||
select(sock + 1, is_read ? &fds : nullptr, is_read ? nullptr : &fds, nullptr, &timeout);
|
||||
if (res == 0) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EWOULDBLOCK;
|
||||
return -1;
|
||||
} else if (res < 0) {
|
||||
return ConvertReturnErrorCode(res);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int PosixSocket::SendMessage(const OrbisNetMsghdr* msg, int flags) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
#ifdef _WIN32
|
||||
int totalSent = 0;
|
||||
bool waitAll = (flags & ORBIS_NET_MSG_WAITALL) != 0;
|
||||
bool dontWait = (flags & ORBIS_NET_MSG_DONTWAIT) != 0;
|
||||
|
||||
// stream socket with multiple buffers
|
||||
bool use_wsamsg =
|
||||
(socket_type == ORBIS_NET_SOCK_STREAM || socket_type == ORBIS_NET_SOCK_STREAM_P2P) &&
|
||||
msg->msg_iovlen > 1;
|
||||
|
||||
for (int i = 0; i < msg->msg_iovlen; ++i) {
|
||||
char* buf = (char*)msg->msg_iov[i].iov_base;
|
||||
size_t remaining = msg->msg_iov[i].iov_len;
|
||||
|
||||
while (remaining > 0) {
|
||||
if (dontWait) {
|
||||
int ready = socket_is_ready(sock, false);
|
||||
if (ready <= 0)
|
||||
return ready;
|
||||
}
|
||||
|
||||
int sent = 0;
|
||||
if (use_wsamsg) {
|
||||
// only call WSASendMsg if we have multiple buffers
|
||||
LPFN_WSASENDMSG wsasendmsg = nullptr;
|
||||
GUID guid = WSAID_WSASENDMSG;
|
||||
DWORD bytes = 0;
|
||||
if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid),
|
||||
&wsasendmsg, sizeof(wsasendmsg), &bytes, nullptr, nullptr) != 0) {
|
||||
// fallback to send()
|
||||
sent = ::send(sock, buf, remaining, 0);
|
||||
} else {
|
||||
DWORD bytesSent = 0;
|
||||
int res = wsasendmsg(
|
||||
sock, reinterpret_cast<LPWSAMSG>(const_cast<OrbisNetMsghdr*>(msg)), 0,
|
||||
&bytesSent, nullptr, nullptr);
|
||||
if (res == SOCKET_ERROR)
|
||||
return ConvertReturnErrorCode(WSAGetLastError());
|
||||
sent = bytesSent;
|
||||
}
|
||||
} else {
|
||||
sent = ::send(sock, buf, remaining, 0);
|
||||
if (sent == SOCKET_ERROR)
|
||||
return ConvertReturnErrorCode(WSAGetLastError());
|
||||
}
|
||||
|
||||
totalSent += sent;
|
||||
remaining -= sent;
|
||||
buf += sent;
|
||||
|
||||
if (!waitAll)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return totalSent;
|
||||
|
||||
#else
|
||||
int native_flags = convertOrbisFlagsToPosix(socket_type, flags);
|
||||
int res = sendmsg(sock, reinterpret_cast<const msghdr*>(msg), native_flags);
|
||||
return ConvertReturnErrorCode(res);
|
||||
#endif
|
||||
}
|
||||
|
||||
int PosixSocket::SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
|
||||
u32 tolen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
int res = 0;
|
||||
#ifdef _WIN32
|
||||
if (flags & ORBIS_NET_MSG_DONTWAIT) {
|
||||
res = socket_is_ready(sock, false);
|
||||
if (res <= 0)
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
const auto posix_flags = convertOrbisFlagsToPosix(socket_type, flags);
|
||||
if (to == nullptr) {
|
||||
res = send(sock, (const char*)msg, len, posix_flags);
|
||||
} else {
|
||||
sockaddr addr{};
|
||||
convertOrbisNetSockaddrToPosix(to, &addr);
|
||||
res = sendto(sock, (const char*)msg, len, posix_flags, &addr, tolen);
|
||||
}
|
||||
return ConvertReturnErrorCode(res);
|
||||
}
|
||||
|
||||
int PosixSocket::ReceiveMessage(OrbisNetMsghdr* msg, int flags) {
|
||||
std::scoped_lock lock{receive_mutex};
|
||||
|
||||
#ifdef _WIN32
|
||||
int totalReceived = 0;
|
||||
bool waitAll = (flags & ORBIS_NET_MSG_WAITALL) != 0;
|
||||
bool dontWait = (flags & ORBIS_NET_MSG_DONTWAIT) != 0;
|
||||
|
||||
// stream socket with multiple buffers
|
||||
bool use_wsarecvmsg =
|
||||
(socket_type == ORBIS_NET_SOCK_STREAM || socket_type == ORBIS_NET_SOCK_STREAM_P2P) &&
|
||||
msg->msg_iovlen > 1;
|
||||
|
||||
for (int i = 0; i < msg->msg_iovlen; ++i) {
|
||||
char* buf = (char*)msg->msg_iov[i].iov_base;
|
||||
size_t remaining = msg->msg_iov[i].iov_len;
|
||||
|
||||
while (remaining > 0) {
|
||||
// emulate DONTWAIT
|
||||
if (dontWait) {
|
||||
int ready = socket_is_ready(sock, true);
|
||||
if (ready <= 0)
|
||||
return ready; // returns ORBIS_NET_ERROR_EWOULDBLOCK or error
|
||||
}
|
||||
|
||||
int received = 0;
|
||||
if (use_wsarecvmsg) {
|
||||
// only call WSARecvMsg if multiple buffers + stream
|
||||
LPFN_WSARECVMSG wsarecvmsg = nullptr;
|
||||
GUID guid = WSAID_WSARECVMSG;
|
||||
DWORD bytes = 0;
|
||||
if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid),
|
||||
&wsarecvmsg, sizeof(wsarecvmsg), &bytes, nullptr, nullptr) != 0) {
|
||||
// fallback to recv()
|
||||
received = ::recv(sock, buf, remaining, 0);
|
||||
if (received == SOCKET_ERROR)
|
||||
return ConvertReturnErrorCode(WSAGetLastError());
|
||||
} else {
|
||||
DWORD bytesReceived = 0;
|
||||
int res = wsarecvmsg(sock, reinterpret_cast<LPWSAMSG>(msg), &bytesReceived,
|
||||
nullptr, nullptr);
|
||||
if (res == SOCKET_ERROR)
|
||||
return ConvertReturnErrorCode(WSAGetLastError());
|
||||
received = bytesReceived;
|
||||
}
|
||||
} else {
|
||||
// fallback to recv() for UDP or single-buffer
|
||||
received = ::recv(sock, buf, remaining, 0);
|
||||
if (received == SOCKET_ERROR)
|
||||
return ConvertReturnErrorCode(WSAGetLastError());
|
||||
}
|
||||
|
||||
totalReceived += received;
|
||||
remaining -= received;
|
||||
buf += received;
|
||||
|
||||
// stop after first receive if WAITALL is not set
|
||||
if (!waitAll)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return totalReceived;
|
||||
|
||||
#else
|
||||
int native_flags = convertOrbisFlagsToPosix(socket_type, flags);
|
||||
int res = recvmsg(sock, reinterpret_cast<msghdr*>(msg), native_flags);
|
||||
return ConvertReturnErrorCode(res);
|
||||
#endif
|
||||
}
|
||||
|
||||
int PosixSocket::ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from,
|
||||
u32* fromlen) {
|
||||
std::scoped_lock lock{receive_mutex};
|
||||
int res = 0;
|
||||
#ifdef _WIN32
|
||||
if (flags & ORBIS_NET_MSG_DONTWAIT) {
|
||||
res = socket_is_ready(sock);
|
||||
if (res <= 0)
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
const auto posix_flags = convertOrbisFlagsToPosix(socket_type, flags);
|
||||
if (from == nullptr) {
|
||||
res = recv(sock, (char*)buf, len, posix_flags);
|
||||
} else {
|
||||
sockaddr addr{};
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
res = recvfrom(sock, (char*)buf, len, posix_flags, &addr,
|
||||
(fromlen && *fromlen <= sizeof(addr) ? (socklen_t*)fromlen : &addrlen));
|
||||
if (res > 0)
|
||||
convertPosixSockaddrToOrbis(&addr, from);
|
||||
}
|
||||
|
||||
return ConvertReturnErrorCode(res);
|
||||
}
|
||||
|
||||
SocketPtr PosixSocket::Accept(OrbisNetSockaddr* addr, u32* addrlen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
sockaddr addr2;
|
||||
socklen_t len = sizeof(addr2);
|
||||
net_socket new_socket = ::accept(sock, &addr2, &len);
|
||||
#ifdef _WIN32
|
||||
if (new_socket != INVALID_SOCKET) {
|
||||
#else
|
||||
if (new_socket >= 0) {
|
||||
#endif
|
||||
if (addr && addrlen) {
|
||||
convertPosixSockaddrToOrbis(&addr2, addr);
|
||||
*addrlen = sizeof(OrbisNetSockaddrIn);
|
||||
}
|
||||
return std::make_shared<PosixSocket>(new_socket);
|
||||
}
|
||||
ConvertReturnErrorCode(new_socket);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int PosixSocket::Connect(const OrbisNetSockaddr* addr, u32 namelen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
sockaddr addr2;
|
||||
convertOrbisNetSockaddrToPosix(addr, &addr2);
|
||||
int result = ::connect(sock, &addr2, sizeof(sockaddr_in));
|
||||
#ifdef _WIN32
|
||||
// Winsock returns EWOULDBLOCK where real hardware returns EINPROGRESS
|
||||
// Step in here on errors to address this.
|
||||
if (result == -1) {
|
||||
if (WSAGetLastError() == WSAEWOULDBLOCK) {
|
||||
WSASetLastError(WSAEINPROGRESS);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
LOG_DEBUG(Lib_Net, "raw connect result = {}, errno = {}", result,
|
||||
result == -1 ? Common::GetLastErrorMsg() : "none");
|
||||
return ConvertReturnErrorCode(result);
|
||||
}
|
||||
|
||||
int PosixSocket::GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
sockaddr addr;
|
||||
convertOrbisNetSockaddrToPosix(name, &addr);
|
||||
if (name != nullptr) {
|
||||
*namelen = sizeof(sockaddr_in);
|
||||
}
|
||||
int res = getsockname(sock, &addr, (socklen_t*)namelen);
|
||||
if (res >= 0) {
|
||||
convertPosixSockaddrToOrbis(&addr, name);
|
||||
*namelen = sizeof(OrbisNetSockaddrIn);
|
||||
}
|
||||
return ConvertReturnErrorCode(res);
|
||||
}
|
||||
|
||||
#define CASE_SETSOCKOPT(opt) \
|
||||
case ORBIS_NET_##opt: \
|
||||
return ConvertReturnErrorCode( \
|
||||
setsockopt(sock, native_level, opt, (const char*)optval, optlen))
|
||||
|
||||
#define CASE_SETSOCKOPT_VALUE(opt, value) \
|
||||
case opt: \
|
||||
if (optlen != sizeof(*value)) { \
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EFAULT; \
|
||||
return -1; \
|
||||
} \
|
||||
memcpy(value, optval, optlen); \
|
||||
return 0
|
||||
|
||||
int PosixSocket::SetSocketOptions(int level, int optname, const void* optval, u32 optlen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
s32 native_level = ConvertLevels(level);
|
||||
::linger native_linger;
|
||||
if (native_level == SOL_SOCKET) {
|
||||
switch (optname) {
|
||||
CASE_SETSOCKOPT(SO_REUSEADDR);
|
||||
CASE_SETSOCKOPT(SO_KEEPALIVE);
|
||||
CASE_SETSOCKOPT(SO_BROADCAST);
|
||||
CASE_SETSOCKOPT(SO_SNDBUF);
|
||||
CASE_SETSOCKOPT(SO_RCVBUF);
|
||||
CASE_SETSOCKOPT_VALUE(ORBIS_NET_SO_CONNECTTIMEO, &sockopt_so_connecttimeo);
|
||||
CASE_SETSOCKOPT_VALUE(ORBIS_NET_SO_REUSEPORT, &sockopt_so_reuseport);
|
||||
CASE_SETSOCKOPT_VALUE(ORBIS_NET_SO_USECRYPTO, &sockopt_so_usecrypto);
|
||||
CASE_SETSOCKOPT_VALUE(ORBIS_NET_SO_USESIGNATURE, &sockopt_so_usesignature);
|
||||
case ORBIS_NET_SO_SNDTIMEO:
|
||||
case ORBIS_NET_SO_RCVTIMEO: {
|
||||
if (optlen != sizeof(int)) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_ERROR_EFAULT;
|
||||
return -1;
|
||||
}
|
||||
std::vector<char> val;
|
||||
const auto optname_nat = (optname == ORBIS_NET_SO_SNDTIMEO) ? SO_SNDTIMEO : SO_RCVTIMEO;
|
||||
int timeout_us = *(const int*)optval;
|
||||
#ifdef _WIN32
|
||||
DWORD timeout = timeout_us / 1000;
|
||||
#else
|
||||
timeval timeout{.tv_sec = timeout_us / 1000000, .tv_usec = timeout_us % 1000000};
|
||||
#endif
|
||||
val.insert(val.end(), (char*)&timeout, (char*)&timeout + sizeof(timeout));
|
||||
optlen = sizeof(timeout);
|
||||
return ConvertReturnErrorCode(
|
||||
setsockopt(sock, native_level, optname_nat, val.data(), optlen));
|
||||
}
|
||||
case ORBIS_NET_SO_ONESBCAST: {
|
||||
|
||||
if (optlen != sizeof(sockopt_so_onesbcast)) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_ERROR_EFAULT;
|
||||
return -1;
|
||||
}
|
||||
memcpy(&sockopt_so_onesbcast, optval, optlen);
|
||||
return ConvertReturnErrorCode(
|
||||
setsockopt(sock, native_level, SO_BROADCAST, (const char*)optval, optlen));
|
||||
}
|
||||
case ORBIS_NET_SO_TYPE:
|
||||
case ORBIS_NET_SO_ERROR: {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_ENOPROTOOPT;
|
||||
return -1;
|
||||
}
|
||||
case ORBIS_NET_SO_LINGER: {
|
||||
if (socket_type != ORBIS_NET_SOCK_STREAM) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EPROCUNAVAIL;
|
||||
return -1;
|
||||
}
|
||||
if (optlen < sizeof(OrbisNetLinger)) {
|
||||
LOG_ERROR(Lib_Net, "size missmatched! optlen = {} OrbisNetLinger={}", optlen,
|
||||
sizeof(OrbisNetLinger));
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
const void* native_val = &native_linger;
|
||||
u32 native_len = sizeof(native_linger);
|
||||
native_linger.l_onoff = reinterpret_cast<const OrbisNetLinger*>(optval)->l_onoff;
|
||||
native_linger.l_linger = reinterpret_cast<const OrbisNetLinger*>(optval)->l_linger;
|
||||
return ConvertReturnErrorCode(
|
||||
setsockopt(sock, native_level, SO_LINGER, (const char*)native_val, native_len));
|
||||
}
|
||||
|
||||
case ORBIS_NET_SO_NAME:
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EINVAL;
|
||||
return -1; // don't support set for name
|
||||
case ORBIS_NET_SO_NBIO: {
|
||||
if (optlen < sizeof(sockopt_so_nbio)) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EINVAL;
|
||||
return -1;
|
||||
} else {
|
||||
memcpy(&sockopt_so_nbio, optval, sizeof(sockopt_so_nbio));
|
||||
}
|
||||
#ifdef _WIN32
|
||||
static_assert(sizeof(u_long) == sizeof(sockopt_so_nbio),
|
||||
"type used for ioctlsocket value does not have the expected size");
|
||||
return ConvertReturnErrorCode(ioctlsocket(sock, FIONBIO, (u_long*)&sockopt_so_nbio));
|
||||
#else
|
||||
return ConvertReturnErrorCode(ioctl(sock, FIONBIO, &sockopt_so_nbio));
|
||||
#endif
|
||||
}
|
||||
case ORBIS_NET_SO_ACCEPTTIMEO:
|
||||
LOG_ERROR(Lib_Net, "Unhandled option ORBIS_NET_SO_ACCEPTTIMEO");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
} else if (native_level == IPPROTO_IP) {
|
||||
switch (optname) {
|
||||
// CASE_SETSOCKOPT(IP_HDRINCL);
|
||||
CASE_SETSOCKOPT(IP_TOS);
|
||||
CASE_SETSOCKOPT(IP_TTL);
|
||||
CASE_SETSOCKOPT(IP_MULTICAST_IF);
|
||||
CASE_SETSOCKOPT(IP_MULTICAST_TTL);
|
||||
CASE_SETSOCKOPT(IP_MULTICAST_LOOP);
|
||||
CASE_SETSOCKOPT(IP_ADD_MEMBERSHIP);
|
||||
CASE_SETSOCKOPT(IP_DROP_MEMBERSHIP);
|
||||
CASE_SETSOCKOPT_VALUE(ORBIS_NET_IP_TTLCHK, &sockopt_ip_ttlchk);
|
||||
CASE_SETSOCKOPT_VALUE(ORBIS_NET_IP_MAXTTL, &sockopt_ip_maxttl);
|
||||
case ORBIS_NET_IP_HDRINCL: {
|
||||
if (socket_type != ORBIS_NET_SOCK_RAW) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EPROCUNAVAIL;
|
||||
return -1;
|
||||
}
|
||||
return ConvertReturnErrorCode(
|
||||
setsockopt(sock, native_level, optname, (const char*)optval, optlen));
|
||||
}
|
||||
}
|
||||
} else if (native_level == IPPROTO_TCP) {
|
||||
switch (optname) {
|
||||
CASE_SETSOCKOPT(TCP_NODELAY);
|
||||
CASE_SETSOCKOPT(TCP_MAXSEG);
|
||||
CASE_SETSOCKOPT_VALUE(ORBIS_NET_TCP_MSS_TO_ADVERTISE, &sockopt_tcp_mss_to_advertise);
|
||||
}
|
||||
}
|
||||
|
||||
UNREACHABLE_MSG("Unknown level ={} optname ={}", level, optname);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define CASE_GETSOCKOPT(opt) \
|
||||
case ORBIS_NET_##opt: { \
|
||||
socklen_t optlen_temp = *optlen; \
|
||||
auto retval = ConvertReturnErrorCode( \
|
||||
getsockopt(sock, native_level, opt, (char*)optval, &optlen_temp)); \
|
||||
*optlen = optlen_temp; \
|
||||
return retval; \
|
||||
}
|
||||
#define CASE_GETSOCKOPT_VALUE(opt, value) \
|
||||
case opt: \
|
||||
if (*optlen < sizeof(value)) { \
|
||||
*optlen = sizeof(value); \
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EFAULT; \
|
||||
return -1; \
|
||||
} \
|
||||
*optlen = sizeof(value); \
|
||||
*(decltype(value)*)optval = value; \
|
||||
return 0;
|
||||
|
||||
int PosixSocket::GetSocketOptions(int level, int optname, void* optval, u32* optlen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
s32 native_level = ConvertLevels(level);
|
||||
if (native_level == SOL_SOCKET) {
|
||||
switch (optname) {
|
||||
CASE_GETSOCKOPT(SO_REUSEADDR);
|
||||
CASE_GETSOCKOPT(SO_KEEPALIVE);
|
||||
CASE_GETSOCKOPT(SO_BROADCAST);
|
||||
CASE_GETSOCKOPT(SO_LINGER);
|
||||
CASE_GETSOCKOPT(SO_SNDBUF);
|
||||
CASE_GETSOCKOPT(SO_RCVBUF);
|
||||
CASE_GETSOCKOPT(SO_SNDTIMEO);
|
||||
CASE_GETSOCKOPT(SO_RCVTIMEO);
|
||||
CASE_GETSOCKOPT(SO_ERROR);
|
||||
CASE_GETSOCKOPT(SO_TYPE);
|
||||
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_NBIO, sockopt_so_nbio);
|
||||
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_CONNECTTIMEO, sockopt_so_connecttimeo);
|
||||
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_REUSEPORT, sockopt_so_reuseport);
|
||||
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_ONESBCAST, sockopt_so_onesbcast);
|
||||
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_USECRYPTO, sockopt_so_usecrypto);
|
||||
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_USESIGNATURE, sockopt_so_usesignature);
|
||||
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_NAME,
|
||||
(char)0); // writes an empty string to the output buffer
|
||||
case ORBIS_NET_SO_ERROR_EX: {
|
||||
socklen_t optlen_temp = *optlen;
|
||||
auto retval = ConvertReturnErrorCode(
|
||||
getsockopt(sock, level, SO_ERROR, (char*)optval, &optlen_temp));
|
||||
*optlen = optlen_temp;
|
||||
if (retval < 0) {
|
||||
s32 r = *Libraries::Kernel::__Error();
|
||||
*Libraries::Kernel::__Error() = 0;
|
||||
return r;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
} else if (native_level == IPPROTO_IP) {
|
||||
switch (optname) {
|
||||
CASE_GETSOCKOPT(IP_HDRINCL);
|
||||
CASE_GETSOCKOPT(IP_TOS);
|
||||
CASE_GETSOCKOPT(IP_TTL);
|
||||
CASE_GETSOCKOPT(IP_MULTICAST_IF);
|
||||
CASE_GETSOCKOPT(IP_MULTICAST_TTL);
|
||||
CASE_GETSOCKOPT(IP_MULTICAST_LOOP);
|
||||
CASE_GETSOCKOPT(IP_ADD_MEMBERSHIP);
|
||||
CASE_GETSOCKOPT(IP_DROP_MEMBERSHIP);
|
||||
CASE_GETSOCKOPT_VALUE(ORBIS_NET_IP_TTLCHK, sockopt_ip_ttlchk);
|
||||
CASE_GETSOCKOPT_VALUE(ORBIS_NET_IP_MAXTTL, sockopt_ip_maxttl);
|
||||
}
|
||||
} else if (native_level == IPPROTO_TCP) {
|
||||
switch (optname) {
|
||||
CASE_GETSOCKOPT(TCP_NODELAY);
|
||||
CASE_GETSOCKOPT(TCP_MAXSEG);
|
||||
CASE_GETSOCKOPT_VALUE(ORBIS_NET_TCP_MSS_TO_ADVERTISE, sockopt_tcp_mss_to_advertise);
|
||||
}
|
||||
}
|
||||
UNREACHABLE_MSG("Unknown level ={} optname ={}", level, optname);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int PosixSocket::GetPeerName(OrbisNetSockaddr* name, u32* namelen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
sockaddr addr;
|
||||
convertOrbisNetSockaddrToPosix(name, &addr);
|
||||
if (name != nullptr) {
|
||||
*namelen = sizeof(sockaddr_in);
|
||||
}
|
||||
int res = ::getpeername(sock, &addr, (socklen_t*)namelen);
|
||||
if (res >= 0) {
|
||||
convertPosixSockaddrToOrbis(&addr, name);
|
||||
*namelen = sizeof(OrbisNetSockaddrIn);
|
||||
}
|
||||
return ConvertReturnErrorCode(res);
|
||||
}
|
||||
|
||||
int PosixSocket::fstat(Libraries::Kernel::OrbisKernelStat* sb) {
|
||||
#ifdef _WIN32
|
||||
LOG_ERROR(Lib_Net, "(STUBBED) called");
|
||||
sb->st_mode = 0000777u | 0140000u;
|
||||
return 0;
|
||||
#else
|
||||
struct stat st{};
|
||||
int result = ::fstat(sock, &st);
|
||||
sb->st_mode = 0000777u | 0140000u;
|
||||
sb->st_size = st.st_size;
|
||||
sb->st_blocks = st.st_blocks;
|
||||
sb->st_blksize = st.st_blksize;
|
||||
// sb->st_flags = st.st_flags;
|
||||
return ConvertReturnErrorCode(result);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Libraries::Net
|
||||
@@ -0,0 +1,242 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef _WIN32
|
||||
#define _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||
// clang-format off
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <afunix.h>
|
||||
#include <iphlpapi.h>
|
||||
#include <mstcpip.h>
|
||||
// clang-format on
|
||||
typedef SOCKET net_socket;
|
||||
typedef int socklen_t;
|
||||
#ifndef LPFN_WSASENDMSG
|
||||
typedef INT(PASCAL* LPFN_WSASENDMSG)(SOCKET s, LPWSAMSG lpMsg, DWORD dwFlags,
|
||||
LPDWORD lpNumberOfBytesSent, LPWSAOVERLAPPED lpOverlapped,
|
||||
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
|
||||
#endif
|
||||
#ifndef WSAID_WSASENDMSG
|
||||
static const GUID WSAID_WSASENDMSG = {
|
||||
0xa441e712, 0x754f, 0x43ca, {0x84, 0xa7, 0x0d, 0xee, 0x44, 0xcf, 0x60, 0x6d}};
|
||||
#endif
|
||||
#ifndef LPFN_WSARECVMSG
|
||||
typedef INT(PASCAL* LPFN_WSARECVMSG)(SOCKET s, LPWSAMSG lpMsg, LPDWORD lpdwNumberOfBytesRecvd,
|
||||
LPWSAOVERLAPPED lpOverlapped,
|
||||
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
|
||||
#endif
|
||||
|
||||
#ifndef WSAID_WSARECVMSG
|
||||
static const GUID WSAID_WSARECVMSG = {
|
||||
0xf689d7c8, 0x6f1f, 0x436b, {0x8a, 0x53, 0xe5, 0x4f, 0xe3, 0x51, 0xc3, 0x22}};
|
||||
#endif
|
||||
#else
|
||||
#include <cerrno>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
typedef int net_socket;
|
||||
#endif
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include "net.h"
|
||||
|
||||
namespace Libraries::Kernel {
|
||||
struct OrbisKernelStat;
|
||||
}
|
||||
|
||||
namespace Libraries::Net {
|
||||
|
||||
struct Socket;
|
||||
|
||||
typedef std::shared_ptr<Socket> SocketPtr;
|
||||
|
||||
struct OrbisNetLinger {
|
||||
s32 l_onoff;
|
||||
s32 l_linger;
|
||||
};
|
||||
struct Socket {
|
||||
explicit Socket(int domain, int type, int protocol) : socket_type(type) {}
|
||||
virtual ~Socket() = default;
|
||||
virtual bool IsValid() const = 0;
|
||||
virtual int Close() = 0;
|
||||
virtual int SetSocketOptions(int level, int optname, const void* optval, u32 optlen) = 0;
|
||||
virtual int GetSocketOptions(int level, int optname, void* optval, u32* optlen) = 0;
|
||||
virtual int Bind(const OrbisNetSockaddr* addr, u32 addrlen) = 0;
|
||||
virtual int Listen(int backlog) = 0;
|
||||
virtual int SendMessage(const OrbisNetMsghdr* msg, int flags) = 0;
|
||||
virtual int SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
|
||||
u32 tolen) = 0;
|
||||
virtual SocketPtr Accept(OrbisNetSockaddr* addr, u32* addrlen) = 0;
|
||||
virtual int ReceiveMessage(OrbisNetMsghdr* msg, int flags) = 0;
|
||||
virtual int ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from,
|
||||
u32* fromlen) = 0;
|
||||
virtual int Connect(const OrbisNetSockaddr* addr, u32 namelen) = 0;
|
||||
virtual int GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) = 0;
|
||||
virtual int GetPeerName(OrbisNetSockaddr* addr, u32* namelen) = 0;
|
||||
virtual int fstat(Libraries::Kernel::OrbisKernelStat* stat) = 0;
|
||||
virtual std::optional<net_socket> Native() = 0;
|
||||
std::mutex m_mutex;
|
||||
std::mutex receive_mutex;
|
||||
int socket_type;
|
||||
};
|
||||
|
||||
struct PosixSocket : public Socket {
|
||||
net_socket sock;
|
||||
int sockopt_so_connecttimeo = 0;
|
||||
int sockopt_so_reuseport = 0;
|
||||
int sockopt_so_onesbcast = 0;
|
||||
int sockopt_so_usecrypto = 0;
|
||||
int sockopt_so_usesignature = 0;
|
||||
int sockopt_so_nbio = 0;
|
||||
int sockopt_ip_ttlchk = 0;
|
||||
int sockopt_ip_maxttl = 0;
|
||||
int sockopt_tcp_mss_to_advertise = 0;
|
||||
int socket_type;
|
||||
explicit PosixSocket(int domain, int type, int protocol)
|
||||
: Socket(domain, type, protocol), sock(socket(domain, type, protocol)) {
|
||||
socket_type = type;
|
||||
}
|
||||
explicit PosixSocket(net_socket sock) : Socket(0, 0, 0), sock(sock) {}
|
||||
bool IsValid() const override;
|
||||
int Close() override;
|
||||
int SetSocketOptions(int level, int optname, const void* optval, u32 optlen) override;
|
||||
int GetSocketOptions(int level, int optname, void* optval, u32* optlen) override;
|
||||
int Bind(const OrbisNetSockaddr* addr, u32 addrlen) override;
|
||||
int Listen(int backlog) override;
|
||||
int SendMessage(const OrbisNetMsghdr* msg, int flags) override;
|
||||
int SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
|
||||
u32 tolen) override;
|
||||
int ReceiveMessage(OrbisNetMsghdr* msg, int flags) override;
|
||||
int ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, u32* fromlen) override;
|
||||
SocketPtr Accept(OrbisNetSockaddr* addr, u32* addrlen) override;
|
||||
int Connect(const OrbisNetSockaddr* addr, u32 namelen) override;
|
||||
int GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) override;
|
||||
int GetPeerName(OrbisNetSockaddr* addr, u32* namelen) override;
|
||||
int fstat(Libraries::Kernel::OrbisKernelStat* stat) override;
|
||||
std::optional<net_socket> Native() override {
|
||||
return sock;
|
||||
}
|
||||
};
|
||||
|
||||
struct P2PDatagram {
|
||||
u32 source_addr{};
|
||||
u16 source_port{};
|
||||
u16 source_vport{};
|
||||
std::vector<u8> payload;
|
||||
};
|
||||
|
||||
struct P2PSocket : public Socket {
|
||||
explicit P2PSocket(int domain, int type, int protocol);
|
||||
~P2PSocket() override;
|
||||
bool IsValid() const override {
|
||||
return valid;
|
||||
}
|
||||
int Close() override;
|
||||
int SetSocketOptions(int level, int optname, const void* optval, u32 optlen) override;
|
||||
int GetSocketOptions(int level, int optname, void* optval, u32* optlen) override;
|
||||
int Bind(const OrbisNetSockaddr* addr, u32 addrlen) override;
|
||||
int Listen(int backlog) override;
|
||||
int SendMessage(const OrbisNetMsghdr* msg, int flags) override;
|
||||
int SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
|
||||
u32 tolen) override;
|
||||
int ReceiveMessage(OrbisNetMsghdr* msg, int flags) override;
|
||||
int ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, u32* fromlen) override;
|
||||
SocketPtr Accept(OrbisNetSockaddr* addr, u32* addrlen) override;
|
||||
int Connect(const OrbisNetSockaddr* addr, u32 namelen) override;
|
||||
int GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) override;
|
||||
int GetPeerName(OrbisNetSockaddr* addr, u32* namelen) override;
|
||||
int fstat(Libraries::Kernel::OrbisKernelStat* stat) override;
|
||||
std::optional<net_socket> Native() override {
|
||||
#ifdef _WIN32
|
||||
if (readiness_fd != INVALID_SOCKET) {
|
||||
return readiness_fd;
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
if (readiness_fd >= 0) {
|
||||
return readiness_fd;
|
||||
}
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
|
||||
// Called by the shared UDP transport after it demultiplexes a virtual port.
|
||||
void EnqueuePacket(P2PDatagram packet);
|
||||
|
||||
private:
|
||||
bool valid{true};
|
||||
bool bound{false};
|
||||
bool connected{false};
|
||||
OrbisNetSockaddrIn local_addr{};
|
||||
OrbisNetSockaddrIn peer_addr{};
|
||||
|
||||
int sockopt_so_nbio{0};
|
||||
int sockopt_so_usecrypto{0};
|
||||
int sockopt_so_usesignature{0};
|
||||
std::map<int, int> integer_socket_options;
|
||||
|
||||
std::condition_variable receive_cv;
|
||||
std::deque<P2PDatagram> receive_queue;
|
||||
|
||||
#ifdef _WIN32
|
||||
// wepoll requires a Winsock handle. A private loopback pair carries readiness bytes only.
|
||||
net_socket readiness_fd{INVALID_SOCKET};
|
||||
net_socket readiness_signal_fd{INVALID_SOCKET};
|
||||
#elif defined(__linux__)
|
||||
// eventfd carries readiness only; network data stays on the shared UDP transport.
|
||||
int readiness_fd{-1};
|
||||
#endif
|
||||
};
|
||||
|
||||
struct UnixSocket : public Socket {
|
||||
net_socket sock;
|
||||
int socket_type;
|
||||
explicit UnixSocket(int domain, int type, int protocol)
|
||||
: Socket(domain, type, protocol), sock(socket(domain, type, protocol)) {
|
||||
socket_type = type;
|
||||
}
|
||||
explicit UnixSocket(net_socket sock) : Socket(0, 0, 0), sock(sock) {}
|
||||
bool IsValid() const override;
|
||||
int Close() override;
|
||||
int SetSocketOptions(int level, int optname, const void* optval, u32 optlen) override;
|
||||
int GetSocketOptions(int level, int optname, void* optval, u32* optlen) override;
|
||||
int Bind(const OrbisNetSockaddr* addr, u32 addrlen) override;
|
||||
int Listen(int backlog) override;
|
||||
int SendMessage(const OrbisNetMsghdr* msg, int flags) override;
|
||||
int SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
|
||||
u32 tolen) override;
|
||||
int ReceiveMessage(OrbisNetMsghdr* msg, int flags) override;
|
||||
int ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, u32* fromlen) override;
|
||||
SocketPtr Accept(OrbisNetSockaddr* addr, u32* addrlen) override;
|
||||
int Connect(const OrbisNetSockaddr* addr, u32 namelen) override;
|
||||
int GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) override;
|
||||
int GetPeerName(OrbisNetSockaddr* addr, u32* namelen) override;
|
||||
int fstat(Libraries::Kernel::OrbisKernelStat* stat) override;
|
||||
std::optional<net_socket> Native() override {
|
||||
return sock;
|
||||
}
|
||||
};
|
||||
|
||||
u16 GetP2PConfiguredPort();
|
||||
u32 GetP2PAdvertisedAddr();
|
||||
bool EnsureP2PTransport();
|
||||
bool P2PTransportIsReady();
|
||||
int P2PSignalingSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port);
|
||||
int P2PSignalingRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port);
|
||||
int P2PControlSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port);
|
||||
int P2PControlRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port);
|
||||
int P2PMatching2SendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port);
|
||||
int P2PMatching2RecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port);
|
||||
|
||||
} // namespace Libraries::Net
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,231 @@
|
||||
// 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::Ssl {
|
||||
|
||||
struct OrbisSslCaCerts;
|
||||
|
||||
struct OrbisSslCaList {
|
||||
void* certs;
|
||||
int certsNum;
|
||||
};
|
||||
|
||||
int PS4_SYSV_ABI CA_MGMT_allocCertDistinguishedName();
|
||||
int PS4_SYSV_ABI CA_MGMT_certDistinguishedNameCompare();
|
||||
int PS4_SYSV_ABI CA_MGMT_convertKeyBlobToPKCS8Key();
|
||||
int PS4_SYSV_ABI CA_MGMT_convertKeyDER();
|
||||
int PS4_SYSV_ABI CA_MGMT_convertKeyPEM();
|
||||
int PS4_SYSV_ABI CA_MGMT_convertPKCS8KeyToKeyBlob();
|
||||
int PS4_SYSV_ABI CA_MGMT_convertProtectedPKCS8KeyToKeyBlob();
|
||||
int PS4_SYSV_ABI CA_MGMT_decodeCertificate();
|
||||
int PS4_SYSV_ABI CA_MGMT_enumAltName();
|
||||
int PS4_SYSV_ABI CA_MGMT_enumCrl();
|
||||
int PS4_SYSV_ABI CA_MGMT_extractAllCertDistinguishedName();
|
||||
int PS4_SYSV_ABI CA_MGMT_extractBasicConstraint();
|
||||
int PS4_SYSV_ABI CA_MGMT_extractCertASN1Name();
|
||||
int PS4_SYSV_ABI CA_MGMT_extractCertTimes();
|
||||
int PS4_SYSV_ABI CA_MGMT_extractKeyBlobEx();
|
||||
int PS4_SYSV_ABI CA_MGMT_extractKeyBlobTypeEx();
|
||||
int PS4_SYSV_ABI CA_MGMT_extractPublicKeyInfo();
|
||||
int PS4_SYSV_ABI CA_MGMT_extractSerialNum();
|
||||
int PS4_SYSV_ABI CA_MGMT_extractSignature();
|
||||
int PS4_SYSV_ABI CA_MGMT_free();
|
||||
int PS4_SYSV_ABI CA_MGMT_freeCertDistinguishedName();
|
||||
int PS4_SYSV_ABI CA_MGMT_freeCertDistinguishedNameOnStack();
|
||||
int PS4_SYSV_ABI CA_MGMT_freeCertificate();
|
||||
int PS4_SYSV_ABI CA_MGMT_freeKeyBlob();
|
||||
int PS4_SYSV_ABI CA_MGMT_freeSearchDetails();
|
||||
int PS4_SYSV_ABI CA_MGMT_getCertSignAlgoType();
|
||||
int PS4_SYSV_ABI CA_MGMT_keyBlobToDER();
|
||||
int PS4_SYSV_ABI CA_MGMT_keyBlobToPEM();
|
||||
int PS4_SYSV_ABI CA_MGMT_makeKeyBlobEx();
|
||||
int PS4_SYSV_ABI CA_MGMT_rawVerifyOID();
|
||||
int PS4_SYSV_ABI CA_MGMT_reorderChain();
|
||||
int PS4_SYSV_ABI CA_MGMT_returnCertificatePrints();
|
||||
int PS4_SYSV_ABI CA_MGMT_verifyCertWithKeyBlob();
|
||||
int PS4_SYSV_ABI CA_MGMT_verifySignature();
|
||||
int PS4_SYSV_ABI CERT_checkCertificateIssuer();
|
||||
int PS4_SYSV_ABI CERT_checkCertificateIssuer2();
|
||||
int PS4_SYSV_ABI CERT_checkCertificateIssuerSerialNumber();
|
||||
int PS4_SYSV_ABI CERT_CompSubjectAltNames();
|
||||
int PS4_SYSV_ABI CERT_CompSubjectAltNames2();
|
||||
int PS4_SYSV_ABI CERT_CompSubjectAltNamesExact();
|
||||
int PS4_SYSV_ABI CERT_CompSubjectCommonName();
|
||||
int PS4_SYSV_ABI CERT_CompSubjectCommonName2();
|
||||
int PS4_SYSV_ABI CERT_ComputeBufferHash();
|
||||
int PS4_SYSV_ABI CERT_decryptRSASignature();
|
||||
int PS4_SYSV_ABI CERT_decryptRSASignatureBuffer();
|
||||
int PS4_SYSV_ABI CERT_enumerateAltName();
|
||||
int PS4_SYSV_ABI CERT_enumerateAltName2();
|
||||
int PS4_SYSV_ABI CERT_enumerateCRL();
|
||||
int PS4_SYSV_ABI CERT_enumerateCRL2();
|
||||
int PS4_SYSV_ABI CERT_enumerateCRLAux();
|
||||
int PS4_SYSV_ABI CERT_extractAllDistinguishedNames();
|
||||
int PS4_SYSV_ABI CERT_extractDistinguishedNames();
|
||||
int PS4_SYSV_ABI CERT_extractDistinguishedNames2();
|
||||
int PS4_SYSV_ABI CERT_extractDistinguishedNamesFromName();
|
||||
int PS4_SYSV_ABI CERT_extractRSAKey();
|
||||
int PS4_SYSV_ABI CERT_extractSerialNum();
|
||||
int PS4_SYSV_ABI CERT_extractSerialNum2();
|
||||
int PS4_SYSV_ABI CERT_extractValidityTime();
|
||||
int PS4_SYSV_ABI CERT_extractValidityTime2();
|
||||
int PS4_SYSV_ABI CERT_getCertExtension();
|
||||
int PS4_SYSV_ABI CERT_getCertificateExtensions();
|
||||
int PS4_SYSV_ABI CERT_getCertificateExtensions2();
|
||||
int PS4_SYSV_ABI CERT_getCertificateIssuerSerialNumber();
|
||||
int PS4_SYSV_ABI CERT_getCertificateIssuerSerialNumber2();
|
||||
int PS4_SYSV_ABI CERT_getCertificateKeyUsage();
|
||||
int PS4_SYSV_ABI CERT_getCertificateKeyUsage2();
|
||||
int PS4_SYSV_ABI CERT_getCertificateSubject();
|
||||
int PS4_SYSV_ABI CERT_getCertificateSubject2();
|
||||
int PS4_SYSV_ABI CERT_getCertSignAlgoType();
|
||||
int PS4_SYSV_ABI CERT_GetCertTime();
|
||||
int PS4_SYSV_ABI CERT_getNumberOfChild();
|
||||
int PS4_SYSV_ABI CERT_getRSASignatureAlgo();
|
||||
int PS4_SYSV_ABI CERT_getSignatureItem();
|
||||
int PS4_SYSV_ABI CERT_getSubjectCommonName();
|
||||
int PS4_SYSV_ABI CERT_getSubjectCommonName2();
|
||||
int PS4_SYSV_ABI CERT_isRootCertificate();
|
||||
int PS4_SYSV_ABI CERT_isRootCertificate2();
|
||||
int PS4_SYSV_ABI CERT_rawVerifyOID();
|
||||
int PS4_SYSV_ABI CERT_rawVerifyOID2();
|
||||
int PS4_SYSV_ABI CERT_setKeyFromSubjectPublicKeyInfo();
|
||||
int PS4_SYSV_ABI CERT_setKeyFromSubjectPublicKeyInfoCert();
|
||||
int PS4_SYSV_ABI CERT_STORE_addCertAuthority();
|
||||
int PS4_SYSV_ABI CERT_STORE_addIdentity();
|
||||
int PS4_SYSV_ABI CERT_STORE_addIdentityNakedKey();
|
||||
int PS4_SYSV_ABI CERT_STORE_addIdentityPSK();
|
||||
int PS4_SYSV_ABI CERT_STORE_addIdentityWithCertificateChain();
|
||||
int PS4_SYSV_ABI CERT_STORE_addTrustPoint();
|
||||
int PS4_SYSV_ABI CERT_STORE_createStore();
|
||||
int PS4_SYSV_ABI CERT_STORE_findCertBySubject();
|
||||
int PS4_SYSV_ABI CERT_STORE_findIdentityByTypeFirst();
|
||||
int PS4_SYSV_ABI CERT_STORE_findIdentityByTypeNext();
|
||||
int PS4_SYSV_ABI CERT_STORE_findIdentityCertChainFirst();
|
||||
int PS4_SYSV_ABI CERT_STORE_findIdentityCertChainNext();
|
||||
int PS4_SYSV_ABI CERT_STORE_findPskByIdentity();
|
||||
int PS4_SYSV_ABI CERT_STORE_releaseStore();
|
||||
int PS4_SYSV_ABI CERT_STORE_traversePskListHead();
|
||||
int PS4_SYSV_ABI CERT_STORE_traversePskListNext();
|
||||
int PS4_SYSV_ABI CERT_validateCertificate();
|
||||
int PS4_SYSV_ABI CERT_validateCertificateWithConf();
|
||||
int PS4_SYSV_ABI CERT_VerifyCertificatePolicies();
|
||||
int PS4_SYSV_ABI CERT_VerifyCertificatePolicies2();
|
||||
int PS4_SYSV_ABI CERT_verifySignature();
|
||||
int PS4_SYSV_ABI CERT_VerifyValidityTime();
|
||||
int PS4_SYSV_ABI CERT_VerifyValidityTime2();
|
||||
int PS4_SYSV_ABI CERT_VerifyValidityTimeWithConf();
|
||||
int PS4_SYSV_ABI CRYPTO_initAsymmetricKey();
|
||||
int PS4_SYSV_ABI CRYPTO_uninitAsymmetricKey();
|
||||
int PS4_SYSV_ABI GC_createInstanceIDs();
|
||||
int PS4_SYSV_ABI getCertSigAlgo();
|
||||
int PS4_SYSV_ABI MOCANA_freeMocana();
|
||||
int PS4_SYSV_ABI MOCANA_initMocana();
|
||||
int PS4_SYSV_ABI RSA_verifySignature();
|
||||
int PS4_SYSV_ABI sceSslCheckRecvPending();
|
||||
int PS4_SYSV_ABI sceSslClose();
|
||||
int PS4_SYSV_ABI sceSslConnect();
|
||||
int PS4_SYSV_ABI sceSslCreateSslConnection();
|
||||
int PS4_SYSV_ABI sceSslDeleteSslConnection();
|
||||
int PS4_SYSV_ABI sceSslDisableOption();
|
||||
int PS4_SYSV_ABI sceSslDisableOptionInternal();
|
||||
int PS4_SYSV_ABI sceSslDisableOptionInternalInsecure();
|
||||
int PS4_SYSV_ABI sceSslEnableOption();
|
||||
int PS4_SYSV_ABI sceSslEnableOptionInternal();
|
||||
int PS4_SYSV_ABI sceSslFreeCaCerts();
|
||||
int PS4_SYSV_ABI sceSslFreeCaList();
|
||||
int PS4_SYSV_ABI sceSslFreeSslCertName();
|
||||
int PS4_SYSV_ABI sceSslGetCaCerts(int sslCtxId, OrbisSslCaCerts* certs);
|
||||
int PS4_SYSV_ABI sceSslGetCaList();
|
||||
int PS4_SYSV_ABI sceSslGetIssuerName();
|
||||
int PS4_SYSV_ABI sceSslGetMemoryPoolStats();
|
||||
int PS4_SYSV_ABI sceSslGetNameEntryCount();
|
||||
int PS4_SYSV_ABI sceSslGetNameEntryInfo();
|
||||
int PS4_SYSV_ABI sceSslGetNanoSSLModuleId();
|
||||
int PS4_SYSV_ABI sceSslGetNotAfter();
|
||||
int PS4_SYSV_ABI sceSslGetNotBefore();
|
||||
int PS4_SYSV_ABI sceSslGetSerialNumber();
|
||||
int PS4_SYSV_ABI sceSslGetSslError();
|
||||
int PS4_SYSV_ABI sceSslGetSubjectName();
|
||||
int PS4_SYSV_ABI sceSslInit(std::size_t poolSize);
|
||||
int PS4_SYSV_ABI sceSslLoadCert();
|
||||
int PS4_SYSV_ABI sceSslLoadRootCACert();
|
||||
int PS4_SYSV_ABI sceSslRecv();
|
||||
int PS4_SYSV_ABI sceSslReuseConnection();
|
||||
int PS4_SYSV_ABI sceSslSend();
|
||||
int PS4_SYSV_ABI sceSslSetMinSslVersion();
|
||||
int PS4_SYSV_ABI sceSslSetSslVersion();
|
||||
int PS4_SYSV_ABI sceSslSetVerifyCallback();
|
||||
int PS4_SYSV_ABI sceSslShowMemoryStat();
|
||||
int PS4_SYSV_ABI sceSslTerm();
|
||||
int PS4_SYSV_ABI sceSslUnloadCert();
|
||||
int PS4_SYSV_ABI SSL_acceptConnection();
|
||||
int PS4_SYSV_ABI SSL_acceptConnectionCommon();
|
||||
int PS4_SYSV_ABI SSL_assignCertificateStore();
|
||||
int PS4_SYSV_ABI SSL_ASYNC_acceptConnection();
|
||||
int PS4_SYSV_ABI SSL_ASYNC_closeConnection();
|
||||
int PS4_SYSV_ABI SSL_ASYNC_connect();
|
||||
int PS4_SYSV_ABI SSL_ASYNC_connectCommon();
|
||||
int PS4_SYSV_ABI SSL_ASYNC_getRecvBuffer();
|
||||
int PS4_SYSV_ABI SSL_ASYNC_getSendBuffer();
|
||||
int PS4_SYSV_ABI SSL_ASYNC_init();
|
||||
int PS4_SYSV_ABI SSL_ASYNC_initServer();
|
||||
int PS4_SYSV_ABI SSL_ASYNC_recvMessage();
|
||||
int PS4_SYSV_ABI SSL_ASYNC_recvMessage2();
|
||||
int PS4_SYSV_ABI SSL_ASYNC_sendMessage();
|
||||
int PS4_SYSV_ABI SSL_ASYNC_sendMessagePending();
|
||||
int PS4_SYSV_ABI SSL_ASYNC_start();
|
||||
int PS4_SYSV_ABI SSL_closeConnection();
|
||||
int PS4_SYSV_ABI SSL_connect();
|
||||
int PS4_SYSV_ABI SSL_connectWithCfgParam();
|
||||
int PS4_SYSV_ABI SSL_enableCiphers();
|
||||
int PS4_SYSV_ABI SSL_findConnectionInstance();
|
||||
int PS4_SYSV_ABI SSL_getCipherInfo();
|
||||
int PS4_SYSV_ABI SSL_getClientRandom();
|
||||
int PS4_SYSV_ABI SSL_getClientSessionInfo();
|
||||
int PS4_SYSV_ABI SSL_getCookie();
|
||||
int PS4_SYSV_ABI SSL_getNextSessionId();
|
||||
int PS4_SYSV_ABI SSL_getServerRandom();
|
||||
int PS4_SYSV_ABI SSL_getSessionCache();
|
||||
int PS4_SYSV_ABI SSL_getSessionFlags();
|
||||
int PS4_SYSV_ABI SSL_getSessionInfo();
|
||||
int PS4_SYSV_ABI SSL_getSessionStatus();
|
||||
int PS4_SYSV_ABI SSL_getSocketId();
|
||||
int PS4_SYSV_ABI SSL_getSSLTLSVersion();
|
||||
int PS4_SYSV_ABI SSL_init();
|
||||
int PS4_SYSV_ABI SSL_initiateRehandshake();
|
||||
int PS4_SYSV_ABI SSL_initServerCert();
|
||||
int PS4_SYSV_ABI SSL_ioctl();
|
||||
int PS4_SYSV_ABI SSL_isSessionSSL();
|
||||
int PS4_SYSV_ABI SSL_lockSessionCacheMutex();
|
||||
int PS4_SYSV_ABI SSL_lookupAlert();
|
||||
int PS4_SYSV_ABI SSL_negotiateConnection();
|
||||
int PS4_SYSV_ABI SSL_recv();
|
||||
int PS4_SYSV_ABI SSL_recvPending();
|
||||
int PS4_SYSV_ABI SSL_releaseTables();
|
||||
int PS4_SYSV_ABI SSL_retrieveServerNameList();
|
||||
int PS4_SYSV_ABI SSL_rngFun();
|
||||
int PS4_SYSV_ABI SSL_send();
|
||||
int PS4_SYSV_ABI SSL_sendAlert();
|
||||
int PS4_SYSV_ABI SSL_sendPending();
|
||||
int PS4_SYSV_ABI SSL_setCookie();
|
||||
int PS4_SYSV_ABI SSL_setServerCert();
|
||||
int PS4_SYSV_ABI SSL_setServerNameList();
|
||||
int PS4_SYSV_ABI SSL_setSessionFlags();
|
||||
int PS4_SYSV_ABI SSL_shutdown();
|
||||
int PS4_SYSV_ABI SSL_sslSettings();
|
||||
int PS4_SYSV_ABI SSL_validateCertParam();
|
||||
int PS4_SYSV_ABI VLONG_freeVlongQueue();
|
||||
int PS4_SYSV_ABI Func_22E76E60BC0587D7();
|
||||
int PS4_SYSV_ABI Func_28F8791A771D39C7();
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::Ssl
|
||||
@@ -0,0 +1,375 @@
|
||||
// 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/libs.h"
|
||||
#include "core/libraries/network/ssl2.h"
|
||||
#include "core/libraries/network/ssl2_error.h"
|
||||
|
||||
namespace Libraries::Ssl2 {
|
||||
|
||||
int PS4_SYSV_ABI CA_MGMT_extractKeyBlobEx() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI CA_MGMT_extractPublicKeyInfo() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI CA_MGMT_freeKeyBlob() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI CRYPTO_initAsymmetricKey() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI CRYPTO_uninitAsymmetricKey() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI RSA_verifySignature() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslCheckRecvPending() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslClose() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslConnect() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslCreateConnection() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslCreateSslConnection() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslDeleteConnection() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslDeleteSslConnection() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslDisableOption() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslDisableOptionInternal() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslDisableOptionInternalInsecure() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslDisableVerifyOption() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslEnableOption() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslEnableOptionInternal() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslEnableVerifyOption() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslFreeCaCerts(s32 ssl_ctx_id, OrbisSslCaCerts* certs) {
|
||||
LOG_ERROR(Lib_Ssl2, "(DUMMY) called");
|
||||
if (certs == nullptr) {
|
||||
return ORBIS_SSL_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (certs->certs != nullptr) {
|
||||
for (s32 data = 0; data < certs->num; data++) {
|
||||
free(certs->certs[data].ptr);
|
||||
}
|
||||
delete (certs->certs);
|
||||
}
|
||||
|
||||
// delete (certs->pool);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslFreeCaList() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslFreeSslCertName() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslGetAlpnSelected() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslGetCaCerts(s32 ssl_ctx_id, OrbisSslCaCerts* certs) {
|
||||
LOG_ERROR(Lib_Ssl2, "(DUMMY) called");
|
||||
if (certs == nullptr) {
|
||||
return ORBIS_SSL_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
// Allocate a buffer to store dummy data in.
|
||||
const char* dummy_data = "dummy";
|
||||
u64 dummy_length = strlen(dummy_data) + 1;
|
||||
char* data = static_cast<char*>(malloc(dummy_length));
|
||||
strncpy(data, dummy_data, dummy_length);
|
||||
certs->certs = new OrbisSslData{data, dummy_length};
|
||||
certs->num = 1;
|
||||
certs->pool = nullptr;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslGetCaList() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslGetFingerprint() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslGetIssuerName() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslGetMemoryPoolStats() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslGetNameEntryCount() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslGetNameEntryInfo() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslGetNanoSSLModuleId() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslGetNotAfter() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslGetNotBefore() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslGetPeerCert() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslGetPem() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslGetSerialNumber() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslGetSslError() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslGetSubjectName() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslInit(std::size_t poolSize) {
|
||||
LOG_ERROR(Lib_Ssl2, "(DUMMY) called poolSize = {}", poolSize);
|
||||
// return a value >1
|
||||
static int id = 0;
|
||||
return ++id;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslLoadCert() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslLoadRootCACert() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslRead() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslRecv() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslReuseConnection() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslSend() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslSetAlpn() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslSetMinSslVersion() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslSetSslVersion() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslSetVerifyCallback() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslTerm() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslUnloadCert() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSslWrite() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI VLONG_freeVlongQueue() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI Func_22E76E60BC0587D7() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI Func_28F8791A771D39C7() {
|
||||
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("Md+HYkCBZB4", "libSceSsl", 1, "libSceSsl", CA_MGMT_extractKeyBlobEx);
|
||||
LIB_FUNCTION("9bKYzKP6kYU", "libSceSsl", 1, "libSceSsl", CA_MGMT_extractPublicKeyInfo);
|
||||
LIB_FUNCTION("ipLIammTj2Q", "libSceSsl", 1, "libSceSsl", CA_MGMT_freeKeyBlob);
|
||||
LIB_FUNCTION("PRWr3-ytpdg", "libSceSsl", 1, "libSceSsl", CRYPTO_initAsymmetricKey);
|
||||
LIB_FUNCTION("cW7VCIMCh9A", "libSceSsl", 1, "libSceSsl", CRYPTO_uninitAsymmetricKey);
|
||||
LIB_FUNCTION("pBwtarKd7eg", "libSceSsl", 1, "libSceSsl", RSA_verifySignature);
|
||||
LIB_FUNCTION("1VM0h1JrUfA", "libSceSsl", 1, "libSceSsl", sceSslCheckRecvPending);
|
||||
LIB_FUNCTION("viRXSHZYd0c", "libSceSsl", 1, "libSceSsl", sceSslClose);
|
||||
LIB_FUNCTION("zXvd6iNyfgc", "libSceSsl", 1, "libSceSsl", sceSslConnect);
|
||||
LIB_FUNCTION("tuscfitnhEo", "libSceSsl", 1, "libSceSsl", sceSslCreateConnection);
|
||||
LIB_FUNCTION("P14ATpXc4J8", "libSceSsl", 1, "libSceSsl", sceSslCreateSslConnection);
|
||||
LIB_FUNCTION("HJ1n138CQ2g", "libSceSsl", 1, "libSceSsl", sceSslDeleteConnection);
|
||||
LIB_FUNCTION("hwrHV6Pprk4", "libSceSsl", 1, "libSceSsl", sceSslDeleteSslConnection);
|
||||
LIB_FUNCTION("iLKz4+ukLqk", "libSceSsl", 1, "libSceSsl", sceSslDisableOption);
|
||||
LIB_FUNCTION("-WqxBRAUVM4", "libSceSsl", 1, "libSceSsl", sceSslDisableOptionInternal);
|
||||
LIB_FUNCTION("w1+L-27nYas", "libSceSsl", 1, "libSceSsl", sceSslDisableOptionInternalInsecure);
|
||||
LIB_FUNCTION("PwsHbErG+e8", "libSceSsl", 1, "libSceSsl", sceSslDisableVerifyOption);
|
||||
LIB_FUNCTION("m-zPyAsIpco", "libSceSsl", 1, "libSceSsl", sceSslEnableOption);
|
||||
LIB_FUNCTION("g-zCwUKstEQ", "libSceSsl", 1, "libSceSsl", sceSslEnableOptionInternal);
|
||||
LIB_FUNCTION("po1X86mgHDU", "libSceSsl", 1, "libSceSsl", sceSslEnableVerifyOption);
|
||||
LIB_FUNCTION("qIvLs0gYxi0", "libSceSsl", 1, "libSceSsl", sceSslFreeCaCerts);
|
||||
LIB_FUNCTION("+DzXseDVkeI", "libSceSsl", 1, "libSceSsl", sceSslFreeCaList);
|
||||
LIB_FUNCTION("RwXD8grHZHM", "libSceSsl", 1, "libSceSsl", sceSslFreeSslCertName);
|
||||
LIB_FUNCTION("4O7+bRkRUe8", "libSceSsl", 1, "libSceSsl", sceSslGetAlpnSelected);
|
||||
LIB_FUNCTION("TDfQqO-gMbY", "libSceSsl", 1, "libSceSsl", sceSslGetCaCerts);
|
||||
LIB_FUNCTION("qOn+wm28wmA", "libSceSsl", 1, "libSceSsl", sceSslGetCaList);
|
||||
LIB_FUNCTION("brRtwGBu4A8", "libSceSsl", 1, "libSceSsl", sceSslGetFingerprint);
|
||||
LIB_FUNCTION("7whYpYfHP74", "libSceSsl", 1, "libSceSsl", sceSslGetIssuerName);
|
||||
LIB_FUNCTION("-PoIzr3PEk0", "libSceSsl", 1, "libSceSsl", sceSslGetMemoryPoolStats);
|
||||
LIB_FUNCTION("R1ePzopYPYM", "libSceSsl", 1, "libSceSsl", sceSslGetNameEntryCount);
|
||||
LIB_FUNCTION("7RBSTKGrmDA", "libSceSsl", 1, "libSceSsl", sceSslGetNameEntryInfo);
|
||||
LIB_FUNCTION("AzUipl-DpIw", "libSceSsl", 1, "libSceSsl", sceSslGetNanoSSLModuleId);
|
||||
LIB_FUNCTION("xHpt6+2pGYk", "libSceSsl", 1, "libSceSsl", sceSslGetNotAfter);
|
||||
LIB_FUNCTION("Eo0S65Jy28Q", "libSceSsl", 1, "libSceSsl", sceSslGetNotBefore);
|
||||
LIB_FUNCTION("-TbZc8pwPNc", "libSceSsl", 1, "libSceSsl", sceSslGetPeerCert);
|
||||
LIB_FUNCTION("kLB5aGoUJXg", "libSceSsl", 1, "libSceSsl", sceSslGetPem);
|
||||
LIB_FUNCTION("DOwXL+FQMEY", "libSceSsl", 1, "libSceSsl", sceSslGetSerialNumber);
|
||||
LIB_FUNCTION("0XcZknp7-Wc", "libSceSsl", 1, "libSceSsl", sceSslGetSslError);
|
||||
LIB_FUNCTION("dQReuBX9sD8", "libSceSsl", 1, "libSceSsl", sceSslGetSubjectName);
|
||||
LIB_FUNCTION("hdpVEUDFW3s", "libSceSsl", 1, "libSceSsl", sceSslInit);
|
||||
LIB_FUNCTION("Ab7+DH+gYyM", "libSceSsl", 1, "libSceSsl", sceSslLoadCert);
|
||||
LIB_FUNCTION("3-643mGVFJo", "libSceSsl", 1, "libSceSsl", sceSslLoadRootCACert);
|
||||
LIB_FUNCTION("jltWpVKtetg", "libSceSsl", 1, "libSceSsl", sceSslRead);
|
||||
LIB_FUNCTION("hi0veU3L2pU", "libSceSsl", 1, "libSceSsl", sceSslRecv);
|
||||
LIB_FUNCTION("50R2xYaYZwE", "libSceSsl", 1, "libSceSsl", sceSslReuseConnection);
|
||||
LIB_FUNCTION("p5bM5PPufFY", "libSceSsl", 1, "libSceSsl", sceSslSend);
|
||||
LIB_FUNCTION("TL86glUrmUw", "libSceSsl", 1, "libSceSsl", sceSslSetAlpn);
|
||||
LIB_FUNCTION("QWSxBzf6lAg", "libSceSsl", 1, "libSceSsl", sceSslSetMinSslVersion);
|
||||
LIB_FUNCTION("bKaEtQnoUuQ", "libSceSsl", 1, "libSceSsl", sceSslSetSslVersion);
|
||||
LIB_FUNCTION("E4a-ahM57QQ", "libSceSsl", 1, "libSceSsl", sceSslSetVerifyCallback);
|
||||
LIB_FUNCTION("0K1yQ6Lv-Yc", "libSceSsl", 1, "libSceSsl", sceSslTerm);
|
||||
LIB_FUNCTION("UQ+3Qu7v3cA", "libSceSsl", 1, "libSceSsl", sceSslUnloadCert);
|
||||
LIB_FUNCTION("iNjkt9Poblw", "libSceSsl", 1, "libSceSsl", sceSslWrite);
|
||||
LIB_FUNCTION("wcVuyTUr5ys", "libSceSsl", 1, "libSceSsl", VLONG_freeVlongQueue);
|
||||
LIB_FUNCTION("IuduYLwFh9c", "libSceSsl", 1, "libSceSsl", Func_22E76E60BC0587D7);
|
||||
LIB_FUNCTION("KPh5GncdOcc", "libSceSsl", 1, "libSceSsl", Func_28F8791A771D39C7);
|
||||
};
|
||||
|
||||
} // namespace Libraries::Ssl2
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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::Ssl2 {
|
||||
|
||||
struct OrbisSslData {
|
||||
char* ptr;
|
||||
u64 size;
|
||||
};
|
||||
|
||||
struct OrbisSslCaCerts {
|
||||
OrbisSslData* certs;
|
||||
u64 num;
|
||||
void* pool;
|
||||
};
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::Ssl2
|
||||
@@ -0,0 +1,8 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/libraries/error_codes.h"
|
||||
|
||||
constexpr int ORBIS_SSL_ERROR_INVALID_ARGUMENT = 0x8095F007;
|
||||
@@ -0,0 +1,440 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <common/assert.h>
|
||||
#include <common/logging/log.h>
|
||||
#include <core/libraries/kernel/kernel.h>
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
#include "common/error.h"
|
||||
#include "common/singleton.h"
|
||||
#include "core/file_sys/fs.h"
|
||||
#include "net_error.h"
|
||||
#include "sockets.h"
|
||||
#include "sys_net.h"
|
||||
|
||||
namespace Libraries::Net {
|
||||
|
||||
using FDTable = Common::Singleton<Core::FileSys::HandleTable>;
|
||||
|
||||
int PS4_SYSV_ABI sys_connect(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen) {
|
||||
auto file = FDTable::Instance()->GetSocket(s);
|
||||
if (!file) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
|
||||
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
|
||||
return -1;
|
||||
}
|
||||
LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name);
|
||||
int returncode = file->socket->Connect(addr, addrlen);
|
||||
if (returncode >= 0) {
|
||||
return returncode;
|
||||
}
|
||||
u32 error = *Libraries::Kernel::__Error();
|
||||
// Don't log EINPROGRESS or EISCONN, these are normal to see from non-blocking communication.
|
||||
if (error != ORBIS_NET_EINPROGRESS && error != ORBIS_NET_EISCONN) {
|
||||
LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name, error);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_bind(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen) {
|
||||
auto file = FDTable::Instance()->GetSocket(s);
|
||||
if (!file) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
|
||||
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
|
||||
return -1;
|
||||
}
|
||||
LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name);
|
||||
int returncode = file->socket->Bind(addr, addrlen);
|
||||
if (returncode >= 0) {
|
||||
return returncode;
|
||||
}
|
||||
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_accept(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) {
|
||||
auto file = FDTable::Instance()->GetSocket(s);
|
||||
if (!file) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
|
||||
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
|
||||
return -1;
|
||||
}
|
||||
LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name);
|
||||
auto new_sock = file->socket->Accept(addr, paddrlen);
|
||||
if (!new_sock) {
|
||||
u32 error = *Libraries::Kernel::__Error();
|
||||
// Don't log EWOULDBLOCK, this is normal to see from non-blocking communication.
|
||||
if (error != ORBIS_NET_EWOULDBLOCK) {
|
||||
LOG_ERROR(Lib_Net,
|
||||
"s = {} ({}) returned error code creating new socket for accepting: {}", s,
|
||||
file->m_guest_name, error);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
auto fd = FDTable::Instance()->CreateHandle();
|
||||
auto* new_file = FDTable::Instance()->GetFile(fd);
|
||||
new_file->is_opened = true;
|
||||
new_file->type = Core::FileSys::FileType::Socket;
|
||||
new_file->socket = new_sock;
|
||||
return fd;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_getpeername(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) {
|
||||
auto file = FDTable::Instance()->GetSocket(s);
|
||||
if (!file) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
|
||||
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
|
||||
return -1;
|
||||
}
|
||||
LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name);
|
||||
int returncode = file->socket->GetPeerName(addr, paddrlen);
|
||||
if (returncode >= 0) {
|
||||
return returncode;
|
||||
}
|
||||
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_getsockname(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) {
|
||||
auto file = FDTable::Instance()->GetSocket(s);
|
||||
if (!file) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
|
||||
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
|
||||
return -1;
|
||||
}
|
||||
LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name);
|
||||
int returncode = file->socket->GetSocketAddress(addr, paddrlen);
|
||||
if (returncode >= 0) {
|
||||
return returncode;
|
||||
}
|
||||
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_getsockopt(OrbisNetId s, int level, int optname, void* optval, u32* optlen) {
|
||||
auto file = FDTable::Instance()->GetSocket(s);
|
||||
if (!file) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
|
||||
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
|
||||
return -1;
|
||||
}
|
||||
auto name = [&] -> std::string_view {
|
||||
switch (level) {
|
||||
case ORBIS_NET_SOL_SOCKET:
|
||||
return NameOf((OrbisNetSocketSoOption)optname);
|
||||
case ORBIS_NET_IPPROTO_IP:
|
||||
return magic_enum::enum_name((OrbisNetSocketIpOption)optname);
|
||||
case ORBIS_NET_IPPROTO_TCP:
|
||||
return magic_enum::enum_name((OrbisNetSocketTcpOption)optname);
|
||||
default:
|
||||
return std::string_view{"unknown"};
|
||||
}
|
||||
}();
|
||||
LOG_DEBUG(Lib_Net, "s = {} ({}), level = {}, optname = {}", s, file->m_guest_name,
|
||||
NameOf((OrbisNetProtocol)level), name);
|
||||
int returncode = file->socket->GetSocketOptions(level, optname, optval, optlen);
|
||||
if (returncode >= 0) {
|
||||
if (optname == ORBIS_NET_SO_ERROR) {
|
||||
LOG_DEBUG(Lib_Net, "so_error = {}", *reinterpret_cast<s32*>(optval));
|
||||
}
|
||||
return returncode;
|
||||
}
|
||||
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_listen(OrbisNetId s, int backlog) {
|
||||
auto file = FDTable::Instance()->GetSocket(s);
|
||||
if (!file) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
|
||||
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
|
||||
return -1;
|
||||
}
|
||||
LOG_DEBUG(Lib_Net, "s = {} ({}), backlog = {}", s, file->m_guest_name, backlog);
|
||||
int returncode = file->socket->Listen(backlog);
|
||||
if (returncode >= 0) {
|
||||
return returncode;
|
||||
}
|
||||
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_setsockopt(OrbisNetId s, int level, int optname, const void* optval,
|
||||
u32 optlen) {
|
||||
auto file = FDTable::Instance()->GetSocket(s);
|
||||
if (!file) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
|
||||
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
|
||||
return -1;
|
||||
}
|
||||
auto name = [&] -> std::string_view {
|
||||
switch (level) {
|
||||
case ORBIS_NET_SOL_SOCKET:
|
||||
return NameOf((OrbisNetSocketSoOption)optname);
|
||||
case ORBIS_NET_IPPROTO_IP:
|
||||
return magic_enum::enum_name((OrbisNetSocketIpOption)optname);
|
||||
case ORBIS_NET_IPPROTO_TCP:
|
||||
return magic_enum::enum_name((OrbisNetSocketTcpOption)optname);
|
||||
default:
|
||||
return std::string_view{"unknown"};
|
||||
}
|
||||
}();
|
||||
LOG_DEBUG(Lib_Net, "s = {} ({}), level = {}, optname = {}", s, file->m_guest_name,
|
||||
NameOf((OrbisNetProtocol)level), name);
|
||||
int returncode = file->socket->SetSocketOptions(level, optname, optval, optlen);
|
||||
if (returncode >= 0) {
|
||||
return returncode;
|
||||
}
|
||||
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_shutdown(OrbisNetId s, int how) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_socketex(const char* name, int family, int type, int protocol) {
|
||||
auto sname = name ? name : "anon";
|
||||
LOG_INFO(Lib_Net, "name = {} family = {} type = {} protocol = {}", std::string(sname),
|
||||
magic_enum::enum_name((OrbisNetFamily)family),
|
||||
magic_enum::enum_name((OrbisNetSocketType)type), protocol);
|
||||
SocketPtr socket;
|
||||
switch (type) {
|
||||
case ORBIS_NET_SOCK_STREAM:
|
||||
case ORBIS_NET_SOCK_DGRAM:
|
||||
if (family == ORBIS_NET_AF_UNIX) {
|
||||
socket = std::make_shared<UnixSocket>(family, type, protocol);
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
case ORBIS_NET_SOCK_RAW:
|
||||
socket = std::make_shared<PosixSocket>(family, type, protocol);
|
||||
break;
|
||||
case ORBIS_NET_SOCK_DGRAM_P2P:
|
||||
case ORBIS_NET_SOCK_STREAM_P2P:
|
||||
socket = std::make_shared<P2PSocket>(family, type, protocol);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Unknown type {}", type);
|
||||
}
|
||||
if (!socket->IsValid()) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EPROTONOSUPPORT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto fd = FDTable::Instance()->CreateHandle();
|
||||
auto* sock = FDTable::Instance()->GetFile(fd);
|
||||
sock->is_opened = true;
|
||||
sock->type = Core::FileSys::FileType::Socket;
|
||||
sock->socket = socket;
|
||||
sock->m_guest_name = sname;
|
||||
return fd;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_socket(int family, int type, int protocol) {
|
||||
return sys_socketex(nullptr, family, type, protocol);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
int socketpair(int family, int type, int protocol, net_socket fd[2]) {
|
||||
if (family != AF_UNIX && family != AF_INET) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EPROTONOSUPPORT;
|
||||
return -1;
|
||||
}
|
||||
if (type != SOCK_STREAM) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EPROTONOSUPPORT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
SOCKET listener = INVALID_SOCKET;
|
||||
SOCKET sock1 = INVALID_SOCKET;
|
||||
SOCKET sock2 = INVALID_SOCKET;
|
||||
sockaddr_in addr{};
|
||||
int addrlen = sizeof(addr);
|
||||
|
||||
listener = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (listener == INVALID_SOCKET)
|
||||
goto fail1;
|
||||
|
||||
ZeroMemory(&addr, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
addr.sin_port = 0;
|
||||
|
||||
if (bind(listener, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR)
|
||||
goto fail1;
|
||||
if (listen(listener, 1) == SOCKET_ERROR)
|
||||
goto fail1;
|
||||
|
||||
if (getsockname(listener, (sockaddr*)&addr, &addrlen) == SOCKET_ERROR)
|
||||
goto fail1;
|
||||
|
||||
sock1 = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sock1 == INVALID_SOCKET)
|
||||
goto fail2;
|
||||
|
||||
if (connect(sock1, (sockaddr*)&addr, addrlen) == SOCKET_ERROR)
|
||||
goto fail2;
|
||||
|
||||
sock2 = accept(listener, nullptr, nullptr);
|
||||
if (sock2 == INVALID_SOCKET)
|
||||
goto fail3;
|
||||
|
||||
closesocket(listener);
|
||||
fd[0] = sock1;
|
||||
fd[1] = sock2;
|
||||
return 0;
|
||||
|
||||
fail3:
|
||||
closesocket(sock2);
|
||||
fail2:
|
||||
closesocket(sock1);
|
||||
fail1:
|
||||
closesocket(listener);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
int PS4_SYSV_ABI sys_socketpair(int family, int type, int protocol, int sv[2]) {
|
||||
if (sv == nullptr) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (family != ORBIS_NET_AF_UNIX) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EPROTONOSUPPORT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
net_socket fd[2];
|
||||
if (socketpair(family, type, protocol, fd) != 0) {
|
||||
LOG_ERROR(Lib_Net, "socketpair failed with {}", Common::GetLastErrorMsg());
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto fd1 = FDTable::Instance()->CreateHandle();
|
||||
auto fd2 = FDTable::Instance()->CreateHandle();
|
||||
auto* sock = FDTable::Instance()->GetFile(fd1);
|
||||
sock->is_opened = true;
|
||||
sock->type = Core::FileSys::FileType::Socket;
|
||||
sock->socket = std::make_shared<UnixSocket>(fd[0]);
|
||||
sock->m_guest_name = "anon_sock0";
|
||||
sock = FDTable::Instance()->GetFile(fd2);
|
||||
sock->is_opened = true;
|
||||
sock->type = Core::FileSys::FileType::Socket;
|
||||
sock->socket = std::make_shared<UnixSocket>(fd[1]);
|
||||
sock->m_guest_name = "anon_sock1";
|
||||
sv[0] = fd1;
|
||||
sv[1] = fd2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_netabort(OrbisNetId s, int flags) {
|
||||
LOG_ERROR(Lib_Net, "(STUBBED) called");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_socketclose(OrbisNetId s) {
|
||||
auto file = FDTable::Instance()->GetSocket(s);
|
||||
if (!file) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
|
||||
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
|
||||
return -1;
|
||||
}
|
||||
LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name);
|
||||
int returncode = file->socket->Close();
|
||||
if (returncode >= 0) {
|
||||
FDTable::Instance()->DeleteHandle(s);
|
||||
return returncode;
|
||||
}
|
||||
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_send(OrbisNetId s, const void* buf, u64 len, int flags) {
|
||||
return sys_sendto(s, buf, len, flags, nullptr, 0);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_sendto(OrbisNetId s, const void* buf, u64 len, int flags,
|
||||
const OrbisNetSockaddr* addr, u32 addrlen) {
|
||||
auto file = FDTable::Instance()->GetSocket(s);
|
||||
if (!file) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
|
||||
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
|
||||
return -1;
|
||||
}
|
||||
LOG_DEBUG(Lib_Net, "s = {} ({}), len = {}, flags = {:#x}", s, file->m_guest_name, len, flags);
|
||||
int returncode = file->socket->SendPacket(buf, len, flags, addr, addrlen);
|
||||
if (returncode >= 0) {
|
||||
return returncode;
|
||||
}
|
||||
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_sendmsg(OrbisNetId s, const OrbisNetMsghdr* msg, int flags) {
|
||||
auto file = FDTable::Instance()->GetSocket(s);
|
||||
if (!file) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
|
||||
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
|
||||
return -1;
|
||||
}
|
||||
LOG_DEBUG(Lib_Net, "s = {} ({}), flags = {:#x}", s, file->m_guest_name, flags);
|
||||
int returncode = file->socket->SendMessage(msg, flags);
|
||||
if (returncode >= 0) {
|
||||
return returncode;
|
||||
}
|
||||
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
|
||||
return -1;
|
||||
}
|
||||
|
||||
s64 PS4_SYSV_ABI sys_recv(OrbisNetId s, void* buf, u64 len, int flags) {
|
||||
return sys_recvfrom(s, buf, len, flags, nullptr, nullptr);
|
||||
}
|
||||
|
||||
s64 PS4_SYSV_ABI sys_recvfrom(OrbisNetId s, void* buf, u64 len, int flags, OrbisNetSockaddr* addr,
|
||||
u32* paddrlen) {
|
||||
auto file = FDTable::Instance()->GetSocket(s);
|
||||
if (!file) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
|
||||
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
|
||||
return -1;
|
||||
}
|
||||
LOG_DEBUG(Lib_Net, "s = {} ({}), len = {}, flags = {:#x}", s, file->m_guest_name, len, flags);
|
||||
int returncode = file->socket->ReceivePacket(buf, len, flags, addr, paddrlen);
|
||||
if (returncode >= 0) {
|
||||
return returncode;
|
||||
}
|
||||
// Don't log EWOULDBLOCK, this is normal to see from non-blocking communication.
|
||||
u32 error = *Libraries::Kernel::__Error();
|
||||
if (error != ORBIS_NET_EWOULDBLOCK) {
|
||||
LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name, error);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
s64 PS4_SYSV_ABI sys_recvmsg(OrbisNetId s, OrbisNetMsghdr* msg, int flags) {
|
||||
auto file = FDTable::Instance()->GetSocket(s);
|
||||
if (!file) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
|
||||
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
|
||||
return -1;
|
||||
}
|
||||
LOG_DEBUG(Lib_Net, "s = {} ({}), flags = {:#x}", s, file->m_guest_name, flags);
|
||||
int returncode = file->socket->ReceiveMessage(msg, flags);
|
||||
if (returncode >= 0) {
|
||||
return returncode;
|
||||
}
|
||||
LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name,
|
||||
(u32)*Libraries::Kernel::__Error());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_htons(u16 v) {
|
||||
return ((v << 8) & 0xff00) | ((v >> 8) & 0x00ff);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_htonl(u32 v) {
|
||||
return (v << 24) | ((v & 0xff00) << 8) | ((v >> 8) & 0xff00) | (v >> 24);
|
||||
}
|
||||
|
||||
} // namespace Libraries::Net
|
||||
@@ -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"
|
||||
#include "net.h"
|
||||
|
||||
namespace Libraries::Net {
|
||||
|
||||
int PS4_SYSV_ABI sys_connect(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen);
|
||||
int PS4_SYSV_ABI sys_bind(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen);
|
||||
int PS4_SYSV_ABI sys_accept(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen);
|
||||
int PS4_SYSV_ABI sys_getpeername(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen);
|
||||
int PS4_SYSV_ABI sys_getsockname(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen);
|
||||
int PS4_SYSV_ABI sys_getsockopt(OrbisNetId s, int level, int optname, void* optval, u32* optlen);
|
||||
int PS4_SYSV_ABI sys_listen(OrbisNetId s, int backlog);
|
||||
int PS4_SYSV_ABI sys_setsockopt(OrbisNetId s, int level, int optname, const void* optval,
|
||||
u32 optlen);
|
||||
int PS4_SYSV_ABI sys_shutdown(OrbisNetId s, int how);
|
||||
int PS4_SYSV_ABI sys_socketex(const char* name, int family, int type, int protocol);
|
||||
int PS4_SYSV_ABI sys_socket(int family, int type, int protocol);
|
||||
int PS4_SYSV_ABI sys_socketpair(int family, int type, int protocol, int sv[2]);
|
||||
int PS4_SYSV_ABI sys_netabort(OrbisNetId s, int flags);
|
||||
int PS4_SYSV_ABI sys_socketclose(OrbisNetId s);
|
||||
int PS4_SYSV_ABI sys_send(OrbisNetId s, const void* buf, u64 len, int flags);
|
||||
int PS4_SYSV_ABI sys_sendto(OrbisNetId s, const void* buf, u64 len, int flags,
|
||||
const OrbisNetSockaddr* addr, u32 addrlen);
|
||||
int PS4_SYSV_ABI sys_sendmsg(OrbisNetId s, const OrbisNetMsghdr* msg, int flags);
|
||||
s64 PS4_SYSV_ABI sys_recv(OrbisNetId s, void* buf, u64 len, int flags);
|
||||
s64 PS4_SYSV_ABI sys_recvfrom(OrbisNetId s, void* buf, u64 len, int flags, OrbisNetSockaddr* addr,
|
||||
u32* paddrlen);
|
||||
s64 PS4_SYSV_ABI sys_recvmsg(OrbisNetId s, OrbisNetMsghdr* msg, int flags);
|
||||
int PS4_SYSV_ABI sys_htons(u16 v);
|
||||
int PS4_SYSV_ABI sys_htonl(u32 v);
|
||||
|
||||
} // namespace Libraries::Net
|
||||
@@ -0,0 +1,393 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <common/assert.h>
|
||||
#include "common/error.h"
|
||||
#include "core/libraries/kernel/file_system.h"
|
||||
#include "core/libraries/kernel/kernel.h"
|
||||
#include "net.h"
|
||||
#ifndef _WIN32
|
||||
#include <sys/stat.h>
|
||||
#include <sys/un.h>
|
||||
#endif
|
||||
#include "net_error.h"
|
||||
#include "sockets.h"
|
||||
|
||||
namespace Libraries::Net {
|
||||
|
||||
#ifdef _WIN32
|
||||
#define ERROR_CASE(errname) \
|
||||
case (WSA##errname): \
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_##errname; \
|
||||
return -1;
|
||||
#else
|
||||
#define ERROR_CASE(errname) \
|
||||
case (errname): \
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_##errname; \
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
static int ConvertReturnErrorCode(int retval) {
|
||||
if (retval < 0) {
|
||||
#ifdef _WIN32
|
||||
switch (WSAGetLastError()) {
|
||||
#else
|
||||
switch (errno) {
|
||||
#endif
|
||||
#ifndef _WIN32 // These errorcodes don't exist in WinSock
|
||||
ERROR_CASE(EPERM)
|
||||
ERROR_CASE(ENOENT)
|
||||
// ERROR_CASE(ESRCH)
|
||||
// ERROR_CASE(EIO)
|
||||
// ERROR_CASE(ENXIO)
|
||||
// ERROR_CASE(E2BIG)
|
||||
// ERROR_CASE(ENOEXEC)
|
||||
// ERROR_CASE(EDEADLK)
|
||||
ERROR_CASE(ENOMEM)
|
||||
// ERROR_CASE(ECHILD)
|
||||
// ERROR_CASE(EBUSY)
|
||||
ERROR_CASE(EEXIST)
|
||||
// ERROR_CASE(EXDEV)
|
||||
ERROR_CASE(ENODEV)
|
||||
// ERROR_CASE(ENOTDIR)
|
||||
// ERROR_CASE(EISDIR)
|
||||
ERROR_CASE(ENFILE)
|
||||
// ERROR_CASE(ENOTTY)
|
||||
// ERROR_CASE(ETXTBSY)
|
||||
// ERROR_CASE(EFBIG)
|
||||
ERROR_CASE(ENOSPC)
|
||||
// ERROR_CASE(ESPIPE)
|
||||
// ERROR_CASE(EROFS)
|
||||
// ERROR_CASE(EMLINK)
|
||||
ERROR_CASE(EPIPE)
|
||||
// ERROR_CASE(EDOM)
|
||||
// ERROR_CASE(ERANGE)
|
||||
// ERROR_CASE(ENOLCK)
|
||||
// ERROR_CASE(ENOSYS)
|
||||
// ERROR_CASE(EIDRM)
|
||||
// ERROR_CASE(EOVERFLOW)
|
||||
// ERROR_CASE(EILSEQ)
|
||||
// ERROR_CASE(ENOTSUP)
|
||||
ERROR_CASE(ECANCELED)
|
||||
// ERROR_CASE(EBADMSG)
|
||||
ERROR_CASE(ENODATA)
|
||||
// ERROR_CASE(ENOSR)
|
||||
// ERROR_CASE(ENOSTR)
|
||||
// ERROR_CASE(ETIME)
|
||||
#endif
|
||||
ERROR_CASE(EINTR)
|
||||
ERROR_CASE(EBADF)
|
||||
ERROR_CASE(EACCES)
|
||||
ERROR_CASE(EFAULT)
|
||||
ERROR_CASE(EINVAL)
|
||||
ERROR_CASE(EMFILE)
|
||||
ERROR_CASE(EWOULDBLOCK)
|
||||
ERROR_CASE(EINPROGRESS)
|
||||
ERROR_CASE(EALREADY)
|
||||
ERROR_CASE(ENOTSOCK)
|
||||
ERROR_CASE(EDESTADDRREQ)
|
||||
ERROR_CASE(EMSGSIZE)
|
||||
ERROR_CASE(EPROTOTYPE)
|
||||
ERROR_CASE(ENOPROTOOPT)
|
||||
ERROR_CASE(EPROTONOSUPPORT)
|
||||
#if defined(__APPLE__) || defined(_WIN32)
|
||||
ERROR_CASE(EOPNOTSUPP)
|
||||
#endif
|
||||
ERROR_CASE(EAFNOSUPPORT)
|
||||
ERROR_CASE(EADDRINUSE)
|
||||
ERROR_CASE(EADDRNOTAVAIL)
|
||||
ERROR_CASE(ENETDOWN)
|
||||
ERROR_CASE(ENETUNREACH)
|
||||
ERROR_CASE(ENETRESET)
|
||||
ERROR_CASE(ECONNABORTED)
|
||||
ERROR_CASE(ECONNRESET)
|
||||
ERROR_CASE(ENOBUFS)
|
||||
ERROR_CASE(EISCONN)
|
||||
ERROR_CASE(ENOTCONN)
|
||||
ERROR_CASE(ETIMEDOUT)
|
||||
ERROR_CASE(ECONNREFUSED)
|
||||
ERROR_CASE(ELOOP)
|
||||
ERROR_CASE(ENAMETOOLONG)
|
||||
ERROR_CASE(EHOSTUNREACH)
|
||||
ERROR_CASE(ENOTEMPTY)
|
||||
}
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EINTERNAL;
|
||||
return -1;
|
||||
}
|
||||
// if it is 0 or positive return it as it is
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int ConvertLevels(int level) {
|
||||
switch (level) {
|
||||
case ORBIS_NET_SOL_SOCKET:
|
||||
return SOL_SOCKET;
|
||||
case ORBIS_NET_IPPROTO_IP:
|
||||
return IPPROTO_IP;
|
||||
case ORBIS_NET_IPPROTO_TCP:
|
||||
return IPPROTO_TCP;
|
||||
case ORBIS_NET_IPPROTO_IPV6:
|
||||
return IPPROTO_IPV6;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void convertOrbisNetSockaddrToUnix(const OrbisNetSockaddr* src, sockaddr_un* dst) {
|
||||
if (src == nullptr || dst == nullptr)
|
||||
return;
|
||||
memset(dst, 0, sizeof(sockaddr_un));
|
||||
const OrbisNetSockaddrUn* src_in = (const OrbisNetSockaddrUn*)src;
|
||||
sockaddr_un* dst_in = (sockaddr_un*)dst;
|
||||
dst_in->sun_family = src_in->sun_family;
|
||||
memcpy(&dst_in->sun_path, &src_in->sun_path, src_in->sun_len);
|
||||
}
|
||||
|
||||
static void convertUnixSockaddrToOrbis(sockaddr* src, OrbisNetSockaddr* dst) {
|
||||
if (src == nullptr || dst == nullptr)
|
||||
return;
|
||||
memset(dst, 0, sizeof(OrbisNetSockaddrUn));
|
||||
OrbisNetSockaddrUn* dst_in = (OrbisNetSockaddrUn*)dst;
|
||||
sockaddr_un* src_in = (sockaddr_un*)src;
|
||||
dst_in->sun_len = strnlen(src_in->sun_path, 108);
|
||||
dst_in->sun_family = src_in->sun_family;
|
||||
memcpy(&dst_in->sun_path, &src_in->sun_path, dst_in->sun_len);
|
||||
}
|
||||
|
||||
bool UnixSocket::IsValid() const {
|
||||
#ifdef _WIN32
|
||||
return sock != INVALID_SOCKET;
|
||||
#else
|
||||
return sock != -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
int UnixSocket::Close() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
#ifdef _WIN32
|
||||
auto out = closesocket(sock);
|
||||
#else
|
||||
auto out = ::close(sock);
|
||||
#endif
|
||||
return ConvertReturnErrorCode(out);
|
||||
}
|
||||
|
||||
int UnixSocket::Bind(const OrbisNetSockaddr* addr, u32 addrlen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
sockaddr_un addr2;
|
||||
convertOrbisNetSockaddrToUnix(addr, &addr2);
|
||||
return ConvertReturnErrorCode(::bind(sock, (const sockaddr*)&addr2, sizeof(sockaddr_un)));
|
||||
}
|
||||
|
||||
int UnixSocket::Listen(int backlog) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
return ConvertReturnErrorCode(::listen(sock, backlog));
|
||||
}
|
||||
|
||||
int UnixSocket::SendMessage(const OrbisNetMsghdr* msg, int flags) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
#ifdef _WIN32
|
||||
DWORD bytesSent = 0;
|
||||
LPFN_WSASENDMSG wsasendmsg = nullptr;
|
||||
GUID guid = WSAID_WSASENDMSG;
|
||||
DWORD bytes = 0;
|
||||
|
||||
if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &wsasendmsg,
|
||||
sizeof(wsasendmsg), &bytes, nullptr, nullptr) != 0) {
|
||||
return ConvertReturnErrorCode(-1);
|
||||
}
|
||||
|
||||
int res = wsasendmsg(sock, reinterpret_cast<LPWSAMSG>(const_cast<OrbisNetMsghdr*>(msg)), flags,
|
||||
&bytesSent, nullptr, nullptr);
|
||||
|
||||
if (res == SOCKET_ERROR) {
|
||||
return ConvertReturnErrorCode(-1);
|
||||
}
|
||||
return static_cast<int>(bytesSent);
|
||||
#else
|
||||
int res = sendmsg(sock, reinterpret_cast<const msghdr*>(msg), flags);
|
||||
return ConvertReturnErrorCode(res);
|
||||
#endif
|
||||
}
|
||||
|
||||
int UnixSocket::SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
|
||||
u32 tolen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
LOG_ERROR(Lib_Net, "called");
|
||||
if (to != nullptr) {
|
||||
sockaddr_un addr;
|
||||
convertOrbisNetSockaddrToUnix(to, &addr);
|
||||
return ConvertReturnErrorCode(
|
||||
sendto(sock, (const char*)msg, len, flags, (sockaddr*)&addr, sizeof(sockaddr_un)));
|
||||
} else {
|
||||
return ConvertReturnErrorCode(send(sock, (const char*)msg, len, flags));
|
||||
}
|
||||
}
|
||||
|
||||
int UnixSocket::ReceiveMessage(OrbisNetMsghdr* msg, int flags) {
|
||||
std::scoped_lock lock{receive_mutex};
|
||||
#ifdef _WIN32
|
||||
LPFN_WSARECVMSG wsarecvmsg = nullptr;
|
||||
GUID guid = WSAID_WSARECVMSG;
|
||||
DWORD bytes = 0;
|
||||
|
||||
if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &wsarecvmsg,
|
||||
sizeof(wsarecvmsg), &bytes, nullptr, nullptr) != 0) {
|
||||
return ConvertReturnErrorCode(-1);
|
||||
}
|
||||
|
||||
DWORD bytesReceived = 0;
|
||||
int res = wsarecvmsg(sock, reinterpret_cast<LPWSAMSG>(msg), &bytesReceived, nullptr, nullptr);
|
||||
|
||||
if (res == SOCKET_ERROR) {
|
||||
return ConvertReturnErrorCode(-1);
|
||||
}
|
||||
return static_cast<int>(bytesReceived);
|
||||
#else
|
||||
int res = recvmsg(sock, reinterpret_cast<msghdr*>(msg), flags);
|
||||
return ConvertReturnErrorCode(res);
|
||||
#endif
|
||||
}
|
||||
|
||||
int UnixSocket::ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, u32* fromlen) {
|
||||
std::scoped_lock lock{receive_mutex};
|
||||
LOG_ERROR(Lib_Net, "called");
|
||||
if (from != nullptr) {
|
||||
sockaddr_un addr;
|
||||
int res = recvfrom(sock, (char*)buf, len, flags, (sockaddr*)&addr, (socklen_t*)fromlen);
|
||||
convertUnixSockaddrToOrbis((sockaddr*)&addr, from);
|
||||
*fromlen = sizeof(OrbisNetSockaddrUn);
|
||||
return ConvertReturnErrorCode(res);
|
||||
} else {
|
||||
return ConvertReturnErrorCode(recv(sock, (char*)buf, len, flags));
|
||||
}
|
||||
}
|
||||
|
||||
SocketPtr UnixSocket::Accept(OrbisNetSockaddr* addr, u32* addrlen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
sockaddr_un addr2;
|
||||
socklen_t len = sizeof(addr2);
|
||||
net_socket new_socket = ::accept(sock, (sockaddr*)&addr2, &len);
|
||||
#ifdef _WIN32
|
||||
if (new_socket != INVALID_SOCKET) {
|
||||
#else
|
||||
if (new_socket >= 0) {
|
||||
#endif
|
||||
if (addr && addrlen) {
|
||||
convertUnixSockaddrToOrbis((sockaddr*)&addr2, addr);
|
||||
*addrlen = sizeof(OrbisNetSockaddrUn);
|
||||
}
|
||||
return std::make_shared<UnixSocket>(new_socket);
|
||||
}
|
||||
ConvertReturnErrorCode(new_socket);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int UnixSocket::Connect(const OrbisNetSockaddr* addr, u32 namelen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
sockaddr_un addr2;
|
||||
convertOrbisNetSockaddrToUnix(addr, &addr2);
|
||||
int result = 0;
|
||||
do {
|
||||
result = ::connect(sock, (sockaddr*)&addr2, sizeof(sockaddr_un));
|
||||
LOG_DEBUG(Lib_Net, "raw connect result = {}, errno = {}", result,
|
||||
result == -1 ? Common::GetLastErrorMsg() : "none");
|
||||
} while (result == -1 && (errno == EINTR || errno == EINPROGRESS));
|
||||
return ConvertReturnErrorCode(result);
|
||||
}
|
||||
|
||||
int UnixSocket::GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
sockaddr_un addr;
|
||||
convertOrbisNetSockaddrToUnix(name, &addr);
|
||||
if (name != nullptr) {
|
||||
*namelen = sizeof(sockaddr_un);
|
||||
}
|
||||
int res = getsockname(sock, (sockaddr*)&addr, (socklen_t*)namelen);
|
||||
if (res >= 0) {
|
||||
convertUnixSockaddrToOrbis((sockaddr*)&addr, name);
|
||||
*namelen = sizeof(OrbisNetSockaddrUn);
|
||||
}
|
||||
return ConvertReturnErrorCode(res);
|
||||
}
|
||||
|
||||
#define CASE_SETSOCKOPT(opt) \
|
||||
case ORBIS_NET_##opt: \
|
||||
return ConvertReturnErrorCode( \
|
||||
setsockopt(sock, native_level, opt, (const char*)optval, optlen))
|
||||
|
||||
#define CASE_SETSOCKOPT_VALUE(opt, value) \
|
||||
case opt: \
|
||||
if (optlen != sizeof(*value)) { \
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EFAULT; \
|
||||
return -1; \
|
||||
} \
|
||||
memcpy(value, optval, optlen); \
|
||||
return 0
|
||||
|
||||
int UnixSocket::SetSocketOptions(int level, int optname, const void* optval, u32 optlen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
LOG_ERROR(Lib_Net, "Unknown level = {} optname = {}", level, optname);
|
||||
return -1;
|
||||
}
|
||||
|
||||
#define CASE_GETSOCKOPT(opt) \
|
||||
case ORBIS_NET_##opt: { \
|
||||
socklen_t optlen_temp = *optlen; \
|
||||
auto retval = ConvertReturnErrorCode( \
|
||||
getsockopt(sock, native_level, opt, (char*)optval, &optlen_temp)); \
|
||||
*optlen = optlen_temp; \
|
||||
return retval; \
|
||||
}
|
||||
#define CASE_GETSOCKOPT_VALUE(opt, value) \
|
||||
case opt: \
|
||||
if (*optlen < sizeof(value)) { \
|
||||
*optlen = sizeof(value); \
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EFAULT; \
|
||||
return -1; \
|
||||
} \
|
||||
*optlen = sizeof(value); \
|
||||
*(decltype(value)*)optval = value; \
|
||||
return 0;
|
||||
|
||||
int UnixSocket::GetSocketOptions(int level, int optname, void* optval, u32* optlen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
LOG_ERROR(Lib_Net, "Unknown level = {} optname = {}", level, optname);
|
||||
return ConvertReturnErrorCode(-1);
|
||||
}
|
||||
|
||||
int UnixSocket::GetPeerName(OrbisNetSockaddr* name, u32* namelen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
LOG_DEBUG(Lib_Net, "called");
|
||||
|
||||
sockaddr_un addr;
|
||||
convertOrbisNetSockaddrToUnix(name, &addr);
|
||||
if (name != nullptr) {
|
||||
*namelen = sizeof(sockaddr_un);
|
||||
}
|
||||
int res = ::getpeername(sock, (sockaddr*)&addr, (socklen_t*)namelen);
|
||||
if (res >= 0) {
|
||||
convertUnixSockaddrToOrbis((sockaddr*)&addr, name);
|
||||
*namelen = sizeof(OrbisNetSockaddrUn);
|
||||
}
|
||||
return ConvertReturnErrorCode(res);
|
||||
}
|
||||
|
||||
int UnixSocket::fstat(Libraries::Kernel::OrbisKernelStat* sb) {
|
||||
#ifdef _WIN32
|
||||
LOG_ERROR(Lib_Net, "(STUBBED) called");
|
||||
sb->st_mode = 0000777u | 0140000u;
|
||||
return 0;
|
||||
#else
|
||||
struct stat st{};
|
||||
int result = ::fstat(sock, &st);
|
||||
sb->st_mode = 0000777u | 0140000u;
|
||||
sb->st_size = st.st_size;
|
||||
sb->st_blocks = st.st_blocks;
|
||||
sb->st_blksize = st.st_blksize;
|
||||
// sb->st_flags = st.st_flags;
|
||||
return ConvertReturnErrorCode(result);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Libraries::Net
|
||||
@@ -0,0 +1,388 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <mutex>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/np/np_auth.h"
|
||||
#include "core/libraries/np/np_error.h"
|
||||
#include "core/libraries/system/userservice.h"
|
||||
|
||||
namespace Libraries::Np::NpAuth {
|
||||
|
||||
static bool g_shadnet_enabled = false;
|
||||
static s32 g_active_auth_requests = 0;
|
||||
static std::mutex g_auth_request_mutex;
|
||||
|
||||
// Internal types for storing request-related information
|
||||
enum class NpAuthRequestState {
|
||||
None = 0,
|
||||
Ready = 1,
|
||||
Aborted = 2,
|
||||
Complete = 3,
|
||||
};
|
||||
|
||||
struct NpAuthRequest {
|
||||
NpAuthRequestState state;
|
||||
bool async;
|
||||
s32 result;
|
||||
};
|
||||
|
||||
static std::vector<NpAuthRequest> g_auth_requests;
|
||||
|
||||
s32 CreateNpAuthRequest(bool async) {
|
||||
if (g_active_auth_requests == ORBIS_NP_AUTH_REQUEST_LIMIT) {
|
||||
return ORBIS_NP_AUTH_ERROR_REQUEST_MAX;
|
||||
}
|
||||
|
||||
std::scoped_lock lk{g_auth_request_mutex};
|
||||
|
||||
s32 req_index = 0;
|
||||
while (req_index < g_auth_requests.size()) {
|
||||
// Find first nonexistant request
|
||||
if (g_auth_requests[req_index].state == NpAuthRequestState::None) {
|
||||
// There is no request at this index, set the index to ready then break.
|
||||
g_auth_requests[req_index].state = NpAuthRequestState::Ready;
|
||||
g_auth_requests[req_index].async = async;
|
||||
break;
|
||||
}
|
||||
req_index++;
|
||||
}
|
||||
|
||||
if (req_index == g_auth_requests.size()) {
|
||||
// There are no requests to replace.
|
||||
NpAuthRequest new_request{NpAuthRequestState::Ready, async, 0};
|
||||
g_auth_requests.emplace_back(new_request);
|
||||
}
|
||||
|
||||
// Offset by one, first returned ID is 0x10000001
|
||||
g_active_auth_requests++;
|
||||
LOG_DEBUG(Lib_NpAuth, "called, async = {}", async);
|
||||
return req_index + ORBIS_NP_AUTH_REQUEST_ID_OFFSET + 1;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpAuthCreateRequest() {
|
||||
return CreateNpAuthRequest(false);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpAuthCreateAsyncRequest(const OrbisNpAuthCreateAsyncRequestParameter* param) {
|
||||
if (param == nullptr) {
|
||||
return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (param->size != sizeof(OrbisNpAuthCreateAsyncRequestParameter)) {
|
||||
return ORBIS_NP_AUTH_ERROR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
return CreateNpAuthRequest(true);
|
||||
}
|
||||
|
||||
s32 GetAuthorizationCode(s32 req_id, const OrbisNpAuthGetAuthorizationCodeParameterA* param,
|
||||
s32 flag, OrbisNpAuthorizationCode* auth_code, s32* issuer_id) {
|
||||
if (param == nullptr || auth_code == nullptr) {
|
||||
return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (param->size != sizeof(OrbisNpAuthGetAuthorizationCodeParameter)) {
|
||||
return ORBIS_NP_AUTH_ERROR_INVALID_SIZE;
|
||||
}
|
||||
if (param->user_id == -1 || param->client_id == nullptr || param->scope == nullptr) {
|
||||
return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
std::scoped_lock lk{g_auth_request_mutex};
|
||||
|
||||
// From here the actual authorization code request is performed.
|
||||
s32 req_index = req_id - ORBIS_NP_AUTH_REQUEST_ID_OFFSET - 1;
|
||||
if (g_active_auth_requests == 0 || g_auth_requests.size() <= req_index ||
|
||||
g_auth_requests[req_index].state == NpAuthRequestState::None) {
|
||||
return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND;
|
||||
}
|
||||
|
||||
auto& request = g_auth_requests[req_index];
|
||||
if (request.state == NpAuthRequestState::Complete) {
|
||||
request.result = ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
|
||||
return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
|
||||
} else if (request.state == NpAuthRequestState::Aborted) {
|
||||
request.result = ORBIS_NP_AUTH_ERROR_ABORTED;
|
||||
return ORBIS_NP_AUTH_ERROR_ABORTED;
|
||||
}
|
||||
|
||||
request.state = NpAuthRequestState::Complete;
|
||||
if (!g_shadnet_enabled) {
|
||||
request.result = ORBIS_NP_ERROR_SIGNED_OUT;
|
||||
// If the request is processed in some form, and it's an async request, then it returns OK.
|
||||
if (request.async) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
return ORBIS_NP_ERROR_SIGNED_OUT;
|
||||
}
|
||||
|
||||
LOG_ERROR(Lib_NpAuth, "(STUBBED) called, req_id = {:#x}, async = {}", req_id, request.async);
|
||||
|
||||
// Not sure what values are expected here, so zeroing these for now.
|
||||
std::memset(auth_code, 0, sizeof(OrbisNpAuthorizationCode));
|
||||
std::strncpy(auth_code->code, "AUTHCODE", 9);
|
||||
if (issuer_id != nullptr) {
|
||||
*issuer_id = 100;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI
|
||||
sceNpAuthGetAuthorizationCode(s32 req_id, const OrbisNpAuthGetAuthorizationCodeParameter* param,
|
||||
OrbisNpAuthorizationCode* auth_code, s32* issuer_id) {
|
||||
if (param == nullptr || auth_code == nullptr) {
|
||||
return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (param->size != sizeof(OrbisNpAuthGetAuthorizationCodeParameter)) {
|
||||
return ORBIS_NP_AUTH_ERROR_INVALID_SIZE;
|
||||
}
|
||||
if (param->online_id == nullptr || param->client_id == nullptr || param->scope == nullptr) {
|
||||
return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (!g_shadnet_enabled) {
|
||||
// Calls sceNpManagerIntGetUserIdByOnlineId to get a user id, returning any errors.
|
||||
// This call will not succeed while signed out because games cannot retrieve an online id.
|
||||
return ORBIS_NP_ERROR_USER_NOT_FOUND;
|
||||
}
|
||||
|
||||
// For simplicity, call sceUserServiceGetInitialUser to get the user_id.
|
||||
s32 user_id = 0;
|
||||
ASSERT(UserService::sceUserServiceGetInitialUser(&user_id) == 0);
|
||||
|
||||
// Library constructs an OrbisNpAuthGetAuthorizationCodeParameterA struct,
|
||||
// then uses that to call GetAuthorizationCode.
|
||||
OrbisNpAuthGetAuthorizationCodeParameterA internal_params;
|
||||
std::memset(&internal_params, 0, sizeof(internal_params));
|
||||
internal_params.size = sizeof(internal_params);
|
||||
internal_params.client_id = param->client_id;
|
||||
internal_params.user_id = user_id;
|
||||
internal_params.scope = param->scope;
|
||||
|
||||
return GetAuthorizationCode(req_id, &internal_params, 0, auth_code, issuer_id);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI
|
||||
sceNpAuthGetAuthorizationCodeA(s32 req_id, const OrbisNpAuthGetAuthorizationCodeParameterA* param,
|
||||
OrbisNpAuthorizationCode* auth_code, s32* issuer_id) {
|
||||
return GetAuthorizationCode(req_id, param, 0, auth_code, issuer_id);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI
|
||||
sceNpAuthGetAuthorizationCodeV3(s32 req_id, const OrbisNpAuthGetAuthorizationCodeParameterA* param,
|
||||
OrbisNpAuthorizationCode* auth_code, s32* issuer_id) {
|
||||
return GetAuthorizationCode(req_id, param, 1, auth_code, issuer_id);
|
||||
}
|
||||
|
||||
s32 GetIdToken(s32 req_id, const OrbisNpAuthGetIdTokenParameterA* param, s32 flag,
|
||||
OrbisNpIdToken* token) {
|
||||
if (param == nullptr || token == nullptr) {
|
||||
return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (param->size != sizeof(OrbisNpAuthGetIdTokenParameterA)) {
|
||||
return ORBIS_NP_AUTH_ERROR_INVALID_SIZE;
|
||||
}
|
||||
if (param->user_id == -1 || param->client_id == nullptr || param->client_secret == nullptr ||
|
||||
param->scope == nullptr) {
|
||||
return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
std::scoped_lock lk{g_auth_request_mutex};
|
||||
|
||||
// From here the actual authorization code request is performed.
|
||||
s32 req_index = req_id - ORBIS_NP_AUTH_REQUEST_ID_OFFSET - 1;
|
||||
if (g_active_auth_requests == 0 || g_auth_requests.size() <= req_index ||
|
||||
g_auth_requests[req_index].state == NpAuthRequestState::None) {
|
||||
return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND;
|
||||
}
|
||||
|
||||
auto& request = g_auth_requests[req_index];
|
||||
if (request.state == NpAuthRequestState::Complete) {
|
||||
request.result = ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
|
||||
return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
|
||||
} else if (request.state == NpAuthRequestState::Aborted) {
|
||||
request.result = ORBIS_NP_AUTH_ERROR_ABORTED;
|
||||
return ORBIS_NP_AUTH_ERROR_ABORTED;
|
||||
}
|
||||
|
||||
request.state = NpAuthRequestState::Complete;
|
||||
if (!g_shadnet_enabled) {
|
||||
request.result = ORBIS_NP_ERROR_SIGNED_OUT;
|
||||
// If the request is processed in some form, and it's an async request, then it returns OK.
|
||||
if (request.async) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
return ORBIS_NP_ERROR_SIGNED_OUT;
|
||||
}
|
||||
|
||||
LOG_ERROR(Lib_NpAuth, "(STUBBED) called, req_id = {:#x}, async = {}", req_id, request.async);
|
||||
|
||||
// Not sure what values are expected here, so zeroing this for now.
|
||||
std::memset(token, 0, sizeof(OrbisNpIdToken));
|
||||
std::strncpy(token->token, "TOKEN", 6);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpAuthGetIdToken(s32 req_id, const OrbisNpAuthGetIdTokenParameter* param,
|
||||
OrbisNpIdToken* token) {
|
||||
if (param == nullptr || token == nullptr) {
|
||||
return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (param->size != sizeof(OrbisNpAuthGetIdTokenParameter)) {
|
||||
return ORBIS_NP_AUTH_ERROR_INVALID_SIZE;
|
||||
}
|
||||
if (param->online_id == nullptr || param->client_id == nullptr ||
|
||||
param->client_secret == nullptr || param->scope == nullptr) {
|
||||
return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (!g_shadnet_enabled) {
|
||||
// Calls sceNpManagerIntGetUserIdByOnlineId to get a user id, returning any errors.
|
||||
// This call will not succeed while signed out because games cannot retrieve an online id.
|
||||
return ORBIS_NP_ERROR_USER_NOT_FOUND;
|
||||
}
|
||||
|
||||
// For simplicity, call sceUserServiceGetInitialUser to get the user_id.
|
||||
s32 user_id = 0;
|
||||
ASSERT(UserService::sceUserServiceGetInitialUser(&user_id) == 0);
|
||||
|
||||
// Library constructs an OrbisNpAuthGetIdTokenParameterA struct,
|
||||
// then uses that to call GetIdToken.
|
||||
OrbisNpAuthGetIdTokenParameterA internal_params;
|
||||
std::memset(&internal_params, 0, sizeof(internal_params));
|
||||
internal_params.size = sizeof(internal_params);
|
||||
internal_params.user_id = user_id;
|
||||
internal_params.client_id = param->client_id;
|
||||
internal_params.client_secret = param->client_secret;
|
||||
internal_params.scope = param->scope;
|
||||
|
||||
return GetIdToken(req_id, &internal_params, 0, token);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpAuthGetIdTokenA(s32 req_id, const OrbisNpAuthGetIdTokenParameterA* param,
|
||||
OrbisNpIdToken* token) {
|
||||
return GetIdToken(req_id, param, 0, token);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpAuthGetIdTokenV3(s32 req_id, const OrbisNpAuthGetIdTokenParameterA* param,
|
||||
OrbisNpIdToken* token) {
|
||||
return GetIdToken(req_id, param, 1, token);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpAuthSetTimeout(s32 req_id, s32 resolve_retry, u32 resolve_timeout,
|
||||
u32 conn_timeout, u32 send_timeout, u32 recv_timeout) {
|
||||
LOG_ERROR(Lib_NpAuth, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpAuthAbortRequest(s32 req_id) {
|
||||
LOG_DEBUG(Lib_NpAuth, "called req_id = {:#x}", req_id);
|
||||
|
||||
std::scoped_lock lk{g_auth_request_mutex};
|
||||
|
||||
s32 req_index = req_id - ORBIS_NP_AUTH_REQUEST_ID_OFFSET - 1;
|
||||
if (g_active_auth_requests == 0 || g_auth_requests.size() <= req_index ||
|
||||
g_auth_requests[req_index].state == NpAuthRequestState::None) {
|
||||
return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (g_auth_requests[req_index].state == NpAuthRequestState::Complete) {
|
||||
// If the request is already complete, abort is ignored.
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
g_auth_requests[req_index].state = NpAuthRequestState::Aborted;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpAuthWaitAsync(s32 req_id, s32* result) {
|
||||
if (result == nullptr) {
|
||||
return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
std::scoped_lock lk{g_auth_request_mutex};
|
||||
|
||||
s32 req_index = req_id - ORBIS_NP_AUTH_REQUEST_ID_OFFSET - 1;
|
||||
if (g_active_auth_requests == 0 || g_auth_requests.size() <= req_index ||
|
||||
g_auth_requests[req_index].state == NpAuthRequestState::None) {
|
||||
return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (!g_auth_requests[req_index].async ||
|
||||
g_auth_requests[req_index].state == NpAuthRequestState::Ready) {
|
||||
return ORBIS_NP_AUTH_ERROR_INVALID_ID;
|
||||
}
|
||||
|
||||
// Since we're not actually performing any sort of network request here,
|
||||
// we can just set result based on the request and return.
|
||||
*result = g_auth_requests[req_index].result;
|
||||
LOG_WARNING(Lib_NpAuth, "called req_id = {:#x}, returning result = {:#x}", req_id,
|
||||
static_cast<u32>(*result));
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpAuthPollAsync(s32 req_id, s32* result) {
|
||||
if (result == nullptr) {
|
||||
return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
std::scoped_lock lk{g_auth_request_mutex};
|
||||
|
||||
s32 req_index = req_id - ORBIS_NP_AUTH_REQUEST_ID_OFFSET - 1;
|
||||
if (g_active_auth_requests == 0 || g_auth_requests.size() <= req_index ||
|
||||
g_auth_requests[req_index].state == NpAuthRequestState::None) {
|
||||
return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (!g_auth_requests[req_index].async ||
|
||||
g_auth_requests[req_index].state == NpAuthRequestState::Ready) {
|
||||
return ORBIS_NP_AUTH_ERROR_INVALID_ID;
|
||||
}
|
||||
|
||||
// Since we're not actually performing any sort of network request here,
|
||||
// we can just set result based on the request and return.
|
||||
*result = g_auth_requests[req_index].result;
|
||||
LOG_WARNING(Lib_NpAuth, "called req_id = {:#x}, returning result = {:#x}", req_id,
|
||||
static_cast<u32>(*result));
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpAuthDeleteRequest(s32 req_id) {
|
||||
LOG_DEBUG(Lib_NpAuth, "called req_id = {:#x}", req_id);
|
||||
|
||||
std::scoped_lock lk{g_auth_request_mutex};
|
||||
|
||||
s32 req_index = req_id - ORBIS_NP_AUTH_REQUEST_ID_OFFSET - 1;
|
||||
if (g_active_auth_requests == 0 || g_auth_requests.size() <= req_index ||
|
||||
g_auth_requests[req_index].state == NpAuthRequestState::None) {
|
||||
return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND;
|
||||
}
|
||||
|
||||
g_active_auth_requests--;
|
||||
g_auth_requests[req_index].state = NpAuthRequestState::None;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
g_shadnet_enabled = EmulatorSettings.IsShadNetEnabled();
|
||||
|
||||
LIB_FUNCTION("6bwFkosYRQg", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthCreateRequest);
|
||||
LIB_FUNCTION("N+mr7GjTvr8", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthCreateAsyncRequest);
|
||||
LIB_FUNCTION("KxGkOrQJTqY", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthGetAuthorizationCode);
|
||||
LIB_FUNCTION("qAUXQ9GdWp8", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthGetAuthorizationCodeA);
|
||||
LIB_FUNCTION("KI4dHLlTNl0", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthGetAuthorizationCodeV3);
|
||||
LIB_FUNCTION("uaB-LoJqHis", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthGetIdToken);
|
||||
LIB_FUNCTION("CocbHVIKPE8", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthGetIdTokenA);
|
||||
LIB_FUNCTION("RdsFVsgSpZY", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthGetIdTokenV3);
|
||||
LIB_FUNCTION("PM3IZCw-7m0", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthSetTimeout);
|
||||
LIB_FUNCTION("cE7wIsqXdZ8", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthAbortRequest);
|
||||
LIB_FUNCTION("SK-S7daqJSE", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthWaitAsync);
|
||||
LIB_FUNCTION("gjSyfzSsDcE", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthPollAsync);
|
||||
LIB_FUNCTION("H8wG9Bk-nPc", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthDeleteRequest);
|
||||
|
||||
LIB_FUNCTION("KxGkOrQJTqY", "libSceNpAuthCompat", 1, "libSceNpAuth",
|
||||
sceNpAuthGetAuthorizationCode);
|
||||
LIB_FUNCTION("uaB-LoJqHis", "libSceNpAuthCompat", 1, "libSceNpAuth", sceNpAuthGetIdToken);
|
||||
};
|
||||
|
||||
} // namespace Libraries::Np::NpAuth
|
||||
@@ -0,0 +1,59 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/np/np_types.h"
|
||||
#include "core/libraries/system/userservice.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
namespace Libraries::Np::NpAuth {
|
||||
|
||||
constexpr s32 ORBIS_NP_AUTH_REQUEST_LIMIT = 0x10;
|
||||
constexpr s32 ORBIS_NP_AUTH_REQUEST_ID_OFFSET = 0x10000000;
|
||||
|
||||
struct OrbisNpAuthCreateAsyncRequestParameter {
|
||||
u64 size;
|
||||
u64 cpu_affinity_mask;
|
||||
s32 thread_priority;
|
||||
u8 padding[4];
|
||||
};
|
||||
|
||||
struct OrbisNpAuthGetAuthorizationCodeParameter {
|
||||
u64 size;
|
||||
const OrbisNpOnlineId* online_id;
|
||||
const OrbisNpClientId* client_id;
|
||||
const char* scope;
|
||||
};
|
||||
|
||||
struct OrbisNpAuthGetAuthorizationCodeParameterA {
|
||||
u64 size;
|
||||
Libraries::UserService::OrbisUserServiceUserId user_id;
|
||||
u8 padding[4];
|
||||
const OrbisNpClientId* client_id;
|
||||
const char* scope;
|
||||
};
|
||||
|
||||
struct OrbisNpAuthGetIdTokenParameter {
|
||||
u64 size;
|
||||
const OrbisNpOnlineId* online_id;
|
||||
const OrbisNpClientId* client_id;
|
||||
const OrbisNpClientSecret* client_secret;
|
||||
const char* scope;
|
||||
};
|
||||
|
||||
struct OrbisNpAuthGetIdTokenParameterA {
|
||||
u64 size;
|
||||
Libraries::UserService::OrbisUserServiceUserId user_id;
|
||||
u8 padding[4];
|
||||
const OrbisNpClientId* client_id;
|
||||
const OrbisNpClientSecret* client_secret;
|
||||
const char* scope;
|
||||
};
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::Np::NpAuth
|
||||
@@ -0,0 +1,192 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/np/np_commerce/np_commerce_ui.h"
|
||||
#include "core/libraries/system/commondialog.h"
|
||||
#include "core/libraries/system/userservice.h"
|
||||
#include "np_commerce.h"
|
||||
|
||||
namespace Libraries::Np::NpCommerce {
|
||||
|
||||
static Status g_dialog_status = Status::NONE;
|
||||
static s32 g_result_code = ORBIS_COMMON_DIALOG_RESULT_USER_CANCELED;
|
||||
static CommerceDialogState g_state;
|
||||
static CommerceDialogUi g_dialog;
|
||||
|
||||
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 sceNpCommerceDialogTerminate() {
|
||||
LOG_INFO(Lib_NpCommerce, "called");
|
||||
if (g_dialog_status == Status::NONE) {
|
||||
return static_cast<s32>(Error::NOT_INITIALIZED);
|
||||
}
|
||||
g_dialog.Finish(CommerceResult::OK);
|
||||
g_dialog_status = Status::NONE;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCommerceDialogClose() {
|
||||
LOG_INFO(Lib_NpCommerce, "called");
|
||||
switch (g_dialog_status) {
|
||||
case Status::NONE:
|
||||
return static_cast<s32>(Error::NOT_INITIALIZED);
|
||||
case Status::INITIALIZED:
|
||||
return static_cast<s32>(Error::NOT_RUNNING);
|
||||
case Status::FINISHED:
|
||||
return static_cast<s32>(Error::ALREADY_CLOSE);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
g_dialog.Finish(CommerceResult::OK);
|
||||
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 sceNpCommerceDialogInitializeInternal() {
|
||||
LOG_INFO(Lib_NpCommerce, "called");
|
||||
return sceNpCommerceDialogInitialize();
|
||||
}
|
||||
|
||||
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 sceNpCommerceDialogGetResult(OrbisNpCommerceDialogResult* result) {
|
||||
if (result == nullptr) {
|
||||
return static_cast<s32>(Error::ARG_NULL);
|
||||
}
|
||||
if (g_dialog_status != Status::FINISHED) {
|
||||
return static_cast<s32>(Error::NOT_FINISHED);
|
||||
}
|
||||
result->result = g_result_code;
|
||||
// authorized is only meaningful in PLUS mode
|
||||
result->authorized = g_state.mode == CommerceMode::PLUS &&
|
||||
g_result_code == ORBIS_NP_COMMERCE_DIALOG_RESULT_PURCHASED;
|
||||
result->userData = g_state.user_data;
|
||||
LOG_INFO(Lib_NpCommerce, "result={} authorized={}", result->result, result->authorized);
|
||||
return g_result_code;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCommerceDialogOpen(const OrbisNpCommerceDialogParam* param) {
|
||||
LOG_INFO(Lib_NpCommerce, "called, param = {}", static_cast<const void*>(param));
|
||||
if (g_dialog_status != Status::INITIALIZED && g_dialog_status != Status::FINISHED) {
|
||||
LOG_WARNING(Lib_NpCommerce, "Open in bad state {}", static_cast<u32>(g_dialog_status));
|
||||
return static_cast<s32>(Error::NOT_INITIALIZED);
|
||||
}
|
||||
if (param == nullptr) {
|
||||
return static_cast<s32>(Error::ARG_NULL);
|
||||
}
|
||||
if (param->mode < ORBIS_NP_COMMERCE_DIALOG_MODE_CATEGORY ||
|
||||
param->mode > ORBIS_NP_COMMERCE_DIALOG_MODE_PLUS) {
|
||||
LOG_ERROR(Lib_NpCommerce, "SceNpCommerceDialog Parameter Error (mode={})", param->mode);
|
||||
g_result_code = static_cast<s32>(Error::PARAM_INVALID);
|
||||
g_dialog_status = Status::FINISHED;
|
||||
return static_cast<s32>(Error::PARAM_INVALID);
|
||||
}
|
||||
|
||||
g_state = CommerceDialogState{};
|
||||
g_state.mode = static_cast<CommerceMode>(param->mode);
|
||||
g_state.user_id = param->userId;
|
||||
g_state.service_label = param->serviceLabel;
|
||||
g_state.user_data = param->userData;
|
||||
// features is only meaningful in PLUS mode
|
||||
g_state.features = g_state.mode == CommerceMode::PLUS ? param->features : 0;
|
||||
|
||||
// Resolve the calling user's name for display
|
||||
char name_buf[Libraries::UserService::ORBIS_USER_SERVICE_MAX_USER_NAME_LENGTH + 1] = {};
|
||||
if (Libraries::UserService::sceUserServiceGetUserName(param->userId, name_buf,
|
||||
sizeof(name_buf)) >= 0) {
|
||||
g_state.username = name_buf;
|
||||
}
|
||||
|
||||
if (param->numTargets > ORBIS_NP_COMMERCE_DIALOG_NUM_TARGETS_MAX) {
|
||||
LOG_WARNING(Lib_NpCommerce, "numTargets {} exceeds max {}, clamping", param->numTargets,
|
||||
ORBIS_NP_COMMERCE_DIALOG_NUM_TARGETS_MAX);
|
||||
}
|
||||
const u32 count = std::min(param->numTargets, ORBIS_NP_COMMERCE_DIALOG_NUM_TARGETS_MAX);
|
||||
if (count > 0 && param->targets != nullptr) {
|
||||
g_state.targets.reserve(count);
|
||||
for (u32 i = 0; i < count; ++i) {
|
||||
const char* label = param->targets[i];
|
||||
if (label != nullptr) {
|
||||
g_state.targets.emplace_back(label, strnlen(label, MAX_LABEL_LEN));
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG_INFO(Lib_NpCommerce,
|
||||
"open: mode={} user_id={} serviceLabel={:#x} numTargets={} features={:#x} first='{}'",
|
||||
param->mode, g_state.user_id, g_state.service_label, param->numTargets,
|
||||
g_state.features, g_state.targets.empty() ? "" : g_state.targets.front());
|
||||
|
||||
g_dialog_status = Status::RUNNING;
|
||||
g_result_code = ORBIS_COMMON_DIALOG_RESULT_USER_CANCELED;
|
||||
g_dialog = CommerceDialogUi(&g_state, &g_dialog_status, &g_result_code);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCommerceHidePsStoreIcon() {
|
||||
LOG_INFO(Lib_NpCommerce, "called");
|
||||
PsStoreIconLayer::Instance().Hide();
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCommerceSetPsStoreIconLayout(s32 layout) {
|
||||
LOG_INFO(Lib_NpCommerce, "called, layout = {}", layout);
|
||||
if (layout < 0 || layout > 2) {
|
||||
LOG_WARNING(Lib_NpCommerce, "invalid layout {}, ignoring", layout);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
PsStoreIconLayer::Instance().SetLayout(static_cast<PsStoreIconLayout>(layout));
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCommerceShowPsStoreIcon(s32 pos) {
|
||||
LOG_INFO(Lib_NpCommerce, "called, pos = {}", pos);
|
||||
const auto p =
|
||||
(pos >= 0 && pos <= 2) ? static_cast<PsStoreIconPos>(pos) : PsStoreIconPos::CENTER;
|
||||
PsStoreIconLayer::Instance().Show(p);
|
||||
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
|
||||
@@ -0,0 +1,61 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 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 {
|
||||
|
||||
using CommonDialog::BaseParam;
|
||||
using CommonDialog::Error;
|
||||
using CommonDialog::Status;
|
||||
|
||||
static constexpr u64 MAX_LABEL_LEN = 128;
|
||||
static constexpr u32 ORBIS_NP_COMMERCE_DIALOG_NUM_TARGETS_MAX = 10;
|
||||
|
||||
struct OrbisNpCommerceDialogParam {
|
||||
BaseParam baseParam;
|
||||
s32 size;
|
||||
Libraries::UserService::OrbisUserServiceUserId userId;
|
||||
s32 mode;
|
||||
u32 serviceLabel;
|
||||
const char* const* targets;
|
||||
u32 numTargets;
|
||||
s32 : 32;
|
||||
u64 features;
|
||||
void* userData;
|
||||
u8 reserved[32];
|
||||
};
|
||||
static_assert(sizeof(OrbisNpCommerceDialogParam) == 0x80);
|
||||
|
||||
struct OrbisNpCommerceDialogResult {
|
||||
s32 result;
|
||||
bool authorized;
|
||||
char : 8;
|
||||
short : 16;
|
||||
void* userData;
|
||||
u8 reserved[32];
|
||||
};
|
||||
static_assert(sizeof(OrbisNpCommerceDialogResult) == 0x30);
|
||||
|
||||
// OrbisNpCommerceDialogResult return codes
|
||||
static constexpr s32 ORBIS_COMMON_DIALOG_RESULT_OK = 0;
|
||||
static constexpr s32 ORBIS_COMMON_DIALOG_RESULT_USER_CANCELED = 1;
|
||||
static constexpr s32 ORBIS_NP_COMMERCE_DIALOG_RESULT_PURCHASED = 2;
|
||||
|
||||
// OrbisNpCommerceDialogMode
|
||||
static constexpr s32 ORBIS_NP_COMMERCE_DIALOG_MODE_CATEGORY = 0; // Browse category mode
|
||||
static constexpr s32 ORBIS_NP_COMMERCE_DIALOG_MODE_PRODUCT = 1; // Browse product mode
|
||||
static constexpr s32 ORBIS_NP_COMMERCE_DIALOG_MODE_PRODUCT_CODE = 2; // promotion code mode
|
||||
static constexpr s32 ORBIS_NP_COMMERCE_DIALOG_MODE_CHECKOUT = 3; // Checkout mode
|
||||
static constexpr s32 ORBIS_NP_COMMERCE_DIALOG_MODE_DOWNLOADLIST = 4; // Download mode
|
||||
static constexpr s32 ORBIS_NP_COMMERCE_DIALOG_MODE_PLUS = 5; // PlayStation Plus mode
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
|
||||
} // namespace Libraries::Np::NpCommerce
|
||||
@@ -0,0 +1,358 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <imgui.h>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/np/np_commerce/np_commerce_ui.h"
|
||||
#include "imgui/imgui_std.h"
|
||||
|
||||
using namespace ImGui;
|
||||
using namespace Libraries::CommonDialog;
|
||||
using namespace Libraries::Np::NpCommerce;
|
||||
|
||||
static constexpr ImVec2 BUTTON_SIZE{160.0f, 30.0f};
|
||||
|
||||
static const char* ModeTitle(CommerceMode m) {
|
||||
switch (m) {
|
||||
case CommerceMode::CATEGORY:
|
||||
return "shadNet Store";
|
||||
case CommerceMode::PRODUCT:
|
||||
return "Product";
|
||||
case CommerceMode::PRODUCT_CODE:
|
||||
return "Redeem Code";
|
||||
case CommerceMode::CHECKOUT:
|
||||
return "Purchase Confirmation";
|
||||
case CommerceMode::DOWNLOADLIST:
|
||||
return "My Downloads";
|
||||
case CommerceMode::PLUS:
|
||||
return "shadNet Plus";
|
||||
default:
|
||||
return "shadNet Store";
|
||||
}
|
||||
}
|
||||
|
||||
static const char* ModeActionLabel(CommerceMode m) {
|
||||
switch (m) {
|
||||
case CommerceMode::CATEGORY:
|
||||
case CommerceMode::PRODUCT:
|
||||
case CommerceMode::CHECKOUT:
|
||||
return "Simulate purchase";
|
||||
case CommerceMode::PRODUCT_CODE:
|
||||
return "Redeem code";
|
||||
case CommerceMode::PLUS:
|
||||
return "Join SN Plus";
|
||||
case CommerceMode::DOWNLOADLIST:
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static const char* ModeEmptyText(CommerceMode m) {
|
||||
switch (m) {
|
||||
case CommerceMode::CATEGORY:
|
||||
return "Top store category.";
|
||||
case CommerceMode::PRODUCT_CODE:
|
||||
return "Enter a promotion code.";
|
||||
case CommerceMode::DOWNLOADLIST:
|
||||
return "All downloadable content.";
|
||||
case CommerceMode::PLUS:
|
||||
return "Join shadNet Plus.";
|
||||
default:
|
||||
return "No product specified.";
|
||||
}
|
||||
}
|
||||
|
||||
static const char* ModeDescription(CommerceMode m) {
|
||||
switch (m) {
|
||||
case CommerceMode::CATEGORY:
|
||||
return "Browsing a store category.";
|
||||
case CommerceMode::PRODUCT:
|
||||
return "Viewing a product.";
|
||||
case CommerceMode::PRODUCT_CODE:
|
||||
return "Redeeming a promotion code.";
|
||||
case CommerceMode::CHECKOUT:
|
||||
return "Confirming a purchase.";
|
||||
case CommerceMode::DOWNLOADLIST:
|
||||
return "Viewing owned downloads.";
|
||||
case CommerceMode::PLUS:
|
||||
return "Joining shadNet Plus.";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
static std::string DescribeFeatures(u64 features) {
|
||||
if (features == 0) {
|
||||
return "none";
|
||||
}
|
||||
std::string out;
|
||||
if (features & 0x1) { // ORBIS_NP_PLUS_FEATURE_REALTIME_MULTIPLAY
|
||||
out += "Realtime Multiplay";
|
||||
}
|
||||
if (out.empty()) {
|
||||
return "unknown";
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Decomposed CHECKOUT/DOWNLOADLIST element "[serviceLabel:]product[-sku]".
|
||||
struct ParsedTarget {
|
||||
std::string service;
|
||||
std::string product;
|
||||
std::string sku;
|
||||
};
|
||||
|
||||
static ParsedTarget ParseTarget(const std::string& t) {
|
||||
ParsedTarget out;
|
||||
std::string s = t;
|
||||
if (const auto colon = s.find(':'); colon != std::string::npos) {
|
||||
out.service = s.substr(0, colon);
|
||||
s = s.substr(colon + 1);
|
||||
}
|
||||
if (const auto dash = s.find('-'); dash != std::string::npos) {
|
||||
out.product = s.substr(0, dash);
|
||||
out.sku = s.substr(dash + 1);
|
||||
} else {
|
||||
out.product = s;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
CommerceDialogUi::CommerceDialogUi(CommerceDialogState* state, Status* status, s32* result)
|
||||
: state(state), status(status), result(result) {
|
||||
if (status && *status == Status::RUNNING) {
|
||||
first_render = true;
|
||||
AddLayer(this);
|
||||
}
|
||||
}
|
||||
|
||||
CommerceDialogUi::~CommerceDialogUi() {
|
||||
Finish(CommerceResult::USER_CANCELED);
|
||||
}
|
||||
|
||||
CommerceDialogUi::CommerceDialogUi(CommerceDialogUi&& other) noexcept
|
||||
: Layer(other), first_render(other.first_render), state(other.state), status(other.status),
|
||||
result(other.result) {
|
||||
other.state = nullptr;
|
||||
other.status = nullptr;
|
||||
other.result = nullptr;
|
||||
}
|
||||
|
||||
CommerceDialogUi& CommerceDialogUi::operator=(CommerceDialogUi&& other) noexcept {
|
||||
if (this != &other) {
|
||||
first_render = other.first_render;
|
||||
state = other.state;
|
||||
status = other.status;
|
||||
result = other.result;
|
||||
other.state = nullptr;
|
||||
other.status = nullptr;
|
||||
other.result = nullptr;
|
||||
if (status && *status == Status::RUNNING) {
|
||||
first_render = true;
|
||||
AddLayer(this);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void CommerceDialogUi::Finish(CommerceResult r) {
|
||||
if (result) {
|
||||
*result = static_cast<s32>(r);
|
||||
}
|
||||
if (status) {
|
||||
*status = Status::FINISHED;
|
||||
}
|
||||
state = nullptr;
|
||||
status = nullptr;
|
||||
result = nullptr;
|
||||
RemoveLayer(this);
|
||||
}
|
||||
|
||||
void CommerceDialogUi::Draw() {
|
||||
if (status == nullptr || *status != Status::RUNNING || state == nullptr) {
|
||||
return;
|
||||
}
|
||||
const auto& io = GetIO();
|
||||
const ImVec2 window_size{std::min(io.DisplaySize.x, 520.0f),
|
||||
std::min(io.DisplaySize.y, 380.0f)};
|
||||
|
||||
CentralizeNextWindow();
|
||||
SetNextWindowSize(window_size);
|
||||
SetNextWindowCollapsed(false);
|
||||
if (first_render || !io.NavActive) {
|
||||
SetNextWindowFocus();
|
||||
}
|
||||
KeepNavHighlight();
|
||||
if (Begin("CommerceDialog##NpCommerceDialog", nullptr,
|
||||
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) {
|
||||
std::string title = ModeTitle(state->mode);
|
||||
if (state->targets.size() == 1 &&
|
||||
(state->mode == CommerceMode::PRODUCT || state->mode == CommerceMode::CATEGORY)) {
|
||||
title += ": " + state->targets.front();
|
||||
}
|
||||
{
|
||||
const float tw = CalcTextSize(title.c_str()).x;
|
||||
SetCursorPosX(std::max(10.0f, (GetWindowSize().x - tw) * 0.5f));
|
||||
TextUnformatted(title.c_str());
|
||||
}
|
||||
Separator();
|
||||
|
||||
TextWrapped("%s", ModeDescription(state->mode));
|
||||
Spacing();
|
||||
|
||||
// Calling user.
|
||||
if (!state->username.empty()) {
|
||||
Text("User: %s (id %d)", state->username.c_str(), state->user_id);
|
||||
} else {
|
||||
Text("User id: %d", state->user_id);
|
||||
}
|
||||
// Service label is meaningful for category/product/checkout/downloadlist.
|
||||
if (state->mode != CommerceMode::PRODUCT_CODE && state->mode != CommerceMode::PLUS) {
|
||||
Text("Service label: %u", state->service_label);
|
||||
}
|
||||
// PS Plus feature only applies in PLUS mode.
|
||||
if (state->mode == CommerceMode::PLUS) {
|
||||
Text("PS Plus feature: %s", DescribeFeatures(state->features).c_str());
|
||||
}
|
||||
|
||||
Spacing();
|
||||
if (state->targets.empty()) {
|
||||
TextWrapped("%s", ModeEmptyText(state->mode));
|
||||
} else {
|
||||
const bool checkout_form =
|
||||
state->mode == CommerceMode::CHECKOUT || state->mode == CommerceMode::DOWNLOADLIST;
|
||||
TextDisabled(state->targets.size() == 1 ? "Target:" : "Targets:");
|
||||
for (const auto& t : state->targets) {
|
||||
if (checkout_form) {
|
||||
const ParsedTarget pt = ParseTarget(t);
|
||||
if (!pt.sku.empty() || !pt.service.empty()) {
|
||||
BulletText("Product %s", pt.product.c_str());
|
||||
if (!pt.sku.empty()) {
|
||||
Indent();
|
||||
Text("SKU: %s", pt.sku.c_str());
|
||||
Unindent();
|
||||
}
|
||||
if (!pt.service.empty()) {
|
||||
Indent();
|
||||
Text("Service: %s", pt.service.c_str());
|
||||
Unindent();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
BulletText("%s", t.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
Spacing();
|
||||
|
||||
const char* action = ModeActionLabel(state->mode);
|
||||
const auto ws = GetWindowSize();
|
||||
const float n = action ? 2.0f : 1.0f;
|
||||
SetCursorPos({ws.x / 2.0f - (BUTTON_SIZE.x * n + 10.0f * (n - 1.0f)) / 2.0f,
|
||||
ws.y - 10.0f - BUTTON_SIZE.y});
|
||||
BeginGroup();
|
||||
if (action) {
|
||||
if (Button(action, BUTTON_SIZE)) {
|
||||
LOG_INFO(Lib_NpCommerce, "CommerceDialog: action '{}' mode={} target='{}'", action,
|
||||
static_cast<s32>(state->mode),
|
||||
state->targets.empty() ? "" : state->targets.front());
|
||||
Finish(CommerceResult::PURCHASED);
|
||||
}
|
||||
SameLine();
|
||||
}
|
||||
if (Button("Close", BUTTON_SIZE)) {
|
||||
Finish(CommerceResult::USER_CANCELED);
|
||||
}
|
||||
// Default focus on Close so a stray confirm press dismisses, never buys.
|
||||
if (first_render) {
|
||||
SetItemCurrentNavFocus();
|
||||
}
|
||||
EndGroup();
|
||||
}
|
||||
End();
|
||||
|
||||
first_render = false;
|
||||
}
|
||||
|
||||
static float LayoutScale(PsStoreIconLayout l) {
|
||||
switch (l) {
|
||||
case PsStoreIconLayout::FIXED_90:
|
||||
return 0.90f;
|
||||
case PsStoreIconLayout::FOLLOW_SAFE_AREA:
|
||||
return 0.95f;
|
||||
case PsStoreIconLayout::DEFAULT:
|
||||
default:
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
PsStoreIconLayer& PsStoreIconLayer::Instance() {
|
||||
static PsStoreIconLayer instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void PsStoreIconLayer::Show(PsStoreIconPos p) {
|
||||
pos.store(p);
|
||||
if (!shown.exchange(true)) {
|
||||
AddLayer(this);
|
||||
}
|
||||
}
|
||||
|
||||
void PsStoreIconLayer::Hide() {
|
||||
if (shown.exchange(false)) {
|
||||
RemoveLayer(this);
|
||||
}
|
||||
}
|
||||
|
||||
void PsStoreIconLayer::SetLayout(PsStoreIconLayout l) {
|
||||
layout.store(l);
|
||||
}
|
||||
|
||||
void PsStoreIconLayer::Draw() {
|
||||
if (!shown.load()) {
|
||||
return;
|
||||
}
|
||||
const auto& io = GetIO();
|
||||
const ImVec2 ds = io.DisplaySize;
|
||||
const float scale = LayoutScale(layout.load());
|
||||
const float inset_x = ds.x * (1.0f - scale) * 0.5f;
|
||||
const float inset_y = ds.y * (1.0f - scale) * 0.5f;
|
||||
|
||||
constexpr ImVec2 icon{52.0f, 52.0f};
|
||||
constexpr float pad = 16.0f;
|
||||
|
||||
float x;
|
||||
switch (pos.load()) {
|
||||
case PsStoreIconPos::LEFT:
|
||||
x = inset_x + pad;
|
||||
break;
|
||||
case PsStoreIconPos::RIGHT:
|
||||
x = ds.x - inset_x - icon.x - pad;
|
||||
break;
|
||||
case PsStoreIconPos::CENTER:
|
||||
default:
|
||||
x = (ds.x - icon.x) * 0.5f;
|
||||
break;
|
||||
}
|
||||
const float y = ds.y - inset_y - icon.y - pad;
|
||||
|
||||
SetNextWindowPos({x, y});
|
||||
SetNextWindowSize(icon);
|
||||
if (Begin("##PsStoreIcon", nullptr,
|
||||
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings |
|
||||
ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoBackground |
|
||||
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) {
|
||||
auto* dl = GetWindowDrawList();
|
||||
const ImVec2 p0 = GetWindowPos();
|
||||
const ImVec2 p1{p0.x + icon.x, p0.y + icon.y};
|
||||
dl->AddRectFilled(p0, p1, IM_COL32(0, 55, 145, 230), 10.0f);
|
||||
dl->AddRect(p0, p1, IM_COL32(255, 255, 255, 210), 10.0f, 0, 2.0f);
|
||||
const char* label = "SN";
|
||||
const ImVec2 ts = CalcTextSize(label);
|
||||
dl->AddText({p0.x + (icon.x - ts.x) * 0.5f, p0.y + (icon.y - ts.y) * 0.5f},
|
||||
IM_COL32(255, 255, 255, 255), label);
|
||||
}
|
||||
End();
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/system/commondialog.h"
|
||||
#include "imgui/imgui_layer.h"
|
||||
|
||||
namespace Libraries::Np::NpCommerce {
|
||||
|
||||
// OrbisNpCommerceDialogMode
|
||||
enum class CommerceMode : s32 {
|
||||
CATEGORY = 0,
|
||||
PRODUCT = 1,
|
||||
PRODUCT_CODE = 2,
|
||||
CHECKOUT = 3,
|
||||
DOWNLOADLIST = 4,
|
||||
PLUS = 5,
|
||||
};
|
||||
|
||||
// OrbisNpCommerceDialogResult result values
|
||||
enum class CommerceResult : s32 {
|
||||
OK = 0,
|
||||
USER_CANCELED = 1,
|
||||
PURCHASED = 2,
|
||||
};
|
||||
|
||||
struct CommerceDialogState {
|
||||
std::vector<std::string> targets; // product / category labels
|
||||
std::string username; // resolved from userId, may be empty
|
||||
CommerceMode mode{CommerceMode::CATEGORY};
|
||||
s32 user_id{-1};
|
||||
u32 service_label{0};
|
||||
u64 features{0}; // ORBIS_NP_PLUS_FEATURE_* (PLUS mode only)
|
||||
void* user_data{nullptr}; // echoed back into the result struct
|
||||
};
|
||||
|
||||
class CommerceDialogUi final : public ImGui::Layer {
|
||||
bool first_render{false};
|
||||
CommerceDialogState* state{};
|
||||
CommonDialog::Status* status{};
|
||||
s32* result{};
|
||||
|
||||
public:
|
||||
explicit CommerceDialogUi(CommerceDialogState* state = nullptr,
|
||||
CommonDialog::Status* status = nullptr, s32* result = nullptr);
|
||||
~CommerceDialogUi() override;
|
||||
CommerceDialogUi(const CommerceDialogUi&) = delete;
|
||||
CommerceDialogUi& operator=(const CommerceDialogUi&) = delete;
|
||||
CommerceDialogUi(CommerceDialogUi&& other) noexcept;
|
||||
CommerceDialogUi& operator=(CommerceDialogUi&& other) noexcept;
|
||||
|
||||
void Finish(CommerceResult r);
|
||||
void Draw() override;
|
||||
};
|
||||
|
||||
// OrbisNpCommercePsStoreIconPos
|
||||
enum class PsStoreIconPos : s32 {
|
||||
CENTER = 0, // screen lower center
|
||||
LEFT = 1, // screen lower left
|
||||
RIGHT = 2, // screen lower right
|
||||
};
|
||||
|
||||
// OrbisNpCommercePsStoreIconLayout
|
||||
enum class PsStoreIconLayout : s32 {
|
||||
DEFAULT = 0, // full screen (100%)
|
||||
FOLLOW_SAFE_AREA = 1, // follow display safe area (90%-100%)
|
||||
FIXED_90 = 2, // fixed 90% of screen
|
||||
};
|
||||
|
||||
class PsStoreIconLayer final : public ImGui::Layer {
|
||||
std::atomic<bool> shown{false};
|
||||
std::atomic<PsStoreIconPos> pos{PsStoreIconPos::CENTER};
|
||||
std::atomic<PsStoreIconLayout> layout{PsStoreIconLayout::DEFAULT};
|
||||
|
||||
public:
|
||||
static PsStoreIconLayer& Instance();
|
||||
|
||||
void Show(PsStoreIconPos p);
|
||||
void Hide();
|
||||
void SetLayout(PsStoreIconLayout l);
|
||||
|
||||
void Draw() override;
|
||||
};
|
||||
|
||||
} // namespace Libraries::Np::NpCommerce
|
||||
@@ -0,0 +1,617 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cctype>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/kernel/kernel.h"
|
||||
#include "core/libraries/kernel/process.h"
|
||||
#include "core/libraries/kernel/threads.h"
|
||||
#include "core/libraries/kernel/threads/pthread.h"
|
||||
#include "core/libraries/kernel/time.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/np/np_common.h"
|
||||
#include "core/libraries/np/np_error.h"
|
||||
#include "core/libraries/np/np_types.h"
|
||||
|
||||
namespace Libraries::Np::NpCommon {
|
||||
|
||||
namespace {
|
||||
static_assert(sizeof(OrbisNpCalloutEntry) == 0x20);
|
||||
static_assert(offsetof(OrbisNpCalloutContext, thread) == 0x00);
|
||||
static_assert(offsetof(OrbisNpCalloutContext, mutex) == 0x08);
|
||||
static_assert(offsetof(OrbisNpCalloutContext, cond) == 0x10);
|
||||
static_assert(offsetof(OrbisNpCalloutContext, active) == 0x18);
|
||||
static_assert(offsetof(OrbisNpCalloutContext, stop_requested) == 0x1c);
|
||||
static_assert(offsetof(OrbisNpCalloutContext, head) == 0x20);
|
||||
static_assert(sizeof(OrbisNpCalloutContext) == 0x28);
|
||||
|
||||
s32 NormalizeNpCommonResult(int rc) {
|
||||
if (rc <= 0) {
|
||||
return rc;
|
||||
}
|
||||
return Libraries::Kernel::ErrnoToSceKernelError(rc);
|
||||
}
|
||||
|
||||
u32 NormalizeNpTryLockResult(int rc) {
|
||||
const s32 sce_rc = NormalizeNpCommonResult(rc);
|
||||
if (sce_rc == ORBIS_KERNEL_ERROR_EBUSY) {
|
||||
return ORBIS_NP_LW_MUTEX_ERROR_BUSY;
|
||||
}
|
||||
return static_cast<u32>(sce_rc);
|
||||
}
|
||||
|
||||
void* PS4_SYSV_ABI NpCalloutThreadMain(void* arg) {
|
||||
auto* ctx = static_cast<OrbisNpCalloutContext*>(arg);
|
||||
const auto mutex = &ctx->mutex;
|
||||
sceNpMutexLock(mutex);
|
||||
|
||||
for (;;) {
|
||||
s64 now_usec = 0;
|
||||
sceNpGetSystemClockUsec(&now_usec);
|
||||
|
||||
auto* entry = ctx->head;
|
||||
u32 wait_usec = 0;
|
||||
|
||||
if (entry == nullptr) {
|
||||
if (ctx->stop_requested > 0) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
const u64 deadline = static_cast<u64>(entry->deadline_usec);
|
||||
const u64 now = static_cast<u64>(now_usec);
|
||||
const u64 remaining = deadline - now;
|
||||
if (deadline < now || remaining == 0) {
|
||||
ctx->head = entry->next;
|
||||
sceNpMutexUnlock(mutex);
|
||||
if (entry->handler != nullptr) {
|
||||
entry->handler(entry->arg);
|
||||
}
|
||||
sceNpMutexLock(mutex);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ctx->stop_requested > 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
wait_usec = static_cast<u32>(remaining > 0xffffffffULL ? 0xffffffffULL : remaining);
|
||||
}
|
||||
|
||||
sceNpCondTimedwait(&ctx->cond, mutex, wait_usec);
|
||||
if (ctx->stop_requested > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sceNpMutexUnlock(mutex);
|
||||
return nullptr;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCmpNpId(OrbisNpId* np_id1, OrbisNpId* np_id2) {
|
||||
if (np_id1 == nullptr || np_id2 == nullptr) {
|
||||
return ORBIS_NP_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (std::strncmp(np_id1->handle.data, np_id2->handle.data, ORBIS_NP_ONLINEID_MAX_LENGTH) != 0) {
|
||||
return ORBIS_NP_UTIL_ERROR_NOT_MATCH;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < 8; i++) {
|
||||
if (np_id1->opt[i] != np_id2->opt[i]) {
|
||||
return ORBIS_NP_UTIL_ERROR_NOT_MATCH;
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < 8; i++) {
|
||||
if (np_id1->reserved[i] != np_id2->reserved[i]) {
|
||||
return ORBIS_NP_UTIL_ERROR_NOT_MATCH;
|
||||
}
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCmpNpIdInOrder(OrbisNpId* np_id1, OrbisNpId* np_id2, u32* out_result) {
|
||||
if (np_id1 == nullptr || np_id2 == nullptr || out_result == nullptr) {
|
||||
return ORBIS_NP_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
u32 compare =
|
||||
std::strncmp(np_id1->handle.data, np_id2->handle.data, ORBIS_NP_ONLINEID_MAX_LENGTH);
|
||||
if (compare < 0) {
|
||||
*out_result = -1;
|
||||
return ORBIS_OK;
|
||||
} else if (compare > 0) {
|
||||
*out_result = 1;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < 8; i++) {
|
||||
if (np_id1->opt[i] < np_id2->opt[i]) {
|
||||
*out_result = -1;
|
||||
return ORBIS_OK;
|
||||
} else if (np_id1->opt[i] > np_id2->opt[i]) {
|
||||
*out_result = 1;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < 8; i++) {
|
||||
if (np_id1->reserved[i] < np_id2->reserved[i]) {
|
||||
*out_result = -1;
|
||||
return ORBIS_OK;
|
||||
} else if (np_id1->reserved[i] > np_id2->reserved[i]) {
|
||||
*out_result = 1;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
*out_result = 0;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCmpOnlineId(OrbisNpOnlineId* online_id1, OrbisNpOnlineId* online_id2) {
|
||||
if (online_id1 == nullptr || online_id2 == nullptr) {
|
||||
return ORBIS_NP_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (std::strncmp(online_id1->data, online_id2->data, ORBIS_NP_ONLINEID_MAX_LENGTH) != 0) {
|
||||
return ORBIS_NP_UTIL_ERROR_NOT_MATCH;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
u32 PS4_SYSV_ABI sceNpMutexLock(Libraries::Kernel::PthreadMutexT* mutex) {
|
||||
LOG_TRACE(Lib_NpCommon, "mutex={:p}", fmt::ptr(mutex));
|
||||
return static_cast<u32>(
|
||||
NormalizeNpCommonResult(Libraries::Kernel::posix_pthread_mutex_lock(mutex)));
|
||||
}
|
||||
|
||||
u32 PS4_SYSV_ABI sceNpMutexUnlock(Libraries::Kernel::PthreadMutexT* mutex) {
|
||||
LOG_TRACE(Lib_NpCommon, "mutex={:p}", fmt::ptr(mutex));
|
||||
return static_cast<u32>(
|
||||
NormalizeNpCommonResult(Libraries::Kernel::posix_pthread_mutex_unlock(mutex)));
|
||||
}
|
||||
|
||||
u32 PS4_SYSV_ABI sceNpMutexInit(Libraries::Kernel::PthreadMutexT* mutex, const char* name,
|
||||
u64 flags) {
|
||||
LOG_DEBUG(Lib_NpCommon, "mutex={:p} name={:p} flags={:#x}", fmt::ptr(mutex), fmt::ptr(name),
|
||||
flags);
|
||||
|
||||
Libraries::Kernel::PthreadMutexAttrT attr = nullptr;
|
||||
int rc = Libraries::Kernel::posix_pthread_mutexattr_init(&attr);
|
||||
if (rc == 0) {
|
||||
if ((flags & 1) != 0) {
|
||||
rc = Libraries::Kernel::posix_pthread_mutexattr_settype(
|
||||
&attr, Libraries::Kernel::PthreadMutexType::Recursive);
|
||||
}
|
||||
if (rc == 0) {
|
||||
rc = Libraries::Kernel::scePthreadMutexInit(mutex, &attr, name);
|
||||
}
|
||||
Libraries::Kernel::posix_pthread_mutexattr_destroy(&attr);
|
||||
}
|
||||
return static_cast<u32>(NormalizeNpCommonResult(rc));
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpMutexDestroy(Libraries::Kernel::PthreadMutexT* mutex) {
|
||||
LOG_DEBUG(Lib_NpCommon, "mutex={:p}", fmt::ptr(mutex));
|
||||
return NormalizeNpCommonResult(Libraries::Kernel::posix_pthread_mutex_destroy(mutex));
|
||||
}
|
||||
|
||||
u32 PS4_SYSV_ABI sceNpMutexTryLock(Libraries::Kernel::PthreadMutexT* mutex) {
|
||||
LOG_TRACE(Lib_NpCommon, "mutex={:p}", fmt::ptr(mutex));
|
||||
return NormalizeNpTryLockResult(Libraries::Kernel::posix_pthread_mutex_trylock(mutex));
|
||||
}
|
||||
|
||||
u32 PS4_SYSV_ABI sceNpLwMutexLock(Libraries::Kernel::PthreadMutexT* mutex) {
|
||||
LOG_TRACE(Lib_NpCommon, "mutex={:p}", fmt::ptr(mutex));
|
||||
return sceNpMutexLock(mutex);
|
||||
}
|
||||
|
||||
u32 PS4_SYSV_ABI sceNpLwMutexUnlock(Libraries::Kernel::PthreadMutexT* mutex) {
|
||||
LOG_TRACE(Lib_NpCommon, "mutex={:p}", fmt::ptr(mutex));
|
||||
return sceNpMutexUnlock(mutex);
|
||||
}
|
||||
|
||||
u32 PS4_SYSV_ABI sceNpLwMutexInit(Libraries::Kernel::PthreadMutexT* mutex, const char* name,
|
||||
u64 flags) {
|
||||
LOG_DEBUG(Lib_NpCommon, "mutex={:p} name={:p} flags={:#x}", fmt::ptr(mutex), fmt::ptr(name),
|
||||
flags);
|
||||
return sceNpMutexInit(mutex, name, flags);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpLwMutexDestroy(Libraries::Kernel::PthreadMutexT* mutex) {
|
||||
LOG_DEBUG(Lib_NpCommon, "mutex={:p}", fmt::ptr(mutex));
|
||||
return sceNpMutexDestroy(mutex);
|
||||
}
|
||||
|
||||
u32 PS4_SYSV_ABI sceNpLwMutexTryLock(Libraries::Kernel::PthreadMutexT* mutex) {
|
||||
LOG_TRACE(Lib_NpCommon, "mutex={:p}", fmt::ptr(mutex));
|
||||
return sceNpMutexTryLock(mutex);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCondInit(Libraries::Kernel::PthreadCondT* cond, const char* name, u64 flags) {
|
||||
LOG_DEBUG(Lib_NpCommon, "cond={:p} name={:p} flags={:#x}", fmt::ptr(cond), fmt::ptr(name),
|
||||
flags);
|
||||
|
||||
Libraries::Kernel::PthreadCondAttrT attr = nullptr;
|
||||
int rc = Libraries::Kernel::posix_pthread_condattr_init(&attr);
|
||||
if (rc == 0) {
|
||||
rc = Libraries::Kernel::scePthreadCondInit(cond, &attr, name);
|
||||
Libraries::Kernel::posix_pthread_condattr_destroy(&attr);
|
||||
}
|
||||
return NormalizeNpCommonResult(rc);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCondDestroy(Libraries::Kernel::PthreadCondT* cond) {
|
||||
LOG_DEBUG(Lib_NpCommon, "cond={:p}", fmt::ptr(cond));
|
||||
return NormalizeNpCommonResult(Libraries::Kernel::posix_pthread_cond_destroy(cond));
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCondSignal(Libraries::Kernel::PthreadCondT* cond) {
|
||||
LOG_DEBUG(Lib_NpCommon, "cond={:p}", fmt::ptr(cond));
|
||||
return NormalizeNpCommonResult(Libraries::Kernel::posix_pthread_cond_signal(cond));
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCondTimedwait(Libraries::Kernel::PthreadCondT* cond,
|
||||
Libraries::Kernel::PthreadMutexT* mutex, u32 usec) {
|
||||
LOG_DEBUG(Lib_NpCommon, "cond={:p} mutex={:p} usec={:#x}", fmt::ptr(cond), fmt::ptr(mutex),
|
||||
usec);
|
||||
if (usec == 0) {
|
||||
return NormalizeNpCommonResult(Libraries::Kernel::posix_pthread_cond_wait(cond, mutex));
|
||||
}
|
||||
const s32 rc = NormalizeNpCommonResult(
|
||||
Libraries::Kernel::posix_pthread_cond_reltimedwait_np(cond, mutex, usec));
|
||||
if (rc == ORBIS_KERNEL_ERROR_ETIMEDOUT) {
|
||||
return ORBIS_NP_LW_COND_ERROR_TIMEDOUT;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCreateThread(Libraries::Kernel::PthreadT* thread, NpThreadEntry start_routine,
|
||||
void* arg, s32 priority, u64 stack_size, u64 affinity_mask,
|
||||
const char* name) {
|
||||
LOG_DEBUG(Lib_NpCommon,
|
||||
"thread={:p} start={:#x} arg={:p} priority={} stack={:#x} "
|
||||
"affinity={:#x} name={:p}",
|
||||
fmt::ptr(thread), reinterpret_cast<u64>(start_routine), fmt::ptr(arg), priority,
|
||||
stack_size, affinity_mask, fmt::ptr(name));
|
||||
|
||||
Libraries::Kernel::PthreadAttrT attr = nullptr;
|
||||
int rc = Libraries::Kernel::posix_pthread_attr_init(&attr);
|
||||
if (rc != 0) {
|
||||
return NormalizeNpCommonResult(rc);
|
||||
}
|
||||
|
||||
rc = Libraries::Kernel::posix_pthread_attr_setstacksize(&attr, static_cast<size_t>(stack_size));
|
||||
if (rc == 0) {
|
||||
if (priority != 0) {
|
||||
s32 sdk_version = 0;
|
||||
rc = Libraries::Kernel::sceKernelGetCompiledSdkVersion(&sdk_version);
|
||||
if (rc >= 0 && sdk_version >= 0x2500000) {
|
||||
rc = Libraries::Kernel::posix_pthread_attr_setinheritsched(&attr, 0);
|
||||
if (rc == 0) {
|
||||
rc = Libraries::Kernel::posix_pthread_attr_setschedpolicy(
|
||||
&attr, Libraries::Kernel::SchedPolicy::Fifo);
|
||||
}
|
||||
}
|
||||
if (rc >= 0) {
|
||||
Libraries::Kernel::SchedParam param{.sched_priority = priority};
|
||||
rc = Libraries::Kernel::posix_pthread_attr_setschedparam(&attr, ¶m);
|
||||
}
|
||||
}
|
||||
if (rc == 0 && affinity_mask != 0) {
|
||||
rc = Libraries::Kernel::scePthreadAttrSetaffinity(&attr, affinity_mask);
|
||||
}
|
||||
if (rc == 0) {
|
||||
rc = Libraries::Kernel::posix_pthread_create_name_np(thread, &attr, start_routine, arg,
|
||||
name);
|
||||
}
|
||||
}
|
||||
|
||||
Libraries::Kernel::posix_pthread_attr_destroy(&attr);
|
||||
return NormalizeNpCommonResult(rc);
|
||||
}
|
||||
|
||||
u32 PS4_SYSV_ABI sceNpJoinThread(Libraries::Kernel::PthreadT thread, void** ret) {
|
||||
LOG_DEBUG(Lib_NpCommon, "thread={:p} ret={:p}", fmt::ptr(thread), fmt::ptr(ret));
|
||||
return static_cast<u32>(
|
||||
NormalizeNpCommonResult(Libraries::Kernel::posix_pthread_join(thread, ret)));
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpGetSystemClockUsec(s64* usec) {
|
||||
LOG_DEBUG(Lib_NpCommon, "usec={:p}", fmt::ptr(usec));
|
||||
Libraries::Kernel::OrbisKernelTimespec ts{};
|
||||
const s32 rc =
|
||||
Libraries::Kernel::sceKernelClockGettime(Libraries::Kernel::ORBIS_CLOCK_MONOTONIC, &ts);
|
||||
if (rc == ORBIS_OK) {
|
||||
*usec = ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpGetPlatformType(const OrbisNpId* npid) {
|
||||
LOG_DEBUG(Lib_NpCommon, "np_id={:p}", fmt::ptr(npid));
|
||||
|
||||
if (npid == nullptr) {
|
||||
return ORBIS_NP_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
static constexpr u32 kTagPs3 = 0x00337370; // "ps3"
|
||||
static constexpr u32 kTagVita = 0x32707370; // "psp2(VITA)"
|
||||
static constexpr u32 kTagPs4 = 0x00347370; // "ps4"
|
||||
|
||||
if (npid->opt[4] == 0) {
|
||||
return OrbisNpPlatformType::None;
|
||||
}
|
||||
u32 tag = 0;
|
||||
std::memcpy(&tag, npid->opt + 4, sizeof(tag));
|
||||
switch (tag) {
|
||||
case kTagPs3:
|
||||
return OrbisNpPlatformType::PS3;
|
||||
case kTagVita:
|
||||
return OrbisNpPlatformType::Vita;
|
||||
case kTagPs4:
|
||||
return OrbisNpPlatformType::PS4;
|
||||
default:
|
||||
return ORBIS_NP_ERROR_INVALID_PLATFORM_TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpIntIsValidOnlineId(const OrbisNpOnlineId* id) {
|
||||
LOG_DEBUG(Lib_NpCommon, "online_id={:p}", fmt::ptr(id));
|
||||
|
||||
if (id == nullptr || id->term != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const size_t len = strnlen(id->data, ORBIS_NP_ONLINEID_MAX_LENGTH + 1);
|
||||
if (len < 3 || len > ORBIS_NP_ONLINEID_MAX_LENGTH) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const unsigned char first = static_cast<unsigned char>(id->data[0]);
|
||||
if (!std::isalnum(first)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (size_t i = 1; i < len; ++i) {
|
||||
const unsigned char c = static_cast<unsigned char>(id->data[i]);
|
||||
if (std::isalnum(c) || c == '_' || c == '-') {
|
||||
continue;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void PS4_SYSV_ABI sceNpGetSdkVersion(char* version_buf) {
|
||||
LOG_DEBUG(Lib_NpCommon, "called");
|
||||
// Real library has no parameter checks, so I guess this is purely for internal use?
|
||||
Libraries::Kernel::SwVersionStruct sw_version{};
|
||||
sw_version.struct_size = 0x28;
|
||||
Libraries::Kernel::sceKernelGetSystemSwVersion(&sw_version);
|
||||
u32 sw_hex = sw_version.hex_representation;
|
||||
snprintf(version_buf, 8, "%d.%02d", ((sw_hex >> 0x18) & 0xf) + ((sw_hex >> 0x1c) * 10),
|
||||
((sw_hex >> 0x10) & 0xf) + (((sw_hex >> 0x14) & 0xf) * 10));
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCalloutInitCtx(OrbisNpCalloutContext* callout_ctx, const char* name,
|
||||
u64 stack_size, s32 priority, u64 affinity_mask) {
|
||||
LOG_DEBUG(Lib_NpCommon, "ctx={:p} name={:p} stack={:#x} priority={} affinity={:#x}",
|
||||
fmt::ptr(callout_ctx), fmt::ptr(name), stack_size, priority, affinity_mask);
|
||||
|
||||
s32 rc = ORBIS_NP_CALLOUT_ERROR_ALREADY_INITIALIZED;
|
||||
if (callout_ctx->active < 1) {
|
||||
callout_ctx->stop_requested = 0;
|
||||
callout_ctx->head = nullptr;
|
||||
rc = static_cast<s32>(sceNpMutexInit(&callout_ctx->mutex, name, 1));
|
||||
if (rc >= 0) {
|
||||
rc = sceNpCondInit(&callout_ctx->cond, name, 0);
|
||||
if (rc >= 0) {
|
||||
rc = sceNpCreateThread(&callout_ctx->thread, NpCalloutThreadMain, callout_ctx,
|
||||
priority, stack_size, affinity_mask, name);
|
||||
if (rc >= 0) {
|
||||
callout_ctx->active = 1;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
sceNpCondDestroy(&callout_ctx->cond);
|
||||
}
|
||||
sceNpMutexDestroy(&callout_ctx->mutex);
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCalloutStartOnCtx(OrbisNpCalloutContext* callout_ctx,
|
||||
OrbisNpCalloutEntry* entry, u64 delay_usec, u64 handler,
|
||||
u64 arg) {
|
||||
LOG_DEBUG(Lib_NpCommon, "ctx={:p} callout={:p} delay={:#x} handler={:p} arg={:#x}",
|
||||
fmt::ptr(callout_ctx), fmt::ptr(entry), delay_usec,
|
||||
fmt::ptr(reinterpret_cast<void*>(static_cast<uintptr_t>(handler))), arg);
|
||||
|
||||
if (callout_ctx->active == 0) {
|
||||
return ORBIS_NP_CALLOUT_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
entry->next = nullptr;
|
||||
entry->handler = reinterpret_cast<OrbisNpCalloutHandler>(handler);
|
||||
entry->arg = arg;
|
||||
sceNpGetSystemClockUsec(&entry->deadline_usec);
|
||||
entry->deadline_usec += static_cast<s64>(static_cast<u32>(delay_usec));
|
||||
|
||||
sceNpMutexLock(&callout_ctx->mutex);
|
||||
|
||||
for (auto* cur = callout_ctx->head; cur != nullptr; cur = cur->next) {
|
||||
if (cur == entry) {
|
||||
sceNpMutexUnlock(&callout_ctx->mutex);
|
||||
return ORBIS_NP_CALLOUT_ERROR_DUPLICATE_ENTRY;
|
||||
}
|
||||
}
|
||||
|
||||
OrbisNpCalloutEntry** prev_next = &callout_ctx->head;
|
||||
for (auto* cur = callout_ctx->head; cur != nullptr; cur = cur->next) {
|
||||
if (cur->deadline_usec >= entry->deadline_usec) {
|
||||
break;
|
||||
}
|
||||
prev_next = &cur->next;
|
||||
}
|
||||
|
||||
entry->next = *prev_next;
|
||||
*prev_next = entry;
|
||||
sceNpCondSignal(&callout_ctx->cond);
|
||||
sceNpMutexUnlock(&callout_ctx->mutex);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCalloutStartOnCtx64(OrbisNpCalloutContext* callout_ctx,
|
||||
OrbisNpCalloutEntry* entry, s64 delay_usec, u64 handler,
|
||||
u64 arg) {
|
||||
LOG_DEBUG(Lib_NpCommon, "ctx={:p} callout={:p} delay={} handler={:p} arg={:#x}",
|
||||
fmt::ptr(callout_ctx), fmt::ptr(entry), delay_usec,
|
||||
fmt::ptr(reinterpret_cast<void*>(static_cast<uintptr_t>(handler))), arg);
|
||||
|
||||
if (callout_ctx->active == 0) {
|
||||
return ORBIS_NP_CALLOUT_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
entry->next = nullptr;
|
||||
entry->handler = reinterpret_cast<OrbisNpCalloutHandler>(handler);
|
||||
entry->arg = arg;
|
||||
sceNpGetSystemClockUsec(&entry->deadline_usec);
|
||||
entry->deadline_usec += delay_usec;
|
||||
|
||||
sceNpMutexLock(&callout_ctx->mutex);
|
||||
|
||||
for (auto* cur = callout_ctx->head; cur != nullptr; cur = cur->next) {
|
||||
if (cur == entry) {
|
||||
sceNpMutexUnlock(&callout_ctx->mutex);
|
||||
return ORBIS_NP_CALLOUT_ERROR_DUPLICATE_ENTRY;
|
||||
}
|
||||
}
|
||||
|
||||
OrbisNpCalloutEntry** prev_next = &callout_ctx->head;
|
||||
for (auto* cur = callout_ctx->head; cur != nullptr; cur = cur->next) {
|
||||
if (cur->deadline_usec >= entry->deadline_usec) {
|
||||
break;
|
||||
}
|
||||
prev_next = &cur->next;
|
||||
}
|
||||
|
||||
entry->next = *prev_next;
|
||||
*prev_next = entry;
|
||||
sceNpCondSignal(&callout_ctx->cond);
|
||||
sceNpMutexUnlock(&callout_ctx->mutex);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCalloutStopOnCtx(OrbisNpCalloutContext* callout_ctx,
|
||||
OrbisNpCalloutEntry* entry, u32* removed) {
|
||||
LOG_DEBUG(Lib_NpCommon, "ctx={:p} callout={:p} removed={:p}", fmt::ptr(callout_ctx),
|
||||
fmt::ptr(entry), fmt::ptr(removed));
|
||||
|
||||
if (callout_ctx->active == 0) {
|
||||
return ORBIS_NP_CALLOUT_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
sceNpMutexLock(&callout_ctx->mutex);
|
||||
u32 found = 0;
|
||||
OrbisNpCalloutEntry** prev_next = &callout_ctx->head;
|
||||
for (auto* cur = callout_ctx->head; cur != nullptr; cur = cur->next) {
|
||||
if (cur == entry) {
|
||||
*prev_next = cur->next;
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
prev_next = &cur->next;
|
||||
}
|
||||
if (removed != nullptr) {
|
||||
*removed = found;
|
||||
}
|
||||
sceNpMutexUnlock(&callout_ctx->mutex);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
void PS4_SYSV_ABI sceNpCalloutTermCtx(OrbisNpCalloutContext* callout_ctx) {
|
||||
LOG_DEBUG(Lib_NpCommon, "ctx={:p}", fmt::ptr(callout_ctx));
|
||||
|
||||
if (callout_ctx->active != 0) {
|
||||
sceNpMutexLock(&callout_ctx->mutex);
|
||||
callout_ctx->stop_requested = 1;
|
||||
sceNpCondSignal(&callout_ctx->cond);
|
||||
sceNpMutexUnlock(&callout_ctx->mutex);
|
||||
sceNpJoinThread(callout_ctx->thread, nullptr);
|
||||
sceNpCondDestroy(&callout_ctx->cond);
|
||||
sceNpMutexDestroy(&callout_ctx->mutex);
|
||||
callout_ctx->active = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("i8UmXTSq7N4", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpCmpNpId);
|
||||
LIB_FUNCTION("TcwEFnakiSc", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpCmpNpIdInOrder);
|
||||
LIB_FUNCTION("dj+O5aD2a0Q", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpCmpOnlineId);
|
||||
LIB_FUNCTION("1a+iY5YUJcI", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpCondDestroy);
|
||||
LIB_FUNCTION("9+m5nRdJ-wQ", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpCalloutInitCtx);
|
||||
LIB_FUNCTION("AqJ4xkWsV+I", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpCalloutTermCtx);
|
||||
LIB_FUNCTION("18j+qk6dRwk", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpLwMutexLock);
|
||||
LIB_FUNCTION("1CiXI-MyEKs", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpLwMutexInit);
|
||||
LIB_FUNCTION("4zxevggtYrQ", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpLwMutexDestroy);
|
||||
LIB_FUNCTION("CQG2oyx1-nM", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpLwMutexUnlock);
|
||||
LIB_FUNCTION("DuslmoqQ+nk", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpMutexTryLock);
|
||||
LIB_FUNCTION("EjMsfO3GCIA", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpJoinThread);
|
||||
LIB_FUNCTION("fClnlkZmA6k", "libSceNpCommonCompat", 1, "libSceNpCommon",
|
||||
sceNpCalloutStartOnCtx);
|
||||
LIB_FUNCTION("fhJ5uKzcn0w", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpCreateThread);
|
||||
LIB_FUNCTION("hkeX9iuCwlI", "libSceNpCommonCompat", 1, "libSceNpCommon",
|
||||
sceNpIntIsValidOnlineId);
|
||||
LIB_FUNCTION("hp0kVgu5Fxw", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpLwMutexTryLock);
|
||||
LIB_FUNCTION("in19gH7G040", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpCalloutStopOnCtx);
|
||||
LIB_FUNCTION("lQ11BpMM4LU", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpMutexDestroy);
|
||||
LIB_FUNCTION("lpr66Gby8dQ", "libSceNpCommonCompat", 1, "libSceNpCommon",
|
||||
sceNpCalloutStartOnCtx64);
|
||||
LIB_FUNCTION("oZyb9ktuCpA", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpMutexUnlock);
|
||||
LIB_FUNCTION("PVVsRmMkO1g", "libSceNpCommonCompat", 1, "libSceNpCommon",
|
||||
sceNpGetSystemClockUsec);
|
||||
LIB_FUNCTION("q2tsVO3lM4A", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpCondInit);
|
||||
LIB_FUNCTION("r9Bet+s6fKc", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpMutexLock);
|
||||
LIB_FUNCTION("sXVQUIGmk2U", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpGetPlatformType);
|
||||
LIB_FUNCTION("ss2xO9IJxKQ", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpCondTimedwait);
|
||||
LIB_FUNCTION("uEwag-0YZPc", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpMutexInit);
|
||||
LIB_FUNCTION("uMJFOA62mVU", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpCondSignal);
|
||||
LIB_FUNCTION("i8UmXTSq7N4", "libSceNpCommon", 1, "libSceNpCommon", sceNpCmpNpId);
|
||||
LIB_FUNCTION("TcwEFnakiSc", "libSceNpCommon", 1, "libSceNpCommon", sceNpCmpNpIdInOrder);
|
||||
LIB_FUNCTION("dj+O5aD2a0Q", "libSceNpCommon", 1, "libSceNpCommon", sceNpCmpOnlineId);
|
||||
LIB_FUNCTION("1a+iY5YUJcI", "libSceNpCommon", 1, "libSceNpCommon", sceNpCondDestroy);
|
||||
LIB_FUNCTION("9+m5nRdJ-wQ", "libSceNpCommon", 1, "libSceNpCommon", sceNpCalloutInitCtx);
|
||||
LIB_FUNCTION("AqJ4xkWsV+I", "libSceNpCommon", 1, "libSceNpCommon", sceNpCalloutTermCtx);
|
||||
LIB_FUNCTION("18j+qk6dRwk", "libSceNpCommon", 1, "libSceNpCommon", sceNpLwMutexLock);
|
||||
LIB_FUNCTION("1CiXI-MyEKs", "libSceNpCommon", 1, "libSceNpCommon", sceNpLwMutexInit);
|
||||
LIB_FUNCTION("4zxevggtYrQ", "libSceNpCommon", 1, "libSceNpCommon", sceNpLwMutexDestroy);
|
||||
LIB_FUNCTION("CQG2oyx1-nM", "libSceNpCommon", 1, "libSceNpCommon", sceNpLwMutexUnlock);
|
||||
LIB_FUNCTION("DuslmoqQ+nk", "libSceNpCommon", 1, "libSceNpCommon", sceNpMutexTryLock);
|
||||
LIB_FUNCTION("EjMsfO3GCIA", "libSceNpCommon", 1, "libSceNpCommon", sceNpJoinThread);
|
||||
LIB_FUNCTION("fClnlkZmA6k", "libSceNpCommon", 1, "libSceNpCommon", sceNpCalloutStartOnCtx);
|
||||
LIB_FUNCTION("fhJ5uKzcn0w", "libSceNpCommon", 1, "libSceNpCommon", sceNpCreateThread);
|
||||
LIB_FUNCTION("hkeX9iuCwlI", "libSceNpCommon", 1, "libSceNpCommon", sceNpIntIsValidOnlineId);
|
||||
LIB_FUNCTION("hp0kVgu5Fxw", "libSceNpCommon", 1, "libSceNpCommon", sceNpLwMutexTryLock);
|
||||
LIB_FUNCTION("in19gH7G040", "libSceNpCommon", 1, "libSceNpCommon", sceNpCalloutStopOnCtx);
|
||||
LIB_FUNCTION("lQ11BpMM4LU", "libSceNpCommon", 1, "libSceNpCommon", sceNpMutexDestroy);
|
||||
LIB_FUNCTION("lpr66Gby8dQ", "libSceNpCommon", 1, "libSceNpCommon", sceNpCalloutStartOnCtx64);
|
||||
LIB_FUNCTION("oZyb9ktuCpA", "libSceNpCommon", 1, "libSceNpCommon", sceNpMutexUnlock);
|
||||
LIB_FUNCTION("PVVsRmMkO1g", "libSceNpCommon", 1, "libSceNpCommon", sceNpGetSystemClockUsec);
|
||||
LIB_FUNCTION("q2tsVO3lM4A", "libSceNpCommon", 1, "libSceNpCommon", sceNpCondInit);
|
||||
LIB_FUNCTION("r9Bet+s6fKc", "libSceNpCommon", 1, "libSceNpCommon", sceNpMutexLock);
|
||||
LIB_FUNCTION("sXVQUIGmk2U", "libSceNpCommon", 1, "libSceNpCommon", sceNpGetPlatformType);
|
||||
LIB_FUNCTION("ss2xO9IJxKQ", "libSceNpCommon", 1, "libSceNpCommon", sceNpCondTimedwait);
|
||||
LIB_FUNCTION("uEwag-0YZPc", "libSceNpCommon", 1, "libSceNpCommon", sceNpMutexInit);
|
||||
LIB_FUNCTION("uMJFOA62mVU", "libSceNpCommon", 1, "libSceNpCommon", sceNpCondSignal);
|
||||
LIB_FUNCTION("Pglk7zFj0DI", "libSceNpCommon", 1, "libSceNpCommon", sceNpGetSdkVersion);
|
||||
};
|
||||
|
||||
} // namespace Libraries::Np::NpCommon
|
||||
@@ -0,0 +1,77 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/kernel/threads/pthread.h"
|
||||
#include "core/libraries/np/np_types.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
namespace Libraries::Np::NpCommon {
|
||||
|
||||
struct OrbisNpFreeKernelMemoryArgs {
|
||||
u64 length;
|
||||
u64 unk;
|
||||
void* addr;
|
||||
};
|
||||
|
||||
using NpThreadEntry = void* PS4_SYSV_ABI (*)(void*);
|
||||
using OrbisNpCalloutHandler = void PS4_SYSV_ABI (*)(u64);
|
||||
|
||||
struct OrbisNpCalloutEntry {
|
||||
OrbisNpCalloutEntry* next;
|
||||
OrbisNpCalloutHandler handler;
|
||||
s64 deadline_usec;
|
||||
u64 arg;
|
||||
};
|
||||
|
||||
struct OrbisNpCalloutContext {
|
||||
Libraries::Kernel::PthreadT thread;
|
||||
Libraries::Kernel::PthreadMutexT mutex;
|
||||
Libraries::Kernel::PthreadCondT cond;
|
||||
s32 active;
|
||||
s32 stop_requested;
|
||||
OrbisNpCalloutEntry* head;
|
||||
};
|
||||
|
||||
u32 PS4_SYSV_ABI sceNpMutexLock(Libraries::Kernel::PthreadMutexT* mutex);
|
||||
u32 PS4_SYSV_ABI sceNpMutexUnlock(Libraries::Kernel::PthreadMutexT* mutex);
|
||||
u32 PS4_SYSV_ABI sceNpMutexInit(Libraries::Kernel::PthreadMutexT* mutex, const char* name,
|
||||
u64 flags);
|
||||
s32 PS4_SYSV_ABI sceNpMutexDestroy(Libraries::Kernel::PthreadMutexT* mutex);
|
||||
u32 PS4_SYSV_ABI sceNpMutexTryLock(Libraries::Kernel::PthreadMutexT* mutex);
|
||||
u32 PS4_SYSV_ABI sceNpLwMutexLock(Libraries::Kernel::PthreadMutexT* mutex);
|
||||
u32 PS4_SYSV_ABI sceNpLwMutexUnlock(Libraries::Kernel::PthreadMutexT* mutex);
|
||||
u32 PS4_SYSV_ABI sceNpLwMutexInit(Libraries::Kernel::PthreadMutexT* mutex, const char* name,
|
||||
u64 flags);
|
||||
s32 PS4_SYSV_ABI sceNpLwMutexDestroy(Libraries::Kernel::PthreadMutexT* mutex);
|
||||
u32 PS4_SYSV_ABI sceNpLwMutexTryLock(Libraries::Kernel::PthreadMutexT* mutex);
|
||||
s32 PS4_SYSV_ABI sceNpCondInit(Libraries::Kernel::PthreadCondT* cond, const char* name, u64 flags);
|
||||
s32 PS4_SYSV_ABI sceNpCondDestroy(Libraries::Kernel::PthreadCondT* cond);
|
||||
s32 PS4_SYSV_ABI sceNpCondSignal(Libraries::Kernel::PthreadCondT* cond);
|
||||
s32 PS4_SYSV_ABI sceNpCondTimedwait(Libraries::Kernel::PthreadCondT* cond,
|
||||
Libraries::Kernel::PthreadMutexT* mutex, u32 usec);
|
||||
s32 PS4_SYSV_ABI sceNpCreateThread(Libraries::Kernel::PthreadT* thread, NpThreadEntry start_routine,
|
||||
void* arg, s32 priority, u64 stack_size, u64 affinity_mask,
|
||||
const char* name);
|
||||
u32 PS4_SYSV_ABI sceNpJoinThread(Libraries::Kernel::PthreadT thread, void** ret);
|
||||
s32 PS4_SYSV_ABI sceNpGetSystemClockUsec(s64* usec);
|
||||
s32 PS4_SYSV_ABI sceNpGetPlatformType(const OrbisNpId* np_id);
|
||||
s32 PS4_SYSV_ABI sceNpIntIsValidOnlineId(const OrbisNpOnlineId* id);
|
||||
void PS4_SYSV_ABI sceNpGetSdkVersion(char* version_buf);
|
||||
s32 PS4_SYSV_ABI sceNpCalloutInitCtx(OrbisNpCalloutContext* ctx, const char* name, u64 stack_size,
|
||||
s32 priority, u64 affinity_mask);
|
||||
s32 PS4_SYSV_ABI sceNpCalloutStartOnCtx(OrbisNpCalloutContext* ctx, OrbisNpCalloutEntry* callout,
|
||||
u64 delay_usec, u64 handler, u64 arg);
|
||||
s32 PS4_SYSV_ABI sceNpCalloutStartOnCtx64(OrbisNpCalloutContext* ctx, OrbisNpCalloutEntry* callout,
|
||||
s64 delay_usec, u64 handler, u64 arg);
|
||||
s32 PS4_SYSV_ABI sceNpCalloutStopOnCtx(OrbisNpCalloutContext* ctx, OrbisNpCalloutEntry* callout,
|
||||
u32* removed);
|
||||
void PS4_SYSV_ABI sceNpCalloutTermCtx(OrbisNpCalloutContext* ctx);
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::Np::NpCommon
|
||||
Loaded 100 of 227 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user