summaryrefslogtreecommitdiff
path: root/kartik/modding
diff options
context:
space:
mode:
authorMinteck <contact@minteck.org>2022-05-18 18:50:12 +0200
committerMinteck <contact@minteck.org>2022-05-18 18:50:12 +0200
commit0bd45cb2cae3af05adaad3f38d129bb67c3e0ec0 (patch)
tree97a7e15187fe7fcb5b8775e03a62f62ac8fc5c61 /kartik/modding
parentd4805039b8ea7b30f5e78cf53caf8fd3f267256a (diff)
downloadarcade-0bd45cb2cae3af05adaad3f38d129bb67c3e0ec0.tar.gz
arcade-0bd45cb2cae3af05adaad3f38d129bb67c3e0ec0.tar.bz2
arcade-0bd45cb2cae3af05adaad3f38d129bb67c3e0ec0.zip
Add KartikHEADtrunk
Diffstat (limited to 'kartik/modding')
-rwxr-xr-xkartik/modding/compiler.js17
-rwxr-xr-xkartik/modding/hooks.js261
-rwxr-xr-xkartik/modding/parser.js278
-rwxr-xr-xkartik/modding/resources.js65
4 files changed, 621 insertions, 0 deletions
diff --git a/kartik/modding/compiler.js b/kartik/modding/compiler.js
new file mode 100755
index 0000000..240c9a9
--- /dev/null
+++ b/kartik/modding/compiler.js
@@ -0,0 +1,17 @@
+class BuildError extends Error {
+ constructor(orig, ...params) {
+ super(...params);
+ this.name = "BuildError";
+ this.stack = this.stack + "\n" + orig.stack;
+ }
+}
+
+const tsbuild = require('../typescript/builder');
+
+for (file of importedTypeScriptFiles) {
+ try {
+ tsbuild(file.file, homedir + "/.kartik/build/" + file.output);
+ } catch (e) {
+ throw new BuildError(e, "Error while building " + file.file + " from package " + file.pkg)
+ }
+} \ No newline at end of file
diff --git a/kartik/modding/hooks.js b/kartik/modding/hooks.js
new file mode 100755
index 0000000..8e189d2
--- /dev/null
+++ b/kartik/modding/hooks.js
@@ -0,0 +1,261 @@
+const { dialog } = require('electron');
+const fs = require('fs');
+
+function hook_Music(mod, type, hname) {
+ hook = mod["Hook"][hname];
+ names = Object.keys(hook);
+
+ for (i in names) {
+ name = names[i];
+ if (name !== "Music.Start" && name !== "Music.Prepare" && name !== "Music.Title" && name !== "Music.Credits" && name !== "Music.Win" && name !== "Music.Game1" && name !== "Music.Game2" && name !== "Music.Game3" && name !== "Music.Game4" && name !== "Music.Game5" && name !== "Music.Game6" && name !== "Music.Game7" && name !== "Music.Game8" && name !== "Music.Game9" && name !== "Music.Menu") {
+ dialog.showMessageBoxSync(
+ {
+ type: "error",
+ title: "KMP Mod Loader",
+ message: "On package " + item + ": hook " + hname + " (" + type + "): invalid hook data, EXITING"
+ }
+ )
+ process.exit(2);
+ } else {
+ switch (name) {
+ case "Music.Start":
+ resources.music['start'].file = mod["_Path"] + "/" + mod["Hook"][hname][name];
+ resources.music['start'].original = false;
+ break;
+ case "Music.Title":
+ resources.music['title'].file = mod["_Path"] + "/" + mod["Hook"][hname][name];
+ resources.music['title'].original = false;
+ break;
+ case "Music.Menu":
+ resources.music['title'].file = mod["_Path"] + "/" + mod["Hook"][hname][name];
+ resources.music['title'].original = false;
+ break;
+ case "Music.Credits":
+ resources.music['credits'].file = mod["_Path"] + "/" + mod["Hook"][hname][name];
+ resources.music['credits'].original = false;
+ break;
+ case "Music.Win":
+ resources.music['win'].file = mod["_Path"] + "/" + mod["Hook"][hname][name];
+ resources.music['win'].original = false;
+ break;
+ case "Music.Prepare":
+ resources.music['prepare'].file = mod["_Path"] + "/" + mod["Hook"][hname][name];
+ resources.music['prepare'].original = false;
+ break;
+ case "Music.Game1":
+ resources.music['game1'].file = mod["_Path"] + "/" + mod["Hook"][hname][name];
+ resources.music['game1'].original = false;
+ break;
+ case "Music.Game2":
+ resources.music['game2'].file = mod["_Path"] + "/" + mod["Hook"][hname][name];
+ resources.music['game2'].original = false;
+ break;
+ case "Music.Game3":
+ resources.music['game3'].file = mod["_Path"] + "/" + mod["Hook"][hname][name];
+ resources.music['game3'].original = false;
+ break;
+ case "Music.Game4":
+ resources.music['game4'].file = mod["_Path"] + "/" + mod["Hook"][hname][name];
+ resources.music['game4'].original = false;
+ break;
+ case "Music.Game5":
+ resources.music['game5'].file = mod["_Path"] + "/" + mod["Hook"][hname][name];
+ resources.music['game5'].original = false;
+ break;
+ case "Music.Game6":
+ resources.music['game6'].file = mod["_Path"] + "/" + mod["Hook"][hname][name];
+ resources.music['game6'].original = false;
+ break;
+ case "Music.Game7":
+ resources.music['game7'].file = mod["_Path"] + "/" + mod["Hook"][hname][name];
+ resources.music['game7'].original = false;
+ break;
+ case "Music.Game8":
+ resources.music['game8'].file = mod["_Path"] + "/" + mod["Hook"][hname][name];
+ resources.music['game8'].original = false;
+ break;
+ case "Music.Game9":
+ resources.music['game9'].file = mod["_Path"] + "/" + mod["Hook"][hname][name];
+ resources.music['game9'].original = false;
+ break;
+ }
+ }
+ }
+}
+
+function hook_SoundFX(mod, type, hname) {
+ hook = mod["Hook"][hname];
+ names = Object.keys(hook);
+
+ for (i in names) {
+ name = names[i];
+ if (name !== "SFX.Click" && name !== "SFX.Crash" && name !== "SFX.Intro" && name !== "SFX.Last" && name !== "SFX.Menu" && name !== "SFX.Pass" && name !== "SFX.Pause" && name !== "SFX.Start" && name !== "SFX.Win") {
+ dialog.showMessageBoxSync(
+ {
+ type: "error",
+ title: "KMP Mod Loader",
+ message: "On package " + item + ": hook " + hname + " (" + type + "): invalid hook data, EXITING"
+ }
+ )
+ process.exit(2);
+ } else {
+ switch (name) {
+ case "SFX.Click":
+ resources.sfx['click'] = mod["_Path"] + "/" + mod["Hook"][hname][name];
+ break;
+ case "SFX.Crash":
+ resources.sfx['crash'] = mod["_Path"] + "/" + mod["Hook"][hname][name];
+ break;
+ case "SFX.Intro":
+ resources.sfx['intro'] = mod["_Path"] + "/" + mod["Hook"][hname][name];
+ break;
+ case "SFX.Last":
+ resources.sfx['last'] = mod["_Path"] + "/" + mod["Hook"][hname][name];
+ break;
+ case "SFX.Menu":
+ resources.sfx['menu'] = mod["_Path"] + "/" + mod["Hook"][hname][name];
+ break;
+ case "SFX.Pass":
+ resources.sfx['pass'] = mod["_Path"] + "/" + mod["Hook"][hname][name];
+ break;
+ case "SFX.Pause":
+ resources.sfx['pause'] = mod["_Path"] + "/" + mod["Hook"][hname][name];
+ break;
+ case "SFX.Start":
+ resources.sfx['start'] = mod["_Path"] + "/" + mod["Hook"][hname][name];
+ break;
+ case "SFX.Win":
+ resources.sfx['win'] = mod["_Path"] + "/" + mod["Hook"][hname][name];
+ break;
+ }
+ }
+ }
+}
+
+function hook_Car(mod, type, hname) {
+ hook = mod["Hook"][hname];
+ names = Object.keys(hook);
+
+ for (i in names) {
+ name = names[i];
+
+ if (name.startsWith("Car.")) {
+ resources.cars[name.substr(4)] = mod["_Path"] + "/" + mod["Hook"][hname][name];
+ } else {
+ dialog.showMessageBoxSync(
+ {
+ type: "error",
+ title: "KMP Mod Loader",
+ message: "On package " + item + ": hook " + hname + " (" + type + "): invalid hook data, EXITING"
+ }
+ )
+ process.exit(2);
+ }
+ }
+}
+
+function hook_Circuit(mod, type, hname) {
+ hook = mod["Hook"][hname];
+ names = Object.keys(hook);
+
+ for (i in names) {
+ name = names[i];
+
+ if (name.startsWith("Circuit.")) {
+ resources.races[name.substr(4)] = mod["_Path"] + "/" + mod["Hook"][hname][name] + ".html";
+ } else {
+ dialog.showMessageBoxSync(
+ {
+ type: "error",
+ title: "KMP Mod Loader",
+ message: "On package " + item + ": hook " + hname + " (" + type + "): invalid hook data, EXITING"
+ }
+ )
+ process.exit(2);
+ }
+ }
+}
+
+function hook_API(mod, type, hname) {
+ console.log(" * KMP-API TypeScript engine: " + item + "$" + hname)
+ hook = mod["Hook"][hname];
+ names = Object.keys(hook);
+
+ for (i in names) {
+ name = names[i];
+
+ if (name === "API.Includes") {
+ if (fs.existsSync(mod["_Path"] + "/" + mod["Hook"][hname][name])) {
+ if (fs.statSync(mod["_Path"] + "/" + mod["Hook"][hname][name]).isDirectory()) {
+ flist = fs.readdirSync(mod["_Path"] + "/" + mod["Hook"][hname][name]);
+ for (file of flist) {
+ if (file.endsWith(".ts")) {
+ importedTypeScriptFiles.push({
+ file: mod["_Path"] + "/" + mod["Hook"][hname][name] + "/" + file,
+ pkg: item,
+ output: item + "--" + file + ".js"
+ });
+ }
+ }
+ } else {
+ importedTypeScriptFiles.push({
+ file: mod["_Path"] + "/" + mod["Hook"][hname][name],
+ pkg: item,
+ output: item + "--" + mod["Hook"][hname][name] + ".js"
+ });
+ }
+ } else {
+ dialog.showMessageBoxSync(
+ {
+ type: "error",
+ title: "KMP Mod Loader",
+ message: "On package " + item + ": KMP-API includes " + mod["Hook"][hname][name] + " (" + mod["_Path"] + "/" + mod["Hook"][hname][name] + "): no such file or directory, EXITING"
+ }
+ )
+ process.exit(2);
+ }
+ } else {
+ dialog.showMessageBoxSync(
+ {
+ type: "error",
+ title: "KMP Mod Loader",
+ message: "On package " + item + ": hook " + hname + " (" + type + "): invalid hook data, EXITING"
+ }
+ )
+ process.exit(2);
+ }
+ }
+}
+
+module.exports = (mod, type, name) => {
+ hook = mod["Hook"][name];
+
+ if (type !== "Kartik.Music" && type !== "Kartik.SoundFX" && type !== "Kartik.Circuit" && type !== "Kartik.Car" && type !== "Kartik.API") {
+ dialog.showMessageBoxSync(
+ {
+ type: "error",
+ title: "KMP Mod Loader",
+ message: "On package " + item + ": invalid hook type, EXITING"
+ }
+ )
+ process.exit(2);
+ }
+
+ switch (type) {
+ case "Kartik.Music":
+ hook_Music(mod, type, name);
+ break;
+ case "Kartik.SoundFX":
+ hook_SoundFX(mod, type, name);
+ break;
+ case "Kartik.Car":
+ hook_Car(mod, type, name);
+ break;
+ case "Kartik.Circuit":
+ hook_Circuit(mod, type, name);
+ break;
+ case "Kartik.API":
+ hook_API(mod, type, name);
+ break;
+ }
+}
diff --git a/kartik/modding/parser.js b/kartik/modding/parser.js
new file mode 100755
index 0000000..2c39055
--- /dev/null
+++ b/kartik/modding/parser.js
@@ -0,0 +1,278 @@
+const fs = require('fs');
+const os = require('os');
+const ini = require('ini');
+const YAML = require('yaml');
+const { dialog } = require('electron');
+const semver = require('semver');
+
+function unload(arr) {
+ var what, a = arguments, L = a.length, ax;
+ while (L > 1 && arr.length) {
+ what = a[--L];
+ while ((ax= arr.indexOf(what)) !== -1) {
+ arr.splice(ax, 1);
+ }
+ }
+ return arr;
+}
+
+list = fs.readdirSync(homedir + "/.kartik/mods");
+
+for (index in list) {
+ item = list[index];
+
+ if (!fs.existsSync(homedir + "/.kartik/mods/" + item + "/kartik.ini") && !fs.existsSync(homedir + "/.kartik/mods/" + item + "/kartik.yml")) {
+ unload(list, item);
+ dialog.showMessageBoxSync(
+ {
+ type: "error",
+ title: "KMP Mod Loader",
+ message: "On package " + item + ": no kartik.ini or kartik.yml file found"
+ }
+ )
+ }
+}
+
+for (index in list) {
+ item = list[index];
+
+ parts = item.split(".");
+ if (parts.length < 2 || parts[0].length > 5 || item.length > 49 || parts.length > 7 || !/^[a-zA-Z0-9.]*$/gm.test(item)) {
+ unload(list, item);
+ if (parts.length < 2) {
+ dialog.showMessageBoxSync(
+ {
+ type: "error",
+ title: "KMP Mod Loader",
+ message: "On package " + item + ": invalid package name: not enough parts"
+ }
+ )
+ }
+ if (parts[0].length > 5) {
+ dialog.showMessageBoxSync(
+ {
+ type: "error",
+ title: "KMP Mod Loader",
+ message: "On package " + item + ": invalid package name: tld too long"
+ }
+ )
+ }
+ if (item.length > 49) {
+ dialog.showMessageBoxSync(
+ {
+ type: "error",
+ title: "KMP Mod Loader",
+ message: "On package " + item + ": invalid package name: too long"
+ }
+ )
+ }
+ if (parts.length > 7) {
+ dialog.showMessageBoxSync(
+ {
+ type: "error",
+ title: "KMP Mod Loader",
+ message: "On package " + item + ": invalid package name: too many parts"
+ }
+ )
+ }
+ if (!/^[a-zA-Z0-9.]*$/gm.test(item)) {
+ dialog.showMessageBoxSync(
+ {
+ type: "error",
+ title: "KMP Mod Loader",
+ message: "On package " + item + ": invalid package name: invalid format"
+ }
+ )
+ }
+ }
+}
+
+for (index in list) {
+ item = list[index];
+
+ if (fs.existsSync(homedir + "/.kartik/mods/" + item + "/kartik.ini") && fs.existsSync(homedir + "/.kartik/mods/" + item + "/kartik.yml")) {
+ unload(list, item);
+ dialog.showMessageBoxSync(
+ {
+ type: "error",
+ title: "KMP Mod Loader",
+ message: "On package " + item + ": both kartik.ini and kartik.yml found"
+ }
+ )
+ }
+}
+
+global.moddata = {};
+
+for (index in list) {
+ if (fs.existsSync(homedir + "/.kartik/mods/" + item + "/kartik.ini")) {
+ try {
+ moddata[list] = ini.parse(fs.readFileSync(homedir + "/.kartik/mods/" + item + "/kartik.ini", "utf-8"));
+ } catch (e) {
+ unload(list, item);
+ console.error(e);
+ dialog.showMessageBoxSync(
+ {
+ type: "error",
+ title: "KMP Mod Loader",
+ message: "On package " + item + ": ini parser error"
+ }
+ )
+ }
+ } else {
+ try {
+ moddata[list] = YAML.parse(fs.readFileSync(homedir + "/.kartik/mods/" + item + "/kartik.yml", "utf-8"));
+ } catch (e) {
+ unload(list, item);
+ console.error(e);
+ dialog.showMessageBoxSync(
+ {
+ type: "error",
+ title: "KMP Mod Loader",
+ message: "On package " + item + ": yaml parser error"
+ }
+ )
+ }
+ }
+}
+
+for (index in list) {
+ item = list[index];
+ mod = moddata[item];
+
+ roots = Object.keys(mod);
+
+ for (i1 in roots) {
+ r = roots[i1];
+ if (r !== "Meta" && r !== "Hook") {
+ unload(list, item);
+ dialog.showMessageBoxSync(
+ {
+ type: "error",
+ title: "KMP Mod Loader",
+ message: "On package " + item + ": invalid root section: " + r
+ }
+ )
+ }
+ }
+
+ for (i2 in Object.keys(mod["Meta"])) {
+ r = Object.keys(mod["Meta"])[i2];
+ if (r !== "Properties" && r !== "Hooks") {
+ unload(list, item);
+ dialog.showMessageBoxSync(
+ {
+ type: "error",
+ title: "KMP Mod Loader",
+ message: "On package " + item + ": invalid meta section: " + r
+ }
+ )
+ }
+ }
+
+ if (Object.keys(mod["Meta"]).length < 2) {
+ unload(list, item);
+ dialog.showMessageBoxSync(
+ {
+ type: "error",
+ title: "KMP Mod Loader",
+ message: "On package " + item + ": meta sections incomplete"
+ }
+ )
+ }
+
+ meta = mod["Meta"];
+
+ for (i3 in Object.keys(meta["Properties"])) {
+ r = Object.keys(meta["Properties"])[i3];
+ if (r !== "Mod.Name" && r !== "Mod.Version" && r !== "Mod.Author" && r !== "Mod.Required") {
+ unload(list, item);
+ dialog.showMessageBoxSync(
+ {
+ type: "error",
+ title: "KMP Mod Loader",
+ message: "On package " + item + ": invalid property: " + r
+ }
+ )
+ }
+ }
+
+ if (Object.keys(meta["Properties"]).length < 4) {
+ unload(list, item);
+ dialog.showMessageBoxSync(
+ {
+ type: "error",
+ title: "KMP Mod Loader",
+ message: "On package " + item + ": properties incomplete"
+ }
+ )
+ }
+
+ props = meta.Properties;
+
+ if (props["Mod.Required"].trim() !== "*") {
+ sver = require('../package.json').version.split(".").map((i) => { if (!isNaN((i - 1 + 1))) { return (i - 1 + 1).toString(); } else { return i; } }).join(".");
+ if (semver.valid(sver)) {
+ if (!semver.satisfies(sver, props["Mod.Required"].trim())) {
+ unload(list, item);
+ dialog.showMessageBoxSync(
+ {
+ type: "error",
+ title: "KMP Mod Loader",
+ message: "On package " + item + ": mod made for another version of Kartik"
+ }
+ )
+ }
+ } else {
+ if (require('../package.json').channel !== "git" && require('../package.json').channel !== "nightly") {
+ unload(list, item);
+ dialog.showMessageBoxSync(
+ {
+ type: "error",
+ title: "KMP Mod Loader",
+ message: "On package " + item + ": invalid game version"
+ }
+ )
+ }
+ }
+ }
+
+ hooks = meta["Hooks"];
+ hname = Object.keys(hooks);
+
+ for (i4 in hname) {
+ hookn = hname[i4];
+ hook = hooks[hookn];
+ if (typeof mod["Hook"][hook] === "undefined") {
+ unload(list, item);
+ dialog.showMessageBoxSync(
+ {
+ type: "error",
+ title: "KMP Mod Loader",
+ message: "On package " + item + ": block " + hook + " required by hook " + hookn + " not found"
+ }
+ )
+ }
+ }
+}
+
+for (index in list) {
+ item = list[index];
+ mod = moddata[item];
+ meta = mod["Meta"];
+
+ hooks = meta["Hooks"];
+ hname = Object.keys(hooks);
+
+ for (i4 in hname) {
+ hookn = hname[i4];
+ hook = hooks[hookn];
+
+ mod["_Path"] = homedir + "/.kartik/mods/" + item;
+
+ console.log(" * KMP hook: " + item + "$" + hookn)
+ require('./hooks')(mod, hookn, hook);
+ }
+}
+
+global.mods = list;
diff --git a/kartik/modding/resources.js b/kartik/modding/resources.js
new file mode 100755
index 0000000..7301901
--- /dev/null
+++ b/kartik/modding/resources.js
@@ -0,0 +1,65 @@
+const fs = require('fs');
+
+global.resources = {
+ music: {},
+ sfx: {},
+ races: {},
+ cars: {},
+ scenario: {
+ voice: {},
+ averi: {}
+ }
+};
+
+music_list = fs.readdirSync("./music");
+for (index in music_list) {
+ music = music_list[index];
+ name = music.split(".")[0];
+ resources.music[name] = {
+ file: KartikRoot + "/music/" + music,
+ original: true
+ };
+}
+
+sfx_list = fs.readdirSync("./sfx");
+for (index in sfx_list) {
+ sfx = sfx_list[index];
+ name = sfx.split(".")[0];
+ if (sfx.split(".")[1] === "mp3") {
+ resources.sfx[name] = KartikRoot + "/sfx/" + sfx;
+ }
+}
+
+races_list = fs.readdirSync("./race/circuits");
+for (index in races_list) {
+ races = races_list[index];
+ name = races.split(".")[0];
+ if (races.split(".")[1] === "html") {
+ resources.races[name] = "./race/circuits/" + races;
+ }
+}
+
+cars_list = fs.readdirSync("./race/models");
+for (index in cars_list) {
+ cars = cars_list[index];
+ name = cars.split(".")[0];
+ resources.cars[name] = KartikRoot + "/race/models/" + cars;
+}
+
+scvoice_list = fs.readdirSync("./scenario/voice");
+for (index in scvoice_list) {
+ scvoice = scvoice_list[index];
+ name = scvoice.split(".")[0];
+ resources.scenario.voice[name] = KartikRoot + "/scenario/voice/" + scvoice;
+}
+
+scchar_list = fs.readdirSync("./scenario/character");
+for (index in scchar_list) {
+ scchar = scchar_list[index];
+ name = scchar.split(".")[0];
+ resources.scenario.averi[name] = KartikRoot + "/scenario/character/" + scchar;
+}
+
+if (typeof building !== "undefined" && building) {
+ global.targetResources = JSON.stringify(resources);
+}