Extraction de modeles 3D nintendo switch

Oni's blog

Image

Après avoir joué au très joli portage de zelda awakening sur la switch, je me suis demandé s'il était possible d'imprimer la carte du monde en 3d.

Voici un résumé rapide des étapes pour arriver à obtenir un modèle 3d complet de la worldmap.

Extraction des modèles

L'extraction d'une rom au format .xci est un peu compliquée. C'est une image chiffrée donc il faut deux fichiers spéciaux contenant les clés de déchiffrement: title.keys et prod.keys
Elles peuvent être trouvées sur le net en cherchant un peu. La partie difficile étant de trouver une version a jour compatible avec votre jeu.

On utilise alors hactool, un logiciel open source. Il faut placer les clés dans le même dossier. La communauté gbatemps fournis un fichier bat Decrypt-XCI-v2.1.bat pour simplifier l'opération d'extraction.

Pour ceux qui comme moi sont sous linux et doivent faire cela manuellement, l'extraction se fait en 3 étapes.

1- Extraction des .NCA (Nintendo Content Archive):

./hactool -txci --securedir="xciDecrypted" myrom.xci

2- Extraction du romfs (file system) du .NCA le plus volumineux:

./hactool --exefsdir="xciDecrypted" \
    --romfs="xciDecrypted/romfs.romfs" \
    xciDecrypted/larger_file.nca

3- Extraction des fichiers depuis le romfs:

./hactool -t romfs --outdir extracted_files \
    xciDecrypted/romfs.romfs

Les modèles qui nous interessent se trouvent dans /region_common/map et sont les fichiers Field_01A.bfres ... Field_16P.bfres
On remarquera que la worldmap est découpée comme dans le jeu original, en 16x16 chunks (256 fichiers).

Conversion des modèles

La conversion des modèles .bfres se fait avec Switch Toolbox. Un des rares logiciels qui supporte les .bfres de la switch (ce format existant aussi sur wii et wii U avec quelques différences). Il est capable de les exporter en .dae mais la conversion doit se faire malheureusement fichier par fichier... Ce logiciel permet aussi de récupérer les textures et les animations, qui ne seront pas utiles dans notre cas.

Import sous blender

L'import sous la map necessiterais de charger les .dae un a un, qui contiennent un ensemble de modèle 3d, puis de les déplacer correctement pour reconstruire la carte (qui je le rappelle est découpée en 16x16 chunks). Cela serait trop fastidieux a faire a la main, l'idée est donc d'utiliser l'API python de blender pour automatiser la tache.

Reconstruction de la map

Le principe du script est d'importer les 256 chunks automatiquement, les placer aux bonnes coordonnées, et ranger les objets dans une collection comme cela:

Image

Voici le code python pour l'import. C'est un bon exemple pour apprendre à manipuler les collections:

import bpy

def addCollection(col_name, parent_collection = bpy.context.scene.collection):
    # Check if collection already exists
    if col_name in bpy.data.collections:
        return bpy.data.collections[col_name]
    else:
        c = bpy.data.collections.new(col_name)
        parent_collection.children.link(c)
        return c

def findCollection(context, item):
    collections = item.users_collection
    if len(collections) > 0:
        return collections[0]
    return context.scene.collection

def importDaeIntoCollection(filepath, col_name):
    # Create the collection
    c = addCollection(col_name)
    # Import the file
    bpy.ops.wm.collada_import(filepath=filepath, import_units=True)
    # Add the imported files to the collection
    for o in bpy.context.selected_objects:
        # Get the current collection associated to the object
        prev_col = findCollection(bpy.context, o)
        # Link the object to the new collection
        c.objects.link(o)
        prev_col.objects.unlink(o)



lines = ["01","02","03","04","05","06","07","08","09","10","11","12","13","14","15","16"]
columns = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P"]

root = "/path/to/zelda/extracted/files/"

x = 0.0
y = 0.0

for line in lines:
    field_line = "Field_" + line
    line_name = "Field_line_" + line

    c = addCollection(line_name)

    for column in columns:
        cfield = addCollection(field_line + column, c)
        importDaeIntoCollection(root + field_line + column + ".dae", field_line + column)
        for o in bpy.context.selected_objects:
            o.location.x += x
            o.location.y += y
            o.select_set(False)
        x += 15
    x = 0
    y -= 12


Résultats

image-text

image-text

image-text

image-text