Extraction de modeles 3D nintendo switch

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 à faire à 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:

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:



