.uib format version 7, unchanged from 0.9.0. pip install ophtml and
npm install -g @ophtml/layout give you this release.
The console launcher
ophtml.elf turns any ps2ui theme into a game launcher: it loads USB, exFAT
HDD, MX4SIO and MMCE drivers, scans each drive for ISOs in OPL's DVD/ and
CD/ layout, fills whatever theme it finds, and hands the selected game to
Neutrino. A theme opts in by using names — nothing in C is the author's to
write.
It is experimental. It has been booted in an emulator in CI and never on a
PlayStation 2. hw.yml boots a mock build in Play! and diffs its filled
list against the previewer drawing the same games, and console/tests covers
the file names, ISO9660, SYSTEM.CNF, the directory scan and Neutrino's
command line on the host. But no drive has been mounted and no game started on
real hardware: bench cases C1 through C8 in console/README.md are all
open. Treat a first run on your own console as untested.
Start here: https://coffeedevsolutions.github.io/OPHTML/runtime/console-launcher/
— drive layout, installing Neutrino, putting your own theme.uib beside the
ELF, the names the launcher fills, the controls, and what each status line and
start-up colour means.
Downloads
| file | sha256 |
|---|---|
ophtml.elf |
9b3f796cdaeb5f9d7bddf2b9979d138106323f07b17b4efb206c5cfe5480f10a |
ophtml-mock.elf |
9dfa8248bf0c543bee7a458041a54cfb18b49d2df28b2dc7360becd2a024c037 |
Built in the unpinned ps2dev container with:
mips64r5900el-ps2-elf-gcc (GCC) 15.2.0
ophtml-mock.elf is the mock build CI boots in Play!; ophtml.elf is the one
for a console.
.uib format version 7, unchanged from the release below.
Zero format moves have landed since 0.9.0, which is what a section
opened straight after a release should say: the release under it
shipped the format this tree still writes, so a blob baked here loads
under a 0.9.0 runtime and the other way round.
That count is the one number in this file that starts correct and
decays. It becomes one the moment a format move lands, and
tools/check-versions.py derives it from the section below rather than
reading it back, so the check fails the change that moves the format
without moving this line.
Added
-
The OPHTML console: any ps2ui theme, driven as a game launcher
(console/). Until now a baked UI could draw a game list and do
nothing with it: the runtime is display-only by design, and device
I/O and launching were the app's to write, so nobody's UI could list
or start a game.ophtml.elfloads USB, exFAT HDD, MX4SIO and MMCE
drivers, scans each drive for ISOs in OPL'sDVD/andCD/layout
(title IDs from the name or the disc'sSYSTEM.CNF), fills whatever
theme it finds —theme.uibbeside the ELF, thenOPHTML/theme.uib
on a drive, then the built-inexamples/console— and hands the
selected game to Neutrino with-qbthrough elf-loader's no-reset
entry point, so Neutrino reads it through the drivers the console
loaded. A theme opts in by using names (game-{i}rows,
game-{i}-title,sel-title,statusand the rest in
console/README.md), every one optional; nothing in C is the
author's to write. Proven so far:console/testson the host (file
names, ISO9660,SYSTEM.CNF, the directory scan, Neutrino's command
line), andhw.ymlbooting a MOCK build in Play! and diffing its
filled list against the previewer drawing the same games. Not yet:
no drive has been mounted and no game started on a console. Bench
cases C1–C8 inconsole/README.mdare that evidence, and each is
open. -
A console theme can be checked, previewed and navigated before it
reaches a console.ps2ui checkholds any blob with numbered
game-Nrows or slots to the console's contract: an error for a row the console would
leave on its placeholder (a gap ingame-N, rows on a screen it never
opens, a slot for a row that doesn't exist), a warning for a name it
never fills (game-0-titel, with "did you mean 'title'?"), a slot too
short for what it writes, or rows with no title;ps2ui-check --consoleforces it on a blob with none.sel-*andstatusalone
do not opt a blob in, because other UIs use them for their own panels
(channel-6 has five sel-* slots); a blob with no numbered row gets
none of these results, so every other blob's count is unchanged.ps2ui serve --consolefills a theme with the mock games
and walks its list the way the console does. Andhw.ymlnow presses
buttons at the console in Play! — Down three times, R1, L1 — and
diffs each frame against the previewer replaying the same keys
(worst tile 14.4-15.1 correct, 62-72 for a selection or window one
step off). That build runs on the ROM's pad modules, because Play!
gives the SDK'ssio2man+freepadno input after an IOP reset
(measured against the ROM pair, which it does answer); so it proves
the console's navigation, andfreepadstays bench case C8. The
contract, the mock list and the list window now live in
ps2ui_bake/console.py, which the check, the server and CI's
reference frames all read. -
Hard caps on what an untrusted theme may ask the compilers for. A
theme is a file somebody else wrote, and the compilers accepted
whatever it asked for. Four caps now, each measured before it was
chosen: canvas dimensions at 2048, elements at 10000, nesting at 64,
and a source image at 32 megapixels. Every one is overridable in
ps2ui.jsonbesidevramBudget--{"limits": {"nodes": 40000}}--
because a cap with no escape gets edited out of the source by the
first person it blocks.The numbers come from the shipped corpus rather than from taste.
Across all 17 screens inexamples/andfixtures/, counted with the
compiler's own walk afterdata-repeatexpands, the largest is 93
elements at depth 5, every supported mode is 640x448 or 640x512, and
the largest source image is 1984x1408. The headroom is each cap's
own: about 108x for elements, about 13x for depth, and for the canvas
none of that kind at all, because 2048 is the widest framebuffer the
hardware scans out rather than a multiple of anything the corpus
says. "An order of magnitude above the biggest real thing" stood
here until it was read back against the three.They fail rather than warn, which is the opposite of what
check-doc-impact.pyargues for itself and right for the same reason:
a warning is correct where the work might be fine, and a PlayStation 2
cannot display a 30000px canvas under any circumstances.The escape hatch broke two verbs before it worked.
ps2ui checkwas
sent a--limitit does not take, andps2ui devwas sent one it
did not take either -- the project-file page had listedlimitsas
reaching dev the whole time -- so a project declaring any cap got
unrecognized argumentsfrom the first and a bare usage line from
the second, whileps2ui buildon the same file was fine. Both are
fixed, and the fence is a test that reads the spawned tool's own
parser rather than a list maintained beside it. And the flag reached
both--helpoutputs before it reached any page that quotes one: the
two CLI pages and the diagnostics catalogue now carry--limit, the
three layout caps, the header-read image refusal and the scan-out
refusal, and a test holds each quoted usage line to the line its bin
prints, because nothing tied the two together. Re-running every
sabotage after that found one more: each cap has a test that its
check fires, and the image cap was the one whose number nothing
read, so raising it to ten billion left the whole suite green. It is
held to the corpus, to the default an ordinary bake gets, and to the
figure the project-file page prints.ps2ui-bake --limit nodes=5says so too now: a real cap, correctly spelled, handed to
the tool that does not enforce it, answered until now by
"takes imagePixels=N with N a positive integer" -- a complaint
about the 5. -
The console launcher can be downloaded, and the docs site says how
to run it. Until nowophtml.elfexisted only as a CI artifact,
which needs a GitHub login and expires after fourteen days, or as a
Docker build of this repository.console-release.ymlnow runs when
a version is tagged: it bakes the built-in theme, buildsophtml.elf
andophtml-mock.elffrom the tag, and attaches both with
SHA256SUMSto that tag's GitHub Release. A tag with no release gets
a draft, and a person publishes it (docs/releasing.mdstep 8b).The new site page, Console launcher, covers the drive layout,
installing Neutrino, putting your owntheme.uibbeside the ELF,
the names the launcher fills, the controls, and what each status
line and start-up colour means. It opens by saying the launcher has
booted only in an emulator, and it points at the bench cases that
are still open. -
A released CHANGELOG section is held to its tag. A merge in #166
put four entries into the released## 0.8.0section with no conflict
marker, and everytools/check-*.pypassed on that state (F53).
check-versions.pyrule 23 now compares each release tag's section,
heading and body, withgit show <tag>:CHANGELOG.md, and fails on a
difference. The one legitimate difference, v0.3.0's "Tagged and
published" paragraph written after its tag, is recorded with its
reason and pinned by hash, so it excuses that text and nothing else.
The rule runs on pull requests too, which is where that merge was. -
The
.uibloader is fuzzed (S2).runtime/tests/fuzz_load.c
runs libFuzzer overps2ui_arena_size,ps2ui_loadand everything a
program does with a blob that loaded, under ASan and UBSan, seeded
from every blob the repository builds, with each mutation's CRC
rewritten the way a hostile file would carry one.ci.ymlgives it
60 seconds on every change, andfuzz.ymlgives it half an hour
nightly, keeping its corpus between nights.make -C runtime fuzz
runs it locally. The Python half,test_fuzz_uib.py, mutates the
example blobs and requiresread_uibto raise only ValueError and
ps2ui checknever to raise. Both found real faults, listed under
Fixed.
Changed
registry.ymlstops carrying 0.8.0's fribidi remedy, now that 0.9.0
is what a stranger installs. It kept the macOS and Windows remedy
steps, and the-plainjobs' assertion that fontgen refuses, while the
published release still needed Raqm, switching on whether the wheel
carried_raqm_remedy. 0.9.0 published, and run 35817721457 passed
every arm against it: the tutorial on all four with the remedies
skipped, and the committed tables from a plain install on macOS arm64,
macOS x86_64 and Windows. The remedies, the switch and the refusal
assertions are deleted. What replaces the switch is a guard: the
first run after the publish, 35817387412, was served 0.8.0 on both
Macs by an index that had not caught up, and the old refusal passed
there, measuring nothing. The-plainjobs now fail by name when pip
hands them a release from before F47, rather than later in fontgen
with a message that blames the machine.
Fixed
-
A crafted
.uibcould crash the console before anything drew.
Nothing checked where a table sat, only that it fit, and every table
is read in place through a struct pointer. On the EE a misaligned
32-bit load is an address error. The fuzzer's first run found
ps2ui_arena_size, the first call a program makes on a blob, reading
the slot table at an odd address. The console launcher makes that
call on anytheme.uibit finds on a drive.ps2ui_loadnow refuses
a blob address, a table, or a font's glyph or kern table that is not
4-byte aligned, withPS2UI_ERR_ALIGN, andps2ui_arena_size
returns 0 for one. Every blob the baker writes passes: its header is
84 bytes, every entry a multiple of 4, and glyph and kern tables are
16-aligned. -
ps2ui checkprinted a Python traceback for a malformed blob.
read_uibpromises ValueError on a bad file, andps2ui check
catches that and prints the message. A table running past the end,
an unknown texture format or a glyph table outside the blob raised
struct.errororKeyErrorinstead. A blob that read cleanly but
referenced past a table gotIndexErrorfrom whichever later check
subscripted it. The reader now checks every table's extent and
alignment as the runtime does and refuses unknown formats. When a
reference or a screen range is bad, the checks that walk them are
skipped and the report says so; any other error stops nothing. -
A raised VRAM budget bought room for a framebuffer, which no budget
can.--vram-budgetexists to let a project declare the texture
space it really has. The refusal for a canvas too large to scan out
lived inside the advice printed only when no budget was given, so
passing one silenced the sentence and the failure together: a
30000x30000 canvas baked to a 17760-byte blob with exit 0, past a
message whose own last line reads "a narrower canvas is the only fix".
The budget charges textures and a framebuffer is not a texture, so the
check is now separate from the one a flag can reach. -
A 439 KiB image could cost 432 MB and eleven seconds, or end the
bake in a traceback. An image is pre-scaled to its laid-out size, so
the baked bytes were already bounded: 40 distinct 1024x1024 sources
bake to 40 KiB. Nothing bounded the decode. A 12000x12000 PNG baked
clean in 11.0 seconds to produce 4 KiB of texture, and one step larger
Pillow's ownDecompressionBombErrorreached the terminal raw. The
size is read from the header now, before any decode, so the same file
fails in 0.08 seconds with one error line. -
Nesting deep enough reported
Maximum call stack size exceeded. That is V8's stack rather than a decision, so the real
limit moved with the machine and the message named neither the
element nor a number. How far it moves, bisected on one checkout:
1842 on node v22.22.2's default stack, 889 under--stack-size=500,
7781 under--stack-size=4000. The first version of this entry said
"deeper than about 1500", which was the midpoint of the
1000-compiled / 2000-died bracket written as though it were a
reading; review of #166 asked for the reading. The refusal is the compiler's now, at 64, and
names the line the deepest element sits on. The check is iterative,
because a recursive walk to find the depth that breaks a recursive
walk overflows before it can report anything.