X-Git-Url: https://deadsoftware.ru/gitweb?a=blobdiff_plain;f=src%2Fshared%2Fwadreader.pas;h=1419f264faf6876b54adfe1d0b3f4dfa076bf713;hb=03988912c6a1d85d8ba35362e45690c414f13d0b;hp=aee12f47e55faea002758b2425fbd0bc5c9a1c1c;hpb=ac201b02f10ef558087d50f6b03b4519ab567558;p=d2df-sdl.git diff --git a/src/shared/wadreader.pas b/src/shared/wadreader.pas index aee12f4..1419f26 100644 --- a/src/shared/wadreader.pas +++ b/src/shared/wadreader.pas @@ -1,121 +1,189 @@ -{$MODE DELPHI} +(* 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 . + *) +{$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 @@ -144,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; @@ -158,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 @@ -188,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; @@ -206,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; @@ -252,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 @@ -274,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; @@ -295,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 @@ -321,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} {