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} }; /// /// Converts a primary key (auto incrementing) into a Battle Video ID. /// 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); } /// /// Converts Battle Video ID back into a primary key. /// 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, } }