diff --git a/Goldtree.py b/Goldtree.py index 567a80d..28d633e 100755 --- a/Goldtree.py +++ b/Goldtree.py @@ -5,138 +5,430 @@ import struct import sys import os +import shutil +import io +from enum import Enum +from pathlib import Path +from collections import OrderedDict -from PFS0 import PFS0 - -def get_switch(): - dev=usb.core.find(idVendor=0x057e, idProduct=0x3000) - if dev is None: - raise ValueError("Device not found") - return dev -def get_ep(dev): - dev.set_configuration() - intf=dev.get_active_configuration()[(0,0)] - return (usb.util.find_descriptor(intf, - custom_match=lambda e:usb.util.endpoint_direction(e.bEndpointAddress)==usb.util.ENDPOINT_OUT), - usb.util.find_descriptor(intf, - custom_match=lambda e:usb.util.endpoint_direction(e.bEndpointAddress)==usb.util.ENDPOINT_IN)) - -class CommandId: - ConnectionRequest=0 - ConnectionResponse=1 - NSPName=2 - Start=3 - NSPData=4 - NSPContent=5 - NSPTicket=6 - Finish=7 +class USBHandler: + CommandBlockLength = 0x1000 + + def __init__(self, idVendor=0x057e, idProduct=0x3000): + super().__init__() + + self.dev = usb.core.find(idVendor=idVendor, idProduct=idProduct) + if self.dev is None: + raise ValueError("Device not found") + + self.ep = self.get_ep() + + self.read_buf = io.BytesIO() + self.write_buf = io.BytesIO() + + self.drives = OrderedDict() + + def get_ep(self): + self.dev.set_configuration() + intf = self.dev.get_active_configuration()[(0,0)] + return (usb.util.find_descriptor(intf, + custom_match=lambda e:usb.util.endpoint_direction(e.bEndpointAddress)==usb.util.ENDPOINT_OUT), + usb.util.find_descriptor(intf, + custom_match=lambda e:usb.util.endpoint_direction(e.bEndpointAddress)==usb.util.ENDPOINT_IN)) + + def clear(self): + self.read_buf = io.BytesIO() + self.write_buf = io.BytesIO() + + def read(self, size=-1): + if size == str: + length = self.read("I") + return self.read(length).decode().replace("\x00", "") + + elif size == Path: + path = self.read(str) + drive = path.split(":", 1)[0] + try: + path = path.replace(drive + ":/", str(self.drives[drive][0])) + except KeyError: + pass + return Path(path) + + elif isinstance(size, str): + fmt = size + size = struct.calcsize(fmt) + + else: + fmt = None + + pos = self.read_buf.tell() + ret = self.read_buf.read(size) + + if size == -1: + pass + elif self.read_buf.read(1) != b"": + self.read_buf.seek(-1, 1) + pass + else: + length = self.read_buf.tell() + + self.read_buf = io.BytesIO(self.read_raw(self.CommandBlockLength)) + + ret += self.read_buf.read(size - length + pos) + + if fmt is not None: + ret = struct.unpack(f"<{fmt}", ret) + if len(ret) == 1: + ret = ret[0] + + return ret + + def write(self, fmt=None, *args): + if fmt is None: + return + + if len(args) < 1: + if isinstance(fmt, str): + self.write("I", len(fmt) + 1) + self.write(fmt.encode() + b"\x00") + + elif isinstance(fmt, Path): + path = str(fmt) + for d,p in self.drives.items(): + if path.startswith(p[0]): + path = f"{d}:/{path[len(p[0]):]}" + break + self.write(path) + + else: + self.write_buf.write(fmt) + + else: + self.write(struct.pack(f"<{fmt}", *args)) + + def send(self): + self.write(b"\x00" * (self.CommandBlockLength - self.write_buf.tell())) + self.write_raw(self.write_buf.getbuffer()) + + def read_raw(self, size_or_buffer, timeout=3000): + return self.ep[1].read(size_or_buffer, timeout=timeout).tobytes() + + def write_raw(self, data, timeout=3000): + self.ep[0].write(data, timeout=timeout) + + def add_drive(self, drive, path, label=None): + if label is None: + label = drive + self.drives[drive] = (path, label) + + def get_drive(self, idx): + return list(self.drives.items())[0] + +def make_result(module, description): + return ((((module)&0x1FF)) | ((description)&0x1FFF)<<9) + +class CommandId(Enum): + GetDriveCount = 0 + GetDriveInfo = 1 + StatPath = 2 + GetFileCount = 3 + GetFile = 4 + GetDirectoryCount = 5 + GetDirectory = 6 + ReadFile = 7 + WriteFile = 8 + Create = 9 + Delete = 10 + Rename = 11 + GetSpecialPathCount = 12 + GetSpecialPath = 13 + SelectFile = 14 + Max = 15 class Command: - GLUC=b"GLUC" - def __init__(self,cmd_id=0,raw=None): - if raw is None: - self.cmd_id=cmd_id - self.magic=self.GLUC + InputMagic = b"GLCI" + OutputMagic = b"GLCO" + + ResultModule = 356 # Goldleaf result module + + ResultSuccess = 0 + ResultInvalidInput = make_result(ResultModule, 101) + + handler = USBHandler() + + def __init__(self, cmd_id=CommandId.GetDriveCount, out=True): + if out: + self.cmd_id = cmd_id + self.magic = self.OutputMagic else: - self.magic=raw[:4] - self.cmd_id=struct.unpack("= len(c.handler.drives): + c.write_base(Command.ResultInvalidInput) + + else: + info = c.handler.get_drive(drive_idx) + + c.write_base() + + c.write(info[1][1]) # Label + c.write(info[0]) # Prefix + + #usage = shutil.disk_usage(info[1][0]) + #c.write("II", usage.free & 0xFFFFFFFF, usage.total & 0xFFFFFFFF) # Not used by Goldleaf but still sent + + c.write("II", 0, 0) # Stubbed free/total space (not used by Goldleaf) + + elif c.has_id(CommandId.StatPath): + path = c.read(Path) + type = 0 + file_size = 0 + + if path.is_file(): + type = 1 + file_size = path.stat().st_size + elif path.is_dir(): + type = 2 + else: + c.write_base(Command.ResultInvalidInput) + c.send() + continue + + c.write_base() + c.write("IQ", type, file_size) + + elif c.has_id(CommandId.GetFileCount): + path = c.read(Path) + + if path.is_dir(): + files = [x for x in path.glob("*") if x.is_file()] + c.write_base() + c.write("I", len(files)) + + else: + c.write_base(Command.ResultInvalidInput) + + elif c.has_id(CommandId.GetFile): + path = c.read(Path) + file_idx = c.read("I") + + if path.is_dir(): + files = [x for x in path.glob("*") if x.is_file()] + + if file_idx >= len(files): + c.write_base(Command.ResultInvalidInput) + + else: + c.write_base() + c.write(files[file_idx].name) + + else: + c.write_base(Command.ResultInvalidInput) + + elif c.has_id(CommandId.GetDirectoryCount): + path = c.read(Path) + + if path.is_dir(): + dirs = [x for x in path.glob("*") if x.is_dir()] + c.write_base() + c.write("I", len(dirs)) + + else: + c.write_base(Command.ResultInvalidInput) + + elif c.has_id(CommandId.GetDirectory): + path = c.read(Path) + dir_idx = c.read("I") + + if path.is_dir(): + dirs = [x for x in path.glob("*") if x.is_dir()] + + if dir_idx >= len(dirs): + c.write_base(Command.ResultInvalidInput) + + else: + c.write_base() + c.write(dirs[dir_idx].name) + + else: + c.write_base(Command.ResultInvalidInput) + + elif c.has_id(CommandId.ReadFile): + path = c.read(Path) + offset, size = c.read("QQ") + + try: + with path.open("rb") as f: + f.seek(offset) + bufs.append(f.read(size)) + + c.write_base() + c.write("Q", size) + + except: + c.write_base(Command.ResultInvalidInput) + + elif c.has_id(CommandId.WriteFile): + path = c.read(Path) + size = c.read("Q") + data = c.handler.read_raw(size) + print(path, data) + + try: + with path.open("wb") as f: + f.write(data) + + c.write_base() + + except: + c.write_base(Command.ResultInvalidInput) + + elif c.has_id(CommandId.Create): + type = c.read("I") + path = c.read(Path) + + if type == 1: + try: + path.touch() + c.write_base() + + except: + c.write_base(Command.ResultInvalidInput) + + elif type == 2: try: - c=Command.read() - break - except usb.core.USBError: - pass - if c.magic_ok(): - if c.has_id(CommandId.Start): - print("Goldleaf is ready for the installation. Preparing everything...") - pnsp=PFS0(sys.argv[1]) - c=Command(CommandId.NSPData) - c.write() - write(struct.pack("= len(special_paths): + c.write_base(Command.ResultInvalidInput) + else: - print(invalid_cmd) - return 1 - elif c.has_id(CommandId.Finish): - print(install_cancelled) - else: - print(invalid_cmd) - return 1 - else: - print(invalid_cmd) - return 1 - print("The installation has finished.") - #c=Command(CommandId.Finish) - #write(bytes(c)) + info = list(special_paths.items())[spath_idx] + + c.write_base() + c.write(info[0]) + c.write(info[1]) + + elif c.has_id(CommandId.SelectFile): # Never used + try: + path = Path(input("Select file for Goldleaf: ")) + c.write_base() + c.write(path) + + except: + c.write_base(Command.ResultInvalidInput) + + c.send() + + for buf in bufs: + c.handler.write_raw(buf) + return 0 -if __name__=="__main__": +if __name__ == "__main__": sys.exit(main()) - diff --git a/PFS0.py b/PFS0.py deleted file mode 100644 index 7dbf986..0000000 --- a/PFS0.py +++ /dev/null @@ -1,38 +0,0 @@ -import struct - -class PFS0: - class FileEntry: - def __init__(self,data): - self.file_offset=struct.unpack("0: - tor=min(chunk_size,to_read) - yield self.read_raw(cur_offset,tor) - cur_offset+=tor - to_read-=tor diff --git a/README.md b/README.md index cfbc389..30aa260 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,6 @@ A python port of XorTroll's [Goldtree](https://github.com/XorTroll/Goldleaf/tree/master/Goldtree) -To use, open **USB Installation** in Goldleaf, then do `sudo ./Goldtree.py [nsp file]` on your computer. It requires [pyusb](https://github.com/pyusb/pyusb). +To use, open Goldleaf, do `sudo ./Goldtree.py [...]` (`sudo` isn't required if you use udev rules), and then open **Explore content -> Remote PC (via USB)** in Goldleaf. The arguments will show up as environment paths in Goldleaf so that you don't have to navigate to the folders/files from the root of your computer. + +To install all the dependencies, do `pip3 install -r requirements.txt`. \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..af159f1 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +pyusb \ No newline at end of file