first commit
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
'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
|
||||
};
|
||||
Reference in New Issue
Block a user