flipper-zero-stuff/nfc/Amiibo/Amiibo_Flipper_Converter/amiiboconvert.py
2024-08-14 08:46:08 -07:00

224 lines
6.9 KiB
Python

"""
amiiboconvert.py
3/12/2022
Modified Amiibo Flipper Conversion Code
Original Code by Friendartiste
Modified by Lamp
Modified again by VapidAnt
Modified and commented by bjschafer
Execute with python amiiboconvert -h to see options
"""
import argparse
import logging
import os
import pathlib
from typing import Tuple
def write_output(name: str, assemble: str, out_dir: str):
"""
Handles writing the converted file
:param name: The base filename - e.g. for Foo.bin, Foo
:param assemble: The converted flipper-compatible contents
:param out_dir: The directory to place Foo.nfc in
"""
with open(os.path.join(out_dir, f"{name}.nfc"), "wt") as f:
f.write(assemble)
def convert(contents: bytes) -> Tuple[str, int]:
"""
Convert from bytes into the Page-based format expected by flipper
Each "Page" is 4 bytes hex, notated like:
Page 0: DE AD BE EF
To process, we grab one byte at a time, turn it into a hex string, and store it in `page`.
When page is "full" (has 4 bytes in it), we flush it to the buffer.
When all's said and done, buffer contains text ready for writing to the end of a .nfc file.
Also tracks and returns running page number, since that's also needed.
:param contents: byte array we're reading, from a .bin file
:return: The full string of Pages, suitable for writing to a file
"""
buffer = []
page_count = 0
page = []
for i in range(len(contents) - 1):
byte = contents[i : i + 1].hex()
page.append(byte)
if len(page) == 4:
buffer.append(f"Page {page_count}: {' '.join(page).upper()}")
page = []
page_count += 1
# we may have an unfilled page. This needs to be filled out and appended
logging.debug(f"We have an unfilled final page: {page} with length {len(page)}")
if len(page) > 0:
# pad with zeroes
for i in range(len(page) - 1, 3):
page.append("00")
buffer.append(f"Page {page_count}: {' '.join(page).upper()}")
page_count += 1
return "\n".join(buffer), page_count
def get_uid(contents: bytes) -> str:
"""
the UID appears to be made up of the first 3 bytes, a byte is skipped, and then the next 4 bytes
:param contents: The bytes object we're operating on
:return: something like `23 20 41 6D 69 69 62 6F`
"""
page = []
for i in range(3):
byte = contents[i : i + 1].hex()
page.append(byte)
for i in range(4, 8):
byte = contents[i : i + 1].hex()
page.append(byte)
return " ".join(page).upper()
def assemble_code(contents: {hex}) -> str:
"""
Convert from .bin files to Flipper text-like .nfc files
:param contents: File contents upon which .hex() can be called
:return: A string to be written to a file
"""
conversion, page_count = convert(contents)
return f"""Filetype: Flipper NFC device
Version: 2
# Nfc device type can be UID, Mifare Ultralight, Bank card
Device type: NTAG215
# UID, ATQA and SAK are common for all formats
UID: {get_uid(contents)}
ATQA: 44 00
SAK: 00
# Mifare Ultralight specific data
Signature: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Mifare version: 00 04 04 02 01 00 11 03
Counter 0: 0
Tearing 0: 00
Counter 1: 0
Tearing 1: 00
Counter 2: 0
Tearing 2: 00
Pages total: {page_count}
{conversion}
"""
def convert_file(input_path: str, output_path: str):
"""
Handles reading, converting, and writing a single file
:param input_path: The full path to the .bin file
:param output_path: The base directory to output to
"""
input_extension = os.path.splitext(input_path)[1]
if input_extension == ".bin":
logging.info(f"Writing: {input_path}")
with open(input_path, "rb") as file:
contents = file.read()
name = os.path.split(input_path)[1]
write_output(name.split(".bin")[0], assemble_code(contents), output_path)
elif input_extension == ".nfc":
logging.warning(f"Seems like {input_path} may already be Flipper-compatible!")
else:
logging.info(f"{input_path} doesn't seem like a relevant file, skipping")
def process(path: str, output_path: str):
"""
Process an input file, or walk through an input directory and process every matching .bin file therein
:param path: Path to a single file or a directory containing one or more .bin files
:param output_path: The base directory to output to
"""
if os.path.isfile(path):
convert_file(path, output_path)
else:
for filename in os.listdir(path):
new_path = os.path.join(path, filename)
logging.debug(f"Current file: {filename}; Current path: {new_path}")
if os.path.isfile(path):
convert_file(path, output_path)
else:
logging.debug(f"Recursing into: {new_path}")
process(new_path, output_path)
def get_args():
parser = argparse.ArgumentParser()
parser.add_argument(
"-i",
"--input-path",
required=True,
type=pathlib.Path,
help="Single file or directory tree to convert",
)
parser.add_argument(
"-o",
"--output-path",
required=False,
type=pathlib.Path,
help="Directory to store output in. Will be created if it doesn't exist. If not specified, the output will be "
"stored in the same location as the original, with a '.nfc' extension.",
)
parser.add_argument(
"-v",
"--verbose",
action="count",
default=0,
help="Show extra info: pass -v to see what's going on, pass -vv to get useful debug info",
)
args = parser.parse_args()
if args.verbose >= 2:
# set debug
logging.basicConfig(level=logging.DEBUG)
elif args.verbose >= 1:
# set info
logging.basicConfig(level=logging.INFO)
logging.debug(f"Parsed args into {args}")
return args
def main():
args = get_args()
# single file mode
if os.path.isfile(args.input_path):
if not args.output_path:
args.output_path = os.path.split(args.input_path)[0]
# recursive directory mode
elif os.path.isdir(args.input_path):
if not args.output_path:
logging.exception(
ValueError(
f"{args.input_path} is a directory, but no output path given."
)
)
elif not os.path.exists(args.input_path):
logging.exception(
FileNotFoundError(f"{args.input_path} doesn't actually exist")
)
logging.debug(f"Going to create output directory {args.output_path}")
os.makedirs(args.output_path, exist_ok=True)
logging.debug(f"input: {args.input_path}, output: {args.output_path}")
process(args.input_path, args.output_path)
if __name__ == "__main__":
main()
print("----Good Execution----")