SELFish documentation
Start here
- README - SELFish documentation
Guide
Reference
- GLOSSARY - Glossary: the formats
- USER_GUIDE - SELFish User Guide &
Packaging Cookbook
- backlog/01-the-vendor-dynamic-table-done-both
- 1.
The vendor dynamic table - done, both directions
- backlog/02-section-headers-and-symbol-tables-done
- 2.
Section headers and symbol tables - done
- backlog/03-param-sfo-and-param-json-done
- 3.
param.sfo and param.json -
done
- backlog/04-writing-the-filesystem-done-all-three
- 4.
Writing the filesystem - done, all three layers
- backlog/05-writing-a-package-done-image-included
- 5.
Writing a package - done, image included
- backlog/06-the-linker-script-done
- 6.
The linker script - done
- backlog/07-platform-abi-declarations-deferred-with
- 7. Platform ABI declarations - deferred with a condition
- backlog/08-not-planned -
Not planned
- backlog/_preamble -
Backlog
- decisions/D001-a-fourth-repository-holding-formats-and
- D001 - A fourth repository, holding formats and nothing that knows
what a consumer is for
- decisions/D002-the-generation-is-a-type-with-no-default
- D002 - The generation is a type with no
Default
- decisions/D003-not-an-sdk-was-the-wrong-line-the-line
- D003 - "Not an SDK" was the wrong line. The line is knowledge against
runtime
- decisions/D004-one-import-hash-pinned-by-389-pairs
- D004 - One import hash, pinned by 389 pairs somebody else
produced
- decisions/D005-oops-is-additive-never-required
- D005 - OOPS is additive, never required
- decisions/D006-entry-answers-for-itself-because-the
- D006 -
Entry answers for itself, because the first
version of it could not be called
- decisions/D007-the-container-is-byte-identical-to-the
- D007 - The container is byte-identical to the implementation it
replaces
- decisions/D008-the-package-s-outer-layer-first-because
- D008 - The package's outer layer first, because it needs no
cryptography
- decisions/D009-the-crypto-chain-is-unchanged-between
- D009 - The crypto chain is unchanged between generations, and that is
a finding
- decisions/D010-every-container-in-three-current
- D010 - Every container in three current-generation packages uses the
previous generation's magic
- decisions/D011-the-image-offset-is-a-header-field-and
- D011 - The image offset is a header field, and the evidence that it
was not could not have failed
- decisions/D012-what-a-package-writer-still-needs-and
- D012 - What a package writer still needs, and what it does not
- decisions/D013-two-vendor-tag-ranges-and-each-side
- D013 - Two vendor tag ranges, and each side documented only the one it
uses
- decisions/D014-reassembly-is-the-inverse-of-building
- D014 - Reassembly is the inverse of building, and both live
together
- decisions/D015-selfish-elf-depends-on-selfish-nid
- D015 -
selfish-elf depends on selfish-nid,
which reorders the spine
- decisions/D016-an-import-s-library-is-looked-up-by-its
- D016 - An import's library is looked up by its id, not by its position
in the table
- decisions/D017-relocations-are-read-and-censused-here
- D017 - Relocations are read and censused here; applying them stays
with the consumer
- decisions/D018-an-unrecognised-relocation-type-gets-no
- D018 - An unrecognised relocation type gets no name
- decisions/D019-the-param-sfo-alignment-rule-came-from
- D019 - The
PARAM.SFO alignment rule came from a source
and was refuted by eleven files
- decisions/D020-unterminated-text-is-a-separate-value
- D020 - Unterminated text is a separate value variant, not a flag
- decisions/D021-there-is-a-second-package-magic-and-it
- D021 - There is a second package magic, and it is named and refused
rather than guessed at
- decisions/D022-package-writing-stays-blocked-and-the
- D022 - Package writing stays blocked, and the source that looked like
it might unblock it does not
- decisions/D023-sections-are-a-separate-module-from
- D023 - Sections are a separate module from
dynamic,
because they answer a different question
- decisions/D024-the-linker-script-moves-here-and-its
- D024 - The linker script moves here, and its constants are tested
against the crate's
- decisions/D025-the-dynamic-table-writer-moves-here-and
- D025 - The dynamic-table writer moves here, and the manifest stays
with the caller
- decisions/D026-the-end-to-end-test-is-the-point-of
- D026 - The end-to-end test is the point of putting both halves in one
crate
- decisions/D027-filesystem-writing-is-blocked-and-now
- D027 - Filesystem writing is blocked, and now measured rather than
asserted
- decisions/D028-the-header-identity-is-stamped-here-too
- D028 - The header identity is stamped here too, and
e_type is a parameter rather than a constant
- decisions/D029-the-first-consumer-migrated-and-the
- D029 - The first consumer migrated, and the migration was the best
review this repository has had
- decisions/D030-the-two-halves-check-each-other-where
- D030 - The two halves check each other where material is
unavailable
- decisions/D031-the-command-line-tool-does-not-panic
- D031 - The command-line tool does not panic when a pipe closes
- decisions/D032-this-repository-gets-a-ci-workflow-and
- D032 - This repository gets a CI workflow, and obSCEne's is marked as
unable to pass
- decisions/D033-the-six-unknown-package-entries-all
- D033 - The six unknown package entries all vary between titles, so
there is no constant to fall back on
- decisions/D034-two-package-entries-were-derived-rather
- D034 - Two package entries were derived rather than cited, and there
is a command that re-derives them
- decisions/D035-a-derivation-ships-with-the-command
- D035 - A derivation ships with the command that re-runs it
- decisions/D036-entry-0x80-is-a-digest-manifest-over
- D036 - Entry
0x80 is a digest manifest over named things,
and two of its slots are established
- decisions/D037-a-package-writer-that-refuses-to-invent
- D037 - A package writer that refuses to invent the entries nothing
established explains
- decisions/D038-what-the-cryptographic-entries-are-not
- D038 - What the cryptographic entries are not, recorded so nobody
re-runs the searches
- decisions/D039-liborbispkg-named-all-six-and-the
- D039 - LibOrbisPkg named all six, and the derivations were right
- decisions/D040-playgo-chunk-sha-is-solved-and-material
- D040 -
PLAYGO_CHUNK_SHA is solved, and material settled
what the source left open
- decisions/D041-the-licence-entries-are-stored
- D041 - The licence entries are stored encrypted, so naming them was
not enough
- decisions/D042-the-filesystem-superblock-was-never-a
- D042 - The filesystem superblock was never a wall - it was 95 unnamed
bytes
- decisions/D043-naming-a-field-is-not-the-same-as-being
- D043 - Naming a field is not the same as being able to write one
- decisions/D044-the-licence-entries-were-never-unknown
- D044 - The licence entries were never unknown - a thirty-two-byte
record, not three fields
- decisions/D045-understood-is-not-written
- D045 - Understood is not written
- decisions/D046-the-licence-structure-is-measured
- D046 - The licence structure is measured; producing one needs two keys
this repository does not hold
- decisions/D047-a-licence-is-built-from-scratch-and-a
- D047 - A licence is built from scratch, and a real one is reproduced
byte for byte
- decisions/D048-pack-now-demands-only-what-a-title-is
- D048 -
pack now demands only what a title is, not what a
package is
- decisions/D049-the-filesystem-is-built-in-two-halves
- D049 - The filesystem is built in two halves, because a package has
two filesystems built to different rules
- decisions/D050-pfsc-does-not-compress-and-that-is-the
- D050 -
PFSC does not compress, and that is the format
rather than a shortcut
- decisions/D051-the-two-ways-of-writing-a-1-near-the
- D051 - The two ways of writing a
1 near the end of the
superblock are two fields, not one
- decisions/D052-a-block-signature-is-an-hmac-under-a
- D052 - A block signature is an HMAC under a key the builder
computes
- decisions/D053-signature-ordering-is-the-correctness
- D053 - Signature ordering is the correctness argument, so it is
written as one sequence
- decisions/D054-the-key-blobs-are-computed-and-the
- D054 - The key blobs are computed, and the proof is that they come out
byte-identical to real packages
- decisions/D055-the-passcode-reaches-further-than-the
- D055 - The passcode reaches further than the key blobs, and a test
keyed a package differently to find out
- decisions/D056-the-header-past-0x410-was-entirely-zero
- D056 - The header past
0x410 was entirely zero, so
nothing could be mounted
- decisions/D057-the-flat-path-table-is-real-now-and-it
- D057 - The flat path table is real now, and it was built precisely
because nothing here reads it
- decisions/D058-selfish-image-exists-because-another
- D058 -
selfish image exists because another session asked
for it in a script comment
- decisions/D059-a-param-sfo-is-a-format-and-belongs
- D059 - A
param.sfo is a format and belongs here. An icon
is a picture and does not
- decisions/D060-the-default-icon-is-selfish-s-own-mark
- D060 - The default icon is selfish's own mark, reversing D059
- decisions/D061-the-param-sfo-field-set-was-the
- D061 - The
param.sfo field set was the previous
generation's, and two homebrew packages said so
- decisions/D062-a-second-param-sfo-implementation-and
- D062 - A second
PARAM.SFO implementation, and why it
happened
- decisions/D063-one-directory-entry-writer-because-the
- D063 - One directory-entry writer, because the copy had the rule
without the reasoning
- decisions/D064-a-literal-nul-byte-in-a-source-file
- D064 - A literal NUL byte in a source file made it invisible to
grep, which hid a duplicate constant
- decisions/D065-cargo-doc-was-failing-and-the-gate-did
- D065 -
cargo doc was failing, and the gate did not build
docs
- decisions/D066-documentation-drifts-in-the-direction
- D066 - Documentation drifts in the direction of claiming less than the
code does
- decisions/D067-supporting-the-current-generation-is
- D067 - Supporting the current generation is three-quarters done and
blocked on one oracle
- decisions/D068-the-logo-is-one-file-committed-and-the
- D068 - The logo is one file, committed, and the drawn mark is
gone
- decisions/D070-the-cache-size-is-a-ceiling-measured
- D070 - The cache size is a ceiling measured from the image, not a
constant
- decisions/D071-d070-s-rule-was-wrong-and-the-corrected
- D071 - D070's rule was wrong, and the corrected one cannot be tested
by avoiding it
- decisions/D072-the-inner-filesystem-s-root-pointed-its
- D072 - The inner filesystem's root pointed its parent at the super
root
- decisions/D073-the-tool-converts-a-supplied-icon
- D073 - The tool converts a supplied icon, rather than asking four
projects to export one
- decisions/D074-an-executable-declares-no-export
- D074 - An executable declares no export library, which frees library
id zero
- decisions/D075-a-container-entry-s-memsz-is-its-data-s
- D075 - A container entry's
memsz is its data's size, not
the segment's memory size
- decisions/D076-the-dynamic-table-lives-at-the-tail-of
- D076 - The dynamic table lives at the tail of the vendor segment, not
in the image
- decisions/D077-the-vendor-segment-begins-with-a
- D077 - The vendor segment begins with a fingerprint region, and
leaving it out moves everything
- decisions/D078-a-bundled-library-is-a-third-layout-and
- D078 - A bundled library is a third layout, and it needs a third
linker script
- decisions/D079-dt-sce-original-filename-is-required
- D079 -
DT_SCE_ORIGINAL_FILENAME is required, and holds
the module's own name
- decisions/D080-the-keystone-is-derived-not-supplied
- D080 - The keystone is derived, not supplied, and every package this
crate built was missing one
- decisions/D081-param-json-does-not-belong-in-a-package
- D081 -
param.json does not belong in a package, and its
presence there was mixing two routes
- decisions/D082-a-package-cannot-produce-a-current
- D082 - A package cannot produce a current-generation title, so
native is a second delivery route rather than a second
package format
- decisions/D083-a-comment-in-the-keyset-file-broke-the
- D083 - A comment in the keyset file broke the licence, because the
reader matched prose
- decisions/D084-a-real-container-can-be-audited-against
- D084 - A real container can be audited against the format table - the
oracle step, as a command
- decisions/D085-getting-a-real-current-generation-self
- D085 - Getting a real current-generation SELF is a measurement obSCEne
runs, not a file this repo holds - and the sandbox makes it
conditional
- decisions/D086-confirm-the-format-on-the-console-and
- D086 - Confirm the format on the console and report the verdict - do
not carry the bytes off
- decisions/D087-native-ps5-title-manifests-param-json
- D087 - Native PS5 title manifests (
param.json) and
differential measurement over fake-signed packaging
- decisions/D088-libkernel-vaddrs-example-exports-as
- D088 -
libkernel_vaddrs example: exports as
name vaddr, and why the names are not ours
- decisions/D089-naming-a-hash-backwards-is-a-search
- D089 - Naming a hash backwards is a search over somebody else's
vocabulary, so the vocabulary is an argument
- decisions/D090-the-unterminated-sfo-format-is-a
- D090 - The unterminated SFO format is a length, not a promise of text,
and reading it as text failed whole files
- decisions/D091-a-writer-must-refuse-where-a-reader
- D091 - A writer must refuse where a reader may guess, and this crate
had them the wrong way round
- decisions/D092-the-container-that-matched-this
- D092 - The container that matched this table was one this table could
have written
- decisions/D093-the-audit-reports-what-kind-of
- D093 - The audit reports what kind of container it just agreed with,
before it reports how many rows agreed
- decisions/D094-the-audit-checks-the-tail-it-pins
- D094 - The audit checks the tail it pins, and reports it apart from
the header
- decisions/D095-a-second-reader-found-three-and-one
- D095 - A second reader found three disagreements, and only one of them
was about the format
- decisions/D096-two-answers-to-the-symbol-count-and
- D096 - Two answers to the symbol count, and the divergence is kept
rather than reconciled
- decisions/D097-the-wrap-default-was-justified-by-a
- D097 - The wrap default was justified by a population counted in the
wrong place, and the default is right anyway
- decisions/D098-measured-vendor-values-are-recorded
- D098 - Measured vendor values are recorded against the rows they
refute, never as a profile a writer could reach for
- decisions/D099-the-inner-size-was-never-unknown
- D099 - The inner size was never unknown, and entry 0x1001 is computed
like every other derivable entry
- decisions/D100-stamp-and-wrap-retire-into-the-pipeline
- D100 -
stamp and wrap retire into the
pipeline; --format prx, and
--privilege/--sdk as pipeline options
- decisions/D101-a-module-built-here-cannot-carry-a-weak-import
- D101 - A module built here cannot carry a weak undefined import, by
construction; the forcing to GLOBAL stays
- decisions/D102-title-metadata-options-on-the-pipeline
- D102 - The title metadata a title carries gets pipeline spellings;
--root does not
- decisions/D103-the-agc-shader-container-and-where-it-may-come-from
- D103 - The AGC shader container is admissible as a format, but only
the part a citable source derives - not the vendor-binary part, and not
because hardware accepts it
- decisions/D104-selfish-cli-depends-on-nothing-outside-this-repository-again
- D104 - selfish-cli depends on nothing outside this repository again:
the commit stamp is local git, the logging was dead weight
- decisions/_preamble -
Decisions
- worklog/001-the-repository-exists-and-the-spine-is
- The repository exists, and the spine is laid
- worklog/002-the-generation-split-made
- The generation split, made unrepresentable rather than tested for
- worklog/003-the-hash-and-an-objection-that-turned
- The hash, and an objection that turned out to argue the other way
- worklog/004-the-executable-format-and-the-container
- The executable format, and the container in both directions
- worklog/005-the-package-s-outer-container
- The package's outer container
- worklog/006-the-key-derivation
- The key derivation
- worklog/007-the-filesystem-and-the-whole-chain-end
- The filesystem, and the whole chain end to end
- worklog/008-a-command-line-tool-and-an-audit-that
- A command-line tool, and an audit that found the real gap
- worklog/009-the-dynamic-table-and-the-whole-stack
- The dynamic table, and the whole stack on genuine material
- worklog/010-the-symbol-table-and-imports-resolved
- The symbol table, and imports resolved to names
- worklog/011-relocations-and-the-check-that-closed
- Relocations, and the check that closed the loop
- worklog/012-param-sfo-and-param-json-and-eleven
-
PARAM.SFO and param.json, and eleven files
that disagreed with the source
- worklog/013-two-package-magics-and-a-source-that
- Two package magics, and a source that was not the one we needed
- worklog/014-section-headers-and-the-link-time
- Section headers and the link-time symbol table
- worklog/015-the-linker-script-and-a-test-that
- The linker script, and a test that actually links
- worklog/016-the-dynamic-table-writer-and-the-loop
- The dynamic-table writer, and the loop closing
- worklog/017-sizing-the-filesystem-writing-gap
- Sizing the filesystem-writing gap instead of starting it
- worklog/018-running-the-finished-thing-and-finding
- Running the finished thing, and finding the last missing field
- worklog/019-the-first-migration
- The first migration
- worklog/020-closing-three-gaps-the-migration-opened
- Closing three gaps the migration opened
- worklog/021-crunching-the-unknown-package-entries
- Crunching the unknown package entries
- worklog/022-package-writing-built-up-to-the-wall
- Package writing, built up to the wall
- worklog/023-the-source-and-what-it-did-and-did-not
- The source, and what it did and did not settle
- worklog/024-the-superblock-was-never-a-wall
- The superblock was never a wall
- worklog/025-licences-built-and-proven
- Licences, built and proven
- worklog/026-sections-dynamic-symbols-reading-dynsym
-
Sections::dynamic_symbols - reading
.dynsym
- worklog/027-the-filesystem-written
- The filesystem, written
- worklog/028-a-package-obscene-can-build-and-what-is
- A package obSCEne can build, and what is still placeholder
- worklog/029-the-reader-s-refusals-tested
- The reader's refusals, tested
- worklog/030-package-header-sensible-defaults
- Package header: sensible defaults instead of zeros (found on
hardware)
- worklog/031-the-package-header-is-integrity
- The package header is integrity-protected, and that is the wall
(measured on hardware)
- worklog/032-a-fake-package-through-three-console
- A fake package through three console rejection stages (measured on
hardware)
- worklog/033-a-hardcoded-cache-size-makes-a-small
- A hardcoded cache size makes a small package unmountable
- worklog/034-auditing-the-other-constants-found-a
- Auditing the other constants found a second one that was never
constant
- worklog/035-the-indirect-block-looked-guilty-and-is
- The indirect block looked guilty and is not, which sets up the next
test
- worklog/036-the-inner-filesystem-mounts-it-was-the
- The inner filesystem mounts. It was the root's parent all along.
- worklog/037-an-eboot-is-not-the-module-this-project
- An eboot is not the module this project builds, in three separate
ways
- worklog/038-the-eboot-executes-it-is-a-process-now
- The eboot executes. It is a process now, and it dies on a
syscall.
- worklog/039-five-refusals-each-naming-the-next-an
- Five refusals, each naming the next: an eboot walked into a console's
loader
- worklog/040-a-package-built-here-installs-mounts
- A package built here installs, mounts, loads and executes on
hardware
- worklog/041-a-third-linker-script-and-the-tag-that
- A third linker script, and the tag that was named but never
written
- worklog/042-libkernel-vaddrs-example
- 2026-08-31 -
libkernel_vaddrs example
- worklog/043-native-ps5-title-generation-obscene
- 2026-08-31 - Native PS5 Title Generation & obSCEne Self-Resolver
Oracle
- worklog/044-three-unnameable-imports-and-one-was
- 2026-09-09 - Three unnameable imports, and one of them was never a
name
- worklog/045-account-id-was-not-text-and-the-reader
- 2026-09-09 -
ACCOUNT_ID was not text, and the reader
refused the file rather than the key
- worklog/046-the-same-function-twice-to-opposite
- 2026-09-09 - The same function twice, written to opposite
standards
- worklog/047-the-table-was-right-and-the-limit-was
- 2026-09-09 - The table was right, and the limit was the wrong
shape
- worklog/048-a-label-in-a-candidate-list-is-not
- 2026-09-09 - A label in a candidate list is not provenance
- worklog/049-the-confirmation-was-circular-and-the
- 2026-09-09 - The confirmation was circular, and the entry that caught
it had just written the rule
- worklog/050-the-lesson-put-in-the-tool-and-the
- 2026-09-09 - The lesson put in the tool, and the reversal made
airtight
- worklog/051-the-tail-held-and-the-instinct-was
- 2026-09-09 - The tail held, and the instinct about it was wrong the
other way
- worklog/052-the-second-reader-and-the-error-that
- 2026-09-09 - The second reader, and the error that pointed at the
wrong thing
- worklog/053-two-readers-agree-on-every-field-and
- 2026-09-09 - Two readers agree on every field, and the third failure
of the day was agreeing
- worklog/054-thirty-two-containers-and-a-confound
- 2026-09-09 - Thirty-two containers, a clean split, and a confound I
had already glossed
- worklog/055-the-symbol-level-agreed-and-the
- 2026-09-09 - The symbol level agreed, and the question about it found
something
- worklog/056-the-accusation-in-the-suffix-file-was
- 2026-09-09 - The accusation in the suffix file was made from a
label
- worklog/057-a-title-id-in-a-citation-read-as-a-fact
- 2026-09-09 - A title id in a citation, read as a fact
- worklog/058-the-audit-closed-and-the-test-that
- 2026-09-09 - The audit closed, and the test that makes it cheap next
time
- worklog/059-the-magic-settled-by-the-row-nobody
- 2026-09-09 - The magic, settled by the row nobody asked for
- worklog/060-both-magics-are-accepted-and-the
- 2026-09-09 - Both magics are accepted, and the cross-census already
contained our own file
- worklog/061-a-green-gate-that-was-never-checked
- 2026-09-10 - A green gate that was never checked, and a commit that
took somebody else's work
- worklog/062-one-vocabulary-and-the-orphaned
- 2026-09-10 - One vocabulary, and an attribute that outlived its
variant
- worklog/063-a-guard-that-covered-one-of-three
- 2026-09-10 - A guard that covered one of three, and five copies of one
table
- worklog/064-half-of-a-refusal-was-never-a-refusal
- 2026-09-10 - Half of a refusal was never a refusal
- worklog/065-the-question-i-filed-was-answered-in
- 2026-09-10 - The question I filed was answered in a file I had already
cited
- worklog/066-the-last-two-verbs-and-a-paid-value-nothing-wrote
- 2026-09-10 - The last two verbs, and a
paid value nothing
wrote
- worklog/067-the-pipeline-learns-what-a-title-carries
- 2026-09-10 - The pipeline learns what a title carries, and an empty
content id that keyed nothing
- worklog/068-a-review-caught-icon-copied-not-converted
- 2026-09-10 - A review caught
--icon copied, not converted
- and my evidence that it was
- worklog/069-the-shader-container-was-citable-after-all
- 2026-09-10 - The shader container was citable after all, once I looked
past the first two emulators
- worklog/070-a-cli-verb-so-a-c-consumer-can-reach-the-shader-builder
- 2026-09-11 - A CLI verb, so a C consumer can reach the shader
builder
- worklog/071-pixel-and-vertex-stages-and-the-constants-i-would-not-ship
- 2026-09-11 - Pixel and vertex stages, the hardware that confirmed the
header, and the constants I would not ship
- worklog/072-selfish-cli-stands-alone-again
- 2026-09-11 - selfish-cli stands alone again: the local commit stamp,
and the logging that logged nothing
- worklog/_preamble -
Worklog
Project memory