X-Git-Url: http://deadsoftware.ru/gitweb?p=d2df-sdl.git;a=blobdiff_plain;f=src%2Fgame%2Fg_res_downloader.pas;h=a092437993d1601f774deb9235ff1ac037916e7d;hp=04048b693c983c558af1c61168f3948eee2c5823;hb=8f5d5b700022732e21511affb218f8179344c90e;hpb=565edf25d031dba9a1d6436e6b758a22c8329b45 diff --git a/src/game/g_res_downloader.pas b/src/game/g_res_downloader.pas index 04048b6..a092437 100644 --- a/src/game/g_res_downloader.pas +++ b/src/game/g_res_downloader.pas @@ -19,44 +19,207 @@ interface uses sysutils, Classes, md5, g_net, g_netmsg, g_console, g_main, e_log; -function g_Res_SearchSameWAD(const path, filename: string; const resMd5: TMD5Digest): string; -function g_Res_DownloadWAD(const FileName: string): string; +function g_Res_SearchSameWAD(const path, filename: AnsiString; const resMd5: TMD5Digest): AnsiString; +function g_Res_SearchResWad (asMap: Boolean; const resMd5: TMD5Digest): AnsiString; + +// download map wad from server (if necessary) +// download all required map resource wads too +// returns name of the map wad (relative to mapdir), or empty string on error +function g_Res_DownloadMapWAD (FileName: AnsiString; const mapHash: TMD5Digest): AnsiString; + +// call this before downloading a new map from a server +procedure g_Res_ClearReplacementWads (); +// returns original name, or replacement name +function g_Res_FindReplacementWad (oldname: AnsiString): AnsiString; +procedure g_Res_PutReplacementWad (oldname: AnsiString; newDiskName: AnsiString); + implementation -uses g_language, sfs, utils, wadreader, g_game; +uses g_language, sfs, utils, wadreader, g_game, hashtable; const DOWNLOAD_DIR = 'downloads'; -procedure FindFiles(const dirName, filename: string; var files: TStringList); +type + TFileInfo = record + diskName: AnsiString; // lowercased + baseName: AnsiString; // lowercased + md5: TMD5Digest; + md5valid: Boolean; + nextBaseNameIndex: Integer; + end; + var - searchResult: TSearchRec; + knownFiles: array of TFileInfo; + knownHash: THashStrInt = nil; // key: base name; value: index + scannedDirs: THashStrInt = nil; // key: lowercased dir name + replacements: THashStrStr = nil; + + +function findKnownFile (diskName: AnsiString): Integer; +var + idx: Integer; + baseName: AnsiString; begin - if FindFirst(dirName+'/*', faAnyFile, searchResult) = 0 then + result := -1; + if not assigned(knownHash) then exit; + if (length(diskName) = 0) then exit; + baseName := toLowerCase1251(ExtractFileName(diskName)); + if (not knownHash.get(baseName, idx)) then exit; + if (idx < 0) or (idx >= length(knownFiles)) then raise Exception.Create('wutafuck?'); + while (idx >= 0) do + begin + if (strEquCI1251(knownFiles[idx].diskName, diskName)) then begin result := idx; exit; end; // i found her! + idx := knownFiles[idx].nextBaseNameIndex; + end; +end; + + +function addKnownFile (diskName: AnsiString): Integer; +var + idx: Integer; + lastIdx: Integer = -1; + baseName: AnsiString; + fi: ^TFileInfo; +begin + result := -1; + if not assigned(knownHash) then knownHash := THashStrInt.Create(); + if (length(diskName) = 0) then exit; + baseName := toLowerCase1251(ExtractFileName(diskName)); + if (length(baseName) = 0) then exit; + // check if we already have this file + if (knownHash.get(baseName, idx)) then + begin + if (idx < 0) or (idx >= length(knownFiles)) then raise Exception.Create('wutafuck?'); + while (idx >= 0) do + begin + if (strEquCI1251(knownFiles[idx].diskName, diskName)) then + begin + // already here + result := idx; + exit; + end; + lastIdx := idx; + idx := knownFiles[idx].nextBaseNameIndex; + end; + end; + // this file is not there, append it + idx := length(knownFiles); + result := idx; + SetLength(knownFiles, idx+1); // sorry + fi := @knownFiles[idx]; + fi.diskName := diskName; + fi.baseName := baseName; + fi.md5valid := false; + fi.nextBaseNameIndex := -1; + if (lastIdx < 0) then + begin + // totally new one + knownHash.put(baseName, idx); + end + else begin - try - repeat - if (searchResult.Attr and faDirectory) = 0 then + knownFiles[lastIdx].nextBaseNameIndex := idx; + end; +end; + + +function getKnownFileWithMD5 (diskDir: AnsiString; baseName: AnsiString; const md5: TMD5Digest): AnsiString; +var + idx: Integer; +begin + result := ''; + if not assigned(knownHash) then exit; + if (not knownHash.get(toLowerCase1251(baseName), idx)) then exit; + if (idx < 0) or (idx >= length(knownFiles)) then raise Exception.Create('wutafuck?'); + while (idx >= 0) do + begin + if (strEquCI1251(knownFiles[idx].diskName, IncludeTrailingPathDelimiter(diskDir)+baseName)) then + begin + if (not knownFiles[idx].md5valid) then + begin + knownFiles[idx].md5 := MD5File(knownFiles[idx].diskName); + knownFiles[idx].md5valid := true; + end; + if (MD5Match(knownFiles[idx].md5, md5)) then + begin + result := knownFiles[idx].diskName; + exit; + end; + end; + idx := knownFiles[idx].nextBaseNameIndex; + end; +end; + + +// call this before downloading a new map from a server +procedure g_Res_ClearReplacementWads (); +begin + if assigned(replacements) then replacements.clear(); + e_LogWriteln('cleared replacement wads'); +end; + + +// returns original name, or replacement name +function g_Res_FindReplacementWad (oldname: AnsiString): AnsiString; +var + fn: AnsiString; +begin + result := oldname; + if not assigned(replacements) then exit; + if (replacements.get(toLowerCase1251(ExtractFileName(oldname)), fn)) then result := fn; +end; + + +procedure g_Res_PutReplacementWad (oldname: AnsiString; newDiskName: AnsiString); +begin + e_LogWritefln('adding replacement wad: oldname=%s; newname=%s', [oldname, newDiskName]); + replacements.put(toLowerCase1251(oldname), newDiskName); +end; + + +procedure scanDir (const dirName: AnsiString; calcMD5: Boolean); +var + searchResult: TSearchRec; + dfn: AnsiString; + idx: Integer; +begin + if not assigned(scannedDirs) then scannedDirs := THashStrInt.Create(); + dfn := toLowerCase1251(IncludeTrailingPathDelimiter(dirName)); + if scannedDirs.has(dfn) then exit; + scannedDirs.put(dfn, 42); + + if (FindFirst(dirName+'/*', faAnyFile, searchResult) <> 0) then exit; + try + repeat + if (searchResult.Attr and faDirectory) = 0 then + begin + dfn := dirName+'/'+searchResult.Name; + idx := addKnownFile(dfn); + if (calcMD5) and (idx >= 0) then begin - if StrEquCI1251(searchResult.Name, filename) then + if (not knownFiles[idx].md5valid) then begin - files.Add(dirName+'/'+filename); - Exit; + knownFiles[idx].md5 := MD5File(knownFiles[idx].diskName); + knownFiles[idx].md5valid := true; end; - end - else if (searchResult.Name <> '.') and (searchResult.Name <> '..') then - FindFiles(IncludeTrailingPathDelimiter(dirName)+searchResult.Name, filename, files); - until FindNext(searchResult) <> 0; - finally - FindClose(searchResult); - end; + end; + end + else if (searchResult.Name <> '.') and (searchResult.Name <> '..') then + begin + scanDir(IncludeTrailingPathDelimiter(dirName)+searchResult.Name, calcMD5); + end; + until (FindNext(searchResult) <> 0); + finally + FindClose(searchResult); end; end; -function CompareFileHash(const filename: string; const resMd5: TMD5Digest): Boolean; + +function CompareFileHash(const filename: AnsiString; const resMd5: TMD5Digest): Boolean; var gResHash: TMD5Digest; - fname: string; + fname: AnsiString; begin fname := findDiskWad(filename); if length(fname) = 0 then begin result := false; exit; end; @@ -64,115 +227,197 @@ begin Result := MD5Match(gResHash, resMd5); end; -function CheckFileHash(const path, filename: string; const resMd5: TMD5Digest): Boolean; +function CheckFileHash(const path, filename: AnsiString; const resMd5: TMD5Digest): Boolean; var - fname: string; + fname: AnsiString; begin fname := findDiskWad(path+filename); if length(fname) = 0 then begin result := false; exit; end; Result := FileExists(fname) and CompareFileHash(fname, resMd5); end; -function g_Res_SearchSameWAD(const path, filename: string; const resMd5: TMD5Digest): string; + +function g_Res_SearchResWad (asMap: Boolean; const resMd5: TMD5Digest): AnsiString; var - res: string; - files: TStringList; - i: Integer; + f: Integer; begin - Result := ''; - - if CheckFileHash(path, filename, resMd5) then + result := ''; + //if not assigned(scannedDirs) then scannedDirs := THashStrInt.Create(); + if (asMap) then begin - Result := path + filename; - Exit; + scanDir(GameDir+'/maps/downloads', true); + end + else + begin + scanDir(GameDir+'/wads/downloads', true); end; - - files := TStringList.Create; - - FindFiles(path, filename, files); - for i := 0 to files.Count - 1 do + for f := Low(knownFiles) to High(knownFiles) do begin - res := files.Strings[i]; - if CompareFileHash(res, resMd5) then + if (not knownFiles[f].md5valid) then continue; + if (MD5Match(knownFiles[f].md5, resMd5)) then begin - Result := res; - Break; + result := knownFiles[f].diskName; + exit; end; end; + //resStream := createDiskFile(GameDir+'/wads/'+mapData.ExternalResources[i].Name); +end; + - files.Free; +function g_Res_SearchSameWAD (const path, filename: AnsiString; const resMd5: TMD5Digest): AnsiString; +begin + scanDir(path, false); + result := getKnownFileWithMD5(path, filename, resMd5); end; -function SaveWAD(const path, filename: string; const data: array of Byte): string; + +function g_Res_DownloadMapWAD (FileName: AnsiString; const mapHash: TMD5Digest): AnsiString; var - resFile: TStream; - dpt: string; + tf: TNetFileTransfer; + resList: TStringList; + f, res: Integer; + strm: TStream; + mmd5: TMD5Digest; + fname: AnsiString; + idx: Integer; + wadname: AnsiString; begin + //SetLength(mapData.ExternalResources, 0); + //result := g_Res_SearchResWad(true{asMap}, mapHash); + result := ''; + g_Res_ClearReplacementWads(); + try - result := path+DOWNLOAD_DIR+'/'+filename; - dpt := path+DOWNLOAD_DIR; - if not findFileCI(dpt, true) then CreateDir(dpt); - resFile := createDiskFile(result); - resFile.WriteBuffer(data[0], Length(data)); - resFile.Free + CreateDir(GameDir+'/maps/downloads'); except - Result := ''; end; -end; -function g_Res_DownloadWAD(const FileName: string): string; -var - msgStream: TMemoryStream; - resStream: TStream; - mapData: TMapDataMsg; - i: Integer; - resData: TResDataMsg; -begin - SetLength(mapData.ExternalResources, 0); - 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); - MC_SEND_MapRequest(); - - msgStream := g_Net_Wait_Event(NET_MSG_MAP_RESPONSE); - if msgStream <> nil then - begin - mapData := MapDataFromMsgStream(msgStream); - msgStream.Free; - end else - mapData.FileSize := 0; + try + CreateDir(GameDir+'/wads/downloads'); + except + end; - for i := 0 to High(mapData.ExternalResources) do - begin - if not CheckFileHash(GameDir + '/wads/', - mapData.ExternalResources[i].Name, - mapData.ExternalResources[i].md5) then - begin - g_Console_Add(Format(_lc[I_NET_WAD_DL], - [mapData.ExternalResources[i].Name])); - e_WriteLog('Downloading Wad `' + mapData.ExternalResources[i].Name + - '` from server', TMsgType.Notify); - g_Game_SetLoadingText(mapData.ExternalResources[i].Name + '...', 0, False); - MC_SEND_ResRequest(mapData.ExternalResources[i].Name); + resList := TStringList.Create(); - msgStream := g_Net_Wait_Event(NET_MSG_RES_RESPONSE); - if msgStream = nil then - continue; + try + 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); + //MC_SEND_MapRequest(); + if (not g_Net_SendMapRequest()) then exit; - resData := ResDataFromMsgStream(msgStream); + FileName := ExtractFileName(FileName); + if (length(FileName) = 0) then FileName := 'fucked_map_wad.wad'; + res := g_Net_Wait_MapInfo(tf, resList); + if (res <> 0) then exit; - resStream := createDiskFile(GameDir+'/wads/'+mapData.ExternalResources[i].Name); - resStream.WriteBuffer(resData.FileData[0], resData.FileSize); + // find or download a map + result := g_Res_SearchResWad(true{asMap}, mapHash); + if (length(result) = 0) then + begin + // download map + res := g_Net_RequestResFileInfo(-1{map}, tf); + if (res <> 0) then + begin + e_LogWriteln('error requesting map wad'); + result := ''; + exit; + end; + fname := GameDir+'/maps/downloads/'+FileName; + try + strm := createDiskFile(fname); + except + e_WriteLog('cannot create map file `'+FileName+'`', TMsgType.Fatal); + result := ''; + exit; + end; + try + res := g_Net_ReceiveResourceFile(-1{map}, tf, strm); + except + e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal); + strm.Free; + result := ''; + exit; + end; + strm.Free; + if (res <> 0) then + begin + e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal); + result := ''; + exit; + end; + mmd5 := MD5File(fname); + if (not MD5Match(mmd5, mapHash)) then + begin + e_WriteLog('error downloading map file `'+FileName+'` (bad hash)', TMsgType.Fatal); + result := ''; + exit; + end; + idx := addKnownFile(fname); + if (idx < 0) then + begin + e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal); + result := ''; + exit; + end; + knownFiles[idx].md5 := mmd5; + knownFiles[idx].md5valid := true; + result := fname; + end; - resData.FileData := nil; - resStream.Free; - msgStream.Free; + // download resources + for f := 0 to resList.Count-1 do + begin + res := g_Net_RequestResFileInfo(f, tf); + if (res <> 0) then begin result := ''; exit; end; + wadname := g_Res_SearchResWad(false{asMap}, tf.hash); + if (length(wadname) <> 0) then + begin + // already here + g_Net_AbortResTransfer(tf); + g_Res_PutReplacementWad(tf.diskName, wadname); + end + else + begin + fname := GameDir+'/wads/downloads/'+tf.diskName; + try + strm := createDiskFile(fname); + except + e_WriteLog('cannot create resource file `'+fname+'`', TMsgType.Fatal); + result := ''; + exit; + end; + try + res := g_Net_ReceiveResourceFile(f, tf, strm); + except + e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal); + strm.Free; + result := ''; + exit; + end; + strm.Free; + if (res <> 0) then + begin + e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal); + result := ''; + exit; + end; + idx := addKnownFile(fname); + if (idx < 0) then + begin + e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal); + result := ''; + exit; + end; + knownFiles[idx].md5 := tf.hash; + knownFiles[idx].md5valid := true; + g_Res_PutReplacementWad(tf.diskName, fname); + end; end; + finally + resList.Free; end; - - Result := SaveWAD(MapsDir, ExtractFileName(FileName), mapData.FileData); - if mapData.FileSize = 0 then - DeleteFile(Result); end; + end.