#include "plugin.h" #include "common.h" #include "ecex.h" #include "util.h" #include #include #include typedef struct ecex_plugin_slot { char *name; void *data; size_t elem_size; size_t count; int type; int exported; int export_flags; } ecex_plugin_slot_t; typedef struct ecex_plugin_object { char *name; void *ptr; size_t size; } ecex_plugin_object_t; typedef struct ecex_plugin_text { int id; char *text; size_t len; } ecex_plugin_text_t; typedef struct ecex_plugin_file_handler { char *extension; ecex_file_handler_fn fn; } ecex_plugin_file_handler_t; struct ecex_plugin { ecex_t *ed; char *id; int api_version; ecex_plugin_slot_t *slots; size_t slot_count; size_t slot_cap; ecex_plugin_object_t *objects; size_t object_count; size_t object_cap; ecex_plugin_text_t *texts; size_t text_count; size_t text_cap; ecex_plugin_file_handler_t *file_handlers; size_t file_handler_count; size_t file_handler_cap; }; struct ecex_plugin_runtime { ecex_plugin_t **plugins; size_t plugin_count; size_t plugin_cap; }; static int plugin_id_valid(const char *id) { if (!id || !id[0]) return 0; for (const unsigned char *p = (const unsigned char *)id; *p; ++p) { if ((*p >= 'a' && *p <= 'z') || (*p >= '0' && *p <= '9') || *p == '-' || *p == '_' || *p == '.') { continue; } return 0; } return 1; } static int str_eq(const char *a, const char *b) { if (!a) a = ""; if (!b) b = ""; return strcmp(a, b) == 0; } static int ascii_equal_ci(const char *a, const char *b) { unsigned char ca; unsigned char cb; if (!a || !b) return 0; while (*a && *b) { ca = (unsigned char)*a; cb = (unsigned char)*b; if (ca >= 'A' && ca <= 'Z') ca = (unsigned char)(ca - 'A' + 'a'); if (cb >= 'A' && cb <= 'Z') cb = (unsigned char)(cb - 'A' + 'a'); if (ca != cb) return 0; ++a; ++b; } return *a == '\0' && *b == '\0'; } static const char *path_extension(const char *path) { const char *dot; if (!path) return NULL; dot = strrchr(path, '.'); return dot && dot[0] ? dot : NULL; } static void slot_clear(ecex_plugin_slot_t *slot) { if (!slot) return; free(slot->name); free(slot->data); memset(slot, 0, sizeof(*slot)); } static void object_clear(ecex_plugin_object_t *object) { if (!object) return; free(object->name); free(object->ptr); memset(object, 0, sizeof(*object)); } static void text_clear(ecex_plugin_text_t *text) { if (!text) return; free(text->text); memset(text, 0, sizeof(*text)); } static void file_handler_clear(ecex_plugin_file_handler_t *handler) { if (!handler) return; free(handler->extension); memset(handler, 0, sizeof(*handler)); } static void plugin_free(ecex_plugin_t *plugin) { if (!plugin) return; for (size_t i = 0; i < plugin->slot_count; ++i) slot_clear(&plugin->slots[i]); for (size_t i = 0; i < plugin->object_count; ++i) object_clear(&plugin->objects[i]); for (size_t i = 0; i < plugin->text_count; ++i) text_clear(&plugin->texts[i]); for (size_t i = 0; i < plugin->file_handler_count; ++i) file_handler_clear(&plugin->file_handlers[i]); free(plugin->slots); free(plugin->objects); free(plugin->texts); free(plugin->file_handlers); free(plugin->id); free(plugin); } ecex_plugin_runtime_t *ecex_plugin_runtime_new(void) { return calloc(1, sizeof(ecex_plugin_runtime_t)); } void ecex_plugin_runtime_free(ecex_plugin_runtime_t *runtime) { if (!runtime) return; for (size_t i = 0; i < runtime->plugin_count; ++i) { plugin_free(runtime->plugins[i]); } free(runtime->plugins); free(runtime); } static ecex_plugin_slot_t *slot_find(ecex_plugin_t *plugin, const char *name) { if (!plugin || !name) return NULL; for (size_t i = 0; i < plugin->slot_count; ++i) { if (str_eq(plugin->slots[i].name, name)) return &plugin->slots[i]; } return NULL; } static ecex_plugin_object_t *object_find(ecex_plugin_t *plugin, void *object) { if (!plugin || !object) return NULL; for (size_t i = 0; i < plugin->object_count; ++i) { if (plugin->objects[i].ptr == object) return &plugin->objects[i]; } return NULL; } static ecex_plugin_text_t *text_find(ecex_plugin_t *plugin, int id) { if (!plugin) return NULL; for (size_t i = 0; i < plugin->text_count; ++i) { if (plugin->texts[i].id == id) return &plugin->texts[i]; } return NULL; } ecex_plugin_t *ecex_plugin_find(ecex_t *ed, const char *id) { if (!ed || !ed->plugins || !id) return NULL; for (size_t i = 0; i < ed->plugins->plugin_count; ++i) { ecex_plugin_t *plugin = ed->plugins->plugins[i]; if (plugin && str_eq(plugin->id, id)) return plugin; } return NULL; } ecex_plugin_t *ecex_plugin_register(ecex_t *ed, const char *id, int api_version) { ecex_plugin_t *plugin; if (!ed || !ed->plugins || !plugin_id_valid(id)) return NULL; if (api_version != ECEX_PLUGIN_API_VERSION) return NULL; if (ecex_plugin_find(ed, id)) return NULL; if (ECEX_GROW_ARRAY(ed->plugins->plugins, ed->plugins->plugin_count, ed->plugins->plugin_cap, 8) != ECEX_OK) { return NULL; } plugin = calloc(1, sizeof(*plugin)); if (!plugin) return NULL; plugin->id = ecex_strdup(id); if (!plugin->id) { free(plugin); return NULL; } plugin->ed = ed; plugin->api_version = api_version; ed->plugins->plugins[ed->plugins->plugin_count++] = plugin; return plugin; } ecex_plugin_t *ecex_plugin_require(ecex_t *ed, const char *id, int api_version) { ecex_plugin_t *plugin; if (api_version != ECEX_PLUGIN_API_VERSION) return NULL; plugin = ecex_plugin_find(ed, id); return plugin ? plugin : ecex_plugin_register(ed, id, api_version); } const char *ecex_plugin_id(ecex_plugin_t *plugin) { return plugin ? plugin->id : NULL; } void *ecex_plugin_slot_alloc(ecex_plugin_t *plugin, const char *name, size_t count, size_t elem_size) { ecex_plugin_slot_t *slot; void *data; if (!plugin || !name || !name[0]) return NULL; if (count == 0) count = 1; if (elem_size == 0) elem_size = 1; if (count > ((size_t)-1) / elem_size) return NULL; slot = slot_find(plugin, name); if (slot) { if (slot->elem_size != elem_size) return NULL; if (slot->count < count) { data = realloc(slot->data, count * elem_size); if (!data) return NULL; memset((char *)data + slot->count * elem_size, 0, (count - slot->count) * elem_size); slot->data = data; slot->count = count; } return slot->data; } if (ECEX_GROW_ARRAY(plugin->slots, plugin->slot_count, plugin->slot_cap, 16) != ECEX_OK) { return NULL; } data = calloc(count, elem_size); if (!data) return NULL; slot = &plugin->slots[plugin->slot_count++]; memset(slot, 0, sizeof(*slot)); slot->name = ecex_strdup(name); if (!slot->name) { free(data); --plugin->slot_count; return NULL; } slot->data = data; slot->count = count; slot->elem_size = elem_size; slot->type = elem_size == sizeof(int) ? ECEX_PLUGIN_I32 : ECEX_PLUGIN_BYTES; return slot->data; } void *ecex_plugin_slot_get(ecex_plugin_t *plugin, const char *name) { ecex_plugin_slot_t *slot = slot_find(plugin, name); return slot ? slot->data : NULL; } int ecex_plugin_slot_free(ecex_plugin_t *plugin, const char *name) { if (!plugin || !name) return ECEX_ERR; for (size_t i = 0; i < plugin->slot_count; ++i) { if (!str_eq(plugin->slots[i].name, name)) continue; slot_clear(&plugin->slots[i]); if (i + 1 < plugin->slot_count) { memmove(&plugin->slots[i], &plugin->slots[i + 1], (plugin->slot_count - i - 1) * sizeof(plugin->slots[i])); } --plugin->slot_count; return ECEX_OK; } return ECEX_ERR; } int ecex_plugin_slot_set_export_flags(ecex_plugin_t *plugin, const char *name, int type, int flags) { ecex_plugin_slot_t *slot = slot_find(plugin, name); if (!slot) return ECEX_ERR; slot->type = type; slot->exported = 1; slot->export_flags = flags; return ECEX_OK; } int ecex_plugin_slot_read_exported(ecex_t *ed, const char *plugin_id, const char *name, void *out, size_t out_cap, size_t *out_len) { ecex_plugin_t *plugin = ecex_plugin_find(ed, plugin_id); ecex_plugin_slot_t *slot = slot_find(plugin, name); size_t len; if (out_len) *out_len = 0; if (!slot || !slot->exported || !(slot->export_flags & ECEX_PLUGIN_EXPORT_READ)) return ECEX_ERR; len = slot->count * slot->elem_size; if (out_len) *out_len = len; if (!out || out_cap < len) return ECEX_ERR; if (len) memcpy(out, slot->data, len); return ECEX_OK; } int ecex_plugin_slot_i32_get(ecex_plugin_t *plugin, const char *name, size_t index, int fallback) { ecex_plugin_slot_t *slot = slot_find(plugin, name); if (!slot || !slot->data || slot->elem_size != sizeof(int) || index >= slot->count) return fallback; return ((int *)slot->data)[index]; } int ecex_plugin_slot_i32_get_scalar(ecex_plugin_t *plugin, const char *name, int fallback) { return ecex_plugin_slot_i32_get(plugin, name, 0, fallback); } int ecex_plugin_slot_i32_set(ecex_plugin_t *plugin, const char *name, size_t index, int value) { int *data; if (!plugin || !name) return ECEX_ERR; data = (int *)ecex_plugin_slot_alloc(plugin, name, index + 1, sizeof(int)); if (!data) return ECEX_ERR; data[index] = value; return ECEX_OK; } static int slot_index_2d(size_t width, size_t x, size_t y, size_t *out) { if (!out || width == 0 || x >= width) return ECEX_ERR; if (y > (((size_t)-1) - x) / width) return ECEX_ERR; *out = y * width + x; return ECEX_OK; } int ecex_plugin_slot_i32_get_2d(ecex_plugin_t *plugin, const char *name, size_t width, size_t x, size_t y, int fallback) { size_t index; if (slot_index_2d(width, x, y, &index) != ECEX_OK) return fallback; return ecex_plugin_slot_i32_get(plugin, name, index, fallback); } int ecex_plugin_slot_i32_set_2d(ecex_plugin_t *plugin, const char *name, size_t width, size_t x, size_t y, int value) { size_t index; if (slot_index_2d(width, x, y, &index) != ECEX_OK) return ECEX_ERR; return ecex_plugin_slot_i32_set(plugin, name, index, value); } int ecex_plugin_slot_i32_set_scalar(ecex_plugin_t *plugin, const char *name, int value) { return ecex_plugin_slot_i32_set(plugin, name, 0, value); } void *ecex_plugin_object_alloc(ecex_plugin_t *plugin, const char *name, size_t size) { ecex_plugin_object_t *object; void *ptr; if (!plugin) return NULL; if (size == 0) size = 1; if (ECEX_GROW_ARRAY(plugin->objects, plugin->object_count, plugin->object_cap, 16) != ECEX_OK) { return NULL; } ptr = malloc(size); if (!ptr) return NULL; object = &plugin->objects[plugin->object_count++]; memset(object, 0, sizeof(*object)); object->name = ecex_strdup(name ? name : ""); if (!object->name) { free(ptr); --plugin->object_count; return NULL; } object->ptr = ptr; object->size = size; return ptr; } void *ecex_plugin_object_calloc(ecex_plugin_t *plugin, const char *name, size_t count, size_t size) { void *ptr; size_t total; if (!plugin) return NULL; if (count == 0) count = 1; if (size == 0) size = 1; if (count > ((size_t)-1) / size) return NULL; total = count * size; ptr = ecex_plugin_object_alloc(plugin, name, total); if (!ptr) return NULL; memset(ptr, 0, total); return ptr; } int ecex_plugin_object_free(ecex_plugin_t *plugin, void *object) { if (!plugin || !object) return ECEX_ERR; for (size_t i = 0; i < plugin->object_count; ++i) { if (plugin->objects[i].ptr != object) continue; object_clear(&plugin->objects[i]); if (i + 1 < plugin->object_count) { memmove(&plugin->objects[i], &plugin->objects[i + 1], (plugin->object_count - i - 1) * sizeof(plugin->objects[i])); } --plugin->object_count; return ECEX_OK; } return ECEX_ERR; } int ecex_plugin_object_valid(ecex_plugin_t *plugin, void *object) { return object_find(plugin, object) ? 1 : 0; } int ecex_plugin_object_i32_get(ecex_plugin_t *plugin, void *object, size_t byte_offset, int fallback) { ecex_plugin_object_t *entry = object_find(plugin, object); int value; if (!entry || !entry->ptr || byte_offset > entry->size || entry->size - byte_offset < sizeof(value)) { return fallback; } memcpy(&value, (const char *)entry->ptr + byte_offset, sizeof(value)); return value; } int ecex_plugin_object_i32_set(ecex_plugin_t *plugin, void *object, size_t byte_offset, int value) { ecex_plugin_object_t *entry = object_find(plugin, object); if (!entry || !entry->ptr || byte_offset > entry->size || entry->size - byte_offset < sizeof(value)) { return ECEX_ERR; } memcpy((char *)entry->ptr + byte_offset, &value, sizeof(value)); return ECEX_OK; } void *ecex_plugin_object_ptr_get(ecex_plugin_t *plugin, void *object, size_t byte_offset) { ecex_plugin_object_t *entry = object_find(plugin, object); void *value = NULL; if (!entry || !entry->ptr || byte_offset > entry->size || entry->size - byte_offset < sizeof(value)) { return NULL; } memcpy(&value, (const char *)entry->ptr + byte_offset, sizeof(value)); return value; } int ecex_plugin_object_ptr_set(ecex_plugin_t *plugin, void *object, size_t byte_offset, void *value) { ecex_plugin_object_t *entry = object_find(plugin, object); if (!entry || !entry->ptr || byte_offset > entry->size || entry->size - byte_offset < sizeof(value)) { return ECEX_ERR; } memcpy((char *)entry->ptr + byte_offset, &value, sizeof(value)); return ECEX_OK; } int ecex_plugin_text_set(ecex_plugin_t *plugin, int id, const char *text, int len) { ecex_plugin_text_t *entry; char *copy; size_t n; if (!plugin || id < 0) return ECEX_ERR; if (!text) text = ""; n = len < 0 ? strlen(text) : (size_t)len; copy = malloc(n + 1); if (!copy) return ECEX_ERR; if (n) memcpy(copy, text, n); copy[n] = '\0'; entry = text_find(plugin, id); if (!entry) { if (ECEX_GROW_ARRAY(plugin->texts, plugin->text_count, plugin->text_cap, 32) != ECEX_OK) { free(copy); return ECEX_ERR; } entry = &plugin->texts[plugin->text_count++]; memset(entry, 0, sizeof(*entry)); entry->id = id; } free(entry->text); entry->text = copy; entry->len = n; return ECEX_OK; } int ecex_plugin_text_set_from_buffer_title(ecex_plugin_t *plugin, int id, buffer_t *buffer) { const char *title = NULL; if (buffer) title = buffer->path ? buffer->path : buffer->name; return ecex_plugin_text_set(plugin, id, title ? title : "(unnamed)", -1); } int ecex_plugin_text_free(ecex_plugin_t *plugin, int id) { if (!plugin) return ECEX_ERR; for (size_t i = 0; i < plugin->text_count; ++i) { if (plugin->texts[i].id != id) continue; text_clear(&plugin->texts[i]); if (i + 1 < plugin->text_count) { memmove(&plugin->texts[i], &plugin->texts[i + 1], (plugin->text_count - i - 1) * sizeof(plugin->texts[i])); } --plugin->text_count; return ECEX_OK; } return ECEX_ERR; } int ecex_plugin_text_free_all(ecex_plugin_t *plugin) { if (!plugin) return ECEX_ERR; for (size_t i = 0; i < plugin->text_count; ++i) text_clear(&plugin->texts[i]); plugin->text_count = 0; return ECEX_OK; } const char *ecex_plugin_text_get_drawable(ecex_t *ed, void *owner, int id) { ecex_plugin_t *plugin = (ecex_plugin_t *)owner; (void)ed; ecex_plugin_text_t *entry = text_find(plugin, id); return entry && entry->text ? entry->text : ""; } int ecex_plugin_file_handler_register(ecex_plugin_t *plugin, const char *extension, ecex_file_handler_fn fn) { ecex_plugin_file_handler_t *handler; if (!plugin || !extension || !extension[0] || !fn) return ECEX_ERR; for (size_t i = 0; i < plugin->file_handler_count; ++i) { if (ascii_equal_ci(plugin->file_handlers[i].extension, extension)) { plugin->file_handlers[i].fn = fn; return ECEX_OK; } } if (ECEX_GROW_ARRAY(plugin->file_handlers, plugin->file_handler_count, plugin->file_handler_cap, 8) != ECEX_OK) { return ECEX_ERR; } handler = &plugin->file_handlers[plugin->file_handler_count++]; memset(handler, 0, sizeof(*handler)); handler->extension = ecex_strdup(extension); if (!handler->extension) { --plugin->file_handler_count; return ECEX_ERR; } handler->fn = fn; return ECEX_OK; } int ecex_plugin_file_handlers_run(ecex_t *ed, buffer_t *buffer) { const char *path; const char *ext; if (!ed || !ed->plugins || !buffer) return ECEX_ERR; path = buffer->path ? buffer->path : buffer->name; ext = path_extension(path); if (!ext) return ECEX_OK; for (size_t i = 0; i < ed->plugins->plugin_count; ++i) { ecex_plugin_t *plugin = ed->plugins->plugins[i]; if (!plugin) continue; for (size_t j = 0; j < plugin->file_handler_count; ++j) { if (ascii_equal_ci(plugin->file_handlers[j].extension, ext)) { return plugin->file_handlers[j].fn(ed, buffer); } } } return ECEX_OK; }