summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tools/mzsurvey.c400
1 files changed, 400 insertions, 0 deletions
diff --git a/tools/mzsurvey.c b/tools/mzsurvey.c
new file mode 100644
index 0000000..61253a6
--- /dev/null
+++ b/tools/mzsurvey.c
@@ -0,0 +1,400 @@
+/* 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 <data folder> [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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*** 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(&noteTags, 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);
+}
+
+int main(int argc, char **argv)
+{
+ char path[1024];
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: mzsurvey <data folder> [plugins.js]\n");
+ return 2;
+ }
+ if (survey(argv[1])) return 1;
+ commandReport();
+ show(&pluginCommands, "plugin commands", 40);
+ show(&scriptCalls, "script calls", 25);
+ show(&noteTags, "note tags", 40);
+ show(&tilesets, "tilesets used by maps", 20);
+ snprintf(path, sizeof path, "%s/../js/plugins.js", argv[1]);
+ plugins(argc > 2 ? argv[2] : path);
+ return 0;
+}