Skip to content

Refactor: SdkModule & Integration ModClient #17

@VIPO777

Description

@VIPO777

Refactor: NdGameSdk Module System (EntryPoint + Submodules)

Summary

Refactor the NdGameSdk module model so the SDK can:

  • Run without any module: only InitializeSdk(cfg) is called; base patches/components are active.
  • Optionally host one Main EntryPoint module (e.g., Nd.modclient) and any number of Submodules registered by that EntryPoint.

This gives developers two paths:

  1. Build a custom EntryPoint (strict/specific purpose of modding).
  2. Build submodules that extend the approved EntryPoint (Nd.modclient).

Scope / Goals

  • Allow InitializeSdk(&cfg) to succeed with no module.
  • Enforce single EntryPoint (a.k.a. “Main module”).
  • Add Submodule concept (+ register/unregister).
  • Both EntryPoint and Submodules can use SDK components & events.
  • EntryPoint can discover/track submodules and optionally expose internal services to them (via a small “services bridge”).
  • Provide soft standards + a patching utility component to avoid chaos.

Non-goals

  • Changing core SDK component lifecycle.

Public API

New/Updated

  • InitializeSdk(const SdkConfig* cfg)
  • RegisterEntryPoint(ISdkModule* main, EntryPointServices* services /*nullable*/)
  • UnregisterEntryPoint(ISdkModule* main)
  • RegisterSubmodule(ISdkModule* sub)
  • UnregisterSubmodule(ISdkModule* sub)
  • GetEntryPoint() -> ISdkModule* /*nullable*/
  • GetSubmodules() -> std::vector<ISdkModule*>
  • HasEntryPoint() -> bool
  • enum class SdkModuleKind { EntryPoint, Submodule }
  • struct EntryPointServices { virtual ~EntryPointServices() = default; /* opaque bridge */ }

Deprecated

  • RegisterSdkModule(ISdkModule* main)alias to RegisterEntryPoint.

ISdkModule (lifecycle hooks)

  • OnModuleRegistered()
  • OnRoleAssigned(SdkModuleKind role, ISdkModule* entrypoint, EntryPointServices* services)
    EntryPoint: role=EntryPoint, entrypoint=this
    Submodule: role=Submodule, entrypoint=GetEntryPoint()
  • (EntryPoint only) OnSubmoduleRegistered(ISdkModule* sub), OnSubmoduleUnregistered(ISdkModule* sub)

Errors (extend SdkModuleException)

  • AlreadyHasEntryPoint
  • NoEntryPoint
  • InvalidRole

Runtime Rules

  • Base-only mode: only InitializeSdk. No modules registered. SDK logs that it runs without an EntryPoint; components still function.
  • EntryPoint: can be registered once. Second registration → AlreadyHasEntryPoint.
  • Submodules: require an active EntryPoint; otherwise → NoEntryPoint.
  • Wiring: EntryPoint and Submodules receive the same SDK component events.
  • Order on register: OnModuleRegistered()OnRoleAssigned(...).
    EntryPoint also receives OnSubmoduleRegistered/Unregistered.
  • Lifetime: SDK does not own module objects. On EntryPoint unload, SDK auto-unregisters submodules first.
  • Thread-safety: operations guarded by a registry mutex.

Soft Standards: Patching Utilities (SDK Component)

Try to Introduce a PatchManager SDK component to standardize patching:

  • Capabilities (conceptual):
    • Detours (store by name/id), branch/call patches, byte patches with restore.
    • Grouping/scope (remove by group); auto-cleanup on module unload.
    • Address resolution goes through SDK discovery (patterns/symbols), no raw magic offsets in modules.
  • Naming convention:
    "<ModuleName>/<System>/<Action>" .
  • Logging: success/failure with name + address; verify page protection & instruction boundaries.

Access via GetSharedSdkComponent<PatchManager>().


EntryPoint Services Bridge

  • Minimal EntryPointServices interface acts as an opaque bridge from EntryPoint to Submodules.
  • The EntryPoint (e.g., Nd.modclient) implements a derived services class to expose optional internals (e.g., NdMods accessor) that submodules can consume after OnRoleAssigned.

Migration Notes

  • Base-only: just call InitializeSdk(&cfg); skip registration.
  • Existing main module: replace RegisterSdkModule(main) with RegisterEntryPoint(main, &services /*optional*/); optionally handle submodule callbacks.
  • Submodules: implement ISdkModule, rely on EntryPoint to call RegisterSubmodule(this); use OnRoleAssigned to bind services and SDK components.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions