unity-find-fault
Version:
A tool to find fault in unity project.
160 lines • 6.99 kB
JavaScript
import fg from "fast-glob";
import fs from "fs-extra";
import path from "path";
import { Project } from "ts-morph";
import { readGB2312, writeGB2312 } from "./vendor.js";
import { toolchain } from "../toolchain.js";
export class ProtocolStriper {
async removeUseless() {
const project = new Project({
tsConfigFilePath: path.join(toolchain.opts.projectRoot, 'TsScripts/tsconfig.json')
});
const outputFile = `tmp/${toolchain.opts.projectName}.protocolUsed.txt`;
await this.findUsed(project, outputFile);
const outContent = await fs.readFile(outputFile, 'utf-8');
const lines = outContent.split(/\r?\n/);
const usedItfs = [];
for (const line of lines) {
if (!line)
continue;
usedItfs.push(line);
}
await this.markItfDefinitions(usedItfs);
}
async findUsed(project, outFile) {
if (!toolchain.opts.svrRoot) {
console.error('no svr root');
process.exit(1);
}
// Protocol.d.ts
const usedItfs = [];
const protocolFile = path.join(toolchain.opts.projectRoot, 'TsScripts/System/protocol/Protocol.d.ts');
const gameConfigSrc = project.getSourceFile(protocolFile);
const module = gameConfigSrc.getModuleOrThrow('Protocol');
const interfaces = module.getInterfaces();
for (const itf of interfaces) {
let used = false;
if (this.isNodeReferedOutside(itf)) {
used = true;
}
else {
// 检查字段是否被使用
const properties = itf.getProperties();
for (const property of properties) {
// if (property.getName() == 'm_iSkillPriority' && itf.getName() == 'SkillConfigM') {
// console.log('this comes!');
// }
if (this.isNodeReferedOutside(property)) {
used = true;
break;
}
}
}
if (used) {
usedItfs.push(itf.getName());
}
}
// ProtocolUtil.ts
// 部分协议没有显式使用Request
const protocolUtilFile = path.join(toolchain.opts.projectRoot, 'TsScripts/System/protocol/ProtocolUtil.ts');
const protoclUtilContent = await fs.readFile(protocolUtilFile, 'utf-8');
const mchs = protoclUtilContent.matchAll(/(?<=SendMsgUtil\.get).+(?=\(\))/g);
for (const mch of mchs) {
const request = mch[0];
if (!usedItfs.includes(request)) {
usedItfs.push(request);
}
}
// 部分协议response也只是监听了msgid,并没有显式使用到msgbody
await this.findByAddNetListener(usedItfs);
await fs.writeFile(outFile, usedItfs.join('\n') + '\n', 'utf-8');
}
async findByAddNetListener(out) {
const tss = await fg('TsScripts/**/*.ts', { cwd: toolchain.opts.projectRoot });
const usedMsgids = [];
for (const ts of tss) {
const tsFile = path.join(toolchain.opts.projectRoot, ts);
const content = await fs.readFile(tsFile, 'utf-8');
const mchs = content.matchAll(/(?<=addNetListener\(Macros.).+?(?=,)/g);
for (const mch of mchs) {
usedMsgids.push(mch[0]);
}
}
// 获取对应的response
const csXml = path.join(toolchain.opts.svrRoot, 'CS.xml');
const csContent = await fs.readFile(csXml, 'utf-8');
const mchs = csContent.matchAll(/<entry\s+name=\s*"\w+"\s+type\s*=\s*"(\w+)"\s+value\s*=\s*"(\w+)"/g);
for (const mch of mchs) {
const rspntf = mch[1];
const msgid = mch[2];
if (usedMsgids.includes(msgid) && !out.includes(rspntf)) {
out.push(rspntf);
}
}
}
isNodeReferedOutside(node) {
const excludes = ['DecodeBase.ts', 'DecodeUtil.ts', 'EncodeUtil.ts', 'SendMsgUtil.ts'];
const refSymbols = node.findReferences();
let used = false;
for (const refSymbol of refSymbols) {
for (const ref of refSymbol.getReferences()) {
if (ref.isDefinition())
continue;
// console.log(`${refSymbol.getDefinition().getName()} refered in ${ref.getSourceFile().getFilePath()}`);
const bn = ref.getSourceFile().getBaseName();
if (!excludes.includes(ref.getSourceFile().getBaseName())) {
used = true;
break;
}
}
if (used)
break;
}
return used;
}
async markItfDefinitions(usedItfs) {
const xmlFiles = ['Common.xml', 'CS.xml', 'ProtoType.xml'];
for (const x of xmlFiles) {
const xmlFile = path.join(toolchain.opts.svrRoot, x);
const xmlContent = await readGB2312(xmlFile);
const lines = xmlContent.split(/\r?\n/);
let modified = false;
for (let i = 0, len = lines.length; i < len; i++) {
let line = lines[i];
const originalLine = line;
const mch = line.match(/^\s*<struct\s+name\s*=\s*"(\w+)".* (?:convertOption\s*=\s*"(\w+)")?/);
if (mch != null) {
// struct定义开始
const theItf = mch[1];
// 只标记一级字段
// if (!theItf.endsWith('Notify') && !theItf.endsWith('Response') && !theItf.endsWith('Request')) continue;
const convertOption = mch[2]?.split(',');
if (usedItfs.includes(theItf)) {
// 添加标记
if (!convertOption?.includes('clientUsed')) {
if (line.includes('convertOption')) {
line = originalLine.replace(/convertOption\s*=\s*"/, `convertOption="clientUsed,`);
}
else {
line = originalLine.replace('>', ' convertOption="clientUsed">');
}
lines[i] = line;
modified = true;
}
}
else {
// 去除标记
if (convertOption?.includes('clientUsed')) {
line = originalLine.replace('clientUsed', '').replace('convertOption=""', '');
lines[i] = line;
modified = true;
}
}
}
}
if (modified)
await writeGB2312(xmlFile, lines.join('\n'));
}
}
}
//# sourceMappingURL=ProtocolStriper.js.map