'use strict'; const path = require('path'); const os = require('os'); const fs = require('fs'); const cp = require('child_process'); const { ActionSizes, ActionAligns, PlaceObjectFlags, ClipEventFlags, ButtonRecordFlags, ButtonActionFlags, FunctionPreloadFlags } = require('./constants'); // ─── Byte order ─── function endian(n, bytes) { bytes = bytes || 4; let v = 0; for (let i = 0; i < bytes; i++) { const b = n & 0xff; v |= b << (8 * (bytes - i - 1)); n >>>= 8; } return v; } // ─── Flag converters ─── function valuetoflags(v, e) { v = parseInt(v, 10); let v0 = v; const keys = []; for (const k in e) { const ek = e[k]; if (v & ek) { keys.push(k); v0 &= ~ek; } } if (v0) { throw new Error('unknown bit flags ' + v0 + ' in ' + v); } return keys.join('|'); } function flagstovalue(f, e) { if (!f || f === '') return 0; const parts = f.split('|'); let v = 0; for (let i = 0; i < parts.length; i++) { const k = e[parts[i]]; if (k !== undefined) { v |= k; } else { throw new Error('invalid bit flag: ' + parts[i]); } } return v; } // ─── String encoding ─── function encodeXML(s) { const a = new Array(s.length); for (let i = 0; i < s.length; i++) { const c = s[i]; if (c === '\r') a[i] = '\\r'; else if (c === '\n') a[i] = '\\n'; else if (c === '"') a[i] = '\\"'; else a[i] = c; } return a.join(''); } function mkstr(s) { return '"' + encodeXML(s) + '"'; } // ─── Cheerio element creation (works with a cheerio $ instance) ─── function el(tagName, $) { return $('<' + tagName + '>'); } function bad(t, $) { return $('').attr('text', encodeURIComponent(t)); } // ─── Label generation ─── let labelID = 0; function makelabel() { return 'lxnx' + labelID++; } function resetLabels() { labelID = 0; } // ─── Indentation helpers ─── function indentstr(n) { const t = n[0].prev; if (t && t.nodeType === 3) { return t.data; } return ''; } function copyindent(ndest, nsrc) { const indent = nsrc[0].prev; if (indent && indent.nodeType === 3) { const indent2 = ndest[0].prev; if (indent2 && indent2.nodeType === 3) { $(indent2).remove(); } ndest.before(indent.cloneNode()); } } // ─── RGBA helpers ─── function rgba(c) { c = parseInt(c, 10); return [ c & 0xff, (c >> 8) & 0xff, (c >> 16) & 0xff, (c >> 24) & 0xff ].join(','); } function getrgba($v) { return $v.attr('red') + ',' + $v.attr('green') + ',' + $v.attr('blue') + ',' + $v.attr('alpha'); } function cleanrgba($v) { $v.removeAttr('red green blue alpha'); } function getmatrix($v) { return [ $v.attr('rotm00'), $v.attr('rotm01'), $v.attr('rotm10'), $v.attr('rotm11'), $v.attr('tx'), $v.attr('ty') ].join(','); } function cleanmatrix($v) { $v.removeAttr('rotm00 rotm01 rotm10 rotm11 tx ty'); } // ─── Action alignment helpers ─── function alnsize(l, aln) { if (l.is('end')) return 0; const la = parseInt(l.attr('action'), 10); const aa = ActionAligns[la]; let sz = ActionSizes[la]; if (aa && ((aln + 1) % 4)) { sz += 4 - ((aln + 1) % 4); } return sz; } function calcaln(list) { let aln = 0; for (let i = 0; i < list.length; i++) { const l = list.eq(i); const sz = alnsize(l, aln); l.attr('aln', aln); aln += sz; } } function thisaln(l) { return parseInt(l.attr('aln'), 10); } function nextaln(l) { const aln = thisaln(l); return aln + alnsize(l, aln); } // ─── Temporary directory management ─── function createTempDir() { return fs.mkdtempSync(os.tmpdir() + path.sep) + path.sep; } function cleanupTempDir(tmpdir, keepTemp) { if (!keepTemp && tmpdir) { try { fs.rmSync(tmpdir, { recursive: true, force: true }); } catch (e) { // ignore cleanup errors } } } // ─── Run cc3tools converter ─── function runApt2Xml(toolsDir, tmpdir, baseName) { cp.execFileSync(toolsDir + 'apt2xml', [tmpdir + baseName]); } function runXml2Apt(toolsDir, tmpdir, baseName) { cp.execFileSync(toolsDir + 'xml2apt', [tmpdir + baseName]); } // ─── Resolve file paths ─── function resolveFilePaths(aptFilePath) { const aptFullPath = path.resolve(aptFilePath); const filedir = aptFullPath.substring(0, aptFullPath.lastIndexOf(path.sep)) + path.sep; const aptName = aptFullPath.substring(aptFullPath.lastIndexOf(path.sep) + 1); const baseName = aptName.replace(/\.apt$/i, ''); return { filedir, aptName, baseName }; } // ─── Copy input files to temp dir ─── function copyInputToTemp(filedir, tmpdir, baseName, aptName) { // Always copy .apt and .const fs.copyFileSync(filedir + aptName, tmpdir + aptName); try { fs.copyFileSync(filedir + baseName + '.const', tmpdir + baseName + '.const'); } catch (e) { // .const file might not exist } // Also copy .xml (manifest) if it exists let manifestData = null; try { manifestData = fs.readFileSync(filedir + baseName + '.xml', 'utf-8'); } catch (e) { // manifest doesn't exist, that's ok } // Also copy .dat if it exists let datData = null; try { datData = fs.readFileSync(filedir + baseName + '.dat', 'utf-8'); } catch (e) { // .dat doesn't exist } return { manifestData, datData }; } // ─── Copy output files from temp to filedir ─── function copyOutputFromTemp(filedir, tmpdir, baseName, aptName) { // Copy .apt and .const back fs.copyFileSync(tmpdir + aptName, filedir + aptName); try { fs.copyFileSync(tmpdir + baseName + '.const', filedir + baseName + '.const'); } catch (e) { // .const may not have been generated } } // ─── Read raw XML from temp dir ─── function readRawXml(tmpdir, baseName) { return fs.readFileSync(tmpdir + baseName + '.xml', 'utf-8'); } function writeRawXml(tmpdir, baseName, xmlStr) { fs.writeFileSync(tmpdir + baseName + '.xml', xmlStr, 'utf-8'); } // ─── Exports ─── module.exports = { endian, valuetoflags, flagstovalue, encodeXML, mkstr, el, bad, makelabel, resetLabels, indentstr, copyindent, rgba, getrgba, cleanrgba, getmatrix, cleanmatrix, alnsize, calcaln, thisaln, nextaln, createTempDir, cleanupTempDir, runApt2Xml, runXml2Apt, resolveFilePaths, copyInputToTemp, copyOutputFromTemp, readRawXml, writeRawXml };