Follow-up to 7aa4547e60. Four ports -- flux, plasma, solarwinds and
hyperspace -- still opened port_free with "if (!g_started) return;". That is
unreachable today, since the JNI only calls destroy after a successful
create, but unreachability is a property of the current call graph rather
than of the function, and it contradicts the rule the fix established: gl1
is given back on every path out, because whatever it holds belongs to an
EGL context that is about to die. All six are now unconditional.
Same argument one level up. port_new opened with "if (g_started) return 1",
which was defensible when gl1's state was whatever the last saver left, but
nativeInit now calls gl1_lost() first -- so gl1 is guaranteed DOWN on entry
and reporting success there would hand the caller a saver with no shim under
it. A stale run is torn down instead, and gl1_init always runs.
No behaviour change on any path reachable today. The point is that a saver
added later, or an upstream cleanup that returns early, fails in its own
saver instead of poisoning the next one.
The on-screen controls have had tap-to-hold since they existed; physical
buttons always followed the button exactly. A game that wants one held while
another control is worked -- MGS2 holding R1 to aim -- is then unplayable for
anyone who cannot hold two controls at once, which is what the request was
about.
Modelled as a transform on the event stream rather than a branch beside
turbo: a tap becomes a synthetic KeyDown, the next tap a synthetic KeyUp,
and everything between is swallowed. Turbo composes with it for free -- flag
a button both and a tap toggles autofire on and off.
Keyed on the physical code for "is this a fresh press", because ACTION_DOWN
auto-repeats while a key is held and each repeat would otherwise toggle, and
on the PS2 target for "is it latched", so two physical buttons bound to the
same button cannot desync.
The state lives in the companion because the boot path that clears it runs
there -- a latch must not outlive the game it was set in -- while the
dispatch that sets it is an instance method. Changing the setting releases
whatever is held: turning it off for a button that is latched down would
otherwise strand it pressed, with no second tap left to release it.
Stored per action per player like turbo, and off by default.
Feature requested by bobo123g (#612)
A saver that dies natively made ARMSX2 unlaunchable. The choice is a
persisted pref read on the library screen -- the first screen -- so the
crash repeated on every launch and Settings was never reachable to turn it
off. The only escape was clearing app data, which takes memory cards and
save states with it. A user lost their saves that way.
Cause: gl1's state is a file-scope global holding GL object names, and
gl1_init() early-returns on g.ready. Skyrocket and Lattice defer initSaver()
to port_resize, so a create-then-teardown with no surface size left
g_started false and their port_free returned BEFORE gl1_shutdown(); flux,
plasma and solarwinds leaked it the same way when initSaver() left
readyToDraw clear, since returning 0 means port_free is never called. Either
way g.ready stayed set with names from a destroyed EGL context, and the next
saver -- new view, new context -- drew against them. Drivers answer that
with anything from a black screen to a segfault.
Each port now gives gl1 back on every path out, and nativeInit calls
gl1_lost() as the invariant: a new context never inherits old GL names.
Contained separately, because native GL can always find a new way to die:
the setting arms itself with a synchronous commit() before the render thread
starts and disarms when that thread exits in an orderly way. Still armed at
startup means the last run died with a saver up, so the background switches
off and the user is told which one. runCatching was never going to catch a
SIGSEGV.
Also guards Thread.start(): it asks for a 16MB stack (Skyrocket declares a
3MB starmap as a local) and an OutOfMemoryError there is an uncaught throw
on the main thread -- the same lockout with no native crash involved.
It fired on its very first run against an APK that DID contain the library. `unzip -l | grep -q`
under `set -o pipefail` is a false-failure generator: grep exits on the first match, SIGPIPEs
unzip, and pipefail then reports the pipeline as failed. Capture the listing and match it with
`case` instead, which is what the existing notes on this already say to do.
2.6.6.8 shipped without Discord and it was only noticed after publication. The SDK is resolved
from $DISCORD_SDK_DIR at configure time and gated on include/discordpp.h existing, so with the
variable unset the build quietly omits it -- no error, no warning, nothing in the log to read
afterwards. Every check that already runs on these artifacts (both cores, alignment, signing,
package, MANAGE_EXTERNAL_STORAGE) would have caught this class of mistake if one had existed.
Both scripts now verify libdiscord_partner_sdk.so in the output. With DISCORD_SDK_DIR set and
the library absent, that is FATAL -- it means the staged directory was wrong, which is easy to
get wrong given the raw SDK download ships an x86-64 .so and only the .aar carries arm64. With
the variable unset it warns loudly instead of failing, because a Discord-less build is still a
legitimate thing to produce on purpose.
Every Android build has failed since the screensavers landed in 81d0a3e510.
The workflow still reported success, because the Android job is
continue-on-error, so nothing said so and no APK has been uploaded since.
shockwave.cpp asked for <gl/gl.h>. The vendored header is compat/GL/gl.h,
with an uppercase directory. macOS resolves that spelling and the Linux
runner does not, so the build dies on "fatal error: 'gl/gl.h' file not
found".
It is the only miscased include in the target. All 742 includes under
savers/ were checked against real directory entries rather than by testing
whether files exist, since a case-insensitive filesystem answers yes to
both spellings and would have hidden exactly this.
The line is also redundant. shockwave.cpp is never compiled on its own:
unit_shockwave.cpp wraps it in a namespace and already includes <GL/gl.h>
above it, so the corrected line hits the header guard and expands to
nothing. Its five sibling files carry no GL include at all and rely on the
wrapper, so deleting it would match them. These savers are vendored GPL
sources kept close to upstream, so this changes one character instead.
What this does not establish is whether the build then finishes. The error
has masked everything after it for days, so a second one may be waiting.
Nightly and MoonStore builds identify themselves to RetroAchievements as
stock PCSX2, so the server allows softcore only. Hardcore has worked on
locally built IPAs and nowhere else.
Nothing was broken. pcsx2/Host.cpp reads the client version from
ra_ua_secret.h behind __has_include and falls back to a stock agent when
the macro is absent. That header is gitignored, so it has never existed on
a runner.
CI now writes it from the IOS_RA_UA_VERSION repository secret, the way the
Android job already writes its keystores from NIGHTLY_RELEASE_KS_B64. A
missing secret warns and continues, because fork pull requests never
receive secrets and a nightly that fails to publish is worse than one
without hardcore. A malformed version fails the build instead:
RetroAchievements refuses a version it cannot order, and a refused agent is
indistinguishable from an unknown one on the client.
Only the nightly and pushes to master embed it. Pull request artifacts keep
the stock agent, so a test build is not one more public copy of an identity
that needs a release to revoke.
A second step reads the finished binary and fails unless the exact version
is in it, trailing space included, since a stale 1.2.3 is a prefix of a
current 1.2.345. Both iOS jobs are continue-on-error, so that failure does
not stop the workflow, but it does skip the upload that follows it, and the
publish step treats a missing IPA as absent rather than fatal. A softcore
build is not shipped.
None of this makes the version private. The compiler bakes the finished
agent into the binary as a plain literal, so anyone holding a build can
read it out. What the gitignored header prevents is a fork inheriting a
live identity straight from source.
All of it was instrumentation for the Persona 3 FES investigation, and that turned
out not to be a texture problem at all -- three named patch groups were failing to
enable, and the texture pack stopped matching because the mod they gate patches the
game's font data.
Removed: the TCPROBE cross-version probe in HashCacheKey::Create, the hit/miss
counters and their geometric reporter, the per-miss detail listing what the pack
holds for a TEX0, the pack filename samples at map load, the duplicate/shadowed
accounting, and the mtime sidecar the palette fallback needed.
Kept the pre-existing "N indexed for '<serial>' (scanned <dir>)" line, which
predates this and is genuinely useful: a zero there with a path that does not match
the user's pack folder is still the fastest read on a pack that does nothing.
The cache budget work is untouched.
"I enabled HostFS and it is still off", with the switch sitting on. His game INI
proves it: [Cheats] present but empty, and no [Patches] section at all -- the
enable-list write never ran.
toggleLocalCheat flips the switch optimistically, then bails early when
setBodyEnabled produced an identical body:
if (newBody == target.body) return // pushEnableList never reached
setBodyEnabled only comments or uncomments patch= lines, and community pnach files
ship UNCOMMENTED. So for exactly those files enabling is a no-op on the body, the
early return skips the enable list, and Patch.cpp applies a NAMED group only when
its name is in [Patches]/[Cheats] Enable -- unlabelled groups are the only ones
that auto-enable. Switch on, file already correct, patch never applied, nothing
anywhere saying so.
The two writes are independent. Rewrite the body only when it differs; always push
the enable list.
This is what actually blocked the Persona 3 FES mods. The HostFS loader group could
not be enabled at all, so the mod never loaded, and the texture pack keyed to the
modded font stopped matching as a consequence -- which is how it arrived as "my
texture mods broke".
"I enabled HostFS and it is still off." The Patch Manager reads the toggle back as
on, and the patch loader never sees it.
ReloadEnabledLists reads through LayeredSettingsInterface, which returns the FIRST
NON-EMPTY layer with LAYER_GAME ahead of LAYER_BASE. So the moment a game's INI
carries any [Patches] Enable entry, the base list is invisible. setEnabledPatches
targeted the game INI only when a VM was running -- opened from the library it had
nowhere to go but base, where a shadowing game layer swallowed it. The UI then
reads the value straight back, which is why it looks like it took.
For this reporter that patch is the HostFS loader their Persona 3 FES mod runs on,
so it stayed off through repeated attempts to switch it on, the mod never loaded,
and the texture pack keyed to the modded font kept missing.
Take the serial from the caller -- PatchManagerViewModel already resolves one via
bestSerial() -- and resolve the game INI by the same glob gameIniBeginWriteForSerial
uses. With no VM and no serial, or no INI for that serial yet, it still falls
through to base, which is correct for a game that has no per-game file at all.
This is the "wants a serial/CRC parameter" the old comment left as the real fix.
It was a one-time repair for lists poisoned by patches arming themselves, and it
has done more harm than the thing it repaired. It deleted every [Patches]/[Cheats]
Enable entry, not just the self-armed ones, so anyone who had turned a patch on
deliberately lost it silently on update -- and when the patch in question is the
HostFS loader a mod depends on, the mod stops loading, the texture pack keyed to
the modded data stops matching with it, and the whole thing presents as "my
texture mods broke". That cost eleven builds to chase from the wrong end.
Restricting it to lists that look auto-armed was the first attempt at a fix, and
it was still wrong: the threshold is a guess, and someone with thirty cheats
enabled on one game would have been wiped by it just the same. A narrower
silent-data-loss bug is not a fix.
Removing it outright instead, because its job is finished:
- It is a migration, and it has already run for everyone who updated in the past
month. What remains is a shrinking population and a permanent hazard.
- Nothing re-poisons a list now. 10f4f73fe8 removed the unconditional
syncAllEnableLists on Patch Manager open, stopped EnumeratePnachFiles falling
back to the bundled zip over on-disk files, made delete() drop names from the
enable list, and stopped D-pad Right arming the row under the cursor.
- Anyone still carrying a poisoned list can clear it themselves, because
disabling a patch sticks now. That was the original complaint.
Nothing writes these lists except the player, which is how it should have been.
Both existed to work around the wrong diagnosis. The Persona 3 FES mods were not
failing in the texture cache at all -- a PNACH mod was switched off by the
enable-list purge, and because PNACH mods patch game DATA the texture pack keyed
to the patched font stopped matching as a side effect.
The palette fallback substituted a different colour variant of a glyph when the
exact palette was missing. It raised the hit count but could not be right: the
palette IS the colour, so substituted art carries the wrong shade, and it never
addressed why the hashes moved. Gone; the replacement path behaves exactly as it
always did.
The one-time notice goes too, per jpolo1224: the purge no longer takes deliberate
lists, so nothing needs announcing going forward.
Kept: the diagnostics that actually found this -- files scanned versus indexed
versus shadowed, hit and miss counts, and the per-miss detail naming what the pack
holds for that texture. Those are what turned "mods do not apply" into a
measurement, and they cost nothing when nothing is wrong.
This is what broke Persona 3 FES mods, and it was never the texture cache.
10f4f73fe8 fixed a real bug -- opening the Patch Manager armed every uncommented
group of every on-disk .pnach -- and cleaned up the damage with a one-time purge
of [Patches]/[Cheats] Enable. The purge deleted every entry unconditionally,
including the ones people had turned on deliberately, so updating past 2.6.6
silently switched off everyone's patches and cheats.
It surfaces in the worst possible way. The reporter's mod is a PNACH,
94A82AAA_Modern_Persona_Switch_aem.pnach, and PNACH mods patch game DATA. With it
disabled the font glyphs in GS memory differ, so the texture pack keyed to the
patched font stops matching as well -- and the whole thing reads as "my texture
mods stopped applying". Their logs say it plainly: 18,791 patch lines applied on
2.6.6 against 76 now, same .pnach found in both.
Two changes:
- The purge now only fires on lists carrying the auto-armed signature.
syncAllEnableLists persisted every uncommented group across ~4000 bundled
files, so a poisoned list runs to hundreds of names; a hand-built one is a
handful. At or under 25 entries it is treated as deliberate and left alone.
- A one-time notice for the people v1/v2 already hit. Nothing can give them back
what was deleted, and they have no way to know it happened -- the .pnach is
still on disk and still found in the log, it is simply no longer enabled.
Saying so once is the only repair available.
The immediate fix for anyone affected is to re-enable their patches in Patches &
Cheats; the purge is one-shot and will not take them again.
A paletted replacement is keyed on TEX0 hash AND palette hash, so a pack only
applies while the game asks for a palette its packer happened to dump. Persona 3
FES lands exactly there: the pack carries several palette variants of each glyph
and the game asks for one that is not among them, so every glyph misses while the
unpaletted art around it replaces fine -- 189 hits against 323 misses, which is
why the scene looked right and the text did not. With this, 441 against 71, and
none of the remainder are palette mismatches.
Lowest palette hash, deliberately. It is arbitrary but STABLE: the same glyph
resolves to the same file on every draw and every run, so text renders in one
consistent colour. Choosing by file mtime was tried and was worse in a way worth
recording -- different glyphs won different variants and the text came out
multicoloured.
The colour can still be wrong, since the replacement image has the packer's
palette baked in and there is no recolouring it. That is the trade, and it is the
right way round: a mod that applies in the wrong shade beats one that does not
apply at all.
This does NOT explain why the pack matched on 2.6.6. That is a separate finding
and still open: the same glyph at the same address with the same palette hashes
differently now (2.6.6 asks 85076d2a533c0128, current asks f0576dc2f0bb17d5),
with TBP0, TBW, PSM, TW/TH, region and lod all identical between them. The bytes
in GS local memory differ, which is upstream of texture replacement entirely.
Both builds report identical TBP0, TBW, PSM, TW/TH, region and lod for the same
font glyph, and identical leading bytes -- yet different TEX0 hashes. Since
HashTextureLevel reads straight out of GS local memory, the bytes at that address
differ; the shipped hash folds the base level and every mip into one value, so it
cannot say which of them moved.
Recompute the base level alone for the probed textures and log it beside the
combined hash, plus the actual mip range rather than the bool the first probe
recorded. Base matching with the combined differing means the mip chain moved;
base differing means the glyph data itself did. Those are different bugs.
The two builds ask for disjoint TEX0 hashes for the same on-screen font -- nine
values on 2.6.6, eight now, no overlap, same palette hash, same PSMT4. Every
function feeding the hash is byte-identical between them, so what differs is the
DATA, and HashCacheKey records only the region's width and height: never where it
starts, never which address it came from, never whether mips were folded in.
Print exactly what the key discards -- TBP0, TBW, TW/TH, the full region rect
including its origin, whether lod was present, and the first bytes actually
hashed -- so two logs can be diffed instead of theorised about.
A paletted glyph's palette IS its colour, so when the game asks for a palette the
pack does not carry there is no "close enough" file to stand in for it.
Substituting produced multicoloured text: the pack holds eight colour variants of
each glyph, all from one extraction seconds apart, and different glyphs won
different variants. Both selection rules were wrong for the same reason -- lowest
hash preferred the base pack, newest was noise, and neither can be right when the
thing being chosen IS the colour.
Keep the diagnostic half, which is what actually advanced this: on a paletted
miss, list every colour of that TEX0 the pack does hold. That distinguishes "the
pack lacks this texture" from "the pack lacks this COLOUR of it", and the second
is the real finding here -- the modder dumped eight colours and the game is asking
for a ninth.
Leaves the replacement path behaving exactly as it did before any of this, with
better logging. The open question is unchanged and now isolated to one value: why
the game asks for a palette that is not among the dumped eight.
Shadowing was not it: 8386 files, 8339 indexed, only 4 collisions -- and the
remaining misses are 640x384 PSMCT32 with no palette, i.e. FMV frames, which hash
uniquely per frame and can never match. So the layered mods index fine and the
game does ask for them.
What actually loses is the choice BETWEEN palette variants. A mod over a base
pack supplies the same glyph under a different palette, so the two never collide
during indexing -- and the first cut of this fallback took the lowest palette
hash, which is arbitrary, and silently preferred the base pack. The player ends
up seeing the pack they installed FIRST, which is the opposite of what layering a
mod means, and matches the report exactly: base HD text applied, slim font did
not.
Prefer the newest file instead. Modification time is the one signal that
separates "the pack added last" from "the pack it was layered over", and the
directory walk already reports it, so this costs no extra I/O. Ties break on the
lower hash purely to stay reproducible.
Also log every candidate -- palette hash, mtime, full path -- for the first few
fallbacks, so the choice is auditable rather than asserted. If the newest file is
still the wrong one, that log says so immediately instead of costing another
round.
Still not shippable: substituted art carries the packer's palette, so tints can
be wrong, and mtime is a heuristic rather than an expression of intent. A real
fix wants explicit pack precedence.
The palette fallback moved Persona 3 FES from 189 hits / 323 misses to 441 / 71,
with ZERO of the remaining misses matching a name under another palette -- so the
palette hash is settled, and what is left is textures the map does not hold under
that name at all. The base HD pack now applies; the layered mods still do not.
emplace does not overwrite, and this scan is RECURSIVE. Two files in different
subdirectories that decode to the same texture name collide, and whichever the
directory walk reaches first wins -- silently, with nothing to say which of them
ended up on screen. That is exactly how a mod layered over a base pack loses: not
on intent, on traversal order.
Count the collisions, name the first six with both paths, and report files
scanned alongside textures indexed. "My mod is not applying" and "my mod is being
shadowed by another pack" are indistinguishable to the person reporting it, and
until now they were indistinguishable from the log too.
Nothing about which file wins changes here. Measure first: if the count is zero,
the layered mods are absent rather than shadowed, and that is a different fix.
A paletted texture is looked up by TEX0 hash AND palette hash, so a pack only
applies while the game asks for a palette its packer happened to dump. The
Persona 3 FES logs land exactly there: the pack carries several palette variants
per glyph (ea34cb7b, df582335, b6eebe9e, 99b15967 ...) and the game asks for
cf4df1c018862d85, which is none of them. Every glyph misses while the unpaletted
art around it replaces fine -- 189 hits alongside 323 misses -- which is why the
scene looks right and the font does not.
Worth recording what this ISN'T, since each cost a round to eliminate: the cache
budget (already present in the working version), the pre-ELF hack strip
(transient, ApplyCoreSettings restores it), paltex (it relaxes the hash cache,
never the replacement lookup -- CreateTextureName always uses the real palette
hash for paletted formats), and name parsing, the hash function and the paltex
defaults, all byte-identical to 2.6.6.
So when the exact palette is missing but the pack holds this TEX0 under others,
take one -- lowest palette hash, so the choice is reproducible rather than
dependent on map iteration order.
NOT correct as shipped, and deliberately labelled so: the replacement image has
the packer's palette baked in, so substituted art can carry the wrong tint. It
exists to answer one question. If the mods appear, the palette hash was the only
obstacle and the real fix is about WHICH palette to prefer, not about loading at
all. If they still do not appear, the pack was never the whole story.
The previous diagnostic could say "the pack holds this TEX0 under a different
palette" but not which, and the difference is the entire diagnosis:
<tex0>-<clut>-<w>x<h>-<bits>.ext a real, different palette hash -- the palette
contents differ at run time from dump time
<tex0>-<w>x<h>-<bits>.ext no palette field, so it indexed with
CLUTHash=0 and can only match under paltex
Answering that meant asking someone to list a directory on their own device. The
people who hit this are players; that is not a reasonable thing to ask, and it is
information the emulator already has in memory.
So print it: up to two matching entries per miss, with their CLUT hash and
filename, plus three sample names at map load so the pack's naming style is on
the record even when nothing misses.
Bounded the same way as the rest of this logging -- at most eight misses reported,
at most two entries each -- because it walks the filename map, and this game's
pack has 8339 entries in it.
The first version could not. LookupReplacementTexture runs once per NEWLY HASHED
texture, not per draw, so a whole session can be a few hundred calls -- and a
report threshold of 20k therefore printed once, at the very first lookup, and
never again. Two tester logs came back reading "0 hits, 1 misses" after 139 and
195 seconds of play, which cannot distinguish one lookup from twenty thousand.
That ambiguity was the whole answer they were supposed to give.
Three changes:
- Report on a geometric schedule (1, 2, 4, 8 ... then every 4096) rather than a
fixed threshold. Bounded whatever the rate, and dense at the start, which is
where the answer usually is.
- Print the first eight misses in full: the TEX0 hash, CLUT hash, dimensions
and PSM actually asked for, plus whether the pack holds that same TEX0 under
a DIFFERENT CLUT hash. That is the difference between "the pack does not have
this texture" and "it has it, under another palette hash" -- and the latter is
the usual answer for paletted UI art, which is what a Persona 3 FES font and
menu panel are.
- Log preloading, paltex, async and upscale once at map load. Those decide
whether a lookup is ever ATTEMPTED rather than whether it matches; a pack that
indexes thousands of files and is then never consulted looks identical from
outside to one that misses every time.
"My texture pack does nothing" has two failure stages and the log only covered
the first. The indexed count proves the FILES were found and their names parsed;
it says nothing about whether any draw ever asks for one. A pack that indexes
thousands of textures and misses every lookup is a hash problem -- wrong dump
settings, paltex vs CLUT, wrong upscale -- and from outside it looks exactly
like a pack that never loaded at all. Both read as "the mods are not applying".
Count hits and misses in LookupReplacementTexture and summarise one line per
20k lookups. Summarised rather than logged per lookup because this runs per
draw, and log volume on its own is enough to stall the emulator.
Also counts how many misses WOULD have matched with the CLUT hash zeroed. That
separates "the pack does not contain this texture" from "it does, but the
palette hash differs", which is the usual answer for paletted UI art -- fonts
and menu panels, the exact things a Persona 3 FES mod replaces.
Counters reset in ReloadReplacementMap: carried over from a previous game, a
stale hit count reads as a healthy pack.
Also -Wno-missing-braces on the savers target. Welsh's sources initialise nested
aggregates without inner braces throughout, which -Wall diagnoses once per site
per TU: ~5 million lines and an 842 MB build log, which is how it was noticed.
Upstream code we do not restyle, so the diagnostic has nothing to tell us. Purely
a diagnostic flag; no codegen change.
ARMSX2 had Flurry alone. This brings over the Really Slick Screensavers tree
ARMSX3 already runs -- Flux, Plasma, SolarWinds, Hyperspace, Lattice and
Skyrocket -- as one libsavers.so beside libflurry.so.
Terry Welsh's savers, GPL-2.0-OR-LATER, which this tree may take under the "or
later" clause. Nothing here comes from rss-glx: that Linux port is GPL-2.0-only
and cannot go into ARMSX2 at all, which is why Lattice and Skyrocket were
hand-ported from the Windows sources upstream rather than taken from the neutral
versions rss-glx already has. The per-file header is what decides this, not the
repository's LICENSE file; they disagree.
The saver sources build with RS_XSCREENSAVER, selecting their platform-neutral
path, against the GLES2 shim in savers/compat and savers/gl1.c rather than being
rewritten -- so they stay re-pullable and recognisably his code.
Four things the tree carries that are not obvious from the sources:
- The GL thread takes a 16MB stack. Skyrocket's World constructor declares a
1024x1024x3 starmap as a local, and a default stack is a SIGSEGV inside
memset before the first frame draws.
- Each saver is compiled through a *_unit.cpp that wraps it in a namespace.
They all declare draw/idleProc/cleanUp/readyToDraw, because each was built as
its own executable, and two in one .so collide at link. The shared libraries
must be pre-included OUTSIDE the namespace or the declarations never match
their definitions -- which compiles clean and fails at link.
- The lifecycle is serialised by a mutex and a generation token: gl1 keeps its
state in one global, so a view may only free the run it started. Without it
the first selection works and every later one is black.
- gl1_frame_begin() masks alpha off after the first frames. These savers fade
the previous frame rather than clearing, and that fade writes destination
alpha, dragging a composited surface transparent -- which reaches the screen
as full-screen TV static.
Hyperspace runs with dShaders = 0 (its ARB shader path needs objects GLES2 does
not have; upstream exposes this as -shaders 0 and falls back to it itself), and
Skyrocket with dSound = 0 (upstream drives OpenAL and bakes ~7MB of samples into
headers; soundEngine.h is a stub here). Hyperspace and Skyrocket ship no presets
upstream, so neither shows a preset picker -- invented ranges are what made
earlier ports fail to start.
SaverGlView replaces FlurryGlView, which it supersedes: it hosts Flurry and the
Really Slick savers behind one SaverSpec, and keeping both would have declared
FlurryNative twice in one package. Helios is deliberately absent -- it rendered
but animated wrongly and the cause was never found.
Merging preserves authorship in git history, but nobody reads git log to find
out who wrote a file. The tree already had the convention -- "Feature
contributed by misantronic (PR #391)" on exportRecentGamesPublic -- it just was
not applied to the three PRs merged here.
MemoryCardBackup.kt bmdhacks (PR #608)
PerGameOverrides.{h,cpp} bmdhacks (PR #593)
RecentGamesContentProvider.kt misantronic (PR #566)
The provider's line also records that the opt-in gate was added on merge, so the
gate is not mistaken for part of the contribution.