uts2puerts
Version:
A tool to convert uts project to puerts project.
172 lines (171 loc) • 10.2 kB
JavaScript
import fs from 'fs-extra';
import path from 'path';
import { RuleParser } from './RuleParser.js';
export class Convertor {
typeMapper = {
'UnityEngine.Type': 'System.Type',
'UnityEngine.UI.UIText': 'UIText',
'UnityEngine.UI.UITextUrl': 'UITextUrl',
'UnityEngine.ShadowCastingMode': 'UnityEngine.Rendering.ShadowCastingMode',
'Game.UIPolygon': 'Game.UIPolygon',
'Game.KeyCode': 'UnityEngine.KeyCode',
'Game.AssetPriority': 'AssetPriority',
'Game.UrlAssetType': 'UrlAssetType',
'UnityEngine.UI.TextAnchor': 'UnityEngine.TextAnchor'
};
ruleCfgs = [];
csTypes = [];
async start(projectRoot, ruleFile) {
if (ruleFile != null) {
const parser = new RuleParser();
this.ruleCfgs = await parser.read(ruleFile);
// console.log(util.inspect(this.ruleCfgs, false, 4))
}
await this.collectUts(projectRoot);
const tsRoot = path.join(projectRoot, 'TsScripts');
await this.processTs(tsRoot);
}
async collectUts(projectRoot) {
const wf = path.join(projectRoot, 'Assets/Scripts/uts/StaticWrap/WrapFiles.cs');
const content = await fs.readFile(wf, 'utf-8');
const r1 = content.matchAll(/_GT\("([^"]+)",typeof\([^"]+\),"([^"]+)"\),?/mg);
for (const r of r1) {
let fullType = undefined;
if (r[2].includes('.'))
fullType = r[2];
this.csTypes.push({ type: `${r[1]}.${r[2]}`, namespace: r[1], fullType });
}
const r2 = content.matchAll(/_GT\("([^"]+)",typeof\(([^"]+)\)\),?/mg);
for (const r of r2) {
let fullType = undefined;
const names = r[2].split('.');
if (names.length > 1)
fullType = r[2];
this.csTypes.push({ type: `${r[1]}.${names[names.length - 1]}`, namespace: r[1], fullType });
}
}
async processTs(tsRoot) {
const files = await fs.readdir(tsRoot);
for (const f of files) {
const file = path.join(tsRoot, f);
const fstat = await fs.stat(file);
if (fstat.isDirectory()) {
await this.processTs(file);
}
else if (path.extname(f) === '.ts') {
const oldContent = await fs.readFile(file, 'utf-8');
let newContent = oldContent;
for (const type of this.csTypes) {
let newType = this.typeMapper[type.type] ?? type.fullType;
if (newType == null) {
if (type.namespace === 'FFTween' || type.namespace === 'Game') {
const narr = type.type.split('.');
newType = narr[narr.length - 1];
}
else {
newType = type.type;
}
}
newType = 'CS.' + newType;
const re = new RegExp('(?<!CS\\.)\\b' + type.type.split('.').join('\\.') + '\\b', 'g');
newContent = newContent.replaceAll(re, newType);
newContent = newContent.replaceAll(newType + '.GetType()', (substring, ...args) => {
return `puerts.$typeof(${substring.substring(0, substring.length - 10)})`;
});
// 数组
newContent = newContent.replaceAll(` as ${newType}[]`, '');
newContent = newContent.replaceAll(new RegExp(':\\s\\b' + newType.split('.').join('\\.') + '\\b\\[\\](?=\\s*=\\s*\\w+\\.GetComponents)', 'g'), '');
}
// 部分衍生类处理
newContent = newContent.replaceAll(/\bFFTween\b\./g, 'CS.');
newContent = newContent.replaceAll(/(?<!CS\.)\bUnityEngine\b\./g, 'CS.UnityEngine.');
newContent = newContent.replaceAll(/(?<!CS\.)\bGame\b\.(?!Vector(2|3))/g, 'CS.Game.');
for (const key in this.typeMapper) {
newContent = newContent.replaceAll('CS.' + key, 'CS.' + this.typeMapper[key]);
}
// newContent = newContent.replaceAll('uts.logError', 'console.error')
// newContent = newContent.replaceAll('uts.logFailure', 'console.error')
// newContent = newContent.replaceAll('uts.logWarning', 'console.warn')
// newContent = newContent.replaceAll('uts.logs', 'console.log')
// newContent = newContent.replaceAll('uts.log', 'console.log')
// newContent = newContent.replaceAll('uts.assert', 'console.assert')
// $ref
newContent = newContent.replaceAll(/(?<=Tools\.(?:GetPosition|GetGameObjectPosition|GetGameObjectLocalPosition|GetAnchoredPosition|GetGameObjectAnchoredPosition|GetRectSize|GetGameObjectRectSize)\([^,]+,\s+?).+(?=\))/mg, (substring, ...args) => {
if (substring.startsWith('puerts.$ref'))
return substring;
return `puerts.$ref(${substring})`;
});
newContent = newContent.replaceAll(/(?<=\.ScreenPointToLocalPointInRectangle\([^,]+,\s+?[^,]+,\s+?[^,]+,\s+?).+(?=\))/mg, (substring, ...args) => {
if (substring.startsWith('puerts.$ref'))
return substring;
return `puerts.$ref(${substring})`;
});
// Equals
newContent = newContent.replaceAll(/([\w.]+)\.Equals\(null\)/g, (substring, ...args) => {
return `CS.UnityEngine.Object.op_Equality(${args[0]}, null)`;
});
// scrollPanel
newContent = newContent.replaceAll(' as CS.UnityEngine.UI.FyScrollRect', ' as unknown as CS.UnityEngine.UI.FyScrollRect');
newContent = newContent.replaceAll(/([\w.]*[Ss]croll\w*)\.onValueChanged = delegate\(this, this.(\w+)\)/g, (substring, ...args) => {
const call = args[1];
if (newContent.search(new RegExp('\\b' + call + '\\b\\s*\\(\\)')) >= 0) {
return `(${args[0]}.onValueChanged as unknown as CS.UnityEngine.Events.UnityEvent).AddListener(()=>this.${call}());`;
}
return `(${args[0]}.onValueChanged as unknown as CS.UnityEngine.Events.UnityEvent).AddListener((change?: CS.UnityEngine.Vector2)=>this.${call}(change));`;
});
newContent = newContent.replaceAll(/([\w.]*[Ss]croll\w*)\.onValueChanged = (\w+)/g, (substring, ...args) => {
const call = args[1];
return `(${args[0]}.onValueChanged as unknown as CS.UnityEngine.Events.UnityEvent).AddListener(${call});`;
// if (newContent.search(new RegExp('\\b' + call + '\\b\\s*\\(\\)')) >= 0) {
// return `(${args[0]}.onValueChanged as unknown as CS.UnityEngine.Events.UnityEvent).AddListener(()=>this.${call}());`
// }
// return `(${args[0]}.onValueChanged as unknown as CS.UnityEngine.Events.UnityEvent).AddListener((change?: CS.UnityEngine.Vector2)=>this.${call}(change));`
});
// slider
newContent = newContent.replaceAll(/([\w.]*[Ss]lider\w*)\.onValueChanged = delegate\(this, this.(\w+)\)/g, (substring, ...args) => {
const call = args[1];
if (newContent.search(new RegExp('\\b' + call + '\\b\\s*\\(\\)')) >= 0) {
return `(${args[0]}.onValueChanged as unknown as CS.UnityEngine.Events.UnityEvent).AddListener(()=>this.${call}());`;
}
return `(${args[0]}.onValueChanged as unknown as CS.UnityEngine.Events.UnityEvent).AddListener((num?: number)=>this.${call}(num));`;
});
// toggle
newContent = newContent.replaceAll(/([\w.]*[Tt]oggle\w*)\.onValueChanged = delegate\(this, this.(\w+)\)/g, (substring, ...args) => {
const call = args[1];
if (newContent.search(new RegExp('\\b' + call + '\\b\\s*\\(\\)')) >= 0) {
return `(${args[0]}.onValueChanged as unknown as CS.UnityEngine.Events.UnityEvent).AddListener(()=>this.${call}());`;
}
return `(${args[0]}.onValueChanged as unknown as CS.UnityEngine.Events.UnityEvent).AddListener((val?: boolean)=>this.${call}(val));`;
});
// array helper
newContent = newContent.replaceAll(/CS\.ArrayHelper\.GetArrayLength\((\w+)\)/g, `$1.Length`);
newContent = newContent.replaceAll(/CS\.ArrayHelper\.GetArrayValue\((\w+),\s*(\w+)\)/g, `$1.get_Item($2)`);
const imports = [];
for (const cfg of this.ruleCfgs) {
if (file.includes(cfg.file)) {
for (const rule of cfg.rules) {
newContent = newContent.replaceAll(rule.searchValue, rule.replaceValue);
}
if (cfg.imports != null) {
for (const ipt of cfg.imports) {
if (!newContent.includes(ipt))
imports.push(ipt);
}
}
}
}
if (imports.length > 0) {
if (newContent[0] === '\ufeff') {
newContent = newContent[0] + imports.join('\n') + '\n' + newContent.substring(1);
}
else {
newContent = imports.join('\n') + '\n' + newContent;
}
}
if (newContent !== oldContent) {
await fs.writeFile(file, newContent, 'utf-8');
}
}
}
}
}