const fs = require('fs'); const path = require('path'); const systemNames = [ 'uint32', 'uint64', 'int32', 'int64', 'sint32', 'sint64', 'fixed32', 'fixed64', 'sfixed32', 'sfixed64', 'float', 'double', 'bool', 'string', 'bytes' ]; const protoFilePath = "deobf.proto"; const outputPath1 = "./unimplemented"; const outputPath2 = "./proto"; const ntPath = ''; const version = `5.0`; var cmdid_gen = []; // Read the skip list const skipFilePath = './skip2.txt'; let skipList; try { const skipFileContent = fs.readFileSync(skipFilePath, 'utf-8'); skipList = skipFileContent.split('\n').map(line => line.trim()).filter(line => line.length > 0); } catch (err) { console.error('Error reading skip file:', err); skipList = []; } function splitProtoFile(protoFilePath, nameTranslationPath = null, version = "0.0") { let ntMap = {}; const lines = fs.readFileSync(protoFilePath, 'utf-8').split('\n'); if (nameTranslationPath) { const ntLines = fs.readFileSync(nameTranslationPath, 'utf-8').split('\n'); for (let ntLine of ntLines) { if (ntLine.trim().startsWith('#')) continue; const [key, value] = ntLine.trim().split(' -> '); ntMap[key] = value; } } let currentContent = []; let currentName = null; let cmdid = null; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (line.startsWith('// CmdID: ')) { cmdid = line; } else if (line.startsWith("message") || line.startsWith("enum")) { currentName = line.split(' ')[1]; currentContent.push(lines[i]); } else if (line.startsWith("}")) { currentContent.push(lines[i]); // save saveContent(currentName, currentContent, ntMap, cmdid, version); if (cmdid) { var d = new Object(); d["name"] = currentName; d["id"] = parseInt(cmdid.replace(`// CmdID: `, "")); cmdid_gen.push(d) } // clear currentContent = []; currentName = null; cmdid = null; } else if (currentName) { currentContent.push(lines[i]); } } save_json(cmdid_gen, `cmdid.json`); } function saveContent(name, content, ntMap = {}, cmdid = "", version = "") { if (name in ntMap && !/[a-z]/.test(name)) { name = ntMap[name]; } const filename = `${name}.proto`; const nestedMessages = findNestedMessages(content); var isEnum = false for (let i = 0; i < content.length; i++) { let line = content[i]; if (isEnum && line.includes("_")) { content[i] = renameConstant(line); } if (line.startsWith('enum')) { isEnum = true; } } let contentStr = content.join('\n'); for (const [key, value] of Object.entries(ntMap)) { if (/[a-z]/.test(key)) continue; contentStr = contentStr.replace(new RegExp(key, 'g'), value); } let output = `syntax = "proto3";\n\noption java_package = "emu.grasscutter.net.proto";\n// Version: ${version}\n`; if (cmdid) { //console.log(cmdid) output += `${cmdid}\n`; } if (nestedMessages.length > 0) { const importStatements = nestedMessages.map(nm => `import "${nm}.proto";`).join('\n'); output += `\n${importStatements}\n\n`; } else { output += '\n'; } output += contentStr; // update file proto const filePath2 = path.join(outputPath2, filename); const filePath1 = path.join(outputPath1, filename); if (fs.existsSync(filePath2)) { // skip update if file skip or broke if (skipList.includes(filename.replace(".proto", ""))) { console.log('Skipping update file:', filePath2); // TODO: fix it } else { console.log(`File exist add to implemented: ${filePath2}`); fs.writeFileSync(filePath2, output); } } else { //console.log(`File does not exist add to unimplemented: ${filePath1}`); fs.writeFileSync(filePath1, output); } } function checkForNestedMessages(line) { const nestedMessages = new Set(); if (line.startsWith('map<')) { const mapsplit = line.replace('map<', '').replace('>', '').replace(',', '').split(' =')[0].split(' ').slice(0, -1); for (const item of mapsplit) { if (!systemNames.includes(item)) { nestedMessages.add(item); } } } else if (line.startsWith('repeated')) { const part1 = line.split(' ')[1]; if (!systemNames.includes(part1)) { nestedMessages.add(part1); } } else { const part1 = line.split(' ')[0]; if (!systemNames.includes(part1)) { nestedMessages.add(part1); } } return nestedMessages; } function findNestedMessages(content) { const nestedMessages = new Set(); const messageType = content[0].trim().split(' ')[0]; if (messageType === 'enum') return nestedMessages; for (let line of content) { line = line.trim(); if (line.startsWith('message') || line === '}' || line === '') continue; const lineSplit = line.split(' '); if (lineSplit[0] === 'oneof') continue; const nested = checkForNestedMessages(line); nested.forEach(nm => nestedMessages.add(nm)); } return [...nestedMessages]; } // 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); }); } function renameConstant(declaration) { // Split the declaration into the name and value parts const [name, value] = declaration.split(" = "); // Extract the relevant parts before and after the first underscore const [prefix, relevantPart] = name.split("_", 2); // Combine the prefix and the relevant part, and then convert to uppercase and replace camelCase with underscores const newName = `${prefix}_${relevantPart}`.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase(); // Return the renamed declaration return `${newName} = ${value}`; } splitProtoFile(protoFilePath, ntPath, version); console.log("Proto file split successfully!");