DEADSOFTWARE

Revert to old wad read/write method
authorDeaDDooMER <deaddoomer@deadsoftware.ru>
Mon, 4 Sep 2023 12:00:04 +0000 (15:00 +0300)
committerDeaDDooMER <deaddoomer@deadsoftware.ru>
Thu, 7 Sep 2023 16:55:51 +0000 (19:55 +0300)
24 files changed:
lang/editor.ru_RU.lng
src/editor/Editor.lpi
src/editor/Editor.lpr
src/editor/f_addresource.pas
src/editor/f_addresource_sky.pas
src/editor/f_addresource_sound.pas
src/editor/f_addresource_texture.pas
src/editor/f_main.lfm
src/editor/f_main.pas
src/editor/f_options.lfm
src/editor/f_options.pas
src/editor/f_packmap.lfm
src/editor/f_packmap.pas
src/editor/f_savemap.pas
src/editor/f_selectmap.pas
src/editor/g_language.pas
src/editor/g_map.pas
src/editor/g_resources.pas [deleted file]
src/editor/g_textures.pas
src/sfs/sfs.pas [deleted file]
src/sfs/sfsPlainFS.pas [deleted file]
src/sfs/sfsZipFS.pas [deleted file]
src/shared/dfzip.pas [deleted file]
src/shared/xstreams.pas [deleted file]

index 681f634a2b843edfee718af889cfb77933506d24..29bf4c95c34c84e49b6e0bbcbe0f76f77b598753 100644 (file)
@@ -1222,10 +1222,10 @@ g_language.MsgWadSpecialMap  = "<WAD-ФАЙЛ КАРТЫ>"
 g_language.MsgWadSpecialTexs$ = "<EXTRA TEXTURES>"
 g_language.MsgWadSpecialTexs  = "<СПЕЦТЕКСТУРЫ>"
 
-g_language.MsgFileFilterAll$ = "Doom 2D: Forever Maps (*.dfz, *.dfzip, *.zip, *.wad)|*.dfz;*.dfzip;*.zip;*.wad|Doom 2D: Forever 0.30 Maps (*.ini)|*.ini|All Files (*.*)|*.*"
-g_language.MsgFileFilterAll  = "Карты Doom 2D: Forever (*.dfz, *.dfzip, *.zip, *.wad)|*.dfz;*.dfzip;*.zip;*.wad|Старые карты Doom 2D: Forever 0.30 (*.ini)|*.ini|Все файлы (*.*)|*.*"
-g_language.MsgFileFilterWad$ = "Doom 2D: Forever Maps (*.dfz)|*.dfz|Doom 2D: Forever Maps (*.dfzip)|*.dfzip|Doom 2D: Forever Maps (*.zip)|*.zip|Doom 2D: Forever Maps (*.wad)|*.wad|All Files (*.*)|*.*"
-g_language.MsgFileFilterWad  = "Карты Doom 2D: Forever (*.dfz)|*.dfz|Карты Doom 2D: Forever (*.dfzip)|*.dfzip|Карты Doom 2D: Forever (*.zip)|*.zip|Карты Doom 2D: Forever (*.wad)|*.wad|Все файлы (*.*)|*.*"
+g_language.MsgFileFilterAll$ = "Doom 2D: Forever Maps (*.wad)|*.wad|Doom 2D: Forever 0.30 Maps (*.ini)|*.ini|All Files (*.*)|*.*"
+g_language.MsgFileFilterAll  = "Карты Doom 2D: Forever (*.wad)|*.wad|Старые карты Doom 2D: Forever 0.30 (*.ini)|*.ini|Все файлы (*.*)|*.*"
+g_language.MsgFileFilterWad$ = "Doom 2D: Forever Maps (*.wad)|*.wad|All Files (*.*)|*.*"
+g_language.MsgFileFilterWad  = "Карты Doom 2D: Forever (*.wad)|*.wad|Все файлы (*.*)|*.*"
 g_language.MsgFileFilterExeMac$ = "Doom 2D Forever.app|*.app|Doom 2D Forever (Unix Executable)|Doom2DF;*"
 g_language.MsgFileFilterExeMac  = "Doom 2D Forever.app|*.app|Doom 2D Forever (Исполняемый файл)|Doom2DF;*"
 g_language.MsgFileFilterExeWin$ = "Doom2DF.exe|Doom2DF.exe;*.exe"
index 1354cd3e31a746655c6aeae35fee632b0788d514..c958b71251f1f6b7abfdf359b3e8cce39285ccbc 100644 (file)
           <Parsing>
             <SyntaxOptions>
               <SyntaxMode Value="Delphi"/>
-              <IncludeAssertionCode Value="True"/>
             </SyntaxOptions>
           </Parsing>
           <CodeGeneration>
             <SmartLinkUnit Value="True"/>
             <Optimizations>
-              <OptimizationLevel Value="2"/>
+              <OptimizationLevel Value="3"/>
             </Optimizations>
           </CodeGeneration>
           <Linking>
index 6bf0b86243c94bff222999756e0d4db8bde52f4a..eaf830dd4e54b85042be85b1135bac2548d14ead 100644 (file)
@@ -18,12 +18,6 @@ uses
   WADEDITOR in '../shared/WADEDITOR.pas',
   WADSTRUCT in '../shared/WADSTRUCT.pas',
   CONFIG in '../shared/CONFIG.pas',
-  xstreams in '../shared/xstreams.pas',
-  dfzip in '../shared/dfzip.pas',
-  sfs in '../sfs/sfs.pas',
-  sfsPlainFS in '../sfs/sfsPlainFS.pas',
-  sfsZipFS in '../sfs/sfsZipFS.pas',
-
   f_about in 'f_about.pas' {AboutForm},
   f_options in 'f_options.pas' {OptionsForm},
   f_main in 'f_main.pas' {MainForm},
index 103d9b031f3165cb39920cd042a8f4d56c5dcf3f..a0bebb99bd2912ada915778f7aef692159bd35d9 100644 (file)
@@ -45,7 +45,7 @@ var
 implementation
 
 uses
-  f_main, WADSTRUCT, g_language, utils, sfs, g_options;
+  f_main, WADSTRUCT, g_language, utils, g_options;
 
 {$R *.lfm}
 
@@ -53,8 +53,9 @@ const
   STANDART_WAD = 'standart.wad';
 
 procedure TAddResourceForm.FormActivate(Sender: TObject);
-  var
-    SR: TSearchRec;
+var
+  SR: TSearchRec;
+
 begin
   cbWADList.Clear();
   cbSectionsList.Clear();
@@ -66,8 +67,7 @@ begin
 
   if FindFirst(WadsDir + DirectorySeparator + '*.*', faAnyFile, SR) = 0 then
   repeat
-    if (SR.name <> '.') and (SR.name <> '..') then
-      cbWADList.Items.Add(SR.Name);
+    cbWADList.Items.Add(SR.Name);
   until FindNext(SR) <> 0;
   FindClose(SR);
 
@@ -102,67 +102,87 @@ begin
 end;
 
 procedure TAddResourceForm.cbWADListChange(Sender: TObject);
-  var
-    wad: TSFSFileList;
-    i: Integer;
-    FileName, Section, sn, rn: String;
+var
+  WAD: TWADEditor_1;
+  SectionList: SArray;
+  i: Integer;
+  FileName, fn, sn, rn: String;
+
 begin
+  WAD := TWADEditor_1.Create();
+
+// Внешний WAD:
   if cbWADList.Text <> MsgWadSpecialMap then
-    FileName := WadsDir + DirectorySeparator + cbWADList.Text (* Resource wad *)
-  else
-    g_ProcessResourceStr(OpenedMap, FileName, sn, rn); (* Map wad *)
+    FileName := WadsDir + DirectorySeparator + cbWADList.Text
+  else // WAD карты:
+    begin
+      g_ProcessResourceStr(OpenedMap, fn, sn, rn);
+      FileName := fn;
+    end;
+
+// Читаем секции:
+  WAD.ReadFile(FileName);
+  SectionList := WAD.GetSectionList();
+  WAD.Free();
 
   cbSectionsList.Clear();
   lbResourcesList.Clear();
 
-  wad := SFSFileList(FileName);
-  if wad <> nil then
-  begin
-    for i := 0 to wad.Count - 1 do
-    begin
-      Section := win2utf(Copy(wad.Files[i].path, 1, Length(wad.Files[i].path) - 1));
-      if cbSectionsList.Items.IndexOf(Section) = -1 then
-        cbSectionsList.Items.Add(Section)
-    end;
-    wad.Destroy
-  end;
-
-  (* Update resource list (see below) *)
-  cbSectionsListChange(Sender)
+  if SectionList <> nil then
+    for i := 0 to High(SectionList) do
+      if SectionList[i] <> '' then
+        cbSectionsList.Items.Add(win2utf(SectionList[i]))
+      else
+        cbSectionsList.Items.Add('..');
 end;
 
 procedure TAddResourceForm.cbSectionsListChange(Sender: TObject);
-  var
-    wad: TSFSFileList;
-    i: Integer;
-    FileName, Section, SectionName, sn, rn: String;
+var
+  ResourceList: SArray;
+  WAD: TWADEditor_1;
+  i: DWORD;
+  FileName, SectionName, fn, sn, rn: String;
+
 begin
+  WAD := TWADEditor_1.Create();
+
+// Внешний WAD:
   if cbWADList.Text <> MsgWadSpecialMap then
-    FileName := WadsDir + DirectorySeparator + cbWADList.Text (* Resource wad *)
+    FileName := WadsDir + DirectorySeparator + cbWADList.Text
+  else // WAD карты:
+    begin
+      g_ProcessResourceStr(OpenedMap, fn, sn, rn);
+      FileName := fn;
+    end;
+
+// Читаем WAD:
+  WAD.ReadFile(FileName);
+
+  if cbSectionsList.Text <> '..' then
+    SectionName := cbSectionsList.Text
   else
-    g_ProcessResourceStr(OpenedMap, FileName, sn, rn); (* Map wad *)
+    SectionName := '';
+
+// Читаем ресурсы выбранной секции:
+  ResourceList := WAD.GetResourcesList(utf2win(SectionName));
+
+  WAD.Free();
 
-  SectionName := cbSectionsList.Text;
   lbResourcesList.Clear();
 
-  wad := SFSFileList(FileName);
-  if wad <> nil then
-  begin
-    for i := 0 to wad.Count - 1 do
-    begin
-      Section := win2utf(Copy(wad.Files[i].path, 1, Length(wad.Files[i].path) - 1));
-      if Section = SectionName then
-        lbResourcesList.Items.Add(win2utf(wad.Files[i].name))
-    end;
-    wad.Destroy
-  end;
+  if ResourceList <> nil then
+    for i := 0 to High(ResourceList) do
+      lbResourcesList.Items.Add(win2utf(ResourceList[i]));
 end;
 
 procedure TAddResourceForm.lbResourcesListClick(Sender: TObject);
-  var
-    FileName, fn: String;
+var
+  FileName, SectionName, fn: String;
+
 begin
-  FResourceSelected := (lbResourcesList.SelCount > 0) or (lbResourcesList.ItemIndex > -1);
+  FResourceSelected := (lbResourcesList.SelCount > 0) or
+                       (lbResourcesList.ItemIndex > -1);
+
   if not FResourceSelected then
   begin
     FResourceName := '';
@@ -170,18 +190,25 @@ begin
     Exit;
   end;
 
+  if cbSectionsList.Text = '..' then
+    SectionName := ''
+  else
+    SectionName := cbSectionsList.Text;
+
   if cbWADList.Text[1] <> '<' then
     FileName := cbWADList.Text
   else
     FileName := '';
 
-  FResourceName := FileName + ':' + cbSectionsList.Text + '\' + lbResourcesList.Items[lbResourcesList.ItemIndex];
+  FResourceName := FileName+':'+SectionName+'\'+lbResourcesList.Items[lbResourcesList.ItemIndex];
 
-  g_ProcessResourceStr(OpenedMap, @fn, nil, nil);
   if FileName <> '' then
     FFullResourceName := WadsDir + DirectorySeparator + FResourceName
   else
-    FFullResourceName := fn + FResourceName
+    begin
+      g_ProcessResourceStr(OpenedMap, @fn, nil, nil);
+      FFullResourceName := fn+FResourceName;
+    end;
 end;
 
 end.
index bb4fa10e2ed6a12d92c739c050b3ea0577d5367c..e7b03613ddcb20db4c4f00e523db8b1647548905 100644 (file)
@@ -31,7 +31,7 @@ var
 implementation
 
 uses
-  WADEDITOR, f_main, g_language, g_resources;
+  BinEditor, WADEDITOR, f_main, g_language;
 
 {$R *.lfm}
 
@@ -47,14 +47,23 @@ var
 
   TextureData:  Pointer;
   ImageSize:    Integer;
+  WAD:          TWADEditor_1;
   WADName:      String;
   SectionName:  String;
   ResourceName: String;
 
 begin
   Result := nil;
+
+// Загружаем ресурс текстуры из WAD:
   g_ProcessResourceStr(ResourceStr, WADName, SectionName, ResourceName);
-  g_ReadResource(WADName, SectionName, ResourceName, TextureData, ImageSize);
+
+  WAD := TWADEditor_1.Create();
+  WAD.ReadFile(WADName);
+
+  WAD.GetResource(utf2win(SectionName), utf2win(ResourceName), TextureData, ImageSize);
+
+  WAD.Free();
 
   (* !!! copypaste from f_addresource_texture.CreateBitMap *)
 
index a3d3fe15c415323c647e899b4f695c81f443f369..eaba574aa61e2d71e0f8621e5f3b0842d3442301 100644 (file)
@@ -45,7 +45,7 @@ var
 implementation
 
 uses
-  BinEditor, WADEDITOR, e_log, f_main, g_language, g_resources
+  BinEditor, WADEDITOR, e_log, f_main, g_language
 {$IFNDEF NOSOUND}, fmod, fmodtypes, fmoderrors;{$ELSE};{$ENDIF}
 
 {$R *.lfm}
@@ -121,6 +121,7 @@ end;
 
 function TAddSoundForm.CreateSoundWAD(Resource: String): Boolean;
 var
+  WAD: TWADEditor_1;
   FileName, SectionName, ResourceName: String;
   ResLength: Integer;
   sz: LongWord;
@@ -138,9 +139,11 @@ begin
 
 {$IFNDEF NOSOUND}
   g_ProcessResourceStr(Resource, FileName, SectionName, ResourceName);
-  g_ReadResource(FileName, SectionName, ResourceName, SoundData, ResLength);
 
-  if SoundData <> nil then
+  WAD := TWADEditor_1.Create;
+  WAD.ReadFile(FileName);
+
+  if WAD.GetResource(utf2win(SectionName), utf2win(ResourceName), SoundData, ResLength) then
     begin
       sz := SizeOf(FMOD_CREATESOUNDEXINFO);
       FillMemory(@soundExInfo, sz, 0);
@@ -155,16 +158,19 @@ begin
       begin
         e_WriteLog(Format('Error creating sound %s', [Resource]), MSG_WARNING);
         e_WriteLog(FMOD_ErrorString(res), MSG_WARNING);
+        WAD.Free();
         Exit;
       end;
     end
   else
     begin
       e_WriteLog(Format('Error loading sound %s', [Resource]), MSG_WARNING);
-      //e_WriteLog(Format('WAD Reader error: %s', [WAD.GetLastErrorStr]), MSG_WARNING);
+      e_WriteLog(Format('WAD Reader error: %s', [WAD.GetLastErrorStr]), MSG_WARNING);
+      WAD.Free();
       Exit;
     end;
  
+  WAD.Free();
   Result := True;
 {$ENDIF}
 end;
index 927017e6de9d6cfba12566faae8c9b5e51eb9d87..13adf3cb958db9f2fe163717b8c574df7eb5b53c 100644 (file)
@@ -49,48 +49,196 @@ implementation
 
 uses
   BinEditor, WADEDITOR, WADSTRUCT, f_main, g_textures, CONFIG, g_map,
-  g_language, e_Log, g_resources;
+  g_language;
 
 {$R *.lfm}
 
 function IsAnim(Res: String): Boolean;
-  var
-    data: Pointer;
-    len: Integer;
-    WADName, SectionName, ResourceName: String;
+var
+  WAD:          TWADEditor_1;
+  WADName:      String;
+  SectionName:  String;
+  ResourceName: String;
+  Data:         Pointer;
+  Size:         Integer;
+  Sign:         Array [0..4] of Char;
+  Sections,
+  Resources:    SArray;
+  a:            Integer;
+  ok:           Boolean;
+
 begin
+  Result := False;
+  Data := nil;
+  Size := 0;
+
+// Читаем файл и ресурс в нем:
   g_ProcessResourceStr(Res, WADName, SectionName, ResourceName);
-  (* just check file existance *)
-  g_ReadSubResource(WADName, SectionName, ResourceName, 'TEXT', 'ANIM', data, len);
-  (* TODO check section TEXTURES *)
-  Result := data <> nil;
-  if data <> nil then
-    FreeMem(data)
+
+  WAD := TWADEditor_1.Create();
+
+  if (not WAD.ReadFile(WADName)) or
+     (not WAD.GetResource(utf2win(SectionName), utf2win(ResourceName), Data, Size)) then
+  begin
+    WAD.Free();
+    Exit;
+  end;
+
+  WAD.FreeWAD();
+
+// Проверка сигнатуры. Если есть - это WAD внутри WAD:
+  CopyMemory(@Sign[0], Data, 5);
+
+  if not (Sign = DFWAD_SIGNATURE) then
+  begin
+    WAD.Free();
+    FreeMem(Data);
+    Exit;
+  end;
+
+// Пробуем прочитать данные:
+  if not WAD.ReadMemory(Data, Size) then
+  begin
+    WAD.Free();
+    FreeMem(Data);
+    Exit;
+  end;
+
+  FreeMem(Data);
+
+// Читаем секции:
+  Sections := WAD.GetSectionList();
+
+  if Sections = nil then
+  begin
+    WAD.Free();
+    Exit;
+  end;
+
+// Ищем в секциях "TEXT":
+  ok := False;
+  for a := 0 to High(Sections) do
+    if Sections[a] = 'TEXT' then
+    begin
+      ok := True;
+      Break;
+    end;
+
+// Ищем в секциях лист текстур - "TEXTURES":
+  for a := 0 to High(Sections) do
+    if Sections[a] = 'TEXTURES' then
+    begin
+      ok := ok and True;
+      Break;
+    end;
+
+  if not ok then
+  begin
+    WAD.Free();
+    Exit;
+  end;
+
+// Получаем ресурсы секции "TEXT":
+  Resources := WAD.GetResourcesList('TEXT');
+
+  if Resources = nil then
+  begin
+    WAD.Free();
+    Exit;
+  end;
+
+// Ищем в них описание анимации - "ANIM":
+  ok := False;
+  for a := 0 to High(Resources) do
+    if Resources[a] = 'ANIM' then
+    begin
+      ok := True;
+      Break;
+    end;
+
+  WAD.Free();
+
+// Если все получилось, то это аним. текстура:
+  Result := ok;
 end;
 
-function GetFrame (Res: String; var Data: Pointer; var DataLen: Integer; var Width, Height: Word): Boolean;
-  var
-    Len: Integer;
-    TextData: Pointer;
-    WADName, SectionName, ResourceName: String;
-    config: TConfig;
+function GetFrame(Res: String; var Data: Pointer; var DataLen: Integer; var Width, Height: Word): Boolean;
+var
+  AnimWAD:      Pointer;
+  WAD:          TWADEditor_1;
+  WADName:      String;
+  SectionName:  String;
+  ResourceName: String;
+  Len:          Integer;
+  config:       TConfig;
+  TextData:     Pointer;
+
 begin
-  Result := False; Data := nil; DataLen := 0; Width := 0; Height := 0;
+  Result := False;
+  AnimWAD := nil;
+  Len := 0;
+  TextData := nil;
+
+// Читаем WAD:
   g_ProcessResourceStr(Res, WADName, SectionName, ResourceName);
-  g_ReadSubResource(WADName, SectionName, ResourceName, 'TEXT', 'ANIM', TextData, Len);
-  if TextData <> nil then
+
+  WAD := TWADEditor_1.Create();
+
+  if not WAD.ReadFile(WADName) then
   begin
-    config := TConfig.CreateMem(TextData, Len);
-    g_ReadSubResource(WADName, SectionName, ResourceName, 'TEXTURES', config.ReadStr('', 'resource', ''), Data, DataLen);
-    if Data <> nil then
-    begin
-      Height := config.ReadInt('', 'frameheight', 0);
-      Width := config.ReadInt('', 'framewidth', 0);
-      Result := True
-    end;
-    config.Free();
-    FreeMem(TextData)
-  end
+    WAD.Free();
+    Exit;
+  end;
+
+// Читаем WAD-ресурс из WAD:
+  if not WAD.GetResource(utf2win(SectionName), utf2win(ResourceName), AnimWAD, Len) then
+  begin
+    WAD.Free();
+    Exit;
+  end;
+
+  WAD.FreeWAD();
+
+// Читаем WAD в WAD'е:
+  if not WAD.ReadMemory(AnimWAD, Len) then
+  begin
+    FreeMem(AnimWAD);
+    WAD.Free();
+    Exit;
+  end;
+
+// Читаем описание анимации:
+  if not WAD.GetResource('TEXT', 'ANIM', TextData, Len) then
+  begin
+    FreeMem(TextData);
+    FreeMem(AnimWAD);
+    WAD.Free();
+    Exit;
+  end;
+
+  config := TConfig.CreateMem(TextData, Len);
+
+// Читаем ресурс - лист текстур:
+  if not WAD.GetResource('TEXTURES', config.ReadStr('', 'resource', ''), Data, Len) then
+  begin
+    FreeMem(TextData);
+    FreeMem(AnimWAD);
+    WAD.Free();
+    Exit;
+  end;
+
+  DataLen := Len;
+
+  Height := config.ReadInt('', 'frameheight', 0);
+  Width := config.ReadInt('', 'framewidth', 0);
+
+  config.Free();
+  WAD.Free();
+
+  FreeMem(TextData);
+  FreeMem(AnimWAD);
+
+  Result := True;
 end;
 
 function CreateBitMap (Data: Pointer; DataSize: Cardinal): TBitMap;
@@ -144,44 +292,94 @@ begin
 end;
 
 function ShowAnim(Res: String): TBitMap;
-  var
-    Len: Integer;
-    TextData, TextureData: Pointer;
-    WADName, SectionName, ResourceName: String;
-    config: TConfig;
+var
+  AnimWAD:      Pointer;
+  WAD:          TWADEditor_1;
+  WADName:      String;
+  SectionName:  String;
+  ResourceName: String;
+  Len:          Integer;
+  config:       TConfig;
+  TextData:     Pointer;
+  TextureData:  Pointer;
+
 begin
   Result := nil;
+  AnimWAD := nil;
+  Len := 0;
+  TextData := nil;
+  TextureData := nil;
+
+// Читаем WAD файл и ресурс в нем:
   g_ProcessResourceStr(Res, WADName, SectionName, ResourceName);
-  g_ReadSubResource(WADName, SectionName, ResourceName, 'TEXT', 'ANIM', TextData, Len);
-  if TextData <> nil then
+
+  WAD := TWADEditor_1.Create();
+  WAD.ReadFile(WADName);
+  WAD.GetResource(utf2win(SectionName), utf2win(ResourceName), AnimWAD, Len);
+  WAD.FreeWAD();
+
+// Читаем описание анимации:
+  WAD.ReadMemory(AnimWAD, Len);
+  WAD.GetResource('TEXT', 'ANIM', TextData, Len);
+
+  config := TConfig.CreateMem(TextData, Len);
+
+// Читаем лист текстур:
+  WAD.GetResource('TEXTURES', config.ReadStr('', 'resource', ''), TextureData, Len);
+  NumFrames := config.ReadInt('', 'framecount', 0);
+
+  if (TextureData <> nil) and
+     (WAD.GetLastError = DFWAD_NOERROR) then
   begin
-    config := TConfig.CreateMem(TextData, Len);
-    g_ReadSubResource(WADName, SectionName, ResourceName, 'TEXTURES', config.ReadStr('', 'resource', ''), TextureData, Len);
-    if TextureData <> nil then
-    begin
-      Result := CreateBitMap(TextureData, Len);
-      (* view only first frame *)
-      NumFrames := config.ReadInt('', 'framecount', 0);
-      Result.Height := config.ReadInt('', 'frameheight', 0);
-      Result.Width := config.ReadInt('', 'framewidth', 0);
-      FreeMem(TextureData)
-    end;
-    config.Free();
-    FreeMem(TextData)
-  end
+  // Создаем BitMap из листа текстур:
+    Result := CreateBitMap(TextureData, Len);
+
+  // Размеры одного кадра - виден только первый кадр:
+    Result.Height := config.ReadInt('', 'frameheight', 0);
+    Result.Width := config.ReadInt('', 'framewidth', 0);
+  end;
+
+  config.Free();
+  WAD.Free();
+
+  FreeMem(TextureData);
+  FreeMem(TextData);
+  FreeMem(AnimWAD);
 end;
 
 function ShowTGATexture(ResourceStr: String): TBitMap;
-  var
-    Len: Integer;
-    TextureData: Pointer;
-    WADName, SectionName, ResourceName: String;
+var
+  TextureData:  Pointer;
+  WAD:          TWADEditor_1;
+  WADName:      String;
+  SectionName:  String;
+  ResourceName: String;
+  Len:          Integer;
+
 begin
   Result := nil;
+  TextureData := nil;
+  Len := 0;
+
+// Читаем WAD:
   g_ProcessResourceStr(ResourceStr, WADName, SectionName, ResourceName);
-  g_ReadResource(WADName, SectionName, ResourceName, TextureData, Len);
-  if TextureData <> nil then
-    Result := CreateBitMap(TextureData, Len)
+
+  WAD := TWADEditor_1.Create();
+  if not WAD.ReadFile(WADName) then
+  begin
+    WAD.Free();
+    Exit;
+  end;
+
+// Читаем ресурс текстуры в нем:
+  WAD.GetResource(utf2win(SectionName), utf2win(ResourceName), TextureData, Len);
+
+  WAD.Free();
+
+// Создаем на его основе BitMap:
+  Result := CreateBitMap(TextureData, Len);
+
+  FreeMem(TextureData);
 end;
 
 procedure TAddTextureForm.FormActivate(Sender: TObject);
index 07a325d4446c46f04247db73bbd093769fdd03e8..79d039a252cdbedeefd5193501b79a03c3d3dbd6 100644 (file)
@@ -746,8 +746,8 @@ object MainForm: TMainForm
     end
   end
   object OpenDialog: TOpenDialog
-    DefaultExt = '.dfz'
-    Filter = 'Карты Doom 2D: Forever (*.dfz, *.dfzip, *.zip, *.wad)|*.dfz;*.dfzip;*.zip;*.wad|Старые карты Doom 2D: Forever 0.30 (*.ini)|*.ini|Все файлы (*.*)|*.*'
+    DefaultExt = '.wad'
+    Filter = 'Карты Doom 2D: Forever (*.wad)|*.wad|Старые карты Doom 2D: Forever (*.ini)|*.ini|Все файлы (*.*)|*.*'
     Options = [ofHideReadOnly, ofNoChangeDir, ofPathMustExist, ofFileMustExist, ofEnableSizing, ofDontAddToRecent]
     Left = 32
     Top = 64
@@ -890,8 +890,8 @@ object MainForm: TMainForm
     }
   end
   object SaveDialog: TSaveDialog
-    DefaultExt = '.dfz'
-    Filter = 'Карты Doom 2D: Forever (*.dfz)|*.dfz|Карты Doom 2D: Forever (*.dfzip)|*.dfzip|Карты Doom 2D: Forever (*.zip)|*.zip|Карты Doom 2D: Forever (*.wad)|*.wad|Все файлы (*.*)|*.*'
+    DefaultExt = '.wad'
+    Filter = 'Карты Doom 2D: Forever (*.wad)|*.wad|Все файлы (*.*)|*.*'
     Options = [ofHideReadOnly, ofNoChangeDir, ofPathMustExist, ofNoReadOnlyReturn, ofEnableSizing, ofDontAddToRecent]
     Left = 64
     Top = 64
index 53bf85da742cf26b936bb5276e2694763f6bc7ea..2bd2fc6bff2a770560fb6b3fad4685fe6ea8e50d 100644 (file)
@@ -349,10 +349,10 @@ uses
   f_mapoptions, g_basic, f_about, f_mapoptimization,
   f_mapcheck, f_addresource_texture, g_textures,
   f_activationtype, f_keys, wadreader, fileutil,
-  MAPREADER, f_selectmap, f_savemap, WADEDITOR, MAPDEF,
+  MAPREADER, f_selectmap, f_savemap, WADEDITOR, WADSTRUCT, MAPDEF,
   g_map, f_saveminimap, f_addresource, CONFIG, f_packmap,
   f_addresource_sound, f_choosetype,
-  g_language, ClipBrd, g_resources, g_options;
+  g_language, ClipBrd, g_options;
 
 const
   UNDO_DELETE_PANEL   = 1;
@@ -2649,13 +2649,21 @@ var
   cwdt, chgt: Byte;
   spc: ShortInt;
   ID: DWORD;
+  wad: TWADEditor_1;
   cfgdata: Pointer;
   cfglen: Integer;
   config: TConfig;
 begin
+  cfgdata := nil;
+  cfglen := 0;
   ID := 0;
-  g_ReadResource(GameWad, 'FONTS', cfgres, cfgdata, cfglen);
-  if cfgdata <> nil then
+
+  wad := TWADEditor_1.Create;
+  if wad.ReadFile(GameWad) then
+    wad.GetResource('FONTS', cfgres, cfgdata, cfglen);
+  wad.Free();
+
+  if cfglen <> 0 then
   begin
     if not g_CreateTextureWAD('FONT_STD', GameWad + ':FONTS\' + texture) then
       e_WriteLog('ERROR ERROR ERROR', MSG_WARNING);
@@ -2666,15 +2674,14 @@ begin
     spc := Min(Max(config.ReadInt('FontMap', 'Kerning', 0), -128), 127);
 
     if g_GetTexture('FONT_STD', ID) then
-      e_TextureFontBuild(ID, FontID, cwdt, chgt, spc - 2);
+      e_TextureFontBuild(ID, FontID, cwdt, chgt, spc-2);
 
     config.Free();
-    FreeMem(cfgdata)
   end
   else
-  begin
-    e_WriteLog('Could not load FONT_STD', MSG_WARNING)
-  end
+    e_WriteLog('Could not load FONT_STD', MSG_WARNING);
+
+  if cfglen <> 0 then FreeMem(cfgdata);
 end;
 
 procedure TMainForm.FormCreate(Sender: TObject);
@@ -2822,9 +2829,6 @@ begin
   s := config.ReadStr('Editor', 'Language', '');
   gLanguage := s;
 
-  Compress := config.ReadBool('Editor', 'Compress', True);
-  Backup := config.ReadBool('Editor', 'Backup', True);
-
   TestGameMode := config.ReadStr('TestRun', 'GameMode', 'DM');
   TestLimTime := config.ReadStr('TestRun', 'LimTime', '0');
   TestLimScore := config.ReadStr('TestRun', 'LimScore', '0');
@@ -6394,47 +6398,65 @@ end;
 
 procedure TMainForm.aDeleteMap(Sender: TObject);
 var
-  res: Integer;
-  FileName: String;
-  MapName: String;
+  WAD: TWADEditor_1;
+  MapList: SArray;
+  MapName: Char16;
+  a: Integer;
+  str: String;
 begin
   OpenDialog.Filter := MsgFileFilterWad;
 
   if not OpenDialog.Execute() then
     Exit;
 
-  FileName := OpenDialog.FileName;
-  SelectMapForm.Caption := MsgCapRemove;
-  SelectMapForm.lbMapList.Items.Clear();
-  SelectMapForm.GetMaps(FileName);
+  WAD := TWADEditor_1.Create();
 
-  if SelectMapForm.ShowModal() <> mrOK then
+  if not WAD.ReadFile(OpenDialog.FileName) then
+  begin
+    WAD.Free();
     Exit;
+  end;
 
-  MapName := SelectMapForm.lbMapList.Items[SelectMapForm.lbMapList.ItemIndex];
-  if Application.MessageBox(PChar(Format(MsgMsgDeleteMapPrompt, [MapName, OpenDialog.FileName])), PChar(MsgMsgDeleteMap), MB_ICONQUESTION or MB_YESNO or MB_DEFBUTTON2) <> mrYes then
-    Exit;
+  WAD.CreateImage();
+
+  MapList := WAD.GetResourcesList('');
 
-  g_DeleteResource(FileName, '', MapName, res);
-  if res <> 0 then
+  SelectMapForm.Caption := MsgCapRemove;
+  SelectMapForm.lbMapList.Items.Clear();
+
+  if MapList <> nil then
+    for a := 0 to High(MapList) do
+      SelectMapForm.lbMapList.Items.Add(win2utf(MapList[a]));
+
+  if (SelectMapForm.ShowModal() = mrOK) then
   begin
-    Application.MessageBox(PChar('Cant delete map res=' + IntToStr(res)), PChar('Map not deleted!'), MB_ICONINFORMATION or MB_OK or MB_DEFBUTTON1);
-    Exit
-  end;
+    str := SelectMapForm.lbMapList.Items[SelectMapForm.lbMapList.ItemIndex];
+    MapName := '';
+    Move(str[1], MapName[0], Min(16, Length(str)));
+
+    if Application.MessageBox(PChar(Format(MsgMsgDeleteMapPrompt, [MapName, OpenDialog.FileName])), PChar(MsgMsgDeleteMap), MB_ICONQUESTION or MB_YESNO or MB_DEFBUTTON2) <> mrYes then
+      Exit;
+
+    WAD.RemoveResource('', utf2win(MapName));
+
+    Application.MessageBox(
+      PChar(Format(MsgMsgMapDeletedPrompt, [MapName])),
+      PChar(MsgMsgMapDeleted),
+      MB_ICONINFORMATION or MB_OK or MB_DEFBUTTON1
+    );
 
-  Application.MessageBox(
-    PChar(Format(MsgMsgMapDeletedPrompt, [MapName])),
-    PChar(MsgMsgMapDeleted),
-    MB_ICONINFORMATION or MB_OK or MB_DEFBUTTON1
-  );
+    WAD.SaveTo(OpenDialog.FileName);
 
   // Удалили текущую карту - сохранять по старому ее нельзя:
-  if OpenedMap = (FileName + ':\' + MapName) then
-  begin
-    OpenedMap := '';
-    OpenedWAD := '';
-    MainForm.Caption := FormCaption
-  end
+    if OpenedMap = (OpenDialog.FileName+':\'+MapName) then
+    begin
+      OpenedMap := '';
+      OpenedWAD := '';
+      MainForm.Caption := FormCaption;
+    end;
+  end;
+
+  WAD.Free();
 end;
 
 procedure TMainForm.vleObjectPropertyKeyDown(Sender: TObject;
index 0a3eca32107fe2c3f30ee1a89ea02cc5c2193a7b..6ae5c8e46d8a6f4f32cd9afc867a4e3bdee0f0b5 100644 (file)
 object OptionsForm: TOptionsForm
-  Left = 98
-  Height = 360
+  Left = 96
+  Height = 401
   Top = 345
-  Width = 640
+  Width = 713
   BorderIcons = [biSystemMenu]
   BorderStyle = bsSingle
   Caption = 'Настройки редактора'
-  ClientHeight = 360
-  ClientWidth = 640
+  ClientHeight = 401
+  ClientWidth = 713
   Color = clBtnFace
+  DesignTimePPI = 107
   Font.Color = clWindowText
-  Font.Height = -11
+  Font.Height = -12
   Font.Name = 'MS Sans Serif'
   OnCreate = FormCreate
   OnShow = FormShow
   Position = poScreenCenter
   LCLVersion = '2.2.4.0'
   object PageControl: TPageControl
-    Left = 8
-    Height = 312
-    Top = 8
-    Width = 624
+    Left = 9
+    Height = 348
+    Top = 9
+    Width = 696
     ActivePage = TabGeneral
     TabIndex = 0
     TabOrder = 0
     Options = [nboKeyboardTabSwitch, nboDoChangeOnSetIndex]
     object TabGeneral: TTabSheet
       Caption = 'General'
-      ClientHeight = 284
-      ClientWidth = 620
+      ClientHeight = 319
+      ClientWidth = 692
       object cbShowDots: TCheckBox
-        Left = 8
+        Left = 9
         Height = 21
-        Top = 8
-        Width = 128
+        Top = 9
+        Width = 135
         Caption = 'Показывать сетку'
         TabOrder = 0
       end
       object cbShowTexture: TCheckBox
-        Left = 8
+        Left = 9
         Height = 21
-        Top = 32
-        Width = 193
+        Top = 36
+        Width = 207
         Caption = 'Показывать текстуру панели'
         TabOrder = 1
       end
       object cbShowSize: TCheckBox
-        Left = 8
+        Left = 9
         Height = 21
-        Top = 56
-        Width = 191
+        Top = 62
+        Width = 204
         Caption = 'Показывать размеры панели'
         TabOrder = 2
       end
       object cbCheckerboard: TCheckBox
-        Left = 8
+        Left = 9
         Height = 21
-        Top = 80
-        Width = 164
+        Top = 89
+        Width = 176
         Caption = 'Использовать шахматку'
         Checked = True
         State = cbChecked
         TabOrder = 3
       end
       object LabelGrid: TLabel
-        Left = 8
-        Height = 14
-        Top = 112
-        Width = 72
+        Left = 9
+        Height = 15
+        Top = 125
+        Width = 78
         Caption = 'Шаги сетки:'
         ParentColor = False
       end
       object SpinEdit1: TSpinEdit
-        Left = 8
-        Height = 22
-        Top = 128
-        Width = 50
+        Left = 9
+        Height = 23
+        Top = 143
+        Width = 56
         MaxValue = 2048
         MinValue = 4
         TabOrder = 4
         Value = 16
       end
       object SpinEdit2: TSpinEdit
-        Left = 8
-        Height = 22
-        Top = 152
-        Width = 50
+        Left = 9
+        Height = 23
+        Top = 169
+        Width = 56
         MaxValue = 2048
         MinValue = 4
         TabOrder = 5
         Value = 8
       end
       object LabelGridSize: TLabel
-        Left = 8
-        Height = 14
-        Top = 184
-        Width = 118
+        Left = 9
+        Height = 15
+        Top = 205
+        Width = 127
         Caption = 'Размер точек сетки:'
         ParentColor = False
         WordWrap = True
       end
       object SpinEdit4: TSpinEdit
-        Left = 8
-        Height = 22
-        Top = 200
-        Width = 50
+        Left = 9
+        Height = 23
+        Top = 223
+        Width = 56
         MaxValue = 2
         MinValue = 1
         TabOrder = 6
         Value = 1
       end
       object LabelMinimap: TLabel
-        Left = 8
-        Height = 14
-        Top = 232
-        Width = 128
+        Left = 9
+        Height = 15
+        Top = 259
+        Width = 139
         Caption = 'Масштаб мини-карты:'
         ParentColor = False
       end
       object SpinEdit5: TSpinEdit
-        Left = 8
-        Height = 22
-        Top = 248
-        Width = 50
+        Left = 9
+        Height = 23
+        Top = 276
+        Width = 56
         MaxValue = 10
         MinValue = 1
         TabOrder = 7
         Value = 1
       end
       object LabelGridCol: TLabel
-        Left = 304
-        Height = 14
-        Top = 8
-        Width = 68
+        Left = 339
+        Height = 15
+        Top = 9
+        Width = 74
         Caption = 'Цвет сетки:'
         ParentColor = False
       end
       object ColorButton1: TColorButton
-        Left = 304
-        Height = 25
-        Top = 24
-        Width = 75
+        Left = 339
+        Height = 28
+        Top = 27
+        Width = 84
         BorderWidth = 2
         ButtonColorSize = 16
         ButtonColor = clRed
       end
       object LabelBack: TLabel
-        Left = 304
-        Height = 14
-        Top = 64
-        Width = 65
+        Left = 339
+        Height = 15
+        Top = 71
+        Width = 70
         Caption = 'Цвет фона:'
         ParentColor = False
       end
       object ColorButton2: TColorButton
-        Left = 304
-        Height = 25
-        Top = 80
-        Width = 75
+        Left = 339
+        Height = 28
+        Top = 89
+        Width = 84
         BorderWidth = 2
         ButtonColorSize = 16
         ButtonColor = clLime
       end
       object LabelPreview: TLabel
-        Left = 304
-        Height = 14
-        Top = 120
-        Width = 248
+        Left = 339
+        Height = 15
+        Top = 134
+        Width = 270
         Caption = 'Цвет фона поля предпросмотра текстуры:'
         ParentColor = False
         WordWrap = True
       end
       object ColorButton3: TColorButton
-        Left = 304
-        Height = 25
-        Top = 136
-        Width = 75
+        Left = 339
+        Height = 28
+        Top = 152
+        Width = 84
         BorderWidth = 2
         ButtonColorSize = 16
         ButtonColor = clBlue
       end
       object LabelLanguage: TLabel
-        Left = 304
-        Height = 14
-        Top = 172
+        Left = 339
+        Height = 15
+        Top = 192
         Width = 34
         Caption = 'Язык:'
         ParentColor = False
       end
       object cbLanguage: TComboBox
-        Left = 304
-        Height = 26
-        Top = 192
-        Width = 120
+        Left = 339
+        Height = 27
+        Top = 214
+        Width = 134
         ItemHeight = 0
         Style = csDropDownList
         TabOrder = 8
@@ -200,227 +201,211 @@ object OptionsForm: TOptionsForm
     end
     object TabFiles: TTabSheet
       Caption = 'Files'
-      ClientHeight = 284
-      ClientWidth = 620
-      object cbCompress: TCheckBox
-        Left = 8
-        Height = 21
-        Top = 8
-        Width = 208
-        Caption = 'Сжимать архив при сохранении'
-        TabOrder = 0
-      end
-      object cbBackup: TCheckBox
-        Left = 8
-        Height = 21
-        Top = 32
-        Width = 218
-        Caption = 'Резервная копия при сохранении'
-        TabOrder = 1
-      end
+      ClientHeight = 319
+      ClientWidth = 692
       object LabelRecent: TLabel
-        Left = 8
-        Height = 14
-        Top = 64
-        Width = 230
+        Left = 9
+        Height = 15
+        Top = 8
+        Width = 250
         Caption = 'Запоминать последних открытых карт:'
         ParentColor = False
         WordWrap = True
       end
       object SpinEdit3: TSpinEdit
-        Left = 8
-        Height = 22
-        Top = 80
-        Width = 50
+        Left = 9
+        Height = 23
+        Top = 32
+        Width = 56
         MaxValue = 10
         MinValue = 2
-        TabOrder = 2
+        TabOrder = 0
         Value = 2
       end
     end
     object TabTesting: TTabSheet
       Caption = 'Testing'
-      ClientHeight = 284
-      ClientWidth = 620
+      ClientHeight = 319
+      ClientWidth = 692
       object LabelPath: TLabel
-        Left = 8
-        Height = 14
-        Top = 8
-        Width = 120
+        Left = 9
+        Height = 15
+        Top = 9
+        Width = 131
         Caption = 'Путь к Doom2DF.exe:'
         ParentColor = False
       end
       object ExeEdit: TFileNameEdit
-        Left = 8
-        Height = 22
-        Top = 24
-        Width = 328
+        Left = 9
+        Height = 23
+        Top = 27
+        Width = 366
         FileName = 'Doom2DF.exe'
         DialogOptions = [ofNoChangeDir, ofDontAddToRecent, ofViewDetail]
         FilterIndex = 0
         HideDirectories = False
-        ButtonWidth = 23
+        ButtonWidth = 26
         NumGlyphs = 1
         MaxLength = 0
         TabOrder = 0
         Text = 'Doom2DF.exe'
       end
       object LabelArgs: TLabel
-        Left = 8
-        Height = 14
-        Top = 55
-        Width = 120
+        Left = 9
+        Height = 15
+        Top = 61
+        Width = 128
         Caption = 'Параметры запуска:'
         ParentColor = False
       end
       object edD2DArgs: TEdit
-        Left = 8
-        Height = 22
-        Top = 72
-        Width = 301
+        Left = 9
+        Height = 23
+        Top = 80
+        Width = 335
         TabOrder = 1
       end
       object rbDM: TRadioButton
-        Left = 8
+        Left = 9
         Height = 21
-        Top = 104
-        Width = 91
+        Top = 116
+        Width = 98
         Caption = 'Deathmatch'
         Checked = True
         TabOrder = 2
         TabStop = True
       end
       object rbTDM: TRadioButton
-        Left = 8
+        Left = 9
         Height = 21
-        Top = 120
-        Width = 124
+        Top = 134
+        Width = 133
         Caption = 'Team Deathmatch'
         TabOrder = 3
       end
       object rbCTF: TRadioButton
-        Left = 8
+        Left = 9
         Height = 21
-        Top = 136
-        Width = 114
+        Top = 152
+        Width = 124
         Caption = 'Capture the Flag'
         TabOrder = 4
       end
       object rbCOOP: TRadioButton
-        Left = 8
+        Left = 9
         Height = 21
-        Top = 152
-        Width = 92
+        Top = 169
+        Width = 94
         Caption = 'Cooperative'
         TabOrder = 5
       end
       object cbTwoPlayers: TCheckBox
-        Left = 168
+        Left = 187
         Height = 21
-        Top = 104
-        Width = 89
+        Top = 116
+        Width = 93
         Caption = 'Два игрока'
         TabOrder = 6
       end
       object cbTeamDamage: TCheckBox
-        Left = 168
+        Left = 187
         Height = 21
-        Top = 120
-        Width = 141
+        Top = 134
+        Width = 150
         Caption = 'Урон своей команде'
         TabOrder = 7
       end
       object cbAllowExit: TCheckBox
-        Left = 168
+        Left = 187
         Height = 21
-        Top = 136
-        Width = 122
+        Top = 152
+        Width = 128
         Caption = 'Выход из уровня'
         Checked = True
         State = cbChecked
         TabOrder = 8
       end
       object cbWeaponStay: TCheckBox
-        Left = 168
+        Left = 187
         Height = 21
-        Top = 152
-        Width = 125
+        Top = 169
+        Width = 133
         Caption = 'Оружие остается'
         TabOrder = 9
       end
       object cbMonstersDM: TCheckBox
-        Left = 168
+        Left = 187
         Height = 21
-        Top = 168
-        Width = 103
+        Top = 187
+        Width = 113
         Caption = 'Монстры в DM'
         TabOrder = 10
       end
       object LabelTime: TLabel
-        Left = 8
-        Height = 14
-        Top = 200
-        Width = 92
+        Left = 9
+        Height = 15
+        Top = 223
+        Width = 103
         Caption = 'Лимит времени:'
         ParentColor = False
       end
       object edTime: TEdit
-        Left = 120
-        Height = 22
-        Top = 200
-        Width = 49
+        Left = 134
+        Height = 23
+        Top = 223
+        Width = 55
         TabOrder = 11
         Text = '0'
       end
       object LabelSecs: TLabel
-        Left = 174
-        Height = 14
-        Top = 200
-        Width = 42
+        Left = 194
+        Height = 15
+        Top = 223
+        Width = 44
         Caption = 'секунд'
         ParentColor = False
       end
       object LabelScore: TLabel
-        Left = 8
-        Height = 14
-        Top = 223
-        Width = 76
+        Left = 9
+        Height = 15
+        Top = 249
+        Width = 84
         Caption = 'Лимит очков:'
         ParentColor = False
       end
       object edScore: TEdit
-        Left = 120
-        Height = 22
-        Top = 223
-        Width = 49
+        Left = 134
+        Height = 23
+        Top = 249
+        Width = 55
         TabOrder = 12
         Text = '0'
       end
       object cbMapOnce: TCheckBox
-        Left = 8
+        Left = 9
         Height = 21
-        Top = 256
-        Width = 241
+        Top = 285
+        Width = 259
         Caption = 'Закрыть игру после выхода из карты'
         TabOrder = 13
       end
     end
   end
   object bOK: TButton
-    Left = 464
-    Height = 25
-    Top = 328
-    Width = 75
+    Left = 517
+    Height = 28
+    Top = 366
+    Width = 84
     Caption = 'ОК'
     Default = True
     OnClick = bOKClick
     TabOrder = 1
   end
   object bCancel: TButton
-    Left = 557
-    Height = 25
-    Top = 328
-    Width = 75
+    Left = 621
+    Height = 28
+    Top = 366
+    Width = 84
     Cancel = True
     Caption = 'Отмена'
     OnClick = bCancelClick
index fe05e20f2e5a964b59a9d6b649a34c5079f0aa46..ebb38cb3a6dae85cb4194bb7c5e29b46fb060676 100644 (file)
@@ -10,16 +10,11 @@ uses
   ExtCtrls, ComCtrls, ActnList, Spin, EditBtn, Registry, Math, Types;
 
 type
-
-  { TOptionsForm }
-
   TOptionsForm = class (TForm)
     bOK: TButton;
     bCancel: TButton;
     cbAllowExit: TCheckBox;
-    cbBackup: TCheckBox;
     cbCheckerboard: TCheckBox;
-    cbCompress: TCheckBox;
     cbLanguage: TComboBox;
     cbMapOnce: TCheckBox;
     cbMonstersDM: TCheckBox;
@@ -77,7 +72,7 @@ procedure RegisterFileType(ext: String; FileName: String);
 implementation
 
 uses
-  LazFileUtils, f_main, StdConvs, CONFIG, g_language, g_resources, g_options;
+  LazFileUtils, f_main, StdConvs, CONFIG, g_language, g_options;
 
 {$R *.lfm}
 
@@ -136,8 +131,6 @@ begin
   end;
 
   // Files Tab:
-  cbCompress.Checked := Compress;
-  cbBackup.Checked   := Backup;
   SpinEdit3.Value    := RecentCount;
 
   // Testing Tab:
@@ -194,8 +187,6 @@ begin
 
   // Files tab:
   re := SpinEdit3.Value;
-  Compress := cbCompress.Checked;
-  Backup := cbBackup.Checked;
 
   // Testing tab:
   TestD2DExe  := ExeEdit.Text;
@@ -240,8 +231,6 @@ begin
   config.WriteStr('Editor', 'Language', gLanguage);
 
   config.WriteInt('Editor', 'RecentCount', re);
-  config.WriteBool('Editor', 'Compress', Compress);
-  config.WriteBool('Editor', 'Backup', Backup);
 
   config.WriteStr('TestRun', 'GameMode', TestGameMode);
   config.WriteStr('TestRun', 'LimTime', TestLimTime);
index 9a6db34d007a2902144bf7ee62ef9aaefbe7c791..2a275ebfac3801352933a77a79652461c68b2d22 100644 (file)
@@ -177,8 +177,8 @@ object PackMapForm: TPackMapForm
     TabOrder = 1
   end
   object SaveDialog: TSaveDialog
-    DefaultExt = '.dfz'
-    Filter = 'Карты Doom 2D: Forever (*.dfz)|*.dfz|Карты Doom 2D: Forever (*.dfzip)|*.dfzip|Карты Doom 2D: Forever (*.zip)|*.zip|Карты Doom2D: Forever (*.wad)|*.wad|All files (*.*)|*.*'
+    DefaultExt = '.wad'
+    Filter = 'Карты Doom2D: Forever (*.wad)|*.wad|All files (*.*)|*.*'
     Options = [ofHideReadOnly, ofPathMustExist, ofEnableSizing, ofDontAddToRecent]
     left = 8
     top = 200
index f06f5c8c6b15e7715131a600304fc5abf2f1c7b3..911700b7df8a5f18cf6aaee493d5cd6371b4546b 100644 (file)
@@ -53,7 +53,7 @@ implementation
 
 uses
   BinEditor, WADEDITOR, g_map, MAPREADER, MAPWRITER, MAPSTRUCT,
-  f_main, math, g_language, g_resources, g_options, e_log;
+  f_main, math, g_language, g_options, e_log;
 
 {$R *.lfm}
 
@@ -70,43 +70,66 @@ begin
     eWAD.Text := SaveDialog.FileName;
 end;
 
-function ProcessResource(wad_to, section_to, filename, section, resource: String): Boolean;
-  var
-    data: Pointer;
-    res, len: Integer;
-    us, un: String;
+function ProcessResource(wad_to: TWADEditor_1; section_to, filename, section, resource: String): Boolean;
+var
+  wad2: TWADEditor_1;
+  data: Pointer;
+  reslen: Integer;
+  //s: string;
+
 begin
-  Result := True;
+  Result := False;
+
   if filename = '' then
-    g_GetResourceSection(OpenedMap, filename, us, un)
+    g_ProcessResourceStr(OpenedMap, @filename, nil, nil)
   else
     filename := WadsDir + DirectorySeparator + filename;
-  e_WriteLog('ProcessResource: "' + wad_to + '" "' + section_to + '" "' + filename + '" "' + section + '" "' + resource + '"', MSG_NOTIFY);
 
-  if resource = '' then Exit;
+// Читаем ресурс из WAD-файла карты или какого-то другого:
+  wad2 := TWADEditor_1.Create();
 
-  g_ReadResource(filename, section, resource, data, len);
-  if data <> nil then
+  if not wad2.ReadFile(filename) then
   begin
-    (* Write resource only if it does not exists *)
-    g_ExistsResource(wad_to, section_to, resource, res);
-    if res <> 0 then
-    begin
-      g_AddResource(wad_to, section_to, resource, data, len, res);
-      ASSERT(res = 0)
-    end;
-    FreeMem(data);
-  end
-  else
+    Application.MessageBox(PChar(Format(MsgMsgWadError, [ExtractFileName(filename)])), PChar(MsgMsgError), MB_OK + MB_ICONERROR);
+    wad2.Free();
+    Exit;
+  end;
+
+  if not wad2.GetResource(utf2win(section), utf2win(resource), data, reslen) then
   begin
-    //Application.MessageBox(PChar(Format(MsgMsgWadError, [ExtractFileName(filename)])), PChar(MsgMsgError), MB_OK + MB_ICONERROR);
     Application.MessageBox(PChar(Format(MsgMsgResError, [filename, section, resource])), PChar(MsgMsgError), MB_OK + MB_ICONERROR);
-    Result := False
-  end
+    wad2.Free();
+    Exit;
+  end;
+
+  wad2.Free();
+
+ {if wad_to.HaveResource(utf2win(section_to), utf2win(resource)) then
+ begin
+  for a := 2 to 256 do
+  begin
+   s := IntToStr(a);
+   if not wad_to.HaveResource(utf2win(section_to), utf2win(resource+s)) then Break;
+  end;
+  resource := resource+s;
+ end;}
+
+// Если такого ресурса нет в WAD-файле-назначении, то копируем:
+  if not wad_to.HaveResource(utf2win(section_to), utf2win(resource)) then
+  begin
+    if not wad_to.HaveSection(utf2win(section_to)) then
+      wad_to.AddSection(utf2win(section_to));
+    wad_to.AddResource(data, reslen, utf2win(resource), utf2win(section_to));
+  end;
+
+  FreeMem(data);
+
+  Result := True;
 end;
 
 procedure TPackMapForm.bPackClick(Sender: TObject);
 var
+  WAD: TWADEditor_1;
   mr: TMapReader_1;
   mw: TMapWriter_1;
   data: Pointer;
@@ -131,8 +154,12 @@ begin
   if data = nil then
     Exit;
 
-  if not cbAdd.Checked then
-    g_DeleteFile(eWAD.Text, '.bak0');
+  WAD := TWADEditor_1.Create();
+
+// Не перезаписывать WAD, а дополнить:
+  if cbAdd.Checked then
+    if WAD.ReadFile(eWAD.Text) then
+      WAD.CreateImage();
 
 // Читаем карту из памяти:
   mr := TMapReader_1.Create();
@@ -150,7 +177,7 @@ begin
       if IsSpecialTexture(res) then
         Continue;
 
-      g_GetResourceSection(res, filename, section, resource);
+      g_ProcessResourceStr(res, @filename, @section, @resource);
 
     // Не записывать стандартные текстуры:
       if (not cbNonStandart.Checked) or
@@ -158,9 +185,10 @@ begin
            (AnsiLowerCase(filename) <> SHRSHADE_WAD) ) then
       begin
       // Копируем ресурс текстуры:
-        if not f_packmap.ProcessResource(eWAD.Text, tsection, filename, section, resource) then
+        if not f_packmap.ProcessResource(WAD, tsection, filename, section, resource) then
         begin
           mr.Free();
+          WAD.Free();
           Exit;
         end;
 
@@ -178,7 +206,7 @@ begin
   if cbSky.Checked then
   begin
     res := win2utf(header.SkyName);
-    g_GetResourceSection(res, filename, section, resource);
+    g_ProcessResourceStr(res, @filename, @section, @resource);
 
   // Не записывать стандартное небо:
     if (not cbNonStandart.Checked) or
@@ -186,9 +214,10 @@ begin
          (AnsiLowerCase(filename) <> SHRSHADE_WAD) ) then
     begin
     // Копируем ресурс неба:
-      if not f_packmap.ProcessResource(eWAD.Text, ssection, filename, section, resource) then
+      if not f_packmap.ProcessResource(WAD, ssection, filename, section, resource) then
       begin
         mr.Free();
+        WAD.Free();
         Exit;
       end;
 
@@ -203,7 +232,7 @@ begin
   if cbMusic.Checked then
   begin
     res := win2utf(header.MusicName);
-    g_GetResourceSection(res, filename, section, resource);
+    g_ProcessResourceStr(res, @filename, @section, @resource);
 
   // Не записывать стандартную музыку:
     if (not cbNonStandart.Checked) or
@@ -211,9 +240,10 @@ begin
          (AnsiLowerCase(filename) <> SHRSHADE_WAD) ) then
     begin
     // Копируем ресурс музыки:
-      if not f_packmap.ProcessResource(eWAD.Text, msection, filename, section, resource) then
+      if not f_packmap.ProcessResource(WAD, msection, filename, section, resource) then
       begin
         mr.Free();
+        WAD.Free();
         Exit;
       end;
 
@@ -263,7 +293,7 @@ begin
           if res = '' then
             Break;
 
-          g_GetResourceSection(res, @filename, @section, @resource);
+          g_ProcessResourceStr(res, @filename, @section, @resource);
 
         // Не записывать стандартные дополнительные текстуры:
           if (not cbNonStandart.Checked) or
@@ -271,7 +301,7 @@ begin
                (AnsiLowerCase(filename) <> SHRSHADE_WAD) ) then
           begin
           // Копируем ресурс дополнительной текстуры:
-            if f_packmap.ProcessResource(eWAD.Text, tsection, filename, section, resource) then
+            if f_packmap.ProcessResource(WAD, tsection, filename, section, resource) then
             begin
 
               Нужно проверять есть такая текстура textures и есть ли она вообще?
@@ -303,13 +333,15 @@ begin
 
 // Сохраняем карту из памяти под новым именем в WAD-файл:
   len := mw.SaveMap(data);
-  g_AddResource(eWAD.Text, '', eResource.Text, data, len, a);
+  WAD.AddResource(data, len, eResource.Text, '');
+  WAD.SaveTo(eWAD.Text);
+
   mw.Free();
   mr.Free();
-  Close();
+  WAD.Free();
 
-  ASSERT(a = 0); (* saved *)
   MessageDlg(Format(MsgMsgPacked, [eResource.Text, ExtractFileName(eWAD.Text)]), mtInformation, [mbOK], 0);
+  Close();
 end;
 
 procedure TPackMapForm.FormCreate(Sender: TObject);
index a508c24bc2a2240d477dab6a5e650c4b6c041c84..e57fb791d4887b01b223c56fbdf76f60f597a429 100644 (file)
@@ -35,7 +35,7 @@ var
 implementation
 
 uses
-  MAPREADER, MAPSTRUCT, g_language, g_resources, sfs;
+  BinEditor, MAPREADER, WADEDITOR, WADSTRUCT, MAPSTRUCT, g_language;
 
 {$R *.lfm}
 
@@ -85,64 +85,67 @@ begin
 end;
 
 procedure TSaveMapForm.GetMaps(FileName: String; placeName: Boolean);
-  var
-    nm: String;
-    data: Pbyte;
-    list: TSFSFileList;
-    i, j, len, max_num: Integer;
-    sign: Array [0..2] of Char;
+var
+  WAD: TWADEditor_1;
+  a, max_num, j: Integer;
+  ResList: SArray;
+  Data: Pointer;
+  Len: Integer;
+  Sign: Array [0..2] of Char;
+  nm: String;
+
 begin
   lbMapList.Items.Clear();
   max_num := 1;
 
-  list := SFSFileList(FileName);
-  if list <> nil then
-  begin
-    for i := 0 to list.Count - 1 do
+  WAD := TWADEditor_1.Create();
+  WAD.ReadFile(FileName);
+  ResList := WAD.GetResourcesList('');
+
+  if ResList <> nil then
+    for a := 0 to High(ResList) do
     begin
-      g_ReadResource(FileName, win2utf(list.Files[i].path), win2utf(list.Files[i].name), data, len);
+      if not WAD.GetResource('', ResList[a], Data, Len) then
+        Continue;
+
+      CopyMemory(@Sign[0], Data, 3);
+      FreeMem(Data);
 
-      if len >= 3 then
+      if Sign = MAP_SIGNATURE then
       begin
-        sign[0] := chr(data[0]);
-        sign[1] := chr(data[1]);
-        sign[2] := chr(data[2]);
-        if sign = MAP_SIGNATURE then
+        nm := win2utf(ResList[a]);
+        lbMapList.Items.Add(nm);
+
+        if placeName then
         begin
-          nm := win2utf(list.Files[i].name);
-          lbMapList.Items.Add(nm);
-          if placeName then
+          nm := UpperCase(nm);
+          if (nm[1] = 'M') and
+             (nm[2] = 'A') and
+             (nm[3] = 'P') then
           begin
-            nm := UpperCase(nm);
-            if (nm[1] = 'M') and (nm[2] = 'A') and (nm[3] = 'P') then
-            begin
-              nm := Trim(Copy(nm, 4, Length(nm)-3));
-              j := StrToIntDef(nm, 0);
-              if j >= max_num then
-                max_num := j + 1;
-            end
-          end
-        end
+            nm := Trim(Copy(nm, 4, Length(nm)-3));
+            j := StrToIntDef(nm, 0);
+            if j >= max_num then
+              max_num := j + 1;
+          end;
+        end;
       end;
 
-      if len > 0 then FreeMem(data)
+      Sign := '';
     end;
 
-    list.Destroy;
-  end;
-
+  WAD.Free();
 
   if placeName then
-  begin
-    nm := IntToStr(max_num);
-    if Length(nm) < 2 then
-      nm := '0' + nm;
-    eMapName.Text := 'MAP' + nm
-  end
+    begin
+      nm := IntToStr(max_num);
+      if Length(nm) < 2 then
+        nm := '0' + nm;
+      nm := 'MAP' + nm;
+      eMapName.Text := nm;
+    end
   else
-  begin
-    eMapName.Text := ''
-  end
+    eMapName.Text := '';
 end;
 
 end.
index 7474330705f0c70b96027895375359b9a5cffbed..46f365c7f6cfe4c9bf0a50ebc6046f559d8c9f69 100644 (file)
@@ -32,7 +32,7 @@ var
 implementation
 
 uses
-  MAPREADER, MAPSTRUCT, g_resources, sfs;
+  BinEditor, MAPREADER, WADEDITOR, WADSTRUCT, MAPSTRUCT;
 
 {$R *.lfm}
 
@@ -54,34 +54,41 @@ begin
 end;
 
 procedure TSelectMapForm.GetMaps(FileName: String);
-  var
-    data: PByte;
-    list: TSFSFileList;
-    sign: Array [0..2] of Char;
-    i, len: Integer;
+var
+  WAD: TWADEditor_1;
+  a: Integer;
+  ResList: SArray;
+  Data: Pointer;
+  Len: Integer;
+  Sign: Array [0..2] of Char;
+
 begin
   lbMapList.Items.Clear();
 
-  list := SFSFileList(FileName);
-  if list = nil then Exit;
-
-  for i := 0 to list.Count - 1 do
+  WAD := TWADEditor_1.Create();
+  if not WAD.ReadFile(FileName) then
   begin
-    g_ReadResource(FileName, win2utf(list.Files[i].path), win2utf(list.Files[i].name), data, len);
+    WAD.Free();
+    Exit;
+  end;
+
+  ResList := WAD.GetResourcesList('');
 
-    if len >= 3 then
+  if ResList <> nil then
+    for a := 0 to High(ResList) do
     begin
-      sign[0] := chr(data[0]);
-      sign[1] := chr(data[1]);
-      sign[2] := chr(data[2]);
-      if sign = MAP_SIGNATURE then
-        lbMapList.Items.Add(win2utf(list.Files[i].name))
-    end;
+      if not WAD.GetResource('', ResList[a], Data, Len) then
+        Continue;
 
-    if len > 0 then FreeMem(data)
-  end;
+      CopyMemory(@Sign[0], Data, 3);
+      FreeMem(Data);
+
+      if Sign = MAP_SIGNATURE then
+        lbMapList.Items.Add(win2utf(ResList[a]));
+      Sign := '';
+    end;
 
-  list.Destroy
+  WAD.Free();
 end;
 
 end.
index 9edd8876eb39be6b19a3ce26f485ab441cff7590..ea1f23660237da8593209739dc0200d84287843f 100644 (file)
@@ -564,8 +564,6 @@ Interface
     MsgLabEsLanguageAuto = 'System Default';
 
     MsgCtrlEsFiles = 'Files';
-    MsgLabEsCompress = 'Compress archive when save';
-    MsgLabEsBackup = 'Make backup before save';
 
     MsgLabPackSaveTo = 'Save to:';
     MsgLabPackMapName = 'Map Resource Name:';
@@ -629,8 +627,8 @@ Interface
     MsgWadSpecialMap = '<MAP WAD-FILE>';
     MsgWadSpecialTexs = '<EXTRA TEXTURES>';
 
-    MsgFileFilterAll = 'Doom 2D: Forever Maps (*.dfz, *.dfzip, *.zip, *.wad)|*.dfz;*.dfzip;*.zip;*.wad|Doom 2D: Forever 0.30 Maps (*.ini)|*.ini|All Files (*.*)|*.*';
-    MsgFileFilterWad = 'Doom 2D: Forever Maps (*.dfz)|*.dfz|Doom 2D: Forever Maps (*.dfzip)|*.dfzip|Doom 2D: Forever Maps (*.zip)|*.zip|Doom 2D: Forever Maps (*.wad)|*.wad|All Files (*.*)|*.*';
+    MsgFileFilterAll = 'Doom 2D: Forever Maps (*.wad)|*.wad|Doom 2D: Forever 0.30 Maps (*.ini)|*.ini|All Files (*.*)|*.*';
+    MsgFileFilterWad = 'Doom 2D: Forever Maps (*.wad)|*.wad|All Files (*.*)|*.*';
     MsgFileFilterExeMac = 'Doom 2D Forever.app|*.app|Doom 2D Forever (Unix Executable)|Doom2DF;*';
     MsgFileFilterExeWin = 'Doom2DF.exe|Doom2DF.exe;*.exe';
     MsgFileFilterExeUnix = 'Doom2DF|Doom2DF;*';
@@ -1175,8 +1173,6 @@ begin
     LabelLanguage.Caption := MsgLabEsLanguage;
   // TabFiles:
     TabFiles.Caption := MsgCtrlEsFiles;
-    cbCompress.Caption := MsgLabEsCompress;
-    cbBackup.Caption := MsgLabEsBackup;
     LabelRecent.Caption := MsgLabEsRecent;
   // TabTesting:
     TabTesting.Caption := MsgCtrlEsTesting;
index 0c35d481384f5164798248af97a007c9244106b7..a93a75af45ac2396fb8f48f8849aeb24d50824eb 100644 (file)
@@ -246,7 +246,7 @@ implementation
 
 uses
   BinEditor, g_textures, Dialogs, SysUtils, CONFIG, f_main,
-  Forms, Math, f_addresource_texture, WADEDITOR, g_language, g_resources, g_options;
+  Forms, Math, f_addresource_texture, WADEDITOR, g_language, g_options;
 
 const
   OLD_ITEM_MEDKIT_SMALL          = 1;
@@ -1053,6 +1053,7 @@ end;
 
 function SaveMap(Res: String): Pointer;
 var
+  WAD: TWADEditor_1;
   MapWriter: TMapWriter_1;
   textures: TTexturesRec1Array;
   panels: TPanelsRec1Array;
@@ -1070,6 +1071,7 @@ var
   Len: LongWord;
 
 begin
+  WAD := nil;
   textures := nil;
   panels := nil;
   items := nil;
@@ -1081,6 +1083,17 @@ begin
   Data := nil;
   Len := 0;
 
+// Открываем WAD, если надо:
+  if Res <> '' then
+  begin
+    WAD := TWADEditor_1.Create();
+    g_ProcessResourceStr(Res, FileName, SectionName, ResName);
+    if not WAD.ReadFile(FileName) then
+      WAD.FreeWAD();
+
+    WAD.CreateImage();
+  end;
+
   MapWriter := TMapWriter_1.Create();
 
 // Сохраняем заголовок:
@@ -1337,17 +1350,19 @@ begin
 
 // Записываем в WAD, если надо:
   if Res <> '' then
-  begin
-    g_ProcessResourceStr(Res, FileName, SectionName, ResName);
-    g_AddResource(FileName, SectionName, ResName, Data, Len, a);
-    ASSERT(a = 0);
-    FreeMem(Data);
-    Result := nil
-  end
+    begin
+      s := utf2win(ResName);
+      WAD.RemoveResource('', s);
+      WAD.AddResource(Data, Len, s, '');
+      WAD.SaveTo(FileName);
+
+      FreeMem(Data);
+      WAD.Free();
+
+      Result := nil;
+    end
   else
-  begin
-    Result := Data
-  end
+    Result := Data;
 end;
 
 procedure AddTexture(res: String; Error: Boolean);
@@ -1368,6 +1383,7 @@ end;
 
 function LoadMap(Res: String): Boolean;
 var
+  WAD: TWADEditor_1;
   MapReader: TMapReader_1;
   Header: TMapHeaderRec_1;
   textures: TTexturesRec1Array;
@@ -1407,10 +1423,24 @@ begin
   MainForm.lLoad.Caption := MsgLoadWad;
   Application.ProcessMessages();
 
-// Читаем ресурс карты
+// Открываем WAD:
+  WAD := TWADEditor_1.Create();
   g_ProcessResourceStr(Res, FileName, SectionName, ResName);
-  g_ReadResource(FileName, SectionName, ResName, pData, Len);
-  if pData = nil then Exit;
+
+  if not WAD.ReadFile(FileName) then
+  begin
+    WAD.Free();
+    Exit;
+  end;
+
+// Читаем ресурс карты:
+  if not WAD.GetResource('', utf2win(ResName), pData, Len) then
+  begin
+    WAD.Free();
+    Exit;
+  end;
+
+  WAD.Free();
 
   MapReader := TMapReader_1.Create();
 
diff --git a/src/editor/g_resources.pas b/src/editor/g_resources.pas
deleted file mode 100644 (file)
index 85a7835..0000000
+++ /dev/null
@@ -1,413 +0,0 @@
-{$ASSERTIONS ON}
-unit g_resources;
-
-interface
-
-  (**
-    g_GetResourceSection
-      Parse path in form 'path/to/file.wad:some/section/resouce' to
-      wad = 'path/to/file.wad', section = 'some/section', name = 'resource'
-
-    g_DeleteFile
-      Delete file if it exists. Make backup if enabled.
-      return true when file not exists.
-
-    g_ReadResource
-      Read whole file from wad
-      (data <> nil) and (len > 0) when ok
-      use FreeMem(data) when done
-
-    g_ReadSubResource
-      Read whole file from folded wad
-      (data <> nil) and (len > 0) when ok
-      use FreeMem(data) when done
-
-    g_DeleteResource
-      Delete file from wad
-      res = 0 when ok
-
-    g_AddResource
-      Add/overwrite file to wad
-      res = 0 when ok
-
-    g_ExistsResource
-      Check that resource exists
-      res = 0 when ok
-  **)
-
-  (* Editor options *)
-  var
-    Compress: Boolean;
-    Backup: Boolean;
-
-  procedure g_GetResourceSection (path: String; out wad, section, name: String);
-  function  g_DeleteFile(wad: String; backupPostfix: String = '.bak'): Boolean;
-
-  procedure g_ReadResource (wad, section, name: String; out data: PByte; out len: Integer);
-  procedure g_ReadSubResource (wad, section0, name0, section1, name1: String; out data: PByte; out len: Integer);
-  procedure g_DeleteResource (wad, section, name: String; out res: Integer);
-  procedure g_AddResource (wad, section, name: String; const data: PByte; len: Integer; out res: Integer);
-  procedure g_ExistsResource (wad, section, name: String; out res: Integer);
-
-implementation
-
-  uses sfs, xstreams, dfzip, utils, Classes, SysUtils, WADEDITOR, e_log;
-
-  function NoTrailing (path: String): String;
-    var i: Integer;
-  begin
-    i := Length(path);
-    while (i > 0) and ((path[i] = '/') or (path[i] = '\')) do dec(i);
-    result := Copy(path, 1, i)
-  end;
-
-  function g_CleanPath (path: String; sys: Boolean = False): String;
-    var i, len: Integer;
-  begin
-    i := 1;
-    result := '';
-    len := Length(path);
-    (* drop separators at the end *)
-    while (len > 1) and ((path[i] = '/') or (path[i] = '\')) do dec(len);
-    while i <= len do
-    begin
-      while (i <= len) and (path[i] <> '/') and (path[i] <> '\') do
-      begin
-        result := result + path[i];
-        inc(i)
-      end;
-      if i <= len then
-        if sys then
-          result := result + DirectorySeparator
-        else
-          result := result + '/';
-      inc(i);
-      while (i <= len) and ((path[i] = '/') or (path[i] = '\')) do inc(i)
-    end;
-  end;
-
-  procedure g_GetResourceSection (path: String; out wad, section, name: String);
-    var i, j, len: Integer;
-  begin
-    len := Length(path);
-    i := len;
-    while (i > 0) and (path[i] <> '/') and (path[i] <> '\') do dec(i);
-    name := Copy(path, i + 1, len);
-    j := i;
-    while (i > 0) and (path[i] <> ':') do dec(i);
-    section := Copy(path, i + 1, j - i - 1);
-    wad := Copy(path, 1, i - 1);
-  end;
-
-  function g_DeleteFile (wad: String; backupPostfix: String = '.bak'): Boolean;
-    var newwad: String; ok: Boolean;
-  begin
-    SFSGCCollect;
-    SFSGCCollect;
-    SFSGCCollect;
-    ok := true;
-    if FileExists(wad) then
-    begin
-      if Backup then
-      begin
-        newwad := wad + backupPostfix;
-        if FileExists(newwad) then ok := DeleteFile(newwad);
-        if ok then ok := RenameFile(wad, newwad);
-      end
-      else
-        ok := DeleteFile(wad);
-    end;
-    result := ok;
-  end;
-
-  procedure g_AddResourceToDFWAD (wad, section, name: String; const data: PByte; len: Integer; out res: Integer);
-    var f: TWADEditor_1;
-  begin
-    res := 1; (* error *)
-    section := utf2win(NoTrailing(section));
-    name := utf2win(name);
-    ASSERT(name <> '');
-    f := TWADEditor_1.Create();
-    if not f.ReadFile(wad) then
-    begin
-      (* do nothing *)
-    end;
-    f.CreateImage;
-    f.RemoveResource(section, name);
-    f.AddResource(data, len, name, section);
-    g_DeleteFile(wad);
-    f.SaveTo(wad);
-    f.Free;
-    res := 0
-  end;
-
-  procedure g_AddResourceToZip (wad, section, name: String; const data: PByte; len: Integer; out res: Integer);
-    var
-      i, n, len0: Integer;
-      data0: PByte;
-      list: TSFSFileList;
-      tmp, path: String;
-      ts: TFileStream;
-      dir: array of TFileInfo;
-      ok: Boolean;
-
-    procedure Add (name: String; data: PByte; len: Integer);
-      var ds: TSFSMemoryChunkStream;
-    begin
-      SetLength(dir, n + 1);
-      ds := TSFSMemoryChunkStream.Create(data, len, False);
-      dir[n] := dfzip.ZipOne(ts, name, ds, Compress);
-      ds.Free;
-      INC(n);
-    end;
-
-  begin
-    res := 1;
-    wad := ExpandFileName(wad);
-    section := utf2win(NoTrailing(section));
-    name := utf2win(name);
-    ASSERT(name <> '');
-    list := SFSFileList(wad);
-    tmp := wad + '.tmp' + IntToStr(Random(100000));
-    ts := TFileStream.Create(tmp, fmCreate);
-    n := 0;
-    SetLength(dir, 0);
-    if list <> nil then
-    begin
-      for i := 0 to list.Count - 1 do
-      begin
-        path := NoTrailing(list.Files[i].path);
-        if (path <> section) or (list.Files[i].name <> name) then
-        begin
-          g_ReadResource(wad, win2utf(path), win2utf(list.Files[i].name), data0, len0);
-          ASSERT(data0 <> nil);
-          if path = '' then
-            path := list.Files[i].name
-          else
-            path := path + '/' + list.Files[i].name;
-          Add(path, data0, len0);
-          FreeMem(data0)
-        end
-      end;
-      list.Destroy
-    end;
-
-    if section = '' then
-      path := name
-    else
-      path := section + '/' + name;
-    Add(path, data, len);
-
-    dfzip.writeCentralDir(ts, dir);
-    ts.Free;
-
-    ok := g_DeleteFile(wad);
-    if not ok then e_WriteLog('Cant delete older wad [' + wad + ']', TRecordCategory.MSG_WARNING);
-    ok := RenameFile(tmp, wad);
-    if not ok then e_WriteLog('ERROR: Cant rename [' + tmp + '] -> [' + wad + ']', TRecordCategory.MSG_WARNING);
-    if ok then res := 0 else res := 2;
-  end;
-
-  procedure g_AddResource (wad, section, name: String; const data: PByte; len: Integer; out res: Integer);
-    var ext: String;
-  begin
-    ASSERT(name <> '');
-    res := 2; (* unknown type *)
-    ext := LowerCase(SysUtils.ExtractFileExt(wad));
-    e_WriteLog('g_AddResource "' + wad + '" "' + section + '" "' + name + '"', MSG_NOTIFY);
-    if ext = '.wad' then
-      g_AddResourceToDFWAD(wad, section, name, data, len, res)
-    else
-      g_AddResourceToZip(wad, section, name, data, len, res)
-  end;
-
-  procedure g_DeleteResourceFromDFWAD (wad, section, name: String; out res: Integer);
-    var f: TWADEditor_1;
-  begin
-    ASSERT(name <> '');
-    res := 1; (* error *)
-    section := utf2win(NoTrailing(section));
-    name := utf2win(name);
-    f := TWADEditor_1.Create;
-    if not f.ReadFile(wad) then
-    begin
-      f.Free;
-      Exit
-    end;
-    f.CreateImage;
-    f.RemoveResource(section, name);
-    g_DeleteFile(wad);
-    f.SaveTo(wad);
-    f.Free;
-    res := 0 (* ok *)
-  end;
-
-  procedure g_DeleteResourceFromZip (wad, section, name: String; out res: Integer);
-    var
-      data0: PByte;
-      i, n, len0: Integer;
-      list: TSFSFileList;
-      tmp, path: String;
-      ts: TFileStream;
-      dir: array of TFileInfo;
-      ok: Boolean;
-
-    procedure Add (name: String; data: PByte; len: Integer);
-      var ds: TSFSMemoryChunkStream;
-    begin
-      SetLength(dir, n + 1);
-      ds := TSFSMemoryChunkStream.Create(data, len, False);
-      dir[n] := dfzip.ZipOne(ts, name, ds, Compress);
-      ds.Free;
-      INC(n);
-    end;
-
-  begin
-    res := 1;
-    wad := ExpandFileName(wad);
-    section := utf2win(NoTrailing(section));
-    name := utf2win(name);
-    ASSERT(name <> '');
-    list := SFSFileList(wad);
-    tmp := wad + '.tmp' + IntToStr(Random(100000));
-    ts := TFileStream.Create(tmp, fmCreate);
-    n := 0;
-    SetLength(dir, 0);
-    if list <> nil then
-    begin
-      for i := 0 to list.Count - 1 do
-      begin
-        path := NoTrailing(list.Files[i].path);
-        if (path <> section) or (list.Files[i].name <> name) then
-        begin
-          g_ReadResource(wad, win2utf(path), win2utf(list.Files[i].name), data0, len0);
-          ASSERT(data0 <> nil);
-          if path = '' then
-            path := list.Files[i].name
-          else
-            path := path + '/' + list.Files[i].name;
-          Add(path, data0, len0);
-          FreeMem(data0)
-        end
-      end;
-      list.Destroy
-    end;
-
-    dfzip.writeCentralDir(ts, dir);
-    ts.Free;
-
-    ok := g_DeleteFile(wad);
-    if not ok then e_WriteLog('Cant delete older wad [' + wad + ']', TRecordCategory.MSG_WARNING);
-    ok := RenameFile(tmp, wad);
-    if not ok then e_WriteLog('ERROR: Cant rename [' + tmp + '] -> [' + wad + ']', TRecordCategory.MSG_WARNING);
-    if ok then res := 0 else res := 2;
-  end;
-
-  procedure g_DeleteResource (wad, section, name: String; out res: Integer);
-    var ext: String;
-  begin
-    ASSERT(name <> '');
-    res := 2; (* unknown type *)
-    ext := LowerCase(SysUtils.ExtractFileExt(wad));
-    if ext = '.wad' then
-      g_DeleteResourceFromDFWAD(wad, section, name, res)
-    else
-      g_DeleteResourceFromZip(wad, section, name, res)
-  end;
-
-  procedure g_ExistsResource (wad, section, name: String; out res: Integer);
-    var str: String; stream: TStream;
-  begin
-    res := 1;
-    section := utf2win(NoTrailing(section));
-    name := utf2win(name);
-    ASSERT(name <> '');
-    if SFSAddDataFileTemp(wad, TRUE) then
-    begin
-      str := SFSGetLastVirtualName(section + '\' + name);
-      stream := SFSFileOpen(wad + '::' + str);
-      if stream <> nil then
-      begin
-        res := 0;
-        stream.Destroy
-      end
-    end;
-    SFSGCCollect
-  end;
-
-  procedure g_ReadResource (wad, section, name: String; out data: PByte; out len: Integer);
-    var stream: TStream; str: String; i: Integer;
-  begin
-    e_WriteLog('g_ReadResource: "' + wad + '" "' + section + '" "' + name + '"', MSG_NOTIFY);
-    section := utf2win(NoTrailing(section));
-    name := utf2win(name);
-    data := nil;
-    len := 0;
-    //ASSERT(name <> '');
-    if name = '' then Exit; (* SKY can be void *)
-    if SFSAddDataFileTemp(wad, TRUE) then
-    begin
-      str := SFSGetLastVirtualName(section + '/' + name);
-      stream := SFSFileOpen(wad + '::' + str);
-      if stream <> nil then
-      begin
-        len := stream.Size;
-        GetMem(data, len);
-        ASSERT(data <> nil);
-        //stream.ReadBuffer(data, len); (* leads to segfault *)
-        for i := 0 to len - 1 do
-          data[i] := stream.ReadByte();
-        stream.Destroy
-      end
-    end;
-    SFSGCCollect
-  end;
-
-  procedure g_ReadSubResource (wad, section0, name0, section1, name1: String; out data: PByte; out len: Integer);
-    var stream0, stream1: TStream; str0, str1: String; i: Integer;
-  begin
-    data := nil;
-    len := 0;
-    section0 := utf2win(NoTrailing(section0));
-    name0 := utf2win(name0);
-    section1 := utf2win(NoTrailing(section1));
-    name1 := utf2win(name1);
-    //ASSERT(name0 <> '');
-    //ASSERT(name1 <> '');
-    if (wad = '') OR (name0 = '') OR (name1 = '') then Exit; (* ??? *)
-    if SFSAddDataFileTemp(wad, TRUE) then
-    begin
-      str0 := SFSGetLastVirtualName(section0 + '\' + name0);
-      stream0 := SFSFileOpen(wad + '::' + str0);
-      if stream0 <> nil then
-      begin
-        if SFSAddSubDataFile(wad + '\' + str0, stream0, TRUE) then
-        begin
-          str1 := SFSGetLastVirtualName(section1 + '\' + name1);
-          stream1 := SFSFileOpen(wad + '\' + str0 + '::' + str1);
-          if stream1 <> nil then
-          begin
-            len := stream1.Size;
-            GetMem(data, len);
-            ASSERT(data <> nil);
-            //stream1.ReadBuffer(data, len); (* leads to segfault *)
-            for i := 0 to len - 1 do
-              data[i] := stream1.ReadByte();
-            stream1.Destroy
-            //stream0.Destroy (* leads to memory corruption, it destroyed with stream1? *)
-          end
-          else
-          begin
-            stream0.Destroy
-          end
-        end
-        else
-        begin
-          stream0.Destroy
-        end
-      end
-    end;
-    SFSGCCollect
-  end;
-
-end.
index 52c6169b807bf8cd88d4cb6db89a171f0abfb017..5d591577a4854b6697a6c965990363757f6860d0 100644 (file)
@@ -28,7 +28,7 @@ procedure g_DeleteAllTextures();
 implementation
 
 uses
-  e_log, WADEDITOR, g_basic, SysUtils, g_resources;
+  e_log, WADEDITOR, g_basic, SysUtils;
 
 type
   _TTexture = record
@@ -65,26 +65,32 @@ begin
  end;
 end;
 
-function g_SimpleCreateTextureWAD (var ID: DWORD; Resource: string): Boolean;
-  var
-    TextureData: Pointer;
-    ResourceLength: Integer;
-    FileName, SectionName, ResourceName: string;
+function g_SimpleCreateTextureWAD(var ID: DWORD; Resource: string): Boolean;
+var
+  WAD: TWADEditor_1;
+  FileName,
+  SectionName,
+  ResourceName: string;
+  TextureData: Pointer;
+  ResourceLength: Integer;
 begin
-  Result := False;
-  g_ProcessResourceStr(Resource, FileName, SectionName, ResourceName);
-  g_ReadResource(FileName, SectionName, ResourceName, TextureData, ResourceLength);
-  if TextureData <> nil then
-  begin
-    if e_CreateTextureMem(TextureData, ResourceLength, ID) then
-      Result := True;
-    FreeMem(TextureData)
-  end
+ Result := False;
+ g_ProcessResourceStr(Resource, FileName, SectionName, ResourceName);
+
+ WAD := TWADEditor_1.Create;
+ WAD.ReadFile(FileName);
+
+ if WAD.GetResource(utf2win(SectionName), utf2win(ResourceName), TextureData, ResourceLength) then
+ begin
+  if e_CreateTextureMem(TextureData, ResourceLength, ID) then Result := True;
+  FreeMem(TextureData);
+ end
   else
-  begin
-    e_WriteLog(Format('Error loading texture %s', [Resource]), MSG_WARNING)
-    //e_WriteLog(Format('WAD Reader error: %s', [WAD.GetLastErrorStr]), MSG_WARNING);
-  end;
+ begin
+  e_WriteLog(Format('Error loading texture %s', [Resource]), MSG_WARNING);
+  e_WriteLog(Format('WAD Reader error: %s', [WAD.GetLastErrorStr]), MSG_WARNING);
+ end;
+ WAD.Destroy;
 end;
 
 function g_CreateTextureMemorySize(pData: Pointer; dataLen: Integer; Name: ShortString; X, Y,
@@ -115,88 +121,108 @@ begin
 end;
 
 function g_CreateTextureWAD(TextureName: ShortString; Resource: string; flag: Byte = 0): Boolean;
-  var
-    TextureData: Pointer;
-    ResourceLength: Integer;
-    FileName, SectionName, ResourceName: string;
-    find_id: DWORD;
+var
+  WAD: TWADEditor_1;
+  FileName,
+  SectionName,
+  ResourceName: string;
+  TextureData: Pointer;
+  find_id: DWORD;
+  ResourceLength: Integer;
 begin
-   find_id := FindTexture;
-   g_ProcessResourceStr(Resource, FileName, SectionName, ResourceName);
-   g_ReadResource(FileName, SectionName, ResourceName, TextureData, ResourceLength);
-   if TextureData <> nil then
-   begin
-     Result := e_CreateTextureMem(TextureData, ResourceLength, TexturesArray[find_id].ID);
-     FreeMem(TextureData);
-     if Result then
-     begin
-       e_GetTextureSize(
-         TexturesArray[find_id].ID,
-         @TexturesArray[find_id].Width,
-         @TexturesArray[find_id].Height
-       );
-       TexturesArray[find_id].Name := TextureName;
-       TexturesArray[find_id].flag := flag
-     end
-   end
-   else
-   begin
-     e_WriteLog(Format('Error loading texture %s', [Resource]), MSG_WARNING);
-     //e_WriteLog(Format('WAD Reader error: %s', [WAD.GetLastErrorStr]), MSG_WARNING);
-     Result := False
-   end
+ g_ProcessResourceStr(Resource, FileName, SectionName, ResourceName);
+
+ find_id := FindTexture;
+
+ WAD := TWADEditor_1.Create;
+ WAD.ReadFile(FileName);
+
+ if WAD.GetResource(utf2win(SectionName), utf2win(ResourceName), TextureData, ResourceLength) then
+ begin
+  Result := e_CreateTextureMem(TextureData, ResourceLength, TexturesArray[find_id].ID);
+  FreeMem(TextureData);
+  if Result then
+  begin
+   e_GetTextureSize(TexturesArray[find_id].ID, @TexturesArray[find_id].Width,
+                    @TexturesArray[find_id].Height);
+   TexturesArray[find_id].Name := TextureName;
+   TexturesArray[find_id].flag := flag;
+  end;
+ end
+  else
+ begin
+  e_WriteLog(Format('Error loading texture %s', [Resource]), MSG_WARNING);
+  e_WriteLog(Format('WAD Reader error: %s', [WAD.GetLastErrorStr]), MSG_WARNING);
+  Result := False;
+ end;
+ WAD.Destroy;
 end;
 
-function g_SimpleCreateTextureWADSize(var ID: DWORD; Resource: String; X, Y, Width, Height: Word): Boolean;
-  var
-    TextureData: Pointer;
-    ResourceLength: Integer;
-    FileName, SectionName, ResourceName: String;
+function g_SimpleCreateTextureWADSize(var ID: DWORD; Resource: string; X, Y, Width, Height: Word): Boolean;
+var
+  WAD: TWADEditor_1;
+  FileName,
+  SectionName,
+  ResourceName: String;
+  TextureData: Pointer;
+  ResourceLength: Integer;
 begin
-   Result := False;
-   g_ProcessResourceStr(Resource, FileName, SectionName, ResourceName);
-   g_ReadResource(FileName, SectionName, ResourceName, TextureData, ResourceLength);
-   if TextureData <> nil then
-   begin
-     if e_CreateTextureMemEx(TextureData, ResourceLength, ID, X, Y, Width, Height) then
-       Result := True;
-     FreeMem(TextureData)
-   end
-   else
-   begin
-     e_WriteLog(Format('Error loading texture %s', [Resource]), MSG_WARNING)
-     //e_WriteLog(Format('WAD Reader error: %s', [WAD.GetLastErrorStr]), MSG_WARNING)
-   end
+ Result := False;
+ g_ProcessResourceStr(Resource, FileName, SectionName, ResourceName);
+
+ WAD := TWADEditor_1.Create;
+ WAD.ReadFile(FileName);
+
+ if WAD.GetResource(utf2win(SectionName), utf2win(ResourceName), TextureData, ResourceLength) then
+ begin
+  if e_CreateTextureMemEx(TextureData, ResourceLength, ID, X, Y, Width, Height) then Result := True;
+  FreeMem(TextureData);
+ end
+  else
+ begin
+  e_WriteLog(Format('Error loading texture %s', [Resource]), MSG_WARNING);
+  e_WriteLog(Format('WAD Reader error: %s', [WAD.GetLastErrorStr]), MSG_WARNING);
+ end;
+ WAD.Destroy;
 end;
 
-function g_CreateTextureWADSize(TextureName: ShortString; Resource: String; X, Y, Width, Height: Word; flag: Byte = 0): Boolean;
-  var
-    TextureData: Pointer;
-    ResourceLength: Integer;
-    FileName, SectionName, ResourceName: String;
-    find_id: DWORD;
+function g_CreateTextureWADSize(TextureName: ShortString; Resource: string;
+                                X, Y, Width, Height: Word; flag: Byte = 0): Boolean;
+var
+  WAD: TWADEditor_1;
+  FileName,
+  SectionName,
+  ResourceName: String;
+  TextureData: Pointer;
+  find_id: DWORD;
+  ResourceLength: Integer;
 begin
-  find_id := FindTexture;
-  g_ProcessResourceStr(Resource, FileName, SectionName, ResourceName);
-  g_ReadResource(FileName, SectionName, ResourceName, TextureData, ResourceLength);
-  if TextureData <> nil then
+ g_ProcessResourceStr(Resource, FileName, SectionName, ResourceName);
+
+ find_id := FindTexture;
+
+ WAD := TWADEditor_1.Create;
+ WAD.ReadFile(FileName);
+
+ if WAD.GetResource(utf2win(SectionName), utf2win(ResourceName), TextureData, ResourceLength) then
+ begin
+  Result := e_CreateTextureMemEx(TextureData, ResourceLength, TexturesArray[find_id].ID, X, Y, Width, Height);
+  FreeMem(TextureData);
+  if Result then
   begin
-    Result := e_CreateTextureMemEx(TextureData, ResourceLength, TexturesArray[find_id].ID, X, Y, Width, Height);
-    FreeMem(TextureData);
-    if Result then
-    begin
-      TexturesArray[find_id].Width := Width;
-      TexturesArray[find_id].Height := Height;
-      TexturesArray[find_id].Name := TextureName;
-      TexturesArray[find_id].flag := flag
-    end
-  end
+   TexturesArray[find_id].Width := Width;
+   TexturesArray[find_id].Height := Height;
+   TexturesArray[find_id].Name := TextureName;
+   TexturesArray[find_id].flag := flag;
+  end;
+ end
   else
-  begin
-    e_WriteLog(Format('Error loading texture %s', [Resource]), MSG_WARNING);
-    //e_WriteLog(Format('WAD Reader error: %s', [WAD.GetLastErrorStr]), MSG_WARNING);
-    Result := False
-  end
+ begin
+  e_WriteLog(Format('Error loading texture %s', [Resource]), MSG_WARNING);
+  e_WriteLog(Format('WAD Reader error: %s', [WAD.GetLastErrorStr]), MSG_WARNING);
+  Result := False;
+ end;
+ WAD.Destroy;
 end;
 
 function g_GetTexture(TextureName: ShortString; var ID: DWORD): Boolean;
diff --git a/src/sfs/sfs.pas b/src/sfs/sfs.pas
deleted file mode 100644 (file)
index 11e1045..0000000
+++ /dev/null
@@ -1,1272 +0,0 @@
-(* Copyright (C)  Doom 2D: Forever Developers
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3 of the License ONLY.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *)
-// streaming file system (virtual)
-{$INCLUDE ../shared/a_modes.inc}
-{$SCOPEDENUMS OFF}
-{.$R+}
-{.$DEFINE SFS_VOLDEBUG}
-unit sfs;
-
-interface
-
-uses
-  SysUtils, Classes, Contnrs;
-
-
-type
-  ESFSError = class(Exception);
-
-  TSFSVolume = class;
-
-  TSFSFileInfo = class
-  public
-    fOwner: TSFSVolume; // òàê, íà âñÿêèé ñëó÷àé
-    fPath: AnsiString;  // ðàçäåëèòåëè êàòàëîãîâ -- "/"; êîðåíü íèêàê íå îáîçíà÷åí, åñëè íå ïóñòîå, îáÿçàíî çàâåðøàòüñÿ "/"
-    fName: AnsiString;  // òîëüêî èìÿ
-    fSize: Int64;       // unpacked
-    fOfs: Int64;        // in VFS (many of 'em need this %-)
-
-    constructor Create (pOwner: TSFSVolume);
-    destructor Destroy (); override;
-
-    property path: AnsiString read fPath;
-    property name: AnsiString read fName;
-    property size: Int64 read fSize; // can be -1 if size is unknown
-  end;
-
-  // âèðòóàëüíàÿ ôàéëîâàÿ ñèñòåìà. ÒÎËÜÊΠÄËß ×ÒÅÍÈß!
-  // òîì ÍÅ ÄÎËÆÅÍ óáèâàòüñÿ íèêàê èíà÷å, ÷åì ïðè ïîìîùè ôàáðèêè!
-  TSFSVolume = class
-  protected
-    fFileName: AnsiString;// îáû÷íî èìÿ îðèãèíàëüíîãî ôàéëà
-    fFileStream: TStream; // îáû÷íî ïîòîê äëÿ ÷òåíèÿ îðèãèíàëüíîãî ôàéëà
-    fFiles: TObjectList;  // TSFSFileInfo èëè íàñëåäíèêè
-
-    // ïðèøèáèòü âñå ñòðóêòóðû.
-    // íå äîëæíà ïàäàòü, åñëè å¸ âûçûâàþò íåñêîëüêî ðàç.
-    procedure Clear (); virtual;
-
-    // âûçûâàåòñÿ èç DoDirectoryRead() äëÿ çàïîëíåíèÿ ñïèñêà ôàéëîâ.
-    // ñ÷èòàåòñÿ, ÷òî âñå ìàãèêè óæå ïðîâåðåíû è ôàéë òî÷íî íàø.
-    // fFileName, fFileStream óæå óñòàíîâëåíû, fFiles ñîçäàí,
-    // â í¸ì, ñêîðåå âñåãî, íèêîãî íåò.
-    // ïîçèöèÿ ïîòîêà -- òà, ÷òî îñòàâèëà ôàáðèêà.
-    // ïðè îøèáêàõ êèäàòü èñêëþ÷åíèå, òîãäà òîì áóäåò ïðèáèò ôàáðèêîé.
-    // ðàçäåëèòåëè ïóòåé äîëæíû áûòü òîëüêî "/", êîðíåâîé "/" äîëæåí
-    // áûòü îïóùåí, ïóòè (åñëè íå ïóñòûå) äîëæíû çàâåðøàòüñÿ "/"!
-    // fName äîëæíî ñîäåðæàòü òîëüêî èìÿ, fPath -- òîëüêî ïóòü.
-    // â ïðèíöèïå, îá ýòîì ïîçàáîòèòñÿ DoDirectoryRead(), íî çà÷åì
-    // äàâàòü åìó ëèøíþþ ðàáîòó?
-    procedure ReadDirectory (); virtual; abstract;
-
-    // íàéòè ôàéë, âåðíóòü åãî èíäåêñ â fFiles.
-    // ýòà ïðîöåäóðà ìîæåò ìåíÿòü fFiles!
-    // fPath -- â ïðàâèëüíîé ôîðìå, ñ "/", êîðíåâîé "/" óáèò, ôèíàëüíûé äîáàâëåí.
-    // åñëè ôàéë íå íàéäåí, âåðíóòü -1.
-    function FindFile (const fPath, fName: AnsiString): Integer; virtual;
-
-    // âîçâðàùàåò êîëè÷åñòâî ôàéëîâ â fFiles
-    function GetFileCount (): Integer; virtual;
-
-    // âîçâðàùàåò ôàéë ñ èíäåêñîì index.
-    // ìîæåò âîçâðàùàòü NIL.
-    // íèêàêèõ ïàäåíèé íà íåïðàâèëüíûå èíäåêñû!
-    function GetFiles (index: Integer): TSFSFileInfo; virtual;
-
-  public
-    // pSt íå îáÿçàòåëüíî çàïîìèíàòü, åñëè îí íå íóæåí.
-    constructor Create (const pFileName: AnsiString; pSt: TStream); virtual;
-    // fFileStream óíè÷òîæàòü íåëüçÿ, åñëè îí ðàâåí ïàðàìåòðó pSt êîíñòðóêòîðà.
-    destructor Destroy (); override;
-
-    // âûçûâàåò ReadDirectory().
-    // ýòà ïðîöåäóðà ñàìà ðàçáåð¸òñÿ ñ äóáëèêàòàìè èì¸í: ïîäîáàâëÿåò â
-    // êîíåö èì¸í-äóáëèêàòîâ ïîä÷¸ðêèâàíèå è äåñÿòè÷íûé íîìåð.
-    // òàêæå îíà íîðìàëèçóåò âèä èì¸í.
-    procedure DoDirectoryRead ();
-
-    // ïðè îøèáêàõ êèäàòüñÿ èñêëþ÷åíèÿìè.
-    function OpenFileByIndex (const index: Integer): TStream; virtual; abstract;
-
-    // åñëè íå ñìîãëî îòêóïîðèòü ôàéëî (èëè åù¸ ãäå îøèáëîñü), çàøâûðí¸ò èñêëþ÷åíèå.
-    function OpenFileEx (const fName: AnsiString): TStream; virtual;
-
-    property FileCount: Integer read GetFileCount; // ìîæåò âåðíóòü íîëü
-    // ìîæåò âîçâðàùàòü NIL.
-    // íèêàêèõ ïàäåíèé íà íåïðàâèëüíûå èíäåêñû!
-    property Files [index: Integer]: TSFSFileInfo read GetFiles;
-  end;
-
-  // ôàáðèêà òîìîâ. âñå SFS ïðè ñòàðòå äîáàâëÿþò ñâîè ôàáðèêè.
-  // áëàãîäàðÿ ýòîìó ìîæíî ñîçäàâàòü ðàçíûå âñÿêèå SFS ñòàíäàðòíûì
-  // âûçîâîì ñòàíäàðòíîé ïðîöåäóðû.
-  // ôàáðèêà ÍÅ ÄÎËÆÍÀ óáèâàòüñÿ íèêàê èíà÷å, ÷åì ïðè ïîìîùè âûçîâà
-  // SFSUnregisterVolumeFactory()! ýòî ãàðàíòèðóåò, ÷òî äâèæîê
-  // ïåðåä ðàññòðåëîì îòäàñò åé âñå å¸ òîìà.
-  TSFSVolumeFactory = class
-  public
-    // åñëè äîáàâëÿåì ôàéë äàííûõ ôàéë ñ èìåíåì òèïà "zip:....", òî
-    // SFS èçâëå÷¸ò ýòî "zip" è ïåðåäàñò â ñèþ ôóíêöèþ.
-    // åæåëè ôóíêöèÿ âåðí¸ò ïðàâäó, òî SFS âûçîâåò Produce äëÿ äàííîãî
-    // ôàéëà. åñëè íè îäíà ôàáðèêà ïðåôèêñ íå ïðèçíàåò, òî ôàéë íå îòêðîþò.
-    // èñïîëüçóåòñÿ äëÿ ñêèïàíèÿ àâòîäåòåêòà.
-    // SFS ÍÅ Ñ×ÈÒÀÅÒ ÏÐÅÔÈÊÑÎÌ ÑÒÐÎÊÓ ÊÎÐÎ×Å ÒШՠÑÈÌÂÎËÎÂ!
-    function IsMyVolumePrefix (const prefix: AnsiString): Boolean; virtual; abstract;
-    // ïðîâåðÿåò, ìîæåò ëè ôàáðèêà ñäåëàòü òîì äëÿ äàííîãî ôàéëà.
-    // st -- îòêðûòûé äëÿ ÷òåíèÿ ôàéëîâé ïîòîê. óêàçàòåëü ÷òåíèÿ ñòîèò â íà÷àëå.
-    // ýòîò ïîòîê íåëüçÿ çàêðûâàòü!
-    // prefix: òî, ÷òî áûëî ïåðåäàíî â IsMyVolumePrefix() èëè ''.
-    // èñêëþ÷åíèå ñ÷èòàåòñÿ îøèáêîé, âîçâðàò NIL ñ÷èòàåòñÿ îøèáêîé.
-    function Produce (const prefix, fileName: AnsiString; st: TStream): TSFSVolume; virtual; abstract;
-    // êîãäà òîì áîëüøå íå íóæåí, îí áóäåò îòäàí ôàáðèêå íà ïåðåðàáîòêó.
-    // äàëåå äâèæîê íå áóäåò þçàòü ñåé òîì.
-    procedure Recycle (vol: TSFSVolume); virtual; abstract;
-  end;
-
-  // "èòåðàòîð", âîçâðàùàåìûé SFSFileList()
-  TSFSFileList = class
-  protected
-    fVolume: TSFSVolume;
-
-    function GetCount (): Integer;
-    function GetFiles (index: Integer): TSFSFileInfo;
-
-  public
-    constructor Create (const pVolume: TSFSVolume);
-    destructor Destroy (); override;
-
-    property Volume: TSFSVolume read fVolume;
-    property Count: Integer read GetCount;
-    // ïðè íåïðàâèëüíîì èíäåêñå ìîë÷à âåðí¸ò NIL.
-    // ïðè ïðàâèëüíîì òîæå ìîæåò âåðíóòü NIL!
-    // î÷åíü íå ñîâåòóþ ìåíÿòü ñîäåðæèìîå ïîëó÷åííîãî êëàññà.
-    // êîíå÷íî, ÿ ìîã áû âîçâðàùàòü íîâóþ ñòðóêòóðó èëè íå÷òî ïîõîæåå,
-    // íî áëèí, åñëè òû èäèîò è íå óìååøü äàæå êîììåíòû ÷èòàòü, òî
-    // êàêîãî òû âîîáùå â ïðîãðàììèíã ïîëåç?
-    property Files [index: Integer]: TSFSFileInfo read GetFiles; default;
-  end;
-
-
-procedure SFSRegisterVolumeFactory (factory: TSFSVolumeFactory);
-// ýòà ôóíêöèÿ àâòîìàòè÷åñêè ïðèáü¸ò factory.
-procedure SFSUnregisterVolumeFactory (factory: TSFSVolumeFactory);
-
-// äîáàâèòü ñáîðíèê â ïîñòîÿííûé ñïèñîê.
-// åñëè ñáîðíèê ñ òàêèì èìåíåì óæå îòêðûò, òî íå îòêðûâàåò åãî ïîâòîðíî.
-// íèêîãäà íå êèäàåò èñêëþ÷åíèé.
-// top: äîáàâèòü â íà÷àëî ñïèñêà ïîèñêà.
-// âåðí¸ò ëîæü ïðè îøèáêå.
-// ñïîñîáíî îòêðûâàòü ñáîðíèêè â ñáîðíèêàõ ïðè ïîìîùè êðóòûõ èì¸í a-la:
-// "zip:pack0::pack:pack1::wad2:pack2".
-// â äàëüíåéøåì ñëåäóåò îáðàùàòüñÿ ê ñáîðíèêó êàê "pack2::xxx".
-// èëè ìîæíî íàïèñàòü:
-// "zip:pack0::pack:pack1::wad2:pack2|datafile".
-// è îáðàùàòüñÿ êàê "datafile::xxx".
-// "||" ïðåîáðàçóþòñÿ â ïðîñòîé "|" è ðàçäåëèòåëåì íå ñ÷èòàþòñÿ.
-// ïðèíèìàåòñÿ âî âíèìàíèå òîëüêî ïîñëåäíÿÿ òðóáà.
-function SFSAddDataFile (const dataFileName: AnsiString; top: Boolean=false): Boolean;
-
-// äîáàâèòü ñáîðíèê âðåìåííî
-function SFSAddDataFileTemp (const dataFileName: AnsiString; top: Boolean=false): Boolean;
-
-// äîáàâèòü â ïîñòîÿííûé ñïèñîê ñáîðíèê èç ïîòîêà ds.
-// åñëè âîçâðàùàåò èñòèíó, òî SFS ñòàíîâèòñÿ âëÿäåëüöåì ïîòîêà ds è ñàìà
-// óãðîáèò ñåé ïîòîê ïî íåîáõîäèìîñòè.
-// virtualName ñòàíîâèòñÿ èìåíåì ñáîðíèêà äëÿ îïåðàöèè îòêðûòèÿ ôàéëà òèïà
-// "packfile:file.ext".
-// åñëè êàêîé-íèáóäü ñáîðíèê ñ èìåíåì virtualName óæå îòêðûò, âåðí¸ò false.
-// íèêîãäà íå êèäàåò èñêëþ÷åíèé.
-// top: äîáàâèòü â íà÷àëî ñïèñêà ïîèñêà.
-// âåðí¸ò ëîæü ïðè îøèáêå.
-// îòêðûâàåò ñáîðíèê èç ïîòîêà. dataFileName -- ÂÈÐÒÓÀËÜÍÎÅ èìÿ.
-// ò.å. íà ñàìîì äåëå òàêîãî ôàéëà ìîæåò è íå áûòü íà äèñêå.
-function SFSAddSubDataFile (const virtualName: AnsiString; ds: TStream; top: Boolean=false): Boolean;
-
-// øâûðÿåòñÿ èñêëþ÷åíèÿìè.
-// åñëè fName íå èìååò óêàçàíèÿ íà ôàéë äàííûõ (ýòî òî, ÷òî îòäåëåíî îò
-// îñòàëüíîãî èìåíè äâîåòî÷èåì), òî èùåì ñíà÷àëà ïî âñåì çàðåãèñòðèðîâàííûì
-// ôàéëàì äàííûõ, ïîòîì â òåêóùåì êàòàëîãå, ïîòîì â êàòàëîãå, îòêóäà ñòàðòîâàëè.
-// åñëè íè÷åãî íå íàøëè, êèäàåì èñêëþ÷åíèå.
-function SFSFileOpenEx (const fName: AnsiString): TStream;
-
-// ïðè îøèáêå -- NIL, è íèêàêèõ èñêëþ÷åíèé.
-function SFSFileOpen (const fName: AnsiString): TStream;
-
-// âîçâðàùàåò NIL ïðè îøèáêå.
-// ïîñëå èñïîëüçîâàíèÿ, íàòóðàëüíî, èòåðàòîð íàäî ãðîõíóòü %-)
-function SFSFileList (const dataFileName: AnsiString): TSFSFileList;
-
-// çàïðåòèòü îñâîáîæäåíèå âðåìåííûõ òîìîâ (ìîæíî âûçûâàòü ðåêóðñèâíî)
-procedure sfsGCDisable ();
-
-// ðàçðåøèòü îñâîáîæäåíèå âðåìåííûõ òîìîâ (ìîæíî âûçûâàòü ðåêóðñèâíî)
-procedure sfsGCEnable ();
-
-// for completeness sake
-procedure sfsGCCollect ();
-
-function SFSReplacePathDelims (const s: AnsiString; newDelim: Char): AnsiString;
-
-// ðàçîáðàòü òîëñòîå èìÿ ôàéëà, âåðíóòü âèðòóàëüíîå èìÿ ïîñëåäíåãî ñïèñêà
-// èëè ïóñòóþ ñòîðîêó, åñëè ñïèñêîâ íå áûëî.
-function SFSGetLastVirtualName (const fn: AnsiString): AnsiString;
-
-// Wildcard matching
-// this code is meant to allow wildcard pattern matches. tt is VERY useful
-// for matching filename wildcard patterns. tt allows unix grep-like pattern
-// comparisons, for instance:
-//
-//       ?       Matches any single characer
-//       +       Matches any single characer or nothing
-//       *       Matches any number of contiguous characters
-//       [abc]   Matches a or b or c at that position
-//       [!abc]  Matches anything but a or b or c at that position
-//       [a-e]   Matches a through e at that position
-//
-//       'ma?ch.*'       -Would match match.exe, mavch.dat, march.on, etc
-//       'this [e-n]s a [!zy]est' -Would match 'this is a test', but would
-//                                 not match 'this as a yest'
-//
-function WildMatch (pattern, text: AnsiString): Boolean;
-function WildListMatch (wildList, text: AnsiString; delimChar: AnsiChar=':'): Integer;
-function HasWildcards (const pattern: AnsiString): Boolean;
-
-
-var
-  // ïðàâäà: ðàçðåøåíî èñêàòü ôàéëî íå òîëüêî â ôàéëàõ äàííûõ, íî è íà äèñêå.
-  sfsDiskEnabled: Boolean = true;
-  // ïðàâäà: åñëè ôàéë íå ïðåôèêñîâàí, òî ñíà÷àëà èùåì ôàéëî íà äèñêå,
-  // ïîòîì â ôàéëàõ äàííûõ.
-  sfsDiskFirst: Boolean = true;
-  // ïðàâäà: äàæå äëÿ ïðåôèêñîâàíûõ ôàéëîâ ñíà÷àëà ïðîñìîòðèì äèñê
-  // (åñëè óñòàíîâëåí ôëàæîê sfsDiskFirst è sfsDiskEnabled).
-  sfsForceDiskForPrefixed: Boolean = false;
-  // ñïèñîê äèñêîâûõ êàòàëîãîâ äëÿ ïîèñêà ôàéëà. åñëè ïóñò -- èùåì òîëüêî â
-  // òåêóùåì. êàòàëîãè ðàçäåëÿþòñÿ òðóáîé ("|").
-  // <currentdir> çàìåíÿåòñÿ íà òåêóùèé êàòàëîã (ñ çàâåðøàþùèì "/"),
-  // <exedir> çàìåíÿåòñÿ íà êàòàëîã, ãäå ñèäèò .EXE (ñ çàâåðøàþùèì "/").
-  sfsDiskDirs: AnsiString = '<currentdir>|<exedir>';
-
-
-implementation
-
-uses
-  xstreams, utils;
-
-
-const
-  // character defines
-  WILD_CHAR_ESCAPE         = '\';
-  WILD_CHAR_SINGLE         = '?';
-  WILD_CHAR_SINGLE_OR_NONE = '+';
-  WILD_CHAR_MULTI          = '*';
-  WILD_CHAR_RANGE_OPEN     = '[';
-  WILD_CHAR_RANGE          = '-';
-  WILD_CHAR_RANGE_CLOSE    = ']';
-  WILD_CHAR_RANGE_NOT      = '!';
-
-
-function HasWildcards (const pattern: AnsiString): Boolean;
-begin
-  result :=
-    (Pos(WILD_CHAR_ESCAPE, pattern) <> 0) or
-    (Pos(WILD_CHAR_SINGLE, pattern) <> 0) or
-    (Pos(WILD_CHAR_SINGLE_OR_NONE, pattern) <> 0) or
-    (Pos(WILD_CHAR_MULTI, pattern) <> 0) or
-    (Pos(WILD_CHAR_RANGE_OPEN, pattern) <> 0);
-end;
-
-function MatchMask (const pattern: AnsiString; p, pend: Integer; const text: AnsiString; t, tend: Integer): Boolean;
-var
-  rangeStart, rangeEnd: AnsiChar;
-  rangeNot, rangeMatched: Boolean;
-  ch: AnsiChar;
-begin
-  // sanity checks
-  if (pend < 0) or (pend > Length(pattern)) then pend := Length(pattern);
-  if (tend < 0) or (tend > Length(text)) then tend := Length(text);
-  if t < 1 then t := 1;
-  if p < 1 then p := 1;
-  while p <= pend do
-  begin
-    if t > tend then
-    begin
-      // no more text. check if there's no more chars in pattern (except "*" & "+")
-      while (p <= pend) and
-            ((pattern[p] = WILD_CHAR_MULTI) or
-             (pattern[p] = WILD_CHAR_SINGLE_OR_NONE)) do Inc(p);
-      result := (p > pend);
-      exit;
-    end;
-    case pattern[p] of
-      WILD_CHAR_SINGLE: ;
-      WILD_CHAR_ESCAPE:
-        begin
-          Inc(p);
-          if p > pend then result := false else result := (pattern[p] = text[t]);
-          if not result then exit;
-        end;
-      WILD_CHAR_RANGE_OPEN:
-        begin
-          result := false;
-          Inc(p); if p > pend then exit; // sanity check
-          rangeNot := (pattern[p] = WILD_CHAR_RANGE_NOT);
-          if rangeNot then begin Inc(p); if p > pend then exit; {sanity check} end;
-          if pattern[p] = WILD_CHAR_RANGE_CLOSE then exit; // sanity check
-          ch := text[t]; // speed reasons
-          rangeMatched := false;
-          repeat
-            if p > pend then exit; // sanity check
-            rangeStart := pattern[p];
-            if rangeStart = WILD_CHAR_RANGE_CLOSE then break;
-            Inc(p); if p > pend then exit; // sanity check
-            if pattern[p] = WILD_CHAR_RANGE then
-            begin
-              Inc(p); if p > pend then exit; // sanity check
-              rangeEnd := pattern[p]; Inc(p);
-              if rangeStart < rangeEnd then
-              begin
-                rangeMatched := (ch >= rangeStart) and (ch <= rangeEnd);
-              end
-              else rangeMatched := (ch >= rangeEnd) and (ch <= rangeStart);
-            end
-            else rangeMatched := (ch = rangeStart);
-          until rangeMatched;
-          if rangeNot = rangeMatched then exit;
-
-          // skip the rest or the range
-          while (p <= pend) and (pattern[p] <> WILD_CHAR_RANGE_CLOSE) do Inc(p);
-          if p > pend then exit; // sanity check
-        end;
-      WILD_CHAR_SINGLE_OR_NONE:
-        begin
-          Inc(p);
-          result := MatchMask(pattern, p, pend, text, t, tend);
-          if not result then result := MatchMask(pattern, p, pend, text, t+1, tend);
-          exit;
-        end;
-      WILD_CHAR_MULTI:
-        begin
-          while (p <= pend) and (pattern[p] = WILD_CHAR_MULTI) do Inc(p);
-          result := (p > pend); if result then exit;
-          while not result and (t <= tend) do
-          begin
-            result := MatchMask(pattern, p, pend, text, t, tend);
-            Inc(t);
-          end;
-          exit;
-        end;
-      else result := (pattern[p] = text[t]); if not result then exit;
-    end;
-    Inc(p); Inc(t);
-  end;
-  result := (t > tend);
-end;
-
-
-function WildMatch (pattern, text: AnsiString): Boolean;
-begin
-  if pattern <> '' then pattern := AnsiLowerCase(pattern);
-  if text <> '' then text := AnsiLowerCase(text);
-  result := MatchMask(pattern, 1, -1, text, 1, -1);
-end;
-
-function WildListMatch (wildList, text: AnsiString; delimChar: AnsiChar=':'): Integer;
-var
-  s, e: Integer;
-begin
-  if wildList <> '' then wildList := AnsiLowerCase(wildList);
-  if text <> '' then text := AnsiLowerCase(text);
-  result := 0;
-  s := 1;
-  while s <= Length(wildList) do
-  begin
-    e := s; while e <= Length(wildList) do
-    begin
-      if wildList[e] = WILD_CHAR_RANGE_OPEN then
-      begin
-        while (e <= Length(wildList)) and (wildList[e] <> WILD_CHAR_RANGE_CLOSE) do Inc(e);
-      end;
-      if wildList[e] = delimChar then break;
-      Inc(e);
-    end;
-    if s < e then
-    begin
-      if MatchMask(wildList, s, e-1, text, 1, -1) then exit;
-    end;
-    Inc(result);
-    s := e+1;
-  end;
-  result := -1;
-end;
-
-
-type
-  TVolumeInfo = class
-  public
-    fFactory: TSFSVolumeFactory;
-    fVolume: TSFSVolume;
-    fPackName: AnsiString; // äëÿ îäíîãî è òîãî æå ôàéëà áóäåò òîëüêî îäèí òîì!
-    fStream: TStream; // ôàéëîâûé ïîòîê äëÿ ñáîðíèêà
-    fPermanent: Boolean; // èñòèíà -- íå áóäåò óãðîáëåíà, åñëè íå îñòàíåòñÿ íè îäíîãî îòêðûòîãî òîìà
-    // èñòèíà -- ýòîò òîì áûë ñîçäàí èç ïîòîêà è íå èìååò äèñêîâîãî ôàéëà, ïîòîìó ôàáðèêå áóäåò ïåðåäàíî íå èìÿ ñáîðíèêà, à ïóñòàÿ ñòðîêà
-    fNoDiskFile: Boolean;
-    fOpenedFilesCount: Integer;
-
-    destructor Destroy (); override;
-  end;
-
-  TOwnedPartialStream = class (TSFSPartialStream)
-  protected
-    fOwner: TVolumeInfo;
-
-  public
-    constructor Create (pOwner: TVolumeInfo; pSrc: TStream; pPos, pSize: Int64; pKillSrc: Boolean);
-    destructor Destroy (); override;
-  end;
-
-
-var
-  factories: TObjectList; // TSFSVolumeFactory
-  volumes: TObjectList;   // TVolumeInfo
-  gcdisabled: Integer = 0; // >0: disabled
-
-
-procedure sfsGCCollect ();
-var
-  f, c: Integer;
-  vi: TVolumeInfo;
-  used: Boolean;
-begin
-  // collect garbage
-  f := 0;
-  while f < volumes.Count do
-  begin
-    vi := TVolumeInfo(volumes[f]);
-    if (vi <> nil) and (not vi.fPermanent) and (vi.fOpenedFilesCount = 0) then
-    begin
-      // this volume probably can be removed
-      used := false;
-      c := volumes.Count-1;
-      while not used and (c >= 0) do
-      begin
-        if (c <> f) and (volumes[c] <> nil) then
-        begin
-          used := (TVolumeInfo(volumes[c]).fStream = vi.fStream);
-          if not used then used := (TVolumeInfo(volumes[c]).fVolume.fFileStream = vi.fStream);
-          if used then break;
-        end;
-        Dec(c);
-      end;
-      if not used then
-      begin
-        {$IFDEF SFS_VOLDEBUG}writeln('000: destroying volume "', TVolumeInfo(volumes[f]).fPackName, '"');{$ENDIF}
-        volumes.extract(vi); // remove from list
-        vi.Free; // and kill
-        f := 0;
-        continue;
-      end;
-    end;
-    Inc(f); // next volume
-  end;
-end;
-
-procedure sfsGCDisable ();
-begin
-  Inc(gcdisabled);
-end;
-
-procedure sfsGCEnable ();
-begin
-  Dec(gcdisabled);
-  if gcdisabled <= 0 then
-  begin
-    gcdisabled := 0;
-    sfsGCCollect();
-  end;
-end;
-
-
-// ðàçáèòü èìÿ ôàéëà íà ÷àñòè: ïðåôèêñ ôàéëîâîé ñèñòåìû, èìÿ ôàéëà äàííûõ,
-// ñîáñòâåííî èìÿ ôàéëà
-// èìÿ âûãëÿäèò êàê:
-// (("sfspfx:")?"datafile::")*"filename"
-procedure SplitFName (const fn: AnsiString; out dataFile, fileName: AnsiString);
-var
-  f: Integer;
-begin
-  f := Length(fn)-1;
-  while f >= 1 do
-  begin
-    if (fn[f] = ':') and (fn[f+1] = ':') then break;
-    Dec(f);
-  end;
-  if f < 1 then begin dataFile := ''; fileName := fn; end
-  else
-  begin
-    dataFile := Copy(fn, 1, f-1);
-    fileName := Copy(fn, f+2, maxInt-10000);
-  end;
-end;
-
-// ñàéäýôôåêò: âûðåçàåò âèðòóàëüíîå èìÿ èç dataFile.
-function ExtractVirtName (var dataFile: AnsiString): AnsiString;
-var
-  f: Integer;
-begin
-  f := Length(dataFile); result := dataFile;
-  while f > 1 do
-  begin
-    if dataFile[f] = ':' then break;
-    if dataFile[f] = '|' then
-    begin
-      if dataFile[f-1] = '|' then begin Dec(f); Delete(dataFile, f, 1); end
-      else
-      begin
-        result := Copy(dataFile, f+1, Length(dataFile));
-        Delete(dataFile, f, Length(dataFile));
-        break;
-      end;
-    end;
-    Dec(f);
-  end;
-end;
-
-// ðàçáèòü èìÿ ñáîðíèêà íà ÷àñòè: ïðåôèêñ ôàéëîâîé ñèñòåìû, èìÿ ôàéëà äàííûõ,
-// âèðòóàëüíîå èìÿ. åñëè âèðòóàëüíîãî èìåíè íå äàíî, îíî áóäåò ðàâíî dataFile.
-// èìÿ âûãëÿäèò êàê:
-// [sfspfx:]datafile[|virtname]
-// åñëè ïåðåä äâîåòî÷èåì ìåíüøå òð¸õ áóêâ, òî ýòî ñ÷èòàåòñÿ íå ïðåôèêñîì,
-// à èìåíåì äèñêà.
-procedure SplitDataName (const fn: AnsiString; out pfx, dataFile, virtName: AnsiString);
-var
-  f: Integer;
-begin
-  f := Pos(':', fn);
-  if f <= 3 then begin pfx := ''; dataFile := fn; end
-  else
-  begin
-    pfx := Copy(fn, 1, f-1);
-    dataFile := Copy(fn, f+1, maxInt-10000);
-  end;
-  virtName := ExtractVirtName(dataFile);
-end;
-
-// íàéòè ïðîèçâîäèòåëÿ äëÿ ýòîãî ôàéëà (åñëè ôàéë óæå îòêðûò).
-// onlyPerm: òîëüêî "ïîñòîÿííûå" ïðîèçâîäèòåëè.
-function FindVolumeInfo (const dataFileName: AnsiString; onlyPerm: Boolean=false): Integer;
-var
-  f: Integer;
-  vi: TVolumeInfo;
-begin
-  f := 0;
-  while f < volumes.Count do
-  begin
-    if volumes[f] <> nil then
-    begin
-      vi := TVolumeInfo(volumes[f]);
-      if not onlyPerm or vi.fPermanent then
-      begin
-        if StrEquCI1251(vi.fPackName, dataFileName) then
-        begin
-          result := f;
-          exit;
-        end;
-      end;
-    end;
-    Inc(f);
-  end;
-  result := -1;
-end;
-
-// íàéòè èíôó äëÿ ýòîãî òîìà.
-// õîðîøåå èìÿ, ïðàâäà? %-)
-function FindVolumeInfoByVolumeInstance (vol: TSFSVolume): Integer;
-begin
-  result := volumes.Count-1;
-  while result >= 0 do
-  begin
-    if volumes[result] <> nil then
-    begin
-      if TVolumeInfo(volumes[result]).fVolume = vol then exit;
-    end;
-    Dec(result);
-  end;
-end;
-
-
-// adds '/' too
-function normalizePath (fn: AnsiString): AnsiString;
-var
-  i: Integer;
-begin
-  result := '';
-  i := 1;
-  while i <= length(fn) do
-  begin
-    if (fn[i] = '.') and ((length(fn)-i = 0) or (fn[i+1] = '/') or (fn[i+1] = '\')) then
-    begin
-      i := i+2;
-      continue;
-    end;
-    if (fn[i] = '/') or (fn[i] = '\') then
-    begin
-      if (length(result) > 0) and (result[length(result)] <> '/') then result := result+'/';
-    end
-    else
-    begin
-      result := result+fn[i];
-    end;
-    Inc(i);
-  end;
-  if (length(result) > 0) and (result[length(result)] <> '/') then result := result+'/';
-end;
-
-function SFSReplacePathDelims (const s: AnsiString; newDelim: Char): AnsiString;
-var
-  f: Integer;
-begin
-  result := s;
-  for f := 1 to Length(result) do
-  begin
-    if (result[f] = '/') or (result[f] = '\') then
-    begin
-      // avoid unnecessary string changes
-      if result[f] <> newDelim then result[f] := newDelim;
-    end;
-  end;
-end;
-
-function SFSGetLastVirtualName (const fn: AnsiString): AnsiString;
-var
-  rest, tmp: AnsiString;
-  f: Integer;
-begin
-  rest := fn;
-  repeat
-    f := Pos('::', rest); if f = 0 then f := Length(rest)+1;
-    tmp := Copy(rest, 1, f-1); Delete(rest, 1, f+1);
-    result := ExtractVirtName(tmp);
-  until rest = '';
-end;
-
-
-{ TVolumeInfo }
-destructor TVolumeInfo.Destroy ();
-var
-  f, me: Integer;
-  used: Boolean; // ôëàæîê çàþçàíîñòè ïîòîêà êåì-òî åù¸
-begin
-  if fFactory <> nil then fFactory.Recycle(fVolume);
-  used := false;
-  fVolume := nil;
-  fFactory := nil;
-  fPackName := '';
-
-  // òèïà ìóñîðîñáîðíèê: åñëè íàø ïîòîê áîëåå íèêåì íå þçàåòñÿ, òî óãðîáèòü åãî íàôèã
-  if not used then
-  begin
-    me := volumes.IndexOf(self);
-    f := volumes.Count-1;
-    while not used and (f >= 0) do
-    begin
-      if (f <> me) and (volumes[f] <> nil) then
-      begin
-        used := (TVolumeInfo(volumes[f]).fStream = fStream);
-        if not used then
-        begin
-          used := (TVolumeInfo(volumes[f]).fVolume.fFileStream = fStream);
-        end;
-        if used then break;
-      end;
-      Dec(f);
-    end;
-  end;
-  if not used then FreeAndNil(fStream); // åñëè áîëüøå íèêåì íå þçàíî, ïðèøèá¸ì
-  inherited Destroy();
-end;
-
-
-{ TOwnedPartialStream }
-constructor TOwnedPartialStream.Create (pOwner: TVolumeInfo; pSrc: TStream;
-  pPos, pSize: Int64; pKillSrc: Boolean);
-begin
-  inherited Create(pSrc, pPos, pSize, pKillSrc);
-  fOwner := pOwner;
-  if pOwner <> nil then Inc(pOwner.fOpenedFilesCount);
-end;
-
-destructor TOwnedPartialStream.Destroy ();
-var
-  f: Integer;
-begin
-  inherited Destroy();
-  if fOwner <> nil then
-  begin
-    Dec(fOwner.fOpenedFilesCount);
-    if (gcdisabled = 0) and not fOwner.fPermanent and (fOwner.fOpenedFilesCount < 1) then
-    begin
-      f := volumes.IndexOf(fOwner);
-      if f <> -1 then
-      begin
-        {$IFDEF SFS_VOLDEBUG}writeln('001: destroying volume "', TVolumeInfo(volumes[f]).fPackName, '"');{$ENDIF}
-        volumes[f] := nil; // this will destroy the volume
-      end;
-    end;
-  end;
-end;
-
-
-{ TSFSFileInfo }
-constructor TSFSFileInfo.Create (pOwner: TSFSVolume);
-begin
-  inherited Create();
-  fOwner := pOwner;
-  fPath := '';
-  fName := '';
-  fSize := 0;
-  fOfs := 0;
-  if pOwner <> nil then pOwner.fFiles.Add(self);
-end;
-
-destructor TSFSFileInfo.Destroy ();
-begin
-  if fOwner <> nil then fOwner.fFiles.Extract(self);
-  inherited Destroy();
-end;
-
-
-{ TSFSVolume }
-constructor TSFSVolume.Create (const pFileName: AnsiString; pSt: TStream);
-begin
-  inherited Create();
-  fFileStream := pSt;
-  fFileName := pFileName;
-  fFiles := TObjectList.Create(true);
-end;
-
-procedure TSFSVolume.DoDirectoryRead ();
-var
-  f, c: Integer;
-  sfi: TSFSFileInfo;
-  tmp: AnsiString;
-begin
-  fFileName := ExpandFileName(SFSReplacePathDelims(fFileName, '/'));
-  ReadDirectory();
-  fFiles.Pack();
-
-  f := 0;
-  while f < fFiles.Count do
-  begin
-    sfi := TSFSFileInfo(fFiles[f]);
-    // normalize name & path
-    sfi.fPath := SFSReplacePathDelims(sfi.fPath, '/');
-    if (sfi.fPath <> '') and (sfi.fPath[1] = '/') then Delete(sfi.fPath, 1, 1);
-    if (sfi.fPath <> '') and (sfi.fPath[Length(sfi.fPath)] <> '/') then sfi.fPath := sfi.fPath+'/';
-    tmp := SFSReplacePathDelims(sfi.fName, '/');
-    c := Length(tmp); while (c > 0) and (tmp[c] <> '/') do Dec(c);
-    if c > 0 then
-    begin
-      // split path and name
-      Delete(sfi.fName, 1, c); // cut name
-      tmp := Copy(tmp, 1, c);  // get path
-      if tmp = '/' then tmp := ''; // just delimiter; ignore it
-      sfi.fPath := sfi.fPath+tmp;
-    end;
-    sfi.fPath := normalizePath(sfi.fPath);
-    if (length(sfi.fPath) = 0) and (length(sfi.fName) = 0) then sfi.Free else Inc(f);
-  end;
-end;
-
-destructor TSFSVolume.Destroy ();
-begin
-  Clear();
-  FreeAndNil(fFiles);
-  inherited Destroy();
-end;
-
-procedure TSFSVolume.Clear ();
-begin
-  fFiles.Clear();
-end;
-
-function TSFSVolume.FindFile (const fPath, fName: AnsiString): Integer;
-begin
-  if fFiles = nil then result := -1
-  else
-  begin
-    result := fFiles.Count;
-    while result > 0 do
-    begin
-      Dec(result);
-      if fFiles[result] <> nil then
-      begin
-        if StrEquCI1251(fPath, TSFSFileInfo(fFiles[result]).fPath) and
-           StrEquCI1251(fName, TSFSFileInfo(fFiles[result]).fName) then exit;
-      end;
-    end;
-    result := -1;
-  end;
-end;
-
-function TSFSVolume.GetFileCount (): Integer;
-begin
-  if fFiles = nil then result := 0 else result := fFiles.Count;
-end;
-
-function TSFSVolume.GetFiles (index: Integer): TSFSFileInfo;
-begin
-  if fFiles = nil then result := nil
-  else
-  begin
-    if (index < 0) or (index >= fFiles.Count) then result := nil
-    else result := TSFSFileInfo(fFiles[index]);
-  end;
-end;
-
-function TSFSVolume.OpenFileEx (const fName: AnsiString): TStream;
-var
-  fp, fn: AnsiString;
-  f, ls: Integer;
-begin
-  fp := fName;
-  // normalize name, find split position
-  if (fp <> '') and ((fp[1] = '/') or (fp[1] = '\')) then Delete(fp, 1, 1);
-  ls := 0;
-  for f := 1 to Length(fp) do
-  begin
-    if fp[f] = '\' then fp[f] := '/';
-    if fp[f] = '/' then ls := f;
-  end;
-  fn := Copy(fp, ls+1, Length(fp));
-  fp := Copy(fp, 1, ls);
-  f := FindFile(fp, fn);
-  if f = -1 then raise ESFSError.Create('file not found: "'+fName+'"');
-  result := OpenFileByIndex(f);
-  if result = nil then raise ESFSError.Create('file not found: "'+fName+'"');
-end;
-
-
-{ TSFSFileList }
-constructor TSFSFileList.Create (const pVolume: TSFSVolume);
-var
-  f: Integer;
-begin
-  inherited Create();
-  ASSERT(pVolume <> nil);
-  f := FindVolumeInfoByVolumeInstance(pVolume);
-  ASSERT(f <> -1);
-  fVolume := pVolume;
-  Inc(TVolumeInfo(volumes[f]).fOpenedFilesCount); // íå ïîçâîëèì óáèòü çàïèñü!
-end;
-
-destructor TSFSFileList.Destroy ();
-var
-  f: Integer;
-begin
-  f := FindVolumeInfoByVolumeInstance(fVolume);
-  ASSERT(f <> -1);
-  Dec(TVolumeInfo(volumes[f]).fOpenedFilesCount);
-  // óáü¸ì çàïèñü, åñëè îíà âðåìåííàÿ, è â íåé íåò áîëüøå íè÷åãî îòêðûòîãî
-  if (gcdisabled = 0) and not TVolumeInfo(volumes[f]).fPermanent and (TVolumeInfo(volumes[f]).fOpenedFilesCount < 1) then
-  begin
-    {$IFDEF SFS_VOLDEBUG}writeln('002: destroying volume "', TVolumeInfo(volumes[f]).fPackName, '"');{$ENDIF}
-    volumes[f] := nil;
-  end;
-  inherited Destroy();
-end;
-
-function TSFSFileList.GetCount (): Integer;
-begin
-  result := fVolume.fFiles.Count;
-end;
-
-function TSFSFileList.GetFiles (index: Integer): TSFSFileInfo;
-begin
-  if (index < 0) or (index >= fVolume.fFiles.Count) then result := nil
-  else result := TSFSFileInfo(fVolume.fFiles[index]);
-end;
-
-
-procedure SFSRegisterVolumeFactory (factory: TSFSVolumeFactory);
-var
-  f: Integer;
-begin
-  if factory = nil then exit;
-  if factories.IndexOf(factory) <> -1 then
-    raise ESFSError.Create('duplicate factories are not allowed');
-  f := factories.IndexOf(nil);
-  if f = -1 then factories.Add(factory) else factories[f] := factory;
-end;
-
-procedure SFSUnregisterVolumeFactory (factory: TSFSVolumeFactory);
-var
-  f: Integer;
-  c: Integer;
-begin
-  if factory = nil then exit;
-  f := factories.IndexOf(factory);
-  if f = -1 then raise ESFSError.Create('can''t unregister nonexisting factory');
-  c := 0; while c < volumes.Count do
-  begin
-    if (volumes[c] <> nil) and (TVolumeInfo(volumes[c]).fFactory = factory) then volumes[c] := nil;
-    Inc(c);
-  end;
-  factories[f] := nil;
-end;
-
-
-function SFSAddDataFileEx (dataFileName: AnsiString; ds: TStream; top, permanent: Integer): Integer;
-// dataFileName ìîæåò èìåòü ïðåôèêñ òèïà "zip:" (ñì. âûøå: IsMyPrefix).
-// ìîæåò âûêèíóòü èñêëþ÷åíèå!
-// top:
-//   <0: äîáàâèòü â íà÷àëî ñïèñêà ïîèñêà.
-//   =0: íå ìåíÿòü.
-//   >0: äîáàâèòü â êîíåö ñïèñêà ïîèñêà.
-// permanent:
-//   <0: ñîçäàòü "âðåìåííûé" òîì.
-//   =0: íå ìåíÿòü ôëàæîê ïîñòîÿíñòâà.
-//   >0: ñîçäàòü "ïîñòîÿííûé" òîì.
-// åñëè ds <> nil, òî ñîçäà¸ò ñáîðíèê èç ïîòîêà. åñëè ñáîðíèê ñ èìåíåì
-// dataFileName óæå çàðåãèñòðèðîâàí, òî ïàäàåò íàôèã.
-// âîçâðàùàåò èíäåêñ â volumes.
-// óìååò äåëàòü ðåêóðñèþ.
-var
-  fac: TSFSVolumeFactory;
-  vol: TSFSVolume;
-  vi: TVolumeInfo;
-  f: Integer;
-  st, st1: TStream;
-  pfx: AnsiString;
-  fn, vfn, tmp: AnsiString;
-begin
-  f := Pos('::', dataFileName);
-  if f <> 0 then
-  begin
-    // ðåêóðñèâíîå îòêðûòèå.
-    // ðàçîáü¸ì dataFileName íà èìÿ ñáîðíèêà è îñòàòîê.
-    // pfx áóäåò èìåíåì ñáîðíèêà, dataFileName -- îñòàòêîì.
-    pfx := Copy(dataFileName, 1, f-1); Delete(dataFileName, 1, f+1);
-    // ñíà÷àëà îòêðîåì ïåðâûé ñïèñîê...
-    result := SFSAddDataFileEx(pfx, ds, 0, 0);
-    // ...òåïåðü ïðîäîëæèì ñ îñòàòêîì.
-    // óçíàåì, êàêîå ôàéëî îòêðûâàòü.
-    // âûêîâûðÿåì ïåðâûé "::" ïðåôèêñ (ýòî áóäåò èìÿ ôàéëà).
-    f := Pos('::', dataFileName); if f = 0 then f := Length(dataFileName)+1;
-    fn := Copy(dataFileName, 1, f-1); Delete(dataFileName, 1, f-1);
-    // dataFileName õðàíèò îñòàòîê.
-    // èçâëå÷¸ì èìÿ ôàéëà:
-    SplitDataName(fn, pfx, tmp, vfn);
-    // îòêðîåì ýòîò ôàéë
-    vi := TVolumeInfo(volumes[result]); st := nil;
-    try
-      st := vi.fVolume.OpenFileEx(tmp);
-      st1 := TOwnedPartialStream.Create(vi, st, 0, st.Size, true);
-    except
-      FreeAndNil(st);
-      // óäàëèì íåèñïîëüçóåìûé âðåìåííûé òîì.
-      if (gcdisabled = 0) and not vi.fPermanent and (vi.fOpenedFilesCount < 1) then volumes[result] := nil;
-      raise;
-    end;
-    // óðà. îòêðûëè ôàéë. êèäàåì â âîçäóõ ÷åï÷èêè, ïðîäîëæàåì ðàçâëå÷åíèå.
-    fn := fn+dataFileName;
-    try
-      st1.Position := 0;
-      result := SFSAddDataFileEx(fn, st1, top, permanent);
-    except
-      st1.Free(); // à âîò íå çàëàäèëîñü. çàêðûëè îòêðûòîå ôàéëî, âûëåòåëè.
-      raise;
-    end;
-    exit;
-  end;
-
-  // îáûêíîâåííîå íåðåêóðñèâíîå îòêðûòèå.
-  SplitDataName(dataFileName, pfx, fn, vfn);
-
-  f := FindVolumeInfo(vfn);
-  if f <> -1 then
-  begin
-    if ds <> nil then raise ESFSError.Create('subdata name conflict');
-    if permanent <> 0 then TVolumeInfo(volumes[f]).fPermanent := (permanent > 0);
-    if top = 0 then result := f
-    else if top < 0 then result := 0
-    else result := volumes.Count-1;
-    if result <> f then volumes.Move(f, result);
-    exit;
-  end;
-
-  if ds <> nil then st := ds
-  else st := TFileStream.Create(fn, fmOpenRead or {fmShareDenyWrite}fmShareDenyNone);
-  st.Position := 0;
-
-  volumes.Pack();
-
-  fac := nil; vol := nil;
-  try
-    for f := 0 to factories.Count-1 do
-    begin
-      fac := TSFSVolumeFactory(factories[f]);
-      if fac = nil then continue;
-      if (pfx <> '') and not fac.IsMyVolumePrefix(pfx) then continue;
-      st.Position := 0;
-      try
-        if ds <> nil then vol := fac.Produce(pfx, '', st)
-        else vol := fac.Produce(pfx, fn, st);
-      except
-        vol := nil;
-      end;
-      if vol <> nil then break;
-    end;
-    if vol = nil then raise ESFSError.Create('no factory for "'+dataFileName+'"');
-  except
-    if st <> ds then st.Free();
-    raise;
-  end;
-
-  vi := TVolumeInfo.Create();
-  try
-    if top < 0 then
-    begin
-      result := 0;
-      volumes.Insert(0, vi);
-    end
-    else result := volumes.Add(vi);
-  except
-    vol.Free();
-    if st <> ds then st.Free();
-    vi.Free();
-    raise;
-  end;
-
-  vi.fFactory := fac;
-  vi.fVolume := vol;
-  vi.fPackName := vfn;
-  vi.fStream := st;
-  vi.fPermanent := (permanent > 0);
-  vi.fNoDiskFile := (ds <> nil);
-  vi.fOpenedFilesCount := 0;
-end;
-
-function SFSAddSubDataFile (const virtualName: AnsiString; ds: TStream; top: Boolean=false): Boolean;
-var
-  tv: Integer;
-begin
-  ASSERT(ds <> nil);
-  try
-    if top then tv := -1 else tv := 1;
-    SFSAddDataFileEx(virtualName, ds, tv, 0);
-    result := true;
-  except
-    result := false;
-  end;
-end;
-
-function SFSAddDataFile (const dataFileName: AnsiString; top: Boolean=false): Boolean;
-var
-  tv: Integer;
-begin
-  try
-    if top then tv := -1 else tv := 1;
-    SFSAddDataFileEx(dataFileName, nil, tv, 1);
-    result := true;
-  except
-    result := false;
-  end;
-end;
-
-function SFSAddDataFileTemp (const dataFileName: AnsiString; top: Boolean=false): Boolean;
-var
-  tv: Integer;
-begin
-  try
-    if top then tv := -1 else tv := 1;
-    SFSAddDataFileEx(dataFileName, nil, tv, 0);
-    result := true;
-  except
-    result := false;
-  end;
-end;
-
-
-
-function SFSExpandDirName (const s: AnsiString): AnsiString;
-var
-  f, e: Integer;
-  es: AnsiString;
-begin
-  f := 1; result := s;
-  while f < Length(result) do
-  begin
-    while (f < Length(result)) and (result[f] <> '<') do Inc(f);
-    if f >= Length(result) then exit;
-    e := f; while (e < Length(result)) and (result[e] <> '>') do Inc(e);
-    es := Copy(result, f, e+1-f);
-
-    if es = '<currentdir>' then es := GetCurrentDir
-    else if es = '<exedir>' then es := ExtractFilePath(ParamStr(0))
-    else es := '';
-
-    if es <> '' then
-    begin
-      if (es[Length(es)] <> '/') and (es[Length(es)] <> '\') then es := es+'/';
-      Delete(result, f, e+1-f);
-      Insert(es, result, f);
-      Inc(f, Length(es));
-    end
-    else f := e+1;
-  end;
-end;
-
-function SFSFileOpenEx (const fName: AnsiString): TStream;
-var
-  dataFileName, fn: AnsiString;
-  f: Integer;
-  vi: TVolumeInfo;
-  diskChecked: Boolean;
-  ps: TStream;
-
-  function CheckDisk (): TStream;
-  // ïðîâåðèì, åñòü ëè ôàëî fn ãäå-òî íà äèñêàõ.
-  var
-    dfn, dirs, cdir: AnsiString;
-    f: Integer;
-  begin
-    result := nil;
-    if diskChecked or not sfsDiskEnabled then exit;
-    diskChecked := true;
-    dfn := SFSReplacePathDelims(fn, '/');
-    dirs := sfsDiskDirs; if dirs = '' then dirs := '<currentdir>';
-    while dirs <> '' do
-    begin
-      f := 1; while (f <= Length(dirs)) and (dirs[f] <> '|') do Inc(f);
-      cdir := Copy(dirs, 1, f-1); Delete(dirs, 1, f);
-      if cdir = '' then continue;
-      cdir := SFSReplacePathDelims(SFSExpandDirName(cdir), '/');
-      if cdir[Length(cdir)] <> '/' then cdir := cdir+'/';
-      try
-        result := TFileStream.Create(cdir+dfn, fmOpenRead or {fmShareDenyWrite}fmShareDenyNone);
-        exit;
-      except
-      end;
-    end;
-  end;
-
-begin
-  SplitFName(fName, dataFileName, fn);
-  if fn = '' then raise ESFSError.Create('invalid file name: "'+fName+'"');
-
-  diskChecked := false;
-
-  if dataFileName <> '' then
-  begin
-    // ïðåôèêñîâàíûé ôàéë
-    if sfsForceDiskForPrefixed then
-    begin
-      result := CheckDisk();
-      if result <> nil then exit;
-    end;
-
-    f := SFSAddDataFileEx(dataFileName, nil, 0, 0);
-    vi := TVolumeInfo(volumes[f]);
-
-    try
-      result := vi.fVolume.OpenFileEx(fn);
-      ps := TOwnedPartialStream.Create(vi, result, 0, result.Size, true);
-    except
-      result.Free();
-      if (gcdisabled = 0) and not vi.fPermanent and (vi.fOpenedFilesCount < 1) then volumes[f] := nil;
-      result := CheckDisk(); // îáëîì ñ datafile, ïðîâåðèì äèñê
-      if result = nil then raise ESFSError.Create('file not found: "'+fName+'"');
-      exit;
-    end;
-    //Inc(vi.fOpenedFilesCount);
-    result := ps;
-    exit;
-  end;
-
-  // íåïðåôèêñîâàíûé ôàéë
-  if sfsDiskFirst then
-  begin
-    result := CheckDisk();
-    if result <> nil then exit;
-  end;
-  // èùåì ïî âñåì ïåðìàíåíòíûì ïðåôèêñàì
-  f := 0;
-  while f < volumes.Count do
-  begin
-    vi := TVolumeInfo(volumes[f]);
-    if (vi <> nil) and vi.fPermanent then
-    begin
-      if vi.fVolume <> nil then
-      begin
-        result := vi.fVolume.OpenFileEx(fn);
-        if result <> nil then
-        begin
-          try
-            ps := TOwnedPartialStream.Create(vi, result, 0, result.Size, true);
-            result := ps;
-            //Inc(vi.fOpenedFilesCount);
-          except
-            FreeAndNil(result);
-          end;
-        end;
-        if result <> nil then exit;
-      end;
-    end;
-    Inc(f);
-  end;
-  result := CheckDisk();
-  if result = nil then raise ESFSError.Create('file not found: "'+fName+'"');
-end;
-
-function SFSFileOpen (const fName: AnsiString): TStream;
-begin
-  try
-    result := SFSFileOpenEx(fName);
-  except
-    result := nil;
-  end;
-end;
-
-function SFSFileList (const dataFileName: AnsiString): TSFSFileList;
-var
-  f: Integer;
-  vi: TVolumeInfo;
-begin
-  result := nil;
-  if dataFileName = '' then exit;
-
-  try
-    f := SFSAddDataFileEx(dataFileName, nil, 0, 0);
-  except
-    exit;
-  end;
-  vi := TVolumeInfo(volumes[f]);
-
-  try
-    result := TSFSFileList.Create(vi.fVolume);
-  except
-    if (gcdisabled = 0) and not vi.fPermanent and (vi.fOpenedFilesCount < 1) then volumes[f] := nil;
-  end;
-end;
-
-
-initialization
-  factories := TObjectList.Create(true);
-  volumes := TObjectList.Create(true);
-//finalization
-  //volumes.Free(); // it fails for some reason... Runtime 217 (^C hit). wtf?!
-  //factories.Free(); // not need to be done actually...
-end.
diff --git a/src/sfs/sfsPlainFS.pas b/src/sfs/sfsPlainFS.pas
deleted file mode 100644 (file)
index 51b0c0d..0000000
+++ /dev/null
@@ -1,146 +0,0 @@
-(* Copyright (C)  Doom 2D: Forever Developers
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3 of the License ONLY.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *)
-// simple grouping files w/o packing:
-//   Quake I/II .PAK (PACK)
-//   SiN .SIN (SPAK)
-//
-{$INCLUDE ../shared/a_modes.inc}
-{$SCOPEDENUMS OFF}
-{.$R+}
-unit sfsPlainFS;
-
-interface
-
-uses
-  SysUtils, Classes, Contnrs, sfs;
-
-
-type
-  TSFSPlainVolumeType = (sfspvNone, sfspvPAK, sfspvSIN);
-
-  TSFSPlainVolume = class (TSFSVolume)
-  protected
-    fType: TSFSPlainVolumeType;
-
-    procedure ReadDirectory (); override;
-
-  public
-    function OpenFileByIndex (const index: Integer): TStream; override;
-  end;
-
-  TSFSPlainVolumeFactory = class (TSFSVolumeFactory)
-  public
-    function IsMyVolumePrefix (const prefix: AnsiString): Boolean; override;
-    function Produce (const prefix, fileName: AnsiString; st: TStream): TSFSVolume; override;
-    procedure Recycle (vol: TSFSVolume); override;
-  end;
-
-
-implementation
-
-uses
-  xstreams, utils;
-
-
-{ TSFSPlainVolume }
-procedure TSFSPlainVolume.ReadDirectory ();
-var
-  dsize, dofs, esz: LongWord;
-  fi: TSFSFileInfo;
-  name: packed array [0..120] of Char;
-begin
-  if (fType <> sfspvPAK) and (fType <> sfspvSIN) then raise ESFSError.Create('invalid archive');
-  fFileStream.Seek(4, soCurrent); // skip signature
-  dofs := readLongWord(fFileStream);
-  dsize := readLongWord(fFileStream);
-  fFileStream.Position := dofs;
-  if fType = sfspvPAK then esz := 64 else esz := 128;
-  while dsize >= esz do
-  begin
-    fi := TSFSFileInfo.Create(self);
-    FillChar(name[0], length(name), 0);
-    fFileStream.ReadBuffer(name[0], esz-8);
-    fi.fName := PChar(@name[0]);
-    fi.fOfs := readLongWord(fFileStream);
-    fi.fSize := readLongWord(fFileStream);
-    Dec(dsize, esz);
-  end;
-end;
-
-function TSFSPlainVolume.OpenFileByIndex (const index: Integer): TStream;
-begin
-  result := nil;
-  if fFiles = nil then exit;
-  if (index < 0) or (index >= fFiles.Count) or (fFiles[index] = nil) then exit;
-  result := TSFSPartialStream.Create(fFileStream, TSFSFileInfo(fFiles[index]).fOfs, TSFSFileInfo(fFiles[index]).fSize, false);
-end;
-
-
-{ TSFSPlainVolumeFactory }
-function TSFSPlainVolumeFactory.IsMyVolumePrefix (const prefix: AnsiString): Boolean;
-begin
-  result :=
-    StrEquCI1251(prefix, 'pak') or
-    StrEquCI1251(prefix, 'sin');
-end;
-
-procedure TSFSPlainVolumeFactory.Recycle (vol: TSFSVolume);
-begin
-  vol.Free();
-end;
-
-function TSFSPlainVolumeFactory.Produce (const prefix, fileName: AnsiString; st: TStream): TSFSVolume;
-var
-  vt: TSFSPlainVolumeType;
-  sign: packed array [0..3] of Char;
-  dsize, dofs: Integer;
-begin
-  result := nil;
-  vt := sfspvNone;
-
-  st.ReadBuffer(sign[0], 4);
-  dofs := readLongWord(st);
-  dsize := readLongWord(st);
-  st.Seek(-12, soCurrent);
-  if sign = 'PACK' then
-  begin
-    if (dsize < 0) or (dofs < 0) or (dofs > st.Size) or (dofs+dsize > st.Size) or (dsize mod 64 <> 0) then exit;
-    vt := sfspvPAK;
-  end
-  else if sign = 'SPAK' then
-  begin
-    if (dsize < 0) or (dofs < 0) or (dofs > st.Size) or (dofs+dsize > st.Size) or (dsize mod 64 <> 0) then exit;
-    vt := sfspvSIN;
-  end;
-
-  result := TSFSPlainVolume.Create(fileName, st);
-  TSFSPlainVolume(result).fType := vt;
-  try
-    result.DoDirectoryRead();
-  except
-    FreeAndNil(result);
-    raise;
-  end;
-end;
-
-
-var
-  pakf: TSFSPlainVolumeFactory;
-initialization
-  pakf := TSFSPlainVolumeFactory.Create();
-  SFSRegisterVolumeFactory(pakf);
-//finalization
-//  SFSUnregisterVolumeFactory(pakf);
-end.
diff --git a/src/sfs/sfsZipFS.pas b/src/sfs/sfsZipFS.pas
deleted file mode 100644 (file)
index 2f4c613..0000000
+++ /dev/null
@@ -1,465 +0,0 @@
-(* Copyright (C)  Doom 2D: Forever Developers
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3 of the License ONLY.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *)
-// grouping files with packing:
-//   zip, pk3: PKZIP-compatible archives (store, deflate)
-//   dfwad   : D2D:F wad archives
-//
-{.$DEFINE SFS_DEBUG_ZIPFS}
-{$INCLUDE ../shared/a_modes.inc}
-{$SCOPEDENUMS OFF}
-{.$R+}
-unit sfsZipFS;
-
-interface
-
-uses
-  SysUtils, Classes, Contnrs, sfs;
-
-
-type
-  TSFSZipVolumeType = (sfszvNone, sfszvZIP, sfszvDFWAD);
-
-  TSFSZipVolume = class(TSFSVolume)
-  protected
-    fType: TSFSZipVolumeType;
-
-    procedure ZIPReadDirectory ();
-    procedure DFWADReadDirectory ();
-
-    procedure ReadDirectory (); override;
-
-  public
-    function OpenFileByIndex (const index: Integer): TStream; override;
-  end;
-
-  TSFSZipVolumeFactory = class(TSFSVolumeFactory)
-  public
-    function IsMyVolumePrefix (const prefix: AnsiString): Boolean; override;
-    function Produce (const prefix, fileName: AnsiString; st: TStream): TSFSVolume; override;
-    procedure Recycle (vol: TSFSVolume); override;
-  end;
-
-
-implementation
-
-uses
-  xstreams, utils;
-
-
-type
-  TSFSZipFileInfo = class(TSFSFileInfo)
-  public
-    fMethod: Byte; // 0: store; 8: deflate; 255: other
-    fPackSz: Int64; // can be -1
-  end;
-
-  TZLocalFileHeader = packed record
-    version: Byte;
-    hostOS: Byte;
-    flags: Word;
-    method: Word;
-    time: LongWord;
-    crc: LongWord;
-    packSz: LongWord;
-    unpackSz: LongWord;
-    fnameSz: Word;
-    localExtraSz: Word;
-  end;
-
-procedure readLFH (st: TStream; var hdr: TZLocalFileHeader);
-{.$IFDEF ENDIAN_LITTLE}
-begin
-  hdr.version := readByte(st);
-  hdr.hostOS := readByte(st);
-  hdr.flags := readWord(st);
-  hdr.method := readWord(st);
-  hdr.time := readLongWord(st);
-  hdr.crc := readLongWord(st);
-  hdr.packSz := readLongWord(st);
-  hdr.unpackSz := readLongWord(st);
-  hdr.fnameSz := readWord(st);
-  hdr.localExtraSz := readWord(st);
-end;
-
-
-function ZIPCheckMagic (st: TStream): Boolean;
-var
-  sign: packed array [0..3] of Char;
-begin
-  result := false;
-  st.ReadBuffer(sign[0], 4);
-  st.Seek(-4, soCurrent);
-  if (sign <> 'PK'#3#4) and (sign <> 'PK'#5#6) then exit;
-  result := true;
-end;
-
-
-function DFWADCheckMagic (st: TStream): Boolean;
-var
-  sign: packed array [0..5] of Char;
-begin
-  result := false;
-  if st.Size < 10 then exit;
-  st.ReadBuffer(sign[0], 6);
-  {fcnt :=} readWord(st);
-  st.Seek(-8, soCurrent);
-  if (sign[0] <> 'D') and (sign[1] <> 'F') and (sign[2] <> 'W') and
-     (sign[3] <> 'A') and (sign[4] <> 'D') and (sign[5] <> #$01) then exit;
-  result := true;
-end;
-
-
-{ TSFSZipVolume }
-procedure TSFSZipVolume.ZIPReadDirectory ();
-var
-  fi: TSFSZipFileInfo;
-  fname: AnsiString = '';
-  sign: packed array [0..3] of Char;
-  lhdr: TZLocalFileHeader;
-  ignoreFile: Boolean;
-  efid, efsz: Word;
-  izver: Byte;
-  izcrc: LongWord;
-  buf: PByte;
-  bufsz, f: Integer;
-  cdofs, hdrofs: Int64;
-  cdsize: LongWord;
-  fileOffsets: array of Int64 = nil;
-  nameLen, extraLen, commentLen: Word;
-  fileIdx: Integer = -1;
-begin
-  // search for central dir pointer
-  if fFileStream.size > 65636 then bufsz := 65636 else bufsz := fFileStream.size;
-  fFileStream.position := fFileStream.size-bufsz;
-  GetMem(buf, bufsz);
-  cdofs := -1;
-  cdsize := 0;
-  try
-    fFileStream.readBuffer(buf^, bufsz);
-    for f := bufsz-16 downto 4 do
-    begin
-      if (buf[f-4] = ord('P')) and (buf[f-3] = ord('K')) and (buf[f-2] = 5) and (buf[f-1] = 6) then
-      begin
-        cdsize := LongWord(buf[f+8])+(LongWord(buf[f+9])<<8)+(LongWord(buf[f+10])<<16)+(LongWord(buf[f+11])<<24);
-        cdofs := Int64(buf[f+12])+(Int64(buf[f+13])<<8)+(Int64(buf[f+14])<<16)+(Int64(buf[f+15])<<24);
-        break;
-      end;
-    end;
-  finally
-    FreeMem(buf);
-  end;
-
-  if (cdofs >= 0) and (cdsize > 0) then
-  begin
-    // wow, we got central directory! process it
-    fFileStream.position := cdofs;
-    while cdsize >= 4 do
-    begin
-      Dec(cdsize, 4);
-      fFileStream.readBuffer(sign, 4);
-      if sign = 'PK'#1#2 then
-      begin
-        if cdsize < 42 then break;
-        Dec(cdsize, 42);
-        // skip uninteresting fields
-        fFileStream.seek(2+2+2+2+2+2+4+4+4, soCurrent);
-        nameLen := readWord(fFileStream);
-        extraLen := readWord(fFileStream);
-        commentLen := readWord(fFileStream);
-        // skip uninteresting fields
-        fFileStream.seek(2+2+4, soCurrent);
-        hdrofs := readLongWord(fFileStream);
-        // now skip name, extra and comment
-        if cdsize < nameLen+extraLen+commentLen then break;
-        Dec(cdsize, nameLen+extraLen+commentLen);
-        fFileStream.seek(nameLen+extraLen+commentLen, soCurrent);
-        SetLength(fileOffsets, length(fileOffsets)+1);
-        fileOffsets[high(fileOffsets)] := hdrofs;
-        //writeln('file #', high(fileOffsets), ' found at ', hdrofs);
-      end
-      else if sign = 'PK'#7#8 then
-      begin
-        if cdsize < 3*4 then break;
-        Dec(cdsize, 3*4);
-        fFileStream.seek(3*4, soCurrent);
-      end
-      else
-      begin
-        break;
-      end;
-    end;
-    if length(fileOffsets) = 0 then exit; // no files at all
-    fileIdx := 0;
-  end
-  else
-  begin
-    fFileStream.position := 0;
-  end;
-
-  // read local directory
-  repeat
-    if fileIdx >= 0 then
-    begin
-      if fileIdx > High(fileOffsets) then break;
-      //writeln('reading file #', fileIdx, ' at ', fileOffsets[fileIdx]);
-      fFileStream.position := fileOffsets[fileIdx];
-      Inc(fileIdx);
-    end;
-
-    while true do
-    begin
-      fFileStream.ReadBuffer(sign[0], Length(sign));
-      // skip data descriptor
-      if sign = 'PK'#7#8 then
-      begin
-        fFileStream.seek(3*4, soCurrent);
-        continue;
-      end;
-      break;
-    end;
-    if sign <> 'PK'#3#4 then break;
-
-    ignoreFile := false;
-
-    readLFH(fFileStream, lhdr);
-
-    fi := TSFSZipFileInfo.Create(self);
-    fi.fPackSz := 0;
-    fi.fMethod := 0;
-
-    SetLength(fname, lhdr.fnameSz);
-    if lhdr.fnameSz > 0 then
-    begin
-      fFileStream.ReadBuffer(fname[1], length(fname));
-      fi.fName := utf8to1251(fname);
-    end;
-
-    // here we should process extra field: it may contain utf8 filename
-    while lhdr.localExtraSz >= 4 do
-    begin
-      efid := readWord(fFileStream);
-      efsz := readWord(fFileStream);
-      Dec(lhdr.localExtraSz, 4);
-      if efsz > lhdr.localExtraSz then break;
-      // Info-ZIP Unicode Path Extra Field?
-      if (efid = $7075) and (efsz > 5) then
-      begin
-        fFileStream.ReadBuffer(izver, 1);
-        Dec(efsz, 1);
-        Dec(lhdr.localExtraSz, 1);
-        if izver = 1 then
-        begin
-          //writeln('!!!!!!!!!!!!');
-          Dec(lhdr.localExtraSz, efsz);
-          fFileStream.ReadBuffer(izcrc, 4); // name crc, ignore it for now
-          Dec(efsz, 4);
-          SetLength(fname, efsz);
-          if length(fname) > 0 then fFileStream.readBuffer(fname[1], length(fname));
-          fi.fName := utf8to1251(fname);
-          //writeln('++++++ [', fi.fName, ']');
-          efsz := 0;
-        end;
-      end;
-      // skip it
-      if efsz > 0 then
-      begin
-        fFileStream.Seek(efsz, soCurrent);
-        Dec(lhdr.localExtraSz, efsz);
-      end;
-    end;
-    // skip the rest
-    if lhdr.localExtraSz > 0 then fFileStream.Seek(lhdr.localExtraSz, soCurrent);
-
-    if (lhdr.flags and 1) <> 0 then
-    begin
-      // encrypted file: skip it
-      ignoreFile := true;
-    end;
-
-    if (lhdr.method <> 0) and (lhdr.method <> 8) then
-    begin
-      // not stored. not deflated. skip.
-      ignoreFile := true;
-    end;
-
-    if (length(fi.fName) = 0) or (fname[length(fi.fName)] = '/') or (fname[length(fi.fName)] = '\') then
-    begin
-      ignoreFile := true;
-    end
-    else
-    begin
-      for f := 1 to length(fi.fName) do if fi.fName[f] = '\' then fi.fName[f] := '/';
-    end;
-
-    fi.fOfs := fFileStream.Position;
-    fi.fSize := lhdr.unpackSz;
-    fi.fPackSz := lhdr.packSz;
-    fi.fMethod := lhdr.method;
-    if fi.fMethod = 0 then fi.fPackSz := fi.fSize;
-
-    // skip packed data
-    if fileIdx < 0 then fFileStream.Seek(lhdr.packSz, soCurrent);
-    if ignoreFile then fi.Free();
-  until false;
-  (*
-  if (sign <> 'PK'#1#2) and (sign <> 'PK'#5#6) then
-  begin
-    {$IFDEF SFS_DEBUG_ZIPFS}
-    WriteLn(ErrOutput, 'end: $', IntToHex(fFileStream.Position, 8));
-    WriteLn(ErrOutput, 'sign: $', sign[0], sign[1], '#', ord(sign[2]), '#', ord(sign[3]));
-    {$ENDIF}
-    raise ESFSError.Create('invalid .ZIP archive (no central dir)');
-  end;
-  *)
-end;
-
-
-procedure TSFSZipVolume.DFWADReadDirectory ();
-// idiotic format
-var
-  fcnt: Word;
-  fi: TSFSZipFileInfo;
-  f, c: Integer;
-  fofs, fpksize: LongWord;
-  curpath, fname: string;
-  name: packed array [0..15] of Char;
-begin
-  curpath := '';
-  fFileStream.Seek(6, soCurrent); // skip signature
-  fcnt := readWord(fFileStream);
-  if fcnt = 0 then exit;
-  // read files
-  for f := 0 to fcnt-1 do
-  begin
-    fFileStream.ReadBuffer(name[0], 16);
-    fofs := readLongWord(fFileStream);
-    fpksize := readLongWord(fFileStream);
-    c := 0;
-    fname := '';
-    while (c < 16) and (name[c] <> #0) do
-    begin
-      if name[c] = '\' then name[c] := '/'
-      else if name[c] = '/' then name[c] := '_';
-      fname := fname+name[c];
-      Inc(c);
-    end;
-    // new directory?
-    if (fofs = 0) and (fpksize = 0) then
-    begin
-      if length(fname) <> 0 then fname := fname+'/';
-      curpath := fname;
-      continue;
-    end;
-    if length(fname) = 0 then continue; // just in case
-    //writeln('DFWAD: [', curpath, '] [', fname, '] at ', fofs, ', size ', fpksize);
-    // create file record
-    fi := TSFSZipFileInfo.Create(self);
-    fi.fOfs := fofs;
-    fi.fSize := -1;
-    fi.fPackSz := fpksize;
-    fi.fName := fname;
-    fi.fPath := curpath;
-    fi.fMethod := 255;
-  end;
-end;
-
-procedure TSFSZipVolume.ReadDirectory ();
-begin
-  case fType of
-    sfszvZIP: ZIPReadDirectory();
-    sfszvDFWAD: DFWADReadDirectory();
-    else raise ESFSError.Create('invalid archive');
-  end;
-end;
-
-function TSFSZipVolume.OpenFileByIndex (const index: Integer): TStream;
-var
-  rs: TStream;
-begin
-  result := nil;
-  rs := nil;
-  if fFiles = nil then exit;
-  if (index < 0) or (index >= fFiles.Count) or (fFiles[index] = nil) then exit;
-  try
-    if TSFSZipFileInfo(fFiles[index]).fMethod = 0 then
-    begin
-      result := TSFSPartialStream.Create(fFileStream, TSFSZipFileInfo(fFiles[index]).fOfs, TSFSZipFileInfo(fFiles[index]).fSize, false);
-    end
-    else
-    begin
-      rs := TSFSPartialStream.Create(fFileStream, TSFSZipFileInfo(fFiles[index]).fOfs, TSFSZipFileInfo(fFiles[index]).fPackSz, false);
-      result := TUnZStream.Create(rs, TSFSZipFileInfo(fFiles[index]).fSize, true, (TSFSZipFileInfo(fFiles[index]).fMethod <> 255));
-    end;
-  except
-    FreeAndNil(rs);
-    result := nil;
-    exit;
-  end;
-end;
-
-
-{ TSFSZipVolumeFactory }
-function TSFSZipVolumeFactory.IsMyVolumePrefix (const prefix: AnsiString): Boolean;
-begin
-  result :=
-    StrEquCI1251(prefix, 'zip') or
-    StrEquCI1251(prefix, 'pk3') or
-    StrEquCI1251(prefix, 'dfz') or
-    StrEquCI1251(prefix, 'dfwad') or
-    StrEquCI1251(prefix, 'dfzip');
-end;
-
-procedure TSFSZipVolumeFactory.Recycle (vol: TSFSVolume);
-begin
-  vol.Free();
-end;
-
-function TSFSZipVolumeFactory.Produce (const prefix, fileName: AnsiString; st: TStream): TSFSVolume;
-var
-  vt: TSFSZipVolumeType;
-begin
-  vt := sfszvNone;
-       if ZIPCheckMagic(st) then vt := sfszvZIP
-  else if DFWADCheckMagic(st) then vt := sfszvDFWAD;
-
-  if vt <> sfszvNone then
-  begin
-    result := TSFSZipVolume.Create(fileName, st);
-    TSFSZipVolume(result).fType := vt;
-    try
-      result.DoDirectoryRead();
-    except {$IFDEF SFS_DEBUG_ZIPFS} on e: Exception do begin
-      WriteLn(errOutput, 'ZIP ERROR: [', e.ClassName, ']: ', e.Message);
-      {$ENDIF}
-      FreeAndNil(result);
-      raise;
-      {$IFDEF SFS_DEBUG_ZIPFS}end;{$ENDIF}
-    end;
-  end
-  else
-  begin
-    result := nil;
-  end;
-end;
-
-
-var
-  zipf: TSFSZipVolumeFactory;
-initialization
-  zipf := TSFSZipVolumeFactory.Create();
-  SFSRegisterVolumeFactory(zipf);
-//finalization
-//  SFSUnregisterVolumeFactory(zipf);
-end.
diff --git a/src/shared/dfzip.pas b/src/shared/dfzip.pas
deleted file mode 100644 (file)
index 0d2ac8a..0000000
+++ /dev/null
@@ -1,390 +0,0 @@
-(* Copyright (C)  Doom 2D: Forever Developers
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3 of the License ONLY.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *)
-{$INCLUDE ../shared/a_modes.inc}
-unit dfzip;
-
-  (** Based on WadCvt tool **)
-
-interface
-
-  uses SysUtils, Classes;
-
-  type
-    TFileInfo = class
-    public
-      name: AnsiString;
-      pkofs: Int64; // offset of file header
-      size: Int64;
-      pksize: Int64;
-      crc: LongWord;
-      method: Word;
-
-      constructor Create ();
-    end;
-
-  function ZipOne (ds: TStream; fname: AnsiString; st: TStream; dopack: Boolean=true): TFileInfo;
-  procedure writeCentralDir (ds: TStream; files: array of TFileInfo);
-
-implementation
-
-  uses utils, xstreams, crc, paszlib, e_log;
-
-  const
-    uni2wint: array [128..255] of Word = (
-      $0402,$0403,$201A,$0453,$201E,$2026,$2020,$2021,$20AC,$2030,$0409,$2039,$040A,$040C,$040B,$040F,
-      $0452,$2018,$2019,$201C,$201D,$2022,$2013,$2014,$003F,$2122,$0459,$203A,$045A,$045C,$045B,$045F,
-      $00A0,$040E,$045E,$0408,$00A4,$0490,$00A6,$00A7,$0401,$00A9,$0404,$00AB,$00AC,$00AD,$00AE,$0407,
-      $00B0,$00B1,$0406,$0456,$0491,$00B5,$00B6,$00B7,$0451,$2116,$0454,$00BB,$0458,$0405,$0455,$0457,
-      $0410,$0411,$0412,$0413,$0414,$0415,$0416,$0417,$0418,$0419,$041A,$041B,$041C,$041D,$041E,$041F,
-      $0420,$0421,$0422,$0423,$0424,$0425,$0426,$0427,$0428,$0429,$042A,$042B,$042C,$042D,$042E,$042F,
-      $0430,$0431,$0432,$0433,$0434,$0435,$0436,$0437,$0438,$0439,$043A,$043B,$043C,$043D,$043E,$043F,
-      $0440,$0441,$0442,$0443,$0444,$0445,$0446,$0447,$0448,$0449,$044A,$044B,$044C,$044D,$044E,$044F
-    );
-
-constructor TFileInfo.Create;
-begin
-  name := '';
-  pkofs := 0;
-  size := 0;
-  pksize := 0;
-  crc := crc32(0, nil, 0);
-  method := 0;
-end;
-
-function toUtf8 (const s: AnsiString): AnsiString;
-var
-  uc: PUnicodeChar;
-  xdc: PChar;
-  pos, f: Integer;
-begin
-  GetMem(uc, length(s)*8);
-  GetMem(xdc, length(s)*8);
-  try
-    FillChar(uc^, length(s)*8, 0);
-    FillChar(xdc^, length(s)*8, 0);
-    pos := 0;
-    for f := 1 to length(s) do
-    begin
-      if ord(s[f]) < 128 then
-        uc[pos] := UnicodeChar(ord(s[f]))
-      else
-        uc[pos] := UnicodeChar(uni2wint[ord(s[f])]);
-      Inc(pos);
-    end;
-    FillChar(xdc^, length(s)*8, 0);
-    f := UnicodeToUtf8(xdc, length(s)*8, uc, pos);
-    while (f > 0) and (xdc[f-1] = #0) do Dec(f);
-    SetLength(result, f);
-    Move(xdc^, result[1], f);
-  finally
-    FreeMem(xdc);
-    FreeMem(uc);
-  end;
-end;
-
-// returs crc
-function zpack (ds: TStream; ss: TStream; var aborted: Boolean): LongWord;
-const
-  IBSize = 65536;
-  OBSize = 65536;
-var
-  zst: TZStream;
-  ib, ob: PByte;
-  err: Integer;
-  rd: Integer;
-  eof: Boolean;
-  crc: LongWord;
-  dstp, srcsize: Int64;
-begin
-  result := 0;
-  //aborted := true; exit;
-  aborted := false;
-  crc := crc32(0, nil, 0);
-  GetMem(ib, IBSize);
-  GetMem(ob, OBSize);
-  ss.position := 0;
-  dstp := ds.position;
-  srcsize := ss.size;
-  try
-    zst.next_out := ob;
-    zst.avail_out := OBSize;
-    zst.next_in := ib;
-    zst.avail_in := 0;
-    err := deflateInit2(zst, Z_BEST_COMPRESSION, Z_DEFLATED, -15, 9, 0);
-    if err <> Z_OK then raise Exception.Create(zerror(err));
-    try
-      eof := false;
-      repeat
-        if zst.avail_in = 0 then
-        begin
-          // read input buffer part
-          rd := ss.read(ib^, IBSize);
-          if rd < 0 then raise Exception.Create('reading error');
-          //writeln('  read ', rd, ' bytes');
-          eof := (rd = 0);
-          if rd <> 0 then begin crc := crc32(crc, Pointer(ib), rd); result := crc; end;
-          zst.next_in := ib;
-          zst.avail_in := rd;
-        end;
-        // now process the whole input
-        while zst.avail_in > 0 do
-        begin
-          err := deflate(zst, Z_NO_FLUSH);
-          if err <> Z_OK then raise Exception.Create(zerror(err));
-          if zst.avail_out < OBSize then
-          begin
-            //writeln('  written ', OBSize-zst.avail_out, ' bytes');
-            if ds.position+(OBSize-zst.avail_out)-dstp >= srcsize then
-            begin
-              // this will be overwritten anyway
-              aborted := true;
-              exit;
-            end;
-            ds.writeBuffer(ob^, OBSize-zst.avail_out);
-            zst.next_out := ob;
-            zst.avail_out := OBSize;
-          end;
-        end;
-      until eof;
-      // do leftovers
-      while true do
-      begin
-        zst.avail_in := 0;
-        err := deflate(zst, Z_FINISH);
-        if (err <> Z_OK) and (err <> Z_STREAM_END) then raise Exception.Create(zerror(err));
-        if zst.avail_out < OBSize then
-        begin
-          //writeln('  .written ', OBSize-zst.avail_out, ' bytes');
-          if ds.position+(OBSize-zst.avail_out)-dstp >= srcsize then
-          begin
-            // this will be overwritten anyway
-            aborted := true;
-            exit;
-          end;
-          ds.writeBuffer(ob^, OBSize-zst.avail_out);
-          zst.next_out := ob;
-          zst.avail_out := OBSize;
-        end;
-        if err <> Z_OK then break;
-      end;
-      // succesfully flushed?
-      if (err <> Z_STREAM_END) then raise Exception.Create(zerror(err));
-    finally
-      deflateEnd(zst);
-    end;
-  finally
-    FreeMem(ob);
-    FreeMem(ib);
-  end;
-end;
-
-// this will write "extra field length" and extra field itself
-{$IFDEF UTFEXTRA}
-const UtfFlags = 0;
-
-type
-  TByteArray = array of Byte;
-
-function buildUtfExtra (fname: AnsiString): TByteArray;
-var
-  crc: LongWord;
-  fu: AnsiString;
-  sz: Word;
-begin
-  fu := toUtf8(fname);
-  if fu = fname then begin result := nil; exit; end; // no need to write anything
-  crc := crc32(0, @fname[1], length(fname));
-  sz := 2+2+1+4+length(fu);
-  SetLength(result, sz);
-  result[0] := ord('u');
-  result[1] := ord('p');
-  Dec(sz, 4);
-  result[2] := sz and $ff;
-  result[3] := (sz shr 8) and $ff;
-  result[4] := 1;
-  result[5] := crc and $ff;
-  result[6] := (crc shr 8) and $ff;
-  result[7] := (crc shr 16) and $ff;
-  result[8] := (crc shr 24) and $ff;
-  Move(fu[1], result[9], length(fu));
-end;
-{$ELSE}
-const UtfFlags = (1 shl 10); // bit 11
-{$ENDIF}
-
-function ZipOne (ds: TStream; fname: AnsiString; st: TStream; dopack: Boolean=true): TFileInfo;
-var
-  oldofs, nfoofs, pkdpos, rd: Int64;
-  sign: packed array [0..3] of Char;
-  buf: PChar;
-  bufsz: Integer;
-  aborted: Boolean = false;
-{$IFDEF UTFEXTRA}
-  ef: TByteArray;
-{$ENDIF}
-begin
-  result := TFileInfo.Create();
-  result.pkofs := ds.position;
-  result.size := st.size;
-  if result.size > 0 then result.method := 8 else result.method := 0;
-  if not dopack then
-  begin
-    result.method := 0;
-    result.pksize := result.size;
-  end;
-{$IFDEF UTFEXTRA}
-  result.name := fname;
-  ef := buildUtfExtra(result.name);
-{$ELSE}
-  result.name := toUtf8(fname);
-{$ENDIF}
-  // write local header
-  sign := 'PK'#3#4;
-  ds.writeBuffer(sign, 4);
-  writeInt(ds, Word($0A10)); // version to extract
-  writeInt(ds, Word(UtfFlags)); // flags
-  writeInt(ds, Word(result.method)); // compression method
-  writeInt(ds, Word(0)); // file time
-  writeInt(ds, Word(0)); // file date
-  nfoofs := ds.position;
-  writeInt(ds, LongWord(result.crc)); // crc32
-  writeInt(ds, LongWord(result.pksize)); // packed size
-  writeInt(ds, LongWord(result.size)); // unpacked size
-  writeInt(ds, Word(length(fname))); // name length
-{$IFDEF UTFEXTRA}
-  writeInt(ds, Word(length(ef))); // extra field length
-{$ELSE}
-  writeInt(ds, Word(0)); // extra field length
-{$ENDIF}
-  ds.writeBuffer(fname[1], length(fname));
-{$IFDEF UTFEXTRA}
-  if length(ef) > 0 then ds.writeBuffer(ef[0], length(ef));
-{$ENDIF}
-  if dopack then
-  begin
-    // now write packed data
-    if result.size > 0 then
-    begin
-      pkdpos := ds.position;
-      st.position := 0;
-      result.crc := zpack(ds, st, aborted);
-      result.pksize := ds.position-pkdpos;
-      if {result.pksize >= result.size} aborted then
-      begin
-        // there's no sence to pack this file, so just store it
-        st.position := 0;
-        ds.position := result.pkofs;
-        result.Free();
-        // store it
-        result := ZipOne(ds, fname, st, false);
-        exit;
-      end
-      else
-      begin
-        // fix header
-        oldofs := ds.position;
-        ds.position := nfoofs;
-        writeInt(ds, LongWord(result.crc)); // crc32
-        writeInt(ds, LongWord(result.pksize)); // crc32
-        ds.position := oldofs;
-      end;
-    end;
-  end
-  else
-  begin
-    bufsz := 1024*1024;
-    GetMem(buf, bufsz);
-    try
-      st.position := 0;
-      result.crc := crc32(0, nil, 0);
-      result.pksize := 0;
-      while result.pksize < result.size do
-      begin
-        rd := result.size-result.pksize;
-        if rd > bufsz then rd := bufsz;
-        st.readBuffer(buf^, rd);
-        ds.writeBuffer(buf^, rd);
-        Inc(result.pksize, rd);
-        result.crc := crc32(result.crc, buf, rd);
-      end;
-    finally
-      FreeMem(buf);
-    end;
-    // fix header
-    oldofs := ds.position;
-    ds.position := nfoofs;
-    writeInt(ds, LongWord(result.crc)); // crc32
-    ds.position := oldofs;
-    //write('(S) ');
-  end;
-end;
-
-
-procedure writeCentralDir (ds: TStream; files: array of TFileInfo);
-var
-  cdofs, cdend: Int64;
-  sign: packed array [0..3] of Char;
-  f: Integer;
-{$IFDEF UTFEXTRA}
-  ef: TByteArray;
-{$ENDIF}
-begin
-  cdofs := ds.position;
-  for f := 0 to high(files) do
-  begin
-{$IFDEF UTFEXTRA}
-    ef := buildUtfExtra(files[f].name);
-{$ENDIF}
-    sign := 'PK'#1#2;
-    ds.writeBuffer(sign, 4);
-    writeInt(ds, Word($0A10)); // version made by
-    writeInt(ds, Word($0010)); // version to extract
-    writeInt(ds, Word(UtfFlags)); // flags
-    writeInt(ds, Word(files[f].method)); // compression method
-    writeInt(ds, Word(0)); // file time
-    writeInt(ds, Word(0)); // file date
-    writeInt(ds, LongWord(files[f].crc));
-    writeInt(ds, LongWord(files[f].pksize));
-    writeInt(ds, LongWord(files[f].size));
-    writeInt(ds, Word(length(files[f].name))); // name length
-{$IFDEF UTFEXTRA}
-    writeInt(ds, Word(length(ef))); // extra field length
-{$ELSE}
-    writeInt(ds, Word(0)); // extra field length
-{$ENDIF}
-    writeInt(ds, Word(0)); // comment length
-    writeInt(ds, Word(0)); // disk start
-    writeInt(ds, Word(0)); // internal attributes
-    writeInt(ds, LongWord(0)); // external attributes
-    writeInt(ds, LongWord(files[f].pkofs)); // header offset
-    ds.writeBuffer(files[f].name[1], length(files[f].name));
-{$IFDEF UTFEXTRA}
-    if length(ef) > 0 then ds.writeBuffer(ef[0], length(ef));
-{$ENDIF}
-  end;
-  cdend := ds.position;
-  // write end of central dir
-  sign := 'PK'#5#6;
-  ds.writeBuffer(sign, 4);
-  writeInt(ds, Word(0)); // disk number
-  writeInt(ds, Word(0)); // disk with central dir
-  writeInt(ds, Word(length(files))); // number of files on this dist
-  writeInt(ds, Word(length(files))); // number of files total
-  writeInt(ds, LongWord(cdend-cdofs)); // size of central directory
-  writeInt(ds, LongWord(cdofs)); // central directory offset
-  writeInt(ds, Word(0)); // archive comment length
-end;
-
-end.
diff --git a/src/shared/xstreams.pas b/src/shared/xstreams.pas
deleted file mode 100644 (file)
index 36e73f9..0000000
+++ /dev/null
@@ -1,566 +0,0 @@
-(* Copyright (C)  Doom 2D: Forever Developers
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3 of the License ONLY.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *)
-// special stream classes
-{$INCLUDE a_modes.inc}
-{.$R+}
-unit xstreams;
-
-interface
-
-uses
-  SysUtils, Classes,
-  zbase{z_stream};
-
-
-type
-  XStreamError = class(Exception);
-
-  // read-only ïîòîê äëÿ èçâëå÷åíèÿ èç èñõîäíîãî òîëüêî êóñî÷êà
-  TSFSPartialStream = class(TStream)
-  protected
-    fSource: TStream;     // èñõîäíûé ïîòîê
-    fKillSource: Boolean; // óáèâàòü èñõîäíèê ïðè ïîìèðàíèè?
-    fLastReadPos: Int64;  // ïîñëåäíèé Read() îñòàíîâèëñÿ çäåñü (îòíîñ. fStartPos)
-    fCurrentPos: Int64;   // ïîñëåäíèé Seek() îñòàíîâèëñÿ çäåñü (îòíîñ. fStartPos)
-    fStartPos: Int64;     // íà÷àëî êóñî÷êà
-    fSize: Int64;         // äëèíà êóñî÷êà
-    fPreBuf: packed array of Byte; // ýòîò áóôåð áóäåò ïåðåä ôàéëîì
-
-    procedure CheckPos ();
-
-  public
-    // aSrc: ïîòîê-èñõîäíèê.
-    // aPos: íà÷àëüíàÿ ïîçèöèÿ â ïîòîêå. -1 -- ñ òåêóùåé.
-    //       åñëè aPos < òåêóùåé ïîçèöèè, òî èñõîäíûé ïîòîê äîëæåí
-    //       íîðìàëüíî ïîääåðæèâàòü Seek()!
-    // aSize: êîëè÷åñòâî áàéòèêîâ, êîòîðîå ìîæíî ïðî÷åñòü èç ïîòîêà.
-    //        åñëè ìåíüøå íóëÿ -- òî äî êîíöà.
-    // aKillSrc: óáèâàòü ëè èñõîäíûé ïîòîê, êîãäà ñàìè óìèðàåì?
-    // òàêæå ìîæåò ïðèøïàíäîðèòü ê íà÷àëó ôàéëà áóôåð. bufSz áóäåò äîáàâëåíî ê
-    // äëèíå ôàéëà.
-    constructor Create (aSrc: TStream; aPos, aSize: Int64; aKillSrc: Boolean; preBuf: Pointer=nil; bufSz: Integer=0);
-    destructor Destroy (); override;
-
-    // íîðìàëèçóåò count è ÷èòàåò.
-    function Read (var buffer; count: LongInt): LongInt; override;
-    // Write() ïðîñòî ãðîìêî ïàäàåò.
-    function Write (const buffer; count: LongInt): LongInt; override;
-    // Seek() ðåàëèçîâàíî, ÷òîáû ìîãëà ðàáîòàòü ïðîïåðòÿ Size.
-    // âîîáùå-òî ìîæíî ïåðåêðûòü ìåòîä GetSize(), íî âäðóã êàêîé
-    // áîëüíîé íà ãîëîâó êîäåð áóäåò ïîëó÷àòü ðàçìåð ïðè ïîìîùè
-    // Seek()'à?
-    function Seek (const offset: Int64; origin: TSeekOrigin): Int64; override;
-  end;
-
-  // this stream can kill both `proxied` and `guarded` streams on closing
-  TSFSGuardStream = class(TStream)
-  protected
-    fSource: TStream;        // èñõîäíûé ïîòîê
-    fGuardedStream: TStream; // ïîòîê, êîòîðûé çàâàëèì ïðè ïîìèðàíèè
-    fKillSource: Boolean;    // óáèâàòü èñõîäíèê ïðè ïîìèðàíèè?
-    fKillGuarded: Boolean;   // óáèâàòü îõðàíÿåìûé ïðè ïîìèðàíèè?
-    fGuardedFirst: Boolean;  // ïðè ñìåðòè ïåðâûì ïðèøèáàåì îõðàíÿåìîãî?
-
-  public
-    // aSrc: ïîòîê-èñõîäíèê (íà êîòîðûé çàìàïåíû îïåðàöèè ÷òåíèÿ/çàïèñè).
-    // aKillSrc: óáèâàòü ëè èñõîäíûé ïîòîê, êîãäà ñàìè óìèðàåì?
-    // aKillGuarded: óáèâàòü ëè îõðàíÿåìûé ïîòîê, êîãäà ñàìè óìèðàåì?
-    // aGuardedFirst: true: ïðè ñìåðòè ïåðâûì ïðèøèáàåì îõðàíÿåìîãî.
-    constructor Create (aSrc, aGuarded: TStream; aKillSrc, aKillGuarded: Boolean; aGuardedFirst: Boolean=true);
-    destructor Destroy (); override;
-
-    // íèæåñëåäóþùåå çàìàïëåíî íà fSource
-    function Read (var buffer; count: LongInt): LongInt; override;
-    function Write (const buffer; count: LongInt): LongInt; override;
-    function Seek (const offset: Int64; origin: TSeekOrigin): Int64; override;
-  end;
-
-  TSFSMemoryStreamRO = class(TCustomMemoryStream)
-  private
-    fFreeMem: Boolean;
-    fMem: Pointer;
-
-  public
-    constructor Create (pMem: Pointer; pSize: Integer; aFreeMem: Boolean=false);
-    destructor Destroy (); override;
-
-    function Write (const buffer; count: LongInt): LongInt; override;
-  end;
-
-  TUnZStream = class(TStream)
-  protected
-    fSrcSt: TStream;
-    fZlibSt: z_stream;
-    fBuffer: PByte;
-    fPos: Int64;
-    fSkipHeader: Boolean;
-    fSize: Int64; // can be -1
-    fSrcStPos: Int64;
-    fSkipToPos: Int64; // >0: skip to this position
-    fKillSrc: Boolean;
-
-    procedure reset ();
-    function readBuf (var buffer; count: LongInt): LongInt;
-    procedure fixPos ();
-    procedure determineSize ();
-
-  public
-    // `aSize` can be -1 if stream size is unknown
-    constructor create (asrc: TStream; aSize: Int64; aKillSrc: Boolean; aSkipHeader: boolean=false);
-    destructor destroy (); override;
-    function read (var buffer; count: LongInt): LongInt; override;
-    function write (const buffer; count: LongInt): LongInt; override;
-    function seek (const offset: Int64; origin: TSeekOrigin): Int64; override;
-  end;
-
-  // fixed memory chunk
-  TSFSMemoryChunkStream = class(TStream)
-  private
-    fFreeMem: Boolean;
-    fMemBuf: PByte;
-    fMemSize: Integer;
-    fCurPos: Integer;
-
-  public
-    // if `pMem` is `nil`, stream will allocate it
-    constructor Create (pMem: Pointer; pSize: Integer; aFreeMem: Boolean=false);
-    destructor Destroy (); override;
-
-    procedure setup (pMem: Pointer; pSize: Integer; aFreeMem: Boolean=false);
-
-    function Seek (const offset: Int64; origin: TSeekOrigin): Int64; override;
-    function Read (var buffer; count: LongInt): LongInt; override;
-    function Write (const buffer; count: LongInt): LongInt; override;
-
-    property chunkSize: Integer read fMemSize;
-    property chunkData: PByte read fMemBuf;
-  end;
-
-
-implementation
-
-uses
-  zinflate;
-
-
-{ TSFSPartialStream }
-constructor TSFSPartialStream.Create (aSrc: TStream; aPos, aSize: Int64; aKillSrc: Boolean; preBuf: Pointer=nil; bufSz: Integer=0);
-begin
-  inherited Create();
-  ASSERT(aSrc <> nil);
-  if aPos < 0 then aPos := aSrc.Position;
-  if aSize < 0 then aSize := 0;
-  fSource := aSrc;
-  fKillSource := aKillSrc;
-  fLastReadPos := 0;
-  fCurrentPos := 0;
-  fStartPos := aPos;
-  fSize := aSize;
-  if bufSz > 0 then
-  begin
-    SetLength(fPreBuf, bufSz);
-    Move(preBuf^, fPreBuf[0], bufSz);
-    Inc(fSize, bufSz);
-  end
-  else
-  begin
-    fPreBuf := nil;
-  end;
-end;
-
-destructor TSFSPartialStream.Destroy ();
-begin
-  if fKillSource then FreeAndNil(fSource);
-  inherited Destroy();
-end;
-
-procedure TSFSPartialStream.CheckPos ();
-begin
-  {
-  if fSource.Position <> fStartPos+fCurrentPos-Length(fPreBuf) then
-  begin
-    fSource.Position := fStartPos+fCurrentPos-Length(fPreBuf);
-  end;
-  }
-  if fCurrentPos >= length(fPreBuf) then
-  begin
-    //writeln('seeking at ', fCurrentPos, ' (real: ', fStartPos+fCurrentPos-Length(fPreBuf), ')');
-    fSource.Position := fStartPos+fCurrentPos-Length(fPreBuf);
-  end;
-  fLastReadPos := fCurrentPos;
-end;
-
-function TSFSPartialStream.Write (const buffer; count: LongInt): LongInt;
-begin
-  result := 0;
-  raise XStreamError.Create('can''t write to read-only stream');
-  // à íå õîäè, íåõîðîøèé, â íàø ñàäèê ãóëÿòü!
-end;
-
-function TSFSPartialStream.Read (var buffer; count: LongInt): LongInt;
-var
-  left: Int64;
-  pc: Pointer;
-  rd: LongInt;
-begin
-  if count < 0 then raise XStreamError.Create('invalid Read() call'); // ñêàçî÷íûé äîëáî¸á...
-  if count = 0 then begin result := 0; exit; end;
-  pc := @buffer;
-  result := 0;
-  if (Length(fPreBuf) > 0) and (fCurrentPos < Length(fPreBuf)) then
-  begin
-    fLastReadPos := fCurrentPos;
-    left := Length(fPreBuf)-fCurrentPos;
-    if left > count then left := count;
-    if left > 0 then
-    begin
-      Move(fPreBuf[fCurrentPos], pc^, left);
-      Inc(PChar(pc), left);
-      Inc(fCurrentPos, left);
-      fLastReadPos := fCurrentPos;
-      Dec(count, left);
-      result := left;
-      if count = 0 then exit;
-    end;
-  end;
-  CheckPos();
-  left := fSize-fCurrentPos;
-  if left < count then count := left; // è òàê ñëó÷àåòñÿ...
-  if count > 0 then
-  begin
-    rd := fSource.Read(pc^, count);
-    Inc(result, rd);
-    Inc(fCurrentPos, rd);
-    fLastReadPos := fCurrentPos;
-  end
-  else
-  begin
-    result := 0;
-  end;
-end;
-
-function TSFSPartialStream.Seek (const offset: Int64; origin: TSeekOrigin): Int64;
-begin
-  case origin of
-    soBeginning: result := offset;
-    soCurrent: result := offset+fCurrentPos;
-    soEnd: result := fSize+offset;
-    else raise XStreamError.Create('invalid Seek() call');
-    // äðóãèõ íå áûâàåò. à ó êîãî áûâàåò, òîìó ÿ íå äîêòîð.
-  end;
-  if result < 0 then result := 0
-  else if result > fSize then result := fSize;
-  fCurrentPos := result;
-end;
-
-
-{ TSFSGuardStream }
-constructor TSFSGuardStream.Create (aSrc, aGuarded: TStream; aKillSrc, aKillGuarded: Boolean; aGuardedFirst: Boolean=true);
-begin
-  inherited Create();
-  fSource := aSrc; fGuardedStream := aGuarded;
-  fKillSource := aKillSrc; fKillGuarded := aKillGuarded;
-  fGuardedFirst := aGuardedFirst;
-end;
-
-destructor TSFSGuardStream.Destroy ();
-begin
-  if fKillGuarded and fGuardedFirst then FreeAndNil(fGuardedStream);
-  if fKillSource then FreeAndNil(fSource);
-  if fKillGuarded and not fGuardedFirst then FreeAndNil(fGuardedStream);
-  inherited Destroy();
-end;
-
-function TSFSGuardStream.Read (var buffer; count: LongInt): LongInt;
-begin
-  result := fSource.Read(buffer, count);
-end;
-
-function TSFSGuardStream.Write (const buffer; count: LongInt): LongInt;
-begin
-  result := fSource.Write(buffer, count);
-end;
-
-function TSFSGuardStream.Seek (const offset: Int64; origin: TSeekOrigin): Int64;
-begin
-  result := fSource.Seek(offset, origin);
-end;
-
-
-{ TSFSMemoryStreamRO }
-constructor TSFSMemoryStreamRO.Create (pMem: Pointer; pSize: Integer; aFreeMem: Boolean=false);
-begin
-  fFreeMem := aFreeMem;
-  fMem := pMem;
-  inherited Create();
-  SetPointer(pMem, pSize);
-  Position := 0;
-end;
-
-destructor TSFSMemoryStreamRO.Destroy ();
-begin
-  if fFreeMem and (fMem <> nil) then FreeMem(fMem);
-end;
-
-function TSFSMemoryStreamRO.Write (const buffer; count: LongInt): LongInt;
-begin
-  result := 0;
-  raise XStreamError.Create('can''t write to read-only stream');
-  // ñîâñåì ñáðåíäèë...
-end;
-
-
-// ////////////////////////////////////////////////////////////////////////// //
-{ TUnZStream }
-const ZBufSize = 32768; // size of the buffer used for temporarily storing data from the child stream
-
-
-constructor TUnZStream.create (asrc: TStream; aSize: Int64; aKillSrc: Boolean; aSkipHeader: boolean=false);
-var
-  err: Integer;
-begin
-  fKillSrc := aKillSrc;
-  fPos := 0;
-  fSkipToPos := -1;
-  fSrcSt := asrc;
-  fSize := aSize;
-  GetMem(fBuffer, ZBufSize);
-  fSkipHeader := aSkipHeader;
-  fSrcStPos := fSrcSt.position;
-  FillChar(fZlibSt, sizeof(fZlibSt), 0);
-  if fSkipHeader then err := inflateInit2(fZlibSt, -MAX_WBITS) else err := inflateInit(fZlibSt);
-  if err <> Z_OK then raise XStreamError.Create(zerror(err));
-end;
-
-
-destructor TUnZStream.destroy ();
-begin
-  inflateEnd(fZlibSt);
-  FreeMem(fBuffer);
-  if fKillSrc then fSrcSt.Free();
-  inherited Destroy();
-end;
-
-
-function TUnZStream.readBuf (var buffer; count: LongInt): LongInt;
-var
-  err: Integer;
-  sz: LongInt;
-begin
-  result := 0;
-  if (fSize >= 0) and (fPos >= fSize) then exit;
-  if count > 0 then
-  begin
-    fZlibSt.next_out := @buffer;
-    fZlibSt.avail_out := count;
-    sz := fZlibSt.avail_out;
-    while fZlibSt.avail_out > 0 do
-    begin
-      if fZlibSt.avail_in = 0 then
-      begin
-        // refill the buffer
-        fZlibSt.next_in := fBuffer;
-        fZlibSt.avail_in := fSrcSt.read(Fbuffer^, ZBufSize);
-      end;
-      err := inflate(fZlibSt, Z_NO_FLUSH);
-      if (err <> Z_OK) and (err <> Z_STREAM_END) then raise XStreamError.Create(zerror(err));
-      Inc(result, sz-fZlibSt.avail_out);
-      Inc(fPos, sz-fZlibSt.avail_out);
-      sz := fZlibSt.avail_out;
-      if err = Z_STREAM_END then begin fSize := fPos; break; end;
-    end;
-  end;
-end;
-
-
-procedure TUnZStream.fixPos ();
-var
-  buf: array [0..4095] of Byte;
-  rd, rr: LongInt;
-begin
-  if fSkipToPos < 0 then exit;
-  //writeln('fixing pos: fPos=', fPos, '; fSkipToPos=', fSkipToPos);
-  if fSkipToPos < fPos then reset();
-  while fPos < fSkipToPos do
-  begin
-    if fSkipToPos-fPos > 4096 then rd := 4096 else rd := LongInt(fSkipToPos-fPos);
-    //writeln('  reading ', rd, ' bytes...');
-    rr := readBuf(buf, rd);
-    //writeln('  got ', rr, ' bytes; fPos=', fPos, '; fSkipToPos=', fSkipToPos);
-    if rr <= 0 then raise XStreamError.Create('seek error');
-  end;
-  //writeln('  pos: fPos=', fPos, '; fSkipToPos=', fSkipToPos);
-  fSkipToPos := -1;
-end;
-
-
-procedure TUnZStream.determineSize ();
-var
-  buf: array [0..4095] of Byte;
-  rd: LongInt;
-  opos: Int64;
-begin
-  if fSize >= 0 then exit;
-  opos := fPos;
-  try
-    //writeln('determining unzstream size...');
-    while true do
-    begin
-      rd := readBuf(buf, 4096);
-      if rd = 0 then break;
-    end;
-    fSize := fPos;
-    //writeln('  unzstream size is ', fSize);
-  finally
-    if fSkipToPos < 0 then fSkipToPos := opos;
-  end;
-end;
-
-
-function TUnZStream.read (var buffer; count: LongInt): LongInt;
-begin
-  if fSkipToPos >= 0 then fixPos();
-  result := readBuf(buffer, count);
-end;
-
-
-function TUnZStream.write (const buffer; count: LongInt): LongInt;
-begin
-  result := 0;
-  raise XStreamError.Create('can''t write to read-only stream');
-end;
-
-
-procedure TUnZStream.reset ();
-var
-  err: Integer;
-begin
-  //writeln('doing RESET');
-  fSrcSt.position := fSrcStPos;
-  fPos := 0;
-  inflateEnd(fZlibSt);
-  FillChar(fZlibSt, sizeof(fZlibSt), 0);
-  if fSkipHeader then err := inflateInit2(fZlibSt, -MAX_WBITS) else err := inflateInit(fZlibSt);
-  if err <> Z_OK then raise XStreamError.Create(zerror(err));
-end;
-
-
-function TUnZStream.Seek (const offset: Int64; origin: TSeekOrigin): Int64;
-var
-  cpos: Int64;
-begin
-  cpos := fPos;
-  if fSkipToPos >= 0 then cpos := fSkipToPos;
-  case origin of
-    soBeginning: result := offset;
-    soCurrent: result := offset+cpos;
-    soEnd: begin determineSize(); result := fSize+offset; end;
-    else raise XStreamError.Create('invalid Seek() call');
-    // äðóãèõ íå áûâàåò. à ó êîãî áûâàåò, òîìó ÿ íå äîêòîð.
-  end;
-  if result < 0 then result := 0;
-  fSkipToPos := result;
-  //writeln('seek: ofs=', offset, '; origin=', origin, '; result=', result);
-end;
-
-
-// ////////////////////////////////////////////////////////////////////////// //
-constructor TSFSMemoryChunkStream.Create (pMem: Pointer; pSize: Integer; aFreeMem: Boolean=false);
-begin
-  fMemBuf := nil;
-  fFreeMem := false;
-  fMemSize := 0;
-  fCurPos := 0;
-  setup(pMem, pSize, aFreeMem);
-end;
-
-
-procedure TSFSMemoryChunkStream.setup (pMem: Pointer; pSize: Integer; aFreeMem: Boolean=false);
-begin
-  if fFreeMem then FreeMem(fMemBuf);
-  fMemBuf := nil;
-  fFreeMem := false;
-  fMemSize := 0;
-  fCurPos := 0;
-  if (pSize < 0) then raise XStreamError.Create('invalid chunk size');
-  if (pMem = nil) then
-  begin
-    if (pSize > 0) then
-    begin
-      GetMem(pMem, pSize);
-      if (pMem = nil) then raise XStreamError.Create('out of memory for chunk');
-      aFreeMem := true;
-    end
-    else
-    begin
-      aFreeMem := false;
-    end;
-  end;
-  fFreeMem := aFreeMem;
-  fMemBuf := PByte(pMem);
-  fMemSize := pSize;
-end;
-
-
-destructor TSFSMemoryChunkStream.Destroy ();
-begin
-  if fFreeMem then FreeMem(fMemBuf);
-  inherited;
-end;
-
-
-function TSFSMemoryChunkStream.Seek (const offset: Int64; origin: TSeekOrigin): Int64;
-begin
-  case origin of
-    soBeginning: result := offset;
-    soCurrent: result := offset+fCurPos;
-    soEnd: result := fMemSize+offset;
-    else raise XStreamError.Create('invalid Seek() call');
-  end;
-  if (result < 0) then raise XStreamError.Create('invalid Seek() call');
-  if (result > fMemSize) then result := fMemSize;
-  fCurPos := result;
-end;
-
-
-function TSFSMemoryChunkStream.Read (var buffer; count: LongInt): LongInt;
-var
-  left: Integer;
-begin
-  if (count < 0) then raise XStreamError.Create('negative read');
-  left := fMemSize-fCurPos;
-  if (left < 0) then raise XStreamError.Create('internal error in TSFSMemoryChunkStream (read)');
-  if (count > left) then count := left;
-  if (count > 0) then Move((fMemBuf+fCurPos)^, buffer, count);
-  Inc(fCurPos, count);
-  result := count;
-end;
-
-
-function TSFSMemoryChunkStream.Write (const buffer; count: LongInt): LongInt;
-var
-  left: Integer;
-begin
-  if (count < 0) then raise XStreamError.Create('negative write');
-  left := fMemSize-fCurPos;
-  if (left < 0) then raise XStreamError.Create('internal error in TSFSMemoryChunkStream (write)');
-  if (count > left) then count := left;
-  if (count > 0) then Move(buffer, (fMemBuf+fCurPos)^, count);
-  Inc(fCurPos, count);
-  result := count;
-end;
-
-
-end.