mirror of
https://github.com/InstanceBrick/nightcap.git
synced 2026-10-17 17:00:55 +02:00
Compare commits
361
Commits
1.0.2
...
steam-frame
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e409d280ce | ||
|
|
932b9ea7d9 | ||
|
|
7dc4f55642 | ||
|
|
adccf4e484 | ||
|
|
c4be3de14a | ||
|
|
8370850bb7 | ||
|
|
876f8b8c32 | ||
|
|
1854e00934 | ||
|
|
c614fba032 | ||
|
|
0221738952 | ||
|
|
046632cf99 | ||
|
|
753a3f8106 | ||
|
|
569df68729 | ||
|
|
043964a5dc | ||
|
|
ea248b1ecf | ||
|
|
35033454b0 | ||
|
|
df025d2eb7 | ||
|
|
dea1d5fd59 | ||
|
|
9ae5667cd7 | ||
|
|
7296520064 | ||
|
|
a3a2b76a06 | ||
|
|
f333399e18 | ||
|
|
e094a4c89d | ||
|
|
de0ddf7864 | ||
|
|
e553db2001 | ||
|
|
44ec71f826 | ||
|
|
3062909209 | ||
|
|
049784d0f8 | ||
|
|
ccf910f064 | ||
|
|
42b5138b11 | ||
|
|
2eaba9048b | ||
|
|
a2ffa5c811 | ||
|
|
2be319a979 | ||
|
|
6cf641d472 | ||
|
|
3788026d10 | ||
|
|
f3e68fc3b7 | ||
|
|
23e7831149 | ||
|
|
5b2ee2c7f3 | ||
|
|
63ed62b4aa | ||
|
|
0b0d8067d8 | ||
|
|
87ac6ee572 | ||
|
|
fcc8b5382a | ||
|
|
9826535102 | ||
|
|
a30f05fc5b | ||
|
|
1ff05e4e6f | ||
|
|
123968be89 | ||
|
|
393297a439 | ||
|
|
7ec8304d8f | ||
|
|
f9289d9947 | ||
|
|
7294a46e8a | ||
|
|
4b67176970 | ||
|
|
228121157b | ||
|
|
a06c7de93e | ||
|
|
8106203c8e | ||
|
|
0603734ff6 | ||
|
|
0b5578d80b | ||
|
|
bafbe4268c | ||
|
|
3ef81c3d93 | ||
|
|
360ab6af58 | ||
|
|
4cc0e41ef3 | ||
|
|
ddc985218b | ||
|
|
33dd7769f0 | ||
|
|
88bb4c940c | ||
|
|
68f8ec82d2 | ||
|
|
509822974b | ||
|
|
a441fa7110 | ||
|
|
d54d20de64 | ||
|
|
81de528668 | ||
|
|
ff81882cb0 | ||
|
|
f9fede1649 | ||
|
|
a87373d003 | ||
|
|
b46098c211 | ||
|
|
144350090e | ||
|
|
3ed0418997 | ||
|
|
8410070eec | ||
|
|
a1166fb6ba | ||
|
|
c9eb4fb7f0 | ||
|
|
b59c070cc0 | ||
|
|
4a9acf8c49 | ||
|
|
c342bab7b5 | ||
|
|
a3b9c872a5 | ||
|
|
4f45baf3f7 | ||
|
|
0ddb546ba8 | ||
|
|
7ba752ebb7 | ||
|
|
227895faa4 | ||
|
|
dfc3c8d555 | ||
|
|
e490dff97f | ||
|
|
b5b9990017 | ||
|
|
d278c0fe0a | ||
|
|
044f97e476 | ||
|
|
321ba5b3a2 | ||
|
|
fd364a7bf2 | ||
|
|
5253563e3f | ||
|
|
551d2a1829 | ||
|
|
37b88103f4 | ||
|
|
763faed0e3 | ||
|
|
74eedada0b | ||
|
|
6cb3dd00a9 | ||
|
|
fe2f641a9e | ||
|
|
cb2e9dd598 | ||
|
|
bc108106db | ||
|
|
aa31cdf395 | ||
|
|
3e6f63ea93 | ||
|
|
bfa6811417 | ||
|
|
84502c0a1a | ||
|
|
46d1d5ffbe | ||
|
|
545fd8344f | ||
|
|
4c40fcdc99 | ||
|
|
d4c7a6f741 | ||
|
|
491925f59e | ||
|
|
2917bc5423 | ||
|
|
2451264696 | ||
|
|
a9a96c6861 | ||
|
|
5bf4eeb41b | ||
|
|
d937dd7ea6 | ||
|
|
169e65bf60 | ||
|
|
69476f0f47 | ||
|
|
79d0cdfb40 | ||
|
|
273209b9ea | ||
|
|
bbfd603e17 | ||
|
|
1ba2941e7f | ||
|
|
68c90ce428 | ||
|
|
9a1ccbb5ce | ||
|
|
068f07e125 | ||
|
|
a469689bbb | ||
|
|
51836a980d | ||
|
|
ad546a7781 | ||
|
|
3c45450dfc | ||
|
|
185c11b57f | ||
|
|
2c3d7b485e | ||
|
|
6f8344a28b | ||
|
|
b60c43e8f0 | ||
|
|
b5e4273277 | ||
|
|
d2858fedc8 | ||
|
|
326a0659ea | ||
|
|
dd45467056 | ||
|
|
94b1036296 | ||
|
|
4c61daad0c | ||
|
|
f5f224875a | ||
|
|
90d4891a4e | ||
|
|
46dd99266c | ||
|
|
77fce18a95 | ||
|
|
ecb80b3ee1 | ||
|
|
d6f0f2c755 | ||
|
|
c6afc5af24 | ||
|
|
74e7455ee1 | ||
|
|
000d021452 | ||
|
|
2eef2c3821 | ||
|
|
4928443f90 | ||
|
|
b37497db7b | ||
|
|
6bbdc39d7e | ||
|
|
1c11336ba1 | ||
|
|
b19bd8406f | ||
|
|
cd46538eb8 | ||
|
|
eca5f395eb | ||
|
|
26767e925c | ||
|
|
01a0e40463 | ||
|
|
a11e833e06 | ||
|
|
b9bce36824 | ||
|
|
14c8a55211 | ||
|
|
5c5422fa02 | ||
|
|
9b041ec2ac | ||
|
|
6362283ade | ||
|
|
5f83c4a64f | ||
|
|
a9d8e3185e | ||
|
|
67a9f4db97 | ||
|
|
f07daccc82 | ||
|
|
2eef4f966f | ||
|
|
41003efb09 | ||
|
|
29a0f03a90 | ||
|
|
5a27aa0366 | ||
|
|
c99a55dc54 | ||
|
|
30d50f15d1 | ||
|
|
ab940acfb5 | ||
|
|
719b1fdd22 | ||
|
|
1fa2e116b3 | ||
|
|
35359f3575 | ||
|
|
fbf97df708 | ||
|
|
40237fae72 | ||
|
|
ad6a3c3147 | ||
|
|
d982cd9506 | ||
|
|
65adb3bddc | ||
|
|
47e23df2e0 | ||
|
|
c0ff1842ee | ||
|
|
e073947b91 | ||
|
|
66de662e5b | ||
|
|
6c3631058c | ||
|
|
6829fd00ed | ||
|
|
8be3618cd5 | ||
|
|
265b476440 | ||
|
|
ff905fe5b7 | ||
|
|
02108a9bb1 | ||
|
|
7dd32335ca | ||
|
|
5ed6ed5e84 | ||
|
|
cef1b3ec4a | ||
|
|
8bde1a1c0d | ||
|
|
60e9c5c3ef | ||
|
|
d76e1b6f54 | ||
|
|
33c06c063e | ||
|
|
9e8afb233a | ||
|
|
7fda1eb239 | ||
|
|
f128e28b83 | ||
|
|
718e67f9e1 | ||
|
|
c6c354fdb1 | ||
|
|
25930d14ff | ||
|
|
854969457f | ||
|
|
65030d8df1 | ||
|
|
0be5e808c5 | ||
|
|
1b030a57eb | ||
|
|
2f5527b8b5 | ||
|
|
579cedbf1d | ||
|
|
a7b90e5919 | ||
|
|
3bba795ea3 | ||
|
|
25cfd94c56 | ||
|
|
5ce4487d4f | ||
|
|
c544389dd0 | ||
|
|
bdb84142d7 | ||
|
|
40698efb7d | ||
|
|
3d2a2b3d98 | ||
|
|
78a314b761 | ||
|
|
2d1dd3788b | ||
|
|
56d3160641 | ||
|
|
5db99041bc | ||
|
|
9ec56338c3 | ||
|
|
894ebf8eb7 | ||
|
|
6bec8a6c5c | ||
|
|
b9e7bc761b | ||
|
|
b54c896498 | ||
|
|
1cd7b7db8f | ||
|
|
f87aaca2fb | ||
|
|
901ba615ab | ||
|
|
cdbf9ac1eb | ||
|
|
f098bc1b5d | ||
|
|
e1f7c8c06c | ||
|
|
a9987546d5 | ||
|
|
6a39887e8f | ||
|
|
349e314850 | ||
|
|
f383fbbff4 | ||
|
|
b1112d8cdc | ||
|
|
27c383a5d9 | ||
|
|
7cf24feef6 | ||
|
|
4443766d39 | ||
|
|
ea2166b09a | ||
|
|
9e4e69be1c | ||
|
|
dd0655bfde | ||
|
|
d2d275706b | ||
|
|
d76d75b7f8 | ||
|
|
3c20ee3c55 | ||
|
|
b7b63dd30f | ||
|
|
aacab4036c | ||
|
|
e104b1d936 | ||
|
|
5e24bee4ab | ||
|
|
a014751cd4 | ||
|
|
537828acc2 | ||
|
|
5be2f68a55 | ||
|
|
f7e519bae1 | ||
|
|
ad8fbfcf65 | ||
|
|
96e1966d16 | ||
|
|
becaee71c0 | ||
|
|
bf6a6b417d | ||
|
|
dfd35b8cc8 | ||
|
|
1632681e6c | ||
|
|
ab4944a907 | ||
|
|
88d10d53f2 | ||
|
|
7e057232e4 | ||
|
|
f5080e8a99 | ||
|
|
eae9fa7030 | ||
|
|
ad76f68224 | ||
|
|
d783793b4e | ||
|
|
3d89f128ce | ||
|
|
b187efbedf | ||
|
|
017f79398d | ||
|
|
fc120e8285 | ||
|
|
117b9b2016 | ||
|
|
1376ab2474 | ||
|
|
dd23f06a8b | ||
|
|
fdce53421f | ||
|
|
770ed086d5 | ||
|
|
c48db09f13 | ||
|
|
0548ebd253 | ||
|
|
c523048518 | ||
|
|
ffe3fba8af | ||
|
|
5d475e03d8 | ||
|
|
240dc1ee63 | ||
|
|
569980ecf2 | ||
|
|
2f80333c5d | ||
|
|
be7d542d49 | ||
|
|
266f917f49 | ||
|
|
859f8f2b8f | ||
|
|
8447bf3f1a | ||
|
|
b71873f2a5 | ||
|
|
92c7d7b12b | ||
|
|
cc3893d422 | ||
|
|
a6fdb5b0cd | ||
|
|
3890efbaaa | ||
|
|
9f7b09e197 | ||
|
|
3284cf9fc1 | ||
|
|
035c7fe1d4 | ||
|
|
d5f9c91358 | ||
|
|
0cc9ab2e32 | ||
|
|
7127ad1bf7 | ||
|
|
54a302dc27 | ||
|
|
23a5c96e3d | ||
|
|
ae417490f1 | ||
|
|
35298050b8 | ||
|
|
703af11d17 | ||
|
|
3a26ecc83e | ||
|
|
278d15e41a | ||
|
|
968df46605 | ||
|
|
409d8a6319 | ||
|
|
9812fbceab | ||
|
|
61a467cadc | ||
|
|
497481e8fa | ||
|
|
5f2be59192 | ||
|
|
9bd5f97063 | ||
|
|
8f63b9deba | ||
|
|
272f61688b | ||
|
|
f797ec7e40 | ||
|
|
02c8c6ca83 | ||
|
|
5af66cf8fc | ||
|
|
b236c59b57 | ||
|
|
386476eda6 | ||
|
|
b6407e7d3a | ||
|
|
21efe88b22 | ||
|
|
0f635abc25 | ||
|
|
2f179068b3 | ||
|
|
81b490e196 | ||
|
|
f2168e2f98 | ||
|
|
64a564f292 | ||
|
|
3728afa0d7 | ||
|
|
931543e976 | ||
|
|
a2a210da87 | ||
|
|
b289e7933e | ||
|
|
497cf9ea6c | ||
|
|
0b7326d8d1 | ||
|
|
e312ab6667 | ||
|
|
b8e535f6f9 | ||
|
|
c0f6787cc6 | ||
|
|
f0e0229ec5 | ||
|
|
56749bbdc8 | ||
|
|
9da2c02836 | ||
|
|
b5eac34e0f | ||
|
|
e4c64c2fec | ||
|
|
ba6287250f | ||
|
|
4b437f623f | ||
|
|
e161fecfc5 | ||
|
|
dcf22a144b | ||
|
|
0bc8823644 | ||
|
|
f9a48cfe83 | ||
|
|
52721bf06c | ||
|
|
9bbd2a1ebf | ||
|
|
9cedf389a7 | ||
|
|
bbe454641b | ||
|
|
097f782694 | ||
|
|
2a154b6f58 | ||
|
|
0b8cbef447 | ||
|
|
f1d4a05051 | ||
|
|
f4e9349f46 | ||
|
|
63070b6462 | ||
|
|
fb5f38cb40 | ||
|
|
fa370b9dbb |
No files matched your search
@@ -0,0 +1,20 @@
|
||||
**/.git
|
||||
/build-frame-arm64/
|
||||
/build/
|
||||
/build-test/
|
||||
/build_*/
|
||||
/frame-ui-local/
|
||||
/rbx_bin/
|
||||
/runtime/
|
||||
/data/
|
||||
/dist/
|
||||
/out/
|
||||
**/__pycache__/
|
||||
*.log
|
||||
*.bundle
|
||||
*.apk
|
||||
*.xapk
|
||||
*.aab
|
||||
*.obb
|
||||
libroblox.so
|
||||
cookies*.txt
|
||||
@@ -1,3 +1,8 @@
|
||||
# Preserve exact upstream whitespace in the provenance-locked Android core
|
||||
# snapshot. Local Mocktail sources remain subject to git diff --check.
|
||||
third_party/mcpelauncher-linker/core/** -whitespace
|
||||
packaging/freebsd/*.patch -whitespace
|
||||
|
||||
# Preserve original third-party shader/header whitespace.
|
||||
src/vr/smaa/** -whitespace
|
||||
tools/frame_video_settings/openvr.h -whitespace
|
||||
@@ -0,0 +1,100 @@
|
||||
# Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
name: Bug report
|
||||
description: Report a reproducible problem on Steam Frame
|
||||
title: "[Bug]: "
|
||||
labels:
|
||||
- bug
|
||||
assignees: []
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please include enough detail for someone else to reproduce the bug.
|
||||
|
||||
Use any language you are comfortable with.
|
||||
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Nightcap version
|
||||
description: Provide the release version, package version, or commit hash.
|
||||
placeholder: "For example: v0.4.0 or 1a2b3c4"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: system
|
||||
attributes:
|
||||
label: System information
|
||||
description: Include the SteamOS, SteamVR and graphics driver versions.
|
||||
placeholder: "Steam Frame, SteamOS version, SteamVR build, Turnip version"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: installation
|
||||
attributes:
|
||||
label: Installation method
|
||||
description: Tell us how Nightcap was installed or built.
|
||||
placeholder: "Source build using the Steam Frame container recipe"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: Describe the problem and its impact.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: List the smallest reliable sequence of actions that triggers the problem.
|
||||
placeholder: |
|
||||
1. Start Nightcap with ...
|
||||
2. Open ...
|
||||
3. Select ...
|
||||
4. Observe ...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: Explain what you expected to happen instead.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Latest log:
|
||||
|
||||
`~/.local/share/nightcap-vr-arm64/last-launch.log`
|
||||
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Logs
|
||||
description: Paste the relevant part of latest.log or the terminal output.
|
||||
render: shell
|
||||
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add screenshots, videos, workarounds, or any other useful details.
|
||||
|
||||
- type: checkboxes
|
||||
id: checks
|
||||
attributes:
|
||||
label: Before submitting
|
||||
options:
|
||||
- label: I searched the existing issues and did not find a duplicate.
|
||||
required: true
|
||||
@@ -0,0 +1,2 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links: []
|
||||
@@ -0,0 +1,51 @@
|
||||
# Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
name: Feature request
|
||||
description: Suggest an improvement or a new capability for Nightcap
|
||||
title: "[Feature]: "
|
||||
labels:
|
||||
- enhancement
|
||||
assignees: []
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Describe what you want to change and why it would be useful.
|
||||
|
||||
Use any language you are comfortable with.
|
||||
|
||||
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: Problem or use case
|
||||
description: What are you trying to do, and what makes it difficult today?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: proposal
|
||||
attributes:
|
||||
label: Proposed solution
|
||||
description: Describe how you would like Nightcap to behave.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Alternatives considered
|
||||
description: Describe any workarounds or alternative designs you have considered.
|
||||
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add mockups, screenshots, links, or implementation notes if useful.
|
||||
|
||||
- type: checkboxes
|
||||
id: checks
|
||||
attributes:
|
||||
label: Before submitting
|
||||
options:
|
||||
- label: I searched the existing issues and did not find a duplicate.
|
||||
required: true
|
||||
@@ -0,0 +1,7 @@
|
||||
## Change
|
||||
|
||||
Describe the behavior changed on Steam Frame and link any relevant issue.
|
||||
|
||||
## Checks
|
||||
|
||||
Describe the build, tests or device checks run for this change.
|
||||
@@ -1,60 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- '.github/workflows/**'
|
||||
- 'CMakeLists.txt'
|
||||
- 'Makefile'
|
||||
- 'README.md'
|
||||
- 'config/**'
|
||||
- 'packaging/**'
|
||||
- 'scripts/**'
|
||||
- 'stubs/**'
|
||||
- 'tests/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/**'
|
||||
- 'CMakeLists.txt'
|
||||
- 'Makefile'
|
||||
- 'README.md'
|
||||
- 'config/**'
|
||||
- 'packaging/**'
|
||||
- 'scripts/**'
|
||||
- 'stubs/**'
|
||||
- 'tests/**'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
contracts:
|
||||
name: Repository contracts
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Check out source
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Validate shell and JSON files
|
||||
shell: bash
|
||||
run: |
|
||||
find scripts tests -type f -name '*.sh' -print0 | xargs -0 -n1 bash -n
|
||||
find config packaging/flatpak -type f -name '*.json' -print0 \
|
||||
| xargs -0 -n1 python3 -m json.tool >/dev/null
|
||||
|
||||
- name: Verify build and packaging contracts
|
||||
shell: bash
|
||||
run: |
|
||||
bash tests/managed_payload_build_contract_test.sh .
|
||||
bash tests/flatpak_packaging_test.sh .
|
||||
bash tests/native_packaging_test.sh .
|
||||
@@ -1,193 +0,0 @@
|
||||
name: Flatpak
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [Native packages]
|
||||
types: [completed]
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/flatpak.yml'
|
||||
- '.gitmodules'
|
||||
- 'CMakeLists.txt'
|
||||
- 'LICENSE'
|
||||
- 'assets/screenshots/**'
|
||||
- 'cmake/**'
|
||||
- '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'
|
||||
- 'scripts/assemble_flatpak_pages.sh'
|
||||
- 'scripts/assemble_native_repositories.sh'
|
||||
- 'site/**'
|
||||
- 'src/**'
|
||||
- 'stubs/**'
|
||||
- 'third_party/**'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: flatpak-${{ github.event.workflow_run.head_sha || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
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 }}
|
||||
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: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install --yes flatpak flatpak-builder gnupg
|
||||
flatpak remote-add --user --if-not-exists flathub \
|
||||
https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||
|
||||
- name: Build Flatpak repository and bundle
|
||||
shell: bash
|
||||
env:
|
||||
FLATPAK_GPG_PRIVATE_KEY: ${{ secrets.FLATPAK_GPG_PRIVATE_KEY }}
|
||||
FLATPAK_GPG_KEY_ID: ${{ secrets.FLATPAK_GPG_KEY_ID }}
|
||||
run: |
|
||||
set -Eeuo pipefail
|
||||
git update-ref refs/heads/main "$MOCKTAIL_SOURCE_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"
|
||||
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
|
||||
builder_signing=(
|
||||
"--gpg-sign=$FLATPAK_GPG_KEY_ID"
|
||||
"--gpg-homedir=$RUNNER_TEMP/mocktail-gnupg"
|
||||
)
|
||||
bundle_keys=(--gpg-keys=mocktail-flatpak.gpg)
|
||||
fi
|
||||
flatpak-builder \
|
||||
--arch=x86_64 \
|
||||
--jobs=4 \
|
||||
--user \
|
||||
--install-deps-from=flathub \
|
||||
--disable-rofiles-fuse \
|
||||
--force-clean \
|
||||
--default-branch=stable \
|
||||
--repo=repo \
|
||||
"${builder_signing[@]}" \
|
||||
build-flatpak-ci \
|
||||
packaging/flatpak/space.bigrat.mocktail.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" \
|
||||
repo
|
||||
fi
|
||||
flatpak build-bundle \
|
||||
--arch=x86_64 \
|
||||
"${bundle_keys[@]}" \
|
||||
repo \
|
||||
Mocktail-x86_64.flatpak \
|
||||
space.bigrat.mocktail \
|
||||
stable
|
||||
if [[ "$MOCKTAIL_PUBLISH" == true ]]; then
|
||||
./scripts/assemble_flatpak_pages.sh \
|
||||
repo Mocktail-x86_64.flatpak mocktail-flatpak.gpg \
|
||||
public native-repositories
|
||||
fi
|
||||
|
||||
- name: Upload Flatpak
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: Mocktail-x86_64-flatpak
|
||||
path: Mocktail-x86_64.flatpak
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
|
||||
- name: Configure GitHub Pages
|
||||
if: env.MOCKTAIL_PUBLISH == 'true'
|
||||
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
|
||||
|
||||
- name: Upload GitHub Pages repository
|
||||
if: env.MOCKTAIL_PUBLISH == 'true'
|
||||
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
|
||||
with:
|
||||
path: public
|
||||
|
||||
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')
|
||||
needs: flatpak
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- name: Deploy GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
|
||||
@@ -1,337 +0,0 @@
|
||||
name: Native packages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- '.github/workflows/ci.yml'
|
||||
- '.gitignore'
|
||||
- 'README.md'
|
||||
- 'assets/screenshots/**'
|
||||
- 'docs/**'
|
||||
- 'packaging/aur/**'
|
||||
- 'scripts/build_flatpak.sh'
|
||||
- 'tests/**'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '.github/workflows/ci.yml'
|
||||
- '.gitignore'
|
||||
- 'README.md'
|
||||
- 'assets/screenshots/**'
|
||||
- 'docs/**'
|
||||
- 'packaging/aur/**'
|
||||
- 'scripts/build_flatpak.sh'
|
||||
- 'tests/**'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: packages-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
appimage:
|
||||
name: AppImage (Ubuntu 26.04)
|
||||
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 \
|
||||
ubuntu:26.04 bash -Eeuo pipefail -c '
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get install --yes --no-install-recommends \
|
||||
build-essential cmake git ninja-build pkg-config \
|
||||
libsdl3-dev libsdl3-ttf-dev libcurl4-openssl-dev \
|
||||
libssl-dev nlohmann-json3-dev 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 zlib1g-dev \
|
||||
libplacebo-dev \
|
||||
bubblewrap xdg-dbus-proxy patchelf jq unzip file curl \
|
||||
ca-certificates default-jdk-headless libgif7 python3 \
|
||||
python3-capstone python3-yaml python3-requests \
|
||||
python3-urllib3 python3-certifi python3-charset-normalizer \
|
||||
python3-idna squashfs-tools
|
||||
|
||||
curl --fail --location --retry 3 \
|
||||
--output /tmp/appimagetool \
|
||||
https://github.com/AppImage/appimagetool/releases/download/1.9.1/appimagetool-x86_64.AppImage
|
||||
echo "ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0 /tmp/appimagetool" | \
|
||||
sha256sum --check --strict
|
||||
install -m 0755 /tmp/appimagetool /usr/local/bin/appimagetool
|
||||
|
||||
curl --fail --location --retry 3 \
|
||||
--output /tmp/android-build-tools.zip \
|
||||
https://dl.google.com/android/repository/build-tools_r35_linux.zip
|
||||
echo "bd3a4966912eb8b30ed0d00b0cda6b6543b949d5ffe00bea54c04c81e1561d88 /tmp/android-build-tools.zip" | \
|
||||
sha256sum --check --strict
|
||||
unzip -q /tmp/android-build-tools.zip -d /tmp/android-build-tools
|
||||
android_tools_root="$(dirname -- "$(find /tmp/android-build-tools -type f -name apksigner -print -quit)")"
|
||||
[[ -n "$android_tools_root" ]]
|
||||
|
||||
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 APPIMAGE_EXTRACT_AND_RUN=1
|
||||
export MOCKTAIL_ANDROID_BUILD_TOOLS_ROOT="$android_tools_root"
|
||||
make appimage BUILD_DIR=build-appimage JOBS=4 \
|
||||
APPIMAGE=dist/Mocktail-x86_64.AppImage \
|
||||
PORTABLE_MODE=standalone
|
||||
test -x dist/Mocktail-x86_64.AppImage
|
||||
'
|
||||
|
||||
- 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
|
||||
|
||||
deb:
|
||||
name: DEB (Ubuntu 26.04)
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Check out source
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Build DEB
|
||||
shell: bash
|
||||
run: |
|
||||
nightly_suffix=".nightly.$(git show -s --format=%cd --date=format:%Y%m%d%H%M%S "$GITHUB_SHA").g${GITHUB_SHA:0:8}"
|
||||
docker run --rm \
|
||||
--volume "$GITHUB_WORKSPACE:/workspace" \
|
||||
--workdir /workspace \
|
||||
--env MOCKTAIL_PACKAGE_VERSION_SUFFIX="$nightly_suffix" \
|
||||
ubuntu:26.04 bash -Eeuo pipefail -c '
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get install --yes --no-install-recommends \
|
||||
build-essential cmake git ninja-build pkg-config fakeroot \
|
||||
dpkg-dev file libsdl3-dev libsdl3-ttf-dev \
|
||||
libcurl4-openssl-dev libssl-dev nlohmann-json3-dev \
|
||||
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 zlib1g-dev
|
||||
cmake -S . -B build-deb -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
-DMOCKTAIL_PACKAGE_NAME=mocktail-nightly \
|
||||
-DMOCKTAIL_PACKAGE_VERSION_SUFFIX="$MOCKTAIL_PACKAGE_VERSION_SUFFIX" \
|
||||
-DBUILD_TESTING=OFF
|
||||
cmake --build build-deb --parallel 4
|
||||
cpack --config build-deb/CPackConfig.cmake \
|
||||
-G DEB -B dist/deb
|
||||
dpkg-deb --info dist/deb/*.deb >/dev/null
|
||||
'
|
||||
|
||||
- name: Upload DEB
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: Mocktail-DEB
|
||||
path: dist/deb/*.deb
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
|
||||
rpm:
|
||||
name: RPM (Fedora 44)
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Check out source
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Build RPM
|
||||
shell: bash
|
||||
run: |
|
||||
nightly_suffix=".nightly.$(git show -s --format=%cd --date=format:%Y%m%d%H%M%S "$GITHUB_SHA").g${GITHUB_SHA:0:8}"
|
||||
docker run --rm \
|
||||
--volume "$GITHUB_WORKSPACE:/workspace" \
|
||||
--workdir /workspace \
|
||||
--env MOCKTAIL_PACKAGE_VERSION_SUFFIX="$nightly_suffix" \
|
||||
fedora:44 bash -Eeuo pipefail -c '
|
||||
dnf install --assumeyes \
|
||||
gcc-c++ cmake git ninja-build pkgconf-pkg-config rpm-build \
|
||||
SDL3-devel SDL3_ttf-devel libcurl-devel openssl-devel \
|
||||
nlohmann-json-devel libyaml-devel elfutils-libelf-devel \
|
||||
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 zlib-ng-compat-devel
|
||||
cmake -S . -B build-rpm -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
-DMOCKTAIL_PACKAGE_NAME=mocktail-nightly \
|
||||
-DMOCKTAIL_PACKAGE_VERSION_SUFFIX="$MOCKTAIL_PACKAGE_VERSION_SUFFIX" \
|
||||
-DBUILD_TESTING=OFF
|
||||
cmake --build build-rpm --parallel 4
|
||||
cpack --config build-rpm/CPackConfig.cmake \
|
||||
-G RPM -B dist/rpm
|
||||
rpm --query --package --info dist/rpm/*.rpm >/dev/null
|
||||
'
|
||||
|
||||
- name: Upload RPM
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: Mocktail-RPM
|
||||
path: dist/rpm/*.rpm
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
|
||||
arch:
|
||||
name: Pacman package (Arch Linux)
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Check out source
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Build pacman package
|
||||
shell: bash
|
||||
run: |
|
||||
docker run --rm \
|
||||
--volume "$GITHUB_WORKSPACE:/workspace" \
|
||||
--workdir /workspace \
|
||||
archlinux:base-devel bash -Eeuo pipefail -c '
|
||||
pacman -Syu --needed --noconfirm \
|
||||
cmake git ninja pkgconf sdl3 sdl3_ttf curl openssl \
|
||||
nlohmann-json libyaml libelf minizip capstone gtk4 \
|
||||
libadwaita webkitgtk-6.0 libutf8proc fontconfig \
|
||||
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 \
|
||||
makepkg --dir /workspace/packaging/arch \
|
||||
--noconfirm --cleanbuild --force
|
||||
install -d dist/arch
|
||||
cp packaging/arch/*.pkg.tar.zst dist/arch/
|
||||
pacman -Qp dist/arch/*.pkg.tar.zst >/dev/null
|
||||
'
|
||||
|
||||
- name: Upload pacman package
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: Mocktail-Arch
|
||||
path: dist/arch/*.pkg.tar.zst
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
|
||||
publish:
|
||||
name: Publish continuous packages
|
||||
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
|
||||
needs: [appimage, deb, rpm, arch]
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Check out source
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download packages
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: Mocktail-*
|
||||
path: release
|
||||
merge-multiple: true
|
||||
|
||||
- name: Prepare continuous packages
|
||||
shell: bash
|
||||
run: |
|
||||
set -Eeuo pipefail
|
||||
mv -- release/*.AppImage release/mocktail-nightly.AppImage
|
||||
mv -- release/*.deb release/mocktail-nightly.deb
|
||||
mv -- release/*.rpm release/mocktail-nightly.rpm
|
||||
mv -- release/*.pkg.tar.zst release/mocktail-nightly.pkg.tar.zst
|
||||
find release -maxdepth 1 -type f -print
|
||||
|
||||
- name: Build package repositories
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
FLATPAK_GPG_PRIVATE_KEY: ${{ secrets.FLATPAK_GPG_PRIVATE_KEY }}
|
||||
FLATPAK_GPG_KEY_ID: ${{ secrets.FLATPAK_GPG_KEY_ID }}
|
||||
run: |
|
||||
set -Eeuo pipefail
|
||||
[[ -n "$FLATPAK_GPG_PRIVATE_KEY" && -n "$FLATPAK_GPG_KEY_ID" ]]
|
||||
sudo apt-get update
|
||||
sudo apt-get install --yes --no-install-recommends \
|
||||
apt-utils createrepo-c gnupg rpm
|
||||
install -d stable/deb stable/rpm
|
||||
stable_tag="$(gh api "repos/$GITHUB_REPOSITORY/releases/latest" --jq .tag_name)"
|
||||
gh release download "$stable_tag" --pattern '*.deb' --dir stable/deb
|
||||
gh release download "$stable_tag" --pattern '*.rpm' --dir stable/rpm
|
||||
mapfile -t stable_debs < <(find stable/deb -maxdepth 1 -type f -name '*.deb' -print)
|
||||
mapfile -t stable_rpms < <(find stable/rpm -maxdepth 1 -type f -name '*.rpm' -print)
|
||||
[[ "${#stable_debs[@]}" -eq 1 && "${#stable_rpms[@]}" -eq 1 ]]
|
||||
install -d -m 0700 "$RUNNER_TEMP/mocktail-native-gnupg"
|
||||
printf '%s' "$FLATPAK_GPG_PRIVATE_KEY" | \
|
||||
gpg --batch --homedir "$RUNNER_TEMP/mocktail-native-gnupg" --import
|
||||
gpg --batch --homedir "$RUNNER_TEMP/mocktail-native-gnupg" \
|
||||
--export "$FLATPAK_GPG_KEY_ID" >mocktail-packages.gpg
|
||||
./scripts/assemble_native_repositories.sh \
|
||||
"${stable_debs[0]}" release/mocktail-nightly.deb \
|
||||
"${stable_rpms[0]}" release/mocktail-nightly.rpm \
|
||||
mocktail-packages.gpg "$FLATPAK_GPG_KEY_ID" \
|
||||
"$RUNNER_TEMP/mocktail-native-gnupg" native-repositories
|
||||
|
||||
- name: Update continuous release
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -Eeuo pipefail
|
||||
git config user.name komaruworld
|
||||
git config user.email rtysus228@gmail.com
|
||||
git tag --force continuous "$GITHUB_SHA"
|
||||
git push origin refs/tags/continuous --force
|
||||
if ! gh release view continuous >/dev/null 2>&1; then
|
||||
gh release create continuous \
|
||||
--prerelease \
|
||||
--title "Mocktail continuous" \
|
||||
--notes "Automated development packages from main."
|
||||
fi
|
||||
gh release upload continuous release/* --clobber
|
||||
while IFS= read -r asset; do
|
||||
case "$asset" in
|
||||
mocktail-nightly.AppImage|mocktail-nightly.deb|mocktail-nightly.rpm|mocktail-nightly.pkg.tar.zst)
|
||||
;;
|
||||
Mocktail-*.AppImage|mocktail_*.deb|mocktail-*.deb|mocktail-*.rpm|mocktail-*.pkg.tar.zst)
|
||||
gh release delete-asset continuous "$asset" --yes
|
||||
;;
|
||||
esac
|
||||
done < <(gh release view continuous --json assets --jq '.assets[].name')
|
||||
|
||||
- name: Upload package repositories
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: Mocktail-native-repositories
|
||||
path: native-repositories
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
@@ -0,0 +1,202 @@
|
||||
name: Steam Frame ARM64
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [steam-frame]
|
||||
tags: ['steam-frame-*']
|
||||
pull_request:
|
||||
branches: [steam-frame]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
publish_release:
|
||||
description: Publish a release for an existing steam-frame-* tag
|
||||
type: boolean
|
||||
default: false
|
||||
release_tag:
|
||||
description: Existing tag to build when publishing (leave empty for artifact only)
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: steam-frame-arm64-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
env:
|
||||
IMAGE: localhost/nightcap-vr-arm64:frame-foveated
|
||||
ASSET: nightcap-steam-frame-arm64.oci.tar.zst
|
||||
|
||||
jobs:
|
||||
source-checks:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.publish_release && inputs.release_tag || github.ref }}
|
||||
- name: Check Native settings and package scripts
|
||||
run: |
|
||||
for script in build-frame-nightcap.sh packaging/steam-frame/verify_image.sh; do
|
||||
bash -n "$script"
|
||||
done
|
||||
python3 - <<'PY'
|
||||
import ast
|
||||
from pathlib import Path
|
||||
paths = list(Path('tools/steam_frame').glob('*.py'))
|
||||
paths.append(Path('tools/steam_frame/nightcap-vr-arm64'))
|
||||
for path in paths:
|
||||
ast.parse(path.read_text(), filename=str(path))
|
||||
PY
|
||||
python3 -m unittest discover -s tests -p 'steam_frame_*_test.py'
|
||||
node tests/steamvr_video_controls_test.js
|
||||
|
||||
build:
|
||||
if: github.event_name != 'pull_request'
|
||||
needs: source-checks
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 90
|
||||
outputs:
|
||||
release_tag: ${{ steps.release-target.outputs.tag }}
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.publish_release && inputs.release_tag || github.ref }}
|
||||
- name: Validate release target
|
||||
id: release-target
|
||||
env:
|
||||
EVENT: ${{ github.event_name }}
|
||||
REF_TYPE: ${{ github.ref_type }}
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
PUBLISH: ${{ inputs.publish_release }}
|
||||
REQUESTED_TAG: ${{ inputs.release_tag }}
|
||||
run: |
|
||||
tag=''
|
||||
if [[ "$EVENT" == push && "$REF_TYPE" == tag ]]; then
|
||||
tag="$REF_NAME"
|
||||
elif [[ "$EVENT" == workflow_dispatch && "$PUBLISH" == true ]]; then
|
||||
tag="$REQUESTED_TAG"
|
||||
fi
|
||||
if [[ -n "$tag" || "$PUBLISH" == true ]]; then
|
||||
[[ "$tag" =~ ^steam-frame-[A-Za-z0-9][A-Za-z0-9._-]*$ ]]
|
||||
[[ "$(git rev-parse "refs/tags/$tag^{commit}")" == "$(git rev-parse HEAD)" ]]
|
||||
fi
|
||||
printf 'tag=%s\n' "$tag" >> "$GITHUB_OUTPUT"
|
||||
- name: Install container tools
|
||||
run: |
|
||||
test "$(uname -m)" = aarch64
|
||||
sudo apt-get update
|
||||
sudo apt-get install --no-install-recommends -y podman zstd
|
||||
- name: Build Native ARM64 image
|
||||
run: |
|
||||
sudo podman build --arch arm64 --jobs 1 \
|
||||
--label "org.opencontainers.image.source=https://github.com/$GITHUB_REPOSITORY" \
|
||||
--label "org.opencontainers.image.revision=$(git rev-parse HEAD)" \
|
||||
--label org.opencontainers.image.title='Nightcap Steam Frame ARM64' \
|
||||
-f packaging/steam-frame/Containerfile -t "$IMAGE" .
|
||||
- name: Run mirror and runtime configuration regressions
|
||||
run: |
|
||||
sudo podman run --rm --entrypoint /bin/bash "$IMAGE" -lc '
|
||||
set -euo pipefail
|
||||
cmake -S /work -B /work/build-frame-arm64 -DBUILD_TESTING=ON
|
||||
cmake --build /work/build-frame-arm64 -j3 \
|
||||
--target desktop_mirror_test runtime_config_test runtime_config_file_test
|
||||
/work/build-frame-arm64/tests/desktop_mirror_test
|
||||
/work/build-frame-arm64/tests/runtime_config_test
|
||||
/work/build-frame-arm64/tests/runtime_config_file_test
|
||||
'
|
||||
- name: Check payload-free image
|
||||
run: |
|
||||
mkdir -p dist
|
||||
sudo bash packaging/steam-frame/verify_image.sh "$IMAGE" > dist/image-check.json
|
||||
cat dist/image-check.json
|
||||
- name: Export image and checksums
|
||||
run: |
|
||||
sudo podman save --format oci-archive "$IMAGE" | zstd -T2 -6 -o "dist/$ASSET"
|
||||
test "$(stat -c %s "dist/$ASSET")" -lt 2147483648
|
||||
python3 - <<'PY'
|
||||
import json, os, pathlib, subprocess, tarfile
|
||||
archive = pathlib.Path('dist') / os.environ['ASSET']
|
||||
process = subprocess.Popen(['zstd', '-dc', str(archive)], stdout=subprocess.PIPE)
|
||||
index = None
|
||||
with tarfile.open(fileobj=process.stdout, mode='r|') as stream:
|
||||
for member in stream:
|
||||
if member.name == 'index.json':
|
||||
index = json.load(stream.extractfile(member))
|
||||
while process.stdout.read(1024 * 1024):
|
||||
pass
|
||||
if process.wait() != 0:
|
||||
raise SystemExit('Compressed OCI archive integrity check failed')
|
||||
manifests = index.get('manifests', []) if index else []
|
||||
if (len(manifests) != 1 or manifests[0].get('annotations', {}).get(
|
||||
'org.opencontainers.image.ref.name') != os.environ['IMAGE']):
|
||||
raise SystemExit('OCI archive must restore the normal launcher image tag')
|
||||
print('Compressed OCI archive and launcher tag verified')
|
||||
PY
|
||||
cd dist
|
||||
sha256sum "$ASSET" > "$ASSET.sha256"
|
||||
- name: Write build metadata
|
||||
env:
|
||||
RELEASE_TAG: ${{ steps.release-target.outputs.tag }}
|
||||
run: |
|
||||
python3 - <<'PY'
|
||||
import json, os, pathlib, subprocess
|
||||
commit = subprocess.check_output(['git', 'rev-parse', 'HEAD'], text=True).strip()
|
||||
image_id = subprocess.check_output(['sudo', 'podman', 'image', 'inspect', '--format', '{{.Id}}', os.environ['IMAGE']], text=True).strip()
|
||||
metadata = {
|
||||
'source': 'https://github.com/' + os.environ['GITHUB_REPOSITORY'],
|
||||
'commit': commit, 'release_tag': os.environ['RELEASE_TAG'] or None,
|
||||
'platform': 'linux/arm64', 'image_tag': os.environ['IMAGE'], 'image_id': image_id,
|
||||
'archive': os.environ['ASSET'], 'upscaling': 'Native', 'fsr3_compiled': False,
|
||||
'default_eye_size': [2160, 2160], 'default_refresh_hz': 72,
|
||||
'default_antialiasing': 'SMAA High',
|
||||
'checks': ['Native single-sample policy', 'refresh-rate policy', 'desktop mirror policy', 'video controls',
|
||||
'mirror command recording and runtime configuration',
|
||||
'AArch64 ELF and dynamic dependencies', 'payload and license packaging',
|
||||
'compressed OCI integrity and launcher tag'],
|
||||
'runtime_testing': 'CI packaging checks do not start SteamVR or Roblox',
|
||||
'licenses': 'Source notices and third-party notices are included under /work in the image',
|
||||
}
|
||||
pathlib.Path('dist/build-info.json').write_text(json.dumps(metadata, indent=2) + '\n')
|
||||
PY
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: nightcap-steam-frame-arm64
|
||||
path: dist/
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
retention-days: 14
|
||||
|
||||
release:
|
||||
if: needs.build.outputs.release_tag != ''
|
||||
needs: build
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: nightcap-steam-frame-arm64
|
||||
path: dist
|
||||
- name: Publish checked ARM64 package
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
RELEASE_TAG: ${{ needs.build.outputs.release_tag }}
|
||||
run: |
|
||||
cd dist
|
||||
sha256sum --check "$ASSET.sha256"
|
||||
printf '%s\n' \
|
||||
'Native ARM64 Steam Frame image and packaging-check metadata.' \
|
||||
'Roblox, SteamVR and host graphics drivers are downloaded or provided separately.' \
|
||||
'Verify the SHA256, then import with: zstd -dc nightcap-steam-frame-arm64.oci.tar.zst | podman load' \
|
||||
'See docs/INSTALL.md in this tag for launcher and SteamVR setup.' > release-notes.md
|
||||
gh release create "$RELEASE_TAG" --verify-tag \
|
||||
--title "Steam Frame ARM64 $RELEASE_TAG" --notes-file release-notes.md \
|
||||
"$ASSET" "$ASSET.sha256" image-check.json build-info.json
|
||||
+29
-13
@@ -1,7 +1,9 @@
|
||||
# Build
|
||||
/build/
|
||||
/build-test/
|
||||
/build_*/
|
||||
/build-flatpak/
|
||||
/build-anylinux*/
|
||||
/.flatpak-builder/
|
||||
/cmake-build-*/
|
||||
/out/
|
||||
@@ -20,6 +22,12 @@ Testing/
|
||||
CMakeUserPresets.json
|
||||
*.cmake.user
|
||||
|
||||
# local direnv development shell cache
|
||||
.direnv/
|
||||
|
||||
# clangd cache
|
||||
.cache
|
||||
|
||||
*.o
|
||||
*.obj
|
||||
*.a
|
||||
@@ -50,19 +58,7 @@ Makefile.cmake
|
||||
/cmake-build-release/
|
||||
|
||||
# Roblox payload
|
||||
/rbx_bin/libroblox.so
|
||||
/rbx_bin/libroblox.so.*
|
||||
/rbx_bin/client_settings_android.json
|
||||
/rbx_bin/assets/
|
||||
/rbx_bin/android/
|
||||
/rbx_bin/sober_apk/
|
||||
/rbx_bin/versions/
|
||||
/rbx_bin/.payload-update.*/
|
||||
/rbx_bin/*.apk
|
||||
/rbx_bin/*.so
|
||||
/rbx_bin/*.so.*
|
||||
|
||||
!/rbx_bin/.gitkeep
|
||||
/rbx_bin/
|
||||
|
||||
# Runtime state
|
||||
/runtime/
|
||||
@@ -181,3 +177,23 @@ lsan.log*
|
||||
# Test fixtures
|
||||
!/third_party/mcpelauncher-linker/core/libziparchive/**/*.zip
|
||||
!/third_party/mcpelauncher-linker/core/libziparchive/**/*.apk
|
||||
|
||||
# Working plans, not part of the shipped documentation
|
||||
/docs/plans/
|
||||
|
||||
# Steam Frame local builds and generated SteamVR assets
|
||||
/build-frame-arm64/
|
||||
/frame-ui-local/
|
||||
/tools/steam_frame/steamvr-*.original.*
|
||||
/tools/steam_frame/steamvr-*.sharpening.*
|
||||
/tools/steam_frame/steamvr-sharpening-ui-patch.json
|
||||
/steam-shortcut.json
|
||||
|
||||
# Local Frame payloads, images and source-transfer archives
|
||||
/data/
|
||||
/host-drivers/
|
||||
*.bundle
|
||||
*.oci
|
||||
*.img
|
||||
/current.json
|
||||
/previous_good.json
|
||||
@@ -4,3 +4,6 @@
|
||||
[submodule "third_party/Vulkan-Headers"]
|
||||
path = third_party/Vulkan-Headers
|
||||
url = https://github.com/KhronosGroup/Vulkan-Headers.git
|
||||
[submodule "third_party/OpenXR-SDK"]
|
||||
path = third_party/OpenXR-SDK
|
||||
url = https://github.com/KhronosGroup/OpenXR-SDK.git
|
||||
+149
-86
@@ -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");
|
||||
@@ -8,8 +9,8 @@
|
||||
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(Mocktail
|
||||
VERSION 1.0.2
|
||||
DESCRIPTION "Android x86-64 Roblox compatibility runtime for Linux"
|
||||
VERSION 0.3.4
|
||||
DESCRIPTION "Android Roblox compatibility runtime for Linux"
|
||||
LANGUAGES CXX
|
||||
)
|
||||
|
||||
@@ -60,7 +61,7 @@ set(MOCKTAIL_DEFAULT_SIGNING_TRUST_MANIFEST
|
||||
CACHE FILEPATH
|
||||
"Fallback signing trust manifest used outside an installed runtime"
|
||||
)
|
||||
set(MOCKTAIL_DISCORD_APPLICATION_ID "1537088975720812655" CACHE STRING
|
||||
set(MOCKTAIL_DISCORD_APPLICATION_ID "1549428231927500843" CACHE STRING
|
||||
"Discord application ID baked into Mocktail Rich Presence releases"
|
||||
)
|
||||
string(LENGTH "${MOCKTAIL_DISCORD_APPLICATION_ID}"
|
||||
@@ -125,6 +126,8 @@ set(VULKAN_HEADERS_ENABLE_INSTALL OFF CACHE BOOL
|
||||
"Install vendored Vulkan-Headers" FORCE)
|
||||
add_subdirectory(third_party/Vulkan-Headers EXCLUDE_FROM_ALL)
|
||||
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/MocktailVR.cmake)
|
||||
|
||||
include(CTest)
|
||||
if(BUILD_TESTING)
|
||||
# Package builds disable tests and do not fetch Google Test.
|
||||
@@ -138,12 +141,7 @@ if(BUILD_TESTING)
|
||||
FetchContent_MakeAvailable(googletest)
|
||||
endif()
|
||||
|
||||
add_library(mocktail_crypto_foundation STATIC
|
||||
src/foundation/sha256.cc
|
||||
)
|
||||
add_library(Mocktail::CryptoFoundation ALIAS mocktail_crypto_foundation)
|
||||
target_include_directories(mocktail_crypto_foundation PUBLIC include)
|
||||
mocktail_apply_compile_options(mocktail_crypto_foundation)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/MocktailPlatformGraphics.cmake)
|
||||
foreach(foundation_target IN ITEMS
|
||||
@@ -201,12 +199,12 @@ target_include_directories(mocktail_bionic_stdio_runtime PUBLIC include)
|
||||
mocktail_apply_compile_options(mocktail_bionic_stdio_runtime)
|
||||
|
||||
find_package(CURL REQUIRED)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
find_package(nlohmann_json CONFIG REQUIRED)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(LIBYAML REQUIRED IMPORTED_TARGET yaml-0.1)
|
||||
pkg_check_modules(MINIZIP REQUIRED IMPORTED_TARGET minizip)
|
||||
pkg_check_modules(CAPSTONE REQUIRED IMPORTED_TARGET capstone)
|
||||
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
|
||||
pkg_check_modules(GTK4 REQUIRED IMPORTED_TARGET gtk4)
|
||||
pkg_check_modules(LIBADWAITA REQUIRED IMPORTED_TARGET "libadwaita-1>=1.6")
|
||||
pkg_check_modules(WEBKITGTK6 REQUIRED IMPORTED_TARGET webkitgtk-6.0)
|
||||
@@ -214,6 +212,7 @@ pkg_check_modules(WEBKITGTK6 REQUIRED IMPORTED_TARGET webkitgtk-6.0)
|
||||
add_library(mocktail_update STATIC
|
||||
src/update/android_manifest.cc
|
||||
src/update/apk_bundle.cc
|
||||
src/update/apk_provider.cc
|
||||
src/update/apk_signature.cc
|
||||
src/update/apkpure_provider.cc
|
||||
src/update/candidate_approval.cc
|
||||
@@ -223,6 +222,7 @@ add_library(mocktail_update STATIC
|
||||
src/update/payload_integrity.cc
|
||||
src/update/payload_store.cc
|
||||
src/update/readiness_canary.cc
|
||||
src/update/unsafe_latest_runner.cc
|
||||
src/update/update_config.cc
|
||||
src/update/update_coordinator.cc
|
||||
src/update/zip_archive.cc
|
||||
@@ -231,7 +231,6 @@ add_library(Mocktail::Update ALIAS mocktail_update)
|
||||
target_include_directories(mocktail_update PUBLIC include)
|
||||
target_link_libraries(mocktail_update PUBLIC
|
||||
Mocktail::Compat
|
||||
Mocktail::CryptoFoundation
|
||||
CURL::libcurl
|
||||
OpenSSL::Crypto
|
||||
PkgConfig::LIBYAML
|
||||
@@ -252,6 +251,10 @@ mocktail_apply_compile_options(mocktail_updater)
|
||||
|
||||
add_library(mocktail_legacy_jnivm STATIC
|
||||
src/jnivm/jnivm.cc
|
||||
src/jnivm/jni_references.cc
|
||||
src/jnivm/jni_fields.cc
|
||||
src/jnivm/jni_strings.cc
|
||||
src/jnivm/jni_method_names.cc
|
||||
)
|
||||
add_library(Mocktail::LegacyJni ALIAS mocktail_legacy_jnivm)
|
||||
target_include_directories(mocktail_legacy_jnivm PUBLIC include)
|
||||
@@ -269,13 +272,16 @@ add_library(mocktail_runtime STATIC
|
||||
src/runtime/crash_report_policy.cc
|
||||
src/runtime/discord_rpc.cc
|
||||
src/runtime/device_profile.cc
|
||||
src/runtime/engine_pump_rest.cc
|
||||
src/runtime/environment.cc
|
||||
src/runtime/external_launch_broker.cc
|
||||
src/runtime/frame_rate_policy.cc
|
||||
src/runtime/fleasion.cc
|
||||
src/runtime/game_mode.cc
|
||||
src/runtime/game_session_coordinator.cc
|
||||
src/runtime/graphics_launch_policy.cc
|
||||
src/runtime/http_client_policy.cc
|
||||
src/runtime/luau_codegen_switch.cc
|
||||
src/runtime/memory_limit.cc
|
||||
src/runtime/owned_pthread.cc
|
||||
src/runtime/performance_policy.cc
|
||||
@@ -286,9 +292,18 @@ add_library(mocktail_runtime STATIC
|
||||
src/runtime/roblox_capability_resolver.cc
|
||||
src/runtime/roblox_experience_launch_bridge.cc
|
||||
src/runtime/roblox_launch_uri.cc
|
||||
src/runtime/roblox_presence_resolver.cc
|
||||
src/runtime/roblox_game_session_native_adapter.cc
|
||||
src/runtime/roblox_web_view_bridge.cc
|
||||
src/runtime/roblox_permissions_bridge.cc
|
||||
src/runtime/roblox_account_bridge.cc
|
||||
src/runtime/roblox_message_bus_request_bridge.cc
|
||||
src/runtime/roblox_call_protocol_bridge.cc
|
||||
src/runtime/roblox_haptic_protocol_bridge.cc
|
||||
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
|
||||
@@ -298,6 +313,9 @@ add_library(mocktail_runtime STATIC
|
||||
src/runtime/startup_state_machine.cc
|
||||
src/runtime/supported_launch_policy.cc
|
||||
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)
|
||||
@@ -328,9 +346,10 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND
|
||||
endif()
|
||||
target_link_libraries(mocktail_runtime PUBLIC Threads::Threads)
|
||||
target_link_libraries(mocktail_runtime PRIVATE
|
||||
Mocktail::CryptoFoundation
|
||||
OpenSSL::Crypto
|
||||
CURL::libcurl
|
||||
nlohmann_json::nlohmann_json
|
||||
PkgConfig::GIO
|
||||
PkgConfig::LIBYAML
|
||||
${CMAKE_DL_LIBS}
|
||||
)
|
||||
@@ -379,7 +398,8 @@ add_library(mocktail_webview_helper_policy STATIC
|
||||
src/webview/webview_helper_policy.cc
|
||||
)
|
||||
add_library(Mocktail::WebViewHelperPolicy ALIAS mocktail_webview_helper_policy)
|
||||
target_include_directories(mocktail_webview_helper_policy PUBLIC src)
|
||||
target_include_directories(mocktail_webview_helper_policy PUBLIC src
|
||||
PRIVATE include)
|
||||
target_link_libraries(mocktail_webview_helper_policy PUBLIC
|
||||
PkgConfig::WEBKITGTK6
|
||||
nlohmann_json::nlohmann_json
|
||||
@@ -410,9 +430,6 @@ target_link_libraries(mocktail_auth_runtime_composition PUBLIC
|
||||
mocktail_runtime
|
||||
mocktail_services
|
||||
)
|
||||
target_link_libraries(mocktail_auth_runtime_composition PRIVATE
|
||||
Mocktail::CryptoFoundation
|
||||
)
|
||||
mocktail_apply_compile_options(mocktail_auth_runtime_composition)
|
||||
|
||||
add_library(mocktail_linker STATIC
|
||||
@@ -424,6 +441,7 @@ target_link_libraries(mocktail_linker PUBLIC
|
||||
linker
|
||||
mocktail_compat
|
||||
mocktail_bionic_stdio_runtime
|
||||
mocktail_bionic_abi_exports
|
||||
)
|
||||
mocktail_apply_compile_options(mocktail_linker)
|
||||
target_compile_definitions(mocktail_linker PUBLIC MOCKTAIL_USE_BIONIC_LINKER=1)
|
||||
@@ -452,6 +470,7 @@ add_library(mocktail_window STATIC
|
||||
src/window/roblox_fullscreen_menu_request_gate.cc
|
||||
src/window/video_driver_policy.cc
|
||||
src/window/window.cc
|
||||
src/window/window_creation_policy.cc
|
||||
src/window/window_fullscreen_request_gate.cc
|
||||
src/window/window_game_surface_bridge.cc
|
||||
src/window/window_pointer_capture_owner.cc
|
||||
@@ -464,9 +483,10 @@ target_include_directories(mocktail_window PUBLIC src)
|
||||
mocktail_apply_compile_options(mocktail_window)
|
||||
target_link_libraries(mocktail_window PUBLIC
|
||||
Mocktail::PlatformSdl
|
||||
Mocktail::SdlVulkanWsi
|
||||
SDL3::SDL3
|
||||
${CMAKE_DL_LIBS}
|
||||
PRIVATE Mocktail::Runtime
|
||||
PRIVATE Mocktail::Runtime Mocktail::GlesTextOverlay Mocktail::GraphicsFoundation
|
||||
)
|
||||
|
||||
add_library(mocktail_roblox_output_device_runtime STATIC
|
||||
@@ -528,56 +548,25 @@ target_include_directories(mocktail_bionic_abi_exports
|
||||
PRIVATE src
|
||||
)
|
||||
mocktail_apply_compile_options(mocktail_bionic_abi_exports)
|
||||
# These exported Bionic FORTIFY symbols intentionally overlap glibc's
|
||||
# __*_chk entry points. When the bridge is visible to whole-program LTO,
|
||||
# GCC can fold a fortified host call back into its own compatibility wrapper
|
||||
# and turn the resulting tail recursion into an infinite loop. Keep this
|
||||
# translation unit opaque to interprocedural optimization and make its calls
|
||||
# to the host C library explicit external calls.
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
||||
set_property(SOURCE src/compat/bionic_abi_exports.cc APPEND PROPERTY
|
||||
COMPILE_OPTIONS
|
||||
-fno-lto
|
||||
-fno-builtin
|
||||
)
|
||||
endif()
|
||||
target_link_libraries(mocktail_bionic_abi_exports PRIVATE
|
||||
mocktail_bionic_stdio_runtime
|
||||
mocktail_window
|
||||
Threads::Threads
|
||||
)
|
||||
|
||||
include(CheckCXXSourceCompiles)
|
||||
include(CheckIncludeFileCXX)
|
||||
include(CMakePushCheckState)
|
||||
|
||||
check_include_file_cxx(execinfo.h MOCKTAIL_HAVE_EXECINFO_H)
|
||||
if(NOT MOCKTAIL_HAVE_EXECINFO_H)
|
||||
message(FATAL_ERROR
|
||||
"execinfo.h is required for crash diagnostics; on musl install "
|
||||
"libexecinfo-devel"
|
||||
)
|
||||
endif()
|
||||
|
||||
set(MOCKTAIL_EXECINFO_LINK_LIBRARY "")
|
||||
set(_mocktail_execinfo_link_probe [=[
|
||||
#include <execinfo.h>
|
||||
int main() {
|
||||
void* frames[1] = {};
|
||||
const int count = backtrace(frames, 1);
|
||||
return backtrace_symbols(frames, count) == nullptr;
|
||||
}
|
||||
]=])
|
||||
check_cxx_source_compiles(
|
||||
"${_mocktail_execinfo_link_probe}"
|
||||
MOCKTAIL_EXECINFO_IN_HOST_LIBC
|
||||
)
|
||||
if(NOT MOCKTAIL_EXECINFO_IN_HOST_LIBC)
|
||||
find_library(MOCKTAIL_EXECINFO_LIBRARY NAMES execinfo REQUIRED)
|
||||
cmake_push_check_state(RESET)
|
||||
set(CMAKE_REQUIRED_LIBRARIES "${MOCKTAIL_EXECINFO_LIBRARY}")
|
||||
check_cxx_source_compiles(
|
||||
"${_mocktail_execinfo_link_probe}"
|
||||
MOCKTAIL_EXECINFO_IN_EXTERNAL_LIBRARY
|
||||
)
|
||||
cmake_pop_check_state()
|
||||
if(NOT MOCKTAIL_EXECINFO_IN_EXTERNAL_LIBRARY)
|
||||
message(FATAL_ERROR
|
||||
"execinfo.h is available, but backtrace and backtrace_symbols cannot "
|
||||
"be linked"
|
||||
)
|
||||
endif()
|
||||
set(MOCKTAIL_EXECINFO_LINK_LIBRARY "${MOCKTAIL_EXECINFO_LIBRARY}")
|
||||
endif()
|
||||
unset(_mocktail_execinfo_link_probe)
|
||||
|
||||
add_library(mocktail_legacy_runtime STATIC
|
||||
src/legacy/legacy_runtime.cc
|
||||
src/runtime/roblox_text_input_jni_bridge.cc
|
||||
@@ -602,7 +591,6 @@ target_link_libraries(mocktail_legacy_runtime PRIVATE
|
||||
libc_shim
|
||||
mocktail_window
|
||||
mocktail_graphics_foundation
|
||||
${MOCKTAIL_EXECINFO_LINK_LIBRARY}
|
||||
)
|
||||
target_compile_definitions(mocktail_legacy_runtime PRIVATE
|
||||
"MOCKTAIL_DEFAULT_COMPATIBILITY_MANIFEST=\"${MOCKTAIL_DEFAULT_COMPATIBILITY_MANIFEST}\""
|
||||
@@ -615,6 +603,7 @@ target_compile_definitions(mocktail PRIVATE
|
||||
"MOCKTAIL_DEFAULT_COMPATIBILITY_MANIFEST=\"${MOCKTAIL_DEFAULT_COMPATIBILITY_MANIFEST}\""
|
||||
)
|
||||
target_link_libraries(mocktail PRIVATE
|
||||
Mocktail::VR
|
||||
mocktail_legacy_runtime
|
||||
mocktail_auth_runtime_composition
|
||||
mocktail_failure_dialog_runtime
|
||||
@@ -630,6 +619,45 @@ target_link_options(mocktail PRIVATE
|
||||
-Wl,--export-dynamic
|
||||
)
|
||||
|
||||
# Native FreeBSD helper for Linuxulator's unsupported DF socket option.
|
||||
# The helper is x86-64 assembly; other host architectures have nothing to
|
||||
# assemble.
|
||||
if(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$")
|
||||
set(MOCKTAIL_BUILD_FREEBSD_SOCKET_HELPER OFF)
|
||||
endif()
|
||||
option(MOCKTAIL_BUILD_FREEBSD_SOCKET_HELPER
|
||||
"Build the native FreeBSD Linuxulator socket helper" ON)
|
||||
if(MOCKTAIL_BUILD_FREEBSD_SOCKET_HELPER)
|
||||
find_program(MOCKTAIL_LLD_EXECUTABLE NAMES ld.lld REQUIRED)
|
||||
find_program(MOCKTAIL_ELFEDIT_EXECUTABLE NAMES elfedit REQUIRED)
|
||||
get_filename_component(MOCKTAIL_LLD_DIRECTORY
|
||||
"${MOCKTAIL_LLD_EXECUTABLE}" DIRECTORY)
|
||||
set(MOCKTAIL_FREEBSD_SOCKET_HELPER
|
||||
"${CMAKE_BINARY_DIR}/mocktail_freebsd_socket_helper")
|
||||
add_custom_command(
|
||||
OUTPUT "${MOCKTAIL_FREEBSD_SOCKET_HELPER}"
|
||||
COMMAND "${CMAKE_CXX_COMPILER}"
|
||||
-x assembler-with-cpp
|
||||
-nostdlib
|
||||
-static
|
||||
-no-pie
|
||||
"-B${MOCKTAIL_LLD_DIRECTORY}/"
|
||||
-fuse-ld=lld
|
||||
-Wl,-e,_start,--build-id=none,-z,noexecstack
|
||||
-o "${MOCKTAIL_FREEBSD_SOCKET_HELPER}"
|
||||
"${CMAKE_SOURCE_DIR}/src/compat/freebsd_socket_helper.S"
|
||||
COMMAND "${MOCKTAIL_ELFEDIT_EXECUTABLE}"
|
||||
--output-osabi FreeBSD "${MOCKTAIL_FREEBSD_SOCKET_HELPER}"
|
||||
DEPENDS "${CMAKE_SOURCE_DIR}/src/compat/freebsd_socket_helper.S"
|
||||
COMMENT "Building native FreeBSD Linuxulator socket helper"
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_target(mocktail_freebsd_socket_helper_build ALL
|
||||
DEPENDS "${MOCKTAIL_FREEBSD_SOCKET_HELPER}"
|
||||
)
|
||||
add_dependencies(mocktail mocktail_freebsd_socket_helper_build)
|
||||
endif()
|
||||
|
||||
set_target_properties(mocktail PROPERTIES
|
||||
BUILD_RPATH "\$ORIGIN"
|
||||
INSTALL_RPATH "\$ORIGIN;\$ORIGIN/../${CMAKE_INSTALL_LIBDIR}/mocktail"
|
||||
@@ -662,6 +690,11 @@ install(TARGETS
|
||||
RUNTIME DESTINATION "${MOCKTAIL_INSTALL_RUNTIME_DIR}"
|
||||
LIBRARY DESTINATION "${MOCKTAIL_INSTALL_RUNTIME_DIR}"
|
||||
)
|
||||
if(MOCKTAIL_BUILD_FREEBSD_SOCKET_HELPER)
|
||||
install(PROGRAMS "${MOCKTAIL_FREEBSD_SOCKET_HELPER}"
|
||||
DESTINATION "${MOCKTAIL_INSTALL_RUNTIME_DIR}"
|
||||
)
|
||||
endif()
|
||||
install(FILES
|
||||
config/roblox_compatibility.json
|
||||
config/roblox_bootstrap_sources.json
|
||||
@@ -669,13 +702,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
|
||||
@@ -684,7 +728,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"
|
||||
)
|
||||
@@ -717,33 +761,39 @@ install(CODE "
|
||||
endif()
|
||||
")
|
||||
|
||||
set(MOCKTAIL_PACKAGE_NAMES mocktail mocktail-nightly nightcap nightcap-nightly)
|
||||
set(MOCKTAIL_PACKAGE_NAME "mocktail" CACHE STRING
|
||||
"Native package name"
|
||||
)
|
||||
set(MOCKTAIL_PACKAGE_VERSION "${PROJECT_VERSION}" CACHE STRING
|
||||
"Native package version"
|
||||
)
|
||||
set(MOCKTAIL_PACKAGE_VERSION_SUFFIX "" CACHE STRING
|
||||
"Native package version suffix"
|
||||
)
|
||||
if(NOT MOCKTAIL_PACKAGE_NAME STREQUAL "mocktail" AND
|
||||
NOT MOCKTAIL_PACKAGE_NAME STREQUAL "mocktail-nightly")
|
||||
if(NOT MOCKTAIL_PACKAGE_NAME IN_LIST MOCKTAIL_PACKAGE_NAMES)
|
||||
string(JOIN ", " MOCKTAIL_PACKAGE_NAMES_TEXT ${MOCKTAIL_PACKAGE_NAMES})
|
||||
message(FATAL_ERROR
|
||||
"MOCKTAIL_PACKAGE_NAME must be mocktail or mocktail-nightly")
|
||||
"MOCKTAIL_PACKAGE_NAME must be one of ${MOCKTAIL_PACKAGE_NAMES_TEXT}")
|
||||
endif()
|
||||
|
||||
set(CPACK_PACKAGE_NAME "${MOCKTAIL_PACKAGE_NAME}")
|
||||
set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}${MOCKTAIL_PACKAGE_VERSION_SUFFIX}")
|
||||
set(CPACK_PACKAGE_VENDOR "Mocktail Project")
|
||||
set(CPACK_PACKAGE_CONTACT "komaruworld <rtysus228@gmail.com>")
|
||||
set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/komaruworld/mocktail")
|
||||
set(CPACK_PACKAGE_VERSION
|
||||
"${MOCKTAIL_PACKAGE_VERSION}${MOCKTAIL_PACKAGE_VERSION_SUFFIX}"
|
||||
)
|
||||
set(CPACK_PACKAGE_VENDOR "Nightcap")
|
||||
set(CPACK_PACKAGE_CONTACT "vii <coderdayton14@gmail.com>")
|
||||
set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/CoderDayton/nightcap")
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PROJECT_DESCRIPTION}")
|
||||
set(CPACK_PACKAGE_DESCRIPTION
|
||||
"Runs the Android x86-64 Roblox client on Linux."
|
||||
"Runs the Android Roblox client on Linux."
|
||||
)
|
||||
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE")
|
||||
set(CPACK_PACKAGING_INSTALL_PREFIX "/usr")
|
||||
set(CPACK_STRIP_FILES TRUE)
|
||||
|
||||
set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)
|
||||
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "komaruworld <rtysus228@gmail.com>")
|
||||
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "vii <coderdayton14@gmail.com>")
|
||||
set(CPACK_DEBIAN_PACKAGE_SECTION "games")
|
||||
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
|
||||
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
|
||||
@@ -753,15 +803,14 @@ set(CPACK_RPM_PACKAGE_LICENSE "Apache-2.0")
|
||||
set(CPACK_RPM_PACKAGE_GROUP "Amusements/Games")
|
||||
set(CPACK_RPM_PACKAGE_AUTOREQPROV ON)
|
||||
|
||||
if(MOCKTAIL_PACKAGE_NAME STREQUAL "mocktail-nightly")
|
||||
set(CPACK_DEBIAN_PACKAGE_CONFLICTS "mocktail")
|
||||
set(CPACK_DEBIAN_PACKAGE_REPLACES "mocktail")
|
||||
set(CPACK_RPM_PACKAGE_CONFLICTS "mocktail")
|
||||
else()
|
||||
set(CPACK_DEBIAN_PACKAGE_CONFLICTS "mocktail-nightly")
|
||||
set(CPACK_DEBIAN_PACKAGE_REPLACES "mocktail-nightly")
|
||||
set(CPACK_RPM_PACKAGE_CONFLICTS "mocktail-nightly")
|
||||
endif()
|
||||
# Every variant installs the same /usr/bin/mocktail and /usr/share/mocktail,
|
||||
# so only one of them can be present at a time.
|
||||
set(MOCKTAIL_PACKAGE_SIBLINGS ${MOCKTAIL_PACKAGE_NAMES})
|
||||
list(REMOVE_ITEM MOCKTAIL_PACKAGE_SIBLINGS "${MOCKTAIL_PACKAGE_NAME}")
|
||||
string(JOIN ", " MOCKTAIL_PACKAGE_CONFLICTS ${MOCKTAIL_PACKAGE_SIBLINGS})
|
||||
set(CPACK_DEBIAN_PACKAGE_CONFLICTS "${MOCKTAIL_PACKAGE_CONFLICTS}")
|
||||
set(CPACK_DEBIAN_PACKAGE_REPLACES "${MOCKTAIL_PACKAGE_CONFLICTS}")
|
||||
set(CPACK_RPM_PACKAGE_CONFLICTS "${MOCKTAIL_PACKAGE_CONFLICTS}")
|
||||
include(CPack)
|
||||
|
||||
add_dependencies(mocktail all_stubs)
|
||||
@@ -771,3 +820,17 @@ add_dependencies(mocktail mocktail_webview_helper)
|
||||
if(BUILD_TESTING)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
# Native SteamVR identity/settings helper; a background client never takes scene focus.
|
||||
add_executable(nightcap-vr-settings tools/frame_video_settings/nightcap-vr-settings.cc)
|
||||
target_include_directories(nightcap-vr-settings PRIVATE tools/frame_video_settings)
|
||||
target_link_libraries(nightcap-vr-settings PRIVATE ${CMAKE_DL_LIBS})
|
||||
install(TARGETS nightcap-vr-settings RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
if(MOCKTAIL_ENABLE_VR)
|
||||
add_executable(nightcap-vr-performance tools/frame_video_settings/performance_overlay.cc)
|
||||
target_include_directories(nightcap-vr-performance PRIVATE include tools/frame_video_settings)
|
||||
target_link_libraries(nightcap-vr-performance PRIVATE SDL3::SDL3 SDL3_ttf::SDL3_ttf
|
||||
PkgConfig::FONTCONFIG ${CMAKE_DL_LIBS})
|
||||
install(TARGETS nightcap-vr-performance RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
endif()
|
||||
@@ -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
|
||||
|
||||
@@ -14,10 +15,13 @@ PORTABLE_MODE ?= standalone
|
||||
PORTABLE_CANONICAL_MODE := $(if $(filter dynamic minimal,$(PORTABLE_MODE)),thin,$(if $(filter full static,$(PORTABLE_MODE)),standalone,$(PORTABLE_MODE)))
|
||||
PORTABLE_DIR ?= dist/mocktail-linux-x86_64-glibc-$(PORTABLE_CANONICAL_MODE)
|
||||
APPIMAGE ?= dist/Mocktail-x86_64.AppImage
|
||||
APPIMAGE_FORMAT ?= classic
|
||||
ANYLINUX_PACKAGER ?= quick-sharun
|
||||
ANYLINUX_APPIMAGETOOL ?= appimagetool
|
||||
PREFIX ?= /usr
|
||||
FLATPAK_BUILD_DIR ?= build-flatpak
|
||||
FLATPAK_MANIFEST ?= packaging/flatpak/space.bigrat.mocktail.json
|
||||
FLATPAK_JOBS ?= 4
|
||||
FLATPAK_MANIFEST ?= packaging/flatpak/io.github.CoderDayton.nightcap.json
|
||||
FLATPAK_JOBS ?= 2
|
||||
|
||||
define build_native_runtime
|
||||
@git submodule update --init --recursive
|
||||
@@ -52,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)
|
||||
@@ -73,13 +77,11 @@ portable-test: release-runtime ## Verify portable packaging and relocation
|
||||
|
||||
standalone: appimage ## Build the standalone x86-64 AppImage
|
||||
|
||||
appimage: release-runtime ## Build AppImage (requires appimagetool)
|
||||
@command -v appimagetool >/dev/null 2>&1 || { \
|
||||
echo "appimagetool is required for make appimage" >&2; \
|
||||
echo "Install it, then rerun make appimage." >&2; \
|
||||
exit 1; \
|
||||
}
|
||||
@./scripts/package_portable.sh --build-dir "$(BUILD_DIR)" \
|
||||
appimage: release-runtime ## Build AppImage (APPIMAGE_FORMAT=classic|anylinux)
|
||||
@MOCKTAIL_APPIMAGE_FORMAT="$(APPIMAGE_FORMAT)" \
|
||||
MOCKTAIL_ANYLINUX_PACKAGER="$(ANYLINUX_PACKAGER)" \
|
||||
MOCKTAIL_ANYLINUX_APPIMAGETOOL="$(ANYLINUX_APPIMAGETOOL)" \
|
||||
./scripts/package_portable.sh --build-dir "$(BUILD_DIR)" \
|
||||
--libc glibc --output "$(PORTABLE_DIR)" --mode "$(PORTABLE_MODE)" \
|
||||
--appimage "$(APPIMAGE)"
|
||||
|
||||
@@ -90,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
|
||||
@@ -128,8 +130,8 @@ auto-run: ## Build/run loop with crash summaries
|
||||
run-gles: build ## Run strict system EGL/OpenGL ES 3 without Vulkan fallback
|
||||
@$(BINARY) --graphics opengl
|
||||
|
||||
run-angle: ## Run with ANGLE/Vulkan compatibility backend
|
||||
@MOCKTAIL_GRAPHICS_BACKEND=angle-vulkan ./scripts/run_sober.sh
|
||||
run-angle: build ## Run with ANGLE/Vulkan compatibility backend
|
||||
@$(BINARY) --graphics angle-vulkan
|
||||
|
||||
update-roblox: ## Validate and import the current x86_64 Roblox bundle cached by Sober
|
||||
@./scripts/update_roblox_payload.sh
|
||||
|
||||
@@ -1,164 +1,62 @@
|
||||
# Mocktail
|
||||
<div align="center">
|
||||
<img src="packaging/io.github.CoderDayton.nightcap.svg" alt="Nightcap logo" width="128">
|
||||
<h1>Nightcap Roblox VR for Steam Frame</h1>
|
||||
</div>
|
||||
|
||||
[](https://github.com/komaruworld/mocktail/actions/workflows/ci.yml)
|
||||
[](https://github.com/komaruworld/mocktail/stargazers)
|
||||
[](https://github.com/komaruworld/mocktail/releases/latest)
|
||||
[](LICENSE)
|
||||
Native ARM64 Roblox VR for Steam Frame, with eye-tracked foveated rendering,
|
||||
controller input and SteamVR integration.
|
||||
|
||||
Mocktail runs the Android `x86_64` Roblox client on Linux. It provides the
|
||||
Android ABI and JNI pieces the client expects, then connects them to SDL3 and
|
||||
Vulkan or OpenGL on the Linux side.
|
||||
The standard video profile is **native 2160 × 2160 per eye**, **72 Hz**,
|
||||
**SMAA High** and **Performance** foveation. Frame rate depends on the Roblox
|
||||
experience and its graphics settings; 72 FPS is the target.
|
||||
|
||||
Mocktail is an independent community project. It is not affiliated with Roblox
|
||||
Corporation or VinegarHQ and does not distribute the Roblox client.
|
||||
## Features
|
||||
|
||||
## How it works
|
||||
- Native ARM64 execution through Nightcap's Android compatibility layer.
|
||||
- Stereo OpenXR rendering, head tracking, VR controllers, haptics and an
|
||||
in-headset keyboard.
|
||||
- Direct eye tracking with a full-detail gaze region and gradual reductions in
|
||||
peripheral shading quality.
|
||||
- Adjustable gaze radius and Quality, Balanced and Performance foveation modes.
|
||||
- Gaze-only FXAA, SMAA Low and SMAA High anti-aliasing.
|
||||
- Native-resolution rendering with a 2160 × 2160 per-eye recommended default.
|
||||
- Roblox's internal frame-rate cap follows the selected headset refresh rate.
|
||||
- Renderer and session handling across menu, game and game-to-game transitions.
|
||||
- Native microphone capture bridge for Roblox voice chat.
|
||||
- One **Nightcap Roblox VR** Steam entry with the original Nightcap icon and an
|
||||
optional desktop mirror. The mirror is off by default on Steam Frame.
|
||||
- Simple **Foveated Rendering**, **Gaze sharp radius** and **Anti Aliasing**
|
||||
controls in SteamVR's Video settings.
|
||||
- Optional in-headset performance overlay with actual game FPS, headset Hz,
|
||||
frame time, GPU time and current per-eye resolution.
|
||||
|
||||
```
|
||||
Roblox APK -> signature and ABI checks -> Bionic + JNI -> SDL3 + Vulkan/OpenGL
|
||||
```
|
||||
## Install
|
||||
|
||||
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
|
||||
an update fails.
|
||||
Install the native ARM64 build from [Releases](https://github.com/InstanceBrick/nightcap/releases),
|
||||
or build it on the Frame using the [installation guide](docs/INSTALL.md).
|
||||
The ARM64 package contains Nightcap and its open-source runtime dependencies.
|
||||
Roblox is downloaded separately on the device. Roblox binaries, account data,
|
||||
SteamVR files and the Frame's graphics drivers are not included in the package.
|
||||
|
||||
## Install with Flatpak
|
||||
Current ARM64 client support: **2.742.1059 (3222)** and **2.736.1408 (2998)**.
|
||||
|
||||
Install the stable release from Flathub:
|
||||
- [Installation and updates](docs/INSTALL.md)
|
||||
- [Video, input and microphone settings](docs/SETTINGS.md)
|
||||
- [Building and source layout](docs/DEVELOPMENT.md)
|
||||
|
||||
```bash
|
||||
flatpak install flathub space.bigrat.mocktail
|
||||
flatpak run space.bigrat.mocktail
|
||||
```
|
||||
## About this fork
|
||||
|
||||
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>
|
||||
|
||||
## Building
|
||||
|
||||
Only Linux `x86_64` is supported. Building requires CMake 3.20+, Git,
|
||||
pkg-config, a C++17 compiler, SDL 3.4+, SDL3_ttf, Vulkan, EGL, libplacebo,
|
||||
fontconfig, libcurl, OpenSSL, libelf, libyaml, minizip, Capstone 5, utf8proc,
|
||||
nlohmann/json, GTK4, libadwaita 1.6+, and WebKitGTK 6.0.
|
||||
|
||||
<details>
|
||||
<summary>Ubuntu 26.04+</summary>
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install build-essential cmake git ninja-build pkg-config \
|
||||
libsdl3-dev libsdl3-ttf-dev libcurl4-openssl-dev libssl-dev \
|
||||
nlohmann-json3-dev 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 zlib1g-dev
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Arch Linux</summary>
|
||||
|
||||
```bash
|
||||
sudo pacman -S --needed base-devel cmake git ninja pkgconf sdl3 sdl3_ttf \
|
||||
curl openssl nlohmann-json libyaml libelf minizip capstone gtk4 \
|
||||
libadwaita webkitgtk-6.0 libutf8proc fontconfig libglvnd \
|
||||
libplacebo vulkan-headers vulkan-icd-loader zlib
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Fedora 44+</summary>
|
||||
|
||||
```bash
|
||||
sudo dnf install gcc-c++ cmake git ninja-build pkgconf-pkg-config \
|
||||
SDL3-devel SDL3_ttf-devel libcurl-devel openssl-devel \
|
||||
nlohmann-json-devel libyaml-devel elfutils-libelf-devel 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 zlib-ng-compat-devel
|
||||
```
|
||||
</details>
|
||||
|
||||
```bash
|
||||
git clone --recurse-submodules https://github.com/komaruworld/mocktail.git
|
||||
cd mocktail
|
||||
make build
|
||||
./build/mocktail
|
||||
```
|
||||
The `steam-frame` branch is the Steam Frame edition of the `vr` branch of
|
||||
[CoderDayton/nightcap](https://github.com/CoderDayton/nightcap), itself a fork of
|
||||
[komaruworld/mocktail](https://github.com/komaruworld/mocktail). It adds the native
|
||||
ARM64 VR bridge and Frame rendering, input, audio and Steam integration changes.
|
||||
The original Nightcap artwork is retained. Internal executable and configuration
|
||||
names keep `mocktail` for compatibility with the upstream source.
|
||||
|
||||
## License
|
||||
|
||||
[Apache License 2.0](LICENSE). Third-party components keep their own licenses.
|
||||
[Apache License 2.0](LICENSE). Original copyright notices and third-party
|
||||
licenses remain in their source directories. See [third-party credits](THIRD_PARTY.md).
|
||||
|
||||
## Support
|
||||
|
||||
You can support the project by giving it a star or with cryptocurrency:
|
||||
|
||||
- USDT (TON): `UQCi6Yzcc9cOctoij6n_r1K90-OdVxAT0D_xo2UzGKkQaJDY`
|
||||
- USDT (TRC20): `TNPMG9Vig2xiuo2r1QqnXRChPH7Vu28Jmx`
|
||||
Nightcap is an independent community project and is not affiliated with Roblox
|
||||
Corporation or Valve.
|
||||
@@ -0,0 +1,31 @@
|
||||
# Credits and third-party components
|
||||
|
||||
This edition includes modifications for Steam Frame ARM64 and is based on:
|
||||
|
||||
- [Nightcap](https://github.com/CoderDayton/nightcap), including vii's changes to
|
||||
Mocktail and the upstream `vr` branch.
|
||||
- [Mocktail](https://github.com/komaruworld/mocktail), by the Mocktail project
|
||||
authors.
|
||||
|
||||
The Steam Frame edition incorporates the upstream VR tree through
|
||||
[`753a3f81`](https://github.com/CoderDayton/nightcap/commit/753a3f8106e4d7fdafea457601a5ff238e77c2d0)
|
||||
(15 October 2026), including GPU-ordered presentation, asynchronous eye and
|
||||
mirror copies, cached JNI method dispatch, headset detection and the JNI haptic
|
||||
protocol. ARM64 client contracts and Frame gaze AA remain platform-specific.
|
||||
|
||||
The project is distributed under the [Apache License 2.0](LICENSE). Existing
|
||||
copyright and modification notices are retained in the source.
|
||||
|
||||
Third-party components retain their own licenses and notices, including the
|
||||
Android/Bionic linker sources under `third_party/mcpelauncher-linker`, the pinned
|
||||
OpenXR SDK, Vulkan headers and libjnivm submodules, SMAA under `src/vr/smaa`, AMD FidelityFX FSR 1 under `src/vr/fsr`
|
||||
([MIT license](src/vr/fsr/LICENSE.txt)),
|
||||
AMD FidelityFX SDK 1.1.4's FSR 3.1.4 upscaler, Optical Flow component and Vulkan
|
||||
backend under `third_party/fidelityfx`
|
||||
([MIT license](third_party/fidelityfx/LICENSE.txt)), and
|
||||
the OpenVR interface header under `tools/frame_video_settings`. The original
|
||||
Nightcap icon and artwork are retained from the upstream project.
|
||||
|
||||
Roblox client binaries and Valve SteamVR dashboard assets are not included.
|
||||
The client is downloaded separately and the Video settings tools read dashboard
|
||||
files from the user's installed SteamVR copy.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 620 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 MiB |
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
cmake -S /work -B /work/build-frame-arm64 -G Ninja -DCMAKE_BUILD_TYPE=Release -DMOCKTAIL_ENABLE_VR=ON -DMOCKTAIL_ENABLE_FSR3=OFF -DMOCKTAIL_VR_DIAGNOSTICS=OFF -DBUILD_TESTING=OFF -DMOCKTAIL_BUILD_FREEBSD_SOCKET_HELPER=OFF -DCMAKE_INSTALL_PREFIX=/opt/nightcap-vr-arm64
|
||||
cmake --build /work/build-frame-arm64 -j3
|
||||
cmake --install /work/build-frame-arm64
|
||||
c++ -std=c++17 -O2 /work/tools/frame_video_settings/nightcap-vr-settings.cc -ldl -o /opt/nightcap-vr-arm64/bin/nightcap-vr-settings
|
||||
printf 'NIGHTCAP_ARM64_BUILD_COMPLETE\n'
|
||||
@@ -24,6 +24,7 @@ set_target_properties(mocktail_audio_core PROPERTIES
|
||||
add_library(Mocktail::AudioCore ALIAS mocktail_audio_core)
|
||||
|
||||
add_library(mocktail_audio_sdl SHARED
|
||||
${MOCKTAIL_AUDIO_ROOT}/src/audio/sdl_audio_capture.cc
|
||||
${MOCKTAIL_AUDIO_ROOT}/src/audio/sdl_audio_sink.cc
|
||||
)
|
||||
target_include_directories(mocktail_audio_sdl PUBLIC
|
||||
@@ -62,6 +63,7 @@ add_library(Mocktail::FmodJavaAudioRuntime ALIAS
|
||||
|
||||
add_library(mocktail_fmod_jni_audio_bridge STATIC
|
||||
${MOCKTAIL_AUDIO_ROOT}/src/audio/fmod_jni_audio_bridge.cc
|
||||
${MOCKTAIL_AUDIO_ROOT}/src/audio/webrtc_jni_audio_bridge.cc
|
||||
)
|
||||
target_include_directories(mocktail_fmod_jni_audio_bridge PUBLIC
|
||||
${MOCKTAIL_AUDIO_ROOT}/include
|
||||
@@ -199,10 +201,4 @@ if(BUILD_TESTING AND TARGET GTest::gtest_main)
|
||||
gtest_discover_tests(fmod_jni_audio_bridge_test
|
||||
PROPERTIES ENVIRONMENT "SDL_AUDIODRIVER=dummy"
|
||||
)
|
||||
|
||||
add_test(NAME audio_readiness_gate_test
|
||||
COMMAND bash
|
||||
${MOCKTAIL_AUDIO_ROOT}/tests/audio_readiness_gate_test.sh
|
||||
${MOCKTAIL_AUDIO_ROOT}/scripts/audio_readiness_gate.sh
|
||||
)
|
||||
endif()
|
||||
@@ -16,8 +16,10 @@ add_library(mocktail_compat STATIC
|
||||
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_dns_runtime.cc
|
||||
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_host_libc_runtime.cc
|
||||
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_large_file_runtime.cc
|
||||
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_prctl_runtime.cc
|
||||
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_rwlock_runtime.cc
|
||||
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_signal_runtime.cc
|
||||
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_socket_runtime.cc
|
||||
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_semaphore_runtime.cc
|
||||
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_pthread_create_runtime.cc
|
||||
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_pthread_key_runtime.cc
|
||||
@@ -34,12 +36,15 @@ add_library(Mocktail::Compat ALIAS mocktail_compat)
|
||||
target_include_directories(mocktail_compat PUBLIC
|
||||
${MOCKTAIL_ELF_COMPAT_ROOT}/include
|
||||
)
|
||||
target_compile_definitions(mocktail_compat PRIVATE
|
||||
"MOCKTAIL_INSTALL_LIBDIR=\"${CMAKE_INSTALL_LIBDIR}\""
|
||||
)
|
||||
target_link_libraries(mocktail_compat PUBLIC
|
||||
PkgConfig::LIBELF
|
||||
nlohmann_json::nlohmann_json
|
||||
Threads::Threads
|
||||
)
|
||||
target_link_libraries(mocktail_compat PRIVATE Mocktail::CryptoFoundation)
|
||||
target_link_libraries(mocktail_compat PRIVATE OpenSSL::Crypto)
|
||||
target_compile_features(mocktail_compat PUBLIC cxx_std_17)
|
||||
|
||||
if(COMMAND mocktail_apply_compile_options)
|
||||
|
||||
@@ -13,13 +13,16 @@ get_filename_component(MOCKTAIL_INPUT_ROOT "${CMAKE_CURRENT_LIST_DIR}/.."
|
||||
)
|
||||
|
||||
add_library(mocktail_input_runtime STATIC
|
||||
${MOCKTAIL_INPUT_ROOT}/src/runtime/roblox_gamepad_input.cc
|
||||
${MOCKTAIL_INPUT_ROOT}/src/runtime/roblox_input_native_adapter.cc
|
||||
${MOCKTAIL_INPUT_ROOT}/src/runtime/roblox_input_router.cc
|
||||
${MOCKTAIL_INPUT_ROOT}/src/runtime/roblox_native_text_box_info_reader.cc
|
||||
${MOCKTAIL_INPUT_ROOT}/src/runtime/roblox_text_display_state.cc
|
||||
${MOCKTAIL_INPUT_ROOT}/src/runtime/roblox_text_editor.cc
|
||||
${MOCKTAIL_INPUT_ROOT}/src/runtime/roblox_text_font_resolver.cc
|
||||
${MOCKTAIL_INPUT_ROOT}/src/runtime/roblox_text_surface_overlay.cc
|
||||
${MOCKTAIL_INPUT_ROOT}/src/runtime/roblox_window_input_runtime.cc
|
||||
${MOCKTAIL_INPUT_ROOT}/src/runtime/roblox_xr_controller_input.cc
|
||||
)
|
||||
add_library(Mocktail::InputRuntime ALIAS mocktail_input_runtime)
|
||||
target_include_directories(mocktail_input_runtime PUBLIC
|
||||
@@ -31,18 +34,23 @@ target_include_directories(mocktail_input_runtime PRIVATE
|
||||
target_link_libraries(mocktail_input_runtime PUBLIC
|
||||
Mocktail::PlatformSdl
|
||||
Mocktail::Runtime
|
||||
Mocktail::VR
|
||||
mocktail_window
|
||||
PkgConfig::UTF8PROC
|
||||
PRIVATE
|
||||
PkgConfig::FONTCONFIG
|
||||
SDL3_ttf::SDL3_ttf
|
||||
nlohmann_json::nlohmann_json
|
||||
${CMAKE_DL_LIBS}
|
||||
)
|
||||
target_compile_features(mocktail_input_runtime PUBLIC cxx_std_17)
|
||||
mocktail_apply_compile_options(mocktail_input_runtime)
|
||||
|
||||
if(BUILD_TESTING AND TARGET GTest::gtest_main)
|
||||
add_executable(roblox_input_router_test
|
||||
${MOCKTAIL_INPUT_ROOT}/tests/roblox_gamepad_input_test.cc
|
||||
${MOCKTAIL_INPUT_ROOT}/tests/roblox_input_router_test.cc
|
||||
${MOCKTAIL_INPUT_ROOT}/tests/roblox_xr_controller_input_test.cc
|
||||
)
|
||||
target_link_libraries(roblox_input_router_test PRIVATE
|
||||
Mocktail::InputRuntime
|
||||
@@ -68,6 +76,15 @@ if(BUILD_TESTING AND TARGET GTest::gtest_main)
|
||||
)
|
||||
mocktail_apply_compile_options(roblox_text_display_state_test)
|
||||
|
||||
add_executable(roblox_text_font_resolver_test
|
||||
${MOCKTAIL_INPUT_ROOT}/tests/roblox_text_font_resolver_test.cc
|
||||
)
|
||||
target_link_libraries(roblox_text_font_resolver_test PRIVATE
|
||||
Mocktail::InputRuntime
|
||||
GTest::gtest_main
|
||||
)
|
||||
mocktail_apply_compile_options(roblox_text_font_resolver_test)
|
||||
|
||||
add_executable(roblox_input_native_adapter_test
|
||||
${MOCKTAIL_INPUT_ROOT}/tests/roblox_input_native_adapter_test.cc
|
||||
)
|
||||
@@ -92,9 +109,7 @@ if(BUILD_TESTING AND TARGET GTest::gtest_main)
|
||||
gtest_discover_tests(roblox_input_router_test)
|
||||
gtest_discover_tests(roblox_text_editor_test)
|
||||
gtest_discover_tests(roblox_text_display_state_test)
|
||||
gtest_discover_tests(roblox_text_font_resolver_test)
|
||||
gtest_discover_tests(roblox_input_native_adapter_test)
|
||||
gtest_discover_tests(roblox_native_text_box_info_reader_test)
|
||||
add_test(NAME input_readiness_gate_synthetic
|
||||
COMMAND bash ${MOCKTAIL_INPUT_ROOT}/tests/input_readiness_gate_test.sh
|
||||
)
|
||||
endif()
|
||||
@@ -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.
|
||||
|
||||
@@ -9,9 +10,10 @@ get_filename_component(MOCKTAIL_PLATFORM_GRAPHICS_ROOT
|
||||
|
||||
find_package(SDL3 3.4 REQUIRED CONFIG)
|
||||
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}"
|
||||
@@ -69,6 +71,7 @@ add_library(mocktail_platform_sdl STATIC
|
||||
${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/src/platform/sdl_application_metadata.cc
|
||||
${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/src/platform/sdl_display_refresh_capabilities.cc
|
||||
${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/src/platform/sdl_event_converter.cc
|
||||
${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/src/platform/sdl_gamepad_manager.cc
|
||||
${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/src/platform/sdl_platform_runtime.cc
|
||||
${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/src/platform/sdl_text_clipboard.cc
|
||||
${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/src/platform/sdl_window_icon.cc
|
||||
@@ -102,6 +105,18 @@ target_link_libraries(mocktail_graphics_foundation PRIVATE
|
||||
target_compile_features(mocktail_graphics_foundation PUBLIC cxx_std_17)
|
||||
add_library(Mocktail::GraphicsFoundation ALIAS mocktail_graphics_foundation)
|
||||
|
||||
add_library(mocktail_gles_text_overlay STATIC
|
||||
${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/src/graphics/gles_text_overlay_compositor.cc
|
||||
)
|
||||
target_include_directories(mocktail_gles_text_overlay
|
||||
PUBLIC ${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/include
|
||||
PRIVATE ${MOCKTAIL_GLES3_INCLUDE_DIR}
|
||||
)
|
||||
target_link_libraries(mocktail_gles_text_overlay PUBLIC SDL3::SDL3)
|
||||
target_compile_features(mocktail_gles_text_overlay PUBLIC cxx_std_17)
|
||||
mocktail_apply_compile_options(mocktail_gles_text_overlay)
|
||||
add_library(Mocktail::GlesTextOverlay ALIAS mocktail_gles_text_overlay)
|
||||
|
||||
add_library(mocktail_sdl_vulkan_wsi STATIC
|
||||
${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/src/graphics/sdl_vulkan_wsi.cc
|
||||
${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/src/graphics/android_vulkan_wsi_adapter.cc
|
||||
@@ -119,6 +134,16 @@ target_compile_features(mocktail_sdl_vulkan_wsi PUBLIC cxx_std_17)
|
||||
add_library(Mocktail::SdlVulkanWsi ALIAS mocktail_sdl_vulkan_wsi)
|
||||
|
||||
if(BUILD_TESTING AND TARGET GTest::gtest_main)
|
||||
add_executable(gles_text_overlay_compositor_test
|
||||
${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/tests/gles_text_overlay_compositor_test.cc
|
||||
)
|
||||
target_include_directories(gles_text_overlay_compositor_test PRIVATE
|
||||
${MOCKTAIL_GLES3_INCLUDE_DIR}
|
||||
)
|
||||
target_link_libraries(gles_text_overlay_compositor_test PRIVATE
|
||||
Mocktail::GlesTextOverlay
|
||||
GTest::gtest_main
|
||||
)
|
||||
add_executable(bionic_egl_bridge_test
|
||||
${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/tests/bionic_egl_bridge_test.cc
|
||||
)
|
||||
@@ -152,6 +177,7 @@ if(BUILD_TESTING AND TARGET GTest::gtest_main)
|
||||
GTest::gtest_main
|
||||
)
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(gles_text_overlay_compositor_test)
|
||||
gtest_discover_tests(bionic_egl_bridge_test)
|
||||
gtest_discover_tests(platform_graphics_foundation_test)
|
||||
gtest_discover_tests(display_refresh_capabilities_test)
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
# Copyright 2026 Mocktail Project Authors
|
||||
# Licensed under the Apache License, Version 2.0.
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
option(MOCKTAIL_ENABLE_FSR3 "Build the optional temporal upscaler SDK" OFF)
|
||||
|
||||
# AUTO builds OpenXR support when the third_party/OpenXR-SDK submodule is
|
||||
# checked out and skips it otherwise, so source packages that do not fetch
|
||||
# submodules still build. ON requires the submodule; OFF never builds it.
|
||||
set(MOCKTAIL_ENABLE_VR "AUTO" CACHE STRING
|
||||
"Build experimental OpenXR support: AUTO, ON or OFF")
|
||||
set_property(CACHE MOCKTAIL_ENABLE_VR PROPERTY STRINGS AUTO ON OFF)
|
||||
set(_mocktail_openxr_sdk "${CMAKE_SOURCE_DIR}/third_party/OpenXR-SDK/CMakeLists.txt")
|
||||
string(TOUPPER "${MOCKTAIL_ENABLE_VR}" _mocktail_vr_request)
|
||||
if(_mocktail_vr_request STREQUAL "AUTO")
|
||||
if(EXISTS "${_mocktail_openxr_sdk}" AND CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
set(MOCKTAIL_ENABLE_VR ON)
|
||||
else()
|
||||
message(STATUS "OpenXR SDK submodule not found; building without VR "
|
||||
"(git submodule update --init third_party/OpenXR-SDK to enable it)")
|
||||
set(MOCKTAIL_ENABLE_VR OFF)
|
||||
endif()
|
||||
elseif(MOCKTAIL_ENABLE_VR)
|
||||
set(MOCKTAIL_ENABLE_VR ON)
|
||||
else()
|
||||
set(MOCKTAIL_ENABLE_VR OFF)
|
||||
endif()
|
||||
|
||||
add_library(mocktail_vr STATIC
|
||||
src/vr/gaze_aa.cc
|
||||
src/vr/fsr_upscaler.cc
|
||||
src/vr/fsr3_scene_capture.cc
|
||||
src/vr/diagnostics.cc
|
||||
src/vr/headset_detection.cc
|
||||
src/vr/openxr_backend_mode.cc
|
||||
src/vr/vr_perf.cc
|
||||
src/vr/vr_mirror.cc
|
||||
src/vr/xr_controller.cc
|
||||
src/vr/roblox_vr_device_bridge.cc
|
||||
src/vr/virtual_keyboard.cc)
|
||||
add_library(Mocktail::VR ALIAS mocktail_vr)
|
||||
target_include_directories(mocktail_vr PUBLIC "${CMAKE_SOURCE_DIR}/include")
|
||||
target_include_directories(mocktail_vr PRIVATE "${CMAKE_SOURCE_DIR}/src")
|
||||
target_link_libraries(mocktail_vr PUBLIC Vulkan::Headers PRIVATE nlohmann_json::nlohmann_json)
|
||||
# shm_open for the desktop eye mirror lives in librt on glibc.
|
||||
find_library(MOCKTAIL_RT_LIBRARY rt)
|
||||
if(MOCKTAIL_RT_LIBRARY)
|
||||
target_link_libraries(mocktail_vr PUBLIC ${MOCKTAIL_RT_LIBRARY})
|
||||
endif()
|
||||
mocktail_apply_compile_options(mocktail_vr)
|
||||
|
||||
if(MOCKTAIL_ENABLE_VR)
|
||||
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
message(FATAL_ERROR "Mocktail OpenXR support currently targets Linux")
|
||||
endif()
|
||||
if(NOT EXISTS "${_mocktail_openxr_sdk}")
|
||||
message(FATAL_ERROR
|
||||
"Initialize OpenXR: git submodule update --init third_party/OpenXR-SDK")
|
||||
endif()
|
||||
|
||||
# Scope upstream's generic option names to this dependency. Static linking
|
||||
# packages the pinned loader without requiring a matching system loader.
|
||||
function(mocktail_add_openxr_sdk)
|
||||
set(BUILD_LOADER ON)
|
||||
set(DYNAMIC_LOADER OFF)
|
||||
set(BUILD_WITH_SYSTEM_JSONCPP OFF)
|
||||
set(BUILD_TESTS OFF)
|
||||
set(BUILD_API_LAYERS OFF)
|
||||
set(BUILD_CONFORMANCE_TESTS OFF)
|
||||
set(BUILD_SDK_TESTS OFF)
|
||||
set(BUILD_FORCE_GENERATION OFF)
|
||||
add_subdirectory("${CMAKE_SOURCE_DIR}/third_party/OpenXR-SDK"
|
||||
"${CMAKE_BINARY_DIR}/third_party/OpenXR-SDK" EXCLUDE_FROM_ALL)
|
||||
endfunction()
|
||||
mocktail_add_openxr_sdk()
|
||||
if(MOCKTAIL_ENABLE_FSR3)
|
||||
add_subdirectory(third_party/fidelityfx)
|
||||
target_sources(mocktail_vr PRIVATE src/vr/fsr3_upscaler.cc src/vr/fsr3_gaze_transport.cc)
|
||||
target_link_libraries(mocktail_vr PRIVATE nightcap_fsr3)
|
||||
target_compile_definitions(mocktail_vr PUBLIC MOCKTAIL_ENABLE_FSR3=1)
|
||||
endif()
|
||||
target_sources(mocktail_vr PRIVATE src/vr/openxr_probe.cc src/vr/openxr_preview.cc
|
||||
src/vr/openxr_backend.cc src/vr/xr_foveation_maps.cc src/vr/gles_transport.cc src/vr/xr_actions.cc
|
||||
src/vr/virtual_keyboard_raster.cc)
|
||||
# The in-headset keyboard draws its labels with the same SDL3_ttf and
|
||||
# fontconfig stack as the TextBox overlay.
|
||||
find_package(SDL3 3.4 REQUIRED CONFIG)
|
||||
find_package(SDL3_ttf REQUIRED CONFIG)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(FONTCONFIG REQUIRED IMPORTED_TARGET fontconfig)
|
||||
target_link_libraries(mocktail_vr PRIVATE SDL3_ttf::SDL3_ttf SDL3::SDL3
|
||||
PkgConfig::FONTCONFIG)
|
||||
# Error unwinding is confined to these native session implementations; their
|
||||
# public entry points catch errors before returning to the guest runtime.
|
||||
set_source_files_properties(src/vr/openxr_preview.cc src/vr/openxr_backend.cc
|
||||
src/vr/xr_actions.cc src/vr/xr_foveation_maps.cc
|
||||
PROPERTIES COMPILE_OPTIONS -fexceptions)
|
||||
target_compile_definitions(mocktail_vr PRIVATE XR_USE_GRAPHICS_API_VULKAN)
|
||||
option(MOCKTAIL_VR_DIAGNOSTICS
|
||||
"Compile VR diagnostics: scripted head pose, eye evidence dumps" OFF)
|
||||
if(MOCKTAIL_VR_DIAGNOSTICS)
|
||||
target_compile_definitions(mocktail_vr PRIVATE MOCKTAIL_VR_DIAGNOSTICS=1)
|
||||
endif()
|
||||
set_source_files_properties(src/vr/openxr_backend.cc PROPERTIES COMPILE_DEFINITIONS "XR_USE_GRAPHICS_API_OPENGL_ES;XR_USE_PLATFORM_EGL")
|
||||
target_include_directories(mocktail_vr PRIVATE ${MOCKTAIL_EGL_INCLUDE_DIR} ${MOCKTAIL_GLES3_INCLUDE_DIR})
|
||||
target_link_libraries(mocktail_vr PRIVATE OpenXR::openxr_loader Vulkan::Headers ${CMAKE_DL_LIBS})
|
||||
else()
|
||||
target_sources(mocktail_vr PRIVATE src/vr/openxr_disabled.cc
|
||||
src/vr/xr_actions_disabled.cc src/vr/xr_foveation_maps_disabled.cc)
|
||||
endif()
|
||||
|
||||
if(MOCKTAIL_ENABLE_VR)
|
||||
add_executable(mocktail_vr_probe src/vr/probe_main.cc)
|
||||
set_target_properties(mocktail_vr_probe PROPERTIES
|
||||
OUTPUT_NAME mocktail-vr-probe)
|
||||
target_link_libraries(mocktail_vr_probe PRIVATE Mocktail::VR)
|
||||
mocktail_apply_compile_options(mocktail_vr_probe)
|
||||
install(TARGETS mocktail_vr_probe
|
||||
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||
endif()
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_PLATFORM_SDL_WINDOW_ICON_DATA_H_
|
||||
#define MOCKTAIL_PLATFORM_SDL_WINDOW_ICON_DATA_H_
|
||||
|
||||
|
||||
+101
-27
@@ -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.
|
||||
|
||||
@@ -27,21 +28,54 @@ device: pc-windows-11
|
||||
runtime:
|
||||
# Boolean (default: false): start without a visible SDL window.
|
||||
headless: false
|
||||
# String (optional): use a specific x86_64 libroblox.so instead of the
|
||||
# String (optional): use a specific native-architecture libroblox.so instead of the
|
||||
# active verified payload.
|
||||
# roblox_library: /absolute/path/to/libroblox.so
|
||||
|
||||
appearance:
|
||||
# String (default: roblox): use Roblox's saved account theme. Supported
|
||||
# overrides: dark, light, or system (follow the desktop color scheme).
|
||||
theme: roblox
|
||||
|
||||
graphics:
|
||||
# String (default: direct-vulkan): graphics backend. Use opengl on hardware
|
||||
# without a working Vulkan driver; OpenGL ES 3.0 or newer is required.
|
||||
# Recommended values: direct-vulkan, opengl, system, angle-vulkan.
|
||||
backend: direct-vulkan
|
||||
# String or integer (default: display): requested frame-rate policy.
|
||||
# Supported values: display, unlimited, 30, 60, 120, 144, 240. Unlimited
|
||||
# selects Roblox's unmodified 240-FPS scheduler maximum.
|
||||
frame_rate_limit: display
|
||||
# String (default: auto): presentation synchronization: auto, on, or off.
|
||||
vsync: auto
|
||||
# Optional string or integer: Mocktail frame-rate override. Leave commented
|
||||
# (or use -1) to let Roblox Basic Settings own the cap. Supported overrides:
|
||||
# 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. auto
|
||||
# presents through the lowest-latency synchronized mode the driver offers
|
||||
# unless frame_rate_limit is unlimited.
|
||||
# vsync: off
|
||||
|
||||
# vr:
|
||||
# String (default: off): experimental VR through OpenXR on supported Roblox
|
||||
# builds (ARM64: 2998 and 3222). off starts on the desktop; on starts in
|
||||
# VR like --vr. auto starts in VR when SteamVR is already running with a connected
|
||||
# headset (ALVR included), and on the desktop otherwise. auto never starts
|
||||
# SteamVR. --vr and --no-vr override this for one launch. The older
|
||||
# enabled: true/false still works as on/off; set one or the other.
|
||||
# Standalone OpenXR test scene: mocktail-vr-probe --scene.
|
||||
# mode: off
|
||||
# Boolean (default: true): also show the left-eye VR view in the desktop
|
||||
# window. Restart Nightcap after changing this setting. Steam Frame
|
||||
# uses its Video settings Desktop Mirror control, off by default.
|
||||
# desktop_mirror: true
|
||||
# String (default: fxaa): smooth jagged edges in the headset. Roblox draws
|
||||
# the VR eyes without anti-aliasing; fxaa filters each eye on its way to the
|
||||
# headset. The Steam Frame profile uses its separate gaze AA control.
|
||||
# off sends the eyes as rendered.
|
||||
# anti_aliasing: fxaa
|
||||
# Optional string or integer: frame-rate limit used instead of
|
||||
# graphics.frame_rate_limit while VR is active. Same values: -1 (Roblox's
|
||||
# own cap, usually 60), display, unlimited, or any positive integer. The
|
||||
# default, unlimited, lets the OpenXR runtime pace frames at the headset's
|
||||
# refresh rate.
|
||||
# frame_rate_limit: unlimited
|
||||
|
||||
performance:
|
||||
# Boolean (default: false): size Roblox scheduler/render queues from every
|
||||
@@ -63,12 +97,34 @@ audio:
|
||||
# String (default: default): `default` follows the host system output.
|
||||
# To pin output, copy an exact SDL device name printed during startup.
|
||||
output_device: default
|
||||
# String (default: default): input device name, `id:<number>` printed during
|
||||
# startup, or `disabled`. IDs disambiguate duplicate names but may change
|
||||
# between boots; prefer the exact device name when it is unique.
|
||||
input_device: default
|
||||
|
||||
input:
|
||||
# Boolean (default: false): send mouse look deltas in the host's own units.
|
||||
# They are otherwise scaled by the window's pixel density divided by the
|
||||
# desktop display scale, which cancels to 1.0 unless fractional scaling makes
|
||||
# the two disagree. Turn this on when first-person aiming feels too slow or
|
||||
# too fast under fractional scaling. The pointer position is unaffected.
|
||||
raw_mouse: false
|
||||
|
||||
integrations:
|
||||
discord_rpc:
|
||||
# Boolean (default: false): publish Mocktail activity to Discord Desktop.
|
||||
# This never signs in to Discord and never reads an account token.
|
||||
fleasion:
|
||||
# Boolean (default: false): trust Fleasion's CA without editing Roblox files.
|
||||
enabled: false
|
||||
# String: match Fleasion's routing mode: env or hosts.
|
||||
proxy_mode: env
|
||||
# Integer: Fleasion's local HTTP proxy port in env mode.
|
||||
proxy_port: 58443
|
||||
# Optional absolute public CA path. Default: $XDG_CONFIG_HOME/Fleasion/proxy_ca/ca.crt
|
||||
# or ~/.config/Fleasion/proxy_ca/ca.crt. Never select ca.key.
|
||||
# ca_certificate: /home/user/.config/Fleasion/proxy_ca/ca.crt
|
||||
discord_rpc:
|
||||
# Boolean (default: true): publish Nightcap activity to Discord Desktop.
|
||||
# This never signs in to Discord and never reads an account token.
|
||||
enabled: true
|
||||
# Boolean (default: true): show the current Roblox experience name.
|
||||
show_place_name: true
|
||||
# Boolean (default: true): show how long the current session has run.
|
||||
@@ -81,30 +137,51 @@ integrations:
|
||||
public_servers_only: true
|
||||
# button_label: Join Server
|
||||
# English defaults. Uncomment only the lines you want to customize.
|
||||
# Text and image fields accept {place_name}, {place_icon}, and
|
||||
# {creator_name}. A field that expands an empty placeholder is left out,
|
||||
# and a "" title shows the app name.
|
||||
# title, state, and the images fields may be "" to hide them.
|
||||
# text:
|
||||
# browsing: Browsing experiences
|
||||
# joining: Joining an experience
|
||||
# playing: "{place_name}"
|
||||
# state: Playing Roblox
|
||||
# playing: "Playing {place_name}"
|
||||
# state: "by {creator_name}"
|
||||
# unknown_place: Unknown experience
|
||||
# title: Roblox
|
||||
# Images take an https:// URL, or an asset key uploaded to application_id.
|
||||
# large: "" hides both images; the small image needs a large one.
|
||||
# images:
|
||||
# large: "{place_icon}"
|
||||
# large_text: "{place_name}"
|
||||
# small: roblox_small
|
||||
# small_text: Roblox
|
||||
# Advanced: overrides the application bundled by the package maintainer.
|
||||
# application_id: 123456789012345678
|
||||
|
||||
window:
|
||||
# Integer (default: 1280): initial window width in pixels.
|
||||
# Integer (default: 1280): initial window width in logical desktop units.
|
||||
width: 1280
|
||||
# Integer (default: 720): initial window height in pixels.
|
||||
# Integer (default: 720): initial window height in logical desktop units.
|
||||
height: 720
|
||||
# String (default: Roblox): window title.
|
||||
title: Roblox
|
||||
# Boolean (default: false): render at physical display-pixel density instead
|
||||
# of the logical desktop resolution. Enable only for sharper high-DPI output.
|
||||
high_dpi: false
|
||||
|
||||
# Optional host HTTP proxy. Uncomment both values together. The host must not
|
||||
# contain a scheme such as http://.
|
||||
# network:
|
||||
# # String (optional, default: disabled): proxy host name or IP address.
|
||||
# proxy_host: 127.0.0.1
|
||||
# # Integer (optional, default: disabled): proxy TCP port from 1 to 65535.
|
||||
# proxy_port: 8080
|
||||
network:
|
||||
# Boolean (default: false): follow the host HTTP/SOCKS5 proxy and
|
||||
# automatically use its current port. Do not combine with a fixed proxy.
|
||||
use_system_proxy: false
|
||||
# Optional fixed HTTP proxy. Uncomment both values together. The host must
|
||||
# not contain a scheme such as http://.
|
||||
# String (optional, default: disabled): proxy host name or IP address.
|
||||
# proxy_host: 127.0.0.1
|
||||
# Integer (optional, default: disabled): proxy TCP port from 1 to 65535.
|
||||
# proxy_port: 8080
|
||||
# Absolute path (optional): custom PEM CA bundle. Mocktail reads this file
|
||||
# directly and does not replace it during Roblox payload updates.
|
||||
# ca_bundle: /home/user/.config/mocktail/cacert.pem
|
||||
|
||||
updates:
|
||||
# Boolean (default: true): check the latest Roblox version, derive an exact
|
||||
@@ -112,12 +189,9 @@ updates:
|
||||
# backend, and promote only on success. An existing current payload is
|
||||
# preserved when probation fails.
|
||||
automatic: true
|
||||
# Boolean (default: true): testing track. Always try provider latest, even
|
||||
# when it has no verified payload profile, and do not download or activate
|
||||
# an older supported payload in its place. Exact HostAbi derivation and two
|
||||
# canaries are still required before latest can become current.
|
||||
testing_latest_only: true
|
||||
# String (default: apk-pure): direct x86_64 APK provider.
|
||||
# String (default: apk-pure): direct x86_64 APK provider selection. Both
|
||||
# accepted values resolve the provider chain, which currently starts with
|
||||
# APKPure. Supported values: auto, apk-pure.
|
||||
source: apk-pure
|
||||
# Reserved for desktop update integrations. `mocktail_updater` itself never
|
||||
# launches another process after changing the active payload.
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
{
|
||||
"version_name": "2.721.1108",
|
||||
"version_code": 2350,
|
||||
"abi": "x86_64",
|
||||
"elf_build_id": "50e1b0abd123350e794226062fe3a1ef360c5f0d",
|
||||
"status": "legacy-researched",
|
||||
"default_allowed": false,
|
||||
@@ -15,6 +16,7 @@
|
||||
{
|
||||
"version_name": "2.725.1142",
|
||||
"version_code": 2546,
|
||||
"abi": "x86_64",
|
||||
"elf_build_id": "d0cb1fa0deb3d9161b4cd77530cbcd2e50de3a21",
|
||||
"status": "supported",
|
||||
"default_allowed": true,
|
||||
@@ -35,6 +37,7 @@
|
||||
{
|
||||
"version_name": "2.727.1199",
|
||||
"version_code": 2628,
|
||||
"abi": "x86_64",
|
||||
"elf_build_id": "1686400865ae0e408cd7bd67de7a439625c6fd13",
|
||||
"status": "supported",
|
||||
"default_allowed": true,
|
||||
@@ -42,6 +45,150 @@
|
||||
"allow_host_abi_bridges": true,
|
||||
"allow_host_constructor_replay": true,
|
||||
"reason": "Verified 2026-07-16 on the current host: independently signature-matched native allocator and pre-JNI state, 3479 constructors replayed without recovery, JNI_OnLoad and ASMA/V2 startup, 2988 Vulkan shaders, a real vkQueuePresentKHR, and explicit lifecycle teardown to Stopped; legacy executable patches and signal recovery remain disabled."
|
||||
},
|
||||
{
|
||||
"version_name": "2.734.917",
|
||||
"version_code": 2908,
|
||||
"abi": "x86_64",
|
||||
"elf_build_id": "63c5109637b7d7b2bdb8ed8f858023ff5ef49326",
|
||||
"status": "supported",
|
||||
"default_allowed": true,
|
||||
"allow_legacy_binary_patches": false,
|
||||
"allow_host_abi_bridges": true,
|
||||
"allow_host_constructor_replay": true,
|
||||
"user_game_settings_fullscreen_setter_rva": "0x44fee64",
|
||||
"fmod_output_device_bridge": {
|
||||
"vtable_rva": "0x6b7eee8",
|
||||
"string_constructor_rva": "0x1cfd5ec",
|
||||
"count_method_rva": "0x32736d2",
|
||||
"info_method_rva": "0x3273772",
|
||||
"current_method_rva": "0x3273722",
|
||||
"select_method_rva": "0x32734a6"
|
||||
},
|
||||
"reason": "Verified 2026-08-28 on the current host: the machine-derived exact-Build-ID profile passed two isolated no-recovery Tier C Vulkan canaries; fullscreen state synchronization and Build-ID-scoped FmodAudioDevice output interposition were re-derived for the current payload; legacy executable patches and signal recovery remain disabled."
|
||||
},
|
||||
{
|
||||
"version_name": "2.736.1408",
|
||||
"version_code": 2998,
|
||||
"abi": "arm64-v8a",
|
||||
"elf_build_id": "ed2b035768b1513561d0e6ea7f9bbec35b0e60e5",
|
||||
"status": "supported",
|
||||
"default_allowed": true,
|
||||
"allow_legacy_binary_patches": false,
|
||||
"allow_host_abi_bridges": true,
|
||||
"allow_host_constructor_replay": true,
|
||||
"reason": "AArch64 arm64-v8a payload of the same 2.736.1408 release as the verified x86-64 profile; allocator, constructor replay, and pre-JNI bootstrap RVAs were derived by cross-architecture semantic matching.",
|
||||
"fmod_output_device_bridge": {
|
||||
"vtable_rva": "0x632aeb0",
|
||||
"string_constructor_rva": "0x1d5a6b0",
|
||||
"count_method_rva": "0x2f96540",
|
||||
"info_method_rva": "0x2f96600",
|
||||
"current_method_rva": "0x2f965a0",
|
||||
"select_method_rva": "0x2f962d4",
|
||||
"input_count_method_rva": "0x2f968b4",
|
||||
"input_info_method_rva": "0x2f968c4",
|
||||
"input_current_method_rva": "0x2f9697c",
|
||||
"input_select_method_rva": "0x2f96088",
|
||||
"vtable_layout_version": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"version_name": "2.736.1408",
|
||||
"version_code": 2998,
|
||||
"abi": "x86_64",
|
||||
"elf_build_id": "ade08266c67aee88ec9c1d00902150e1684dad3a",
|
||||
"status": "supported",
|
||||
"default_allowed": true,
|
||||
"allow_legacy_binary_patches": false,
|
||||
"allow_host_abi_bridges": true,
|
||||
"allow_host_constructor_replay": true,
|
||||
"user_game_settings_fullscreen_setter_rva": "0x45ad8aa",
|
||||
"fmod_output_device_bridge": {
|
||||
"vtable_rva": "0x6c58040",
|
||||
"string_constructor_rva": "0x1d32df8",
|
||||
"count_method_rva": "0x32d0ee0",
|
||||
"info_method_rva": "0x32d0f80",
|
||||
"current_method_rva": "0x32d0f30",
|
||||
"select_method_rva": "0x32d0cb4",
|
||||
"input_count_method_rva": "0x32d12dc",
|
||||
"input_info_method_rva": "0x32d12ea",
|
||||
"input_current_method_rva": "0x32d13a6",
|
||||
"input_select_method_rva": "0x32d0a56"
|
||||
},
|
||||
"vr_debug_device_bridge": {
|
||||
"vtable_rva": "0x6c87b38",
|
||||
"constructor_rva": "0x3810614",
|
||||
"state_getter_rva": "0x38109f6",
|
||||
"eye_getter_rva": "0x38109e6",
|
||||
"eye_initializer_rva": "0x3810a18",
|
||||
"emulator_flag_storage_rva": "0x7369920",
|
||||
"device_create_framebuffer_vtable_offset": "0x180"
|
||||
},
|
||||
"reason": "Verified 2026-09-06: exact Build-ID profile passed two isolated no-recovery Tier C Vulkan canaries and real game sessions, including fullscreen state synchronization and host FMOD output-device interposition; legacy executable patches and signal recovery remain disabled. Experimental vr_debug_device_bridge metadata (DebugDeviceVR constructor/state/eye/initializer RVAs) is machine-validated at activation and gated behind --vr; native stereo output is not yet verified end-to-end."
|
||||
},
|
||||
{
|
||||
"allow_host_abi_bridges": true,
|
||||
"allow_host_constructor_replay": true,
|
||||
"allow_legacy_binary_patches": false,
|
||||
"default_allowed": true,
|
||||
"elf_build_id": "5f0704edd9064f566ee3d6df2bd2fabbcc709f03",
|
||||
"fmod_output_device_bridge": {
|
||||
"count_method_rva": "0x320c676",
|
||||
"current_method_rva": "0x320c6c6",
|
||||
"info_method_rva": "0x320c716",
|
||||
"select_method_rva": "0x320bf78",
|
||||
"string_constructor_rva": "0x1d7a038",
|
||||
"vtable_layout_version": 2,
|
||||
"vtable_rva": "0x6cd3ce0"
|
||||
},
|
||||
"reason": "Exact-Build-ID host ABI promoted from the validated probation profile; native stereo, VR state/pointer/haptic contracts and FMOD output-device layout v2 were re-derived for 3092. Experimental: runs when selected explicitly (--roblox-lib) but is never an update target, so non-VR users stay on the default payload.",
|
||||
"status": "experimental",
|
||||
"user_game_settings_fullscreen_setter_rva": "0x45c5f42",
|
||||
"version_code": 3092,
|
||||
"version_name": "2.738.1397",
|
||||
"vr_debug_device_bridge": {
|
||||
"vtable_rva": "0x6d08108",
|
||||
"constructor_rva": "0x37f9fe4",
|
||||
"state_getter_rva": "0x37fa3c6",
|
||||
"eye_getter_rva": "0x37fa3b6",
|
||||
"eye_initializer_rva": "0x37fa3e8",
|
||||
"emulator_flag_storage_rva": "0x73f4690",
|
||||
"device_create_framebuffer_vtable_offset": "0x188"
|
||||
}
|
||||
},
|
||||
{
|
||||
"version_name": "2.742.1059",
|
||||
"version_code": 3222,
|
||||
"abi": "arm64-v8a",
|
||||
"elf_build_id": "25aa7fd31008a744c3c99f717e7d16d81d371d12",
|
||||
"status": "supported",
|
||||
"default_allowed": true,
|
||||
"allow_legacy_binary_patches": false,
|
||||
"allow_host_abi_bridges": true,
|
||||
"allow_host_constructor_replay": true,
|
||||
"reason": "Verified on Steam Frame 2026-10-15: signed ARM64 3222 payload, exact allocator/bootstrap and reordered constructor roles, VR state/pointer/reflection/framebuffer contracts and layout-2 FMOD bridge; native startup, two distinct eye bindings at 2592x2592, real OpenXR projection and controlled experience transitions. Worn-headset quality and remote voice audibility remain unconfirmed.",
|
||||
"fmod_output_device_bridge": {
|
||||
"vtable_rva": "0x6569770",
|
||||
"string_constructor_rva": "0x1e47180",
|
||||
"count_method_rva": "0x2c35714",
|
||||
"info_method_rva": "0x2c357d4",
|
||||
"current_method_rva": "0x2c35774",
|
||||
"select_method_rva": "0x2c34f18",
|
||||
"input_count_method_rva": "0x2c35d58",
|
||||
"input_info_method_rva": "0x2c35d68",
|
||||
"input_current_method_rva": "0x2c361a0",
|
||||
"input_select_method_rva": "0x2c34c80",
|
||||
"vtable_layout_version": 2
|
||||
},
|
||||
"vr_debug_device_bridge": {
|
||||
"vtable_rva": "0x67b6688",
|
||||
"constructor_rva": "0x4e5558c",
|
||||
"state_getter_rva": "0x4e55964",
|
||||
"eye_getter_rva": "0x4e55958",
|
||||
"eye_initializer_rva": "0x4e55974",
|
||||
"emulator_flag_storage_rva": "0x6c0d1b0",
|
||||
"device_create_framebuffer_vtable_offset": "0x188"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,64 +1,64 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"elf_build_id": "1686400865ae0e408cd7bd67de7a439625c6fd13",
|
||||
"payload_sha256": "3e9c26c81186f93458ff65d8a8bc240c220974db02b8388b91340b77b336997d",
|
||||
"payload_id": "2628-1686400865ae0e408cd7bd67de7a439625c6fd13",
|
||||
"payload_path": "payloads/2628-1686400865ae0e408cd7bd67de7a439625c6fd13",
|
||||
"elf_build_id": "ade08266c67aee88ec9c1d00902150e1684dad3a",
|
||||
"payload_sha256": "4343a6a900d1fca27ff8e10b9d9c86bee40a5d0ba547cb29903b7769f4f42a9d",
|
||||
"payload_id": "2998-ade08266c67aee88ec9c1d00902150e1684dad3a",
|
||||
"payload_path": "payloads/2998-ade08266c67aee88ec9c1d00902150e1684dad3a",
|
||||
"reference": {
|
||||
"elf_build_id": "d0cb1fa0deb3d9161b4cd77530cbcd2e50de3a21",
|
||||
"payload_sha256": "92ca0e4d8bc4e1c7a37fc2b52544869299ec8b9a40ea8da96ebac66660e0b1af"
|
||||
"elf_build_id": "63c5109637b7d7b2bdb8ed8f858023ff5ef49326",
|
||||
"payload_sha256": "72c3b1a5c151b5ac0bfc5394ce3a91775884fbd777589ccab8ac1c4f9885f739"
|
||||
},
|
||||
"profile": {
|
||||
"elf_build_id": "1686400865ae0e408cd7bd67de7a439625c6fd13",
|
||||
"elf_build_id": "ade08266c67aee88ec9c1d00902150e1684dad3a",
|
||||
"bridge_entries": [
|
||||
{
|
||||
"rva": "0x1c39ea2",
|
||||
"rva": "0x1d2c1e2",
|
||||
"kind": "allocate",
|
||||
"label": "small-allocate"
|
||||
},
|
||||
{
|
||||
"rva": "0x1fe4c84",
|
||||
"rva": "0x1d2f43a",
|
||||
"kind": "usable_size",
|
||||
"label": "usable-size"
|
||||
},
|
||||
{
|
||||
"rva": "0x203daae",
|
||||
"rva": "0x21e68bf",
|
||||
"kind": "reallocate",
|
||||
"label": "reallocate"
|
||||
},
|
||||
{
|
||||
"rva": "0x1c39c3a",
|
||||
"rva": "0x1d2bf82",
|
||||
"kind": "allocate",
|
||||
"label": "allocate"
|
||||
},
|
||||
{
|
||||
"rva": "0x1c8dbf8",
|
||||
"rva": "0x1d47a63",
|
||||
"kind": "aligned_allocate",
|
||||
"label": "aligned-allocate-direct"
|
||||
},
|
||||
{
|
||||
"rva": "0x1c3eb72",
|
||||
"rva": "0x1d2f9d9",
|
||||
"kind": "free",
|
||||
"label": "free"
|
||||
}
|
||||
],
|
||||
"data_seeds": {
|
||||
"allocator_object_slot": "0x7030ae0",
|
||||
"empty_string_slot": "0x703a7e8",
|
||||
"jni_singleton_slot": "0x72a16d0",
|
||||
"allocator_object_slot": "0x7652e70",
|
||||
"empty_string_slot": "0x775fd28",
|
||||
"jni_singleton_slot": "0x7bb4340",
|
||||
"jni_singleton_bytes": 1024,
|
||||
"arena_initializer": "0x1c39f1d",
|
||||
"allocator_thread_initializer": "0x2040817",
|
||||
"arena_guard_slot": "0x71e92f0",
|
||||
"arena_table_slot": "0x71e9380",
|
||||
"arena_initializer": "0x1d2c5f8",
|
||||
"allocator_thread_initializer": "0x1db7b57",
|
||||
"arena_guard_slot": "0x7af0430",
|
||||
"arena_table_slot": "0x7af04c0",
|
||||
"arena_table_slot_count": 4194304
|
||||
},
|
||||
"native_allocator": {
|
||||
"allocate": "0x1c39c3a",
|
||||
"deallocate": "0x1c3eb72"
|
||||
"allocate": "0x1d2bf82",
|
||||
"deallocate": "0x1d2f9d9"
|
||||
},
|
||||
"init_array_offset": "0x6c2c9b0",
|
||||
"init_array_count": 3481,
|
||||
"init_array_offset": "0x7057818",
|
||||
"init_array_count": 3570,
|
||||
"constructor_run_ranges": [
|
||||
{
|
||||
"begin": 2,
|
||||
@@ -66,32 +66,32 @@
|
||||
},
|
||||
{
|
||||
"begin": 5,
|
||||
"end_exclusive": 3481
|
||||
"end_exclusive": 3570
|
||||
}
|
||||
],
|
||||
"native_mimalloc_constructor_run_ranges": [
|
||||
{
|
||||
"begin": 2,
|
||||
"end_exclusive": 3481
|
||||
"end_exclusive": 3570
|
||||
}
|
||||
],
|
||||
"native_mimalloc_thread_initializer_after_constructor": 2,
|
||||
"native_pre_jni_bootstrap": {
|
||||
"registry_initializer": "0x1fe72e5",
|
||||
"registry_slot": "0x71ed428"
|
||||
"registry_initializer": "0x21e8e09",
|
||||
"registry_slot": "0x7af4598"
|
||||
},
|
||||
"default_allocator_strategy": "native_mimalloc"
|
||||
},
|
||||
"derivation_anchors": {
|
||||
"signature_version": 1,
|
||||
"allocator_object_initializer_rva": "0x1fbf1de",
|
||||
"empty_string_initializer_rva": "0x1fc82b0",
|
||||
"jni_singleton_initializer_rva": "0x1c39a34",
|
||||
"allocator_object_initializer_rva": "0x21bde7d",
|
||||
"empty_string_initializer_rva": "0x21c9a86",
|
||||
"jni_singleton_initializer_rva": "0x1d2aec0",
|
||||
"constructor_rvas": {
|
||||
"2": "0x1c3c764",
|
||||
"3": "0x1c38c00",
|
||||
"4": "0x1c39884",
|
||||
"5": "0x1c39a34"
|
||||
"2": "0x1d2f731",
|
||||
"3": "0x1d2af84",
|
||||
"4": "0x1d2bc08",
|
||||
"5": "0x1d2aec0"
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-11
@@ -1,23 +1,24 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"package": "com.roblox.client",
|
||||
"version_name": "2.725.1142",
|
||||
"version_code": 2546,
|
||||
"version_name": "2.736.1408",
|
||||
"version_code": 2998,
|
||||
"abi": "x86_64",
|
||||
"elf_build_id": "d0cb1fa0deb3d9161b4cd77530cbcd2e50de3a21",
|
||||
"elf_build_id": "ade08266c67aee88ec9c1d00902150e1684dad3a",
|
||||
"sha256": {
|
||||
"libroblox": "92ca0e4d8bc4e1c7a37fc2b52544869299ec8b9a40ea8da96ebac66660e0b1af",
|
||||
"base_apk": "eba476fec3020eaceb28436c1d160eb35d6689d8ffb060e09678535584babc7a",
|
||||
"x86_64_split_apk": "5c4193c9700481079cee958b38ba9cbb97768b0a130b7f704ed084753f01ce2b"
|
||||
"libroblox": "4343a6a900d1fca27ff8e10b9d9c86bee40a5d0ba547cb29903b7769f4f42a9d",
|
||||
"base_apk": "a4a71ec39df6619a56ab7adce0959d1d60354835485b53806f4026914287ea86",
|
||||
"x86_64_split_apk": "a4a71ec39df6619a56ab7adce0959d1d60354835485b53806f4026914287ea86"
|
||||
},
|
||||
"signing_certificates_sha256": [
|
||||
"2bebd189e8d3106401347056c93d045b61e20e22d0c3cbed85474aeb00a3d12a",
|
||||
"44932ea35a17a267372d71b54d1a0cb3da0dca5113e94406ae2fe18090ba1477"
|
||||
],
|
||||
"assets": {
|
||||
"file_count": 1837
|
||||
"file_count": 1836,
|
||||
"sha256_tree": "caee48e11c194bc11852493f53ff67d5e2bb233172f12b87ea5fca97c0c10840"
|
||||
},
|
||||
"source": "sober-update-service",
|
||||
"imported_at": "2026-07-09T19:12:54Z",
|
||||
"compatibility_status": "supported"
|
||||
"source": "apk-pure-native",
|
||||
"imported_at": "2026-08-31T16:30:19Z",
|
||||
"compatibility_status": "supported",
|
||||
"apk_layout": "monolithic"
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
version: 1
|
||||
device: pc-windows-11
|
||||
|
||||
graphics:
|
||||
backend: direct-vulkan
|
||||
vsync: off
|
||||
|
||||
vr:
|
||||
enabled: true
|
||||
desktop_mirror: false
|
||||
frame_rate_limit: unlimited
|
||||
|
||||
audio:
|
||||
output_device: default
|
||||
input_device: default
|
||||
|
||||
updates:
|
||||
automatic: false
|
||||
launch_after_update: false
|
||||
source: auto
|
||||
@@ -32,7 +32,7 @@
|
||||
"mode": "vendored-patched-snapshot",
|
||||
"url": "https://github.com/minecraft-linux/mcpelauncher-linker.git",
|
||||
"upstream_commit": "1ac3ea6c1cf4f73a84d73f0bff510fba94a7d0f2",
|
||||
"vendor_tree": "fca44d85e3773c135ccfc603b14abdf6aeb8efd1",
|
||||
"vendor_tree": "0637221e48f382ec8d0bc701385f4b10bcd07a65",
|
||||
"patch": "third_party/patches/mcpelauncher-linker.patch",
|
||||
"nested_sources": {
|
||||
"android_bionic": {
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
# Building and source layout
|
||||
|
||||
The [installation guide](INSTALL.md) covers a complete source build on Steam
|
||||
Frame. The container recipe uses Fedora 44 ARM64 with CMake, Ninja, SDL3,
|
||||
OpenXR, Vulkan, WebKitGTK and Nightcap's other native dependencies.
|
||||
The Frame build enables OpenXR and the ARM64 bridge, disables runtime
|
||||
diagnostics, and uses native rendering. Fedora's `glslang` and `spirv-tools`
|
||||
packages are included for shader generation and validation. The ARM64 workflow
|
||||
builds the same container recipe and checks the package before export.
|
||||
|
||||
## Upstream source
|
||||
|
||||
This branch includes Nightcap's `vr` additions through
|
||||
[`adccf4e4841275bce85d0330013367e2c124bd10`](https://github.com/CoderDayton/nightcap/commit/adccf4e4841275bce85d0330013367e2c124bd10),
|
||||
including all changes from its `main` branch at that revision. The update adds
|
||||
emoji fallback fonts for native text fields, decoded/compressed emulated-texture
|
||||
byte counters, menu-only native Luau compilation, and queued game switching.
|
||||
Menu compilation is enabled only after validating the native flag on ARM64;
|
||||
explicit client settings remain in control.
|
||||
|
||||
Frame already uses the same reference SMAA revision as this update. Its existing
|
||||
gaze AA path is retained without adding a second full-eye filter. Frame's native
|
||||
2160 × 2160 rendering, 72 Hz profile, simple Video controls, controllers, audio
|
||||
and foveation integration remain the platform defaults.
|
||||
|
||||
## Build scripts
|
||||
|
||||
`build-frame-nightcap.sh` configures a Release build at `/work/build-frame-arm64`
|
||||
and installs to `/opt/nightcap-vr-arm64`. VR is enabled and runtime diagnostics
|
||||
are disabled. It also builds the Steam application identity helper.
|
||||
|
||||
For incremental development in an existing Frame image, with Nightcap closed:
|
||||
|
||||
```bash
|
||||
podman run --arch arm64 --name nightcap-frame-development \
|
||||
--cpus 3 --memory 8g --security-opt label=disable \
|
||||
-v "$PWD:/work" --entrypoint /bin/bash \
|
||||
localhost/nightcap-vr-arm64:frame-foveated -lc \
|
||||
'bash /work/build-frame-nightcap.sh && bash /work/test-arm64-bridge.sh'
|
||||
```
|
||||
|
||||
The test script runs focused ARM64 bridge, pose, controller, lifecycle, JNI,
|
||||
audio and host ABI tests, including the native assembly return-value fixture.
|
||||
It also builds the GPU test tools. Build and test commands share one CMake tree;
|
||||
run them sequentially.
|
||||
|
||||
## Main components
|
||||
|
||||
| Path | Purpose |
|
||||
| --- | --- |
|
||||
| `src/vr/` | OpenXR lifecycle, gaze, stereo eye transport, controllers, AA and foveation maps |
|
||||
| `src/vr/roblox_vr_arm64_build_contracts.h` | Selects exact ARM64 client contracts |
|
||||
| `src/vr/roblox_vr_arm64_contract*.h` | Client-specific ARM instruction and object layout contracts |
|
||||
| `src/audio/` | Audio playback and microphone recording bridges |
|
||||
| `src/compat/host_abi_profile.cc` | Client startup, allocator and native host ABI profiles |
|
||||
| `src/graphics/vulkan_foveation_adapter.inc` | Scene rendering integration for density maps |
|
||||
| `tools/steam_frame/` | Frame launcher, Steam shortcut and Video settings integration |
|
||||
| `tools/frame_video_settings/` | OpenVR settings, refresh-rate and timing helpers |
|
||||
| `config/roblox_compatibility.json` | Supported client identities and compatibility metadata |
|
||||
| `config/steam-frame.example.yaml` | Initial Frame runtime settings |
|
||||
|
||||
## Client compatibility
|
||||
|
||||
The ARM64 bridge supports these client identities:
|
||||
|
||||
| Version | Build | ELF build ID |
|
||||
| --- | --- | --- |
|
||||
| 2.742.1059 | 3222 | `25aa7fd31008a744c3c99f717e7d16d81d371d12` |
|
||||
| 2.736.1408 | 2998 | `ed2b035768b1513561d0e6ea7f9bbec35b0e60e5` |
|
||||
|
||||
New clients can change native methods, virtual tables, object fields and startup
|
||||
ordering. Add matching ARM64 contracts and host/audio profiles before enabling a
|
||||
new client. Keep the existing signature and build identity checks in place.
|
||||
|
||||
The timing helper in `tools/frame_video_settings/frame_timings.cc` reports the
|
||||
scene process, dashboard visibility, headset activity and compositor throttling
|
||||
alongside frame times. Compare the same view in the same experience while the
|
||||
headset is displaying projection frames. Standby or desktop-only frames do not
|
||||
measure active VR throughput.
|
||||
|
||||
Shader sources and their generated SPIR-V header are included. Regenerating the
|
||||
AA shaders with `tools/compile_gaze_aa.py` additionally needs `glslangValidator`.
|
||||
The SMAA sources retain their upstream license in `src/vr/smaa/`.
|
||||
|
||||
## Native rendering
|
||||
|
||||
The shipping Frame profile uses native rendering with gaze anti-aliasing.
|
||||
MSAA and upscaling controls are absent from the Frame Video settings. The build
|
||||
sets `MOCKTAIL_ENABLE_FSR3=OFF`, so the FidelityFX SDK is not linked into the
|
||||
Frame runtime. Reference upscaler sources and their licenses remain in the tree.
|
||||
|
||||
To regenerate the gaze AA shaders after changing them:
|
||||
|
||||
```sh
|
||||
python3 tools/compile_gaze_aa.py .
|
||||
```
|
||||
|
||||
This requires `glslangValidator`. The SMAA sources retain their upstream license
|
||||
in `src/vr/smaa/`.
|
||||
|
||||
## Foveation tile calibration
|
||||
|
||||
Density-map texels and GMEM render tiles have different sizes. The gaze guard
|
||||
must protect every render tile touching the full-detail disk. The narrow entry
|
||||
in `include/mocktail/vr/foveation_tile_bounds.h` applies only to this identity:
|
||||
|
||||
- Turnip driver ID 18, vendor `0x5143`, device `0x43051401`, version `109060195`.
|
||||
- Driver name `turnip Mesa driver`, info `Mesa 26.3.0-devel (git-abc426bb62)`.
|
||||
- Driver UUID `586b9fa4514cb88b3c11a968ef4d62e4`; measured ELF build ID
|
||||
`b7e5995d0b202f06e81cd5a0f809ec1b744a0cb8`.
|
||||
- A 2160 × 2160 framebuffer, one layer/subpass, single-sample
|
||||
`R8G8B8A8_UNORM` color and `D32_SFLOAT` depth, plus the density attachment.
|
||||
Offsets, resolves, extra attachments, multiview and feedback/input attachments
|
||||
retain the conservative guard.
|
||||
|
||||
On that binary, `vkCmdBeginRenderPass2` at RVA `0xb12180` calls the layout
|
||||
selector at `0xad67c0`. The selector initializes its layout from the FDM flag
|
||||
at render-pass byte 145; subsequent checks can only select `AVOID_CCU` again.
|
||||
The divisor routine at `0xa9e300` returns 1 for FDM. CPU-only creation of 72
|
||||
render-pass/imageless-framebuffer combinations, covering FDM on/off and every
|
||||
standard color/depth load/store combination, produced identical budgets:
|
||||
356352 pixels for `FULL`, 350208 for `AVOID_CCU`. Their base tiles are
|
||||
480 × 736 and 768 × 448 respectively. FDM therefore selects 768 × 448
|
||||
independently of shaders and load/store operations for this tuple.
|
||||
|
||||
The GPU qualification compared a high-frequency full-density reference against
|
||||
the production map helper with its 68 × 68 map and 768 × 448 guard. Sweep both
|
||||
gaze axes through `{-1, -.75, -.4, 0, .4, .75, 1}`, radii
|
||||
`{.08, .15, .22, .35}`, and all three active profiles. All 588 cases preserved
|
||||
every pixel inside the gaze disk exactly, with 588 valid fragment-invocation
|
||||
queries and no validation errors. Mean fragment invocations were 51.46% of the
|
||||
reference in this fixture; this measures shading work, not game FPS. Requalify
|
||||
both the driver invariant and this image/counter sweep before adding identities
|
||||
or pass tuples. Unknown identities retain the conservative policy.
|
||||
|
||||
The CPU identity and coordinate contracts can be rerun with:
|
||||
|
||||
```sh
|
||||
cmake --build build-frame-arm64 --target foveation_tile_bounds_test framebuffer_density_map_test
|
||||
ctest --test-dir build-frame-arm64 -R 'FoveationTileBounds|FramebufferDensityMap' --output-on-failure
|
||||
```
|
||||
|
||||
## Frame-time stability
|
||||
|
||||
The Android `mallinfo` bridge shares a real host allocator snapshot for up to
|
||||
500 ms. glibc scans allocator free lists under arena locks; several consecutive
|
||||
queries therefore reuse a recent coherent sample. A scan runs outside the cache
|
||||
mutex, and concurrent readers can keep using the previous completed sample.
|
||||
Allocation, deallocation and the process-memory limit checks retain their normal
|
||||
behavior.
|
||||
|
||||
Gaze density generation reuses per-eye pixel and half-float buffers across
|
||||
frames. The density values and gaze protection are unchanged. At a 1:1 render and output size, the native path avoids unnecessary
|
||||
reconstruction, temporal resource tracking and upscaler shader dispatches.
|
||||
Gaze anti-aliasing runs on the composed eye image.
|
||||
|
||||
|
||||
The Vulkan eye copy is submitted on the OpenXR binding queue before releasing
|
||||
its swapchain images, so SteamVR can enqueue composition immediately. When the
|
||||
guest device requested exactly one unprotected queue, completion is deferred
|
||||
until the next use of its eye-copy and anti-aliasing resources. GPU queue ordering keeps subsequent guest
|
||||
commands behind the copy. The upstream submission ring tracks command buffers, fences and source/destination
|
||||
images. Presentation dependencies are consumed on the GPU and replaced with a
|
||||
per-swapchain-image semaphore, so the CPU does not wait for the current render.
|
||||
AA resources wait only for previous eye-copy users;
|
||||
current-frame present dependencies are excluded from that resource wait.
|
||||
Captured source-image handles are drained before
|
||||
image destruction; queue exposure, swapchain replacement and teardown also
|
||||
drain pending work before releasing resources. `xrWaitFrame` remains on the
|
||||
original render thread. Additional queues, readbacks and the virtual keyboard
|
||||
retain completed-copy synchronization. `MOCKTAIL_VR_EARLY_SUBMIT=0` restores
|
||||
the original submission order, while `MOCKTAIL_VR_DEFER_COPY_WAIT=0` keeps the
|
||||
early submission but completes its fence before waiting for the next frame.
|
||||
|
||||
Density-map generation skips the distance/ramp calculation inside the protected
|
||||
full-detail disk and beyond the peripheral plateau. Transition cells retain the
|
||||
same distance calculation and spatial pattern. This reduces CPU work without
|
||||
changing gaze protection or density values.
|
||||
|
||||
The Frame launcher enables a render-thread CPU preference after projection
|
||||
submission starts. It reads Linux CPU capacity values only for the thread's
|
||||
existing allowed CPUs and selects capacity peers within 80% of their maximum.
|
||||
Incomplete capacity information or fewer than two preferred CPUs retains the
|
||||
original allocation. This keeps render work on the application's performance
|
||||
cores while respecting SteamVR's CPU reservation. Other engine threads retain
|
||||
their normal allocation.
|
||||
|
||||
## Upstream integration
|
||||
|
||||
The imported Nightcap VR revision is recorded in [the credits](../THIRD_PARTY.md).
|
||||
The Frame launcher disables the generic full-eye FXAA pass so Steam Video's
|
||||
Anti Aliasing setting remains the single AA control. The upstream JNI haptic
|
||||
protocol runs natively on ARM64. Its optional x86 SetMotor instruction scanner
|
||||
is gated to x86; ARM64 build IDs, instruction checks and native return-value
|
||||
bridges retain their own contracts.
|
||||
|
||||
Steam Video settings are loaded once at startup and then refreshed on a worker.
|
||||
The rendering callbacks copy a completed snapshot; they never read the settings
|
||||
file or parse JSON. Unchanged content reuses its parsed values. A failed refresh
|
||||
keeps the last completed snapshot, and shutdown wakes the polling worker.
|
||||
The launcher gives a configured per-app headset refresh rate precedence over
|
||||
the desktop rate observed before the app gains focus, and derives Roblox's
|
||||
startup FPS cap from that selected profile.
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
# Install on Steam Frame
|
||||
|
||||
## Requirements
|
||||
|
||||
- Steam Frame running its ARM64 Linux desktop, with Steam and SteamVR installed.
|
||||
- Native SteamVR at `/opt/steamvr`, including `steamxr_linuxarm64.json`.
|
||||
- The Frame's Turnip/Freedreno Vulkan driver under `/usr/lib` and its Vulkan ICD
|
||||
under `/usr/share/vulkan/icd.d`.
|
||||
- Rootless Podman, Git, Python 3 and a C++17 compiler on the Frame.
|
||||
- `zstd` for importing the compressed ARM64 release archive.
|
||||
- A Linux filesystem for the checkout, internet access and space for the source,
|
||||
Fedora build image and a separate Roblox client download.
|
||||
|
||||
The runtime uses Fedora 44 ARM64 in a rootless container. Keep the Frame's
|
||||
own graphics drivers: the launcher mounts them into the container. Building
|
||||
from source also needs a case-sensitive Linux checkout because some Android
|
||||
headers differ only by letter case. The source and ARM64 build are public.
|
||||
|
||||
## 1. Get the source
|
||||
|
||||
```bash
|
||||
git clone --branch steam-frame --recurse-submodules \
|
||||
https://github.com/InstanceBrick/nightcap.git \
|
||||
"$HOME/.local/share/nightcap-steam-frame-src"
|
||||
cd "$HOME/.local/share/nightcap-steam-frame-src"
|
||||
```
|
||||
|
||||
## 2. Install the ARM64 image
|
||||
|
||||
Download `nightcap-steam-frame-arm64.oci.tar.zst` and its `.sha256` file from
|
||||
[Releases](https://github.com/InstanceBrick/nightcap/releases). From the
|
||||
directory containing both files:
|
||||
|
||||
```bash
|
||||
set -euo pipefail
|
||||
sha256sum -c nightcap-steam-frame-arm64.oci.tar.zst.sha256
|
||||
zstd -dc nightcap-steam-frame-arm64.oci.tar.zst | podman load
|
||||
```
|
||||
|
||||
The archive imports as `localhost/nightcap-vr-arm64:frame-foveated`. It contains
|
||||
the ARM64 Nightcap runtime and its Fedora dependencies, with no Roblox client,
|
||||
personal data, SteamVR installation or proprietary graphics drivers.
|
||||
|
||||
To build the same image from source instead:
|
||||
|
||||
```bash
|
||||
podman build --arch arm64 --jobs 1 \
|
||||
-f packaging/steam-frame/Containerfile \
|
||||
-t localhost/nightcap-vr-arm64:frame-foveated .
|
||||
```
|
||||
|
||||
This builds Nightcap and its helpers from source. It does not download or bundle
|
||||
Roblox. The image tag is also the one used by the Frame launcher.
|
||||
|
||||
## 3. Install the launcher and refresh-rate helper
|
||||
|
||||
```bash
|
||||
python3 tools/steam_frame/install_launcher.py
|
||||
mkdir -p "$HOME/.local/share/nightcap-vr-arm64/data/tools"
|
||||
c++ -std=c++17 -O2 tools/frame_video_settings/refresh_rate.cc -ldl \
|
||||
-o "$HOME/.local/share/nightcap-vr-arm64/data/tools/refresh_rate"
|
||||
```
|
||||
|
||||
The application menu entry is **Nightcap Roblox VR**.
|
||||
|
||||
## 4. Download the compatible Roblox client
|
||||
|
||||
Close Nightcap first. Prepare its data directory and copy the initial settings
|
||||
only if no configuration exists yet:
|
||||
|
||||
```bash
|
||||
frame_data="$HOME/.local/share/nightcap-vr-arm64/data"
|
||||
mkdir -p "$frame_data/.config/mocktail"
|
||||
if [ ! -f "$frame_data/.config/mocktail/config.yaml" ]; then
|
||||
cp config/steam-frame.example.yaml "$frame_data/.config/mocktail/config.yaml"
|
||||
fi
|
||||
podman run --rm --arch arm64 --userns=keep-id \
|
||||
--security-opt label=disable \
|
||||
-v "$frame_data:/data:rw" \
|
||||
-e HOME=/data -e XDG_CONFIG_HOME=/data/.config \
|
||||
-e XDG_DATA_HOME=/data/.local/share -e XDG_CACHE_HOME=/data/.cache \
|
||||
--entrypoint /opt/nightcap-vr-arm64/lib64/mocktail/mocktail_updater \
|
||||
localhost/nightcap-vr-arm64:frame-foveated \
|
||||
update --no-latest-check --skip-canary
|
||||
```
|
||||
|
||||
The updater downloads the newest ARM64 client supported by the included catalog.
|
||||
The command retains APK signature, architecture, build identity and payload
|
||||
integrity checks. `--skip-canary` skips the separate desktop graphics test; VR
|
||||
starts through the Frame launcher in the next step. Client files stay in your
|
||||
local data directory.
|
||||
|
||||
## 5. Launch in VR and add to Steam
|
||||
|
||||
Start SteamVR and connect the headset, then open **Nightcap Roblox VR** from the
|
||||
application menu. Sign in through Roblox's normal interface.
|
||||
|
||||
To add the Steam shortcut automatically, enable Steam's local browser debugging
|
||||
endpoint at `127.0.0.1:8080`, keep Steam running and use:
|
||||
|
||||
```bash
|
||||
python3 tools/steam_frame/register_steam_shortcut.py
|
||||
```
|
||||
|
||||
The script creates or reuses the launcher shortcut, sets its Nightcap icon and
|
||||
marks it as a VR application. It saves the generated identity locally. If the
|
||||
endpoint is unavailable, use Steam's **Add a Non-Steam Game** dialog and select
|
||||
`~/.local/bin/nightcap-vr-arm64`, then mark the shortcut as a VR application.
|
||||
|
||||
## 6. Add the SteamVR Video controls
|
||||
|
||||
The settings integration supports a specific SteamVR dashboard revision. It
|
||||
checks the installed dashboard before applying changes and leaves other
|
||||
revisions untouched. Valve dashboard files are read from your local installation.
|
||||
|
||||
With SteamVR closed and the original dashboard files visible:
|
||||
|
||||
```bash
|
||||
mkdir -p frame-ui-local/originals frame-ui-local/generated
|
||||
cp /opt/steamvr/resources/webinterface/dashboard/chunk~8012d0c89.js \
|
||||
frame-ui-local/originals/steamvr-dashboard.original.js
|
||||
cp /opt/steamvr/resources/webinterface/dashboard/systemui.html \
|
||||
frame-ui-local/originals/steamvr-systemui.original.html
|
||||
cp /opt/steamvr/resources/webinterface/dashboard/settings_desktop.html \
|
||||
frame-ui-local/originals/steamvr-settings_desktop.original.html
|
||||
python3 tools/steam_frame/prepare_video_ui.py \
|
||||
frame-ui-local/originals frame-ui-local/generated
|
||||
sudo python3 tools/steam_frame/install_video_ui.py frame-ui-local/generated
|
||||
```
|
||||
|
||||
Start SteamVR again. The settings are described in [Video settings](SETTINGS.md).
|
||||
The service mounts three generated files read-only; the original SteamVR files
|
||||
are preserved. If upgrading an existing installation, reuse your saved original
|
||||
files instead of copying the already patched dashboard.
|
||||
|
||||
## Update Nightcap
|
||||
|
||||
Close Nightcap before replacing its image or launcher. Pull the `steam-frame`
|
||||
branch, then load the new ARM64 release archive as in step 2 and rerun the
|
||||
launcher installer. Existing Roblox downloads, sign-in state and settings stay
|
||||
in the separate data directory.
|
||||
|
||||
For source builds, pull this branch and rebuild under a temporary image tag:
|
||||
|
||||
```bash
|
||||
git pull --ff-only
|
||||
git submodule update --init --recursive
|
||||
podman build --arch arm64 --jobs 1 \
|
||||
-f packaging/steam-frame/Containerfile \
|
||||
-t localhost/nightcap-vr-arm64:next .
|
||||
podman tag localhost/nightcap-vr-arm64:frame-foveated \
|
||||
localhost/nightcap-vr-arm64:previous
|
||||
podman tag localhost/nightcap-vr-arm64:next \
|
||||
localhost/nightcap-vr-arm64:frame-foveated
|
||||
python3 tools/steam_frame/install_launcher.py
|
||||
```
|
||||
|
||||
Existing Roblox downloads, sign-in state and settings remain in the data
|
||||
directory. To install a newly supported Roblox client, repeat step 4 after
|
||||
updating Nightcap. Automatic client updates are off in the supplied Frame
|
||||
configuration, since the ARM64 VR bridge needs matching client interfaces.
|
||||
|
||||
To restore the previous Nightcap image with the app closed:
|
||||
|
||||
```bash
|
||||
podman tag localhost/nightcap-vr-arm64:previous \
|
||||
localhost/nightcap-vr-arm64:frame-foveated
|
||||
```
|
||||
@@ -0,0 +1,124 @@
|
||||
# Settings
|
||||
|
||||
## Video
|
||||
|
||||
Open the game's SteamVR **Video** settings after installing the settings
|
||||
integration.
|
||||
|
||||
The standard profile is **native 2160 × 2160 per eye**, **72 Hz**, **SMAA High**
|
||||
and **Performance** foveated rendering. The application targets 72 FPS at 72 Hz;
|
||||
actual frame rate depends on the experience and Roblox's graphics settings.
|
||||
The gaze radius remains adjustable, and existing saved choices are preserved.
|
||||
|
||||
| Setting | Effect |
|
||||
| --- | --- |
|
||||
| Foveated Rendering | Off uses full-density shading throughout. Quality, Balanced and Performance progressively reduce peripheral shading work. Each mode keeps the same full-detail gaze region. |
|
||||
| Gaze sharp radius | Sets the minimum full-detail region around your gaze, from 8% to 35%. Larger values preserve more of the view at full density and cost more GPU time. Hardware tile boundaries can enlarge this region. |
|
||||
| Anti Aliasing | Off, FXAA, SMAA Low or SMAA High. Filtering runs around your gaze on the composed eye image, including interface edges. SMAA High is the default. |
|
||||
| Desktop Mirror | Off by default on Frame. On displays the VR image in the desktop window as well as the headset. Relaunch Nightcap after changing it. |
|
||||
| Performance Overlay | Shows actual game FPS, headset Hz, average frame time, SteamVR GPU time and per-eye resolution at the bottom center of the headset view. It hides while the Steam menu is open and does not capture controller input. |
|
||||
| Resolution Per Eye | Sets the headset eye-buffer size. Nightcap's recommended default is 2160 × 2160 per eye; Auto and Reset to Defaults use this baseline. Explicit custom values stay selected. Changes apply after the slider settles, with a brief pause while eye buffers are rebuilt at a safe frame boundary. |
|
||||
| Display Refresh Rate | The standard profile uses 72 Hz. The launcher follows the selected per-game headset rate and any explicit Steam frame limit. Restart Nightcap after changing the rate or frame limit so Roblox's internal cap matches it. |
|
||||
|
||||
Enable **Performance Overlay**, then close the Steam menu. **APP FPS** counts
|
||||
new Roblox projections over a rolling second. **HMD Hz** is the headset refresh
|
||||
rate. **FRAME** is average time per submitted game frame, **TARGET** is the
|
||||
configured application frame rate, and **BUDGET** is the time available per
|
||||
application frame: 13.89 ms for 72 FPS at 72 Hz. Repeated or reprojected
|
||||
compositor frames do not count as new game frames.
|
||||
|
||||
**GPU** averages SteamVR timings for new application frames, excluding
|
||||
compositor-only reprojections. It can include scheduling gaps. The overlay
|
||||
reports standby or missing frames instead of retaining a stale FPS. Its setting
|
||||
persists between launches and can be turned off at any time.
|
||||
|
||||
Foveated rendering reduces fragment-shader work in the 3D scene before the
|
||||
image is composed. The final composition stays at native resolution, keeping
|
||||
screen-space text and interface elements from being coarsened a second time.
|
||||
Your gaze region retains the detail available at the selected eye resolution;
|
||||
the periphery uses progressively lower shading density.
|
||||
|
||||
Foveation, gaze radius and anti-aliasing controls update while the application
|
||||
is running. Higher eye resolutions improve fine detail and increase GPU and
|
||||
memory cost. For more headroom, keep Performance foveation, reduce the gaze
|
||||
radius if needed, or lower Roblox's own graphics quality.
|
||||
|
||||
The launcher uses the selected Steam Video resolution without an extra
|
||||
multiplier. To add supersampling, set `NIGHTCAP_VR_RENDER_SCALE` when launching
|
||||
Nightcap; for example, `NIGHTCAP_VR_RENDER_SCALE=1.25`. The default is `1.0`.
|
||||
Larger values increase GPU work.
|
||||
|
||||
The launcher reads the selected headset refresh rate and SteamVR's frame limit
|
||||
on every launch. At 72 Hz with no frame limit, Roblox's internal scheduler is
|
||||
capped at 72 FPS. Half-rate rendering is not forced automatically.
|
||||
|
||||
On Frame, the render thread prefers higher-capacity cores within the CPU
|
||||
allocation supplied by SteamVR. To use the default thread placement, launch with
|
||||
`NIGHTCAP_VR_PREFER_PERFORMANCE_CORES=0`.
|
||||
|
||||
## Controllers and typing
|
||||
|
||||
The native VR bridge supplies controller tracking, buttons, axes and haptics.
|
||||
Roblox experiences choose how to use those inputs. The in-headset keyboard opens
|
||||
when a text field receives focus; point at a key and pull the trigger to type.
|
||||
Steam Controller desktop navigation can also be configured through the Steam
|
||||
shortcut's controller layout.
|
||||
|
||||
## Microphone
|
||||
|
||||
Set the Frame's headset microphone as the default input in the desktop audio
|
||||
settings and check that it is not muted. Roblox voice chat uses its normal
|
||||
account eligibility, microphone permission and in-experience controls.
|
||||
|
||||
Nightcap's native recording bridge captures the selected input when Roblox
|
||||
requests recording. To choose a different input, close Nightcap and edit
|
||||
`~/.local/share/nightcap-vr-arm64/data/.config/mocktail/config.yaml`:
|
||||
|
||||
```yaml
|
||||
audio:
|
||||
input_device: default
|
||||
```
|
||||
|
||||
An exact SDL device name can replace `default`. Input device names appear in the
|
||||
launch log.
|
||||
|
||||
## Desktop mirror
|
||||
|
||||
Open SteamVR's per-game **Video** settings and set **Desktop Mirror** to **On**
|
||||
to show the VR view in a desktop window. Set it to **Off** to keep the Frame's
|
||||
single headset application without the extra desktop view. Relaunch Nightcap
|
||||
after changing this setting. The Frame default is **Off**.
|
||||
|
||||
The Frame launcher reads the saved SteamVR mirror choice and coordinates both
|
||||
the mirror renderer and desktop-window visibility. This setting takes precedence
|
||||
over the YAML value when using the Frame launcher. Generic Nightcap launches
|
||||
can configure the mirror beside the VR option:
|
||||
|
||||
```yaml
|
||||
vr:
|
||||
enabled: true
|
||||
desktop_mirror: true
|
||||
```
|
||||
|
||||
`MOCKTAIL_VR_DESKTOP_MIRROR=1` or `0` overrides the YAML setting for generic
|
||||
launches. VR and desktop mirroring are separate options; enabling VR does not
|
||||
require the desktop mirror.
|
||||
|
||||
Roblox's desktop viewport stays at 1920 × 1080 during VR for stable in-game
|
||||
menu size, independently of headset eye resolution. On Frame, desktop window geometry
|
||||
is not restored or saved in VR, including when the mirror is enabled.
|
||||
|
||||
## Files and troubleshooting
|
||||
|
||||
| Item | Location |
|
||||
| --- | --- |
|
||||
| Launcher | `~/.local/bin/nightcap-vr-arm64` |
|
||||
| Launch log | `~/.local/share/nightcap-vr-arm64/last-launch.log` |
|
||||
| Configuration | `~/.local/share/nightcap-vr-arm64/data/.config/mocktail/config.yaml` |
|
||||
| Local client store | `~/.local/share/nightcap-vr-arm64/data/.local/share/mocktail` |
|
||||
| Steam shortcut identity | `~/.local/share/nightcap-vr-arm64/steam-shortcut.json` |
|
||||
|
||||
If launching fails, check the launch log, make sure SteamVR is running and confirm
|
||||
that the `localhost/nightcap-vr-arm64:frame-foveated` image exists. If the game
|
||||
reports an unsupported client, update Nightcap before replacing the client.
|
||||
Keep account data and client downloads outside source control.
|
||||
Generated
+61
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1785627969,
|
||||
"narHash": "sha256-4dtXQk/NMePegK/nWp5NSeuZKLATItOq61lpEvmXqGw=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "427bf4bd9435fdf21321c8cc628c24efc14c0f7a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1787135253,
|
||||
"narHash": "sha256-RD2kNWCG+Bjo6h+JVjWVNntZs2GtRoeY2xHjts/FNkA=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "ffb3c9b700e759be2ef13237c9d8f953b32a1e46",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1785031560,
|
||||
"narHash": "sha256-OmshNvn2vupOFpYinLUu+1Dnpu4n7Q5N3ggGVNHpkUI=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "0e79af5e3d4dcfcd676ab5ba3f95d2e3352e078c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-parts": "flake-parts",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
description = "Experimental Roblox compatibility runtime for Linux and FreeBSD";
|
||||
|
||||
inputs = {
|
||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
};
|
||||
|
||||
outputs =
|
||||
inputs@{ flake-parts, ... }:
|
||||
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||
imports = [
|
||||
|
||||
];
|
||||
systems = [
|
||||
"x86_64-linux"
|
||||
# "aarch64-linux"
|
||||
# "aarch64-darwin"
|
||||
# "x86_64-darwin"
|
||||
];
|
||||
perSystem =
|
||||
{
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
package = pkgs.callPackage ./packaging/nix/package.nix { };
|
||||
in
|
||||
{
|
||||
packages.default = package;
|
||||
devShells.default = pkgs.mkShell {
|
||||
inputsFrom = [ package ];
|
||||
|
||||
# nixpkgs installs the header we need under include/libutf8proc
|
||||
NIX_CFLAGS_COMPILE = "-I${pkgs.libutf8proc}/include/libutf8proc";
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
cmake
|
||||
git
|
||||
lld
|
||||
ninja
|
||||
pkg-config
|
||||
vulkan-headers
|
||||
|
||||
llvmPackages_22.clang-tools
|
||||
llvmPackages_22.libclang
|
||||
|
||||
# debugging
|
||||
gdb
|
||||
];
|
||||
|
||||
desktopItems = [
|
||||
(pkgs.makeDesktopItem {
|
||||
name = "Mocktail";
|
||||
exec = "mocktail";
|
||||
icon = "mocktail";
|
||||
desktopName = "Mocktail";
|
||||
comment = "Experimental Roblox compatibility runtime for Linux and FreeBSD";
|
||||
categories = [ "Game" ];
|
||||
})
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${pkgs.lib.makeLibraryPath package.buildInputs}"
|
||||
|
||||
# nix packages graphics drivers differently, and mocktail will still work if it cannot guarantee symbols
|
||||
export MOCKTAIL_REQUIRE_REAL_GRAPHICS=0
|
||||
'';
|
||||
};
|
||||
};
|
||||
flake = { };
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
#include "compat/bionic_host_libc_runtime.h"
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
|
||||
namespace mocktail::compat {
|
||||
// Heap accounting is a sampled statistic. A glibc scan walks free lists while
|
||||
// holding arena locks, so back-to-back Android queries share a recent sample.
|
||||
// The scan runs outside this mutex; concurrent callers can use the last complete
|
||||
// sample without waiting for another allocator walk.
|
||||
class AllocatorSnapshotCache {
|
||||
public:
|
||||
static constexpr std::uint64_t kRefreshNs=500'000'000ULL;
|
||||
template<class Reader>
|
||||
BionicMallinfoSnapshot Read(std::uint64_t now,Reader reader) noexcept {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
if(refreshing_ || (initialized_ && now<refresh_after_))return snapshot_;
|
||||
refreshing_=true;
|
||||
lock.unlock();
|
||||
const auto fresh=reader();
|
||||
lock.lock();
|
||||
snapshot_=fresh;
|
||||
initialized_=true;
|
||||
refresh_after_=now+kRefreshNs;
|
||||
refreshing_=false;
|
||||
return snapshot_;
|
||||
}
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
BionicMallinfoSnapshot snapshot_{};
|
||||
bool initialized_=false,refreshing_=false;
|
||||
std::uint64_t refresh_after_=0;
|
||||
};
|
||||
}
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_COMPAT_BIONIC_ABI_EXPORTS_H_
|
||||
#define MOCKTAIL_COMPAT_BIONIC_ABI_EXPORTS_H_
|
||||
|
||||
@@ -47,6 +33,23 @@ void SetLegacyBionicDiagnosticsEnabled(bool enabled);
|
||||
// guest layout explicit instead of exposing the incompatible host libc type.
|
||||
using MocktailBionicMutexAttr = int64_t;
|
||||
|
||||
// Bionic LP64 pthread_attr_t: 56 bytes with explicit field offsets. The host
|
||||
// glibc layout is opaque and wider on aarch64, so guest buffers must be read
|
||||
// and written through this struct only.
|
||||
struct MocktailBionicPthreadAttr {
|
||||
std::uint32_t flags;
|
||||
void* stack_base;
|
||||
std::size_t stack_size;
|
||||
std::size_t guard_size;
|
||||
std::int32_t sched_policy;
|
||||
std::int32_t sched_priority;
|
||||
char reserved[16];
|
||||
};
|
||||
static_assert(sizeof(MocktailBionicPthreadAttr) == 56);
|
||||
|
||||
inline constexpr std::uint32_t kMocktailBionicAttrFlagDetached = 0x1;
|
||||
inline constexpr std::uint32_t kMocktailBionicAttrFlagUserStack = 0x2;
|
||||
|
||||
extern "C" {
|
||||
|
||||
void mocktail_set_current_jni_env(void* env);
|
||||
@@ -85,6 +88,31 @@ int mocktail_pthread_mutex_destroy(pthread_mutex_t* mutex);
|
||||
int mocktail_pthread_mutex_lock(pthread_mutex_t* mutex);
|
||||
int mocktail_pthread_mutex_trylock(pthread_mutex_t* mutex);
|
||||
int mocktail_pthread_mutex_unlock(pthread_mutex_t* mutex);
|
||||
int mocktail_pthread_once(pthread_once_t* once_control,
|
||||
void (*init_routine)(void));
|
||||
int mocktail_pthread_spin_init(pthread_spinlock_t* lock, int pshared);
|
||||
int mocktail_pthread_spin_destroy(pthread_spinlock_t* lock);
|
||||
int mocktail_pthread_spin_lock(pthread_spinlock_t* lock);
|
||||
int mocktail_pthread_spin_trylock(pthread_spinlock_t* lock);
|
||||
int mocktail_pthread_spin_unlock(pthread_spinlock_t* lock);
|
||||
int mocktail_pthread_barrier_init(pthread_barrier_t* barrier,
|
||||
const pthread_barrierattr_t* attr,
|
||||
unsigned count);
|
||||
int mocktail_pthread_barrier_destroy(pthread_barrier_t* barrier);
|
||||
int mocktail_pthread_barrier_wait(pthread_barrier_t* barrier);
|
||||
|
||||
int mocktail_pthread_attr_init(MocktailBionicPthreadAttr* attr);
|
||||
int mocktail_pthread_attr_destroy(MocktailBionicPthreadAttr* attr);
|
||||
int mocktail_pthread_attr_setstacksize(MocktailBionicPthreadAttr* attr,
|
||||
size_t stack_size);
|
||||
int mocktail_pthread_attr_setdetachstate(MocktailBionicPthreadAttr* attr,
|
||||
int detach_state);
|
||||
int mocktail_pthread_attr_setschedparam(MocktailBionicPthreadAttr* attr,
|
||||
const struct sched_param* parameters);
|
||||
int mocktail_pthread_getattr_np(pthread_t thread,
|
||||
MocktailBionicPthreadAttr* attr);
|
||||
int mocktail_pthread_attr_getstack(const MocktailBionicPthreadAttr* attr,
|
||||
void** stack_base, size_t* stack_size);
|
||||
|
||||
extern void* mocktail_gameactivity_on_start_native;
|
||||
extern void* mocktail_gameactivity_on_resume_native;
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_COMPAT_BIONIC_ATFORK_RUNTIME_H_
|
||||
#define MOCKTAIL_COMPAT_BIONIC_ATFORK_RUNTIME_H_
|
||||
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_COMPAT_BIONIC_DNS_RUNTIME_H_
|
||||
#define MOCKTAIL_COMPAT_BIONIC_DNS_RUNTIME_H_
|
||||
|
||||
|
||||
@@ -1,40 +1,37 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_COMPAT_BIONIC_HOST_LIBC_RUNTIME_H_
|
||||
#define MOCKTAIL_COMPAT_BIONIC_HOST_LIBC_RUNTIME_H_
|
||||
|
||||
#include <sys/sysinfo.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/utsname.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace mocktail::compat {
|
||||
|
||||
// glibc's atoi is (int)strtol(), exactly like Bionic's, so the host symbol is
|
||||
// used as-is there. Other libcs (musl) differ on overflow and need the shim.
|
||||
#ifdef __GLIBC__
|
||||
inline constexpr bool kHostAtoiMatchesBionic = true;
|
||||
#else
|
||||
inline constexpr bool kHostAtoiMatchesBionic = false;
|
||||
#endif
|
||||
|
||||
// Android uses size_t fields here; glibc uses int and musl has no equivalent.
|
||||
// An all-zero fallback means unknown, not zero usage.
|
||||
struct BionicMallinfoSnapshot {
|
||||
size_t arena = 0;
|
||||
size_t ordblks = 0;
|
||||
size_t smblks = 0;
|
||||
size_t hblks = 0;
|
||||
size_t hblkhd = 0;
|
||||
size_t usmblks = 0;
|
||||
size_t fsmblks = 0;
|
||||
size_t uordblks = 0;
|
||||
size_t fordblks = 0;
|
||||
size_t keepcost = 0;
|
||||
size_t arena;
|
||||
size_t ordblks;
|
||||
size_t smblks;
|
||||
size_t hblks;
|
||||
size_t hblkhd;
|
||||
size_t usmblks;
|
||||
size_t fsmblks;
|
||||
size_t uordblks;
|
||||
size_t fordblks;
|
||||
size_t keepcost;
|
||||
};
|
||||
|
||||
struct BionicLocaleState;
|
||||
@@ -72,6 +69,15 @@ void BionicArc4RandomBuffer(void* buffer, size_t size) noexcept;
|
||||
BionicMallinfoSnapshot BionicMallinfo() noexcept;
|
||||
bool BionicMallinfoHasHostTelemetry() noexcept;
|
||||
|
||||
void NormalizeBionicSysInfo(struct sysinfo* info) noexcept;
|
||||
int BionicSysInfo(struct sysinfo* info) noexcept;
|
||||
bool NormalizeLinuxulatorUnameVersion(struct utsname* name,
|
||||
const char* linux_version) noexcept;
|
||||
int BionicUname(struct utsname* name) noexcept;
|
||||
uid_t NormalizeBionicApplicationUid(uid_t host_uid) noexcept;
|
||||
uid_t BionicGetUid() noexcept;
|
||||
uid_t BionicGetEffectiveUid() noexcept;
|
||||
|
||||
// POSIX strerror_r contract: zero on success, -1/ERANGE on truncation.
|
||||
int BionicStrError(int error_number, char* buffer, size_t buffer_size) noexcept;
|
||||
|
||||
@@ -92,7 +98,12 @@ int mocktail_bionic_cxa_thread_atexit_impl(
|
||||
mocktail::compat::BionicThreadDestructor destructor, void* argument,
|
||||
void* dso_handle);
|
||||
void mocktail_bionic_arc4random_buf(void* buffer, size_t size);
|
||||
int mocktail_bionic_atoi(const char* text);
|
||||
mocktail::compat::BionicMallinfoSnapshot mocktail_bionic_mallinfo();
|
||||
int mocktail_bionic_sysinfo(struct sysinfo* info);
|
||||
int mocktail_bionic_uname(struct utsname* name);
|
||||
uid_t mocktail_bionic_getuid();
|
||||
uid_t mocktail_bionic_geteuid();
|
||||
int mocktail_bionic_strerror_r(int error_number, char* buffer,
|
||||
size_t buffer_size);
|
||||
long long mocktail_bionic_strtoll_l(const char* text, char** end, int base,
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_COMPAT_BIONIC_LARGE_FILE_RUNTIME_H_
|
||||
#define MOCKTAIL_COMPAT_BIONIC_LARGE_FILE_RUNTIME_H_
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef MOCKTAIL_COMPAT_BIONIC_PRCTL_RUNTIME_H_
|
||||
#define MOCKTAIL_COMPAT_BIONIC_PRCTL_RUNTIME_H_
|
||||
|
||||
extern "C" {
|
||||
|
||||
int mocktail_bionic_prctl(int option, unsigned long argument2,
|
||||
unsigned long argument3, unsigned long argument4,
|
||||
unsigned long argument5);
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // MOCKTAIL_COMPAT_BIONIC_PRCTL_RUNTIME_H_
|
||||
@@ -1,21 +1,9 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_COMPAT_BIONIC_PTHREAD_CREATE_RUNTIME_H_
|
||||
#define MOCKTAIL_COMPAT_BIONIC_PTHREAD_CREATE_RUNTIME_H_
|
||||
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
@@ -37,10 +25,17 @@ void ConfigureBionicPthreadThreadInitializer(
|
||||
|
||||
} // namespace mocktail::compat
|
||||
|
||||
struct MocktailBionicPthreadAttr;
|
||||
|
||||
// Rebuilds attributes instead of passing Android pthread_attr_t to glibc.
|
||||
extern "C" int mocktail_bionic_pthread_create(pthread_t* thread,
|
||||
const pthread_attr_t* attr,
|
||||
void* (*start_routine)(void*),
|
||||
void* argument);
|
||||
extern "C" int mocktail_bionic_pthread_create(
|
||||
pthread_t* thread, const MocktailBionicPthreadAttr* attr,
|
||||
void* (*start_routine)(void*), void* argument);
|
||||
|
||||
// Guest real-time scheduling is denied to avoid the host RLIMIT_RTTIME watchdog.
|
||||
extern "C" int mocktail_bionic_pthread_setschedparam(
|
||||
pthread_t thread, int policy, const struct sched_param* parameters);
|
||||
extern "C" int mocktail_bionic_sched_setscheduler(
|
||||
pid_t tid, int policy, const struct sched_param* parameters);
|
||||
|
||||
#endif // MOCKTAIL_COMPAT_BIONIC_PTHREAD_CREATE_RUNTIME_H_
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_COMPAT_BIONIC_PTHREAD_KEY_RUNTIME_H_
|
||||
#define MOCKTAIL_COMPAT_BIONIC_PTHREAD_KEY_RUNTIME_H_
|
||||
|
||||
|
||||
@@ -1,24 +1,11 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_COMPAT_BIONIC_RWLOCK_RUNTIME_H_
|
||||
#define MOCKTAIL_COMPAT_BIONIC_RWLOCK_RUNTIME_H_
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
// Bionic and glibc pthread_rwlock_t storage is not ABI-compatible. The guest
|
||||
// address is an opaque key; the host object lives entirely in this runtime.
|
||||
// Bionic pthread_rwlock_t is a state word in guest storage. rdlock/wrlock/
|
||||
// unlock use __atomic_* and futex on that word. Zero-initialized storage
|
||||
// still locks.
|
||||
extern "C" {
|
||||
|
||||
int mocktail_bionic_pthread_rwlock_init(pthread_rwlock_t* rwlock,
|
||||
|
||||
@@ -1,24 +1,11 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_COMPAT_BIONIC_SEMAPHORE_RUNTIME_H_
|
||||
#define MOCKTAIL_COMPAT_BIONIC_SEMAPHORE_RUNTIME_H_
|
||||
|
||||
#include <semaphore.h>
|
||||
|
||||
// Bionic and glibc use different sem_t representations. These exports keep
|
||||
// host semaphore objects out of guest storage and key them by guest address.
|
||||
// Bionic sem_t is a count word in guest storage (value << 1, shared bit 0).
|
||||
// Wait/post use __atomic_* and futex on that word so inlined guest futex
|
||||
// waiters wake. Process-shared semaphores are rejected.
|
||||
extern "C" {
|
||||
|
||||
int mocktail_bionic_sem_init(sem_t* semaphore, int process_shared,
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_COMPAT_BIONIC_SIGNAL_RUNTIME_H_
|
||||
#define MOCKTAIL_COMPAT_BIONIC_SIGNAL_RUNTIME_H_
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef MOCKTAIL_COMPAT_BIONIC_SOCKET_RUNTIME_H_
|
||||
#define MOCKTAIL_COMPAT_BIONIC_SOCKET_RUNTIME_H_
|
||||
|
||||
#include <sys/socket.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
int mocktail_bionic_setsockopt(int socket, int level, int option_name,
|
||||
const void *option_value,
|
||||
socklen_t option_length);
|
||||
|
||||
ssize_t mocktail_bionic_sendmsg(int socket, const struct msghdr *message,
|
||||
int flags);
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // MOCKTAIL_COMPAT_BIONIC_SOCKET_RUNTIME_H_
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_COMPAT_BIONIC_STDIO_RUNTIME_H_
|
||||
#define MOCKTAIL_COMPAT_BIONIC_STDIO_RUNTIME_H_
|
||||
|
||||
@@ -19,6 +5,7 @@
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cwchar>
|
||||
|
||||
@@ -31,12 +18,6 @@ namespace mocktail::compat {
|
||||
constexpr size_t kBionicFileSize = 152;
|
||||
constexpr size_t kBionicStandardStreamCount = 3;
|
||||
|
||||
FILE* TranslateBionicFile(FILE* stream) noexcept;
|
||||
void* BionicFileArraySymbolAddress() noexcept;
|
||||
void* BionicStdinSymbolAddress() noexcept;
|
||||
void* BionicStdoutSymbolAddress() noexcept;
|
||||
void* BionicStderrSymbolAddress() noexcept;
|
||||
|
||||
} // namespace mocktail::compat
|
||||
|
||||
extern "C" {
|
||||
@@ -45,8 +26,31 @@ alignas(sizeof(void*)) extern unsigned char
|
||||
__sF[mocktail::compat::kBionicStandardStreamCount]
|
||||
[mocktail::compat::kBionicFileSize];
|
||||
|
||||
} // extern "C"
|
||||
|
||||
namespace mocktail::compat {
|
||||
|
||||
inline FILE* TranslateBionicFile(FILE* stream) noexcept {
|
||||
const uintptr_t address = reinterpret_cast<uintptr_t>(stream);
|
||||
if (__builtin_expect(address == reinterpret_cast<uintptr_t>(&__sF[0]), 0)) {
|
||||
return stdin;
|
||||
}
|
||||
if (__builtin_expect(address == reinterpret_cast<uintptr_t>(&__sF[1]), 0)) {
|
||||
return stdout;
|
||||
}
|
||||
if (__builtin_expect(address == reinterpret_cast<uintptr_t>(&__sF[2]), 0)) {
|
||||
return stderr;
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
void* BionicFileArraySymbolAddress() noexcept;
|
||||
void* BionicStdinSymbolAddress() noexcept;
|
||||
void* BionicStdoutSymbolAddress() noexcept;
|
||||
void* BionicStderrSymbolAddress() noexcept;
|
||||
|
||||
} // namespace mocktail::compat
|
||||
|
||||
// FILE values returned by host fopen/fdopen pass through unchanged. Pointers
|
||||
// into Bionic's ABI-sized __sF array are translated to the corresponding host
|
||||
// standard stream at this single boundary.
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_COMPAT_BIONIC_SYSCONF_H_
|
||||
#define MOCKTAIL_COMPAT_BIONIC_SYSCONF_H_
|
||||
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
#ifndef MOCKTAIL_COMPAT_BUILD_PROFILE_H_
|
||||
#define MOCKTAIL_COMPAT_BUILD_PROFILE_H_
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
@@ -30,6 +25,45 @@ struct FmodOutputDeviceBridgeProfile {
|
||||
std::uintptr_t info_method_rva = 0;
|
||||
std::uintptr_t current_method_rva = 0;
|
||||
std::uintptr_t select_method_rva = 0;
|
||||
// Roblox 2.738 inserted device-list methods before the current/select slots.
|
||||
int vtable_layout_version = 1;
|
||||
|
||||
// Optional exact-build input count/info/current/select methods.
|
||||
std::array<std::uintptr_t, 4> input_method_rvas{};
|
||||
bool has_input_devices() const { return input_method_rvas[0] != 0; }
|
||||
std::array<std::size_t, 4> input_vtable_indexes() const {
|
||||
return vtable_layout_version == 2
|
||||
? std::array<std::size_t, 4>{9, 10, 12, 18}
|
||||
: std::array<std::size_t, 4>{8, 9, 10, 16};
|
||||
}
|
||||
std::array<std::size_t, 6> capture_vtable_indexes() const {
|
||||
return vtable_layout_version == 2
|
||||
? std::array<std::size_t, 6>{4, 13, 14, 15, 16, 17}
|
||||
: std::array<std::size_t, 6>{4, 11, 12, 13, 14, 15};
|
||||
}
|
||||
|
||||
bool valid_vtable_layout() const {
|
||||
return vtable_layout_version == 1 || vtable_layout_version == 2;
|
||||
}
|
||||
std::size_t current_vtable_index() const {
|
||||
return vtable_layout_version == 2 ? 8 : 7;
|
||||
}
|
||||
std::size_t select_vtable_index() const {
|
||||
return vtable_layout_version == 2 ? 19 : 17;
|
||||
}
|
||||
};
|
||||
|
||||
// Exact-build metadata for the experimental native VR bridge. The bridge
|
||||
// interposes DebugDeviceVR slots and a checked world-pointer trampoline.
|
||||
// All RVAs are validated against machine-code contracts before first use.
|
||||
struct VrDebugDeviceBridgeProfile {
|
||||
std::uintptr_t vtable_rva = 0;
|
||||
std::uintptr_t constructor_rva = 0;
|
||||
std::uintptr_t state_getter_rva = 0;
|
||||
std::uintptr_t eye_getter_rva = 0;
|
||||
std::uintptr_t eye_initializer_rva = 0;
|
||||
std::uintptr_t emulator_flag_storage_rva = 0;
|
||||
std::uintptr_t device_create_framebuffer_vtable_offset = 0;
|
||||
};
|
||||
|
||||
struct BuildProfile {
|
||||
@@ -49,6 +83,8 @@ struct BuildProfile {
|
||||
// output-device virtual slots and restores them during controlled teardown;
|
||||
// executable guest code is never modified.
|
||||
std::optional<FmodOutputDeviceBridgeProfile> fmod_output_device_bridge;
|
||||
// Optional exact-build experimental VR capability; see the struct above.
|
||||
std::optional<VrDebugDeviceBridgeProfile> vr_debug_device_bridge;
|
||||
std::string reason;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
#ifndef MOCKTAIL_COMPAT_ELF_BUILD_ID_H_
|
||||
#define MOCKTAIL_COMPAT_ELF_BUILD_ID_H_
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef MOCKTAIL_COMPAT_FMOD_OUTPUT_DEVICE_CONTRACT_H_
|
||||
#define MOCKTAIL_COMPAT_FMOD_OUTPUT_DEVICE_CONTRACT_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <initializer_list>
|
||||
#include <string_view>
|
||||
|
||||
namespace mocktail::compat {
|
||||
|
||||
// The 2.738 integer-index selector adds a feature-flag dispatch before the
|
||||
// count/clamp/setDriver path. Keep the calling convention, count slot (5),
|
||||
// object offset (0x28), and branch into that path exact. Only relocated
|
||||
// addresses and non-local branch/call targets vary between builds.
|
||||
inline constexpr char kFmodDeviceListsSelectContract[] =
|
||||
"\x55\x48\x89\xe5\x41\x57\x41\x56\x41\x54\x53\x48\x83\xec\x50\x41"
|
||||
"\x89\xf7\x49\x89\xfe\x4c\x8b\x25\x00\x00\x00\x00\x49\x8b\x04\x24"
|
||||
"\x48\x89\x45\xd8\x80\x3d\x00\x00\x00\x00\x00\x74\x25\x49\x8b\x04"
|
||||
"\x24\x48\x3b\x45\xd8\x0f\x85\x00\x00\x00\x00\x4c\x89\xf7\x44\x89"
|
||||
"\xfe\x48\x83\xc4\x50\x5b\x41\x5c\x41\x5e\x41\x5f\x5d\xe9\x00\x00"
|
||||
"\x00\x00\x49\x8b\x06\x4c\x89\xf7\xff\x50\x28\x44\x39\xf8\x0f\x8e"
|
||||
"\x00\x00\x00\x00\x31\xdb\x45\x85\xff\x41\x0f\x4f\xdf\x49\x8b\x7e"
|
||||
"\x28\x89\xde\xe8\x00\x00\x00\x00";
|
||||
inline constexpr std::size_t kFmodDeviceListsSelectContractSize =
|
||||
sizeof(kFmodDeviceListsSelectContract) - 1;
|
||||
|
||||
inline bool HasFmodDeviceListsSelectContract(std::string_view code) {
|
||||
if (code.size() < kFmodDeviceListsSelectContractSize) return false;
|
||||
for (std::size_t index = 0; index < kFmodDeviceListsSelectContractSize;
|
||||
++index) {
|
||||
bool relocated = false;
|
||||
for (const std::size_t start : {24, 38, 55, 78, 96, 116}) {
|
||||
if (index >= start && index < start + 4) relocated = true;
|
||||
}
|
||||
if (!relocated && code[index] != kFmodDeviceListsSelectContract[index])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace mocktail::compat
|
||||
|
||||
#endif // MOCKTAIL_COMPAT_FMOD_OUTPUT_DEVICE_CONTRACT_H_
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef MOCKTAIL_COMPAT_GUEST_ABI_H_
|
||||
#define MOCKTAIL_COMPAT_GUEST_ABI_H_
|
||||
|
||||
#include <elf.h>
|
||||
#include <string_view>
|
||||
|
||||
namespace mocktail::compat {
|
||||
|
||||
#if defined(__aarch64__)
|
||||
// Android ABI name as it appears in APK lib/ directories and build metadata.
|
||||
inline constexpr std::string_view kGuestAbi = "arm64-v8a";
|
||||
// APK split-config name carrying the guest native libraries.
|
||||
inline constexpr std::string_view kGuestApkSplitName = "config.arm64_v8a";
|
||||
// File name the payload store uses for the split APK.
|
||||
inline constexpr std::string_view kGuestSplitApkFile =
|
||||
"split_config.arm64_v8a.apk";
|
||||
// sha256 map key holding the split APK digest in payload metadata.
|
||||
inline constexpr std::string_view kGuestSplitApkHashKey = "arm64_v8a_split_apk";
|
||||
// Short CPU label for device metadata and user-facing strings.
|
||||
inline constexpr std::string_view kGuestCpuName = "aarch64";
|
||||
inline constexpr int kGuestElfMachine = EM_AARCH64;
|
||||
#elif defined(__x86_64__)
|
||||
inline constexpr std::string_view kGuestAbi = "x86_64";
|
||||
inline constexpr std::string_view kGuestApkSplitName = "config.x86_64";
|
||||
inline constexpr std::string_view kGuestSplitApkFile = "split_config.x86_64.apk";
|
||||
inline constexpr std::string_view kGuestSplitApkHashKey = "x86_64_split_apk";
|
||||
inline constexpr std::string_view kGuestCpuName = "x86_64";
|
||||
inline constexpr int kGuestElfMachine = EM_X86_64;
|
||||
#else
|
||||
#error "mocktail only supports aarch64 and x86_64 hosts"
|
||||
#endif
|
||||
|
||||
// APK zip entry holding the guest libroblox.so.
|
||||
inline constexpr std::string_view kGuestLibApkEntryPrefix = "lib/";
|
||||
|
||||
} // namespace mocktail::compat
|
||||
|
||||
#endif // MOCKTAIL_COMPAT_GUEST_ABI_H_
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_COMPAT_HOST_ABI_EXPERIMENT_H_
|
||||
#define MOCKTAIL_COMPAT_HOST_ABI_EXPERIMENT_H_
|
||||
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_COMPAT_HOST_ABI_PROFILE_H_
|
||||
#define MOCKTAIL_COMPAT_HOST_ABI_PROFILE_H_
|
||||
|
||||
@@ -123,6 +109,15 @@ struct HostAbiProfile {
|
||||
size_t NativeMimallocConstructorRangeEndExclusive() const noexcept;
|
||||
bool ShouldInitializeNativeMimallocThreadAfterConstructor(
|
||||
size_t index) const noexcept;
|
||||
// True when both replays have the one verified shape. The native replay is
|
||||
// one range that runs to the end of .init_array. Its first four
|
||||
// constructors are the process-load constructor at the thread-initializer
|
||||
// checkpoint, the JNI singleton initializer, and two wiring entries. The
|
||||
// host-bridge replay runs the same constructors without the two wiring
|
||||
// entries. `jni_singleton_constructor` is the .init_array index of the JNI
|
||||
// singleton initializer.
|
||||
bool HasVerifiedConstructorPolicy(
|
||||
size_t jni_singleton_constructor) const noexcept;
|
||||
bool HasValidNativePreJniBootstrap() const noexcept;
|
||||
HostAllocatorStrategy ResolveAllocatorStrategy(
|
||||
bool host_bridges_override_present,
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_COMPAT_HOST_ABI_PROFILE_LOADER_H_
|
||||
#define MOCKTAIL_COMPAT_HOST_ABI_PROFILE_LOADER_H_
|
||||
|
||||
@@ -68,6 +54,8 @@ ExternalHostAbiProfileResult InitializeHostAbiProfileFromEnvironment(
|
||||
const HostAbiProfile* FindLoadedExternalHostAbiProfile(
|
||||
std::string_view build_id) noexcept;
|
||||
|
||||
void SetHostAbiProfileHashingFaultForTesting(bool enabled) noexcept;
|
||||
|
||||
} // namespace mocktail::compat
|
||||
|
||||
#endif // MOCKTAIL_COMPAT_HOST_ABI_PROFILE_LOADER_H_
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_COMPAT_HOST_ALLOCATOR_BRIDGE_H_
|
||||
#define MOCKTAIL_COMPAT_HOST_ALLOCATOR_BRIDGE_H_
|
||||
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_COMPAT_HTTP_CLIENT_SPIN_GUARD_H_
|
||||
#define MOCKTAIL_COMPAT_HTTP_CLIENT_SPIN_GUARD_H_
|
||||
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_COMPAT_PAYLOAD_COMPATIBILITY_H_
|
||||
#define MOCKTAIL_COMPAT_PAYLOAD_COMPATIBILITY_H_
|
||||
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
#ifndef MOCKTAIL_COMPAT_WEB_VIEW_PROTOCOL_CONTRACT_H_
|
||||
#define MOCKTAIL_COMPAT_WEB_VIEW_PROTOCOL_CONTRACT_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
// Roblox's Subsystem<IWebViewProtocol> getter is not exported. The exported
|
||||
// WebViewProtocol.initializeAndroidWebViewProtocol calls it first, into a
|
||||
// stack slot, then reads the protocol from the returned holder:
|
||||
//
|
||||
// x86-64: lea slot(%rbp),%rbx; mov %rbx,%rdi; call getter;
|
||||
// mov (%rbx),%rbx; mov 0x18(%rbx),%rax
|
||||
// AArch64: add x8, sp, #slot; bl getter; ldr xN, [sp, #slot];
|
||||
// ldr x8, [xN, #0x18]
|
||||
//
|
||||
// These functions find that call site in the initializer's first bytes and
|
||||
// check the getter's own code. Only relocated or build-varying fields (stack
|
||||
// slots, frame sizes, PC-relative offsets, the call target) are masked.
|
||||
// Verified on x86_64 2.734.917, 2.736.1408 and 2.738.1397, and on arm64-v8a
|
||||
// 2.738.1397 and 2.740.931.
|
||||
|
||||
namespace mocktail::compat {
|
||||
|
||||
// Covers the call site in every verified build (x86-64 +0x27, AArch64 +0x34).
|
||||
inline constexpr std::size_t kWebViewProtocolInitializerScanBytes = 128;
|
||||
inline constexpr std::size_t kWebViewProtocolGetterBytesX86_64 = 44;
|
||||
inline constexpr std::size_t kWebViewProtocolGetterBytesArm64 = 60;
|
||||
|
||||
namespace web_view_protocol_contract_detail {
|
||||
|
||||
inline bool MatchesMasked(std::string_view code, std::size_t offset,
|
||||
std::string_view pattern,
|
||||
std::string_view wildcard) {
|
||||
if (offset > code.size() || code.size() - offset < pattern.size()) {
|
||||
return false;
|
||||
}
|
||||
for (std::size_t index = 0; index < pattern.size(); ++index) {
|
||||
if (wildcard[index] == 'x') continue;
|
||||
if (code[offset + index] != pattern[index]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// AArch64 instructions are little-endian words.
|
||||
inline std::uint32_t Word(std::string_view code, std::size_t index) {
|
||||
std::uint32_t word = 0;
|
||||
for (std::size_t byte = 0; byte < 4; ++byte) {
|
||||
word |= static_cast<std::uint32_t>(
|
||||
static_cast<unsigned char>(code[index * 4 + byte]))
|
||||
<< (8 * byte);
|
||||
}
|
||||
return word;
|
||||
}
|
||||
|
||||
// One AArch64 instruction word: bits outside `mask` may vary.
|
||||
struct MaskedWord {
|
||||
std::uint32_t mask;
|
||||
std::uint32_t value;
|
||||
};
|
||||
|
||||
inline constexpr std::uint32_t kExact = 0xffffffffU;
|
||||
// adrp Xd, page: immlo and immhi vary.
|
||||
inline constexpr std::uint32_t kAdrpMask = 0x9f00001fU;
|
||||
// add Xd, Xn, #imm12: imm12 varies.
|
||||
inline constexpr std::uint32_t kAddImmediateMask = 0xffc003ffU;
|
||||
|
||||
} // namespace web_view_protocol_contract_detail
|
||||
|
||||
// Returns the getter address called from the x86-64 initializer at
|
||||
// `address`, or nullopt unless the frame setup and exactly one call site
|
||||
// match.
|
||||
inline std::optional<std::uintptr_t> FindWebViewProtocolGetterCallX86_64(
|
||||
std::string_view code, std::uintptr_t address) {
|
||||
using web_view_protocol_contract_detail::MatchesMasked;
|
||||
// push %rbp; mov %rsp,%rbp
|
||||
if (!MatchesMasked(code, 0, "\x55\x48\x89\xe5", "....")) {
|
||||
return std::nullopt;
|
||||
}
|
||||
static constexpr std::string_view kCallSite(
|
||||
"\x48\x8d\x5d\x00\x48\x89\xdf\xe8\x00\x00\x00\x00\x48\x8b\x1b\x48\x8b\x43"
|
||||
"\x18",
|
||||
19);
|
||||
static constexpr std::string_view kCallSiteWildcard("...x....xxxx.......");
|
||||
static constexpr std::size_t kCallDisplacement = 8;
|
||||
const std::string_view window =
|
||||
code.substr(0, kWebViewProtocolInitializerScanBytes);
|
||||
std::optional<std::size_t> call_site;
|
||||
for (std::size_t offset = 0; offset < window.size(); ++offset) {
|
||||
if (MatchesMasked(window, offset, kCallSite, kCallSiteWildcard)) {
|
||||
if (call_site.has_value()) return std::nullopt;
|
||||
call_site = offset;
|
||||
}
|
||||
}
|
||||
if (!call_site.has_value()) return std::nullopt;
|
||||
std::int32_t displacement = 0;
|
||||
std::memcpy(&displacement, window.data() + *call_site + kCallDisplacement,
|
||||
sizeof(displacement));
|
||||
const std::uintptr_t next_instruction =
|
||||
address + *call_site + kCallDisplacement + sizeof(displacement);
|
||||
return next_instruction +
|
||||
static_cast<std::uintptr_t>(static_cast<std::intptr_t>(displacement));
|
||||
}
|
||||
|
||||
// The x86-64 getter stores the holder returned by the subsystem's acquire
|
||||
// into its result slot (%rdi) and returns that slot.
|
||||
inline bool HasWebViewProtocolGetterContractX86_64(std::string_view code) {
|
||||
return web_view_protocol_contract_detail::MatchesMasked(
|
||||
code, 0,
|
||||
std::string_view(
|
||||
"\x55\x48\x89\xe5\x53\x50\x48\x89\xfb\x8a\x05\x00\x00\x00\x00\x84\xc0"
|
||||
"\x74\x19\x48\x8d\x3d\x00\x00\x00\x00\xe8\x59\x00\x00\x00\x48\x89\x03"
|
||||
"\x48\x89\xd8\x48\x83\xc4\x08\x5b\x5d\xc3",
|
||||
kWebViewProtocolGetterBytesX86_64),
|
||||
"...........xxxx.......xxxx..................");
|
||||
}
|
||||
|
||||
// Returns the getter address called from the AArch64 initializer at
|
||||
// `address`, or nullopt unless exactly one call site matches.
|
||||
inline std::optional<std::uintptr_t> FindWebViewProtocolGetterCallArm64(
|
||||
std::string_view code, std::uintptr_t address) {
|
||||
using web_view_protocol_contract_detail::Word;
|
||||
const std::size_t words =
|
||||
code.substr(0, kWebViewProtocolInitializerScanBytes).size() / 4;
|
||||
std::optional<std::uintptr_t> getter;
|
||||
for (std::size_t index = 0; index + 4 <= words; ++index) {
|
||||
const std::uint32_t slot_address = Word(code, index);
|
||||
const std::uint32_t call = Word(code, index + 1);
|
||||
const std::uint32_t handle_load = Word(code, index + 2);
|
||||
const std::uint32_t protocol_load = Word(code, index + 3);
|
||||
// add x8, sp, #slot
|
||||
if ((slot_address & 0xffc003ffU) != 0x910003e8U) continue;
|
||||
// bl getter
|
||||
if ((call & 0xfc000000U) != 0x94000000U) continue;
|
||||
// ldr xN, [sp, #slot]
|
||||
if ((handle_load & 0xffc003e0U) != 0xf94003e0U) continue;
|
||||
const std::uint32_t slot = (slot_address >> 10) & 0xfffU;
|
||||
if (((handle_load >> 10) & 0xfffU) * 8 != slot) continue;
|
||||
// ldr x8, [xN, #0x18]
|
||||
const std::uint32_t handle_register = handle_load & 0x1fU;
|
||||
if (protocol_load != (0xf9400c08U | (handle_register << 5))) continue;
|
||||
if (getter.has_value()) return std::nullopt;
|
||||
// imm26 is a signed word offset from the bl instruction.
|
||||
const std::int64_t imm26 = call & 0x03ffffffU;
|
||||
const std::int64_t words_from_call =
|
||||
(imm26 & 0x02000000) != 0 ? imm26 - 0x04000000 : imm26;
|
||||
getter = address + (index + 1) * 4 +
|
||||
static_cast<std::uintptr_t>(words_from_call * 4);
|
||||
}
|
||||
return getter;
|
||||
}
|
||||
|
||||
// The AArch64 getter saves its result slot (x8) and stores the holder
|
||||
// returned by the subsystem's acquire there.
|
||||
inline bool HasWebViewProtocolGetterContractArm64(std::string_view code) {
|
||||
using namespace web_view_protocol_contract_detail;
|
||||
static constexpr MaskedWord kGetter[] = {
|
||||
{kExact, 0xa9be7bfdU}, // stp x29, x30, [sp, #-0x20]!
|
||||
{kExact, 0xf9000bf3U}, // str x19, [sp, #0x10]
|
||||
{kExact, 0x910003fdU}, // mov x29, sp
|
||||
{kAdrpMask, 0x90000009U}, // adrp x9, guard
|
||||
{kAddImmediateMask, 0x91000129U}, // add x9, x9, :lo12:guard
|
||||
{kExact, 0xaa0803f3U}, // mov x19, x8
|
||||
{kExact, 0x08dffd29U}, // ldarb w9, [x9]
|
||||
{kExact, 0x36000109U}, // tbz w9, #0, initialize
|
||||
{kAdrpMask, 0x90000000U}, // adrp x0, holder
|
||||
{kAddImmediateMask, 0x91000000U}, // add x0, x0, :lo12:holder
|
||||
{kExact, 0x94000017U}, // bl acquire
|
||||
{kExact, 0xf9000260U}, // str x0, [x19]
|
||||
{kExact, 0xf9400bf3U}, // ldr x19, [sp, #0x10]
|
||||
{kExact, 0xa8c27bfdU}, // ldp x29, x30, [sp], #0x20
|
||||
{kExact, 0xd65f03c0U}, // ret
|
||||
};
|
||||
static_assert(sizeof(kGetter) / sizeof(kGetter[0]) * 4 ==
|
||||
kWebViewProtocolGetterBytesArm64);
|
||||
if (code.size() < kWebViewProtocolGetterBytesArm64) return false;
|
||||
for (std::size_t index = 0; index < sizeof(kGetter) / sizeof(kGetter[0]);
|
||||
++index) {
|
||||
if ((Word(code, index) & kGetter[index].mask) != kGetter[index].value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace mocktail::compat
|
||||
|
||||
#endif // MOCKTAIL_COMPAT_WEB_VIEW_PROTOCOL_CONTRACT_H_
|
||||
+184
-18
@@ -1,19 +1,5 @@
|
||||
// Copyright 2026 Sober Test Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef SOBER_TEST_JNIVM_JNIVM_H_
|
||||
#define SOBER_TEST_JNIVM_JNIVM_H_
|
||||
#ifndef MOCKTAIL_JNIVM_JNIVM_H_
|
||||
#define MOCKTAIL_JNIVM_JNIVM_H_
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
@@ -62,9 +48,15 @@ struct PlatformIdentity {
|
||||
std::string manufacturer = "Mocktail";
|
||||
std::string model = "Mocktail Headless";
|
||||
std::string brand = "Mocktail";
|
||||
#if defined(__aarch64__)
|
||||
std::string device_code = "linux-arm64";
|
||||
std::string device_sku = "mocktail-arm64";
|
||||
std::string soc_model = "aarch64";
|
||||
#else
|
||||
std::string device_code = "linux-x86_64";
|
||||
std::string device_sku = "mocktail-x86_64";
|
||||
std::string soc_model = "x86_64";
|
||||
#endif
|
||||
};
|
||||
|
||||
struct RobloxCredentialView {
|
||||
@@ -73,6 +65,8 @@ struct RobloxCredentialView {
|
||||
};
|
||||
|
||||
using RobloxCredentialProvider = RobloxCredentialView (*)(const void *context);
|
||||
using RobloxCookieGetter = jstring (*)(JNIEnv* env, jclass clazz,
|
||||
jstring domain);
|
||||
|
||||
jobject CreateAndroidConfiguration(JNIEnv *env);
|
||||
|
||||
@@ -94,6 +88,67 @@ struct FmodAudioDeviceCallbacks {
|
||||
void (*shutdown)(void *context) = nullptr;
|
||||
};
|
||||
|
||||
// Parameters passed by WebRtcAudioManager's constructor to the guest's
|
||||
// nativeCacheAudioParameters(IIIZZZZZZZIIJ)V. Buffer sizes are PCM frames,
|
||||
// not bytes. Querying them must not open a microphone.
|
||||
struct WebRtcAudioManagerParameters {
|
||||
int sample_rate_hz = 0;
|
||||
int output_channels = 0;
|
||||
int input_channels = 0;
|
||||
bool hardware_aec = false;
|
||||
bool hardware_agc = false;
|
||||
bool hardware_ns = false;
|
||||
bool low_latency_output = false;
|
||||
bool low_latency_input = false;
|
||||
bool pro_audio = false;
|
||||
bool aaudio = false;
|
||||
int output_buffer_size_frames = 0;
|
||||
int input_buffer_size_frames = 0;
|
||||
};
|
||||
|
||||
struct WebRtcAudioManagerCallbacks {
|
||||
bool (*get_parameters)(void* context,
|
||||
WebRtcAudioManagerParameters* parameters) = nullptr;
|
||||
bool (*init)(void* context, const void* identity) = nullptr;
|
||||
void (*dispose)(void* context, const void* identity) = nullptr;
|
||||
void (*set_microphone_mute)(void* context, bool muted) = nullptr;
|
||||
};
|
||||
|
||||
using WebRtcAudioRecordDataCallback = void (*)(void *context,
|
||||
const void *identity,
|
||||
std::size_t size_bytes);
|
||||
|
||||
// Host implementation of org/webrtc/voiceengine/WebRtcAudioRecord. Init
|
||||
// returns the number of PCM frames in the stable direct buffer, or -1.
|
||||
struct WebRtcAudioRecordCallbacks {
|
||||
int (*init)(void *context, const void *identity, int sample_rate_hz,
|
||||
int channels, WebRtcAudioRecordDataCallback data_callback,
|
||||
void *data_context, void **direct_buffer,
|
||||
std::size_t *direct_buffer_capacity) = nullptr;
|
||||
bool (*start)(void *context, const void *identity) = nullptr;
|
||||
bool (*stop)(void *context, const void *identity) = nullptr;
|
||||
void (*close)(void *context, const void *identity) = nullptr;
|
||||
void (*shutdown)(void *context) = nullptr;
|
||||
};
|
||||
|
||||
using WebRtcAudioTrackDataCallback = void (*)(void *context,
|
||||
const void *identity,
|
||||
std::size_t size_bytes);
|
||||
|
||||
// Host implementation of org/webrtc/voiceengine/WebRtcAudioTrack. Init
|
||||
// returns the emulated Android AudioTrack buffer size in bytes, or -1.
|
||||
struct WebRtcAudioTrackCallbacks {
|
||||
int (*init)(void *context, const void *identity, int sample_rate_hz,
|
||||
int channels, double buffer_size_factor,
|
||||
WebRtcAudioTrackDataCallback data_callback, void *data_context,
|
||||
void **direct_buffer,
|
||||
std::size_t *direct_buffer_capacity) = nullptr;
|
||||
int (*buffer_size_frames)(void *context, const void *identity) = nullptr;
|
||||
bool (*start)(void *context, const void *identity) = nullptr;
|
||||
bool (*stop)(void *context, const void *identity) = nullptr;
|
||||
void (*close)(void *context, const void *identity) = nullptr;
|
||||
};
|
||||
|
||||
// Guest JNI threads only publish Android window flags. The host callback must
|
||||
// marshal SDL work onto the main thread.
|
||||
struct AndroidWindowCallbacks {
|
||||
@@ -144,6 +199,11 @@ struct MessageBusRequestHandlerCallbacks {
|
||||
std::string (*run)(void *context, JNIEnv *env, jstring message) = nullptr;
|
||||
};
|
||||
|
||||
struct MessageBusAsyncRequestHandlerCallbacks {
|
||||
void (*run)(void* context, JNIEnv* env, jstring message,
|
||||
jstring response_id) = nullptr;
|
||||
};
|
||||
|
||||
// Dispatch accepts Callback.onItemSet(String) only on a receiver created by
|
||||
// this VM. The context stays alive while the callback runs.
|
||||
struct MemStorageCallbackCallbacks {
|
||||
@@ -208,10 +268,12 @@ public:
|
||||
|
||||
JavaVM *GetJavaVM() { return java_vm_; }
|
||||
|
||||
// Resolves the process pseudo-VM that owns an exact JavaVM pointer. Returns
|
||||
// Resolves the live pseudo-VM that owns an exact JavaVM pointer. Returns
|
||||
// null for foreign VMs and after the owner is destroyed.
|
||||
static VM *FromJavaVM(JavaVM *java_vm);
|
||||
|
||||
// Binds this thread's JNI environment to this VM. Switching owners must
|
||||
// not reuse the previous VM's native function table.
|
||||
JNIEnv *GetJNIEnv();
|
||||
|
||||
std::size_t GetClassCount() const { return class_registry_.size(); }
|
||||
@@ -242,6 +304,9 @@ public:
|
||||
// retained privately and returned instead until the provider is cleared.
|
||||
bool CopyRobloxCredentialFromProvider(std::string *credential) const;
|
||||
|
||||
void SetRobloxCookieGetter(RobloxCookieGetter getter);
|
||||
bool RefreshRobloxCredentialFromEngine(JNIEnv* env);
|
||||
|
||||
void SetRobloxCredentialSink(std::shared_ptr<void> context,
|
||||
const RobloxCredentialSinkCallbacks &callbacks);
|
||||
void ClearRobloxCredentialSink();
|
||||
@@ -264,6 +329,55 @@ public:
|
||||
const std::uint8_t *data, std::size_t size);
|
||||
bool DispatchFmodAudioDeviceClose(const void *identity);
|
||||
|
||||
void SetWebRtcAudioManagerCallbacks(
|
||||
std::shared_ptr<void> context,
|
||||
const WebRtcAudioManagerCallbacks& callbacks);
|
||||
void ClearWebRtcAudioManagerCallbacks();
|
||||
void RegisterWebRtcAudioManagerNative(const char* name, const char* signature,
|
||||
void* function);
|
||||
bool DispatchWebRtcAudioManagerConstruct(jobject manager,
|
||||
jlong native_audio_manager);
|
||||
bool DispatchWebRtcAudioManagerInit(jobject manager);
|
||||
void DispatchWebRtcAudioManagerDispose(jobject manager);
|
||||
void DispatchWebRtcAudioManagerMicrophoneMute(jobject manager, bool muted);
|
||||
|
||||
void
|
||||
SetWebRtcAudioRecordCallbacks(std::shared_ptr<void> context,
|
||||
const WebRtcAudioRecordCallbacks &callbacks);
|
||||
void ClearWebRtcAudioRecordCallbacks();
|
||||
int DispatchWebRtcAudioRecordInit(const void *identity, int sample_rate_hz,
|
||||
int channels, void **direct_buffer,
|
||||
std::size_t *direct_buffer_capacity);
|
||||
bool DispatchWebRtcAudioRecordStart(const void *identity);
|
||||
bool DispatchWebRtcAudioRecordStop(const void *identity);
|
||||
void DispatchWebRtcAudioRecordClose(const void *identity);
|
||||
void DispatchWebRtcAudioRecordData(const void *identity,
|
||||
std::size_t size_bytes);
|
||||
|
||||
// RegisterNatives stores these exact WebRTC callbacks so the host capture
|
||||
// thread can emulate the Java AudioRecord thread.
|
||||
void RegisterWebRtcAudioRecordNative(const char *name, const char *signature,
|
||||
void *function);
|
||||
|
||||
void SetWebRtcAudioTrackCallbacks(std::shared_ptr<void> context,
|
||||
const WebRtcAudioTrackCallbacks &callbacks);
|
||||
void ClearWebRtcAudioTrackCallbacks();
|
||||
int DispatchWebRtcAudioTrackInit(const void *identity, int sample_rate_hz,
|
||||
int channels, double buffer_size_factor,
|
||||
void **direct_buffer,
|
||||
std::size_t *direct_buffer_capacity);
|
||||
int DispatchWebRtcAudioTrackBufferSizeFrames(const void *identity);
|
||||
bool DispatchWebRtcAudioTrackStart(const void *identity);
|
||||
bool DispatchWebRtcAudioTrackStop(const void *identity);
|
||||
void DispatchWebRtcAudioTrackClose(const void *identity);
|
||||
void DispatchWebRtcAudioTrackData(const void *identity,
|
||||
std::size_t size_bytes);
|
||||
|
||||
// RegisterNatives stores the exact WebRTC playout callbacks so the host
|
||||
// thread can emulate AudioTrackJavaThread.
|
||||
void RegisterWebRtcAudioTrackNative(const char *name, const char *signature,
|
||||
void *function);
|
||||
|
||||
void SetAndroidWindowCallbacks(std::shared_ptr<void> context,
|
||||
const AndroidWindowCallbacks &callbacks);
|
||||
void ClearAndroidWindowCallbacks();
|
||||
@@ -292,6 +406,16 @@ public:
|
||||
jstring DispatchMessageBusRequestHandler(jobject handler, JNIEnv *env,
|
||||
jstring message);
|
||||
|
||||
// Exact RequestHandlerAsyncRaw.run(String, String) receiver. Clearing stops
|
||||
// new calls; an in-flight call retains the host context until it returns.
|
||||
jobject CreateMessageBusAsyncRequestHandler(
|
||||
std::shared_ptr<void> context,
|
||||
const MessageBusAsyncRequestHandlerCallbacks& callbacks);
|
||||
void ClearMessageBusAsyncRequestHandler(jobject handler);
|
||||
bool DispatchMessageBusAsyncRequestHandler(jobject handler, JNIEnv* env,
|
||||
jstring message,
|
||||
jstring response_id);
|
||||
|
||||
// Creates the exact com.roblox.engine.jni.memstorage.Callback receiver used
|
||||
// by MemStorage.bind. Clear prevents future dispatch while an in-flight call
|
||||
// retains its shared context until the callback returns.
|
||||
@@ -352,6 +476,9 @@ private:
|
||||
RobloxCredentialProvider roblox_credential_provider_ = nullptr;
|
||||
std::string roblox_credential_override_;
|
||||
|
||||
mutable std::mutex roblox_cookie_getter_mutex_;
|
||||
RobloxCookieGetter roblox_cookie_getter_ = nullptr;
|
||||
|
||||
struct RobloxCredentialSinkBinding {
|
||||
std::shared_ptr<void> context;
|
||||
RobloxCredentialSinkCallbacks callbacks;
|
||||
@@ -366,6 +493,36 @@ private:
|
||||
mutable std::mutex fmod_audio_device_mutex_;
|
||||
FmodAudioDeviceBinding fmod_audio_device_binding_;
|
||||
|
||||
struct WebRtcAudioManagerBinding {
|
||||
std::shared_ptr<void> context;
|
||||
WebRtcAudioManagerCallbacks callbacks;
|
||||
};
|
||||
mutable std::mutex webrtc_audio_manager_mutex_;
|
||||
WebRtcAudioManagerBinding webrtc_audio_manager_binding_;
|
||||
void* webrtc_cache_audio_parameters_native_ = nullptr;
|
||||
|
||||
struct WebRtcAudioRecordBinding {
|
||||
std::shared_ptr<void> context;
|
||||
WebRtcAudioRecordCallbacks callbacks;
|
||||
};
|
||||
mutable std::mutex webrtc_audio_record_mutex_;
|
||||
WebRtcAudioRecordBinding webrtc_audio_record_binding_;
|
||||
|
||||
mutable std::mutex webrtc_audio_record_native_mutex_;
|
||||
void *webrtc_cache_direct_buffer_native_ = nullptr;
|
||||
void *webrtc_data_is_recorded_native_ = nullptr;
|
||||
|
||||
struct WebRtcAudioTrackBinding {
|
||||
std::shared_ptr<void> context;
|
||||
WebRtcAudioTrackCallbacks callbacks;
|
||||
};
|
||||
mutable std::mutex webrtc_audio_track_mutex_;
|
||||
WebRtcAudioTrackBinding webrtc_audio_track_binding_;
|
||||
|
||||
mutable std::mutex webrtc_audio_track_native_mutex_;
|
||||
void *webrtc_track_cache_direct_buffer_native_ = nullptr;
|
||||
void *webrtc_get_playout_data_native_ = nullptr;
|
||||
|
||||
struct AndroidWindowBinding {
|
||||
std::shared_ptr<void> context;
|
||||
AndroidWindowCallbacks callbacks;
|
||||
@@ -393,6 +550,15 @@ private:
|
||||
std::unordered_map<jobject, std::shared_ptr<MessageBusRequestHandlerBinding>>
|
||||
message_bus_request_handler_bindings_;
|
||||
|
||||
struct MessageBusAsyncRequestHandlerBinding {
|
||||
std::shared_ptr<void> context;
|
||||
MessageBusAsyncRequestHandlerCallbacks callbacks;
|
||||
};
|
||||
mutable std::mutex message_bus_async_request_handler_mutex_;
|
||||
std::unordered_map<jobject,
|
||||
std::shared_ptr<MessageBusAsyncRequestHandlerBinding>>
|
||||
message_bus_async_request_handler_bindings_;
|
||||
|
||||
struct MemStorageCallbackBinding {
|
||||
std::shared_ptr<void> context;
|
||||
MemStorageCallbackCallbacks callbacks;
|
||||
@@ -422,4 +588,4 @@ private:
|
||||
|
||||
} // namespace jnivm
|
||||
|
||||
#endif // SOBER_TEST_JNIVM_JNIVM_H_
|
||||
#endif // MOCKTAIL_JNIVM_JNIVM_H_
|
||||
@@ -1,11 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
#ifndef MOCKTAIL_LEGACY_LEGACY_RUNTIME_H_
|
||||
#define MOCKTAIL_LEGACY_LEGACY_RUNTIME_H_
|
||||
|
||||
@@ -33,6 +25,8 @@ class RuntimeDependencies final {
|
||||
: jni_vm_(std::move(composition.jni_vm)),
|
||||
account_identity_(std::move(composition.account_identity)),
|
||||
roblox_credential_(std::move(composition.credential)),
|
||||
clear_persisted_web_view_cookie_(
|
||||
composition.rejected_credential_retired),
|
||||
shutdown_before_platform_(shutdown_before_platform) {}
|
||||
~RuntimeDependencies() {
|
||||
if (jni_vm_ != nullptr && shutdown_before_platform_ != nullptr) {
|
||||
@@ -46,6 +40,8 @@ class RuntimeDependencies final {
|
||||
: jni_vm_(std::move(other.jni_vm_)),
|
||||
account_identity_(std::move(other.account_identity_)),
|
||||
roblox_credential_(std::move(other.roblox_credential_)),
|
||||
clear_persisted_web_view_cookie_(std::exchange(
|
||||
other.clear_persisted_web_view_cookie_, false)),
|
||||
shutdown_before_platform_(
|
||||
std::exchange(other.shutdown_before_platform_, nullptr)) {}
|
||||
RuntimeDependencies& operator=(RuntimeDependencies&& other) noexcept {
|
||||
@@ -58,6 +54,8 @@ class RuntimeDependencies final {
|
||||
jni_vm_ = std::move(other.jni_vm_);
|
||||
account_identity_ = std::move(other.account_identity_);
|
||||
roblox_credential_ = std::move(other.roblox_credential_);
|
||||
clear_persisted_web_view_cookie_ =
|
||||
std::exchange(other.clear_persisted_web_view_cookie_, false);
|
||||
shutdown_before_platform_ =
|
||||
std::exchange(other.shutdown_before_platform_, nullptr);
|
||||
return *this;
|
||||
@@ -70,6 +68,9 @@ class RuntimeDependencies final {
|
||||
const runtime::SecureRobloxCredential& roblox_credential() const {
|
||||
return roblox_credential_;
|
||||
}
|
||||
bool clear_persisted_web_view_cookie() const {
|
||||
return clear_persisted_web_view_cookie_;
|
||||
}
|
||||
|
||||
// Transitional teardown boundary. Runtime-owned subsystems release
|
||||
// SDL resources here after guest workers stop and before window shutdown.
|
||||
@@ -83,6 +84,7 @@ class RuntimeDependencies final {
|
||||
std::shared_ptr<jnivm::VM> jni_vm_;
|
||||
jnivm::RobloxAuthIdentity account_identity_;
|
||||
runtime::SecureRobloxCredential roblox_credential_;
|
||||
bool clear_persisted_web_view_cookie_ = false;
|
||||
ShutdownBeforePlatformCallback shutdown_before_platform_ = nullptr;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,19 +1,5 @@
|
||||
// Copyright 2026 Sober Test Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef SOBER_TEST_LIBC_SHIM_LIBC_SHIM_H_
|
||||
#define SOBER_TEST_LIBC_SHIM_LIBC_SHIM_H_
|
||||
#ifndef MOCKTAIL_LIBC_SHIM_LIBC_SHIM_H_
|
||||
#define MOCKTAIL_LIBC_SHIM_LIBC_SHIM_H_
|
||||
|
||||
#include <cstdio>
|
||||
#include <dirent.h>
|
||||
@@ -94,4 +80,4 @@ ssize_t mocktail___readlink_chk(const char* path, char* buf, size_t len,
|
||||
size_t buf_len);
|
||||
}
|
||||
|
||||
#endif // SOBER_TEST_LIBC_SHIM_LIBC_SHIM_H_
|
||||
#endif // MOCKTAIL_LIBC_SHIM_LIBC_SHIM_H_
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef MOCKTAIL_LIBC_SHIM_VULKAN_ETC1_SKY_TRANSCODER_H_
|
||||
#define MOCKTAIL_LIBC_SHIM_VULKAN_ETC1_SKY_TRANSCODER_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace libc_shim {
|
||||
|
||||
// Converts one opaque ETC1 RGB block to an opaque BC1 RGB block.
|
||||
void ConvertEtc1RgbBlockToBc1(const uint8_t *etc1, uint8_t *bc1);
|
||||
|
||||
bool TranscodeEtc1SkyTextureForVulkan(const char *path,
|
||||
std::vector<unsigned char> *data);
|
||||
|
||||
} // namespace libc_shim
|
||||
|
||||
#endif // MOCKTAIL_LIBC_SHIM_VULKAN_ETC1_SKY_TRANSCODER_H_
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_LINKER_LINKER_H_
|
||||
#define MOCKTAIL_LINKER_LINKER_H_
|
||||
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_AUDIO_AUDIO_SINK_H_
|
||||
#define MOCKTAIL_AUDIO_AUDIO_SINK_H_
|
||||
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_AUDIO_FMOD_JAVA_AUDIO_RUNTIME_H_
|
||||
#define MOCKTAIL_AUDIO_FMOD_JAVA_AUDIO_RUNTIME_H_
|
||||
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_AUDIO_FMOD_JNI_AUDIO_BRIDGE_H_
|
||||
#define MOCKTAIL_AUDIO_FMOD_JNI_AUDIO_BRIDGE_H_
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
#ifndef MOCKTAIL_AUDIO_FMOD_THREAD_FLOATING_POINT_H_
|
||||
#define MOCKTAIL_AUDIO_FMOD_THREAD_FLOATING_POINT_H_
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#if defined(__x86_64__)
|
||||
#include <xmmintrin.h>
|
||||
#endif
|
||||
#if defined(__aarch64__)
|
||||
#include <cstdint>
|
||||
#endif
|
||||
|
||||
namespace mocktail::audio {
|
||||
|
||||
// FMOD's Android threads identify themselves through JavaVMAttachArgs before
|
||||
// entering their mix loop. Voice DSP filters can accumulate subnormal values
|
||||
// and spend an entire x86 core on floating-point assists (issue #128).
|
||||
// Limit FTZ/DAZ to these audio threads; retain the caller's other FP controls.
|
||||
class FmodThreadFloatingPointMode final {
|
||||
public:
|
||||
FmodThreadFloatingPointMode() = default;
|
||||
~FmodThreadFloatingPointMode() { Restore(); }
|
||||
FmodThreadFloatingPointMode(const FmodThreadFloatingPointMode&) = delete;
|
||||
FmodThreadFloatingPointMode& operator=(const FmodThreadFloatingPointMode&) =
|
||||
delete;
|
||||
|
||||
bool Enable(const char* thread_name) noexcept {
|
||||
#if defined(__x86_64__) || defined(__aarch64__)
|
||||
if (active_ || thread_name == nullptr) return false;
|
||||
const std::string_view name(thread_name);
|
||||
if (name != "FMOD mixer thread" && name != "FMOD feeder thread" &&
|
||||
name != "FMOD Convolution thread" && name != "FMOD Worker Thread") {
|
||||
return false;
|
||||
}
|
||||
#if defined(__x86_64__)
|
||||
const unsigned int control = _mm_getcsr();
|
||||
original_mode_ = control & kModeMask;
|
||||
_mm_setcsr(control | kModeMask);
|
||||
#else
|
||||
std::uint64_t control;
|
||||
__asm__ volatile("mrs %0, fpcr" : "=r"(control));
|
||||
original_mode_ = control & kModeMask;
|
||||
__asm__ volatile("msr fpcr, %0" ::"r"(control | kModeMask));
|
||||
#endif
|
||||
active_ = true;
|
||||
return true;
|
||||
#else
|
||||
(void)thread_name;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Restore() noexcept {
|
||||
#if defined(__x86_64__) || defined(__aarch64__)
|
||||
if (active_) {
|
||||
// Restore only our two bits, preserving rounding, exception masks and
|
||||
// status flags set by the guest while the thread was attached.
|
||||
#if defined(__x86_64__)
|
||||
_mm_setcsr((_mm_getcsr() & ~kModeMask) | original_mode_);
|
||||
#else
|
||||
std::uint64_t control;
|
||||
__asm__ volatile("mrs %0, fpcr" : "=r"(control));
|
||||
__asm__ volatile("msr fpcr, %0" ::"r"((control & ~kModeMask) |
|
||||
original_mode_));
|
||||
#endif
|
||||
active_ = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
#if defined(__x86_64__)
|
||||
static constexpr unsigned int kModeMask = (1U << 15) | (1U << 6);
|
||||
unsigned int original_mode_ = 0;
|
||||
bool active_ = false;
|
||||
#elif defined(__aarch64__)
|
||||
// FPCR FZ flushes single/double subnormals; FZ16 covers the half-precision
|
||||
// path used by some voice DSP filters.
|
||||
static constexpr std::uint64_t kModeMask =
|
||||
(std::uint64_t{1} << 24) | (std::uint64_t{1} << 19);
|
||||
std::uint64_t original_mode_ = 0;
|
||||
bool active_ = false;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace mocktail::audio
|
||||
|
||||
#endif // MOCKTAIL_AUDIO_FMOD_THREAD_FLOATING_POINT_H_
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_AUDIO_OPENSL_ABI_H_
|
||||
#define MOCKTAIL_AUDIO_OPENSL_ABI_H_
|
||||
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_AUDIO_OPENSL_PLAYBACK_RUNTIME_H_
|
||||
#define MOCKTAIL_AUDIO_OPENSL_PLAYBACK_RUNTIME_H_
|
||||
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_AUDIO_OPENSL_RUNTIME_ABI_H_
|
||||
#define MOCKTAIL_AUDIO_OPENSL_RUNTIME_ABI_H_
|
||||
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_AUDIO_OPENSL_SIMPLE_BUFFER_QUEUE_H_
|
||||
#define MOCKTAIL_AUDIO_OPENSL_SIMPLE_BUFFER_QUEUE_H_
|
||||
|
||||
|
||||
@@ -1,23 +1,10 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_AUDIO_ROBLOX_OUTPUT_DEVICE_BRIDGE_H_
|
||||
#define MOCKTAIL_AUDIO_ROBLOX_OUTPUT_DEVICE_BRIDGE_H_
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
@@ -27,13 +14,15 @@
|
||||
|
||||
namespace mocktail::audio {
|
||||
|
||||
// Replaces only the output-device query/select slots of the exact
|
||||
class NativeInputCapture;
|
||||
|
||||
// Replaces the profiled input/output-device query/select slots of the exact
|
||||
// Build-ID-scoped FmodAudioDevice vtable. Roblox keeps owning its FMOD engine,
|
||||
// while the existing settings UI sees and selects the SDL host routes that
|
||||
// consume Android AudioTrack PCM.
|
||||
class RobloxOutputDeviceBridge final {
|
||||
public:
|
||||
RobloxOutputDeviceBridge() = default;
|
||||
RobloxOutputDeviceBridge();
|
||||
~RobloxOutputDeviceBridge();
|
||||
|
||||
RobloxOutputDeviceBridge(const RobloxOutputDeviceBridge&) = delete;
|
||||
@@ -55,23 +44,34 @@ class RobloxOutputDeviceBridge final {
|
||||
std::string_view logical_name,
|
||||
std::uintptr_t image_base);
|
||||
static int GetOutputDeviceCount(void* self);
|
||||
static void* GetOutputDeviceInfo(void* result, void* self, int index);
|
||||
// A trivial aggregate lets the compiler implement the guest's platform
|
||||
// sret convention (x8 on AArch64, hidden first argument on x86-64).
|
||||
struct alignas(8) DeviceInfoResult { std::uint8_t bytes[56]{}; };
|
||||
static DeviceInfoResult GetOutputDeviceInfo(void* self, int index);
|
||||
static int GetCurrentOutputDevice(void* self);
|
||||
static void SetCurrentOutputDevice(void* self, int index);
|
||||
|
||||
Status Activate(std::uintptr_t image_base);
|
||||
Status PatchVtableLocked();
|
||||
bool RestoreVtableLocked();
|
||||
int DeviceCount();
|
||||
void* DeviceInfo(void* result, int index);
|
||||
int CurrentDevice();
|
||||
void SelectDevice(int index);
|
||||
static int GetInputDeviceCount(void *self);
|
||||
static DeviceInfoResult GetInputDeviceInfo(void* self, int index);
|
||||
static int GetCurrentInputDevice(void *self);
|
||||
static void SetCurrentInputDevice(void *self, int index);
|
||||
int DeviceCount(bool input = false);
|
||||
void *DeviceInfo(void *result, int index, bool input = false);
|
||||
int CurrentDevice(bool input = false);
|
||||
void SelectDevice(int index, bool input = false);
|
||||
void ConstructGuestString(void* destination, std::string_view value) const;
|
||||
|
||||
std::shared_ptr<NativeInputCapture> capture_;
|
||||
std::array<std::uintptr_t, 6> original_capture_methods_{};
|
||||
mutable std::mutex mutex_;
|
||||
compat::FmodOutputDeviceBridgeProfile profile_;
|
||||
std::vector<MenuDevice> devices_;
|
||||
std::array<std::uintptr_t, 4> original_methods_{};
|
||||
std::vector<MenuDevice> input_devices_;
|
||||
int selected_input_index_ = 0;
|
||||
std::array<std::uintptr_t, 8> original_methods_{};
|
||||
std::uintptr_t library_base_ = 0;
|
||||
std::uintptr_t* vtable_ = nullptr;
|
||||
void* string_constructor_ = nullptr;
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
#ifndef MOCKTAIL_AUDIO_SDL_AUDIO_CAPTURE_H_
|
||||
#define MOCKTAIL_AUDIO_SDL_AUDIO_CAPTURE_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "mocktail/audio/audio_sink.h"
|
||||
|
||||
namespace mocktail::audio {
|
||||
|
||||
struct SdlRecordingDevice {
|
||||
std::uint32_t id = 0;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
// The virtual `default` target is not returned. Device IDs are process-local;
|
||||
// names are the stable configuration choice, while `id:<number>` disambiguates
|
||||
// duplicate names for the current host session.
|
||||
Status ListSdlRecordingDevices(std::vector<SdlRecordingDevice>* devices);
|
||||
|
||||
Status ResolveSdlRecordingDevice(
|
||||
std::string_view requested,
|
||||
const std::vector<SdlRecordingDevice>& available_devices,
|
||||
std::uint32_t* recording_device_id, std::string* resolved_name);
|
||||
|
||||
// Must run after SDL audio initialization and before the first capture opens.
|
||||
Status ConfigureSdlRecordingDevice(
|
||||
std::string_view requested,
|
||||
std::vector<SdlRecordingDevice>* available_devices,
|
||||
std::string* resolved_name);
|
||||
|
||||
Status GetConfiguredSdlRecordingDevice(std::uint32_t* recording_device_id,
|
||||
std::string* resolved_name);
|
||||
|
||||
// Migrates live captures without changing their format or callback buffers.
|
||||
Status SwitchSdlRecordingDevice(std::uint32_t recording_device_id,
|
||||
std::string *resolved_name);
|
||||
|
||||
using AudioCaptureDataCallback = void (*)(void* context,
|
||||
std::size_t size_bytes);
|
||||
|
||||
class AudioCapture {
|
||||
public:
|
||||
virtual ~AudioCapture() = default;
|
||||
|
||||
virtual const PcmSpec& output_spec() const = 0;
|
||||
virtual void* buffer_data() = 0;
|
||||
virtual std::size_t buffer_size_bytes() const = 0;
|
||||
virtual std::size_t frames_per_buffer() const = 0;
|
||||
virtual Status Start() = 0;
|
||||
virtual Status Stop() = 0;
|
||||
virtual void Shutdown() = 0;
|
||||
};
|
||||
|
||||
struct SdlAudioCaptureOptions {
|
||||
PcmSpec output_spec;
|
||||
|
||||
// Zero selects the configured process target.
|
||||
std::uint32_t recording_device_id = 0;
|
||||
std::uint32_t frame_duration_ms = 10;
|
||||
AudioCaptureDataCallback data_callback = nullptr;
|
||||
void* data_context = nullptr;
|
||||
};
|
||||
|
||||
// SDL converts the host input to output_spec and delivers fixed-duration
|
||||
// frames through one stable buffer. The callback runs synchronously on SDL's
|
||||
// recording thread after that buffer has been filled.
|
||||
Status CreateSdlAudioCapture(const SdlAudioCaptureOptions& options,
|
||||
std::unique_ptr<AudioCapture>* capture);
|
||||
|
||||
// Called by the SDL subsystem owner before SDL_QuitSubSystem.
|
||||
Status PrepareSdlAudioCaptureSubsystemShutdown();
|
||||
|
||||
} // namespace mocktail::audio
|
||||
|
||||
#endif // MOCKTAIL_AUDIO_SDL_AUDIO_CAPTURE_H_
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_AUDIO_SDL_AUDIO_SINK_H_
|
||||
#define MOCKTAIL_AUDIO_SDL_AUDIO_SINK_H_
|
||||
|
||||
@@ -62,6 +48,13 @@ struct SdlAudioSinkOptions {
|
||||
// Zero selects the configured process target.
|
||||
std::uint32_t playback_device_id = 0;
|
||||
bool start_paused = true;
|
||||
|
||||
// Optional pull provider for the host device. Each call fills one source
|
||||
// buffer whose address remains valid until the next provider call. SDL
|
||||
// copies as many buffers as the current device request needs.
|
||||
bool (*data_needed_callback)(void *context, const void **data,
|
||||
std::size_t *size_bytes) = nullptr;
|
||||
void *data_needed_context = nullptr;
|
||||
};
|
||||
|
||||
// SDL owns conversion, resampling, and borrowed-buffer consumption.
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef MOCKTAIL_AUDIO_WEBRTC_JNI_AUDIO_BRIDGE_H_
|
||||
#define MOCKTAIL_AUDIO_WEBRTC_JNI_AUDIO_BRIDGE_H_
|
||||
|
||||
#include "mocktail/status.h"
|
||||
|
||||
namespace jnivm {
|
||||
class VM;
|
||||
}
|
||||
|
||||
namespace mocktail::audio {
|
||||
|
||||
// Installs WebRtcAudioManager, WebRtcAudioRecord and WebRtcAudioTrack. The
|
||||
// manager only advertises the host PCM contract; capture opens on recording
|
||||
// init, never on manager construction. SDL's audio subsystem must remain alive
|
||||
// until ShutdownWebRtcJniAudioBridge.
|
||||
Status InstallWebRtcJniAudioBridge(jnivm::VM* vm);
|
||||
Status ShutdownWebRtcJniAudioBridge(jnivm::VM* vm);
|
||||
|
||||
} // namespace mocktail::audio
|
||||
|
||||
#endif // MOCKTAIL_AUDIO_WEBRTC_JNI_AUDIO_BRIDGE_H_
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_GRAPHICS_ANDROID_VULKAN_WSI_ADAPTER_H_
|
||||
#define MOCKTAIL_GRAPHICS_ANDROID_VULKAN_WSI_ADAPTER_H_
|
||||
|
||||
@@ -33,30 +19,14 @@ inline constexpr AndroidVulkanResult kAndroidVulkanSuccess =
|
||||
static_cast<AndroidVulkanResult>(0);
|
||||
inline constexpr AndroidVulkanResult kAndroidVulkanErrorInitializationFailed =
|
||||
static_cast<AndroidVulkanResult>(-3);
|
||||
inline constexpr std::uint64_t kHostInfiniteWaitDiagnosticSliceNs =
|
||||
1000000000ULL;
|
||||
inline constexpr std::uint64_t kHostImageAcquireWatchdogTimeoutNs =
|
||||
kHostInfiniteWaitDiagnosticSliceNs;
|
||||
|
||||
inline constexpr char kAndroidSurfaceExtension[] =
|
||||
"VK_KHR_android_surface";
|
||||
|
||||
// Roblox treats VK_SUBOPTIMAL_KHR as a hard error, so expose it as success.
|
||||
// VK_SUBOPTIMAL_KHR still returns a usable image. Roblox handles it as a fatal
|
||||
// error, so accept it until the host reports a real out-of-date surface.
|
||||
VkResult NormalizeAndroidSwapchainResult(VkResult result);
|
||||
|
||||
// Bounds only infinite acquire waits so a lost compositor wakeup cannot hang.
|
||||
std::uint64_t BoundHostImageAcquireTimeout(std::uint64_t requested_timeout);
|
||||
bool IsHostImageAcquireWatchdogTimeout(std::uint64_t requested_timeout,
|
||||
VkResult host_result);
|
||||
VkResult NormalizeHostImageAcquireResult(std::uint64_t requested_timeout,
|
||||
VkResult host_result);
|
||||
|
||||
// Infinite guest waits use finite host slices without exposing slice timeouts.
|
||||
std::uint64_t BoundHostSynchronizationWaitTimeout(
|
||||
std::uint64_t requested_timeout);
|
||||
bool ShouldContinueHostSynchronizationWait(std::uint64_t requested_timeout,
|
||||
VkResult host_result);
|
||||
|
||||
// Returned storage owns the translated extension names.
|
||||
Status TranslateAndroidVulkanInstanceExtensions(
|
||||
const std::vector<std::string>& android_extensions,
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_GRAPHICS_ANGLE_PROBE_H_
|
||||
#define MOCKTAIL_GRAPHICS_ANGLE_PROBE_H_
|
||||
|
||||
@@ -30,6 +16,9 @@ struct AngleProbeOptions {
|
||||
bool allow_software_device = false;
|
||||
};
|
||||
|
||||
// Checks ANGLE entry points and client extensions without initializing a display.
|
||||
BackendCapability InspectAngleLibraries(const AngleProbeOptions& options);
|
||||
|
||||
// Loads a pinned ANGLE pair, verifies the required EGL/GLES symbols, and
|
||||
// initializes an EGL_PLATFORM_ANGLE Vulkan display. No context is fabricated.
|
||||
BackendCapability ProbeAngleVulkan(const AngleProbeOptions& options);
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Loading by SONAME can select host ANGLE instead of the Bionic adapter. This
|
||||
// bridge therefore opens the library beside the executable by exact path.
|
||||
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
#ifndef MOCKTAIL_GRAPHICS_CHROME_TRACE_WRITER_H_
|
||||
#define MOCKTAIL_GRAPHICS_CHROME_TRACE_WRITER_H_
|
||||
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
namespace mocktail {
|
||||
namespace graphics {
|
||||
|
||||
// Environment variable naming the trace file the Vulkan adapter writes.
|
||||
inline constexpr char kProfileTraceEnvironment[] = "MOCKTAIL_PROFILE_TRACE";
|
||||
|
||||
struct TraceArg {
|
||||
const char* key;
|
||||
std::int64_t value;
|
||||
};
|
||||
|
||||
// CLOCK_MONOTONIC in nanoseconds.
|
||||
std::uint64_t TraceClockNanos();
|
||||
// CPU time used so far by the calling thread, in nanoseconds.
|
||||
std::uint64_t TraceThreadCpuNanos();
|
||||
// CPU time used so far by every thread of the process, in nanoseconds.
|
||||
std::uint64_t TraceProcessCpuNanos();
|
||||
|
||||
// CPU time spent between two successive presents.
|
||||
struct FrameCpuSample {
|
||||
double process_ms = 0;
|
||||
// Set only when the previous present came from the same thread; thread CPU
|
||||
// clocks of different threads cannot be subtracted.
|
||||
bool has_thread = false;
|
||||
double thread_ms = 0;
|
||||
};
|
||||
|
||||
// Turns CPU clocks read at each present into per-frame CPU time.
|
||||
// Thread-safe.
|
||||
class FrameCpuSampler final {
|
||||
public:
|
||||
// Returns false for the first present, which ends no frame.
|
||||
bool Sample(std::uint64_t thread_id, std::uint64_t thread_cpu_ns,
|
||||
std::uint64_t process_cpu_ns, FrameCpuSample* sample);
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
bool has_previous_ = false;
|
||||
std::uint64_t previous_thread_id_ = 0;
|
||||
std::uint64_t previous_thread_cpu_ns_ = 0;
|
||||
std::uint64_t previous_process_cpu_ns_ = 0;
|
||||
};
|
||||
|
||||
// Writes the Trace Event Format JSON array read by chrome://tracing and
|
||||
// ui.perfetto.dev. Recording threads only format into a shared buffer; a
|
||||
// background thread owns file writes, so disk stalls do not land inside the
|
||||
// measured calls. Events are written in arrival order. Each write ends the
|
||||
// file with the closing bracket, so a killed session leaves a loadable trace
|
||||
// holding everything written before it died.
|
||||
// Names, categories, and keys must outlive the call.
|
||||
class ChromeTraceWriter final {
|
||||
public:
|
||||
static std::unique_ptr<ChromeTraceWriter> Open(
|
||||
const std::filesystem::path& path, std::string* error);
|
||||
|
||||
~ChromeTraceWriter();
|
||||
ChromeTraceWriter(const ChromeTraceWriter&) = delete;
|
||||
ChromeTraceWriter& operator=(const ChromeTraceWriter&) = delete;
|
||||
|
||||
// A complete ("X") slice on the calling thread. Slices on one thread must
|
||||
// nest, which scoped timing gives.
|
||||
void Slice(const char* name, const char* category, std::uint64_t start_ns,
|
||||
std::uint64_t end_ns, std::initializer_list<TraceArg> args = {});
|
||||
void Slice(const char* name, const char* category, std::uint64_t start_ns,
|
||||
std::uint64_t end_ns, const TraceArg* args,
|
||||
std::size_t arg_count);
|
||||
// A counter ("C") sample; each name becomes one counter track.
|
||||
void Counter(const char* name, std::uint64_t timestamp_ns, double value);
|
||||
|
||||
// Returns once every event recorded before the call is on disk.
|
||||
void Flush();
|
||||
// Flushes, closes the file, and drops later events. Idempotent.
|
||||
void Close();
|
||||
|
||||
private:
|
||||
explicit ChromeTraceWriter(std::FILE* file);
|
||||
void Append(const char* event, std::size_t size);
|
||||
void WriterLoop();
|
||||
void WritePending();
|
||||
|
||||
std::FILE* file_;
|
||||
const int owner_pid_;
|
||||
std::mutex file_mutex_; // Guards file_ and spare_.
|
||||
std::string spare_;
|
||||
std::mutex mutex_;
|
||||
std::condition_variable wake_;
|
||||
std::string pending_;
|
||||
bool first_event_ = true;
|
||||
bool closed_ = false;
|
||||
bool stopping_ = false;
|
||||
std::thread writer_;
|
||||
};
|
||||
|
||||
// The process trace named by MOCKTAIL_PROFILE_TRACE, opened on first use.
|
||||
// Null when profiling is off or the file could not be opened. The writer is
|
||||
// never destroyed; it is closed when the process exits normally.
|
||||
ChromeTraceWriter* ActiveProfileTrace();
|
||||
|
||||
// Times its own lifetime as one slice. A null writer records nothing.
|
||||
class TraceScope final {
|
||||
public:
|
||||
TraceScope(ChromeTraceWriter* writer, const char* name,
|
||||
const char* category)
|
||||
: writer_(writer),
|
||||
name_(name),
|
||||
category_(category),
|
||||
start_ns_(writer != nullptr ? TraceClockNanos() : 0) {}
|
||||
~TraceScope() {
|
||||
if (writer_ != nullptr) {
|
||||
writer_->Slice(name_, category_, start_ns_, TraceClockNanos(), args_,
|
||||
arg_count_);
|
||||
}
|
||||
}
|
||||
TraceScope(const TraceScope&) = delete;
|
||||
TraceScope& operator=(const TraceScope&) = delete;
|
||||
|
||||
// Keeps the first kMaxArgs arguments.
|
||||
void Arg(const char* key, std::int64_t value) {
|
||||
if (writer_ != nullptr && arg_count_ < kMaxArgs) {
|
||||
args_[arg_count_++] = {key, value};
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr std::size_t kMaxArgs = 4;
|
||||
ChromeTraceWriter* writer_;
|
||||
const char* name_;
|
||||
const char* category_;
|
||||
std::uint64_t start_ns_;
|
||||
TraceArg args_[kMaxArgs]{};
|
||||
std::size_t arg_count_ = 0;
|
||||
};
|
||||
|
||||
} // namespace graphics
|
||||
} // namespace mocktail
|
||||
|
||||
#endif // MOCKTAIL_GRAPHICS_CHROME_TRACE_WRITER_H_
|
||||
@@ -0,0 +1,79 @@
|
||||
#ifndef MOCKTAIL_GRAPHICS_ETC2_DECODER_H_
|
||||
#define MOCKTAIL_GRAPHICS_ETC2_DECODER_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
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,
|
||||
kEtc2Rgba8,
|
||||
kEacR11,
|
||||
kEacR11Signed,
|
||||
kEacRg11,
|
||||
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,
|
||||
std::uint32_t first_block_row,
|
||||
std::uint32_t block_row_count,
|
||||
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;
|
||||
std::size_t source_bytes = 0;
|
||||
std::uint32_t width = 0;
|
||||
std::uint32_t height = 0;
|
||||
std::uint8_t* destination = nullptr;
|
||||
std::size_t destination_bytes = 0;
|
||||
// Set to DecodeEtcImage's result.
|
||||
bool ok = false;
|
||||
};
|
||||
|
||||
// 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);
|
||||
|
||||
// Calls run(context) worker_count times, once on the caller and the rest on
|
||||
// pooled decode workers that run alongside it, and returns when every call
|
||||
// has returned. `run` must be safe to call from several threads at once, so
|
||||
// it should drain a shared queue. When the pool is busy with another batch,
|
||||
// or no worker could be started, run is called on the caller alone.
|
||||
void RunOnDecodeWorkers(void (*run)(void*), void* context,
|
||||
unsigned worker_count);
|
||||
|
||||
} // namespace mocktail::graphics
|
||||
|
||||
#endif // MOCKTAIL_GRAPHICS_ETC2_DECODER_H_
|
||||
@@ -0,0 +1,36 @@
|
||||
#ifndef MOCKTAIL_GRAPHICS_GLES_TEXT_OVERLAY_COMPOSITOR_H_
|
||||
#define MOCKTAIL_GRAPHICS_GLES_TEXT_OVERLAY_COMPOSITOR_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "mocktail/graphics/text_overlay_frame.h"
|
||||
|
||||
struct SDL_Window;
|
||||
|
||||
namespace mocktail::graphics {
|
||||
|
||||
struct GlesTextOverlaySource {
|
||||
bool (*may_present)() = nullptr;
|
||||
bool (*query)(MocktailTextOverlayFrameInfo *) = nullptr;
|
||||
bool (*copy)(std::uint64_t, void *, std::size_t) = nullptr;
|
||||
};
|
||||
|
||||
// Composites the host text editor before SDL swaps the game's GLES backbuffer.
|
||||
// Draw and destruction use the owning GL context when it is current. If that
|
||||
// context is no longer current at destruction, its destruction frees the GL
|
||||
// resources. Create a new compositor when creating a new context.
|
||||
class GlesTextOverlayCompositor {
|
||||
public:
|
||||
explicit GlesTextOverlayCompositor(GlesTextOverlaySource source);
|
||||
~GlesTextOverlayCompositor();
|
||||
|
||||
bool Draw(SDL_Window *window);
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
} // namespace mocktail::graphics
|
||||
|
||||
#endif // MOCKTAIL_GRAPHICS_GLES_TEXT_OVERLAY_COMPOSITOR_H_
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_GRAPHICS_GRAPHICS_BACKEND_H_
|
||||
#define MOCKTAIL_GRAPHICS_GRAPHICS_BACKEND_H_
|
||||
|
||||
|
||||
@@ -1,20 +1,8 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_GRAPHICS_PRESENT_MODE_POLICY_H_
|
||||
#define MOCKTAIL_GRAPHICS_PRESENT_MODE_POLICY_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
@@ -31,10 +19,52 @@ enum class PresentModePolicy {
|
||||
|
||||
PresentModePolicy ResolvePresentModePolicy(std::string_view vsync_value,
|
||||
std::string_view frame_rate_value);
|
||||
|
||||
// Reads MOCKTAIL_VSYNC / MOCKTAIL_FRAME_RATE_LIMIT once. Window pacing and
|
||||
// the Vulkan adapter must use this instead of getenv'ing the same keys.
|
||||
PresentModePolicy CachedPresentModePolicy();
|
||||
|
||||
std::vector<VkPresentModeKHR>
|
||||
FilterPresentModes(PresentModePolicy policy,
|
||||
const std::vector<VkPresentModeKHR> &host_modes);
|
||||
const char *PresentModePolicyName(PresentModePolicy policy);
|
||||
const char *PresentModeKhrName(VkPresentModeKHR mode);
|
||||
|
||||
// Creates the swapchain with the mode in `info`. A driver can reject a mode
|
||||
// that the device did not enable, so a failed create is retried once with the
|
||||
// mode the game asked for. `create` takes the info and returns a VkResult.
|
||||
template <typename Create>
|
||||
VkResult CreateSwapchainWithPresentModeFallback(VkSwapchainCreateInfoKHR &info,
|
||||
VkPresentModeKHR requested,
|
||||
Create create) {
|
||||
const VkResult result = create(info);
|
||||
if (result == VK_SUCCESS || info.presentMode == requested) {
|
||||
return result;
|
||||
}
|
||||
switch (result) {
|
||||
case VK_ERROR_OUT_OF_HOST_MEMORY:
|
||||
case VK_ERROR_OUT_OF_DEVICE_MEMORY:
|
||||
case VK_ERROR_DEVICE_LOST:
|
||||
case VK_ERROR_SURFACE_LOST_KHR:
|
||||
case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR:
|
||||
return result; // Another present mode cannot fix these.
|
||||
default:
|
||||
break;
|
||||
}
|
||||
std::fprintf(stderr,
|
||||
" [vulkan] swapchain presentMode %s failed (%d); retrying "
|
||||
"with %s\n",
|
||||
PresentModeKhrName(info.presentMode), static_cast<int>(result),
|
||||
PresentModeKhrName(requested));
|
||||
info.presentMode = requested;
|
||||
return create(info);
|
||||
}
|
||||
|
||||
// Extra images so acquire does not wait on the compositor-held buffer.
|
||||
std::uint32_t PreferSwapchainMinImageCount(PresentModePolicy policy,
|
||||
std::uint32_t requested,
|
||||
std::uint32_t min_images,
|
||||
std::uint32_t max_images);
|
||||
|
||||
} // namespace graphics
|
||||
} // namespace mocktail
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_GRAPHICS_SDL_VULKAN_WSI_H_
|
||||
#define MOCKTAIL_GRAPHICS_SDL_VULKAN_WSI_H_
|
||||
|
||||
|
||||
@@ -1,24 +1,10 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_GRAPHICS_TEXT_OVERLAY_FRAME_H_
|
||||
#define MOCKTAIL_GRAPHICS_TEXT_OVERLAY_FRAME_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
// Process-local ABI with libvulkan.so. Pixels are RGBA8; consumers must copy a
|
||||
// Process-local ABI for host compositors. Pixels are RGBA8; consumers must copy a
|
||||
// matching revision before the producer clears its storage.
|
||||
struct MocktailTextOverlayFrameInfo {
|
||||
static constexpr std::uint32_t kAbiVersion = 2;
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
#ifndef MOCKTAIL_GRAPHICS_TEXTURE_OVERRIDE_H_
|
||||
#define MOCKTAIL_GRAPHICS_TEXTURE_OVERRIDE_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
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);
|
||||
void ResampleRgba(const std::uint8_t* source, std::uint32_t source_width,
|
||||
std::uint32_t source_height, std::size_t source_stride_bytes,
|
||||
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);
|
||||
|
||||
private:
|
||||
std::string override_dir_;
|
||||
std::string dump_dir_;
|
||||
std::mutex mutex_;
|
||||
std::unordered_map<std::uint64_t, std::shared_ptr<const RgbaImage>> cache_;
|
||||
std::unordered_set<std::uint64_t> dumped_;
|
||||
};
|
||||
|
||||
} // namespace mocktail::graphics
|
||||
|
||||
#endif // MOCKTAIL_GRAPHICS_TEXTURE_OVERRIDE_H_
|
||||
@@ -1,6 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
// Licensed under the Apache License, Version 2.0.
|
||||
|
||||
#ifndef MOCKTAIL_GRAPHICS_VULKAN_ABI_H_
|
||||
#define MOCKTAIL_GRAPHICS_VULKAN_ABI_H_
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
namespace mocktail::graphics::internal {
|
||||
|
||||
// Only transfers between two concrete queue families can be converted for a
|
||||
// desktop swapchain. External/foreign ownership must reach the driver intact.
|
||||
inline bool NeedsDesktopQueueNormalization(std::uint32_t source,
|
||||
std::uint32_t destination) {
|
||||
return source != destination && source < VK_QUEUE_FAMILY_FOREIGN_EXT &&
|
||||
destination < VK_QUEUE_FAMILY_FOREIGN_EXT;
|
||||
}
|
||||
|
||||
template <class Barrier>
|
||||
inline bool HasDesktopQueueTransfers(std::uint32_t count,
|
||||
const Barrier* barriers) {
|
||||
if (!barriers) return false;
|
||||
for (std::uint32_t i = 0; i < count; ++i)
|
||||
if (NeedsDesktopQueueNormalization(barriers[i].srcQueueFamilyIndex,
|
||||
barriers[i].dstQueueFamilyIndex))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Keep the caller's array for the common no-ownership-transfer path. A batch
|
||||
// callback can inspect a mixed array under one backend lock; the original
|
||||
// array and every structure chained through pNext remain untouched.
|
||||
template <class Barrier, class Normalize, class Forward>
|
||||
inline void WithDesktopQueueBarriers(std::uint32_t count,
|
||||
const Barrier* barriers,
|
||||
Normalize&& normalize, Forward&& forward) {
|
||||
if (!HasDesktopQueueTransfers(count, barriers)) {
|
||||
forward(barriers);
|
||||
return;
|
||||
}
|
||||
constexpr std::size_t kStackCount = 8;
|
||||
if (count <= kStackCount) {
|
||||
std::array<Barrier, kStackCount> adjusted;
|
||||
std::copy_n(barriers, count, adjusted.data());
|
||||
normalize(count, adjusted.data());
|
||||
forward(adjusted.data());
|
||||
} else {
|
||||
std::vector<Barrier> adjusted(barriers, barriers + count);
|
||||
normalize(count, adjusted.data());
|
||||
forward(adjusted.data());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mocktail::graphics::internal
|
||||
@@ -0,0 +1,179 @@
|
||||
#ifndef MOCKTAIL_GRAPHICS_VULKAN_ETC2_EMULATION_H_
|
||||
#define MOCKTAIL_GRAPHICS_VULKAN_ETC2_EMULATION_H_
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "mocktail/graphics/etc2_decoder.h"
|
||||
|
||||
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);
|
||||
|
||||
// Sums every mip level and layer; `scale` multiplies the host image's
|
||||
// level-0 width and height only.
|
||||
bool Etc2ImageByteCounts(EtcFormat format, const VkExtent3D& extent,
|
||||
std::uint32_t mip_levels, std::uint32_t layers,
|
||||
std::uint32_t scale, VkDeviceSize* compressed,
|
||||
VkDeviceSize* decoded);
|
||||
|
||||
struct EmulatedTextureBytes {
|
||||
VkDeviceSize compressed = 0;
|
||||
VkDeviceSize decoded = 0;
|
||||
};
|
||||
|
||||
// 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);
|
||||
|
||||
// Staging for decoded uploads is carved out of host-visible blocks this
|
||||
// large; an upload that needs more gets a block of its own.
|
||||
inline constexpr VkDeviceSize kEtc2StagingBlockBytes = 16 * 1024 * 1024;
|
||||
|
||||
// A timeline semaphore and the value an asynchronous decode will signal.
|
||||
// `semaphore` is VK_NULL_HANDLE when the decode already finished on the
|
||||
// calling thread and the submit needs no extra wait.
|
||||
struct Etc2SubmitWait {
|
||||
VkSemaphore semaphore = VK_NULL_HANDLE;
|
||||
std::uint64_t value = 0;
|
||||
};
|
||||
|
||||
// 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 ranges live until their command buffer is begun, reset or freed.
|
||||
// MOCKTAIL_DISABLE_ETC2_EMULATION=1 turns the emulation off.
|
||||
class VulkanEtc2Emulation final {
|
||||
public:
|
||||
explicit VulkanEtc2Emulation(
|
||||
VkDeviceSize staging_block_bytes = kEtc2StagingBlockBytes);
|
||||
~VulkanEtc2Emulation();
|
||||
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);
|
||||
|
||||
// `timeline_semaphore` states whether the device was created with the
|
||||
// timelineSemaphore feature enabled. Without it every decode runs on the
|
||||
// calling thread.
|
||||
void RegisterDevice(VkDevice device, VkPhysicalDevice physical_device,
|
||||
bool emulated,
|
||||
const VkPhysicalDeviceMemoryProperties& memory,
|
||||
PFN_vkGetDeviceProcAddr get_device_proc_addr,
|
||||
bool timeline_semaphore = false);
|
||||
void DestroyDevice(VkDevice device);
|
||||
|
||||
VkResult CreateImage(VkDevice device, const VkImageCreateInfo* create_info,
|
||||
const VkAllocationCallbacks* allocator, VkImage* image);
|
||||
void DestroyImage(VkDevice device, VkImage image,
|
||||
const VkAllocationCallbacks* allocator);
|
||||
VkResult CreateImageView(VkDevice device,
|
||||
const VkImageViewCreateInfo* create_info,
|
||||
const VkAllocationCallbacks* allocator,
|
||||
VkImageView* view);
|
||||
VkResult BindBufferMemory(VkDevice device, VkBuffer buffer,
|
||||
VkDeviceMemory memory, VkDeviceSize offset);
|
||||
VkResult BindBufferMemory2(VkDevice device, std::uint32_t count,
|
||||
const VkBindBufferMemoryInfo* infos);
|
||||
VkResult MapMemory(VkDevice device, VkDeviceMemory memory,
|
||||
VkDeviceSize offset, VkDeviceSize size,
|
||||
VkMemoryMapFlags flags, void** data);
|
||||
VkResult MapMemory2(VkDevice device, const VkMemoryMapInfo* info,
|
||||
void** data);
|
||||
void UnmapMemory(VkDevice device, VkDeviceMemory memory);
|
||||
VkResult UnmapMemory2(VkDevice device, const VkMemoryUnmapInfo* info);
|
||||
void FreeMemory(VkDevice device, VkDeviceMemory memory,
|
||||
const VkAllocationCallbacks* allocator);
|
||||
void DestroyBuffer(VkDevice device, VkBuffer buffer,
|
||||
const VkAllocationCallbacks* allocator);
|
||||
void CmdCopyBufferToImage(VkDevice device, VkCommandBuffer command_buffer,
|
||||
VkBuffer source, VkImage destination,
|
||||
VkImageLayout layout, std::uint32_t region_count,
|
||||
const VkBufferImageCopy* regions);
|
||||
void CmdCopyBufferToImage2(VkDevice device, VkCommandBuffer command_buffer,
|
||||
const VkCopyBufferToImageInfo2* info);
|
||||
void CmdCopyImage(VkDevice device, VkCommandBuffer command_buffer,
|
||||
VkImage source, VkImageLayout source_layout,
|
||||
VkImage destination, VkImageLayout destination_layout,
|
||||
std::uint32_t region_count, const VkImageCopy* regions);
|
||||
void CmdCopyImage2(VkDevice device, VkCommandBuffer command_buffer,
|
||||
const VkCopyImageInfo2* info);
|
||||
void CmdExecuteCommands(VkDevice device, VkCommandBuffer command_buffer,
|
||||
std::uint32_t count,
|
||||
const VkCommandBuffer* secondaries);
|
||||
|
||||
// Decodes every upload recorded into these command buffers and the
|
||||
// secondary command buffers they execute.
|
||||
//
|
||||
// With `allow_async` on a device that has a timeline semaphore, the decode
|
||||
// runs on worker threads, and so does the gather of the compressed bytes
|
||||
// whenever the application already has them mapped. The returned semaphore
|
||||
// and value must then be added to the submit's waits, so the GPU copy
|
||||
// cannot execute before the decode finishes. Values are signalled in the
|
||||
// order they are handed out.
|
||||
//
|
||||
// Those threads read the application's source bytes after this returns,
|
||||
// which Vulkan allows: the application may not write them between the
|
||||
// submit and the copy's completion. Unmapping or freeing that memory waits
|
||||
// for the decodes still reading it.
|
||||
//
|
||||
// Returns `{VK_NULL_HANDLE, 0}` when there was nothing to decode, when
|
||||
// `allow_async` is false, or when the device has no timeline semaphore. In
|
||||
// those cases every upload is already decoded when this returns.
|
||||
Etc2SubmitWait PrepareSubmit(const VkCommandBuffer* command_buffers,
|
||||
std::uint32_t count, bool allow_async = false);
|
||||
// Destroys the staging buffers owned by a command buffer that is no longer
|
||||
// pending, after waiting for any decode still writing into them.
|
||||
void ReleaseCommandBuffer(VkCommandBuffer command_buffer);
|
||||
|
||||
// Traced as the "emulated texture (MB)" and "compressed texture (MB)"
|
||||
// counters.
|
||||
EmulatedTextureBytes TextureBytes() const;
|
||||
|
||||
struct State;
|
||||
|
||||
private:
|
||||
State* state_;
|
||||
};
|
||||
|
||||
} // namespace mocktail::graphics
|
||||
|
||||
#endif // MOCKTAIL_GRAPHICS_VULKAN_ETC2_EMULATION_H_
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
namespace mocktail::graphics::internal {
|
||||
|
||||
struct FoveationDevicePolicy {
|
||||
bool density_offsets_enabled = true;
|
||||
};
|
||||
|
||||
// Capture before invoking a runtime/loader device callback: its temporary
|
||||
// feature-chain records must never be inspected after the call returns.
|
||||
inline FoveationDevicePolicy SnapshotFoveationDevicePolicy(
|
||||
const VkDeviceCreateInfo* info) {
|
||||
if (!info || info->sType != VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO ||
|
||||
info->enabledExtensionCount > 4096 ||
|
||||
(info->enabledExtensionCount && !info->ppEnabledExtensionNames)) return {};
|
||||
bool offsets = false;
|
||||
for (std::uint32_t i = 0; i < info->enabledExtensionCount; ++i) {
|
||||
const char* name = info->ppEnabledExtensionNames[i];
|
||||
if (!name) return {};
|
||||
if (!std::strcmp(name, "VK_QCOM_fragment_density_map_offset") ||
|
||||
!std::strcmp(name, "VK_EXT_fragment_density_map_offset"))
|
||||
offsets = true;
|
||||
}
|
||||
std::array<const VkBaseInStructure*, 64> seen{};
|
||||
std::size_t count = 0;
|
||||
for (auto* node = static_cast<const VkBaseInStructure*>(info->pNext);
|
||||
node; node = node->pNext) {
|
||||
if (count == seen.size()) return {};
|
||||
for (std::size_t i = 0; i < count; ++i) if (seen[i] == node) return {};
|
||||
seen[count++] = node;
|
||||
if (node->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_DENSITY_MAP_OFFSET_FEATURES_EXT)
|
||||
offsets |= reinterpret_cast<const VkPhysicalDeviceFragmentDensityMapOffsetFeaturesEXT*>(node)
|
||||
->fragmentDensityMapOffset != VK_FALSE;
|
||||
}
|
||||
return {offsets};
|
||||
}
|
||||
|
||||
} // namespace mocktail::graphics::internal
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_GRAPHICS_VULKAN_TEXT_OVERLAY_COMPOSITOR_H_
|
||||
#define MOCKTAIL_GRAPHICS_VULKAN_TEXT_OVERLAY_COMPOSITOR_H_
|
||||
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_PLATFORM_ANDROID_LOG_OBSERVER_H_
|
||||
#define MOCKTAIL_PLATFORM_ANDROID_LOG_OBSERVER_H_
|
||||
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_PLATFORM_DISPLAY_REFRESH_CAPABILITIES_H_
|
||||
#define MOCKTAIL_PLATFORM_DISPLAY_REFRESH_CAPABILITIES_H_
|
||||
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
// Copyright 2026 Mocktail Project Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MOCKTAIL_PLATFORM_PLATFORM_RUNTIME_H_
|
||||
#define MOCKTAIL_PLATFORM_PLATFORM_RUNTIME_H_
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
@@ -40,7 +27,7 @@ struct WindowOptions {
|
||||
int height = 720;
|
||||
std::string title = "Roblox";
|
||||
WindowSurfaceApi surface_api = WindowSurfaceApi::kAngleEgl;
|
||||
bool high_pixel_density = true;
|
||||
bool high_pixel_density = false;
|
||||
bool initially_hidden = true;
|
||||
|
||||
// Production callers should keep this true. Tests using SDL's dummy driver
|
||||
@@ -72,11 +59,69 @@ struct NativeWindowDescriptor {
|
||||
|
||||
struct QuitEvent {};
|
||||
|
||||
// Android exposes pointer and TextBox geometry in density-independent surface
|
||||
// coordinates. SDL exposes pointer positions in logical window coordinates,
|
||||
// while Vulkan presents to the pixel-sized drawable. Keep all three spaces in
|
||||
// one value so input, IME placement and same-surface overlays use exact inverse
|
||||
// transforms.
|
||||
struct SurfaceCoordinateTransform {
|
||||
int32_t logical_width = 0;
|
||||
int32_t logical_height = 0;
|
||||
int32_t pixel_width = 0;
|
||||
int32_t pixel_height = 0;
|
||||
float density_scale = 1.0F;
|
||||
|
||||
bool valid() const {
|
||||
return logical_width > 0 && logical_height > 0 && pixel_width > 0 &&
|
||||
pixel_height > 0 && std::isfinite(density_scale) &&
|
||||
density_scale > 0.0F;
|
||||
}
|
||||
|
||||
float guest_width() const {
|
||||
return valid() ? static_cast<float>(pixel_width) / density_scale : 0.0F;
|
||||
}
|
||||
float guest_height() const {
|
||||
return valid() ? static_cast<float>(pixel_height) / density_scale : 0.0F;
|
||||
}
|
||||
int32_t rounded_guest_width() const {
|
||||
return valid() ? static_cast<int32_t>(std::lround(guest_width())) : 0;
|
||||
}
|
||||
int32_t rounded_guest_height() const {
|
||||
return valid() ? static_cast<int32_t>(std::lround(guest_height())) : 0;
|
||||
}
|
||||
|
||||
float HostLogicalToGuestX(float coordinate) const {
|
||||
return valid() ? coordinate * static_cast<float>(pixel_width) /
|
||||
static_cast<float>(logical_width) / density_scale
|
||||
: coordinate;
|
||||
}
|
||||
float HostLogicalToGuestY(float coordinate) const {
|
||||
return valid() ? coordinate * static_cast<float>(pixel_height) /
|
||||
static_cast<float>(logical_height) / density_scale
|
||||
: coordinate;
|
||||
}
|
||||
float GuestToHostLogicalX(float coordinate) const {
|
||||
return valid() ? coordinate * density_scale *
|
||||
static_cast<float>(logical_width) /
|
||||
static_cast<float>(pixel_width)
|
||||
: coordinate;
|
||||
}
|
||||
float GuestToHostLogicalY(float coordinate) const {
|
||||
return valid() ? coordinate * density_scale *
|
||||
static_cast<float>(logical_height) /
|
||||
static_cast<float>(pixel_height)
|
||||
: coordinate;
|
||||
}
|
||||
};
|
||||
|
||||
struct WindowResizedEvent {
|
||||
int logical_width = 0;
|
||||
int logical_height = 0;
|
||||
int pixel_width = 0;
|
||||
int pixel_height = 0;
|
||||
// Android DisplayMetrics.density equivalent. This is independent of the
|
||||
// logical-to-pixel drawable ratio on X11/XWayland.
|
||||
float density_scale = 1.0F;
|
||||
};
|
||||
|
||||
struct WindowFocusEvent {
|
||||
@@ -107,6 +152,10 @@ struct MouseMotionEvent {
|
||||
float delta_x = 0.0f;
|
||||
float delta_y = 0.0f;
|
||||
std::uint32_t buttons = 0;
|
||||
// True while the window holds relative mouse mode. The absolute coordinates
|
||||
// are meaningless then: X11 reports zeroes, Wayland reports a position that
|
||||
// accumulates the deltas and can leave the window entirely.
|
||||
bool relative = false;
|
||||
};
|
||||
|
||||
struct MouseButtonEvent {
|
||||
@@ -115,6 +164,8 @@ struct MouseButtonEvent {
|
||||
std::uint8_t clicks = 0;
|
||||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
// See MouseMotionEvent::relative.
|
||||
bool relative = false;
|
||||
};
|
||||
|
||||
struct MouseWheelEvent {
|
||||
@@ -122,6 +173,8 @@ struct MouseWheelEvent {
|
||||
float delta_y = 0.0f;
|
||||
float mouse_x = 0.0f;
|
||||
float mouse_y = 0.0f;
|
||||
// See MouseMotionEvent::relative.
|
||||
bool relative = false;
|
||||
};
|
||||
|
||||
struct TouchEvent {
|
||||
@@ -142,9 +195,27 @@ struct TouchEvent {
|
||||
float pressure = 0.0f;
|
||||
};
|
||||
|
||||
enum class GamepadFamily {
|
||||
kUnknown,
|
||||
kXbox,
|
||||
kPlayStation4,
|
||||
kPlayStation5,
|
||||
};
|
||||
|
||||
struct GamepadDescriptor {
|
||||
GamepadFamily family = GamepadFamily::kUnknown;
|
||||
std::string name;
|
||||
// Bit positions are SDL's standardized gamepad buttons and axes, not raw
|
||||
// joystick indices or Android key codes.
|
||||
std::uint32_t buttons = 0;
|
||||
std::uint32_t axes = 0;
|
||||
};
|
||||
|
||||
struct GamepadConnectionEvent {
|
||||
std::int64_t instance_id = 0;
|
||||
bool connected = false;
|
||||
GamepadDescriptor descriptor{};
|
||||
bool remapped = false;
|
||||
};
|
||||
|
||||
struct GamepadAxisEvent {
|
||||
|
||||
Loaded 100 of 953 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user