DEADSOFTWARE

net: slightly better detection of "map change" event 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;
22 function g_Res_SearchSameWAD(const path, filename: AnsiString; const resMd5: TMD5Digest): AnsiString;
24 // download map wad from server (if necessary)
25 // download all required map resource wads too
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 // call this before downloading a new map from a server
30 procedure g_Res_ClearReplacementWads ();
31 // returns original name, or replacement name
32 function g_Res_FindReplacementWad (oldname: AnsiString): AnsiString;
33 procedure g_Res_PutReplacementWad (oldname: AnsiString; newDiskName: AnsiString);
36 implementation
38 uses g_language, sfs, utils, wadreader, g_game, hashtable;
40 //const DOWNLOAD_DIR = 'downloads';
42 var
43 replacements: THashStrStr = nil;
46 // call this before downloading a new map from a server
47 procedure g_Res_ClearReplacementWads ();
48 begin
49 if assigned(replacements) then replacements.clear();
50 e_LogWriteln('cleared replacement wads');
51 end;
54 // returns original name, or replacement name
55 function g_Res_FindReplacementWad (oldname: AnsiString): AnsiString;
56 var
57 fn: AnsiString;
58 begin
59 result := oldname;
60 if not assigned(replacements) then exit;
61 if (replacements.get(toLowerCase1251(ExtractFileName(oldname)), fn)) then result := fn;
62 end;
65 procedure g_Res_PutReplacementWad (oldname: AnsiString; newDiskName: AnsiString);
66 begin
67 e_LogWritefln('adding replacement wad: oldname=%s; newname=%s', [oldname, newDiskName]);
68 if not assigned(replacements) then replacements := THashStrStr.Create();
69 replacements.put(toLowerCase1251(oldname), newDiskName);
70 end;
73 function scanDir (dirName: AnsiString; baseName: AnsiString; const resMd5: TMD5Digest): AnsiString;
74 var
75 searchResult: TSearchRec;
76 dfn: AnsiString;
77 md5: TMD5Digest;
78 dirs: array of AnsiString;
79 f: Integer;
80 begin
81 result := '';
82 SetLength(dirs, 0);
83 if (length(baseName) = 0) then exit;
84 dirName := IncludeTrailingPathDelimiter(dirName);
85 e_LogWritefln('scanning dir `%s` for file `%s`...', [dirName, baseName]);
87 // scan files
88 if (FindFirst(dirName+'*', faAnyFile, searchResult) <> 0) then exit;
89 try
90 repeat
91 if ((searchResult.Attr and faDirectory) = 0) then
92 begin
93 if (isWadNamesEqu(searchResult.Name, baseName)) then
94 begin
95 dfn := dirName+searchResult.Name;
96 if FileExists(dfn) then
97 begin
98 e_LogWritefln(' found `%s`...', [dfn]);
99 md5 := MD5File(dfn);
100 if MD5Match(md5, resMd5) then
101 begin
102 e_LogWritefln(' MATCH `%s`...', [dfn]);
103 SetLength(dirs, 0);
104 result := dfn;
105 exit;
106 end;
107 end;
108 end;
109 end
110 else
111 begin
112 if (searchResult.Name <> '.') and (searchResult.Name <> '..') then
113 begin
114 dfn := dirName+searchResult.Name;
115 SetLength(dirs, Length(dirs)+1);
116 dirs[length(dirs)-1] := dfn;
117 end;
118 end;
119 until (FindNext(searchResult) <> 0);
120 finally
121 FindClose(searchResult);
122 end;
124 // scan subdirs
125 for f := 0 to High(dirs) do
126 begin
127 dfn := dirs[f];
128 result := scanDir(dfn, baseName, resMd5);
129 if (length(result) <> 0) then begin SetLength(dirs, 0); exit; end;
130 end;
131 SetLength(dirs, 0);
132 end;
135 function g_Res_SearchResWad (asMap: Boolean; fname: AnsiString; const resMd5: TMD5Digest): AnsiString;
136 begin
137 result := '';
138 //if not assigned(scannedDirs) then scannedDirs := THashStrInt.Create();
139 if (asMap) then
140 begin
141 result := scanDir(GameDir+'/maps', ExtractFileName(fname), resMd5);
142 end
143 else
144 begin
145 result := scanDir(GameDir+'/wads', ExtractFileName(fname), resMd5);
146 end;
147 end;
150 function g_Res_SearchSameWAD (const path, filename: AnsiString; const resMd5: TMD5Digest): AnsiString;
151 begin
152 result := scanDir(path, filename, resMd5);
153 end;
156 function g_Res_DownloadMapWAD (FileName: AnsiString; const mapHash: TMD5Digest): AnsiString;
157 var
158 tf: TNetFileTransfer;
159 resList: TStringList;
160 f, res: Integer;
161 strm: TStream;
162 fname: AnsiString;
163 wadname: AnsiString;
164 md5: TMD5Digest;
165 begin
166 //SetLength(mapData.ExternalResources, 0);
167 result := '';
168 g_Res_ClearReplacementWads();
170 resList := TStringList.Create();
172 try
173 g_Res_received_map_start := 1;
174 g_Console_Add(Format(_lc[I_NET_MAP_DL], [FileName]));
175 e_WriteLog('Downloading map `' + FileName + '` from server', TMsgType.Notify);
176 g_Game_SetLoadingText(FileName + '...', 0, False);
177 //MC_SEND_MapRequest();
178 if (not g_Net_SendMapRequest()) then exit;
180 FileName := ExtractFileName(FileName);
181 if (length(FileName) = 0) then FileName := 'fucked_map_wad.wad';
182 res := g_Net_Wait_MapInfo(tf, resList);
183 if (res <> 0) then exit;
185 // find or download a map
186 result := g_Res_SearchResWad(true{asMap}, tf.diskName, mapHash);
187 if (length(result) = 0) then
188 begin
189 // download map
190 res := g_Net_RequestResFileInfo(-1{map}, tf);
191 if (res <> 0) then
192 begin
193 e_LogWriteln('error requesting map wad');
194 result := '';
195 exit;
196 end;
197 try
198 CreateDir(GameDir+'/maps/downloads');
199 except
200 end;
201 fname := GameDir+'/maps/downloads/'+FileName;
202 try
203 strm := openDiskFileRW(fname);
204 except
205 e_WriteLog('cannot create map file `'+FileName+'`', TMsgType.Fatal);
206 result := '';
207 exit;
208 end;
209 tf.diskName := fname;
210 try
211 res := g_Net_ReceiveResourceFile(-1{map}, tf, strm);
212 except
213 e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal);
214 strm.Free;
215 result := '';
216 exit;
217 end;
218 strm.Free;
219 if (res <> 0) then
220 begin
221 e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal);
222 result := '';
223 exit;
224 end;
225 // if it was resumed, check md5 and initiate full download if necessary
226 if tf.resumed then
227 begin
228 md5 := MD5File(fname);
229 // sorry for pasta, i am asshole
230 if not MD5Match(md5, tf.hash) then
231 begin
232 e_LogWritefln('resuming failed; downloading map `%s` from scratch...', [fname]);
233 try
234 DeleteFile(fname);
235 strm := createDiskFile(fname);
236 except
237 e_WriteLog('cannot create map file `'+fname+'`', TMsgType.Fatal);
238 result := '';
239 exit;
240 end;
241 try
242 res := g_Net_ReceiveResourceFile(-1{map}, tf, strm);
243 except
244 e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal);
245 strm.Free;
246 result := '';
247 exit;
248 end;
249 strm.Free;
250 if (res <> 0) then
251 begin
252 e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal);
253 result := '';
254 exit;
255 end;
256 end;
257 end;
258 result := fname;
259 end;
261 // download resources
262 for f := 0 to resList.Count-1 do
263 begin
264 res := g_Net_RequestResFileInfo(f, tf);
265 if (res <> 0) then begin result := ''; exit; end;
266 wadname := g_Res_SearchResWad(false{asMap}, tf.diskName, tf.hash);
267 if (length(wadname) <> 0) then
268 begin
269 // already here
270 g_Net_AbortResTransfer(tf);
271 g_Res_PutReplacementWad(tf.diskName, wadname);
272 end
273 else
274 begin
275 try
276 CreateDir(GameDir+'/wads/downloads');
277 except
278 end;
279 fname := GameDir+'/wads/downloads/'+tf.diskName;
280 e_LogWritefln('downloading resource `%s` to `%s`...', [tf.diskName, fname]);
281 try
282 strm := openDiskFileRW(fname);
283 except
284 e_WriteLog('cannot create resource file `'+fname+'`', TMsgType.Fatal);
285 result := '';
286 exit;
287 end;
288 try
289 res := g_Net_ReceiveResourceFile(f, tf, strm);
290 except
291 e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal);
292 strm.Free;
293 result := '';
294 exit;
295 end;
296 strm.Free;
297 if (res <> 0) then
298 begin
299 e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal);
300 result := '';
301 exit;
302 end;
303 // if it was resumed, check md5 and initiate full download if necessary
304 if tf.resumed then
305 begin
306 md5 := MD5File(fname);
307 // sorry for pasta, i am asshole
308 if not MD5Match(md5, tf.hash) then
309 begin
310 e_LogWritefln('resuming failed; downloading resource `%s` to `%s` from scratch...', [tf.diskName, fname]);
311 try
312 DeleteFile(fname);
313 strm := createDiskFile(fname);
314 except
315 e_WriteLog('cannot create resource file `'+fname+'`', TMsgType.Fatal);
316 result := '';
317 exit;
318 end;
319 try
320 res := g_Net_ReceiveResourceFile(f, tf, strm);
321 except
322 e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal);
323 strm.Free;
324 result := '';
325 exit;
326 end;
327 strm.Free;
328 if (res <> 0) then
329 begin
330 e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal);
331 result := '';
332 exit;
333 end;
334 end;
335 end;
336 g_Res_PutReplacementWad(tf.diskName, fname);
337 end;
338 end;
339 finally
340 resList.Free;
341 g_Res_received_map_start := 0;
342 end;
343 end;
346 end.