1 /* ZX Spectrum Emulator
2 * Copyright (C) 2012-2017 Ketmar // Invisible Vector
3 * This file originating from Unreal Spectrum (DeathSoft mod)
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
29 // ////////////////////////////////////////////////////////////////////////// //
30 public __gshared
ubyte[] wdsnapbuf
; // large temporary buffer (for reading snapshots)
31 public __gshared
uint wdsnapsize
;
33 shared static this () { wdsnapbuf
.length
= 8*1048576; }
35 public __gshared
ubyte[] defaultBootHob
;
37 //@property int z80tstates () nothrow @trusted @nogc { return emuz80.tstates; }
38 @property long totalTS () nothrow @trusted @nogc { return emuFullFrameTS
+cast(long)emuz80
.tstates
; }
40 @property ushort z80pc () nothrow @trusted @nogc { return emuz80
.PC
; }
41 @property void z80pc (uint v
) nothrow @trusted @nogc { emuz80
.PC
= v
&0xffff; }
43 @property ushort z80pop () nothrow @trusted { return emuz80
.pop(); }
45 public __gshared
ubyte delegate (uint pc
) nothrow trdosROMread
;
48 // ////////////////////////////////////////////////////////////////////////// //
49 __gshared
bool trdTrapsDebug
= false;
51 shared static this () {
52 conRegVar
!trdTrapsDebug("trd_debug_traps", "show TRD traps debug info");
56 // ////////////////////////////////////////////////////////////////////////// //
57 shared int led1793Load
;
58 shared int led1793Save
;
59 shared int led1793Format
;
60 shared int led1793Seek
;
62 enum LedFrames
= 25*2; // ~0.5 seconds
64 void ledUpdate (ref shared int led
) nothrow @trusted @nogc {
66 atomicStore(led
, LedFrames
); // ~0.5 seconds
70 /// call this in redraw loop, ONCE!
71 /// returns alpha (i.e. 255 means "don't show")
72 public ubyte led1793Check () nothrow @trusted @nogc {
77 void decIt(alias sym
) () {
78 ledv
= atomicOp
!"-="(sym
, 1);
82 if (maxled
< ledv
) maxled
= ledv
;
91 if (maxled
< 1) return 255;
92 if (maxled
>= LedFrames
/2) return 0; // fully opaque
93 return 255-clampToByte(255*maxled
/(LedFrames
/2));
97 // ////////////////////////////////////////////////////////////////////////// //
98 //#define align_by(a,b) (((ULONG_PTR)(a) + ((b)-1)) & ~((b)-1))
99 //uint align_by (uint a, uint b) pure nothrow @safe @nogc { pragma(inline, true); return ((a+(b-1))&~(b-1)); }
102 // ////////////////////////////////////////////////////////////////////////// //
104 uint wd93_crc (const(ubyte)* ptr
, uint size
) nothrow @trusted @nogc {
108 for (int j
= 8; j
; j
--) // todo: rewrite with pre-calc'ed table
109 if ((crc
*= 2)&0x10000) crc ^
= 0x1021; // bit representation of x^12+x^5+1
111 //return _byteswap_ushort(crc); // return crc&0xFFFF;
113 return cast(ushort)((crc
>>8)|
(crc
<<8));
118 ushort crc16 (const(ubyte)* buf
, uint size
) nothrow @trusted @nogc {
119 static immutable ushort[256] crcTab
= [
120 0x0000, 0x97A0, 0xB9E1, 0x2E41, 0xE563, 0x72C3, 0x5C82, 0xCB22,
121 0xCAC7, 0x5D67, 0x7326, 0xE486, 0x2FA4, 0xB804, 0x9645, 0x01E5,
122 0x032F, 0x948F, 0xBACE, 0x2D6E, 0xE64C, 0x71EC, 0x5FAD, 0xC80D,
123 0xC9E8, 0x5E48, 0x7009, 0xE7A9, 0x2C8B, 0xBB2B, 0x956A, 0x02CA,
124 0x065E, 0x91FE, 0xBFBF, 0x281F, 0xE33D, 0x749D, 0x5ADC, 0xCD7C,
125 0xCC99, 0x5B39, 0x7578, 0xE2D8, 0x29FA, 0xBE5A, 0x901B, 0x07BB,
126 0x0571, 0x92D1, 0xBC90, 0x2B30, 0xE012, 0x77B2, 0x59F3, 0xCE53,
127 0xCFB6, 0x5816, 0x7657, 0xE1F7, 0x2AD5, 0xBD75, 0x9334, 0x0494,
128 0x0CBC, 0x9B1C, 0xB55D, 0x22FD, 0xE9DF, 0x7E7F, 0x503E, 0xC79E,
129 0xC67B, 0x51DB, 0x7F9A, 0xE83A, 0x2318, 0xB4B8, 0x9AF9, 0x0D59,
130 0x0F93, 0x9833, 0xB672, 0x21D2, 0xEAF0, 0x7D50, 0x5311, 0xC4B1,
131 0xC554, 0x52F4, 0x7CB5, 0xEB15, 0x2037, 0xB797, 0x99D6, 0x0E76,
132 0x0AE2, 0x9D42, 0xB303, 0x24A3, 0xEF81, 0x7821, 0x5660, 0xC1C0,
133 0xC025, 0x5785, 0x79C4, 0xEE64, 0x2546, 0xB2E6, 0x9CA7, 0x0B07,
134 0x09CD, 0x9E6D, 0xB02C, 0x278C, 0xECAE, 0x7B0E, 0x554F, 0xC2EF,
135 0xC30A, 0x54AA, 0x7AEB, 0xED4B, 0x2669, 0xB1C9, 0x9F88, 0x0828,
136 0x8FD8, 0x1878, 0x3639, 0xA199, 0x6ABB, 0xFD1B, 0xD35A, 0x44FA,
137 0x451F, 0xD2BF, 0xFCFE, 0x6B5E, 0xA07C, 0x37DC, 0x199D, 0x8E3D,
138 0x8CF7, 0x1B57, 0x3516, 0xA2B6, 0x6994, 0xFE34, 0xD075, 0x47D5,
139 0x4630, 0xD190, 0xFFD1, 0x6871, 0xA353, 0x34F3, 0x1AB2, 0x8D12,
140 0x8986, 0x1E26, 0x3067, 0xA7C7, 0x6CE5, 0xFB45, 0xD504, 0x42A4,
141 0x4341, 0xD4E1, 0xFAA0, 0x6D00, 0xA622, 0x3182, 0x1FC3, 0x8863,
142 0x8AA9, 0x1D09, 0x3348, 0xA4E8, 0x6FCA, 0xF86A, 0xD62B, 0x418B,
143 0x406E, 0xD7CE, 0xF98F, 0x6E2F, 0xA50D, 0x32AD, 0x1CEC, 0x8B4C,
144 0x8364, 0x14C4, 0x3A85, 0xAD25, 0x6607, 0xF1A7, 0xDFE6, 0x4846,
145 0x49A3, 0xDE03, 0xF042, 0x67E2, 0xACC0, 0x3B60, 0x1521, 0x8281,
146 0x804B, 0x17EB, 0x39AA, 0xAE0A, 0x6528, 0xF288, 0xDCC9, 0x4B69,
147 0x4A8C, 0xDD2C, 0xF36D, 0x64CD, 0xAFEF, 0x384F, 0x160E, 0x81AE,
148 0x853A, 0x129A, 0x3CDB, 0xAB7B, 0x6059, 0xF7F9, 0xD9B8, 0x4E18,
149 0x4FFD, 0xD85D, 0xF61C, 0x61BC, 0xAA9E, 0x3D3E, 0x137F, 0x84DF,
150 0x8615, 0x11B5, 0x3FF4, 0xA854, 0x6376, 0xF4D6, 0xDA97, 0x4D37,
151 0x4CD2, 0xDB72, 0xF533, 0x6293, 0xA9B1, 0x3E11, 0x1050, 0x87F0,
154 while (size
--) crc
= (crc
>>8)^crcTab
.ptr
[(crc
&0xff)^
*buf
++];
155 //return _byteswap_ushort(crc);
157 return cast(ushort)((crc
>>8)|
(crc
<<8));
162 void udi_buggy_crc (ref int crc
, const(ubyte)* buf
, uint len
) nothrow @trusted @nogc {
165 for (int k
= 8; k
--; ) { int temp
= -(crc
&1); crc
>>= 1; crc ^
= 0xEDB88320&temp
; }
171 // ////////////////////////////////////////////////////////////////////////// //
172 //bool done_fdd(bool Cancelable);
174 //enum Z80FQ = 3500000; // todo: #define as (wd93conf.frame*wd93conf.intfq)
175 @property uint Z80FQ () nothrow @trusted @nogc { pragma(inline
, true); return Config
.machine
.cpuSpeed
; }
176 enum FDD_RPS
= 5; // rotation speed
178 enum MAX_TRACK_LEN
= 6250;
179 enum MAX_CYLS
= 86; // don't load images with so many tracks
180 enum MAX_PHYS_CYL
= 86; // don't seek over it
184 // ////////////////////////////////////////////////////////////////////////// //
185 struct SectorHeader
{
187 ushort crc
; // CRC заголовка сектора
188 // флаги crc зоны адреса и данных:
189 // При форматировании:
190 // 0 - генерируется правильное значение crc
191 // 1 - запись crc из crc(для адреса)/crcd(для данных)
192 // 2 - ошибочный crc (генерируется инверсией правильного crc))
193 // При чтении (функция seek устанавливает поля c1 и c2):
194 // 0 - рассчитанное crc не совпадает с указанным в заголовке (возвращается crc error)
195 // 1 - рассчитанное crc совпадает с указанным в заголовке
197 ubyte* data
; // Указатель на данные сектора внутри трэка
198 ubyte* id
; // Указатель на заголовок сектора внутри трэка
199 ubyte* wp
; // Указатель на битовую карту сбойных байтов сектора внутри трэка (используется только при загрузке)
200 uint wp_start
; // Номер первого бита в карте сбойных байтов (относительно начала трэка) для данного сектора
201 uint datlen
; // Размер сектора в байтах
202 uint crcd
; // used to load specific CRC from FDI-file
206 enum SeekMode
{ SeekOnly
= 0, LoadSectors
= 1 }
209 bool test_bit (const(ubyte)* data
, uint bit
) nothrow @trusted @nogc {
210 return (data
[bit
>>3]&(1U<<(bit
&7))) != 0;
213 void set_bit (ubyte* data
, uint bit
) nothrow @trusted @nogc {
214 data
[bit
>>3] |
= (1U<<(bit
&7));
217 void clr_bit (ubyte* data
, uint bit
) nothrow @trusted @nogc {
218 data
[bit
>>3] &= ~(1U<<(bit
&7));
222 // ////////////////////////////////////////////////////////////////////////// //
224 // cached track position
228 // generic track data
230 // pointer to data inside UDI
231 ubyte* trkd
; // данные
232 ubyte* trki
; // битовая карта синхроимпульсов
233 ubyte* trkwp
; // битовая карта сбойных байтов
234 uint ts_byte
;// = Z80FQ/(MAX_TRACK_LEN*FDD_RPS); // wd93cpu.t per byte
235 SeekMode sf
; // flag: is sectors filled
236 uint s
; // no. of sectors
239 SectorHeader
[MAX_SEC
] hdr
;
241 void set (uint pos
) nothrow @trusted @nogc { set_bit(trki
, pos
); }
242 void res (uint pos
) nothrow @trusted @nogc { clr_bit(trki
, pos
); }
243 bool bit (uint pos
) const nothrow @trusted @nogc { return test_bit(trki
, pos
); }
245 void setWriteProtected (uint pos
) nothrow @trusted @nogc { set_bit(trkwp
, pos
); }
246 bool getWriteProtected (uint pos
) const nothrow @trusted @nogc { return test_bit(trkwp
, pos
); }
248 void write (uint pos
, ubyte b
, byte index
) nothrow @trusted @nogc {
249 if (trkd
is null) return;
251 if (index
) set(pos
); else res(pos
);
254 void clear () nothrow @trusted @nogc {
257 ts_byte
= Z80FQ
/(MAX_TRACK_LEN
*FDD_RPS
);
260 void seek (FDD d
, uint cyl
, uint side
, SeekMode fs
) nothrow @trusted {
261 if (d
is drive
&& sf
== fs
&& cyl
== this.cyl
&& side
== this.side
) return;
268 if (cyl
>= d
.cyls ||
!d
.rawdata
) { trkd
= null; return; }
270 assert(cyl
< MAX_CYLS
);
271 trkd
= d
.trkd
[cyl
][side
];
272 trki
= d
.trki
[cyl
][side
];
273 trkwp
= d
.trkwp
[cyl
][side
];
274 trklen
= d
.trklen
[cyl
][side
];
275 if (!trklen
) { trkd
= null; return; }
277 ts_byte
= Z80FQ
/(trklen
*FDD_RPS
);
278 if (fs
== SeekMode
.SeekOnly
) return; // else find sectors
280 for (uint i
= 0; i
< trklen
-8; ++i
) {
281 import std
.algorithm
: min
;
282 if (trkd
[i
] != 0xA1 || trkd
[i
+1] != 0xFE ||
!bit(i
)) continue; // Поиск idam
283 if (s
== MAX_SEC
) assert(0, "too many sectors");
284 SectorHeader
* h
= &hdr
[s
++]; // Заполнение заголовка
285 h
.id
= trkd
+i
+2; // Указатель на заголовок сектора
290 h
.crc
= *cast(ushort*)(trkd
+i
+6);
291 h
.c1
= (wd93_crc(trkd
+i
+1, 5) == h
.crc
);
295 //if (h.l > 5) continue; [vv]
296 uint end
= min(trklen
-8, i
+8+43); // 43-DD, 30-SD
297 // Формирование указателя на зону данных сектора
298 for (uint j
= i
+8; j
< end
; ++j
) {
299 if (trkd
[j
] != 0xA1 ||
!bit(j
) ||
bit(j
+1)) continue;
300 if (trkd
[j
+1] == 0xF8 || trkd
[j
+1] == 0xFB) {
302 h
.datlen
= 128<<(h
.l
&3); // [vv] FD1793 use only 2 lsb of sector size code
304 h
.c2
= (wd93_crc(h
.data
-1, h
.datlen
+1) == *cast(ushort*)(h
.data
+h
.datlen
));
306 for (uint b
= 0; b
< h
.datlen
; ++b
) {
307 if (getWriteProtected(j
+2+b
)) {
308 h
.wp_start
= j
+2; // Есть хотябы один сбойный байт
319 void format () nothrow @trusted {
320 import core
.stdc
.string
: memcpy
, memset
;
322 memset(trkd
, 0, trklen
);
323 memset(trki
, 0, cast(uint)(trklen
+7U)>>3);
324 memset(trkwp
, 0, cast(uint)(trklen
+7U)>>3);
331 //gap4a(80)+sync0(12)+iam(3)+1+s*(gap1(50)+sync1(12)+idam(3)+1+4+2+gap2(22)+sync2(12)+data_am(3)+1+2)
343 for (uint isx
= 0; isx
< s
; isx
++) {
344 SectorHeader
* sechdr
= hdr
.ptr
+isx
;
345 data_sz
+= (128<<(sechdr
.l
&3)); // n
348 if ((gap4a
+sync0
+i_am
+1+data_sz
+s
*(gap1
+sync1
+id_am
+1+4+2+gap2
+sync2
+data_am
+1+2)) >= MAX_TRACK_LEN
) {
349 // Превышение стандартной длины дорожки, сокращаем параметры до минимальных
361 memset(dst
, 0x4E, gap4a
); dst
+= gap4a
; // gap4a
362 memset(dst
, 0, sync0
); dst
+= sync0
; //sync
364 for (i
= 0; i
< i_am
; i
++) write(dst
++-trkd
, 0xC2, 1); // iam
367 for (uint isx
= 0; isx
< s
; isx
++) {
368 memset(dst
, 0x4E, gap1
); dst
+= gap1
; // gap1 // 50 [vv] // fixme: recalculate gap1 only for non standard formats
369 memset(dst
, 0, sync1
); dst
+= sync1
; //sync
370 for (i
= 0; i
< id_am
; i
++) write(dst
++-trkd
, 0xA1, 1); // idam
373 SectorHeader
* sechdr
= hdr
.ptr
+isx
;
374 *dst
++ = sechdr
.c
; // c
375 *dst
++ = sechdr
.s
; // h
376 *dst
++ = sechdr
.n
; // s
377 *dst
++ = sechdr
.l
; // n
379 uint crc
= wd93_crc(dst
-5, 5); // crc
380 if (sechdr
.c1
== 1) crc
= sechdr
.crc
;
381 if (sechdr
.c1
== 2) crc ^
= 0xFFFF;
382 *cast(uint*)dst
= crc
;
386 memset(dst
, 0x4E, gap2
); dst
+= gap2
; // gap2
387 memset(dst
, 0, sync2
); dst
+= sync2
; //sync
388 for (i
= 0; i
< data_am
; i
++) write(dst
++-trkd
, 0xA1, 1); // data am
391 //if (sechdr.l > 5) errexit("strange sector"); // [vv]
392 uint len
= 128<<(sechdr
.l
&3); // data
393 if (sechdr
.data
!= cast(ubyte*)1) {
394 memcpy(dst
, sechdr
.data
, len
);
396 // Копирование битовой карты сбойных байтов
397 uint wp_start
= dst
-trkd
;
398 sechdr
.wp_start
= wp_start
;
399 for (uint b
= 0; b
< len
; b
++) {
400 if (test_bit(sechdr
.wp
, b
)) setWriteProtected(wp_start
+b
);
407 crc
= wd93_crc(dst
-1, len
+1); // crc
408 if (sechdr
.c2
== 1) crc
= sechdr
.crcd
;
409 if (sechdr
.c2
== 2) crc ^
= 0xFFFF;
410 *cast(uint*)(dst
+len
) = crc
;
414 if (dst
> trklen
+trkd
) {
415 //printf("cyl=%u, h=%u, additional len=%u\n", cyl, side, dst-(trklen+trkd));
416 //errexit("track too long");
419 while (dst
< trkd
+trklen
) *dst
++ = 0x4E;
422 int writeSector (uint sec
, const(ubyte)* data
) nothrow @trusted {
423 import core
.stdc
.string
: memcpy
;
424 SectorHeader
* h
= getSector(sec
);
425 if (h
is null ||
!h
.data
) return 0;
427 if (h
.data
!= data
) memcpy(h
.data
, data
, sz
);
428 *cast(ushort*)(h
.data
+sz
) = cast(ushort)wd93_crc(h
.data
-1, sz
+1);
432 SectorHeader
* getSector (uint sec
) nothrow @trusted {
434 for (i
= 0; i
< s
; i
++) if (hdr
[i
].n
== sec
) break;
435 if (i
== s
) return null;
436 if (/*(hdr[i].l&3) != 1 ||*/ hdr
[i
].c
!= cyl
) return null; // [vv]
442 // ////////////////////////////////////////////////////////////////////////// //
443 public class WD1793
{
478 WDSTATE state
, state2
;
481 ubyte data
, track
, sector
;
483 ubyte sign_status
; // Внешние сигналы (пока только HLD)
485 uint drive
, side
; // update this with changing 'system'
488 ubyte system
; // beta128 system register
490 uint idx_cnt
; // idx counter
492 // read/write sector(s) data
494 uint foundid
; // index in trkcache.hdr for next encountered ID and bytes before this ID
500 enum CMDBITS
: ubyte {
503 SEEK_HEADLOAD
= 0x08,
508 SIDE_CMP_FLAG
= 0x02,
515 enum BETA_STATUS
: ubyte {
520 enum WD_STATUS
: ubyte {
536 enum WD_SYS
: ubyte {
540 enum WD_SIG
: ubyte {
549 foreach (ubyte i
; 0..4) fdd
[i
] = new FDD(i
);
550 for (ubyte i
= 0; i
< 4; i
++) fdd
[i
].Id
= i
; // [vv] Для удобства отладки
557 private void process () nothrow @trusted {
558 import core
.stdc
.string
: memcpy
;
562 if (!(sign_status
&WD_SIG
.SIG_HLD
) && !(system
&0x20)) seldrive
.motor
= 0;
564 if (seldrive
.rawdata
) status
&= ~cast(uint)WD_STATUS
.WDS_NOTRDY
; else status |
= WD_STATUS
.WDS_NOTRDY
;
566 if (!(cmd
&0x80) ||
(cmd
&0xF0) == 0xD0) {
567 // seek/step commands
568 status
&= ~cast(uint)WD_STATUS
.WDS_INDEX
;
570 if (state
!= WDSTATE
.S_IDLE
) {
571 status
&= ~cast(uint)(WD_STATUS
.WDS_TRK00|WD_STATUS
.WDS_INDEX
);
572 if (!seldrive
.track
) status |
= WD_STATUS
.WDS_TRK00
;
575 // todo: test spinning
576 if (seldrive
.rawdata
&& seldrive
.motor
&& ((time
+tshift
)%(Z80FQ
/FDD_RPS
) < (Z80FQ
*4/1000))) {
577 // index every turn, len=4ms (if disk present)
578 if (state
== WDSTATE
.S_IDLE
) {
579 if (time
< idx_tmo
) status |
= WD_STATUS
.WDS_INDEX
;
581 status |
= WD_STATUS
.WDS_INDEX
;
587 if (!seldrive
.motor
) status |
= WD_STATUS
.WDS_NOTRDY
; else status
&= ~cast(uint)WD_STATUS
.WDS_NOTRDY
;
589 // ----------------------------------------------------
591 status
&= ~cast(uint)WD_STATUS
.WDS_BUSY
;
592 if (idx_cnt
>= 15 || time
> idx_tmo
) {
594 status
&= WD_STATUS
.WDS_NOTRDY
;
595 status |
= WD_STATUS
.WDS_NOTRDY
;
597 sign_status
&= ~cast(uint)WD_SIG
.SIG_HLD
;
599 rqs
= BETA_STATUS
.INTRQ
;
603 if (time
< next
) return;
607 // ----------------------------------------------------
608 // Подана команда типа 2 или 3 (read/write)/(read am/read trk/write trk)
609 case WDSTATE
.S_DELAY_BEFORE_CMD
:
610 if (!Config
.WD93
.noDelay
&& (cmd
&CMDBITS
.DELAY
)) { // Проверка бита E=1
611 next
+= (Z80FQ
*15/1000); // 15ms delay
613 state2
= WDSTATE
.S_WAIT_HLT_RW
;
614 state
= WDSTATE
.S_WAIT
;
617 case WDSTATE
.S_WAIT_HLT_RW
:
618 if (!(system
&0x08)) return; // HLT = 0 (бесконечное ожидание HLT)
619 state
= WDSTATE
.S_CMD_RW
;
622 case WDSTATE
.S_CMD_RW
:
623 if (((cmd
&0xE0) == 0xA0 ||
(cmd
&0xF0) == 0xF0) && Config
.WD93
.writeProt(drive
)) {
624 status |
= WD_STATUS
.WDS_WRITEP
;
625 state
= WDSTATE
.S_IDLE
;
629 if ((cmd
&0xC0) == 0x80 ||
(cmd
&0xF8) == 0xC0) {
630 // read/write sectors or read am-find next AM
631 end_waiting_am
= next
+5*Z80FQ
/FDD_RPS
; // max wait disk 5 turns
636 if ((cmd
&0xF8) == 0xF0) {
638 rqs
= BETA_STATUS
.DRQ
;
639 status |
= WD_STATUS
.WDS_DRQ
;
640 next
+= 3*seldrive
.t
.ts_byte
;
641 state2
= WDSTATE
.S_WRTRACK
;
642 state
= WDSTATE
.S_WAIT
;
646 if ((cmd
&0xF8) == 0xE0) {
650 rwlen
= seldrive
.t
.trklen
;
651 state2
= WDSTATE
.S_READ
;
656 // else unknown command
657 state
= WDSTATE
.S_IDLE
;
660 case WDSTATE
.S_FOUND_NEXT_ID
:
661 if (!seldrive
.rawdata
) {
662 // no disk-wait again
663 end_waiting_am
= next
+5*Z80FQ
/FDD_RPS
;
669 if (next
>= end_waiting_am
) {
671 status |
= WD_STATUS
.WDS_NOTFOUND
;
672 state
= WDSTATE
.S_IDLE
;
676 if (foundid
== -1) goto nf
;
678 status
&= ~cast(uint)WD_STATUS
.WDS_CRCERR
;
683 if (seldrive
.t
.hdr
[foundid
].c
!= track
) goto nextmk
;
684 if (!seldrive
.t
.hdr
[foundid
].c1
) {
685 status |
= WD_STATUS
.WDS_CRCERR
;
688 state
= WDSTATE
.S_IDLE
; break;
691 if ((cmd
&0xF0) == 0xC0) {
693 rwptr
= seldrive
.t
.hdr
[foundid
].id
-seldrive
.t
.trkd
;
698 if (seldrive
.t
.trkwp
) {
699 // проверяем карту сбойных байтов
700 if (seldrive
.t
.getWriteProtected(rwptr
)) {
702 seldrive
.t
.hdr
[foundid
].c1
= 0; // bad address crc
705 data
= seldrive
.t
.trkd
[rwptr
++];
708 rqs
= BETA_STATUS
.DRQ
;
709 status |
= WD_STATUS
.WDS_DRQ
;
710 next
+= seldrive
.t
.ts_byte
;
711 state
= WDSTATE
.S_WAIT
;
712 state2
= WDSTATE
.S_READ
;
716 // else R/W sector(s)
717 if (seldrive
.t
.hdr
[foundid
].c
!= track || seldrive
.t
.hdr
[foundid
].n
!= sector
) goto nextmk
;
718 if ((cmd
&CMDBITS
.SIDE_CMP_FLAG
) && (((cmd
>>CMDBITS
.SIDE_SHIFT
)^seldrive
.t
.hdr
[foundid
].s
)&1)) goto nextmk
;
719 if (!seldrive
.t
.hdr
[foundid
].c1
) { status |
= WD_STATUS
.WDS_CRCERR
; goto nextmk
; }
723 rqs
= BETA_STATUS
.DRQ
;
724 status |
= WD_STATUS
.WDS_DRQ
;
725 next
+= seldrive
.t
.ts_byte
*9;
726 state
= WDSTATE
.S_WAIT
; state2
= WDSTATE
.S_WRSEC
;
731 if (!seldrive
.t
.hdr
[foundid
].data
) goto nextmk
; // Сектор без зоны данных
732 if (!Config
.WD93
.noDelay
) next
+= seldrive
.t
.ts_byte
*(seldrive
.t
.hdr
[foundid
].data
-seldrive
.t
.hdr
[foundid
].id
); // Задержка на пропуск заголовка сектора и пробела между заголовком и зоной данных
733 state
= WDSTATE
.S_WAIT
;
734 state2
= WDSTATE
.S_RDSEC
;
737 case WDSTATE
.S_RDSEC
:
738 if (seldrive
.t
.hdr
[foundid
].data
[-1] == 0xF8) status |
= WD_STATUS
.WDS_RECORDT
; else status
&= ~cast(uint)WD_STATUS
.WDS_RECORDT
;
739 rwptr
= seldrive
.t
.hdr
[foundid
].data
-seldrive
.t
.trkd
; // Смещение зоны данных сектора (в байтах) относительно начала трека
740 rwlen
= 128<<(seldrive
.t
.hdr
[foundid
].l
&3); // [vv]
741 goto read_first_byte
;
747 if (!seldrive
.t
.trkd
) {
748 status |
= WD_STATUS
.WDS_NOTFOUND
;
749 state
= WDSTATE
.S_IDLE
;
754 ledUpdate(led1793Load
);
755 if (rqs
&BETA_STATUS
.DRQ
) status |
= WD_STATUS
.WDS_LOST
;
758 if (seldrive
.t
.trkwp
) {
759 // проверяем карту сбойных байтов
760 if (seldrive
.t
.getWriteProtected(rwptr
)) {
762 if ((cmd
&0xE0) == 0x80) {
764 seldrive
.t
.hdr
[foundid
].c2
= 0; // bad data crc
766 if ((cmd
&0xF0) == 0xC0) {
768 seldrive
.t
.hdr
[foundid
].c1
= 0; // bad address crc
772 data
= seldrive
.t
.trkd
[rwptr
++];
775 rqs
= BETA_STATUS
.DRQ
;
776 status |
= WD_STATUS
.WDS_DRQ
;
778 if (!Config
.WD93
.noDelay
) next
+= seldrive
.t
.ts_byte
; else next
= time
+1;
779 state
= WDSTATE
.S_WAIT
;
780 state2
= WDSTATE
.S_READ
;
782 if ((cmd
&0xE0) == 0x80) {
784 if (!seldrive
.t
.hdr
[foundid
].c2
) status |
= WD_STATUS
.WDS_CRCERR
;
785 if (cmd
&CMDBITS
.MULTIPLE
) {
787 state
= WDSTATE
.S_CMD_RW
;
792 // FIXME: Временный хак для zx-format 3
793 if (((cmd
&0xF8) == 0xE0) && (seldrive
.t
.trklen
< MAX_TRACK_LEN
)) {
795 status |
= WD_STATUS
.WDS_LOST
;
798 if ((cmd
&0xF0) == 0xC0) {
800 if (!seldrive
.t
.hdr
[foundid
].c1
) status |
= WD_STATUS
.WDS_CRCERR
;
802 state
= WDSTATE
.S_IDLE
;
806 case WDSTATE
.S_WRSEC
:
808 if (rqs
&BETA_STATUS
.DRQ
) {
809 status |
= WD_STATUS
.WDS_LOST
;
810 state
= WDSTATE
.S_IDLE
;
813 seldrive
.optype |
= 1;
814 rwptr
= seldrive
.t
.hdr
[foundid
].id
+6+11+11-seldrive
.t
.trkd
;
815 for (rwlen
= 0; rwlen
< 12; rwlen
++) seldrive
.t
.write(rwptr
++, 0, 0);
816 for (rwlen
= 0; rwlen
< 3; rwlen
++) seldrive
.t
.write(rwptr
++, 0xA1, 1);
817 seldrive
.t
.write(rwptr
++, (cmd
&CMDBITS
.WRITE_DEL ?
0xF8 : 0xFB), 0);
818 rwlen
= 128<<(seldrive
.t
.hdr
[foundid
].l
&3); // [vv]
819 state
= WDSTATE
.S_WRITE
;
822 case WDSTATE
.S_WRITE
:
824 if (rqs
&BETA_STATUS
.DRQ
) status |
= WD_STATUS
.WDS_LOST
, data
= 0;
825 ledUpdate(led1793Save
);
826 seldrive
.t
.write(rwptr
++, data
, 0); rwlen
--;
827 if (rwptr
== seldrive
.t
.trklen
) rwptr
= 0;
828 seldrive
.t
.sf
= SeekMode
.SeekOnly
; // invalidate sectors
830 if (!Config
.WD93
.noDelay
) next
+= seldrive
.t
.ts_byte
;
831 state
= WDSTATE
.S_WAIT
; state2
= WDSTATE
.S_WRITE
;
832 rqs
= BETA_STATUS
.DRQ
; status |
= WD_STATUS
.WDS_DRQ
;
834 uint len
= (128<<(seldrive
.t
.hdr
[foundid
].l
&3))+1; //[vv]
835 ubyte[2056] sc
= void;
837 memcpy(sc
.ptr
, seldrive
.t
.trkd
+seldrive
.t
.trklen
-rwptr
, rwptr
);
838 memcpy(sc
.ptr
+rwptr
, seldrive
.t
.trkd
, len
-rwptr
);
840 memcpy(sc
.ptr
, seldrive
.t
.trkd
+rwptr
-len
, len
);
842 uint crc
= wd93_crc(sc
.ptr
, len
);
843 seldrive
.t
.write(rwptr
++, cast(ubyte)crc
, 0);
844 seldrive
.t
.write(rwptr
++, cast(ubyte)(crc
>>8), 0);
845 seldrive
.t
.write(rwptr
, 0xFF, 0);
846 if (cmd
&CMDBITS
.MULTIPLE
) {
848 state
= WDSTATE
.S_CMD_RW
;
851 state
= WDSTATE
.S_IDLE
;
855 case WDSTATE
.S_WRTRACK
:
856 if (rqs
&BETA_STATUS
.DRQ
) {
857 status |
= WD_STATUS
.WDS_LOST
;
858 state
= WDSTATE
.S_IDLE
;
861 seldrive
.optype |
= 2;
862 state2
= WDSTATE
.S_WR_TRACK_DATA
;
865 end_waiting_am
= next
+5*Z80FQ
/FDD_RPS
;
868 case WDSTATE
.S_WR_TRACK_DATA
:
870 ledUpdate(led1793Format
);
871 if (rqs
&BETA_STATUS
.DRQ
) {
872 status |
= WD_STATUS
.WDS_LOST
;
875 seldrive
.t
.seek(seldrive
, seldrive
.track
, side
, SeekMode
.SeekOnly
);
876 seldrive
.t
.sf
= SeekMode
.SeekOnly
; // invalidate sectors
878 if (!seldrive
.t
.trkd
) {
879 state
= WDSTATE
.S_IDLE
;
883 ubyte marker
= 0, byt
= data
;
896 crc
= wd93_crc(seldrive
.t
.trkd
+start_crc
, rwptr
-start_crc
);
902 seldrive
.t
.write(rwptr
++, byt
, marker
);
905 seldrive
.t
.write(rwptr
++, (crc
>>8)&0xFF, 0);
906 rwlen
--; // second byte of CRC16
909 if (cast(int)rwlen
> 0) {
910 if (!Config
.WD93
.noDelay
) next
+= seldrive
.t
.ts_byte
;
911 state2
= WDSTATE
.S_WR_TRACK_DATA
;
912 state
= WDSTATE
.S_WAIT
;
913 rqs
= BETA_STATUS
.DRQ
;
914 status |
= WD_STATUS
.WDS_DRQ
;
917 state
= WDSTATE
.S_IDLE
;
920 // ----------------------------------------------------
921 case WDSTATE
.S_TYPE1_CMD
: // Подана команда типа 1 (restore/seek/step)
922 status
&= ~cast(uint)(WD_STATUS
.WDS_CRCERR|WD_STATUS
.WDS_SEEKERR|WD_STATUS
.WDS_WRITEP
);
925 if (Config
.WD93
.writeProt(drive
)) status |
= WD_STATUS
.WDS_WRITEP
;
927 seldrive
.motor
= (cmd
&CMDBITS
.SEEK_HEADLOAD
) ||
(system
&0x20 ? next
+2*Z80FQ
: 0);
929 state2
= WDSTATE
.S_SEEKSTART
; // default is seek/restore
933 if (cmd
&0x40) stepdirection
= (cmd
&CMDBITS
.SEEK_DIR ?
-1 : 1); // step in/step out
934 state2
= WDSTATE
.S_STEP
;
937 next
+= (Z80FQ
*14)/1000000;
938 state
= WDSTATE
.S_WAIT
; // Задержка 14мкс перед появленеим статуса BUSY
942 status |
= WD_STATUS
.WDS_BUSY
;
943 ledUpdate(led1793Seek
);
945 if (seldrive
.track
== 0 && stepdirection
< 0) {
948 state
= WDSTATE
.S_VERIFY
; // Обработка бита команды V
952 // Обработкабита T=1 (для seek всегда 1, для restore всегда 0, но обновление регистра трэка делается)
953 if (!(cmd
&0xF0) ||
(cmd
&CMDBITS
.SEEK_TRKUPD
)) track
+= stepdirection
;
955 if (seldrive
.motor
) {
956 seldrive
.track
+= stepdirection
;
957 if (seldrive
.track
== cast(ubyte)-1) seldrive
.track
= 0;
958 if (seldrive
.track
>= MAX_PHYS_CYL
) seldrive
.track
= MAX_PHYS_CYL
;
962 static immutable uint[4] steps
= [ 6, 12, 20, 30 ];
963 if (!Config
.WD93
.noDelay
) next
+= steps
[cmd
&CMDBITS
.SEEK_RATE
]*Z80FQ
/1000;
966 if (!Config
.WD93
.noDelay
&& wd93conf
.fdd_noise
) Beep((stepdirection
> 0?
600 : 800), 2);
969 state2
= (cmd
&0xE0 ? WDSTATE
.S_VERIFY
/*Команда step*/: WDSTATE
.S_SEEK
/*Команда seek/restore*/);
970 state
= WDSTATE
.S_WAIT
;
973 case WDSTATE
.S_SEEKSTART
:
974 status |
= WD_STATUS
.WDS_BUSY
;
977 if (!Config
.WD93
.noDelay
) {
978 state2
= WDSTATE
.S_RESTORE
;
979 next
+= (Z80FQ
*21)/1000000; // задержка ~21мкс перед загрузкой регистра трэка
980 state
= WDSTATE
.S_WAIT
;
983 state
= WDSTATE
.S_RESTORE
;
986 state
= WDSTATE
.S_SEEK
;
989 case WDSTATE
.S_RESTORE
:
992 state
= WDSTATE
.S_SEEK
;
997 state
= WDSTATE
.S_VERIFY
;
1000 stepdirection
= (data
< track ?
-1 : 1);
1001 state
= WDSTATE
.S_STEP
;
1004 case WDSTATE
.S_VERIFY
:
1005 if (!(cmd
&CMDBITS
.SEEK_VERIFY
)) {
1006 // Проверка номера трэка не нужна V=0
1007 status |
= WD_STATUS
.WDS_BUSY
;
1008 state2
= WDSTATE
.S_IDLE
;
1009 state
= WDSTATE
.S_WAIT
;
1010 idx_tmo
= next
+15*Z80FQ
/FDD_RPS
; // 15 disk turns
1011 next
+= (105*Z80FQ
)/1000000; // Задержка 105мкс со статусом BUSY
1015 // Проверка номера трэка нужна V=1
1016 sign_status |
= WD_SIG
.SIG_HLD
;
1021 // Ожидание HLT == 1
1025 if (!Config
.WD93
.noDelay
) {
1026 next
+= (15*Z80FQ
)/1000; // Задержка 15мс
1027 state2
= WDSTATE
.S_WAIT_HLT
;
1028 state
= WDSTATE
.S_WAIT
;
1031 state
= WDSTATE
.S_WAIT_HLT
;
1034 case WDSTATE
.S_WAIT_HLT
:
1035 if (!(system
&0x08)) return; // HLT = 0 (бесконечное ожидание HLT)
1036 state
= WDSTATE
.S_VERIFY2
;
1039 case WDSTATE
.S_VERIFY2
:
1040 end_waiting_am
= next
+6*Z80FQ
/FDD_RPS
; // max wait disk 6 turns
1045 // ----------------------------------------------------
1046 case WDSTATE
.S_RESET
: // seek to trk0, but don't be busy
1047 if (!seldrive
.track
) {
1048 state
= WDSTATE
.S_IDLE
;
1053 // if (!seldrive.track) track = 0;
1054 next
+= 6*Z80FQ
/1000;
1058 assert(0, "WD1793 in wrong state");
1063 private void findMarker () nothrow @trusted {
1064 if (Config
.WD93
.noDelay
&& seldrive
.track
!= track
) seldrive
.track
= track
;
1068 if (seldrive
.motor
&& seldrive
.rawdata
) {
1069 uint div = seldrive
.t
.trklen
*seldrive
.t
.ts_byte
; // Длина дорожки в тактах wd93cpu
1070 uint i
= cast(uint)((next
+tshift
)%div)/seldrive
.t
.ts_byte
; // Позиция байта соответствующего текущему такту на дорожке
1073 // Поиск заголовка минимально отстоящего от текущего байта
1074 for (uint isx
= 0; isx
< seldrive
.t
.s
; isx
++) {
1075 uint pos
= seldrive
.t
.hdr
[isx
].id
-seldrive
.t
.trkd
; // Смещение (в байтах) заголовка относительно начала дорожки
1076 uint dist
= (pos
> i ? pos
-i
: seldrive
.t
.trklen
+pos
-i
); // Расстояние (в байтах) от заголовка до текущего байта
1084 wait *= seldrive
.t
.ts_byte
; // Задержка в тактах от текущего такта до такта чтения первого байта заголовка
1086 wait = 10*Z80FQ
/FDD_RPS
;
1088 if (Config
.WD93
.noDelay
&& foundid
!= -1) {
1089 // adjust tshift, that id appares right under head
1090 uint pos
= seldrive
.t
.hdr
[foundid
].id
-seldrive
.t
.trkd
+2;
1091 tshift
= cast(uint)(((pos
*seldrive
.t
.ts_byte
)-(next
%div)+div)%div);
1092 wait = 100; // delay=0 causes fdc to search infinitely, when no matched id on track
1097 // else no index pulses - infinite wait
1101 if (seldrive
.rawdata
&& next
> end_waiting_am
) {
1102 next
= end_waiting_am
;
1105 state
= WDSTATE
.S_WAIT
;
1106 state2
= WDSTATE
.S_FOUND_NEXT_ID
;
1109 private bool notReady () nothrow @trusted {
1110 // fdc is too fast in no-delay mode, wait until cpu handles DRQ, but not more 'end_waiting_am'
1111 if (Config
.WD93
.noDelay
&& (rqs
&BETA_STATUS
.DRQ
) && (next
< (end_waiting_am
-5*Z80FQ
/FDD_RPS
)+(600*Z80FQ
)/1000)) {
1113 state
= WDSTATE
.S_WAIT
;
1114 next
+= seldrive
.t
.ts_byte
;
1120 private void getIndex () nothrow @trusted {
1121 uint trlen
= seldrive
.t
.trklen
*seldrive
.t
.ts_byte
;
1122 uint ticks
= cast(uint)((next
+tshift
)%trlen
);
1123 if (!Config
.WD93
.noDelay
) next
+= (trlen
-ticks
);
1125 rwlen
= seldrive
.t
.trklen
;
1126 state
= WDSTATE
.S_WAIT
;
1129 void load () nothrow @trusted {
1130 seldrive
.t
.seek(seldrive
, seldrive
.track
, side
, SeekMode
.LoadSectors
);
1133 ubyte inp (ubyte port
) nothrow @trusted {
1135 if (port
&0x80) return rqs|
(system
&0x3F);
1137 rqs
&= ~cast(uint)BETA_STATUS
.INTRQ
;
1140 if (port
== 0x3F) return track
;
1141 if (port
== 0x5F) return sector
;
1143 status
&= ~cast(uint)WD_STATUS
.WDS_DRQ
;
1144 rqs
&= ~cast(uint)BETA_STATUS
.DRQ
;
1150 ubyte rdStatus () nothrow @trusted {
1153 return status|
((sign_status
&WD_SIG
.SIG_HLD
) && (system
&8) ? WD_STATUS
.WDS_HEADL
: 0);
1158 void outp (ubyte port
, ubyte val
) nothrow @trusted {
1164 // force interrupt (type 4)
1165 if ((val
&0xF0) == 0xD0) {
1166 ubyte Cond
= (val
&0xF);
1169 idx_tmo
= next
+15*Z80FQ
/FDD_RPS
; // 15 disk turns
1173 state
= WDSTATE
.S_IDLE
; rqs
= 0;
1174 status
&= ~cast(uint)WD_STATUS
.WDS_BUSY
;
1179 // unconditional int
1180 state
= WDSTATE
.S_IDLE
; rqs
= BETA_STATUS
.INTRQ
;
1181 status
&= ~cast(uint)WD_STATUS
.WDS_BUSY
;
1186 // int by idam (unimplemented yet)
1187 state
= WDSTATE
.S_IDLE
; rqs
= BETA_STATUS
.INTRQ
;
1188 status
&= ~cast(uint)WD_STATUS
.WDS_BUSY
;
1193 // int 1.0 rdy (unimplemented yet)
1194 state
= WDSTATE
.S_IDLE
; rqs
= BETA_STATUS
.INTRQ
;
1195 status
&= ~cast(uint)WD_STATUS
.WDS_BUSY
;
1200 // int 0.1 rdy (unimplemented yet)
1201 state
= WDSTATE
.S_IDLE
; rqs
= BETA_STATUS
.INTRQ
;
1202 status
&= ~cast(uint)WD_STATUS
.WDS_BUSY
;
1209 if (status
&WD_STATUS
.WDS_BUSY
) return;
1212 status |
= WD_STATUS
.WDS_BUSY
;
1217 //-----------------------------------------------------------------------
1220 // read/write command (type 2, 3)
1221 status
= (status|WD_STATUS
.WDS_BUSY
)&~cast(uint)(WD_STATUS
.WDS_DRQ|WD_STATUS
.WDS_LOST|WD_STATUS
.WDS_NOTFOUND|WD_STATUS
.WDS_RECORDT|WD_STATUS
.WDS_WRITEP
);
1223 // continue disk spinning
1224 seldrive
.motor
= next
+2*Z80FQ
;
1227 if (status
&WD_STATUS
.WDS_NOTRDY
) {
1228 state2
= WDSTATE
.S_IDLE
;
1229 state
= WDSTATE
.S_WAIT
;
1230 next
= totalTS
+Z80FQ
/FDD_RPS
;
1231 rqs
= BETA_STATUS
.INTRQ
;
1235 sign_status |
= WD_SIG
.SIG_HLD
;
1237 state
= WDSTATE
.S_DELAY_BEFORE_CMD
;
1242 if (cmd
&CMDBITS
.SEEK_HEADLOAD
) // h = 1
1243 sign_status |
= WD_SIG
.SIG_HLD
;
1245 sign_status
&= ~cast(uint)WD_SIG
.SIG_HLD
;
1247 // else seek/step command
1248 status
&= ~cast(uint)WD_STATUS
.WDS_BUSY
;
1249 state
= WDSTATE
.S_TYPE1_CMD
;
1253 //=======================================================================
1267 rqs
&= ~cast(uint)BETA_STATUS
.DRQ
;
1268 status
&= ~cast(uint)WD_STATUS
.WDS_DRQ
;
1277 seldrive
= fdd
[drive
];
1282 status
= WD_STATUS
.WDS_NOTRDY
;
1283 rqs
= BETA_STATUS
.INTRQ
;
1285 state
= WDSTATE
.S_IDLE
;
1287 version(all
) { // move head to trk00
1288 //steptime = 6*(Z80FQ/1000); // 6ms
1289 next
+= 1*Z80FQ
/1000; // 1ms before command
1290 state
= WDSTATE
.S_RESET
;
1291 //seldrive.track = 0;
1294 if ((system^val
)&WD_SYS
.SYS_HLT
) {
1296 if (!(status
&WD_STATUS
.WDS_BUSY
)) idx_cnt
++;
1301 // В quorum бит D5 управляет мотором дисковода
1302 sign_status |
= WD_SIG
.SIG_HLD
;
1303 seldrive
.motor
= next
+2*Z80FQ
;
1309 void trdosTraps () nothrow @trusted {
1311 if (pc
< 0x3DFD) return;
1313 // Позиционирование на соседнюю дорожку (пауза)
1314 if (pc
== 0x3DFD && trdosROMread(0x3DFD) == 0x3E && trdosROMread(0x3DFF) == 0x0E) {
1315 if (trdTrapsDebug
) conwriteln("TRDTRAP: next track");
1321 // Позиционирование на произвольную дорожку (пауза)
1322 if (pc
== 0x3EA0 && trdosROMread(0x3EA0) == 0x06 && trdosROMread(0x3EA2) == 0x3E) {
1323 if (trdTrapsDebug
) conwriteln("TRDTRAP: seek track");
1329 if (pc
== 0x3E01 && trdosROMread(0x3E01) == 0x0D) {
1330 if (trdTrapsDebug
) conwriteln("TRDTRAP: delay skip");
1331 emuz80
.AF
.a
= emuz80
.BC
.c
= 1;
1335 if (pc
== 0x3FEC && trdosROMread(0x3FED) == 0xA2 && (state
== WDSTATE
.S_READ ||
(state2
== WDSTATE
.S_READ
&& state
== WDSTATE
.S_WAIT
))) {
1336 if (trdTrapsDebug
) conwriteln("TRDTRAP: read byte");
1337 ledUpdate(led1793Load
);
1338 if (rqs
&BETA_STATUS
.DRQ
) {
1339 emuz80
.memPokeB(emuz80
.HL
, data
);
1340 //wd93cpu.DbgMemIf.wm(wd93cpu.hl, data); // move byte from controller
1343 rqs
&= ~cast(uint)BETA_STATUS
.DRQ
;
1344 status
&= ~cast(uint)WD_STATUS
.WDS_DRQ
;
1346 if (seldrive
.t
.trkd
) {
1349 emuz80
.memPokeB(emuz80
.HL
, seldrive
.t
.trkd
[rwptr
++]);
1350 //wd93cpu.DbgMemIf.wm(wd93cpu.hl, seldrive.t.trkd[rwptr++]);
1356 z80pc
= z80pc
+2; // skip INI
1360 if (pc
== 0x3FD1 && trdosROMread(0x3FD2) == 0xA3 && (rqs
&BETA_STATUS
.DRQ
) && (rwlen
>1) && (state
== WDSTATE
.S_WRITE ||
(state2
== WDSTATE
.S_WRITE
&& state
== WDSTATE
.S_WAIT
))) {
1361 if (trdTrapsDebug
) conwriteln("TRDTRAP: write byte");
1362 ledUpdate(led1793Save
);
1364 //seldrive.t.write(rwptr++, wd93cpu.DbgMemIf.rm(wd93cpu.hl), 0);
1365 seldrive
.t
.write(rwptr
++, emuz80
.memPeekB(emuz80
.HL
), 0);
1370 z80pc
= z80pc
+2; // skip OUTI
1377 // ////////////////////////////////////////////////////////////////////////// //
1382 long motor
; // 0 - not spinning, >0 - time when it'll stop
1383 ubyte track
; // head position
1386 ubyte* rawdata
; // used in VirtualAlloc/VirtualFree
1389 uint[2][MAX_CYLS
] trklen
;
1390 ubyte*[2][MAX_CYLS
] trkd
; // данные
1391 ubyte*[2][MAX_CYLS
] trki
; // битовые карты синхроимпульсов
1392 ubyte*[2][MAX_CYLS
] trkwp
; // битовые карты сбойных байтов
1393 ubyte optype
; // bits: 0-not modified, 1-write sector, 2-format track
1396 TrackCache t
; // used in read/write image
1401 @property const(char)[] namestr () const pure nothrow @trusted @nogc {
1402 foreach (immutable idx
, char ch
; name
[]) if (ch
== 0) return name
[0..idx
];
1406 @property const(char)[] dscstr () const pure nothrow @trusted @nogc {
1407 foreach (immutable idx
, char ch
; dsc
[]) if (ch
== 0) return dsc
[0..idx
];
1412 ubyte index
; // INIT THIS!
1415 this (ubyte aindex
) nothrow @trusted { index
= aindex
; clear(); }
1417 ~this () nothrow @trusted { clear(); }
1419 void clear () nothrow @trusted {
1420 import core
.stdc
.string
: memset
;
1422 if (rawdata
!is null) {
1423 import core
.stdc
.stdlib
: free
;
1436 memset(trklen
.ptr
, 0, trklen
.sizeof
);
1437 memset(trkd
.ptr
, 0, trkd
.sizeof
);
1438 memset(trki
.ptr
, 0, trki
.sizeof
);
1439 memset(trkwp
.ptr
, 0, trkwp
.sizeof
);
1442 Config
.WD93
.writeProt(index
, false);
1443 /*wd93comp.wd.trkcache.clear(); [vv]*/
1447 protected void allocRawData (uint size
) nothrow @trusted {
1448 import core
.stdc
.stdlib
: realloc
;
1449 if (size
>= uint.max
/8) assert(0, "invalid allocRawData call");
1450 if (rawsize
!= size
) {
1451 rawdata
= cast(ubyte*)realloc(rawdata
, size
+1024);
1452 if (rawdata
is null) assert(0, "out of memory");
1455 rawdata
[0..rawsize
] = 0;
1458 void newDisk (uint cyls
, uint sides
) nothrow @trusted {
1459 import core
.stdc
.stdlib
: malloc
;
1463 uint len
= MAX_TRACK_LEN
;
1464 uint bitmap_len
= uint(len
+7U)>>3;
1465 uint len2
= len
+bitmap_len
*2;
1466 allocRawData(cyls
*sides
*len2
);
1467 for (uint c
= 0; c
< cyls
; c
++) {
1468 for (uint h
= 0; h
< sides
; h
++) {
1470 trkd
[c
][h
] = rawdata
+len2
*(c
*sides
+h
);
1471 trki
[c
][h
] = trkd
[c
][h
]+len
;
1472 trkwp
[c
][h
] = trkd
[c
][h
]+len
+bitmap_len
;
1475 // wd93comp.wd.trkcache.clear(); // already done in free()
1478 // //////////////////////////////////////////////////////////////////// //
1479 // TRD/SCL/HoBeta READER
1480 enum TRDDiskType
: ubyte {
1487 enum TRD_SIG
= 0x10;
1489 align(1) static struct TRDDirEntryBase
{
1495 ubyte seccnt
; // Длина файла в секторах
1498 align(1) static struct TRDDirEntry
{
1504 ubyte seccnt
; // Длина файла в секторах
1506 ubyte sec
; // Начальный сектор
1507 ubyte trk
; // Начальная дорожка
1510 align(1) static struct TRDSec9
{
1513 ubyte[224] Reserved
;
1514 ubyte FirstFreeSec
; // E1
1515 ubyte FirstFreeTrk
; // E2
1516 ubyte DiskType
; // E3
1517 ubyte FileCnt
; // E4
1518 ushort FreeSecCnt
; // E5, E6
1519 ubyte TrDosSig
; // E7
1520 ubyte[2] Res1
; // | 0
1521 ubyte[9] Res2
; // | 32
1523 ubyte DelFileCnt
; // F4
1524 char[8] Label
; // F5-FC
1525 ubyte[3] Res4
; // | 0
1528 align(1) static struct SCLHeader
{
1530 char[8] Sig
; // SINCLAIR
1532 TRDDirEntryBase
[0] Files
;
1535 // Проверка на то что диск имеет tr-dos формат
1536 bool isTRD () nothrow @trusted {
1537 static bool checkFiller (const(void)[] buf
, char fill
) nothrow @trusted @nogc {
1538 const(char)* p
= cast(const(char)*)buf
.ptr
;
1539 foreach (immutable _
; 0..buf
.length
) if (*p
++ != fill
) return false;
1543 t
.seek(this, 0, 0, SeekMode
.LoadSectors
);
1545 SectorHeader
* hdr
= t
.getSector(9);
1546 if (hdr
is null) return false;
1548 //conwriteln("000");
1549 if ((hdr
.l
&3) != 1) return false; // Проверка кода размера сектора (1 - 256 байт)
1551 TRDSec9
* sec9
= cast(TRDSec9
*)hdr
.data
;
1552 if (sec9
is null) return false;
1554 //conwriteln("001");
1555 if (sec9
.Zero
!= 0) return false;
1557 //conwriteln("002");
1558 if (sec9
.TrDosSig
!= TRD_SIG
) return false;
1560 //conwriteln("003");
1561 if (!(checkFiller(sec9
.Res2
[], ' ') ||
checkFiller(sec9
.Res2
[], 0))) return false;
1563 //conwriteln("004");
1564 if (!(sec9
.DiskType
== TRDDiskType
.DS_80 || sec9
.DiskType
== TRDDiskType
.DS_40 ||
1565 sec9
.DiskType
== TRDDiskType
.SS_80 || sec9
.DiskType
== TRDDiskType
.SS_40
)) return false;
1567 //conwriteln("005");
1571 void formatTRD (uint CylCnt
) nothrow @trusted {
1572 static immutable ubyte[16][3] lv
=
1573 [ [ 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 ],
1574 [ 1,9,2,10,3,11,4,12,5,13,6,14,7,15,8,16 ],
1575 [ 1,12,7,2,13,8,3,14,9,4,15,10,5,16,11,6 ] ];
1577 if (/*Config.WD93.trdInterleave < 0 ||*/ Config
.WD93
.trdInterleave
>= lv
.length
) {
1580 ilv
= lv
[Config
.WD93
.trdInterleave
].ptr
;
1583 for (uint c
= 0; c
< cyls
; c
++) {
1584 for (uint side
= 0; side
< 2; side
++) {
1585 t
.seek(this, c
, side
, SeekMode
.SeekOnly
);
1587 for (uint sn
= 0; sn
< 16; sn
++) {
1588 uint s
= ilv
[sn
]; //lv[Config.WD93.trdInterleave][sn];
1589 t
.hdr
[sn
].n
= cast(ubyte)s
;
1591 t
.hdr
[sn
].c
= cast(ubyte)c
;
1593 t
.hdr
[sn
].c1
= t
.hdr
[sn
].c2
= 0;
1594 t
.hdr
[sn
].data
= cast(ubyte*)1;
1601 void emptyDiskEmpty (uint FreeSecCnt
=2544) nothrow @trusted {
1602 uint SecCnt
= FreeSecCnt
+16;
1603 uint CylCnt
= SecCnt
/(16*2)+(SecCnt
%(16*2) ?
1 : 0);
1607 void emptyDiskTRD (uint FreeSecCnt
=2544) nothrow @trusted {
1608 uint SecCnt
= FreeSecCnt
+16;
1609 uint CylCnt
= SecCnt
/(16*2)+(SecCnt
%(16*2) ?
1 : 0);
1611 t
.seek(this, 0, 0, SeekMode
.LoadSectors
);
1612 SectorHeader
* sec9hdr
= t
.getSector(9);
1613 if (sec9hdr
is null) return;
1614 TRDSec9
* sec9
= cast(TRDSec9
*)sec9hdr
.data
;
1615 sec9
.FirstFreeTrk
= 1; // first free track
1616 sec9
.DiskType
= TRDDiskType
.DS_80
; // 80T,DS
1617 sec9
.FreeSecCnt
= cast(ushort)FreeSecCnt
; // free sec
1618 sec9
.TrDosSig
= TRD_SIG
; // trdos flag
1619 sec9
.Label
[] = ' '; // label
1620 sec9
.Res2
[] = ' '; // reserved
1621 t
.writeSector(9, sec9hdr
.data
); // update sector CRC
1624 bool addfile (const(ubyte)* hdr
, const(ubyte)* data
, bool tosys
=false) nothrow @trusted {
1625 import core
.stdc
.string
: memcpy
;
1627 if (!isTRD
) { conwriteln("not a TR-DOS disk"); return false; }
1629 t
.seek(this, 0, 0, SeekMode
.LoadSectors
);
1630 SectorHeader
* sec9hdr
= t
.getSector(9);
1631 assert(sec9hdr
!is null);
1633 TRDSec9
* sec9
= cast(TRDSec9
*)sec9hdr
.data
;
1634 assert(sec9
!is null);
1636 if (sec9
.FileCnt
>= 128) { conwriteln("TRD: too many files"); return false; } // Каталог заполнен полностью
1638 uint len
= (cast(TRDDirEntry
*)hdr
).seccnt
;
1639 uint pos
= sec9
.FileCnt
*TRDDirEntry
.sizeof
;
1640 SectorHeader
* dir
= t
.getSector(1+pos
/0x100);
1642 if (dir
is null) { conwriteln("TRD: can't find directory"); return false; }
1644 if (tosys
&& len
> 7*256) { conwriteln("TRD: file is too big for system area; using normal placement"); tosys
= false; }
1645 if (!tosys
&& sec9
.FreeSecCnt
< len
) { conwriteln("TRD: no room: len=", len
, "; freesectors=", sec9
.FreeSecCnt
); return false; } // На диске нет места
1646 //conwriteln("TRD: room: len=", len, "; freesectors=", sec9.FreeSecCnt);
1648 TRDDirEntry
* TrdDirEntry
= cast(TRDDirEntry
*)(dir
.data
+(pos
&0xFF));
1649 memcpy(TrdDirEntry
, hdr
, 14);
1651 TrdDirEntry
.sec
= 9;
1652 TrdDirEntry
.trk
= 0;
1654 TrdDirEntry
.sec
= sec9
.FirstFreeSec
;
1655 TrdDirEntry
.trk
= sec9
.FirstFreeTrk
;
1658 t
.writeSector(1+pos
/0x100, dir
.data
);
1660 //pos = sec9.FirstFreeSec+16*sec9.FirstFreeTrk;
1661 pos
= TrdDirEntry
.sec
+16*TrdDirEntry
.trk
;
1662 sec9
.FirstFreeSec
= (pos
+len
)&0x0F;
1663 sec9
.FirstFreeTrk
= ((pos
+len
)>>4)&0xff;
1665 sec9
.FreeSecCnt
-= len
;
1666 t
.writeSector(9, sec9hdr
.data
);
1668 // goto next track. s8 become invalid
1669 for (uint i
= 0; i
< len
; i
++, pos
++) {
1670 t
.seek(this, pos
/32, (pos
/16)&1, SeekMode
.LoadSectors
);
1671 if (!t
.trkd
) { conwriteln("TRD: no dir track"); return false; }
1672 if (!t
.writeSector((pos
&0x0F)+1, data
+i
*0x100)) { conwriteln("TRD: can't write sector"); return false; }
1678 // destroys wdsnapbuf - use after loading all files
1679 void addboot () nothrow @trusted {
1680 static immutable ubyte[273] bootsmall
= [
1681 0x62,0x6f,0x6f,0x74,0x20,0x20,0x20,0x20,0x42,0xfc,0x00,0xfc,0x00,0x00,0x01,0xd8,
1682 0x73,0x00,0x01,0xf8,0x00,0xe7,0xbc,0xa7,0x3a,0xf9,0xc0,0xb0,0x22,0x32,0x33,0x39,
1683 0x31,0x30,0x22,0x3a,0xea,0x3a,0xf7,0x22,0xe8,0x0f,0xe8,0xbf,0xc0,0xc3,0xbb,0xca,
1684 0xbd,0xc8,0xca,0xb6,0xbc,0xbb,0xe8,0xde,0xd6,0xd6,0xd9,0x22,0xfd,0x36,0x53,0x0e,
1685 0xcd,0x6b,0x0d,0x09,0x01,0x05,0x09,0xe5,0xcd,0x13,0x3d,0x06,0x13,0x21,0x52,0x5d,
1686 0x96,0xd7,0x23,0x10,0xfb,0x3e,0x02,0xcd,0x01,0x16,0xe1,0x54,0x5d,0x01,0x08,0x00,
1687 0x35,0x28,0x28,0x34,0x28,0x2a,0xe5,0x09,0x7e,0xe1,0xfe,0x42,0x20,0x1d,0xe5,0x3e,
1688 0x20,0xd7,0x7e,0xd7,0xed,0xa0,0xea,0x9c,0x5d,0x21,0xd1,0x70,0x34,0x7e,0xd6,0x03,
1689 0x28,0x05,0x30,0xfa,0x3e,0x20,0xd7,0x3e,0x20,0xd7,0xe1,0x0e,0x10,0x09,0x18,0xcd,
1690 0x26,0x57,0x7e,0x0f,0xb6,0x77,0x2b,0x7c,0xb7,0x20,0xf7,0x48,0x21,0xf5,0x57,0x97,
1691 0x11,0x0b,0x00,0x06,0x03,0x19,0xb9,0x28,0x06,0x3c,0x10,0xf9,0x2b,0x18,0xf4,0x06,
1692 0x0a,0xed,0x5b,0xd3,0x70,0x22,0xd3,0x70,0x3e,0x0e,0x12,0x13,0x36,0x22,0x23,0x10,
1693 0xf9,0x21,0x08,0x5c,0x70,0x7e,0xfe,0x0d,0x28,0x30,0xfe,0x20,0xca,0x66,0x5d,0xcb,
1694 0xef,0xfe,0x70,0x28,0x1b,0xfe,0x6f,0x28,0x0a,0xfe,0x61,0x28,0x11,0xfe,0x71,0x20,
1695 0xe4,0x0d,0x0d,0x0d,0xf2,0xc6,0x5d,0xed,0x4b,0xd1,0x70,0x0d,0x18,0xae,0x0c,0x0c,
1696 0x0c,0x3a,0xd1,0x70,0x3d,0x91,0x30,0xa4,0x18,0xa1,0x60,0x69,0x29,0x29,0x29,0x11,
1697 0x01,0x68,0x19,0x11,0x52,0x5d,0x0e,0x08,0xed,0xb0,0xc3,0x03,0x3d,0x80,0xaa,0x01,
1700 0x62,0x6F,0x6F,0x74,0x20,0x20,0x20,0x20,0x42,0xFC,0x00,0xFC,0x00,0x00,0x01,0xD8,
1701 0x73,0x00,0x01,0xF8,0x00,0xE7,0xBC,0xA7,0x3A,0xF9,0xC0,0xB0,0x22,0x32,0x33,0x39,
1702 0x30,0x38,0x22,0x3A,0xEA,0x3A,0xF7,0x22,0x17,0x09,0x20,0x50,0x4F,0x4C,0x54,0x45,
1703 0x52,0x47,0x45,0x59,0x53,0x54,0x2B,0x4B,0x38,0x22,0xFD,0x36,0x53,0x0E,0xCD,0x6B,
1704 0x0D,0x09,0x01,0x05,0x09,0xE5,0xCD,0x13,0x3D,0x11,0x52,0x5D,0x0E,0x11,0xCD,0x3C,
1705 0x20,0x3E,0x02,0xCD,0x01,0x16,0xE1,0x54,0x5D,0x01,0x08,0x00,0x35,0x28,0x28,0x34,
1706 0x28,0x2A,0xE5,0x09,0x7E,0xE1,0xFE,0x42,0x20,0x1D,0xE5,0x3E,0x20,0xD7,0x7E,0xD7,
1707 0xED,0xA0,0xEA,0x98,0x5D,0x21,0xD1,0x70,0x34,0x7E,0xD6,0x03,0x28,0x05,0x30,0xFA,
1708 0x3E,0x20,0xD7,0x3E,0x20,0xD7,0xE1,0x0E,0x10,0x09,0x18,0xCD,0x26,0x57,0x7E,0x0F,
1709 0xB6,0x77,0x2B,0xCB,0x74,0x20,0xF7,0x48,0x21,0xF5,0x57,0xAF,0x11,0x0B,0x00,0x06,
1710 0x03,0x19,0xB9,0x28,0x06,0x3C,0x10,0xF9,0x2B,0x18,0xF4,0x06,0x0A,0xED,0x5B,0xD3,
1711 0x70,0x22,0xD3,0x70,0x3E,0x0E,0x12,0x13,0x36,0x22,0x23,0x10,0xF9,0x21,0x08,0x5C,
1712 0x70,0x06,0x05,0x76,0x10,0xFD,0x7E,0xFE,0x20,0xCA,0x64,0x5D,0xAF,0xDB,0x1F,0x1F,
1713 0x38,0x2E,0x1F,0x38,0x1E,0x1F,0x38,0x26,0x1F,0x38,0x16,0x1F,0x30,0xE3,0x60,0x69,
1714 0x29,0x29,0x29,0x11,0x01,0x68,0x19,0x11,0x52,0x5D,0x0E,0x08,0xED,0xB0,0xC3,0x03,
1715 0x3D,0x0D,0x0D,0x0D,0xF2,0xC2,0x5D,0xED,0x4B,0xD1,0x70,0x0D,0x18,0x9A,0x0C,0x0C,
1716 0x0C,0x3A,0xD1,0x70,0x3D,0x91,0x30,0x90,0x18,0x8D,0x00,0x00,0x00,0x80,0xAA,0x01,
1721 conwriteln("addboot: checking disk...");
1722 if (!isTRD
) { conwriteln("not a TR-DOS disk"); return; }
1724 t
.seek(this, 0, 0, SeekMode
.LoadSectors
);
1726 SectorHeader
* hdr
= t
.getSector(9);
1727 assert(hdr
!is null);
1729 TRDSec9
* sec9
= cast(TRDSec9
*)hdr
.data
;
1730 assert(sec9
!is null);
1732 for (uint s
= 0; s
< 8; s
++) {
1733 SectorHeader
* sc
= t
.getSector(1+s
);
1734 if (sc
is null) return;
1735 TRDDirEntry
* TrdDirEntry
= cast(TRDDirEntry
*)sc
.data
;
1736 for (uint i
= 0; i
< 16; i
++) {
1737 import core
.stdc
.string
: memcmp
;
1738 if (memcmp(TrdDirEntry
[i
].name
.ptr
, "boot B".ptr
, 9) == 0) return;
1742 if (defaultBootHob
.length
) {
1743 conwriteln("addboot: adding boot...");
1744 wdsnapbuf
[0..defaultBootHob
.length
] = defaultBootHob
[];
1745 wdsnapsize
= cast(uint)defaultBootHob
.length
;
1747 conwriteln("addboot: adding built-in boot...");
1748 wdsnapbuf
[0..bootsmall
.length
] = bootsmall
[];
1749 wdsnapsize
= cast(uint)bootsmall
.length
;
1752 readHOB(Config
.WD93
.trdAddBootToSys
);
1755 bool readSCL () nothrow @trusted {
1757 SCLHeader
* SclHdr
= cast(SCLHeader
*)wdsnapbuf
;
1758 for (i
= 0; i
< SclHdr
.FileCnt
; i
++) size
+= SclHdr
.Files
.ptr
[i
].seccnt
;
1759 conwriteln("SCL size: ", size
, " sectors");
1760 if (size
< 2544) size
= 2544; else if (size
> 2635) size
= 2635;
1761 //if (size > 2544) size = 2544;
1762 //size = 2544; // so we'll have room for boot, for example
1764 ubyte* data
= wdsnapbuf
.ptr
+SCLHeader
.sizeof
+SclHdr
.FileCnt
*TRDDirEntryBase
.sizeof
;
1765 for (i
= 0; i
< SclHdr
.FileCnt
; i
++) {
1766 if (!addfile(cast(ubyte*)&SclHdr
.Files
.ptr
[i
], data
)) {
1767 conwriteln("can't add file #", i
, "...");
1770 data
+= SclHdr
.Files
.ptr
[i
].seccnt
*0x100;
1772 if (Config
.WD93
.trdAddBoot
) addboot();
1776 bool readHOB (bool tosys
=false) nothrow @trusted {
1777 if (rawdata
is null) emptyDiskTRD(2544);
1778 wdsnapbuf
[13] = wdsnapbuf
[14]; // copy length
1779 return addfile(wdsnapbuf
.ptr
, wdsnapbuf
.ptr
+0x11, tosys
);
1782 bool readTRD () nothrow @trusted {
1783 uint CylCnt
= wdsnapsize
/(256*16*2)+(wdsnapsize
%(256*16*2) ?
1 : 0);
1784 if (CylCnt
> MAX_CYLS
) {
1785 conwritefln
!"cylinders (%d) > MAX_CYLS(%d)"(CylCnt
, MAX_CYLS
);
1789 for (uint i
= 0; i
< wdsnapsize
; i
+= 0x100) {
1790 t
.seek(this, i
>>13, (i
>>12)&1, SeekMode
.LoadSectors
);
1791 t
.writeSector(((i
>>8)&0x0F)+1, wdsnapbuf
.ptr
+i
);
1793 if (Config
.WD93
.trdAddBoot
) addboot();
1797 void writeTRD (VFile fo
) {
1798 static immutable ubyte[256] zerosec
= 0;
1799 for (uint i
= 0; i
< cyls
*sides
*16; i
++) {
1800 t
.seek(this, i
>>5, (i
>>4)&1, SeekMode
.LoadSectors
);
1801 SectorHeader
* hdr
= t
.getSector((i
&0x0F)+1);
1802 const(ubyte)* ptr
= zerosec
.ptr
;
1803 if (hdr
&& hdr
.data
) ptr
= hdr
.data
;
1804 fo
.rawWriteExact(ptr
[0..256]);
1808 // will destroy wdsnapbuf
1809 void writeSCL (VFile fo
) {
1810 if (!isTRD
) { conwriteln("the disk doesn't look like TR-DOS one. T_T"); throw new Exception("SCL error"); }
1812 t
.seek(this, 0, 0, SeekMode
.LoadSectors
);
1813 SectorHeader
* sec9hdr
= t
.getSector(9);
1814 assert(sec9hdr
!is null);
1816 TRDSec9
* sec9
= cast(TRDSec9
*)sec9hdr
.data
;
1817 assert(sec9
!is null);
1819 if (sec9
.FileCnt
> 128) { conwriteln("TRD: too many files"); throw new Exception("SCL error"); } // Каталог заполнен полностью
1823 void put (const(void)[] data
) {
1824 import core
.stdc
.string
: memcpy
;
1825 if (wdsbpos
+data
.length
> wdsnapbuf
.length
) { conwriteln("wtf?!"); throw new Exception("SCL error"); }
1826 memcpy(wdsnapbuf
.ptr
+wdsbpos
, data
.ptr
, data
.length
);
1827 wdsbpos
+= cast(uint)data
.length
;
1830 void putB (ubyte b
) { put((&b
)[0..1]); }
1831 //void putW (ushort w) { put(w&0xff); put(w>>8); }
1834 putB(0); // file count, will be fixed later
1837 foreach (immutable uint fidx
; 0..sec9
.FileCnt
) {
1838 SectorHeader
* dir
= t
.getSector(1+fidx
/16);
1839 if (dir
is null) throw new Exception("SCL error");
1840 auto th
= (cast(TRDDirEntry
*)dir
.data
)+fidx
%16;
1841 if (th
.name
[0] == 0) continue;
1842 // increment file count
1844 //conwriteln("files: ", wdsnapbuf[8], " (", fidx, ")");
1845 put((cast(ubyte*)th
)[0..14]); // header
1849 foreach (immutable uint fidx
; 0..sec9
.FileCnt
) {
1850 t
.seek(this, 0, 0, SeekMode
.LoadSectors
);
1851 SectorHeader
* dir
= t
.getSector(1+fidx
/16);
1852 if (dir
is null) throw new Exception("SCL error");
1853 auto th
= (cast(TRDDirEntry
*)dir
.data
)+fidx
%16;
1854 if (th
.name
[0] == 0) continue;
1855 auto size
= th
.seccnt
;
1858 //conwriteln("file idx: ", fidx, "; sector: ", th.sec, "; track: ", th.trk, "; size: ", size);
1859 while (size
-- > 0) {
1860 t
.seek(this, trk
/2, trk
&1, SeekMode
.LoadSectors
);
1861 dir
= t
.getSector(1+sec
);
1862 if (dir
is null) throw new Exception("SCL error");
1863 put((cast(ubyte*)dir
.data
)[0..256]);
1864 if (++sec
== 16) { sec
= 0; ++trk
; }
1867 //conwriteln("done");
1869 // calculate checksum
1871 foreach (immutable ubyte b
; wdsnapbuf
[0..wdsbpos
]) csum
+= b
;
1873 putB((csum
>>8)&0xff);
1874 putB((csum
>>16)&0xff);
1875 putB((csum
>>24)&0xff);
1877 fo
.rawWriteExact(wdsnapbuf
[0..wdsbpos
]);
1880 // //////////////////////////////////////////////////////////////////// //
1882 static align(1) struct UDI
{
1889 // http://speccy.info/UDI
1890 bool readUDI () nothrow @trusted {
1891 if (wdsnapsize
< 16) return false;
1892 if ((cast(const(char)[])wdsnapbuf
)[0..4] != "UDI!") return false;
1893 if (wdsnapbuf
.ptr
[8] != 0) return false; // invalid version
1894 if (wdsnapbuf
.ptr
[9] == 0 || wdsnapbuf
.ptr
[9] > MAX_CYLS
) return false;
1895 if (wdsnapbuf
.ptr
[10] > 2) return false; // sides
1896 //if (wdsnapbuf.ptr[11] != 0) return false; // reserved
1898 if (wdsnapbuf
.ptr
[12] != 0 || wdsnapbuf
.ptr
[13] != 0) return false;
1899 if (wdsnapbuf
.ptr
[14] != 0 || wdsnapbuf
.ptr
[15] != 0) return false;
1904 const(ubyte)* ptr
= wdsnapbuf
.ptr
+0x10;
1906 for (c
= 0; c
<= wdsnapbuf
[9]; c
++) {
1907 for (s
= 0; s
<= wdsnapbuf
[10]; s
++) {
1908 if (*ptr
) return false;
1909 uint sz
= *cast(ushort*)(ptr
+1);
1910 sz
+= sz
/8+(sz
&7 ?
1 : 0);
1913 if (ptr
> wdsnapbuf
.ptr
+wdsnapsize
) return false;
1917 cyls
= wdsnapbuf
[9]+1;
1918 sides
= wdsnapbuf
[10]+1;
1920 if (cyls
> MAX_CYLS
) {
1921 conwritefln
!"cylinders (%d) > MAX_CYLS(%d)"(cyls
, MAX_CYLS
);
1926 conwritefln
!"sides (%d) > 2"(sides
);
1931 rawsize = align_by(mem, 4096);
1932 rawdata = (unsigned char*)VirtualAlloc(0, rawsize, MEM_COMMIT, PAGE_READWRITE);
1935 ptr
= wdsnapbuf
.ptr
+0x10;
1936 ubyte* dst
= rawdata
;
1938 for (c
= 0; c
< cyls
; c
++) {
1939 for (s
= 0; s
< sides
; s
++) {
1940 import core
.stdc
.string
: memcpy
;
1941 uint sz
= *cast(ushort*)(ptr
+1);
1944 trki
[c
][s
] = dst
+sz
;
1945 sz
+= sz
/8+(sz
&7 ?
1 : 0);
1946 memcpy(dst
, ptr
+3, sz
);
1951 uint crc1
= *(cast(uint*)ptr
);
1953 udi_buggy_crc(crc2
, wdsnapbuf
.ptr
, cast(uint)(ptr
-wdsnapbuf
.ptr
));
1955 conwritefln
!"udi crc mismatch: image=0x%08X, calc=0x%08X"(crc1
, crc2
);
1958 if (wdsnapbuf
[11]&1) {
1960 foreach (ref char dch
; dsc
[]) {
1961 if (*ptr
== 0) break;
1962 dch
= cast(char)*ptr
;
1967 if (Config
.WD93
.trdAddBoot
) addboot();
1971 void writeUDI (VFile fo
) {
1972 import core
.stdc
.string
: memcpy
, memset
;
1979 wdsnapbuf
[8] = wdsnapbuf
[11] = 0;
1980 wdsnapbuf
[9] = cast(ubyte)(cyls
-1);
1981 wdsnapbuf
[10] = cast(ubyte)(sides
-1);
1982 //*(unsigned*)(wdsnapbuf+12) = 0;
1984 ubyte* dst
= wdsnapbuf
.ptr
+0x10;
1985 for (uint c
= 0; c
< cyls
; c
++) {
1986 for (uint s
= 0; s
< sides
; s
++) {
1988 uint len
= trklen
[c
][s
];
1989 *cast(ushort*)dst
= cast(ushort)len
;
1991 memcpy(dst
, trkd
[c
][s
], len
);
1994 memcpy(dst
, trki
[c
][s
], len
);
1998 if (dscstr
.length
) {
1999 //strcpy((char*)dst, dsc), dst += strlen(dsc)+1, wdsnapbuf[11] = 1;
2000 memcpy(dst
, dscstr
.ptr
, dscstr
.length
);
2001 dst
+= dscstr
.length
;
2005 *cast(uint*)(wdsnapbuf
.ptr
+4) = cast(uint)(dst
-wdsnapbuf
.ptr
);
2007 udi_buggy_crc(crc
, wdsnapbuf
.ptr
, cast(uint)(dst
-wdsnapbuf
.ptr
));
2008 *cast(uint*)dst
= crc
;
2010 fo
.rawWriteExact(wdsnapbuf
[0..cast(uint)(dst
-wdsnapbuf
.ptr
)]);
2013 // //////////////////////////////////////////////////////////////////// //
2015 void formatISD () nothrow @trusted {
2016 static immutable ubyte[5] sn
= [ 1, 2, 3, 4, 9 ];
2018 for (uint c
= 0; c
< cyls
; c
++) {
2019 for (uint h
= 0; h
< 2; h
++) {
2020 t
.seek(this, c
, h
, SeekMode
.SeekOnly
);
2022 for (uint s
= 0; s
< 5; s
++) {
2024 t
.hdr
[s
].n
= cast(ubyte)n
;
2026 t
.hdr
[s
].c
= cast(ubyte)c
;
2028 t
.hdr
[s
].c1
= t
.hdr
[s
].c2
= 0;
2029 t
.hdr
[s
].data
= cast(ubyte*)1;
2036 bool readISD () nothrow @trusted {
2037 static immutable ubyte[5] sn
= [ 1, 2, 3, 4, 9 ];
2039 for (uint c
= 0; c
< cyls
; c
++) {
2040 for (uint h
= 0; h
< 2; h
++) {
2041 for (uint s
= 0; s
< 5; s
++) {
2042 t
.seek(this, c
, h
, SeekMode
.LoadSectors
);
2043 t
.writeSector(sn
[s
], wdsnapbuf
.ptr
+(c
*10+h
*5+s
)*1024);
2050 void writeISD (VFile fo
) {
2051 for (uint c
= 0; c
< 80; c
++) {
2052 for (uint h
= 0; h
< 2; h
++) {
2053 t
.seek(this, c
, h
, SeekMode
.LoadSectors
);
2054 for (uint s
= 0; s
< 5; s
++) {
2055 fo
.rawWriteExact(t
.hdr
[s
].data
[0..1024]);
2061 // //////////////////////////////////////////////////////////////////// //
2063 align(1) static struct TFdiSecHdr
{
2066 FL_DELETED_DATA
= 0x80,
2068 FL_GOOD_CRC_4096
= 0x20,
2069 FL_GOOD_CRC_2048
= 0x10,
2070 FL_GOOD_CRC_1024
= 0x8,
2071 FL_GOOD_CRC_512
= 0x4,
2072 FL_GOOD_CRC_256
= 0x2,
2073 FL_GOOD_CRC_128
= 0x1,
2081 // bit 7 - 1 = deleted data (F8) / 0 = normal data (FB)
2082 // bit 6 - 1 - sector with no data
2083 // bits 0..5 - 1 = good crc for sector size (128, 256, 512, 1024, 2048, 4096)
2088 align(1) static struct TFdiTrkHdr
{
2096 align(1) static struct TFdiHdr
{
2105 ubyte[0] AddData
; // AddLen -> TFdiAddInfo
2106 //TFdiTrkHdr Trk[c*h];
2109 align(1) static struct TFdiAddInfo
{
2113 ushort Ver
; // 2 - FDI 2
2114 ushort AddInfoType
; // 1 - bad bytes info
2115 uint TrkAddInfoOffset
; // -> TFdiTrkAddInfo
2119 align(1) static struct TFdiSecAddInfo
{
2121 ubyte Flags
; // 1 - Массив сбойных байтов присутствует
2122 // Смещение битового массива сбойных байтов внутри трэка
2123 // Число битов определяется размером сектора
2124 // Один бит соответствует одному сбойному байту
2128 align(1) static struct TFdiTrkAddInfo
{
2130 uint TrkOffset
; // Смещение массива сбойных байтов для трэка относительно TFdiAddInfo->DataOffset,
2131 // 0xFFFFFFFF - Массив описателей параметров секторов отсутствует
2132 TFdiSecAddInfo
[0] Sec
; // Spt
2135 // http://speccy.info/FDI
2136 bool readFDI () nothrow @trusted {
2137 if (wdsnapsize
< 16) return false;
2138 if ((cast(const(char)[])wdsnapbuf
)[0..3] != "FDI") return false;
2139 if (wdsnapbuf
.ptr
[5] != 0 || wdsnapbuf
.ptr
[4] == 0 || wdsnapbuf
.ptr
[4] > MAX_CYLS
) return false;
2140 if (wdsnapbuf
.ptr
[7] != 0 || wdsnapbuf
.ptr
[6] < 1 || wdsnapbuf
.ptr
[6] > 2) return false; // heads
2142 const(TFdiHdr
)* FdiHdr
= cast(const(TFdiHdr
)*)wdsnapbuf
.ptr
;
2143 uint cyls
= FdiHdr
.c
;
2144 uint sides
= FdiHdr
.h
;
2145 uint AddLen
= FdiHdr
.AddLen
;
2147 if (cyls
> MAX_CYLS
) {
2148 conwritefln
!"cylinders (%d) > MAX_CYLS(%d)"(cyls
, MAX_CYLS
);
2153 conwritefln
!"sides (%d) > 2"(sides
);
2157 newDisk(cyls
, sides
);
2159 const(TFdiAddInfo
)* FdiAddInfo
= null;
2160 const(TFdiTrkAddInfo
)* FdiTrkAddInfo
= null;
2161 if (AddLen
>= TFdiAddInfo
.sizeof
) {
2162 // Проверить параметры FdiAddInfo (версию, тип и т.д.)
2163 FdiAddInfo
= cast(const(TFdiAddInfo
)*)FdiHdr
.AddData
.ptr
;
2164 if (FdiAddInfo
.Ver
>= TFdiAddInfo
.FDI_2
&& FdiAddInfo
.AddInfoType
== TFdiAddInfo
.BAD_BYTES
) {
2165 FdiTrkAddInfo
= cast(const(TFdiTrkAddInfo
)*)(wdsnapbuf
.ptr
+FdiAddInfo
.TrkAddInfoOffset
);
2169 //strncpy(dsc, (const char *)&wdsnapbuf[FdiHdr.TextOffset], sizeof(dsc));
2170 //dsc[sizeof(dsc) - 1] = 0;
2172 foreach (immutable idx
, ref char dch
; dsc
[]) {
2173 if (wdsnapbuf
[FdiHdr
.TextOffset
+idx
] == 0) break;
2174 dch
= cast(char)(wdsnapbuf
[FdiHdr
.TextOffset
+idx
]);
2177 const(TFdiTrkHdr
)* FdiTrkHdr
= cast(const(TFdiTrkHdr
)*)&wdsnapbuf
[TFdiHdr
.sizeof
+FdiHdr
.AddLen
];
2178 ubyte* dat
= wdsnapbuf
.ptr
+FdiHdr
.DataOffset
;
2180 for (uint c
= 0; c
< cyls
; c
++) {
2181 for (uint s
= 0; s
< sides
; s
++) {
2182 t
.seek(this, c
,s
, SeekMode
.SeekOnly
);
2184 ubyte *t0
= dat
+FdiTrkHdr
.TrkOffset
;
2185 uint ns
= FdiTrkHdr
.Spt
;
2188 if (FdiTrkAddInfo
&& FdiTrkAddInfo
.TrkOffset
!= uint.max
) {
2189 wp0
= wdsnapbuf
.ptr
+FdiAddInfo
.DataOffset
+FdiTrkAddInfo
.TrkOffset
;
2190 if (wp0
> wdsnapbuf
.ptr
+wdsnapsize
) {
2191 conwriteln("bad bytes data is beyond disk image end");
2196 for (uint sec
= 0; sec
< ns
; sec
++) {
2197 t
.hdr
[sec
].c
= FdiTrkHdr
.Sec
.ptr
[sec
].c
;
2198 t
.hdr
[sec
].s
= FdiTrkHdr
.Sec
.ptr
[sec
].h
;
2199 t
.hdr
[sec
].n
= FdiTrkHdr
.Sec
.ptr
[sec
].r
;
2200 t
.hdr
[sec
].l
= FdiTrkHdr
.Sec
.ptr
[sec
].n
;
2202 t
.hdr
[sec
].wp
= null;
2204 if (FdiTrkHdr
.Sec
.ptr
[sec
].fl
&TFdiSecHdr
.FL_NO_DATA
) {
2205 t
.hdr
[sec
].data
= null;
2207 if (t0
+FdiTrkHdr
.Sec
.ptr
[sec
].DataOffset
> wdsnapbuf
.ptr
+wdsnapsize
) {
2208 conwriteln("sector data is beyond disk image end");
2211 t
.hdr
[sec
].data
= t0
+FdiTrkHdr
.Sec
.ptr
[sec
].DataOffset
;
2213 if (FdiTrkAddInfo
&& FdiTrkAddInfo
.TrkOffset
!= uint.max
) {
2214 t
.hdr
[sec
].wp
= (FdiTrkAddInfo
.Sec
.ptr
[sec
].Flags
&1 ?
(wp0
+FdiTrkAddInfo
.Sec
.ptr
[sec
].DataOffset
) : null);
2218 if(FdiTrkHdr.Sec.ptr[sec].n > 3)
2221 buf[0] = (FdiTrkHdr.Sec.ptr[sec].fl&TFdiSecHdr::FL_DELETED_DATA ? 0xF8 : 0xFB);
2222 memcpy(buf+1, t.hdr[sec].data, 128U<<(FdiTrkHdr.Sec.ptr[sec].n&3));
2224 ushort crc_calc = wd93_crc(buf, (128U<<(FdiTrkHdr.Sec.ptr[sec].n&3))+1);
2225 ushort crc_from_hdr = *(ushort*)(t.hdr[sec].data+(128U<<(FdiTrkHdr.Sec.ptr[sec].n&3)));
2226 printf("phys: c=%-2u, h=%u, s=%u|hdr: c=0x%02X, h=0x%02X, r=0x%02X, n=%02X(%u)|crc1=0x%04X, crc2=0x%04X\n",
2228 FdiTrkHdr.Sec.ptr[sec].c, FdiTrkHdr.Sec.ptr[sec].h, FdiTrkHdr.Sec.ptr[sec].r, FdiTrkHdr.Sec.ptr[sec].n, (FdiTrkHdr.Sec.ptr[sec].n&3),
2229 crc_calc, crc_from_hdr);
2231 if(crc_calc == crc_from_hdr)
2233 TFdiTrkHdr *FdiTrkHdrRW = const_cast<TFdiTrkHdr *>(FdiTrkHdr);
2234 FdiTrkHdrRW.Sec.ptr[sec].fl |= (1<<(FdiTrkHdr.Sec.ptr[sec].n&3));
2240 t
.hdr
[sec
].c2
= (FdiTrkHdr
.Sec
.ptr
[sec
].fl
&(1<<(FdiTrkHdr
.Sec
.ptr
[sec
].n
&3)) ?
0 : 2); // [vv]
2246 if (FdiTrkAddInfo
) {
2247 FdiTrkAddInfo
= cast(const(TFdiTrkAddInfo
)*)((cast(const(ubyte)*)FdiTrkAddInfo
)+TFdiTrkAddInfo
.sizeof
+
2248 (FdiTrkAddInfo
.TrkOffset
!= uint.max ? FdiTrkHdr
.Spt
*TFdiSecAddInfo
.sizeof
: 0));
2251 FdiTrkHdr
= cast(const(TFdiTrkHdr
)*)((cast(const(ubyte)*)FdiTrkHdr
)+TFdiTrkHdr
.sizeof
+FdiTrkHdr
.Spt
*TFdiSecHdr
.sizeof
);
2254 if (Config
.WD93
.trdAddBoot
) addboot();
2258 void writeFDI (VFile fo
) {
2259 auto fspos
= fo
.tell
;
2261 uint b
, c
, s
, se
, total_s
= 0;
2262 uint sectors_wp
= 0; // Общее число секторов для которых пишутся заголовки с дополнительной информацией
2263 uint total_size
= 0; // Общий размер данных занимаемый секторами
2265 // Подсчет общего числа секторов на диске
2266 for (c
= 0; c
< cyls
; c
++) {
2267 for (s
= 0; s
< sides
; s
++) {
2268 t
.seek(this, c
, s
, SeekMode
.LoadSectors
);
2269 for (se
= 0; se
< t
.s
; se
++) {
2270 total_size
+= (t
.hdr
[se
].data ? t
.hdr
[se
].datlen
: 0);
2272 for (se
= 0; se
< t
.s
; se
++) {
2273 if (t
.hdr
[se
].wp_start
) {
2282 uint AddLen
= (sectors_wp ?
cast(uint)TFdiAddInfo
.sizeof
: 0);
2283 uint tlen
= cast(uint)dscstr
.length
+1;
2284 uint hsize
= cast(uint)(TFdiHdr
.sizeof
+AddLen
+cyls
*sides
*TFdiTrkHdr
.sizeof
+total_s
*TFdiSecHdr
.sizeof
);
2285 uint AddHdrsSize
= cast(uint)(cyls
*sides
*TFdiTrkAddInfo
.sizeof
+sectors_wp
*TFdiSecAddInfo
.sizeof
);
2287 // Формирование FDI заголовка
2288 TFdiHdr
* FdiHdr
= cast(TFdiHdr
*)wdsnapbuf
;
2291 FdiHdr
.c
= cast(ushort)cyls
;
2292 FdiHdr
.h
= cast(ushort)sides
;
2293 FdiHdr
.TextOffset
= cast(ushort)hsize
;
2294 FdiHdr
.DataOffset
= cast(ushort)(FdiHdr
.TextOffset
+tlen
);
2295 FdiHdr
.AddLen
= cast(ushort)AddLen
;
2297 TFdiAddInfo
* FdiAddInfo
= cast(TFdiAddInfo
*)FdiHdr
.AddData
.ptr
;
2299 FdiAddInfo
.Ver
= TFdiAddInfo
.FDI_2
; // FDI ver 2
2300 FdiAddInfo
.AddInfoType
= TFdiAddInfo
.BAD_BYTES
; // Информация о сбойных байтах
2301 FdiAddInfo
.TrkAddInfoOffset
= FdiHdr
.DataOffset
+total_size
;
2302 FdiAddInfo
.DataOffset
= FdiAddInfo
.TrkAddInfoOffset
+AddHdrsSize
;
2305 // Запись FDI заголовка с дополнительными данными
2306 fo
.rawWriteExact((cast(ubyte*)FdiHdr
)[0..TFdiHdr
.sizeof
+AddLen
]);
2309 for (c
= 0; c
< cyls
; c
++) {
2310 for (s
= 0; s
< sides
; s
++) {
2311 t
.seek(this, c
, s
, SeekMode
.LoadSectors
);
2313 // Формирование заголовка трэка
2314 TFdiTrkHdr FdiTrkHdr
;
2315 FdiTrkHdr
.TrkOffset
= trkoffs
;
2317 FdiTrkHdr
.Spt
= cast(ubyte)t
.s
;
2319 // Запись заголовка трэка
2320 fo
.rawWriteExact((cast(ubyte*)&FdiTrkHdr
)[0..FdiTrkHdr
.sizeof
]);
2323 for (se
= 0; se
< t
.s
; se
++) {
2324 // Формирование заголовка сектора
2325 TFdiSecHdr FdiSecHdr
;
2326 FdiSecHdr
.c
= t
.hdr
[se
].c
;
2327 FdiSecHdr
.h
= t
.hdr
[se
].s
;
2328 FdiSecHdr
.r
= t
.hdr
[se
].n
;
2329 FdiSecHdr
.n
= t
.hdr
[se
].l
;
2332 if (t
.hdr
[se
].data
) {
2333 if (t
.hdr
[se
].data
[-1] == 0xF8)
2334 FdiSecHdr
.fl |
= TFdiSecHdr
.FL_DELETED_DATA
;
2336 FdiSecHdr
.fl |
= (t
.hdr
[se
].c2 ?
(1<<(t
.hdr
[se
].l
&3)) : 0); // [vv]
2338 FdiSecHdr
.fl |
= TFdiSecHdr
.FL_NO_DATA
;
2341 FdiSecHdr
.DataOffset
= cast(ushort)secoffs
;
2343 // Запись заголовка сектора
2344 fo
.rawWriteExact((cast(ubyte*)&FdiSecHdr
)[0..FdiSecHdr
.sizeof
]);
2345 secoffs
+= t
.hdr
[se
].datlen
;
2351 // Запись комментария
2352 auto cfpos
= fo
.tell
;
2353 if (cfpos
> fspos
+FdiHdr
.TextOffset
) assert(0, "FDI writer internal error");
2354 while (cfpos
< fspos
+FdiHdr
.TextOffset
) {
2355 fo
.writeNum
!ubyte(0);
2358 //fo.seek(fspos+FdiHdr.TextOffset);
2359 if (tlen
> 1) fo
.rawWriteExact(dsc
[0..tlen
]);
2360 fo
.writeNum
!ubyte(0);
2362 // Запись зон данных трэков
2363 for (c
= 0; c
< cyls
; c
++) {
2364 for (s
= 0; s
< sides
; s
++) {
2365 t
.seek(this, c
, s
, SeekMode
.LoadSectors
);
2366 for (uint sex
= 0; sex
< t
.s
; sex
++) {
2367 if (t
.hdr
[sex
].data
) {
2368 fo
.rawWriteExact((cast(ubyte*)t
.hdr
[sex
].data
)[0..t
.hdr
[sex
].datlen
]);
2374 // Запись дополниетльной информации (информации о сбойных байтах)
2377 for (c
= 0; c
< cyls
; c
++) {
2378 for (s
= 0; s
< sides
; s
++) {
2379 t
.seek(this, c
, s
, SeekMode
.LoadSectors
);
2381 // Формирование заголовка трэка
2382 TFdiTrkAddInfo FdiTrkAddInfo
;
2383 FdiTrkAddInfo
.TrkOffset
= uint.max
;
2384 for (b
= 0; b
< t
.trklen
; b
++) {
2385 if (t
.getWriteProtected(b
)) {
2386 FdiTrkAddInfo
.TrkOffset
= trkoffs
;
2391 // Запись заголовка трэка
2392 fo
.rawWriteExact((cast(ubyte*)&FdiTrkAddInfo
)[0..FdiTrkAddInfo
.sizeof
]);
2395 if (FdiTrkAddInfo
.TrkOffset
!= uint.max
) {
2396 for (se
= 0; se
< t
.s
; se
++) {
2397 // Формирование заголовка сектора
2398 TFdiSecAddInfo FdiSecAddInfo
;
2399 FdiSecAddInfo
.Flags
= 0;
2400 FdiSecAddInfo
.DataOffset
= 0;
2402 if (t
.hdr
[se
].wp_start
) {
2403 FdiSecAddInfo
.Flags |
= 1;
2404 FdiSecAddInfo
.DataOffset
= cast(ushort)secoffs
;
2407 // Запись заголовка сектора
2408 fo
.rawWriteExact((cast(ubyte*)&FdiSecAddInfo
)[0..FdiSecAddInfo
.sizeof
]);
2409 secoffs
+= (t
.hdr
[se
].wp_start ?
((t
.hdr
[se
].datlen
+7)>>3) : 0);
2416 // Запись зон сбойных байтов
2417 for (c
= 0; c
< cyls
; c
++) {
2418 for (s
= 0; s
< sides
; s
++) {
2419 t
.seek(this, c
, s
, SeekMode
.LoadSectors
);
2420 for (uint sex
= 0; sex
< t
.s
; sex
++) {
2421 if (t
.hdr
[sex
].wp_start
) {
2422 uint nbits
= t
.hdr
[sex
].datlen
;
2423 ubyte[1024U>>3U] wp_bits
= 0;
2424 for (b
= 0; b
< nbits
; b
++) {
2425 if (t
.getWriteProtected(t
.hdr
[sex
].wp_start
+b
)) set_bit(wp_bits
.ptr
, b
);
2427 fo
.rawWriteExact(wp_bits
[0..(nbits
+7)>>3]);
2435 // //////////////////////////////////////////////////////////////////// //
2436 // `wdsnapbuf` and `wdsnapsize` should be set
2437 bool readDiskImage () nothrow @trusted {
2438 if (wdsnapsize
< 16) return false; // alas
2440 if ((cast(const(char)[])wdsnapbuf
)[0..8] == "SINCLAIR") return readSCL();
2441 if ((cast(const(char)[])wdsnapbuf
)[0..4] == "UDI!") return readUDI();
2442 if ((cast(const(char)[])wdsnapbuf
)[0..3] == "FDI") return readFDI();
2443 if (wdsnapsize
< 256*16) return false; // simple sanity check