/* SPDX-License-Identifier: MIT */ /* * mzscript.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: mzscript [-n name] [-split rows] [-text] * turns the events of an exported RPG Maker MV or MZ game into C, ready to build with the engine. * * name.h, name.c every common event (label common_N), troop page (tN pM) and map event page (mN eN pN) as one list of * kon_cmd_t, see konscriptc.h for how it is written and checked. with -split the list is also name_0.inc and so on * name_speakers.c what the numbers after say mean: face file, face index, name, window background, window position, * and the scratch switch and variable the host answers conditions and battle results in * name.kons only with -text, the same script as konscript text for reading * * what konscript has itself becomes konscript. everything else becomes host commands, where the host number is the * RPG Maker command code and the parameters are b, c, d (the first three numbers) and a text with all of them split by * tabs, in the order RPG Maker keeps them. a choice, loop or battle result is turned into labels and jumps */ #define KONJSON_IMPLEMENTATION #include "konjson.h" #define KONSCRIPTC_IMPLEMENTATION #include "konscriptc.h" #include #include #include #include #include /*** output ***/ typedef struct buf { char *p; size_t n, cap; } buf_t; static void put(buf_t *b, const char *fmt, ...) { va_list ap; int need; for (;;) { va_start(ap, fmt); need = vsnprintf(b->p ? b->p + b->n : NULL, b->cap - b->n, fmt, ap); va_end(ap); if (need >= 0 && b->p && (size_t)need < b->cap - b->n) { b->n += (size_t)need; return; } b->cap = b->cap ? b->cap * 2 : 1 << 16; b->p = realloc(b->p, b->cap); if (!b->p) { fprintf(stderr, "out of memory\n"); exit(1); } } } #define MAX_LINE 3800 static void putString(buf_t *b, const char *s) { size_t i, n = strlen(s); if (n > MAX_LINE) n = MAX_LINE; put(b, "\""); for (i = 0; i < n; i++) { unsigned char c = (unsigned char)s[i]; if (c == '"') put(b, "\\\""); else if (c == '\\') put(b, "\\\\"); else if (c == '\n') put(b, "\\n"); else if (c == '\t') put(b, "\\t"); else if (c < 0x20) put(b, " "); else put(b, "%c", c); } put(b, "\""); } /*** counting ***/ typedef struct tally { int code; int n; } tally_t; static tally_t hosts[256]; static int hostKinds; static int structured, hostLines, warnings; static void countHost(int code) { int i; hostLines++; for (i = 0; i < hostKinds; i++) { if (hosts[i].code == code) { hosts[i].n++; return; } } if (hostKinds < 256) { hosts[hostKinds].code = code; hosts[hostKinds].n = 1; hostKinds++; } } /* plugin commands by name, to see what a game asks of its plugins */ typedef struct pluginTally { char name[64]; int n; } pluginTally_t; static pluginTally_t plugins[256]; static int pluginKinds; static void countPlugin(const char *first, const char *second) { char name[64]; size_t len = 0; int i; while (first[len] && first[len] != ' ' && len < sizeof name - 1) { name[len] = first[len]; len++; } if (second && len < sizeof name - 2) { const char *s = second; name[len++] = '.'; while (*s && len < sizeof name - 1) name[len++] = *s++; } name[len] = '\0'; for (i = 0; i < pluginKinds; i++) { if (!strcmp(plugins[i].name, name)) { plugins[i].n++; return; } } if (pluginKinds < 256) { strcpy(plugins[pluginKinds].name, name); plugins[pluginKinds++].n = 1; } } static void warn(const char *scope, const char *what) { if (warnings++ < 20) fprintf(stderr, "%s: %s\n", scope, what); } /*** speakers ***/ typedef struct speaker { char face[96]; int index, background, position; char name[96]; } speaker_t; static speaker_t *speakers; static int speakerCount, speakerCap; static int speakerFor(const char *face, int index, const char *name, int background, int position) { int i; if (!face[0] && !name[0] && background == 0 && position == 2) return -1; for (i = 0; i < speakerCount; i++) { speaker_t *s = &speakers[i]; if (!strcmp(s->face, face) && s->index == index && !strcmp(s->name, name) && s->background == background && s->position == position) return i; } if (speakerCount == speakerCap) { speakerCap = speakerCap ? speakerCap * 2 : 64; speakers = realloc(speakers, (size_t)speakerCap * sizeof *speakers); if (!speakers) exit(1); } snprintf(speakers[speakerCount].face, sizeof speakers[0].face, "%s", face); snprintf(speakers[speakerCount].name, sizeof speakers[0].name, "%s", name); speakers[speakerCount].index = index; speakers[speakerCount].background = background; speakers[speakerCount].position = position; return speakerCount++; } /*** parameters ***/ #define MAX_TOKENS 4096 typedef struct tokens { char **item; int count; int numbers[3], numberCount; int hasString; } tokens_t; static void addToken(tokens_t *t, const char *text, int isNumber, double number) { char *copy; size_t i; if (t->count >= MAX_TOKENS) return; copy = malloc(strlen(text) + 1); if (!copy) exit(1); strcpy(copy, text); for (i = 0; copy[i]; i++) { if (copy[i] == '\t') copy[i] = ' '; } t->item[t->count++] = copy; if (isNumber) { if (t->numberCount < 3) t->numbers[t->numberCount++] = number > 2147483647.0 ? 2147483647 : number < -2147483647.0 ? -2147483647 : (int)number; } else { t->hasString = 1; } } static void flatten(tokens_t *t, const kon_json_t *j) { int i; char text[64]; if (!j) return; switch (j->type) { case KON_JSON_NUMBER: if (j->number == (double)(long long)j->number) snprintf(text, sizeof text, "%lld", (long long)j->number); else snprintf(text, sizeof text, "%.6g", j->number); addToken(t, text, 1, j->number); break; case KON_JSON_BOOL: addToken(t, j->number ? "1" : "0", 1, j->number); break; case KON_JSON_STRING: addToken(t, j->string, 0, 0); break; case KON_JSON_NULL: addToken(t, "", 0, 0); break; default: for (i = 0; i < j->count; i++) flatten(t, &j->items[i]); break; } } static void freeTokens(tokens_t *t) { int i; for (i = 0; i < t->count; i++) free(t->item[i]); free(t->item); } static void host(buf_t *out, int code, const kon_json_t *parameters) { tokens_t t; int i; size_t len = 0; memset(&t, 0, sizeof t); t.item = calloc(MAX_TOKENS, sizeof *t.item); if (!t.item) exit(1); flatten(&t, parameters); if (code == 356 && t.count > 0) countPlugin(t.item[0], NULL); if (code == 357 && t.count > 1) countPlugin(t.item[0], t.item[1]); countHost(code); put(out, "\thost %d", code); for (i = 0; i < t.numberCount; i++) put(out, " %d", t.numbers[i]); if (t.hasString || t.count > 3) { char *joined; for (i = 0; i < t.count; i++) len += strlen(t.item[i]) + 1; joined = malloc(len + 1); if (!joined) exit(1); joined[0] = '\0'; for (i = 0; i < t.count; i++) { if (i) strcat(joined, "\t"); strcat(joined, t.item[i]); } if (t.numberCount < 3) { while (t.numberCount < 3) { put(out, " 0"); t.numberCount++; } } put(out, " "); putString(out, joined); free(joined); } put(out, "\n"); freeTokens(&t); } /* command 205: who, then the route as numbers, code count and the count numbers of each command, tab apart. what a command has besides numbers (a character sheet, a script) is left out and its count is 0, the same as in the map's own routes */ static void routeHost(buf_t *out, const kon_json_t *p) { const kon_json_t *route = kon_jsonAt(p, 1); const kon_json_t *list = kon_jsonGet(route, "list"); int flags = (kon_jsonBool(kon_jsonGet(route, "repeat"), 0) ? 1 : 0) | (kon_jsonBool(kon_jsonGet(route, "skippable"), 0) ? 2 : 0) | (kon_jsonBool(kon_jsonGet(route, "wait"), 0) ? 4 : 0); buf_t text = {0}; int i, k, written = 0; countHost(205); for (i = 0; i < kon_jsonCount(list) && text.n < 3500; i++) { const kon_json_t *command = kon_jsonAt(list, i); const kon_json_t *params = kon_jsonGet(command, "parameters"); int code = kon_jsonInt(kon_jsonGet(command, "code"), 0), count = 0, allNumbers = 1; if (code <= 0 || code > 1000) continue; for (k = 0; k < kon_jsonCount(params) && k < 8; k++) { const kon_json_t *q = kon_jsonAt(params, k); if (!q || (q->type != KON_JSON_NUMBER && q->type != KON_JSON_BOOL)) allNumbers = 0; } if (allNumbers) count = kon_jsonCount(params) < 8 ? kon_jsonCount(params) : 8; put(&text, "%s%d\t%d", written ? "\t" : "", code, count); for (k = 0; k < count; k++) put(&text, "\t%d", kon_jsonInt(kon_jsonAt(params, k), 0)); written++; } put(out, "\thost 205 %d %d 0", kon_jsonInt(kon_jsonAt(p, 0), 0), flags); if (written) { text.p[text.n] = '\0'; put(out, " "); putString(out, text.p); } put(out, "\n"); free(text.p); } /*** one event list ***/ typedef struct frame { int kind, indent, id, hasElse, branch, count; } frame_t; enum { F_COND, F_CHOICE, F_LOOP, F_BATTLE }; typedef struct context { buf_t *out; const char *scope; int counter; int scratchSwitch, scratchVar, maxCommon; char labelNames[512][64]; char labelDone[512]; int labelCount; frame_t stack[48]; int depth; } context_t; static const char *labelName(context_t *c, char tag, int id, int n, char *out) { if (tag == 'e') snprintf(out, 40, "%s_%d", c->scope, id); else snprintf(out, 40, "%s_%d%c%d", c->scope, id, tag, n); return out; } static int commandCode(const kon_json_t *list, int i) { return kon_jsonInt(kon_jsonGet(kon_jsonAt(list, i), "code"), 0); } static int commandIndent(const kon_json_t *list, int i) { return kon_jsonInt(kon_jsonGet(kon_jsonAt(list, i), "indent"), 0); } static const kon_json_t *commandParameters(const kon_json_t *list, int i) { return kon_jsonGet(kon_jsonAt(list, i), "parameters"); } static int nextAtIndent(const kon_json_t *list, int from, int indent) { int n = kon_jsonCount(list), j; for (j = from + 1; j < n; j++) { if (commandIndent(list, j) == indent) return j; if (commandIndent(list, j) < indent) return -1; } return -1; } static int hasElse(const kon_json_t *list, int from, int indent) { int j = from; for (;;) { j = nextAtIndent(list, j, indent); if (j < 0) return 0; if (commandCode(list, j) == 411) return 1; if (commandCode(list, j) == 412) return 0; } } /* a call with nothing after it but the ends of blocks can be a jump, so a common event that calls itself doesn't pile up */ static int tailCall(const kon_json_t *list, int i) { int n = kon_jsonCount(list), j; for (j = i + 1; j < n; j++) { int code = commandCode(list, j); if (code != 0 && code != 412 && code != 404) return 0; } return 1; } static void scriptLabels(context_t *c, const kon_json_t *list) { int i, n = kon_jsonCount(list); c->labelCount = 0; memset(c->labelDone, 0, sizeof c->labelDone); for (i = 0; i < n; i++) { if (commandCode(list, i) == 118 && c->labelCount < 512) { snprintf(c->labelNames[c->labelCount], sizeof c->labelNames[0], "%s", kon_jsonString(kon_jsonAt(commandParameters(list, i), 0), "")); c->labelCount++; } } } static int labelIndex(context_t *c, const char *name) { int i; for (i = 0; i < c->labelCount; i++) { if (!strcmp(c->labelNames[i], name)) return i; } return -1; } static frame_t *top(context_t *c, int indent) { if (c->depth && c->stack[c->depth - 1].indent == indent) return &c->stack[c->depth - 1]; return NULL; } static frame_t *push(context_t *c, int kind, int indent) { frame_t *f = &c->stack[c->depth < 47 ? c->depth++ : 47]; memset(f, 0, sizeof *f); f->kind = kind; f->indent = indent; f->id = ++c->counter; return f; } static const char *compare[] = {"==", ">=", "<=", ">", "<", "!="}; static void condition(context_t *c, const kon_json_t *p, const char *elseLabel) { buf_t *out = c->out; int type = kon_jsonInt(kon_jsonAt(p, 0), -1); if (type == 0) { put(out, "\tif s%d %s else %s\n", kon_jsonInt(kon_jsonAt(p, 1), 0), kon_jsonInt(kon_jsonAt(p, 2), 0) == 0 ? "on" : "off", elseLabel); structured++; return; } if (type == 1) { int cmp = kon_jsonInt(kon_jsonAt(p, 4), 0); int operandVar = kon_jsonInt(kon_jsonAt(p, 2), 0) == 1; double v = kon_jsonNumber(kon_jsonAt(p, 3), 0); if (cmp >= 0 && cmp <= 5 && (operandVar || (v > -200000000 && v < 200000000))) { put(out, "\tif v%d %s ", kon_jsonInt(kon_jsonAt(p, 1), 0), compare[cmp]); if (operandVar) put(out, "v%d", kon_jsonInt(kon_jsonAt(p, 3), 0)); else put(out, "%d", (int)v); put(out, " else %s\n", elseLabel); structured++; return; } } host(out, 111, p); put(out, "\tif s%d on else %s\n", c->scratchSwitch, elseLabel); } static void variables(context_t *c, const kon_json_t *p, const kon_json_t *whole) { static const char *const ops[] = {"=", "+=", "-=", "*=", "/=", "%="}; int first = kon_jsonInt(kon_jsonAt(p, 0), 0), last = kon_jsonInt(kon_jsonAt(p, 1), 0); int op = kon_jsonInt(kon_jsonAt(p, 2), 0), type = kon_jsonInt(kon_jsonAt(p, 3), 0); double v = kon_jsonNumber(kon_jsonAt(p, 4), 0); int id; if (op >= 0 && op <= 5 && last >= first && last - first < 1000 && ((type == 0 && v > -200000000 && v < 200000000) || type == 1)) { for (id = first; id <= last; id++) { put(c->out, "\tset v%d %s ", id, ops[op]); if (type == 1) put(c->out, "v%d\n", (int)v); else put(c->out, "%d\n", (int)v); } structured++; return; } host(c->out, 122, whole); } static void textBlock(const kon_json_t *list, int *i, int continuation, tokens_t *lines) { int n = kon_jsonCount(list); while (*i + 1 < n && commandCode(list, *i + 1) == continuation) { (*i)++; addToken(lines, kon_jsonString(kon_jsonAt(commandParameters(list, *i), 0), ""), 0, 0); } } static char *joinLines(tokens_t *t, const char *first) { size_t len = strlen(first) + 1; char *s; int i; for (i = 0; i < t->count; i++) len += strlen(t->item[i]) + 1; s = malloc(len + 1); if (!s) exit(1); strcpy(s, first); for (i = 0; i < t->count; i++) { if (i || first[0]) strcat(s, "\n"); strcat(s, t->item[i]); } return s; } static void convert(context_t *c, const kon_json_t *list) { buf_t *out = c->out; int n = kon_jsonCount(list), i; char a[40], b[40]; c->depth = 0; scriptLabels(c, list); for (i = 0; i < n; i++) { int code = commandCode(list, i), indent = commandIndent(list, i); const kon_json_t *p = commandParameters(list, i); frame_t *f; switch (code) { case 0: break; case 101: { tokens_t lines; char *text; int who; memset(&lines, 0, sizeof lines); lines.item = calloc(MAX_TOKENS, sizeof *lines.item); if (!lines.item) exit(1); textBlock(list, &i, 401, &lines); who = speakerFor(kon_jsonString(kon_jsonAt(p, 0), ""), kon_jsonInt(kon_jsonAt(p, 1), 0), kon_jsonString(kon_jsonAt(p, 4), ""), kon_jsonInt(kon_jsonAt(p, 2), 0), kon_jsonInt(kon_jsonAt(p, 3), 2)); text = joinLines(&lines, ""); if (who < 0) put(out, "\tsay - "); else put(out, "\tsay %d ", who); putString(out, text); put(out, "\n"); free(text); freeTokens(&lines); structured++; break; } case 102: { const kon_json_t *choices = kon_jsonAt(p, 0); int k, count = kon_jsonCount(choices); f = push(c, F_CHOICE, indent); f->count = count > 16 ? 16 : count; if (count > 16) warn(c->scope, "more than 16 choices"); put(out, "\tmenu\n"); for (k = 0; k < f->count; k++) { put(out, "\toption "); putString(out, kon_jsonString(kon_jsonAt(choices, k), "")); put(out, " %s\n", labelName(c, 'o', f->id, k, a)); } structured++; break; } case 402: f = top(c, indent); if (!f || f->kind != F_CHOICE) { warn(c->scope, "choice branch without choices"); break; } if (f->branch) put(out, "\tjump %s\n", labelName(c, 'e', f->id, 0, a)); f->branch++; put(out, "%s:\n", labelName(c, 'o', f->id, kon_jsonInt(kon_jsonAt(p, 0), 0), a)); break; case 403: f = top(c, indent); if (!f || f->kind != F_CHOICE) break; put(out, "\tjump %s\n", labelName(c, 'e', f->id, 0, a)); put(out, "%s_%dcancel:\n", c->scope, f->id); warnings += 0; break; case 404: f = top(c, indent); if (!f || f->kind != F_CHOICE) break; put(out, "%s:\n", labelName(c, 'e', f->id, 0, a)); c->depth--; break; case 111: f = push(c, F_COND, indent); f->hasElse = hasElse(list, i, indent); condition(c, p, f->hasElse ? labelName(c, 'n', f->id, 0, a) : labelName(c, 'e', f->id, 0, b)); break; case 411: f = top(c, indent); if (!f || f->kind != F_COND) break; put(out, "\tjump %s\n", labelName(c, 'e', f->id, 0, a)); put(out, "%s:\n", labelName(c, 'n', f->id, 0, a)); break; case 412: f = top(c, indent); if (!f || f->kind != F_COND) break; put(out, "%s:\n", labelName(c, 'e', f->id, 0, a)); c->depth--; break; case 112: f = push(c, F_LOOP, indent); put(out, "%s:\n", labelName(c, 't', f->id, 0, a)); break; case 413: f = top(c, indent); if (!f || f->kind != F_LOOP) break; put(out, "\tjump %s\n", labelName(c, 't', f->id, 0, a)); put(out, "%s:\n", labelName(c, 'e', f->id, 0, a)); c->depth--; break; case 113: { int k; for (k = c->depth - 1; k >= 0 && c->stack[k].kind != F_LOOP; k--) {} if (k < 0) { warn(c->scope, "break loop outside a loop"); break; } put(out, "\tjump %s\n", labelName(c, 'e', c->stack[k].id, 0, a)); break; } case 115: put(out, "\treturn\n"); break; case 117: if (kon_jsonInt(kon_jsonAt(p, 0), 0) < 1 || kon_jsonInt(kon_jsonAt(p, 0), 0) > c->maxCommon) { put(out, "\t# call of a common event that is not there\n"); break; } put(out, "\t%s common_%d\n", tailCall(list, i) ? "jump" : "call", kon_jsonInt(kon_jsonAt(p, 0), 0)); structured++; break; case 118: { int k = labelIndex(c, kon_jsonString(kon_jsonAt(p, 0), "")); if (k >= 0 && !c->labelDone[k]) { c->labelDone[k] = 1; put(out, "%s_L%d:\n", c->scope, k); } break; } case 119: { int k = labelIndex(c, kon_jsonString(kon_jsonAt(p, 0), "")); if (k < 0) { put(out, "\t# jump to a label that is not there\n"); break; } put(out, "\tjump %s_L%d\n", c->scope, k); structured++; break; } case 121: { int id, first = kon_jsonInt(kon_jsonAt(p, 0), 0), last = kon_jsonInt(kon_jsonAt(p, 1), 0); if (last - first > 2000) last = first + 2000; for (id = first; id <= last; id++) put(out, "\t%s s%d\n", kon_jsonInt(kon_jsonAt(p, 2), 0) == 0 ? "on" : "off", id); structured++; break; } case 122: variables(c, p, p); break; case 108: { tokens_t lines; int k; memset(&lines, 0, sizeof lines); lines.item = calloc(MAX_TOKENS, sizeof *lines.item); if (!lines.item) exit(1); addToken(&lines, kon_jsonString(kon_jsonAt(p, 0), ""), 0, 0); textBlock(list, &i, 408, &lines); for (k = 0; k < lines.count; k++) { char *s; for (s = lines.item[k]; *s; s++) { if ((unsigned char)*s < 0x20) *s = ' '; } if (strlen(lines.item[k]) > 400) lines.item[k][400] = '\0'; put(out, "\t# %s\n", lines.item[k]); } freeTokens(&lines); break; } case 230: put(out, "\twait %d\n", (int)(kon_jsonNumber(kon_jsonAt(p, 0), 0) * 1000.0 / 60.0 + 0.5)); structured++; break; case 301: { int next = nextAtIndent(list, i, indent); host(out, 301, p); if (next >= 0 && (commandCode(list, next) == 601 || commandCode(list, next) == 602 || commandCode(list, next) == 603)) push(c, F_BATTLE, indent); break; } case 601: case 602: case 603: f = top(c, indent); if (!f || f->kind != F_BATTLE) break; if (f->branch) { put(out, "\tjump %s\n", labelName(c, 'e', f->id, 0, a)); put(out, "%s:\n", labelName(c, 'n', f->id, f->branch - 1, a)); } put(out, "\tif v%d == %d else %s\n", c->scratchVar, code - 601, labelName(c, 'n', f->id, f->branch, a)); f->branch++; break; case 604: f = top(c, indent); if (!f || f->kind != F_BATTLE) break; put(out, "\tjump %s\n", labelName(c, 'e', f->id, 0, a)); put(out, "%s:\n", labelName(c, 'n', f->id, f->branch - 1, a)); put(out, "%s:\n", labelName(c, 'e', f->id, 0, a)); c->depth--; break; case 105: case 355: { tokens_t lines; char *text; int number = 0; memset(&lines, 0, sizeof lines); lines.item = calloc(MAX_TOKENS, sizeof *lines.item); if (!lines.item) exit(1); textBlock(list, &i, code == 105 ? 405 : 655, &lines); text = joinLines(&lines, code == 355 ? kon_jsonString(kon_jsonAt(p, 0), "") : ""); if (code == 105) number = kon_jsonInt(kon_jsonAt(p, 0), 2); countHost(code); put(out, "\thost %d %d 0 0 ", code, number); putString(out, text); put(out, "\n"); free(text); freeTokens(&lines); break; } case 205: routeHost(out, p); while (i + 1 < n && commandCode(list, i + 1) == 505) i++; break; case 357: host(out, 357, p); while (i + 1 < n && commandCode(list, i + 1) == 657) i++; break; case 401: case 405: case 408: case 505: case 655: case 657: break; default: host(out, code, p); break; } } while (c->depth) { warn(c->scope, "a block was left open"); c->depth--; } } /*** files ***/ /* 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 int writeSpeakers(const char *prefix, const char *name, int scratchSwitch, int scratchVar) { char path[1100]; const char *base = strrchr(prefix, '/'); char upper[64]; FILE *f; int k; base = base ? base + 1 : prefix; for (k = 0; name[k] && k < 63; k++) upper[k] = (char)toupper((unsigned char)name[k]); upper[k] = '\0'; snprintf(path, sizeof path, "%s_speakers.h", prefix); f = fopen(path, "wb"); if (!f) { fprintf(stderr, "cannot write %s (does the output folder exist?)\n", path); return 1; } fprintf(f, "/* made by mzscript, don't edit */\n\n#ifndef %s_SPEAKERS_H\n#define %s_SPEAKERS_H\n\n", upper, upper); fprintf(f, "#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n"); fprintf(f, "/* the host answers conditions and battle results in this switch and variable */\n"); fprintf(f, "#define %s_SCRATCH_SWITCH %d\n#define %s_SCRATCH_VARIABLE %d\n\n", upper, scratchSwitch, upper, scratchVar); fprintf(f, "typedef struct %s_speaker {\n\tconst char *face;\n\tint faceIndex;\n\tconst char *name;\n\tint background, position;\n} %s_speaker_t;\n\n", name, name); fprintf(f, "extern const int %s_speakerCount;\nextern const %s_speaker_t %s_speakers[%d];\n\n", name, name, name, speakerCount > 0 ? speakerCount : 1); fprintf(f, "#ifdef __cplusplus\n}\n#endif\n#endif\n"); if (fclose(f) != 0) return 1; snprintf(path, sizeof path, "%s_speakers.c", prefix); f = fopen(path, "wb"); if (!f) { fprintf(stderr, "cannot write %s (does the output folder exist?)\n", path); return 1; } fprintf(f, "/* made by mzscript, don't edit */\n\n#include \"%s_speakers.h\"\n\n", base); fprintf(f, "const int %s_speakerCount = %d;\n\n", name, speakerCount); fprintf(f, "const %s_speaker_t %s_speakers[%d] = {\n", name, name, speakerCount > 0 ? speakerCount : 1); for (k = 0; k < speakerCount; k++) { fputs("\t{", f); konsc_string(f, speakers[k].face); fprintf(f, ", %d, ", speakers[k].index); konsc_string(f, speakers[k].name); fprintf(f, ", %d, %d},\n", speakers[k].background, speakers[k].position); } if (!speakerCount) fputs("\t{\"\", 0, \"\", 0, 0}\n", f); fputs("};\n", f); return fclose(f) != 0; } int main(int argc, char **argv) { buf_t out; context_t c; kon_json_t *system, *json; char scope[40], name[64] = "game", prefix[1024], error[200], dataPath[1100]; const char *folder = NULL, *outFolder = NULL; int id, k, p, a, maxCommon = 0, entries = 0, split = 0, text = 0; konsc_result_t result; for (a = 1; a < argc; a++) { if (!strcmp(argv[a], "-n") && a + 1 < argc) snprintf(name, sizeof name, "%s", argv[++a]); else if (!strcmp(argv[a], "-split") && a + 1 < argc) split = atoi(argv[++a]); else if (!strcmp(argv[a], "-text")) text = 1; else if (!folder) folder = argv[a]; else if (!outFolder) outFolder = argv[a]; } if (!folder || !outFolder) { fprintf(stderr, "usage: mzscript [-n name] [-split rows] [-text] \n"); return 2; } if (!findData(dataPath, sizeof dataPath, folder)) return 1; folder = dataPath; snprintf(prefix, sizeof prefix, "%s/%s", outFolder, name); memset(&out, 0, sizeof out); memset(&c, 0, sizeof c); c.out = &out; system = load(folder, "System.json"); if (!system) return 1; c.scratchSwitch = kon_jsonCount(kon_jsonGet(system, "switches")) + 1; c.scratchVar = kon_jsonCount(kon_jsonGet(system, "variables")) + 1; kon_jsonFree(system); put(&out, "# scratch switch s%d and scratch variable v%d are the host's\n\n", c.scratchSwitch, c.scratchVar); json = load(folder, "CommonEvents.json"); if (json) { maxCommon = kon_jsonCount(json) - 1; c.maxCommon = maxCommon; for (id = 1; id <= maxCommon; id++) { const kon_json_t *event = kon_jsonAt(json, id); snprintf(scope, sizeof scope, "c%d", id); c.scope = scope; c.counter = 0; put(&out, "common_%d:\n", id); if (event && event->type == KON_JSON_OBJECT) convert(&c, kon_jsonGet(event, "list")); put(&out, "\treturn\n\n"); entries++; } kon_jsonFree(json); } json = load(folder, "Troops.json"); if (json) { for (id = 1; id < kon_jsonCount(json); id++) { const kon_json_t *pages = kon_jsonGet(kon_jsonAt(json, id), "pages"); for (p = 0; p < kon_jsonCount(pages); p++) { snprintf(scope, sizeof scope, "t%dp%d", id, p + 1); c.scope = scope; c.counter = 0; put(&out, "%s:\n", scope); convert(&c, kon_jsonGet(kon_jsonAt(pages, p), "list")); put(&out, "\treturn\n\n"); entries++; } } kon_jsonFree(json); } for (id = 1; id < 2000; id++) { char file[32]; const kon_json_t *events; snprintf(file, sizeof file, "Map%03d.json", id); json = load(folder, file); if (!json) continue; events = kon_jsonGet(json, "events"); for (k = 1; k < kon_jsonCount(events); k++) { const kon_json_t *event = kon_jsonAt(events, k), *pages = kon_jsonGet(event, "pages"); if (!event || event->type != KON_JSON_OBJECT) continue; for (p = 0; p < kon_jsonCount(pages); p++) { snprintf(scope, sizeof scope, "m%de%dp%d", id, kon_jsonInt(kon_jsonGet(event, "id"), k), p + 1); c.scope = scope; c.counter = 0; put(&out, "%s:\n", scope); convert(&c, kon_jsonGet(kon_jsonAt(pages, p), "list")); put(&out, "\treturn\n\n"); entries++; } } kon_jsonFree(json); } if (text) { char path[1100]; FILE *f; snprintf(path, sizeof path, "%s.kons", prefix); f = fopen(path, "wb"); if (!f || fwrite(out.p, 1, out.n, f) != out.n || fclose(f) != 0) { fprintf(stderr, "cannot write %s (does the output folder exist?)\n", path); return 1; } } if (konsc_write(out.p, out.n, prefix, name, split, &result, error, sizeof error) != 0) { fprintf(stderr, "the script could not be written: %s (use -text to see it)\n", error); return 1; } free(out.p); if (writeSpeakers(prefix, name, c.scratchSwitch, c.scratchVar)) return 1; printf("%d scripts, %d commands, %d labels, %d speakers\n", entries, result.commands, result.labels, speakerCount); printf("%d commands became konscript, %d became host commands", structured, hostLines); printf(" (%d kinds)\n", hostKinds); for (k = 0; k < hostKinds; k++) { int best = k, j; tally_t t; for (j = k + 1; j < hostKinds; j++) { if (hosts[j].n > hosts[best].n) best = j; } t = hosts[k]; hosts[k] = hosts[best]; hosts[best] = t; if (k < 15) printf(" %6d host %d\n", hosts[k].n, hosts[k].code); } if (pluginKinds) printf("%d kinds of plugin commands (356 and 357):\n", pluginKinds); for (k = 0; k < pluginKinds; k++) { int best = k, j; pluginTally_t t; for (j = k + 1; j < pluginKinds; j++) { if (plugins[j].n > plugins[best].n) best = j; } t = plugins[k]; plugins[k] = plugins[best]; plugins[best] = t; if (k < 15) printf(" %6d %s\n", plugins[k].n, plugins[k].name); } if (warnings) printf("%d warnings\n", warnings); free(speakers); return 0; }