summaryrefslogtreecommitdiffstatshomepage
path: root/src/lib/formats/adam_dsk.cpp
blob: 78ae0761afb0a0ebed8aa708278d36a82f6d7b38 (plain) (blame)
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
// license:BSD-3-Clause
// copyright-holders:AJR
/***************************************************************************

    Coleco Adam disk image format (new implementation)

    The Adam's Elementary Operating System (EOS) sees all mass storage
    devices as sequences of 1024-byte blocks addressed linearly through the
    ADAMnet bus. However, these blocks are actually stored on 40-track or
    80-track floppy disks in two logically consecutive 512-byte sectors.
    Furthermore, logically consecutive sectors are physically non-adjacent
    due to interleaving implemented at the level of the FDC MCU (the
    geometry is transparent to the operating system), and different native
    interleaving schemes were devised depending on the number of sectors
    per track. However, .dsk images are stored using the original 8-sector
    interleave (1,6,3,8,5,2,7,4) regardless of their capacity. The 3.5-inch
    formats, which use 9 or 18 sectors per track, thus require a convoluted
    permutation of the sectors to transform their native interleave to or
    from the non-native interleave.

    Track description for 160KB SSDD format:
    100x4e 12x00 3xf5 fe 2x00 01 02 f7 22x4e 12x00 3xf5 fb 512xe5 f7
    100x4e 12x00 3xf5 fe 2x00 02 02 f7 22x4e 12x00 3xf5 fb 512xe5 f7
    100x4e 12x00 3xf5 fe 2x00 03 02 f7 22x4e 12x00 3xf5 fb 512xe5 f7
    100x4e 12x00 3xf5 fe 2x00 04 02 f7 22x4e 12x00 3xf5 fb 512xe5 f7
    100x4e 12x00 3xf5 fe 2x00 05 02 f7 22x4e 12x00 3xf5 fb 512xe5 f7
    100x4e 12x00 3xf5 fe 2x00 06 02 f7 22x4e 12x00 3xf5 fb 512xe5 f7
    100x4e 12x00 3xf5 fe 2x00 07 02 f7 22x4e 12x00 3xf5 fb 512xe5 f7
    100x4e 12x00 3xf5 fe 2x00 08 02 f7 22x4e 12x00 3xf5 fb 512xe5 f7
    859x4e

***************************************************************************/

#include "adam_dsk.h"

#include "ioprocs.h"

#include <cstring>
#include <memory>


namespace {

struct format_desc
{
	uint32_t size;
	uint32_t form_factor;
	uint32_t variant;
	uint32_t cell_size;
	uint8_t track_count;
	uint8_t sector_count;
	uint8_t head_count;
};

const format_desc s_formats[] =
{
	{ 163840, floppy_image::FF_525, floppy_image::SSDD, 2000, 40, 8, 1 },
	{ 327680, floppy_image::FF_525, floppy_image::DSDD, 2000, 40, 8, 2 },
	{ 737280, floppy_image::FF_35, floppy_image::DSDD, 2000, 80, 9, 2 },
	{ 1474560, floppy_image::FF_35, floppy_image::DSHD, 1000, 80, 18, 2 },
	{ 0, floppy_image::FF_UNKNOWN, 0, 0, 0, 0, 0 }
};

const uint8_t s_interleave_9s[9] =
{
	0, 4, 8, 3, 7, 2, 6, 1, 5
};

const uint8_t s_interleave_18s[18] =
{
	0, 4, 8, 12, 16, 2, 6, 10, 14,
	1, 5, 9, 13, 17, 3, 7, 11, 15
};

const uint8_t s_cpm_boot[8] =
{
	0x18, 0x01, 0xe5, 0x3e, 0xc9, 0x32, 0x66, 0x00
};

constexpr uint8_t ZZZZ = 0x40; // a "don't care" value

const uint8_t s_eos_directory[0x38] =
{
	0x55, 0xaa, 0x00, 0xff, ZZZZ, ZZZZ, ZZZZ, ZZZZ, ZZZZ, ZZZZ, ZZZZ, ZZZZ, ZZZZ, // magic numbers required by EOS
	'B',  'O',  'O',  'T',  0x03, ZZZZ, ZZZZ, ZZZZ, ZZZZ, ZZZZ, ZZZZ, ZZZZ,
	ZZZZ, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, ZZZZ, ZZZZ, ZZZZ, ZZZZ, ZZZZ, // covers block 0 and only block 0
	'D',  'I',  'R',  'E',  'C',  'T',  'O',  'R',  'Y',  0x03, ZZZZ, ZZZZ,
	ZZZZ, 0x01, 0x00, 0x00, 0x00 // begins with block 1
};

} // anonymous namespace


const adam_format FLOPPY_ADAM_FORMAT;

adam_format::adam_format()
{
}

const char *adam_format::name() const noexcept
{
	return "adam";
}

const char *adam_format::description() const noexcept
{
	return "Coleco Adam disk image";
}

const char *adam_format::extensions() const noexcept
{
	return "dsk";
}

bool adam_format::supports_save() const noexcept
{
	return true;
}

int adam_format::identify(util::random_read &io, uint32_t form_factor, const std::vector<uint32_t> &variants) const
{
	uint64_t size;
	if (io.length(size))
		return 0;

	// Size must match exactly
	const format_desc *f = &s_formats[0];
	while (f->size != size)
	{
		if (!(++f)->size)
			return 0;
	}

	// Form factor and variant must agree if provided
	if (form_factor != floppy_image::FF_UNKNOWN && form_factor != f->form_factor)
		return 0;
	if (!variants.empty() && !has_variant(variants, f->variant))
		return 0;

	// Size confirmed; now do a little extra work to confirm presence of Adam data
	// (since .dsk extension is shared by a lot of formats for other systems)

	// First, check for the beginning of a standalone bootloader commonly used on CP/M disks
	std::uint8_t buffer[0x38];
	auto const [err, actual] = read_at(io, 0, &buffer[0], 8);
	if (!err && actual == 8 && !std::memcmp(&buffer[0], s_cpm_boot, 8))
		return FIFID_SIZE | FIFID_HINT;

	// EOS bootloaders are varied enough that checking for their system calls is annoying inefficient
	// Instead, check in block 1 for the magic signature and the special BOOT and DIRECTORY entries
	// (which some CP/M disks also have as part of a fake directory structure)
	auto const [err2, actual2] = read_at(io, 0x40d, &buffer[0], 0x38);
	if (!err2 && actual2 == 0x38)
	{
		int i = 0;
		while (i < 0x38 && (s_eos_directory[i] == ZZZZ || s_eos_directory[i] == buffer[i]))
			i++;
		if (i == 0x38)
			return FIFID_SIZE | FIFID_HINT;
	}

	return FIFID_SIZE;
}

bool adam_format::load(util::random_read &io, uint32_t form_factor, const std::vector<uint32_t> &variants, floppy_image &image) const
{
	uint64_t size;
	if (io.length(size))
		return false;

	// Find the size that matches
	const format_desc *f = &s_formats[0];
	while (f->size != size)
	{
		if (!(++f)->size)
			return false;
	}

	// Verify that the geometry will fit
	int max_tracks, max_heads;
	image.get_maximal_geometry(max_tracks, max_heads);
	if (f->track_count > max_tracks || f->head_count > max_heads)
		return false;

	const int track_size = f->sector_count * 512;
	const auto track_data = std::make_unique<uint8_t []>(track_size);
	desc_pc_sector sector_desc[18];

	const uint32_t cell_count = 200000000 / f->cell_size;
	unsigned sector_offset = 0;
	switch (f->form_factor)
	{
	case floppy_image::FF_525:
		// Sectors are stored in native order
		for (int sector = 0; sector < f->sector_count; sector++)
		{
			sector_desc[sector].sector = sector + 1;
			sector_desc[sector].size = 2;
			sector_desc[sector].actual_size = 512;
			sector_desc[sector].data = &track_data[uint32_t(sector) * 512];
			sector_desc[sector].deleted = false;
			sector_desc[sector].bad_data_crc = false;
			sector_desc[sector].bad_addr_crc = false;
		}

		// For double-sided 5.25" images, read one side after the other
		for (int head = 0; head < f->head_count; head++)
		{
			for (int track = 0; track < f->track_count; track++)
			{
				// Read in one entire track
				auto const [err, actual] = read_at(io, uint32_t(sector_offset) * 512, track_data.get(), track_size);
				if (err || (actual != track_size))
					return false;

				// Finish up the sector descriptions
				for (int sector = 0; sector < f->sector_count; sector++)
				{
					sector_desc[sector].track = track;
					sector_desc[sector].head = head;
				}

				// Create the track
				build_wd_track_mfm(track, head, image, cell_count, f->sector_count, sector_desc, 100, 100, 22);
				sector_offset += f->sector_count;
			}
		}
		break;

	case floppy_image::FF_35:
		// Set up the native interleave
		for (int i = 0; i < f->sector_count; i++)
		{
			const int sector = f->sector_count == 18 ? s_interleave_18s[i] : s_interleave_9s[i];
			sector_desc[sector].sector = sector + 1;
			sector_desc[sector].size = 2;
			sector_desc[sector].actual_size = 512;
			sector_desc[sector].data = &track_data[uint32_t(i) * 512];
			sector_desc[sector].deleted = false;
			sector_desc[sector].bad_data_crc = false;
			sector_desc[sector].bad_addr_crc = false;
		}

		// For 3.5" images, tracks are interleaved between sides
		for (int track = 0; track < f->track_count; track++)
		{
			for (int head = 0; head < f->head_count; head++)
			{
				for (int i = 0; i < f->sector_count; i++, sector_offset++)
				{
					// Apply the non-native sector interleave (5 modulo 8)
					const unsigned mangled_offset = sector_offset ^ ((sector_offset & 1) << 2);

					// Read in one sector at a time
					auto const [err, actual] = read_at(io, uint32_t(mangled_offset) * 512, &track_data[uint32_t(i) * 512], 512);
					if (err || (actual != 512))
						return false;

					// Also finish up the sector descriptions
					sector_desc[i].track = track;
					sector_desc[i].head = head;
				}

				// Create the track (TODO: verify gap sizes)
				build_wd_track_mfm(track, head, image, cell_count, f->sector_count, sector_desc, 84, 100, 22);
			}
		}
		break;

	default:
		// Never happens
		return false;
	}

	// Assign the form factor and variant
	image.set_form_variant(f->form_factor, f->variant);

	// Success
	return true;
}

bool adam_format::save(util::random_read_write &io, const std::vector<uint32_t> &variants, const floppy_image &image) const
{
	int tracks, heads;
	image.get_actual_geometry(tracks, heads);

	// Find an appropriate format
	// TODO: Analyze the cell structure of a track rather than rely on the variant field being set correctly
	const format_desc *f = &s_formats[0];
	while (f->form_factor != image.get_form_factor() || f->variant != image.get_variant()
			|| f->track_count > tracks || f->head_count > heads)
	{
		if (!(++f)->size)
			return false;
	}

	unsigned sector_offset = 0;
	switch (f->form_factor)
	{
	case floppy_image::FF_525:
		for (int head = 0; head < f->head_count; head++)
		{
			for (int track = 0; track < f->track_count; track++)
			{
				// Extract the sectors for each track
				const auto bitstream = generate_bitstream_from_track(track, head, f->cell_size, image);
				const auto sectors = extract_sectors_from_bitstream_mfm_pc(bitstream);
				if (sectors.size() > f->sector_count + 1)
					return false;

				for (int sector = 1; sector <= f->sector_count; sector++, sector_offset++)
				{
					const auto &s = sectors[sector];
					if (s.size() != 512)
						return false;

					// Write each sector in sequence
					auto const [err, actual] = write_at(io, uint32_t(sector_offset) * 512, s.data(), 512);
					if (err || (actual != 512))
						return false;
				}
			}
		}
		break;

	case floppy_image::FF_35:
		for (int track = 0; track < f->track_count; track++)
		{
			for (int head = 0; head < f->head_count; head++)
			{
				// Extract the sectors for each track
				const auto bitstream = generate_bitstream_from_track(track, head, f->cell_size, image);
				const auto sectors = extract_sectors_from_bitstream_mfm_pc(bitstream);
				if (sectors.size() > f->sector_count + 1)
					return false;

				for (int i = 0; i < f->sector_count; i++, sector_offset++)
				{
					// Resolve the native interleave
					const auto &s = sectors[(f->sector_count == 18 ? s_interleave_18s[i] : s_interleave_9s[i]) + 1];
					if (s.size() != 512)
						return false;

					// Apply the non-native sector interleave (5 modulo 8)
					const unsigned mangled_offset = sector_offset ^ ((sector_offset & 1) << 2);

					// Write each sector
					auto const [err, actual] = write_at(io, uint32_t(mangled_offset) * 512, s.data(), 512);
					if (err || (actual != 512))
						return false;
				}
			}
		}
		break;

	default:
		// Never happens
		return false;
	}

	// Success at last
	return true;
}