Files
2026-06-14 01:40:04 +02:00

267 lines
6.3 KiB
JavaScript

'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 $('<bad>').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
};