Skip to content

refactor: integrate portablemc 5.x with in-memory loading#7

Merged
PythonChicken123 merged 1 commit into
memoryfrom
minecraft-launcher-refactor
May 10, 2026
Merged

refactor: integrate portablemc 5.x with in-memory loading#7
PythonChicken123 merged 1 commit into
memoryfrom
minecraft-launcher-refactor

Conversation

@PythonChicken123

@PythonChicken123 PythonChicken123 commented May 10, 2026

Copy link
Copy Markdown
Owner

Summary by Sourcery

Add support for loading the portablemc 5.x native extension (.pyd) directly into memory and integrate this mode into the in-process launcher flow for environments with strict executable policies.

New Features:

  • Introduce an in-memory loading path for the _portablemc.pyd native extension using pythonmemorymodule, alongside the existing vendored portablemc bootstrap.
  • Add a configurable in-memory portablemc initialization path to the in-process JVM launcher and game adapter, controllable via function parameters and environment variables.
  • Provide a reusable pyd_memory_loader utility module for generic in-memory loading of Python extension modules and their submodules.

Enhancements:

  • Improve error handling and logging around portablemc 5.x environment initialization and API resolution, including clearer diagnostics for import and API mismatches.
  • Expose helper functions to query whether the portablemc native extension is currently loaded in-memory and retrieve the module object.

Documentation:

  • Update launcher CLI help and runtime output to describe the new portablemc in-memory loading mode and its interaction with memory-resident JVM mode.

Co-authored-by: PythonChicken123 <101510622+PythonChicken123@users.noreply.github.com>
@vercel

vercel Bot commented May 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
minecraft-portable-web Ready Ready Preview, Comment, Open in v0 May 10, 2026 5:45pm

@sourcery-ai

sourcery-ai Bot commented May 10, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Integrates optional in-memory loading for the vendored portablemc 5.x native extension into the in-process launcher, adds a reusable .pyd memory loader utility, and threads configuration and logging of the chosen loading mode through the JVM boot glue and CLI entrypoints.

Sequence diagram for in-process launcher with portablemc in-memory mode

sequenceDiagram
    actor User
    participant main_py as main_py
    participant jvm_boot_glue as jvm_boot_glue_py
    participant Adapter as PortableMCGameAdapter
    participant Env as _ensure_portablemc_environment
    participant local_pmc as local_portablemc
    participant PydLoader as PydMemoryLoader

    User->>main_py: select option 4 or 5
    main_py->>main_py: run_inprocess_launcher(memory_resident, portablemc_inmemory)
    main_py->>jvm_boot_glue: launch_minecraft(..., portablemc_inmemory)
    jvm_boot_glue->>Adapter: PortableMCGameAdapter(...)
    jvm_boot_glue->>Adapter: resolve(inmemory=portablemc_inmemory)

    Adapter->>Env: _ensure_portablemc_environment(inmemory)
    alt inmemory is True
        Env->>local_pmc: bootstrap_inmemory(scripts_dir)
        local_pmc->>local_pmc: _ensure_pyd(), _scrub_pip_portablemc(), _prepend_vendor()
        local_pmc->>local_pmc: _find_pyd_file()
        local_pmc->>PydLoader: PydMemoryLoader.load()
        PydLoader->>PydLoader: map .pyd via MemoryModule
        PydLoader->>PydLoader: call PyInit__portablemc
        PydLoader-->>local_pmc: module
        local_pmc->>local_pmc: _create_portablemc_package()
        local_pmc->>local_pmc: _setup_portablemc_submodule_stubs()
        Env-->>Adapter: True, _PMC_INMEMORY_MODE=True
    else inmemory is False
        Env->>local_pmc: bootstrap(scripts_dir)
        Env-->>Adapter: True, _PMC_INMEMORY_MODE=False
    end

    Adapter->>Adapter: _via_python_api()
    Adapter-->>jvm_boot_glue: jvm_flags, classpath, main_class, game_args
    jvm_boot_glue->>jvm_boot_glue: log portablemc mode via is_portablemc_inmemory()
    jvm_boot_glue-->>main_py: launch JVM and game
    main_py-->>User: game runs in-process
Loading

Class diagram for new in-memory PYD loading utilities

classDiagram
    class PydMemoryLoader {
        -Path pyd_path
        -MemoryModule _memory_module
        -ModuleType _module
        -str _init_func_name
        -bool _loaded
        +PydMemoryLoader(pyd_path)
        +bool is_loaded
        +ModuleType module
        -str _infer_module_name()
        +bool load()
        +bool register_module(name)
        +ModuleType get_submodule(submod_name)
        +int register_submodules(parent_name, submod_names)
    }

    class pyd_memory_loader_module {
        -dict~str, PydMemoryLoader~ _LOADED_PYDS
        +ModuleType load_pyd_inmemory(pyd_path)
        +bool is_pyd_loaded(pyd_path)
        +PydMemoryLoader get_loaded_pyd(pyd_path)
    }

    class local_portablemc_module {
        -bool _PYD_LOADED_INMEMORY
        -object _PYD_MEMORY_MODULE
        +Path _find_pyd_file()
        +ModuleType _load_pyd_inmemory(pyd_path)
        +bool _create_portablemc_package()
        +_setup_portablemc_submodule_stubs()
        +bool bootstrap_inmemory(scripts_dir)
        +bool is_pyd_loaded_inmemory()
        +object get_inmemory_module()
    }

    class jvm_boot_glue_module {
        -bool _PMC_BOOTSTRAP_COMPLETE
        -bool _PMC_INMEMORY_MODE
        +bool _ensure_portablemc_environment(inmemory)
        +bool is_portablemc_inmemory()
    }

    class PortableMCGameAdapter {
        +resolve(inmemory) tuple
        -tuple _via_python_api()
        +static _extract_args(jvm_args, game_args, main_class)
    }

    class main_module {
        +run_inprocess_launcher(memory_resident, portablemc_inmemory)
        +main()
    }

    pyd_memory_loader_module --> PydMemoryLoader : manages
    local_portablemc_module --> PydMemoryLoader : uses for in-memory loading
    jvm_boot_glue_module --> local_portablemc_module : calls bootstrap_inmemory
    PortableMCGameAdapter --> jvm_boot_glue_module : calls _ensure_portablemc_environment
    main_module --> jvm_boot_glue_module : calls launch_minecraft
    main_module --> PortableMCGameAdapter : indirectly via launch_minecraft
Loading

File-Level Changes

Change Details Files
Add in-memory bootstrap path for the vendored _portablemc.pyd and expose helpers to inspect its state.
  • Introduce internal helpers to locate the vendored _portablemc*.pyd file and load it in-memory using pythonmemorymodule, registering it into sys.modules under portablemc and _portablemc names.
  • Create bootstrap_inmemory workflow that reuses existing vendored-tree bootstrap (wheel extraction, path prep, pip scrubbing) before mapping the .pyd into memory and wiring submodule stubs for the portablemc.* PyO3 submodules.
  • Track and expose whether the native extension was loaded in-memory and return the in-memory module object for diagnostics or advanced callers.
scripts/local_portablemc.py
Wire portablemc environment bootstrap (including optional in-memory loading) into the in-process JVM boot flow with clearer error reporting and mode logging.
  • Add _ensure_portablemc_environment to perform one-time portablemc 5.x environment setup, optionally using bootstrap_inmemory and tracking if in-memory mode is active.
  • Extend PortableMCGameAdapter.resolve to accept an inmemory flag, call the environment bootstrap first, log which loading mode is active, and refine RuntimeError messages for import, API mismatch, and generic failures.
  • Expose is_portablemc_inmemory helper so callers can query the active mode and log it during resolution.
scripts/jvm_boot_glue.py
Allow the CLI in-process launcher to toggle portablemc in-memory loading via function parameters and environment variables, and surface the active mode in logs and crash reports.
  • Extend run_inprocess_launcher and launch_minecraft to accept a portablemc_inmemory flag, resolve the effective value from LAUNCHER_PORTABLEMC_INMEMORY and menu choices, and pass it into the adapter resolve path.
  • Print the chosen portablemc loading mode (standard vs in-memory) alongside JVM loader mode in the in-process launcher banner and include it in crash-log payloads.
  • Default to enabling portablemc in-memory loading for the strict in-process memory-resident mode and optionally for standard in-process mode based on environment variables.
main.py
scripts/jvm_boot_glue.py
Introduce a reusable utility for loading arbitrary .pyd extension modules directly into memory using pythonmemorymodule.
  • Implement PydMemoryLoader class that maps a .pyd into memory, infers its PyInit_ symbol, calls it via ctypes to obtain the module object, and exposes registration helpers for modules and submodules in sys.modules.
  • Provide small convenience helpers (load_pyd_inmemory, is_pyd_loaded, get_loaded_pyd) and a simple cache to avoid double-loading the same .pyd.
  • Document constraints, usage patterns, and failure modes for in-memory .pyd loading in a dedicated utility module to decouple the mechanism from portablemc-specific wiring.
scripts/pyd_memory_loader.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@PythonChicken123
PythonChicken123 marked this pull request as ready for review May 10, 2026 17:46
@PythonChicken123
PythonChicken123 merged commit e2fb463 into memory May 10, 2026
5 of 7 checks passed

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • The low-level pythonmemorymodule wiring in local_portablemc._load_pyd_inmemory duplicates the new PydMemoryLoader logic in pyd_memory_loader.py; consider refactoring local_portablemc to use PydMemoryLoader so the in-memory loading behavior and error handling are centralized and easier to maintain.
  • Several of the section banner comments now contain garbled box-drawing characters (e.g., '══════════════��════════'); it would be good to normalize these strings to avoid encoding artifacts creeping into the codebase.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The low-level pythonmemorymodule wiring in local_portablemc._load_pyd_inmemory duplicates the new PydMemoryLoader logic in pyd_memory_loader.py; consider refactoring local_portablemc to use PydMemoryLoader so the in-memory loading behavior and error handling are centralized and easier to maintain.
- Several of the section banner comments now contain garbled box-drawing characters (e.g., '══════════════��════════'); it would be good to normalize these strings to avoid encoding artifacts creeping into the codebase.

## Individual Comments

### Comment 1
<location path="scripts/local_portablemc.py" line_range="268-277" />
<code_context>
+        mem_mod = MemoryModule(data=pyd_data, debug=False)
</code_context>
<issue_to_address>
**issue (bug_risk):** Keep a strong reference to MemoryModule to avoid underlying mapping being GC'd while the module is in use.

Because `MemoryModule` is only assigned to the local `mem_mod`, it can be garbage-collected after `_load_pyd_inmemory` returns, potentially unmapping the PE image while `_portablemc` is still in use and causing crashes. Please keep a strong reference for at least as long as the module is loaded (e.g., via a module-level variable like `_PYD_MEMORY_HANDLE` or an attribute on the module).
</issue_to_address>

### Comment 2
<location path="scripts/local_portablemc.py" line_range="240-249" />
<code_context>
+    return None
+
+
+def _load_pyd_inmemory(pyd_path: Path) -> types.ModuleType | None:
+    """
+    Load a .pyd file into memory using pythonmemorymodule.
+
+    Returns the loaded module object, or None on failure.
+    This function:
+    1. Reads the .pyd binary into memory
+    2. Uses pythonmemorymodule.MemoryModule to map it
+    3. Creates a module object and initializes it via PyInit_*
+    """
+    global _PYD_LOADED_INMEMORY, _PYD_MEMORY_MODULE
+
+    if _PYD_LOADED_INMEMORY and _PYD_MEMORY_MODULE is not None:
+        log.debug("_portablemc pyd already loaded in-memory")
+        return _PYD_MEMORY_MODULE
+
+    try:
</code_context>
<issue_to_address>
**suggestion:** Consider reusing the generic PydMemoryLoader instead of duplicating in-memory .pyd loading logic.

This function reimplements the same flow as `PydMemoryLoader` (reading the .pyd, creating `MemoryModule`, invoking `PyInit_*`, and registering into `sys.modules`). To avoid duplication and drift, consider having `_load_pyd_inmemory` call into `PydMemoryLoader` and then perform any portablemc-specific registration, so fixes for error handling or platform-specific behavior stay centralized.

Suggested implementation:

```python
def _load_pyd_inmemory(pyd_path: Path) -> types.ModuleType | None:
    """
    Load a .pyd file into memory using the generic PydMemoryLoader.

    Returns the loaded module object, or None on failure.
    This function delegates the low-level in-memory loading to PydMemoryLoader
    and only handles portablemc-specific caching of the loaded module.
    """
    global _PYD_LOADED_INMEMORY, _PYD_MEMORY_MODULE

    if _PYD_LOADED_INMEMORY and _PYD_MEMORY_MODULE is not None:
        log.debug("_portablemc pyd already loaded in-memory")
        return _PYD_MEMORY_MODULE

    try:
        from some_module import PydMemoryLoader  # adjust import to actual location

        log.info("Loading _portablemc.pyd in-memory from: %s", pyd_path)
        loader = PydMemoryLoader(pyd_path)
        module = loader.load()
    except Exception:
        log.exception("Failed to load _portablemc.pyd in-memory from %s", pyd_path)
        return None

    _PYD_LOADED_INMEMORY = True
    _PYD_MEMORY_MODULE = module
    return module

```

1. Replace `from some_module import PydMemoryLoader` with the correct import path for `PydMemoryLoader` as used elsewhere in your codebase.
2. Remove any remaining legacy in-memory loading logic that may still exist after this snippet (e.g., direct `pythonmemorymodule.MemoryModule` usage, manual `PyInit_*` invocation, or `sys.modules` registration) so that `_load_pyd_inmemory` only delegates to `PydMemoryLoader` and performs caching.
3. Ensure `PydMemoryLoader.load()` returns the initialized module object and handles platform-specific behaviors and error handling consistently with its other call sites.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +268 to +277
mem_mod = MemoryModule(data=pyd_data, debug=False)

# Get the PyInit function address
# The function name is PyInit_<module_name> for Python 3 extensions
init_func_name = b"PyInit__portablemc"
init_func_addr = mem_mod.get_proc_addr("PyInit__portablemc")

if not init_func_addr:
log.error("Could not find PyInit__portablemc in pyd")
return None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Keep a strong reference to MemoryModule to avoid underlying mapping being GC'd while the module is in use.

Because MemoryModule is only assigned to the local mem_mod, it can be garbage-collected after _load_pyd_inmemory returns, potentially unmapping the PE image while _portablemc is still in use and causing crashes. Please keep a strong reference for at least as long as the module is loaded (e.g., via a module-level variable like _PYD_MEMORY_HANDLE or an attribute on the module).

Comment on lines +240 to +249
def _load_pyd_inmemory(pyd_path: Path) -> types.ModuleType | None:
"""
Load a .pyd file into memory using pythonmemorymodule.

Returns the loaded module object, or None on failure.
This function:
1. Reads the .pyd binary into memory
2. Uses pythonmemorymodule.MemoryModule to map it
3. Creates a module object and initializes it via PyInit_*
"""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider reusing the generic PydMemoryLoader instead of duplicating in-memory .pyd loading logic.

This function reimplements the same flow as PydMemoryLoader (reading the .pyd, creating MemoryModule, invoking PyInit_*, and registering into sys.modules). To avoid duplication and drift, consider having _load_pyd_inmemory call into PydMemoryLoader and then perform any portablemc-specific registration, so fixes for error handling or platform-specific behavior stay centralized.

Suggested implementation:

def _load_pyd_inmemory(pyd_path: Path) -> types.ModuleType | None:
    """
    Load a .pyd file into memory using the generic PydMemoryLoader.

    Returns the loaded module object, or None on failure.
    This function delegates the low-level in-memory loading to PydMemoryLoader
    and only handles portablemc-specific caching of the loaded module.
    """
    global _PYD_LOADED_INMEMORY, _PYD_MEMORY_MODULE

    if _PYD_LOADED_INMEMORY and _PYD_MEMORY_MODULE is not None:
        log.debug("_portablemc pyd already loaded in-memory")
        return _PYD_MEMORY_MODULE

    try:
        from some_module import PydMemoryLoader  # adjust import to actual location

        log.info("Loading _portablemc.pyd in-memory from: %s", pyd_path)
        loader = PydMemoryLoader(pyd_path)
        module = loader.load()
    except Exception:
        log.exception("Failed to load _portablemc.pyd in-memory from %s", pyd_path)
        return None

    _PYD_LOADED_INMEMORY = True
    _PYD_MEMORY_MODULE = module
    return module
  1. Replace from some_module import PydMemoryLoader with the correct import path for PydMemoryLoader as used elsewhere in your codebase.
  2. Remove any remaining legacy in-memory loading logic that may still exist after this snippet (e.g., direct pythonmemorymodule.MemoryModule usage, manual PyInit_* invocation, or sys.modules registration) so that _load_pyd_inmemory only delegates to PydMemoryLoader and performs caching.
  3. Ensure PydMemoryLoader.load() returns the initialized module object and handles platform-specific behaviors and error handling consistently with its other call sites.

@PythonChicken123
PythonChicken123 deleted the minecraft-launcher-refactor branch May 10, 2026 17:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant