Skip to content

Instantly share code, notes, and snippets.

@Frixuu
Last active March 26, 2024 01:11
Show Gist options
  • Save Frixuu/c2d0896356437d493842c3e2e6f4cf20 to your computer and use it in GitHub Desktop.
Save Frixuu/c2d0896356437d493842c3e2e6f4cf20 to your computer and use it in GitHub Desktop.
How to make Ren'Py 8 load raw Python files from .rpa archives
import renpy.loader
from importlib.machinery import ModuleSpec
from types import ModuleType
from typing import Any, List, Optional, Union
class DummyLoader:
def __init__(self) -> None:
pass
def create_module(self, spec: ModuleSpec) -> None:
return None
def exec_module(self, module: ModuleType) -> None:
pass
class PythonFromRpaLoader:
archive_path: str
inner_path: str
def __init__(self, archive_path: str, inner_path: str) -> None:
self.archive_path = archive_path
self.inner_path = inner_path
def create_module(self, spec: ModuleSpec) -> None:
return None
def exec_module(self, module: ModuleType) -> None:
reader = renpy.loader.load_from_archive(self.inner_path) # type: ignore
if reader is None:
raise ModuleNotFoundError(path=self.archive_path)
module.__file__ = self.archive_path
with reader:
exec(reader.read(), module.__dict__)
class PythonFromRpaFinder:
@classmethod
def find_spec(
cls,
fullname: str,
path: Union[List[str], Any, None],
target: Optional[Any] = None,
) -> Optional[ModuleSpec]:
archives: list[tuple[str, Any]] = renpy.loader.archives # type: ignore
inner_path_base = fullname.removeprefix("game.").replace(".", "/")
candidate = inner_path_base + "/__init__.py"
for archive_path, index in archives:
if candidate in index:
return ModuleSpec(
fullname,
PythonFromRpaLoader(archive_path, candidate), # type: ignore
is_package=True,
)
candidate = inner_path_base + ".py"
for archive_path, index in archives:
if candidate in index:
return ModuleSpec(
fullname,
PythonFromRpaLoader(archive_path, candidate), # type: ignore
is_package=False,
)
candidate = inner_path_base + "/"
for _, index in archives:
files_in_archive = list(index.keys())
if any(file_path.startswith(candidate) for file_path in files_in_archive):
return ModuleSpec(
fullname,
DummyLoader(), # type: ignore
is_package=True,
)
return None
python early:
import sys
from game.load_python import PythonFromRpaFinder
sys.meta_path.append(PythonFromRpaFinder)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment