UNPKG

uts2puerts

Version:

A tool to convert uts project to puerts project.

172 lines (171 loc) 10.2 kB
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'); } } } } }