// 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~0x9F display Cyrillics, just to fill the void " ¡¢£¤¥¦§¨©ª«¬–®¯°±²³´µ¶·¸¹º»¼½¾¿"+ "ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞß"+ "àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"; const CPRISCOS = "⌂"+ "€Ŵŵ◰﯀Ŷŷ�⇦⇨⇩⇧…™‰•‘’‹›“”„–—−Œœ†‡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 of uint8 (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; } /** * 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(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 {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 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 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'? X.readBytes(0,X.Sz(),true): bf), CP866, Chars0to1F)); _log('--->8-') } function _l2r(name,pos,issue) { _setResult('debug',name,'@'+Hex(pos),issue); } /* beautify ignore:end */