From e7070b67581a6d277b2db431f2d036df07dc1342 Mon Sep 17 00:00:00 2001 From: NinjaCheetah <58050615+NinjaCheetah@users.noreply.github.com> Date: Thu, 16 Jan 2025 22:44:28 -0500 Subject: [PATCH] Unfinished wiiload module and LZ77 compression code --- src/libWiiPy/archive/lz77.py | 58 ++++++++++++++++++++++++++++++++ src/libWiiPy/title/wiiload.py | 62 +++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 src/libWiiPy/title/wiiload.py diff --git a/src/libWiiPy/archive/lz77.py b/src/libWiiPy/archive/lz77.py index 8215fb8..88e94be 100644 --- a/src/libWiiPy/archive/lz77.py +++ b/src/libWiiPy/archive/lz77.py @@ -6,6 +6,64 @@ import io +def compress_lz77(data: bytes) -> bytes: + """ + + Parameters + ---------- + data + + Returns + ------- + + """ + def compare_bytes(byte1: bytes, offset1: int, byte2: bytes, offset2: int, len_max: int, abs_len_max: int) -> int: + num_matched = 0 + if len_max > abs_len_max: + len_max = abs_len_max + for i in range(0, len_max): + if byte1[offset1 + 1] == byte2[offset2 + 1]: + num_matched += 1 + else: + break + if num_matched == len_max: + offset1 -= len_max + for i in range(0, abs_len_max - len_max): + if byte1[offset1 + 1] == byte2[offset2 + 1]: + num_matched += 1 + else: + break + return num_matched + + def search_match(buffer: bytes, pos: int) -> (int, int): + bytes_left = len(buffer) - pos + # Default to only looking back 4096 bytes, unless we've moved fewer than 4096 bytes, in which case we should + # only look as far back as we've gone. + max_dist = 0x1000 + if max_dist > pos: + max_dist = pos + # We want the longest match we can find. + biggest_match = 0 + biggest_match_pos = 0 + # Default to only matching up to 18 bytes, unless fewer than 18 bytes remain, in which case we can only match + # up to that many bytes. + max_len = 0x12 + if max_len > bytes_left: + max_len = bytes_left + for i in range(1, max_dist): + num_compare = max_len + if num_compare > i: + num_compare = i + if num_compare > max_len: + num_compare = max_len + num_matched = compare_bytes(buffer, pos - i, buffer, pos, max_len, 0x12) + + output_data = b'LZ77\x10' + # Write the header by finding the size of the uncompressed data. + output_data += int.to_bytes(len(data), 3, 'little') + search_match(data, 0) + + def decompress_lz77(lz77_data: bytes) -> bytes: """ Decompresses LZ77-compressed data and returns the decompressed result. Supports data both with and without the diff --git a/src/libWiiPy/title/wiiload.py b/src/libWiiPy/title/wiiload.py new file mode 100644 index 0000000..eeb9d85 --- /dev/null +++ b/src/libWiiPy/title/wiiload.py @@ -0,0 +1,62 @@ +# "title/wiiload.py" from libWiiPy by NinjaCheetah & Contributors +# https://github.com/NinjaCheetah/libWiiPy +# +# This code is adapted from "wiiload.py", which can be found on the WiiBrew page for Wiiload. +# https://pastebin.com/4nWAkBpw +# +# See https://wiibrew.org/wiki/Wiiload for details about how Wiiload works + +import sys +import zlib +import socket +import struct + + +def send_bin_wiiload(target_ip: str, bin_data: bytes, name: str) -> None: + """ + Sends an ELF or DOL binary to The Homebrew Channel via Wiiload. This requires the IP address of the console you + want to send the binary to. + + Parameters + ---------- + target_ip: str + The IP address of the console to send the binary to. + bin_data: bytes + The data of the ELF or DOL to send. + name: str + The name of the application being sent. + """ + wii_ip = (target_ip, 4299) + + WIILOAD_VERSION_MAJOR=0 + WIILOAD_VERSION_MINOR=5 + + len_uncompressed = len(bin_data) + c_data = zlib.compress(bin_data, 6) + + chunk_size = 1024*128 + chunks = [c_data[i:i+chunk_size] for i in range(0, len(c_data), chunk_size)] + + args = [name] + args = "\x00".join(args) + "\x00" + + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect(wii_ip) + + s.send("HAXX") + s.send(struct.pack("B", WIILOAD_VERSION_MAJOR)) # one byte, unsigned + s.send(struct.pack("B", WIILOAD_VERSION_MINOR)) # one byte, unsigned + s.send(struct.pack(">H",len(args))) # bigendian, 2 bytes, unsigned + s.send(struct.pack(">L",len(c_data))) # bigendian, 4 bytes, unsigned + s.send(struct.pack(">L",len_uncompressed)) # bigendian, 4 bytes, unsigned + + print(len(chunks),"chunks to send") + for piece in chunks: + s.send(piece) + sys.stdout.write("."); sys.stdout.flush() + sys.stdout.write("\n") + + s.send(args) + + s.close() + print("done")