Exemple #1
0
 def wrapped(self: MemoryHook, *args, **kwargs):
     try:
         with PACKAGE_PATH("__address_cache__").open("rb") as f:
             self._address_cache = pickle.load(f)
     except (FileNotFoundError, EOFError, ValueError):
         self._address_cache = {}
     result = func(self, *args, **kwargs)
     with PACKAGE_PATH("__address_cache__").open("wb") as f:
         pickle.dump(self._address_cache, f)
     return result
Exemple #2
0
def _temp_lua_path(content, as_bytes=False, encoding=None, set_cwd=False):
    temp = PACKAGE_PATH("base/ai/lua/temp")
    if set_cwd:
        previous_cwd = os.getcwd()
        os.chdir(str(temp.parent))
    else:
        previous_cwd = None
    with temp.open(mode="wb" if as_bytes else "w", encoding=encoding) as f:
        f.write(content)
    yield temp
    try:
        os.remove(str(PACKAGE_PATH("base/ai/lua/temp")))
    except FileNotFoundError:
        pass
    if previous_cwd:
        os.chdir(previous_cwd)
Exemple #3
0
    def __init__(self, paramdef_bnd_source=None):
        """BND container with all the `ParamDef` definitions for a given game.

        The latest versions of these files are included with Soulstruct for some games, and can be loaded simply by
        passing the game name to this constructor. They will also be loaded automatically when needed by `Param`
        instances.

        If you want to modify a `ParamDefBND`, you are far too powerful a modder for Soulstruct, and I cannot make that
        journey with you at this time.
        """
        if paramdef_bnd_source is None:
            paramdef_bnd_source = self.GAME
        elif isinstance(paramdef_bnd_source, str):
            try:
                paramdef_bnd_source = get_game(paramdef_bnd_source)
            except ValueError:
                # Will try to use as a path.
                pass
        if isinstance(paramdef_bnd_source, Game):
            # Load vanilla ParamDefBND bundled with Soulstruct (easiest).
            if not paramdef_bnd_source.bundled_paramdef_path:
                raise NotImplementedError(
                    f"Soulstruct does not have a bundled `paramdefbnd` file for game {paramdef_bnd_source.name}."
                )
            paramdef_bnd_source = PACKAGE_PATH(
                paramdef_bnd_source.bundled_paramdef_path)
            if not paramdef_bnd_source.is_file():
                raise FileNotFoundError(
                    f"Could not find bundled `paramdefbnd` file for game {paramdef_bnd_source.name} in Soulstruct.\n"
                    "Update/reinstall Soulstruct or copy the ParamDef files in yourself."
                )

        self._bnd = Binder(paramdef_bnd_source)

        self.paramdefs = {}  # type: dict[str, ParamDef]
        for entry in self.bnd.entries:
            try:
                paramdef = self.PARAMDEF_CLASS(entry)
            except Exception:
                _LOGGER.error(
                    f"Could not load ParamDefBND entry {entry.name}.")
                raise
            if paramdef.param_type in self.paramdefs:
                raise KeyError(
                    f"ParamDef type {paramdef.param_type} was loaded more than once in ParamDefBND."
                )
            self.paramdefs[paramdef.param_type] = paramdef
Exemple #4
0
    def __init__(self, paramdef_bnd_source=None):
        """Elden Ring does not come with ParamDef files, so this "BND" is actually a dictionary of `ParamDef`s loaded
        from the XMl files generously provided in Paramdex.

        Get Paramdex here and set its root path (the one containing the game subfolders) in `soulstruct_config.json`:
            https://github.com/soulsmods/Paramdex/

        You can also use the Paramdex fork extended for Yapped (Rune Bear), which contains better English descriptions.
        """
        if paramdef_bnd_source is None:
            # Default to PARAMDEX_PATH.
            if not PARAMDEX_PATH:
                paramdef_bnd_source = PACKAGE_PATH("Paramdex/ER/Defs")
            else:
                paramdef_bnd_source = Path(PARAMDEX_PATH, "ER/Defs")
            if not paramdef_bnd_source.is_dir():
                raise FileNotFoundError(
                    f"Could not find path '{paramdef_bnd_source}' for loading Elden Ring XML paramdefs.\n"
                    f"This path defaults to the Soulstruct root. Change it in `soulstruct_config.json` if needed."
                )
        elif not isinstance(paramdef_bnd_source, (str, Path)):
            raise TypeError(
                f"`paramdef_bnd_source` for Elden Ring `ParamDefBND` must be the path to a folder of Paramdex files."
            )
        elif not Path(paramdef_bnd_source).is_dir():
            raise FileNotFoundError(
                f"Could not find '{paramdef_bnd_source}' for loading Elden Ring XML paramdefs."
            )

        paramdef_bnd_source = Path(paramdef_bnd_source)
        self.paramdefs = {}  # type: dict[str, ParamDef]

        # Iterate over all XML files.
        for xml_path in Path(paramdef_bnd_source).glob("*.xml"):
            try:
                paramdef = self.PARAMDEF_CLASS(xml_path)
            except Exception:
                _LOGGER.error(
                    f"Could not parse Paramdex XML file '{xml_path}'.")
                raise
            if paramdef.param_type in self.paramdefs:
                raise KeyError(
                    f"ParamDef type {paramdef.param_type} was loaded more than once from Paramdex."
                )
            self.paramdefs[paramdef.param_type] = paramdef
Exemple #5
0
 def copy_events_submodule(self):
     """Also need PTDE submodule for DSR."""
     super().copy_events_submodule()
     name = "darksouls1ptde"
     events_submodule = PACKAGE_PATH(f"{name}/events")
     (self.project_root / f"events/soulstruct/{name}").mkdir(parents=True,
                                                             exist_ok=True)
     shutil.copytree(events_submodule,
                     self.project_root / f"events/soulstruct/{name}/events")
     _LOGGER.info(
         f"Copied `soulstruct.{name}.events` submodule into project events folder (needed for DSR)."
     )
Exemple #6
0
def bloodborne():
    from soulstruct.bloodborne.events.emevd import compiler
    from soulstruct.bloodborne.events.emevd.emedf import EMEDF, EMEDF_ALIASES, EMEDF_TESTS, EMEDF_COMPARISON_TESTS
    generate_instr_pyi(
        "bloodborne",
        EMEDF,
        EMEDF_ALIASES,
        EMEDF_TESTS,
        EMEDF_COMPARISON_TESTS,
        15,
        PACKAGE_PATH("bloodborne/events/instructions.pyi"),
        compiler,
    )
Exemple #7
0
def darksouls1r():
    from soulstruct.darksouls1r.events.emevd import compiler
    from soulstruct.darksouls1r.events.emevd.emedf import EMEDF, EMEDF_ALIASES, EMEDF_TESTS, EMEDF_COMPARISON_TESTS
    generate_instr_pyi(
        "darksouls1r",
        EMEDF,
        EMEDF_ALIASES,
        EMEDF_TESTS,
        EMEDF_COMPARISON_TESTS,
        7,
        PACKAGE_PATH("darksouls1r/events/instructions.pyi"),
        compiler,
    )
Exemple #8
0
def eldenring():
    from soulstruct.eldenring.events.emevd import compiler
    from soulstruct.eldenring.events.emevd.emedf import EMEDF, EMEDF_ALIASES, EMEDF_TESTS, EMEDF_COMPARISON_TESTS
    generate_instr_pyi(
        "eldenring",
        EMEDF,
        EMEDF_ALIASES,
        EMEDF_TESTS,
        EMEDF_COMPARISON_TESTS,
        15,
        PACKAGE_PATH("eldenring/events/instructions.pyi"),
        compiler,
        has_event_layers=True,
    )
Exemple #9
0
def convert_dds_file(dds_path: tp.Union[str, Path],
                     output_dir: tp.Union[str, Path],
                     output_format: str,
                     input_format: str = None):
    """Convert DDS file path to a different format using DirectX-powered `texconv.exe`.

    If `input_format` is given, the source DDS will be checked to make sure it matches that format.
    """
    texconv_path = PACKAGE_PATH("base/textures/texconv.exe")
    if not texconv_path.is_file():
        raise FileNotFoundError(
            "Cannot find `texconv.exe` that should be bundled with Soulstruct in 'base/textures'."
        )
    if input_format is not None:
        dds = DDS(dds_path)
        if dds.header.fourcc.decode() != input_format:
            raise ValueError(
                f"DDS format {dds.header.fourcc} does not match `input_format` {input_format}"
            )
    subprocess.run(
        f"{texconv_path} -f {output_format} -o {output_dir} {dds_path}",
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    )
Exemple #10
0
 def restore_game_backup(self, backup_path=None):
     """Restore game files from the given folder, which defaults to 'soulstruct-backup' in your game directory.
     Create these backup files with `create_game_backup(backup_dir)`."""
     if backup_path is None:
         backup_path = self.game_root / "soulstruct-backup"
     with PACKAGE_PATH("project/files.json").open("rb") as f:
         game_files = json.load(f)[self.GAME.name]
     for file_path_parts in traverse_path_tree(game_files):
         backup_file_path = backup_path / Path(*file_path_parts)
         if not backup_file_path.is_file():
             raise FileNotFoundError(
                 f"Could not find backup file to restore: "
                 f"{str(backup_file_path)}")
         game_file_path = Path(self.game_root, *file_path_parts)
         game_file_path.parent.mkdir(parents=True, exist_ok=True)
         shutil.copy2(str(backup_file_path), str(game_file_path))
Exemple #11
0
 def create_game_backup(self, backup_path=None):
     """Copy existing game files (that can be edited with Soulstruct) to a backup directory, which defaults to
     'soulstruct-backup' in your game directory. Use `restore_game_backup(backup_dir)` to restore those files."""
     if backup_path is None:
         backup_path = self.game_root / "soulstruct-backup"
     else:
         backup_path = Path(backup_path)
     with PACKAGE_PATH("project/files.json").open("r") as f:
         game_files = json.load(f)[self.GAME.name]
     for file_path_parts in traverse_path_tree(game_files):
         game_file_path = self.game_root / Path(*file_path_parts)
         if not game_file_path.is_file():
             raise FileNotFoundError(
                 f"Could not find file in game directory to back up: "
                 f"{str(game_file_path)}")
         backup_file_path = Path(backup_path, *file_path_parts)
         backup_file_path.parent.mkdir(parents=True, exist_ok=True)
         shutil.copy2(str(game_file_path), str(backup_file_path))
Exemple #12
0
def fsbext(fsb_path: Path | str, options=""):
    """Call `fsbext.exe` with given options on `fsb_path`."""
    executable = PACKAGE_PATH("darksouls1r/sound/fsbext.exe")
    if not executable.is_file():
        raise FileNotFoundError("`fsbext.exe` is missing from Soulstruct package. Cannot extract FSB file.")
    sp.call(f"{executable} {options} {fsb_path}")
Exemple #13
0
    __DLL_GetCompressedBufferSizeNeeded.restype = c.c_long
    __DLL_GetCompressedBufferSizeNeeded.argtypes = (
        c.c_long,  # rawSize
    )

    __DLL_GetDecodeBufferSize = __DLL["OodleLZ_GetDecodeBufferSize"]
    __DLL_GetDecodeBufferSize.restype = c.c_long
    __DLL_GetDecodeBufferSize.argtypes = (
        c.c_long,  # rawSize
        c.c_bool,  # corruptionPossible
    )


# Try to load DLL automatically from Soulstruct, Sekiro, or Elden Ring path.
_auto_oodle_locations = (
    PACKAGE_PATH(__DLL_NAME),
    PACKAGE_PATH("..", __DLL_NAME),
    Path(SEKIRO_PATH, __DLL_NAME),
    Path(ELDEN_RING_PATH, __DLL_NAME),
)
for _location in _auto_oodle_locations:
    if _location.exists():
        LOAD_DLL(str(_location))
        break
else:
    # DLL not found in one of these default locations.
    print(
        f"{YELLOW}# WARNING: Could not find `oo2core_6_win64.dll` in Soulstruct, Sekiro, or Elden Ring paths.\n"
        f"# You will not be able to compress/decompress Sekiro or Elden Ring files using Soulstruct.\n"
        f"# Call `oodle.LOAD_DLL(path)` to load the DLL from an arbitrary path.{RESET}"
    )
Exemple #14
0
        "docstring":
        "Special instruction added by Meowmaritus for stopping [redacted] in Nightfall.",
        "args": {},
        "arg_types": "",
    },
    (2009, 12): {
        "alias": "NightfallCameraResetRequest",
        "docstring":
        "Special instruction added by Meowmaritus for immediate camera manipulation in Nightfall.",
        "args": {},
        "arg_types": "",
    },
}

add_common_emedf_info(
    EMEDF, PACKAGE_PATH("darksouls1ptde/events/emevd/ds1-common.emedf.json"))
EMEDF_ALIASES, EMEDF_TESTS, EMEDF_COMPARISON_TESTS = build_emedf_aliases_tests(
    EMEDF)

# Extra tests that use custom instructions from `compiler`.
EMEDF_TESTS |= {
    "ActionButton": {
        "if": "IfActionButton",
    },
    "PlayerHasWeapon": {
        "if": "IfPlayerHasWeapon",
    },
    "PlayerHasArmor": {
        "if": "IfPlayerHasArmor",
    },
    "PlayerHasRing": {
Exemple #15
0
from contextlib import contextmanager
from copy import deepcopy
from pathlib import Path

from soulstruct.containers import Binder
from soulstruct.containers.entry import BinderEntry
from soulstruct.base.game_file import GameFile, InvalidGameFileTypeError
from soulstruct.utilities.files import PACKAGE_PATH
from soulstruct.utilities.misc import get_startupinfo
from soulstruct.utilities.binary import BinaryStruct, BinaryReader, BinaryWriter

from .exceptions import LuaError, LuaCompileError, LuaDecompileError

_LOGGER = logging.getLogger(__name__)

COMPILER_x64 = str(PACKAGE_PATH("base/ai/lua/x64/LuaC.exe"))
COMPILER_x86 = str(PACKAGE_PATH("base/ai/lua/x86/luac50.exe"))
DECOMPILER_x64 = str(PACKAGE_PATH("base/ai/lua/x64/DSLuaDecompiler.exe"))
# No x86 decompiler.

_SNAKE_CASE_RE = re.compile(r"(?<!^)(?=[A-Z])")
_GOAL_SCRIPT_RE = re.compile(r"^(\d{6})_(battle|logic)\.lua$")
_LUA_SCRIPT_RE = re.compile(r"(.*)\.lua$")


class LuaBND:
    """Automatically loads all scripts, LuaInfo, and LuaGNL objects."""
    def __init__(self,
                 luabnd_path,
                 sort_goals=True,
                 require_luainfo=False,
Exemple #16
0
    def offer_entities_export(self, with_window: ProjectWindow = None):
        """Offer to export all entities modules."""
        # TODO: Offer to automatically set MSB entity ID sync with modules.
        if with_window:
            result = with_window.CustomDialog(
                title="Entities Export",
                message=
                "Would you also like to export all 'entities' Python modules for EVS use?\n",
                # TODO: Third option for automatic sync.
                button_names=("Yes, export them", "No, don't write them"),
                button_kwargs=("YES", "NO"),
                cancel_output=1,
                default_output=1,
            )
        else:
            result = 1 if (input(
                "Would you also like to export all 'entities' Python modules for EVS use? [y]/n"
            ).lower() == "n") else 0
        if result == 0:

            if with_window:
                write_vanilla_entities_result = with_window.CustomDialog(
                    title="Entities Export",
                    message=
                    "Would you also like to include pre-identified game IDs in the 'entities' module?\n",
                    button_names=("Yes, include them",
                                  "No, just write entities in entities"),
                    button_kwargs=("YES", "NO"),
                    cancel_output=1,
                    default_output=1,
                )
            else:
                write_vanilla_entities_result = 1 if (input(
                    "Would you also like to include pre-identified game IDs in the 'entities' module?\n",
                ).lower() == "n") else 0

            # NOTE: Vanilla entities are read from PTDE directory.

            if write_vanilla_entities_result == 0:
                # Write vanilla `common` entities, if it exists.
                try:
                    vanilla_common_entities = PACKAGE_PATH(
                        f"darksouls1ptde/events/vanilla_entities/common_entities.py"
                    ).read_text()
                except FileNotFoundError:
                    _LOGGER.warning(
                        "Could not find `common_entities.py` in Soulstruct for this game. Ignoring."
                    )
                    pass
                else:
                    _LOGGER.info(
                        "Writing `common_entities.py` to project from Soulstruct."
                    )
                    common_entities_path = self.project_root / f"entities/common_entities.py"
                    common_entities_path.parent.mkdir(parents=True,
                                                      exist_ok=True)
                    common_entities_path.write_text(vanilla_common_entities)
                    # `EMEVDDirectory.to_evs()` will automatically add non-star imports from `common` if it exists.

            for map_name, msb in self.maps.msbs.items():
                game_map = self.maps.GET_MAP(map_name)

                if write_vanilla_entities_result == 0:
                    try:
                        vanilla_module = PACKAGE_PATH(
                            f"darksouls1ptde/events/vanilla_entities/{game_map.emevd_file_stem}_entities.py"
                        ).read_text()
                    except FileNotFoundError:
                        vanilla_module = ""
                else:
                    vanilla_module = ""

                msb.write_entities_module(
                    self.project_root /
                    f"entities/{game_map.emevd_file_stem}_entities.py",
                    append_to_module=vanilla_module,
                )
            return True
        else:
            return False
Exemple #17
0
    default_game_path=DESR_PATH,
)


class DemonsSoulsRemakeType(GameSpecificType):
    GAME = DEMONS_SOULS_REMAKE


DARK_SOULS_PTDE = Game(
    "DARK_SOULS_PTDE",
    "Dark Souls Prepare to Die Edition",
    subpackage_name="darksouls1ptde",
    aliases=("darksoulspreparetodieedition", "darksoulsptde", "ptde",
             "darksouls1ptde"),
    uses_dcx=False,
    bundled_paramdef_path=PACKAGE_PATH(
        "darksouls1ptde/params/resources/darksouls1ptde.paramdefbnd"),
    steam_appid=211420,
    default_game_path=PTDE_PATH,
    generic_game_path=
    "C:/Program Files (x86)/Steam/steamapps/common/Dark Souls Prepare to Die Edition/DATA",
    save_file_path=Path("~/Documents/NBGI/DarkSouls").expanduser(),
    executable_name="DARKSOULS.exe",
    gadget_name="DS Gadget.exe",
    default_file_paths={
        "AIDirectory": "script",
        "DrawParamDirectory": "param/DrawParam",
        "EMEVDDirectory": "event",
        "GameParamBND": "param/GameParam/GameParam.parambnd",
        "MapStudioDirectory": "map/MapStudio",
        "MSGDirectory": "msg/ENGLISH",
        "ParamDefBND": "paramdef/paramdef.paramdefbnd",
Exemple #18
0
                        new_module += f"        \"args\": {{}},\n"
                    else:
                        new_module += f"        \"args\": {{\n"
                        for arg_dict in arg_dicts:
                            new_module += f"            \"{arg_dict['name']}\": {{\n"
                            if arg_dict['type'] is None:
                                new_module += f"                \"type\": {arg_dict['type']},  # TODO\n"
                            else:
                                new_module += f"                \"type\": {arg_dict['type']},\n"
                            new_module += f"                \"default\": {repr(arg_dict['default'])},\n"
                            if "internal_default" in arg_dict and arg_dict[
                                    "internal_default"] != 0:
                                new_module += f"{' ' * 16}\"internal_default\": {repr(arg_dict['internal_default'])},\n"
                            new_module += f"            }},\n"
                        new_module += f"        }},\n"
                    new_module += "    },\n"
                    break
            else:
                # new_module += f"    # TODO: Could not find {category_index}: {instruction_class['name']}\n"
                pass

    new_module += "}\n"
    print(new_module)


if __name__ == '__main__':
    _generate(
        PACKAGE_PATH("eldenring/events/emevd/instructions.py"),
        PACKAGE_PATH("eldenring/events/emevd/er-common.emedf.json"),
    )
Exemple #19
0
    args_ = []
    type_hints_ = []
    for arg_hint in args_with_type_hints:
        if not arg_hint:
            continue
        if ":" in arg_hint:
            arg, type_hint = [a.strip() for a in arg_hint.split(":")]
            args_.append(arg)
            type_hints_.append(type_hint)
        else:
            args_.append(arg_hint)
            type_hints_.append(None)
    return name_, tuple(args_), tuple(type_hints_)


_stubs_path = PACKAGE_PATH("base/ezstate/esd/functions.pyi")
with open(_stubs_path) as f:
    _stub_lines = f.readlines()
i = 0
while i < len(_stub_lines) - 1:
    command_match = re.match(_COMMAND_RE, _stub_lines[i])
    if command_match is not None:
        esd_type, bank, f_id = command_match.group(1, 2, 3)
        i += 1
        COMMANDS[ESDType(esd_type)].setdefault(
            int(bank), {})[int(f_id)] = _parse_function_def(i, _stub_lines[i])
        i += 1
        continue
    test_match = re.match(_TEST_RE, _stub_lines[i])
    if test_match is not None:
        esd_type, f_id = test_match.group(1, 2)
                name = match.group(2)
                minimum_str = match.group(3)
                increment_str = match.group(4)
                maximum_str = match.group(5)
                default_str = match.group(6)
                arg = {
                    "name": name,
                    "type": arg_types.index(arg_type_str),
                    "enum_name": None,
                    "default": int_or_float(default_str),
                    "min": int_or_float(minimum_str),
                    "max": int_or_float(maximum_str),
                    "increment": int_or_float(increment_str),
                    "format_string": "%0.3f"
                    if arg_type_str == "f" else "%d",  # TODO: handle "s"
                    "unk1": 0,
                    "unk2": 0,
                    "unk3": 0,
                    "unk4": 0,
                }
                instruction["args"].append(arg)

    write_json(json_path, emedf_json, indent=2)


if __name__ == '__main__':
    emedf_txt_to_json(
        r"C:\Users\Scott\Downloads\er-emedf-common-partial.txt",
        PACKAGE_PATH("eldenring/events/emevd/er-common.emedf.json"),
    )