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, version 3 of the License ONLY.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 {$INCLUDE ../shared/a_modes.inc}
16 unit g_res_downloader
;
20 uses sysutils
, Classes
, md5
, g_net
, g_netmsg
, g_console
, g_main
, e_log
;
23 // download map wad from server (if necessary)
24 // download all required map resource wads too
25 // registers all required replacement wads
26 // returns name of the map wad (relative to mapdir), or empty string on error
27 function g_Res_DownloadMapWAD (FileName
: AnsiString; const mapHash
: TMD5Digest
): AnsiString;
29 // returns original name, or replacement name
30 function g_Res_FindReplacementWad (oldname
: AnsiString): AnsiString;
35 uses g_language
, sfs
, utils
, wadreader
, g_game
, hashtable
;
39 g_res_ignore_names
: AnsiString = 'standard;shrshade';
40 g_res_ignore_enabled
: Boolean = true;
42 replacements
: THashStrStr
= nil;
45 //==========================================================================
47 // clearReplacementWads
49 // call this before downloading a new map from a server
51 //==========================================================================
52 procedure clearReplacementWads ();
54 if assigned(replacements
) then replacements
.clear();
55 e_LogWriteln('cleared replacement wads');
59 //==========================================================================
63 // register new replacement wad
65 //==========================================================================
66 procedure addReplacementWad (oldname
: AnsiString; newDiskName
: AnsiString);
68 e_LogWritefln('adding replacement wad: oldname=%s; newname=%s', [oldname
, newDiskName
]);
69 if not assigned(replacements
) then replacements
:= THashStrStr
.Create();
70 replacements
.put(toLowerCase1251(oldname
), newDiskName
);
74 //==========================================================================
76 // g_Res_FindReplacementWad
78 // returns original name, or replacement name
80 //==========================================================================
81 function g_Res_FindReplacementWad (oldname
: AnsiString): AnsiString;
86 if not assigned(replacements
) then exit
;
87 if (replacements
.get(toLowerCase1251(ExtractFileName(oldname
)), fn
)) then result
:= fn
;
91 //==========================================================================
95 // look for a wad to match the hash
96 // scans subdirs, ignores known wad extensions
98 // returns found wad disk name, or empty string
100 //==========================================================================
101 function scanDir (dirName
: AnsiString; baseName
: AnsiString; const resMd5
: TMD5Digest
): AnsiString;
103 searchResult
: TSearchRec
;
106 dirs
: array of AnsiString;
111 if (length(baseName
) = 0) then exit
;
112 dirName
:= IncludeTrailingPathDelimiter(dirName
);
113 e_LogWritefln('scanning dir `%s` for file `%s`...', [dirName
, baseName
]);
116 if (FindFirst(dirName
+'*', faAnyFile
, searchResult
) <> 0) then exit
;
119 if ((searchResult
.Attr
and faDirectory
) = 0) then
121 if (isWadNamesEqu(searchResult
.Name
, baseName
)) then
123 dfn
:= dirName
+searchResult
.Name
;
124 if FileExists(dfn
) then
126 e_LogWritefln(' found `%s`...', [dfn
]);
128 if MD5Match(md5
, resMd5
) then
130 e_LogWritefln(' MATCH `%s`...', [dfn
]);
140 if (searchResult
.Name
<> '.') and (searchResult
.Name
<> '..') then
142 dfn
:= dirName
+searchResult
.Name
;
143 SetLength(dirs
, Length(dirs
)+1);
144 dirs
[length(dirs
)-1] := dfn
;
147 until (FindNext(searchResult
) <> 0);
149 FindClose(searchResult
);
153 for f
:= 0 to High(dirs
) do
156 result
:= scanDir(dfn
, baseName
, resMd5
);
157 if (length(result
) <> 0) then begin SetLength(dirs
, 0); exit
; end;
163 //==========================================================================
165 // findExistingMapWadWithHash
167 // find map or resource wad using its base name and hash
169 // returns found wad disk name, or empty string
171 //==========================================================================
172 function findExistingMapWadWithHash (fname
: AnsiString; const resMd5
: TMD5Digest
): AnsiString;
174 result
:= scanDir(GameDir
+'/maps', ExtractFileName(fname
), resMd5
);
178 //==========================================================================
180 // findExistingResWadWithHash
182 // find map or resource wad using its base name and hash
184 // returns found wad disk name, or empty string
186 //==========================================================================
187 function findExistingResWadWithHash (fname
: AnsiString; const resMd5
: TMD5Digest
): AnsiString;
189 result
:= scanDir(GameDir
+'/wads', ExtractFileName(fname
), resMd5
);
193 //==========================================================================
195 // g_Res_DownloadMapWAD
197 // download map wad from server (if necessary)
198 // download all required map resource wads too
199 // registers all required replacement wads
201 // returns name of the map wad (relative to mapdir), or empty string on error
203 //==========================================================================
204 function g_Res_DownloadMapWAD (FileName
: AnsiString; const mapHash
: TMD5Digest
): AnsiString;
206 tf
: TNetFileTransfer
;
207 resList
: TStringList
;
215 clearReplacementWads();
217 resList
:= TStringList
.Create();
220 g_Res_received_map_start
:= 1;
221 g_Console_Add(Format(_lc
[I_NET_MAP_DL
], [FileName
]));
222 e_WriteLog('Downloading map `' + FileName
+ '` from server', TMsgType
.Notify
);
223 g_Game_SetLoadingText(FileName
+ '...', 0, False);
224 if (not g_Net_SendMapRequest()) then exit
;
226 FileName
:= ExtractFileName(FileName
);
227 if (length(FileName
) = 0) then FileName
:= 'fucked_map_wad.wad';
228 res
:= g_Net_Wait_MapInfo(tf
, resList
);
229 if (res
<> 0) then exit
;
231 // find or download a map
232 result
:= findExistingMapWadWithHash(tf
.diskName
, mapHash
);
233 if (length(result
) = 0) then
236 res
:= g_Net_RequestResFileInfo(-1{map}, tf
);
239 e_LogWriteln('error requesting map wad');
244 CreateDir(GameDir
+'/maps/downloads');
247 fname
:= GameDir
+'/maps/downloads/'+FileName
;
249 strm
:= openDiskFileRW(fname
);
251 e_WriteLog('cannot create map file `'+FileName
+'`', TMsgType
.Fatal
);
255 tf
.diskName
:= fname
;
257 res
:= g_Net_ReceiveResourceFile(-1{map}, tf
, strm
);
259 e_WriteLog('error downloading map file `'+FileName
+'`', TMsgType
.Fatal
);
267 e_WriteLog('error downloading map file `'+FileName
+'`', TMsgType
.Fatal
);
271 // if it was resumed, check md5 and initiate full download if necessary
274 md5
:= MD5File(fname
);
275 // sorry for pasta, i am asshole
276 if not MD5Match(md5
, tf
.hash
) then
278 e_LogWritefln('resuming failed; downloading map `%s` from scratch...', [fname
]);
281 strm
:= createDiskFile(fname
);
283 e_WriteLog('cannot create map file `'+fname
+'`', TMsgType
.Fatal
);
288 res
:= g_Net_ReceiveResourceFile(-1{map}, tf
, strm
);
290 e_WriteLog('error downloading map file `'+FileName
+'`', TMsgType
.Fatal
);
298 e_WriteLog('error downloading map file `'+FileName
+'`', TMsgType
.Fatal
);
307 // download resources
308 for f
:= 0 to resList
.Count
-1 do
310 res
:= g_Net_RequestResFileInfo(f
, tf
);
311 if (res
<> 0) then begin result
:= ''; exit
; end;
312 wadname
:= findExistingResWadWithHash(tf
.diskName
, tf
.hash
);
313 if (length(wadname
) <> 0) then
316 g_Net_AbortResTransfer(tf
);
317 addReplacementWad(tf
.diskName
, wadname
);
322 CreateDir(GameDir
+'/wads/downloads');
325 fname
:= GameDir
+'/wads/downloads/'+tf
.diskName
;
326 e_LogWritefln('downloading resource `%s` to `%s`...', [tf
.diskName
, fname
]);
328 strm
:= openDiskFileRW(fname
);
330 e_WriteLog('cannot create resource file `'+fname
+'`', TMsgType
.Fatal
);
335 res
:= g_Net_ReceiveResourceFile(f
, tf
, strm
);
337 e_WriteLog('error downloading map file `'+FileName
+'`', TMsgType
.Fatal
);
345 e_WriteLog('error downloading map file `'+FileName
+'`', TMsgType
.Fatal
);
349 // if it was resumed, check md5 and initiate full download if necessary
352 md5
:= MD5File(fname
);
353 // sorry for pasta, i am asshole
354 if not MD5Match(md5
, tf
.hash
) then
356 e_LogWritefln('resuming failed; downloading resource `%s` to `%s` from scratch...', [tf
.diskName
, fname
]);
359 strm
:= createDiskFile(fname
);
361 e_WriteLog('cannot create resource file `'+fname
+'`', TMsgType
.Fatal
);
366 res
:= g_Net_ReceiveResourceFile(f
, tf
, strm
);
368 e_WriteLog('error downloading map file `'+FileName
+'`', TMsgType
.Fatal
);
376 e_WriteLog('error downloading map file `'+FileName
+'`', TMsgType
.Fatal
);
382 addReplacementWad(tf
.diskName
, fname
);
387 g_Res_received_map_start
:= 0;
393 conRegVar('rdl_ignore_names', @g_res_ignore_names
, 'list of resource wad names (without extensions) to ignore in dl hash checks', 'dl ignore wads');
394 conRegVar('rdl_ignore_enabled', @g_res_ignore_enabled
, 'enable dl hash check ignore list', 'dl hash check ignore list active');