diff --git a/Documentation/Changes.txt b/Documentation/Changes.txt
index 1b22ef2..da502e9 100755
--- a/Documentation/Changes.txt
+++ b/Documentation/Changes.txt
@@ -826,6 +826,28 @@ Bug fixes
* Acorn/Watford DFS, Commodore 64, Acorn CFS and Spark images were being opened with the 'directory been read' flag not set, so appeared as if the directories were unread.
* DFS images were still not correctly being IDed.
+1.42 - 23rd April 2022
+----------------------
+New or improved features
+* SparkFS images are now fully writable:
+* Can now write files to a SparkFS image.
+* Can now create a blank directory on a SparkFS image.
+* Can now rename files and directories on a SparkFS image.
+* Can now move and copy files and directories on a SparkFS image.
+* Can now change the timestamp on files and directories on a SparkFS image.
+* Can now change the filetype on files on a SparkFS image.
+* Can now change the load and execution addresses on files on a SparkFS image.
+* Can now change file and directory attibutes on a SparkFS image.
+* Can now delete files/directories from a SparkFS image.
+
+Bug fixes
+* SparkFS failed to extract the file data if it was the first file in the archive.
+* If a ZIP file is read in where the reported number of entries does not match the actual number of entries, this would crash the application.
+* If the directory entry, in a SparkFS archive, appeared after any of it's child entries, then these child entries would not get shown in the directory tree.
+* A bug had crept in, at some earlier version, that crashed the application when the filetype was clicked on.
+* When editing the date/time stamp, clicking on a field during editing would instead commence editing the selected node on the directory tree for renaming.
+
+
Platform History
----------------
diff --git a/Documentation/Disc Image Manager User Guide.docx b/Documentation/Disc Image Manager User Guide.docx
index 75ed587..140545a 100644
Binary files a/Documentation/Disc Image Manager User Guide.docx and b/Documentation/Disc Image Manager User Guide.docx differ
diff --git a/Documentation/Disc Image Manager User Guide.pdf b/Documentation/Disc Image Manager User Guide.pdf
index fcd7113..7595d26 100644
Binary files a/Documentation/Disc Image Manager User Guide.pdf and b/Documentation/Disc Image Manager User Guide.pdf differ
diff --git a/Documentation/ToDo.txt b/Documentation/ToDo.txt
index 0d9c744..270eed0 100644
--- a/Documentation/ToDo.txt
+++ b/Documentation/ToDo.txt
@@ -44,12 +44,6 @@ Spectrum/Amstrad
MMFS
* Rewrite/rethink entire MMFS idea. Possibly remove from Disc Image Manager.
-Spark
-* Create new archive.
-* Create directory.
-* Write file.
-* Rename file/directory.
-* Delete file/directory.
-* Move file.
+SparkFS
DOS Plus
\ No newline at end of file
diff --git a/LazarusSource/DiscImage.pas b/LazarusSource/DiscImage.pas
index 9e17fd8..c87a735 100755
--- a/LazarusSource/DiscImage.pas
+++ b/LazarusSource/DiscImage.pas
@@ -1,7 +1,7 @@
unit DiscImage;
{
-TDiscImage class V1.41
+TDiscImage class V1.42
Manages retro disc images, presenting a list of files and directories to the
parent application. Will also extract files and write new files. Almost a complete
filing system in itself. Compatible with Acorn DFS, Acorn ADFS, UEF, Commodore
@@ -140,7 +140,7 @@ TFragment = record //For retrieving the ADFS E/F fragment informati
free_space : array of QWord;//Free space per partition
disc_name : array of String;//Disc title(s)
bootoption : TDIByteArray; //Boot Option(s)
- CFSFiles : array of TDIByteArray;//All the data for the CFS files
+ FilesData : array of TDIByteArray;//All the data for CFS or Spark files
FProgress : TProgressProc;//Used for feedback
SparkFile : TSpark; //For reading in Spark archives
//Private methods
@@ -377,6 +377,16 @@ TFragment = record //For retrieving the ADFS E/F fragment informati
function ID_Spark: Boolean;
function ReadSparkArchive: TDisc;
function ExtractSparkFile(filename: String;var buffer: TDIByteArray): Boolean;
+ function FormatSpark(Zipfilename: String): TDisc;
+ function DeleteSparkFile(filename: String): Boolean;
+ function UpdateSparkAttributes(filename,attributes: String): Boolean;
+ function MoveSparkFile(filename, dest: String): Integer;
+ function WriteSparkFile(var file_details:TDirEntry;var buffer:TDIByteArray):Integer;
+ function RenameSparkFile(filename, newfilename: String): Integer;
+ function UpdateSparkFileType(filename: String; newtype: String): Boolean;
+ function UpdateSparkTimeStamp(filename: String;newtimedate:TDateTime): Boolean;
+ function UpdateSparkFileAddr(filename:String;newaddr:Cardinal;load:Boolean):Boolean;
+ function CreateSparkDirectory(filename, parent, attributes: String): Integer;
//DOS Plus Routines
function ID_DOSPlus: Boolean;
function IDDOSPartition(ctr: Cardinal): Boolean;
@@ -448,7 +458,7 @@ TFragment = record //For retrieving the ADFS E/F fragment informati
procedure ReadImage;
procedure SaveToFile(filename: String;uncompress: Boolean=False);
procedure Close;
- function FormatFDD(major:Word;minor,tracks: Byte): Boolean;
+ function FormatFDD(major:Word;minor:Byte=0;tracks: Byte=0;filename: String=''): Boolean;
function FormatHDD(major:Word;harddrivesize:Cardinal;newmap:Boolean;dirtype:Byte):Boolean;
function ExtractFile(filename:String;var buffer:TDIByteArray;entry:Cardinal=0): Boolean;
function WriteFile(var file_details: TDirEntry; var buffer: TDIByteArray): Integer;
diff --git a/LazarusSource/DiscImageManager.lpi b/LazarusSource/DiscImageManager.lpi
index ef21262..1d37ab6 100644
--- a/LazarusSource/DiscImageManager.lpi
+++ b/LazarusSource/DiscImageManager.lpi
@@ -21,9 +21,9 @@
-
+
-
+
diff --git a/LazarusSource/DiscImageManager.lps b/LazarusSource/DiscImageManager.lps
index 1f3ca08..61013d2 100644
--- a/LazarusSource/DiscImageManager.lps
+++ b/LazarusSource/DiscImageManager.lps
@@ -19,10 +19,9 @@
-
-
-
+
+
@@ -31,8 +30,8 @@
-
-
+
+
@@ -55,9 +54,9 @@
-
-
-
+
+
+
@@ -68,7 +67,7 @@
-
+
@@ -78,16 +77,16 @@
-
-
-
+
+
+
-
+
@@ -99,8 +98,8 @@
-
-
+
+
@@ -112,7 +111,7 @@
-
+
@@ -133,9 +132,10 @@
-
-
-
+
+
+
+
@@ -155,8 +155,8 @@
-
-
+
+
@@ -180,7 +180,7 @@
-
+
@@ -266,15 +266,15 @@
-
-
-
+
+
+
-
+
@@ -282,7 +282,7 @@
-
+
@@ -290,7 +290,7 @@
-
+
@@ -311,9 +311,9 @@
-
-
-
+
+
+
@@ -326,38 +326,38 @@
-
+
+
-
+
-
-
-
+
+
+
-
-
+
-
-
+
+
@@ -366,127 +366,131 @@
-
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
+
+
+
+
@@ -498,17 +502,28 @@
-
-
+
+
+
+ -
+
-
-
+
+
+
+ -
+
+
-
-
+
+
-
-
+
+
diff --git a/LazarusSource/DiscImageManager.res b/LazarusSource/DiscImageManager.res
index 286e2a8..2f35918 100644
Binary files a/LazarusSource/DiscImageManager.res and b/LazarusSource/DiscImageManager.res differ
diff --git a/LazarusSource/DiscImageUtils.pas b/LazarusSource/DiscImageUtils.pas
index d333f0b..60a5ae8 100644
--- a/LazarusSource/DiscImageUtils.pas
+++ b/LazarusSource/DiscImageUtils.pas
@@ -1,7 +1,7 @@
unit DiscImageUtils;
{
-DiscImageUtils V1.41 - part of TDiscImage class
+DiscImageUtils V1.42 - part of TDiscImage class
Copyright (C) 2018-2022 Gerald Holdsworth gerald@hollypops.co.uk
diff --git a/LazarusSource/DiscImage_ADFS.pas b/LazarusSource/DiscImage_ADFS.pas
index aca61d8..a79b8d2 100644
--- a/LazarusSource/DiscImage_ADFS.pas
+++ b/LazarusSource/DiscImage_ADFS.pas
@@ -3119,7 +3119,7 @@ function TDiscImage.DeleteADFSFile(filename: String;
//encounters a directory, that will get it's contents deleted, then itself.
while(Length(FDisc[FDisc[dir].Entries[entry].DirRef].Entries)>0)
and(success)do
- //If any fail for some reason, the whole thing fails
+ //If any fail for some reason, the whole thing fails
success:=DeleteADFSFile(
filename
+dir_sep
diff --git a/LazarusSource/DiscImage_CFS.pas b/LazarusSource/DiscImage_CFS.pas
index b6428ce..c701679 100644
--- a/LazarusSource/DiscImage_CFS.pas
+++ b/LazarusSource/DiscImage_CFS.pas
@@ -109,8 +109,8 @@ function TDiscImage.ReadUEFFile: TDisc;
if filenum>=Length(Result[0].Entries) then
begin
//If the last file failed CRC checks on any block, clear the data
- if (not crcok) and (Length(CFSFiles)>0) then
- SetLength(CFSFiles[filenum],0);
+ if (not crcok) and (Length(FilesData)>0) then
+ SetLength(FilesData[filenum],0);
//Now create the entry for this file
SetLength(Result[0].Entries,filenum+1);
ResetDirEntry(Result[0].Entries[filenum]);
@@ -119,7 +119,7 @@ function TDiscImage.ReadUEFFile: TDisc;
Result[0].Entries[filenum].Sector :=pos-6; //Where to find it (first block)
Result[0].Entries[filenum].Parent :=Result[0].Directory;
Result[0].Entries[filenum].DirRef :=-1;
- SetLength(CFSFiles,filenum+1);
+ SetLength(FilesData,filenum+1);
firstblck:=True;
//Read in the load address
Result[0].Entries[filenum].LoadAddr:=Read32b(pos+i);
@@ -161,15 +161,15 @@ function TDiscImage.ReadUEFFile: TDisc;
//Move our chunk pointer onto the data
inc(i,19);//Points to the data
//Increase the file's data length to match the total length, so far
- SetLength(CFSFiles[filenum],Result[0].Entries[filenum].Length);
+ SetLength(FilesData[filenum],Result[0].Entries[filenum].Length);
//And copy in the data in this block
- for j:=0 to blocklen-1 do CFSFiles[filenum][ptr+j]:=ReadByte(pos+i+j);
+ for j:=0 to blocklen-1 do FilesData[filenum][ptr+j]:=ReadByte(pos+i+j);
//Move to after the data
inc(i,blocklen);
//So we can read in the data's CRC
datacrc:=Read16b(pos+i);
//Check it is valid
- if datacrc<>GetCRC16(ptr,blocklen,CFSFiles[filenum]) then crcok:=False;
+ if datacrc<>GetCRC16(ptr,blocklen,FilesData[filenum]) then crcok:=False;
end;
{ $0110 : //High Tone ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//Work out the length of the tone
@@ -218,14 +218,14 @@ function TDiscImage.ExtractCFSFile(entry: Integer;var buffer:TDIByteArray):Boole
begin
//As UEFs can have many files with the same name, we need to use the direct
//access into the array - entry is the index of FDisc{x].Entries
- Result:=Length(CFSFiles[entry])>0; //Return a false result if no data
+ Result:=Length(FilesData[entry])>0; //Return a false result if no data
//If the CRC check failed, there will be no data
if Result then
begin
//Set the buffer
- SetLength(buffer,Length(CFSFiles[entry]));
+ SetLength(buffer,Length(FilesData[entry]));
//Copy the data across
- for i:=0 to Length(CFSFiles[entry])-1 do buffer[i]:=CFSFiles[entry][i];
+ for i:=0 to Length(FilesData[entry])-1 do buffer[i]:=FilesData[entry][i];
end;
end;
@@ -293,14 +293,14 @@ procedure TDiscImage.WriteUEFFile(filename: String;uncompress: Boolean=False);
fileptr:=0;
//Block counter
blocknum:=0;
- while fileptrLength(CFSFiles[entry]) then
- len:=Length(CFSFiles[entry])-fileptr
+ if fileptr+$100>Length(FilesData[entry]) then
+ len:=Length(FilesData[entry])-fileptr
else
len:=$100;
//And the length of the filename
@@ -328,7 +328,7 @@ procedure TDiscImage.WriteUEFFile(filename: String;uncompress: Boolean=False);
Write16b(len,ptr+10);
//Block status
blockst:=$00;
- if fileptr+len>=Length(CFSFiles[entry]) then
+ if fileptr+len>=Length(FilesData[entry]) then
blockst:=blockst OR $80; //Final block
if Pos('L',FDisc[0].Entries[entry].Attributes)>0 then
blockst:=blockst OR $01; //Locked
@@ -339,7 +339,7 @@ procedure TDiscImage.WriteUEFFile(filename: String;uncompress: Boolean=False);
Write16b(GetCRC16(ptr-Length(temp),Length(temp)+17,dummy),ptr+17);
//Data
SetDataLength(GetDataLength+len+3);
- for j:=0 to len do WriteByte(CFSFiles[entry][fileptr+j],ptr+19+j);
+ for j:=0 to len do WriteByte(FilesData[entry][fileptr+j],ptr+19+j);
//Data CRC-16
Write16b(GetCRC16(ptr+19,len,dummy),ptr+19+len);
//Move data pointer on
@@ -348,7 +348,7 @@ procedure TDiscImage.WriteUEFFile(filename: String;uncompress: Boolean=False);
SetDataLength(GetDataLength+8);
Write16b($110,ptr);
Write32b(2,ptr+2);
- if fileptr+len>=Length(CFSFiles[entry]) then
+ if fileptr+len>=Length(FilesData[entry]) then
Write16b($07D0,ptr+6) //Final block, so longer tone
else
Write16b($0258,ptr+6); //Short tone as not at the end
@@ -431,13 +431,13 @@ function TDiscImage.DeleteCFSFile(entry: Cardinal): Boolean;
for i:=entry+1 to Length(FDisc[0].Entries)-1 do
begin
FDisc[0].Entries[i-1]:=FDisc[0].Entries[i];
- CFSFiles[i-1]:=CFSFiles[i];
+ FilesData[i-1]:=FilesData[i];
end;
end;
//Reduce the length by one
SetLength(FDisc[0].Entries,Length(FDisc[0].Entries)-1);
//And the data files
- SetLength(CFSFiles,Length(CFSFiles)-1);
+ SetLength(FilesData,Length(FilesData)-1);
//And signal a success
Result:=True;
end;
@@ -491,7 +491,7 @@ function TDiscImage.MoveCFSFile(entry: Cardinal;dest: Integer): Integer;
for i:=entry downto dest+2 do
begin
FDisc[0].Entries[i]:=FDisc[0].Entries[i-1];
- CFSFiles[i]:=CFSFiles[i-1];
+ FilesData[i]:=FilesData[i-1];
end;
inc(dest);
end;
@@ -500,7 +500,7 @@ function TDiscImage.MoveCFSFile(entry: Cardinal;dest: Integer): Integer;
for i:=entry+1 to dest do
begin
FDisc[0].Entries[i-1]:=FDisc[0].Entries[i];
- CFSFiles[i-1]:=CFSFiles[i];
+ FilesData[i-1]:=FilesData[i];
end;
end;
//Is the destination -1? This means insert at the front
@@ -509,13 +509,13 @@ function TDiscImage.MoveCFSFile(entry: Cardinal;dest: Integer): Integer;
for i:=entry-1 downto 0 do
begin
FDisc[0].Entries[i+1]:=FDisc[0].Entries[i];
- CFSFiles[i+1]:=CFSFiles[i];
+ FilesData[i+1]:=FilesData[i];
end;
dest:=0; //Where we are moving to
end;
//Then insert it after the one specified
FDisc[0].Entries[dest]:=file_details;
- CFSFiles[dest]:=buffer;
+ FilesData[dest]:=buffer;
Result:=dest;
end;
end;
@@ -545,17 +545,17 @@ function TDiscImage.CopyCFSFile(entry: Cardinal;dest: Integer): Integer;
file_details:=FDisc[0].Entries[entry];
//Increase the list length
SetLength(FDisc[0].Entries,Length(FDisc[0].Entries)+1);
- SetLength(CFSFiles,Length(CFSFiles)+1);
+ SetLength(FilesData,Length(FilesData)+1);
if dest+10;
end;
+ diSpark://Create Spark
+ begin
+ FDisc:=FormatSpark(filename);
+ Result:=Length(FDisc)>0;
+ end;
diDOSPlus://Create DOS or DOS Plus
begin
case minor of
@@ -369,11 +374,14 @@ function TDiscImage.WriteFile(var file_details: TDirEntry; var buffer: TDIByteAr
//Get the length of data to be written
count:=file_details.Length;
//Only write a file if there is actually any data to be written
- if(count>0)and(Length(free_space)>0)then
+ if(count>0)and((Length(free_space)>0)or(FFormat>>4=diSpark))then
begin
- file_details.Side:=file_details.Side mod Length(free_space);
+ if FFormat>>4<>diSpark then
+ file_details.Side:=file_details.Side mod Length(free_space);
//Can only write a file that will fit on the disc, or CFS
- if(count<=free_space[file_details.Side])or(FFormat>>4=diAcornUEF)then
+ if(count<=free_space[file_details.Side])
+ or(FFormat>>4=diAcornUEF)
+ or(FFormat>>4=diSpark)then
case FFormat>>4 of
diAcornDFS :Result:=WriteDFSFile(file_details,buffer); //Write DFS
diAcornADFS:Result:=WriteADFSFile(file_details,buffer); //Write ADFS
@@ -381,6 +389,7 @@ function TDiscImage.WriteFile(var file_details: TDirEntry; var buffer: TDIByteAr
diSinclair :Result:=WriteSpectrumFile(file_details,buffer);//Write Sinclair/Amstrad
diAmiga :Result:=WriteAmigaFile(file_details,buffer); //Write AmigaDOS
diAcornUEF :Result:=WriteCFSFile(file_details,buffer); //Write CFS
+ diSpark :Result:=WriteSparkFile(file_details,buffer); //Write !Spark
diAcornFS :Result:=WriteAFSFile(file_details,buffer); //Write Acorn FS
diDOSPlus :Result:=WriteDOSFile(file_details,buffer); //Write DOS Plus
end;
@@ -406,6 +415,8 @@ function TDiscImage.CreateDirectory(var filename,parent,attributes: String): Int
diAcornUEF : exit;//Can't create directories on CFS
diAcornFS : //Create directory on Acorn FS
Result:=CreateAFSDirectory(filename,parent,attributes);
+ diSpark : //Create directory on !SparkFS
+ Result:=CreateSparkDirectory(filename,parent,attributes);
diDOSPlus : //Create directory on DOS Plus
Result:=CreateDOSDirectory(filename,parent,attributes);
end;
@@ -832,6 +843,7 @@ function TDiscImage.RenameFile(oldfilename: String;var newfilename: String;
diAmiga : Result:=RenameAmigaFile(oldfilename,newfilename); //Rename AmigaDOS
diAcornUEF : Result:=RenameCFSFile(entry,newfilename); //Rename CFS
diAcornFS : Result:=RenameAFSFile(oldfilename,newfilename); //Rename AFS
+ diSpark : Result:=RenameSparkFile(oldfilename,newfilename); //Rename Spark
diDOSPlus : Result:=RenameDOSFile(oldfilename,newfilename); //Rename DOS Plus
end;
end;
@@ -850,6 +862,7 @@ function TDiscImage.DeleteFile(filename: String;entry: Cardinal=0): Boolean;
diAmiga : Result:=DeleteAmigaFile(filename); //Delete AmigaDOS
diAcornUEF : Result:=DeleteCFSFile(entry); //Delete CFS
diAcornFS : Result:=DeleteAFSFile(filename); //Delete Acorn FS
+ diSpark : Result:=DeleteSparkFile(filename); //Delete SparkFS
diDOSPlus : Result:=DeleteDOSFile(filename); //Delete DOS Plus
end;
end;
@@ -865,6 +878,7 @@ function TDiscImage.MoveFile(filename,directory: String): Integer;
diAcornADFS: Result:=MoveADFSFile(filename,directory); //Move ADFS File
diAmiga : Result:=MoveAmigaFile(filename,directory);//Move Amiga File
diAcornFS : Result:=MoveAFSFile(filename,directory); //Move AFS File
+ diSpark : Result:=MoveSparkFile(filename,directory);//Move Spark File
diDOSPlus : Result:=MoveDOSFile(filename,directory); //Move DOS File
end;
end;
@@ -976,6 +990,7 @@ function TDiscImage.UpdateAttributes(filename,attributes: String;entry:Cardinal=
diAmiga : Result:=UpdateAmigaFileAttributes(filename,attributes); //Update AmigaDOS attributes
diAcornUEF : Result:=UpdateCFSAttributes(entry,attributes); //Update CFS attributes
diAcornFS : Result:=UpdateAFSAttributes(filename,attributes); //Update AFS attributes
+ diSpark : Result:=UpdateSparkAttributes(filename,attributes); //Update Spark attributes
diDOSPlus : Result:=UpdateDOSAttributes(filename,attributes); //Update DOS Plus attributes
end;
end;
@@ -1036,6 +1051,7 @@ function TDiscImage.UpdateLoadAddr(filename:String;newaddr:Cardinal;entry:Cardin
diAmiga : exit;//Update AmigaDOS Load Address
diAcornUEF : Result:=UpdateCFSFileAddr(entry,newaddr,True); //Update CFS Load Address
diAcornFS : Result:=UpdateAFSFileAddr(filename,newaddr,True); //Update AFS Load Address
+ diSpark : Result:=UpdateSparkFileAddr(filename,newaddr,True);//Update Spark Load Address
diDOSPlus : exit;//No Load address on DOS Plus
end;
end;
@@ -1054,6 +1070,7 @@ function TDiscImage.UpdateExecAddr(filename:String;newaddr:Cardinal;entry:Cardin
diAmiga : exit;//Update AmigaDOS Execution Address
diAcornUEF : Result:=UpdateCFSFileAddr(entry,newaddr,False); //Update CFS Execution Address
diAcornFS : Result:=UpdateAFSFileAddr(filename,newaddr,False); //Update AFS Execution Address
+ diSpark : Result:=UpdateSparkFileAddr(filename,newaddr,False);//Update Spark Load Address
diDOSPlus : exit;//No Execution address on DOS Plus
end;
end;
@@ -1072,6 +1089,7 @@ function TDiscImage.TimeStampFile(filename:String;newtimedate:TDateTime):Boolean
diAmiga : exit;//Update AmigaDOS Timestamp
diAcornUEF : exit;//Update CFS Timestamp
diAcornFS : Result:=UpdateAFSTimeStamp(filename,newtimedate);//Update AFS Timestamp
+ diSpark : Result:=UpdateSparkTimeStamp(filename,newtimedate);//Update Spark Timestamp
diDOSPlus : Result:=UpdateDOSTimeStamp(filename,newtimedate);//Update DOS Timestamp
end;
end;
@@ -1090,6 +1108,7 @@ function TDiscImage.ChangeFileType(filename,newtype: String): Boolean;
diAmiga : exit;//Update AmigaDOS Filetype
diAcornUEF : exit;//Update CFS Filetype
diAcornFS : exit;//Update AFS Filetype
+ diSpark : Result:=UpdateSparkFileType(filename,newtype);
diDOSPlus : exit;//Update DOS Filetype - done through renaming
end;
end;
diff --git a/LazarusSource/DiscImage_Spark.pas b/LazarusSource/DiscImage_Spark.pas
index 438b0d3..875bd08 100644
--- a/LazarusSource/DiscImage_Spark.pas
+++ b/LazarusSource/DiscImage_Spark.pas
@@ -31,15 +31,11 @@ function TDiscImage.ID_Spark: Boolean;
function TDiscImage.ReadSparkArchive: TDisc;
var
index,
- cnt,
ref : Integer;
d,
e : Cardinal;
- pnt,
- temp : String;
+ pnt : String;
OldDisc: TDisc;
-const
- NewAtts: array[0..7] of Char = ('R','W','L','D','r','w',' ',' ');
begin
//In order to be able to use FileExists, we need to populate FDisc
OldDisc:=FDisc; //So take a note
@@ -50,7 +46,7 @@ function TDiscImage.ReadSparkArchive: TDisc;
ResetDir(FDisc[0]);
FDisc[0].Directory:='$';
FDisc[0].BeenRead:=True;
- //Now go through all the entries i the spark and add them
+ //Now go through all the entries in the spark and add them
for index:=0 to Length(SparkFile.FileList)-1 do
begin
//Work out the parent, relative to root
@@ -102,14 +98,8 @@ function TDiscImage.ReadSparkArchive: TDisc;
FDisc[d].Entries[e].Track:=Index;//Reference into the archive
FDisc[d].Entries[e].DirRef:=ref; //Directory reference from earlier
//Collate the attributes
- temp:='';
- for cnt:=0 to 7 do
- if IsBitSet(SparkFile.FileList[index].Attributes,cnt) then
- temp:=temp+NewAtts[cnt];
- //Reverse the attribute order to match actual ADFS
- if Length(temp)>0 then
- for cnt:=Length(temp) downto 1 do
- FDisc[d].Entries[e].Attributes:=FDisc[d].Entries[e].Attributes+temp[cnt];
+ FDisc[d].Entries[e].Attributes:=
+ SparkFile.ConvertAttribute(SparkFile.FileList[index].Attributes);
//Calculate the timestamp and filetype
ADFSCalcFileDate(FDisc[d].Entries[e]);
end;
@@ -148,3 +138,469 @@ function TDiscImage.ExtractSparkFile(filename: String;var buffer: TDIByteArray):
end;
end;
end;
+
+{-------------------------------------------------------------------------------
+Create a new Spark archive
+-------------------------------------------------------------------------------}
+function TDiscImage.FormatSpark(Zipfilename: String): TDisc;
+begin
+ Result:=nil;
+ if Zipfilename='' then exit;
+ //Set up the TDisc structure for return
+ SetLength(Result,1);
+ ResetDir(Result[0]);
+ //Set the root directory name
+ root_name:='$';
+ Result[0].Directory:=root_name;
+ Result[0].BeenRead:=True;
+ //Set the format
+ FFormat:=diSpark<<4;
+ //Create a blank file
+ SparkFile:=TSpark.Create(Zipfilename,True);
+ //Set the filename
+ imagefilename:=Zipfilename;
+end;
+
+{-------------------------------------------------------------------------------
+Delete a file from a Spark archive
+-------------------------------------------------------------------------------}
+function TDiscImage.DeleteSparkFile(filename: String): Boolean;
+var
+ index,
+ dir,
+ entry : Cardinal;
+ olddir : String;
+begin
+ Result:=False;
+ //Ensure the file actually exists
+ if FileExists(filename,dir,entry) then
+ begin
+ //Convert the filename
+ SparkFile.SwapDirSep(filename);
+ if FDisc[dir].Entries[entry].DirRef<>-1 then filename:=filename+'/';
+ if filename[1]='$' then filename:=Copy(filename,3);
+ //Delete the file/directory
+ SparkFile.DeleteFile(filename);
+ //Return a postive result (the above method will return a false)
+ Result:=True;
+ //This needs to replicate when the Spark class is doing for the local copy
+ //i.e., for directories, delete all contents as well as the directory
+ if entry-1 then filename:=filename+'/';
+ if filename[1]='$' then filename:=Copy(filename,3);
+ //Update the attributes
+ if SparkFile.UpdateAttributes(filename,SparkFile.ConvertAttribute(attributes)) then
+ begin
+ //And update the local copy of the attributes
+ FDisc[dir].Entries[entry].Attributes:=attributes;
+ //And set a positive result
+ Result:=True;
+ end;
+ end;
+end;
+
+{-------------------------------------------------------------------------------
+Move a file from one directory to another
+-------------------------------------------------------------------------------}
+function TDiscImage.MoveSparkFile(filename, dest: String): Integer;
+var
+ sdir,sentry,
+ ddir,dentry : Cardinal;
+ olddir : String;
+ entry,
+ index : Integer;
+begin
+ Result:=-1;
+ //Moving is just the same as renaming, but supplying the entire path for both
+ //source and destination.
+ if FileExists(filename,sdir,sentry) then //Make sure the source file exists
+ if FileExists(dest,ddir,dentry) then //And the destination exists
+ begin
+ //As we are just renaming, tag the filename onto the end of the destination
+ dest:=dest+dir_sep+FDisc[sdir].Entries[sentry].Filename;
+ //Swap the directory separators for both
+ SparkFile.SwapDirSep(filename);
+ SparkFile.SwapDirSep(dest);
+ //Add the trailing '/' if the source is a directory
+ if FDisc[sdir].Entries[sentry].DirRef<>-1 then
+ begin
+ filename:=filename+'/';
+ dest:=dest+'/';
+ end;
+ //Rename it
+ SparkFile.RenameFile(filename,dest);
+ //Update the local copy - destination
+ entry:=FDisc[ddir].Entries[dentry].DirRef;
+ SetLength(FDisc[entry].Entries,Length(FDisc[entry].Entries)+1);
+ FDisc[entry].Entries[Length(FDisc[entry].Entries)-1]:=FDisc[sdir].Entries[sentry];
+ Result:=Length(FDisc[entry].Entries)-1;
+ //Update the local copy - source
+ if Length(FDisc[sdir].Entries)=1 then SetLength(FDisc[sdir].Entries,0)
+ else
+ begin
+ //If not the end one
+ if sentry'$' then
+ begin
+ //Get the number of entries in the directory
+ ref:=Length(FDisc[dir].Entries);
+ //Convert load/exec address into filetype and datestamp, if necessary
+ ADFSCalcFileDate(file_details); //Spark is based on ADFS
+ //Now we add the entry into the directory catalogue
+ ptr:=ExtendADFSCat(dir,file_details); //Spark is based on ADFS
+ //Not a directory
+ FDisc[dir].Entries[ptr].DirRef:=-1;
+ //Filetype and Timestamp for Arthur and RISC OS ADFS
+ if (FDisc[dir].Entries[ptr].LoadAddr=0)
+ and(FDisc[dir].Entries[ptr].ExecAddr=0)
+ then
+ begin
+ FDisc[dir].Entries[ptr].LoadAddr:=$FFF00000;
+ //Set the filetype, if not already set
+ if FDisc[dir].Entries[ptr].ShortFileType<>'' then
+ begin
+ FDisc[dir].Entries[ptr].LoadAddr:=FDisc[dir].Entries[ptr].LoadAddr OR
+ (StrToIntDef('$'+FDisc[dir].Entries[ptr].ShortFileType,0)<<8);
+ FDisc[dir].Entries[ptr].FileType:=
+ GetFiletypeFromNumber(StrToIntDef('$'+FDisc[dir].Entries[ptr].ShortFileType,0));
+ end;
+ //Timestamp it, if not already done
+ timestamp:=TimeDateToRISCOS(Now);
+ FDisc[dir].Entries[ptr].TimeStamp:=Now;
+ FDisc[dir].Entries[ptr].LoadAddr:=FDisc[dir].Entries[ptr].LoadAddr OR
+ (timestamp DIV $100000000);
+ FDisc[dir].Entries[ptr].ExecAddr:=timestamp MOD $100000000;
+ end;
+ //File imported from DFS, expand the tube address, if needed
+ if (FDisc[dir].Entries[ptr].LoadAddr>>16=$00FF)
+ and(FDisc[dir].Entries[ptr].ExecAddr>>16=$00FF)then
+ begin
+ FDisc[dir].Entries[ptr].LoadAddr:=
+ (FDisc[dir].Entries[ptr].LoadAddr AND $0000FFFF)
+ OR$FFFF0000;
+ FDisc[dir].Entries[ptr].ExecAddr:=
+ (FDisc[dir].Entries[ptr].ExecAddr AND $0000FFFF)
+ OR$FFFF0000;
+ end;
+ //Is the file actually a directory?
+ if Pos('D',file_details.Attributes)>0 then
+ begin
+ //Increase the number of directories by one
+ SetLength(FDisc,Length(FDisc)+1);
+ //Then assign DirRef
+ FDisc[dir].Entries[ptr].DirRef:=Length(FDisc)-1;
+ //Assign the directory properties
+ FDisc[Length(FDisc)-1].Directory:=FDisc[dir].Entries[ptr].Filename;
+ FDisc[Length(FDisc)-1].Title :=FDisc[dir].Entries[ptr].Filename;
+ FDisc[Length(FDisc)-1].Broken :=False;
+ FDisc[Length(FDisc)-1].Parent :=dir;
+ FDisc[Length(FDisc)-1].Sector :=FDisc[dir].Entries[ptr].Sector;
+ SetLength(FDisc[Length(FDisc)-1].Entries,0);
+ end;
+ //And send the result back to the client
+ Result:=ptr;
+ //Write the result to the ZIP file
+ filetozip.Filename:=FDisc[dir].Entries[ptr].Filename; //RISC OS filename
+ filetozip.Parent:=FDisc[dir].Entries[ptr].Parent; //RISC OS parent
+ //The actual name in the archive
+ filetozip.ArchiveName:=FDisc[dir].Entries[ptr].Parent
+ +'.'+FDisc[dir].Entries[ptr].Filename;
+ //Remove the root name, if present
+ if LeftStr(filetozip.ArchiveName,2)='$.' then
+ filetozip.ArchiveName:=Copy(filetozip.ArchiveName,3);
+ //Need to swap ADFS directory and extension separators
+ SparkFile.SwapDirSep(filetozip.ArchiveName);
+ //Convert the attributes from a string to a byte
+ filetozip.Attributes:=
+ SparkFile.ConvertAttribute(FDisc[dir].Entries[ptr].Attributes);
+ //Is it a directory?
+ filetozip.Directory:=FDisc[dir].Entries[ptr].DirRef<>-1;
+ //Load and execution addresses (i.e., filetype and datestamp)
+ filetozip.ExecAddr:=FDisc[dir].Entries[ptr].ExecAddr;
+ filetozip.LoadAddr:=FDisc[dir].Entries[ptr].LoadAddr;
+ //File length
+ filetozip.Size:=FDisc[dir].Entries[ptr].Length;
+ //Write the file
+ SparkFile.WriteFile(filetozip,buffer);
+ //Update the used space
+ disc_size[0]:=SparkFile.UncompressedSize;
+ end else Result:=0;
+ end else Result:=-6; //Directory does not exist
+end;
+
+{-------------------------------------------------------------------------------
+Rename a file
+-------------------------------------------------------------------------------}
+function TDiscImage.RenameSparkFile(filename, newfilename: String): Integer;
+var
+ dir,
+ entry,
+ ptr : Cardinal;
+begin
+ Result:=-1;
+ //Does the file exist?
+ if FileExists(filename,dir,entry) then
+ //And the proposed filename not exist?
+ if not FileExists(GetParent(dir)+dir_sep+newfilename,ptr) then
+ begin
+ //Swap the directory separators
+ SparkFile.SwapDirSep(filename);
+ //Update our local copy
+ FDisc[dir].Entries[entry].Filename:=newfilename;
+ //Are we renaming a directory?
+ if FDisc[dir].Entries[entry].DirRef<>-1 then
+ begin
+ //If the title is the same, change it also
+ if FDisc[FDisc[dir].Entries[entry].DirRef].Title=FDisc[dir].Entries[entry].Filename then
+ FDisc[FDisc[dir].Entries[entry].DirRef].Title:=newfilename;
+ //And the directory name
+ FDisc[FDisc[dir].Entries[entry].DirRef].Directory:=newfilename;
+ end;
+ //Add the path for the proposed filename
+ newfilename:=GetParent(dir)+dir_sep+newfilename;
+ //And swap the separators
+ SparkFile.SwapDirSep(newfilename);
+ //Is it a directory?
+ if FDisc[dir].Entries[entry].DirRef<>-1 then
+ begin
+ //Add the trailing '/'
+ filename:=filename+'/';
+ newfilename:=newfilename+'/';
+ end;
+ SparkFile.RenameFile(filename,newfilename);
+ //Return the original entry point
+ Result:=entry;
+ end;
+end;
+
+{-------------------------------------------------------------------------------
+Update the file type of a file
+-------------------------------------------------------------------------------}
+function TDiscImage.UpdateSparkFileType(filename: String; newtype: String): Boolean;
+var
+ dir,
+ entry,
+ load,
+ exec : Cardinal;
+ newft : Integer;
+begin
+ Result:=False;
+ load:=0;
+ exec:=0;
+ //Ensure the file actually exists
+ if FileExists(filename,dir,entry) then
+ begin
+ //Hex number?
+ if IntToHex(StrToIntDef('$'+newtype,0),3)<>UpperCase(newtype) then
+ newft:=GetFileTypeFromName(newtype) //No, so translate
+ else
+ newft:=StrToInt('$'+newtype); //Yes, just convert
+ //Valid filetype?
+ if newft>=0 then
+ begin
+ //Get the execution address
+ exec:=FDisc[dir].Entries[entry].ExecAddr;
+ //Calculate the new load address
+ load:=FDisc[dir].Entries[entry].LoadAddr;
+ //Set the top 12 bits to indicate filetyped
+ load:=load OR$FFF00000;
+ //Clear the filetype area, preserving the rest
+ load:=load AND$FFF000FF;
+ //Set the filetype
+ load:=load OR(newft<<8);
+ //Convert the filename
+ SparkFile.SwapDirSep(filename);
+ if FDisc[dir].Entries[entry].DirRef<>-1 then filename:=filename+'/';
+ if filename[1]='$' then filename:=Copy(filename,3);
+ //Update the load and exec addresses
+ if SparkFile.UpdateLoadExecAddress(filename,load,exec) then
+ begin
+ //And update the local copy of the filetypes
+ FDisc[dir].Entries[entry].ShortFileType:=IntToHex(newft,3);
+ FDisc[dir].Entries[entry].Filetype:=GetFileTypeFromNumber(newft);
+ //And set a positive result
+ Result:=True;
+ end;
+ end;
+ end;
+end;
+
+{-------------------------------------------------------------------------------
+Update the timestamp of a file
+-------------------------------------------------------------------------------}
+function TDiscImage.UpdateSparkTimeStamp(filename: String;newtimedate:TDateTime): Boolean;
+var
+ dir,
+ entry,
+ load,
+ exec : Cardinal;
+ rotd : QWord;
+begin
+ Result:=False;
+ load:=0;
+ exec:=0;
+ //Ensure the file actually exists
+ if FileExists(filename,dir,entry) then
+ begin
+ //Convert to RISC OS time format
+ rotd:=TimeDatetoRISCOS(newtimedate);//RISC OS TimeDate is 40 bits long
+ //Calculate the new load address
+ load:=FDisc[dir].Entries[entry].LoadAddr;
+ //Set the top 12 bits to indicate filetyped, and preserve the rest
+ load:=load OR$FFF00000;
+ //Clear the bottom 8 bits
+ load:=load AND$FFFFFF00;
+ //Set the bottom 8 bits with the new time
+ load:=load OR(rotd>>32);
+ //Now calculate the new exec address
+ exec:=rotd AND $FFFFFFFF;
+ //Convert the filename
+ SparkFile.SwapDirSep(filename);
+ if FDisc[dir].Entries[entry].DirRef<>-1 then filename:=filename+'/';
+ if filename[1]='$' then filename:=Copy(filename,3);
+ //Update the load and exec addresses
+ if SparkFile.UpdateLoadExecAddress(filename,load,exec) then
+ begin
+ //Update the local copy
+ FDisc[dir].Entries[entry].LoadAddr:=load;
+ FDisc[dir].Entries[entry].ExecAddr:=exec;
+ FDisc[dir].Entries[entry].TimeStamp:=newtimedate;
+ //Return a positive result
+ Result:=True;
+ end;
+ end;
+end;
+
+{-------------------------------------------------------------------------------
+Update the file type of a file
+-------------------------------------------------------------------------------}
+function TDiscImage.UpdateSparkFileAddr(filename: String; newaddr: Cardinal;
+ load: Boolean): Boolean;
+var
+ dir,
+ entry,
+ loadad,
+ execad : Cardinal;
+begin
+ Result:=False;
+ loadad:=0;
+ execad:=0;
+ //Ensure the file actually exists
+ if FileExists(filename,dir,entry) then
+ begin
+ //Get the current load address
+ loadad:=FDisc[dir].Entries[entry].LoadAddr;
+ //Get the current exec address
+ execad:=FDisc[dir].Entries[entry].LoadAddr;
+ //Change the one specified
+ if load then loadad:=newaddr else execad:=newaddr;
+ //Convert the filename
+ SparkFile.SwapDirSep(filename);
+ if FDisc[dir].Entries[entry].DirRef<>-1 then filename:=filename+'/';
+ //Send to the class
+ if SparkFile.UpdateLoadExecAddress(filename,loadad,execad) then
+ begin
+ //Update the local copy
+ FDisc[dir].Entries[entry].LoadAddr:=loadad;
+ FDisc[dir].Entries[entry].ExecAddr:=execad;
+ //Return a positive result
+ Result:=True;
+ end;
+ end;
+end;
+
+{-------------------------------------------------------------------------------
+Create a directory
+-------------------------------------------------------------------------------}
+function TDiscImage.CreateSparkDirectory(filename, parent, attributes: String): Integer;
+var
+ file_details: TDirEntry;
+ buffer : TDIByteArray;
+begin
+ buffer:=nil;
+ SetLength(buffer,0);
+ //Set up the TDirEntry
+ file_details.Filename:=filename;
+ file_details.Parent:=parent;
+ if Pos('D',attributes)<1 then attributes:='D'+attributes;
+ file_details.Attributes:=attributes;
+ //And just pass to the WriteFile function above
+ Result:=WriteSparkFile(file_details,buffer);
+end;
diff --git a/LazarusSource/MainUnit.lfm b/LazarusSource/MainUnit.lfm
index b4a9360..268d329 100755
--- a/LazarusSource/MainUnit.lfm
+++ b/LazarusSource/MainUnit.lfm
@@ -1,7 +1,7 @@
object MainForm: TMainForm
- Left = 369
+ Left = 393
Height = 599
- Top = 158
+ Top = 185
Width = 751
AllowDropFiles = True
ClientHeight = 599
diff --git a/LazarusSource/MainUnit.pas b/LazarusSource/MainUnit.pas
index 6fdad55..6a30323 100755
--- a/LazarusSource/MainUnit.pas
+++ b/LazarusSource/MainUnit.pas
@@ -527,7 +527,7 @@ TMainForm = class(TForm)
DesignedDPI = 96;
//Application Title
ApplicationTitle = 'Disc Image Manager';
- ApplicationVersion = '1.41';
+ ApplicationVersion = '1.42';
//Current platform and architecture (compile time directive)
TargetOS = {$I %FPCTARGETOS%};
TargetCPU = {$I %FPCTARGETCPU%};
@@ -942,7 +942,8 @@ function TMainForm.AddFileToImage(filename:String;filedetails: TDirEntry;
if ((Pos(',',importfilename)>0)
or (Pos('.',importfilename)>0))
and((Image.FormatNumber>>4=diAcornADFS) //ADFS
- or (Image.FormatNumber>>4=diCommodore))then //Commodore
+ or (Image.FormatNumber>>4=diCommodore) //Commodore
+ or (Image.FormatNumber>>4=diSpark))then //!Spark
begin
i:=Length(importfilename);
while (importfilename[i]<>'.')and(importfilename[i]<>',')do dec(i);
@@ -954,17 +955,19 @@ function TMainForm.AddFileToImage(filename:String;filedetails: TDirEntry;
for index:=1 to Length(Extensions) do
if Copy(Extensions[index],4)=LowerCase(filetype) then
filetype:=LeftStr(Extensions[index],3);
- //ADFS
- if Image.FormatNumber>>4=diAcornADFS then
+ //ADFS and Spark
+ if(Image.FormatNumber>>4=diAcornADFS)
+ or(Image.FormatNumber>>4=diSpark)then
begin
filetype:=IntToHex(StrToIntDef('$'+filetype,0),3);
if filetype='000' then filetype:='';//None, so reset
end;
end;
- //ADFS, AFS, DFS & CFS only stuff
+ //ADFS, AFS, DFS, Spark & CFS only stuff
if((Image.FormatNumber>>4=diAcornDFS)
or(Image.FormatNumber>>4=diAcornADFS)
or(Image.FormatNumber>>4=diAcornUEF)
+ or(Image.FormatNumber>>4=diSpark)
or(Image.FormatNumber>>4=diAcornFS))
and(filename<>'')then
begin
@@ -1001,28 +1004,31 @@ function TMainForm.AddFileToImage(filename:String;filedetails: TDirEntry;
attributes:=''; //Default
if attr1='' then
begin
- if Image.FormatNumber>>4=diAcornADFS then attributes:='WR';//Default for ADFS
+ if(Image.FormatNumber>>4=diAcornADFS)
+ or(Image.FormatNumber>>4=diSpark) then attributes:='WR';//Default for ADFS and Spark
if Image.FormatNumber>>4=diCommodore then attributes:='C' ;//Default for Commodore
end;
attributes:=attributes+GetAttributes(attr1,Image.FormatNumber>>4);
if importfilename='' then importfilename:='NewFile';
- //Validate the filename (ADFS, AFS, DFS & CFS only)
+ //Validate the filename (ADFS, AFS, DFS, Spark & CFS only)
if(Image.FormatNumber>>4=diAcornDFS)
or(Image.FormatNumber>>4=diAcornADFS)
or(Image.FormatNumber>>4=diAcornUEF)
+ or(Image.FormatNumber>>4=diSpark)
or(Image.FormatNumber>>4=diAcornFS)then
begin
//Remove any extraenous specifiers
while (importfilename[4]=Image.DirSep) do
importfilename:=RightStr(importfilename,Length(importfilename)-2);
//If root, remove the directory specifier
- if (importfilename[2]=Image.DirSep) and (importfilename[1]='$') then
+ if(importfilename[2]=Image.DirSep)and(importfilename[1]='$')then
importfilename:=RightStr(importfilename,Length(importfilename)-2);
//Convert a Windows filename to a BBC filename
WinToBBC(importfilename);
//Check to make sure that a DFS directory hasn't been changed
if((Image.FormatNumber>>4=diAcornDFS)
or(Image.FormatNumber>>4=diAcornADFS)
+ or(Image.FormatNumber>>4=diSpark)
or(Image.FormatNumber>>4=diAcornFS))
and(importfilename[2]='/')then
importfilename[2]:=Image.DirSep;
@@ -1039,6 +1045,7 @@ function TMainForm.AddFileToImage(filename:String;filedetails: TDirEntry;
NewFile.DirRef :=-1; //Not a directory
NewFile.ShortFileType:=filetype;
if(Image.FormatNumber>>4=diAcornADFS) //Need the selected directory for ADFS
+ or(Image.FormatNumber>>4=diSpark) //And Spark
or(Image.FormatNumber>>4=diAcornFS) //And Acorn FS
or(Image.FormatNumber>>4=diDOSPlus)then//And DOS Plus
if(DirList.Selected.Text='$')
@@ -1080,7 +1087,7 @@ function TMainForm.AddFileToImage(filename:String;filedetails: TDirEntry;
//Function returns pointer to next item (or parent if no children)
if Result>-1 then //File added OK
begin
- HasChanged:=True;
+ if Image.FormatNumber>>4<>diSpark then HasChanged:=True;
AddFileToTree(DirList.Selected,NewFile.Filename,Result,False,DirList);
UpdateImageInfo(side);
end
@@ -2070,9 +2077,6 @@ procedure TMainForm.DirListChange(Sender: TObject; Node: TTreeNode);
or(Image.FormatNumber>>4=diAcornFS) //Acorn FS
or((Image.FormatNumber>>4=diAcornADFS) //Acorn ADFS
and(Image.InterleaveMethod>0)); //with non-auto interleave
- {btn_ChangeInterleave.Enabled:=(Image.FormatNumber=diAcornADFS<<4+2) //ADFS L
- or(Image.FormatNumber=diAcornADFS<<4+$E)//ADFS Hybrid
- or(Image.FormatNumber>>4=diAcornFS); //Acorn FS }
menuChangeInterleave.Enabled:=btn_ChangeInterleave.Enabled;
//Change the captions
temp:='Partition';
@@ -2111,7 +2115,8 @@ procedure TMainForm.DirListChange(Sender: TObject; Node: TTreeNode);
if(Image.FormatNumber>>4=diAcornADFS)
OR(Image.FormatNumber>>4=diAmiga)
or(Image.FormatNumber>>4=diAcornFS)
- or(Image.FormatNumber>>4=diDOSPlus)then //ADFS, Amiga, Acorn FS and DOS Plus
+ or(Image.FormatNumber>>4=diSpark)
+ or(Image.FormatNumber>>4=diDOSPlus)then //ADFS, Amiga, Acorn FS, Spark and DOS Plus
begin
NewDirectory1.Enabled :=True;
btn_NewDirectory.Enabled:=True;
@@ -3618,8 +3623,9 @@ procedure TMainForm.sb_FileTypeClick(Sender: TObject);
dir,
entry: Integer;
begin
- //ADFS non directories only
- if (Image.FormatNumber>>4=diAcornADFS)
+ //ADFS or Spark non directories only
+ if((Image.FormatNumber>>4=diAcornADFS)
+ or(Image.FormatNumber>>4=diSpark))
and(not TMyTreeNode(DirList.Selected).IsDir)then
begin
//Get the references
@@ -3634,8 +3640,9 @@ procedure TMainForm.sb_FileTypeClick(Sender: TObject);
//Reset all the buttons to up
FTDummyBtn.Down:=True;
//Now set the one for our filetype, if it is there, to down
- for i:=0 to Length(FTButtons)-1 do
- if FTButtons[i].Tag=ft then FTButtons[i].Down:=True;
+ if Length(FTButtons)>0 then
+ for i:=0 to Length(FTButtons)-1 do
+ if FTButtons[i].Tag=ft then FTButtons[i].Down:=True;
//Set the custom filetype to this filetype
FTEdit.Text:=IntToHex(ft,3);
//Show the dialogue modally
@@ -3677,7 +3684,7 @@ procedure TMainForm.FileTypeClick(Sender: TObject);
//If all went OK, update the display
DirListChange(Sender,DirList.Selected);
//And mark as changed
- HasChanged:=True;
+ if Image.FormatNumber>>4<>diSpark then HasChanged:=True;
end;
end;
end;
@@ -3781,7 +3788,8 @@ procedure TMainForm.lb_timestampClick(Sender: TObject);
//ADFS and AFS only
if(Image.FormatNumber>>4=diAcornADFS)
or(Image.FormatNumber>>4=diAcornFS)
- or(Image.FormatNumber>>4=diDOSPlus)then
+ or(Image.FormatNumber>>4=diDOSPlus)
+ or(Image.FormatNumber>>4=diSpark)then
begin
//Get the references
entry:=DirList.Selected.Index;
@@ -3830,7 +3838,7 @@ procedure TMainForm.ed_timestampEditingDone(Sender: TObject);
//Display the new details
lb_timestamp.Caption:=FormatDateTime(TimeDateFormat,
Image.Disc[dir].Entries[entry].TimeStamp);
- HasChanged:=True;
+ if Image.FormatNumber>>4<>diSpark then HasChanged:=True;
end;
end;
@@ -3936,7 +3944,7 @@ procedure TMainForm.ed_execaddrEditingDone(Sender: TObject);
//If success, then change the text
lb_execaddr.Caption:='0x'+IntToHex(Image.Disc[dir].Entries[entry].ExecAddr,8);
lb_loadaddr.Caption:='0x'+IntToHex(Image.Disc[dir].Entries[entry].LoadAddr,8);
- HasChanged:=True;
+ if Image.FormatNumber>>4<>diSpark then HasChanged:=True;
end;
end;
end;
@@ -4931,7 +4939,7 @@ function TMainForm.CreateDirectory(dirname, attr: String): TTreeNode;
if index>-1 then //Directory added OK
begin
//Mark as changed
- HasChanged:=True;
+ if Image.FormatNumber>>4<>diSpark then HasChanged:=True;
//Create the node as a file
Node:=AddFileToTree(DirList.Selected,dirname,index,True,DirList);
//Update the directory reference and the directory flag
@@ -5332,7 +5340,7 @@ procedure TMainForm.DoCopyMove(copymode: Boolean);
end;
end;
//Mark as changed
- HasChanged:=True;
+ if Image.FormatNumber>>4<>diSpark then HasChanged:=True;
if not copymode then //If moving
begin
//Update any open hexdumps
@@ -5420,10 +5428,12 @@ procedure TMainForm.CancelDragDropExecute(Sender: TObject);
{------------------------------------------------------------------------------}
procedure TMainForm.btn_NewImageClick(Sender: TObject);
var
- major : Word;
+ major : Word;
minor,
- tracks : Byte;
- ok : Boolean;
+ tracks : Byte;
+ ok : Boolean;
+ index : Integer;
+ filename : String;
begin
if QueryUnsaved then
begin
@@ -5432,9 +5442,6 @@ procedure TMainForm.btn_NewImageClick(Sender: TObject);
//If Create was clicked, then create a new image
if NewImageForm.ModalResult=mrOK then
begin
- ProgressForm.Show;
- Application.ProcessMessages;
- Image.ProgressIndicator:=@UpdateProgress;
//Get the main format
major:=$FFF;
case NewImageForm.MainFormat.ItemIndex of
@@ -5466,6 +5473,25 @@ procedure TMainForm.btn_NewImageClick(Sender: TObject);
tracks:=NewImageForm.DFSTracks.ItemIndex;
//Now create the image
ok:=False;
+ //Get the filename for a new Spark
+ filename:='';
+ if major=diSpark then
+ begin
+ SaveImage.Title:='Create New !Spark Image As';
+ //Populate the filter part of the dialogue
+ index:=0;
+ SaveImage.Filter:=Image.SaveFilter(index,diSpark<<4);
+ if index=0 then index:=1;
+ SaveImage.FilterIndex:=index;
+ //Populate the filename part of the dialogue
+ SaveImage.FileName:='Untitled.zip';
+ SaveImage.DefaultExt:='.zip';
+ //Show the dialogue and set the filename
+ if SaveImage.Execute then filename:=SaveImage.FileName else exit;
+ end;
+ ProgressForm.Show;
+ Application.ProcessMessages;
+ Image.ProgressIndicator:=@UpdateProgress;
//ADFS Hard Drive
if(major=diAcornADFS)and(minor=8)then
ok:=Image.FormatHDD(diAcornADFS,
@@ -5492,11 +5518,11 @@ procedure TMainForm.btn_NewImageClick(Sender: TObject);
False,
NewImageForm.fat)
else //Floppy Drive
- ok:=Image.FormatFDD(major,minor,tracks);
+ ok:=Image.FormatFDD(major,minor,tracks,filename);
if ok then
begin
CloseAllHexDumps;
- HasChanged:=True;
+ if major<>diSpark then HasChanged:=True;
ShowNewImage(Image.Filename); //This updates the status bar
end
else
@@ -5541,7 +5567,9 @@ procedure TMainForm.AttributeChangeClick(Sender: TObject);
or(Image.FormatNumber>>4=diAcornUEF)then
if cb_DFS_l.Checked then att:=att+'L';
//Attributes - ADFS
- if(Image.FormatNumber>>4=diAcornADFS)and(not afs)and(not dos)then
+ if((Image.FormatNumber>>4=diAcornADFS)
+ or(Image.FormatNumber>>4=diSpark))
+ and(not afs)and(not dos)then
begin
if cb_ADFS_ownw.Checked then att:=att+'W';
if cb_ADFS_ownr.Checked then att:=att+'R';
@@ -5583,7 +5611,7 @@ procedure TMainForm.AttributeChangeClick(Sender: TObject);
//Update the attributes for the file
if Image.UpdateAttributes(filepath,att,DirList.Selected.Index) then
begin
- HasChanged:=True;
+ if Image.FormatNumber>>4<>diSpark then HasChanged:=True;
//Update the status bar
UpdateImageInfo;
end
@@ -5661,7 +5689,7 @@ procedure TMainForm.DeleteFile(confirm: Boolean);
if R then
if Image.DeleteFile(filepath) then
begin
- HasChanged:=True;
+ if Image.FormatNumber>>4<>diSpark then HasChanged:=True;
//Update the status bar
UpdateImageInfo;
//Now update the node and filedetails panel
@@ -5889,12 +5917,24 @@ procedure TMainForm.DirListExit(Sender: TObject);
{------------------------------------------------------------------------------}
procedure TMainForm.DirListEditing(Sender: TObject; Node: TTreeNode;
var AllowEdit: Boolean);
+var
+ ctrl: TControl;
+ pt : TPoint;
begin
- //Set the node to edit mode, if not the root
- AllowEdit:=Node.Parent<>nil;
- //Save the path and name before they get edited
- PathBeforeEdit:=GetFilePath(Node);
- NameBeforeEdit:=Node.Text;
+ AllowEdit:=False;
+ //Get the control under the mouse cursor
+ pt:=ScreenToClient(Mouse.CursorPos);
+ ctrl:=ControlAtPos(pt,[capfRecursive,capfAllowWinControls]);
+ //Make sure it is indeed over the directory tree
+ if Assigned(ctrl) then
+ if ctrl.Name=DirList.Name then
+ begin
+ //Set the node to edit mode, if not the root
+ AllowEdit:=Node.Parent<>nil;
+ //Save the path and name before they get edited
+ PathBeforeEdit:=GetFilePath(Node);
+ NameBeforeEdit:=Node.Text;
+ end;
end;
{------------------------------------------------------------------------------}
@@ -5927,7 +5967,7 @@ procedure TMainForm.DirListEditingEnd(Sender: TObject; Node: TTreeNode;
end
else
begin
- HasChanged:=True;
+ if Image.FormatNumber>>4<>diSpark then HasChanged:=True;
//Update the status bar
UpdateImageInfo;
//Otherwise change the text on the tree and the file details panel
@@ -6298,7 +6338,8 @@ procedure TMainForm.CreateFileTypeDialogue;
sc.BorderStyle:=bsSingle;
sc.OnPaint:=@FileInfoPanelPaint;
//Controls
- SetLength(FTButtons,High(RISCOSFileTypes)-2);
+ SetLength(FTButtons,High(RISCOSFileTypes)-Low(RISCOSFileTypes));
+ i:=Length(FTButtons);
x:=Low(RISCOSFileTypes);
for i:=x to High(RISCOSFileTypes) do
begin
diff --git a/LazarusSource/NewImageUnit.pas b/LazarusSource/NewImageUnit.pas
index f64497a..9e53ba1 100755
--- a/LazarusSource/NewImageUnit.pas
+++ b/LazarusSource/NewImageUnit.pas
@@ -103,6 +103,7 @@ procedure TNewImageForm.MainFormatClick(Sender: TObject);
OR(MainFormat.ItemIndex=1) //ADFS
OR(MainFormat.ItemIndex=2) //C64
OR(MainFormat.ItemIndex=5) //CFS
+ OR(MainFormat.ItemIndex=6) //Spark
OR(MainFormat.ItemIndex=7) //AFS
OR(MainFormat.ItemIndex=8);//DOS
end;
diff --git a/LazarusSource/Spark.pas b/LazarusSource/Spark.pas
index c1ef1f4..1d9d34d 100644
--- a/LazarusSource/Spark.pas
+++ b/LazarusSource/Spark.pas
@@ -1,7 +1,7 @@
unit Spark;
{
-TSpark class V1.04
+TSpark class V1.05
Decompress a Zip or PackDir archive, preserving the extra RISC OS information.
Thank you to David Pilling for his assistance.
@@ -27,29 +27,29 @@
interface
-uses Classes,SysUtils,Zipper,ExtCtrls;
+uses Classes,SysUtils,Zipper,ExtCtrls,DateUtils,Math;
{$M+}
type
TDynByteArray = array of Byte;
+ TFileEntry = record
+ LoadAddr, //Load Address
+ ExecAddr, //Execution Address
+ Length, //Uncompressed size
+ Size, //Compressed size
+ NumEntries, //Number of directory entries
+ Attributes, //File attributes (hex)
+ DataOffset : Cardinal; //Where to find the data
+ Filename, //RISC OS filename
+ Parent, //RISC OS parent
+ ArchiveName: String; //Name (and path) in archive
+ Directory : Boolean; //Is it a directory?
+ end;
type
TSpark = Class
private
type
- TFileEntry = record
- LoadAddr, //Load Address
- ExecAddr, //Execution Address
- Length, //Uncompressed size
- Size, //Compressed size
- NumEntries, //Number of directory entries
- Attributes, //File attributes (hex)
- DataOffset : Cardinal; //Where to find the data
- Filename, //RISC OS filename
- Parent, //RISC OS parent
- ArchiveName: String; //Name (and path) in archive
- Directory : Boolean; //Is it a directory?
- end;
TFileList = array of TFileEntry;
TProgressProc = procedure(Sender: TObject;const Fupdate: Double) of Object;
private
@@ -64,6 +64,7 @@ TFileEntry = record
FTimeOut : Cardinal; //Length of time out, in seconds
FMaxDirEnt : Integer; //Maximum size of directory
FBitLength : Integer; //Bit length (for LZW)
+ //Private methods
function ExtractFiles: TFileList;
function ExtractSparkFiles: TFileList;
function ExtractPackFiles: TFileList;
@@ -73,12 +74,35 @@ TFileEntry = record
AItem: TFullZipFileEntry);
function GetUncompressedSize: Cardinal;
function IsItSpark: Boolean;
+ function FindEoCL(var CL: Cardinal): Cardinal;
+ procedure UpdateCL(CL,EoCL: Cardinal);
function ExtractFileDataFromSpark(index: Integer):TDynByteArray;
function ExtractFileDataFromPack(index: Integer):TDynByteArray;
+ procedure SaveData;
+ function FindEntry(path: String;matchpath: Boolean;var CLptr: Cardinal;
+ var dataptr: Cardinal): Boolean;
+ function RenameTheFile(oldpath, newpath: String): Boolean;
+ function DeleteTheFile(filename: String):Boolean;
+ //Private constants
+ const
+ NewAtts: array[0..7] of Char = ('R','W','L','D','r','w',' ',' ');
published
- constructor Create(filename: String);
+ //Published methods
+ constructor Create(filename: String;blank: Boolean=false);
constructor Create(stream: TStream); overload;
function ExtractFileData(Index: Integer):TDynByteArray;
+ procedure WriteFile(var filetozip: TFileEntry;var buffer: TDynByteArray);
+ procedure CreateDirectory(path: String);
+ function RenameFile(oldpath, newpath: String): Boolean;
+ function UpdateLoadExecAddress(path: String;load, exec: Cardinal): Boolean;
+ function UpdateAttributes(path: String; attributes: Word): Boolean;
+ function DeleteFile(filename: String):Boolean;
+ procedure RISCOSFilename(path: String;addroot: Boolean;var filename: String;
+ var parent: String);
+ procedure SwapDirSep(var path: String);
+ function ConvertAttribute(attr: Byte): String;
+ function ConvertAttribute(attr: String): Byte; overload;
+ //Published properties
property IsSpark: Boolean read IsItSpark;
property FileList: TFileList read FFileList;
property UncompressedSize: Cardinal read GetUncompressedSize;
@@ -92,59 +116,7 @@ TFileEntry = record
implementation
-{-------------------------------------------------------------------------------
-Create the instance
--------------------------------------------------------------------------------}
-constructor TSpark.Create(filename: String);
-var
- F: TFileStream;
-begin
- //Set the filename
- ZipFilename:=filename;
- //Create a stream
- F:=TFileStream.Create(ZipFilename,fmOpenRead or fmShareDenyNone);
- //Read it using the overloaded constructor
- Create(F);
- //And free it up
- F.Free;
-end;
-constructor TSpark.Create(Stream: TStream);
-begin
- inherited Create;
- //Initialise the variables
- Fbuffer :=nil;
- Fversion :='1.04';
- FTimeOut :=30;
- FIsSpark :=False;
- FIsPack :=False;
- //Read the zip file into memory from the stream
- SetLength(Fbuffer,Stream.Size);
- Stream.Position:=0;
- Stream.Read(Fbuffer[0],Stream.Size);
- //Check it is a !Spark file
- if (Fbuffer[0]=$50)
- and(Fbuffer[1]=$4B)
- and(Fbuffer[2]=$03)
- and(Fbuffer[3]=$04)then FIsSpark:=True;
- //Is it a !PackDir file
- if (Fbuffer[0]=$50)
- and(Fbuffer[1]=$41)
- and(Fbuffer[2]=$43)
- and(Fbuffer[3]=$4B)
- and(Fbuffer[4]=$00)then FIsPack:=True;
- //It is neither
- if(not FIsSpark)and(not FIsPack)then SetLength(Fbuffer,0);//Clear the buffer
- //The extract the file details - the function will select the appropriate algorithm
- FFileList:=ExtractFiles;
-end;
-
-{-------------------------------------------------------------------------------
-Destroy (free) the instance
--------------------------------------------------------------------------------}
-destructor TSpark.Destroy;
-begin
- inherited;
-end;
+//++++++++++++++++++ Private methods +++++++++++++++++++++++++++++++++++++++++++
{-------------------------------------------------------------------------------
Extract the list of files
@@ -164,44 +136,26 @@ function TSpark.ExtractSparkFiles: TFileList;
ptr,
EoCL,
CL,
-// fileoffset,
ctr,
fnc : Cardinal;
- c : Char;
fnL,
exL,
cmL : LongWord;
fn,
zipfn : String;
+ exists : Integer;
+ temp : TFileEntry;
begin
Result:=nil;
if FIsSpark then
begin
FBitLength:=0; //Not used with !Spark
//Find the 'End of central library'
- ptr :=Length(Fbuffer)-4; //Start at the end
- EoCL:=0;
- //And decrease the pointer until we find the signature, or get to the start of file
- repeat
- dec(ptr);
- until((Fbuffer[ptr] =$50)
- and(Fbuffer[ptr+1]=$4B)
- and(Fbuffer[ptr+2]=$05)
- and(Fbuffer[ptr+3]=$06))
- or(ptr=0);
- //Signature found, so mark it
- if (Fbuffer[ptr]=$50)
- and(Fbuffer[ptr+1]=$4B)
- and(Fbuffer[ptr+2]=$05)
- and(Fbuffer[ptr+3]=$06)then EoCL:=ptr;
+ CL:=0;
+ EoCL:=FindEoCL(CL);
//Only continue of we have a marker
if EoCL<>0 then
begin
- //Now we can get the start of the central library
- CL:=Fbuffer[EoCL+$10]
- +Fbuffer[EoCL+$11]<<8
- +Fbuffer[EoCL+$12]<<16
- +Fbuffer[EoCL+$13]<<24;
//Set up the result to the number of entries
SetLength(Result,Fbuffer[EoCL+$0A]+Fbuffer[EoCL+$0B]<<8);
//Now iterate through to find each entry
@@ -209,6 +163,9 @@ function TSpark.ExtractSparkFiles: TFileList;
ctr:=0; //File count
while ptr+CLLength(Result) then SetLength(Result,ctr+1);
+ //Read in the values
fnL :=Fbuffer[CL+ptr+$1C] //Length of filename
+Fbuffer[CL+ptr+$1D]<<8;
exL :=Fbuffer[CL+ptr+$1E] //Length of extra field
@@ -227,34 +184,20 @@ function TSpark.ExtractSparkFiles: TFileList;
+Fbuffer[Result[ctr].DataOffset+$17]<< 8
+Fbuffer[Result[ctr].DataOffset+$18]<<16
+Fbuffer[Result[ctr].DataOffset+$19]<<24;
- //Get the RISC OS filename, and internal zip name
- fn :='';
+ //Get the internal zip name
zipfn:='';
- for fnc:=0 to fnL-1 do
- begin
- c:=chr(Fbuffer[CL+ptr+$2E+fnc]);
- zipfn:=zipfn+c;
- if c='/' then c:='.' //Swap the zip directory separator for a RISC OS one
- else if c='.' then c:=','; //And remove the period for a comma (usually filetype)
- fn:=fn+c;
- end;
+ for fnc:=0 to fnL-1 do zipfn:=zipfn+chr(Fbuffer[CL+ptr+$2E+fnc]);
//Determine if this is a directory or not
- if fn[Length(fn)]='.' then
+ if zipfn[Length(zipfn)]='/' then
begin
Result[ctr].Directory:=True;
- fn:=LeftStr(fn,Length(fn)-1);
+ zipfn:=LeftStr(zipfn,Length(zipfn)-1);
end else Result[ctr].Directory:=False;
- //And remove the parent, if any
- Result[ctr].Parent:='';
- if Pos('.',fn)>0 then
- begin
- fnc:=Length(fn);
- while fn[fnc]<>'.' do dec(fnc);
- Result[ctr].Parent:=LeftStr(fn,fnc-1);
- fn:=Copy(fn,fnc+1);
- end;
- //Set the filename to the RISC OS filename
- Result[ctr].Filename:=fn;
+ fn:=zipfn;
+ //Swap the directory separators around
+ SwapDirSep(fn);
+ //Split the filename and parent (without the root)
+ RISCOSFilename(zipfn,false,Result[ctr].Filename,Result[ctr].Parent);
//And remember the zip filename, for later extraction
Result[ctr].ArchiveName:=zipfn;
//Defaults if not RISC OS
@@ -292,6 +235,66 @@ function TSpark.ExtractSparkFiles: TFileList;
inc(ctr);
end;
end;
+ //Go through each entry and make sure that the parent exists for each one
+ ctr:=0;
+ while ctr'')and(Result[ctr].Parent<>'$')then
+ begin
+ for ptr:=0 to Length(Result)-1 do
+ if Result[ptr].Directory then //If it is a directory
+ if((Result[ctr].Parent=Result[ptr].Parent+'.'+Result[ptr].Filename)
+ and(Result[ptr].Parent<>''))
+ or((Result[ctr].Parent=Result[ptr].Filename)
+ and(Result[ptr].Parent=''))then
+ exists:=ptr; //And it exists, then mark it so
+ //If it does exist, but is after the current entry, move it down
+ if exists>ctr then
+ begin
+ //Remember it
+ temp:=Result[exists];
+ //Move from the current one to this one upwards
+ for ptr:=exists downto ctr+1 do Result[ptr]:=Result[ptr-1];
+ //Now put the original in this position
+ Result[ctr]:=temp;
+ //And move ctr up to match
+ inc(ctr);
+ end;
+ //If it doesn't exist, then we create it just before
+ if exists=-1 then
+ begin
+ //Make space
+ SetLength(Result,Length(Result)+1);
+ //Move everything up by one
+ for ptr:=Length(Result)-1 downto ctr+1 do
+ Result[ptr]:=Result[ptr-1];
+ //Take a note
+ fn:=Result[ctr].Parent;
+ //Drop in our 'new' directory
+ Result[ctr].Directory:=True;
+ SwapDirSep(fn);
+ Result[ctr].ArchiveName:=fn+'/';
+ SwapDirSep(fn);
+ Result[ctr].Length:=0;
+ Result[ctr].Size:=0;
+ Result[ctr].NumEntries:=0;
+ Result[ctr].Attributes:=$0F;
+ Result[ctr].DataOffset:=Length(Fbuffer);
+ //And split the filename and parent
+ RISCOSFilename(fn,True,Result[ctr].Filename,Result[ctr].Parent);
+ //Remove the root specifier
+ if Length(Result[ctr].Parent)>2 then
+ Result[ctr].Parent:=Copy(Result[ctr].Parent,3)
+ else
+ Result[ctr].Parent:='';
+ inc(ctr);
+ end;
+ end;
+ inc(ctr);
+ end;
//Analyse the results to count the number of directory entries, per directory
if Length(Result)>0 then
begin
@@ -479,7 +482,7 @@ function TSpark.ExtractFileData(Index: Integer):TDynByteArray;
begin
Result:=nil;
//Make sure we are within limits
- if(index>0)and(index=0)and(index'' then
+ begin
+ tempfile:=TFileStream.Create(ZipFilename,fmOpenWrite or fmShareDenyNone);
+ tempfile.Position:=0;
+ tempfile.Write(Fbuffer[0],Length(Fbuffer));
+ tempfile.Size:=Length(Fbuffer);//Ensures the file is the correct length
+ tempfile.Free;
+ end;
+end;
+
+{-------------------------------------------------------------------------------
+Find a file entry in the central library and main header
+-------------------------------------------------------------------------------}
+function TSpark.FindEntry(path: String;matchpath: Boolean;var CLptr: Cardinal;
+ var dataptr: Cardinal): Boolean;
+var
+ CL,
+ EoCL : Cardinal;
+ temp : String;
+ fnL : Word;
+ index : Integer;
+begin
+ Result:=False;
+ //Get the location of the central library
+ CL:=0;
+ EoCL:=FindEoCL(CL);
+ //If it exists
+ if CL<>EoCL then
+ begin
+ //Find the entry in the Central Library (and, hence, the main header)
+ CLptr:=CL;
+ temp:='';
+ while(temp<>path)and(CLptr'')and(newpath<>'')then
+ begin
+ //We need to make sure that if we are renaming a directory, both have the '/'
+ if(oldpath[Length(oldpath)]='/')and(newpath[Length(newpath)]<>'/')then
+ newpath:=newpath+'/';
+ if(oldpath[Length(oldpath)]<>'/')and(newpath[Length(newpath)]='/')then
+ oldpath:=oldpath+'/';
+ //Make sure that the root is not part of either path
+ if Length(oldpath)>2 then
+ if oldpath[1]='$' then oldpath:=Copy(oldpath,3);
+ if Length(newpath)>2 then
+ if newpath[1]='$' then newpath:=Copy(newpath,3);
+ //Is there any data, both paths have something, and are not the same
+ if(Length(Fbuffer)>0)and(oldpath<>newpath)then
+ begin
+ //Get the Central Library markers
+ EoCL:=FindEoCL(CL);
+ //And the difference between the two names
+ diff:=Length(newpath)-Length(oldpath); //+ve move forwards, -ve move back
+ //Find the entry in the main header
+ ptr:=0;
+ dataptr:=0;
+ if FindEntry(oldpath,true,ptr,dataptr) then
+ begin
+ //Filename length
+ fnL:=Fbuffer[dataptr+$1A]+Fbuffer[dataptr+$1B]<<8;
+ //New name is bigger?
+ if diff>0 then
+ begin
+ //Extend
+ SetLength(Fbuffer,Length(Fbuffer)+diff);
+ for index:=Length(Fbuffer)-1 downto dataptr+$1E+Length(oldpath) do
+ Fbuffer[index]:=Fbuffer[index-diff];
+ end;
+ //New name is shorter?
+ if diff<0 then
+ begin
+ //Contract
+ for index:=dataptr+$1E+Length(oldpath) to Length(Fbuffer)-1 do
+ Fbuffer[index+diff]:=Fbuffer[index];
+ SetLength(Fbuffer,Length(Fbuffer)+diff);
+ end;
+ //Update the filename
+ for index:=1 to Length(newpath) do Fbuffer[dataptr+$1D+index]:=Ord(newpath[index]);
+ //Update the filename length
+ Fbuffer[dataptr+$1A]:=(fnL+diff)mod$100;
+ Fbuffer[dataptr+$1B]:=(fnL+diff)>>8;
+ //Update the central library markers
+ inc(EoCL,diff);
+ inc(CL,diff);
+ UpdateCL(CL,EoCL);
+ //New name is bigger?
+ if diff>0 then
+ begin
+ //Extend
+ SetLength(Fbuffer,Length(Fbuffer)+diff);
+ for index:=Length(Fbuffer)-1 downto ptr+$2E+Length(oldpath) do
+ Fbuffer[index]:=Fbuffer[index-diff];
+ end;
+ //New name is shorter?
+ if diff<0 then
+ begin
+ //Contract
+ for index:=ptr+$2E+Length(oldpath) to Length(Fbuffer)-1 do
+ Fbuffer[index+diff]:=Fbuffer[index];
+ SetLength(Fbuffer,Length(Fbuffer)+diff);
+ end;
+ //Update the filename
+ for index:=1 to Length(newpath) do Fbuffer[ptr+$2D+index]:=Ord(newpath[index]);
+ //Update the filename length
+ Fbuffer[ptr+$1C]:=(fnL+diff)mod$100;
+ Fbuffer[ptr+$1D]:=(fnL+diff)>>8;
+ //Update the data offsets for all the subsequent entries
+ for index:=ptr+1 to EoCL+diff do
+ if (Fbuffer[index]=$50) //Entry signature
+ and(Fbuffer[index+1]=$4B)
+ and(Fbuffer[index+2]=$01)
+ and(Fbuffer[index+3]=$02)then
+ begin
+ hdrpos:=Fbuffer[index+$2A] //Get the current
+ +Fbuffer[index+$2B]<<8
+ +Fbuffer[index+$2C]<<16
+ +Fbuffer[index+$2D]<<24;
+ inc(hdrpos,diff); //Adjust
+ Fbuffer[index+$2A]:=hdrpos mod$100;//Put back
+ Fbuffer[index+$2B]:=(hdrpos>>8) mod$100;
+ Fbuffer[index+$2C]:=(hdrpos>>16) mod$100;
+ Fbuffer[index+$2D]:=(hdrpos>>24) mod$100;
+ end;
+ //Update the central library markers, again
+ inc(EoCL,diff);
+ UpdateCL(CL,EoCL);
+ //Save the data
+ SaveData;
+ Result:=True;
+ //Update the entry in the list of files
+ if Length(FFileList)>0 then
+ for index:=0 to Length(FFileList)-1 do
+ if((Copy(FFileList[index].ArchiveName,1,Length(oldpath))=oldpath)
+ and(oldpath[Length(oldpath)]='/'))
+ or(FFileList[index].ArchiveName=oldpath)then
+ begin
+ //Name as held in the archive
+ if FFileList[index].ArchiveName=oldpath then
+ FFileList[index].ArchiveName:=newpath
+ else
+ FFileList[index].ArchiveName:=newpath
+ +Copy(FFileList[index].ArchiveName,Length(oldpath));
+ RISCOSFilename(newpath,True,FFileList[index].Filename,FFileList[index].Parent);
+ end;
+ //If it is a directory then we recurse until they are all changed
+ if oldpath[Length(oldpath)]='/' then
+ while Result do Result:=RenameFile(oldpath,newpath);
+ end;
+ end;
+ end;
+end;
+
+{-------------------------------------------------------------------------------
+Delete a file/directory (internal)
+-------------------------------------------------------------------------------}
+function TSpark.DeleteTheFile(filename: String):Boolean;
+var
+ EoCL,
+ CL,
+ hdrsize,
+ clsize,
+ fnL,
+ exL,
+ cmL,
+ ptr,
+ CLptr,
+ data,
+ index : Cardinal;
+ match : Boolean;
+begin
+ //Default return - a false result does not mean it is a fail.
+ Result:=False;
+ //Remove the root, if present
+ if Length(filename)>2 then if filename[1]='$' then filename:=Copy(filename,3);
+ //Set up our variables
+ CL:=0;
+ ptr:=0;
+ CLptr:=0;
+ //Find the Central library
+ EoCL:=FindEoCL(CL);
+ if EoCL<>CL then
+ begin
+ //Find the entry in both the CL and header
+ if filename[Length(filename)]='/' then match:=True else match:=False;
+ if FindEntry(filename,match,CLptr,ptr) then
+ begin
+ //Move the data following the entry in the header down
+ fnL:=Fbuffer[ptr+$1A]+Fbuffer[ptr+$1B]<<8; //Filename length
+ exL:=Fbuffer[ptr+$1C]+Fbuffer[ptr+$1D]<<8; //Extra length
+ hdrsize:=$1E+fnL+exL
+ +Fbuffer[ptr+$12]
+ +Fbuffer[ptr+$13]<<8
+ +Fbuffer[ptr+$14]<<16
+ +Fbuffer[ptr+$15]<<24; //Total size of entry in the header
+ for index:=ptr+hdrsize to Length(Fbuffer)-1 do
+ Fbuffer[index-hdrsize]:=Fbuffer[index];
+ //Recalculate, and update, the EoCL
+ dec(CL,hdrsize);
+ dec(EoCL,hdrsize);
+ UpdateCL(CL,EoCL);
+ SetLength(Fbuffer,Length(Fbuffer)-hdrsize);
+ //Move the data following the entry in the CL down, updating the data pointers
+ dec(CLptr,hdrsize);
+ cmL:=Fbuffer[CLptr+$20]+Fbuffer[CLptr+$21]<<8;
+ CLsize:=$2E+fnL+exL+cmL;
+ for index:=CLptr+CLsize to Length(Fbuffer)-1 do
+ begin
+ //Update the data pointers for the other entries
+ if (Fbuffer[index]=$50)
+ and(Fbuffer[index+1]=$4B)
+ and(Fbuffer[index+2]=$01)
+ and(Fbuffer[index+3]=$02)then
+ begin
+ //Read in
+ data:=Fbuffer[index+$2A]
+ +Fbuffer[index+$2B]<<8
+ +Fbuffer[index+$2C]<<16
+ +Fbuffer[index+$2D]<<24;
+ //Adjust
+ dec(data,hdrsize);
+ //Put back
+ Fbuffer[index+$2A]:=data mod$100;
+ Fbuffer[index+$2B]:=(data>>8)mod$100;
+ Fbuffer[index+$2C]:=(data>>16)mod$100;
+ Fbuffer[index+$2D]:=(data>>24)mod$100;
+ end;
+ //And move the data down
+ Fbuffer[index-CLsize]:=Fbuffer[index];
+ end;
+ //Recalculate, and update, the EoCL and CL size
+ dec(EoCL,CLsize);
+ UpdateCL(CL,EoCL);
+ SetLength(Fbuffer,Length(Fbuffer)-CLsize);
+ //Recalculate, and update, the number of entries in the CL
+ data:=0;
+ for index:=CL to EoCL do
+ if (Fbuffer[index]=$50)
+ and(Fbuffer[index+1]=$4B)
+ and(Fbuffer[index+2]=$01)
+ and(Fbuffer[index+3]=$02)then inc(data);
+ //Number of entries
+ Fbuffer[EoCL+$8]:=data mod$100;
+ Fbuffer[EoCL+$9]:=(data>>8)mod$100;
+ Fbuffer[EoCL+$A]:=data mod $100;
+ Fbuffer[EoCL+$B]:=(data>>8)mod$100;
+ //Save the data
+ SaveData;
+ //Remove it from the list of files
+ if Length(FFileList)>0 then
+ begin
+ for index:=0 to Length(FFileList)-1 do
+ if FFileList[index].ArchiveName=filename then
+ ptr:=index;
+ if ptr4 then
+ begin
+ //Start at the end
+ ptr :=Length(Fbuffer)-4;
+ //And decrease the pointer until we find the signature, or get to the start of file
+ repeat
+ dec(ptr);
+ until((Fbuffer[ptr] =$50)
+ and(Fbuffer[ptr+1]=$4B)
+ and(Fbuffer[ptr+2]=$05)
+ and(Fbuffer[ptr+3]=$06))
+ or(ptr=0);
+ //Signature found, so mark it
+ if (Fbuffer[ptr]=$50)
+ and(Fbuffer[ptr+1]=$4B)
+ and(Fbuffer[ptr+2]=$05)
+ and(Fbuffer[ptr+3]=$06)then Result:=ptr;
+ //Return the central library beginning too
+ if Result>0 then
+ CL:=Fbuffer[Result+$10]
+ +Fbuffer[Result+$11]<<8
+ +Fbuffer[Result+$12]<<16
+ +Fbuffer[Result+$13]<<24;
+ end;
+end;
+
+{-------------------------------------------------------------------------------
+Update the Central Library pointer, with EoCL having already been moved
+-------------------------------------------------------------------------------}
+procedure TSpark.UpdateCL(CL,EoCL: Cardinal);
+var
+ ptr : Cardinal;
+begin
+ if(EoCL<>0)and(CL<>0)then
+ begin
+ //Work out the length
+ ptr:=EoCL-CL;
+ //And write it
+ Fbuffer[EoCL+$0C]:=ptr mod $100;
+ Fbuffer[EoCL+$0D]:=(ptr>>8)mod$100;
+ Fbuffer[EoCL+$0E]:=(ptr>>16)mod$100;
+ Fbuffer[EoCL+$0F]:=(ptr>>24)mod$100;
+ //And the location of the central library
+ Fbuffer[EoCL+$10]:=CL mod$100;
+ Fbuffer[EoCL+$11]:=(CL>>8)mod$100;
+ Fbuffer[EoCL+$12]:=(CL>>16)mod$100;
+ Fbuffer[EoCL+$13]:=(CL>>24)mod$100;
+ end;
+end;
+
+//++++++++++++++++++ Published Methods +++++++++++++++++++++++++++++++++++++++++
+
+{-------------------------------------------------------------------------------
+Create the instance
+-------------------------------------------------------------------------------}
+constructor TSpark.Create(filename: String;blank: Boolean=false);
+var
+ F: TFileStream;
+begin
+ //Set the filename
+ ZipFilename:=filename;
+ //Create a blank file
+ if blank then
+ begin
+ F:=TFileStream.Create(ZipFilename,fmCreate or fmShareDenyNone);
+ F.Free;
+ end;
+ //Create a stream
+ F:=TFileStream.Create(ZipFilename,fmOpenRead or fmShareDenyNone);
+ //Read it using the overloaded constructor
+ Create(F);
+ //And free it up
+ F.Free;
+end;
+constructor TSpark.Create(Stream: TStream);
+begin
+ inherited Create;
+ //Initialise the variables
+ Fbuffer :=nil;
+ Fversion :='1.05';
+ FTimeOut :=30;
+ FIsSpark :=False;
+ FIsPack :=False;
+ //Read the zip file into memory from the stream
+ SetLength(Fbuffer,Stream.Size);
+ Stream.Position:=0;
+ Stream.Read(Fbuffer[0],Stream.Size);
+ if Length(Fbuffer)<5 then exit;
+ //Check it is a !Spark file (or just a zip file)
+ if (Fbuffer[0]=$50)
+ and(Fbuffer[1]=$4B)
+ and(Fbuffer[2]=$03)
+ and(Fbuffer[3]=$04)then FIsSpark:=True;
+ //Is it a !PackDir file
+ if (Fbuffer[0]=$50)
+ and(Fbuffer[1]=$41)
+ and(Fbuffer[2]=$43)
+ and(Fbuffer[3]=$4B)
+ and(Fbuffer[4]=$00)then FIsPack:=True;
+ //It is neither
+ if(not FIsSpark)and(not FIsPack)then SetLength(Fbuffer,0);//Clear the buffer
+ //The extract the file details - the function will select the appropriate algorithm
+ FFileList:=ExtractFiles;
+end;
+
+{-------------------------------------------------------------------------------
+Destroy (free) the instance
+-------------------------------------------------------------------------------}
+destructor TSpark.Destroy;
+begin
+ inherited;
+end;
+
+{-------------------------------------------------------------------------------
+Write a file to the archive
+-------------------------------------------------------------------------------}
+procedure TSpark.WriteFile(var filetozip: TFileEntry;var buffer: TDynByteArray);
+var
+ tempname : String;
+ tempfile : TFileStream;
+ zipfile : TZipper;
+ EoCL,
+ CL,ptr,
+ dataptr : Cardinal;
+ fnL,exL : Word;
+ index,
+ adjust : Integer;
+const newExL = $18;
+begin
+ if FIsPack then exit;
+ if filetozip.Directory then CreateDirectory(filetozip.ArchiveName)
+ else
+ begin
+ //Zipper will only zip up existing files, so we'll need to save the data to a
+ //temporary file first, then zip that.
+ tempname:=GetTempFileName;//Get a temporary name
+ tempfile:=TFileStream.Create(tempname,fmCreate);//Create the file
+ tempfile.Position:=0;
+ tempfile.Write(buffer[0],Length(buffer)); //Write the file data to it
+ tempfile.Free; //Close the file
+ //Now we can open the zipfile
+ zipfile:=TZipper.Create;
+ try
+ zipfile.Filename:=ZipFilename; //Set the zipfile name
+ zipfile.Entries.AddFileEntry(tempname,filetozip.ArchiveName); //Add the file
+ zipfile.ZipAllFiles; //Then zip all the files
+ finally
+ zipfile.Free; //And close it
+ end;
+ //Finally, delete the temporary file
+ DeleteFile(tempname);
+ //Then we will need to load the zip file in and change the values and add to
+ //the library at the end.
+ tempfile:=TFileStream.Create(ZipFilename,fmOpenRead or fmShareDenyNone);
+ SetLength(Fbuffer,tempfile.Size);
+ tempfile.Position:=0;
+ tempfile.Read(Fbuffer[0],tempfile.Size);
+ tempfile.Free;
+ CL:=0;
+ EoCL:=FindEoCL(CL); //Get the end of central library marker
+ if EoCL<>0 then
+ begin
+ //Find the entry for this file
+ if FindEntry(filetozip.ArchiveName,false,ptr,dataptr) then
+ begin
+ //Get the variable length fields lengths
+ fnL:=Fbuffer[ptr+$1C]+Fbuffer[ptr+$1D]<<8;
+ exL:=Fbuffer[ptr+$1E]+Fbuffer[ptr+$1F]<<8;
+ //We need to add up to 24 bytes, so everything from the next entry needs to shift along
+ adjust:=0;
+ if exL0 then
+ begin
+ SetLength(Fbuffer,Length(Fbuffer)+adjust);
+ for index:=Length(Fbuffer)-1 downto ptr+$2E+fnL+exL+adjust do
+ Fbuffer[index]:=Fbuffer[index-adjust];
+ end;
+ //Update the OS type to RISC OS
+ Fbuffer[ptr+$05]:=13;
+ //Update the extra field length
+ Fbuffer[ptr+$1E]:=(exL+adjust)mod$100;
+ Fbuffer[ptr+$1F]:=(exL+adjust)>>8;
+ //Fill in the extra field
+ Fbuffer[ptr+$2E+fnL]:=$41;//A
+ Fbuffer[ptr+$2F+fnL]:=$43;//C
+ //Length of extra -4
+ Fbuffer[ptr+$30+fnL]:=((exL+adjust)-4)mod$100;
+ Fbuffer[ptr+$31+fnL]:=((exL+adjust)-4)>>8;
+ Fbuffer[ptr+$32+fnL]:=$41;//A
+ Fbuffer[ptr+$33+fnL]:=$52;//R
+ Fbuffer[ptr+$34+fnL]:=$43;//C
+ Fbuffer[ptr+$35+fnL]:=$30;//0
+ //Load address
+ Fbuffer[ptr+$36+fnL]:=filetozip.LoadAddr mod $100;
+ Fbuffer[ptr+$37+fnL]:=(filetozip.LoadAddr>>8)mod $100;
+ Fbuffer[ptr+$38+fnL]:=(filetozip.LoadAddr>>16)mod $100;
+ Fbuffer[ptr+$39+fnL]:=(filetozip.LoadAddr>>24)mod $100;
+ //Exec address
+ Fbuffer[ptr+$3A+fnL]:=filetozip.ExecAddr mod $100;
+ Fbuffer[ptr+$3B+fnL]:=(filetozip.ExecAddr>>8)mod $100;
+ Fbuffer[ptr+$3C+fnL]:=(filetozip.ExecAddr>>16)mod $100;
+ Fbuffer[ptr+$3D+fnL]:=(filetozip.ExecAddr>>24)mod $100;
+ //Attributes
+ Fbuffer[ptr+$3E+fnL]:=filetozip.Attributes;
+ //Blank off the rest
+ if adjust>0 then
+ begin
+ for index:=17 to adjust-1 do Fbuffer[ptr+fnL+index+$2E]:=0;
+ //Now we need to replicate this extra field into the main header
+ SetLength(Fbuffer,Length(Fbuffer)+adjust);//First, extend again
+ for index:=Length(Fbuffer)-1 downto dataptr+$1E+fnL+exL+adjust do
+ Fbuffer[index]:=Fbuffer[index-adjust];
+ end;
+ //Now just replicate from above
+ inc(ptr,adjust);//Don't forget this has moved too
+ for index:=0 to newExL-1 do
+ Fbuffer[dataptr+$1E+fnL+index]:=Fbuffer[ptr+$2E+fnL+index];
+ //And update the field in the main header
+ Fbuffer[dataptr+$1C]:=(exL+adjust)mod$100;
+ Fbuffer[dataptr+$1D]:=(exL+adjust)>>8;
+ //Get the compressed size of the file
+ filetozip.Length:=Length(buffer);
+ filetozip.Size:=Fbuffer[dataptr+$12]
+ +Fbuffer[dataptr+$13]<<8
+ +Fbuffer[dataptr+$14]<<16
+ +Fbuffer[dataptr+$15]<<24;
+ //And the data offset
+ filetozip.DataOffset:=dataptr;
+ //Update the End of Central Library
+ inc(EoCL,adjust*2);
+ //Update Offset of CL
+ inc(CL,adjust);
+ UpdateCL(CL,EoCL); //Write both fields
+ //Save it back again
+ SaveData;
+ //And now add it to the overall list
+ SetLength(FFileList,Length(FFileList)+1);
+ FFileList[Length(FFileList)-1]:=filetozip;
+ //Set the spark flag
+ FIsSpark:=True;
+ end;
+ end;
+ end;
+end;
+{-------------------------------------------------------------------------------
+Create an empty directory
+-------------------------------------------------------------------------------}
+procedure TSpark.CreateDirectory(path: String);
+var
+ index,
+ ctr : Integer;
+ buffer : TDynByteArray;
+ EoCL,CL,
+ offset : Cardinal;
+ datetime : QWord;
+ year,
+ month,
+ day,
+ hour,
+ minute,
+ second,
+ ms : Word;
+ filetozip: TFileEntry;
+const
+ headersig: array[0..3] of Byte = ($50,$4B,$03,$04);
+ clsig : array[0..3] of Byte = ($50,$4B,$01,$02);
+ eoclsig : array[0..3] of Byte = ($50,$4B,$05,$06);
+begin
+ if FIsPack then exit;
+ //Make sure that the path is not empty
+ if path<>'' then
+ begin
+ //Ensure it ends with a directory specifier
+ if path[Length(path)]<>'/' then path:=path+'/';
+ //And does not start with the root
+ if Length(path)>2 then if path[1]='$' then path:=Copy(path,3);
+ //Set up the buffer for the header entry
+ SetLength(buffer,Length(path)+$36);
+ for index:=0 to 3 do buffer[index]:=headersig[index];
+ //Version
+ buffer[4]:=$0A;
+ //Blank the rest
+ for index:=5 to $1D do buffer[index]:=$00;
+ //Modification time $0A/$0B
+ DecodeDateTime(Now,year,month,day,hour,minute,second,ms);
+ datetime:=(hour<<$B)OR(minute<<5)OR(second div 2);
+ buffer[$0A]:=datetime mod $100;
+ buffer[$0B]:=datetime>>8;
+ //Modification date $0C/$0D
+ datetime:=((year-1980)<<9)OR(month<<5)OR day;
+ buffer[$0C]:=datetime mod $100;
+ buffer[$0D]:=datetime>>8;
+ //Filename length
+ buffer[$1A]:=Length(path)mod$100;
+ buffer[$1B]:=Length(path)>>8;
+ //Extra length
+ buffer[$1C]:=$18;
+ //Filename
+ for index:=1 to Length(path) do buffer[$1D+index]:=Ord(path[index]);
+ //Fill in the extra field
+ buffer[$1E+Length(path)]:=$41;//A
+ buffer[$1F+Length(path)]:=$43;//C
+ //Length of extra -4
+ buffer[$20+Length(path)]:=$14;
+ buffer[$21+Length(path)]:=$00;
+ buffer[$22+Length(path)]:=$41;//A
+ buffer[$23+Length(path)]:=$52;//R
+ buffer[$24+Length(path)]:=$43;//C
+ buffer[$25+Length(path)]:=$30;//0
+ //Load address
+ datetime:=((Floor(Now)-2)*8640000)+Floor((Now-Floor(Now))*8640000);
+ buffer[$26+Length(path)]:=(datetime>>32)mod$100;//last byte of the time/date
+ buffer[$27+Length(path)]:=$FD;
+ buffer[$28+Length(path)]:=$FF;
+ buffer[$29+Length(path)]:=$FF;
+ //Exec address
+ buffer[$2A+Length(path)]:= datetime mod $100; //RISC OS time/date
+ buffer[$2B+Length(path)]:=(datetime>>8)mod $100;
+ buffer[$2C+Length(path)]:=(datetime>>16)mod $100;
+ buffer[$2D+Length(path)]:=(datetime>>24)mod $100;
+ //Attributes
+ buffer[$2E+Length(path)]:=$0F;
+ //Blank off the rest
+ for index:=0 to 7 do buffer[$2F+Length(path)+index]:=$00;
+ //Find the end of the list
+ CL:=0;
+ EoCL:=FindEoCL(CL);
+ //Increase the archive size
+ SetLength(Fbuffer,Length(Fbuffer)+Length(buffer));
+ //Move the data up
+ if EoCL>0 then
+ for index:=Length(Fbuffer)-1 downto CL do
+ Fbuffer[index]:=Fbuffer[index-Length(buffer)];
+ //There is no EoCL, so we need to create one
+ if EoCL=0 then
+ begin
+ EoCL:=Length(Fbuffer);
+ SetLength(Fbuffer,Length(Fbuffer)+$16);
+ //Write the signature
+ for index:=0 to 3 do Fbuffer[EoCL+index]:=eoclsig[index];
+ //Reset the EoCL back to 0, for now
+ EoCL:=0;
+ end;
+ //Then insert where the CL was
+ for index:=0 to Length(buffer)-1 do Fbuffer[CL+index]:=buffer[index];
+ //Remember where we put it
+ offset:=CL;
+ //Adjust the CL and EoCL
+ inc(EoCL,Length(buffer));
+ inc(CL,Length(buffer));
+ //Update the CL location
+ UpdateCL(CL,EoCL);
+ //Now add the entry to the central database
+ SetLength(buffer,Length(buffer)+$10);
+ //Now move data around - start at the end so we don't overwrite anything
+ for index:=Length(buffer)-1 downto $2E do buffer[index]:=buffer[index-$10];
+ //Middle part
+ for index:=$1F downto $06 do buffer[index]:=buffer[index-2];
+ //CL signature
+ for index:=0 to 3 do buffer[index]:=clsig[index];
+ //Version
+ buffer[$04]:=$14;
+ //OS
+ buffer[$05]:=$0D;
+ //Zero out the non-required fields
+ for index:=$20 to $29 do buffer[index]:=$00;
+ //Offset of main entry
+ buffer[$2A]:=offset mod$100;
+ buffer[$2B]:=(offset>>8)mod$100;
+ buffer[$2C]:=(offset>>16)mod$100;
+ buffer[$2D]:=(offset>>24)mod$100;
+ //Now to insert this into the Central Library
+ //Increase the archive size
+ SetLength(Fbuffer,Length(Fbuffer)+Length(buffer));
+ //Move the data up
+ for index:=Length(Fbuffer)-1 downto EoCL do
+ Fbuffer[index]:=Fbuffer[index-Length(buffer)];
+ //Then insert where the EoCL was
+ for index:=0 to Length(buffer)-1 do Fbuffer[EoCL+index]:=buffer[index];
+ //And update again
+ inc(EoCL,Length(buffer));
+ UpdateCL(CL,EoCL);
+ //Count the number of entries
+ ctr:=0;
+ for index:=CL to EoCL do
+ if (Fbuffer[index]=$50)
+ and(Fbuffer[index+1]=$4B)
+ and(Fbuffer[index+2]=$01)
+ and(Fbuffer[index+3]=$02)then inc(ctr);
+ //Number of entries
+ Fbuffer[EoCL+$8]:=ctr mod$100;
+ Fbuffer[EoCL+$9]:=(ctr>>8)mod$100;
+ Fbuffer[EoCL+$A]:=ctr mod $100;
+ Fbuffer[EoCL+$B]:=(ctr>>8)mod$100;
+ //Save it back again
+ SaveData;
+ //Populate the entry
+ filetozip.ArchiveName:=path;
+ filetozip.Attributes:=$0F;
+ filetozip.LoadAddr:=((datetime>>32)mod$100)+$FFFFFD00;
+ filetozip.ExecAddr:=datetime and $FFFFFFFF;
+ filetozip.DataOffset:=offset;
+ filetozip.Directory:=True;
+ filetozip.Size:=0;
+ filetozip.Length:=0;
+ filetozip.NumEntries:=0;
+ //Sort the filename and path out for RISC OS
+ RISCOSFilename(path,True,filetozip.Filename,filetozip.Parent);
+ //And now add it to the overall list
+ SetLength(FFileList,Length(FFileList)+1);
+ FFileList[Length(FFileList)-1]:=filetozip;
+ //Set the spark flag
+ FIsSpark:=True;
+ end;
+end;
+
+{-------------------------------------------------------------------------------
+Rename, or move, a file or directory (published)
+-------------------------------------------------------------------------------}
+function TSpark.RenameFile(oldpath, newpath: String): Boolean;
+begin
+ Result:=False;
+ //We do this to eliminate any false positives. Some will still get through
+ if(oldpath<>'')and(newpath<>'')and(oldpath<>newpath)and(Length(Fbuffer)>0)then
+ begin
+ //Fire off the function
+ Result:=RenameTheFile(oldpath,newpath);
+ //A directory will return a false result, whatever
+ if oldpath[Length(oldpath)]='/' then Result:=True;
+ end;
+end;
+
+{-------------------------------------------------------------------------------
+Delete a file/directory (published)
+-------------------------------------------------------------------------------}
+function TSpark.DeleteFile(filename: String):Boolean;
+begin
+ Result:=False;
+ //We do this to eliminate any false positives. Some will still get through
+ if(filename<>'')and(Length(Fbuffer)>0)then
+ begin
+ //Fire off the function
+ Result:=DeleteTheFile(filename);
+ //A directory will return a false result, whatever
+ if filename[Length(filename)]='/' then Result:=True;
+ end;
+end;
+
+{-------------------------------------------------------------------------------
+Update the load and execution address
+-------------------------------------------------------------------------------}
+function TSpark.UpdateLoadExecAddress(path: String;load, exec: Cardinal): Boolean;
+var
+ ptr,
+ dataptr : Cardinal;
+ fnL : Word;
+ index : Integer;
+begin
+ Result:=False;
+ if FIsPack then exit;
+ //No blanks
+ if path<>'' then
+ begin
+ //Remove the root, if present
+ if Length(path)>2 then if path[1]='$' then path:=Copy(path,3);
+ //Set up our variables
+ ptr:=0;
+ dataptr:=0;
+ fnL:=Length(path);
+ //Find the entry
+ if FindEntry(path,False,ptr,dataptr) then
+ begin
+ //Update the entries (CL)
+ Fbuffer[ptr+fnL+$36]:=load mod$100;
+ Fbuffer[ptr+fnL+$37]:=(load>>8)mod$100;
+ Fbuffer[ptr+fnL+$38]:=(load>>16)mod$100;
+ Fbuffer[ptr+fnL+$39]:=(load>>24)mod$100;
+ Fbuffer[ptr+fnL+$3A]:=exec mod$100;
+ Fbuffer[ptr+fnL+$3B]:=(exec>>8)mod$100;
+ Fbuffer[ptr+fnL+$3C]:=(exec>>16)mod$100;
+ Fbuffer[ptr+fnL+$3D]:=(exec>>24)mod$100;
+ //Update the entries (header)
+ Fbuffer[dataptr+fnL+$26]:=load mod$100;
+ Fbuffer[dataptr+fnL+$27]:=(load>>8)mod$100;
+ Fbuffer[dataptr+fnL+$28]:=(load>>16)mod$100;
+ Fbuffer[dataptr+fnL+$29]:=(load>>24)mod$100;
+ Fbuffer[dataptr+fnL+$2A]:=exec mod$100;
+ Fbuffer[dataptr+fnL+$2B]:=(exec>>8)mod$100;
+ Fbuffer[dataptr+fnL+$2C]:=(exec>>16)mod$100;
+ Fbuffer[dataptr+fnL+$2D]:=(exec>>24)mod$100;
+ //Save the data
+ SaveData;
+ //Update the entry in the list of files
+ if Length(FFileList)>0 then
+ for index:=0 to Length(FFileList)-1 do
+ if FFileList[index].ArchiveName=path then
+ begin
+ FFileList[index].LoadAddr:=load;
+ FFileList[index].ExecAddr:=exec;
+ end;
+ Result:=True;
+ end;
+ end;
+end;
+
+{-------------------------------------------------------------------------------
+Update a file's attributes
+-------------------------------------------------------------------------------}
+function TSpark.UpdateAttributes(path: String; attributes: Word): Boolean;
+var
+ ptr,
+ dataptr : Cardinal;
+ fnL : Word;
+ index : Integer;
+begin
+ Result:=False;
+ if FIsPack then exit;
+ //No blanks
+ if path<>'' then
+ begin
+ //Remove the root if present
+ if Length(path)>2 then if path[1]='$' then path:=Copy(path,3);
+ //Set up our variables
+ ptr:=0;
+ dataptr:=0;
+ fnL:=Length(path);
+ //Find the entry
+ if FindEntry(path,False,ptr,dataptr) then
+ begin
+ //Update the entries (CL)
+ Fbuffer[ptr+fnL+$3E]:=attributes mod$100;
+ //Update the entries (header)
+ Fbuffer[dataptr+fnL+$2E]:=attributes mod$100;
+ //Save the data
+ SaveData;
+ //Update the entry in the list of files
+ if Length(FFileList)>0 then
+ for index:=0 to Length(FFileList)-1 do
+ if FFileList[index].ArchiveName=path then
+ FFileList[index].Attributes:=attributes;
+ Result:=True;
+ end;
+ end;
+end;
+
+{-------------------------------------------------------------------------------
+Split a path into parent and filename for RISC OS
+-------------------------------------------------------------------------------}
+procedure TSpark.RISCOSFilename(path: String;addroot: Boolean;
+ var filename: String;var parent: String);
+var
+ index: Integer;
+begin
+ filename:='';
+ if addroot then parent:='$' else parent:='';
+ //Remove any trailing '/'
+ if path[Length(path)]='/' then path:=LeftStr(path,Length(path)-1);
+ //Swap the '/' for '.' and vice versa
+ SwapDirSep(path);
+ //Is there a directory separator?
+ if Pos('.',path)=0 then //No
+ filename:=path
+ else
+ begin
+ //Yes, find the last one
+ index:=Length(path);
+ while(path[index]<>'.')and(index>1)do dec(index);
+ filename:=Copy(path,index+1);
+ if addroot then parent:=parent+'.';
+ parent:=parent+Copy(path,1,index-1);
+ end;
+end;
+
+{-------------------------------------------------------------------------------
+Swap directory separators
+-------------------------------------------------------------------------------}
+procedure TSpark.SwapDirSep(var path: String);
+var
+ index: Integer;
+begin
+ for index:=1 to Length(path) do
+ if path[index]='/' then path[index]:='.'
+ else if path[index]='.' then path[index]:='/';
+end;
+
+{-------------------------------------------------------------------------------
+Convert an attribute byte to a string (ADFS compatible)
+-------------------------------------------------------------------------------}
+function TSpark.ConvertAttribute(attr: Byte): String;
+var
+ cnt : Byte;
+ temp: String;
+begin
+ Result:='';
+ //Collate the attributes
+ for cnt:=0 to 7 do
+ if attr AND(1<0 then
+ for cnt:=Length(Result) downto 1 do
+ temp:=temp+Result[cnt];
+ Result:=temp;
+end;
+
+{-------------------------------------------------------------------------------
+Convert an attribute string to a byte (ADFS compatible)
+-------------------------------------------------------------------------------}
+function TSpark.ConvertAttribute(attr: String): Byte;
+var
+ cnt : Byte;
+begin
+ //Convert the attributes from a string to a byte
+ Result:=0;
+ for cnt:=0 to Length(NewAtts)-1 do
+ if Pos(NewAtts[cnt],attr)>0 then
+ Result:=Result OR 1<
• Acorn ADFS, floppy formats S, M, L, D, E, E+, F, F+ and Hard Discs - Read and Write
• Acorn Cassette Filing System (CFS) - Read and Write
-• !SparkFS and !PackDir - Read only
+• !SparkFS - Read and Write (ZIP Format)
+• !PackDir - Read only
• Acorn File Server Level 2 and 3, including ADFS Hybrids - Read and Write
• DOS Plus (BBC Master 512) - Read and Write.
• MS-DOS FAT12, FAT16 and FAT32 - Read and Write, including Long FileName support
diff --git a/binaries/Linux/Disc Image Manager 32 bit.zip b/binaries/Linux/Disc Image Manager 32 bit.zip
index 2bd1ec9..524b152 100644
Binary files a/binaries/Linux/Disc Image Manager 32 bit.zip and b/binaries/Linux/Disc Image Manager 32 bit.zip differ
diff --git a/binaries/Linux/Disc Image Manager ARM 32 bit.zip b/binaries/Linux/Disc Image Manager ARM 32 bit.zip
index f7cc78c..9490d63 100644
Binary files a/binaries/Linux/Disc Image Manager ARM 32 bit.zip and b/binaries/Linux/Disc Image Manager ARM 32 bit.zip differ
diff --git a/binaries/Linux/Disc Image Manager.zip b/binaries/Linux/Disc Image Manager.zip
index f2a8871..80d2052 100644
Binary files a/binaries/Linux/Disc Image Manager.zip and b/binaries/Linux/Disc Image Manager.zip differ
diff --git a/binaries/Windows/Disc Image Manager 32 bit.zip b/binaries/Windows/Disc Image Manager 32 bit.zip
index 2f96162..41415a8 100644
Binary files a/binaries/Windows/Disc Image Manager 32 bit.zip and b/binaries/Windows/Disc Image Manager 32 bit.zip differ
diff --git a/binaries/Windows/Disc Image Manager.zip b/binaries/Windows/Disc Image Manager.zip
index 5ebd936..ef973c3 100644
Binary files a/binaries/Windows/Disc Image Manager.zip and b/binaries/Windows/Disc Image Manager.zip differ
diff --git a/binaries/macOS/Disc Image Manager 32 bit.dmg b/binaries/macOS/Disc Image Manager 32 bit.dmg
index 3d7c328..0b5f37b 100644
Binary files a/binaries/macOS/Disc Image Manager 32 bit.dmg and b/binaries/macOS/Disc Image Manager 32 bit.dmg differ
diff --git a/binaries/macOS/Disc Image Manager ARM.dmg b/binaries/macOS/Disc Image Manager ARM.dmg
index 188a00b..885fb6d 100644
Binary files a/binaries/macOS/Disc Image Manager ARM.dmg and b/binaries/macOS/Disc Image Manager ARM.dmg differ
diff --git a/binaries/macOS/Disc Image Manager.dmg b/binaries/macOS/Disc Image Manager.dmg
index 3a45c42..de9e48f 100644
Binary files a/binaries/macOS/Disc Image Manager.dmg and b/binaries/macOS/Disc Image Manager.dmg differ