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