static bool rtmp_stream_start(void *data) { struct rtmp_stream *stream = data; obs_service_t *service = obs_output_get_service(stream->output); obs_data_t *settings; if (!obs_output_can_begin_data_capture(stream->output, 0)) return false; if (!obs_output_initialize_encoders(stream->output, 0)) return false; stream->total_bytes_sent = 0; stream->dropped_frames = 0; settings = obs_output_get_settings(stream->output); dstr_copy(&stream->path, obs_service_get_url(service)); dstr_copy(&stream->key, obs_service_get_key(service)); dstr_copy(&stream->username, obs_service_get_username(service)); dstr_copy(&stream->password, obs_service_get_password(service)); stream->drop_threshold_usec = (int64_t)obs_data_get_int(settings, OPT_DROP_THRESHOLD) * 1000; obs_data_release(settings); return pthread_create(&stream->connect_thread, NULL, connect_thread, stream) == 0; }
static obs_properties_t *color_grade_filter_properties(void *data) { struct lut_filter_data *s = data; struct dstr path = {0}; const char *slash; obs_properties_t *props = obs_properties_create(); struct dstr filter_str = {0}; dstr_cat(&filter_str, "(*.png)"); if (s && s->file && *s->file) { dstr_copy(&path, s->file); } else { dstr_copy(&path, obs_module_file("LUTs")); dstr_cat_ch(&path, '/'); } dstr_replace(&path, "\\", "/"); slash = strrchr(path.array, '/'); if (slash) dstr_resize(&path, slash - path.array + 1); obs_properties_add_path(props, SETTING_IMAGE_PATH, TEXT_IMAGE_PATH, OBS_PATH_FILE, filter_str.array, path.array); obs_properties_add_float_slider(props, SETTING_CLUT_AMOUNT, TEXT_AMOUNT, 0, 1, 0.01); dstr_free(&filter_str); UNUSED_PARAMETER(data); return props; }
static bool init_connect(struct rtmp_stream *stream) { obs_service_t *service; obs_data_t *settings; if (stopping(stream)) pthread_join(stream->send_thread, NULL); free_packets(stream); service = obs_output_get_service(stream->output); if (!service) return false; os_atomic_set_bool(&stream->disconnected, false); stream->total_bytes_sent = 0; stream->dropped_frames = 0; stream->min_drop_dts_usec= 0; stream->min_priority = 0; settings = obs_output_get_settings(stream->output); dstr_copy(&stream->path, obs_service_get_url(service)); dstr_copy(&stream->key, obs_service_get_key(service)); dstr_copy(&stream->username, obs_service_get_username(service)); dstr_copy(&stream->password, obs_service_get_password(service)); stream->drop_threshold_usec = (int64_t)obs_data_get_int(settings, OPT_DROP_THRESHOLD) * 1000; stream->max_shutdown_time_sec = (int)obs_data_get_int(settings, OPT_MAX_SHUTDOWN_TIME_SEC); obs_data_release(settings); return true; }
const char *get_font_path(const char *family, uint16_t size, const char *style, uint32_t flags, FT_Long *idx) { const char *best_path = NULL; double best_rating = 0.0; struct dstr face_and_style = {0}; struct dstr style_str = {0}; bool bold = !!(flags & OBS_FONT_BOLD); bool italic = !!(flags & OBS_FONT_ITALIC); if (!family || !*family) return NULL; if (style) { dstr_copy(&style_str, style); dstr_replace(&style_str, "Bold", ""); dstr_replace(&style_str, "Italic", ""); dstr_replace(&style_str, " ", " "); dstr_depad(&style_str); } dstr_copy(&face_and_style, family); if (!dstr_is_empty(&style_str)) { dstr_cat(&face_and_style, " "); dstr_cat_dstr(&face_and_style, &style_str); } for (size_t i = 0; i < font_list.num; i++) { struct font_path_info *info = font_list.array + i; double rating = (double)get_rating(info, &face_and_style); if (rating < info->face_len) continue; if (info->is_bitmap) { int best_diff = 1000; for (size_t j = 0; j < info->num_sizes; j++) { int diff = abs(info->sizes[j] - size); if (diff < best_diff) best_diff = diff; } rating /= (double)(best_diff + 1.0); } if (info->bold == bold) rating += 1.0; if (info->italic == italic) rating += 1.0; if (rating > best_rating) { best_path = info->path; *idx = info->index; best_rating = rating; } } dstr_free(&style_str); dstr_free(&face_and_style); return best_path; }
static bool init_connect(struct rtmp_stream *stream) { obs_service_t *service; obs_data_t *settings; const char *bind_ip; int64_t drop_p; int64_t drop_b; if (stopping(stream)) { pthread_join(stream->send_thread, NULL); } free_packets(stream); service = obs_output_get_service(stream->output); if (!service) return false; os_atomic_set_bool(&stream->disconnected, false); stream->total_bytes_sent = 0; stream->dropped_frames = 0; stream->min_priority = 0; settings = obs_output_get_settings(stream->output); dstr_copy(&stream->path, obs_service_get_url(service)); dstr_copy(&stream->key, obs_service_get_key(service)); dstr_copy(&stream->username, obs_service_get_username(service)); dstr_copy(&stream->password, obs_service_get_password(service)); dstr_depad(&stream->path); dstr_depad(&stream->key); drop_b = (int64_t)obs_data_get_int(settings, OPT_DROP_THRESHOLD); drop_p = (int64_t)obs_data_get_int(settings, OPT_PFRAME_DROP_THRESHOLD); stream->max_shutdown_time_sec = (int)obs_data_get_int(settings, OPT_MAX_SHUTDOWN_TIME_SEC); if (drop_p < (drop_b + 200)) drop_p = drop_b + 200; stream->drop_threshold_usec = 1000 * drop_b; stream->pframe_drop_threshold_usec = 1000 * drop_p; bind_ip = obs_data_get_string(settings, OPT_BIND_IP); dstr_copy(&stream->bind_ip, bind_ip); stream->new_socket_loop = obs_data_get_bool(settings, OPT_NEWSOCKETLOOP_ENABLED); stream->low_latency_mode = obs_data_get_bool(settings, OPT_LOWLATENCY_ENABLED); obs_data_release(settings); return true; }
static void gl_write_main_storage_assign(struct gl_shader_parser *glsp, struct shader_var *var, const char *dst, const char *src, bool input) { struct shader_struct *st; struct dstr dst_copy = {0}; char ch_left = input ? '.' : '_'; char ch_right = input ? '_' : '.'; if (dst) { dstr_copy(&dst_copy, dst); dstr_cat_ch(&dst_copy, ch_left); } else { dstr_copy(&dst_copy, "\t"); } dstr_cat(&dst_copy, var->name); st = shader_parser_getstruct(&glsp->parser, var->type); if (st) { struct dstr src_copy = {0}; size_t i; if (src) dstr_copy(&src_copy, src); dstr_cat(&src_copy, var->name); dstr_cat_ch(&src_copy, ch_right); for (i = 0; i < st->vars.num; i++) { struct shader_var *st_var = st->vars.array+i; gl_write_main_storage_assign(glsp, st_var, dst_copy.array, src_copy.array, input); } dstr_free(&src_copy); } else { if (!dstr_isempty(&dst_copy)) dstr_cat_dstr(&glsp->gl_string, &dst_copy); dstr_cat(&glsp->gl_string, " = "); if (src) dstr_cat(&glsp->gl_string, src); dstr_cat(&glsp->gl_string, var->name); dstr_cat(&glsp->gl_string, ";\n"); if (!input) gl_write_main_interface_assign(glsp, var, src); } dstr_free(&dst_copy); }
static void add_font_path(FT_Face face, FT_Long idx, const char *family_in, const char *style_in, const char *path) { struct dstr face_and_style = {0}; struct font_path_info info; if (!family_in || !path) return; dstr_copy(&face_and_style, family_in); if (face->style_name) { struct dstr style = {0}; dstr_copy(&style, style_in); dstr_replace(&style, "Bold", ""); dstr_replace(&style, "Italic", ""); dstr_replace(&style, " ", " "); dstr_depad(&style); if (!dstr_is_empty(&style)) { dstr_cat(&face_and_style, " "); dstr_cat_dstr(&face_and_style, &style); } dstr_free(&style); } info.face_and_style = face_and_style.array; info.full_len = face_and_style.len; info.face_len = strlen(family_in); info.is_bitmap = !!(face->face_flags & FT_FACE_FLAG_FIXED_SIZES); info.bold = !!(face->style_flags & FT_STYLE_FLAG_BOLD); info.italic = !!(face->style_flags & FT_STYLE_FLAG_ITALIC); info.index = idx; info.path = bstrdup(path); create_bitmap_sizes(&info, face); da_push_back(font_list, &info); /*blog(LOG_DEBUG, "name: %s\n\tstyle: %s\n\tpath: %s\n", family_in, style_in, path);*/ }
static obs_properties_t *image_source_properties(void *data) { struct image_source *s = data; struct dstr path = {0}; obs_properties_t *props = obs_properties_create(); if (s && s->file && *s->file) { const char *slash; dstr_copy(&path, s->file); dstr_replace(&path, "\\", "/"); slash = strrchr(path.array, '/'); if (slash) dstr_resize(&path, slash - path.array + 1); } obs_properties_add_path(props, "file", obs_module_text("File"), OBS_PATH_FILE, image_filter, path.array); obs_properties_add_bool(props, "unload", obs_module_text("UnloadWhenNotShowing")); dstr_free(&path); return props; }
os_dir_t os_opendir(const char *path) { struct dstr path_str = {0}; struct os_dir *dir = NULL; WIN32_FIND_DATA wfd; HANDLE handle; wchar_t *w_path; dstr_copy(&path_str, path); dstr_cat(&path_str, "/*.*"); if (os_utf8_to_wcs_ptr(path_str.array, path_str.len, &w_path) > 0) { handle = FindFirstFileW(w_path, &wfd); if (handle != INVALID_HANDLE_VALUE) { dir = bzalloc(sizeof(struct os_dir)); dir->handle = handle; dir->first = true; dir->wfd = wfd; } bfree(w_path); } dstr_free(&path_str); return dir; }
/* * List formats for device */ static void v4l2_format_list(int dev, obs_property_t *prop) { struct v4l2_fmtdesc fmt; fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.index = 0; struct dstr buffer; dstr_init(&buffer); obs_property_list_clear(prop); while (v4l2_ioctl(dev, VIDIOC_ENUM_FMT, &fmt) == 0) { dstr_copy(&buffer, (char *) fmt.description); if (fmt.flags & V4L2_FMT_FLAG_EMULATED) dstr_cat(&buffer, " (Emulated)"); if (v4l2_to_obs_video_format(fmt.pixelformat) != VIDEO_FORMAT_NONE) { obs_property_list_add_int(prop, buffer.array, fmt.pixelformat); blog(LOG_INFO, "Pixelformat: %s (available)", buffer.array); } else { blog(LOG_INFO, "Pixelformat: %s (unavailable)", buffer.array); } fmt.index++; } dstr_free(&buffer); }
static bool ffmpeg_mux_start(void *data) { struct ffmpeg_muxer *stream = data; obs_data_t *settings; struct dstr cmd; const char *path; if (!obs_output_can_begin_data_capture(stream->output, 0)) return false; if (!obs_output_initialize_encoders(stream->output, 0)) return false; settings = obs_output_get_settings(stream->output); path = obs_data_get_string(settings, "path"); dstr_copy(&stream->path, path); dstr_replace(&stream->path, "\"", "\"\""); obs_data_release(settings); build_command_line(stream, &cmd); stream->pipe = os_process_pipe_create(cmd.array, "w"); dstr_free(&cmd); if (!stream->pipe) { warn("Failed to create process pipe"); return false; } /* write headers and start capture */ stream->active = true; stream->capturing = true; obs_output_begin_data_capture(stream->output, 0); info("Writing file '%s'...", stream->path.array); return true; }
static bool parse_binary_from_directory(struct dstr *parsed_bin_path, const char *bin_path, const char *file) { struct dstr directory = {0}; bool found = true; dstr_copy(&directory, bin_path); dstr_replace(&directory, "%module%", file); if (dstr_end(&directory) != '/') dstr_cat_ch(&directory, '/'); dstr_copy_dstr(parsed_bin_path, &directory); dstr_cat(parsed_bin_path, file); dstr_cat(parsed_bin_path, get_module_extension()); if (!os_file_exists(parsed_bin_path->array)) { /* if the file doesn't exist, check with 'lib' prefix */ dstr_copy_dstr(parsed_bin_path, &directory); dstr_cat(parsed_bin_path, "lib"); dstr_cat(parsed_bin_path, file); dstr_cat(parsed_bin_path, get_module_extension()); /* if neither exist, don't include this as a library */ if (!os_file_exists(parsed_bin_path->array)) { dstr_free(parsed_bin_path); found = false; } } dstr_free(&directory); return found; }
static bool flv_output_start(void *data) { struct flv_output *stream = data; obs_data_t *settings; const char *path; if (!obs_output_can_begin_data_capture(stream->output, 0)) return false; if (!obs_output_initialize_encoders(stream->output, 0)) return false; /* get path */ settings = obs_output_get_settings(stream->output); path = obs_data_get_string(settings, "path"); dstr_copy(&stream->path, path); obs_data_release(settings); stream->file = os_fopen(stream->path.array, "wb"); if (!stream->file) { warn("Unable to open FLV file '%s'", stream->path.array); return false; } /* write headers and start capture */ stream->active = true; obs_output_begin_data_capture(stream->output, 0); info("Writing FLV file '%s'...", stream->path.array); return true; }
/* internal initialization */ bool obs_source_init(struct obs_source *source, const char *settings, const struct source_info *info) { uint32_t flags = info->get_output_flags(source->data); source->refs = 1; source->volume = 1.0f; pthread_mutex_init_value(&source->filter_mutex); pthread_mutex_init_value(&source->video_mutex); pthread_mutex_init_value(&source->audio_mutex); dstr_copy(&source->settings, settings); memcpy(&source->callbacks, info, sizeof(struct source_info)); if (pthread_mutex_init(&source->filter_mutex, NULL) != 0) return false; if (pthread_mutex_init(&source->audio_mutex, NULL) != 0) return false; if (pthread_mutex_init(&source->video_mutex, NULL) != 0) return false; if (flags & SOURCE_AUDIO) { source->audio_line = audio_output_createline(obs->audio.audio, source->name); if (!source->audio_line) { blog(LOG_ERROR, "Failed to create audio line for " "source '%s'", source->name); return false; } } return true; }
static inline void make_data_dir(struct dstr *parsed_data_dir, const char *data_dir, const char *name) { dstr_copy(parsed_data_dir, data_dir); dstr_replace(parsed_data_dir, "%module%", name); if (dstr_end(parsed_data_dir) == '/') dstr_resize(parsed_data_dir, parsed_data_dir->len - 1); }
static inline char *decode_str(const char *src) { struct dstr str = {0}; dstr_copy(&str, src); dstr_replace(&str, "#3A", ":"); dstr_replace(&str, "#22", "#"); return str.array; }
lookup_t *obs_module_load_locale(obs_module_t *module, const char *default_locale, const char *locale) { struct dstr str = {0}; lookup_t *lookup = NULL; if (!module || !default_locale || !locale) { blog(LOG_WARNING, "obs_module_load_locale: Invalid parameters"); return NULL; } dstr_copy(&str, "locale/"); dstr_cat(&str, default_locale); dstr_cat(&str, ".ini"); char *file = obs_find_module_file(module, str.array); if (file) lookup = text_lookup_create(file); bfree(file); if (!lookup) { blog(LOG_WARNING, "Failed to load '%s' text for module: '%s'", default_locale, module->file); goto cleanup; } if (astrcmpi(locale, default_locale) == 0) goto cleanup; dstr_copy(&str, "/locale/"); dstr_cat(&str, locale); dstr_cat(&str, ".ini"); file = obs_find_module_file(module, str.array); if (!text_lookup_add(lookup, file)) blog(LOG_WARNING, "Failed to load '%s' text for module: '%s'", locale, module->file); bfree(file); cleanup: dstr_free(&str); return lookup; }
bool os_quick_write_utf8_file_safe(const char *path, const char *str, size_t len, bool marker, const char *temp_ext, const char *backup_ext) { struct dstr backup_path = {0}; struct dstr temp_path = {0}; bool success = false; if (!temp_ext || !*temp_ext) { blog(LOG_ERROR, "os_quick_write_utf8_file_safe: invalid " "temporary extension specified"); return false; } dstr_copy(&temp_path, path); if (*temp_ext != '.') dstr_cat(&temp_path, "."); dstr_cat(&temp_path, temp_ext); if (!os_quick_write_utf8_file(temp_path.array, str, len, marker)) { goto cleanup; } if (backup_ext && *backup_ext) { dstr_copy(&backup_path, path); if (*backup_ext != '.') dstr_cat(&backup_path, "."); dstr_cat(&backup_path, backup_ext); os_unlink(backup_path.array); os_rename(path, backup_path.array); dstr_free(&backup_path); } else { os_unlink(path); } os_rename(temp_path.array, path); success = true; cleanup: dstr_free(&backup_path); dstr_free(&temp_path); return success; }
int config_save(config_t *config) { FILE *f; struct dstr str, tmp; size_t i, j; if (!config) return CONFIG_ERROR; if (!config->file) return CONFIG_ERROR; dstr_init(&str); dstr_init(&tmp); f = os_fopen(config->file, "wb"); if (!f) return CONFIG_FILENOTFOUND; for (i = 0; i < config->sections.num; i++) { struct config_section *section = darray_item( sizeof(struct config_section), &config->sections, i); if (i) dstr_cat(&str, "\n"); dstr_cat(&str, "["); dstr_cat(&str, section->name); dstr_cat(&str, "]\n"); for (j = 0; j < section->items.num; j++) { struct config_item *item = darray_item( sizeof(struct config_item), §ion->items, j); dstr_copy(&tmp, item->value ? item->value : ""); dstr_replace(&tmp, "\\", "\\\\"); dstr_replace(&tmp, "\r", "\\r"); dstr_replace(&tmp, "\n", "\\n"); dstr_cat(&str, item->name); dstr_cat(&str, "="); dstr_cat(&str, tmp.array); dstr_cat(&str, "\n"); } } #ifdef _WIN32 fwrite("\xEF\xBB\xBF", 1, 3, f); #endif fwrite(str.array, 1, str.len, f); fclose(f); dstr_free(&tmp); dstr_free(&str); return CONFIG_SUCCESS; }
static inline bool check_path(const char* data, const char *path, struct dstr * output) { dstr_copy(output, path); dstr_cat(output, data); blog(LOG_DEBUG, "Attempting path: %s\n", output->array); return os_file_exists(output->array); }
static inline void init_cpu_info(struct exception_handler_data *data) { HKEY key; LSTATUS status; status = RegOpenKeyW(HKEY_LOCAL_MACHINE, PROCESSOR_REG_KEY, &key); if (status == ERROR_SUCCESS) { wchar_t str[1024]; DWORD size = 1024; status = RegQueryValueExW(key, L"ProcessorNameString", NULL, NULL, (LPBYTE)str, &size); if (status == ERROR_SUCCESS) dstr_from_wcs(&data->cpu_info, str); else dstr_copy(&data->cpu_info, CPU_ERROR); } else { dstr_copy(&data->cpu_info, CPU_ERROR); } }
static void process_found_module(struct obs_module_path *omp, const char *path, bool directory, obs_find_module_callback_t callback, void *param) { struct obs_module_info info; struct dstr name = {0}; struct dstr parsed_bin_path = {0}; const char *file; char *parsed_data_dir; bool bin_found = true; file = strrchr(path, '/'); file = file ? (file + 1) : path; if (strcmp(file, ".") == 0 || strcmp(file, "..") == 0) return; dstr_copy(&name, file); if (!directory) { char *ext = strrchr(name.array, '.'); if (ext) dstr_resize(&name, ext - name.array); dstr_copy(&parsed_bin_path, path); } else { bin_found = parse_binary_from_directory(&parsed_bin_path, omp->bin, file); } parsed_data_dir = make_data_directory(name.array, omp->data); if (parsed_data_dir && bin_found) { info.bin_path = parsed_bin_path.array; info.data_path = parsed_data_dir; callback(param, &info); } bfree(parsed_data_dir); dstr_free(&name); dstr_free(&parsed_bin_path); }
static char *get_path(const char *dir, const char *file) { struct dstr str = {0}; dstr_copy(&str, dir); if (str.array && dstr_end(&str) != '/' && dstr_end(&str) != '\\') dstr_cat_ch(&str, '/'); dstr_cat(&str, file); return str.array; }
char *obs_module_get_config_path(obs_module_t *module, const char *file) { struct dstr output = {0}; dstr_copy(&output, obs->module_config_path); if (!dstr_is_empty(&output) && dstr_end(&output) != '/') dstr_cat_ch(&output, '/'); dstr_cat(&output, module->mod_name); dstr_cat_ch(&output, '/'); dstr_cat(&output, file); return output.array; }
static bool init_update(struct update_info *info) { struct dstr user_agent = {0}; info->curl = curl_easy_init(); if (!info->curl) { warn("Could not initialize Curl"); return false; } info->local_package = get_package(info->local, "package.json"); info->cache_package = get_package(info->cache, "package.json"); obs_data_t *metadata = get_package(info->cache, "meta.json"); if (metadata) { const char *etag = obs_data_get_string(metadata, "etag"); if (etag) { struct dstr if_none_match = { 0 }; dstr_copy(&if_none_match, "If-None-Match: "); dstr_cat(&if_none_match, etag); info->etag_local = bstrdup(etag); info->header = curl_slist_append(info->header, if_none_match.array); } obs_data_release(metadata); } dstr_copy(&user_agent, "User-Agent: "); dstr_cat(&user_agent, info->user_agent); info->header = curl_slist_append(info->header, user_agent.array); dstr_free(&user_agent); return true; }
static inline void do_log(int level, const char *msg, ...) { va_list args; struct dstr str = {0}; va_start(args, msg); dstr_copy(&str, "[GDI monitor capture]: "); dstr_vcatf(&str, msg, args); blog(level, "%s", str.array); dstr_free(&str); va_end(args); }
void dstr_safe_printf(struct dstr *dst, const char *format, const char *val1, const char *val2, const char *val3, const char *val4) { dstr_copy(dst, format); if (val1) dstr_replace(dst, "$1", val1); if (val2) dstr_replace(dst, "$2", val2); if (val3) dstr_replace(dst, "$3", val3); if (val4) dstr_replace(dst, "$4", val4); }
static inline char *get_module_name(const char *file) { static size_t ext_len = 0; struct dstr name = {0}; if (ext_len == 0) { const char *ext = get_module_extension(); ext_len = strlen(ext); } dstr_copy(&name, file); dstr_resize(&name, name.len - ext_len); return name.array; }
update_info_t *update_info_create( const char *log_prefix, const char *user_agent, const char *update_url, const char *local_dir, const char *cache_dir, confirm_file_callback_t confirm_callback, void *param) { struct update_info *info; struct dstr dir = {0}; if (!log_prefix) log_prefix = ""; if (os_mkdir(cache_dir) < 0) { blog(LOG_WARNING, "%sCould not create cache directory %s", log_prefix, cache_dir); return NULL; } dstr_copy(&dir, cache_dir); if (dstr_end(&dir) != '/' && dstr_end(&dir) != '\\') dstr_cat_ch(&dir, '/'); dstr_cat(&dir, ".temp"); if (os_mkdir(dir.array) < 0) { blog(LOG_WARNING, "%sCould not create temp directory %s", log_prefix, cache_dir); dstr_free(&dir); return NULL; } info = bzalloc(sizeof(*info)); info->log_prefix = bstrdup(log_prefix); info->user_agent = bstrdup(user_agent); info->temp = dir.array; info->local = bstrdup(local_dir); info->cache = bstrdup(cache_dir); info->url = get_path(update_url, "package.json"); info->callback = confirm_callback; info->param = param; if (pthread_create(&info->thread, NULL, update_thread, info) == 0) info->thread_created = true; return info; }
bool load_graphics_offsets(bool is32bit) { char *offset_exe_path = NULL; struct dstr offset_exe = {0}; char *config_ini = NULL; struct dstr str = {0}; os_process_pipe_t *pp; bool success = false; char data[128]; dstr_copy(&offset_exe, "get-graphics-offsets"); dstr_cat(&offset_exe, is32bit ? "32.exe" : "64.exe"); offset_exe_path = obs_module_file(offset_exe.array); pp = os_process_pipe_create(offset_exe_path, "r"); if (!pp) { blog(LOG_INFO, "load_graphics_offsets: Failed to start '%s'", offset_exe.array); goto error; } for (;;) { size_t len = os_process_pipe_read(pp, (uint8_t*)data, 128); if (!len) break; dstr_ncat(&str, data, len); } config_ini = obs_module_config_path(is32bit ? "32.ini" : "64.ini"); os_quick_write_utf8_file_safe(config_ini, str.array, str.len, false, "tmp", NULL); bfree(config_ini); success = load_offsets_from_string(is32bit ? &offsets32 : &offsets64, str.array); if (!success) { blog(LOG_INFO, "load_graphics_offsets: Failed to load string"); } os_process_pipe_destroy(pp); error: bfree(offset_exe_path); dstr_free(&offset_exe); dstr_free(&str); return success; }