cffs-image-tool 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Aaron Wright
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # cffs
2
+
3
+ A CLI tool for creating and managing CompactFlash filesystem images for the A.C. Wright 6502 project.
4
+
5
+ The filesystem uses a simple flat layout: a single 512-byte directory sector (LBA 0) holding up to 16 entries in 8.3 filename format, followed by contiguous data sectors starting at LBA 1.
6
+
7
+ ## Features
8
+
9
+ - Create blank CF images of configurable size
10
+ - Add, remove, list, and extract files
11
+ - Defragment images to reclaim gaps
12
+ - Display image statistics
13
+
14
+ ## Prerequisites
15
+
16
+ - [Node.js](https://nodejs.org/) (v18 or later recommended)
17
+ - npm
18
+
19
+ ## Build
20
+
21
+ ```bash
22
+ npm install
23
+ npm run build
24
+ ```
25
+
26
+ The compiled output is written to the `dist/` directory.
27
+
28
+ ## Usage
29
+
30
+ After building, run the tool directly with Node or via the `cffs` bin entry:
31
+
32
+ ```bash
33
+ # Create a 32 MB image
34
+ npx cffs create disk.img --size 32M
35
+
36
+ # Add a file
37
+ npx cffs add disk.img firmware.bin
38
+
39
+ # List files
40
+ npx cffs list disk.img
41
+
42
+ # Extract a file
43
+ npx cffs extract disk.img FIRMWARE.BIN output.bin
44
+
45
+ # Show image info
46
+ npx cffs info disk.img
47
+
48
+ # Remove a file
49
+ npx cffs remove disk.img FIRMWARE.BIN
50
+
51
+ # Defragment
52
+ npx cffs defrag disk.img
53
+
54
+ # Clear all entries
55
+ npx cffs clear disk.img
56
+ ```
57
+
58
+ ### Development
59
+
60
+ Run directly from TypeScript without compiling:
61
+
62
+ ```bash
63
+ npm run dev -- <command> [options]
64
+ ```
65
+
66
+ ## Filesystem Layout
67
+
68
+ | Region | LBA | Size | Description |
69
+ |-----------|-------|------------|--------------------------------------|
70
+ | Directory | 0 | 512 bytes | Up to 16 × 32-byte directory entries |
71
+ | Data | 1+ | Remainder | Contiguous file data sectors |
72
+
73
+ Each directory entry is 32 bytes:
74
+
75
+ | Offset | Length | Field |
76
+ |--------|--------|--------------|
77
+ | 0 | 8 | Filename |
78
+ | 8 | 3 | Extension |
79
+ | 11 | 1 | Flags |
80
+ | 12 | 2 | Start sector |
81
+ | 14 | 2 | File size |
82
+ | 16 | 16 | Reserved |
83
+
84
+ ## License
85
+
86
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { createInterface } from 'node:readline/promises';
4
+ import { stdin, stdout } from 'node:process';
5
+ import { openImage, saveImage, createImage } from './lib/image.js';
6
+ import { formatName } from './lib/filename.js';
7
+ import { calcSectorCount } from './lib/directory.js';
8
+ import { addFile, removeFile, listFiles, extractFile, defragment, clearImage, imageInfo, } from './lib/operations.js';
9
+ const program = new Command();
10
+ program
11
+ .name('cffs')
12
+ .description('CompactFlash Filesystem Image Tool for A.C. Wright 6502 Project')
13
+ .version('1.0.0');
14
+ /**
15
+ * Parse a size string like "32M", "512K", "1G", or a plain number (bytes).
16
+ */
17
+ function parseSize(value) {
18
+ const match = value.match(/^(\d+)\s*([KMGkmg])?[Bb]?$/);
19
+ if (!match) {
20
+ throw new Error(`Invalid size format: "${value}". Use e.g. 32M, 512K, or bytes.`);
21
+ }
22
+ const num = parseInt(match[1], 10);
23
+ const unit = (match[2] ?? '').toUpperCase();
24
+ switch (unit) {
25
+ case 'K': return num * 1024;
26
+ case 'M': return num * 1024 * 1024;
27
+ case 'G': return num * 1024 * 1024 * 1024;
28
+ default: return num;
29
+ }
30
+ }
31
+ // ── create ──────────────────────────────────────────────────────────────────
32
+ program
33
+ .command('create')
34
+ .description('Create a blank CompactFlash image')
35
+ .argument('<image>', 'Path to image file to create')
36
+ .option('-s, --size <size>', 'Image size (e.g. 32M, 512K)', '32M')
37
+ .action((image, opts) => {
38
+ const totalBytes = parseSize(opts.size);
39
+ if (totalBytes % 512 !== 0) {
40
+ console.error('Error: size must be a multiple of 512 bytes');
41
+ process.exit(1);
42
+ }
43
+ const totalSectors = totalBytes / 512;
44
+ const buf = createImage(totalSectors);
45
+ saveImage(image, buf);
46
+ console.log(`Created ${image} (${totalBytes.toLocaleString()} bytes, ${totalSectors.toLocaleString()} sectors)`);
47
+ });
48
+ // ── list ────────────────────────────────────────────────────────────────────
49
+ program
50
+ .command('list')
51
+ .description('List files in the image')
52
+ .argument('<image>', 'Path to image file')
53
+ .action((image) => {
54
+ const buf = openImage(image);
55
+ const files = listFiles(buf);
56
+ if (files.length === 0) {
57
+ console.log('No files in image.');
58
+ return;
59
+ }
60
+ console.log('Name Size Start Sectors');
61
+ console.log('──────────── ─────── ───── ───────');
62
+ for (const f of files) {
63
+ const display = formatName(f).padEnd(12);
64
+ const size = f.fileSize.toString().padStart(7);
65
+ const start = f.startSector.toString().padStart(5);
66
+ const sectors = calcSectorCount(f.fileSize).toString().padStart(7);
67
+ console.log(`${display} ${size} ${start} ${sectors}`);
68
+ }
69
+ });
70
+ // ── add ─────────────────────────────────────────────────────────────────────
71
+ program
72
+ .command('add')
73
+ .description('Add a host file to the image')
74
+ .argument('<image>', 'Path to image file')
75
+ .argument('<file>', 'Host file to add')
76
+ .option('-n, --name <name>', 'Target 8.3 filename (default: source filename)')
77
+ .action((image, file, opts) => {
78
+ const buf = openImage(image);
79
+ addFile(buf, file, opts.name);
80
+ saveImage(image, buf);
81
+ console.log(`Added ${opts.name ?? file} to ${image}`);
82
+ });
83
+ // ── remove ──────────────────────────────────────────────────────────────────
84
+ program
85
+ .command('remove')
86
+ .description('Delete a file entry from the image')
87
+ .argument('<image>', 'Path to image file')
88
+ .argument('<name>', 'Filename to remove (8.3 format)')
89
+ .action((image, name) => {
90
+ const buf = openImage(image);
91
+ removeFile(buf, name);
92
+ saveImage(image, buf);
93
+ console.log(`Removed ${name} from ${image}`);
94
+ });
95
+ // ── extract ─────────────────────────────────────────────────────────────────
96
+ program
97
+ .command('extract')
98
+ .description('Extract a file from the image to the host filesystem')
99
+ .argument('<image>', 'Path to image file')
100
+ .argument('<name>', 'Filename to extract (8.3 format)')
101
+ .argument('[output]', 'Output path (default: original filename)')
102
+ .action((image, name, output) => {
103
+ const buf = openImage(image);
104
+ extractFile(buf, name, output);
105
+ console.log(`Extracted ${name} from ${image}`);
106
+ });
107
+ // ── defrag ──────────────────────────────────────────────────────────────────
108
+ program
109
+ .command('defrag')
110
+ .description('Defragment the image (compact files)')
111
+ .argument('<image>', 'Path to image file')
112
+ .action((image) => {
113
+ const buf = openImage(image);
114
+ defragment(buf);
115
+ saveImage(image, buf);
116
+ console.log(`Defragmented ${image}`);
117
+ });
118
+ // ── clear ───────────────────────────────────────────────────────────────────
119
+ program
120
+ .command('clear')
121
+ .description('Clear all directory entries')
122
+ .argument('<image>', 'Path to image file')
123
+ .option('-y, --yes', 'Skip confirmation prompt')
124
+ .action(async (image, opts) => {
125
+ if (!opts.yes) {
126
+ const rl = createInterface({ input: stdin, output: stdout });
127
+ const answer = await rl.question(`Clear all entries in ${image}? [y/N] `);
128
+ rl.close();
129
+ if (answer.toLowerCase() !== 'y') {
130
+ console.log('Aborted.');
131
+ return;
132
+ }
133
+ }
134
+ const buf = openImage(image);
135
+ clearImage(buf);
136
+ saveImage(image, buf);
137
+ console.log(`Cleared all directory entries in ${image}`);
138
+ });
139
+ // ── info ────────────────────────────────────────────────────────────────────
140
+ program
141
+ .command('info')
142
+ .description('Display image statistics')
143
+ .argument('<image>', 'Path to image file')
144
+ .action((image) => {
145
+ const buf = openImage(image);
146
+ const info = imageInfo(buf);
147
+ console.log(`Image size: ${(info.totalSectors * 512).toLocaleString()} bytes (${info.totalSectors.toLocaleString()} sectors)`);
148
+ console.log(`Directory: ${info.usedEntries}/16 entries used, ${info.freeEntries} free`);
149
+ console.log(`Data sectors: ${info.usedDataSectors} used, ${info.freeDataSectors.toLocaleString()} free`);
150
+ console.log(`Next free sector: ${info.nextFreeSector}`);
151
+ });
152
+ program.parse();
153
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ export declare const SECTOR_SIZE = 512;
2
+ export declare const DIR_LBA = 0;
3
+ export declare const DATA_START = 1;
4
+ export declare const MAX_FILES = 16;
5
+ export declare const ENTRY_SIZE = 32;
6
+ export declare const NAME_OFFSET = 0;
7
+ export declare const NAME_LENGTH = 8;
8
+ export declare const EXT_OFFSET = 8;
9
+ export declare const EXT_LENGTH = 3;
10
+ export declare const FLAGS_OFFSET = 11;
11
+ export declare const START_OFFSET = 12;
12
+ export declare const FSIZE_OFFSET = 14;
13
+ export declare const RESERVED_OFFSET = 16;
14
+ export declare const RESERVED_LENGTH = 16;
15
+ export declare const FLAG_USED = 1;
@@ -0,0 +1,18 @@
1
+ export const SECTOR_SIZE = 512;
2
+ export const DIR_LBA = 0;
3
+ export const DATA_START = 1;
4
+ export const MAX_FILES = 16;
5
+ export const ENTRY_SIZE = 32;
6
+ // Field offsets within a directory entry
7
+ export const NAME_OFFSET = 0;
8
+ export const NAME_LENGTH = 8;
9
+ export const EXT_OFFSET = 8;
10
+ export const EXT_LENGTH = 3;
11
+ export const FLAGS_OFFSET = 11;
12
+ export const START_OFFSET = 12;
13
+ export const FSIZE_OFFSET = 14;
14
+ export const RESERVED_OFFSET = 16;
15
+ export const RESERVED_LENGTH = 16;
16
+ // Flags
17
+ export const FLAG_USED = 0x01;
18
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/lib/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAC/B,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,CAAC;AACzB,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC;AAC5B,MAAM,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC;AAC5B,MAAM,CAAC,MAAM,UAAU,GAAG,EAAE,CAAC;AAE7B,yCAAyC;AACzC,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;AAC7B,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;AAC7B,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC;AAC5B,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC;AAC5B,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,CAAC;AAC/B,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,CAAC;AAC/B,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,CAAC;AAC/B,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,CAAC;AAClC,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,CAAC;AAElC,QAAQ;AACR,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { DirEntry } from './types.js';
2
+ /**
3
+ * Parse all 16 directory entries from LBA 0.
4
+ */
5
+ export declare function readDirectory(buf: Buffer): DirEntry[];
6
+ /**
7
+ * Serialize directory entries back to LBA 0.
8
+ */
9
+ export declare function writeDirectory(buf: Buffer, entries: DirEntry[]): void;
10
+ /**
11
+ * Find a used directory entry by name and extension (space-padded, uppercase).
12
+ */
13
+ export declare function findFile(entries: DirEntry[], name: string, ext: string): DirEntry | undefined;
14
+ /**
15
+ * Find the first free (unused) directory slot index, or -1 if full.
16
+ */
17
+ export declare function findFreeSlot(entries: DirEntry[]): number;
18
+ /**
19
+ * Calculate the next free sector after all allocated files.
20
+ * Replicates BIOS FsCalcNextSec logic:
21
+ * sectors = highByte >> 1; if (highByte & 1 || lowByte != 0) sectors++
22
+ */
23
+ export declare function calcSectorCount(fileSize: number): number;
24
+ /**
25
+ * Calculate the next free sector for allocation.
26
+ * Returns max(startSector + sectorCount) across all used entries, minimum DATA_START.
27
+ */
28
+ export declare function calcNextSector(entries: DirEntry[]): number;
@@ -0,0 +1,85 @@
1
+ import { SECTOR_SIZE, DIR_LBA, MAX_FILES, ENTRY_SIZE, NAME_OFFSET, NAME_LENGTH, EXT_OFFSET, EXT_LENGTH, FLAGS_OFFSET, START_OFFSET, FSIZE_OFFSET, FLAG_USED, DATA_START, } from './constants.js';
2
+ import { readSector, writeSector } from './image.js';
3
+ /**
4
+ * Parse all 16 directory entries from LBA 0.
5
+ */
6
+ export function readDirectory(buf) {
7
+ const sector = readSector(buf, DIR_LBA);
8
+ const entries = [];
9
+ for (let i = 0; i < MAX_FILES; i++) {
10
+ const off = i * ENTRY_SIZE;
11
+ const name = sector.subarray(off + NAME_OFFSET, off + NAME_OFFSET + NAME_LENGTH).toString('ascii');
12
+ const ext = sector.subarray(off + EXT_OFFSET, off + EXT_OFFSET + EXT_LENGTH).toString('ascii');
13
+ const flags = sector[off + FLAGS_OFFSET];
14
+ const startSector = sector.readUInt16LE(off + START_OFFSET);
15
+ const fileSize = sector.readUInt16LE(off + FSIZE_OFFSET);
16
+ entries.push({ name, ext, flags, startSector, fileSize, index: i });
17
+ }
18
+ return entries;
19
+ }
20
+ /**
21
+ * Serialize directory entries back to LBA 0.
22
+ */
23
+ export function writeDirectory(buf, entries) {
24
+ const sector = Buffer.alloc(SECTOR_SIZE);
25
+ for (const entry of entries) {
26
+ const off = entry.index * ENTRY_SIZE;
27
+ // Write name (8 bytes, space-padded)
28
+ sector.write(entry.name.padEnd(NAME_LENGTH, ' '), off + NAME_OFFSET, NAME_LENGTH, 'ascii');
29
+ // Write extension (3 bytes, space-padded)
30
+ sector.write(entry.ext.padEnd(EXT_LENGTH, ' '), off + EXT_OFFSET, EXT_LENGTH, 'ascii');
31
+ // Flags
32
+ sector[off + FLAGS_OFFSET] = entry.flags;
33
+ // Start sector (u16 LE)
34
+ sector.writeUInt16LE(entry.startSector, off + START_OFFSET);
35
+ // File size (u16 LE)
36
+ sector.writeUInt16LE(entry.fileSize, off + FSIZE_OFFSET);
37
+ // Reserved bytes stay zeroed
38
+ }
39
+ writeSector(buf, DIR_LBA, sector);
40
+ }
41
+ /**
42
+ * Find a used directory entry by name and extension (space-padded, uppercase).
43
+ */
44
+ export function findFile(entries, name, ext) {
45
+ return entries.find((e) => (e.flags & FLAG_USED) !== 0 && e.name === name && e.ext === ext);
46
+ }
47
+ /**
48
+ * Find the first free (unused) directory slot index, or -1 if full.
49
+ */
50
+ export function findFreeSlot(entries) {
51
+ const entry = entries.find((e) => (e.flags & FLAG_USED) === 0);
52
+ return entry ? entry.index : -1;
53
+ }
54
+ /**
55
+ * Calculate the next free sector after all allocated files.
56
+ * Replicates BIOS FsCalcNextSec logic:
57
+ * sectors = highByte >> 1; if (highByte & 1 || lowByte != 0) sectors++
58
+ */
59
+ export function calcSectorCount(fileSize) {
60
+ const lowByte = fileSize & 0xFF;
61
+ const highByte = (fileSize >> 8) & 0xFF;
62
+ let sectors = highByte >> 1;
63
+ if ((highByte & 1) !== 0 || lowByte !== 0) {
64
+ sectors++;
65
+ }
66
+ return sectors;
67
+ }
68
+ /**
69
+ * Calculate the next free sector for allocation.
70
+ * Returns max(startSector + sectorCount) across all used entries, minimum DATA_START.
71
+ */
72
+ export function calcNextSector(entries) {
73
+ let next = DATA_START;
74
+ for (const e of entries) {
75
+ if ((e.flags & FLAG_USED) === 0)
76
+ continue;
77
+ const sectorCount = calcSectorCount(e.fileSize);
78
+ const end = e.startSector + sectorCount;
79
+ if (end > next) {
80
+ next = end;
81
+ }
82
+ }
83
+ return next;
84
+ }
85
+ //# sourceMappingURL=directory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directory.js","sourceRoot":"","sources":["../../src/lib/directory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { DirEntry } from './types.js';
2
+ /**
3
+ * Parse user input into 8.3 filename components.
4
+ * Splits on first dot, uppercases, space-pads to 8+3.
5
+ */
6
+ export declare function parseName(input: string): {
7
+ name: string;
8
+ ext: string;
9
+ };
10
+ /**
11
+ * Format a directory entry's name for display as NAME.EXT (trimmed).
12
+ */
13
+ export declare function formatName(entry: DirEntry): string;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Parse user input into 8.3 filename components.
3
+ * Splits on first dot, uppercases, space-pads to 8+3.
4
+ */
5
+ export function parseName(input) {
6
+ const dotIndex = input.indexOf('.');
7
+ let rawName;
8
+ let rawExt;
9
+ if (dotIndex === -1) {
10
+ rawName = input;
11
+ rawExt = '';
12
+ }
13
+ else {
14
+ rawName = input.substring(0, dotIndex);
15
+ rawExt = input.substring(dotIndex + 1);
16
+ }
17
+ rawName = rawName.toUpperCase();
18
+ rawExt = rawExt.toUpperCase();
19
+ if (rawName.length === 0 || rawName.length > 8) {
20
+ throw new Error(`Invalid filename: name part must be 1-8 characters, got "${rawName}"`);
21
+ }
22
+ if (rawExt.length > 3) {
23
+ throw new Error(`Invalid filename: extension must be 0-3 characters, got "${rawExt}"`);
24
+ }
25
+ // Validate ASCII printable (no spaces or special control chars in input)
26
+ const validChars = /^[A-Z0-9!#$%&'()\-@^_`{}~]+$/;
27
+ if (!validChars.test(rawName)) {
28
+ throw new Error(`Invalid filename: name contains invalid characters "${rawName}"`);
29
+ }
30
+ if (rawExt.length > 0 && !validChars.test(rawExt)) {
31
+ throw new Error(`Invalid filename: extension contains invalid characters "${rawExt}"`);
32
+ }
33
+ const name = rawName.padEnd(8, ' ');
34
+ const ext = rawExt.padEnd(3, ' ');
35
+ return { name, ext };
36
+ }
37
+ /**
38
+ * Format a directory entry's name for display as NAME.EXT (trimmed).
39
+ */
40
+ export function formatName(entry) {
41
+ const name = entry.name.trimEnd();
42
+ const ext = entry.ext.trimEnd();
43
+ if (ext.length === 0) {
44
+ return name;
45
+ }
46
+ return `${name}.${ext}`;
47
+ }
48
+ //# sourceMappingURL=filename.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filename.js","sourceRoot":"","sources":["../../src/lib/filename.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,OAAe,CAAC;IACpB,IAAI,MAAc,CAAC;IAEnB,IAAI,QAAQ,KAAK,CAAC,CAAC,EAAE,CAAC;QACpB,OAAO,GAAG,KAAK,CAAC;QAChB,MAAM,GAAG,EAAE,CAAC;IACd,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;QACvC,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC;IACzC,CAAC;IAED,OAAO,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAChC,MAAM,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;IAE9B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CAAC,4DAA4D,OAAO,GAAG,CAAC,CAAC;IAC1F,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,4DAA4D,MAAM,GAAG,CAAC,CAAC;IACzF,CAAC;IAED,yEAAyE;IACzE,MAAM,UAAU,GAAG,8BAA8B,CAAC;IAClD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,uDAAuD,OAAO,GAAG,CAAC,CAAC;IACrF,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,4DAA4D,MAAM,GAAG,CAAC,CAAC;IACzF,CAAC;IAED,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACpC,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAElC,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;AACvB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,KAAe;IACxC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;IAClC,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IAChC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,GAAG,IAAI,IAAI,GAAG,EAAE,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Read an entire image file into a Buffer.
3
+ */
4
+ export declare function openImage(path: string): Buffer;
5
+ /**
6
+ * Write a Buffer to disk as an image file.
7
+ */
8
+ export declare function saveImage(path: string, buf: Buffer): void;
9
+ /**
10
+ * Create a new zeroed image buffer with the given number of sectors.
11
+ */
12
+ export declare function createImage(totalSectors: number): Buffer;
13
+ /**
14
+ * Read a single sector (512 bytes) from the image buffer at the given LBA.
15
+ */
16
+ export declare function readSector(buf: Buffer, lba: number): Buffer;
17
+ /**
18
+ * Write 512 bytes of data to the image buffer at the given LBA.
19
+ */
20
+ export declare function writeSector(buf: Buffer, lba: number, data: Buffer): void;
@@ -0,0 +1,46 @@
1
+ import { readFileSync, writeFileSync } from 'node:fs';
2
+ import { SECTOR_SIZE } from './constants.js';
3
+ /**
4
+ * Read an entire image file into a Buffer.
5
+ */
6
+ export function openImage(path) {
7
+ return readFileSync(path);
8
+ }
9
+ /**
10
+ * Write a Buffer to disk as an image file.
11
+ */
12
+ export function saveImage(path, buf) {
13
+ writeFileSync(path, buf);
14
+ }
15
+ /**
16
+ * Create a new zeroed image buffer with the given number of sectors.
17
+ */
18
+ export function createImage(totalSectors) {
19
+ return Buffer.alloc(totalSectors * SECTOR_SIZE);
20
+ }
21
+ /**
22
+ * Read a single sector (512 bytes) from the image buffer at the given LBA.
23
+ */
24
+ export function readSector(buf, lba) {
25
+ const offset = lba * SECTOR_SIZE;
26
+ if (offset + SECTOR_SIZE > buf.length) {
27
+ throw new Error(`Sector ${lba} is out of bounds (image size: ${buf.length} bytes)`);
28
+ }
29
+ return Buffer.from(buf.subarray(offset, offset + SECTOR_SIZE));
30
+ }
31
+ /**
32
+ * Write 512 bytes of data to the image buffer at the given LBA.
33
+ */
34
+ export function writeSector(buf, lba, data) {
35
+ const offset = lba * SECTOR_SIZE;
36
+ if (offset + SECTOR_SIZE > buf.length) {
37
+ throw new Error(`Sector ${lba} is out of bounds (image size: ${buf.length} bytes)`);
38
+ }
39
+ if (data.length > SECTOR_SIZE) {
40
+ throw new Error(`Data exceeds sector size (${data.length} > ${SECTOR_SIZE})`);
41
+ }
42
+ // Zero the sector first, then copy data (handles data shorter than 512)
43
+ buf.fill(0, offset, offset + SECTOR_SIZE);
44
+ data.copy(buf, offset);
45
+ }
46
+ //# sourceMappingURL=image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.js","sourceRoot":"","sources":["../../src/lib/image.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,GAAW;IACjD,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AAC3B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,YAAoB;IAC9C,OAAO,MAAM,CAAC,KAAK,CAAC,YAAY,GAAG,WAAW,CAAC,CAAC;AAClD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,GAAW;IACjD,MAAM,MAAM,GAAG,GAAG,GAAG,WAAW,CAAC;IACjC,IAAI,MAAM,GAAG,WAAW,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,UAAU,GAAG,kCAAkC,GAAG,CAAC,MAAM,SAAS,CAAC,CAAC;IACtF,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC;AACjE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,GAAW,EAAE,IAAY;IAChE,MAAM,MAAM,GAAG,GAAG,GAAG,WAAW,CAAC;IACjC,IAAI,MAAM,GAAG,WAAW,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,UAAU,GAAG,kCAAkC,GAAG,CAAC,MAAM,SAAS,CAAC,CAAC;IACtF,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,GAAG,WAAW,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,CAAC,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC;IAChF,CAAC;IACD,wEAAwE;IACxE,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC;IAC1C,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;AACzB,CAAC"}
@@ -0,0 +1,36 @@
1
+ import { DirEntry } from './types.js';
2
+ /**
3
+ * Add a host file to the image. Optionally rename with targetName (8.3 format).
4
+ */
5
+ export declare function addFile(buf: Buffer, hostPath: string, targetName?: string): void;
6
+ /**
7
+ * Remove a file by name (clears flags only, no compaction — matches BIOS FsDeleteFile).
8
+ */
9
+ export declare function removeFile(buf: Buffer, nameInput: string): void;
10
+ /**
11
+ * List all in-use directory entries.
12
+ */
13
+ export declare function listFiles(buf: Buffer): DirEntry[];
14
+ /**
15
+ * Extract a file from the image to the host filesystem.
16
+ */
17
+ export declare function extractFile(buf: Buffer, nameInput: string, outputPath?: string): void;
18
+ /**
19
+ * Defragment the image: sort used entries by startSector, rewrite contiguously from LBA 1.
20
+ */
21
+ export declare function defragment(buf: Buffer): void;
22
+ /**
23
+ * Clear all directory entries (zero the directory sector).
24
+ */
25
+ export declare function clearImage(buf: Buffer): void;
26
+ /**
27
+ * Return image stats.
28
+ */
29
+ export declare function imageInfo(buf: Buffer): {
30
+ totalSectors: number;
31
+ usedEntries: number;
32
+ freeEntries: number;
33
+ nextFreeSector: number;
34
+ usedDataSectors: number;
35
+ freeDataSectors: number;
36
+ };