Probably implemented battle videos server on Generation IV.

This commit is contained in:
Greg Edwards 2014-06-30 02:17:13 -04:00
parent 660b72d69a
commit 5b775ed32b
6 changed files with 341 additions and 28 deletions

View File

@ -159,14 +159,89 @@ namespace PkmnFoundations.GlobalTerminalService
} break;
case RequestTypes4.BattleVideoUpload:
{
if (data.Length != 0x1e8c)
{
response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
break;
}
int pid = BitConverter.ToInt32(data, 8);
byte[] battlevidData = new byte[0x1d4c];
Array.Copy(data, 0x140, battlevidData, 0, 0x1d4c);
BattleVideoRecord4 record = new BattleVideoRecord4(pid, 0, battlevidData);
long serial = DataAbstract.Instance.BattleVideoUpload4(record);
if (serial == 0)
{
Console.WriteLine("Uploaded battle video already in server.");
response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
break;
}
Console.WriteLine("Battle video uploaded successfully.");
response.Write(new byte[] { 0x00, 0x00 }, 0, 2); // result code (0 for OK)
response.Write(BitConverter.GetBytes(serial), 0, 8);
} break;
case RequestTypes4.BattleVideoSearch:
{
if (data.Length != 0x15c)
{
response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
break;
}
// todo: validate or log some of this?
ushort species = BitConverter.ToUInt16(data, 0x144);
BattleVideoMetagames4 meta = (BattleVideoMetagames4)data[0x146];
byte country = data[0x147];
byte region = data[0x148];
Console.Write("Searching for ");
if (species != 0xffff)
Console.Write("species {0}, ", species);
if (meta != BattleVideoMetagames4.Latest30)
Console.Write("{0}, ", meta);
if (country != 0xff)
Console.Write("country {0}, ", country);
if (region != 0xff)
Console.Write("region {0}", region);
BattleVideoHeader4[] results = DataAbstract.Instance.BattleVideoSearch4(species, meta, country, region, 30);
response.Write(new byte[] { 0x00, 0x00 }, 0, 2); // result code (0 for OK)
response.Write(BitConverter.GetBytes(results.Length), 0, 4);
foreach (BattleVideoHeader4 result in results)
{
response.Write(BitConverter.GetBytes(result.PID), 0, 4);
response.Write(BitConverter.GetBytes(result.SerialNumber), 0, 8);
response.Write(result.Data, 0, 0xe4);
}
Console.WriteLine("Retrieved {0} battle video results.", results.Length);
} break;
case RequestTypes4.BattleVideoWatch:
{
if (data.Length != 0x14c)
{
response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
break;
}
long serial = BitConverter.ToInt64(data, 0x140);
BattleVideoRecord4 record = DataAbstract.Instance.BattleVideoGet4(serial);
if (record == null)
{
response.Write(new byte[] { 0x02, 0x00 }, 0, 2);
Console.WriteLine("Requested battle video {0} was missing.", BattleVideoHeader4.FormatSerial(serial));
break;
}
response.Write(BitConverter.GetBytes(record.PID), 0, 4);
response.Write(BitConverter.GetBytes(record.SerialNumber), 0, 8);
response.Write(record.Header.Data, 0, 0xe4);
response.Write(record.Data, 0, 0x1c68);
Console.WriteLine("Retrieved battle video {0}.", BattleVideoHeader4.FormatSerial(serial));
} break;
}

View File

@ -8,6 +8,7 @@ using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
using System.Net;
using PkmnFoundations.Support;
namespace PkmnFoundations.GlobalTerminalService
{
@ -106,11 +107,11 @@ namespace PkmnFoundations.GlobalTerminalService
}
byte[] data = new byte[4];
ReadAll(s, data, 0, 4);
s.ReadBlock(data, 0, 4);
int length = BitConverter.ToInt32(data, 0);
data = new byte[length];
BitConverter.GetBytes(length).CopyTo(data, 0);
ReadAll(s, data, 4, length - 4); // todo: stop DoS by timing out blocking requests
s.ReadBlock(data, 4, length - 4); // todo: stop DoS by timing out blocking requests
// todo after that: ban IPs that make lots of blocking requests
byte[] response = ProcessRequest(data);
@ -137,24 +138,5 @@ namespace PkmnFoundations.GlobalTerminalService
protected abstract byte[] ProcessRequest(byte[] data);
public abstract String Title { get; }
/// <summary>
/// Reads bytes from a stream and waits until it can read them all.
/// </summary>
/// <param name="s"></param>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <param name="count"></param>
public static bool ReadAll(Stream s, byte[] buffer, int offset, int count)
{
int readBytes = 0;
while (readBytes < count)
{
int x = s.Read(buffer, offset + readBytes, count - readBytes);
if (x == 0) return false;
readBytes += x;
}
return true;
}
}
}

View File

@ -80,6 +80,8 @@ namespace PkmnFoundations.Data
#region Global Terminal 4
public const int DRESSUP_VERSION_4 = 1;
public const int BOX_VERSION_4 = 1;
public const int BATTLEVIDEO_VERSION_4 = 1;
public abstract long DressupUpload4(DressupRecord4 record);
public abstract DressupRecord4[] DressupSearch4(ushort species, int count);
@ -87,7 +89,9 @@ namespace PkmnFoundations.Data
public abstract long BoxUpload4(BoxRecord4 record);
public abstract BoxRecord4[] BoxSearch4(BoxLabels4 label, int count);
public abstract long BattleVideoUpload4(BattleVideoRecord4 record);
public abstract BattleVideoHeader4[] BattleVideoSearch4(ushort species, BattleVideoMetagames4 metagame, byte country, byte region, int count);
public abstract BattleVideoRecord4 BattleVideoGet4(long serial);
#endregion
}
}

View File

@ -740,7 +740,7 @@ namespace PkmnFoundations.Data
{
int rows = tran.ExecuteNonQuery("INSERT INTO TerminalDressup4 (pid, SerialNumber, " +
"Data, md5, TimeAdded, ParseVersion, Species) VALUES (@pid, @serial, @data, " +
"unhex(md5(@data)), UTC_TIMESTAMP(), 1, @species); SELECT LAST_INSERT_ID()",
"unhex(md5(@data)), UTC_TIMESTAMP(), 1, @species)",
new MySqlParameter("@pid", record.PID),
new MySqlParameter("@serial", record.SerialNumber),
new MySqlParameter("@data", record.Data),
@ -810,7 +810,7 @@ namespace PkmnFoundations.Data
{
int rows = tran.ExecuteNonQuery("INSERT INTO TerminalBoxes4 (pid, SerialNumber, " +
"Data, md5, TimeAdded, ParseVersion, Label) VALUES (@pid, @serial, @data, " +
"unhex(md5(@data)), UTC_TIMESTAMP(), 1, @label); SELECT LAST_INSERT_ID()",
"unhex(md5(@data)), UTC_TIMESTAMP(), 1, @label)",
new MySqlParameter("@pid", record.PID),
new MySqlParameter("@serial", record.SerialNumber),
new MySqlParameter("@data", record.Data),
@ -854,6 +854,191 @@ namespace PkmnFoundations.Data
return new BoxRecord4(reader.GetInt32(0), (BoxLabels4)reader.GetInt32(1), reader.GetInt64(2), data);
}
public override long BattleVideoUpload4(BattleVideoRecord4 record)
{
if (record.Data.Length != 7272) throw new ArgumentException();
if (record.Header.Data.Length != 228) throw new ArgumentException();
using (MySqlConnection db = CreateConnection())
{
db.Open();
using (MySqlTransaction tran = db.BeginTransaction())
{
long exists = (long)tran.ExecuteScalar("SELECT EXISTS(SELECT * " +
"FROM TerminalBattleVideos4 WHERE md5 = unhex(md5(CONCAT(@header, @data))) " +
"AND Data = @data AND Header = @header)",
new MySqlParameter("@header", record.Header.Data),
new MySqlParameter("@data", record.Data));
if (exists != 0) return 0;
if (record.SerialNumber == 0)
{
ulong key = (ulong)tran.ExecuteScalar("INSERT INTO TerminalBattleVideos4 " +
"(pid, Header, Data, md5, TimeAdded, ParseVersion, TrainerName, " +
"Metagame, Country, Region) " +
"VALUES (@pid, @header, @data, unhex(md5(CONCAT(@header, @data))), " +
"UTC_TIMESTAMP(), 1, @trainer, @metagame, @country, @region); " +
"SELECT LAST_INSERT_ID()",
new MySqlParameter("@pid", record.PID),
new MySqlParameter("@header", record.Header.Data),
new MySqlParameter("@data", record.Data),
new MySqlParameter("@trainer", record.Header.TrainerName),
new MySqlParameter("@metagame", (byte)record.Header.Metagame),
new MySqlParameter("@country", (byte)record.Header.Country),
new MySqlParameter("@region", (byte)record.Header.Region)
);
long serial = BattleVideoHeader4.KeyToSerial((long)key);
tran.ExecuteNonQuery("UPDATE TerminalBattleVideos4 SET " +
"SerialNumber = @serial WHERE id = @key",
new MySqlParameter("@serial", serial),
new MySqlParameter("@key", key));
// todo: make a proc to insert both video and party.
InsertBattleVideoParty(record.Header, key, tran);
tran.Commit();
return serial;
}
else
{
ulong key = (ulong)BattleVideoHeader4.SerialToKey(record.SerialNumber);
int rows = tran.ExecuteNonQuery("INSERT INTO TerminalBattleVideoPokemon4 " +
"(id, pid, SerialNumber, Header, Data, md5, TimeAdded, ParseVersion, TrainerName, " +
"Metagame, Country, Region) " +
"VALUES (@key, @pid, @serial, @header, @data, unhex(md5(CONCAT(@header, @data))), " +
"UTC_TIMESTAMP(), 1, @trainer, @metagame, @country, @region)",
new MySqlParameter("@key", key),
new MySqlParameter("@pid", record.PID),
new MySqlParameter("@serial", record.SerialNumber),
new MySqlParameter("@header", record.Header.Data),
new MySqlParameter("@data", record.Data),
new MySqlParameter("@trainer", record.Header.TrainerName),
new MySqlParameter("@metagame", (byte)record.Header.Metagame),
new MySqlParameter("@country", (byte)record.Header.Country),
new MySqlParameter("@region", (byte)record.Header.Region)
);
if (rows == 0) return 0;
InsertBattleVideoParty(record.Header, key, tran);
tran.Commit();
return record.SerialNumber;
}
}
}
}
private void InsertBattleVideoParty(BattleVideoHeader4 header, ulong key, MySqlTransaction tran)
{
MySqlCommand cmd = new MySqlCommand("INSERT INTO " +
"TermainalBattleVideoPokemon4 (video_id, Slot, Species) VALUES " +
"(@key, @slot, @species)", tran.Connection, tran);
cmd.Parameters.Add("@key", MySqlDbType.UInt64).Value = key;
cmd.Parameters.Add("@slot", MySqlDbType.UByte);
cmd.Parameters.Add("@species", MySqlDbType.UInt16);
ushort[] party = header.Party;
for (byte x = 0; x < 12; x++)
{
ushort species = party[x];
if (species == 0) continue;
cmd.Parameters["@slot"].Value = x;
cmd.Parameters["@species"].Value = species;
cmd.ExecuteNonQuery();
}
}
public override BattleVideoHeader4[] BattleVideoSearch4(ushort species, BattleVideoMetagames4 metagame, byte country, byte region, int count)
{
using (MySqlConnection db = CreateConnection())
{
List<MySqlParameter> _params = new List<MySqlParameter>();
String where = "";
bool hasSearch = false;
if (species != 0xffff)
{
where += " WHERE EXISTS(SELECT * FROM TerminalBattleVideoPokemon4 " +
"WHERE video_id = TerminalBattleVideos4.id AND Species = @species)";
_params.Add(new MySqlParameter("@species", species));
hasSearch = true;
}
if (metagame != BattleVideoMetagames4.Latest30)
{
where += (hasSearch ? " AND " : " WHERE ") + "Metagame = @metagame";
_params.Add(new MySqlParameter("@metagame", (byte)metagame));
hasSearch = true;
}
if (country != 0xff)
{
where += (hasSearch ? " AND " : " WHERE ") + "Country = @country";
_params.Add(new MySqlParameter("@country", country));
hasSearch = true;
}
if (region != 0xff)
{
where += (hasSearch ? " AND " : " WHERE ") + "Region = @region";
_params.Add(new MySqlParameter("@region", region));
}
_params.Add(new MySqlParameter("@count", count));
db.Open();
List<BattleVideoHeader4> results = new List<BattleVideoHeader4>(count);
MySqlDataReader reader = (MySqlDataReader)db.ExecuteReader("SELECT pid, " +
"SerialNumber, Header FROM TerminalBattleVideos4" + where +
" ORDER BY TimeAdded DESC LIMIT @count",
_params.ToArray());
while (reader.Read())
{
results.Add(BattleVideoHeader4FromReader(reader));
}
reader.Close();
db.Close();
return results.ToArray();
}
}
private BattleVideoHeader4 BattleVideoHeader4FromReader(MySqlDataReader reader)
{
byte[] data = new byte[228];
reader.GetBytes(2, 0, data, 0, 228);
return new BattleVideoHeader4(reader.GetInt32(0), reader.GetInt64(1), data);
}
public override BattleVideoRecord4 BattleVideoGet4(long serial)
{
using (MySqlConnection db = CreateConnection())
{
db.Open();
MySqlDataReader reader = (MySqlDataReader)db.ExecuteReader("SELECT pid, " +
"SerialNumber, Header, Data FROM TerminalBattleVideos4 " +
"WHERE SerialNumber = @serial",
new MySqlParameter("@serial", serial));
if (reader.HasRows)
return BattleVideo4FromReader(reader);
else return null;
}
}
private BattleVideoRecord4 BattleVideo4FromReader(MySqlDataReader reader)
{
byte[] data = new byte[7272];
reader.GetBytes(3, 0, data, 0, 7272);
BattleVideoHeader4 header = BattleVideoHeader4FromReader(reader);
return new BattleVideoRecord4(header.PID, header.SerialNumber, header, data);
}
#endregion
}

View File

@ -25,19 +25,29 @@ namespace PkmnFoundations.Structures
public long SerialNumber;
public byte[] Data;
public short[] Party
public ushort[] Party
{
get
{
short[] result = new short[12];
ushort[] result = new ushort[12];
for (int x = 0; x < result.Length; x++)
{
result[x] = BitConverter.ToInt16(Data, 0x7c + x * 2);
result[x] = BitConverter.ToUInt16(Data, 0x7c + x * 2);
}
return result;
}
}
public byte[] TrainerName
{
get
{
byte[] result = new byte[16];
Array.Copy(Data, 0, result, 0, 16);
return result;
}
}
public BattleVideoMetagames4 Metagame
{
get
@ -229,6 +239,16 @@ namespace PkmnFoundations.Structures
}
#endregion
public static String FormatSerial(long serial)
{
String number = serial.ToString("D12");
String[] split = new String[3];
split[0] = number.Substring(0, number.Length - 10);
split[1] = number.Substring(number.Length - 10, 5);
split[2] = number.Substring(number.Length - 5, 5);
return String.Join("-", split);
}
}
public enum BattleVideoMetagames4 : byte

View File

@ -5,7 +5,54 @@ using System.Text;
namespace PkmnFoundations.Structures
{
class BattleVideoRecord4
public class BattleVideoRecord4
{
public BattleVideoRecord4()
{
}
public BattleVideoRecord4(int pid, long serial_number, byte[] data)
{
if (data.Length != 7500) throw new ArgumentException("Battle video data must be 7500 bytes.");
byte[] data_head = new byte[228];
byte[] data_main = new byte[7272];
Array.Copy(data, 0, data_head, 0, 228);
Array.Copy(data, 228, data_main, 0, 7272);
PID = pid;
SerialNumber = serial_number;
Header = new BattleVideoHeader4(pid, serial_number, data_head);
Data = data_main;
}
public BattleVideoRecord4(int pid, long serial_number, BattleVideoHeader4 header, byte[] data_main)
{
if (data_main.Length != 7272) throw new ArgumentException("Battle video main data must be 7500 bytes.");
PID = pid;
SerialNumber = serial_number;
Header = header;
Data = data_main;
}
public int PID;
public long SerialNumber;
public BattleVideoHeader4 Header;
public byte[] Data;
public BattleVideoRecord4 Clone()
{
return new BattleVideoRecord4(PID, SerialNumber, Header.Clone(), Data.ToArray());
}
public byte[] Save()
{
byte[] result = new byte[7500];
Array.Copy(Header.Data, 0, result, 0, 228);
Array.Copy(Data, 228, result, 0, 7272);
return result;
}
}
}