mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
867 presets over 27 categories, one manifest and one zip each, and no third request anywhere. A tester asked for what Manic EMU has: a button that fetches the collection instead of making people find a zip and side-load it. The closure resolution and the licence sign-off landed first; this is the phone half. Order is the whole safety argument, because a remote manifest is attacker- controlled if the host is. The stated size is refused before the transfer rather than after -- the manifest carries it, so the refusal costs nothing. The received byte count and the SHA-256 are both compared before the importer is called, and the hash is streamed rather than read whole. The relative path is validated before it becomes a URL, because .. and / both survive percent-encoding. Then the fenced extractor does the writing, unchanged, so there is no second containment guard to get wrong. The manifest is 8 MB raw and 312 KB gzipped, and 96% of those bytes are the per-file array. The entry type does not declare that key, so it is skipped: the zip carries its own hash and that covers every file inside it. What lands in the cache is this build's own projection rather than the served bytes, which is also what makes browsing work with no network -- a failed refresh ages the list instead of emptying it. Two things the import path never had. Staging files are swept at launch, because defer does not run when iOS kills a backgrounded app mid-download, which is the ordinary outcome and not an edge case. And cancelling removes the pack if the extract already began, which is not the same as stopping it; the comment says so rather than implying otherwise. The importer returns the name it installed instead of only publishing it. One property on a shared object is fine for one caller and wrong for a screen with 867 rows and no reason to install them one at a time: two installs overwrite each other's answer, which would write one entry's marker into the other's folder and make cancelling one delete the other. Three fixes in the code around it, from the same review. A loaded chain owned a render target and a pipeline per pass and nothing freed any of it when the player turned shaders off, because DestroyShaderChain had exactly two callers, a preset change and device teardown. The Metal frame path flushed on success and returned on failure, though a chain that failed partway has already encoded passes into the same command buffer and needs the submit for the same reason the success path does. And the pack extractor held every file's bytes resident to the 32 MB cap, because the autoreleased data was never drained inside the loop. The catalogue is not published yet. The base URL is one constant, and an INI key no UI writes can repoint it, accepting only https and file -- which is how a simulator reads a local emit, since ATS refuses plain HTTP and there is no reason to weaken it for a test. Eleven checks in the new fence, six mutations run against the real source and all six caught, each restored byte for byte. Still open and written down rather than left to be rediscovered: the extractor's per-entry decompressed cap is applied after the entry is fully inflated, so a crafted zip can spend up to that cap before the refusal. Bounding it earlier needs a streaming inflate. The extractor's own fence gains an ordering claim. It asserted that a canonical resolve appears somewhere in the method, which passes for a resolve whose answer is discarded; deleting the entire containment refusal left it green. It anchors on resolvedParent now, because the body carries several refusal sites naming the same constants and anything looser is satisfied by a neighbouring refusal that has nothing to do with containment -- which is the same trap the first attempt at this fix fell into.