public byte[] DownloadFileData(CacheIndex index, int fileId, CacheFileInfo fileInfo) { if (!fileInfo.Crc.HasValue) { throw new ArgumentException("File CRC must be set when requesting HTTP files."); } if (!fileInfo.Version.HasValue) { throw new ArgumentException("File version must be set when requesting HTTP files."); } if (!fileInfo.CompressedSize.HasValue) { throw new ArgumentException("File compressed size must be set when requesting HTTP files."); } var webRequest = WebRequest.CreateHttp( $"https://{ClientDetails.GetContentServerHostname()}/ms?m=0&a={(int)index}&k={ClientDetails.GetBuildNumber().Item1}&g={fileId}&c={fileInfo.Crc}&v={fileInfo.Version}" ); try { using var response = (HttpWebResponse)webRequest.GetResponse(); if (response.StatusCode != HttpStatusCode.OK) { throw new DownloaderException($"HTTP interface responded with status code: {response.StatusCode}."); } if (response.ContentLength != fileInfo.CompressedSize) { throw new DownloaderException($"Downloaded file size {response.ContentLength} does not match expected {fileInfo.CompressedSize}."); } var dataStream = new MemoryStream(); var dataWriter = new BinaryWriter(dataStream); var responseReader = new BinaryReader(response.GetResponseStream()); dataWriter.Write(responseReader.ReadBytesExactly((int)response.ContentLength)); return(dataStream.ToArray()); } catch (WebException exception) { throw new DownloaderException( $"Could not download {(int)index}/{fileId} via HTTP due to a request error.", exception ); } }
private void Connect() { if (this._connected) { throw new DownloaderException("Tried to connect while already connected."); } // Retry connecting with an increasing major version until the server no longer reports we're outdated var currentBuildNumber = ClientDetails.HasBuildNumber() ? ClientDetails.GetBuildNumber() : new Tuple <int, int>(TcpFileDownloader.StartBuildNumber, 1); var connected = false; while (!connected) { this._contentClient = new TcpClient( ClientDetails.GetContentServerHostname(), ClientDetails.GetContentServerTcpPort() ); var handshakeWriter = new BinaryWriter(this._contentClient.GetStream()); var handshakeReader = new BinaryReader(this._contentClient.GetStream()); var handshakeKey = ClientDetails.GetContentServerTcpHandshakeKey(); Log.Debug($"Attempting to connect to TCP content server with version {currentBuildNumber.Item1}.{currentBuildNumber.Item2}..."); handshakeWriter.Write((byte)15); // Handshake type handshakeWriter.Write((byte)(9 + handshakeKey.Length + 1)); // Handshake length (42) handshakeWriter.WriteUInt32BigEndian((uint)currentBuildNumber.Item1); handshakeWriter.WriteUInt32BigEndian((uint)currentBuildNumber.Item2); handshakeWriter.WriteNullTerminatedString(handshakeKey); handshakeWriter.Write((byte)Language.English); handshakeWriter.Flush(); var response = (HandshakeResponse)handshakeReader.ReadByte(); switch (response) { case HandshakeResponse.Success: connected = true; // Make build number globally available. ClientDetails.SetBuildNumber(currentBuildNumber); break; case HandshakeResponse.Outdated: this._contentClient.Dispose(); this._contentClient = null; currentBuildNumber = new Tuple <int, int>(currentBuildNumber.Item1 + 1, 1); break; default: this._contentClient.Dispose(); this._contentClient = null; throw new DownloaderException($"Content server responded to handshake with {response}."); } } Log.Debug($"Successfully connected to content server with version {currentBuildNumber.Item1}.{currentBuildNumber.Item2}."); var contentReader = new BinaryReader(this._contentClient.GetStream()); // Loading requirements. Whatever that might mean: // 00 00 0c ea // 00 01 10 a3 // 00 00 a2 b3 // 00 00 8c 1a // 00 05 79 3c // 00 42 d4 96 // 00 00 ad 57 // 00 00 47 3f // 00 01 0b 50 // 00 06 b6 55 00 13 dd 79 00 0a 1f cf 00 0a b2 93 00 13 d9 2a 00 15 dd e7 00 00 a4 3c 00 14 10 50 00 00 5a e3 00 00 97 75 00 00 04 dc 00 01 70 0d 00 00 09 20 00 00 00 77 00 13 73 f2 00 51 66 05 00 00 ab ee 00 00 61 ba 00 02 2d 43 var loadingRequirements = contentReader.ReadBytesExactly(28 * 4); // Send the initial connection status and login packets to the server. I don't know what the individual // writes mean but they do the trick. Log.Debug("Sending initial connection status and login packets..."); var contentWriter = new BinaryWriter(this._contentClient.GetStream()); const int unknownTribyte1 = 0x000005; const ushort unknownShort1 = 0x0000; const ushort unknownShort2 = 0x0000; // Observed to be different and is used in every file request. contentWriter.Write((byte)0x06); contentWriter.WriteUInt24BigEndian(unknownTribyte1); contentWriter.WriteUInt16BigEndian(unknownShort1); contentWriter.WriteUInt16BigEndian((ushort)currentBuildNumber.Item1); contentWriter.WriteUInt16BigEndian(unknownShort2); contentWriter.Flush(); contentWriter.Write((byte)0x03); contentWriter.WriteUInt24BigEndian(unknownTribyte1); contentWriter.WriteUInt16BigEndian(unknownShort1); contentWriter.WriteUInt16BigEndian((ushort)currentBuildNumber.Item1); contentWriter.WriteUInt16BigEndian(unknownShort2); contentWriter.Flush(); this._connected = true; }