GC-Proto/proto_split.js

217 lines
6.5 KiB
JavaScript

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;
// console.log(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!");