public abstract ulong BattleSubwayUpdateRecord5(BattleSubwayRecord5 record);
        public override void ProcessGamestatsRequest(byte[] request, MemoryStream response, string url, int pid, HttpContext context, GamestatsSession session)
        {
            Pokedex.Pokedex pokedex = AppStateHelper.Pokedex(context.Application);

            switch (url)
            {
                default:
                    SessionManager.Remove(session);

                    // unrecognized page url
                    ShowError(context, 404);
                    return;

                #region Common
                // Called during startup. Seems to contain trainer profile stats.
                case "/syachi2ds/web/common/setProfile.asp":
                    SessionManager.Remove(session);

                    if (request.Length != 100)
                    {
                        ShowError(context, 400);
                        return;
                    }

            #if !DEBUG
                    try
                    {
            #endif
                        // todo: Figure out what fun stuff is contained in this blob!
                        byte[] profileBinary = new byte[100];
                        Array.Copy(request, 0, profileBinary, 0, 100);
                        TrainerProfile5 profile = new TrainerProfile5(pid, profileBinary);
                        Database.Instance.GamestatsSetProfile5(profile);
            #if !DEBUG
                    }
                    catch { }
            #endif

                    response.Write(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
                        0, 8);
                    break;
                #endregion

                #region GTS
                // Called during startup. Unknown purpose.
                case "/syachi2ds/web/worldexchange/info.asp":
                    SessionManager.Remove(session);

                    // todo: find out the meaning of this request.
                    // is it simply done to check whether the GTS is online?
                    response.Write(new byte[] { 0x01, 0x00 }, 0, 2);
                    break;

                // Called during startup and when you check your pokemon's status.
                case "/syachi2ds/web/worldexchange/result.asp":
                {
                    SessionManager.Remove(session);

                    // todo: more fun stuff is contained in this blob on genV.
                    // my guess is that it's trainer profile info like setProfile.asp
                    // There's a long string of 0s which could be a trainer card signature raster

                    GtsRecord5 record = Database.Instance.GtsDataForUser5(pokedex, pid);

                    if (record == null)
                    {
                        // No pokemon in the system
                        response.Write(new byte[] { 0x05, 0x00 }, 0, 2);
                    }
                    else if (record.IsExchanged > 0)
                    {
                        // traded pokemon arriving!!!
                        response.Write(record.Save(), 0, 296);
                    }
                    else
                    {
                        // my existing pokemon is in the system, untraded
                        response.Write(new byte[] { 0x04, 0x00 }, 0, 2);
                    }

                } break;

                // Called after result.asp returns 4 when you check your pokemon's status
                case "/syachi2ds/web/worldexchange/get.asp":
                {
                    SessionManager.Remove(session);

                    // this is only called if result.asp returned 4.
                    // todo: what does this do if the contained pokemon is traded??
                    // todo: the same big blob of stuff from result.asp is sent here too.

                    GtsRecord5 record = Database.Instance.GtsDataForUser5(pokedex, pid);

                    if (record == null)
                    {
                        // No pokemon in the system
                        // what do here?
                        ShowError(context, 400);
                        return;
                    }
                    else
                    {
                        // just write the record whether traded or not...
                        response.Write(record.Save(), 0, 296);
                    }
                } break;

                // Called after result.asp returns an inbound pokemon record to delete it
                case "/syachi2ds/web/worldexchange/delete.asp":
                {
                    SessionManager.Remove(session);

                    // todo: the same big blob of stuff from result.asp is sent here too.

                    GtsRecord5 record = Database.Instance.GtsDataForUser5(pokedex, pid);
                    if (record == null)
                    {
                        response.Write(new byte[] { 0x00, 0x00 }, 0, 2);
                    }
                    else if (record.IsExchanged > 0)
                    {
                        // delete the arrived pokemon from the system
                        // todo: add transactions
                        // todo: log the successful trade?
                        // (either here or when the trade is done)
                        bool success = Database.Instance.GtsDeletePokemon5(pid);
                        if (success)
                            response.Write(new byte[] { 0x01, 0x00 }, 0, 2);
                        else
                            response.Write(new byte[] { 0x00, 0x00 }, 0, 2);
                    }
                    else
                    {
                        // own pokemon is there, fail. Use return.asp instead.
                        response.Write(new byte[] { 0x00, 0x00 }, 0, 2);
                    }
                } break;

                // called to delete your own pokemon after taking it back
                case "/syachi2ds/web/worldexchange/return.asp":
                {
                    SessionManager.Remove(session);

                    GtsRecord5 record = Database.Instance.GtsDataForUser5(pokedex, pid);
                    if (record == null)
                    {
                        response.Write(new byte[] { 0x00, 0x00 }, 0, 2);
                    }
                    else if (record.IsExchanged > 0)
                    {
                        // a traded pokemon is there, fail. Use delete.asp instead.
                        response.Write(new byte[] { 0x00, 0x00 }, 0, 2);
                    }
                    else
                    {
                        // delete own pokemon
                        // todo: add transactions
                        bool success = Database.Instance.GtsDeletePokemon5(pid);
                        if (success)
                        {
                            response.Write(new byte[] { 0x01, 0x00 }, 0, 2);
                            // todo: invalidate cache
                            //manager.RefreshStats();
                        }
                        else
                        {
                            response.Write(new byte[] { 0x00, 0x00 }, 0, 2);
                        }
                    }
                } break;

                // Called when you deposit a pokemon into the system.
                case "/syachi2ds/web/worldexchange/post.asp":
                {
                    if (request.Length != 432)
                    {
                        SessionManager.Remove(session);
                        ShowError(context, 400);
                        return;
                    }

                    // todo: add transaction
                    if (Database.Instance.GtsDataForUser5(pokedex, pid) != null)
                    {
                        // there's already a pokemon inside
                        SessionManager.Remove(session);
                        response.Write(new byte[] { 0x00, 0x00 }, 0, 2);
                        break;
                    }

                    // keep the record in memory while we wait for post_finish.asp request
                    byte[] recordBinary = new byte[296];
                    Array.Copy(request, 0, recordBinary, 0, 296);
                    GtsRecord5 record = new GtsRecord5(pokedex, recordBinary);

                    // todo: figure out what bytes 296-431 do:
                    // appears to be 4 bytes of 00, 128 bytes of stuff, 4 bytes of 80 00 00 00
                    // probably a pkvldtprod signature

                    if (!record.Validate())
                    {
                        // hack check failed
                        // todo: test that 0c 00 is the correct code for GenV
                        SessionManager.Remove(session);
                        response.Write(new byte[] { 0x0c, 0x00 }, 0, 2);
                        break;
                    }

                    // the following two fields are blank in the uploaded record.
                    // The server must provide them instead.
                    record.TimeDeposited = DateTime.UtcNow;
                    record.TimeExchanged = null;
                    record.IsExchanged = 0;
                    record.PID = pid;

                    session.Tag = record;
                    // todo: delete any other post.asp sessions registered under this PID

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

                } break;

                case "/syachi2ds/web/worldexchange/post_finish.asp":
                {
                    SessionManager.Remove(session);

                    if (request.Length != 8)
                    {
                        ShowError(context, 400);
                        return;
                    }

                    // find a matching session which contains our record
                    GamestatsSession prevSession = SessionManager.FindSession(pid, "/syachi2ds/web/worldexchange/post.asp");
                    if (prevSession == null)
                    {
                        response.Write(new byte[] { 0x00, 0x00 }, 0, 2);
                        return;
                    }

                    SessionManager.Remove(prevSession);
                    if (prevSession.Tag == null)
                    {
                        response.Write(new byte[] { 0x00, 0x00 }, 0, 2);
                        return;
                    }
                    AssertHelper.Assert(prevSession.Tag is GtsRecord5);
                    GtsRecord5 record = (GtsRecord5)prevSession.Tag;

                    if (Database.Instance.GtsDepositPokemon5(record))
                    {
                        // todo: invalidate cache
                        //manager.RefreshStats();
                        response.Write(new byte[] { 0x01, 0x00 }, 0, 2);
                    }
                    else
                        response.Write(new byte[] { 0x00, 0x00 }, 0, 2);

                } break;

                // the search request has a funny bit string request of search terms
                // and just returns a chunk of records end to end.
                case "/syachi2ds/web/worldexchange/search.asp":
                {
                    SessionManager.Remove(session);

                    if (request.Length < 7 || request.Length > 8)
                    {
                        ShowError(context, 400);
                        return;
                    }

                    int resultsCount = (int)request[6];

                    ushort species = BitConverter.ToUInt16(request, 0);
                    if (species < 1)
                    {
                        ShowError(context, 400);
                        return;
                    }

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

                    if (resultsCount < 1) break; // optimize away requests for no rows

                    Genders gender = (Genders)request[2];
                    byte minLevel = request[3];
                    byte maxLevel = request[4];
                    // byte 5 unknown
                    byte country = 0;
                    if (request.Length > 7) country = request[7];

                    if (resultsCount > 7) resultsCount = 7; // stop DDOS
                    GtsRecord5[] records = Database.Instance.GtsSearch5(pokedex, pid, species, gender, minLevel, maxLevel, country, resultsCount);
                    foreach (GtsRecord5 record in records)
                    {
                        response.Write(record.Save(), 0, 296);
                    }

                    Database.Instance.GtsSetLastSearch5(pid);

                } break;

                // the exchange request uploads a record of the exchangee pokemon
                // plus the desired PID to trade for at the very end.
                case "/syachi2ds/web/worldexchange/exchange.asp":
                {
                    if (request.Length != 432)
                    {
                        SessionManager.Remove(session);
                        ShowError(context, 400);
                        return;
                    }

                    byte[] uploadData = new byte[296];
                    Array.Copy(request, 0, uploadData, 0, 296);
                    GtsRecord5 upload = new GtsRecord5(pokedex, uploadData);
                    upload.IsExchanged = 0;
                    int targetPid = BitConverter.ToInt32(request, 296);
                    GtsRecord5 result = Database.Instance.GtsDataForUser5(pokedex, targetPid);
                    DateTime ? searchTime = Database.Instance.GtsGetLastSearch5(pid);

                    if (result == null || searchTime == null ||
                        result.TimeDeposited > (DateTime)searchTime || // If this condition is met, it means the pokemon in the system is DIFFERENT from the one the user is trying to trade for, ie. it was deposited AFTER the user did their search. The one the user wants was either taken back or traded.
                        result.IsExchanged != 0)
                    {
                        // Pokémon is traded (or was never here to begin with)
                        SessionManager.Remove(session);
                        response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
                        break;
                    }

                    // enforce request requirements server side
                    if (!upload.Validate() || !upload.CanTrade(result))
                    {
                        // todo: find the correct codes for these
                        SessionManager.Remove(session);
                        response.Write(new byte[] { 0x0c, 0x00 }, 0, 2);
                        return;
                    }

                    object[] tag = new GtsRecord5[2];
                    tag[0] = upload;
                    tag[1] = result;
                    session.Tag = tag;

                    GtsRecord5 tradedResult = result.Clone();
                    tradedResult.FlagTraded(upload); // only real purpose is to generate a proper response

                    // todo: we need a mechanism to "reserve" a pokemon being traded at this
                    // point in the process, but be able to relinquish it if exchange_finish
                    // never happens.
                    // Currently, if two people try to take the same pokemon, it will appear
                    // to work for both but then fail for the second after they've saved
                    // their game. This causes a hard crash and a "save file is corrupt,
                    // "previous will be loaded" error when restarting.
                    // the reservation can be done in application state and has no reason
                    // to touch the database. (exchange_finish won't work anyway if application
                    // state is lost.)

                    response.Write(result.Save(), 0, 296);

                } break;

                case "/syachi2ds/web/worldexchange/exchange_finish.asp":
                {
                    SessionManager.Remove(session);

                    if (request.Length != 8)
                    {
                        ShowError(context, 400);
                        return;
                    }

                    // find a matching session which contains our record
                    GamestatsSession prevSession = SessionManager.FindSession(pid, "/syachi2ds/web/worldexchange/exchange.asp");
                    if (prevSession == null)
                    {
                        response.Write(new byte[] { 0x00, 0x00 }, 0, 2);
                        return;
                    }

                    SessionManager.Remove(prevSession);
                    if (prevSession.Tag == null)
                    {
                        response.Write(new byte[] { 0x00, 0x00 }, 0, 2);
                        return;
                    }
                    AssertHelper.Assert(prevSession.Tag is GtsRecord5[]);
                    GtsRecord5[] tag = (GtsRecord5[])prevSession.Tag;
                    AssertHelper.Assert(tag.Length == 2);

                    GtsRecord5 upload = (GtsRecord5)tag[0];
                    GtsRecord5 result = (GtsRecord5)tag[1];

                    if (Database.Instance.GtsTradePokemon5(upload, result, pid))
                        response.Write(new byte[] { 0x01, 0x00 }, 0, 2);
                    else
                        response.Write(new byte[] { 0x00, 0x00 }, 0, 2);

                } break;
                #endregion

                #region Battle Subway
                case "/syachi2ds/web/battletower/info.asp":
                    SessionManager.Remove(session);

                    // Probably an availability/status code.
                    // todo: See how the game reacts to various values.
                    response.Write(new byte[] { 0x01, 0x00 }, 0, 2);
                    break;

                case "/syachi2ds/web/battletower/roomnum.asp":
                    SessionManager.Remove(session);

                    //byte rank = data[0x00];
                    response.Write(new byte[] { 0x32, 0x00 }, 0, 2);
                    break;

                case "/syachi2ds/web/battletower/download.asp":
                {
                    SessionManager.Remove(session);

                    if (request.Length != 2)
                    {
                        ShowError(context, 400);
                        return;
                    }

                    byte rank = request[0];
                    byte roomNum = request[1];

                    if (rank > 9 || roomNum > 49)
                    {
                        ShowError(context, 400);
                        return;
                    }

                    BattleSubwayRecord5[] opponents = Database.Instance.BattleSubwayGetOpponents5(pid, rank, roomNum);
                    BattleSubwayProfile5[] leaders = Database.Instance.BattleSubwayGetLeaders5(rank, roomNum);

                    if (opponents.Length != 7)
                    {
                        // todo: Implement fake trainers on Gen5 too.
                        ShowError(context, 500);
                        return;
                    }

                    foreach (BattleSubwayRecord5 record in opponents)
                    {
                        response.Write(record.Save(), 0, 240);
                    }

                    foreach (BattleSubwayProfile5 leader in leaders)
                    {
                        response.Write(leader.Save(), 0, 34);
                    }

                } break;

                case "/syachi2ds/web/battletower/upload.asp":
                {
                    SessionManager.Remove(session);

                    if (request.Length != 388)
                    {
                        ShowError(context, 400);
                        return;
                    }

                    BattleSubwayRecord5 record = new BattleSubwayRecord5(request, 0);

                    record.Rank = request[0xf0];
                    record.RoomNum = request[0xf1];
                    record.BattlesWon = request[0xf2];
                    record.Unknown4 = new byte[5];
                    Array.Copy(request, 0xf3, record.Unknown4, 0, 5);
                    record.Unknown5 = BitConverter.ToUInt64(request, 0xf8);

                    // todo: Check pkvldtprod signature and/or revalidate

                    // todo: Do we want to store their record anyway if they lost the first round?
                    if (record.BattlesWon > 0)
                        Database.Instance.BattleSubwayUpdateRecord5(record);
                    if (record.BattlesWon == 7)
                        Database.Instance.BattleSubwayAddLeader5(record);

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

                } break;

                #endregion

            }
        }
 public abstract ulong BattleSubwayAddLeader5(BattleSubwayRecord5 record);
Example #4
0
        static void Main(string[] args)
        {
            if (args.Length == 0)
            {
                Console.WriteLine("Usage: towerRestorer4 <path>");
                Console.WriteLine("Attempts to insert files in <path>");
                Console.WriteLine("into the database in app configuration.");
                Console.WriteLine("Only inserts files whose names match the naming pattern:");
                Console.WriteLine("g*_pid*_rank*_room*");
                Console.WriteLine("Rank and room number are taken from the filename.");
                return;
            }

            Database db = Database.Instance;

            String[] filenames = Directory.GetFiles(args[0]);
            int successCount = 0;
            int failureCount = 0;
            int opponentSuccessCount = 0;
            int opponentFailureCount = 0;
            int leaderSuccessCount = 0;
            int leaderFailureCount = 0;

            foreach (String s in filenames)
            {
                String filename = s;

                int slashIndex = filename.LastIndexOf(Path.DirectorySeparatorChar);
                if (slashIndex >= 0)
                {
                    filename = filename.Substring(slashIndex + 1);
                }

                int dotIndex = filename.LastIndexOf('.');
                if (dotIndex >= 0)
                {
                    filename = filename.Substring(0, dotIndex);
                }

                String[] split = filename.Split('_');

                byte rank, room;

                if (split.Length != 4 ||
                    (split[0] != "g4" && split[0] != "g5") ||
                    split[2].Substring(0, 4) != "rank" ||
                    !Byte.TryParse(split[2].Substring(4), out rank) ||
                    split[3].Substring(0, 4) != "room" ||
                    !Byte.TryParse(split[3].Substring(4), out room)
                    )
                {
                    Console.WriteLine("{0}: Filename pattern does not match, skipped.", filename);
                    failureCount++;
                    continue;
                }

                int gen = Convert.ToInt32(split[0].Substring(1));

                rank--;
                room--;

                switch (gen)
                {
                    case 4:
                    {
                        FileStream fs = File.OpenRead(s);
                        if (fs.Length != 0xa38)
                        {
                            Console.WriteLine("{0}: file size is wrong, skipped.", filename);
                            failureCount++;
                            continue;
                        }

                        byte[] data = new byte[0xa38];
                        fs.ReadBlock(data, 0, 0xa38);
                        fs.Close();

                        // battletower/download.asp response: 2616 bytes
                        // 00-63b: BattleTowerRecord objects x7
                        // 63c-a37: BattleTowerTrainerProfile objects x30
                        for (int x = 0; x < 7; x++)
                        {
                            try
                            {
                                BattleTowerRecord4 record = new BattleTowerRecord4(data, 0xe4 * x);
                                record.PID = 0;
                                record.Rank = rank;
                                record.RoomNum = room;
                                record.BattlesWon = 7;
                                db.BattleTowerUpdateRecord4(record);
                                opponentSuccessCount++;
                            }
                            catch (Exception ex)
                            {
                                Console.WriteLine(ex.Message);
                                opponentFailureCount++;
                            }
                        }

                        for (int x = 0; x < 30; x++)
                        {
                            try
                            {
                                BattleTowerProfile4 profile = new BattleTowerProfile4(data, 0x63c + 0x22 * x);
                                BattleTowerRecord4 record = new BattleTowerRecord4();
                                record.Profile = profile;
                                record.PID = 0;
                                record.Rank = rank;
                                record.RoomNum = room;
                                db.BattleTowerAddLeader4(record);
                                leaderSuccessCount++;
                            }
                            catch (Exception ex)
                            {
                                Console.WriteLine(ex.Message);
                                leaderFailureCount++;
                            }
                        }
                    } break;

                    case 5:
                    {
                        FileStream fs = File.OpenRead(s);
                        if (fs.Length != 0xab4)
                        {
                            Console.WriteLine("{0}: file size is wrong, skipped.", filename);
                            failureCount++;
                            continue;
                        }

                        byte[] data = new byte[0xab4];
                        fs.ReadBlock(data, 0, 0xab4);
                        fs.Close();

                        //web/battletower/download.asp response: 2700 bytes
                        //00-68f: BattleSubwayRecord objects x7
                        //690-a8b: BattleSubwayTrainerProfile objects x30
                        for (int x = 0; x < 7; x++)
                        {
                            try
                            {
                                BattleSubwayRecord5 record = new BattleSubwayRecord5(data, 0xf0 * x);
                                record.PID = 0;
                                record.Rank = rank;
                                record.RoomNum = room;
                                record.BattlesWon = 7;
                                record.Unknown4 = new byte[5];
                                db.BattleSubwayUpdateRecord5(record);
                                opponentSuccessCount++;
                            }
                            catch (Exception ex)
                            {
                                Console.WriteLine(ex.Message);
                                opponentFailureCount++;
                            }
                        }

                        for (int x = 0; x < 30; x++)
                        {
                            try
                            {
                                BattleSubwayProfile5 profile = new BattleSubwayProfile5(data, 0x690 + 0x22 * x);
                                BattleSubwayRecord5 record = new BattleSubwayRecord5();
                                record.Profile = profile;
                                record.PID = 0;
                                record.Rank = rank;
                                record.RoomNum = room;
                                db.BattleSubwayAddLeader5(record);
                                leaderSuccessCount++;
                            }
                            catch (Exception ex)
                            {
                                Console.WriteLine(ex.Message);
                                leaderFailureCount++;
                            }
                        }
                    } break;
                }

                Console.WriteLine("{0} complete", s);
            }

            Console.WriteLine("Added {0} files, {1} opponents, {2} leaders.", successCount, opponentSuccessCount, leaderSuccessCount);
            Console.WriteLine("Failed: {0} files, {1} opponents, {2} leaders.", failureCount, opponentFailureCount, leaderFailureCount);
            Console.ReadKey();
        }