Example #1
0
        protected override byte[] ProcessRequest(byte[] data, TcpClient c)
        {
            int length = BitConverter.ToInt32(data, 0);

            AssertHelper.Equals(length, data.Length);

            RequestTypes4 requestType = (RequestTypes4)data[4];
            StringBuilder logEntry    = new StringBuilder();

            logEntry.AppendFormat("Handling Generation IV {0} request.\nHost: {1}", requestType, c.Client.RemoteEndPoint);
            logEntry.AppendLine();
            EventLogEntryType type = EventLogEntryType.Information;

            CryptMessage(data);

            MemoryStream response = new MemoryStream();

            response.Write(new byte[] { 0x00, 0x00, 0x00, 0x00 }, 0, 4); // placeholder for length
            response.WriteByte((byte)requestType);
            response.WriteByte(data[5]);

            try
            {
                int  pid      = BitConverter.ToInt32(data, 8);
                byte version  = data[0x0c];
                byte language = data[0x0d];
                logEntry.AppendFormat("pid: {0}", pid);
                logEntry.AppendLine();

                switch (requestType)
                {
                    #region Box upload
                case RequestTypes4.BoxUpload:
                {
                    if (data.Length != 0x360)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    BoxLabels4 label   = (BoxLabels4)BitConverter.ToInt32(data, 0x140);
                    byte[]     boxData = new byte[0x21c];
                    Array.Copy(data, 0x144, boxData, 0, 0x21c);
                    BoxRecord4 record = new BoxRecord4(pid, label, 0, boxData);
                    ulong      serial = Database.Instance.BoxUpload4(record);

                    if (serial == 0)
                    {
                        logEntry.AppendLine("Uploaded box already in server.");
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    logEntry.AppendFormat("Box {0} uploaded successfully.", serial);
                    logEntry.AppendLine();
                    response.Write(new byte[] { 0x00, 0x00 }, 0, 2);     // result code (0 for OK)
                    response.Write(BitConverter.GetBytes(serial), 0, 8);
                } break;

                case RequestTypes4.BoxSearch:
                {
                    if (data.Length != 0x14c)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    // todo: validate or log some of this?
                    BoxLabels4 label = (BoxLabels4)BitConverter.ToInt32(data, 0x144);

                    logEntry.AppendFormat("Searching for {0} boxes.", label);
                    logEntry.AppendLine();

                    BoxRecord4[] results = Database.Instance.BoxSearch4(label, 20);
                    response.Write(new byte[] { 0x00, 0x00 }, 0, 2);     // result code (0 for OK)
                    response.Write(BitConverter.GetBytes(results.Length), 0, 4);

                    foreach (BoxRecord4 result in results)
                    {
                        response.Write(BitConverter.GetBytes(result.PID), 0, 4);
                        response.Write(BitConverter.GetBytes((int)result.Label), 0, 4);
                        response.Write(BitConverter.GetBytes(result.SerialNumber), 0, 8);
                        // xxx: this may throw if there's database corruption
                        response.Write(result.Data, 0, 0x21c);
                    }
                    logEntry.AppendFormat("Retrieved {0} boxes.", results.Length);
                    logEntry.AppendLine();
                } break;
                    #endregion

                    #region Dressup
                case RequestTypes4.DressupUpload:
                {
                    if (data.Length != 0x220)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    byte[] dressupData = new byte[0xe0];
                    Array.Copy(data, 0x140, dressupData, 0, 0xe0);
                    DressupRecord4 record = new DressupRecord4(pid, 0, dressupData);
                    ulong          serial = Database.Instance.DressupUpload4(record);

                    if (serial == 0)
                    {
                        logEntry.AppendLine("Uploaded dressup already in server.");
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    logEntry.AppendFormat("Dressup {0} uploaded successfully.", serial);
                    response.Write(new byte[] { 0x00, 0x00 }, 0, 2);     // result code (0 for OK)
                    response.Write(BitConverter.GetBytes(serial), 0, 8);
                } break;

                case RequestTypes4.DressupSearch:
                {
                    if (data.Length != 0x14c)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    // todo: validate or log some of this?
                    ushort species = BitConverter.ToUInt16(data, 0x144);

                    logEntry.AppendFormat("Searching for dressups of species {0}.", species);
                    logEntry.AppendLine();

                    DressupRecord4[] results = Database.Instance.DressupSearch4(species, 10);
                    response.Write(new byte[] { 0x00, 0x00 }, 0, 2);     // result code (0 for OK)
                    response.Write(BitConverter.GetBytes(results.Length), 0, 4);

                    foreach (DressupRecord4 result in results)
                    {
                        response.Write(BitConverter.GetBytes(result.PID), 0, 4);
                        response.Write(BitConverter.GetBytes(result.SerialNumber), 0, 8);
                        response.Write(result.Data, 0, 0xe0);
                    }
                    logEntry.AppendFormat("Retrieved {0} dressup results.", results.Length);
                    logEntry.AppendLine();
                } break;
                    #endregion

                    #region Battle videos
                case RequestTypes4.BattleVideoUpload:
                {
                    if (data.Length != 0x1e8c)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    byte[] battlevidData = new byte[0x1d4c];
                    Array.Copy(data, 0x140, battlevidData, 0, 0x1d4c);
                    BattleVideoRecord4 record = new BattleVideoRecord4(pid, 0, battlevidData);
                    ulong serial = Database.Instance.BattleVideoUpload4(record);

                    if (serial == 0)
                    {
                        logEntry.AppendFormat("Uploaded battle video already in server.");
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    logEntry.AppendFormat("Battle video {0} uploaded successfully.", BattleVideoHeader4.FormatSerial(serial));
                    logEntry.AppendLine();
                    response.Write(new byte[] { 0x00, 0x00 }, 0, 2);     // result code (0 for OK)
                    response.Write(BitConverter.GetBytes(serial), 0, 8);
                } break;

                case RequestTypes4.BattleVideoSearch:
                {
                    if (data.Length != 0x15c)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    // todo: validate or log some of this?
                    BattleVideoRankings4 ranking = (BattleVideoRankings4)BitConverter.ToUInt32(data, 0x140);
                    ushort species             = BitConverter.ToUInt16(data, 0x144);
                    BattleVideoMetagames4 meta = (BattleVideoMetagames4)data[0x146];
                    byte country = data[0x147];
                    byte region  = data[0x148];

                    logEntry.Append("Searching for ");
                    if (ranking != BattleVideoRankings4.None)
                    {
                        logEntry.AppendFormat("{0}", ranking);
                    }
                    else
                    {
                        if (species != 0xffff)
                        {
                            logEntry.AppendFormat("species {0}, ", species);
                        }
                        logEntry.AppendFormat("{0}", meta);
                        if (country != 0xff)
                        {
                            logEntry.AppendFormat(", country {0}", country);
                        }
                        if (region != 0xff)
                        {
                            logEntry.AppendFormat(", region {0}", region);
                        }
                    }
                    logEntry.AppendLine(".");

                    BattleVideoHeader4[] results = Database.Instance.BattleVideoSearch4(species, ranking, meta, country, region, 30);
                    response.Write(new byte[] { 0x00, 0x00 }, 0, 2);     // result code (0 for OK)
                    response.Write(BitConverter.GetBytes(results.Length), 0, 4);

                    foreach (BattleVideoHeader4 result in results)
                    {
                        response.Write(BitConverter.GetBytes(result.PID), 0, 4);
                        response.Write(BitConverter.GetBytes(result.SerialNumber), 0, 8);
                        response.Write(result.Data, 0, 0xe4);
                    }
                    logEntry.AppendFormat("Retrieved {0} battle video results.", results.Length);
                    logEntry.AppendLine();
                } break;

                case RequestTypes4.BattleVideoWatch:
                {
                    if (data.Length != 0x14c)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    ulong serial = BitConverter.ToUInt64(data, 0x140);
                    BattleVideoRecord4 record = Database.Instance.BattleVideoGet4(serial, true);
                    if (record == null)
                    {
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        logEntry.AppendFormat("Requested battle video {0} was missing.", BattleVideoHeader4.FormatSerial(serial));
                        logEntry.AppendLine();
                        type = EventLogEntryType.FailureAudit;
                        break;
                    }

                    response.Write(new byte[] { 0x00, 0x00 }, 0, 2);     // result code (0 for OK)
                    response.Write(BitConverter.GetBytes(record.PID), 0, 4);
                    response.Write(BitConverter.GetBytes(record.SerialNumber), 0, 8);
                    response.Write(record.Header.Data, 0, 0xe4);
                    response.Write(record.Data, 0, 0x1c68);
                    logEntry.AppendFormat("Retrieved battle video {0}.", BattleVideoHeader4.FormatSerial(serial));
                    logEntry.AppendLine();
                } break;

                case RequestTypes4.BattleVideoSaved:
                {
                    if (data.Length != 0x148)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    ulong serial = BitConverter.ToUInt64(data, 0x140);

                    if (Database.Instance.BattleVideoFlagSaved4(serial))
                    {
                        response.Write(new byte[] { 0x00, 0x00 }, 0, 2);     // result code (0 for OK)
                        logEntry.AppendFormat("Battle video {0} flagged saved.", BattleVideoHeader4.FormatSerial(serial));
                        logEntry.AppendLine();
                    }
                    else
                    {
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        logEntry.AppendFormat("Requested battle video {0} was missing.", BattleVideoHeader4.FormatSerial(serial));
                        logEntry.AppendLine();
                    }
                } break;
                    #endregion

                    #region Trainer Rankings
                case RequestTypes4.TrainerRankingsHead:
                {
                    if (data.Length != 0x140)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    // AdmiralCurtiss's request: completely blank! (just the 0x140 byte header)

                    // AdmiralCurtiss's response:
                    // 0000: 0c000000f0550000 0128431c

                    // Response format seems to be a list of the three record categories currently being collected.
                    // 0x01: Hall of fame entries
                    // 0x28: Completed GTS trades
                    // 0x43: Facilities challenged at battle frontier
                    // The purpose of 0x1c is unclear to me.

                    if (Database.Instance.TrainerRankingsPerformRollover())
                    {
                        logEntry.AppendLine("Leaderboard rollover.");
                    }
                    var recordTypes = Database.Instance.TrainerRankingsGetActiveRecordTypes();

                    // todo: If we can be sure the player has already sent us their up to date records,
                    // then we can lie here about the active records and collect more complete trainer
                    // stats for better trainer profiles

                    response.Write(new byte[] { 0x00, 0x00 }, 0, 2);

                    // The game will give error 10609 if the response is longer than 4 bytes.
                    // The game will bluescreen if the response is shorter than 3 bytes.
                    // The 4th byte is optional and I don't know what, if anything, it does.
                    int i;
                    for (i = 0; i < 3 && i < recordTypes.Count; i++)
                    {
                        response.WriteByte((byte)recordTypes[i]);
                    }
                    for (; i < 3; i++)
                    {
                        // Must be valid RecordTypes. If we pad with 0x00, it causes a bluescreen.
                        response.WriteByte((byte)0x01);
                    }
                    response.WriteByte(0x1c);     // todo: Find out the meaning of this 0x1c.
                } break;

                case RequestTypes4.TrainerRankingsSearch:
                {
#if !DEBUG
                    goto default;
#endif
                    if (data.Length != 0x164)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    // The request is important. It contains the player's records for the above three chosen categories.
                    // It also (presumably) conveys which teams the player is a part of.
                    // (Trainer Class, Birth Month, Favourite Pokémon)

                    // AdmiralCurtiss's request:
                    // 0140: 0c02050900000000 1800303b01000000
                    // 0150: 0100000028000000 0000000043000000
                    // 0160: 00000000

                    // Hikari's request:
                    // Platinum EN July AceTrainerF Gallade
                    // 0140: 0c02070bdb010000 e200350f01000000
                    // 0150: 0300000028000000 0800000043000000
                    // 0160: 28000000

                    // 140: Version
                    // 141: Language
                    // 142: Birth Month
                    // 143: Trainer Class
                    // 144-145: Favourite Pokémon
                    // 146-14b: Unknown
                    // 14c-163: Three record records

                    // Record record contains 4 bytes of category and 4 bytes of my score in the category.

                    // Note: Although the game gives instructions that only
                    // your first submission will apply, I think it's more
                    // fun if we allow you to update your results by
                    // submitting again. In this way, you can race against
                    // competing teams to get the highest score.
                    var submission = new TrainerRankingsSubmission(pid, data, 0x140);
                    Database.Instance.TrainerRankingsSubmit(submission);

                    response.Write(new byte[] { 0x00, 0x00 }, 0, 2);

                    // The response is biig and contains all the records for both this week and last week.
                    // This week lacks numbers because they're still growing and to make it a surprise.
                    // Including more than 3 RecordTypes in the response will give error 10609.

                    TrainerRankingsReport thisWeek = Database.Instance.TrainerRankingsGetPendingReport();
                    thisWeek.PadResults();
                    TrainerRankingsReport lastWeek = Database.Instance.TrainerRankingsGetReport(DateTime.MinValue, thisWeek.StartDate.AddDays(-1), 1).FirstOrDefault();
                    if (lastWeek == null)
                    {
                        lastWeek = GenerateFakeReport(thisWeek.StartDate.AddDays(-7),
                                                      new TrainerRankingsRecordTypes[]
                            {
                                0, 0, 0
                            });
                    }
                    else
                    {
                        lastWeek.PadResults();
                    }

                    // Last week:
                    foreach (var lbg in lastWeek.Leaderboards)
                    {
                        response.Write(BitConverter.GetBytes((int)lbg.RecordType), 0, 4);

                        foreach (var lbe in lbg.LeaderboardTrainerClass.Entries)
                        {
                            response.WriteByte((byte)lbe.Team);
                        }
                        foreach (var lbe in lbg.LeaderboardTrainerClass.Entries)
                        {
                            response.Write(BitConverter.GetBytes(lbe.Score), 0, 8);
                        }

                        foreach (var lbe in lbg.LeaderboardBirthMonth.Entries)
                        {
                            response.WriteByte((byte)lbe.Team);
                        }
                        foreach (var lbe in lbg.LeaderboardBirthMonth.Entries)
                        {
                            response.Write(BitConverter.GetBytes(lbe.Score), 0, 8);
                        }

                        foreach (var lbe in lbg.LeaderboardFavouritePokemon.Entries)
                        {
                            response.Write(BitConverter.GetBytes((short)lbe.Team), 0, 2);
                        }
                        foreach (var lbe in lbg.LeaderboardFavouritePokemon.Entries)
                        {
                            response.Write(BitConverter.GetBytes(lbe.Score), 0, 8);
                        }
                    }

                    // This week:
                    foreach (var lbg in thisWeek.Leaderboards)
                    {
                        response.Write(BitConverter.GetBytes((int)lbg.RecordType), 0, 4);

                        foreach (var lbe in lbg.LeaderboardTrainerClass.Entries)
                        {
                            response.WriteByte((byte)lbe.Team);
                        }

                        foreach (var lbe in lbg.LeaderboardBirthMonth.Entries)
                        {
                            response.WriteByte((byte)lbe.Team);
                        }

                        foreach (var lbe in lbg.LeaderboardFavouritePokemon.Entries)
                        {
                            response.Write(BitConverter.GetBytes((short)lbe.Team), 0, 2);
                        }
                    }
                } break;
                    #endregion

                default:
                    logEntry.AppendLine("Unrecognized request type.");
                    response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                    break;
                }
            }
            catch (Exception ex)
            {
                logEntry.AppendFormat("Unhandled exception while handling request.\nException: {0}", ex.ToString());
                logEntry.AppendLine();
                type = EventLogEntryType.Error;
                response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
            }

            response.Flush();
            byte[] responseData = response.ToArray();
            WriteLength(responseData);
            CryptMessage(responseData);

            LogHelper.Write(logEntry.ToString(), type);
            return(responseData);
        }
Example #2
0
        protected override byte[] ProcessRequest(byte[] data, TcpClient c)
        {
            int length = BitConverter.ToInt32(data, 0);

            AssertHelper.Equals(length, data.Length);

            RequestTypes4 requestType = (RequestTypes4)data[4];
            StringBuilder logEntry    = new StringBuilder();

            logEntry.AppendFormat("Handling Generation IV {0} request.\nHost: {1}", requestType, c.Client.RemoteEndPoint);
            logEntry.AppendLine();
            EventLogEntryType type = EventLogEntryType.Information;

            CryptMessage(data);

            MemoryStream response = new MemoryStream();

            response.Write(new byte[] { 0x00, 0x00, 0x00, 0x00 }, 0, 4); // placeholder for length
            response.WriteByte((byte)requestType);
            response.WriteByte(data[5]);

            try
            {
                int  pid      = BitConverter.ToInt32(data, 8);
                byte version  = data[0x0c];
                byte language = data[0x0d];
                logEntry.AppendFormat("pid: {0}", pid);
                logEntry.AppendLine();

                switch (requestType)
                {
                    #region Box upload
                case RequestTypes4.BoxUpload:
                {
                    if (data.Length != 0x360)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    BoxLabels4 label   = (BoxLabels4)BitConverter.ToInt32(data, 0x140);
                    byte[]     boxData = new byte[0x21c];
                    Array.Copy(data, 0x144, boxData, 0, 0x21c);
                    BoxRecord4 record = new BoxRecord4(pid, label, 0, boxData);
                    ulong      serial = Database.Instance.BoxUpload4(record);

                    if (serial == 0)
                    {
                        logEntry.AppendLine("Uploaded box already in server.");
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    logEntry.AppendFormat("Box {0} uploaded successfully.", serial);
                    logEntry.AppendLine();
                    response.Write(new byte[] { 0x00, 0x00 }, 0, 2);     // result code (0 for OK)
                    response.Write(BitConverter.GetBytes(serial), 0, 8);
                } break;

                case RequestTypes4.BoxSearch:
                {
                    if (data.Length != 0x14c)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    // todo: validate or log some of this?
                    BoxLabels4 label = (BoxLabels4)BitConverter.ToInt32(data, 0x144);

                    logEntry.AppendFormat("Searching for {0} boxes.", label);
                    logEntry.AppendLine();

                    BoxRecord4[] results = Database.Instance.BoxSearch4(label, 20);
                    response.Write(new byte[] { 0x00, 0x00 }, 0, 2);     // result code (0 for OK)
                    response.Write(BitConverter.GetBytes(results.Length), 0, 4);

                    foreach (BoxRecord4 result in results)
                    {
                        response.Write(BitConverter.GetBytes(result.PID), 0, 4);
                        response.Write(BitConverter.GetBytes((int)result.Label), 0, 4);
                        response.Write(BitConverter.GetBytes(result.SerialNumber), 0, 8);
                        // xxx: this may throw if there's database corruption
                        response.Write(result.Data, 0, 0x21c);
                    }
                    logEntry.AppendFormat("Retrieved {0} boxes.", results.Length);
                    logEntry.AppendLine();
                } break;
                    #endregion

                    #region Dressup
                case RequestTypes4.DressupUpload:
                {
                    if (data.Length != 0x220)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    byte[] dressupData = new byte[0xe0];
                    Array.Copy(data, 0x140, dressupData, 0, 0xe0);
                    DressupRecord4 record = new DressupRecord4(pid, 0, dressupData);
                    ulong          serial = Database.Instance.DressupUpload4(record);

                    if (serial == 0)
                    {
                        logEntry.AppendLine("Uploaded dressup already in server.");
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    logEntry.AppendFormat("Dressup {0} uploaded successfully.", serial);
                    response.Write(new byte[] { 0x00, 0x00 }, 0, 2);     // result code (0 for OK)
                    response.Write(BitConverter.GetBytes(serial), 0, 8);
                } break;

                case RequestTypes4.DressupSearch:
                {
                    if (data.Length != 0x14c)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    // todo: validate or log some of this?
                    ushort species = BitConverter.ToUInt16(data, 0x144);

                    logEntry.AppendFormat("Searching for dressups of species {0}.", species);
                    logEntry.AppendLine();

                    DressupRecord4[] results = Database.Instance.DressupSearch4(species, 10);
                    response.Write(new byte[] { 0x00, 0x00 }, 0, 2);     // result code (0 for OK)
                    response.Write(BitConverter.GetBytes(results.Length), 0, 4);

                    foreach (DressupRecord4 result in results)
                    {
                        response.Write(BitConverter.GetBytes(result.PID), 0, 4);
                        response.Write(BitConverter.GetBytes(result.SerialNumber), 0, 8);
                        response.Write(result.Data, 0, 0xe0);
                    }
                    logEntry.AppendFormat("Retrieved {0} dressup results.", results.Length);
                    logEntry.AppendLine();
                } break;
                    #endregion

                    #region Battle videos
                case RequestTypes4.BattleVideoUpload:
                {
                    if (data.Length != 0x1e8c)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    byte[] battlevidData = new byte[0x1d4c];
                    Array.Copy(data, 0x140, battlevidData, 0, 0x1d4c);
                    BattleVideoRecord4 record = new BattleVideoRecord4(pid, 0, battlevidData);
                    ulong serial = Database.Instance.BattleVideoUpload4(record);

                    if (serial == 0)
                    {
                        logEntry.AppendFormat("Uploaded battle video already in server.");
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    logEntry.AppendFormat("Battle video {0} uploaded successfully.", BattleVideoHeader4.FormatSerial(serial));
                    logEntry.AppendLine();
                    response.Write(new byte[] { 0x00, 0x00 }, 0, 2);     // result code (0 for OK)
                    response.Write(BitConverter.GetBytes(serial), 0, 8);
                } break;

                case RequestTypes4.BattleVideoSearch:
                {
                    if (data.Length != 0x15c)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    // todo: validate or log some of this?
                    BattleVideoRankings4 ranking = (BattleVideoRankings4)BitConverter.ToUInt32(data, 0x140);
                    ushort species             = BitConverter.ToUInt16(data, 0x144);
                    BattleVideoMetagames4 meta = (BattleVideoMetagames4)data[0x146];
                    byte country = data[0x147];
                    byte region  = data[0x148];

                    logEntry.Append("Searching for ");
                    if (ranking != BattleVideoRankings4.None)
                    {
                        logEntry.AppendFormat("{0}", ranking);
                    }
                    else
                    {
                        if (species != 0xffff)
                        {
                            logEntry.AppendFormat("species {0}, ", species);
                        }
                        logEntry.AppendFormat("{0}", meta);
                        if (country != 0xff)
                        {
                            logEntry.AppendFormat(", country {0}", country);
                        }
                        if (region != 0xff)
                        {
                            logEntry.AppendFormat(", region {0}", region);
                        }
                    }
                    logEntry.AppendLine(".");

                    if ((byte)meta == 254)
                    {
                        // todo: Figure out how to make the game perform this search!
                        logEntry.AppendLine("Reverting to latest 30.");
                        meta = BattleVideoMetagames4.SearchLatest30;
                    }

                    BattleVideoHeader4[] results = Database.Instance.BattleVideoSearch4(species, ranking, meta, country, region, 30);
                    response.Write(new byte[] { 0x00, 0x00 }, 0, 2);     // result code (0 for OK)
                    response.Write(BitConverter.GetBytes(results.Length), 0, 4);

                    foreach (BattleVideoHeader4 result in results)
                    {
                        response.Write(BitConverter.GetBytes(result.PID), 0, 4);
                        response.Write(BitConverter.GetBytes(result.SerialNumber), 0, 8);
                        response.Write(result.Data, 0, 0xe4);
                    }
                    logEntry.AppendFormat("Retrieved {0} battle video results.", results.Length);
                    logEntry.AppendLine();
                } break;

                case RequestTypes4.BattleVideoWatch:
                {
                    if (data.Length != 0x14c)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    ulong serial = BitConverter.ToUInt64(data, 0x140);
                    BattleVideoRecord4 record = Database.Instance.BattleVideoGet4(serial, true);
                    if (record == null)
                    {
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        logEntry.AppendFormat("Requested battle video {0} was missing.", BattleVideoHeader4.FormatSerial(serial));
                        logEntry.AppendLine();
                        type = EventLogEntryType.FailureAudit;
                        break;
                    }

                    response.Write(new byte[] { 0x00, 0x00 }, 0, 2);     // result code (0 for OK)
                    response.Write(BitConverter.GetBytes(record.PID), 0, 4);
                    response.Write(BitConverter.GetBytes(record.SerialNumber), 0, 8);
                    response.Write(record.Header.Data, 0, 0xe4);
                    response.Write(record.Data, 0, 0x1c68);
                    logEntry.AppendFormat("Retrieved battle video {0}.", BattleVideoHeader4.FormatSerial(serial));
                    logEntry.AppendLine();
                } break;

                case RequestTypes4.BattleVideoSaved:
                {
                    if (data.Length != 0x148)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    ulong serial = BitConverter.ToUInt64(data, 0x140);

                    if (Database.Instance.BattleVideoFlagSaved4(serial))
                    {
                        response.Write(new byte[] { 0x00, 0x00 }, 0, 2);     // result code (0 for OK)
                        logEntry.AppendFormat("Battle video {0} flagged saved.", BattleVideoHeader4.FormatSerial(serial));
                        logEntry.AppendLine();
                    }
                    else
                    {
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        logEntry.AppendFormat("Requested battle video {0} was missing.", BattleVideoHeader4.FormatSerial(serial));
                        logEntry.AppendLine();
                    }
                } break;
                    #endregion

                    #region Trainer Rankings
                case RequestTypes4.TrainerRankingsHead:
                {
                    if (data.Length != 0x140)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    logEntry.AppendLine("Replayed response.");
                    response.Write(new byte[] { 0x00, 0x00 }, 0, 2);

                    // Response format seems to be a list of the three record categories currently being collected.
                    // 0x01: Hall of fame entries
                    // 0x28: Completed GTS trades
                    // 0x43: Facilities challenged at battle frontier
                    // The purpose of 0x1c is unclear to me.
                    response.Write(new byte[] { 0x01, 0x28, 0x43, 0x1c }, 0, 4);
                } break;

                case RequestTypes4.TrainerRankingsSearch:
                {
                    if (data.Length != 0x164)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    // The request is important. It contains the player's records for the above three chosen categories.
                    // It also (presumably) conveys which teams the player is a part of.
                    // (Trainer Class, Birth Month, Favourite Pokémon)

                    // The response is biig and contains all the records for both this week and last week.
                    // This week lacks numbers because they're still growing and to make it a surprise.

                    logEntry.AppendLine("Replayed response.");
                    response.Write(new byte[] { 0x00, 0x00 }, 0, 2);

                    response.WriteBytes(new byte[] {
                            0x1c, 0x00, 0x00, 0x00, 0x02, 0x07, 0x06, 0x00,
                            0x04, 0x01, 0x05, 0x03, 0x0c, 0x09, 0x0f, 0x0e,
                            0x0d, 0x08, 0x0a, 0x0b, 0x89, 0xe9, 0x01, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x8a, 0x66, 0x01, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x8f, 0x04, 0x01, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0xb1, 0xe0, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x31, 0xa4, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0xf5, 0xa0, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0xa8, 0x9d, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x04, 0x71, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0xd6, 0x51, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0xc9, 0x46, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x17, 0x2a, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0xb2, 0x26, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x58, 0x22, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x20, 0x21, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x99, 0x1e, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x6b, 0x12, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x01, 0x09, 0x05, 0x0c,
                            0x07, 0x04, 0x08, 0x0a, 0x02, 0x03, 0x06, 0x0b,
                            0xd3, 0x16, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0xf0, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0xa4, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x17, 0xea, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x90, 0xcd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x86, 0xb6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x6e, 0xb5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0xdf, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0xde, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x7b, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x46, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x89, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x9d, 0x00, 0xc0, 0x01, 0xf9, 0x00, 0x5e, 0x01,
                            0xf8, 0x00, 0x06, 0x00, 0x95, 0x01, 0xa0, 0x00,
                            0x97, 0x00, 0x95, 0x00, 0xbd, 0x01, 0x7f, 0x01,
                            0x5f, 0x00, 0xd8, 0x01, 0x88, 0x01, 0x94, 0x00,
                            0xed, 0x01, 0x19, 0x00, 0x93, 0x00, 0x96, 0x00,
                            0xb6, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x95, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x61, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0xb1, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x0b, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0xd3, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0xa4, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0xee, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x22, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x1f, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x27, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x3a, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x6f, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x5e, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x41, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x20, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x33, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0xeb, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0xdf, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x4c, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x2b, 0x00, 0x00, 0x00, 0x07, 0x03, 0x0f, 0x02,
                            0x05, 0x0e, 0x04, 0x01, 0x09, 0x00, 0x0a, 0x0c,
                            0x06, 0x0d, 0x0b, 0x08, 0x01, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x0c, 0x08, 0x0b, 0x09,
                            0x01, 0x07, 0x03, 0x04, 0x06, 0x0a, 0x05, 0x02,
                            0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0xf8, 0x00, 0xbd, 0x00, 0x8d, 0x00, 0x71, 0x01,
                            0x8e, 0x01, 0x98, 0x00, 0xa2, 0x00, 0x7e, 0x00,
                            0x42, 0x01, 0x55, 0x01, 0x48, 0x01, 0xa7, 0x01,
                            0x53, 0x00, 0x4f, 0x01, 0xa6, 0x00, 0xd7, 0x00,
                            0xcc, 0x01, 0x48, 0x00, 0xf3, 0x00, 0x94, 0x01,
                            0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x4a, 0x00, 0x00, 0x00, 0x02, 0x06, 0x07, 0x05,
                            0x00, 0x03, 0x04, 0x01, 0x08, 0x09, 0x0e, 0x0c,
                            0x0d, 0x0a, 0x0b, 0x0f, 0x5e, 0x76, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x8f, 0x39, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x16, 0x28, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x8e, 0x1e, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x4a, 0x13, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0xb6, 0x0c, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x33, 0x0b, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0xfe, 0x0a, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0xaa, 0x0a, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x2b, 0x04, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0xb8, 0x02, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x63, 0x02, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x14, 0x02, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0xad, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x6a, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x02, 0x04, 0x01, 0x09,
                            0x0a, 0x05, 0x03, 0x0b, 0x07, 0x08, 0x0c, 0x06,
                            0x64, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0xd7, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x24, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x7a, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x5a, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0xfc, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0xf3, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x9f, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x3a, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x82, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0xbb, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0xa6, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0xf9, 0x00, 0x79, 0x00, 0x9d, 0x00, 0x80, 0x01,
                            0xa0, 0x00, 0x9b, 0x01, 0x95, 0x01, 0x70, 0x01,
                            0xc0, 0x01, 0xd0, 0x01, 0x7f, 0x01, 0x94, 0x01,
                            0x95, 0x00, 0xbd, 0x01, 0x7d, 0x01, 0xe6, 0x01,
                            0x94, 0x00, 0x92, 0x00, 0xd2, 0x01, 0x01, 0x00,
                            0xf5, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0xec, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x85, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x1d, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x38, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x1a, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x27, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0xd4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0xb7, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x87, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x87, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x6e, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x67, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0xd4, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0xb2, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x9d, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x97, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0xcd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0xb4, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x80, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x01, 0x00, 0x00, 0x00, 0x02, 0x04, 0x06, 0x0f,
                            0x03, 0x05, 0x07, 0x00, 0x08, 0x09, 0x01, 0x0c,
                            0x0e, 0x0b, 0x0d, 0x0a, 0x03, 0x06, 0x01, 0x04,
                            0x0a, 0x05, 0x08, 0x0c, 0x09, 0x0b, 0x02, 0x07,
                            0x8e, 0x00, 0x7c, 0x01, 0x75, 0x01, 0x8e, 0x01,
                            0x9d, 0x00, 0xe4, 0x01, 0x04, 0x01, 0x80, 0x01,
                            0xfe, 0x00, 0xaf, 0x00, 0xe3, 0x01, 0x88, 0x01,
                            0xe7, 0x01, 0x82, 0x00, 0x5e, 0x01, 0xeb, 0x01,
                            0xe5, 0x00, 0xe8, 0x01, 0x93, 0x00, 0x96, 0x00,
                            0x28, 0x00, 0x00, 0x00, 0x03, 0x07, 0x00, 0x0c,
                            0x04, 0x02, 0x05, 0x06, 0x0b, 0x01, 0x0f, 0x0d,
                            0x08, 0x09, 0x0e, 0x0a, 0x0a, 0x07, 0x0c, 0x06,
                            0x01, 0x02, 0x08, 0x03, 0x05, 0x09, 0x0b, 0x04,
                            0x88, 0x01, 0x8f, 0x00, 0x7c, 0x01, 0x8e, 0x01,
                            0xea, 0x00, 0x01, 0x01, 0xa4, 0x01, 0xe6, 0x01,
                            0x5e, 0x01, 0xc0, 0x01, 0x8e, 0x00, 0x9a, 0x00,
                            0xe7, 0x01, 0xbd, 0x01, 0xfe, 0x00, 0x82, 0x00,
                            0xbb, 0x00, 0x04, 0x01, 0xb0, 0x00, 0x9d, 0x00,
                            0x43, 0x00, 0x00, 0x00, 0x00, 0x02, 0x06, 0x04,
                            0x03, 0x08, 0x07, 0x05, 0x0f, 0x01, 0x0c, 0x09,
                            0x0b, 0x0e, 0x0d, 0x0a, 0x0a, 0x05, 0x04, 0x07,
                            0x01, 0x03, 0x0b, 0x0c, 0x09, 0x06, 0x08, 0x02,
                            0x8e, 0x01, 0x75, 0x01, 0x82, 0x00, 0x80, 0x01,
                            0xaf, 0x00, 0xeb, 0x01, 0xe7, 0x01, 0x5e, 0x01,
                            0x9a, 0x00, 0x8f, 0x00, 0x9d, 0x00, 0xe8, 0x01,
                            0x8e, 0x00, 0x7c, 0x01, 0x7d, 0x01, 0xe5, 0x00,
                            0x88, 0x01, 0x1a, 0x00, 0xfe, 0x00, 0xf9, 0x00
                        });
                } break;
                    #endregion

                default:
                    logEntry.AppendLine("Unrecognized request type.");
                    response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                    break;
                }
            }
            catch (Exception ex)
            {
                logEntry.AppendFormat("Unhandled exception while handling request.\nException: {0}", ex.ToString());
                logEntry.AppendLine();
                type = EventLogEntryType.Error;
                response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
            }

            response.Flush();
            byte[] responseData = response.ToArray();
            WriteLength(responseData);
            CryptMessage(responseData);

            LogHelper.Write(logEntry.ToString(), type);
            return(responseData);
        }
Example #3
0
        protected override byte[] ProcessRequest(byte[] data, TcpClient c)
        {
            int length = BitConverter.ToInt32(data, 0);

            AssertHelper.Equals(length, data.Length);

            RequestTypes4 requestType = (RequestTypes4)data[4];
            StringBuilder logEntry    = new StringBuilder();

            logEntry.AppendFormat("Handling Generation IV {0} request.\nHost: {1}", requestType, c.Client.RemoteEndPoint);
            logEntry.AppendLine();
            EventLogEntryType type = EventLogEntryType.Information;

            CryptMessage(data);

            MemoryStream response = new MemoryStream();

            response.Write(new byte[] { 0x00, 0x00, 0x00, 0x00 }, 0, 4); // placeholder for length
            response.WriteByte((byte)requestType);
            response.WriteByte(data[5]);

            try
            {
                int  pid      = BitConverter.ToInt32(data, 8);
                byte version  = data[0x0c];
                byte language = data[0x0d];
                logEntry.AppendFormat("pid: {0}", pid);
                logEntry.AppendLine();

                switch (requestType)
                {
                    #region Box upload
                case RequestTypes4.BoxUpload:
                {
                    if (data.Length != 0x360)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    BoxLabels4 label   = (BoxLabels4)BitConverter.ToInt32(data, 0x140);
                    byte[]     boxData = new byte[0x21c];
                    Array.Copy(data, 0x144, boxData, 0, 0x21c);
                    BoxRecord4 record = new BoxRecord4(pid, label, 0, boxData);
                    ulong      serial = Database.Instance.BoxUpload4(record);

                    if (serial == 0)
                    {
                        logEntry.AppendLine("Uploaded box already in server.");
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    logEntry.AppendFormat("Box {0} uploaded successfully.", serial);
                    logEntry.AppendLine();
                    response.Write(new byte[] { 0x00, 0x00 }, 0, 2);     // result code (0 for OK)
                    response.Write(BitConverter.GetBytes(serial), 0, 8);
                } break;

                case RequestTypes4.BoxSearch:
                {
                    if (data.Length != 0x14c)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    // todo: validate or log some of this?
                    BoxLabels4 label = (BoxLabels4)BitConverter.ToInt32(data, 0x144);

                    logEntry.AppendFormat("Searching for {0} boxes.", label);
                    logEntry.AppendLine();

                    BoxRecord4[] results = Database.Instance.BoxSearch4(label, 20);
                    response.Write(new byte[] { 0x00, 0x00 }, 0, 2);     // result code (0 for OK)
                    response.Write(BitConverter.GetBytes(results.Length), 0, 4);

                    foreach (BoxRecord4 result in results)
                    {
                        response.Write(BitConverter.GetBytes(result.PID), 0, 4);
                        response.Write(BitConverter.GetBytes((int)result.Label), 0, 4);
                        response.Write(BitConverter.GetBytes(result.SerialNumber), 0, 8);
                        response.Write(result.Data, 0, 0x21c);
                    }
                    logEntry.AppendFormat("Retrieved {0} boxes.", results.Length);
                    logEntry.AppendLine();
                } break;
                    #endregion

                    #region Dressup
                case RequestTypes4.DressupUpload:
                {
                    if (data.Length != 0x220)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    byte[] dressupData = new byte[0xe0];
                    Array.Copy(data, 0x140, dressupData, 0, 0xe0);
                    DressupRecord4 record = new DressupRecord4(pid, 0, dressupData);
                    ulong          serial = Database.Instance.DressupUpload4(record);

                    if (serial == 0)
                    {
                        logEntry.AppendLine("Uploaded dressup already in server.");
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    logEntry.AppendFormat("Dressup {0} uploaded successfully.", serial);
                    response.Write(new byte[] { 0x00, 0x00 }, 0, 2);     // result code (0 for OK)
                    response.Write(BitConverter.GetBytes(serial), 0, 8);
                } break;

                case RequestTypes4.DressupSearch:
                {
                    if (data.Length != 0x14c)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    // todo: validate or log some of this?
                    ushort species = BitConverter.ToUInt16(data, 0x144);

                    logEntry.AppendFormat("Searching for dressups of species {0}.", species);
                    logEntry.AppendLine();

                    DressupRecord4[] results = Database.Instance.DressupSearch4(species, 10);
                    response.Write(new byte[] { 0x00, 0x00 }, 0, 2);     // result code (0 for OK)
                    response.Write(BitConverter.GetBytes(results.Length), 0, 4);

                    foreach (DressupRecord4 result in results)
                    {
                        response.Write(BitConverter.GetBytes(result.PID), 0, 4);
                        response.Write(BitConverter.GetBytes(result.SerialNumber), 0, 8);
                        response.Write(result.Data, 0, 0xe0);
                    }
                    logEntry.AppendFormat("Retrieved {0} dressup results.", results.Length);
                    logEntry.AppendLine();
                } break;
                    #endregion

                    #region Battle videos
                case RequestTypes4.BattleVideoUpload:
                {
                    if (data.Length != 0x1e8c)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    byte[] battlevidData = new byte[0x1d4c];
                    Array.Copy(data, 0x140, battlevidData, 0, 0x1d4c);
                    BattleVideoRecord4 record = new BattleVideoRecord4(pid, 0, battlevidData);
                    ulong serial = Database.Instance.BattleVideoUpload4(record);

                    if (serial == 0)
                    {
                        logEntry.AppendFormat("Uploaded battle video already in server.");
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    logEntry.AppendFormat("Battle video {0} uploaded successfully.", BattleVideoHeader4.FormatSerial(serial));
                    logEntry.AppendLine();
                    response.Write(new byte[] { 0x00, 0x00 }, 0, 2);     // result code (0 for OK)
                    response.Write(BitConverter.GetBytes(serial), 0, 8);
                } break;

                case RequestTypes4.BattleVideoSearch:
                {
                    if (data.Length != 0x15c)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    // todo: validate or log some of this?
                    BattleVideoRankings4 ranking = (BattleVideoRankings4)BitConverter.ToUInt32(data, 0x140);
                    ushort species             = BitConverter.ToUInt16(data, 0x144);
                    BattleVideoMetagames4 meta = (BattleVideoMetagames4)data[0x146];
                    byte country = data[0x147];
                    byte region  = data[0x148];

                    logEntry.Append("Searching for ");
                    if (ranking != BattleVideoRankings4.None)
                    {
                        logEntry.AppendFormat("{0}", ranking);
                    }
                    else
                    {
                        if (species != 0xffff)
                        {
                            logEntry.AppendFormat("species {0}, ", species);
                        }
                        logEntry.AppendFormat("{0}", meta);
                        if (country != 0xff)
                        {
                            logEntry.AppendFormat(", country {0}", country);
                        }
                        if (region != 0xff)
                        {
                            logEntry.AppendFormat(", region {0}", region);
                        }
                    }
                    logEntry.AppendLine(".");

                    if ((byte)meta == 254)
                    {
                        // todo: Figure out how to make the game perform this search!
                        logEntry.AppendLine("Reverting to latest 30.");
                        meta = BattleVideoMetagames4.SearchLatest30;
                    }

                    BattleVideoHeader4[] results = Database.Instance.BattleVideoSearch4(species, ranking, meta, country, region, 30);
                    response.Write(new byte[] { 0x00, 0x00 }, 0, 2);     // result code (0 for OK)
                    response.Write(BitConverter.GetBytes(results.Length), 0, 4);

                    foreach (BattleVideoHeader4 result in results)
                    {
                        response.Write(BitConverter.GetBytes(result.PID), 0, 4);
                        response.Write(BitConverter.GetBytes(result.SerialNumber), 0, 8);
                        response.Write(result.Data, 0, 0xe4);
                    }
                    logEntry.AppendFormat("Retrieved {0} battle video results.", results.Length);
                    logEntry.AppendLine();
                } break;

                case RequestTypes4.BattleVideoWatch:
                {
                    if (data.Length != 0x14c)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    ulong serial = BitConverter.ToUInt64(data, 0x140);
                    BattleVideoRecord4 record = Database.Instance.BattleVideoGet4(serial, true);
                    if (record == null)
                    {
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        logEntry.AppendFormat("Requested battle video {0} was missing.", BattleVideoHeader4.FormatSerial(serial));
                        logEntry.AppendLine();
                        type = EventLogEntryType.FailureAudit;
                        break;
                    }

                    response.Write(new byte[] { 0x00, 0x00 }, 0, 2);     // result code (0 for OK)
                    response.Write(BitConverter.GetBytes(record.PID), 0, 4);
                    response.Write(BitConverter.GetBytes(record.SerialNumber), 0, 8);
                    response.Write(record.Header.Data, 0, 0xe4);
                    response.Write(record.Data, 0, 0x1c68);
                    logEntry.AppendFormat("Retrieved battle video {0}.", BattleVideoHeader4.FormatSerial(serial));
                    logEntry.AppendLine();
                } break;

                case RequestTypes4.BattleVideoSaved:
                {
                    if (data.Length != 0x148)
                    {
                        logEntry.AppendLine("Length did not validate.");
                        type = EventLogEntryType.FailureAudit;
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    ulong serial = BitConverter.ToUInt64(data, 0x140);

                    if (Database.Instance.BattleVideoFlagSaved4(serial))
                    {
                        response.Write(new byte[] { 0x00, 0x00 }, 0, 2);     // result code (0 for OK)
                        logEntry.AppendFormat("Battle video {0} flagged saved.", BattleVideoHeader4.FormatSerial(serial));
                        logEntry.AppendLine();
                    }
                    else
                    {
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        logEntry.AppendFormat("Requested battle video {0} was missing.", BattleVideoHeader4.FormatSerial(serial));
                        logEntry.AppendLine();
                    }
                } break;
                    #endregion

                default:
                    logEntry.AppendLine("Unrecognized request type.");
                    response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                    break;
                }
            }
            catch (Exception ex)
            {
                logEntry.AppendFormat("Unhandled exception while handling request.\nException: {0}", ex.ToString());
                logEntry.AppendLine();
                type = EventLogEntryType.Error;
                response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
            }

            response.Flush();
            byte[] responseData = response.ToArray();
            WriteLength(responseData);
            CryptMessage(responseData);

            LogHelper.Write(logEntry.ToString(), type);
            return(responseData);
        }
Example #4
0
 public abstract BattleVideoHeader4[] BattleVideoSearch4(ushort species, BattleVideoRankings4 ranking, BattleVideoMetagames4 metagame, byte country, byte region, int count);
 public abstract BattleVideoHeader4[] BattleVideoSearch4(ushort species, BattleVideoRankings4 ranking, BattleVideoMetagames4 metagame, byte country, byte region, int count);