miijs 2.3.1 → 2.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -12,7 +12,7 @@ const httpsLib = require('https');
12
12
  const asmCrypto = require("./asmCrypto.js");
13
13
  const path = require("path");
14
14
  const createGL = require('gl');
15
-
15
+ const typeCheat = [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3];
16
16
  const {
17
17
  createCharModel, initCharModelTextures,
18
18
  initializeFFL, exitFFL, parseHexOrB64ToUint8Array,
@@ -1255,14 +1255,40 @@ let _fflRes;
1255
1255
  function getFFLRes() {
1256
1256
  // If we've already tried loading, just return the result
1257
1257
  if (_fflRes !== undefined) return _fflRes;
1258
- for (const path of ["./FFLResHigh.dat", "./ffl/FFLResHigh.dat", "./AFLResHigh.dat", "./afl/AFLResHigh.dat", "./ffl/AFLResHigh.dat"]) {
1259
- if (fs.existsSync(path)) {
1260
- // Convert Buffer to Uint8Array explicitly
1261
- const buffer = fs.readFileSync(path);
1262
- return _fflRes = new Uint8Array(buffer);
1258
+
1259
+ const searchPaths = [
1260
+ "./FFLResHigh.dat",
1261
+ "../FFLResHigh.dat",
1262
+ "../../FFLResHigh.dat",
1263
+ "./ffl/FFLResHigh.dat",
1264
+ "./afl/AFLResHigh.dat",
1265
+ "../ffl/FFLResHigh.dat",
1266
+ "../afl/AFLResHigh.dat",
1267
+ "../../ffl/FFLResHigh.dat",
1268
+ "../../afl/AFLResHigh.dat"
1269
+ ];
1270
+
1271
+ for (const filePath of searchPaths) {
1272
+ try {
1273
+ if (fs.existsSync(filePath)) {
1274
+ const stats = fs.statSync(filePath);
1275
+ // Make sure it's a file, not a directory
1276
+ if (stats.isFile()) {
1277
+ // Convert Buffer to Uint8Array explicitly
1278
+ const buffer = fs.readFileSync(filePath);
1279
+ _fflRes = new Uint8Array(buffer);
1280
+ console.log(`Loaded FFLResHigh.dat from: ${filePath} (${_fflRes.length} bytes)`);
1281
+ return _fflRes;
1282
+ }
1283
+ }
1284
+ } catch (e) {
1285
+ // Silently continue to next path
1286
+ continue;
1263
1287
  }
1264
1288
  }
1289
+
1265
1290
  // If no file found, mark as null
1291
+ console.warn('FFLResHigh.dat not found. Mii rendering will fall back to Mii Studio.');
1266
1292
  return _fflRes = null;
1267
1293
  }
1268
1294
 
@@ -1742,23 +1768,88 @@ function convertMii(jsonIn, typeTo) {
1742
1768
  miiTo.console = "wii";
1743
1769
  }
1744
1770
  else if (typeFrom === "wii") {
1745
- miiTo.perms.sharing = mii.general.mingle;
1746
- miiTo.perms.copying = mii.general.mingle;
1747
- miiTo.hair.style = convTables.hairWiiTo3DS[mii.hair.page][mii.hair.type];
1748
- miiTo.face.shape = convTables.faceWiiTo3DS[mii.face.shape];
1771
+ miiTo.perms.sharing = mii.perms.mingle;
1772
+ miiTo.perms.copying = mii.perms.mingle;
1773
+
1774
+ // Convert hair
1775
+ const hairConv = convTables.hairWiiTo3DS[mii.hair.page][mii.hair.type];
1776
+ miiTo.hair.page = hairConv[0];
1777
+ miiTo.hair.type = hairConv[1];
1778
+ miiTo.hair.color = mii.hair.color;
1779
+ miiTo.hair.flipped = mii.hair.flipped;
1780
+
1781
+ // Convert face
1782
+ miiTo.face.type = convTables.faceWiiTo3DS[mii.face.type];
1783
+ miiTo.face.color = mii.face.color;
1749
1784
  miiTo.face.makeup = 0;
1750
1785
  miiTo.face.feature = 0;
1786
+
1787
+ // Handle facial features/makeup
1751
1788
  if (typeof (convTables.featureWiiTo3DS[mii.face.feature]) === 'string') {
1752
- miiTo.face.makeup = makeups3DS[+convTables.featureWiiTo3DS[mii.face.feature]];
1789
+ miiTo.face.makeup = +convTables.featureWiiTo3DS[mii.face.feature];
1753
1790
  }
1754
1791
  else {
1755
- miiTo.face.feature = faceFeatures3DS[convTables.featureWiiTo3DS[mii.face.feature]];
1792
+ miiTo.face.feature = +convTables.featureWiiTo3DS[mii.face.feature];
1756
1793
  }
1757
- miiTo.eyes.squash = 3;
1758
- miiTo.eyebrows.squash = 3;
1794
+
1795
+ // Convert eyes - preserve page/type structure
1796
+ miiTo.eyes.page = mii.eyes.page;
1797
+ miiTo.eyes.type = mii.eyes.type;
1798
+ miiTo.eyes.color = mii.eyes.color;
1799
+ miiTo.eyes.size = mii.eyes.size;
1800
+ miiTo.eyes.squash = 3; // Default for 3DS
1801
+ miiTo.eyes.rotation = mii.eyes.rotation;
1802
+ miiTo.eyes.distanceApart = mii.eyes.distanceApart;
1803
+ miiTo.eyes.yPosition = mii.eyes.yPosition;
1804
+
1805
+ // Convert eyebrows - preserve page/type structure
1806
+ miiTo.eyebrows.page = mii.eyebrows.page;
1807
+ miiTo.eyebrows.type = mii.eyebrows.type;
1808
+ miiTo.eyebrows.color = mii.eyebrows.color;
1809
+ miiTo.eyebrows.size = mii.eyebrows.size;
1810
+ miiTo.eyebrows.squash = 3; // Default for 3DS
1811
+ miiTo.eyebrows.rotation = mii.eyebrows.rotation;
1812
+ miiTo.eyebrows.distanceApart = mii.eyebrows.distanceApart;
1813
+ miiTo.eyebrows.yPosition = mii.eyebrows.yPosition;
1814
+
1815
+ // Convert nose - preserve page/type structure
1816
+ miiTo.nose.page = mii.nose.page || 0;
1817
+ miiTo.nose.type = mii.nose.type;
1818
+ miiTo.nose.size = mii.nose.size;
1819
+ miiTo.nose.yPosition = mii.nose.yPosition;
1820
+
1821
+ // Convert mouth - preserve page/type structure
1822
+ miiTo.mouth.page = mii.mouth.page;
1823
+ miiTo.mouth.type = mii.mouth.type;
1759
1824
  miiTo.mouth.color = mii.mouth.color;
1760
- miiTo.mouth.squash = 3;
1761
- miiTo.console = "3ds";
1825
+ miiTo.mouth.size = mii.mouth.size;
1826
+ miiTo.mouth.squash = 3; // Default for 3DS
1827
+ miiTo.mouth.yPosition = mii.mouth.yPosition;
1828
+
1829
+ // Convert glasses
1830
+ miiTo.glasses.type = mii.glasses.type;
1831
+ miiTo.glasses.color = mii.glasses.color;
1832
+ miiTo.glasses.size = mii.glasses.size;
1833
+ miiTo.glasses.yPosition = mii.glasses.yPosition;
1834
+
1835
+ // Convert beard
1836
+ miiTo.beard.mustache.type = mii.beard.mustache.type;
1837
+ miiTo.beard.mustache.size = mii.beard.mustache.size;
1838
+ miiTo.beard.mustache.yPosition = mii.beard.mustache.yPosition;
1839
+ miiTo.beard.type = mii.beard.type;
1840
+ miiTo.beard.color = mii.beard.color;
1841
+
1842
+ // Convert mole
1843
+ miiTo.mole.on = mii.mole.on;
1844
+ miiTo.mole.size = mii.mole.size;
1845
+ miiTo.mole.xPosition = mii.mole.xPosition;
1846
+ miiTo.mole.yPosition = mii.mole.yPosition;
1847
+
1848
+ // Copy general info
1849
+ miiTo.general = { ...mii.general };
1850
+ miiTo.meta = { ...mii.meta };
1851
+
1852
+ miiTo.console = "3DS";
1762
1853
  }
1763
1854
  return miiTo;
1764
1855
  }
@@ -1838,7 +1929,11 @@ function convertStudioToMii(input) {
1838
1929
  gender: s[0x16],
1839
1930
  favoriteColor: s[0x15],
1840
1931
  height: s[0x1E],
1841
- weight: s[0x02]
1932
+ weight: s[0x02],
1933
+
1934
+ //The following is not provided by Studio codes and are hardcoded
1935
+ birthday: 0,
1936
+ birthMonth: 0
1842
1937
  },
1843
1938
 
1844
1939
  face: {
@@ -1937,7 +2032,8 @@ function convertStudioToMii(input) {
1937
2032
  meta: {
1938
2033
  name: "Studio Mii",
1939
2034
  creatorName: "StudioUser",
1940
- console: "3DS"
2035
+ console: "3DS",
2036
+ type: "Default"
1941
2037
  },
1942
2038
 
1943
2039
  perms: {
@@ -2255,182 +2351,373 @@ async function renderMiiWithStudio(jsonIn) {
2255
2351
  var studioMii = convertMiiToStudio(jsonIn);
2256
2352
  return await downloadImage('https://studio.mii.nintendo.com/miis/image.png?data=' + studioMii + "&width=270&type=face");
2257
2353
  }
2258
- async function createFFLMiiIcon(data, width, height, useBody, shirtColor, fflRes) {
2259
- /**
2260
- * Creates a Mii face render using FFL.js/Three.js/gl-headless.
2261
- * @example
2262
- * const fs = require('fs');
2263
- * // NOTE: ASSUMES that this function IS EXPORTED in index.js.
2264
- * const createFFLMiiIcon = require('./index.js').createFFLMiiIcon;
2265
- * const miiData = '000d142a303f434b717a7b84939ba6b2bbbec5cbc9d0e2ea010d15252b3250535960736f726870757f8289a0a7aeb1';
2266
- * const outFilePath = 'mii-render.png';
2267
- * const fflRes = fs.readFileSync('./FFLResHigh.dat');
2268
- * createFFLMiiIcon(miiData, 512, 512, fflRes)
2269
- * .then(pngBytes => fs.writeFileSync(outFilePath, pngBytes));
2270
- */
2271
-
2272
- // Create WebGL context.
2354
+
2355
+
2356
+ function flipPixelsVertically(src, w, h) {
2357
+ const dst = new Uint8Array(src.length);
2358
+ const row = w * 4;
2359
+ for (let y = 0; y < h; y++) {
2360
+ const a = y * row, b = (h - 1 - y) * row;
2361
+ dst.set(src.subarray(a, a + row), b);
2362
+ }
2363
+ return dst;
2364
+ }
2365
+ function invLerp(a, b, v) { return (v - a) / (b - a); }
2366
+ function clamp01(t) { return Math.max(0, Math.min(1, t)); }
2367
+ function easePow(t, p) { return Math.pow(t, p); }
2368
+ // ------------------------------
2369
+ // Small helpers
2370
+ // ------------------------------
2371
+ function remap01(v, min = 0, max = 127) {
2372
+ const cl = Math.min(max, Math.max(min, +v || 0));
2373
+ return (cl - min) / (max - min || 1);
2374
+ }
2375
+ function lerp(a, b, t) { return a + (b - a) * t; }
2376
+
2377
+ function offsetObjectAlongView(object3D, camera, delta) {
2378
+ const forward = new THREE.Vector3();
2379
+ camera.getWorldDirection(forward); // forward
2380
+ object3D.position.addScaledVector(forward, -delta); // -delta → toward camera
2381
+ }
2382
+
2383
+ // Project a pixel Y offset at a given depth into world Y units
2384
+ function pixelYToWorldY(camera, depthZ, pixels, viewportHeightPx) {
2385
+ // Visible height at depth for a perspective camera:
2386
+ const fov = (camera.fov ?? 30) * Math.PI / 180;
2387
+ const visibleH = 2 * Math.abs(depthZ) * Math.tan(fov / 2);
2388
+ return (pixels / viewportHeightPx) * visibleH;
2389
+ }
2390
+
2391
+ // Render a specific layer to a pixel buffer (optionally flipY)
2392
+ function renderLayerToPixels(renderer, scene, camera, gl, width, height, layerIndex, flipY) {
2393
+ const rt = new THREE.WebGLRenderTarget(width, height, { depthBuffer: true, stencilBuffer: false });
2394
+ camera.layers.set(layerIndex);
2395
+ renderer.setRenderTarget(rt);
2396
+ renderer.clear(true, true, true);
2397
+ renderer.render(scene, camera);
2398
+
2399
+ const pixels = new Uint8Array(width * height * 4);
2400
+ gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
2401
+
2402
+ renderer.setRenderTarget(null);
2403
+ rt.dispose();
2404
+
2405
+ if (!flipY) return pixels;
2406
+
2407
+ // flipVert
2408
+ const rowBytes = width * 4;
2409
+ const out = new Uint8Array(pixels.length);
2410
+ for (let y = 0; y < height; y++) {
2411
+ const src = y * rowBytes;
2412
+ const dst = (height - 1 - y) * rowBytes;
2413
+ out.set(pixels.subarray(src, src + rowBytes), dst);
2414
+ }
2415
+ return out;
2416
+ }
2417
+
2418
+ // Camera fit (unchanged)
2419
+ function fitCameraToObject(camera, object3D) {
2420
+ const padding = 0.525;
2421
+ const box = new THREE.Box3().setFromObject(object3D);
2422
+ const size = new THREE.Vector3();
2423
+ const center = new THREE.Vector3();
2424
+ box.getSize(size);
2425
+ box.getCenter(center);
2426
+
2427
+ const maxSize = Math.max(size.y, size.x / camera.aspect);
2428
+ const fov = (camera.fov ?? 30) * Math.PI / 180;
2429
+ const dist = (maxSize * padding) / Math.tan(fov / 2);
2430
+
2431
+ const dir = new THREE.Vector3();
2432
+ camera.getWorldDirection(dir); // forward
2433
+ dir.normalize().multiplyScalar(-dist); // back
2434
+
2435
+ camera.position.copy(center).add(dir);
2436
+ camera.near = Math.max(0.1, dist - maxSize * 3.0);
2437
+ camera.far = dist + maxSize * 3.0;
2438
+ camera.lookAt(center);
2439
+ camera.updateProjectionMatrix();
2440
+ }
2441
+ async function createFFLMiiIcon(data, options, shirtColor, fflRes) {
2442
+ options ||= {};
2443
+ const isFullBody = !!options.fullBody;
2444
+
2445
+ const width = 450;
2446
+ const height = 900;
2447
+ const BODY_SCALE_Y_RANGE = [0.55, 1.35];
2448
+ const FULLBODY_CROP_BOTTOM_PX_RANGE = [220, 40]; // [at minYScale, at maxYScale]
2449
+
2273
2450
  const gl = createGL(width, height);
2274
- if (!gl) {
2275
- throw new Error('Failed to create WebGL 1 context');
2451
+ if (!gl) throw new Error("Failed to create WebGL 1 context");
2452
+
2453
+ // Normalize potential gender inputs; And the body files for females and males have different mesh names for some reason, so adjust for that too
2454
+ let shirtMesh = "mesh_1_";
2455
+ if (typeof options.gender === "string") {
2456
+ options.gender = options.gender.toLowerCase() === "female" ? "Female" : "Male";
2276
2457
  }
2458
+ else if (typeof options.gender === "number") {
2459
+ options.gender = options.gender === 1 ? "Female" : "Male";
2460
+ }
2461
+ else {
2462
+ options.gender = "Male";
2463
+ }
2464
+ if (options.gender === "Female") shirtMesh = "mesh_0_";
2277
2465
 
2278
- // Create a dummy canvas for Three.js to use.
2466
+ // Fake canvas
2279
2467
  const canvas = {
2280
2468
  width, height, style: {},
2281
- addEventListener() { },
2282
- removeEventListener() { },
2283
- // Return the context for 'webgl' (not webgl2)
2284
- getContext: (type, _) => type === 'webgl' ? gl : null,
2469
+ addEventListener() { }, removeEventListener() { },
2470
+ getContext: (t) => (t === "webgl" ? gl : null),
2285
2471
  };
2472
+ globalThis.self ??= { cancelAnimationFrame: () => { } };
2286
2473
 
2287
- // WebGLRenderer constructor sets "self" as the context.
2288
- // As of r162, it only tries to call cancelAnimationFrame frame on it.
2289
- globalThis.self ??= {
2290
- // Mock window functions called by Three.js.
2291
- cancelAnimationFrame: () => { },
2292
- };
2293
- // Create the Three.js renderer and scene.
2294
2474
  const renderer = new THREE.WebGLRenderer({ canvas, context: gl, alpha: true });
2295
- setIsWebGL1State(!renderer.capabilities.isWebGL2); // Tell FFL.js we are WebGL1
2475
+ renderer.setSize(width, height, false);
2476
+ setIsWebGL1State(!renderer.capabilities.isWebGL2);
2477
+
2478
+ // Color mgmt + silence warnings
2479
+ THREE.ColorManagement.enabled = true;
2480
+ renderer.outputColorSpace = THREE.SRGBColorSpace;
2481
+ const _warn = console.warn;
2482
+ console.warn = function (...args) {
2483
+ const s = String(args[0] ?? "");
2484
+ if (s.includes("ImageUtils.sRGBToLinear(): Unsupported image type")) return;
2485
+ if (s.includes("Texture is not power of two")) return;
2486
+ return _warn.apply(this, args);
2487
+ };
2296
2488
 
2297
2489
  const scene = new THREE.Scene();
2298
- scene.background = null; // Transparent background.
2299
-
2300
- if (useBody) {
2301
- // After: const scene = new THREE.Scene(); scene.background = null;
2302
- const ambient = new THREE.AmbientLight(0xffffff, 0.15);
2303
- scene.add(ambient);
2304
-
2305
- const rim = new THREE.DirectionalLight(0xffffff, 3);
2306
- rim.position.set(0.5, -7, -1.0);
2307
- scene.add(rim);
2308
-
2309
- }
2490
+ scene.background = null;
2310
2491
 
2311
2492
  let ffl, currentCharModel;
2312
-
2313
- const _realConsoleDebug = console.debug;
2493
+ const _realDebug = console.debug;
2314
2494
  console.debug = () => { };
2495
+
2315
2496
  try {
2316
- // Initialize FFL
2497
+ // Head (FFL)
2317
2498
  ffl = await initializeFFL(fflRes, ModuleFFL);
2318
-
2319
- // Create Mii model and add to the scene.
2320
- const studioRaw = parseHexOrB64ToUint8Array(data); // Parse studio data
2321
-
2322
- // Convert Uint8Array to Buffer for struct-fu compatibility
2499
+ const studioRaw = parseHexOrB64ToUint8Array(data);
2323
2500
  const studioBuffer = Buffer.from(studioRaw);
2324
-
2325
2501
  currentCharModel = createCharModel(studioBuffer, null, FFLShaderMaterial, ffl.module);
2326
- initCharModelTextures(currentCharModel, renderer); // Initialize fully
2327
- scene.add(currentCharModel.meshes); // Add to scene
2328
-
2329
- //Add body
2330
- if (useBody) {
2331
- if (typeof GLTFLoader === 'undefined' || !GLTFLoader) {
2332
- const mod = await import('three/examples/jsm/loaders/GLTFLoader.js');
2333
- GLTFLoader = mod.GLTFLoader;
2334
- }
2335
- //Read GLB from disk and parse (avoids URL/fetch issues)
2336
- const absPath = path.resolve(__dirname, './mii-body.glb');
2337
- const buf = fs.readFileSync(absPath);
2338
- const arrayBuffer = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
2339
- const loader = new GLTFLoader();
2340
- const gltf = await new Promise((resolve, reject) => {
2341
- loader.parse(
2342
- arrayBuffer,
2343
- path.dirname(absPath) + path.sep,
2344
- resolve,
2345
- reject
2346
- );
2347
- });
2502
+ initCharModelTextures(currentCharModel, renderer);
2348
2503
 
2349
- const body = gltf.scene;
2350
-
2351
- body.position.y -= 110;
2352
-
2353
- //Recolor
2354
- body.userData.isMiiBody = true;
2355
- body.traverse(o => {
2356
- if (o.isMesh) {
2357
- if (!o.geometry.attributes.normal) {
2358
- o.geometry.computeVertexNormals();
2359
- }
2360
- const isShirt = (o.name === 'mesh_1_');
2361
- o.material?.dispose?.();
2362
- o.material = new THREE.MeshLambertMaterial({
2363
- //["Red", "Orange", "Yellow", "Lime", "Green", "Blue", "Cyan", "Pink", "Purple", "Brown", "White", "Black"]
2364
- color: isShirt ? [0xFF2400, 0xF08000, 0xFFD700, 0xAAFF00, 0x008000, 0x0000FF, 0x00D7FF, 0xFF69B4, 0x7F00FF, 0x6F4E37, 0xFFFFFF, 0x303030][shirtColor] : 0x808080,
2365
- emissive: isShirt ? 0x330000 : 0x222222,
2366
- emissiveIntensity: 0.0
2367
- });
2368
- o.material.side = THREE.DoubleSide;
2369
- o.material.needsUpdate = true;
2370
- }
2371
- });
2504
+ // Body GLTF (for baking)
2505
+ if (typeof GLTFLoader === "undefined" || !GLTFLoader) {
2506
+ const mod = await import("three/examples/jsm/loaders/GLTFLoader.js");
2507
+ GLTFLoader = mod.GLTFLoader;
2508
+ }
2509
+ const absPath = path.resolve(__dirname, `./mii${options.gender}Body.glb`);
2510
+ const buf = fs.readFileSync(absPath);
2511
+ const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
2512
+ const loader = new GLTFLoader();
2513
+ const gltf = await new Promise((res, rej) =>
2514
+ loader.parse(ab, path.dirname(absPath) + path.sep, res, rej)
2515
+ );
2516
+ const body = gltf.scene;
2517
+ body.position.y -= 110;
2518
+ body.userData.isMiiBody = true;
2519
+
2520
+ // Recolor body (bakes into texture)
2521
+ var pantsColor=[0x808080,0xFFC000,0x89CFF0,0x913831,0x913831][["default","special","foreign","favorite","favorited"].indexOf(options.pantsType?.toLowerCase()||"default")];
2522
+ body.traverse((o) => {
2523
+ if (o.isMesh) {
2524
+ if (!o.geometry.attributes.normal) o.geometry.computeVertexNormals();
2525
+ const isShirt = o.name === shirtMesh;
2526
+ o.material?.dispose?.();
2527
+ o.material = new THREE.MeshLambertMaterial({
2528
+ color: isShirt
2529
+ ? [
2530
+ 0xff2400, 0xf08000, 0xffd700, 0xaaff00, 0x008000, 0x0000ff,
2531
+ 0x00d7ff, 0xff69b4, 0x7f00ff, 0x6f4e37, 0xffffff, 0x303030,
2532
+ ][shirtColor]
2533
+ : pantsColor,
2534
+ emissive: isShirt ? 0x330000 : 0x222222,
2535
+ emissiveIntensity: 0.0,
2536
+ side: THREE.DoubleSide,
2537
+ });
2538
+ o.material.needsUpdate = true;
2539
+ }
2540
+ });
2372
2541
 
2542
+ // Graph (only used for framing / body bbox)
2543
+ const wholeMii = new THREE.Group();
2544
+ wholeMii.add(body);
2545
+ wholeMii.add(currentCharModel.meshes);
2546
+ scene.add(wholeMii);
2373
2547
 
2548
+ // Layers for baking head/body
2549
+ body.traverse(obj => obj.layers?.set(1));
2550
+ currentCharModel.meshes.traverse(obj => obj.layers?.set(2)); // head only on 2
2374
2551
 
2375
- // (6) Add to scene
2376
- scene.add(body);
2552
+ // Camera
2553
+ const camera = getCameraForViewType(ViewType.MakeIcon);
2554
+ camera.aspect = width / height;
2555
+ camera.updateProjectionMatrix();
2556
+ fitCameraToObject(camera, wholeMii);
2557
+
2558
+ // --- Body world bounds (for plane sizing/placement)
2559
+ const bodyBox = new THREE.Box3().setFromObject(body);
2560
+ const bodySize = new THREE.Vector3();
2561
+ const bodyCenter = new THREE.Vector3();
2562
+ bodyBox.getSize(bodySize);
2563
+ bodyBox.getCenter(bodyCenter);
2564
+
2565
+ // --- BODY BAKE (lights for body only, depend on mode)
2566
+ const bakeAmbient = new THREE.AmbientLight(0xffffff, 0.15);
2567
+ const bakeRim = new THREE.DirectionalLight(
2568
+ 0xffffff,
2569
+ 3
2570
+ );
2571
+ bakeRim.position.set(-3, 7, 1.0);
2572
+ bakeAmbient.layers.enable(1);
2573
+ bakeRim.layers.enable(1);
2574
+ scene.add(bakeAmbient, bakeRim);
2575
+
2576
+ // Pass: body layer → pixels (no CPU flip; we'll let Three flip on texture)
2577
+ const bodyPixels = renderLayerToPixels(renderer, scene, camera, gl, width, height, /*layer*/1, /*flipY*/false);
2578
+ const bodyCanvas = createCanvas(width, height);
2579
+ bodyCanvas.getContext("2d").putImageData(new ImageData(new Uint8ClampedArray(bodyPixels), width, height), 0, 0);
2580
+
2581
+ // Remove bake lights & 3D body; we’ll insert a plane instead
2582
+ scene.remove(bakeAmbient, bakeRim);
2583
+ wholeMii.remove(body);
2584
+ bakeAmbient.dispose?.(); bakeRim.dispose?.();
2585
+
2586
+ // --- BODY PLANE (unlit; texture carries shading)
2587
+ const bodyTex = new THREE.CanvasTexture(bodyCanvas);
2588
+ bodyTex.colorSpace = THREE.SRGBColorSpace;
2589
+ bodyTex.generateMipmaps = false;
2590
+ bodyTex.minFilter = THREE.LinearFilter;
2591
+ bodyTex.magFilter = THREE.LinearFilter;
2592
+ bodyTex.wrapS = THREE.ClampToEdgeWrapping;
2593
+ bodyTex.wrapT = THREE.ClampToEdgeWrapping;
2594
+ bodyTex.flipY = true; // let Three handle UV-space flip
2595
+ bodyTex.premultiplyAlpha = true;
2596
+ bodyTex.needsUpdate = true;
2597
+ bodyTex.flipY = false;
2598
+
2599
+ const planeW = Math.max(1e-4, bodySize.x);
2600
+ const planeH = Math.max(1e-4, bodySize.y);
2601
+ const planeGeo = new THREE.PlaneGeometry(planeW, planeH);
2602
+ const planeMat = new THREE.MeshBasicMaterial({ map: bodyTex, transparent: true, depthWrite: true, depthTest: true });
2603
+ const bodyPlane = new THREE.Mesh(planeGeo, planeMat);
2604
+ bodyPlane.userData.isBodyPlane = true;
2605
+
2606
+ // Place plane at body world center so the neck peg aligns into head
2607
+ bodyPlane.position.copy(bodyCenter);
2608
+ bodyPlane.layers.set(2); // render with head
2609
+
2610
+ // === Apply height/weight scaling in BOTH modes ===
2611
+ const w01 = remap01(options.weight ?? 64);
2612
+ const h01 = remap01(options.height ?? 64);
2613
+ const scaleX = lerp(0.55, 1.50, w01);
2614
+ const scaleY = lerp(0.55, 1.35, h01);
2615
+ bodyPlane.scale.set(scaleX, scaleY, 1);
2616
+
2617
+ // --- Auto vertical offset (per-mode) + manual per-mode knob ---
2618
+ var tYraw = invLerp(BODY_SCALE_Y_RANGE[0], BODY_SCALE_Y_RANGE[1], scaleY);
2619
+ var tY = clamp01(easePow(tYraw, 1));
2620
+
2621
+ const autoRange = [150, 125];
2622
+
2623
+ const autoOffsetYPx = autoRange[0] + (autoRange[1] - autoRange[0]) * tY;
2624
+
2625
+ // Manual knobs (mode-specific; falls back to legacy bodyOffsetYPx)
2626
+ const manualPx = (options.bodyOffsetYPxFull ?? options.bodyOffsetYPx ?? 0);
2627
+
2628
+ const combinedOffsetYPx = Math.round(manualPx + autoOffsetYPx);
2629
+
2630
+ // Convert screen-px → world-Y at the plane depth & apply
2631
+ if (combinedOffsetYPx) {
2632
+ const planeDepthFromCam = bodyPlane.position.clone().sub(camera.position).length();
2633
+ const worldYOffset = pixelYToWorldY(camera, planeDepthFromCam, combinedOffsetYPx, height);
2634
+ bodyPlane.position.y += worldYOffset;
2377
2635
  }
2378
2636
 
2379
2637
 
2380
- // Use the camera for an icon pose.
2381
- const camera = getCameraForViewType(ViewType.MakeIcon);
2638
+ // Optional depth nudge
2639
+ const bodyDepthOffset = Number(options.bodyDepthOffset ?? 0);
2640
+ if (bodyDepthOffset) offsetObjectAlongView(bodyPlane, camera, bodyDepthOffset);
2382
2641
 
2383
- // The pixels coming from WebGL are upside down.
2384
- camera.projectionMatrix.elements[5] *= -1; // Flip the camera Y axis.
2385
- // When flipping the camera, the triangles are in the wrong direction.
2386
- scene.traverse(mesh => {
2387
- if (
2388
- mesh.isMesh &&
2389
- mesh.material.side === THREE.FrontSide &&
2390
- !mesh.userData.isMiiBody
2391
- ) {
2392
- mesh.material.side = THREE.BackSide;
2393
- }
2394
- });
2642
+ scene.add(bodyPlane);
2395
2643
 
2644
+ // Ensure head renders with the plane on the same layer and with NO head lights
2645
+ currentCharModel.meshes.traverse(o => o.layers?.set(2));
2396
2646
 
2397
- // Render the scene, and read the pixels into a buffer.
2647
+ // Final pass: render layer 2 (head + body plane), then flip pixels for PNG
2648
+ camera.layers.set(2);
2649
+ renderer.setRenderTarget(null);
2650
+ renderer.clear(true, true, true);
2398
2651
  renderer.render(scene, camera);
2399
- const pixels = new Uint8Array(width * height * 4);
2400
- gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
2401
-
2402
- // Draw the pixels to a new canvas.
2403
- const canvas = createCanvas(width, height);
2404
- const img = new ImageData(new Uint8ClampedArray(pixels), width, height);
2405
- canvas.getContext('2d').putImageData(img, 0, 0);
2406
-
2407
- return canvas.toBuffer('image/png'); // Encode image to PNG
2408
2652
 
2409
- } catch (error) {
2410
- console.error('Error during rendering:', error);
2411
- throw error;
2653
+ // Read back & flip to top-left
2654
+ const finalPixels = new Uint8Array(width * height * 4);
2655
+ gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, finalPixels);
2656
+ const upright = flipPixelsVertically(finalPixels, width, height);
2657
+
2658
+ // Stage onto a canvas
2659
+ const stage = createCanvas(width, height);
2660
+ stage.getContext("2d").putImageData(
2661
+ new ImageData(new Uint8ClampedArray(upright), width, height),
2662
+ 0, 0
2663
+ );
2664
+
2665
+ // === FullBody-only: crop from the BOTTOM based on scaleY ===
2666
+ let cropBottom = 0;
2667
+ tYraw = invLerp(BODY_SCALE_Y_RANGE[0], BODY_SCALE_Y_RANGE[1], scaleY);
2668
+ tY = clamp01(easePow(tYraw, 1));
2669
+
2670
+ // Interpolate bottom crop across the configured range
2671
+ const bottomPx = Math.round(
2672
+ FULLBODY_CROP_BOTTOM_PX_RANGE[0] +
2673
+ (FULLBODY_CROP_BOTTOM_PX_RANGE[1] - FULLBODY_CROP_BOTTOM_PX_RANGE[0]) * tY
2674
+ );
2675
+
2676
+ cropBottom = Math.max(0, Math.min(height - 1, bottomPx + (options.fullBodyCropExtraBottomPx ?? 0)));
2677
+
2678
+ // Output with bottom crop applied (no top crop)
2679
+ const outH = Math.max(1, isFullBody?height - cropBottom:450);
2680
+ const outCanvas = createCanvas(width, outH);
2681
+ const ctxOut = outCanvas.getContext("2d");
2682
+
2683
+ // Source: take the top `outH` rows (i.e., drop `cropBottom` pixels at the bottom)
2684
+ ctxOut.drawImage(
2685
+ stage,
2686
+ 0, 0, // sx, sy
2687
+ width, outH, // sw, sh
2688
+ 0, 0, // dx, dy
2689
+ width, outH // dw, dh
2690
+ );
2691
+
2692
+ return outCanvas.toBuffer("image/png");
2693
+
2694
+ } catch (err) {
2695
+ console.error("Error rendering Mii:", err);
2696
+ throw err;
2412
2697
  } finally {
2413
- // Clean up.
2414
2698
  try {
2415
- (currentCharModel) && currentCharModel.dispose(); // Mii model
2416
- exitFFL(ffl.module, ffl.resourceDesc); // Free fflRes from memory.
2417
- renderer.dispose(); // Dispose Three.js renderer.
2699
+ currentCharModel?.dispose?.();
2700
+ exitFFL(ffl?.module, ffl?.resourceDesc);
2701
+ renderer.dispose();
2418
2702
  gl.finish();
2419
- } catch (error) {
2420
- console.warn('Error disposing Mii and renderer:', error);
2421
- }// finally {
2422
- // console.debug = _realConsoleDebug;
2423
- //}
2703
+ } catch { }
2704
+ console.debug = _realDebug;
2424
2705
  }
2425
2706
  }
2426
- async function renderMii(jsonIn, fflRes = getFFLRes()) {
2707
+
2708
+ async function renderMii(jsonIn, options = {}, fflRes = getFFLRes()) {
2427
2709
  if (!["3ds", "wii u"].includes(jsonIn.console?.toLowerCase())) {
2428
2710
  jsonIn = convertMii(jsonIn);
2429
2711
  }
2430
2712
  const studioMii = convertMiiToStudio(jsonIn);
2431
- const width = height = 600;
2713
+ options = Object.assign(options, {
2714
+ gender: jsonIn.general.gender,
2715
+ height: jsonIn.general.height,
2716
+ weight: jsonIn.general.weight,
2717
+ pantsType: jsonIn.meta?.type||"Default"
2718
+ });
2432
2719
 
2433
- return createFFLMiiIcon(studioMii, width, height, true, jsonIn.general.favoriteColor, fflRes);
2720
+ return createFFLMiiIcon(studioMii, options, jsonIn.general.favoriteColor, fflRes);
2434
2721
  }
2435
2722
  async function writeWiiBin(jsonIn, outPath) {
2436
2723
  if (jsonIn.console?.toLowerCase() !== "wii") {
@@ -2674,7 +2961,9 @@ async function write3DSQR(miiJson, outPath, returnBin, fflRes = getFFLRes()) {
2674
2961
  }
2675
2962
  const buffer = Buffer.from(buffers);
2676
2963
  var encryptedData = Buffer.from(encodeAesCcm(new Uint8Array(buffer)));
2677
-
2964
+ if (returnBin) {
2965
+ return encryptedData;
2966
+ }
2678
2967
  //Prepare a QR code
2679
2968
  const options = {
2680
2969
  width: 300,
@@ -2776,7 +3065,7 @@ function make3DSChild(dad, mom, options = {}) {
2776
3065
  "height": 64,
2777
3066
  "weight": 64,
2778
3067
  "gender": g,
2779
- "favColor": options.favColor || favCols[Math.floor(Math.random() * favCols.length)]
3068
+ "favoriteColor": options.favoriteColor || favCols[Math.floor(Math.random() * favCols.length)]
2780
3069
  },
2781
3070
  "meta": {
2782
3071
  "name": options.name || kidNames[g][Math.floor(Math.random() * kidNames[g].length)],
@@ -2826,7 +3115,6 @@ function make3DSChild(dad, mom, options = {}) {
2826
3115
  function generateInstructions(mii, full) {
2827
3116
  let type = mii.console?.toLowerCase();
2828
3117
  if (type.toLowerCase() === "wii") {
2829
- var typeCheat = [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3];
2830
3118
  var instrs = {
2831
3119
  "base": `Select "${mii.general.gender}", and then "Start from Scratch".`,
2832
3120
  "col": `On the info page (first tab), set the Favorite Color to ${lookupTables.favCols[mii.general.favoriteColor]} (${mii.general.favoriteColor <= 5 ? mii.general.favoriteColor + 1 : mii.general.favoriteColor - 5} from the left, ${mii.general.favoriteColor > 5 ? "bottom" : "top"} row).`,
@@ -2836,7 +3124,7 @@ function generateInstructions(mii, full) {
2836
3124
  "makeup": `On the face page's makeup tab, set the makeup to the one ${Math.ceil((mii.face.feature + 1) / 3)} from the top, and ${typeCheat[mii.face.feature]} from the left.`,
2837
3125
  "hairStyle": `On the hair page (fourth tab), set the hair style to the one ${typeCheat[mii.hair.type]} from the left, ${Math.ceil((mii.hair.type + 1) / 3)} from the top, on page ${mii.hair.page}.`,
2838
3126
  "hairFlipped": `${mii.hair.flipped ? `On the hair page (fourth tab), press the button to flip the hair.` : ``}`,
2839
- "hairColor": `On the hair page (fourth tab), set the hair color to the one ${mii.hair.col + (mii.hair.col > 3 ? -3 : 1)} from the left, on the ${mii.hair.col > 3 ? `bottom` : `top`} row.`,
3127
+ "hairColor": `On the hair page (fourth tab), set the hair color to the one ${mii.hair.color + (mii.hair.color > 3 ? -3 : 1)} from the left, on the ${mii.hair.color > 3 ? `bottom` : `top`} row.`,
2840
3128
  "eyebrowStyle": `On the eyebrow page (fifth tab), set the eyebrow style to the one ${typeCheat[mii.eyebrows.type]} from the left, ${Math.ceil((mii.eyebrows.type + 1) / 3)} from the top, on page ${mii.eyebrows.page}.`,
2841
3129
  "eyebrowColor": `On the eyebrow page (fifth tab), set the eyebrow color to the one ${mii.eyebrows.color + (mii.eyebrows.color > 3 ? -3 : 1)} from the left, on the ${mii.eyebrows.color > 3 ? `bottom` : `top`} row.`,
2842
3130
  "eyebrowY": `${mii.eyebrows.yPos !== 7 ? `On the eyebrow page (fifth tab), ` : ``}${mii.eyebrows.yPosition < 7 ? `press the up button ${7 - mii.eyebrows.yPosition} times.` : mii.eyebrows.yPosition > 7 ? `press the down button ${mii.eyebrows.yPosition - 7} times.` : ``}`,
@@ -2853,7 +3141,7 @@ function generateInstructions(mii, full) {
2853
3141
  "noseY": `${mii.nose.yPosition !== 9 ? `On the nose page (seventh tab), ` : ``}${mii.nose.yPosition < 9 ? `press the up button ${9 - mii.nose.yPosition} times.` : mii.nose.yPosition > 9 ? `press the down button ${mii.nose.yPosition - 9} times.` : ``}`,
2854
3142
  "noseSize": `${mii.nose.size !== 4 ? `On the nose page (seventh tab), ` : ``}${mii.nose.size < 4 ? `press the shrink button ${4 - mii.nose.size} times.` : mii.nose.size > 4 ? `press the enlarge button ${mii.nose.size - 4} times.` : ``}`,
2855
3143
  "mouthType": `On the mouth page (eighth tab), set the mouth type to the one ${typeCheat[mii.mouth.type]} from the left, ${Math.ceil((mii.mouth.type + 1) / 3)} from the top, on page ${mii.mouth.page}.`,
2856
- "mouthCol": `On the mouth page (eighth tab), set the color to the one ${mii.mouth.col + 1} from the left.`,
3144
+ "mouthCol": `On the mouth page (eighth tab), set the color to the one ${mii.mouth.color + 1} from the left.`,
2857
3145
  "mouthY": `${mii.mouth.yPosition !== 13 ? `On the mouth page (eighth tab), ` : ``}${mii.mouth.yPosition < 13 ? `press the up button ${13 - mii.mouth.yPosition} times.` : mii.mouth.yPosition > 13 ? `press the down button ${mii.mouth.yPosition - 13} times.` : ``}`,
2858
3146
  "mouthSize": `${mii.mouth.size !== 4 ? `On the mouth page (eighth tab), ` : ``}${mii.mouth.size < 4 ? `press the shrink button ${4 - mii.mouth.size} times.` : mii.mouth.size > 4 ? `press the enlarge button ${mii.mouth.size - 4} times.` : ``}`,
2859
3147
  "glasses": `On the glasses page (within the ninth tab), set the glasses to the one ${Math.ceil((mii.glasses.type + 1) / 3)} from the top, and ${typeCheat[mii.glasses.type]} from the left.`,
@@ -2868,7 +3156,7 @@ function generateInstructions(mii, full) {
2868
3156
  "moleY": `${mii.mole.yPosition !== 20 ? `On the mole page (within the ninth tab), press the ` : ``}${mii.mole.yPosition > 20 ? `down button ${mii.mole.yPosition - 20} times.` : mii.mole.yPosition < 20 ? `up button ${20 - mii.mole.yPosition} times.` : ``}`,
2869
3157
  "moleSize": `${mii.mole.size !== 4 ? `On the mole page (within the ninth tab), ` : ``}${mii.mole.size < 4 ? `press the shrink button ${4 - mii.mole.size} times.` : mii.mole.size > 4 ? `press the enlarge button ${mii.mole.size - 4} times.` : ``}`,
2870
3158
  "beard": `On the beard page (within the ninth tab), set the beard to the one on the ${[0, 1].includes(mii.beard.type) ? `top` : `bottom`}-${[0, 2].includes(mii.beard.type) ? `left` : `right`}.`,
2871
- "beardCol": `On the mustache OR beard pages (within the ninth tab), set the color to the one ${mii.beard.col + (mii.beard.col > 3 ? -3 : 1)} from the left, on the ${mii.facialHair.col > 3 ? `bottom` : `top`} row.`,
3159
+ "beardCol": `On the mustache OR beard pages (within the ninth tab), set the color to the one ${mii.beard.color + (mii.beard.color > 3 ? -3 : 1)} from the left, on the ${mii.facialHair.color > 3 ? `bottom` : `top`} row.`,
2872
3160
  "other": `The Nickname of this Mii is ${mii.info.name}.${mii.info.creatorName ? ` The creator was ${mii.info.creatorName}.` : ``} Mingle was turned ${mii.info.mingle ? `on` : `off`}.${mii.info.birthday !== 0 ? ` Its birthday is ${["", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"][mii.info.birthMonth]} ${mii.info.birthday}.` : ``}`
2873
3161
  };
2874
3162
  if (!full) {
@@ -2899,7 +3187,7 @@ function generateInstructions(mii, full) {
2899
3187
  "eyebrowDist": `${mii.eyebrows.distanceApart !== 2 ? `On the eyebrow page (third tab), ` : ``}${mii.eyebrows.distanceApart < 2 ? `press the closer-together button ${2 - mii.eyebrows.distanceApart} times.` : mii.eyebrows.distanceApart > 2 ? `press the further-apart button ${mii.eyebrows.distanceApart - 2} times.` : ``}`,
2900
3188
  "eyebrowSquash": `${mii.eyebrows.squash !== 3 ? `On the eyebrow page (third tab), ` : ``}${mii.eyebrows.squash < 3 ? `press the squish button ${3 - mii.eyebrows.squash} times.` : mii.eyebrows.squash > 3 ? `press the un-squish button ${mii.eyebrows.squash - 3} times.` : ``}`,
2901
3189
  "eyeType": `On the eye page (fourth tab), set the eye type to the one ${typeCheat[mii.eyes.type]} from the left, ${Math.ceil((mii.eyes.type + 1) / 3)} from the top, on page ${mii.eyes.page + 1}.`,
2902
- "eyeColor": `On the eye page (fourth tab), set the color to the one ${mii.eyes.col + 1} from the top.`,
3190
+ "eyeColor": `On the eye page (fourth tab), set the color to the one ${mii.eyes.color + 1} from the top.`,
2903
3191
  "eyeY": `${mii.eyes.yPosition !== 12 ? `On the eye page (fourth tab), ` : ``}${mii.eyes.yPosition < 12 ? `press the up button ${12 - mii.eyes.yPosition} times.` : mii.eyes.yPosition > 12 ? `press the down button ${mii.eyes.yPosition - 12} times.` : ``}`,
2904
3192
  "eyeSize": `${mii.eyes.size !== 4 ? `On the eye page (fourth tab), ` : ``}${mii.eyes.size < 4 ? `press the shrink button ${4 - mii.eyes.size} times.` : mii.eyes.size > 4 ? `press the enlarge button ${mii.eyes.size - 4} times.` : ``}`,
2905
3193
  "eyeRot": `${mii.eyes.rotation !== (mii.general.gender === "Female" ? 3 : 4) ? `On the eye page (fourth tab), ` : ``}${mii.eyes.rotation < (mii.general.gender === "Female" ? 3 : 4) ? `press the rotate clockwise button ${(mii.general.gender === "Female" ? 3 : 4) - mii.eyes.rotation} times.` : mii.eyes.rotation > (mii.general.gender === "Female" ? 3 : 4) ? `press the rotate counter-clockwise button ${mii.eyes.rotation - (mii.general.gender === "Female" ? 3 : 4)} times.` : ``}`,
@@ -2914,7 +3202,7 @@ function generateInstructions(mii, full) {
2914
3202
  "mouthSize": `${mii.mouth.size !== 4 ? `On the mouth page (sixth tab), ` : ``}${mii.mouth.size < 4 ? `press the shrink button ${4 - mii.mouth.size} times.` : mii.mouth.size > 4 ? `press the enlarge button ${mii.mouth.size - 4} times.` : ``}`,
2915
3203
  "mouthSquash": `${mii.mouth.squash !== 3 ? `On the mouth page (sixth tab), ` : ``}${mii.mouth.squash < 3 ? `press the squish button ${3 - mii.mouth.squash} times.` : mii.mouth.squash > 3 ? `press the un-squish button ${mii.mouth.squash - 3} times.` : ``}`,
2916
3204
  "glasses": `On the glasses page (within the seventh tab), set the glasses to the one ${Math.ceil((mii.glasses.type + 1) / 3)} from the top, and ${typeCheat[mii.glasses.type]} from the left.`,
2917
- "glassesCol": `On the glasses page (within the seventh tab), set the color to the one ${mii.glasses.col + 1} from the top.`,
3205
+ "glassesCol": `On the glasses page (within the seventh tab), set the color to the one ${mii.glasses.color + 1} from the top.`,
2918
3206
  "glassesY": `${mii.glasses.yPosition !== 10 ? `On the glasses page (within the seventh tab), ` : ``}${mii.glasses.yPosition < 10 ? `press the up button ${10 - mii.glasses.yPosition} times.` : mii.glasses.yPosition > 10 ? `press the down button ${mii.glasses.yPosition - 10} times.` : ``}`,
2919
3207
  "glassesSize": `${mii.glasses.size !== 4 ? `On the glasses page (within the seventh tab), ` : ``}${mii.glasses.size < 4 ? `press the shrink button ${4 - mii.glasses.size} times.` : mii.glasses.size > 4 ? `press the enlarge button ${mii.glasses.size - 4} times.` : ``}`,
2920
3208
  "stache": `On the mustache page (within the seventh tab), set the mustache to the one on the ${[0, 1].includes(mii.beard.mustache.type) ? `top` : [2, 3].includes(mii.beard.mustache.type) ? `middle` : `bottom`}-${[0, 2, 4].includes(mii.beard.mustache.type) ? `left` : `right`}.`,
@@ -2928,7 +3216,7 @@ function generateInstructions(mii, full) {
2928
3216
  "beardCol": `On the mustache OR beard pages (within the seventh tab), set the color to the one ${mii.beard.color + 1} from the top.`,
2929
3217
  "heightWeight": `On the build page (eighth tab), set the height to ${Math.round((100 / 128) * mii.general.height)}%, and the weight to ${Math.round((100 / 128) * mii.general.weight)}%.`,
2930
3218
  "col": `On the info page (after pressing "Next"), set the Favorite Color to ${mii.general.favoriteColor} (${mii.general.favoriteColor <= 5 ? mii.general.favoriteColor + 1 : mii.general.favoriteColor - 5} from the left, ${mii.general.favoriteColor > 5 ? "bottom" : "top"} row).`,
2931
- "other": `The Nickname of this Mii is ${mii.general.name}.${mii.general.creatorName ? ` The creator was ${mii.general.creatorName}.` : ``} ${mii.general.birthday !== 0 ? ` Its birthday is ${["", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"][mii.general.birthMonth]} ${mii.general.birthday}.` : ``}`
3219
+ "other": `The Nickname of this Mii is ${mii.meta.name}.${mii.meta.creatorName ? ` The creator was ${mii.meta.creatorName}.` : ``} ${mii.general.birthday !== 0 ? ` Its birthday is ${["", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"][mii.general.birthMonth]} ${mii.general.birthday}.` : ``}`
2932
3220
  };
2933
3221
  if (!full) {
2934
3222
  var defaultMiiInstrs = structuredClone(mii.general.gender === "Male" ? defaultInstrs["3ds"].male : defaultInstrs["3ds"].female);
@@ -3051,9 +3339,9 @@ module.exports = {
3051
3339
  miiWeightToRealWeight,//EXPERIMENTAL
3052
3340
 
3053
3341
  /*
3054
- Handle Amiibo Functions
3055
- insertMiiIntoAmiibo(amiiboDump, decrypted3DSMiiBuffer),
3056
- extractMiiFromAmiibo(amiiboDump)
3342
+ Handle Amiibo Functions
3343
+ insertMiiIntoAmiibo(amiiboDump, decrypted3DSMiiBuffer),
3344
+ extractMiiFromAmiibo(amiiboDump)
3057
3345
  */
3058
3346
  ...require("./amiiboHandler.js")
3059
3347
  }
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "miijs",
3
- "version": "2.3.1",
3
+ "version": "2.3.3",
4
4
  "description": "Work with Mii characters in every possible way needed for your project.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "repository": {
11
11
  "type": "git",
12
- "url": "git+https://github.com/KestronProgramming/MiiJS.git"
12
+ "url": "git+https://github.com/Stewared/MiiJS.git"
13
13
  },
14
14
  "keywords": [
15
15
  "Mii",
@@ -19,12 +19,12 @@
19
19
  "Remote",
20
20
  "Special"
21
21
  ],
22
- "author": "Kestron",
22
+ "author": "Stewared",
23
23
  "license": "ISC",
24
24
  "bugs": {
25
- "url": "https://github.com/KestronProgramming/MiiJS/issues"
25
+ "url": "https://github.com/Stewared/MiiJS/issues"
26
26
  },
27
- "homepage": "https://github.com/KestronProgramming/MiiJS#readme",
27
+ "homepage": "https://github.com/Stewared/MiiJS#readme",
28
28
  "dependencies": {
29
29
  "canvas": "^3.1.0",
30
30
  "ffl.js": "github:ariankordi/FFL.js#06ede8f",
File without changes