wiithon.disc.patcher module
Modifying a Wii disck image and rebuilding it
This module exposes WiiIsoPatcher. To inspect it without modifying it, see wiithon.disc.reader
- class wiithon.disc.patcher.WiiIsoPatcher(src_path)[source]
Bases:
objectCollects modifications to a wii ISO and writes a new one
Nothing is applied as you go. Every call records an intent and the whole set is replayed when
build()is called The source ISO is opened read only and never written toWarning
Only the DATA partition is patched. Other partition are copied to the output byte by byte
Example
>>> with WiiIsoPatcher("path/to/iso") as patcher: ... patcher.replace_file("opening.bnr", data) ... patcher.modify_title("My super game") ... patcher.build("output/iso")
- Parameters:
src_path (
str)
- reader: WiiIsoReader | None
WiiIsoReader. Used internally
- data_partition: WiiPartitionInfo | None
The opened data partition
- dol_modifiers: list[Callable[[DOL], None]]
A callback function that runs when build is called. Used for modifying the DOL
- fst_modifier: Callable[[FST], None] | None
Callback applied to the FST at build time, set by
modify_fst()
- cached_archive: tuple[str, Archive, list[Container]] | None
Archive currently open for
edit_as(), kept so successive edits share one decompression
- modify_fst(fn)[source]
Register a callback that edits the file system table directly
The callback runs during
build(), before the additions and removals queued byadd_file()andremove_file()are applied- Parameters:
fn (
Callable[[FST],None]) – Called with the FST of the DATA partition. Its return value is ignored, modify the tree in place- Return type:
Note
Only one callback is kept. Calling this twice replaces the first
Example
>>> with WiiIsoPatcher("path/to/iso") as patcher: ... patcher.modify_fst(lambda x: print(x.count_files())) ... patcher.build("output")
- add_file(path, data)[source]
Queue a new file for insertion
The file is added to the file system table and its data is written at build time. Parent directories must exists
- remove_file(path)[source]
Queue a new file for deletion
If
pathwas queued byadd_file()earlier that pending addition is cancelled instead of scheduling a removal- Parameters:
path (
str) – Destination path inside the DATA partition. Leading and trailing slashes are stripped- Return type:
Note
Removing a path that does not exist just do nothing, since the FST finds nothing
- replace_file(path, data)[source]
Queue new contents for an existing file
Unlike
add_file(), this does not touch the file system table, so the file must already exist on the disc. The new data may be of any size
- list_files()[source]
List every file of the DATA partition
Warning
This reflects the source disc. Files queued by
add_file()orremove_file()do not appear or disappear untilbuild()
- read_file(path)[source]
Read a file from the source disc
- Parameters:
path (
str) – Path inside the DATA partition- Return type:
- Returns:
The contents as stored in the source ISO. Pending replacements are not applied, so reading a file you just replaced returns the original data
Note
edit_as()does see its own earlier edits since it keeps the archive in memory. This method does not: it always reads the source- Raises:
FstFileNotFoundError – If no such file exists
FstIsADirectoryError – If the path is a directory
- Parameters:
path (str)
- Return type:
- edit_as(path, cls, **kwargs)[source]
Edit a file in place parsed as a given format
Reads the file, parses it with
cls.read(), hands you the object, then serialises it back withobj.write()and queues the result as a replacement when the block exitsThe path may cross archive boundaries Given
"Stage.arc/scenariodata.bcsv", the RARC archive is opened, the inner file is extracted, and the archive is re-serialised around your changes Yaz0 compression is handled transparently. Everything is transparent. You want the object, you have the object- Parameters:
- Yields:
The parsed object, ready to modify
- Return type:
Iterator[T]
Warning
Successive calls on files of the same archive reuse the archive already in memory so edits accumulate instead of overwriting each other. The archive is decompressed once and re-serialised once, which also makes editing many files in a loop cheap
Note
If the block raises, nothing is written back
Example
>>> with patcher.edit_as("AstroDome/AstroDome.arc/stageinfo/layera", BCSV, str_fmt="shift_jis") as bcsv: ... for entry in bcsv.entries: ... entry["Timer"] = 0
- patch_dol(fn, *args, **kwargs)[source]
Register a callback that patches the main executable
The callback runs during
build(), on the DOL of the DATA partition- Parameters:
fn (
Callable[[Concatenate[DOL,ParamSpec(P, bound=None)]],None]) – Called with the parsedDOL. Modify it in place*args (
ParamSpecArgs) – Extra positional arguments forwarded tofn**kwargs (
ParamSpecKwargs) – Extra keyword arguments forwarded tofn
- Return type:
Note
Callbacks accumulate. Call this several times and they run in registration order each seeing the DOL as the previous one left it
Note
modify_fst()does not work this way, it keeps a single callback (for now)See also
Patching a Wii ISO for code injection above the arena
- read_dol()[source]
Read the main executable of the source disc
- Return type:
- Returns:
The parsed DOL, without any pending patch applied
- set_banner_title(new_title, language='English')[source]
Change the title shown in the Wii menu, for one language
Reads
opening.bnr, rewrites its IMET header and queues the result as a replacement- Parameters:
- Raises:
ValueError – If
languageis not one of the values above- Return type:
- build(output_path, progress_cb=None)[source]
Write the patched ISO
Every partition of the source disc is copied to the output The DATA partition additionally receives the queued file changes, the FST callback and the DOL callback Hashes and encryption are recomputed as required
- Parameters:
- Return type:
Note
This is where all the work happens. Expect it to take a while and to need free disc space of roughly the size of the source ISO