3ds/wiiu mii data decoding and encoding using struct-fu sample

by arian_

HTML

<!--
<img id="image" data-src="https://mii-unsecure.ariankordi.net/miis/image.png?verifyCRC16=0&type=all_body_sugar&shaderType=switch&data=">
-->

<img id="image" data-src="https://mii-unsecure.ariankordi.net/miis/image.png?verifyCRC16=0&type=all_body_sugar&shaderType=switch&data=">
<div id="global-error-box" style="display:none;background:#ffeeee;color:#000;border:2px solid red;padding:1em;margin:1em 0;font-family:monospace">
  <div style="font-weight:bold;font-size:1.2em"><span id="global-error-text-error">⚠️ JS Error:</span><span id="global-error-text-promise-rejection">⚠️ JS Promise Rejection:</span></div>
  <div id="global-error-message" style="white-space:pre-wrap;margin-top:0.5em"></div>
  <button id="global-error-hide-button" style="margin-top:1em">Close</button>
</div>

<form id="mii-form">
  <textarea id="mii-input" rows="3" style="width:100%;font-family:monospace"
    placeholder="Base64 or Hex FFLiMiiDataCore / StoreData (74 or 96 bytes)"></textarea>
  <button type="submit">Parse</button>
</form>

<img id="image" src="" style="display:none">
<button id="json-to-ffsd-btn" type="button">Convert JSON to FFSD and render</button>
<textarea id="output" rows="30" style="width:100%;font-family:monospace"></textarea>

<div style="margin-top:1em">
  <label for="base-url-input">Image base URL:</label>
  <input id="base-url-input" type="text" style="width:60%;font-family:monospace" value="https://mii-unsecure.ariankordi.net/miis/image.png?verifyCRC16=0&type=all_body_sugar&shaderType=switch&data=">
</div>

<!-- Global error handling snippet. -->
<script>
  (function () {
    var box = document.getElementById('global-error-box');
    var message = document.getElementById('global-error-message');
    var closeBtn = document.getElementById('global-error-hide-button');
    var textError = document.getElementById('global-error-text-error');
    var textPromiseRejection = document.getElementById('global-error-text-promise-rejection');
 ...

JavaScript

//import _ from 'https://debian.local:8445/assets/struct-fu/lib-browser-working.js';

//import { Buffer } from 'https://cdn.jsdelivr.net/npm/[email protected]/+esm';
//import _ from 'https://cdn.jsdelivr.net/npm/[email protected]/+esm';

function assertField(fieldName, actual, expected) {
    if (actual === expected) {
        console.log('%c' + fieldName + ': expected ' + expected + ', got ' + actual + ' ✅', 'color: green; font-weight: bold;');
    } else {
        console.error('%c' + fieldName + ': expected ' + expected + ', got ' + actual + ' ❌', 'color: red; font-weight: bold;');
    }
}

var stripSpaces = function(str) {
  return str.replace(/\s+/g, '');
};

var hexToUint8Array = function(hex) {
  return new Uint8Array(hex.match(/.{1,2}/g).map(function(byte) {
    return parseInt(byte, 16);
  }));
};

var base64ToUint8Array = function(base64) {
  // Replace URL-safe Base64 characters
  var normalizedBase64 = base64.replace(/-/g, '+').replace(/_/g, '/');

  // Custom function to pad the string with '=' manually
  var padBase64 = function(str) {
    while (str.length % 4 !== 0) {
      str += '=';
    }
    return str;
  };
  // Add padding if necessary
  var paddedBase64 = padBase64(normalizedBase64);
  var binaryString = atob(paddedBase64);
  var len = binaryString.length;
  var bytes = new Uint8Array(len);
  for (var i = 0; i < len; i++) {
    bytes[i] = binaryString.charCodeAt(i);
  }
  return bytes;
};

var uint8ArrayToBase64 = function(data) {
  return btoa(String.fromCharCode.apply(null, data));
};

var byteToHex = function(num) {
  return ('0' + num.toString(16)).slice(-2);
};

var parseHexOrB64TextStringToUint8Array = function(text) {
  var inputData;
  // decode it to a uint8array whether it's hex or base64
  var textData = stripSpaces(text);
  // check if it's base 16 exclusively, otherwise assume base64
  if (/^[0-9a-fA-F]+$/.test(textData))
    inputData = hexToUint8Array(textData);
  else
   ...