/* SPDX-License-Identifier: MIT */ /* * mzmap.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: mzmap [-n name] * turns the maps, tilesets and start settings of an exported RPG Maker MV or MZ game into C, as const data for konrmmap.h. * * name_maps.h, name_maps.c the tables to use: name_maps[id] and name_tilesets[id] (NULL for an id that is not there), * and name_system. each map is in its own name_mapNNN.inc that name_maps.c includes * * only const data is written: numbers, strings and pointers to other const data, nothing that runs. a map that is not * the size it says it is, or has a tile that does not fit, is made safe (missing tiles are empty) instead of refused */ #define KONJSON_IMPLEMENTATION #include "konjson.h" #include #include #include #include #define MAX_MAP_ID 1999 #define MAX_SIDE 256 #define STRING_PIECE 100 #define LAYERS 6 static int mapCount, eventCount, pageCount, tileTotal; /*** output ***/ static void cstring(FILE *f, const char *s) { size_t i, piece = 0; if (!s || !*s) { fputs("\"\"", f); return; } fputc('"', f); for (i = 0; s[i]; i++) { unsigned char ch = (unsigned char)s[i]; if (piece >= STRING_PIECE) { fputs("\"\n\t\"", f); piece = 0; } if (ch == '"' || ch == '\\') fprintf(f, "\\%c", ch); else if (ch == '\n') fputs("\\n", f); else if (ch == '\t') fputs("\\t", f); else if (ch >= 32 && ch < 127 && ch != '?') fputc(ch, f); else fprintf(f, "\\%03o", ch); piece++; } fputc('"', f); } static const char *text(const kon_json_t *j) { const char *s = kon_jsonString(j, ""); return s ? s : ""; } static int number(const kon_json_t *j, int low, int high, int fallback) { int v = kon_jsonInt(j, fallback); return v < low ? low : v > high ? high : v; } /* a value that does not fit in 16 bits is not a tile or a flag, so it becomes 0 */ static int unsigned16(const kon_json_t *j) { int v = kon_jsonInt(j, 0); return v < 0 || v > 65535 ? 0 : v; } static int flag(const kon_json_t *j) { return kon_jsonBool(j, 0) ? 1 : 0; } /* 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; } static kon_json_t *load(const char *folder, const char *name) { char path[1024], error[120]; kon_json_t *j; snprintf(path, sizeof path, "%s/%s", folder, name); j = kon_jsonParseFile(path, error, sizeof error); if (!j && strncmp(error, "cannot open", 11)) fprintf(stderr, "%s: %s\n", path, error); return j; } static void audio(FILE *f, const kon_json_t *a) { fputs("{", f); cstring(f, text(kon_jsonGet(a, "name"))); fprintf(f, ", %d, %d, %d}", number(kon_jsonGet(a, "volume"), 0, 100, 90), number(kon_jsonGet(a, "pitch"), 50, 150, 100), number(kon_jsonGet(a, "pan"), -100, 100, 0)); } /*** one map ***/ static void pageRoute(FILE *f, const char *name, const kon_json_t *route) { const kon_json_t *list = kon_jsonGet(route, "list"); int i, k, written = 0; for (i = 0; i < kon_jsonCount(list); i++) { const kon_json_t *command = kon_jsonAt(list, i); const kon_json_t *params = kon_jsonGet(command, "parameters"); int code = number(kon_jsonGet(command, "code"), 0, 1000, 0); int count = 0, allNumbers = 1; if (code == 0) continue; for (k = 0; k < kon_jsonCount(params) && k < 8; k++) { const kon_json_t *p = kon_jsonAt(params, k); if (!p || (p->type != KON_JSON_NUMBER && p->type != KON_JSON_BOOL)) allNumbers = 0; } if (allNumbers) count = kon_jsonCount(params) < 8 ? kon_jsonCount(params) : 8; if (!written) fprintf(f, "static const int32_t %s[] = {\n", name); fprintf(f, "\t%d, %d", code, count); for (k = 0; k < count; k++) fprintf(f, ", %d", number(kon_jsonAt(params, k), -2147483647, 2147483647, 0)); fputs(",\n", f); written++; } if (written) fputs("};\n\n", f); } static int routeSize(const kon_json_t *route) { const kon_json_t *list = kon_jsonGet(route, "list"); int i, k, size = 0; for (i = 0; i < kon_jsonCount(list); i++) { const kon_json_t *command = kon_jsonAt(list, i); const kon_json_t *params = kon_jsonGet(command, "parameters"); int allNumbers = 1, count; if (number(kon_jsonGet(command, "code"), 0, 1000, 0) == 0) continue; for (k = 0; k < kon_jsonCount(params) && k < 8; k++) { const kon_json_t *p = kon_jsonAt(params, k); if (!p || (p->type != KON_JSON_NUMBER && p->type != KON_JSON_BOOL)) allNumbers = 0; } count = allNumbers ? (kon_jsonCount(params) < 8 ? kon_jsonCount(params) : 8) : 0; size += 2 + count; } return size; } static void writeEvent(FILE *f, const char *name, int mapId, const kon_json_t *event, int index) { const kon_json_t *pages = kon_jsonGet(event, "pages"); int eventId = number(kon_jsonGet(event, "id"), 0, 1000000, index); int p, np = kon_jsonCount(pages); for (p = 0; p < np; p++) { char routeName[128]; snprintf(routeName, sizeof routeName, "%s_m%d_e%d_p%d_route", name, mapId, eventId, p + 1); pageRoute(f, routeName, kon_jsonGet(kon_jsonAt(pages, p), "moveRoute")); } if (np == 0) return; fprintf(f, "static const kon_rmPage_t %s_m%d_e%d_pages[] = {\n", name, mapId, eventId); for (p = 0; p < np; p++) { const kon_json_t *page = kon_jsonAt(pages, p); const kon_json_t *c = kon_jsonGet(page, "conditions"); const kon_json_t *image = kon_jsonGet(page, "image"); const kon_json_t *route = kon_jsonGet(page, "moveRoute"); const char *letter = text(kon_jsonGet(c, "selfSwitchCh")); int size = routeSize(route); fprintf(f, "\t{%d, %d, %d, %d, %d, %d, '%c', %d, %d, %d, %d, %d, %d,\n\t\t", flag(kon_jsonGet(c, "switch1Valid")), flag(kon_jsonGet(c, "switch2Valid")), flag(kon_jsonGet(c, "variableValid")), flag(kon_jsonGet(c, "selfSwitchValid")), flag(kon_jsonGet(c, "itemValid")), flag(kon_jsonGet(c, "actorValid")), letter[0] >= 'A' && letter[0] <= 'D' ? letter[0] : 'A', number(kon_jsonGet(c, "switch1Id"), 0, 100000, 0), number(kon_jsonGet(c, "switch2Id"), 0, 100000, 0), number(kon_jsonGet(c, "variableId"), 0, 100000, 0), number(kon_jsonGet(c, "variableValue"), -2147483647, 2147483647, 0), number(kon_jsonGet(c, "itemId"), 0, 100000, 0), number(kon_jsonGet(c, "actorId"), 0, 100000, 0)); cstring(f, text(kon_jsonGet(image, "characterName"))); fprintf(f, ", %d, %d, %d, %d,\n\t\t%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d,\n\t\t", number(kon_jsonGet(image, "characterIndex"), 0, 7, 0), number(kon_jsonGet(image, "direction"), 0, 10, 2), number(kon_jsonGet(image, "pattern"), 0, 2, 0), number(kon_jsonGet(image, "tileId"), 0, 65535, 0), number(kon_jsonGet(page, "moveType"), 0, 3, 0), number(kon_jsonGet(page, "moveSpeed"), 1, 6, 3), number(kon_jsonGet(page, "moveFrequency"), 1, 5, 3), number(kon_jsonGet(page, "priorityType"), 0, 2, 0), number(kon_jsonGet(page, "trigger"), 0, 4, 0), flag(kon_jsonGet(page, "walkAnime")), flag(kon_jsonGet(page, "stepAnime")), flag(kon_jsonGet(page, "directionFix")), flag(kon_jsonGet(page, "through")), flag(kon_jsonGet(route, "repeat")), flag(kon_jsonGet(route, "skippable")), flag(kon_jsonGet(route, "wait"))); fprintf(f, "\"m%de%dp%d\", ", mapId, eventId, p + 1); if (size > 0) fprintf(f, "%s_m%d_e%d_p%d_route, %d},\n", name, mapId, eventId, p + 1, size); else fputs("NULL, 0},\n", f); pageCount++; } fputs("};\n\n", f); } static int writeMap(const char *folder, const char *outFolder, const char *name, int id) { char file[32], path[1100]; kon_json_t *json; const kon_json_t *data, *events, *encounters; FILE *f; int width, height, layerSize, i, k, written = 0, hasEvents = 0, hasEncounters = 0; size_t total, count; snprintf(file, sizeof file, "Map%03d.json", id); json = load(folder, file); if (!json) return 0; width = kon_jsonInt(kon_jsonGet(json, "width"), 0); height = kon_jsonInt(kon_jsonGet(json, "height"), 0); if (width < 1 || height < 1 || width > MAX_SIDE || height > MAX_SIDE) { fprintf(stderr, "%s has no usable size, skipped\n", file); kon_jsonFree(json); return 0; } layerSize = width * height; total = (size_t)layerSize * LAYERS; data = kon_jsonGet(json, "data"); count = (size_t)kon_jsonCount(data); snprintf(path, sizeof path, "%s/%s_map%03d.inc", outFolder, name, id); f = fopen(path, "wb"); if (!f) { fprintf(stderr, "cannot write %s\n", path); kon_jsonFree(json); return -1; } fprintf(f, "/* map %d from %s, written by mzmap */\n\n", id, file); fprintf(f, "static const uint16_t %s_m%d_tiles[] = {\n", name, id); for (i = 0; (size_t)i < total; i++) { int v = (size_t)i < count ? unsigned16(kon_jsonAt(data, i)) : 0; fprintf(f, "%s%d,", written % 24 == 0 ? "\t" : " ", v); if (++written % 24 == 0) fputc('\n', f); } if (written % 24) fputc('\n', f); fputs("};\n\n", f); tileTotal += (int)total; events = kon_jsonGet(json, "events"); for (i = 0; i < kon_jsonCount(events); i++) { const kon_json_t *event = kon_jsonAt(events, i); if (!event || event->type != KON_JSON_OBJECT) continue; writeEvent(f, name, id, event, i); hasEvents++; } if (hasEvents) { fprintf(f, "static const kon_rmEvent_t %s_m%d_events[] = {\n", name, id); for (i = 0; i < kon_jsonCount(events); i++) { const kon_json_t *event = kon_jsonAt(events, i); int eventId; if (!event || event->type != KON_JSON_OBJECT) continue; eventId = number(kon_jsonGet(event, "id"), 0, 1000000, i); fprintf(f, "\t{%d, ", eventId); cstring(f, text(kon_jsonGet(event, "name"))); fprintf(f, ", %d, %d, ", number(kon_jsonGet(event, "x"), 0, MAX_SIDE, 0), number(kon_jsonGet(event, "y"), 0, MAX_SIDE, 0)); if (kon_jsonCount(kon_jsonGet(event, "pages")) > 0) fprintf(f, "%s_m%d_e%d_pages, %d},\n", name, id, eventId, kon_jsonCount(kon_jsonGet(event, "pages"))); else fputs("NULL, 0},\n", f); eventCount++; } fputs("};\n\n", f); } encounters = kon_jsonGet(json, "encounterList"); for (i = 0; i < kon_jsonCount(encounters); i++) { const kon_json_t *regions = kon_jsonGet(kon_jsonAt(encounters, i), "regionSet"); if (kon_jsonCount(regions) == 0) continue; fprintf(f, "static const int %s_m%d_enc%d_regions[] = {", name, id, i); for (k = 0; k < kon_jsonCount(regions); k++) fprintf(f, "%s%d", k ? ", " : "", number(kon_jsonAt(regions, k), 0, 255, 0)); fputs("};\n", f); } if (kon_jsonCount(encounters) > 0) { fprintf(f, "\nstatic const kon_rmEncounter_t %s_m%d_encounters[] = {\n", name, id); for (i = 0; i < kon_jsonCount(encounters); i++) { const kon_json_t *e = kon_jsonAt(encounters, i); int rc = kon_jsonCount(kon_jsonGet(e, "regionSet")); fprintf(f, "\t{%d, %d, ", number(kon_jsonGet(e, "troopId"), 0, 100000, 0), number(kon_jsonGet(e, "weight"), 0, 1000, 1)); if (rc > 0) fprintf(f, "%s_m%d_enc%d_regions, %d},\n", name, id, i, rc); else fputs("NULL, 0},\n", f); } fputs("};\n", f); hasEncounters = 1; } fputs("\n", f); fprintf(f, "static const kon_rmMap_t %s_m%d = {\n\t%d, %d, %d, %d, %s_m%d_tiles,\n\t", name, id, id, width, height, number(kon_jsonGet(json, "tilesetId"), 0, 100000, 0), name, id); cstring(f, text(kon_jsonGet(json, "displayName"))); fputs(",\n\t", f); cstring(f, text(kon_jsonGet(json, "note"))); fprintf(f, ",\n\t%d, %d, %d, %d,\n\t", number(kon_jsonGet(json, "scrollType"), 0, 3, 0), flag(kon_jsonGet(json, "disableDashing")), flag(kon_jsonGet(json, "autoplayBgm")), flag(kon_jsonGet(json, "autoplayBgs"))); audio(f, kon_jsonGet(json, "bgm")); fputs(", ", f); audio(f, kon_jsonGet(json, "bgs")); fputs(",\n\t", f); cstring(f, text(kon_jsonGet(json, "parallaxName"))); fprintf(f, ", %d, %d, %d, %d, %d,\n\t%d,\n\t", flag(kon_jsonGet(json, "parallaxLoopX")), flag(kon_jsonGet(json, "parallaxLoopY")), number(kon_jsonGet(json, "parallaxSx"), -100, 100, 0), number(kon_jsonGet(json, "parallaxSy"), -100, 100, 0), flag(kon_jsonGet(json, "parallaxShow")), number(kon_jsonGet(json, "encounterStep"), 0, 10000, 30)); if (hasEncounters) fprintf(f, "%s_m%d_encounters, %d,\n\t", name, id, kon_jsonCount(encounters)); else fputs("NULL, 0,\n\t", f); if (hasEvents) fprintf(f, "%s_m%d_events, %d\n};\n", name, id, hasEvents); else fputs("NULL, 0\n};\n", f); if (ferror(f) || fclose(f) != 0) { fprintf(stderr, "cannot write %s\n", path); kon_jsonFree(json); return -1; } kon_jsonFree(json); mapCount++; return id; } /*** tilesets and system ***/ static int writeTilesets(FILE *f, const char *name, const kon_json_t *json) { int i, k, highest = 0; for (i = 1; i < kon_jsonCount(json); i++) { const kon_json_t *t = kon_jsonAt(json, i); const kon_json_t *flags = kon_jsonGet(t, "flags"); int id = number(kon_jsonGet(t, "id"), 0, 100000, i), n = kon_jsonCount(flags); if (!t || t->type != KON_JSON_OBJECT || id < 1) continue; if (id > highest) highest = id; if (n > 0) { fprintf(f, "static const uint16_t %s_tileset%d_flags[] = {\n", name, id); for (k = 0; k < n; k++) { fprintf(f, "%s%d,", k % 24 == 0 ? "\t" : " ", unsigned16(kon_jsonAt(flags, k))); if (k % 24 == 23 || k == n - 1) fputc('\n', f); } fputs("};\n", f); } fprintf(f, "static const kon_rmTileset_t %s_tileset%d = {\n\t%d, %d, {", name, id, id, number(kon_jsonGet(t, "mode"), 0, 1, 1)); for (k = 0; k < 9; k++) { if (k) fputs(", ", f); cstring(f, text(kon_jsonAt(kon_jsonGet(t, "tilesetNames"), k))); } fputs("},\n\t", f); if (n > 0) fprintf(f, "%s_tileset%d_flags, %d\n};\n\n", name, id, n); else fputs("NULL, 0\n};\n\n", f); } return highest; } static int validName(const char *s) { if (!isalpha((unsigned char)s[0]) && s[0] != '_') return 0; for (; *s; s++) { if (!isalnum((unsigned char)*s) && *s != '_') return 0; } return 1; } int main(int argc, char **argv) { char name[64] = "game", path[1100], upper[64], dataPath[1100]; const char *folder = NULL, *outFolder = NULL; kon_json_t *system, *tilesets; int ids[MAX_MAP_ID + 1], found = 0, highestMap = 0, highestSet = 0, a, id, k, n; FILE *f; for (a = 1; a < argc; a++) { if (!strcmp(argv[a], "-n") && a + 1 < argc) snprintf(name, sizeof name, "%s", argv[++a]); else if (!folder) folder = argv[a]; else if (!outFolder) outFolder = argv[a]; } if (!folder || !outFolder) { fprintf(stderr, "usage: mzmap [-n name] \n"); return 2; } if (!validName(name)) { fprintf(stderr, "the name has to be usable in C: letters, digits and _\n"); return 2; } if (!findData(dataPath, sizeof dataPath, folder)) return 1; folder = dataPath; memset(ids, 0, sizeof ids); for (id = 1; id <= MAX_MAP_ID; id++) { int r = writeMap(folder, outFolder, name, id); if (r < 0) return 1; if (r > 0) { ids[id] = 1; found++; highestMap = id; } } if (!found) { fprintf(stderr, "no maps found in %s\n", folder); return 1; } snprintf(path, sizeof path, "%s/%s_maps.c", outFolder, name); f = fopen(path, "wb"); if (!f) { fprintf(stderr, "cannot write %s\n", path); return 1; } fprintf(f, "/* written by mzmap, the maps, tilesets and start settings of the game */\n\n#include \"%s_maps.h\"\n\n#include \n\n", name); for (id = 1; id <= highestMap; id++) { if (ids[id]) fprintf(f, "#include \"%s_map%03d.inc\"\n", name, id); } fputs("\n", f); tilesets = load(folder, "Tilesets.json"); if (tilesets) highestSet = writeTilesets(f, name, tilesets); fprintf(f, "const kon_rmMap_t *const %s_maps[%d] = {\n\tNULL,\n", name, highestMap + 1); for (id = 1; id <= highestMap; id++) { if (ids[id]) fprintf(f, "\t&%s_m%d,\n", name, id); else fputs("\tNULL,\n", f); } fputs("};\n\n", f); fprintf(f, "const kon_rmTileset_t *const %s_tilesets[%d] = {\n\tNULL,\n", name, highestSet + 1); for (id = 1; id <= highestSet; id++) { int there = 0; for (k = 1; tilesets && k < kon_jsonCount(tilesets); k++) { if (number(kon_jsonGet(kon_jsonAt(tilesets, k), "id"), 0, 100000, k) == id && kon_jsonAt(tilesets, k)->type == KON_JSON_OBJECT) there = 1; } if (there) fprintf(f, "\t&%s_tileset%d,\n", name, id); else fputs("\tNULL,\n", f); } fputs("};\n\n", f); kon_jsonFree(tilesets); system = load(folder, "System.json"); if (system) { const kon_json_t *party = kon_jsonGet(system, "partyMembers"), *tone = kon_jsonGet(system, "windowTone"); n = kon_jsonCount(party); if (n > 0) { fprintf(f, "static const int %s_party[] = {", name); for (k = 0; k < n; k++) fprintf(f, "%s%d", k ? ", " : "", number(kon_jsonAt(party, k), 0, 100000, 0)); fputs("};\n", f); } fprintf(f, "const kon_rmSystem_t %s_system = {\n\t", name); cstring(f, text(kon_jsonGet(system, "gameTitle"))); fprintf(f, ",\n\t%d, %d, %d,\n\t", number(kon_jsonGet(system, "startMapId"), 0, MAX_MAP_ID, 0), number(kon_jsonGet(system, "startX"), 0, MAX_SIDE, 0), number(kon_jsonGet(system, "startY"), 0, MAX_SIDE, 0)); if (n > 0) fprintf(f, "%s_party, %d,\n\t", name, n); else fputs("NULL, 0,\n\t", f); fprintf(f, "%d, %d, %d, %d, {%d, %d, %d}\n};\n", kon_jsonCount(kon_jsonGet(system, "switches")) - 1 > 0 ? kon_jsonCount(kon_jsonGet(system, "switches")) - 1 : 0, kon_jsonCount(kon_jsonGet(system, "variables")) - 1 > 0 ? kon_jsonCount(kon_jsonGet(system, "variables")) - 1 : 0, flag(kon_jsonGet(system, "optFollowers")), flag(kon_jsonGet(system, "optTransparent")), number(kon_jsonAt(tone, 0), -255, 255, 0), number(kon_jsonAt(tone, 1), -255, 255, 0), number(kon_jsonAt(tone, 2), -255, 255, 0)); kon_jsonFree(system); } else { fprintf(f, "const kon_rmSystem_t %s_system = {\"\", 0, 0, 0, NULL, 0, 0, 0, 0, 0, {0, 0, 0}};\n", name); } if (ferror(f) || fclose(f) != 0) { fprintf(stderr, "cannot write %s\n", path); return 1; } for (k = 0; name[k]; k++) upper[k] = (char)toupper((unsigned char)name[k]); upper[k] = '\0'; snprintf(path, sizeof path, "%s/%s_maps.h", outFolder, name); f = fopen(path, "wb"); if (!f) { fprintf(stderr, "cannot write %s\n", path); return 1; } fprintf(f, "/* written by mzmap */\n\n#ifndef %s_MAPS_H\n#define %s_MAPS_H\n\n#include \"konrmmap.h\"\n\n", upper, upper); fprintf(f, "#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n"); fprintf(f, "#define %s_MAP_IDS %d\n#define %s_TILESET_IDS %d\n\n", upper, highestMap + 1, upper, highestSet + 1); fprintf(f, "/* index by the id the game uses, NULL when there is none */\nextern const kon_rmMap_t *const %s_maps[%d];\nextern const kon_rmTileset_t *const %s_tilesets[%d];\nextern const kon_rmSystem_t %s_system;\n\n", name, highestMap + 1, name, highestSet + 1, name); fprintf(f, "#ifdef __cplusplus\n}\n#endif\n#endif\n"); if (ferror(f) || fclose(f) != 0) { fprintf(stderr, "cannot write %s\n", path); return 1; } printf("%d maps, %d events, %d pages, %d tiles\n", mapCount, eventCount, pageCount, tileTotal); return 0; }