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_modifier: Callable[[DOL], None] | 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

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

Raises:
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 – Forwarded to cls.read()

Yields:

The parsed object, ready to modify

Return type:

Iterator[T]

Warning

Each call re-reads from the source disc. Editing two files inside the same archive with two successive calls loses the first edit, because the second call reopens the original archive Do both edits in a single block, opening the archive itself as Rarc

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)[source]

Register a callback that patches the main executable

The callback runs during build(), on the DOL of the DATA partition

Parameters:

fn (Callable[[DOL], None]) – Called with the parsed DOL. Modify it in place

Return type:

None

Note

Only one callback is kept. Calling this twice replaces the first

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_infos()[source]

Summarise the source disc

Return type:

dict

Returns:

A dict with keys game_id, title, disc_number and version game_id is decoded to str and stripped of padding

modify_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

modify_title(new_title)[source]

Change the game title stored in the disc header

Parameters:

new_title (str) – New title. It is truncated to the field size when written

Return type:

None

Note

This changes the disc header only. The name shown in the Wii menu comes from the banner, see modify_banner_title()

modify_title_id(new_id)[source]

Change the game ID of the disc and of the ticket

Parameters:

new_id (str) – Exactly 6 ASCII characters, such as "FEUR69"

Raises:
Return type:

None

Note

The ticket title ID is rebuilt as 0x00010000 followed by the first four characters of the new ID

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 (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