Skip to content

Instantly share code, notes, and snippets.

@ariankordi
Created July 12, 2024 21:15
Show Gist options
  • Save ariankordi/1fcd67bb99181fc29fb112de75ac5071 to your computer and use it in GitHub Desktop.
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
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