├── glua.h ├── init ├── embed.lua ├── main.c ├── COPYING.txt ├── whereami.h ├── binject.h ├── test └── binject.sh ├── binject_example.cc ├── example_launcher.lua ├── Readme.md ├── binject.c ├── glua.c └── whereami.c /glua.h: -------------------------------------------------------------------------------- 1 | 2 | typedef struct lua_State lua_State; 3 | 4 | int set_self_binary_path(const char* self_path); 5 | int binject_main_app_has_internal_script(); 6 | int binject_main_app_internal_script_handle(lua_State *L, int argc, char **argv); 7 | int luaopen_glua(lua_State* L); 8 | 9 | -------------------------------------------------------------------------------- /init: -------------------------------------------------------------------------------- 1 | local root = arg[0]:match('^(.-[/\\])[^/\\]*$') or '' 2 | package.path = root .. '?.lua;' .. root .. '?/init.lua' 3 | package.cpath = root .. '?.dll;' .. root .. 'lib?.so' 4 | local p = print 5 | local r = io.read 6 | xpcall(function() 7 | require 'default_launcher' 8 | end,function(x) 9 | end) 10 | p('press return to quit') 11 | r(1) 12 | -------------------------------------------------------------------------------- /embed.lua: -------------------------------------------------------------------------------- 1 | local INITFILE = 'init' 2 | local f, err = io.open(INITFILE, 'rb') 3 | if not f or err then 4 | io.stderr:write("Can not open " .. INITFILE .. " " .. err .. "\n") 5 | return -1 6 | end 7 | local s = f:read('a') 8 | f:close() 9 | local f, err = load(s) 10 | if not f or err then 11 | io.stderr:write("Can not open " .. INITFILE .. " " .. err .. "\n") 12 | return -1 13 | end 14 | f() 15 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | 2 | #include "whereami.h" 3 | #include "glua.h" 4 | #include "binject.h" 5 | 6 | #define ERROR_EXIT 13 7 | 8 | #define luaL_openlibs(L) luaopen_glua(L) 9 | #define main lua_main 10 | #include ENABLE_STANDARD_LUA_CLI 11 | #undef main 12 | #undef luaL_openlibs(...) 13 | 14 | int main(int argc, char **argv) { 15 | 16 | // Set the binary path 17 | #ifndef USE_WHEREAMI 18 | set_self_binary_path(argv[0]); 19 | #else // USE_WHEREAMI 20 | char * exe_path = ""; 21 | int length; 22 | length = wai_getExecutablePath(0, 0, 0); 23 | char *buf = malloc(length+1); 24 | buf[0] = '\0'; 25 | if (length > 0) { 26 | exe_path = buf; 27 | int dirpathlen; 28 | wai_getExecutablePath(exe_path, length, &dirpathlen); 29 | exe_path[length] = '\0'; 30 | set_self_binary_path(exe_path); 31 | } else { 32 | set_self_binary_path(argv[0]); 33 | } 34 | #endif // USE_WHEREAMI 35 | 36 | if (binject_main_app_has_internal_script()){ 37 | // Script found: run it 38 | return binject_main_app_internal_script_handle(0, argc, argv); 39 | } else { 40 | // No script found: run lua 41 | return lua_main(argc, argv); 42 | } 43 | return ERROR_EXIT; 44 | } 45 | 46 | -------------------------------------------------------------------------------- /COPYING.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /whereami.h: -------------------------------------------------------------------------------- 1 | // (‑●‑●)> released under the WTFPL v2 license, by Gregory Pakosz (@gpakosz) 2 | // https://github.com/gpakosz/whereami 3 | 4 | #ifndef WHEREAMI_H 5 | #define WHEREAMI_H 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | #ifndef WAI_FUNCSPEC 12 | #define WAI_FUNCSPEC 13 | #endif 14 | #ifndef WAI_PREFIX 15 | #define WAI_PREFIX(function) wai_##function 16 | #endif 17 | 18 | /** 19 | * Returns the path to the current executable. 20 | * 21 | * Usage: 22 | * - first call `int length = wai_getExecutablePath(NULL, 0, NULL);` to 23 | * retrieve the length of the path 24 | * - allocate the destination buffer with `path = (char*)malloc(length + 1);` 25 | * - call `wai_getExecutablePath(path, length, NULL)` again to retrieve the 26 | * path 27 | * - add a terminal NUL character with `path[length] = '\0';` 28 | * 29 | * @param out destination buffer, optional 30 | * @param capacity destination buffer capacity 31 | * @param dirname_length optional recipient for the length of the dirname part 32 | * of the path. 33 | * 34 | * @return the length of the executable path on success (without a terminal NUL 35 | * character), otherwise `-1` 36 | */ 37 | WAI_FUNCSPEC 38 | int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length); 39 | 40 | /** 41 | * Returns the path to the current module 42 | * 43 | * Usage: 44 | * - first call `int length = wai_getModulePath(NULL, 0, NULL);` to retrieve 45 | * the length of the path 46 | * - allocate the destination buffer with `path = (char*)malloc(length + 1);` 47 | * - call `wai_getModulePath(path, length, NULL)` again to retrieve the path 48 | * - add a terminal NUL character with `path[length] = '\0';` 49 | * 50 | * @param out destination buffer, optional 51 | * @param capacity destination buffer capacity 52 | * @param dirname_length optional recipient for the length of the dirname part 53 | * of the path. 54 | * 55 | * @return the length of the module path on success (without a terminal NUL 56 | * character), otherwise `-1` 57 | */ 58 | WAI_FUNCSPEC 59 | int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length); 60 | 61 | #ifdef __cplusplus 62 | } 63 | #endif 64 | 65 | #endif // #ifndef WHEREAMI_H 66 | -------------------------------------------------------------------------------- /binject.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _BINJECT_H_ 3 | #define _BINJECT_H_ 4 | 5 | #include 6 | 7 | // -------------------------------------------------------------------------- 8 | 9 | typedef enum { 10 | NO_ERROR = 0, 11 | GENERIC_ERROR = -1, 12 | ACCESS_ERROR = -2, 13 | INVALID_RESOURCE_ERROR = -3, 14 | SIZE_ERROR = -4, 15 | INVALID_DATA_ERROR = -5, 16 | } binject_error_t; 17 | 18 | // -------------------------------------------------------------------------- 19 | // Handle custom struct static data 20 | 21 | // e.g. 22 | // BINJECT_STATIC("`````", double, the_data, 3.14); 23 | // ... { ... 24 | // double * content = (double*) binject_data(the_data); 25 | 26 | typedef struct binject_static_s binject_static_t; 27 | 28 | // This MUST be instantiated as the STATIC values (i.e. global/top-level) 29 | // T: in-file mark (string literal) 30 | // S: type of the content data 31 | // N: identifier of the binject_static_t * variable that will be defined 32 | // V: initialization data as C struct literal 33 | #define BINJECT_STATIC_DATA(T, S, N, V) \ 34 | typedef struct { \ 35 | unsigned int tag_size; \ 36 | unsigned int content_size; \ 37 | unsigned int content_offset; \ 38 | char start_tag[sizeof(T)]; \ 39 | S content; \ 40 | } N ## _t; \ 41 | static N ## _t N ## _istance = { \ 42 | .tag_size = sizeof(T), \ 43 | .content_size = sizeof(S), \ 44 | .content_offset = offsetof(N ## _t, content),\ 45 | .start_tag = T, \ 46 | .content = V, \ 47 | }; \ 48 | binject_static_t * N = (binject_static_t*) & N ## _istance 49 | 50 | // ------------------------------------------------------------------------- 51 | // Handle custom static char array 52 | 53 | // e.g. 54 | // BINJECT_STATIC_STRING("`````", 256, the_data); 55 | // ... { ... 56 | // unsigned int size; 57 | // char * content = binject_info(the_data, &size); 58 | 59 | // This MUST be instantiated as the STATIC values (i.e. global/top-level) 60 | // T: in-file mark (string literal) 61 | // S: size of the char[] content 62 | // N: identifier of the binject_static_t * variable that will be defined 63 | #define BINJECT_STATIC_STRING(T, S, N) \ 64 | typedef struct { \ 65 | unsigned int tail_position; \ 66 | unsigned int len; \ 67 | unsigned int max; \ 68 | char raw[S]; \ 69 | } N ## _inner_t; \ 70 | BINJECT_STATIC_DATA(T, N ## _inner_t, N, {.max = S}) 71 | 72 | // ------------------------------------------------------------------------- 73 | // API functions for Read 74 | 75 | void * binject_data(binject_static_t * ds); 76 | char * binject_get_static_script(binject_static_t * DS, unsigned int * script_size, unsigned int * file_offset); 77 | int binject_get_tail_script(binject_static_t * DS, const char * self_path, char * buffer, unsigned int size, unsigned int offset); 78 | 79 | // ------------------------------------------------------------------------- 80 | // API functions for Write 81 | 82 | int binject_duplicate_binary(binject_static_t * DS, const char * self_path, const char * destination_path); 83 | int binject_step(binject_static_t * DS, const char * destination_path, const char * data, unsigned int r); 84 | 85 | // ------------------------------------------------------------------------- 86 | 87 | #endif // _BINJECT_H_ 88 | 89 | -------------------------------------------------------------------------------- /test/binject.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Running some tests (note: error states are not currently tested)." 4 | 5 | ############################################################# 6 | # Prepare directory 7 | 8 | # Linux 9 | # SHEXT="so" 10 | # CC_ARCH_FLAG="-fPIC" 11 | 12 | # Windows 13 | SHEXT="dll" 14 | CC_ARCH_FLAG="" 15 | 16 | TEST_DIR="$(readlink -f "$(dirname "$0")")/tmp" 17 | CC="gcc -std=c99 -Wall $CC_ARCH_FLAG " 18 | 19 | rm -fR "$TEST_DIR" 20 | mkdir "$TEST_DIR" 21 | cd "$TEST_DIR" 22 | 23 | ############################################################# 24 | # Compile static 25 | 26 | $CC -o ./array_static.exe ../../*.c 27 | strip ./array_static.exe 28 | 29 | $CC -D'BINJECT_ARRAY_SIZE=3' -o ./tail_static.exe ../../*.c 30 | strip ./tail_static.exe 31 | 32 | cp ./tail_static.exe ./tail_static_bis.exe 33 | 34 | ############################################################# 35 | # Compile shared 36 | 37 | $CC -shared -fPIC -o ./libbinject_array.$SHEXT ../../binject.c || exit -1 38 | $CC -o ./array_shared.exe ../../example.c -L ./ -lbinject_array || exit -1 39 | strip ./libbinject_array.$SHEXT 40 | strip ./array_shared.exe 41 | 42 | cp ./array_shared.exe ./array_shared_bis.exe 43 | 44 | export LD_LIBRARY_PATH="./" 45 | 46 | ############################################################# 47 | # Test working 48 | 49 | SEDSIZCOL='s:^[^ ]* *[^ ]* *[^ ]* *[^ ]* *[^ ]* *\([^ ]*\) *.*$:\1:g' 50 | 51 | should_be() { 52 | if [ "$2" = "=" -a "$1" = "$3" ] ; then return ; fi 53 | if [ "$2" = "!=" -a "$1" != "$3" ] ; then return ; fi 54 | echo "TEST FAILS ! EXPECTING >>>" 55 | echo "$1" 56 | echo "<<< TO BE $2 TO >>>" 57 | echo "$3" 58 | echo "<<<" 59 | exit -1 60 | } 61 | 62 | test_working_sequence(){ 63 | TEXT="hello world $1" 64 | 65 | # create text to embed 66 | echo "$TEXT" > ./"$1".txt || exit -1 67 | 68 | # embed the text 69 | echo "------> $1" 70 | ./"$1" ./"$1".txt || exit -1 71 | mv injed.exe ./"$1".emb || exit -1 72 | chmod ugo+x ./"$1".emb || exit -1 73 | 74 | # run the app with the embedded text 75 | echo "------> $1.emb" 76 | ./"$1".emb > ./"$1".rpt || exit -1 77 | 78 | # check output 79 | LEN="+$TEXT" 80 | LEN="${#LEN}" 81 | RES=$(cat ./"$1".rpt | tail -n 2 | head -n 1 | sed 's:^ A .. ::') 82 | EXP="A $LEN byte script was found (dump:)[$TEXT" 83 | should_be "$EXP" = "$RES" 84 | 85 | echo "------" 86 | } 87 | 88 | test_working_sequence array_static.exe 89 | test_working_sequence tail_static.exe 90 | test_working_sequence tail_static_bis.exe 91 | test_working_sequence array_shared.exe 92 | test_working_sequence array_shared_bis.exe 93 | 94 | ############################################################# 95 | # Test Array / Tail method trhough exe size 96 | 97 | ls -l > my_size_info.txt 98 | grep '.exe.emb$' my_size_info.txt 99 | 100 | ARRAY=$(grep 'array_shared.exe.emb$' my_size_info.txt | sed "$SEDSIZCOL") 101 | ARRAYBIS=$(grep 'array_shared_bis.exe.emb$' my_size_info.txt | sed "$SEDSIZCOL") 102 | 103 | # If the array method was chosen, the size of the exe is always the same 104 | should_be "$ARRAY" = "$ARRAYBIS" 105 | 106 | TAIL=$(grep 'tail_static.exe.emb$' my_size_info.txt | sed "$SEDSIZCOL") 107 | TAILBIS=$(grep 'tail_static_bis.exe.emb$' my_size_info.txt | sed "$SEDSIZCOL") 108 | 109 | # If the tail method was chosen, the size of the exe depend on the embeded script 110 | should_be "$TAIL" != "$TAILBIS" 111 | 112 | ############################################################# 113 | # Test shared 114 | 115 | rm *.$SHEXT 116 | ./array_shared.exe.emb > array_shared.exe.empty.rpt 2> ./array_shared.exe.error.rpt 117 | 118 | RES=$(cat array_shared.exe.empty.rpt) 119 | should_be "" = "$RES" 120 | 121 | ############################################################# 122 | # Print succesfull summary 123 | 124 | echo "ALL RIGHT" 125 | 126 | -------------------------------------------------------------------------------- /binject_example.cc: -------------------------------------------------------------------------------- 1 | 2 | #include "binject.h" 3 | #include 4 | #include 5 | #include 6 | #include "unistd.h" 7 | 8 | // Size of the data for the INTERNAL ARRAY mechanism. It should be 9 | // a positive integer 10 | #ifndef BINJECT_ARRAY_SIZE 11 | #define BINJECT_ARRAY_SIZE (9216) 12 | #endif // BINJECT_ARRAY_SIZE 13 | 14 | static void error_report(int internal_error) { 15 | if (internal_error != NO_ERROR) 16 | fprintf(stderr, "Error %d\n", internal_error); 17 | if (0 != errno) 18 | fprintf(stderr, "Error %d: %s\n", errno, strerror(errno)); 19 | } 20 | 21 | BINJECT_STATIC_STRING("```replace_data```", BINJECT_ARRAY_SIZE, static_data); 22 | 23 | static int aux_print_help(const char * command){ 24 | printf("\nUsage:\n %s script.txt\n\n", command); 25 | printf("script.txt.exe executable will be generated or overwritten.\n"); 26 | printf("script.txt.exe will print to the stdout an embedded copy of script.txt.\n\n"); 27 | printf("NOTE: depending on the chosen embedding mechanism, some help information will be\n"); 28 | printf("appended at end of script.txt.exe.\n"); 29 | return 0; 30 | } 31 | 32 | static char * aux_script_prepare(char * buf, int * off, int * siz){ 33 | *off = *siz; // buffer processing finished 34 | return buf; 35 | } 36 | 37 | static int aux_script_run(const char * scr, int size, int argc, char ** argv){ 38 | // Script echo 39 | printf("A %d byte script was found (dump:)[", size); 40 | int w = fwrite(scr, 1, size, stdout); 41 | if (w != size) return ACCESS_ERROR; 42 | printf("]\n"); 43 | return 0; 44 | } 45 | 46 | // -------------------------------------------------------------------- 47 | 48 | static int binject_main_app_internal_script_inject(binject_static_t * info, const char * scr_path, const char* bin_path, const char * outpath){ 49 | int result = ACCESS_ERROR; 50 | 51 | // Open the scipt 52 | FILE * scr = fopen(scr_path, "rb"); 53 | if (!scr) goto end; 54 | 55 | // Get the original binary size 56 | if (fseek(scr, 0, SEEK_END)) goto end; 57 | 58 | { // Scope block to avoid goto and variable length issue 59 | int siz = ftell(scr); 60 | if (siz < 0) goto end; 61 | if (fseek(scr, 0, SEEK_SET)) goto end; 62 | 63 | int bufsize = siz; 64 | int off = 0; 65 | char buf[bufsize]; 66 | 67 | // Copy the binary 68 | result = binject_duplicate_binary(info, bin_path, outpath); 69 | if (NO_ERROR != result) goto end; 70 | 71 | // Prepare the script for the injection 72 | if (0> fread(buf, 1, siz, scr)) goto end; 73 | while (off >=0 && off < siz) { 74 | char * injdat = buf; 75 | injdat = aux_script_prepare(injdat, &off, &siz); 76 | 77 | // Inject the partial script and update static info into the binary 78 | result = binject_step(info, outpath, injdat, siz); 79 | if (NO_ERROR != result) goto end; 80 | } 81 | } 82 | 83 | end: 84 | error_report(0); 85 | if (scr) fclose(scr); 86 | return result; 87 | } 88 | 89 | static int binject_main_app_internal_script_handle(binject_static_t * info, const char* bin_path, int argc, char **argv) { 90 | unsigned int size; 91 | unsigned int offset; 92 | 93 | // Get information from static section 94 | char * script = binject_get_static_script(info, &size, &offset); 95 | 96 | if (script) { 97 | // Script found in the static section 98 | return aux_script_run(script, size, argc, argv); 99 | 100 | } else { 101 | // Script should be at end of the binary 102 | unsigned int script_size = binject_get_tail_script(info, bin_path, 0, 0, offset); 103 | char buf[script_size]; 104 | binject_get_tail_script(info, bin_path, buf, script_size, offset); 105 | return aux_script_run(buf, script_size, argc, argv); 106 | } 107 | 108 | return NO_ERROR; 109 | } 110 | 111 | int main(int argc, char **argv) { 112 | int result = GENERIC_ERROR; 113 | 114 | // Get information from static section 115 | unsigned int size = 0; 116 | unsigned int offset = 0; 117 | binject_get_static_script(static_data, &size, &offset); 118 | 119 | // Run the proper tool 120 | if (size > 0 || offset > 0) { 121 | // Script found: handle it 122 | result = binject_main_app_internal_script_handle(static_data, argv[0], argc, argv); 123 | 124 | } else if (argc < 2 || argv[1][0] == '\0') { 125 | // No arguments: print help 126 | aux_print_help(argv[0]); 127 | result = NO_ERROR; 128 | 129 | } else { 130 | // No script found: inject 131 | if (argc < 2) { aux_print_help(argv[0]); goto end; } 132 | result = binject_main_app_internal_script_inject(static_data, argv[1], argv[0], "injed.exe"); 133 | } 134 | 135 | end: 136 | if (result != NO_ERROR) fprintf(stderr, "Error %d\n", result); 137 | error_report(0); 138 | return result; 139 | } 140 | 141 | -------------------------------------------------------------------------------- /example_launcher.lua: -------------------------------------------------------------------------------- 1 | 2 | --[[DOC 3 | 4 | A more complex example to be embedded with linject, e.g. into luancher.exe. It 5 | is a lua script runner that handle some non-trivial cases. 6 | 7 | luancher.exe will set the lua package.path and cpath so that lua will load all 8 | the .lua and .dll files in the same directory (of luancher.exe). Then it will 9 | try to execute two lua script. 10 | 11 | The first script is config.lua in the same directory. Its purpose is to 12 | configure global variables, so it is run in a sandbox environment just contain 13 | - the variable 'this_directory' containing the obsolute path of the directory 14 | containing config.lua file with the links expanded 15 | - the following standard lua globals: assert, coroutine, error, os.getenv, 16 | ipairs, math, next, pairs, pcall, string, table, tonumber, tostring, type, 17 | unpack, xpcall. 18 | - the 'chainload' global function that will load another lua script in the same 19 | environment (it takes the path to the file as the argument) 20 | 21 | If the config.lua file is not found luancher.exe will not raise any error. 22 | 23 | The second script is the first match of the following list: 24 | - the main.lua file in the same directory of the executable 25 | - the .lua file with same name in the same directory of the executable (e.g. 26 | luancher.lua) 27 | - the first argument ONLY IF it has the .lua extension 28 | - the file handle_e_YYY.XXX.lua in the same directory of luancher.exe, where 29 | YYY is the laucher name (e.g. luancher) and .XXX is the extension of the 30 | file passed as first argument (can be empty) 31 | - the file handler_n_YYY.XXX.lua where YYY is the laucher name (e.g. luancher) 32 | and XXX is the first argument ONLY IF the first argument has no path and no 33 | extension 34 | 35 | It is run in the full lua environment and if it is not found luancher will 36 | print an error message containing all the script tryed. 37 | 38 | Whatever script is run, it will receve the following arguments: 39 | same arguments descibed in the 40 | luaject section but with arg[0] replaced by 41 | 42 | - arg[-1] - command line path to the executable (the C argv[0]) with links resolved ( ??? TODO : check this !!! ) 43 | - arg[0] - the script that was chosen (absolute path with links resolved) 44 | - arg[N] - (N>0) are the rest of command line arguments 45 | 46 | Note that the directory containing the launcher, that is used for package.path 47 | and cpath also, is the one passed by luamain.o as arg[-1] i.e. has all the 48 | filesystem link resolved. So, for example, if you have 49 | 50 | ``` 51 | ... 52 | /bin/luancher.exe 53 | /bin/luancher.lua 54 | /bin/module.lua 55 | /module.lua 56 | /luancher.lua 57 | /luancher.exe -> my_project/bin/luancher.exe 58 | ... 59 | ``` 60 | 61 | and /bin/luancher.lua contains 62 | 63 | ``` 64 | local ex = require 'module' 65 | ``` 66 | 67 | while /luancher.lua is empty, launching /luancher.exe will 68 | results in the loading of /bin/luancher.lua (not /luancher.lua) and then it will 69 | require /bin/module.lua (not /module.lua) 70 | 71 | Note that you can find tools similar to luancher around the web, e.g. 72 | [l-bia](http://l-bia.sourceforge.net) (but I never tryed it). 73 | 74 | Why all this words for such small tool? I changed a lot of times the way this 75 | software work. So I just needed to FREEZE all, and print my thought somewhere 76 | for the me of the future. :) 77 | 78 | ]] 79 | 80 | if type(arg[-1]) ~= 'string' then return nil end 81 | 82 | function path_split(str) 83 | local p = str:match('(.*[/\\])[^/\\]*') 84 | local n = str:match('([^/\\]*)$'):gsub('%.[^/\\%.]*$','') 85 | local e = str:match('%.[^/\\%.]*$') 86 | if not p then p = '' end 87 | if not n then n = '' end 88 | if not e then e = '' end 89 | return p,n,e 90 | end 91 | 92 | local realpath, progname, extension = path_split(whereami) 93 | package.path = realpath .. '?.lua;' .. realpath .. '?/init.lua' 94 | package.cpath = realpath .. '?.dll;' .. realpath .. 'lib?.so' 95 | 96 | local chainload = (function() 97 | local sandbox = { 98 | --print = print, 99 | assert = assert, coroutine = coroutine, error = error, 100 | os = {getenv = os.getenv}, ipairs = ipairs, math = math, next = next, 101 | pairs = pairs, pcall = pcall, string = string, table = table, 102 | tonumber = tonumber, tostring = tostring, type = type, unpack = unpack, 103 | xpcall = xpall, 104 | } 105 | sandbox.chainload = function (file) 106 | sandbox.this_directory = file:gsub('[/\\][^/\\]*$','') 107 | local t = io.open(file) 108 | if t then 109 | t:close() 110 | loadfile(file,'t',sandbox)() 111 | end -- missing config is not an error ! 112 | return sandbox 113 | end 114 | return sandbox.chainload 115 | end)() 116 | 117 | local conf = chainload(realpath..'config.lua') 118 | -- CONF keys are merged into global when there is no conflict. 119 | -- Conflicting keys are avaiable in the CONFLICT global 120 | for k,v in pairs(conf) do 121 | if _G[k] == nil then 122 | _G[k] = v 123 | else 124 | if not CONFLICT then CONFLICT = {} end 125 | CONFLICT[k] = v 126 | end 127 | end 128 | 129 | local script_list = {} 130 | function script_list:append(s) script_list[1+#(script_list)]=s end 131 | 132 | script_list:append(realpath .. 'main.lua') 133 | script_list:append(realpath .. progname .. '.lua') 134 | if arg[1] then 135 | if ae == '.lua' then 136 | script_list:append (arg[1]) 137 | end 138 | local ap,an,ae = path_split(arg[1]) 139 | script_list:append (realpath .. 'handle_e_' .. progname .. ae .. '.lua') 140 | if ap == '' and ae == '' and an ~= '' then 141 | script_list:append (realpath .. 'handle_n_' .. progname .. '.' .. an .. '.lua') 142 | end 143 | end 144 | 145 | for _,s in ipairs(script_list) do 146 | local file = io.open(s,'rb') 147 | if file then 148 | file:close() 149 | arg[0] = s 150 | local chunk,err = loadfile(arg[0]) 151 | if not chunk then 152 | error('error loading file '..arg[0]..':\n'..err) 153 | end 154 | return chunk() 155 | end 156 | end 157 | 158 | local err = '' 159 | if #(script_list) == 0 then 160 | err = ' None' 161 | else 162 | for _,s in ipairs(script_list) do 163 | err = err .. '\n ' .. s 164 | end 165 | end 166 | error('Can not find the script to launch. Tryed: '..err) 167 | 168 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | Glua 3 | ===== 4 | 5 | This utility generates single binary file containing both the lua runtime with 6 | some utility functions, and optionally a script to run. You can compile it 7 | upon the standard lua or luajit, as well as any C implementation of the lua 8 | API. 9 | 10 | It is tested with lua 5.4.0, but older version should work too. 11 | 12 | Built packages for linux, windows and mac can be found in [lua static 13 | battery](http://github.com/pocomane/lua_static_battery). 14 | 15 | This software is released under the [Unlicense](http://unlicense.org), so the 16 | legal statement in COPYING.txt applies, unless differently stated in individual 17 | files. 18 | 19 | Usage 20 | ------ 21 | 22 | If no script is embed in the binary, it behaves like a standard lua command 23 | line application, with some library added. If a script is embeded, it is run 24 | instead of the standard lua command line application. By default it has 25 | no script embeded. 26 | 27 | The embeded funcitons can be accessed with `require "whereami"`, that is a 28 | function that returns the path to the executable, and `require "glua_pack"`. 29 | The latter is a function that takes two arguments: a script to embed an a path. It 30 | copies whole application in the path and embeds the script in it. 31 | 32 | So, for example, you can generate an executable that embeds the `test.lua` script in it, 33 | and execute it when launched, with the following one liner: 34 | 35 | ``` 36 | echo "print'hello world!" > hello_world.lua 37 | 38 | ./glua.exe -e 'require"glua_pack"("test.lua", "glued.exe")' 39 | 40 | chmod ugo+x glued.exe 41 | ./glued.exe 42 | ``` 43 | 44 | The newly created `glued.exe` contains the script. Launching it will diplay the 45 | message `hello world!` in the console. You can simplify its command line with: 46 | 47 | ``` 48 | echo "require"glua_pack"(argv[1], "glued.exe")" > test.lua 49 | ./glua.exe -e 'require"glua_pack"("test.lua", "gluasimple.exe")' 50 | chmod ugo+x gluasmple.exe 51 | ``` 52 | 53 | This `gluasimple.exe` can generate new executables simply with: 54 | 55 | ``` 56 | ./glua2.exe test.lua 57 | ``` 58 | 59 | (or drag `hello_world.lua` on `glua2.exe`). 60 | 61 | Please, be aware that glua.exe is (deliberately) an extremly simple tool. It 62 | does not try to reduce size, or to embed other lua modules. For such advanced 63 | operation you can use something like [lua squish](http://matthewwild.co.uk/projects/squish/home) and then use glua.exe 64 | on the resulting file. 65 | 66 | There are other tools that archive somehow the same result of glua: 67 | - [bin2c](https://sourceforge.net/p/wxlua/svn/217/tree/trunk/wxLua/util/bin2c/bin2c.lua) 68 | converts the lua bytestream in lua C API call ready to be compiled. 69 | - [srlua](http://webserver2.tecgraf.puc-rio.br/~lhf/ftp/lua/#srlua) paste the 70 | script at end of the exe, and at run-time open the exe searching for the 71 | script. 72 | 73 | Build 74 | ------ 75 | 76 | There is no actual build system. You can compile it with gcc using: 77 | 78 | ``` 79 | gcc -I . -o glua.exe *.c lua_lib -lm -ldl 80 | ``` 81 | 82 | This assumes that you have copied the lua headers in the current directoy and 83 | the binary library (static or shared) in the `lua_lib file`. For windows you can 84 | add `-D_WIN32_WINNT=0x0600`, and `-mconsole` or `-mwindows` (depending on if 85 | you want or not a console to appear at start of the application). 86 | 87 | // TODO : document gcc linker ORIGIN 88 | 89 | Build options 90 | -------------- 91 | 92 | This library must be linked statically in the executable. 93 | 94 | If the compilation flag `USE_WHEREAMI` is enabled, the embedded `whereami` 95 | library will use some system dependent code to guess where the binary is. 96 | Otherwise it will use the first command line argument. 97 | 98 | If you define `ENABLE_STANDARD_LUA_CLI` pointing the lua `lua.c`, it will be 99 | included in the `glua.exe`/`glued.exe` binaries. This enable to run the 100 | standard lua interpreter when `--lua` is passed as the LAST argument to 101 | `glua.exe` or `glued.exe`. Please note that the macro definition must begin and 102 | end `"`, e.g. `gcc -DENABLE_STANDARD_LUA_CLI='"/path/tp/lua.c"' ...` 103 | 104 | The code that actually embed and extract the script is [binject](#Binject), so 105 | refer to its [documentation](#Binject working) for additional options. 106 | 107 | Link extra modules 108 | ------------------- 109 | 110 | To embed extra C modules in `glua.exe`, just call `luaL-openlibs`-like function 111 | fron `preload.c`. 112 | 113 | // TODO : multiple script from command line -> include wrapping in a 114 | require-able enclosure 115 | 116 | luancher 117 | --------- 118 | 119 | The command 120 | 121 | ``` 122 | ./glua.exe embed.lua 123 | ``` 124 | 125 | will generate int `glued.exe` a minimal lua script launcher. It simply load the 126 | `init` file in its same directory, and run it as a lua script/bytecode. The 127 | script will have access to the common lua globals. Moreover the libraries 128 | defined in [preload.c](preload.c) are embedded. 129 | 130 | This tool is usefull while developing, when you are ready to deploy, you can 131 | embed your script directly in a executable by means of `glua.exe`. In this 132 | phase, if you have some issue with the package.path and cpath, just copy the 133 | ones you found on the top of the default_launcher.lua (that is the script 134 | embeded in luancher). 135 | 136 | example_launcher 137 | ----------------- 138 | 139 | A more complex lua script launcher is in the `example_laucher.lua`. Open it for 140 | some documentation. 141 | 142 | preload.c 143 | ---------- 144 | 145 | The `preload.c` defines what lua libraries are embedded into the executables. To 146 | add new C-modules, you just need to call their standard `loaopen_` function from 147 | the `preaload_all` one. 148 | 149 | Note: pure lua modules can be preloaded in the same way, wrapping them in a C 150 | `luaopen_` function that just do a `luaL_loadbuffer` plus a `lua_call`. 151 | 152 | By default just the `whereami` library is loaded, it can be called with 153 | `local path = require'whereami'()`. 154 | 155 | Binject 156 | -------- 157 | 158 | The code that actually embed the script in the executable is in the 159 | `binject.c/h` files. It is designed as a "Library" so you can use them alone 160 | without lua or anything else. You just need to provide the parsing function or 161 | manipulate the data before the injection. 162 | 163 | A complete but simple "Echo" example is provided for reference. It can be 164 | compiled with 165 | 166 | ``` 167 | gcc -std=c99 -o binject.exe binject.c binject_example.cc 168 | ``` 169 | 170 | As it is, this will compile the "Echo" example. For some customization, read 171 | the 'Binject working' section. 172 | 173 | When called without argument, some help information will be printed. To embed a 174 | script pass it as argument. 175 | 176 | ``` 177 | ./binject.exe my_script 178 | ``` 179 | 180 | This will generate the file injed.exe. Then: 181 | 182 | ``` 183 | echo "hello world" > my_text.txt 184 | ./binject.exe my_text.txt 185 | rm my_text.txt 186 | ./injed.exe 187 | ``` 188 | 189 | will print "hello world" to the screen. 190 | 191 | In the test directory there is a test that will execute all the commands seen 192 | before. At end it will also check that the output of the example app is the 193 | expected one. 194 | 195 | Binject working 196 | ---------------- 197 | 198 | Two methods are avaiable to embed the script. By default, the "Array" method 199 | will be tryed first, and if the script is too big, it will fallback to the 200 | "Tail" method. 201 | 202 | In the "Array" method the script will overwrite the initialization data of a 203 | static struct. 204 | 205 | In the "Tail" method the script will be appended at end of the 206 | executable, and in the static struct will be kept only the informations 207 | about where the script begin. With this method you can edit you script 208 | directly in the exectuable. 209 | 210 | The applications using `binject` can be configured at compile time by means of 211 | the following definitions. 212 | 213 | `BINJECT_ARRAY_SIZE` - Size of the data for the INTERNAL ARRAY 214 | mechanism. It should be a positive integer. If you put this value to 0, 215 | you can actually force to always use the tail method. The default is 216 | 9216 byte. 217 | 218 | -------------------------------------------------------------------------------- /binject.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "binject.h" 7 | 8 | // -------------------------------------------------------------------------- 9 | 10 | struct binject_static_s { 11 | unsigned int tag_size; 12 | unsigned int content_size; 13 | unsigned int content_offset; 14 | char start_tag[1]; 15 | // .content considered inside the previous Flexible Array Member 16 | }; 17 | 18 | typedef struct { 19 | unsigned int tail_position; 20 | unsigned int len; 21 | unsigned int max; 22 | char raw[1]; 23 | } binject_data_t; 24 | 25 | static long int binject_find_last_tag_byte(FILE* f, const char* tag, size_t tagsize){ 26 | int c = '\n'; 27 | int count = 0; 28 | long match = 0; 29 | 30 | long int result = -1; 31 | if (tagsize <= 0) return result; 32 | 33 | while (!match) { 34 | 35 | c = fgetc(f); 36 | if (c == EOF) break; 37 | 38 | if (c != tag[count]) count = 0; 39 | else count += 1; 40 | 41 | if (count >= tagsize) { 42 | match = 1; 43 | break; 44 | } 45 | } 46 | 47 | if (match) result = ftell(f); 48 | if (result < 0) result = ACCESS_ERROR; 49 | 50 | return result; 51 | } 52 | 53 | static long int binject_find_static_data(binject_static_t * ds, FILE* file){ 54 | return binject_find_last_tag_byte(file, ds->start_tag, ds->tag_size) 55 | - ds->tag_size - offsetof(binject_static_t, start_tag); 56 | } 57 | 58 | static unsigned int container_size(binject_static_t * ds) { 59 | return ds->content_offset + ds->content_size; 60 | } 61 | 62 | static binject_error_t binject_write_data(binject_static_t * ds, FILE * file, long int position){ 63 | long int result = NO_ERROR; 64 | 65 | if (0 != fseek(file, position, SEEK_SET)) result = ACCESS_ERROR; 66 | if (result == NO_ERROR) 67 | if (container_size(ds) != fwrite(ds, 1, container_size(ds), file)) 68 | result = ACCESS_ERROR; 69 | 70 | return result; 71 | } 72 | 73 | static int binject_tail_append(unsigned int * pos, const char * self_path, const char * data, unsigned int size){ 74 | FILE * f = fopen(self_path, "r+b"); 75 | if (!f) return ACCESS_ERROR; 76 | 77 | if (fseek(f, 0, SEEK_END)) goto err; 78 | if (pos && *pos == 0) { 79 | *pos = ftell(f); 80 | if (*pos < 0) goto err; 81 | } 82 | 83 | if (size != fwrite(data, 1, size, f)) 84 | goto err; 85 | 86 | fclose(f); 87 | return NO_ERROR; 88 | 89 | err: 90 | fclose(f); 91 | return ACCESS_ERROR; 92 | } 93 | 94 | static long int binject_read_static_data(const char * path, binject_static_t * ds){ 95 | FILE * file = fopen(path, "rb"); 96 | if (!file) return ACCESS_ERROR; 97 | 98 | long int position = binject_find_static_data(ds, file); 99 | if (0 != fseek(file, position, SEEK_SET)) { 100 | fclose(file); 101 | return ACCESS_ERROR; 102 | } 103 | 104 | long int result = fread(ds, 1, container_size(ds), file); 105 | fclose(file); 106 | return result; 107 | } 108 | 109 | static binject_error_t binject_inject(binject_static_t * ds, const char * path){ 110 | 111 | FILE * file = fopen(path, "r+b"); 112 | if (!file) return ACCESS_ERROR; 113 | 114 | long int position = binject_find_static_data(ds, file); 115 | 116 | binject_error_t result = INVALID_RESOURCE_ERROR; 117 | if (position > 0) result = binject_write_data(ds, file, position); 118 | fclose(file); 119 | 120 | return result; 121 | } 122 | 123 | static void binject_use_tail(binject_static_t * DS) { 124 | binject_data_t * toinj = (binject_data_t *)binject_data(DS); 125 | toinj->max = 0; 126 | } 127 | 128 | static int binject_does_use_tail(binject_static_t * DS) { 129 | binject_data_t * toinj = (binject_data_t *)binject_data(DS); 130 | if (toinj->max > 0) return 0; 131 | return 1; 132 | } 133 | 134 | // -------------------------------------------------------------------- 135 | 136 | void * binject_data(binject_static_t * ds){ 137 | return ((char*)ds) + ds->content_offset; 138 | } 139 | 140 | char * binject_get_static_script(binject_static_t * DS, unsigned int * script_size, unsigned int * file_offset){ 141 | 142 | if (script_size) *script_size = 0; 143 | if (file_offset) *file_offset = 0; 144 | 145 | binject_data_t * data = (binject_data_t*) binject_data(DS); 146 | if (binject_does_use_tail(DS)) { 147 | 148 | if (file_offset) *file_offset = data->tail_position; 149 | return NULL; 150 | 151 | } else { 152 | if (script_size) *script_size = data->len; 153 | return data->raw; 154 | } 155 | } 156 | 157 | int binject_get_tail_script(binject_static_t * DS, const char * self_path, char * buffer, unsigned int size, unsigned int offset){ 158 | 159 | // Open file 160 | FILE * f = fopen(self_path, "rb"); 161 | if (!f) return ACCESS_ERROR; 162 | if (0 != fseek(f, offset, SEEK_SET)) return ACCESS_ERROR; 163 | 164 | // Read data 165 | int actread = fread(buffer, 1, size, f); 166 | if (0> actread && actread != size) return ACCESS_ERROR; 167 | 168 | // Calc remaining bytes 169 | if (0 != fseek(f, 0, SEEK_END)) return ACCESS_ERROR; 170 | int result = ftell(f) - offset - actread; 171 | 172 | // TODO : close file on error ? it could override errno ! 173 | fclose(f); 174 | return result; 175 | } 176 | 177 | // -------------------------------------------------------------------- 178 | 179 | int binject_duplicate_binary(binject_static_t * DS, const char * self_path, const char * destination_path){ 180 | int r = 0; 181 | int w = 0; 182 | char b[128]; 183 | unsigned int stop = ( (binject_data_t *) binject_data(DS) ) -> tail_position; 184 | 185 | // Open files 186 | FILE * fs = fopen(self_path, "rb"); 187 | if (!fs) return ACCESS_ERROR; 188 | FILE * fd = fopen(destination_path, "wb"); 189 | if (!fd) return ACCESS_ERROR; 190 | 191 | // Copy from source file to destination 192 | unsigned int tot = 0; 193 | while (tot <= stop) { 194 | r = fread(b, 1, sizeof(b), fs); 195 | if (0 > r) break; 196 | if (stop > 0 && tot + r > stop) r = stop - tot; // Do not copy the possible final script: it must be injected again if needed. 197 | w = fwrite(b, 1, r, fd); 198 | if (r != sizeof(b) || r != w) break; 199 | } 200 | 201 | // Error report 202 | if (r != w) return ACCESS_ERROR; 203 | 204 | // TODO : close file on error ? it could override errno ! 205 | fclose(fd); 206 | fclose(fs); 207 | 208 | // Clear the static data section 209 | binject_static_t *clean_static_data = (binject_static_t *) malloc(container_size(DS)); 210 | memcpy(clean_static_data, DS, container_size(DS)); 211 | binject_data_t *clean_content = (binject_data_t *)binject_data(clean_static_data); 212 | clean_content->len = 0; 213 | memset(clean_content->raw, 0, clean_content->max); 214 | binject_inject(clean_static_data, destination_path); 215 | free(clean_static_data); 216 | 217 | return NO_ERROR; 218 | } 219 | 220 | int binject_step(binject_static_t * DS, const char * destination_path, const char * data, unsigned int r){ 221 | 222 | // Using the static data FROM the target binary 223 | binject_static_t * ds = (binject_static_t *) malloc(container_size(DS)); 224 | memcpy(ds, DS, container_size(DS)); 225 | binject_read_static_data(destination_path, ds); 226 | 227 | binject_data_t * toinj = (binject_data_t *)binject_data(ds); 228 | int result = NO_ERROR; 229 | 230 | if (binject_does_use_tail(ds)) { 231 | // Tail mode 232 | result = binject_tail_append(0, destination_path, data, r); 233 | 234 | } else { 235 | if ((long)toinj->len + (long)r < (long)toinj->max-1) { 236 | // Static arry mode 237 | memcpy(toinj->raw + toinj->len, data, r); 238 | toinj->len += r; 239 | result = NO_ERROR; 240 | 241 | } else { 242 | // Switch to tail mode 243 | binject_use_tail(ds); 244 | unsigned int * pos = &(( (binject_data_t *) binject_data(ds) ) -> tail_position); 245 | if (*pos > 0) pos = NULL; 246 | result = binject_tail_append(pos, destination_path, toinj->raw, toinj->len); 247 | if (NO_ERROR == result) result = binject_tail_append(pos, destination_path, data, r); 248 | } 249 | } 250 | if (NO_ERROR == result) result = binject_inject(ds, destination_path); 251 | free(ds); 252 | return result; 253 | } 254 | 255 | // -------------------------------------------------------------------- 256 | 257 | -------------------------------------------------------------------------------- /glua.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "unistd.h" 8 | 9 | #include "lua.h" 10 | #include "lualib.h" 11 | #include "lauxlib.h" 12 | #include "binject.h" 13 | 14 | // -------------------------------------------------------------------------------- 15 | 16 | #define FAIL_INIT -125 17 | #define FAIL_ALLOC -126 18 | #define FAIL_EXECUTION -127 19 | #define ALL_IS_RIGHT 0 20 | 21 | #ifdef LUA_OK 22 | #define is_lua_ok(status_code) (LUA_OK == status_code) 23 | #else 24 | #define is_lua_ok(status_code) (!status_code) 25 | #endif 26 | 27 | #ifdef LUA_OK 28 | #define lua_is_bad() (!LUA_OK) 29 | #else 30 | #define lua_is_bad() (is_lua_ok(0) ? 1 : 0) 31 | #endif 32 | 33 | void luaL_openlibs (lua_State *L); // Lua internal - not part of the lua API 34 | 35 | static int script_msghandler (lua_State *L) { 36 | 37 | // is error object not a string? 38 | const char *msg = lua_tostring(L, 1); 39 | if (msg == NULL) { 40 | 41 | // call a tostring metamethod if any 42 | if (luaL_callmeta(L, 1, "__tostring") && lua_type(L, -1) == LUA_TSTRING){ 43 | msg = lua_tostring(L, -1); 44 | lua_remove(L, -1); 45 | 46 | // else push a standard error message 47 | } else { 48 | msg = lua_pushfstring(L, "(error object is a %s value)", luaL_typename(L, 1));} 49 | } 50 | 51 | // append a traceback 52 | luaL_traceback(L, L, msg, 1); 53 | 54 | return 1; 55 | } 56 | 57 | // Signal hook: stop the interpreter. Just like standard lua interpreter. 58 | static void clear_and_stop(lua_State *L, lua_Debug *ar) { 59 | (void)ar; // unused arg. 60 | lua_sethook(L, NULL, 0, 0); 61 | luaL_error(L, "interrupted!"); 62 | } 63 | static lua_State *script_globalL = NULL; 64 | static void sigint_handler (int i) { 65 | signal(i, SIG_DFL); // if another SIGINT happens, terminate process 66 | lua_State *L = script_globalL; 67 | script_globalL = NULL; 68 | lua_sethook(L, clear_and_stop, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1); // Run 'clear_and_stop' before any other lua code 69 | } 70 | 71 | static void report_error(lua_State *L, const char * title){ 72 | if (!title || title[0] == '\0') 73 | title = "An error occurred somewhere.\n"; 74 | char * data = "No other information are avaiable\n"; 75 | if (lua_isstring(L, -1)) data = (char *) lua_tostring(L, -1); 76 | lua_getglobal(L, "print"); 77 | lua_pushstring(L, title); 78 | lua_pushstring(L, data); 79 | lua_pcall(L, 2, 1, 0); 80 | } 81 | 82 | int luamain_start(lua_State *L, char* script, int size, int argc, char **argv) { 83 | int status; 84 | int create_lua = 0; 85 | int base = 0; 86 | 87 | // create state as needed 88 | if (L == NULL) { 89 | create_lua = 1; 90 | L = luaL_newstate(); 91 | if (L == NULL) return FAIL_ALLOC; 92 | } 93 | 94 | // os signal handler 95 | void (*previous_sighandl)(int) = signal(SIGINT, sigint_handler); 96 | if (SIG_DFL == previous_sighandl) { 97 | script_globalL = L; // to be available to 'sigint_handler' 98 | } 99 | 100 | luaopen_glua(L); 101 | 102 | // Prepare the stack with the error handler 103 | lua_pushcfunction(L, script_msghandler); 104 | base = lua_gettop(L); 105 | 106 | // Create a table to store the command line arguments 107 | lua_createtable(L, argc-1, 1); 108 | 109 | // Arg 0 : command-line-like path to the executable: 110 | // it may be a link and/or be relative to the current directory 111 | lua_pushstring(L, argv[0]); 112 | lua_rawseti(L, -2, 0); 113 | 114 | // Args N... : command line arguments 115 | for (int i = 1; i < argc; i++) { 116 | lua_pushstring(L, argv[i]); 117 | lua_rawseti(L, -2, i); 118 | } 119 | 120 | // Save the table in the global namespace 121 | lua_setglobal(L, "arg"); 122 | 123 | // Load the script in the stack 124 | if (size < 0) size = strlen(script); 125 | status = luaL_loadbuffer(L, script, size, "embedded"); 126 | if (!is_lua_ok(status)) { 127 | report_error(L, "An error occurred during the script load."); 128 | status = FAIL_EXECUTION; 129 | goto luamain_end; 130 | } 131 | 132 | // Run the script with the signal handler 133 | status = lua_is_bad(); 134 | status = lua_pcall(L, 0, LUA_MULTRET, base); 135 | if (is_lua_ok(status)) { 136 | status = ALL_IS_RIGHT; 137 | goto luamain_end; 138 | } 139 | 140 | // Report error 141 | report_error(L, "An error accurred during the script execution."); 142 | if (lua_isnumber(L, -1)) status = lua_tonumber(L, -1); 143 | else status = FAIL_EXECUTION; 144 | 145 | luamain_end: 146 | 147 | // clear C-signal handler 148 | if (SIG_DFL == previous_sighandl) { 149 | signal(SIGINT, SIG_DFL); 150 | script_globalL = NULL; 151 | } 152 | 153 | if (base>0) lua_remove(L, base); // remove lua message handler 154 | if (create_lua) lua_close(L); 155 | return status; 156 | } 157 | 158 | // -------------------------------------------------------------------------------- 159 | 160 | // Size of the data for the INTERNAL ARRAY mechanism. It should be 161 | // a positive integer 162 | #ifndef BINJECT_ARRAY_SIZE 163 | #define BINJECT_ARRAY_SIZE (9216) 164 | #endif // BINJECT_ARRAY_SIZE 165 | 166 | BINJECT_STATIC_STRING("```replace_data```", BINJECT_ARRAY_SIZE, static_data); 167 | 168 | static char* self_binary_path = 0; 169 | 170 | int set_self_binary_path(const char* self_path){ 171 | self_binary_path = (char*) self_path; 172 | } 173 | 174 | int binject_main_app_has_internal_script() { 175 | unsigned int size = 0; 176 | unsigned int offset = 0; 177 | binject_get_static_script(static_data, &size, &offset); 178 | if (size > 0 || offset > 0) return 1; 179 | return 0; 180 | } 181 | 182 | int binject_main_app_internal_script_handle(lua_State *L, int argc, char **argv) { 183 | unsigned int size; 184 | unsigned int offset; 185 | 186 | // Get information from static section 187 | char * script = binject_get_static_script(static_data, &size, &offset); 188 | 189 | if (script) { 190 | // Script found in the static section 191 | return luamain_start(L, script, size, argc, argv); 192 | 193 | } else { 194 | // Script should be at end of the binary 195 | unsigned int script_size = binject_get_tail_script(static_data, self_binary_path, 0, 0, offset); 196 | char buf[script_size]; 197 | binject_get_tail_script(static_data, self_binary_path, buf, script_size, offset); 198 | return luamain_start(L, buf, script_size, argc, argv); 199 | } 200 | 201 | return NO_ERROR; 202 | } 203 | 204 | static int binject_main_app_internal_script_inject(const char * scr_path, const char * outpath){ 205 | int result = ACCESS_ERROR; 206 | errno = 0; 207 | 208 | // Open the scipt 209 | FILE * scr = fopen(scr_path, "rb"); 210 | if (!scr) goto end; 211 | 212 | // Get the original binary size 213 | if (fseek(scr, 0, SEEK_END)) goto end; 214 | int siz = ftell(scr); 215 | if (siz < 0) goto end; 216 | if (fseek(scr, 0, SEEK_SET)) goto end; 217 | 218 | { // Scope block to avoid goto and variable length issue 219 | char buf[siz]; 220 | 221 | // Copy the binary 222 | result = binject_duplicate_binary(static_data, self_binary_path, outpath); 223 | if (NO_ERROR != result) goto end; 224 | 225 | // Read the script for the injection 226 | if (0> fread(buf, 1, siz, scr)) goto end; 227 | 228 | // Inject the script and update static info into the binary 229 | result = binject_step(static_data, outpath, buf, siz); 230 | if (NO_ERROR != result) goto end; 231 | } 232 | 233 | end: 234 | if (0 != errno) 235 | fprintf(stderr, "Error %d: %s\n", errno, strerror(errno)); 236 | if (scr) fclose(scr); 237 | return result; 238 | } 239 | 240 | // -------------------------------------------------------------------------------- 241 | 242 | static int glua_pack_call(lua_State* L){ 243 | if (!self_binary_path){ 244 | lua_pushnil(L); 245 | lua_pushstring(L, "can not retrieve the self binary path"); 246 | return 2; 247 | } 248 | const char *inpath = luaL_checkstring(L, 1); 249 | const char *outpath = luaL_checkstring(L, 2); 250 | if (!inpath || !outpath || *inpath == '\0' || *outpath == '\0'){ 251 | lua_pushnil(L); 252 | lua_pushstring(L, "input or output file not provided"); 253 | return 2; 254 | } 255 | const int result = binject_main_app_internal_script_inject(inpath, outpath); 256 | if (result) { 257 | lua_pushnil(L); 258 | lua_pushstring(L, "can not read input file or generate output one"); 259 | return 2; 260 | } 261 | return 0; 262 | } 263 | 264 | // -------------------------------------------------------------------------------- 265 | 266 | int luaopen_glua_pack(lua_State* L){ 267 | lua_pushcfunction(L, glua_pack_call); 268 | return 1; 269 | } 270 | 271 | int luaopen_whereami(lua_State* L){ 272 | lua_pushstring(L, self_binary_path); 273 | return 1; 274 | } 275 | 276 | #ifdef PRELOAD_EXTRA 277 | int PRELOAD_EXTRA(lua_State* L); 278 | #endif 279 | 280 | int luaopen_glua(lua_State* L){ 281 | luaL_openlibs(L); 282 | #ifdef PRELOAD_EXTRA 283 | PRELOAD_EXTRA(L); 284 | #endif 285 | 286 | luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE); 287 | 288 | lua_pushcfunction(L, luaopen_whereami); lua_setfield(L, -2, "whereami"); 289 | lua_pushcfunction(L, luaopen_glua_pack); lua_setfield(L, -2, "glua_pack"); 290 | 291 | lua_pop(L, 1); 292 | return 0; 293 | } 294 | 295 | -------------------------------------------------------------------------------- /whereami.c: -------------------------------------------------------------------------------- 1 | // (‑●‑●)> released under the WTFPL v2 license, by Gregory Pakosz (@gpakosz) 2 | // https://github.com/gpakosz/whereami 3 | 4 | // in case you want to #include "whereami.c" in a larger compilation unit 5 | #if !defined(WHEREAMI_H) 6 | #include 7 | #endif 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | #if !defined(WAI_MALLOC) || !defined(WAI_FREE) || !defined(WAI_REALLOC) 14 | #include 15 | #endif 16 | 17 | #if !defined(WAI_MALLOC) 18 | #define WAI_MALLOC(size) malloc(size) 19 | #endif 20 | 21 | #if !defined(WAI_FREE) 22 | #define WAI_FREE(p) free(p) 23 | #endif 24 | 25 | #if !defined(WAI_REALLOC) 26 | #define WAI_REALLOC(p, size) realloc(p, size) 27 | #endif 28 | 29 | #ifndef WAI_NOINLINE 30 | #if defined(_MSC_VER) 31 | #define WAI_NOINLINE __declspec(noinline) 32 | #elif defined(__GNUC__) 33 | #define WAI_NOINLINE __attribute__((noinline)) 34 | #else 35 | #error unsupported compiler 36 | #endif 37 | #endif 38 | 39 | #if defined(_MSC_VER) 40 | #define WAI_RETURN_ADDRESS() _ReturnAddress() 41 | #elif defined(__GNUC__) 42 | #define WAI_RETURN_ADDRESS() __builtin_extract_return_addr(__builtin_return_address(0)) 43 | #else 44 | #error unsupported compiler 45 | #endif 46 | 47 | #if defined(_WIN32) 48 | 49 | #define WIN32_LEAN_AND_MEAN 50 | #if defined(_MSC_VER) 51 | #pragma warning(push, 3) 52 | #endif 53 | #include 54 | #include 55 | #if defined(_MSC_VER) 56 | #pragma warning(pop) 57 | #endif 58 | 59 | static int WAI_PREFIX(getModulePath_)(HMODULE module, char* out, int capacity, int* dirname_length) 60 | { 61 | wchar_t buffer1[MAX_PATH]; 62 | wchar_t buffer2[MAX_PATH]; 63 | wchar_t* path = NULL; 64 | int length = -1; 65 | 66 | for (;;) 67 | { 68 | DWORD size; 69 | int length_, length__; 70 | 71 | size = GetModuleFileNameW(module, buffer1, sizeof(buffer1) / sizeof(buffer1[0])); 72 | 73 | if (size == 0) 74 | break; 75 | else if (size == (DWORD)(sizeof(buffer1) / sizeof(buffer1[0]))) 76 | { 77 | DWORD size_ = size; 78 | do 79 | { 80 | wchar_t* path_; 81 | 82 | path_ = (wchar_t*)WAI_REALLOC(path, sizeof(wchar_t) * size_ * 2); 83 | if (!path_) 84 | break; 85 | size_ *= 2; 86 | path = path_; 87 | size = GetModuleFileNameW(module, path, size_); 88 | } 89 | while (size == size_); 90 | 91 | if (size == size_) 92 | break; 93 | } 94 | else 95 | path = buffer1; 96 | 97 | if (!_wfullpath(buffer2, path, MAX_PATH)) 98 | break; 99 | length_ = (int)wcslen(buffer2); 100 | length__ = WideCharToMultiByte(CP_UTF8, 0, buffer2, length_ , out, capacity, NULL, NULL); 101 | 102 | if (length__ == 0) 103 | length__ = WideCharToMultiByte(CP_UTF8, 0, buffer2, length_, NULL, 0, NULL, NULL); 104 | if (length__ == 0) 105 | break; 106 | 107 | if (length__ <= capacity && dirname_length) 108 | { 109 | int i; 110 | 111 | for (i = length__ - 1; i >= 0; --i) 112 | { 113 | if (out[i] == '\\') 114 | { 115 | *dirname_length = i; 116 | break; 117 | } 118 | } 119 | } 120 | 121 | length = length__; 122 | 123 | break; 124 | } 125 | 126 | if (path != buffer1) 127 | WAI_FREE(path); 128 | 129 | return length; 130 | } 131 | 132 | WAI_NOINLINE 133 | WAI_FUNCSPEC 134 | int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) 135 | { 136 | return WAI_PREFIX(getModulePath_)(NULL, out, capacity, dirname_length); 137 | } 138 | 139 | WAI_NOINLINE 140 | WAI_FUNCSPEC 141 | int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) 142 | { 143 | HMODULE module; 144 | int length = -1; 145 | 146 | #if defined(_MSC_VER) 147 | #pragma warning(push) 148 | #pragma warning(disable: 4054) 149 | #endif 150 | if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCTSTR)WAI_RETURN_ADDRESS(), &module)) 151 | #if defined(_MSC_VER) 152 | #pragma warning(pop) 153 | #endif 154 | { 155 | length = WAI_PREFIX(getModulePath_)(module, out, capacity, dirname_length); 156 | } 157 | 158 | return length; 159 | } 160 | 161 | #elif defined(__linux__) || defined(__CYGWIN__) 162 | 163 | #include 164 | #include 165 | #include 166 | #include 167 | #ifndef __STDC_FORMAT_MACROS 168 | #define __STDC_FORMAT_MACROS 169 | #endif 170 | #include 171 | 172 | #if !defined(WAI_PROC_SELF_EXE) 173 | #define WAI_PROC_SELF_EXE "/proc/self/exe" 174 | #endif 175 | 176 | WAI_FUNCSPEC 177 | int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) 178 | { 179 | char buffer[PATH_MAX]; 180 | char* resolved = NULL; 181 | int length = -1; 182 | 183 | for (;;) 184 | { 185 | resolved = realpath(WAI_PROC_SELF_EXE, buffer); 186 | if (!resolved) 187 | break; 188 | 189 | length = (int)strlen(resolved); 190 | if (length <= capacity) 191 | { 192 | memcpy(out, resolved, length); 193 | 194 | if (dirname_length) 195 | { 196 | int i; 197 | 198 | for (i = length - 1; i >= 0; --i) 199 | { 200 | if (out[i] == '/') 201 | { 202 | *dirname_length = i; 203 | break; 204 | } 205 | } 206 | } 207 | } 208 | 209 | break; 210 | } 211 | 212 | return length; 213 | } 214 | 215 | #if !defined(WAI_PROC_SELF_MAPS_RETRY) 216 | #define WAI_PROC_SELF_MAPS_RETRY 5 217 | #endif 218 | 219 | #if !defined(WAI_PROC_SELF_MAPS) 220 | #define WAI_PROC_SELF_MAPS "/proc/self/maps" 221 | #endif 222 | 223 | #if defined(__ANDROID__) || defined(ANDROID) 224 | #include 225 | #include 226 | #include 227 | #endif 228 | 229 | WAI_NOINLINE 230 | WAI_FUNCSPEC 231 | int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) 232 | { 233 | int length = -1; 234 | FILE* maps = NULL; 235 | int i; 236 | 237 | for (i = 0; i < WAI_PROC_SELF_MAPS_RETRY; ++i) 238 | { 239 | maps = fopen(WAI_PROC_SELF_MAPS, "r"); 240 | if (!maps) 241 | break; 242 | 243 | for (;;) 244 | { 245 | char buffer[PATH_MAX < 1024 ? 1024 : PATH_MAX]; 246 | uint64_t low, high; 247 | char perms[5]; 248 | uint64_t offset; 249 | uint32_t major, minor; 250 | char path[PATH_MAX]; 251 | uint32_t inode; 252 | 253 | if (!fgets(buffer, sizeof(buffer), maps)) 254 | break; 255 | 256 | if (sscanf(buffer, "%" PRIx64 "-%" PRIx64 " %s %" PRIx64 " %x:%x %u %s\n", &low, &high, perms, &offset, &major, &minor, &inode, path) == 8) 257 | { 258 | uint64_t addr = (uint64_t)(uintptr_t)WAI_RETURN_ADDRESS(); 259 | if (low <= addr && addr <= high) 260 | { 261 | char* resolved; 262 | 263 | resolved = realpath(path, buffer); 264 | if (!resolved) 265 | break; 266 | 267 | length = (int)strlen(resolved); 268 | #if defined(__ANDROID__) || defined(ANDROID) 269 | if (length > 4 270 | &&buffer[length - 1] == 'k' 271 | &&buffer[length - 2] == 'p' 272 | &&buffer[length - 3] == 'a' 273 | &&buffer[length - 4] == '.') 274 | { 275 | int fd = open(path, O_RDONLY); 276 | char* begin; 277 | char* p; 278 | 279 | begin = (char*)mmap(0, offset, PROT_READ, MAP_SHARED, fd, 0); 280 | p = begin + offset; 281 | 282 | while (p >= begin) // scan backwards 283 | { 284 | if (*((uint32_t*)p) == 0x04034b50UL) // local file header found 285 | { 286 | uint16_t length_ = *((uint16_t*)(p + 26)); 287 | 288 | if (length + 2 + length_ < (int)sizeof(buffer)) 289 | { 290 | memcpy(&buffer[length], "!/", 2); 291 | memcpy(&buffer[length + 2], p + 30, length_); 292 | length += 2 + length_; 293 | } 294 | 295 | break; 296 | } 297 | 298 | p -= 4; 299 | } 300 | 301 | munmap(begin, offset); 302 | close(fd); 303 | } 304 | #endif 305 | if (length <= capacity) 306 | { 307 | memcpy(out, resolved, length); 308 | 309 | if (dirname_length) 310 | { 311 | int i; 312 | 313 | for (i = length - 1; i >= 0; --i) 314 | { 315 | if (out[i] == '/') 316 | { 317 | *dirname_length = i; 318 | break; 319 | } 320 | } 321 | } 322 | } 323 | 324 | break; 325 | } 326 | } 327 | } 328 | 329 | fclose(maps); 330 | 331 | if (length != -1) 332 | break; 333 | } 334 | 335 | return length; 336 | } 337 | 338 | #elif defined(__APPLE__) 339 | 340 | #define _DARWIN_BETTER_REALPATH 341 | #include 342 | #include 343 | #include 344 | #include 345 | #include 346 | 347 | WAI_FUNCSPEC 348 | int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) 349 | { 350 | char buffer1[PATH_MAX]; 351 | char buffer2[PATH_MAX]; 352 | char* path = buffer1; 353 | char* resolved = NULL; 354 | int length = -1; 355 | 356 | for (;;) 357 | { 358 | uint32_t size = (uint32_t)sizeof(buffer1); 359 | if (_NSGetExecutablePath(path, &size) == -1) 360 | { 361 | path = (char*)WAI_MALLOC(size); 362 | if (!_NSGetExecutablePath(path, &size)) 363 | break; 364 | } 365 | 366 | resolved = realpath(path, buffer2); 367 | if (!resolved) 368 | break; 369 | 370 | length = (int)strlen(resolved); 371 | if (length <= capacity) 372 | { 373 | memcpy(out, resolved, length); 374 | 375 | if (dirname_length) 376 | { 377 | int i; 378 | 379 | for (i = length - 1; i >= 0; --i) 380 | { 381 | if (out[i] == '/') 382 | { 383 | *dirname_length = i; 384 | break; 385 | } 386 | } 387 | } 388 | } 389 | 390 | break; 391 | } 392 | 393 | if (path != buffer1) 394 | WAI_FREE(path); 395 | 396 | return length; 397 | } 398 | 399 | WAI_NOINLINE 400 | WAI_FUNCSPEC 401 | int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) 402 | { 403 | char buffer[PATH_MAX]; 404 | char* resolved = NULL; 405 | int length = -1; 406 | 407 | for(;;) 408 | { 409 | Dl_info info; 410 | 411 | if (dladdr(WAI_RETURN_ADDRESS(), &info)) 412 | { 413 | resolved = realpath(info.dli_fname, buffer); 414 | if (!resolved) 415 | break; 416 | 417 | length = (int)strlen(resolved); 418 | if (length <= capacity) 419 | { 420 | memcpy(out, resolved, length); 421 | 422 | if (dirname_length) 423 | { 424 | int i; 425 | 426 | for (i = length - 1; i >= 0; --i) 427 | { 428 | if (out[i] == '/') 429 | { 430 | *dirname_length = i; 431 | break; 432 | } 433 | } 434 | } 435 | } 436 | } 437 | 438 | break; 439 | } 440 | 441 | return length; 442 | } 443 | 444 | #elif defined(__QNXNTO__) 445 | 446 | #include 447 | #include 448 | #include 449 | #include 450 | #include 451 | 452 | #if !defined(WAI_PROC_SELF_EXE) 453 | #define WAI_PROC_SELF_EXE "/proc/self/exefile" 454 | #endif 455 | 456 | WAI_FUNCSPEC 457 | int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) 458 | { 459 | char buffer1[PATH_MAX]; 460 | char buffer2[PATH_MAX]; 461 | char* resolved = NULL; 462 | FILE* self_exe = NULL; 463 | int length = -1; 464 | 465 | for (;;) 466 | { 467 | self_exe = fopen(WAI_PROC_SELF_EXE, "r"); 468 | if (!self_exe) 469 | break; 470 | 471 | if (!fgets(buffer1, sizeof(buffer1), self_exe)) 472 | break; 473 | 474 | resolved = realpath(buffer1, buffer2); 475 | if (!resolved) 476 | break; 477 | 478 | length = (int)strlen(resolved); 479 | if (length <= capacity) 480 | { 481 | memcpy(out, resolved, length); 482 | 483 | if (dirname_length) 484 | { 485 | int i; 486 | 487 | for (i = length - 1; i >= 0; --i) 488 | { 489 | if (out[i] == '/') 490 | { 491 | *dirname_length = i; 492 | break; 493 | } 494 | } 495 | } 496 | } 497 | 498 | break; 499 | } 500 | 501 | fclose(self_exe); 502 | 503 | return length; 504 | } 505 | 506 | WAI_FUNCSPEC 507 | int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) 508 | { 509 | char buffer[PATH_MAX]; 510 | char* resolved = NULL; 511 | int length = -1; 512 | 513 | for(;;) 514 | { 515 | Dl_info info; 516 | 517 | if (dladdr(WAI_RETURN_ADDRESS(), &info)) 518 | { 519 | resolved = realpath(info.dli_fname, buffer); 520 | if (!resolved) 521 | break; 522 | 523 | length = (int)strlen(resolved); 524 | if (length <= capacity) 525 | { 526 | memcpy(out, resolved, length); 527 | 528 | if (dirname_length) 529 | { 530 | int i; 531 | 532 | for (i = length - 1; i >= 0; --i) 533 | { 534 | if (out[i] == '/') 535 | { 536 | *dirname_length = i; 537 | break; 538 | } 539 | } 540 | } 541 | } 542 | } 543 | 544 | break; 545 | } 546 | 547 | return length; 548 | } 549 | 550 | #elif defined(__DragonFly__) || defined(__FreeBSD__) || \ 551 | defined(__FreeBSD_kernel__) || defined(__NetBSD__) 552 | 553 | #include 554 | #include 555 | #include 556 | #include 557 | #include 558 | #include 559 | 560 | WAI_FUNCSPEC 561 | int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) 562 | { 563 | char buffer1[PATH_MAX]; 564 | char buffer2[PATH_MAX]; 565 | char* path = buffer1; 566 | char* resolved = NULL; 567 | int length = -1; 568 | 569 | for (;;) 570 | { 571 | int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; 572 | size_t size = sizeof(buffer1); 573 | 574 | if (sysctl(mib, (u_int)(sizeof(mib) / sizeof(mib[0])), path, &size, NULL, 0) != 0) 575 | break; 576 | 577 | resolved = realpath(path, buffer2); 578 | if (!resolved) 579 | break; 580 | 581 | length = (int)strlen(resolved); 582 | if (length <= capacity) 583 | { 584 | memcpy(out, resolved, length); 585 | 586 | if (dirname_length) 587 | { 588 | int i; 589 | 590 | for (i = length - 1; i >= 0; --i) 591 | { 592 | if (out[i] == '/') 593 | { 594 | *dirname_length = i; 595 | break; 596 | } 597 | } 598 | } 599 | } 600 | 601 | break; 602 | } 603 | 604 | if (path != buffer1) 605 | WAI_FREE(path); 606 | 607 | return length; 608 | } 609 | 610 | WAI_NOINLINE 611 | WAI_FUNCSPEC 612 | int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) 613 | { 614 | char buffer[PATH_MAX]; 615 | char* resolved = NULL; 616 | int length = -1; 617 | 618 | for(;;) 619 | { 620 | Dl_info info; 621 | 622 | if (dladdr(WAI_RETURN_ADDRESS(), &info)) 623 | { 624 | resolved = realpath(info.dli_fname, buffer); 625 | if (!resolved) 626 | break; 627 | 628 | length = (int)strlen(resolved); 629 | if (length <= capacity) 630 | { 631 | memcpy(out, resolved, length); 632 | 633 | if (dirname_length) 634 | { 635 | int i; 636 | 637 | for (i = length - 1; i >= 0; --i) 638 | { 639 | if (out[i] == '/') 640 | { 641 | *dirname_length = i; 642 | break; 643 | } 644 | } 645 | } 646 | } 647 | } 648 | 649 | break; 650 | } 651 | 652 | return length; 653 | } 654 | 655 | #else 656 | 657 | #error unsupported platform 658 | 659 | #endif 660 | 661 | #ifdef __cplusplus 662 | } 663 | #endif 664 | --------------------------------------------------------------------------------