index 1f5c6cd4fbaf8f3c432ad6677f27c30da5271ad8..68ceb0a71aa8be99baf763cbf8d98ba5b8ad366c 100644 (file)
@@ -29,17 +29,166 @@ function g_Res_DownloadMapWAD (FileName: AnsiString; const mapHash: TMD5Digest):
// returns original name, or replacement name
function g_Res_FindReplacementWad (oldname: AnsiString): AnsiString;
+// call this somewhere in startup sequence
+procedure g_Res_CreateDatabases ();
+
implementation
-uses g_language, sfs, utils, wadreader, g_game, hashtable;
+uses g_language, sfs, utils, wadreader, g_game, hashtable, fhashdb;
var
// cvars
- g_res_ignore_names: AnsiString = 'standard;shrshade';
+ g_res_ignore_names: AnsiString = 'standart;shrshade';
g_res_ignore_enabled: Boolean = true;
+ g_res_save_databases: Boolean = true;
// other vars
replacements: THashStrStr = nil;
+ knownMaps: TFileHashDB = nil;
+ knownRes: TFileHashDB = nil;
+ saveDBsToDiskEnabled: Boolean = false; // this will be set to `true` if initial database saving succeed
+
+
+//==========================================================================
+//
+// saveDatabases
+//
+//==========================================================================
+procedure saveDatabases (saveMap, saveRes: Boolean);
+var
+ err: Boolean;
+ st: TStream;
+begin
+ if (not saveDBsToDiskEnabled) or (not g_res_save_databases) then exit;
+ // rescan dirs
+ // save map database
+ if (saveMap) then
+ begin
+ err := true;
+ st := nil;
+ try
+ st := createDiskFile(GameDir+'/data/maphash.db');
+ knownMaps.saveTo(st);
+ err := false;
+ except
+ end;
+ st.Free;
+ if (err) then begin saveDBsToDiskEnabled := false; e_LogWriteln('cannot write map database, disk refresh disabled'); exit; end;
+ end;
+ // save resource database
+ if (saveRes) then
+ begin
+ err := true;
+ st := nil;
+ try
+ st := createDiskFile(GameDir+'/data/reshash.db');
+ knownRes.saveTo(st);
+ err := false;
+ except
+ end;
+ st.Free;
+ if (err) then begin saveDBsToDiskEnabled := false; e_LogWriteln('cannot write resource database, disk refresh disabled'); exit; end;
+ end;
+end;
+
+
+//==========================================================================
+//
+// g_Res_CreateDatabases
+//
+//==========================================================================
+procedure g_Res_CreateDatabases ();
+var
+ st: TStream;
+begin
+ // create and load a know map database, if necessary
+ knownMaps.Free;
+ knownMaps := TFileHashDB.Create(GameDir+'/maps/');
+ knownRes := TFileHashDB.Create(GameDir+'/wads/');
+ saveDBsToDiskEnabled := true;
+ // load map database
+ st := nil;
+ try
+ st := openDiskFileRO(GameDir+'/data/maphash.db');
+ knownMaps.loadFrom(st);
+ e_LogWriteln('loaded map database');
+ except
+ end;
+ st.Free;
+ // load resource database
+ st := nil;
+ try
+ st := openDiskFileRO(GameDir+'/data/reshash.db');
+ knownRes.loadFrom(st);
+ e_LogWriteln('loaded resource database');
+ except
+ end;
+ st.Free;
+ // rescan dirs
+ e_LogWriteln('refreshing map database');
+ knownMaps.scanFiles();
+ e_LogWriteln('refreshing resource database');
+ knownRes.scanFiles();
+ // save databases
+ saveDatabases(true, true);
+end;
+
+
+//==========================================================================
+//
+// getWord
+//
+// get next word from a string
+// words are delimited with ';'
+// ignores leading and trailing spaces
+// returns empty string if there are no more words
+//
+//==========================================================================
+function getWord (var list: AnsiString): AnsiString;
+var
+ pos: Integer;
+begin
+ result := '';
+ while (length(list) > 0) do
+ begin
+ if (ord(list[1]) <= 32) or (list[1] = ';') or (list[1] = ':') then begin Delete(list, 1, 1); continue; end;
+ pos := 1;
+ while (pos <= length(list)) and (list[pos] <> ';') and (list[pos] <> ':') do Inc(pos);
+ result := Copy(list, 1, pos-1);
+ Delete(list, 1, pos);
+ while (length(result) > 0) and (ord(result[length(result)]) <= 32) do Delete(result, length(result), 1);
+ if (length(result) > 0) then exit;
+ end;
+end;
+
+
+//==========================================================================
+//
+// isIgnoredResWad
+//
+// checks if the given resource wad can be ignored
+//
+// FIXME: preparse name list?
+//
+//==========================================================================
+function isIgnoredResWad (fname: AnsiString): Boolean;
+var
+ list: AnsiString;
+ name: AnsiString;
+begin
+ result := false;
+ if (not g_res_ignore_enabled) then exit;
+ fname := forceFilenameExt(ExtractFileName(fname), '');
+ list := g_res_ignore_names;
+ name := getWord(list);
+ while (length(name) > 0) do
+ begin
+ name := forceFilenameExt(name, '');
+ //writeln('*** name=[', name, ']; fname=[', fname, ']');
+ if (StrEquCI1251(name, fname)) then begin result := true; exit; end;
+ name := getWord(list);
+ end;
+end;
//==========================================================================
var
fn: AnsiString;
begin
+ //e_LogWritefln('LOOKING for replacement wad for [%s]...', [oldname], TMsgType.Notify);
result := oldname;
if not assigned(replacements) then exit;
- if (replacements.get(toLowerCase1251(ExtractFileName(oldname)), fn)) then result := fn;
+ if (replacements.get(toLowerCase1251(ExtractFileName(oldname)), fn)) then
+ begin
+ //e_LogWritefln('found replacement wad for [%s] -> [%s]', [oldname, fn], TMsgType.Notify);
+ result := fn;
+ end;
end;
// returns found wad disk name, or empty string
//
//==========================================================================
+(*
function scanDir (dirName: AnsiString; baseName: AnsiString; const resMd5: TMD5Digest): AnsiString;
var
searchResult: TSearchRec;
end;
SetLength(dirs, 0);
end;
+*)
//==========================================================================
//==========================================================================
function findExistingMapWadWithHash (fname: AnsiString; const resMd5: TMD5Digest): AnsiString;
begin
- result := scanDir(GameDir+'/maps', ExtractFileName(fname), resMd5);
+ //result := scanDir(GameDir+'/maps', ExtractFileName(fname), resMd5);
+ result := knownMaps.findByHash(resMd5);
+ if (length(result) > 0) then
+ begin
+ result := GameDir+'/maps/'+result;
+ if not FileExists(result) then
+ begin
+ if (knownMaps.scanFiles()) then saveDatabases(true, false);
+ result := '';
+ end;
+ end;
end;
//==========================================================================
function findExistingResWadWithHash (fname: AnsiString; const resMd5: TMD5Digest): AnsiString;
begin
- result := scanDir(GameDir+'/wads', ExtractFileName(fname), resMd5);
+ //result := scanDir(GameDir+'/wads', ExtractFileName(fname), resMd5);
+ result := knownRes.findByHash(resMd5);
+ if (length(result) > 0) then
+ begin
+ result := GameDir+'/wads/'+result;
+ if not FileExists(result) then
+ begin
+ if (knownRes.scanFiles()) then saveDatabases(false, true);
+ result := '';
+ end;
+ end;
+end;
+
+
+//==========================================================================
+//
+// generateFileName
+//
+// generate new file name based on the given one and the hash
+// you can pass files with pathes here too
+//
+//==========================================================================
+function generateFileName (fname: AnsiString; const hash: TMD5Digest): AnsiString;
+var
+ mds: AnsiString;
+ path: AnsiString;
+ base: AnsiString;
+ ext: AnsiString;
+begin
+ mds := MD5Print(hash);
+ if (length(mds) > 16) then mds := Copy(mds, 1, 16);
+ mds := '_'+mds;
+ if (length(fname) = 0) then begin result := mds; exit; end;
+ path := ExtractFilePath(fname);
+ base := ExtractFileName(fname);
+ ext := getFilenameExt(base);
+ base := forceFilenameExt(base, '');
+ if (length(path) > 0) then result := IncludeTrailingPathDelimiter(path) else result := '';
+ result := result+base+mds+ext;
end;
fname: AnsiString;
wadname: AnsiString;
md5: TMD5Digest;
+ mapdbUpdated: Boolean = false;
+ resdbUpdated: Boolean = false;
begin
result := '';
clearReplacementWads();
g_Console_Add(Format(_lc[I_NET_MAP_DL], [FileName]));
e_WriteLog('Downloading map `' + FileName + '` from server', TMsgType.Notify);
g_Game_SetLoadingText(FileName + '...', 0, False);
- if (not g_Net_SendMapRequest()) then exit;
FileName := ExtractFileName(FileName);
if (length(FileName) = 0) then FileName := 'fucked_map_wad.wad';
+
+ // this also sends map request
res := g_Net_Wait_MapInfo(tf, resList);
if (res <> 0) then exit;
CreateDir(GameDir+'/maps/downloads');
except
end;
- fname := GameDir+'/maps/downloads/'+FileName;
+ fname := GameDir+'/maps/downloads/'+generateFileName(FileName, mapHash);
+ tf.diskName := fname;
try
strm := openDiskFileRW(fname);
except
result := '';
exit;
end;
- tf.diskName := fname;
try
res := g_Net_ReceiveResourceFile(-1{map}, tf, strm);
except
end;
end;
end;
+ if (knownMaps.addWithHash(fname, mapHash)) then mapdbUpdated := true;
result := fname;
end;
begin
res := g_Net_RequestResFileInfo(f, tf);
if (res <> 0) then begin result := ''; exit; end;
+ if (isIgnoredResWad(tf.diskName)) then
+ begin
+ // ignored file, abort download
+ g_Net_AbortResTransfer(tf);
+ e_LogWritefln('ignoring wad resource `%s` by user request', [tf.diskName]);
+ continue;
+ end;
wadname := findExistingResWadWithHash(tf.diskName, tf.hash);
if (length(wadname) <> 0) then
begin
CreateDir(GameDir+'/wads/downloads');
except
end;
- fname := GameDir+'/wads/downloads/'+tf.diskName;
+ fname := GameDir+'/wads/downloads/'+generateFileName(tf.diskName, tf.hash);
e_LogWritefln('downloading resource `%s` to `%s`...', [tf.diskName, fname]);
try
strm := openDiskFileRW(fname);
end;
end;
addReplacementWad(tf.diskName, fname);
+ if (knownRes.addWithHash(fname, tf.hash)) then resdbUpdated := true;
end;
end;
finally
resList.Free;
g_Res_received_map_start := 0;
end;
+
+ if saveDBsToDiskEnabled and (mapdbUpdated or resdbUpdated) then saveDatabases(mapdbUpdated, resdbUpdated);
end;
initialization
conRegVar('rdl_ignore_names', @g_res_ignore_names, 'list of resource wad names (without extensions) to ignore in dl hash checks', 'dl ignore wads');
conRegVar('rdl_ignore_enabled', @g_res_ignore_enabled, 'enable dl hash check ignore list', 'dl hash check ignore list active');
+ conRegVar('rdl_hashdb_save_enabled', @g_res_save_databases, 'enable saving map/resource hash databases to disk', 'controls storing hash databases to disk');
end.