forked from geraldholdsworth/DiscImageManager
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDiscImage_AFS.pas
2480 lines (2446 loc) · 81.8 KB
/
DiscImage_AFS.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 FileStore +++++++++++++++++++++++++++++++++++++++++++
{-------------------------------------------------------------------------------
IDs an AFS disc
-------------------------------------------------------------------------------}
function TDiscImage.ID_AFS: Boolean;
//This will only identify a plain AFS disc, not an ADFS Hybrid
function IDAFSPass(chgint:Boolean): Boolean;
var
afspart1,
afspart2,
Lafsroot : Cardinal;
index : Integer;
ok : Boolean;
begin
ResetVariables;
//Is there actually any data?
if GetDataLength>0 then
begin
//Set default sector size and sectors per track
secspertrack:= 16;
secsize :=256;
//Is an AFS Level 2?
if ReadString(0,-4)='AFS0' then
begin
//Need to set a format to fire off the interleave options
FFormat:=diAcornFS<<4+1;
if(FForceInter=0)and(chgint)then //Auto detect interleave
Finterleave:=1; //Start with SEQ for AFS L2
//The afs header address will be sectors 0 and 10
afspart1:=$0*secsize;
afspart2:=$A*secsize;
end
else //Not an AFS Level 2, so see if it is Level 3
begin
//Need to set a format to fire off the interleave options
FFormat:=diAcornFS<<4+2;
if(FForceInter=0)and(chgint)then //Auto detect interleave
Finterleave:=3; //Start with MUX for AFS L3
//Get the afs header address from the ADFS map
afspart1:=Read24b($0F6)*secsize;
afspart2:=Read24b($1F6)*secsize;
end;
//Have we got an ID header?
if(ReadString(afspart1,-4)='AFS0')and(ReadString(afspart2,-4)='AFS0')then
begin
//Compare the two partition headers
ok:=True;
for index:=0 to $25 do
if ReadByte(afspart1+index)<>ReadByte(afspart2+index)then ok:=False;
//Both match? Do we have an ID string?
if ok then
begin
//Level 2
if FFormat=diAcornFS<<4+1 then
//Confirm the root is where it says it is
if ReadByte((Read24b(afspart1+$16)*secsize)+3)=$24 then
begin
afshead:=afspart1;
afshead2:=afspart2;
end
else FFormat:=diInvalidImg; //It isn't, so invalid image
//Level 3
if FFormat=diAcornFS<<4+2 then
begin
//Now we confirm we are looking at the root
//Read where the header is pointing towards the root
Lafsroot:=Read24b(afspart1+$1F)*$100;
//Read the ID string
if ReadString(Lafsroot,-6)='JesMap' then
//Confirm it is the root
if ReadByte((Read24b(Lafsroot+$0A)*secsize)+3)=$24 then // $24='$'
begin
afshead:=afspart1; //Make a note of the header location
afshead2:=afspart2;//And the copy
end
else FFormat:=diInvalidImg //No root, invalid image
else FFormat:=diInvalidImg; //No map ID, invalid image
end;
end else FFormat:=diInvalidImg; //Headers not matching, no format
end else FFormat:=diInvalidImg; //No header ID, invalid image
end;
Result:=GetMajorFormatNumber=diAcornFS;
end;
var
start: Byte;
begin
Result:=False;
if(FFormat=diInvalidImg)or(GetMajorFormatNumber=diAcornFS)then
begin
//Interleaving, depending on the option
Finterleave:=FForceInter;
if Finterleave=0 then Finterleave:=1; //Don't know the format, so go with SEQ
//Do a first pass for ID
Result:=IDAFSPass(True);
//Are we set to autodetect interleaving, if no positive ID result?
if(FForceInter=0)and(not Result)then //Auto
begin
start:=FInterleave;
repeat
//Next method
dec(FInterleave);
if FInterleave=0 then FInterleave:=4; //Wrap around
Result:=IDAFSPass(False);
until(Result)or(FInterleave=start); //Continue until a result or back at the start
end;
end;
end;
{-------------------------------------------------------------------------------
Reads an AFS partition
-------------------------------------------------------------------------------}
procedure TDiscImage.ReadAFSPartition;
var
d,e,i : Integer;
allocmap : Cardinal;
startdir : String;
visited : array of Cardinal;
begin
visited:=nil;
//Is this an ADFS disc with Acorn FileStore partition?
if((GetMajorFormatNumber=diAcornADFS)and(FAFSPresent))
or(GetMajorFormatNumber=diAcornFS)then
begin
if GetMajorFormatNumber=diAcornADFS then
begin
afshead:=Read24b($0F6)*secsize;
afshead2:=Read24b($1F6)*secsize;
end;
//Confirm that there is a valid AFS partition here
if (ReadString(afshead,-4)='AFS0')
and(ReadString(afshead2,-4)='AFS0')then //Should be 'AFS0'
begin
//Update the progress indicator
UpdateProgress('Reading Acorn FS partition');
//Size of the disc
if FFormat=diAcornFS<<4+1 then //Level 2
disc_size[0]:=Read16b(afshead+$14)*2*secsize; //Only gives number of sectors for 1 side
if FFormat=diAcornFS<<4+2 then //Level 3
disc_size[0]:=Read24b(afshead+$16)*secsize;
i:=0;
if GetMajorFormatNumber=diAcornADFS then //Level 3/ADFS Hybrid
begin
SetLength(disc_size,2);
SetLength(free_space,2);
SetLength(disc_name,2);
disc_size[1]:=(Read16b(afshead+$16)*secsize)-disc_size[0];
i:=1;
end;
//Disc title
disc_name[i]:=ReadString(afshead+4,-16);
RemoveSpaces(disc_name[i]); //Minus trailing spaces
//Where is the AFS root?
if FFormat=diAcornFS<<4+1 then
allocmap:=Read24b(afshead+$16)*secsize //Level 2
else
allocmap:=Read24b(afshead+$1F)*secsize;//Level 3
//Is the ADFS root broken with no entries (i.e. valid?)
if Length(FDisc)>0 then //This will also be zero if this is an AFS image
if(FDisc[0].Broken)and(Length(FDisc[0].Entries)=0)then
SetLength(FDisc,0); //Yes, so overwrite it
//Add a new entry
d:=Length(FDisc);
SetLength(FDisc,d+1);
//Start the chain by reading the root
if GetMajorFormatNumber=diAcornADFS then startdir:=afsrootname else startdir:='$';
FDisc[d]:=ReadAFSDirectory(startdir,allocmap);
//Add the root as a visited directory
SetLength(visited,1);
visited[0]:=allocmap div secsize;
//Now go through the root's entries and read them in
repeat
if Length(FDisc[d].Entries)>0 then
for e:=0 to Length(FDisc[d].Entries)-1 do
begin
//Make sure we haven't seen this before. If a directory references a higher
//directory we will end up in an infinite loop.
if Length(visited)>0 then
for i:=0 to Length(visited)-1 do
if visited[i]=FDisc[d].Entries[e].Sector then
FDisc[d].Entries[e].Filename:='';//Blank off the filename so we can remove it later
if FDisc[d].Entries[e].Filename<>'' then //Needs to have an actual name
//If it is a directory, read that in
if Pos('D',FDisc[d].Entries[e].Attributes)>0 then
begin
//Making room for it
SetLength(FDisc,Length(FDisc)+1);
//And now read it in
if FScanSubDirs then
FDisc[Length(FDisc)-1]:=ReadAFSDirectory(GetParent(d)
+dir_sep
+FDisc[d].Entries[e].Filename,
FDisc[d].Entries[e].Sector*secsize);
FDisc[Length(FDisc)-1].Parent:=d;
//Remember it
SetLength(visited,Length(visited)+1);
visited[Length(visited)-1]:=FDisc[d].Entries[e].Sector;
//Reference to it
FDisc[d].Entries[e].DirRef:=Length(FDisc)-1;
end;
end;
inc(d);
until d=Length(FDisc);
//Update the progress indicator
UpdateProgress('Removing blank entries');
//Remove any blank entries or parent directory references
if Length(FDisc)>0 then
begin
//Directory counter
d:=0;
//Iterate through all directories
while d<Length(FDisc) do
begin
//Are there any entries?
if Length(FDisc[d].Entries)>0 then
begin
//Entry counter
e:=0;
//Iterate through all entries
while e<Length(FDisc[d].Entries) do
begin
//Do we have a blank filename?
if FDisc[d].Entries[e].Filename='' then
begin
//If not the last entry
if e<Length(FDisc[d].Entries)-1 then
//Move the entries above it down by one
for i:=e+1 to Length(FDisc[d].Entries)-1 do
FDisc[d].Entries[i-1]:=FDisc[d].Entries[i];
//Remove the final entry
SetLength(FDisc[d].Entries,Length(FDisc[d].Entries)-1);
end;
//Next entry (which now could take us over the length)
inc(e);
end;
end;
//Next directory
inc(d);
end;
end;
//Read in the free space map
ReadAFSFSM;
end
else FAFSPresent:=False; //No valid partition
end;
end;
{-------------------------------------------------------------------------------
Reads an AFS directory
-------------------------------------------------------------------------------}
function TDiscImage.ReadAFSDirectory(dirname:String;addr: Cardinal):TDir;
var
numentries,
index : Integer;
side,
entry,
objaddr,
segaddr : Cardinal;
attr : String;
access : Byte;
buffer : TDIByteArray;
begin
Result.Directory:='';
buffer:=nil;
//Reset the directory settings to a default value
ResetDir(Result);
//Update the progress indicator
UpdateProgress('Reading '+dirname);
//Read, and assemble, the directory into a temporary buffer, if valid
buffer:=ReadAFSObject(addr);
//Have we got any data to look at?
if Length(buffer)>0 then
begin
//And set the partition flag to true
Result.AFSPartition:=True;
//ADFS hybrid?
if GetMajorFormatNumber=diAcornADFS then side:=1 else side:=0;
Result.Partition:=side;
//Directory title
Result.Directory:=ReadString($03,-10,buffer);
RemoveSpaces(Result.Directory);
if Result.Directory='$' then
begin
Result.Directory:=dirname; //Change the name, in case we already have a '$'
Fafsroot:=addr div secsize; //Make a note of where the root is
afsroot_size:=GetAFSObjLength(addr); //And a note of the root size
end;
Result.Sector:=addr div secsize;
//Number of entries in the directory
numentries:=ReadByte($0F,buffer);
SetLength(Result.Entries,numentries);
//Pointer to first entry
entry:=Read16b($00,buffer);
//Read all the entries in
if(numentries>0)and(entry>0)then
for index:=0 to numentries-1 do
begin
//Reset all other entries
Result.Entries[index].FileType:='';
Result.Entries[index].ShortFileType:='';
Result.Entries[index].Parent:=dirname;
Result.Entries[index].Side:=side;
//Filename
Result.Entries[index].Filename:=ReadString(entry+$02,-10,buffer);
RemoveSpaces(Result.Entries[index].Filename);
//Load address
Result.Entries[index].LoadAddr:=Read32b(entry+$0C,buffer);
//Execution address
Result.Entries[index].ExecAddr:=Read32b(entry+$10,buffer);
//Attributes
attr:='';
access:=ReadByte(entry+$14,buffer);
if access AND $20=$20 then attr:=attr+'D';
if access AND $10=$10 then attr:=attr+'L';
if access AND $08=$08 then attr:=attr+'W';
if access AND $04=$04 then attr:=attr+'R';
if access AND $02=$02 then attr:=attr+'w';
if access AND $01=$01 then attr:=attr+'r';
Result.Entries[index].Attributes:=attr;
//Modification Date
segaddr:=Read16b(entry+$15,buffer);
Result.Entries[Index].TimeStamp:=AFSToDateTime(segaddr);
//Location of the object
objaddr:=Read24b(entry+$17,buffer)*secsize;
if(ReadString(objaddr,-6)='JesMap')or(FFormat=diAcornFS<<4+1)then
begin
//Location of the data
Result.Entries[index].Sector:=objaddr div secsize;
//Length
Result.Entries[index].Length:=GetAFSObjLength(objaddr);
end
else
begin //Invalid block, so blank these parameters off
Result.Entries[index].Sector:=0;
Result.Entries[index].Length:=0;
end;
//Directory Reference
Result.Entries[index].DirRef:=-1;
//Next entry
entry:=Read16b(entry+$00,buffer);
end;
Result.BeenRead:=True;
end;
end;
{-------------------------------------------------------------------------------
Extracts a file, filename contains complete path
-------------------------------------------------------------------------------}
function TDiscImage.ExtractAFSFile(filename: String;
var buffer: TDIByteArray): Boolean;
var
dir,
entry : Cardinal;
begin
Result:=False;
if FileExists(filename,dir,entry) then //Does the file actually exist?
begin
//Just use the method for reading in objects
buffer:=ReadAFSObject(FDisc[dir].Entries[entry].Sector*secsize);
//And return a positive result
Result:=True;
end;
end;
{-------------------------------------------------------------------------------
Read and assemble an object's data
-------------------------------------------------------------------------------}
function TDiscImage.ReadAFSObject(offset: Cardinal): TDIByteArray;
var
ptr,
addr,
len,
bufptr,
almap : Cardinal;
index : Integer;
begin
Result:=nil;
//Make sure it is a valid allocation map for Level 3
if(ReadString(offset,-6)='JesMap')
and((FFormat=diAcornFS<<4+2)or(GetMajorFormatNumber=diAcornADFS))
and(ReadByte(offset+6)=ReadByte(offset+$FF))then
begin
//Start of the list
ptr:=$0A;
//Read the first entry and length
addr:=$FF;
while(addr<>0)and(ptr<$FA)do
begin
addr:=Read24b(offset+ptr)*secsize;
len:=Read16b(offset+ptr+3)*secsize;
if(addr<>0)and(len<>0)then
begin
bufptr:=Length(Result);
//Copy into the buffer
SetLength(Result,Length(Result)+len);
for index:=0 to len-1 do
Result[bufptr+index]:=ReadByte(addr+index);
end;
//Move onto the next entry
inc(ptr,5);
end;
//Truncate to the total length
len:=ReadByte(offset+$08);
if(addr=0)and(len>0)then
begin
len:=$100-len;
SetLength(Result,Length(Result)-len);
end;
end;
//We are looking at a Level 2 map, with no ID string
if(FFormat=diAcornFS<<4+1)and(offset<>0)then
begin
offset:=offset div secsize;
//Get the address of the current allocation map
almap:=GetAllocationMap;
//Find our entry and read it in
repeat
addr:=Read16b(almap+(offset*2)+5);
len:=secsize; //Length of data to read in
if (addr AND $4000)=$4000 then
begin
len:=addr AND $FF;//Last sector, so bytes used
if len=0 then len:=secsize; //if zero, then it must be a full sector
end;
if (addr AND $1000)=$1000 then len:=0; //This entry is empty
if len>0 then
begin
//Copy the data into the buffer
bufptr:=Length(Result);
SetLength(Result,bufptr+len);
for index:=0 to len-1 do
Result[bufptr+index]:=ReadByte(offset*secsize+index);
end;
if (addr AND $4000)<>$4000 then offset:=addr AND $FFF; //This is the next sector in the chain
until (addr AND $4000)=$4000; //Continue until everything is read in
end;
end;
{-------------------------------------------------------------------------------
Read the length of an object
-------------------------------------------------------------------------------}
function TDiscImage.GetAFSObjLength(offset: Cardinal): Cardinal;
var
almap,
addr,
len,
ptr,
segaddr: Cardinal;
lenLSB : Byte;
begin
Result:=0;
//Level 2
if(FFormat=diAcornFS<<4+1)and(offset<>0)then
begin
offset:=offset div secsize;
//Get the address of the current allocation map
almap:=GetAllocationMap;
//Find our entry and read it in
repeat
addr:=Read16b(almap+(offset*2)+5);
//Length of data in this sector
len:=secsize;
if (addr AND $4000)=$4000 then
begin
//Last sector, so bytes used
len:=addr AND $FF;
//if zero, then it must be a full sector
if len=0 then len:=secsize;
end;
if (addr AND $1000)=$1000 then len:=0; //This entry is empty
//Add to the total
inc(Result,len);
if (addr AND $4000)<>$4000 then offset:=addr AND $FFF; //This is the next sector in the chain
until (addr AND $4000)=$4000; //Continue until everything is read in
end;
//Level 3
if(ReadString(offset,-6)='JesMap')
and((FFormat=diAcornFS<<4+2)or(GetMajorFormatNumber=diAcornADFS))then
begin
lenLSB:=ReadByte(offset+$08); //LSB of the length
ptr:=$0A;
segaddr:=$FF;
while(ptr<$FA)and(segaddr<>$00)do
begin
//Read the segment address
segaddr:=Read24b(offset+ptr);
//Read the length of this segment and add to the total
inc(Result,Read16b(offset+ptr+3));
//Next segment
inc(ptr,5);
end;
//Total length read, but may be over
if lenLSB>0 then
Result:=(Result-1)*secsize+lenLSB
else
Result:=Result*secsize;
end;
end;
{-------------------------------------------------------------------------------
Gets the allocation map address
-------------------------------------------------------------------------------}
function TDiscImage.GetAllocationMap: Cardinal;
var
dummy: Cardinal;
begin
Result:=GetAllocationMap(0,dummy);
end;
function TDiscImage.GetAllocationMap(sector:Cardinal;var spt:Cardinal):Cardinal;
var
szofbmp,
Ldiscsize,
afsstart,
trackstrt : Cardinal;
begin
Result:=0; //Default
//Level 2
if FFormat=diAcornFS<<4+1 then
if ReadByte(Read24b(afshead+$1B)*secsize)>ReadByte(Read24b(afshead+$1E)*secsize)then
Result:=Read24b(afshead+$1B)*secsize else Result:=Read24b(afshead+$1E)*secsize;
//Level 3 and Hybrid
if(FFormat=diAcornFS<<4+2)or(GetMajorFormatNumber=diAcornADFS)then
begin
spt:=Read16b(afshead+$1A);
//Level 3
if FFormat=diAcornFS<<4+2 then
begin
Ldiscsize:=disc_size[0]; //Look at the entire image
afsstart:=0; //But not below here, which is ADFS header
end
else
begin //Hybrids - AFS will take up the second 'side'
Ldiscsize:=disc_size[1]; //Only look at the AFS part of the image
afsstart:=disc_size[0]; //And not below here, which is the ADFS partition
end;
if sector*secsize>=afsstart then //Ensure it is in our 'area'
begin
//Read the size of the bitmap
szofbmp:=ReadByte(afshead+$1C)*secsize;
//Is it big enough to hold the entire disc?
if szofbmp>=(((afsstart+Ldiscsize)div secsize)div 8)+1 then //Yes
begin
Result:=(Fafsroot*secsize)-szofbmp;
spt:=Ldiscsize div secsize;
end
else
begin
//Get the sector offset of each track
trackstrt:=spt*secsize;
//Our counter - first track after the ADFS part
Result:=((sector*secsize)div trackstrt)*trackstrt;
end;
end;
end;
end;
{-------------------------------------------------------------------------------
Read the free space map
-------------------------------------------------------------------------------}
procedure TDiscImage.ReadAFSFSM;
var
fragments: TFragmentArray;
part : Byte;
spt,
tracks,
entry,
index,
t,s : Cardinal;
begin
//Get all the used sectors
fragments:=AFSGetFreeSectors(True);
//Initialise the variables
if GetMajorFormatNumber=diAcornFS then //AFS Level 2 and 3
begin
SetLength(free_space_map,1);
part:=0;
end;
if GetMajorFormatNumber=diAcornADFS then //Hybrids - AFS will take up the second 'side'
begin
SetLength(free_space_map,2);
part:=1;
end;
//Get the local sectors per track
if(GetMajorFormatNumber=diAcornADFS)or(FFormat=diAcornFS<<4+2)then
spt:=Read16b(afshead+$1A) else spt:=secspertrack;
//Initialise the free space
free_space[part]:=disc_size[part];
//Set up the array
tracks:=Ceil((disc_size[part]div secsize)/spt);
SetLength(free_space_map[part],tracks);
for entry:=0 to Length(free_space_map[part])-1 do //Sectors per track
begin
SetLength(free_space_map[part,entry],spt);
for index:=0 to spt-1 do free_space_map[part,entry,index]:=$00;
end;
//Go through the fragments and mark those as used
if Length(fragments)>0 then
for index:=0 to Length(fragments)-1 do
begin
for entry:=0 to (fragments[index].Length div secsize)-1 do
begin
//Get the track and sector
t:=((fragments[index].Offset div secsize)+entry)div spt;
s:=((fragments[index].Offset div secsize)+entry)mod spt;
if t<tracks then //Make sure it is within range
begin
free_space_map[part,t,s]:=$FF-fragments[index].Zone;
dec(free_space[part],secsize); //Decrease the free space
end;
end;
//Decrease the total free space
//dec(free_space[part],fragments[index].Length);
end;
end;
{-------------------------------------------------------------------------------
Get an array of all the free sectors (or used, if set to True)
-------------------------------------------------------------------------------}
function TDiscImage.AFSGetFreeSectors(used: Boolean=False): TFragmentArray;
var
allocmap,
index,
entry,
szofbmp,
bmploc,
afsstart,
trackstrt : Cardinal;
Lsecspertrack: Word;
Ldiscsize : Int64;
status : Byte;
begin
Result:=nil;
//Initialise the variables
SetLength(Result,0);
//Level 2
if FFormat=diAcornFS<<4+1 then
begin
//Get the allocation map address
allocmap:=GetAllocationMap;
szofbmp :=Read24b(afshead+$21);//The size of the map
//Get the other map address
index :=Read24b(afshead+$1E)*secsize;
if index=allocmap then index:=Read24b(afshead+$1B)*secsize;
//Now find the free sectors
for entry:=0 to (disc_size[0]div secsize)-1 do
begin
//Read the status of this sector
status:=ReadByte(allocmap+6+(entry*2));
//If it has not been written then mark as free
if((status AND $80)<>$80)and(not used)then
begin
SetLength(Result,Length(Result)+1);
Result[Length(Result)-1].Offset:=entry*secsize;
Result[Length(Result)-1].Length:=secsize;
Result[Length(Result)-1].Zone :=0;
end;
//If it has been written then mark as used
if((status AND $80)=$80)and(used)then
begin
SetLength(Result,Length(Result)+1);
Result[Length(Result)-1].Offset:=entry*secsize;
Result[Length(Result)-1].Length:=secsize;
Result[Length(Result)-1].Zone :=0;
//System?
if(entry*secsize=afshead)or(entry*secsize=afshead2)
or((entry>=Fafsroot)and(entry*secsize<=Fafsroot*secsize+afsroot_size))
or((entry*secsize>=allocmap)and(entry*secsize<=allocmap+szofbmp))
or((entry*secsize>=index)and(entry*secsize<=index+szofbmp))then
Result[Length(Result)-1].Zone:=1;
end;
end;
end;
//Level 3 and ADFS/AFS Hybrid
if FFormat<>diAcornFS<<4+1 then
begin
Lsecspertrack:=Read16b(afshead+$1A);
//Level 3
if FFormat=diAcornFS<<4+2 then
begin
Ldiscsize:=disc_size[0]; //Look at the entire image
afsstart:=0; //But not below here, which is ADFS header
end
else
begin //Hybrids - AFS will take up the second 'side'
Ldiscsize:=disc_size[1]; //Only look at the AFS part of the image
afsstart:=disc_size[0]; //And not below here, which is the ADFS partition
end;
//Read the size of the bitmap
szofbmp:=ReadByte(afshead+$1C)*secsize;
if szofbmp=0 then exit; //equals zero? There has been an error
//Is it big enough to hold the entire disc?
if szofbmp>=(((afsstart+Ldiscsize) div secsize)div 8)+1 then //Yes
begin
//Location of the bitmap (just before the root)
bmploc:=(Fafsroot*secsize)-szofbmp;
//Go through the bitmap, sector by sector
for index:=afsstart div secsize to ((afsstart+Ldiscsize)div secsize)-1 do
begin
//Is the bit set, then it is free
if(IsBitSet(ReadByte(bmploc+(index div 8)),index mod 8))and(not used)then
begin
SetLength(Result,Length(Result)+1);
Result[Length(Result)-1].Offset:=(index*secsize)-afsstart;
Result[Length(Result)-1].Length:=secsize;
Result[Length(Result)-1].Zone :=0;
end;
//Is the bit not set, then it is used
if(not IsBitSet(ReadByte(bmploc+(index div 8)),index mod 8))and(used)then
begin
SetLength(Result,Length(Result)+1);
Result[Length(Result)-1].Offset:=(index*secsize)-afsstart;
Result[Length(Result)-1].Length:=secsize;
Result[Length(Result)-1].Zone :=0;
//System?
if(index*secsize=afshead)or(index*secsize=afshead2)
or((index>=Fafsroot)and(index*secsize<=Fafsroot*secsize+afsroot_size))
or((index*secsize>=bmploc)and(index*secsize<=bmploc+szofbmp))then
Result[Length(Result)-1].Zone:=1;
end;
end;
end
else //Not big enough for entire disc, so will be on every track
begin
//Get the sector offset of each track
trackstrt:=Lsecspertrack*secsize;
//Our counter - first track after the ADFS part
bmploc:=(afsstart div trackstrt)*trackstrt;
if bmploc<afsstart then bmploc:=trackstrt;
while bmploc<afsstart+Ldiscsize do //Do the entire image, track by track
begin
//Every sector per track
for index:=0 to Lsecspertrack-1 do
begin
//Is the bit set? Yes - free
if(IsBitSet(ReadByte(bmploc+(index div 8)),index mod 8))and(not used)then
begin
SetLength(Result,Length(Result)+1);
Result[Length(Result)-1].Offset:=(bmploc+index*secsize)-afsstart;
Result[Length(Result)-1].Length:=secsize;
Result[Length(Result)-1].Zone :=0;
end;
//Is the bit set? No - used
if(not IsBitSet(ReadByte(bmploc+(index div 8)),index mod 8))and(used)then
begin
SetLength(Result,Length(Result)+1);
Result[Length(Result)-1].Offset:=(bmploc+index*secsize)-afsstart;
Result[Length(Result)-1].Length:=secsize;
Result[Length(Result)-1].Zone :=0;
//But is it system
if(bmploc+index*secsize=afshead)or(bmploc+index*secsize=afshead2)
or((bmploc+index*secsize>=Fafsroot*secsize)
and(bmploc+index*secsize<=Fafsroot*secsize+afsroot_size))
or(index*secsize<=szofbmp)then Result[Length(Result)-1].Zone:=1;
end;
end;
//Move on to the next track
inc(bmploc,trackstrt);
end;
end;
end;
//Now compact the array by joining adjacent sectors
if Length(Result)>1 then
begin
index:=Length(Result);
while index>0 do
begin
dec(index);
//Is the one below it adjacent?
if Length(Result)>2 then
if(Result[index].Offset=Result[index-1].Offset+secsize)
and(Result[index].Zone=Result[index-1].Zone)then
begin
//Then join them
inc(Result[index-1].Length,Result[index].Length);
//Move the ones above down
if index<Length(Result)-1 then
for entry:=index to Length(Result)-2 do
Result[entry]:=Result[entry+1];
//And let the last one drop off
SetLength(Result,Length(Result)-1);
end;
end;
end;
end;
{-------------------------------------------------------------------------------
Find and allocate some free space
-------------------------------------------------------------------------------}
function TDiscImage.AFSAllocateFreeSpace(size :Cardinal;
var fragments: TFragmentArray;addheader:Boolean=True): Cardinal;
var
FSM,
alloc : TFragmentArray;
found : Boolean;
index : Integer;
sector,
fragsize,
allocmap,
spt : Cardinal;
begin
//Return a erroronous result
Result:=$FFFFFFFF;
//Get the current free space fragments
FSM:=AFSGetFreeSectors;
//Are there any?
if Length(FSM)>0 then
begin
//The above array will have offsets relative to the start of the AFS partition
if GetMajorFormatNumber=diAcornADFS then
for index:=0 to Length(FSM)-1 do
inc(FSM[index].Offset,disc_size[0]); //So add the ADFS size to the offset
//Level 3 includes a 256 byte object header
if((FFormat=diAcornFS<<4+2)or(GetMajorFormatNumber=diAcornADFS))
and(addheader)then inc(size,$100);
//Are there any that will fit the data without fragmenting?
index:=0;
found:=False;
while(index<Length(FSM))and(not found)do
begin
if FSM[index].Length>=size then //Yes
begin
//Mark as found so we stop looking
found:=True;
//Copy to the supplied array
SetLength(fragments,1);
fragments[0].Offset:=FSM[index].Offset;
fragments[0].Length:=size;
end
else inc(index); //No, go to next one
end;
//If not then split the file into fragments
//(this part will also work if the file doesn't need fragmenting. However,
//this can cause a small file to be fragmented if the first free fragment is
//smaller)
if not found then
begin
//Counter into the free fragments
index:=0;
//Iterate while there is data to account for
while size>0 do
begin
//Add a new fragment to our list
SetLength(fragments,Length(fragments)+1);
fragments[Length(fragments)-1].Offset:=FSM[index].Offset;
if FSM[index].Length>=size then
fragments[Length(fragments)-1].Length:=FSM[index].Length
else
fragments[Length(fragments)-1].Length:=size;
//Then decrease the overall length by this fragment's length
dec(size,fragments[Length(fragments)-1].Length);
//Next FSM fragment
inc(index);
end;
end;
//Split the fragments into sector sized fragments for allocation
//If more than 47, on Level 3, then we'll need another JesMap block
if(Length(fragments)>0)and(Length(fragments)<48)then //For now, it'll fail
begin
//Our destination array
SetLength(alloc,0);
for index:=0 to Length(fragments)-1 do
begin
//Make a note of the total length and start sector
fragsize:=fragments[index].Length;
sector :=fragments[index].Offset div secsize;
//Now split this into fragments
while fragsize>0 do
begin
//New entry in the array
SetLength(alloc,Length(alloc)+1);
//Populate it
alloc[Length(alloc)-1].Offset:=sector AND$FFF;
//Length no bigger than a sector size
if fragsize>secsize then
alloc[Length(alloc)-1].Length:=secsize
else
alloc[Length(alloc)-1].Length:=fragsize;
//Decrease this fragment size by what we've allocated
dec(fragsize,alloc[Length(alloc)-1].Length);
//And onto the next sector
inc(sector);
end;
end;
//Write it to the FSM
if Length(alloc)>0 then
begin
//Current map (Level 2)
if FFormat=diAcornFS<<4+1 then allocmap:=GetAllocationMap;
for index:=0 to Length(alloc)-1 do
begin
//Level 2
if FFormat=diAcornFS<<4+1 then
begin
//Clear the entry
WriteByte(0,allocmap+(alloc[index].Offset*2)+6);
if index<Length(alloc)-1 then //Write pointer to next sector
Write16b(alloc[index+1].Offset,allocmap+(alloc[index].Offset*2)+5)
else //Write the length
WriteByte(alloc[index].Length mod$100,allocmap+(alloc[index].Offset*2)+5);
//If this is the first, set the bit
if index=0 then WriteBits(1,allocmap+(alloc[index].Offset*2)+6,5,1);
//If this is the last, set the bit
if index=Length(alloc)-1 then
WriteBits(1,allocmap+(alloc[index].Offset*2)+6,6,1);
//Mark as written
WriteBits(1,allocmap+(alloc[index].Offset*2)+6,7,1);
end;
if(FFormat=diAcornFS<<4+2)or(GetMajorFormatNumber=diAcornADFS)then
begin
for fragsize:=0 to Ceil(alloc[index].Length/secsize)-1 do
begin
//Need to find where the FSM for this sector is
allocmap:=GetAllocationMap(alloc[index].Offset+fragsize,spt);
//Get the sector within this area
sector:=(alloc[index].Offset+fragsize)mod spt;
//And mark the areas as used
WriteBits(0,allocmap+(sector div 8),sector mod 8,1);
end;
end;
end;
//Compact the free space map
if FFormat=diAcornFS<<4+1 then FinaliseAFSL2Map;
//Return the address of the first fragment
Result:=fragments[0].Offset;
end;
end;
end;
end;
{-------------------------------------------------------------------------------
Deallocate specified area in the free space map
-------------------------------------------------------------------------------}
procedure TDiscImage.AFSDeAllocateFreeSpace(addr: Cardinal);
var
allocmap,
sector,
spt : Cardinal;
index,
frag : Integer;
begin
//Level 2
if FFormat=diAcornFS<<4+1 then
begin
//Get the address of the allocation map
allocmap:=GetAllocationMap;
while ReadByte(allocmap+(addr*2)+6)AND$40<>$40 do
begin
//Clear the top nybble
WriteBits(0,allocmap+(addr*2)+6,4,4);
//Next in the chain
addr:=Read16b(allocmap+(addr*2)+5)AND$FFF;
end;
//The last entry (as this would get missed in the loop)
Write16b(0,allocmap+(addr*2)+5);
//Compact the map
FinaliseAFSL2Map;
end;
//Level 3 and Hybrid
if(FFormat=diAcornFS<<4+2)or(GetMajorFormatNumber=diAcornADFS)then
begin
addr:=addr*secsize;
if ReadString(addr,-6)='JesMap' then //Make sure it is a valid block
begin
//Deallocate the JesMap header
allocmap:=GetAllocationMap(addr div secsize,spt); //Get the address
//And mark it as free
WriteBits(1,allocmap+((addr div secsize)mod spt)div 8,(addr div secsize)mod 8,1);
//Set the starting point
frag:=$0A;
//Dummy sector
sector:=$FF;
while(sector<>0)and(frag<$FA)do
begin
//Get the next sector
sector:=Read24b(addr+frag);
//Just to be sure
if sector<>0 then
for index:=0 to Read16b(addr+frag+3)-1 do
begin
//Get the location of the map for this sector
allocmap:=GetAllocationMap(sector+index,spt);
//Mark it as free
WriteBits(1,allocmap+((sector+index)mod spt)div 8,(sector+index)mod 8,1);
end;
//Next pointer
inc(frag,5);
end;
end;
end;
end;
{-------------------------------------------------------------------------------
Write the headers and compact the Level 2 map
-------------------------------------------------------------------------------}
procedure TDiscImage.FinaliseAFSL2Map;
var
freesecs,
firstfree : Word;
allocmap,
mapsize : Cardinal;