DEADSOFTWARE

net: cosmetix and code cleanup in resource downloader
[d2df-sdl.git] / src / game / g_res_downloader.pas
1 (* Copyright (C) Doom 2D: Forever Developers
2 *
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.
6 *
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.
11 *
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/>.
14 *)
15 {$INCLUDE ../shared/a_modes.inc}
16 unit g_res_downloader;
18 interface
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;
33 implementation
35 uses g_language, sfs, utils, wadreader, g_game, hashtable;
37 var
38 // cvars
39 g_res_ignore_names: AnsiString = 'standard;shrshade';
40 g_res_ignore_enabled: Boolean = true;
41 // other vars
42 replacements: THashStrStr = nil;
45 //==========================================================================
46 //
47 // clearReplacementWads
48 //
49 // call this before downloading a new map from a server
50 //
51 //==========================================================================
52 procedure clearReplacementWads ();
53 begin
54 if assigned(replacements) then replacements.clear();
55 e_LogWriteln('cleared replacement wads');
56 end;
59 //==========================================================================
60 //
61 // addReplacementWad
62 //
63 // register new replacement wad
64 //
65 //==========================================================================
66 procedure addReplacementWad (oldname: AnsiString; newDiskName: AnsiString);
67 begin
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);
71 end;
74 //==========================================================================
75 //
76 // g_Res_FindReplacementWad
77 //
78 // returns original name, or replacement name
79 //
80 //==========================================================================
81 function g_Res_FindReplacementWad (oldname: AnsiString): AnsiString;
82 var
83 fn: AnsiString;
84 begin
85 result := oldname;
86 if not assigned(replacements) then exit;
87 if (replacements.get(toLowerCase1251(ExtractFileName(oldname)), fn)) then result := fn;
88 end;
91 //==========================================================================
92 //
93 // scanDir
94 //
95 // look for a wad to match the hash
96 // scans subdirs, ignores known wad extensions
97 //
98 // returns found wad disk name, or empty string
99 //
100 //==========================================================================
101 function scanDir (dirName: AnsiString; baseName: AnsiString; const resMd5: TMD5Digest): AnsiString;
102 var
103 searchResult: TSearchRec;
104 dfn: AnsiString;
105 md5: TMD5Digest;
106 dirs: array of AnsiString;
107 f: Integer;
108 begin
109 result := '';
110 SetLength(dirs, 0);
111 if (length(baseName) = 0) then exit;
112 dirName := IncludeTrailingPathDelimiter(dirName);
113 e_LogWritefln('scanning dir `%s` for file `%s`...', [dirName, baseName]);
115 // scan files
116 if (FindFirst(dirName+'*', faAnyFile, searchResult) <> 0) then exit;
117 try
118 repeat
119 if ((searchResult.Attr and faDirectory) = 0) then
120 begin
121 if (isWadNamesEqu(searchResult.Name, baseName)) then
122 begin
123 dfn := dirName+searchResult.Name;
124 if FileExists(dfn) then
125 begin
126 e_LogWritefln(' found `%s`...', [dfn]);
127 md5 := MD5File(dfn);
128 if MD5Match(md5, resMd5) then
129 begin
130 e_LogWritefln(' MATCH `%s`...', [dfn]);
131 SetLength(dirs, 0);
132 result := dfn;
133 exit;
134 end;
135 end;
136 end;
137 end
138 else
139 begin
140 if (searchResult.Name <> '.') and (searchResult.Name <> '..') then
141 begin
142 dfn := dirName+searchResult.Name;
143 SetLength(dirs, Length(dirs)+1);
144 dirs[length(dirs)-1] := dfn;
145 end;
146 end;
147 until (FindNext(searchResult) <> 0);
148 finally
149 FindClose(searchResult);
150 end;
152 // scan subdirs
153 for f := 0 to High(dirs) do
154 begin
155 dfn := dirs[f];
156 result := scanDir(dfn, baseName, resMd5);
157 if (length(result) <> 0) then begin SetLength(dirs, 0); exit; end;
158 end;
159 SetLength(dirs, 0);
160 end;
163 //==========================================================================
164 //
165 // findExistingMapWadWithHash
166 //
167 // find map or resource wad using its base name and hash
168 //
169 // returns found wad disk name, or empty string
170 //
171 //==========================================================================
172 function findExistingMapWadWithHash (fname: AnsiString; const resMd5: TMD5Digest): AnsiString;
173 begin
174 result := scanDir(GameDir+'/maps', ExtractFileName(fname), resMd5);
175 end;
178 //==========================================================================
179 //
180 // findExistingResWadWithHash
181 //
182 // find map or resource wad using its base name and hash
183 //
184 // returns found wad disk name, or empty string
185 //
186 //==========================================================================
187 function findExistingResWadWithHash (fname: AnsiString; const resMd5: TMD5Digest): AnsiString;
188 begin
189 result := scanDir(GameDir+'/wads', ExtractFileName(fname), resMd5);
190 end;
193 //==========================================================================
194 //
195 // g_Res_DownloadMapWAD
196 //
197 // download map wad from server (if necessary)
198 // download all required map resource wads too
199 // registers all required replacement wads
200 //
201 // returns name of the map wad (relative to mapdir), or empty string on error
202 //
203 //==========================================================================
204 function g_Res_DownloadMapWAD (FileName: AnsiString; const mapHash: TMD5Digest): AnsiString;
205 var
206 tf: TNetFileTransfer;
207 resList: TStringList;
208 f, res: Integer;
209 strm: TStream;
210 fname: AnsiString;
211 wadname: AnsiString;
212 md5: TMD5Digest;
213 begin
214 result := '';
215 clearReplacementWads();
217 resList := TStringList.Create();
219 try
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
234 begin
235 // download map
236 res := g_Net_RequestResFileInfo(-1{map}, tf);
237 if (res <> 0) then
238 begin
239 e_LogWriteln('error requesting map wad');
240 result := '';
241 exit;
242 end;
243 try
244 CreateDir(GameDir+'/maps/downloads');
245 except
246 end;
247 fname := GameDir+'/maps/downloads/'+FileName;
248 try
249 strm := openDiskFileRW(fname);
250 except
251 e_WriteLog('cannot create map file `'+FileName+'`', TMsgType.Fatal);
252 result := '';
253 exit;
254 end;
255 tf.diskName := fname;
256 try
257 res := g_Net_ReceiveResourceFile(-1{map}, tf, strm);
258 except
259 e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal);
260 strm.Free;
261 result := '';
262 exit;
263 end;
264 strm.Free;
265 if (res <> 0) then
266 begin
267 e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal);
268 result := '';
269 exit;
270 end;
271 // if it was resumed, check md5 and initiate full download if necessary
272 if tf.resumed then
273 begin
274 md5 := MD5File(fname);
275 // sorry for pasta, i am asshole
276 if not MD5Match(md5, tf.hash) then
277 begin
278 e_LogWritefln('resuming failed; downloading map `%s` from scratch...', [fname]);
279 try
280 DeleteFile(fname);
281 strm := createDiskFile(fname);
282 except
283 e_WriteLog('cannot create map file `'+fname+'`', TMsgType.Fatal);
284 result := '';
285 exit;
286 end;
287 try
288 res := g_Net_ReceiveResourceFile(-1{map}, tf, strm);
289 except
290 e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal);
291 strm.Free;
292 result := '';
293 exit;
294 end;
295 strm.Free;
296 if (res <> 0) then
297 begin
298 e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal);
299 result := '';
300 exit;
301 end;
302 end;
303 end;
304 result := fname;
305 end;
307 // download resources
308 for f := 0 to resList.Count-1 do
309 begin
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
314 begin
315 // already here
316 g_Net_AbortResTransfer(tf);
317 addReplacementWad(tf.diskName, wadname);
318 end
319 else
320 begin
321 try
322 CreateDir(GameDir+'/wads/downloads');
323 except
324 end;
325 fname := GameDir+'/wads/downloads/'+tf.diskName;
326 e_LogWritefln('downloading resource `%s` to `%s`...', [tf.diskName, fname]);
327 try
328 strm := openDiskFileRW(fname);
329 except
330 e_WriteLog('cannot create resource file `'+fname+'`', TMsgType.Fatal);
331 result := '';
332 exit;
333 end;
334 try
335 res := g_Net_ReceiveResourceFile(f, tf, strm);
336 except
337 e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal);
338 strm.Free;
339 result := '';
340 exit;
341 end;
342 strm.Free;
343 if (res <> 0) then
344 begin
345 e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal);
346 result := '';
347 exit;
348 end;
349 // if it was resumed, check md5 and initiate full download if necessary
350 if tf.resumed then
351 begin
352 md5 := MD5File(fname);
353 // sorry for pasta, i am asshole
354 if not MD5Match(md5, tf.hash) then
355 begin
356 e_LogWritefln('resuming failed; downloading resource `%s` to `%s` from scratch...', [tf.diskName, fname]);
357 try
358 DeleteFile(fname);
359 strm := createDiskFile(fname);
360 except
361 e_WriteLog('cannot create resource file `'+fname+'`', TMsgType.Fatal);
362 result := '';
363 exit;
364 end;
365 try
366 res := g_Net_ReceiveResourceFile(f, tf, strm);
367 except
368 e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal);
369 strm.Free;
370 result := '';
371 exit;
372 end;
373 strm.Free;
374 if (res <> 0) then
375 begin
376 e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal);
377 result := '';
378 exit;
379 end;
380 end;
381 end;
382 addReplacementWad(tf.diskName, fname);
383 end;
384 end;
385 finally
386 resList.Free;
387 g_Res_received_map_start := 0;
388 end;
389 end;
392 initialization
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');
395 end.