mirror of
https://github.com/InstanceBrick/nightcap.git
synced 2026-10-17 13:00:45 +02:00
Merge remote-tracking branch 'upstream/main' into vr
# Conflicts: # stubs/libandroid_stub.cc
This commit is contained in:
18 files changed
+474
-18
No files matched your search
@@ -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
|
||||
@@ -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 }}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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,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')
|
||||
|
||||
@@ -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,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')
|
||||
|
||||
@@ -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,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')
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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.
|
||||
Reference in new issue
Block a user