pkmn-classic-framework/library/Structures/GtsRecord4.cs

243 lines
8.3 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.IO;
using PkmnFoundations.Support;
namespace PkmnFoundations.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>
public class GtsRecord4
{
public GtsRecord4()
{
}
public GtsRecord4(byte[] data)
{
Load(data);
}
/// <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 TrainerGenders TrainerGender;
public byte Unknown2;
public DateTime ? TimeDeposited;
public DateTime ? TimeExchanged;
/// <summary>
/// User ID of the player (not Personality Value)
/// </summary>
public int PID;
/// <summary>
/// 16 bytes
/// </summary>
public EncodedString4 TrainerName;
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.RawData.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(DateToTimestamp(TimeDeposited)), 0, 8);
s.Write(BitConverter.GetBytes(DateToTimestamp(TimeExchanged)), 0, 8);
s.Write(BitConverter.GetBytes(PID), 0, 4);
s.Write(TrainerName.RawData, 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 record 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 = (TrainerGenders)data[0xF6];
Unknown2 = data[0xF7];
TimeDeposited = TimestampToDate(BitConverter.ToUInt64(data, 0xF8));
TimeExchanged = TimestampToDate(BitConverter.ToUInt64(data, 0x100));
PID = BitConverter.ToInt32(data, 0x108);
TrainerName = new EncodedString4(data, 0x10C, 0x10);
TrainerOT = BitConverter.ToUInt16(data, 0x11C);
TrainerCountry = data[0x11E];
TrainerRegion = data[0x11F];
TrainerClass = data[0x120];
IsExchanged = data[0x121];
TrainerVersion = data[0x122];
TrainerLanguage = data[0x123];
}
public GtsRecord4 Clone()
{
// todo: I am not very efficient
return new GtsRecord4(Save());
}
public bool Validate()
{
// todo: a. legitimacy check, and b. check that pkm data matches metadata
return true;
}
public bool CanTrade(GtsRecord4 other)
{
if (IsExchanged != 0 || other.IsExchanged != 0) return false;
if (Species != other.RequestedSpecies) return false;
if (other.RequestedGender != Genders.Either && Gender != other.RequestedGender) return false;
if (!CheckLevels(other.RequestedMinLevel, other.RequestedMaxLevel, Level)) return false;
if (RequestedSpecies != other.Species) return false;
if (RequestedGender != Genders.Either && other.Gender != RequestedGender) return false;
if (!CheckLevels(RequestedMinLevel, RequestedMaxLevel, other.Level)) return false;
return true;
}
public void FlagTraded(GtsRecord4 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;
TimeExchanged = DateTime.UtcNow;
PID = other.PID;
IsExchanged = 0x01;
}
public static bool CheckLevels(byte min, byte max, byte other)
{
if (max == 0) max = 255;
return other >= min && other <= max;
}
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); // always 0
try
{
return new DateTime(year, month, day, hour, minute, second);
}
catch (ArgumentOutOfRangeException)
{
return null;
}
}
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 ==(GtsRecord4 a, GtsRecord4 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 !=(GtsRecord4 a, GtsRecord4 b)
{
return !(a == b);
}
}
}