fix: 审查修复—cleartext+package.json main+dead HTML+comment

This commit is contained in:
LukeMackin
2026-07-22 18:21:42 +08:00
parent ce7204b016
commit 09fde0501f
7 changed files with 40 additions and 226 deletions

View File

@@ -4,7 +4,7 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application <application android:usesCleartextTraffic="true"
android:label="Yuzu GCA v0.1.0" android:label="Yuzu GCA v0.1.0"
android:allowBackup="false" android:allowBackup="false"
android:supportsRtl="true" android:supportsRtl="true"

View File

@@ -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>

View File

@@ -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>

View File

@@ -1,76 +1,23 @@
package dev.yuzu.gca package dev.yuzu.gca
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.webkit.WebView import android.webkit.WebView
import androidx.appcompat.app.AppCompatActivity 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() { class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
WebView(this).apply {
val cacheFile = File(filesDir, "gca_hot.html")
val wv = WebView(this).apply {
settings.javaScriptEnabled = true settings.javaScriptEnabled = true
settings.domStorageEnabled = true settings.domStorageEnabled = true
settings.allowFileAccess = true // Load Expo dev server on local network
addJavascriptInterface(Bridge(this@MainActivity), "Android") // Change to your PC's LAN IP (e.g. 192.168.x.x)
loadUrl("http://10.0.2.2:8081")
if (cacheFile.exists()) { }.also { setContentView(it) }
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)
}
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" }
}

View File

@@ -1,43 +1,4 @@
var React = require("react"); import { registerRootComponent } from 'expo';
var View = require("react-native").View; import App from './App';
var Text = require("react-native").Text;
var AppRegistry = require("react-native").AppRegistry;
var Updates = require("expo-updates");
function App() { registerRootComponent(App);
var [msg, setMsg] = React.useState("checking...");
React.useEffect(function() {
async function check() {
try {
var rv = Updates.runtimeVersion || "empty";
var ch = Updates.channel || "default";
var id = Updates.updateId || "none";
var emb = Updates.isEmbeddedLaunch ? "embedded" : "not-embedded";
var info = "rv=" + rv + " ch=" + ch + " id=" + id + " emb=" + emb;
var result = await Updates.checkForUpdateAsync();
if (result.isAvailable) {
info += "\nUPDATE AVAILABLE! downloading...";
await Updates.fetchUpdateAsync();
info += "\ndownloaded. Restart app.";
await Updates.reloadAsync();
} else {
info += "\nno update available";
}
setMsg(info);
} catch(e) {
setMsg("Error: " + e.message);
}
}
check();
}, []);
return React.createElement(View, {style: {flex:1,backgroundColor:"#0f172a",alignItems:"center",justifyContent:"center"}},
React.createElement(Text, {style: {fontSize:28,fontWeight:"800",color:"#e2e8f0"}}, "Yuzu GCA"),
React.createElement(Text, {style: {fontSize:14,color:"#64748b",marginTop:8}}, "v0.1.0-dav-android-10"),
React.createElement(Text, {style: {fontSize:10,color:"#3b82f6",marginTop:8,textAlign:"center"}}, msg)
);
}
AppRegistry.registerComponent("main", function() { return App; });

View File

@@ -1,8 +1,8 @@
{ {
"name": "@yuzu-gca/client-android", "name": "@yuzu-gca/client-android",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"main": "expo-router/entry", "main": "index.js",
"scripts": { "scripts": {
"start": "expo start", "start": "expo start",
"android": "expo start --android", "android": "expo start --android",