-
Notifications
You must be signed in to change notification settings - Fork 1
/
sector-cpc.c
378 lines (302 loc) · 12.2 KB
/
sector-cpc.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
/*
General Information
The file structure is composed of several "layers", so to speak. The outermost
layer is the CPCEMU disk image format. CPCEMU is an Amstrad CPC emulator from
early 1990s, and specifies a disk format that emulates the interface of a
floppy drive such as NEC765 used in Amstrad CPC machines. Incidentally, it has
become a standard for emulators for the purpose of representing disk images on
other platforms. It's specification is defined in detail in the following
link:
http://www.benchmarko.de/cpcemu/cpcdoc/chapter/cpcdoc7_e.html#I_FILE_STRUCTURE
It contains information about the disk needed to emulate a floppy disk
controller such number of tracks, sectors.
Inside CPCEMU disk image, there is the actual data that is stored in the disk
as a long byte stream segmented into sectors and tracks. The first layer here
is the CP/M 2.2 disk file system. It is defined in Digital Research CP/M
Operating System Manual, and also explained in detail in Programmer's CPM
Handbook by Andy Johnson-Laird. CP/M defines where the file entries are
stored, and in what format. It divides the data into blocks, in CPC's case
1024 bytes in length, and treats a segment as 128 bytes. However these values
are not standard, and are defined as part of the BIOS in Directory Parameter
Block. Typically in the first 2 block, Directory Entry list is stored. It
lists information such as file name, file extension, user number, attributes,
file size in blocks, and Allocation table which points to the blocks where the
respective file is stored.
Amstrad CPC has a Disk Operation System in a ROM chip, called AMSDOS, and
specifies extra information for the filesystem inside CP/M layer. It is
described in the book SOFT158A DDI-1 Firmware by Locomotive Software in
chapter 3. It's primary purpose seems to be providing compatibility with the
casette interface, and defines information such as user name, file name, file
type (binary, basic file, ascii), file checksum, data length, etc.
CPCEMU Data Structure
- It is written and read from the .dsk file.
+---------------------+
| CPCEMU Disk Info | <- cpcemu_disk_info_s
+---------------------+
| CPCEMU Track 1 Info | <- cpcemu_track_info_s
+---------------------+
| Segment 1 | <- u8 *buffer
+---------------------+
| Segment 2 |
+---------------------+
| ... |
+---------------------+
| Segment 9 |
+---------------------+
| CPCEMU Track 2 Info |
+---------------------+
| ... |
+---------------------+
CPM Data Structure
- It is written and read from CPCEMU segments, and consists of a number of
Directory Entries, and immediately followed Data Blocks. Their size are
defined by DPB (Disk Parameter Block) in BIOS, DRM and BSH respectively.
+--------------------+
| Directory Entry 1 | <- cpm_diren_s
+--------------------+
| Directory Entry 2 |
+--------------------+
| ... |
+--------------------+
| Directory Entry 64 |
+--------------------+
| Data Block 1 | <- u8 *buffer
+--------------------+
| Data Block 2 |
+--------------------+
| ... |
+--------------------+
AMSDOS Data Structure
- It is a 128 byte header written and read from CP/M Data Blocks.
Code Structure
Every layer gets its own namespace. These layers are as mentioned before:
- CPCEMU: Low level disk access such as sector read, and write
functions. Prefixed with `cpcemu_`.
- CPM: CP/M file system structures, such as Directory Entry Block, and file
records. Prefixed with `cpm_`.
- AMSDOS: Amstrad CPC specific file system information similar to CPM. This
is not required when disks are accessed through CP/M. Prefixed with
`amsdos_`.
Improvement Notes
Although the primary purpose of this code is to emulate Amstrad CPC disk
images, aim for trying to keep it as parametrized and modular as possible,
potentially making it easy to support other disk image formats, and CP/M
versions on their own.
Resources
- http://www.benchmarko.de/cpcemu/cpcdoc/chapter/cpcdoc7_e.html
- https://www.seasip.info/Cpm/amsform.html
- https://www.seasip.info/Cpm/format22.html
*/
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "platformdef.h"
#include "types.h"
#include "cpm.h"
#include "cpcemu.h"
#define VERSION "0.2.1"
void print_usage_and_exit()
{
printf("Arguments:\n");
printf(" --file filename.dsk <command>\n");
printf(" --no-amsdos Do not add AMSDOS header.\n");
printf(" --text Treat file as text, and SUB byte as EOF marker. [0]\n");
printf("Options:\n");
printf(" <command>:\n");
printf(" new Create a new empty disk image.\n");
printf(" dir Lists contents of disk image.\n");
printf(" dump <file_name> Hexdump contents of file to standard output. [1]\n");
printf(" extract <file_name> Extract contents of file into host disk.\n");
printf(" insert <file_name> [<entry_addr>, <exec_addr>]\n"
" Insert file on host system into disk.\n");
printf(" del <file_name> Delete file from disk.\n");
printf(" info <file_name> [--tracks] Print info about file in disk.\n");
printf("\n");
printf("Notes:\n");
printf(" - [0] In CP/M 2.2 there is no way to distinguish if a file is text or binary. When\n"
" extracting file records of every 128 bytes, an ASCII file past SUB byte is garbage\n"
" as it signifies end of file. Use this flag when extracing text files.\n");
printf("\n");
printf(" - [1] <entry_addr> and <exec_addr> are in base 16, non-numeric characters will be ignored.\n"
" Also give space between the two."
" E.g. 0x8000, or &8000 and 8000h are valid.\n");
printf("\n");
printf("sector-cpc " VERSION " 2019\n");
exit(0);
}
struct args_s {
struct {
char *file_name;
int valid;
struct {
char *file_name;
int valid;
} extract;
struct {
char *file_name;
int valid;
} dump;
struct {
int valid;
} dir;
struct {
int valid;
} new;
struct {
char *file_name;
int tracks;
int valid;
} info;
struct {
char *file_name;
u16 entry_addr;
u16 exec_addr;
int valid;
} insert;
struct {
char *file_name;
int valid;
} del;
} file;
struct {
int valid;
} no_amsdos;
struct {
int valid;
} text;
struct {
int valid;
} version;
};
void parse_args(struct args_s *opts, int argc, char *argv[])
{
int i;
assert(opts);
memset(opts, 0, sizeof(struct args_s));
for (i = 0; i < argc; i++) {
if (strcmp(argv[i], "--file") == 0) {
if (i + 1 == argc) {
print_usage_and_exit();
}
opts->file.valid = 1;
opts->file.file_name = argv[i + 1];
}
if (strcmp(argv[i], "--no-amsdos") == 0) {
opts->no_amsdos.valid = 1;
}
if (strcmp(argv[i], "--text") == 0) {
opts->text.valid = 1;
}
if (opts->file.valid) {
if (strcmp(argv[i], "new") == 0) {
opts->file.new.valid = 1;
}
if (strcmp(argv[i], "dir") == 0) {
opts->file.dir.valid = 1;
}
if (strcmp(argv[i], "info") == 0) {
if (i + 1 == argc) {
print_usage_and_exit();
}
opts->file.info.valid = 1;
opts->file.info.file_name = argv[i + 1];
if (i + 2 != argc && strcmp("--tracks", argv[i + 2]) == 0) {
opts->file.info.tracks = 1;
}
}
if (strcmp(argv[i], "dump") == 0) {
if (i + 1 == argc) {
print_usage_and_exit();
}
opts->file.dump.valid = 1;
opts->file.dump.file_name = argv[i + 1];
}
if (strcmp(argv[i], "extract") == 0) {
if (i + 1 == argc) {
print_usage_and_exit();
}
opts->file.extract.valid = 1;
opts->file.extract.file_name = argv[i + 1];
}
if (strcmp(argv[i], "insert") == 0) {
if (i + 1 == argc) {
print_usage_and_exit();
}
opts->file.insert.valid = 1;
opts->file.insert.file_name = argv[i + 1];
if (i + 2 < argc) {
opts->file.insert.entry_addr = strtol(argv[i + 2], NULL, 16);
}
if (i + 3 < argc) {
opts->file.insert.exec_addr = strtol(argv[i + 3], NULL, 16);
}
}
if (strcmp(argv[i], "del") == 0) {
if (i + 1 == argc) {
print_usage_and_exit();
}
opts->file.del.valid = 1;
opts->file.del.file_name = argv[i + 1];
}
}
}
if (!opts->file.valid) {
print_usage_and_exit();
}
if (opts->file.valid
&& !opts->file.dump.valid
&& !opts->file.new.valid
&& !opts->file.dir.valid
&& !opts->file.info.valid
&& !opts->file.extract.valid
&& !opts->file.insert.valid
&& !opts->file.del.valid) {
print_usage_and_exit();
}
}
int main(int argc, char *argv[])
{
struct args_s opts;
parse_args(&opts, argc, argv);
if (opts.file.valid) {
FILE *fp;
char *fopen_flags = "rb+";
if (opts.file.new.valid) {
fopen_flags = "wb+";
}
fp = fopen(opts.file.file_name, fopen_flags);
if (!fp) {
fprintf(stderr, "Failed to open file %s.\n", opts.file.file_name);
exit(1);
}
if (opts.file.new.valid) {
cpm_new(fp);
}
cpm_init(fp);
if (opts.file.dir.valid) {
cpm_dir(fp);
}
if (opts.file.info.valid) {
cpm_info(fp, opts.file.info.file_name, opts.file.info.tracks);
}
if (opts.file.dump.valid) {
cpm_dump(fp, opts.file.dump.file_name, 0, 0);
}
if (opts.file.extract.valid) {
cpm_dump(fp, opts.file.extract.file_name, 1, opts.text.valid);
}
if (opts.file.insert.valid) {
cpm_insert(fp, opts.file.insert.file_name, opts.file.insert.entry_addr,
opts.file.insert.exec_addr, !opts.no_amsdos.valid);
}
if (opts.file.del.valid) {
if (cpm_del(fp, opts.file.del.file_name)) {
printf("%s is deleted.\n", opts.file.del.file_name);
}
}
fclose(fp);
}
return 0;
}