Merge remote-tracking branch 'upstream/main' into vr

# Conflicts:
#	stubs/libandroid_stub.cc
This commit is contained in:
dim-ghub committed 2026-10-12 21:50:15 -05:00
commit 33dd7769f0
18 files changed
+474 -18

No files matched your search

+118
View File
@@ -0,0 +1,118 @@
name: Publish AUR packages
on:
workflow_dispatch:
inputs:
tag:
description: Release tag to publish, such as v0.3.4
required: true
type: string
workflow_call:
inputs:
tag:
required: true
type: string
secrets:
AUR_SSH_PRIVATE_KEY:
required: false
permissions:
contents: read
concurrency:
group: aur-publish
cancel-in-progress: false
jobs:
publish:
name: Publish nightcap and nightcap-bin
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- name: Check out the release
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.tag }}
persist-credentials: false
- name: Render the recipes
shell: bash
env:
TAG: ${{ inputs.tag }}
run: |
set -Eeuo pipefail
[[ "$TAG" =~ ^v[0-9]+(\.[0-9]+)*$ ]]
version="${TAG#v}"
archive="nightcap-${version}-1-x86_64.pkg.tar.zst"
# nightcap-bin installs the pacman package from the release page, so
# its checksum is the one published beside that asset.
checksum="$(curl -fsSL \
"https://github.com/${GITHUB_REPOSITORY}/releases/download/${TAG}/${archive}.sha256" |
awk '{print $1}')"
[[ "$checksum" =~ ^[0-9a-f]{64}$ ]]
sed -i "s/^pkgver=.*/pkgver=${version}/" \
packaging/aur/nightcap/PKGBUILD packaging/aur/nightcap-bin/PKGBUILD
sed -i "s/^sha256sums=('SKIP')$/sha256sums=('${checksum}')/" \
packaging/aur/nightcap-bin/PKGBUILD
grep -Fqx "sha256sums=('${checksum}')" packaging/aur/nightcap-bin/PKGBUILD
docker run --rm \
--volume "$GITHUB_WORKSPACE/packaging/aur:/aur" \
ghcr.io/archlinux/archlinux:base-devel@sha256:c3953446016873a756fb7814f10087cac1634c127f7302e80c81c512da796d05 bash -Eeuo pipefail -c '
useradd --create-home builder
for package in nightcap nightcap-bin; do
install -d -o builder -g builder "/tmp/${package}"
install -o builder -g builder -m 0644 \
"/aur/${package}/PKGBUILD" "/tmp/${package}/PKGBUILD"
runuser -u builder -- bash -c \
"cd /tmp/${package} && makepkg --printsrcinfo" \
> "/aur/${package}/.SRCINFO"
done
'
for package in nightcap nightcap-bin; do
grep -Fqx "pkgver=${version}" "packaging/aur/${package}/PKGBUILD"
grep -Fqx $'\tpkgver = '"${version}" "packaging/aur/${package}/.SRCINFO"
done
echo "VERSION=${version}" >> "$GITHUB_ENV"
- name: Push to the AUR
shell: bash
env:
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
run: |
set -Eeuo pipefail
if [[ -z "${AUR_SSH_PRIVATE_KEY}" ]]; then
echo "::notice::AUR_SSH_PRIVATE_KEY is not set; nothing was pushed to the AUR"
exit 0
fi
ssh_dir="$(mktemp -d)"
trap 'rm -rf "$ssh_dir"' EXIT
install -m 0600 /dev/null "$ssh_dir/key"
printf '%s\n' "$AUR_SSH_PRIVATE_KEY" > "$ssh_dir/key"
# The AUR lists this key's fingerprint on its front page:
# SHA256:RFzBCUItH9LZS0cKB5UE6ceAYhBD5C8GeOBip8Z11+4
printf '%s\n' \
'aur.archlinux.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEuBKrPzbawxA/k2g6NcyV5jmqwJ2s+zpgZGZ7tpLIcN' \
> "$ssh_dir/known_hosts"
export GIT_SSH_COMMAND="ssh -i $ssh_dir/key -o IdentitiesOnly=yes -o UserKnownHostsFile=$ssh_dir/known_hosts -o StrictHostKeyChecking=yes"
for package in nightcap nightcap-bin; do
checkout="$ssh_dir/$package"
git clone "ssh://aur@aur.archlinux.org/${package}.git" "$checkout"
cp "packaging/aur/${package}/PKGBUILD" "packaging/aur/${package}/.SRCINFO" \
"$checkout/"
git -C "$checkout" add PKGBUILD .SRCINFO
if git -C "$checkout" diff --cached --quiet; then
echo "::notice::${package} is already at ${VERSION}"
continue
fi
git -C "$checkout" \
-c user.name='Nightcap release' \
-c user.email='CoderDayton@users.noreply.github.com' \
commit --quiet -m "Update to ${VERSION}"
# The AUR only accepts the master branch.
git -C "$checkout" push origin HEAD:master
done
+12
View File
@@ -336,3 +336,15 @@ jobs:
--title "Nightcap $tag" --notes-file release/notes.md
gh release upload "$tag" --repo "$GITHUB_REPOSITORY" --clobber \
"${uploads[@]}"
# nightcap-bin downloads the pacman package from the release page, so this
# waits for the upload.
aur:
name: Publish AUR packages
if: startsWith(github.ref, 'refs/tags/v')
needs: publish
uses: ./.github/workflows/aur-publish.yml
with:
tag: ${{ github.ref_name }}
secrets:
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
+6
View File
@@ -11,6 +11,12 @@ Release notes on GitHub are taken from the matching section here.
ships at the same path, such as a cursor, a font or a sky texture, and it stays in place when Roblox
updates. The layout matches Sober's `asset_overlay` folder. `MOCKTAIL_ASSET_OVERLAY_DIR` moves it. Thanks to
@dim-ghub.
- The asset overlay folder now also replaces files Roblox downloads and no longer ships, such as the shift
lock and text cursors. Put the file under `content/`, at the path Roblox uses. Thanks to @dim-ghub for the
idea.
- `MOCKTAIL_ASSET_TRACE=1` now also prints the files Roblox asks for and does not get, as `[asset] miss`.
- Arch Linux: `nightcap-git` is on the AUR and builds the current `main` branch. Thanks to @dim-ghub, who
maintains it.
## [0.3.4] - 2026-10-11
+1
View File
@@ -300,6 +300,7 @@ add_library(mocktail_runtime STATIC
src/runtime/payload_update_preflight.cc
src/runtime/process_launch_policy.cc
src/runtime/process_diagnostics.cc
src/runtime/asset_overlay_names.cc
src/runtime/runtime_config.cc
src/runtime/runtime_config_bootstrap.cc
src/runtime/runtime_config_file.cc
+11 -2
View File
@@ -84,8 +84,17 @@ sudo pacman -U nightcap-<version>-1-x86_64.pkg.tar.zst
```
They install `/usr/bin/mocktail`, so they conflict with upstream's `mocktail`
package. Nightcap is not in any distribution repository or the AUR; the
recipes under `packaging/aur` are kept for local builds only.
package.
On Arch Linux, [`nightcap-git`](https://aur.archlinux.org/packages/nightcap-git)
is on the AUR. It builds the current `main` branch and is maintained by
[@dim-ghub](https://github.com/dim-ghub):
```bash
yay -S nightcap-git
```
Nightcap is in no other distribution repository.
Or build and install from source using the steps under
[Building](#building).
+27
View File
@@ -0,0 +1,27 @@
#ifndef MOCKTAIL_RUNTIME_ASSET_OVERLAY_NAMES_H_
#define MOCKTAIL_RUNTIME_ASSET_OVERLAY_NAMES_H_
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <string>
#include <vector>
namespace mocktail {
namespace runtime {
// The rbxasset:// name of every regular file under <overlay_dir>/content, such
// as rbxasset://textures/MouseLockedCursor.png. Sorted.
std::vector<std::string> OverlayRbxAssetNames(
const std::filesystem::path& overlay_dir);
// Roblox opens no local file for an rbxasset:// name it maps to a download.
// Writes '!' over the first byte of each whole NUL-terminated copy of a name
// in `data`, which unmaps it. Returns the number of copies changed.
std::size_t DisableAssetNameMappings(std::uint8_t* data, std::size_t size,
const std::vector<std::string>& names);
}
}
#endif
+1 -1
View File
@@ -1,7 +1,7 @@
# Maintainer: vii <coderdayton14@gmail.com>
pkgname=nightcap
pkgver=0.3.1
pkgver=0.3.4
pkgrel=1
pkgdesc='Android x86-64 Roblox compatibility runtime for Linux'
arch=('x86_64')
+3 -3
View File
@@ -1,6 +1,6 @@
pkgbase = nightcap-bin
pkgdesc = Android x86-64 Roblox compatibility runtime for Linux (prebuilt)
pkgver = 0.3.1
pkgver = 0.3.4
pkgrel = 1
url = https://github.com/CoderDayton/nightcap
arch = x86_64
@@ -34,10 +34,10 @@ pkgbase = nightcap-bin
conflicts = mocktail
conflicts = nightcap
conflicts = nightcap-git
noextract = nightcap-0.3.1-1-x86_64.pkg.tar.zst
noextract = nightcap-0.3.4-1-x86_64.pkg.tar.zst
options = !debug
options = !strip
source = nightcap-0.3.1-1-x86_64.pkg.tar.zst::https://github.com/CoderDayton/nightcap/releases/download/v0.3.1/nightcap-0.3.1-1-x86_64.pkg.tar.zst
source = nightcap-0.3.4-1-x86_64.pkg.tar.zst::https://github.com/CoderDayton/nightcap/releases/download/v0.3.4/nightcap-0.3.4-1-x86_64.pkg.tar.zst
sha256sums = SKIP
pkgname = nightcap-bin
+1 -1
View File
@@ -1,7 +1,7 @@
# Maintainer: vii <coderdayton14 at gmail dot com>
pkgname=nightcap-bin
pkgver=0.3.1
pkgver=0.3.4
pkgrel=1
pkgdesc='Android x86-64 Roblox compatibility runtime for Linux (prebuilt)'
arch=('x86_64')
+2 -2
View File
@@ -1,6 +1,6 @@
pkgbase = nightcap
pkgdesc = Android x86-64 Roblox compatibility runtime for Linux
pkgver = 0.3.1
pkgver = 0.3.4
pkgrel = 1
url = https://github.com/CoderDayton/nightcap
arch = x86_64
@@ -42,7 +42,7 @@ pkgbase = nightcap
conflicts = nightcap-git
options = !debug
options = !lto
source = nightcap::git+https://github.com/CoderDayton/nightcap.git#tag=v0.3.1
source = nightcap::git+https://github.com/CoderDayton/nightcap.git#tag=v0.3.4
sha256sums = SKIP
pkgname = nightcap
+1 -1
View File
@@ -1,7 +1,7 @@
# Maintainer: vii <coderdayton14 at gmail dot com>
pkgname=nightcap
pkgver=0.3.1
pkgver=0.3.4
pkgrel=1
pkgdesc='Android x86-64 Roblox compatibility runtime for Linux'
arch=('x86_64')
+44
View File
@@ -70,6 +70,7 @@
#include "runtime/jnivm_platform_web_callbacks.h"
#include "runtime/owned_pthread.h"
#include "runtime/platform_cache_migration.h"
#include "runtime/asset_overlay_names.h"
#include "runtime/roblox_app_lifecycle.h"
#include "runtime/roblox_capability_resolver.h"
#include "runtime/roblox_platform_web_symbols.h"
@@ -1964,6 +1965,48 @@ extern "C" void MocktailConstructorWrapper() {
MaybePrintLibRobloxConstructorSummary(idx);
}
// Unmaps the rbxasset:// names that have a file in the asset overlay folder, so
// Roblox opens those files instead of downloading the stock asset. Must run
// before any libroblox code does: the segment loses PROT_EXEC while it is
// written.
void DisableOverlaidAssetMappings(uintptr_t base) {
const std::vector<std::string> names = mocktail::runtime::OverlayRbxAssetNames(
CurrentRuntimePaths().asset_overlay_dir());
const long page_size = sysconf(_SC_PAGESIZE);
if (names.empty() || page_size <= 0) {
return;
}
const auto* header = reinterpret_cast<const Elf64_Ehdr*>(base);
const auto* segments =
reinterpret_cast<const Elf64_Phdr*>(base + header->e_phoff);
std::size_t changed = 0;
for (std::size_t index = 0; index < header->e_phnum; ++index) {
const Elf64_Phdr& segment = segments[index];
if (segment.p_type != PT_LOAD || (segment.p_flags & PF_W) != 0 ||
segment.p_filesz == 0) {
continue;
}
const uintptr_t begin = base + segment.p_vaddr;
const uintptr_t page_mask = static_cast<uintptr_t>(page_size) - 1;
const uintptr_t page_begin = begin & ~page_mask;
const std::size_t span =
((begin + segment.p_filesz + page_mask) & ~page_mask) - page_begin;
const int original =
PROT_READ | ((segment.p_flags & PF_X) != 0 ? PROT_EXEC : 0);
if (mprotect(reinterpret_cast<void*>(page_begin), span,
PROT_READ | PROT_WRITE) != 0) {
continue;
}
changed += mocktail::runtime::DisableAssetNameMappings(
reinterpret_cast<std::uint8_t*>(begin), segment.p_filesz, names);
mprotect(reinterpret_cast<void*>(page_begin), span, original);
}
std::cout << " [asset] overlay holds " << names.size()
<< " rbxasset file(s); unmapped " << changed
<< " download name(s)\n"
<< std::flush;
}
extern "C" void mocktail_before_soinfo_constructors(const char* realpath,
uintptr_t base) {
if (realpath == nullptr || std::strstr(realpath, "libroblox.so") == nullptr ||
@@ -1972,6 +2015,7 @@ extern "C" void mocktail_before_soinfo_constructors(const char* realpath,
}
g_libroblox_base = base;
DisableOverlaidAssetMappings(base);
g_mocktail_abort_libroblox_base = base;
g_libroblox_base_static = base;
+64
View File
@@ -0,0 +1,64 @@
#include "runtime/asset_overlay_names.h"
#include <algorithm>
#include <cstring>
#include <system_error>
namespace mocktail {
namespace runtime {
std::vector<std::string> OverlayRbxAssetNames(
const std::filesystem::path& overlay_dir) {
std::vector<std::string> names;
const std::filesystem::path content = overlay_dir / "content";
std::error_code error;
std::filesystem::recursive_directory_iterator it(
content, std::filesystem::directory_options::skip_permission_denied,
error);
const std::filesystem::recursive_directory_iterator end;
for (; !error && it != end; it.increment(error)) {
std::error_code entry_error;
if (!it->is_regular_file(entry_error)) {
continue;
}
const std::filesystem::path relative =
it->path().lexically_relative(content);
names.push_back("rbxasset://" + relative.generic_string());
}
std::sort(names.begin(), names.end());
return names;
}
std::size_t DisableAssetNameMappings(std::uint8_t* data, std::size_t size,
const std::vector<std::string>& names) {
std::size_t changed = 0;
if (data == nullptr) {
return changed;
}
std::uint8_t* const end = data + size;
for (const std::string& name : names) {
// The trailing NUL is part of the needle, so a longer string never matches.
const std::size_t needle_size = name.size() + 1;
if (name.empty() || needle_size > size) {
continue;
}
std::uint8_t* cursor = data;
while (cursor + needle_size <= end) {
auto* found = static_cast<std::uint8_t*>(
memmem(cursor, static_cast<std::size_t>(end - cursor), name.c_str(),
needle_size));
if (found == nullptr) {
break;
}
if (found == data || found[-1] == 0) {
found[0] = '!';
++changed;
}
cursor = found + needle_size;
}
}
return changed;
}
} // namespace runtime
} // namespace mocktail
+2 -1
View File
@@ -388,7 +388,8 @@ AAsset* AAssetManager_open(AAssetManager* mgr, const char* filename,
ResolvedAsset resolved = ResolveAssetPath(filename);
if (resolved.path.empty()) {
if (AssetTraceEnabled()) {
std::fprintf(stderr, "[asset] open failed for %s\n", filename);
std::fprintf(stderr, "[asset] miss %s\n",
filename != nullptr ? filename : "(null)");
}
return nullptr;
}
+7
View File
@@ -304,6 +304,12 @@ target_link_libraries(runtime_paths_test PRIVATE
GTest::gtest_main
)
add_executable(asset_overlay_names_test asset_overlay_names_test.cc)
target_link_libraries(asset_overlay_names_test PRIVATE
Mocktail::Runtime
GTest::gtest_main
)
add_executable(single_instance_lock_test single_instance_lock_test.cc)
target_link_libraries(single_instance_lock_test PRIVATE
Mocktail::Runtime
@@ -1148,6 +1154,7 @@ gtest_discover_tests(game_mode_link_namespace_test PROPERTIES
)
gtest_discover_tests(memory_limit_test)
gtest_discover_tests(runtime_paths_test)
gtest_discover_tests(asset_overlay_names_test)
gtest_discover_tests(single_instance_lock_test)
gtest_discover_tests(supported_launch_policy_test)
gtest_discover_tests(process_launch_policy_test)
+122
View File
@@ -0,0 +1,122 @@
#include "runtime/asset_overlay_names.h"
#include <gtest/gtest.h>
#include <unistd.h>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <string>
#include <string_view>
#include <vector>
namespace {
using mocktail::runtime::DisableAssetNameMappings;
using mocktail::runtime::OverlayRbxAssetNames;
class TemporaryDirectory {
public:
TemporaryDirectory() {
char pattern[] = "/tmp/mocktail_asset_overlay_names_XXXXXX";
char* created = mkdtemp(pattern);
if (created != nullptr) {
path_ = created;
}
}
~TemporaryDirectory() {
std::error_code error;
std::filesystem::remove_all(path_, error);
}
const std::filesystem::path& path() const { return path_; }
private:
std::filesystem::path path_;
};
void WriteFile(const std::filesystem::path& path) {
std::filesystem::create_directories(path.parent_path());
std::ofstream(path) << "x";
}
// Joins strings the way a string table stores them: each followed by a NUL.
std::vector<std::uint8_t> StringTable(
const std::vector<std::string_view>& strings) {
std::vector<std::uint8_t> table;
for (std::string_view text : strings) {
table.insert(table.end(), text.begin(), text.end());
table.push_back(0);
}
return table;
}
std::string TextAt(const std::vector<std::uint8_t>& table, std::size_t offset) {
return std::string(reinterpret_cast<const char*>(table.data()) + offset);
}
TEST(AssetOverlayNamesTest, ListsFilesUnderContentAsRbxAssetNames) {
TemporaryDirectory overlay;
ASSERT_FALSE(overlay.path().empty());
WriteFile(overlay.path() / "content/textures/MouseLockedCursor.png");
WriteFile(overlay.path() /
"content/textures/Cursors/KeyboardMouse/IBeamCursor.png");
WriteFile(overlay.path() / "notes.txt");
std::filesystem::create_directories(overlay.path() / "content/empty");
EXPECT_EQ(OverlayRbxAssetNames(overlay.path()),
(std::vector<std::string>{
"rbxasset://textures/Cursors/KeyboardMouse/IBeamCursor.png",
"rbxasset://textures/MouseLockedCursor.png",
}));
}
TEST(AssetOverlayNamesTest, MissingOverlayFolderHasNoNames) {
TemporaryDirectory overlay;
ASSERT_FALSE(overlay.path().empty());
EXPECT_TRUE(OverlayRbxAssetNames(overlay.path() / "absent").empty());
EXPECT_TRUE(OverlayRbxAssetNames(overlay.path()).empty());
}
TEST(AssetOverlayNamesTest, ChangesOnlyTheNamedWholeStrings) {
const std::string name = "rbxasset://textures/MouseLockedCursor.png";
const std::string longer = name + ".bak";
const std::string prefixed = "x" + name;
std::vector<std::uint8_t> table =
StringTable({"first", name, longer, prefixed, name});
const std::size_t first_copy = 6;
const std::size_t longer_copy = first_copy + name.size() + 1;
const std::size_t prefixed_copy = longer_copy + longer.size() + 1;
const std::size_t second_copy = prefixed_copy + prefixed.size() + 1;
EXPECT_EQ(DisableAssetNameMappings(table.data(), table.size(), {name}), 2u);
EXPECT_EQ(TextAt(table, 0), "first");
EXPECT_EQ(TextAt(table, first_copy), "!" + name.substr(1));
EXPECT_EQ(TextAt(table, longer_copy), longer);
EXPECT_EQ(TextAt(table, prefixed_copy), prefixed);
EXPECT_EQ(TextAt(table, second_copy), "!" + name.substr(1));
}
TEST(AssetOverlayNamesTest, MatchesANameAtTheStartOfTheData) {
const std::string name = "rbxasset://textures/IBeamCursor.png";
std::vector<std::uint8_t> table = StringTable({name, "other"});
EXPECT_EQ(DisableAssetNameMappings(table.data(), table.size(), {name}), 1u);
EXPECT_EQ(TextAt(table, 0), "!" + name.substr(1));
}
TEST(AssetOverlayNamesTest, LeavesDataAloneWithoutAMatch) {
std::vector<std::uint8_t> table = StringTable({"alpha", "beta"});
const std::vector<std::uint8_t> before = table;
EXPECT_EQ(DisableAssetNameMappings(table.data(), table.size(),
{"rbxasset://missing.png", ""}),
0u);
EXPECT_EQ(table, before);
EXPECT_EQ(DisableAssetNameMappings(nullptr, 0, {"alpha"}), 0u);
}
} // namespace
+35 -5
View File
@@ -15,6 +15,7 @@ readonly AUR_BIN_SRCINFO="${ROOT}/packaging/aur/nightcap-bin/.SRCINFO"
readonly AUR_PKGBUILD="${ROOT}/packaging/aur/nightcap-git/PKGBUILD"
readonly AUR_SRCINFO="${ROOT}/packaging/aur/nightcap-git/.SRCINFO"
readonly AUR_WORKFLOW="${ROOT}/.github/workflows/aur.yml"
readonly AUR_PUBLISH_WORKFLOW="${ROOT}/.github/workflows/aur-publish.yml"
readonly WORKFLOW="${ROOT}/.github/workflows/packages.yml"
readonly RELEASE_WORKFLOW="${ROOT}/.github/workflows/release.yml"
readonly README="${ROOT}/README.md"
@@ -93,11 +94,11 @@ grep -Fq 'git describe' "${AUR_PKGBUILD}" ||
grep -Fq 'git describe' "${AUR_WORKFLOW}" ||
Fail 'AUR workflow does not version from the release tag'
for pkgbuild in "${PKGBUILD}" "${AUR_STABLE_PKGBUILD}" "${AUR_BIN_PKGBUILD}"; do
grep -Fqx 'pkgver=0.3.1' "${pkgbuild}" ||
grep -Fqx 'pkgver=0.3.4' "${pkgbuild}" ||
Fail "package version does not match the release being prepared: ${pkgbuild}"
done
# Nightcap keeps its AUR recipes in the tree. Nothing publishes them.
# aur.yml only checks the nightcap-git recipe. Publishing is aur-publish.yml.
for unexpected in \
'aur.archlinux.org' \
'AUR_SSH_PRIVATE_KEY' \
@@ -106,6 +107,34 @@ for unexpected in \
Fail "AUR workflow should not publish: ${unexpected}"
fi
done
for expected in \
'workflow_call:' \
'workflow_dispatch:' \
'for package in nightcap nightcap-bin' \
'${archive}.sha256' \
'makepkg --printsrcinfo' \
'secrets.AUR_SSH_PRIVATE_KEY' \
'ssh://aur@aur.archlinux.org/' \
'aur.archlinux.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEuBKrPzbawxA/k2g6NcyV5jmqwJ2s+zpgZGZ7tpLIcN' \
'StrictHostKeyChecking=yes'; do
grep -Fq -- "${expected}" "${AUR_PUBLISH_WORKFLOW}" ||
Fail "AUR publish workflow is missing: ${expected}"
done
# The publish workflow holds the AUR key. It runs for a release tag or by hand,
# never for a push or a pull request, and it leaves nightcap-git to its own
# maintainer.
for unexpected in \
'pull_request' \
'push:' \
'nightcap-git' \
'StrictHostKeyChecking=no' \
'set -x'; do
if grep -Fq -- "${unexpected}" "${AUR_PUBLISH_WORKFLOW}"; then
Fail "AUR publish workflow should not contain: ${unexpected}"
fi
done
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}" ||
@@ -155,13 +184,14 @@ for expected in \
'-DMOCKTAIL_PACKAGE_NAME=nightcap' \
'-DMOCKTAIL_PACKAGE_VERSION=' \
'Nightcap-x86_64.AppImage' \
'gh release upload'; do
'gh release upload' \
'uses: ./.github/workflows/aur-publish.yml'; do
grep -Fq -- "${expected}" "${RELEASE_WORKFLOW}" ||
Fail "release workflow is missing: ${expected}"
done
# Nightcap publishes its packages on the release page only. Pushing them to
# APT, RPM or AUR repositories is out of scope for this workflow.
# This workflow publishes on the release page only. It hands the AUR push to
# aur-publish.yml, and APT and RPM repositories are out of scope.
for unexpected in \
'createrepo' \
'reprepro' \
+17 -2
View File
@@ -57,9 +57,24 @@ Restart the game. The layout matches Sober's `asset_overlay` folder, so a file
set made for Sober can be copied in as it is. The Flatpak build keeps the
folder at `~/.var/app/io.github.CoderDayton.nightcap/data/mocktail/asset_overlay`.
Roblox downloads some of these files, such as the shift lock and text cursors,
and reads no local file for them. A file under `content/` in the overlay folder
still replaces one: at start, Nightcap changes that file's name inside the
loaded Roblox library, in memory only, so Roblox reads the local file. Nothing
is changed when the overlay folder holds no file. Roblox does not allow
modified clients, so use this at your own risk.
```sh
dir=~/.local/share/mocktail/asset_overlay/content/textures
mkdir -p "$dir/Cursors/KeyboardMouse"
cp my-shift-lock.png "$dir/MouseLockedCursor.png"
cp my-text-cursor.png "$dir/Cursors/KeyboardMouse/IBeamCursor.png"
```
Set `MOCKTAIL_ASSET_TRACE=1` to print every file Roblox opens as
`[asset] open <name> -> <path>`. A line that ends in `[overlay]` came from the
overlay folder. `MOCKTAIL_ASSET_OVERLAY_DIR` points the lookup somewhere else.
`[asset] open <name> -> <path>`, and every file it asks for and does not get as
`[asset] miss <name>`. A line that ends in `[overlay]` came from the overlay
folder. `MOCKTAIL_ASSET_OVERLAY_DIR` points the lookup somewhere else.
An overlay file changes the texture's bytes, so a texture override keyed on the
original file's hash stops matching it. Use one of the two for a given texture.