diff --git a/main.c b/main.c index 11ee4fb..3f19aa8 100644 --- a/main.c +++ b/main.c @@ -1777,7 +1777,6 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) { "fetch('/api/sm/gamelist').then(function(r){return r.text();})" ".then(function(html){" "if(gl)gl.innerHTML=html;" - "var noData=gl&&gl.querySelector('p')&&gl.textContent.indexOf('Keine')<0&&gl.textContent.indexOf('nicht')<0;" "if(_gpR===0&&gl&&!gl.querySelector('button')){" "setTimeout(function(){if(_gpR===0)loadGamePanel(1);},8000);}" "}).catch(function(){" @@ -2899,53 +2898,59 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) { #undef SM_MANUAL } else if(mg_match(hm->uri,mg_str("/api/sm/gamelist"),NULL)){ - /* server-side game list HTML using /api/v1/games (has title_name) */ - char *games_json=sm_api_req("POST","/api/v1/games","{}"); - char *out=malloc(131072); if(!out){mg_http_reply(c,500,"","");if(games_json)free(games_json);return;} + /* images always available; enrich with title_name from games API when mounted */ + char *img_json=sm_api_req("POST","/api/v1/images","{}"); + char *gam_json=sm_api_req("POST","/api/v1/games","{}"); + char *out=malloc(131072); if(!out){if(img_json)free(img_json);if(gam_json)free(gam_json);mg_http_reply(c,500,"","");return;} int pos=0; #define GL(fmt,...) pos+=snprintf(out+pos,131071-pos,fmt,##__VA_ARGS__) - if(!games_json){ + if(!img_json){ GL("

SM API nicht erreichbar

"); } else { - const char *gstart=strstr(games_json,"\"games\""); - if(!gstart||!strstr(gstart,"\"title_id\"")){GL("

Keine Spiele gefunden

");} + const char *istart=strstr(img_json,"\"images\""); + if(!istart){GL("

Keine Spiele gefunden

");} else { int n=0; - const char *cur=gstart; + const char *cur=istart; while((cur=strstr(cur,"{\"path\""))!=NULL&&pos<120000){ - char tid[16]={0},name[128]={0},imgpath[512]={0}; - int mounted=0; - /* extract title_id */ - const char *tv=strstr(cur,"\"title_id\":\""); - if(tv){tv+=12;const char *te=strchr(tv,'"');if(te&&te-tv<(int)sizeof(tid)){memcpy(tid,tv,te-tv);}} - /* extract title_name */ - const char *nv=strstr(cur,"\"title_name\":\""); - if(nv){nv+=14;const char *ne=strchr(nv,'"');if(ne&&ne-nv<(int)sizeof(name)){memcpy(name,nv,ne-nv);}} - /* extract image_path for size */ - const char *iv=strstr(cur,"\"image_path\":\""); - if(iv){iv+=14;const char *ie=strchr(iv,'"');if(ie&&ie-iv<(int)sizeof(imgpath)){memcpy(imgpath,iv,ie-iv);}} - /* extract mounted */ - const char *mv=strstr(cur,"\"mounted\":"); - if(mv){mv+=10;mounted=(mv[0]=='t');} - /* get file size from image_path */ - char szbuf[32]="?"; - if(imgpath[0]){ - /* unescape JSON \/ */ - char rp[512]={0};int ri=0; - for(int i=0;imgpath[i]&&ri<(int)sizeof(rp)-1;i++){ - if(imgpath[i]=='\\'&&imgpath[i+1]=='/'){rp[ri++]='/';i++;} - else rp[ri++]=imgpath[i];} - struct stat ist; if(stat(rp,&ist)==0){ - if(ist.st_size>1073741824) snprintf(szbuf,sizeof(szbuf),"%.2f GB",(double)ist.st_size/1073741824.0); - else snprintf(szbuf,sizeof(szbuf),"%lld MB",(long long)ist.st_size/1048576);} + char imgpath[512]={0},mp[256]={0}; + long long sz=0; int mounted=0; + /* extract path, mount_point, size, mounted from images API */ + const char *pv=strstr(cur,"\"path\":\""); if(pv){pv+=8;const char *pe=strchr(pv,'"');if(pe&&pe-pv<(int)sizeof(imgpath)-1)memcpy(imgpath,pv,pe-pv);} + const char *mv2=strstr(cur,"\"mount_point\":\""); if(mv2){mv2+=15;const char *me=strchr(mv2,'"');if(me&&me-mv2<(int)sizeof(mp)-1)memcpy(mp,mv2,me-mv2);} + const char *sv=strstr(cur,"\"size\":"); if(sv){sv+=7;sz=strtoll(sv,NULL,10);} + const char *mv=strstr(cur,"\"mounted\":"); if(mv){mv+=10;mounted=(mv[0]=='t');} + /* extract title_id from mount_point: /mnt/shadowmnt/PPSA20515_name_hash */ + char tid[16]={0}; + const char *bn=strrchr(mp,'/'); bn=bn?bn+1:mp; + char tmp[64]={0}; strncpy(tmp,bn,sizeof(tmp)-1); + char *us=strchr(tmp,'_'); if(us)*us=0; + if(strlen(tmp)==9){int ok=1; + for(int i=0;i<4;i++) if(!isalpha((unsigned char)tmp[i])){ok=0;break;} + for(int i=4;i<9;i++) if(!isdigit((unsigned char)tmp[i])){ok=0;break;} + if(ok) strncpy(tid,tmp,sizeof(tid)-1);} + /* look up title_name from games JSON if available */ + char dname[128]={0}; + if(tid[0]&&gam_json){ + char srch[32]; snprintf(srch,sizeof(srch),"\"title_id\":\"%s\"",tid); + const char *gp=strstr(gam_json,srch); + if(gp){const char *nn=strstr(gp,"\"title_name\":\""); + if(nn){nn+=14;const char *ne=strchr(nn,'"');if(ne&&ne-nn<(int)sizeof(dname)-1)memcpy(dname,nn,ne-nn);}} } - /* unescape name */ - char dname[128]={0};{int di=0; - for(int i=0;name[i]&&di<(int)sizeof(dname)-1;i++){ - if(name[i]=='\\'&&name[i+1]=='/'){ dname[di++]='/';i++;} - else dname[di++]=name[i];} - } - if(!dname[0]) strncpy(dname,tid,sizeof(dname)-1); + if(!dname[0]&&tid[0]) strncpy(dname,tid,sizeof(dname)-1); + /* unescape path for stat */ + char rp[512]={0};{int ri=0;for(int i=0;imgpath[i]&&ri<(int)sizeof(rp)-1;i++){ + if(imgpath[i]=='\\'&&imgpath[i+1]=='/'){rp[ri++]='/';i++;}else rp[ri++]=imgpath[i];}} + /* size from stat (more reliable than JSON size field) */ + char szbuf[32]={0}; + {struct stat ist; + if(rp[0]&&stat(rp,&ist)==0){ + if(ist.st_size>1073741824) snprintf(szbuf,sizeof(szbuf),"%.2f GB",(double)ist.st_size/1073741824.0); + else snprintf(szbuf,sizeof(szbuf),"%lld MB",(long long)ist.st_size/1048576);} + else if(sz>0){ + if(sz>1073741824) snprintf(szbuf,sizeof(szbuf),"%.2f GB",(double)sz/1073741824.0); + else snprintf(szbuf,sizeof(szbuf),"%lld MB",sz/1048576);} + else strncpy(szbuf,"?",sizeof(szbuf)-1);} const char *mntcol=mounted?"color:#10b981":"color:var(--dim)"; const char *mnttxt=mounted?"● aktiv":"○ inaktiv"; const char *bs_um="background:transparent;border:1px solid #f87171;border-radius:4px;color:#f87171;padding:3px 10px;cursor:pointer;font-size:.75rem;"; @@ -2953,23 +2958,24 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) { GL("
"); GL("
" "" - "%.4s
",tid,tid); + "%.4s
",tid,tid[0]?tid:"?"); GL("
" "
%s
" "
%s — %s
", - dname, tid, szbuf); + dname[0]?dname:rp, tid[0]?tid:"–", szbuf); GL("
" "%s" "" "
", - mntcol,mnttxt, tid,imgpath, mounted?"_smUnmount":"_smMount", + mntcol,mnttxt, tid,rp, mounted?"_smUnmount":"_smMount", mounted?bs_um:bs_mt, mounted?"Unmount":"Mount"); n++; cur++; } if(n==0) GL("

Keine Spiele gefunden

"); } - free(games_json); + free(img_json); } + if(gam_json) free(gam_json); out[pos]=0; #undef GL mg_http_reply(c,200,"Content-Type: text/html; charset=utf-8\r\nCache-Control: no-store\r\n","%s",out); @@ -3009,6 +3015,16 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) { if(stat(src,&sst)!=0) src[0]=0;} closedir(sd);} } + /* fallback: use GC's icon cache (already resized PNGs from Game Compressor) */ + if(!src[0]){ + DIR *gcd=opendir("/data/GameCompressor/icon-cache"); + if(gcd){struct dirent *de;size_t tl=strlen(tid); + while(!src[0]&&(de=readdir(gcd))!=NULL) + if(strncmp(de->d_name,tid,tl)==0&&de->d_name[tl]=='-'){ + snprintf(src,sizeof(src),"/data/GameCompressor/icon-cache/%s",de->d_name); + if(stat(src,&sst)!=0) src[0]=0;} + closedir(gcd);} + } if(src[0]){ /* copy source to cache using cp via raw read/write */ FILE *sf=fopen(src,"rb"); diff --git a/mongoose.c b/mongoose.c index e0485ee..97fab2e 100644 --- a/mongoose.c +++ b/mongoose.c @@ -3501,9 +3501,10 @@ static void *p_open(const char *path, int flags) { MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) / sizeof(b2[0])); return (void *) _wfopen(b1, b2); #else - const char *mode = flags == MG_FS_READ ? "rbe" - : (flags & MG_FS_EXCL) ? "wxbe" - : "a+be"; // e for CLOSEXEC + /* PS5/FreeBSD: 'e' flag (O_CLOEXEC) may not be supported; use plain modes */ + const char *mode = flags == MG_FS_READ ? "rb" + : (flags & MG_FS_EXCL) ? "wxb" + : "a+b"; return (void *) fopen(path, mode); #endif }