Esempio n. 1
0
def replace_file(root_sarc: Sarc, file: str, new_data: bytes) -> Sarc:
    if file.endswith("/"):
        file = file[0:-1]
    parent = get_parent_sarc(root_sarc, file)
    filename = file.split("//")[-1]
    new_sarc: SarcWriter = SarcWriter.from_sarc(parent)
    new_sarc.files[filename] = new_data
    while root_sarc != parent:
        _, child = new_sarc.write()
        file = file[0:file.rindex("//")]
        if file.endswith("/"):
            file = file[:-1]
        parent = get_parent_sarc(root_sarc, file)
        new_sarc = SarcWriter.from_sarc(parent)
        ext = file[file.rindex("."):]
        new_sarc.files[file] = (child if
                                not (ext.startswith(".s") and ext != ".sarc")
                                else compress(child))
    return Sarc(new_sarc.write()[1])
Esempio n. 2
0
def _build_actorinfo(params: BuildParams):
    actors = []
    for actor_file in (params.out / params.content / "Actor" / "ActorInfo").glob(
        "*.info"
    ):
        actors.append(oead.byml.from_binary(actor_file.read_bytes()))
        actor_file.unlink()
    hashes = oead.byml.Array(
        [
            oead.S32(crc) if crc < 2147483648 else oead.U32(crc)
            for crc in sorted({crc32(a["name"].encode("utf8")) for a in actors})
        ]
    )
    actors.sort(key=lambda actor: crc32(actor["name"].encode("utf8")))
    actor_info = oead.byml.Hash({"Actors": oead.byml.Array(actors), "Hashes": hashes})
    info_path = params.out / params.content / "Actor" / "ActorInfo.product.sbyml"
    info_path.parent.mkdir(exist_ok=True, parents=True)
    info_path.write_bytes(
        compress(oead.byml.to_binary(actor_info, big_endian=params.be))
    )
Esempio n. 3
0
def _build_actorinfo(params: BuildParams):
    actors = []
    for actor_file in (params.mod / params.content / 'Actor' /
                       'ActorInfo').glob('*.info.yml'):
        actors.append(
            oead.byml.from_text(actor_file.read_text(encoding='utf-8')))
    hashes = oead.byml.Array([
        oead.S32(crc) if crc < 2147483648 else oead.U32(crc)
        for crc in sorted({crc32(a['name'].encode('utf8'))
                           for a in actors})
    ])
    actors.sort(key=lambda actor: crc32(actor['name'].encode('utf8')))
    actor_info = oead.byml.Hash({
        'Actors': oead.byml.Array(actors),
        'Hashes': hashes
    })
    info_path = params.out / params.content / 'Actor' / 'ActorInfo.product.sbyml'
    info_path.parent.mkdir(exist_ok=True, parents=True)
    info_path.write_bytes(
        compress(oead.byml.to_binary(actor_info, big_endian=params.be)))
Esempio n. 4
0
 def save_yaml(self, yaml: str, obj_type: str, big_endian: bool,
               path: str) -> dict:
     if not path:
         result = self.window.create_file_dialog(webview.SAVE_DIALOG)
         if result:
             path = result if isinstance(result, str) else result[0]
         else:
             return {"error": {"msg": "Cancelled", "traceback": ""}}
     try:
         data = _yaml.save_yaml(yaml, obj_type, big_endian)
         pathy_path = Path(path)
         if pathy_path.suffix.startswith(".s"):
             data = compress(data)
         if not path.startswith("SARC:"):
             pathy_path.write_bytes(data)
         else:
             self._open_sarc, _, modded = _sarc.open_sarc(
                 _sarc.add_file(self._open_sarc, path, data))
             return {"modded": modded}
     except Exception as err:  # pylint: disable=broad-except
         return {"error": {"msg": str(err), "traceback": format_exc(-5)}}
     return {}
Esempio n. 5
0
def rename_file(root_sarc: Sarc, file: str, new_name: str) -> Sarc:
    if file.endswith("/"):
        file = file[0:-1]
    if any(char in new_name for char in r"""\/:*?"'<>|"""):
        raise ValueError(f"{new_name} is not a valid file name.")
    parent = get_parent_sarc(root_sarc, file)
    filename = file.split("//")[-1]
    new_sarc: SarcWriter = SarcWriter.from_sarc(parent)
    new_sarc.files[(Path(filename).parent / new_name).as_posix()] = Bytes(
        parent.get_file(filename).data)
    del new_sarc.files[filename]
    while root_sarc != parent:
        _, child = new_sarc.write()
        file = file[0:file.rindex("//")]
        if file.endswith("/"):
            file = file[:-1]
        parent = get_parent_sarc(root_sarc, file)
        new_sarc = SarcWriter.from_sarc(parent)
        ext = file[file.rindex("."):]
        new_sarc.files[file] = (child if
                                not (ext.startswith(".s") and ext != ".sarc")
                                else compress(child))
    return Sarc(new_sarc.write()[1])
Esempio n. 6
0
def build_mod(args):
    mod = Path(args.directory)
    meta = {}
    if (mod / "config.yml").exists():
        meta = _parse_config(mod / "config.yml", args)

    content = "content" if args.be else "01007EF00011E000/romfs"
    aoc = "aoc" if args.be else "01007EF00011F001/romfs"
    if not ((mod / content).exists() or (mod / aoc).exists()):
        print(
            "The specified directory does not appear to have a valid folder structure."
        )
        print("Run `hyrule_builder build --help` for more information.")
        sys.exit(2)
    out = mod.with_name(f"{mod.name}_build") if not args.output else Path(args.output)
    if out.exists():
        print("Removing old build...")
        shutil.rmtree(out)

    params = BuildParams(
        mod=mod,
        out=out,
        be=args.be,
        guess=not args.no_guess,
        verbose=args.verbose,
        content=content,
        aoc=aoc,
        titles=set(args.title_actors.split(",")),
        table=StockHashTable(args.be),
        warn=not args.no_warn,
        strict=args.hard_warn,
    )

    print("Scanning source files...")
    files = {
        f
        for f in mod.rglob("**/*")
        if f.is_file()
        # and "ActorInfo" not in f.parts
        and not str(f.relative_to(mod)).startswith(".")
    }
    other_files = {f for f in files if f.suffix not in {".yml", ".msyt"}}
    yml_files = {f for f in files if f.suffix == ".yml"}
    f: Path
    rvs = {}
    if not args.single:
        p = Pool()

    print("Copying miscellaneous files...")
    if args.single or len(other_files) < 2:
        for f in other_files:
            rvs.update(_copy_file(f, params))
    else:
        results = p.map(partial(_copy_file, params=params), other_files)
        for r in results:
            rvs.update(r)

    if (mod / content).exists():
        msg_dirs = {
            d
            for d in mod.glob(f"{content}/Pack/Bootup_*.pack")
            if d.is_dir() and not d.name == "Bootup_Graphics.pack"
        }
        if msg_dirs:
            print("Building MSBT files...")
        for d in msg_dirs:
            msg_dir = next(d.glob("Message/*"))
            new_dir = out / msg_dir.relative_to(mod).with_suffix(".ssarc.ssarc")
            pymsyt.create(msg_dir, new_dir)

    print("Building AAMP and BYML files...")
    if args.single or len(yml_files) < 2:
        for f in yml_files:
            rvs.update(_build_yml(f, params))
    else:
        try:
            results = p.map(partial(_build_yml, params=params), yml_files)
        except RuntimeError as err:
            print(err)
            sys.exit(1)
        for r in results:
            rvs.update(r)

    main_aoc = Path(aoc) / ("0010" if params.be else "")
    if (params.out / main_aoc / "Map" / "MainField").exists():
        (params.out / main_aoc / "Pack").mkdir(parents=True, exist_ok=True)
        (params.out / main_aoc / "Pack" / "AocMainField.pack").write_bytes(b"")

    if (mod / content / "Actor" / "ActorInfo").is_dir():
        print("Building actor info...")
        _build_actorinfo(params)

    actors = {f for f in (out / content / "Actor" / "ActorLink").glob("*.bxml")}
    if actors:
        (out / content / "Actor" / "Pack").mkdir(parents=True, exist_ok=True)
        print("Building actor packs...")
        if args.single or len(actors) < 2:
            for a in actors:
                rvs.update(_build_actor(a, params))
        else:
            try:
                results = p.map(partial(_build_actor, params=params), actors)
            except RuntimeError as err:
                print(err)
                sys.exit(1)
            for r in results:
                rvs.update(r)
    for d in (out / content / "Physics").glob("*"):
        if d.stem not in ["StaticCompound", "TeraMeshRigidBody"]:
            shutil.rmtree(d)
    {  # pylint: disable=expression-not-assigned
        shutil.rmtree(d)
        for d in (out / content / "Actor").glob("*")
        if d.is_dir() and d.stem != "Pack"
    }

    print("Building SARC files...")
    dirs = {d for d in out.rglob("**/*") if d.is_dir()}
    sarc_folders = {d for d in dirs if d.suffix in SARC_EXTS and d.suffix != ".pack"}
    pack_folders = {d for d in dirs if d.suffix == ".pack"}
    if args.single or (len(sarc_folders) + len(pack_folders)) < 3:
        for d in sarc_folders:
            rvs.update(_build_sarc(d, params))
        for d in pack_folders:
            rvs.update(_build_sarc(d, params))
    else:
        sarc_func = partial(_build_sarc, params=params)
        results = p.map(sarc_func, sarc_folders)
        for r in results:
            rvs.update(r)
        results = p.map(sarc_func, pack_folders)
        for r in results:
            rvs.update(r)

    if p:
        p.close()
        p.join()

    rp = out / content / "System" / "Resource" / "ResourceSizeTable.product.json"
    if rp.exists() or rvs:
        print("Updating RSTB...")
        table: ResourceSizeTable
        if args.no_rstb:
            if rp.exists():
                table = load_rstb(args.be, file=rp)
        else:
            if rp.exists():
                table = load_rstb(args.be, file=rp)
            else:
                table = load_rstb(args.be)
                rp.parent.mkdir(parents=True, exist_ok=True)
            if rvs and not (len(rvs) == 1 and list(rvs.keys())[0] is None):
                for p, v in rvs.items():
                    if not p:
                        continue
                    msg: str = ""
                    if table.is_in_table(p):
                        if v > table.get_size(p) > 0:
                            table.set_size(p, v)
                            msg = f"Updated {p} to {v}"
                        elif v == 0:
                            table.delete_entry(p)
                            msg = f"Deleted {p}"
                        else:
                            msg = f"Skipped {p}"
                    else:
                        if v > 0 and p not in STOCK_FILES:
                            table.set_size(p, v)
                            msg = f"Added {p}, set to {v}"
                    if args.verbose and msg:
                        print(msg)
        buf = BytesIO()
        table.write(buf, args.be)
        rp.with_suffix(".srsizetable").write_bytes(compress(buf.getvalue()))
        if rp.exists():
            rp.unlink()

    if meta:
        with (out / "rules.txt").open("w", encoding="utf-8") as rules:
            rules.write("[Definition]\n")
            rules.write(
                "titleIds = 00050000101C9300,00050000101C9400,00050000101C9500\n"
            )
            for key, val in meta.items():
                rules.write(f"{key} = {val}\n")
            if "path" not in meta and "name" in meta:
                rules.write(
                    f"path = The Legend of Zelda: Breath of the Wild/Mods/{meta['name']}\n"
                )
            rules.write("version = 4\n")

    print("Mod built successfully")
Esempio n. 7
0
def _build_actor(link: Path, params: BuildParams):
    pack = oead.SarcWriter(
        endian=oead.Endianness.Big if params.be else oead.Endianness.Little)
    actor_name = link.stem
    actor = oead.aamp.ParameterIO.from_binary(link.read_bytes())
    actor_path = params.out / params.content / 'Actor'
    targets = actor.objects['LinkTarget']
    modified = False
    try:
        files = {f'Actor/ActorLink/{actor_name}.bxml': link}
        for p, name in targets.params.items():
            name = name.v
            if name == 'Dummy':
                continue
            if p.hash in LINK_MAP:
                path = LINK_MAP[p.hash].replace('*', name)
                files['Actor/' + path] = actor_path / path
            elif p == 110127898:  # ASUser
                list_path = actor_path / 'ASList' / f'{name}.baslist'
                aslist_bytes = list_path.read_bytes()
                files[f'Actor/ASList/{name}.baslist'] = list_path
                aslist = oead.aamp.ParameterIO.from_binary(aslist_bytes)
                for _, anim in aslist.lists['ASDefines'].objects.items():
                    filename = anim.params["Filename"].v
                    if filename != 'Dummy':
                        files[
                            f'Actor/AS/{filename}.bas'] = actor_path / 'AS' / f'{filename}.bas'
            elif p == 1086735552:  # AttentionUser
                list_path = actor_path / 'AttClientList' / f'{name}.batcllist'
                atcllist_bytes = list_path.read_bytes()
                files[f'Actor/AttClientList/{name}.batcllist'] = list_path
                atcllist = oead.aamp.ParameterIO.from_binary(atcllist_bytes)
                for _, atcl in atcllist.lists['AttClients'].objects.items():
                    filename = atcl.params['FileName'].v
                    if filename != 'Dummy':
                        files[
                            f'Actor/AttClient/{filename}.batcl'] = actor_path / 'AttClient' / f'{filename}.batcl'
            elif p == 4022948047:  # RgConfigListUser
                list_path = actor_path / 'RagdollConfigList' / f'{name}.brgconfiglist'
                rgconfiglist_bytes = list_path.read_bytes()
                files[
                    f'Actor/RagdollConfigList/{name}.brgconfiglist'] = list_path
                rgconfiglist = oead.aamp.ParameterIO.from_binary(
                    rgconfiglist_bytes)
                for _, impl in rgconfiglist.lists[
                        'ImpulseParamList'].objects.items():
                    filename = impl.params['FileName'].v
                    if filename != 'Dummy':
                        files[f'Actor/RagdollConfig/{filename}.brgconfig'] = actor_path / \
                            'RagdollConfig' / f'{filename}.brgconfig'
            elif p == 2366604039:  # PhysicsUser
                phys_source = params.out / params.content / 'Physics'
                phys_path = actor_path / 'Physics' / f'{name}.bphysics'
                phys_bytes = phys_path.read_bytes()
                files[f'Actor/Physics/{name}.bphysics'] = phys_path
                phys = oead.aamp.ParameterIO.from_binary(phys_bytes)
                types = phys.lists['ParamSet'].objects[1258832850]
                if types.params['use_ragdoll'].v:
                    rg_path = str(phys.lists['ParamSet'].objects['Ragdoll'].
                                  params['ragdoll_setup_file_path'].v)
                    files[
                        f'Physics/Ragdoll/{rg_path}'] = phys_source / 'Ragdoll' / rg_path
                if types.params['use_support_bone'].v:
                    sb_path = str(
                        phys.lists['ParamSet'].objects['SupportBone'].
                        params['support_bone_setup_file_path'].v)
                    files[
                        f'Physics/SupportBone/{sb_path}'] = phys_source / 'SupportBone' / sb_path
                if types.params['use_cloth'].v:
                    cloth_path = str(
                        phys.lists['ParamSet'].lists['Cloth'].objects[
                            'ClothHeader'].params['cloth_setup_file_path'].v)
                    files[
                        f'Physics/Cloth/{cloth_path}'] = phys_source / 'Cloth' / cloth_path
                if types.params['use_rigid_body_set_num'].v > 0:
                    for _, rigid in phys.lists['ParamSet'].lists[
                            'RigidBodySet'].lists.items():
                        try:
                            rigid_path = str(rigid.objects[4288596824].
                                             params['setup_file_path'].v)
                            if (phys_path / 'RigidBody' / rigid_path).exists():
                                files[
                                    f'Physics/RigidBody/{rigid_path}'] = phys_path / 'RigidBody' / rigid_path
                        except KeyError:
                            continue
        for name, path in files.items():
            data = path.read_bytes()
            pack.files[name] = data
            if not modified and params.table.is_file_modded(
                    name.replace('.s', ''), memoryview(data), True):
                modified = True
    except FileNotFoundError as e:
        print(
            f'Failed to build actor "{actor_name}": Could not find linked file "{e.filename}".'
        )
        return {}
    _, sb = pack.write()
    dest: Path
    if actor_name in TITLE_ACTORS:
        dest = params.out / params.content / 'Pack' / 'TitleBG.pack' / 'Actor' / 'Pack' / f'{actor_name}.sbactorpack'
    else:
        dest = params.out / params.content / 'Actor' / 'Pack' / f'{actor_name}.sbactorpack'
    if not dest.parent.exists():
        dest.parent.mkdir(parents=True, exist_ok=True)
    dest.write_bytes(compress(sb))
    if modified:
        return {
            f'Actor/Pack/{actor_name}.bactorpack':
            _get_rstb_val('.sbactorpack', sb, params.guess, params.be)
        }
    return {}
Esempio n. 8
0
    def build(self):
        if not ((self.mod / self.content).exists() or
                (self.mod / self.aoc).exists()):
            print(
                "The specified directory does not appear to have a valid folder structure."
            )
            print("Run `hyrule_builder build --help` for more information.")
            sys.exit(2)
        if self.out.exists():
            print("Removing old build...")
            shutil.rmtree(self.out)
        print("Scanning source files...")
        files = {
            f
            for f in self.mod.rglob("**/*")
            if f.is_file() and "build" not in f.parts
            and not str(f.relative_to(self.mod)).startswith(".")
        }
        other_files = {f for f in files if f.suffix not in {".yml", ".msyt"}}
        yml_files = {f for f in files if f.suffix == ".yml"}
        f: Path
        rvs = {}
        if not self.single:
            p = Pool(maxtasksperchild=256)

        print("Copying miscellaneous files...")
        if self.single or len(other_files) < 2:
            for f in other_files:
                rvs.update(self._copy_file(f))
        else:
            results = p.map(self._copy_file, other_files)
            for r in results:
                rvs.update(r)

        if (self.mod / self.content).exists():
            msg_dirs = {
                d
                for d in self.mod.glob(f"{self.content}/Pack/Bootup_*.pack")
                if d.is_dir() and not d.name == "Bootup_Graphics.pack"
            }
            if msg_dirs:
                print("Building MSBT files...")
            for d in msg_dirs:
                msg_dir = next(d.glob("Message/*"))
                new_dir = self.out / msg_dir.relative_to(
                    self.mod).with_suffix(".ssarc")
                pymsyt.create(str(msg_dir), self.be, output=str(new_dir))

        print("Building AAMP and BYML files...")
        if self.single or len(yml_files) < 2:
            for f in yml_files:
                rvs.update(self._build_yml(f))
        else:
            try:
                results = p.map(self._build_yml, yml_files)
            except RuntimeError as err:
                print(err)
                sys.exit(1)
            for r in results:
                rvs.update(r)

        main_aoc = Path(self.aoc) / ("0010" if self.be else "")
        if (self.out / main_aoc / "Map" / "MainField").exists():
            (self.out / main_aoc / "Pack").mkdir(parents=True, exist_ok=True)
            (self.out / main_aoc / "Pack" /
             "AocMainField.pack").write_bytes(b"")

        if (self.mod / self.content / "Actor" / "ActorInfo").is_dir():
            print("Building actor info...")
            self._build_actorinfo()

        actors = {
            f
            for f in (self.out / self.content / "Actor" /
                      "ActorLink").glob("*.bxml")
        }
        if actors:
            (self.out / self.content / "Actor" / "Pack").mkdir(parents=True,
                                                               exist_ok=True)
            print("Building actor packs...")
            if self.single or len(actors) < 2:
                for a in actors:
                    rvs.update(self._build_actor(a))
            else:
                try:
                    results = p.map(self._build_actor, actors)
                except RuntimeError as err:
                    print(err)
                    sys.exit(1)
                for r in results:
                    rvs.update(r)
        for d in (self.out / self.content / "Physics").glob("*"):
            if d.stem not in ["StaticCompound", "TeraMeshRigidBody"]:
                shutil.rmtree(d)
        {  # pylint: disable=expression-not-assigned
            shutil.rmtree(d)
            for d in (self.out / self.content / "Actor").glob("*")
            if d.is_dir() and d.stem != "Pack"
        }

        print("Building SARC files...")
        dirs = {d for d in self.out.rglob("**/*") if d.is_dir()}
        sarc_folders = {
            d
            for d in dirs if d.suffix in SARC_EXTS and d.suffix != ".pack"
        }
        pack_folders = {d for d in dirs if d.suffix == ".pack"}
        if self.single or (len(sarc_folders) + len(pack_folders)) < 3:
            for d in sarc_folders:
                rvs.update(self._build_sarc(d))
            for d in pack_folders:
                rvs.update(self._build_sarc(d))
        else:
            results = p.map(self._build_sarc, sarc_folders)
            for r in results:
                rvs.update(r)
            results = p.map(self._build_sarc, pack_folders)
            for r in results:
                rvs.update(r)

        if p:
            p.close()
            p.join()

        rp = (self.out / self.content / "System" / "Resource" /
              "ResourceSizeTable.product.json")
        if rp.exists() or rvs:
            print("Updating RSTB...")
            table: ResourceSizeTable
            if self.no_rstb:
                if rp.exists():
                    table = self.load_rstb(file=rp)
            else:
                if rp.exists():
                    table = self.load_rstb(file=rp)
                else:
                    table = self.load_rstb()
                    rp.parent.mkdir(parents=True, exist_ok=True)
                if rvs and not (len(rvs) == 1 and list(rvs.keys())[0] is None):
                    for p, v in rvs.items():
                        if not p:
                            continue
                        msg: str = ""
                        if table.is_in_table(p):
                            if v > table.get_size(p) > 0:
                                table.set_size(p, v)
                                msg = f"Updated {p} to {v}"
                            elif v == 0:
                                table.delete_entry(p)
                                msg = f"Deleted {p}"
                            else:
                                msg = f"Skipped {p}"
                        else:
                            if v > 0 and p not in STOCK_FILES:
                                table.set_size(p, v)
                                msg = f"Added {p}, set to {v}"
                        if self.verbose and msg:
                            print(msg)
            buf = BytesIO()
            table.write(buf, self.be)
            rp.with_suffix(".srsizetable").write_bytes(compress(
                buf.getvalue()))
            if rp.exists():
                rp.unlink()

        if self.meta:
            with (self.out / "rules.txt").open("w", encoding="utf-8") as rules:
                rules.write("[Definition]\n")
                if "path" not in self.meta and "name" in self.meta:
                    self.meta[
                        "path"] = f"The Legend of Zelda: Breath of the Wild/Mods/{self.meta['name']}"
                if "titleIds" not in self.meta:
                    self.meta[
                        "titleIds"] = "00050000101C9300,00050000101C9400,00050000101C9500"
                for key, val in self.meta.items():
                    rules.write(f"{key} = {val}\n")
                rules.write("version = 4\n")
Esempio n. 9
0
def write_rstb(rstb: ResourceSizeTable, path: Path, be: bool):
    buf = BytesIO()
    rstb.write(buf, be)
    path.write_bytes(compress(buf.getvalue()))