/// <summary> /// Randomly selects some fake opponents without repeats (if possible) /// </summary> /// <param name="count"></param> /// <returns></returns> public static BattleTowerRecord4[] GenerateFakeOpponents(int count) { // todo: allow more with repeats if (count > FAKE_OPPONENTS_COUNT) { throw new ArgumentOutOfRangeException("count"); } List <int> values = Enumerable.Range(0, FAKE_OPPONENTS_COUNT).ToList(); BattleTowerRecord4[] result = new BattleTowerRecord4[count]; Random rand = new Random(); for (int x = 0; x < count; x++) { int index = rand.Next(values.Count); int index2 = values[index]; values.RemoveAt(index); result[x] = GenerateFakeOpponent(index2); } return(result); }
public static BattleTowerRecord4 GenerateFakeOpponent(int index) { if (index >= FAKE_OPPONENTS_COUNT) { throw new ArgumentOutOfRangeException("index"); } BattleTowerRecord4 record = new BattleTowerRecord4(); record.Party = new BattleTowerPokemon4[3]; record.Unknown3 = 7983; switch (index) { case 0: default: record.Party[0] = new BattleTowerPokemon4( 9, // Blastoise 234, // Leftovers new ushort[] { 57, // Surf 58, // Ice beam 252, // Fake out 156 // Rest }, 0x01020304, 15, // Modest BattleTowerPokemon4.PackIVs(31, 10, 20, 31, 31, 20), new byte[] { 6, 0, 0, 252, 252, 0 }, 0, Languages.English, 67, // Torrent 255, new EncodedString4("Leonardo", 22) ); record.Party[1] = new BattleTowerPokemon4( 389, // Torterra 287, // Choice scarf new ushort[] { 452, // Wood hammer 89, // Earthquake 276, // Superpower 242 // Crunch }, 0x01020304, 13, // Jolly BattleTowerPokemon4.PackIVs(31, 31, 20, 31, 10, 20), new byte[] { 6, 252, 0, 252, 0, 0 }, 0, Languages.English, 65, // Overgrow 255, new EncodedString4("Donatello", 22) ); record.Party[2] = new BattleTowerPokemon4( 324, // Torkoal 217, // Quick claw new ushort[] { 133, // Amnesia 156, // Rest 261, // Will-o-wisp 90 // Fissure }, 0x01020304, 23, // Careful BattleTowerPokemon4.PackIVs(31, 10, 31, 20, 10, 31), new byte[] { 252, 0, 6, 0, 0, 252 }, 0, Languages.English, 73, // White smoke 255, new EncodedString4("Raphael", 22) ); record.Profile = new BattleTowerProfile4( new EncodedString4("Splnter", 16), Versions.Platinum, Languages.English, 0, 0, 0x01020304, new TrendyPhrase4(0, 16, 291, 7), // Ninjask! Squirtle power! 0, 14 // Black belt ); record.PhraseChallenged = new TrendyPhrase4(0, 16, 291, 7); record.PhraseWon = new TrendyPhrase4(1, 11, 766, 65535); record.PhraseLost = new TrendyPhrase4(2, 8, 1406, 65535); break; case 1: record.Party[0] = new BattleTowerPokemon4( 376, // Metagross 268, // Expert belt new ushort[] { 89, // EQ 309, // Meteor mash 9, // Thunderpunch 153 // Explosion }, 15562158, 2106656978, // Actual TID/PV. Adamant, chained shiny 493780176, // Actual IVs which are crap. new byte[] { 252, 238, 0, 20, 0, 0 }, 0, Languages.English, 29, // Clear body 255, new EncodedString4("Goldfinger", 22) ); record.Party[1] = new BattleTowerPokemon4( 282, // Gardevoir 297, // Choice specs new ushort[] { 94, // Psychic 85, // Thunderbolt 247, // Shadow ball 271 // Trick }, 15562158, 4094067015, // Actual TID/PV. Modest, chained shiny 663420771, // Actual IVs, should be decent new byte[] { 254, 0, 56, 144, 56, 0 }, 0, Languages.English, 36, // Trace 255, new EncodedString4("Curly", 22) ); record.Party[2] = new BattleTowerPokemon4( 134, // Vaporeon 234, // Leftovers new ushort[] { 57, // Surf 164, // Substitute 273, // Wish 226 // Baton pass }, 15562158, 895218680, // Bold 514292539, new byte[] { 204, 0, 254, 0, 52, 0 }, 0, Languages.English, 11, // Water absorb 255, new EncodedString4("Seabiscuit", 22) ); record.Profile = new BattleTowerProfile4( new EncodedString4("Kate", 16), Versions.Platinum, Languages.English, 0, 0, 0x02030405, new TrendyPhrase4(3, 8, 1487, 65535), // There's only WI-FI left! 2, 33 // Lady ); record.PhraseChallenged = new TrendyPhrase4(3, 8, 1487, 65535); record.PhraseWon = new TrendyPhrase4(3, 3, 1439, 65535); // This BATTLE TOWER is DIFFICULT, isn't it? record.PhraseLost = new TrendyPhrase4(3, 2, 1493, 1492); // I love GTS! I love BATTLE TOWER too! break; case 2: record.Party[0] = new BattleTowerPokemon4( 392, // Infernape 275, // Focus sash new ushort[] { 252, // Fake out 283, // Endeavour 183, // Mach punch 7 // Fire punch }, 0x02030405, 13, // Jolly BattleTowerPokemon4.PackIVs(31, 31, 20, 31, 10, 20), new byte[] { 6, 252, 0, 252, 0, 0 }, 0, Languages.English, 66, // Blaze 255, new EncodedString4("FunkyMunky", 22) ); record.Party[1] = new BattleTowerPokemon4( 235, // Smeargle 210, // Custap new ushort[] { 147, // Spore 169, // Spider web 286, // Imprison 144 // Transform }, 0x02030405, 10, // Timid BattleTowerPokemon4.PackIVs(31, 10, 31, 31, 10, 20), new byte[] { 252, 0, 6, 252, 0, 0 }, 0, Languages.English, 101, // Technician 255, new EncodedString4("Yourself", 22) ); record.Party[2] = new BattleTowerPokemon4( 365, // Walrein 217, // Quick claw new ushort[] { 156, // Rest 214, // Sleep talk 104, // Double team 329 // Sheer cold }, 5, 5, // Bold BattleTowerPokemon4.PackIVs(31, 10, 20, 31, 10, 31), new byte[] { 252, 0, 0, 252, 0, 6 }, 0, Languages.English, 47, // Thick fat 255, new EncodedString4("Problem?", 22) ); record.Profile = new BattleTowerProfile4( new EncodedString4("Dennis", 16), Versions.Platinum, Languages.English, 0, 0, 0x02030405, new TrendyPhrase4(1, 12, 1147, 65535), // I get the happiest with MOTHER 0, 32 // Rich boy ); record.PhraseChallenged = new TrendyPhrase4(1, 12, 1147, 65535); record.PhraseWon = new TrendyPhrase4(2, 8, 1140, 65535); // You're WEAK, aren't you? record.PhraseLost = new TrendyPhrase4(2, 6, 1421, 65535); // ROFL! How awful! break; case 3: record.Party[0] = new BattleTowerPokemon4( 248, // Tyranitar 189, // Chople new ushort[] { 446, // Stealth rock 349, // Dragon dance 89, // EQ 444 // Stone edge }, 13, 13, // Jolly BattleTowerPokemon4.PackIVs(31, 31, 20, 31, 10, 20), new byte[] { 6, 252, 0, 252, 0, 0 }, 0, Languages.English, 45, // Sand stream 255, new EncodedString4("Tyranitar", 22) ); record.Party[1] = new BattleTowerPokemon4( 212, // Scizor 270, // Life orb new ushort[] { 418, // Bullet punch 450, // Bug bite 14, // Swords dance 355 // Roost }, 0x03040506, 3, // Adamant BattleTowerPokemon4.PackIVs(31, 31, 20, 31, 10, 20), new byte[] { 6, 252, 0, 252, 0, 0 }, 0, Languages.English, 101, // Technician 255, new EncodedString4("Scizor", 22) ); record.Party[2] = new BattleTowerPokemon4( 485, // Heatran 234, // Leftovers new ushort[] { 436, // Lava plume 414, // Earth power 156, // Rest 214 // Sleep talk }, 0x03040506, 3, // Modest // fixme: these IVs are unreasonably high for Soft Resetting. BattleTowerPokemon4.PackIVs(31, 10, 20, 20, 31, 31), new byte[] { 250, 0, 0, 0, 56, 204 }, 0, Languages.English, 18, // Flash fire 255, new EncodedString4("Heatran", 22) ); record.Profile = new BattleTowerProfile4( new EncodedString4("Dusty", 16), Versions.Platinum, Languages.English, 0, 0, 0x03040506, new TrendyPhrase4(3, 4, 1342, 65535), // I can do anything for TREASURE 0, 48 // Ruin Maniac ); record.PhraseChallenged = new TrendyPhrase4(3, 4, 1342, 65535); record.PhraseWon = new TrendyPhrase4(3, 6, 1148, 1107); // GRANDFATHER is the real NO.1 record.PhraseLost = new TrendyPhrase4(3, 10, 1389, 65535); // I prefer VACATION after all break; case 4: record.Party[0] = new BattleTowerPokemon4( 460, // Abomasnow 287, // Scarf new ushort[] { 59, // Blizzard 452, // Wood hammer 237, // Hidden power 89 // EQ }, 0x04050607, 11, // Hasty BattleTowerPokemon4.PackIVs(19, 31, 18, 30, 28, 19), // HP:fire base 59 // Original EVs: 228Spe/164Atk/116SAtk // Adjusted for Hidden Power IVs, sacrificing some Attack new byte[] { 0, 148, 0, 234, 128, 0 }, 0, Languages.English, 117, // Snow warning 255, new EncodedString4("Abomasnow", 22) ); record.Party[1] = new BattleTowerPokemon4( 471, // Glaceon 246, // Nevermeltice new ushort[] { 59, // Blizzard 247, // Shadow ball 273, // Wish 182 // Protect }, 15, 15, // Modest, shiny BattleTowerPokemon4.PackIVs(31, 10, 20, 31, 31, 20), new byte[] { 6, 0, 0, 252, 252, 0 }, 0, Languages.English, 81, // Snow cloak 255, new EncodedString4("Glaceon", 22) ); record.Party[2] = new BattleTowerPokemon4( 461, // Weavile 275, // Focus sash new ushort[] { 14, // Swords dance 400, // Night slash 8, // Ice punch 67 // Low kick }, 13, 13, // Jolly BattleTowerPokemon4.PackIVs(31, 31, 20, 31, 10, 20), new byte[] { 40, 252, 0, 218, 0, 0 }, 0, Languages.English, 46, // Pressure 255, new EncodedString4("Weavile", 22) ); record.Profile = new BattleTowerProfile4( new EncodedString4("Frosty", 16), Versions.Platinum, Languages.English, 0, 0, 0x04050607, new TrendyPhrase4(3, 3, 677, 1438), // This POWDER SNOW is NICE, isn't it? 2, 35 // Socialite ); record.PhraseChallenged = new TrendyPhrase4(3, 3, 677, 1438); record.PhraseWon = new TrendyPhrase4(1, 14, 797, 65535); // This ICE BALL was really good record.PhraseLost = new TrendyPhrase4(2, 5, 752, 65535); // Could it be? HEAT WAVE break; case 5: record.Party[0] = new BattleTowerPokemon4( 437, // Bronzong 234, // Leftovers new ushort[] { 433, // Trick room 360, // Gyro ball 95, // Hypnosis 153 // Explosion }, 22, 22, // Sassy BattleTowerPokemon4.PackIVs(31, 20, 31, 0, 10, 20), new byte[] { 252, 0, 252, 0, 0, 6 }, 0, Languages.English, 26, // Levitate 255, new EncodedString4("Bronzong", 22) ); record.Party[1] = new BattleTowerPokemon4( 464, // Rhyperior 270, // Life orb new ushort[] { 89, // EQ 444, // Stone edge 401, // Aqua tail 224 // Megahorn }, 0x05060708, 2, // Brave BattleTowerPokemon4.PackIVs(31, 31, 31, 10, 10, 20), new byte[] { 248, 252, 10, 0, 0, 0 }, 0, Languages.English, 116, // Solid rock 255, new EncodedString4("Rhyperior", 22) ); record.Party[2] = new BattleTowerPokemon4( 462, // Magnezone 268, // Expert belt new ushort[] { 237, // Hidden power 430, // Flash cannon 85, // Thunderbolt 393 // Magnet rise }, 0x05060708, 17, // Quiet BattleTowerPokemon4.PackIVs(31, 10, 31, 10, 31, 20), new byte[] { 252, 0, 6, 0, 252, 0 }, 0, Languages.English, 42, // Magnet pull 255, new EncodedString4("Magnezone", 22) ); record.Profile = new BattleTowerProfile4( new EncodedString4("Cassie", 16), Versions.Platinum, Languages.English, 0, 0, 0x05060708, new TrendyPhrase4(2, 3, 1146, 65535), // I want to go home with YOU... 2, 85 // Idol ); record.PhraseChallenged = new TrendyPhrase4(2, 3, 1146, 65535); record.PhraseWon = new TrendyPhrase4(4, 10, 1245, 65535); // Let's GO AHEAD! record.PhraseLost = new TrendyPhrase4(4, 11, 1348, 65535); // Want to DATE? break; case 6: record.Party[0] = new BattleTowerPokemon4( 65, // Alakazam 275, // Focus sash new ushort[] { 269, // Taunt 94, // Psychic 411, // Focus blast 324 // Signal beam }, 15, 15, // Modest BattleTowerPokemon4.PackIVs(31, 10, 20, 31, 31, 20), new byte[] { 6, 0, 0, 252, 252, 6 }, 0, Languages.English, 39, // Inner focus 255, new EncodedString4("Alakazam", 22) ); record.Party[1] = new BattleTowerPokemon4( 445, // Garchomp 270, // Life orb new ushort[] { 14, // Swords dance 89, // EQ 200, // Outrage 424 // Fire fang }, 0x06070809, 13, // Jolly BattleTowerPokemon4.PackIVs(31, 31, 20, 31, 10, 20), new byte[] { 6, 252, 0, 252, 0, 0 }, 0, Languages.English, 8, // Sand veil 255, new EncodedString4("Garchomp", 22) ); record.Party[2] = new BattleTowerPokemon4( 242, // Blissey 234, // Leftovers new ushort[] { 135, // Softboiled 104, // Double team 92, // Toxic 69 // Seismic toss }, 0x06070809, 5, // Bold BattleTowerPokemon4.PackIVs(31, 10, 31, 31, 20, 20), new byte[] { 252, 0, 252, 6, 0, 0 }, 0, Languages.English, 30, // Natural cure 255, new EncodedString4("Blissey", 22) ); record.Profile = new BattleTowerProfile4( new EncodedString4("Evan", 16), Versions.Platinum, Languages.English, 0, 0, 0x06070809, new TrendyPhrase4(0, 2, 566, 65535), // I'll battle with STRENGTH! 0, 24 // Ace trainer M ); record.PhraseChallenged = new TrendyPhrase4(0, 2, 566, 65535); record.PhraseWon = new TrendyPhrase4(1, 1, 1418, 65535); // I won! I won with SKILLFUL! record.PhraseLost = new TrendyPhrase4(2, 17, 1428, 65535); // The way I lost... It's like RARE... break; } return(record); }
public abstract ulong BattleTowerAddLeader4(BattleTowerRecord4 record);
public abstract ulong BattleTowerUpdateRecord4(BattleTowerRecord4 record);
public override void ProcessGamestatsRequest(byte[] data, MemoryStream response, string url, int pid, HttpContext context, GamestatsSession session) { 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 "/pokemondpds/common/setProfile.asp": { SessionManager.Remove(session); if (data.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(data, 0, profileBinary, 0, 100); TrainerProfile4 profile = new TrainerProfile4(pid, profileBinary); Database.Instance.GamestatsSetProfile4(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 "/pokemondpds/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? // todo: add a mostly null record to GtsProfiles4 if the // player is on D/P. We can still track last visit times // and gather trainer profile data from GTS activity. response.Write(new byte[] { 0x01, 0x00 }, 0, 2); break; // Called during startup and when you check your pokemon's status. case "/pokemondpds/worldexchange/result.asp": { SessionManager.Remove(session); /* After the above step(s) or performing any of * the tasks below other than searching, the game * makes a request to /pokemondpds/worldexchange/result.asp. * If the game has had a Pokémon sent to it via a trade, * the server responds with the entire encrypted Pokémon * save struct. Otherwise, if there is a Pokémon deposited * in the GTS, it responds with 0x0004; if not, it responds * with 0x0005. */ GtsRecord4 record = Database.Instance.GtsDataForUser4(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, 292); } else { // my existing pokemon is in the system, untraded response.Write(new byte[] { 0x04, 0x00 }, 0, 2); } // other responses: // 0-2 causes a BSOD but it flashes siezure. Scary // 3 causes it to be "checking GTS's status" forever. } break; // Called after result.asp returns 4 when you check your pokemon's status case "/pokemondpds/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?? GtsRecord4 record = Database.Instance.GtsDataForUser4(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, 292); } } break; // Called after result.asp returns an inbound pokemon record to delete it case "/pokemondpds/worldexchange/delete.asp": { SessionManager.Remove(session); GtsRecord4 record = Database.Instance.GtsDataForUser4(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.GtsDeletePokemon4(pid); if (success) { #if !DEBUG try { #endif Database.Instance.GtsLogTrade4(record, DateTime.UtcNow); #if !DEBUG } catch { } #endif 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 "/pokemondpds/worldexchange/return.asp": { SessionManager.Remove(session); GtsRecord4 record = Database.Instance.GtsDataForUser4(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.GtsDeletePokemon4(pid); if (success) { response.Write(new byte[] { 0x01, 0x00 }, 0, 2); } else { response.Write(new byte[] { 0x00, 0x00 }, 0, 2); } } } break; // Called when you deposit a pokemon into the system. case "/pokemondpds/worldexchange/post.asp": { if (data.Length != 292) { SessionManager.Remove(session); ShowError(context, 400); return; } // todo: add transaction if (Database.Instance.GtsDataForUser4(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[292]; Array.Copy(data, 0, recordBinary, 0, 292); GtsRecord4 record = new GtsRecord4(recordBinary); if (!record.Validate()) { // hack check failed 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 "/pokemondpds/worldexchange/post_finish.asp": { SessionManager.Remove(session); if (data.Length != 8) { ShowError(context, 400); return; } // todo: these _finish requests seem to come with a magic number of 4 bytes // at offset 0. Find out what this is supposed to do and how to validate it. // find a matching session which contains our record GamestatsSession prevSession = SessionManager.FindSession(pid, "/pokemondpds/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 GtsRecord4); GtsRecord4 record = (GtsRecord4)prevSession.Tag; if (Database.Instance.GtsDepositPokemon4(record)) { 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 "/pokemondpds/worldexchange/search.asp": { SessionManager.Remove(session); if (data.Length < 7 || data.Length > 8) { ShowError(context, 400); return; } ushort species = BitConverter.ToUInt16(data, 0); if (species < 1) { ShowError(context, 400); return; } int resultsCount = (int)data[6]; if (resultsCount < 1) { break; // optimize away requests for no rows } Genders gender = (Genders)data[2]; byte minLevel = data[3]; byte maxLevel = data[4]; // byte 5 unknown byte country = 0; if (data.Length > 7) { country = data[7]; } if (resultsCount > 7) { resultsCount = 7; // stop DDOS } GtsRecord4[] records = Database.Instance.GtsSearch4(pid, species, gender, minLevel, maxLevel, country, resultsCount); foreach (GtsRecord4 record in records) { response.Write(record.Save(), 0, 292); } } break; // the exchange request uploads a record of the exchangee pokemon // plus the desired PID to trade for at the very end. case "/pokemondpds/worldexchange/exchange.asp": { if (data.Length != 296) { SessionManager.Remove(session); ShowError(context, 400); return; } byte[] uploadData = new byte[292]; Array.Copy(data, 0, uploadData, 0, 292); GtsRecord4 upload = new GtsRecord4(uploadData); upload.IsExchanged = 0; int targetPid = BitConverter.ToInt32(data, 292); GtsRecord4 result = Database.Instance.GtsDataForUser4(targetPid); if (result == null || result.IsExchanged != 0) { // Pokémon is traded (or was never here to begin with) // todo: I only checked this on GenV. Check that this // is the correct response on GenIV. 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 GtsRecord4[2]; tag[0] = upload; tag[1] = result; session.Tag = tag; GtsRecord4 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.) // I also have a hunch that failure to send the exchange_finish request // is what causes the notorious GTS glitch where a pokemon is listed // under the wrong species and you can't trade it response.Write(result.Save(), 0, 292); } break; case "/pokemondpds/worldexchange/exchange_finish.asp": { SessionManager.Remove(session); if (data.Length != 8) { ShowError(context, 400); return; } // find a matching session which contains our record GamestatsSession prevSession = SessionManager.FindSession(pid, "/pokemondpds/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 GtsRecord4[]); GtsRecord4[] tag = (GtsRecord4[])prevSession.Tag; AssertHelper.Assert(tag.Length == 2); AssertHelper.Assert(tag[0] is GtsRecord4); AssertHelper.Assert(tag[0] is GtsRecord4); GtsRecord4 upload = (GtsRecord4)tag[0]; GtsRecord4 result = (GtsRecord4)tag[1]; if (Database.Instance.GtsTradePokemon4(upload, result)) { #if !DEBUG try { #endif Database.Instance.GtsLogTrade4(result, null); #if !DEBUG } catch { } #endif response.Write(new byte[] { 0x01, 0x00 }, 0, 2); } else { response.Write(new byte[] { 0x00, 0x00 }, 0, 2); } } break; #endregion #region Battle Tower case "/pokemondpds/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 "/pokemondpds/battletower/roomnum.asp": SessionManager.Remove(session); //byte rank = data[0x00]; response.Write(new byte[] { 0x32, 0x00 }, 0, 2); break; case "/pokemondpds/battletower/download.asp": { SessionManager.Remove(session); if (data.Length != 2) { ShowError(context, 400); return; } byte rank = data[0x00]; byte roomNum = data[0x01]; if (rank > 9 || roomNum > 49) { ShowError(context, 400); return; } BattleTowerRecord4[] opponents = Database.Instance.BattleTowerGetOpponents4(pid, rank, roomNum); BattleTowerProfile4[] leaders = Database.Instance.BattleTowerGetLeaders4(rank, roomNum); BattleTowerRecord4[] fakeOpponents = FakeOpponentGenerator4.GenerateFakeOpponents(7 - opponents.Length); foreach (BattleTowerRecord4 record in fakeOpponents) { response.Write(record.Save(), 0, 228); } foreach (BattleTowerRecord4 record in opponents) { response.Write(record.Save(), 0, 228); } foreach (BattleTowerProfile4 leader in leaders) { response.Write(leader.Save(), 0, 34); } // This is completely insane. The game crashes when you // use Check Leaders if the response arrives too fast, // so we artificially delay it. // todo: This is slower than it needs to be if the // database is slow to respond. We should sleep for a // variable time based on when the request was received. Thread.Sleep(500); } break; case "/pokemondpds/battletower/upload.asp": { SessionManager.Remove(session); if (data.Length != 239) { ShowError(context, 400); return; } BattleTowerRecord4 record = new BattleTowerRecord4(data, 0); record.Rank = data[0xe4]; record.RoomNum = data[0xe5]; record.BattlesWon = data[0xe6]; record.Unknown5 = BitConverter.ToUInt64(data, 0xe7); // todo: Do we want to store their record anyway if they lost the first round? if (record.BattlesWon > 0) { Database.Instance.BattleTowerUpdateRecord4(record); } if (record.BattlesWon == 7) { Database.Instance.BattleTowerAddLeader4(record); } response.Write(new byte[] { 0x01, 0x00 }, 0, 2); } break; #endregion } }
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; Pokedex pokedex = new Pokedex(db, false); 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(pokedex, 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(pokedex); 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(pokedex, 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(pokedex); 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(); }
public override void ProcessGamestatsRequest(byte[] data, MemoryStream response, string url, int pid, HttpContext context, GamestatsSession session) { { BanStatus ban = BanHelper.GetBanStatus(pid, IpAddressHelper.GetIpAddress(context.Request), Generations.Generation4); if (ban != null && ban.Level > BanLevels.Restricted) { ShowError(context, 403); return; } } 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 "/pokemondpds/common/setProfile.asp": { SessionManager.Remove(session); if (data.Length != 100) { ShowError(context, 400); return; } #if !DEBUG try { #endif byte[] profileBinary = new byte[100]; Array.Copy(data, 0, profileBinary, 0, 100); TrainerProfile4 profile = new TrainerProfile4(pid, profileBinary, IpAddressHelper.GetIpAddress(context.Request)); Database.Instance.GamestatsSetProfile4(profile); #if !DEBUG } catch { } #endif short clientSecret = BitConverter.ToInt16(data, 96); short mailSecret = BitConverter.ToInt16(data, 98); // response: // 4 bytes of response code A // 4 bytes of response code B // Response code A values: // 0: Continues normally. // 1: The data was corrupted. It could not be sent. // 2: The server is undergoing maintenance. Please connect again later. // 3: BSOD if (mailSecret == -1) { // Register wii mail // Response code B values: // 0: There was a communication error. // 1: The Registration Code has been sent to your Wii console. Please enter the Registration Code. // 2: There was an error while attempting to send an authentication Wii message. // 3: There was a communication error. // 4: BSOD response.Write(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00 }, 0, 8); } else if (mailSecret != 0 || clientSecret != 0) { // Send wii mail confirmation code OR GTS when mail is configured (we can't tell them apart T__T) // (todo: We could use database to tell them apart. // If the previously stored profile has mailSecret == -1 then this is a wii mail confirmation. // If the previously stored profile has mailSecret == this mailSecret then this is GTS.) // Response code B values: // 0: Your Wii Number has been registered. // 1: There was a communication error. // 2: There was a communication error. // 3: Incorrect Registration Code. // 4: BSOD response.Write(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 0, 8); } else { // GTS // Response code B values: // 0: Continues normally // 1: There was a communication error. // 2: There was a communication error. // 3: There was a Wii message authentication error. // 4: BSOD response.Write(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 0, 8); } } break; #endregion #region GTS // Called during startup. Unknown purpose. case "/pokemondpds/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? var ip = IpAddressHelper.GetIpAddress(context.Request); Database.Instance.GamestatsBumpProfile4(pid, ip); response.Write(new byte[] { 0x01, 0x00 }, 0, 2); } break; // Called during startup and when you check your pokemon's status. case "/pokemondpds/worldexchange/result.asp": { SessionManager.Remove(session); /* After the above step(s) or performing any of * the tasks below other than searching, the game * makes a request to /pokemondpds/worldexchange/result.asp. * If the game has had a Pokémon sent to it via a trade, * the server responds with the entire encrypted Pokémon * save struct. Otherwise, if there is a Pokémon deposited * in the GTS, it responds with 0x0004; if not, it responds * with 0x0005. */ GtsRecord4 record = Database.Instance.GtsDataForUser4(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, 292); } else { // my existing pokemon is in the system, untraded response.Write(new byte[] { 0x04, 0x00 }, 0, 2); } // other responses: // 0-2 causes a BSOD but it flashes siezure. Scary // 3 causes it to be "checking GTS's status" forever. // 6 is also the flashy BSOD. So probably all invalid values do that. } break; // Called after result.asp returns 4 when you check your pokemon's status case "/pokemondpds/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?? GtsRecord4 record = Database.Instance.GtsDataForUser4(pokedex, pid); if (record == null) { // No pokemon in the system // what do here? ShowError(context, 403); return; } else { // just write the record whether traded or not... response.Write(record.Save(), 0, 292); } } break; // Called after result.asp returns an inbound pokemon record to delete it case "/pokemondpds/worldexchange/delete.asp": { SessionManager.Remove(session); GtsRecord4 record = Database.Instance.GtsDataForUser4(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.GtsDeletePokemon4(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 "/pokemondpds/worldexchange/return.asp": { SessionManager.Remove(session); GtsRecord4 record = Database.Instance.GtsDataForUser4(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.GtsDeletePokemon4(pid); if (success) { response.Write(new byte[] { 0x01, 0x00 }, 0, 2); } else { response.Write(new byte[] { 0x00, 0x00 }, 0, 2); } } } break; // Called when you deposit a pokemon into the system. case "/pokemondpds/worldexchange/post.asp": { if (data.Length != 292) { SessionManager.Remove(session); ShowError(context, 400); return; } // todo: add transaction if (Database.Instance.GtsDataForUser4(pokedex, pid) != null) { // there's already a pokemon inside. // Force the player out so they'll recheck its status. SessionManager.Remove(session); response.Write(new byte[] { 0x0e, 0x00 }, 0, 2); break; } // keep the record in memory while we wait for post_finish.asp request byte[] recordBinary = new byte[292]; Array.Copy(data, 0, recordBinary, 0, 292); GtsRecord4 record = new GtsRecord4(pokedex, recordBinary); record.IsExchanged = 0; if (!record.Validate()) { // hack check failed SessionManager.Remove(session); // responses: // 0x00: Appears to start depositing? todo: test if this code leads to a normal deposit. // 0x01: successful deposit // 0x02-0x03: Communication error... // 0x04-0x06: bsod // 0x07: The GTS is very crowded now. Please try again later. (and it boots you!) // 0x08-0x0d: That Pokémon may not be offered for trade! // 0x0e: You were disconnected from the GTS. Returning to the reception counter. // 0x0f: Blue screen of death 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.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 "/pokemondpds/worldexchange/post_finish.asp": { SessionManager.Remove(session); if (data.Length != 8) { ShowError(context, 400); return; } // todo: these _finish requests seem to come with a magic number of 4 bytes // at offset 0. Find out what this is supposed to do and how to validate it. // find a matching session which contains our record GamestatsSession prevSession = SessionManager.FindSession(pid, "/pokemondpds/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 GtsRecord4); GtsRecord4 record = (GtsRecord4)prevSession.Tag; if (Database.Instance.GtsDepositPokemon4(record)) { 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 "/pokemondpds/worldexchange/search.asp": { SessionManager.Remove(session); if (data.Length < 7 || data.Length > 8) { ShowError(context, 400); return; } ushort species = BitConverter.ToUInt16(data, 0); if (species < 1) { ShowError(context, 400); return; } int resultsCount = (int)data[6]; if (resultsCount < 1) { break; // optimize away requests for no rows } Genders gender = (Genders)data[2]; byte minLevel = data[3]; byte maxLevel = data[4]; // byte 5 unknown byte country = 0; if (data.Length > 7) { country = data[7]; } if (resultsCount > 7) { resultsCount = 7; // stop DDOS } GtsRecord4[] records = Database.Instance.GtsSearch4(pokedex, pid, species, gender, minLevel, maxLevel, country, resultsCount); foreach (GtsRecord4 record in records) { response.Write(record.Save(), 0, 292); } Database.Instance.GtsSetLastSearch4(pid); } break; // the exchange request uploads a record of the exchangee pokemon // plus the desired PID to trade for at the very end. case "/pokemondpds/worldexchange/exchange.asp": { if (data.Length != 296) { SessionManager.Remove(session); ShowError(context, 400); return; } byte[] uploadData = new byte[292]; Array.Copy(data, 0, uploadData, 0, 292); GtsRecord4 upload = new GtsRecord4(pokedex, uploadData); upload.IsExchanged = 0; int targetPid = BitConverter.ToInt32(data, 292); GtsRecord4 result = Database.Instance.GtsDataForUser4(pokedex, targetPid); DateTime? searchTime = Database.Instance.GtsGetLastSearch4(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); // responses: // 0x00-0x01: bsod // 0x02: Unfortunately, it was traded to another Trainer. // 0x03-0x07: bsod // 0x08-0x0d: That Pokémon may not be offered for trade! // 0x0e: You were disconnected from the GTS. Returning to the reception counter. // 0x0f: bsod response.Write(new byte[] { 0x0c, 0x00 }, 0, 2); return; } // uncomment these two lines if you're replaying gamestats requests and need to skip the random token //session = new GamestatsSession(this.GameId, this.Salt, pid, "/pokemondpds/worldexchange/exchange.asp"); //SessionManager.Add(session); object[] tag = new GtsRecord4[2]; tag[0] = upload; tag[1] = result; session.Tag = tag; GtsRecord4 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.) // I also have a hunch that failure to send the exchange_finish request // is what causes the notorious GTS glitch where a pokemon is listed // under the wrong species and you can't trade it response.Write(result.Save(), 0, 292); } break; case "/pokemondpds/worldexchange/exchange_finish.asp": { //if (session != null) SessionManager.Remove(session); if (data.Length != 8) { ShowError(context, 400); return; } // find a matching session which contains our record GamestatsSession prevSession = SessionManager.FindSession(pid, "/pokemondpds/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 GtsRecord4[]); GtsRecord4[] tag = (GtsRecord4[])prevSession.Tag; AssertHelper.Assert(tag.Length == 2); GtsRecord4 upload = tag[0]; GtsRecord4 result = tag[1]; if (Database.Instance.GtsTradePokemon4(upload, result, pid)) { response.Write(new byte[] { 0x01, 0x00 }, 0, 2); } else { response.Write(new byte[] { 0x00, 0x00 }, 0, 2); } } break; #endregion #region Battle Tower case "/pokemondpds/battletower/info.asp": SessionManager.Remove(session); // Probably an availability/status code. Database.Instance.GamestatsBumpProfile4(pid, IpAddressHelper.GetIpAddress(context.Request)); // Response codes: // 0x00: BSOD // 0x01: Continues normally // 0x02: BSOD // 0x03: The Wi-Fi Battle Tower is currently undergoing maintenance. Please try again later. // 0x04: The Wi-Fi Battle Tower is very crowded. Please try again later. // 0x05: Unable to connect to the Wi-Fi Battle Tower. Returning to the reception counter. // 0x06: BSOD response.Write(new byte[] { 0x01, 0x00 }, 0, 2); break; case "/pokemondpds/battletower/roomnum.asp": SessionManager.Remove(session); //byte rank = data[0x00]; response.Write(new byte[] { 0x32, 0x00 }, 0, 2); break; case "/pokemondpds/battletower/download.asp": { SessionManager.Remove(session); if (data.Length != 2) { ShowError(context, 400); return; } byte rank = data[0x00]; byte roomNum = data[0x01]; if (rank > 9 || roomNum > 49) { ShowError(context, 400); return; } BattleTowerRecord4[] opponents = Database.Instance.BattleTowerGetOpponents4(pokedex, pid, rank, roomNum); BattleTowerProfile4[] leaders = Database.Instance.BattleTowerGetLeaders4(pokedex, rank, roomNum); BattleTowerRecord4[] fakeOpponents = FakeOpponentGenerator4.GenerateFakeOpponents(7 - opponents.Length); foreach (BattleTowerRecord4 record in fakeOpponents) { response.Write(record.Save(), 0, 228); } foreach (BattleTowerRecord4 record in opponents) { response.Write(record.Save(), 0, 228); } foreach (BattleTowerProfile4 leader in leaders) { response.Write(leader.Save(), 0, 34); } if (leaders.Length < 30) { byte[] fakeLeader = new BattleTowerProfile4 ( new EncodedString4("-----", 16), Versions.Platinum, Languages.English, 0, 0, 0x00000000, new TrendyPhrase4(5, 0, 0, 0), 0, 0 ).Save(); for (int x = leaders.Length; x < 30; x++) { response.Write(fakeLeader, 0, 34); } } // This is completely insane. The game crashes when you // use Check Leaders if the response arrives too fast, // so we artificially delay it. // todo: This is slower than it needs to be if the // database is slow to respond. We should sleep for a // variable time based on when the request was received. Thread.Sleep(500); } break; case "/pokemondpds/battletower/upload.asp": { SessionManager.Remove(session); if (data.Length != 239) { ShowError(context, 400); return; } BattleTowerRecord4 record = new BattleTowerRecord4(pokedex, data, 0); record.Rank = data[0xe4]; record.RoomNum = data[0xe5]; record.BattlesWon = data[0xe6]; record.Unknown5 = BitConverter.ToUInt64(data, 0xe7); record.PID = pid; foreach (var p in record.Party) { if (!p.Validate().IsValid) { // Tell the client it was successful so they don't keep retrying. response.Write(new byte[] { 0x01, 0x00 }, 0, 2); return; } } // todo: Do we want to store their record anyway if they lost the first round? if (record.BattlesWon > 0) { Database.Instance.BattleTowerUpdateRecord4(record); } if (record.BattlesWon == 7) { Database.Instance.BattleTowerAddLeader4(record); } // List of responses: // 0x00: BSOD // 0x01: Uploads successfully // 0x02: That number cannot be specified for the Wi-Fi Battle Tower. // 0x03: BSOD // 0x04: The Wi-Fi Battle Tower is very crowded. Please try again later. // 0x05: Unable to connect to the Wi-Fi Battle Tower. Returning to the reception counter. // 0x06: BSOD // 0x07: BSOD // 0x08: BSOD response.Write(new byte[] { 0x01, 0x00 }, 0, 2); } break; #endregion } }