Replace bundled ROM extraction with runtime import/download flow
Commodore ROMs are copyrighted and can no longer ship inside the APK. MainActivity now detects missing ROMs on first launch and blocks startup with a dialog offering two paths: pick files via the system file picker, or download the official VICE 3.8 tarball and extract the four ROMs from it client-side (minimal USTAR reader, no extra deps). - Drop the extractViceRoms Gradle task and asset bundling; add res/extract_roms.sh for local sideloading during development instead - gitignore keystores/keystore.properties ahead of a signed release - Move architecture.md into docs/, refresh it for the screen-mirroring and watch-input additions, and add accompanying Mermaid diagrams - Add docs/debugging.md and docs/publish.md (Play Store release notes, ROM-import compliance rationale)
This commit is contained in:
+236
@@ -0,0 +1,236 @@
|
||||
# Publishing guide
|
||||
|
||||
Steps to generate signing credentials and publish both apps. See
|
||||
`docs/architecture.md` for what each app does and `CLAUDE.md` for build
|
||||
commands.
|
||||
|
||||
## 0. Legal considerations — read before publishing either app
|
||||
|
||||
**The companion app no longer bundles Commodore ROM files.** The
|
||||
`kernal`, `basic`, `chargen`, and `1541` ROMs are still under copyright
|
||||
(commercial rights are held by Cloanto, who license them as part of "C64
|
||||
Forever"), so shipping them pre-installed would be copyright infringement,
|
||||
independent of Google Play's own policy on emulators.
|
||||
|
||||
The `extractViceRoms` Gradle task that used to copy these ROMs out of
|
||||
`res/vice-3.8.tar.gz` into `app/src/main/assets/` (and the auto-copy-on-first-
|
||||
launch logic in `MainActivity`) has been removed. Instead, on first launch
|
||||
`MainActivity.initEmulator()` checks `getExternalFilesDir(null)` for
|
||||
`kernal`/`basic`/`chargen`/`1541` and, if any are missing, shows a blocking
|
||||
dialog (`showRomImportDialog`) that lets the user pick their own ROM dump via
|
||||
the system file picker (the same Storage Access Framework flow already used
|
||||
for `.d64` disk imports). Picked files are matched to a canonical ROM name by
|
||||
filename substring (`romNameForFile`), so both VICE's versioned names (e.g.
|
||||
`kernal-901227-03.bin`) and plain renamed files work.
|
||||
|
||||
`res/extract_roms.sh` still exists for pulling the ROMs out of the bundled
|
||||
tarball into `res/roms/` (gitignored) for local testing/sideloading — it is
|
||||
no longer wired into the Gradle build and nothing under it ships in the APK.
|
||||
|
||||
The dialog also offers a **"Download ROMs"** button (`downloadRoms` /
|
||||
`extractRomsFromTarGz` in `MainActivity.kt`), which fetches VICE's own
|
||||
official source release —
|
||||
`https://github.com/VICE-Team/svn-mirror/releases/download/3.8.0/vice-3.8.tar.gz`
|
||||
(byte-identical to `res/vice-3.8.tar.gz`, verified by SHA-256) — and extracts
|
||||
the same four ROMs client-side. **This is a weaker legal position than the
|
||||
import flow, not a replacement for it:** the app is still facilitating
|
||||
acquisition of the ROMs over the network, rather than requiring the user to
|
||||
already possess a legally-obtained dump. It avoids *bundling* the ROMs in the
|
||||
APK (the Play Store policy trigger called out below), but if you want the
|
||||
strictest "bring your own ROM" posture for a public Play Store listing,
|
||||
consider removing this button before submission and keeping only the import
|
||||
path.
|
||||
|
||||
The actual game disk images (`versions/*.d64`, the commercial "Schwert und
|
||||
Magie" releases) are **not** bundled either — `CLAUDE.md` describes copying
|
||||
them onto the device manually via USB/adb. Keep it that way; never add a
|
||||
"download the game" path to either app.
|
||||
|
||||
The Rebble community store (watch app distribution) is far less strictly
|
||||
enforced, but the same legal exposure exists regardless of where the watch
|
||||
app is hosted, since the watch app only talks to the companion app — the ROM
|
||||
bundling was entirely a companion-app concern, now resolved.
|
||||
|
||||
## 1. .gitignore
|
||||
|
||||
Keystore files and credential properties must never be committed. Already
|
||||
added to `.gitignore`:
|
||||
|
||||
```
|
||||
*.jks
|
||||
*.keystore
|
||||
keystore.properties
|
||||
```
|
||||
|
||||
If you generate a keystore with a different name/extension, add that
|
||||
specific path too — don't rely on a broad glob you might forget to check.
|
||||
|
||||
## 2. Android companion app → Google Play
|
||||
|
||||
### 2.1 Generate an upload keystore
|
||||
|
||||
```bash
|
||||
keytool -genkeypair -v \
|
||||
-keystore SchwertUndMagieOnPebbleCompanionApp/release.keystore \
|
||||
-alias sum-release \
|
||||
-keyalg RSA -keysize 2048 -validity 10000
|
||||
```
|
||||
|
||||
`keytool` will prompt for a keystore password, a key password (can be the
|
||||
same), and your name/org details for the certificate (these become public
|
||||
metadata in the signed APK, not secret). Store the keystore file and both
|
||||
passwords in a password manager — **losing this keystore means you can never
|
||||
publish an update to the same Play Store listing again** under the same app;
|
||||
Google cannot recover or reset it for you.
|
||||
|
||||
### 2.2 Store credentials in a gitignored properties file
|
||||
|
||||
Create `SchwertUndMagieOnPebbleCompanionApp/keystore.properties` (already
|
||||
gitignored, never commit it):
|
||||
|
||||
```properties
|
||||
storeFile=release.keystore
|
||||
storePassword=<keystore password>
|
||||
keyAlias=sum-release
|
||||
keyPassword=<key password>
|
||||
```
|
||||
|
||||
### 2.3 Wire the signing config
|
||||
|
||||
Add to `SchwertUndMagieOnPebbleCompanionApp/app/build.gradle.kts`, near the
|
||||
top:
|
||||
|
||||
```kotlin
|
||||
import java.util.Properties
|
||||
|
||||
val keystoreProps = Properties().apply {
|
||||
rootProject.file("keystore.properties").takeIf { it.exists() }
|
||||
?.reader()?.use { load(it) }
|
||||
}
|
||||
```
|
||||
|
||||
Inside the `android { }` block:
|
||||
|
||||
```kotlin
|
||||
signingConfigs {
|
||||
create("release") {
|
||||
keystoreProps["storeFile"]?.let { storeFile = file(it as String) }
|
||||
storePassword = keystoreProps["storePassword"] as String?
|
||||
keyAlias = keystoreProps["keyAlias"] as String?
|
||||
keyPassword = keystoreProps["keyPassword"] as String?
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
// ...existing isMinifyEnabled / proguardFiles
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This degrades gracefully (null signing config values) on any machine without
|
||||
`keystore.properties` — CI or a contributor's checkout — rather than failing
|
||||
the whole Gradle configuration.
|
||||
|
||||
### 2.4 Build the release bundle
|
||||
|
||||
```bash
|
||||
cd SchwertUndMagieOnPebbleCompanionApp
|
||||
./gradlew bundleRelease # produces app/build/outputs/bundle/release/app-release.aab
|
||||
./gradlew assembleRelease # produces app/build/outputs/apk/release/app-release.apk, for sideload testing
|
||||
```
|
||||
|
||||
Play Store requires the **AAB** (`bundleRelease` output), not the APK — Play
|
||||
re-packages per-device APKs from it (including ABI splits, so `arm64-v8a`/
|
||||
`x86_64` native VICE libraries each ship only to matching devices).
|
||||
|
||||
### 2.5 Play Console setup (one-time, per app listing)
|
||||
|
||||
1. Enroll in the Google Play Developer program (one-time account fee).
|
||||
2. Create the app in Play Console, set its package name
|
||||
(`de.ladkau.schwertundmagieonpebblecompanionapp`) — this is permanent.
|
||||
3. **App content**: privacy policy URL, content rating questionnaire, data
|
||||
safety form (declare what data the app collects — this app's only network
|
||||
activity is the loopback HTTP server talking to Core for Pebble, so
|
||||
"no data collected/shared" likely applies, but fill out the form yourself).
|
||||
4. **Store listing**: title, short/full description, icon (512×512 PNG),
|
||||
feature graphic (1024×500), phone screenshots (min 2, current device's
|
||||
actual aspect ratio).
|
||||
5. Enroll in **Play App Signing** when prompted on first upload — Google
|
||||
re-signs your AAB with its own key for distribution; your upload keystore
|
||||
(§2.1) only needs to be kept for *future uploads to this listing*, not for
|
||||
the keys end users' devices actually trust.
|
||||
6. Upload the AAB to an **internal testing** track first, verify the install
|
||||
works on a real device, then promote to closed/open testing or production.
|
||||
|
||||
### 2.6 Versioning for future releases
|
||||
|
||||
Bump both fields in `app/build.gradle.kts` before every release build:
|
||||
|
||||
```kotlin
|
||||
versionCode = 2 // must strictly increase on every Play Store upload
|
||||
versionName = "1.1" // user-visible, free-form
|
||||
```
|
||||
|
||||
## 3. Pebble watch app → Rebble app store / direct distribution
|
||||
|
||||
The official Pebble app store shut down years ago; the community-run
|
||||
**Rebble** store is the closest equivalent today, alongside the 2025 Core
|
||||
Devices relaunch of Pebble hardware. Check Rebble's current developer portal
|
||||
directly for their exact submission flow and requirements — that's outside
|
||||
this repo and changes independently of it.
|
||||
|
||||
### 3.1 Build the artifact
|
||||
|
||||
```bash
|
||||
cd SchwertUndMagieOnPebbleWatchApp
|
||||
pebble build # produces build/SchwertUndMagieOnPebbleWatchApp.pbw
|
||||
```
|
||||
|
||||
The `.pbw` is the complete distributable — it bundles all target platforms
|
||||
(`aplite`/`basalt`/`chalk`/`diorite`/`emery`/`flint`/`gabbro`) declared in
|
||||
`package.json`. There is no signing step analogous to Android; Pebble apps
|
||||
are not cryptographically signed by the developer.
|
||||
|
||||
### 3.2 Direct distribution (no store)
|
||||
|
||||
Anyone with the `.pbw` file and Core for Pebble installed can sideload it —
|
||||
this is the lowest-friction path and doesn't depend on any third party's
|
||||
store being operational. This watch app is tightly coupled to the companion
|
||||
app's HTTP bridge (see `docs/architecture.md` §1), so it's not really
|
||||
meaningful as a standalone listing anyway — distribute both together.
|
||||
|
||||
### 3.3 Store submission (if Rebble's process applies)
|
||||
|
||||
Expect to need: an app icon resource (not yet configured in `package.json` —
|
||||
there's no `icon`/menu-icon entry currently, only the in-app `IMAGE_SPLASH`
|
||||
bitmap), a short description, and screenshots per platform. Generate
|
||||
screenshots the same way used during development:
|
||||
|
||||
```bash
|
||||
pebble install --emulator emery --vnc
|
||||
pebble screenshot --vnc --no-open docs/screenshots/emery.png
|
||||
```
|
||||
|
||||
(repeat per target platform you want a store screenshot for).
|
||||
|
||||
### 3.4 Versioning
|
||||
|
||||
Bump `version` in `SchwertUndMagieOnPebbleWatchApp/package.json` before each
|
||||
release build.
|
||||
|
||||
## 4. Pre-publish checklist
|
||||
|
||||
- [x] Resolved ROM bundling (§0) — companion app no longer ships/auto-installs
|
||||
copyrighted Commodore ROMs; it prompts the user to import their own dump
|
||||
- [ ] Release keystore generated, passwords saved in a password manager, both
|
||||
gitignored (§1, §2.1)
|
||||
- [ ] `keystore.properties` exists locally and is **not** tracked by git
|
||||
- [ ] `./gradlew bundleRelease` succeeds and installs/runs on a real device
|
||||
from the resulting AAB (test via `bundletool` or Play internal testing)
|
||||
- [ ] Play Console store listing content complete (icon, screenshots,
|
||||
privacy policy, content rating, data safety form)
|
||||
- [ ] `pebble build` succeeds for all target platforms; `.pbw` sideloads and
|
||||
runs correctly against the signed companion app build
|
||||
- [ ] `versionCode`/`versionName` (Android) and `version` (Pebble) bumped
|
||||
Reference in New Issue
Block a user