Rebrand as TPSchedule and refine clear liquid glass dock

This commit is contained in:
Codex
2026-10-13 22:08:20 +08:00
parent 50fcc70e8e
commit 204e945b2e
45 changed files with 1155 additions and 103 deletions
+22 -5
View File
@@ -1,15 +1,32 @@
param([string]$ToolchainRoot = 'C:/Users/david/Desktop/app/.tools')
param(
[string]$ToolchainRoot = 'C:/Users/david/Desktop/app/.tools',
[ValidateSet('debug', 'release')][string]$Mode = 'release'
)
$ErrorActionPreference = 'Stop'
$env:HEPAN_TOOLCHAIN_ROOT=$ToolchainRoot
. "$PSScriptRoot/android-env.ps1"
$projectRoot=Split-Path $PSScriptRoot -Parent
Push-Location "$projectRoot/mobile"
try {
flutter.bat build apk --debug
# Refresh local dependency metadata without network. The build then regenerates
# plugin registration for its mode, excluding test-only plugins from release.
flutter.bat pub get --offline
if($LASTEXITCODE -ne 0) { throw '本地依赖准备失败,未开始构建' }
flutter.bat build apk "--$Mode"
if($LASTEXITCODE -ne 0) { throw 'Android 构建失败,未复制 APK' }
$built=Join-Path (Get-Location) 'build/app/outputs/flutter-apk/app-debug.apk'
$built=Join-Path (Get-Location) "build/app/outputs/flutter-apk/app-$Mode.apk"
if(!(Test-Path -LiteralPath $built)) { throw '本次构建未生成 APK' }
$taskSigner = Join-Path $env:ANDROID_HOME 'build-tools/36.0.0/apksigner.bat'
$taskSignature = & $taskSigner verify --print-certs $built
if ($LASTEXITCODE -ne 0) { throw 'APK 签名验证失败,未复制 APK' }
$taskExpectedCertificate = 'AF5C21B1BA1E48E461D8EA38DF0FE1C3653F64EB7E8E7728BAE6E5D988BA0D0A'
$taskSignatureText = $taskSignature -join "`n"
if ($taskSignatureText -notmatch 'certificate SHA-256 digest:\s*([a-fA-F0-9]{64})' -or
$Matches[1].ToUpperInvariant() -ne $taskExpectedCertificate) {
throw '签名证书与原证书不一致,未复制 APK。请保留原有签名文件,不要重新生成。'
}
New-Item -ItemType Directory -Force "$projectRoot/dist" | Out-Null
Copy-Item -LiteralPath $built -Destination "$projectRoot/dist/RSchedule-debug.apk" -Force
Get-Item -LiteralPath "$projectRoot/dist/RSchedule-debug.apk" | Select-Object FullName,Length,LastWriteTime
$taskArtifact = if ($Mode -eq 'release') { 'TPSchedule.apk' } else { 'TPSchedule-debug.apk' }
Copy-Item -LiteralPath $built -Destination "$projectRoot/dist/$taskArtifact" -Force
Get-Item -LiteralPath "$projectRoot/dist/$taskArtifact" | Select-Object FullName,Length,LastWriteTime
} finally { Pop-Location }
+125
View File
@@ -0,0 +1,125 @@
"""Export public APK signing information and verify continuity. No private keys."""
import argparse
import hashlib
import json
import struct
import zipfile
from pathlib import Path
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.serialization import pkcs7
from cryptography import x509
def length_prefixed(data, offset=0):
size = struct.unpack_from('<I', data, offset)[0]
end = offset + 4 + size
if end > len(data):
raise ValueError('Malformed APK signing record.')
return data[offset + 4:end], end
def modern_certificate(apk):
# APK v2/v3 certificates live before the ZIP central directory, not META-INF.
with open(apk, 'rb') as stream:
stream.seek(0, 2)
size = stream.tell()
stream.seek(max(0, size - 65557))
tail = stream.read()
eocd = tail.rfind(b'PK\x05\x06')
if eocd < 0:
raise ValueError('Missing ZIP directory.')
central = struct.unpack_from('<I', tail, eocd + 16)[0]
stream.seek(central - 24)
footer = stream.read(24)
if footer[8:] != b'APK Sig Block 42':
return None
block_size = struct.unpack_from('<Q', footer)[0]
if block_size < 24 or block_size + 8 > central:
raise ValueError('Malformed APK signing block.')
stream.seek(central - block_size - 8)
block = stream.read(block_size + 8)
if struct.unpack_from('<Q', block)[0] != block_size:
raise ValueError('APK signing block sizes differ.')
offset = 8
while offset < len(block) - 24:
pair_size = struct.unpack_from('<Q', block, offset)[0]
offset += 8
pair = block[offset:offset + pair_size]
if pair_size < 4 or len(pair) != pair_size:
raise ValueError('Malformed APK signer pair.')
scheme = struct.unpack_from('<I', pair)[0]
if scheme in (0x7109871A, 0xF05368C0, 0x1B93AD61):
signers, _ = length_prefixed(pair[4:])
signer, signer_end = length_prefixed(signers)
if signer_end != len(signers):
raise ValueError('Multiple APK signers are not supported by this report.')
signed_data, _ = length_prefixed(signer)
_, digest_end = length_prefixed(signed_data)
certificates, _ = length_prefixed(signed_data, digest_end)
cert_der, _ = length_prefixed(certificates)
return x509.load_der_x509_certificate(cert_der)
offset += pair_size
return None
def certificate(apk):
modern = modern_certificate(apk)
if modern is not None:
return modern
with zipfile.ZipFile(apk) as archive:
blocks = [name for name in archive.namelist()
if name.upper().startswith('META-INF/')
and name.upper().endswith(('.RSA', '.EC', '.DSA'))]
if len(blocks) != 1:
raise ValueError('Expected one v1 signing certificate block; verify APK with apksigner.')
certificates = pkcs7.load_der_pkcs7_certificates(archive.read(blocks[0]))
if len(certificates) != 1:
raise ValueError('Expected one signing certificate, not a certificate chain.')
return certificates[0]
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--previous', required=True)
parser.add_argument('--current', required=True)
parser.add_argument('--output-dir', required=True)
args = parser.parse_args()
previous = certificate(args.previous)
current = certificate(args.current)
old_der = previous.public_bytes(serialization.Encoding.DER)
der = current.public_bytes(serialization.Encoding.DER)
public = current.public_key().public_bytes(
serialization.Encoding.DER, serialization.PublicFormat.SubjectPublicKeyInfo)
if old_der != der:
raise ValueError('Signing certificate changed. Existing signing material must be preserved.')
output = Path(args.output_dir)
output.mkdir(parents=True, exist_ok=True)
details = {
'appName': 'TPSchedule',
'applicationId': 'com.tpschedule.app',
'certificateUnchanged': True,
'subject': current.subject.rfc4514_string(),
'certificateSHA256': hashlib.sha256(der).hexdigest().upper(),
'certificateSHA1': hashlib.sha1(der).hexdigest().upper(),
'certificateMD5': hashlib.md5(der).hexdigest().upper(),
'publicKeySHA256': hashlib.sha256(public).hexdigest().upper(),
'publicKeyMD5': hashlib.md5(public).hexdigest().upper(),
'publicKeySPKIHex': public.hex().upper(),
'publicKeyBits': current.public_key().key_size,
'certificateNote': 'Existing Android Debug certificate preserved; store/filing acceptance must be checked.',
}
(output / 'TPSchedule-signing-info.json').write_text(
json.dumps(details, ensure_ascii=False, indent=2), encoding='utf-8')
(output / 'TPSchedule-signing-certificate.pem').write_bytes(
current.public_bytes(serialization.Encoding.PEM))
(output / 'TPSchedule-public-key.pem').write_bytes(
current.public_key().public_bytes(serialization.Encoding.PEM,
serialization.PublicFormat.SubjectPublicKeyInfo))
print(json.dumps({'certificateUnchanged': True,
'certificateSHA256': details['certificateSHA256'],
'publicFiles': str(output)}, ensure_ascii=True))
if __name__ == '__main__':
main()