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: object

Collects 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 to

Warning

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)

src_path: str

The source path

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

file_replacements: dict[str, bytes]

A dictionnary that map files to their data

fst_modifier: Callable[[FST], None] | None

Callback applied to the FST at build time, set by modify_fst()

files_to_add: dict[str, bytes]

A dictionnary that map new files to their data

files_to_remove: list[str]

A list of files to remove

cached_archive: tuple[str, Archive, list[Container]] | None

Archive currently open for edit_as(), kept so successive edits share one decompression

property title: str
property title_id: str
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 by add_file() and remove_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:

None

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

Parameters:
  • path (str) – Destination path inside the DATA partition. Leading and trailing slashes are stripped

  • data (bytes) – File content in bytes

Return type:

None

remove_file(path)[source]

Queue a new file for deletion

If path was queued by add_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:

None

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

Parameters:
  • path (str) – Path inside the DATA partition

  • data (bytes) – Replacement contents

Return type:

None

list_files()[source]

List every file of the DATA partition

Return type:

list[str]

Returns:

Full paths, using / as separator

Warning

This reflects the source disc. Files queued by add_file() or remove_file() do not appear or disappear until build()

read_file(path)[source]

Read a file from the source disc

Parameters:

path (str) – Path inside the DATA partition

Return type:

bytes

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:
Parameters:

path (str)

Return type:

bytes

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 with obj.write() and queues the result as a replacement when the block exits

The 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:
  • path (str) – Path inside the DATA partition, optionally continuing inside an archive

  • cls (type[TypeVar(T)]) – Format class providing read(stream, **kwargs) and write(stream), such as BCSV or Rarc

  • **kwargs (int) – Forwarded to cls.read()

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:
Return type:

None

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:

DOL

Returns:

The parsed DOL, without any pending patch applied

get_banner_title(language='English')[source]
Parameters:

language (str, default: 'English')

Return type:

str

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:
  • new_title (str) – New title

  • language (str, default: 'English') – One of Japanese, English, German, French, Spanish, Italian, Dutch, Simplified Chinese, Traditional Chinese or Korean

Raises:

ValueError – If language is not one of the values above

Return type:

None

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:
  • output_path (str) – Path of the ISO to create. It is overwritten if it exists

  • progress_cb (Callable | None, default: None) – Called with an integer percentage from 0 to 100. It is invoked once per partition, so the value restarts at 0 for each

Return type:

None

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