#include #include #include #include #include #include #include #include #include "env.h" #include "khash.h" #include "debug.h" #include "fileutils.h" #include "box64context.h" #include "rbtree.h" box64env_t box64env = { 0 }; KHASH_MAP_INIT_STR(box64env_entry, box64env_t) static kh_box64env_entry_t* box64env_entries = NULL; static kh_box64env_entry_t* box64env_entries_gen = NULL; static rbtree_t* envmap = NULL; static const char default_rcfile[] = "[bash]\n" "BOX64_LOG=0\n" "\n" "[deadcells]\n" "BOX64_PREFER_EMULATED=1\n" "\n" "[dontstarve]\n" "BOX64_EMULATED_LIBS=libSDL2-2.0.so.0\n" "\n" "[dota2]\n" "BOX64_CRASHHANDLER=1\n" "BOX64_DYNAREC_STRONGMEM=1\n" "\n" "[factorio]\n" "BOX64_DYNAREC_FASTROUND=0\n" "\n" "[heroic]\n" "BOX64_NOSANDBOX=1\n" "BOX64_MALLOC_HACK=2\n" "\n" "[LotCG.x86_64]\n" "BOX64_DYNAREC_FASTROUND=0\n" "\n" "[Mini Metro]\n" "BOX64_ADDLIBS=stdc++.so.6\n" "\n" "[pressure-vessel-wrap]\n" "BOX64_NOGTK=1\n" "\n" "[ShovelKnight]\n" "BOX64_SDL2_JGUID=1\n" "\n" "[Soma.bin.x86_64]\n" "BOX64_DYNAREC_FASTROUND=0\n" "\n" "[streaming_client]\n" "BOX64_EMULATED_LIBS=libSDL2-2.0.so.0:libSDL2_ttf-2.0.so.0\n" "\n" "[steam-runtime-check-requirements]\n" "BOX64_EXIT=1\n" "\n" "[steam-runtime-launcher-service]\n" "BOX64_EXIT=1\n" ; static void addNewEnvVar(const char* s) { if (!s) return; char* p = box_strdup(s); char* e = strchr(p, '='); if (!e) { printf_log(LOG_INFO, "Invalid specific env. var. '%s'\n", s); box_free(p); return; } *e = '\0'; ++e; setenv(p, e, 1); box_free(p); } static void applyCustomRules() { if (BOX64ENV(log) == LOG_NEVER) { SET_BOX64ENV(log, BOX64ENV(log) - 1); SET_BOX64ENV(dump, 1); } if (BOX64ENV(rolling_log) == 1) { SET_BOX64ENV(rolling_log, 16); } if (BOX64ENV(rolling_log) && BOX64ENV(log) > LOG_INFO) { SET_BOX64ENV(rolling_log, 0); } if (box64env.is_dynarec_test_str_overridden) { if (strlen(box64env.dynarec_test_str) == 1) { if (box64env.dynarec_test_str[0] >= '0' && box64env.dynarec_test_str[0] <= '1') box64env.dynarec_test = box64env.dynarec_test_str[0] - '0'; box64env.dynarec_test_start = 0x0; box64env.dynarec_test_end = 0x0; } else if (strchr(box64env.dynarec_test_str, '-')) { if (sscanf(box64env.dynarec_test_str, "%ld-%ld", &box64env.dynarec_test_start, &box64env.dynarec_test_end) != 2) { if (sscanf(box64env.dynarec_test_str, "0x%lX-0x%lX", &box64env.dynarec_test_start, &box64env.dynarec_test_end) != 2) sscanf(box64env.dynarec_test_str, "%lx-%lx", &box64env.dynarec_test_start, &box64env.dynarec_test_end); } if (box64env.dynarec_test_end > box64env.dynarec_test_start) { box64env.dynarec_test = 1; } else { box64env.dynarec_test = 0; } } } if (box64env.is_nodynarec_overridden) { if(box64env.nodynarec) { if (strchr(box64env.nodynarec,'-')) { if(sscanf(box64env.nodynarec, "%ld-%ld", &box64env.nodynarec_start, &box64env.nodynarec_end)!=2) { if(sscanf(box64env.nodynarec, "0x%lX-0x%lX", &box64env.nodynarec_start, &box64env.nodynarec_end)!=2) sscanf(box64env.nodynarec, "%lx-%lx", &box64env.nodynarec_start, &box64env.nodynarec_end); } } } } if (box64env.dynarec_test) { SET_BOX64ENV(dynarec_fastnan, 0); SET_BOX64ENV(dynarec_fastround, 0); SET_BOX64ENV(dynarec_x87double, 1); SET_BOX64ENV(dynarec_div0, 1); SET_BOX64ENV(dynarec_callret, 0); #if defined(RV64) || defined(LA64) SET_BOX64ENV(dynarec_nativeflags, 0); #endif } if (box64env.maxcpu == 0 || (!box64_wine && box64env.new_maxcpu < box64env.maxcpu)) { box64env.maxcpu = box64env.new_maxcpu; } if (box64env.dynarec_perf_map) { char pathname[32]; snprintf(pathname, sizeof(pathname), "/tmp/perf-%d.map", getpid()); SET_BOX64ENV(dynarec_perf_map_fd, open(pathname, O_CREAT | O_RDWR | O_APPEND, S_IRUSR | S_IWUSR)); } if (!box64env.libgl) { const char *p = getenv("SDL_VIDEO_GL_DRIVER"); if(p) SET_BOX64ENV(libgl, box_strdup(p)); } if (box64env.avx == 2) { box64env.avx = 1; box64env.avx2 = 1; } if (box64env.exit) exit(0); if (box64env.env) addNewEnvVar(box64env.env); if (box64env.env1) addNewEnvVar(box64env.env1); if (box64env.env2) addNewEnvVar(box64env.env2); if (box64env.env3) addNewEnvVar(box64env.env3); if (box64env.env4) addNewEnvVar(box64env.env4); if (box64env.env5) addNewEnvVar(box64env.env5); } static void trimStringInplace(char* s) { if (!s) return; // trim right space/tab size_t len = strlen(s); while (len && (s[len - 1] == ' ' || s[len - 1] == '\t' || s[len - 1] == '\n')) s[--len] = '\0'; // trim left space/tab while (s[0] == ' ' || s[0] == '\t') memmove(s, s + 1, strlen(s)); } static void freeEnv(box64env_t* env) { #define INTEGER(NAME, name, default, min, max) #define INTEGER64(NAME, name, default) #define BOOLEAN(NAME, name, default) #define ADDRESS(NAME, name) #define STRING(NAME, name) box_free(env->name); ENVSUPER() #undef INTEGER #undef INTEGER64 #undef BOOLEAN #undef ADDRESS #undef STRING } static void pushNewEntry(const char* name, box64env_t* env, int gen) { khint_t k; kh_box64env_entry_t* khp = gen ? box64env_entries_gen : box64env_entries; k = kh_get(box64env_entry, khp, name); if (k == kh_end(khp)) { int ret; k = kh_put(box64env_entry, khp, strdup(name), &ret); } else { freeEnv(&kh_value(khp, k)); } box64env_t* p = &kh_value(khp, k); memcpy(p, env, sizeof(box64env_t)); } #ifdef ANDROID static int shm_open(const char *name, int oflag, mode_t mode) { return -1; } static int shm_unlink(const char *name) { return -1; } #endif static void initializeEnvFile(const char* filename) { if (box64env.noenvfiles) return; FILE* f = NULL; if (filename) f = fopen(filename, "r"); else { #define TMP_MEMRCFILE "/box64_rcfile" int tmp = shm_open(TMP_MEMRCFILE, O_RDWR | O_CREAT, S_IRWXU); if(tmp<0) return; // error, bye bye shm_unlink(TMP_MEMRCFILE); // remove the shm file, but it will still exist because it's currently in use int dummy = write(tmp, default_rcfile, sizeof(default_rcfile)); (void)dummy; lseek(tmp, 0, SEEK_SET); f = fdopen(tmp, "r"); } if (!f) { printf("Error: Cannot open env file %s\n", filename); return; } if (!box64env_entries) box64env_entries = kh_init(box64env_entry); if (!box64env_entries_gen) box64env_entries_gen = kh_init(box64env_entry); box64env_t current_env = { 0 }; size_t linesize = 0, len = 0; char *line = NULL, *current_name = NULL; int ret; bool is_wildcard_name = false; while ((ret = getline(&line, &linesize, f)) != -1) { // remove comments char* p = strchr(line, '#'); if (p) *p = '\0'; trimStringInplace(line); len = strlen(line); if (line[0] == '[' && strchr(line, ']')) { // new entry, push the previous one if (current_name) pushNewEntry(current_name, ¤t_env, is_wildcard_name); is_wildcard_name = (line[1] == '*' && line[(intptr_t)(strchr(line, ']') - line) - 1] == '*'); memset(¤t_env, 0, sizeof(current_env)); box_free(current_name); current_name = LowerCase(line + (is_wildcard_name ? 2 : 1)); *(strchr(current_name, ']') + 1 - (is_wildcard_name ? 2 : 1)) = '\0'; trimStringInplace(current_name); } else if (strchr(line, '=')) { char* key = line; char* val = strchr(key, '=') + 1; *strchr(key, '=') = '\0'; trimStringInplace(key); trimStringInplace(val); #define INTEGER(NAME, name, default, min, max) \ else if (!strcmp(key, #NAME)) \ { \ int v = strtol(val, &p, 0); \ if (p != val && v >= min && v <= max) { \ current_env.is_##name##_overridden = 1; \ current_env.is_any_overridden = 1; \ current_env.name = v; \ } \ } #define INTEGER64(NAME, name, default) \ else if (!strcmp(key, #NAME)) \ { \ int64_t v = strtoll(val, &p, 0); \ if (p != val) { \ current_env.is_##name##_overridden = 1; \ current_env.is_any_overridden = 1; \ current_env.name = v; \ } \ } #define BOOLEAN(NAME, name, default) \ else if (!strcmp(key, #NAME)) \ { \ if (strcmp(val, "0")) { \ current_env.is_##name##_overridden = 1; \ current_env.is_any_overridden = 1; \ current_env.name = 1; \ } else { \ current_env.is_##name##_overridden = 1; \ current_env.is_any_overridden = 1; \ current_env.name = 0; \ } \ } #define ADDRESS(NAME, name) \ else if (!strcmp(key, #NAME)) \ { \ uintptr_t v = (uintptr_t)strtoll(val, &p, 0); \ if (p != val) { \ current_env.is_##name##_overridden = 1; \ current_env.is_any_overridden = 1; \ current_env.name = v; \ } \ } #define STRING(NAME, name) \ else if (!strcmp(key, #NAME)) \ { \ current_env.is_##name##_overridden = 1; \ current_env.is_any_overridden = 1; \ if (current_env.name) box_free(current_env.name); \ current_env.name = strdup(val); \ } if (0) ; ENVSUPER() else if (len && current_name) { printf_log(LOG_INFO, "BOX64ENV: Warning, unsupported %s=%s for [%s] in %s\n", key, val, current_name, filename); } #undef INTEGER #undef INTEGER64 #undef BOOLEAN #undef ADDRESS #undef STRING } } // push the last entry if (current_name) { pushNewEntry(current_name, ¤t_env, is_wildcard_name); box_free(current_name); } box_free(line); fclose(f); } void InitializeEnvFiles() { if (BOX64ENV(envfile) && FileExist(BOX64ENV(envfile), IS_FILE)) initializeEnvFile(BOX64ENV(envfile)); #ifndef TERMUX else if (FileExist("/etc/box64.box64rc", IS_FILE)) initializeEnvFile("/etc/box64.box64rc"); else if (FileExist("/data/data/com.termux/files/usr/glibc/etc/box64.box64rc", IS_FILE)) initializeEnvFile("/data/data/com.termux/files/usr/glibc/etc/box64.box64rc"); #else else if (FileExist("/data/data/com.termux/files/usr/etc/box64.box64rc", IS_FILE)) initializeEnvFile("/data/data/com.termux/files/usr/etc/box64.box64rc"); #endif else initializeEnvFile(NULL); // load default rcfile char* p = getenv("HOME"); if (p) { static char tmp[4096]; strncpy(tmp, p, 4095); strncat(tmp, "/.box64rc", 4095); if (FileExist(tmp, IS_FILE)) { initializeEnvFile(tmp); } } } static char old_entryname[256] = ""; const char* GetLastApplyEntryName() { return old_entryname; } static void internalApplyEnvFileEntry(const char* entryname, const box64env_t* env) { #define INTEGER(NAME, name, default, min, max) \ if (env->is_##name##_overridden) { \ box64env.name = env->name; \ box64env.is_##name##_overridden = 1; \ box64env.is_any_overridden = 1; \ } #define INTEGER64(NAME, name, default) \ if (env->is_##name##_overridden) { \ box64env.name = env->name; \ box64env.is_##name##_overridden = 1; \ box64env.is_any_overridden = 1; \ } #define BOOLEAN(NAME, name, default) \ if (env->is_##name##_overridden) { \ box64env.name = env->name; \ box64env.is_##name##_overridden = 1; \ box64env.is_any_overridden = 1; \ } #define ADDRESS(NAME, name) \ if (env->is_##name##_overridden) { \ box64env.name = env->name; \ box64env.is_##name##_overridden = 1; \ box64env.is_any_overridden = 1; \ } #define STRING(NAME, name) \ if (env->is_##name##_overridden) { \ box64env.name = env->name; \ box64env.is_##name##_overridden = 1; \ box64env.is_any_overridden = 1; \ } ENVSUPER() #undef INTEGER #undef INTEGER64 #undef BOOLEAN #undef ADDRESS #undef STRING } void ApplyEnvFileEntry(const char* entryname) { if (!entryname || !box64env_entries) return; if (!strcasecmp(entryname, old_entryname)) return; strncpy(old_entryname, entryname, 255); khint_t k1; { char* lowercase_entryname = LowerCase(entryname); k1 = kh_get(box64env_entry, box64env_entries, lowercase_entryname); box64env_t* env; const char* k2; kh_foreach_ref(box64env_entries_gen, k2, env, if (strstr(lowercase_entryname, k2)) internalApplyEnvFileEntry(entryname, env);) box_free(lowercase_entryname); } if (k1 == kh_end(box64env_entries)) return; box64env_t* env = &kh_value(box64env_entries, k1); internalApplyEnvFileEntry(entryname, env); applyCustomRules(); } void LoadEnvVariables() { #define INTEGER(NAME, name, default, min, max) box64env.name = default; #define INTEGER64(NAME, name, default) box64env.name = default; #define BOOLEAN(NAME, name, default) box64env.name = default; #define ADDRESS(NAME, name) box64env.name = 0; #define STRING(NAME, name) box64env.name = NULL; ENVSUPER() #undef INTEGER #undef INTEGER64 #undef BOOLEAN #undef ADDRESS #undef STRING char* p; // load env vars from getenv() #define INTEGER(NAME, name, default, min, max) \ p = getenv(#NAME); \ if (p) { \ box64env.name = atoi(p); \ if (box64env.name < min || box64env.name > max) { \ box64env.name = default; \ } else { \ box64env.is_##name##_overridden = 1; \ box64env.is_any_overridden = 1; \ } \ } #define INTEGER64(NAME, name, default) \ p = getenv(#NAME); \ if (p) { \ box64env.name = atoll(p); \ box64env.is_##name##_overridden = 1; \ box64env.is_any_overridden = 1; \ } #define BOOLEAN(NAME, name, default) \ p = getenv(#NAME); \ if (p) { \ box64env.name = p[0] != '0'; \ box64env.is_##name##_overridden = 1; \ box64env.is_any_overridden = 1; \ } #define ADDRESS(NAME, name) \ p = getenv(#NAME); \ if (p) { \ box64env.name = (uintptr_t)atoll(p); \ box64env.is_##name##_overridden = 1; \ box64env.is_any_overridden = 1; \ } #define STRING(NAME, name) \ p = getenv(#NAME); \ if (p) { \ box64env.name = strdup(p); \ box64env.is_##name##_overridden = 1; \ box64env.is_any_overridden = 1; \ } ENVSUPER() #undef INTEGER #undef INTEGER64 #undef BOOLEAN #undef ADDRESS #undef STRING applyCustomRules(); } void PrintEnvVariables(box64env_t* env, int level) { if (env->is_any_overridden) printf_log(level, "BOX64ENV: Variables overridden via env and/or RC file:\n"); #define INTEGER(NAME, name, default, min, max) \ if (env->is_##name##_overridden) \ printf_log_prefix(0, level, "\t%s=%d\n", #NAME, env->name); #define INTEGER64(NAME, name, default) \ if (env->is_##name##_overridden) \ printf_log_prefix(0, level, "\t%s=%lld\n", #NAME, env->name); #define BOOLEAN(NAME, name, default) \ if (env->is_##name##_overridden) \ printf_log_prefix(0, level, "\t%s=%d\n", #NAME, env->name); #define ADDRESS(NAME, name) \ if (env->is_##name##_overridden) \ printf_log_prefix(0, level, "\t%s=%p\n", #NAME, (void*)env->name); #define STRING(NAME, name) \ if (env->is_##name##_overridden) \ printf_log_prefix(0, level, "\t%s=%s\n", #NAME, env->name); ENVSUPER() #undef INTEGER #undef INTEGER64 #undef BOOLEAN #undef ADDRESS #undef STRING } typedef struct mapping_s { char* filename; char* fullname; box64env_t* env; } mapping_t; KHASH_MAP_INIT_STR(mapping_entry, mapping_t*); static kh_mapping_entry_t* mapping_entries = NULL; void RecordEnvMappings(uintptr_t addr, size_t length, int fd) { #ifndef ANDROID if (!envmap) { envmap = rbtree_init("envmap"); } if(!mapping_entries) mapping_entries = kh_init(mapping_entry); char* filename = NULL; static char fullname[4096]; if (fd > 0) { static char buf[128]; sprintf(buf, "/proc/self/fd/%d", fd); ssize_t r = readlink(buf, fullname, sizeof(fullname) - 1); if (r != -1) fullname[r] = 0; filename = strrchr(fullname, '/'); } if (!filename) return; char* lowercase_filename = LowerCase(filename); int ret; mapping_t* mapping = NULL; khint_t k = kh_get(mapping_entry, mapping_entries, lowercase_filename); if(k==kh_end(mapping_entries)) { mapping = box_calloc(1, sizeof(mapping_t)); mapping->filename = box_strdup(lowercase_filename); mapping->fullname = box_strdup(fullname); k = kh_put(mapping_entry, mapping_entries, mapping->filename, &ret); kh_value(mapping_entries, k) = mapping; khint_t k = kh_get(box64env_entry, box64env_entries, mapping->filename); if (k != kh_end(box64env_entries)) mapping->env = &kh_value(box64env_entries, k); } else mapping = kh_value(mapping_entries, k); rb_set_64(envmap, addr, addr + length, (uint64_t)mapping); if(mapping->env) { printf_log(LOG_DEBUG, "Applied [%s] of range %p:%p\n", filename, addr, addr + length); PrintEnvVariables(mapping->env, LOG_DEBUG); } #endif } void RemoveMapping(uintptr_t addr, size_t length) { if(!envmap) return; mapping_t* mapping = (mapping_t*)rb_get_64(envmap, addr); rb_unset(envmap, addr, addr+length); // quick check at next address if(mapping) { if(mapping == (mapping_t*)rb_get_64(envmap, addr+length)) return; // still present, don't purge mapping // Will traverse the tree to find any left over uintptr_t start = rb_get_lefter(envmap); uintptr_t end; uint64_t val; do { rb_get_end_64(envmap, start, &val, &end); if((mapping_t*)val==mapping) return; // found more occurance, exiting start = end; } while(end!=UINTPTR_MAX); // no occurence found, delete mapping khint_t k = kh_get(mapping_entry, mapping_entries, mapping->filename); if(k!=kh_end(mapping_entries)) kh_del(mapping_entry, mapping_entries, k); box_free(mapping->filename); box_free(mapping->fullname); box_free(mapping); } } box64env_t* GetCurEnvByAddr(uintptr_t addr) { if (!envmap) return &box64env; mapping_t* mapping = ((mapping_t*)rb_get_64(envmap, addr)); if(!mapping) return &box64env; box64env_t* env = mapping->env; if(!env) return &box64env; return env; } int IsAddrFileMapped(uintptr_t addr) { if(!envmap) return 0; mapping_t* mapping = ((mapping_t*)rb_get_64(envmap, addr)); if(!mapping) return 0; if(mapping->fullname) return 1; return 0; }