AsphyreDb.pas
上传用户:ctlcnc
上传日期:2021-12-10
资源大小:4933k
文件大小:30k
源码类别:

2D图形编程

开发平台:

Delphi

  1. unit AsphyreDb;
  2. //---------------------------------------------------------------------------
  3. // AsphyreDb.pas                                        Modified: 07-Jan-2007
  4. // Asphyre Secure Database (ASDb) implementation                  Version 1.1
  5. //---------------------------------------------------------------------------
  6. // The contents of this file are subject to the Mozilla Public License
  7. // Version 1.1 (the "License"); you may not use this file except in
  8. // compliance with the License. You may obtain a copy of the License at
  9. // http://www.mozilla.org/MPL/
  10. //
  11. // Software distributed under the License is distributed on an "AS IS"
  12. // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
  13. // License for the specific language governing rights and limitations
  14. // under the License.
  15. //---------------------------------------------------------------------------
  16. // This library uses RSA Data Security, Inc., MD5 message-digest algorithm to
  17. // verify data integrity.
  18. //---------------------------------------------------------------------------
  19. interface
  20. //---------------------------------------------------------------------------
  21. uses
  22.  Windows, Classes, Math, SysUtils, StreamEx, AsphyreData, AsphyreMD5,
  23.  AsphyreXTEA;
  24. //---------------------------------------------------------------------------
  25. const
  26.  ASDbSignature = $62445341; // 'ASDb'
  27.  // record type enumerations
  28.  recUnknown    = 0;
  29.  recGraphics   = 1;
  30.  recFile       = 2;
  31.  recFont       = 3;
  32. //---------------------------------------------------------------------------
  33. type
  34.  PASDbHeader = ^TASDbHeader;
  35.  TASDbHeader = packed record 
  36.   Signature  : Longword; // signature ('ASDb' = 62445341h)
  37.   RecordCount: Longword; // number of records in the archive
  38.   TableOffset: Longword; // table offset
  39.  end;
  40. //---------------------------------------------------------------------------
  41. {
  42.  ASDb Table structure:
  43.   Key Name      -  4+ bytes (dword: length; [length-bytes]: string chars)
  44.   Offset        -  unsigned dword
  45.  ASDb Record structure:
  46.   RecordType    -  word
  47.   OrigSize      -  unsigned dword
  48.   PhysSize      -  unsigned dword
  49.   DateTime      -  double (unsigned qword)
  50.   Checksum      -  16 bytes (MD5 message-digest)
  51.   Encoding      -  word
  52.   IV            -  unsigned qword (8 bytes) as IV
  53.   DataBlock     -  DataSize bytes
  54. }
  55. //---------------------------------------------------------------------------
  56.  TRecordInfo = record
  57.   Key       : string;    // record unique identifier
  58.   Offset    : Longword;  // record offset in archive
  59.   RecordType: Cardinal;  // type of the record (generic, file, graphics, etc)
  60.   OrigSize: Cardinal;  // original data size
  61.   PhysSize: Cardinal;  // physical data size
  62.   DateTime: TDateTime; // record date & time
  63.   Checksum: TKey128; // MD5 message-digest
  64.   Secure : Boolean;   // whether record is encrypted
  65.   InitVec: TBlock64;
  66.  end;
  67. //---------------------------------------------------------------------------
  68.  TOpenModes = (opUpdate, opOverwrite, opReadOnly);
  69. //---------------------------------------------------------------------------
  70.  TASDb = class
  71.  private
  72.   FUpdatedOnce: Boolean;
  73.   FFileSize : Cardinal;
  74.   FFileName : string;
  75.   FOpenMode : TOpenModes;
  76.   FRecords  : array of TRecordInfo;
  77.   ASDbHeader: TASDbHeader;
  78.   FPassword : Pointer;
  79.   InsideKey : TKey128;
  80.   function GetRecordDate(Num: Integer): TDateTime;
  81.   function GetRecordCount(): Integer;
  82.   function GetRecordPhysSize(Num: Integer): Integer;
  83.   function GetRecordKey(Num: Integer): string;
  84.   function GetRecordNum(Key: string): Integer;
  85.   function GetRecordOrigSize(Num: Integer): Integer;
  86.   procedure SetFileName(const Value: string);
  87.   function CreateEmtpyFile(): Boolean;
  88.   function GetRecordType(Num: Integer): Integer;
  89.   function GetRecordSecure(Num: Integer): Boolean;
  90.   function GetRecordChecksum(Num: Integer): PKey128;
  91.   function ReadASDbHeader(Stream: TStream; ASDbHeader: PASDbHeader): Boolean;
  92.   function ReadASDbInfo(Stream: TStream): Boolean;
  93.   function WriteRecordTable(): Boolean;
  94.   function CompressData(Source: Pointer; SourceSize: Cardinal;
  95.    out Data: Pointer; out DataSize: Cardinal): Boolean;
  96.   function DecompressData(Source: Pointer; SourceSize: Cardinal;
  97.    out Data: Pointer; DataSize: Cardinal): Boolean;
  98.  public
  99.   //=========================================================================
  100.   // PUBLIC Properties
  101.   //=========================================================================
  102.   property UpdatedOnce: Boolean read FUpdatedOnce;
  103.   property FileSize: Cardinal read FFileSize;
  104.   property Password: Pointer read FPassword write FPassword;
  105.   property RecordCount: Integer read GetRecordCount;
  106.   property RecordKey[Num: Integer]: string read GetRecordKey;
  107.   property RecordPhysSize[Num: Integer]: Integer read GetRecordPhysSize;
  108.   property RecordOrigSize[Num: Integer]: Integer read GetRecordOrigSize;
  109.   property RecordNum[Key: string]: Integer read GetRecordNum;
  110.   property RecordType[Num: Integer]: Integer read GetRecordType;
  111.   property RecordDate[Num: Integer]: TDateTime read GetRecordDate;
  112.   property RecordSecure[Num: Integer]: Boolean read GetRecordSecure;
  113.   property RecordChecksum[Num: Integer]: PKey128 read GetRecordChecksum;
  114.   //=========================================================================
  115.   // PUBLIC Methods
  116.   //=========================================================================
  117.   procedure SetPassword(const Pass: ShortString);
  118.   procedure BurnPassword();
  119.   // writes the specific record to ASDb archive
  120.   function WriteRecord(const Key: string; Source: Pointer;
  121.    SourceSize: Cardinal; RecordType: Integer): Boolean;
  122.   // writes the entire stream to ASDb archive
  123.   function WriteStream(const Key: string; Stream: TStream;
  124.    RecordType: Integer): Boolean;
  125.   function WriteString(const Key, Text: string;
  126.    RecordType: Integer): Boolean;
  127.   // reads the specified record from ASDb archive
  128.   // NOTE: this method allocates memory which needs to be freed by FreeMem
  129.   function ReadRecord(const Key: string; out Data: Pointer;
  130.    out DataSize: Cardinal): Boolean;
  131.   // reads the record and stores it in the stream
  132.   function ReadStream(const Key: string; Stream: TStream): Boolean;
  133.   // reads ASDb record contents as a text string
  134.   function ReadString(const Key: string; out Text: string): Boolean;
  135.   // removes the record from archive
  136.   function RemoveRecord(const Key: string): Boolean;
  137.   // changes the key of the record without physically moving it
  138.   function RenameRecord(const Key, NewKey: string): Boolean;
  139.   // switches the positions of two records
  140.   function SwitchRecords(Index1, Index2: Integer): Boolean;
  141.   // sorts the records by type not affecting order of items with the same type
  142.   function SortRecords(): Boolean;
  143.   constructor Create();
  144.   // updates the list of ASDb records
  145.   function Update(): Boolean;
  146.   // updates the record list only once
  147.   function UpdateOnce(): Boolean;
  148.  published
  149.   // The name of the archive
  150.   property FileName: string read FFileName write SetFileName;
  151.   // open mode (e.g. WriteBuffer-only)
  152.   property OpenMode: TOpenModes read FOpenMode write FOpeNmode;
  153.  end;
  154. //---------------------------------------------------------------------------
  155. implementation
  156. //---------------------------------------------------------------------------
  157. const
  158.  // A record name returned when invalid index is specified
  159.  invRecordName = '[invalid-record-#]';
  160.  // When using compression, a temporary buffer is used to store the final
  161.  // output. Under certain circumstances, the output data size is bigger than
  162.  // the original. For these cases, output buffer is created slightly bigger
  163.  // than the original. The additional percentage added is specified below.
  164.  BufferGrow    = 5; // default: 5 (in %)
  165.  // For the same purpose as BufferGrow, this value is simply added to the
  166.  // buffer size previously increased by BufferGrow (for very short buffers).
  167.  BufferGrowAdd = 256; // default: 256
  168.  // In original record position, this offset determines where record data
  169.  // is allocated. This is used for ReadRecord method to get directly to
  170.  // record data. Also used for removing records.
  171.  DataOffset    = 44;
  172.  // Temporary archive name to be used when deleting or overwriting records
  173.  TempFilename  = 'asdb.tmp';
  174. //---------------------------------------------------------------------------
  175. constructor TASDb.Create();
  176. begin
  177.  inherited;
  178.  FUpdatedOnce:= False;
  179.  FFileSize:= 0;
  180.  FFileName:= '';
  181.  FOpenMode:= opUpdate;
  182.  FPassword:= nil;
  183.  SetLength(FRecords, 0);
  184.  FillChar(ASDbHeader, SizeOf(TASDbHeader), 0);
  185. end;
  186. //---------------------------------------------------------------------------
  187. function TASDb.GetRecordCount(): Integer;
  188. begin
  189.  Result:= Length(FRecords);
  190. end;
  191. //---------------------------------------------------------------------------
  192. function TASDb.GetRecordPhysSize(Num: Integer): Integer;
  193. begin
  194.  if (Num >= 0)and(Num < Length(FRecords)) then
  195.   begin
  196.    Result:= FRecords[Num].PhysSize;
  197.   end else Result:= 0;
  198. end;
  199. //---------------------------------------------------------------------------
  200. function TASDb.GetRecordOrigSize(Num: Integer): Integer;
  201. begin
  202.  if (Num >= 0)and(Num < Length(FRecords)) then
  203.   begin
  204.    Result:= FRecords[Num].OrigSize;
  205.   end else Result:= 0;
  206. end;
  207. //---------------------------------------------------------------------------
  208. function TASDb.GetRecordType(Num: Integer): Integer;
  209. begin
  210.  if (Num >= 0)and(Num < Length(FRecords)) then
  211.   begin
  212.    Result:= FRecords[Num].RecordType;
  213.   end else Result:= 0;
  214. end;
  215. //---------------------------------------------------------------------------
  216. function TASDb.GetRecordKey(Num: Integer): string;
  217. begin
  218.  if (Num >= 0)and(Num < Length(FRecords)) then
  219.   begin
  220.    Result:= FRecords[Num].Key;
  221.   end else Result:= invRecordName;
  222. end;
  223. //---------------------------------------------------------------------------
  224. function TASDb.GetRecordDate(Num: Integer): TDateTime;
  225. begin
  226.  if (Num >= 0)and(Num < Length(FRecords)) then
  227.   begin
  228.    Result:= FRecords[Num].DateTime;
  229.   end else Result:= Now();
  230. end;
  231. //---------------------------------------------------------------------------
  232. function TASDb.GetRecordSecure(Num: Integer): Boolean;
  233. begin
  234.  if (Num >= 0)and(Num < Length(FRecords)) then
  235.   begin
  236.    Result:= FRecords[Num].Secure;
  237.   end else Result:= False;
  238. end;
  239. //---------------------------------------------------------------------------
  240. function TASDb.GetRecordChecksum(Num: Integer): PKey128;
  241. begin
  242.  if (Num >= 0)and(Num < Length(FRecords)) then
  243.   begin
  244.    Result:= @FRecords[Num].Checksum;
  245.   end else Result:= nil;
  246. end;
  247. //---------------------------------------------------------------------------
  248. procedure TASDb.SetFileName(const Value: string);
  249. begin
  250.  FFileName:= Value;
  251.  FUpdatedOnce:= False;
  252. end;
  253. //---------------------------------------------------------------------------
  254. procedure TASDb.SetPassword(const Pass: ShortString);
  255. begin
  256.  MD5Checksum(@Pass[1], Length(Pass), @InsideKey);
  257.  FPassword:= @InsideKey;
  258. end;
  259. //---------------------------------------------------------------------------
  260. procedure TASDb.BurnPassword();
  261. begin
  262.  FillChar(InsideKey, SizeOf(TKey128), 0);
  263.  FPassword:= nil;
  264. end;
  265. //---------------------------------------------------------------------------
  266. function TASDb.GetRecordNum(Key: string): Integer;
  267. var
  268.  i: Integer;
  269. begin
  270.  Key:= LowerCase(Key);
  271.  for i:= 0 to Length(FRecords) - 1 do
  272.   if (LowerCase(FRecords[i].Key) = Key) then
  273.    begin
  274.     Result:= i;
  275.     Exit;
  276.    end;
  277.  Result:= -1;
  278. end;
  279. //---------------------------------------------------------------------------
  280. function TASDb.CreateEmtpyFile(): Boolean;
  281. var
  282.  fs: TStream;
  283. begin
  284.  // prepare empty header
  285.  FillChar(ASDbHeader, SizeOf(TASDbHeader), 0);
  286.  ASDbHeader.Signature:= ASDbSignature;
  287.  ASDbHeader.RecordCount:= 0;
  288.  // offset to non-existant table
  289.  ASDbHeader.TableOffset:= SizeOf(TASDbHeader);
  290.  // create file stream
  291.  try
  292.   fs:= TFileStream.Create(FFileName, fmCreate or fmShareExclusive);
  293.  except
  294.   Result:= False;
  295.   Exit;
  296.  end;
  297.  // write header
  298.  Result:= True;
  299.  try
  300.   fs.WriteBuffer(ASDbHeader, SizeOf(ASDbHeader));
  301.  except
  302.   Result:= False;
  303.  end;
  304.  // free file stream
  305.  fs.Free();
  306.  // file size
  307.  FFileSize:= SizeOf(ASDbHeader);
  308.  // assume no records exist
  309.  SetLength(FRecords, 0);
  310. end;
  311. //---------------------------------------------------------------------------
  312. function TASDb.ReadASDbHeader(Stream: TStream; ASDbHeader: PASDbHeader): Boolean;
  313. begin
  314.  if (Stream = nil) then
  315.   begin
  316.    Result:= False;
  317.    Exit;
  318.   end;
  319.  Result:= True;
  320.  try
  321.   // read ASDb Header
  322.   Stream.Seek(0, soFromBeginning);
  323.   Stream.ReadBuffer(ASDbHeader^, SizeOf(TASDbHeader));
  324.  except
  325.   Result:= False;
  326.  end;
  327. end;
  328. //---------------------------------------------------------------------------
  329. function TASDb.ReadASDbInfo(Stream: TStream): Boolean;
  330. var
  331.  i: Integer;
  332.  NoStream: Boolean;
  333. begin
  334.  // release records
  335.  SetLength(FRecords, 0);
  336.  NoStream:= (Stream = nil);
  337.  if (NoStream) then
  338.   begin
  339.    // open the specified file
  340.    try
  341.     Stream:= TFileStream.Create(FFileName, fmOpenRead or fmShareDenyWrite);
  342.    except
  343.     Result:= False;
  344.     Exit;
  345.    end;
  346.   end;
  347.  // read & validate ASDbHeader
  348.  Result:= ReadASDbHeader(Stream, @ASDbHeader);
  349.  if (not Result) then
  350.   begin
  351.    if (NoStream) then Stream.Free();
  352.    Exit;
  353.   end;
  354.  // retreive file size
  355.  FFileSize:= Stream.Size;
  356.  // seek record table in archive
  357.  Stream.Seek(ASDbHeader.TableOffset, soFromBeginning);
  358.  // specify record count
  359.  SetLength(FRecords, ASDbHeader.RecordCount);
  360.  // read record names and positions
  361.  try
  362.   for i:= 0 to Length(FRecords) - 1 do
  363.    begin
  364.     // read record name
  365.     FRecords[i].Key:= stReadString(Stream);
  366.     FRecords[i].Offset:= stReadLongword(Stream);
  367.    end;
  368.  except
  369.   Result:= False;
  370.  end;
  371.  // check for Read errors
  372.  if (not Result) then
  373.   begin
  374.    // no records are saved on error
  375.    SetLength(FRecords, 0);
  376.    if (NoStream) then Stream.Free();
  377.    Exit;
  378.   end;
  379.  // check if any records exist in archive
  380.  if (ASDbHeader.RecordCount < 1) then
  381.   begin
  382.    if (NoStream) then Stream.Free();
  383.    Exit;
  384.   end;
  385.  // read record detailed information
  386.  try
  387.   for i:= 0 to Length(FRecords) - 1 do
  388.    begin
  389.     // seek record's position
  390.     Stream.Seek(FRecords[i].Offset, soFromBeginning);
  391.     // record type
  392.     FRecords[i].RecordType:= stReadWord(Stream);
  393.     // basic info
  394.     FRecords[i].OrigSize:= stReadLongword(Stream);
  395.     FRecords[i].PhysSize:= stReadLongword(Stream);
  396.     FRecordS[i].DateTime:= stReadDouble(Stream);
  397.     // MD5 message-digest of record's contents
  398.     Stream.ReadBuffer(FRecords[i].Checksum, SizeOf(TKey128));
  399.     // security information
  400.     FRecords[i].Secure:= Boolean(stReadWord(Stream));
  401.     Stream.ReadBuffer(FRecords[i].InitVec, SizeOf(TBlock64));
  402.    end; // for
  403.  except
  404.   Result:= False;
  405.  end;
  406.  // release stream's memory
  407.  if (NoStream) then Stream.Free();
  408. end;
  409. //---------------------------------------------------------------------------
  410. function TASDb.Update(): Boolean;
  411. begin
  412.  Result:= True;
  413.  // act depending of opening mode
  414.  case FOpenMode of
  415.   // create new file
  416.   opOverwrite:
  417.    Result:= CreateEmtpyFile();
  418.   // open file for reading
  419.   opReadOnly:
  420.    Result:= ReadASDbInfo(nil);
  421.   // open file for update
  422.   opUpdate:
  423.    begin
  424.     if (FileExists(FFileName)) then Result:= ReadASDbInfo(nil)
  425.      else Result:= CreateEmtpyFile();
  426.    end;
  427.  end;
  428.  FUpdatedOnce:= Result;
  429. end;
  430. //---------------------------------------------------------------------------
  431. function TASDb.UpdateOnce(): Boolean;
  432. begin
  433.  Result:= FUpdatedOnce;
  434.  if (not Result) then Result:= Update();
  435. end;
  436. //---------------------------------------------------------------------------
  437. function TASDb.CompressData(Source: Pointer; SourceSize: Cardinal;
  438.  out Data: Pointer; out DataSize: Cardinal): Boolean;
  439. var
  440.  CodeBuf   : Pointer;
  441.  BufferSize: Cardinal;
  442. begin
  443.  Result:= True;
  444.  // guaranteed buffer size
  445.  BufferSize:= Ceil((SourceSize * (100 + BufferGrow)) / 100) + BufferGrowAdd;
  446.  // allocate encoding buffer
  447.  GetMem(CodeBuf, BufferSize);
  448.  // inflate the buffer
  449.  DataSize:= AsphyreData.CompressData(Source, CodeBuf, SourceSize, BufferSize,
  450.   clHighest);
  451.  if (DataSize = 0) then
  452.   begin
  453.    FreeMem(CodeBuf);
  454.    Result:= False;
  455.    Exit;
  456.   end;
  457.  // allocate real data container
  458.  GetMem(Data, DataSize);
  459.  // copy the compressed data
  460.  Move(CodeBuf^, Data^, DataSize);
  461.  // release encoding buffer
  462.  FreeMem(CodeBuf);
  463. end;
  464. //---------------------------------------------------------------------------
  465. function TASDb.DecompressData(Source: Pointer; SourceSize: Cardinal;
  466.  out Data: Pointer; DataSize: Longword): Boolean;
  467. var
  468.  OutSize: Integer;
  469. begin
  470.  Result:= True;
  471.  // allocate output buffer
  472.  GetMem(Data, DataSize);
  473.  // decompress the data stream
  474.  OutSize:= AsphyreData.DecompressData(Source, Data, SourceSize, DataSize);
  475.  if (OutSize = 0)or(Int64(OutSize) <> DataSize) then
  476.   begin
  477.    FreeMem(Data);
  478.    Data:= nil;
  479.    Result:= False;
  480.   end;
  481. end;
  482. //---------------------------------------------------------------------------
  483. function TASDb.WriteRecordTable(): Boolean;
  484. var
  485.  Stream: TFileStream;
  486.  i: Integer;
  487. begin
  488.  Result:= True;
  489.  // (1) OPEN THE ARCHIVE for *writing*
  490.  try
  491.   Stream:= TFileStream.Create(FFileName, fmOpenWrite or fmShareExclusive);
  492.  except
  493.   Result:= False;
  494.   Exit;
  495.  end;
  496.  try
  497.   // (2) go to the position of record table
  498.   Stream.Seek(ASDbHeader.TableOffset, soFromBeginning);
  499.   // (3) flush the record table
  500.   for i:= 0 to Length(FRecords) - 1 do
  501.    begin
  502.     stWriteString(Stream, FRecords[i].Key);
  503.     stWriteLongword(Stream, FRecords[i].Offset);
  504.    end;
  505.  except
  506.   Result:= False;
  507.  end;
  508.  // (4) release the file stream
  509.  Stream.Free();
  510. end;
  511. //---------------------------------------------------------------------------
  512. function TASDb.WriteRecord(const Key: string; Source: Pointer;
  513.  SourceSize: Cardinal; RecordType: Integer): Boolean;
  514. var
  515.  Data: Pointer;
  516.  DataSize: Cardinal;
  517.  i, NewIndex: Integer;
  518.  Stream: TStream;
  519.  RecordOffset: Cardinal;
  520.  InitVec : TBlock64;
  521.  Checksum: TKey128;
  522.  CurDate : TDateTime;
  523. begin
  524.  Result := False;
  525.  CurDate:= Now();
  526.  // (1) verify open mode
  527.  if (FOpenMode = opReadOnly) then Exit;
  528.  // (2) if the record exists, remove it
  529.  if (GetRecordNum(Key) <> -1) then RemoveRecord(Key);
  530.  // (3) calculate checksum and digest
  531.  MD5Checksum(Source, SourceSize, @Checksum);
  532.  // (4) compress input data
  533.  Result:= CompressData(Source, SourceSize, Data, DataSize);
  534.  if (not Result) then Exit;
  535.  // (5) Apply security
  536.  if (FPassword <> nil) then
  537.   begin
  538.    // -> generate random IV keys
  539.    InitVec[0]:= Round(Random * High(Longword));
  540.    InitVec[1]:= Round(Random * High(Longword));
  541.    // -> encrypt compressed data
  542.    CipherDataXTEA(Data, Data, DataSize, FPassword, @InitVec);
  543.   end else
  544.   begin
  545.    InitVec[0]:= 0;
  546.    InitVec[1]:= 0;
  547.   end;
  548.  // (6) OPEN THE ARCHIVE for reading & writing
  549.  try
  550.   Stream:= TFileStream.Create(FFileName, fmOpenReadWrite or fmShareExclusive);
  551.  except
  552.   Result:= False;
  553.   Exit;
  554.  end;
  555.  // (7) update ASDb info, in case it has been changed
  556.  Result:= ReadASDbInfo(Stream);
  557.  if (not Result) then
  558.   begin
  559.    Stream.Free();
  560.    Exit;
  561.   end;
  562.  // (8) if the record still exists, we cannot proceed
  563.  if (GetRecordNum(Key) <> -1) then
  564.   begin
  565.    Stream.Free();
  566.    Result:= False;
  567.    Exit;
  568.   end;
  569.  // (9) write the ENTIRE RECORD
  570.  try
  571.   // seek the record table position and write the record there!
  572.   RecordOffset:= ASDbHeader.TableOffset;
  573.   Stream.Seek(RecordOffset, soFromBeginning);
  574.   // RECORD TYPE
  575.   stWriteWord(Stream, RecordType);
  576.   // ORIGINAL SIZE
  577.   stWriteLongword(Stream, SourceSize);
  578.   // PHYSICAL SIZE
  579.   stWriteLongword(Stream, DataSize);
  580.   // DATE & TIME
  581.   stWriteDouble(Stream, CurDate);
  582.   // Checksum: MD5 message-digest
  583.   Stream.WriteBuffer(Checksum, SizeOf(Checksum));
  584.   // Security Information
  585.   stWriteWord(Stream, Word(FPassword <> nil));
  586.   Stream.WriteBuffer(InitVec, SizeOf(TBlock64));
  587.   // RECORD DATA
  588.   Stream.WriteBuffer(Data^, DataSize);
  589.  except
  590.   Result:= False;
  591.   FreeMem(Data);
  592.   Stream.Free();
  593.   Exit;
  594.  end;
  595.  // (10) add new record to the record list
  596.  NewIndex:= Length(FRecords);
  597.  SetLength(FRecords, NewIndex + 1);
  598.  FRecords[NewIndex].Key:= Key;
  599.  Move(Checksum, FRecords[NewIndex].Checksum, SizeOf(Checksum));
  600.  FRecords[NewIndex].RecordType:= RecordType;
  601.  FRecords[NewIndex].OrigSize:= SourceSize;
  602.  FRecords[NewIndex].PhysSize:= DataSize;
  603.  FRecords[NewIndex].Offset  := RecordOffset;
  604.  FRecords[NewIndex].DateTime:= CurDate;
  605.  FRecords[NewIndex].Secure  := (FPassword <> nil);
  606.  FRecords[NewIndex].InitVec[0]:= InitVec[0];
  607.  FRecords[NewIndex].InitVec[1]:= InitVec[1];
  608.  // (11) update ASDb Header information
  609.  ASDbHeader.TableOffset:= Stream.Position;
  610.  ASDbHeader.RecordCount:= ASDbHeader.RecordCount + 1;
  611.  try
  612.   // (12) rewrite entire RECORD TABLE
  613.   for i:= 0 to Length(FRecords) - 1 do
  614.    begin
  615.     stWriteString(Stream, FRecords[i].Key);
  616.     stWriteLongword(Stream, FRecords[i].Offset);
  617.    end;
  618.  // (13) write down ASDb HEADER
  619.  Stream.Seek(0, soFromBeginning);
  620.  Stream.WriteBuffer(ASDbHeader, SizeOf(TASDbHeader));
  621.  except
  622.   Result:= False;
  623.  end;
  624.  // (14) Release the stream and memory
  625.  FreeMem(Data);
  626.  Stream.Free();
  627. end;
  628. //---------------------------------------------------------------------------
  629. function TASDb.WriteStream(const Key: string; Stream: TStream;
  630.  RecordType: Integer): Boolean;
  631. var
  632.  Data: Pointer;
  633.  DataSize, ReadBytes: Integer;
  634. begin
  635.  Result:= False;
  636.  // verify open mode
  637.  if (FOpenMode = opReadOnly) then Exit;
  638.  // allocate memory for stream data
  639.  DataSize:= Stream.Size - Stream.Position;
  640.  Data:= AllocMem(DataSize);
  641.  // read the stream data
  642.  ReadBytes:= Stream.Read(Data^, DataSize);
  643.  if (ReadBytes <> DataSize) then
  644.   begin
  645.    FreeMem(Data);
  646.    Exit;
  647.   end;
  648.  // write the data to ASDb
  649.  Result:= WriteRecord(Key, Data, DataSize, RecordType);
  650.  // free the unused memory
  651.  FreeMem(Data);
  652. end;
  653. //---------------------------------------------------------------------------
  654. function TASDb.WriteString(const Key, Text: string; RecordType: Integer): Boolean;
  655. begin
  656.  if (Length(Text) < 1) then
  657.   begin
  658.    Result:= False;
  659.    Exit;
  660.   end;
  661.  Result:= WriteRecord(Key, @Text[1], Length(Text), RecordType);
  662. end;
  663. //---------------------------------------------------------------------------
  664. function TASDb.ReadRecord(const Key: string; out Data: Pointer;
  665.  out DataSize: Cardinal): Boolean;
  666. var
  667.  PreRelease: Boolean;
  668.  PreBuf  : Pointer;
  669.  PreSize : Cardinal;
  670.  Index   : Integer;
  671.  Stream  : TStream;
  672.  Checksum: TKey128;
  673. begin
  674.  Result:= False;
  675.  // (1) OPEN archive
  676.  try
  677.   Stream:= TFileStream.Create(FFileName, fmOpenRead or fmShareDenyWrite);
  678.  except
  679.   Exit;
  680.  end;
  681.  // (2) update ASDb info, in case it has been changed
  682.  Result:= ReadASDbInfo(Stream);
  683.  if (not Result) then
  684.   begin
  685.    Stream.Free();
  686.    Exit;
  687.   end;
  688.  // (3) find record index
  689.  Index:= GetRecordNum(Key);
  690.  if (Index = -1) then
  691.   begin
  692.    Stream.Free();
  693.    Result:= False;
  694.    Exit;
  695.   end;
  696.  // assign data size
  697.  DataSize:= FRecords[Index].OrigSize;
  698.  // (4) create temporary buffers
  699.  PreSize:= FRecords[Index].PhysSize;
  700.  GetMem(PreBuf, PreSize);
  701.  PreRelease:= True;
  702.  // (5) read the ENTIRE RECORD
  703.  try
  704.   // seek the record position in the file
  705.   Stream.Seek(FRecords[Index].Offset + DataOffset, soFromBeginning);
  706.   // read record data
  707.   Stream.ReadBuffer(PreBuf^, PreSize);
  708.  except
  709.   Result:= False;
  710.   FreeMem(PreBuf);
  711.   Stream.Free();
  712.   Exit;
  713.  end;
  714.  // close the file stream
  715.  Stream.Free();
  716.  // (6) Apply security
  717.  if (FRecords[Index].Secure)and(FPassword <> nil) then
  718.   begin
  719.    DecipherDataXTEA(PreBuf, PreBuf, PreSize, FPassword,
  720.     @FRecords[Index].InitVec);
  721.   end;
  722.  // (7) decompress the data stream
  723.  Result:= DecompressData(PreBuf, PreSize, Data, DataSize);
  724.  if (not Result) then
  725.   begin
  726.    FreeMem(PreBuf);
  727.    Exit;
  728.   end;
  729.  // (8) release buffers
  730.  if (PreRelease) then FreeMem(PreBuf);
  731.  // (9) checksum verification
  732.  MD5Checksum(Data, DataSize, @Checksum);
  733.  Result:= CompareMem(@Checksum, @FRecords[Index].Checksum, SizeOf(Checksum));
  734. end;
  735. //---------------------------------------------------------------------------
  736. function TASDb.ReadStream(const Key: string; Stream: TStream): Boolean;
  737. var
  738.  Data: Pointer;
  739.  DataSize, BytesWritten: Cardinal;
  740. begin
  741.  // read the record data
  742.  Result:= ReadRecord(Key, Data, DataSize);
  743.  // write the record data to stream
  744.  if (Result) then
  745.   begin
  746.    BytesWritten:= Stream.Write(Data^, DataSize);
  747.    Result:= (BytesWritten = DataSize);
  748.    // free the unused memory
  749.    FreeMem(Data);
  750.   end;
  751. end;
  752. //---------------------------------------------------------------------------
  753. function TASDb.ReadString(const Key: string; out Text: string): Boolean;
  754. var
  755.  Data: Pointer;
  756.  Size: Cardinal;
  757. begin
  758.  Result:= ReadRecord(Key, Data, Size);
  759.  if (Result) then
  760.   begin
  761.    if (Size > 0) then
  762.     begin
  763.      SetLength(Text, Size);
  764.      Move(Data^, (@Text[1])^, Size);
  765.      FreeMem(Data);
  766.     end else Text:= '';
  767.   end;
  768. end;
  769. //---------------------------------------------------------------------------
  770. function TASDb.RemoveRecord(const Key: string): Boolean;
  771. var
  772.  InStream, OutStream: TFileStream;
  773.  NewHeader: TASDbHeader;
  774.  NewRecords: array of TRecordInfo;
  775.  i, Index, NewIndex: Integer;
  776.  Data: Pointer;
  777.  DataSize: Cardinal;
  778. begin
  779.  SetLength(NewRecords, 0);
  780.  Data:= nil;
  781.  // (1) Update record list
  782.  Result:= Update();
  783.  if (not Result) then Exit;
  784.  // (2) retreive record index
  785.  Index:= GetRecordNum(Key);
  786.  if (Index = -1) then
  787.   begin
  788.    Result:= False;
  789.    Exit;
  790.   end; 
  791.  // (3) OPEN THE SOURCE for reading & writing
  792.  try
  793.   InStream:= TFileStream.Create(FFileName, fmOpenReadWrite or fmShareDenyWrite);
  794.  except
  795.   Exit;
  796.  end;
  797.  // (4) OPEN THE DESTINATION for writing
  798.  try
  799.   OutStream:= TFileStream.Create(TempFilename, fmCreate);
  800.  except
  801.   Exit;
  802.  end;
  803.  // (5) update ASDb info, in case it has been changed
  804.  Result:= ReadASDbInfo(InStream);
  805.  if (not Result) then
  806.   begin
  807.    InStream.Free();
  808.    OutStream.Free();
  809.    Exit;
  810.   end;
  811.  // (6) create NEW HEADER
  812.  Move(ASDbHeader, NewHeader, SizeOf(TASDbHeader));
  813.  NewHeader.RecordCount:= ASDbHeader.RecordCount - 1;
  814.  // (7) Write temporary ASDb header
  815.  try
  816.   OutStream.WriteBuffer(NewHeader, SizeOf(TASDbHeader));
  817.  except
  818.   Result:= False;
  819.   InStream.Free();
  820.   OutStream.Free();
  821.   Exit;
  822.  end;
  823.  // (8) Completely rewrite RECORD LIST
  824.  for i:= 0 to Length(FRecords) - 1 do
  825.   if (i <> Index) then
  826.    begin
  827.     // create a copy of previous record
  828.     NewIndex:= Length(NewRecords);
  829.     SetLength(NewRecords, NewIndex + 1);
  830.     NewRecords[NewIndex]:= FRecords[i];
  831.     // update record offset
  832.     NewRecords[NewIndex].Offset:= OutStream.Position;
  833.     // allocate temporary buffers
  834.     DataSize:= NewRecords[NewIndex].PhysSize + DataOffset;
  835.     ReallocMem(Data, DataSize);
  836.     // read the whole record block
  837.     try
  838.      InStream.Seek(FRecords[i].Offset, soFromBeginning);
  839.      InStream.ReadBuffer(Data^, DataSize);
  840.     except
  841.      InStream.Free();
  842.      OutStream.Free();
  843.      FreeMem(Data);
  844.      Result:= False;
  845.      Exit;
  846.     end;
  847.     // write the whole record block
  848.     try
  849.      OutStream.WriteBuffer(Data^, DataSize);
  850.     except
  851.      InStream.Free();
  852.      OutStream.Free();
  853.      FreeMem(Data);
  854.      Result:= False;
  855.      Exit;
  856.     end;
  857.    end; // rewrite records
  858.  // the record table follows, update ASDb header
  859.  NewHeader.TableOffset:= OutStream.Position;
  860.  // (9) write NEW RECORD table (and update the current one)
  861.  SetLength(FRecords, Length(NewRecords));
  862.  try
  863.   for i:= 0 to Length(NewRecords) - 1 do
  864.    begin
  865.     // write record info
  866.     stWriteString(OutStream, NewRecords[i].Key);
  867.     stWriteLongword(OutStream, NewRecords[i].Offset);
  868.     // update the record table
  869.     FRecords[i]:= NewRecords[i];
  870.    end;
  871.   // (10) write updated ASDb header
  872.   OutStream.Seek(0, soFromBeginning);
  873.   OutStream.WriteBuffer(NewHeader, SizeOf(TASDbHeader));
  874.  except
  875.   Result:= False;
  876.   InStream.Free();
  877.   OutStream.Free();
  878.   Exit;
  879.  end;
  880.  // update file size
  881.  FFileSize:= OutStream.Size;
  882.  // (11) Release allocated buffers
  883.  if (Data <> nil) then FreeMem(Data);
  884.  InStream.Free();
  885.  OutStream.Free();
  886.  // (12) Switch between temporary file and real one
  887.  try
  888.   DeleteFile(FFileName);
  889.   RenameFile(TempFilename, FFileName);
  890.  except
  891.   Result:= False;
  892.  end;
  893. end;
  894. //---------------------------------------------------------------------------
  895. function TASDb.RenameRecord(const Key, NewKey: string): Boolean;
  896. var
  897.  Index: Integer;
  898. begin
  899.  // (1) Check the validity of OpenMode.
  900.  if (FOpenMode in [opOverwrite, opReadonly]) then
  901.   begin
  902.    Result:= False;
  903.    Exit;
  904.   end;
  905.  // (2) Refresh record list.
  906.  Result:= ReadASDbInfo(nil);
  907.  if (not Result) then Exit;
  908.  // (3) Check the validity of specified keys.
  909.  Index:= GetRecordNum(Key);
  910.  if (Index = -1)or(GetRecordNum(NewKey) <> -1) then
  911.   begin
  912.    Result:= False;
  913.    Exit;
  914.   end;
  915.  // (4) Modify record table.
  916.  FRecords[Index].Key:= NewKey;
  917.  // (5) Write new record table.
  918.  Result:= WriteRecordTable();
  919. end;
  920. //---------------------------------------------------------------------------
  921. function TASDb.SwitchRecords(Index1, Index2: Integer): Boolean;
  922. var
  923.  Aux: TRecordInfo;
  924. begin
  925.  // (1) Check the validity of OpenMode.
  926.  if (FOpenMode in [opOverwrite, opReadonly]) then
  927.   begin
  928.    Result:= False;
  929.    Exit;
  930.   end;
  931.  // (2) Refresh record list.
  932.  Result:= ReadASDbInfo(nil);
  933.  if (not Result) then Exit;
  934.  // (3) Validate indexes with updated list.
  935.  if (Index1 < 0)or(Index2 < 0)or(Index1 >= Length(FRecords))or
  936.   (Index2 >= Length(FRecords)) then
  937.   begin
  938.    Result:= False;
  939.    Exit;
  940.   end;
  941.  // (4) Exchange two records.
  942.  Aux:= FRecords[Index1];
  943.  FRecords[Index1]:= FRecords[Index2];
  944.  FRecords[Index2]:= Aux;
  945.  // (5) Write new record table.
  946.  Result:= WriteRecordTable();
  947. end;
  948. //---------------------------------------------------------------------------
  949. function TASDb.SortRecords(): Boolean;
  950. var
  951.  i, j: Integer;
  952.  Aux: TRecordInfo;
  953. begin
  954.  for i:= 0 to Length(FRecords) - 2 do
  955.   for j:= 0 to Length(FRecords) - i - 2 do
  956.    if (FRecords[j].RecordType > FRecords[j + 1].RecordType) then
  957.     begin
  958.      Aux:= FRecords[j];
  959.      FRecords[j]:= FRecords[j + 1];
  960.      FRecords[j + 1]:= Aux;
  961.     end;
  962.  Result:= WriteRecordTable();
  963. end;
  964. //---------------------------------------------------------------------------
  965. end.