UNPKG

libmount

Version:

Standalone FAT12, FAT16, FAT32, VFAT implementation in JavaScript

1,681 lines (1,671 loc) 79.5 kB
/** * iconv-tiny v0.6.0 * (c) 2025-present vip.delete * @license MIT **/ // src/const.mjs var MAX_BYTE = 255; var MAX_WORD = 65535; var MAX_DOUBLE_WORD = 4294967295; var SZ = 512; var BS_JUMP_BOOT_LENGTH = 3; var BS_OEM_NAME_LENGTH = 8; var DIR_NAME_LENGTH = 11; var BS_FIL_SYS_TYPE_LENGTH = 8; var BPB_FAT32_RESERVED_LENGTH = 12; var BS_BOOT_CODE_LENGTH = 448; var BS_BOOT_CODE_FAT32_LENGTH = 420; var BS_SIGNATURE_WORD = 43605; var FSI_LEAD_SIG = 1096897106; var FSI_STRUC_SIG = 1631679090; var FSI_TRAIL_SIG = 2857697280; var FSI_NEXT_FREE_OFFSET = 492; var LFN_MAX_LEN = 255; var LFN_NAME1_LENGTH = 10; var LFN_NAME2_LENGTH = 12; var LFN_NAME3_LENGTH = 4; var LFN_ALL_NAMES_LENGTH = LFN_NAME1_LENGTH + LFN_NAME2_LENGTH + LFN_NAME3_LENGTH; var LFN_BUFFER_LEN = 520; var DIR_ENTRY_SIZE_BITS = 5; var DIR_ENTRY_SIZE = 1 << DIR_ENTRY_SIZE_BITS; var FAT_THRESHOLD = 4087; var WINDOWS_MIN_COUNT_OF_CLUSTERS_FAT12 = 0; var WINDOWS_MAX_COUNT_OF_CLUSTERS_FAT12 = 4085 - 1 - 6; var WINDOWS_MIN_COUNT_OF_CLUSTERS_FAT16 = FAT_THRESHOLD; var WINDOWS_MAX_COUNT_OF_CLUSTERS_FAT16 = 65525 - 1 - 6; var WINDOWS_MIN_COUNT_OF_CLUSTERS_FAT32 = 1; var WINDOWS_MAX_COUNT_OF_CLUSTERS_FAT32 = 268435445 - 1 - 6; var COUNT_OF_CLUSTERS_COMPATIBILITY = 16; var MIN_CLUS_NUM = 2; var FREE_CLUS = 0; var DIR_ENTRY_ATTR_VOLUME_ID = 8; var DIR_ENTRY_ATTR_DIRECTORY = 16; var DIR_ENTRY_ATTR_LFN = 15; var DIR_ENTRY_FLAG_LAST = 0; var DIR_ENTRY_FLAG_DELETED = 229; var DIR_ENTRY_FLAG_E5 = 5; // src/types.mjs var ValidationError = class extends Error { /** * @param {string} [message] */ constructor(message) { super(message); this.name = "ValidationError"; } }; // src/utils.mjs var ASSERTS_ENABLED = typeof USE_ASSERTS === "boolean" ? USE_ASSERTS : true; var assert = (expression, msg) => { if (ASSERTS_ENABLED) { if (!expression) { throw new Error(msg ?? "AssertionError"); } } }; var str2bytes = (str) => new Uint8Array([...str].map((it) => it.charCodeAt(0))); var SHORT_NAME_SPECIAL_CHARACTERS = str2bytes(" $%'-_@~`!(){}^#&"); var LONG_NAME_SPECIAL_CHARACTERS = str2bytes(".+,;=[]"); var isCapitalLetter = (code) => code > "A".charCodeAt(0) - 1 && code < "Z".charCodeAt(0) + 1; var isSmallLetter = (code) => code > "a".charCodeAt(0) - 1 && code < "z".charCodeAt(0) + 1; var isDigit = (code) => code > "0".charCodeAt(0) - 1 && code < "9".charCodeAt(0) + 1; var isUnicode = (code) => code > 255; var isExtended = (code) => code > 127; var getChkSum = (sfn) => { assert(sfn.length === DIR_NAME_LENGTH); let sum = sfn[0]; for (let i = 1, len = sfn.length; i < len; i++) { sum = (sum << 7 | sum >> 1) + sfn[i] & 255; } return sum; }; var isShortNameValidCode = (code) => { assert(code >= 0 && code <= MAX_BYTE); return isExtended(code) || isCapitalLetter(code) || isDigit(code) || SHORT_NAME_SPECIAL_CHARACTERS.includes(code); }; var isLongNameValidCode = (wcCode) => { assert(wcCode >= 0 && wcCode <= MAX_WORD); return isUnicode(wcCode) || isSmallLetter(wcCode) || isShortNameValidCode(wcCode) || LONG_NAME_SPECIAL_CHARACTERS.includes(wcCode); }; var normalizeLongName = (longName) => { let i = 0; while (i < longName.length && longName.charCodeAt(i) === " ".charCodeAt(0)) { i++; } let j = longName.length - 1; let ch; while (j >= i && ((ch = longName.charCodeAt(j)) === " ".charCodeAt(0) || ch === ".".charCodeAt(0))) { j--; } return longName.slice(i, j + 1); }; var split = (path) => { const names = []; const parts = path.split(/[/\\]/u); for (let i = 0; i < parts.length; i++) { const part = parts[i].trim(); if (part !== "" && part !== ".") { if (part === "..") { if (names.length) { names.length--; } } else { const name = normalizeLongName(part); if (name !== "") { names.push(name); } } } } return names; }; var sfnToStr = (sfn, cp) => { assert(sfn.length === DIR_NAME_LENGTH); const str = cp.decode(sfn); const basename = str.slice(0, 8).trimEnd(); const ext = str.slice(8, 11).trimEnd(); return ext === "" ? basename : basename + "." + ext; }; var appendToSFN = (sfn, offset, len, cp, str) => { if (str.startsWith(" ") || str.endsWith(" ")) { return false; } let i = 0; const buf = cp.encode(str); if (buf.length > len) { return false; } while (i < buf.length) { const code = buf[i]; if (!isShortNameValidCode(code)) { return false; } sfn[offset + i] = code; i++; } while (i < len) { sfn[offset + i] = " ".charCodeAt(0); i++; } return true; }; var strToSfn = (str, codepage) => { const i = str.lastIndexOf("."); const basename = i < 0 ? str : str.substring(0, i); const ext = i < 0 ? "" : str.substring(i + 1); if (basename === "" && ext === "") { return null; } const sfn = new Uint8Array(DIR_NAME_LENGTH); if (!appendToSFN(sfn, 0, 8, codepage, basename)) { return null; } if (!appendToSFN(sfn, 8, 3, codepage, ext)) { return null; } return sfn; }; var LFN_BUFFER = new Uint8Array(LFN_BUFFER_LEN); var strToLfn = (str) => { assert(str.length && str.length <= LFN_MAX_LEN); const lfn = LFN_BUFFER; let i = 0; let j = 0; while (i < str.length) { let ch = str.charCodeAt(i++); if (!isLongNameValidCode(ch)) { return null; } lfn[j++] = ch; ch >>= 8; lfn[j++] = ch; } if (j % LFN_ALL_NAMES_LENGTH !== 0) { lfn[j++] = 0; lfn[j++] = 0; while (j % LFN_ALL_NAMES_LENGTH !== 0) { lfn[j++] = MAX_BYTE; lfn[j++] = MAX_BYTE; } } assert(j <= lfn.length); return lfn.subarray(0, j); }; var LFN_DECODE_BUFFER = new Uint16Array(LFN_BUFFER_LEN / 2); var lfnToStr = (chain) => { assert(chain.length > 0); const buf = LFN_DECODE_BUFFER; let len = 0; let k = chain.length - 1; let ch; do { const item = chain[k--]; const Name1 = item.Name1; let i = 0; while (i < LFN_NAME1_LENGTH && (ch = Name1[i++] | Name1[i++] << 8)) { buf[len++] = ch; } if (ch) { const Name2 = item.Name2; i = 0; while (i < LFN_NAME2_LENGTH && (ch = Name2[i++] | Name2[i++] << 8)) { buf[len++] = ch; } if (ch) { const Name3 = item.Name3; i = 0; while (i < LFN_NAME3_LENGTH && (ch = Name3[i++] | Name3[i++] << 8)) { buf[len++] = ch; } } } } while (ch && k >= 0); return String.fromCharCode(...buf.subarray(0, len)); }; var toValidShortNameCharacters = (str, max, cp) => { let ret = ""; let i = 0; let count = 0; while (i < str.length && count < max) { const ch = str.charAt(i); if (ret !== "" || ch !== " ") { const buf = cp.encode(ch); const code = buf[0]; if (code !== "?".charCodeAt(0)) { if (isShortNameValidCode(code)) { ret += ch; count += buf.length; } else { ret += "_"; count++; } } } i++; } return ret; }; var strToHash = (str) => { let sum = 0; for (let i = 0; i < str.length; i++) { sum = sum + str.charCodeAt(i) & 65535; } return sum.toString(16).padStart(4, "0").toUpperCase(); }; var strToTildeName = (str, cp, fileNames) => { str = str.toUpperCase(); const i = str.lastIndexOf("."); const basename = i < 0 ? str : str.substring(0, i); const ext = i < 0 ? "" : str.substring(i + 1); const basename6 = toValidShortNameCharacters(basename, 6, cp); const ext3 = toValidShortNameCharacters(ext, 3, cp); assert(!basename6.startsWith(" ") && basename6.length <= 6); assert(!ext3.startsWith(" ") && ext3.length <= 3); const prefix = basename6.length > 2 ? basename6 : basename6 + strToHash(str); const postfix = ext3 === "" ? "" : "." + ext3; let num = 1; let numLen = 1; while (numLen <= 7) { const filename = prefix.substring(0, 7 - numLen) + "~" + num + postfix; if (!fileNames.has(filename)) { return filename; } num++; numLen = num.toString().length; } return ""; }; var parseDate = (date) => { if (!date) { return null; } const dayOfMonth = date & 31; const monthOfYear = date >> 5 & 15; const yearSince1980 = date >> 9 & 127; return new Date(1980 + yearSince1980, Math.max(0, monthOfYear - 1), Math.max(1, dayOfMonth)); }; var parseDateTime = (date, time, timeTenth) => { if (!date) { return null; } const dayOfMonth = date & 31; const monthOfYear = date >> 5 & 15; const yearSince1980 = date >> 9 & 127; const millis = timeTenth % 100 * 10; const seconds = Math.floor(timeTenth / 100) + ((time & 31) << 1); const minutes = time >> 5 & 63; const hours = time >> 11 & 31; return new Date(1980 + yearSince1980, Math.max(0, monthOfYear - 1), Math.max(1, dayOfMonth), hours, minutes, seconds, millis); }; var toDate = (date) => { if (!date) { return 0; } const yearSince1980 = date.getFullYear() - 1980; const monthOfYear = date.getMonth() + 1; const dayOfMonth = date.getDate(); return yearSince1980 << 9 | monthOfYear << 5 | dayOfMonth; }; var toTime = (date) => { if (!date) { return 0; } const hours = date.getHours(); const minutes = date.getMinutes(); const seconds = date.getSeconds(); return hours << 11 | minutes << 5 | seconds >> 1; }; var toTimeTenth = (date) => { if (!date) { return 0; } const seconds = date.getSeconds(); const millis = date.getMilliseconds(); return Math.floor((seconds % 2 * 1e3 + Number(millis)) / 10); }; var strToUint8Array = (cp, len, str) => { const data = new Uint8Array(len).fill(" ".charCodeAt(0)); if (str) { data.set(cp.encode(str.substring(0, len)).subarray(0, len)); } return data; }; // src/dao.mjs var validate = (expression) => { if (!expression) { throw new ValidationError(); } }; var loadCHS = (io) => { const b1 = io.readByte(); const b2 = io.readByte(); const b3 = io.readByte(); return { Cylinder: b2 >> 6 << 8 | b3, Head: b1, Sector: b2 & 63 }; }; var writeCHS = (io, chs) => { const b1 = chs.Head; const b2 = chs.Cylinder >> 8 << 6 | chs.Sector & 63; const b3 = chs.Cylinder & 255; io.writeByte(b1).writeByte(b2).writeByte(b3); }; var loadPartitionTable = (io) => { validate(io.len() >= 512); io.seek(510 - 16 * 4); const table = []; for (let i = 0; i < 4; i++) { let partition = { BootIndicator: io.readByte(), Starting: loadCHS(io), SystemID: io.readByte(), Ending: loadCHS(io), RelativeSectors: io.readDoubleWord(), TotalSectors: io.readDoubleWord() }; try { validate(partition.SystemID > 0); validate([0, 128].includes(partition.BootIndicator)); validate(partition.TotalSectors > 0); } catch { partition = null; } if (partition) { table.push(partition); } } return table; }; var writePartitionTable = (io, partitions) => { io.seek(510 - 4 * 16); let i = 0; while (i < 4 && i < partitions.length) { const e = partitions[i]; io.writeByte(e.BootIndicator); writeCHS(io, e.Starting); io.writeByte(e.SystemID); writeCHS(io, e.Ending); io.writeDoubleWord(e.RelativeSectors); io.writeDoubleWord(e.TotalSectors); i++; } io.writeBytes(0, (4 - i) * 16); io.writeWord(BS_SIGNATURE_WORD); }; var isBpbFat32 = (bpb) => bpb.FATSz16 === 0; var loadBootSector = (io) => { validate(io.len() >= 512); io.seek(0); const jmpBoot = io.readUint8Array(BS_JUMP_BOOT_LENGTH); const OEMName = io.readUint8Array(BS_OEM_NAME_LENGTH); const bpb = { BytsPerSec: io.readWord(), SecPerClus: io.readByte(), RsvdSecCnt: io.readWord(), NumFATs: io.readByte(), RootEntCnt: io.readWord(), TotSec16: io.readWord(), Media: io.readByte(), FATSz16: io.readWord(), SecPerTrk: io.readWord(), NumHeads: io.readWord(), HiddSec: io.readDoubleWord(), TotSec32: io.readDoubleWord(), // FAT32 specific fields FATSz32: -1, ExtFlags: -1, FSVer: -1, RootClus: -1, FSInfo: -1, BkBootSec: -1 }; validate([512, 1024, 2048, 4096].includes(bpb.BytsPerSec)); validate([1, 2, 4, 8, 16, 32, 64, 128].includes(bpb.SecPerClus)); validate(bpb.RsvdSecCnt > 0); validate(bpb.NumFATs > 0); validate((bpb.RootEntCnt << DIR_ENTRY_SIZE_BITS) % bpb.BytsPerSec === 0); validate(bpb.RootEntCnt === 0 || bpb.FATSz16 > 0); validate(bpb.TotSec16 > 0 || bpb.TotSec32 > MAX_WORD); if (isBpbFat32(bpb)) { bpb.FATSz32 = io.readDoubleWord(); bpb.ExtFlags = io.readWord(); bpb.FSVer = io.readWord(); bpb.RootClus = io.readDoubleWord(); bpb.FSInfo = io.readWord(); bpb.BkBootSec = io.readWord(); io.skip(BPB_FAT32_RESERVED_LENGTH); validate(bpb.RootClus >= MIN_CLUS_NUM); validate(bpb.BkBootSec >= 0); validate(bpb.FSInfo > 0); } const bs = { jmpBoot, OEMName, bpb, DrvNum: io.readByte(), Reserved1: io.readByte(), BootSig: io.readByte(), VolID: io.readDoubleWord(), VolLab: io.readUint8Array(DIR_NAME_LENGTH), FilSysType: io.readUint8Array(BS_FIL_SYS_TYPE_LENGTH), BootCode: io.readUint8Array(510 - io.pos()) }; return bs; }; var writeBootSector = (io, bs) => { io.seek(0); const { bpb } = bs; const bpbFat32 = isBpbFat32(bpb); assert(bs.jmpBoot.length === BS_JUMP_BOOT_LENGTH); assert(bs.OEMName.length === BS_OEM_NAME_LENGTH); assert(bs.VolLab.length === DIR_NAME_LENGTH); assert(bs.FilSysType.length === BS_FIL_SYS_TYPE_LENGTH); assert(bs.BootCode.length === (bpbFat32 ? BS_BOOT_CODE_FAT32_LENGTH : BS_BOOT_CODE_LENGTH)); io.writeUint8Array(bs.jmpBoot).writeUint8Array(bs.OEMName).writeWord(bpb.BytsPerSec).writeByte(bpb.SecPerClus).writeWord(bpb.RsvdSecCnt).writeByte(bpb.NumFATs).writeWord(bpb.RootEntCnt).writeWord(bpb.TotSec16).writeByte(bpb.Media).writeWord(bpb.FATSz16).writeWord(bpb.SecPerTrk).writeWord(bpb.NumHeads).writeDoubleWord(bpb.HiddSec).writeDoubleWord(bpb.TotSec32); if (bpbFat32) { io.writeDoubleWord(bpb.FATSz32).writeWord(bpb.ExtFlags).writeWord(bpb.FSVer).writeDoubleWord(bpb.RootClus).writeWord(bpb.FSInfo).writeWord(bpb.BkBootSec).skip(BPB_FAT32_RESERVED_LENGTH); } io.writeByte(bs.DrvNum).writeByte(bs.Reserved1).writeByte(bs.BootSig).writeDoubleWord(bs.VolID).writeUint8Array(bs.VolLab).writeUint8Array(bs.FilSysType).writeUint8Array(bs.BootCode).writeWord(BS_SIGNATURE_WORD); assert(io.pos() === 512); }; var loadFSI = (io) => { const LeadSig = io.readDoubleWord(); const StrucSig = io.skip(480).readDoubleWord(); const FreeCount = io.readDoubleWord(); const NxtFree = io.readDoubleWord(); const TrailSig = io.skip(12).readDoubleWord(); validate(LeadSig === FSI_LEAD_SIG && StrucSig === FSI_STRUC_SIG && TrailSig === FSI_TRAIL_SIG); return { FreeCount, NxtFree }; }; var writeFSI = (io, fsInfo) => { io.writeDoubleWord(FSI_LEAD_SIG).writeBytes(0, 480).writeDoubleWord(FSI_STRUC_SIG).writeDoubleWord(fsInfo.FreeCount).writeDoubleWord(fsInfo.NxtFree).writeBytes(0, 12).writeDoubleWord(FSI_TRAIL_SIG); }; var isDirEntryLFN = (io) => { const flag = io.skip(DIR_NAME_LENGTH).readByte() === DIR_ENTRY_ATTR_LFN; io.skip(-DIR_NAME_LENGTH - 1); return flag; }; var readDirEntry = (io) => ({ Name: io.readUint8Array(DIR_NAME_LENGTH), Attributes: io.readByte(), NTRes: io.readByte(), CrtTimeTenth: io.readByte(), CrtTime: io.readWord(), CrtDate: io.readWord(), LstAccDate: io.readWord(), FstClusHI: io.readWord(), WrtTime: io.readWord(), WrtDate: io.readWord(), FstClusLO: io.readWord(), FileSize: io.readDoubleWord() }); var loadDirEntry = (io) => { const dir = readDirEntry(io); if (dir.Name[0] === DIR_ENTRY_FLAG_E5) { dir.Name[0] = DIR_ENTRY_FLAG_DELETED; } return dir; }; var writeDirEntry = (io, offset, dirEntry) => { assert(dirEntry.Name.length === DIR_NAME_LENGTH); if (dirEntry.Name[0] === DIR_ENTRY_FLAG_DELETED) { dirEntry.Name[0] = DIR_ENTRY_FLAG_E5; } io.seek(offset).writeUint8Array(dirEntry.Name).writeByte(dirEntry.Attributes).writeByte(dirEntry.NTRes).writeByte(dirEntry.CrtTimeTenth).writeWord(dirEntry.CrtTime).writeWord(dirEntry.CrtDate).writeWord(dirEntry.LstAccDate).writeWord(dirEntry.FstClusHI).writeWord(dirEntry.WrtTime).writeWord(dirEntry.WrtDate).writeWord(dirEntry.FstClusLO).writeDoubleWord(dirEntry.FileSize); }; var writeDirEntryLFN = (io, offset, dir) => { assert(dir.Name1.length === LFN_NAME1_LENGTH); assert(dir.Name2.length === LFN_NAME2_LENGTH); assert(dir.Name3.length === LFN_NAME3_LENGTH); io.seek(offset).writeByte(dir.Ord).writeUint8Array(dir.Name1).writeByte(dir.Attributes).writeByte(dir.Type).writeByte(dir.Chksum).writeUint8Array(dir.Name2).writeWord(dir.FstClusLO).writeUint8Array(dir.Name3); }; var loadDirEntryLFN = (io) => ({ Ord: io.readByte(), Name1: io.readUint8Array(LFN_NAME1_LENGTH), Attributes: io.readByte(), Type: io.readByte(), Chksum: io.readByte(), Name2: io.readUint8Array(LFN_NAME2_LENGTH), FstClusLO: io.readWord(), Name3: io.readUint8Array(LFN_NAME3_LENGTH) }); var createVolumeDirEntry = (sfn) => { const now = /* @__PURE__ */ new Date(); return { Name: sfn, Attributes: DIR_ENTRY_ATTR_VOLUME_ID, NTRes: 0, CrtTimeTenth: 0, CrtTime: 0, CrtDate: 0, LstAccDate: 0, FstClusHI: 0, WrtTime: toTime(now), WrtDate: toDate(now), FstClusLO: 0, FileSize: 0 }; }; var createDotDirEntry = (sfn, dir) => ({ Name: sfn, Attributes: DIR_ENTRY_ATTR_DIRECTORY, NTRes: 0, CrtTimeTenth: dir.CrtTimeTenth, CrtTime: dir.CrtTime, CrtDate: dir.CrtDate, LstAccDate: dir.LstAccDate, FstClusHI: dir.FstClusHI, WrtTime: dir.WrtTime, WrtDate: dir.WrtDate, FstClusLO: dir.FstClusLO, FileSize: 0 }); var createDirEntry = (Name, dateTime) => { const date = toDate(dateTime); const time = toTime(dateTime); const timeTenth = toTimeTenth(dateTime); return { Name, Attributes: 0, NTRes: 0, CrtTimeTenth: timeTenth, CrtTime: time, CrtDate: date, LstAccDate: date, FstClusHI: 0, WrtTime: time, WrtDate: date, FstClusLO: 0, FileSize: 0 }; }; // src/io.mjs var DriverIO = class { /** * @param {!ns.RandomAccessDriver} driver */ constructor(driver) { this.driver = driver; } // Driver /** * @override * @return {number} */ // @ts-expect-error len() { return this.driver.capacity; } /** * @override * @param {number} address * @param {number} len * @return {!Uint8Array} */ // @ts-expect-error readUint8Array(address, len) { assert(address + len <= this.driver.capacity); return this.driver.read(address, len); } /** * @override * @param {number} address * @return {number} */ // @ts-expect-error readByte(address) { return this.readUint8Array(address, 1)[0]; } /** * @override * @param {number} address * @return {number} */ // @ts-expect-error readWord(address) { const array = this.readUint8Array(address, 2); const k = array[0] | array[1] << 8; return k; } /** * @override * @param {number} address * @return {number} */ // @ts-expect-error readDoubleWord(address) { const array = this.readUint8Array(address, 4); const k = (array[0] | array[1] << 8 | array[2] << 16 | array[3] << 24) >>> 0; return k; } /** * @override * @param {number} address * @param {!Uint8Array} array */ // @ts-expect-error writeUint8Array(address, array) { assert(address + array.length <= this.driver.capacity); this.driver.write?.(address, array); } /** * @override * @param {number} address * @param {number} data */ // @ts-expect-error writeByte(address, data) { this.writeUint8Array(address, new Uint8Array([data])); } /** * @override * @param {number} address * @param {number} data * @param {number} count */ // @ts-expect-error writeBytes(address, data, count) { this.writeUint8Array(address, new Uint8Array(count).fill(data)); } /** * @override * @param {number} address * @param {number} data */ // @ts-expect-error writeWord(address, data) { this.writeUint8Array(address, new Uint8Array([data, data >>> 8])); } /** * @override * @param {number} address * @param {number} data */ // @ts-expect-error writeDoubleWord(address, data) { this.writeUint8Array(address, new Uint8Array([data, data >>> 8, data >>> 16, data >>> 24])); } }; var SyncIO = class { /** * @param {!Uint8Array} array */ constructor(array) { this.array = array; this.i = 0; } // IO /** * @override * @return {number} */ // @ts-expect-error pos() { return this.i; } /** * @override * @return {number} */ // @ts-expect-error len() { return this.array.length; } /** * @override * @param {number} offset * @return {!IO} */ // @ts-expect-error seek(offset) { this.i = offset; return this; } /** * @override * @param {number} bytes * @return {!IO} */ // @ts-expect-error skip(bytes) { this.i += bytes; return this; } /** * @override * @param {number} len * @return {!Uint8Array} */ // @ts-expect-error readUint8Array(len) { assert(this.i + len <= this.array.length); const k = new Uint8Array(this.array.subarray(this.i, this.i += len)); return k; } /** * @override * @return {number} */ // @ts-expect-error readByte() { assert(this.i < this.array.length); return this.array[this.i++]; } /** * @override * @return {number} */ // @ts-expect-error readWord() { const { array } = this; assert(this.i + 2 <= array.length); const k = array[this.i++] | array[this.i++] << 8; return k; } /** * @override * @return {number} */ // @ts-expect-error readDoubleWord() { const { array } = this; assert(this.i + 4 <= array.length); const k = (array[this.i++] | array[this.i++] << 8 | array[this.i++] << 16 | array[this.i++] << 24) >>> 0; return k; } /** * @override * @param {!Uint8Array} array * @return {!IO} */ // @ts-expect-error writeUint8Array(array) { assert(this.i + array.length <= this.array.length); this.array.set(array, this.i); this.i += array.length; return this; } /** * @override * @param {number} data * @return {!IO} */ // @ts-expect-error writeByte(data) { assert(this.i + 1 <= this.array.length); assert(data >= 0 && data <= MAX_BYTE); this.array[this.i++] = data; return this; } /** * @override * @param {number} data * @param {number} count * @return {!IO} */ // @ts-expect-error writeBytes(data, count) { assert(count >= 0); assert(this.i + count <= this.array.length); assert(data >= 0 && data <= MAX_BYTE); this.array.fill(data, this.i, this.i += count); return this; } /** * @override * @param {number} data * @return {!IO} */ // @ts-expect-error writeWord(data) { const { array } = this; assert(this.i + 2 <= array.length); assert(data >= 0 && data <= MAX_WORD); array[this.i++] = data; data >>>= 8; array[this.i++] = data; return this; } /** * @override * @param {number} data * @return {!IO} */ // @ts-expect-error writeDoubleWord(data) { const { array } = this; assert(this.i + 4 <= array.length); assert(data >= 0 && data <= MAX_DOUBLE_WORD); array[this.i++] = data; data >>>= 8; array[this.i++] = data; data >>>= 8; array[this.i++] = data; data >>>= 8; array[this.i++] = data; return this; } }; var createDriver = (driver) => new DriverIO(driver); var createIO = (array) => new SyncIO(array); // src/fdisk.mjs var fdisk = (partitions) => { const bs = new Uint8Array(512); writePartitionTable( createIO(bs), partitions.map(({ active, type, relativeSectors, totalSectors }) => { const noCHS = { Cylinder: 0, Head: 0, Sector: 0 }; return { BootIndicator: active ? 128 : 0, Starting: noCHS, SystemID: type, Ending: noCHS, RelativeSectors: relativeSectors, TotalSectors: totalSectors }; }) ); return { bytsPerSec: 512, zeroRegions: [], dataSectors: [{ i: 0, data: bs }] }; }; // src/bs.mjs var jmpBoot12 = [235, 60, 144]; var jmpBoot32 = [235, 88, 144]; var BootCode12 = str2bytes("¾V|´¬„ÀtÍë÷1ÀÍÍëþ"); var BootCode32 = str2bytes("¾r|´¬„ÀtÍë÷1ÀÍÍëþ"); // src/mkfsvfat.mjs var BYTES_PER_SECTOR_BITS = 9; var MAX_RECOMMENDED_BYTES_PER_CLUSTER_BITS = 15; var OEM_NAME = str2bytes("LIBMNTJS"); var BOOT_ERROR_MESSAGE = str2bytes("Non-system disk or disk error\r\nreplace and strike any key when ready\r\n"); var MIN_RESERVED_SECTOR_COUNT = 1; var MIN_RESERVED_SECTOR_COUNT_FAT32 = 32; var FAT12 = 12; var FAT16 = 16; var FAT32 = 32; var NO_NAME_SFN = str2bytes("NO NAME "); var FLOPPY_FORMATS = /* @__PURE__ */ new Map([ // capacity: Media, Tracks, NumHeads, SecPerTrk, SecPerClusBits, FATSz, RootDirSectors [160, [254, 40, 1, 8, 0, 1, 4]], [180, [252, 40, 1, 9, 0, 2, 4]], [320, [255, 40, 2, 8, 1, 1, 7]], [360, [253, 40, 2, 9, 1, 2, 7]], [720, [249, 80, 2, 9, 1, 3, 7]], [1200, [249, 80, 2, 15, 0, 7, 14]], [1440, [240, 80, 2, 18, 0, 9, 14]], [2880, [240, 80, 2, 36, 1, 9, 15]] ]); var getIndexBits = (capacity, type) => { let IndexBits = FAT32; if (type === "FAT12") { IndexBits = FAT12; } else if (type === "FAT16") { IndexBits = FAT16; } else if (type === "FAT32") { IndexBits = FAT32; } else if (capacity <= /* 4 MB */ 4 << 20) { IndexBits = FAT12; } else if (capacity < /* 512 MB */ 512 << 20) { IndexBits = FAT16; } return IndexBits; }; var getIntegerOrDefault = (min, max, defaultValue, value) => { if (Number.isInteger(value)) { value = Number(value); if (value >= min && value <= max) { return value; } } return defaultValue; }; var getDefaultVolID = () => { const now = /* @__PURE__ */ new Date(); return (toDate(now) << 16 | toTime(now)) >>> 0; }; var getMinFATSz = (CountOfClusters, IndexBits, BytsPerSecBits) => { assert(CountOfClusters >= 0); assert(BytsPerSecBits >= 9); assert(IndexBits === FAT12 || IndexBits === FAT16 || IndexBits === FAT32); const OriginalMinFATSz = Math.ceil((CountOfClusters + 2) * IndexBits / (1 << BytsPerSecBits + 3)); let Dividend = CountOfClusters + 2; assert(Dividend <= 268435455); let DivisorShift = BytsPerSecBits + 3; if (IndexBits === FAT32) { DivisorShift -= 5; } else if (IndexBits === FAT16) { DivisorShift -= 4; } else { Dividend *= 3; DivisorShift -= 2; } assert(Dividend <= 1073741823); assert(DivisorShift > 0); const Quotient = Dividend >>> DivisorShift; const Reminder = Dividend & (1 << DivisorShift) - 1; const MinFATSz = Quotient + (Reminder === 0 ? 0 : 1); assert(MinFATSz === OriginalMinFATSz); return MinFATSz; }; var getSecPerClusBits = (IndexBits, DskSz, MaxClusCntSafe, BytsPerSecBits, DataAndFAT, NumFATs, secPerClus) => { if (Number.isInteger(secPerClus)) { const i = [1, 2, 4, 8, 16, 32, 64, 128].indexOf(Number(secPerClus)); if (i >= 0) { return i; } } if (IndexBits === FAT32) { if (DskSz <= 260 << 11) { return 0; } return 3; } const MaxFATSz = getMinFATSz(MaxClusCntSafe, IndexBits, BytsPerSecBits); const MinDataSec = DataAndFAT - NumFATs * MaxFATSz; const MaxSecPerClusBits = MAX_RECOMMENDED_BYTES_PER_CLUSTER_BITS - BytsPerSecBits; let SecPerClusBits = 0; let MaxDataSec = MaxClusCntSafe; while (SecPerClusBits < MaxSecPerClusBits && MaxDataSec < MinDataSec) { SecPerClusBits++; MaxDataSec *= 2; } return SecPerClusBits; }; var getOptimalFATSz = (DataAndFAT, NumFATs, SecPerClusBits, IndexBits, BytsPerSecBits) => { const Numerator = DataAndFAT + (1 << SecPerClusBits + 1); const Denominator = (1 << SecPerClusBits + BytsPerSecBits + 3) / IndexBits + NumFATs; return Math.ceil(Numerator / Denominator); }; var getRootDirSectors = (BytsPerSecBits, rootEntCnt) => { const RootEntCnt = getIntegerOrDefault(1, 512, 512, rootEntCnt); const RootEntPerSecBits = BytsPerSecBits - DIR_ENTRY_SIZE_BITS; return (RootEntCnt >> RootEntPerSecBits) + ((RootEntCnt & (1 << RootEntPerSecBits) - 1) === 0 ? 0 : 1); }; var createFloppyDiskLayout = (capacity, floppyFormat) => { const [Media, Tracks, NumHeads, SecPerTrk, SecPerClusBits, FATSz, RootDirSectors] = floppyFormat; const BytsPerSecBits = 9; const TotSec = Tracks * NumHeads * SecPerTrk; assert(TotSec << BytsPerSecBits === capacity); const RsvdSecCnt = 1; const NumFATs = 2; const MetaSec = RsvdSecCnt + FATSz * NumFATs + RootDirSectors; assert(MetaSec % (1 << SecPerClusBits) === 0); const DataSec = TotSec - MetaSec; const CountOfClusters = DataSec >> SecPerClusBits; return { IndexBits: FAT12, BytsPerSecBits, SecPerClusBits, RsvdSecCnt, NumFATs, RootDirSectors, FATSz, TotSec, CountOfClusters, Media, NumHeads, SecPerTrk }; }; var createDiskLayout = (capacity, options) => { const maxCapacity = Number.MAX_SAFE_INTEGER; capacity = getIntegerOrDefault(0, maxCapacity, maxCapacity, capacity); const IndexBits = getIndexBits(capacity, options?.type); const BytsPerSecBits = BYTES_PER_SECTOR_BITS; const BytsPerSec = 1 << BytsPerSecBits; const DskSz = Math.min(MAX_DOUBLE_WORD, Math.floor(capacity / BytsPerSec)); const NumFATs = options?.numFATs === 1 ? 1 : 2; let RootDirSectors = 0; let RsvdSecCnt = MIN_RESERVED_SECTOR_COUNT_FAT32; if (IndexBits !== FAT32) { RootDirSectors = getRootDirSectors(BytsPerSecBits, options?.rootEntCnt); RsvdSecCnt = MIN_RESERVED_SECTOR_COUNT; } let MinClusCntSafe = WINDOWS_MIN_COUNT_OF_CLUSTERS_FAT32; let MaxClusCntSafe = WINDOWS_MAX_COUNT_OF_CLUSTERS_FAT32; if (IndexBits === FAT12) { MinClusCntSafe = WINDOWS_MIN_COUNT_OF_CLUSTERS_FAT12; MaxClusCntSafe = WINDOWS_MAX_COUNT_OF_CLUSTERS_FAT12; } else if (IndexBits === FAT16) { MinClusCntSafe = WINDOWS_MIN_COUNT_OF_CLUSTERS_FAT16; MaxClusCntSafe = WINDOWS_MAX_COUNT_OF_CLUSTERS_FAT16; } const DataAndFAT = DskSz - RsvdSecCnt - RootDirSectors; if (DataAndFAT < NumFATs) { return null; } const compat = getIntegerOrDefault(0, COUNT_OF_CLUSTERS_COMPATIBILITY, COUNT_OF_CLUSTERS_COMPATIBILITY, options?.compat); MinClusCntSafe += compat; MaxClusCntSafe -= compat; const SecPerClusBits = getSecPerClusBits(IndexBits, DskSz, MaxClusCntSafe, BytsPerSecBits, DataAndFAT, NumFATs, options?.secPerClus); const SecPerClus = 1 << SecPerClusBits; let FATSz = getOptimalFATSz(DataAndFAT, NumFATs, SecPerClusBits, IndexBits, BytsPerSecBits); let DataSec = DataAndFAT - FATSz * NumFATs; let CountOfClusters = Math.floor(DataSec / SecPerClus); if (CountOfClusters < MinClusCntSafe) { return null; } if (CountOfClusters > MaxClusCntSafe) { CountOfClusters = MaxClusCntSafe; } FATSz = getMinFATSz(CountOfClusters, IndexBits, BytsPerSecBits); let MetaSec = RsvdSecCnt + FATSz * NumFATs + RootDirSectors; DataSec = CountOfClusters * SecPerClus; let TotSec = MetaSec + DataSec; let Wasted = DskSz - TotSec; assert(Wasted >= 0); const Reminder = MetaSec % SecPerClus; const Alignment = Reminder === 0 ? 0 : SecPerClus - Reminder; let AlignmentAdjustment = 0; if (Alignment > Wasted) { CountOfClusters--; Wasted += SecPerClus; if (CountOfClusters < MinClusCntSafe) { return null; } const NewFATSz = getMinFATSz(CountOfClusters, IndexBits, BytsPerSecBits); AlignmentAdjustment = (FATSz - NewFATSz) * NumFATs; assert(AlignmentAdjustment >= 0); FATSz = NewFATSz; } Wasted -= Alignment; assert(Wasted >= 0); RsvdSecCnt += Alignment + AlignmentAdjustment; TotSec = DskSz - Wasted; assert(TotSec <= MAX_DOUBLE_WORD); MetaSec = RsvdSecCnt + FATSz * NumFATs + RootDirSectors; DataSec = CountOfClusters * SecPerClus; assert(MetaSec % SecPerClus === 0); assert(TotSec === MetaSec + DataSec); return { IndexBits, BytsPerSecBits, SecPerClusBits, RsvdSecCnt, NumFATs, RootDirSectors, FATSz, TotSec, CountOfClusters, Media: 248, NumHeads: 255, SecPerTrk: 63 }; }; var createBootSector = (diskLayout, options) => { const { IndexBits, BytsPerSecBits, SecPerClusBits, RsvdSecCnt, NumFATs, RootDirSectors, FATSz, TotSec } = diskLayout; const bpbFat32 = IndexBits === FAT32; const BootCodeLength = bpbFat32 ? BS_BOOT_CODE_FAT32_LENGTH : BS_BOOT_CODE_LENGTH; const BytsPerSec = 1 << BytsPerSecBits; const RootEntCnt = RootDirSectors << BytsPerSecBits - DIR_ENTRY_SIZE_BITS; const bootSector = { jmpBoot: new Uint8Array(BS_JUMP_BOOT_LENGTH), OEMName: new Uint8Array(BS_OEM_NAME_LENGTH).fill(" ".charCodeAt(0)), bpb: { BytsPerSec, SecPerClus: 1 << SecPerClusBits, RsvdSecCnt, NumFATs, RootEntCnt, TotSec16: TotSec > MAX_WORD ? 0 : TotSec, Media: getIntegerOrDefault(0, MAX_BYTE, diskLayout.Media, options?.media), FATSz16: bpbFat32 ? 0 : FATSz, SecPerTrk: getIntegerOrDefault(0, MAX_WORD, diskLayout.SecPerTrk, options?.secPerTrk), NumHeads: getIntegerOrDefault(0, MAX_WORD, diskLayout.NumHeads, options?.numHeads), HiddSec: getIntegerOrDefault(0, MAX_DOUBLE_WORD, 0, options?.hiddSec), TotSec32: TotSec > MAX_WORD ? TotSec : 0, FATSz32: FATSz, ExtFlags: 0, FSVer: 0, RootClus: 2, FSInfo: 1, BkBootSec: 6 }, DrvNum: 0, Reserved1: 0, BootSig: 41, VolID: getIntegerOrDefault(0, MAX_DOUBLE_WORD, getDefaultVolID(), options?.id), VolLab: NO_NAME_SFN, FilSysType: new Uint8Array(BS_FIL_SYS_TYPE_LENGTH).fill(" ".charCodeAt(0)), BootCode: new Uint8Array(BootCodeLength) }; bootSector.OEMName.set((options?.oemName ?? OEM_NAME).subarray(0, BS_OEM_NAME_LENGTH)); bootSector.FilSysType.set(str2bytes("FAT" + IndexBits)); const bs = options?.bs; if (bs && bs.length >= 512) { bootSector.jmpBoot.set(bs.subarray(0, BS_JUMP_BOOT_LENGTH)); bootSector.BootCode.set(bs.subarray(510 - BootCodeLength, 510)); } else { let jmpBoot = jmpBoot12; let BootCode = BootCode12; const BootCodeOffset = 510 - BootCodeLength; if (bpbFat32) { jmpBoot = jmpBoot32; BootCode = BootCode32; } bootSector.jmpBoot.set(jmpBoot); bootSector.BootCode.set(BootCode); const message = options?.message ?? BOOT_ERROR_MESSAGE; const maxMessageLength = 510 - 1 - BootCodeOffset - BootCode.length; bootSector.BootCode.set(message.subarray(0, maxMessageLength), BootCode.length); } return bootSector; }; var createDiskSectors = (IndexBits, bootSector, options) => { const bpb = bootSector.bpb; const { BytsPerSec, SecPerClus, RsvdSecCnt, NumFATs, RootEntCnt, TotSec16, Media, FATSz16, TotSec32, FATSz32 } = bpb; const bpbFat32 = IndexBits === FAT32; const RootDirSectors = bpbFat32 ? 0 : Math.ceil((RootEntCnt << DIR_ENTRY_SIZE_BITS) / BytsPerSec); const FATSz = bpbFat32 ? FATSz32 : FATSz16; const TotSec = TotSec16 ? TotSec16 : TotSec32; const MetaSec = RsvdSecCnt + FATSz * NumFATs + RootDirSectors; const DataSec = TotSec - MetaSec; const CountOfClusters = Math.floor(DataSec / SecPerClus); const zeroRegions = []; zeroRegions.push({ i: 0, count: MetaSec + (bpbFat32 ? SecPerClus : 0) }); const dataSectors = []; const bs = new Uint8Array(BytsPerSec); writeBootSector(createIO(bs), bootSector); dataSectors.push({ i: 0, data: bs }); const fat = new Uint8Array(BytsPerSec); fat[0] = Media; for (let j = 0; j < IndexBits / 4 - 1; j++) { fat[j + 1] = 255; } if (bpbFat32) { const { RootClus, FSInfo, BkBootSec } = bpb; const offset = RootClus * 4; fat[offset] = 255; fat[offset + 1] = 255; fat[offset + 2] = 255; fat[offset + 3] = 15; const fsi = new Uint8Array(BytsPerSec); writeFSI(createIO(fsi), { FreeCount: CountOfClusters - 1, NxtFree: MIN_CLUS_NUM }); dataSectors.push({ i: FSInfo, data: fsi }); dataSectors.push({ i: BkBootSec, data: bs }); dataSectors.push({ i: BkBootSec + 1, data: fsi }); } for (let i = 0; i < NumFATs; i++) { const offset = RsvdSecCnt + i * FATSz; dataSectors.push({ i: offset, data: fat }); } if (options?.label) { const root = new Uint8Array(BytsPerSec); const RootDirSec = RsvdSecCnt + FATSz * NumFATs + (bpbFat32 ? (bpb.RootClus - MIN_CLUS_NUM) * SecPerClus : 0); const sfn = new Uint8Array(DIR_NAME_LENGTH).fill(" ".charCodeAt(0)); sfn.set(options.label.subarray(0, DIR_NAME_LENGTH)); writeDirEntry(createIO(root), 0, createVolumeDirEntry(sfn)); dataSectors.push({ i: RootDirSec, data: root }); } return { bytsPerSec: BytsPerSec, zeroRegions, dataSectors }; }; var mkfsvfat = (capacity, options) => { const floppyFormat = FLOPPY_FORMATS.get(capacity / 1024); const diskLayout = floppyFormat ? createFloppyDiskLayout(capacity, floppyFormat) : createDiskLayout(capacity, options); if (!diskLayout) { return null; } const { IndexBits, BytsPerSecBits, SecPerClusBits, RsvdSecCnt, NumFATs, RootDirSectors, FATSz, TotSec, CountOfClusters } = diskLayout; const bootSector = createBootSector(diskLayout, options); const sectors = createDiskSectors(IndexBits, bootSector, options); return { sectors, id: bootSector.VolID, type: "FAT" + IndexBits, totSec: TotSec, rsvdSecCnt: RsvdSecCnt, numFATs: NumFATs, fatSz: FATSz, rootDirSectors: RootDirSectors, countOfClusters: CountOfClusters, secPerClus: 1 << SecPerClusBits, bytsPerSec: 1 << BytsPerSecBits }; }; // src/fs.mjs var LFN_ENABLED = typeof USE_LFN === "boolean" ? USE_LFN : true; var NODE_ROOT = 0; var NODE_LABEL = 1; var NODE_DOT_DIR = 2; var NODE_REG_DIR = 3; var NODE_REG_FILE = 4; var NODE_DELETED = 5; var NODE_LAST = 6; var DIR_LN_LAST_LONG_ENTRY = 64; var createNode = (kind, shortName, offset, dirEntry) => { const isRoot = kind === NODE_ROOT; const isRegDir = kind === NODE_REG_DIR; const isRegFile = kind === NODE_REG_FILE; return { shortName, dirOffset: offset, dirEntry, fstClus: dirEntry.FstClusHI << 16 | dirEntry.FstClusLO, isRoot, isLabel: kind === NODE_LABEL, isRegDir, isRegFile, isDir: isRoot || isRegDir, isReg: isRegDir || isRegFile, isDeleted: kind === NODE_DELETED, isLast: kind === NODE_LAST, // LFN extension longName: shortName, firstDirOffset: offset, dirCount: 1 }; }; var DUMMY_DIR = createDirEntry(new Uint8Array(DIR_NAME_LENGTH), null); var ROOT_NODE = createNode(NODE_ROOT, "", 0, DUMMY_DIR); var DOT_SFN = str2bytes(". "); var DOT_DOT_SFN = str2bytes(".. "); var readNode = (io, flag, offset, cp) => { if (flag === DIR_ENTRY_FLAG_LAST) { return createNode(NODE_LAST, "", offset, DUMMY_DIR); } if (flag === DIR_ENTRY_FLAG_DELETED) { return createNode(NODE_DELETED, "", offset, DUMMY_DIR); } const dir = loadDirEntry(io); const dirName = dir.Name; const dirAttributes = dir.Attributes; const isLabel = (dirAttributes & DIR_ENTRY_ATTR_VOLUME_ID) > 0; if (isLabel) { return createNode(NODE_LABEL, cp.decode(dirName).trimEnd(), offset, dir); } const isDir = (dirAttributes & DIR_ENTRY_ATTR_DIRECTORY) > 0; if (isDir && dirName.every((it, i) => it === DOT_SFN[i])) { return createNode(NODE_DOT_DIR, ".", offset, dir); } if (isDir && dirName.every((it, i) => it === DOT_DOT_SFN[i])) { return createNode(NODE_DOT_DIR, "..", offset, dir); } if (dirName.every(isShortNameValidCode)) { const shortName = sfnToStr(dirName, cp); if (shortName && !shortName.startsWith(" ")) { return createNode(isDir ? NODE_REG_DIR : NODE_REG_FILE, shortName, offset, dir); } } return null; }; var flushDirEntry = (driver, offset, dirEntry) => { const array = new Uint8Array(DIR_ENTRY_SIZE); const io = createIO(array); writeDirEntry(io, 0, dirEntry); driver.writeUint8Array(offset, array); }; var flushNode = (driver, node) => { flushDirEntry(driver, node.dirOffset, node.dirEntry); }; var loadFATVariables = (bs) => { const bpb = bs.bpb; const bpbFat32 = isBpbFat32(bpb); const { BytsPerSec, SecPerClus, RsvdSecCnt, NumFATs, RootEntCnt, TotSec16, FATSz16, TotSec32, FATSz32, RootClus } = bpb; const RootDirSectors = bpbFat32 ? 0 : Math.ceil((RootEntCnt << DIR_ENTRY_SIZE_BITS) / BytsPerSec); const FATSz = bpbFat32 ? FATSz32 : FATSz16; const TotSec = TotSec16 ? TotSec16 : TotSec32; const MetaSec = RsvdSecCnt + FATSz * NumFATs + RootDirSectors; const DataSec = TotSec - MetaSec; const SizeOfCluster = BytsPerSec * SecPerClus; const CountOfClusters = Math.floor(DataSec / SecPerClus); const IndexBits = bpbFat32 ? 32 : CountOfClusters < FAT_THRESHOLD ? 12 : 16; const MaxClus = CountOfClusters + 1; const SystemArea = RsvdSecCnt + FATSz * NumFATs; const RootDirSec = SystemArea + (bpbFat32 ? (RootClus - MIN_CLUS_NUM) * SecPerClus : 0); const FirstDataSec = SystemArea + RootDirSectors; const RootDirOffset = BytsPerSec * RootDirSec; const FinalClus = (1 << Math.min(28, IndexBits)) - 1; return { FATSz, TotSec, DataSec, SizeOfCluster, CountOfClusters, IndexBits, MaxClus, FirstDataSec, RootDirOffset, FinalClus }; }; var match = (name, node) => { const upper = name.toUpperCase(); return upper === node.shortName || LFN_ENABLED && upper === node.longName.toUpperCase(); }; var fillNode = (node, chain, firstDirOffset) => { if (LFN_ENABLED && chain.length) { const ord = chain[chain.length - 1].Ord & DIR_LN_LAST_LONG_ENTRY - 1; if (ord === 1) { const chksum = getChkSum(node.dirEntry.Name); if (chain.every((it) => it.Chksum === chksum)) { node.longName = lfnToStr(chain); node.firstDirOffset = firstDirOffset; node.dirCount = chain.length + 1; } else { } } else { } } }; var FAT122 = class { /** * @param {!Driver} driver * @param {!FATVariables} vars * @param {!Array<number>} offsetFATs */ constructor(driver, vars, offsetFATs) { this.driver = driver; this.vars = vars; this.offsetFATs = offsetFATs; } /** * @override * @param {number} clusNum * @return {number} */ // @ts-expect-error getNextClusNum(clusNum) { const { driver, vars, offsetFATs } = this; assert(clusNum >= MIN_CLUS_NUM && clusNum <= vars.MaxClus); const val = driver.readWord(offsetFATs[0] + clusNum + (clusNum >> 1)); return clusNum & 1 ? val >> 4 : val & vars.FinalClus; } /** * @override * @param {number} clusNum * @param {number} value */ // @ts-expect-error setNextClusNum(clusNum, value) { const { driver, vars, offsetFATs } = this; assert(clusNum >= MIN_CLUS_NUM && clusNum <= vars.MaxClus); for (let i = 0; i < offsetFATs.length; i++) { const address = offsetFATs[i] + clusNum + (clusNum >> 1); const val = driver.readWord(address); driver.writeWord(address, clusNum & 1 ? value << 4 | val & 15 : val & 61440 | value); } } /** * @override * @return {number} */ // @ts-expect-error // eslint-disable-next-line class-methods-use-this getNextFreeClus() { return MIN_CLUS_NUM; } /** * @override * @param {number} clusNum */ // @ts-expect-error // eslint-disable-next-line no-unused-vars, class-methods-use-this setNextFreeClus(clusNum) { } }; var FAT162 = class { /** * @param {!Driver} driver * @param {!FATVariables} vars * @param {!Array<number>} offsetFATs */ constructor(driver, vars, offsetFATs) { this.driver = driver; this.vars = vars; this.offsetFATs = offsetFATs; } /** * @override * @param {number} clusNum * @return {number} */ // @ts-expect-error getNextClusNum(clusNum) { const { driver, vars, offsetFATs } = this; assert(clusNum >= MIN_CLUS_NUM && clusNum <= vars.MaxClus); return driver.readWord(offsetFATs[0] + clusNum * 2); } /** * @override * @param {number} clusNum * @param {number} value */ // @ts-expect-error setNextClusNum(clusNum, value) { const { driver, vars, offsetFATs } = this; assert(clusNum >= MIN_CLUS_NUM && clusNum <= vars.MaxClus); for (let i = 0; i < offsetFATs.length; i++) { driver.writeWord(offsetFATs[i] + clusNum * 2, value); } } /** * @override * @return {number} */ // @ts-expect-error // eslint-disable-next-line class-methods-use-this getNextFreeClus() { return MIN_CLUS_NUM; } /** * @override * @param {number} clusNum */ // @ts-expect-error // eslint-disable-next-line no-unused-vars, class-methods-use-this setNextFreeClus(clusNum) { } }; var FAT322 = class { /** * @param {!Driver} driver * @param {!FATVariables} vars * @param {!Array<number>} offsetFATs * @param {number} fsiOffset * @param {number} fsiNxtFreeOffset */ constructor(driver, vars, offsetFATs, fsiOffset, fsiNxtFreeOffset) { this.driver = driver; this.vars = vars; this.offsetFATs = offsetFATs; this.fsiOffset = fsiOffset; this.fsiNxtFreeOffset = fsiNxtFreeOffset; } /** * @override * @param {number} clusNum * @return {number} */ // @ts-expect-error getNextClusNum(clusNum) { const { driver, vars, offsetFATs } = this; assert(clusNum >= MIN_CLUS_NUM && clusNum <= vars.MaxClus); return driver.readDoubleWord(offsetFATs[0] + clusNum * 4) & vars.FinalClus; } /** * @override * @param {number} clusNum * @param {number} value */ // @ts-expect-error setNextClusNum(clusNum, value) { const { driver, vars, offsetFATs } = this; assert(clusNum >= MIN_CLUS_NUM && clusNum <= vars.MaxClus); for (let i = 0; i < offsetFATs.length; i++) { const address = offsetFATs[i] + clusNum * 4; const val = driver.readDoubleWord(address); driver.writeDoubleWord(address, val & 4026531840 | value); } } /** * @override * @return {number} */ // @ts-expect-error getNextFreeClus() { const { driver, vars, fsiNxtFreeOffset } = this; const clusNum = driver.readDoubleWord(fsiNxtFreeOffset); return clusNum >= MIN_CLUS_NUM && clusNum <= vars.MaxClus ? clusNum : MIN_CLUS_NUM; } /** * @override * @param {number} clusNum */ // @ts-expect-error setNextFreeClus(clusNum) { const { driver, fsiNxtFreeOffset } = this; driver.writeDoubleWord(fsiNxtFreeOffset, clusNum); } }; var FileIO = class { /** * @param {!FileSystem} fs * @param {!FATNode} node */ constructor(fs, node) { this.fs = fs; this.sizeOfCluster = fs.vars.SizeOfCluster; this.node = node; this.prev = 0; this.curr = node.fstClus; this.pos = 0; this.fileSize = 0; } /** * @override */ // @ts-expect-error rewind() { this.prev = 0; this.curr = this.node.fstClus; this.pos = 0; this.fileSize = 0; } /** * @override * @return {number} */ // @ts-expect-error skipClus() { const { fs, sizeOfCluster, node } = this; let len = Math.min(sizeOfCluster, Math.max(0, node.dirEntry.FileSize - this.pos)); if (len) { if (this.curr >= MIN_CLUS_NUM && this.curr <= fs.vars.MaxClus) { this.prev = this.curr; this.curr = fs.FAT.getNextClusNum(this.curr); this.pos += sizeOfCluster; } else { len = 0; } } return len; } /** * @override * @param {!Uint8Array} buf * @return {number} */ // @ts-expect-error readClus(buf) { const { fs, sizeOfCluster, node } = this; let len = Math.min(sizeOfCluster, Math.max(0, node.dirEntry.FileSize - this.pos)); if (len) { const offset = fs.getContentOffset(this.curr); if (offset) { buf.set(fs.driver.readUint8Array(offset, len)); this.prev = this.curr; this.curr = fs.FAT.getNextClusNum(this.curr); this.pos += sizeOfCluster; } else { len = 0; } } return len; } /** * @override * @param {!Uint8Array} buf * @return {number} */ // @ts-expect-error writeClus(buf) { assert(buf.length > 0); const { fs, sizeOfCluster, node } = this; const len = Math.min(sizeOfCluster, buf.length); let offset = fs.getContentOffset(this.curr); if (!offset) { const newClusNum = fs.allocateCluster(); if (!newClusNum) { return 0; } if (node.fstClus) { fs.FAT.setNextClusNum(this.prev, newClusNum); } else { this.setFirstClus(newClusNum); } this.curr = newClusNum; offset = fs.getContentOffset(newClusNum); node.dirEntry.FileSize = this.pos + len; flushNode(this.fs.driver, this.node); } fs.driver.writeUint8Array(offset, buf.subarray(0, len)); this.fileSize = this.pos + len; this.prev = this.curr; this.curr = fs.FAT.getNextClusNum(this.curr); this.pos += sizeOfCluster; return len; } /** * @override * @return {!Uint8Array} */ // @ts-expect-error readData() { this.rewind(); const fileSize = this.node.dirEntry.FileSize; const data = new Uint8Array(fileSize); if (fileSize) { let len; let tmp = data; while (len = this.readClus(tmp)) { tmp = tmp.subarray(len); } } return data; } /** * @override * @param {!Uint8Array} data * @return {number} */ // @ts-expect-error writeData(data) { this.rewind(); let len; let tmp = data; while (tmp.length && (len = this.writeClus(tmp))) { tmp = tmp.subarray(len); } this.fs.unlink(this.curr); if (this.node.fstClus === this.curr) { this.setFirstClus(0); assert(this.pos === 0); assert(this.fileSize === 0); } this.node.dirEntry.FileSize = this.fileSize; flushNode(this.fs.driver, this.node); const fileSize = data.length