package gen7 import ( "bytes" "encoding/binary" "fmt" "strings" "time" "unicode/utf16" p "github.com/ajswis/go-pkparse-server/pokemon" ) type consoleRegion map[uint8]string // Parse accepts a raw pokemon (byte slice) and either returns a Pokemon struct // with all applicable fields populated or an error depicting the reason for // failure. func Parse(rawPokemon p.RawPokemon) (*p.Pokemon, error) { if err := validateRawPokemon(rawPokemon); err != nil { return nil, err } consoleRegions := consoleRegion{0x00: "J", 0x01: "U", 0x02: "E", 0x03: "?", 0x04: "C", 0x05: "K", 0x06: "T"} var pkmn p.Pokemon pkmn.RawPokemon = &rawPokemon // Block A pkmn.EncryptionConstant = binary.LittleEndian.Uint32(rawPokemon[0x00:0x08]) pkmn.PokedexNumber = toUint16(rawPokemon[0x08:0x0a]) pkmn.HeldItemID = toUint16(rawPokemon[0x0a:0x0c]) pkmn.TrainerID = toUint16(rawPokemon[0x0c:0x0e]) pkmn.SecretID = toUint16(rawPokemon[0x0e:0x10]) pkmn.FullTrainerID = binary.LittleEndian.Uint32(rawPokemon[0x0c:0x10]) pkmn.Experience = binary.LittleEndian.Uint32(rawPokemon[0x10:0x15]) pkmn.AbilityID = rawPokemon[0x14] pkmn.AbilityNum = rawPokemon[0x14] pkmn.PID = binary.LittleEndian.Uint32(rawPokemon[0x18:0x1c]) pkmn.NatureID = rawPokemon[0x1c] pkmnGenderByte := rawPokemon[0x1d] pkmn.FatefulEncounter = (pkmnGenderByte & 0x01) == 1 if (pkmnGenderByte & 0x02) == 0x02 { pkmn.Gender = "F" } else if (pkmnGenderByte & 0x04) == 0x04 { pkmn.Gender = "" } else { pkmn.Gender = "M" } pkmn.FormID = pkmnGenderByte >> 3 pkmn.HPEV = rawPokemon[0x1e] pkmn.AtkEV = rawPokemon[0x1f] pkmn.DefEV = rawPokemon[0x20] pkmn.SpeEV = rawPokemon[0x21] pkmn.SpAtkEV = rawPokemon[0x22] pkmn.SpDefEV = rawPokemon[0x23] pokerusByte := rawPokemon[0x2b] pkmn.PokerusDuration = pokerusByte & 15 pkmn.PokerusStrain = pokerusByte >> 4 // Block B pkmn.RawNickname = rawPokemon[0x40:0x58] pkmn.Nickname = binaryToUTF16leString(pkmn.RawNickname) pkmn.Move1ID = toUint16(rawPokemon[0x5a:0x5c]) pkmn.Move2ID = toUint16(rawPokemon[0x5c:0x5e]) pkmn.Move3ID = toUint16(rawPokemon[0x5e:0x60]) pkmn.Move4ID = toUint16(rawPokemon[0x60:0x62]) pkmn.Move1PP = rawPokemon[0x62] pkmn.Move2PP = rawPokemon[0x63] pkmn.Move3PP = rawPokemon[0x64] pkmn.Move4PP = rawPokemon[0x65] pkmn.Move1PPUsed = rawPokemon[0x66] pkmn.Move2PPUsed = rawPokemon[0x67] pkmn.Move3PPUsed = rawPokemon[0x68] pkmn.Move4PPUsed = rawPokemon[0x69] pkmn.EggMove1ID = toUint16(rawPokemon[0x6a:0x6c]) pkmn.EggMove2ID = toUint16(rawPokemon[0x6c:0x6e]) pkmn.EggMove3ID = toUint16(rawPokemon[0x6e:0x70]) pkmn.EggMove4ID = toUint16(rawPokemon[0x70:0x72]) ivBytes := binary.LittleEndian.Uint32(rawPokemon[0x74:0x78]) pkmn.HPIV = ivBytes & 0x1f pkmn.AtkIV = ivBytes >> 5 & 0x1f pkmn.DefIV = ivBytes >> 10 & 0x1f pkmn.SpeIV = ivBytes >> 15 & 0x1f pkmn.SpAtkIV = ivBytes >> 20 & 0x1f pkmn.SpDefIV = ivBytes >> 25 & 0x1f pkmn.IsEgg = ((ivBytes >> 30) % 2) == 1 pkmn.IsNicknamed = ((ivBytes >> 31) % 2) == 1 // Block C pkmn.RawNotOTName = rawPokemon[0x78:0x90] pkmn.NotOTName = binaryToUTF16leString(pkmn.RawNotOTName) if rawPokemon[0x92] == 1 { pkmn.NotOTGender = "F" } else { pkmn.NotOTGender = "M" } pkmn.CurrentHandlerIsOT = rawPokemon[0x93] == 1 pkmn.GeoLocation1RegionID = rawPokemon[0x94] pkmn.GeoLocation1CountryID = rawPokemon[0x95] pkmn.GeoLocation2RegionID = rawPokemon[0x96] pkmn.GeoLocation2CountryID = rawPokemon[0x97] pkmn.GeoLocation3RegionID = rawPokemon[0x98] pkmn.GeoLocation3CountryID = rawPokemon[0x99] pkmn.GeoLocation4RegionID = rawPokemon[0x9a] pkmn.GeoLocation4CountryID = rawPokemon[0x9b] pkmn.GeoLocation5RegionID = rawPokemon[0x9c] pkmn.GeoLocation5CountryID = rawPokemon[0x9d] pkmn.NotOTFriendship = rawPokemon[0xa2] pkmn.NotOTAffection = rawPokemon[0xa3] pkmn.NotOTMemoryIntensity = rawPokemon[0xa4] pkmn.NotOTMemoryLine = rawPokemon[0xa5] pkmn.NotOTMemoryFeeling = rawPokemon[0xa6] pkmn.NotOTMemoryTextVar = toUint16(rawPokemon[0xa8:0xaa]) pkmn.Fullness = rawPokemon[0xae] pkmn.Enjoyment = rawPokemon[0xaf] // Block D pkmn.RawOTName = rawPokemon[0xb0:0xc8] pkmn.OTName = binaryToUTF16leString(pkmn.RawOTName) pkmn.OTFriendship = rawPokemon[0xca] pkmn.OTAffection = rawPokemon[0xcb] pkmn.OTMemoryIntensity = rawPokemon[0xcc] pkmn.OTMemoryLine = rawPokemon[0xcd] pkmn.OTMemoryTextVar = toUint16(rawPokemon[0xce:0xd0]) pkmn.OTMemoryFeeling = rawPokemon[0xd0] pkmn.DateEggReceived = uint16ToDatetime(binary.LittleEndian.Uint32(rawPokemon[0xd0:0xd4])) pkmn.DateMet = uint16ToDatetime(binary.LittleEndian.Uint32(rawPokemon[0xd4:0xd8])) pkmn.EggLocationID = toUint16(rawPokemon[0xd8:0xda]) pkmn.MetLocationID = toUint16(rawPokemon[0xda:0xdc]) pkmn.PokeballID = rawPokemon[0xdc] pkmn.LevelMet = rawPokemon[0xdd] & 0x7f if otGenderBit := rawPokemon[0xdd] >> 7; otGenderBit == 1 { pkmn.OTGender = "F" } else { pkmn.OTGender = "M" } pkmn.Gen4EncounterTypeID = rawPokemon[0xde] pkmn.OTGameID = rawPokemon[0xdf] pkmn.CountryID = rawPokemon[0xe0] pkmn.RegionID = rawPokemon[0xe1] pkmn.RawConsoleRegion = rawPokemon[0xe2] pkmn.ConsoleRegion = consoleRegions[pkmn.RawConsoleRegion] pkmn.OTLanguageID = rawPokemon[0xe3] return &pkmn, nil } func binaryToUTF16leString(b []byte) string { r := bytes.NewReader(b) u16 := make([]uint16, len(b)/2) binary.Read(r, binary.LittleEndian, &u16) runes := utf16.Decode(u16) s := strings.Split(string(runes), "\u0000")[0] return s } func calculateChecksum(b []byte) uint16 { var sum uint16 for i := 8; i < 232; i += 2 { sum += toUint16(b[i : i+2]) } return sum & 0xffff } func toUint16(b []byte) uint16 { return binary.LittleEndian.Uint16(b) } func validateRawPokemon(rawPokemon p.RawPokemon) error { if l := len(rawPokemon); l != 232 && l != 260 { return fmt.Errorf("Invalid length for generation 7 pokemon: %d bytes", l) } checksumValue := toUint16(rawPokemon[0x06:0x08]) if calculatedChecksum := calculateChecksum(rawPokemon); calculatedChecksum != checksumValue { return fmt.Errorf("Invalid checksum for generation 7 pokemon") } if toUint16(rawPokemon[0x04:0x06]) == 1 || rawPokemon[0x58] == 1 || rawPokemon[0x90] == 1 || rawPokemon[0xc8] == 1 { return fmt.Errorf("Invalid bytes supplied for generation 7 pokemon") } return nil } func uint16ToDatetime(i uint32) int64 { if i == 0 { return 0 } year := int((i & 0xff) + 2000) month := time.Month(int((i >> 8) & 0xff)) day := int((i >> 16) & 0xff) date := time.Date(year, month, day, 0, 0, 0, 0, time.UTC) return date.Unix() }