First draft of savefile bans

This commit is contained in:
Greg Edwards 2022-03-27 02:01:04 -04:00
parent 31070df11b
commit 9d761a7d7d
13 changed files with 284 additions and 83 deletions

View File

@ -27,11 +27,13 @@ namespace PkmnFoundations.GTS
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;
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);
@ -64,62 +66,69 @@ namespace PkmnFoundations.GTS
Array.Copy(data, 0, profileBinary, 0, 100);
TrainerProfile4 profile = new TrainerProfile4(pid, profileBinary, IpAddressHelper.GetIpAddress(context.Request));
Database.Instance.GamestatsSetProfile4(profile);
BanStatus ban = Database.Instance.CheckBanStatus(profile);
if (ban != null && ban.Level > BanLevels.Restricted)
{
ShowError(context, 403);
return;
}
#if !DEBUG
}
catch { }
#endif
short clientSecret = BitConverter.ToInt16(data, 96);
short mailSecret = BitConverter.ToInt16(data, 98);
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);
}
// 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);
}
break;
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
@ -135,8 +144,7 @@ namespace PkmnFoundations.GTS
Database.Instance.GamestatsBumpProfile4(pid, ip);
response.Write(new byte[] { 0x01, 0x00 }, 0, 2);
break;
}
} break;
// Called during startup and when you check your pokemon's status.
case "/pokemondpds/worldexchange/result.asp":

View File

@ -25,11 +25,13 @@ namespace PkmnFoundations.GTS
public override void ProcessGamestatsRequest(byte[] request, MemoryStream response, string url, int pid, HttpContext context, GamestatsSession session)
{
BanStatus ban = BanHelper.GetBanStatus(pid, IpAddressHelper.GetIpAddress(context.Request), Generations.Generation5);
if (ban != null && ban.Level > BanLevels.Restricted)
{
ShowError(context, 403);
return;
BanStatus ban = BanHelper.GetBanStatus(pid, IpAddressHelper.GetIpAddress(context.Request), Generations.Generation5);
if (ban != null && ban.Level > BanLevels.Restricted)
{
ShowError(context, 403);
return;
}
}
Pokedex.Pokedex pokedex = AppStateHelper.Pokedex(context.Application);
@ -57,13 +59,20 @@ namespace PkmnFoundations.GTS
try
{
#endif
// this blob appears to share the same format with GenIV only with (obviously) a GenV string for the trainer name
// and the email-related fields dummied out.
// Specifically, email, notification status, and the two secrets appear to always be 0.
// this blob appears to share the same format with GenIV only with (obviously) a GenV string for the trainer name
// and the email-related fields dummied out.
// Specifically, email, notification status, and the two secrets appear to always be 0.
byte[] profileBinary = new byte[100];
Array.Copy(request, 0, profileBinary, 0, 100);
TrainerProfile5 profile = new TrainerProfile5(pid, profileBinary, IpAddressHelper.GetIpAddress(context.Request));
Database.Instance.GamestatsSetProfile5(profile);
BanStatus ban = Database.Instance.CheckBanStatus(profile);
if (ban != null && ban.Level > BanLevels.Restricted)
{
ShowError(context, 403);
return;
}
#if !DEBUG
}
catch { }

View File

@ -464,8 +464,8 @@ namespace PkmnFoundations.Data
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);
result.TrainerVersion = (Versions)reader.GetByte(20);
result.TrainerLanguage = (Languages)reader.GetByte(21);
return result;
}
@ -1201,6 +1201,98 @@ namespace PkmnFoundations.Data
DatabaseExtender.Cast<DateTime?>(row["Expires"])
);
}
/// <summary>
/// Checks JUST for a matching savefile ban. Use the appropriate pid/MAC CheckBanStatus overload to check those tables.
/// </summary>
/// <param name="profile"></param>
/// <returns></returns>
public override BanStatus CheckBanStatus(TrainerProfileBase profile)
{
return WithTransaction(tran => CheckBanStatus(tran, profile));
}
public BanStatus CheckBanStatus(MySqlTransaction tran, TrainerProfileBase profile)
{
byte[] name = null;
if (profile is TrainerProfile4)
{
name = ((TrainerProfile4)profile).Name.RawData;
}
if (profile is TrainerProfile5)
{
name = ((TrainerProfile5)profile).Name.RawData;
}
DataTable result = tran.ExecuteDataTable("SELECT Level, Reason, Expires " +
"FROM pkmncf_gamestats_bans_savefile WHERE Version = @version AND Language = @language AND OT = @ot AND Name = @name " +
"AND (Expires > UTC_TIMESTAMP() OR Expires IS NULL)",
new MySqlParameter("@version", profile.Version),
new MySqlParameter("@language", profile.Language),
new MySqlParameter("@ot", profile.OT),
new MySqlParameter("@name", name));
if (result.Rows.Count == 0) return new BanStatus(BanLevels.None, null, DateTime.MinValue);
DataRow row = result.Rows[0];
return new BanStatus(
(BanLevels)DatabaseExtender.Cast<int>(row["Level"]),
DatabaseExtender.Cast<string>(row["Reason"]),
DatabaseExtender.Cast<DateTime?>(row["Expires"])
);
}
public override void AddBan(int pid, BanStatus status)
{
WithTransaction(tran => AddBan(tran, pid, status));
}
public void AddBan(MySqlTransaction tran, int pid, BanStatus status)
{
AddBan(tran, "pkmncf_gamestats_bans_pid", "pid", new MySqlParameter("@pk", pid), status);
}
public override void AddBan(byte[] mac_address, BanStatus status)
{
WithTransaction(tran => AddBan(tran, mac_address, status));
}
public void AddBan(MySqlTransaction tran, byte[] mac_address, BanStatus status)
{
AddBan(tran, "pkmncf_gamestats_bans_mac", "MacAddress", new MySqlParameter("@pk", mac_address), status);
}
public override void AddBan(string ip_address, BanStatus status)
{
WithTransaction(tran => AddBan(tran, ip_address, status));
}
public void AddBan(MySqlTransaction tran, string ip_address, BanStatus status)
{
AddBan(tran, "pkmncf_gamestats_bans_ip", "IpAddress", new MySqlParameter("@pk", ip_address), status);
}
private void AddBan(MySqlTransaction tran, string tbl, string primary_key, MySqlParameter pk_param, BanStatus status)
{
string pkParamName = pk_param.ParameterName;
string sqlExists = "SELECT EXISTS(SELECT * FROM " + tbl + " WHERE " + primary_key + " = " + pkParamName + ")";
string sqlInsert = "INSERT INTO " + tbl + " (" + primary_key + ", Level, Reason, Expires) VALUES (" + pkParamName + ", @level, @reason, @expires)";
string sqlUpdate = "UPDATE " + tbl + " SET Level = @level, Reason = @reason, Expires = GREATEST(Expires, @expires) WHERE " + primary_key + " = " + pkParamName;
if (Convert.ToSByte(tran.ExecuteScalar(sqlExists, pk_param.CloneParameter())) > 0)
{
tran.ExecuteNonQuery(sqlUpdate, pk_param.CloneParameter(),
new MySqlParameter("@level", status.Level),
new MySqlParameter("@reason", status.Reason),
new MySqlParameter("@expires", status.Expires));
}
else
{
tran.ExecuteNonQuery(sqlInsert, pk_param.CloneParameter(),
new MySqlParameter("@level", status.Level),
new MySqlParameter("@reason", status.Reason),
new MySqlParameter("@expires", status.Expires));
}
}
#endregion
#region GTS 5
@ -1535,8 +1627,8 @@ namespace PkmnFoundations.Data
result.TrainerRegion = reader.GetByte(18);
result.TrainerClass = reader.GetByte(19);
result.IsExchanged = reader.GetByte(20);
result.TrainerVersion = reader.GetByte(21);
result.TrainerLanguage = reader.GetByte(22);
result.TrainerVersion = (Versions)reader.GetByte(21);
result.TrainerLanguage = (Languages)reader.GetByte(22);
result.TrainerBadges = reader.GetByte(23);
result.TrainerUnityTower = reader.GetByte(24);

View File

@ -96,9 +96,17 @@ namespace PkmnFoundations.Data
public abstract bool GamestatsBumpProfile4(int pid, string ip_address);
public abstract bool GamestatsSetProfile4(TrainerProfile4 profile);
public abstract TrainerProfile4 GamestatsGetProfile4(int pid);
#endregion
#region Bans
public abstract BanStatus CheckBanStatus(int pid);
public abstract BanStatus CheckBanStatus(byte[] mac_address);
public abstract BanStatus CheckBanStatus(string ip_address);
public abstract BanStatus CheckBanStatus(TrainerProfileBase profile);
public abstract void AddBan(int pid, BanStatus status);
public abstract void AddBan(byte[] mac_address, BanStatus status);
public abstract void AddBan(string ip_address, BanStatus status);
#endregion
#region GTS 5

View File

@ -24,15 +24,23 @@ namespace PkmnFoundations.Data
int x = 0;
foreach (MySqlParameter p in collection)
{
MySqlParameter param = new MySqlParameter(p.ParameterName, (MySqlDbType)p.DbType, p.Size, p.Direction, p.IsNullable, p.Precision, p.Scale, p.SourceColumn, p.SourceVersion, p.Value);
param.DbType = p.DbType;
result[x] = param;
result[x] = p.CloneParameter();
x++;
}
return result;
}
public static MySqlParameter CloneParameter(this MySqlParameter param)
{
MySqlParameter result = new MySqlParameter(param.ParameterName,
(MySqlDbType)param.DbType, param.Size, param.Direction,
param.IsNullable, param.Precision, param.Scale,
param.SourceColumn, param.SourceVersion, param.Value);
result.DbType = param.DbType;
return result;
}
/// <summary>
/// Runs a proc and returns its return value.
/// </summary>

View File

@ -130,7 +130,7 @@ namespace PkmnFoundations.Structures
// todo: enclose in properties and validate these when assigning.
if (TrainerNameEncoded.RawData.Length != 0x10) throw new FormatException("Trainer name length is incorrect");
writer.Write(DataActual, 0, 0xEC); // 0000
writer.Write(DataActual, 0, 0xec); // 0000
writer.Write(Species); // 00EC
writer.Write((byte)Gender); // 00EE
writer.Write(Level); // 00EF
@ -150,13 +150,13 @@ namespace PkmnFoundations.Structures
writer.Write(TrainerRegion); // 011F
writer.Write(TrainerClass); // 0120
writer.Write(IsExchanged); // 0121
writer.Write(TrainerVersion); // 0122
writer.Write(TrainerLanguage); // 0123
writer.Write((byte)TrainerVersion); // 0122
writer.Write((byte)TrainerLanguage); // 0123
}
protected override void Load(BinaryReader reader)
{
DataActual = reader.ReadBytes(0xEC); // 0000
DataActual = reader.ReadBytes(0xec); // 0000
Species = reader.ReadUInt16(); // 00EC
Gender = (Genders)reader.ReadByte(); // 00EE
Level = reader.ReadByte(); // 00EF
@ -176,8 +176,8 @@ namespace PkmnFoundations.Structures
TrainerRegion = reader.ReadByte(); // 011F
TrainerClass = reader.ReadByte(); // 0120
IsExchanged = reader.ReadByte(); // 0121
TrainerVersion = reader.ReadByte(); // 0122
TrainerLanguage = reader.ReadByte(); // 0123
TrainerVersion = (Versions)reader.ReadByte(); // 0122
TrainerLanguage = (Languages)reader.ReadByte(); // 0123
}
public override int Size
@ -231,6 +231,20 @@ namespace PkmnFoundations.Structures
IsExchanged = 0x01;
}
public override TrainerProfileBase ExtrapolateProfile()
{
TrainerProfile4 result = new TrainerProfile4();
result.PID = PID;
result.Version = TrainerVersion;
result.Language = TrainerLanguage;
result.Country = TrainerCountry;
result.Region = TrainerRegion;
result.OT = TrainerOT;
result.Name = TrainerNameEncoded.Clone();
return result;
}
public static bool operator ==(GtsRecord4 a, GtsRecord4 b)
{
if ((object)a == null && (object)b == null) return true;

View File

@ -153,8 +153,8 @@ namespace PkmnFoundations.Structures
writer.Write(TrainerRegion); // 0121
writer.Write(TrainerClass); // 0122
writer.Write(IsExchanged); // 0123
writer.Write(TrainerVersion); // 0124
writer.Write(TrainerLanguage); // 0125
writer.Write((byte)TrainerVersion); // 0124
writer.Write((byte)TrainerLanguage); // 0125
writer.Write(TrainerBadges); // 0126
writer.Write(TrainerUnityTower); // 0127
}
@ -181,8 +181,8 @@ namespace PkmnFoundations.Structures
TrainerRegion = reader.ReadByte(); // 0121
TrainerClass = reader.ReadByte(); // 0122
IsExchanged = reader.ReadByte(); // 0123
TrainerVersion = reader.ReadByte(); // 0124
TrainerLanguage = reader.ReadByte(); // 0125
TrainerVersion = (Versions)reader.ReadByte(); // 0124
TrainerLanguage = (Languages)reader.ReadByte(); // 0125
TrainerBadges = reader.ReadByte(); // 0126
TrainerUnityTower = reader.ReadByte(); // 0127
}
@ -235,6 +235,20 @@ namespace PkmnFoundations.Structures
IsExchanged = 0x01;
}
public override TrainerProfileBase ExtrapolateProfile()
{
TrainerProfile5 result = new TrainerProfile5();
result.PID = PID;
result.Version = TrainerVersion;
result.Language = TrainerLanguage;
result.Country = TrainerCountry;
result.Region = TrainerRegion;
result.OT = TrainerOT;
result.Name = TrainerNameEncoded.Clone();
return result;
}
public static bool operator ==(GtsRecord5 a, GtsRecord5 b)
{
if ((object)a == null && (object)b == null) return true;

View File

@ -78,8 +78,8 @@ namespace PkmnFoundations.Structures
public byte IsExchanged;
public byte TrainerVersion;
public byte TrainerLanguage;
public Versions TrainerVersion;
public Languages TrainerLanguage;
public virtual bool Validate()
{
@ -110,6 +110,8 @@ namespace PkmnFoundations.Structures
return true;
}
public abstract TrainerProfileBase ExtrapolateProfile();
public static bool CheckLevels(byte min, byte max, byte other)
{
if (max == 0) max = 255;

View File

@ -21,6 +21,10 @@ namespace PkmnFoundations.Structures
{
return new EncodedString4(Data, 8, 16);
}
set
{
Array.Copy(value.RawData, 0, Data, 8, 16);
}
}
public TrainerProfile4 Clone()

View File

@ -21,6 +21,10 @@ namespace PkmnFoundations.Structures
{
return new EncodedString5(Data, 8, 16);
}
set
{
Array.Copy(value.RawData, 0, Data, 8, 16);
}
}
public TrainerProfile5 Clone()

View File

@ -33,6 +33,10 @@ namespace PkmnFoundations.Structures
{
return (Versions)Data[0];
}
set
{
Data[0] = (byte)value;
}
}
public Languages Language
@ -41,6 +45,10 @@ namespace PkmnFoundations.Structures
{
return (Languages)Data[1];
}
set
{
Data[1] = (byte)value;
}
}
public byte Country
@ -49,6 +57,10 @@ namespace PkmnFoundations.Structures
{
return Data[2];
}
set
{
Data[2] = value;
}
}
public byte Region
@ -57,6 +69,10 @@ namespace PkmnFoundations.Structures
{
return Data[3];
}
set
{
Data[3] = value;
}
}
public uint OT
@ -65,8 +81,20 @@ namespace PkmnFoundations.Structures
{
return BitConverter.ToUInt32(Data, 4);
}
set
{
Array.Copy(BitConverter.GetBytes(value), 0, Data, 4, 4);
}
}
/*
// xxx: This would require C# 9 covariant return types to work. https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/covariant-returns
public abstract EncodedStringBase Name
{
get;
}
*/
public byte[] MacAddress
{
get

View File

@ -149,7 +149,7 @@ namespace PkmnFoundations.Support
m_text = null;
}
private void AssignText(String text)
private void AssignText(string text)
{
m_text = text;
m_raw_data = null;
@ -160,6 +160,11 @@ namespace PkmnFoundations.Support
return Text;
}
public EncodedString4 Clone()
{
return new EncodedString4(RawData);
}
private static Dictionary<char, ushort> m_lookup_reverse = null;
private static Dictionary<char, ushort> LookupReverse
{

View File

@ -160,6 +160,11 @@ namespace PkmnFoundations.Support
return Text;
}
public EncodedString5 Clone()
{
return new EncodedString5(RawData);
}
private static Dictionary<char, ushort> m_lookup_reverse = null;
private static Dictionary<char, ushort> LookupReverse
{