import fs from 'fs'; import path from 'path'; type FileRoomData = { [filePath: string]: number[] }; type FileRoomJsonData = { [uuid: string]: string }; const ROOM_COUNT = 7; const BASE64_KEYS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; const HEXCHAR = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'] const BASE64_VALUES = new Array(123); for (let i = 0; i < 123; ++i) BASE64_VALUES[i] = 64; for (let i = 0; i < 64; ++i) BASE64_VALUES[BASE64_KEYS.charCodeAt(i)] = i; const _t = ['', '', '', '']; const uuidTemplate = _t.concat(_t, '-', _t, '-', _t, '-', _t, '-', _t, _t, _t); const indices = uuidTemplate.map(function (value: string, index: number) { return value === '-' ? NaN : index; }).filter(isFinite); class SettingJSDecode { private packedAssets: CCSettingsPackedAssets = null; constructor(buildDir: string) { const settingDir = path.join(buildDir, 'src'); const files = fs.readdirSync(settingDir); for (const file of files) { if (!file.startsWith('settings')) continue; const settingsContent = fs.readFileSync(path.join(settingDir, file)).toString(); const ccSetting = this.evalSettings(settingsContent); this.packedAssets = ccSetting?.packedAssets; break; } } private evalSettings(content: string): CCSettings { content = content.replace('window.', 'settingsContainer.'); const settingsContainer: { _CCSettings?: CCSettings } = {}; eval(content); return settingsContainer._CCSettings; } private toUUID(base64: string) { let baselen = base64.length; if (baselen < 22) { return base64; } uuidTemplate[0] = base64[0]; uuidTemplate[1] = base64[1]; for (let i = 2, j = 2; i < 22; i += 2) { let lhs = BASE64_VALUES[base64.charCodeAt(i)]; let rhs = BASE64_VALUES[base64.charCodeAt(i + 1)]; uuidTemplate[indices[j++]] = HEXCHAR[lhs >> 2]; uuidTemplate[indices[j++]] = HEXCHAR[((lhs & 3) << 2) | rhs >> 4]; uuidTemplate[indices[j++]] = HEXCHAR[rhs & 0xF]; } return uuidTemplate.join(''); } public pickPackageFiles(uuids: string[]): string[] { if (!this.packedAssets) return uuids; for (const fileName in this.packedAssets) { const packageUUIDs = this.packedAssets[fileName]; let match = true; const removedUUIDs: string[] = []; for (const packageKey of packageUUIDs) { const packageUUID = this.toUUID(packageKey); const index = uuids.indexOf(packageUUID); match = index >= 0; if (match) { removedUUIDs.push(...uuids.splice(index, 1)); } else { uuids.push(...removedUUIDs); match = false; break; } } match && uuids.push(fileName); } return uuids; } } export class JsonPackage { private readonly assetsDir: string; private readonly buildDir: string; private readonly resourcesDir: string; private readonly outputDir: string; private readonly uuidMap: { db: string, uuid: string, dest: string }[]; private readonly fileRoomData: FileRoomData; private readonly settingJSDecode: SettingJSDecode; constructor(projectDir: string, buildDir: string) { this.assetsDir = path.join(projectDir, 'assets'); this.buildDir = buildDir; this.resourcesDir = path.join(buildDir, 'res'); this.outputDir = path.join(buildDir, 'res_package'); this.settingJSDecode = new SettingJSDecode(buildDir); const buildJson = fs.readFileSync(path.join(projectDir, 'build', 'build.json')); const buildData = JSON.parse(buildJson.toString()); this.uuidMap = buildData.uuid; this.fileRoomData = this.decodeRoomData(); } //通过鱼属性表读取相关资源所属的房间 private decodeRoomData(): FileRoomData { const filesRoomData: FileRoomData = {}; const gameConfigPath = path.join(this.assetsDir, 'resources', 'conf', 'allConf.json'); const gameConfigContent = fs.readFileSync(gameConfigPath); const gameConfig = JSON.parse(gameConfigContent.toString()); const fishAttribute = this.dataArrayToObjectArray(gameConfig['FishAttribute']); for (const fish of fishAttribute) { //房间数据 const roomData: number[] = []; const fishName: string = fish.name; if (!fishName) continue; for (let roomType = 1; roomType <= ROOM_COUNT; roomType++) { roomData.push(fish[`room${roomType}`]); } //处理鱼资源、鱼技能资源 this.updateRoomData(filesRoomData, `resources/anim/clip/fish/${fishName}`, roomData);//鱼动画 this.updateRoomData(filesRoomData, `res/anim/texture/fish/${fishName}`, roomData);//鱼动画纹理 this.updateRoomData(filesRoomData, `resources/texture/hall/scenePage/${fishName}_rk`, roomData);//Loading图标 this.updateRoomData(filesRoomData, `resources/texture/hall/scenePage/${fishName}_rkdes`, roomData);//Loading文字 //处理背景资源 const backgroundString: string | null = fish.Background if (backgroundString) { const background = backgroundString.split('|'); switch (background[0]) { case '1': this.updateRoomData(filesRoomData, `resources/texture/mainScene/bg/${background[1]}.png`, roomData);//鱼背景静态图 break; case '2': } } //处理音效资源 const audioString: string | null = fish.bossBgm } return filesRoomData; } private getUUIDsInPath(filePath: string, uuids?: string[]): string[] { uuids = uuids || []; this.uuidMap.forEach(({ db, uuid }) => { db = db.substring(12);//"db://assets/".length if (!db.startsWith(filePath)) return; if (uuids.indexOf(uuid) >= 0) return; uuids.push(uuid); }); return uuids; } private dataArrayToObjectArray(data: any[][]): any[] { const title: string[] = data.shift()!; const objArray: { [title: string]: string }[] = []; for (const dataItem of data) { const dataObj: { [title: string]: string } = {} for (let i = 0, length = title.length; i < length; i++) { const key: string = title[i]; if (!key) break; dataObj[key] = dataItem[i]; } objArray.push(dataObj); } return objArray; } private updateRoomData(container: FileRoomData, key: string, data: number[]): void { const exitsData = container[key]; if (!exitsData) { container[key] = data; } else { const result: number[] = []; for (let i = 0, length = Math.max(exitsData.length, data.length); i < length; i++) { result.push((exitsData[i] || data[i]) ? 1 : 0); } container[key] = result; } } private mkdir(dir: string): void { if (!fs.existsSync(dir)) { let parentDir = path.dirname(dir); this.mkdir(parentDir); fs.mkdirSync(dir); } } private pickPackageFile(dir: string, names: string[], cache?: FileRoomJsonData): FileRoomJsonData { cache = cache || {}; const files = fs.readdirSync(dir); for (let i = 0; i < files.length; i++) { const subFileName = files[i]; const subFilePath = path.join(dir, subFileName) const stat = fs.statSync(subFilePath); if (stat.isDirectory()) { this.pickPackageFile(subFilePath, names, cache); } else { if (path.extname(subFileName) !== '.json') continue; const fileName = subFileName.substring(0, subFileName.indexOf('.')); const nameIndex = names.indexOf(fileName); if (nameIndex < 0) { continue; } names.splice(nameIndex, 1); const content = fs.readFileSync(subFilePath).toString(); cache[fileName] = content; } } return cache; } public package(): void { this.mkdir(this.outputDir); const result: { uuid: string, name: string }[] = []; for (let roomType = 1; roomType <= ROOM_COUNT; roomType++) { const configName = `config_${roomType}.json`; const uuids: string[] = []; for (const filePath in this.fileRoomData) { const fishRoomData = this.fileRoomData[filePath]; if (!fishRoomData[roomType - 1]) continue; this.getUUIDsInPath(filePath, uuids); } this.getUUIDsInPath(`resources/spine/mainScene/nGun`, uuids); this.getUUIDsInPath(`resources/spine/mainScene/yuwang`, uuids); this.getUUIDsInPath(`resources/spine/mainScene/superWeapon`, uuids); this.getUUIDsInPath(`resources/prefab/mainScene/preload`, uuids); this.getUUIDsInPath(`resources/spine/mainScene/preload`, uuids); this.getUUIDsInPath(`resources/prefab/mainScene/init`, uuids); this.getUUIDsInPath(`resources/prefab/mainScene/skill`, uuids); this.settingJSDecode.pickPackageFiles(uuids); const contents: FileRoomJsonData = this.pickPackageFile(this.resourcesDir, uuids); // console.log(`RoomUUIDs:\n${uuids.join(', ')}`); // console.log(`RoomErrorUUIDs:\n${uuids.join(', ')}`); for (const uuid in contents) result.push({ uuid, name: configName }); fs.writeFileSync(path.join(this.outputDir, configName), JSON.stringify(contents)); } fs.writeFileSync(path.join(this.outputDir, 'result.json'), JSON.stringify(result)); fs.copyFileSync(path.join(__dirname, 'prj.js'), path.join(this.buildDir, 'src', 'prj.js')); const indexPath = path.join(this.buildDir, 'index.html'); let htmlContent = fs.readFileSync(indexPath).toString(); const bodyEndIndex = htmlContent.indexOf(''); htmlContent = `${htmlContent.substring(0, bodyEndIndex)}${htmlContent.substring(bodyEndIndex)}` fs.writeFileSync(indexPath, htmlContent); } } const args = process.argv; const projectDir = args[2]; const buildDir = args[3] new JsonPackage(projectDir, buildDir).package();