Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9987546d5 | ||
|
|
6a39887e8f | ||
|
|
349e314850 | ||
|
|
b1112d8cdc | ||
|
|
27c383a5d9 | ||
|
|
7cf24feef6 | ||
|
|
4443766d39 | ||
|
|
ea2166b09a | ||
|
|
9e4e69be1c | ||
|
|
dd0655bfde | ||
|
|
d2d275706b | ||
|
|
b7b63dd30f | ||
|
|
aacab4036c | ||
|
|
e104b1d936 | ||
|
|
bf6a6b417d | ||
|
|
dfd35b8cc8 | ||
|
|
1632681e6c | ||
|
|
88d10d53f2 | ||
|
|
eae9fa7030 | ||
|
|
ad76f68224 | ||
|
|
d783793b4e |
No files matched your search
@@ -1,5 +1,6 @@
|
||||
# Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
name: Bug report
|
||||
description: Report a reproducible problem in Mocktail
|
||||
description: Report a reproducible problem in Nightcap
|
||||
title: "[Bug]: "
|
||||
labels:
|
||||
- bug
|
||||
@@ -75,7 +76,7 @@ body:
|
||||
value: |
|
||||
Latest log:
|
||||
|
||||
- Flatpak: `~/.var/app/space.bigrat.mocktail/.local/state/mocktail/logs/latest.log`
|
||||
- Flatpak: `~/.var/app/io.github.CoderDayton.nightcap/.local/state/mocktail/logs/latest.log`
|
||||
- Other installs: `~/.local/state/mocktail/logs/latest.log`
|
||||
|
||||
Older logs are in the `sessions` directory next to `latest.log`.
|
||||
|
||||
@@ -64,4 +64,3 @@ jobs:
|
||||
bash tests/managed_payload_build_contract_test.sh .
|
||||
bash tests/flatpak_packaging_test.sh .
|
||||
bash tests/native_packaging_test.sh .
|
||||
bash tests/anylinux_dependencies_test.sh .
|
||||
@@ -1,9 +1,9 @@
|
||||
# Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
name: Flatpak
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [Native packages]
|
||||
types: [completed]
|
||||
push:
|
||||
tags: ['v*']
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/flatpak.yml'
|
||||
@@ -15,13 +15,11 @@ on:
|
||||
- 'config/**'
|
||||
- 'include/**'
|
||||
- 'packaging/flatpak/**'
|
||||
- 'packaging/discord-join.html'
|
||||
- 'packaging/icons/**'
|
||||
- 'packaging/space.bigrat.mocktail.desktop'
|
||||
- 'packaging/space.bigrat.mocktail.metainfo.xml'
|
||||
- 'packaging/space.bigrat.mocktail.svg'
|
||||
- 'packaging/io.github.CoderDayton.nightcap.desktop'
|
||||
- 'packaging/io.github.CoderDayton.nightcap.metainfo.xml'
|
||||
- 'packaging/io.github.CoderDayton.nightcap.svg'
|
||||
- 'scripts/assemble_flatpak_pages.sh'
|
||||
- 'scripts/assemble_native_repositories.sh'
|
||||
- 'site/**'
|
||||
- 'src/**'
|
||||
- 'stubs/**'
|
||||
@@ -29,61 +27,28 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: flatpak-${{ github.event.workflow_run.head_sha || github.ref }}
|
||||
cancel-in-progress: true
|
||||
group: flatpak-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
flatpak:
|
||||
name: Flatpak build
|
||||
if: >-
|
||||
github.event_name != 'workflow_run' ||
|
||||
(github.event.workflow_run.conclusion == 'success' &&
|
||||
github.event.workflow_run.event != 'pull_request' &&
|
||||
github.event.workflow_run.head_branch == 'main')
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 120
|
||||
env:
|
||||
MOCKTAIL_PUBLISH: ${{ (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event != 'pull_request' && github.event.workflow_run.head_branch == 'main') || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') }}
|
||||
MOCKTAIL_SOURCE_SHA: ${{ github.event.workflow_run.head_sha || github.sha }}
|
||||
# Publish on release tags and on manual runs from main. Pull requests
|
||||
# only verify that the manifest still builds.
|
||||
MOCKTAIL_PUBLISH: ${{ startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') }}
|
||||
steps:
|
||||
- name: Check out source
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
|
||||
submodules: recursive
|
||||
|
||||
- name: Download package repositories
|
||||
if: env.MOCKTAIL_PUBLISH == 'true'
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -Eeuo pipefail
|
||||
run_id="${{ github.event.workflow_run.id }}"
|
||||
if [[ -z "$run_id" ]]; then
|
||||
run_id="$(gh run list \
|
||||
--workflow packages.yml \
|
||||
--branch main \
|
||||
--event push \
|
||||
--status success \
|
||||
--limit 1 \
|
||||
--json databaseId \
|
||||
--jq '.[0].databaseId')"
|
||||
fi
|
||||
[[ -n "$run_id" ]]
|
||||
gh run download "$run_id" \
|
||||
--name Mocktail-native-repositories \
|
||||
--dir native-repositories
|
||||
[[ -d native-repositories/apt && \
|
||||
-d native-repositories/rpm && \
|
||||
-d native-repositories/downloads && \
|
||||
-s native-repositories/mocktail-packages.gpg ]]
|
||||
|
||||
- name: Install Flatpak builder
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -99,22 +64,23 @@ jobs:
|
||||
FLATPAK_GPG_KEY_ID: ${{ secrets.FLATPAK_GPG_KEY_ID }}
|
||||
run: |
|
||||
set -Eeuo pipefail
|
||||
git update-ref refs/heads/main "$MOCKTAIL_SOURCE_SHA"
|
||||
# The manifest builds the local checkout from refs/heads/main.
|
||||
git update-ref refs/heads/main "$GITHUB_SHA"
|
||||
builder_signing=()
|
||||
bundle_keys=()
|
||||
if [[ "$MOCKTAIL_PUBLISH" == true ]]; then
|
||||
[[ -n "$FLATPAK_GPG_PRIVATE_KEY" && \
|
||||
-n "$FLATPAK_GPG_KEY_ID" ]]
|
||||
install -d -m 0700 "$RUNNER_TEMP/mocktail-gnupg"
|
||||
install -d -m 0700 "$RUNNER_TEMP/nightcap-gnupg"
|
||||
printf '%s' "$FLATPAK_GPG_PRIVATE_KEY" | \
|
||||
gpg --batch --homedir "$RUNNER_TEMP/mocktail-gnupg" --import
|
||||
gpg --batch --homedir "$RUNNER_TEMP/mocktail-gnupg" \
|
||||
--export "$FLATPAK_GPG_KEY_ID" >mocktail-flatpak.gpg
|
||||
gpg --batch --homedir "$RUNNER_TEMP/nightcap-gnupg" --import
|
||||
gpg --batch --homedir "$RUNNER_TEMP/nightcap-gnupg" \
|
||||
--export "$FLATPAK_GPG_KEY_ID" >nightcap-flatpak.gpg
|
||||
builder_signing=(
|
||||
"--gpg-sign=$FLATPAK_GPG_KEY_ID"
|
||||
"--gpg-homedir=$RUNNER_TEMP/mocktail-gnupg"
|
||||
"--gpg-homedir=$RUNNER_TEMP/nightcap-gnupg"
|
||||
)
|
||||
bundle_keys=(--gpg-keys=mocktail-flatpak.gpg)
|
||||
bundle_keys=(--gpg-keys=nightcap-flatpak.gpg)
|
||||
fi
|
||||
flatpak-builder \
|
||||
--arch=x86_64 \
|
||||
@@ -127,34 +93,33 @@ jobs:
|
||||
--repo=repo \
|
||||
"${builder_signing[@]}" \
|
||||
build-flatpak-ci \
|
||||
packaging/flatpak/space.bigrat.mocktail.json
|
||||
packaging/flatpak/io.github.CoderDayton.nightcap.json
|
||||
if [[ "$MOCKTAIL_PUBLISH" == true ]]; then
|
||||
flatpak build-update-repo \
|
||||
--generate-static-deltas \
|
||||
--static-delta-jobs=2 \
|
||||
--prune \
|
||||
--gpg-sign="$FLATPAK_GPG_KEY_ID" \
|
||||
--gpg-homedir="$RUNNER_TEMP/mocktail-gnupg" \
|
||||
--gpg-homedir="$RUNNER_TEMP/nightcap-gnupg" \
|
||||
repo
|
||||
fi
|
||||
flatpak build-bundle \
|
||||
--arch=x86_64 \
|
||||
"${bundle_keys[@]}" \
|
||||
repo \
|
||||
Mocktail-x86_64.flatpak \
|
||||
space.bigrat.mocktail \
|
||||
Nightcap-x86_64.flatpak \
|
||||
io.github.CoderDayton.nightcap \
|
||||
stable
|
||||
if [[ "$MOCKTAIL_PUBLISH" == true ]]; then
|
||||
./scripts/assemble_flatpak_pages.sh \
|
||||
repo Mocktail-x86_64.flatpak mocktail-flatpak.gpg \
|
||||
public native-repositories
|
||||
repo Nightcap-x86_64.flatpak nightcap-flatpak.gpg public
|
||||
fi
|
||||
|
||||
- name: Upload Flatpak
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: Mocktail-x86_64-flatpak
|
||||
path: Mocktail-x86_64.flatpak
|
||||
name: Nightcap-x86_64-flatpak
|
||||
path: Nightcap-x86_64.flatpak
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
|
||||
@@ -170,13 +135,7 @@ jobs:
|
||||
|
||||
deploy-pages:
|
||||
name: Publish Flatpak repository
|
||||
if: >-
|
||||
(github.event_name == 'workflow_run' &&
|
||||
github.event.workflow_run.conclusion == 'success' &&
|
||||
github.event.workflow_run.event != 'pull_request' &&
|
||||
github.event.workflow_run.head_branch == 'main') ||
|
||||
(github.event_name == 'workflow_dispatch' &&
|
||||
github.ref == 'refs/heads/main')
|
||||
if: startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main')
|
||||
needs: flatpak
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
@@ -191,3 +150,37 @@ jobs:
|
||||
- name: Deploy GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
|
||||
|
||||
release:
|
||||
name: Attach bundle to GitHub release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
needs: flatpak
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Download Flatpak
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: Nightcap-x86_64-flatpak
|
||||
path: release
|
||||
|
||||
- name: Upload to release
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -Eeuo pipefail
|
||||
tag="${GITHUB_REF_NAME}"
|
||||
(cd release && sha256sum Nightcap-x86_64.flatpak > Nightcap-x86_64.flatpak.sha256)
|
||||
# The Release workflow creates the same release; whichever job
|
||||
# arrives second sees "already exists" and continues.
|
||||
if ! gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||||
gh release create "$tag" --repo "$GITHUB_REPOSITORY" \
|
||||
--title "Nightcap $tag" --generate-notes ||
|
||||
gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null
|
||||
fi
|
||||
gh release upload "$tag" --repo "$GITHUB_REPOSITORY" --clobber \
|
||||
release/Nightcap-x86_64.flatpak \
|
||||
release/Nightcap-x86_64.flatpak.sha256
|
||||
@@ -45,19 +45,16 @@ jobs:
|
||||
|
||||
- name: Build AppImage
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
docker run --rm \
|
||||
--volume "$GITHUB_WORKSPACE:/workspace" \
|
||||
--workdir /workspace \
|
||||
--env GH_TOKEN \
|
||||
ghcr.io/archlinux/archlinux:base-devel@sha256:c3953446016873a756fb7814f10087cac1634c127f7302e80c81c512da796d05 bash -Eeuo pipefail -c '
|
||||
pacman -Syu --noconfirm --needed \
|
||||
cmake git ninja pkgconf lld sdl3 sdl3_ttf curl openssl \
|
||||
nlohmann-json libyaml libelf minizip minizip-ng capstone gtk4 \
|
||||
libadwaita webkitgtk-6.0 libutf8proc fontconfig libglvnd \
|
||||
vulkan-headers zlib libplacebo libpng bubblewrap xdg-dbus-proxy \
|
||||
vulkan-headers zlib libplacebo bubblewrap xdg-dbus-proxy \
|
||||
patchelf jq unzip file ca-certificates dbus zsync
|
||||
|
||||
# Reduced ABI-compatible packages belong only in this builder.
|
||||
@@ -172,7 +169,7 @@ jobs:
|
||||
libyaml-dev libelf-dev libminizip-dev libcapstone-dev \
|
||||
libgtk-4-dev libadwaita-1-dev libwebkitgtk-6.0-dev \
|
||||
libutf8proc-dev libfontconfig1-dev libegl-dev \
|
||||
libvulkan-dev libplacebo-dev libpng-dev zlib1g-dev
|
||||
libvulkan-dev libplacebo-dev zlib1g-dev
|
||||
cmake -S . -B build-deb -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
@@ -220,7 +217,7 @@ jobs:
|
||||
minizip-ng-compat-devel capstone-devel gtk4-devel libadwaita-devel \
|
||||
webkitgtk6.0-devel utf8proc-devel fontconfig-devel \
|
||||
libglvnd-devel vulkan-headers vulkan-loader-devel \
|
||||
libplacebo-devel libpng-devel zlib-ng-compat-devel
|
||||
libplacebo-devel zlib-ng-compat-devel
|
||||
cmake -S . -B build-rpm -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
@@ -264,7 +261,7 @@ jobs:
|
||||
cmake git ninja pkgconf lld sdl3 sdl3_ttf curl openssl \
|
||||
nlohmann-json libyaml libelf minizip capstone gtk4 \
|
||||
libadwaita webkitgtk-6.0 libutf8proc fontconfig \
|
||||
libplacebo libpng vulkan-headers vulkan-icd-loader libglvnd zlib
|
||||
libplacebo vulkan-headers vulkan-icd-loader libglvnd zlib
|
||||
useradd --create-home builder
|
||||
chown -R builder:builder /workspace
|
||||
runuser -u builder -- env MOCKTAIL_SOURCE_DIR=/workspace \
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ['v*']
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: release-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
appimage:
|
||||
name: AppImage (AnyLinux)
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Check out source
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Build AppImage
|
||||
shell: bash
|
||||
run: |
|
||||
docker run --rm \
|
||||
--volume "$GITHUB_WORKSPACE:/workspace" \
|
||||
--workdir /workspace \
|
||||
ghcr.io/archlinux/archlinux:base-devel@sha256:c3953446016873a756fb7814f10087cac1634c127f7302e80c81c512da796d05 bash -Eeuo pipefail -c '
|
||||
pacman -Syu --noconfirm --needed \
|
||||
cmake git ninja pkgconf lld sdl3 sdl3_ttf curl openssl \
|
||||
nlohmann-json libyaml libelf minizip minizip-ng capstone gtk4 \
|
||||
libadwaita webkitgtk-6.0 libutf8proc fontconfig libglvnd \
|
||||
vulkan-headers zlib libplacebo bubblewrap xdg-dbus-proxy \
|
||||
patchelf jq unzip file ca-certificates dbus zsync
|
||||
|
||||
./scripts/install_anylinux_dependencies.sh
|
||||
|
||||
curl --fail --location --retry 3 \
|
||||
--output /tmp/quick-sharun \
|
||||
https://raw.githubusercontent.com/pkgforge-dev/Anylinux-AppImages/2a025cef14483c5bc2a75b479071f4d5c58a906f/useful-tools/quick-sharun.sh
|
||||
echo "fa66d0189e1e87df781c049dfb23cbb1e753786056f693a4483d6d3af61e7ee1 /tmp/quick-sharun" | \
|
||||
sha256sum --check --strict
|
||||
chmod 0755 /tmp/quick-sharun
|
||||
|
||||
curl --fail --location --retry 3 \
|
||||
--output /tmp/sharun \
|
||||
https://github.com/pkgforge-dev/Anylinux-sharun/releases/download/2.2.6/sharun-x86_64
|
||||
echo "a4d51752f9861b4870ca529fea04087310b28f8943cc55b99a95b777d4db92ec /tmp/sharun" | \
|
||||
sha256sum --check --strict
|
||||
chmod 0755 /tmp/sharun
|
||||
|
||||
curl --fail --location --retry 3 \
|
||||
--output /tmp/anylinux.c \
|
||||
https://raw.githubusercontent.com/pkgforge-dev/Anylinux-AppImages/2a025cef14483c5bc2a75b479071f4d5c58a906f/useful-tools/lib/anylinux.c
|
||||
echo "f50650ad96d177559bdd0737df509fa20bd4af08887631c738fc1f31bfac185f /tmp/anylinux.c" | \
|
||||
sha256sum --check --strict
|
||||
|
||||
curl --fail --location --retry 3 \
|
||||
--output /tmp/appimagetool \
|
||||
https://github.com/pkgforge-dev/appimagetool/releases/download/0.3.3/appimagetool-x86_64-linux
|
||||
echo "0d01a4e2628efc897be9663f0faeed6a171e1851270b4e1ce4c22434a36ecebd /tmp/appimagetool" | \
|
||||
sha256sum --check --strict
|
||||
chmod 0755 /tmp/appimagetool
|
||||
|
||||
curl --fail --location --retry 3 \
|
||||
--output /tmp/uruntime \
|
||||
https://github.com/VHSgunzo/uruntime/releases/download/v0.5.9/uruntime-appimage-dwarfs-lite-x86_64
|
||||
echo "cef962c299f2fa19b2b3cdf2fa1565ee8541796cc89b9a97a591f94041e8b083 /tmp/uruntime" | \
|
||||
sha256sum --check --strict
|
||||
chmod 0755 /tmp/uruntime
|
||||
|
||||
curl --fail --location --retry 3 \
|
||||
--output /tmp/mkdwarfs \
|
||||
https://github.com/mhx/dwarfs/releases/download/v0.15.6/dwarfs-universal-0.15.6-Linux-x86_64
|
||||
echo "50891c38ba359db8271819a6cbf6aaa8068681523f0c4f2b8242007a45edaa28 /tmp/mkdwarfs" | \
|
||||
sha256sum --check --strict
|
||||
chmod 0755 /tmp/mkdwarfs
|
||||
|
||||
git config --global --add safe.directory /workspace
|
||||
git config --global --add safe.directory /workspace/third_party/Vulkan-Headers
|
||||
git config --global --add safe.directory /workspace/third_party/libjnivm
|
||||
export MOCKTAIL_ANYLINUX_SYSTEM_INSTALL=1
|
||||
export MOCKTAIL_ANYLINUX_SHARUN=/tmp/sharun
|
||||
export MOCKTAIL_ANYLINUX_C_SOURCE=/tmp/anylinux.c
|
||||
export RUNTIME=/tmp/uruntime
|
||||
export DWARFS_CMD=/tmp/mkdwarfs
|
||||
make appimage BUILD_DIR=build-appimage JOBS=4 \
|
||||
APPIMAGE=dist/Mocktail-x86_64.AppImage \
|
||||
PORTABLE_MODE=standalone APPIMAGE_FORMAT=anylinux \
|
||||
ANYLINUX_PACKAGER=/tmp/quick-sharun \
|
||||
ANYLINUX_APPIMAGETOOL=/tmp/appimagetool
|
||||
test -x dist/Mocktail-x86_64.AppImage
|
||||
'
|
||||
|
||||
- name: Smoke-test AppImage without FUSE
|
||||
shell: bash
|
||||
run: |
|
||||
smoke_dir="$(mktemp -d)"
|
||||
trap 'rm -rf -- "$smoke_dir"' EXIT
|
||||
export XDG_DATA_HOME="$smoke_dir/data"
|
||||
export XDG_CONFIG_HOME="$smoke_dir/config"
|
||||
export XDG_CACHE_HOME="$smoke_dir/cache"
|
||||
export XDG_STATE_HOME="$smoke_dir/state"
|
||||
mkdir -p "$XDG_DATA_HOME" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" "$XDG_STATE_HOME"
|
||||
timeout 60s dist/Mocktail-x86_64.AppImage --appimage-extract-and-run --help
|
||||
timeout 60s dist/Mocktail-x86_64.AppImage --appimage-extract-and-run mocktail_updater status
|
||||
|
||||
- name: Upload AppImage
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: Mocktail-AppImage
|
||||
path: dist/Mocktail-x86_64.AppImage
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
|
||||
publish:
|
||||
name: Publish GitHub release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
needs: [appimage]
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Download AppImage
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: Mocktail-AppImage
|
||||
path: release
|
||||
|
||||
- name: Create release
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -Eeuo pipefail
|
||||
tag="${GITHUB_REF_NAME}"
|
||||
mv release/Mocktail-x86_64.AppImage release/Nightcap-x86_64.AppImage
|
||||
(cd release && sha256sum Nightcap-x86_64.AppImage > Nightcap-x86_64.AppImage.sha256)
|
||||
# The Flatpak workflow creates the same release; whichever job
|
||||
# arrives second sees "already exists" and continues.
|
||||
if ! gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||||
gh release create "$tag" --repo "$GITHUB_REPOSITORY" \
|
||||
--title "Nightcap $tag" --generate-notes ||
|
||||
gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null
|
||||
fi
|
||||
gh release upload "$tag" --repo "$GITHUB_REPOSITORY" --clobber \
|
||||
release/Nightcap-x86_64.AppImage \
|
||||
release/Nightcap-x86_64.AppImage.sha256
|
||||
@@ -1,5 +1,6 @@
|
||||
# Build
|
||||
/build/
|
||||
/build-test/
|
||||
/build_*/
|
||||
/build-flatpak/
|
||||
/build-anylinux*/
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
# Copyright 2026 Mocktail Project Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -264,7 +265,6 @@ add_library(mocktail_runtime STATIC
|
||||
src/runtime/command_line.cc
|
||||
src/runtime/crash_report_policy.cc
|
||||
src/runtime/discord_rpc.cc
|
||||
src/runtime/device_memory_profile.cc
|
||||
src/runtime/device_profile.cc
|
||||
src/runtime/environment.cc
|
||||
src/runtime/external_launch_broker.cc
|
||||
@@ -301,6 +301,7 @@ add_library(mocktail_runtime STATIC
|
||||
src/runtime/support_bundle.cc
|
||||
src/runtime/system_proxy.cc
|
||||
src/runtime/texture_memory_policy.cc
|
||||
src/runtime/device_memory_profile.cc
|
||||
)
|
||||
add_library(Mocktail::Runtime ALIAS mocktail_runtime)
|
||||
target_include_directories(mocktail_runtime PUBLIC include)
|
||||
@@ -725,13 +726,24 @@ install(FILES
|
||||
config/roblox_signing_certificates.json
|
||||
DESTINATION "${MOCKTAIL_INSTALL_DATA_DIR}/metadata"
|
||||
)
|
||||
install(FILES packaging/space.bigrat.mocktail.desktop
|
||||
DESTINATION "${CMAKE_INSTALL_DATADIR}/applications"
|
||||
)
|
||||
install(FILES packaging/space.bigrat.mocktail.metainfo.xml
|
||||
# The installed desktop entry names the binary by its install path, so it
|
||||
# launches from sessions whose PATH lacks the install prefix. Uses the prefix
|
||||
# given at install time, not the configure-time one.
|
||||
install(CODE "
|
||||
file(READ \"${CMAKE_SOURCE_DIR}/packaging/io.github.CoderDayton.nightcap.desktop\"
|
||||
MOCKTAIL_DESKTOP_ENTRY)
|
||||
string(REPLACE \"Exec=env SDL_VIDEODRIVER=wayland,x11 mocktail %u\"
|
||||
\"Exec=env SDL_VIDEODRIVER=wayland,x11 \${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/mocktail %u\"
|
||||
MOCKTAIL_DESKTOP_ENTRY \"\${MOCKTAIL_DESKTOP_ENTRY}\")
|
||||
file(WRITE \"${CMAKE_BINARY_DIR}/io.github.CoderDayton.nightcap.desktop\"
|
||||
\"\${MOCKTAIL_DESKTOP_ENTRY}\")
|
||||
file(INSTALL \"${CMAKE_BINARY_DIR}/io.github.CoderDayton.nightcap.desktop\"
|
||||
DESTINATION \"\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/applications\")
|
||||
")
|
||||
install(FILES packaging/io.github.CoderDayton.nightcap.metainfo.xml
|
||||
DESTINATION "${CMAKE_INSTALL_DATADIR}/metainfo"
|
||||
)
|
||||
install(FILES packaging/space.bigrat.mocktail.svg
|
||||
install(FILES packaging/io.github.CoderDayton.nightcap.svg
|
||||
DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps"
|
||||
)
|
||||
set(MOCKTAIL_HICOLOR_ICON_SIZES
|
||||
@@ -740,7 +752,7 @@ set(MOCKTAIL_HICOLOR_ICON_SIZES
|
||||
# Generated icons are committed, so ImageMagick is not a build dependency.
|
||||
foreach(icon_size IN LISTS MOCKTAIL_HICOLOR_ICON_SIZES)
|
||||
install(FILES
|
||||
"packaging/icons/hicolor/${icon_size}x${icon_size}/apps/space.bigrat.mocktail.png"
|
||||
"packaging/icons/hicolor/${icon_size}x${icon_size}/apps/io.github.CoderDayton.nightcap.png"
|
||||
DESTINATION
|
||||
"${CMAKE_INSTALL_DATADIR}/icons/hicolor/${icon_size}x${icon_size}/apps"
|
||||
)
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
# FAQ
|
||||
|
||||
## How do I enable Discord RPC?
|
||||
|
||||
RPC is disabled by default. Launch Mocktail once to create the config, then
|
||||
close it. Open `config.yaml`:
|
||||
|
||||
- Native / AppImage: `~/.config/mocktail/config.yaml`
|
||||
- Flatpak: `~/.var/app/space.bigrat.mocktail/config/mocktail/config.yaml`
|
||||
|
||||
For native installs, a custom `$XDG_CONFIG_HOME` replaces `~/.config`.
|
||||
|
||||
Set `enabled` to `true` under `integrations.discord_rpc`. If the block is
|
||||
missing, add it under the existing `integrations` section:
|
||||
|
||||
```yaml
|
||||
integrations:
|
||||
discord_rpc:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
Save the file, open Discord Desktop, and restart Mocktail. Set `enabled`
|
||||
back to `false` to disable RPC.
|
||||
|
||||
## Where can I find logs for a bug report?
|
||||
|
||||
Mocktail saves logs automatically. Reproduce the issue, close Mocktail, and
|
||||
attach `latest.log`:
|
||||
|
||||
- Native / AppImage: `~/.local/state/mocktail/logs/latest.log`
|
||||
- Flatpak: `~/.var/app/space.bigrat.mocktail/.local/state/mocktail/logs/latest.log`
|
||||
|
||||
For native installs, a custom `$XDG_STATE_HOME` replaces `~/.local/state`.
|
||||
|
||||
## Can I play in VR?
|
||||
|
||||
VR is experimental. Install the [build dependencies](README.md#building),
|
||||
then switch to the `vr` branch and build:
|
||||
|
||||
```bash
|
||||
git fetch origin
|
||||
git switch vr
|
||||
git submodule update --init --recursive
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DMOCKTAIL_ENABLE_VR=ON
|
||||
cmake --build build -j4
|
||||
```
|
||||
|
||||
Start WiVRn or SteamVR/ALVR, connect your headset, then run:
|
||||
|
||||
```bash
|
||||
./build/mocktail -vr
|
||||
```
|
||||
@@ -1,3 +1,4 @@
|
||||
# Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
.DEFAULT_GOAL := all
|
||||
.PHONY: all build release debug test apk install register-url-handler release-runtime portable portable-test standalone appimage flatpak run-flatpak run run-smoke run-unlimited run-game run-input run-resize run-network auto-run run-gles run-angle update-roblox update-auto update-auto-launch payload-status payload-rollback support-bundle clean submodules help
|
||||
|
||||
@@ -19,7 +20,7 @@ ANYLINUX_PACKAGER ?= quick-sharun
|
||||
ANYLINUX_APPIMAGETOOL ?= appimagetool
|
||||
PREFIX ?= /usr
|
||||
FLATPAK_BUILD_DIR ?= build-flatpak
|
||||
FLATPAK_MANIFEST ?= packaging/flatpak/space.bigrat.mocktail.json
|
||||
FLATPAK_MANIFEST ?= packaging/flatpak/io.github.CoderDayton.nightcap.json
|
||||
FLATPAK_JOBS ?= 4
|
||||
|
||||
define build_native_runtime
|
||||
@@ -55,7 +56,7 @@ install: ## Install an already-built dynamic runtime (use: sudo make install)
|
||||
register-url-handler: build ## Select this build for Roblox website links
|
||||
@./scripts/register_url_handler.sh --set-default \
|
||||
--executable "$(abspath $(BINARY))" \
|
||||
--desktop-file "$(abspath packaging/space.bigrat.mocktail.desktop)"
|
||||
--desktop-file "$(abspath packaging/io.github.CoderDayton.nightcap.desktop)"
|
||||
|
||||
test: ## Build and run all unit tests
|
||||
$(call build_native_runtime,Debug,ON)
|
||||
@@ -91,7 +92,7 @@ flatpak: ## Build and install the local x86-64 Flatpak
|
||||
--jobs "$(FLATPAK_JOBS)"
|
||||
|
||||
run-flatpak: ## Run the installed Flatpak
|
||||
@flatpak run space.bigrat.mocktail
|
||||
@flatpak run io.github.CoderDayton.nightcap
|
||||
|
||||
run: export MOCKTAIL_AUTO_EXIT_AFTER_PRESENT_MS = 0
|
||||
run: build ## Run the native binary interactively until the window is closed
|
||||
|
||||
@@ -1,19 +1,67 @@
|
||||
# Mocktail
|
||||
<!-- Modified by vii from komaruworld/mocktail. See "About this fork" below. -->
|
||||
# Nightcap
|
||||
|
||||
[](https://github.com/komaruworld/mocktail/actions/workflows/ci.yml)
|
||||
[](https://github.com/komaruworld/mocktail/stargazers)
|
||||
[](https://github.com/komaruworld/mocktail/releases/latest)
|
||||
[](https://github.com/CoderDayton/nightcap/actions/workflows/ci.yml)
|
||||
[](https://github.com/CoderDayton/nightcap/stargazers)
|
||||
[](LICENSE)
|
||||
[](https://discord.gg/rhgpfcmFSD)
|
||||
|
||||
Mocktail runs the Android `x86_64` Roblox client on Linux, including a Linux
|
||||
userspace hosted by FreeBSD's Linuxulator. It provides the Android ABI and JNI
|
||||
pieces the client expects, then connects them to SDL3 and Vulkan or OpenGL on
|
||||
the Linux side.
|
||||
Nightcap is Mocktail, tuned for playing on a real PC. It runs the Android
|
||||
`x86_64` Roblox client on Linux, including a Linux userspace hosted by
|
||||
FreeBSD's Linuxulator. It provides the Android ABI and JNI pieces the client
|
||||
expects, then connects them to SDL3 and Vulkan or OpenGL on the Linux side.
|
||||
|
||||
Mocktail is an independent community project. It is not affiliated with Roblox
|
||||
Nightcap is an independent community project. It is not affiliated with Roblox
|
||||
Corporation or VinegarHQ and does not distribute the Roblox client.
|
||||
|
||||
## About this fork
|
||||
|
||||
Nightcap is a fork of [komaruworld/mocktail](https://github.com/komaruworld/mocktail)
|
||||
with extra fixes for playing on a PC. The program and config paths keep the
|
||||
`mocktail` name so upstream fixes still apply cleanly. The app ID is
|
||||
`io.github.CoderDayton.nightcap`, so Nightcap and upstream can be installed
|
||||
side by side.
|
||||
|
||||
- **Lower input lag.** Frames are shown as soon as they are ready instead of
|
||||
waiting in a queue.
|
||||
- **Sharper textures.** Small textures are upscaled 4x by default, and you
|
||||
can replace any texture with your own PNG in `~/.config/mocktail/textures`.
|
||||
Set `MOCKTAIL_SMALL_TEXTURE_UPSCALE=1` to turn the upscale off.
|
||||
- **ETC2 textures on any GPU.** Games that use compressed mobile textures now
|
||||
render correctly on desktop GPUs, without stutter.
|
||||
- **No more error 319 kicks.** Servers no longer drop you shortly after
|
||||
joining.
|
||||
- **Correct device info.** Roblox sees your real RAM and screen size.
|
||||
- **Better desktop launcher.** Runs on Wayland and always finds the installed
|
||||
binary.
|
||||
|
||||
Everything else works the same as upstream. See the
|
||||
[roadmap](docs/ROADMAP.md) for what is coming next.
|
||||
|
||||
## Install with Flatpak
|
||||
|
||||
```bash
|
||||
flatpak install --user https://coderdayton.github.io/nightcap/nightcap.flatpakref
|
||||
flatpak run io.github.CoderDayton.nightcap
|
||||
```
|
||||
|
||||
Updates arrive through `flatpak update`. Nightcap is not on Flathub yet.
|
||||
|
||||
## AppImage
|
||||
|
||||
Grab the AppImage from the
|
||||
[latest release](https://github.com/CoderDayton/nightcap/releases/latest),
|
||||
make it executable, and run it:
|
||||
|
||||
```bash
|
||||
chmod +x Nightcap-x86_64.AppImage
|
||||
./Nightcap-x86_64.AppImage
|
||||
```
|
||||
|
||||
Or build and install it from source using the steps under
|
||||
[Building](#building). There are no AUR, DEB, or RPM packages of Nightcap.
|
||||
Upstream ships those for unmodified Mocktail, see
|
||||
[komaruworld/mocktail](https://github.com/komaruworld/mocktail#readme).
|
||||
|
||||
## How it works
|
||||
|
||||
```
|
||||
@@ -21,91 +69,24 @@ Roblox APK -> signature and ABI checks -> Bionic + JNI -> SDL3 + Vulkan/OpenGL
|
||||
```
|
||||
|
||||
The APK is checked before any native code is loaded. It is downloaded on first
|
||||
launch and is not bundled with Mocktail. The last working copy is kept in case
|
||||
launch and is not bundled with Nightcap. The last working copy is kept in case
|
||||
an update fails.
|
||||
|
||||
## Install with Flatpak
|
||||
|
||||
Install the stable release from Flathub:
|
||||
|
||||
```bash
|
||||
flatpak install flathub space.bigrat.mocktail
|
||||
flatpak run space.bigrat.mocktail
|
||||
```
|
||||
|
||||
Nightly builds are produced automatically from the latest `main` branch:
|
||||
|
||||
```bash
|
||||
flatpak install --user https://mocktail.bigrat.space/mocktail.flatpakref
|
||||
flatpak run space.bigrat.mocktail
|
||||
```
|
||||
|
||||
## Install from the AUR
|
||||
|
||||
Arch Linux users can install either the pinned source release (`mocktail`) or
|
||||
the current development version (`mocktail-git`) with an AUR helper:
|
||||
|
||||
```bash
|
||||
paru -S mocktail-git
|
||||
# or
|
||||
yay -S mocktail-git
|
||||
```
|
||||
|
||||
Use `mocktail` instead of `mocktail-git` to build the pinned release from
|
||||
source, or install the stable prebuilt package with `paru -S mocktail-bin` or
|
||||
`yay -S mocktail-bin`.
|
||||
|
||||
## Install with DNF
|
||||
|
||||
Fedora 44 users can install either the stable `mocktail` package or the
|
||||
`mocktail-nightly` package built from `main`:
|
||||
|
||||
```bash
|
||||
sudo curl -fsSL https://mocktail.bigrat.space/rpm/mocktail.repo \
|
||||
-o /etc/yum.repos.d/mocktail.repo
|
||||
sudo dnf install mocktail
|
||||
# or
|
||||
sudo dnf install mocktail-nightly
|
||||
```
|
||||
|
||||
## Install with APT
|
||||
|
||||
Ubuntu 26.04 users can install either the stable `mocktail` package or the
|
||||
`mocktail-nightly` package built from `main`:
|
||||
|
||||
```bash
|
||||
sudo install -d -m 0755 /etc/apt/keyrings
|
||||
sudo curl -fsSL https://mocktail.bigrat.space/mocktail-packages.gpg \
|
||||
-o /etc/apt/keyrings/mocktail.gpg
|
||||
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/mocktail.gpg] https://mocktail.bigrat.space/apt mocktail main" | \
|
||||
sudo tee /etc/apt/sources.list.d/mocktail.list >/dev/null
|
||||
sudo apt update
|
||||
sudo apt install mocktail
|
||||
# or
|
||||
sudo apt install mocktail-nightly
|
||||
```
|
||||
|
||||
## Direct downloads
|
||||
|
||||
Direct AppImage, DEB, and RPM downloads are available from the
|
||||
[Website](https://mocktail.bigrat.space/) and
|
||||
[GitHub Releases](https://github.com/komaruworld/mocktail/releases).
|
||||
|
||||
<details>
|
||||
<summary>Screenshots</summary>
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
</details>
|
||||
|
||||
## FFlag overrides
|
||||
|
||||
Put a JSON object in `$XDG_CONFIG_HOME/mocktail/fflags.json` (usually
|
||||
`~/.config/mocktail/fflags.json`). Mocktail applies it on the next launch.
|
||||
`~/.config/mocktail/fflags.json`). Nightcap applies it on the next launch.
|
||||
|
||||
PC profiles use the legacy Charts page by default to avoid the blank screen
|
||||
caused by `Color3` errors in the newer SDUI page. An explicit
|
||||
@@ -117,15 +98,18 @@ caused by `Color3` errors in the newer SDUI page. An explicit
|
||||
"DFIntExample": "120"
|
||||
}
|
||||
```
|
||||
## FAQ
|
||||
## Settings
|
||||
|
||||
To see the most Frequently Asked Question go check [FAQ.md](FAQ.md)
|
||||
Edit `~/.config/mocktail/config.yaml`. Useful options:
|
||||
|
||||
- `graphics.frame_rate_limit: display` to unlock FPS to your monitor's rate.
|
||||
- `graphics.vsync: off` for the lowest lag, at the cost of tearing.
|
||||
- `graphics.backend: opengl` if Vulkan does not work on your GPU.
|
||||
|
||||
## Building
|
||||
|
||||
Linux `x86_64` is supported. Experimental FreeBSD 15.1 Linuxulator support has
|
||||
been tested with an `x86_64` Fedora 44 userspace. On FreeBSD, Mocktail runs
|
||||
been tested with an `x86_64` Fedora 44 userspace. On FreeBSD, Nightcap runs
|
||||
inside Linuxulator; it is not a native FreeBSD binary. Building requires CMake
|
||||
3.20+, Git, pkg-config, LLD, binutils, a C++17 compiler, SDL 3.4+, SDL3_ttf,
|
||||
Vulkan, EGL, libplacebo, fontconfig, libcurl, OpenSSL, libelf, libyaml, minizip,
|
||||
@@ -172,17 +156,34 @@ sudo dnf install gcc-c++ cmake git ninja-build pkgconf-pkg-config lld \
|
||||
</details>
|
||||
|
||||
```bash
|
||||
git clone --recurse-submodules https://github.com/komaruworld/mocktail.git
|
||||
cd mocktail
|
||||
git clone --recurse-submodules https://github.com/CoderDayton/nightcap.git
|
||||
cd nightcap
|
||||
make build
|
||||
./build/mocktail
|
||||
```
|
||||
|
||||
To install it for your user, with an app menu entry and Roblox link handling:
|
||||
|
||||
```bash
|
||||
make install PREFIX=~/.local
|
||||
```
|
||||
|
||||
Use `sudo make install` instead to install system-wide under `/usr`. To make
|
||||
Roblox website links open the build tree copy without installing, run
|
||||
`make register-url-handler`.
|
||||
|
||||
To run the same checks as CI before each commit and push, install
|
||||
[lefthook](https://github.com/evilmartians/lefthook) and run once:
|
||||
|
||||
```bash
|
||||
lefthook install
|
||||
```
|
||||
|
||||
## Known limitations
|
||||
|
||||
Mocktail does not currently work with `hardened_malloc`. Using
|
||||
`hardened_malloc` may cause Mocktail to fail to start or crash during runtime.
|
||||
Run Mocktail without `hardened_malloc` enabled.
|
||||
Nightcap does not currently work with `hardened_malloc`. Using
|
||||
`hardened_malloc` may cause Nightcap to fail to start or crash during runtime.
|
||||
Run Nightcap without `hardened_malloc` enabled.
|
||||
|
||||
## License
|
||||
|
||||
@@ -190,7 +191,13 @@ Run Mocktail without `hardened_malloc` enabled.
|
||||
|
||||
## Support
|
||||
|
||||
You can support the project by giving it a star or with cryptocurrency:
|
||||
Support this fork by giving it a star or with cryptocurrency:
|
||||
|
||||
- LTC: `LdABR2ELRYrUESWZWrEk1uNx38gHHMuHUU`
|
||||
- SOL: `tjeDnPoWeyW8zfTf8c5CVAm5QTV8vFPaPouaScAdwfT`
|
||||
- BCH: `qpktw5jwut9ae8e6898qt3v7rj3km44utgk2r9mj8n`
|
||||
|
||||
Support the upstream project, which does most of the heavy lifting:
|
||||
|
||||
- USDT (TON): `UQCi6Yzcc9cOctoij6n_r1K90-OdVxAT0D_xo2UzGKkQaJDY`
|
||||
- USDT (TRC20): `TNPMG9Vig2xiuo2r1QqnXRChPH7Vu28Jmx`
|
||||
@@ -1,3 +1,4 @@
|
||||
# Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
# Copyright 2026 Mocktail Project Authors
|
||||
# Licensed under the Apache License, Version 2.0.
|
||||
|
||||
@@ -12,7 +13,7 @@ find_path(MOCKTAIL_EGL_INCLUDE_DIR EGL/egl.h REQUIRED)
|
||||
find_path(MOCKTAIL_GLES3_INCLUDE_DIR GLES3/gl3.h REQUIRED)
|
||||
|
||||
set(MOCKTAIL_WINDOW_ICON_PNG
|
||||
"${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/packaging/icons/hicolor/48x48/apps/space.bigrat.mocktail.png"
|
||||
"${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/packaging/icons/hicolor/48x48/apps/io.github.CoderDayton.nightcap.png"
|
||||
)
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
|
||||
"${MOCKTAIL_WINDOW_ICON_PNG}"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
# Mocktail configuration.
|
||||
# The file is created once with mode 0600. Mocktail never replaces your edits.
|
||||
|
||||
@@ -46,7 +47,9 @@ graphics:
|
||||
# display, unlimited, or any positive integer; fixed values are forwarded to
|
||||
# DFIntTaskSchedulerTargetFps without a whitelist.
|
||||
# frame_rate_limit: -1
|
||||
# Optional presentation synchronization override: auto, on, or off.
|
||||
# Optional presentation synchronization override: auto, on, or off. auto
|
||||
# presents through the lowest-latency synchronized mode the driver offers
|
||||
# unless frame_rate_limit is unlimited.
|
||||
# vsync: off
|
||||
|
||||
performance:
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
# Nightcap roadmap
|
||||
|
||||
What Nightcap is working toward, in priority order. Items move to
|
||||
[Shipped](#shipped) when they land on `main`. Open an issue to propose a
|
||||
change to this list.
|
||||
|
||||
## 1. Settings UI
|
||||
|
||||
Nightcap is configured by editing `~/.config/mocktail/config.yaml`. Most
|
||||
people never open it, so they never find the FPS unlock or the vsync switch.
|
||||
|
||||
Goal: a small GTK4 settings window, reachable from the app menu and from a
|
||||
`--settings` flag, that reads and writes the same `config.yaml`.
|
||||
|
||||
- Graphics tab: backend, frame rate limit, vsync, small texture upscale.
|
||||
- Input tab: raw mouse and controller options (see item 2).
|
||||
- Advanced tab: FFlag overrides with a plain text editor for `fflags.json`.
|
||||
- Discord tab: the `discord_rpc` block (see item 3).
|
||||
- Changes apply on next launch. A restart button restarts Nightcap.
|
||||
|
||||
Done when every option in `config/mocktail.example.yaml` that a player would
|
||||
touch can be set without a text editor.
|
||||
|
||||
## 2. Controller and raw mouse input
|
||||
|
||||
Upstream added Xbox and PlayStation gamepad support through SDL. Desktop play
|
||||
still has rough edges.
|
||||
|
||||
- Raw mouse input in first-person games, with no acceleration or clamping.
|
||||
- Better controller support: hot-plug without a restart, correct button
|
||||
glyphs for the connected pad, and no stuck inputs after a disconnect.
|
||||
|
||||
Done when a mouse feels like a mouse and a plugged-in controller just works.
|
||||
|
||||
## 3. Discord Rich Presence
|
||||
|
||||
Upstream ships Discord RPC, off by default, behind `discord_rpc.enabled`. It
|
||||
shows the experience name, elapsed time, and a join button when the server is
|
||||
public.
|
||||
|
||||
- Turn it on by default. It never reads a Discord token, so the privacy cost
|
||||
is only the experience name, which the player already shares in Roblox.
|
||||
- Show the experience icon as the large image instead of a static logo.
|
||||
- Show the current server region and player count when Roblox exposes them.
|
||||
- Make the join button work for private servers the player has a link for.
|
||||
- A Discord tab in the settings window to toggle each field.
|
||||
|
||||
Done when a friend can see what you are playing and join in one click, and
|
||||
you can switch it all off in one click.
|
||||
|
||||
## 4. Performance profiling
|
||||
|
||||
Every performance change should come with numbers. Today they come with a
|
||||
feeling.
|
||||
|
||||
- A frame time overlay, toggled by a key and a config flag, showing CPU time,
|
||||
GPU time, and present latency.
|
||||
- Shader compile and pipeline cache tracing, so first-load stutter can be
|
||||
measured and cut.
|
||||
- A `--profile` flag that writes a trace file readable by Perfetto.
|
||||
- A short benchmark guide in `docs/` with a fixed set of experiences, so
|
||||
before and after numbers can be compared across machines.
|
||||
|
||||
Done when a pull request that claims a speedup can show the trace that
|
||||
proves it.
|
||||
|
||||
## Shipped
|
||||
|
||||
- Lower input latency: frames are presented as soon as they are ready.
|
||||
- Small textures upscaled 4x by default, with PNG overrides in
|
||||
`~/.config/mocktail/textures`.
|
||||
- ETC2 textures decoded on desktop GPUs without stutter.
|
||||
- No more error 319 kicks after joining a server.
|
||||
- Roblox sees the real host RAM and screen size.
|
||||
- Desktop launcher that works on Wayland and finds the installed binary.
|
||||
- Own app ID `io.github.CoderDayton.nightcap`, so Nightcap installs next to
|
||||
upstream.
|
||||
- AppImage and signed Flatpak releases on every `v*` tag, with the Flatpak
|
||||
repo on GitHub Pages.
|
||||
|
||||
## Not planned
|
||||
|
||||
- Replacing the `mocktail` binary and config names. Keeping them makes
|
||||
upstream merges clean.
|
||||
- DEB, RPM, and AUR packages. Upstream ships those for unmodified Mocktail.
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
namespace mocktail::graphics {
|
||||
|
||||
// ETC2 and EAC block encodings from the OpenGL ES 3.0 / Vulkan core formats.
|
||||
// sRGB variants share the unorm encoding; the colour space belongs to the
|
||||
// destination format.
|
||||
enum class EtcFormat {
|
||||
kEtc2Rgb8,
|
||||
kEtc2Rgb8A1,
|
||||
@@ -16,15 +19,26 @@ enum class EtcFormat {
|
||||
kEacRg11Signed,
|
||||
};
|
||||
|
||||
// Compressed bytes per 4x4 block.
|
||||
std::size_t EtcBlockBytes(EtcFormat format);
|
||||
|
||||
// Decoded bytes per texel: 4 (RGBA8) for ETC2 formats, 2 per channel
|
||||
// (16-bit, little-endian) for EAC formats.
|
||||
std::size_t EtcDecodedTexelBytes(EtcFormat format);
|
||||
|
||||
// Decodes a width x height image stored as ceil(width/4) x ceil(height/4)
|
||||
// blocks into row-major texels. Returns false, writing nothing, when a buffer
|
||||
// is smaller than the image requires.
|
||||
bool DecodeEtcImage(EtcFormat format, const std::uint8_t* source,
|
||||
std::size_t source_bytes, std::uint32_t width,
|
||||
std::uint32_t height, std::uint8_t* destination,
|
||||
std::size_t destination_bytes);
|
||||
|
||||
// Decodes block rows [first_block_row, first_block_row + block_row_count) of
|
||||
// the image DecodeEtcImage describes, writing only the texel rows those blocks
|
||||
// cover. Bands that together cover every block row produce DecodeEtcImage's
|
||||
// output. Returns false, writing nothing, when a buffer is too small or the
|
||||
// rows lie outside the image.
|
||||
bool DecodeEtcImageBlockRows(EtcFormat format, const std::uint8_t* source,
|
||||
std::size_t source_bytes, std::uint32_t width,
|
||||
std::uint32_t height,
|
||||
@@ -33,6 +47,7 @@ bool DecodeEtcImageBlockRows(EtcFormat format, const std::uint8_t* source,
|
||||
std::uint8_t* destination,
|
||||
std::size_t destination_bytes);
|
||||
|
||||
// One DecodeEtcImage call. Jobs must not share destination bytes.
|
||||
struct EtcDecodeJob {
|
||||
EtcFormat format = EtcFormat::kEtc2Rgb8;
|
||||
const std::uint8_t* source = nullptr;
|
||||
@@ -41,10 +56,13 @@ struct EtcDecodeJob {
|
||||
std::uint32_t height = 0;
|
||||
std::uint8_t* destination = nullptr;
|
||||
std::size_t destination_bytes = 0;
|
||||
// Set to DecodeEtcImage's result.
|
||||
bool ok = false;
|
||||
};
|
||||
|
||||
// Synchronous; job destination buffers must not overlap.
|
||||
// Decodes every job before returning. Large batches are split into block-row
|
||||
// bands decoded on up to worker_count threads, the caller included; small
|
||||
// batches decode on the caller only.
|
||||
void DecodeEtcJobs(EtcDecodeJob* jobs, std::size_t count,
|
||||
unsigned worker_count);
|
||||
|
||||
|
||||
@@ -12,32 +12,45 @@
|
||||
|
||||
namespace mocktail::graphics {
|
||||
|
||||
// Tightly packed RGBA8 pixels, row-major, top row first.
|
||||
struct RgbaImage {
|
||||
std::uint32_t width = 0;
|
||||
std::uint32_t height = 0;
|
||||
std::vector<std::uint8_t> pixels;
|
||||
};
|
||||
|
||||
// FNV-1a over the bytes; the texture identity used for dump and override
|
||||
// file names.
|
||||
std::uint64_t HashBytes(const std::uint8_t* data, std::size_t size);
|
||||
// 16 lowercase hex digits.
|
||||
std::string HashName(std::uint64_t hash);
|
||||
|
||||
bool ReadPngRgba(const std::string& path, RgbaImage* image);
|
||||
bool WritePngRgba(const std::string& path, const RgbaImage& image);
|
||||
|
||||
// Writes width x height RGBA8 pixels to destination, each destination pixel
|
||||
// the average of the source box it covers. Growing repeats source pixels.
|
||||
void ResampleRgba(const RgbaImage& source, std::uint32_t width,
|
||||
std::uint32_t height, std::uint8_t* destination);
|
||||
void ResampleRgba(const std::uint8_t* source, std::uint32_t source_width,
|
||||
std::uint32_t source_height, std::uint32_t width,
|
||||
std::uint32_t height, std::uint8_t* destination);
|
||||
|
||||
// Replacement textures keyed by the hash of a texture's level-0 compressed
|
||||
// bytes. An override lives at <override_dir>/<hash>.png. With a dump
|
||||
// directory, every level-0 texture seen is written once as
|
||||
// <dump_dir>/<hash>_<width>x<height>.png so the hash can be found.
|
||||
// Thread-safe.
|
||||
class TextureOverrides {
|
||||
public:
|
||||
TextureOverrides(std::string override_dir, std::string dump_dir);
|
||||
// MOCKTAIL_TEXTURE_OVERRIDE_DIR and MOCKTAIL_TEXTURE_DUMP_DIR.
|
||||
static TextureOverrides FromEnvironment();
|
||||
|
||||
bool enabled() const { return !override_dir_.empty() || !dump_dir_.empty(); }
|
||||
bool dumping() const { return !dump_dir_.empty(); }
|
||||
|
||||
// Null when no override file exists; lookups are cached either way.
|
||||
std::shared_ptr<const RgbaImage> Lookup(std::uint64_t hash);
|
||||
void Dump(std::uint64_t hash, std::uint32_t width, std::uint32_t height,
|
||||
const std::uint8_t* rgba);
|
||||
|
||||
@@ -9,27 +9,52 @@
|
||||
|
||||
namespace mocktail::graphics {
|
||||
|
||||
// Host format that stores a decoded ETC2/EAC image, with its block encoding.
|
||||
struct Etc2EmulatedFormat {
|
||||
VkFormat host_format = VK_FORMAT_UNDEFINED;
|
||||
EtcFormat etc_format = EtcFormat::kEtc2Rgb8;
|
||||
};
|
||||
|
||||
// Maps an ETC2/EAC Vulkan format to its decoded host format. Returns false for
|
||||
// every other format.
|
||||
bool LookupEmulatedEtc2Format(VkFormat format, Etc2EmulatedFormat* out);
|
||||
|
||||
// Format features reported for an emulated ETC2/EAC format: the host format's
|
||||
// sampling and transfer features only, as for a native compressed format.
|
||||
VkFormatFeatureFlags EmulatedEtc2FormatFeatures(
|
||||
VkFormatFeatureFlags host_features);
|
||||
|
||||
// Staging bytes consumed and decoded bytes produced by one 2D upload region.
|
||||
// Returns false for an empty extent, a depth other than 1, or no layers.
|
||||
bool Etc2UploadByteCounts(EtcFormat format, const VkExtent3D& extent,
|
||||
std::uint32_t layer_count, VkDeviceSize* compressed,
|
||||
VkDeviceSize* decoded);
|
||||
|
||||
// Whether emulated ETC2 is reported to the application as a supported
|
||||
// feature, from the MOCKTAIL_ADVERTISE_ETC2 value: "0" hides it, anything
|
||||
// else (or unset) reports it. Hidden ETC2 still decodes ETC2 images the
|
||||
// application creates; it only steers the application toward other formats.
|
||||
bool Etc2SupportAdvertised(const char* value);
|
||||
bool Etc2SupportAdvertised();
|
||||
|
||||
// Factor applied to emulated ETC2 colour images no larger than
|
||||
// kSmallTextureMaxExtent, from the MOCKTAIL_SMALL_TEXTURE_UPSCALE value:
|
||||
// 1 leaves them alone; 2..8 create them that many times larger and resample
|
||||
// every upload, so a texture override can carry more detail than the
|
||||
// original. Unset or non-numeric values use kDefaultSmallTextureUpscale;
|
||||
// numeric values out of range are clamped.
|
||||
inline constexpr std::uint32_t kSmallTextureMaxExtent = 64;
|
||||
inline constexpr std::uint32_t kDefaultSmallTextureUpscale = 4;
|
||||
std::uint32_t SmallTextureUpscale(const char* value);
|
||||
|
||||
// Reports ETC2/EAC support on hosts without native ETC2 (desktop GPUs) and
|
||||
// stores those textures decoded. Images are created with the host format;
|
||||
// each staged upload into one is redirected to a host-visible staging buffer
|
||||
// that is filled by decoding the application's staging bytes when the
|
||||
// command buffer is submitted. Every submit decodes again, as a native copy
|
||||
// re-reads its source on each execution.
|
||||
// Staging buffers live until their command buffer is begun, reset or freed.
|
||||
// MOCKTAIL_DISABLE_ETC2_EMULATION=1 turns the emulation off.
|
||||
class VulkanEtc2Emulation final {
|
||||
public:
|
||||
VulkanEtc2Emulation();
|
||||
@@ -37,6 +62,7 @@ class VulkanEtc2Emulation final {
|
||||
VulkanEtc2Emulation(const VulkanEtc2Emulation&) = delete;
|
||||
VulkanEtc2Emulation& operator=(const VulkanEtc2Emulation&) = delete;
|
||||
|
||||
// True when this physical device lacks native ETC2 and emulation is on.
|
||||
bool PhysicalDeviceNeedsEmulation(VkPhysicalDevice physical_device,
|
||||
PFN_vkGetPhysicalDeviceFeatures host);
|
||||
|
||||
@@ -85,10 +111,13 @@ class VulkanEtc2Emulation final {
|
||||
std::uint32_t count,
|
||||
const VkCommandBuffer* secondaries);
|
||||
|
||||
// Resubmission must reread the source bytes.
|
||||
// Decodes every upload recorded into these command buffers and the
|
||||
// secondary command buffers they execute. Large batches decode on worker
|
||||
// threads; this returns once every upload is decoded.
|
||||
void PrepareSubmit(const VkCommandBuffer* command_buffers,
|
||||
std::uint32_t count);
|
||||
// Call only after the command buffer is no longer pending.
|
||||
// Destroys the staging buffers owned by a command buffer that is no longer
|
||||
// pending.
|
||||
void ReleaseCommandBuffer(VkCommandBuffer command_buffer);
|
||||
|
||||
struct State;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
#ifndef MOCKTAIL_PLATFORM_SDL_APPLICATION_METADATA_H_
|
||||
#define MOCKTAIL_PLATFORM_SDL_APPLICATION_METADATA_H_
|
||||
|
||||
@@ -6,9 +7,9 @@
|
||||
namespace mocktail {
|
||||
namespace platform {
|
||||
|
||||
inline constexpr char kMocktailApplicationName[] = "Mocktail";
|
||||
inline constexpr char kMocktailApplicationName[] = "Nightcap";
|
||||
inline constexpr char kMocktailApplicationIdentifier[] =
|
||||
"space.bigrat.mocktail";
|
||||
"io.github.CoderDayton.nightcap";
|
||||
|
||||
// Configures the stable compositor identity before SDL initializes. The
|
||||
// identifier matches the installed desktop file and icon name so Wayland and
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
namespace mocktail {
|
||||
namespace runtime {
|
||||
|
||||
// Android ActivityManager memory values published through DeviceParams.
|
||||
// Roblox derives its device RAM tier, and tiered render budgets, from them.
|
||||
struct DeviceMemoryProfile {
|
||||
std::int32_t total_memory_mb = 0;
|
||||
std::int32_t memory_class_mb = 0;
|
||||
@@ -15,6 +17,9 @@ struct DeviceMemoryProfile {
|
||||
bool low_ram_device = false;
|
||||
};
|
||||
|
||||
// Hosts with at least 4 GiB report their real total memory as a normal-RAM
|
||||
// device. Smaller hosts, or an unreadable total (0), keep the 2 GiB low-RAM
|
||||
// profile.
|
||||
DeviceMemoryProfile BuildDeviceMemoryProfile(std::uint64_t host_memory_bytes);
|
||||
|
||||
} // namespace runtime
|
||||
|
||||
@@ -7,17 +7,22 @@
|
||||
namespace mocktail {
|
||||
namespace runtime {
|
||||
|
||||
// Host display or window size in physical pixels, published to the Android
|
||||
// guest. Header-only so the JNI VM can use it without link dependencies.
|
||||
struct DisplaySize {
|
||||
std::int32_t width = 1920;
|
||||
std::int32_t height = 1080;
|
||||
};
|
||||
|
||||
// Physical pixels; window size is captured at startup.
|
||||
// Environment handoffs written after the host window exists, as
|
||||
// "WIDTHxHEIGHT": the window's display mode and the window's initial pixels.
|
||||
inline constexpr const char* kDisplaySizeEnvironment =
|
||||
"MOCKTAIL_DISPLAY_SIZE_INTERNAL";
|
||||
inline constexpr const char* kWindowSizeEnvironment =
|
||||
"MOCKTAIL_WINDOW_SIZE_INTERNAL";
|
||||
|
||||
// Parses "WIDTHxHEIGHT". A null, malformed or non-positive value, or one that
|
||||
// does not fit a Java int, yields the 1920x1080 fallback.
|
||||
inline DisplaySize ParseDisplaySize(const char* value) {
|
||||
const DisplaySize fallback;
|
||||
if (value == nullptr) {
|
||||
@@ -55,6 +60,8 @@ inline DisplaySize ParseDisplaySize(const char* value) {
|
||||
static_cast<std::int32_t>(parts[1])};
|
||||
}
|
||||
|
||||
// Physical length of a pixel span at the guest's fixed 160 dpi, rounded down
|
||||
// to whole millimetres.
|
||||
inline std::int32_t PixelsToMillimetersAt160Dpi(std::int32_t pixels) {
|
||||
return static_cast<std::int32_t>(static_cast<std::int64_t>(pixels) * 254 /
|
||||
1600);
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Local git hooks. Install once with: lefthook install
|
||||
# Mirrors the "Repository contracts" job in .github/workflows/ci.yml.
|
||||
|
||||
pre-commit:
|
||||
parallel: true
|
||||
commands:
|
||||
shell-syntax:
|
||||
glob: "{scripts,tests,packaging}/**/*.sh"
|
||||
run: for f in {staged_files}; do bash -n "$f" || exit 1; done
|
||||
json:
|
||||
glob: "{config,packaging}/**/*.json"
|
||||
run: for f in {staged_files}; do jq empty "$f" || exit 1; done
|
||||
desktop-entry:
|
||||
glob: "*.desktop"
|
||||
run: desktop-file-validate {staged_files}
|
||||
|
||||
pre-push:
|
||||
commands:
|
||||
packaging-contracts:
|
||||
run: >-
|
||||
bash tests/managed_payload_build_contract_test.sh . &&
|
||||
bash tests/flatpak_packaging_test.sh . &&
|
||||
bash tests/native_packaging_test.sh .
|
||||
@@ -0,0 +1,12 @@
|
||||
# Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
# pkgforge-dev/archlinux-pkgs-debloated, continuous release, 2026-09-15.
|
||||
# These assets are rolling. Refresh names/hashes together; do not bypass a
|
||||
# checksum mismatch. Only the disposable AppImage build container installs them.
|
||||
eb87771dabc819b318e0ddf94e7ea175a662c1b6fbe748661126c520a0fd4627 ffmpeg-mini-x86_64.pkg.tar.zst
|
||||
e62d30173cc87d3a717a218610c9291b9f647b29b99ee86a0a591226c39a0728 gdk-pixbuf2-mini-x86_64.pkg.tar.zst
|
||||
ca18bc00a343650cde3d674135fa482b54faa639017f82792906db8c68ca9fb6 gtk4-mini-x86_64.pkg.tar.zst
|
||||
8500f31bb0f793d159d58f2e428594baba63f0c1a0276394c5a3f1ac1da422ef icu-mini-x86_64.pkg.tar.zst
|
||||
3d51ee49d0e49a065971703b3f61b610bd1c307cd5165ec16846d75f3efe4a08 librsvg-mini-x86_64.pkg.tar.zst
|
||||
fd52222372202ac7d37ff3f6ef0a47b0decee224aab37ac2b24086fb623b8b86 libxml2-mini-x86_64.pkg.tar.zst
|
||||
da5598b3f8e3a33ba0811c225752466a1abd3821053a19b480410937454feb04 mesa-mini-x86_64.pkg.tar.zst
|
||||
ca23eed050345d8c3f45a21610f91aecc273569b09d8d6eed574014e2e560d89 opus-mini-x86_64.pkg.tar.zst
|
||||
@@ -1,12 +0,0 @@
|
||||
# Allowed reduced packages from pkgforge-dev/archlinux-pkgs-debloated.
|
||||
# continuous is rolling: the installer snapshots GitHub asset IDs and SHA256
|
||||
# digests together at build time, and verifies every archive before installation.
|
||||
# Only the disposable AppImage build container installs these packages.
|
||||
ffmpeg-mini-x86_64.pkg.tar.zst
|
||||
gdk-pixbuf2-mini-x86_64.pkg.tar.zst
|
||||
gtk4-mini-x86_64.pkg.tar.zst
|
||||
icu-mini-x86_64.pkg.tar.zst
|
||||
librsvg-mini-x86_64.pkg.tar.zst
|
||||
libxml2-mini-x86_64.pkg.tar.zst
|
||||
mesa-mini-x86_64.pkg.tar.zst
|
||||
opus-mini-x86_64.pkg.tar.zst
|
||||
@@ -16,7 +16,6 @@ depends=(
|
||||
'libelf'
|
||||
'libglvnd'
|
||||
'libplacebo'
|
||||
'libpng'
|
||||
'libutf8proc'
|
||||
'libyaml'
|
||||
'minizip'
|
||||
|
||||
@@ -17,7 +17,6 @@ pkgbase = mocktail-bin
|
||||
depends = libelf
|
||||
depends = libglvnd
|
||||
depends = libplacebo
|
||||
depends = libpng
|
||||
depends = libsoup3
|
||||
depends = libutf8proc
|
||||
depends = libyaml
|
||||
|
||||
@@ -20,7 +20,6 @@ depends=(
|
||||
'libelf'
|
||||
'libglvnd'
|
||||
'libplacebo'
|
||||
'libpng'
|
||||
'libsoup3'
|
||||
'libutf8proc'
|
||||
'libyaml'
|
||||
|
||||
@@ -24,7 +24,6 @@ pkgbase = mocktail-git
|
||||
depends = libelf
|
||||
depends = libglvnd
|
||||
depends = libplacebo
|
||||
depends = libpng
|
||||
depends = libsoup3
|
||||
depends = libutf8proc
|
||||
depends = libyaml
|
||||
|
||||
@@ -20,7 +20,6 @@ depends=(
|
||||
'libelf'
|
||||
'libglvnd'
|
||||
'libplacebo'
|
||||
'libpng'
|
||||
'libsoup3'
|
||||
'libutf8proc'
|
||||
'libyaml'
|
||||
|
||||
@@ -24,7 +24,6 @@ pkgbase = mocktail
|
||||
depends = libelf
|
||||
depends = libglvnd
|
||||
depends = libplacebo
|
||||
depends = libpng
|
||||
depends = libsoup3
|
||||
depends = libutf8proc
|
||||
depends = libyaml
|
||||
|
||||
@@ -20,7 +20,6 @@ depends=(
|
||||
'libelf'
|
||||
'libglvnd'
|
||||
'libplacebo'
|
||||
'libpng'
|
||||
'libsoup3'
|
||||
'libutf8proc'
|
||||
'libyaml'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"app-id": "space.bigrat.mocktail",
|
||||
"app-id": "io.github.CoderDayton.nightcap",
|
||||
"runtime": "org.gnome.Platform",
|
||||
"runtime-version": "50",
|
||||
"sdk": "org.gnome.Sdk",
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- Modified by vii from komaruworld/mocktail. See the top-level README "About this fork". -->
|
||||
# FreeBSD Linuxulator
|
||||
|
||||
FreeBSD Linuxulator support is experimental. I tested it on FreeBSD
|
||||
@@ -15,7 +16,7 @@ tested source revision is `aadd58dddcbc78f4d5594827b46b5633552b15ce`. Run
|
||||
the following commands as root.
|
||||
|
||||
```sh
|
||||
fetch -o /root/linuxulator.patch https://raw.githubusercontent.com/komaruworld/mocktail/main/packaging/freebsd/linuxulator.patch
|
||||
fetch -o /root/linuxulator.patch https://raw.githubusercontent.com/CoderDayton/nightcap/main/packaging/freebsd/linuxulator.patch
|
||||
cd /usr/src
|
||||
git apply /root/linuxulator.patch
|
||||
make -j2 kernel-toolchain
|
||||
@@ -40,11 +41,10 @@ After reboot, verify the result from the Fedora userspace. It must print
|
||||
chroot /compat/linux stat -f -c %t /proc
|
||||
```
|
||||
|
||||
## Run Mocktail
|
||||
## Run Nightcap
|
||||
|
||||
You can simply run the Mocktail AppImage from the
|
||||
[latest release](https://github.com/komaruworld/mocktail/releases/latest) or
|
||||
[nightly build](https://github.com/komaruworld/mocktail/releases/tag/continuous)
|
||||
You can simply run the Nightcap AppImage from the
|
||||
[latest release](https://github.com/CoderDayton/nightcap/releases/latest)
|
||||
inside the Fedora userspace.
|
||||
|
||||
## Audio
|
||||
@@ -72,11 +72,11 @@ ctl.!default {
|
||||
EOF
|
||||
```
|
||||
|
||||
Launch Mocktail with the ALSA audio driver.
|
||||
Launch Nightcap with the ALSA audio driver.
|
||||
|
||||
```sh
|
||||
SDL_AUDIO_DRIVER=alsa ./Mocktail-x86_64.AppImage
|
||||
SDL_AUDIO_DRIVER=alsa ./Nightcap-x86_64.AppImage
|
||||
```
|
||||
|
||||
Mocktail cannot override this check because Roblox reads `/proc` directly. The
|
||||
Nightcap cannot override this check because Roblox reads `/proc` directly. The
|
||||
Linuxulator patch fixes the value before Roblox sees it.
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 538 B |
|
Before Width: | Height: | Size: 617 B |
|
After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 725 B |
|
Before Width: | Height: | Size: 903 B |
|
After Width: | Height: | Size: 771 B |
|
Before Width: | Height: | Size: 1014 B |
|
After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
@@ -0,0 +1,13 @@
|
||||
# Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Nightcap
|
||||
GenericName=Roblox Player
|
||||
Comment=Play Roblox on Linux
|
||||
Exec=env SDL_VIDEODRIVER=wayland,x11 mocktail %u
|
||||
Icon=io.github.CoderDayton.nightcap
|
||||
StartupWMClass=io.github.CoderDayton.nightcap
|
||||
Categories=Game;
|
||||
Terminal=false
|
||||
MimeType=x-scheme-handler/roblox;x-scheme-handler/roblox-player;
|
||||
X-Mocktail-Managed=true
|
||||
@@ -1,20 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Modified by vii from komaruworld/mocktail. See README "About this fork". -->
|
||||
<!-- Copyright 2026 Mocktail Project Authors -->
|
||||
<!-- SPDX-License-Identifier: CC0-1.0 -->
|
||||
<component type="desktop-application">
|
||||
<id>space.bigrat.mocktail</id>
|
||||
<name>Mocktail</name>
|
||||
<id>io.github.CoderDayton.nightcap</id>
|
||||
<name>Nightcap</name>
|
||||
<summary>Play Roblox on Linux</summary>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>Apache-2.0</project_license>
|
||||
<developer id="space.bigrat">
|
||||
<name>Mocktail Project Authors</name>
|
||||
<developer id="io.github.coderdayton">
|
||||
<name>vii</name>
|
||||
</developer>
|
||||
<branding>
|
||||
<color type="primary" scheme_preference="light">#CEB3EF</color>
|
||||
<color type="primary" scheme_preference="dark">#2F2A6B</color>
|
||||
</branding>
|
||||
<url type="homepage">https://github.com/komaruworld/mocktail</url>
|
||||
<url type="homepage">https://github.com/CoderDayton/nightcap</url>
|
||||
<description>
|
||||
<p>
|
||||
Sign in to Roblox, browse experiences, and play them on Linux.
|
||||
@@ -38,7 +39,7 @@
|
||||
<caption>Play DOORS on Linux</caption>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<launchable type="desktop-id">space.bigrat.mocktail.desktop</launchable>
|
||||
<launchable type="desktop-id">io.github.CoderDayton.nightcap.desktop</launchable>
|
||||
<content_rating type="oars-1.1">
|
||||
<content_attribute id="violence-cartoon">moderate</content_attribute>
|
||||
<content_attribute id="violence-fantasy">moderate</content_attribute>
|
||||
@@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Modified by vii from komaruworld/mocktail. See README "About this fork". -->
|
||||
<!-- Copyright 2026 Mocktail Project Authors -->
|
||||
<!-- SPDX-License-Identifier: Apache-2.0 -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -6,7 +7,7 @@
|
||||
height="512"
|
||||
viewBox="96 96 320 320"
|
||||
fill="none">
|
||||
<title>Mocktail</title>
|
||||
<title>Nightcap</title>
|
||||
<mask id="mocktail-mark"
|
||||
mask-type="alpha"
|
||||
maskUnits="userSpaceOnUse"
|
||||
@@ -19,12 +20,12 @@
|
||||
</mask>
|
||||
<g mask="url(#mocktail-mark)">
|
||||
<path d="M461.343 267.246L461.165 265.65L444.708 108.145C444.345 105.047 443.009 102.143 440.894 99.8515C438.769 97.5865 436.001 96.028 432.962 95.3864C429.924 94.7448 426.761 95.051 423.903 96.2637C421.056 97.5364 418.67 99.6554 417.07 102.332L356.5 146L206 108.145L193.521 42.4325C193.5 39.3045 192.493 36.2626 190.644 33.7398C188.774 31.2601 186.189 29.4138 183.237 28.4502C180.285 27.4866 177.108 27.4521 174.135 28.3513C171.161 29.2864 168.555 31.1314 166.685 33.6262L73.5931 161.71L72.6616 163.009C59.2667 181.485 52.2413 203.814 52.6444 226.631C53.0476 249.448 60.8576 271.515 74.8967 289.505L75.0459 289.702L75.453 290.192L142.096 374.97L174.978 417.241L195.02 442.943C197.361 445.962 200.625 448.131 204.315 449.12C208.005 450.109 211.918 449.862 215.455 448.418L245.662 436.181L295.274 416.014L395.981 375.674L396.235 375.562C417.35 366.989 435.112 351.794 446.852 332.262C458.593 312.73 463.678 289.915 461.343 267.246Z"
|
||||
fill="#E24329"/>
|
||||
fill="#2F2A6B"/>
|
||||
<path d="M461.403 267.484L461.225 265.887C433.09 264.216 404.933 268.779 378.766 279.25L234.209 337.935C266.844 379.87 295.27 416.324 295.27 416.324L403.5 418L396.231 375.873C417.371 367.306 435.156 352.105 446.91 332.557C458.664 313.009 463.75 290.172 461.403 267.484Z"
|
||||
fill="#FC6D26"/>
|
||||
fill="#6C4FD1"/>
|
||||
<path d="M142.135 375.279L175.016 417.549L195.058 443.252C197.399 446.271 200.664 448.44 204.354 449.429C208.044 450.418 211.956 450.171 215.493 448.727L245.701 436.489L295.313 416.323C295.313 416.323 266.867 379.774 234.21 337.922C185.002 357.838 142.135 375.279 142.135 375.279Z"
|
||||
fill="#FCA326"/>
|
||||
fill="#A78BFA"/>
|
||||
<path d="M138.341 214.829C120.941 192.647 98.8514 174.589 73.6535 161.948L72.722 163.247C59.3272 181.723 52.3017 204.052 52.7049 226.869C53.1081 249.685 60.918 271.752 74.9572 289.743L75.1064 289.94L41.5 337.851L142.156 375.208C142.156 375.208 184.959 357.839 234.232 337.851L138.341 214.829Z"
|
||||
fill="#FC6D26"/>
|
||||
fill="#6C4FD1"/>
|
||||
</g>
|
||||
</svg>
|
||||
@@ -23,7 +23,6 @@
|
||||
elfutils,
|
||||
libglvnd,
|
||||
libplacebo,
|
||||
libpng,
|
||||
libsoup_3,
|
||||
libutf8proc,
|
||||
libyaml,
|
||||
@@ -93,7 +92,6 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
elfutils
|
||||
libglvnd
|
||||
libplacebo
|
||||
libpng
|
||||
libsoup_3
|
||||
libutf8proc
|
||||
libyaml
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Mocktail
|
||||
GenericName=Roblox Player
|
||||
Comment=Play Roblox on Linux
|
||||
Exec=mocktail %u
|
||||
Icon=space.bigrat.mocktail
|
||||
StartupWMClass=space.bigrat.mocktail
|
||||
Categories=Game;
|
||||
Terminal=false
|
||||
MimeType=x-scheme-handler/roblox;x-scheme-handler/roblox-player;
|
||||
X-Mocktail-Managed=true
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
# Copyright 2026 Mocktail Project Authors
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@@ -9,8 +10,8 @@ readonly REPOSITORY="${1:?Flatpak repository path is required}"
|
||||
readonly BUNDLE="${2:?Flatpak bundle path is required}"
|
||||
readonly PUBLIC_KEY="${3:?GPG public key path is required}"
|
||||
readonly OUTPUT="${4:?Pages output path is required}"
|
||||
readonly NATIVE_REPOSITORIES="${5:?Native repositories path is required}"
|
||||
readonly BASE_URL="https://mocktail.bigrat.space"
|
||||
readonly BASE_URL="https://coderdayton.github.io/nightcap"
|
||||
readonly APP_ID="io.github.CoderDayton.nightcap"
|
||||
readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||
readonly PROJECT_DIR="$(dirname -- "${SCRIPT_DIR}")"
|
||||
|
||||
@@ -26,13 +27,6 @@ readonly PROJECT_DIR="$(dirname -- "${SCRIPT_DIR}")"
|
||||
printf 'Flatpak public key is missing or unsafe: %s\n' "${PUBLIC_KEY}" >&2
|
||||
exit 1
|
||||
}
|
||||
[[ -d "${NATIVE_REPOSITORIES}/apt" &&
|
||||
-d "${NATIVE_REPOSITORIES}/rpm" &&
|
||||
-d "${NATIVE_REPOSITORIES}/downloads" &&
|
||||
-s "${NATIVE_REPOSITORIES}/mocktail-packages.gpg" ]] || {
|
||||
printf 'Native repositories are missing: %s\n' "${NATIVE_REPOSITORIES}" >&2
|
||||
exit 1
|
||||
}
|
||||
[[ ! -e "${OUTPUT}" ]] || {
|
||||
printf 'Pages output already exists: %s\n' "${OUTPUT}" >&2
|
||||
exit 1
|
||||
@@ -40,50 +34,39 @@ readonly PROJECT_DIR="$(dirname -- "${SCRIPT_DIR}")"
|
||||
|
||||
mkdir -p -- "${OUTPUT}"
|
||||
cp -a -- "${REPOSITORY}" "${OUTPUT}/repo"
|
||||
install -m 0644 -- "${BUNDLE}" "${OUTPUT}/Mocktail-x86_64.flatpak"
|
||||
install -m 0644 -- "${PUBLIC_KEY}" "${OUTPUT}/mocktail-flatpak.gpg"
|
||||
install -m 0644 -- "${BUNDLE}" "${OUTPUT}/Nightcap-x86_64.flatpak"
|
||||
install -m 0644 -- "${PUBLIC_KEY}" "${OUTPUT}/nightcap-flatpak.gpg"
|
||||
install -m 0644 -- \
|
||||
"${PROJECT_DIR}/packaging/space.bigrat.mocktail.svg" \
|
||||
"${OUTPUT}/mocktail.svg"
|
||||
install -m 0644 -- \
|
||||
"${PROJECT_DIR}/packaging/discord-join.html" \
|
||||
"${OUTPUT}/join.html"
|
||||
install -m 0644 -- "${PROJECT_DIR}/site/index.html" "${OUTPUT}/index.html"
|
||||
install -m 0644 -- "${PROJECT_DIR}/site/styles.css" "${OUTPUT}/styles.css"
|
||||
install -d -- "${OUTPUT}/packaging"
|
||||
install -m 0644 -- \
|
||||
"${PROJECT_DIR}/packaging/space.bigrat.mocktail.svg" \
|
||||
"${OUTPUT}/packaging/space.bigrat.mocktail.svg"
|
||||
cp -a -- "${NATIVE_REPOSITORIES}/apt" "${OUTPUT}/apt"
|
||||
cp -a -- "${NATIVE_REPOSITORIES}/rpm" "${OUTPUT}/rpm"
|
||||
cp -a -- "${NATIVE_REPOSITORIES}/downloads" "${OUTPUT}/downloads"
|
||||
install -m 0644 -- \
|
||||
"${NATIVE_REPOSITORIES}/mocktail-packages.gpg" \
|
||||
"${OUTPUT}/mocktail-packages.gpg"
|
||||
"${PROJECT_DIR}/packaging/${APP_ID}.svg" \
|
||||
"${OUTPUT}/nightcap.svg"
|
||||
touch -- "${OUTPUT}/.nojekyll"
|
||||
|
||||
readonly GPG_KEY="$(base64 --wrap=0 "${PUBLIC_KEY}")"
|
||||
|
||||
cat >"${OUTPUT}/mocktail.flatpakrepo" <<EOF
|
||||
cat >"${OUTPUT}/nightcap.flatpakrepo" <<EOF
|
||||
[Flatpak Repo]
|
||||
Title=Mocktail Nightly
|
||||
Title=Nightcap
|
||||
Url=${BASE_URL}/repo/
|
||||
Homepage=https://github.com/komaruworld/mocktail
|
||||
Comment=Nightly builds of Mocktail
|
||||
Description=Signed x86_64 nightly builds published from the latest Mocktail main branch
|
||||
Icon=${BASE_URL}/mocktail.svg
|
||||
Homepage=https://github.com/CoderDayton/nightcap
|
||||
Comment=Nightcap releases
|
||||
Description=Signed x86_64 release builds of Nightcap
|
||||
Icon=${BASE_URL}/nightcap.svg
|
||||
GPGKey=${GPG_KEY}
|
||||
EOF
|
||||
|
||||
cat >"${OUTPUT}/mocktail.flatpakref" <<EOF
|
||||
cat >"${OUTPUT}/nightcap.flatpakref" <<EOF
|
||||
[Flatpak Ref]
|
||||
Title=Mocktail Nightly
|
||||
Name=space.bigrat.mocktail
|
||||
Title=Nightcap
|
||||
Name=${APP_ID}
|
||||
Branch=stable
|
||||
Url=${BASE_URL}/repo/
|
||||
RuntimeRepo=https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||
Homepage=https://github.com/komaruworld/mocktail
|
||||
Comment=Nightly build from the latest main branch
|
||||
Icon=${BASE_URL}/mocktail.svg
|
||||
Homepage=https://github.com/CoderDayton/nightcap
|
||||
Comment=Roblox on Linux, tuned for a real PC
|
||||
Icon=${BASE_URL}/nightcap.svg
|
||||
GPGKey=${GPG_KEY}
|
||||
IsRuntime=false
|
||||
EOF
|
||||
|
||||
install -m 0644 -- "${PROJECT_DIR}/site/index.html" "${OUTPUT}/index.html"
|
||||
install -m 0644 -- "${PROJECT_DIR}/site/styles.css" "${OUTPUT}/styles.css"
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
# Copyright 2026 Mocktail Project Authors
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@@ -8,7 +9,7 @@ readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||
readonly PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd -P)"
|
||||
|
||||
BUILD_DIR="${PROJECT_ROOT}/build-flatpak"
|
||||
MANIFEST="${PROJECT_ROOT}/packaging/flatpak/space.bigrat.mocktail.json"
|
||||
MANIFEST="${PROJECT_ROOT}/packaging/flatpak/io.github.CoderDayton.nightcap.json"
|
||||
JOBS="${MOCKTAIL_FLATPAK_JOBS:-4}"
|
||||
|
||||
Usage() {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
# Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
# Copyright 2026 Mocktail Project Authors
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
readonly project_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
readonly source_icon="${project_root}/packaging/space.bigrat.mocktail.svg"
|
||||
readonly source_icon="${project_root}/packaging/io.github.CoderDayton.nightcap.svg"
|
||||
readonly output_root="${project_root}/packaging/icons/hicolor"
|
||||
readonly icon_name="space.bigrat.mocktail.png"
|
||||
readonly icon_name="io.github.CoderDayton.nightcap.png"
|
||||
readonly icon_sizes=(16 22 24 32 36 48 64 72 96 128 192 256 512)
|
||||
|
||||
if ! command -v magick >/dev/null 2>&1; then
|
||||
|
||||
@@ -4,122 +4,36 @@
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
AnyLinuxDependencyDie() {
|
||||
printf '[anylinux-dependencies] error: %s\n' "$*" >&2
|
||||
# The reduced packages are intentionally limited to the disposable builder.
|
||||
# Never replace a developer's desktop / graphics packages on the host.
|
||||
[[ -f /.dockerenv || -f /run/.containerenv ]] || {
|
||||
printf 'AnyLinux dependency installation requires a disposable container\n' >&2
|
||||
exit 1
|
||||
}
|
||||
[[ "$(id -u)" == 0 ]] || {
|
||||
printf 'AnyLinux dependency installation requires container root\n' >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
AnyLinuxDependencyRequireContainer() {
|
||||
# Never replace a developer's desktop / graphics packages on the host.
|
||||
[[ -f /.dockerenv || -f /run/.containerenv ]] ||
|
||||
AnyLinuxDependencyDie 'installation requires a disposable container'
|
||||
[[ "$(id -u)" == 0 ]] ||
|
||||
AnyLinuxDependencyDie 'installation requires container root'
|
||||
}
|
||||
|
||||
AnyLinuxDependencySnapshot() {
|
||||
local manifest="$1" release="$2" filename remainder names_json
|
||||
local -a names=()
|
||||
local -A seen=()
|
||||
while read -r filename remainder || [[ -n "${filename}" ]]; do
|
||||
[[ -n "${filename}" && "${filename}" != \#* ]] || continue
|
||||
[[ "${filename}" =~ ^[a-z0-9-]+-mini-x86_64\.pkg\.tar\.zst$ &&
|
||||
-z "${remainder}" && -z "${seen[${filename}]:-}" ]] ||
|
||||
AnyLinuxDependencyDie 'invalid or duplicate dependency manifest entry'
|
||||
seen["${filename}"]=1
|
||||
names+=("${filename}")
|
||||
done <"${manifest}"
|
||||
(( ${#names[@]} > 0 )) || AnyLinuxDependencyDie 'empty dependency manifest'
|
||||
names_json="$(printf '%s\n' "${names[@]}" | jq --raw-input --slurp \
|
||||
'split("\n")[:-1]')"
|
||||
|
||||
# continuous assets are replaced on upstream rebuilds. Resolve all required
|
||||
# names and GitHub-computed digests in one response, then download by asset ID
|
||||
# rather than by rolling filename. These are per-build integrity checks, not
|
||||
# immutable version pins. Never accept absent digests or compute our own trust.
|
||||
jq --exit-status --raw-output --argjson names "${names_json}" \
|
||||
--arg base 'https://api.github.com/repos/pkgforge-dev/archlinux-pkgs-debloated/releases/assets/' '
|
||||
(if .tag_name != "continuous" or (.assets | type) != "array" then
|
||||
error("invalid continuous release metadata")
|
||||
else .assets end) as $assets |
|
||||
$names[] as $name |
|
||||
[$assets[] | select(.name == $name)] as $matches |
|
||||
if ($matches | length) != 1 then
|
||||
error("missing or duplicate dependency asset: " + $name)
|
||||
else $matches[0] end |
|
||||
if .state != "uploaded" or (.id | type) != "number" or
|
||||
.id <= 0 or .id != (.id | floor) or
|
||||
(.digest | type) != "string" then
|
||||
error("invalid dependency asset metadata: " + $name)
|
||||
elif (.digest | test("^sha256:[0-9a-f]{64}$") | not) or
|
||||
.url != ($base + (.id | tostring)) then
|
||||
error("invalid dependency digest or URL: " + $name)
|
||||
else
|
||||
[(.digest | ltrimstr("sha256:")), $name, .url] | @tsv
|
||||
end
|
||||
' "${release}" || AnyLinuxDependencyDie 'cannot resolve verified dependency assets'
|
||||
}
|
||||
|
||||
AnyLinuxDownloadDependencies() {
|
||||
local manifest="$1" downloads="$2" attempt digest filename url failed
|
||||
local -a curl_options=(
|
||||
--fail --silent --show-error --location --retry 3
|
||||
--proto '=https' --proto-redir '=https'
|
||||
--connect-timeout 15 --max-time 180
|
||||
--header 'X-GitHub-Api-Version: 2022-11-28'
|
||||
)
|
||||
if [[ -n "${GH_TOKEN:-}" ]]; then
|
||||
curl_options+=(--header "Authorization: Bearer ${GH_TOKEN}")
|
||||
fi
|
||||
for attempt in 1 2 3; do
|
||||
curl "${curl_options[@]}" --header 'Accept: application/vnd.github+json' \
|
||||
--output "${downloads}/release.json" \
|
||||
https://api.github.com/repos/pkgforge-dev/archlinux-pkgs-debloated/releases/tags/continuous ||
|
||||
AnyLinuxDependencyDie 'cannot fetch upstream release metadata'
|
||||
AnyLinuxDependencySnapshot "${manifest}" "${downloads}/release.json" \
|
||||
>"${downloads}/dependencies.tsv"
|
||||
: >"${downloads}/SHA256SUMS"
|
||||
failed=false
|
||||
while IFS=$'\t' read -r digest filename url; do
|
||||
printf '[anylinux-dependencies] downloading %s (SHA256 %s)\n' \
|
||||
"${filename}" "${digest}" >&2
|
||||
if ! curl "${curl_options[@]}" --header 'Accept: application/octet-stream' \
|
||||
--output "${downloads}/${filename}" "${url}"; then
|
||||
failed=true
|
||||
break
|
||||
fi
|
||||
printf '%s %s\n' "${digest}" "${filename}" >>"${downloads}/SHA256SUMS"
|
||||
done <"${downloads}/dependencies.tsv"
|
||||
if [[ "${failed}" == true ]]; then
|
||||
# An upstream upload can delete an ID between resolution and download.
|
||||
# Retry a complete snapshot, never mix old checksums with new filenames.
|
||||
printf '[anylinux-dependencies] download failed; refreshing snapshot (%s/3)\n' \
|
||||
"${attempt}" >&2
|
||||
continue
|
||||
fi
|
||||
(cd -- "${downloads}" && sha256sum --check --strict SHA256SUMS) ||
|
||||
AnyLinuxDependencyDie 'dependency checksum verification failed'
|
||||
return 0
|
||||
done
|
||||
AnyLinuxDependencyDie 'dependency downloads failed after 3 snapshots'
|
||||
}
|
||||
|
||||
AnyLinuxDependenciesMain() (
|
||||
AnyLinuxDependencyRequireContainer
|
||||
(( $# == 0 )) || AnyLinuxDependencyDie 'no arguments are supported'
|
||||
readonly ROOT="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
readonly MANIFEST="${ROOT}/packaging/anylinux-dependencies.txt"
|
||||
readonly DOWNLOADS="$(mktemp -d "${TMPDIR:-/tmp}/mocktail-debloated.XXXXXX")"
|
||||
trap 'rm -rf -- "${DOWNLOADS}"' EXIT
|
||||
AnyLinuxDownloadDependencies "${MANIFEST}" "${DOWNLOADS}"
|
||||
local digest filename url
|
||||
local -a packages=()
|
||||
while IFS=$'\t' read -r digest filename url; do
|
||||
packages+=("${DOWNLOADS}/${filename}")
|
||||
done <"${DOWNLOADS}/dependencies.tsv"
|
||||
pacman -U --noconfirm --needed "${packages[@]}"
|
||||
)
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
||||
AnyLinuxDependenciesMain "$@"
|
||||
fi
|
||||
readonly ROOT="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
readonly MANIFEST="${ROOT}/packaging/anylinux-dependencies.sha256"
|
||||
readonly DOWNLOADS="$(mktemp -d "${TMPDIR:-/tmp}/mocktail-debloated.XXXXXX")"
|
||||
trap 'rm -rf -- "${DOWNLOADS}"' EXIT
|
||||
readonly BASE_URL=https://github.com/pkgforge-dev/archlinux-pkgs-debloated/releases/download/continuous
|
||||
declare -a packages=()
|
||||
while read -r digest filename remainder; do
|
||||
[[ -n "${digest}" && "${digest}" != \#* ]] || continue
|
||||
[[ "${digest}" =~ ^[0-9a-f]{64}$ &&
|
||||
"${filename}" =~ ^[a-z0-9-]+-x86_64\.pkg\.tar\.zst$ &&
|
||||
-z "${remainder}" ]] || {
|
||||
printf 'Invalid AnyLinux dependency manifest entry\n' >&2
|
||||
exit 1
|
||||
}
|
||||
curl --fail --location --retry 3 --output "${DOWNLOADS}/${filename}" \
|
||||
"${BASE_URL}/${filename}"
|
||||
printf '%s %s\n' "${digest}" "${DOWNLOADS}/${filename}" |
|
||||
sha256sum --check --strict
|
||||
packages+=("${DOWNLOADS}/${filename}")
|
||||
done <"${MANIFEST}"
|
||||
(( ${#packages[@]} > 0 ))
|
||||
pacman -U --noconfirm --needed "${packages[@]}"
|
||||
@@ -119,7 +119,6 @@ libyaml
|
||||
fontconfig
|
||||
minizip
|
||||
libplacebo
|
||||
libpng
|
||||
util-linux'
|
||||
INSTALL_ARGS='-S --needed --noconfirm'
|
||||
;;
|
||||
@@ -145,7 +144,6 @@ libyaml
|
||||
fontconfig
|
||||
minizip
|
||||
libplacebo
|
||||
libpng
|
||||
util-linux'
|
||||
if HostUsesMusl; then
|
||||
PACKAGES="$PACKAGES gcompat"
|
||||
@@ -174,7 +172,6 @@ libyaml
|
||||
fontconfig
|
||||
minizip
|
||||
libplacebo
|
||||
libpng
|
||||
coreutils
|
||||
findutils
|
||||
util-linux'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
# Copyright 2026 Mocktail Project Authors
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@@ -152,8 +153,8 @@ AnyLinuxDeploy() {
|
||||
local -a deployment_environment=(
|
||||
"APPDIR=${app_dir}" "MAIN_BIN=mocktail" "LIB_DIR=/usr/lib"
|
||||
"TMPDIR=${ANYLINUX_WORK}/tmp"
|
||||
"DESKTOP=/usr/share/applications/space.bigrat.mocktail.desktop"
|
||||
"ICON=/usr/share/icons/hicolor/scalable/apps/space.bigrat.mocktail.svg"
|
||||
"DESKTOP=/usr/share/applications/io.github.CoderDayton.nightcap.desktop"
|
||||
"ICON=/usr/share/icons/hicolor/scalable/apps/io.github.CoderDayton.nightcap.svg"
|
||||
"DEPLOY_DATADIR=0" "DEPLOY_LOCALE=0" "DEPLOY_VULKAN=0"
|
||||
"STRACE_MODE=1" "STRACE_BINARY=mocktail" "STRACE_FLAGS=--help"
|
||||
"STRACE_TIME=1"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
# Copyright 2026 Mocktail Project Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -920,25 +921,25 @@ BuildAppImage() {
|
||||
ln -s "share/mocktail-bundle/run.sh" "${APPDIR}/usr/mocktail"
|
||||
install -m 0755 -- "${PROJECT_ROOT}/packaging/AppRun" "${APPDIR}/AppRun"
|
||||
install -m 0644 -- \
|
||||
"${PROJECT_ROOT}/packaging/space.bigrat.mocktail.desktop" \
|
||||
"${APPDIR}/space.bigrat.mocktail.desktop"
|
||||
"${PROJECT_ROOT}/packaging/io.github.CoderDayton.nightcap.desktop" \
|
||||
"${APPDIR}/io.github.CoderDayton.nightcap.desktop"
|
||||
install -m 0644 -- \
|
||||
"${PROJECT_ROOT}/packaging/space.bigrat.mocktail.desktop" \
|
||||
"${APPDIR}/usr/share/applications/space.bigrat.mocktail.desktop"
|
||||
"${PROJECT_ROOT}/packaging/io.github.CoderDayton.nightcap.desktop" \
|
||||
"${APPDIR}/usr/share/applications/io.github.CoderDayton.nightcap.desktop"
|
||||
install -m 0644 -- \
|
||||
"${PROJECT_ROOT}/packaging/space.bigrat.mocktail.svg" \
|
||||
"${APPDIR}/space.bigrat.mocktail.svg"
|
||||
"${PROJECT_ROOT}/packaging/io.github.CoderDayton.nightcap.svg" \
|
||||
"${APPDIR}/io.github.CoderDayton.nightcap.svg"
|
||||
install -m 0644 -- \
|
||||
"${PROJECT_ROOT}/packaging/space.bigrat.mocktail.svg" \
|
||||
"${APPDIR}/usr/share/icons/hicolor/scalable/apps/space.bigrat.mocktail.svg"
|
||||
"${PROJECT_ROOT}/packaging/io.github.CoderDayton.nightcap.svg" \
|
||||
"${APPDIR}/usr/share/icons/hicolor/scalable/apps/io.github.CoderDayton.nightcap.svg"
|
||||
for icon_path in \
|
||||
"${PROJECT_ROOT}"/packaging/icons/hicolor/*x*/apps/space.bigrat.mocktail.png; do
|
||||
"${PROJECT_ROOT}"/packaging/icons/hicolor/*x*/apps/io.github.CoderDayton.nightcap.png; do
|
||||
[[ -f "${icon_path}" ]] || Die "hicolor icon set is incomplete"
|
||||
icon_relative_path="${icon_path#"${PROJECT_ROOT}/packaging/icons/hicolor/"}"
|
||||
install -D -m 0644 -- "${icon_path}" \
|
||||
"${APPDIR}/usr/share/icons/hicolor/${icon_relative_path}"
|
||||
done
|
||||
ln -s space.bigrat.mocktail.svg "${APPDIR}/.DirIcon"
|
||||
ln -s io.github.CoderDayton.nightcap.svg "${APPDIR}/.DirIcon"
|
||||
mkdir -p -- "$(dirname -- "${APPIMAGE_OUTPUT}")"
|
||||
ARCH=x86_64 appimagetool "${APPDIR}" "${APPIMAGE_OUTPUT}"
|
||||
rm -rf -- "${APPDIR}"
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
# Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
# Copyright 2026 Mocktail Project Authors
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
set -Eeuo pipefail
|
||||
umask 077
|
||||
|
||||
readonly DESKTOP_ID="space.bigrat.mocktail.desktop"
|
||||
readonly DESKTOP_ID="io.github.CoderDayton.nightcap.desktop"
|
||||
readonly ROBLOX_SCHEME="x-scheme-handler/roblox"
|
||||
readonly ROBLOX_PLAYER_SCHEME="x-scheme-handler/roblox-player"
|
||||
|
||||
@@ -139,8 +140,9 @@ desktop_source="$(ResolveRegularFile "${desktop_source}" "desktop template")"
|
||||
|
||||
[[ "$(grep -Fxc '[Desktop Entry]' "${desktop_source}")" == 1 &&
|
||||
"$(grep -Fxc 'Type=Application' "${desktop_source}")" == 1 &&
|
||||
"$(grep -Fxc 'Icon=space.bigrat.mocktail' "${desktop_source}")" == 1 &&
|
||||
"$(grep -Fxc 'Exec=mocktail %u' "${desktop_source}")" == 1 &&
|
||||
"$(grep -Fxc 'Icon=io.github.CoderDayton.nightcap' "${desktop_source}")" == 1 &&
|
||||
"$(grep -Fxc 'Exec=env SDL_VIDEODRIVER=wayland,x11 mocktail %u' \
|
||||
"${desktop_source}")" == 1 &&
|
||||
"$(grep -Fxc 'MimeType=x-scheme-handler/roblox;x-scheme-handler/roblox-player;' \
|
||||
"${desktop_source}")" == 1 &&
|
||||
"$(grep -Fxc 'X-Mocktail-Managed=true' "${desktop_source}")" == 1 &&
|
||||
@@ -173,7 +175,8 @@ temporary="$(mktemp "${applications_dir}/.${DESKTOP_ID}.XXXXXX.desktop")"
|
||||
trap 'rm -f -- "${temporary}"' EXIT
|
||||
while IFS= read -r line || [[ -n "${line}" ]]; do
|
||||
if [[ "${line}" == Exec=* ]]; then
|
||||
printf 'Exec=%s %%u\n' "${mocktail_executable}"
|
||||
printf 'Exec=env SDL_VIDEODRIVER=wayland,x11 %s %%u\n' \
|
||||
"${mocktail_executable}"
|
||||
if [[ -n "${launch_working_directory}" ]]; then
|
||||
printf 'Path=%s\n' "${launch_working_directory}"
|
||||
fi
|
||||
|
||||
@@ -1,70 +1,49 @@
|
||||
<!doctype html>
|
||||
<!-- Modified by vii from komaruworld/mocktail. See README "About this fork". -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="color-scheme" content="dark">
|
||||
<title>Mocktail for Linux</title>
|
||||
<title>Nightcap for Linux</title>
|
||||
<link rel="icon" href="nightcap.svg" type="image/svg+xml">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body><main>
|
||||
<img src="../packaging/space.bigrat.mocktail.svg" alt="Mocktail logo">
|
||||
<h1>Mocktail</h1>
|
||||
<p>Play Roblox on Linux. Use the stable Flathub release, or try the latest nightly build from main.</p>
|
||||
<a class="button" href="https://flathub.org/apps/space.bigrat.mocktail">Stable on Flathub</a>
|
||||
<a class="button secondary" href="https://mocktail.bigrat.space/mocktail.flatpakref">Nightly build</a>
|
||||
<img src="nightcap.svg" alt="Nightcap logo">
|
||||
<h1>Nightcap</h1>
|
||||
<p>Play Roblox on Linux like it was made for your PC. Nightcap is Mocktail, tuned to feel fast and look sharp.</p>
|
||||
<a class="button" href="https://github.com/CoderDayton/nightcap/releases/latest/download/Nightcap-x86_64.AppImage">Download AppImage</a>
|
||||
<a class="button secondary" href="nightcap.flatpakref">Install with Flatpak</a>
|
||||
<div class="commands">
|
||||
<p>Stable</p>
|
||||
<code>flatpak install flathub space.bigrat.mocktail</code>
|
||||
<p>Nightly</p>
|
||||
<code>flatpak install --user https://mocktail.bigrat.space/mocktail.flatpakref</code>
|
||||
</div>
|
||||
<h2>AUR packages</h2>
|
||||
<p>Available from the AUR. Choose one package:</p>
|
||||
<p class="links">
|
||||
<a href="https://aur.archlinux.org/packages/mocktail">mocktail</a>
|
||||
<a href="https://aur.archlinux.org/packages/mocktail-bin">mocktail-bin</a>
|
||||
<a href="https://aur.archlinux.org/packages/mocktail-git">mocktail-git</a>
|
||||
</p>
|
||||
<code>yay -S mocktail
|
||||
yay -S mocktail-bin
|
||||
yay -S mocktail-git</code>
|
||||
<h2>DNF repository</h2>
|
||||
<p>Stable and nightly packages for Fedora 44. Choose one package.</p>
|
||||
<code>sudo curl -fsSL https://mocktail.bigrat.space/rpm/mocktail.repo -o /etc/yum.repos.d/mocktail.repo</code>
|
||||
<div class="commands">
|
||||
<p>Stable</p>
|
||||
<code>sudo dnf install mocktail</code>
|
||||
<p>Nightly</p>
|
||||
<code>sudo dnf install mocktail-nightly</code>
|
||||
</div>
|
||||
<h2>APT repository</h2>
|
||||
<p>Stable and nightly packages for Ubuntu 26.04. Choose one package.</p>
|
||||
<code>sudo install -d -m 0755 /etc/apt/keyrings
|
||||
sudo curl -fsSL https://mocktail.bigrat.space/mocktail-packages.gpg -o /etc/apt/keyrings/mocktail.gpg
|
||||
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/mocktail.gpg] https://mocktail.bigrat.space/apt mocktail main" | sudo tee /etc/apt/sources.list.d/mocktail.list >/dev/null
|
||||
sudo apt update</code>
|
||||
<div class="commands">
|
||||
<p>Stable</p>
|
||||
<code>sudo apt install mocktail</code>
|
||||
<p>Nightly</p>
|
||||
<code>sudo apt install mocktail-nightly</code>
|
||||
<p>AppImage</p>
|
||||
<code>chmod +x Nightcap-x86_64.AppImage
|
||||
./Nightcap-x86_64.AppImage</code>
|
||||
<p>Flatpak</p>
|
||||
<code>flatpak install --user https://coderdayton.github.io/nightcap/nightcap.flatpakref
|
||||
flatpak run io.github.CoderDayton.nightcap</code>
|
||||
<p>Build from source</p>
|
||||
<code>git clone --recurse-submodules https://github.com/CoderDayton/nightcap.git
|
||||
cd nightcap && make build
|
||||
./build/mocktail</code>
|
||||
</div>
|
||||
<h2>What's different</h2>
|
||||
<ul>
|
||||
<li>Lower input lag. Frames are shown as soon as they are ready.</li>
|
||||
<li>Sharper textures, with your own PNG overrides if you want them.</li>
|
||||
<li>ETC2 textures render correctly on desktop GPUs.</li>
|
||||
<li>No more error 319 kicks after joining a server.</li>
|
||||
<li>Roblox sees your real RAM and screen size.</li>
|
||||
</ul>
|
||||
<h2>Direct downloads</h2>
|
||||
<p>Prebuilt packages for Linux x86_64.</p>
|
||||
<p class="download-label">Stable</p>
|
||||
<p>Prebuilt builds for Linux x86_64.</p>
|
||||
<p class="links">
|
||||
<a href="https://github.com/komaruworld/mocktail/releases/latest/download/Mocktail-x86_64.AppImage">AppImage</a>
|
||||
<a href="https://mocktail.bigrat.space/downloads/mocktail.deb">DEB</a>
|
||||
<a href="https://mocktail.bigrat.space/downloads/mocktail.rpm">RPM</a>
|
||||
<a href="https://github.com/CoderDayton/nightcap/releases/latest/download/Nightcap-x86_64.AppImage">AppImage</a>
|
||||
<a href="Nightcap-x86_64.flatpak">Flatpak bundle</a>
|
||||
<a href="nightcap-flatpak.gpg">Signing key</a>
|
||||
</p>
|
||||
<p class="download-label">Nightly</p>
|
||||
<p class="links">
|
||||
<a href="https://github.com/komaruworld/mocktail/releases/download/continuous/mocktail-nightly.AppImage">AppImage</a>
|
||||
<a href="https://github.com/komaruworld/mocktail/releases/download/continuous/mocktail-nightly.deb">DEB</a>
|
||||
<a href="https://github.com/komaruworld/mocktail/releases/download/continuous/mocktail-nightly.rpm">RPM</a>
|
||||
</p>
|
||||
<p><a class="source" href="https://github.com/komaruworld/mocktail">Source code</a></p>
|
||||
<p>Mocktail is an independent community project and is not affiliated with Roblox Corporation.</p>
|
||||
<p><a class="source" href="https://github.com/CoderDayton/nightcap">Source code</a> ·
|
||||
<a class="source" href="https://github.com/komaruworld/mocktail">Upstream Mocktail</a></p>
|
||||
<p>Nightcap is an independent community project and is not affiliated with Roblox Corporation.</p>
|
||||
</main></body>
|
||||
</html>
|
||||
@@ -1,3 +1,4 @@
|
||||
/* Modified by vii from komaruworld/mocktail. See README "About this fork". */
|
||||
:root {
|
||||
font: 17px/1.55 system-ui, sans-serif;
|
||||
color-scheme: dark;
|
||||
@@ -5,8 +6,8 @@
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: #0d1117;
|
||||
color: #e6edf3;
|
||||
background: #14122b;
|
||||
color: #ece8ff;
|
||||
}
|
||||
|
||||
main {
|
||||
@@ -30,8 +31,13 @@ h2 {
|
||||
font-size: 1.15rem;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #9da7b3;
|
||||
p,
|
||||
li {
|
||||
color: #b3aad6;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 1.2rem;
|
||||
}
|
||||
|
||||
a.button {
|
||||
@@ -39,14 +45,14 @@ a.button {
|
||||
margin: 16px 8px 16px 0;
|
||||
padding: 12px 18px;
|
||||
border-radius: 9px;
|
||||
background: #238636;
|
||||
background: #6c4fd1;
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.secondary {
|
||||
background: #21262d;
|
||||
background: #2f2a6b;
|
||||
}
|
||||
|
||||
.commands {
|
||||
@@ -56,7 +62,7 @@ a.secondary {
|
||||
.commands p,
|
||||
.download-label {
|
||||
margin: 14px 0 5px;
|
||||
color: #e6edf3;
|
||||
color: #ece8ff;
|
||||
font-size: .82rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: .04em;
|
||||
@@ -67,9 +73,9 @@ code {
|
||||
display: block;
|
||||
padding: 14px;
|
||||
overflow-wrap: anywhere;
|
||||
border: 1px solid #30363d;
|
||||
border: 1px solid #3a3470;
|
||||
border-radius: 6px;
|
||||
background: #161b22;
|
||||
background: #1d1a3d;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@@ -80,9 +86,9 @@ code {
|
||||
.links a {
|
||||
display: inline-block;
|
||||
margin: 0 14px 6px 0;
|
||||
color: #e6edf3;
|
||||
color: #a78bfa;
|
||||
}
|
||||
|
||||
a.source {
|
||||
color: #e6edf3;
|
||||
color: #a78bfa;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Includes changes by vii from CoderDayton/nightcap.
|
||||
// Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
// Android Vulkan loader ABI -> host Vulkan loader + SDL3 WSI.
|
||||
|
||||
#include <dlfcn.h>
|
||||
@@ -780,6 +780,7 @@ bool NameInList(const char* name, const char* const* names,
|
||||
});
|
||||
}
|
||||
|
||||
// Device commands wrapped for ETC2/EAC emulation.
|
||||
bool IsEtc2DeviceProc(const char* name) {
|
||||
static constexpr const char* kNames[] = {
|
||||
"vkCreateImage", "vkDestroyImage",
|
||||
@@ -797,6 +798,7 @@ bool IsEtc2DeviceProc(const char* name) {
|
||||
return NameInList(name, kNames, sizeof(kNames) / sizeof(kNames[0]));
|
||||
}
|
||||
|
||||
// Physical-device queries that report emulated ETC2/EAC support.
|
||||
bool IsEtc2PhysicalDeviceProc(const char* name) {
|
||||
static constexpr const char* kNames[] = {
|
||||
"vkGetPhysicalDeviceFeatures",
|
||||
@@ -1040,6 +1042,7 @@ struct FpsWaitTrace {
|
||||
std::atomic<std::uint64_t> total_ns{0};
|
||||
std::atomic<std::uint64_t> max_ns{0};
|
||||
std::atomic<std::uint64_t> window_start_ns{0};
|
||||
// Longest time between consecutive call starts; for present, a frame time.
|
||||
std::atomic<std::uint64_t> max_gap_ns{0};
|
||||
std::atomic<std::uint64_t> last_start_ns{0};
|
||||
|
||||
@@ -1469,7 +1472,8 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(
|
||||
host_enabled_features.textureCompressionETC2 = VK_FALSE;
|
||||
host_info.pEnabledFeatures = &host_enabled_features;
|
||||
}
|
||||
// Restore the caller-owned feature bit after the host call.
|
||||
// The feature chain is caller-owned; clear the emulated bit only for the
|
||||
// host call and restore it afterwards.
|
||||
requested_features2 = const_cast<VkPhysicalDeviceFeatures2*>(
|
||||
reinterpret_cast<const VkPhysicalDeviceFeatures2*>(FindFeature(
|
||||
create_info->pNext, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2)));
|
||||
|
||||
@@ -40,7 +40,7 @@ int Expand5(int value) { return (value << 3) | (value >> 2); }
|
||||
int Expand6(int value) { return (value << 2) | (value >> 4); }
|
||||
int Expand7(int value) { return (value << 1) | (value >> 6); }
|
||||
|
||||
// Texels use column-major indexing: x * 4 + y.
|
||||
// Decodes one ETC2 RGB block into 16 column-major RGBA texels (x * 4 + y).
|
||||
void DecodeRgbBlock(const std::uint8_t* b, bool punchthrough,
|
||||
std::array<std::uint8_t, 64>* out) {
|
||||
static constexpr int kDelta[8] = {0, 1, 2, 3, -4, -3, -2, -1};
|
||||
@@ -159,6 +159,7 @@ void DecodeRgbBlock(const std::uint8_t* b, bool punchthrough,
|
||||
}
|
||||
}
|
||||
|
||||
// EAC 3-bit index for column-major texel (x * 4 + y).
|
||||
int EacIndex(const std::uint8_t* b, int texel) {
|
||||
std::uint64_t bits = 0;
|
||||
for (int i = 2; i < 8; ++i) {
|
||||
@@ -212,7 +213,9 @@ std::size_t EtcDecodedTexelBytes(EtcFormat format) {
|
||||
|
||||
namespace {
|
||||
|
||||
// Blocks per band handed to one worker: about a 256x256 texel image.
|
||||
constexpr std::uint64_t kBlocksPerBand = 16384;
|
||||
// Batches smaller than a 1024x1024 texel image decode on the caller.
|
||||
constexpr std::uint64_t kParallelMinimumBlocks = 65536;
|
||||
|
||||
bool ImageFits(EtcFormat format, const std::uint8_t* source,
|
||||
@@ -312,6 +315,7 @@ void DecodeEtcJobs(EtcDecodeJob* jobs, std::size_t count,
|
||||
workers.reserve(threads > 0 ? threads - 1 : 0);
|
||||
for (std::size_t index = 1; index < threads; ++index) {
|
||||
pthread_t worker;
|
||||
// The caller decodes whatever workers that failed to start leave behind.
|
||||
if (pthread_create(&worker, nullptr, RunDecodeBandQueue, &queue) != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
#include "mocktail/graphics/present_mode_policy.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -28,10 +29,9 @@ PresentModePolicy ResolvePresentModePolicy(std::string_view vsync_value,
|
||||
if (frame_rate_value == "unlimited") {
|
||||
return PresentModePolicy::kUnthrottled;
|
||||
}
|
||||
if (frame_rate_value.empty() || frame_rate_value == "-1") {
|
||||
return PresentModePolicy::kHostDefault;
|
||||
}
|
||||
// Display or any fixed scheduler target keeps synchronized presentation.
|
||||
// Every synchronized target, including the unmanaged Roblox cap, presents
|
||||
// through the lowest-latency vsync mode the host offers. Roblox's own FIFO
|
||||
// request queues up to two rendered frames before they reach the display.
|
||||
return PresentModePolicy::kVsync;
|
||||
}
|
||||
|
||||
|
||||
@@ -123,6 +123,8 @@ bool ShouldLog(std::atomic<unsigned>* counter, unsigned limit) {
|
||||
return counter->fetch_add(1, std::memory_order_relaxed) < limit;
|
||||
}
|
||||
|
||||
// Decode threads for one submit, the submitting thread included. Half the
|
||||
// host's hardware threads, at most 8, leave cores for the game's own threads.
|
||||
unsigned DecodeWorkerCount() {
|
||||
static const unsigned count =
|
||||
std::min(8U, std::max(1U, std::thread::hardware_concurrency() / 2));
|
||||
@@ -179,10 +181,16 @@ struct Mapping {
|
||||
struct ImageRecord {
|
||||
VkDevice device = VK_NULL_HANDLE;
|
||||
Etc2EmulatedFormat format;
|
||||
// Host image dimensions are the application's times this factor.
|
||||
std::uint32_t scale = 1;
|
||||
// Application-side level-0 extent; mip extents derive from it.
|
||||
std::uint32_t width = 0;
|
||||
std::uint32_t height = 0;
|
||||
// Replacement pixels chosen from the level-0 upload; smaller mip levels
|
||||
// are resampled from it.
|
||||
std::shared_ptr<const RgbaImage> override;
|
||||
// Decoded level-0 texels of a scaled image; every host mip is resampled
|
||||
// from them so the texture keeps its detail at every draw size.
|
||||
std::shared_ptr<const RgbaImage> base;
|
||||
};
|
||||
|
||||
@@ -201,23 +209,25 @@ struct PendingUpload {
|
||||
VkDevice device = VK_NULL_HANDLE;
|
||||
VkBuffer source = VK_NULL_HANDLE;
|
||||
VkDeviceSize source_offset = 0;
|
||||
VkDeviceSize source_row_stride = 0;
|
||||
VkDeviceSize source_layer_stride = 0;
|
||||
VkDeviceSize source_span = 0;
|
||||
VkDeviceSize compressed = 0;
|
||||
VkDeviceSize decoded = 0;
|
||||
VkDeviceSize target_offset = 0;
|
||||
EtcFormat format = EtcFormat::kEtc2Rgb8;
|
||||
VkImage image = VK_NULL_HANDLE;
|
||||
std::uint32_t mip_level = 0;
|
||||
// Application-side region; `decoded` covers width x height texels.
|
||||
std::uint32_t width = 0;
|
||||
std::uint32_t height = 0;
|
||||
std::uint32_t layers = 0;
|
||||
std::uint32_t scale = 1;
|
||||
// Texels of the region inside the application mip (block-rounded
|
||||
// regions overhang it) and the host region they map to, which takes
|
||||
// `target_bytes`.
|
||||
std::uint32_t source_width = 0;
|
||||
std::uint32_t source_height = 0;
|
||||
std::uint32_t target_width = 0;
|
||||
std::uint32_t target_height = 0;
|
||||
// The region covers the whole application mip.
|
||||
bool full_mip = false;
|
||||
VkDeviceSize target_bytes = 0;
|
||||
std::uint8_t* target = nullptr;
|
||||
@@ -295,6 +305,7 @@ bool CreateStaging(const HostDevice& dev, VkDeviceSize size, Staging* staging,
|
||||
return true;
|
||||
}
|
||||
|
||||
// Freeing mapped memory unmaps it implicitly.
|
||||
void DestroyStaging(const HostDevice& dev, const Staging& staging) {
|
||||
if (dev.destroy_buffer != nullptr) {
|
||||
dev.destroy_buffer(dev.device, staging.buffer, nullptr);
|
||||
@@ -304,7 +315,10 @@ void DestroyStaging(const HostDevice& dev, const Staging& staging) {
|
||||
}
|
||||
}
|
||||
|
||||
// Clip block padding before scaling to the host mip.
|
||||
// Maps one axis of an application region at `mip` onto the host image of a
|
||||
// scaled record. A region reaching the application mip's edge reaches the
|
||||
// host mip's edge; block-rounded overhang is dropped rather than scaled.
|
||||
// `texels` receives the application texels inside the mip.
|
||||
bool ScaledAxis(std::uint32_t base, std::uint32_t scale, std::uint32_t mip,
|
||||
std::int32_t offset, std::uint32_t extent,
|
||||
std::uint32_t* texels, std::uint32_t* host_start,
|
||||
@@ -329,6 +343,8 @@ bool ScaledAxis(std::uint32_t base, std::uint32_t scale, std::uint32_t mip,
|
||||
return true;
|
||||
}
|
||||
|
||||
// Host-side bounds of an application region on one image; unscaled images
|
||||
// keep the region as given.
|
||||
bool HostRegion(const ImageRecord* record, std::uint32_t mip,
|
||||
VkOffset3D offset, VkExtent3D extent, VkOffset3D* start,
|
||||
VkOffset3D* end) {
|
||||
@@ -386,23 +402,6 @@ bool PlanRegions(const ImageRecord& record, VkDevice device, VkBuffer source,
|
||||
upload.width = region.imageExtent.width;
|
||||
upload.height = region.imageExtent.height;
|
||||
upload.layers = region.imageSubresource.layerCount;
|
||||
const VkDeviceSize row_texels =
|
||||
region.bufferRowLength != 0 ? region.bufferRowLength : upload.width;
|
||||
const VkDeviceSize layer_rows =
|
||||
region.bufferImageHeight != 0 ? region.bufferImageHeight : upload.height;
|
||||
if (row_texels < upload.width || layer_rows < upload.height) {
|
||||
return false;
|
||||
}
|
||||
const VkDeviceSize block_bytes = EtcBlockBytes(upload.format);
|
||||
const VkDeviceSize block_rows =
|
||||
(static_cast<VkDeviceSize>(upload.height) + 3) / 4;
|
||||
const VkDeviceSize packed_row =
|
||||
((static_cast<VkDeviceSize>(upload.width) + 3) / 4) * block_bytes;
|
||||
upload.source_row_stride = ((row_texels + 3) / 4) * block_bytes;
|
||||
upload.source_layer_stride =
|
||||
upload.source_row_stride * ((layer_rows + 3) / 4);
|
||||
upload.source_span = (upload.layers - 1) * upload.source_layer_stride +
|
||||
(block_rows - 1) * upload.source_row_stride + packed_row;
|
||||
upload.scale = scale;
|
||||
upload.source_width = upload.width;
|
||||
upload.source_height = upload.height;
|
||||
@@ -466,6 +465,7 @@ struct VulkanEtc2Emulation::State {
|
||||
std::unordered_map<VkDeviceMemory, Mapping> mappings;
|
||||
std::unordered_map<VkCommandBuffer, CommandRecord> commands;
|
||||
std::atomic<bool> has_commands{false};
|
||||
// Heap buffers reused across submits; they only grow.
|
||||
std::mutex scratch_mutex;
|
||||
std::vector<std::uint8_t> scratch_source;
|
||||
std::vector<std::uint8_t> scratch_decoded;
|
||||
@@ -473,6 +473,8 @@ struct VulkanEtc2Emulation::State {
|
||||
const std::uint32_t upscale =
|
||||
SmallTextureUpscale(std::getenv("MOCKTAIL_SMALL_TEXTURE_UPSCALE"));
|
||||
|
||||
// Level-0 colour uploads are hashed, dumped and matched against override
|
||||
// files; the match is kept on the image so its mip levels follow.
|
||||
std::shared_ptr<const RgbaImage> FindOverride(const PendingUpload& upload,
|
||||
const std::uint8_t* compressed,
|
||||
const std::uint8_t* decoded) {
|
||||
@@ -500,6 +502,8 @@ struct VulkanEtc2Emulation::State {
|
||||
return replacement;
|
||||
}
|
||||
|
||||
// Writes the host-side texels of one upload: the override or the decoded
|
||||
// texels, resampled to the host region size.
|
||||
void EmitUpload(const PendingUpload& upload, const std::uint8_t* compressed,
|
||||
const std::uint8_t* decoded) {
|
||||
const std::shared_ptr<const RgbaImage> replacement =
|
||||
@@ -995,6 +999,8 @@ void VulkanEtc2Emulation::CmdCopyBufferToImage2(
|
||||
dev->copy_buffer_to_image2(command_buffer, &host_info);
|
||||
}
|
||||
|
||||
// Copies touching a scaled image become blits, so both sides land on their
|
||||
// own host bounds. Returns false when no image involved is scaled.
|
||||
bool VulkanEtc2Emulation::State::BlitScaledCopy(
|
||||
const HostDevice& dev, VkCommandBuffer command_buffer, VkImage source,
|
||||
VkImageLayout source_layout, VkImage destination,
|
||||
@@ -1140,7 +1146,7 @@ void VulkanEtc2Emulation::PrepareSubmit(const VkCommandBuffer* command_buffers,
|
||||
(mapping->second.size == VK_WHOLE_SIZE ||
|
||||
(absolute - mapping->second.offset <=
|
||||
mapping->second.size &&
|
||||
upload.source_span <= mapping->second.size -
|
||||
upload.compressed <= mapping->second.size -
|
||||
(absolute -
|
||||
mapping->second.offset)))) {
|
||||
item.source = static_cast<std::uint8_t*>(mapping->second.data) +
|
||||
@@ -1164,8 +1170,12 @@ void VulkanEtc2Emulation::PrepareSubmit(const VkCommandBuffer* command_buffers,
|
||||
}
|
||||
}
|
||||
}
|
||||
// Decode in cached RAM; mapped Vulkan memory makes scattered reads slow.
|
||||
// Map each source allocation once: Vulkan forbids concurrent mappings.
|
||||
// Mapped Vulkan memory is uncached or write-combined, which makes the
|
||||
// decoder's scattered byte reads and writes tens of times slower than on
|
||||
// heap memory. Compressed bytes are streamed into a heap scratch buffer,
|
||||
// decoded heap to heap, and the result streamed into the staging buffer.
|
||||
// Vulkan forbids mapping one memory object twice, so each unmapped source
|
||||
// memory is mapped once for the whole batch and unmapped after copying.
|
||||
struct TemporaryMapping {
|
||||
const HostDevice* dev = nullptr;
|
||||
std::uint8_t* data = nullptr;
|
||||
@@ -1225,23 +1235,9 @@ void VulkanEtc2Emulation::PrepareSubmit(const VkCommandBuffer* command_buffers,
|
||||
const PendingUpload& upload = *copied_uploads[index];
|
||||
std::uint8_t* source = scratch_source.data() + compressed_offset;
|
||||
std::uint8_t* decoded = scratch_decoded.data() + decoded_offset;
|
||||
std::memcpy(source, copy.source, copy.compressed);
|
||||
const VkDeviceSize compressed_layer = upload.compressed / upload.layers;
|
||||
const VkDeviceSize decoded_layer = upload.decoded / upload.layers;
|
||||
if (upload.source_span == copy.compressed) {
|
||||
std::memcpy(source, copy.source, copy.compressed);
|
||||
} else {
|
||||
const VkDeviceSize block_rows =
|
||||
(static_cast<VkDeviceSize>(upload.height) + 3) / 4;
|
||||
const VkDeviceSize packed_row = compressed_layer / block_rows;
|
||||
for (std::uint32_t layer = 0; layer < upload.layers; ++layer) {
|
||||
for (VkDeviceSize row = 0; row < block_rows; ++row) {
|
||||
std::memcpy(source + layer * compressed_layer + row * packed_row,
|
||||
copy.source + layer * upload.source_layer_stride +
|
||||
row * upload.source_row_stride,
|
||||
packed_row);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (std::uint32_t layer = 0; layer < upload.layers; ++layer) {
|
||||
EtcDecodeJob job;
|
||||
job.format = upload.format;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
#include "jnivm/jnivm.h"
|
||||
|
||||
#include "mocktail/audio/fmod_thread_floating_point.h"
|
||||
@@ -2499,6 +2500,7 @@ void EnsureAndroidObjectGraph() {
|
||||
: PlatformIdentity{};
|
||||
SetIntFieldRaw(configuration, "orientation", 2);
|
||||
SetIntFieldRaw(configuration, "densityDpi", 160);
|
||||
// Density is 1.0, so dp equals window pixels.
|
||||
SetIntFieldRaw(configuration, "screenWidthDp", host_window.width);
|
||||
SetIntFieldRaw(configuration, "screenHeightDp", host_window.height);
|
||||
SetIntFieldRaw(configuration, "smallestScreenWidthDp",
|
||||
@@ -3863,8 +3865,30 @@ jobjectArray MakeObjectArray(jsize len, jobject init) {
|
||||
return ref;
|
||||
}
|
||||
|
||||
// NativeQuoteInterface.requestResponse([B)[B reply when no Android Keystore
|
||||
// exists: status byte 1, attestation-available byte 0, then the error text.
|
||||
// A real device with a keystore answers [1, 1, chain] instead.
|
||||
jbyteArray MakeQuoteUnavailableResponse() {
|
||||
static const char kMessage[] =
|
||||
"java.security.KeyStoreException: AndroidKeyStore is not available";
|
||||
const std::size_t message_length = sizeof(kMessage) - 1;
|
||||
jbyteArray result = MakeByteArray(static_cast<jsize>(2 + message_length));
|
||||
PseudoArray* array = ArrayFromRef(result);
|
||||
if (array == nullptr || array->bytes.size() != 2 + message_length) {
|
||||
return nullptr;
|
||||
}
|
||||
array->bytes[0] = 1;
|
||||
array->bytes[1] = 0;
|
||||
std::memcpy(array->bytes.data() + 2, kMessage, message_length);
|
||||
return result;
|
||||
}
|
||||
|
||||
jobject StaticObjectResultForMethod(jmethodID method_id) {
|
||||
const char* name = MethodName(method_id);
|
||||
if (std::strcmp(name, "requestResponse") == 0 &&
|
||||
std::strcmp(MethodSignature(method_id), "([B)[B") == 0) {
|
||||
return MakeQuoteUnavailableResponse();
|
||||
}
|
||||
const PlatformIdentity identity = CurrentPlatformIdentity();
|
||||
jobject android_object = AndroidObjectForMethod(name);
|
||||
if (android_object) {
|
||||
@@ -4976,6 +5000,7 @@ jobject CreateAndroidConfiguration(JNIEnv* env) {
|
||||
SetIntFieldRaw(configuration, "navigation", 1);
|
||||
SetIntFieldRaw(configuration, "navigationHidden", 1);
|
||||
SetIntFieldRaw(configuration, "orientation", 2);
|
||||
// Density is 1.0, so dp equals window pixels.
|
||||
const mocktail::runtime::DisplaySize config_window =
|
||||
mocktail::runtime::ParseDisplaySize(
|
||||
std::getenv(mocktail::runtime::kWindowSizeEnvironment));
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cctype>
|
||||
@@ -2402,6 +2403,8 @@ mocktail::runtime::DisplaySize HostDisplaySize() {
|
||||
std::getenv(mocktail::runtime::kDisplaySizeEnvironment));
|
||||
}
|
||||
|
||||
// Live window pixels once the host window exists; the initial window size
|
||||
// handoff otherwise.
|
||||
mocktail::runtime::DisplaySize HostWindowPixelSize() {
|
||||
const mocktail::window::WindowViewportSnapshot viewport =
|
||||
mocktail::window::GetWindowViewportSnapshot();
|
||||
@@ -2446,6 +2449,8 @@ jobject BuildDeviceParams(JNIEnv* env) {
|
||||
SetStringField(env, params, "appBuildVariant", "googleProdRelease");
|
||||
SetStringField(env, params, "testDeviceName", device_name.c_str());
|
||||
|
||||
// MOCKTAIL_DISABLE_LOW_RAM_DEVICE forces the normal-RAM profile even when
|
||||
// the host total cannot be read or is below 4 GiB.
|
||||
std::uint64_t host_memory_bytes = mocktail::runtime::DetectHostMemoryBytes();
|
||||
if (IsEnabled("MOCKTAIL_DISABLE_LOW_RAM_DEVICE")) {
|
||||
host_memory_bytes =
|
||||
@@ -4780,7 +4785,8 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options,
|
||||
const SDL_DisplayMode* mode =
|
||||
display != 0 ? SDL_GetCurrentDisplayMode(display) : nullptr;
|
||||
if (mode != nullptr && mode->w > 0 && mode->h > 0) {
|
||||
// SDL mode dimensions need pixel_density to become physical pixels.
|
||||
// SDL display modes are in desktop units; pixel_density maps them to
|
||||
// physical pixels.
|
||||
const float density = mode->pixel_density > 0.0f ? mode->pixel_density
|
||||
: 1.0f;
|
||||
const std::string display_size =
|
||||
|
||||
@@ -18,6 +18,8 @@ namespace mocktail {
|
||||
namespace runtime {
|
||||
namespace {
|
||||
|
||||
// Keep the Vulkan shader workaround narrowly scoped. TextureManager2 is left
|
||||
// enabled so the runtime can use its normal texture path and mip selection.
|
||||
constexpr char kVulkanClientSettingsOverrides[] =
|
||||
R"({"FStringGraphicsVulkanShaderMTDenyPattern":"4318:.*"})";
|
||||
|
||||
|
||||
@@ -244,7 +244,7 @@ struct TextBytePosition {
|
||||
};
|
||||
|
||||
TextBytePosition TextPosition(TTF_Text* layout, std::size_t byte_offset,
|
||||
int text_width, bool single_line) {
|
||||
int text_width) {
|
||||
if (layout == nullptr) {
|
||||
return {};
|
||||
}
|
||||
@@ -259,11 +259,7 @@ TextBytePosition TextPosition(TTF_Text* layout, std::size_t byte_offset,
|
||||
position.y = substring.rect.y;
|
||||
position.height = substring.rect.h;
|
||||
if ((substring.flags & TTF_SUBSTRING_TEXT_END) != 0) {
|
||||
// Trailing whitespace has no ink, so the final cluster rect stops short of
|
||||
// the laid-out width that a caret at the end of a single line sits behind.
|
||||
position.x = single_line ? text_width
|
||||
: std::min(text_width,
|
||||
substring.rect.x + substring.rect.w);
|
||||
position.x = std::min(text_width, substring.rect.x + substring.rect.w);
|
||||
}
|
||||
return position;
|
||||
}
|
||||
@@ -505,12 +501,11 @@ Status RobloxTextSurfaceOverlay::RasterizeLocked() {
|
||||
TextBytePosition selection_end_position;
|
||||
if (layout != nullptr) {
|
||||
(void)TTF_GetTextSize(layout, &text_width, &text_height);
|
||||
caret_position =
|
||||
TextPosition(layout, caret_byte, text_width, !wrapped_layout);
|
||||
selection_begin_position = TextPosition(layout, selection_begin_byte,
|
||||
text_width, !wrapped_layout);
|
||||
caret_position = TextPosition(layout, caret_byte, text_width);
|
||||
selection_begin_position =
|
||||
TextPosition(layout, selection_begin_byte, text_width);
|
||||
selection_end_position =
|
||||
TextPosition(layout, selection_end_byte, text_width, !wrapped_layout);
|
||||
TextPosition(layout, selection_end_byte, text_width);
|
||||
}
|
||||
if (!text.value.empty()) {
|
||||
// SDL_ttf implementations have historically differed on whether fg.a is
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
#include "runtime/runtime_config_bootstrap.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
@@ -64,7 +65,9 @@ graphics:
|
||||
# display, unlimited, or any positive integer; fixed values are forwarded to
|
||||
# DFIntTaskSchedulerTargetFps without a whitelist.
|
||||
# frame_rate_limit: -1
|
||||
# Optional presentation synchronization override: auto, on, or off.
|
||||
# Optional presentation synchronization override: auto, on, or off. auto
|
||||
# presents through the lowest-latency synchronized mode the driver offers
|
||||
# unless frame_rate_limit is unlimited.
|
||||
# vsync: off
|
||||
|
||||
performance:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Includes changes by vii from CoderDayton/nightcap.
|
||||
// Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
#include "runtime/runtime_paths.h"
|
||||
|
||||
#define JSON_NOEXCEPTION 1
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
#include <adwaita.h>
|
||||
#include <glib-unix.h>
|
||||
#include <sys/socket.h>
|
||||
@@ -153,7 +154,7 @@ void InstallProgressStyle(GdkDisplay* display) {
|
||||
}
|
||||
|
||||
bool InitializeUi(UiStyle style) {
|
||||
g_set_application_name("Mocktail");
|
||||
g_set_application_name("Nightcap");
|
||||
if (!gtk_init_check()) {
|
||||
return false;
|
||||
}
|
||||
@@ -169,7 +170,7 @@ bool InitializeUi(UiStyle style) {
|
||||
if (display == nullptr) {
|
||||
return false;
|
||||
}
|
||||
gtk_window_set_default_icon_name("space.bigrat.mocktail");
|
||||
gtk_window_set_default_icon_name("io.github.CoderDayton.nightcap");
|
||||
adw_style_manager_set_color_scheme(adw_style_manager_get_default(),
|
||||
ADW_COLOR_SCHEME_FORCE_DARK);
|
||||
adw_style_manager_set_color_scheme(adw_style_manager_get_for_display(display),
|
||||
@@ -229,7 +230,7 @@ void PresentProgressWindow(ProgressState* state, std::string_view message) {
|
||||
GtkWidget* window_widget = adw_window_new();
|
||||
state->window = GTK_WINDOW(g_object_ref_sink(window_widget));
|
||||
gtk_widget_add_css_class(window_widget, "mocktail-update-progress");
|
||||
gtk_window_set_title(state->window, "Mocktail");
|
||||
gtk_window_set_title(state->window, "Nightcap");
|
||||
gtk_window_set_default_size(state->window, 470, 640);
|
||||
|
||||
GtkWidget* toolbar_widget = adw_toolbar_view_new();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
#include "update/host_abi_deriver.h"
|
||||
|
||||
#define JSON_NOEXCEPTION 1
|
||||
@@ -14,6 +15,7 @@
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <iterator>
|
||||
@@ -1358,132 +1360,152 @@ std::optional<Json> DeriveRuntimeCompatibility(
|
||||
*error = "reference FMOD string constructor contract changed";
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto string_constructor = FindUniqueSemanticSignatureMatch(
|
||||
reference, candidate, disassembler,
|
||||
{"fmod-string-constructor", old.string_constructor, 32, false, 3},
|
||||
error);
|
||||
const auto info_method = FindUniqueSemanticSignatureMatch(
|
||||
reference, candidate, disassembler,
|
||||
{"fmod-output-info", old.info_method, 18, false, 3}, error);
|
||||
// A missing legacy selector is expected at the device-list ABI transition.
|
||||
// The new layout is accepted only with its own complete selector contract.
|
||||
std::string select_error;
|
||||
const auto select_method = FindUniqueSemanticSignatureMatch(
|
||||
reference, candidate, disassembler,
|
||||
{"fmod-output-select", old.select_method, 18, false, 3}, &select_error);
|
||||
if (!string_constructor.has_value() || !info_method.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto count_matches = FindSignatureMatches(
|
||||
reference, candidate, disassembler,
|
||||
{"fmod-output-count", old.count_method, 24, false, 3}, true, error);
|
||||
const auto current_matches = FindSignatureMatches(
|
||||
reference, candidate, disassembler,
|
||||
{"fmod-output-current", old.current_method, 24, false, 3}, true,
|
||||
error);
|
||||
if (!count_matches.has_value() || !current_matches.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::set<std::uint64_t> count_candidates;
|
||||
std::set<std::uint64_t> current_candidates;
|
||||
for (const SignatureMatch& match : *count_matches)
|
||||
count_candidates.insert(match.rva);
|
||||
for (const SignatureMatch& match : *current_matches)
|
||||
current_candidates.insert(match.rva);
|
||||
if (count_candidates.empty() || current_candidates.empty()) {
|
||||
*error = "FMOD count/current method signatures have no candidates";
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto relocations = candidate.RelativeRelocations(error);
|
||||
if (!relocations.has_value()) return std::nullopt;
|
||||
std::vector<FmodRuntimeAnchors> candidates;
|
||||
for (const auto& [offset, addend] : *relocations) {
|
||||
if (addend != info_method->rva || offset < kInfoIndex * 8) continue;
|
||||
const std::uint64_t vtable = offset - kInfoIndex * 8;
|
||||
const auto count = relocations->find(vtable + kCountIndex * 8);
|
||||
for (const int layout : {1, 2}) {
|
||||
FmodRuntimeAnchors derived;
|
||||
derived.layout_version = layout;
|
||||
const auto current = relocations->find(vtable + derived.current_index() * 8);
|
||||
const auto select = relocations->find(vtable + derived.select_index() * 8);
|
||||
std::string relro_error;
|
||||
if (vtable % 8 != 0 || count == relocations->end() ||
|
||||
current == relocations->end() || select == relocations->end() ||
|
||||
count_candidates.find(count->second) == count_candidates.end() ||
|
||||
current_candidates.find(current->second) == current_candidates.end() ||
|
||||
count->second == current->second ||
|
||||
!candidate.RequireRelro(vtable, (derived.select_index() + 1) * 8,
|
||||
&relro_error)) {
|
||||
continue;
|
||||
}
|
||||
if (layout == 1) {
|
||||
if (old.layout_version != 1 || !select_method.has_value() ||
|
||||
select->second != select_method->rva) continue;
|
||||
} else if (!HasFmodDeviceListsSelectContract(candidate, select->second)) {
|
||||
continue;
|
||||
}
|
||||
candidates.push_back({vtable, string_constructor->rva, count->second,
|
||||
info_method->rva, current->second, select->second,
|
||||
layout});
|
||||
// The FMOD output-device bridge is optional at runtime. A candidate whose
|
||||
// FMOD output methods cannot be matched is derived without the bridge.
|
||||
std::string fmod_error;
|
||||
const auto bridge = [&]() -> std::optional<Json> {
|
||||
const auto string_constructor = FindUniqueSemanticSignatureMatch(
|
||||
reference, candidate, disassembler,
|
||||
{"fmod-string-constructor", old.string_constructor, 32, false, 3},
|
||||
&fmod_error);
|
||||
const auto info_method = FindUniqueSemanticSignatureMatch(
|
||||
reference, candidate, disassembler,
|
||||
{"fmod-output-info", old.info_method, 18, false, 3}, &fmod_error);
|
||||
// A missing legacy selector is expected at the device-list ABI
|
||||
// transition. The new layout is accepted only with its own complete
|
||||
// selector contract.
|
||||
std::string select_error;
|
||||
const auto select_method = FindUniqueSemanticSignatureMatch(
|
||||
reference, candidate, disassembler,
|
||||
{"fmod-output-select", old.select_method, 18, false, 3},
|
||||
&select_error);
|
||||
if (!string_constructor.has_value() || !info_method.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
if (candidates.size() != 1U) {
|
||||
*error = "FMOD output vtable matched " +
|
||||
std::to_string(candidates.size()) + " candidate locations";
|
||||
return std::nullopt;
|
||||
}
|
||||
const FmodRuntimeAnchors& derived = candidates.front();
|
||||
if (!HasFmodStringConstructorContract(candidate,
|
||||
derived.string_constructor)) {
|
||||
*error = "candidate FMOD string constructor contract changed";
|
||||
return std::nullopt;
|
||||
}
|
||||
result["fmod_output_device_bridge"] = {
|
||||
{"vtable_rva", FormatRva(derived.vtable)},
|
||||
{"string_constructor_rva", FormatRva(derived.string_constructor)},
|
||||
{"count_method_rva", FormatRva(derived.count_method)},
|
||||
{"info_method_rva", FormatRva(derived.info_method)},
|
||||
{"current_method_rva", FormatRva(derived.current_method)},
|
||||
{"select_method_rva", FormatRva(derived.select_method)},
|
||||
};
|
||||
if (old.input_methods[0] != 0) {
|
||||
constexpr std::array<std::string_view, 4> fields = {
|
||||
"input_count_method_rva", "input_info_method_rva",
|
||||
"input_current_method_rva", "input_select_method_rva"};
|
||||
constexpr std::array<std::size_t, 4> lengths = {4, 16, 12, 16};
|
||||
const auto old_slots = old.input_indexes();
|
||||
const auto new_slots = derived.input_indexes();
|
||||
for (std::size_t i = 0; i < fields.size(); ++i) {
|
||||
const auto old_method =
|
||||
reference_relocations->find(old.vtable + old_slots[i] * 8);
|
||||
const auto new_method =
|
||||
relocations->find(derived.vtable + new_slots[i] * 8);
|
||||
if (old_method == reference_relocations->end() ||
|
||||
old_method->second != old.input_methods[i] ||
|
||||
new_method == relocations->end()) {
|
||||
*error = "FMOD input vtable does not match its methods";
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto matches = FindSignatureMatches(
|
||||
reference, candidate, disassembler,
|
||||
{std::string(fields[i]), old.input_methods[i], lengths[i], false, 3},
|
||||
true, error);
|
||||
if (!matches || !std::any_of(matches->begin(), matches->end(),
|
||||
[&](const auto &match) {
|
||||
return match.rva == new_method->second;
|
||||
})) {
|
||||
if (error->empty())
|
||||
*error = "candidate FMOD input contract changed";
|
||||
return std::nullopt;
|
||||
}
|
||||
result["fmod_output_device_bridge"][fields[i]] =
|
||||
FormatRva(new_method->second);
|
||||
const auto count_matches = FindSignatureMatches(
|
||||
reference, candidate, disassembler,
|
||||
{"fmod-output-count", old.count_method, 24, false, 3}, true,
|
||||
&fmod_error);
|
||||
const auto current_matches = FindSignatureMatches(
|
||||
reference, candidate, disassembler,
|
||||
{"fmod-output-current", old.current_method, 24, false, 3}, true,
|
||||
&fmod_error);
|
||||
if (!count_matches.has_value() || !current_matches.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::set<std::uint64_t> count_candidates;
|
||||
std::set<std::uint64_t> current_candidates;
|
||||
for (const SignatureMatch& match : *count_matches)
|
||||
count_candidates.insert(match.rva);
|
||||
for (const SignatureMatch& match : *current_matches)
|
||||
current_candidates.insert(match.rva);
|
||||
if (count_candidates.empty() || current_candidates.empty()) {
|
||||
fmod_error = "FMOD count/current method signatures have no candidates";
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto relocations = candidate.RelativeRelocations(&fmod_error);
|
||||
if (!relocations.has_value()) return std::nullopt;
|
||||
std::vector<FmodRuntimeAnchors> candidates;
|
||||
for (const auto& [offset, addend] : *relocations) {
|
||||
if (addend != info_method->rva || offset < kInfoIndex * 8) continue;
|
||||
const std::uint64_t vtable = offset - kInfoIndex * 8;
|
||||
const auto count = relocations->find(vtable + kCountIndex * 8);
|
||||
for (const int layout : {1, 2}) {
|
||||
FmodRuntimeAnchors derived;
|
||||
derived.layout_version = layout;
|
||||
const auto current =
|
||||
relocations->find(vtable + derived.current_index() * 8);
|
||||
const auto select =
|
||||
relocations->find(vtable + derived.select_index() * 8);
|
||||
std::string relro_error;
|
||||
if (vtable % 8 != 0 || count == relocations->end() ||
|
||||
current == relocations->end() || select == relocations->end() ||
|
||||
count_candidates.find(count->second) == count_candidates.end() ||
|
||||
current_candidates.find(current->second) ==
|
||||
current_candidates.end() ||
|
||||
count->second == current->second ||
|
||||
!candidate.RequireRelro(vtable, (derived.select_index() + 1) * 8,
|
||||
&relro_error)) {
|
||||
continue;
|
||||
}
|
||||
if (layout == 1) {
|
||||
if (old.layout_version != 1 || !select_method.has_value() ||
|
||||
select->second != select_method->rva)
|
||||
continue;
|
||||
} else if (!HasFmodDeviceListsSelectContract(candidate,
|
||||
select->second)) {
|
||||
continue;
|
||||
}
|
||||
candidates.push_back({vtable, string_constructor->rva, count->second,
|
||||
info_method->rva, current->second,
|
||||
select->second, layout});
|
||||
}
|
||||
}
|
||||
if (candidates.size() != 1U) {
|
||||
fmod_error = "FMOD output vtable matched " +
|
||||
std::to_string(candidates.size()) + " candidate locations";
|
||||
return std::nullopt;
|
||||
}
|
||||
const FmodRuntimeAnchors& derived = candidates.front();
|
||||
if (!HasFmodStringConstructorContract(candidate,
|
||||
derived.string_constructor)) {
|
||||
fmod_error = "candidate FMOD string constructor contract changed";
|
||||
return std::nullopt;
|
||||
}
|
||||
Json bridge_json = {
|
||||
{"vtable_rva", FormatRva(derived.vtable)},
|
||||
{"string_constructor_rva", FormatRva(derived.string_constructor)},
|
||||
{"count_method_rva", FormatRva(derived.count_method)},
|
||||
{"info_method_rva", FormatRva(derived.info_method)},
|
||||
{"current_method_rva", FormatRva(derived.current_method)},
|
||||
{"select_method_rva", FormatRva(derived.select_method)},
|
||||
};
|
||||
if (old.input_methods[0] != 0) {
|
||||
constexpr std::array<std::string_view, 4> fields = {
|
||||
"input_count_method_rva", "input_info_method_rva",
|
||||
"input_current_method_rva", "input_select_method_rva"};
|
||||
constexpr std::array<std::size_t, 4> lengths = {4, 16, 12, 16};
|
||||
const auto old_slots = old.input_indexes();
|
||||
const auto new_slots = derived.input_indexes();
|
||||
for (std::size_t i = 0; i < fields.size(); ++i) {
|
||||
const auto old_method =
|
||||
reference_relocations->find(old.vtable + old_slots[i] * 8);
|
||||
const auto new_method =
|
||||
relocations->find(derived.vtable + new_slots[i] * 8);
|
||||
if (old_method == reference_relocations->end() ||
|
||||
old_method->second != old.input_methods[i] ||
|
||||
new_method == relocations->end()) {
|
||||
fmod_error = "FMOD input vtable does not match its methods";
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto matches = FindSignatureMatches(
|
||||
reference, candidate, disassembler,
|
||||
{std::string(fields[i]), old.input_methods[i], lengths[i], false,
|
||||
3},
|
||||
true, &fmod_error);
|
||||
if (!matches || !std::any_of(matches->begin(), matches->end(),
|
||||
[&](const auto& match) {
|
||||
return match.rva == new_method->second;
|
||||
})) {
|
||||
if (fmod_error.empty())
|
||||
fmod_error = "candidate FMOD input contract changed";
|
||||
return std::nullopt;
|
||||
}
|
||||
bridge_json[fields[i]] = FormatRva(new_method->second);
|
||||
}
|
||||
}
|
||||
if (derived.layout_version != 1) {
|
||||
bridge_json["vtable_layout_version"] = derived.layout_version;
|
||||
}
|
||||
return bridge_json;
|
||||
}();
|
||||
if (!bridge.has_value()) {
|
||||
std::fprintf(stderr,
|
||||
"[native-updater] FMOD output-device bridge omitted: %s\n",
|
||||
fmod_error.c_str());
|
||||
return result;
|
||||
}
|
||||
if (derived.layout_version != 1) {
|
||||
result["fmod_output_device_bridge"]["vtable_layout_version"] =
|
||||
derived.layout_version;
|
||||
}
|
||||
result["fmod_output_device_bridge"] = *bridge;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Includes changes by vii from CoderDayton/nightcap.
|
||||
# Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
# Copyright 2026 Mocktail Project Authors
|
||||
# Licensed under the Apache License, Version 2.0.
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
# Copyright 2026 Mocktail Project Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License")
|
||||
@@ -119,6 +120,18 @@ target_link_libraries(texture_memory_policy_test PRIVATE
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
add_executable(device_memory_profile_test device_memory_profile_test.cc)
|
||||
target_link_libraries(device_memory_profile_test PRIVATE
|
||||
Mocktail::Runtime
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
add_executable(display_size_test display_size_test.cc)
|
||||
target_link_libraries(display_size_test PRIVATE
|
||||
Mocktail::Runtime
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
add_executable(http_client_policy_test http_client_policy_test.cc)
|
||||
target_link_libraries(http_client_policy_test PRIVATE
|
||||
Mocktail::Runtime
|
||||
@@ -477,6 +490,42 @@ target_link_libraries(vulkan_text_overlay_compositor_test PRIVATE
|
||||
)
|
||||
mocktail_apply_compile_options(vulkan_text_overlay_compositor_test)
|
||||
|
||||
add_executable(etc2_decoder_test
|
||||
etc2_decoder_test.cc
|
||||
${PROJECT_SOURCE_DIR}/src/graphics/etc2_decoder.cc
|
||||
)
|
||||
target_include_directories(etc2_decoder_test PRIVATE
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
)
|
||||
target_link_libraries(etc2_decoder_test PRIVATE GTest::gtest_main Threads::Threads)
|
||||
mocktail_apply_compile_options(etc2_decoder_test)
|
||||
|
||||
pkg_check_modules(LIBPNG REQUIRED IMPORTED_TARGET libpng)
|
||||
|
||||
add_executable(texture_override_test
|
||||
texture_override_test.cc
|
||||
${PROJECT_SOURCE_DIR}/src/graphics/texture_override.cc
|
||||
)
|
||||
target_include_directories(texture_override_test PRIVATE
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
)
|
||||
target_link_libraries(texture_override_test PRIVATE GTest::gtest_main
|
||||
PkgConfig::LIBPNG)
|
||||
mocktail_apply_compile_options(texture_override_test)
|
||||
|
||||
add_executable(vulkan_etc2_emulation_test
|
||||
vulkan_etc2_emulation_test.cc
|
||||
${PROJECT_SOURCE_DIR}/src/graphics/vulkan_etc2_emulation.cc
|
||||
${PROJECT_SOURCE_DIR}/src/graphics/etc2_decoder.cc
|
||||
${PROJECT_SOURCE_DIR}/src/graphics/texture_override.cc
|
||||
)
|
||||
target_include_directories(vulkan_etc2_emulation_test PRIVATE
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
)
|
||||
target_link_libraries(vulkan_etc2_emulation_test PRIVATE GTest::gtest_main
|
||||
Threads::Threads PkgConfig::LIBPNG)
|
||||
mocktail_apply_compile_options(vulkan_etc2_emulation_test)
|
||||
|
||||
add_executable(video_driver_policy_test
|
||||
video_driver_policy_test.cc
|
||||
)
|
||||
@@ -894,6 +943,8 @@ gtest_discover_tests(failure_dialog_test)
|
||||
gtest_discover_tests(frame_rate_policy_test)
|
||||
gtest_discover_tests(performance_policy_test)
|
||||
gtest_discover_tests(texture_memory_policy_test)
|
||||
gtest_discover_tests(device_memory_profile_test)
|
||||
gtest_discover_tests(display_size_test)
|
||||
gtest_discover_tests(http_client_policy_test)
|
||||
gtest_discover_tests(http_client_spin_guard_test)
|
||||
gtest_discover_tests(crash_report_policy_test)
|
||||
@@ -937,6 +988,8 @@ gtest_discover_tests(input_pump_pacer_test)
|
||||
gtest_discover_tests(vulkan_surface_recovery_gate_test)
|
||||
gtest_discover_tests(vulkan_present_progress_gate_test)
|
||||
gtest_discover_tests(vulkan_text_overlay_compositor_test)
|
||||
gtest_discover_tests(etc2_decoder_test)
|
||||
gtest_discover_tests(vulkan_etc2_emulation_test)
|
||||
gtest_discover_tests(video_driver_policy_test)
|
||||
gtest_discover_tests(window_surface_lifecycle_test)
|
||||
gtest_discover_tests(window_creation_policy_test)
|
||||
@@ -996,12 +1049,6 @@ add_test(
|
||||
${CMAKE_SOURCE_DIR}/packaging/AppRun
|
||||
)
|
||||
|
||||
add_test(
|
||||
NAME AnyLinuxDependenciesTest.RollingAssetsAreVerified
|
||||
COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/anylinux_dependencies_test.sh
|
||||
${CMAKE_SOURCE_DIR}
|
||||
)
|
||||
|
||||
add_test(
|
||||
NAME AnyLinuxPackagingTest.RelocatedRuntimePaths
|
||||
COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/anylinux_packaging_test.sh
|
||||
@@ -1012,7 +1059,7 @@ add_test(
|
||||
NAME UrlHandlerRegistrationTest.ExplicitPerUserDefault
|
||||
COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/url_handler_registration_test.sh
|
||||
${CMAKE_SOURCE_DIR}/scripts/register_url_handler.sh
|
||||
${CMAKE_SOURCE_DIR}/packaging/space.bigrat.mocktail.desktop
|
||||
${CMAKE_SOURCE_DIR}/packaging/io.github.CoderDayton.nightcap.desktop
|
||||
)
|
||||
|
||||
add_test(
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright 2026 Mocktail Project Authors
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
readonly ROOT="${1:?source root is required}"
|
||||
readonly TEMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/mocktail-dependency-test.XXXXXX")"
|
||||
trap 'rm -rf -- "${TEMP_DIR}"' EXIT
|
||||
source "${ROOT}/scripts/install_anylinux_dependencies.sh"
|
||||
|
||||
Fail() {
|
||||
printf 'AnyLinux dependency test failed: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
readonly API=https://api.github.com/repos/pkgforge-dev/archlinux-pkgs-debloated/releases
|
||||
readonly FFMPEG=ffmpeg-mini-x86_64.pkg.tar.zst
|
||||
readonly OPUS=opus-mini-x86_64.pkg.tar.zst
|
||||
printf 'ffmpeg fixture\n' >"${TEMP_DIR}/ffmpeg"
|
||||
printf 'updated ffmpeg fixture\n' >"${TEMP_DIR}/updated-ffmpeg"
|
||||
printf 'opus fixture\n' >"${TEMP_DIR}/opus"
|
||||
readonly FFMPEG_HASH="$(sha256sum "${TEMP_DIR}/ffmpeg" | cut -d ' ' -f 1)"
|
||||
readonly UPDATED_FFMPEG_HASH="$(sha256sum "${TEMP_DIR}/updated-ffmpeg" | cut -d ' ' -f 1)"
|
||||
readonly OPUS_HASH="$(sha256sum "${TEMP_DIR}/opus" | cut -d ' ' -f 1)"
|
||||
printf '# Required fixture packages\n\n%s\n%s' "${FFMPEG}" "${OPUS}" \
|
||||
>"${TEMP_DIR}/manifest.txt"
|
||||
jq --null-input --arg api "${API}" --arg ffmpeg "${FFMPEG}" --arg opus "${OPUS}" \
|
||||
--arg ffmpeg_hash "${FFMPEG_HASH}" --arg opus_hash "${OPUS_HASH}" '
|
||||
{tag_name: "continuous", assets: [
|
||||
{id: 10, name: $ffmpeg, state: "uploaded", digest: ("sha256:" + $ffmpeg_hash),
|
||||
url: ($api + "/assets/10")},
|
||||
{id: 20, name: $opus, state: "uploaded", digest: ("sha256:" + $opus_hash),
|
||||
url: ($api + "/assets/20")},
|
||||
{id: 30, name: "unrequested-mini-x86_64.pkg.tar.zst", state: "uploaded",
|
||||
digest: null, url: ($api + "/assets/30")}
|
||||
]}
|
||||
' >"${TEMP_DIR}/release.json"
|
||||
jq --arg hash "${UPDATED_FFMPEG_HASH}" '
|
||||
.assets[0].id = 11 | .assets[0].url |= sub("/10$"; "/11") |
|
||||
.assets[0].digest = ("sha256:" + $hash) |
|
||||
.assets[1].id = 21 | .assets[1].url |= sub("/20$"; "/21")
|
||||
' \
|
||||
"${TEMP_DIR}/release.json" >"${TEMP_DIR}/refreshed-release.json"
|
||||
mkdir -p "${TEMP_DIR}/project/scripts" "${TEMP_DIR}/project/packaging"
|
||||
cp "${ROOT}/scripts/install_anylinux_dependencies.sh" "${TEMP_DIR}/project/scripts/"
|
||||
cp "${TEMP_DIR}/manifest.txt" "${TEMP_DIR}/project/packaging/anylinux-dependencies.txt"
|
||||
|
||||
# Record requests without any network access or changes to host packages.
|
||||
curl() {
|
||||
local output="" accept="" authorization="" url=""
|
||||
while (( $# > 0 )); do
|
||||
case "$1" in
|
||||
--output) output="$2"; shift 2 ;;
|
||||
--header)
|
||||
case "$2" in
|
||||
Accept:*) accept="$2" ;;
|
||||
Authorization:*) authorization="$2" ;;
|
||||
esac
|
||||
shift 2 ;;
|
||||
--retry|--proto|--proto-redir|--connect-timeout|--max-time) shift 2 ;;
|
||||
--fail|--silent|--show-error|--location) shift ;;
|
||||
https://*) url="$1"; shift ;;
|
||||
*) Fail "unexpected curl option: $1" ;;
|
||||
esac
|
||||
done
|
||||
if [[ -n "${GH_TOKEN:-}" ]]; then
|
||||
[[ "${authorization}" == "Authorization: Bearer ${GH_TOKEN}" ]] ||
|
||||
Fail 'missing authenticated GitHub request'
|
||||
fi
|
||||
printf '%s\n' "${url}" >>"${TEMP_DIR}/calls"
|
||||
case "${url}" in
|
||||
"${API}/tags/continuous")
|
||||
[[ "${accept}" == 'Accept: application/vnd.github+json' ]]
|
||||
if [[ "${SCENARIO}" == metadata-failure ]]; then return 22; fi
|
||||
if [[ "${SCENARIO}" == replaced && -f "${TEMP_DIR}/asset-replaced" ]]; then
|
||||
cp "${TEMP_DIR}/refreshed-release.json" "${output}"
|
||||
else
|
||||
cp "${TEST_RELEASE}" "${output}"
|
||||
fi ;;
|
||||
"${API}/assets/10"|"${API}/assets/11"|"${API}/assets/20"|"${API}/assets/21")
|
||||
[[ "${accept}" == 'Accept: application/octet-stream' ]]
|
||||
if [[ "${SCENARIO}" == download-failure ]]; then return 22; fi
|
||||
if [[ "${SCENARIO}" == replaced && "${url}" == "${API}/assets/20" ]]; then
|
||||
touch "${TEMP_DIR}/asset-replaced"
|
||||
return 22
|
||||
fi
|
||||
if [[ "${url}" == "${API}/assets/20" || "${url}" == "${API}/assets/21" ]]; then
|
||||
cp "${TEMP_DIR}/opus" "${output}"
|
||||
elif [[ "${url}" == "${API}/assets/11" ]]; then
|
||||
cp "${TEMP_DIR}/updated-ffmpeg" "${output}"
|
||||
else
|
||||
cp "${TEMP_DIR}/ffmpeg" "${output}"
|
||||
fi
|
||||
if [[ "${SCENARIO}" == corrupted && "${url}" == "${API}/assets/20" ]]; then
|
||||
printf 'corrupted\n' >>"${output}"
|
||||
fi ;;
|
||||
*) Fail "unrequested or rolling asset URL: ${url}" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
pacman() {
|
||||
[[ "$#" == 5 && "$1" == -U && "$2" == --noconfirm && "$3" == --needed ]]
|
||||
if [[ "${SCENARIO}" == replaced ]]; then
|
||||
cmp "${TEMP_DIR}/updated-ffmpeg" "$4"
|
||||
else
|
||||
cmp "${TEMP_DIR}/ffmpeg" "$4"
|
||||
fi
|
||||
cmp "${TEMP_DIR}/opus" "$5"
|
||||
local downloads="${4%/*}"
|
||||
(cd "${downloads}" && sha256sum --check --strict SHA256SUMS)
|
||||
cp "$4" "$5" "${downloads}/SHA256SUMS" "${downloads}/dependencies.tsv" \
|
||||
"${TEMP_DIR}/${SCENARIO}/"
|
||||
touch "${TEMP_DIR}/installed"
|
||||
}
|
||||
|
||||
export -f curl pacman Fail
|
||||
export TEMP_DIR API FFMPEG OPUS SCENARIO TEST_RELEASE
|
||||
|
||||
ExpectRejectedSnapshot() {
|
||||
local description="$1" filter="$2"
|
||||
jq "${filter}" "${TEMP_DIR}/release.json" >"${TEMP_DIR}/invalid.json"
|
||||
if (AnyLinuxDependencySnapshot "${TEMP_DIR}/manifest.txt" \
|
||||
"${TEMP_DIR}/invalid.json") >"${TEMP_DIR}/invalid.log" 2>&1; then
|
||||
Fail "accepted ${description}"
|
||||
fi
|
||||
}
|
||||
|
||||
(AnyLinuxDependencySnapshot "${TEMP_DIR}/manifest.txt" \
|
||||
"${TEMP_DIR}/release.json") >"${TEMP_DIR}/snapshot.tsv" ||
|
||||
Fail 'rejected valid dependency snapshot'
|
||||
printf '%s\t%s\t%s\n' \
|
||||
"${FFMPEG_HASH}" "${FFMPEG}" "${API}/assets/10" \
|
||||
"${OPUS_HASH}" "${OPUS}" "${API}/assets/20" \
|
||||
>"${TEMP_DIR}/expected-snapshot.tsv"
|
||||
cmp "${TEMP_DIR}/expected-snapshot.tsv" "${TEMP_DIR}/snapshot.tsv" ||
|
||||
Fail 'incorrect dependency snapshot'
|
||||
|
||||
ExpectRejectedSnapshot 'missing asset' '.assets |= .[1:]'
|
||||
ExpectRejectedSnapshot 'duplicate asset' '.assets += [.assets[0]]'
|
||||
ExpectRejectedSnapshot 'absent SHA256' '.assets[0].digest = null'
|
||||
ExpectRejectedSnapshot 'malformed SHA256' '.assets[0].digest = "sha256:bad"'
|
||||
ExpectRejectedSnapshot 'foreign asset URL' '.assets[0].url = "https://example.org/archive"'
|
||||
ExpectRejectedSnapshot 'fractional asset ID' '.assets[0].id = 10.5'
|
||||
ExpectRejectedSnapshot 'unuploaded asset' '.assets[0].state = "starter"'
|
||||
ExpectRejectedSnapshot 'different release' '.tag_name = "other"'
|
||||
|
||||
for invalid_manifest in '../ffmpeg-mini-x86_64.pkg.tar.zst' \
|
||||
"${FFMPEG} extra" "${FFMPEG}"$'\n'"${FFMPEG}" ''; do
|
||||
printf '%s\n' "${invalid_manifest}" >"${TEMP_DIR}/invalid-manifest.txt"
|
||||
if (AnyLinuxDependencySnapshot "${TEMP_DIR}/invalid-manifest.txt" \
|
||||
"${TEMP_DIR}/release.json") >"${TEMP_DIR}/invalid.log" 2>&1; then
|
||||
Fail 'accepted unsafe, duplicate, or empty package allowlist'
|
||||
fi
|
||||
done
|
||||
|
||||
for SCENARIO in success replaced corrupted download-failure metadata-failure invalid-metadata; do
|
||||
TEST_RELEASE="${TEMP_DIR}/release.json"
|
||||
if [[ "${SCENARIO}" == invalid-metadata ]]; then
|
||||
# A valid first row must not allow downloads when a later row is invalid.
|
||||
jq '.assets[1].digest = null' "${TEMP_DIR}/release.json" >"${TEMP_DIR}/invalid.json"
|
||||
TEST_RELEASE="${TEMP_DIR}/invalid.json"
|
||||
fi
|
||||
mkdir "${TEMP_DIR}/${SCENARIO}"
|
||||
: >"${TEMP_DIR}/calls"
|
||||
if GH_TOKEN=fixture-token bash -Eeuo pipefail -c '
|
||||
source "$1/project/scripts/install_anylinux_dependencies.sh"
|
||||
AnyLinuxDependencyRequireContainer() { :; }
|
||||
AnyLinuxDependenciesMain
|
||||
' _ "${TEMP_DIR}" \
|
||||
>"${TEMP_DIR}/${SCENARIO}.log" 2>&1; then
|
||||
[[ "${SCENARIO}" == success || "${SCENARIO}" == replaced ]] ||
|
||||
Fail "installed packages after ${SCENARIO}"
|
||||
[[ -f "${TEMP_DIR}/installed" ]]
|
||||
(cd "${TEMP_DIR}/${SCENARIO}" && sha256sum --check --strict SHA256SUMS)
|
||||
rm -- "${TEMP_DIR}/installed"
|
||||
else
|
||||
if [[ "${SCENARIO}" == success || "${SCENARIO}" == replaced ]]; then
|
||||
cat -- "${TEMP_DIR}/${SCENARIO}.log" >&2
|
||||
Fail "valid download failed: ${SCENARIO}"
|
||||
fi
|
||||
[[ ! -e "${TEMP_DIR}/installed" ]] || Fail 'installed unverified archives'
|
||||
fi
|
||||
case "${SCENARIO}" in
|
||||
success)
|
||||
[[ "$(wc -l <"${TEMP_DIR}/calls")" == 3 ]]
|
||||
grep -Fq "${API}/assets/10" "${TEMP_DIR}/calls" ;;
|
||||
replaced)
|
||||
[[ "$(wc -l <"${TEMP_DIR}/calls")" == 6 ]]
|
||||
grep -Fq "${API}/assets/11" "${TEMP_DIR}/calls"
|
||||
grep -Fq "${API}/assets/11" "${TEMP_DIR}/replaced/dependencies.tsv"
|
||||
grep -Fq "${API}/assets/21" "${TEMP_DIR}/replaced/dependencies.tsv" ;;
|
||||
corrupted)
|
||||
grep -Fq 'dependency checksum verification failed' "${TEMP_DIR}/${SCENARIO}.log" ;;
|
||||
download-failure)
|
||||
[[ "$(wc -l <"${TEMP_DIR}/calls")" == 6 ]]
|
||||
grep -Fq 'failed after 3 snapshots' "${TEMP_DIR}/${SCENARIO}.log" ;;
|
||||
metadata-failure|invalid-metadata)
|
||||
[[ "$(wc -l <"${TEMP_DIR}/calls")" == 1 ]] ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if (id() { printf '1000\n'; }; AnyLinuxDependencyRequireContainer) \
|
||||
>"${TEMP_DIR}/guard.log" 2>&1; then
|
||||
Fail 'container/root installation guard was bypassed'
|
||||
fi
|
||||
|
||||
printf 'AnyLinux dependency tests passed\n'
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
# Copyright 2026 Mocktail Project Authors
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@@ -86,7 +87,7 @@ fi
|
||||
"$4" == /usr/lib/mocktail/mocktail_updater &&
|
||||
"$5" == /usr/lib/mocktail && "$6" == /usr/share/mocktail &&
|
||||
"$7" == /usr/bin/bash ]]
|
||||
[[ "${DESKTOP}" == /usr/share/applications/space.bigrat.mocktail.desktop ]]
|
||||
[[ "${DESKTOP}" == /usr/share/applications/io.github.CoderDayton.nightcap.desktop ]]
|
||||
[[ "${MAIN_BIN}" == mocktail && "${STRACE_MODE}" == 1 ]]
|
||||
[[ "${STRACE_BINARY}" == mocktail && "${STRACE_FLAGS}" == --help ]]
|
||||
[[ -z "${NO_STRIP:-}" ]]
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
#include "runtime/device_memory_profile.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
namespace mocktail {
|
||||
namespace runtime {
|
||||
namespace {
|
||||
|
||||
constexpr std::uint64_t kMebibyte = 1024U * 1024U;
|
||||
|
||||
void ExpectLowRamProfile(const DeviceMemoryProfile& profile) {
|
||||
EXPECT_TRUE(profile.low_ram_device);
|
||||
EXPECT_EQ(profile.total_memory_mb, 2048);
|
||||
EXPECT_EQ(profile.memory_class_mb, 256);
|
||||
EXPECT_EQ(profile.large_memory_class_mb, 512);
|
||||
EXPECT_EQ(profile.low_memory_killer_background_threshold, 256);
|
||||
EXPECT_EQ(profile.low_memory_killer_foreground_threshold, 512);
|
||||
}
|
||||
|
||||
TEST(DeviceMemoryProfileTest, KeepsTheLowRamProfileOnSmallOrUnknownHosts) {
|
||||
ExpectLowRamProfile(BuildDeviceMemoryProfile(0));
|
||||
ExpectLowRamProfile(BuildDeviceMemoryProfile(2048U * kMebibyte));
|
||||
ExpectLowRamProfile(BuildDeviceMemoryProfile(4095U * kMebibyte));
|
||||
}
|
||||
|
||||
TEST(DeviceMemoryProfileTest, ReportsRealHostMemoryAsANormalRamDevice) {
|
||||
const DeviceMemoryProfile profile =
|
||||
BuildDeviceMemoryProfile(62951U * kMebibyte);
|
||||
EXPECT_FALSE(profile.low_ram_device);
|
||||
EXPECT_EQ(profile.total_memory_mb, 62951);
|
||||
EXPECT_EQ(profile.memory_class_mb, 512);
|
||||
EXPECT_EQ(profile.large_memory_class_mb, 1024);
|
||||
EXPECT_EQ(profile.low_memory_killer_background_threshold, 0);
|
||||
EXPECT_EQ(profile.low_memory_killer_foreground_threshold, 0);
|
||||
}
|
||||
|
||||
TEST(DeviceMemoryProfileTest, StartsTheNormalRamProfileAtFourGibibytes) {
|
||||
const DeviceMemoryProfile profile =
|
||||
BuildDeviceMemoryProfile(4096U * kMebibyte);
|
||||
EXPECT_FALSE(profile.low_ram_device);
|
||||
EXPECT_EQ(profile.total_memory_mb, 4096);
|
||||
}
|
||||
|
||||
TEST(DeviceMemoryProfileTest, ClampsTotalMemoryToTheJavaIntRange) {
|
||||
const DeviceMemoryProfile profile =
|
||||
BuildDeviceMemoryProfile(std::numeric_limits<std::uint64_t>::max());
|
||||
EXPECT_FALSE(profile.low_ram_device);
|
||||
EXPECT_EQ(profile.total_memory_mb, std::numeric_limits<std::int32_t>::max());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace runtime
|
||||
} // namespace mocktail
|
||||
@@ -0,0 +1,44 @@
|
||||
#include "runtime/display_size.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace mocktail {
|
||||
namespace runtime {
|
||||
namespace {
|
||||
|
||||
void ExpectSize(const DisplaySize& size, int width, int height) {
|
||||
EXPECT_EQ(size.width, width);
|
||||
EXPECT_EQ(size.height, height);
|
||||
}
|
||||
|
||||
TEST(DisplaySizeTest, ParsesHostDisplayPixels) {
|
||||
ExpectSize(ParseDisplaySize("3440x1440"), 3440, 1440);
|
||||
ExpectSize(ParseDisplaySize("1280x720"), 1280, 720);
|
||||
}
|
||||
|
||||
TEST(DisplaySizeTest, FallsBackForMissingOrMalformedValues) {
|
||||
ExpectSize(ParseDisplaySize(nullptr), 1920, 1080);
|
||||
ExpectSize(ParseDisplaySize(""), 1920, 1080);
|
||||
ExpectSize(ParseDisplaySize("3440"), 1920, 1080);
|
||||
ExpectSize(ParseDisplaySize("3440x"), 1920, 1080);
|
||||
ExpectSize(ParseDisplaySize("x1440"), 1920, 1080);
|
||||
ExpectSize(ParseDisplaySize("3440x1440junk"), 1920, 1080);
|
||||
ExpectSize(ParseDisplaySize("-3440x1440"), 1920, 1080);
|
||||
ExpectSize(ParseDisplaySize("0x1440"), 1920, 1080);
|
||||
}
|
||||
|
||||
TEST(DisplaySizeTest, FallsBackWhenAValueDoesNotFitAJavaInt) {
|
||||
ExpectSize(ParseDisplaySize("2147483648x1440"), 1920, 1080);
|
||||
ExpectSize(ParseDisplaySize("99999999999999999999x1440"), 1920, 1080);
|
||||
}
|
||||
|
||||
TEST(DisplaySizeTest, ConvertsPixelsToMillimetersAt160Dpi) {
|
||||
EXPECT_EQ(PixelsToMillimetersAt160Dpi(1280), 203);
|
||||
EXPECT_EQ(PixelsToMillimetersAt160Dpi(720), 114);
|
||||
EXPECT_EQ(PixelsToMillimetersAt160Dpi(3440), 546);
|
||||
EXPECT_EQ(PixelsToMillimetersAt160Dpi(0), 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace runtime
|
||||
} // namespace mocktail
|
||||
@@ -0,0 +1,271 @@
|
||||
#include "mocktail/graphics/etc2_decoder.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
namespace mocktail::graphics {
|
||||
namespace {
|
||||
|
||||
using Block8 = std::array<std::uint8_t, 8>;
|
||||
|
||||
struct Rgba {
|
||||
int r;
|
||||
int g;
|
||||
int b;
|
||||
int a;
|
||||
};
|
||||
|
||||
std::vector<std::uint8_t> Decode(EtcFormat format,
|
||||
const std::vector<std::uint8_t>& source,
|
||||
std::uint32_t width, std::uint32_t height) {
|
||||
std::vector<std::uint8_t> output(
|
||||
static_cast<std::size_t>(width) * height * EtcDecodedTexelBytes(format),
|
||||
0xAB);
|
||||
EXPECT_TRUE(DecodeEtcImage(format, source.data(), source.size(), width,
|
||||
height, output.data(), output.size()));
|
||||
return output;
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> Bytes(const Block8& block) {
|
||||
return {block.begin(), block.end()};
|
||||
}
|
||||
|
||||
void ExpectRgba(const std::vector<std::uint8_t>& texels, std::uint32_t width,
|
||||
std::uint32_t x, std::uint32_t y, Rgba expected) {
|
||||
const std::size_t offset = (static_cast<std::size_t>(y) * width + x) * 4;
|
||||
ASSERT_LT(offset + 3, texels.size());
|
||||
EXPECT_EQ(texels[offset], expected.r) << "x=" << x << " y=" << y;
|
||||
EXPECT_EQ(texels[offset + 1], expected.g) << "x=" << x << " y=" << y;
|
||||
EXPECT_EQ(texels[offset + 2], expected.b) << "x=" << x << " y=" << y;
|
||||
EXPECT_EQ(texels[offset + 3], expected.a) << "x=" << x << " y=" << y;
|
||||
}
|
||||
|
||||
std::uint16_t Unsigned16(const std::vector<std::uint8_t>& texels,
|
||||
std::size_t index) {
|
||||
return static_cast<std::uint16_t>(texels[index * 2] |
|
||||
(texels[index * 2 + 1] << 8));
|
||||
}
|
||||
|
||||
std::int16_t Signed16(const std::vector<std::uint8_t>& texels,
|
||||
std::size_t index) {
|
||||
return static_cast<std::int16_t>(Unsigned16(texels, index));
|
||||
}
|
||||
|
||||
TEST(Etc2DecoderTest, ReportsBlockAndTexelSizes) {
|
||||
EXPECT_EQ(EtcBlockBytes(EtcFormat::kEtc2Rgb8), 8U);
|
||||
EXPECT_EQ(EtcBlockBytes(EtcFormat::kEtc2Rgb8A1), 8U);
|
||||
EXPECT_EQ(EtcBlockBytes(EtcFormat::kEtc2Rgba8), 16U);
|
||||
EXPECT_EQ(EtcBlockBytes(EtcFormat::kEacR11), 8U);
|
||||
EXPECT_EQ(EtcBlockBytes(EtcFormat::kEacR11Signed), 8U);
|
||||
EXPECT_EQ(EtcBlockBytes(EtcFormat::kEacRg11), 16U);
|
||||
EXPECT_EQ(EtcBlockBytes(EtcFormat::kEacRg11Signed), 16U);
|
||||
EXPECT_EQ(EtcDecodedTexelBytes(EtcFormat::kEtc2Rgb8), 4U);
|
||||
EXPECT_EQ(EtcDecodedTexelBytes(EtcFormat::kEtc2Rgb8A1), 4U);
|
||||
EXPECT_EQ(EtcDecodedTexelBytes(EtcFormat::kEtc2Rgba8), 4U);
|
||||
EXPECT_EQ(EtcDecodedTexelBytes(EtcFormat::kEacR11), 2U);
|
||||
EXPECT_EQ(EtcDecodedTexelBytes(EtcFormat::kEacRg11Signed), 4U);
|
||||
}
|
||||
|
||||
TEST(Etc2DecoderTest, DecodesIndividualMode) {
|
||||
// Base colours 8 (left sub-block) and 0 (right), codeword 0 (2/8).
|
||||
// Pixel (0,0) index 1 (+8), pixels in column 1 index 2 (-2).
|
||||
const Block8 block = {0x80, 0x80, 0x80, 0x00, 0x00, 0xF0, 0x00, 0x01};
|
||||
const auto texels = Decode(EtcFormat::kEtc2Rgb8, Bytes(block), 4, 4);
|
||||
ExpectRgba(texels, 4, 0, 0, {144, 144, 144, 255});
|
||||
ExpectRgba(texels, 4, 0, 1, {138, 138, 138, 255});
|
||||
ExpectRgba(texels, 4, 1, 0, {134, 134, 134, 255});
|
||||
ExpectRgba(texels, 4, 2, 0, {2, 2, 2, 255});
|
||||
}
|
||||
|
||||
TEST(Etc2DecoderTest, DecodesDifferentialMode) {
|
||||
// R/G/B = 16 with delta +1: base colours 132 and 140, modifier +2.
|
||||
const Block8 block = {0x81, 0x81, 0x81, 0x02, 0x00, 0x00, 0x00, 0x00};
|
||||
const auto texels = Decode(EtcFormat::kEtc2Rgb8, Bytes(block), 4, 4);
|
||||
ExpectRgba(texels, 4, 0, 0, {134, 134, 134, 255});
|
||||
ExpectRgba(texels, 4, 2, 3, {142, 142, 142, 255});
|
||||
}
|
||||
|
||||
TEST(Etc2DecoderTest, DecodesTMode) {
|
||||
// Red overflows: colour 1 (0,255,0), colour 2 (0,255,136), distance 11.
|
||||
const Block8 block = {0x04, 0xF0, 0x0F, 0x86, 0x00, 0x12, 0x00, 0x03};
|
||||
const auto texels = Decode(EtcFormat::kEtc2Rgb8, Bytes(block), 4, 4);
|
||||
ExpectRgba(texels, 4, 0, 0, {11, 255, 147, 255});
|
||||
ExpectRgba(texels, 4, 0, 1, {0, 244, 125, 255});
|
||||
ExpectRgba(texels, 4, 1, 0, {0, 255, 136, 255});
|
||||
ExpectRgba(texels, 4, 1, 1, {0, 255, 0, 255});
|
||||
}
|
||||
|
||||
TEST(Etc2DecoderTest, DecodesHMode) {
|
||||
// Green overflows: colour 1 (0,0,0), colour 2 (255,17,255), distance 3.
|
||||
const Block8 block = {0x00, 0x04, 0x78, 0xFA, 0x80, 0x01, 0x80, 0x00};
|
||||
const auto texels = Decode(EtcFormat::kEtc2Rgb8, Bytes(block), 4, 4);
|
||||
ExpectRgba(texels, 4, 0, 0, {255, 20, 255, 255});
|
||||
ExpectRgba(texels, 4, 3, 3, {252, 14, 252, 255});
|
||||
ExpectRgba(texels, 4, 1, 0, {3, 3, 3, 255});
|
||||
}
|
||||
|
||||
TEST(Etc2DecoderTest, DecodesPlanarMode) {
|
||||
// Blue overflows: O=(130,64,0), H=(0,255,0), V=(255,0,255).
|
||||
const Block8 block = {0x40, 0x40, 0x04, 0x02, 0xFE, 0x07, 0xE0, 0x3F};
|
||||
const auto texels = Decode(EtcFormat::kEtc2Rgb8, Bytes(block), 4, 4);
|
||||
ExpectRgba(texels, 4, 0, 0, {130, 64, 0, 255});
|
||||
ExpectRgba(texels, 4, 3, 0, {33, 207, 0, 255});
|
||||
ExpectRgba(texels, 4, 0, 3, {224, 16, 191, 255});
|
||||
ExpectRgba(texels, 4, 3, 3, {126, 159, 191, 255});
|
||||
}
|
||||
|
||||
TEST(Etc2DecoderTest, DecodesPunchthroughAlpha) {
|
||||
// Opaque bit clear: index 2 is transparent black, index 0 has no modifier.
|
||||
const Block8 block = {0x81, 0x81, 0x81, 0x00, 0x00, 0x01, 0x00, 0x02};
|
||||
const auto texels = Decode(EtcFormat::kEtc2Rgb8A1, Bytes(block), 4, 4);
|
||||
ExpectRgba(texels, 4, 0, 0, {0, 0, 0, 0});
|
||||
ExpectRgba(texels, 4, 0, 1, {140, 140, 140, 255});
|
||||
ExpectRgba(texels, 4, 1, 0, {132, 132, 132, 255});
|
||||
ExpectRgba(texels, 4, 2, 0, {140, 140, 140, 255});
|
||||
}
|
||||
|
||||
TEST(Etc2DecoderTest, DecodesRgba8WithAlphaBlockFirst) {
|
||||
// Alpha: base 100, multiplier 2, table 0; pixel (0,0) index 7 (+14).
|
||||
const std::vector<std::uint8_t> block = {
|
||||
100, 0x20, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
const auto texels = Decode(EtcFormat::kEtc2Rgba8, block, 4, 4);
|
||||
ExpectRgba(texels, 4, 0, 0, {138, 138, 138, 128});
|
||||
ExpectRgba(texels, 4, 1, 0, {138, 138, 138, 94});
|
||||
}
|
||||
|
||||
TEST(Etc2DecoderTest, DecodesUnsignedR11) {
|
||||
const Block8 block = {128, 0x10, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const auto texels = Decode(EtcFormat::kEacR11, Bytes(block), 4, 4);
|
||||
EXPECT_EQ(Unsigned16(texels, 0), 36497);
|
||||
EXPECT_EQ(Unsigned16(texels, 1), 32143);
|
||||
|
||||
const Block8 zero_multiplier = {0, 0x00, 0, 0, 0, 0, 0, 0};
|
||||
const auto flat = Decode(EtcFormat::kEacR11, Bytes(zero_multiplier), 1, 1);
|
||||
EXPECT_EQ(Unsigned16(flat, 0), 32);
|
||||
}
|
||||
|
||||
TEST(Etc2DecoderTest, DecodesSignedR11) {
|
||||
const Block8 block = {0x80, 0x10, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const auto texels = Decode(EtcFormat::kEacR11Signed, Bytes(block), 4, 4);
|
||||
EXPECT_EQ(Signed16(texels, 0), -28956);
|
||||
EXPECT_EQ(Signed16(texels, 1), -32767);
|
||||
}
|
||||
|
||||
TEST(Etc2DecoderTest, DecodesRg11AsTwoChannels) {
|
||||
const std::vector<std::uint8_t> block = {
|
||||
128, 0x10, 0xE0, 0, 0, 0, 0, 0,
|
||||
0, 0x00, 0x00, 0, 0, 0, 0, 0,
|
||||
};
|
||||
const auto texels = Decode(EtcFormat::kEacRg11, block, 1, 1);
|
||||
EXPECT_EQ(Unsigned16(texels, 0), 36497);
|
||||
EXPECT_EQ(Unsigned16(texels, 1), 32);
|
||||
}
|
||||
|
||||
TEST(Etc2DecoderTest, DecodesPartialAndMultipleBlocks) {
|
||||
const Block8 t_mode = {0x04, 0xF0, 0x0F, 0x86, 0x00, 0x12, 0x00, 0x03};
|
||||
const Block8 differential = {0x81, 0x81, 0x81, 0x02, 0, 0, 0, 0};
|
||||
std::vector<std::uint8_t> source = Bytes(t_mode);
|
||||
source.insert(source.end(), differential.begin(), differential.end());
|
||||
const auto texels = Decode(EtcFormat::kEtc2Rgb8, source, 5, 1);
|
||||
ExpectRgba(texels, 5, 0, 0, {11, 255, 147, 255});
|
||||
ExpectRgba(texels, 5, 1, 0, {0, 255, 136, 255});
|
||||
ExpectRgba(texels, 5, 4, 0, {134, 134, 134, 255});
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> RandomBlocks(EtcFormat format, std::uint32_t width,
|
||||
std::uint32_t height,
|
||||
std::uint32_t seed) {
|
||||
std::mt19937 rng(seed);
|
||||
std::vector<std::uint8_t> source(((width + 3) / 4) * ((height + 3) / 4) *
|
||||
EtcBlockBytes(format));
|
||||
for (std::uint8_t& byte : source) {
|
||||
byte = static_cast<std::uint8_t>(rng());
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
TEST(Etc2DecoderTest, DecodesBlockRowBandsLikeWholeImage) {
|
||||
const std::uint32_t width = 13;
|
||||
const std::uint32_t height = 10;
|
||||
const auto source = RandomBlocks(EtcFormat::kEtc2Rgba8, width, height, 1);
|
||||
const auto whole = Decode(EtcFormat::kEtc2Rgba8, source, width, height);
|
||||
|
||||
std::vector<std::uint8_t> banded(whole.size(), 0xAB);
|
||||
ASSERT_TRUE(DecodeEtcImageBlockRows(EtcFormat::kEtc2Rgba8, source.data(),
|
||||
source.size(), width, height, 1, 2,
|
||||
banded.data(), banded.size()));
|
||||
ASSERT_TRUE(DecodeEtcImageBlockRows(EtcFormat::kEtc2Rgba8, source.data(),
|
||||
source.size(), width, height, 0, 1,
|
||||
banded.data(), banded.size()));
|
||||
EXPECT_EQ(banded, whole);
|
||||
|
||||
EXPECT_FALSE(DecodeEtcImageBlockRows(EtcFormat::kEtc2Rgba8, source.data(),
|
||||
source.size(), width, height, 2, 2,
|
||||
banded.data(), banded.size()));
|
||||
}
|
||||
|
||||
TEST(Etc2DecoderTest, DecodesJobsOnWorkersLikeSerialDecode) {
|
||||
struct Case {
|
||||
EtcFormat format;
|
||||
std::uint32_t width;
|
||||
std::uint32_t height;
|
||||
};
|
||||
const Case cases[] = {{EtcFormat::kEtc2Rgb8, 1030, 770},
|
||||
{EtcFormat::kEtc2Rgba8, 517, 1024},
|
||||
{EtcFormat::kEacRg11, 300, 301},
|
||||
{EtcFormat::kEtc2Rgb8A1, 3, 5}};
|
||||
std::vector<std::vector<std::uint8_t>> sources;
|
||||
std::vector<std::vector<std::uint8_t>> expected;
|
||||
std::vector<std::vector<std::uint8_t>> outputs;
|
||||
std::vector<EtcDecodeJob> jobs;
|
||||
for (std::size_t index = 0; index < std::size(cases); ++index) {
|
||||
const Case& c = cases[index];
|
||||
sources.push_back(RandomBlocks(c.format, c.width, c.height,
|
||||
static_cast<std::uint32_t>(index + 2)));
|
||||
expected.push_back(Decode(c.format, sources.back(), c.width, c.height));
|
||||
outputs.emplace_back(expected.back().size(), 0xAB);
|
||||
}
|
||||
for (std::size_t index = 0; index < std::size(cases); ++index) {
|
||||
const Case& c = cases[index];
|
||||
jobs.push_back({c.format, sources[index].data(), sources[index].size(),
|
||||
c.width, c.height, outputs[index].data(),
|
||||
outputs[index].size()});
|
||||
}
|
||||
std::vector<std::uint8_t> short_output(4 * 4 * 4 - 1, 0xAB);
|
||||
jobs.push_back({EtcFormat::kEtc2Rgb8, sources[0].data(), sources[0].size(),
|
||||
4, 4, short_output.data(), short_output.size()});
|
||||
|
||||
DecodeEtcJobs(jobs.data(), jobs.size(), 4);
|
||||
|
||||
for (std::size_t index = 0; index < std::size(cases); ++index) {
|
||||
EXPECT_TRUE(jobs[index].ok) << index;
|
||||
EXPECT_EQ(outputs[index], expected[index]) << index;
|
||||
}
|
||||
EXPECT_FALSE(jobs.back().ok);
|
||||
EXPECT_EQ(short_output, std::vector<std::uint8_t>(4 * 4 * 4 - 1, 0xAB));
|
||||
}
|
||||
|
||||
TEST(Etc2DecoderTest, RejectsShortBuffers) {
|
||||
const std::vector<std::uint8_t> source(8, 0);
|
||||
std::vector<std::uint8_t> output(4 * 4 * 4, 0);
|
||||
EXPECT_FALSE(DecodeEtcImage(EtcFormat::kEtc2Rgb8, source.data(), 7, 4, 4,
|
||||
output.data(), output.size()));
|
||||
EXPECT_FALSE(DecodeEtcImage(EtcFormat::kEtc2Rgb8, source.data(),
|
||||
source.size(), 4, 4, output.data(),
|
||||
output.size() - 1));
|
||||
EXPECT_FALSE(DecodeEtcImage(EtcFormat::kEtc2Rgb8, source.data(),
|
||||
source.size(), 5, 4, output.data(),
|
||||
output.size()));
|
||||
EXPECT_FALSE(DecodeEtcImage(EtcFormat::kEtc2Rgb8, nullptr, 0, 0, 0,
|
||||
output.data(), output.size()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mocktail::graphics
|
||||
@@ -1,11 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
# Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
# Copyright 2026 Mocktail Project Authors
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
readonly ROOT="${1:?source root is required}"
|
||||
readonly MANIFEST="${ROOT}/packaging/flatpak/space.bigrat.mocktail.json"
|
||||
readonly MANIFEST="${ROOT}/packaging/flatpak/io.github.CoderDayton.nightcap.json"
|
||||
readonly BUILD_HELPER="${ROOT}/scripts/build_flatpak.sh"
|
||||
readonly PAGES_HELPER="${ROOT}/scripts/assemble_flatpak_pages.sh"
|
||||
readonly GITHUB_CI="${ROOT}/.github/workflows/flatpak.yml"
|
||||
@@ -25,7 +26,7 @@ import sys
|
||||
with open(sys.argv[1], encoding="utf-8") as source:
|
||||
manifest = json.load(source)
|
||||
|
||||
assert manifest["app-id"] == "space.bigrat.mocktail"
|
||||
assert manifest["app-id"] == "io.github.CoderDayton.nightcap"
|
||||
assert manifest["runtime"] == "org.gnome.Platform"
|
||||
assert manifest["runtime-version"] == "50"
|
||||
assert manifest["sdk"] == "org.gnome.Sdk"
|
||||
@@ -129,24 +130,14 @@ grep -Fq 'CleanupStaleBuilderMounts' "${BUILD_HELPER}" ||
|
||||
Fail 'obsolete GitLab CI configuration is still present'
|
||||
grep -Fq 'name: Flatpak' "${GITHUB_CI}" ||
|
||||
Fail 'GitHub Actions workflow has no stable display name'
|
||||
validates_main=false
|
||||
if grep -Fq 'branches: [main]' "${GITHUB_CI}"; then
|
||||
validates_main=true
|
||||
elif grep -Fq 'workflow_run:' "${GITHUB_CI}" &&
|
||||
grep -Fq 'workflows: [Native packages]' "${GITHUB_CI}" &&
|
||||
grep -Fq "head_branch == 'main'" "${GITHUB_CI}"; then
|
||||
validates_main=true
|
||||
fi
|
||||
[[ "${validates_main}" == true ]] ||
|
||||
Fail 'GitHub Actions does not validate the main branch'
|
||||
grep -Fq "tags: ['v*']" "${GITHUB_CI}" ||
|
||||
Fail 'GitHub Actions does not publish on release tags'
|
||||
grep -Fq 'pull_request:' "${GITHUB_CI}" ||
|
||||
Fail 'GitHub Actions does not validate pull requests'
|
||||
grep -Fq 'workflow_dispatch:' "${GITHUB_CI}" ||
|
||||
Fail 'GitHub Actions cannot be started manually'
|
||||
grep -Fq "'assets/screenshots/**'" "${GITHUB_CI}" ||
|
||||
Fail 'Flatpak workflow does not react to screenshot changes'
|
||||
grep -Fq "'packaging/discord-join.html'" "${GITHUB_CI}" ||
|
||||
Fail 'Flatpak workflow does not publish Discord join-page changes'
|
||||
grep -Fq 'contents: read' "${GITHUB_CI}" ||
|
||||
Fail 'GitHub Actions permissions are not read-only'
|
||||
grep -Fq 'submodules: recursive' "${GITHUB_CI}" ||
|
||||
@@ -159,7 +150,7 @@ grep -Fq -- '--install-deps-from=flathub' "${GITHUB_CI}" ||
|
||||
Fail 'GitHub Actions does not install manifest dependencies from Flathub'
|
||||
grep -Fq -- '--default-branch=stable' "${GITHUB_CI}" ||
|
||||
Fail 'GitHub Actions does not produce the stable Flatpak branch'
|
||||
grep -Fq 'Mocktail-x86_64.flatpak' "${GITHUB_CI}" ||
|
||||
grep -Fq 'Nightcap-x86_64.flatpak' "${GITHUB_CI}" ||
|
||||
Fail 'GitHub Actions does not build the installable Flatpak bundle'
|
||||
grep -Fq 'actions/upload-artifact@' "${GITHUB_CI}" ||
|
||||
Fail 'GitHub Actions does not publish the Flatpak artifact'
|
||||
@@ -183,7 +174,7 @@ dry_run="$(
|
||||
)"
|
||||
grep -Fq './scripts/build_flatpak.sh' <<<"${dry_run}" ||
|
||||
Fail 'make flatpak does not invoke the guarded Flatpak builder'
|
||||
grep -Fq -- '--manifest packaging/flatpak/space.bigrat.mocktail.json' \
|
||||
grep -Fq -- '--manifest packaging/flatpak/io.github.CoderDayton.nightcap.json' \
|
||||
<<<"${dry_run}" || Fail 'make flatpak lost the canonical manifest'
|
||||
grep -Fq -- '--jobs 4' <<<"${dry_run}" ||
|
||||
Fail 'make flatpak does not cap local build parallelism'
|
||||
@@ -210,33 +201,25 @@ grep -Fq 'flatpak-builder is unavailable' "${TEMP_DIR}/stderr" ||
|
||||
Fail 'helper did not explain how to install flatpak-builder'
|
||||
|
||||
mkdir -p -- "${TEMP_DIR}/repo/objects"
|
||||
mkdir -p -- \
|
||||
"${TEMP_DIR}/native-repositories/apt" \
|
||||
"${TEMP_DIR}/native-repositories/rpm" \
|
||||
"${TEMP_DIR}/native-repositories/downloads"
|
||||
printf 'bundle\n' >"${TEMP_DIR}/Mocktail-x86_64.flatpak"
|
||||
printf 'public-key\n' >"${TEMP_DIR}/mocktail-flatpak.gpg"
|
||||
printf 'public-key\n' \
|
||||
>"${TEMP_DIR}/native-repositories/mocktail-packages.gpg"
|
||||
printf 'bundle\n' >"${TEMP_DIR}/Nightcap-x86_64.flatpak"
|
||||
printf 'public-key\n' >"${TEMP_DIR}/nightcap-flatpak.gpg"
|
||||
"${PAGES_HELPER}" \
|
||||
"${TEMP_DIR}/repo" \
|
||||
"${TEMP_DIR}/Mocktail-x86_64.flatpak" \
|
||||
"${TEMP_DIR}/mocktail-flatpak.gpg" \
|
||||
"${TEMP_DIR}/public" \
|
||||
"${TEMP_DIR}/native-repositories"
|
||||
grep -Fq 'Url=https://mocktail.bigrat.space/repo/' \
|
||||
"${TEMP_DIR}/public/mocktail.flatpakrepo" ||
|
||||
"${TEMP_DIR}/Nightcap-x86_64.flatpak" \
|
||||
"${TEMP_DIR}/nightcap-flatpak.gpg" \
|
||||
"${TEMP_DIR}/public"
|
||||
grep -Fq 'Url=https://coderdayton.github.io/nightcap/repo/' \
|
||||
"${TEMP_DIR}/public/nightcap.flatpakrepo" ||
|
||||
Fail 'published Flatpak repository URL is incorrect'
|
||||
grep -Fq 'Name=space.bigrat.mocktail' \
|
||||
"${TEMP_DIR}/public/mocktail.flatpakref" ||
|
||||
grep -Fq 'Name=io.github.CoderDayton.nightcap' \
|
||||
"${TEMP_DIR}/public/nightcap.flatpakref" ||
|
||||
Fail 'published Flatpak ref has the wrong application ID'
|
||||
grep -Fq 'GPGKey=' "${TEMP_DIR}/public/nightcap.flatpakref" ||
|
||||
Fail 'published Flatpak ref is not pinned to the signing key'
|
||||
grep -Fq 'flatpak install --user' "${TEMP_DIR}/public/index.html" ||
|
||||
Fail 'Pages landing page has no direct installation command'
|
||||
grep -Fq 'roblox://experiences/start' "${TEMP_DIR}/public/join.html" ||
|
||||
Fail 'Pages output has no Discord join bridge'
|
||||
[[ -d "${TEMP_DIR}/public/apt" &&
|
||||
-d "${TEMP_DIR}/public/rpm" &&
|
||||
-d "${TEMP_DIR}/public/downloads" ]] ||
|
||||
Fail 'Pages output has no native package repositories'
|
||||
[[ -f "${TEMP_DIR}/public/Nightcap-x86_64.flatpak" &&
|
||||
-f "${TEMP_DIR}/public/.nojekyll" ]] ||
|
||||
Fail 'Pages output is missing the bundle or the .nojekyll marker'
|
||||
|
||||
printf 'Flatpak packaging contract test passed\n'
|
||||
@@ -1,3 +1,4 @@
|
||||
// Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
#include "jnivm/jnivm.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
@@ -2675,5 +2676,30 @@ TEST_F(JniVmTest, MainGameActivityReturnsNativeHelperAndTracksCallbacks) {
|
||||
EXPECT_EQ(env->GetBooleanField(helper, is_engine_initialized), JNI_TRUE);
|
||||
}
|
||||
|
||||
TEST_F(JniVmTest, NativeQuoteInterfaceReportsKeystoreUnavailable) {
|
||||
JNIEnv* env = vm_->GetJNIEnv();
|
||||
jclass cls = env->FindClass("com/roblox/engine/jni/NativeQuoteInterface");
|
||||
ASSERT_NE(cls, nullptr);
|
||||
jmethodID method =
|
||||
env->GetStaticMethodID(cls, "requestResponse", "([B)[B");
|
||||
ASSERT_NE(method, nullptr);
|
||||
|
||||
jbyteArray challenge = env->NewByteArray(32);
|
||||
ASSERT_NE(challenge, nullptr);
|
||||
auto result = static_cast<jbyteArray>(
|
||||
env->CallStaticObjectMethod(cls, method, challenge));
|
||||
ASSERT_NE(result, nullptr);
|
||||
|
||||
const jsize length = env->GetArrayLength(result);
|
||||
ASSERT_GT(length, 2);
|
||||
std::vector<jbyte> bytes(static_cast<std::size_t>(length));
|
||||
env->GetByteArrayRegion(result, 0, length, bytes.data());
|
||||
EXPECT_EQ(bytes[0], 1);
|
||||
EXPECT_EQ(bytes[1], 0);
|
||||
const std::string message(reinterpret_cast<const char*>(bytes.data() + 2),
|
||||
static_cast<std::size_t>(length - 2));
|
||||
EXPECT_NE(message.find("AndroidKeyStore"), std::string::npos);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace jnivm
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
# Copyright 2026 Mocktail Project Authors
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@@ -77,10 +78,6 @@ grep -Fq -- \
|
||||
Fail 'AUR package embeds a build-tree compatibility manifest path'
|
||||
grep -Fq 'pkgbase = mocktail-git' "${AUR_SRCINFO}" ||
|
||||
Fail 'AUR package has no generated .SRCINFO metadata'
|
||||
grep -Fq 'paru -S mocktail-git' "${README}" ||
|
||||
Fail 'README has no AUR installation instructions'
|
||||
grep -Fq 'yay -S mocktail-git' "${README}" ||
|
||||
Fail 'README has no alternative AUR helper command'
|
||||
grep -Fq 'LINKER:--no-as-needed' "${STUB_CMAKE}" ||
|
||||
Fail 'system shims can lose their host libc dependencies'
|
||||
grep -Fq 'MOCKTAIL_MINIZIP_HAS_STREAM_TELL' "${STUB_CMAKE}" ||
|
||||
@@ -118,10 +115,6 @@ if grep -Fq "'site/**'" "${WORKFLOW}"; then
|
||||
Fail 'native packages ignore website changes required by Pages publication'
|
||||
fi
|
||||
|
||||
grep -Fq 'sudo dnf install mocktail-nightly' "${README}" ||
|
||||
Fail 'README has no nightly DNF installation command'
|
||||
grep -Fq 'sudo apt install mocktail-nightly' "${README}" ||
|
||||
Fail 'README has no nightly APT installation command'
|
||||
grep -Fq 'Ubuntu 26.04+' "${README}" ||
|
||||
Fail 'README has no Ubuntu source dependency guide'
|
||||
grep -Fq 'Arch Linux' "${README}" ||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
#include "mocktail/graphics/present_mode_policy.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
@@ -7,8 +8,11 @@ namespace graphics {
|
||||
namespace {
|
||||
|
||||
TEST(PresentModePolicyTest, DerivesDisplayAndUnlimitedDefaults) {
|
||||
// The unmanaged default keeps Roblox's frame cap but still selects the
|
||||
// lowest-latency synchronized present mode instead of the engine's FIFO.
|
||||
EXPECT_EQ(ResolvePresentModePolicy("auto", "-1"),
|
||||
PresentModePolicy::kHostDefault);
|
||||
PresentModePolicy::kVsync);
|
||||
EXPECT_EQ(ResolvePresentModePolicy("auto", ""), PresentModePolicy::kVsync);
|
||||
EXPECT_EQ(ResolvePresentModePolicy("auto", "display"),
|
||||
PresentModePolicy::kVsync);
|
||||
EXPECT_EQ(ResolvePresentModePolicy("auto", "60"),
|
||||
|
||||
@@ -569,31 +569,6 @@ TEST(RobloxTextDisplayStateTest, WrapsMultilineTextInsideNativeTextBox) {
|
||||
EXPECT_TRUE(overlay.Shutdown().ok());
|
||||
}
|
||||
|
||||
TEST(RobloxTextDisplayStateTest, AdvancesCaretForTrailingSpace) {
|
||||
RobloxTextSurfaceOverlay overlay;
|
||||
ASSERT_TRUE(overlay.Initialize({800, 600}).ok());
|
||||
RobloxTextDisplaySink sink = overlay.sink();
|
||||
const auto rightmost_pixel = [&](const std::string& text,
|
||||
RobloxTextDisplayEvent event) {
|
||||
RobloxTextDisplayUpdate update =
|
||||
Show(1, text, static_cast<int32_t>(text.size()));
|
||||
update.event = event;
|
||||
update.font_size = 18.0F;
|
||||
sink.update(sink.context, update);
|
||||
MocktailTextOverlayFrameInfo frame;
|
||||
EXPECT_TRUE(overlay.QueryFrame(&frame));
|
||||
std::vector<std::uint8_t> rgba(frame.rgba_bytes);
|
||||
EXPECT_TRUE(overlay.CopyFrame(frame.revision, rgba.data(), rgba.size()));
|
||||
return FindAlphaBounds(frame, rgba).maximum_x;
|
||||
};
|
||||
|
||||
const int typed = rightmost_pixel("hi", RobloxTextDisplayEvent::kShow);
|
||||
const int spaced = rightmost_pixel("hi ", RobloxTextDisplayEvent::kUpdate);
|
||||
|
||||
EXPECT_GT(spaced, typed);
|
||||
EXPECT_TRUE(overlay.Shutdown().ok());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace runtime
|
||||
} // namespace mocktail
|
||||
@@ -1,3 +1,4 @@
|
||||
// Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
#include "runtime/runtime_config_file.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
@@ -139,7 +140,9 @@ TEST(RuntimeConfigBootstrapTest,
|
||||
"# DFIntTaskSchedulerTargetFps without a whitelist.\n "
|
||||
"# frame_rate_limit: -1",
|
||||
"# Optional presentation synchronization override: auto, on, or "
|
||||
"off.\n # vsync: off",
|
||||
"off. auto\n # presents through the lowest-latency synchronized "
|
||||
"mode the driver offers\n # unless frame_rate_limit is unlimited.\n"
|
||||
" # vsync: off",
|
||||
"# physical CPU core. A place's Lua/main thread can still remain "
|
||||
"serial.\n multithreaded_rendering: false",
|
||||
"# sizes and coalesces midphase work. Supported: auto, latency, "
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
#include "runtime/runtime_paths.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
@@ -585,9 +586,12 @@ TEST(RuntimePathsTest, ExportsXdgBackedAndroidPathsAndCreatesParents) {
|
||||
"MOCKTAIL_ANDROID_CACHE_HOST_ROOT");
|
||||
const ScopedProcessEnvironment shader_cache(
|
||||
"MOCKTAIL_VULKAN_SHADER_CACHE_HOST_PATH");
|
||||
const ScopedProcessEnvironment texture_overrides(
|
||||
"MOCKTAIL_TEXTURE_OVERRIDE_DIR");
|
||||
ASSERT_EQ(unsetenv("MOCKTAIL_RUNTIME_ROOT"), 0);
|
||||
ASSERT_EQ(unsetenv("MOCKTAIL_ANDROID_CACHE_HOST_ROOT"), 0);
|
||||
ASSERT_EQ(unsetenv("MOCKTAIL_VULKAN_SHADER_CACHE_HOST_PATH"), 0);
|
||||
ASSERT_EQ(unsetenv("MOCKTAIL_TEXTURE_OVERRIDE_DIR"), 0);
|
||||
|
||||
const MapEnvironment environment({
|
||||
{"HOME", temporary.path().string()},
|
||||
@@ -605,6 +609,8 @@ TEST(RuntimePathsTest, ExportsXdgBackedAndroidPathsAndCreatesParents) {
|
||||
paths.android_cache_root().c_str());
|
||||
EXPECT_STREQ(std::getenv("MOCKTAIL_VULKAN_SHADER_CACHE_HOST_PATH"),
|
||||
paths.vulkan_shader_cache_file().c_str());
|
||||
EXPECT_STREQ(std::getenv("MOCKTAIL_TEXTURE_OVERRIDE_DIR"),
|
||||
(paths.config_root() / "textures").c_str());
|
||||
EXPECT_TRUE(std::filesystem::is_directory(paths.android_runtime_root()));
|
||||
EXPECT_TRUE(std::filesystem::is_directory(paths.android_cache_root()));
|
||||
EXPECT_TRUE(std::filesystem::is_directory(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
# Copyright 2026 Mocktail Project Authors
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@@ -18,9 +19,9 @@ else
|
||||
readonly runtime="${install_root}/usr/lib/mocktail"
|
||||
fi
|
||||
readonly data="${install_root}/usr/share/mocktail"
|
||||
readonly desktop="${install_root}/usr/share/applications/space.bigrat.mocktail.desktop"
|
||||
readonly metainfo="${install_root}/usr/share/metainfo/space.bigrat.mocktail.metainfo.xml"
|
||||
readonly icon="${install_root}/usr/share/icons/hicolor/scalable/apps/space.bigrat.mocktail.svg"
|
||||
readonly desktop="${install_root}/usr/share/applications/io.github.CoderDayton.nightcap.desktop"
|
||||
readonly metainfo="${install_root}/usr/share/metainfo/io.github.CoderDayton.nightcap.metainfo.xml"
|
||||
readonly icon="${install_root}/usr/share/icons/hicolor/scalable/apps/io.github.CoderDayton.nightcap.svg"
|
||||
|
||||
[[ -x "${binary}" ]]
|
||||
[[ -x "${runtime}/mocktail_updater" ]]
|
||||
@@ -47,14 +48,14 @@ if find "${install_root}/usr" -type f \( -name '*.py' -o -name '*.sh' \) \
|
||||
fi
|
||||
|
||||
[[ -f "${desktop}" ]]
|
||||
grep -Fq 'Name=Mocktail' "${desktop}"
|
||||
grep -Fxq 'Exec=mocktail %u' "${desktop}"
|
||||
grep -Fq 'Icon=space.bigrat.mocktail' "${desktop}"
|
||||
grep -Fq 'Name=Nightcap' "${desktop}"
|
||||
grep -Fxq 'Exec=env SDL_VIDEODRIVER=wayland,x11 /usr/bin/mocktail %u' "${desktop}"
|
||||
grep -Fq 'Icon=io.github.CoderDayton.nightcap' "${desktop}"
|
||||
[[ -f "${metainfo}" ]]
|
||||
grep -Fq '<id>space.bigrat.mocktail</id>' "${metainfo}"
|
||||
grep -Fq '<id>io.github.CoderDayton.nightcap</id>' "${metainfo}"
|
||||
[[ -f "${icon}" ]]
|
||||
for icon_size in 16 22 24 32 36 48 64 72 96 128 192 256 512; do
|
||||
raster_icon="${install_root}/usr/share/icons/hicolor/${icon_size}x${icon_size}/apps/space.bigrat.mocktail.png"
|
||||
raster_icon="${install_root}/usr/share/icons/hicolor/${icon_size}x${icon_size}/apps/io.github.CoderDayton.nightcap.png"
|
||||
[[ -f "${raster_icon}" ]]
|
||||
file "${raster_icon}" | grep -Fq "${icon_size} x ${icon_size}"
|
||||
done
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
#include "mocktail/graphics/texture_override.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace mocktail::graphics {
|
||||
namespace {
|
||||
|
||||
class TempDir {
|
||||
public:
|
||||
TempDir() {
|
||||
char pattern[] = "/tmp/mocktail-texture-override-XXXXXX";
|
||||
const char* made = mkdtemp(pattern);
|
||||
path_ = made != nullptr ? made : "";
|
||||
}
|
||||
~TempDir() {
|
||||
std::error_code error;
|
||||
std::filesystem::remove_all(path_, error);
|
||||
}
|
||||
const std::string& path() const { return path_; }
|
||||
|
||||
private:
|
||||
std::string path_;
|
||||
};
|
||||
|
||||
RgbaImage Gradient(std::uint32_t width, std::uint32_t height) {
|
||||
RgbaImage image;
|
||||
image.width = width;
|
||||
image.height = height;
|
||||
image.pixels.resize(static_cast<std::size_t>(width) * height * 4);
|
||||
for (std::uint32_t y = 0; y < height; ++y) {
|
||||
for (std::uint32_t x = 0; x < width; ++x) {
|
||||
std::uint8_t* pixel = image.pixels.data() + (y * width + x) * 4;
|
||||
pixel[0] = static_cast<std::uint8_t>(x * 7);
|
||||
pixel[1] = static_cast<std::uint8_t>(y * 11);
|
||||
pixel[2] = static_cast<std::uint8_t>(x + y);
|
||||
pixel[3] = static_cast<std::uint8_t>(255 - x);
|
||||
}
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
TEST(TextureOverrideTest, HashIsStableAndDependsOnBytes) {
|
||||
const std::uint8_t a[] = {1, 2, 3, 4};
|
||||
const std::uint8_t b[] = {1, 2, 3, 5};
|
||||
EXPECT_EQ(HashBytes(a, sizeof(a)), HashBytes(a, sizeof(a)));
|
||||
EXPECT_NE(HashBytes(a, sizeof(a)), HashBytes(b, sizeof(b)));
|
||||
EXPECT_EQ(HashName(0x0123456789abcdefULL), "0123456789abcdef");
|
||||
}
|
||||
|
||||
TEST(TextureOverrideTest, PngRoundTripKeepsEveryPixel) {
|
||||
TempDir dir;
|
||||
ASSERT_FALSE(dir.path().empty());
|
||||
const RgbaImage image = Gradient(9, 5);
|
||||
const std::string path = dir.path() + "/image.png";
|
||||
ASSERT_TRUE(WritePngRgba(path, image));
|
||||
RgbaImage read;
|
||||
ASSERT_TRUE(ReadPngRgba(path, &read));
|
||||
EXPECT_EQ(read.width, image.width);
|
||||
EXPECT_EQ(read.height, image.height);
|
||||
EXPECT_EQ(read.pixels, image.pixels);
|
||||
}
|
||||
|
||||
TEST(TextureOverrideTest, ReadPngFailsForMissingFile) {
|
||||
RgbaImage read;
|
||||
EXPECT_FALSE(ReadPngRgba("/nonexistent/mocktail.png", &read));
|
||||
}
|
||||
|
||||
TEST(TextureOverrideTest, ResampleAveragesSourceBoxes) {
|
||||
RgbaImage source;
|
||||
source.width = 2;
|
||||
source.height = 2;
|
||||
source.pixels = {0, 0, 0, 0, 100, 100, 100, 100,
|
||||
200, 200, 200, 200, 100, 100, 100, 100};
|
||||
std::uint8_t one[4] = {};
|
||||
ResampleRgba(source, 1, 1, one);
|
||||
EXPECT_EQ(one[0], 100);
|
||||
EXPECT_EQ(one[3], 100);
|
||||
|
||||
std::uint8_t four[16] = {};
|
||||
ResampleRgba(source, 2, 2, four);
|
||||
EXPECT_EQ(std::vector<std::uint8_t>(four, four + 16), source.pixels);
|
||||
|
||||
RgbaImage single;
|
||||
single.width = 1;
|
||||
single.height = 1;
|
||||
single.pixels = {9, 8, 7, 6};
|
||||
std::uint8_t grown[16] = {};
|
||||
ResampleRgba(single, 2, 2, grown);
|
||||
for (int pixel = 0; pixel < 4; ++pixel) {
|
||||
EXPECT_EQ(grown[pixel * 4 + 0], 9);
|
||||
EXPECT_EQ(grown[pixel * 4 + 3], 6);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TextureOverrideTest, LookupFindsPngNamedByHashAndCachesMisses) {
|
||||
TempDir dir;
|
||||
ASSERT_FALSE(dir.path().empty());
|
||||
const RgbaImage image = Gradient(4, 4);
|
||||
const std::uint64_t hash = 0xfeedfacecafebeefULL;
|
||||
ASSERT_TRUE(WritePngRgba(dir.path() + "/" + HashName(hash) + ".png", image));
|
||||
|
||||
TextureOverrides overrides(dir.path(), "");
|
||||
EXPECT_TRUE(overrides.enabled());
|
||||
const auto found = overrides.Lookup(hash);
|
||||
ASSERT_NE(found, nullptr);
|
||||
EXPECT_EQ(found->pixels, image.pixels);
|
||||
EXPECT_EQ(overrides.Lookup(hash), found);
|
||||
EXPECT_EQ(overrides.Lookup(hash + 1), nullptr);
|
||||
|
||||
TextureOverrides disabled("", "");
|
||||
EXPECT_FALSE(disabled.enabled());
|
||||
EXPECT_EQ(disabled.Lookup(hash), nullptr);
|
||||
}
|
||||
|
||||
TEST(TextureOverrideTest, DumpWritesOnePngPerHash) {
|
||||
TempDir dir;
|
||||
ASSERT_FALSE(dir.path().empty());
|
||||
const RgbaImage image = Gradient(3, 2);
|
||||
TextureOverrides overrides("", dir.path());
|
||||
EXPECT_TRUE(overrides.enabled());
|
||||
const std::uint64_t hash = 0x1234ULL;
|
||||
overrides.Dump(hash, image.width, image.height, image.pixels.data());
|
||||
overrides.Dump(hash, image.width, image.height, image.pixels.data());
|
||||
const std::string expected =
|
||||
dir.path() + "/" + HashName(hash) + "_3x2.png";
|
||||
ASSERT_TRUE(std::filesystem::exists(expected));
|
||||
RgbaImage read;
|
||||
ASSERT_TRUE(ReadPngRgba(expected, &read));
|
||||
EXPECT_EQ(read.pixels, image.pixels);
|
||||
std::size_t files = 0;
|
||||
for (const auto& entry : std::filesystem::directory_iterator(dir.path())) {
|
||||
static_cast<void>(entry);
|
||||
++files;
|
||||
}
|
||||
EXPECT_EQ(files, 1u);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mocktail::graphics
|
||||