// Removed useless files, to speed up build process and make it clearer. const path = require("path"); const fs = require("fs"); const readline = require("readline"); // cmdid current version const read_cmdid_last = "cmdid_last.csv"; const read_cmdid = "cmdid.csv"; const read_cmdid_output = "cmdid.json"; const read_cmdid_ht_output = "cmdid_ht_40.json"; const read_cmdid_output_gc = "cmdid_gc.json"; const read_cmdid_output_gc_update = "cmdid_gc_update.json"; const read_cmdid_output_gc_nofound = "cmdid_gc_nofound.json"; const file_gc_needed = "gc_needed.json"; const file_gc_needed2 = "gc_needed2.json"; const write_op = "PacketOpcodes.java"; console.log(process.cwd()); //(TODO: add input file) // folder gc auto-generated proto const folder_proto_gc_gen = "../GSServer-GC/src/generated/main/java/emu/grasscutter/net/proto/"; // file PacketOpcodes currently in use const read_cmdid_gc = "../GSServer-GC/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java"; const folder_packet_gc = "../GSServer-GC/src/main/java/emu/grasscutter/server/packet/"; const folder_gc_scan = "../GSServer-GCOriginal/src/main/java/emu/grasscutter/"; //const read_cmdid = fs.readFileSync("cmdid.csv"); //const read_packetopcodes = fs.readFileSync("PacketOpcodes.java"); var data = []; var data_gc = []; var index_file_gen = 0; var index_file_cmdid = 0; var index_cmdid_gc = 0; var index_cmdid_gc_out = 0; function check_gen() { fs.readdir(folder_proto_gc_gen, function (err, files) { //handling error if (err) { return console.log("Unable to scan directory: " + err); } files.forEach(function (file) { index_file_gen++; }); console.log("file proto gen: " + index_file_gen); }); } // gen json file cmdid function get_cmdid_json() { const inputStreamcmdid = fs.createReadStream(read_cmdid); var lineReadercmdid = readline.createInterface({ input: inputStreamcmdid, terminal: false, }); lineReadercmdid.on("line", function (line) { var config = line.split(","); var subdata = new Object(); subdata["name"] = config[0]; subdata["id"] = parseInt(config[1]); data.push(subdata); index_file_cmdid++; }); lineReadercmdid.on("close", function () { console.log("found cmd id " + index_file_cmdid); save_json(data, read_cmdid_output); }); } var data_gen = []; var index_file_cmdid_gen = 0; // gen json file cmdid function read_cmdid_ht_json() { const k = read_json(read_cmdid_ht_output); // console.log(k); for (const key in k) { var name = k[key]; var id = parseInt(key); //console.log(id); var subdata = new Object(); subdata["name"] = name; subdata["id"] = id; data_gen.push(subdata); index_file_cmdid_gen++; } console.log("found cmd id " + index_file_cmdid_gen); save_json(data_gen, read_cmdid_output); } // create cmdid from gc which comes from PacketOpcodes function get_cmdid_gc() { const inputStreamcmdid = fs.createReadStream(read_cmdid_gc); var lineReadercmdid = readline.createInterface({ input: inputStreamcmdid, terminal: false, }); lineReadercmdid.on("line", function (line) { var config = line.split(" = "); var name = config[0]; var id = parseInt(config[1]); if (name.includes("public static final int")) { name = name.replace("\tpublic static final int ", ""); // skip 0 ? if (id == 0) { return; } var subdata = new Object(); subdata["name"] = name; subdata["id"] = id; data_gc.push(subdata); //console.log(name); index_cmdid_gc++; } else { index_cmdid_gc_out++; } }); lineReadercmdid.on("close", function () { console.log( "found cmd id " + index_cmdid_gc + " | no need " + index_cmdid_gc_out ); save_json(data_gc, read_cmdid_output_gc); }); } var found_cmdid_new = 0; var nofound_cmdid_new = 0; var rename_name_cmdid = 0; var noneed_rename_name_cmdid = 0; var data_gc_cmdid_nofound = []; var check_dunp_id = []; function update_cmdid_gc() { let melon = ""; // cmdid_gc.json (read_cmdid_output_gc) and cmdid.json (read_cmdid_output) const json_cmdid_last = read_json(read_cmdid_output); const json_cmdid_old = read_json(read_cmdid_output_gc); json_cmdid_old.forEach(function (s, index) { var id = s.id; var name = s.name.trim(); // switch to name mode? var found_id = json_cmdid_last.find((j) => j.name == name); if (found_id) { found_cmdid_new++; if (id == found_id.id) { noneed_rename_name_cmdid++; } else { rename_name_cmdid++; //console.log("Wow rename -> ID: "+id+" > "+found_id.id); s.replace = s.id; // old s.id = found_id.id; // rename id } } else { console.log("Wow nofound -> ID: " + id + " | Name: " + name); data_gc_cmdid_nofound.push(s); nofound_cmdid_new++; } melon += `${name},${id}\n`; // find dump by id /* var found_id = check_dunp_id.find((j) => j.id == id); if (found_id) { console.log("Wow dup -> ID: " +id +" (ADD " +found_id.id +") | Name Remove: " +name +" (ADD " +found_id.name +")"); // remove bad json_cmdid_old.splice(index, 1); } else { check_dunp_id.push(s); } */ // find dump by name? }); check_dunp_id = []; // clear // I don't know why this happened but make sure to check again /* var check_dunp_name = []; json_cmdid_old.forEach(function (s, index) { var id = s.id; var name = s.name.trim(); var found_name = check_dunp_name.find((j) => j.name === name); if (found_name) { console.log( "Wow dup -> ID: " + id + " (ADD " + found_name.id + ") | Name Remove: " + name + " (ADD " + found_name.name + ")" ); // remove bad json_cmdid_old.splice(index, 1); } else { check_dunp_name.push(s); } var found_id = json_cmdid_last.find((j) => j.id == id); if (found_id) { if (name != found_id.name) { console.log( "Wow why? -> ID: " + id + " | Name: " + name + " > " + found_id.name ); s.name = found_id.name; } } }); */ //check_dunp_name = []; // clear console.log( "found " + found_cmdid_new + " | no found " + nofound_cmdid_new + " | rename " + rename_name_cmdid + " | noneed rename " + noneed_rename_name_cmdid ); // const csvRows = Object.entries(read_cmdid_output_gc_update).map(([name, id]) => `${name},${id}`); // const csvData = csvRows.join("\n"); fs.writeFileSync(read_cmdid_last, melon); save_json(json_cmdid_old, read_cmdid_output_gc_update); save_json(data_gc_cmdid_nofound, read_cmdid_output_gc_nofound); } function read_json(file) { return JSON.parse(fs.readFileSync(file)); } // save json function save_json(raw, file) { var j = JSON.stringify(raw, null, 4); save(j, file); } function save(raw, file) { fs.writeFile(file, raw, "utf8", function (err) { if (err) { console.log("An error occured while writing to File."); return console.log(err); } console.log("File has been saved: " + file); }); } var dup_name = []; var count_dup = 0; var count_nodup = 0; function cmdid_to_op() { let melon = "\ package emu.grasscutter.net.packet;\ \n\ \npublic class PacketOpcodes {\ \n// Empty\ \npublic static final int NONE = 0;\ \n\ \n// Opcodes\ "; //const read_file_gcneed = fs.readFileSync(file_gc_needed2); //const json_gcneed_raw = JSON.parse(read_file_gcneed); const cmdidfix_raw = fs.readFileSync(read_cmdid_output_gc_update); const json_cmdidfix_raw = JSON.parse(cmdidfix_raw); //const json_cmdidfix_raw = read_json(read_cmdid_output_gc_update); json_cmdidfix_raw.forEach(function (s) { var found_id = dup_name.find((j) => j.name == s.name); if (!found_id) { melon += "\npublic static final int " + s.name + " = " + s.id + ";"; dup_name.push(s); count_nodup++; } else { count_dup++; if (s.replace) { console.log("DUP: " + found_id.id + " > " + s.id + " "); } } //console.log(s); /* var found_new = json_cmdidfix_raw.find((j) => j.name == s.name); if(found_new){ } */ }); console.log("done no dup " + count_nodup + " | dup " + count_dup); melon += "\n}"; save(melon, write_op); // use "npx prettier --write PacketOpcodes.java" for better Formatter } var index_file_packet = 0; var index_file_packet_found = 0; var index_file_packet_nofound = 0; var index_file_packet_rename = 0; var index_file_packet_norename = 0; var index_file_packet_renamemulti = 0; var file_gc_need = []; function fix_packet(saveit = false) { const files = getAllFiles(folder_packet_gc); const json_cmdid_last = read_json(read_cmdid_output); const json_cmdid_old = read_json(read_cmdid_output_gc); const json_cmdidfix_raw = read_json(read_cmdid_output_gc_update); files.forEach(function (file) { //var f = path + "/" + file; const read = fs.readFileSync(file); var real = read.toString(); var name = getPacketOpcodes(real); if (!name) { console.log("no found"); return; } if (name === "NONE") { return; } var subdata = new Object(); subdata["name"] = name; file_gc_need.push(subdata); //var name = c[1]; //console.log(r); var found_old = json_cmdid_old.find((j) => j.name === name); if (found_old) { //console.log(found_old); index_file_packet_found++; var found_new = json_cmdid_last.find((j) => j.id == found_old.id); if (found_new) { if (found_new.name != found_old.name) { index_file_packet_rename++; console.log( "Found need rename: " + found_old.name + " > " + found_new.name ); // rename all json_cmdidfix_raw.forEach(function (s) { var r = s.replace; if (r) { // var notify = HomeNewUnlockedBgmIdListNotify.Unk2700_MEBFPBDNPGO_ServerNotify // var notify = Unk2700MEBFPBDNPGOServerNotify.HomeNewUnlockedBgmIdListNotify // Unk2700MEBFPBDNPGOServerNotify to HomeNewUnlockedBgmIdListNotifyOuterClass // Unk2700OGHMHELMBNNServerRsp to HomeChangeBgmRspOuterClass // - Need More Auto like - // addUnk2700ELJPLMIHNIP to addNewUnlockedBgmIdList (this should be found inside gen proto) // setUnk2700BJHAMKKECEI to setBgmId if (r.match("Unk")) { //console.log(r); var x = r.split("_"); var tr = x.join(""); var realneed = tr; if (tr.match("ServerNotify")) { //console.log("found: "+tr); let re = new RegExp(`${tr}`, "g"); tr = tr.replace(re, `${s.name}OuterClass`); //console.log("found: " + tr); } else if (tr.match("ServerRsp")) { let re = new RegExp(`${tr}`, "g"); tr = tr.replace(re, `${s.name}OuterClass`); } else { let re = new RegExp(`${tr}`, "g"); tr = tr.replace(re, s.name); //console.log("found: " + tr); } let re = new RegExp(`${realneed}`, "g"); real = real.replace(re, tr); } let re = new RegExp(`${r}`, "g"); real = real.replace(re, s.name); } }); // simpel rename //let re = new RegExp(`${found_old.name}`, "g"); //real = real.replace(re, found_new.name); //console.log(real); if (saveit) { save(real, f); } } } else { index_file_packet_norename++; //console.log("Same name "+name); } } else { index_file_packet_nofound++; console.log("No found " + name); } //return; index_file_packet++; }); save_json(file_gc_need, file_gc_needed); // This only applies to PacketOpcodes console.log( "Index file: " + index_file_packet + " | found " + index_file_packet_found + " | No found " + index_file_packet_nofound + " | Rename " + index_file_packet_rename + " | NoRename " + index_file_packet_norename ); } function isBlank(str) { return !!!str || /^\s*$/.test(str); } function getAllFiles(dirPath, arrayOfFiles) { files = fs.readdirSync(dirPath); arrayOfFiles = arrayOfFiles || []; files.forEach(function (file) { if (fs.statSync(dirPath + "/" + file).isDirectory()) { arrayOfFiles = getAllFiles(dirPath + "/" + file, arrayOfFiles); } else { arrayOfFiles.push(path.join(__dirname, dirPath, "/", file)); } }); return arrayOfFiles; } function getPacketOpcodes(raw) { var r = raw.match(/\(.*?\)/g).map((x) => x.replace(/[()]/g, "")); var name; r.forEach(function (s, index) { if (s.match("PacketOpcodes.")) { name = s.split("PacketOpcodes.")[1]; if (name.match(",")) { name = name.split(",")[0]; } return; } }); return name; } // C:\Users\Administrator\Desktop\Projek\Docker\GS\gs\GSServer-GC\src\main\java\emu\grasscutter var file_proto = []; var file_proto_more = []; var found_noclean = 0; var found_needclean = 0; var found_maybe_related = 0; const regex_import = /\import "(.*?)\.proto"/g; // find import in file function find_import(file) { var torequire = []; var dir = path.parse(file).dir; if (file.match("ModifierDurability")) { //console.log("fff"); } var rd; // read file try { const read = fs.readFileSync(file); rd = read.toString(); } catch (error) { return; } while ((m = regex_import.exec(rd)) !== null) { // This is necessary to avoid infinite loops with zero-width matches if (m.index === regex_import.lastIndex) { regex_import.lastIndex++; } // The result can be accessed through the `m`-variable. m.forEach((match, groupIndex) => { // only index 1 grup if (groupIndex == 1) { var found_rt = torequire.find((j) => j === match); if (found_rt) { //console.log(`Skip ${match}`); } else { if (match.match("Unk")) { //console.log(file + " require " + match); } //var subdata = new Object(); //subdata["name"] = match; var sub = find_import(dir + "/" + match + ".proto"); if (sub) { sub.forEach(function (k) { var v = torequire.find((j) => j === k); if (!v) { torequire.push(k); } }); } torequire.push(match); } } }); } return torequire; } function clean_proto_gen() { //const files = getAllFiles(folder_proto_gc_gen); const files = getAllFiles("./proto"); const json_gc_needed = read_json(file_gc_needed); const json_gc_needed2 = read_json(file_gc_needed2); const json_gc_now = read_json(read_cmdid_output_gc); const json_gc_update = read_json(read_cmdid_output_gc_update); //AbilityInvokeArgument console.log( "File proto: " + files.length + " | Need " + json_gc_needed.length ); // find all file import files.forEach(function (file) { var name_file = path.parse(file).name; var toaddfile = new Object(); toaddfile["file"] = name_file; toaddfile["import"] = find_import(file); file_proto.push(toaddfile); }); //console.log(file_proto); function find_json_proto(name_file, newnew = null) { var noe = []; if (newnew) { noe = newnew; } var found_proto = file_proto.find((j) => name_file === j.file); // must same file if (found_proto) { // if found import if (found_proto.import) { found_proto.import.forEach(function (s) { var f = noe.find((j) => j === s); if (!f) { noe.push(s); // check sub var sub = find_json_proto(s, noe); sub.forEach(function (k) { var v = noe.find((j) => j === k); if (!v) { noe.push(k); } }); } }); } var ss = noe.find((j) => j === name_file); if (!ss) { noe.push(name_file); } } else { // skip } return noe; } //console.log(find_json_proto("ActivityInfo")); // try with proto json var filedonotdelete = []; files.forEach(function (file) { var name_file = path.parse(file).name; var findme = false; // find main file var found = json_gc_needed.find((j) => name_file.match(j.name)); if (found) { findme = true; } else { // skip } // find miss scan var found2 = json_gc_needed2.find((j) => name_file.match(j.name)); if (found2) { findme = true; } else { // skip } // ModifierDurability >AbilityAppliedModifier -> AbilitySyncStateInfo -> AvatarEnterSceneInfo and multi file // main file if (findme) { var tosub = find_json_proto(name_file); tosub.forEach(function (k) { var oo = filedonotdelete.find((j) => j === k); if (!oo) { filedonotdelete.push(k); } }); } else { // not main file /* if (name_file.match("ModifierDurability")) { console.log("3"); } */ if (file.match("ChannelerSlabChallenge")) { console.log(file); } } }); // ModifierDurability //console.log(filedonotdelete); // last files.forEach(function (file) { var name_file = path.parse(file).name; var toskip = filedonotdelete.find((j) => name_file === j); if (toskip) { found_noclean++; } else { found_needclean++; // ActivityInfo if (file.match("ChannelerSlabChallenge")) { console.log("ChannelerSlabChallenge"); } //console.log(name_file); //console.log("Remove file: " + file); try { fs.unlinkSync(file); //file removed } catch (err) { console.error(err); } } }); //console.log(filedonotdelete); console.log( "No clean: " + found_noclean + " | Need to clean: " + found_needclean + " | Related " + found_maybe_related ); } function clean_proto_gen_v2() { const files = getAllFiles("./proto"); console.log("File proto: " + files.length); // find all file import files.forEach(function (file) { // Get the file name from the file path const fileName = path.basename(file); // Use regular expressions to extract the desired part const match = fileName.match(/^(.*)\.proto$/); if (match == null) { return; } const messageName = match[1]; // Check if the string is all uppercase if (messageName === messageName.toUpperCase()) { console.log("Remove: " + file); try { fs.unlinkSync(file); //file removed } catch (err) { console.error(err); } } else { // console.log("String contains non-uppercase letters"); } }); } var g_todump = []; function scan_gc() { const files = getAllFiles(folder_gc_scan); console.log("index file: " + files.length); const regex = /import emu.grasscutter.net.proto.(.*?);/g; files.forEach(function (file) { // read file const read = fs.readFileSync(file); var rd = read.toString(); // find import while ((m = regex.exec(rd)) !== null) { // This is necessary to avoid infinite loops with zero-width matches if (m.index === regex.lastIndex) { regex.lastIndex++; } // The result can be accessed through the `m`-variable. m.forEach((match, groupIndex) => { // only index 1 grup if (groupIndex == 1) { if (match === "*") { return; } if (match.match(".")) { match = match.split(".")[0]; } match = match.replace("OuterClass", ""); var found_rt = g_todump.find((j) => j.name === match); if (found_rt) { //found_maybe_related++; //console.log(`Skip ${match}`); } else { var subdata = new Object(); subdata["name"] = match; g_todump.push(subdata); } } }); } }); save_json(g_todump, file_gc_needed2); // This only applies to PacketOpcodes //console.log(g_todump); } function clean_proto_event() { var torequire = [ "ActivityPushTipsData", "ActivityWatcherInfo", "MusicGameActivityDetailInfo", ]; var filedonotdelete = []; let dir = "./proto"; let file = "./proto/ActivityInfo.proto"; var rd; const files = getAllFiles(dir); // read file try { const read = fs.readFileSync(file); rd = read.toString(); } catch (error) { console.log(error); return; } //console.log(rd); while ((m = regex_import.exec(rd)) !== null) { // This is necessary to avoid infinite loops with zero-width matches if (m.index === regex_import.lastIndex) { regex_import.lastIndex++; } // The result can be accessed through the `m`-variable. m.forEach((match, groupIndex) => { // only index 1 grup if (groupIndex == 1) { //console.log(`t: ${match}`); var found_rt = torequire.find((j) => j === match); if (found_rt) { //console.log(`Skip ${match}`); } else { //console.log(`add ${match}`); var sub = find_import(dir + "/" + match + ".proto"); //console.log(sub); if (sub) { sub.forEach(function (k) { var v = torequire.find((j) => j === k); if (!v) { torequire.push(k); } }); } filedonotdelete.push(match); torequire.push(match); } } }); } // last files.forEach(function (file) { var name_file = path.parse(file).name; var toskip = filedonotdelete.find((j) => name_file === j); if (toskip) { found_needclean++; //console.log(name_file); try { fs.unlinkSync(file); console.log("Remove file: " + file); //file removed } catch (err) { //console.error(err); } } else { found_noclean++; } }); console.log( "No clean: " + found_noclean + " | Need to clean: " + found_needclean + " | Related " + found_maybe_related ); } function clean_proto_event_v2() { var torequire = [ "ParamList", "StrengthenPointData", "TowerLevelEndNotify", "TrialAvatarFirstPassDungeonNotify", ]; var filedonotdelete = []; let dir = "./proto"; let file = "./proto/DungeonSettleNotify.proto"; var rd; const files = getAllFiles(dir); // read file try { const read = fs.readFileSync(file); rd = read.toString(); } catch (error) { console.log(error); return; } //console.log(rd); while ((m = regex_import.exec(rd)) !== null) { // This is necessary to avoid infinite loops with zero-width matches if (m.index === regex_import.lastIndex) { regex_import.lastIndex++; } // The result can be accessed through the `m`-variable. m.forEach((match, groupIndex) => { // only index 1 grup if (groupIndex == 1) { //console.log(`t: ${match}`); var found_rt = torequire.find((j) => j === match); if (found_rt) { //console.log(`Skip ${match}`); } else { //console.log(`add ${match}`); var sub = find_import(dir + "/" + match + ".proto"); //console.log(sub); if (sub) { sub.forEach(function (k) { var v = torequire.find((j) => j === k); if (!v) { torequire.push(k); } }); } filedonotdelete.push(match); torequire.push(match); } } }); } console.log(filedonotdelete); // last files.forEach(function (file) { var name_file = path.parse(file).name; var toskip = filedonotdelete.find((j) => name_file === j); if (toskip) { found_needclean++; console.log(name_file); try { fs.unlinkSync(file); console.log("Remove file: " + file); //file removed } catch (err) { //console.error(err); } } else { found_noclean++; //console.log("skip "+name_file); } }); console.log( "No clean: " + found_noclean + " | Need to clean: " + found_needclean + " | Related " + found_maybe_related ); } function cmdid_json_to_csv() { // read_cmdid_last const k = read_json(read_cmdid_ht_output); //console.log(k); const csvHeaders = "ID,Name\n"; const csvRows = Object.entries(k).map(([name, id]) => `${id},${name}`); const csvData = csvRows.join("\n"); fs.writeFileSync(read_cmdid_last, csvData); console.log("CSV file written successfully"); } // cmdid_json_to_csv(); // Update GC Proto // get_cmdid_gc(); // 1. get cmd old gc //read_cmdid_ht_json(); // 2 or // get_cmdid_json(); // 2. get last cmdid.csv to json // now we have cmdid_gc.json and cmdid.json // update_cmdid_gc(); // 3. update gc cmdid (mode by id) // cmdid_to_op(); // 4. update op // npx prettier --write PacketOpcodes.java // scan_gc(); // 5. scan gc // clean_proto_event(); // 6. clean event, need manual // clean_proto_gen(); // 6. clean proto // clean_proto_gen_v2(); // TODO: clean DungeonSettleNotify // clean_proto_event_v2();