1 (* Copyright (C) Doom 2D: Forever Developers
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 {$INCLUDE a_modes.inc}
19 {$DEFINE SFS_DFWAD_DEBUG}
20 {$DEFINE SFS_MAPDETECT_FX}
26 {$IFDEF USE_MEMPOOL}mempool
,{$ENDIF}
31 TWADFile
= class{$IFDEF USE_MEMPOOL}(TPoolObject
){$ENDIF}
33 fFileName
: AnsiString; // empty: not opened
36 function getIsOpen (): Boolean;
37 function isMapResource (idx
: Integer): Boolean;
39 function GetResourceEx (name
: AnsiString; wantMap
: Boolean; var pData
: Pointer; var Len
: Integer; logError
: Boolean=true): Boolean;
42 constructor Create ();
43 destructor Destroy (); override;
47 function ReadFile (FileName
: AnsiString): Boolean;
48 function ReadMemory (Data
: Pointer; Len
: LongWord): Boolean;
50 function GetResource (name
: AnsiString; var pData
: Pointer; var Len
: Integer; logError
: Boolean=true): Boolean;
51 function GetMapResource (name
: AnsiString; var pData
: Pointer; var Len
: Integer; logError
: Boolean=true): Boolean;
52 function GetMapResources (): SSArray
;
54 // returns `nil` if file wasn't found
55 function openFileStream (name
: AnsiString): TStream
;
57 property isOpen
: Boolean read getIsOpen
;
61 function g_ExtractWadName (resourceStr
: AnsiString): AnsiString;
62 function g_ExtractWadNameNoPath (resourceStr
: AnsiString): AnsiString;
63 function g_ExtractFilePath (resourceStr
: AnsiString): AnsiString;
64 function g_ExtractFileName (resourceStr
: AnsiString): AnsiString; // without path
65 function g_ExtractFilePathName (resourceStr
: AnsiString): AnsiString;
69 wadoptDebug
: Boolean = false;
70 wadoptFast
: Boolean = false;
76 SysUtils
, e_log
, MAPDEF
, xdynrec
;
79 function normSlashes (s
: AnsiString): AnsiString;
83 for f
:= 1 to length(s
) do if s
[f
] = '\' then s
[f
] := '/';
87 function g_ExtractWadNameNoPath (resourceStr
: AnsiString): AnsiString;
91 for f
:= length(resourceStr
) downto 1 do
93 if resourceStr
[f
] = ':' then
95 result
:= normSlashes(Copy(resourceStr
, 1, f
-1));
97 while (c
> 0) and (result
[c
] <> '/') do Dec(c
);
98 if c
> 0 then result
:= Copy(result
, c
+1, length(result
));
105 function g_ExtractWadName (resourceStr
: AnsiString): AnsiString;
109 for f
:= length(resourceStr
) downto 1 do
111 if resourceStr
[f
] = ':' then
113 result
:= normSlashes(Copy(resourceStr
, 1, f
-1));
120 function g_ExtractFilePath (resourceStr
: AnsiString): AnsiString;
122 f
, lastSlash
: Integer;
126 for f
:= length(resourceStr
) downto 1 do
128 if (lastSlash
< 0) and (resourceStr
[f
] = '\') or (resourceStr
[f
] = '/') then lastSlash
:= f
;
129 if resourceStr
[f
] = ':' then
131 if lastSlash
> 0 then
133 result
:= normSlashes(Copy(resourceStr
, f
, lastSlash
-f
));
134 while (length(result
) > 0) and (result
[1] = '/') do Delete(result
, 1, 1);
139 if lastSlash
> 0 then result
:= normSlashes(Copy(resourceStr
, 1, lastSlash
-1));
142 function g_ExtractFileName (resourceStr
: AnsiString): AnsiString; // without path
144 f
, lastSlash
: Integer;
148 for f
:= length(resourceStr
) downto 1 do
150 if (lastSlash
< 0) and (resourceStr
[f
] = '\') or (resourceStr
[f
] = '/') then lastSlash
:= f
;
151 if resourceStr
[f
] = ':' then
153 if lastSlash
> 0 then result
:= Copy(resourceStr
, lastSlash
+1, length(resourceStr
));
157 if lastSlash
> 0 then result
:= Copy(resourceStr
, lastSlash
+1, length(resourceStr
));
160 function g_ExtractFilePathName (resourceStr
: AnsiString): AnsiString;
165 for f
:= length(resourceStr
) downto 1 do
167 if resourceStr
[f
] = ':' then
169 result
:= normSlashes(Copy(resourceStr
, f
+1, length(resourceStr
)));
170 while (length(result
) > 0) and (result
[1] = '/') do Delete(result
, 1, 1);
174 result
:= normSlashes(resourceStr
);
175 while (length(result
) > 0) and (result
[1] = '/') do Delete(result
, 1, 1);
181 constructor TWADFile
.Create();
187 destructor TWADFile
.Destroy();
194 function TWADFile
.getIsOpen (): Boolean;
196 result
:= (fFileName
<> '');
200 procedure TWADFile
.FreeWAD();
202 if fIter
<> nil then FreeAndNil(fIter
);
203 //if fFileName <> '' then e_WriteLog(Format('TWADFile.ReadFile: [%s] closed', [fFileName]), MSG_NOTIFY);
208 //FIXME: detect text maps properly here
209 function TWADFile
.isMapResource (idx
: Integer): Boolean;
211 //sign: packed array [0..2] of Char;
215 if not isOpen
or (fIter
= nil) then exit
;
216 if (idx
< 0) or (idx
>= fIter
.Count
) then exit
;
218 fs
:= fIter
.volume
.OpenFileByIndex(idx
);
219 result
:= TDynMapDef
.canBeMap(fs
);
221 fs.readBuffer(sign, 3);
222 result := (sign = MAP_SIGNATURE);
223 if not result then result := (sign[0] = 'm') and (sign[1] = 'a') and (sign[2] = 'p');
227 result
:= false; // just in case
234 // returns `nil` if file wasn't found
235 function TWADFile
.openFileStream (name
: AnsiString): TStream
;
241 // backwards, due to possible similar names and such
242 for f
:= fIter
.Count
-1 downto 0 do
244 fi
:= fIter
.Files
[f
];
245 if fi
= nil then continue
;
246 if StrEquCI1251(fi
.name
, name
) then
249 result
:= fIter
.volume
.OpenFileByIndex(f
);
253 if (result
<> nil) then exit
;
259 function removeExt (s
: AnsiString): AnsiString;
264 while (i
> 1) and (s
[i
-1] <> '.') and (s
[i
-1] <> '/') do Dec(i
);
265 if (i
> 1) and (s
[i
-1] = '.') then
267 //writeln('[', s, '] -> [', Copy(s, 1, i-2), ']');
268 s
:= Copy(s
, 1, i
-2);
274 function TWADFile
.GetResourceEx (name
: AnsiString; wantMap
: Boolean; var pData
: Pointer; var Len
: Integer; logError
: Boolean=true): Boolean;
276 f
, lastSlash
: Integer;
280 rpath
, rname
: AnsiString;
281 //sign: packed array [0..2] of Char;
283 {$IFNDEF SFS_MAPDETECT_FX}
284 wst
: TSFSMemoryChunkStream
;
288 if not isOpen
or (fIter
= nil) then Exit
;
289 rname
:= removeExt(name
);
290 if length(rname
) = 0 then Exit
; // just in case
292 for f
:= 1 to length(rname
) do
294 if rname
[f
] = '\' then rname
[f
] := '/';
295 if rname
[f
] = '/' then lastSlash
:= f
;
297 if lastSlash
> 0 then
299 rpath
:= Copy(rname
, 1, lastSlash
);
300 Delete(rname
, 1, lastSlash
);
306 // backwards, due to possible similar names and such
307 for f
:= fIter
.Count
-1 downto 0 do
309 fi
:= fIter
.Files
[f
];
310 if fi
= nil then continue
;
311 if StrEquCI1251(removeExt(fi
.name
), rname
) then
313 // i found her (maybe)
316 if length(fi
.path
) < length(rpath
) then continue
; // alas
317 if length(fi
.path
) = length(rpath
) then
319 if not StrEquCI1251(fi
.path
, rpath
) then continue
; // alas
323 if fi
.path
[length(fi
.path
)-length(rpath
)] <> '/' then continue
; // alas
324 if not StrEquCI1251(Copy(fi
.path
, length(fi
.path
)+1-length(rpath
), length(fi
.path
)), rpath
) then continue
; // alas
328 fs
:= fIter
.volume
.OpenFileByIndex(f
);
334 if wantMap
then continue
;
335 if logError
then e_WriteLog(Format('DFWAD: can''t open file [%s] in [%s]', [name
, fFileName
]), TMsgType
.Warning
);
338 // if we want only maps, check if this is map
339 {$IFDEF SFS_MAPDETECT_FX}
343 {$IF DEFINED(D2D_NEW_MAP_READER_DBG)}
344 e_LogWritefln('DFWAD: checking for good map in wad [%s], file [%s] (#%d)', [fFileName
, fi
.fname
, f
]);
347 //fs.readBuffer(sign, 3);
348 //goodMap := (sign = MAP_SIGNATURE);
349 //if not goodMap then goodMap := (sign[0] = 'm') and (sign[1] = 'a') and (sign[2] = 'p');
350 goodMap
:= TDynMapDef
.canBeMap(fs
);
351 {$IF DEFINED(D2D_NEW_MAP_READER_DBG)}
353 e_LogWritefln(' GOOD map in wad [%s], file [%s] (#%d)', [fFileName
, fi
.fname
, f
])
355 e_LogWritefln(' BAD map in wad [%s], file [%s] (#%d)', [fFileName
, fi
.fname
, f
]);
361 {$IF DEFINED(D2D_NEW_MAP_READER_DBG)}
362 e_LogWritefln(' not a map in wad [%s], file [%s] (#%d)', [fFileName
, fi
.fname
, f
]);
370 Len
:= Integer(fs
.size
);
374 fs
.ReadBuffer(pData
^, Len
);
385 {$IFNDEF SFS_MAPDETECT_FX}
391 //Move(pData^, sign, 3);
392 //goodMap := (sign = MAP_SIGNATURE);
393 wst
:= TSFSMemoryChunkStream
.Create(pData
, Len
);
395 goodMap
:= TDynMapDef
.canBeMap(wst
);
403 //e_WriteLog(Format(' not a map in wad [%s], file [%s] (#%d)', [fFileName, fi.fname, f]), MSG_NOTIFY);
412 {$IFDEF SFS_DFWAD_DEBUG}
414 e_WriteLog(Format('DFWAD: file [%s] FOUND in [%s]; size is %d bytes', [name
, fFileName
, Len
]), TMsgType
.Notify
);
419 if logError
then e_WriteLog(Format('DFWAD: file [%s] not found in [%s]', [name
, fFileName
]), TMsgType
.Warning
);
422 function TWADFile
.GetResource (name
: AnsiString; var pData
: Pointer; var Len
: Integer; logError
: Boolean=true): Boolean;
424 result
:= GetResourceEx(name
, false, pData
, Len
, logError
);
427 function TWADFile
.GetMapResource (name
: AnsiString; var pData
: Pointer; var Len
: Integer; logError
: Boolean=true): Boolean;
429 result
:= GetResourceEx(name
, true, pData
, Len
, logError
);
432 function TWADFile
.GetMapResources (): SSArray
;
439 if not isOpen
or (fIter
= nil) then Exit
;
440 for f
:= fIter
.Count
-1 downto 0 do
442 fi
:= fIter
.Files
[f
];
443 if fi
= nil then continue
;
444 if length(fi
.name
) = 0 then continue
;
445 {$IF DEFINED(D2D_NEW_MAP_READER)}
446 //e_LogWritefln('DFWAD: checking for map in wad [%s], file [%s] (#%d)', [fFileName, fi.fname, f]);
448 if isMapResource(f
) then
450 s
:= removeExt(fi
.name
);
454 if StrEquCI1251(result
[c
], s
) then break
;
459 SetLength(result
, Length(result
)+1);
460 result
[high(result
)] := removeExt(fi
.name
);
467 function TWADFile
.ReadFile (FileName
: AnsiString): Boolean;
474 //e_WriteLog(Format('TWADFile.ReadFile: [%s]', [FileName]), MSG_NOTIFY);
476 rfn
:= findDiskWad(FileName
);
477 if length(rfn
) = 0 then
479 e_WriteLog(Format('TWADFile.ReadFile: error looking for [%s]', [FileName
]), TMsgType
.Notify
);
482 {$IFDEF SFS_DFWAD_DEBUG}
483 if wadoptDebug
then e_WriteLog(Format('TWADFile.ReadFile: FOUND [%s]', [rfn
]), TMsgType
.Notify
);
489 if not SFSAddDataFile(rfn
, true) then exit
;
493 if not SFSAddDataFileTemp(rfn
, true) then exit
;
498 fIter
:= SFSFileList(rfn
);
499 if fIter
= nil then Exit
;
501 {$IFDEF SFS_DFWAD_DEBUG}
502 if wadoptDebug
then e_WriteLog(Format('TWADFile.ReadFile: [%s] opened', [fFileName
]), TMsgType
.Notify
);
509 uniqueCounter
: Integer = 0;
511 function TWADFile
.ReadMemory (Data
: Pointer; Len
: LongWord): Boolean;
520 if (Data
= nil) or (Len
= 0) then
522 e_WriteLog('TWADFile.ReadMemory: EMPTY SUBWAD!', TMsgType
.Warning
);
526 fn
:= Format(' -- memwad %d -- ', [uniqueCounter
]);
528 {$IFDEF SFS_DFWAD_DEBUG}
529 if wadoptDebug
then e_WriteLog(Format('TWADFile.ReadMemory: [%s]', [fn
]), TMsgType
.Notify
);
533 st
:= TSFSMemoryStreamRO
.Create(Data
, Len
);
534 if not SFSAddSubDataFile(fn
, st
, true) then
544 fIter
:= SFSFileList(fn
);
545 if fIter
= nil then Exit
;
548 {$IFDEF SFS_DFWAD_DEBUG}
549 if wadoptDebug
then e_WriteLog(Format('TWADFile.ReadMemory: [%s] opened', [fFileName
]), TMsgType
.Notify
);
553 for f := 0 to fIter.Count-1 do
555 fi := fIter.Files[f];
556 if fi = nil then continue;
557 st := fIter.volume.OpenFileByIndex(f);
560 e_WriteLog(Format('[%s]: [%s : %s] CAN''T OPEN', [fFileName, fi.path, fi.name]), MSG_NOTIFY);
564 e_WriteLog(Format('[%s]: [%s : %s] %u', [fFileName, fi.path, fi.name, st.size]), MSG_NOTIFY);
568 //fIter.volume.OpenFileByIndex(0);
576 sfsDiskDirs
:= '<exedir>/data'; //FIXME