Skip to content

Commit

Permalink
add legacy compat for arcania
Browse files Browse the repository at this point in the history
  • Loading branch information
apple1417 committed Jan 5, 2025
1 parent 6812b1b commit 0ad88cd
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 9 deletions.
3 changes: 3 additions & 0 deletions text_mod_loader/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ Text Mod Loader supports the following tags.

# Changelog

## Text Mod Loader v4
- Added legacy compat for Arcania, which it turns out did actually use `add_custom_mod_path`.

## Text Mod Loader v3
- Fixed that the settings file would corrupt every other launch, losing your auto enable settings.

Expand Down
4 changes: 4 additions & 0 deletions text_mod_loader/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@

from typing import Any

from legacy_compat import add_compat_module
from mods_base import ButtonOption, Library, build_mod, hook

from . import legacy_compat as tml_legacy_compat
from .loader import all_text_mods, load_all_text_mods
from .settings import (
all_settings,
Expand Down Expand Up @@ -46,3 +48,5 @@ def reload(_: ButtonOption) -> None:
)
sanitize_settings()
load_all_text_mods()

add_compat_module("Mods.TextModLoader", tml_legacy_compat)
73 changes: 73 additions & 0 deletions text_mod_loader/legacy_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from collections.abc import Callable
from pathlib import Path
from typing import Any

from legacy_compat import legacy_compat
from mods_base import hook, register_mod
from unrealsdk.hooks import Block
from unrealsdk.unreal import BoundFunction, UFunction, UObject, WrappedStruct

from .anti_circular_import import all_text_mods
from .loader import load_mod_info
from .settings import get_cached_mod_info, update_cached_mod_info
from .text_mod import TextMod as NewTextMod

# The old TML Python interface was a very leaky abstraction. Our internals don't really match up
# with it anymore.
# Luckily, it seems there's only actually one mod which used it, Arcania. We can just create a fake
# interface to catch it specifically.

__all__: tuple[str, ...] = (
"TextMod",
"add_custom_mod_path",
)


class TextMod:
Name: str
Author: str
Description: str
Version: str

onLevelTransition: Callable[[UObject, UFunction, WrappedStruct], bool] # noqa: N815


def add_custom_mod_path(filename: str, cls: type[TextMod] = TextMod) -> None: # noqa: D103
if not ((path := Path(filename)).name == "Arcania.blcm" and cls.__name__ == "Arcania"):
raise RuntimeError(f"Text Mod Loader legacy compat not implemented for {path.name}")

if (mod_info := get_cached_mod_info(path)) is None:
mod_info = load_mod_info(path)

mod_info["title"] = cls.Name
mod_info["author"] = cls.Author
mod_info["description"] = cls.Description
mod_info["version"] = cls.Version

update_cached_mod_info(path, mod_info)

@hook("Engine.GameInfo:PostCommitMapChange")
def on_level_transition(
obj: UObject,
args: WrappedStruct,
_3: Any,
func: BoundFunction,
) -> type[Block] | None:
with legacy_compat():
ret = cls.onLevelTransition(obj, func.func, args)
return Block if ret else None

mod = NewTextMod(
name=mod_info["title"],
author=mod_info["author"],
version=mod_info["version"],
file=path,
spark_service_idx=mod_info["spark_service_idx"],
recommended_game=mod_info["recommended_game"],
internal_description=mod_info["description"],
prevent_reloading=True,
hooks=(on_level_transition,),
)

all_text_mods[path] = mod
register_mod(mod)
3 changes: 3 additions & 0 deletions text_mod_loader/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ def load_all_text_mods() -> None:
for mod in list(all_text_mods.values()):
mod.check_deleted()

if mod.prevent_reloading:
continue

match mod.state:
# Delete what mods we can
case (
Expand Down
2 changes: 1 addition & 1 deletion text_mod_loader/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "text_mod_loader"
version = "3"
version = "4"
authors = [{ name = "apple1417" }]
description = """\
Displays Text Mods from binaries in the SDK mods menu.
Expand Down
20 changes: 12 additions & 8 deletions text_mod_loader/text_mod.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
from __future__ import annotations

import os
import sys
from dataclasses import KW_ONLY, dataclass
from typing import TYPE_CHECKING, Literal
from pathlib import Path
from typing import Literal

from mods_base import Game, Mod, get_pc

from .anti_circular_import import TextModState

if TYPE_CHECKING:
from pathlib import Path

from ui_utils import TrainingBox

from .anti_circular_import import TextModState
from .hotfixes import any_hotfix_used, is_hotfix_service
from .settings import change_mod_auto_enable

BINARIES_DIR = Path(sys.executable).parent.parent


@dataclass
class TextMod(Mod):
Expand All @@ -24,6 +24,7 @@ class TextMod(Mod):

file: Path
state: TextModState = TextModState.Disabled
prevent_reloading: bool = False

spark_service_idx: int | None
recommended_game: Game | None
Expand Down Expand Up @@ -127,7 +128,10 @@ def enable(self) -> None: # noqa: D102
return

case TextModState.Disabled:
get_pc().ConsoleCommand(f'exec "{self.file}"')
# Path.relative_to requires one path be a subpath of the other, it won't prefix
# `../`s if we're executing something in a parent dir of binaries
get_pc().ConsoleCommand(f'exec "{os.path.relpath(self.file, BINARIES_DIR)}"')

self.state = TextModState.Enabled
change_mod_auto_enable(self, True)
case TextModState.DisableOnRestart:
Expand Down

0 comments on commit 0ad88cd

Please sign in to comment.