DS Mii Format to Wii Format Converter

Drop a DS format (".ncd") Mii file or its 74-byte data in hex here.
Or drop a save file/state/memdump from Tomodachi Collection.



Result List

nc function handleWiiDS(){ clearList('wiiResults'); const fileInput=document.getElementById('wiiFileInput'); if(fileInput.files.length===0){ showMessage('wiiResults','Sube un archivo .rcd/.mii/.mae/.miigx'); return; } for(const f of fileInput.files){ try{ const arr = new Uint8Array(await f.arrayBuffer()); // strip common RCD header if present let payload = arr; if(arr.length>8 && arr[0]===0x52 && arr[1]===0x43 && arr[2]===0x44){ // starts with 'RCD' // assume header then raw data payload = arr.slice(8); // skip header used above } // if payload is longer than 74, try to find 74-byte region (take first 74) let dsBlock = padTo74Bytes(payload.slice(0,DATA_LENGTH)); // Many Wii files store in different endian; attempt reverse-swap to DS format try{ dsBlock = convertWiiToDs(dsBlock); }catch(e){ /* ignore and use raw */ } const hex = bytesToHex(dsBlock); const name = extractName(dsBlock); const ul=document.getElementById('wiiResults'); const li=document.createElement('li'); li.textContent = name + ' — ' + hex.slice(0,32)+'...'; const btn = document.createElement('button'); btn.textContent='Descargar .ncd'; btn.onclick=()=>downloadBytes(dsBlock, name+'.ncd'); li.appendChild(btn); ul.appendChild(li); }catch(err){ showMessage('wiiResults','Error leyendo archivo: '+err.message); } } } function convertWiiToDs(wiiBlock){ const copy = new Uint8Array(wiiBlock); swapEndian(copy, reverseSwapDesc(SWAP_ENDIAN_DESC_RFL)); return padTo74Bytes(copy); } // ---------- Sección C: Insertar Miis en save (editar en memoria, opción 2) ---------- let currentSave = null; // Uint8Array let modifiedSave = null; document.getElementById('formInsert').addEventListener('submit', function(e){ e.preventDefault(); handleInsert(); }); async function handleInsert(){ const destInput = document.getElementById('saveDestInput'); const sourceInput = document.getElementById('saveSourceInput'); const statusDiv = document.getElementById('insertStatus'); statusDiv.textContent=''; if(destInput.files.length===0){ statusDiv.textContent='Sube primero el save destino (.sav)'; return; } // load destination if not loaded or different file try{ currentSave = new Uint8Array(await destInput.files[0].arrayBuffer()); modifiedSave = new Uint8Array(currentSave); }catch(err){ statusDiv.textContent='Error leyendo save destino: '+err.message; return; } if(sourceInput.files.length===0){ statusDiv.textContent='Sube al menos un .ncd o un save fuente con Miis para insertar'; return; } // for each source file: if .ncd (len 74 or >=74?) treat as single block; if .sav treat as source save and extract blocks let insertedCount = 0; for(const f of sourceInput.files){ try{ const arr = new Uint8Array(await f.arrayBuffer()); if(arr.length===DATA_LENGTH){ // single .ncd const block = arr; if(insertBlockIntoModifiedSave(block)) insertedCount++; else { statusDiv.textContent = 'No se encontró espacio para insertar (se agregará al final) — procediendo a añadir.'; insertBlockIntoModifiedSave(block,true); insertedCount++; } } else if(arr.length>DATA_LENGTH && containsSaveSignature(arr)){ // source save — extract blocks const blocks = dedupeBlocks(extractMiiBlocksFromData(arr)); if(blocks.length===0){ statusDiv.textContent = 'No se encontraron Miis en el save fuente: '+f.name; } for(const b of blocks){ if(insertBlockIntoModifiedSave(b.block)) insertedCount++; else { insertBlockIntoModifiedSave(b.block,true); insertedCount++; } } } else if(arr.length>DATA_LENGTH && !containsSaveSignature(arr)){ // maybe the user uploaded an .rcd or Wii file — try convert to DS then insert let payload = arr; if(arr.length>8 && arr[0]===0x52 && arr[1]===0x43 && arr[2]===0x44) payload = arr.slice(8); const ds = padTo74Bytes(payload.slice(0,DATA_LENGTH)); const dsConverted = convertWiiToDs(ds); if(insertBlockIntoModifiedSave(dsConverted)) insertedCount++; else { insertBlockIntoModifiedSave(dsConverted,true); insertedCount++; } } else { statusDiv.textContent = 'Formato de archivo no reconocido: '+f.name; } }catch(err){ statusDiv.textContent = 'Error procesando '+f.name+': '+err.message; } } statusDiv.textContent = `Miis insertados en memoria: ${insertedCount}`; if(modifiedSave) document.getElementById('downloadModifiedSave').disabled = false; } function containsSaveSignature(arr){ for(let i=0;i<=arr.length-SAVE_SIGNATURE.length;i++){ let ok=true; for(let j=0;j0){ modifiedSave.set(block, offsets[0]); return true; } // else append at end if(appendIfNoSlot){ const newLen = modifiedSave.length + SAVE_SIGNATURE.length + DATA_LENGTH; const out = new Uint8Array(newLen); out.set(modifiedSave,0); out.set(SAVE_SIGNATURE, modifiedSave.length); out.set(block, modifiedSave.length + SAVE_SIGNATURE.length); modifiedSave = out; return true; } return false; } // Download modified save button document.getElementById('downloadModifiedSave').addEventListener('click', function(){ if(!modifiedSave){ alert('No hay save modificado en memoria'); return; } const blob = new Blob([modifiedSave], {type:'application/octet-stream'}); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'tomodachi_modified.sav'; a.click(); }); // ---------- Helpers ---------- function downloadBytes(bytes, filename){ const blob = new Blob([bytes], {type:'application/octet-stream'}); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = filename; a.click(); } function clearList(id){ const ul=document.getElementById(id); ul.innerHTML=''; } function showMessage(id, text){ const ul=document.getElementById(id); const li=document.createElement('li'); li.textContent = text; ul.appendChild(li); }