From 427c5008cab00da2a649a64df48a67caab035288 Mon Sep 17 00:00:00 2001 From: Joud Kandeel Date: Fri, 22 May 2026 23:11:11 +0200 Subject: [PATCH] --- Minecraft-Community-Edition/Mod/ModLoader.cpp | 168 +++++++++ Minecraft-Community-Edition/Mod/ModLoader.h | 11 + Minecraft-Community-Edition/Mod/XAPI.h | 141 ++++++++ Minecraft-Community-Edition/Mod/readme.md | 321 ++++++++++++++++++ .../assets/images/jj.jpg | Bin 0 -> 31173 bytes SampleMod/CXAPI.c | 120 +++++++ SampleMod/CXAPI.h | 19 ++ SampleMod/MyMod.c | 34 ++ SampleMod/SampleMod.vcxproj | 164 +++++++++ SampleMod/SampleMod.vcxproj.filters | 30 ++ SampleModC#/SampleModC#.csproj | 15 + SampleModC#/XAPI.cs | 254 ++++++++++++++ SampleModCpp/MyMod.cpp | 28 ++ SampleModCpp/SampleModCpp.vcxproj | 155 +++++++++ SampleModCpp/SampleModCpp.vcxproj.filters | 30 ++ SampleModCpp/XPP.cpp | 124 +++++++ SampleModCpp/XPP.hpp | 28 ++ 17 files changed, 1642 insertions(+) create mode 100644 Minecraft-Community-Edition/Mod/ModLoader.cpp create mode 100644 Minecraft-Community-Edition/Mod/ModLoader.h create mode 100644 Minecraft-Community-Edition/Mod/XAPI.h create mode 100644 Minecraft-Community-Edition/Mod/readme.md create mode 100644 Minecraft-Community-Edition/assets/images/jj.jpg create mode 100644 SampleMod/CXAPI.c create mode 100644 SampleMod/CXAPI.h create mode 100644 SampleMod/MyMod.c create mode 100644 SampleMod/SampleMod.vcxproj create mode 100644 SampleMod/SampleMod.vcxproj.filters create mode 100644 SampleModC#/SampleModC#.csproj create mode 100644 SampleModC#/XAPI.cs create mode 100644 SampleModCpp/MyMod.cpp create mode 100644 SampleModCpp/SampleModCpp.vcxproj create mode 100644 SampleModCpp/SampleModCpp.vcxproj.filters create mode 100644 SampleModCpp/XPP.cpp create mode 100644 SampleModCpp/XPP.hpp diff --git a/Minecraft-Community-Edition/Mod/ModLoader.cpp b/Minecraft-Community-Edition/Mod/ModLoader.cpp new file mode 100644 index 0000000..7255148 --- /dev/null +++ b/Minecraft-Community-Edition/Mod/ModLoader.cpp @@ -0,0 +1,168 @@ +#include "ModLoader.h" + +static XIDevice* g_device; +static XIContext* g_ctx; +static XIExports g_exp; +eastl::unordered_map registeries; +Xuint64 hash_str(Xcstr str) { + Xuint64 h = 0xcbf29ce484222325ULL; // FNV offset basis + + while (*str) { + h ^= (Xuint8)(*str++); + h *= 0x100000001b3ULL; // FNV prime + } + + // Final mixing (improves avalanche) + h ^= h >> 33; + h *= 0xff51afd7ed558ccdULL; + h ^= h >> 33; + h *= 0xc4ceb9fe1a85ec53ULL; + h ^= h >> 33; + + return h; +} +typedef struct { + XIDevice iface; + Xint32 ref; + Xint32 handle; +}DeviceImpl; + +typedef struct { + XIContext iface; + Xint32 ref; +}ContextImpl; + +Xvoid mce_device_addref(XIDevice* device) { + ((DeviceImpl*)device)->ref++; +} + +Xvoid mce_device_release(XIDevice* device) { + DeviceImpl* self = (DeviceImpl*)device; + if (--self->ref == 0) { + //printf("[Engine] Device destroyed\n"); + } +} + +Xint32 mce_device_createQEventBus(XIDevice* device, XHQEventBus* qBus, XQEventBusDescriptor desc) { + + Xuint64 hash = hash_str(desc.name); + auto it = registeries.find(hash); + if (it == registeries.end()) { + registeries[hash] = new mce::core::QEventBus(desc.name); + qBus->idx = hash; + return 1; + } + else { + qBus->idx = it->first; + } + return 1; +} + +Xvoid mce_device_setXIExports(XIDevice* device, XIExports exp) { + g_exp = exp; +} + +Xvoid mce_ctx_addRef(XIContext* ctx) { + ((ContextImpl*)ctx)->ref++; +} + +Xvoid mce_ctx_release(XIContext* device) { + ContextImpl* self = (ContextImpl*)device; + if (--self->ref == 0) { + //printf("[Engine] Context destroyed\n"); + } +} + + + +Xuint32 mce_ctx_postEvent(XHQEventBus* qBus, Xvoid* event, Xconst Xuint64 type) { + auto it = registeries.find(qBus->idx); + if (it != registeries.end()) { + mce::core::QEventBus* _qBus = reinterpret_cast(it->second); + + if (type == XE_EVENT_TYPE_LOG) { + XSEventLog* XAPI_eventLog = reinterpret_cast(event); + mce::event::Log log; + log.channel = XAPI_eventLog->channel; + log.msg = XAPI_eventLog->msg; + log.severity = static_cast(XAPI_eventLog->severity); + _qBus->post(log); + return 1; + } + + } + + return XE_ERROR; +} + +void zMem(void*& mem, size_t size) { + char* _mem = (char*)mem; + + for (size_t i = 0; i < size; i++) { + _mem[i] = 0; + } +} + +Xvoid setGlobalQEventBus(mce::core::QEventBus* qBus) { + //auto num = hash_str("mce.core.event.logger_output"); + Xuint64 hash = hash_str(qBus->getNamespace().data()); + auto it = registeries.find(hash); + + if (it == registeries.end()) { + registeries[hash] = qBus; + } +} + +Xint32 mce_createDeviceAndContext(XIDevice** device, XIContext** ctx) { + if ((*device) != nullptr && (*ctx) != nullptr) { + return XE_ERROR; + } + + *device = new XIDevice(); + *ctx = new XIContext(); + + XIDeviceVTable* device_vtbl = new XIDeviceVTable(); + XIContextVTable* ctx_vtbl = new XIContextVTable(); + + //ZeroMemory(device_vtbl, sizeof(XIDeviceVTable)); + + //memset(device_vtbl, 0, sizeof(XIDeviceVTable)); + + if (!device_vtbl && !ctx_vtbl) { + return XE_ERROR; + } + const_cast(device_vtbl)->addRef = mce_device_addref; + const_cast(device_vtbl)->addRef = mce_device_release; + const_cast(device_vtbl)->createQEventBus = mce_device_createQEventBus; + const_cast(device_vtbl)->setXIExports = mce_device_setXIExports; + + const_cast(ctx_vtbl)->addRef = mce_ctx_addRef; + const_cast(ctx_vtbl)->release = mce_ctx_release; + //const_cast(ctx_vtbl)->setXIExports = mce_ctx_setXIExports; + const_cast(ctx_vtbl)->postEvent = mce_ctx_postEvent; + + (*device)->vtbl = device_vtbl; + (*ctx)->vtbl = ctx_vtbl; + + g_device = (*device); + g_ctx = (*ctx); + + return 1; +} + +Xint32 mce_destroyDeviceAndContext(XIDevice** device, XIContext** ctx) { + if ((*device) == nullptr || (*ctx) == nullptr) { + return XE_ERROR; + } + + delete (*device)->vtbl; + delete (*ctx)->vtbl; + + delete* device; + delete* ctx; + return 1; +} + +Xconst XIExports mce_pullSessionsExports() { + return g_exp; +} diff --git a/Minecraft-Community-Edition/Mod/ModLoader.h b/Minecraft-Community-Edition/Mod/ModLoader.h new file mode 100644 index 0000000..f7b8b16 --- /dev/null +++ b/Minecraft-Community-Edition/Mod/ModLoader.h @@ -0,0 +1,11 @@ +#pragma once +#define XAPI_USE_V1_0_0 +#include "XAPI.h" +#include + +Xvoid setGlobalQEventBus(mce::core::QEventBus* qBus); + +Xint32 mce_createDeviceAndContext(XIDevice** device, XIContext** ctx); +Xint32 mce_destroyDeviceAndContext(XIDevice** device, XIContext** ctx); + +Xconst XIExports mce_pullSessionsExports(); diff --git a/Minecraft-Community-Edition/Mod/XAPI.h b/Minecraft-Community-Edition/Mod/XAPI.h new file mode 100644 index 0000000..083744e --- /dev/null +++ b/Minecraft-Community-Edition/Mod/XAPI.h @@ -0,0 +1,141 @@ +///---------------------------------------------------------------------------------\\\ +/// MIT License \\\ +/// \\\ +/// Copyright(c) 2026 Joud Kandeel \\\ +/// \\\ +/// Permission is hereby granted, free of charge, to any person obtaining a copy \\\ +/// of this software and associated documentation files(the "Software"), to deal \\\ +/// in the Software without restriction, including without limitation the rights \\\ +/// to use, copy, modify, merge, publish, distribute, sublicense, and /or sell \\\ +/// copies of the Software, and to permit persons to whom the Software is \\\ +/// furnished to do so, subject to the following conditions : \\\ +/// \\\ +/// The above copyright notice and this permission notice shall be included in all\\\ +/// copies or substantial portions of the Software. \\\ +/// \\\ +/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \\\ +/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \\\ +/// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE \\\ +/// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \\\ +/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, \\\ +/// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE \\\ +/// SOFTWARE. \\\ +///---------------------------------------------------------------------------------\\\ + +#pragma once +#ifdef XAPI_USE_V1_0_0 +#ifndef XAPI_HEADER +#define XAPI_HEADER + +#ifdef _WIN32 +#ifdef __cplusplus +#define XAPI_EXPORT __declspec(dllexport) extern "C" +#define XAPI_LOCAL +#else +#define XAPI_EXPORT __declspec(dllexport) +#define XAPI_LOCAL +#endif +#else +#define XAPI_EXPORT __attribute__((visibility("default"))) +#define XAPI_LOCAL __attribute__((visibility("hidden"))) +#endif + +#define XAPI_LOCAL +#define XAPI_STDCALL __stdcall + +#define XAPI_VERSION 0x01000000u +#define XAPI_NULL (Xvoid*)0 + +#define XE_ERROR 0xDEADBEEF + +#define XE_EVENT_TYPE_LOG 0xA2942192B2001D9E //mce.core.event.logger_output + +#define XE_EVENT_LOG_DEBUG 0x07 + +#define Xconst const +#define Xconstptr const* +#define XInterface struct + +#ifdef __cplusplus +extern "C" { +#endif + + typedef char Xint8; + typedef short Xint16; + typedef int Xint32; + typedef long long Xint64; + + typedef unsigned char Xuint8; + typedef unsigned short Xuint16; + typedef unsigned int Xuint32; + typedef unsigned long long Xuint64; + + typedef Xconst char Xconstptr Xcstrcp; + typedef Xconst char* Xcstr; + + typedef void Xvoid; + + typedef XInterface XIDevice XIDevice; + typedef XInterface XIContext XIContext; + typedef Xconst Xint32(XAPI_STDCALL* XI_createDeviceAndContextFn)(XIDevice**, XIContext**); + typedef Xconst Xint32(XAPI_STDCALL* XI_destroyDeviceAndContextFn)(XIDevice**, XIContext**); + + typedef struct { + Xcstrcp name; + Xcstrcp author; + Xint32 version; + Xcstrcp language;// programming language + Xcstrcp sdkName; + Xcstrcp dependencies; + }XAPIDescriptor; + + typedef struct { + Xint64 idx; + }XHQEventBus; + + typedef struct { + Xcstr name; + Xuint16 maxSize; + }XQEventBusDescriptor; + + typedef struct { + Xcstr channel; + Xcstr msg; + Xuint8 severity; + }XSEventLog; + + typedef struct { + Xint32(*onShutdown)(Xvoid); + Xint32(*onUpdate)(Xvoid); + Xint32(*onInit)(Xvoid); + }XIExports; + + typedef struct { + Xvoid(*addRef)(XIDevice* device); + Xvoid(*release)(XIDevice* device); + Xint32(*createQEventBus)(XIDevice* device, XHQEventBus* qBus, XQEventBusDescriptor desc); + Xvoid(*setXIExports)(XIDevice* device, XIExports exp); + }XIDeviceVTable; + + XInterface XIDevice{ + Xconst XIDeviceVTable Xconstptr vtbl; + }; + + + typedef struct { + Xvoid(*addRef)(XIContext* ctx); + Xvoid(*release)(XIContext* ctx); + //Xvoid(*setXIExports)(XIExports exp); + Xuint32(*postEvent)(XHQEventBus* qBus, Xvoid* event, Xconst Xuint64 type); + Xuint32(*subscribeEvent)(XHQEventBus* qBus, Xconst Xuint64 type, Xconst Xvoid Xconstptr fn); + }XIContextVTable; + + XInterface XIContext{ + Xconst XIContextVTable Xconstptr vtbl; + }; + +#ifdef __cplusplus +} +#endif +#endif +#endif \ No newline at end of file diff --git a/Minecraft-Community-Edition/Mod/readme.md b/Minecraft-Community-Edition/Mod/readme.md new file mode 100644 index 0000000..7b20240 --- /dev/null +++ b/Minecraft-Community-Edition/Mod/readme.md @@ -0,0 +1,321 @@ +ο»Ώ-- - + +# XAPI β€” Cross - Engine Modding ABI + +XAPI is a **C ABI - based modding interface** designed for voxel / sandbox - style engines. +It provides a **stable, low - level contract** between engine and mods, enabling binary compatibility across different builds, forks, and even entirely separate engines. + +-- - + +## ✨ Overview + +XAPI is not tied to a specific engine implementation. + +Instead, it defines a **strict binary interface(ABI)** that any engine can implement and any mod can target. + +This allows: + +- Mods compiled once to run across multiple engines +- Different codebases to share the same mod ecosystem +- Language - agnostic bindings via C ABI + +-- - + +## 🧠 Design Philosophy + +- **C ABI first** β€” maximum portability and language interoperability +- **No engine pointers exposed**β€” mods operate on handles, not raw memory +- **Explicit lifecycle**β€” no hidden initialization or runtime magic +- **Event - driven communication**β€” decoupled interaction via event buses +- **Stable contracts over implementations**β€” behavior defined by spec, not engine internals + +-- - + +## 🧩 Core Concepts + +### 1. Entry Points + +Every XAPI module exports three functions : + +```c +XAPI_EXPORT XAPIDescriptor XI_query(Xvoid); +XAPI_EXPORT Xint32 XI_main(Xvoid* pParam); +XAPI_EXPORT Xint32 XI_terminate(Xvoid* pParam); +```` + +* `XI_query` β†’ returns mod metadata +* `XI_main` β†’ initializes the module +* `XI_terminate` β†’ cleans up resources + +-- - + +### 2. Device & Context + +The engine provides : + +* `XIDevice` β†’ resource and system access +* `XIContext` β†’ runtime interaction(events, callbacks) + +Mods never construct these manually β€” they are provided by the engine. + +-- - + +### 3. Function Table(VTable) + +All engine interaction is done through function tables : + +```c +device->vtbl->createQEventBus(...) +ctx->vtbl->postEvent(...) +``` + +This ensures : + +* ABI stability +* no direct linking against engine internals + +-- - + +### 4. Event System + +XAPI uses an **event - driven architecture**: + +* Mods post events via `postEvent` +* Systems subscribe via `subscribeEvent` +* Communication is decoupled and asynchronous + +Example : + +```c +XSEventLog log; +log.channel = "default"; +log.msg = "Hello World"; +log.severity = XE_EVENT_LOG_DEBUG; + +ctx->vtbl->postEvent(NULL, &log, XE_EVENT_TYPE_LOG); +``` + +-- - + +### 5. Handles Instead of Pointers + +All engine - side objects are represented as **handles**, not pointers: + +```c +typedef struct { + Xint64 idx; +} XHQEventBus; +``` + +This ensures : + +* memory safety across modules +* engine - side ownership control +* compatibility across implementations + +-- - + +## πŸ”„ Module Lifecycle + +1. Engine loads module(`.dll`) + 2. Calls `XI_query` β†’ retrieves metadata + 3. Calls `XI_main` + + * provides function pointer to create device / context + * mod initializes and registers callbacks + 4. Engine drives execution via callbacks : + +* `onInit` +* `onUpdate` +* `onShutdown` +5. Engine calls `XI_terminate` on unload + +-- - + +## 🧱 CXAPI(C Wrapper) + +CXAPI is a thin helper layer built on top of XAPI to simplify usage in C. + +It provides : + +* global state management +* callback registration +* utility functions(logging, event posting) + +Example: + +```c +CXAPI_setOnInitCB(onInit); +CXAPI_setOnUpdateCB(onUpdate); +CXAPI_setOnShutdownCB(onShutdown); +``` + +-- - + +## 🌍 Multi - Language Support + +Because XAPI is pure C ABI, it can be used from many languages. + +Bindings can be created by the community for: + +* C / C++ (native) +* Rust +* Zig +* Nim +* Go +* C# (AOT) +* Python(via C API / Cython) +* JavaScript / Lua(via embedded runtimes) + +The core project focuses on : + +> **C/C++ reference implementation and wrappers** + +-- - + +## πŸ” Safety Model + +* Mods never receive raw engine pointers +* All access goes through validated interfaces +* Engine retains full ownership of resources +* Invalid operations return error codes(`XE_ERROR`) + +-- - + +## πŸ”’ Versioning + +XAPI uses explicit versioning: + +```c +#define XAPI_VERSION 1 +``` + +Future versions must maintain : + +* backward compatibility where possible +* strict ABI guarantees + +-- - + +## 🎯 Goals + +* Provide a **portable modding standard** +* Enable **cross - engine compatibility** +* Keep the core **minimal, stable, and predictable** +* Allow higher - level systems to be built on top + +-- - + +## πŸš€ Example(Minimal Mod) + +```c +//Globals +XAPIDescriptor desc; +XIExports exports; +XHQEventBus qBus; +XI_createDeviceAndContextFn XI_createDeviceAndContext; +XI_destroyDeviceAndContextFn XI_destroyDeviceAndContext; + +XAPI_LOCAL Xint32 onUpdate(Xvoid) { + return 1; +} + +XAPI_LOCAL Xint32 onInit(Xvoid) { + return 1; +} + +XAPI_LOCAL Xint32 onShutdown(Xvoid) { + return 1; +} + +XAPI_EXPORT Xconst XAPIDescriptor XI_query(Xvoid) { + XAPIDescriptor desc; + desc.author = "CDevJoud"; + desc.language = "C"; + desc.name = "My Mod!"; + desc.version = 1; + desc.dependencies = "xapi.sys.q_eventBus"; + desc.sdkName = "CXAPI"; + return desc; +} + +XAPI_EXPORT Xint32 XI_main(Xvoid* pParam) { + XI_createDeviceAndContext = (XI_createDeviceAndContextFn)pParam; + Xconst Xint32 ret = XI_createDeviceAndContext(&device, &ctx); + + if (ret != XE_ERROR) { + // request for the APP QEventBus + if (device->vtbl->createQEventBus != XAPI_NULL) { + XQEventBusDescriptor qBusdesc; + qBusdesc.name = "APP"; + qBusdesc.maxSize = 0xFFFF; + Xint32 res = device->vtbl->createQEventBus(device, &qBus, qBusdesc); + } + else { + return XE_ERROR; //Fatal Error! + } + + exports.onInit = onInit; + exports.onShutdown = onShutdown; + exports.onUpdate = onUpdate; + + if (device->vtbl->setXIExports != XAPI_NULL) { + device->vtbl->setXIExports(&device, exports); + } + else { + return XE_ERROR; //Fatal Error! + } + } + XSEventLog log; + log.channel = "default"; + log.msg = "Running XAPI!"; + log.severity = XE_EVENT_LOG_DEBUG; + ctx->vtbl->postEvent(&qBus, &log, XE_EVENT_TYPE_LOG); + return ret != XE_ERROR; +} + +XAPI_EXPORT Xint32 XI_terminate(Xvoid* pParam) { + XI_destroyDeviceAndContext = (XI_destroyDeviceAndContextFn)pParam; + return XI_destroyDeviceAndContext(&device, &ctx) != XE_ERROR; +} +``` + +--- + +## πŸ“Œ Summary + +XAPI is : + +* a **specification**, not just an implementation +* a **binary contract**, not a framework +* a **foundation layer** for building mod ecosystems + +--- + +**XAPI is an open specification. Anyone is free to implement it in their own engine or create language bindings.** + +## πŸ“„ License + +MIT License + +Copyright (c) 2026 Joud Kandeel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--- \ No newline at end of file diff --git a/Minecraft-Community-Edition/assets/images/jj.jpg b/Minecraft-Community-Edition/assets/images/jj.jpg new file mode 100644 index 0000000000000000000000000000000000000000..559340737f619ba830f0f0b2bb61c0dd6e3efb2b GIT binary patch literal 31173 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 zt~=IR9n8$!@muvtbl->createQEventBus != XAPI_NULL) { + XQEventBusDescriptor qBusdesc; + qBusdesc.name = "APP"; + Xint32 res = device->vtbl->createQEventBus(device, &qBus, qBusdesc); + } + else { + return XE_ERROR; //Fatal Error! + } + + CXAPI_logDebug("CXAPI running XAPI on version: 1.0.0, instantiating the mod loading phase!"); + + exports.onInit = CXAPI_onInit; + exports.onShutdown = CXAPI_onShutdown; + exports.onUpdate = CXAPI_onUpdate; + + if (device->vtbl->setXIExports != XAPI_NULL) { + device->vtbl->setXIExports(&device, exports); + } + else { + return XE_ERROR; //Fatal Error! + } + } + + CXAPI_logDebug("CXAPI has been instantiated!"); + return ret != XE_ERROR; +} + +XAPI_EXPORT Xint32 XI_terminate(Xvoid* pParam) { + XI_destroyDeviceAndContext = (XI_destroyDeviceAndContextFn)pParam; + return XI_destroyDeviceAndContext(&device, &ctx) != XE_ERROR; +} + +XAPI_LOCAL Xvoid CXAPI_setOnShutdownCB(onShutdownCB cb) { + cxapiExports.onShutdown = cb; +} + +XAPI_LOCAL Xvoid CXAPI_setOnInitCB(onInitCB cb) { + cxapiExports.onInit = cb; +} + +XAPI_LOCAL Xvoid CXAPI_setOnUpdateCB(onUpdateCB cb) { + cxapiExports.onUpdate = cb; +} + +XAPI_LOCAL Xvoid CXAPI_logDebug(Xcstr msg) { + XSEventLog log; + log.channel = "default"; + log.msg = msg; + log.severity = XE_EVENT_LOG_DEBUG; + CXAPI_postEvent(XAPI_NULL, &log, XE_EVENT_TYPE_LOG); +} + +XHQEventBus CXAPI_createQEventBus(Xcstr name) { + XHQEventBus qBus; + if (device->vtbl->createQEventBus != XAPI_NULL) { + XQEventBusDescriptor qBusdesc; + qBusdesc.name = name; + qBusdesc.maxSize = 0xFFFF; + device->vtbl->createQEventBus(device, &qBus, qBusdesc); + } + else { + qBus.idx = 0; + } + return qBus; +} + +Xuint32 CXAPI_postEvent(XHQEventBus* _qBus, Xvoid* event, Xconst Xuint64 type) { + XHQEventBus thisQbus; + if (_qBus == XAPI_NULL) { + thisQbus.idx = qBus.idx; + } + else { + thisQbus.idx = _qBus->idx; + } + return (ctx->vtbl->postEvent != XAPI_NULL) ? ctx->vtbl->postEvent(&thisQbus, event, type) : XE_ERROR; +} + + diff --git a/SampleMod/CXAPI.h b/SampleMod/CXAPI.h new file mode 100644 index 0000000..c0ba576 --- /dev/null +++ b/SampleMod/CXAPI.h @@ -0,0 +1,19 @@ +#pragma once +#define XAPI_USE_V1_0_0 +#include "XAPI.h" + +typedef Xint32(XAPI_STDCALL onShutdownCB)(Xvoid); +typedef Xint32(XAPI_STDCALL onInitCB)(Xvoid); +typedef Xint32(XAPI_STDCALL onUpdateCB)(Xvoid); +XAPI_LOCAL Xvoid initCXAPI(); +XAPI_LOCAL XAPIDescriptor queryCXAPIDescriptor(); + +XAPI_LOCAL XHQEventBus CXAPI_createQEventBus(Xcstr name); +XAPI_LOCAL Xuint32 CXAPI_postEvent(XHQEventBus* qBus, Xvoid* event, Xconst Xuint64 type); + +XAPI_LOCAL Xvoid CXAPI_logDebug(Xcstr msg); + +XAPI_LOCAL Xvoid CXAPI_setOnShutdownCB(onShutdownCB cb); +XAPI_LOCAL Xvoid CXAPI_setOnInitCB(onInitCB cb); +XAPI_LOCAL Xvoid CXAPI_setOnUpdateCB(onUpdateCB cb); + diff --git a/SampleMod/MyMod.c b/SampleMod/MyMod.c new file mode 100644 index 0000000..d03c06a --- /dev/null +++ b/SampleMod/MyMod.c @@ -0,0 +1,34 @@ +#include "CXAPI.h" + +XAPI_LOCAL Xint32 onInit(Xvoid); +XAPI_LOCAL Xint32 onUpdate(Xvoid); +XAPI_LOCAL Xint32 onShutdown(Xvoid); + +XAPI_LOCAL XAPIDescriptor queryCXAPIDescriptor() { + XAPIDescriptor desc; + desc.author = "CDevJoud"; + desc.language = "C"; + desc.name = "My Mod!"; + desc.version = 1; + desc.dependencies = "mce.core.*"; + return desc; +} + +XAPI_LOCAL Xvoid initCXAPI() { + CXAPI_setOnInitCB(onInit); + CXAPI_setOnUpdateCB(onUpdate); + CXAPI_setOnShutdownCB(onShutdown); +} + +XAPI_LOCAL Xint32 onInit(Xvoid) { + CXAPI_logDebug("Hello From MyMod!Joud!"); + return 1; +} + +XAPI_LOCAL Xint32 onUpdate(Xvoid) { + return 1; +} + +XAPI_LOCAL Xint32 onShutdown(Xvoid) { + return 1; +} \ No newline at end of file diff --git a/SampleMod/SampleMod.vcxproj b/SampleMod/SampleMod.vcxproj new file mode 100644 index 0000000..0a5b1d0 --- /dev/null +++ b/SampleMod/SampleMod.vcxproj @@ -0,0 +1,164 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + 17.0 + Win32Proj + {43a5cfef-b842-465d-94e5-b2af0bbaceae} + SampleMod + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)\Minecraft-Community-Edition\Mod\;$(IncludePath) + + + $(SolutionDir)\Minecraft-Community-Edition\Mod\;$(IncludePath) + + + $(SolutionDir)\Minecraft-Community-Edition\Mod\;$(IncludePath) + + + $(SolutionDir)\Minecraft-Community-Edition\Mod\;$(IncludePath) + + + + Level3 + true + WIN32;_DEBUG;SAMPLEMOD_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + WIN32;NDEBUG;SAMPLEMOD_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + false + + + Windows + true + false + true + + + + + Level3 + true + _DEBUG;SAMPLEMOD_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + stdcpp23 + + + Windows + true + false + + + + + Level3 + true + true + true + NDEBUG;SAMPLEMOD_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + true + + + Windows + true + false + false + + + + + + \ No newline at end of file diff --git a/SampleMod/SampleMod.vcxproj.filters b/SampleMod/SampleMod.vcxproj.filters new file mode 100644 index 0000000..72d00e0 --- /dev/null +++ b/SampleMod/SampleMod.vcxproj.filters @@ -0,0 +1,30 @@ +ο»Ώ + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + + + Header Files + + + \ No newline at end of file diff --git a/SampleModC#/SampleModC#.csproj b/SampleModC#/SampleModC#.csproj new file mode 100644 index 0000000..327d896 --- /dev/null +++ b/SampleModC#/SampleModC#.csproj @@ -0,0 +1,15 @@ + + + net9.0-windows10.0.26100.0 + 10.0.17763.0 + enable + true + en-US + true + true + True + Z:\mce\x64 + true + true + + diff --git a/SampleModC#/XAPI.cs b/SampleModC#/XAPI.cs new file mode 100644 index 0000000..b40b876 --- /dev/null +++ b/SampleModC#/XAPI.cs @@ -0,0 +1,254 @@ +ο»Ώusing System; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using System.Text; + +#pragma warning disable CS8500 + +// ========================== +// BASIC TYPES +// ========================== +using Xint8 = System.SByte; +using Xint16 = System.Int16; +using Xint32 = System.Int32; +using Xint64 = System.Int64; + +using Xuint8 = System.Byte; +using Xuint16 = System.UInt16; +using Xuint32 = System.UInt32; +using Xuint64 = System.UInt64; + +// ========================== +// CONSTANTS +// ========================== +public static class XE { + public const Xuint32 ERROR = 0xDEADBEEF; + public const Xuint64 EVENT_TYPE_LOG = 0xA2942192B2001D9E; + public const Xuint8 EVENT_LOG_DEBUG = 0x07; +} + +// ========================== +// STRUCTS +// ========================== +[StructLayout(LayoutKind.Sequential)] +public unsafe struct XHQEventBus { + public Xint64 idx; +} + +[StructLayout(LayoutKind.Sequential)] +public unsafe struct XQEventBusDescriptor { + public Xint8* name; + public Xuint16 maxSize; +} + +[StructLayout(LayoutKind.Sequential)] +public unsafe struct XSEventLog { + public Xint8* channel; + public Xint8* msg; + public Xuint8 severity; +} + +[StructLayout(LayoutKind.Sequential)] +public unsafe struct XAPIDescriptor { + public Xint8* name; + public Xint8* author; + public Xint32 version; + public Xint8* language; + public Xint8* sdkName; + public Xint8* dependencies; +} + +// ========================== +// FORWARD TYPES +// ========================== +public unsafe struct XIDevice { + public XIDeviceVTable* vtbl; +} + +public unsafe struct XIContext { + public XIContextVTable* vtbl; +} + +// ========================== +// VTABLES +// ========================== +public unsafe struct XIDeviceVTable { + public delegate* unmanaged[Stdcall] addRef; + public delegate* unmanaged[Stdcall] release; + public delegate* unmanaged[Stdcall] createQEventBus; + public delegate* unmanaged[Stdcall] setXIExports; + +} + +public unsafe struct XIContextVTable { + public delegate* unmanaged[Stdcall] addRef; + public delegate* unmanaged[Stdcall] release; + + //public delegate* unmanaged[Stdcall] setXIExports; + + public delegate* unmanaged[Stdcall] postEvent; + + public delegate* unmanaged[Stdcall] subscribeEvent; +} + +// ========================== +// EXPORT CALLBACKS +// ========================== +public unsafe struct XIExports { + public delegate* unmanaged[Stdcall] onShutdown; + public delegate* unmanaged[Stdcall] onUpdate; + public delegate* unmanaged[Stdcall] onInit; +} + +// ========================== +// FUNCTION POINTER TYPES +// ========================== +public unsafe class XAPIFunctions { + public delegate* unmanaged[Stdcall] CreateDeviceAndContext; + + public delegate* unmanaged[Stdcall] DestroyDeviceAndContext; +} + +// ========================== +// GLOBAL STATE +// ========================== +public unsafe static class CSX { + public static XIDevice* device; + public static XIContext* ctx; + public static XHQEventBus qBus; + + // ====================== + // INIT + // ====================== + public static void Init(void* fn) { + var create = (delegate* unmanaged[Stdcall])fn; + + XIDevice* dev = null; + XIContext* context = null; + + int ret = create(&dev, &context); + if (ret == XE.ERROR) + return; + + // create default event bus + XQEventBusDescriptor desc; + desc.name = CString("APP"); + desc.maxSize = 0xFFFF; + + device = dev; + ctx = context; + + XHQEventBus hQEventBus; + + if (device != null) { + if (device->vtbl != null) { + if (device->vtbl->createQEventBus != null) { + device->vtbl->createQEventBus(device, &hQEventBus, desc); + qBus = hQEventBus; + } else { + return; + } + } + } + } + + // ====================== + // LOGGING + // ====================== + public static void LogDebug(string msg) { + XSEventLog log; + + log.channel = CString("default"); + log.msg = CString(msg); + log.severity = XE.EVENT_LOG_DEBUG; + + PostEvent(null, &log, XE.EVENT_TYPE_LOG); + } + + // ====================== + // EVENTS + // ====================== + public static Xuint32 PostEvent(XHQEventBus* bus, void* ev, Xuint64 type) { + XHQEventBus target; + + if (bus == null) + target = qBus; + else + target = *bus; + + return ctx->vtbl->postEvent(&target, ev, type); + } + + // ====================== + // STRING HELPER + // ====================== + public static sbyte* CString(string s) { + byte[] bytes = Encoding.ASCII.GetBytes(s + "\0"); + fixed (byte* p = bytes) + return (sbyte*)p; + } +} + +// ========================== +// EXAMPLE MOD IMPLEMENTATION +// ========================== +public unsafe static class MyMod { + // callbacks + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })] + public static int OnInit() { + CSX.LogDebug("Hello from C# XAPI mod!"); + return 1; + } + + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })] + public static int OnUpdate() { + //CSX.LogDebug("This is Update"); + return 1; + } + + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })] + public static int OnShutdown() { + return 1; + } + static XIExports exp; + // ====================== + // EXPORTS + // ====================== + [UnmanagedCallersOnly(EntryPoint = "XI_main")] + public static int XI_main(void* p) { + CSX.Init(p); + + exp.onInit = &OnInit; + exp.onUpdate = &OnUpdate; + exp.onShutdown = &OnShutdown; + + CSX.device->vtbl->setXIExports(CSX.device, exp); + return 1; + } + + [UnmanagedCallersOnly(EntryPoint = "XI_query", CallConvs = new[] { typeof(CallConvStdcall) })] + public static XAPIDescriptor XI_query() { + XAPIDescriptor desc; + + desc.name = CSX.CString("C# Mod"); + desc.author = CSX.CString("CDevJoud"); + desc.version = 1; + desc.language = CSX.CString("C#"); + desc.sdkName = CSX.CString("XAPI-CS"); + desc.dependencies = CSX.CString("mce.core.*"); + + + return desc; + } + + [UnmanagedCallersOnly(EntryPoint = "XI_terminate")] + public static void XI_terminate(void* p) { + var destroy = (delegate* unmanaged[Stdcall])p; + XIDevice* dev = null; + XIContext* context = null; + destroy(&dev, &context); + + CSX.device = dev; + CSX.ctx = context; + } +} \ No newline at end of file diff --git a/SampleModCpp/MyMod.cpp b/SampleModCpp/MyMod.cpp new file mode 100644 index 0000000..ab1167c --- /dev/null +++ b/SampleModCpp/MyMod.cpp @@ -0,0 +1,28 @@ +#include "XPP.hpp" + +class MyMod : public IXPP { +public: + MyMod(); + ~MyMod(); + XAPIDescriptor query() override { + return XAPIDescriptor(); + } + int onInit() override { + XPP::logDebug("Hello C++"); + return 1; + } + int onUpdate() override { + return 1; + } + int onShutdown() override { + return 1; + } +}mod; + +MyMod::MyMod() { + XPP::initXPP(&mod); +} + +MyMod::~MyMod() { + +} diff --git a/SampleModCpp/SampleModCpp.vcxproj b/SampleModCpp/SampleModCpp.vcxproj new file mode 100644 index 0000000..bbe1bc5 --- /dev/null +++ b/SampleModCpp/SampleModCpp.vcxproj @@ -0,0 +1,155 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + 17.0 + Win32Proj + {62121dc8-0ebd-43f8-8d30-85db30a3574d} + SampleModCpp + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)\Minecraft-Community-Edition\Mod\;$(IncludePath) + + + $(SolutionDir)\Minecraft-Community-Edition\Mod\;$(IncludePath) + + + + Level3 + true + WIN32;_DEBUG;SAMPLEMODCPP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + WIN32;NDEBUG;SAMPLEMODCPP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + _DEBUG;SAMPLEMODCPP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + stdcpp23 + + + Windows + true + false + + + + + Level3 + true + true + true + NDEBUG;SAMPLEMODCPP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + stdcpp23 + + + Windows + true + false + + + + + + \ No newline at end of file diff --git a/SampleModCpp/SampleModCpp.vcxproj.filters b/SampleModCpp/SampleModCpp.vcxproj.filters new file mode 100644 index 0000000..dde0bb9 --- /dev/null +++ b/SampleModCpp/SampleModCpp.vcxproj.filters @@ -0,0 +1,30 @@ +ο»Ώ + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + + + Header Files + + + \ No newline at end of file diff --git a/SampleModCpp/XPP.cpp b/SampleModCpp/XPP.cpp new file mode 100644 index 0000000..c7ab0cb --- /dev/null +++ b/SampleModCpp/XPP.cpp @@ -0,0 +1,124 @@ +#include "XPP.hpp" + +IXPP* XPP::_instance; +XI_createDeviceAndContextFn XI_createDeviceAndContext; +XI_destroyDeviceAndContextFn XI_destroyDeviceAndContext; +XHQEventBus qBus; +XIDevice* device; +XIContext* ctx; +XIExports exports; + +XPP& XPP::getSingleton() { + static XPP xpp; + return xpp; +} + +void XPP::initXPP(IXPP* instance) { + _instance = instance; +} + +void XPP::logDebug(const std::string& msg) { + XSEventLog log; + log.channel = "default"; + log.msg = msg.c_str(); + log.severity = XE_EVENT_LOG_DEBUG; + XPP::postEvent(nullptr, &log, XE_EVENT_TYPE_LOG); +} + +IXPP* XPP::getModInstance() { + return _instance; +} +XHQEventBus CXAPI_createQEventBus(Xcstr name) { + XHQEventBus qBus; + if (device->vtbl->createQEventBus != XAPI_NULL) { + XQEventBusDescriptor qBusdesc; + qBusdesc.name = name; + qBusdesc.maxSize = 0xFFFF; + device->vtbl->createQEventBus(device, &qBus, qBusdesc); + } + else { + qBus.idx = 0; + } + return qBus; +} +XHQEventBus XPP::createQEventBus(const std::string& name) { + XHQEventBus qBus; + if (device->vtbl->createQEventBus != XAPI_NULL) { + XQEventBusDescriptor qBusDesc; + qBusDesc.name = name.c_str(); + qBusDesc.maxSize = 0xFFFF; + device->vtbl->createQEventBus(device, &qBus, qBusDesc); + } + else { + qBus.idx = 0; + } + return qBus; +} + +int XPP::postEvent(XHQEventBus* _qBus, Xvoid* event, Xconst Xuint64 type) { + XHQEventBus thisQbus; + if (_qBus == XAPI_NULL) { + thisQbus.idx = qBus.idx; + } + else { + thisQbus.idx = _qBus->idx; + } + return (ctx->vtbl->postEvent != XAPI_NULL) ? ctx->vtbl->postEvent(&thisQbus, event, type) : XE_ERROR; +} + +extern "C" XAPI_EXPORT XAPIDescriptor XI_query(Xvoid) { + auto& instance = XPP::getSingleton(); + auto desc = instance.getModInstance()->query(); + desc.sdkName = "XPP"; + return desc; +} + +extern "C" XAPI_EXPORT Xint32 XI_main(Xvoid* pParam) { + XI_createDeviceAndContext = (XI_createDeviceAndContextFn)pParam; + Xconst Xint32 ret = XI_createDeviceAndContext(&device, &ctx); + + if (ret != XE_ERROR) { + + if (device != XAPI_NULL && ctx != XAPI_NULL) { + if (device->vtbl != XAPI_NULL && ctx->vtbl != XAPI_NULL) { + if (device->vtbl->createQEventBus != XAPI_NULL) { + XQEventBusDescriptor desc{}; + desc.maxSize = 0xFFFF; + desc.name = "APP"; + device->vtbl->createQEventBus(device, &qBus, desc); + } + else { + return XE_ERROR; + } + + exports.onInit = []() -> Xint32 {return XPP::getSingleton().getModInstance()->onInit(); }; + exports.onUpdate = []() -> Xint32 {return XPP::getSingleton().getModInstance()->onUpdate(); }; + exports.onShutdown = []() -> Xint32 {return XPP::getSingleton().getModInstance()->onShutdown(); }; + + if (device->vtbl->setXIExports != XAPI_NULL) { + device->vtbl->setXIExports(device, exports); + } + else { + return XE_ERROR; + } + } + else { + return XE_ERROR; + } + } + else { + return XE_ERROR; + } + } + XPP::getSingleton().logDebug("XPP loaded up successfully! XAPI version v1.0.0"); + return ret; +} + + +extern "C" XAPI_EXPORT Xint32 XI_terminate(Xvoid* pParam) { + XI_destroyDeviceAndContext = (XI_destroyDeviceAndContextFn)pParam; + + auto ret = XI_destroyDeviceAndContext(&device, &ctx); + + return ret; +} diff --git a/SampleModCpp/XPP.hpp b/SampleModCpp/XPP.hpp new file mode 100644 index 0000000..c2074b5 --- /dev/null +++ b/SampleModCpp/XPP.hpp @@ -0,0 +1,28 @@ +#pragma once +#define XAPI_USE_V1_0_0 +#include "XAPI.h" +#include + +class IXPP { +public: + virtual ~IXPP() { + + } + virtual XAPIDescriptor query() = 0; + virtual int onInit() = 0; + virtual int onUpdate() = 0; + virtual int onShutdown() = 0; +}; + +class XPP { +public: + static XPP& getSingleton(); + static void initXPP(IXPP* instance); + static void logDebug(const std::string& msg); + static IXPP* getModInstance(); + static XHQEventBus createQEventBus(const std::string& name); + static int postEvent(XHQEventBus* _qBus, Xvoid* event, Xconst Xuint64 type); +private: + static IXPP* _instance; +}; +