350 lines
13 KiB
Python
350 lines
13 KiB
Python
import orjson
|
||
from typing import Any, Dict, List, Optional, Tuple, Iterable
|
||
|
||
from .data import NATIONAL_ID_TO_SPECIES_ID, data
|
||
|
||
|
||
CHARACTER_DECODING_MAP = {
|
||
0x00: " ", 0x01: "À", 0x02: "Á", 0x03: "Â", 0x04: "Ç",
|
||
0x05: "È", 0x06: "É", 0x07: "Ê", 0x08: "Ë", 0x09: "Ì",
|
||
0x0B: "Î", 0x0C: "Ï", 0x0D: "Ò", 0x0E: "Ó", 0x0F: "Ô",
|
||
0x10: "Œ", 0x11: "Ù", 0x12: "Ú", 0x13: "Û", 0x14: "Ñ",
|
||
0x15: "ß", 0x16: "à", 0x17: "á", 0x19: "ç", 0x1A: "è",
|
||
0x1B: "é", 0x1C: "ê", 0x1D: "ë", 0x1E: "ì", 0x20: "î",
|
||
0x21: "ï", 0x22: "ò", 0x23: "ó", 0x24: "ô", 0x25: "œ",
|
||
0x26: "ù", 0x27: "ú", 0x28: "û", 0x29: "ñ", 0x2A: "°",
|
||
0x2B: "ª", 0x2D: "&", 0x2E: "+", 0x35: "=", 0x36: ";",
|
||
0x50: "▯", 0x51: "¿", 0x52: "¡", 0x5A: "Í", 0x5B: "%",
|
||
0x5C: "(", 0x5D: ")", 0x68: "â", 0x6F: "í", 0x79: "⬆",
|
||
0x7A: "⬇", 0x7B: "⬅", 0x7C: "➡", 0x7D: "*", 0x84: "ᵉ",
|
||
0x85: "<", 0x86: ">", 0xA1: "0", 0xA2: "1", 0xA3: "2",
|
||
0xA4: "3", 0xA5: "4", 0xA6: "5", 0xA7: "6", 0xA8: "7",
|
||
0xA9: "8", 0xAA: "9", 0xAB: "!", 0xAC: "?", 0xAD: ".",
|
||
0xAE: "-", 0xB0: "…", 0xB1: "“", 0xB2: "”", 0xB3: "‘",
|
||
0xB4: "’", 0xB5: "♂", 0xB6: "♀", 0xB8: ",", 0xB9: "×",
|
||
0xBA: "/", 0xBB: "A", 0xBC: "B", 0xBD: "C", 0xBE: "D",
|
||
0xBF: "E", 0xC0: "F", 0xC1: "G", 0xC2: "H", 0xC3: "I",
|
||
0xC4: "J", 0xC5: "K", 0xC6: "L", 0xC7: "M", 0xC8: "N",
|
||
0xC9: "O", 0xCA: "P", 0xCB: "Q", 0xCC: "R", 0xCD: "S",
|
||
0xCE: "T", 0xCF: "U", 0xD0: "V", 0xD1: "W", 0xD2: "X",
|
||
0xD3: "Y", 0xD4: "Z", 0xD5: "a", 0xD6: "b", 0xD7: "c",
|
||
0xD8: "d", 0xD9: "e", 0xDA: "f", 0xDB: "g", 0xDC: "h",
|
||
0xDD: "i", 0xDE: "j", 0xDF: "k", 0xE0: "l", 0xE1: "m",
|
||
0xE2: "n", 0xE3: "o", 0xE4: "p", 0xE5: "q", 0xE6: "r",
|
||
0xE7: "s", 0xE8: "t", 0xE9: "u", 0xEA: "v", 0xEB: "w",
|
||
0xEC: "x", 0xED: "y", 0xEE: "z", 0xEF: "▶", 0xF0: ":",
|
||
}
|
||
|
||
CHARACTER_ENCODING_MAP = {value: key for key, value in CHARACTER_DECODING_MAP.items()}
|
||
CHARACTER_ENCODING_MAP.update({
|
||
"'": CHARACTER_ENCODING_MAP["’"],
|
||
"\"": CHARACTER_ENCODING_MAP["”"],
|
||
"_": CHARACTER_ENCODING_MAP[" "],
|
||
})
|
||
|
||
ALLOWED_TRAINER_NAME_CHARACTERS = frozenset({
|
||
" ", "0", "1", "2", "3", "4", "5", "6", "7", "8",
|
||
"9", "!", "?", ".", "-", "…", "“", "”", "‘", "’",
|
||
"♂", "♀", ",", "/", "A", "B", "C", "D", "E", "F",
|
||
"G", "H", "I", "J", "K", "L", "M", "N", "O", "P",
|
||
"Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
|
||
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j",
|
||
"k", "l", "m", "n", "o", "p", "q", "r", "s", "t",
|
||
"u", "v", "w", "x", "y", "z",
|
||
})
|
||
|
||
|
||
def encode_string(string: str, length: Optional[int] = None) -> bytes:
|
||
arr = []
|
||
length = len(string) if length is None else length
|
||
|
||
for i in range(length):
|
||
if i >= len(string):
|
||
arr.append(0xFF)
|
||
continue
|
||
|
||
char = string[i]
|
||
if char in CHARACTER_ENCODING_MAP:
|
||
arr.append(CHARACTER_ENCODING_MAP[char])
|
||
else:
|
||
arr.append(CHARACTER_ENCODING_MAP["?"])
|
||
|
||
return bytes(arr)
|
||
|
||
|
||
def decode_string(string_data: Iterable[int]) -> str:
|
||
string = ""
|
||
for code in string_data:
|
||
if code == 0xFF:
|
||
break
|
||
|
||
if code in CHARACTER_DECODING_MAP:
|
||
string += CHARACTER_DECODING_MAP[code]
|
||
else:
|
||
raise KeyError(f"The following value does not correspond to a character in Pokemon Emerald: {code}")
|
||
|
||
return string
|
||
|
||
|
||
def get_easter_egg(easter_egg: str) -> Tuple[int, int]:
|
||
easter_egg = easter_egg.upper()
|
||
result1 = 0
|
||
result2 = 0
|
||
for c in easter_egg:
|
||
result1 = ((result1 << 5) - result1 + ord(c)) & 0xFFFFFFFF
|
||
result2 = ((result2 << 4) - result2 + ord(c)) & 0xFF
|
||
|
||
if result1 == 0x9137C17B:
|
||
value = (result2 + 23) & 0xFF
|
||
if value > 0 and (value < 252 or (value > 276 and value < 412)):
|
||
return (1, value)
|
||
elif result1 == 0x9AECC7C6:
|
||
value = (result2 + 64) & 0xFF
|
||
if value > 0 and value < 355:
|
||
return (2, value)
|
||
elif result1 == 0x506D2690:
|
||
value = (result2 + 169) & 0xFF
|
||
if value > 0 and value < 78:
|
||
return (3, value)
|
||
elif result1 == 0xA7850E45 and (result1 ^ result2) & 0xFF == 96:
|
||
return (4, 0)
|
||
|
||
return (0, 0)
|
||
|
||
|
||
def location_name_to_label(name: str) -> str:
|
||
return data.locations[name].label
|
||
|
||
|
||
def int_to_bool_array(num: int) -> List[bool]:
|
||
binary_string = format(num, "064b")
|
||
bool_array = [bit == "1" for bit in reversed(binary_string)]
|
||
return bool_array
|
||
|
||
|
||
def bool_array_to_int(bool_array: List[bool]) -> int:
|
||
binary_string = "".join(["1" if bit else "0" for bit in reversed(bool_array)])
|
||
num = int(binary_string, 2)
|
||
return num
|
||
|
||
|
||
_SUBSTRUCT_ORDERS = [
|
||
[0, 1, 2, 3], [0, 1, 3, 2], [0, 2, 1, 3], [0, 3, 1, 2],
|
||
[0, 2, 3, 1], [0, 3, 2, 1], [1, 0, 2, 3], [1, 0, 3, 2],
|
||
[2, 0, 1, 3], [3, 0, 1, 2], [2, 0, 3, 1], [3, 0, 2, 1],
|
||
[1, 2, 0, 3], [1, 3, 0, 2], [2, 1, 0, 3], [3, 1, 0, 2],
|
||
[2, 3, 0, 1], [3, 2, 0, 1], [1, 2, 3, 0], [1, 3, 2, 0],
|
||
[2, 1, 3, 0], [3, 1, 2, 0], [2, 3, 1, 0], [3, 2, 1, 0],
|
||
]
|
||
|
||
_LANGUAGE_IDS = {
|
||
"Japanese": 1,
|
||
"English": 2,
|
||
"French": 3,
|
||
"Italian": 4,
|
||
"German": 5,
|
||
"Spanish": 7,
|
||
}
|
||
|
||
_MODERN_ITEM_TO_EMERALD_ITEM = {
|
||
item.modern_id: item.item_id
|
||
for item in data.items.values()
|
||
if item.modern_id is not None
|
||
}
|
||
|
||
|
||
def _encrypt_or_decrypt_substruct(substruct_data: Iterable[int], key: int) -> bytearray:
|
||
modified_data = bytearray()
|
||
for i in range(int(len(substruct_data) / 4)):
|
||
modified_data.extend((int.from_bytes(substruct_data[i * 4 : (i + 1) * 4], "little") ^ key).to_bytes(4, "little"))
|
||
|
||
return modified_data
|
||
|
||
|
||
def pokemon_data_to_json(pokemon_data: Iterable[int]) -> str:
|
||
personality = int.from_bytes(pokemon_data[0:4], "little")
|
||
tid = int.from_bytes(pokemon_data[4:8], "little")
|
||
|
||
substruct_order = _SUBSTRUCT_ORDERS[personality % 24]
|
||
substructs = []
|
||
for i in substruct_order:
|
||
substructs.append(pokemon_data[32 + (i * 12) : 32 + ((i + 1) * 12)])
|
||
|
||
decrypted_substructs = [_encrypt_or_decrypt_substruct(substruct, personality ^ tid) for substruct in substructs]
|
||
|
||
iv_ability_info = int.from_bytes(decrypted_substructs[3][4:8], "little")
|
||
met_info = int.from_bytes(decrypted_substructs[3][2:4], "little")
|
||
|
||
held_item = int.from_bytes(decrypted_substructs[0][2:4], "little")
|
||
|
||
json_object = {
|
||
"version": "1",
|
||
"personality": personality,
|
||
"nickname": decode_string(pokemon_data[8:18]),
|
||
"language": {v: k for k, v in _LANGUAGE_IDS.items()}[pokemon_data[18]],
|
||
"species": data.species[int.from_bytes(decrypted_substructs[0][0:2], "little")].national_dex_number,
|
||
"experience": int.from_bytes(decrypted_substructs[0][4:8], "little"),
|
||
"ability": iv_ability_info >> 31,
|
||
"ivs": [(iv_ability_info >> (i * 5)) & 0x1F for i in range(6)],
|
||
"evs": list(decrypted_substructs[2][0:6]),
|
||
"conditions": list(decrypted_substructs[2][6:12]),
|
||
"pokerus": decrypted_substructs[3][0],
|
||
"location_met": decrypted_substructs[3][1],
|
||
"level_met": met_info & 0b0000000001111111,
|
||
"game": (met_info & 0b0000011110000000) >> 7,
|
||
"ball": (met_info & 0b0111100000000000) >> 11,
|
||
"moves": [
|
||
[
|
||
int.from_bytes(decrypted_substructs[1][i * 2 : (i + 1) * 2], "little"),
|
||
decrypted_substructs[1][8 + i],
|
||
(decrypted_substructs[0][8] & (0b00000011 << (i * 2))) >> (i * 2)
|
||
] for i in range(4)
|
||
],
|
||
"trainer": {
|
||
"name": decode_string(pokemon_data[20:27]),
|
||
"id": tid,
|
||
"female": (met_info & 0b1000000000000000) != 0,
|
||
},
|
||
}
|
||
|
||
if held_item != 0:
|
||
json_object["item"] = data.items[held_item].modern_id
|
||
|
||
return orjson.dumps(json_object).decode("utf-8")
|
||
|
||
|
||
def json_to_pokemon_data(json_str: str) -> bytearray:
|
||
pokemon_json: Dict[str, Any] = orjson.loads(json_str)
|
||
|
||
# Default values to cover for optional or accidentally missed fields
|
||
default_pokemon = {
|
||
"nickname": "A",
|
||
"personality": 0,
|
||
"species": 1,
|
||
"experience": 0,
|
||
"ability": 0,
|
||
"ivs": [0, 0, 0, 0, 0, 0],
|
||
"evs": [0, 0, 0, 0, 0, 0],
|
||
"conditions": [0, 0, 0, 0, 0, 0],
|
||
"pokerus": 0,
|
||
"game": 3,
|
||
"location_met": 0,
|
||
"level_met": 1,
|
||
"ball": 4,
|
||
"moves": [[33, 35, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]],
|
||
}
|
||
|
||
default_trainer = {
|
||
"name": "A",
|
||
"id": 0,
|
||
"female": False,
|
||
}
|
||
|
||
pokemon_json = {**default_pokemon, **{k: v for k, v in pokemon_json.items()}}
|
||
pokemon_json["trainer"] = {**default_trainer, **pokemon_json["trainer"]}
|
||
|
||
# Cutting string lengths to Emerald sizes
|
||
pokemon_json["nickname"] = pokemon_json["nickname"][0:10]
|
||
pokemon_json["trainer"]["name"] = pokemon_json["trainer"]["name"][0:7]
|
||
|
||
# Handle data from incompatible games
|
||
if pokemon_json["species"] > 387:
|
||
pokemon_json["species"] = 201 # Unown
|
||
if pokemon_json["ball"] > 12:
|
||
pokemon_json["ball"] = 4 # Pokeball
|
||
if "game" not in pokemon_json or (pokemon_json["game"] > 5 and pokemon_json["game"] != 15):
|
||
pokemon_json["game"] = 0 # Unknown
|
||
pokemon_json["location_met"] = 0 # Littleroot
|
||
|
||
substructs = [bytearray([0 for _ in range(12)]) for _ in range(4)]
|
||
|
||
# Substruct type 0
|
||
for i, byte in enumerate(NATIONAL_ID_TO_SPECIES_ID[pokemon_json["species"]].to_bytes(2, "little")):
|
||
substructs[0][0 + i] = byte
|
||
|
||
if "item" in pokemon_json:
|
||
if pokemon_json["item"] in _MODERN_ITEM_TO_EMERALD_ITEM:
|
||
for i, byte in enumerate(_MODERN_ITEM_TO_EMERALD_ITEM[pokemon_json["item"]].to_bytes(2, "little")):
|
||
substructs[0][2 + i] = byte
|
||
|
||
for i, byte in enumerate((pokemon_json["experience"]).to_bytes(4, "little")):
|
||
substructs[0][4 + i] = byte
|
||
|
||
for i, move_info in enumerate(pokemon_json["moves"]):
|
||
substructs[0][8] |= ((move_info[2] & 0b11) << (2 * i))
|
||
|
||
substructs[0][9] = data.species[NATIONAL_ID_TO_SPECIES_ID[pokemon_json["species"]]].friendship
|
||
|
||
# Substruct type 1
|
||
for i, move_info in enumerate(pokemon_json["moves"]):
|
||
for j, byte in enumerate(move_info[0].to_bytes(2, "little")):
|
||
substructs[1][(i * 2) + j] = byte
|
||
|
||
substructs[1][8 + i] = move_info[1]
|
||
|
||
# Substruct type 2
|
||
for i, ev in enumerate(pokemon_json["evs"]):
|
||
substructs[2][0 + i] = ev
|
||
|
||
for i, condition in enumerate(pokemon_json["conditions"]):
|
||
substructs[2][6 + i] = condition
|
||
|
||
# Substruct type 3
|
||
substructs[3][0] = pokemon_json["pokerus"]
|
||
substructs[3][1] = pokemon_json["location_met"]
|
||
|
||
origin = pokemon_json["level_met"] | (pokemon_json["game"] << 7) | (pokemon_json["ball"] << 11)
|
||
origin |= (1 << 15) if pokemon_json["trainer"]["female"] else 0
|
||
for i, byte in enumerate(origin.to_bytes(2, "little")):
|
||
substructs[3][2 + i] = byte
|
||
|
||
iv_ability_info = 0
|
||
for i, iv in enumerate(pokemon_json["ivs"]):
|
||
iv_ability_info |= iv << (i * 5)
|
||
iv_ability_info |= 1 << 31 if pokemon_json["ability"] == 1 else 0
|
||
for i, byte in enumerate(iv_ability_info.to_bytes(4, "little")):
|
||
substructs[3][4 + i] = byte
|
||
|
||
# Main data
|
||
pokemon_data = bytearray([0 for _ in range(80)])
|
||
for i, byte in enumerate(pokemon_json["personality"].to_bytes(4, "little")):
|
||
pokemon_data[0 + i] = byte
|
||
|
||
for i, byte in enumerate(pokemon_json["trainer"]["id"].to_bytes(4, "little")):
|
||
pokemon_data[4 + i] = byte
|
||
|
||
for i, byte in enumerate(encode_string(pokemon_json["nickname"], 10)):
|
||
pokemon_data[8 + i] = byte
|
||
|
||
pokemon_data[18] = _LANGUAGE_IDS[pokemon_json["language"]]
|
||
pokemon_data[19] = 0b00000010 # Flags for Bad Egg, Has Species, Is Egg, padding bits (low to high)
|
||
|
||
for i, byte in enumerate(encode_string(pokemon_json["trainer"]["name"], 7)):
|
||
pokemon_data[20 + i] = byte
|
||
|
||
# Markings, 1 byte
|
||
|
||
checksum = 0
|
||
for i in range(4):
|
||
for j in range(6):
|
||
checksum += int.from_bytes(substructs[i][j * 2 : (j + 1) * 2], "little")
|
||
checksum &= 0xFFFF
|
||
for i, byte in enumerate(checksum.to_bytes(2, "little")):
|
||
pokemon_data[28 + i] = byte
|
||
|
||
# Separator, 2 bytes
|
||
|
||
substruct_order = [_SUBSTRUCT_ORDERS[pokemon_json["personality"] % 24].index(n) for n in [0, 1, 2, 3]]
|
||
encrypted_substructs = [None for _ in range(4)]
|
||
encryption_key = pokemon_json["personality"] ^ pokemon_json["trainer"]["id"]
|
||
encrypted_substructs[0] = _encrypt_or_decrypt_substruct(substructs[substruct_order[0]], encryption_key)
|
||
encrypted_substructs[1] = _encrypt_or_decrypt_substruct(substructs[substruct_order[1]], encryption_key)
|
||
encrypted_substructs[2] = _encrypt_or_decrypt_substruct(substructs[substruct_order[2]], encryption_key)
|
||
encrypted_substructs[3] = _encrypt_or_decrypt_substruct(substructs[substruct_order[3]], encryption_key)
|
||
|
||
for i in range(4):
|
||
for j in range(12):
|
||
pokemon_data[32 + (i * 12) + j] = encrypted_substructs[i][j]
|
||
|
||
return pokemon_data
|