DEADSOFTWARE

add cvar d_eres
[d2df-sdl.git] / src / shared / wadreader.pas
index 252075fac9b76aae2c7a4fc614c77221c35cf015..1419f264faf6876b54adfe1d0b3f4dfa076bf713 100644 (file)
+(* 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 a_modes.inc}
 unit wadreader;
 
-{$DEFINE SFS_DWFAD_DEBUG}
+{$DEFINE SFS_DFWAD_DEBUG}
+{$DEFINE SFS_MAPDETECT_FX}
 
 interface
 
 uses
-  sfs, xstreams;
+  Classes,
+  {$IFDEF USE_MEMPOOL}mempool,{$ENDIF}
+  sfs, xstreams, utils;
 
 
 type
-  SArray = array of ShortString;
-
-  TWADFile = class(TObject)
+  TWADFile = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
   private
     fFileName: AnsiString; // empty: not opened
     fIter: TSFSFileList;
 
     function getIsOpen (): Boolean;
+    function isMapResource (idx: Integer): Boolean;
+
+    function GetResourceEx (name: AnsiString; wantMap: Boolean; var pData: Pointer; var Len: Integer; logError: Boolean=true): Boolean;
 
    public
-    constructor Create();
-    destructor Destroy(); override;
+    constructor Create ();
+    destructor Destroy (); override;
 
-    procedure FreeWAD();
+    procedure FreeWAD ();
 
     function ReadFile (FileName: AnsiString): Boolean;
     function ReadMemory (Data: Pointer; Len: LongWord): Boolean;
-    function GetResource (Section, Resource: AnsiString; var pData: Pointer; var Len: Integer): Boolean;
-    function GetResourcesList (Section: AnsiString): SArray;
+
+    function GetResource (name: AnsiString; var pData: Pointer; var Len: Integer; logError: Boolean=true): Boolean;
+    function GetMapResource (name: AnsiString; var pData: Pointer; var Len: Integer; logError: Boolean=true): Boolean;
+    function GetMapResources (): SSArray;
+
+    // returns `nil` if file wasn't found
+    function openFileStream (name: AnsiString): TStream;
 
     property isOpen: Boolean read getIsOpen;
   end;
 
+// g_ExtractWadName C:\svr\shit.wad:\MAPS\MAP01 -> C:/svr/shit.wad
+function g_ExtractWadName (resourceStr: AnsiString): AnsiString;
+
+// g_ExtractWadNameNoPath C:\svr\shit.wad:\MAPS\MAP01 -> shit.wad
+function g_ExtractWadNameNoPath (resourceStr: AnsiString): AnsiString;
 
-procedure g_ProcessResourceStr (ResourceStr: AnsiString; var FileName, SectionName, ResourceName: AnsiString); overload;
-procedure g_ProcessResourceStr (ResourceStr: AnsiString; FileName, SectionName, ResourceName: PString); overload;
+// g_ExtractFilePath C:\svr\shit.wad:\MAPS\MAP01 -> :/MAPS
+function g_ExtractFilePath (resourceStr: AnsiString): AnsiString;
 
-// return fixed AnsiString or empty AnsiString
-function findDiskWad (fname: AnsiString): AnsiString;
+// g_ExtractFileName C:\svr\shit.wad:\MAPS\MAP01 -> MAP01
+function g_ExtractFileName (resourceStr: AnsiString): AnsiString; // without path
+
+// g_ExtractFilePathName C:\svr\shit.wad:\MAPS\MAP01 -> MAPS/MAP01
+function g_ExtractFilePathName (resourceStr: AnsiString): AnsiString;
+
+
+var
+  wadoptDebug: Boolean = false;
+  wadoptFast: Boolean = false;
 
 
 implementation
 
 uses
-  SysUtils, Classes, BinEditor, e_log, g_options, utils;
+  SysUtils, e_log, MAPDEF, xdynrec;
 
 
-function findDiskWad (fname: AnsiString): AnsiString;
+function normSlashes (s: AnsiString): AnsiString;
+var
+  f: Integer;
 begin
-  result := '';
-  if not findFileCI(fname) then
+  for f := 1 to length(s) do if s[f] = '\' then s[f] := '/';
+  result := s;
+end;
+
+function g_ExtractWadNameNoPath (resourceStr: AnsiString): AnsiString;
+var
+  f, c: Integer;
+begin
+  for f := length(resourceStr) downto 1 do
   begin
-    //e_WriteLog(Format('findDiskWad: error looking for [%s]', [fname]), MSG_NOTIFY);
-    if StrEquCI1251(ExtractFileExt(fname), '.wad') then
-    begin
-      fname := ChangeFileExt(fname, '.pk3');
-      //e_WriteLog(Format('  looking for [%s]', [fname]), MSG_NOTIFY);
-      if not findFileCI(fname) then
-      begin
-        fname := ChangeFileExt(fname, '.zip');
-        //e_WriteLog(Format('  looking for [%s]', [fname]), MSG_NOTIFY);
-        if not findFileCI(fname) then exit;
-      end;
-    end
-    else
+    if resourceStr[f] = ':' then
     begin
+      result := normSlashes(Copy(resourceStr, 1, f-1));
+      c := length(result);
+      while (c > 0) and (result[c] <> '/') do Dec(c);
+      if c > 0 then result := Copy(result, c+1, length(result));
       exit;
     end;
   end;
-  //e_WriteLog(Format('findDiskWad: FOUND [%s]', [fname]), MSG_NOTIFY);
-  result := fname;
+  result := '';
 end;
 
-
-procedure g_ProcessResourceStr (ResourceStr: AnsiString; var FileName, SectionName, ResourceName: AnsiString);
+function g_ExtractWadName (resourceStr: AnsiString): AnsiString;
 var
-  a, i: Integer;
+  f: Integer;
 begin
-  //e_WriteLog(Format('g_ProcessResourceStr0: [%s]', [ResourceStr]), MSG_NOTIFY);
-  for i := Length(ResourceStr) downto 1 do if ResourceStr[i] = ':' then break;
-  FileName := Copy(ResourceStr, 1, i-1);
-  for a := i+1 to Length(ResourceStr) do if (ResourceStr[a] = '\') or (ResourceStr[a] = '/') then Break;
-  ResourceName := Copy(ResourceStr, a+1, Length(ResourceStr)-Abs(a));
-  SectionName := Copy(ResourceStr, i+1, Length(ResourceStr)-Length(ResourceName)-Length(FileName)-2);
+  for f := length(resourceStr) downto 1 do
+  begin
+    if resourceStr[f] = ':' then
+    begin
+      result := normSlashes(Copy(resourceStr, 1, f-1));
+      exit;
+    end;
+  end;
+  result := '';
 end;
 
-
-procedure g_ProcessResourceStr (ResourceStr: AnsiString; FileName, SectionName, ResourceName: PAnsiString);
+function g_ExtractFilePath (resourceStr: AnsiString): AnsiString;
 var
-  a, i, l1, l2: Integer;
+  f, lastSlash: Integer;
 begin
-  //e_WriteLog(Format('g_ProcessResourceStr1: [%s]', [ResourceStr]), MSG_NOTIFY);
-  for i := Length(ResourceStr) downto 1 do if ResourceStr[i] = ':' then break;
-  if FileName <> nil then
-  begin
-    FileName^ := Copy(ResourceStr, 1, i-1);
-    l1 := Length(FileName^);
-  end
-  else
+  result := '';
+  lastSlash := -1;
+  for f := length(resourceStr) downto 1 do
   begin
-    l1 := 0;
+    if (lastSlash < 0) and (resourceStr[f] = '\') or (resourceStr[f] = '/') then lastSlash := f;
+    if resourceStr[f] = ':' then
+    begin
+      if lastSlash > 0 then
+      begin
+        result := normSlashes(Copy(resourceStr, f, lastSlash-f));
+        while (length(result) > 0) and (result[1] = '/') do Delete(result, 1, 1);
+      end;
+      exit;
+    end;
   end;
-  for a := i+1 to Length(ResourceStr) do if (ResourceStr[a] = '\') or (ResourceStr[a] = '/') then break;
-  if ResourceName <> nil then
+  if lastSlash > 0 then result := normSlashes(Copy(resourceStr, 1, lastSlash-1));
+end;
+
+function g_ExtractFileName (resourceStr: AnsiString): AnsiString; // without path
+var
+  f, lastSlash: Integer;
+begin
+  result := '';
+  lastSlash := -1;
+  for f := length(resourceStr) downto 1 do
   begin
-    ResourceName^ := Copy(ResourceStr, a+1, Length(ResourceStr)-Abs(a));
-    l2 := Length(ResourceName^);
-  end
-  else
+    if (lastSlash < 0) and (resourceStr[f] = '\') or (resourceStr[f] = '/') then lastSlash := f;
+    if resourceStr[f] = ':' then
+    begin
+      if lastSlash > 0 then result := Copy(resourceStr, lastSlash+1, length(resourceStr));
+      exit;
+    end;
+  end;
+  if lastSlash > 0 then result := Copy(resourceStr, lastSlash+1, length(resourceStr));
+end;
+
+function g_ExtractFilePathName (resourceStr: AnsiString): AnsiString;
+var
+  f: Integer;
+begin
+  result := '';
+  for f := length(resourceStr) downto 1 do
   begin
-    l2 := 0;
+    if resourceStr[f] = ':' then
+    begin
+      result := normSlashes(Copy(resourceStr, f+1, length(resourceStr)));
+      while (length(result) > 0) and (result[1] = '/') do Delete(result, 1, 1);
+      exit;
+    end;
   end;
-  if SectionName <> nil then SectionName^ := Copy(ResourceStr, i+1, Length(ResourceStr)-l2-l1-2);
+  result := normSlashes(resourceStr);
+  while (length(result) > 0) and (result[1] = '/') do Delete(result, 1, 1);
 end;
 
 
+
 { TWADFile }
 constructor TWADFile.Create();
 begin
@@ -143,6 +212,57 @@ begin
 end;
 
 
+//FIXME: detect text maps properly here
+function TWADFile.isMapResource (idx: Integer): Boolean;
+var
+  //sign: packed array [0..2] of Char;
+  fs: TStream = nil;
+begin
+  result := false;
+  if not isOpen or (fIter = nil) then exit;
+  if (idx < 0) or (idx >= fIter.Count) then exit;
+  try
+    fs := fIter.volume.OpenFileByIndex(idx);
+    result := TDynMapDef.canBeMap(fs);
+    (*
+    fs.readBuffer(sign, 3);
+    result := (sign = MAP_SIGNATURE);
+    if not result then result := (sign[0] = 'm') and (sign[1] = 'a') and (sign[2] = 'p');
+    *)
+  except
+    fs.Free();
+    result := false; // just in case
+    exit;
+  end;
+  fs.Free();
+end;
+
+
+// returns `nil` if file wasn't found
+function TWADFile.openFileStream (name: AnsiString): TStream;
+var
+  f: Integer;
+  fi: TSFSFileInfo;
+begin
+  result := nil;
+  // backwards, due to possible similar names and such
+  for f := fIter.Count-1 downto 0 do
+  begin
+    fi := fIter.Files[f];
+    if fi = nil then continue;
+    if StrEquCI1251(fi.name, name) then
+    begin
+      try
+        result := fIter.volume.OpenFileByIndex(f);
+      except
+        result := nil;
+      end;
+      if (result <> nil) then exit;
+    end;
+  end;
+end;
+
+
 function removeExt (s: AnsiString): AnsiString;
 var
   i: Integer;
@@ -157,29 +277,60 @@ begin
   result := s;
 end;
 
-function TWADFile.GetResource (Section, Resource: AnsiString; var pData: Pointer; var Len: Integer): Boolean;
+
+function TWADFile.GetResourceEx (name: AnsiString; wantMap: Boolean; var pData: Pointer; var Len: Integer; logError: Boolean=true): Boolean;
 var
-  f: Integer;
+  f, lastSlash: Integer;
   fi: TSFSFileInfo;
   fs: TStream;
   fpp: Pointer;
-  //fn: AnsiString;
+  rpath, rname: AnsiString;
+  //sign: packed array [0..2] of Char;
+  goodMap: Boolean;
+  {$IFNDEF SFS_MAPDETECT_FX}
+  wst: TSFSMemoryChunkStream;
+  {$ENDIF}
 begin
   Result := False;
   if not isOpen or (fIter = nil) then Exit;
-  if length(Resource) = 0 then Exit; // just in case
-  if (length(Section) <> 0) and (Section[length(Section)] <> '/') then Section := Section+'/';
+  rname := removeExt(name);
+  if length(rname) = 0 then Exit; // just in case
+  lastSlash := -1;
+  for f := 1 to length(rname) do
+  begin
+    if rname[f] = '\' then rname[f] := '/';
+    if rname[f] = '/' then lastSlash := f;
+  end;
+  if lastSlash > 0 then
+  begin
+    rpath := Copy(rname, 1, lastSlash);
+    Delete(rname, 1, lastSlash);
+  end
+  else
+  begin
+    rpath := '';
+  end;
   // backwards, due to possible similar names and such
   for f := fIter.Count-1 downto 0 do
   begin
     fi := fIter.Files[f];
     if fi = nil then continue;
-    //e_WriteLog(Format('DFWAD: searching for [%s : %s] in [%s]; current is [%s : %s]', [Section, Resource, fFileName, fi.path, fi.name]), MSG_NOTIFY);
-    if StrEquCI1251(fi.path, Section) and StrEquCI1251(removeExt(fi.name), Resource) then
+    if StrEquCI1251(removeExt(fi.name), rname) then
     begin
-      // i found her!
-      //fn := fFileName+'::'+fi.path+fi.name;
-      //fs := SFSFileOpen(fn);
+      // i found her (maybe)
+      if not wantMap then
+      begin
+        if length(fi.path) < length(rpath) then continue; // alas
+        if length(fi.path) = length(rpath) then
+        begin
+          if not StrEquCI1251(fi.path, rpath) then continue; // alas
+        end
+        else
+        begin
+          if fi.path[length(fi.path)-length(rpath)] <> '/' then continue; // alas
+          if not StrEquCI1251(Copy(fi.path, length(fi.path)+1-length(rpath), length(fi.path)), rpath) then continue; // alas
+        end;
+      end;
       try
         fs := fIter.volume.OpenFileByIndex(f);
       except
@@ -187,9 +338,42 @@ begin
       end;
       if fs = nil then
       begin
-        e_WriteLog(Format('DFWAD: can''t open file [%s%s] in [%s]', [Section, Resource, fFileName]), MSG_WARNING);
+        if wantMap then continue;
+        if logError then e_WriteLog(Format('DFWAD: can''t open file [%s] in [%s]', [name, fFileName]), TMsgType.Warning);
         break;
       end;
+      // if we want only maps, check if this is map
+{$IFDEF SFS_MAPDETECT_FX}
+      if wantMap then
+      begin
+        goodMap := false;
+        {$IF DEFINED(D2D_NEW_MAP_READER_DBG)}
+        e_LogWritefln('DFWAD: checking for good map in wad [%s], file [%s] (#%d)', [fFileName, fi.fname, f]);
+        {$ENDIF}
+        try
+          //fs.readBuffer(sign, 3);
+          //goodMap := (sign = MAP_SIGNATURE);
+          //if not goodMap then goodMap := (sign[0] = 'm') and (sign[1] = 'a') and (sign[2] = 'p');
+          goodMap := TDynMapDef.canBeMap(fs);
+          {$IF DEFINED(D2D_NEW_MAP_READER_DBG)}
+          if goodMap then
+            e_LogWritefln('  GOOD map in wad [%s], file [%s] (#%d)', [fFileName, fi.fname, f])
+          else
+            e_LogWritefln('  BAD map in wad [%s], file [%s] (#%d)', [fFileName, fi.fname, f]);
+          {$ENDIF}
+        except
+        end;
+        if not goodMap then
+        begin
+          {$IF DEFINED(D2D_NEW_MAP_READER_DBG)}
+          e_LogWritefln('  not a map in wad [%s], file [%s] (#%d)', [fFileName, fi.fname, f]);
+          {$ENDIF}
+          fs.Free();
+          continue;
+        end;
+        fs.position := 0;
+      end;
+{$ENDIF}
       Len := Integer(fs.size);
       GetMem(pData, Len);
       fpp := pData;
@@ -205,35 +389,83 @@ begin
         end;
         fs.Free;
       end;
+{$IFNDEF SFS_MAPDETECT_FX}
+      if wantMap then
+      begin
+        goodMap := false;
+        if Len >= 3 then
+        begin
+          //Move(pData^, sign, 3);
+          //goodMap := (sign = MAP_SIGNATURE);
+          wst := TSFSMemoryChunkStream.Create(pData, Len);
+          try
+            goodMap := TDynMapDef.canBeMap(wst);
+          except
+            goodMap := false;
+          end;
+          wst.Free();
+        end;
+        if not goodMap then
+        begin
+          //e_WriteLog(Format('  not a map in wad [%s], file [%s] (#%d)', [fFileName, fi.fname, f]), MSG_NOTIFY);
+          FreeMem(pData);
+          pData := nil;
+          Len := 0;
+          continue;
+        end;
+      end;
+{$ENDIF}
       result := true;
-      {$IFDEF SFS_DWFAD_DEBUG}
-      if gSFSDebug then
-        e_WriteLog(Format('DFWAD: file [%s%s] FOUND in [%s]; size is %d bytes', [Section, Resource, fFileName, Len]), MSG_NOTIFY);
+      {$IFDEF SFS_DFWAD_DEBUG}
+      if wadoptDebug then
+        e_WriteLog(Format('DFWAD: file [%s] FOUND in [%s]; size is %d bytes', [name, fFileName, Len]), TMsgType.Notify);
       {$ENDIF}
       exit;
     end;
   end;
-  e_WriteLog(Format('DFWAD: file [%s%s] not found in [%s]', [Section, Resource, fFileName]), MSG_WARNING);
+  if logError then e_WriteLog(Format('DFWAD: file [%s] not found in [%s]', [name, fFileName]), TMsgType.Warning);
 end;
 
+function TWADFile.GetResource (name: AnsiString; var pData: Pointer; var Len: Integer; logError: Boolean=true): Boolean;
+begin
+  result := GetResourceEx(name, false, pData, Len, logError);
+end;
+
+function TWADFile.GetMapResource (name: AnsiString; var pData: Pointer; var Len: Integer; logError: Boolean=true): Boolean;
+begin
+  result := GetResourceEx(name, true, pData, Len, logError);
+end;
 
-function TWADFile.GetResourcesList (Section: AnsiString): SArray;
+function TWADFile.GetMapResources (): SSArray;
 var
-  f: Integer;
+  f, c: Integer;
   fi: TSFSFileInfo;
+  s: AnsiString;
 begin
   Result := nil;
   if not isOpen or (fIter = nil) then Exit;
-  if (length(Section) <> 0) and (Section[length(Section)] <> '/') then Section := Section+'/';
-  for f := 0 to fIter.Count-1 do
+  for f := fIter.Count-1 downto 0 do
   begin
     fi := fIter.Files[f];
     if fi = nil then continue;
     if length(fi.name) = 0 then continue;
-    if StrEquCI1251(fi.path, Section) then
+    {$IF DEFINED(D2D_NEW_MAP_READER)}
+    //e_LogWritefln('DFWAD: checking for map in wad [%s], file [%s] (#%d)', [fFileName, fi.fname, f]);
+    {$ENDIF}
+    if isMapResource(f) then
     begin
-      SetLength(result, Length(result)+1);
-      result[high(result)] := removeExt(fi.name);
+      s := removeExt(fi.name);
+      c := High(result);
+      while c >= 0 do
+      begin
+        if StrEquCI1251(result[c], s) then break;
+        Dec(c);
+      end;
+      if c < 0 then
+      begin
+        SetLength(result, Length(result)+1);
+        result[high(result)] := removeExt(fi.name);
+      end;
     end;
   end;
 end;
@@ -251,15 +483,15 @@ begin
   rfn := findDiskWad(FileName);
   if length(rfn) = 0 then
   begin
-    e_WriteLog(Format('TWADFile.ReadFile: error looking for [%s]', [FileName]), MSG_NOTIFY);
+    e_WriteLog(Format('TWADFile.ReadFile: error looking for [%s]', [FileName]), TMsgType.Notify);
     exit;
   end;
-  {$IFDEF SFS_DWFAD_DEBUG}
-  if gSFSDebug then e_WriteLog(Format('TWADFile.ReadFile: FOUND [%s]', [rfn]), MSG_NOTIFY);
+  {$IFDEF SFS_DFWAD_DEBUG}
+  if wadoptDebug then e_WriteLog(Format('TWADFile.ReadFile: FOUND [%s]', [rfn]), TMsgType.Notify);
   {$ENDIF}
   // cache this wad
   try
-    if gSFSFastMode then
+    if wadoptFast then
     begin
       if not SFSAddDataFile(rfn, true) then exit;
     end
@@ -273,8 +505,8 @@ begin
   fIter := SFSFileList(rfn);
   if fIter = nil then Exit;
   fFileName := rfn;
-  {$IFDEF SFS_DWFAD_DEBUG}
-  if gSFSDebug then e_WriteLog(Format('TWADFile.ReadFile: [%s] opened', [fFileName]), MSG_NOTIFY);
+  {$IFDEF SFS_DFWAD_DEBUG}
+  if wadoptDebug then e_WriteLog(Format('TWADFile.ReadFile: [%s] opened', [fFileName]), TMsgType.Notify);
   {$ENDIF}
   Result := True;
 end;
@@ -294,14 +526,14 @@ begin
   FreeWAD();
   if (Data = nil) or (Len = 0) then
   begin
-    e_WriteLog('TWADFile.ReadMemory: EMPTY SUBWAD!', MSG_WARNING);
+    e_WriteLog('TWADFile.ReadMemory: EMPTY SUBWAD!', TMsgType.Warning);
     Exit;
   end;
 
   fn := Format(' -- memwad %d -- ', [uniqueCounter]);
   Inc(uniqueCounter);
-  {$IFDEF SFS_DWFAD_DEBUG}
-    e_WriteLog(Format('TWADFile.ReadMemory: [%s]', [fn]), MSG_NOTIFY);
+  {$IFDEF SFS_DFWAD_DEBUG}
+  if wadoptDebug then e_WriteLog(Format('TWADFile.ReadMemory: [%s]', [fn]), TMsgType.Notify);
   {$ENDIF}
 
   try
@@ -320,8 +552,8 @@ begin
   if fIter = nil then Exit;
 
   fFileName := fn;
-  {$IFDEF SFS_DWFAD_DEBUG}
-    e_WriteLog(Format('TWADFile.ReadMemory: [%s] opened', [fFileName]), MSG_NOTIFY);
+  {$IFDEF SFS_DFWAD_DEBUG}
+  if wadoptDebug then e_WriteLog(Format('TWADFile.ReadMemory: [%s] opened', [fFileName]), TMsgType.Notify);
   {$ENDIF}
 
   {