Detect-It-Easy/db/read
Kaens 7f0444f052 db/read bugfixes
firstNotOf and _logHex fixed
2024-10-25 19:17:26 +02:00

707 lines
No EOL
34 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Supplemental read functions.
// Authors: unknown guy, Kaens (TG @kaens)
// Lots of legacy,
// TODO update the old scripts to use the new functions,
// and get rid of the functions themselves
/* beautify ignore:start */
const _BE = true, _LE = false; //endianness for read_int16+
//little-endian = reversed notation (Intel, ZX Spectrum),
//big-endian = direct notation (TCP/IP, Motorola, Amiga)
//For the BitReader Object, BE is MSB and LE is LSB (intuitively)
const CS_ALL = true, CS_BEST = false; //charStat needall
const TOEOF = -1; //use for the size parameter in findSignature
// ---------- START OF PRE-v3.06 CODE --------------------
/**
* Read a big-endian word.
* @param {UInt} nOffset - The offset in the file.
* @returns {UShort} The word value.
* @alias Binary.readBEWord
*/
File.readBEWord = function(nOffset) { return X.U16(nOffset,_BE) }
/**
* Read a big-endian dword.
* @param {UInt} nOffset - The offset in the file.
* @returns {UInt} The dword value.
* @alias Binary.readBEDword
*/
File.readBEDword = function(nOffset) { return X.U32(nOffset,_BE) }
/**
* Read a word, selecting endianness.
* @param {UInt} nOffset - The offset in the file.
* @param {Bool} bBE - True for big-endian.
* @returns {UShort} The word value.
* @alias Binary.readEWord
*/
File.readEWord = function(nOffset,bBE) { return X.U16(nOffset,bBE) }
/**
* Read a dword, selecting endianness.
* @param {UInt} nOffset - The offset in the file.
* @param {Bool} bBE - True for big-endian.
* @returns {UInt} The dword value.
* @alias Binary.readEDWord
*/
File.readEDword = function(nOffset,bBE) { return X.U16(nOffset,bBE) }
/**
* Read a short (signed 16-bit) value.
* @param {UInt} nOffset - The offset in the file.
* @returns {Short} The short value.
* @alias Binary.readShort
*/
File.readShort = function(nOffset) { return X.I16(nOffset,_LE) }
// -------- END OF PRE-v3.06 CODE
// The encoding tables start with 7F, not 80! 7F is undefined in many charsets but it's good to have something
// The N(on)B(reakable)SP(ace) and S(oft)HY(phen) are kept as actual A0 and AD characters in this file
const CP437 = "⌂"+
"ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒ"+
"áíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐"+
"└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀"+
"αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ ";
const CP866 = "⌂"+ //DOS Cyrillic
'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ'+
'абвгдежзийклмноп░▒▓│┤╡╢╖╕╣║╗╝╜╛┐'+
'└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀'+
'рстуфхцчшщъыьэюяЁёЄєЇїЎў°∙·√№¤■ ';
const CP1251 = "⌂"+
"ЂЃ‚ѓ„…†‡€‰Љ‹ЊЌЋЏђ‘’“”•–—・™љ›њќћџ"+
" ЎўЈ¤Ґ¦§Ё©Є«¬­®Ї°±Ііґµ¶·ё№є»јЅѕї"+
"АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ"+
"абвгдежзийклмнопрстуфхцчшщъыьэюя";
const CP1252 = "⌂"+ //aka. Western aka. ISO-8859-1
"€・‚ƒ„…†‡ˆ‰Š‹Œ・Ž・・‘’“”•–—˜™š›œ・žŸ"+
" ¡¢£¤¥¦§¨©ª«¬・®¯°±²³´µ¶·¸¹º»¼½¾¿"+
"ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞß"+
"àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ";
const KOI8R = "⌂"+ //aka. RFC 1489, Morse code based
'─│┌┐└┘├┤┬┴┼▀▄█▌▐░▒▓⌠■∙√≈≤≥ ⌡°²·÷'+
'═║╒ё╓╔╕╖╗╘╙╚╛╜╝╞╟╠╡Ё╢╣╤╥╦╧╨╩╪╫╬©'+
'юабцдефгхийклмнопярстужвьызшэщчъ'+
'ЮАБЦДЕФГХИЙКЛМНОПЯРСТУЖВЬЫЗШЭЩЧЪ';
const JISX0201 = "⌂"+
"→-‚ƒ„…†‡ˆ‰Š‹Œ↑޳™‘’“”•–—˜™š›œ¢žŸ"+ //decided to mix it with cp1252
"→。「」、・ヲァィゥェォャュョッーアイウエカキクケコサシスセソタ"+
"チツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゙゚"+
"àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ";
const CPAmiga = "⫽"+ // alternatively, "▒""
"абвгдежзийклмнопрстуфхцчшщъыьэюя"+ //0x80~0xA0 display Cyrillics, just to fill the void
" ¡¢£¤¥¦§¨©ª«¬–®¯°±²³´µ¶·¸¹º»¼½¾¿"+
"ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞß"+
"àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ";
const CPRISCOS = "⌂"+
"€Ŵŵ◰﯀Ŷŷ<C5B6>⇦⇨⇩⇧…™‰•“”„Œœ†‡fifl"+
" ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿"+
"ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞß"+
"àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ";
const CPAtariST = "⌂"+
"ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥ßƒ"+
"áíóúñѪº¿⌐¬½¼¡«»ãõØøœŒÀÃÕ¨´†¶©®™"+
"ijIJאבגדהוזחטיכלמנסעפצקרשתןךםףץ§∧∞"+
"αβΓπΣσµτΦΘΩδ∮φ∈∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²³¯";
const CPSpeccy = ['©', //too SPECIAL with all the tokens-for-characters, gotta use lists
' ',' ▀','▀ ','▀▀',' ▄',' █','▀▄','▄█', '▄ ','▄▀','█ ','█▀','▄▄','▄█','█▄','██', //80~F
'𝘼','𝘽','𝘾','𝘿','𝙀','𝙁','𝙂','𝙃','𝙄','𝙅','𝙆','𝙇','𝙈','𝙉','𝙊','𝙋', //90~F
'𝙌','𝙍','𝙎','𝚉𝚇¹²⁸','⏯️','𝚁𝙽𝙳','𝙸𝙽𝙺𝙴𝚈$','π', '𝙵𝙽 ','𝙿𝙾𝙸𝙽𝚃 ','𝚂𝙲𝚁𝙴𝙴𝙽$ ','𝙰𝚃𝚃𝚁 ','𝙰𝚃 ','𝚃𝙰𝙱 ','𝚅𝙰𝙻$ ','𝙲𝙾𝙳𝙴' , //A0~F
'𝚅𝙰𝙻 ','𝙻𝙴𝙽 ','𝚂𝙸𝙽 ','𝙲𝙾𝚂 ','𝚃𝙰𝙽 ','𝙰𝚂𝙽 ','𝙰𝙲𝚂 ','𝙰𝚃𝙽 ', '𝙻𝙽 ','𝙴𝚇𝙿 ','𝙸𝙽𝚃 ','𝚂𝚀𝚁 ','𝚂𝙶𝙽 ','𝙰𝙱𝚂 ','𝙿𝙴𝙴𝙺 ','𝙸𝙽 ', //B0~F
'𝚄𝚂𝚁 ','𝚂𝚃𝚁$ ','𝙲𝙷𝚁$ ','𝙽𝙾𝚃 ','𝙱𝙸𝙽 ','𝙾𝚁 ','𝙰𝙽𝙳 ','≤', '≥','≠','𝙻𝙸𝙽𝙴 ','𝚃𝙷𝙴𝙽 ','𝚃𝙾 ','𝚂𝚃𝙴𝙿 ','𝙳𝙴𝙵 𝙵𝙽 ','𝙲𝙰𝚃 ', //C0~F
'𝙵𝙾𝚁𝙼𝙰𝚃 ','𝙼𝙾𝚅𝙴 ','𝙴𝚁𝙰𝚂𝙴 ','𝙾𝙿𝙴𝙽 # ','𝙲𝙻𝙾𝚂𝙴 # ','𝙼𝙴𝚁𝙶𝙴 ','𝚅𝙴𝚁𝙸𝙵𝚈 ', '𝙱𝙴𝙴𝙿 ','𝙲𝙸𝚁𝙲𝙻𝙴 ','𝙸𝙽𝙺 ','𝙿𝙰𝙿𝙴𝚁 ','𝙵𝙻𝙰𝚂𝙷 ','𝙱𝚁𝙸𝙶𝙷𝚃 ','𝙸𝙽𝚅𝙴𝚁𝚂𝙴 ','𝙾𝚅𝙴𝚁 ','𝙾𝚄𝚃 ', //D0~F
'𝙻𝙿𝚁𝙸𝙽𝚃 ','𝙻𝙻𝙸𝚂𝚃 ','𝚂𝚃𝙾𝙿 ','𝚁𝙴𝙰𝙳 ','𝙳𝙰𝚃𝙰 ','𝚁𝙴𝚂𝚃𝙾𝚁𝙴 ','𝙽𝙴𝚆 ', '𝙱𝙾𝚁𝙳𝙴𝚁 ','𝙲𝙾𝙽𝚃𝙸𝙽𝚄𝙴 ','𝙳𝙸𝙼 ','𝚁𝙴𝙼 ','𝙵𝙾𝚁 ','𝙶𝙾 𝚃𝙾 ','𝙶𝙾 𝚂𝚄𝙱 ','𝙸𝙽𝙿𝚄𝚃 ','𝙻𝙾𝙰𝙳 ', //E0~F
'𝙻𝙸𝚂𝚃 ','𝙻𝙴𝚃 ','𝙿𝙰𝚄𝚂𝙴 ','𝙽𝙴𝚇𝚃 ','𝙿𝙾𝙺𝙴 ','𝙿𝚁𝙸𝙽𝚃 ','𝙿𝙻𝙾𝚃 ', '𝚁𝚄𝙽 ','𝚂𝙰𝚅𝙴 ','𝚁𝙰𝙽𝙳𝙾𝙼𝙸𝚉𝙴 ','𝙸𝙵 ','𝙲𝙻𝚂','𝙳𝚁𝙰𝚆 ','𝙲𝙻𝙴𝙰𝚁 ','𝚁𝙴𝚃𝚄𝚁𝙽', '𝙲𝙾𝙿𝚈']; //F0~F
const Chars0to1F = "・☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼"; //#0 is a small dot from Japanese
const Chars0to1FLF = "・☺☻♥♦♣♠•◘○\x0A♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼";
const Chars0to1FCRLF = "・☺☻♥♦♣♠•◘○\x0A♂♀\x0D♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼";
const Chars0to1FSpeccy = "\0\1\2\3\4\5📝⬅➡⬇⬆⌫\x0A№\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"; //not mixing...
/**
* Decode a 1-byte encoding from a byte array using the 128-byte-long table given,
* as well as a table to display the first 32 characters.
* @param {[uint8]} ansi - an array returned by readBytes.
* @param {String[0x81]} dectbl - a decoding table; just make a const here in db/read for that
* @param {bool} zstop (optional, default=true) - whether to stop reading on 0 (ASCIIZ behaviour)
* @param {Array} tbl01F (optional, default=Chars0to1FCRLF) - which table to use for the first 32 characters
* @returns {String} a string value usable with js.
*/
function decEncoding(ansi, dectbl, zstop, tbl01F) {
if(typeof zstop === 'undefined') zstop = true;
if(typeof tbl01F === 'undefined')
if(dectbl == CPSpeccy) tbl01F = Chars0to1FSpeccy;
else tbl01F = Chars0to1FCRLF;
var s = "", bit8 = 0;
for(var i=0; i < ansi.length; i++) {
if (!ansi[i] && zstop) break;
else if(ansi[i] < 0x80)
switch(ansi[i]) { // 7-bit variation processing
case 0x0E: if(dectbl == JISX0201 || dectbl == KOI8R) bit8 = 0x80;
else s += tbl01F[0xE]; break;
case 0x0F: if(dectbl == JISX0201 || dectbl == KOI8R) bit8 = 0;
else s += tbl01F[0xF]; break;
case 0x5C: if(dectbl == JISX0201) s += "¥"; else s += "\\"; break;
case 0x5E: if(dectbl == CPSpeccy) s += '↑'; else s += '^'; break;
case 0x60: if(dectbl == CPSpeccy) s += '£'; else s += '`'; break;
case 0x7E: if(dectbl == JISX0201) s += "‾"; else s += "~"; break;
case 0x7F:
if(dectbl != JISX0201) s += dectbl[0];
else s += String.fromCharCode(bit8+ansi[i]); break;
default:
if(!bit8 && ansi[i] >= 0 && ansi[i] < 0x20) s += tbl01F[ansi[i]];
else s += String.fromCharCode(bit8+ansi[i]);
}
else s += dectbl[ansi[i]-0x7F];
}
return s;
}
/**
* Read a byte array from file.
* @param {UInt} ofs - the offset to start from.
* @param {Int} len - the amount of bytes to read.
* @param {Bool} zspace (optional, default=false) - replace 0 with 0x20 (space characters).
* @returns {[uint8]} The file slice. If you go beyond EoF, read_uint8 only knows what happens.
*/
function readBytes(ofs, len, zspace) { //for now; feels like this should be a system function
var c, s = []; if(typeof zspace === 'undefined') zspace = false;
for (var i=0; i < len && ofs+i < File.getSize(); i++) {
c = File.read_uint8(ofs+i);
if(zspace && !c) c = 0x20;
s.push(c);
}
return s;
}
/**
* Decode a 1-byte encoding from file using the 128-byte-long table given.
* @param {UInt} ofs - the offset to start from.
* @param {UInt} len - the amount of bytes to read.
* @param {String[0x81]} dectbl - a decoding table; just make a const here in db/read for that
* @param {bool} zstop (optional, default=true) - whether to stop reading on 0 (ASCIIZ behaviour)
* @param {Array} tbl01F (optional, default=Chars0to1FCRLF) - which table to use for the first 32 characters
* @returns {String} a string value usable with js.
*/
function decAnsi(ofs, len, dectbl, zstop, tbl01F) {
return(decEncoding(readBytes(ofs,len), dectbl, zstop, tbl01F))
}
/**
* Checks for whether a value fits the limits using <=, as opposed to isInside.
* @param {Number} a - the value to check.
* @param {Number} mina
* @param {Number} maxa
* @returns {Bool}
*/
function isWithin(a, mina, maxa) {
return mina <= a && a <= maxa
}
/**
* Checks for whether a value fits the limits using <, as opposed to isWithin. Useful for floats.
* @param {Number} a - the value to check.
* @param {Number} mina
* @param {Number} maxa
* @returns {Bool}
*/
function isInside(a, mina, maxa) {
return mina < a && a < maxa
}
/**
* Derive a string hexadecimal value, zero-padded.
* @param {Int} a - the numerical value.
* @param {UInt} padz (optional,default=2) - how many characters to zero-pad.
* @returns {String} The hex value, capital letters A~F, ending with "h".
*/
function Hex(a, padz) {
if(typeof a === 'undefined') return "!Hex("+a+")";
if(typeof padz === 'undefined') padz = 2;
var minus=""; if(a<0) { a = -a; minus = "-" }
var r = a.toString(16).toUpperCase(); var pads="";
if(r.length < padz) pads = Array(1 + padz - r.length).join('0');
return minus+pads+r+"h"
}
function Bin(a, padz) {
if(typeof a === 'undefined') return "!Bin("+a+")";
if(typeof padz === 'undefined') padz = 4;
var minus = ""; if(a < 0) { a = -a; minus = "-" }
var r = a.toString(2); var pads="";
if(r.length < padz) pads = Array(1 + padz - r.length).join('0');
return minus+pads+r+"b"
}
function Oct(a, padz) {
if(typeof a === 'undefined') return "!Oct("+a+")";
if(typeof padz === 'undefined') padz = 3;
var minus = ""; if(a < 0) { a = -a; minus = "-" }
var r = a.toString(8); var pads="";
if(r.length < padz) pads = Array(1 + padz - r.length).join('0');
return minus+pads+r+"o"
}
/**
* Read a variable-length quantity, an unsigned integer like in MIDI files, from the file.
* @param {UInt} ofs - the offset to start from.
* @returns {List} [length,value] - if length (in physical bytes) = 0, the value had a problem.
**/
function readVarUInt(ofs) {
if(ofs < 0 || ofs >= File.getSize()) return [0,0];
var t = 0, wb = 1, r = 1, o = ofs;
var b = File.read_uint8(o++); t = (t << 7) | (b&0x7F);
var b_ = b; while(b_) { b_ >>= 1; wb++ }
while(r < 16 && (b&0x80)) {
b = File.read_uint8(o++); t = (t << 7) | (b&0x7F); r++
}
if(wb > 64) return [0,0xFFFFFFFFFFFFFFFF]; // sizeof(target) in bits. A 64bit value should be enough, right?
else if(b&0x80) return [0,-1]; //EOF
else return [r,t]
}
/**
* This object facilitates reading a file as a sequence of bits
* @init {UInt} [nOffset = 0] - provide the file offset
* @param {UInt} nBits - bits to read, autolimits to 32 (so read little by little!)
* @returns {UInt} read value as integer, -1 if EoF reached
* @example
* First create an instance with the file object: var bits = new BitReader(10);
* Then call the readBits method with the number of bits you want: var value = bits.read(5)
* Or put the reader towards a different place: bits.init(10)
* Receive the current bit-file offset: bits.offset
* Set the bit-file's offset, in bytes, without changing state: bits.seek(10)
* Set the bit-file's offset in bits: bits.bseek(14)
* Skip some bytes without changing state: bits.consume(2)
**/
function BitReader(nOffset, nEndian) {
this.n = 0; // the number of bits in the buffer
this.buf = 0; // the bit buffer
this.offset = nOffset ? nOffset : 0; // the file offset
this.endian = nEndian ? nEndian : _LE; // for different mechanics of bitstreaming; ogg/flac use _BE
// use this to change the pointer, which will reinit the reader, but not the logger
this.init = function(nOffset) { this.ofs = nOffset ? nOffset : 0; this.n = this.buf = 0 }
// the method to read b bits from the file
this.read = function(nBits) {
if(nBits > 64) nBits = 64; if(nBits < 0) return 0;
if(this.endian === _LE) {
while(this.n < nBits) { // while the buffer is not enough
this.buf |= Util.shlu64(File.read_uint8(this.offset++),this.n); // read a byte and append it to the buffer
this.n += 8; // increase the bit number by 8
}
var v = this.buf & (Util.shlu64(1,nBits) - 1); // extract the desired bits from the buffer
this.buf = Util.shru64(this.buf,nBits); // shift the buffer to the right
} else {
while(this.n < nBits) {
this.buf = Util.shlu64(this.buf,8) | File.read_uint8(this.offset++); // shift the buffer to the left and append a byte
this.n += 8;
}
var v = Util.shru64(this.buf,this.n - nBits); // extract the desired bits from the most significant part of the buffer
this.buf &= Util.shru64((Util.shlu64(1,this.n)-1),nBits); // clear the extracted bits from the buffer
}
this.n -= nBits; // decrease the bit number by b
return v; // return the value even if the file is exhausted
}
// Skip some bytes without changing state: bits.consume(2)
this.consume = function(nBytes) { this.offset += nBytes; }
// Set the bit-file's offset, in bytes, without changing state:
this.seek = function(nOfs) { this.offset = nOfs; }
// Set the bit-file's offset in bits:
this.bseek = function(nOfs) { this.offset = nOfs - (nOfs%8); this.buf = this.n = 0; this.read(nOfs%8); }
}
/**
* Check a file slice for being all one of a lineup of specified characters.
* @param {UInt} ofs - the offset to start from.
* @param {UInt} len - the amount of bytes to check.
* @param {[Array(UInt)] | UInt} bl = [0] - the list of possible u_int8 codes, or just one u_int8. 0 by default.
* @returns {UInt} - offset if a byte in the slice doesn't belong to the list. If you go beyond EoF or all good, -1.
* @example
* For '00 01 02 01' at offset 6, firstNotOf (6, 4, [0,1]) === 8, and firstNotOf(6, 4, 1) === 6
*/
function firstNotOf(ofs, len, bl) {
if(ofs+len > X.Sz()) return -1; var c = i = 0;
if(Array.isArray(bl)) {
for(i = 0; i < bl.length; i++) if(typeof bl[i] !== 'number' || bl[i] < 0 || bl[i] % 1 != 0) break;
if(i < bl.length) throw new Error('firstNotOf cannot parse: '+outArray(bl));
}
else if(typeof bl === 'number' && bl > 0 && bl % 1 == 0) bl = [bl]; else bl = [0];
// and now test the slice
for(i = 0; i < len && ofs+i < X.Sz(); i++) if(bl.indexOf(X.U8(ofs+i)) < 0) break;
return i < len? ofs+i : -1
}
function isAllZeroes(ofs, len) { return firstNotOf(ofs, len, 0) < 0 } //a subcase for whether a slice is all zeroes
/**
* If the string was too long and has been read incompletely, adds an ellipsis after the last
* complete word, to avoid cut-off words. If `space characters' are not detected,
* replaces the last character with an ellipsis. Does NOT do the trim().
* Mostly usable for lengthy multiline comments/messages.
* @param {String} a - the original incomplete string.
* @param {Number} trim - the buffer size; if a.length == trim, we decide it was cropped.
* @param {Number} [mintrim = 78] - don't try searching for spaces below this point.
* @returns {String} - the resulting string.
*/
function addEllipsis(a, trim, mintrim) {
if(!trim) trim = 0xA0;
if(!mintrim) mintrim = 78; if(a.length < trim || mintrim > trim) return a.trim();
const spaces = " .,:;!\\/'\"=&\x09\x0D\x0A\x1A\x26。、。,,・";
var i = trim, c = 0, ci = -1;
while(i >= mintrim && c < 2) {
if(spaces.indexOf(a[i]) >= 0) { c++; while(spaces.indexOf(a[i]) >= 0) i--; if(ci < 0) ci = i+1 }
while(spaces.indexOf(a[i]) < 0 && i >= 0) i--
}
if((i < mintrim && c < 2) //we conclude this language doesn't really have that many spaces...
|| !c) //...or none at all in the trimmable slice...
return a.slice(0,trim)+'…';
else //this language has some spaces and we can use the last one to trim
return a.slice(0,Math.max(ci),mintrim)+'…';
}
/**
* sOptions.append a string (optionally prefixed) if the space-trimmed string is not empty.
* @param {variant} a - the string to output (safe to accidentally drop a non-string in)
* @param {String} prefix (optional) - what to put in front of the output string
* @param {String} suffix (optional) - what to put after the output string
*/
function sOptionT(a, prefix, suffix) {
if (typeof prefix === 'undefined') prefix = ""; if (typeof suffix === 'undefined') suffix = "";
if ((""+a).trim() != "") sOptions = sOptions.append(prefix+(""+a).trim()+suffix)
}
/**
* sOptions.append a string (optionally prefixed) if the string is not empty.
* @param {variant} a - the string to output (safe to accidentally drop a non-string in)
* @param {String} prefix (optional) - what to put in front of the output string
* @param {String} suffix (optional) - what to put after the output string
*/
function sOption(a, prefix, suffix) {
if (typeof prefix === 'undefined') prefix = ""; if (typeof suffix === 'undefined') suffix = "";
if ((""+a).trim() != "") sOptions = sOptions.append(prefix+(""+a).trim()+suffix)
}
/**
* A more verbose (but still concise) way of outputting the calculated size(s, derived using different algorithms),
* taking into account and visualising the difference from the actual file size.
* @param {...Number} sizes - numerical values
* For example, if a file is 100 bytes long, outSz(90,100,105) will yield "90(+10)/100/105(-5!)"
* The "!)" thus indicates the file is too short compared to the algorithmic estimation.
* If some of the reported sizes match, the value will only be displayed once.
* It's still a good idea to add "/malformed!short" to the version string — it's visible without isVerbose.
*/
function outSz() { if(!arguments.length || typeof arguments[0] === 'undefined') return "?";
var sizes = [], origs = [];
for(i = 0; i < arguments.length; i++)
if(arguments[i] >= 0) if(!origs.length || origs.indexOf(arguments[i]) < 0) {
origs.push(arguments[i]);
sizes.push(
arguments[i] < File.getSize() ? arguments[i]+"(+"+(File.getSize()-arguments[i])+")"
: arguments[i] > File.getSize() ? arguments[i]+"(-"+(arguments[i]-File.getSize())+"!)"
: arguments[i]
)
} else; else sizes.push("?");
return sizes.join("/")
}
/**
* Converts an array to a better-looking line than the usual flat thing DiE's _log would output.
* @param {Array} a - Array to process consisting of any information including arrays
* @param {Int = 10} base - If an integer value is found, in which base to display it
* @param {Int} zeropad - If an integer value is found, how many zeroes to pad it with (smart by default)
* @returns {String} A beautiful output!
* Ex. 𝑓([ 1, [5, [10,30]], [[23],'test'] ], 2) = "[0001, [0101, [1010, 11110]], [[10111], test]]"
* Ex. 𝑓([ 1, [5, [10,30]], [[23],'test'] ], 16) = "[01, [05, [0A, 1E]], [[17], test]]"
*/
function outArray(a,base,pad) {
if(!Array.isArray(a)) return a;
if(typeof base !== 'number' || base % 1 !== 0) base = 10;
if(typeof pad !== 'number' || pad % 1 !== 0) //not integer
if(typeof pad === 'undefined')
if(base == 8) pad = 3; else if(base == 16) pad = 2; else if(base == 2) pad = 4; else pad = 0;
for(var i=0, s = []; i < a.length; i++)
if(Array.isArray(a[i])) s.push(outArray(a[i],base,pad)); else
if(typeof a[i] === 'number' && a[i] % 1 === 0) //integer
s.push(a[i].toString(base).toUpperCase().padStart(pad,'0'));
else if(typeof a[i] === 'string') s.push('"'+a[i]+'"');
else s.push(a[i]);
return '['+s.join(', ')+']' //put '[ '...' ]' for an even more spaced output
}
/**
* A shorthand for the situation where you compare the file suffix to what you'd expect. Use as the option to isHeuristicScan being true.
* @param {String} a - the expected file suffix, case-insensitive, no heading period unlike Python
* @returns {bool} if a match is reached
*/
function extIs(a) { return File.getFileSuffix().toLowerCase() == a.toLowerCase() }
/**
* slashTag formats a string in a way that's useful when a tag has two versions (for ex. in different languages). It will either show both with "/" in between, or one of them if the other one's an empty string, or an empty string if both are empty.
* @param {String} a - the first of the two
* @param {String} b - the second of the two
* @returns {String}
*/
function slashTag(a, b) {
if(a == b) return a;
else if(a != "" && b == "")
return a;
else if(a == "" && b != "")
return b;
else if(a != "" && b != "")
return a+"/"+b;
else return ""
}
/**
* createOrderlyHuffmanTable is just for detections but it does return the table for further checks. Or it returns false.
* @param {Array} lent - the lengths table
* @param {String} btl - bit table length
* @param {BitReader} br - a BitReader object pointing somewhere at the right position for this. The provided BitReader WILL change state.
* @returns {Array or false}
*/
// createOrderlyHuffmanTable is just for detections but it does return the table for further checks. Or it returns false.
function createOrderlyHuffmanTable(lent, btl, br) {
var md = 32, Md = reall = code = 0; var _t = [], fi = [], li = [], ni = [];
for(i = 0; i < 33; i++) fi[i] = 0xFFFF;
for(i = 0; i < btl; i++) { len = lent[i]; if(len) {
if(len < md) md = len; if(len > Md) Md = len;
if(fi[len] == 0xFFFF) { fi[len] = li[len] = i } else { ni[li[len]] = i; li[len] = i } reall++ } }
if(!Md) return false;
for(d = md; d <= Md; d++) {
if(fi[d] != 0xFFFF) ni[li[d]] = btl;
for(i = fi[d]; i < btl; i = ni[i]) {
//insert HuffmanCode:
var j = 0, le = _t.length;
for(var cb = d; cb >= 0; cb--) {
var cob = (cb && ( ( (code>>(Md-d)) >> (cb-1) ) & 1 ) ) ? 1 : 0;
if(j != le) {
if(!cb || (!_t[j][0] && !_t[j][1])) return false; //[0] is left, [1] is right, [2] is value
if(!_t[j][cob]) _t[j][cob] = j = le; else j = _t[j][cob];
} else {
_t.push([ (cb&&!cob)?le+1:0, (cb&&cob)?le+1:0, cb?0:i ]);
j++; le++
} }
code += 1 << (Md-d) } }
return _t
}
/**
* Outputs time in seconds as short human-readable, with a "h:mm:ss" alternative when < 1 day.
* Millenia, centuries and years a sidereal years, a "month" duration is 1/12 of such year.
* @param {Number} seconds
* @returns {String}
* Ex. 𝑓(123456789) = "31Y10M4w21h33m9s"; 𝑓(1234567) = "2w6h56m7s"; 𝑓(12345) = "3:25:45"
*/
function secondsToTimeStr(s) {
const mul = [/*millenia*/315581497635,/*centuries*/3155814976,/*yrs*/31558150,/*mns*/2629846,/*wks*/604800,86400,3600,60];
var r = "", ss = s%mul[7], mm = Util.div64(s%mul[6],mul[7]), hh = Util.div64(s%mul[5],mul[6]),
dd = Util.div64(s%mul[4],mul[5]), ww = Util.div64(s%mul[3],mul[4]), mn = Util.div64(s%mul[2],mul[3]),
yy = Util.div64(s%mul[1],mul[2]), cc = Util.div64(s%mul[0],mul[1]), mi = Util.div64(s,mul[0]);
if(s < 86400) { r = mm.padStart(2,'0')+":"+ss.padStart(2,'0'); if(hh) r = hh+":"+r; return r }
if(ss) r = ss+"s"+r; if(mm) r = mm+"m"+r; if(hh) r = hh+"h"+r; if(dd) r = dd+"d"+r; if(ww) r = ww+"w"+r;
if(mn) r = mn+"M"+r; if(yy) r = yy+"Y"+r; if(cc) r = cc+"C"+r; if(mi) r = ci+"Mil"+r; return r
}
/**
* Examines a sequence and gives a generalised idea of what sort of characters a string is (mostly) made of.
* Could be useful for validating structured files with human-filled fields.
* @param {String/Number[]} s - your string in question, or a List of charcodes.
* @param {Boolean=false} needall - if true, you have "allascallt allnum" for a line of spaces, else just "allt ".
* @returns {String} - at least one or a combo of these, optional prefix "all" (otherwise "mostly"):
* '?': wrong type; 'empty': 0 length; '00': zeroes; 't ': tabs/spaces; 'ctl': 0-1Fh & 7Fh; 'num': numerical;
* 'asc': 20h-7Eh; 'xsc': zeroes+tabs+♫+FFh+crlf+ascii; 'foreign': 00,t, ,crlf, 80h+; 'any': decision cannot be made.
* Parse the result using indexOf.
* Ex. 𝑓("-123 456.789") = "allnum", 𝑓("-123 456.789",1) = "allnumallascallxscallforeign"
*/
/* beautify preserve:start */
function charStat() {
if(!arguments.length) return "?";
if(typeof arguments[0] === "undefined" || typeof arguments[0] === "number") return "?";
str = arguments[0];
if(arguments.length < 2) needall = false; else needall = !!arguments[1];
if(str == "" || str == []) return "empty";
var i, s = [], c = [ /*[0]00*/0, /*[1]t */0, /*[2]asc*/0, /*[3]xsc*/0, /*[4]num*/0,
/*[5]ctl*/0, /*[6]foreign*/0 ], o = [0,0,0,0,0,0,0,0];
if(typeof str === "string") for(i = 0; i < str.length; i++) s.push(str.charCodeAt(i)); else s = str;
for(i=0;i<s.length;i++) {
if(!s[i]) { c[0]++; c[3]++; c[5]++; c[6]++ }
else if(s[i] == 9) { c[1]++; c[3]++; c[5]++; c[6]++ }
else if(s[i] == 0xA || s[i] == 0xD) { c[3]++; c[5]++; c[6]++ }
else if(s[i] == 0xE) { c[3]++; c[5]++ }
else if(s[i] <= 0x1F || s[i] == 0x7F) c[5]++;
else if(s[i] == 0x20) { c[1]++; c[2]++; c[3]++; c[4]++; c[6]++; }
else if(0x2B <= s[i] && s[i] <= 0x2D || 0x30 <= s[i] && s[i] <= 0x39) { c[2]++; c[3]++; c[4]++; c[6]++ }
else if(s[i] <= 0x7E) { c[2]++; c[3]++ }
else if(s[i] > 0x7F) { c[3]++; c[6]++ }
} for(i = 0; i < c.length; i++) o[i] = Util.div64(c[i]*100,s.length);
r = "";
//_log((typeof str)+" "+s+" {"+c+"} <"+o+">")
if(!needall) {
if(o[0] > 70) { if(o[0] === 100) r += "all"; r += "00" }
else if(o[1] > 70) { if(o[1] === 100) r += "all"; r += "t " }
else if(o[4] > 70) { if(o[4] === 100) r += "all"; r += "num" }
else if(o[2] > 70) { if(o[2] === 100) r += "all"; r += "asc" }
else if(o[3] > 70) { if(o[3] === 100) r += "all"; r += "xsc" }
else if(o[5] > 70) { if(o[5] === 100) r += "all"; r += "ctl" }
else if(o[6] > 70) { if(o[6] === 100) r += "all"; r += "foreign" }
} else {
if(o[0] > 70) { if(o[0] === 100) r += "all"; r += "00" }
if(o[1] > 70) { if(o[1] === 100) r += "all"; r += "t " }
if(o[4] > 70) { if(o[4] === 100) r += "all"; r += "num" }
if(o[2] > 70) { if(o[2] === 100) r += "all"; r += "asc" }
if(o[3] > 70) { if(o[3] === 100) r += "all"; r += "xsc" }
if(o[5] > 70) { if(o[5] === 100) r += "all"; r += "ctl" }
if(o[6] > 70) { if(o[6] === 100) r += "all"; r += "foreign" }
}
if(r == "") return "any"+o; else return r
}
// PATCHING FUNCTIONALITY; promised to become native
var patcheddata = [];
function rpU8(adr) { //read patched data or passthrough, U8
for(var i=0; i < patcheddata.length; i++) if(patcheddata[i][0] == adr) return patcheddata[i][1];
return X.U8(adr)
}
function rpU16be(adr) { //read patched data or passthrough, U16 BE
return (rpU8(adr) << 8) | rpU8(adr+1)
}
function rpU32be(adr) { //read patched data or passthrough, U32 BE
return (rpU8(adr) << 24) | (rpU8(adr+1) << 16) | (rpU8(adr+2) << 8) | rpU8(adr+3)
}
function wpU8(adr,val) { //add to patches, U8
for(var i=0; i < patcheddata.length; i++)
if(patcheddata[i][0] == adr) { patcheddata[i][1] = val; return }
patcheddata.push([adr,val])
}
function wpU16be(adr,val) { //add to patches, U16 BE
wpU8(adr,(val>>8)&0xFF); wpU8(adr+1,val&0xFF)
}
function wpU32be(adr,val) { //add to patches, U32 BE
wpU8(adr,(val>>24)&0xFF); wpU8(adr+1,(val>>16)&0xFF);
wpU8(adr+2,(val>>8)&0xFF); wpU8(adr+3,val&0xFF)
}
function patchLength() { return patcheddata.length }
function patchClear() { patcheddata = [] }
/**
* Discovers gaps in an array of pairs of numbers, with a minimum-to-report gap as optional parameter.
* @param {Array of arrays[2]} lst - List to process consisting of ranges defined as [offset,length]
* @param {UInt = 1} mingap - Minimum gap, default to at least 1 byte between the ranges
* @returns {Array of arrays[2]} Sorted list of gap ranges in the same format
* Ex. 𝑓([ [10,10], [25,5], [40,10] ]) = [[20,5], [30,10]]
*/
function findGaps(lst, mingap) {
var i, r = []; /* tests for input typing follow */ if(!Array.isArray(lst) || lst.length < 2) return r;
for(i = 0; i < lst.length; i++) if(!Array.isArray(lst[i]) || lst[i].length != 2
|| typeof lst[i][0] !== 'number' || typeof lst[i][1] !== 'number') return r;
if(typeof mingap !== 'number') mingap = 1;
function sf(a, b) { if(a[0] != b[0]) return a[0]-b[0]; else return a[1]-b[1] }
var a = lst.sort(sf);
for(i = 1; i < a.length; i++)
if((t=a[i-1][0]+a[i-1][1]) < a[i][0] && a[i][0]-mingap >= 0) r.push([t, a[i][0]-t])
return r
}
/**
* Discovers intersections in an array of pairs of numbers, considering all possible pairs.
* @param {Array of arrays[2]} lst - List to process consisting of ranges defined as [offset,length]
* @param {Boolean = false} detectone - Stop searching after finding even one intersection
* @returns {Array of arrays[2]} Sorted list of intersection ranges in the same format; if length > 0, intersection present
* Ex. 𝑓([ [10,20], [15,10], [23,30] ]) = [[15,10], [23,2], [23,7]]
* Ex. 𝑓([ [10,20], [15,10], [23,30] ], true) = [[15,10]]
*/
function findIntersections(lst,detectone) {
var i, t, r = []; /* tests for input typing follow */ if(!Array.isArray(lst) || lst.length < 2) return r;
for(i=0; i < lst.length; i++) if(!Array.isArray(lst[i]) || lst[i].length != 2
|| typeof lst[i][0] !== 'number' || typeof lst[i][1] !== 'number') return r;
function sf(a, b) { if(a[0] != b[0]) return a[0]-b[0]; else return a[1]-b[1] }
var a = lst.sort(sf); var found = false;
for(i=1; i < a.length && !found; i++)
for(j=0; j < i && !found; j++)
if((t=a[j][0]+a[j][1]) > a[i][0]) { if(detectone) found = true;
//_log(' item#'+j+' ['+lst[j][0]+' -> '+lst[j][1]+'] intersects with item#'+i+' ['+lst[i][0]+' -> '+lst[i][1]+']')
r.push([a[i][0], a[i][0]+a[i][1] <= t ? a[i][1] : t-a[i][0]])
}
return r.sort(sf);
}
b64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
function toBase64(buf) {
var r = '', i = 0, indexOfLastCompleteTriple = buf.length - (buf.length%3);
for (i = 0; i < indexOfLastCompleteTriple; i += 3) {
r += b64Chars[buf[i] >> 2];
r += b64Chars[((buf[i] & 3) << 4) | ((buf[i+1] & 0xF0) >> 4)];
r += b64Chars[((buf[i+1] & 0xF) << 2) | ((buf[i+2] & 0xC0) >> 6)];
r += b64Chars[buf[i+2] & 0x3F]
}
if (i < buf.length) {
var i1 = buf[i], i2 = (i+1 < buf.length) ? buf[i+1] : 0;
r += b64Chars[i1 >> 2];
r += b64Chars[((i1 & 0x03) << 4) | ((i2 & 0xF0) >> 4)];
if (i+1 < buf.length) {
r += b64Chars[((i2 & 0x0F) << 2)];
}
else r += '=';
r += '=';
}
return r
}
/**
* If it's an Amiga hunk file, proceeds to process hunks
* @returns {Int} -1 in case of errors, otherwise expected size
*/
function calcAmigaFileSize() {
if(!X.c("000003F3")) return -1; //is it an Amiga hunk file?
var p = 4, x = sz = i = reslibs = 0, sizes = [], load = true;
//library strings:
while(p < X.Sz()) {
x = X.U32(4,_BE); p += 4; if(!reslibs && x) load = false; if(x) reslibs++; else break; p += 4*x
}
var hunks = X.U32(p+8,_BE) - X.U32(p+4,_BE) + 1; p += 12;
sOption(hunks+' hunks')
//hunk table:
for(i=0; i < hunks && p < X.Sz(); i++,p+=4) {
var t = X.U32(p,_BE), add = (t>>30) == 3? 4: 0; t &= 0x3FFFFFFF; t <<= 2; t += add;
_log('@'+Hex(p)+' hunk#'+i+' = '+Hex(t));
sizes.push(t); sz += t
}
//traverse hunks:
sz += p; //if(sz >= X.Sz()) return -1;
return sz
}
function _logBase64(buf) { //simply plops the buffer contents into log stream with a suitable MIME header
var fn = File.getFileBaseName()+'.'+File.getFileCompleteSuffix();
_log('MIME-Version: 1.0\nContent-Type: application/octet-stream; name="'+fn+'.dec"\n'+
'Content-Transfer-Encoding: base64\nContent-Disposition: attachment; filename="'+fn+'.dec"\n');
_log(buf)
}
function _logHex(buf) { //same but no header and in hex
var o = ''; for(i=0; i < buf.length; i++) {
if(!(i % 16)) o += (i? ' |\n': '')+i.toString(16).padStart(6,'0')+' |'; if(!(i % 8)) o += ' ';
o += ' '+buf[i].toString(16).padStart(2,'0');
}
_log('-8<---'); _log(o); _log('--->8-')
}
function _logText(buf) { //same but as text put through CP866, or all file with zeroes as spaces and control characters as emoji
_log('-8<---['+(typeof buf)+' '+(buf.length)+' bytes]---');
if(typeof buf === 'object') {
var bf = buf, i = 0; for(; i < buf.length; i++) if(bf[i] == 0) bf[i] = 0x20
}
_log(decEncoding((typeof buf === 'undefined'? readBytes(0,X.Sz(),true): bf), CP866, Chars0to1F));
_log('--->8-')
}
function _l2r(name,pos,issue) { _setResult('debug',name,'@'+Hex(pos),issue); }
/* beautify ignore:end */