mirror of
https://github.com/TheCriicom/omega-dashboard-ps5.git
synced 2026-10-17 14:01:33 +02:00
- Il demone legge lo stato di alimentazione dai flag del kernel SceSystemStateMgrInfo e SceSystemStateMgrStatus (come ShadowMountPlus e OnionHEN) e, prima che la console si spenga o si addormenti, ferma musica, voce del party, server di controllo e download di PatchDL e smonta i giochi avviati da un disco esterno (in riposo resta montato quello in primo piano). Al risveglio riparte da solo (power.c). - Spegni, Riavvia e Modalità riposo dal menu di Omega aspettano installazioni, copie e salvataggi online, smontano i giochi esterni e chiedono al demone di fermarsi prima. - /system_ex non resta più montato in scrittura dopo l'avvio di un gioco da disco esterno. - Demone: niente più giri a vuoto se l'uscita audio si rompe; party e notifiche interrogati meno spesso e con attese crescenti senza rete; caricamenti di musica e comandi della voce non bloccano più il server di controllo; la UI riavvia il demone solo se è bloccato davvero; gestore dei crash con stack proprio, SIGTERM gestito, registri sotto 1 MB. - fsync prima di ogni rename (aggiornamenti, caricatori, libreria, lettore, copie dei giochi); le copie interrotte vengono riconosciute e pulite. - Installer: ogni passo di rete finisce in omega-installer.log, incluso nella diagnostica.
440 lines
25 KiB
C
440 lines
25 KiB
C
// Omega UI — Archivio e spostamenti: dove sta ogni gioco e quanto occupa, e
|
|
// lo spostamento (o la copia) tra memoria interna e dischi esterni.
|
|
//
|
|
// Si spostano i giochi in cartella (con eboot.bin): quelli installati da Omega
|
|
// come cartella, caricati dal PC o già su un disco. La copia avviene in un
|
|
// thread con l'avanzamento nella fila della home; si controlla lo spazio prima,
|
|
// si verifica la dimensione dopo e solo allora si toglie l'originale.
|
|
// interna → disco /user/app/<TID> → <disco>/homebrew/<TID>, poi il gioco si
|
|
// disinstalla dalla console e il montaggio automatico (o il
|
|
// primo avvio) lo registra di nuovo dal disco;
|
|
// disco → interna si smonta da /system_ex/app, i file vanno in /user/app/<TID>
|
|
// (dove c'era solo la registrazione) e si registra lì;
|
|
// disco → disco si copia e, spostando, si smonta e si cancella l'originale.
|
|
// Ogni file copiato va su disco (fsync) e, prima di togliere l'originale, sync().
|
|
// Durante la copia la destinazione ha il file OMEGA_COPY_MARK: se resta (corrente
|
|
// mancata a metà), al tentativo successivo la copia parziale si toglie.
|
|
// I giochi installati dal sistema come pacchetto (app.pkg cifrato) non si
|
|
// possono montare da un disco: per quelli c'è lo spostamento della console.
|
|
// Anche i file .pkg (gli installer) si spostano o si copiano da qui (pkgs.c).
|
|
#include "app.h"
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#ifdef PS5
|
|
#define APP_ROOT "/user/app"
|
|
int sceAppInstUtilInitialize(void);
|
|
int sceAppInstUtilAppUnInstall(const char *);
|
|
int unmount(const char *dir, int flags);
|
|
int app_register(const char *title_id, const char *dir); // install.c
|
|
#else
|
|
#define APP_ROOT OMEGA_DIR "/apps" // sul Mac i giochi "installati" stanno qui
|
|
#endif
|
|
|
|
enum { SK_FOLDER, SK_SYSPKG, SK_EXT, SK_OTHER };
|
|
typedef struct { char tid[16], name[96], path[400], where[48]; int kind, app; long long size; } StEnt;
|
|
#define ST_MAX 200
|
|
static StEnt st[ST_MAX]; static int nst, st_sel; static float st_anim, st_scroll;
|
|
static SDL_atomic_t sizing; static SDL_mutex *smx;
|
|
|
|
// ------------------------------------------------------------- dimensioni --
|
|
static long long tree_size(const char *p, int depth) {
|
|
struct stat s; if (lstat(p, &s) != 0) return 0;
|
|
if (!S_ISDIR(s.st_mode)) return (long long)s.st_size;
|
|
if (depth > 24) return 0;
|
|
long long t = 0; DIR *d = opendir(p); if (!d) return 0;
|
|
struct dirent *e;
|
|
while ((e = readdir(d))) { if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) continue; char q[1024]; snprintf(q, sizeof q, "%s/%s", p, e->d_name); t += tree_size(q, depth + 1); }
|
|
closedir(d); return t;
|
|
}
|
|
static int size_thread(void *ud) {
|
|
(void)ud;
|
|
for (int i = 0; i < nst; i++) {
|
|
char p[400]; SDL_LockMutex(smx); snprintf(p, sizeof p, "%s", st[i].path); SDL_UnlockMutex(smx);
|
|
long long s = tree_size(p, 0);
|
|
SDL_LockMutex(smx); if (i < nst && !strcmp(st[i].path, p)) st[i].size = s; SDL_UnlockMutex(smx);
|
|
}
|
|
SDL_AtomicSet(&sizing, 0);
|
|
return 0;
|
|
}
|
|
|
|
static void fmt_size(long long b, char *o, size_t n) {
|
|
if (b <= 0) snprintf(o, n, "\xE2\x80\xA6");
|
|
else if (b >= 1000000000LL) snprintf(o, n, _("%.1f GB"), b / 1e9); else snprintf(o, n, _("%.0f MB"), b / 1e6);
|
|
}
|
|
static double free_gb(const char *p) { struct statfs s; return statfs(p, &s) == 0 ? (double)s.f_bavail * s.f_bsize / 1e9 : -1; }
|
|
|
|
// ------------------------------------------------------------------ elenco --
|
|
static void st_build(void) {
|
|
if (!smx) smx = SDL_CreateMutex();
|
|
SDL_LockMutex(smx);
|
|
nst = 0;
|
|
for (int i = 0; i < napps && nst < ST_MAX; i++) {
|
|
AppEntry *a = &apps[i];
|
|
if (a->builtin || a->hb || a->pld) continue;
|
|
StEnt *e = &st[nst]; memset(e, 0, sizeof *e);
|
|
snprintf(e->tid, sizeof e->tid, "%s", a->tid); snprintf(e->name, sizeof e->name, "%s", a->name); e->app = i;
|
|
char eb[300];
|
|
if (a->ext) { e->kind = SK_EXT; snprintf(e->path, sizeof e->path, "%s", a->src); snprintf(e->where, sizeof e->where, "%s", a->drive); }
|
|
else if (snprintf(eb, sizeof eb, APP_ROOT "/%s/eboot.bin", a->tid), access(eb, 0) == 0) { e->kind = SK_FOLDER; snprintf(e->path, sizeof e->path, APP_ROOT "/%s", a->tid); snprintf(e->where, sizeof e->where, "%s", _("Memoria interna")); }
|
|
else if (snprintf(eb, sizeof eb, APP_ROOT "/%s/app.pkg", a->tid), access(eb, 0) == 0) { e->kind = SK_SYSPKG; snprintf(e->path, sizeof e->path, APP_ROOT "/%s", a->tid); snprintf(e->where, sizeof e->where, "%s", _("Memoria interna")); }
|
|
else {
|
|
// montato su /system_ex/app da un altro strumento (ShadowMount) oppure installato
|
|
// dal sistema altrove (archivio esteso, M.2): in entrambi i casi non lo sposta Omega
|
|
char sx[120]; snprintf(sx, sizeof sx, OMEGA_SYSROOT "/system_ex/app/%s/eboot.bin", a->tid);
|
|
if (access(sx, 0) == 0) { e->kind = SK_OTHER; snprintf(e->path, sizeof e->path, OMEGA_SYSROOT "/system_ex/app/%s", a->tid); snprintf(e->where, sizeof e->where, "%s", _("Montato da un altro strumento")); }
|
|
else { e->kind = SK_SYSPKG; snprintf(e->path, sizeof e->path, APP_ROOT "/%s", a->tid); snprintf(e->where, sizeof e->where, "%s", _("Archivio della console")); }
|
|
}
|
|
e->size = -1;
|
|
nst++;
|
|
}
|
|
SDL_UnlockMutex(smx);
|
|
if (st_sel >= nst) st_sel = nst ? nst - 1 : 0;
|
|
if (!SDL_AtomicGet(&sizing)) {
|
|
SDL_AtomicSet(&sizing, 1);
|
|
SDL_Thread *t = SDL_CreateThread(size_thread, "sizes", NULL);
|
|
if (t) SDL_DetachThread(t); else SDL_AtomicSet(&sizing, 0);
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------------------- lavoro --
|
|
// Una sola operazione alla volta; l'avanzamento si vede nella fila della home.
|
|
typedef struct { StEnt e; char dst_mount[160], dst_label[48], file_src[600]; int copy_only, to_internal, is_file; } MoveJob;
|
|
static SDL_atomic_t mv_state; // 0 fermo, 1 in corso, 2 finito
|
|
static volatile long long mv_done, mv_total; static volatile int mv_cancel;
|
|
static char mv_label[96], mv_phase[96], mv_result[300]; static int mv_err; static Uint32 mv_ended, rebuild_at;
|
|
|
|
static int copy_file(const char *a, const char *b) {
|
|
int in = open(a, O_RDONLY); if (in < 0) return -1;
|
|
int out = open(b, O_WRONLY | O_CREAT | O_TRUNC, 0777); if (out < 0) { close(in); return -2; }
|
|
char *buf = malloc(1 << 20); ssize_t r = 0; int rc = buf ? 0 : -3;
|
|
while (buf && (r = read(in, buf, 1 << 20)) > 0) {
|
|
if (mv_cancel) { rc = -9; break; }
|
|
if (write(out, buf, (size_t)r) != r) { rc = -4; break; }
|
|
mv_done += r;
|
|
}
|
|
if (r < 0) rc = -5;
|
|
if (!rc && fsync(out) != 0) rc = -10; // exFAT sui dischi USB: i dati restano in cache finché non si chiede
|
|
free(buf); close(in); if (close(out) != 0 && !rc) rc = -6;
|
|
if (rc) unlink(b);
|
|
else { struct stat s; if (stat(a, &s) == 0) chmod(b, s.st_mode & 0777); }
|
|
return rc;
|
|
}
|
|
static int copy_tree(const char *a, const char *b) {
|
|
struct stat s; if (lstat(a, &s) != 0) return -1;
|
|
if (!S_ISDIR(s.st_mode)) return copy_file(a, b);
|
|
if (mkdir(b, 0777) != 0 && errno != EEXIST) return -7;
|
|
DIR *d = opendir(a); if (!d) return -8;
|
|
struct dirent *e; int rc = 0;
|
|
while (!rc && (e = readdir(d))) {
|
|
if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) continue;
|
|
char pa[1024], pb[1024]; snprintf(pa, sizeof pa, "%s/%s", a, e->d_name); snprintf(pb, sizeof pb, "%s/%s", b, e->d_name);
|
|
rc = copy_tree(pa, pb);
|
|
}
|
|
closedir(d); return rc;
|
|
}
|
|
static int rm_tree(const char *p) {
|
|
struct stat s; if (lstat(p, &s) != 0) return 0;
|
|
if (!S_ISDIR(s.st_mode)) return unlink(p);
|
|
DIR *d = opendir(p); if (!d) return -1;
|
|
struct dirent *e; int rc = 0;
|
|
while ((e = readdir(d))) { if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) continue; char q[1024]; snprintf(q, sizeof q, "%s/%s", p, e->d_name); if (rm_tree(q)) rc = -1; }
|
|
closedir(d);
|
|
return rmdir(p) || rc;
|
|
}
|
|
|
|
// copia a metà di un tentativo precedente: sul disco si toglie tutta; nella
|
|
// memoria interna restano la registrazione di Omega (sce_sys, mount.lnk)
|
|
static void clean_partial(const char *dst, int internal) {
|
|
if (!internal) { rm_tree(dst); return; }
|
|
DIR *d = opendir(dst); if (!d) return;
|
|
struct dirent *e;
|
|
while ((e = readdir(d))) {
|
|
if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..") || !strcmp(e->d_name, "sce_sys") || !strcmp(e->d_name, "mount.lnk") || !strcmp(e->d_name, OMEGA_COPY_MARK)) continue;
|
|
char q[1024]; snprintf(q, sizeof q, "%s/%s", dst, e->d_name); rm_tree(q);
|
|
}
|
|
closedir(d);
|
|
char mk[1024]; snprintf(mk, sizeof mk, "%s/" OMEGA_COPY_MARK, dst); unlink(mk);
|
|
}
|
|
|
|
static int move_thread(void *arg) {
|
|
MoveJob *j = arg;
|
|
mv_done = 0; mv_cancel = 0; mv_err = 0; mv_result[0] = 0;
|
|
char dst[600], src[600];
|
|
int rc = 0;
|
|
if (j->is_file) {
|
|
// un file .pkg in <disco>/PKG/ (o /data/pkg)
|
|
snprintf(src, sizeof src, "%s", j->file_src);
|
|
const char *base = strrchr(src, '/'); base = base ? base + 1 : src;
|
|
char dir[400]; snprintf(dir, sizeof dir, "%s", j->dst_mount);
|
|
mkdir(dir, 0777);
|
|
snprintf(dst, sizeof dst, "%s/%s", dir, base);
|
|
char part[620]; snprintf(part, sizeof part, "%s.part", dst); unlink(part); // resto di una copia interrotta
|
|
struct stat s; mv_total = stat(src, &s) == 0 ? (long long)s.st_size : 0;
|
|
double fr = free_gb(dir);
|
|
if (fr >= 0 && fr * 1e9 < mv_total + 64e6) { snprintf(mv_result, sizeof mv_result, _("Spazio insufficiente su %s: servono %.1f GB"), j->dst_label, mv_total / 1e9); mv_err = 1; goto out; }
|
|
if (access(dst, 0) == 0) { snprintf(mv_result, sizeof mv_result, _("Su %s c'è già un file con questo nome"), j->dst_label); mv_err = 1; goto out; }
|
|
snprintf(mv_phase, sizeof mv_phase, "%s", j->copy_only ? _("Copia") : _("Spostamento"));
|
|
if (!j->copy_only && rename(src, dst) == 0) { mv_done = mv_total; } // stesso disco: istantaneo
|
|
else if ((rc = copy_file(src, part)) != 0 || (rc = rename(part, dst)) != 0) { unlink(part); snprintf(mv_result, sizeof mv_result, mv_cancel ? _("Operazione annullata") : _("Copia non riuscita (errore %d): il disco è pieno o in sola lettura?"), rc); mv_err = !mv_cancel; goto out; }
|
|
else if (!j->copy_only) { sync(); unlink(src); }
|
|
snprintf(mv_result, sizeof mv_result, j->copy_only ? _("Copiato su %s") : _("Spostato su %s"), j->dst_label);
|
|
goto out;
|
|
}
|
|
StEnt *e = &j->e;
|
|
snprintf(src, sizeof src, "%s", e->path);
|
|
if (j->to_internal) snprintf(dst, sizeof dst, APP_ROOT "/%s", e->tid);
|
|
else { char hb[300]; snprintf(hb, sizeof hb, "%s/homebrew", j->dst_mount); mkdir(hb, 0777); snprintf(dst, sizeof dst, "%s/%s", hb, e->tid); }
|
|
snprintf(mv_phase, sizeof mv_phase, "%s", _("Calcolo dello spazio"));
|
|
mv_total = e->size > 0 ? e->size : tree_size(src, 0);
|
|
// spazio: quello del gioco più mezzo giga di margine
|
|
double fr = free_gb(j->to_internal ? APP_ROOT : j->dst_mount);
|
|
if (fr >= 0 && fr * 1e9 < mv_total + 512e6) { snprintf(mv_result, sizeof mv_result, _("Spazio insufficiente su %s: servono %.1f GB, liberi %.1f GB"), j->dst_label, mv_total / 1e9, fr); mv_err = 1; goto out; }
|
|
char mk[700]; snprintf(mk, sizeof mk, "%s/" OMEGA_COPY_MARK, dst);
|
|
if (access(mk, 0) == 0) { omega_log("archivio: copia interrotta in %s, la tolgo", dst); clean_partial(dst, j->to_internal); }
|
|
// a destinazione deve esserci al massimo la registrazione che fa Omega (mount.lnk + sce_sys)
|
|
char eb[700]; snprintf(eb, sizeof eb, "%s/eboot.bin", dst);
|
|
if (access(eb, 0) == 0) { snprintf(mv_result, sizeof mv_result, _("%s c'è già su %s"), e->name, j->dst_label); mv_err = 1; goto out; }
|
|
snprintf(mv_phase, sizeof mv_phase, "%s", j->copy_only ? _("Copia") : _("Spostamento"));
|
|
if (mkdir(dst, 0777) == 0 || errno == EEXIST) { int fd = open(mk, O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd >= 0) { fsync(fd); close(fd); } sync(); }
|
|
rc = copy_tree(src, dst);
|
|
if (rc) {
|
|
if (!j->to_internal) rm_tree(dst); // copia a metà sul disco: via
|
|
snprintf(mv_result, sizeof mv_result, mv_cancel ? _("Operazione annullata: l'originale è intatto") : _("Copia non riuscita (errore %d): l'originale è intatto"), rc);
|
|
mv_err = !mv_cancel; goto out;
|
|
}
|
|
snprintf(mv_phase, sizeof mv_phase, "%s", _("Verifica"));
|
|
long long got = tree_size(dst, 0);
|
|
if (got < mv_total) { snprintf(mv_result, sizeof mv_result, _("La copia è incompleta (%lld di %lld byte): l'originale è intatto"), got, mv_total); mv_err = 1; goto out; }
|
|
// copia completa e su disco: solo adesso si toglie il segno e poi l'originale
|
|
unlink(mk); sync();
|
|
snprintf(mv_phase, sizeof mv_phase, "%s", _("Registrazione"));
|
|
#ifdef PS5
|
|
if (j->to_internal) {
|
|
// dal disco alla memoria interna: niente più mount, i file sono a casa
|
|
char mp[96]; snprintf(mp, sizeof mp, "/system_ex/app/%s", e->tid);
|
|
unmount(mp, MNT_FORCE); rmdir(mp);
|
|
char ln[700]; snprintf(ln, sizeof ln, "%s/mount.lnk", dst); unlink(ln);
|
|
int r = app_register(e->tid, "/user/app/");
|
|
omega_log("archivio: %s registrato dalla memoria interna -> 0x%x", e->tid, r);
|
|
if (r != 0 && r != (int)0x80990002) { snprintf(mv_result, sizeof mv_result, _("Copiato, ma la console non l'ha registrato (0x%08X)"), (unsigned)r); mv_err = 1; goto out; }
|
|
} else if (e->kind == SK_FOLDER && !j->copy_only) {
|
|
// dalla memoria interna al disco: si disinstalla; il montaggio automatico
|
|
// (o il primo avvio) lo registra di nuovo dal disco
|
|
sceAppInstUtilInitialize();
|
|
int r = sceAppInstUtilAppUnInstall(e->tid);
|
|
omega_log("archivio: %s disinstallato dalla memoria interna -> 0x%x", e->tid, r);
|
|
}
|
|
if (e->kind == SK_EXT && !j->copy_only) { char mp[96]; snprintf(mp, sizeof mp, "/system_ex/app/%s", e->tid); unmount(mp, MNT_FORCE); }
|
|
#endif
|
|
if (!j->copy_only && !(e->kind == SK_FOLDER && !j->to_internal)) {
|
|
// l'originale su disco si toglie solo adesso, a copia verificata
|
|
if (rm_tree(src) != 0) omega_log("archivio: originale %s non tolto del tutto", src);
|
|
}
|
|
#ifndef PS5
|
|
if (e->kind == SK_FOLDER && !j->copy_only) rm_tree(src); // sul Mac non c'è la disinstallazione
|
|
#endif
|
|
snprintf(mv_result, sizeof mv_result, j->copy_only ? _("%s copiato su %s") : _("%s spostato su %s"), e->name, j->dst_label);
|
|
out:
|
|
omega_log("archivio: %s", mv_result);
|
|
free(j);
|
|
SDL_AtomicSet(&mv_state, 2);
|
|
return 0;
|
|
}
|
|
|
|
static void move_start(MoveJob *j) {
|
|
if (SDL_AtomicGet(&mv_state) == 1) { free(j); set_msg(_("C'è già uno spostamento in corso"), 1); return; }
|
|
snprintf(mv_label, sizeof mv_label, "%s", j->is_file ? (strrchr(j->file_src, '/') ? strrchr(j->file_src, '/') + 1 : j->file_src) : j->e.name);
|
|
mv_phase[0] = 0; mv_total = 0; mv_done = 0; mv_ended = 0;
|
|
SDL_AtomicSet(&mv_state, 1);
|
|
SDL_Thread *t = SDL_CreateThreadWithStackSize(move_thread, "move", 512 * 1024, j);
|
|
if (t) SDL_DetachThread(t); else { free(j); SDL_AtomicSet(&mv_state, 0); }
|
|
set_msg(_("Spostamento avviato: segui la barra nella home"), 0);
|
|
}
|
|
|
|
void storage_tick(void) {
|
|
if (SDL_AtomicGet(&mv_state) != 2) return;
|
|
SDL_AtomicSet(&mv_state, 0);
|
|
mv_ended = SDL_GetTicks();
|
|
set_msg(mv_result, mv_err);
|
|
toast(mv_err ? IC_CLOSE : IC_DRIVE, NULL, 0, mv_label, mv_result);
|
|
rebuild_at = SDL_GetTicks() + 4500; // dopo che la home ha riletto dischi e titoli
|
|
}
|
|
|
|
int storage_view(InstallView *v) {
|
|
memset(v, 0, sizeof *v);
|
|
if (SDL_AtomicGet(&mv_state) == 1) {
|
|
v->active = 1; snprintf(v->name, sizeof v->name, "%s", mv_label);
|
|
long long d = mv_done, t = mv_total;
|
|
v->prog = t > 0 ? clampf((float)d / (float)t, 0, 1) : -1;
|
|
if (t > 0) snprintf(v->phase, sizeof v->phase, _("%s \xC2\xB7 %d%% \xC2\xB7 %.1f / %.1f GB"), mv_phase, (int)(v->prog * 100), d / 1e9, t / 1e9);
|
|
else snprintf(v->phase, sizeof v->phase, "%s...", mv_phase);
|
|
return 1;
|
|
}
|
|
if (mv_ended && SDL_GetTicks() - mv_ended < (mv_err ? 60000u : 12000u)) {
|
|
v->ended = mv_ended; v->err = mv_err; v->prog = 1; snprintf(v->name, sizeof v->name, "%s", mv_label); snprintf(v->result, sizeof v->result, "%s", mv_result);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
int storage_busy(void) { return SDL_AtomicGet(&mv_state) == 1; }
|
|
void storage_cancel(void) { if (SDL_AtomicGet(&mv_state) == 1) mv_cancel = 1; }
|
|
|
|
// ------------------------------------------------------ scelta del disco --
|
|
static Drive dd[12]; static int ndd;
|
|
static char dlab[14][120]; static const char *dit[14]; static int dkind[14]; // dkind: -1 interna, >= 0 indice in dd
|
|
static MoveJob pend; static int pend_copy;
|
|
static void dest_pick(int idx, void *ud) {
|
|
(void)ud; if (idx < 0 || idx >= 14 || !dit[idx]) return;
|
|
MoveJob *j = malloc(sizeof *j); if (!j) return;
|
|
*j = pend; j->copy_only = pend_copy;
|
|
if (dkind[idx] < 0) { j->to_internal = 1; snprintf(j->dst_label, sizeof j->dst_label, "%s", _("Memoria interna")); snprintf(j->dst_mount, sizeof j->dst_mount, "%s", j->is_file ? OMEGA_SYSROOT "/data/pkg" : APP_ROOT); }
|
|
else {
|
|
snprintf(j->dst_label, sizeof j->dst_label, "%s", dd[dkind[idx]].label);
|
|
if (j->is_file) snprintf(j->dst_mount, sizeof j->dst_mount, "%s/PKG", dd[dkind[idx]].mount);
|
|
else snprintf(j->dst_mount, sizeof j->dst_mount, "%s", dd[dkind[idx]].mount);
|
|
}
|
|
move_start(j);
|
|
}
|
|
// elenca le destinazioni possibili (tutte tranne quella dove sta già)
|
|
static int dest_menu(const char *title, const char *cur_path, int allow_internal) {
|
|
ndd = drives_list(dd, 12);
|
|
int n = 0;
|
|
memset(dit, 0, sizeof dit);
|
|
if (allow_internal && strncmp(cur_path, APP_ROOT, strlen(APP_ROOT)) && strncmp(cur_path, OMEGA_SYSROOT "/data/pkg", strlen(OMEGA_SYSROOT "/data/pkg"))) {
|
|
double fr = free_gb(APP_ROOT);
|
|
snprintf(dlab[n], sizeof dlab[0], _("Memoria interna \xC2\xB7 %.0f GB liberi"), fr < 0 ? 0 : fr); dkind[n] = -1; dit[n] = dlab[n]; n++;
|
|
}
|
|
for (int i = 0; i < ndd && n < 13; i++) {
|
|
if (!strncmp(cur_path, dd[i].mount, strlen(dd[i].mount))) continue;
|
|
snprintf(dlab[n], sizeof dlab[0], _("%s \xC2\xB7 %.0f GB liberi di %.0f"), dd[i].label, dd[i].free_gb, dd[i].total_gb); dkind[n] = i; dit[n] = dlab[n]; n++;
|
|
}
|
|
if (!n) { set_msg(_("Collega un disco esterno (exFAT o archivio esteso) per spostare qui"), 1); return 0; }
|
|
menu_open(title, dit, n, dest_pick, NULL);
|
|
return n;
|
|
}
|
|
|
|
// dal menu di un gioco o dal pannello: sposta (copy 0) o copia (copy 1)
|
|
static void move_entry(const StEnt *e, int copy) {
|
|
if (e->kind == SK_SYSPKG) {
|
|
confirm_open(_("Questo gioco è installato dal sistema come pacchetto cifrato e non si può leggere da un disco con Omega. Per i giochi PS4 usa Impostazioni della console \xE2\x80\xBA Archiviazione \xE2\x80\xBA Sposta su archivio esteso."), _("Ho capito"), NULL, NULL);
|
|
return;
|
|
}
|
|
if (e->kind == SK_OTHER) { set_msg(_("Questo gioco lo ha montato un altro strumento (es. ShadowMount): spostalo dalla sua cartella sul disco"), 1); return; }
|
|
memset(&pend, 0, sizeof pend); pend.e = *e; pend_copy = copy;
|
|
char t[160]; snprintf(t, sizeof t, copy ? _("Copia %s su...") : _("Sposta %s su..."), e->name);
|
|
dest_menu(t, e->path, 1);
|
|
}
|
|
void storage_move_app(int app_idx) {
|
|
st_build();
|
|
for (int i = 0; i < nst; i++) if (st[i].app == app_idx) { move_entry(&st[i], 0); return; }
|
|
set_msg(_("Si spostano solo i giochi: homebrew e payload stanno nelle cartelle del caricatore"), 1);
|
|
}
|
|
// file .pkg (pkgs.c)
|
|
void storage_move_pkg(const char *path, int copy) {
|
|
memset(&pend, 0, sizeof pend); pend.is_file = 1; pend_copy = copy;
|
|
snprintf(pend.file_src, sizeof pend.file_src, "%s", path);
|
|
dest_menu(copy ? _("Copia il pkg su...") : _("Sposta il pkg su..."), path, 1);
|
|
}
|
|
|
|
// ---------------------------------------------------------------- pannello --
|
|
void storage_open(void) {
|
|
st_build(); st_sel = 0; st_scroll = 0;
|
|
if (ov_top() != OV_STORAGE) ov_push(OV_STORAGE);
|
|
}
|
|
|
|
static char del_path[400];
|
|
static void del_yes(int idx, void *ud) {
|
|
(void)ud; if (idx != 0) return;
|
|
// solo cartelle di giochi sui dischi esterni: dentro /mnt e con eboot.bin
|
|
char eb[500]; snprintf(eb, sizeof eb, "%s/eboot.bin", del_path);
|
|
if (strncmp(del_path, OMEGA_SYSROOT "/mnt/", strlen(OMEGA_SYSROOT "/mnt/")) || access(eb, 0) != 0) { set_msg(_("Qui non si cancella niente"), 1); return; }
|
|
set_msg(rm_tree(del_path) == 0 ? _("Gioco cancellato dal disco") : _("Cancellazione non riuscita (disco in sola lettura?)"), 0);
|
|
st_build();
|
|
}
|
|
static void opts_pick(int idx, void *ud) {
|
|
(void)ud; if (st_sel >= nst) return;
|
|
StEnt *e = &st[st_sel];
|
|
if (idx == 0) move_entry(e, 0);
|
|
else if (idx == 1) move_entry(e, 1);
|
|
else if (idx == 2 && e->kind == SK_EXT) { snprintf(del_path, sizeof del_path, "%s", e->path); static char q[300]; snprintf(q, sizeof q, _("Cancellare definitivamente %s da %s? I salvataggi restano sulla console."), e->name, e->where); confirm_open(q, _("Cancella"), del_yes, NULL); }
|
|
}
|
|
|
|
void storage_draw(float t) {
|
|
int a = (int)(255 * t);
|
|
if (rebuild_at && SDL_GetTicks() > rebuild_at) { rebuild_at = 0; st_build(); }
|
|
fill_rect(0, 0, SCREEN_W, SCREEN_H, mix(RGB(10, 12, 20), g_theme_base, 0.12f), t > 0.98f ? 255 : a);
|
|
int x0 = 110;
|
|
draw_icon(IC_DRIVE, x0 + 22, 74, 44, C_ACC2, a);
|
|
draw_text(font(W_LIGHT, 38), _("Archivio e spostamenti"), x0 + 62, 52, C_WHITE, a, AL_L);
|
|
// dischi: barra dello spazio
|
|
Drive dv[12]; int nd = drives_list(dv, 12);
|
|
int cx = x0, cy = 140, cw = 340;
|
|
{ double fr = free_gb(APP_ROOT); struct statfs s; double tot = statfs(APP_ROOT, &s) == 0 ? (double)s.f_blocks * s.f_bsize / 1e9 : 0;
|
|
fill_rrect(cx, cy, cw, 96, 20, C_WHITE, a * 6 / 100);
|
|
draw_icon(IC_GAMEPAD, cx + 36, cy + 34, 28, C_TXT, a);
|
|
draw_text_fit(font(W_MED, 23), _("Memoria interna"), cx + 66, cy + 18, cw - 80, C_TXT, a, AL_L);
|
|
char m[64]; snprintf(m, sizeof m, _("%.0f GB liberi"), fr < 0 ? 0 : fr); draw_text(font(W_REG, 19), m, cx + 66, cy + 48, C_DIM, a, AL_L);
|
|
float k = tot > 0 ? (float)((tot - fr) / tot) : 0; fill_rrect(cx + 20, cy + 76, cw - 40, 8, 4, C_WHITE, a * 14 / 100); fill_rrect(cx + 20, cy + 76, (int)((cw - 40) * clampf(k, 0, 1)), 8, 4, k > 0.9f ? C_ERR : C_ACC, a); }
|
|
for (int i = 0; i < nd && i < 4; i++) {
|
|
int x = cx + (i + 1) * (cw + 20);
|
|
fill_rrect(x, cy, cw, 96, 20, C_WHITE, a * 6 / 100);
|
|
draw_icon(IC_USB, x + 36, cy + 34, 28, C_ACC2, a);
|
|
draw_text_fit(font(W_MED, 23), dv[i].label, x + 66, cy + 18, cw - 80, C_TXT, a, AL_L);
|
|
char m[64]; snprintf(m, sizeof m, _("%.0f GB liberi di %.0f"), dv[i].free_gb, dv[i].total_gb); draw_text(font(W_REG, 19), m, x + 66, cy + 48, C_DIM, a, AL_L);
|
|
float k = dv[i].total_gb > 0 ? (float)((dv[i].total_gb - dv[i].free_gb) / dv[i].total_gb) : 0;
|
|
fill_rrect(x + 20, cy + 76, cw - 40, 8, 4, C_WHITE, a * 14 / 100); fill_rrect(x + 20, cy + 76, (int)((cw - 40) * clampf(k, 0, 1)), 8, 4, k > 0.9f ? C_ERR : C_ACC2, a);
|
|
}
|
|
if (!nd) draw_text_wrap(font(W_REG, 22), _("Nessun disco esterno collegato: collegane uno (exFAT) per spostare lì i giochi."), cx + cw + 30, cy + 22, 900, 2, 30, C_FAINT, a);
|
|
// giochi
|
|
int top = 270, rh = 92, w = SCREEN_W - 2 * x0, bottom = SCREEN_H - 130;
|
|
if (!nst) { draw_text(font(W_REG, 26), _("Nessun gioco installato"), SCREEN_W / 2, top + 80, C_FAINT, a, AL_C); }
|
|
st_anim = approach(st_anim, (float)st_sel, 20.0f);
|
|
float tgt = st_sel * rh + rh > bottom - top ? (float)(st_sel * rh + rh - (bottom - top) + 20) : 0;
|
|
st_scroll = approach(st_scroll, tgt, 14.0f);
|
|
SDL_Rect clip = { 0, top - 8, SCREEN_W, bottom - top + 8 }; SDL_RenderSetClipRect(R, &clip);
|
|
if (smx) SDL_LockMutex(smx);
|
|
for (int i = 0; i < nst; i++) {
|
|
int y = top + i * rh - (int)st_scroll;
|
|
if (y + rh < top || y > bottom) continue;
|
|
StEnt *e = &st[i];
|
|
float fa = clampf(1 - fabsf(st_anim - i), 0, 1);
|
|
fill_rrect(x0, y, w, rh - 12, 18, C_WHITE, (int)(a * (0.05f + 0.08f * fa)));
|
|
if (i == st_sel) stroke_rrect(x0 - 3, y - 3, w + 6, rh - 6, 21, 3, C_WHITE, a);
|
|
if (e->app >= 0 && e->app < napps) home_tile(e->app, x0 + 10, y + 8, rh - 28, a);
|
|
draw_text_fit(font(W_MED, 27), e->name, x0 + rh + 10, y + 12, w - 700, C_TXT, a, AL_L);
|
|
const char *kd = e->kind == SK_FOLDER ? _("cartella, si sposta") : e->kind == SK_EXT ? _("su disco, si sposta") : e->kind == SK_SYSPKG ? _("pacchetto di sistema") : _("montato da altro");
|
|
char sub[160]; snprintf(sub, sizeof sub, "%s \xC2\xB7 %s", e->tid, kd);
|
|
draw_text_fit(font(W_REG, 20), sub, x0 + rh + 10, y + 48, w - 700, e->kind <= SK_EXT || e->kind == SK_EXT ? C_DIM : C_FAINT, a, AL_L);
|
|
draw_text_fit(font(W_MED, 22), e->where, x0 + w - 380, y + 16, 250, e->kind == SK_EXT ? C_ACC2 : C_TXT, a, AL_L);
|
|
char sz[24]; fmt_size(e->size, sz, sizeof sz); draw_text(font(W_REG, 22), sz, x0 + w - 30, y + 28, C_DIM, a, AL_R);
|
|
}
|
|
if (smx) SDL_UnlockMutex(smx);
|
|
SDL_RenderSetClipRect(R, NULL);
|
|
int ic[4]; const char *lb[4]; int n = 0;
|
|
ic[n] = IC_BTN_X; lb[n++] = _("Sposta"); ic[n] = IC_BTN_SQ; lb[n++] = _("Opzioni");
|
|
if (SDL_AtomicGet(&mv_state) == 1) { ic[n] = IC_BTN_TRI; lb[n++] = _("Annulla spostamento"); }
|
|
ic[n] = IC_BTN_O; lb[n++] = _("Indietro");
|
|
hints(ic, lb, n, a);
|
|
}
|
|
|
|
void storage_input(int b) {
|
|
if (b == B_O) { ov_pop(); return; }
|
|
if (b == B_UP && st_sel > 0) { st_sel--; sfx_play(SFX_MOVE); }
|
|
else if (b == B_DOWN && st_sel < nst - 1) { st_sel++; sfx_play(SFX_MOVE); }
|
|
else if (b == B_X && st_sel < nst) move_entry(&st[st_sel], 0);
|
|
else if (b == B_TRI) { storage_cancel(); set_msg(_("Annullo: l'originale resta dov'era"), 0); }
|
|
else if (b == B_SQ && st_sel < nst) {
|
|
static const char *it[3]; int n = 0;
|
|
it[n++] = _("Sposta su un altro disco"); it[n++] = _("Copia su un altro disco");
|
|
if (st[st_sel].kind == SK_EXT) it[n++] = _("Cancella dal disco");
|
|
menu_open(st[st_sel].name, it, n, opts_pick, NULL);
|
|
}
|
|
}
|