mirror of
https://github.com/fstat13/PSCapture.git
synced 2026-10-18 21:00:45 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d292d0fbf7 | ||
|
|
edf3c4c6f2 | ||
|
|
876f8ef82b | ||
|
|
07fe4968da | ||
|
|
472ea0cab0 | ||
|
|
c0ea0921c9 | ||
|
|
a3692b1e25 | ||
|
|
0daa328a3c | ||
|
|
99f895cafb | ||
|
|
9676e67432 |
No files matched your search
@@ -10,3 +10,8 @@ pscapture_trace.txt
|
||||
# ambienti
|
||||
_sequenza_esiti/
|
||||
_BOZZE/
|
||||
|
||||
# runtime
|
||||
pscapture-app.log
|
||||
pscapture.ini
|
||||
dist/SHA256SUMS.txt
|
||||
@@ -15,6 +15,30 @@ No capture card. No Remote Play. No cloud. Just your console and your network.<b
|
||||
<b>by fstat13</b>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
📖 <b><a href="docs/GUIDE.md">How to use it — step-by-step guide</a></b>
|
||||
·
|
||||
🇮🇹 <b><a href="docs/GUIDE.it.md">Guida passo-passo in italiano</a></b>
|
||||
·
|
||||
🔧 <b><a href="docs/BUILD.md">Build from source</a></b>
|
||||
·
|
||||
🇮🇹 <b><a href="docs/BUILD.it.md">Build dai sorgenti</a></b>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
🌐 Illustrated guide:
|
||||
<b><a href="https://fstat13.github.io/PSCapture/">English</a></b>
|
||||
·
|
||||
<b><a href="https://fstat13.github.io/PSCapture/index.it.html">Italiano</a></b>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/fstat13/PSCapture/releases"><img src="https://img.shields.io/github/v/release/fstat13/PSCapture?style=flat-square&color=0070d1&label=release" alt="Release"></a>
|
||||
<a href="https://github.com/fstat13/PSCapture/stargazers"><img src="https://img.shields.io/github/stars/fstat13/PSCapture?style=flat-square&color=0070d1" alt="Stars"></a>
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/license-source--available-35a3ff?style=flat-square" alt="License"></a>
|
||||
<img src="https://img.shields.io/badge/platform-Windows%2010%2F11-0070d1?style=flat-square" alt="Platform">
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## ✨ What you get
|
||||
@@ -31,50 +55,82 @@ No capture card. No Remote Play. No cloud. Just your console and your network.<b
|
||||
|
||||
## 🚀 Quick start
|
||||
|
||||
1. **Jailbreak your PS5** (payload loader on `:9021`, anonymous FTP on `:2121`,
|
||||
`pldmgr` autoloader — all standard in today's setups).
|
||||
2. **Run `PSCapture.exe`** (admin is requested automatically; set your PS5 IP
|
||||
if it isn't the default).
|
||||
3. **Press Start.** The app does the rest. The console pops a notification:
|
||||
`PSCapture is streaming on:PORT`
|
||||
4. In Discord/OBS pick the camera **PS5 Capture**.
|
||||
1. **Jailbreak your PS5** — you need the standard setup: a payload loader on
|
||||
TCP `9021` (`elfldr`-style), anonymous FTP on TCP `2121`, and optionally
|
||||
`pldmgr` so the payload starts on every boot.
|
||||
2. **Double-click `PSCapture.exe`** (one file — the console payload and the
|
||||
Windows driver travel inside it). Accept the UAC prompt. If your PS5 isn't
|
||||
at `192.168.0.36`, type its IP in the *PS5 IP* field and press *Apply*.
|
||||
3. **Watch the log pane** — the app installs the capture driver (first run
|
||||
only), sends the payload to the console and connects by itself. The status
|
||||
pill turns green: `STREAMING`. The console shows the system toast
|
||||
**PSCapture is streaming on:PORT**.
|
||||
4. In Discord (*Settings → Voice & Video → Camera*) or OBS pick the camera
|
||||
**PS5 Capture**.
|
||||
|
||||
📖 The illustrated guide lives in [`docs/index.html`](docs/index.html)
|
||||
(open it in a browser, or enable GitHub Pages on `/docs`).
|
||||
🔧 Curious about what happens under the hood when you press Start? See
|
||||
[`docs/INSTALLER.md`](docs/INSTALLER.md) — the exact four steps the app runs,
|
||||
down to registry keys and console paths.
|
||||
Stuck on *“IN ATTESA DELLA PS5”*? Console off, not jailbroken yet, or wrong
|
||||
IP — the app retries by itself every 2 seconds.
|
||||
|
||||
📖 Step-by-step with every log line explained: [`docs/GUIDE.md`](docs/GUIDE.md)
|
||||
— or the [illustrated HTML guide](docs/index.html).
|
||||
🔧 What the app does under the hood when you press Start:
|
||||
[`docs/INSTALLER.md`](docs/INSTALLER.md).
|
||||
|
||||
## 🔒 Verify the download
|
||||
|
||||
The release binaries are built from this repository's sources. Every release
|
||||
carries a `SHA256SUMS.txt` — check it before running the exe:
|
||||
|
||||
```
|
||||
certutil -hashfile pscapture.exe SHA256 (Windows)
|
||||
sha256sum -c SHA256SUMS.txt (Linux/macOS)
|
||||
```
|
||||
|
||||
And you never have to trust any binary: the whole toolchain is documented in
|
||||
[`docs/BUILD.md`](docs/BUILD.md) — rebuild `pscapture.exe` from the sources
|
||||
yourself and compare.
|
||||
|
||||
## 📦 Repository layout
|
||||
|
||||
```
|
||||
payload/ PS5 side: display-engine reader + NV12 streamer (ps5capture.elf)
|
||||
pc/ PSCapture.exe — GUI + installer + payload deployer + feeder
|
||||
payload/ PS5 side: display-engine reader + NV12 streamer (pscapture.elf)
|
||||
pc/ PSCapture.exe — GUI + installer + payload deployer + feeder
|
||||
pc/ps5cam.c the virtual capture-card driver (DirectShow source filter)
|
||||
pc/shm.h shared-memory contract between feeder and driver
|
||||
pc/*.dll the virtual capture-card driver (DirectShow source filter)
|
||||
docs/ HTML guide (dark theme, ready for GitHub Pages)
|
||||
docs/ guides: HTML, step-by-step, build, installer, RE, log
|
||||
```
|
||||
|
||||
## 🔧 Build
|
||||
|
||||
**Console payload** (needs the PS5 payload SDK; any Linux/docker with it):
|
||||
Full walk-through (toolchain install, verification, gotchas):
|
||||
**[`docs/BUILD.md`](docs/BUILD.md)**. The short version, in the **mandatory
|
||||
order** (the `.res` embeds the driver and the payload as they are on disk):
|
||||
|
||||
**Console payload** → `payload/pscapture.elf` (needs Docker + the PS5 payload
|
||||
SDK image; any tag works, here `ps5sdk:18`):
|
||||
|
||||
```bash
|
||||
docker run --rm -v "$PWD/payload:/payload" -w /payload <ps5-payload-sdk-image> make
|
||||
docker run --rm -v "$PWD/payload:/payload" -w /payload ps5sdk:18 make
|
||||
```
|
||||
|
||||
**PC app** (mingw-w64, no dependencies):
|
||||
**PC side** → `pscapture.exe` + `pscapture.dll` (needs MinGW-w64 with `gcc` +
|
||||
`windres`):
|
||||
|
||||
```bash
|
||||
windres pscapture.rc -O coff -o pscapture.res # icon + embedded payload
|
||||
gcc -O2 -mwindows -o pscapture.exe pscapture.c pscapture.res -lws2_32 -lshell32 -ladvapi32
|
||||
cd pc
|
||||
cp ../payload/pscapture.elf . # gets embedded as-is
|
||||
# 1) the capture-card driver (-static is mandatory, see BUILD.md)
|
||||
gcc -O2 -shared -static -static-libgcc -o pscapture.dll ps5cam.c \
|
||||
-lole32 -loleaut32 -luser32 -ladvapi32 -lws2_32
|
||||
# 2) resources = icon + payload + driver
|
||||
windres pscapture.rc -O coff -o pscapture.res
|
||||
# 3) the app (one self-contained executable)
|
||||
gcc -O2 -mwindows -o pscapture.exe pscapture.c pscapture.res \
|
||||
-lws2_32 -lshell32 -ladvapi32 -luxtheme
|
||||
```
|
||||
|
||||
**Driver** (mingw-w64):
|
||||
|
||||
```bash
|
||||
gcc -O2 -shared -o pscapture.dll ps5cam.c -lole32 -loleaut32 -luser32 -ladvapi32 -lws2_32
|
||||
```
|
||||
Rebuild rule: **DLL → windres → exe, every time** — rebuilding only the exe
|
||||
ships the old driver inside it.
|
||||
|
||||
## ❓ FAQ
|
||||
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
# PSCapture — build dai sorgenti, passo per passo
|
||||
|
||||
Tutto il necessario per passare da questi sorgenti a un `PSCapture.exe`
|
||||
funzionante e a un `pscapture.elf` per la console. Ogni comando è scritto
|
||||
esattamente come va eseguito, in ordine. Qui dentro non c'è magia: l'ordine
|
||||
conta e ogni passo è spiegato.
|
||||
|
||||
Ci sono due metà, che si compilano in posti diversi:
|
||||
|
||||
| Metà | Output | Dove si compila | Toolchain |
|
||||
|---|---|---|---|
|
||||
| Payload console | `payload/pscapture.elf` | Docker (container Linux) | PS5 payload SDK (prospero-clang) |
|
||||
| Lato PC | `pc/pscapture.exe` + `pc/pscapture.dll` | Windows | MinGW-w64 (`gcc` + `windres`) |
|
||||
|
||||
🇬🇧 English version: [BUILD.md](BUILD.md)
|
||||
|
||||
---
|
||||
|
||||
## Parte 1 — Il payload per la console
|
||||
|
||||
### 1.1 Cosa serve
|
||||
|
||||
- **Docker** (Docker Desktop su Windows, o Docker normale su Linux/macOS).
|
||||
- **L'immagine del PS5 payload SDK.** È il payload SDK della community per
|
||||
PS5 (`prospero-clang`, `make`, `PS5_PAYLOAD_SDK` nell'ambiente) impacchettato
|
||||
in una immagine. Sulla macchina di sviluppo l'immagine si chiama `ps5sdk:18`
|
||||
— qualsiasi nome va bene, basta adattare i comandi.
|
||||
|
||||
Controlla che l'immagine ci sia:
|
||||
|
||||
```bash
|
||||
docker images | grep ps5sdk
|
||||
```
|
||||
|
||||
### 1.2 Build
|
||||
|
||||
Dalla radice della repo:
|
||||
|
||||
```bash
|
||||
docker run --rm -v "$PWD/payload:/payload" -w /payload ps5sdk:18 make
|
||||
```
|
||||
|
||||
Su Windows (PowerShell/cmd) metti il percorso completo al posto di
|
||||
`"$PWD/payload"`, es.: `-v "C:\Users\tuonome\PSCapture\payload:/payload"`.
|
||||
|
||||
Risultato: **`payload/pscapture.elf`** (~175 KB).
|
||||
|
||||
Il `Makefile` fa l'unica cosa che conta: compila `main.c` con il clang
|
||||
Prospero dell'SDK e linka l'ELF finale. Nessun altro input.
|
||||
|
||||
### 1.3 Opzionale — mandarlo alla console
|
||||
|
||||
Con la console jailbroken (payload loader sulla porta 9021):
|
||||
|
||||
```bash
|
||||
docker run --rm -v "$PWD/payload:/payload" -w /payload ps5sdk:18 \
|
||||
make deploy PS5_HOST=192.168.0.36
|
||||
```
|
||||
|
||||
(oppure carica `pscapture.elf` con il loader che usi di solito).
|
||||
|
||||
Come si presenta un avvio sano sulla console:
|
||||
|
||||
- appare `/data/pscapture.log` con il log di partenza,
|
||||
- appare `/data/pscapture.port` con la porta TCP dello stream,
|
||||
- la console mostra la notifica *PSCapture is streaming on:PORT*.
|
||||
|
||||
---
|
||||
|
||||
## Parte 2 — Il lato PC
|
||||
|
||||
### 2.1 Cosa serve (Windows)
|
||||
|
||||
**MinGW-w64 64-bit** con `gcc` e `windres`. Sulla macchina di sviluppo:
|
||||
|
||||
```powershell
|
||||
winget install BrechtSanders.WinLibs.POSIX.UCRT
|
||||
```
|
||||
|
||||
(oppure qualsiasi MSYS2 con `mingw-w64-x86_64-gcc` — `pacman -S mingw-w64-x86_64-gcc`).
|
||||
|
||||
Controlla che entrambi siano nel PATH:
|
||||
|
||||
```bash
|
||||
gcc --version
|
||||
windres --version
|
||||
```
|
||||
|
||||
### 2.2 Build — l'ordine è obbligatorio
|
||||
|
||||
`pscapture.res` è un bundle che **incastra i file così come sono su disco**:
|
||||
l'icona, il payload della console e il driver della capture card. Quindi la
|
||||
DLL va compilata *prima* del passo risorse, e il payload deve già trovarsi in
|
||||
`pc/` quando gira `windres`. L'EXE si compila per ultimo.
|
||||
|
||||
```bash
|
||||
cd pc
|
||||
|
||||
# 0) porta il payload appena compilato accanto al .rc (viene incastrato così com'è)
|
||||
cp ../payload/pscapture.elf .
|
||||
|
||||
# 1) il driver della capture card (filtro sorgente DirectShow)
|
||||
gcc -O2 -shared -static -static-libgcc -o pscapture.dll ps5cam.c \
|
||||
-lole32 -loleaut32 -luser32 -ladvapi32 -lws2_32
|
||||
|
||||
# 2) bundle: icona + payload + driver in un unico oggetto risorse
|
||||
# (pscapture.rc => 1 ICON pscapture.ico / 2 RCDATA pscapture.elf
|
||||
# / 3 RCDATA pscapture.dll)
|
||||
windres pscapture.rc -O coff -o pscapture.res
|
||||
|
||||
# 3) l'app — un unico eseguibile autocontenuto
|
||||
gcc -O2 -mwindows -o pscapture.exe pscapture.c pscapture.res \
|
||||
-lws2_32 -lshell32 -ladvapi32 -luxtheme
|
||||
```
|
||||
|
||||
### 2.3 Verifica della build
|
||||
|
||||
```bash
|
||||
# il driver deve dipendere SOLO da DLL di sistema (niente libgcc/libwinpthread):
|
||||
objdump -p pscapture.dll | grep "DLL Name"
|
||||
|
||||
# le dimensioni giuste: exe ~500 KB, dll ~220 KB
|
||||
ls -la pscapture.exe pscapture.dll
|
||||
|
||||
# payload e driver sono davvero dentro l'eseguibile:
|
||||
strings pscapture.exe | grep -c PSCapture
|
||||
```
|
||||
|
||||
Avvia `pscapture.exe`: si apre la finestra (prompt UAC → Sì) e il pannello log
|
||||
mostra la sequenza controllo driver → estrazione payload → tentativo di
|
||||
deploy. È la stessa sequenza descritta in `docs/INSTALLER.md`.
|
||||
|
||||
### 2.4 Trappole (imparate sul campo)
|
||||
|
||||
- **`-static -static-libgcc` sulla DLL non è cosmetico.** Senza, il driver
|
||||
può ritrovarsi a dipendere dalle DLL runtime di mingw che *non* sono nel
|
||||
PATH di sistema quando Windows (o un'altra app) lo carica — il classico
|
||||
*"impossibile trovare il modulo specificato"*.
|
||||
- **Ordine di build.** `windres` legge `pscapture.dll` e `pscapture.elf` da
|
||||
`pc/` al momento della compilazione. Ricompili la DLL → rifai `windres` →
|
||||
ricompili l'EXE, ogni volta. Dimenticare il passo centrale spedisce il
|
||||
driver vecchio dentro l'exe nuovo.
|
||||
- **Solo ASCII** nelle stringhe passate alle API Win32 `*A` (titoli, bottoni,
|
||||
log). Byte UTF-8 in una stringa ANSI si vedono come caratteri rotti.
|
||||
- Il **manifest** (`pc/pscapture.manifest`) è tenuto solo per riferimento;
|
||||
la build attuale **non** lo incastra — l'auto-elevazione è fatta nel codice
|
||||
(`ShellExecuteEx … "runas"`), cosa che evita il `.rsrc merge failure` di
|
||||
mingw con più manifest.
|
||||
|
||||
### 2.5 Preparare una release
|
||||
|
||||
```bash
|
||||
mkdir -p dist
|
||||
cp pc/pscapture.exe pc/pscapture.dll dist/
|
||||
```
|
||||
|
||||
Per gli utenti basta `pscapture.exe` da solo (la DLL e il payload viaggiano
|
||||
dentro). La `pscapture.dll` sciolta si allega per le installazioni manuali:
|
||||
|
||||
```bash
|
||||
rundll32 "pscapture.dll",DllRegisterServer
|
||||
```
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
# PSCapture — build from source, step by step
|
||||
|
||||
🇮🇹 Versione italiana: [BUILD.it.md](BUILD.it.md)
|
||||
|
||||
Everything needed to go from these sources to a working `PSCapture.exe` and a
|
||||
`pscapture.elf` for the console. Every command is written exactly as it is
|
||||
run, in order. Nothing here is optional magic — the order matters and each
|
||||
step is explained.
|
||||
|
||||
There are two halves, built in different places:
|
||||
|
||||
| Half | Output | Where it builds | Toolchain |
|
||||
|---|---|---|---|
|
||||
| Console payload | `payload/pscapture.elf` | Docker (Linux container) | PS5 payload SDK (prospero-clang) |
|
||||
| PC side | `pc/pscapture.exe` + `pc/pscapture.dll` | Windows | MinGW-w64 (`gcc` + `windres`) |
|
||||
|
||||
---
|
||||
|
||||
## Part 1 — The console payload
|
||||
|
||||
### 1.1 What you need
|
||||
|
||||
- **Docker** (Docker Desktop on Windows, or plain Docker on Linux/macOS).
|
||||
- **The PS5 payload SDK container image.** This is the community PS5 payload
|
||||
SDK (`prospero-clang`, `make`, `PS5_PAYLOAD_SDK` set in the environment)
|
||||
baked into an image. On the development machine the image is tagged
|
||||
`ps5sdk:18` — any name works, adjust the commands below to match yours.
|
||||
|
||||
Check the image is there:
|
||||
|
||||
```bash
|
||||
docker images | grep ps5sdk
|
||||
```
|
||||
|
||||
### 1.2 Build
|
||||
|
||||
From the repository root:
|
||||
|
||||
```bash
|
||||
docker run --rm -v "$PWD/payload:/payload" -w /payload ps5sdk:18 make
|
||||
```
|
||||
|
||||
On Windows PowerShell/cmd, give the full path instead of `"$PWD/payload"`,
|
||||
e.g. `-v "C:\Users\you\PSCapture\payload:/payload"`.
|
||||
|
||||
Result: **`payload/pscapture.elf`** (~175 KB).
|
||||
|
||||
The `Makefile` does the only thing that matters: compile `main.c` with the
|
||||
Prospero clang from the SDK and link the final ELF. No other inputs.
|
||||
|
||||
### 1.3 Optional — send it to the console
|
||||
|
||||
With the console jailbroken (payload loader on port 9021):
|
||||
|
||||
```bash
|
||||
docker run --rm -v "$PWD/payload:/payload" -w /payload ps5sdk:18 \
|
||||
make deploy PS5_HOST=192.168.0.36
|
||||
```
|
||||
|
||||
(or just push `pscapture.elf` with whatever loader you normally use).
|
||||
|
||||
What a healthy run looks like on the console:
|
||||
|
||||
- `/data/pscapture.log` appears and logs the startup,
|
||||
- `/data/pscapture.port` appears containing the TCP port of the stream,
|
||||
- the console pops the notification *PSCapture is streaming on:PORT*.
|
||||
|
||||
---
|
||||
|
||||
## Part 2 — The PC side
|
||||
|
||||
### 2.1 What you need (Windows)
|
||||
|
||||
**MinGW-w64 64-bit** with `gcc` and `windres`. On the development machine:
|
||||
|
||||
```powershell
|
||||
winget install BrechtSanders.WinLibs.POSIX.UCRT
|
||||
```
|
||||
|
||||
(or any MSYS2 `mingw-w64-x86_64-gcc` install — `pacman -S mingw-w64-x86_64-gcc`).
|
||||
|
||||
Check both tools are on PATH:
|
||||
|
||||
```bash
|
||||
gcc --version
|
||||
windres --version
|
||||
```
|
||||
|
||||
### 2.2 Build — the order is mandatory
|
||||
|
||||
`pscapture.res` is a bundle that **embeds files as they are on disk**: the
|
||||
icon, the console payload and the capture-card driver. So the DLL must be
|
||||
built *before* the resource step, and the payload must be sitting in `pc/`
|
||||
when `windres` runs. Build the EXE last.
|
||||
|
||||
```bash
|
||||
cd pc
|
||||
|
||||
# 0) bring the freshly built payload next to the .rc (it gets embedded as-is)
|
||||
cp ../payload/pscapture.elf .
|
||||
|
||||
# 1) the capture-card driver (DirectShow source filter)
|
||||
gcc -O2 -shared -static -static-libgcc -o pscapture.dll ps5cam.c \
|
||||
-lole32 -loleaut32 -luser32 -ladvapi32 -lws2_32
|
||||
|
||||
# 2) bundle icon + payload + driver into one resource object
|
||||
# (pscapture.rc => 1 ICON pscapture.ico / 2 RCDATA pscapture.elf
|
||||
# / 3 RCDATA pscapture.dll)
|
||||
windres pscapture.rc -O coff -o pscapture.res
|
||||
|
||||
# 3) the app — one self-contained executable
|
||||
gcc -O2 -mwindows -o pscapture.exe pscapture.c pscapture.res \
|
||||
-lws2_32 -lshell32 -ladvapi32 -luxtheme
|
||||
```
|
||||
|
||||
### 2.3 Verify the build
|
||||
|
||||
```bash
|
||||
# the driver must depend on system DLLs ONLY (no libgcc/libwinpthread):
|
||||
objdump -p pscapture.dll | grep "DLL Name"
|
||||
|
||||
# sizes look right: exe ~500 KB, dll ~220 KB
|
||||
ls -la pscapture.exe pscapture.dll
|
||||
|
||||
# both the payload and the driver are really inside the exe:
|
||||
strings pscapture.exe | grep -c PSCapture
|
||||
```
|
||||
|
||||
Run `pscapture.exe`: the window opens (UAC prompt → Yes), the log pane walks
|
||||
through driver check → payload extraction → deploy attempt. That is the same
|
||||
sequence described in `docs/INSTALLER.md`.
|
||||
|
||||
### 2.4 Gotchas (learned the hard way)
|
||||
|
||||
- **`-static -static-libgcc` on the DLL is not cosmetic.** Without it the
|
||||
driver can end up needing mingw runtime DLLs that are *not* on the system
|
||||
PATH when Windows (or another app) loads it — the classic
|
||||
*"the specified module could not be found"*.
|
||||
- **Build order.** `windres` reads `pscapture.dll` and `pscapture.elf` from
|
||||
`pc/` at compile time. Rebuild the DLL → re-run `windres` → rebuild the
|
||||
EXE, every time. Forgetting the middle step ships an old driver inside the
|
||||
new exe.
|
||||
- **ASCII only** in strings passed to the `*A` Win32 APIs (window titles,
|
||||
buttons, logs). UTF-8 bytes in an ANSI string render as mojibake.
|
||||
- The **manifest** (`pc/pscapture.manifest`) is kept for reference only;
|
||||
the current build does **not** embed it — self-elevation is done in code
|
||||
(`ShellExecuteEx … "runas"`), which avoids mingw's `.rsrc merge failure`
|
||||
with multiple manifests.
|
||||
|
||||
### 2.5 Package a release
|
||||
|
||||
```bash
|
||||
mkdir -p dist
|
||||
cp pc/pscapture.exe pc/pscapture.dll dist/
|
||||
```
|
||||
|
||||
`pscapture.exe` alone is enough for users (the DLL and the payload travel
|
||||
inside it). The bare `pscapture.dll` is attached for manual installs:
|
||||
|
||||
```bash
|
||||
rundll32 "pscapture.dll",DllRegisterServer
|
||||
```
|
||||
@@ -0,0 +1,233 @@
|
||||
# PSCapture — Engineering log: problems, diagnoses, work-arounds
|
||||
|
||||
This is the long story: every wall we hit while building PSCapture, how it
|
||||
was diagnosed, and what finally fixed (or bypassed) it. Dead ends are kept on
|
||||
purpose — they cost real time and shouldn't cost yours.
|
||||
|
||||
*(Testbed: retail PS5, firmware 13.60, jailbroken; PC on the same LAN.
|
||||
All work done on personally owned hardware.)*
|
||||
|
||||
---
|
||||
|
||||
## Part A — Getting pixels out of the console
|
||||
|
||||
### A1. The official capture stack is a dead end
|
||||
**Symptoms / approach.** First attempts used the console services that look
|
||||
like capture APIs: `libSceAvcap2` / `libSceVideoRecording` etc.
|
||||
**What we found.** `libSceVideoRecording` opens a session backed by a **128 MB
|
||||
file** and talks to its client over internal IPC; calls from an unprivileged
|
||||
payload fail with the service unavailable. The IPMI (inter-process messaging)
|
||||
client table is limited, and every live instance of our payload holds a
|
||||
session — with ~26 stale instances, a new payload waits **forever**
|
||||
(`connect -> 0x8002000d`).
|
||||
**Resolution.** Abandoned the service path entirely. The work-around that
|
||||
later became permanent: (1) never rely on the recording service; (2) payloads
|
||||
kill their own stale instances at boot ("v17 hygiene"), fingerprinting them by
|
||||
*their* fixed cacheable mapping in the page tables (PML4 entry #128 — no
|
||||
system process uses it).
|
||||
|
||||
### A2. The direct path: the display engine's scan-out ring
|
||||
**Diagnosis.** The AV-control daemon (`SceSysAvControl.elf`) exposes the
|
||||
scan-out geometry: ring at `0x200200000`, data at `+0x4200000`, four surfaces
|
||||
`0x600000` apart, rewritten at 60 Hz, stored as P010 (10-bit).
|
||||
**First bug — P010 decoding.** Extracting the *even* bytes gave noise: the
|
||||
usable byte of a 10-bit sample is the **odd** one. Lesson recorded in
|
||||
`REVERSE_ENGINEERING.md` so nobody re-makes it.
|
||||
|
||||
### A3. The rotating image (the pan)
|
||||
**Symptom.** The picture "rotated" and slid horizontally forever; reading the
|
||||
four surfaces in alternation made it jump every frame.
|
||||
**Diagnosis.** The scan-out base drifts within the ring: the frame does not
|
||||
start at a fixed offset. The surface content is also quasi-periodic, which
|
||||
made the first metric find a **twin minimum** 1920 bytes away and oscillate
|
||||
between the two.
|
||||
**Dead ends (documented so you skip them):**
|
||||
- a "junction" continuity metric that needed a `+960` fudge — produced
|
||||
oscillation between twins;
|
||||
- chasing the drift with a per-frame tracker — the pursuit itself caused
|
||||
visible horizontal wiggling.
|
||||
**Final metric** ("exact cut"): strip `[row r, 0..maxj]` must match
|
||||
`[row r-1, c..]`, minimizing `Σ |Y[r][j*2+1] − Y[r-1][(c+j)*2+1]|` over
|
||||
sampled rows; conversion `pan = ((1920 − c) % 1920) * 2`.
|
||||
**Resolution.** Field-verified fixed `pan = 2230` (the drift is ~2 B/s,
|
||||
below visibility), and later a **user-tunable fine shift** on the PC side
|
||||
(even pixels only, see D2).
|
||||
|
||||
### A4. The color saga (or: "v10 colors were right")
|
||||
**Symptom.** Backgrounds looked red/amber instead of blue; users kept saying
|
||||
"too red", "now too gray", "the colors of v10 were right".
|
||||
**Diagnosis path.** The chroma plane does **not** start after the luma:
|
||||
it begins at byte `0x3EA300` (= 4 104 960), 11 rows *before* the presumed
|
||||
boundary, with dead zones before and after (the "green bar" of legend was the
|
||||
tail read into the gap → replicate the last good row).
|
||||
But position alone wasn't the whole story. Comparing a live frame **byte for
|
||||
byte** against an old reference frame of the same screen showed the output
|
||||
matched only with **U/V pairs swapped**: in this plane the 16-bit words are
|
||||
stored **V-first**. Mean absolute error dropped 15.5 → 1.7 → 0.06 once the
|
||||
pair order was corrected in the P010→NV12 conversion (a second shuffle mask
|
||||
for the chroma region only).
|
||||
**Honest note.** Before that discovery, the wrong knob was shipped: a chroma
|
||||
**gain** to "lower the red" — it made the cast less visible but never right.
|
||||
The gain knob survives as an optional tuning, default 1.0 (= raw).
|
||||
**How the truth was found.** Three independent anchors: neutral white stays
|
||||
(128,128) either way, the bluish background flips the right way only with the
|
||||
swap, and the byte-level diff against the reference settles it numerically.
|
||||
Rendering the *same bytes* with two different YUV formulas long ago had also
|
||||
caused a wild goose chase — the rule became: **compare numerically, never
|
||||
eye-render across periods** (see Lessons).
|
||||
|
||||
### A5. Kernel reads without crashing the console (three crashes, one rule)
|
||||
**Crash #1/#2.** Full console lock-ups (all TCP ports mute, ICMP half-dead)
|
||||
after storms of live payload instances + IPMI maintenance traffic. Recovered
|
||||
only by physical power cycle. Root cause never *proven*; the permanent
|
||||
hygiene (single instance, kill-istanze at boot) removed the whole class.
|
||||
**Crash #3 (diagnosed exactly).** A kernel-dump experiment read kernel VA
|
||||
through `kernel_copyout` — a fault there is a **kernel-mode fault**: instant
|
||||
panic. **Rule, now absolute:** kernel memory is only ever read via a page
|
||||
table walk with present-bit validation + 8-byte reads through the direct map;
|
||||
`kernel_copyout` on kernel addresses is banned.
|
||||
**Related guard bug (fun one).** The cacheable-mapping builder refused
|
||||
physical addresses above 16 GB as "suspicious" — but the display surfaces
|
||||
live at `0x459200000` (≈18.7 GB). Result: silent fallback to the slow path
|
||||
(45 MB/s, 6 fps). Fix: raise the guard to 32 GB; the real safety net is the
|
||||
page-table validation, not the numeric limit.
|
||||
|
||||
### A6. Speed: from 45 MB/s to 4 GB/s
|
||||
**Diagnosis.** Reading the ring through syscall copies serialized inside the
|
||||
process (multi-threading didn't help — the copies bottleneck in the kernel
|
||||
path).
|
||||
**Fix.** Build a **cacheable mapping** of the DCE physical range inside the
|
||||
payload process: allocate our own PDPT/PD pages, replicate the daemon's PDE
|
||||
flags (write-back, no PCD/PWT), write **one** PML4 entry, done. Bulk frame
|
||||
reads went to ~4 GB/s.
|
||||
**Tearing.** The DCE rewrites a surface at 60 Hz; a read during a rewrite
|
||||
yields a mixed frame (ghosts, colored tails). Fix: read, wait 2 ms, compare;
|
||||
if the surface changed it was alive — move to the next one.
|
||||
|
||||
---
|
||||
|
||||
## Part B — The PC side: from frames to a "webcam"
|
||||
|
||||
### B1. Pipeline shape
|
||||
Feeder (TCP receiver) → shared memory ring → **DirectShow source filter**
|
||||
(`ps5cam.c` → `pscapture.dll`) that every consumer sees as a camera called
|
||||
**PS5 Capture**.
|
||||
|
||||
### B2. Discord error 2014 (spinner → no camera), root cause found in their binary
|
||||
**Symptoms.** Camera selectable, infinite spinner, then Discord error 2014 —
|
||||
with *any* virtual camera (ours and OBS Virtual Camera alike).
|
||||
**Diagnosis.** Discord's own media logs contained the smoking gun:
|
||||
`Failed to create capturer (video_capture_module_source.cpp:105)`. Reading
|
||||
their capture engine (WebRTC's DShow path inside the Discord binary):
|
||||
the engine's device **picker requests 1280×720@30** and only accepts a device
|
||||
that lists a *matching* capability. Ours offered only 1920×1080 → no match →
|
||||
capturer never created. OBS Virtual Camera exposed zero caps → same fate.
|
||||
**Fix — "real webcam" capability profile.** The driver now exposes 11 UVC-like
|
||||
capabilities (YUY2/NV12/I420/RGB24/RGB32 at 1080p/720p/480p, negative-height
|
||||
variants first), plus a `DevicePath` string in the registry (the modern
|
||||
identifier consumers use instead of the friendly name). Verified in their
|
||||
logs: `Capturer running at 1280x720@30`, first frame in 10–13 ms.
|
||||
**Tooling discoveries that made this tractable:**
|
||||
- Discord can be driven with `--remote-debugging-port`; from it the **media
|
||||
engine is scriptable** (device selection, camera on/off) without touching
|
||||
the UI — see `docs/` history and our notes.
|
||||
- The binary is searchable for log strings (`(file:line)` hints) and
|
||||
RIP-relative xrefs; that's how the picker logic was pinned down.
|
||||
|
||||
### B3. Flips and orientations
|
||||
**Symptom.** 720p output upside-down for one consumer but not another.
|
||||
**Diagnosis.** DIB convention sign vs YUV conventions differ between
|
||||
consumers: ffmpeg/WebRTC treat YUV as top-down regardless of `biHeight` sign,
|
||||
but XRGB-style DIBs as bottom-up.
|
||||
**Fix.** Flip **only** for RGB subtypes when `biHeight > 0`; all YUV paths
|
||||
stay top-down. Verified with ffmpeg grabs.
|
||||
|
||||
### B4. Alignment ("more space on the left than on the right")
|
||||
**Symptom.** The image sat a few dozen pixels off; users described margins,
|
||||
not offsets.
|
||||
**Diagnosis.** Measured **inside** the frame: the centered title text of the
|
||||
screen had its glyph center at x≈993 (960 = screen center) → the capture was
|
||||
~33 px to the right; button margins agreed (~29 px, arrow ~18 px — the title
|
||||
is the cleanest anchor).
|
||||
**Fix.** A **cyclic horizontal shift** in the feeder (whole luma plane and UV
|
||||
plane, same byte count; **even** pixels only — an odd shift re-labels U/V
|
||||
pairs and hue-inverts), exposed as the live `shift px` knob.
|
||||
**Long-term.** Bake the value into the payload's pan and drop the PC-side
|
||||
shift (planned).
|
||||
|
||||
### B5. The 1080p question (and what Discord really does)
|
||||
Users compared with capture cards: "Elgatos stream at 1080p!".
|
||||
**Findings.** (1) The capture path in Discord calls is capped by *Discord's
|
||||
engine* (it asks every camera for 720p — capture cards included); (2) as a
|
||||
generic DirectShow source, PSCapture **already delivers 1920×1080** to OBS and
|
||||
ffmpeg (verified with an ffmpeg 1080p grab while Discord was live) and that's
|
||||
the road for 1080p streaming; (3) 1080p *inside* a Discord call via screen
|
||||
share is possible up to 1080p60 with Nitro.
|
||||
|
||||
---
|
||||
|
||||
### B6. The installer crash-course: a cryptic module error and a lying log
|
||||
**Symptom.** Pressing *Install driver* showed *"the specified module could not
|
||||
be found"* - the classic rundll32 message, with no other clue anywhere.
|
||||
**Diagnosis.** Two faults stacked: when the app was not elevated, writing
|
||||
`pscapture.dll` into `%ProgramFiles%` failed silently - the old code logged
|
||||
the extraction as successful anyway - and then `rundll32` was asked to load a
|
||||
file that did not exist (hence the generic, useless message). On top of that,
|
||||
`rundll32` never reports what went wrong back to the caller.
|
||||
**Fix.** Every filesystem step is now verified (write -> check size ->
|
||||
report), the install aborts honestly when the dll cannot be placed, and
|
||||
registration happens in-process (`LoadLibrary` + `DllRegisterServer`) so
|
||||
failures come back as real HRESULTs. The driver check also verifies that the
|
||||
registered dll still *exists* on disk before declaring victory.
|
||||
|
||||
### B7. The modern control center UI
|
||||
The first GUI iteration was a plain Win32 dialog. It was rebuilt as a dark
|
||||
custom-chrome window: painted title bar with integrated minimize/close,
|
||||
rounded window region, owner-drawn buttons, a live status pill with a state
|
||||
dot, and a dark monospace console pane. Everything is still pure C with zero
|
||||
frameworks - the UI is drawn by hand.
|
||||
|
||||
## Part C — The road to 60 fps
|
||||
|
||||
**The math.** Raw NV12 1080p is 3.11 MB/frame → 30 fps ≈ 93 MB/s (a gigabit
|
||||
LAN is already at the wall at ~110 MB/s); 60 fps ≈ 186 MB/s → needs 2.5 GbE
|
||||
*or* compression.
|
||||
|
||||
**Raw path (2.5 GbE adapter on the console).** The user's adapter is a TP-Link
|
||||
UE302C (Realtek RTL8156B). Findings: the console kernel is **FreeBSD 9.0**
|
||||
based; the matching driver (`ure`) appears in FreeBSD 13+, and Sony ships only
|
||||
the drivers it uses — so nothing will recognize the adapter out of the box.
|
||||
Two enablers were secured anyway: the **PLD payload format** and the kernel
|
||||
payload loader on port **9019** (how the scene's kernel payloads ship), plus
|
||||
the kernel offsets table for 13.60 — enough to *deliver* a custom kernel
|
||||
module when the port is ready. (Full port = a separate workstream; the
|
||||
homework is in `REVERSE_ENGINEERING.md` §4.)
|
||||
|
||||
**Compressed path (stays on gigabit).** The console has a hardware JPEG
|
||||
encoder (`libSceJpegEnc`). At ~250-350 KB/frame, **60 fps ≈ 15-21 MB/s** —
|
||||
five times inside a gigabit. The API surface was recovered from the library
|
||||
itself (exports, NIDs, argument structs, validation rules — see the notes in
|
||||
`REVERSE_ENGINEERING.md`), and a measuring payload is ready to run on the
|
||||
console (each call in a forked child with timeout — Sony APIs abort on wrong
|
||||
arguments, so a bad call must die alone).
|
||||
|
||||
---
|
||||
|
||||
## Lessons (the rules we now follow)
|
||||
|
||||
1. **Never** hand a kernel address to `kernel_copyout` — page-walk + present
|
||||
check + direct-map reads, always.
|
||||
2. One live payload instance. Kill stale ones at boot; never accumulate.
|
||||
3. Measure with bytes, not eyes: same formula, same reference frame, signed
|
||||
deltas. Eye-comparison across renderers lies.
|
||||
4. Even-pixel shifts only (U/V pairing), for the same reason.
|
||||
5. Never call `sceAvControlChangeScanInMode` from a payload (it can wedge the
|
||||
display engine).
|
||||
6. When an API can abort the host on bad arguments, isolate the call in a
|
||||
child process with a timeout.
|
||||
7. When a suspicious "fix" targets a symptom (the chroma gain), keep looking
|
||||
for the *identity* of the problem (the U/V swap).
|
||||
|
||||
8. **Never log success without checking the I/O result.** A lying log (the
|
||||
installer claiming "extracted ok" on a failed write) costs far more time
|
||||
than a missing one - it sends you hunting in the wrong place.
|
||||
@@ -0,0 +1,116 @@
|
||||
# PSCapture — la guida passo-passo completa
|
||||
|
||||
Da zero alla tua PS5 dentro Discord. Ogni passo dice esattamente cosa devi
|
||||
vedere sullo schermo. Se qualcosa non torna, salta alla tabella dei problemi
|
||||
in fondo.
|
||||
|
||||
🇬🇧 English version: [GUIDE.md](GUIDE.md)
|
||||
|
||||
---
|
||||
|
||||
## 0. Cosa serve
|
||||
|
||||
**Sulla console** — tutto standard nei setup jailbreak di oggi:
|
||||
|
||||
| Cosa | Porta | A che serve |
|
||||
|---|---|---|
|
||||
| Payload loader (tipo `elfldr`) | TCP **9021** | esegue il payload che gli mandiamo |
|
||||
| FTP anonimo | TCP **2121** | l'app legge/scrive file sulla console |
|
||||
| `pldmgr` autoloader (opzionale) | — | fa partire il payload a ogni boot |
|
||||
|
||||
**Sul PC**: Windows 10/11 64-bit, rete cablata consigliata.
|
||||
|
||||
**L'app**: `PSCapture.exe` — **un solo file**. Scaricalo dalla pagina Releases
|
||||
della repo. È il pacchetto completo: il payload per la console e il driver
|
||||
per Windows sono dentro di lui.
|
||||
|
||||
---
|
||||
|
||||
## 1. Primo avvio sul PC
|
||||
|
||||
1. Metti `PSCapture.exe` dove vuoi (va benissimo il Desktop) e **fai doppio click**.
|
||||
|
||||
2. Windows chiede *"Consenti a questa app di apportare modifiche al
|
||||
dispositivo?"* → clicca **Sì**.
|
||||
*Perché:* registrare la webcam virtuale per tutti i programmi del PC
|
||||
richiede i privilegi di amministratore, una volta sola. L'app si eleva da
|
||||
sola — niente tasto destro.
|
||||
|
||||
3. Si apre la finestra (scura, con il bottone blu grande **START STREAMING**).
|
||||
Se la tua PS5 non è a `192.168.0.36`: scrivi il suo indirizzo nel campo
|
||||
**PS5 IP** e premi **Apply**.
|
||||
|
||||
4. Da qui in poi l'app fa tutto da sola. Guarda il pannello log scuro:
|
||||
|
||||
| Dovresti leggere | Significa |
|
||||
|---|---|
|
||||
| `driver gia' installato` | la camera è già registrata — ok |
|
||||
| `driver estratto dall'eseguibile (… byte)` + `driver registrato - la webcam 'PS5 Capture' e' pronta` | primo avvio: driver installato |
|
||||
| `payload dall'eseguibile (… byte)` + `payload avviato - la PS5 deve mostrare la notifica PSCapture` | il payload sta girando sulla console |
|
||||
| `porta stream: 5599` poi `collegato a 192.168.0.36:5599` | stream connesso |
|
||||
| la pillola diventa **verde**: `STREAMING - scegli 'PS5 Capture' in Discord` | sei live |
|
||||
|
||||
5. Sulla PS5 compare la notifica di sistema: **PSCapture is streaming on:PORT**.
|
||||
(Quel toast è la console che ti dice che il payload è vivo.)
|
||||
|
||||
> Se la pillola resta su *"IN ATTESA DELLA PS5 — accesa e col jailbreak
|
||||
> avviato?"* la console è spenta, non è jailbroken, o l'IP è sbagliato.
|
||||
> Vedi la sezione 5.
|
||||
|
||||
## 2. Usarlo
|
||||
|
||||
**Discord (in chiamata)** — *Impostazioni → Voce e video → Telecamera* →
|
||||
**PS5 Capture**. In chiamata l'immagine è 720p: è il motore di Discord, chiede
|
||||
1280x720 a *tutte* le camere (capture card incluse).
|
||||
|
||||
**OBS** — *Sorgenti → **+** → Video Capture Device* → **PS5 Capture**.
|
||||
Qui va alla piena risoluzione **1920x1080**.
|
||||
|
||||
**ffmpeg** — `ffmpeg -f dshow -i video="PS5 Capture" registrazione.mkv`
|
||||
|
||||
## 3. Uso quotidiano
|
||||
|
||||
- Apri `PSCapture.exe` → **Sì** all'UAC. L'app si ricollega da sola: se il
|
||||
payload è già sulla console (autoloader) lo stream parte subito; se la
|
||||
console è spenta la pillola dice *"IN ATTESA DELLA PS5"* e si connette nel
|
||||
momento in cui la console è pronta.
|
||||
- Premi **STOP** (o chiudi l'app) per fermare lo stream. La camera si congela
|
||||
sull'ultimo frame; gli altri programmi continuano a vedere il dispositivo.
|
||||
- **Deploy payload** rimanda il payload a mano (utile dopo un riavvio della
|
||||
console senza autoloader). **Install driver** riregistra la camera a mano
|
||||
(utile se il log mostra un errore del driver).
|
||||
|
||||
## 4. Le due manopole
|
||||
|
||||
| Manopola | Cosa fa | Regole |
|
||||
|---|---|---|
|
||||
| `gain` | forza del colore (croma) | `1.0` = grezzo, è il valore giusto. Toccala solo se i colori sembrano sbagliati. |
|
||||
| `shift px` | sposta l'immagine a destra/sinistra | **solo numeri PARI** (un numero dispari inverte i colori). Regola finché l'immagine non è centrata: il testo del titolo della console è un buon riferimento. |
|
||||
|
||||
Entrambe si applicano in diretta (circa un secondo) e sono salvate in
|
||||
`pscapture.ini` accanto all'exe, quindi sopravvivono ai riavvii.
|
||||
|
||||
## 5. Problemi — sintomo per sintomo
|
||||
|
||||
| Sintomo | Causa | Rimedio |
|
||||
|---|---|---|
|
||||
| La pillola resta su `AVVIO...` / `IN ATTESA DELLA PS5` | console spenta, **non jailbroken**, o IP sbagliato | accendi la console, avvia il jailbreak (le porte 9021/2121 devono essere aperte), controlla il campo IP |
|
||||
| Il log ripete `PS5 non raggiungibile (...), riprovo` | come sopra | come sopra |
|
||||
| La camera `PS5 Capture` non appare in Discord/OBS | driver mai registrato (install fallito) | premi **Install driver**; leggi la riga di log che segue — ora riporta l'errore *vero* |
|
||||
| Log: `ERRORE: driver non scrivibile in C:\Program Files\PSCapture (serve admin?)` | app non elevata | chiudi e riavvia `PSCapture.exe` accettando il prompt UAC |
|
||||
| Log: `ERRORE caricamento dll (err=126)` | l'antivirus ha messo in quarantena la dll estratta | controlla la quarantena dell'antivirus per `pscapture.dll`, sbloccala, premi di nuovo **Install driver** |
|
||||
| La camera appare ma l'immagine è nera | stream non partito | controlla la pillola; premi **Deploy payload** e/o **START STREAMING** |
|
||||
| I colori sembrano sbagliati (pelle rossastra, cielo arancione) | qualcuno ha toccato `gain` | rimetti `gain` a `1.0` → Apply |
|
||||
| Immagine decentrata / barra nera | allineamento | regola `shift px` con numeri pari finché non è centrata |
|
||||
| Discord continua a mostrare 720p | il motore di Discord limita ogni camera a 720p | usa OBS per il 1080p, o la condivisione schermo di Discord (1080p60 con Nitro) |
|
||||
| OBS vede la camera, Discord no | Discord era già aperto quando hai installato il driver | chiudi Discord COMPLETAMENTE (anche dalla tray) e riaprilo |
|
||||
| Tutto il resto | — | leggi `pscapture-app.log` (accanto all'exe, o in `%TEMP%`) e il log della console `/data/pscapture.log` (via FTP) |
|
||||
|
||||
## 6. Disinstallazione
|
||||
|
||||
1. Chiudi l'app.
|
||||
2. `rundll32 "%ProgramFiles%\PSCapture\pscapture.dll",DllUnregisterServer`
|
||||
(rimuove la registrazione della camera).
|
||||
3. Cancella `C:\Program Files\PSCapture`.
|
||||
4. Opzionale, sulla console: cancella `/data/pldmgr/payloads/pscapture/` così
|
||||
l'autoloader smette di avviarlo.
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
# PSCapture — the complete step-by-step guide
|
||||
|
||||
🇮🇹 Versione italiana: [GUIDE.it.md](GUIDE.it.md)
|
||||
|
||||
From zero to your PS5 inside Discord. Every step says exactly what you should
|
||||
see on screen. If something doesn't match, jump to the troubleshooting table
|
||||
at the bottom.
|
||||
|
||||
---
|
||||
|
||||
## 0. What you need
|
||||
|
||||
**On the console** — all standard in current jailbreak setups:
|
||||
|
||||
| Thing | Port | Why |
|
||||
|---|---|---|
|
||||
| Payload loader (`elfldr`-style) | TCP **9021** | runs the payload we send |
|
||||
| Anonymous FTP | TCP **2121** | the app reads/writes files on the console |
|
||||
| `pldmgr` autoloader (optional) | — | makes the payload start at every boot |
|
||||
|
||||
**On the PC**: Windows 10/11 64-bit, wired network recommended.
|
||||
|
||||
**The app**: `PSCapture.exe` — **one single file**. Download it from the
|
||||
repository's Releases page. That's the whole package: the console payload and
|
||||
the Windows capture-card driver are inside it.
|
||||
|
||||
---
|
||||
|
||||
## 1. First run on the PC
|
||||
|
||||
1. Put `PSCapture.exe` anywhere (the Desktop is fine) and **double-click it**.
|
||||
|
||||
2. Windows asks *"Do you want to allow this app to make changes to your
|
||||
device?"* → click **Yes**.
|
||||
*Why:* registering the virtual camera for every app on the machine needs
|
||||
administrator rights, once. The app elevates itself — no right-click needed.
|
||||
|
||||
3. The window opens (dark, with a big blue **START STREAMING** button).
|
||||
If your PS5 is not at `192.168.0.36`: type its address in the **PS5 IP**
|
||||
field and press **Apply**.
|
||||
|
||||
4. From here the app does everything alone. Watch the dark log pane:
|
||||
|
||||
| You should read | Meaning |
|
||||
|---|---|
|
||||
| `driver gia' installato` | camera already registered — fine |
|
||||
| `driver estratto dall'eseguibile (… byte)` + `driver registrato - la webcam 'PS5 Capture' e' pronta` | first run: driver installed |
|
||||
| `payload dall'eseguibile (… byte)` + `payload avviato - la PS5 deve mostrare la notifica PSCapture` | the payload is now running on the console |
|
||||
| `porta stream: 5599` then `collegato a 192.168.0.36:5599` | stream connected |
|
||||
| status pill turns **green**: `STREAMING - scegli 'PS5 Capture' in Discord` | you're live |
|
||||
|
||||
5. The PS5 shows a system notification: **PSCapture is streaming on:PORT**.
|
||||
(That toast is the console telling you the payload is alive.)
|
||||
|
||||
> If the pill stays on *"IN ATTESA DELLA PS5 — accesa e col jailbreak avviato?"*
|
||||
> the console is off, not jailbroken, or the IP is wrong. See section 5.
|
||||
|
||||
## 2. Use it
|
||||
|
||||
**Discord (in a call)** — *Settings → Voice & Video → Camera* → **PS5 Capture**.
|
||||
The in-call image is 720p: that's Discord's engine, it asks *every* camera
|
||||
(capture cards included) for 1280x720.
|
||||
|
||||
**OBS** — *Sources → **+** → Video Capture Device* → **PS5 Capture**.
|
||||
This runs at the full **1920x1080**.
|
||||
|
||||
**ffmpeg** — `ffmpeg -f dshow -i video="PS5 Capture" capture.mkv`
|
||||
|
||||
## 3. Daily use
|
||||
|
||||
- Open `PSCapture.exe` → **Yes** to the admin prompt. The app reconnects by
|
||||
itself: if the payload is already on the console (autoloader) the stream
|
||||
starts by itself; if the console is off the pill says
|
||||
*"IN ATTESA DELLA PS5"* and connects the moment the console is ready.
|
||||
- Press **STOP** (or close the app) to stop streaming. The camera goes black /
|
||||
freezes on the last frame; consumers keep the device listed.
|
||||
- **Deploy payload** re-sends the payload by hand (useful after a console
|
||||
reboot without autoloader). **Install driver** re-registers the camera by
|
||||
hand (useful if the log shows a driver error).
|
||||
|
||||
## 4. The two knobs
|
||||
|
||||
| Knob | What it does | Rules |
|
||||
|---|---|---|
|
||||
| `gain` | chroma (color) strength | `1.0` = raw, the correct default. Only touch it if colors look wrong. |
|
||||
| `shift px` | moves the image left/right | **even numbers only** (an odd number inverts the colors). Tune until the picture is centered; the console's title text is a good anchor. |
|
||||
|
||||
Both apply live (about a second) and are saved in `pscapture.ini` next to the
|
||||
exe, so they survive restarts.
|
||||
|
||||
## 5. Troubleshooting — symptom by symptom
|
||||
|
||||
| Symptom | Cause | Fix |
|
||||
|---|---|---|
|
||||
| Pill stays `AVVIO...` / `IN ATTESA DELLA PS5` | console off, **not jailbroken**, or wrong IP | turn the console on, run the jailbreak (ports 9021/2121 must be up), check the IP field |
|
||||
| Log repeats `PS5 non raggiungibile (...)`, riprova | same as above | same |
|
||||
| No `PS5 Capture` camera appears in Discord/OBS | driver never registered (install failed) | press **Install driver**; read the log line after it — it now reports the *real* error |
|
||||
| Log: `ERRORE: driver non scrivibile in C:\Program Files\PSCapture (serve admin?)` | app not elevated | close it, restart `PSCapture.exe` and accept the UAC prompt |
|
||||
| Log: `ERRORE caricamento dll (err=126)` | antivirus quarantined the extracted dll | check the antivirus quarantine for `pscapture.dll`, allow it, press **Install driver** again |
|
||||
| Camera listed but the image is black | stream not running | check the pill; press **Deploy payload** and/or **START STREAMING** |
|
||||
| Colors look wrong (skin reddish, sky orange) | `gain` was changed | set `gain` back to `1.0` → Apply |
|
||||
| Image has a black bar / is off-center | alignment | tune `shift px` (even values) until centered |
|
||||
| Discord keeps showing 720p | Discord's engine caps every camera at 720p | use OBS for 1080p, or Discord screen-share (1080p60 with Nitro) |
|
||||
| OBS shows the camera, Discord doesn't | Discord was already open when the driver was installed | fully quit Discord (also from the tray) and reopen it |
|
||||
| Everything else | — | read `pscapture-app.log` (next to the exe, or in `%TEMP%`) and the console log `/data/pscapture.log` (FTP) |
|
||||
|
||||
## 6. Uninstall
|
||||
|
||||
1. Close the app.
|
||||
2. `rundll32 "%ProgramFiles%\PSCapture\pscapture.dll",DllUnregisterServer`
|
||||
(removes the camera registration).
|
||||
3. Delete `C:\Program Files\PSCapture`.
|
||||
4. Optional, on the console: delete `/data/pldmgr/payloads/pscapture/` so the
|
||||
autoloader stops starting it.
|
||||
+13
-13
@@ -31,23 +31,23 @@ the driver is considered installed and this step is skipped.
|
||||
## Step 2 — Installing the driver (first run only)
|
||||
|
||||
1. Creates `%ProgramFiles%\PSCapture`.
|
||||
2. Copies `pscapture.dll` there — the DirectShow source filter. (The app also
|
||||
copies itself, so the installed folder is self-contained.)
|
||||
3. Registers the filter by running:
|
||||
2. **Extracts `pscapture.dll` from its own resources** (the driver travels
|
||||
embedded inside the executable, resource `RCDATA` n.3) and writes it
|
||||
there. It also copies itself, so the installed folder is self-contained
|
||||
— the distributed installer is literally **one file**.
|
||||
3. Registers the filter by loading the dll into the app itself and calling
|
||||
`DllRegisterServer` directly. *(Earlier builds shelled out to `rundll32`,
|
||||
which turned a missing dll into a cryptic "the specified module could not
|
||||
be found" dialog; the in-process call returns real error codes.)* Every
|
||||
step is checked: if the dll could not be written (not elevated, no
|
||||
rights), the app says so instead of attempting a doomed registration.
|
||||
|
||||
```
|
||||
rundll32.exe "%ProgramFiles%\PSCapture\pscapture.dll",DllRegisterServer
|
||||
```
|
||||
|
||||
`DllRegisterServer` writes the COM registration for the filter CLSID,
|
||||
DllRegisterServer writes the COM registration for the filter CLSID,
|
||||
including the **DevicePath** entry that modern capture consumers
|
||||
(Chromium/Discord media engine among them) use to pick the device.
|
||||
`DllUnregisterServer` removes it — that is the uninstall path today
|
||||
DllUnregisterServer removes it - that is the uninstall path today
|
||||
(delete the folder + call it).
|
||||
|
||||
After this step the system has a camera called **PS5 Capture**. It will show
|
||||
a black frame until a stream arrives — the feeder is what fills it.
|
||||
|
||||
## Step 3 — Deploying the console payload
|
||||
|
||||
The payload (`pscapture.elf`) ships **inside the executable** as a Windows
|
||||
@@ -82,7 +82,7 @@ reconnects automatically the next time you press **Start**.
|
||||
|
||||
| Where | What |
|
||||
|---|---|
|
||||
| `%ProgramFiles%\PSCapture\` | `pscapture.exe`, `pscapture.dll` |
|
||||
| `%ProgramFiles%\PSCapture\` | `pscapture.exe` (self-contained) + the `pscapture.dll` it extracts |
|
||||
| Registry `HKCR\CLSID\{50533543-…-192080102030}` | the capture-card filter registration |
|
||||
| Console `/data/pldmgr/payloads/pscapture/pscapture.elf` | payload (autoloader) |
|
||||
| Console `/data/pscapture.port` | stream port announced by the payload |
|
||||
|
||||
+420
-175
@@ -1,199 +1,444 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" class="no-js">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>PSCapture by fstat13 — PS5 screen as a capture card on PC</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg:#0d1015; --card:#151a22; --card2:#1b222d; --line:#242c38;
|
||||
--fg:#e8edf4; --dim:#96a0b0; --accent:#0070d1; --accent2:#35a3ff;
|
||||
--rec:#e22236; --ok:#1fbf6b;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;background:var(--bg);color:var(--fg);
|
||||
font:16px/1.65 "Segoe UI",system-ui,-apple-system,Roboto,sans-serif;}
|
||||
a{color:var(--accent2);text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
.wrap{max-width:880px;margin:0 auto;padding:0 20px}
|
||||
header{background:
|
||||
radial-gradient(900px 320px at 20% -40%, rgba(0,112,209,.35), transparent 60%),
|
||||
radial-gradient(700px 300px at 90% -30%, rgba(226,34,54,.22), transparent 60%),
|
||||
var(--bg);
|
||||
border-bottom:1px solid var(--line);padding:56px 0 40px}
|
||||
.brand{display:flex;align-items:center;gap:18px}
|
||||
.brand img{width:84px;height:84px;border-radius:20px;box-shadow:0 8px 30px rgba(0,0,0,.5)}
|
||||
h1{font-size:44px;margin:0;letter-spacing:.5px}
|
||||
.tag{color:var(--dim);font-size:18px;margin-top:6px}
|
||||
.badges{margin-top:22px;display:flex;gap:10px;flex-wrap:wrap}
|
||||
.badge{border:1px solid var(--line);background:var(--card);
|
||||
padding:5px 12px;border-radius:999px;font-size:13px;color:var(--dim)}
|
||||
.badge b{color:var(--fg)}
|
||||
h2{margin:52px 0 14px;font-size:26px}
|
||||
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px}
|
||||
.card{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:18px 20px}
|
||||
.card h3{margin:0 0 8px;font-size:17px}
|
||||
.card p{margin:0;color:var(--dim);font-size:14.5px}
|
||||
.flow{display:flex;gap:10px;align-items:stretch;flex-wrap:wrap;margin-top:16px}
|
||||
.box{flex:1;min-width:180px;background:var(--card2);border:1px solid var(--line);
|
||||
border-radius:12px;padding:14px 16px}
|
||||
.box b{display:block;margin-bottom:6px}
|
||||
.box div{color:var(--dim);font-size:13.5px}
|
||||
.arrow{align-self:center;color:var(--accent2);font-size:22px;padding:0 2px}
|
||||
ol.steps{counter-reset:s;list-style:none;padding:0;margin:18px 0 0}
|
||||
ol.steps li{counter-increment:s;position:relative;padding:0 0 18px 54px}
|
||||
ol.steps li::before{content:counter(s);position:absolute;left:0;top:0;
|
||||
width:34px;height:34px;border-radius:10px;background:var(--accent);
|
||||
color:#fff;font-weight:600;display:flex;align-items:center;justify-content:center}
|
||||
ol.steps b{color:var(--fg)}
|
||||
ol.steps span{color:var(--dim)}
|
||||
table{width:100%;border-collapse:collapse;margin-top:14px;font-size:14.5px}
|
||||
th,td{text-align:left;padding:10px 12px;border-bottom:1px solid var(--line)}
|
||||
th{color:var(--dim);font-weight:500}
|
||||
code,kbd{background:#0a0d12;border:1px solid var(--line);border-radius:6px;
|
||||
padding:1px 7px;font:13.5px/1.5 Consolas,monospace;color:#cfe3ff}
|
||||
details{background:var(--card);border:1px solid var(--line);border-radius:12px;
|
||||
padding:14px 18px;margin:10px 0}
|
||||
summary{cursor:pointer;font-weight:600}
|
||||
details p{color:var(--dim);margin:10px 0 0}
|
||||
.note{background:linear-gradient(0deg,rgba(0,112,209,.08),rgba(0,112,209,.08)),
|
||||
var(--card);border:1px solid #23405f;border-radius:12px;padding:14px 18px;color:#cfe3ff}
|
||||
footer{color:var(--dim);border-top:1px solid var(--line);margin-top:70px;
|
||||
padding:26px 0 40px;font-size:13.5px}
|
||||
.dot{display:inline-block;width:9px;height:9px;border-radius:50%;
|
||||
background:var(--rec);margin-right:8px;vertical-align:1px}
|
||||
.ok{color:var(--ok);font-weight:600}
|
||||
</style>
|
||||
<title>PSCapture — your PS5 screen as a capture card on PC</title>
|
||||
<meta name="description" content="PSCapture turns a jailbroken PS5 into a capture card for your PC: the console screen streams over your LAN and shows up in Discord, OBS and ffmpeg as a camera called “PS5 Capture”.">
|
||||
<meta name="theme-color" content="#07090d">
|
||||
<link rel="icon" type="image/png" href="pscapture.png">
|
||||
<link rel="alternate" hreflang="en" href="https://fstat13.github.io/PSCapture/">
|
||||
<link rel="alternate" hreflang="it" href="https://fstat13.github.io/PSCapture/index.it.html">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="PSCapture — your PS5 screen, live on your PC">
|
||||
<meta property="og:description" content="The PS5 screen shows up in Discord, OBS and ffmpeg as the camera “PS5 Capture”. No capture card, no Remote Play, no cloud.">
|
||||
<meta property="og:image" content="https://fstat13.github.io/PSCapture/social-preview.png">
|
||||
<meta property="og:image:width" content="1280">
|
||||
<meta property="og:image:height" content="640">
|
||||
<meta property="og:url" content="https://fstat13.github.io/PSCapture/">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<svg width="0" height="0" style="position:absolute" aria-hidden="true">
|
||||
<symbol id="i-download" viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></symbol>
|
||||
<symbol id="i-star" viewBox="0 0 24 24"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></symbol>
|
||||
<symbol id="i-check" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></symbol>
|
||||
<symbol id="i-video" viewBox="0 0 24 24"><path d="m22 8-6 4 6 4V8Z"/><rect x="2" y="6" width="14" height="12" rx="2"/></symbol>
|
||||
<symbol id="i-zap" viewBox="0 0 24 24"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></symbol>
|
||||
<symbol id="i-file" viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></symbol>
|
||||
<symbol id="i-refresh" viewBox="0 0 24 24"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></symbol>
|
||||
<symbol id="i-sliders" viewBox="0 0 24 24"><line x1="4" y1="21" x2="4" y2="14"/><line x1="4" y1="10" x2="4" y2="3"/><line x1="12" y1="21" x2="12" y2="12"/><line x1="12" y1="8" x2="12" y2="3"/><line x1="20" y1="21" x2="20" y2="16"/><line x1="20" y1="12" x2="20" y2="3"/><line x1="1" y1="14" x2="7" y2="14"/><line x1="9" y1="8" x2="15" y2="8"/><line x1="17" y1="16" x2="23" y2="16"/></symbol>
|
||||
<symbol id="i-lock" viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></symbol>
|
||||
<symbol id="i-console" viewBox="0 0 24 24"><rect x="2" y="6" width="20" height="12" rx="3"/><line x1="6" y1="12" x2="10" y2="12"/><line x1="8" y1="10" x2="8" y2="14"/><circle cx="16" cy="11" r=".6"/><circle cx="18" cy="13" r=".6"/></symbol>
|
||||
<symbol id="i-monitor" viewBox="0 0 24 24"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></symbol>
|
||||
<symbol id="i-info" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></symbol>
|
||||
<symbol id="i-alert" viewBox="0 0 24 24"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></symbol>
|
||||
<symbol id="i-search" viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></symbol>
|
||||
</svg>
|
||||
|
||||
<nav class="nav">
|
||||
<div class="wrap">
|
||||
<div class="brand">
|
||||
<img src="pscapture.png" alt="PSCapture">
|
||||
<div>
|
||||
<h1>PSCapture</h1>
|
||||
<div class="tag">The PlayStation 5 screen, live on your PC — as a real capture card.</div>
|
||||
<a class="logo" href="#top"><img src="pscapture.png" alt="">PSCapture</a>
|
||||
<div class="nav-links">
|
||||
<a href="#how">How it works</a>
|
||||
<a href="#start">Setup</a>
|
||||
<a href="#trouble">Troubleshooting</a>
|
||||
<a href="#build">Build</a>
|
||||
<a href="#faq">FAQ</a>
|
||||
</div>
|
||||
<div class="nav-right">
|
||||
<a class="lang" href="index.it.html" hreflang="it">Italiano</a>
|
||||
<a class="btn btn-ghost btn-sm" href="https://github.com/fstat13/PSCapture"><svg class="i"><use href="#i-star"/></svg>Star <span class="stars" data-stars hidden></span></a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<header class="hero" id="top">
|
||||
<div class="wrap hero-grid">
|
||||
<div>
|
||||
<span class="eyebrow"><span class="tagv">v0.1</span>Tested on PS5 firmware 13.60</span>
|
||||
<h1>Your PS5 screen,<br><span class="grad">live on your PC.</span></h1>
|
||||
<p class="lead">PSCapture turns a jailbroken PlayStation 5 into a capture card.
|
||||
The console streams its own screen over your LAN, and Windows sees it as a
|
||||
camera called <b>PS5 Capture</b>, ready for Discord, OBS and ffmpeg.</p>
|
||||
<div class="cta">
|
||||
<a class="btn btn-primary" href="https://github.com/fstat13/PSCapture/releases"><svg class="i"><use href="#i-download"/></svg>Download for Windows</a>
|
||||
<a class="btn btn-ghost" href="https://github.com/fstat13/PSCapture"><svg class="i"><use href="#i-star"/></svg>Star on GitHub <span class="stars" data-stars hidden></span></a>
|
||||
</div>
|
||||
<div class="hero-meta">
|
||||
<span><svg class="i"><use href="#i-check"/></svg>No capture card</span>
|
||||
<span><svg class="i"><use href="#i-check"/></svg>No Remote Play</span>
|
||||
<span><svg class="i"><use href="#i-check"/></svg>No cloud</span>
|
||||
<span><svg class="i"><use href="#i-check"/></svg>One .exe</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="badges">
|
||||
<span class="badge"><b>PS5</b> jailbroken</span>
|
||||
<span class="badge"><b>Windows</b> 10 / 11</span>
|
||||
<span class="badge"><span class="dot"></span><b>No</b> capture card</span>
|
||||
<span class="badge"><b>No</b> Remote Play</span>
|
||||
<span class="badge">1080p · ~30 fps</span>
|
||||
|
||||
<div class="app" role="img" aria-label="The PSCapture window: the log shows the driver check, the payload being sent and the stream connecting; the status pill reads STREAMING.">
|
||||
<div class="app-bar">
|
||||
<img src="pscapture.png" alt="">
|
||||
<span class="t">PSCapture by fstat13 - PS5 screen to PC</span>
|
||||
<span class="w"><span>–</span><span>□</span><span>✕</span></span>
|
||||
</div>
|
||||
<div class="app-body">
|
||||
<div class="app-head">
|
||||
<div class="app-title">PSCapture<small>PS5 screen to PC</small></div>
|
||||
<span class="pill live"><i></i> STREAMING 29.8 fps</span>
|
||||
</div>
|
||||
<div class="app-row">
|
||||
<span class="app-lbl">PS5 IP</span><span class="app-in">192.168.0.36</span>
|
||||
<span class="app-btn">Deploy payload</span><span class="app-btn">Install driver</span>
|
||||
</div>
|
||||
<div class="app-row">
|
||||
<span class="app-btn big">START STREAMING</span><span class="app-btn stop">STOP</span>
|
||||
<span class="app-knobs app-row" style="margin:0">
|
||||
<span class="app-lbl">gain</span><span class="app-in">1.0</span>
|
||||
<span class="app-lbl">shift px</span><span class="app-in">0</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="app-log" aria-hidden="true">
|
||||
<div>PSCapture pronto: premo Start e faccio tutto da solo.</div>
|
||||
<div>--- AVVIO AUTOMATICO ---</div>
|
||||
<div>driver gia' installato</div>
|
||||
<div>payload dall'eseguibile (175480 byte)</div>
|
||||
<div>payload 175480 byte, invio a 192.168.0.36</div>
|
||||
<div>autoloader aggiornato (pldmgr)</div>
|
||||
<div class="hl">payload avviato - la PS5 deve mostrare la notifica PSCapture</div>
|
||||
<div>porta stream: 5599</div>
|
||||
<div class="ok">collegato a 192.168.0.36:5599</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wrap">
|
||||
<div class="stats">
|
||||
<div class="stat"><div class="n">1920×1080</div><div class="l">full HD in OBS</div></div>
|
||||
<div class="stat"><div class="n">~30 fps</div><div class="l">over a plain LAN</div></div>
|
||||
<div class="stat"><div class="n">0</div><div class="l">re-encodes on the console</div></div>
|
||||
<div class="stat"><div class="n">1 file</div><div class="l">driver and payload inside</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="wrap">
|
||||
<main class="wrap">
|
||||
|
||||
<h2>What it is</h2>
|
||||
<div class="cards">
|
||||
<div class="card">
|
||||
<h3>🎥 Virtual capture card</h3>
|
||||
<p>PSCapture registers a DirectShow device named <b>PS5 Capture</b>.
|
||||
Discord, OBS and ffmpeg see it like an Elgato — pick it and go live.</p>
|
||||
<section id="why" class="reveal">
|
||||
<div class="sec-head">
|
||||
<div class="kicker">Why PSCapture</div>
|
||||
<h2>A capture card, without the capture card.</h2>
|
||||
<p class="sub">Everything runs on hardware you already own: the console, your network and your PC.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>⚡ Straight from the GPU</h3>
|
||||
<p>The console payload reads the display engine's scan-out buffer directly
|
||||
and streams NV12 frames over your LAN. No re-encode delay, no cloud.</p>
|
||||
<div class="grid g3">
|
||||
<div class="card"><div class="ico"><svg class="i"><use href="#i-video"/></svg></div>
|
||||
<h3>A real camera device</h3>
|
||||
<p>A DirectShow source filter named <b>PS5 Capture</b>. Any app that can open a webcam can open your console.</p></div>
|
||||
<div class="card"><div class="ico v"><svg class="i"><use href="#i-zap"/></svg></div>
|
||||
<h3>Straight from the display engine</h3>
|
||||
<p>The payload reads the console's scan-out buffer and sends raw NV12 frames. Nothing is re-encoded, so nothing waits for an encoder.</p></div>
|
||||
<div class="card"><div class="ico g"><svg class="i"><use href="#i-file"/></svg></div>
|
||||
<h3>One file, one window</h3>
|
||||
<p><b>PSCapture.exe</b> installs the driver, sends the payload, registers it in the autoloader and connects, all on its own.</p></div>
|
||||
<div class="card"><div class="ico a"><svg class="i"><use href="#i-refresh"/></svg></div>
|
||||
<h3>Recovers by itself</h3>
|
||||
<p>Console still off? It retries every 2 seconds. Stream dropped? It reconnects. You don't have to babysit it.</p></div>
|
||||
<div class="card"><div class="ico"><svg class="i"><use href="#i-sliders"/></svg></div>
|
||||
<h3>Live tuning</h3>
|
||||
<p>Colour <code>gain</code> and horizontal <code>shift</code> apply in about a second and are saved in <code>pscapture.ini</code>.</p></div>
|
||||
<div class="card"><div class="ico v"><svg class="i"><use href="#i-lock"/></svg></div>
|
||||
<h3>Stays on your network</h3>
|
||||
<p>No accounts, no cloud relay, no Remote Play. Frames go from the console to your PC and nowhere else.</p></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>🖱️ One executable</h3>
|
||||
<p><b>PSCapture.exe</b> carries the console payload inside itself:
|
||||
install the driver, push the payload, start streaming — one window,
|
||||
one click.</p>
|
||||
</section>
|
||||
|
||||
<section id="how" class="reveal">
|
||||
<div class="sec-head">
|
||||
<div class="kicker">How it works</div>
|
||||
<h2>Three small pieces, one camera.</h2>
|
||||
<p class="sub">A payload on the console, a feeder and a driver on the PC. The apps you already use just see a webcam.</p>
|
||||
</div>
|
||||
<div class="pipe">
|
||||
<div class="node"><div class="where">PS5</div><h3>Payload</h3>
|
||||
<p>Reads the display engine's scan-out ring, builds NV12 frames and serves them on TCP <code>5599</code>. Pops the toast <i>“PSCapture is streaming on:5599”</i>.</p></div>
|
||||
<div class="link"><span>TCP · NV12<br>your LAN</span><div class="line"></div></div>
|
||||
<div class="node"><div class="where">PC · PSCapture.exe</div><h3>Feeder</h3>
|
||||
<p>Receives the frames and publishes them into a shared-memory ring.</p></div>
|
||||
<div class="link"><span>shared<br>memory</span><div class="line"></div></div>
|
||||
<div class="node"><div class="where">PC · pscapture.dll</div><h3>Driver</h3>
|
||||
<p>A DirectShow source filter that exposes the ring as the camera <b>PS5 Capture</b>.</p></div>
|
||||
<div class="link"><span>DirectShow</span><div class="line"></div></div>
|
||||
<div class="node end"><div class="where">Your apps</div><h3>Any camera app</h3>
|
||||
<p>Pick <b>PS5 Capture</b> like any webcam.</p>
|
||||
<div class="chips"><span class="chip">Discord</span><span class="chip">OBS</span><span class="chip">ffmpeg</span></div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="requirements" class="reveal">
|
||||
<div class="sec-head">
|
||||
<div class="kicker">Before you start</div>
|
||||
<h2>What you need.</h2>
|
||||
</div>
|
||||
<div class="grid g2">
|
||||
<div class="req">
|
||||
<div class="req-h"><div class="ico"><svg class="i"><use href="#i-console"/></svg></div><h3>On the PS5</h3></div>
|
||||
<ul>
|
||||
<li><svg class="i"><use href="#i-check"/></svg><span>A jailbroken console<small>Built and tuned on firmware <b>13.60</b>. See the FAQ for other versions.</small></span></li>
|
||||
<li><svg class="i"><use href="#i-check"/></svg><span>Payload loader on TCP <code>9021</code><small><code>elfldr</code>-style, as in every current jailbreak setup.</small></span></li>
|
||||
<li><svg class="i"><use href="#i-check"/></svg><span>Anonymous FTP on TCP <code>2121</code><small>The app reads and writes a few files on the console.</small></span></li>
|
||||
<li class="opt"><svg class="i"><use href="#i-check"/></svg><span><code>pldmgr</code> autoloader <span class="tagopt">optional</span><small>Starts the payload on every boot, so you never deploy by hand.</small></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="req">
|
||||
<div class="req-h"><div class="ico v"><svg class="i"><use href="#i-monitor"/></svg></div><h3>On the PC</h3></div>
|
||||
<ul>
|
||||
<li><svg class="i"><use href="#i-check"/></svg><span>Windows 10 or 11, 64-bit<small>The camera is a DirectShow device, so the PC side is Windows-only.</small></span></li>
|
||||
<li><svg class="i"><use href="#i-check"/></svg><span>Same network as the console<small>Wired recommended. Gigabit is plenty for 1080p at 30 fps.</small></span></li>
|
||||
<li><svg class="i"><use href="#i-check"/></svg><span>Admin rights, once<small>To register the camera for every program on the machine.</small></span></li>
|
||||
<li><svg class="i"><use href="#i-check"/></svg><span><b>PSCapture.exe</b> from <a href="https://github.com/fstat13/PSCapture/releases">Releases</a><small>One single file: the payload and the driver are inside it.</small></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="start" class="reveal">
|
||||
<div class="sec-head">
|
||||
<div class="kicker">Setup</div>
|
||||
<h2>From zero to live in five steps.</h2>
|
||||
<p class="sub">After step 2 you mostly watch: the app does the rest and tells you what it's doing.</p>
|
||||
</div>
|
||||
<ol class="steps">
|
||||
<li><h3>Jailbreak the console</h3>
|
||||
<p>Run your usual jailbreak so the payload loader (<code>9021</code>) and FTP (<code>2121</code>) are up.</p></li>
|
||||
<li><h3>Double-click <code>PSCapture.exe</code></h3>
|
||||
<p>Windows asks <i>“Do you want to allow this app to make changes to your device?”</i>. Click <b>Yes</b>. The app elevates itself, no right-click needed.</p></li>
|
||||
<li><h3>Check the IP</h3>
|
||||
<p>The default is <code>192.168.0.36</code>. If your PS5 has a different address, type it in <b>PS5 IP</b> and press <b>Apply</b>. It's remembered. You can find it on the console under <i>Settings → Network → Connection Status → View Connection Status</i>.</p></li>
|
||||
<li><h3>Hands off</h3>
|
||||
<p>On the first run the app installs the driver, then sends the payload and connects. The console shows <i>“PSCapture is streaming on:5599”</i> and the status pill turns <b style="color:var(--green)">green: STREAMING</b>.</p></li>
|
||||
<li><h3>Pick <b>PS5 Capture</b> in your app</h3>
|
||||
<p>Discord, OBS or ffmpeg. See <a href="#use">where to find it</a> below.</p></li>
|
||||
</ol>
|
||||
|
||||
<h3 style="margin:30px 0 6px">Reading the log</h3>
|
||||
<p style="color:var(--dim)">The app's log is in Italian. Here's what each line means, in the order you'll see them.</p>
|
||||
<div class="term" style="margin-top:14px">
|
||||
<div class="term-bar"><i></i><i></i><i></i><b>pscapture-app.log</b></div>
|
||||
<div class="term-row"><code>driver gia' installato</code><span class="m">The camera is already registered. Nothing to do.</span></div>
|
||||
<div class="term-row"><code>driver non installato: lo installo ora (primo avvio)<br>driver estratto dall'eseguibile (… byte)<br>driver registrato - la webcam 'PS5 Capture' e' pronta</code><span class="m">First run: the driver is unpacked from the exe and registered.</span></div>
|
||||
<div class="term-row"><code>payload dall'eseguibile (… byte)<br>payload … byte, invio a 192.168.0.36</code><span class="m">The payload is on its way to the console.</span></div>
|
||||
<div class="term-row"><code>autoloader aggiornato (pldmgr)</code><span class="m">The payload was copied to the autoloader, so it'll start on every boot.</span></div>
|
||||
<div class="term-row"><code>payload avviato - la PS5 deve mostrare la notifica PSCapture</code><span class="m">The payload is running. Look for the toast on the TV.</span></div>
|
||||
<div class="term-row ok"><code>porta stream: 5599<br>collegato a 192.168.0.36:5599</code><span class="m">Connected. The pill reads <b>STREAMING</b> with the live fps.</span></div>
|
||||
</div>
|
||||
|
||||
<div class="callout warn" style="margin-top:18px">
|
||||
<svg class="i"><use href="#i-alert"/></svg>
|
||||
<p><b>Pill stuck on “IN ATTESA DELLA PS5”</b> (“waiting for the PS5”)? The console is off, not jailbroken yet, or the IP is wrong. You don't need to restart anything: the app retries every 2 seconds and connects the moment ports <code>9021</code> and <code>2121</code> answer.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="use" class="reveal">
|
||||
<div class="sec-head">
|
||||
<div class="kicker">Use it</div>
|
||||
<h2>Works wherever a webcam works.</h2>
|
||||
</div>
|
||||
<div class="grid g3">
|
||||
<div class="card target"><h3>Discord</h3>
|
||||
<div class="path"><i>Settings → Voice & Video → Camera</i> → <b>PS5 Capture</b></div>
|
||||
<p style="margin-top:10px">Discord asks every camera for 720p, capture cards included. For 1080p60 use screen-share (Nitro).</p>
|
||||
<div class="res">in calls <b>1280×720</b></div></div>
|
||||
<div class="card target"><h3>OBS Studio</h3>
|
||||
<div class="path"><i>Sources → + → Video Capture Device</i> → <b>PS5 Capture</b></div>
|
||||
<p style="margin-top:10px">Full resolution, ready for recording or streaming.</p>
|
||||
<div class="res">native <b>1920×1080</b></div></div>
|
||||
<div class="card target"><h3>ffmpeg</h3>
|
||||
<div class="path"><code>ffmpeg -f dshow -i video="PS5 Capture" capture.mkv</code></div>
|
||||
<p style="margin-top:10px">Scripted captures and quick tests.</p>
|
||||
<div class="res">native <b>1920×1080</b></div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="daily" class="reveal">
|
||||
<div class="sec-head">
|
||||
<div class="kicker">Daily use</div>
|
||||
<h2>Open it and play.</h2>
|
||||
</div>
|
||||
<div class="grid g3">
|
||||
<div class="card knob"><span class="k">every day</span>
|
||||
<p>Open <b>PSCapture.exe</b> and accept the UAC prompt. With the autoloader the stream starts by itself.</p>
|
||||
<ul>
|
||||
<li><b>Deploy payload</b>: re-sends the payload by hand, after a reboot without autoloader.</li>
|
||||
<li><b>Install driver</b>: re-registers the camera if the log shows a driver error.</li>
|
||||
<li><b>STOP</b> or closing the app: the camera freezes on the last frame and stays listed.</li>
|
||||
</ul></div>
|
||||
<div class="card knob"><span class="k">gain</span>
|
||||
<p>Colour (chroma) strength.</p>
|
||||
<ul><li><code>1.0</code> is raw and correct.</li><li>Only touch it if colours look off.</li></ul></div>
|
||||
<div class="card knob"><span class="k">shift px</span>
|
||||
<p>Moves the picture left or right.</p>
|
||||
<ul><li><b>Even numbers only</b>: an odd value swaps the colours.</li><li>The console's title text makes a good reference.</li></ul></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="trouble" class="reveal">
|
||||
<div class="sec-head">
|
||||
<div class="kicker">Troubleshooting</div>
|
||||
<h2>Symptom → fix.</h2>
|
||||
<p class="sub">Type what you see, or paste a line from the log.</p>
|
||||
</div>
|
||||
<div class="search"><svg class="i"><use href="#i-search"/></svg>
|
||||
<input id="q" type="search" placeholder="e.g. black, 720p, err=126, ATTESA…" autocomplete="off" aria-label="Filter problems"></div>
|
||||
<div class="issues">
|
||||
<div class="issue"><div class="sym">Pill stays on <code>AVVIO…</code> or <code>IN ATTESA DELLA PS5</code></div>
|
||||
<div class="fix"><span class="cause">console off, not jailbroken, or wrong IP</span>Turn the console on, run the jailbreak (ports 9021 and 2121 must be up), check the <b>PS5 IP</b> field. The app keeps retrying.</div></div>
|
||||
<div class="issue"><div class="sym">Log repeats <code>PS5 non raggiungibile (…), riprovo</code></div>
|
||||
<div class="fix"><span class="cause">console not reachable</span>Same as above. It connects by itself as soon as the console answers.</div></div>
|
||||
<div class="issue"><div class="sym">Log: <code>elfldr :9021 non raggiungibile</code></div>
|
||||
<div class="fix"><span class="cause">payload loader not running</span>Run the jailbreak again, then press <b>Deploy payload</b>.</div></div>
|
||||
<div class="issue"><div class="sym">No <b>PS5 Capture</b> camera in Discord or OBS</div>
|
||||
<div class="fix"><span class="cause">driver not registered</span>Press <b>Install driver</b> and read the next log line: it reports the real error.</div></div>
|
||||
<div class="issue"><div class="sym">Log: <code>ERRORE: driver non scrivibile in C:\Program Files\PSCapture (serve admin?)</code></div>
|
||||
<div class="fix"><span class="cause">app not elevated</span>Close it, start <code>PSCapture.exe</code> again and answer <b>Yes</b> to the UAC prompt.</div></div>
|
||||
<div class="issue"><div class="sym">Log: <code>elevazione rifiutata: continuo senza privilegi</code></div>
|
||||
<div class="fix"><span class="cause">UAC prompt answered “No”</span>Streaming can still work, but the driver can't be installed. Restart the app and click <b>Yes</b>.</div></div>
|
||||
<div class="issue"><div class="sym">Log: <code>ERRORE caricamento dll (err=126)</code></div>
|
||||
<div class="fix"><span class="cause">antivirus quarantined the driver</span>Restore and allow <code>pscapture.dll</code> in your antivirus, then press <b>Install driver</b> again.</div></div>
|
||||
<div class="issue"><div class="sym">Log: <code>autoloader: copia FALLITA (pldmgr c'e'?)</code></div>
|
||||
<div class="fix"><span class="cause"><code>pldmgr</code> not installed</span>Harmless: streaming works now. Without the autoloader, press <b>Deploy payload</b> after every console reboot.</div></div>
|
||||
<div class="issue"><div class="sym">Camera is listed but the image is black</div>
|
||||
<div class="fix"><span class="cause">stream not running</span>Check the pill. Press <b>Deploy payload</b> and/or <b>START STREAMING</b>.</div></div>
|
||||
<div class="issue"><div class="sym">Log: <code>stream caduto, riconnetto</code></div>
|
||||
<div class="fix"><span class="cause">network hiccup or console went to rest mode</span>The app reconnects by itself. If it happens often, use a cable instead of Wi-Fi.</div></div>
|
||||
<div class="issue"><div class="sym">Colours look wrong (reddish skin, orange sky)</div>
|
||||
<div class="fix"><span class="cause"><code>gain</code> was changed</span>Set <code>gain</code> back to <code>1.0</code> and press <b>Apply</b>.</div></div>
|
||||
<div class="issue"><div class="sym">Colours inverted, black bar, picture off-centre</div>
|
||||
<div class="fix"><span class="cause">alignment</span>Tune <code>shift px</code> using <b>even</b> values only. An odd value swaps the colours.</div></div>
|
||||
<div class="issue"><div class="sym">Discord shows only 720p</div>
|
||||
<div class="fix"><span class="cause">Discord's limit, not PSCapture's</span>Discord caps every camera at 720p in calls. Use OBS for 1080p, or Discord screen-share (1080p60 with Nitro).</div></div>
|
||||
<div class="issue"><div class="sym">OBS sees the camera, Discord doesn't</div>
|
||||
<div class="fix"><span class="cause">Discord was open during the driver install</span>Quit Discord completely (tray icon too) and reopen it.</div></div>
|
||||
<div class="issue"><div class="sym">Anything else</div>
|
||||
<div class="fix"><span class="cause">read the logs</span><code>pscapture-app.log</code> (next to the exe, or in <code>%TEMP%</code>) and the console log <code>/data/pscapture.log</code> over FTP. Then <a href="https://github.com/fstat13/PSCapture/issues">open an issue</a> with both.</div></div>
|
||||
<div class="noresult" hidden>No match. <a href="https://github.com/fstat13/PSCapture/issues">Open an issue</a> with your <code>pscapture-app.log</code>.</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="build" class="reveal">
|
||||
<div class="sec-head">
|
||||
<div class="kicker">Build from source</div>
|
||||
<h2>Two halves, two toolchains.</h2>
|
||||
<p class="sub">The repository holds sources only; ready-made binaries are on <a href="https://github.com/fstat13/PSCapture/releases">Releases</a>. The full walkthrough (toolchain install, checks, pitfalls) is in <a href="https://github.com/fstat13/PSCapture/blob/main/docs/BUILD.md">BUILD.md</a>.</p>
|
||||
</div>
|
||||
|
||||
<div class="build-step">
|
||||
<div class="num">Step 1 · console</div>
|
||||
<h3>Payload → <code>pscapture.elf</code></h3>
|
||||
<p>Needs Docker and the PS5 payload SDK image (prospero-clang; any tag works, here <code>ps5sdk:18</code>).</p>
|
||||
<div class="codeblock"><pre><span class="p">$ </span>docker run --rm -v "$PWD/payload:/payload" -w /payload ps5sdk:18 make</pre></div>
|
||||
</div>
|
||||
|
||||
<div class="build-step">
|
||||
<div class="num">Step 2 · PC</div>
|
||||
<h3>App and driver → <code>pscapture.exe</code> + <code>pscapture.dll</code></h3>
|
||||
<p>Needs MinGW-w64 (<code>gcc</code> + <code>windres</code>). <b>The order matters</b>: the <code>.res</code> embeds the driver and the payload exactly as they are on disk.</p>
|
||||
<div class="codeblock"><pre><span class="c"># from the repo root</span>
|
||||
cp payload/pscapture.elf pc/
|
||||
cd pc
|
||||
|
||||
<span class="c"># 1) the capture-card driver (DirectShow source filter)</span>
|
||||
gcc -O2 -shared -static -static-libgcc -o pscapture.dll ps5cam.c \
|
||||
-lole32 -loleaut32 -luser32 -ladvapi32 -lws2_32
|
||||
|
||||
<span class="c"># 2) bundle icon + payload + driver into the resource object</span>
|
||||
windres pscapture.rc -O coff -o pscapture.res
|
||||
|
||||
<span class="c"># 3) the single-file app</span>
|
||||
gcc -O2 -mwindows -o pscapture.exe pscapture.c pscapture.res \
|
||||
-lws2_32 -lshell32 -ladvapi32 -luxtheme</pre></div>
|
||||
</div>
|
||||
|
||||
<div class="callout info" style="margin-top:22px">
|
||||
<svg class="i"><use href="#i-info"/></svg>
|
||||
<p><b>The two classic mistakes.</b> Rebuilding the DLL or the payload <i>after</i> <code>windres</code> leaves the old ones inside the exe, so always go DLL → windres → exe. And <code>-static -static-libgcc</code> on the DLL is mandatory: without it Windows may refuse to load the driver with “the specified module could not be found” (missing MinGW runtime).</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="faq" class="reveal">
|
||||
<div class="sec-head">
|
||||
<div class="kicker">FAQ</div>
|
||||
<h2>Questions.</h2>
|
||||
</div>
|
||||
<div class="faq">
|
||||
<details><summary>Does it work on firmwares other than 13.60?</summary>
|
||||
<div class="body"><p>The PC side works with any firmware: the app and the driver only use TCP, FTP and shared memory. The console payload is the version-specific part. It was built and tuned on <b>13.60</b> using display-engine anchors found on that firmware. Other versions may need a small porting pass to find the same anchors. <a href="https://github.com/fstat13/PSCapture/issues">Open an issue</a> with your firmware version.</p></div></details>
|
||||
<details><summary>Why is the camera 720p in Discord calls?</summary>
|
||||
<div class="body"><p>That's Discord's voice engine: it requests 1280×720 from every camera. In OBS, PS5 Capture runs at the full 1920×1080. Discord's screen-share goes up to 1080p60 with Nitro.</p></div></details>
|
||||
<details><summary>Do I need a capture card or Remote Play?</summary>
|
||||
<div class="body"><p>No. PSCapture never touches Remote Play. The payload reads the display engine on the console itself and sends raw frames over your LAN.</p></div></details>
|
||||
<details><summary>Does it capture game audio?</summary>
|
||||
<div class="body"><p>Not yet. PSCapture is video-only for now.</p></div></details>
|
||||
<details><summary>Does it work on macOS or Linux?</summary>
|
||||
<div class="body"><p>The PC side is Windows-only, because the camera is a DirectShow device.</p></div></details>
|
||||
<details><summary>How do I uninstall it?</summary>
|
||||
<div class="body"><ol>
|
||||
<li>Close the app.</li>
|
||||
<li>Run <code>rundll32 "%ProgramFiles%\PSCapture\pscapture.dll",DllUnregisterServer</code> to remove the camera.</li>
|
||||
<li>Delete <code>C:\Program Files\PSCapture</code>.</li>
|
||||
<li>Optional, on the console: delete <code>/data/pldmgr/payloads/pscapture/</code> so the autoloader stops starting it.</li>
|
||||
</ol></div></details>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="band reveal">
|
||||
<div>
|
||||
<h2>Useful? Leave a star.</h2>
|
||||
<p>PSCapture is free. A star on GitHub helps other PS5 homebrew users find it.</p>
|
||||
</div>
|
||||
<div class="cta">
|
||||
<a class="btn btn-primary" href="https://github.com/fstat13/PSCapture"><svg class="i"><use href="#i-star"/></svg>Star on GitHub <span class="stars" data-stars hidden></span></a>
|
||||
<a class="btn btn-ghost" href="https://github.com/fstat13/PSCapture/issues">Report an issue</a>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<h2>How it works</h2>
|
||||
<div class="flow">
|
||||
<div class="box"><b>PS5 · payload</b>
|
||||
<div>reads the DCE scan-out ring, builds NV12 frames, serves them on a TCP
|
||||
port, pops a toast: <i>“PSCapture is streaming on:PORT”</i>.</div></div>
|
||||
<div class="arrow">→</div>
|
||||
<div class="box"><b>PC · feeder</b>
|
||||
<div>receives frames and publishes them into a shared-memory ring.</div></div>
|
||||
<div class="arrow">→</div>
|
||||
<div class="box"><b>PC · driver</b>
|
||||
<div>DirectShow filter exposes the ring as a camera every app can use.</div></div>
|
||||
<footer>
|
||||
<div class="wrap">
|
||||
<div class="foot">
|
||||
<div>
|
||||
<a class="logo" href="#top"><img src="pscapture.png" alt="">PSCapture</a>
|
||||
<p>Your PS5 screen anywhere on your LAN. No card, no cloud.<br>by <b>fstat13</b></p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Guides</h4>
|
||||
<ul>
|
||||
<li><a href="https://github.com/fstat13/PSCapture/blob/main/docs/GUIDE.md">Step-by-step guide</a></li>
|
||||
<li><a href="https://github.com/fstat13/PSCapture/blob/main/docs/BUILD.md">Build from source</a></li>
|
||||
<li><a href="https://github.com/fstat13/PSCapture/blob/main/docs/INSTALLER.md">What the installer does</a></li>
|
||||
<li><a href="https://github.com/fstat13/PSCapture/blob/main/docs/REVERSE_ENGINEERING.md">Reverse engineering</a></li>
|
||||
<li><a href="https://github.com/fstat13/PSCapture/blob/main/docs/ENGINEERING_LOG.md">Engineering log</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Project</h4>
|
||||
<ul>
|
||||
<li><a href="https://github.com/fstat13/PSCapture">GitHub</a></li>
|
||||
<li><a href="https://github.com/fstat13/PSCapture/releases">Releases</a></li>
|
||||
<li><a href="https://github.com/fstat13/PSCapture/issues">Issues</a></li>
|
||||
<li><a href="https://github.com/fstat13/PSCapture/blob/main/LICENSE">License</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="legal">
|
||||
<span>© 2026 fstat13 · source-available</span>
|
||||
<span>Not affiliated with Sony Interactive Entertainment. “PlayStation” and “PS5” are trademarks of their respective owners.</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<h2>Quick start</h2>
|
||||
<ol class="steps">
|
||||
<li><b>Jailbreak your PS5</b><br>
|
||||
<span>Any kernel-capable jailbreak that gives you payloads
|
||||
(<code>elfldr</code> on port 9021, anonymous FTP on 2121 and the
|
||||
<code>pldmgr</code> autoloader).</span></li>
|
||||
<li><b>Run <code>PSCapture.exe</code></b><br>
|
||||
<span>Windows asks for administrator once. Type the PS5 IP address if it
|
||||
is not the default <code>192.168.0.36</code>.</span></li>
|
||||
<li><b>Press <i>Start</i></b><br>
|
||||
<span>The app installs the virtual capture driver (first run), sends the
|
||||
payload to the console, registers it in the autoloader and starts the
|
||||
stream. The PS5 shows the streaming notification.</span></li>
|
||||
<li><b>Discord / OBS / anything</b><br>
|
||||
<span>Choose the camera <b>PS5 Capture</b>. Done — that is your PS5 on
|
||||
the other side.</span></li>
|
||||
</ol>
|
||||
|
||||
<div class="note" style="margin-top:22px">
|
||||
<b>Tuning:</b> the <code>gain</code> field adjusts chroma intensity and
|
||||
<code>shift px</code> fine-aligns the image horizontally (both live,
|
||||
saved in <code>pscapture.ini</code> next to the exe).
|
||||
</div>
|
||||
|
||||
<h2>Requirements</h2>
|
||||
<table>
|
||||
<tr><th>Where</th><th>What</th></tr>
|
||||
<tr><td>PS5</td><td>Jailbroken (payload loader + FTP + pldmgr autoloader)</td></tr>
|
||||
<tr><td>PS5</td><td>Latest tested firmware: <b>13.60</b> — see the FAQ for other versions</td></tr>
|
||||
<tr><td>PC</td><td>Windows 10/11 64-bit, wired network recommended</td></tr>
|
||||
<tr><td>Network</td><td>Both on the same LAN (gigabit is plenty for 1080p30)</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>FAQ</h2>
|
||||
|
||||
<details>
|
||||
<summary>Does it work on firmware versions older than 13.60?</summary>
|
||||
<p>The PC side works on every firmware: the app and the driver only speak
|
||||
TCP/FTP and shared memory. The console payload is the version-specific
|
||||
part — it was built and tuned on <b>13.60</b>, using display-engine
|
||||
anchors found on that firmware. On other firmwares it may need a small
|
||||
porting pass (the fix is finding the same anchors on that system).
|
||||
If you have another firmware, open an issue with your version and we
|
||||
can add a profile.</p>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Why is the call camera 720p and not 1080p?</summary>
|
||||
<p>That cap is Discord's voice engine, not PSCapture: it requests 1280x720
|
||||
from every camera. For full-quality capture use OBS (where PS5 Capture
|
||||
runs at 1920x1080) or Discord's screen-share, which goes up to
|
||||
1080p60 with Nitro.</p>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Is a capture card or Remote Play needed?</summary>
|
||||
<p>No. PSCapture never touches Remote Play: the payload reads the display
|
||||
engine on the console itself and streams raw frames over your LAN.</p>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>The camera lists but stays black / spinner</summary>
|
||||
<p>Open the app: the status line shows what is happening — not reachable
|
||||
IP, payload not running, or feeder reconnecting. Everything is logged
|
||||
in the window; the console side logs to <code>/data/pscapture.log</code>
|
||||
over FTP.</p>
|
||||
</details>
|
||||
|
||||
<footer>
|
||||
PSCapture — bring your PS5 screen anywhere on your LAN, no card, no cloud.
|
||||
<br><b>by fstat13</b> · source-available. Built for the homebrew community. <span class="ok">Play responsibly.</span>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,445 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it" class="no-js">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>PSCapture — la PS5 come scheda di acquisizione sul PC</title>
|
||||
<meta name="description" content="PSCapture trasforma una PS5 jailbroken in una scheda di acquisizione per il PC: lo schermo della console passa sulla tua rete locale e compare in Discord, OBS e ffmpeg come la webcam “PS5 Capture”.">
|
||||
<meta name="theme-color" content="#07090d">
|
||||
<link rel="icon" type="image/png" href="pscapture.png">
|
||||
<link rel="alternate" hreflang="en" href="https://fstat13.github.io/PSCapture/">
|
||||
<link rel="alternate" hreflang="it" href="https://fstat13.github.io/PSCapture/index.it.html">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:locale" content="it_IT">
|
||||
<meta property="og:title" content="PSCapture — lo schermo della PS5, live sul tuo PC">
|
||||
<meta property="og:description" content="Lo schermo della PS5 compare in Discord, OBS e ffmpeg come la webcam “PS5 Capture”. Niente capture card, niente Remote Play, niente cloud.">
|
||||
<meta property="og:image" content="https://fstat13.github.io/PSCapture/social-preview.png">
|
||||
<meta property="og:image:width" content="1280">
|
||||
<meta property="og:image:height" content="640">
|
||||
<meta property="og:url" content="https://fstat13.github.io/PSCapture/index.it.html">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<svg width="0" height="0" style="position:absolute" aria-hidden="true">
|
||||
<symbol id="i-download" viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></symbol>
|
||||
<symbol id="i-star" viewBox="0 0 24 24"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></symbol>
|
||||
<symbol id="i-check" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></symbol>
|
||||
<symbol id="i-video" viewBox="0 0 24 24"><path d="m22 8-6 4 6 4V8Z"/><rect x="2" y="6" width="14" height="12" rx="2"/></symbol>
|
||||
<symbol id="i-zap" viewBox="0 0 24 24"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></symbol>
|
||||
<symbol id="i-file" viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></symbol>
|
||||
<symbol id="i-refresh" viewBox="0 0 24 24"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></symbol>
|
||||
<symbol id="i-sliders" viewBox="0 0 24 24"><line x1="4" y1="21" x2="4" y2="14"/><line x1="4" y1="10" x2="4" y2="3"/><line x1="12" y1="21" x2="12" y2="12"/><line x1="12" y1="8" x2="12" y2="3"/><line x1="20" y1="21" x2="20" y2="16"/><line x1="20" y1="12" x2="20" y2="3"/><line x1="1" y1="14" x2="7" y2="14"/><line x1="9" y1="8" x2="15" y2="8"/><line x1="17" y1="16" x2="23" y2="16"/></symbol>
|
||||
<symbol id="i-lock" viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></symbol>
|
||||
<symbol id="i-console" viewBox="0 0 24 24"><rect x="2" y="6" width="20" height="12" rx="3"/><line x1="6" y1="12" x2="10" y2="12"/><line x1="8" y1="10" x2="8" y2="14"/><circle cx="16" cy="11" r=".6"/><circle cx="18" cy="13" r=".6"/></symbol>
|
||||
<symbol id="i-monitor" viewBox="0 0 24 24"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></symbol>
|
||||
<symbol id="i-info" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></symbol>
|
||||
<symbol id="i-alert" viewBox="0 0 24 24"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></symbol>
|
||||
<symbol id="i-search" viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></symbol>
|
||||
</svg>
|
||||
|
||||
<nav class="nav">
|
||||
<div class="wrap">
|
||||
<a class="logo" href="#top"><img src="pscapture.png" alt="">PSCapture</a>
|
||||
<div class="nav-links">
|
||||
<a href="#how">Come funziona</a>
|
||||
<a href="#start">Installazione</a>
|
||||
<a href="#trouble">Problemi</a>
|
||||
<a href="#build">Build</a>
|
||||
<a href="#faq">FAQ</a>
|
||||
</div>
|
||||
<div class="nav-right">
|
||||
<a class="lang" href="index.html" hreflang="en">English</a>
|
||||
<a class="btn btn-ghost btn-sm" href="https://github.com/fstat13/PSCapture"><svg class="i"><use href="#i-star"/></svg>Star <span class="stars" data-stars hidden></span></a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<header class="hero" id="top">
|
||||
<div class="wrap hero-grid">
|
||||
<div>
|
||||
<span class="eyebrow"><span class="tagv">v0.1</span>Testato sul firmware PS5 13.60</span>
|
||||
<h1>Lo schermo della PS5,<br><span class="grad">live sul tuo PC.</span></h1>
|
||||
<p class="lead">PSCapture trasforma una PlayStation 5 jailbroken in una scheda di acquisizione.
|
||||
La console manda il proprio schermo sulla tua rete locale e Windows lo vede come una
|
||||
webcam chiamata <b>PS5 Capture</b>, pronta per Discord, OBS e ffmpeg.</p>
|
||||
<div class="cta">
|
||||
<a class="btn btn-primary" href="https://github.com/fstat13/PSCapture/releases"><svg class="i"><use href="#i-download"/></svg>Scarica per Windows</a>
|
||||
<a class="btn btn-ghost" href="https://github.com/fstat13/PSCapture"><svg class="i"><use href="#i-star"/></svg>Metti una stella <span class="stars" data-stars hidden></span></a>
|
||||
</div>
|
||||
<div class="hero-meta">
|
||||
<span><svg class="i"><use href="#i-check"/></svg>Niente capture card</span>
|
||||
<span><svg class="i"><use href="#i-check"/></svg>Niente Remote Play</span>
|
||||
<span><svg class="i"><use href="#i-check"/></svg>Niente cloud</span>
|
||||
<span><svg class="i"><use href="#i-check"/></svg>Un solo .exe</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="app" role="img" aria-label="La finestra di PSCapture: il log mostra il controllo del driver, l'invio del payload e la connessione dello stream; la pillola di stato dice STREAMING.">
|
||||
<div class="app-bar">
|
||||
<img src="pscapture.png" alt="">
|
||||
<span class="t">PSCapture by fstat13 - PS5 screen to PC</span>
|
||||
<span class="w"><span>–</span><span>□</span><span>✕</span></span>
|
||||
</div>
|
||||
<div class="app-body">
|
||||
<div class="app-head">
|
||||
<div class="app-title">PSCapture<small>PS5 screen to PC</small></div>
|
||||
<span class="pill live"><i></i> STREAMING 29.8 fps</span>
|
||||
</div>
|
||||
<div class="app-row">
|
||||
<span class="app-lbl">PS5 IP</span><span class="app-in">192.168.0.36</span>
|
||||
<span class="app-btn">Deploy payload</span><span class="app-btn">Install driver</span>
|
||||
</div>
|
||||
<div class="app-row">
|
||||
<span class="app-btn big">START STREAMING</span><span class="app-btn stop">STOP</span>
|
||||
<span class="app-knobs app-row" style="margin:0">
|
||||
<span class="app-lbl">gain</span><span class="app-in">1.0</span>
|
||||
<span class="app-lbl">shift px</span><span class="app-in">0</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="app-log" aria-hidden="true">
|
||||
<div>PSCapture pronto: premo Start e faccio tutto da solo.</div>
|
||||
<div>--- AVVIO AUTOMATICO ---</div>
|
||||
<div>driver gia' installato</div>
|
||||
<div>payload dall'eseguibile (175480 byte)</div>
|
||||
<div>payload 175480 byte, invio a 192.168.0.36</div>
|
||||
<div>autoloader aggiornato (pldmgr)</div>
|
||||
<div class="hl">payload avviato - la PS5 deve mostrare la notifica PSCapture</div>
|
||||
<div>porta stream: 5599</div>
|
||||
<div class="ok">collegato a 192.168.0.36:5599</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wrap">
|
||||
<div class="stats">
|
||||
<div class="stat"><div class="n">1920×1080</div><div class="l">full HD in OBS</div></div>
|
||||
<div class="stat"><div class="n">~30 fps</div><div class="l">su una normale LAN</div></div>
|
||||
<div class="stat"><div class="n">0</div><div class="l">ricodifiche sulla console</div></div>
|
||||
<div class="stat"><div class="n">1 file</div><div class="l">driver e payload inclusi</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="wrap">
|
||||
|
||||
<section id="why" class="reveal">
|
||||
<div class="sec-head">
|
||||
<div class="kicker">Perché PSCapture</div>
|
||||
<h2>Una capture card, senza la capture card.</h2>
|
||||
<p class="sub">Funziona tutto con quello che hai già: la console, la tua rete e il tuo PC.</p>
|
||||
</div>
|
||||
<div class="grid g3">
|
||||
<div class="card"><div class="ico"><svg class="i"><use href="#i-video"/></svg></div>
|
||||
<h3>Una vera webcam</h3>
|
||||
<p>Un filtro sorgente DirectShow chiamato <b>PS5 Capture</b>. Qualsiasi programma che apre una webcam può aprire la tua console.</p></div>
|
||||
<div class="card"><div class="ico v"><svg class="i"><use href="#i-zap"/></svg></div>
|
||||
<h3>Direttamente dal display engine</h3>
|
||||
<p>Il payload legge il buffer di scan-out della console e manda frame NV12 grezzi. Niente ricodifica, quindi niente attese per un encoder.</p></div>
|
||||
<div class="card"><div class="ico g"><svg class="i"><use href="#i-file"/></svg></div>
|
||||
<h3>Un file, una finestra</h3>
|
||||
<p><b>PSCapture.exe</b> installa il driver, manda il payload, lo registra nell'autoloader e si collega. Tutto da solo.</p></div>
|
||||
<div class="card"><div class="ico a"><svg class="i"><use href="#i-refresh"/></svg></div>
|
||||
<h3>Si riprende da solo</h3>
|
||||
<p>Console ancora spenta? Riprova ogni 2 secondi. Stream caduto? Si ricollega. Non devi stargli dietro.</p></div>
|
||||
<div class="card"><div class="ico"><svg class="i"><use href="#i-sliders"/></svg></div>
|
||||
<h3>Regolazioni in diretta</h3>
|
||||
<p><code>gain</code> del colore e <code>shift</code> orizzontale si applicano in circa un secondo e restano salvati in <code>pscapture.ini</code>.</p></div>
|
||||
<div class="card"><div class="ico v"><svg class="i"><use href="#i-lock"/></svg></div>
|
||||
<h3>Resta nella tua rete</h3>
|
||||
<p>Niente account, niente server esterni, niente Remote Play. I frame vanno dalla console al PC e basta.</p></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="how" class="reveal">
|
||||
<div class="sec-head">
|
||||
<div class="kicker">Come funziona</div>
|
||||
<h2>Tre pezzi piccoli, una webcam.</h2>
|
||||
<p class="sub">Un payload sulla console, un feeder e un driver sul PC. I programmi che usi già vedono semplicemente una webcam.</p>
|
||||
</div>
|
||||
<div class="pipe">
|
||||
<div class="node"><div class="where">PS5</div><h3>Payload</h3>
|
||||
<p>Legge il ring di scan-out del display engine, costruisce i frame NV12 e li serve sulla porta TCP <code>5599</code>. Mostra la notifica <i>“PSCapture is streaming on:5599”</i>.</p></div>
|
||||
<div class="link"><span>TCP · NV12<br>rete locale</span><div class="line"></div></div>
|
||||
<div class="node"><div class="where">PC · PSCapture.exe</div><h3>Feeder</h3>
|
||||
<p>Riceve i frame e li pubblica in un ring di memoria condivisa.</p></div>
|
||||
<div class="link"><span>memoria<br>condivisa</span><div class="line"></div></div>
|
||||
<div class="node"><div class="where">PC · pscapture.dll</div><h3>Driver</h3>
|
||||
<p>Un filtro sorgente DirectShow che espone il ring come la webcam <b>PS5 Capture</b>.</p></div>
|
||||
<div class="link"><span>DirectShow</span><div class="line"></div></div>
|
||||
<div class="node end"><div class="where">I tuoi programmi</div><h3>Qualsiasi app video</h3>
|
||||
<p>Scegli <b>PS5 Capture</b> come una webcam qualunque.</p>
|
||||
<div class="chips"><span class="chip">Discord</span><span class="chip">OBS</span><span class="chip">ffmpeg</span></div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="requirements" class="reveal">
|
||||
<div class="sec-head">
|
||||
<div class="kicker">Prima di iniziare</div>
|
||||
<h2>Cosa ti serve.</h2>
|
||||
</div>
|
||||
<div class="grid g2">
|
||||
<div class="req">
|
||||
<div class="req-h"><div class="ico"><svg class="i"><use href="#i-console"/></svg></div><h3>Sulla PS5</h3></div>
|
||||
<ul>
|
||||
<li><svg class="i"><use href="#i-check"/></svg><span>Una console jailbroken<small>Sviluppato e tarato sul firmware <b>13.60</b>. Per le altre versioni vedi la FAQ.</small></span></li>
|
||||
<li><svg class="i"><use href="#i-check"/></svg><span>Payload loader sulla porta TCP <code>9021</code><small>Tipo <code>elfldr</code>, come in tutti i setup di jailbreak attuali.</small></span></li>
|
||||
<li><svg class="i"><use href="#i-check"/></svg><span>FTP anonimo sulla porta TCP <code>2121</code><small>L'app legge e scrive alcuni file sulla console.</small></span></li>
|
||||
<li class="opt"><svg class="i"><use href="#i-check"/></svg><span>Autoloader <code>pldmgr</code> <span class="tagopt">opzionale</span><small>Avvia il payload a ogni accensione, così non devi mai mandarlo a mano.</small></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="req">
|
||||
<div class="req-h"><div class="ico v"><svg class="i"><use href="#i-monitor"/></svg></div><h3>Sul PC</h3></div>
|
||||
<ul>
|
||||
<li><svg class="i"><use href="#i-check"/></svg><span>Windows 10 o 11, 64 bit<small>La webcam è un dispositivo DirectShow, quindi il lato PC è solo per Windows.</small></span></li>
|
||||
<li><svg class="i"><use href="#i-check"/></svg><span>Stessa rete della console<small>Meglio via cavo. Il gigabit basta e avanza per 1080p a 30 fps.</small></span></li>
|
||||
<li><svg class="i"><use href="#i-check"/></svg><span>Permessi di amministratore, una volta<small>Per registrare la webcam per tutti i programmi del PC.</small></span></li>
|
||||
<li><svg class="i"><use href="#i-check"/></svg><span><b>PSCapture.exe</b> dalle <a href="https://github.com/fstat13/PSCapture/releases">Releases</a><small>Un unico file: payload e driver sono già dentro.</small></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="start" class="reveal">
|
||||
<div class="sec-head">
|
||||
<div class="kicker">Installazione</div>
|
||||
<h2>Da zero a live in cinque passi.</h2>
|
||||
<p class="sub">Dal passo 2 in poi ti basta guardare: l'app fa il resto e ti dice cosa sta facendo.</p>
|
||||
</div>
|
||||
<ol class="steps">
|
||||
<li><h3>Fai il jailbreak della console</h3>
|
||||
<p>Avvia il tuo solito jailbreak, così il payload loader (<code>9021</code>) e l'FTP (<code>2121</code>) sono attivi.</p></li>
|
||||
<li><h3>Doppio clic su <code>PSCapture.exe</code></h3>
|
||||
<p>Windows chiede <i>“Consentire a questa app di apportare modifiche al dispositivo?”</i>. Clicca <b>Sì</b>. L'app chiede da sola i permessi, non serve il tasto destro.</p></li>
|
||||
<li><h3>Controlla l'IP</h3>
|
||||
<p>Quello predefinito è <code>192.168.0.36</code>. Se la tua PS5 ha un altro indirizzo, scrivilo in <b>PS5 IP</b> e premi <b>Apply</b>: viene ricordato. Lo trovi sulla console in <i>Impostazioni → Rete → Stato della connessione → Visualizza stato della connessione</i>.</p></li>
|
||||
<li><h3>Non toccare niente</h3>
|
||||
<p>Al primo avvio l'app installa il driver, poi manda il payload e si collega. Sulla console compare <i>“PSCapture is streaming on:5599”</i> e la pillola di stato diventa <b style="color:var(--green)">verde: STREAMING</b>.</p></li>
|
||||
<li><h3>Scegli <b>PS5 Capture</b> nel tuo programma</h3>
|
||||
<p>Discord, OBS o ffmpeg. Più sotto trovi <a href="#use">dove trovarla</a>.</p></li>
|
||||
</ol>
|
||||
|
||||
<h3 style="margin:30px 0 6px">Come leggere il log</h3>
|
||||
<p style="color:var(--dim)">Queste sono le righe che vedrai, nell'ordine in cui compaiono.</p>
|
||||
<div class="term" style="margin-top:14px">
|
||||
<div class="term-bar"><i></i><i></i><i></i><b>pscapture-app.log</b></div>
|
||||
<div class="term-row"><code>driver gia' installato</code><span class="m">La webcam è già registrata. Niente da fare.</span></div>
|
||||
<div class="term-row"><code>driver non installato: lo installo ora (primo avvio)<br>driver estratto dall'eseguibile (… byte)<br>driver registrato - la webcam 'PS5 Capture' e' pronta</code><span class="m">Primo avvio: il driver viene estratto dall'exe e registrato.</span></div>
|
||||
<div class="term-row"><code>payload dall'eseguibile (… byte)<br>payload … byte, invio a 192.168.0.36</code><span class="m">Il payload sta andando verso la console.</span></div>
|
||||
<div class="term-row"><code>autoloader aggiornato (pldmgr)</code><span class="m">Il payload è stato copiato nell'autoloader: partirà a ogni accensione.</span></div>
|
||||
<div class="term-row"><code>payload avviato - la PS5 deve mostrare la notifica PSCapture</code><span class="m">Il payload gira. Cerca la notifica sulla TV.</span></div>
|
||||
<div class="term-row ok"><code>porta stream: 5599<br>collegato a 192.168.0.36:5599</code><span class="m">Collegato. La pillola dice <b>STREAMING</b> con gli fps in tempo reale.</span></div>
|
||||
</div>
|
||||
|
||||
<div class="callout warn" style="margin-top:18px">
|
||||
<svg class="i"><use href="#i-alert"/></svg>
|
||||
<p><b>Pillola ferma su “IN ATTESA DELLA PS5”?</b> La console è spenta, il jailbreak non è ancora partito o l'IP è sbagliato. Non serve riavviare niente: l'app riprova ogni 2 secondi e si collega appena le porte <code>9021</code> e <code>2121</code> rispondono.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="use" class="reveal">
|
||||
<div class="sec-head">
|
||||
<div class="kicker">Usalo</div>
|
||||
<h2>Funziona ovunque funzioni una webcam.</h2>
|
||||
</div>
|
||||
<div class="grid g3">
|
||||
<div class="card target"><h3>Discord</h3>
|
||||
<div class="path"><i>Impostazioni → Voce e video → Videocamera</i> → <b>PS5 Capture</b></div>
|
||||
<p style="margin-top:10px">Discord chiede 720p a tutte le webcam, capture card comprese. Per il 1080p60 usa la condivisione schermo (Nitro).</p>
|
||||
<div class="res">in chiamata <b>1280×720</b></div></div>
|
||||
<div class="card target"><h3>OBS Studio</h3>
|
||||
<div class="path"><i>Fonti → + → Dispositivo di acquisizione video</i> → <b>PS5 Capture</b></div>
|
||||
<p style="margin-top:10px">Risoluzione piena, pronta per registrare o andare in live.</p>
|
||||
<div class="res">nativo <b>1920×1080</b></div></div>
|
||||
<div class="card target"><h3>ffmpeg</h3>
|
||||
<div class="path"><code>ffmpeg -f dshow -i video="PS5 Capture" capture.mkv</code></div>
|
||||
<p style="margin-top:10px">Per catture da script e test veloci.</p>
|
||||
<div class="res">nativo <b>1920×1080</b></div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="daily" class="reveal">
|
||||
<div class="sec-head">
|
||||
<div class="kicker">Uso quotidiano</div>
|
||||
<h2>Aprilo e gioca.</h2>
|
||||
</div>
|
||||
<div class="grid g3">
|
||||
<div class="card knob"><span class="k">ogni giorno</span>
|
||||
<p>Apri <b>PSCapture.exe</b> e accetta il prompt UAC. Con l'autoloader lo stream parte da solo.</p>
|
||||
<ul>
|
||||
<li><b>Deploy payload</b>: rimanda il payload a mano, dopo un riavvio senza autoloader.</li>
|
||||
<li><b>Install driver</b>: registra di nuovo la webcam se il log mostra un errore del driver.</li>
|
||||
<li><b>STOP</b> o chiudere l'app: la webcam si ferma sull'ultimo frame e resta nell'elenco.</li>
|
||||
</ul></div>
|
||||
<div class="card knob"><span class="k">gain</span>
|
||||
<p>Intensità del colore (croma).</p>
|
||||
<ul><li><code>1.0</code> è il valore grezzo e corretto.</li><li>Cambialo solo se i colori sembrano sbagliati.</li></ul></div>
|
||||
<div class="card knob"><span class="k">shift px</span>
|
||||
<p>Sposta l'immagine a destra o a sinistra.</p>
|
||||
<ul><li><b>Solo numeri pari</b>: un valore dispari scambia i colori.</li><li>Le scritte dei titoli della console sono un buon riferimento.</li></ul></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="trouble" class="reveal">
|
||||
<div class="sec-head">
|
||||
<div class="kicker">Problemi</div>
|
||||
<h2>Sintomo → rimedio.</h2>
|
||||
<p class="sub">Scrivi cosa vedi, oppure incolla una riga del log.</p>
|
||||
</div>
|
||||
<div class="search"><svg class="i"><use href="#i-search"/></svg>
|
||||
<input id="q" type="search" placeholder="es. nera, 720p, err=126, ATTESA…" autocomplete="off" aria-label="Filtra i problemi"></div>
|
||||
<div class="issues">
|
||||
<div class="issue"><div class="sym">La pillola resta su <code>AVVIO…</code> o <code>IN ATTESA DELLA PS5</code></div>
|
||||
<div class="fix"><span class="cause">console spenta, senza jailbreak o IP sbagliato</span>Accendi la console, avvia il jailbreak (le porte 9021 e 2121 devono essere attive), controlla il campo <b>PS5 IP</b>. L'app continua a riprovare.</div></div>
|
||||
<div class="issue"><div class="sym">Il log ripete <code>PS5 non raggiungibile (…), riprovo</code></div>
|
||||
<div class="fix"><span class="cause">console non raggiungibile</span>Come sopra. Si collega da sola appena la console risponde.</div></div>
|
||||
<div class="issue"><div class="sym">Log: <code>elfldr :9021 non raggiungibile</code></div>
|
||||
<div class="fix"><span class="cause">payload loader non attivo</span>Rilancia il jailbreak, poi premi <b>Deploy payload</b>.</div></div>
|
||||
<div class="issue"><div class="sym">La webcam <b>PS5 Capture</b> non compare in Discord o OBS</div>
|
||||
<div class="fix"><span class="cause">driver non registrato</span>Premi <b>Install driver</b> e leggi la riga successiva del log: riporta l'errore vero.</div></div>
|
||||
<div class="issue"><div class="sym">Log: <code>ERRORE: driver non scrivibile in C:\Program Files\PSCapture (serve admin?)</code></div>
|
||||
<div class="fix"><span class="cause">app senza permessi di amministratore</span>Chiudila, riapri <code>PSCapture.exe</code> e rispondi <b>Sì</b> al prompt UAC.</div></div>
|
||||
<div class="issue"><div class="sym">Log: <code>elevazione rifiutata: continuo senza privilegi</code></div>
|
||||
<div class="fix"><span class="cause">al prompt UAC hai risposto “No”</span>Lo stream può funzionare lo stesso, ma il driver non si installa. Riavvia l'app e clicca <b>Sì</b>.</div></div>
|
||||
<div class="issue"><div class="sym">Log: <code>ERRORE caricamento dll (err=126)</code></div>
|
||||
<div class="fix"><span class="cause">l'antivirus ha messo in quarantena il driver</span>Ripristina e consenti <code>pscapture.dll</code> nell'antivirus, poi premi di nuovo <b>Install driver</b>.</div></div>
|
||||
<div class="issue"><div class="sym">Log: <code>autoloader: copia FALLITA (pldmgr c'e'?)</code></div>
|
||||
<div class="fix"><span class="cause"><code>pldmgr</code> non installato</span>Nessun danno: lo stream funziona. Senza autoloader, però, dopo ogni riavvio della console devi premere <b>Deploy payload</b>.</div></div>
|
||||
<div class="issue"><div class="sym">La webcam compare ma l'immagine è nera</div>
|
||||
<div class="fix"><span class="cause">stream non partito</span>Guarda la pillola. Premi <b>Deploy payload</b> e/o <b>START STREAMING</b>.</div></div>
|
||||
<div class="issue"><div class="sym">Log: <code>stream caduto, riconnetto</code></div>
|
||||
<div class="fix"><span class="cause">problema di rete o console in modalità riposo</span>L'app si ricollega da sola. Se succede spesso, passa dal Wi-Fi al cavo.</div></div>
|
||||
<div class="issue"><div class="sym">I colori sembrano sbagliati (pelle rossastra, cielo arancione)</div>
|
||||
<div class="fix"><span class="cause">hai cambiato <code>gain</code></span>Rimetti <code>gain</code> a <code>1.0</code> e premi <b>Apply</b>.</div></div>
|
||||
<div class="issue"><div class="sym">Colori invertiti, barra nera, immagine decentrata</div>
|
||||
<div class="fix"><span class="cause">allineamento</span>Regola <code>shift px</code> usando solo valori <b>pari</b>. Un valore dispari scambia i colori.</div></div>
|
||||
<div class="issue"><div class="sym">Discord mostra solo 720p</div>
|
||||
<div class="fix"><span class="cause">limite di Discord, non di PSCapture</span>In chiamata Discord limita ogni webcam a 720p. Usa OBS per il 1080p, o la condivisione schermo di Discord (1080p60 con Nitro).</div></div>
|
||||
<div class="issue"><div class="sym">OBS vede la webcam, Discord no</div>
|
||||
<div class="fix"><span class="cause">Discord era aperto durante l'installazione del driver</span>Chiudi Discord del tutto (anche dall'icona nella tray) e riaprilo.</div></div>
|
||||
<div class="issue"><div class="sym">Tutto il resto</div>
|
||||
<div class="fix"><span class="cause">leggi i log</span><code>pscapture-app.log</code> (accanto all'exe o in <code>%TEMP%</code>) e il log della console <code>/data/pscapture.log</code> via FTP. Poi <a href="https://github.com/fstat13/PSCapture/issues">apri una issue</a> allegandoli.</div></div>
|
||||
<div class="noresult" hidden>Nessun risultato. <a href="https://github.com/fstat13/PSCapture/issues">Apri una issue</a> con il tuo <code>pscapture-app.log</code>.</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="build" class="reveal">
|
||||
<div class="sec-head">
|
||||
<div class="kicker">Build dai sorgenti</div>
|
||||
<h2>Due metà, due toolchain.</h2>
|
||||
<p class="sub">La repo contiene solo i sorgenti; i binari pronti sono nelle <a href="https://github.com/fstat13/PSCapture/releases">Releases</a>. La guida completa (installazione toolchain, verifiche, trappole) è in <a href="https://github.com/fstat13/PSCapture/blob/main/docs/BUILD.it.md">BUILD.it.md</a>.</p>
|
||||
</div>
|
||||
|
||||
<div class="build-step">
|
||||
<div class="num">Passo 1 · console</div>
|
||||
<h3>Payload → <code>pscapture.elf</code></h3>
|
||||
<p>Servono Docker e l'immagine del PS5 payload SDK (prospero-clang; va bene qualsiasi tag, qui <code>ps5sdk:18</code>).</p>
|
||||
<div class="codeblock"><pre><span class="p">$ </span>docker run --rm -v "$PWD/payload:/payload" -w /payload ps5sdk:18 make</pre></div>
|
||||
</div>
|
||||
|
||||
<div class="build-step">
|
||||
<div class="num">Passo 2 · PC</div>
|
||||
<h3>App e driver → <code>pscapture.exe</code> + <code>pscapture.dll</code></h3>
|
||||
<p>Serve MinGW-w64 (<code>gcc</code> + <code>windres</code>). <b>L'ordine conta</b>: il <code>.res</code> incorpora driver e payload esattamente come sono su disco.</p>
|
||||
<div class="codeblock"><pre><span class="c"># dalla radice della repo</span>
|
||||
cp payload/pscapture.elf pc/
|
||||
cd pc
|
||||
|
||||
<span class="c"># 1) il driver della capture card (filtro sorgente DirectShow)</span>
|
||||
gcc -O2 -shared -static -static-libgcc -o pscapture.dll ps5cam.c \
|
||||
-lole32 -loleaut32 -luser32 -ladvapi32 -lws2_32
|
||||
|
||||
<span class="c"># 2) icona + payload + driver nell'oggetto risorse</span>
|
||||
windres pscapture.rc -O coff -o pscapture.res
|
||||
|
||||
<span class="c"># 3) l'app in un unico file</span>
|
||||
gcc -O2 -mwindows -o pscapture.exe pscapture.c pscapture.res \
|
||||
-lws2_32 -lshell32 -ladvapi32 -luxtheme</pre></div>
|
||||
</div>
|
||||
|
||||
<div class="callout info" style="margin-top:22px">
|
||||
<svg class="i"><use href="#i-info"/></svg>
|
||||
<p><b>I due errori classici.</b> Se ricompili la DLL o il payload <i>dopo</i> <code>windres</code>, l'exe si porta dentro quelli vecchi: l'ordine è sempre DLL → windres → exe. E <code>-static -static-libgcc</code> sulla DLL è obbligatorio: senza, Windows può rifiutarsi di caricare il driver con “impossibile trovare il modulo specificato” (manca il runtime MinGW).</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="faq" class="reveal">
|
||||
<div class="sec-head">
|
||||
<div class="kicker">FAQ</div>
|
||||
<h2>Domande.</h2>
|
||||
</div>
|
||||
<div class="faq">
|
||||
<details><summary>Funziona su firmware diversi dal 13.60?</summary>
|
||||
<div class="body"><p>Il lato PC funziona con qualsiasi firmware: app e driver usano solo TCP, FTP e memoria condivisa. La parte legata alla versione è il payload della console, sviluppato e tarato sul <b>13.60</b> usando gli ancoraggi del display engine di quel firmware. Su altre versioni può servire un piccolo porting per ritrovare gli stessi ancoraggi. <a href="https://github.com/fstat13/PSCapture/issues">Apri una issue</a> indicando il tuo firmware.</p></div></details>
|
||||
<details><summary>Perché in chiamata su Discord la webcam è a 720p?</summary>
|
||||
<div class="body"><p>È il motore vocale di Discord: chiede 1280×720 a ogni webcam. In OBS, PS5 Capture va a 1920×1080 pieni. La condivisione schermo di Discord arriva a 1080p60 con Nitro.</p></div></details>
|
||||
<details><summary>Servono una capture card o il Remote Play?</summary>
|
||||
<div class="body"><p>No. PSCapture non usa mai il Remote Play: il payload legge il display engine direttamente sulla console e manda i frame grezzi sulla tua rete locale.</p></div></details>
|
||||
<details><summary>Cattura anche l'audio del gioco?</summary>
|
||||
<div class="body"><p>Non ancora. Per ora PSCapture trasmette solo il video.</p></div></details>
|
||||
<details><summary>Funziona su macOS o Linux?</summary>
|
||||
<div class="body"><p>Il lato PC è solo per Windows, perché la webcam è un dispositivo DirectShow.</p></div></details>
|
||||
<details><summary>Come si disinstalla?</summary>
|
||||
<div class="body"><ol>
|
||||
<li>Chiudi l'app.</li>
|
||||
<li>Esegui <code>rundll32 "%ProgramFiles%\PSCapture\pscapture.dll",DllUnregisterServer</code> per rimuovere la webcam.</li>
|
||||
<li>Cancella <code>C:\Program Files\PSCapture</code>.</li>
|
||||
<li>Facoltativo, sulla console: cancella <code>/data/pldmgr/payloads/pscapture/</code> così l'autoloader smette di avviarlo.</li>
|
||||
</ol></div></details>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="band reveal">
|
||||
<div>
|
||||
<h2>Ti è utile? Lascia una stella.</h2>
|
||||
<p>PSCapture è gratuito. Una stella su GitHub aiuta altri appassionati di homebrew PS5 a trovarlo.</p>
|
||||
</div>
|
||||
<div class="cta">
|
||||
<a class="btn btn-primary" href="https://github.com/fstat13/PSCapture"><svg class="i"><use href="#i-star"/></svg>Metti una stella <span class="stars" data-stars hidden></span></a>
|
||||
<a class="btn btn-ghost" href="https://github.com/fstat13/PSCapture/issues">Segnala un problema</a>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="wrap">
|
||||
<div class="foot">
|
||||
<div>
|
||||
<a class="logo" href="#top"><img src="pscapture.png" alt="">PSCapture</a>
|
||||
<p>Lo schermo della PS5 ovunque nella tua rete. Senza card, senza cloud.<br>di <b>fstat13</b></p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Guide</h4>
|
||||
<ul>
|
||||
<li><a href="https://github.com/fstat13/PSCapture/blob/main/docs/GUIDE.it.md">Guida passo-passo</a></li>
|
||||
<li><a href="https://github.com/fstat13/PSCapture/blob/main/docs/BUILD.it.md">Build dai sorgenti</a></li>
|
||||
<li><a href="https://github.com/fstat13/PSCapture/blob/main/docs/INSTALLER.md">Cosa fa l'installer (EN)</a></li>
|
||||
<li><a href="https://github.com/fstat13/PSCapture/blob/main/docs/REVERSE_ENGINEERING.md">Reverse engineering (EN)</a></li>
|
||||
<li><a href="https://github.com/fstat13/PSCapture/blob/main/docs/ENGINEERING_LOG.md">Engineering log (EN)</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Progetto</h4>
|
||||
<ul>
|
||||
<li><a href="https://github.com/fstat13/PSCapture">GitHub</a></li>
|
||||
<li><a href="https://github.com/fstat13/PSCapture/releases">Releases</a></li>
|
||||
<li><a href="https://github.com/fstat13/PSCapture/issues">Issues</a></li>
|
||||
<li><a href="https://github.com/fstat13/PSCapture/blob/main/LICENSE">Licenza</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="legal">
|
||||
<span>© 2026 fstat13 · source-available</span>
|
||||
<span>Non affiliato a Sony Interactive Entertainment. “PlayStation” e “PS5” sono marchi dei rispettivi proprietari.</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/* PSCapture — page behaviour shared by index.html / index.it.html */
|
||||
(function () {
|
||||
var doc = document.documentElement;
|
||||
doc.classList.remove('no-js');
|
||||
var it = doc.lang === 'it';
|
||||
var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
|
||||
/* nav border once the page scrolls */
|
||||
var nav = document.querySelector('.nav');
|
||||
function onScroll() { nav.classList.toggle('scrolled', window.scrollY > 8); }
|
||||
window.addEventListener('scroll', onScroll, { passive: true });
|
||||
onScroll();
|
||||
|
||||
/* app mock: replay the real start-up log, then flip the pill to STREAMING */
|
||||
var log = document.querySelector('.app-log');
|
||||
var pill = document.querySelector('.pill');
|
||||
if (log && pill) {
|
||||
var lines = Array.prototype.slice.call(log.children);
|
||||
var live = function () {
|
||||
pill.classList.add('live');
|
||||
pill.lastChild.textContent = ' STREAMING 29.8 fps';
|
||||
};
|
||||
/* the HTML ships in the final state, so no-JS and reduced-motion visitors see it as-is */
|
||||
if (!reduce) {
|
||||
pill.classList.remove('live');
|
||||
pill.lastChild.textContent = ' AVVIO...';
|
||||
lines.forEach(function (l) { l.style.display = 'none'; });
|
||||
var i = 0;
|
||||
(function next() {
|
||||
if (i < lines.length) {
|
||||
lines[i].style.display = '';
|
||||
i++;
|
||||
setTimeout(next, i === 1 ? 500 : 420 + Math.random() * 380);
|
||||
} else {
|
||||
live();
|
||||
}
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
/* copy buttons on code blocks (prompts and comments are not copied) */
|
||||
document.querySelectorAll('.codeblock').forEach(function (block) {
|
||||
var btn = document.createElement('button');
|
||||
btn.className = 'copy';
|
||||
btn.type = 'button';
|
||||
btn.textContent = it ? 'Copia' : 'Copy';
|
||||
btn.addEventListener('click', function () {
|
||||
var pre = block.querySelector('pre').cloneNode(true);
|
||||
pre.querySelectorAll('.p, .c').forEach(function (n) { n.remove(); });
|
||||
var text = pre.textContent.split('\n')
|
||||
.map(function (s) { return s.replace(/\s+$/, ''); })
|
||||
.filter(function (s) { return s.length; })
|
||||
.join('\n');
|
||||
navigator.clipboard.writeText(text).then(function () {
|
||||
btn.textContent = it ? 'Copiato ✓' : 'Copied ✓';
|
||||
btn.classList.add('done');
|
||||
setTimeout(function () {
|
||||
btn.textContent = it ? 'Copia' : 'Copy';
|
||||
btn.classList.remove('done');
|
||||
}, 1600);
|
||||
});
|
||||
});
|
||||
block.appendChild(btn);
|
||||
});
|
||||
|
||||
/* troubleshooting filter */
|
||||
var q = document.getElementById('q');
|
||||
if (q) {
|
||||
var issues = document.querySelectorAll('.issue');
|
||||
var none = document.querySelector('.noresult');
|
||||
q.addEventListener('input', function () {
|
||||
var words = q.value.toLowerCase().trim().split(/\s+/).filter(Boolean);
|
||||
var shown = 0;
|
||||
issues.forEach(function (el) {
|
||||
var txt = el.textContent.toLowerCase();
|
||||
var ok = words.every(function (w) { return txt.indexOf(w) !== -1; });
|
||||
el.hidden = !ok;
|
||||
if (ok) shown++;
|
||||
});
|
||||
none.hidden = shown !== 0;
|
||||
});
|
||||
}
|
||||
|
||||
/* live star count on the GitHub buttons */
|
||||
fetch('https://api.github.com/repos/fstat13/PSCapture')
|
||||
.then(function (r) { return r.ok ? r.json() : null; })
|
||||
.then(function (d) {
|
||||
if (!d || typeof d.stargazers_count !== 'number') return;
|
||||
document.querySelectorAll('[data-stars]').forEach(function (el) {
|
||||
el.textContent = d.stargazers_count.toLocaleString(doc.lang);
|
||||
el.hidden = false;
|
||||
});
|
||||
})
|
||||
.catch(function () {});
|
||||
|
||||
/* fade sections in as they scroll into view */
|
||||
if ('IntersectionObserver' in window && !reduce) {
|
||||
var io = new IntersectionObserver(function (entries) {
|
||||
entries.forEach(function (e) {
|
||||
if (e.isIntersecting) { e.target.classList.add('in'); io.unobserve(e.target); }
|
||||
});
|
||||
}, { rootMargin: '0px 0px -8% 0px' });
|
||||
document.querySelectorAll('.reveal').forEach(function (el) { io.observe(el); });
|
||||
} else {
|
||||
document.querySelectorAll('.reveal').forEach(function (el) { el.classList.add('in'); });
|
||||
}
|
||||
})();
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 227 KiB |
+395
@@ -0,0 +1,395 @@
|
||||
/* PSCapture — shared stylesheet for index.html / index.it.html */
|
||||
|
||||
:root{
|
||||
color-scheme: dark;
|
||||
--bg:#07090d;
|
||||
--bg-2:#0b0e14;
|
||||
--surface:#10141c;
|
||||
--surface-2:#151b25;
|
||||
--surface-3:#1b2230;
|
||||
--line:#1f2733;
|
||||
--line-2:#2a3445;
|
||||
--fg:#eef2f8;
|
||||
--fg-2:#c3cbd8;
|
||||
--dim:#8a95a8;
|
||||
--dim-2:#5f6b7f;
|
||||
--accent:#2986eb; /* same blue as the app's START button */
|
||||
--accent-2:#5aa8ff;
|
||||
--accent-glow:rgba(41,134,235,.35);
|
||||
--violet:#7c5cff;
|
||||
--green:#22c55e;
|
||||
--amber:#f59e0b;
|
||||
--red:#ef4444;
|
||||
--radius:16px;
|
||||
--radius-sm:10px;
|
||||
--maxw:1120px;
|
||||
--font:"Inter",system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
|
||||
--mono:"JetBrains Mono",ui-monospace,Consolas,"Cascadia Mono",monospace;
|
||||
}
|
||||
|
||||
*,*::before,*::after{box-sizing:border-box}
|
||||
html{scroll-behavior:smooth;scroll-padding-top:84px;-webkit-text-size-adjust:100%}
|
||||
body{
|
||||
margin:0;background:var(--bg);color:var(--fg);
|
||||
font:16px/1.65 var(--font);
|
||||
-webkit-font-smoothing:antialiased;
|
||||
overflow-x:hidden;
|
||||
}
|
||||
img{max-width:100%;display:block}
|
||||
a{color:var(--accent-2);text-decoration:none}
|
||||
a:hover{text-decoration:underline;text-underline-offset:3px}
|
||||
b,strong{color:var(--fg);font-weight:600}
|
||||
::selection{background:var(--accent);color:#fff}
|
||||
|
||||
.wrap{max-width:var(--maxw);margin:0 auto;padding:0 24px}
|
||||
@media (max-width:600px){.wrap{padding:0 16px}}
|
||||
|
||||
/* ───────── nav ───────── */
|
||||
.nav{
|
||||
position:sticky;top:0;z-index:50;
|
||||
background:rgba(7,9,13,.72);
|
||||
backdrop-filter:saturate(160%) blur(14px);
|
||||
-webkit-backdrop-filter:saturate(160%) blur(14px);
|
||||
border-bottom:1px solid transparent;
|
||||
transition:border-color .2s;
|
||||
}
|
||||
.nav.scrolled{border-bottom-color:var(--line)}
|
||||
.nav .wrap{display:flex;align-items:center;gap:20px;height:64px}
|
||||
.logo{display:flex;align-items:center;gap:10px;color:var(--fg);font-weight:700;letter-spacing:-.01em}
|
||||
.logo:hover{text-decoration:none}
|
||||
.logo img{width:30px;height:30px;border-radius:8px}
|
||||
.nav-links{display:flex;gap:4px;margin-left:auto}
|
||||
.nav-links a{color:var(--dim);font-size:14px;padding:6px 10px;border-radius:8px}
|
||||
.nav-links a:hover{color:var(--fg);background:var(--surface);text-decoration:none}
|
||||
.nav-right{display:flex;align-items:center;gap:8px}
|
||||
.lang{font-size:13px;color:var(--dim);border:1px solid var(--line-2);border-radius:999px;padding:4px 11px}
|
||||
.lang:hover{color:var(--fg);text-decoration:none;border-color:var(--dim-2)}
|
||||
@media (max-width:860px){.nav-links{display:none}.nav-right{margin-left:auto}}
|
||||
|
||||
/* ───────── buttons ───────── */
|
||||
.btn{
|
||||
display:inline-flex;align-items:center;gap:9px;
|
||||
font:600 15px/1 var(--font);
|
||||
padding:13px 20px;border-radius:12px;border:1px solid transparent;
|
||||
cursor:pointer;transition:transform .15s,background .15s,border-color .15s,box-shadow .15s;
|
||||
white-space:nowrap;
|
||||
}
|
||||
.btn:hover{text-decoration:none;transform:translateY(-1px)}
|
||||
.btn svg{width:18px;height:18px;flex:none}
|
||||
.btn-primary{background:var(--accent);color:#fff;box-shadow:0 8px 30px -8px var(--accent-glow),inset 0 1px 0 rgba(255,255,255,.18)}
|
||||
.btn-primary:hover{background:#3592f5}
|
||||
.btn-ghost{background:var(--surface);color:var(--fg);border-color:var(--line-2)}
|
||||
.btn-ghost:hover{border-color:var(--dim-2)}
|
||||
.btn-sm{padding:8px 13px;font-size:13.5px;border-radius:9px}
|
||||
|
||||
/* ───────── hero ───────── */
|
||||
.hero{position:relative;padding:72px 0 56px;isolation:isolate}
|
||||
.hero::before{
|
||||
content:"";position:absolute;inset:-64px 0 0;z-index:-1;
|
||||
background:
|
||||
radial-gradient(600px 380px at 12% 8%, rgba(41,134,235,.22), transparent 70%),
|
||||
radial-gradient(520px 360px at 88% 18%, rgba(124,92,255,.16), transparent 70%);
|
||||
}
|
||||
.hero::after{
|
||||
content:"";position:absolute;inset:-64px 0 0;z-index:-1;
|
||||
background-image:linear-gradient(var(--line) 1px,transparent 1px),linear-gradient(90deg,var(--line) 1px,transparent 1px);
|
||||
background-size:56px 56px;opacity:.28;
|
||||
-webkit-mask-image:radial-gradient(ellipse 70% 60% at 50% 20%,#000 30%,transparent 75%);
|
||||
mask-image:radial-gradient(ellipse 70% 60% at 50% 20%,#000 30%,transparent 75%);
|
||||
}
|
||||
.hero-grid{display:grid;grid-template-columns:1fr 1.08fr;gap:56px;align-items:center}
|
||||
@media (max-width:960px){.hero-grid{grid-template-columns:1fr;gap:44px}}
|
||||
.eyebrow{
|
||||
display:inline-flex;align-items:center;gap:8px;
|
||||
font-size:13px;color:var(--fg-2);
|
||||
border:1px solid var(--line-2);background:rgba(16,20,28,.7);
|
||||
padding:5px 12px 5px 8px;border-radius:999px;
|
||||
}
|
||||
.eyebrow .tagv{background:var(--accent);color:#fff;font-weight:700;font-size:11.5px;padding:2px 8px;border-radius:999px}
|
||||
.hero h1{
|
||||
font-size:clamp(38px,5.6vw,62px);line-height:1.04;letter-spacing:-.035em;
|
||||
margin:20px 0 18px;font-weight:800;
|
||||
}
|
||||
.grad{background:linear-gradient(100deg,#fff 10%,var(--accent-2) 55%,var(--violet));-webkit-background-clip:text;background-clip:text;color:transparent}
|
||||
.lead{font-size:18.5px;color:var(--fg-2);margin:0 0 30px;max-width:540px}
|
||||
.cta{display:flex;gap:12px;flex-wrap:wrap}
|
||||
.hero-meta{display:flex;flex-wrap:wrap;gap:8px 18px;margin-top:26px;color:var(--dim);font-size:14px}
|
||||
.hero-meta span{display:inline-flex;align-items:center;gap:7px}
|
||||
.hero-meta svg{width:16px;height:16px;color:var(--green)}
|
||||
|
||||
/* ───────── app mock ───────── */
|
||||
.app{
|
||||
position:relative;
|
||||
background:#10141b;border:1px solid var(--line-2);border-radius:14px;
|
||||
box-shadow:0 40px 80px -30px rgba(0,0,0,.8),0 0 0 1px rgba(255,255,255,.02),0 0 120px -40px var(--accent-glow);
|
||||
overflow:hidden;font-size:13px;
|
||||
}
|
||||
.app-bar{display:flex;align-items:center;gap:10px;background:#0b0e13;border-bottom:1px solid #202733;padding:10px 14px}
|
||||
.app-bar img{width:18px;height:18px;border-radius:4px}
|
||||
.app-bar .t{color:var(--fg-2);font-size:12.5px;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.app-bar .w{display:flex;gap:14px;color:var(--dim-2);font-size:12px}
|
||||
.app-body{padding:18px 18px 16px}
|
||||
.app-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:16px}
|
||||
.app-title{font-weight:700;font-size:17px;letter-spacing:-.01em}
|
||||
.app-title small{display:block;color:var(--dim);font-weight:400;font-size:12px}
|
||||
.pill{
|
||||
display:inline-flex;align-items:center;gap:8px;white-space:nowrap;
|
||||
background:#1a2130;border:1px solid #273042;border-radius:999px;
|
||||
padding:6px 12px;font:600 11.5px/1 var(--mono);letter-spacing:.02em;color:var(--amber);
|
||||
transition:color .3s;
|
||||
}
|
||||
.pill i{width:8px;height:8px;border-radius:50%;background:currentColor;box-shadow:0 0 0 0 currentColor;animation:pulse 1.6s infinite}
|
||||
.pill.live{color:var(--green)}
|
||||
@keyframes pulse{0%{box-shadow:0 0 0 0 color-mix(in srgb,currentColor 60%,transparent)}70%{box-shadow:0 0 0 7px transparent}100%{box-shadow:0 0 0 0 transparent}}
|
||||
.app-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:10px}
|
||||
.app-lbl{color:var(--dim);font-size:12px}
|
||||
.app-in{background:#0c0f15;border:1px solid #202733;border-radius:7px;padding:6px 10px;color:#cbd3e0;font:12px var(--mono)}
|
||||
.app-btn{background:#1a2029;border:1px solid #2c3646;border-radius:7px;padding:6px 11px;color:var(--fg-2);font-size:12px}
|
||||
.app-btn.big{background:var(--accent);border-color:var(--accent);color:#fff;font-weight:700;padding:12px 22px;font-size:13px;letter-spacing:.02em}
|
||||
.app-btn.stop{padding:12px 18px;font-weight:600}
|
||||
.app-log{
|
||||
margin-top:14px;background:#0c0f15;border:1px solid #202733;border-radius:9px;
|
||||
padding:12px 14px;height:196px;overflow:hidden;
|
||||
font:11.5px/1.75 var(--mono);color:#aeb8c8;
|
||||
display:flex;flex-direction:column;justify-content:flex-end;
|
||||
}
|
||||
.app-log div{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;animation:logIn .35s ease-out both}
|
||||
.app-log .ok{color:#7ee2a5}
|
||||
.app-log .hl{color:#8fc3ff}
|
||||
@keyframes logIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
|
||||
@media (max-width:520px){
|
||||
.app-bar .w{display:none}
|
||||
.app-knobs{display:none}
|
||||
}
|
||||
|
||||
/* ───────── sections ───────── */
|
||||
section{padding:84px 0 0}
|
||||
.sec-head{max-width:720px;margin-bottom:34px}
|
||||
.kicker{font:600 12.5px/1 var(--mono);letter-spacing:.12em;text-transform:uppercase;color:var(--accent-2);margin-bottom:14px}
|
||||
h2{font-size:clamp(28px,3.6vw,40px);line-height:1.12;letter-spacing:-.028em;margin:0 0 14px;font-weight:750}
|
||||
h3{font-size:18px;letter-spacing:-.01em;margin:0 0 8px;font-weight:650}
|
||||
.sub{color:var(--dim);font-size:17px;margin:0}
|
||||
p{margin:0 0 14px}
|
||||
|
||||
/* stats */
|
||||
.stats{display:grid;grid-template-columns:repeat(4,1fr);border:1px solid var(--line);border-radius:var(--radius);background:var(--bg-2);overflow:hidden;margin-top:12px}
|
||||
.stat{padding:22px 24px;border-left:1px solid var(--line)}
|
||||
.stat:first-child{border-left:0}
|
||||
.stat .n{font-size:28px;font-weight:750;letter-spacing:-.03em}
|
||||
.stat .l{color:var(--dim);font-size:13.5px}
|
||||
@media (max-width:760px){.stats{grid-template-columns:repeat(2,1fr)}.stat:nth-child(3){border-left:0}.stat:nth-child(n+3){border-top:1px solid var(--line)}}
|
||||
|
||||
/* feature cards */
|
||||
.grid{display:grid;gap:16px}
|
||||
.g3{grid-template-columns:repeat(3,1fr)}
|
||||
.g2{grid-template-columns:repeat(2,1fr)}
|
||||
@media (max-width:900px){.g3{grid-template-columns:1fr 1fr}}
|
||||
@media (max-width:640px){.g3,.g2{grid-template-columns:1fr}}
|
||||
.card{
|
||||
position:relative;background:linear-gradient(180deg,var(--surface),var(--bg-2));
|
||||
border:1px solid var(--line);border-radius:var(--radius);padding:24px;
|
||||
transition:border-color .2s,transform .2s;
|
||||
}
|
||||
.card:hover{border-color:var(--line-2)}
|
||||
.card p{color:var(--dim);font-size:15px;margin:0}
|
||||
.ico{
|
||||
width:42px;height:42px;border-radius:11px;display:grid;place-items:center;margin-bottom:16px;
|
||||
background:rgba(41,134,235,.12);color:var(--accent-2);border:1px solid rgba(41,134,235,.25);
|
||||
}
|
||||
.ico svg{width:21px;height:21px}
|
||||
.ico.v{background:rgba(124,92,255,.12);color:#a996ff;border-color:rgba(124,92,255,.28)}
|
||||
.ico.g{background:rgba(34,197,94,.1);color:#5fdc8f;border-color:rgba(34,197,94,.25)}
|
||||
.ico.a{background:rgba(245,158,11,.1);color:#fbbf4a;border-color:rgba(245,158,11,.25)}
|
||||
|
||||
/* pipeline */
|
||||
.pipe{display:grid;grid-template-columns:1fr auto 1fr auto 1fr auto 1fr;align-items:stretch;gap:0}
|
||||
.node{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);padding:20px}
|
||||
.node .where{font:600 11px/1 var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--dim-2);margin-bottom:10px}
|
||||
.node h3{font-size:16px}
|
||||
.node p{color:var(--dim);font-size:14px;margin:0}
|
||||
.node.end{background:linear-gradient(160deg,rgba(41,134,235,.14),var(--surface) 60%);border-color:rgba(41,134,235,.35)}
|
||||
.chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:12px}
|
||||
.chip{font-size:12px;color:var(--fg-2);background:var(--surface-3);border:1px solid var(--line-2);border-radius:999px;padding:3px 10px}
|
||||
.link{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:0 10px;color:var(--dim-2);min-width:64px}
|
||||
.link span{font:500 10.5px/1.2 var(--mono);text-align:center;margin-bottom:6px;color:var(--dim)}
|
||||
.link .line{position:relative;width:100%;height:2px;background:linear-gradient(90deg,var(--line-2),var(--accent),var(--line-2));background-size:200% 100%;animation:flow 2.2s linear infinite;border-radius:2px}
|
||||
.link .line::after{content:"";position:absolute;right:-1px;top:-4px;border:5px solid transparent;border-left-color:var(--accent);border-right:0}
|
||||
@keyframes flow{from{background-position:100% 0}to{background-position:-100% 0}}
|
||||
@media (max-width:960px){
|
||||
.pipe{grid-template-columns:1fr}
|
||||
.link{flex-direction:row;gap:10px;padding:10px 0;min-height:44px}
|
||||
.link span{margin:0;order:2}
|
||||
.link .line{width:2px;height:28px;background:linear-gradient(180deg,var(--line-2),var(--accent),var(--line-2));background-size:100% 200%}
|
||||
.link .line::after{right:-4px;top:auto;bottom:-1px;border:5px solid transparent;border-top-color:var(--accent);border-bottom:0}
|
||||
}
|
||||
|
||||
/* requirements */
|
||||
.req{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);padding:24px}
|
||||
.req-h{display:flex;align-items:center;gap:12px;margin-bottom:16px}
|
||||
.req-h .ico{margin:0}
|
||||
.req-h h3{margin:0}
|
||||
.req ul{list-style:none;margin:0;padding:0}
|
||||
.req li{display:flex;gap:12px;padding:11px 0;border-top:1px solid var(--line);font-size:15px;color:var(--fg-2)}
|
||||
.req li:first-child{border-top:0;padding-top:0}
|
||||
.req li svg{width:18px;height:18px;flex:none;margin-top:3px;color:var(--green)}
|
||||
.req li.opt svg{color:var(--dim-2)}
|
||||
.req li small{color:var(--dim);display:block;font-size:13.5px}
|
||||
.tagopt{font:600 10.5px/1 var(--mono);color:var(--dim);border:1px solid var(--line-2);border-radius:5px;padding:2px 6px;margin-left:6px;vertical-align:2px;text-transform:uppercase;letter-spacing:.06em}
|
||||
|
||||
/* steps */
|
||||
.steps{list-style:none;margin:0;padding:0;counter-reset:s;position:relative}
|
||||
.steps>li{counter-increment:s;position:relative;padding:0 0 30px 68px}
|
||||
.steps>li::before{
|
||||
content:counter(s);position:absolute;left:0;top:-2px;
|
||||
width:44px;height:44px;border-radius:13px;display:grid;place-items:center;
|
||||
font:700 17px/1 var(--font);color:#fff;
|
||||
background:linear-gradient(160deg,var(--accent),#1a5fb4);
|
||||
box-shadow:0 8px 24px -8px var(--accent-glow),inset 0 1px 0 rgba(255,255,255,.2);
|
||||
}
|
||||
.steps>li::after{content:"";position:absolute;left:21px;top:50px;bottom:6px;width:2px;background:linear-gradient(var(--line-2),transparent)}
|
||||
.steps>li:last-child::after{display:none}
|
||||
.steps h3{margin:6px 0 6px;font-size:18.5px}
|
||||
.steps p{color:var(--dim);margin:0}
|
||||
|
||||
/* callouts */
|
||||
.callout{display:flex;gap:14px;border-radius:var(--radius);padding:18px 20px;border:1px solid;font-size:15px}
|
||||
.callout svg{width:20px;height:20px;flex:none;margin-top:2px}
|
||||
.callout p{margin:0;color:inherit}
|
||||
.callout.info{background:rgba(41,134,235,.07);border-color:rgba(41,134,235,.3);color:#cfe3ff}
|
||||
.callout.info svg{color:var(--accent-2)}
|
||||
.callout.warn{background:rgba(245,158,11,.06);border-color:rgba(245,158,11,.3);color:#f6e2bd}
|
||||
.callout.warn svg{color:var(--amber)}
|
||||
|
||||
/* terminal-style log legend */
|
||||
.term{background:#0a0d12;border:1px solid var(--line);border-radius:var(--radius);overflow:hidden}
|
||||
.term-bar{display:flex;align-items:center;gap:8px;padding:11px 16px;border-bottom:1px solid var(--line);background:var(--bg-2);font:12px var(--mono);color:var(--dim)}
|
||||
.term-bar i{width:10px;height:10px;border-radius:50%;background:var(--line-2)}
|
||||
.term-bar b{margin-left:8px;font-weight:500;color:var(--dim)}
|
||||
.term-row{display:grid;grid-template-columns:minmax(0,1.25fr) minmax(0,1fr);gap:20px;padding:14px 18px;border-top:1px solid var(--line)}
|
||||
.term-row:first-of-type{border-top:0}
|
||||
.term-row code{background:none;border:0;padding:0;color:#9fd0ff;font-size:13px;display:block;word-break:break-word}
|
||||
.term-row .m{color:var(--fg-2);font-size:14.5px}
|
||||
.term-row.ok code{color:#7ee2a5}
|
||||
@media (max-width:700px){.term-row{grid-template-columns:1fr;gap:6px}}
|
||||
|
||||
/* app targets */
|
||||
.target{display:flex;flex-direction:column}
|
||||
.target .res{margin-top:auto;padding-top:16px;display:flex;align-items:center;justify-content:space-between;gap:10px;border-top:1px dashed var(--line-2);margin-top:18px;font-size:13.5px;color:var(--dim)}
|
||||
.target .res b{font:700 15px var(--mono);color:var(--fg)}
|
||||
.target .path{font-size:14px;color:var(--fg-2);margin-top:6px}
|
||||
|
||||
/* knobs */
|
||||
.knob .k{display:inline-block;font:600 14px var(--mono);color:var(--fg);background:var(--surface-3);border:1px solid var(--line-2);border-radius:7px;padding:3px 9px;margin-bottom:12px}
|
||||
.knob ul{margin:12px 0 0;padding-left:18px;color:var(--dim);font-size:14.5px}
|
||||
.knob li{margin:4px 0}
|
||||
|
||||
/* troubleshooting */
|
||||
.search{position:relative;margin-bottom:16px}
|
||||
.search svg{position:absolute;left:16px;top:50%;transform:translateY(-50%);width:18px;height:18px;color:var(--dim-2)}
|
||||
.search input{
|
||||
width:100%;font:15px var(--font);color:var(--fg);
|
||||
background:var(--surface);border:1px solid var(--line-2);border-radius:12px;
|
||||
padding:14px 16px 14px 46px;outline:none;transition:border-color .15s,box-shadow .15s;
|
||||
}
|
||||
.search input::placeholder{color:var(--dim-2)}
|
||||
.search input:focus{border-color:var(--accent);box-shadow:0 0 0 4px rgba(41,134,235,.15)}
|
||||
.issues{display:grid;gap:10px}
|
||||
.issue{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.15fr);gap:24px;background:var(--surface);border:1px solid var(--line);border-radius:14px;padding:18px 20px}
|
||||
.issue .sym{font-weight:600;color:var(--fg)}
|
||||
.issue .sym code{font-weight:400}
|
||||
.issue .fix{color:var(--dim);font-size:15px}
|
||||
.issue .fix b{color:var(--fg-2)}
|
||||
.issue .cause{display:block;font-size:12px;font-family:var(--mono);color:var(--dim-2);text-transform:uppercase;letter-spacing:.08em;margin-bottom:4px}
|
||||
.issue[hidden]{display:none}
|
||||
.noresult{color:var(--dim);padding:18px;text-align:center;border:1px dashed var(--line-2);border-radius:14px}
|
||||
@media (max-width:700px){.issue{grid-template-columns:1fr;gap:10px}}
|
||||
|
||||
/* code */
|
||||
code,kbd{
|
||||
font:.86em/1.5 var(--mono);color:#cfe3ff;
|
||||
background:var(--surface-3);border:1px solid var(--line-2);border-radius:6px;padding:1px 6px;
|
||||
overflow-wrap:anywhere;
|
||||
}
|
||||
.codeblock{position:relative;margin:12px 0 0}
|
||||
.codeblock pre{
|
||||
margin:0;background:#0a0d12;border:1px solid var(--line);border-radius:14px;
|
||||
padding:18px 20px;overflow-x:auto;font:13.5px/1.7 var(--mono);color:#d6e4f5;
|
||||
}
|
||||
.codeblock pre .c{color:#5d6b80}
|
||||
.codeblock pre .p{color:var(--accent-2);user-select:none}
|
||||
.copy{
|
||||
position:absolute;top:10px;right:10px;
|
||||
font:500 12px var(--font);color:var(--dim);
|
||||
background:var(--surface-2);border:1px solid var(--line-2);border-radius:8px;
|
||||
padding:5px 10px;cursor:pointer;opacity:.85;transition:all .15s;
|
||||
}
|
||||
.copy:hover{color:var(--fg);opacity:1}
|
||||
.copy.done{color:var(--green);border-color:rgba(34,197,94,.4)}
|
||||
.build-step{margin-top:34px}
|
||||
.build-step:first-child{margin-top:0}
|
||||
.build-step .num{font:600 12px var(--mono);color:var(--accent-2);letter-spacing:.08em;text-transform:uppercase}
|
||||
.build-step h3{margin:6px 0 6px;font-size:20px}
|
||||
.build-step>p{color:var(--dim);margin:0}
|
||||
|
||||
/* faq */
|
||||
.faq{display:grid;gap:10px}
|
||||
details{background:var(--surface);border:1px solid var(--line);border-radius:14px;transition:border-color .2s}
|
||||
details[open]{border-color:var(--line-2)}
|
||||
summary{
|
||||
list-style:none;cursor:pointer;padding:18px 54px 18px 20px;position:relative;
|
||||
font-weight:600;font-size:16px;
|
||||
}
|
||||
summary::-webkit-details-marker{display:none}
|
||||
summary::after{
|
||||
content:"";position:absolute;right:22px;top:50%;width:9px;height:9px;
|
||||
border-right:2px solid var(--dim);border-bottom:2px solid var(--dim);
|
||||
transform:translateY(-70%) rotate(45deg);transition:transform .2s;
|
||||
}
|
||||
details[open] summary::after{transform:translateY(-30%) rotate(-135deg)}
|
||||
details .body{padding:0 20px 18px;color:var(--dim);font-size:15px}
|
||||
details .body p:last-child{margin:0}
|
||||
details .body ol{margin:0;padding-left:20px}
|
||||
details .body li{margin:6px 0}
|
||||
|
||||
/* cta band */
|
||||
.band{
|
||||
margin-top:96px;position:relative;overflow:hidden;
|
||||
border:1px solid rgba(41,134,235,.3);border-radius:22px;padding:48px 40px;
|
||||
background:
|
||||
radial-gradient(500px 260px at 0% 0%,rgba(41,134,235,.25),transparent 70%),
|
||||
radial-gradient(500px 260px at 100% 100%,rgba(124,92,255,.18),transparent 70%),
|
||||
var(--bg-2);
|
||||
display:flex;align-items:center;justify-content:space-between;gap:28px;flex-wrap:wrap;
|
||||
}
|
||||
.band h2{margin:0 0 6px;font-size:clamp(24px,3vw,32px)}
|
||||
.band p{margin:0;color:var(--fg-2)}
|
||||
@media (max-width:600px){.band{padding:32px 22px}}
|
||||
|
||||
/* footer */
|
||||
footer{margin-top:80px;border-top:1px solid var(--line);padding:44px 0 56px;color:var(--dim);font-size:14px}
|
||||
.foot{display:grid;grid-template-columns:1.4fr 1fr 1fr;gap:32px}
|
||||
@media (max-width:760px){.foot{grid-template-columns:1fr}}
|
||||
.foot h4{margin:0 0 12px;font-size:12px;font-family:var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--dim-2);font-weight:600}
|
||||
.foot ul{list-style:none;margin:0;padding:0}
|
||||
.foot li{margin:7px 0}
|
||||
.foot a{color:var(--fg-2)}
|
||||
.foot .logo{margin-bottom:12px}
|
||||
.legal{margin-top:36px;padding-top:22px;border-top:1px solid var(--line);font-size:12.5px;color:var(--dim-2);display:flex;justify-content:space-between;gap:16px;flex-wrap:wrap}
|
||||
|
||||
/* reveal on scroll */
|
||||
.reveal{opacity:0;transform:translateY(14px);transition:opacity .6s ease,transform .6s ease}
|
||||
.reveal.in{opacity:1;transform:none}
|
||||
.no-js .reveal{opacity:1;transform:none}
|
||||
|
||||
@media (prefers-reduced-motion:reduce){
|
||||
html{scroll-behavior:auto}
|
||||
*,*::before,*::after{animation:none!important;transition:none!important}
|
||||
.reveal{opacity:1;transform:none}
|
||||
}
|
||||
|
||||
/* star counter inside GitHub buttons */
|
||||
.stars{font:600 12.5px/1 var(--mono);color:var(--fg-2);background:var(--surface-3);border:1px solid var(--line-2);border-radius:6px;padding:3px 7px;margin-left:2px}
|
||||
.btn-primary .stars{background:rgba(255,255,255,.16);border-color:rgba(255,255,255,.22);color:#fff}
|
||||
|
||||
/* inline svg icons (sprite in each page) */
|
||||
.i{fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
|
||||
+12
-1
@@ -2545,11 +2545,22 @@ __declspec(dllexport) HRESULT WINAPI
|
||||
DllRegisterServer(void)
|
||||
{
|
||||
WCHAR dll[MAX_PATH];
|
||||
WCHAR p[512];
|
||||
WCHAR guid[64];
|
||||
HKEY k;
|
||||
|
||||
if(!GetModuleFileNameW(g_hinst_ps5cam, dll, MAX_PATH)) return E_FAIL;
|
||||
registra(HKEY_CURRENT_USER, dll); /* niente admin */
|
||||
registra(HKEY_LOCAL_MACHINE, dll); /* se siamo admin, meglio */
|
||||
return S_OK;
|
||||
/* il risultato si verifica davvero: se almeno la chiave per-utente e'
|
||||
* leggibile la registrazione e' utilizzabile (HKCR unisce HKCU+HKLM) */
|
||||
StringFromGUID2(&CLSID_Ps5Cam, guid, 64);
|
||||
wsprintfW(p, L"Software\\Classes\\CLSID\\%s\\InprocServer32", guid);
|
||||
if(RegOpenKeyExW(HKEY_CURRENT_USER, p, 0, KEY_READ, &k) == ERROR_SUCCESS) {
|
||||
RegCloseKey(k);
|
||||
return S_OK;
|
||||
}
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
__declspec(dllexport) HRESULT WINAPI
|
||||
|
||||
+471
-101
@@ -19,10 +19,12 @@
|
||||
#include <ws2tcpip.h>
|
||||
#include <shellapi.h>
|
||||
#include <windows.h>
|
||||
#include <uxtheme.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "shm.h"
|
||||
|
||||
@@ -38,9 +40,28 @@ static DWORD WINAPI lavoro_deploy(LPVOID p);
|
||||
static volatile long g_streaming;
|
||||
static HANDLE g_thread;
|
||||
static char g_dir[MAX_PATH];
|
||||
static HFONT g_font, g_font_b, g_font_t;
|
||||
static HWND g_h_sub;
|
||||
static int g_stato_on;
|
||||
static HFONT g_font, g_font_b, g_font_t, g_font_s, g_font_log;
|
||||
static HBRUSH g_br_bg, g_br_edit, g_br_pill;
|
||||
static char g_stato_text[96] = "IDLE";
|
||||
static int g_stato_level; /* 0 idle, 1 attesa, 2 streaming */
|
||||
|
||||
/* palette scura */
|
||||
#define CL_BG RGB(16, 20, 27)
|
||||
#define CL_TBAR RGB(11, 14, 19)
|
||||
#define CL_EDIT RGB(12, 15, 21)
|
||||
#define CL_LINE RGB(32, 39, 51)
|
||||
#define CL_TXT RGB(232, 237, 244)
|
||||
#define CL_DIM RGB(152, 162, 179)
|
||||
#define CL_ACC RGB(41, 134, 235)
|
||||
#define CL_ACC_D RGB(28, 100, 180)
|
||||
#define CL_GHOST RGB(26, 32, 41)
|
||||
#define CL_GHOST_D RGB(38, 46, 59)
|
||||
#define CL_GBD RGB(44, 54, 70)
|
||||
#define CL_GREEN RGB(34, 197, 94)
|
||||
#define CL_AMBER RGB(245, 158, 11)
|
||||
#define CL_DIM2 RGB(120, 130, 145)
|
||||
#define CL_PILL RGB(26, 33, 48)
|
||||
#define CL_EDTX RGB(203, 211, 224)
|
||||
|
||||
static BOOL CALLBACK
|
||||
set_font_cb(HWND c, LPARAM f)
|
||||
@@ -97,12 +118,45 @@ logUI(const char *fmt, ...)
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(b, sizeof(b), fmt, ap);
|
||||
va_end(ap);
|
||||
{
|
||||
char p[MAX_PATH];
|
||||
FILE *f;
|
||||
|
||||
snprintf(p, sizeof(p), "%s\\pscapture-app.log", g_dir);
|
||||
f = fopen(p, "a");
|
||||
if(!f) {
|
||||
char t[MAX_PATH];
|
||||
int e = errno;
|
||||
DWORD n = GetTempPathA(sizeof(t), t);
|
||||
|
||||
if(n > 0 && n < sizeof(t)) {
|
||||
snprintf(p, sizeof(p), "%sps5capture-app.log", t);
|
||||
f = fopen(p, "a");
|
||||
}
|
||||
if(f) {
|
||||
char nota[300];
|
||||
|
||||
snprintf(nota, sizeof(nota),
|
||||
"log accanto all'exe non scrivibile (errno=%d), uso: %s", e, p);
|
||||
fprintf(f, "%s\n", nota);
|
||||
}
|
||||
}
|
||||
if(f) {
|
||||
SYSTEMTIME st;
|
||||
|
||||
GetLocalTime(&st);
|
||||
fprintf(f, "[%02d:%02d:%02d] %s\n", st.wHour, st.wMinute,
|
||||
st.wSecond, b);
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
if(g_hwnd) PostMessageA(g_hwnd, WM_APP_LOG, 0, (LPARAM)_strdup(b));
|
||||
}
|
||||
|
||||
static void
|
||||
statoUI(const char *s)
|
||||
{
|
||||
if(!strcmp(s, g_stato_text)) return;
|
||||
if(g_hwnd) PostMessageA(g_hwnd, WM_APP_STATO, 0, (LPARAM)_strdup(s));
|
||||
}
|
||||
|
||||
@@ -189,6 +243,22 @@ ftp_cmd(int c, const char *cmd, char *risp, int sz)
|
||||
return buf[0] == '2' || buf[0] == '3' || buf[0] == '1' ? 0 : -1;
|
||||
}
|
||||
|
||||
/* invio completo: TCP non garantisce che una singola send() trasferisca
|
||||
* tutti i byte richiesti */
|
||||
static int
|
||||
send_all(int s, const char *d, int len)
|
||||
{
|
||||
int off = 0;
|
||||
|
||||
while(off < len) {
|
||||
int n = send((SOCKET)s, d + off, len - off, 0);
|
||||
|
||||
if(n <= 0) return -1;
|
||||
off += n;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
ftp_leggi_file(const char *ip, const char *file, char *out, int outsz)
|
||||
{
|
||||
@@ -197,7 +267,8 @@ ftp_leggi_file(const char *ip, const char *file, char *out, int outsz)
|
||||
int n;
|
||||
|
||||
if(c < 0) return -1;
|
||||
ftp_cmd(c, "PASV\r\n", buf, sizeof(buf));
|
||||
memset(buf, 0, sizeof(buf));
|
||||
if(ftp_cmd(c, "PASV\r\n", buf, sizeof(buf))) goto ko;
|
||||
{
|
||||
char *p = strchr(buf, '(');
|
||||
unsigned h1, h2, h3, h4, p1, p2;
|
||||
@@ -238,7 +309,8 @@ ftp_manda_file(const char *ip, const char *remoto, const unsigned char *d,
|
||||
char buf[4096];
|
||||
|
||||
if(c < 0) return -1;
|
||||
ftp_cmd(c, "PASV\r\n", buf, sizeof(buf));
|
||||
memset(buf, 0, sizeof(buf));
|
||||
if(ftp_cmd(c, "PASV\r\n", buf, sizeof(buf))) goto ko;
|
||||
{
|
||||
char *p = strchr(buf, '(');
|
||||
unsigned h1, h2, h3, h4, p1, p2;
|
||||
@@ -256,7 +328,7 @@ ftp_manda_file(const char *ip, const char *remoto, const unsigned char *d,
|
||||
if(recv((SOCKET)c, buf, sizeof(buf) - 1, 0) <= 0 || buf[0] != '1')
|
||||
goto ko;
|
||||
}
|
||||
if(send((SOCKET)ds, (const char *)d, len, 0) != len) goto ko;
|
||||
if(send_all(ds, (const char *)d, len)) goto ko;
|
||||
closesocket((SOCKET)ds);
|
||||
ds = -1;
|
||||
recv((SOCKET)c, buf, sizeof(buf) - 1, 0);
|
||||
@@ -373,6 +445,7 @@ feeder(LPVOID p)
|
||||
s = connetti(g_ip, port, 8000);
|
||||
if(s < 0) {
|
||||
logUI("PS5 non raggiungibile (%s), riprovo", g_ip);
|
||||
statoUI("IN ATTESA DELLA PS5 - accesa e col jailbreak avviato?");
|
||||
Sleep(2000);
|
||||
port = -1;
|
||||
continue;
|
||||
@@ -520,7 +593,12 @@ lavoro_deploy(LPVOID p)
|
||||
free(elf);
|
||||
return 1;
|
||||
}
|
||||
send((SOCKET)s, (const char *)elf, len, 0);
|
||||
if(send_all(s, (const char *)elf, len)) {
|
||||
logUI("ERRORE: invio payload all'elfldr incompleto");
|
||||
closesocket((SOCKET)s);
|
||||
free(elf);
|
||||
return 1;
|
||||
}
|
||||
/* 3) leggi lo stream di log per qualche secondo */
|
||||
{
|
||||
DWORD t0 = GetTickCount();
|
||||
@@ -539,7 +617,7 @@ lavoro_deploy(LPVOID p)
|
||||
}
|
||||
closesocket((SOCKET)s);
|
||||
free(elf);
|
||||
logUI("payload avviato — la PS5 deve mostrare la notifica PSCapture");
|
||||
logUI("payload avviato - la PS5 deve mostrare la notifica PSCapture");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -547,9 +625,6 @@ static DWORD WINAPI
|
||||
lavoro_installa(LPVOID p)
|
||||
{
|
||||
char dst[MAX_PATH], src[MAX_PATH], cmd[MAX_PATH * 2];
|
||||
STARTUPINFOA si;
|
||||
PROCESS_INFORMATION pi;
|
||||
char win[MAX_PATH];
|
||||
|
||||
(void)p;
|
||||
{
|
||||
@@ -561,43 +636,87 @@ lavoro_installa(LPVOID p)
|
||||
CreateDirectoryA(dst, NULL);
|
||||
logUI("installazione in %s", dst);
|
||||
|
||||
/* copia exe (payload embedded) e dll driver (pscapture.dll o ps5cam.dll) */
|
||||
/* il driver viaggia DENTRO l'eseguibile (risorsa RCDATA n.3): lo si
|
||||
* estrae e lo si scrive in Program Files; fallback: copia dal disco */
|
||||
snprintf(cmd, sizeof(cmd), "%s\\pscapture.dll", dst);
|
||||
{
|
||||
const char *dlls[] = { "pscapture.dll", "ps5cam.dll", NULL };
|
||||
int i;
|
||||
HRSRC r = FindResourceA(NULL, MAKEINTRESOURCEA(3), RT_RCDATA);
|
||||
int scritto = 0;
|
||||
|
||||
for(i = 0; dlls[i]; i++) {
|
||||
snprintf(src, sizeof(src), "%s\\%s", g_dir, dlls[i]);
|
||||
if(GetFileAttributesA(src) != INVALID_FILE_ATTRIBUTES) {
|
||||
snprintf(cmd, sizeof(cmd), "%s\\%s", dst, "pscapture.dll");
|
||||
CopyFileA(src, cmd, FALSE);
|
||||
logUI("driver copiato: %s", dlls[i]);
|
||||
break;
|
||||
if(r) {
|
||||
HGLOBAL g = LoadResource(NULL, r);
|
||||
DWORD n = SizeofResource(NULL, r);
|
||||
void *sd = g ? LockResource(g) : NULL;
|
||||
|
||||
if(sd && n) {
|
||||
HANDLE h = CreateFileA(cmd, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
|
||||
FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
DWORD wr = 0;
|
||||
|
||||
if(h != INVALID_HANDLE_VALUE) {
|
||||
if(WriteFile(h, sd, n, &wr, NULL) && wr == n) scritto = 1;
|
||||
CloseHandle(h);
|
||||
}
|
||||
if(scritto) {
|
||||
logUI("driver estratto dall'eseguibile (%u byte)", (unsigned)n);
|
||||
} else {
|
||||
logUI("ERRORE: driver non scrivibile in %s (serve admin?)", dst);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const char *dlls[] = { "pscapture.dll", "ps5cam.dll", NULL };
|
||||
int i;
|
||||
|
||||
for(i = 0; dlls[i]; i++) {
|
||||
snprintf(src, sizeof(src), "%s\\%s", g_dir, dlls[i]);
|
||||
if(GetFileAttributesA(src) != INVALID_FILE_ATTRIBUTES &&
|
||||
CopyFileA(src, cmd, FALSE)) {
|
||||
scritto = 1;
|
||||
logUI("driver copiato dal disco: %s", dlls[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!dlls[i]) logUI("ATTENZIONE: nessuna dll driver accanto all'exe");
|
||||
{
|
||||
char self[MAX_PATH];
|
||||
|
||||
GetModuleFileNameA(NULL, self, sizeof(self));
|
||||
snprintf(cmd, sizeof(cmd), "%s\\pscapture.exe", dst);
|
||||
CopyFileA(self, cmd, FALSE);
|
||||
logUI("eseguibile installato (payload incluso)");
|
||||
if(!CopyFileA(self, cmd, FALSE)) {
|
||||
logUI("eseguibile gia' presente in %s", dst);
|
||||
} else {
|
||||
logUI("eseguibile installato (payload e driver inclusi)");
|
||||
}
|
||||
snprintf(cmd, sizeof(cmd), "%s\\pscapture.dll", dst);
|
||||
}
|
||||
}
|
||||
|
||||
/* registra il filtro DirectShow */
|
||||
snprintf(cmd, sizeof(cmd),
|
||||
"rundll32.exe \"%s\\pscapture.dll\",DllRegisterServer", dst);
|
||||
memset(&si, 0, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
if(CreateProcessA(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
|
||||
WaitForSingleObject(pi.hProcess, 20000);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
logUI("driver registrato — la webcam 'PS5 Capture' e' pronta");
|
||||
} else {
|
||||
logUI("ERRORE registrazione driver (serve admin?)");
|
||||
/* registra il filtro DirectShow (con codici errore veri, niente
|
||||
* rundll32: se la dll non si carica lo diciamo e basta) */
|
||||
if(GetFileAttributesA(cmd) != INVALID_FILE_ATTRIBUTES) {
|
||||
HMODULE m = LoadLibraryExA(cmd, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
|
||||
|
||||
if(m) {
|
||||
typedef HRESULT (WINAPI *reg_t)(void);
|
||||
reg_t fn = (reg_t)(void *)GetProcAddress(m, "DllRegisterServer");
|
||||
|
||||
if(fn) {
|
||||
HRESULT hr = fn();
|
||||
|
||||
if(SUCCEEDED(hr)) {
|
||||
logUI("driver registrato - la webcam 'PS5 Capture' e' pronta");
|
||||
} else {
|
||||
logUI("ERRORE registrazione driver (hr=0x%08lx)", (unsigned long)hr);
|
||||
}
|
||||
} else {
|
||||
logUI("ERRORE: DllRegisterServer assente nella dll");
|
||||
}
|
||||
FreeLibrary(m);
|
||||
} else {
|
||||
logUI("ERRORE caricamento dll (err=%lu)", GetLastError());
|
||||
}
|
||||
} else {
|
||||
logUI("registrazione saltata: dll assente in %s", dst);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -658,7 +777,6 @@ WndProc(HWND h, UINT m, WPARAM w, LPARAM l)
|
||||
case WM_CREATE: {
|
||||
char b[64];
|
||||
HINSTANCE hi = g_inst;
|
||||
HWND c;
|
||||
|
||||
g_font = CreateFontA(-15, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET,
|
||||
OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY,
|
||||
@@ -666,59 +784,66 @@ WndProc(HWND h, UINT m, WPARAM w, LPARAM l)
|
||||
g_font_b = CreateFontA(-16, 0, 0, 0, FW_SEMIBOLD, 0, 0, 0, DEFAULT_CHARSET,
|
||||
OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY,
|
||||
DEFAULT_PITCH, "Segoe UI");
|
||||
g_font_t = CreateFontA(-30, 0, 0, 0, FW_SEMIBOLD, 0, 0, 0, DEFAULT_CHARSET,
|
||||
g_font_t = CreateFontA(-34, 0, 0, 0, FW_SEMIBOLD, 0, 0, 0, DEFAULT_CHARSET,
|
||||
OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY,
|
||||
DEFAULT_PITCH, "Segoe UI");
|
||||
g_font_s = CreateFontA(-14, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET,
|
||||
OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY,
|
||||
DEFAULT_PITCH, "Segoe UI");
|
||||
g_font_log = CreateFontA(-13, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET,
|
||||
OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY,
|
||||
FIXED_PITCH, "Consolas");
|
||||
|
||||
c = CreateWindowA("STATIC", "PSCapture", WS_CHILD | WS_VISIBLE,
|
||||
20, 14, 400, 42, h, 0, hi, 0);
|
||||
SendMessageA(c, WM_SETFONT, (WPARAM)g_font_t, 1);
|
||||
g_h_sub = CreateWindowA("STATIC", "PS5 screen to PC capture card - by fstat13 - v0.1",
|
||||
WS_CHILD | WS_VISIBLE, 22, 58, 600, 20, h, 0, hi, 0);
|
||||
g_br_bg = CreateSolidBrush(CL_BG);
|
||||
g_br_edit = CreateSolidBrush(CL_EDIT);
|
||||
g_br_pill = CreateSolidBrush(CL_PILL);
|
||||
|
||||
CreateWindowA("STATIC", "PS5 IP:", WS_CHILD | WS_VISIBLE,
|
||||
22, 98, 55, 22, h, 0, hi, 0);
|
||||
g_h_ip = CreateWindowA("EDIT", "", WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
|
||||
78, 96, 140, 24, h, (HMENU)ID_IP, hi, 0);
|
||||
/* riga 1: indirizzo + azioni */
|
||||
CreateWindowA("STATIC", "PS5 IP", WS_CHILD | WS_VISIBLE,
|
||||
26, 148, 50, 18, h, 0, hi, 0);
|
||||
g_h_ip = CreateWindowA("EDIT", "", WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL,
|
||||
82, 142, 150, 30, h, (HMENU)ID_IP, hi, 0);
|
||||
ini_leggi("IP", "192.168.0.36", b, sizeof(b));
|
||||
SetWindowTextA(g_h_ip, b);
|
||||
|
||||
CreateWindowA("BUTTON", "Deploy payload", WS_CHILD | WS_VISIBLE,
|
||||
238, 95, 130, 26, h, (HMENU)ID_DEPLOY, hi, 0);
|
||||
CreateWindowA("BUTTON", "Install driver", WS_CHILD | WS_VISIBLE,
|
||||
376, 95, 120, 26, h, (HMENU)ID_INSTALL, hi, 0);
|
||||
c = CreateWindowA("BUTTON", "Start", WS_CHILD | WS_VISIBLE,
|
||||
504, 95, 92, 26, h, (HMENU)ID_START, hi, 0);
|
||||
SendMessageA(c, WM_SETFONT, (WPARAM)g_font_b, 1);
|
||||
c = CreateWindowA("BUTTON", "Stop", WS_CHILD | WS_VISIBLE,
|
||||
604, 95, 92, 26, h, (HMENU)ID_STOP, hi, 0);
|
||||
SendMessageA(c, WM_SETFONT, (WPARAM)g_font_b, 1);
|
||||
CreateWindowA("BUTTON", "", WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
|
||||
250, 142, 132, 30, h, (HMENU)ID_DEPLOY, hi, 0);
|
||||
CreateWindowA("BUTTON", "", WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
|
||||
392, 142, 124, 30, h, (HMENU)ID_INSTALL, hi, 0);
|
||||
|
||||
CreateWindowA("STATIC", "gain:", WS_CHILD | WS_VISIBLE,
|
||||
22, 138, 38, 22, h, 0, hi, 0);
|
||||
g_h_gain = CreateWindowA("EDIT", "1.0", WS_CHILD | WS_VISIBLE | WS_BORDER,
|
||||
62, 136, 55, 24, h, (HMENU)ID_GAIN, hi, 0);
|
||||
/* riga 2: start/stop + regolazioni */
|
||||
CreateWindowA("BUTTON", "", WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
|
||||
26, 188, 210, 48, h, (HMENU)ID_START, hi, 0);
|
||||
CreateWindowA("BUTTON", "", WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
|
||||
250, 188, 110, 48, h, (HMENU)ID_STOP, hi, 0);
|
||||
|
||||
CreateWindowA("STATIC", "gain", WS_CHILD | WS_VISIBLE,
|
||||
388, 204, 32, 18, h, 0, hi, 0);
|
||||
g_h_gain = CreateWindowA("EDIT", "1.0", WS_CHILD | WS_VISIBLE,
|
||||
424, 199, 58, 26, h, (HMENU)ID_GAIN, hi, 0);
|
||||
ini_leggi("GAIN", "1.0", b, sizeof(b));
|
||||
SetWindowTextA(g_h_gain, b);
|
||||
CreateWindowA("STATIC", "shift px:", WS_CHILD | WS_VISIBLE,
|
||||
130, 138, 58, 22, h, 0, hi, 0);
|
||||
g_h_shift = CreateWindowA("EDIT", "0", WS_CHILD | WS_VISIBLE | WS_BORDER,
|
||||
190, 136, 55, 24, h, (HMENU)ID_SHIFT, hi, 0);
|
||||
CreateWindowA("STATIC", "shift px", WS_CHILD | WS_VISIBLE,
|
||||
496, 204, 48, 18, h, 0, hi, 0);
|
||||
g_h_shift = CreateWindowA("EDIT", "0", WS_CHILD | WS_VISIBLE,
|
||||
550, 199, 58, 26, h, (HMENU)ID_SHIFT, hi, 0);
|
||||
ini_leggi("SHIFT", "0", b, sizeof(b));
|
||||
SetWindowTextA(g_h_shift, b);
|
||||
CreateWindowA("BUTTON", "Apply", WS_CHILD | WS_VISIBLE,
|
||||
258, 135, 78, 26, h, (HMENU)ID_APPLY, hi, 0);
|
||||
CreateWindowA("BUTTON", "", WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
|
||||
622, 197, 100, 30, h, (HMENU)ID_APPLY, hi, 0);
|
||||
|
||||
g_h_stato = CreateWindowA("STATIC", "IDLE", WS_CHILD | WS_VISIBLE,
|
||||
24, 176, 660, 24, h, (HMENU)ID_STATO, hi, 0);
|
||||
g_h_log = CreateWindowA("LISTBOX", "",
|
||||
WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL |
|
||||
LBS_NOINTEGRALHEIGHT,
|
||||
22, 210, 664, 320, h, (HMENU)ID_LOG, hi, 0);
|
||||
/* log */
|
||||
g_h_log = CreateWindowA("EDIT", "",
|
||||
WS_CHILD | WS_VISIBLE | WS_VSCROLL |
|
||||
ES_MULTILINE | ES_READONLY | ES_AUTOVSCROLL,
|
||||
26, 324, 728, 312, h, (HMENU)ID_LOG, hi, 0);
|
||||
SendMessageA(g_h_log, EM_SETLIMITTEXT, 250000, 0);
|
||||
SendMessageA(g_h_log, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
|
||||
MAKELPARAM(12, 12));
|
||||
SetWindowTheme(g_h_log, L"DarkMode_Explorer", NULL);
|
||||
|
||||
EnumChildWindows(h, set_font_cb, (LPARAM)g_font);
|
||||
SendMessageA(g_h_sub, WM_SETFONT, (WPARAM)g_font, 1);
|
||||
SendMessageA(g_h_stato, WM_SETFONT, (WPARAM)g_font_b, 1);
|
||||
SendMessageA(g_h_log, WM_SETFONT, (WPARAM)g_font_log, 1);
|
||||
|
||||
logUI("PSCapture pronto: premo Start e faccio tutto da solo.");
|
||||
return 0;
|
||||
@@ -762,30 +887,223 @@ WndProc(HWND h, UINT m, WPARAM w, LPARAM l)
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
case WM_PAINT: {
|
||||
PAINTSTRUCT ps;
|
||||
HDC dc = BeginPaint(h, &ps);
|
||||
RECT r, rr;
|
||||
HBRUSH b, ob;
|
||||
HPEN op;
|
||||
HFONT old;
|
||||
|
||||
GetClientRect(h, &r);
|
||||
FillRect(dc, &r, g_br_bg);
|
||||
|
||||
/* barra titolo con logo */
|
||||
rr = r;
|
||||
rr.bottom = 44;
|
||||
b = CreateSolidBrush(CL_TBAR);
|
||||
FillRect(dc, &rr, b);
|
||||
DeleteObject(b);
|
||||
b = CreateSolidBrush(CL_ACC);
|
||||
ob = (HBRUSH)SelectObject(dc, b);
|
||||
op = (HPEN)SelectObject(dc, GetStockObject(NULL_PEN));
|
||||
RoundRect(dc, 16, 9, 42, 35, 10, 10);
|
||||
SelectObject(dc, ob);
|
||||
SelectObject(dc, op);
|
||||
DeleteObject(b);
|
||||
{
|
||||
POINT pt[3];
|
||||
|
||||
pt[0].x = 24; pt[0].y = 15;
|
||||
pt[1].x = 24; pt[1].y = 29;
|
||||
pt[2].x = 34; pt[2].y = 22;
|
||||
b = CreateSolidBrush(RGB(255, 255, 255));
|
||||
ob = (HBRUSH)SelectObject(dc, b);
|
||||
op = (HPEN)SelectObject(dc, GetStockObject(NULL_PEN));
|
||||
Polygon(dc, pt, 3);
|
||||
SelectObject(dc, ob);
|
||||
SelectObject(dc, op);
|
||||
DeleteObject(b);
|
||||
}
|
||||
SetBkMode(dc, TRANSPARENT);
|
||||
SetTextColor(dc, CL_TXT);
|
||||
old = SelectObject(dc, g_font_b);
|
||||
{
|
||||
RECT t = {52, 12, 300, 34};
|
||||
|
||||
DrawTextA(dc, "PSCapture", -1, &t, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
|
||||
}
|
||||
SelectObject(dc, old);
|
||||
{
|
||||
HPEN pn = CreatePen(PS_SOLID, 2, CL_DIM);
|
||||
int W = r.right;
|
||||
|
||||
op = (HPEN)SelectObject(dc, pn);
|
||||
MoveToEx(dc, W - 72, 22, NULL);
|
||||
LineTo(dc, W - 58, 22);
|
||||
MoveToEx(dc, W - 34, 16, NULL);
|
||||
LineTo(dc, W - 20, 30);
|
||||
MoveToEx(dc, W - 34, 30, NULL);
|
||||
LineTo(dc, W - 20, 16);
|
||||
SelectObject(dc, op);
|
||||
DeleteObject(pn);
|
||||
}
|
||||
/* hero */
|
||||
SetTextColor(dc, CL_TXT);
|
||||
old = SelectObject(dc, g_font_t);
|
||||
{
|
||||
RECT t = {26, 50, 700, 92};
|
||||
|
||||
DrawTextA(dc, "PSCapture", -1, &t, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
|
||||
}
|
||||
SelectObject(dc, old);
|
||||
old = SelectObject(dc, g_font_s);
|
||||
SetTextColor(dc, CL_DIM);
|
||||
{
|
||||
RECT t = {28, 92, 750, 114};
|
||||
|
||||
DrawTextA(dc, "PS5 screen to PC capture card - by fstat13 - v0.1",
|
||||
-1, &t, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
|
||||
}
|
||||
SelectObject(dc, old);
|
||||
b = CreateSolidBrush(CL_LINE);
|
||||
rr.left = 0;
|
||||
rr.top = 126;
|
||||
rr.right = r.right;
|
||||
rr.bottom = 127;
|
||||
FillRect(dc, &rr, b);
|
||||
DeleteObject(b);
|
||||
/* cornici dei campi e del log */
|
||||
b = CreateSolidBrush(CL_GBD);
|
||||
{
|
||||
RECT e = {81, 141, 233, 173};
|
||||
|
||||
FrameRect(dc, &e, b);
|
||||
e.left = 423; e.top = 198; e.right = 483; e.bottom = 226;
|
||||
FrameRect(dc, &e, b);
|
||||
e.left = 549; e.top = 198; e.right = 609; e.bottom = 226;
|
||||
FrameRect(dc, &e, b);
|
||||
e.left = 25; e.top = 323; e.right = 755; e.bottom = 637;
|
||||
FrameRect(dc, &e, b);
|
||||
}
|
||||
DeleteObject(b);
|
||||
/* pill di stato */
|
||||
{
|
||||
COLORREF dot = (g_stato_level == 2) ? CL_GREEN :
|
||||
(g_stato_level == 1) ? CL_AMBER : CL_DIM2;
|
||||
|
||||
b = CreateSolidBrush(CL_PILL);
|
||||
ob = (HBRUSH)SelectObject(dc, b);
|
||||
op = (HPEN)SelectObject(dc, GetStockObject(NULL_PEN));
|
||||
RoundRect(dc, 26, 258, 754, 302, 44, 44);
|
||||
SelectObject(dc, ob);
|
||||
SelectObject(dc, op);
|
||||
DeleteObject(b);
|
||||
b = CreateSolidBrush(dot);
|
||||
ob = (HBRUSH)SelectObject(dc, b);
|
||||
op = (HPEN)SelectObject(dc, GetStockObject(NULL_PEN));
|
||||
Ellipse(dc, 46, 269, 58, 281);
|
||||
SelectObject(dc, ob);
|
||||
SelectObject(dc, op);
|
||||
DeleteObject(b);
|
||||
SetTextColor(dc, (g_stato_level == 2) ? CL_TXT : CL_DIM);
|
||||
old = SelectObject(dc, g_font_b);
|
||||
{
|
||||
RECT t = {68, 258, 740, 302};
|
||||
|
||||
DrawTextA(dc, g_stato_text, -1, &t,
|
||||
DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
|
||||
}
|
||||
SelectObject(dc, old);
|
||||
}
|
||||
EndPaint(h, &ps);
|
||||
return 0;
|
||||
}
|
||||
case WM_DRAWITEM: {
|
||||
DRAWITEMSTRUCT *d = (DRAWITEMSTRUCT *)l;
|
||||
RECT r = d->rcItem;
|
||||
int start = (d->CtlID == ID_START);
|
||||
int sel = (d->itemState & ODS_SELECTED) != 0;
|
||||
int dis = (d->itemState & ODS_DISABLED) != 0;
|
||||
const char *lbl = "";
|
||||
COLORREF bg, fg, bd;
|
||||
HBRUSH br, ob;
|
||||
HPEN pn, op;
|
||||
HFONT of;
|
||||
|
||||
switch(d->CtlID) {
|
||||
case ID_START: lbl = "START STREAMING"; break;
|
||||
case ID_STOP: lbl = "STOP"; break;
|
||||
case ID_DEPLOY: lbl = "Deploy payload"; break;
|
||||
case ID_INSTALL: lbl = "Install driver"; break;
|
||||
case ID_APPLY: lbl = "Apply"; break;
|
||||
}
|
||||
if(start) {
|
||||
bg = sel ? CL_ACC_D : CL_ACC;
|
||||
fg = RGB(255, 255, 255);
|
||||
bd = bg;
|
||||
} else {
|
||||
bg = sel ? CL_GHOST_D : CL_GHOST;
|
||||
fg = CL_TXT;
|
||||
bd = CL_GBD;
|
||||
}
|
||||
if(dis) {
|
||||
bg = CL_GHOST;
|
||||
fg = CL_DIM2;
|
||||
}
|
||||
br = CreateSolidBrush(bg);
|
||||
pn = CreatePen(PS_SOLID, 1, bd);
|
||||
ob = (HBRUSH)SelectObject(d->hDC, br);
|
||||
op = (HPEN)SelectObject(d->hDC, pn);
|
||||
RoundRect(d->hDC, r.left + 1, r.top + 1, r.right - 1, r.bottom - 1,
|
||||
start ? 14 : 10, start ? 14 : 10);
|
||||
SelectObject(d->hDC, ob);
|
||||
SelectObject(d->hDC, op);
|
||||
DeleteObject(br);
|
||||
DeleteObject(pn);
|
||||
SetBkMode(d->hDC, TRANSPARENT);
|
||||
SetTextColor(d->hDC, fg);
|
||||
of = (HFONT)SelectObject(d->hDC, g_font_b);
|
||||
DrawTextA(d->hDC, lbl, -1, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
||||
SelectObject(d->hDC, of);
|
||||
return TRUE;
|
||||
}
|
||||
case WM_CTLCOLORSTATIC: {
|
||||
HDC dc = (HDC)w;
|
||||
HWND ctl = (HWND)l;
|
||||
|
||||
if(ctl == g_h_log) { /* l'EDIT readonly arriva qui */
|
||||
SetBkMode(dc, OPAQUE);
|
||||
SetBkColor(dc, CL_EDIT);
|
||||
SetTextColor(dc, CL_EDTX);
|
||||
return (LRESULT)g_br_edit;
|
||||
}
|
||||
SetBkMode(dc, TRANSPARENT);
|
||||
if(ctl == g_h_stato) {
|
||||
SetTextColor(dc, g_stato_on ? RGB(14, 150, 80) : RGB(120, 120, 120));
|
||||
return (LRESULT)GetSysColorBrush(COLOR_BTNFACE);
|
||||
}
|
||||
if(ctl == g_h_sub) {
|
||||
SetTextColor(dc, RGB(110, 110, 110));
|
||||
return (LRESULT)GetSysColorBrush(COLOR_BTNFACE);
|
||||
}
|
||||
return (LRESULT)GetSysColorBrush(COLOR_BTNFACE);
|
||||
SetTextColor(dc, CL_DIM);
|
||||
return (LRESULT)g_br_bg;
|
||||
}
|
||||
case WM_CTLCOLOREDIT: {
|
||||
HDC dc = (HDC)w;
|
||||
|
||||
SetBkColor(dc, CL_EDIT);
|
||||
SetTextColor(dc, CL_TXT);
|
||||
return (LRESULT)g_br_edit;
|
||||
}
|
||||
case WM_APP_LOG: {
|
||||
char *s = (char *)l;
|
||||
|
||||
if(s) {
|
||||
int i = (int)SendMessageA(g_h_log, LB_ADDSTRING, 0, (LPARAM)s);
|
||||
int cnt = (int)SendMessageA(g_h_log, LB_GETCOUNT, 0, 0);
|
||||
int len = (int)SendMessageA(g_h_log, WM_GETTEXTLENGTH, 0, 0);
|
||||
|
||||
if(cnt > 500) SendMessageA(g_h_log, LB_DELETESTRING, 0, 0);
|
||||
SendMessageA(g_h_log, LB_SETTOPINDEX, i, 0);
|
||||
if(len > 150000) {
|
||||
SendMessageA(g_h_log, EM_SETSEL, 0, 60000);
|
||||
SendMessageA(g_h_log, EM_REPLACESEL, FALSE, (LPARAM)"");
|
||||
len = (int)SendMessageA(g_h_log, WM_GETTEXTLENGTH, 0, 0);
|
||||
}
|
||||
SendMessageA(g_h_log, EM_SETSEL, len, len);
|
||||
SendMessageA(g_h_log, EM_REPLACESEL, FALSE, (LPARAM)s);
|
||||
SendMessageA(g_h_log, EM_REPLACESEL, FALSE, (LPARAM)"\r\n");
|
||||
SendMessageA(g_h_log, EM_SCROLLCARET, 0, 0);
|
||||
free(s);
|
||||
}
|
||||
return 0;
|
||||
@@ -794,13 +1112,42 @@ WndProc(HWND h, UINT m, WPARAM w, LPARAM l)
|
||||
char *s = (char *)l;
|
||||
|
||||
if(s) {
|
||||
SetWindowTextA(g_h_stato, s);
|
||||
g_stato_on = (strstr(s, "STREAMING") || strstr(s, "AVVIO")) ? 1 : 0;
|
||||
InvalidateRect(g_h_stato, NULL, TRUE);
|
||||
RECT pr = {26, 258, 754, 302};
|
||||
|
||||
snprintf(g_stato_text, sizeof(g_stato_text), "%s", s);
|
||||
g_stato_level = strstr(s, "STREAMING") ? 2 :
|
||||
(strstr(s, "ATTESA") || strstr(s, "AVVIO")) ? 1 : 0;
|
||||
InvalidateRect(h, &pr, FALSE);
|
||||
free(s);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
case WM_NCHITTEST: {
|
||||
POINT p = {LOWORD(l), HIWORD(l)};
|
||||
RECT wr;
|
||||
int W;
|
||||
|
||||
GetWindowRect(h, &wr);
|
||||
p.x -= wr.left;
|
||||
p.y -= wr.top;
|
||||
W = wr.right - wr.left;
|
||||
if(p.y >= 0 && p.y < 44) {
|
||||
if(p.x >= W - 80 && p.x < W - 44) return HTMINBUTTON;
|
||||
if(p.x >= W - 44) return HTCLOSE;
|
||||
return HTCAPTION;
|
||||
}
|
||||
return HTCLIENT;
|
||||
}
|
||||
case WM_NCLBUTTONDOWN:
|
||||
if(w == HTMINBUTTON) {
|
||||
ShowWindow(h, SW_MINIMIZE);
|
||||
return 0;
|
||||
}
|
||||
if(w == HTCLOSE) {
|
||||
PostMessageA(h, WM_CLOSE, 0, 0);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case WM_CLOSE:
|
||||
if(g_streaming) {
|
||||
g_streaming = 0;
|
||||
@@ -839,7 +1186,14 @@ driver_installato(void)
|
||||
if(RegOpenKeyA(HKEY_CLASSES_ROOT,
|
||||
"CLSID\\{50533543-414d-4def-9b0e-192080102030}\\InprocServer32",
|
||||
&k) == ERROR_SUCCESS) {
|
||||
ok = 1;
|
||||
char dll[MAX_PATH];
|
||||
DWORD sz = sizeof(dll), tipo = 0;
|
||||
|
||||
if(RegQueryValueExA(k, NULL, NULL, &tipo, (LPBYTE)dll, &sz) == ERROR_SUCCESS &&
|
||||
(tipo == REG_SZ || tipo == REG_EXPAND_SZ) &&
|
||||
GetFileAttributesA(dll) != INVALID_FILE_ATTRIBUTES) {
|
||||
ok = 1;
|
||||
}
|
||||
RegCloseKey(k);
|
||||
}
|
||||
return ok;
|
||||
@@ -854,7 +1208,13 @@ WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int show)
|
||||
|
||||
(void)prev;
|
||||
(void)cmd;
|
||||
if(!is_admin()) {
|
||||
|
||||
g_inst = inst;
|
||||
exe_dir(g_dir, sizeof(g_dir));
|
||||
logUI("PSCapture avvio: admin=%d no_elevate=%d dir=%s", is_admin(),
|
||||
getenv("PSCAPTURE_NO_ELEVATE") != NULL, g_dir);
|
||||
|
||||
if(!is_admin() && !getenv("PSCAPTURE_NO_ELEVATE")) {
|
||||
SHELLEXECUTEINFOA sei;
|
||||
char self[MAX_PATH];
|
||||
|
||||
@@ -866,10 +1226,10 @@ WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int show)
|
||||
sei.lpFile = self;
|
||||
sei.lpDirectory = NULL;
|
||||
sei.nShow = SW_SHOWNORMAL;
|
||||
logUI("richiedo elevazione (prompt UAC)...");
|
||||
if(ShellExecuteExA(&sei)) return 0; /* rilanciata come admin */
|
||||
logUI("elevazione rifiutata: continuo senza privilegi");
|
||||
}
|
||||
g_inst = inst;
|
||||
exe_dir(g_dir, sizeof(g_dir));
|
||||
|
||||
WSAStartup(MAKEWORD(2, 2), &(WSADATA){0});
|
||||
|
||||
@@ -880,23 +1240,33 @@ WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int show)
|
||||
sizeof(ps5cam_shm_t));
|
||||
}
|
||||
if(!g_shm) {
|
||||
logUI("ERRORE: shared memory non creabile");
|
||||
MessageBoxA(NULL, "Impossibile creare la shared memory del driver.",
|
||||
"PSCapture", MB_ICONERROR);
|
||||
return 1;
|
||||
}
|
||||
logUI("shared memory OK");
|
||||
memset(&wc, 0, sizeof(wc));
|
||||
wc.lpfnWndProc = WndProc;
|
||||
wc.hInstance = inst;
|
||||
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||||
wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
|
||||
wc.hbrBackground = CreateSolidBrush(CL_BG);
|
||||
wc.hIcon = LoadIconA(inst, MAKEINTRESOURCEA(1));
|
||||
wc.lpszClassName = "PSCaptureWnd";
|
||||
RegisterClassA(&wc);
|
||||
|
||||
g_hwnd = CreateWindowA("PSCaptureWnd", "PSCapture by fstat13 — PS5 screen to PC",
|
||||
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
|
||||
CW_USEDEFAULT, CW_USEDEFAULT, 728, 580,
|
||||
NULL, NULL, inst, NULL);
|
||||
{
|
||||
g_hwnd = CreateWindowExA(WS_EX_APPWINDOW, "PSCaptureWnd",
|
||||
"PSCapture by fstat13 - PS5 screen to PC",
|
||||
WS_POPUP,
|
||||
CW_USEDEFAULT, CW_USEDEFAULT, 780, 660,
|
||||
NULL, NULL, inst, NULL);
|
||||
if(g_hwnd) {
|
||||
HRGN rg = CreateRoundRectRgn(0, 0, 781, 661, 20, 20);
|
||||
|
||||
if(rg) SetWindowRgn(g_hwnd, rg, TRUE);
|
||||
}
|
||||
}
|
||||
ShowWindow(g_hwnd, show);
|
||||
UpdateWindow(g_hwnd);
|
||||
PostMessageA(g_hwnd, WM_APP_AUTOSTART, 0, 0);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <windows.h>
|
||||
1 ICON "pscapture.ico"
|
||||
2 RCDATA "pscapture.elf"
|
||||
3 RCDATA "pscapture.dll"
|
||||
Reference in new issue
Block a user