Most stuff really works!

This commit is contained in:
Greg Edwards 2014-04-28 01:29:36 -04:00
parent aa9e8942f9
commit 4716449659
7 changed files with 611 additions and 115 deletions

View File

@ -44,6 +44,8 @@
</providers>
</roleManager>
<customErrors mode="Off" />
</system.web>
<system.webServer>

View File

@ -23,6 +23,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>

View File

@ -4,6 +4,8 @@ using System.Linq;
using System.Web;
using System.Web.SessionState;
using PokeFoundations.Data;
using PokeFoundations.Structures;
using PokeFoundations.Support;
namespace PokeFoundations.GTS
{
@ -90,19 +92,27 @@ namespace PokeFoundations.GTS
switch (session.URL)
{
default:
sessions.Remove(session.Hash);
// 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":
sessions.Remove(session.Hash);
// todo: find out the meaning of this request.
// is it simply done to check whether the GTS is online?
context.Response.OutputStream.Write(new byte[] { 0x01, 0x00 }, 0, 2);
break;
// Called during startup. Seems to contain trainer profile stats.
case "/common/setProfile.asp":
sessions.Remove(session.Hash);
// todo: Figure out what fun stuff is contained in this blob!
context.Response.OutputStream.Write(new byte[]
@ -110,15 +120,264 @@ namespace PokeFoundations.GTS
0, 8);
break;
// Called during startup and when you check your pokemon's status.
case "/worldexchange/result.asp":
{
sessions.Remove(session.Hash);
/* 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. */
GtsDatagram4 datagram = DataAbstract.Instance.GtsDataForUser4(pid);
if (datagram == null)
{
// No pokemon in the system
context.Response.OutputStream.Write(new byte[]
{ 0x05, 0x00 }, 0, 2);
}
break;
else if (datagram.IsExchanged > 0)
{
// traded pokemon arriving!!!
context.Response.OutputStream.Write(datagram.Save(), 0, 292);
}
else
{
// my existing pokemon is in the system, untraded
context.Response.OutputStream.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":
{
sessions.Remove(session.Hash);
// this is only called if result.asp returned 4.
// todo: what does this do if the contained pokemon is traded??
GtsDatagram4 datagram = DataAbstract.Instance.GtsDataForUser4(pid);
if (datagram == null)
{
// No pokemon in the system
// what do here?
}
else
{
// just write the datagram whether traded or not...
context.Response.OutputStream.Write(datagram.Save(), 0, 292);
}
} break;
// Called after result.asp returns an inbound pokemon datagram to delete it
case "/worldexchange/delete.asp":
{
sessions.Remove(session.Hash);
GtsDatagram4 datagram = DataAbstract.Instance.GtsDataForUser4(pid);
if (datagram == null)
{
context.Response.OutputStream.Write(new byte[]
{ 0x00, 0x00 }, 0, 2);
}
else if (datagram.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.GtsDeletePokemon4(pid);
if (success)
{
context.Response.OutputStream.Write(new byte[] { 0x01, 0x00 }, 0, 2);
}
else
{
context.Response.OutputStream.Write(new byte[] { 0x00, 0x00 }, 0, 2);
}
}
else
{
// own pokemon is there, fail. Use return.asp instead.
context.Response.OutputStream.Write(new byte[] { 0x00, 0x00 }, 0, 2);
}
} break;
// called to delete your own pokemon after taking it back
case "/worldexchange/return.asp":
{
sessions.Remove(session.Hash);
GtsDatagram4 datagram = DataAbstract.Instance.GtsDataForUser4(pid);
if (datagram == null)
{
context.Response.OutputStream.Write(new byte[]
{ 0x00, 0x00 }, 0, 2);
}
else if (datagram.IsExchanged > 0)
{
// a traded pokemon is there, fail. Use delete.asp instead.
context.Response.OutputStream.Write(new byte[] { 0x00, 0x00 }, 0, 2);
}
else
{
// delete own pokemon
// todo: add transactions
bool success = DataAbstract.Instance.GtsDeletePokemon4(pid);
if (success)
{
context.Response.OutputStream.Write(new byte[] { 0x01, 0x00 }, 0, 2);
}
else
{
context.Response.OutputStream.Write(new byte[] { 0x00, 0x00 }, 0, 2);
}
}
} break;
// Called when you deposit a pokemon into the system.
case "/worldexchange/post.asp":
{
if (data.Length != 296)
{
Error400(context);
return;
}
// keep the datagram in memory while we wait for post_finish.asp request
byte[] datagramBinary = new byte[292];
Array.Copy(data, 4, datagramBinary, 0, 292);
session.Tag = new GtsDatagram4(datagramBinary);
// todo: delete any other post.asp sessions registered under this PID
context.Response.OutputStream.Write(new byte[] { 0x01, 0x00 }, 0, 2);
} break;
case "/worldexchange/post_finish.asp":
{
sessions.Remove(session.Hash);
if (data.Length != 12)
{
Error400(context);
return;
}
// find a matching session which contains our datagram
GtsSession4 prevSession = FindSession(sessions, pid, "/worldexchange/post.asp");
sessions.Remove(prevSession.Hash);
AssertHelper.Assert(prevSession.Tag is GtsDatagram4);
GtsDatagram4 datagram = (GtsDatagram4)prevSession.Tag;
if (DataAbstract.Instance.GtsDepositPokemon4(datagram))
context.Response.OutputStream.Write(new byte[] { 0x01, 0x00 }, 0, 2);
else
context.Response.OutputStream.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 datagrams end to end.
case "/worldexchange/search.asp":
{
sessions.Remove(session.Hash);
if (data.Length < 11 || data.Length > 12)
{
Error400(context);
return;
}
int resultsCount = (int)data[10];
if (resultsCount < 1) break; // optimize away requests for no rows
ushort species = BitConverter.ToUInt16(data, 4);
Genders gender = (Genders)data[6];
byte minLevel = data[7];
byte maxLevel = data[8];
// byte 9 unknown
byte country = 0;
if (data.Length > 11) country = data[11];
if (resultsCount > 7) resultsCount = 7; // stop DDOS
GtsDatagram4[] datagrams = DataAbstract.Instance.GtsSearch4(species, gender, minLevel, maxLevel, country, resultsCount);
foreach (GtsDatagram4 datagram in datagrams)
{
context.Response.OutputStream.Write(datagram.Save(), 0, 292);
}
} break;
// the exchange request uploads a datagram of the exchangee pokemon
// plus the desired PID to trade for at the very end.
case "/worldexchange/exchange.asp":
{
if (data.Length != 300)
{
Error400(context);
return;
}
byte[] uploadData = new byte[292];
Array.Copy(data, 4, uploadData, 0, 292);
GtsDatagram4 upload = new GtsDatagram4(uploadData);
int targetPid = BitConverter.ToInt32(data, 296);
GtsDatagram4 result = DataAbstract.Instance.GtsDataForUser4(targetPid);
// todo: maybe strong type this
object[] tag = new object[2];
tag[0] = upload;
tag[1] = result;
session.Tag = tag;
GtsDatagram4 tradedResult = result.Clone();
tradedResult.FlagTraded(upload); // only real purpose is to generate a proper response
context.Response.OutputStream.Write(result.Save(), 0, 292);
} break;
case "/worldexchange/exchange_finish.asp":
{
sessions.Remove(session.Hash);
if (data.Length != 12)
{
Error400(context);
return;
}
// find a matching session which contains our datagram
GtsSession4 prevSession = FindSession(sessions, pid, "/worldexchange/exchange.asp");
sessions.Remove(prevSession.Hash);
AssertHelper.Assert(prevSession.Tag is object[]);
object[] tag = (object[])prevSession.Tag;
AssertHelper.Assert(tag.Length == 2);
AssertHelper.Assert(tag[0] is GtsDatagram4);
AssertHelper.Assert(tag[0] is GtsDatagram4);
GtsDatagram4 upload = (GtsDatagram4)tag[0];
GtsDatagram4 result = (GtsDatagram4)tag[1];
if (DataAbstract.Instance.GtsTradePokemon4(upload, result))
context.Response.OutputStream.Write(new byte[] { 0x01, 0x00 }, 0, 2);
else
context.Response.OutputStream.Write(new byte[] { 0x00, 0x00 }, 0, 2);
} break;
}
}
else
@ -139,5 +398,24 @@ namespace PokeFoundations.GTS
return false;
}
}
private GtsSession4 FindSession(Dictionary<String, GtsSession4> sessions, int pid, String url)
{
GtsSession4 result = null;
foreach (GtsSession4 sess in sessions.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;
}
}
}

View File

@ -95,6 +95,12 @@ namespace PokeFoundations.GTS
}
}
public object Tag
{
get;
set;
}
public static String CreateToken()
{
if (m_rng == null) m_rng = new RNGCryptoServiceProvider();

View File

@ -36,6 +36,9 @@ namespace PokeFoundations.Data
public abstract bool GtsDeletePokemon4(int pid);
public abstract bool GtsTradePokemon4(int pidSrc, int pidDest);
public abstract bool GtsTradePokemon4(GtsDatagram4 upload, GtsDatagram4 result);
public abstract GtsDatagram4[] GtsSearch4(ushort species, Genders gender, byte minLevel, byte maxLevel, byte country, int count);
#endregion
}

View File

@ -35,60 +35,73 @@ namespace PokeFoundations.Data
#endregion
#region GTS
public GtsDatagram4 GtsDataForUser4(MySqlTransaction tran, int pid)
{
MySqlDataReader reader = (MySqlDataReader)tran.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())
{
reader.Close();
return null;
}
GtsDatagram4 result = Datagram4FromReader(reader);
#if DEBUG
AssertHelper.Equals(result.PID, pid);
#endif
reader.Close();
return result;
}
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();
MySqlTransaction tran = db.BeginTransaction();
GtsDatagram4 result = GtsDataForUser4(tran, pid);
tran.Commit();
return result;
}
}
public bool GtsDepositPokemon4(MySqlTransaction tran, 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.
long count = (long)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
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)",
ParamsFromDatagram4(datagram));
return true;
}
public override bool GtsDepositPokemon4(GtsDatagram4 datagram)
{
if (datagram.Data.Length != 236) throw new FormatException("pkm data must be 236 bytes.");
@ -99,64 +112,14 @@ namespace PokeFoundations.Data
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();
long count = (long)tran.ExecuteScalar("SELECT Count(*) FROM GtsPokemon4 WHERE pid = @pid",
new MySqlParameter("@pid", datagram.PID));
if (count > 0)
if (!GtsDepositPokemon4(tran, datagram))
{
// 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;
}
@ -171,21 +134,29 @@ namespace PokeFoundations.Data
return (int)o;
}
public bool GtsDeletePokemon4(MySqlTransaction tran, int pid)
{
int pkmnId = GtsGetDepositId(pid, tran);
if (pkmnId == 0) return false;
tran.ExecuteNonQuery("DELETE FROM GtsPokemon4 WHERE id = @id",
new MySqlParameter("@id", pkmnId));
return true;
}
public override bool GtsDeletePokemon4(int pid)
{
using (MySqlConnection db = CreateConnection())
{
db.Open();
MySqlTransaction tran = db.BeginTransaction();
int pkmnId = GtsGetDepositId(pid, tran);
if (pkmnId == 0)
if (!GtsDeletePokemon4(tran, pid))
{
tran.Rollback();
return false;
}
tran.ExecuteNonQuery("DELETE FROM GtsPokemon4 WHERE id = @id",
new MySqlParameter("@id", pkmnId));
tran.Commit();
return true;
}
@ -193,9 +164,174 @@ namespace PokeFoundations.Data
public override bool GtsTradePokemon4(int pidSrc, int pidDest)
{
// not needed yet.
return false;
}
public override bool GtsTradePokemon4(GtsDatagram4 upload, GtsDatagram4 result)
{
GtsDatagram4 traded = upload.Clone();
traded.FlagTraded(result);
using (MySqlConnection db = CreateConnection())
{
db.Open();
MySqlTransaction tran = db.BeginTransaction();
GtsDatagram4 resultOrig = GtsDataForUser4(tran, result.PID);
if (resultOrig == null || resultOrig != result)
{
// looks like the pokemon was ninja'd between the Exchange and Exchange_finish
tran.Rollback();
return false;
}
if (!GtsDeletePokemon4(tran, result.PID))
{
tran.Rollback();
return false;
}
if (!GtsDepositPokemon4(tran, traded))
{
tran.Rollback();
return false;
}
tran.Commit();
return true;
}
}
public override GtsDatagram4[] GtsSearch4(ushort species, Genders gender, byte minLevel, byte maxLevel, byte country, int count)
{
using (MySqlConnection db = CreateConnection())
{
List<MySqlParameter> _params = new List<MySqlParameter>();
String where = "WHERE Species = @species";
_params.Add(new MySqlParameter("@species", species));
if (gender != Genders.Either)
{
where += " AND Gender = @gender";
_params.Add(new MySqlParameter("@gender", (byte)gender));
}
if (minLevel > 0 && maxLevel > 0)
{
where += " AND Level BETWEEN @min_level AND @max_level";
_params.Add(new MySqlParameter("@min_level", minLevel));
_params.Add(new MySqlParameter("@max_level", maxLevel));
}
else if (minLevel > 0)
{
where += " AND Level >= @min_level";
_params.Add(new MySqlParameter("@min_level", minLevel));
}
else if (maxLevel > 0)
{
where += " AND Level <= @max_level";
_params.Add(new MySqlParameter("@max_level", maxLevel));
}
if (country > 0)
{
where += " AND TrainerCountry = @country";
_params.Add(new MySqlParameter("@country", country));
}
_params.Add(new MySqlParameter("@count", count));
db.Open();
// todo: sort me in creative ways
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 +
" ORDER BY TimeDeposited DESC LIMIT @count",
_params.ToArray());
List<GtsDatagram4> datagrams = new List<GtsDatagram4>(count);
while (reader.Read())
{
datagrams.Add(Datagram4FromReader(reader));
}
return datagrams.ToArray();
}
}
private static GtsDatagram4 Datagram4FromReader(MySqlDataReader reader)
{
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);
return result;
}
private static MySqlParameter[] ParamsFromDatagram4(GtsDatagram4 datagram)
{
MySqlParameter[] result = new MySqlParameter[22];
result[0] = new MySqlParameter("@Data", datagram.Data);
result[1] = new MySqlParameter("@Species", datagram.Species);
result[2] = new MySqlParameter("@Gender", (byte)datagram.Gender);
result[3] = new MySqlParameter("@Level", datagram.Level);
result[4] = new MySqlParameter("@RequestedSpecies", datagram.RequestedSpecies);
result[5] = new MySqlParameter("@RequestedGender", (byte)datagram.RequestedGender);
result[6] = new MySqlParameter("@RequestedMinLevel", datagram.RequestedMinLevel);
result[7] = new MySqlParameter("@RequestedMaxLevel", datagram.RequestedMaxLevel);
result[8] = new MySqlParameter("@Unknown1", datagram.Unknown1);
result[9] = new MySqlParameter("@TrainerGender", (byte)datagram.TrainerGender);
result[10] = new MySqlParameter("@Unknown2", datagram.Unknown2);
result[11] = new MySqlParameter("@TimeDeposited", datagram.TimeDeposited);
result[12] = new MySqlParameter("@TimeWithdrawn", datagram.TimeWithdrawn);
result[13] = new MySqlParameter("@pid", datagram.PID);
result[14] = new MySqlParameter("@TrainerName", datagram.TrainerName);
result[15] = new MySqlParameter("@TrainerOT", datagram.TrainerOT);
result[16] = new MySqlParameter("@TrainerCountry", datagram.TrainerCountry);
result[17] = new MySqlParameter("@TrainerRegion", datagram.TrainerRegion);
result[18] = new MySqlParameter("@TrainerClass", datagram.TrainerClass);
result[19] = new MySqlParameter("@IsExchanged", datagram.IsExchanged);
result[20] = new MySqlParameter("@TrainerVersion", datagram.TrainerVersion);
result[21] = new MySqlParameter("@TrainerLanguage", datagram.TrainerLanguage);
return result;
}
#endregion
}
}

View File

@ -15,6 +15,15 @@ namespace PokeFoundations.Structures
[Serializable()]
public class GtsDatagram4
{
public GtsDatagram4()
{
}
public GtsDatagram4(byte[] data)
{
Load(data);
}
/// <summary>
/// Obfuscated Pokémon (pkm) data. 236 bytes
/// </summary>
@ -48,8 +57,8 @@ namespace PokeFoundations.Structures
public GtsTrainerGenders TrainerGender;
public byte Unknown2;
public DateTime TimeDeposited;
public DateTime TimeWithdrawn;
public DateTime ? TimeDeposited;
public DateTime ? TimeWithdrawn;
/// <summary>
/// User ID of the player (not Personality Value)
@ -90,8 +99,8 @@ namespace PokeFoundations.Structures
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(DateToTimestamp(TimeDeposited)), 0, 8);
s.Write(BitConverter.GetBytes(DateToTimestamp(TimeWithdrawn)), 0, 8);
s.Write(BitConverter.GetBytes(PID), 0, 4);
s.Write(TrainerName, 0, 0x10);
s.Write(BitConverter.GetBytes(TrainerOT), 0, 2);
@ -121,10 +130,8 @@ namespace PokeFoundations.Structures
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));
TimeDeposited = TimestampToDate(BitConverter.ToUInt64(data, 0xF8));
TimeWithdrawn = TimestampToDate(BitConverter.ToUInt64(data, 0x100));
PID = BitConverter.ToInt32(data, 0x108);
TrainerName = new byte[0x10];
Array.Copy(data, 0x10C, TrainerName, 0, 0x10);
@ -136,5 +143,68 @@ namespace PokeFoundations.Structures
TrainerVersion = data[0x122];
TrainerLanguage = data[0x123];
}
public GtsDatagram4 Clone()
{
// todo: I am not very efficient
return new GtsDatagram4(Save());
}
public void FlagTraded(GtsDatagram4 other)
{
Species = other.Species;
Gender = other.Gender;
Level = other.Level;
RequestedSpecies = other.RequestedSpecies;
RequestedGender = other.RequestedGender;
RequestedMinLevel = other.RequestedMinLevel;
RequestedMaxLevel = other.RequestedMaxLevel;
TimeDeposited = other.TimeDeposited;
TimeWithdrawn = DateTime.Now; // figure out where this really comes from. It seems to psychically know the player's timezone
PID = other.PID;
IsExchanged = 0x01;
}
public static DateTime ? TimestampToDate(ulong timestamp)
{
if (timestamp == 0) return null;
ushort year = (ushort)((timestamp >> 0x30) & 0xffff);
byte month = (byte)((timestamp >> 0x28) & 0xff);
byte day = (byte)((timestamp >> 0x20) & 0xff);
byte hour = (byte)((timestamp >> 0x18) & 0xff);
byte minute = (byte)((timestamp >> 0x10) & 0xff);
byte second = (byte)((timestamp >> 0x08) & 0xff);
//byte fractional = (byte)(timestamp & 0xff);
// allow ArgumentOutOfRangeExceptions to escape
return new DateTime(year, month, day, hour, minute, second);
}
public static ulong DateToTimestamp(DateTime ? date)
{
if (date == null) return 0;
DateTime date2 = (DateTime)date;
return (ulong)(date2.Year & 0xffff) << 0x30
| (ulong)(date2.Month & 0xff) << 0x28
| (ulong)(date2.Day & 0xff) << 0x20
| (ulong)(date2.Hour & 0xff) << 0x18
| (ulong)(date2.Minute & 0xff) << 0x10
| (ulong)(date2.Second & 0xff) << 0x08;
}
public static bool operator ==(GtsDatagram4 a, GtsDatagram4 b)
{
if ((object)a == null && (object)b == null) return true;
if ((object)a == null || (object)b == null) return false;
// todo: optimize me
return a.Save().SequenceEqual(b.Save());
}
public static bool operator !=(GtsDatagram4 a, GtsDatagram4 b)
{
return !(a == b);
}
}
}