Beispiel #1
0
        public GtsSession5 FindSession5(int pid, String url)
        {
            // todo: keep a hash table of pids that maps them onto session objects
            GtsSession5 result = null;

            foreach (GtsSession5 sess in Sessions5.Values)
            {
                if (sess.PID == pid && sess.URL == url)
                {
                    if (result != null)
                    {
                        // todo: there's more than one matching session... delete them all.
                    }
                    return(sess); // temp until I get it to cleanup old sessions

                    result = sess;
                }
            }
            return(result);
        }
Beispiel #2
0
        public void ProcessRequest(HttpContext context)
        {
            int pid;

            if (context.Request.QueryString["pid"] == null ||
                !Int32.TryParse(context.Request.QueryString["pid"], out pid))
            {
                // pid missing or bad format
                Error400(context);
                return;
            }

            if (context.Request.QueryString.Count == 1)
            {
                // this is a new session request
                GtsSession5       session = new GtsSession5(pid, context.Request.PathInfo);
                GtsSessionManager manager = GtsSessionManager.FromContext(context);
                manager.Add(session);

                context.Response.Write(session.Token);
                return;
            }
            else if (context.Request.QueryString.Count == 3)
            {
                // this is a main request
                if (context.Request.QueryString["hash"] == null ||
                    context.Request.QueryString["data"] == null ||
                    context.Request.QueryString["data"].Length < 16)
                {
                    // arguments missing, partial check for data length.
                    // (Here, we require data to hold at least 10 bytes.
                    // In reality, it must hold at least 12, which is checked
                    // for below after decoding)
                    Error400(context);
                    return;
                }

                GtsSessionManager manager = GtsSessionManager.FromContext(context);
                if (!manager.Sessions5.ContainsKey(context.Request.QueryString["hash"]))
                {
                    // session hash not matched
                    Error400(context);
                    return;
                }

                GtsSession5 session = manager.Sessions5[context.Request.QueryString["hash"]];
                byte[]      data;
                try
                {
                    data = GtsSession5.DecryptData(context.Request.QueryString["data"]);
                    if (data.Length < 8)
                    {
                        // data too short to contain a pid and length.
                        // We check for 8 bytes, not 12, since the hash
                        // isn't included in DecryptData's result.
                        Error400(context);
                        return;
                    }
                }
                catch (FormatException)
                {
                    // data too short to contain a checksum,
                    // base64 format errors
                    Error400(context);
                    return;
                }

                int pid2 = BitConverter.ToInt32(data, 0);
                if (pid2 != pid)
                {
                    // packed pid doesn't match ?pid=
                    Error400(context);
                    return;
                }

                int length = BitConverter.ToInt32(data, 4);
                if (length + 8 != data.Length)
                {
                    // message length is incorrect
                    Error400(context);
                    return;
                }

                MemoryStream response = new MemoryStream();

                switch (session.URL)
                {
                default:
                    manager.Remove(session);

                    // unrecognized page url
                    // should be error 404 once we're done debugging
                    context.Response.Write("Almost there. Your path is:\n");
                    context.Response.Write(session.URL);
                    return;

                // Called during startup. Unknown purpose.
                case "/worldexchange/info.asp":
                    manager.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. Seems to contain trainer profile stats.
                case "/common/setProfile.asp":
                    manager.Remove(session);

                    // todo: Figure out what fun stuff is contained in this blob!

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

                // Called during startup and when you check your pokemon's status.
                case "/worldexchange/result.asp":
                {
                    manager.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 = DataAbstract.Instance.GtsDataForUser5(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 "/worldexchange/get.asp":
                {
                    manager.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 = DataAbstract.Instance.GtsDataForUser5(pid);

                    if (record == null)
                    {
                        // No pokemon in the system
                        // what do here?
                    }
                    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 "/worldexchange/delete.asp":
                {
                    manager.Remove(session);

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

                    GtsRecord5 record = DataAbstract.Instance.GtsDataForUser5(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 = DataAbstract.Instance.GtsDeletePokemon5(pid);
                        if (success)
                        {
                            response.Write(new byte[] { 0x01, 0x00 }, 0, 2);
                            manager.RefreshStats();
                        }
                        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 "/worldexchange/return.asp":
                {
                    manager.Remove(session);

                    GtsRecord5 record = DataAbstract.Instance.GtsDataForUser5(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 = DataAbstract.Instance.GtsDeletePokemon5(pid);
                        if (success)
                        {
                            response.Write(new byte[] { 0x01, 0x00 }, 0, 2);
                            manager.RefreshStats();
                        }
                        else
                        {
                            response.Write(new byte[] { 0x00, 0x00 }, 0, 2);
                        }
                    }
                } break;

                // Called when you deposit a pokemon into the system.
                case "/worldexchange/post.asp":
                {
                    if (data.Length != 440)
                    {
                        manager.Remove(session);
                        Error400(context);
                        return;
                    }

                    // todo: add transaction
                    if (DataAbstract.Instance.GtsDataForUser5(pid) != null)
                    {
                        // there's already a pokemon inside
                        manager.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(data, 8, recordBinary, 0, 296);
                    GtsRecord5 record = new GtsRecord5(recordBinary);

                    // todo: figure out what bytes 304-439 do:
                    // appears to be 4 bytes of 00, 128 bytes of stuff, 4 bytes of 80 00 00 00
                    // projectpokemon says it's a "signature of pokémon struct."
                    // does this mean sha1 with a secret const?
                    // if it's just a checksum, we can probably ignore it for now.

                    if (!record.Validate())
                    {
                        // hack check failed
                        // todo: test that 0c 00 is the correct code for GenV
                        manager.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.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 "/worldexchange/post_finish.asp":
                {
                    manager.Remove(session);

                    if (data.Length != 16)
                    {
                        Error400(context);
                        return;
                    }

                    // find a matching session which contains our record
                    GtsSession5 prevSession = manager.FindSession5(pid, "/worldexchange/post.asp");

                    manager.Remove(prevSession);
                    AssertHelper.Assert(prevSession.Tag is GtsRecord5);
                    GtsRecord5 record = (GtsRecord5)prevSession.Tag;

                    if (DataAbstract.Instance.GtsDepositPokemon5(record))
                    {
                        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 "/worldexchange/search.asp":
                {
                    manager.Remove(session);

                    if (data.Length < 15 || data.Length > 16)
                    {
                        Error400(context);
                        return;
                    }

                    int resultsCount = (int)data[14];

                    ushort species = BitConverter.ToUInt16(data, 8);
                    if (species < 1)
                    {
                        Error400(context);
                        return;
                    }

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

                    if (resultsCount < 1)
                    {
                        break;                           // optimize away requests for no rows
                    }
                    Genders gender   = (Genders)data[10];
                    byte    minLevel = data[11];
                    byte    maxLevel = data[12];
                    // byte 13 unknown
                    byte country = 0;
                    if (data.Length > 15)
                    {
                        country = data[15];
                    }

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

                // the exchange request uploads a record of the exchangee pokemon
                // plus the desired PID to trade for at the very end.
                case "/worldexchange/exchange.asp":
                {
                    if (data.Length != 440)
                    {
                        manager.Remove(session);
                        Error400(context);
                        return;
                    }

                    byte[] uploadData = new byte[296];
                    Array.Copy(data, 8, uploadData, 0, 296);
                    GtsRecord5 upload    = new GtsRecord5(uploadData);
                    int        targetPid = BitConverter.ToInt32(data, 304);
                    GtsRecord5 result    = DataAbstract.Instance.GtsDataForUser5(targetPid);

                    if (result == null || result.IsExchanged != 0)
                    {
                        // Pokémon is traded (or was never here to begin with)
                        manager.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
                        manager.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 "/worldexchange/exchange_finish.asp":
                {
                    manager.Remove(session);

                    if (data.Length != 16)
                    {
                        Error400(context);
                        return;
                    }

                    // find a matching session which contains our record
                    GtsSession5 prevSession = manager.FindSession5(pid, "/worldexchange/exchange.asp");

                    manager.Remove(prevSession);
                    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 (DataAbstract.Instance.GtsTradePokemon5(upload, result))
                    {
                        manager.RefreshStats();
                        response.Write(new byte[] { 0x01, 0x00 }, 0, 2);
                    }
                    else
                    {
                        response.Write(new byte[] { 0x00, 0x00 }, 0, 2);
                    }
                } break;
                }

                response.Flush();
                byte[] responseArray = response.ToArray();
                response.Dispose();
                response = null;

                // write GenV response checksum
                context.Response.OutputStream.Write(responseArray, 0, responseArray.Length);
                context.Response.Write(GtsSession5.ResponseChecksum(responseArray));
            }
            else
            {
                // wrong number of querystring arguments
                Error400(context);
            }
        }
Beispiel #3
0
 public void Remove(GtsSession5 session)
 {
     Sessions5.Remove(session.Hash);
 }
Beispiel #4
0
 public void Add(GtsSession5 session)
 {
     Sessions5.Add(session.Hash, session);
 }