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 +21 -0
- package/README.md +86 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +153 -0
- package/dist/cli.js.map +1 -0
- package/dist/lib/constants.d.ts +15 -0
- package/dist/lib/constants.js +18 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/directory.d.ts +28 -0
- package/dist/lib/directory.js +85 -0
- package/dist/lib/directory.js.map +1 -0
- package/dist/lib/filename.d.ts +13 -0
- package/dist/lib/filename.js +48 -0
- package/dist/lib/filename.js.map +1 -0
- package/dist/lib/image.d.ts +20 -0
- package/dist/lib/image.js +46 -0
- package/dist/lib/image.js.map +1 -0
- package/dist/lib/operations.d.ts +36 -0
- package/dist/lib/operations.js +161 -0
- package/dist/lib/operations.js.map +1 -0
- package/dist/lib/types.d.ts +8 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/types.js.map +1 -0
- package/package.json +31 -0
- package/src/cli.ts +170 -0
- package/src/lib/constants.ts +19 -0
- package/src/lib/directory.ts +104 -0
- package/src/lib/filename.ts +55 -0
- package/src/lib/image.ts +50 -0
- package/src/lib/operations.ts +204 -0
- package/src/lib/types.ts +8 -0
- package/tsconfig.json +18 -0
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
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
|
package/dist/cli.js.map
ADDED
|
@@ -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
|
+
};
|