├── Makefile ├── README.md ├── api ├── nb │ ├── events.h │ ├── interfaces.h │ └── types.h └── nbukkit.h ├── build.c ├── build.lua ├── config └── plugin.yml ├── java ├── ca │ └── jarcode │ │ └── nativebukkit │ │ └── NativeBukkit.java └── jni │ ├── JNIEntry.java │ ├── JNIPlugin.java │ ├── JNIRunnable.java │ └── NativeException.java ├── pom.xml └── src ├── cutils.c ├── entry.c ├── hooks.c ├── jutils.c ├── nbukkit_impl.c ├── plugin.c └── runnable.c /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | LUA_EXEC = "lua" 3 | # build file 4 | BUILD_FILE = "build.lua" 5 | 6 | .PHONY: all 7 | 8 | all: 9 | $(LUA_EXEC) $(BUILD_FILE) all 10 | 11 | install: 12 | $(LUA_EXEC) $(BUILD_FILE) install 13 | 14 | clean: 15 | $(LUA_EXEC) $(BUILD_FILE) clean 16 | 17 | debug: 18 | $(LUA_EXEC) $(BUILD_FILE) debug 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NativeBukkit 2 | 3 | **Do not use this unless you have a very good reason for it!** 4 | 5 | NativeBukkit is two things: 6 | 7 | - a bukkit plugin (containing its own plugin loader) that allows the loading of .so libraries as plugins for a minecraft server. 8 | 9 | - a version of the Bukkit API extended in pure C, allowing developers to write plugins in native code using NativeBukkit's header files. 10 | 11 | Because NativeBukkit only works with ELF dynamic libraries (`.so`), Linux and BSD will be the only supported platforms. I don't care about your proprietary platform. 12 | 13 | ### Performance 14 | 15 | NativeBukkit uses the JNI and its own wrapper structures and functions to call into Java code. When first initialized, NativeBukkit will resolve method IDs/handles for all of Bukkit's types, methods, and classes - leaving invocation with a low overhead. 16 | 17 | However, some operations (ie. wrapping Bukkit types) will require heavier interaction with the JNI. This means handling operations such as events will be **slower** in native code than in pure Java. **NativeBukkit is only a viable option for performance and memory usage if your plugin does most of its operations independant of the Bukkit API.** 18 | 19 | ### Supported languages 20 | 21 | You can write plugins in native code as long as you can interact with C functions and types and compile to a shared object (.so). This includes C, C++, D, Rust, and many, many others. 22 | 23 | Some languages will be very difficult to use with NativeBukkit. You need to pass function pointers to the NativeBukkit API for various reasons, call functions from a pointer, and work with C-style strings. I recommend writing your plugins in C/C++ or Rust for these compatibility reasons. -------------------------------------------------------------------------------- /api/nb/events.h: -------------------------------------------------------------------------------- 1 | #ifndef NB_EVENTS_H 2 | #define NB_EVENTS_H 3 | 4 | /* nb_ep: nativebukkit event priority */ 5 | enum nb_ep { 6 | NBEP_HIGHEST = 4, 7 | NBEP_HIGH = 3, 8 | NBEP_NORMAL = 2, 9 | NBEP_LOW = 1, 10 | NBEP_LOWEST = 0 11 | }; 12 | 13 | /* nb_ev: nativebukkit event */ 14 | enum nb_ev { 15 | NBEV_CMD 16 | }; 17 | 18 | typedef struct { 19 | nb_type sender; 20 | nb_vtsender vtsender; 21 | const char const** argv; 22 | int argc; 23 | bool cancelled; 24 | } nb_evcmd; 25 | 26 | #endif /* NB_EVENTS_H */ 27 | -------------------------------------------------------------------------------- /api/nb/interfaces.h: -------------------------------------------------------------------------------- 1 | #ifndef NB_INTERFACES_H 2 | #define NB_INTERFACES_H 3 | 4 | /* NativeBukkit uses a very primitive approach to OOP, there are 5 | virtual tables for different, abstract types (ie. interfaces), 6 | and then there are concrete types (ie. player) that have their 7 | own data and implementations of these interfaces. Concrete 8 | types are represented as nb_type (aka. void*) and are casted to 9 | their concrete type depending on the context. 10 | 11 | Abstract types do not have any data, they only have an interface 12 | in which you interact with the underlying concrete type. 13 | 14 | For casting and type checking at runtime, the following code is 15 | valid: 16 | 17 | nb_type player = (...); 18 | nb_vtsender* interface = (...); 19 | if (interface->type == N_PLAYER) { 20 | // call method from another interface that N_PLAYER implements 21 | // (vt collection).(concrete type).(interface).foobar(self, ...) 22 | 23 | // usually less verbose to keep a pointer to the vtables you need 24 | nb_vtplauer vplayer = &(api->vt.player.player); 25 | vplayer.sethealth(player, 0); 26 | 27 | // cast 'player' from nb_type to its concrete type 28 | nb_tplayer* cplayer = (nb_tplayer*) player; 29 | nb->logf(state, "%s's ip is: %s", interface->name(player), 30 | cplayer->address); 31 | } 32 | */ 33 | 34 | /* nb_vtXXX: nativebukkit virtual table (abstract type) */ 35 | /* nb_tXXX: nativebukkit type */ 36 | /* nb_type: enum that each vtable contains, can be used to 37 | check type at runtime */ 38 | 39 | typedef nb_type void*; 40 | 41 | struct nb_vtcollection { 42 | struct { 43 | nb_vtsender sender; 44 | nb_vtplayer player; 45 | } player; 46 | struct { 47 | nb_vtsender sender; 48 | } console; 49 | }; 50 | 51 | /* some vtables do not have a 'type' member, this is because they only have a single implementation, 52 | usually the same name (nb_vtplayer <-> nb_tplayer) */ 53 | 54 | typedef struct { 55 | enum nb_type type; 56 | void (*send) (nb_type self, const char* cmd); 57 | const char* (*name) (nb_type self); 58 | bool (*hasperm) (nb_type self, const char* perm); 59 | } nb_vtsender; 60 | 61 | /* note: some functions from the bukkit API do not exist here in favour of 62 | using raw packets (some are also deprecated) */ 63 | 64 | typedef struct { 65 | 66 | /* sends a raw packet, will automatically compress and encrypt if either are enabled */ 67 | void (*sendpacket) (nb_tplayer self, void* buf, size_t len); 68 | 69 | const char* (*dname) (nb_tplayer self); /* display name */ 70 | const char* (*tabname) (nb_tplayer self); /* tab list name */ 71 | 72 | int (*level) (nb_tplayer self); 73 | void (*setlevel) (nb_tplayer self, int level); 74 | double (*exp) (nb_tplayer self); 75 | void (*setexp) (nb_tplayer self, double exp); 76 | double (*exhaustion) (nb_tplayer self); 77 | void (*setexhaustion) (nb_tplayer self, double exhaustion); 78 | double (*saturation) (nb_tplayer self); 79 | void (*setsaturation) (nb_tplauer self, double saturation); 80 | int (*foodlevel) (nb_tplayer self); 81 | void (*setfoodlevel) (nb_tplayer self, int foodlevel); 82 | 83 | double (*healthscale) (nb_tplayer self); /* returns < 0 if not scaled */ 84 | void (*sethealthscale) (nb_tplayer self, double scale); /* set to < 0 to disable scaling */ 85 | 86 | bool (*canfly) (nb_tplayer self); 87 | void (*allowfly) (nb_tplayer self, bool allow); 88 | double (*flyspeed) (nb_tplayer self); 89 | void (*setflyspeed) (nb_tplayer self, double speed); 90 | bool (*flying) (nb_tplayer self); 91 | void (*setwalkspeed) (nb_tplayer self, double speed); 92 | 93 | bool (*sneaking) (nb_tplayer self); 94 | bool (*sprinting) (nb_tplayer self); 95 | bool (*sleeping) (nb_tplayer self); 96 | 97 | nb_loc (*comptarget) (nb_tplayer self); 98 | void (*setcomptarget) (nb_tplayer self, nb_loc loc); 99 | nb_loc (*bedspawn) (nb_tplayer self); 100 | void (*setbedspawn) (nb_tplayer self, nb_loc loc); 101 | 102 | void (*chat) (nb_tplayer self, const char* msg); /* perform command or chat */ 103 | void (*sendraw) (nb_tplayer self, const char* json); 104 | void (*kick) (nb_tplayer self, const char* msg); 105 | 106 | void (*setresoucepack) (nb_tplayer self, const char* url); 107 | 108 | } nb_vtplayer; 109 | 110 | typedef struct { 111 | enum nb_type type; 112 | } nb_vtentity; 113 | 114 | #endif /* NB_INTERFACES_H */ 115 | -------------------------------------------------------------------------------- /api/nb/types.h: -------------------------------------------------------------------------------- 1 | #ifndef NB_TYPES_H 2 | #define NB_TYPES_H 3 | 4 | typedef struct { 5 | double x, y, z; 6 | nb_tworld* world; 7 | } nb_loc; 8 | 9 | /* nb_ctype: nativebukkit concrete types */ 10 | enum nb_type { 11 | N_PLAYER, 12 | N_CONSOLE, 13 | N_WORLD 14 | }; 15 | 16 | /* N_PLAYER */ 17 | /* most interaction with player types is done through the player interface */ 18 | typedef struct { 19 | const char* address; /* IPv4 or IPv6 address that the player is connected with */ 20 | short port; /* port that the player is connected with */ 21 | const char* hostname; /* hostname of this player */ 22 | } nb_tplayer; 23 | 24 | /* N_WORLD */ 25 | typedef struct { 26 | const char* name; 27 | /* ... */ 28 | } nb_tworld; 29 | 30 | #endif /* NB_TYPES_H */ 31 | -------------------------------------------------------------------------------- /api/nbukkit.h: -------------------------------------------------------------------------------- 1 | 2 | /* Core NativeBukkit type definitions and macros */ 3 | 4 | #ifndef NBUKKIT_H 5 | #define NBUKKIT_H 6 | 7 | #ifdef __cplusplus 8 | #define NB_VISIBLE extern "C" __attribute__ ((visibility("default"))) 9 | #else 10 | #define NB_VISIBLE __attribute__ ((visibility("default"))) 11 | #endif 12 | #define NB_SYM NB_VISIBLE __attribute__ ((noinline)) 13 | 14 | /* compatibility version; this is incremented when an introduced change breaks 15 | this API's binary compatibility. */ 16 | #define NB_COMPAT_VERSION 4 17 | 18 | /* macros for symbols that NativeBukkit requires for a library to be loaded */ 19 | #define NB_ENABLE_SYM nb_enable_hook 20 | #define NB_DISABLE_SYM nb_disable_hook 21 | #define NB_LOAD_SYM nb_load_hook 22 | #define NB_VERSION_SYM nb_version 23 | 24 | /* define symbol for NB_COMAPT_VERSION */ 25 | #define NB_VERSION_DEF() NB_VISIBLE int NB_VERSION_SYM = NB_COMPAT_VERSION 26 | 27 | /* define enable, disable, and load functions*/ 28 | #define NB_ENABLE_DEF() NB_SYM void NB_ENABLE_SYM (void) 29 | #define NB_DISABLE_DEF() NB_SYM void NB_DISABLE_SYM (void) 30 | #define NB_LOAD_DEF(arg, api) NB_SYM void NB_LOAD_SYM (nb_state* arg, nb_api const* api) 31 | 32 | #define NBEX_INTERNAL 4 /* internal error or exception in the server, reason is set */ 33 | #define NBEX_GENFAIL 3 /* generic failure, reason is not set */ 34 | #define NBEX_BADARGS 2 /* bad arguments, reason is not set */ 35 | #define NBEX_UNKNOWN 1 /* unknown error, reason is not set */ 36 | 37 | #define NB_COMPLAIN(state, api) \ 38 | api->logf(state, "ERROR: %s", state->ex.type == NBEX_INTERNAL ? state->ex.reason \ 39 | : "bad arguments or nonstandard error") 40 | 41 | /* exception state */ 42 | typedef struct { 43 | int type; 44 | char* reason; 45 | } nb_ex; 46 | 47 | /* plugin-specific state */ 48 | typedef struct { 49 | #define NB_STATE_MEMBERS \ 50 | const char const* name; \ 51 | nb_ex ex; 52 | NB_STATE_MEMBERS 53 | } nb_state; 54 | 55 | #include 56 | #include 57 | #include 58 | 59 | /* primary API interface, static once handed to plugin in enable() */ 60 | typedef struct { 61 | /* name of the server implementation (usually "Spigot") */ 62 | const char* impl; 63 | /* the versions of Minecraft that the server supports */ 64 | const char const** impl_versions; 65 | /* extra information about the server implementation */ 66 | const char* impl_extra; 67 | 68 | /* logging functions; these do not interact with Bukkit's logging */ 69 | void (*logf) (nb_state* state, const char* format, ...); 70 | void (*log) (nb_state* state, const char* info); 71 | 72 | /* managed allocation functions (stubs) */ 73 | void* (*alloc) (nb_state* state, size_t size); 74 | void* (*realloc) (nb_state* state, void* ptr, size_t size); 75 | void (*free) (nb_state* state, void* ptr); 76 | 77 | /* the following functions can set state->ex if a non-fatal error occurred */ 78 | 79 | int unit; /* unit for listener ticks, in nanoseconds */ 80 | bool absolute_units; /* if event units are not tied to the server tickrate */ 81 | 82 | /* register a listener */ 83 | void (*lreg) (nb_state* state, enum nb_ev type, enum nb_ep priority, void (*handle) (void* event)); 84 | 85 | /* register a task; if period > 0, the task will be repeating */ 86 | void* (*treg) (nb_state* state, int delay, int period, void* udata, void (*task) (void* udata)); 87 | void (*tcancel) (nb_state* state, void* handle); 88 | 89 | /* collection of virtual tables, organized by vt.type.interface (eg. vt.player.sender) */ 90 | struct nb_vtcollection vt; 91 | 92 | /* unsafe functions */ 93 | struct { 94 | /* function to obtain JNIEnv* pointer for main server thread. 95 | returns NULL if not backed by a Java server */ 96 | void* (*java_env) (nb_state* state); 97 | /* function to obtain jobject (not a pointer, cast straight to 98 | jobject), as a Runnable instance */ 99 | void* (*java_runnable) (nb_state* state, void* udata, void (*task) (void* udata)); 100 | /* sets a pending exception to the nb_state and clears it */ 101 | void (*java_setex) (nb_state* state); 102 | } unsafe; 103 | } nb_api; 104 | 105 | typedef void (*nb_fenable) (void); 106 | typedef void (*nb_fdisable) (void); 107 | typedef void (*nb_fload) (nb_state*, nb_api* api); 108 | 109 | #endif /* NBUKKIT_H */ 110 | -------------------------------------------------------------------------------- /build.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | #define LSET(S, K, V) \ 21 | do { \ 22 | lua_pushstring(S, K); \ 23 | lua_pushcfunction(S, V); \ 24 | lua_settable(S, -3); \ 25 | } while (0) 26 | 27 | static int c_list(lua_State* state) { 28 | const char* arg = luaL_checkstring(state, 1); 29 | DIR* directory = opendir(arg); 30 | struct dirent *entry; 31 | if (directory == NULL || errno == ENOENT) { 32 | return 0; 33 | } 34 | int idx = 1; 35 | lua_newtable(state); 36 | while ((entry = readdir(directory)) != NULL) { 37 | if (strcmp("..", entry->d_name) == 0 || strcmp(".", entry->d_name) == 0) { 38 | continue; 39 | } 40 | lua_pushstring(state, entry->d_name); 41 | lua_rawseti(state, -2, idx); 42 | ++idx; 43 | } 44 | closedir(directory); 45 | return 1; 46 | } 47 | 48 | static const char* dirs[6] = { 49 | "/usr/bin", "/bin", "/usr/local/bin", /* always check */ 50 | "/sbin", "/usr/sbin", "/usr/local/sbin" /* if superuser */ 51 | }; 52 | 53 | static int c_findprog(lua_State* state) { 54 | const char* name = luaL_checkstring(state, 1); 55 | char a[PATH_MAX]; 56 | bool ret = false; 57 | size_t t, len = strlen(name), pre; 58 | struct stat buf; 59 | 60 | for (t = 0; getuid() == 0 ? t < 6 : t < 3; ++t) { 61 | pre = strlen(dirs[t]); 62 | assert(len + pre <= PATH_MAX - 2); 63 | memcpy(a, dirs[t], pre); 64 | a[pre] = '/'; 65 | memcpy(a + pre + 1, name, len); 66 | a[pre + len + 1] = '\0'; 67 | if (!stat(a, &buf)) { 68 | st: if (S_ISREG(buf.st_mode)) { 69 | ret = true; 70 | break; 71 | } 72 | else if (S_ISLNK(buf.st_mode)) { /* resolve symlink */ 73 | realpath(a, a); 74 | stat(a, &buf); 75 | goto st; 76 | } 77 | } 78 | else { 79 | fprintf(stderr, "failed to stat '%s': %s", a, strerror(errno)); 80 | errno = 0; 81 | } 82 | } 83 | 84 | lua_pushboolean(state, ret); 85 | return 1; 86 | } 87 | 88 | static int c_isfile(lua_State* state) { 89 | const char* arg = luaL_checkstring(state, 1); 90 | char buf[PATH_MAX]; 91 | realpath(arg, buf); 92 | struct stat path_stat; 93 | if (stat(buf, &path_stat)) { 94 | lua_pushboolean(state, false); 95 | return 1; 96 | } 97 | lua_pushboolean(state, S_ISREG(path_stat.st_mode)); 98 | return 1; 99 | } 100 | 101 | /* because some implemetnations of Lua's os.system do this differently */ 102 | static int c_system(lua_State* state) { 103 | const char* arg = luaL_checkstring(state, 1); 104 | int status = system(arg); 105 | lua_pushinteger(state, WEXITSTATUS(status)); 106 | return 1; 107 | } 108 | 109 | int entry(lua_State* state) { 110 | printf("\x1b[32m" "defining native functions..." "\x1b[0m\n"); 111 | lua_newtable(state); 112 | LSET(state, "list", &c_list); 113 | LSET(state, "isfile", &c_isfile); 114 | LSET(state, "findprog", &c_findprog); 115 | LSET(state, "system", &c_system); 116 | lua_setglobal(state, "C"); 117 | return 0; 118 | } 119 | 120 | -------------------------------------------------------------------------------- /build.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | NativeBukkit's build script. It assumes a POSIX-compliant environment with bash. 3 | 4 | if you are compiling for another platform, create a .lua script that sets these 5 | variables accordingly and then call 'dofile("build.lua")'. You can also add 6 | another rule in the makefile. 7 | ]] 8 | 9 | -- buffering is annoying, turn it off 10 | io.output():setvbuf("no") 11 | 12 | --[[ Settings ]] 13 | 14 | function default(gstr, val) 15 | if _G[gstr] == nil then _G[gstr] = val 16 | else assert(type(_G[gstr]) == type(val)) end 17 | end 18 | -- assume openjdk8 if JAVA_HOME is not set 19 | JAVA_HOME = os.getenv("JAVA_HOME") or "/usr/lib/jvm/java-8-openjdk" 20 | 21 | -- include folders 22 | default("INCLUDES", {"api", JAVA_HOME .. "/include", JAVA_HOME .. "/include/linux"}) 23 | -- library names to pass to the compiler 24 | default("LIBRARIES", {}) 25 | -- full library names (not paths) such as 'libfoo.so.0' for prequisite checks 26 | default("LIB_DEPENDENCIES", {}) 27 | 28 | default("COMPILER", "gcc") 29 | -- if you are compiling for your own system, use -march=native 30 | default("COMPILER_CFLAGS", "-m64") 31 | default("COMPILER_ARGS", "-Wall -Werror -fPIC -O2 " .. COMPILER_CFLAGS) 32 | default("COMPILER_DEBUG_ARGS", "-Wall -Wextra -fPIC -O0 -ggdb3 -DJU_DEBUG " .. COMPILER_CFLAGS) 33 | default("COMPILER_OUTFLAG", "-o") 34 | default("COMPILER_INCLUDEFLAG", "-I") 35 | default("COMPILER_SKIP_LINK", "-c") 36 | default("COMPILER_LANG_FLAG", "-x") 37 | 38 | default("LINKER", "gcc") 39 | default("LINKER_ARGS", "-shared -fvisibility=hidden") 40 | default("LINKER_OUTFLAG", "-o") 41 | default("LINKER_LIBRARYFLAG", "-l") 42 | 43 | -- symbols to preserve when stripping the final executable 44 | default("PRESERVE_SYMBOLS", { 45 | "Java_jni_JNIEntry_entry", 46 | "Java_jni_JNIPlugin_onLoad", 47 | "Java_jni_JNIPlugin_onEnable", 48 | "Java_jni_JNIPlugin_onDisable", 49 | "Java_jni_JNIPlugin_close", 50 | "Java_jni_JNIPlugin_open", 51 | "Java_jni_JNIRunnable_run" 52 | }) 53 | -- whether to strip all symbols, or only strip uneeded symbols 54 | default("AGGRESSIVE_STRIP", true) 55 | 56 | default("SOURCES", "src") -- sources, recursively searched 57 | default("OUTPUTS", "out") -- objects 58 | default("HEADERS", "gen") -- generated headers 59 | 60 | -- final executable or library name 61 | default("FINAL", "native-final/nativebukkit.so") 62 | 63 | -- whether to strip source files for iheader syntax, and to generate headers 64 | default("IHEADERS", true) 65 | 66 | -- if the current system complies with the Filesystem Hierarchy Standard (most Linux distributions). 67 | -- BSD users can leave this as true, since everything this script uses that relies on FHS is also 68 | -- true for BSD systems. 69 | 70 | -- Disabling this will remove some OS-dependent checks before building. 71 | default("FHS_COMPLIANT_SYSTEM", true) 72 | 73 | -- if the compiler in use is GCC. Disabling this will remove prerequisite checks for libraries. 74 | default("GCC_BASED_COMPILER", true) 75 | 76 | -- for build.c 77 | NATIVE_LIB = "build.c" 78 | LUA_CFLAGS = "-Wall -fPIC" 79 | LUA_LFLAGS = "-shared" 80 | LUA_LINK = "lua" 81 | 82 | -- like table.concat, except it ignores nil and empty string values 83 | function concat_s(tbl, pre, idx) 84 | local str = "" 85 | for i = 1, #tbl do 86 | local v = idx ~= nil and tbl[i][idx] or tbl[i] 87 | if (v ~= nil and v ~= "") then 88 | str = pre ~= nil and str .. pre .. v or str .. v 89 | if (i ~= #tbl) then str = str .. " " end 90 | end 91 | end 92 | return str 93 | end 94 | 95 | -- Ugly hack for lua 5.x's os.execute function to return the right value. 96 | -- LuaJIT will return the correct value, though. 97 | if string.sub(_VERSION, 1, 4) == "Lua " then 98 | __old_exec = os.execute 99 | os.execute = function(str) 100 | local ret = __old_exec(str) 101 | if type(ret) == "number" then 102 | return math.floor(ret / 256) 103 | elseif type(ret) == "boolean" then 104 | -- because for some undocumented reason, this might actually return a boolean. 105 | return ret and 0 or 1 106 | end 107 | end 108 | end 109 | 110 | -- goals 111 | goals = { 112 | prep = function() 113 | prereq {"iheaders", "strip"} 114 | prereq_libs(LIB_DEPENDENCIES) 115 | SOURCES = trim_node(SOURCES) 116 | OUTPUTS = trim_node(OUTPUTS) 117 | INCLUDES[#INCLUDES + 1] = "gen" 118 | os.execute("mkdir -p " .. SOURCES .. " && mkdir -p " 119 | .. OUTPUTS .. " && mkdir -p " .. HEADERS .. " && mkdir -p native-final") 120 | end, 121 | mvn = function() 122 | goals.load_native() 123 | goals.prep() 124 | if (C.isfile(FINAL) ~= true) then 125 | error("the 'mvn' goal cannot be ran before 'all'") 126 | end 127 | writeb("running maven goal: ", TERM_GREEN) 128 | print("install") 129 | local mvn_cmd = "mvn install" 130 | printcmd(mvn_cmd) 131 | if (os.execute(mvn_cmd) ~= 0) then 132 | error("maven build failed") 133 | end 134 | end, 135 | debug = function() 136 | goals.all(true) 137 | end, 138 | all = function(debug_mode) 139 | goals.load_native() 140 | goals.prep() 141 | if IHEADERS then 142 | writeb("generating headers: ", TERM_GREEN) 143 | print(SOURCES .. " -> " .. HEADERS) 144 | local cmd = "iheaders -G -d " .. HEADERS .. " -r " .. SOURCES 145 | .. " " .. concat_s(sort_files_t(SOURCES, "c", "cpp"), nil, "full") 146 | printcmd("iheaders -G -d " .. HEADERS .. " -r " .. SOURCES .. " {sources}") 147 | if (os.execute(cmd) ~= 0) then 148 | error("failed to generate headers") 149 | end 150 | end 151 | for k, entry in sort_files(SOURCES, "c", "cpp", "S") do 152 | if (string.sub(entry.filen, 1, 1) ~= ".") then 153 | writeb("compiling: ", TERM_GREEN) 154 | print(entry.full) 155 | local ext = entry.file:sub(entry.file:find(".", 1, true), entry.file:len()) 156 | local genstr = "" 157 | if #INCLUDES > 0 then 158 | for i = 1, #INCLUDES do 159 | genstr = genstr .. COMPILER_INCLUDEFLAG .. " \"" .. INCLUDES[i] .. "\"" 160 | if (#INCLUDES ~= i) then genstr = genstr .. " " end 161 | end 162 | end 163 | local parts = nil 164 | if debug_mode == true then COMPILER_ARGS = COMPILER_DEBUG_ARGS end 165 | if ext == ".c" and IHEADERS then 166 | parts = { 167 | "iheaders", "-p", "-O", entry.full, "|", 168 | COMPILER, COMPILER_ARGS, genstr, 169 | COMPILER_INCLUDEFLAG, "\"" .. SOURCES .. "\"", COMPILER_OUTFLAG, 170 | OUTPUTS .. entry.path .. "/" .. entry.filen .. ".o", 171 | COMPILER_SKIP_LINK, COMPILER_LANG_FLAG, "c", "-" 172 | } 173 | else 174 | parts = { 175 | COMPILER, COMPILER_ARGS, genstr, 176 | COMPILER_INCLUDEFLAG, "\"" .. SOURCES .. "\"", 177 | entry.full, COMPILER_OUTFLAG, 178 | OUTPUTS .. entry.path .. "/" .. entry.filen .. ".o", 179 | COMPILER_SKIP_LINK 180 | } 181 | end 182 | local cmd = concat_s(parts) 183 | printcmd(cmd) 184 | if (os.execute("mkdir -p " .. OUTPUTS .. entry.path .. "/") ~= 0) then 185 | error("failed to make directory: " .. OUTPUTS .. entry.path .. "/"); 186 | end 187 | if (os.execute(cmd) ~= 0) then 188 | error("failed to compile: " .. entry.full) 189 | end 190 | end 191 | end 192 | writeb("linking: ", TERM_GREEN) 193 | print(FINAL) 194 | local genstr = "" 195 | if #LIBRARIES > 0 then 196 | for i = 1, #LIBRARIES do 197 | genstr = genstr .. LINKER_LIBRARYFLAG .. LIBRARIES[i] 198 | if (#LIBRARIES ~= i) then genstr = genstr .. " " end 199 | end 200 | end 201 | local obj_list = {} 202 | for k, entry in sort_files(OUTPUTS, "o") do 203 | obj_list[#obj_list + 1] = entry.full 204 | end 205 | local parts = { 206 | LINKER, LINKER_ARGS, genstr, table.concat(obj_list, " "), LINKER_OUTFLAG, FINAL 207 | } 208 | local cparts = { 209 | LINKER, LINKER_ARGS, genstr, "{objects}", LINKER_OUTFLAG, FINAL 210 | } 211 | printcmd(concat_s(cparts)) 212 | 213 | if (os.execute(concat_s(parts)) ~= 0) then 214 | error("failed to link: " .. FINAL) 215 | end 216 | 217 | if debug_mode ~= true then 218 | writeb("stripping: ", TERM_GREEN); 219 | print(FINAL) 220 | local strip_ignore = concat_s(PRESERVE_SYMBOLS, "-K") 221 | local sparts = { 222 | "strip", AGGRESSIVE_STRIP and "--strip-all" or "--strip-unneeded", 223 | strip_ignore, FINAL 224 | } 225 | printcmd(concat_s(sparts)) 226 | if (os.execute(concat_s(sparts)) ~= 0) then 227 | error("failed to strip: " .. FINAL) 228 | end 229 | end 230 | goals.mvn() 231 | end, 232 | install = function() 233 | goals.load_native() 234 | goals.prep() 235 | error("stub") 236 | end, 237 | clean = function() 238 | local cmd = "rm -rf " .. OUTPUTS 239 | printb("cleaning output directory...", TERM_GREEN) 240 | printcmd(cmd) 241 | os.execute(cmd) 242 | end, 243 | load_native = function() 244 | BUILD_CMD = concat_s { 245 | COMPILER, LUA_CFLAGS, NATIVE_LIB, COMPILER_OUTFLAG, "build.o", COMPILER_SKIP_LINK 246 | } 247 | 248 | LINK_CMD = concat_s { 249 | LINKER, LUA_LFLAGS, LINKER_LIBRARYFLAG, LUA_LINK, 250 | "build.o", LINKER_OUTFLAG, "build.so" 251 | } 252 | 253 | printb(string.format("compiling %s...", NATIVE_LIB), TERM_GREEN) 254 | printcmd(BUILD_CMD) 255 | 256 | if os.execute(BUILD_CMD) ~= 0 then 257 | error("failed to compile " .. NATIVE_LIB) 258 | end 259 | 260 | printb(string.format("linking %s...", NATIVE_LIB), TERM_GREEN) 261 | printcmd(LINK_CMD) 262 | 263 | if os.execute(LINK_CMD) ~= 0 then 264 | error("failed to link " .. NATIVE_LIB) 265 | end 266 | 267 | package.loadlib("./build.so", "entry")() 268 | 269 | -- replace our ugly hack with a less hacky one 270 | os.execute = C.system; 271 | end 272 | } 273 | 274 | -- prevent goals from being ran twice in single execution 275 | for k, v in pairs(goals) do 276 | local temp = goals[k] 277 | goals[k] = function(...) 278 | if _G["once_" .. k] == nil then 279 | temp(...) 280 | _G["once_" .. k] = true 281 | end 282 | end 283 | end 284 | 285 | -- colors 286 | 287 | TERM_CHAR = string.char(0x1b) 288 | TERM_RED = TERM_CHAR .. "[31m" 289 | TERM_GREEN = TERM_CHAR .. "[32m" 290 | TERM_CMD = TERM_CHAR .. "[36m" 291 | TERM_BOLD = TERM_CHAR .. "[1m" 292 | TERM_RESET = TERM_CHAR .. "[0m" 293 | 294 | __error = error 295 | function error(message, level) 296 | printb(message, TERM_RED) 297 | __error("aborting...", 2) 298 | end 299 | 300 | function printc(message, c) print(c .. message .. TERM_RESET) end 301 | function printb(message, c) print(TERM_BOLD .. c .. message .. TERM_RESET) end 302 | function writec(message, c) io.write(c .. message .. TERM_RESET) end 303 | function writeb(message, c) io.write(TERM_BOLD .. c .. message .. TERM_RESET) end 304 | function printcmd(cmd) print(TERM_BOLD .. "-> " .. TERM_RESET .. TERM_CMD .. cmd .. TERM_RESET) end 305 | 306 | -- assert programs exist 307 | function prereq(tbl) 308 | if FHS_COMPLIANT_SYSTEM then 309 | printc("checking for prerequisite programs...", TERM_GREEN); 310 | for i = 1, #tbl do 311 | if C.findprog(tbl[i]) == false then 312 | error("program '" .. tbl[i] .. "' is not installed."); 313 | else print("'" .. tbl[i] .. "' is installed") end 314 | end 315 | end 316 | end 317 | 318 | -- assert libraries exist using GCC options 319 | function prereq_libs(tbl) 320 | local paths = nil; 321 | if GCC_BASED_COMPILER then 322 | printc("checking for prerequisite libraries...", TERM_GREEN); 323 | local handle = io.popen(COMPILER .. " --print-search-dirs") 324 | for ln in handle:lines() do 325 | if (string.sub(ln, 1, 12) == "libraries: =") then 326 | paths = {} 327 | local path_list = string.sub(ln, 13) 328 | local seg = 0 329 | while true do 330 | local idx = string.find(path_list, ":", seg + 1, true) 331 | if idx ~= nil then 332 | paths[#paths + 1] = trim_node(string.sub(path_list, seg + 1, idx - 1)) 333 | seg = idx; 334 | else break end 335 | end 336 | break 337 | end 338 | end 339 | handle:close() 340 | else return end 341 | if paths == nil and GCC_BASED_COMPILER then 342 | printc("Couldn't retrieve library search directory from GCC!", TERM_RED) 343 | return 344 | end 345 | for i = 1, #tbl do 346 | local exists = false 347 | for t = 1, #paths do 348 | if C.isfile(paths[t] .. "/" .. tbl[i]) then exists = true break end 349 | end 350 | if ~exists then 351 | error("library '" .. tbl[i] .. "' is not installed."); 352 | end 353 | end 354 | end 355 | 356 | -- recursive iterate over files 357 | -- returns table with 'file' set to the filename, 'path' to its folder relative to the 'folder' arg, and 358 | -- 'full', which corresponds to the full path of the file 359 | function r_iter(folder, path, base) 360 | if (path == nil) then path = "" end 361 | if (base == nil) then base = folder end 362 | local tbl = {} 363 | local list = C.list(folder) 364 | if (list == nil) then 365 | error(folder .. " does not exist") 366 | end 367 | for i = 1, #list do 368 | local node = path .. "/" .. list[i] 369 | if C.isfile(base .. "/" .. node) == false then 370 | local next_list = r_iter(base .. "/" .. node, node, base) 371 | for v = 1, #next_list do 372 | tbl[#tbl + 1] = next_list[v] 373 | end 374 | else 375 | tbl[#tbl + 1] = {file = list[i], path = path, full = base .. node} 376 | end 377 | end 378 | return tbl 379 | end 380 | 381 | -- find files with a given extension, using r_iter 382 | function sort_files_t(root, ...) 383 | local list = r_iter(root) 384 | for i = 1, #list do 385 | local entry = list[i].file 386 | local ext_idx = string.find(entry, ".", 1, true) 387 | local rm = true 388 | for k, v in ipairs({...}) do 389 | if string.sub(entry, ext_idx + 1) == v then 390 | rm = false 391 | break 392 | end 393 | end 394 | if rm then 395 | list[i] = nil 396 | else 397 | list[i].filen = string.sub(entry, 1, ext_idx - 1) 398 | end 399 | end 400 | return list 401 | end 402 | 403 | function sort_files(root, ...) 404 | return pairs(sort_files_t(root, ...)) 405 | end 406 | 407 | -- prevent a node from ending with a "/" (doesn't affect anything, just makes a weird path) 408 | function trim_node(base) 409 | while string.sub(base, base:len()) == "/" do 410 | base = string.sub(base, 1, base:len() - 1) 411 | end 412 | return base 413 | end 414 | 415 | -- print lua version 416 | print("Lua environment: " .. _VERSION); 417 | -- assert we have a working shell 418 | assert(__old_exec(nil) ~= 0) 419 | -- assert there's an argument 420 | assert(arg[1] ~= nil) 421 | -- execute goal 422 | goals[arg[1]]() 423 | -------------------------------------------------------------------------------- /config/plugin.yml: -------------------------------------------------------------------------------- 1 | name: NativeBukkit 2 | load: STARTUP 3 | version: ${project.version} 4 | author: Jarcode 5 | main: ca.jarcode.nativebukkit.NativeBukkit -------------------------------------------------------------------------------- /java/ca/jarcode/nativebukkit/NativeBukkit.java: -------------------------------------------------------------------------------- 1 | 2 | package ca.jarcode.nativebukkit; 3 | 4 | import org.bukkit.*; 5 | import org.bukkit.event.*; 6 | import org.bukkit.plugin.*; 7 | import org.bukkit.configuration.*; 8 | import org.bukkit.configuration.file.*; 9 | import org.bukkit.plugin.java.*; 10 | import org.bukkit.command.*; 11 | import org.bukkit.generator.*; 12 | 13 | import java.util.*; 14 | import java.util.logging.*; 15 | import java.util.regex.*; 16 | 17 | import java.io.*; 18 | import java.nio.file.*; 19 | 20 | import jni.*; 21 | 22 | public class NativeBukkit extends JavaPlugin { 23 | 24 | private static final String NATIVE_LIBRARY = "nativebukkit.so"; 25 | private static boolean LOADED_NATIVES = false; 26 | private static Loader LOADER; 27 | private static NativeBukkit instance; 28 | 29 | { instance = this; } 30 | 31 | public void onEnable() { 32 | if (!LOADED_NATIVES) { 33 | File result = null; 34 | InputStream stream = null; 35 | try { 36 | stream = this.getClass().getClassLoader().getResourceAsStream(NATIVE_LIBRARY); 37 | if (stream == null) 38 | throw new RuntimeException("Could not obtain stream to native library (null)"); 39 | File folder = getDataFolder(); 40 | if (!folder.exists()) 41 | if (!folder.mkdir()) 42 | throw new RuntimeException("Failed to create plugin folder: " + folder.getAbsolutePath()); 43 | result = new File(folder, NATIVE_LIBRARY); 44 | if (result.exists()) result.delete(); 45 | Files.copy(stream, Paths.get(result.getAbsolutePath())); 46 | } catch (Throwable e) { 47 | throw new RuntimeException(e); 48 | } finally { 49 | if (stream != null) { 50 | try { 51 | stream.close(); 52 | } catch (Throwable ignored) {} 53 | } 54 | } 55 | System.load(result.getAbsolutePath()); 56 | LOADED_NATIVES = true; 57 | new JNIEntry(this).entry(); 58 | /* Provide a simple loader for native plugins */ 59 | Bukkit.getPluginManager().registerInterface(Loader.class); 60 | /* Load native plugins through the plugin manager */ 61 | for (File f : getFile().getParentFile().listFiles()) { 62 | if (!f.isDirectory() && f.getName().endsWith(".so")) { 63 | try { 64 | Bukkit.getPluginManager().enablePlugin(Bukkit.getPluginManager().loadPlugin(f)); 65 | } catch (InvalidPluginException | InvalidDescriptionException e) { 66 | e.printStackTrace(); 67 | } 68 | } 69 | } 70 | } 71 | } 72 | 73 | /* Native plugin loader, ignores traditional listener registration and provides stub descriptions */ 74 | public static class Loader implements PluginLoader { 75 | public Loader(Server server) {} 76 | public Map, Set> 77 | createRegisteredListeners(Listener listener, Plugin plugin) { 78 | throw new RuntimeException("Cannot register listener classes for native plugins"); 79 | } 80 | public void disablePlugin(Plugin plugin) { ((NativePlugin) plugin).onDisable(); } 81 | public void enablePlugin(Plugin plugin) { ((NativePlugin) plugin).onEnable(); } 82 | public PluginDescriptionFile getPluginDescription(File file) { 83 | return new PluginDescriptionFile(file.getName(), "unknown", "native"); 84 | } 85 | public Pattern[] getPluginFileFilters() { return new Pattern[] { Pattern.compile("\\.so$") }; } 86 | public Plugin loadPlugin(File file) throws InvalidPluginException { 87 | Bukkit.getLogger().info(String.format("Loading native plugin '%s'...", file.getName())); 88 | try { 89 | NativePlugin p = new NativePlugin(file, this, JNIPlugin.open(file.getAbsolutePath())); 90 | Bukkit.getLogger().info(String.format("Loaded '%s'.", file.getName())); 91 | return p; 92 | } catch (NativeException e) { 93 | throw new InvalidPluginException(e); 94 | } 95 | 96 | } 97 | } 98 | 99 | /* Plugin implementation for native libraries */ 100 | /* Most of the implementations in this class are stubs */ 101 | public static class NativePlugin implements Plugin { 102 | private boolean enabled = false; 103 | private final File lib; 104 | private final JNIPlugin plugin; 105 | private final Loader loader; 106 | private NativePlugin(File lib, Loader loader, long handle) { 107 | this.lib = lib; 108 | this.loader = loader; 109 | plugin = new JNIPlugin(handle, this); 110 | onLoad(); 111 | } 112 | public FileConfiguration getConfig() { return null; } /* ignore */ 113 | public com.avaje.ebean.EbeanServer getDatabase() { return null; } /* stub */ 114 | public File getDataFolder() { 115 | return new File(instance.getFile().getParentFile(), getName().split(".")[0]); 116 | } 117 | public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) { return null; } 118 | /* native plugins don't include any extra information */ 119 | public PluginDescriptionFile getDescription() { 120 | return new PluginDescriptionFile(lib.getName(), "unknown", "native"); 121 | } 122 | public Logger getLogger() { return Bukkit.getLogger(); } /* never used */ 123 | public String getName() { return lib.getName(); } 124 | public PluginLoader getPluginLoader() { return loader; } 125 | public InputStream getResource(String filename) { return null; } /* never used */ 126 | public Server getServer() { return Bukkit.getServer(); } 127 | public boolean isEnabled() { return enabled; } 128 | public boolean isNaggable() { return false; } /* never used */ 129 | public void onDisable() { 130 | if (enabled) { 131 | Bukkit.getLogger().info(String.format("Disabling '%s'...", getName())); 132 | enabled = false; 133 | plugin.onDisable(); 134 | Bukkit.getLogger().info(String.format("'%s' has been disabled.", getName())); 135 | } 136 | } 137 | public void onEnable() { 138 | if (!enabled) { 139 | Bukkit.getLogger().info(String.format("Enabling '%s'...", getName())); 140 | enabled = true; 141 | plugin.onEnable(); 142 | Bukkit.getLogger().info(String.format("'%s' has been enabled.", getName())); 143 | } 144 | } 145 | public void onLoad() { 146 | plugin.onLoad(); 147 | } 148 | public void reloadConfig() {} /* ignore */ 149 | public void saveConfig() {} /* ignore */ 150 | public void saveDefaultConfig() {} /* ignore */ 151 | public void saveResource(String path, boolean replace) {} /* ignore */ 152 | public void setNaggable(boolean nag) {} /* never used */ 153 | public List 154 | onTabComplete(CommandSender sender, Command command, String alias, String[] args) { 155 | return new ArrayList(); 156 | } 157 | public boolean 158 | onCommand(CommandSender sender, Command command, String label, String[] args) { return true; } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /java/jni/JNIEntry.java: -------------------------------------------------------------------------------- 1 | 2 | package jni; 3 | 4 | import org.bukkit.plugin.java.JavaPlugin; 5 | 6 | public final class JNIEntry { 7 | 8 | private final JavaPlugin plugin; 9 | 10 | public JNIEntry(JavaPlugin plugin) { 11 | this.plugin = plugin; 12 | } 13 | 14 | public native void entry(); 15 | } 16 | -------------------------------------------------------------------------------- /java/jni/JNIPlugin.java: -------------------------------------------------------------------------------- 1 | 2 | package jni; 3 | 4 | import org.bukkit.plugin.Plugin; 5 | 6 | import ca.jarcode.nativebukkit.NativeBukkit; 7 | import ca.jarcode.nativebukkit.NativeBukkit.*; 8 | 9 | public final class JNIPlugin { 10 | 11 | public static native long open(String path); 12 | 13 | private final long __handle; 14 | private final String __name; 15 | private long __internal = 0; 16 | private final Plugin __plugin; 17 | 18 | public JNIPlugin(long handle, NativePlugin plugin) { 19 | __handle = handle; 20 | __name = plugin.getName(); 21 | __plugin = plugin; 22 | } 23 | public native void onEnable(); 24 | public native void onDisable(); 25 | public native void onLoad(); 26 | public native void close(); 27 | } 28 | -------------------------------------------------------------------------------- /java/jni/JNIRunnable.java: -------------------------------------------------------------------------------- 1 | 2 | package jni; 3 | 4 | public final class JNIRunnable implements Runnable { 5 | private final long __handle; 6 | private final long __udata; 7 | /* (void* udata, void (*ptr) (void* udata)) */ 8 | public JNIRunnable(long udata, long handle) { 9 | __handle = handle; /* function to be executed */ 10 | __udata = udata; /* extra instance data */ 11 | } 12 | @Override 13 | public native void run(); 14 | } 15 | -------------------------------------------------------------------------------- /java/jni/NativeException.java: -------------------------------------------------------------------------------- 1 | 2 | package jni; 3 | 4 | public final class NativeException extends RuntimeException { 5 | public NativeException(String message) { 6 | super(message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | ca.jarcode 7 | native-bukkit 8 | 0.1-pre 9 | 10 | 11 | 12 | spigot-repo 13 | https://hub.spigotmc.org/nexus/content/repositories/snapshots/ 14 | 15 | 16 | 17 | 18 | 19 | jarcode-site 20 | sftp://jarcode.ca/home/jarcode/maven2 21 | 22 | 23 | 24 | 25 | ${project.basedir}/java 26 | ${project.basedir}/mvn 27 | ${project.artifactId}-${project.version} 28 | 29 | 30 | 31 | ${project.basedir}/config 32 | true 33 | 34 | 35 | 36 | ${project.basedir}/native-final 37 | 38 | 39 | 40 | 41 | org.apache.maven.plugins 42 | maven-compiler-plugin 43 | 3.1 44 | 45 | 1.8 46 | 1.8 47 | 48 | 49 | 50 | org.apache.maven.plugins 51 | maven-assembly-plugin 52 | 53 | 54 | package 55 | 56 | single 57 | 58 | 59 | 60 | 61 | ${project.basedir} 62 | 63 | jar-with-dependencies 64 | 65 | 66 | 67 | 68 | org.apache.maven.plugins 69 | maven-deploy-plugin 70 | 2.7 71 | 72 | 73 | 74 | deploy-standard 75 | 76 | deploy-file 77 | 78 | 79 | ${project.build.directory}/${project.artifactId}-${project.version}.jar 80 | 81 | 82 | 83 | deploy-with-deps 84 | 85 | deploy-file 86 | 87 | 88 | ${project.basedir}/${project.artifactId}-${project.version}-jar-with-dependencies.jar 89 | 90 | 91 | 92 | 93 | 94 | org.apache.maven.plugins 95 | maven-jar-plugin 96 | 2.5 97 | false 98 | 99 | 100 | 101 | 102 | org.apache.maven.wagon 103 | wagon-ssh 104 | 1.0-beta-6 105 | 106 | 107 | 108 | 109 | 110 | org.bukkit 111 | bukkit 112 | 1.9.2-R0.1-SNAPSHOT 113 | provided 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /src/cutils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | @ { 10 | /* for atomics and pointer aliasing */ 11 | #define sw_barrier() asm volatile("": : :"memory") 12 | #define hw_barrier() __sync_synchronize 13 | 14 | #ifdef __linux__ 15 | #define smalloc(size) malloc(size) 16 | #define srealloc(ptr, size) realloc(size) 17 | #else 18 | #define smalloc(size) __safe_malloc(size) 19 | #define srealloc(ptr, size) __safe_realloc(ptr, size) 20 | #endif 21 | 22 | /* for other *nix variants; linux will lazily allocate memory and we'll 23 | probably have our process killed by the OOM killer before we run out 24 | actual, physical memory mapped to our process. */ 25 | 26 | /* it's also likely in other systems that JVM code is more likely to abort 27 | than our own code since it interacts with the heap much more 28 | aggressively. */ 29 | 30 | static inline void* __safe_malloc(size_t size) { 31 | void* ret; 32 | if (!(ret = malloc(size))) { 33 | fprintf(stderr, "malloc(): failed to allocate %d bytes", (int) size); 34 | abort(); 35 | } 36 | } 37 | 38 | static inline void* __safe_realloc(void* ptr, size_t size) { 39 | void* ret; 40 | if (!(ret = realloc(ptr, size))) { 41 | fprintf(stderr, "realloc(): failed to re-allocate to %d bytes", (int) size); 42 | abort(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/entry.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | /* entry JNI function called when this library is loaded */ 13 | JNIEXPORT void JNICALL Java_jni_JNIEntry_entry(JNIEnv* env, jobject this) { 14 | nb_log(&nb_stub, "initializing..."); 15 | hk_resolveall(env); 16 | } 17 | -------------------------------------------------------------------------------- /src/hooks.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | static ju_hook* hooks[] = { 13 | ju_hooks, /* needs to be resolved first, otherwise exceptions won't work */ 14 | pl_hooks, jrn_hooks, nb_hooks, 15 | NULL 16 | }; 17 | 18 | @() void hk_resolveall(JNIEnv* env) { 19 | nb_log(&nb_stub, "resolving JNI classes and members..."); 20 | ju_resb(env, hooks); 21 | nb_initsingletons(env); 22 | } 23 | -------------------------------------------------------------------------------- /src/jutils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | /* ju: java utils */ 14 | 15 | static jclass exclass; 16 | 17 | @ { 18 | #ifdef JU_DEBUG 19 | #define ASSERTEX(e) \ 20 | do { if ((*e)->ExceptionCheck(e) == JNI_TRUE) { ju_afatal(e, __func__, __LINE__); } } while (0) 21 | #else 22 | #define ASSERTEX(e) do {} while (0) 23 | #endif 24 | 25 | #define CHECKEX(e, b) do { if ((*e)->ExceptionCheck(e) == JNI_TRUE) { goto b; } } while (0) 26 | 27 | #define JU_NULL { NULL, NULL, NULL, JU_NONE } 28 | 29 | typedef enum { 30 | JU_CLASS, 31 | JU_METHOD, 32 | JU_STATIC_METHOD, 33 | JU_FIELD, 34 | JU_NONE 35 | } ju_hooktype; 36 | 37 | typedef struct { 38 | const char* name; /* name of class or member */ 39 | const char* sig; /* if memeber, its signature */ 40 | void* mem; /* pointer to memory storage for class or id */ 41 | ju_hooktype type; /* type of hook */ 42 | } ju_hook; 43 | } 44 | 45 | @(extern) ju_hook ju_hooks[] = { 46 | { "jni/NativeException", NULL, &exclass, JU_CLASS }, JU_NULL 47 | }; 48 | 49 | /* Fatal errors should be used carefully! They are only for when classes and members cannot be resolved, or 50 | for handling exceptions that _should_ not be raised when calling into Java. Checked exceptions should 51 | always stop the current API call, and then set the error state accordingly for a plugin's nb_state */ 52 | 53 | /* fatal error from ASSERTEX, expects a pending exception */ 54 | @(__attribute__ ((noreturn))) void ju_afatal(JNIEnv* env, const char* func, int line) { 55 | nb_logf(NULL, "JNI Assertion failed (%s:%d)", func, line); 56 | (*env)->ExceptionDescribe(env); 57 | abort(); 58 | } 59 | 60 | /* fatal error, usually unhandled exception or issues with getting handles/ids */ 61 | @(__attribute__ ((noreturn))) void ju_fatal(JNIEnv* env) { 62 | if ((*env)->ExceptionCheck(env) == JNI_TRUE) { 63 | nb_log(NULL, "Encountered an unhandled, fatal exception:"); 64 | (*env)->ExceptionDescribe(env); 65 | } else nb_log(NULL, "Encountered a fatal error, aborting"); 66 | abort(); 67 | } 68 | 69 | #define RFATAL(env, format, ...) \ 70 | do { \ 71 | nb_logf(NULL, format, ##__VA_ARGS__); \ 72 | ju_fatal(env); \ 73 | } while (0) 74 | 75 | @() jmethodID ju_resolvem(JNIEnv* env, jclass type, const char* method, const char* signature) { 76 | jmethodID ret = (*env)->GetMethodID(env, type, method, signature); 77 | CHECKEX(env, ex); 78 | return ret; 79 | ex: 80 | RFATAL(env, "Could not resolve non-static method '%s' (signature: '%s')", method, signature); 81 | } 82 | 83 | @() jfieldID ju_resolvef(JNIEnv* env, jclass type, const char* field, const char* signature) { 84 | jfieldID ret = (*env)->GetFieldID(env, type, field, signature); 85 | CHECKEX(env, ex); 86 | return ret; 87 | ex: 88 | RFATAL(env, "Could not resolve non-static field '%s' (signature: '%s')", field, signature); 89 | } 90 | 91 | @() jmethodID ju_resolvesm(JNIEnv* env, jclass type, const char* method, const char* signature) { 92 | jmethodID ret = (*env)->GetStaticMethodID(env, type, method, signature); 93 | CHECKEX(env, ex); 94 | return ret; 95 | ex: 96 | RFATAL(env, "Could not resolve static method '%s' (signature: '%s')", method, signature); 97 | } 98 | 99 | /* handle 2D array of hooks, null terminated */ 100 | @() void ju_resb(JNIEnv* env, ju_hook** hook_sets) { 101 | ju_hook* arr; 102 | size_t i; 103 | for (i = 0; (arr = hook_sets[i]) != NULL; ++i) { 104 | ju_res(env, arr); 105 | } 106 | } 107 | 108 | /* handle array of hooks, null terminated */ 109 | @() void ju_res(JNIEnv* env, ju_hook* hooks) { 110 | ju_hook* at; 111 | size_t i; 112 | jclass last = NULL; 113 | for (i = 0; (at = hooks + i)->name != NULL; ++i) { 114 | switch (at->type) { 115 | case JU_CLASS: 116 | last = ju_classreg(env, at->name, (jclass*) at->mem); 117 | nb_logf(&nb_stub, "obtained global handle for '%s' at %p", at->name, at->mem); 118 | break; 119 | case JU_STATIC_METHOD: 120 | *((jmethodID*) at->mem) = ju_resolvesm(env, last, at->name, at->sig); 121 | break; 122 | case JU_METHOD: 123 | *((jmethodID*) at->mem) = ju_resolvem(env, last, at->name, at->sig); 124 | break; 125 | case JU_FIELD: 126 | *((jfieldID*) at->mem) = ju_resolvef(env, last, at->name, at->sig); 127 | break; 128 | case JU_NONE: break; 129 | } 130 | } 131 | } 132 | 133 | @() jclass ju_classreg(JNIEnv* env, const char* name, jclass* mem) { 134 | jobject local = (*env)->FindClass(env, name); 135 | CHECKEX(env, ex); 136 | if (local == NULL) { 137 | ju_fatal(env); 138 | } 139 | *mem = (*env)->NewGlobalRef(env, local); 140 | CHECKEX(env, ex); 141 | (*env)->DeleteLocalRef(env, local); 142 | CHECKEX(env, ex); 143 | if (!(*mem)) goto ex; 144 | return *mem; 145 | ex: 146 | ju_fatal(env); 147 | } 148 | 149 | @() int ju_throwf(JNIEnv* env, const char* format, ...) { 150 | va_list argptr; 151 | jint ret; 152 | va_start(argptr, format); 153 | char buf[128]; 154 | vsnprintf(buf, 128, format, argptr); 155 | ret = exclass ? (*env)->ThrowNew(env, exclass, buf) : 0; 156 | va_end(argptr); 157 | return ret; 158 | } 159 | 160 | @() int ju_throw(JNIEnv* env, const char* message) { 161 | if (exclass) 162 | return (*env)->ThrowNew(env, exclass, message); 163 | else return 0; 164 | } 165 | -------------------------------------------------------------------------------- /src/nbukkit_impl.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | @ { 19 | #include 20 | typedef struct { 21 | NB_STATE_MEMBERS 22 | JNIEnv* env; 23 | jobject jplugin; 24 | nb_fenable enable; 25 | nb_fdisable disable; 26 | nb_fload load; 27 | char exbuf[1024]; 28 | char namebuf[128]; 29 | } nb_istate; 30 | } 31 | 32 | #define LOG_BUFSIZE 1024 33 | #define LOG_TIMEFORMAT "%H:%M:%S" 34 | 35 | static void* unsafe_jenv(nb_state* state) { 36 | return ((nb_istate*) state)->env; 37 | } 38 | 39 | static void* unsafe_jrunnable(nb_state* state, void* udata, void (*ptr) (void* udata)) { 40 | return jrn_new(((nb_istate*) state)->env, udata, ptr); 41 | } 42 | 43 | @(extern) jclass nb_jbukkit; /* org.bukkit.Bukkit */ 44 | static jmethodID id_getsched; /* static Bukkit#getScheduler() */ 45 | static jmethodID id_getsname; /* static Bukkit#getName() */ 46 | static jmethodID id_getsversion; /* static Bukkit#getVersion() */ 47 | static jmethodID id_getsbversion; /* static Bukkit#getBukkitVersion() */ 48 | 49 | static jclass class_jsched; /* BukkitScheduler singleton */ 50 | static jmethodID id_schedrepeating; /* BukkitScheduler#scheduleSyncRepeatingTask(...) */ 51 | static jmethodID id_scheddelayed; /* BukkitScheduler#scheduleSyncDelayedTask(...) */ 52 | static jmethodID id_schedrm; /* BukkitScheduler#cancelTask(id) */ 53 | 54 | static jclass class_type; /* java.lang.Class */ 55 | static jmethodID id_getname; /* Class#getName() */ 56 | static jclass throwable_type; /* java.lang.Throwable */ 57 | static jmethodID id_getmessage; /* Throwable#getMessage() */ 58 | 59 | @(extern) ju_hook nb_hooks[] = { 60 | { "org/bukkit/Bukkit", NULL, &nb_jbukkit, JU_CLASS }, 61 | { "getScheduler", "()Lorg/bukkit/scheduler/BukkitScheduler;", &id_getsched, JU_STATIC_METHOD }, 62 | { "getName", "()Ljava/lang/String;", &id_getsname, JU_STATIC_METHOD }, 63 | { "getVersion", "()Ljava/lang/String;", &id_getsversion, JU_STATIC_METHOD }, 64 | { "getBukkitVersion", "()Ljava/lang/String;", &id_getsbversion, JU_STATIC_METHOD }, 65 | 66 | { "org/bukkit/scheduler/BukkitScheduler", "J", &class_jsched, JU_CLASS }, 67 | { "scheduleSyncRepeatingTask", 68 | "(Lorg/bukkit/plugin/Plugin;Ljava/lang/Runnable;JJ)I", &id_schedrepeating, JU_METHOD }, 69 | { "scheduleSyncDelayedTask", 70 | "(Lorg/bukkit/plugin/Plugin;Ljava/lang/Runnable;J)I", &id_scheddelayed, JU_METHOD }, 71 | { "cancelTask", "(I)V", &id_schedrm, JU_METHOD }, 72 | 73 | { "java/lang/Class", NULL, &class_type, JU_CLASS }, 74 | { "getName", "()Ljava/lang/String;", &id_getname, JU_METHOD }, 75 | 76 | { "java/lang/Throwable", NULL, &throwable_type, JU_CLASS }, 77 | { "getMessage", "()Ljava/lang/String;", &id_getmessage, JU_METHOD }, 78 | 79 | JU_NULL 80 | }; 81 | 82 | static char bukkit_impl[128]; 83 | static char bukkit_ver[128]; 84 | static char bukkit_info[128]; 85 | 86 | /* there is only ever one version that Bukkit will report to us */ 87 | static char* bukkit_versions[] = { bukkit_ver, NULL }; 88 | 89 | @(extern) nb_state nb_stub = { .name = "loader" }; 90 | 91 | @(extern) nb_api nb_global_api = { 92 | .impl = bukkit_impl, .impl_versions = bukkit_versions, .impl_extra = bukkit_info, 93 | 94 | .unit = 50000000, .absolute_units = false, /* 50Hz tickrate, tied to server tickrate */ 95 | 96 | .logf = &nb_logf, .log = &nb_log, 97 | 98 | .alloc = &nb_alloc, .realloc = &nb_realloc, .free = &nb_free, 99 | 100 | .lreg = &nb_lreg, .treg = &nb_treg, .tcancel = &nb_tcancel, 101 | 102 | .unsafe = { 103 | .java_env = &unsafe_jenv, 104 | .java_runnable = &unsafe_jrunnable, 105 | .java_setex = &nb_setex 106 | } 107 | }; 108 | 109 | /* global references to singletons that are used in the bukkit API */ 110 | @(extern) jobject nb_jsched = NULL; 111 | 112 | @() void nb_initsingletons(JNIEnv* e) { 113 | nb_jsched = (*e)->NewGlobalRef(e, (*e)->CallStaticObjectMethod(e, nb_jbukkit, id_getsched)); 114 | ASSERTEX(e); 115 | jstring jimpl = (*e)->CallStaticObjectMethod(e, nb_jbukkit, id_getsname); 116 | ASSERTEX(e); 117 | jstring jver = (*e)->CallStaticObjectMethod(e, nb_jbukkit, id_getsversion); 118 | ASSERTEX(e); 119 | jstring jbver = (*e)->CallStaticObjectMethod(e, nb_jbukkit, id_getsbversion); 120 | ASSERTEX(e); 121 | 122 | if (jimpl) { 123 | const char* impl = (*e)->GetStringUTFChars(e, jimpl, NULL); 124 | strncpy(bukkit_impl, impl, 128); 125 | (*e)->ReleaseStringUTFChars(e, jimpl, impl); 126 | } else nb_global_api.impl = "NULL"; 127 | 128 | if (jver) { 129 | const char* ver = (*e)->GetStringUTFChars(e, jver, NULL); 130 | strncpy(bukkit_ver, ver, 128); 131 | (*e)->ReleaseStringUTFChars(e, jver, ver); 132 | } else nb_global_api.impl_version = "NULL"; 133 | 134 | if (jbver) { 135 | const char* bver = (*e)->GetStringUTFChars(e, jbver, NULL); 136 | strncpy(bukkit_info, bver, 128); 137 | (*e)->ReleaseStringUTFChars(e, jbver, bver); 138 | } else bukkit_versions[0] = "NULL"; 139 | } 140 | 141 | @() void nb_logf(nb_state* state, const char* format, ...) { 142 | va_list argptr; 143 | va_start(argptr, format); 144 | char buf[LOG_BUFSIZE]; 145 | time_t raw; 146 | time(&raw); 147 | struct tm spec; 148 | localtime_r(&raw, &spec); 149 | size_t off = strftime(buf, sizeof(buf) - 1, "[" LOG_TIMEFORMAT, &spec); 150 | off += snprintf(buf + off, sizeof(buf) - (off + 1), " N:%s] ", 151 | state ? state->name : "!"); 152 | off += vsnprintf(buf + off, sizeof(buf) - (off + 1), format, argptr); 153 | va_end(argptr); 154 | fputs(buf, state ? stdout : stderr); 155 | fputc('\n', state ? stdout : stderr); 156 | } 157 | 158 | @() void nb_log(nb_state* state, const char* info) { 159 | char buf[LOG_BUFSIZE]; 160 | time_t raw; 161 | time(&raw); 162 | struct tm spec; 163 | localtime_r(&raw, &spec); 164 | size_t off = strftime(buf, sizeof(buf) - 1, "[" LOG_TIMEFORMAT, &spec); 165 | off += (size_t) snprintf(buf + off, sizeof(buf) - (off + 1), " N:%s] ", 166 | state ? state->name : "!"); 167 | strncpy(buf + off, info, sizeof(buf) - (off + 1)); 168 | fputs(buf, stdout ? stdout : stderr); 169 | fputc('\n', stdout ? stdout : stderr); 170 | } 171 | 172 | @() void nb_lreg(nb_state* state, short type, short priority, void (*handle) (void*)) { 173 | nb_istate* i = (nb_istate*) state; 174 | //TODO: finish 175 | } 176 | 177 | @() void* nb_treg(nb_state* state, int delay, int period, void* udata, void (*task) (void* udata)) { 178 | nb_istate* i = (nb_istate*) state; 179 | JNIEnv* e = i->env; 180 | 181 | jobject r = jrn_new(e, udata, task); 182 | 183 | jint ret; 184 | if (period > 0) { 185 | ret = (*e)->CallIntMethod(e, nb_jsched, id_schedrepeating, i->jplugin, r, delay, period); 186 | } else { 187 | ret = (*e)->CallIntMethod(e, nb_jsched, id_scheddelayed, i->jplugin, r, delay); 188 | } 189 | CHECKEX(e, err); 190 | if (ret == -1) goto bukkit_err; 191 | return (void*) (uintptr_t) ret; /* lazy solution to return the integer result/handle as a pointer 192 | won't be an issue since sizeof(jint) >= sizeof(void*) */ 193 | err: /* unchecked exception */ 194 | nb_setex(state); 195 | return NULL; 196 | bukkit_err: /* according to the docs, Bukkit can apparently return -1 */ 197 | i->ex.type = NBEX_GENFAIL; 198 | return NULL; 199 | } 200 | 201 | @() void nb_tcancel(nb_state* state, void* handle) { 202 | nb_istate* i = (nb_istate*) state; 203 | JNIEnv* e = i->env; 204 | 205 | (*e)->CallVoidMethod(e, nb_jsched, id_schedrm, (jint) (uinxtptr_t) handle); 206 | CHECKEX(e, err); 207 | return; 208 | err: 209 | nb_setex(state); 210 | return; 211 | } 212 | 213 | /* 214 | These are currently checked wrappers for malloc/free, but ideally should be using a custom allocator 215 | with its own heap (checked mmap syscalls?) 216 | */ 217 | 218 | @() void* nb_alloc(nb_state* state, size_t size) { 219 | void* ret; 220 | if (!(ret = smalloc(size))) { 221 | nb_logf(NULL, "failed to allocate %n bytes", (int) size); 222 | ju_fatal(((nb_istate*) state)->env); 223 | } 224 | return ret; 225 | } 226 | 227 | @() void* nb_realloc(nb_state* state, void* ptr, size_t size) { 228 | void* ret; 229 | if (!(ret = realloc(ptr, size))) { 230 | nb_logf(NULL, "failed to re-allocate %n bytes at %p", (int) size, ptr); 231 | ju_fatal(((nb_istate*) state)->env); 232 | } 233 | return ret; 234 | } 235 | 236 | @() void nb_free(nb_state* state, void* ptr) { 237 | return free(ptr); 238 | } 239 | 240 | @() void nb_setex(nb_state* state) { 241 | nb_istate* i = (nb_istate*) state; 242 | JNIEnv* e = i->env; 243 | 244 | jthrowable ex = (*e)->ExceptionOccurred(e); 245 | (*e)->ExceptionClear(e); 246 | jclass type = (*e)->GetObjectClass(e, ex); 247 | ASSERTEX(e); 248 | jstring type_name = (*e)->CallObjectMethod(e, type, id_getname); 249 | ASSERTEX(e); 250 | jstring ex_reason = (*e)->CallObjectMethod(e, ex, id_getmessage); 251 | ASSERTEX(e); 252 | 253 | const char* cname = (*e)->GetStringUTFChars(e, type_name, NULL); 254 | const char* creason = (*e)->GetStringUTFChars(e, ex_reason, NULL); 255 | 256 | snprintf(i->exbuf, 1024, "%s: %s", cname, creason); 257 | 258 | (*e)->ReleaseStringUTFChars(e, type_name, cname); 259 | (*e)->ReleaseStringUTFChars(e, ex_reason, creason); 260 | 261 | i->ex.type = NBEX_INTERNAL; 262 | i->ex.reason = i->exbuf; 263 | } 264 | -------------------------------------------------------------------------------- /src/plugin.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | /* pl: JNIPlugin implementation */ 19 | 20 | #define STRING(x) #x 21 | #define MSTRING(x) STRING(x) 22 | 23 | static jclass type; 24 | static jfieldID id_internal; 25 | static jfieldID id_handle; 26 | static jfieldID id_name; 27 | static jfieldID id_plugin; 28 | 29 | @(extern) ju_hook pl_hooks[] = { 30 | { "jni/JNIPlugin", NULL, &type, JU_CLASS }, 31 | { "__internal", "J", &id_internal, JU_FIELD }, 32 | { "__handle", "J", &id_handle, JU_FIELD }, 33 | { "__name", "Ljava/lang/String;", &id_name, JU_FIELD }, 34 | { "__plugin", "Lorg/bukkit/plugin/Plugin;", &id_plugin, JU_FIELD }, 35 | JU_NULL 36 | }; 37 | 38 | JNIEXPORT JNICALL jlong Java_jni_JNIPlugin_open(JNIEnv* e, jclass type, jstring jstr) { 39 | if (!jstr) goto badarg; 40 | const char* str = (*e)->GetStringUTFChars(e, jstr, NULL); 41 | if (!str) goto badarg; 42 | void* handle; 43 | if (!(handle = dlopen(str, RTLD_LAZY))) { 44 | ju_throwf(e, "dlopen() failed: %s", dlerror()); 45 | handle = NULL; 46 | } else nb_logf(&nb_stub, "obtained handle for %s at %p", str, handle); 47 | (*e)->ReleaseStringUTFChars(e, jstr, str); 48 | return (jlong) (intptr_t) handle; 49 | badarg: 50 | ju_throw(e, "cannot make call to dlopen() with NULL argument"); 51 | return 0; 52 | } 53 | 54 | JNIEXPORT JNICALL void Java_jni_JNIPlugin_onLoad(JNIEnv* e, jobject this) { 55 | nb_istate* self = smalloc(sizeof(nb_istate)); 56 | 57 | jobject plugin = (*e)->GetObjectField(e, this, id_plugin); 58 | ASSERTEX(e); 59 | jstring jstr = (*e)->GetObjectField(e, this, id_name); 60 | ASSERTEX(e); 61 | 62 | const char* bstr = (*e)->GetStringUTFChars(e, jstr, NULL); 63 | strncpy(self->namebuf, bstr, 128); 64 | (*e)->ReleaseStringUTFChars(e, jstr, bstr); 65 | 66 | *(char**) &self->name = self->namebuf; 67 | self->env = e; 68 | self->jplugin = (*e)->NewGlobalRef(e, plugin); 69 | self->ex.type = 0; 70 | 71 | (*e)->SetLongField(e, this, id_internal, (jlong) (intptr_t) self); 72 | ASSERTEX(e); 73 | 74 | void* handle = (void*) (intptr_t) (*e)->GetLongField(e, this, id_handle); 75 | ASSERTEX(e); 76 | char* err; 77 | int compat_version = *(int*) dlsym(handle, MSTRING(NB_VERSION_SYM)); 78 | if ((err = dlerror())) 79 | goto dlsym_fail; 80 | if ((compat_version != NB_COMPAT_VERSION)) 81 | goto version_mismatch; 82 | self->enable = dlsym(handle, MSTRING(NB_ENABLE_SYM)); 83 | if ((err = dlerror())) 84 | goto dlsym_fail; 85 | self->disable = dlsym(handle, MSTRING(NB_DISABLE_SYM)); 86 | if ((err = dlerror())) 87 | goto dlsym_fail; 88 | self->load = dlsym(handle, MSTRING(NB_LOAD_SYM)); 89 | if ((err = dlerror())) 90 | goto dlsym_fail; 91 | 92 | sw_barrier(); /* aliasing */ 93 | self->load((nb_state*) self, &nb_global_api); 94 | 95 | return; 96 | dlsym_fail: 97 | nb_log(NULL, "\n\n" 98 | "Symbol resolution failed when attempting to load a plugin. This\n" 99 | "normally means the author forgot to define the required plugin\n" 100 | "hooks in his code.\n"); 101 | ju_throwf(e, "dlsym() failed (%p): %s", handle, err); 102 | return; 103 | version_mismatch: 104 | if (compat_version < NB_COMPAT_VERSION) { 105 | nb_log(NULL, "\n\n" 106 | "A native plugin failed to load because it was compiled with an\n" 107 | "older version of the NativeBukkit API. Please update the plugin\n" 108 | "or report the issue to the author.\n"); 109 | } else { 110 | nb_log(NULL, "\n\n" 111 | "A native plugin was compiled with a newer version of the NativeBukkit\n" 112 | "API, this likely means you need to update NativeBukkit.\n"); 113 | } 114 | ju_throwf(e, "failed to load plugin '%s': bad API compatibility version (got %d, expected %d)", 115 | self->name, compat_version, NB_COMPAT_VERSION); 116 | return; 117 | } 118 | 119 | JNIEXPORT JNICALL void Java_jni_JNIPlugin_onEnable(JNIEnv* e, jobject this) { 120 | nb_istate* self = (nb_istate*) (intptr_t) (*e)->GetLongField(e, this, id_internal); 121 | ASSERTEX(e); 122 | if (self) { 123 | self->enable(); 124 | } else ju_throw(e, "__internal is null"); 125 | } 126 | 127 | JNIEXPORT JNICALL void Java_jni_JNIPlugin_onDisable(JNIEnv* e, jobject this) { 128 | nb_istate* self = (nb_istate*) (intptr_t) (*e)->GetLongField(e, this, id_internal); 129 | ASSERTEX(e); 130 | if (self) { 131 | self->disable(); 132 | } else ju_throw(e, "__internal is null"); 133 | } 134 | 135 | JNIEXPORT JNICALL void Java_jni_JNIPlugin_close(JNIEnv* e, jobject this) { 136 | nb_istate* self = (nb_istate*) (intptr_t) (*e)->GetLongField(e, this, id_internal); 137 | ASSERTEX(e); 138 | void* handle = (void*) (intptr_t) (*e)->GetLongField(e, this, id_handle); 139 | ASSERTEX(e); 140 | if (handle) { 141 | if (dlclose(handle)) { 142 | ju_throwf(e, "dlclose() failed (%p): %s:", handle, dlerror()); 143 | return; 144 | } else { 145 | (*e)->SetLongField(e, this, id_handle, (jlong) 0); 146 | ASSERTEX(e); 147 | } 148 | } 149 | if (self) { 150 | free(self); 151 | (*e)->SetLongField(e, this, id_internal, (jlong) 0); 152 | ASSERTEX(e); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/runnable.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | 15 | /* jrn: java runnable */ 16 | 17 | static jclass type; 18 | static jmethodID id_init; 19 | static jfieldID id_udata; 20 | static jfieldID id_handle; 21 | 22 | @(extern) ju_hook jrn_hooks[] = { 23 | { "jni/JNIRunnable", NULL, &type, JU_CLASS }, 24 | { "", "(JJ)V", &id_init, JU_METHOD }, 25 | { "__udata", "J", &id_udata, JU_FIELD }, 26 | { "__handle", "J", &id_handle, JU_FIELD }, 27 | JU_NULL 28 | }; 29 | 30 | JNIEXPORT JNICALL void Java_jni_JNIRunnable_run(JNIEnv* env, jobject this) { 31 | void (*handle) (void*) = (void (*) (void*)) 32 | (intptr_t) (*env)->GetLongField(env, this, id_handle); 33 | void* udata = (void*) (intptr_t) (*env)->GetLongField(env, this, id_udata); 34 | handle(udata); 35 | } 36 | 37 | @() jobject jrn_new(JNIEnv* env, void* udata, void (*ptr) (void* udata)) { 38 | jobject ret = (*env)->NewObject(env, type, id_init, 39 | (jlong) (intptr_t) udata, 40 | (jlong) (intptr_t) ptr); 41 | ASSERTEX(env); 42 | return ret; 43 | } 44 | --------------------------------------------------------------------------------