/* SPDX-License-Identifier: MIT */ /* * mzsurvey.c * * Copyright (c) 2026 KonAki * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /* * usage: mzsurvey [plugins.js] * reads an exported RPG Maker MV or MZ game and says what is in it and how much of it the engine can do */ #define KONJSON_IMPLEMENTATION #include "konjson.h" #include #include #include /*** counters ***/ typedef struct count { char key[120]; int n; } count_t; typedef struct table { count_t *rows; int count; int capacity; } table_t; static void tally(table_t *t, const char *key) { int i; for (i = 0; i < t->count; i++) { if (!strcmp(t->rows[i].key, key)) { t->rows[i].n++; return; } } if (t->count == t->capacity) { int grown = t->capacity ? t->capacity * 2 : 64; count_t *rows = realloc(t->rows, (size_t)grown * sizeof *rows); if (!rows) return; t->rows = rows; t->capacity = grown; } snprintf(t->rows[t->count].key, sizeof t->rows[t->count].key, "%s", key); t->rows[t->count].n = 1; t->count++; } static int byCount(const void *a, const void *b) { const count_t *x = a, *y = b; if (x->n != y->n) return y->n - x->n; return strcmp(x->key, y->key); } static void show(table_t *t, const char *title, int limit) { int i; if (!t->count) return; qsort(t->rows, (size_t)t->count, sizeof *t->rows, byCount); printf("\n%s (%d)\n", title, t->count); for (i = 0; i < t->count && i < limit; i++) printf(" %6d %s\n", t->rows[i].n, t->rows[i].key); if (t->count > limit) printf(" ... and %d more\n", t->count - limit); } /*** event commands ***/ enum { TODO, PARTIAL, SCRIPT }; typedef struct command { int code; const char *name; int status; } command_t; static const command_t commands[] = { {0, "end of list", SCRIPT}, {101, "show text", SCRIPT}, {102, "show choices", PARTIAL}, {103, "input number", TODO}, {104, "select item", TODO}, {105, "show scrolling text", TODO}, {108, "comment", SCRIPT}, {109, "skip", TODO}, {111, "conditional branch", PARTIAL}, {112, "loop", TODO}, {113, "break loop", TODO}, {115, "exit event processing", SCRIPT}, {116, "erase event", TODO}, {117, "common event", SCRIPT}, {118, "label", SCRIPT}, {119, "jump to label", SCRIPT}, {121, "control switches", SCRIPT}, {122, "control variables", PARTIAL}, {123, "control self switch", TODO}, {124, "control timer", TODO}, {125, "change gold", TODO}, {126, "change items", TODO}, {127, "change weapons", TODO}, {128, "change armor", TODO}, {129, "change party member", TODO}, {132, "change battle bgm", TODO}, {133, "change victory me", TODO}, {134, "change save access", TODO}, {135, "change menu access", TODO}, {136, "change encounter", TODO}, {137, "change formation access", TODO}, {138, "change window color", TODO}, {139, "change defeat me", TODO}, {140, "change vehicle bgm", TODO}, {201, "transfer player", TODO}, {202, "set vehicle location", TODO}, {203, "set event location", TODO}, {204, "scroll map", TODO}, {205, "set movement route", TODO}, {206, "get on or off vehicle", TODO}, {211, "change transparency", TODO}, {212, "show animation", TODO}, {213, "show balloon icon", TODO}, {214, "erase event", TODO}, {216, "change player followers", TODO}, {217, "gather followers", TODO}, {221, "fadeout screen", TODO}, {222, "fadein screen", TODO}, {223, "tint screen", TODO}, {224, "flash screen", TODO}, {225, "shake screen", TODO}, {230, "wait", SCRIPT}, {231, "show picture", TODO}, {232, "move picture", TODO}, {233, "rotate picture", TODO}, {234, "tint picture", TODO}, {235, "erase picture", TODO}, {236, "set weather effect", TODO}, {241, "play bgm", TODO}, {242, "fadeout bgm", TODO}, {243, "save bgm", TODO}, {244, "resume bgm", TODO}, {245, "play bgs", TODO}, {246, "fadeout bgs", TODO}, {249, "play me", TODO}, {250, "play se", TODO}, {251, "stop se", TODO}, {261, "play movie", TODO}, {281, "change map name display", TODO}, {282, "change tileset", TODO}, {283, "change battle back", TODO}, {284, "change parallax", TODO}, {285, "get location info", TODO}, {301, "battle processing", TODO}, {302, "shop processing", TODO}, {303, "name input processing", TODO}, {311, "change hp", TODO}, {312, "change mp", TODO}, {313, "change state", TODO}, {314, "recover all", TODO}, {315, "change exp", TODO}, {316, "change level", TODO}, {317, "change parameter", TODO}, {318, "change skill", TODO}, {319, "change equipment", TODO}, {320, "change name", TODO}, {321, "change class", TODO}, {322, "change actor images", TODO}, {323, "change vehicle image", TODO}, {324, "change nickname", TODO}, {325, "change profile", TODO}, {326, "change tp", TODO}, {331, "change enemy hp", TODO}, {332, "change enemy mp", TODO}, {333, "change enemy state", TODO}, {334, "enemy recover all", TODO}, {335, "enemy appear", TODO}, {336, "enemy transform", TODO}, {337, "show battle animation", TODO}, {339, "force action", TODO}, {340, "abort battle", TODO}, {342, "change enemy tp", TODO}, {351, "open menu screen", TODO}, {352, "open save screen", TODO}, {353, "game over", TODO}, {354, "return to title screen", TODO}, {355, "script", TODO}, {356, "plugin command (mv)", TODO}, {357, "plugin command", TODO}, {401, "text line", SCRIPT}, {402, "when choice", PARTIAL}, {403, "when cancel", PARTIAL}, {404, "end of choices", PARTIAL}, {405, "scrolling text line", TODO}, {408, "comment line", SCRIPT}, {411, "else", PARTIAL}, {412, "end of branch", PARTIAL}, {413, "repeat above", TODO}, {505, "movement route step", TODO}, {601, "if win", TODO}, {602, "if escape", TODO}, {603, "if lose", TODO}, {604, "end of battle result", TODO}, {605, "shop item line", TODO}, {655, "script line", TODO}, {657, "plugin command line", TODO} }; static const command_t *commandFor(int code) { size_t i; for (i = 0; i < sizeof commands / sizeof *commands; i++) { if (commands[i].code == code) return &commands[i]; } return NULL; } /*** survey ***/ static table_t codes, pluginCommands, scriptCalls, noteTags, tilesets; static int mapCount, eventCount, pageCount, listCount, conditionScripts, variableScripts, parallelPages, autorunPages; static void tagNotes(const char *note) { const char *p = note; while (note && (p = strchr(p, '<'))) { const char *q = p + 1; char key[64]; size_t n = 0; if (*q == '/') { p = q; continue; } while (*q && *q != '>' && *q != ':' && *q != ' ' && *q != '\n' && n < sizeof key - 1) key[n++] = *q++; key[n] = '\0'; if (n && *q) tally(¬eTags, key); p = q; } } static void noteTable(const kon_json_t *list) { int i; for (i = 0; i < kon_jsonCount(list); i++) tagNotes(kon_jsonString(kon_jsonGet(kon_jsonAt(list, i), "note"), NULL)); } static void list(const kon_json_t *commandList) { int i; if (!commandList) return; listCount++; for (i = 0; i < kon_jsonCount(commandList); i++) { const kon_json_t *c = kon_jsonAt(commandList, i); const kon_json_t *p = kon_jsonGet(c, "parameters"); int code = kon_jsonInt(kon_jsonGet(c, "code"), -1); char key[16]; snprintf(key, sizeof key, "%d", code); tally(&codes, key); if (code == 357) { char name[120]; snprintf(name, sizeof name, "%s : %s", kon_jsonString(kon_jsonAt(p, 0), "?"), kon_jsonString(kon_jsonAt(p, 1), "?")); tally(&pluginCommands, name); } else if (code == 356) { char name[120]; const char *text = kon_jsonString(kon_jsonAt(p, 0), "?"); size_t n = strcspn(text, " "); snprintf(name, sizeof name, "%.*s", (int)(n < 100 ? n : 100), text); tally(&pluginCommands, name); } else if (code == 355) { char name[120]; snprintf(name, sizeof name, "%.100s", kon_jsonString(kon_jsonAt(p, 0), "?")); tally(&scriptCalls, name); } else if (code == 111 && kon_jsonInt(kon_jsonAt(p, 0), 0) == 12) { conditionScripts++; } else if (code == 122 && kon_jsonInt(kon_jsonAt(p, 3), 0) == 4) { variableScripts++; } } } static void pages(const kon_json_t *eventPages) { int i; for (i = 0; i < kon_jsonCount(eventPages); i++) { const kon_json_t *page = kon_jsonAt(eventPages, i); int trigger = kon_jsonInt(kon_jsonGet(page, "trigger"), 0); pageCount++; if (trigger == 3) autorunPages++; if (trigger == 4) parallelPages++; list(kon_jsonGet(page, "list")); } } static int survey(const char *folder) { char path[1024], error[120]; const kon_json_t *system; kon_json_t *json; int id; snprintf(path, sizeof path, "%s/System.json", folder); json = kon_jsonParseFile(path, error, sizeof error); if (!json) { fprintf(stderr, "%s: %s\n", path, error); return 1; } system = json; printf("%s\n", kon_jsonString(kon_jsonGet(system, "gameTitle"), "(untitled)")); printf(" %s, window %dx%d, tiles %d, %s battles\n", kon_jsonGet(system, "advanced") ? "RPG Maker MZ" : "RPG Maker MV", kon_jsonInt(kon_jsonGet(kon_jsonGet(system, "advanced"), "screenWidth"), 816), kon_jsonInt(kon_jsonGet(kon_jsonGet(system, "advanced"), "screenHeight"), 624), kon_jsonInt(kon_jsonGet(system, "tileSize"), 48), kon_jsonBool(kon_jsonGet(system, "optSideView"), 0) ? "side view" : "front view"); printf(" switches %d, variables %d, start map %d\n", kon_jsonCount(kon_jsonGet(system, "switches")) - 1, kon_jsonCount(kon_jsonGet(system, "variables")) - 1, kon_jsonInt(kon_jsonGet(system, "startMapId"), 0)); kon_jsonFree(json); { static const char *const names[] = {"Actors", "Classes", "Skills", "Items", "Weapons", "Armors", "Enemies", "Troops", "States", "Animations", "Tilesets", "CommonEvents"}; size_t i; printf("\ndatabase\n"); for (i = 0; i < sizeof names / sizeof *names; i++) { snprintf(path, sizeof path, "%s/%s.json", folder, names[i]); json = kon_jsonParseFile(path, error, sizeof error); if (!json) { printf(" %-13s %s\n", names[i], error); continue; } printf(" %-13s %d\n", names[i], kon_jsonCount(json) - 1); if (!strcmp(names[i], "CommonEvents") || !strcmp(names[i], "Troops")) { int k; for (k = 1; k < kon_jsonCount(json); k++) { list(kon_jsonGet(kon_jsonAt(json, k), "list")); pages(kon_jsonGet(kon_jsonAt(json, k), "pages")); } } else if (strcmp(names[i], "Animations") && strcmp(names[i], "Tilesets")) { noteTable(json); } kon_jsonFree(json); } } for (id = 1; id < 2000; id++) { const kon_json_t *events; int k; char tile[32]; snprintf(path, sizeof path, "%s/Map%03d.json", folder, id); json = kon_jsonParseFile(path, error, sizeof error); if (!json) { if (strncmp(error, "cannot open", 11)) fprintf(stderr, "%s: %s\n", path, error); continue; } mapCount++; snprintf(tile, sizeof tile, "tileset %d", kon_jsonInt(kon_jsonGet(json, "tilesetId"), 0)); tally(&tilesets, tile); events = kon_jsonGet(json, "events"); for (k = 1; k < kon_jsonCount(events); k++) { const kon_json_t *event = kon_jsonAt(events, k); if (event->type != KON_JSON_OBJECT) continue; eventCount++; tagNotes(kon_jsonString(kon_jsonGet(event, "note"), NULL)); pages(kon_jsonGet(event, "pages")); } tagNotes(kon_jsonString(kon_jsonGet(json, "note"), NULL)); kon_jsonFree(json); } printf("\nmaps %d, events %d, pages %d, command lists %d\n", mapCount, eventCount, pageCount, listCount); printf("autorun pages %d, parallel process pages %d\n", autorunPages, parallelPages); return 0; } static void commandReport(void) { int done = 0, partial = 0, todo = 0, unknown = 0, i; qsort(codes.rows, (size_t)codes.count, sizeof *codes.rows, byCount); printf("\nevent commands\n"); for (i = 0; i < codes.count; i++) { int code = atoi(codes.rows[i].key); const command_t *c = commandFor(code); int n = codes.rows[i].n; if (!c) unknown += n; else if (c->status == SCRIPT) done += n; else if (c->status == PARTIAL) partial += n; else todo += n; printf(" %6d %-4d %-28s %s\n", n, code, c ? c->name : "unknown", !c ? "?" : c->status == SCRIPT ? "konscript" : c->status == PARTIAL ? "partly" : "not yet"); } printf("\ncommands konscript has today: %d, partly: %d, not yet: %d, unknown: %d\n", done, partial, todo, unknown); printf("script conditions %d, script variable values %d\n", conditionScripts, variableScripts); } /*** plugins ***/ static void plugins(const char *path) { FILE *f = fopen(path, "rb"); char *text, *a, *b, error[120]; long size; kon_json_t *json; int i; if (!f) { printf("\nno plugins.js at %s\n", path); return; } fseek(f, 0, SEEK_END); size = ftell(f); fseek(f, 0, SEEK_SET); text = malloc((size_t)size + 1); if (!text) { fclose(f); return; } size = (long)fread(text, 1, (size_t)size, f); fclose(f); text[size] = '\0'; a = strchr(text, '['); b = strrchr(text, ']'); json = a && b && b > a ? kon_jsonParse(a, (size_t)(b - a + 1), error, sizeof error) : NULL; if (!json) { printf("\nplugins.js: %s\n", a && b ? error : "no list found"); free(text); return; } printf("\nplugins (%d)\n", kon_jsonCount(json)); for (i = 0; i < kon_jsonCount(json); i++) { const kon_json_t *p = kon_jsonAt(json, i); printf(" %-3s %s\n", kon_jsonBool(kon_jsonGet(p, "status"), 0) ? "on" : "off", kon_jsonString(kon_jsonGet(p, "name"), "?")); } kon_jsonFree(json); free(text); } /* a game folder works too, the data is in data, or in www/data for MV */ static int findData(char *out, size_t size, const char *folder) { static const char *const where[] = {"", "/data", "/www/data"}; char path[1100]; FILE *f; int i; for (i = 0; i < 3; i++) { snprintf(path, sizeof path, "%s%s/System.json", folder, where[i]); f = fopen(path, "rb"); if (f) { fclose(f); snprintf(out, size, "%s%s", folder, where[i]); return 1; } } fprintf(stderr, "no System.json in %s, %s/data or %s/www/data\n", folder, folder, folder); return 0; } int main(int argc, char **argv) { char path[1024], dataPath[1000]; if (argc < 2) { fprintf(stderr, "usage: mzsurvey [plugins.js]\n"); return 2; } if (!findData(dataPath, sizeof dataPath, argv[1])) return 1; if (survey(dataPath)) return 1; commandReport(); show(&pluginCommands, "plugin commands", 40); show(&scriptCalls, "script calls", 25); show(¬eTags, "note tags", 40); show(&tilesets, "tilesets used by maps", 20); snprintf(path, sizeof path, "%s/../js/plugins.js", dataPath); plugins(argc > 2 ? argv[2] : path); return 0; }