js-dos 7.4.9 → 8.1.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/.github/workflows/build.yml +51 -0
- package/.gitmodules +4 -0
- package/README.deployment.md +11 -0
- package/README.md +29 -89
- package/index.html +106 -0
- package/package.json +46 -71
- package/postcss.config.cjs +8 -0
- package/src/app.css +14 -0
- package/src/base.css +10 -0
- package/src/components/checkbox.tsx +41 -0
- package/src/components/close-button.tsx +17 -0
- package/src/components/components.css +28 -0
- package/src/components/dos-option-checkbox.tsx +94 -0
- package/src/components/dos-option-select.tsx +108 -0
- package/src/components/dos-option-slider.tsx +35 -0
- package/src/components/error.tsx +16 -0
- package/src/components/loading.tsx +26 -0
- package/src/components/lock.tsx +10 -0
- package/src/components/select.tsx +33 -0
- package/src/components/slider.tsx +69 -0
- package/src/download-file.ts +18 -0
- package/src/frame/account-frame.tsx +156 -0
- package/src/frame/editor/defaults.ts +419 -0
- package/src/frame/editor/editor-conf-frame.tsx +226 -0
- package/src/frame/editor/editor-frame.css +55 -0
- package/src/frame/editor/editor-fs-frame.tsx +314 -0
- package/src/frame/fat-drives-frame.tsx +135 -0
- package/src/frame/frame.css +119 -0
- package/src/frame/frame.tsx +34 -0
- package/src/frame/image-rendering-frame.tsx +8 -0
- package/src/frame/network-frame.tsx +157 -0
- package/src/frame/quick-save-frame.tsx +96 -0
- package/src/frame/settings-frame.tsx +14 -0
- package/src/frame/stats-frame.tsx +87 -0
- package/src/host/bundle-storage.ts +91 -0
- package/src/host/fullscreen.ts +35 -0
- package/src/host/lcache.ts +203 -0
- package/src/host/lstorage.ts +156 -0
- package/src/i18n.ts +179 -0
- package/src/index.css +7 -0
- package/src/load.ts +132 -0
- package/src/login/login.css +9 -0
- package/src/login/login.tsx +77 -0
- package/src/main.tsx +197 -0
- package/src/public/types.ts +41 -0
- package/src/sidebar/account-button.tsx +33 -0
- package/src/sidebar/cloud-save-button.tsx +89 -0
- package/src/sidebar/diskette-icon.tsx +14 -0
- package/src/sidebar/fullscreen-button.tsx +44 -0
- package/src/sidebar/network-button.tsx +39 -0
- package/src/sidebar/sidebar-button.tsx +244 -0
- package/src/sidebar/sidebar.css +47 -0
- package/src/sidebar/sidebar.tsx +30 -0
- package/src/sockdrive/LICENSE +674 -0
- package/src/sockdrive/README.md +45 -0
- package/src/sockdrive/include/sockdrive.h +23 -0
- package/src/sockdrive/js/.eslintrc.json +43 -0
- package/src/sockdrive/js/package.json +29 -0
- package/src/sockdrive/js/src/fatfs/chains.js +318 -0
- package/src/sockdrive/js/src/fatfs/dir.js +385 -0
- package/src/sockdrive/js/src/fatfs/helpers.js +210 -0
- package/src/sockdrive/js/src/fatfs/index.js +606 -0
- package/src/sockdrive/js/src/fatfs/structs.js +255 -0
- package/src/sockdrive/js/src/fatfs/vol.js +166 -0
- package/src/sockdrive/js/src/sockdrive/cache.ts +133 -0
- package/src/sockdrive/js/src/sockdrive/drive.ts +457 -0
- package/src/sockdrive/js/src/sockdrive/lru.ts +221 -0
- package/src/sockdrive/js/src/sockdrive/types.ts +14 -0
- package/src/sockdrive/js/src/sockdrive-fat.d.ts +64 -0
- package/src/sockdrive/js/src/sockdrive-fat.ts +226 -0
- package/src/sockdrive/js/src/sockdrive-native.ts +100 -0
- package/src/sockdrive/js/src/test/test.ts +288 -0
- package/src/sockdrive/js/tsconfig.json +11 -0
- package/src/sockdrive/js/webpack.config.js +68 -0
- package/src/sockdrive/js/yarn.lock +1808 -0
- package/src/store/auth.ts +115 -0
- package/src/store/dos.ts +305 -0
- package/src/store/editor.ts +61 -0
- package/src/store/init.ts +48 -0
- package/src/store/storage.ts +32 -0
- package/src/store/ui.ts +186 -0
- package/src/store.tsx +108 -0
- package/src/ui.tsx +104 -0
- package/src/v8/changes.ts +56 -0
- package/src/v8/config.ts +18 -0
- package/src/v8/subscriptions.ts +62 -0
- package/src/vite-env.d.ts +2 -0
- package/src/window/dos/controls/keyboard.ts +32 -0
- package/src/window/dos/controls/keys.ts +331 -0
- package/src/window/dos/controls/mouse/mount.ts +99 -0
- package/src/window/dos/controls/mouse/mouse-locked.ts +51 -0
- package/src/window/dos/controls/mouse/mouse-not-locked.ts +84 -0
- package/src/window/dos/controls/mouse/mouse-swipe.ts +57 -0
- package/src/window/dos/controls/mouse/pointer.ts +85 -0
- package/src/window/dos/controls/mouse.ts +21 -0
- package/src/window/dos/dos-dhry2.tsx +191 -0
- package/src/window/dos/dos-runtime.tsx +214 -0
- package/src/window/dos/dos-window.tsx +84 -0
- package/src/window/dos/render/canvas.ts +65 -0
- package/src/window/dos/render/resize.ts +31 -0
- package/src/window/dos/render/webgl.ts +173 -0
- package/src/window/dos/sound/audio-node.ts +148 -0
- package/src/window/error-window.tsx +9 -0
- package/src/window/loading-window.tsx +54 -0
- package/src/window/prerun-window.tsx +123 -0
- package/src/window/select-window.tsx +118 -0
- package/src/window/window.css +78 -0
- package/src/window/window.tsx +49 -0
- package/src/ws/ws-sockdrive.ts +101 -0
- package/src/ws/ws-transport-layer.ts +455 -0
- package/tailwind.config.cjs +48 -0
- package/tsconfig.json +32 -0
- package/tsconfig.node.json +9 -0
- package/vite.config.ts +22 -0
- package/LICENSE +0 -339
- package/dist/emulators-ui-loader.png +0 -0
- package/dist/js-dos.css +0 -1690
- package/dist/js-dos.js +0 -17
- package/dist/js-dos.js.map +0 -1
- package/dist/types/src/backend/v7/latency.d.ts +0 -8
- package/dist/types/src/backend/v7/personal.d.ts +0 -2
- package/dist/types/src/backend/v7/v7-config.d.ts +0 -16
- package/dist/types/src/components/action-bar.d.ts +0 -2
- package/dist/types/src/components/action-hide.d.ts +0 -4
- package/dist/types/src/components/action-save-or-exit.d.ts +0 -4
- package/dist/types/src/components/client.d.ts +0 -6
- package/dist/types/src/components/controls.d.ts +0 -8
- package/dist/types/src/components/cpu-control.d.ts +0 -2
- package/dist/types/src/components/horizontal-slider.d.ts +0 -13
- package/dist/types/src/components/region.d.ts +0 -6
- package/dist/types/src/components/scale-control.d.ts +0 -3
- package/dist/types/src/components/sensitivity-control.d.ts +0 -3
- package/dist/types/src/components/sidebar/latency-info.d.ts +0 -7
- package/dist/types/src/components/sidebar/main.d.ts +0 -2
- package/dist/types/src/components/sidebar/networking.d.ts +0 -2
- package/dist/types/src/components/sidebar/token/token-add-time.d.ts +0 -2
- package/dist/types/src/components/sidebar/token/token-select.d.ts +0 -2
- package/dist/types/src/components/sidebar/token/token.d.ts +0 -17
- package/dist/types/src/components/sidebar.d.ts +0 -2
- package/dist/types/src/components/sync-control.d.ts +0 -2
- package/dist/types/src/components/tip.d.ts +0 -2
- package/dist/types/src/components/vertical-slider.d.ts +0 -12
- package/dist/types/src/components/volume-control.d.ts +0 -3
- package/dist/types/src/dom.d.ts +0 -8
- package/dist/types/src/hardware-transport-layer.d.ts +0 -18
- package/dist/types/src/icons.d.ts +0 -95
- package/dist/types/src/player-app.d.ts +0 -50
- package/dist/types/src/player.d.ts +0 -29
- package/dist/types/src/request.d.ts +0 -1
- package/dist/types/src/xhr.d.ts +0 -6
- package/dist/types/v7-services/src/personal.d.ts +0 -3
- package/dist/wdosbox.js +0 -55
- package/dist/wdosbox.js.symbols +0 -3174
- package/dist/wdosbox.wasm +0 -0
- package/examples/digger/index.html +0 -71
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// see http://staff.washington.edu/dittrich/misc/fatgen103.pdf
|
|
2
|
+
// and http://www.cse.scu.edu/~tschwarz/COEN252_09/Lectures/FAT.html
|
|
3
|
+
|
|
4
|
+
const _ = require("struct-fu");
|
|
5
|
+
const __ = require("./helpers.js");
|
|
6
|
+
|
|
7
|
+
const bootBase = _.struct([
|
|
8
|
+
_.byte("jmpBoot", 3),
|
|
9
|
+
_.char("OEMName", 8),
|
|
10
|
+
_.uint16le("BytsPerSec"),
|
|
11
|
+
_.uint8("SecPerClus"),
|
|
12
|
+
_.uint16le("ResvdSecCnt"), // Rsvd in table, but Resvd in calcs…
|
|
13
|
+
_.uint8("NumFATs"),
|
|
14
|
+
_.uint16le("RootEntCnt"),
|
|
15
|
+
_.uint16le("TotSec16"),
|
|
16
|
+
_.uint8("Media"),
|
|
17
|
+
_.uint16le("FATSz16"),
|
|
18
|
+
_.uint16le("SecPerTrk"),
|
|
19
|
+
_.uint16le("NumHeads"),
|
|
20
|
+
_.uint32le("HiddSec"),
|
|
21
|
+
_.uint32le("TotSec32"),
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
const bootInfo = _.struct([
|
|
25
|
+
_.uint8("DrvNum"),
|
|
26
|
+
_.uint8("Reserved1"),
|
|
27
|
+
_.uint8("BootSig"),
|
|
28
|
+
_.uint32le("VolID"),
|
|
29
|
+
_.char("VolLab", 11),
|
|
30
|
+
_.char("FilSysType", 8),
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
exports.boot16 = _.struct([
|
|
34
|
+
bootBase,
|
|
35
|
+
bootInfo,
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
exports.boot32 = _.struct([
|
|
39
|
+
bootBase,
|
|
40
|
+
_.uint32le("FATSz32"),
|
|
41
|
+
_.struct("ExtFlags", [
|
|
42
|
+
_.ubit("NumActiveFAT", 4),
|
|
43
|
+
_.ubit("_reserved1", 3),
|
|
44
|
+
_.bool("MirroredFAT"),
|
|
45
|
+
_.ubit("_reserved2", 8),
|
|
46
|
+
].reverse()),
|
|
47
|
+
_.struct("FSVer", [
|
|
48
|
+
_.uint8("Major"),
|
|
49
|
+
_.uint8("Minor"),
|
|
50
|
+
]),
|
|
51
|
+
_.uint32le("RootClus"),
|
|
52
|
+
_.uint16le("FSInfo"),
|
|
53
|
+
_.uint16le("BkBootSec"),
|
|
54
|
+
_.byte("Reserved", 12),
|
|
55
|
+
bootInfo,
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
const _time = _.struct([
|
|
60
|
+
_.ubit("hours", 5),
|
|
61
|
+
_.ubit("minutes", 6),
|
|
62
|
+
_.ubit("seconds_2", 5),
|
|
63
|
+
]); const time = {
|
|
64
|
+
valueFromBytes: function(buf, off) {
|
|
65
|
+
off || (off = { bytes: 0 });
|
|
66
|
+
|
|
67
|
+
const _buf = __.bufferFrom([buf[off.bytes+1], buf[off.bytes+0]]);
|
|
68
|
+
const val = _time.valueFromBytes(_buf);
|
|
69
|
+
off.bytes += this.size;
|
|
70
|
+
return val;
|
|
71
|
+
},
|
|
72
|
+
bytesFromValue: function(val, buf, off) {
|
|
73
|
+
val || (val = { hours: 0, minutes: 0, seconds_2: 0 });
|
|
74
|
+
buf || (buf = __.allocBuffer(this.size));
|
|
75
|
+
off || (off = { bytes: 0 });
|
|
76
|
+
|
|
77
|
+
const _buf = _time.bytesFromValue(val);
|
|
78
|
+
buf[off.bytes+1] = _buf[0];
|
|
79
|
+
buf[off.bytes+0] = _buf[1];
|
|
80
|
+
off.bytes += this.size;
|
|
81
|
+
return buf;
|
|
82
|
+
},
|
|
83
|
+
size: _time.size,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const _date = _.struct([
|
|
87
|
+
_.ubit("year", 7),
|
|
88
|
+
_.ubit("month", 4),
|
|
89
|
+
_.ubit("day", 5),
|
|
90
|
+
]); const date = {
|
|
91
|
+
valueFromBytes: function(buf, off) {
|
|
92
|
+
off || (off = { bytes: 0 });
|
|
93
|
+
|
|
94
|
+
const _buf = __.bufferFrom([buf[off.bytes+1], buf[off.bytes+0]]);
|
|
95
|
+
const val = _date.valueFromBytes(_buf);
|
|
96
|
+
off.bytes += this.size;
|
|
97
|
+
return val;
|
|
98
|
+
},
|
|
99
|
+
bytesFromValue: function(val, buf, off) {
|
|
100
|
+
val || (val = { year: 0, month: 0, day: 0 });
|
|
101
|
+
buf || (buf = __.allocBuffer(this.size));
|
|
102
|
+
off || (off = { bytes: 0 });
|
|
103
|
+
|
|
104
|
+
const _buf = _date.bytesFromValue(val);
|
|
105
|
+
buf[off.bytes+1] = _buf[0];
|
|
106
|
+
buf[off.bytes+0] = _buf[1];
|
|
107
|
+
off.bytes += this.size;
|
|
108
|
+
return buf;
|
|
109
|
+
},
|
|
110
|
+
size: _date.size,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
exports.dirEntry = _.struct([
|
|
114
|
+
_.struct("Name", [
|
|
115
|
+
_.char("filename", 8),
|
|
116
|
+
_.char("extension", 3),
|
|
117
|
+
]),
|
|
118
|
+
_.struct("Attr", [
|
|
119
|
+
_.bool("readonly"),
|
|
120
|
+
_.bool("hidden"),
|
|
121
|
+
_.bool("system"),
|
|
122
|
+
_.bool("volume_id"),
|
|
123
|
+
_.bool("directory"),
|
|
124
|
+
_.bool("archive"),
|
|
125
|
+
_.ubit("reserved", 2),
|
|
126
|
+
].reverse()),
|
|
127
|
+
_.byte("NTRes", 1),
|
|
128
|
+
_.uint8("CrtTimeTenth"),
|
|
129
|
+
_.struct("CrtTime", [time]),
|
|
130
|
+
_.struct("CrtDate", [date]),
|
|
131
|
+
_.struct("LstAccDate", [date]),
|
|
132
|
+
_.uint16le("FstClusHI"),
|
|
133
|
+
_.struct("WrtTime", [time]),
|
|
134
|
+
_.struct("WrtDate", [date]),
|
|
135
|
+
_.uint16le("FstClusLO"),
|
|
136
|
+
_.uint32le("FileSize"),
|
|
137
|
+
]);
|
|
138
|
+
exports.entryDoneFlag = 0x00;
|
|
139
|
+
exports.entryFreeFlag = 0xE5;
|
|
140
|
+
exports.entryIsE5Flag = 0x05;
|
|
141
|
+
|
|
142
|
+
exports.dirEntry_simple = _.struct([
|
|
143
|
+
_.struct("Name", [
|
|
144
|
+
_.char("filename", 8),
|
|
145
|
+
_.char("extension", 3),
|
|
146
|
+
]),
|
|
147
|
+
_.padTo(exports.dirEntry.size),
|
|
148
|
+
/*
|
|
149
|
+
_.uint8('Attr_raw'),
|
|
150
|
+
_.byte('NTRes', 1),
|
|
151
|
+
_.byte('Crt_raw', 1+2+2),
|
|
152
|
+
_.byte('Lst_raw', 2),
|
|
153
|
+
_.uint16le('FstClusHI'),
|
|
154
|
+
_.byte('Wrt_raw', 2+2),
|
|
155
|
+
_.uint16le('FstClusLO'),
|
|
156
|
+
_.uint32le('FileSize')
|
|
157
|
+
*/
|
|
158
|
+
]);
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
exports.lastLongFlag = 0x40;
|
|
162
|
+
exports.longDirFlag = 0x0F;
|
|
163
|
+
exports.longDirEntry = _.struct([
|
|
164
|
+
_.uint8("Ord"),
|
|
165
|
+
_.char16le("Name1", 10),
|
|
166
|
+
_.uint8("Attr_raw"),
|
|
167
|
+
_.uint8("Type"),
|
|
168
|
+
_.uint8("Chksum"),
|
|
169
|
+
_.char16le("Name2", 12),
|
|
170
|
+
_.uint16le("FstClusLO"),
|
|
171
|
+
_.char16le("Name3", 4),
|
|
172
|
+
]);
|
|
173
|
+
|
|
174
|
+
if (exports.longDirEntry.size !== exports.dirEntry.size) throw Error("Structs ain't right!");
|
|
175
|
+
|
|
176
|
+
exports.fatField = {
|
|
177
|
+
"fat12": _.struct("Status", [
|
|
178
|
+
_.ubit("field0bc", 8),
|
|
179
|
+
_.ubit("field1c", 4),
|
|
180
|
+
_.ubit("field0a", 4),
|
|
181
|
+
_.ubit("field1ab", 8),
|
|
182
|
+
]),
|
|
183
|
+
"fat16": _.uint16le("Status"),
|
|
184
|
+
"fat32": _.uint32le("Status"), // more properly this 4 bits reserved + uint28le
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
exports.fatPrefix = {
|
|
188
|
+
"fat12": 0xF00,
|
|
189
|
+
"fat16": 0xFF00,
|
|
190
|
+
"fat32": 0x0FFFFF00,
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
exports.fatStat = {
|
|
194
|
+
free: 0x00,
|
|
195
|
+
_undef: 0x01,
|
|
196
|
+
rsvMin: 0xF0,
|
|
197
|
+
bad: 0xF7,
|
|
198
|
+
eofMin: 0xF8,
|
|
199
|
+
eof: 0xFF,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
exports._I = {
|
|
203
|
+
RUSR: 0o400,
|
|
204
|
+
WUSR: 0o200,
|
|
205
|
+
XUSR: 0o100,
|
|
206
|
+
|
|
207
|
+
RGRP: 0o040,
|
|
208
|
+
WGRP: 0o020,
|
|
209
|
+
XGRP: 0o010,
|
|
210
|
+
|
|
211
|
+
ROTH: 0o004,
|
|
212
|
+
WOTH: 0o002,
|
|
213
|
+
XOTH: 0o001,
|
|
214
|
+
|
|
215
|
+
SUID: 0o4000,
|
|
216
|
+
SGID: 0o2000,
|
|
217
|
+
SVTX: 0o1000,
|
|
218
|
+
|
|
219
|
+
FDIR: 0o40000,
|
|
220
|
+
FREG: 0o100000,
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
exports._I.RWXU = exports._I.RUSR | exports._I.WUSR | exports._I.XUSR;
|
|
224
|
+
exports._I.RWXG = exports._I.RGRP | exports._I.WGRP | exports._I.XGRP;
|
|
225
|
+
exports._I.RWXO = exports._I.ROTH | exports._I.WOTH | exports._I.XOTH;
|
|
226
|
+
exports._I._sss = exports._I.SUID | exports._I.SGID | exports._I.SVTX;
|
|
227
|
+
exports._I._chmoddable = exports._I.RWXU | exports._I.RWXG | exports._I.RWXO | exports._I._sss;
|
|
228
|
+
|
|
229
|
+
const _errors = {
|
|
230
|
+
IO: "Input/output error",
|
|
231
|
+
NOENT: "No such file or directory",
|
|
232
|
+
INVAL: "Invalid argument",
|
|
233
|
+
EXIST: "File exists",
|
|
234
|
+
NAMETOOLONG: "Filename too long",
|
|
235
|
+
NOSPC: "No space left on device",
|
|
236
|
+
NOSYS: "Function not supported",
|
|
237
|
+
ROFS: "ROFLCopter file system",
|
|
238
|
+
NOTDIR: "Not a directory",
|
|
239
|
+
BADF: "Bad file descriptor",
|
|
240
|
+
EXIST: "File exists",
|
|
241
|
+
ISDIR: "Is a directory",
|
|
242
|
+
ACCES: "Permission denied",
|
|
243
|
+
NOSYS: "Function not implemented",
|
|
244
|
+
_TODO: "Not implemented yet!",
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
exports.err = {};
|
|
248
|
+
Object.keys(_errors).forEach(function(sym) {
|
|
249
|
+
const msg = _errors[sym];
|
|
250
|
+
exports.err[sym] = function() {
|
|
251
|
+
const e = new Error(msg);
|
|
252
|
+
e.code = sym;
|
|
253
|
+
return e;
|
|
254
|
+
};
|
|
255
|
+
});
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
const S = require("./structs.js");
|
|
2
|
+
const c = require("./chains.js");
|
|
3
|
+
const _ = require("./helpers.js");
|
|
4
|
+
|
|
5
|
+
exports.init = function(volume, opts, bootSector) {
|
|
6
|
+
if (bootSector[510] !== 0x55 || bootSector[511] !== 0xAA) throw Error("Invalid volume signature!");
|
|
7
|
+
|
|
8
|
+
const isFAT16 = bootSector.readUInt16LE(S.boot16.fields["FATSz16"].offset);
|
|
9
|
+
const bootStruct = (isFAT16) ? S.boot16 : S.boot32;
|
|
10
|
+
const BS = bootStruct.valueFromBytes(bootSector);
|
|
11
|
+
_.log(_.log.DBG, "Boot sector info:", BS);
|
|
12
|
+
bootSector = null; // allow GC
|
|
13
|
+
if (!BS.BytsPerSec) throw Error("This looks like an ExFAT volume! (unsupported)");
|
|
14
|
+
else if (BS.BytsPerSec !== volume.sectorSize) throw Error("Sector size mismatch with FAT table.");
|
|
15
|
+
|
|
16
|
+
const FATSz = (isFAT16) ? BS.FATSz16 : BS.FATSz32;
|
|
17
|
+
const rootDirSectors = Math.ceil((BS.RootEntCnt * 32) / BS.BytsPerSec);
|
|
18
|
+
const firstDataSector = BS.ResvdSecCnt + (BS.NumFATs * FATSz) + rootDirSectors;
|
|
19
|
+
const totSec = (BS.TotSec16) ? BS.TotSec16 : BS.TotSec32;
|
|
20
|
+
const dataSec = totSec - firstDataSector;
|
|
21
|
+
const countofClusters = Math.floor(dataSec / BS.SecPerClus);
|
|
22
|
+
// avoid corrupting sectors from other partitions or whatnot
|
|
23
|
+
if (totSec > volume.numSectors) throw Error("Volume size mismatch!");
|
|
24
|
+
|
|
25
|
+
let fatType;
|
|
26
|
+
if (countofClusters < 4085) {
|
|
27
|
+
fatType = "fat12";
|
|
28
|
+
} else if (countofClusters < 65525) {
|
|
29
|
+
fatType = "fat16";
|
|
30
|
+
} else {
|
|
31
|
+
fatType = "fat32";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_.log(_.log.DBG, "rootDirSectors", rootDirSectors, "firstDataSector", firstDataSector, "countofClusters", countofClusters, "=>", fatType);
|
|
35
|
+
|
|
36
|
+
const vol = {};
|
|
37
|
+
|
|
38
|
+
vol.opts = opts;
|
|
39
|
+
|
|
40
|
+
vol._sectorSize = BS.BytsPerSec;
|
|
41
|
+
vol._sectorsPerCluster = BS.SecPerClus;
|
|
42
|
+
vol._firstSectorOfCluster = function(n) {
|
|
43
|
+
return firstDataSector + (n-2)*vol._sectorsPerCluster;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
vol._readSectors = function(secNum, dest, cb) {
|
|
47
|
+
if (typeof dest === "function") {
|
|
48
|
+
cb = dest;
|
|
49
|
+
dest = _.allocBuffer(vol._sectorSize);
|
|
50
|
+
}
|
|
51
|
+
_.log(_.log.DBG, "vol._readSectors", secNum, dest.length);
|
|
52
|
+
if (secNum < volume.numSectors) {
|
|
53
|
+
volume.readSectors(secNum, dest, function(e) {
|
|
54
|
+
cb(e, dest);
|
|
55
|
+
});
|
|
56
|
+
} else throw Error("Invalid sector number!");
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
vol._writeSectors = function(secNum, data, cb) {
|
|
60
|
+
_.log(_.log.DBG, "vol._writeSectors", secNum, data.length);
|
|
61
|
+
// NOTE: these are internal assertions, public API will get proper `S.err`s
|
|
62
|
+
if (data.length % volume.sectorSize) throw Error("Buffer length not a multiple of sector size");
|
|
63
|
+
else if (opts.ro) throw Error("Read-only filesystem");
|
|
64
|
+
else if (secNum < volume.numSectors) volume.writeSectors(secNum, data, cb);
|
|
65
|
+
else throw Error("Invalid sector number!");
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
function fatInfoForCluster(n) {
|
|
69
|
+
const entryStruct = S.fatField[fatType];
|
|
70
|
+
const FATOffset = (fatType === "fat12") ? Math.floor(n/2) * entryStruct.size : n * entryStruct.size;
|
|
71
|
+
const SecNum = BS.ResvdSecCnt + Math.floor(FATOffset / BS.BytsPerSec);
|
|
72
|
+
const EntOffset = FATOffset % BS.BytsPerSec;
|
|
73
|
+
return { sector: SecNum-BS.ResvdSecCnt, offset: EntOffset, struct: entryStruct };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// TODO: all this FAT manipulation is crazy inefficient! needs read caching *and* write caching
|
|
77
|
+
// …the best place for cache might be in `volume` handler, though. add a `flush` method to that spec?
|
|
78
|
+
// TODO: how should we handle redundant FATs? mirror every write? just ignore completely? copy-on-eject?
|
|
79
|
+
|
|
80
|
+
const fatChain = c.sectorChain(vol, BS.ResvdSecCnt, FATSz);
|
|
81
|
+
fatChain.cacheAdvice = "RANDOM";
|
|
82
|
+
|
|
83
|
+
vol.fetchFromFAT = function(clusterNum, cb) {
|
|
84
|
+
const info = fatInfoForCluster(clusterNum);
|
|
85
|
+
fatChain.readFromPosition(info, info.struct.size, function(e, n, d) {
|
|
86
|
+
if (e) return cb(e);
|
|
87
|
+
let status = info.struct.valueFromBytes(d); var prefix;
|
|
88
|
+
if (fatType === "fat12") {
|
|
89
|
+
if (clusterNum % 2) {
|
|
90
|
+
status = (status.field1ab << 4) + status.field1c;
|
|
91
|
+
} else {
|
|
92
|
+
status = (status.field0a << 8) + status.field0bc;
|
|
93
|
+
}
|
|
94
|
+
} else if (fatType === "fat32") {
|
|
95
|
+
status &= 0x0FFFFFFF;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
var prefix = S.fatPrefix[fatType];
|
|
99
|
+
if (status === S.fatStat.free) cb(null, "free");
|
|
100
|
+
else if (status === S.fatStat._undef) cb(null, "-invalid-");
|
|
101
|
+
else if (status > prefix+S.fatStat.eofMin) cb(null, "eof");
|
|
102
|
+
else if (status === prefix+S.fatStat.bad) cb(null, "bad");
|
|
103
|
+
else if (status > prefix+S.fatStat.rsvMin) cb(null, "reserved");
|
|
104
|
+
else cb(null, status);
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
vol.storeToFAT = function(clusterNum, status, cb) {
|
|
109
|
+
if (typeof status === "string") {
|
|
110
|
+
status = S.fatStat[status];
|
|
111
|
+
status += S.fatPrefix[fatType];
|
|
112
|
+
}
|
|
113
|
+
const info = fatInfoForCluster(clusterNum);
|
|
114
|
+
// TODO: technically fat32 needs to *preserve* the high 4 bits
|
|
115
|
+
if (fatType === "fat12") {
|
|
116
|
+
fatChain.readFromPosition(info, info.struct.size, function(e, n, d) {
|
|
117
|
+
const value = info.struct.valueFromBytes(d);
|
|
118
|
+
if (clusterNum % 2) {
|
|
119
|
+
value.field1ab = status >>> 4;
|
|
120
|
+
value.field1c = status & 0x0F;
|
|
121
|
+
} else {
|
|
122
|
+
value.field0a = status >>> 8;
|
|
123
|
+
value.field0bc = status & 0xFF;
|
|
124
|
+
}
|
|
125
|
+
const entry = info.struct.bytesFromValue(value);
|
|
126
|
+
fatChain.writeToPosition(info, entry, cb);
|
|
127
|
+
});
|
|
128
|
+
} else {
|
|
129
|
+
const entry = info.struct.bytesFromValue(status);
|
|
130
|
+
fatChain.writeToPosition(info, entry, cb);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
vol.allocateInFAT = function(hint, cb) {
|
|
135
|
+
if (typeof hint === "function") {
|
|
136
|
+
cb = hint;
|
|
137
|
+
hint = 2; // TODO: cache a better starting point?
|
|
138
|
+
}
|
|
139
|
+
function searchForFreeCluster(num, cb) {
|
|
140
|
+
if (num < countofClusters) {
|
|
141
|
+
vol.fetchFromFAT(num, function(e, status) {
|
|
142
|
+
if (e) cb(e);
|
|
143
|
+
else if (status === "free") cb(null, num);
|
|
144
|
+
else searchForFreeCluster(num+1, cb);
|
|
145
|
+
});
|
|
146
|
+
} else cb(S.err.NOSPC()); // TODO: try searching backwards from hint…
|
|
147
|
+
}
|
|
148
|
+
searchForFreeCluster(hint, function(e, clusterNum) {
|
|
149
|
+
if (e) cb(e);
|
|
150
|
+
else vol.storeToFAT(clusterNum, "eof", cb.bind(null, null, clusterNum));
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
vol.rootDirectoryChain = (isFAT16) ?
|
|
155
|
+
c.sectorChain(vol, firstDataSector - rootDirSectors, rootDirSectors) :
|
|
156
|
+
c.clusterChain(vol, BS.RootClus);
|
|
157
|
+
vol.rootDirectoryChain.cacheAdvice = "WILLNEED";
|
|
158
|
+
vol.chainForCluster = c.clusterChain.bind(c, vol);
|
|
159
|
+
vol.chainFromJSON = function(d) {
|
|
160
|
+
return ("numSectors" in d) ?
|
|
161
|
+
c.sectorChain(vol, d.firstSector, d.numSectors) :
|
|
162
|
+
c.clusterChain(vol, d.firstCluster);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
return vol;
|
|
166
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { LRUMap } from "./lru";
|
|
2
|
+
|
|
3
|
+
export interface Cache {
|
|
4
|
+
read(sector: number): Uint8Array | null;
|
|
5
|
+
write(sector: number, buffer: Uint8Array): boolean;
|
|
6
|
+
create(origin: number, buffer: Uint8Array): void;
|
|
7
|
+
getOrigin(sector: number): number;
|
|
8
|
+
memUsed(): number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class BlockCache implements Cache {
|
|
12
|
+
aheadRange: number;
|
|
13
|
+
sectorSize: number;
|
|
14
|
+
lru: LRUMap;
|
|
15
|
+
|
|
16
|
+
constructor(sectorSize: number, aheadRange: number, memoryLimit: number) {
|
|
17
|
+
this.aheadRange = aheadRange;
|
|
18
|
+
this.sectorSize = sectorSize;
|
|
19
|
+
this.lru = new LRUMap(Math.floor(memoryLimit / (aheadRange * sectorSize)));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public read(sector: number): Uint8Array | null {
|
|
23
|
+
const origin = this.getOrigin(sector);
|
|
24
|
+
const cached = this.lru.get(origin) as Uint8Array;
|
|
25
|
+
if (cached) {
|
|
26
|
+
const offset = sector - origin;
|
|
27
|
+
return cached.slice(offset * this.sectorSize, (offset + 1) * this.sectorSize);
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public write(sector: number, buffer: Uint8Array) {
|
|
33
|
+
const origin = this.getOrigin(sector);
|
|
34
|
+
const cached = this.lru.get(origin);
|
|
35
|
+
if (cached) {
|
|
36
|
+
cached.set(buffer, (sector - origin) * this.sectorSize);
|
|
37
|
+
}
|
|
38
|
+
return cached !== undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public create(origin: number, buffer: Uint8Array) {
|
|
42
|
+
this.lru.set(origin, buffer.slice(0));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public getOrigin(sector: number) {
|
|
46
|
+
return sector - sector % this.aheadRange;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public memUsed() {
|
|
50
|
+
return this.lru.size * this.aheadRange * this.sectorSize;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export class SimpleCache implements Cache {
|
|
55
|
+
aheadRange: number;
|
|
56
|
+
sectorSize: number;
|
|
57
|
+
lru: LRUMap;
|
|
58
|
+
|
|
59
|
+
constructor(sectorSize: number, aheadRange: number, memoryLimit: number) {
|
|
60
|
+
this.aheadRange = aheadRange;
|
|
61
|
+
this.sectorSize = sectorSize;
|
|
62
|
+
this.lru = new LRUMap(Math.floor(memoryLimit / sectorSize));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public read(sector: number): Uint8Array | null {
|
|
66
|
+
const cached = this.lru.get(sector) as Uint8Array;
|
|
67
|
+
if (cached) {
|
|
68
|
+
return cached;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public write(sector: number, buffer: Uint8Array) {
|
|
74
|
+
this.lru.set(sector, buffer);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public create(sector: number, buffer: Uint8Array) {
|
|
79
|
+
for (let i = 0; i < this.aheadRange; ++i) {
|
|
80
|
+
this.lru.set(sector + i, buffer.slice(i * this.sectorSize, (i + 1) * this.sectorSize));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public getOrigin(sector: number) {
|
|
85
|
+
return sector;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public memUsed() {
|
|
89
|
+
return this.lru.size * this.sectorSize;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export class BlockAndWriteCache implements Cache {
|
|
94
|
+
aheadRange: number;
|
|
95
|
+
sectorSize: number;
|
|
96
|
+
blockCache: BlockCache;
|
|
97
|
+
writeCache: SimpleCache;
|
|
98
|
+
|
|
99
|
+
constructor(sectorSize: number, aheadRange: number, memoryLimit: number) {
|
|
100
|
+
this.aheadRange = aheadRange;
|
|
101
|
+
this.sectorSize = sectorSize;
|
|
102
|
+
this.blockCache = new BlockCache(sectorSize, aheadRange, memoryLimit);
|
|
103
|
+
this.writeCache = new SimpleCache(sectorSize, aheadRange, Math.floor(memoryLimit / 10));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
read(sector: number) {
|
|
107
|
+
return this.blockCache.read(sector) || this.writeCache.read(sector);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
write(sector: number, buffer: Uint8Array) {
|
|
111
|
+
if (!this.blockCache.write(sector, buffer)) {
|
|
112
|
+
this.writeCache.write(sector, buffer);
|
|
113
|
+
}
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
create(origin: number, buffer: Uint8Array): void {
|
|
118
|
+
this.blockCache.create(origin, buffer);
|
|
119
|
+
for (let i = 0; i < this.aheadRange; ++i) {
|
|
120
|
+
if (this.writeCache.read(origin + i) != null) {
|
|
121
|
+
this.writeCache.write(origin + i, buffer.slice(i * this.sectorSize, (i + 1) * this.sectorSize));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
getOrigin(sector: number): number {
|
|
127
|
+
return this.blockCache.getOrigin(sector);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
memUsed(): number {
|
|
131
|
+
return this.blockCache.memUsed() + this.writeCache.memUsed();
|
|
132
|
+
}
|
|
133
|
+
}
|