mirror of
https://github.com/fstat13/PSCapture.git
synced 2026-10-18 18:00:39 +02:00
- payload: display-engine scan-out reader, NV12 streamer over TCP, system toast notification, autoloader support - PSCapture.exe: GUI, admin auto-elevation, driver installer, embedded payload deployer, live feeder with gain/shift tuning - ps5cam.c: DirectShow virtual capture driver (PS5 Capture) - docs: HTML guide (GitHub Pages ready), README, source-available license
1222 lines
37 KiB
C
1222 lines
37 KiB
C
/*
|
|
* PSCapture — PS5 payload: reads the display engine scan-out and
|
|
* streams NV12 frames over TCP; writes /data/pscapture.port and shows
|
|
* the system toast notification.
|
|
* (c) 2026 fstat13 — all rights reserved. See LICENSE.
|
|
*/
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <immintrin.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
#include <pthread.h>
|
|
#include <setjmp.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <ucontext.h>
|
|
#include <unistd.h>
|
|
#include <arpa/inet.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <ps5/kernel.h>
|
|
#include <ps5/klog.h>
|
|
|
|
#define PORT 5599
|
|
#define RING_OFF 0x4200000UL
|
|
#define SURF_STRIDE 0x600000UL
|
|
#define FRAME_SZ 0x5EE000UL
|
|
#define W 1920
|
|
#define H 1080
|
|
#define NV12_SZ (W * H * 3 / 2)
|
|
#define BLK 0x8000UL
|
|
#define NBLK ((int)((FRAME_SZ + BLK - 1) / BLK))
|
|
|
|
static void
|
|
logfile(const char *fmt, ...)
|
|
{
|
|
FILE *f = fopen("/data/pscapture.log", "a");
|
|
|
|
if(f) {
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
vfprintf(f, fmt, ap);
|
|
va_end(ap);
|
|
fclose(f);
|
|
}
|
|
}
|
|
|
|
#define LOG(fmt, ...) do { \
|
|
printf(fmt "\n", ##__VA_ARGS__); \
|
|
logfile(fmt "\n", ##__VA_ARGS__); \
|
|
klog_printf("[avstream] " fmt, ##__VA_ARGS__); \
|
|
} while(0)
|
|
|
|
extern unsigned long KERNEL_ADDRESS_DMAP_BASE;
|
|
|
|
extern int sceKernelLoadStartModule(const char *path, size_t argc,
|
|
const void *argv, unsigned int flags,
|
|
void *unk1, unsigned int *unk2,
|
|
int *handle);
|
|
|
|
static sigjmp_buf g_rec;
|
|
static volatile int g_in_probe;
|
|
|
|
static void
|
|
on_segv(int sig, siginfo_t *si, void *uc)
|
|
{
|
|
ucontext_t *u = (ucontext_t *)uc;
|
|
char buf[160];
|
|
int n;
|
|
|
|
(void)sig;
|
|
if(g_in_probe) siglongjmp(g_rec, 1); /* durante la prova: si torna indietro */
|
|
n = snprintf(buf, sizeof(buf),
|
|
"\n[avstream] SIGSEGV rip=0x%llx addr=0x%llx\n",
|
|
(unsigned long long)u->uc_mcontext.mc_rip,
|
|
(unsigned long long)(unsigned long)si->si_addr);
|
|
write(1, buf, n);
|
|
klog_printf("%s", buf);
|
|
_exit(2);
|
|
}
|
|
|
|
static unsigned long
|
|
now_ms(void)
|
|
{
|
|
struct timespec ts;
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
return (unsigned long)(ts.tv_sec * 1000 + ts.tv_nsec / 1000000);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* page walk con cache del blocco */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static unsigned long
|
|
pt_walk_ex(unsigned long pml4_pa, unsigned long va, int *ok, int *big)
|
|
{
|
|
unsigned long e, pa;
|
|
|
|
*ok = 0;
|
|
if(big) *big = 0;
|
|
e = kernel_getlong(KERNEL_ADDRESS_DMAP_BASE + pml4_pa +
|
|
((va >> 39) & 0x1ff) * 8);
|
|
if(!(e & 1)) return 0;
|
|
pa = e & 0x000ffffffffff000UL;
|
|
e = kernel_getlong(KERNEL_ADDRESS_DMAP_BASE + pa + ((va >> 30) & 0x1ff) * 8);
|
|
if(!(e & 1)) return 0;
|
|
if(e & 0x80) {
|
|
*ok = 1;
|
|
if(big) *big = 2;
|
|
return (e & 0x000fffffffe00000UL) + (va & 0x1fffffUL);
|
|
}
|
|
if(big == NULL && 0) return 0;
|
|
pa = e & 0x000ffffffffff000UL;
|
|
e = kernel_getlong(KERNEL_ADDRESS_DMAP_BASE + pa + ((va >> 21) & 0x1ff) * 8);
|
|
if(!(e & 1)) return 0;
|
|
if(e & 0x80) {
|
|
*ok = 1;
|
|
if(big) *big = 1;
|
|
return (e & 0x000fffffffe00000UL) + (va & 0x1fffffUL);
|
|
}
|
|
pa = e & 0x000ffffffffff000UL;
|
|
e = kernel_getlong(KERNEL_ADDRESS_DMAP_BASE + pa + ((va >> 12) & 0x1ff) * 8);
|
|
if(!(e & 1)) return 0;
|
|
*ok = 1;
|
|
return (e & 0x000ffffffffff000UL) + (va & 0xfffUL);
|
|
}
|
|
|
|
static unsigned long
|
|
pt_walk(unsigned long pml4_pa, unsigned long va, int *ok)
|
|
{
|
|
return pt_walk_ex(pml4_pa, va, ok, NULL);
|
|
}
|
|
|
|
struct ptcache {
|
|
unsigned long va;
|
|
unsigned long pa;
|
|
unsigned long len;
|
|
};
|
|
|
|
static unsigned long g_cache_va;
|
|
static unsigned long g_cache_pa;
|
|
static unsigned long g_scanin_obj;
|
|
static unsigned long g_pan[4]; /* offset della LUMA per superficie */
|
|
static unsigned long g_pan_ob[4]; /* obiettivo del tracking (filtro anti-rumore) */
|
|
static unsigned long g_pan_uv[4]; /* offset della CROMA (piano UV) */
|
|
static int g_sup = -1; /* superficie agganciata (-1 = da scegliere) */
|
|
static unsigned long g_cache_len;
|
|
static unsigned char *g_map; /* mappatura cacheabile del ring (o NULL) */
|
|
|
|
static int
|
|
pt_read_c(struct ptcache *c, unsigned long pml4_pa, unsigned long va,
|
|
void *buf, size_t len)
|
|
{
|
|
size_t done = 0;
|
|
|
|
while(done < len) {
|
|
unsigned long pa;
|
|
size_t run;
|
|
|
|
if(c->len && va + done >= c->va && va + done < c->va + c->len) {
|
|
pa = c->pa + ((va + done) - c->va);
|
|
} else {
|
|
int ok = 0, big = 0;
|
|
|
|
pa = pt_walk_ex(pml4_pa, va + done, &ok, &big);
|
|
if(!ok) return -1;
|
|
if(big && ((va + done) & 0xfff) == 0) {
|
|
c->va = (va + done) & ~0x1fffffUL;
|
|
c->pa = pa & ~0x1fffffUL;
|
|
c->len = 0x200000UL;
|
|
} else {
|
|
c->len = 0;
|
|
}
|
|
}
|
|
run = 0x1000 - ((va + done) & 0xfff);
|
|
while(done + run + 0x1000 <= len && ((va + done + run) & 0xfff) == 0 &&
|
|
run < 0x4000000UL) {
|
|
unsigned long nxt;
|
|
|
|
if(c->len && va + done + run < c->va + c->len) {
|
|
nxt = c->pa + ((va + done + run) - c->va);
|
|
} else {
|
|
int ok2 = 0, big2 = 0;
|
|
|
|
nxt = pt_walk_ex(pml4_pa, va + done + run, &ok2, &big2);
|
|
if(!ok2) break;
|
|
if(big2) {
|
|
c->va = (va + done + run) & ~0x1fffffUL;
|
|
c->pa = nxt & ~0x1fffffUL;
|
|
c->len = 0x200000UL;
|
|
} else {
|
|
c->len = 0;
|
|
}
|
|
}
|
|
if((nxt & ~0xfffUL) != ((pa + run) & ~0xfffUL)) break;
|
|
run += 0x1000;
|
|
}
|
|
if(run > len - done) run = len - done;
|
|
if(kernel_copyout(KERNEL_ADDRESS_DMAP_BASE + pa, (char *)buf + done, run))
|
|
return -1;
|
|
done += run;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
typedef struct {
|
|
unsigned long pa;
|
|
unsigned char *dst;
|
|
size_t len;
|
|
} run_t;
|
|
|
|
typedef struct {
|
|
run_t *runs;
|
|
int n;
|
|
int tid;
|
|
int nth;
|
|
int rc;
|
|
} warg_t;
|
|
|
|
static void *
|
|
wcopy(void *a)
|
|
{
|
|
warg_t *w = a;
|
|
int i;
|
|
|
|
w->rc = 0;
|
|
for(i = w->tid; i < w->n; i += w->nth) {
|
|
if(kernel_copyout(KERNEL_ADDRESS_DMAP_BASE + w->runs[i].pa,
|
|
w->runs[i].dst, w->runs[i].len)) {
|
|
w->rc = -1;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
#define RTHREADS 4
|
|
#define MAX_RUNS 512
|
|
|
|
/* Cammina in serie (i walk non sono thread-safe: condividono lo stato del
|
|
* kexec) e poi fa copiare le run contigue a piu' thread: le copyout sono
|
|
* syscall pure e i buffer non si sovrappongono. */
|
|
static int
|
|
pt_read_parallel(unsigned long pml4_pa, unsigned long va, unsigned char *buf,
|
|
size_t len)
|
|
{
|
|
static run_t runs[MAX_RUNS];
|
|
struct ptcache c = { 0, 0, 0 };
|
|
size_t done = 0;
|
|
int nr = 0, i, rc = 0;
|
|
pthread_t th[RTHREADS];
|
|
warg_t wa[RTHREADS];
|
|
|
|
while(done < len && nr < MAX_RUNS) {
|
|
unsigned long pa;
|
|
size_t run;
|
|
|
|
if(c.len && va + done >= c.va && va + done < c.va + c.len) {
|
|
pa = c.pa + ((va + done) - c.va);
|
|
} else {
|
|
int ok = 0, big = 0;
|
|
|
|
pa = pt_walk_ex(pml4_pa, va + done, &ok, &big);
|
|
if(!ok) return -1;
|
|
if(big && ((va + done) & 0xfff) == 0) {
|
|
c.va = (va + done) & ~0x1fffffUL;
|
|
c.pa = pa & ~0x1fffffUL;
|
|
c.len = 0x200000UL;
|
|
} else {
|
|
c.len = 0;
|
|
}
|
|
}
|
|
run = 0x1000 - ((va + done) & 0xfff);
|
|
while(done + run + 0x1000 <= len && ((va + done + run) & 0xfff) == 0 &&
|
|
run < 0x4000000UL && nr + 1 < MAX_RUNS) {
|
|
unsigned long nxt;
|
|
|
|
if(c.len && va + done + run < c.va + c.len) {
|
|
nxt = c.pa + ((va + done + run) - c.va);
|
|
} else {
|
|
int ok2 = 0, big2 = 0;
|
|
|
|
nxt = pt_walk_ex(pml4_pa, va + done + run, &ok2, &big2);
|
|
if(!ok2) break;
|
|
if(big2) {
|
|
c.va = (va + done + run) & ~0x1fffffUL;
|
|
c.pa = nxt & ~0x1fffffUL;
|
|
c.len = 0x200000UL;
|
|
} else {
|
|
c.len = 0;
|
|
}
|
|
}
|
|
if((nxt & ~0xfffUL) != ((pa + run) & ~0xfffUL)) break;
|
|
run += 0x1000;
|
|
}
|
|
if(run > len - done) run = len - done;
|
|
runs[nr].pa = pa;
|
|
runs[nr].dst = buf + done;
|
|
runs[nr].len = run;
|
|
nr++;
|
|
done += run;
|
|
}
|
|
if(done < len) return -1;
|
|
|
|
for(i = 0; i < RTHREADS; i++) {
|
|
wa[i].runs = runs;
|
|
wa[i].n = nr;
|
|
wa[i].tid = i;
|
|
wa[i].nth = RTHREADS;
|
|
wa[i].rc = 0;
|
|
if(pthread_create(&th[i], NULL, wcopy, &wa[i])) {
|
|
/* niente thread: copia il resto qui */
|
|
wcopy(&wa[i]);
|
|
th[i] = 0;
|
|
}
|
|
}
|
|
for(i = 0; i < RTHREADS; i++) {
|
|
if(th[i]) pthread_join(th[i], NULL);
|
|
if(wa[i].rc) rc = -1;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/* Legge dal direct map. La cache copre solo i blocchi risolti con una
|
|
* pagina da 2 MB (li' l'offset virtuale e' l'offset fisico); per le
|
|
* mappature a 4 KB ogni pagina si cammina, fondendo pero' le run
|
|
* fisicamente contigue in una sola copyout. */
|
|
static int
|
|
pt_read(unsigned long pml4_pa, unsigned long va, void *buf, size_t len)
|
|
{
|
|
size_t done = 0;
|
|
|
|
while(done < len) {
|
|
unsigned long pa;
|
|
size_t run;
|
|
|
|
if(g_cache_len && va + done >= g_cache_va &&
|
|
va + done < g_cache_va + g_cache_len) {
|
|
pa = g_cache_pa + ((va + done) - g_cache_va);
|
|
} else {
|
|
int ok = 0, big = 0;
|
|
|
|
pa = pt_walk_ex(pml4_pa, va + done, &ok, &big);
|
|
if(!ok) return -1;
|
|
if(big && ((va + done) & 0xfff) == 0) {
|
|
g_cache_va = (va + done) & ~0x1fffffUL;
|
|
g_cache_pa = pa & ~0x1fffffUL;
|
|
g_cache_len = 0x200000UL;
|
|
} else {
|
|
g_cache_len = 0;
|
|
}
|
|
}
|
|
|
|
run = 0x1000 - ((va + done) & 0xfff);
|
|
while(done + run + 0x1000 <= len && ((va + done + run) & 0xfff) == 0 &&
|
|
run < 0x4000000UL) {
|
|
unsigned long nxt;
|
|
|
|
if(g_cache_len && va + done + run < g_cache_va + g_cache_len) {
|
|
nxt = g_cache_pa + ((va + done + run) - g_cache_va);
|
|
} else {
|
|
int ok2 = 0, big2 = 0;
|
|
|
|
nxt = pt_walk_ex(pml4_pa, va + done + run, &ok2, &big2);
|
|
if(!ok2) break;
|
|
if(big2) {
|
|
g_cache_va = (va + done + run) & ~0x1fffffUL;
|
|
g_cache_pa = nxt & ~0x1fffffUL;
|
|
g_cache_len = 0x200000UL;
|
|
} else {
|
|
g_cache_len = 0;
|
|
}
|
|
}
|
|
if((nxt & ~0xfffUL) != ((pa + run) & ~0xfffUL)) break;
|
|
run += 0x1000;
|
|
}
|
|
if(run > len - done) run = len - done;
|
|
if(kernel_copyout(KERNEL_ADDRESS_DMAP_BASE + pa, (char *)buf + done, run))
|
|
return -1;
|
|
done += run;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* P010 -> NV12 8 bit (byte dispari), con PSHUFB: 16 byte per giro */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
/* --- auto-allineamento matematico: al taglio giusto c*, la striscia
|
|
* [riga r, col 0..] e' la continuazione di [riga r-1, col c*..]: sono
|
|
* pezzi della STESSA riga del frame originale. Si minimizza l'errore su
|
|
* 64 righe campionate: nessun bordo di menu puo' imitare questa firma. */
|
|
static unsigned long
|
|
scan_pan(unsigned char *base_p, unsigned long pm4, unsigned long va_base,
|
|
unsigned long atteso)
|
|
{
|
|
static unsigned char buf[64][3840];
|
|
unsigned long bestv = 0xFFFFFFFFUL;
|
|
int migliore = -1, c, r, j;
|
|
int c0 = 0, c1 = 1918;
|
|
|
|
for(r = 0; r < 64; r++) {
|
|
if(base_p) {
|
|
memcpy(buf[r], base_p + (unsigned long)(r * 8) * 3840UL, 3840);
|
|
} else {
|
|
g_cache_len = 0;
|
|
if(pt_read(pm4, va_base + (unsigned long)(r * 8) * 3840UL, buf[r], 3840))
|
|
return 0xFFFFFFFFUL;
|
|
}
|
|
}
|
|
if(atteso != 0xFFFFFFFFUL) {
|
|
int cc = (int)((1920UL - atteso / 2UL) % 1920UL);
|
|
int s0 = cc - 120, s1 = cc + 120;
|
|
|
|
if(s0 < 4) s0 = 4;
|
|
if(s1 > 1914) s1 = 1914;
|
|
c0 = s0; c1 = s1;
|
|
}
|
|
for(c = c0; c <= c1; c += 2) {
|
|
unsigned long e = 0, n = 0;
|
|
int maxj = 1918 - c;
|
|
int passo = maxj > 200 ? maxj / 20 : 1;
|
|
|
|
/* finestre troppo strette = degeneri (su contenuto piatto vincono
|
|
* sempre): si salta, e si normalizza per il numero di campioni.
|
|
* Senza questo la scansione libera tornava sempre "pan 4". */
|
|
if(maxj < 200) continue;
|
|
for(r = 1; r < 64; r++) {
|
|
for(j = 0; j <= maxj; j += passo) {
|
|
long d = (long)buf[r][j * 2 + 1] - (long)buf[r - 1][(c + j) * 2 + 1];
|
|
|
|
if(d < 0) d = -d;
|
|
e += (unsigned long)d;
|
|
n += 1;
|
|
}
|
|
}
|
|
if(!n) continue;
|
|
if(e * 1024UL / n < bestv) { bestv = e * 1024UL / n; migliore = c; }
|
|
}
|
|
if(migliore < 0) return 0xFFFFFFFFUL;
|
|
return ((1920UL - (unsigned long)migliore) % 1920UL) * 2UL;
|
|
}
|
|
|
|
static void
|
|
p010_to_nv12(unsigned char *dst, const unsigned char *p010, unsigned long n)
|
|
{
|
|
/* si carica da p010+1: gli eventuali byte del campione sono a indice
|
|
* PARI del carico (errore storico al contrario: byte bassi -> rumore) */
|
|
const __m128i mA = _mm_setr_epi8(0, 2, 4, 6, 8, 10, 12, 14,
|
|
-1, -1, -1, -1, -1, -1, -1, -1);
|
|
/* nella zona CROMA il piano della superficie tiene la parola V PRIMA
|
|
* della U (misurato contro il frame di riferimento v10: invertendo le
|
|
* due meta' l'errore scende da 15.5 a 1.7). Il piano luma resta a mA. */
|
|
const __m128i mB = _mm_setr_epi8(2, 0, 6, 4, 10, 8, 14, 12,
|
|
-1, -1, -1, -1, -1, -1, -1, -1);
|
|
const unsigned long LM = 1920UL * 1080UL; /* confine luma/croma */
|
|
unsigned long i = 0;
|
|
|
|
for(; i + 16 <= n; i += 16) {
|
|
const unsigned char *s = p010 + 1 + 2 * i;
|
|
__m128i mk = (i >= LM) ? mB : mA;
|
|
__m128i a = _mm_loadu_si128((const __m128i *)(const void *)s);
|
|
__m128i b = _mm_loadu_si128((const __m128i *)(const void *)(s + 16));
|
|
__m128i x = _mm_shuffle_epi8(a, mk);
|
|
__m128i y = _mm_shuffle_epi8(b, mk);
|
|
|
|
_mm_storeu_si128((__m128i *)(void *)(dst + i), _mm_unpacklo_epi64(x, y));
|
|
}
|
|
for(; i < n; i++) {
|
|
const unsigned char *s = p010 + 1 + 2 * i;
|
|
|
|
dst[i] = (i < LM) ? s[0] : ((i & 1) ? s[-2] : s[2]);
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* mappatura cacheabile del buffer DCE (il muro dei 45 MB/s) */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
/* u64 in kernel: kernel_copyin e' (src user, kaddr, len) */
|
|
static int
|
|
kwrite64(unsigned long kaddr, unsigned long long val)
|
|
{
|
|
return kernel_copyin(&val, (intptr_t)kaddr, 8);
|
|
}
|
|
|
|
/* Costruisce nel NOSTRO processo una mappatura CACHEABILE (pagine da
|
|
* 2 MB, sola lettura, NX) del range fisico che comincia a rv_va, su una
|
|
* sotto-catena NUOVA appesa a una entry PML4 libera: le pagine PDPT e PD
|
|
* sono nostre (mmap) e le scriviamo noi; l'unica scrittura kernel e' la
|
|
* entry PML4. Le PDE replicano i flag della mappa del daemon
|
|
* (0xA5 = P|US|A|PS, senza PCD/PWT: write-back). Ritorna la base o NULL
|
|
* (in tal caso si resta sul percorso lento). */
|
|
static unsigned char *
|
|
build_fastmap(unsigned long pml4, unsigned long rv_va, int slots)
|
|
{
|
|
static unsigned char *hh;
|
|
unsigned long pas[32], kp, vm, pmp, pm4, base = 0, ptabs = 0;
|
|
unsigned long i;
|
|
int ok, big;
|
|
|
|
if(slots < 1 || slots > 32) return NULL;
|
|
if(rv_va & 0x1fffffUL) { LOG(" fastmap: base non allineata"); return NULL; }
|
|
|
|
for(i = 0; i < (unsigned long)slots; i++) {
|
|
pas[i] = pt_walk_ex(pml4, rv_va + i * 0x200000UL, &ok, &big);
|
|
if(!ok || big != 1 || (pas[i] & 0x1fffffUL) || !pas[i]) {
|
|
LOG(" fastmap: walk slot %lu ko", i);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
hh = mmap(NULL, 0x2000, PROT_READ | PROT_WRITE,
|
|
MAP_PRIVATE | MAP_ANON, -1, 0);
|
|
if(hh == MAP_FAILED) { LOG(" fastmap: mmap ko"); return NULL; }
|
|
memset(hh, 0, 0x2000);
|
|
hh[0] = 0;
|
|
hh[0x1000] = 0;
|
|
|
|
kp = kernel_get_proc(getpid());
|
|
vm = kernel_getlong(kp + KERNEL_OFFSET_PROC_P_VMSPACE);
|
|
pmp = vm ? kernel_getlong(vm + 0x1d8) : 0;
|
|
pm4 = pmp ? kernel_getlong(pmp + 0x28) : 0;
|
|
if(!pm4) { LOG(" fastmap: pmap nostra non trovata"); return NULL; }
|
|
|
|
{
|
|
unsigned long pa0, pa1;
|
|
|
|
pa0 = pt_walk_ex(pm4, (unsigned long)hh, &ok, NULL);
|
|
pa1 = pt_walk_ex(pm4, (unsigned long)hh + 0x1000, &ok, NULL);
|
|
/* il paddr space della console ha alias hardware oltre i 16GB (le
|
|
* superfici DCE stanno a 0x459200000): niente rifiuto su questo.
|
|
* Il paracadute vero resta la verifica sotto sigsetjmp piu' sotto. */
|
|
if(!pa0 || !pa1 || (pa0 & 0xfff) || (pa1 & 0xfff) ||
|
|
pa0 > 0x800000000UL || pa1 > 0x800000000UL) {
|
|
LOG(" fastmap: paddr delle tabelle sospetti");
|
|
return NULL;
|
|
}
|
|
*(unsigned long long *)(void *)(hh + 0) = 0x7ULL | pa1; /* PDPT -> PD */
|
|
for(i = 0; i < (unsigned long)slots; i++) {
|
|
*(unsigned long long *)(void *)(hh + 0x1000 + i * 8) =
|
|
0x8000000000000000ULL | pas[i] | 0xa5ULL;
|
|
}
|
|
__asm volatile("mfence" ::: "memory");
|
|
ptabs = pa0;
|
|
}
|
|
|
|
/* entry PML4 libera (con indici PDPT/PD a zero: cosi' la catena e'
|
|
* fatta solo da PDPT[0] e PD[0..slots)) */
|
|
for(i = 128; i < 256; i++) {
|
|
unsigned long v = i << 39;
|
|
|
|
if(((v >> 30) & 0x1ff) || ((v >> 21) & 0x1ff)) continue;
|
|
if(kernel_getlong(KERNEL_ADDRESS_DMAP_BASE + pm4 + i * 8)) continue;
|
|
base = v;
|
|
break;
|
|
}
|
|
if(!base) { LOG(" fastmap: nessuna entry PML4 libera"); return NULL; }
|
|
|
|
if(kwrite64(KERNEL_ADDRESS_DMAP_BASE + pm4 + ((base >> 39) & 0x1ff) * 8,
|
|
0x7ULL | ptabs)) {
|
|
LOG(" fastmap: scrittura PML4 fallita");
|
|
return NULL;
|
|
}
|
|
|
|
/* verifica sotto protezione fault: la prima QWORD di ogni slot deve
|
|
* essere leggibile; qualunque fault -> fallback (il contenuto non si
|
|
* confronta: lo schermo anima e i byte cambiano fra due letture) */
|
|
{
|
|
unsigned long n;
|
|
|
|
g_in_probe = 1;
|
|
if(sigsetjmp(g_rec, 1) == 0) {
|
|
unsigned long long acc = 0;
|
|
|
|
for(n = 0; n < (unsigned long)slots; n++) {
|
|
acc += *(volatile unsigned long long *)(void *)(base + n * 0x200000UL);
|
|
}
|
|
/* anche l'ultima qword dell'ultimo slot: mai sforare la mappa */
|
|
acc += *(volatile unsigned long long *)(void *)
|
|
(base + (unsigned long)slots * 0x200000UL - 8);
|
|
g_in_probe = 0;
|
|
if(acc == 0) {
|
|
LOG(" fastmap: nota: prime qword tutte nulle");
|
|
}
|
|
} else {
|
|
g_in_probe = 0;
|
|
LOG(" fastmap: fault durante la verifica: fallback");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
LOG(" mappa rapida: v=0x%lx <= paddr 0x%lx (%d slot, cacheable)",
|
|
base, pas[0], slots);
|
|
return (unsigned char *)base;
|
|
}
|
|
|
|
static int
|
|
has_sub(const unsigned char *p, int n, const char *needle)
|
|
{
|
|
int m = (int)strlen(needle), i;
|
|
|
|
for(i = 0; i + m <= n; i++) {
|
|
if(!memcmp(p + i, needle, m)) return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* set scanin via IPMI (la pipeline del tap si riaccende cosi') */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
/* Il client IPMI va distrutto dopo l'uso: le sessioni lasciate aperte si
|
|
* accumulano e il connect comincia a fallire con 0x8002000d. Si usa il
|
|
* distruttore base D2 (~Client): chiude la sessione senza liberare la
|
|
* memoria (l'oggetto sta nel pool che passiamo noi a create). */
|
|
static void *g_ipmi;
|
|
static void (*g_ipmi_dtor)(void *);
|
|
|
|
static int
|
|
set_scanin(void)
|
|
{
|
|
uint32_t ih = 0;
|
|
intptr_t q_cfg, q_esize, q_create, q_dtor;
|
|
static unsigned char cfg[0x150];
|
|
void *buf;
|
|
void *client = 0;
|
|
unsigned long csz;
|
|
int r;
|
|
|
|
if(kernel_dynlib_handle(getpid(), "libSceIpmi.sprx", &ih)) {
|
|
int hh = 0;
|
|
|
|
r = sceKernelLoadStartModule("/system/common/lib/libSceIpmi.sprx", 0,
|
|
NULL, 0, NULL, NULL, &hh);
|
|
LOG(" load libSceIpmi -> 0x%x", r);
|
|
kernel_dynlib_handle(getpid(), "libSceIpmi.sprx", &ih);
|
|
}
|
|
if(!ih) { LOG(" libSceIpmi non disponibile"); return -1; }
|
|
|
|
q_cfg = kernel_dynlib_resolve(getpid(), ih, "O1lQ2+do5r4");
|
|
q_esize = kernel_dynlib_resolve(getpid(), ih, "Rih7HnIM3JQ");
|
|
q_create = kernel_dynlib_resolve(getpid(), ih, "0zsTiDhM0nU");
|
|
q_dtor = kernel_dynlib_resolve(getpid(), ih, "QLDcLR7Pzbg");
|
|
if(!q_cfg || !q_esize || !q_create) {
|
|
LOG(" NID IPMI non risolti");
|
|
return -1;
|
|
}
|
|
|
|
memset(cfg, 0, sizeof(cfg));
|
|
((void (*)(void *))q_cfg)(cfg);
|
|
memcpy(cfg, "SceSysAvControlIpc", 18);
|
|
*(unsigned *)(cfg + 0x1c) = 1;
|
|
*(unsigned *)(cfg + 0x20) = 1;
|
|
*(unsigned long long *)(cfg + 0x28) = 0x400;
|
|
*(unsigned long long *)(cfg + 0x30) = 0x400;
|
|
*(unsigned *)(cfg + 0x38) = 2;
|
|
*(unsigned long long *)(cfg + 0x40) = 0x400;
|
|
*(unsigned long long *)(cfg + 0x48) = 0x400;
|
|
|
|
if(g_ipmi && g_ipmi_dtor) {
|
|
g_ipmi_dtor(g_ipmi); /* chiude la sessione del giro prima */
|
|
g_ipmi = 0;
|
|
}
|
|
|
|
csz = ((unsigned long (*)(void))q_esize)();
|
|
buf = malloc(csz ? csz : 0x10000);
|
|
r = ((int (*)(void **, void *, void *, void *))q_create)(&client, cfg, 0, buf);
|
|
if(r || !client) {
|
|
LOG(" Client::create -> 0x%x", r);
|
|
return -1;
|
|
}
|
|
g_ipmi = client;
|
|
g_ipmi_dtor = (void (*)(void *))q_dtor;
|
|
|
|
{
|
|
unsigned long vt = *(unsigned long *)client;
|
|
long timeout = 20000000;
|
|
int (*connect)(void *, int, int, void *, long *) =
|
|
(int (*)(void *, int, int, void *, long *))(*(unsigned long *)(vt + 0x80));
|
|
|
|
r = connect(client, 0, 0, cfg, &timeout);
|
|
if(r) {
|
|
LOG(" connect -> 0x%x", r);
|
|
if(g_ipmi == client) {
|
|
if(g_ipmi_dtor) g_ipmi_dtor(client);
|
|
g_ipmi = 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
{
|
|
unsigned scaninId = 1;
|
|
int params[8];
|
|
struct { void *p; unsigned long len; } data[2];
|
|
static unsigned long out1[4], out2[4];
|
|
int (*invoke)(void *, unsigned, void *, unsigned, void *, void *) =
|
|
(int (*)(void *, unsigned, void *, unsigned, void *, void *))
|
|
(*(unsigned long *)(vt + 0x60));
|
|
|
|
memset(params, 0, sizeof(params));
|
|
params[0] = 2; /* tapPoint */
|
|
params[1] = 1;
|
|
params[2] = 0x18;
|
|
params[3] = 11; /* risoluzione: 11 = 1920x1080 */
|
|
params[4] = 1;
|
|
params[5] = 1;
|
|
params[6] = 0;
|
|
data[0].p = &scaninId;
|
|
data[0].len = 4;
|
|
data[1].p = params;
|
|
data[1].len = 0x20;
|
|
memset(out1, 0, sizeof(out1));
|
|
memset(out2, 0, sizeof(out2));
|
|
r = invoke(client, 0x24, data, 2, out1, out2);
|
|
LOG(" set scanin -> r=0x%x", r);
|
|
return r;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Manda un NV12 intero. Ritorna la connessione (o -1 se e' caduta). */
|
|
static int
|
|
send_frame(int conn, const unsigned char *buf, int frame)
|
|
{
|
|
unsigned char hdr[12];
|
|
unsigned nlen = NV12_SZ;
|
|
ssize_t w, off = 0;
|
|
|
|
if(conn < 0) return conn;
|
|
hdr[0] = 'A'; hdr[1] = 'V'; hdr[2] = 'S'; hdr[3] = '2';
|
|
memcpy(hdr + 4, &frame, 4);
|
|
memcpy(hdr + 8, &nlen, 4);
|
|
if(write(conn, hdr, 12) != 12) { close(conn); return -1; }
|
|
while(off < (ssize_t)NV12_SZ) {
|
|
w = write(conn, buf + off, NV12_SZ - off);
|
|
if(w <= 0) { close(conn); return -1; }
|
|
off += w;
|
|
}
|
|
return conn;
|
|
}
|
|
|
|
/* ---- notifica di sistema: "PSCapture is streaming on port N" ---- */
|
|
|
|
typedef int (*fn_notify)(int, int, const char *);
|
|
static fn_notify g_notify;
|
|
|
|
static int
|
|
notifiche_carica(void)
|
|
{
|
|
int h = 0;
|
|
|
|
if(g_notify) return 0;
|
|
if(kernel_dynlib_handle(getpid(), "libSceNotification.sprx",
|
|
(unsigned int *)&h)) {
|
|
int h2 = 0;
|
|
|
|
sceKernelLoadStartModule("/system/common/lib/libSceNotification.sprx",
|
|
0, NULL, 0, NULL, NULL, &h2);
|
|
kernel_dynlib_handle(getpid(), "libSceNotification.sprx",
|
|
(unsigned int *)&h);
|
|
}
|
|
if(!h) return -1;
|
|
g_notify = (fn_notify)kernel_dynlib_resolve(getpid(), (unsigned)h,
|
|
"jSWBvOPfSOk");
|
|
return g_notify ? 0 : -1;
|
|
}
|
|
|
|
static void
|
|
notifica_porta(int port)
|
|
{
|
|
char json[1600];
|
|
char data[32];
|
|
time_t t = time(NULL);
|
|
struct tm tmv;
|
|
|
|
gmtime_r(&t, &tmv);
|
|
strftime(data, sizeof(data), "%Y-%m-%dT%H:%M:%SZ", &tmv);
|
|
snprintf(json, sizeof(json),
|
|
"{"
|
|
"\"rawData\":{"
|
|
"\"viewTemplateType\":\"InteractiveToastTemplateB\","
|
|
"\"channelType\":\"Downloads\",\"useCaseId\":\"IDC\","
|
|
"\"toastOverwriteType\":\"No\",\"isImmediate\":true,"
|
|
"\"priority\":100,"
|
|
"\"viewData\":{"
|
|
"\"message\":{\"body\":\"PSCapture is streaming on:%d\"},"
|
|
"\"subMessage\":{\"body\":\"avvia lo streaming dal PC\"}"
|
|
"},"
|
|
"\"platformViews\":{\"previewDisabled\":{\"viewData\":{"
|
|
"\"icon\":{\"type\":\"Predefined\",\"parameters\":{\"icon\":\"download\"}},"
|
|
"\"message\":{\"body\":\"PSCapture\"}}}}"
|
|
"},"
|
|
"\"createdDateTime\":\"%s\","
|
|
"\"localNotificationId\":\"588193127\""
|
|
"}", port, data);
|
|
if(notifiche_carica()) {
|
|
LOG("notifica: libSceNotification non disponibile");
|
|
return;
|
|
}
|
|
g_notify(0xFE, 1, json);
|
|
LOG("notifica inviata (porta %d)", port);
|
|
}
|
|
|
|
int
|
|
main(void)
|
|
{
|
|
unsigned char *p010 = NULL;
|
|
unsigned long pml4 = 0, tap_va = 0;
|
|
int srv, conn = -1, frame = 0, inviati = 0;
|
|
struct sockaddr_in addr;
|
|
static unsigned char strip[0x1000];
|
|
unsigned long t0;
|
|
|
|
{
|
|
struct sigaction sa;
|
|
|
|
memset(&sa, 0, sizeof(sa));
|
|
sa.sa_sigaction = on_segv;
|
|
sa.sa_flags = SA_SIGINFO;
|
|
sigaction(SIGSEGV, &sa, NULL);
|
|
}
|
|
|
|
LOG("avstream: partenza");
|
|
|
|
/* --- 0) igiene: NON si uccide piu' nessuno ---
|
|
* (una istanza con la mappatura iniettata non va strappata via: il
|
|
* teardown di tabelle alterate e' il sospetto del crash del 15/10).
|
|
* Le istanze senza client non fanno nulla: si lascia stare. */
|
|
{
|
|
/* riga 0a: UCCIDE le vecchie istanze avstream.
|
|
* Si riconoscono per la mappa rapida alla base fissa
|
|
* 0x400000000000 (entry PML4 n. 128 presente nel loro pmap):
|
|
* nessun processo di sistema la usa. Serve anche a liberare le
|
|
* sessioni IPMI: finche' la tabella e' piena il connect muore
|
|
* con 0x8002000d. Il teardown della sotto-catena e' innocuo
|
|
* (gia' dimostrato dal test v6.2: la console non e' caduta). */
|
|
{
|
|
int pid, mio = getpid(), uccise = 0;
|
|
|
|
for(pid = 1; pid < 400; pid++) {
|
|
long kp2;
|
|
unsigned long vm2, pmp2, pl4, e;
|
|
|
|
if(pid == mio || pid == 50 || pid == 59 || pid == 79) continue;
|
|
kp2 = kernel_get_proc(pid);
|
|
if(!kp2) continue;
|
|
vm2 = kernel_getlong(kp2 + KERNEL_OFFSET_PROC_P_VMSPACE);
|
|
if(!vm2) continue;
|
|
pmp2 = kernel_getlong(vm2 + 0x1d8);
|
|
if(!pmp2) continue;
|
|
pl4 = kernel_getlong(pmp2 + 0x28);
|
|
if(!pl4) continue;
|
|
e = kernel_getlong(KERNEL_ADDRESS_DMAP_BASE + pl4 + 128UL * 8UL);
|
|
if(!(e & 1UL)) continue;
|
|
if(kill(pid, SIGKILL) == 0) uccise++;
|
|
}
|
|
if(uccise) {
|
|
LOG(" uccise %d vecchie istanze avstream", uccise);
|
|
usleep(1500000);
|
|
}
|
|
}
|
|
|
|
/* riga 0b: tocco in localhost sulle porte vecchie (per quelle che
|
|
* hanno il cap e escono dopo un accept). */
|
|
{
|
|
int p, svegliate = 0;
|
|
|
|
for(p = PORT; p < PORT + 30; p++) {
|
|
int sk = socket(AF_INET, SOCK_STREAM, 0);
|
|
struct sockaddr_in a;
|
|
|
|
if(sk < 0) break;
|
|
memset(&a, 0, sizeof(a));
|
|
a.sin_family = AF_INET;
|
|
a.sin_port = htons(p);
|
|
a.sin_addr.s_addr = htonl(0x7f000001UL);
|
|
if(!connect(sk, (struct sockaddr *)&a, sizeof(a))) svegliate++;
|
|
close(sk);
|
|
}
|
|
if(svegliate) {
|
|
LOG(" svegliate %d vecchie istanze ferme in accept", svegliate);
|
|
usleep(500000);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* --- 1) walk: pml4 di pid 50 --- */
|
|
{
|
|
long kp = kernel_get_proc(50);
|
|
unsigned long vm = 0, pmp = 0;
|
|
|
|
if(!kp) { LOG(" kernel_get_proc(50) fallita"); return 1; }
|
|
vm = kernel_getlong(kp + KERNEL_OFFSET_PROC_P_VMSPACE);
|
|
if(!vm) { LOG(" vmspace nulla"); return 1; }
|
|
pmp = kernel_getlong(vm + 0x1d8);
|
|
if(pmp) pml4 = kernel_getlong(pmp + 0x28);
|
|
LOG(" pmap=0x%lx pml4=0x%lx", pmp, pml4);
|
|
if(!pml4) { LOG(" pml4 non trovato"); return 1; }
|
|
g_cache_len = 0;
|
|
if(!pt_read(pml4, 0x4c64e2UL, strip, 0x40) &&
|
|
has_sub(strip, 0x40, "getScaninBufferOffset")) {
|
|
LOG(" verifica del modulo: OK (delta 0)");
|
|
} else {
|
|
LOG(" verifica del modulo: FALLITA");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* --- 2) ScaninControl: va del ring --- */
|
|
{
|
|
unsigned long va;
|
|
|
|
for(va = 0x4a0000UL; va < 0x570000UL && !tap_va; va += 0x1000) {
|
|
unsigned char page[0x1000];
|
|
int j;
|
|
|
|
g_cache_len = 0;
|
|
if(pt_read(pml4, va, page, 0x1000)) continue;
|
|
for(j = 0; j + 8 <= 0x1000; j += 8) {
|
|
unsigned long q = *(unsigned long *)(page + j);
|
|
|
|
if(q == 0x4e5078UL) {
|
|
unsigned char f[0x48];
|
|
|
|
g_cache_len = 0;
|
|
if(!pt_read(pml4, va + j + 0x520UL, f, 0x48)) {
|
|
unsigned long sz = *(unsigned int *)(f + 0x30);
|
|
|
|
tap_va = *(unsigned long *)(f + 0x20);
|
|
g_scanin_obj = va + j;
|
|
LOG(" ScaninControl @ 0x%lx: va=0x%lx dim=0x%lx",
|
|
va + j, tap_va, sz);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(!tap_va) { LOG(" ring non trovato"); return 1; }
|
|
}
|
|
|
|
/* --- 2b) mappatura cacheabile del ring (~4 GB/s invece di 45 MB/s) --- */
|
|
/* il ring copre fino a 3*0x600000+0x5EE000 = 0x17EE000 -> 12 slot */
|
|
g_map = build_fastmap(pml4, tap_va + RING_OFF, 12);
|
|
if(!g_map) LOG(" mappa rapida non disponibile: si usa il direct map");
|
|
{
|
|
int k;
|
|
|
|
for(k = 0; k < 4; k++) {
|
|
unsigned long p = scan_pan(g_map ? g_map + (unsigned long)k * SURF_STRIDE : NULL,
|
|
pml4, tap_va + RING_OFF + (unsigned long)k * SURF_STRIDE,
|
|
0xFFFFFFFFUL);
|
|
unsigned long qu = 0xFFFFFFFFUL;
|
|
|
|
if(p != 0xFFFFFFFFUL) {
|
|
g_pan[k] = p;
|
|
} else {
|
|
g_pan[k] = g_pan[0] ? g_pan[0] : 2230;
|
|
}
|
|
LOG(" scan sup %d: %lx", k, p);
|
|
g_pan[k] = 2230; /* dimensione giusta (verificata sul campo) */
|
|
g_pan_ob[k] = 2230;
|
|
g_pan_uv[k] = 2230;
|
|
}
|
|
}
|
|
|
|
/* --- 3) la pipeline: set scanin (con ritentativi) --- */
|
|
{
|
|
int tent;
|
|
|
|
for(tent = 0; tent < 3; tent++) {
|
|
int r = set_scanin();
|
|
|
|
if(r == 0) break;
|
|
LOG(" set scanin fallito (0x%x), riprovo fra 10 s", r);
|
|
usleep(10000000);
|
|
}
|
|
/* niente attesa lunga: il ring e' gia' attivo (lo scanin e' globale),
|
|
* i frame arrivano anche se la sessione IPMI e' occupata */
|
|
}
|
|
|
|
/* --- 4) buffer + server TCP --- */
|
|
p010 = malloc(FRAME_SZ + 0x2000);
|
|
if(!p010) { LOG(" malloc fallita"); return 1; }
|
|
|
|
srv = socket(AF_INET, SOCK_STREAM, 0);
|
|
{
|
|
int one = 1;
|
|
|
|
setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
|
|
/* listen non bloccante: senza client il turno deve finire lo
|
|
* stesso (se no il processo resta appeso in accept per sempre) */
|
|
fcntl(srv, F_SETFL, fcntl(srv, F_GETFL, 0) | O_NONBLOCK);
|
|
}
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr.s_addr = INADDR_ANY;
|
|
{
|
|
int port = PORT;
|
|
|
|
for(;;) {
|
|
addr.sin_port = htons(port);
|
|
if(!bind(srv, (struct sockaddr *)&addr, sizeof(addr))) break;
|
|
LOG(" porta %d occupata, provo la successiva", port);
|
|
port++;
|
|
if(port > PORT + 30) return 1;
|
|
}
|
|
listen(srv, 1);
|
|
LOG(" in ascolto su :%d (il PC si collega)", port);
|
|
notifica_porta(port);
|
|
/* --- benchmark: il costo e' la copyout o la cache della zona? --- */
|
|
{
|
|
static unsigned char bench[0x400000];
|
|
unsigned long t_a, t_b, t_c, tap_pa = 0;
|
|
int okb = 0;
|
|
|
|
tap_pa = pt_walk_ex(pml4, tap_va + RING_OFF, &okb, NULL) & ~0xfffUL;
|
|
t_a = now_ms();
|
|
kernel_copyout(KERNEL_ADDRESS_DMAP_BASE + 0x2c600000UL, bench, 0x400000);
|
|
t_b = now_ms();
|
|
kernel_copyout(KERNEL_ADDRESS_DMAP_BASE + (tap_pa ? tap_pa : 0x455000000UL),
|
|
bench, 0x400000);
|
|
t_c = now_ms();
|
|
LOG(" bench: 4MB da paddr basso 0x2c600000 -> %lu ms | da DCE 0x%lx -> %lu ms",
|
|
t_b - t_a, tap_pa, t_c - t_b);
|
|
}
|
|
|
|
{
|
|
const char *devs[5] = { "/dev/mem", "/dev/kmem", "/dev/dmap", "/dev/physmem", "/dev/pcislot" };
|
|
int di;
|
|
|
|
for(di = 0; di < 5; di++) {
|
|
int fd = open(devs[di], O_RDONLY);
|
|
|
|
LOG(" %s -> fd=%d errno=%d", devs[di], fd, fd < 0 ? errno : 0);
|
|
if(fd >= 0) close(fd);
|
|
}
|
|
}
|
|
{
|
|
int fd = open("/data/pscapture.port", O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
|
char buf[16];
|
|
|
|
snprintf(buf, sizeof(buf), "%d", port);
|
|
if(fd >= 0) {
|
|
write(fd, buf, strlen(buf));
|
|
close(fd);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* --- 5) loop: frame INTERO ad ogni giro ---
|
|
* Con la mappa rapida la lettura completa costa ~1 ms: niente piu'
|
|
* campionamenti a blocchi (accumulavano contenuto vecchio = fantasmi
|
|
* "tripli"), ogni frame inviato e' coerente al 100%. */
|
|
{
|
|
unsigned char *nv12 = malloc(NV12_SZ);
|
|
unsigned long last_set_ms = now_ms();
|
|
int rot = 3;
|
|
|
|
if(!nv12) { LOG(" malloc nv12 fallita"); return 1; }
|
|
|
|
for(;;) {
|
|
unsigned long tr0, tr1;
|
|
int pick;
|
|
|
|
if(conn < 0) {
|
|
conn = accept(srv, NULL, NULL);
|
|
if(conn >= 0) {
|
|
int one = 1;
|
|
|
|
fcntl(conn, F_SETFL, fcntl(conn, F_GETFL, 0) & ~O_NONBLOCK);
|
|
setsockopt(conn, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
|
|
LOG(" PC collegato");
|
|
} else {
|
|
if((now_ms() - last_set_ms) > 300000UL) {
|
|
LOG(" set scanin di manutenzione (a vuoto)");
|
|
set_scanin();
|
|
last_set_ms = now_ms();
|
|
}
|
|
usleep(20000);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* si resta SEMPRE sulla stessa superficie: ogni superficie ha il
|
|
* suo pan; ruotare fra le 4 fa saltare l'immagine in continuazione.
|
|
* Il pan scorre di ~2 byte/s: lo si insegue di continuo (leggero) */
|
|
{
|
|
static unsigned long ultimo_track;
|
|
|
|
if(0 && (now_ms() - ultimo_track) > 500UL) {
|
|
ultimo_track = now_ms();
|
|
if(g_sup >= 0) {
|
|
unsigned long p =
|
|
scan_pan(g_map + (unsigned long)g_sup * SURF_STRIDE, pml4,
|
|
tap_va + RING_OFF + (unsigned long)g_sup * SURF_STRIDE,
|
|
g_pan[g_sup]);
|
|
|
|
long dd = (long)((p + 3840UL - g_pan_ob[g_sup]) % 3840UL);
|
|
|
|
if(dd > 1920) dd -= 3840;
|
|
if(dd < 0) dd = -dd;
|
|
if((dd > 24 && dd < 400) || g_pan_ob[g_sup] == 0xFFFFFFFFUL) {
|
|
if(dd > 96)
|
|
LOG(" track: ob %lu -> %lu (pan %lu)",
|
|
g_pan_ob[g_sup], p, g_pan[g_sup]);
|
|
g_pan_ob[g_sup] = p; /* rumore < 24 B: si ignora */
|
|
}
|
|
if((frame % 240) < 2)
|
|
LOG(" stato: pan=%lu ob=%lu scan=%lx (sup %d)",
|
|
g_pan[g_sup], g_pan_ob[g_sup], p, g_sup);
|
|
}
|
|
}
|
|
}
|
|
/* inseguimento morbido: 1 byte/frame verso l'obiettivo */
|
|
if(0 && g_sup >= 0 && g_pan_ob[g_sup] != 0xFFFFFFFFUL) {
|
|
long dz = (long)((g_pan_ob[g_sup] + 3840UL - g_pan[g_sup]) % 3840UL);
|
|
|
|
if(dz != 0) {
|
|
if(dz > 1920) g_pan[g_sup] = (g_pan[g_sup] + 3839UL) % 3840UL;
|
|
else g_pan[g_sup] = (g_pan[g_sup] + 1UL) % 3840UL;
|
|
}
|
|
}
|
|
tr0 = now_ms();
|
|
if(g_map) {
|
|
int tent;
|
|
|
|
/* il DCE riscrive ogni superficie a 60 Hz: leggendo mentre
|
|
* scrive esce un frame misto (ghost e coda verde). Si legge,
|
|
* si attende 2 ms e si confronta: se e' cambiata era viva e
|
|
* si passa alla successiva. */
|
|
if(g_sup < 0) {
|
|
int k;
|
|
|
|
for(k = 0; k < 4; k++) {
|
|
unsigned char *sl = g_map + (unsigned long)k * SURF_STRIDE;
|
|
|
|
memcpy(p010, sl + g_pan[k], FRAME_SZ);
|
|
usleep(2500);
|
|
if(memcmp(p010, sl + g_pan[k], FRAME_SZ) == 0) {
|
|
g_sup = k;
|
|
break;
|
|
}
|
|
}
|
|
if(g_sup < 0) {
|
|
g_sup = 0;
|
|
memcpy(p010, g_map + g_pan[0], FRAME_SZ);
|
|
}
|
|
LOG(" agganciata superficie %d (pan %lu)", g_sup, g_pan[g_sup]);
|
|
} else {
|
|
unsigned char *slot = g_map + (unsigned long)g_sup * SURF_STRIDE;
|
|
|
|
for(tent = 0; tent < 3; tent++) {
|
|
/* Luma: come sempre (il piano vero e' 11 righe prima, shift
|
|
* verticale invisibile). Croma MISURATA sul frame vivo:
|
|
* il piano croma comincia a 4.104.960 (0x3EA300), cioe'
|
|
* 42.240 byte (11 righe uv) PRIMA del bordo luma, stesso
|
|
* pan. Ancorata coi due bordi della barra bianca (righe
|
|
* luma 318/402 = 4.104.960+159/201 righe uv, errore 0.0).
|
|
* Prima di 4.104.960 c'e' zona morta (non si legge).
|
|
* Le ultime ~10 righe del piano non sono ancora scritte:
|
|
* si replica l'ultima riga buona. */
|
|
memcpy(p010, slot + g_pan[g_sup], 0x3F4800UL);
|
|
memcpy(p010 + 0x3F4800UL,
|
|
slot + 4104960UL + g_pan[g_sup],
|
|
527UL * 3840UL);
|
|
{
|
|
unsigned char *u = p010 + 0x3F4800UL + 527UL * 3840UL;
|
|
const unsigned char *riga = u - 3840UL;
|
|
int riga_i;
|
|
|
|
for(riga_i = 527; riga_i < 540; riga_i++) {
|
|
memcpy(u, riga, 3840);
|
|
u += 3840;
|
|
}
|
|
}
|
|
usleep(6000);
|
|
if(memcmp(p010, slot + g_pan[g_sup], 0x3F4800UL) == 0)
|
|
break;
|
|
}
|
|
(void)slot;
|
|
}
|
|
} else if(pt_read_parallel(pml4, tap_va + RING_OFF + g_pan[0],
|
|
p010, FRAME_SZ)) {
|
|
usleep(5000);
|
|
continue;
|
|
}
|
|
p010_to_nv12(nv12, p010, NV12_SZ);
|
|
tr1 = now_ms();
|
|
|
|
conn = send_frame(conn, nv12, frame);
|
|
inviati++;
|
|
frame++;
|
|
if(frame < 3 || (frame % 120) == 0) {
|
|
LOG(" frame %d: sup %d, lettura+conv %lums, inviati %d",
|
|
frame, g_sup, tr1 - tr0, inviati);
|
|
}
|
|
|
|
if((now_ms() - last_set_ms) > 300000UL) {
|
|
LOG(" set scanin di manutenzione");
|
|
set_scanin();
|
|
last_set_ms = now_ms();
|
|
}
|
|
}
|
|
}
|
|
|
|
if(conn >= 0) close(conn);
|
|
close(srv);
|
|
if(g_ipmi && g_ipmi_dtor) {
|
|
g_ipmi_dtor(g_ipmi);
|
|
g_ipmi = 0;
|
|
}
|
|
LOG("avstream: fine");
|
|
return 0;
|
|
}
|