Compare commits
4 Commits
v0.5.2
...
790b4ffd21
| Author | SHA1 | Date | |
|---|---|---|---|
| 790b4ffd21 | |||
| 135e474a14 | |||
| 295f4869c1 | |||
| f0a83df667 |
@@ -69,7 +69,25 @@ jobs:
|
|||||||
dist/shockolate-*-linux-*.tar.gz
|
dist/shockolate-*-linux-*.tar.gz
|
||||||
dist/shockolate-*-windows-*.zip
|
dist/shockolate-*-windows-*.zip
|
||||||
|
|
||||||
|
# The release key is stored base64-encoded in ANDROID_KEYSTORE_BASE64
|
||||||
|
# (see README section 4.3). Without it the APK is a debug build.
|
||||||
|
- name: Prepare release signing
|
||||||
|
env:
|
||||||
|
KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if [ -z "$KEYSTORE_BASE64" ]; then
|
||||||
|
echo "No signing key: the APK will be a debug build"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "$KEYSTORE_BASE64" | base64 -d > "${RUNNER_TEMP:-/tmp}/questshock-release.jks"
|
||||||
|
echo "QUESTSHOCK_KEYSTORE=${RUNNER_TEMP:-/tmp}/questshock-release.jks" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Build Quest APK
|
- name: Build Quest APK
|
||||||
|
env:
|
||||||
|
QUESTSHOCK_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||||
|
QUESTSHOCK_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||||
|
QUESTSHOCK_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||||
# QUESTSHOCK_BUILD_IMAGE is already set (see Preflight above), so
|
# QUESTSHOCK_BUILD_IMAGE is already set (see Preflight above), so
|
||||||
# this runs build-apk.sh directly instead of trying to docker-run
|
# this runs build-apk.sh directly instead of trying to docker-run
|
||||||
# the image again - same reasoning as `make package` above.
|
# the image again - same reasoning as `make package` above.
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
name: release
|
||||||
|
|
||||||
|
# GitHub's build. The Gitea build is .gitea/workflows/build.yml.
|
||||||
|
#
|
||||||
|
# Pushing a tag vX.Y.Z builds the Linux and Windows packages and the Quest APK
|
||||||
|
# inside the build-image and attaches them to a GitHub release. A manual run
|
||||||
|
# builds the same files and keeps them as a workflow artifact without
|
||||||
|
# releasing them.
|
||||||
|
#
|
||||||
|
# The build-image (build-image/Dockerfile) is published to GHCR, tagged with a
|
||||||
|
# hash of the files that define it, and only built when that tag does not
|
||||||
|
# exist yet.
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
image:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
outputs:
|
||||||
|
name: ${{ steps.name.outputs.name }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Name the image after the files that define it
|
||||||
|
id: name
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
hash="$(cat build-image/Dockerfile build-image/docker-entrypoint.sh build-image/build-engine.sh | sha256sum | cut -c1-16)"
|
||||||
|
echo "name=ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/questshock-builder:${hash}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Build and push the image if it does not exist yet
|
||||||
|
env:
|
||||||
|
IMAGE: ${{ steps.name.outputs.name }}
|
||||||
|
REGISTRY_USER: ${{ github.actor }}
|
||||||
|
REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
echo "$REGISTRY_PASSWORD" | docker login ghcr.io -u "$REGISTRY_USER" --password-stdin
|
||||||
|
if docker manifest inspect "$IMAGE" >/dev/null 2>&1; then
|
||||||
|
echo "$IMAGE already exists"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
# The image is large and the runner's disk is small.
|
||||||
|
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL
|
||||||
|
# The label links the package to this repository, so the build job
|
||||||
|
# can pull it with the workflow's token.
|
||||||
|
docker build -f build-image/Dockerfile \
|
||||||
|
--label "org.opencontainers.image.source=https://github.com/${GITHUB_REPOSITORY}" \
|
||||||
|
-t "$IMAGE" .
|
||||||
|
docker push "$IMAGE"
|
||||||
|
|
||||||
|
build:
|
||||||
|
needs: image
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: read
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
# The job runs inside the build-image, so `make` compiles directly instead
|
||||||
|
# of starting the image itself (QUESTSHOCK_BUILD_IMAGE is set in it).
|
||||||
|
container:
|
||||||
|
image: ${{ needs.image.outputs.name }}
|
||||||
|
credentials:
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
# The version comes from `git describe --tags`.
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Build the Linux and Windows packages
|
||||||
|
run: make package
|
||||||
|
|
||||||
|
# The release key is stored base64-encoded in ANDROID_KEYSTORE_BASE64; see
|
||||||
|
# README section 4.3. A tag build must be signed with it.
|
||||||
|
- name: Prepare release signing
|
||||||
|
env:
|
||||||
|
KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if [ -z "$KEYSTORE_BASE64" ]; then
|
||||||
|
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
|
||||||
|
echo "::error::ANDROID_KEYSTORE_BASE64 is not set: a release APK must be signed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "No signing key: the APK will be a debug build"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "$KEYSTORE_BASE64" | base64 -d > "${RUNNER_TEMP:-/tmp}/questshock-release.jks"
|
||||||
|
echo "QUESTSHOCK_KEYSTORE=${RUNNER_TEMP:-/tmp}/questshock-release.jks" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
# Needs network access for Gradle's own dependency resolution.
|
||||||
|
- name: Build the Quest APK
|
||||||
|
env:
|
||||||
|
QUESTSHOCK_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||||
|
QUESTSHOCK_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||||
|
QUESTSHOCK_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||||
|
run: make apk
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: questshock
|
||||||
|
path: |
|
||||||
|
dist/shockolate-*-linux-*.tar.gz
|
||||||
|
dist/shockolate-*-windows-*.zip
|
||||||
|
dist/questshock-*-android-*.apk
|
||||||
|
|
||||||
|
release:
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
GH_REPO: ${{ github.repository }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: questshock
|
||||||
|
path: dist
|
||||||
|
|
||||||
|
- name: Create the release, or update its files if it exists
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
|
||||||
|
gh release upload "$GITHUB_REF_NAME" dist/* --clobber
|
||||||
|
else
|
||||||
|
gh release create "$GITHUB_REF_NAME" dist/* \
|
||||||
|
--title "$GITHUB_REF_NAME" \
|
||||||
|
--generate-notes \
|
||||||
|
--notes "Sideload the APK onto a Meta Quest (see README section 5.1). The game data is not included: you need your own copy of System Shock: Enhanced Edition."
|
||||||
|
fi
|
||||||
@@ -38,3 +38,7 @@
|
|||||||
|
|
||||||
# Local registry credentials (see registry.env.example)
|
# Local registry credentials (see registry.env.example)
|
||||||
/registry.env
|
/registry.env
|
||||||
|
|
||||||
|
# Signing keys
|
||||||
|
*.jks
|
||||||
|
*.keystore
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ These come from the maintainer. Follow them without being asked again.
|
|||||||
`make apk` and `make android-studio` overwrite `android/engine.properties`
|
`make apk` and `make android-studio` overwrite `android/engine.properties`
|
||||||
with container-only paths and break an open Android Studio project. What you
|
with container-only paths and break an open Android Studio project. What you
|
||||||
can do is dry-run patches (section 6) and run static consistency checks
|
can do is dry-run patches (section 6) and run static consistency checks
|
||||||
(section 12). Say plainly in your report that the change was not built or
|
(section 13). Say plainly in your report that the change was not built or
|
||||||
tested.
|
tested.
|
||||||
3. **`engine/` and `android/gl4es-src/` are never edited.** They are vendored
|
3. **`engine/` and `android/gl4es-src/` are never edited.** They are vendored
|
||||||
snapshots. Every change to them is a numbered patch under
|
snapshots. Every change to them is a numbered patch under
|
||||||
@@ -74,7 +74,8 @@ These come from the maintainer. Follow them without being asked again.
|
|||||||
| `android/app/build.gradle` | Gradle project; the `stageEngine` task stages the patched engine. |
|
| `android/app/build.gradle` | Gradle project; the `stageEngine` task stages the patched engine. |
|
||||||
| `android/force-rebuild.sh` | Stops Gradle daemons and wipes staged/build output for a fully fresh native build. |
|
| `android/force-rebuild.sh` | Stops Gradle daemons and wipes staged/build output for a fully fresh native build. |
|
||||||
| `build-image/` | Docker build environment (`Dockerfile`, `VERSION`, `prepare-android-project.sh`, `build-apk.sh`, engine build scripts). |
|
| `build-image/` | Docker build environment (`Dockerfile`, `VERSION`, `prepare-android-project.sh`, `build-apk.sh`, engine build scripts). |
|
||||||
| `res/` | Bundled resources: `questshock.config.default.json`, `controller_profiles/` (JSON + PNG per controller), `assets/` (where the user's own game data goes, git-ignored), `run.sh`/`run.bat`. |
|
| `res/` | Bundled resources: `questshock.config.default.json`, `controller_profiles/` (JSON + PNG per controller), `splash.png` (startup splash), `logo.png` (1024x1024 logo, master of the launcher icons in `android/app/src/main/res/mipmap-*/`), `logo_small.png` (the README header), `screenshots/` (headset captures and videos named `questshock_v<version>_<nnn>.<ext>`, numbered per version), `assets/` (where the user's own game data goes, git-ignored), `run.sh`/`run.bat`. |
|
||||||
|
| `.gitea/workflows/build.yml`, `.github/workflows/release.yml` | CI. Gitea is the primary repository and builds on every push; GitHub is a mirror whose workflow builds and publishes a release when a `vX.Y.Z` tag is pushed. Each platform has its own file. |
|
||||||
| `Makefile` | `engine`, `dist`, `package`, `apk`, `android-studio`, `clean`, ... (see README section 2). |
|
| `Makefile` | `engine`, `dist`, `package`, `apk`, `android-studio`, `clean`, ... (see README section 2). |
|
||||||
| `README.md`, `CONFIG.md`, `NOTICE.txt` | User docs, config reference, license notice. |
|
| `README.md`, `CONFIG.md`, `NOTICE.txt` | User docs, config reference, license notice. |
|
||||||
|
|
||||||
@@ -140,7 +141,7 @@ not print it or commit it.
|
|||||||
`qs_config_frame_tick()`) is called from `xr_input.c`.
|
`qs_config_frame_tick()`) is called from `xr_input.c`.
|
||||||
- JNI symbol names must match the Java `native` declarations exactly
|
- JNI symbol names must match the Java `native` declarations exactly
|
||||||
(`Java_de_ladkau_questshock_<Class>_<method>`). A mismatch only fails at
|
(`Java_de_ladkau_questshock_<Class>_<method>`). A mismatch only fails at
|
||||||
runtime, so check it (section 12).
|
runtime, so check it (section 13).
|
||||||
|
|
||||||
### 4.5 Off-screen views (`OverlayPanel`, `MenuOverlay`, `KeyboardOverlay`)
|
### 4.5 Off-screen views (`OverlayPanel`, `MenuOverlay`, `KeyboardOverlay`)
|
||||||
|
|
||||||
@@ -336,7 +337,19 @@ or directory must also be declared as `inputs.file(...)`/`inputs.dir(...)` of
|
|||||||
The proprietary game data (`res/data/`, `res/sound/`) is never bundled or
|
The proprietary game data (`res/data/`, `res/sound/`) is never bundled or
|
||||||
committed. The app refuses to start the engine until it is present.
|
committed. The app refuses to start the engine until it is present.
|
||||||
|
|
||||||
## 11. Code and documentation style
|
## 11. Signing
|
||||||
|
|
||||||
|
Distributed APKs are release builds signed with one fixed key, so users can
|
||||||
|
update in place. `build-image/build-apk.sh` reads the key from
|
||||||
|
`QUESTSHOCK_KEYSTORE`, `QUESTSHOCK_KEYSTORE_PASSWORD`, `QUESTSHOCK_KEY_ALIAS`
|
||||||
|
and `QUESTSHOCK_KEY_PASSWORD`; `android/app/build.gradle` applies it to the
|
||||||
|
`release` build type only when `QUESTSHOCK_KEYSTORE` is set. Both CI workflows
|
||||||
|
decode it from the `ANDROID_KEYSTORE_BASE64` secret. Without a key the build is
|
||||||
|
a debug APK (`...-debug.apk`), which is only for testing. Never commit a
|
||||||
|
keystore or a password, print them, or put them in a workflow file (README
|
||||||
|
section 4.3 lists the secrets).
|
||||||
|
|
||||||
|
## 12. Code and documentation style
|
||||||
|
|
||||||
- Match the surrounding code: naming, comment density, idiom. C is plain C with
|
- Match the surrounding code: naming, comment density, idiom. C is plain C with
|
||||||
`static` helpers and `//` comments; Java targets Android API 34 and uses only the SDK plus
|
`static` helpers and `//` comments; Java targets Android API 34 and uses only the SDK plus
|
||||||
@@ -352,7 +365,7 @@ committed. The app refuses to start the engine until it is present.
|
|||||||
- English, French and German strings live in `Strings.java`; keep the three
|
- English, French and German strings live in `Strings.java`; keep the three
|
||||||
entries of each key on the same `put(...)`.
|
entries of each key on the same `put(...)`.
|
||||||
|
|
||||||
## 12. Verifying a change without building
|
## 13. Verifying a change without building
|
||||||
|
|
||||||
Because you may not build (rule 2), check statically and say so in the report:
|
Because you may not build (rule 2), check statically and say so in the report:
|
||||||
|
|
||||||
@@ -374,7 +387,7 @@ Then report what was checked, what was not (build, on-device behaviour), and
|
|||||||
any device check the maintainer should do (for example a new patch that may
|
any device check the maintainer should do (for example a new patch that may
|
||||||
need `android/force-rebuild.sh`).
|
need `android/force-rebuild.sh`).
|
||||||
|
|
||||||
## 13. Commit messages
|
## 14. Commit messages
|
||||||
|
|
||||||
Draft only (rule 1). Use a short imperative subject (about 70 characters), a
|
Draft only (rule 1). Use a short imperative subject (about 70 characters), a
|
||||||
blank line, then a body that explains what the change does and why: the
|
blank line, then a body that explains what the change does and why: the
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ and is not relicensed by this project's MIT license.
|
|||||||
|
|
||||||
Game assets are not included - see res/GET_ASSETS.txt.
|
Game assets are not included - see res/GET_ASSETS.txt.
|
||||||
|
|
||||||
|
System Shock is a trademark of its respective owners. This is an unofficial
|
||||||
|
fan project and is not affiliated with, endorsed by or supported by them.
|
||||||
|
|
||||||
The Android build additionally bundles:
|
The Android build additionally bundles:
|
||||||
|
|
||||||
- GL4ES (https://github.com/ptitSeb/gl4es), vendored and patched (see
|
- GL4ES (https://github.com/ptitSeb/gl4es), vendored and patched (see
|
||||||
|
|||||||
@@ -17,6 +17,45 @@ button, stick direction and game hotkey can be assigned to a game action.
|
|||||||
Questshock runs entirely on the headset (tested on a Meta Quest 2) as a
|
Questshock runs entirely on the headset (tested on a Meta Quest 2) as a
|
||||||
sideloaded Android app, and needs your own copy of the game data.
|
sideloaded Android app, and needs your own copy of the game data.
|
||||||
|
|
||||||
|
> **Unofficial fan project.** Questshock is not affiliated with, endorsed by or
|
||||||
|
> supported by the rights holders of System Shock. It contains no game data: you
|
||||||
|
> need your own copy of System Shock: Enhanced Edition.
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td width="50%"><img src="res/screenshots/questshock_v0.5.3_001.jpg" alt="Exploring the station on the virtual screen"><br><sub>Exploring the station on the virtual screen</sub></td>
|
||||||
|
<td width="50%"><img src="res/screenshots/questshock_v0.5.3_003.jpg" alt="Reading an email - the laser pointer clicks like a mouse"><br><sub>Reading an email - the laser pointer clicks like a mouse</sub></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="50%"><img src="res/screenshots/questshock_v0.5.3_005.jpg" alt="Cyberspace"><br><sub>Cyberspace</sub></td>
|
||||||
|
<td width="50%"><img src="res/screenshots/questshock_v0.5.3_006.jpg" alt="The game screen, the menu and the keyboard float independently"><br><sub>The game screen, the menu and the keyboard float independently</sub></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="50%"><img src="res/screenshots/questshock_v0.5.3_007.jpg" alt="The VR menu"><br><sub>The VR menu</sub></td>
|
||||||
|
<td width="50%"><img src="res/screenshots/questshock_v0.5.3_008.jpg" alt="Assigning controller inputs to game actions"><br><sub>Assigning controller inputs to game actions</sub></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
[A short video](res/screenshots/questshock_v0.5.3_010.mp4) shows the game in use.
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
You need a Meta Quest (tested on a Quest 2) and your own copy of the game data.
|
||||||
|
|
||||||
|
1. Get **System Shock: Enhanced Edition** (for example from
|
||||||
|
[gog.com](https://www.gog.com/)); see "3. Game assets" below.
|
||||||
|
2. Download `questshock-<version>-android-arm64.apk` from the repository's
|
||||||
|
Releases page and sideload it onto the headset with
|
||||||
|
[SideQuest](https://sidequestvr.com/) or `adb install`.
|
||||||
|
3. Launch it once. It creates `/sdcard/QuestShock/`. Copy your game's `res/data/`
|
||||||
|
and `res/sound/` folders into `/sdcard/QuestShock/res/`.
|
||||||
|
4. Launch it again. The left controller's menu button opens the menu; the
|
||||||
|
default controls are in "5.1.1. Menu, controls and cheats" below.
|
||||||
|
|
||||||
|
Section "5.1. Installing and playing" has the details.
|
||||||
|
|
||||||
## 1. Design principles
|
## 1. Design principles
|
||||||
|
|
||||||
- **Standalone-headset-only.** The game must run entirely on the headset
|
- **Standalone-headset-only.** The game must run entirely on the headset
|
||||||
@@ -46,7 +85,11 @@ sideloaded Android app, and needs your own copy of the game data.
|
|||||||
(see [CONFIG.md](CONFIG.md)); `res/controller_profiles/` - the per-controller
|
(see [CONFIG.md](CONFIG.md)); `res/controller_profiles/` - the per-controller
|
||||||
illustrations and labels shown in the Controller Options tab (see
|
illustrations and labels shown in the Controller Options tab (see
|
||||||
[CONFIG.md](CONFIG.md)). Both are copied into the APK, as is
|
[CONFIG.md](CONFIG.md)). Both are copied into the APK, as is
|
||||||
`res/splash.png`, the startup splash image.
|
`res/splash.png`, the startup splash image. `res/logo.png` is the 1024x1024
|
||||||
|
logo, the master of the launcher icon
|
||||||
|
(`android/app/src/main/res/mipmap-*/ic_launcher.png` are scaled copies of it)
|
||||||
|
and the image to use for store listings; `res/logo_small.png` is the copy
|
||||||
|
shown at the top of this file.
|
||||||
- `Makefile` - targets:
|
- `Makefile` - targets:
|
||||||
- `all` (default) - alias for `dist`.
|
- `all` (default) - alias for `dist`.
|
||||||
- `build-image` - builds the Docker build-image (`./build-image.sh`).
|
- `build-image` - builds the Docker build-image (`./build-image.sh`).
|
||||||
@@ -65,9 +108,11 @@ sideloaded Android app, and needs your own copy of the game data.
|
|||||||
proprietary game assets (ships `res/GET_ASSETS.txt` in their place).
|
proprietary game assets (ships `res/GET_ASSETS.txt` in their place).
|
||||||
See "4.1. Packaging a distributable build" below.
|
See "4.1. Packaging a distributable build" below.
|
||||||
- `apk` - builds `dist/questshock-<version>-android-arm64.apk` for
|
- `apk` - builds `dist/questshock-<version>-android-arm64.apk` for
|
||||||
Android-based OpenXR headsets. Unlike every other target, this needs
|
Android-based OpenXR headsets (a debug build, `...-arm64-debug.apk`,
|
||||||
network access at build time (Gradle/AGP's own dependency
|
when there is no signing key - see "4.3. Releases and signing" below).
|
||||||
resolution). See "5. Android / Quest build" below.
|
Unlike every other target, this needs network access at build time
|
||||||
|
(Gradle/AGP's own dependency resolution). See "5. Android / Quest
|
||||||
|
build" below.
|
||||||
- `android-studio` - stages `engine/`/`android/gl4es-src/` (scratch,
|
- `android-studio` - stages `engine/`/`android/gl4es-src/` (scratch,
|
||||||
patched copies) and the Android prebuilts with host-resolvable paths,
|
patched copies) and the Android prebuilts with host-resolvable paths,
|
||||||
so Android Studio can compile and deploy `android/` natively instead
|
so Android Studio can compile and deploy `android/` natively instead
|
||||||
@@ -211,6 +256,49 @@ package on every push, using the build-image as its container (so no
|
|||||||
extra setup is needed in CI beyond the image itself), and publishes the
|
extra setup is needed in CI beyond the image itself), and publishes the
|
||||||
resulting tarball to dl.ladkau.de.
|
resulting tarball to dl.ladkau.de.
|
||||||
|
|
||||||
|
### 4.3. Releases and signing
|
||||||
|
|
||||||
|
The repository is developed on Gitea, and the GitHub repository is a mirror of
|
||||||
|
it. `.github/workflows/release.yml` is GitHub's own workflow (the Gitea build
|
||||||
|
is `.gitea/workflows/build.yml`):
|
||||||
|
|
||||||
|
- Pushing a tag `vX.Y.Z` builds the Linux and Windows packages and the Quest
|
||||||
|
APK inside the build-image and attaches them to a GitHub release with the
|
||||||
|
same name. A manual run ("Run workflow") builds the same files without
|
||||||
|
releasing them and keeps them as a downloadable workflow artifact.
|
||||||
|
- The build-image is pushed to GitHub's container registry (`ghcr.io`) when it
|
||||||
|
does not exist there yet. Its tag is a hash of `build-image/Dockerfile`,
|
||||||
|
`docker-entrypoint.sh` and `build-engine.sh`, so a change to any of them
|
||||||
|
builds a new image (`build-image/VERSION` is not used). The first run is slow
|
||||||
|
because it builds the image.
|
||||||
|
- The APK is signed with the release key described below.
|
||||||
|
|
||||||
|
**Signing.** Android only updates an installed app from an APK signed with the
|
||||||
|
same key, so every distributed APK is signed with one fixed key. `make apk`
|
||||||
|
(`build-image/build-apk.sh`) builds and signs the release APK when
|
||||||
|
`QUESTSHOCK_KEYSTORE` is set, together with `QUESTSHOCK_KEYSTORE_PASSWORD` and
|
||||||
|
optionally `QUESTSHOCK_KEY_ALIAS` (default `questshock`) and
|
||||||
|
`QUESTSHOCK_KEY_PASSWORD` (default: the keystore password). Without a keystore
|
||||||
|
it builds a debug APK, `questshock-<version>-android-arm64-debug.apk`, signed
|
||||||
|
with a per-machine debug key. That is what Android Studio's Run uses too; an
|
||||||
|
installed build has to be uninstalled before one signed with another key can be
|
||||||
|
installed.
|
||||||
|
|
||||||
|
Create the key once and keep the file and its password safe: without them,
|
||||||
|
users have to uninstall the app to get an update.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
keytool -genkeypair -v -storetype PKCS12 -keystore questshock-release.jks \
|
||||||
|
-alias questshock -keyalg RSA -keysize 4096 -validity 10000
|
||||||
|
base64 -w0 questshock-release.jks # the value of ANDROID_KEYSTORE_BASE64
|
||||||
|
```
|
||||||
|
|
||||||
|
Both CI systems read the key from these secrets: `ANDROID_KEYSTORE_BASE64`,
|
||||||
|
`ANDROID_KEYSTORE_PASSWORD`, and optionally `ANDROID_KEY_ALIAS` and
|
||||||
|
`ANDROID_KEY_PASSWORD`. Without `ANDROID_KEYSTORE_BASE64`, the Gitea build
|
||||||
|
produces a debug APK and the GitHub build fails on a tag. `*.jks` and
|
||||||
|
`*.keystore` are git-ignored.
|
||||||
|
|
||||||
## 5. Android / Quest build
|
## 5. Android / Quest build
|
||||||
|
|
||||||
**Prerequisites:**
|
**Prerequisites:**
|
||||||
@@ -225,7 +313,8 @@ resulting tarball to dl.ladkau.de.
|
|||||||
apk` needs its own separate toolchain - see "5.2. Building natively in
|
apk` needs its own separate toolchain - see "5.2. Building natively in
|
||||||
Android Studio" below.
|
Android Studio" below.
|
||||||
|
|
||||||
`make apk` builds `dist/questshock-<version>-android-arm64.apk` - an
|
`make apk` builds `dist/questshock-<version>-android-arm64.apk` (or
|
||||||
|
`...-arm64-debug.apk`, see "4.3. Releases and signing" above) - an
|
||||||
immersive OpenXR app (see `android/app/src/main/cpp/xr_session.c`) that
|
immersive OpenXR app (see `android/app/src/main/cpp/xr_session.c`) that
|
||||||
can be sideloaded onto any Android-based VR headset with OpenXR support
|
can be sideloaded onto any Android-based VR headset with OpenXR support
|
||||||
(Meta Quest, Pico, etc. - see "1. Design principles" above), not just one
|
(Meta Quest, Pico, etc. - see "1. Design principles" above), not just one
|
||||||
@@ -965,6 +1054,10 @@ under the **GNU GPLv3** (see `engine/LICENSE`) - it is included unchanged
|
|||||||
and is *not* relicensed by this project's MIT license. Any build or
|
and is *not* relicensed by this project's MIT license. Any build or
|
||||||
distribution of the compiled engine must comply with the GPLv3.
|
distribution of the compiled engine must comply with the GPLv3.
|
||||||
|
|
||||||
|
System Shock is a trademark of its respective owners. Questshock is an
|
||||||
|
unofficial fan project and is not affiliated with, endorsed by or supported by
|
||||||
|
them.
|
||||||
|
|
||||||
The game assets extracted into `res/assets/ss_ee/` are proprietary,
|
The game assets extracted into `res/assets/ss_ee/` are proprietary,
|
||||||
copyrighted game data owned by their respective rightsholders - they are
|
copyrighted game data owned by their respective rightsholders - they are
|
||||||
never committed to this repository (see `.gitignore`) and must be
|
never committed to this repository (see `.gitignore`) and must be
|
||||||
|
|||||||
@@ -212,9 +212,28 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The release key comes from the environment (build-image/build-apk.sh,
|
||||||
|
// CI secrets). Without it there is no release signing, and the release
|
||||||
|
// build type is left unsigned.
|
||||||
|
def releaseKeystore = System.getenv("QUESTSHOCK_KEYSTORE")
|
||||||
|
def releaseStorePassword = System.getenv("QUESTSHOCK_KEYSTORE_PASSWORD")
|
||||||
|
signingConfigs {
|
||||||
|
release {
|
||||||
|
if (releaseKeystore) {
|
||||||
|
storeFile file(releaseKeystore)
|
||||||
|
storePassword releaseStorePassword
|
||||||
|
keyAlias System.getenv("QUESTSHOCK_KEY_ALIAS") ?: "questshock"
|
||||||
|
keyPassword System.getenv("QUESTSHOCK_KEY_PASSWORD") ?: releaseStorePassword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
|
if (releaseKeystore) {
|
||||||
|
signingConfig signingConfigs.release
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 16 KiB |
@@ -24,16 +24,33 @@ VERSION="$("$REPO_ROOT/build-image/version.sh")"
|
|||||||
VERSION_CODE="$(git -C "$REPO_ROOT" rev-list --count HEAD)"
|
VERSION_CODE="$(git -C "$REPO_ROOT" rev-list --count HEAD)"
|
||||||
echo "Version: $VERSION (versionCode $VERSION_CODE)"
|
echo "Version: $VERSION (versionCode $VERSION_CODE)"
|
||||||
|
|
||||||
echo "== Building the APK (gradlew assembleDebug) =="
|
# A release APK is built and signed when QUESTSHOCK_KEYSTORE is set (with
|
||||||
|
# QUESTSHOCK_KEYSTORE_PASSWORD, and optionally QUESTSHOCK_KEY_ALIAS,
|
||||||
|
# default "questshock", and QUESTSHOCK_KEY_PASSWORD, default the keystore
|
||||||
|
# password). Without it the APK is a debug build, signed with a per-machine
|
||||||
|
# debug key.
|
||||||
|
if [ -n "${QUESTSHOCK_KEYSTORE:-}" ]; then
|
||||||
|
[ -f "$QUESTSHOCK_KEYSTORE" ] || { echo "BUILD FAIL: QUESTSHOCK_KEYSTORE ($QUESTSHOCK_KEYSTORE) does not exist" >&2; exit 1; }
|
||||||
|
[ -n "${QUESTSHOCK_KEYSTORE_PASSWORD:-}" ] || { echo "BUILD FAIL: QUESTSHOCK_KEYSTORE_PASSWORD is not set" >&2; exit 1; }
|
||||||
|
GRADLE_TASK=assembleRelease
|
||||||
|
APK="$ANDROID_DIR/app/build/outputs/apk/release/app-release.apk"
|
||||||
|
PKG_SUFFIX=""
|
||||||
|
else
|
||||||
|
echo "== QUESTSHOCK_KEYSTORE is not set: building a debug APK, not for distribution =="
|
||||||
|
GRADLE_TASK=assembleDebug
|
||||||
|
APK="$ANDROID_DIR/app/build/outputs/apk/debug/app-debug.apk"
|
||||||
|
PKG_SUFFIX="-debug"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "== Building the APK (gradlew $GRADLE_TASK) =="
|
||||||
cd "$ANDROID_DIR"
|
cd "$ANDROID_DIR"
|
||||||
./gradlew --no-daemon assembleDebug \
|
./gradlew --no-daemon "$GRADLE_TASK" \
|
||||||
"-PquestshockVersionName=$VERSION" "-PquestshockVersionCode=$VERSION_CODE"
|
"-PquestshockVersionName=$VERSION" "-PquestshockVersionCode=$VERSION_CODE"
|
||||||
|
|
||||||
APK="$ANDROID_DIR/app/build/outputs/apk/debug/app-debug.apk"
|
[ -f "$APK" ] || { echo "BUILD FAIL: $APK not found after $GRADLE_TASK" >&2; exit 1; }
|
||||||
[ -f "$APK" ] || { echo "BUILD FAIL: $APK not found after assembleDebug" >&2; exit 1; }
|
|
||||||
|
|
||||||
mkdir -p "$REPO_ROOT/dist"
|
mkdir -p "$REPO_ROOT/dist"
|
||||||
PKG_NAME="questshock-$VERSION-android-arm64.apk"
|
PKG_NAME="questshock-$VERSION-android-arm64$PKG_SUFFIX.apk"
|
||||||
cp "$APK" "$REPO_ROOT/dist/$PKG_NAME"
|
cp "$APK" "$REPO_ROOT/dist/$PKG_NAME"
|
||||||
|
|
||||||
echo "== Done =="
|
echo "== Done =="
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 245 KiB |
|
Before Width: | Height: | Size: 628 KiB After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 493 KiB After Width: | Height: | Size: 493 KiB |
|
Before Width: | Height: | Size: 434 KiB After Width: | Height: | Size: 434 KiB |
|
Before Width: | Height: | Size: 343 KiB After Width: | Height: | Size: 343 KiB |
|
Before Width: | Height: | Size: 272 KiB After Width: | Height: | Size: 272 KiB |
|
Before Width: | Height: | Size: 309 KiB After Width: | Height: | Size: 309 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 265 KiB After Width: | Height: | Size: 265 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 310 KiB After Width: | Height: | Size: 310 KiB |
|
Before Width: | Height: | Size: 297 KiB After Width: | Height: | Size: 297 KiB |
|
Before Width: | Height: | Size: 184 KiB After Width: | Height: | Size: 184 KiB |
|
Before Width: | Height: | Size: 402 KiB After Width: | Height: | Size: 402 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 157 KiB |
|
Before Width: | Height: | Size: 641 KiB After Width: | Height: | Size: 641 KiB |
|
Before Width: | Height: | Size: 575 KiB After Width: | Height: | Size: 575 KiB |
|
Before Width: | Height: | Size: 707 KiB After Width: | Height: | Size: 707 KiB |
|
Before Width: | Height: | Size: 382 KiB After Width: | Height: | Size: 382 KiB |
|
Before Width: | Height: | Size: 413 KiB After Width: | Height: | Size: 413 KiB |
|
Before Width: | Height: | Size: 368 KiB After Width: | Height: | Size: 368 KiB |
|
Before Width: | Height: | Size: 435 KiB After Width: | Height: | Size: 435 KiB |
|
Before Width: | Height: | Size: 276 KiB After Width: | Height: | Size: 276 KiB |
|
Before Width: | Height: | Size: 209 KiB After Width: | Height: | Size: 209 KiB |
|
Before Width: | Height: | Size: 283 KiB After Width: | Height: | Size: 283 KiB |
|
After Width: | Height: | Size: 517 KiB |
|
After Width: | Height: | Size: 445 KiB |
|
After Width: | Height: | Size: 484 KiB |
|
After Width: | Height: | Size: 422 KiB |
|
After Width: | Height: | Size: 300 KiB |
|
After Width: | Height: | Size: 361 KiB |
|
After Width: | Height: | Size: 423 KiB |
|
After Width: | Height: | Size: 401 KiB |
|
After Width: | Height: | Size: 389 KiB |