forked from geraldholdsworth/DiscImageManager
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDiscImage_ADFS.pas
4806 lines (4756 loc) · 162 KB
/
DiscImage_ADFS.pas
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
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//++++++++++++++++++ Acorn ADFS ++++++++++++++++++++++++++++++++++++++++++++++++
{-------------------------------------------------------------------------------
Identifies an ADFS disc and which type
-------------------------------------------------------------------------------}
function TDiscImage.ID_ADFS: Boolean;
var
Check0,
Check1,
Check0a,
Check1a : Byte;
ctr,ds,
dr_size,
dr_ptr,
zone : Cardinal;
begin
Result:=False;
if FFormat=diInvalidImg then
begin
ResetVariables;
//Interleaving, depending on the option
Finterleave:=FForceInter;
if Finterleave=0 then Finterleave:=2; //Auto, so pick INT for ADFS
//Is there actually any data?
if GetDataLength>0 then
begin
//Check for Old Map
Check0 :=ReadByte($0FF);
Check1 :=ReadByte($1FF);
Check0a :=ByteCheckSum($0000,$100,False);
Check1a :=ByteCheckSum($0100,$100,False);
//Do the checksums on both sectors
if (Check0a=Check0)
and (Check1a=Check1) then
begin
//Checks are successful, now find out which type of disc: S/M/L/D
Result:=True;
//FFormat:=$1F; //Default to ADFS Hard drive
FMap:=False; //Set to old map
FDirType:=diADFSOldDir; //Set to old directory
//Check where the root is.
if (Read24b($6D6)=$000002) //Address of the root ($200 for old dir)
and(ReadByte($200)=ReadByte($6FA)) then //Directory check bytes
FDirType:=diADFSOldDir; //old map, old directory - either S, M or L
if (Read24b($BDA)=$000004) //Address of the root ($400 for new dir)
and(ReadByte($400)=ReadByte($BFA)) then //Directory check bytes
begin
FDirType:=diADFSNewDir; //So, old map, new directory must be ADFS D
FFormat:=diAcornADFS<<4+$03;
end;
disc_size[0]:=Read24b($0FC)*$100;
//The above checks will pass through if the first 512 bytes are all zeros,
//meaning a, e.g., Commodore 64 image will be IDed as an ADFS Old Map.
//So, we need to check the disc size is not zero also.
if disc_size[0]=0 then
begin
Result:=False;
ResetVariables;
end;
if(disc_size[0]>0)and(FFormat=diInvalidImg)then
begin
//Not a reliable way of determining disc shape. However, there are three
//different sizes of the same format.
ctr:=0;
while(FFormat=diInvalidImg)and(ctr<2)do
begin
//First check the size as recorded
ds:=disc_size[0];
//If that fails, we'll check the total data length
if ctr=1 then ds:=GetDataLength;
case ds of
163840: FFormat:=diAcornADFS<<4+$00; // ADFS S
327680: FFormat:=diAcornADFS<<4+$01; // ADFS M
655360: FFormat:=diAcornADFS<<4+$02; // ADFS L
end;
//800K size was IDed earlier, but anything above will be HDD
if(ds>819200)and(FFormat=diInvalidImg)then
FFormat:=diAcornADFS<<4+$0F; //Hard drive
//Next step
inc(ctr);
//Unless we have found a format
if FFormat<>diInvalidImg then ctr:=2;
end;
//Do we have an ADFS L, but reported size is $000AA0?
if(FFormat=diAcornADFS<<4+2)and(Read24b($0FC)=$000AA0)
and(ReadString($6FB,-4)='Hugo')then
begin
FDOSPresent:=True;
//Automatic interleave?
if FForceInter=0 then
begin
//If this is not as expected, then change the interleave
if(ReadByte($1000)<>$FF)or(Read16b($1001)<>$FFFF)then
begin
if FInterleave=2 then FInterleave:=1 else FInterleave:=2;
//Still not as expected? Change back and clear the flag
if(ReadByte($1000)<>$FF)or(Read16b($1001)<>$FFFF)then
begin
if FInterleave=2 then FInterleave:=1 else FInterleave:=2;
FDOSPresent:=False;
end;
end;
end;
//If, at this stage, the flag is set, reset the disc size to 4K.
if FDOSPresent then disc_size[0]:=$1000;
end;
//Do we still have a mystery size? We'll ignore 800K as this was set earlier
if(FFormat=diInvalidImg)and(disc_size[0]<819200)and(GetDataLength<819200)then
FFormat:=diAcornADFS<<4+$0E; //Mark it as an unknown shape
//Check for AFS Level 3 partition
//Value at $0F6 must be zero for normal ADFS, so if non zero will be AFS
FAFSPresent:=ReadByte($0F6)<>0;
//Check that the root is valid - could be AFS with no ADFS
if FAFSPresent then //We'll just check the end name, and set to AFS L3
if ReadString($6FB,-4)<>'Hugo' then FFormat:=diAcornFS<<4+2;
end;
if(GetMinorFormatNumber<3)or(FFormat=diAcornADFS<<4+$0E)then //ADFS S,M,L or unknown
begin
//Set the number of sectors per track - this is not held in the disc
secspertrack:= 16;
//Size of the sectors in bytes
secsize :=256;
end;
if GetMinorFormatNumber=3 then //ADFS D
begin
//Set the number of sectors per track - this is not held in the disc
secspertrack:= 5;
//Size of the sectors in bytes
secsize :=1024;
end;
if GetMinorFormatNumber=$F then //ADFS Hard drive
begin
secspertrack:= 16;
secsize :=256;
//Make sure that we get a whole number of sectors on every track
if disc_size[0]mod(secspertrack*secsize)>0 then
secspertrack:=Round(secspertrack
*((disc_size[0]/ (secspertrack*secsize))
- (disc_size[0]div(secspertrack*secsize))));
end;
end;
if not Result then
begin
FMap:=True; //Assume New Map for now
ctr:=0;
dr_ptr:=$0000;
repeat
if ctr=0 then dr_ptr:=$0004; //Point the disc record to $0004
if ctr=1 then dr_ptr:=$0DC0; //Point the disc record to $0DC0
if ctr=2 then emuheader:=$0200;//Might have a header, added by an emulator
//Then find the map
dr_size :=60; //Disc record size
//Read some values from the disc record in the boot block
//These are the minimum we require to find the map
if emuheader+dr_ptr+$40<GetDataLength then
begin
secsize :=1<<ReadByte(dr_ptr+$00); //Sector size
secspertrack:=ReadByte(dr_ptr+$01); //Sectors per track
heads :=ReadByte(dr_ptr+$02); //Number of heads
idlen :=ReadByte(dr_ptr+$04); //idlen
bpmb :=1<<ReadByte(dr_ptr+$05); //Bits per map bit
nzones :=ReadByte(dr_ptr+$09)
+ReadByte(dr_ptr+$2A)*$100; //nzones is 2 bytes, for E+ and F+
zone_spare:=Read16b(dr_ptr+$0A); //Zone spare bits
rootfrag :=Read32b(dr_ptr+$0C); //Indirect address of root
root_size :=Read32b(dr_ptr+$30); //Size of root (big dir only)
if root_size=0 then root_size:=$800; //Not big map
end;
//If there are more than 2 zones, we need the disc record size in bits
if nzones>2 then
zone:=dr_size*8
else
zone:=0;
//Calculate the start of the map
bootmap:=((nzones div 2)*(8*secsize-zone_spare)-zone)*bpmb;
//If the bootmap is within the size of the disc, and there is at least
//a single zone then continue
if(emuheader+bootmap+nzones*secsize<GetDataLength)and(nzones>0)then
begin
Result:=True;
//Check the checksums for each zone
Check1:=$00;
for zone:=0 to nzones-1 do
begin
//ZoneCheck checksum
Check0:=ReadByte(bootmap+zone*secsize+$00);
//CrossCheck checksum
Check1:=Check1 XOR ReadByte(bootmap+zone*secsize+$03);
//Check failed, reset format
if Check0<>GeneralChecksum(bootmap+zone*secsize,
secsize,secsize+4,$4,true) then
Result:=False;
end;
//Cross zone check - should be $FF
if Check1<>$FF then
Result:=False;
end;
//Check the bootblock checksum
if (ctr>0) and (Result) then
begin
Check0:=ReadByte($0C00+$1FF);
if ByteChecksum($0C00,$200,True)<>Check0 then Result:=False;
end;
inc(ctr);
until (Result) or (ctr=3);
if Result then
begin
case ctr of
1: FFormat:=diAcornADFS<<4+$04; //ADFS E/E+
2: FFormat:=diAcornADFS<<4+$06; //ADFS F/F+
3: FFormat:=diAcornADFS<<4+$0F; //ADFS Hard Drive
end;
//Boot block checksum, if there is a partial disc record at $0DC0
if dr_ptr=$DC0 then
begin
Check0 :=ByteChecksum($C00,$200,True);
Check0a:=ReadByte($DFF);
if Check0<>Check0a then FFormat:=diInvalidImg; //Checksums do not match
end;
end;
//Check for type of directory, and change the format if necessary
if FFormat<>diInvalidImg then
begin
FDirType:=diADFSNewDir; //New Directory
//Determine if it is a '+' format by reading the version flag
if ReadByte(dr_ptr+$2C)>0 then
begin
if FFormat<>diAcornADFS<<4+$0F then inc(FFormat);
FDirType:=diADFSBigDir;
end;
//Root address for old map
if FDirType=diADFSOldDir then
begin
root:=$200;
root_size:=1280;
end;
if(FDirType=diADFSNewDir)and(not FMap)then
begin
root:=$400;
root_size:=2048;
end;
end;
end;
//Check for DOS partition on ADFS Hard drives
if(FFormat=diAcornADFS<<4+$F)and(not FAFSPresent)and(not FDOSPresent)
and(FDirType=diADFSOldDir)and(FOpenDOSPart)then
begin
//Start at the root
ctr:=root;
ds:=GetDataLength;
while(ctr<=ds)and(not FDOSPresent)do
begin
//Is there one here?
IDDOSPartition(ctr);
//Next sector
inc(ctr,$100);
end;
end;
end;
//Return a true or false
Result:=GetMajorFormatNumber=diAcornADFS;
if Result then root_name:='$';
end;
end;
{-------------------------------------------------------------------------------
Read ADFS Directory
-------------------------------------------------------------------------------}
function TDiscImage.ReadADFSDir(dirname: String; sector: Cardinal): TDir;
var
Entry : TDirEntry;
temp,
StartName,EndName,
dirtitle,pathname : String;
ptr,
dircheck,numentrys,
dirsize,
entrys,nameheap,
tail,NameLen,
entrysize,offset,
NameOff,amt : Cardinal;
addr : TFragmentArray;
StartSeq,EndSeq,
dirchk,NewDirAtts : Byte;
validdir,validentry,
endofentry : Boolean;
dirbuffer : TDIByteArray;
begin
SetLength(dirbuffer,0);
RemoveControl(dirname);
//This is only here to stop the hints that Result isn't intialised
Result.Directory:=dirname;
//Reset the Result TDir to default values
ResetDir(Result);
//Store complete path
pathname:=dirname;
//Update the progress indicator
UpdateProgress('Reading '+pathname);
//Store directory name
if Pos(dir_sep,dirname)>0 then
begin
temp:=dirname;
repeat
temp:=Copy(temp,Pos(dir_sep,temp)+1,Length(temp))
until Pos(dir_sep,temp)=0;
Result.Directory:=temp;
end
else
Result.Directory:=dirname;
//Reset the Partition flags
Result.AFSPartition:=False;
Result.DOSPartition:=False;
//Set the sector
Result.Sector:=sector;
//Initialise some of the variables
StartSeq :=$00;
EndSeq :=$FF;
numentrys :=0;
tail :=$00;
dirsize :=$00;
nameheap :=$00;
entrys :=0;
entrysize :=$00;
NewDirAtts :=$00;
dirchk :=0;
namesize :=$00;
dirtitle :='';
StartName :='';
EndName :='';
SetLength(addr,0);
//Get the offset address
if FMap then
begin
//New Map, so the sector will be an internal disc address
if dirname=root_name then //root address
begin
if rootfrag=sector then addr:=NewDiscAddrToOffset(rootfrag)
else
begin
SetLength(addr,1);
addr[0].Offset:=sector;
addr[0].Length:=root_size;
end;
Result.Sector:=rootfrag;
dirsize:=addr[0].Length;
end
else //other object address
addr:=NewDiscAddrToOffset(sector);
//We need the total length of the big directory
if Length(addr)>0 then
begin
if FDirType=diADFSOldDir then dirsize:=1280;
if FDirType=diADFSNewDir then dirsize:=2048;
if FDirType=diADFSBigDir then
for amt:=0 to Length(addr)-1 do inc(dirsize,addr[amt].Length);
end;
end
else
begin
//But we need it as an offset into the data, but set up as fragments
SetLength(addr,1);
//Is Old Map, so offset is just the sector * $100
addr[0].Offset:=sector*$100;
//Length - old and new type directories are fixed length
if FDirType=diADFSOldDir then addr[0].Length:=1280;
if FDirType=diADFSNewDir then addr[0].Length:=2048;
//But big type directories the length varies - we worked this out above
dirsize:=addr[0].Length;
end;
Result.Length:=dirsize;
//Read the entire directory into a buffer
if ExtractFragmentedData(addr,dirsize,dirbuffer) then
begin
sector:=0;
//Read in the directory header
case FDirType of
diADFSOldDir,diADFSNewDir: //Old and New Directory
begin
StartSeq :=ReadByte(0,dirbuffer); //Start Sequence Number to match with end
StartName:=ReadString(1,-4,dirbuffer); //Hugo or Nick
if FDirType=diADFSOldDir then //Old Directory
begin
numentrys:=47; //Number of entries per directory
dirsize :=1280; //Directory size in bytes
tail :=$35; //Size of directory tail
end;
if FDirType=diADFSNewDir then //New Directory
begin
numentrys:=77; //Number of entries per directory
dirsize :=2048; //Directory size in bytes
tail :=$29; //Size of directory tail
end;
entrys :=$05; //Pointer to entries, from sector
entrysize:=$1A; //Size of each entry
end;
diADFSBigDir: //Big Directory
begin
StartSeq :=ReadByte(0,dirbuffer); //Start sequence number to match with end
StartName:=ReadString($04,-4,dirbuffer);//Should be SBPr
NameLen :=Read32b($08,dirbuffer); //Length of directory name
dirsize :=Read32b($0C,dirbuffer); //Directory size in bytes
numentrys:=Read32b($10,dirbuffer); //Number of entries in this directory
namesize :=Read32b($14,dirbuffer); //Size of the name heap in bytes
dirname :=ReadString($1C,-NameLen,dirbuffer);//Directory name
entrys :=(($1C+NameLen+1+3)div 4)*4; //Pointer to entries, from sector
tail :=$08; //Size of directory tail
entrysize:=$1C; //Size of each entry
nameheap :=entrys+numentrys*entrysize; //Offset of name heap
end;
end;
//Now we know the size of the directory, we can read in the tail
tail:=dirsize-tail;
//And mark it on the Free Space Map
for amt:=0 to dirsize do ADFSFillFreeSpaceMap(amt,$FD);
//Not all of the tail is read in
case FDirType of
diADFSOldDir:
begin
dirtitle:=ReadString(tail+$0E,-19,dirbuffer);//Title of the directory
EndSeq :=ReadByte(tail+$2F,dirbuffer); //End sequence number to match with start
EndName :=ReadString(tail+$30,-4,dirbuffer); //Hugo or Nick
dirchk :=ReadByte(tail+$34,dirbuffer); //Directory Check Byte
end;
diADFSNewDir:
begin
dirtitle:=ReadString(tail+$06,-19,dirbuffer);//Title of the directory
EndSeq :=ReadByte(tail+$23,dirbuffer); //End sequence number to match with start
EndName :=ReadString(tail+$24,-4,dirbuffer); //Hugo or Nick
dirchk :=ReadByte(tail+$28,dirbuffer); //Directory Check Byte
end;
diADFSBigDir:
begin
EndName :=ReadString(tail+$00,-4,dirbuffer); //Should be oven
EndSeq :=ReadByte(tail+$04,dirbuffer); //End sequence number to match with start
dirtitle:=dirname; //Does not have a directory title
dirchk :=ReadByte(tail+$07,dirbuffer); //Directory Check Byte
end;
end;
//Save the directory title
Result.Title:=dirtitle;
//Check for broken directory
//This can result in having a valid directory structure, but a broken directory
//ADFS normally refuses to list broken directories, but we will list them anyway,
//just marking the directory as broken and return an error code
Result.ErrorCode:=0;
//Start and End sequence numbers do not match
if EndSeq<>StartSeq then
Result.ErrorCode:=Result.ErrorCode OR $01;
if FDirType<diADFSBigDir then
if StartName<>EndName then//Start and End names do not match (Hugo or Nick)
Result.ErrorCode:=Result.ErrorCode OR $02
else //Start and End names are not valid for Old or New Directories
if((StartName<>'Hugo')and(StartName<>'Nick'))
or((EndName<>'Hugo')and(EndName<>'Nick'))then
Result.ErrorCode:=Result.ErrorCode OR $40;
//Start and End names are not valid for Big Directories
if(FDirType=diADFSBigDir)and((StartName<>'SBPr') or (EndName<>'oven'))then
Result.ErrorCode:=Result.ErrorCode OR $04;
//Not sector aligned
if sector mod secsize<>0 then
Result.ErrorCode:=Result.ErrorCode OR $20;
Result.Broken:=Result.ErrorCode<>$00;
//Check for valid directory
//We won't try and get the directory structure if it appears that it is invalid
//Could just be that one of the names has got corrupt, but could be much worse
validdir:=False;
if((FDirType<diADFSBigDir)and(StartName=EndName)and((StartName='Hugo')or(StartName='Nick')))
or((FDirType=diADFSBigDir)and(StartName='SBPr')and(EndName='oven'))then
validdir:=True;
//Load the entries
if validdir then
begin
//Set up the array
SetLength(Result.Entries,0);
//Pointer to entry number - we'll use this later to find the end of the list
ptr:=0;
//Flag for a valid entry
validentry:=True;
while (ptr<numentrys) and (validentry) do
begin
//Offset to entry
offset:=entrys+ptr*entrysize;
//Blank the entries
ResetDirEntry(Entry);
//Sometimes the pathname has the root missing
if pathname[1]=dir_sep then pathname:=root_name+pathname;
Entry.Parent:=pathname;
//Read in the entries
case FDirType of
diADFSOldDir,diADFSNewDir: //Old and New Directory
if ReadByte(offset,dirbuffer)<>0 then //0 marks the end of the entries
begin
Entry.Filename :=ReadString(offset,-10,dirbuffer,True);//Filename (including attributes for old)
Entry.LoadAddr :=Read32b(offset+$0A,dirbuffer); //Load Address (can be timestamp)
Entry.ExecAddr :=Read32b(offset+$0E,dirbuffer); //Execution Address (can be filetype)
Entry.Length :=Read32b(offset+$12,dirbuffer); //Length in bytes
Entry.Sector :=Read24b(offset+$16,dirbuffer); //How to find the file
temp:='';
//Old directories - attributes are in the filename's top bit
if FDirType=diADFSOldDir then
begin
endofentry:=False;
if Length(Entry.Filename)>0 then
begin
for amt:=0 to 9 do
begin
if ReadByte(offset+amt,dirbuffer)>>7=1 then
temp:=temp+ADFSOldAttributes[amt+1];
if amt<Length(Entry.Filename) then
begin
if ord(Entry.Filename[amt+1])AND$7F=$0D then endofentry:=True;
//Clear the top bit
if not endofentry then
Entry.Filename[amt+1]:=chr(ord(Entry.Filename[amt+1])AND$7F)
else
Entry.Filename[amt+1]:=' ';
end;
end;
RemoveSpaces(Entry.Filename);
end;
//Reverse the attribute order to match actual ADFS
if Length(temp)>0 then
for amt:=Length(temp) downto 1 do
Entry.Attributes:=Entry.Attributes+temp[amt];//Attributes
end;
//New directories - attributes are separate, so filenames can have top bit set
if FDirType=diADFSNewDir then
NewDirAtts :=ReadByte(offset+$19,dirbuffer); //Attributes will be disected with Big
end
else validentry:=False;
diADFSBigDir: //Big Directory
begin
Entry.LoadAddr :=Read32b(offset+$00,dirbuffer); //Load Address
Entry.ExecAddr :=Read32b(offset+$04,dirbuffer); //Execution Address
Entry.Length :=Read32b(offset+$08,dirbuffer); //Length in bytes
Entry.Sector :=Read32b(offset+$0C,dirbuffer); //How to find file
NewDirAtts :=Read32b(offset+$10,dirbuffer); //Attributes (as New)
NameLen :=Read32b(offset+$14,dirbuffer); //Length of filename
NameOff :=Read32b(offset+$18,dirbuffer); //Offset into heap of filename
Entry.Filename :=ReadString(nameheap+NameOff,-NameLen,dirbuffer); //Filename
end;
end;
RemoveControl(Entry.Filename);
//Attributes for New and Big
if FDirType>diADFSOldDir then
begin
temp:='';
for amt:=0 to 5 do
if IsBitSet(NewDirAtts,amt) then temp:=temp+ADFSNewAttributes[amt+1];
//Reverse the attribute order to match actual ADFS
if Length(temp)>1 then
for amt:=Length(temp) downto 1 do
Entry.Attributes:=Entry.Attributes+temp[amt];
if Length(temp)=1 then Entry.Attributes:=temp;
end;
//If we have a valid entry then we can see if it is filetyped/datestamped
//and add it to the list
if validentry then
begin
//RISC OS - file may be datestamped and filetyped
ADFSCalcFileDate(Entry);
//Not a directory - default. Will be determined later
Entry.DirRef:=-1;
//Is this entry the DOS partition?
if doshead=Entry.Sector*secsize then Entry.IsDOSPart:=True;
//Add to the result
SetLength(Result.Entries,Length(Result.Entries)+1);
Result.Entries[Length(Result.Entries)-1]:=Entry;
//Move on to next
inc(ptr);
end;
end;
//Now we can run the directory check on DirCheckByte
//But only for New and Big Directories, optional for old (ignored if zero)
if((FDirType=diADFSOldDir)and(dirchk<>0))or(FDirType>diADFSOldDir)then
begin
//This value is the check byte.
dircheck:=CalculateADFSDirCheck(0,dirbuffer);
//Compare with what is stored
if dirchk<>dircheck then
begin
//If different, just mark as broken directory
Result.Broken:=True;
Result.ErrorCode:=Result.ErrorCode OR $08;
end;
end;
Result.BeenRead:=True;
end
else
begin //Could not be read in for some other reason
if not Result.Broken then Result.ErrorCode:=Result.ErrorCode OR $10;
Result.Broken:=True;
end;
end;
if Result.Broken then inc(brokendircount);
end;
{-------------------------------------------------------------------------------
Convert a load and execution address to filetype and date/time
-------------------------------------------------------------------------------}
procedure TDiscImage.ADFSCalcFileDate(var Entry: TDirEntry);
var
temp: String;
rotd: Int64;
begin
//Only valid for New and Big directories in ADFS or SparkFS
if((GetMajorFormatNumber=diAcornADFS)and((FDirType=diADFSNewDir)or(FDirType=diADFSBigDir)))
or(GetMajorFormatNumber=diSpark)then
if Entry.LoadAddr>>20=$FFF then //Only if the top 12 bits are set
begin
//Get the 12 bit filetype
temp:=IntToHex((Entry.LoadAddr AND$000FFF00)>>8,3);
Entry.Filetype:=GetFiletypeFromNumber(StrToInt('$'+temp));
Entry.ShortFiletype:=temp;
//Now sort the timestamp
rotd:=Entry.LoadAddr AND$FF; //Convert to 64 bit integer
rotd:=(rotd<<32)OR Entry.ExecAddr; //Shift to the left and add the rest
Entry.TimeStamp:=RISCOSToTimeDate(rotd);
end;
end;
{-------------------------------------------------------------------------------
Calculate the directory check byte
-------------------------------------------------------------------------------}
function TDiscImage.CalculateADFSDirCheck(sector:Cardinal): Byte;
begin
Result:=CalculateADFSDirCheck(sector,nil);
end;
function TDiscImage.CalculateADFSDirCheck(sector:Cardinal;buffer:TDIByteArray): Byte;
var
dircheck,
amt,
offset,
tail,
dirsize,
EndOfChk,
numentrys : Cardinal;
begin
EndOfChk:=0;
tail :=0;
dirsize :=0;
//Set up variables
if FDirType=diADFSOldDir then //Old Directory
begin
dirsize:=1280;
tail:=dirsize-$35;
end;
if FDirType=diADFSNewDir then //New Directory
begin
dirsize:=2048;
tail:=dirsize-$29;
end;
if FDirType<diADFSBigDir then //Old or New Directory
begin
//Count the number of entries
numentrys:=0;
while ReadByte(sector+$05+numentrys*$1A,buffer)<>0 do inc(numentrys);
EndOfChk:=numentrys*$1A+$05;
end;
if FDirType=diADFSBigDir then //Big Directory
begin
//Need to do some more calculation for the end of check figure
dirsize:=Read32b(sector+$0C,buffer);
tail:=dirsize-$08;
numentrys:=Read32b(sector+$10,buffer);
EndOfChk:=((($1C+Read32b(sector+$08,buffer)+1+3)div 4)*4)
+(numentrys*$1C)
+Read32b(sector+$14,buffer);
end;
//This has virtually the same loop repeated 5 times - but it is less code to
//do it like this, than a single loop with if...then conditions to determine
//the different iterations.
dircheck:=0;
amt:=0;
//Stage 1: All the whole words at the start of the directory are accumulated
while amt+3<EndOfChk do
begin
offset:=Read32b(sector+amt,buffer);
dircheck:=offset XOR ROR13(dircheck);
inc(amt,4);
end;
//Stage 2: The bytes (<4) at the start of the directory are accumulated
//individually.
while amt<EndOfChk do
begin
offset:=ReadByte(sector+amt,buffer);
dircheck:=offset XOR ROR13(dircheck);
inc(amt);
end;
//Stage 3: The first byte at the beginning of the directory tail is skipped.
amt:=tail;
//But not with Big Directories
if FDirType<diADFSBigDir then inc(amt);
//Stage 4: The whole words in the directory tail are accumulated, except the
//very last word which is excluded as it contains the check byte.
while amt+3<dirsize-4 do
begin
offset:=Read32b(sector+amt,buffer);
dircheck:=offset XOR ROR13(dircheck);
inc(amt,4);
end;
//Stage 4a: Big Directories also accumulate the final few bytes, but not the
//final byte
if FDirType=diADFSBigDir then
while amt<dirsize-1 do
begin
offset:=ReadByte(sector+amt,buffer);
dircheck:=offset XOR ROR13(dircheck);
inc(amt);
end;
//Stage 5: The accumulated word has its four bytes exclusive ORd (EOR) together.
Result :=(dircheck AND$FF)
XOR ((dircheck>>24)AND$FF)
XOR ((dircheck>>16)AND$FF)
XOR ((dircheck>> 8)AND$FF);
end;
{-------------------------------------------------------------------------------
Convert an ADFS New Map address to buffer offset address, with fragment lengths
-------------------------------------------------------------------------------}
function TDiscImage.NewDiscAddrToOffset(addr: Cardinal;
offset:Boolean=True): TFragmentArray;
var
i,j,sector,id,
allmap,len,off,
zone,start,
start_zone,
zonecounter,
fragid,
id_per_zone : Cardinal;
const
dr_size = $40; //Size of disc record + header (zone 0)
header = 4; //Size of zone header only (zones >0)
begin
//Reset the result
Result:=nil;
SetLength(Result,0);
if FMap then //Only works for new maps
begin
if(addr=0)or(addr=rootfrag)then //Root
begin
//We've been given the address of the root, but we know where this is so no
//need to calculate it.
SetLength(Result,1);
Result[0].Offset:=bootmap+(nzones*secsize*2);
case FDirType of
diADFSOldDir: Result[0].Length:=$500;
diADFSNewDir: Result[0].Length:=$800;
diADFSBigDir: Result[0].Length:=root_size;
end;
end
else
begin
//Extract the fragment ID part of the address
fragid:=(addr div $100)mod(1<<idlen);
//Calculate the sector offset
sector:=addr mod $100;
//Sector needs to have 1 subtracted, if >=1
if sector>=1 then dec(sector);
//Go through the allocation map, looking for the fragment
//First we need to know how many ids per zone there are (max)
id_per_zone:=((secsize*8)-zone_spare)div(idlen+1);
//Then work out the start zone
start_zone:=((addr DIV $100)mod(1<<idlen))div id_per_zone;
//This is because the first fragment of an object does not necessarily
//appear in zone order. Later fragments could be in earlier zones.
for zonecounter:=0 to nzones-1 do
begin
//Account for which zone to start searching from
zone:=(zonecounter+start_zone)mod nzones;
//This is the start of where we take the offsets from
start :=bootmap+dr_size;
//Work out the end of this zone
allmap:=(zone+1)*secsize*8-dr_size*8;
//i is the bit counter - we need to move onto the next zone boundary
i :=zone*secsize*8;
if zone>0 then dec(i,dr_size*8-header*8);
repeat
//Mark the offset
off:=i;
//Read in idlen number of bits
id:=ReadBits(start,i,idlen);
//and move the pointer on idlen bits
inc(i,idlen);
//Now find the end of the fragment entry
j:=i-1;
repeat
inc(j);
until(IsBitSet(ReadByte(start+(j div 8)),j mod 8))or(j>=allmap);
//Make a note of the length
if offset then
len:=((j-i)+1+idlen)*bpmb
else
len:=(j-i)+1+idlen;
//Move the pointer on, after the '1'
i:=j;
//Does it match the id we are looking for?
if id=fragid then
begin
if offset then //Offset as image file offset
off:=((off-(zone_spare*zone))*bpmb) mod disc_size[0]
else //Offset as number of bits from start of zone
begin
//Add the disc record (we are counting from the zone start
inc(off,(dr_size*8));
//Remove the other zones
dec(off,(zone*secsize*8));
//Remove the intial byte, as we need to point our offset from freelink
dec(off,8);
end;
//Fragment ID found, so add it - there could be a few entries
SetLength(Result,Length(Result)+1);
Result[Length(Result)-1].Offset:=off;
//Save the length
Result[Length(Result)-1].Length:=len;
//Save the zone
Result[Length(Result)-1].Zone :=zone;
end;
inc(i);
until i>=allmap;
end;
//If offsets have been called for, then convert them
if Length(Result)>0 then
if offset then
for i:=0 to Length(Result)-1 do
Result[i].Offset:=(Result[i].Offset+(sector*secsize));
//Root indirect address
if(addr=rootfrag)and(Length(Result)>1)and(nzones>1)
and(Result[0].Offset=sector*secsize)then
begin
for i:=1 to Length(Result) do
Result[i-1]:=Result[i];
SetLength(Result,Length(Result)-1);
end;
end;
end;
end;
{-------------------------------------------------------------------------------
Calculate disc address given the offset into image (Interleave)
-------------------------------------------------------------------------------}
function TDiscImage.OffsetToOldDiscAddr(offset: Cardinal): Cardinal;
var
track_size,
track,
side,
data_offset : Cardinal;
const
tracks = 80;
oldheads = 2;
begin
Result:=offset;
//ADFS L or AFS with 'INT' option
if((FFormat=diAcornADFS<<4+$02)or(GetMajorFormatNumber=diAcornFS))
and(Finterleave=2)then
begin
//Track Size;
track_size:=secspertrack*secsize;
//Track number
track:=offset DIV (track_size*oldheads);
//Which side
side:=(offset MOD (track_size*oldheads))DIV track_size;
//Offset into the sector for the data
data_offset:=offset MOD track_size;
//Final result
Result:= (track*track_size)+(tracks*track_size*side)+data_offset;
end;
end;
{-------------------------------------------------------------------------------
Calculate Boot Block or Old Map Free Space Checksum
-------------------------------------------------------------------------------}
function TDiscImage.ByteChecksum(offset,size: Cardinal;newmap: Boolean): Byte;
var
buffer: TDIByteArray;
begin
SetLength(buffer,0);
Result:=ByteChecksum(offset,size,newmap,buffer);
end;
function TDiscImage.ByteChecksum(offset,size: Cardinal;newmap: Boolean;
var buffer:TDIByteArray): Byte;
var
acc,
pointer: Cardinal;
carry : Byte;
begin
//Reset the accumulator
//This should be 255 for the FSM in Old Map, but zero for New Map boot block
if newmap then acc:=0 //New
else acc:=$FF; //Old
//We can't used the global FMap, as this may not have been defined yet
//Iterate through the block, ignoring the final byte (which contains the
//checksum)
for pointer:=size-2 downto 0 do
begin
//Make a note of the carry
carry:=acc div $100;
//and ensure the accumulator is <=255
acc:=acc and $FF;
//Add each byte to the accumulator, along with the carry
inc(acc,ReadByte(offset+pointer,buffer)+carry);
end;
//Return an 8 bit number
Result:=acc and $FF;
end;
{-------------------------------------------------------------------------------
Read ADFS Disc
-------------------------------------------------------------------------------}
function TDiscImage.ReadADFSDisc: Boolean;
function ReadTheADFSDisc: Boolean;
type
TVisit = record
Sector : Cardinal;
Name : String;
end;
var
d,ptr,i : Cardinal;
OldName0,
OldName1 : String;
addr : TFragmentArray;
visited : array of TVisit;
begin
//Initialise some variables
root :=$00; //Root address (set to zero so we can id the disc)
FDisc:=nil;
Result:=False;
SetLength(FDisc,0);
brokendircount:=0;
//Read in the header information (that hasn't already been read in during
//the initial checks
//ADFS Old Map
if not FMap then
begin
//Set up boot option
SetLength(bootoption,1);
bootoption[0]:=ReadByte($1FD);
//We already found the root when IDing it as ADFS, so now just confirm
d:=2;
root:=0;
//Root size for old map old directory - assume for now
root_size:=$500;
repeat
if(ReadString((d*$100)+1,-4)='Hugo')
or(ReadString((d*$100)+1,-4)='Nick')then
root:=d;
inc(d);
until(d=(disc_size[0]div$100)-1)or(root>0);
if root=0 then //Failed to find root, so reset the format
begin
ResetVariables;
//Now, let's see if it is an AFS
if ID_AFS then exit else ResetVariables;
end
else
begin
//Set the root size for old map new directory
if FDirType=diADFSNewDir then root_size:=$800;
//Get the two parts of the disc title
OldName0 :=ReadString($0F7,-5);
OldName1 :=ReadString($1F6,-5);
//Start with a blank title
disc_name[0]:=' ';
//Disc title
if not FAFSPresent then //AFS partition present, so skip this
begin
//Re-assemble the disc title
if Length(OldName0)>0 then
for d:=1 to Length(OldName0) do
disc_name[0][(d*2)-1] :=chr(ord(OldName0[d])AND$7F);
//Both parts are interleaved
if Length(OldName1)>0 then
for d:=1 to Length(OldName1) do
disc_name[0][ d*2 ] :=chr(ord(OldName1[d])AND$7F);
//Then remove all extraenous spaces
RemoveSpaces(disc_name[0]);
end else disc_name[0]:='AFS L3';
end;
end;
//ADFS New Map
if FMap then
begin
SetLength(bootoption,1);
//Disc description starts at offset 4 and is 60 bytes long
//Not all of these values will be used
secsize :=1<<ReadByte(bootmap+$04);
secspertrack:=ReadByte(bootmap+$05);
heads :=ReadByte(bootmap+$06);
density :=ReadByte(bootmap+$07);
idlen :=ReadByte(bootmap+$08);
bpmb :=1<<ReadByte(bootmap+$09);
skew :=ReadByte(bootmap+$0A);
bootoption[0]:=ReadByte(bootmap+$0B);
lowsector :=ReadByte(bootmap+$0C);
nzones :=ReadByte(bootmap+$0D);
zone_spare :=Read16b(bootmap+$0E);
rootfrag :=Read32b(bootmap+$10);
disc_size[0]:=Read32b(bootmap+$14);
disc_id :=Read16b(bootmap+$18);
disc_name[0]:=ReadString(bootmap+$1A,-10);