Woohoo, having asked on the Celestial Heavens forum, I could use MMExtension to get the map data from the original game. I’ve managed to load it into Godot, a bare view:

This is mean to be the Harmondale castle, as the geometry is relatively simple (walls and floors). Missing are of course textures, lights, contents, etc., but that’s the start!
To get it, I needed to use the MMExtension and MMEditor made by GrayFace. Start the game, get to the location you want to export, ALT+F1 to bring up the extension’s menu, then select Export (third row, third column).

The output is custom .obj file, listing rooms (“g”), vertices (“v”), textures (“vt”) and facets (“f”). Facets turn out to be triangle fans of the defined vertices.
I could then write a very simple GDScript to parse parts of it:
class_name DungeonLoader
extends Script
const kScalingFactor: float = 0.01
static func load_dungeon_map(path: String) -> DungeonGeometry:
var file = FileAccess.open(path, FileAccess.READ)
if file == null:
Log.error("Could not load %s: %s", [path, error_string(FileAccess.get_open_error())])
return null
var geometry = DungeonGeometry.new()
while !file.eof_reached():
var line = file.get_line()
if line.is_empty():
continue
match line.substr(0, 2):
"v ":
geometry.vertices.append(_parse_vertex_line(line))
"f ":
geometry.facets.append(_parse_facet_line(line))
return geometry
static func _parse_vertex_line(line: String) -> Vector3:
var parts = line.split(" ")
return kScalingFactor * Vector3(float(parts[1]), float(parts[2]), float(parts[3]))
static func _parse_facet_line(line: String) -> Array[int]:
var parts = line.split(" ")
var indexes: Array[int] = []
for i in range(1, parts.size()):
var final_parts = parts[i].split("/")
indexes.append(int(final_parts[0]) - 1)
return indexes
… and eventually consume it to generate the geometry:
func _debug_mesh() -> void:
var surface_tool = SurfaceTool.new()
surface_tool.begin(Mesh.PRIMITIVE_TRIANGLES)
surface_tool.set_color(Color(0, 0, 1, 1))
var geometry = DungeonLoader.load_dungeon_map("res://emerald_world/scenes/world/d29.obj")
for facet in geometry.facets:
var vertices = []
for index in facet:
vertices.append(geometry.vertices[index])
surface_tool.add_triangle_fan(vertices)
surface_tool.generate_normals(false)
$DebugBody3D/MeshInstance3D.mesh = surface_tool.commit()
$DebugBody3D/MeshInstance3D.global_position = Vector3(150, 0.1, 80)
Since Godot supports hot reloads, I could work on the script while debugging it, to see if everything’s loaded nicely.