Mii Studio Local Storage: Difference between revisions
Appearance
Removed redirect to Mii Studio#Storage Format Tags: Removed redirect Visual edit |
add c struct, obfuscation details |
||
| (4 intermediate revisions by one other user not shown) | |||
| Line 1: | Line 1: | ||
A format. | A format. Used in [[Mii Studio (nintendo.com)]]. | ||
The struct name for this is unknown, as it's implemented in minified JS. However, it resembles a part of [[nn::mii::CharInfo]], with CreateID and names removes, and fields in alphabetical order. | |||
The JS contains field names that are minified/obfuscated ($$_19, $$_1a...), but some aren't: <code>height</code>, <code>build</code>, <code>gender</code>, and interestingly <code>fontRegion</code>, <code>regionMove</code> which are from CharInfo but not included in the format. | |||
== Data format == | |||
<syntaxhighlight lang="c"> | |||
typedef struct { | |||
u8 beard_color; // Beard color | |||
u8 beard_type; // Beard (goatee) type | |||
u8 build; // Body weight | |||
u8 eye_aspect; // Eye stretch | |||
u8 eye_color; // Eye color | |||
u8 eye_rotate; // Eye rotation | |||
u8 eye_scale; // Eye size | |||
u8 eye_type; // Eye type | |||
u8 eye_x; // Eye X (horizontal) distance | |||
u8 eye_y; // Eye Y (vertical) position | |||
u8 eyebrow_aspect; // Eyebrow stretch | |||
u8 eyebrow_color; // Eyebrow color | |||
u8 eyebrow_rotate; // Eyebrow rotation | |||
u8 eyebrow_scale; // Eyebrow size | |||
u8 eyebrow_type; // Eyebrow type | |||
u8 eyebrow_x; // Eyebrow X (horizontal) distance | |||
u8 eyebrow_y; // Eyebrow Y (vertical) distance | |||
u8 faceline_color; // Skin color | |||
u8 faceline_make; // Face makeup | |||
u8 faceline_type; // Face shape | |||
u8 faceline_wrinkle; // Face wrinkles | |||
u8 favorite_color; // Favorite color | |||
u8 gender; // Mii gender | |||
u8 glass_color; // Glasses color | |||
u8 glass_scale; // Glasses size | |||
u8 glass_type; // Glasses type | |||
u8 glass_y; // Glasses Y (vertical) position | |||
u8 hair_color; // Hair color | |||
u8 hair_flip; // Flip hair | |||
u8 hair_type; // Hair type | |||
u8 height; // Body height | |||
u8 mole_scale; // Beauty mark size | |||
u8 mole_type; // Enable beauty mark | |||
u8 mole_x; // Beauty mark X (horizontal) position | |||
u8 mole_y; // Beauty mark Y (vertical) position | |||
u8 mouth_aspect; // Mouth stretch | |||
u8 mouth_color; // Mouth color | |||
u8 mouth_scale; // Mouth size | |||
u8 mouth_type; // Mouth type | |||
u8 mouth_y; // Mouth Y (vertical) position | |||
u8 mustache_scale; // Mustache size | |||
u8 mustache_type; // Mustache type | |||
u8 mustache_y; // Mustache Y (vertical) position | |||
u8 nose_scale; // Nose size | |||
u8 nose_type; // Nose type | |||
u8 nose_y; // Nose Y (vertical) position | |||
} charInfoStudio; | |||
</syntaxhighlight> | |||
(TODO: Need ksy that isn't from mii2studio/HEYimHeroic/Larsenv due to AGPL license.) | |||
== Obfuscation == | |||
When used in a URL (as opposed to local storage), there is light XOR obfuscation applied with a one byte key at the beginning. This code can be located in the website's "editor.pc.*.js" file: | |||
<syntaxhighlight lang="javascript"> | |||
function(t, e, r) { | |||
"use strict"; | |||
function i(t) { | |||
for (var e = (t % 256).toString(16); e.length < 2;) | |||
e = "0" + e; | |||
return e | |||
} | |||
Object.defineProperty(e, "__esModule", { | |||
value: !0 | |||
}); | |||
var a = ["$$_1r", "$$_1q", "build", "$$_1p", "$$_1o", "$$_1n", "$$_1m", "$$_1l", "$$_1k", "$$_1j", "$$_1i", "$$_1h", "$$_1g", "$$_1f", "$$_1e", "$$_1d", "$$_1c", "$$_1b", "$$_1a", "$$_19", "$$_18", "$$_17", "gender", "$$_16", "$$_15", "$$_14", "$$_13", "$$_12", "$$_11", "$$_10", "height", "$$_e", "$$_d", "$$_c", "$$_b", "$$_a", "$$_9", "$$_8", "$$_7", "$$_6", "$$_5", "$$_4", "$$_3", "$$_2", "$$_1", "$$_0"], | |||
n = function() { | |||
function t() {} | |||
return t.prototype.encode = function(t) { | |||
for (var e = a.map(function(e) { | |||
return t[e] | |||
}), r = Math.floor(256 * Math.random()), n = r, o = 0, s = e.length; o < s; o++) { | |||
var l = e[o]; | |||
e[o] = (7 + (l ^ n)) % 256, | |||
n = e[o] | |||
} | |||
return [r].concat(e).map(function(t) { | |||
return i(t) | |||
}).join("") | |||
}, t.prototype.decode = function(t) { | |||
for (var e = [], r = 0, i = a.length + 1; r < i; r++) { | |||
var n = parseInt(t.substr(2 * r, 2), 16); | |||
e[r] = n | |||
} | |||
var o = e.shift(); | |||
if (void 0 === o) | |||
throw new Error("invalid data"); | |||
for (var r = 0, i = e.length; r < i; r++) { | |||
var s = e[r]; | |||
e[r] = (256 + s - 7) % 256 ^ o, | |||
o = s | |||
} | |||
for (var l = {}, r = 0, i = a.length; r < i; r++) | |||
l[a[r]] = e[r]; | |||
return l | |||
}, t | |||
}(); | |||
e.default = n | |||
} | |||
</syntaxhighlight> | |||
== In codebases == | |||
# [https://github.com/ariankordi/FFL-Testing/blob/6e95ca86ef8bdd159d3763e4a717f54b8cf64d05/include/mii_ext_MiiPort.h#L180-L227 mii_ext_MiiPort.h from FFL-Testing renderer server] | |||
== References == | |||
<references /> | |||
{{Format-Navbox}} | {{Format-Navbox}} | ||
Latest revision as of 14:53, 24 September 2025
A format. Used in Mii Studio (nintendo.com).
The struct name for this is unknown, as it's implemented in minified JS. However, it resembles a part of nn::mii::CharInfo, with CreateID and names removes, and fields in alphabetical order.
The JS contains field names that are minified/obfuscated ($$_19, $$_1a...), but some aren't: height, build, gender, and interestingly fontRegion, regionMove which are from CharInfo but not included in the format.
Data format
[edit | edit source]typedef struct {
u8 beard_color; // Beard color
u8 beard_type; // Beard (goatee) type
u8 build; // Body weight
u8 eye_aspect; // Eye stretch
u8 eye_color; // Eye color
u8 eye_rotate; // Eye rotation
u8 eye_scale; // Eye size
u8 eye_type; // Eye type
u8 eye_x; // Eye X (horizontal) distance
u8 eye_y; // Eye Y (vertical) position
u8 eyebrow_aspect; // Eyebrow stretch
u8 eyebrow_color; // Eyebrow color
u8 eyebrow_rotate; // Eyebrow rotation
u8 eyebrow_scale; // Eyebrow size
u8 eyebrow_type; // Eyebrow type
u8 eyebrow_x; // Eyebrow X (horizontal) distance
u8 eyebrow_y; // Eyebrow Y (vertical) distance
u8 faceline_color; // Skin color
u8 faceline_make; // Face makeup
u8 faceline_type; // Face shape
u8 faceline_wrinkle; // Face wrinkles
u8 favorite_color; // Favorite color
u8 gender; // Mii gender
u8 glass_color; // Glasses color
u8 glass_scale; // Glasses size
u8 glass_type; // Glasses type
u8 glass_y; // Glasses Y (vertical) position
u8 hair_color; // Hair color
u8 hair_flip; // Flip hair
u8 hair_type; // Hair type
u8 height; // Body height
u8 mole_scale; // Beauty mark size
u8 mole_type; // Enable beauty mark
u8 mole_x; // Beauty mark X (horizontal) position
u8 mole_y; // Beauty mark Y (vertical) position
u8 mouth_aspect; // Mouth stretch
u8 mouth_color; // Mouth color
u8 mouth_scale; // Mouth size
u8 mouth_type; // Mouth type
u8 mouth_y; // Mouth Y (vertical) position
u8 mustache_scale; // Mustache size
u8 mustache_type; // Mustache type
u8 mustache_y; // Mustache Y (vertical) position
u8 nose_scale; // Nose size
u8 nose_type; // Nose type
u8 nose_y; // Nose Y (vertical) position
} charInfoStudio;
(TODO: Need ksy that isn't from mii2studio/HEYimHeroic/Larsenv due to AGPL license.)
Obfuscation
[edit | edit source]When used in a URL (as opposed to local storage), there is light XOR obfuscation applied with a one byte key at the beginning. This code can be located in the website's "editor.pc.*.js" file:
function(t, e, r) {
"use strict";
function i(t) {
for (var e = (t % 256).toString(16); e.length < 2;)
e = "0" + e;
return e
}
Object.defineProperty(e, "__esModule", {
value: !0
});
var a = ["$$_1r", "$$_1q", "build", "$$_1p", "$$_1o", "$$_1n", "$$_1m", "$$_1l", "$$_1k", "$$_1j", "$$_1i", "$$_1h", "$$_1g", "$$_1f", "$$_1e", "$$_1d", "$$_1c", "$$_1b", "$$_1a", "$$_19", "$$_18", "$$_17", "gender", "$$_16", "$$_15", "$$_14", "$$_13", "$$_12", "$$_11", "$$_10", "height", "$$_e", "$$_d", "$$_c", "$$_b", "$$_a", "$$_9", "$$_8", "$$_7", "$$_6", "$$_5", "$$_4", "$$_3", "$$_2", "$$_1", "$$_0"],
n = function() {
function t() {}
return t.prototype.encode = function(t) {
for (var e = a.map(function(e) {
return t[e]
}), r = Math.floor(256 * Math.random()), n = r, o = 0, s = e.length; o < s; o++) {
var l = e[o];
e[o] = (7 + (l ^ n)) % 256,
n = e[o]
}
return [r].concat(e).map(function(t) {
return i(t)
}).join("")
}, t.prototype.decode = function(t) {
for (var e = [], r = 0, i = a.length + 1; r < i; r++) {
var n = parseInt(t.substr(2 * r, 2), 16);
e[r] = n
}
var o = e.shift();
if (void 0 === o)
throw new Error("invalid data");
for (var r = 0, i = e.length; r < i; r++) {
var s = e[r];
e[r] = (256 + s - 7) % 256 ^ o,
o = s
}
for (var l = {}, r = 0, i = a.length; r < i; r++)
l[a[r]] = e[r];
return l
}, t
}();
e.default = n
}