DEADSOFTWARE

dfwad: reimplement dfwad reader/writer
authorDeaDDooMER <deaddoomer@deadsoftware.ru>
Wed, 13 Sep 2023 15:34:18 +0000 (18:34 +0300)
committerDeaDDooMER <deaddoomer@deadsoftware.ru>
Wed, 13 Sep 2023 15:35:19 +0000 (18:35 +0300)
src/shared/WADEDITOR_dfwad.pas

index f011d30a62f531b0602d955ee0f7796fba23d9c7..97097bb2f7c47ac3fa2c406ee6dfb8368810f9d6 100644 (file)
+{$INCLUDE ../shared/a_modes.inc}
+
 unit WADEDITOR_dfwad;
 
-{
------------------------------------
-WADEDITOR.PAS ÂÅÐÑÈß ÎÒ 26.08.08
+interface
 
-Ïîääåðæêà âàäîâ âåðñèè 1
------------------------------------
-}
+  uses Classes, WADEDITOR;
 
-interface
+  type
+    TData = class
+      ref: Integer;          // number of links
+      pos: Int64;            // position in source (if pos < 0 -> not in source file)
+      csize: Int64;          // compressed size
+      usize: Int64;          // decompressed size (usize < 0 -> unknown)
+      stream: TMemoryStream; // copy of compressed data
+    end;
 
-uses WADEDITOR, WADSTRUCT;
-
-type
-  TWADEditor_1 = class sealed(WADEDITOR.TWADEditor)
-   private
-    FResData:   Pointer;
-    FResTable:  packed array of TResourceTableRec_1;
-    FHeader:    TWADHeaderRec_1;
-    FDataSize:  LongWord;
-    FOffset:    LongWord;
-    FFileName:  string;
-    FWADOpened: Byte;
-    FLastError: Integer;
-    FVersion:   Byte;
-    function LastErrorString(): string;
-    function GetResName(ResName: string): Char16;
-   public
-    constructor Create();
-    destructor Destroy(); override;
-    procedure FreeWAD(); override;
-    function  ReadFile2(FileName: string): Boolean; override;
-    function  ReadMemory(Data: Pointer; Len: LongWord): Boolean; override;
-    procedure CreateImage(); override;
-    function AddResource(Data: Pointer; Len: LongWord; Name: string;
-                         Section: string): Boolean; override; overload;
-    function AddResource(FileName, Name, Section: string): Boolean; override; overload;
-    function AddAlias(Res, Alias: string): Boolean; override;
-    procedure AddSection(Name: string); override;
-    procedure RemoveResource(Section, Resource: string); override;
-    procedure SaveTo(FileName: string); override;
-    function HaveResource(Section, Resource: string): Boolean; override;
-    function HaveSection(Section: string): Boolean; override;
-    function GetResource(Section, Resource: string; var pData: Pointer;
-                         var Len: Integer): Boolean; override;
-    function GetSectionList(): SArray; override;
-    function GetResourcesList(Section: string): SArray; override;
-
-    function GetLastError: Integer; override;
-    function GetLastErrorStr: String; override;
-    function GetResourcesCount: Word; override;
-    function GetVersion: Byte; override;
-
-    // property GetLastError: Integer read FLastError;
-    // property GetLastErrorStr: string read LastErrorString;
-    // property GetResourcesCount: Word read FHeader.RecordsCount;
-    // property GetVersion: Byte read FVersion;
-  end;
-
-const
-  DFWAD_NOERROR                = 0;
-  DFWAD_ERROR_WADNOTFOUND      = -1;
-  DFWAD_ERROR_CANTOPENWAD      = -2;
-  DFWAD_ERROR_RESOURCENOTFOUND = -3;
-  DFWAD_ERROR_FILENOTWAD       = -4;
-  DFWAD_ERROR_WADNOTLOADED     = -5;
-  DFWAD_ERROR_READRESOURCE     = -6;
-  DFWAD_ERROR_READWAD          = -7;
-  DFWAD_ERROR_WRONGVERSION     = -8;
+    TResource = record
+      name: AnsiString;
+      data: TData;
+    end;
+
+    TSection = record
+      name: AnsiString;
+      list: array of TResource;
+    end;
+
+    PResource = ^TResource;
+    PSection = ^TSection;
+
+    TDFWEditor = class sealed(WADEDITOR.TWADEditor)
+      private
+        FSection: array of TSection;
+        FData: array of TData;
+        FStream: TStream;
+        FLastError: Integer;
+        FVersion: Byte;
+
+        function FindSectionIDRAW(name: AnsiString; caseSensitive: Boolean): Integer;
+        function FindSectionRAW(name: AnsiString; caseSensitive: Boolean): PSection;
+        function InsertSectionRAW(name: AnsiString): PSection;
+
+        function FindSectionID(name: AnsiString): Integer;
+        function FindSection(name: AnsiString): PSection;
+        function InsertSection(name: AnsiString): PSection;
+
+        function FindDataID(pos: Int64): Integer;
+        function FindData(pos: Int64): TData;
+        function InsertData(ref, pos, csize, usize: Int64; stream: TMemoryStream): TData;
+
+        function InsertFileInfoS(p: PSection; const name: AnsiString; pos, csize, usize: Int64; stream: TMemoryStream): PResource;
+        function InsertFileInfo(const section, name: AnsiString; pos, csize, usize: Int64; stream: TMemoryStream): PResource;
+        function Preload(data: TData): Boolean;
+        function GetSourceStream(p: PResource): TStream;
+
+        procedure Clear();
+        procedure Collect();
+        procedure ReadFromStream(s: TStream);
+        procedure SaveToStream(s: TStream);
+
+      public
+        constructor Create();
+        destructor Destroy(); override;
+        procedure FreeWAD(); override;
+        function  ReadFile2(FileName: string): Boolean; override;
+        function  ReadMemory(Data: Pointer; Len: LongWord): Boolean; override;
+        procedure CreateImage(); override;
+        function AddResource(Data: Pointer; Len: LongWord; Name, Section: String): Boolean; override; overload;
+        function AddResource(FileName, Name, Section: String): Boolean; override; overload;
+        function AddAlias(Res, Alias: String): Boolean; override;
+        procedure AddSection(Name: String); override;
+        procedure RemoveResource(Section, Resource: String); override;
+        procedure SaveTo(FileName: String); override;
+        function HaveResource(Section, Resource: String): Boolean; override;
+        function HaveSection(Section: string): Boolean; override;
+        function GetResource(Section, Resource: String; var pData: Pointer; var Len: Integer): Boolean; override;
+        function GetSectionList(): SArray; override;
+        function GetResourcesList(Section: String): SArray; override;
+
+        function GetLastError: Integer; override;
+        function GetLastErrorStr: String; override;
+        function GetResourcesCount: Word; override;
+        function GetVersion: Byte; override;
+    end;
 
 implementation
 
-uses
-  SysUtils, BinEditor, ZLib, utils, e_log;
+  uses SysUtils, StrUtils, DateUtils, Math, utils, zstream, crc, e_log;
 
-const
-  DFWAD_OPENED_NONE   = 0;
-  DFWAD_OPENED_FILE   = 1;
-  DFWAD_OPENED_MEMORY = 2;
+  function PrepString(const s: AnsiString; caseSensitive, extSensitive: Boolean): AnsiString; inline;
+    var i: Integer;
+  begin
+    Result := s;
+    if caseSensitive = False then
+    begin
+      Result := UpperCase(Result);
+    end;
+    if extSensitive = False then
+    begin
+      i := Pos('.', Result); // fix dotfiles
+      if i > 1 then
+        SetLength(Result, i - 1);
+    end;
+  end;
 
-procedure DecompressBuf(const InBuf: Pointer; InBytes: Integer;
-  OutEstimate: Integer; out OutBuf: Pointer; out OutBytes: Integer);
-var
-  strm: TZStreamRec;
-  P: Pointer;
-  BufInc: Integer;
-begin
-  FillChar(strm, sizeof(strm), 0);
-  BufInc := (InBytes + 255) and not 255;
-  if OutEstimate = 0 then
-    OutBytes := BufInc
-  else
-    OutBytes := OutEstimate;
-  GetMem(OutBuf, OutBytes);
-  try
-    strm.next_in := InBuf;
-    strm.avail_in := InBytes;
-    strm.next_out := OutBuf;
-    strm.avail_out := OutBytes;
-    inflateInit_(strm, zlib_version, sizeof(strm));
-    try
-      while inflate(strm, Z_FINISH) <> Z_STREAM_END do
+  function FindResourceIDRAW(p: PSection; name: AnsiString; caseSensitive, extSensitive: Boolean): Integer;
+    var i: Integer; pname: AnsiString;
+  begin
+    if p <> nil then
+    begin
+      pname := PrepString(name, caseSensitive, extSensitive);
+      for i := 0 to High(p.list) do
       begin
-        P := OutBuf;
-        Inc(OutBytes, BufInc);
-        ReallocMem(OutBuf, OutBytes);
-        strm.next_out := PByteF(PChar(OutBuf) + (PChar(strm.next_out) - PChar(P)));
-        strm.avail_out := BufInc;
+        if PrepString(p.list[i].name, caseSensitive, extSensitive) = pname then
+        begin
+          Result := i;
+          exit;
+        end;
       end;
-    finally
-      inflateEnd(strm);
     end;
-    ReallocMem(OutBuf, strm.total_out);
-    OutBytes := strm.total_out;
-  except
-    FreeMem(OutBuf);
-    raise
-  end;
-end;
-
-procedure CompressBuf(const InBuf: Pointer; InBytes: Integer;
-                      out OutBuf: Pointer; out OutBytes: Integer);
-var
-  strm: TZStreamRec;
-  P: Pointer;
-begin
-  FillChar(strm, sizeof(strm), 0);
-  OutBytes := ((InBytes + (InBytes div 10) + 12) + 255) and not 255;
-  GetMem(OutBuf, OutBytes);
-  try
-    strm.next_in := InBuf;
-    strm.avail_in := InBytes;
-    strm.next_out := OutBuf;
-    strm.avail_out := OutBytes;
-    deflateInit_(strm, Z_BEST_COMPRESSION, zlib_version, sizeof(strm));
-    try
-      while deflate(strm, Z_FINISH) <> Z_STREAM_END do
+    Result := -1;
+  end;
+
+  function FindResourceID(p: PSection; name: AnsiString): Integer;
+    var i: Integer;
+  begin
+    i := FindResourceIDRAW(p, name, True, True); // CaSeNaMe.Ext
+    if i < 0 then
+    begin
+      i := FindResourceIDRAW(p, name, False, True); // CASENAME.EXT
+      if i < 0 then
       begin
-        P := OutBuf;
-        Inc(OutBytes, 256);
-        ReallocMem(OutBuf, OutBytes);
-        strm.next_out := PByteF(PtrUInt(OutBuf + (strm.next_out - P)));
-        strm.avail_out := 256;
+        i := FindResourceIDRAW(p, name, True, False); // CaSeNaMe
+        if i < 0 then
+        begin
+          i := FindResourceIDRAW(p, name, False, False); // CASENAME
+        end;
       end;
-    finally
-      deflateEnd(strm);
     end;
-    ReallocMem(OutBuf, strm.total_out);
-    OutBytes := strm.total_out;
-  except
-    FreeMem(OutBuf);
-    raise
-  end;
-end;
-
-{ TWADEditor_1 }
-
-function TWADEditor_1.AddResource(Data: Pointer; Len: LongWord; Name: string;
-                                  Section: string): Boolean;
-var
-  ResCompressed: Pointer;
-  ResCompressedSize: Integer;
-  a, b: Integer;
-begin
- Result := False;
-
- SetLength(FResTable, Length(FResTable)+1);
-
- if Section = '' then
- begin
-  if Length(FResTable) > 1 then
-   for a := High(FResTable) downto 1 do
-    FResTable[a] := FResTable[a-1];
-
-  a := 0;
- end
-  else
- begin
-  Section := AnsiUpperCase(Section);
-  b := -1;
-
-  for a := 0 to High(FResTable) do
-   if (FResTable[a].Length = 0) and (FResTable[a].ResourceName = Section) then
-   begin
-    for b := High(FResTable) downto a+2 do
-     FResTable[b] := FResTable[b-1];
-
-    b := a+1;
-    Break;
-   end;
+    Result := i;
+  end;
 
-  if b = -1 then
+  function FindResource(p: PSection; name: AnsiString): PResource;
+    var i: Integer;
   begin
-   SetLength(FResTable, Length(FResTable)-1);
-   Exit;
+    i := FindResourceID(p, name);
+    if i >= 0 then Result := @p.list[i] else Result := nil;
   end;
-  a := b;
- end;
-
- ResCompressed := nil;
- ResCompressedSize := 0;
- CompressBuf(Data, Len, ResCompressed, ResCompressedSize);
- if ResCompressed = nil then Exit;
- e_WriteLog('Fuck me (D)', MSG_NOTIFY);
 
- if FResData = nil then FResData := AllocMem(ResCompressedSize)
-  else ReallocMem(FResData, FDataSize+Cardinal(ResCompressedSize));
 
- FDataSize := FDataSize+LongWord(ResCompressedSize);
 
- CopyMemory(Pointer(PChar(FResData)+FDataSize-PChar(ResCompressedSize)),
-            ResCompressed, ResCompressedSize);
- FreeMemory(ResCompressed);
-
- Inc(FHeader.RecordsCount);
-
- with FResTable[a] do
- begin
-  ResourceName := GetResName(Name);
-  Address := FOffset;
-  Length := ResCompressedSize;
- end;
-
- FOffset := FOffset+Cardinal(ResCompressedSize);
-
- Result := True;
-end;
-
-function TWADEditor_1.AddAlias(Res, Alias: string): Boolean;
-var
-  a, b: Integer;
-  ares: Char16;
-begin
- Result := False;
-
- if FResTable = nil then Exit;
-
- b := -1;
- ares := GetResName(Alias);
- for a := 0 to High(FResTable) do
-  if FResTable[a].ResourceName = Res then
+  function TDFWEditor.FindSectionIDRAW(name: AnsiString; caseSensitive: Boolean): Integer;
+    var i: Integer; pname: AnsiString;
   begin
-   b := a;
-   Break;
+    if FSection <> nil then
+    begin
+      pname := PrepString(name, caseSensitive, True);
+      for i := 0 to High(FSection) do
+      begin
+        if PrepString(FSection[i].name, caseSensitive, True) = pname then
+        begin
+          Result := i;
+          exit;
+        end;
+      end;
+    end;
+    Result := -1;
   end;
 
- if b = -1 then Exit;
-
- Inc(FHeader.RecordsCount);
-
- SetLength(FResTable, Length(FResTable)+1);
-
- with FResTable[High(FResTable)] do
- begin
-  ResourceName := ares;
-  Address := FResTable[b].Address;
-  Length := FResTable[b].Length;
- end;
-
- Result := True;
-end;
-
-function TWADEditor_1.AddResource(FileName, Name, Section: string): Boolean;
-var
-  ResCompressed: Pointer;
-  ResCompressedSize: Integer;
-  ResourceFile: File;
-  TempResource: Pointer;
-  OriginalSize: Integer;
-  a, b: Integer;
-begin
- Result := False;
-
- AssignFile(ResourceFile, FileName);
-
- try
-  Reset(ResourceFile, 1);
- except
-  FLastError := DFWAD_ERROR_CANTOPENWAD;
-  Exit;
- end;
-
- OriginalSize := FileSize(ResourceFile);
- GetMem(TempResource, OriginalSize);
-
- try
-  BlockRead(ResourceFile, TempResource^, OriginalSize);
- except
-  FLastError := DFWAD_ERROR_READWAD;
-  FreeMemory(TempResource);
-  CloseFile(ResourceFile);
-  Exit;
- end;
-
- CloseFile(ResourceFile);
-
- ResCompressed := nil;
- ResCompressedSize := 0;
- CompressBuf(TempResource, OriginalSize, ResCompressed, ResCompressedSize);
- FreeMemory(TempResource);
- if ResCompressed = nil then Exit;
-
- SetLength(FResTable, Length(FResTable)+1);
-
- if Section = '' then
- begin
-  if Length(FResTable) > 1 then
-   for a := High(FResTable) downto 1 do
-    FResTable[a] := FResTable[a-1];
-
-  a := 0;
- end
-  else
- begin
-  Section := AnsiUpperCase(Section);
-  b := -1;
-
-  for a := 0 to High(FResTable) do
-   if (FResTable[a].Length = 0) and (FResTable[a].ResourceName = Section) then
-   begin
-    for b := High(FResTable) downto a+2 do
-     FResTable[b] := FResTable[b-1];
-
-    b := a+1;
-    Break;
-   end;
-
-  if b = -1 then
+  function TDFWEditor.FindSectionRAW(name: AnsiString; caseSensitive: Boolean): PSection;
+    var i: Integer;
   begin
-   FreeMemory(ResCompressed);
-   SetLength(FResTable, Length(FResTable)-1);
-   Exit;
+    i := FindSectionIDRAW(name, caseSensitive);
+    if i >= 0 then Result := @FSection[i] else Result := nil;
   end;
 
-  a := b;
- end;
-
- if FResData = nil then FResData := AllocMem(ResCompressedSize)
-  else ReallocMem(FResData, FDataSize+Cardinal(ResCompressedSize));
-
- FDataSize := FDataSize+LongWord(ResCompressedSize);
- CopyMemory(Pointer(PChar(FResData)+FDataSize-PChar(ResCompressedSize)),
-            ResCompressed, ResCompressedSize);
- FreeMemory(ResCompressed);
-
- Inc(FHeader.RecordsCount);
-
- with FResTable[a] do
- begin
-  ResourceName := GetResName(Name);
-  Address := FOffset;
-  Length := ResCompressedSize;
- end;
-
- FOffset := FOffset+Cardinal(ResCompressedSize);
-
- Result := True;
-end;
-
-procedure TWADEditor_1.AddSection(Name: string);
-begin
- if Name = '' then Exit;
-
- Inc(FHeader.RecordsCount);
-
- SetLength(FResTable, Length(FResTable)+1);
- with FResTable[High(FResTable)] do
- begin
-  ResourceName := GetResName(Name);
-  Address := $00000000;
-  Length := $00000000;
- end;
-end;
-
-constructor TWADEditor_1.Create();
-begin
- FResData := nil;
- FResTable := nil;
- FDataSize := 0;
- FOffset := 0;
- FHeader.RecordsCount := 0;
- FFileName := '';
- FWADOpened := DFWAD_OPENED_NONE;
- FLastError := DFWAD_NOERROR;
- FVersion := DFWAD_VERSION;
-end;
-
-procedure TWADEditor_1.CreateImage();
-var
-  WADFile: File;
-  b: LongWord;
-begin
- if FWADOpened = DFWAD_OPENED_NONE then
- begin
-  FLastError := DFWAD_ERROR_WADNOTLOADED;
-  Exit;
- end;
-
- if FWADOpened = DFWAD_OPENED_MEMORY then Exit;
-
- if FResData <> nil then FreeMem(FResData);
-
- try
-  AssignFile(WADFile, FFileName);
-  Reset(WADFile, 1);
-
-  b := 6+SizeOf(TWADHeaderRec_1)+SizeOf(TResourceTableRec_1)*Length(FResTable);
-
-  FDataSize := LongWord(FileSize(WADFile))-b;
-
-  GetMem(FResData, FDataSize);
-
-  Seek(WADFile, b);
-  BlockRead(WADFile, FResData^, FDataSize);
-
-  CloseFile(WADFile);
-
-  FOffset := FDataSize;
- except
-  FLastError := DFWAD_ERROR_CANTOPENWAD;
-  CloseFile(WADFile);
-  Exit;
- end;
-
- FLastError := DFWAD_NOERROR;
-end;
-
-destructor TWADEditor_1.Destroy();
-begin
- FreeWAD();
-
- inherited;
-end;
-
-procedure TWADEditor_1.FreeWAD();
-begin
- if FResData <> nil then FreeMem(FResData);
- FResTable := nil;
- FDataSize := 0;
- FOffset := 0;
- FHeader.RecordsCount := 0;
- FFileName := '';
- FWADOpened := DFWAD_OPENED_NONE;
- FLastError := DFWAD_NOERROR;
- FVersion := DFWAD_VERSION;
-end;
-
-function TWADEditor_1.GetResName(ResName: string): Char16;
-begin
- ZeroMemory(@Result[0], 16);
- if ResName = '' then Exit;
-
- ResName := Trim(UpperCase(ResName));
- if Length(ResName) > 16 then SetLength(ResName, 16);
-
- CopyMemory(@Result[0], @ResName[1], Length(ResName));
-end;
-
-function TWADEditor_1.HaveResource(Section, Resource: string): Boolean;
-var
-  a: Integer;
-  CurrentSection: string;
-begin
- Result := False;
+  function TDFWEditor.InsertSectionRAW(name: AnsiString): PSection;
+    var i: Integer;
+  begin
+    if FSection = nil then i := 0 else i := Length(FSection);
+    SetLength(FSection, i + 1);
+    FSection[i] := Default(TSection);
+    FSection[i].name := name;
+    Result := @FSection[i];
+  end;
 
- if FResTable = nil then Exit;
 
- CurrentSection := '';
- Section := AnsiUpperCase(Section);
- Resource := AnsiUpperCase(Resource);
 
- for a := 0 to High(FResTable) do
- begin
-  if FResTable[a].Length = 0 then
+  function TDFWEditor.FindSectionID(name: AnsiString): Integer;
+    var fixName: AnsiString;
   begin
-   CurrentSection := FResTable[a].ResourceName;
-   Continue;
+    fixName := StringReplace(name, '\', '/', [rfReplaceAll], TStringReplaceAlgorithm.sraManySmall);
+    Result := FindSectionIDRAW(fixName, True); // CaSeNaMe
+    if Result < 0 then
+      Result := FindSectionIDRAW(fixName, False); // CASENAME
   end;
 
-  if (FResTable[a].ResourceName = Resource) and
-     (CurrentSection = Section) then
+  function TDFWEditor.FindSection(name: AnsiString): PSection;
+    var fixName: AnsiString;
   begin
-   Result := True;
-   Break;
+    fixName := StringReplace(name, '\', '/', [rfReplaceAll], TStringReplaceAlgorithm.sraManySmall);
+    Result := FindSectionRAW(fixName, True); // CaSeNaMe
+    if Result = nil then
+      Result := FindSectionRAW(fixName, False); // CASENAME
   end;
- end;
-end;
 
-function TWADEditor_1.HaveSection(Section: string): Boolean;
-var
-  a: Integer;
-begin
- Result := False;
-
- if FResTable = nil then Exit;
- if Section = '' then
- begin
-  Result := True;
-  Exit;
- end;
-
- Section := AnsiUpperCase(Section);
-
- for a := 0 to High(FResTable) do
-  if (FResTable[a].Length = 0) and (FResTable[a].ResourceName = Section) then
-  begin
-   Result := True;
-   Exit;
-  end;
-end;
-
-function TWADEditor_1.GetResource(Section, Resource: string;
-  var pData: Pointer; var Len: Integer): Boolean;
-var
-  a: LongWord;
-  i: Integer;
-  WADFile: File;
-  CurrentSection: string;
-  TempData: Pointer;
-  OutBytes: Integer;
-begin
- Result := False;
-
- CurrentSection := '';
-
- if FWADOpened = DFWAD_OPENED_NONE then
- begin
-  FLastError := DFWAD_ERROR_WADNOTLOADED;
-  Exit;
- end;
-
- Section := toLowerCase1251(Section);
- Resource := toLowerCase1251(Resource);
-
- i := -1;
- for a := 0 to High(FResTable) do
- begin
-  if FResTable[a].Length = 0 then
-  begin
-   CurrentSection := toLowerCase1251(FResTable[a].ResourceName);
-   Continue;
-  end;
-
-  if (toLowerCase1251(FResTable[a].ResourceName) = Resource) and
-     (CurrentSection = Section) then
-  begin
-   i := a;
-   Break;
-  end;
- end;
-
- if i = -1 then
- begin
-  FLastError := DFWAD_ERROR_RESOURCENOTFOUND;
-  Exit;
- end;
-
- if FWADOpened = DFWAD_OPENED_FILE then
- begin
-  try
-   AssignFile(WADFile, FFileName);
-   Reset(WADFile, 1);
-
-   Seek(WADFile, FResTable[i].Address+6+
-        LongWord(SizeOf(TWADHeaderRec_1)+SizeOf(TResourceTableRec_1)*Length(FResTable)));
-   TempData := GetMemory(FResTable[i].Length);
-   BlockRead(WADFile, TempData^, FResTable[i].Length);
-   DecompressBuf(TempData, FResTable[i].Length, 0, pData, OutBytes);
-   FreeMem(TempData);
-
-   Len := OutBytes;
-
-   CloseFile(WADFile);
-  except
-   FLastError := DFWAD_ERROR_CANTOPENWAD;
-   CloseFile(WADFile);
-   Exit;
-  end;
- end
-  else
- begin
-  TempData := GetMemory(FResTable[i].Length);
-  CopyMemory(TempData, Pointer(PtrUInt(FResData)+FResTable[i].Address+6+
-             PtrUInt(SizeOf(TWADHeaderRec_1)+SizeOf(TResourceTableRec_1)*Length(FResTable))),
-             FResTable[i].Length);
-  DecompressBuf(TempData, FResTable[i].Length, 0, pData, OutBytes);
-  FreeMem(TempData);
-
-  Len := OutBytes;
- end;
-
- FLastError := DFWAD_NOERROR;
- Result := True;
-end;
-
-function TWADEditor_1.GetResourcesList(Section: string): SArray;
-var
-  a: Integer;
-  CurrentSection: Char16;
-begin
- Result := nil;
+  function TDFWEditor.InsertSection(name: AnsiString): PSection;
+  begin
+    Result := FindSection(name);
+    if Result = nil then
+      Result := InsertSectionRAW(name);
+  end;
 
- if FResTable = nil then Exit;
- if Length(Section) > 16 then Exit;
 
- CurrentSection := '';
 
- for a := 0 to High(FResTable) do
- begin
-  if FResTable[a].Length = 0 then
+  function TDFWEditor.FindDataID(pos: Int64): Integer;
+    var i: Integer;
   begin
-   CurrentSection := FResTable[a].ResourceName;
-   Continue;
+    if (pos >= 0) and (FData <> nil) then
+    begin
+      for i := 0 to High(FData) do
+      begin
+        if FData[i].pos = pos then
+        begin
+          Result := i;
+          exit;
+        end;
+      end;
+    end;
+    Result := -1;
   end;
 
-  if CurrentSection = Section then
+  function TDFWEditor.FindData(pos: Int64): TData;
+    var i: Integer;
   begin
-   SetLength(Result, Length(Result)+1);
-   Result[High(Result)] := FResTable[a].ResourceName;
+    i := FindDataID(pos);
+    if i >= 0 then Result := FData[i] else Result := nil;
   end;
- end;
-end;
 
-function TWADEditor_1.GetSectionList(): SArray;
-var
-  i: DWORD;
-begin
- Result := nil;
+  function TDFWEditor.InsertData(ref, pos, csize, usize: Int64; stream: TMemoryStream): TData;
+    var i: Integer; data: TData;
+  begin
+    data := TData.Create();
+    data.ref := ref;
+    data.pos := pos;
+    data.csize := csize;
+    data.usize := usize;
+    data.stream := stream;
+
+    if FData = nil then i := 0 else i := Length(FData);
+    SetLength(FData, i + 1);
+    FData[i] := data;
+    Result := data;
+  end;
 
- if FResTable = nil then Exit;
 
- if FResTable[0].Length <> 0 then
- begin
-  SetLength(Result, 1);
-  Result[0] := '';
- end;
 
- for i := 0 to High(FResTable) do
-  if FResTable[i].Length = 0 then
+  function TDFWEditor.InsertFileInfoS(p: PSection; const name: AnsiString; pos, csize, usize: Int64; stream: TMemoryStream): PResource;
+    var i: Integer; data: TData;
   begin
-   SetLength(Result, Length(Result)+1);
-   Result[High(Result)] := FResTable[i].ResourceName;
+    Result := nil;
+    if p = nil then
+      exit;
+
+    data := FindData(pos);
+    if data = nil then
+      data := InsertData(0, pos, csize, usize, stream);
+
+    if p.list = nil then i := 0 else i := Length(p.list);
+    SetLength(p.list, i + 1);
+    Inc(data.ref);
+    p.list[i] := Default(TResource);
+    p.list[i].name := name;
+    p.list[i].data := data;
+    Result := @p.list[i];
   end;
-end;
-
-function TWADEditor_1.LastErrorString(): string;
-begin
- case FLastError of
-  DFWAD_NOERROR: Result := '';
-  DFWAD_ERROR_WADNOTFOUND: Result := 'DFWAD file not found';
-  DFWAD_ERROR_CANTOPENWAD: Result := 'Can''t open DFWAD file';
-  DFWAD_ERROR_RESOURCENOTFOUND: Result := 'Resource not found';
-  DFWAD_ERROR_FILENOTWAD: Result := 'File is not DFWAD';
-  DFWAD_ERROR_WADNOTLOADED: Result := 'DFWAD file is not loaded';
-  DFWAD_ERROR_READRESOURCE: Result := 'Read resource error';
-  DFWAD_ERROR_READWAD: Result := 'Read DFWAD error';
- end;
-end;
-
-function TWADEditor_1.ReadFile2(FileName: string): Boolean;
-var
-  WADFile: File;
-  Signature: array[0..4] of Char;
-  a: Integer;
-begin
- FreeWAD();
-
- Result := False;
 
- if not FileExists(FileName) then
- begin
-  FLastError := DFWAD_ERROR_WADNOTFOUND;
-  Exit;
- end;
+  function TDFWEditor.InsertFileInfo(const section, name: AnsiString; pos, csize, usize: Int64; stream: TMemoryStream): PResource;
+    var p: PSection;
+  begin
+    p := FindSectionRAW(section, True);
+    if p = nil then
+      p := InsertSectionRAW(section);
 
- FFileName := FileName;
+    Result := InsertFileInfoS(p, name, pos, csize, usize, stream);
+  end;
 
- AssignFile(WADFile, FFileName);
 
- try
-  Reset(WADFile, 1);
- except
-  FLastError := DFWAD_ERROR_CANTOPENWAD;
-  Exit;
- end;
 
- try
-  BlockRead(WADFile, Signature, 5);
-  if Signature <> DFWAD_SIGNATURE then
+  function TDFWEditor.AddAlias(Res, Alias: String): Boolean;
   begin
-   FLastError := DFWAD_ERROR_FILENOTWAD;
-   CloseFile(WADFile);
-   Exit;
+    // New hard-links are not supported
+    // However, they never created by editor
+    Result := False;
   end;
 
-  BlockRead(WADFile, FVersion, 1);
-  if FVersion <> DFWAD_VERSION then
+  function TDFWEditor.AddResource(Data: Pointer; Len: LongWord; Name, Section: String): Boolean;
+    const level: TCompressionLevel = TCompressionLevel.clMax;
+    var s: TMemoryStream; cs: TCompressionStream; p: PResource;
   begin
-    FLastError := DFWAD_ERROR_WRONGVERSION;
-    CloseFile(WADFile);
-    Exit;
+    Name := win2utf(Name);
+    Section := win2utf(Section);
+    Result := False;
+    if Name <> '' then
+    begin
+      s := TMemoryStream.Create();
+      try
+        cs := TCompressionStream.Create(level, s, False);
+        try
+          cs.WriteBuffer(PByte(Data)[0], Len);
+          cs.Flush();
+        finally
+          cs.Free();
+        end;
+        p := InsertFileInfo(Section, Name, -1, s.Size, Len, s);
+        Result := p <> nil;
+      except
+        s.Free();
+        raise;
+      end;
+    end;
   end;
 
-  BlockRead(WADFile, FHeader, SizeOf(TWADHeaderRec_1));
-  FHeader.RecordsCount := LEtoN(FHeader.RecordsCount);
-  SetLength(FResTable, FHeader.RecordsCount);
-  if FResTable <> nil then
+  function TDFWEditor.AddResource(FileName, Name, Section: String): Boolean;
+    var s: TFileStream; ptr: PByte;
   begin
-   BlockRead(WADFile, FResTable[0], SizeOf(TResourceTableRec_1)*FHeader.RecordsCount);
-
-   for a := 0 to High(FResTable) do
-   begin
-    FResTable[a].Address := LEtoN(FResTable[a].Address);
-    FResTable[a].Length := LEtoN(FResTable[a].Length);
-    if FResTable[a].Length <> 0 then
-     FResTable[a].Address := FResTable[a].Address-6-(LongWord(SizeOf(TWADHeaderRec_1)+
-                             SizeOf(TResourceTableRec_1)*Length(FResTable)));
-   end;
+    Result := False;
+    FLastError := DFWAD_ERROR_READWAD;
+    try
+      s := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
+      try
+        GetMem(ptr, s.Size);
+        try
+          s.ReadBuffer(ptr[0], s.Size);
+          Result := AddResource(ptr, s.Size, Name, Section);
+          if Result = True then FLastError := DFWAD_NOERROR;
+        finally
+          FreeMem(ptr);
+        end;
+      finally
+        s.Free();
+      end;
+    except
+      on e: EFOpenError do
+      begin
+        if gWADEditorLogLevel >= DFWAD_LOG_INFO then
+          e_WriteLog('DFWAD: AddResource: failed to open file ' + FileName, MSG_NOTIFY);
+        FLastError := DFWAD_ERROR_CANTOPENWAD;
+      end;
+    end;
   end;
 
-  CloseFile(WADFile);
- except
-  FLastError := DFWAD_ERROR_READWAD;
-  CloseFile(WADFile);
-  Exit;
- end;
-
- FWADOpened := DFWAD_OPENED_FILE;
- FLastError := DFWAD_NOERROR;
- Result := True;
-end;
-
-function TWADEditor_1.ReadMemory(Data: Pointer; Len: LongWord): Boolean;
-var
-  Signature: array[0..4] of Char;
-  a: Integer;
-begin
- FreeWAD();
-
- Result := False;
-
- CopyMemory(@Signature[0], Data, 5);
- if Signature <> DFWAD_SIGNATURE then
- begin
-  FLastError := DFWAD_ERROR_FILENOTWAD;
-  Exit;
- end;
+  constructor TDFWEditor.Create();
+  begin
+    FSection := nil;
+    FData := nil;
+    FStream := nil;
+    FLastError := DFWAD_NOERROR;
+    FVersion := 1;
+    FreeWAD();
+  end;
 
- CopyMemory(@FVersion, Pointer(PtrUInt(Data)+5), 1);
- if FVersion <> DFWAD_VERSION then
- begin
-   FLastError := DFWAD_ERROR_WRONGVERSION;
-   Exit;
- end;
+  destructor TDFWEditor.Destroy();
+  begin
+    Clear();
+    inherited;
+  end;
 
- CopyMemory(@FHeader, Pointer(PtrUInt(Data)+6), SizeOf(TWADHeaderRec_1));
- FHeader.RecordsCount := LEtoN(FHeader.RecordsCount);
+  procedure TDFWEditor.Clear();
+    var i: Integer;
+  begin
+    if FSection <> nil then
+    begin
+      for i := 0 to High(FSection) do
+        if FSection[i].list <> nil then
+          SetLength(FSection[i].list, 0);
+      SetLength(FSection, 0);
+    end;
+    if FData <> nil then
+    begin
+      for i := 0 to High(FData) do
+      begin
+        if FData[i] <> nil then
+        begin
+          if FData[i].stream <> nil then
+            FreeAndNil(FData[i].stream);
+          FreeAndNil(FData[i]);
+        end;
+      end;
+      SetLength(FData, 0);
+    end;
+    if FStream <> nil then
+      FreeAndNil(FStream);
+  end;
 
- SetLength(FResTable, FHeader.RecordsCount);
- if FResTable <> nil then
- begin
-  CopyMemory(@FResTable[0], Pointer(PtrUInt(Data)+6+SizeOf(TWADHeaderRec_1)),
-             SizeOf(TResourceTableRec_1)*FHeader.RecordsCount);
+  procedure TDFWEditor.FreeWAD();
+  begin
+    Clear();
+    FLastError := DFWAD_NOERROR;
+    FVersion := 1;
+  end;
 
-  for a := 0 to High(FResTable) do
+  function TDFWEditor.Preload(data: TData): Boolean;
+    var s: TMemoryStream;
   begin
-   FResTable[a].Address := LEtoN(FResTable[a].Address);
-   FResTable[a].Length := LEtoN(FResTable[a].Length);
-   if FResTable[a].Length <> 0 then
-    FResTable[a].Address := FResTable[a].Address-6-(LongWord(SizeOf(TWADHeaderRec_1)+
-                            SizeOf(TResourceTableRec_1)*Length(FResTable)));
+    Result := False;
+    if data <> nil then
+    begin
+      Result := data.stream <> nil;
+      if (data.stream = nil) and (FStream <> nil) then
+      begin
+        s := TMemoryStream.Create();
+        try
+          if data.csize > 0 then
+          begin
+            FStream.Seek(data.pos, TSeekOrigin.soBeginning);
+            s.CopyFrom(FStream, data.csize);
+          end;
+          Assert(s.Size = data.csize); // wtf, random size if copied zero bytes!
+          data.stream := s;
+          Result := True;
+        except
+          s.Free();
+        end;
+      end;
+    end;
   end;
- end;
 
- GetMem(FResData, Len);
- CopyMemory(FResData, Data, Len);
+  procedure TDFWEditor.CreateImage();
+    var i, j: Integer;
+  begin
+    if FStream = nil then
+    begin
+      if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
+        e_WriteLog('DFWAD: CreateImage: File not assigned', MSG_NOTIFY);
+      FLastError := DFWAD_ERROR_WADNOTLOADED;
+    end
+    else if FStream is TMemoryStream then
+    begin
+      if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
+        e_WriteLog('DFWAD: CreateImage: Memory stream', MSG_NOTIFY);
+      FLastError := DFWAD_NOERROR;
+    end
+    else
+    begin
+      if FSection <> nil then
+      begin
+        for i := 0 to High(FData) do
+        begin
+          if Preload(FData[i]) = False then
+          begin
+            if gWADEditorLogLevel >= DFWAD_LOG_WARN then
+              e_WriteLog('DFWAD: CreateImage: failed to preload resource data #' + IntToStr(i), MSG_WARNING);
+            FLastError := DFWAD_ERROR_CANTOPENWAD;
+            exit;
+          end;
+          FData[i].pos := -1;
+        end;
+      end;
+      FreeAndNil(FStream);
+      FLastError := DFWAD_NOERROR;
+    end;
+  end;
 
- FWADOpened := DFWAD_OPENED_MEMORY;
- FLastError := DFWAD_NOERROR;
+  procedure TDFWEditor.AddSection(Name: String);
+  begin
+    Name := win2utf(Name);
+    if InsertSection(Name) = nil then
+      raise Exception.Create('DFWAD: AddSection[' + Name + ']: failed to insert');
+  end;
 
- Result := True;
-end;
+  function TDFWEditor.HaveResource(Section, Resource: String): Boolean;
+  begin
+    Section := win2utf(Section);
+    Resource := win2utf(Resource);
+    Result := FindResource(FindSection(Section), Resource) <> nil;
+  end;
 
-procedure TWADEditor_1.RemoveResource(Section, Resource: string);
-var
-  a, i: Integer;
-  CurrentSection: Char16;
-  b, c, d: LongWord;
-begin
- if FResTable = nil then Exit;
+  function TDFWEditor.HaveSection(Section: String): Boolean;
+  begin
+    Section := win2utf(Section);
+    Result := FindSection(Section) <> nil;
+  end;
 
- e_WriteLog('Fuck me (B) ' + Section + ' ' + Resource, MSG_NOTIFY);
+  function TDFWEditor.GetSourceStream(p: PResource): TStream;
+    var src: TStream;
+  begin
+    src := nil;
+    if p.data.stream <> nil then
+    begin
+      src := p.data.stream;
+      src.Seek(0, TSeekOrigin.soBeginning);
+    end
+    else if (p.data.pos >= 0) and (FStream <> nil) then
+    begin
+      src := FStream;
+      src.Seek(p.data.pos, TSeekOrigin.soBeginning);
+    end;
+    Result := src;
+  end;
 
- i := -1;
- b := 0;
- c := 0;
- CurrentSection := '';
+  function TDFWEditor.GetResource(Section, Resource: String; var pData: Pointer; var Len: Integer): Boolean;
+    const BLOCK_STEP = 4096;
+    var p: PResource; src: TStream; tmp: TDecompressionStream; ptr: PByte; size, r: Int64;
+  begin
+    Section := win2utf(Section);
+    Resource := win2utf(Resource);
+    FLastError := DFWAD_ERROR_CANTOPENWAD;
+    Result := False;
+    pData := nil;
+    Len := 0;
+    p := FindResource(FindSection(Section), Resource);
+    if p <> nil then
+    begin
+      src := GetSourceStream(p);
+      if src <> nil then
+      begin
+        try
+          tmp := TDecompressionStream.Create(src, False);
+          try
+            if p.data.usize < 0 then
+            begin
+              size := 0;
+              GetMem(ptr, BLOCK_STEP);
+              try
+                repeat
+                  r := tmp.Read(ptr[size], BLOCK_STEP);
+                  size := size + r;
+                  if r <> 0 then
+                    ReallocMem(ptr, size + BLOCK_STEP);
+                until r = 0;
+                ReallocMem(ptr, size);
+                p.data.usize := size; // cache size
+                pData := ptr;
+                Len := size;
+                Result := True;
+              except
+                FreeMem(ptr);
+                raise;
+              end;
+            end
+            else
+            begin
+              GetMem(ptr, p.data.usize);
+              try
+                tmp.ReadBuffer(ptr[0], p.data.usize);
+                pData := ptr;
+                Len := p.data.usize;
+                Result := True;
+              except
+                FreeMem(ptr);
+                raise;
+              end;
+            end;
+          finally
+            tmp.Free();
+          end;
+        except
+          on e: EStreamError do
+          begin
+            if gWADEditorLogLevel >= DFWAD_LOG_INFO then
+              e_WriteLog('DFWAD: Failed to decompress DEFLATEd data, reason: ' + e.Message, MSG_WARNING);
+            raise e;
+          end;
+        end;
+      end
+      else
+      begin
+        if gWADEditorLogLevel >= DFWAD_LOG_WARN then
+          e_WriteLog('DFWAD: No available source for file data', MSG_WARNING);
+        FLastError := DFWAD_ERROR_WADNOTLOADED;
+      end;
+    end
+    else
+    begin
+      if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
+        e_WriteLog('DFWAD: Resource not found', MSG_NOTIFY);
+      FLastError := DFWAD_ERROR_RESOURCENOTFOUND;
+    end;
+  end;
 
- for a := 0 to High(FResTable) do
- begin
-  if FResTable[a].Length = 0 then
+  function TDFWEditor.GetResourcesList(Section: String): SArray;
+    var p: PSection; i: Integer;
   begin
-   CurrentSection := FResTable[a].ResourceName;
-   Continue;
+    Section := win2utf(Section);
+    Result := nil;
+    p := FindSection(Section);
+    if (p <> nil) and (p.list <> nil) then
+    begin
+      SetLength(Result, Length(p.list));
+      for i := 0 to High(p.list) do
+      begin
+        Result[i] := utf2win(p.list[i].name);
+      end;
+    end;
   end;
 
-  if (FResTable[a].ResourceName = Resource) and
-     (CurrentSection = Section) then
+  function TDFWEditor.GetSectionList(): SArray;
+    var i: Integer;
   begin
-   i := a;
-   b := FResTable[a].Length;
-   c := FResTable[a].Address;
-   Break;
+    Result := nil;
+    if FSection <> nil then
+    begin
+      SetLength(Result, Length(FSection));
+      for i := 0 to High(FSection) do
+      begin
+        Result[i] := utf2win(FSection[i].name);
+      end;
+    end;
   end;
- end;
 
- if i = -1 then Exit;
+  procedure TDFWEditor.ReadFromStream(s: TStream);
+    var sig: packed array [0..4] of Char;
+    var ver: UInt8; nrec: UInt16; offset, csize: UInt32;
+    var name1251: packed array [0..16] of Char;
+    var section, name: AnsiString;
+    var i: Integer;
+    var sec: PSection;
+    var res: PResource;
+  begin
+    s.ReadBuffer(sig[0], 5);
+    if sig = 'DFWAD' then
+    begin
+      ver := s.ReadByte();
+      if ver = 1 then
+      begin
+        nrec := LEtoN(s.ReadWord());
+        section := '';
+        for i := 0 to nrec - 1 do
+        begin
+          s.ReadBuffer(name1251[0], 16);
+          name1251[16] := #0;
+          name := win2utf(PChar(@name1251[0]));
+          offset := LEtoN(s.ReadDWord());
+          csize := LEtoN(s.ReadDWord());
+          if csize = 0 then
+          begin
+            section := name;
+            sec := InsertSectionRAW(section);
+            if sec = nil then
+              raise Exception.Create('Failed to register section [' + section + ']');
+          end
+          else
+          begin
+            if sec = nil then
+              sec := InsertSectionRAW('');
+            res := InsertFileInfoS(sec, name, offset, csize, -1, nil);
+            if res = nil then
+              raise Exception.Create('Failed to register resource [' + section + '][' + name + ']');
+            if res.data.csize <> csize then
+              raise Exception.Create('Invalid compressed size for [' + section + '][' + name + '] (corrupted archive?)');
+          end;
+        end;
+      end
+      else
+      begin
+        FLastError := DFWAD_ERROR_WRONGVERSION;
+        raise Exception.Create('Unsupported DFWAD version ' + IntToStr(ver) + ' (expected 1)');
+      end;
+    end
+    else
+    begin
+      FLastError := DFWAD_ERROR_FILENOTWAD;
+      raise Exception.Create('Not DFWAD file');
+    end;
+  end;
 
- e_WriteLog('Fuck me (C) ' + Section + ' ' + Resource, MSG_NOTIFY);
+  function TDFWEditor.ReadFile2(FileName: String): Boolean;
+    var s: TFileStream;
+  begin
+    FreeWAD();
+    Result := False;
+    try
+      try
+        s := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
+        try
+          ReadFromStream(s);
+          FStream := s;
+          FLastError := DFWAD_NOERROR;
+          Result := True;
+        except
+          s.Free();
+          raise;
+        end;
+      except
+        on e: Exception do
+        begin
+          if gWADEditorLogLevel >= DFWAD_LOG_INFO then
+            e_WriteLog('DFWAD: Failed to read DFWAD from file ' + FileName + ', reason: ' + e.Message, MSG_WARNING);
+          Clear();
+        end;
+      end;
+    except
+      on e: EFOpenError do
+      begin
+        if gWADEditorLogLevel >= DFWAD_LOG_INFO then
+          e_WriteLog('DFWAD: Failed to open file ' + FileName + ', reason: ' + e.Message, MSG_WARNING);
+        if FileExists(FileName) then
+          FLastError := DFWAD_ERROR_CANTOPENWAD
+        else
+          FLastError := DFWAD_ERROR_WADNOTFOUND;
+      end;
+    end;
+  end;
 
- for a := i to High(FResTable)-1 do
-  FResTable[a] := FResTable[a+1];
+  function TDFWEditor.ReadMemory(Data: Pointer; Len: LongWord): Boolean;
+    var s: TMemoryStream;
+  begin
+    FreeWAD();
+    Result := False;
+    try
+      s := TMemoryStream.Create;
+      try
+        s.SetSize(Len);
+        s.WriteBuffer(PByte(Data)[0], Len);
+        s.Seek(0, soBeginning);
+        ReadFromStream(s);
+        FStream := s;
+        FLastError := DFWAD_NOERROR;
+        Result := True;
+      except
+        s.Free();
+        raise;
+      end;
+    except
+      on e: Exception do
+      begin
+        if gWADEditorLogLevel >= DFWAD_LOG_INFO then
+          e_WriteLog('DFWAD: Failed to read DFWAD from memory, reason: ' + e.Message, MSG_WARNING);
+        Clear();
+      end;
+    end;
+  end;
 
- SetLength(FResTable, Length(FResTable)-1);
+  procedure TDFWEditor.Collect();
+    var i, n: Integer;
+  begin
+    if FData <> nil then
+    begin
+      n := 0;
+      for i := 0 to High(FData) do
+      begin
+        if FData[i] <> nil then
+        begin
+          if FData[i].ref > 0 then
+          begin
+            FData[n] := FData[i];
+            Inc(n);
+          end
+          else
+          begin
+            if FData[i].stream <> nil then
+              FreeAndNil(FData[i].stream);
+            FreeAndNil(FData[i]);
+          end;
+        end;
+      end;
+      SetLength(FData, n);
+    end;
+  end;
 
- d := 0;
- for a := 0 to High(FResTable) do
-  if (FResTable[a].Length <> 0) and (FResTable[a].Address > c) then
+  procedure TDFWEditor.RemoveResource(Section, Resource: String);
+    var p: PSection; i: Integer; data: TData;
   begin
-   FResTable[a].Address := FResTable[a].Address-b;
-   d := d+FResTable[a].Length;
+    Section := win2utf(Section);
+    Resource := win2utf(Resource);
+    p := FindSection(Section);
+    i := FindResourceID(p, Resource);
+    if i >= 0 then
+    begin
+      data := p.list[i].data;
+      for i := i + 1 to High(p.list) do
+      begin
+        p.list[i - 1] := p.list[i];
+      end;
+      SetLength(p.list, High(p.list));
+      Dec(data.ref);
+      if data.ref <= 0 then
+        Collect();
+    end;
   end;
 
- CopyMemory(Pointer(PtrUInt(FResData)+c), Pointer(PtrUInt(FResData)+c+b), d);
+  procedure TDFWEditor.SaveToStream(s: TStream);
+    type TName16 = packed array [0..16] of Char;
+    var count: UInt16;
+    var name1251: TName16;
+    var i, j: Integer;
+    var p: PResource;
+    var data: TData;
+
+    function GetOffset(data: TData): UInt32;
+      var i: Integer;
+    begin
+      Assert(data <> nil);
+      Result := 6 + 2 + count * 24;
+      for i := 0 to High(FData) do
+      begin
+        if FData[i] = data then
+          exit;
+        if FData[i] <> nil then
+          Result := Result + FData[i].csize;
+      end;
+      raise Exception.Create('Failed to calculate offset (BUG!)');
+    end;
 
- FDataSize := FDataSize-b;
- FOffset := FOffset-b;
- ReallocMem(FResData, FDataSize);
+  begin
+    count := GetResourcesCount();
+    s.WriteBuffer('DFWAD', 5);
+    s.WriteByte(1);
+    WriteInt(s, UInt16(count));
+    if FSection <> nil then
+    begin
+      for i := 0 to High(FSection) do
+      begin
+        if (i <> 0) or (FSection[i].name <> '') then
+        begin
+          name1251 := Default(TName16);
+          name1251 := utf2win(FSection[i].name);
+          s.WriteBuffer(name1251[0], 16);
+          WriteInt(s, UInt32(0));
+          WriteInt(s, UInt32(0));
+        end;
+        if FSection[i].list <> nil then
+        begin
+          for j := 0 to High(FSection[i].list) do
+          begin
+            p := @FSection[i].list[j];
+            name1251 := Default(TName16);
+            name1251 := utf2win(p.name);
+            s.WriteBuffer(name1251[0], 16);
+            WriteInt(s, UInt32(GetOffset(p.data)));
+            WriteInt(s, UInt32(p.data.csize));
+          end;
+        end;
+      end;
+      if FData <> nil then
+      begin
+        for i := 0 to High(FData) do
+        begin
+          data := FData[i];
+          if data <> nil then
+          begin
+            Assert(s.Position = GetOffset(data));
+            if data.stream <> nil then
+            begin
+              Assert(data.stream.Size = data.csize);
+              data.stream.SaveToStream(s);
+            end
+            else if (data.pos >= 0) and (FStream <> nil) then
+            begin
+              FStream.Seek(data.pos, TSeekOrigin.soBeginning);
+              s.CopyFrom(FStream, data.csize);
+            end
+            else
+            begin
+              raise Exception.Create('No data source available (somethig very wrong)');
+            end;
+          end;
+        end;
+      end;
+    end;
+  end;
 
- FHeader.RecordsCount := FHeader.RecordsCount-1;
-end;
+  procedure TDFWEditor.SaveTo(FileName: String);
+    var s: TFileStream;
+  begin
+    try
+      s := TFileStream.Create(FileName, fmCreate);
+      try
+        SaveToStream(s);
+      finally
+        s.Free();
+      end;
+    except
+      on e: Exception do
+      begin
+        if gWADEditorLogLevel >= DFWAD_LOG_INFO then
+          e_WriteLog('DFWAD: Failed to create file ' + FileName + ', reason: ' + e.Message, MSG_WARNING);
+        raise e;
+      end;
+    end;
+  end;
 
-procedure TWADEditor_1.SaveTo(FileName: string);
-var
-  WADFile: File;
-  sign: string;
-  ver: Byte;
-  Header, HeaderLE: TWADHeaderRec_1;
-  i: Integer;
-begin
- sign := DFWAD_SIGNATURE;
- ver := DFWAD_VERSION;
-
- Header.RecordsCount := Length(FResTable);
- HeaderLE := Header;
- HeaderLE.RecordsCount := NtoLE(HeaderLE.RecordsCount);
-
- if FResTable <> nil then
-  for i := 0 to High(FResTable) do
-  begin
-   if FResTable[i].Length <> 0 then
-    FResTable[i].Address := FResTable[i].Address+6+SizeOf(TWADHeaderRec_1)+
-                            SizeOf(TResourceTableRec_1)*Header.RecordsCount;
-{$IFDEF FPC_BIG_ENDIAN}
-    FResTable[i].Address := NtoLE(FResTable[i].Address);
-    FResTable[i].Length := NtoLE(FResTable[i].Length);
-{$ENDIF}
-  end;
-
- AssignFile(WADFile, FileName);
- Rewrite(WADFile, 1);
-  BlockWrite(WADFile, sign[1], 5);
-  BlockWrite(WADFile, ver, 1);
-  BlockWrite(WADFile, HeaderLE, SizeOf(TWADHeaderRec_1));
-  if FResTable <> nil then BlockWrite(WADFile, FResTable[0],
-                                      SizeOf(TResourceTableRec_1)*Header.RecordsCount);
-  if FResData <> nil then BlockWrite(WADFile, FResData^, FDataSize);
- CloseFile(WADFile);
-
-{$IFDEF FPC_BIG_ENDIAN}
- // restore back to native endian
- if FResTable <> nil then
-  for i := 0 to High(FResTable) do
-  begin
-    FResTable[i].Address := LEtoN(FResTable[i].Address);
-    FResTable[i].Length := LEtoN(FResTable[i].Length);
-  end;
-{$ENDIF}
-end;
-
-function TWADEditor_1.GetLastError: Integer;
-begin
-  Result := FLastError;
-end;
+  function TDFWEditor.GetLastError: Integer;
+  begin
+    Result := FLastError;
+  end;
 
-function TWADEditor_1.GetLastErrorStr: String;
-begin
-  Result := LastErrorString();
-end;
+  function TDFWEditor.GetLastErrorStr: String;
+  begin
+    case FLastError of
+      DFWAD_NOERROR: Result := '';
+      DFWAD_ERROR_WADNOTFOUND: Result := 'DFWAD file not found';
+      DFWAD_ERROR_CANTOPENWAD: Result := 'Can''t open DFWAD file';
+      DFWAD_ERROR_RESOURCENOTFOUND: Result := 'Resource not found';
+      DFWAD_ERROR_FILENOTWAD: Result := 'File is not DFWAD';
+      DFWAD_ERROR_WADNOTLOADED: Result := 'DFWAD file is not loaded';
+      DFWAD_ERROR_READRESOURCE: Result := 'Read resource error';
+      DFWAD_ERROR_READWAD: Result := 'Read DFWAD error';
+      otherwise Result := IntToStr(FLastError);
+    end;
+  end;
 
-function TWADEditor_1.GetResourcesCount: Word;
-begin
-  Result := FHeader.RecordsCount;
-end;
+  function TDFWEditor.GetResourcesCount: Word;
+    var i: Integer;
+  begin
+    Result := 0;
+    if FSection <> nil then
+    begin
+      Result := Result + Length(FSection);
+      for i := 0 to High(FSection) do
+        if FSection[i].list <> nil then
+          Result := Result + Length(FSection[i].list);
+      if FSection[0].name = '' then
+        Dec(Result); // First root section not counted
+    end;
+  end;
 
-function TWADEditor_1.GetVersion: Byte;
-begin
-  Result := FVersion;
-end;
+  function TDFWEditor.GetVersion: Byte;
+  begin
+    Result := FVersion;
+  end;
 
 begin
-  gWADEditorFactory.RegisterEditor('DFWAD', TWADEditor_1);
+  gWADEditorFactory.RegisterEditor('DFWAD', TDFWEditor);
 end.