mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 01:00:18 +02:00
Merge branch 'more-sources' into apk-store
This commit is contained in:
commit
c5b5930582
20 files changed
+1422
No files matched your search
@@ -0,0 +1,6 @@
|
||||
/proof-cache/
|
||||
/amazon.html
|
||||
/aptoide-terms.html
|
||||
/itch-game.html
|
||||
/meta.html
|
||||
/uptodown-terms.html
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -0,0 +1,5 @@
|
||||
................................................................................................................................................................................
|
||||
----------------------------------------------------------------------
|
||||
Ran 176 tests in 5.879s
|
||||
|
||||
OK
|
||||
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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'])
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
@@ -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')
|
||||
Reference in new issue
Block a user