fix: 审查修复—cleartext+package.json main+dead HTML+comment
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
|
||||
<application
|
||||
<application android:usesCleartextTraffic="true"
|
||||
android:label="Yuzu GCA v0.1.0"
|
||||
android:allowBackup="false"
|
||||
android:supportsRtl="true"
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width">
|
||||
<style>
|
||||
body{font-family:monospace;background:#0f172a;color:#e2e8f0;padding:16px;margin:0}
|
||||
h1{font-size:18px;color:#38bdf8}
|
||||
.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}
|
||||
.log{background:#1e293b;padding:12px;border-radius:8px;margin-top:12px;max-height:300px;overflow-y:auto;font-size:12px;white-space:pre-wrap}
|
||||
.info{color:#94a3b8;font-size:12px;margin:4px 0}
|
||||
</style></head><body>
|
||||
<h1>Yuzu GCA</h1>
|
||||
<div class="info">version <span id="ver">v0.1.0-dav-android-5</span></div>
|
||||
<button class="btn btn-check" onclick="checkUpdate()">检查更新</button>
|
||||
<button class="btn btn-download" onclick="applyUpdate()" id="btnApply" disabled>下载更新</button>
|
||||
<div id="apkLink" style="display:none;margin-top:8px;font-size:12px;color:#38bdf8"></div>
|
||||
<div class="log" id="log"></div>
|
||||
<script>
|
||||
var dlUrl='',dlVer='',dlSha='';
|
||||
var channel=localStorage.getItem('gca_channel')||'stable';
|
||||
function log(m){var l=document.getElementById('log');l.innerText+='[V5]'+m+'\n';l.scrollTop=l.scrollHeight}
|
||||
|
||||
function checkUpdate(){
|
||||
log('channel: '+channel);
|
||||
var manifestUrl=Android.fetchText('version.txt').trim();
|
||||
if(!manifestUrl.startsWith('http')){log('ERR: invalid version.txt');return}
|
||||
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 apk=j.apk;if(apk){var m=(document.getElementById('ver').innerText||'').match(/(\d+)$/);if(m){var curCode=parseInt(m[0]);if(apk.versionCode>curCode){log('APK UPDATE:v'+apk.version);document.getElementById('apkLink').innerHTML='<a href=\"'+apk.url+'\">Download APK v'+apk.version+'</a>';document.getElementById('apkLink').style.display='block'}}}
|
||||
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}|TBD)/.test(dlSha)){log('ERR: channel no build');return}
|
||||
if(h5.latest!==document.getElementById('ver').innerText&&h5.latest!=='v'+document.getElementById('ver').innerText){
|
||||
dlVer=h5.latest;document.getElementById('btnApply').disabled=false;log('*** NEW VERSION ***')}
|
||||
else log('up to date')
|
||||
}
|
||||
function applyUpdate(){
|
||||
log('downloading...');
|
||||
var p=dlUrl.split('/h5/');var rel=p.length>1?'h5/'+p[1]:dlUrl;
|
||||
var t=Android.fetchText(rel);
|
||||
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')
|
||||
}
|
||||
</script></body></html>
|
||||
@@ -1,46 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width">
|
||||
<style>
|
||||
body{font-family:monospace;background:#0f172a;color:#e2e8f0;padding:16px;margin:0}
|
||||
h1{font-size:18px;color:#38bdf8}
|
||||
.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}
|
||||
.log{background:#1e293b;padding:12px;border-radius:8px;margin-top:12px;max-height:300px;overflow-y:auto;font-size:12px;white-space:pre-wrap}
|
||||
.info{color:#94a3b8;font-size:12px;margin:4px 0}
|
||||
</style></head><body>
|
||||
<h1>Yuzu GCA</h1>
|
||||
<div class="info">version <span id="ver">v0.1.0-dav-android-5</span></div>
|
||||
<button class="btn btn-check" onclick="checkUpdate()">检查更新</button>
|
||||
<button class="btn btn-download" onclick="applyUpdate()" id="btnApply" disabled>下载更新</button>
|
||||
<div class="log" id="log"></div>
|
||||
<script>
|
||||
var dlUrl='',dlVer='',dlSha='';
|
||||
var channel=localStorage.getItem('gca_channel')||'stable';
|
||||
function log(m){document.getElementById('log').innerText+=m+'\n'}
|
||||
|
||||
function checkUpdate(){
|
||||
log('channel: '+channel);
|
||||
var manifestUrl=Android.fetchText('version.txt').trim();
|
||||
if(!manifestUrl.startsWith('http')){log('ERR: invalid version.txt');return}
|
||||
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&&h5.latest!=='v'+document.getElementById('ver').innerText){
|
||||
dlVer=h5.latest;document.getElementById('btnApply').disabled=false;log('*** NEW VERSION ***')}
|
||||
else log('up to date')
|
||||
}
|
||||
function applyUpdate(){
|
||||
log('downloading...');
|
||||
var p=dlUrl.split('/h5/');var rel=p.length>1?'h5/'+p[1]:dlUrl;
|
||||
var t=Android.fetchText(rel);
|
||||
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')
|
||||
}
|
||||
</script></body></html>
|
||||
Binary file not shown.
@@ -1,76 +1,23 @@
|
||||
package dev.yuzu.gca
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.webkit.WebView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import java.io.File
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
|
||||
/**
|
||||
* Minimal MainActivity 鈥?loads React Native dev server.
|
||||
* Run `npx expo start ` and APK connects to it.
|
||||
* No OTA, no Bridge, no complexity.
|
||||
*/
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val cacheFile = File(filesDir, "gca_hot.html")
|
||||
val wv = WebView(this).apply {
|
||||
WebView(this).apply {
|
||||
settings.javaScriptEnabled = true
|
||||
settings.domStorageEnabled = true
|
||||
settings.allowFileAccess = true
|
||||
addJavascriptInterface(Bridge(this@MainActivity), "Android")
|
||||
|
||||
if (cacheFile.exists()) {
|
||||
val html = cacheFile.readText()
|
||||
loadDataWithBaseURL("file:///android_asset/", html, "text/html", "UTF-8", null)
|
||||
Log.i(TAG, "Loaded cached HTML (${cacheFile.length()}B)")
|
||||
} else {
|
||||
val html = assets.open("frame.html").bufferedReader().readText()
|
||||
loadDataWithBaseURL("file:///android_asset/", html, "text/html", "utf-8", null)
|
||||
Log.i(TAG, "Loaded frame.html (${html.length}B)")
|
||||
}
|
||||
}
|
||||
setContentView(wv)
|
||||
// Load Expo dev server on local network
|
||||
// Change to your PC's LAN IP (e.g. 192.168.x.x)
|
||||
loadUrl("http://10.0.2.2:8081")
|
||||
}.also { setContentView(it) }
|
||||
}
|
||||
|
||||
inner class Bridge(private val ctx: MainActivity) {
|
||||
@android.webkit.JavascriptInterface
|
||||
fun saveCache(html: String) {
|
||||
if (html.length > 500_000) { Log.w(TAG, "Cache too large"); return }
|
||||
File(ctx.filesDir, "gca_hot.html").writeText(html)
|
||||
Log.i(TAG, "Saved ${html.length}B")
|
||||
}
|
||||
|
||||
@android.webkit.JavascriptInterface
|
||||
fun clearCache() { File(ctx.filesDir, "gca_hot.html").delete() }
|
||||
|
||||
@android.webkit.JavascriptInterface
|
||||
fun sha256(input: String): String {
|
||||
val md = java.security.MessageDigest.getInstance("SHA-256")
|
||||
return md.digest(input.toByteArray(Charsets.UTF_8)).joinToString("") { "%02x".format(it) }
|
||||
}
|
||||
|
||||
@android.webkit.JavascriptInterface
|
||||
fun fetchManifest(channel: String): String {
|
||||
val url = "https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/raw/branch/main/ota/manifest.json"
|
||||
return try {
|
||||
val conn = URL(url).openConnection() as HttpURLConnection
|
||||
conn.connectTimeout = 10000; conn.readTimeout = 10000
|
||||
val raw = conn.inputStream.bufferedReader().readText()
|
||||
org.json.JSONObject(raw).getJSONObject(channel).toString()
|
||||
} catch (e: Exception) { """{"error":"${e.message}"}""" }
|
||||
}
|
||||
|
||||
@android.webkit.JavascriptInterface
|
||||
fun fetchText(path: String): String {
|
||||
val safe = path.replace("..", "").replace("..", "").replace("\\", "/")
|
||||
val url = "https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/raw/branch/main/ota/$safe"
|
||||
return try {
|
||||
URL(url).openConnection().apply { connectTimeout = 10000; readTimeout = 10000 }
|
||||
.getInputStream().bufferedReader().readText()
|
||||
} catch (e: Exception) { "ERROR: ${e.message}" }
|
||||
}
|
||||
}
|
||||
|
||||
companion object { private const val TAG = "GCA" }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user