mirror of
https://github.com/mm201/pkmn-classic-framework.git
synced 2026-03-22 09:54:09 -05:00
322 lines
10 KiB
C#
322 lines
10 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Text;
|
||
|
||
namespace PkmnFoundations.Structures
|
||
{
|
||
public class BattleVideoHeader4
|
||
{
|
||
public BattleVideoHeader4()
|
||
{
|
||
}
|
||
|
||
public BattleVideoHeader4(int pid, ulong serial_number, byte[] data)
|
||
{
|
||
if (data.Length != 228) throw new ArgumentException("Battle video header data must be 228 bytes.");
|
||
|
||
PID = pid;
|
||
SerialNumber = serial_number;
|
||
Data = data;
|
||
}
|
||
|
||
// todo: encapsulate these so calculated fields are always correct
|
||
public int PID;
|
||
public ulong SerialNumber;
|
||
public byte[] Data;
|
||
|
||
public ushort Streak
|
||
{
|
||
get
|
||
{
|
||
return BitConverter.ToUInt16(Data, 0xa4);
|
||
}
|
||
}
|
||
|
||
public ushort[] Party
|
||
{
|
||
get
|
||
{
|
||
ushort[] result = new ushort[12];
|
||
for (int x = 0; x < result.Length; x++)
|
||
{
|
||
result[x] = BitConverter.ToUInt16(Data, 0x80 + 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
|
||
{
|
||
return (BattleVideoMetagames4)Data[0xa6];
|
||
}
|
||
}
|
||
|
||
public byte Country
|
||
{
|
||
get
|
||
{
|
||
return Data[0x17];
|
||
}
|
||
}
|
||
|
||
public byte Region
|
||
{
|
||
get
|
||
{
|
||
return Data[0x18];
|
||
}
|
||
}
|
||
|
||
public BattleVideoHeader4 Clone()
|
||
{
|
||
return new BattleVideoHeader4(PID, SerialNumber, Data.ToArray());
|
||
}
|
||
|
||
#region ID number calculation
|
||
// Generating battle video IDs:
|
||
//
|
||
// STEP 1: Prepping the Template:
|
||
// Start with an auto-incrementing primary key. Treat as a string of 12
|
||
// decimal digits. Pad with 0s. We call this the Key.
|
||
//
|
||
// The ones place (rightmost) is used to select one of ten fixed
|
||
// Templates. A Template has 12 (11 useful) digits. They are basically
|
||
// hardcoded random-looking numbers. The rightmost digit of each
|
||
// template is undefined/unused. The leftmost digit is never 0.
|
||
//
|
||
// The Key’s tens place and hundreds place are added together mod-10 to
|
||
// get a constant which is used to roll each of the digits in the
|
||
// Template by. Here, rolling means adding in mod-10.
|
||
//
|
||
// The Template’s leftmost digit is rolled in mod-9 instead of 10.
|
||
// (While rolling, skip the number 0.) If the value in the
|
||
// second-leftmost digit wraps around to or past 0, this leftmost digit
|
||
// is rolled one less time than it would be otherwise.
|
||
//
|
||
// Note: There is probably a better way to represent this algorithm
|
||
// that doesn't require special handling for the leftmost digit based
|
||
// on what the second one is doing. It would involve choosing different
|
||
// Template constants and using a different roll formula.
|
||
//
|
||
// STEP 2: Prepping the Key:
|
||
// Remove the tens place from the Key, then shift all the digits
|
||
// EXCEPT the leftmost and rightmost one place to the right to fill the
|
||
// gap. The second leftmost digit is filled with a constant 0.
|
||
// (Example: 123333345678 becomes 102333334568)
|
||
//
|
||
// Note: This handling of the left two digits is an extrapolation on
|
||
// my part. It gives us the maximum possible range of 10-00000-00000
|
||
// through to 99-99999-99999 in a 1:1 mapping.
|
||
//
|
||
// STEP 3: Putting it together:
|
||
// Overwrite the rightmost digit of the Template with the Key’s ones
|
||
// place digit.
|
||
//
|
||
// For each remaining digit, roll the Template *backwards* a number of
|
||
// times equal to the Key’s digit in that place.
|
||
|
||
private static byte[][] m_templates = new byte[][]{
|
||
new byte[]{6, 9, 3, 6, 1, 2, 7, 5, 2, 2, 4, 0},
|
||
new byte[]{8, 0, 7, 2, 6, 2, 4, 9, 1, 4, 4, 1},
|
||
new byte[]{1, 4, 9, 7, 3, 8, 5, 7, 6, 4, 7, 2},
|
||
new byte[]{9, 4, 1, 7, 6, 3, 9, 6, 2, 7, 5, 3},
|
||
new byte[]{4, 6, 4, 2, 9, 9, 7, 3, 4, 6, 1, 4},
|
||
new byte[]{9, 5, 1, 0, 9, 7, 8, 6, 6, 3, 5, 5},
|
||
new byte[]{8, 8, 7, 4, 4, 4, 3, 4, 6, 2, 9, 6},
|
||
new byte[]{6, 3, 7, 7, 5, 6, 7, 6, 1, 9, 5, 7},
|
||
new byte[]{2, 6, 7, 2, 3, 2, 4, 5, 8, 9, 7, 8},
|
||
new byte[]{2, 1, 9, 1, 7, 9, 9, 1, 6, 5, 6, 9}
|
||
};
|
||
|
||
/// <summary>
|
||
/// Converts a primary key (auto incrementing) into a Battle Video ID.
|
||
/// </summary>
|
||
public static ulong KeyToSerial(ulong key)
|
||
{
|
||
if (key > 899999999999L || key < 0L) throw new ArgumentOutOfRangeException();
|
||
|
||
byte[] keyDigits = LongToDigits(key);
|
||
byte[] serialDigits = m_templates[keyDigits[11]].ToArray();
|
||
|
||
byte valueShift = 0;
|
||
valueShift += keyDigits[9];
|
||
valueShift += keyDigits[10];
|
||
valueShift %= 10;
|
||
|
||
if (valueShift + serialDigits[1] > 9)
|
||
serialDigits[0]--;
|
||
|
||
for (int x = 0; x < 11; x++)
|
||
{
|
||
serialDigits[x] += valueShift;
|
||
}
|
||
|
||
serialDigits[0] += 9;
|
||
serialDigits[0] -= keyDigits[0];
|
||
serialDigits[0] %= 9;
|
||
if (serialDigits[0] == 0) serialDigits[0] = 9;
|
||
|
||
serialDigits[1] %= 10;
|
||
|
||
for (int x = 1; x < 10; x++)
|
||
{
|
||
serialDigits[x + 1] += 10;
|
||
serialDigits[x + 1] -= keyDigits[x];
|
||
serialDigits[x + 1] %= 10;
|
||
}
|
||
|
||
return DigitsToLong(serialDigits);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Converts Battle Video ID back into a primary key.
|
||
/// </summary>
|
||
public static ulong SerialToKey(ulong serial)
|
||
{
|
||
if (serial > 999999999999L || serial < 100000000000L)
|
||
throw new ArgumentOutOfRangeException();
|
||
|
||
byte[] serialDigits = LongToDigits(serial);
|
||
byte[] templateDigits = m_templates[serialDigits[11]];
|
||
|
||
serialDigits[0] = (byte)(10 + templateDigits[0] - serialDigits[0]);
|
||
for (int x = 1; x < 11; x++)
|
||
{
|
||
serialDigits[x] = (byte)(10 + templateDigits[x] - serialDigits[x]);
|
||
}
|
||
|
||
byte valueShift = (byte)(serialDigits[1] % 10);
|
||
if (templateDigits[1] - valueShift >= 0)
|
||
serialDigits[0]--;
|
||
|
||
serialDigits[0] += (byte)(9 - valueShift);
|
||
serialDigits[0] %= 9;
|
||
|
||
for (int x = 1; x < 11; x++)
|
||
{
|
||
serialDigits[x] += (byte)(10 - valueShift);
|
||
serialDigits[x] %= 10;
|
||
}
|
||
for (int x = 1; x < 10; x++)
|
||
{
|
||
serialDigits[x] = serialDigits[x + 1];
|
||
}
|
||
|
||
serialDigits[10] = (byte)((20 - valueShift - serialDigits[9]) % 10);
|
||
|
||
return DigitsToLong(serialDigits);
|
||
}
|
||
|
||
private static byte[] LongToDigits(ulong value)
|
||
{
|
||
if (value > 999999999999L || value < 0L) throw new ArgumentException();
|
||
byte[] result = new byte[12];
|
||
for (int x = 11; x >= 0; x--)
|
||
{
|
||
result[x] = (byte)(value % 10);
|
||
value /= 10;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
private static ulong DigitsToLong(byte[] digits)
|
||
{
|
||
if (digits.Length != 12) throw new ArgumentException();
|
||
ulong result = 0;
|
||
ulong pow = 1;
|
||
for (int x = 11; x >= 0; x--)
|
||
{
|
||
if (digits[x] > 9) throw new ArgumentException();
|
||
result += digits[x] * pow;
|
||
pow *= 10;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
#endregion
|
||
|
||
public static String FormatSerial(ulong 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
|
||
{
|
||
ColosseumSingleNoRestrictions = 0x00,
|
||
ColosseumSingleStandardCup = 0x01,
|
||
ColosseumSingleFancyCup = 0x02,
|
||
ColosseumSingleLittleCup = 0x03,
|
||
ColosseumSingleLightCup = 0x04,
|
||
ColosseumSingleDoubleCup = 0x05,
|
||
ColosseumSingleOtherCup = 0x06,
|
||
|
||
ColosseumDoubleNoRestrictions = 0x07,
|
||
ColosseumDoubleStandardCup = 0x08,
|
||
ColosseumDoubleFancyCup = 0x09,
|
||
ColosseumDoubleLittleCup = 0x0a,
|
||
ColosseumDoubleLightCup = 0x0b,
|
||
ColosseumDoubleDoubleCup = 0x0c,
|
||
ColosseumDoubleOtherCup = 0x0d,
|
||
|
||
ColosseumMulti = 0x0e,
|
||
|
||
BattleTowerSingle = 0x0f,
|
||
BattleTowerDouble = 0x10,
|
||
BattleTowerMulti = 0x11,
|
||
|
||
BattleFactoryLv50Single = 0x12,
|
||
BattleFactoryLv50Double = 0x13,
|
||
BattleFactoryLv50Multi = 0x14,
|
||
|
||
BattleFactoryOpenSingle = 0x15,
|
||
BattleFactoryOpenDouble = 0x16,
|
||
BattleFactoryOpenMulti = 0x17,
|
||
|
||
BattleHallSingle = 0x18,
|
||
BattleHallDouble = 0x19,
|
||
BattleHallMulti = 0x1a,
|
||
|
||
BattleCastleSingle = 0x1b,
|
||
BattleCastleDouble = 0x1c,
|
||
BattleCastleMulti = 0x1d,
|
||
|
||
BattleArcadeSingle = 0x1e,
|
||
BattleArcadeDouble = 0x1f,
|
||
BattleArcadeMulti = 0x20,
|
||
|
||
SearchColosseumSingleNoRestrictions = 0xfa,
|
||
SearchColosseumSingleCupMatch = 0xfb,
|
||
SearchColosseumDoubleNoRestrictions = 0xfc,
|
||
SearchColosseumDoubleCupMatch = 0xfd,
|
||
// fixme: There's a search type 0xfe showing in my logs.
|
||
// https://github.com/mm201/pkmnFoundations/issues/9
|
||
SearchLatest30 = 0xff,
|
||
}
|
||
|
||
public enum BattleVideoRankings4 : uint
|
||
{
|
||
None = 0x00000000,
|
||
Colosseum = 0x00000001,
|
||
BattleFrontier = 0x00000002,
|
||
}
|
||
}
|