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
;
22 function g_Res_SearchSameWAD(const path
, filename
: AnsiString; const resMd5
: TMD5Digest
): AnsiString;
23 function g_Res_SearchResWad (asMap
: Boolean; const resMd5
: TMD5Digest
): AnsiString;
25 // download map wad from server (if necessary)
26 // download all required map resource wads too
27 // returns name of the map wad (relative to mapdir), or empty string on error
28 function g_Res_DownloadMapWAD (FileName
: AnsiString; const mapHash
: TMD5Digest
): AnsiString;
30 // call this before downloading a new map from a server
31 procedure g_Res_ClearReplacementWads ();
32 // returns original name, or replacement name
33 function g_Res_FindReplacementWad (oldname
: AnsiString): AnsiString;
34 procedure g_Res_PutReplacementWad (oldname
: AnsiString; newDiskName
: AnsiString);
39 uses g_language
, sfs
, utils
, wadreader
, g_game
, hashtable
;
41 const DOWNLOAD_DIR
= 'downloads';
45 diskName
: AnsiString; // lowercased
46 baseName
: AnsiString; // lowercased
49 nextBaseNameIndex
: Integer;
53 knownFiles
: array of TFileInfo
;
54 knownHash
: THashStrInt
= nil; // key: base name; value: index
55 scannedDirs
: THashStrInt
= nil; // key: lowercased dir name
56 replacements
: THashStrStr
= nil;
59 function findKnownFile (diskName
: AnsiString): Integer;
65 if not assigned(knownHash
) then exit
;
66 if (length(diskName
) = 0) then exit
;
67 baseName
:= toLowerCase1251(ExtractFileName(diskName
));
68 if (not knownHash
.get(baseName
, idx
)) then exit
;
69 if (idx
< 0) or (idx
>= length(knownFiles
)) then raise Exception
.Create('wutafuck?');
72 if (strEquCI1251(knownFiles
[idx
].diskName
, diskName
)) then begin result
:= idx
; exit
; end; // i found her!
73 idx
:= knownFiles
[idx
].nextBaseNameIndex
;
78 function addKnownFile (diskName
: AnsiString): Integer;
81 lastIdx
: Integer = -1;
86 if not assigned(knownHash
) then knownHash
:= THashStrInt
.Create();
87 if (length(diskName
) = 0) then exit
;
88 baseName
:= toLowerCase1251(ExtractFileName(diskName
));
89 if (length(baseName
) = 0) then exit
;
90 // check if we already have this file
91 if (knownHash
.get(baseName
, idx
)) then
93 if (idx
< 0) or (idx
>= length(knownFiles
)) then raise Exception
.Create('wutafuck?');
96 if (strEquCI1251(knownFiles
[idx
].diskName
, diskName
)) then
103 idx
:= knownFiles
[idx
].nextBaseNameIndex
;
106 // this file is not there, append it
107 idx
:= length(knownFiles
);
109 SetLength(knownFiles
, idx
+1); // sorry
110 fi
:= @knownFiles
[idx
];
111 fi
.diskName
:= diskName
;
112 fi
.baseName
:= baseName
;
113 fi
.md5valid
:= false;
114 fi
.nextBaseNameIndex
:= -1;
115 if (lastIdx
< 0) then
118 knownHash
.put(baseName
, idx
);
122 knownFiles
[lastIdx
].nextBaseNameIndex
:= idx
;
127 function getKnownFileWithMD5 (diskDir
: AnsiString; baseName
: AnsiString; const md5
: TMD5Digest
): AnsiString;
132 if not assigned(knownHash
) then exit
;
133 if (not knownHash
.get(toLowerCase1251(baseName
), idx
)) then exit
;
134 if (idx
< 0) or (idx
>= length(knownFiles
)) then raise Exception
.Create('wutafuck?');
137 if (strEquCI1251(knownFiles
[idx
].diskName
, IncludeTrailingPathDelimiter(diskDir
)+baseName
)) then
139 if (not knownFiles
[idx
].md5valid
) then
141 knownFiles
[idx
].md5
:= MD5File(knownFiles
[idx
].diskName
);
142 knownFiles
[idx
].md5valid
:= true;
144 if (MD5Match(knownFiles
[idx
].md5
, md5
)) then
146 result
:= knownFiles
[idx
].diskName
;
150 idx
:= knownFiles
[idx
].nextBaseNameIndex
;
155 // call this before downloading a new map from a server
156 procedure g_Res_ClearReplacementWads ();
158 if assigned(replacements
) then replacements
.clear();
159 e_LogWriteln('cleared replacement wads');
163 // returns original name, or replacement name
164 function g_Res_FindReplacementWad (oldname
: AnsiString): AnsiString;
169 if not assigned(replacements
) then exit
;
170 if (replacements
.get(toLowerCase1251(ExtractFileName(oldname
)), fn
)) then result
:= fn
;
174 procedure g_Res_PutReplacementWad (oldname
: AnsiString; newDiskName
: AnsiString);
176 e_LogWritefln('adding replacement wad: oldname=%s; newname=%s', [oldname
, newDiskName
]);
177 replacements
.put(toLowerCase1251(oldname
), newDiskName
);
181 procedure scanDir (const dirName
: AnsiString; calcMD5
: Boolean);
183 searchResult
: TSearchRec
;
187 if not assigned(scannedDirs
) then scannedDirs
:= THashStrInt
.Create();
188 dfn
:= toLowerCase1251(IncludeTrailingPathDelimiter(dirName
));
189 if scannedDirs
.has(dfn
) then exit
;
190 scannedDirs
.put(dfn
, 42);
192 if (FindFirst(dirName
+'/*', faAnyFile
, searchResult
) <> 0) then exit
;
195 if (searchResult
.Attr
and faDirectory
) = 0 then
197 dfn
:= dirName
+'/'+searchResult
.Name
;
198 idx
:= addKnownFile(dfn
);
199 if (calcMD5
) and (idx
>= 0) then
201 if (not knownFiles
[idx
].md5valid
) then
203 knownFiles
[idx
].md5
:= MD5File(knownFiles
[idx
].diskName
);
204 knownFiles
[idx
].md5valid
:= true;
208 else if (searchResult
.Name
<> '.') and (searchResult
.Name
<> '..') then
210 scanDir(IncludeTrailingPathDelimiter(dirName
)+searchResult
.Name
, calcMD5
);
212 until (FindNext(searchResult
) <> 0);
214 FindClose(searchResult
);
219 function CompareFileHash(const filename
: AnsiString; const resMd5
: TMD5Digest
): Boolean;
221 gResHash
: TMD5Digest
;
224 fname
:= findDiskWad(filename
);
225 if length(fname
) = 0 then begin result
:= false; exit
; end;
226 gResHash
:= MD5File(fname
);
227 Result
:= MD5Match(gResHash
, resMd5
);
230 function CheckFileHash(const path
, filename
: AnsiString; const resMd5
: TMD5Digest
): Boolean;
234 fname
:= findDiskWad(path
+filename
);
235 if length(fname
) = 0 then begin result
:= false; exit
; end;
236 Result
:= FileExists(fname
) and CompareFileHash(fname
, resMd5
);
240 function g_Res_SearchResWad (asMap
: Boolean; const resMd5
: TMD5Digest
): AnsiString;
245 //if not assigned(scannedDirs) then scannedDirs := THashStrInt.Create();
248 scanDir(GameDir
+'/maps/downloads', true);
252 scanDir(GameDir
+'/wads/downloads', true);
254 for f
:= Low(knownFiles
) to High(knownFiles
) do
256 if (not knownFiles
[f
].md5valid
) then continue
;
257 if (MD5Match(knownFiles
[f
].md5
, resMd5
)) then
259 result
:= knownFiles
[f
].diskName
;
263 //resStream := createDiskFile(GameDir+'/wads/'+mapData.ExternalResources[i].Name);
267 function g_Res_SearchSameWAD (const path
, filename
: AnsiString; const resMd5
: TMD5Digest
): AnsiString;
269 scanDir(path
, false);
270 result
:= getKnownFileWithMD5(path
, filename
, resMd5
);
274 function g_Res_DownloadMapWAD (FileName
: AnsiString; const mapHash
: TMD5Digest
): AnsiString;
276 tf
: TNetFileTransfer
;
277 resList
: TStringList
;
285 //SetLength(mapData.ExternalResources, 0);
286 //result := g_Res_SearchResWad(true{asMap}, mapHash);
288 g_Res_ClearReplacementWads();
291 CreateDir(GameDir
+'/maps/downloads');
296 CreateDir(GameDir
+'/wads/downloads');
300 resList
:= TStringList
.Create();
303 g_Console_Add(Format(_lc
[I_NET_MAP_DL
], [FileName
]));
304 e_WriteLog('Downloading map `' + FileName
+ '` from server', TMsgType
.Notify
);
305 g_Game_SetLoadingText(FileName
+ '...', 0, False);
306 //MC_SEND_MapRequest();
307 if (not g_Net_SendMapRequest()) then exit
;
309 FileName
:= ExtractFileName(FileName
);
310 if (length(FileName
) = 0) then FileName
:= 'fucked_map_wad.wad';
311 res
:= g_Net_Wait_MapInfo(tf
, resList
);
312 if (res
<> 0) then exit
;
314 // find or download a map
315 result
:= g_Res_SearchResWad(true{asMap}, mapHash
);
316 if (length(result
) = 0) then
319 res
:= g_Net_RequestResFileInfo(-1{map}, tf
);
322 e_LogWriteln('error requesting map wad');
326 fname
:= GameDir
+'/maps/downloads/'+FileName
;
328 strm
:= createDiskFile(fname
);
330 e_WriteLog('cannot create map file `'+FileName
+'`', TMsgType
.Fatal
);
335 res
:= g_Net_ReceiveResourceFile(-1{map}, tf
, strm
);
337 e_WriteLog('error downloading map file `'+FileName
+'`', TMsgType
.Fatal
);
345 e_WriteLog('error downloading map file `'+FileName
+'`', TMsgType
.Fatal
);
349 mmd5
:= MD5File(fname
);
350 if (not MD5Match(mmd5
, mapHash
)) then
352 e_WriteLog('error downloading map file `'+FileName
+'` (bad hash)', TMsgType
.Fatal
);
356 idx
:= addKnownFile(fname
);
359 e_WriteLog('error downloading map file `'+FileName
+'`', TMsgType
.Fatal
);
363 knownFiles
[idx
].md5
:= mmd5
;
364 knownFiles
[idx
].md5valid
:= true;
368 // download resources
369 for f
:= 0 to resList
.Count
-1 do
371 res
:= g_Net_RequestResFileInfo(f
, tf
);
372 if (res
<> 0) then begin result
:= ''; exit
; end;
373 wadname
:= g_Res_SearchResWad(false{asMap}, tf
.hash
);
374 if (length(wadname
) <> 0) then
377 g_Net_AbortResTransfer(tf
);
378 g_Res_PutReplacementWad(tf
.diskName
, wadname
);
382 fname
:= GameDir
+'/wads/downloads/'+tf
.diskName
;
384 strm
:= createDiskFile(fname
);
386 e_WriteLog('cannot create resource file `'+fname
+'`', TMsgType
.Fatal
);
391 res
:= g_Net_ReceiveResourceFile(f
, tf
, strm
);
393 e_WriteLog('error downloading map file `'+FileName
+'`', TMsgType
.Fatal
);
401 e_WriteLog('error downloading map file `'+FileName
+'`', TMsgType
.Fatal
);
405 idx
:= addKnownFile(fname
);
408 e_WriteLog('error downloading map file `'+FileName
+'`', TMsgType
.Fatal
);
412 knownFiles
[idx
].md5
:= tf
.hash
;
413 knownFiles
[idx
].md5valid
:= true;
414 g_Res_PutReplacementWad(tf
.diskName
, fname
);