Skip to content

v0.10.0

Latest

Choose a tag to compare

@github-actions github-actions released this 26 Sep 15:35
60810bd

.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.elf loads USB, exFAT HDD, MX4SIO and MMCE
    drivers, scans each drive for ISOs in OPL's DVD/ and CD/ layout
    (title IDs from the name or the disc's SYSTEM.CNF), fills whatever
    theme it finds — theme.uib beside the ELF, then OPHTML/theme.uib
    on a drive, then the built-in examples/console — and hands the
    selected game to Neutrino with -qb through 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, status and the rest in
    console/README.md), every one optional; nothing in C is the
    author's to write. Proven so far: console/tests on the host (file
    names, ISO9660, SYSTEM.CNF, the directory scan, Neutrino's command
    line), and hw.yml booting 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 in console/README.md are that evidence, and each is
    open.

  • A console theme can be checked, previewed and navigated before it
    reaches a console.
    ps2ui check holds any blob with numbered
    game-N rows or slots to the console's contract: an error for a row the console would
    leave on its placeholder (a gap in game-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 --console forces it on a blob with none. sel-* and status alone
    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 --console fills a theme with the mock games
    and walks its list the way the console does. And hw.yml now 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's sio2man+freepad no input after an IOP reset
    (measured against the ROM pair, which it does answer); so it proves
    the console's navigation, and freepad stays 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.json beside vramBudget -- {"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 in examples/ and fixtures/, counted with the
    compiler's own walk after data-repeat expands, 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.py argues 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 check was
    sent a --limit it does not take, and ps2ui dev was sent one it
    did not take either -- the project-file page had listed limits as
    reaching dev the whole time -- so a project declaring any cap got
    unrecognized arguments from the first and a bare usage line from
    the second, while ps2ui build on 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 --help outputs 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=5 says 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 now ophtml.elf existed 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.yml now runs when
    a version is tagged: it bakes the built-in theme, builds ophtml.elf
    and ophtml-mock.elf from the tag, and attaches both with
    SHA256SUMS to that tag's GitHub Release. A tag with no release gets
    a draft, and a person publishes it (docs/releasing.md step 8b).

    The new site page, Console launcher, covers the 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. 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.0 section with no conflict
    marker, and every tools/check-*.py passed on that state (F53).
    check-versions.py rule 23 now compares each release tag's section,
    heading and body, with git 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 .uib loader is fuzzed (S2). runtime/tests/fuzz_load.c
    runs libFuzzer over ps2ui_arena_size, ps2ui_load and 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.yml gives it
    60 seconds on every change, and fuzz.yml gives 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 requires read_uib to raise only ValueError and
    ps2ui check never to raise. Both found real faults, listed under
    Fixed.

Changed

  • registry.yml stops 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 -plain jobs' 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 -plain jobs 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 .uib could 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 any theme.uib it finds on a drive. ps2ui_load now refuses
    a blob address, a table, or a font's glyph or kern table that is not
    4-byte aligned, with PS2UI_ERR_ALIGN, and ps2ui_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 check printed a Python traceback for a malformed blob.
    read_uib promises ValueError on a bad file, and ps2ui 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.error or KeyError instead. A blob that read cleanly but
    referenced past a table got IndexError from 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-budget exists 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 own DecompressionBombError reached 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.