Merge branch 'more-sources' into apk-store

This commit is contained in:
saphid committed 2026-09-28 21:54:36 +10:00
commit c5b5930582
20 files changed
+1422

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
/proof-cache/
/amazon.html
/aptoide-terms.html
/itch-game.html
/meta.html
/uptodown-terms.html
+69
View File
@@ -0,0 +1,69 @@
# more-sources
Scope: only this worktree/branch; no delegation, SSH, installation, push, PR or
issue writes. Parent performs integration and independent review per brief.
Decisions: implement GitHub curated public APK releases and itch.io RSS page
links. itch robots exclude keyed download pages used by /tmp/vrapk/itchdl.py;
no bypass. Topic results are not automatically trusted. Curated Open Brush and
SuperTux prereleases must be explicitly allowed; discovered during fixture tests.
No shared files changed. Default GitHub search makes no network request.
Evidence (2026-09-28):
- Read all six prerequisite files and the full brief.
- Fetched itch terms, root/author robots and OpenXR RSS with FrameControl UA.
- Anonymous GitHub returned 403 rate-limit; authenticated gh API reads succeeded.
- GitHub fixtures record upstream Khronos, Open Brush and SuperTux releases.
- `python3 .claude/prove-more-sources.py`: GitHub search/download succeeded,
published SHA-256 matched; `python3 ui/frame_android.py info` exited 0.
Overall script exit 1 because subsequent itch RSS request returned HTTP 429.
A second invocation had the same outcome; no further live itch retries.
- Download: `.claude/proof-cache/f24bbe8ba6f6339fca658628868ba8189cbc33390d6ac508f69d76fb67b5fa34.apk`.
Package org.khronos.openxr.hello_xr.vulkan; 1.1.63/1063; API 24; arm64;
OpenXR. No runtime compatibility claim.
- Initial whole suite: 174 tests, 1 failed (prerelease handling). After explicit
curated prerelease support: 174 tests passed, exit 0. Final rerun below.
Unverified: live itch search completion (429), itch download/info (disallowed
flow), Open Brush/SuperTux downloads, topic live adapter search (API fixture
capture succeeded), headset runtime, UI integration, independent provider
review (explicit implementation brief prohibits delegation and assigns review
to parent). No claim that any reviewer/model participated.
Research HTML and local downloaded APKs are local evidence only, not committed.
Recorded fixtures retain public source data, not credentials. The proof script
uses gh's token in memory and overrides cache into this worktree.
Final verification:
- Python version: 3.9.6.
- `python3 -m unittest discover -s tests > .claude/tests-more-sources.log 2>&1`:
174 tests passed in 5.569s, real exit 0.
- `git diff --check`: exit 0.
- Final artifact: docs/apk-sources.md plus two source modules, a private HTTPS
helper, curated JSON, recorded fixtures and tests. Evidence scripts/logs stay
in .claude; research HTML and APK cache are explicitly ignored there.
## Artwork follow-up
Added curated icon/images metadata and plain-language summaries. Icons use
publisher repository assets pinned to inspected commits. Open Brush's banner
is its README image; three screenshots are from its README-linked Steam page.
SuperTux's banner/screenshot is the upstream gameplay preview in the port's
README, not a Quest capture. hello_xr has its actual Vulkan launcher icon and
repository social banner; no real screenshot was found in its repository or
README, so screenshots stays empty rather than mislabeling branding.
Topic search uses owner.avatar_url and the requested GitHub social-preview
pattern, retaining curated artwork when a curated app is discovered by topic.
Unknown repository details use GitHub's owner.png avatar endpoint and the same
social banner without an extra API request. Existing itch cover behavior is
unchanged; tests now assert icon/banner equality and empty screenshots because
the recorded RSS provides no separate screenshots.
Validation: all nine distinct curated artwork URLs returned HTTP 200, image
Content-Type and image magic. Of six topic artwork URLs, five returned images
and LWJGL's social preview returned HTTP 429. Recorded in
`tests/fixtures/more_sources/artwork-check.json`; no repeated retry.
`python3 -m unittest discover -s tests`: 176 passed in 5.879s, real exit 0.
`git diff --check`: exit 0. No UI rendering/headset testing or independent
review; parent retains integration/review, and delegation remains prohibited.
+36
View File
@@ -0,0 +1,36 @@
org.khronos.openxr.hello_xr.vulkan · 1.1.63 (code 1063)
Minimum: Android 7.0 (API 24)
ABIs: arm64-v8a, armeabi-v7a, x86, x86_64
Lepton can install this APK. Features may still need services Lepton lacks.
VR app
Uses OpenXR (good).
GitHub search: [{"source": "github", "id": "KhronosGroup/OpenXR-SDK-Source", "package": null, "name": "hello_xr", "summary": "Khronos OpenXR sample (Vulkan and OpenGL ES)", "icon": null, "images": {"icon": null, "banner": null, "screenshots": []}, "page": "https://github.com/KhronosGroup/OpenXR-SDK-Source", "version": null, "version_code": null, "min_sdk": null, "abis": null, "vr": true, "size": null, "free": true, "license": "Apache-2.0", "updated": null, "downloadable": true}]
GitHub download: {"apk": "/Users/saphid/projects/steam-frame-moresrc/.claude/proof-cache/f24bbe8ba6f6339fca658628868ba8189cbc33390d6ac508f69d76fb67b5fa34.apk", "obb": [], "sha256": "f24bbe8ba6f6339fca658628868ba8189cbc33390d6ac508f69d76fb67b5fa34", "verified": true}
info exit: 0
Traceback (most recent call last):
File "/Users/saphid/projects/steam-frame-moresrc/ui/apk_sources/_web.py", line 47, in read
with open_url(url, hosts, headers) as r:
File "/Users/saphid/projects/steam-frame-moresrc/ui/apk_sources/_web.py", line 37, in open_url
return urllib.request.build_opener(Redirect(hosts)).open(
File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/urllib/request.py", line 523, in open
response = meth(req, response)
File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/urllib/request.py", line 632, in http_response
response = self.parent.error(
File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/urllib/request.py", line 561, in error
return self._call_chain(*args)
File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/urllib/request.py", line 494, in _call_chain
result = func(*args)
File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/urllib/request.py", line 641, in http_error_default
raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 429: Too Many Requests
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/Users/saphid/projects/steam-frame-moresrc/.claude/prove-more-sources.py", line 18, in <module>
entries = itch.search(itch.sources()[0], 'off nominal')
File "/Users/saphid/projects/steam-frame-moresrc/ui/apk_sources/itch.py", line 58, in search
for entry in _parse(source, _web.read(url, ('itch.io',))):
File "/Users/saphid/projects/steam-frame-moresrc/ui/apk_sources/_web.py", line 62, in read
raise SourceError('Source refused access or reached its rate limit; try later (GitHub accepts FRAME_GITHUB_TOKEN)') from e
apk_sources.SourceError: Source refused access or reached its rate limit; try later (GitHub accepts FRAME_GITHUB_TOKEN)
+24
View File
@@ -0,0 +1,24 @@
import json, os, subprocess, sys
from pathlib import Path
sys.path.insert(0, str(Path.cwd() / 'ui'))
from apk_sources import github, itch, _web
proof = Path.cwd() / '.claude' / 'proof-cache'
proof.mkdir(exist_ok=True)
_web.cache = lambda: str(proof)
token = subprocess.run(['gh', 'auth', 'token'], capture_output=True, text=True, check=True).stdout.strip()
os.environ['FRAME_GITHUB_TOKEN'] = token
s = github.sources()[0]
entries = github.search(s, 'hello')
print('GitHub search:', json.dumps(entries))
r = github.download(s, entries[0]['id'])
print('GitHub download:', json.dumps(r))
p = subprocess.run(['python3', 'ui/frame_android.py', 'info', r['apk']])
print('info exit:', p.returncode)
assert p.returncode == 0
entries = itch.search(itch.sources()[0], 'off nominal')
print('itch.io search:', json.dumps(entries))
assert entries and entries[0]['downloadable'] is False
try:
itch.download(itch.sources()[0], entries[0]['id'])
except Exception as e:
print('itch.io download correctly refused:', e)
+5
View File
@@ -0,0 +1,5 @@
................................................................................................................................................................................
----------------------------------------------------------------------
Ran 176 tests in 5.879s
OK
+103
View File
@@ -0,0 +1,103 @@
# Developer-consented APK sources
Surveyed 2026-09-28. Free access is not proof of redistribution permission or
Frame compatibility. These adapters fetch only public publisher releases or
link to publisher pages. They do not acquire store entitlements, defeat access
checks, install anything, or rehost APKs. See [VR compatibility](vr-apks.md).
| Source | Developer consent and automated-access position | API/feed; VR coverage | Decision |
|---|---|---|---|
| [itch.io](https://itch.io/docs/legal/terms) | Publishers warrant distribution rights (§4). Users may access content through the service; this is not blanket scraping permission. Main robots excludes `/game/download/`; author subdomains exclude `/*/download/`. No challenge bypass. | Public free Android RSS for `openxr` and `oculus-quest`; substantial indie VR. Server API is mostly authenticated publisher/account functionality, not a general anonymous store-download API. | Implement RSS search, artwork and page links; `downloadable: False`. The supplied free-download script follows keyed download pages excluded by robots, so it is not shipped. |
| [GitHub releases](https://docs.github.com/en/rest/releases/releases) | Maintainers publish assets; curated repositories below establish provenance. Public hosting or an open-source topic alone does not establish rights to every uploaded binary. Use supported REST API under [API terms](https://docs.github.com/en/site-policy/github-terms/github-terms-of-service#h-api-terms), not HTML crawling. | Releases API includes APK assets and sometimes SHA-256. Topic search finds OpenXR/Quest projects. 60 unauthenticated requests/hour; authenticated user limits are generally 5,000/hour, with separate search/secondary limits. | Implement curated downloads and explicit topic discovery. Unreviewed topic results are page-only. |
| [Uptodown](https://www.uptodown.com/aboutus) | Developer distribution program exists, but that does not prove publisher authorization for every catalog item. [Privacy policy](https://www.uptodown.com/aboutus/privacy) explicitly describes protection against automated access. General automation permission was not established. | Broad Android catalog, limited VR focus; no supported public consumer-download API established in this survey. | Page links only; no downloader. Do not infer consent from an unchanged APK signature. |
| [APKPure](https://apkpure.com/terms) | Third-party APK catalog; individual publisher consent and automation rights were not established. Terms request returned HTTP 403; no bypass attempted. | Broad Android coverage, incidental VR; internal endpoints are not permission to automate. | Exclude automatic indexing/downloading; user may open site. |
| [APKMirror](https://www.apkmirror.com/faq/) | Publisher-signed files and a free-app policy are not a blanket developer-consent or automation grant. FAQ request returned HTTP 403, so current terms could not be confirmed. | General Android/version archive; APK bundles often need another installer; little VR focus. No supported consumer-download API established. | Page links only, no scraping or bundle conversion. |
| [Aptoide](https://en.aptoide.com/company/legal) | Terms define an app supplier as developer, owner or authorized distributor; user stores still require per-item provenance. API availability alone does not settle third-party access rights. | API ecosystem and general Android catalog; weak VR focus. | Defer until a publisher-owned store and its API terms can be approved. No blanket community-store downloader. |
| [Amazon Appstore](https://developer.amazon.com/docs/app-submission/understanding-submission.html) | Official developer submissions; store account, device and license rules apply. Publisher submission APIs do not authorize public binary extraction. | Fire-device distribution; Android-device Appstore support ended in 2025; little Quest relevance. | Official product links only; no account or entitlement extraction. |
| [PICO / ByteDance store](https://developer.picoxr.com/document/distribute) | Official publisher channel with store/device entitlements. No public unauthenticated binary-download grant established; documentation request encountered a redirect error. | Strong standalone VR; PICO builds may depend on PICO services/extensions. | Store links only. A developer's independently published GitHub/itch build can qualify separately. |
| [Meta Horizon Store / former App Lab](https://www.meta.com/experiences/) | Official developer submissions. A free store entitlement is still an entitlement; no license bypass or authenticated store extraction. App Lab was folded into the main store in 2024. | Strongest Quest coverage; no supported anonymous APK-download API established. | Store links only; independently distributed free builds use their publisher source. |
| [Khronos samples](https://github.com/KhronosGroup/OpenXR-SDK-Source) | Official upstream, Apache-2.0 sample; developer-published release APKs. GitHub API terms apply. | `hello_xr` Vulkan/OpenGL ES APKs; excellent OpenXR diagnostics. | Included in GitHub curated list, Vulkan variant selected. |
| [Meta OpenXR samples](https://github.com/meta-quest/Meta-OpenXR-SDK) | Official upstream; check each sample's license. Source availability does not imply a published APK, and some samples require Meta extensions/services. | Source/build examples, inconsistent ready-made APK releases. | Link to upstream; add specific free APKs only after release/provenance review. |
| [Godot XR demos](https://github.com/GodotVR/godot-xr-tools) | Official project source and publisher demo pages; licenses and dependencies vary by demo. | OpenXR examples on GitHub/itch. Older Godot builds can fail on Lepton's missing clipboard service. | Covered by source discovery; no compatibility promise from an OpenXR tag. |
The table distinguishes observed restrictions from unknown permission. An
unverified policy is a reason to defer automation, not a claim that a site is
unlawful. Only the two implemented source kinds are registered by their own
`sources()` functions; the other rows are recommendations, not new UI entries.
## Adapters
`ui/apk_sources/github.py` uses `github_curated.json`: Khronos `hello_xr`,
[Open Brush](https://github.com/icosa-foundation/open-brush), and
[SuperTux 3D](https://github.com/SgtBilko76/SuperTux-3D). These have official
OpenXR project/release evidence, not a blanket claim of headset compatibility.
Open Brush's compatibility evidence is recorded in [vr-apks.md](vr-apks.md).
Open Brush and SuperTux publish the selected builds as prereleases; curated
opt-ins preserve that label in version records. Exact APK filename patterns
avoid downloading desktop archives or alternate non-Quest builds.
[OpenSaberPlus](https://github.com/arpruss/OpenSaberPlus) was examined but not
curated: GitHub reports its license as `NOASSERTION`, and current OpenXR APK
provenance was not established in this pass.
Default GitHub search is offline against this small list. Queries
`topic:openxr`, `topic:oculus-quest`, and `topic:quest` explicitly call repository
search. Results outside the curated list stay page-only, even if a repository
claims an open-source license. This prevents an arbitrary tagged mirror from
becoming a trusted downloader. Extend the curated JSON after provenance review.
Set optional `FRAME_GITHUB_TOKEN` in the process environment for a higher API
quota. Tokens are sent only to `api.github.com`, never written to the cache,
never sent to asset hosts, and removed on redirects. The adapter does not
read `gh` credentials automatically. Metadata is cached for one hour under
`frame_host.cache_dir('apk-sources', 'publisher')`. A cold details request
fetches at most ten releases. Rate-limit errors are surfaced without retry
loops. Asset IDs and release tags are not Android version codes: metadata
leaves the latter unknown and rejects a requested `version_code` rather than
silently fetching a different build.
`itch.py` exposes separate OpenXR and Quest feed sources, so one feed's failure
does not suppress the other at the aggregator level. Queries filter the current
feed window locally: this is not an exhaustive historical itch search. Only
explicit zero-price Android entries are returned. Covers are exposed in
`images`; absent screenshots, APK version, ABI and minimum SDK stay unknown.
Curated GitHub entries include publisher artwork and plain-language summaries.
Repository image URLs are pinned to inspected commits. Open Brush screenshots
come from its README-linked Steam listing; SuperTux uses the upstream gameplay
preview embedded in the port's README (not a headset capture). The hello_xr
sample has a launcher icon and GitHub social banner; no published screenshot
was found in the inspected repository/README, so its screenshot list is empty.
Uncurated topic results use the owner's avatar and GitHub's repository social
preview. These are repository placeholders, not app screenshots. Itch's recorded
RSS includes only covers, so screenshot lists remain empty without page scraping. VR is
based on curated evidence or a VR-specific feed/topic, not a compatibility claim.
Downloads stream to unique temporary files, require an APK manifest entry,
restrict HTTPS origins and redirects, and enforce a 2 GiB ceiling. `verified`
means the downloaded SHA-256 matches GitHub's published digest. Without such a
digest, the computed SHA-256 is returned with `verified: False`; neither value
claims publisher-signature validation. Installation must inspect the APK as
usual. OBBs, split APKs, paid assets and external release-body download links
are unsupported.
## Evidence and limits
On this Mac, Python 3.9 downloaded the real Khronos Vulkan 1.1.63 APK through
the GitHub adapter, matched its published SHA-256
`f24bbe8ba6f6339fca658628868ba8189cbc33390d6ac508f69d76fb67b5fa34`, and
`python3 ui/frame_android.py info <apk>` exited 0: package
`org.khronos.openxr.hello_xr.vulkan`, version code 1063, minimum API 24,
arm64-v8a present, OpenXR detected. No Frame connection or installation occurred.
The itch OpenXR RSS was fetched successfully and recorded as a fixture.
Subsequent live adapter search encountered HTTP 429; it is not claimed as a
successful live end-to-end search. Fixture search finds Off Nominal and parses
nine Android entries from the ten-item feed (one has only an HTML platform).
A real itch download and APK inspection were deliberately not performed:
robots restrictions take precedence over that requested proof. No current
policy text is claimed verified where the table records failed access.
Tests use recorded, reduced API/RSS fixtures with network access blocked in
the new test class. They cover selection, prereleases, unknown topic results,
paid/non-Android exclusion, URL restrictions, redirect credential removal,
caching, rate limits, checksum mismatch, non-APK rejection and partial-file
cleanup. See `.claude/NOTES-more-sources.md` for commands and local evidence.
+23
View File
@@ -0,0 +1,23 @@
Recorded 2026-09-28 from public publisher endpoints using FrameControl/0.1 or
`gh api`. JSON fixtures are reduced to fields consumed by the adapters; API
values are unchanged. No token, cookies or signed download URL is included.
- `*-releases.json`: `/repos/{repo}/releases?per_page=10`, first two releases,
for KhronosGroup/OpenXR-SDK-Source, icosa-foundation/open-brush and
SgtBilko76/SuperTux-3D (one release).
- `topic.json`: `/search/repositories?q=topic:openxr+archived:false&sort=stars&per_page=3`.
- `itch-feed.txt`: `https://itch.io/games/free/platform-android/tag-openxr.xml`.
- `itch-robots.txt`: `https://itch.io/robots.txt`.
- `itch-author-robots.txt`: `https://godotvr.itch.io/robots.txt`.
The synthetic ZIP in tests is only a transport/integrity fixture, not an
installable APK. Actual APK parsing was verified separately on the downloaded
Khronos Vulkan sample; see docs/apk-sources.md.
Artwork follow-up: `topic.json` now retains `owner.avatar_url` from authenticated
repository API reads. `artwork-check.json` records HTTPS response status,
Content-Type and image magic checks for all curated URLs and three topic
results. All curated URLs returned real images; LWJGL's social preview returned
HTTP 429. This fixture is evidence of a point-in-time check, not an uptime test.
Open Brush screenshots came from the Steam appdetails response for app 1634870,
linked by its README. SuperTux's README links the recorded upstream screenshot.
+90
View File
@@ -0,0 +1,90 @@
[
{
"url": "https://avatars.githubusercontent.com/u/2757344?v=4",
"status": 200,
"content_type": "image/png",
"image": true
},
{
"url": "https://avatars.githubusercontent.com/u/784805?v=4",
"status": 200,
"content_type": "image/png",
"image": true
},
{
"url": "https://avatars.githubusercontent.com/u/94376830?v=4",
"status": 200,
"content_type": "image/png",
"image": true
},
{
"url": "https://opengraph.githubassets.com/1/KhronosGroup/OpenXR-SDK-Source",
"status": 200,
"content_type": "image/png",
"image": true
},
{
"url": "https://opengraph.githubassets.com/1/LWJGL/lwjgl3",
"error": "HTTP Error 429: Too Many Requests"
},
{
"url": "https://opengraph.githubassets.com/1/bjornbytes/lovr",
"status": 200,
"content_type": "image/png",
"image": true
},
{
"url": "https://opengraph.githubassets.com/1/sahibzada-allahyar/YC-Killer",
"status": 200,
"content_type": "image/png",
"image": true
},
{
"url": "https://raw.githubusercontent.com/KhronosGroup/OpenXR-SDK-Source/3ed64d0f9bb680f24b80a085091e5c8fab38f7b7/src/tests/hello_xr/android_resources/vulkan/mipmap-xxxhdpi/ic_helloxr_launcher.png",
"status": 200,
"content_type": "image/png",
"image": true
},
{
"url": "https://raw.githubusercontent.com/SgtBilko76/SuperTux-3D/1955493ee6f1000e048c58db40d4904df827210e/data/images/engine/icons/supertux-256x256.png",
"status": 200,
"content_type": "image/png",
"image": true
},
{
"url": "https://raw.githubusercontent.com/icosa-foundation/open-brush/56acbce831c7e9f257bfee9e21853da99773787b/Assets/Resources/DefaultImages/OpenBrushLogo.png",
"status": 200,
"content_type": "image/png",
"image": true
},
{
"url": "https://raw.githubusercontent.com/icosa-foundation/open-brush/56acbce831c7e9f257bfee9e21853da99773787b/open-brush.png",
"status": 200,
"content_type": "image/png",
"image": true
},
{
"url": "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/1634870/ss_0a9c208e26a43cf34879c2ca361d4c8f18af8cba.1920x1080.jpg",
"status": 200,
"content_type": "image/jpeg",
"image": true
},
{
"url": "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/1634870/ss_19b25b86ef55c0d8769a65135d60eaae8fa40553.1920x1080.jpg",
"status": 200,
"content_type": "image/jpeg",
"image": true
},
{
"url": "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/1634870/ss_785ea37d63378146dfe0f0ffa3f1d5c155ca978f.1920x1080.jpg",
"status": 200,
"content_type": "image/jpeg",
"image": true
},
{
"url": "https://www.supertux.org/images/0_7_0/github_preview.png",
"status": 200,
"content_type": "image/png",
"image": true
}
]
+88
View File
@@ -0,0 +1,88 @@
[
{
"tag_name": "2.32.29",
"draft": false,
"prerelease": true,
"published_at": "2026-09-26T17:49:28Z",
"assets": [
{
"id": 591143285,
"name": "OpenBrush_Android_2.32.29.apk",
"size": 314602794,
"browser_download_url": "https://github.com/icosa-foundation/open-brush/releases/download/2.32.29/OpenBrush_Android_2.32.29.apk",
"digest": "sha256:f20361b830803a2bf53e2af648bba59ee17bc4615bde534dbf6c4f0012bbd291"
},
{
"id": 591143287,
"name": "OpenBrush_Desktop_2.32.29.zip",
"size": 380735034,
"browser_download_url": "https://github.com/icosa-foundation/open-brush/releases/download/2.32.29/OpenBrush_Desktop_2.32.29.zip",
"digest": "sha256:3b680b07ca0b8def579fe194916aa7db4d007c3094c4dea818124776098c9b9a"
},
{
"id": 591143282,
"name": "OpenBrush_Linux_2.32.29.zip",
"size": 364906490,
"browser_download_url": "https://github.com/icosa-foundation/open-brush/releases/download/2.32.29/OpenBrush_Linux_2.32.29.zip",
"digest": "sha256:e380934f77d2b1441179424193a75f7b4b5793b34761c04cbf2d87bad9f8fc16"
},
{
"id": 591143283,
"name": "OpenBrush_Mac_2.32.29.dmg",
"size": 373196471,
"browser_download_url": "https://github.com/icosa-foundation/open-brush/releases/download/2.32.29/OpenBrush_Mac_2.32.29.dmg",
"digest": "sha256:5d544d0a64bed1cae65173fcfa7ada069d4f81b42385e806e99cc4427ef3513c"
},
{
"id": 591143286,
"name": "OpenBrush_Quest_2.32.29.apk",
"size": 314603546,
"browser_download_url": "https://github.com/icosa-foundation/open-brush/releases/download/2.32.29/OpenBrush_Quest_2.32.29.apk",
"digest": "sha256:57cd7b9067689060451494e55dc06276f934a992f5cbbd44d965069903937ba6"
}
]
},
{
"tag_name": "2.32.28",
"draft": false,
"prerelease": true,
"published_at": "2026-09-26T14:42:27Z",
"assets": [
{
"id": 590837298,
"name": "OpenBrush_Android_2.32.28.apk",
"size": 314603450,
"browser_download_url": "https://github.com/icosa-foundation/open-brush/releases/download/2.32.28/OpenBrush_Android_2.32.28.apk",
"digest": "sha256:1a3af1a194c4348ef67c8ea61d9a8258e2490cdfe1f760cbc3c69f2978a6a082"
},
{
"id": 590837301,
"name": "OpenBrush_Desktop_2.32.28.zip",
"size": 380738236,
"browser_download_url": "https://github.com/icosa-foundation/open-brush/releases/download/2.32.28/OpenBrush_Desktop_2.32.28.zip",
"digest": "sha256:c2de5424bc022951f0e0bdbd652ede549a1e60d9cfbf41c730ea43d16d97262f"
},
{
"id": 590837297,
"name": "OpenBrush_Linux_2.32.28.zip",
"size": 364907046,
"browser_download_url": "https://github.com/icosa-foundation/open-brush/releases/download/2.32.28/OpenBrush_Linux_2.32.28.zip",
"digest": "sha256:29daf410347b3ca36e47808e31172270df8040ba7decc83be2bdcd51de895e06"
},
{
"id": 590837296,
"name": "OpenBrush_Mac_2.32.28.dmg",
"size": 373195966,
"browser_download_url": "https://github.com/icosa-foundation/open-brush/releases/download/2.32.28/OpenBrush_Mac_2.32.28.dmg",
"digest": "sha256:2f352d766450c993fdcae8a8552878a6a976d32d675246b63c81bb1b326fd20d"
},
{
"id": 590837295,
"name": "OpenBrush_Quest_2.32.28.apk",
"size": 314604234,
"browser_download_url": "https://github.com/icosa-foundation/open-brush/releases/download/2.32.28/OpenBrush_Quest_2.32.28.apk",
"digest": "sha256:9a3af6a6e838dd8bd201e549c5570f67db794df940e960390aeeae93235d702e"
}
]
}
]
+12
View File
@@ -0,0 +1,12 @@
User-agent: Mediapartners-Google
Disallow:
User-agent: *
Disallow: /*/download/
Disallow: /*/rh/
Disallow: /*/rp/
Disallow: /-/
Sitemap: https://itch.io/sitemap.xml
# vim: set ft=robots:
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?><rss version="2.0"><channel><title>Top free games for Android tagged openxr - itch.io</title><link>https://itch.io/games/free/platform-android/tag-openxr</link><item><guid>https://leandrodreamer.itch.io/open-saber</guid><title>Open Saber [Free] [Rhythm] [Windows] [Linux] [Android]</title><plainTitle>Open Saber</plainTitle><imageurl>https://img.itch.zone/aW1nLzEzMzgzMzgzLmdpZg==/original/P7L1sC.gif</imageurl><price>$0.00</price><currency>USD</currency><link>https://leandrodreamer.itch.io/open-saber</link><description><![CDATA[Open Source Rythmic Block Cutting Game :)
<img src="https://img.itch.zone/aW1nLzEzMzgzMzgzLmdpZg==/original/P7L1sC.gif" alt="Open Saber"/>]]></description><pubDate>Thu, 07 Sep 2023 02:11:50 GMT</pubDate><createDate>Thu, 07 Sep 2023 02:11:50 GMT</createDate><updateDate>Wed, 08 Jan 2025 02:24:29 GMT</updateDate><platforms><html>yes</html></platforms></item><item><guid>https://absyo.itch.io/off-nominal</guid><title>Off Nominal [Free] [Puzzle] [Windows] [Linux] [Android]</title><plainTitle>Off Nominal</plainTitle><imageurl>https://img.itch.zone/aW1nLzMwMjk0MDA1LnBuZw==/315x250%23c/QSbOIy.png</imageurl><price>$0.00</price><currency>USD</currency><link>https://absyo.itch.io/off-nominal</link><description><![CDATA[Adrift in space. Fix the ship. Return home.
<img src="https://img.itch.zone/aW1nLzMwMjk0MDA1LnBuZw==/315x250%23c/QSbOIy.png" alt="Off Nominal"/>]]></description><pubDate>Fri, 25 Sep 2026 19:54:48 GMT</pubDate><createDate>Fri, 25 Sep 2026 19:54:48 GMT</createDate><updateDate>Sun, 27 Sep 2026 23:25:20 GMT</updateDate><platforms><windows>yes</windows><linux>yes</linux><android>yes</android></platforms></item><item><guid>https://somar-project.itch.io/somar-project</guid><title>Somar-project [Free] [Educational] [Android]</title><plainTitle>Somar-project</plainTitle><imageurl>https://img.itch.zone/aW1nLzIwNDM2MzM1LnBuZw==/315x250%23c/Xswj5t.png</imageurl><price>$0.00</price><currency>USD</currency><link>https://somar-project.itch.io/somar-project</link><description><![CDATA[Open-Source OpenXR application for raising awareness about negative impacts of underwater noise pollution on marine life
<img src="https://img.itch.zone/aW1nLzIwNDM2MzM1LnBuZw==/315x250%23c/Xswj5t.png" alt="Somar-project"/>]]></description><pubDate>Wed, 26 Mar 2025 17:17:58 GMT</pubDate><createDate>Wed, 26 Mar 2025 17:17:58 GMT</createDate><updateDate>Fri, 28 Mar 2025 15:52:59 GMT</updateDate><platforms><android>yes</android></platforms></item><item><guid>https://5imon.itch.io/buggenesis</guid><title>Bug Genesis VR [Free] [Interactive Fiction] [Windows] [Android]</title><plainTitle>Bug Genesis VR</plainTitle><imageurl>https://img.itch.zone/aW1nLzIxMzYzODczLmpwZw==/315x250%23c/LVpznb.jpg</imageurl><price>$0.00</price><currency>USD</currency><link>https://5imon.itch.io/buggenesis</link><description><![CDATA[Use the bug generator to populate the planet
<img src="https://img.itch.zone/aW1nLzIxMzYzODczLmpwZw==/315x250%23c/LVpznb.jpg" alt="Bug Genesis VR"/>]]></description><pubDate>Sun, 25 May 2025 20:22:49 GMT</pubDate><createDate>Sun, 25 May 2025 20:22:49 GMT</createDate><updateDate>Sun, 25 May 2025 20:37:39 GMT</updateDate><platforms><windows>yes</windows><android>yes</android></platforms></item><item><guid>https://benmclean.itch.io/wolfsharp</guid><title>WolfSharp [Free] [Shooter] [Windows] [Linux] [Android]</title><plainTitle>WolfSharp</plainTitle><imageurl>https://img.itch.zone/aW1nLzI4NzMyNjQyLnBuZw==/315x250%23c/heg%2BmL.png</imageurl><price>$0.00</price><currency>USD</currency><link>https://benmclean.itch.io/wolfsharp</link><description><![CDATA[Wolfenstein 3-D for VR
<img src="https://img.itch.zone/aW1nLzI4NzMyNjQyLnBuZw==/315x250%23c/heg%2BmL.png" alt="WolfSharp"/>]]></description><pubDate>Sat, 25 Jul 2026 12:16:01 GMT</pubDate><createDate>Sat, 25 Jul 2026 12:16:01 GMT</createDate><updateDate>Sat, 25 Jul 2026 13:45:38 GMT</updateDate><platforms><windows>yes</windows><linux>yes</linux><android>yes</android></platforms></item><item><guid>https://mimekunst.itch.io/winter-solitude-vr</guid><title>Winter Solitude VR [Free] [Simulation] [Windows] [macOS] [Linux] [Android]</title><plainTitle>Winter Solitude VR</plainTitle><imageurl>https://img.itch.zone/aW1nLzE0NDA4NzQxLnBuZw==/315x250%23c/EOaygC.png</imageurl><price>$0.00</price><currency>USD</currency><link>https://mimekunst.itch.io/winter-solitude-vr</link><description><![CDATA[Step into the Frozen Abyss: FREE VR Game Experience
<img src="https://img.itch.zone/aW1nLzE0NDA4NzQxLnBuZw==/315x250%23c/EOaygC.png" alt="Winter Solitude VR"/>]]></description><pubDate>Tue, 19 Dec 2023 19:19:59 GMT</pubDate><createDate>Tue, 19 Dec 2023 19:19:59 GMT</createDate><updateDate>Wed, 20 Dec 2023 22:49:23 GMT</updateDate><platforms><windows>yes</windows><osx>yes</osx><linux>yes</linux><android>yes</android></platforms></item><item><guid>https://salmondev.itch.io/evil-miner-vr</guid><title>EVIL MINER VR [Free] [Other] [Windows] [Android]</title><plainTitle>EVIL MINER VR</plainTitle><imageurl>https://img.itch.zone/aW1nLzIxNjY2NDA0LnBuZw==/315x250%23c/n4bF8N.png</imageurl><price>$0.00</price><currency>USD</currency><link>https://salmondev.itch.io/evil-miner-vr</link><description><![CDATA[
<img src="https://img.itch.zone/aW1nLzIxNjY2NDA0LnBuZw==/315x250%23c/n4bF8N.png" alt="EVIL MINER VR"/>]]></description><pubDate>Fri, 13 Jun 2025 16:48:01 GMT</pubDate><createDate>Fri, 13 Jun 2025 16:48:01 GMT</createDate><updateDate>Sun, 15 Jun 2025 15:19:53 GMT</updateDate><platforms><windows>yes</windows><android>yes</android></platforms></item><item><guid>https://robinhuud.itch.io/winter-challenge-north-pole-defense</guid><title>North Pole Defense VR [Free] [Action] [Android]</title><plainTitle>North Pole Defense VR</plainTitle><imageurl>https://img.itch.zone/aW1nLzc2NzU1ODMucG5n/315x250%23c/71b4aj.png</imageurl><price>$0.00</price><currency>USD</currency><link>https://robinhuud.itch.io/winter-challenge-north-pole-defense</link><description><![CDATA[Defend the north pole against giant snowmen using VR snowballs
<img src="https://img.itch.zone/aW1nLzc2NzU1ODMucG5n/315x250%23c/71b4aj.png" alt="North Pole Defense VR"/>]]></description><pubDate>Thu, 16 Dec 2021 01:33:33 GMT</pubDate><createDate>Thu, 16 Dec 2021 01:33:33 GMT</createDate><updateDate>Thu, 16 Dec 2021 01:51:29 GMT</updateDate><platforms><android>yes</android></platforms></item><item><guid>https://envemos.itch.io/ambly-native-xr</guid><title>Ambly Native XR [Free] [Linux] [Android]</title><plainTitle>Ambly Native XR</plainTitle><imageurl>https://img.itch.zone/aW1nLzI4NzEwMTc0LmpwZw==/315x250%23c/4XHeya.jpg</imageurl><price>$0.00</price><currency>USD</currency><link>https://envemos.itch.io/ambly-native-xr</link><description><![CDATA[OpenXR games for Meta Quest, PICO and Linux.
<img src="https://img.itch.zone/aW1nLzI4NzEwMTc0LmpwZw==/315x250%23c/4XHeya.jpg" alt="Ambly Native XR"/>]]></description><pubDate>Wed, 22 Jul 2026 18:38:55 GMT</pubDate><createDate>Wed, 22 Jul 2026 18:38:55 GMT</createDate><updateDate>Fri, 07 Aug 2026 16:04:20 GMT</updateDate><platforms><linux>yes</linux><android>yes</android></platforms></item><item><guid>https://andyman404.itch.io/glow-up-garden</guid><title>Glow Up Garden (VR) [Free] [Action] [Windows] [Android]</title><plainTitle>Glow Up Garden (VR)</plainTitle><imageurl>https://img.itch.zone/aW1nLzE2NDE3NjE3LmpwZw==/315x250%23c/nHkM4g.jpg</imageurl><price>$0.00</price><currency>USD</currency><link>https://andyman404.itch.io/glow-up-garden</link><description><![CDATA[Speak positive affirmations out loud to grow your garden (voice-driven VR game)
<img src="https://img.itch.zone/aW1nLzE2NDE3NjE3LmpwZw==/315x250%23c/nHkM4g.jpg" alt="Glow Up Garden (VR)"/>]]></description><pubDate>Mon, 03 Jun 2024 20:36:53 GMT</pubDate><createDate>Mon, 03 Jun 2024 20:36:53 GMT</createDate><updateDate>Tue, 04 Jun 2024 04:20:37 GMT</updateDate><platforms><windows>yes</windows><android>yes</android></platforms></item></channel></rss>
+11
View File
@@ -0,0 +1,11 @@
User-agent: *
Disallow: /embed/
Disallow: /embed-upload/
Disallow: /search
Disallow: /checkout/
Disallow: /game/download/
Disallow: /bundle/download/
Disallow: /register-for-purchase/
Disallow: /email-feedback/
Sitemap: https://itch.io/sitemap.xml
+410
View File
@@ -0,0 +1,410 @@
[
{
"tag_name": "release-1.1.63",
"draft": false,
"prerelease": false,
"published_at": "2026-09-02T21:22:32Z",
"assets": [
{
"id": 541779948,
"name": "apilayer_api_dump-1.1.63.aar",
"size": 5120886,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/apilayer_api_dump-1.1.63.aar",
"digest": "sha256:9cab975cc8df3a99f6530f47a1fbabfa0527109a138f5a3ef5150672a658c61c"
},
{
"id": 541779969,
"name": "apilayer_api_dump-1.1.63.aar.asc",
"size": 215,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/apilayer_api_dump-1.1.63.aar.asc",
"digest": "sha256:419ec65d3f526c617176f272d8a481488181ade017cb05ef42205cb2c5a86f05"
},
{
"id": 541779983,
"name": "apilayer_api_dump-1.1.63.pom",
"size": 1462,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/apilayer_api_dump-1.1.63.pom",
"digest": "sha256:9a5b3e470830ae471fe317bc63f43b33bc063458239238fe27e234232c45ff28"
},
{
"id": 541780002,
"name": "apilayer_api_dump-1.1.63.pom.asc",
"size": 215,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/apilayer_api_dump-1.1.63.pom.asc",
"digest": "sha256:7cbf9f1af504ca68fc36e0985dadb74d1fbf4d2bbdcde3e00bfe9ea6168fc4a8"
},
{
"id": 541780126,
"name": "apilayer_best_practices_validation-1.1.63.aar",
"size": 484596,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/apilayer_best_practices_validation-1.1.63.aar",
"digest": "sha256:0c56cb3dc0b093b28f4e5490820d3cb3f7b201b48444134f347455d4ee99abd2"
},
{
"id": 541780150,
"name": "apilayer_best_practices_validation-1.1.63.aar.asc",
"size": 215,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/apilayer_best_practices_validation-1.1.63.aar.asc",
"digest": "sha256:7eb9ab3efe939c367e4661d5a75836c1d9e0799ab627e99211253546935defa7"
},
{
"id": 541780162,
"name": "apilayer_best_practices_validation-1.1.63.pom",
"size": 1487,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/apilayer_best_practices_validation-1.1.63.pom",
"digest": "sha256:97d410936f5f051ab2a73cdac196c744ac56b2dde6279a5e46e944ed61316147"
},
{
"id": 541780192,
"name": "apilayer_best_practices_validation-1.1.63.pom.asc",
"size": 215,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/apilayer_best_practices_validation-1.1.63.pom.asc",
"digest": "sha256:7f9fa0cd33627c4ecc2a4410e53dedadb5731b3cddae59a3c0d4fcd467b3472d"
},
{
"id": 541780025,
"name": "apilayer_core_validation-1.1.63.aar",
"size": 6386964,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/apilayer_core_validation-1.1.63.aar",
"digest": "sha256:9663ce94a5076b6707502cf5503bac456987ccf1f39a3f7c8f350d4521db8647"
},
{
"id": 541780057,
"name": "apilayer_core_validation-1.1.63.aar.asc",
"size": 215,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/apilayer_core_validation-1.1.63.aar.asc",
"digest": "sha256:c6e75df848ca6d436fe24f680bde73a9e69972b67eef46e49aba4b77dec366e9"
},
{
"id": 541780090,
"name": "apilayer_core_validation-1.1.63.pom",
"size": 1455,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/apilayer_core_validation-1.1.63.pom",
"digest": "sha256:6aa9337f5e645baf489c05e562cd074dc696bad87db70a0cdd661dffc63b2c89"
},
{
"id": 541780108,
"name": "apilayer_core_validation-1.1.63.pom.asc",
"size": 215,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/apilayer_core_validation-1.1.63.pom.asc",
"digest": "sha256:0fe8ded9fdf0660bf28a544ae405b9b58682a8d9648dacedf4e4ceef2f827869"
},
{
"id": 541777706,
"name": "hello_xr-OpenGLES-release-1.1.63.apk",
"size": 9557049,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/hello_xr-OpenGLES-release-1.1.63.apk",
"digest": "sha256:7c96022ac002cb0c72e3cd14c11a817767b7b5dbdf5a1d1c85d0c083b5719056"
},
{
"id": 541777527,
"name": "hello_xr-Vulkan-release-1.1.63.apk",
"size": 9557441,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/hello_xr-Vulkan-release-1.1.63.apk",
"digest": "sha256:f24bbe8ba6f6339fca658628868ba8189cbc33390d6ac508f69d76fb67b5fa34"
},
{
"id": 541800362,
"name": "OpenXR-SDK-Source-release-1.1.63.tar.gz",
"size": 4857593,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/OpenXR-SDK-Source-release-1.1.63.tar.gz",
"digest": "sha256:a3b97a36f11abe256a7ea1668a0a468aac9b738e94bea6b468f0ae31ad537a46"
},
{
"id": 541800378,
"name": "OpenXR-SDK-Source-release-1.1.63.tar.gz.asc",
"size": 870,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/OpenXR-SDK-Source-release-1.1.63.tar.gz.asc",
"digest": "sha256:4f97028306ae219f599e9960adbf9bb072e8da2bfbedffd1f0de312516a61f87"
},
{
"id": 541821806,
"name": "OpenXR.Loader.1.1.63.nupkg",
"size": 1916162,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/OpenXR.Loader.1.1.63.nupkg",
"digest": "sha256:4e5a50a8807ef66f25180ff224e7d8150b594aa8ee4b07590f9ade55a8e98703"
},
{
"id": 541821827,
"name": "OpenXR.Loader.1.1.63.nupkg.asc",
"size": 870,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/OpenXR.Loader.1.1.63.nupkg.asc",
"digest": "sha256:9d66a6e958f7c2d5c4a0a4aef8df90a7beecab13547440c9fa2069979eab7ac7"
},
{
"id": 541779892,
"name": "openxr_loader_for_android-1.1.63-sources.jar",
"size": 1141287,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/openxr_loader_for_android-1.1.63-sources.jar",
"digest": "sha256:6f964ad09c4afa3f42f451cada86e61503392b018660b22dd34b61dfbf71a555"
},
{
"id": 541779920,
"name": "openxr_loader_for_android-1.1.63-sources.jar.asc",
"size": 215,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/openxr_loader_for_android-1.1.63-sources.jar.asc",
"digest": "sha256:b98cba20f5c3b202b887307cb19196f4459f895413e55b68823a606f50d045fa"
},
{
"id": 541779720,
"name": "openxr_loader_for_android-1.1.63.aar",
"size": 4170279,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/openxr_loader_for_android-1.1.63.aar",
"digest": "sha256:622419d2f6741c3443a3beb4779af0764318edd01830de967f24c741ebcded73"
},
{
"id": 541779762,
"name": "openxr_loader_for_android-1.1.63.aar.asc",
"size": 215,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/openxr_loader_for_android-1.1.63.aar.asc",
"digest": "sha256:3bb68b26d7def68b4fe8506bf09f302116290c2de9cf91fdfdba753978bff5ed"
},
{
"id": 541779849,
"name": "openxr_loader_for_android-1.1.63.pom",
"size": 1598,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/openxr_loader_for_android-1.1.63.pom",
"digest": "sha256:c98f38fa8acf4cf1bd8bcb40774f9815ae6ed9da94c8a7be2ed9db7815c85404"
},
{
"id": 541779867,
"name": "openxr_loader_for_android-1.1.63.pom.asc",
"size": 215,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/openxr_loader_for_android-1.1.63.pom.asc",
"digest": "sha256:1c2625f5b889c7ed967c8a6010294ca94bbd96091d879b2fc989c6f40f9a6af1"
},
{
"id": 541793000,
"name": "openxr_loader_macos-1.1.63.zip",
"size": 826298,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/openxr_loader_macos-1.1.63.zip",
"digest": "sha256:b243eebcdfa8683d17ccc8cfbfb9036be94b3f5a22b3eb73c39da0877694a3ab"
},
{
"id": 541793027,
"name": "openxr_loader_macos-1.1.63.zip.asc",
"size": 870,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/openxr_loader_macos-1.1.63.zip.asc",
"digest": "sha256:3e95172aabc4bd2537a7125060abbb8efbdd0cee19bdf1bf30cbd9736b7dcbd2"
},
{
"id": 541784717,
"name": "openxr_loader_windows-1.1.63.zip",
"size": 31961521,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/openxr_loader_windows-1.1.63.zip",
"digest": "sha256:01c631aeabbfe0879540f77ef833416c532a20746285b494630160c23588b771"
},
{
"id": 541784760,
"name": "openxr_loader_windows-1.1.63.zip.asc",
"size": 870,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.63/openxr_loader_windows-1.1.63.zip.asc",
"digest": "sha256:e9384e2a94d82c5059d1d83c57d0da585056d95e393255048b0955b0ce69b3fc"
}
]
},
{
"tag_name": "release-1.1.62",
"draft": false,
"prerelease": false,
"published_at": "2026-08-01T01:32:30Z",
"assets": [
{
"id": 500510340,
"name": "apilayer_api_dump-1.1.62.aar",
"size": 4800443,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/apilayer_api_dump-1.1.62.aar",
"digest": "sha256:2a7c2d1bb14d94dfed8c1aaf170a9dda649756477fbcba4a143c76d08a50bed8"
},
{
"id": 500510366,
"name": "apilayer_api_dump-1.1.62.aar.asc",
"size": 215,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/apilayer_api_dump-1.1.62.aar.asc",
"digest": "sha256:7ab53e3c1fcaabf49561737fe8ed5df9ad9a5a761615f05e1d5eed2799e85c5f"
},
{
"id": 500510378,
"name": "apilayer_api_dump-1.1.62.pom",
"size": 1462,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/apilayer_api_dump-1.1.62.pom",
"digest": "sha256:fcd4358d7582ce0787b96aacb9840afc086a28499767a6aa7491dbb682bcc921"
},
{
"id": 500510385,
"name": "apilayer_api_dump-1.1.62.pom.asc",
"size": 215,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/apilayer_api_dump-1.1.62.pom.asc",
"digest": "sha256:4832ce5c8835d1880ae5adbc535b774d50f8c86f9870650a50fbf3b9993ec5fa"
},
{
"id": 500510464,
"name": "apilayer_best_practices_validation-1.1.62.aar",
"size": 482607,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/apilayer_best_practices_validation-1.1.62.aar",
"digest": "sha256:6d1a369ba367049aff23ae554b284ccc17cb3be8832869de0c1d151228a26502"
},
{
"id": 500510477,
"name": "apilayer_best_practices_validation-1.1.62.aar.asc",
"size": 215,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/apilayer_best_practices_validation-1.1.62.aar.asc",
"digest": "sha256:658ecbb7f871e97ed0d659ed55b27ca6ff6cc6e1853699498ee7b1d5583d7313"
},
{
"id": 500510480,
"name": "apilayer_best_practices_validation-1.1.62.pom",
"size": 1487,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/apilayer_best_practices_validation-1.1.62.pom",
"digest": "sha256:571d7c5587075e83ca0a4d2382d87515de614ab8c34416a82a1b9b1a7eb5f9c0"
},
{
"id": 500510489,
"name": "apilayer_best_practices_validation-1.1.62.pom.asc",
"size": 215,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/apilayer_best_practices_validation-1.1.62.pom.asc",
"digest": "sha256:342d0ed7080e92399dbc8648df4fe9378086718f1abc73f79f3a52de211ed36e"
},
{
"id": 500510395,
"name": "apilayer_core_validation-1.1.62.aar",
"size": 5910024,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/apilayer_core_validation-1.1.62.aar",
"digest": "sha256:5692ccd5563a0963c4d842614af11d7c280960687a221643a46266ef968c4d70"
},
{
"id": 500510422,
"name": "apilayer_core_validation-1.1.62.aar.asc",
"size": 215,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/apilayer_core_validation-1.1.62.aar.asc",
"digest": "sha256:1e39ff48d4cd87231d931515968317c717a6811da84585650f0623c49329ec41"
},
{
"id": 500510432,
"name": "apilayer_core_validation-1.1.62.pom",
"size": 1455,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/apilayer_core_validation-1.1.62.pom",
"digest": "sha256:1917e5cee9b979c9032c7819c386ea62e4498c30ffbbcf0c25578ebcd0c1e441"
},
{
"id": 500510453,
"name": "apilayer_core_validation-1.1.62.pom.asc",
"size": 215,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/apilayer_core_validation-1.1.62.pom.asc",
"digest": "sha256:8aed84009daa5e388b7d179ab90837e9537b4f762a1676aab922e03dc633ed18"
},
{
"id": 497398718,
"name": "hello_xr-OpenGLES-release-1.1.62.apk",
"size": 9548745,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/hello_xr-OpenGLES-release-1.1.62.apk",
"digest": "sha256:da5e421795b801684cab50156c572422b73cd98fc8c75aeeb968962b43a2ab46"
},
{
"id": 497398704,
"name": "hello_xr-Vulkan-release-1.1.62.apk",
"size": 9549137,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/hello_xr-Vulkan-release-1.1.62.apk",
"digest": "sha256:a154b2353983f8c0cb1827ddf51d7e80fc105085253fe524502f35c5ddac3284"
},
{
"id": 500514717,
"name": "OpenXR-SDK-Source-release-1.1.62.tar.gz",
"size": 4834887,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/OpenXR-SDK-Source-release-1.1.62.tar.gz",
"digest": "sha256:977073d7f4c0d1af8ab975f57e4b6ffd1c4e9209be66075812b890576e0e1e5f"
},
{
"id": 500514735,
"name": "OpenXR-SDK-Source-release-1.1.62.tar.gz.asc",
"size": 870,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/OpenXR-SDK-Source-release-1.1.62.tar.gz.asc",
"digest": "sha256:3ea4d6e47da6a8b3480931636af3e85eb2e0cddaf582153ee97973a8b05a5214"
},
{
"id": 500514501,
"name": "OpenXR.Loader.1.1.62.nupkg",
"size": 1902954,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/OpenXR.Loader.1.1.62.nupkg",
"digest": "sha256:6bb16b4dbe3c11f29605def2def5b7d1177784c0403dc9a24bc2e13d7eb82604"
},
{
"id": 500514512,
"name": "OpenXR.Loader.1.1.62.nupkg.asc",
"size": 870,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/OpenXR.Loader.1.1.62.nupkg.asc",
"digest": "sha256:386dfd33e9c2db9c9c37d881b77eec9b74d181fda394b47c473b2bce37fd7005"
},
{
"id": 500510319,
"name": "openxr_loader_for_android-1.1.62-sources.jar",
"size": 1110593,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/openxr_loader_for_android-1.1.62-sources.jar",
"digest": "sha256:b83318394b30bb129b069dd854de2b1e21df4376dda1a7fa342aeaff17a71d3d"
},
{
"id": 500510327,
"name": "openxr_loader_for_android-1.1.62-sources.jar.asc",
"size": 215,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/openxr_loader_for_android-1.1.62-sources.jar.asc",
"digest": "sha256:838b5f5a23329eb7f0e13afde7a7d6ae73b439c7cbf6d3ec0af3e65efd7d5bd6"
},
{
"id": 500510263,
"name": "openxr_loader_for_android-1.1.62.aar",
"size": 4158815,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/openxr_loader_for_android-1.1.62.aar",
"digest": "sha256:c03c689fed9a48f9394af953660982c998b00f6d2d2d8d150bc3f890c75a7465"
},
{
"id": 500510280,
"name": "openxr_loader_for_android-1.1.62.aar.asc",
"size": 215,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/openxr_loader_for_android-1.1.62.aar.asc",
"digest": "sha256:883ccab775776c65ee35bf3120553f6a3f0f47de2dd661e074469e98d3caea46"
},
{
"id": 500510292,
"name": "openxr_loader_for_android-1.1.62.pom",
"size": 1598,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/openxr_loader_for_android-1.1.62.pom",
"digest": "sha256:9b1047158a416984fd6d60c472da09bb324aec74709f83a1516435917fa05064"
},
{
"id": 500510305,
"name": "openxr_loader_for_android-1.1.62.pom.asc",
"size": 215,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/openxr_loader_for_android-1.1.62.pom.asc",
"digest": "sha256:9dd7d3f79ad76a48f709f55d8c205892ae30f70b10a44c4afbd51f50603c4478"
},
{
"id": 500514048,
"name": "openxr_loader_macos-1.1.62.zip",
"size": 817438,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/openxr_loader_macos-1.1.62.zip",
"digest": "sha256:400bf9ab932d04cf8a315fe8e63d5cd9824015b64ad2e5d72b2ffc086c54313c"
},
{
"id": 500514061,
"name": "openxr_loader_macos-1.1.62.zip.asc",
"size": 870,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/openxr_loader_macos-1.1.62.zip.asc",
"digest": "sha256:034a3575bbee545926dc59558aa5d62ea9fc2de9e23c426ac640cbb68bd8db88"
},
{
"id": 500513308,
"name": "openxr_loader_windows-1.1.62.zip",
"size": 30975472,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/openxr_loader_windows-1.1.62.zip",
"digest": "sha256:800ec772e2f9448a26ab9f579f4914d984346dd9d0d7c007841abe21d2c8ff2f"
},
{
"id": 500513351,
"name": "openxr_loader_windows-1.1.62.zip.asc",
"size": 870,
"browser_download_url": "https://github.com/KhronosGroup/OpenXR-SDK-Source/releases/download/release-1.1.62/openxr_loader_windows-1.1.62.zip.asc",
"digest": "sha256:8117563bfc5092895e17112366cb954ca78f84964e5c09526dfd69656c1713fd"
}
]
}
]
+28
View File
@@ -0,0 +1,28 @@
{
"items": [
{
"full_name": "LWJGL/lwjgl3",
"name": "lwjgl3",
"description": "LWJGL is a Java library that enables cross-platform access to popular native APIs useful in the development of graphics (OpenGL, Vulkan, bgfx), audio (OpenAL, Opus), parallel computing (OpenCL, CUDA) and XR (OpenVR, LibOVR, OpenXR) applications.",
"owner": {
"avatar_url": "https://avatars.githubusercontent.com/u/2757344?v=4"
}
},
{
"full_name": "sahibzada-allahyar/YC-Killer",
"name": "YC-Killer",
"description": "A library of enterprise-grade AI agents designed to democratize artificial intelligence and provide free, open-source alternatives to overvalued Y Combinator startups.",
"owner": {
"avatar_url": "https://avatars.githubusercontent.com/u/94376830?v=4"
}
},
{
"full_name": "bjornbytes/lovr",
"name": "lovr",
"description": "Lua Virtual Reality Framework",
"owner": {
"avatar_url": "https://avatars.githubusercontent.com/u/784805?v=4"
}
}
]
}
+17
View File
@@ -0,0 +1,17 @@
[
{
"tag_name": "Beta0.2",
"draft": false,
"prerelease": true,
"published_at": "2026-09-23T14:51:47Z",
"assets": [
{
"id": 583977968,
"name": "SuperTux-Beta0.2-quest-pico-arm64-v8a.apk",
"size": 294152462,
"browser_download_url": "https://github.com/SgtBilko76/SuperTux-3D/releases/download/Beta0.2/SuperTux-Beta0.2-quest-pico-arm64-v8a.apk",
"digest": "sha256:63287e5d6f1866193e0d4730bf4a2ba87fd2fbdbe6317bd6bd24b96a8ddc9963"
}
]
}
]
+148
View File
@@ -0,0 +1,148 @@
import hashlib, io, json, os, sys, tempfile, unittest, urllib.error, urllib.request, zipfile
from pathlib import Path
from unittest.mock import patch
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'ui'))
from apk_sources import SourceError, github, itch, _web
FIX = Path(__file__).parent / 'fixtures' / 'more_sources'
class PublisherSources(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.addCleanup(self.tmp.cleanup)
self.cache = patch.object(_web, 'cache', return_value=self.tmp.name)
self.cache.start()
self.addCleanup(self.cache.stop)
self.network = patch('urllib.request.OpenerDirector.open', side_effect=AssertionError('network in test'))
self.network.start()
self.addCleanup(self.network.stop)
def test_curated_search_is_offline(self):
self.assertEqual(github.search(github.sources()[0], 'hello')[0]['id'], 'KhronosGroup/OpenXR-SDK-Source')
self.assertEqual(github.search(github.sources()[0], '', 0), [])
def test_curated_artwork_has_recorded_image_evidence(self):
evidence = {r['url']: r for r in json.loads((FIX / 'artwork-check.json').read_text())}
entries = github.search(github.sources()[0], '')
self.assertEqual(len(entries), 3)
for entry in entries:
self.assertTrue(entry['summary'])
images = entry['images']
self.assertEqual(entry['icon'], images['icon'])
for url in [images['icon'], images['banner']] + images['screenshots']:
self.assertTrue(url.startswith('https://'))
self.assertEqual(evidence[url]['status'], 200)
self.assertTrue(evidence[url]['image'])
self.assertTrue(entries[1]['images']['screenshots'])
self.assertTrue(entries[2]['images']['screenshots'])
def test_topic_keeps_curated_artwork(self):
curated = github._curated()[1]
repo = {'full_name': curated['repo'], 'name': 'open-brush',
'owner': {'avatar_url': 'https://avatars.githubusercontent.com/u/1'}}
with patch.object(github, '_api', return_value={'items': [repo]}):
entry = github.search(github.sources()[0], 'topic:openxr')[0]
self.assertEqual(entry['images'], curated['images'])
unknown = github.details(github.sources()[0], 'unknown/project')
self.assertEqual(unknown['icon'], 'https://github.com/unknown.png')
self.assertEqual(unknown['images']['banner'],
'https://opengraph.githubassets.com/1/unknown/project')
def test_real_releases(self):
for key, repo in [('khronos', 'KhronosGroup/OpenXR-SDK-Source'),
('brush', 'icosa-foundation/open-brush'), ('tux', 'SgtBilko76/SuperTux-3D')]:
with patch.object(github, '_api', return_value=json.loads((FIX / (key + '-releases.json')).read_text())):
e = github.details(github.sources()[0], repo)
self.assertTrue(e['downloadable'])
self.assertTrue(e['versions'][0]['name'].endswith('.apk'))
self.assertIsNone(e['version_code'])
search_entry = github.search(github.sources()[0], repo)[0]
self.assertEqual(e['images'], search_entry['images'])
self.assertEqual(e['icon'], e['images']['icon'])
with self.assertRaises(SourceError):
github.download(github.sources()[0], repo, 123)
def test_topic_results_need_approval(self):
data = json.loads((FIX / 'topic.json').read_text())
with patch.object(github, '_api', return_value=data):
entries = github.search(github.sources()[0], 'topic:openxr')
self.assertTrue(entries)
self.assertTrue(any(not e['downloadable'] for e in entries))
for entry, repo in zip(entries, data['items']):
self.assertEqual(entry['icon'], repo['owner']['avatar_url'])
self.assertEqual(entry['images']['icon'], entry['icon'])
self.assertEqual(entry['images']['banner'],
'https://opengraph.githubassets.com/1/' + repo['full_name'])
self.assertEqual(entry['images']['screenshots'], [])
self.assertFalse(github.details(github.sources()[0], 'unknown/project')['downloadable'])
with self.assertRaises(SourceError):
github.search(github.sources()[0], 'topic:piracy')
def test_feed_free_android_only_and_deduplicated(self):
with patch.object(_web, 'read', return_value=(FIX / 'itch-feed.txt').read_bytes()):
entries = itch.search(itch.sources()[0], '')
self.assertEqual(len(entries), 9)
e = itch.details(itch.sources()[0], entries[0]['id'])
self.assertTrue(e['vr'])
self.assertTrue(e['images']['banner'])
for item in entries:
self.assertEqual(item['icon'], item['images']['icon'])
self.assertEqual(item['icon'], item['images']['banner'])
self.assertEqual(item['images']['screenshots'], [])
self.assertFalse(e['downloadable'])
self.assertEqual(itch.search(itch.sources()[0], 'off nominal')[0]['name'], 'Off Nominal')
with self.assertRaises(SourceError):
itch.download(itch.sources()[0], entries[0]['id'])
with self.assertRaises(SourceError):
itch._parse(itch.sources()[0], b'not xml')
def test_paid_and_unsafe_feed(self):
raw = (FIX / 'itch-feed.txt').read_bytes().replace(b'$0.00', b'$1.00')
self.assertEqual(itch._parse(itch.sources()[0], raw), [])
raw = (FIX / 'itch-feed.txt').read_bytes().replace(b'https://absyo.itch.io', b'http://localhost')
self.assertFalse(any(e['name'] == 'Off Nominal' for e in itch._parse(itch.sources()[0], raw)))
def test_download_hash_and_cleanup(self):
b = io.BytesIO()
with zipfile.ZipFile(b, 'w') as z:
z.writestr('AndroidManifest.xml', b'fixture')
raw = b.getvalue()
digest = hashlib.sha256(raw).hexdigest()
with patch.object(_web, 'open_url', return_value=io.BytesIO(raw)):
result = _web.apk('https://github.com/owner/repo/file.apk', github.HOSTS, digest)
self.assertTrue(result['verified'])
self.assertEqual(Path(result['apk']).read_bytes(), raw)
with patch.object(_web, 'open_url', return_value=io.BytesIO(raw)):
self.assertFalse(_web.apk('https://github.com/file.apk', github.HOSTS)['verified'])
for content, expected in [(raw, '0' * 64), (b'html challenge', None)]:
with patch.object(_web, 'open_url', return_value=io.BytesIO(content)), self.assertRaises(SourceError):
_web.apk('https://github.com/file.apk', github.HOSTS, expected)
self.assertFalse(list(Path(self.tmp.name).glob('*.part')))
def test_origin_and_redirect(self):
for url in ['http://github.com/x', 'https://evil.test/x', 'https://user@github.com/x']:
with self.assertRaises(SourceError):
_web.checked_url(url, github.HOSTS)
req = urllib.request.Request('https://api.github.com/x', headers={'Authorization': 'Bearer secret'})
handler = _web.Redirect(('api.github.com', 'github.com'))
redirected = handler.redirect_request(req, None, 302, '', {}, 'https://github.com/x')
self.assertFalse(redirected.has_header('Authorization'))
with self.assertRaises(SourceError):
handler.redirect_request(req, None, 302, '', {}, 'https://evil.test/x')
def test_cache_rate_limit_and_invalid_json(self):
with patch.object(_web, 'open_url', return_value=io.BytesIO(b'index')) as op:
self.assertEqual(_web.read('https://itch.io/test', ('itch.io',)), b'index')
self.assertEqual(_web.read('https://itch.io/test', ('itch.io',)), b'index')
self.assertEqual(op.call_count, 1)
error = urllib.error.HTTPError('https://api.github.com/x', 403, 'limited', {}, None)
with patch.object(_web, 'open_url', side_effect=error), self.assertRaisesRegex(SourceError, 'rate limit'):
_web.read('https://api.github.com/x', ('api.github.com',))
with patch.object(_web, 'read', return_value=b'<html>'), self.assertRaises(SourceError):
github._api('/x')
if __name__ == '__main__':
unittest.main()
+101
View File
@@ -0,0 +1,101 @@
"""Small HTTPS cache and APK downloader for public publisher sources."""
import hashlib, os, tempfile, time, urllib.error, urllib.parse, urllib.request, zipfile
import frame_host
from . import SourceError
UA = 'FrameControl/0.1'
def cache():
path = frame_host.cache_dir('apk-sources', 'publisher')
os.makedirs(path, exist_ok=True)
return str(path)
def checked_url(url, hosts):
try:
p = urllib.parse.urlsplit(url)
port = p.port
except (ValueError, TypeError) as e:
raise SourceError('Source returned an invalid URL') from e
if p.scheme != 'https' or p.username or p.password or port not in (None, 443) or p.hostname not in hosts:
raise SourceError('Source returned an unexpected download URL')
return url
class Redirect(urllib.request.HTTPRedirectHandler):
def __init__(self, hosts):
self.hosts = hosts
def redirect_request(self, req, fp, code, msg, headers, newurl):
checked_url(newurl, self.hosts)
result = super().redirect_request(req, fp, code, msg, headers, newurl)
if result:
result.remove_header('Authorization')
return result
def open_url(url, hosts, headers=None):
checked_url(url, hosts)
return urllib.request.build_opener(Redirect(hosts)).open(
urllib.request.Request(url, headers={'User-Agent': UA, **(headers or {})}), timeout=60)
def read(url, hosts, headers=None, ttl=3600):
path = os.path.join(cache(), hashlib.sha256(url.encode()).hexdigest() + '.data')
try:
if os.path.isfile(path) and time.time() - os.path.getmtime(path) < ttl:
with open(path, 'rb') as f:
return f.read()
with open_url(url, hosts, headers) as r:
data = r.read(8 * 1024 * 1024 + 1)
if len(data) > 8 * 1024 * 1024:
raise SourceError('Source index is too large')
fd, tmp = tempfile.mkstemp(dir=cache(), suffix='.part')
try:
with os.fdopen(fd, 'wb') as f:
f.write(data)
os.replace(tmp, path)
finally:
if os.path.exists(tmp):
os.remove(tmp)
return data
except urllib.error.HTTPError as e:
if e.code in (403, 429):
raise SourceError('Source refused access or reached its rate limit; try later (GitHub accepts FRAME_GITHUB_TOKEN)') from e
raise SourceError('Source HTTP error: ' + str(e.code)) from e
except (OSError, ValueError) as e:
raise SourceError('Could not read source: ' + str(e)) from e
def apk(url, hosts, digest=None):
tmp = None
try:
fd, tmp = tempfile.mkstemp(dir=cache(), suffix='.part')
h = hashlib.sha256()
with os.fdopen(fd, 'wb') as f, open_url(url, hosts) as r:
size = 0
while True:
chunk = r.read(1 << 20)
if not chunk:
break
size += len(chunk)
if size > 2 * 1024 ** 3:
raise SourceError('APK exceeds the 2 GiB download limit')
h.update(chunk)
f.write(chunk)
actual = h.hexdigest()
if digest and actual != digest:
raise SourceError('SHA-256 mismatch; download discarded')
with zipfile.ZipFile(tmp) as z:
if 'AndroidManifest.xml' not in z.namelist():
raise SourceError('Download is not an APK')
path = os.path.join(cache(), actual + '.apk')
os.replace(tmp, path)
return {'apk': path, 'obb': [], 'sha256': actual, 'verified': bool(digest)}
except (OSError, ValueError, zipfile.BadZipFile) as e:
raise SourceError('Could not download APK: ' + str(e)) from e
finally:
if tmp and os.path.exists(tmp):
os.remove(tmp)
+115
View File
@@ -0,0 +1,115 @@
"""Curated publisher releases; optional topic discovery is page-only."""
import fnmatch, json, os, re, urllib.parse
from . import SourceError, _web
KIND = 'github'
API = 'https://api.github.com'
TOPICS = ('oculus-quest', 'openxr', 'quest')
HOSTS = ('github.com', 'release-assets.githubusercontent.com', 'objects.githubusercontent.com')
def sources():
return [{'id': KIND, 'kind': KIND, 'name': 'GitHub releases', 'url': 'https://github.com',
'builtin': True, 'enabled': True, 'trust': 'community'}]
def _curated():
with open(os.path.join(os.path.dirname(__file__), 'github_curated.json')) as f:
return json.load(f)
def _api(path):
headers = {'Accept': 'application/vnd.github+json', 'X-GitHub-Api-Version': '2022-11-28'}
token = os.environ.get('FRAME_GITHUB_TOKEN')
if token:
headers['Authorization'] = 'Bearer ' + token
try:
return json.loads(_web.read(API + path, ('api.github.com',), headers))
except (ValueError, TypeError) as e:
raise SourceError('Invalid GitHub response') from e
def _entry(source, c, approved=True):
artwork = c.get('images') or {}
icon = artwork.get('icon') or c.get('icon') or 'https://github.com/' + c['repo'].split('/')[0] + '.png'
images = {'icon': icon,
'banner': artwork.get('banner') or 'https://opengraph.githubassets.com/1/' + c['repo'],
'screenshots': list(artwork.get('screenshots') or [])}
return {'source': source['id'], 'id': c['repo'], 'package': None,
'name': c['name'], 'summary': c.get('summary') or '', 'icon': icon,
'images': images,
'page': 'https://github.com/' + c['repo'], 'version': None, 'version_code': None,
'min_sdk': None, 'abis': None, 'vr': c.get('vr'), 'size': None,
'free': True if approved else None, 'license': c.get('license'), 'updated': None,
'downloadable': approved}
def search(source, query, limit=50):
limit = max(0, min(int(limit), 100))
if not limit:
return []
if query.startswith('topic:'):
topic = query[6:].strip()
if topic not in TOPICS:
raise SourceError('Choose topic:oculus-quest, topic:openxr or topic:quest')
data = _api('/search/repositories?' + urllib.parse.urlencode(
{'q': 'topic:' + topic + ' archived:false', 'sort': 'stars', 'per_page': limit}))
curated = {c['repo'].lower(): c for c in _curated()}
out = []
for repo in data.get('items', []):
c = curated.get(repo['full_name'].lower())
entry = _entry(source, c or {'repo': repo['full_name'], 'name': repo['name'],
'summary': repo.get('description'), 'vr': True,
'icon': (repo.get('owner') or {}).get('avatar_url')}, bool(c))
out.append(entry)
return out
words = query.lower().split()
return [_entry(source, c) for c in _curated()
if all(w in (c['name'] + ' ' + c['repo'] + ' ' + c['summary']).lower()
for w in words)][:limit]
def details(source, entry_id):
c = next((c for c in _curated() if c['repo'].lower() == entry_id.lower()), None)
if not c:
if not re.fullmatch(r'[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+', entry_id):
raise SourceError('Invalid GitHub repository')
entry = _entry(source, {'repo': entry_id, 'name': entry_id}, False)
return {**entry, 'versions': []}
entry = _entry(source, c)
releases = _api('/repos/' + c['repo'] + '/releases?per_page=10')
versions = []
for release in releases:
if release.get('draft') or (release.get('prerelease') and not c.get('allow_prerelease')):
continue
assets = [a for a in release.get('assets', []) if
fnmatch.fnmatch(a['name'].lower(), c['asset_pattern'].lower())]
for asset in assets:
digest = asset.get('digest') or ''
versions.append({'version': release['tag_name'], 'version_code': None,
'asset_id': asset['id'], 'name': asset['name'], 'min_sdk': None,
'prerelease': bool(release.get('prerelease')),
'size': asset.get('size'), 'updated': release.get('published_at'),
'url': asset['browser_download_url'],
'sha256': digest[7:] if re.fullmatch(r'sha256:[0-9a-f]{64}', digest) else None})
entry['versions'] = versions
entry['downloadable'] = bool(versions)
if versions:
entry.update({k: versions[0][k] for k in ('version', 'size', 'updated')})
return entry
def download(source, entry_id, version_code=None):
entry = details(source, entry_id)
versions = entry['versions']
if not versions:
raise SourceError('No approved APK release; open the publisher page')
# GitHub asset IDs and tags are not Android version codes.
if version_code is not None:
raise SourceError('GitHub does not publish Android version codes; download the latest release')
v = versions[0]
expected = 'https://github.com/' + entry['id'] + '/releases/download/'
if not v['url'].startswith(expected):
raise SourceError('APK URL does not belong to the curated publisher')
return _web.apk(v['url'], HOSTS, v['sha256'])
+53
View File
@@ -0,0 +1,53 @@
[
{
"repo": "KhronosGroup/OpenXR-SDK-Source",
"name": "hello_xr",
"summary": "A simple VR scene for checking that OpenXR graphics and controllers work.",
"license": "Apache-2.0",
"vr": true,
"asset_pattern": "hello_xr-Vulkan-*.apk",
"allow_prerelease": false,
"images": {
"icon": "https://raw.githubusercontent.com/KhronosGroup/OpenXR-SDK-Source/3ed64d0f9bb680f24b80a085091e5c8fab38f7b7/src/tests/hello_xr/android_resources/vulkan/mipmap-xxxhdpi/ic_helloxr_launcher.png",
"banner": "https://opengraph.githubassets.com/1/KhronosGroup/OpenXR-SDK-Source",
"screenshots": []
},
"icon": "https://raw.githubusercontent.com/KhronosGroup/OpenXR-SDK-Source/3ed64d0f9bb680f24b80a085091e5c8fab38f7b7/src/tests/hello_xr/android_resources/vulkan/mipmap-xxxhdpi/ic_helloxr_launcher.png"
},
{
"repo": "icosa-foundation/open-brush",
"name": "Open Brush",
"summary": "Paint in the air and create colorful 3D artwork in VR.",
"license": "Apache-2.0",
"vr": true,
"asset_pattern": "*Quest*.apk",
"allow_prerelease": true,
"images": {
"icon": "https://raw.githubusercontent.com/icosa-foundation/open-brush/56acbce831c7e9f257bfee9e21853da99773787b/Assets/Resources/DefaultImages/OpenBrushLogo.png",
"banner": "https://raw.githubusercontent.com/icosa-foundation/open-brush/56acbce831c7e9f257bfee9e21853da99773787b/open-brush.png",
"screenshots": [
"https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/1634870/ss_785ea37d63378146dfe0f0ffa3f1d5c155ca978f.1920x1080.jpg",
"https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/1634870/ss_0a9c208e26a43cf34879c2ca361d4c8f18af8cba.1920x1080.jpg",
"https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/1634870/ss_19b25b86ef55c0d8769a65135d60eaae8fa40553.1920x1080.jpg"
]
},
"icon": "https://raw.githubusercontent.com/icosa-foundation/open-brush/56acbce831c7e9f257bfee9e21853da99773787b/Assets/Resources/DefaultImages/OpenBrushLogo.png"
},
{
"repo": "SgtBilko76/SuperTux-3D",
"name": "SuperTux 3D",
"summary": "Run and jump through Tux’s platform adventure on a layered 3D screen in VR.",
"license": "GPL-3.0",
"vr": true,
"asset_pattern": "*Quest*.apk",
"allow_prerelease": true,
"images": {
"icon": "https://raw.githubusercontent.com/SgtBilko76/SuperTux-3D/1955493ee6f1000e048c58db40d4904df827210e/data/images/engine/icons/supertux-256x256.png",
"banner": "https://www.supertux.org/images/0_7_0/github_preview.png",
"screenshots": [
"https://www.supertux.org/images/0_7_0/github_preview.png"
]
},
"icon": "https://raw.githubusercontent.com/SgtBilko76/SuperTux-3D/1955493ee6f1000e048c58db40d4904df827210e/data/images/engine/icons/supertux-256x256.png"
}
]
+72
View File
@@ -0,0 +1,72 @@
"""Free Android VR RSS listings. Download pages stay in the publisher's UI."""
import html, re, urllib.parse, xml.etree.ElementTree as ET
from email.utils import parsedate_to_datetime
from . import SourceError, _web
KIND = 'itch'
FEEDS = ('openxr', 'oculus-quest')
def sources():
return [{'id': KIND if tag == 'openxr' else 'itch-quest', 'kind': KIND,
'name': 'itch.io (' + tag + ')', 'url': 'https://itch.io', 'tag': tag,
'builtin': True, 'enabled': True, 'trust': 'community'} for tag in FEEDS]
def _parse(source, data):
try:
root = ET.fromstring(data)
except ET.ParseError as e:
raise SourceError('Invalid itch.io RSS feed') from e
entries = []
for item in root.findall('./channel/item'):
page = item.findtext('link') or ''
p = urllib.parse.urlsplit(page)
if p.scheme != 'https' or not (p.hostname or '').endswith('.itch.io') or p.username or ':' in p.netloc:
continue
if item.findtext('price') != '$0.00' or item.findtext('platforms/android') != 'yes':
continue
cover = item.findtext('imageurl') or None
if cover and not cover.startswith('https://img.itch.zone/'):
cover = None
updated = None
try:
updated = parsedate_to_datetime(item.findtext('updateDate')).date().isoformat()
except (ValueError, TypeError, AttributeError):
pass
entries.append({'source': source['id'], 'id': page, 'package': None,
'name': item.findtext('plainTitle') or item.findtext('title') or page,
'summary': html.unescape(re.sub('<[^>]*>', '', item.findtext('description') or '')).strip(),
'icon': cover, 'images': {'icon': cover, 'banner': cover, 'screenshots': []},
'page': page, 'version': None, 'version_code': None, 'min_sdk': None,
'abis': None, 'vr': True, 'size': None, 'free': True, 'license': None,
'updated': updated, 'downloadable': False})
return entries
def search(source, query, limit=50):
limit = max(0, min(int(limit), 100))
if not limit:
return []
entries = {}
tag = source.get('tag', 'openxr')
if tag not in FEEDS:
raise SourceError('Unsupported itch.io feed')
url = 'https://itch.io/games/free/platform-android/tag-' + tag + '.xml'
for entry in _parse(source, _web.read(url, ('itch.io',))):
entries.setdefault(entry['id'], entry)
words = query.lower().split()
return [e for e in entries.values() if all(w in (e['name'] + ' ' + e['summary']).lower()
for w in words)][:limit]
def details(source, entry_id):
entry = next((e for e in search(source, '', 100) if e['id'] == entry_id), None)
if not entry:
raise SourceError('Game is not in the current free Android VR feed; open its publisher page')
return {**entry, 'versions': []}
def download(source, entry_id, version_code=None):
raise SourceError('Open the itch.io publisher page to download; automated download pages are disallowed by robots rules')