diff --git a/main.c b/main.c index 87433bd..9e1848d 100644 --- a/main.c +++ b/main.c @@ -1780,15 +1780,11 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) { "if(!gd||gd.error||gd.status){" "gl.innerHTML='

SM API nicht erreichbar — l\\u00e4uft ShadowMountPlus?

';}" "else if(!gd.games||!gd.games.length){" - "if(_gpR<2){" - "gl.innerHTML='

Warte auf SM... ('+(_gpR+1)+'/2)

';" + "if(_gpR<4){" + "gl.innerHTML='

Warte auf SM... ('+(_gpR+1)+'/4)

';" "setTimeout(function(){loadGamePanel(_gpR+1);},4000);" "}else{" - "fetch('/api/local/games').then(function(r){return r.json();})" - ".then(function(ld){" - "if(ld.games&&ld.games.length)renderGamesList(gl,ld.games);" - "else gl.innerHTML='

Keine Spiele — noch nichts gemountet?

';" - "}).catch(function(){gl.innerHTML='

Keine Spiele gefunden

';});" + "gl.innerHTML='

Keine Spiele — SM scan noch aktiv oder keine Spiele gemountet.
Images unten mounten → dann Refresh.

';" "}}" "else{_gpR=0;renderGamesList(gl,gd.games);}" "if(!il)return;" @@ -1808,10 +1804,10 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) { "var sz=img.size?(img.size>1073741824?(img.size/1073741824).toFixed(2)+' GB':(img.size/1048576).toFixed(0)+' MB'):'?';" "var st=img.mounted?'● aktiv':'○';" "var itid=img.title_id||'';" - "var iba=itid?(img.mounted" - "?''" - ":'')" - ":'–';" + "var ipath=img.path||'';" + "var iba=img.mounted" + "?(itid?'':'● aktiv')" + ":'';" "ih+='';" "ih+=''+fn+'';" "ih+='
'+dir+'';" @@ -1821,6 +1817,15 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) { "ih+=''+iba+'';" "});ih+='';il.innerHTML=ih;});}" "function loadGames(){loadGamePanel(0);}" + "function manualMount(path){" + "var il=document.getElementById('images-list');" + "if(il)il.innerHTML='

Mounting...

';" + "fetch('/api/manual/add?path='+encodeURIComponent(path),{method:'POST'})" + ".then(function(r){return r.json();})" + ".then(function(d){" + "if(d.error&&il)il.innerHTML='

'+d.error+'

';" + "setTimeout(function(){loadGamePanel(0);},4000);})" + ".catch(function(){setTimeout(function(){loadGamePanel(0);},4000);});}" "function renderGamesList(el,games){" "if(!el||!games||!games.length){if(el)el.innerHTML='

Keine Spiele gefunden

';return;}" "var h='';" @@ -2875,53 +2880,26 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) { write_prefs(&p); mg_http_reply(c,200,"Content-Type: application/json\r\n","{\"ok\":true}"); } - else if(mg_match(hm->uri,mg_str("/api/local/games"),NULL)){ - /* filesystem scan: /data/homebrew/ entries whose names are PS5 title IDs */ - char *out=malloc(65536); if(!out){mg_http_reply(c,500,"","");return;} - int pos=0; - #define AP(fmt,...) pos+=snprintf(out+pos,65535-pos,fmt,##__VA_ARGS__) - AP("{\"games\":["); - int first=1; - static const char *scan_dirs[]= - {"/data/homebrew","/data/etaHEN/games","/mnt/ext0/homebrew",NULL}; - /* track which title_ids we've already emitted */ - char seen[64][10]; int nseen=0; - for(int di=0;scan_dirs[di]&&pos<60000;di++){ - DIR *d=opendir(scan_dirs[di]); if(!d) continue; - struct dirent *de; - while((de=readdir(d))!=NULL&&pos<60000){ - const char *nm=de->d_name; - /* PS5 title ID: exactly 9 alnum chars, first 4 alpha, last 5 digit */ - if(strlen(nm)!=9) continue; - int ok=1; - for(int i=0;i<4;i++) if(!isalpha((unsigned char)nm[i])){ok=0;break;} - for(int i=4;i<9;i++) if(!isdigit((unsigned char)nm[i])){ok=0;break;} - if(!ok) continue; - /* dedup across directories */ - int dup=0; - for(int s=0;suri,mg_str("/api/manual/add"),NULL)){ + /* add image path to SM's manual.lst and touch it to trigger a scan */ + #define SM_MANUAL "/data/shadowmount/manual.lst" + char path[512]={0}; + mg_http_get_var(&hm->query,"path",path,sizeof(path)); + if(!path[0]) mg_http_get_var(&hm->body,"path",path,sizeof(path)); + /* reject empty, relative, or traversal paths */ + if(!path[0]||path[0]!='/'||strstr(path,"..")) + {mg_http_reply(c,400,"Content-Type: application/json\r\n","{\"error\":\"bad path\"}");return;} + /* append path to manual.lst (create if absent) */ + FILE *ml=fopen(SM_MANUAL,"a"); if(!ml) + {mg_http_reply(c,500,"Content-Type: application/json\r\n","{\"error\":\"open manual.lst\"}");return;} + fprintf(ml,"%s\n",path); fclose(ml); chmod(SM_MANUAL,0666); + /* touch manual.lst so SM detects the change */ + struct timeval tv[2]; gettimeofday(&tv[0],NULL); + struct stat mst; if(stat(SM_MANUAL,&mst)==0&&tv[0].tv_sec<=mst.st_mtime) + tv[0].tv_sec=mst.st_mtime+1; + tv[1]=tv[0]; utimes(SM_MANUAL,tv); + mg_http_reply(c,200,"Content-Type: application/json\r\n","{\"ok\":true}"); + #undef SM_MANUAL } else if(mg_match(hm->uri,mg_str("/api/game/icon"),NULL)){ char tid[64]={0}; mg_http_get_var(&hm->query,"title_id",tid,sizeof(tid));