index 3cce5df6c0053f1b7f0c5445799feaa78a06c97b..1419f264faf6876b54adfe1d0b3f4dfa076bf713 100644 (file)
--- a/src/shared/wadreader.pas
+++ b/src/shared/wadreader.pas
+(* 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: string; // empty: not opened
+ 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: string): Boolean;
+ function ReadFile (FileName: AnsiString): Boolean;
function ReadMemory (Data: Pointer; Len: LongWord): Boolean;
- function GetResource (Section, Resource: string; var pData: Pointer; var Len: Integer): Boolean;
- function GetResourcesList (Section: string): 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: String; var FileName, SectionName, ResourceName: String); overload;
-procedure g_ProcessResourceStr (ResourceStr: String; FileName, SectionName, ResourceName: PString); overload;
+// g_ExtractFilePath C:\svr\shit.wad:\MAPS\MAP01 -> :/MAPS
+function g_ExtractFilePath (resourceStr: AnsiString): AnsiString;
-// return fixed string or empty string
-function findDiskWad (fname: string): string;
+// 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: string): string;
+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('TWADFile.ReadFile: error looking for [%s] [%s]', [path, ExtractFileName(fname)]), MSG_NOTIFY);
- if StrEquCI1251(ExtractFileExt(fname), '.wad') then
- begin
- fname := ChangeFileExt(ExtractFileName(fname), '.pk3');
- //e_WriteLog(Format(' looking for [%s] [%s]', [path, rfn]), MSG_NOTIFY);
- if not findFileCI(fname) then
- begin
- //e_WriteLog(Format(' looking for [%s] [%s]', [path, rfn]), MSG_NOTIFY);
- fname := ChangeFileExt(ExtractFileName(fname), '.zip');
- 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;
- //e_WriteLog(Format('TWADFile.ReadFile: FOUND [%s]', [rfn]), MSG_NOTIFY);
- end
- else
- begin
- //if rfn <> ExtractFileName(FileName) then e_WriteLog(Format('TWADFile.ReadFile: FOUND [%s]', [rfn]), MSG_NOTIFY);
end;
- result := fname;
+ result := '';
end;
-
-procedure g_ProcessResourceStr (ResourceStr: String; var FileName, SectionName, ResourceName: String);
+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
+ result := '';
+ lastSlash := -1;
+ for f := length(resourceStr) downto 1 do
+ begin
+ if (lastSlash < 0) and (resourceStr[f] = '\') or (resourceStr[f] = '/') then lastSlash := f;
+ if resourceStr[f] = ':' then
begin
- FileName^ := Copy(ResourceStr, 1, i-1);
- l1 := Length(FileName^);
- end
- else
- l1 := 0;
-
- for a := i+1 to Length(ResourceStr) do
- if (ResourceStr[a] = '\') or (ResourceStr[a] = '/') then Break;
+ 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;
+ if lastSlash > 0 then result := normSlashes(Copy(resourceStr, 1, lastSlash-1));
+end;
- if ResourceName <> nil then
+function g_ExtractFileName (resourceStr: AnsiString): AnsiString; // without path
+var
+ f, lastSlash: Integer;
+begin
+ result := '';
+ lastSlash := -1;
+ for f := length(resourceStr) downto 1 do
+ begin
+ if (lastSlash < 0) and (resourceStr[f] = '\') or (resourceStr[f] = '/') then lastSlash := f;
+ if resourceStr[f] = ':' then
begin
- ResourceName^ := Copy(ResourceStr, a+1, Length(ResourceStr)-Abs(a));
- l2 := Length(ResourceName^);
- end
- else
- l2 := 0;
+ 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;
- if SectionName <> nil then
- SectionName^ := Copy(ResourceStr, i+1, Length(ResourceStr)-l2-l1-2);
+function g_ExtractFilePathName (resourceStr: AnsiString): AnsiString;
+var
+ f: Integer;
+begin
+ result := '';
+ for f := length(resourceStr) downto 1 do
+ begin
+ 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;
+ result := normSlashes(resourceStr);
+ while (length(result) > 0) and (result[1] = '/') do Delete(result, 1, 1);
end;
+
{ TWADFile }
constructor TWADFile.Create();
begin
end;
-function removeExt (s: string): string;
+//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;
begin
result := s;
end;
-function TWADFile.GetResource (Section, Resource: string; 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: string;
+ 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
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;
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.GetResourcesList (Section: string): SArray;
+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.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;
-function TWADFile.ReadFile (FileName: string): Boolean;
+function TWADFile.ReadFile (FileName: AnsiString): Boolean;
var
- rfn: string;
+ rfn: AnsiString;
//f: Integer;
//fi: TSFSFileInfo;
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
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;
function TWADFile.ReadMemory (Data: Pointer; Len: LongWord): Boolean;
var
- fn: string;
+ fn: AnsiString;
st: TStream = nil;
//f: Integer;
//fi: TSFSFileInfo;
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
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}
{