예제 #1
0
        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;
        }