# 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= keyAlias=sum-release keyPassword= ``` ### 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