Files
Yuzu-GCA/ota/h5/index-stable.html
LukeMackin 4b4103cf06 fix: 审查阻塞修复 — SHA256 TBD占位+beta/nightly→stable+硬编码channel+路径校验
- manifest.json: beta/nightly h5.url → index-stable.html
- SHA256: TBD占位(build后push-hotfix.sh更新)
- index-stable: ver元素用channel变量, match?.[1]防御
- fetchText: 过滤..防止路径遍历
2026-07-22 00:25:40 +08:00

76 lines
4.4 KiB
HTML

<!DOCTYPE html>
<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width">
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui;background:#0f172a;color:#e2e8f0;padding:16px}
h1{font-size:18px;color:#38bdf8}
.card{background:#1e293b;border-radius:8px;padding:16px;margin:12px 0}
.card-title{font-size:14px;color:#94a3b8;margin-bottom:8px}
.btn{display:block;width:100%;padding:14px;margin:8px 0;border:none;border-radius:8px;font-size:16px;cursor:pointer}
.btn-check{background:#1e40af;color:#fff}
.btn-download{background:#15803d;color:#fff;display:none}
.btn-download.show{display:block}
.log{background:#0f172a;padding:12px;border-radius:8px;margin-top:12px;max-height:200px;overflow-y:auto;font-size:11px;white-space:pre-wrap;color:#94a3b8}
.ver{color:#94a3b8;font-size:12px;margin:4px 0}
.gear{position:absolute;top:20px;right:16px;font-size:20px;cursor:pointer;color:#94a3b8}
.modal{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.7);z-index:99}
.modal.show{display:flex;align-items:center;justify-content:center}
.modal-box{background:#1e293b;border-radius:12px;padding:24px;width:80%;max-width:360px}
.modal-title{font-size:16px;color:#38bdf8;margin-bottom:16px}
.ch-opt{display:block;width:100%;padding:12px;margin:6px 0;border:1px solid #334155;border-radius:6px;background:transparent;color:#e2e8f0;font-size:14px;cursor:pointer;text-align:left}
.ch-opt.on{border-color:#38bdf8;background:#1e40af40}
.badge{display:inline-block;padding:2px 8px;border-radius:4px;font-size:10px;margin-left:8px}
.badge-new{background:#dc2626;color:#fff}
</style></head><body>
<div class="gear" onclick="openSettings()">⚙️</div>
<h1>Yuzu GCA</h1>
<div class="ver"><span id="ver"></span><span id="badge" class="badge badge-new" style="display:none">NEW</span></div>
<div class="card"><div class="card-title">当前渠道: <span id="chLabel">Stable</span></div></div>
<button class="btn btn-check" onclick="checkUpdate()">检查更新</button>
<button class="btn btn-download" id="btnApply" onclick="applyUpdate()">下载更新</button>
<div class="log" id="log"></div>
<div class="modal" id="modal">
<div class="modal-box">
<div class="modal-title">选择渠道</div>
<button class="ch-opt on" onclick="setChannel('stable')">Stable (发行)</button>
<button class="ch-opt" onclick="setChannel('beta')">Beta (测试)</button>
<button class="ch-opt" onclick="setChannel('nightly')">Nightly (开发)</button>
<button class="btn btn-check" style="margin-top:12px" onclick="closeSettings()">关闭</button>
</div>
</div>
<script>
var channel=localStorage.getItem('gca_channel')||'stable';
var dlUrl='',dlVer='',dlSha='';
function log(m){var l=document.getElementById('log');l.innerText+=m+'\n';l.scrollTop=l.scrollHeight}
function openSettings(){document.getElementById('modal').classList.add('show')}
function closeSettings(){document.getElementById('modal').classList.remove('show')}
function setChannel(ch){
channel=ch;localStorage.setItem('gca_channel',ch);
document.getElementById('chLabel').innerText=ch==='stable'?'Stable (发行)':ch==='beta'?'Beta (测试)':'Nightly (开发)';
closeSettings();checkUpdate()
}
function checkUpdate(){
log('channel: '+channel+' checking manifest...');
var j=JSON.parse(Android.fetchManifest(channel));
if(j.error){log('ERR: '+j.error);return}
var h5=j.h5;log('latest: '+h5.latest+' min: '+h5.min_version);
var v=h5.min_version.replace(/[^0-9]/g,''),my=(document.getElementById('ver').innerText||'').replace(/[^0-9]/g,'');
if(parseInt(my||'0')<parseInt(v||'0')){log('ERR: APK too old (need v'+h5.min_version+')');return}
dlUrl=h5.url;dlSha=h5.sha256||'';
if(!dlSha||/^0{64}$/.test(dlSha)){log('ERR: channel no build');return}
if(h5.latest!==document.getElementById('ver').innerText){
dlVer=h5.latest;document.getElementById('btnApply').classList.add('show');log('*** NEW VERSION ***')}
else log('up to date')
}
function applyUpdate(){
log('downloading...');
var t=Android.fetchText(h5UrlRelative(dlUrl));
if(typeof t!=='string'||t.startsWith('ERROR:')){log('ERR: '+t);return}
var h=Android.sha256(t);
if(h!==dlSha){log('SHA256 MISMATCH!');return}
Android.saveCache(t);log('OK! restart app to apply')
}
function h5UrlRelative(u){var p=u.split('/ota/');return p.length>1?'h5/'+p[1].split('/h5/')[1]:u}
document.getElementById('ver').innerText=Android.fetchManifest(channel).match(/"latest":"([^"]+)"/)?.[1]||channel;
</script></body></html>