WIP of GTS database logic

This commit is contained in:
Greg Edwards
2014-04-27 00:55:25 -04:00
parent 7e6fdb4602
commit cc2a3365d7
15 changed files with 567 additions and 21 deletions

24
gts/BoxUploadDecode.aspx Normal file
View File

@@ -0,0 +1,24 @@
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="BoxUploadDecode.aspx.cs" Inherits="PokeFoundations.GTS.BoxUploadDecode" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<p>Please upload your captured TCP request here:</p>
<div>
<asp:FileUpload ID="fuRequest" runat="server" />
<asp:Button ID="btnUpload" Text="Upload" runat="server" />
</div>
<div>
<asp:Literal ID="litDecrypted" runat="server" />
</div>
</form>
</body>
</html>

View File

@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Text;
namespace PokeFoundations.GTS
{
public partial class BoxUploadDecode : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
byte data = 0x00;
StringBuilder builder = new StringBuilder();
for (int x = 0; x < 512; x++)
{
data = runRng(data);
builder.Append(data.ToString("x2"));
builder.Append(" ");
if (x % 16 == 15) builder.Append("<br />");
}
litDecrypted.Text = builder.ToString();
}
private byte runRng(byte data)
{
return (byte)((data + 0x4f) * 0x1c);
}
}
}

51
gts/BoxUploadDecode.aspx.designer.cs generated Normal file
View File

@@ -0,0 +1,51 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace PokeFoundations.GTS {
public partial class BoxUploadDecode {
/// <summary>
/// form1 control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.HtmlControls.HtmlForm form1;
/// <summary>
/// fuRequest control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.FileUpload fuRequest;
/// <summary>
/// btnUpload control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.Button btnUpload;
/// <summary>
/// litDecrypted control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.Literal litDecrypted;
}
}

View File

@@ -4,6 +4,5 @@
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="cpMain" runat="server">
customGTS<br />
<asp:Literal ID="litDebug" runat="server" />
</asp:Content>

View File

@@ -34,6 +34,9 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
<Reference Include="mysql.data">
<HintPath>..\library\lib\mysql.data.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Core" />
@@ -51,6 +54,7 @@
<Reference Include="System.Web.ApplicationServices" />
</ItemGroup>
<ItemGroup>
<Content Include="BoxUploadDecode.aspx" />
<Content Include="Default.aspx" />
<Content Include="Global.asax" />
<Content Include="Web.config" />
@@ -62,6 +66,13 @@
</Content>
</ItemGroup>
<ItemGroup>
<Compile Include="BoxUploadDecode.aspx.cs">
<DependentUpon>BoxUploadDecode.aspx</DependentUpon>
<SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="BoxUploadDecode.aspx.designer.cs">
<DependentUpon>BoxUploadDecode.aspx</DependentUpon>
</Compile>
<Compile Include="pokemondpds.ashx.cs">
<DependentUpon>pokemondpds.ashx</DependentUpon>
</Compile>
@@ -100,6 +111,12 @@
<ItemGroup>
<Content Include="pokemondpds.ashx" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\library\Library.csproj">
<Project>{408EFC7E-C6B0-4160-8628-2679E34385CE}</Project>
<Name>Library</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />
<ProjectExtensions>

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.SessionState;
using PokeFoundations.Data;
namespace PokeFoundations.GTS
{
@@ -41,8 +42,9 @@ namespace PokeFoundations.GTS
context.Request.QueryString["data"].Length < 12)
{
// arguments missing, partial check for data length.
// (require data to hold at least 7 bytes.
// In reality, it must hold at least 8.)
// (Here, we require data to hold at least 7 bytes.
// In reality, it must hold at least 8, which is checked
// for below after decoding)
Error400(context);
return;
}
@@ -63,6 +65,8 @@ namespace PokeFoundations.GTS
if (data.Length < 4)
{
// data too short to contain a pid
// We check for 4 bytes, not 8, since the decrypt seed
// isn't included in DecryptData's result.
Error400(context);
return;
}
@@ -87,13 +91,34 @@ namespace PokeFoundations.GTS
{
default:
// 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;
case "/worldexchange/info.asp":
context.Response.Write("It worked");
context.Response.OutputStream.Write(new byte[] { 0x01, 0x00 }, 0, 2);
break;
case "/common/setProfile.asp":
// todo: Figure out what fun stuff is contained in this blob!
context.Response.OutputStream.Write(new byte[]
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
0, 8);
break;
case "/worldexchange/result.asp":
{
}
break;
}
}
else

View File

@@ -30,10 +30,19 @@ namespace PokeFoundations.GTS
Dictionary<String, GtsSession4> sessions = context.AllSessions4();
DateTime now = DateTime.UtcNow;
foreach (KeyValuePair<String, GtsSession4> session in sessions)
lock (sessions)
{
if (session.Value.ExpiryDate < now) sessions.Remove(session.Key);
Queue<String> toRemove = new Queue<String>();
foreach (KeyValuePair<String, GtsSession4> session in sessions)
{
if (session.Value.ExpiryDate < now) toRemove.Enqueue(session.Key);
}
while (toRemove.Count > 0)
{
sessions.Remove(toRemove.Dequeue());
}
}
}
}
}

View File

@@ -30,7 +30,7 @@ namespace PokeFoundations.GTS
byte[] data = new byte[longToken.Length];
MemoryStream stream = new MemoryStream(data);
StreamWriter writer = new StreamWriter(stream);
writer.Write(longToken);
writer.Write(longToken); // fixme: this throws an OutOfBoundsException if the passed token contains non-ascii.
writer.Flush();
return m_sha1.ComputeHash(data).ToHexStringLower();

View File

@@ -104,7 +104,7 @@ namespace PokeFoundations.GTS
for (int x = 0; x < token.Length; x++)
{
// tokens have 62 chars: 0-9, A-Z, and a-z
// tokens have 62 possible chars: 0-9, A-Z, and a-z
m_rng.GetBytes(data);
uint rand = BitConverter.ToUInt32(data, 0) % 62u;

View File

@@ -3,22 +3,40 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using PokeFoundations.Structures;
namespace PokeFoundations.Data
{
public abstract class DataAbstract
{
public static String SqlSanitize(String s)
{
return SqlSanitize(s, "");
}
#region Initialization
private static DataAbstract m_instance;
public static String SqlSanitize(String s, String newChar)
public static DataAbstract Instance
{
String result = s.Replace("\'", newChar).Replace("[", newChar).Replace("]", newChar).Replace("`", newChar);
int x = result.IndexOf("--");
if (x != -1) result = result.Substring(0, x);
return result;
get
{
if (m_instance == null)
{
m_instance = new DataMysql();
}
return m_instance;
}
}
#endregion
#region Utility
#endregion
#region GTS
public abstract GtsDatagram4 GtsDataForUser4(int pid);
public abstract void GtsDepositPokemon4(GtsDatagram4 datagram);
public abstract void GtsDeletePokemon4(int pid);
public abstract void GtsTradePokemon4(int pidSrc, int pidDest);
#endregion
}
}

View File

@@ -4,14 +4,173 @@ using System.Linq;
using System.Text;
using System.Data;
using MySql.Data.MySqlClient;
using PokeFoundations.Structures;
using PokeFoundations.Support;
namespace PokeFoundations.Data
{
public class DataMysql : DataAbstract
{
#region Initialization
private MySqlConnection CreateConnection()
{
return new MySqlConnection("Server=localhost;Database=pfound;User ID=pfound;Password=goldeen need log;Pooling=true;");
// hard-coded for now, will need to add configuration later
return new MySqlConnection("Server=10.211.55.2;Database=gts;User ID=gts;Password=gts;Pooling=true;");
}
#endregion
#region Utility
public static String SqlSanitize(String s)
{
return SqlSanitize(s, "");
}
public static String SqlSanitize(String s, String newChar)
{
String result = s.Replace("\'", newChar).Replace("[", newChar).Replace("]", newChar).Replace("`", newChar);
int x = result.IndexOf("--");
if (x != -1) result = result.Substring(0, x);
return result;
}
#endregion
#region GTS
public override GtsDatagram4 GtsDataForUser4(int pid)
{
using (MySqlConnection db = CreateConnection())
{
db.Open();
MySqlDataReader reader = (MySqlDataReader)db.ExecuteReader("SELECT Data, Species, Gender, Level, " +
"RequestedSpecies, RequestedGender, RequestedMinLevel, RequestedMaxLevel, " +
"Unknown1, TrainerGender, Unknown2, TimeDeposited, TimeWithdrawn, pid, " +
"TrainerName, TrainerOT, TrainerCountry, TrainerRegion, TrainerClass, " +
"IsExchanged, TrainerVersion, TrainerLanguage FROM GtsPokemon4 WHERE pid = @pid",
new MySqlParameter("@pid", pid));
if (!reader.Read()) return null;
GtsDatagram4 result = new GtsDatagram4();
byte[] data = new byte[236];
reader.GetBytes(0, 0, data, 0, 236);
result.Data = data;
data = null;
result.Species = reader.GetUInt16(1);
result.Gender = (Genders)reader.GetByte(2);
result.Level = reader.GetByte(3);
result.RequestedSpecies = reader.GetUInt16(4);
result.RequestedGender = (Genders)reader.GetByte(5);
result.RequestedMinLevel = reader.GetByte(6);
result.RequestedMaxLevel = reader.GetByte(7);
result.Unknown1 = reader.GetByte(8);
result.TrainerGender = (GtsTrainerGenders)reader.GetByte(9);
result.Unknown2 = reader.GetByte(10);
result.TimeDeposited = reader.GetDateTime(11);
result.TimeWithdrawn = reader.GetDateTime(12);
result.PID = reader.GetInt32(13);
data = new byte[16];
reader.GetBytes(14, 0, data, 0, 16);
result.TrainerName = data;
data = null;
result.TrainerOT = reader.GetUInt16(15);
result.TrainerCountry = reader.GetByte(16);
result.TrainerRegion = reader.GetByte(17);
result.TrainerClass = reader.GetByte(18);
result.IsExchanged = reader.GetByte(19);
result.TrainerVersion = reader.GetByte(20);
result.TrainerLanguage = reader.GetByte(21);
#if DEBUG
AssertHelper.Equals(result.PID, pid);
#endif
reader.Close();
return result;
}
}
public override bool GtsDepositPokemon4(GtsDatagram4 datagram)
{
if (datagram.Data.Length != 236) throw new FormatException("pkm data must be 236 bytes.");
if (datagram.TrainerName.Length != 16) throw new FormatException("Trainer name must be 16 bytes.");
// note that IsTraded being true in the datagram is not an error condition
// since it might have use later on. You should check for this in the upload handler.
using (MySqlConnection db = CreateConnection())
{
db.Open();
/*
MySqlDataReader reader = (MySqlDataReader)db.ExecuteReader("SELECT Data, Species, Gender, Level, " +
"RequestedSpecies, RequestedGender, RequestedMinLevel, RequestedMaxLevel, " +
"Unknown1, TrainerGender, Unknown2, TimeDeposited, TimeWithdrawn, pid, " +
"TrainerName, TrainerOT, TrainerCountry, TrainerRegion, TrainerClass, " +
"IsExchanged, TrainerVersion, TrainerLanguage FROM GtsPokemon4 WHERE pid = @pid",
new MySqlParameter("@pid", pid));
*
* */
MySqlTransaction tran = db.BeginTransaction();
int count = (int)tran.ExecuteScalar("SELECT Count(*) FROM GtsPokemon4 WHERE pid = @pid",
new MySqlParameter("@pid", datagram.PID));
if (count > 0)
{
// This player already has a pokemon in the system.
// we can possibly allow multiples under some future conditions
tran.Rollback();
return false;
}
tran.ExecuteNonQuery("INSERT INTO GtsPokemon4 " +
"(Data, Species, Gender, Level, RequestedSpecies, RequestedGender, " +
"RequestedMinLevel, RequestedMaxLevel, Unknown1, TrainerGender, " +
"Unknown2, TimeDeposited, TimeWithdrawn, pid, TrainerName, TrainerOT, " +
"TrainerCountry, TrainerRegion, TrainerClass, IsExchanged, TrainerVersion, " +
"TrainerLanguage) " +
"VALUES (@Data, @Species, @Gender, @Level, @RequestedSpecies, " +
"@RequestedGender, @RequestedMinLevel, @RequestedMaxLevel, @Unknown1, " +
"@TrainerGender, @Unknown2, @TimeDeposited, @TimeWithdrawn, @pid, " +
"@TrainerName, @TrainerOT, @TrainerCountry, @TrainerRegion, @TrainerClass, " +
"@IsExchanged, @TrainerVersion, @TrainerLanguage)",
new MySqlParameter("@Data", datagram.Data),
new MySqlParameter("@Species", datagram.Species),
new MySqlParameter("@Gender", (byte)datagram.Gender),
new MySqlParameter("@Level", datagram.Level),
new MySqlParameter("@RequestedSpecies", datagram.RequestedSpecies),
new MySqlParameter("@RequestedGender", (byte)datagram.RequestedGender),
new MySqlParameter("@RequestedMinLevel", datagram.RequestedMinLevel),
new MySqlParameter("@RequestedMaxLevel", datagram.RequestedMaxLevel),
new MySqlParameter("@Unknown1", datagram.Unknown1),
new MySqlParameter("@TrainerGender", (byte)datagram.TrainerGender),
new MySqlParameter("@Unknown2", datagram.Unknown2),
new MySqlParameter("@TimeDeposited", datagram.TimeDeposited),
new MySqlParameter("@TimeWithdrawn", datagram.TimeWithdrawn),
new MySqlParameter("@pid", datagram.PID),
new MySqlParameter("@TrainerName", datagram.TrainerName),
new MySqlParameter("@TrainerOT", datagram.TrainerOT),
new MySqlParameter("@TrainerCountry", datagram.TrainerCountry),
new MySqlParameter("@TrainerRegion", datagram.TrainerRegion),
new MySqlParameter("@TrainerClass", datagram.TrainerClass),
new MySqlParameter("@IsExchanged", datagram.IsExchanged),
new MySqlParameter("@TrainerVersion", datagram.TrainerVersion),
new MySqlParameter("@TrainerLanguage", datagram.TrainerLanguage));
tran.Commit();
return true;
}
}
public override void GtsDeletePokemon4(int pid)
{
}
public override void GtsTradePokemon4(int pidSrc, int pidDest)
{
}
#endregion
}
}

View File

@@ -53,6 +53,10 @@
<Compile Include="Data\DataSqlite.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Structures\Enums.cs" />
<Compile Include="Structures\GtsDatagram4.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="Support\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

View File

@@ -11,7 +11,8 @@ namespace PokeFoundations.Structures
Generation2 = 2,
Generation3 = 3,
Generation4 = 4,
Generation5 = 5
Generation5 = 5,
Generation6 = 6
}
[FlagsAttribute]
@@ -21,10 +22,11 @@ namespace PokeFoundations.Structures
Generation2 = 2,
Generation3 = 4,
Generation4 = 8,
Generation5 = 16
Generation5 = 16,
Generation6 = 32
}
public enum EvolutionMethods
public enum EvolutionTriggers
{
Level = 1,
Item = 2,
@@ -65,4 +67,18 @@ namespace PokeFoundations.Structures
Erratic = 5,
Fluctuating = 6
}
public enum Genders : byte
{
Male = 1,
Female = 2,
None = 3,
Either = 3
}
public enum GtsTrainerGenders : byte
{
Male = 0,
Female = 1
}
}

View File

@@ -0,0 +1,140 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.IO;
namespace PokeFoundations.Structures
{
/// <summary>
/// Structure used to represent Pokémon on the GTS in Generation IV.
/// Includes a Pokémon box structure and metadata related to the trainer
/// and request.
/// </summary>
[Serializable()]
public class GtsDatagram4 : ISerializable
{
/// <summary>
/// Obfuscated Pokémon (pkm) data. 236 bytes
/// </summary>
public byte[] Data;
/// <summary>
/// National Dex species number
/// </summary>
public ushort Species;
/// <summary>
/// Pokémon gender
/// </summary>
public Genders Gender;
/// <summary>
/// Pokémon level
/// </summary>
public byte Level;
/// <summary>
/// Requested National Dex species number
/// </summary>
public ushort RequestedSpecies;
public Genders RequestedGender;
public byte RequestedMinLevel;
public byte RequestedMaxLevel;
public byte Unknown1;
public GtsTrainerGenders TrainerGender;
public byte Unknown2;
public DateTime TimeDeposited;
public DateTime TimeWithdrawn;
/// <summary>
/// User ID of the player (not Personality Value)
/// </summary>
public int PID;
/// <summary>
/// 16 bytes
/// </summary>
public byte[] TrainerName; // todo: decode/encode to unicode and provide a String wrapper.
public ushort TrainerOT;
public byte TrainerCountry;
public byte TrainerRegion;
public byte TrainerClass;
public byte IsExchanged;
public byte TrainerVersion;
public byte TrainerLanguage;
public byte[] Save()
{
// todo: enclose in properties and validate these when assigning.
if (Data.Length != 0xEC) throw new FormatException("PKM length is incorrect");
if (TrainerName.Length != 0x10) throw new FormatException("Trainer name length is incorrect");
byte[] data = new byte[292];
MemoryStream s = new MemoryStream(data);
s.Write(Data, 0, 0xEC);
s.Write(BitConverter.GetBytes(Species), 0, 2);
s.WriteByte((byte)Gender);
s.WriteByte(Level);
s.Write(BitConverter.GetBytes(RequestedSpecies), 0, 2);
s.WriteByte((byte)RequestedGender);
s.WriteByte(RequestedMinLevel);
s.WriteByte(RequestedMaxLevel);
s.WriteByte(Unknown1);
s.WriteByte((byte)TrainerGender);
s.WriteByte(Unknown2);
s.Write(BitConverter.GetBytes(TimeDeposited.ToBinary()), 0, 8);
s.Write(BitConverter.GetBytes(TimeWithdrawn.ToBinary()), 0, 8);
s.Write(BitConverter.GetBytes(PID), 0, 4);
s.Write(TrainerName, 0, 0x10);
s.Write(BitConverter.GetBytes(TrainerOT), 0, 2);
s.WriteByte(TrainerCountry);
s.WriteByte(TrainerRegion);
s.WriteByte(TrainerClass);
s.WriteByte(IsExchanged);
s.WriteByte(TrainerVersion);
s.WriteByte(TrainerLanguage);
s.Close();
return data;
}
public void Load(byte[] data)
{
if (data.Length != 292) throw new FormatException("GTS datagram length is incorrect.");
Data = new byte[0xEC];
Array.Copy(data, 0, Data, 0, 0xEC);
Species = BitConverter.ToUInt16(data, 0xEC);
Gender = (Genders)data[0xEE];
Level = data[0xEF];
RequestedSpecies = BitConverter.ToUInt16(data, 0xF0);
RequestedGender = (Genders)data[0xF2];
RequestedMinLevel = data[0xF3];
RequestedMaxLevel = data[0xF4];
Unknown1 = data[0xF5];
TrainerGender = (GtsTrainerGenders)data[0xF6];
Unknown2 = data[0xF7];
// todo: Figure out what the correct binary storage format is for
// official GTS timestamps
TimeDeposited = DateTime.FromBinary(BitConverter.ToInt64(data, 0xF8));
TimeWithdrawn = DateTime.FromBinary(BitConverter.ToInt64(data, 0x100));
PID = BitConverter.ToInt32(data, 0x108);
TrainerName = new byte[0x10];
Array.Copy(data, 0x10C, TrainerName, 0, 0x10);
TrainerOT = BitConverter.ToUInt16(data, 0x11C);
TrainerCountry = data[0x11E];
TrainerRegion = data[0x11F];
TrainerClass = data[0x120];
IsExchanged = data[0x121];
TrainerVersion = data[0x122];
TrainerLanguage = data[0x123];
}
}
}

View File

@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.IO;
namespace PokeFoundations.Support
{
public class AssertHelper
{
public static void Assert(bool condition, String message)
{
if (!condition) try
{
using (StreamWriter s = File.CreateText("assert.log"))
{
s.Write("Assert failed: ");
s.WriteLine(message);
s.Write("Date: ");
s.WriteLine(DateTime.Now.ToString("G"));
s.Write("Stack trace: ");
s.WriteLine(new StackTrace(true).ToString());
s.WriteLine();
}
}
catch (Exception)
{
// directory not found or no write permissions there
}
Debug.Assert(condition, message);
}
public static void Assert(bool condition)
{
Assert(condition, "Assert failed.");
}
public static void Unreachable()
{
Assert(false, "Unreachable code has been reached.");
}
public static void Equals<T>(T first, T second) where T : IEquatable<T>
{
Assert((IEquatable<T>)first == (IEquatable<T>)second, "Values are not equal.");
}
}
}