Created
July 12, 2024 21:15
-
-
Save ariankordi/1fcd67bb99181fc29fb112de75ac5071 to your computer and use it in GitHub Desktop.
uses HEYimHEROIC/mii2studio (PLACE IN THAT REPO!!!) to convert to the Switch Mii format, nn::mii::CharInfo, hacky script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
from struct import pack | |
import os | |
from kaitaistruct import KaitaiStream, BytesIO | |
if len(sys.argv) < 4: | |
print("CLI Usage: python mii2studio.py <input mii file / qr code / cmoc entry number> <output studio mii file> <input type (wii/ds/3ds/wiiu/miitomo/switchdb/switch/studio)>\n") | |
input_file = input("Enter the path to the input file (binary file or QR Code), a CMOC entry number, or a URL to a QR Code: ") | |
output_file = input("Enter the path to the output file (which will be importable with Mii Studio): ") | |
input_type = input("Enter the input type (wii/ds/3ds/wiiu/miitomo/switchdb/switch/studio): ") | |
print("") | |
else: | |
input_file = sys.argv[1] | |
output_file = sys.argv[2] | |
input_type = sys.argv[3] | |
from gen3_switchgame import CharInfoSwitch | |
if input_type == "3ds" or input_type == "wiiu" or input_type == "miitomo": | |
from gen2_wiiu_3ds_miitomo import CoreData3ds | |
orig_mii = CoreData3ds.from_file(input_file) | |
elif input_type == "switchdb": | |
from gen3_switch import CoreDataSwitch | |
orig_mii = CoreDataSwitch.from_file(input_file) | |
elif input_type == "switch": | |
orig_mii = CharInfoSwitch.from_file(sys.argv[1]) | |
elif input_type == "miistudio" or input_type == "studio": | |
from gen3_studio import MiidataStudio | |
orig_mii = MiidataStudio.from_file(sys.argv[1]) | |
else: | |
print("Error: Invalid input type.") | |
exit() | |
def u8(data): | |
return pack(">B", data) | |
print("Mii Info:\n") | |
print("Mii Name: " + orig_mii.mii_name) | |
favorite_colors = { | |
0: "Red", | |
1: "Orange", | |
2: "Yellow", | |
3: "Lime Green", | |
4: "Forest Green", | |
5: "Royal Blue", | |
6: "Sky Blue", | |
7: "Pink", | |
8: "Purple", | |
9: "Brown", | |
10: "White", | |
11: "Black" | |
} | |
print("Favorite Color: " + favorite_colors[orig_mii.favorite_color]) | |
print("Height: " + str(orig_mii.body_height) + " out of 127") | |
print("Build: " + str(orig_mii.body_weight) + " out of 127") | |
mii_types = { | |
0x00: "Special Mii - Gold Pants", | |
0x20: "Normal Mii - Black Pants", | |
0x40: "Special Mii - Gold Pants", | |
0x60: "Normal Mii - Black Pants", | |
0xC0: "Foreign Mii - Blue Pants (uneditable)", | |
0xE0: "Normal Mii - Black Pants", | |
0x100: "???" | |
} | |
print("Gender: Male" if orig_mii.gender == 0 else "Gender: Female") | |
""" | |
if "switch" not in input_type: | |
print("Mingle: Yes" if orig_mii.mingle == 0 else "Mingle: No") | |
if "switch" not in input_type and input_type != "wii" and input_type != "ds": | |
print("Copying: Yes" if orig_mii.copying == 1 else "Copying: No") | |
print("") | |
""" | |
#studio_mii = {} | |
CHARINFO_SIZE = 88 | |
charinfonx_mii = CharInfoSwitch(KaitaiStream(BytesIO(bytearray(CHARINFO_SIZE)))) | |
# nothing else is done with the bytesio it is just read | |
studio_mii = charinfonx_mii.__dict__ | |
makeup = { # lookup table | |
1: 1, | |
2: 6, | |
3: 9, | |
9: 10 | |
} | |
wrinkles = { # lookup table | |
4: 5, | |
5: 2, | |
6: 3, | |
7: 7, | |
8: 8, | |
10: 9, | |
11: 11 | |
} | |
# ue generate the Mii Studio file by reading each Mii format from the Kaitai files. | |
# unlike consoles which store Mii data in an odd number of bits, | |
# all the Mii data for a Mii Studio Mii is stored as unsigned 8-bit integers. makes it easier. | |
if input_type == "studio": | |
input_type = "switch_studio" | |
if "switch" not in input_type: | |
if orig_mii.facial_hair_color == 0: | |
studio_mii["facial_hair_color"] = 8 | |
else: | |
studio_mii["facial_hair_color"] = orig_mii.facial_hair_color | |
else: | |
studio_mii["facial_hair_color"] = orig_mii.facial_hair_color | |
if "studio" in input_type: | |
studio_mii["beard_goatee"] = orig_mii.beard_goatee | |
else: | |
studio_mii["beard_goatee"] = orig_mii.facial_hair_beard | |
studio_mii["body_weight"] = orig_mii.body_weight | |
if input_type == "wii" or input_type == "ds": | |
studio_mii["eye_stretch"] = 3 | |
else: | |
studio_mii["eye_stretch"] = orig_mii.eye_stretch | |
if "switch" not in input_type: | |
studio_mii["eye_color"] = orig_mii.eye_color + 8 | |
else: | |
studio_mii["eye_color"] = orig_mii.eye_color | |
studio_mii["eye_rotation"] = orig_mii.eye_rotation | |
studio_mii["eye_size"] = orig_mii.eye_size | |
studio_mii["eye_type"] = orig_mii.eye_type | |
studio_mii["eye_horizontal"] = orig_mii.eye_horizontal | |
studio_mii["eye_vertical"] = orig_mii.eye_vertical | |
if input_type == "wii" or input_type == "ds": | |
studio_mii["eyebrow_stretch"] = 3 | |
else: | |
studio_mii["eyebrow_stretch"] = orig_mii.eyebrow_stretch | |
if "switch" not in input_type: | |
if orig_mii.eyebrow_color == 0: | |
studio_mii["eyebrow_color"] = 8 | |
else: | |
studio_mii["eyebrow_color"] = orig_mii.eyebrow_color | |
else: | |
studio_mii["eyebrow_color"] = orig_mii.eyebrow_color | |
studio_mii["eyebrow_rotation"] = orig_mii.eyebrow_rotation | |
studio_mii["eyebrow_size"] = orig_mii.eyebrow_size | |
studio_mii["eyebrow_type"] = orig_mii.eyebrow_type | |
studio_mii["eyebrow_horizontal"] = orig_mii.eyebrow_horizontal | |
if input_type != "switchdb": | |
studio_mii["eyebrow_vertical"] = orig_mii.eyebrow_vertical | |
else: | |
studio_mii["eyebrow_vertical"] = orig_mii.eyebrow_vertical + 3 | |
studio_mii["face_color"] = orig_mii.face_color | |
if input_type == "wii" or input_type == "ds": | |
if orig_mii.facial_feature in makeup: | |
studio_mii["face_makeup"] = makeup[orig_mii.facial_feature] | |
else: | |
studio_mii["face_makeup"] = 0 | |
else: | |
studio_mii["face_makeup"] = orig_mii.face_makeup | |
studio_mii["face_type"] = orig_mii.face_type | |
if input_type == "wii" or input_type == "ds": | |
if orig_mii.facial_feature in wrinkles: | |
studio_mii["face_wrinkles"] = wrinkles[orig_mii.facial_feature] | |
else: | |
studio_mii["face_wrinkles"] = 0 | |
else: | |
studio_mii["face_wrinkles"] = orig_mii.face_wrinkles | |
studio_mii["favorite_color"] = orig_mii.favorite_color | |
studio_mii["gender"] = orig_mii.gender | |
if "switch" not in input_type: | |
if orig_mii.glasses_color == 0: | |
studio_mii["glasses_color"] = 8 | |
elif orig_mii.glasses_color < 6: | |
studio_mii["glasses_color"] = orig_mii.glasses_color + 13 | |
else: | |
studio_mii["glasses_color"] = 0 | |
else: | |
studio_mii["glasses_color"] = orig_mii.glasses_color | |
studio_mii["glasses_size"] = orig_mii.glasses_size | |
studio_mii["glasses_type"] = orig_mii.glasses_type | |
studio_mii["glasses_vertical"] = orig_mii.glasses_vertical | |
if "switch" not in input_type: | |
if orig_mii.hair_color == 0: | |
studio_mii["hair_color"] = 8 | |
else: | |
studio_mii["hair_color"] = orig_mii.hair_color | |
else: | |
studio_mii["hair_color"] = orig_mii.hair_color | |
studio_mii["hair_flip"] = orig_mii.hair_flip | |
studio_mii["hair_type"] = orig_mii.hair_type | |
studio_mii["body_height"] = orig_mii.body_height | |
studio_mii["mole_size"] = orig_mii.mole_size | |
studio_mii["mole_enable"] = orig_mii.mole_enable | |
studio_mii["mole_horizontal"] = orig_mii.mole_horizontal | |
studio_mii["mole_vertical"] = orig_mii.mole_vertical | |
if input_type == "wii" or input_type == "ds": | |
studio_mii["mouth_stretch"] = 3 | |
else: | |
studio_mii["mouth_stretch"] = orig_mii.mouth_stretch | |
if "switch" not in input_type: | |
if orig_mii.mouth_color < 4: | |
studio_mii["mouth_color"] = orig_mii.mouth_color + 19 | |
else: | |
studio_mii["mouth_color"] = 0 | |
else: | |
studio_mii["mouth_color"] = orig_mii.mouth_color | |
studio_mii["mouth_size"] = orig_mii.mouth_size | |
studio_mii["mouth_type"] = orig_mii.mouth_type | |
studio_mii["mouth_vertical"] = orig_mii.mouth_vertical | |
if "studio" in input_type: | |
studio_mii["beard_size"] = orig_mii.beard_size | |
else: | |
studio_mii["beard_size"] = orig_mii.facial_hair_size | |
if "studio" in input_type: | |
studio_mii["beard_mustache"] = orig_mii.beard_mustache | |
else: | |
studio_mii["beard_mustache"] = orig_mii.facial_hair_mustache | |
if "studio" in input_type: | |
studio_mii["beard_vertical"] = orig_mii.beard_size | |
else: | |
studio_mii["beard_vertical"] = orig_mii.facial_hair_vertical | |
studio_mii["nose_size"] = orig_mii.nose_size | |
studio_mii["nose_type"] = orig_mii.nose_type | |
studio_mii["nose_vertical"] = orig_mii.nose_vertical | |
if "studio" in input_type: | |
charinfonx_mii.mii_name = 'yes name\x00\x00\x00' | |
else: | |
charinfonx_mii.mii_name = orig_mii.mii_name + '\x00' | |
# Generate a random 16-byte array | |
uuid = bytearray(os.urandom(16)) | |
# Ensure the two leftmost bits of the 9th byte (index 8) are 0b10 | |
uuid[8] &= 0b10111111 # Clear the leftmost bit | |
uuid[8] |= 0b10000000 # Set the second leftmost bit | |
# NOTE: just makes a random mii id | |
charinfonx_mii.mii_id = list(uuid) | |
with open(output_file, "wb") as f: | |
""" | |
mii_data_bytes = "" | |
mii_data = b"" | |
n = r = 256 | |
mii_dict = [] | |
if input_type == "miistudio": | |
with open(input_file, "rb") as g: | |
read = g.read() | |
g.close() | |
for i in range(0, len(hexlify(read)), 2): | |
mii_dict.append(int(hexlify(read)[i:i + 2], 16)) | |
else: | |
mii_dict = studio_mii.values() | |
# mii_data_bytes += hexlify(u8(0)) | |
mii_data += hexlify(u8(0)) | |
for v in mii_dict: | |
eo = (7 + (v ^ n)) % 256 # encode the Mii, Nintendo seemed to have randomized the encoding using Math.random() in JS, but we removed randomizing | |
n = eo | |
# mii_data_bytes += hexlify(u8(mii_dict)) | |
mii_data += hexlify(u8(eo)) | |
f.write(u8(v)) | |
mii_data_bytes += str(hexlify(u8(v)), "ascii") | |
""" | |
# Step 2: Initialize a byte array | |
byte_array = bytearray() | |
# Step 3: Iterate through the dictionary | |
for key, value in studio_mii.items(): | |
# Skip keys that start with an underscore | |
if key.startswith('_'): | |
continue | |
# HACK: stop at charinfo max size | |
if len(byte_array) == CHARINFO_SIZE: | |
break | |
# Process based on value type | |
if isinstance(value, int): | |
# Append integer as uint8 | |
byte_array.append(value) | |
elif isinstance(value, str): | |
# Append string as utf16le | |
byte_array.extend(value.encode('utf-16le')) | |
elif isinstance(value, list): | |
for item in value: | |
if isinstance(item, int): | |
# Append each integer in the list as uint8 | |
byte_array.append(item) | |
f.write(byte_array) | |
f.close() | |
print("Completed Successfully") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment