Detect-It-Easy/db/read
2026-05-01 12:15:44 +02:00

948 lines
No EOL
47 KiB
Text
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

// Detect It Easy: DiE-JS framework file
// Don't change anything unless you're sure about what you're doing
// If you want to change the style, please consider yourself unsure!
// Supplemental read functions. Also common wrappers for info output, array processors, comparers, search algos, logging.
// Author: Kaens (TG @kaens)
/* beautify ignore:start */
// don't make the following var or const; they need to be global constants
_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)
CS_ALL = true; CS_BEST = false; // charStat needall
FINT_QUICK = FINT_FAST = FINT_1 = FXSEC1 = true; // findIntersections: find just one for the speed's sake
TOEOF = -1; // use for the size parameter in findSignature
// 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
CP437 = "⌂"+
"ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒ"+
"áíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐"+
"└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀"+
"αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ ";
CP866 = "⌂"+ //DOS Cyrillic
'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ'+
'абвгдежзийклмноп░▒▓│┤╡╢╖╕╣║╗╝╜╛┐'+
'└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀'+
'рстуфхцчшщъыьэюяЁёЄєЇїЎў°∙·√№¤■ ';
CP1251 = "⌂"+
"ЂЃ‚ѓ„…†‡€‰Љ‹ЊЌЋЏђ‘’“”•–—・™љ›њќћџ"+
" ЎўЈ¤Ґ¦§Ё©Є«¬­®Ї°±Ііґµ¶·ё№є»јЅѕї"+
"АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ"+
"абвгдежзийклмнопрстуфхцчшщъыьэюя";
CP1252 = "⌂"+ //aka. Western aka. ISO-8859-1
"€・‚ƒ„…†‡ˆ‰Š‹Œ・Ž・・‘’“”•–—˜™š›œ・žŸ"+
" ¡¢£¤¥¦§¨©ª«¬・®¯°±²³´µ¶·¸¹º»¼½¾¿"+
"ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞß"+
"àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ";
KOI8R = "⌂"+ //aka. RFC 1489, Morse code based
'─│┌┐└┘├┤┬┴┼▀▄█▌▐░▒▓⌠■∙√≈≤≥ ⌡°²·÷'+
'═║╒ё╓╔╕╖╗╘╙╚╛╜╝╞╟╠╡Ё╢╣╤╥╦╧╨╩╪╫╬©'+
'юабцдефгхийклмнопярстужвьызшэщчъ'+
'ЮАБЦДЕФГХИЙКЛМНОПЯРСТУЖВЬЫЗШЭЩЧЪ';
JISX0201 = "⌂"+
"→-‚ƒ„…†‡ˆ‰Š‹Œ↑޳™‘’“”•–—˜™š›œ¢žŸ"+ //decided to mix it with cp1252
"→。「」、・ヲァィゥェォャュョッーアイウエカキクケコサシスセソタ"+
"チツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゙゚"+
"àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ";
CPAmiga = "⫽"+ // alternatively, "▒"
"абвгдежзийклмнопрстуфхцчшщъыьэюя"+ //0x80~0x9F display Cyrillics, just to fill the void
" ¡¢£¤¥¦§¨©ª«¬–®¯°±²³´µ¶·¸¹º»¼½¾¿"+ // Filling the void is important because we still want to see
"ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞß"+ // differences in neighbouring values, for non-reading purposes
"àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ";
CPRISCOS = "⌂"+
"€Ŵŵ◰﯀Ŷŷ<C5B6>⇦⇨⇩⇧…™‰•“”„Œœ†‡fifl"+
" ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿"+
"ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞß"+
"àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ";
CPATASCII = ['▶',
'🖤','├','▊','┘','┤','┐','','╲','◤','▛','◥','▙','▟','▆',' ̄',
'▜','♣','┌','─','┼','◘','▀','▐','┬','┴','▐','└','\n','↑','↓','←','→',
'█','','”','','','','','','','','','','','ー','',
'/','𝟶','𝟷','𝟸','𝟹','𝟺','𝟻','𝟼','𝟽','𝟾','𝟿','','','','','','',
'','𝙰','𝙱','𝙲','𝙳','𝙴','𝙵','𝙶','𝙷','𝙸','𝙹','𝙺','𝙻','𝙼','𝙽','𝙾',
'𝙿','𝚀','𝚁','𝚂','𝚃','𝚄','𝚅','𝚆','𝚇','𝚈','𝚉','【','\\','】','','_',
'♦','𝚊','𝚋','𝚌','𝚍','𝚎','𝚏','𝚐','𝚑','𝚒','𝚓','𝚔','𝚕','𝚖','𝚗','𝚘',
'𝚙','𝚚','𝚛','𝚜','𝚝','𝚞','𝚟','𝚠','𝚡','𝚢','𝚣','♠','-','↰','◁','▷']; //arrow up then NW (🢰) not included for UTF limitations
CPAtariST = "⌂"+
"ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥ßƒ"+
"áíóúñѪº¿⌐¬½¼¡«»ãõØøœŒÀÃÕ¨´†¶©®™"+
"ijIJאבגדהוזחטיכלמנסעפצקרשתןךםףץ§∧∞"+
"αβΓπΣσµτΦΘΩδ∮φ∈∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²³¯";
CPFullCPETshifted = [ //all 256 ch. Commodore PET 2001 version. PETSCII UNSHIFTED and 8032 are different.
// Multiple rather approximate values, until Unicode fonts catch up; ref.Wiki
// It's an array because of unicode shenanigans representing some characters with multiple codepoints
"\x00","\x01","_","⛔️","\x04","⚪","\x06","🔔","⇪","\t","\x0A","↘","⇪","\x0D","↘️","💥",
"\x10","⬇️","↩️","⇱","⌫","🗑️","🗯️","\x17","⇆","↟","?","⎋","🟥","➡️","🟩","🟦",
" ", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?",
"@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O",
"P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "↑", "←",
" ", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "▧",
"\x80", "🟣", "💥", "🚀", "🕶️", "[F1]", "[F3]", "[F5]", "[F7]", "[F2]", "[F4]", "[F6]", "[F8]", "↵", "⇪", "🕶️",
"⬛️", "⬆️", "↪️", "🗑", "🗯", "🟫", "🌸","🧪" , "⚫️", "🟢", "🔷", "🔳", "🟪", "⬅️", "🟨", "🧪",
"\xA0", "▌", "▄", "▔", "▁", "▏", "░", "▕", "▄", "▒", "▕", "├", "▗", "└", "┐", "▂",
"┌", "┴", "┬", "┤", "▎", "▍", "▕", "▔", "▔", "▃", "✓", "▖", "▝", "┘", "▘", "▚",
"─", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
"p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "┼", "▏", "│", "▓", "█",
"\xA0", "▌", "▄", "▔", "▁", "▏", "░", "▕", "▄", "▒", "▕", "├", "▗", "└", "┐", "▂",
"┌", "┴", "┬", "┤", "▎", "▍", "▕", "▔", "▔", "▃", "✓", "▖", "▝", "┘", "▘", "▚"];
CPSpeccy = ['©', //too SPECIAL with all the tokens-for-characters, gotta use an array
' ',' ▀','▀ ','▀▀',' ▄',' █','▀▄','▄█', '▄ ','▄▀','█ ','█▀','▄▄','▄█','█▄','██', //80~F
'𝘼','𝘽','𝘾','𝘿','𝙀','𝙁','𝙂','𝙃','𝙄','𝙅','𝙆','𝙇','𝙈','𝙉','𝙊','𝙋', //90~F
'𝙌','𝙍','𝙎','𝚉𝚇¹²⁸','⏯︎','𝚁𝙽𝙳','𝙸𝙽𝙺𝙴𝚈$','π', '𝙵𝙽 ','𝙿𝙾𝙸𝙽𝚃 ','𝚂𝙲𝚁𝙴𝙴𝙽$ ','𝙰𝚃𝚃𝚁 ','𝙰𝚃 ','𝚃𝙰𝙱 ','𝚅𝙰𝙻$ ','𝙲𝙾𝙳𝙴' , //A0~F
'𝚅𝙰𝙻 ','𝙻𝙴𝙽 ','𝚂𝙸𝙽 ','𝙲𝙾𝚂 ','𝚃𝙰𝙽 ','𝙰𝚂𝙽 ','𝙰𝙲𝚂 ','𝙰𝚃𝙽 ', '𝙻𝙽 ','𝙴𝚇𝙿 ','𝙸𝙽𝚃 ','𝚂𝚀𝚁 ','𝚂𝙶𝙽 ','𝙰𝙱𝚂 ','𝙿𝙴𝙴𝙺 ','𝙸𝙽 ', //B0~F
'𝚄𝚂𝚁 ','𝚂𝚃𝚁$ ','𝙲𝙷𝚁$ ','𝙽𝙾𝚃 ','𝙱𝙸𝙽 ','𝙾𝚁 ','𝙰𝙽𝙳 ','≤', '≥','≠','𝙻𝙸𝙽𝙴 ','𝚃𝙷𝙴𝙽 ','𝚃𝙾 ','𝚂𝚃𝙴𝙿 ','𝙳𝙴𝙵 𝙵𝙽 ','𝙲𝙰𝚃 ', //C0~F
'𝙵𝙾𝚁𝙼𝙰𝚃 ','𝙼𝙾𝚅𝙴 ','𝙴𝚁𝙰𝚂𝙴 ','𝙾𝙿𝙴𝙽 # ','𝙲𝙻𝙾𝚂𝙴 # ','𝙼𝙴𝚁𝙶𝙴 ','𝚅𝙴𝚁𝙸𝙵𝚈 ', '𝙱𝙴𝙴𝙿 ','𝙲𝙸𝚁𝙲𝙻𝙴 ','𝙸𝙽𝙺 ','𝙿𝙰𝙿𝙴𝚁 ','𝙵𝙻𝙰𝚂𝙷 ','𝙱𝚁𝙸𝙶𝙷𝚃 ','𝙸𝙽𝚅𝙴𝚁𝚂𝙴 ','𝙾𝚅𝙴𝚁 ','𝙾𝚄𝚃 ', //D0~F
'𝙻𝙿𝚁𝙸𝙽𝚃 ','𝙻𝙻𝙸𝚂𝚃 ','𝚂𝚃𝙾𝙿 ','𝚁𝙴𝙰𝙳 ','𝙳𝙰𝚃𝙰 ','𝚁𝙴𝚂𝚃𝙾𝚁𝙴 ','𝙽𝙴𝚆 ', '𝙱𝙾𝚁𝙳𝙴𝚁 ','𝙲𝙾𝙽𝚃𝙸𝙽𝚄𝙴 ','𝙳𝙸𝙼 ','𝚁𝙴𝙼 ','𝙵𝙾𝚁 ','𝙶𝙾 𝚃𝙾 ','𝙶𝙾 𝚂𝚄𝙱 ','𝙸𝙽𝙿𝚄𝚃 ','𝙻𝙾𝙰𝙳 ', //E0~F
'𝙻𝙸𝚂𝚃 ','𝙻𝙴𝚃 ','𝙿𝙰𝚄𝚂𝙴 ','𝙽𝙴𝚇𝚃 ','𝙿𝙾𝙺𝙴 ','𝙿𝚁𝙸𝙽𝚃 ','𝙿𝙻𝙾𝚃 ', '𝚁𝚄𝙽 ','𝚂𝙰𝚅𝙴 ','𝚁𝙰𝙽𝙳𝙾𝙼𝙸𝚉𝙴 ','𝙸𝙵 ','𝙲𝙻𝚂','𝙳𝚁𝙰𝚆 ','𝙲𝙻𝙴𝙰𝚁 ','𝚁𝙴𝚃𝚄𝚁𝙽', '𝙲𝙾𝙿𝚈']; //F0~F
Chars0to1F = "・☺☻♥♦♣♠•◘○◙♂♀♪♫☼▶◀↕‼¶§▬↨↑↓→←∟↔▲▼"; //#0 is a small dot from Japanese; could've used ␀ but readability
Chars0to1FLF = "・☺☻♥♦♣♠•◘○\x0A♂♀♪♫☼▶◀↕‼¶§▬↨↑↓→←∟↔▲▼";
Chars0to1FCRLF = "・☺☻♥♦♣♠•◘○\x0A♂♀\x0D♫☼▶◀↕‼¶§▬↨↑↓→←∟↔▲▼";
Chars0to1FSpeccy = "\x00\x01\x02\x03\x04\x05📝///⬅➡⬇⬆⌫\x0A№\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"; //not mixing...
Chars0to1FATASCII = "♥├◨┘┤┐/╲◢▗◣▝▘ ̄▂▖♣┌─┼•▄▎┬┴▌└␛↑↓←→";
Chars0to1FATASCII2 = "áùÑÉçôòì£ïüäöúóöÜâûîéèñêȧàȦ␛↑↓←→";
Chars0to1FATASCII_PL = "ŹąźćŚėöÖ£üߣłŃÓ√ĘśäÜĆĄŻÄż␛↑↓←→";
Chars0to1FAtariX = "áùÑÉçôòì£ïüäÖúóöÜâûîéèñêȧàȦË↑↓←→";
Chars0to1FPETSCII = "\x00\x01_⛔\x04⚪\x06🔔⇪\t\x0A↘⇪\x0D↘💥\x10↓🔲⇱⌫🗑🗯\x17⇆↟?⎋🟥→🟩🟦"; //took the ctrl codes that meant something for each char
/**
* Decode a 1-byte-per-character encoding from a byte array using the 129-byte-long table given,
* as well as a table to display the first 32 characters.
* @param {number[]} ansi - an array of uint8 (returned by readBytes).
* @param {string[]} dectbl - a decoding table[0x81], String or Array; just make a const here in db/read for that
* @param {boolean} [zstop = true] - whether to stop reading on 0 (ASCIIZ behaviour)
* @param {string[]} [tbl01F = Chars0to1FCRLF] - which table to use for the first 32 characters
* @returns {String} a string value usable with js, or an empty line if the decoding table wasn't found.
* @example
* 𝑓([7, 0x7F, 0, 0x32], true, Chars0to1FSpeccy, CPSpeccy]) === "📝︎©" ("2" will be lost)
* @example
* 𝑓("\x07\x7F\x00\x32", false, Chars0to1FSpeccy, CPSpeccy]) === "📝︎© 2"
*/
function decEncoding(ansi, dectbl, zstop, tbl01F) {
if(typeof dectbl === 'undefined') return '';
if(typeof zstop === 'undefined') zstop = true;
if(dectbl.length > 129) { // full 256-byte tables :) Nothing fancy, no control bytes.
var s = ""; for(var i=0; i < ansi.length; i++) { if(!ansi[i] && zstop) break; else s += dectbl[ansi[i]] } return s
}
if(typeof tbl01F === 'undefined')
if(dectbl == CPSpeccy) tbl01F = Chars0to1FSpeccy;
else if(dectbl == CPATASCII) tbl01F = Chars0to1FATASCII;
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 if(dectbl == CPFullCPETshifted) s += ' '; else s += '`'; break;
case 0x7B: if(dectbl == CPATASCII)
if(tbl01F == Chars0to1FATASCII2) s += 'Ä';
else if(tbl01F == Chars0to1FATASCII_PL) s += ' '; //the Poles didn't need the spades without the other 3 :D
else s += '♠';
else s += '{'; break;
case 0x7D: if(dectbl == CPATASCII) s += '↖'; else s += '}'; break; //arrow N-NW (🢰) not included for UTF limitations
case 0x7E: if(dectbl == JISX0201) s += '‾'; else if(dectbl == CPATASCII) 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;
}
/**
* Decode a 1-byte encoding from file using the 129-byte-long table given, as well as a table to display
* the first 32 characters. Analogous to decEncoding but reads the file directly.
* @param {number} ofs - the offset to start from.
* @param {number} len - the amount of bytes to read.
* @param {string[]} dectbl - a decoding table[0x81[, String or Array; just make a const here in db/read for that
* @param {boolean} [zstop = true] - whether to stop reading on 0 (ASCIIZ behaviour)
* @param {array} [tbl01F = Chars0to1FCRLF] - which table to use for the first 32 characters
* @returns {string} a string value usable with js, or an empty line if the decoding table wasn't found.
*/
function decAnsi(ofs, len, dectbl, zstop, tbl01F) {
return decEncoding(X.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 {boolean}
* @example
* 𝑓(20, 10, 40) === true; 𝑓(20,10,20) === true
*/
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 {boolean}
* @example
* 𝑓(20.1, 10.0, 40.0) === true; 𝑓(20.1, 10.0, 20.0) === false
*/
function isInside(a, mina, maxa) {
return mina < a && a < maxa;
}
/**
* isWithin but for a list of ranges. Useful for heuristic byte-has-a-possible-value checks
* @param {Array} a - Can contain either values like [min,max] or singular values to compare (strictly) against
* @returns {boolean}
*/
function isWithinRanges(a, rr) {
if (isNaN(a) || !Array.isArray(rr)) return;
var i = 0, found = false;
for (; i < rr.length; i++) {
if (Array.isArray(rr[i])) {
if (!rr[i].length) return;
if (rr[i].length > 1) { if (rr[i][0] <= a && a <= rr[i][1]) { found = true; break } }
else if (a === rr[i][0]) { found = true; break }
}
else if (a === rr[i]) { found = true; break}
}
return found
}
/**
* Derive a string hexadecimal value, zero-padded.
* @param {number} a - the numerical value.
* @param {number} [padz=2] - how many characters to zero-pad to.
* @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";
}
/**
* Derive a string binary value, zero-padded.
* @param {number} a - the numerical value.
* @param {number} [padz=4] - how many characters to zero-pad to.
* @returns {string} The bin value, characters '0' or '1', ending with "b".
*/
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";
}
/**
* Derive a string octal value, zero-padded.
* @param {number} a - the numerical value.
* @param {number} [padz=3] - how many characters to zero-pad to.
* @returns {string} The octal value, characters 0~7, ending with "o".
*/
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 {number} ofs - the offset to start from.
* @returns {Array} [length,value] - if length (in physical bytes) = 0, the value had a problem.
* @example
* //For file containing '81 80 01 81 83 7F':
* 𝑓(0) === 0x4001; 𝑓(3) === 0x41FF
**/
function readVarUInt(ofs) {
if(ofs < 0 || ofs >= File.getSize()) return [0,0];
var t = 0, wb = 1, r = 1, o = ofs;
var b = X.U8(o++); t = (t << 7) | (b&0x7F);
var b_ = b; while(b_) { b_ >>= 1; wb++ }
for(; r < 16 && (b&0x80); r++) { b = X.U8(o++); t = (t << 7) | (b&0x7F); }
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];
}
function readFloat80(ofs, e) {
if (e != _BE) e = _LE;
// Normalize into little-endian order
var b = [];
if (e == _LE) {
for (var i = 0; i < 10; i++) b[i] = X.U8(ofs + i);
} else {
for (var i = 0; i < 10; i++) b[9 - i] = X.U8(ofs + i);
}
// Mantissa: 8 bytes, little-endian
var mantissa = 0;
for (var i = 0; i < 8; i++) {
mantissa += b[i] * Math.pow(256, i);
}
// Exponent+sign: 2 bytes, little-endian
var expWord = b[8] + (b[9] << 8);
var sign = (expWord & 0x8000) ? -1 : 1;
var exponent = expWord & 0x7FFF;
if (exponent === 0 && mantissa === 0) return 0.0;
if (exponent === 0x7FFF) {
if (mantissa === 0) return sign * Infinity;
return NaN;
}
// Bias 16383
var eVal = exponent - 16383;
// Integer bit is explicit (bit 63 of mantissa)
var intBit = Math.floor(mantissa / Math.pow(2, 63));
var frac = (mantissa % Math.pow(2, 63)) / Math.pow(2, 63);
return sign * (intBit + frac) * Math.pow(2, eVal);
}
/**
* This object facilitates reading a file as a sequence of bits
* @constructor {number} [nOffset = 0] - initialise: provide the file offset
* @param {number} nBits - bits to read, autolimits to 32 (so read little by little!)
* @returns {number} read value as integer, -1 if EoF reached
* @example
* var bits = new BitReader(10); // First create an instance with the file object
* var value = bits.read(5); // Then call the readBits method with the number of bits you want
* bits.init(10); // Or put the reader towards a different place
* p = bits.offset; // Receive the current bit-file offset
* bits.seek(10); // Set the bit-file's offset, in bytes, without changing state
* bits.bseek(14); // Set the bit-file's offset in bits
* bits.consume(2); // Skip some bytes without changing state
**/
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
// 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 }
// 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:
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); }
}
/**
* Count set bits in an integer.
* @param {number} n
*/
function bitCount(n) {
var c = 0; n = !!n; while(n) { if(n&1) c++; n >>= 1 } return c;
}
/**
* Check a file slice for being all one of a lineup of specified characters.
* @param {number} ofs - the offset to start from.
* @param {number} len - the amount of bytes to check.
* @param {Array | number} [bl = 0x00] - the list of possible uint8 codes, or just one uint8.
* @returns {number} - 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 in the file:
* 𝑓(6, 4, [0,1]) === 8; 𝑓(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 || typeof bl === 'string') bl = [bl]; else bl = [0];
len = Math.min(len, X.Sz()-ofs);
// and now test the slice
for(i = 0; i < len; i++) if(!bl.includes(X.U8(ofs+i))) break;
return i < len? ofs+i : -1;
}
//A subcase of firstNotOf for whether a slice is all zeroes.
function isAllZeroes(ofs, len) { return firstNotOf(ofs, len) < 0 }
/**
* 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() unless the whole string fits the limit.
* 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.
* @example
* // Full string fits the limits, no need to search for a good cut-off place, and it's trimmed for pretty:
* 𝑓(" 12345 7890, 34 678. 1 3'56789", 35, 15) === "12345 7890, 34 678. 1 3'56789"
* @example
* // The length is a full match, but it might be trimmed right along that length, so a good cut-off place is found:
* 𝑓(" 12345 7890, 34 678. 1 3'56789", 30, 15) === " 12345 7890, 34 678. 1 3…"
* @example
* // The length is a full match and last 5 characters don't have spaces, so it's treated as trimmed but it's not cut:
* 𝑓(" 12345 7890, 34 678. 1 3'56789", 30, 25) === " 12345 7890, 34 678. 1 3'56789…"
* @example
* // Just another example of a well-trimmed line, in which you need a lot less info that it has to offer:
* 𝑓(" 12345 7890, 34 678. 1 3'56789", 16, 10).trim() === "12345 7890, 34…"
*/
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.appendS 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=''] - what to put in front of the output string
* @param {String} [suffix=''] - what to put after the output string
* @param {String} [sep=', '] - what to put between the previous string and this addition
* @example
* //for sOptions === ' ch:2':
* 𝑓('hello world ', 'msg:"', '"', ', '); //sOptions === 'ch:2, msg:"hello world\"'
*/
function sOptionT(a, prefix, suffix, sep) {
if (typeof prefix === 'undefined') prefix = ""; if (typeof suffix === 'undefined') suffix = "";
if (typeof sep === 'undefined') sep = ', ';
if ((""+a).trim() != "") sOptions = sOptions.appendS(prefix+(""+a).trim()+suffix, sep);
}
/**
* sOptions.appendS 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 = ''] - what to put in front of the output string
* @param {string} [suffix = ''] - what to put after the output string
* @param {string} [sep = ', '] - what to put between the previous string and this addition
* @example
* //for sOptions === ' ch:2':
* 𝑓('hello world ', 'msg:"', '"', ', ') === 'ch:2, msg:"hello world\"'
*/
function sOption(a, prefix, suffix) {
if (typeof prefix === 'undefined') prefix = ""; if (typeof suffix === 'undefined') suffix = "";
if (typeof sep === 'undefined') sep = ', ';
if ((""+a) != "") sOptions = sOptions.appendS(prefix+(""+a)+suffix, sep);
}
/**
* 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.
* 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.
* @param {...number} sizes - numerical values
* @example
* // If a file is 100 bytes long:
* outSz(90,100,105) === "90(+10)/100/105(-5!)"
* // The "!)" thus indicates the file is too short compared to the algorithmic estimation.
*/
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] < X.Sz() ? arguments[i]+"(+"+(X.Sz()-arguments[i])+")"
: arguments[i] > X.Sz() ? arguments[i]+"(-"+(arguments[i]-X.Sz())+"!)"
: 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} [base = 10] - 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!
* @example
* 𝑓([ 1, [5, [10,30]], [[23],'test'] ], 2) == "[0001, [0101, [1010, 11110]], [[10111], "test"]]"
* 𝑓([ 1, [5, [10,30]], [[23],'test'] ], 2, 8) == "[00000001, [00000101, [00001010, 00011110]], [[00010111], "test"]]"
* 𝑓([ 1, [5, [10,30]], [[23],'test'] ], 16) == "[01, [05, [0A, 1E]], [[17], "test"]]"
*/
function outArray(a,base,pad) {
if(typeof base !== 'number' || base % 1 !== 0) base = 10;
if(typeof pad !== 'number' || pad % 1 !== 0) //not integer
if(typeof pad === 'undefined') switch(base) {
case 8: pad = 3; break; case 16: pad = 2; break; case 2: pad = 4; break; default: pad = 0
}
if(typeof a === 'number') return a.toString(base).toUpperCase().padStart(pad,'0');
if(typeof a === 'string') return '"'+a+'"';
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 '[ ' and ' ]' for an even more spaced output
}
//_l2r('WELP','tests','A:'+outArray(X.readBytes(0,5),16)+' B:'+outArray([...Array(5)].map((_, i) => X.U8(i)),16))
/**
* A shorthand for the situation where you compare the file suffix to what you'd expect.
* Use as the option to Binary.isHeuristicScan().
* @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 | 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 as sidereal years, a "month" duration is 1/12 of such year.
* @param {number} s - seconds
* @returns {string}
* @example
* 𝑓(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 = mi+"Mil"+r; return r
}
/**
* Examines a sequence and gives a generalised idea of what sort of characters a string is (mostly) made of.
* Parse the result using indexOf.
* 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} [needall=CS_BEST] - 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 treat as "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.
* @example
* 𝑓("-123 456.789") === "allnum"; 𝑓("-123 456.789", CS_ALL) === "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] == 0xFF) { c[3]++; c[6]++ }
else if(s[i] > 0x7F) 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
// May be useful in some ugly cases for quickly pre-processing the file contents which are then detected.
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 unsorted array of pairs of [offset, length], with a minimum-to-report gap as optional parameter.
* Useful for extra checks in file resource formats, without signatures but not sparse, so you can tell whether the resource blocks have no spaces in-between and don't overlap, using this and findIntersections.
* @param {Array of arrays[2]} lst - List to process consisting of ranges
* @param {UInt} [mingap=1] - Minimum gap, default to at least 1 byte between the ranges. Change for aligned data
* @returns {Array of arrays[2]} Sorted list of gap ranges in the same format
* @example
* // We have data defined in 10..20, 25..30, 40..50:
* 𝑓([ [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]-t-mingap >= 0) r.push([t, a[i][0]-t])
return r;
}
/**
* Discovers intersections in an unsorted array of pairs of [offset, length], considering all possible pairs.
* Useful for extra checks in file resource formats, without signatures but not sparse, so you can tell whether the resource blocks don't overlap and have no spaces in-between, using this and findGaps.
* @param {Array of arrays[2]} lst - List to process consisting of ranges defined as [offset,length]
* @param {Boolean} [detectone=false] - 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
* @example
* 𝑓([ [10,20], [15,10], [23,30] ]) = [[15,10], [23,2], [23,7]]
* 𝑓([ [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);
}
/**
* Finds several signatures one after the other, using an array of pairs of [signature, range] and starting from a specified offset. The range to search across is in the 0-th element of aList.
* @param {UInt} nOffset - Where to start searching from (or 0 if bad data).
* @param {UInt} nLength - Maxlength: the same as in findSignature. (It has to be at least the length of the signature.)
* @param {Array of arrays[2]} aList - List to process consisting of pairs of [signature, range].
* @param {UInt} [nStep=1] - In case subsequent searches fail, skip this many bytes before searching for more (this handles alignments too). If 0, stop if not found immediately.
* @returns -1 if not found, offset otherwise.
* @example
* //on your common Windows EXE file:
* 𝑓(0, 0x400, [ ["'MZ'",0], ["'PE'0000",0x200] ])
*/
function findMultiple(nOffset, nLength, aList, nStep) {
var r, lst = aList || [], ofs = ofs0 = nOffset || 0, max = Math.min(X.Sz(), ofs+(nLength||0)), st = nStep || 1;
if(!lst.length) return -1;
for(i = 0; i < lst.length && ofs <= max; i++) {
if(ofs >= max) return -1;
r = X.fSig(ofs, lst[i][1], lst[i][0]);
//_l2r('FMP',ofs,'['+i+'] st:'+st+' max:'+Hex(max)+' r '+lst[i][0]+' ~ '+Hex(lst[i][1])+' = '+Hex(r));
if(r < 0) { if(!st) return -1; ofs0 += st; ofs = ofs0; i = -1; }
else { ofs = r+lst[i][1]; if(!i) ofs0 = r; }
}
return ofs0;
}
// Heuristic considerations for whether the sample name will be interesting enough to an average user.
//
// The optional "ctx" is context, an array of other sample names (or other tokens) known so far.
// For example, if the file's just starting and you're on the fence whether "loop" is good to know or not,
// it'll give you the chance to put it there just in case, but not if the other names are "boring" or "useless".)
// Try not to use it but also feel free to use it! Fun takes priority.
//
// Please add conditions liberally!
function funSampleName(n, ctx) {
var fun = 1, bore = 0; //by default, we're curious about the new line just a little
n = n.toString();
if(n.trim() == '') return false;
if(n.endsWithCI('.wav') || n.endsWithCI('.smp') || n.endsWithCI('.ins') || n.endsWithCI('.iff')) bore++;
if(n.startsWithCI('ST-') || n.startsWithCI('df0:')) bore++;
if(/^\d+$/.test(n)) bore++;
if(n.startsWith('#')) fun += 5;
if(/^\s*(unnamed|dr[u]?m\d*|loop\s*\d*|strings?\s*\d*|bass\s*\d*|guitar\s*\d*|snare\s*\d*|piano)\s*$/i.test(n)
|| /^\s*(trumpet\s*\d*|bells\s*|synth\s*[0-9iv]*|shaker|banjo|lead|syn\d+|\w*\s*hihat|organ\s*\d*)\s*$/i.test(n)
|| /^\s*(voice\s*\d+|crash|cymbal\s*\d*|wonderpad\s*[0-9iv]+|tambourine\s*[0-9iv]*|tamb\d+)\s*$/i.test(n)
|| /^\s*((hard|echo)\s?tom|splash|cymbhit|orchm(in|aj)|orch\s?hit|chimes?|kick|scratch)\s*$/i.test(n)
|| /^\s*((ghost\s?|brass\s?)pad\s*(min|maj|\(sus4\))?|blip|bleep|hhc|hho|clv|bd|sd|pad)\s*$/i.test(n)
|| /^\s*(m(aj|in)or\s*[0-9iv]*|sawsynth|synbrass|hihat\sclosed)\s*$/i.test(n))
bore++;
//_setResult('fsn',n,'fun='+fun+' bore='+bore,'')
return fun > bore;
}
const 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
}
// A simple array1's contents == array2's contents? kinda thing, goes deep
function compareArrays(arr1, arr2) {
var l = Math.min(arr1.length, arr2.length);
if (arr1.length+arr2.length == 0) return true;
if (l == 0 || arr1.length != arr2.length) return false;
for (var i = 0; i < l; ++i) {
if (Array.isArray(arr1[i])) { // an element of arr1 is an Array too?
if (!Array.isArray(arr2[i])) return false;
if (!compareArrays(arr1[i], arr2[i])) return false; // go deeper
}
else if (Array.isArray(arr2[i])) return false;
else if (arr1[i] !== arr2[i]) return false;
}
return true
}
// Simply plops the buffer contents into the log stream with a suitable MIME header
function _logBase64(buf) {
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);
}
// Same but no header and in hex
function _logHex(buf) {
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-')
}
// Same but as text put through CP866 for clarity of eyeballing.
// That is, the zeros are spaces, and the control characters and i18n are mapped to an old DOS encoding that has all the characters.
function _logText(buf) {
_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'? X.readBytes(0,X.Sz(),true): bf), CP866, Chars0to1F));
_log('--->8-')
}
// Debug logger to results.
function _l2r(name, pos, issue) { _setResult('debug', issue, '@'+Hex(pos), name) }
// Returns the current line of the callee (ie. where you call this function from)
function _currentLine() {
const e = new Error();
const stackLine = e.stack.split("\n")[1];
const match = stackLine.match(/:(\d+)?$/);
return match ? parseInt(match[1], 10) : null;
}
// Simply logs the calling function and the current line. All hail the printf debugging!
function _logIt(msg) {
if (typeof msg === "undefined") msg = "";
const callerLine = new Error().stack.split("\n")[1] || "",
fnmatch = callerLine.match(/^(\w+)@/), // extract function name
fnName = fnmatch ? fnmatch[1] : "<anon>",
lineMatch = callerLine.match(/:(\d+)(?!.*:)/),
lineNum = lineMatch ? lineMatch[1] : "?";
_log(fnName + (lineNum == "?" ? "" : ": " + lineNum) + (!msg.length ? "" : ": " + msg));
}
/** A class that'll help profile overly long scripts until Qt offers better tools!
* Counts timings between the timer.next calls, outputs if too many milliseconds passed.
* @example
* var timer = new CheckpointTimer();
* timer.init(100);
* heavycode1();
* timer.next('after heavycode1:');
* heavycode2();
* timer.next('after heavycode2:')
*/
function CheckpointTimer() {
this.last = 0;
this.min = 100; // milliseconds
this.init = function(min) {
this.last = new Date().getTime();
if (typeof min == 'number' && min >= 0) this.min = min;
}
this.next = function(msg) {
var now = new Date().getTime();
var delta = now - this.last;
if (delta >= this.min) _setResult('prof', msg, '', delta + " ms passed. "
+(X.isOverlay()?' overlay':'')+(X.isResource()?' resource':'')+(X.isFilePart()?' file_part':'')+" Hex at [0]: "
+X.getSignature(0,32));
this.last = now;
}
}
/* beautify ignore:end */