Skip to content

Instantly share code, notes, and snippets.

@jbreckmckye
Created August 3, 2025 21:10
Show Gist options
  • Save jbreckmckye/6b79ee712b49a82303e1447c09ab8095 to your computer and use it in GitHub Desktop.
Save jbreckmckye/6b79ee712b49a82303e1447c09ab8095 to your computer and use it in GitHub Desktop.
FFVII Map to Obj (I didn't write this! It was by Omzy - see https://forums.qhimm.com/index.php?topic=10717.0)
/*
ff7MapToObj
by Omzy ([email protected]) 09/05/2010
[REL] thread at: http://forums.qhimm.com/index.php?topic=10717.0
See my WIP at the MDMD forums: http://z9.invisionfree.com/industrialpolygons/index.php?showtopic=562
Description: Converts a final fantasy 7 .map file to an .obj file that can be opened in various 3d rendering programs.
Requires: lzscdec.exe (by NFITC1) and .map files (wm0, wm2, wm3 from data/wm) must be in same directory.
Download lzscdec.exe from http://www.mediafire.com/?hwyyk2zjjjj
Requires: textures directory with .tga's from world_us.lgp in data/wm.
For documentation on .map format, see http://wiki.qhimm.com/FF7/WorldMap_Module
For documentation on .lsz compression, see http://wiki.qhimm.com/FF7/LZS_format
*/
#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <string>
using namespace std;
string getMtl (short mtlIndex) //mtlIndex is 9 bit value that represents texture id
{
//mtlcodes.txt and mtlnames.txt are in separate files to make texture swapping easier
char * mtlCode = new char[2]; //mtlcode is 2 alpha characters
ifstream mtlcodefile ("mtlcodes.txt", ios::in); //Codes match material names in mtlnames.txt
if (mtlcodefile.is_open())
{
int count(0); //This loop gets the code at line mtlIndex
mtlcodefile.seekg(0, ios::beg);
while (!mtlcodefile.eof() && mtlcodefile.good() && count < mtlIndex)
{
mtlcodefile.get(mtlCode, 3);
mtlcodefile.ignore(100, '\n');
count++;
}
}
else cout << "Unable to open" << "mtlcodes.txt" << endl;
mtlcodefile.close();
char * curCode = new char[2]; //Current code in file
string mtlName = "";
ifstream mtlnamefile ("mtlnames.txt", ios::in); //Names match material codes in mtlcodes.txt
if (mtlnamefile.is_open())
{
mtlnamefile.seekg(0, ios::beg); //This loop gets the name at the line starting with mtlCode
while (!mtlnamefile.eof() && mtlnamefile.good())
{
mtlnamefile.get(curCode, 3);
if (mtlCode[0] == curCode[0] && mtlCode[1] == curCode[1])
{
mtlnamefile.seekg(0x0001, ios::cur); //Skip '=' in file
char * temp = new char[1];
while (*temp != '\0')
{
mtlnamefile.get(temp, 2);
mtlName.append(temp);
}
break;
}
else
{
mtlnamefile.ignore(100, '\n');
}
}
}
else cout << "Unable to open" << "mtlnames.txt" << endl;
mtlnamefile.close();
//cout << mtlIndex << " " << mtlCode << " " << mtlName << endl;
return mtlName;
}
char * getTexDims (string mtlName)
{
char * texDim = new char[4];
//Open .tga file to check dimensions given mtlName
ifstream mtltexfile ("textures\\" + mtlName + ".tga", ios::in);
if (mtltexfile.is_open())
{
mtltexfile.seekg(0x000C, ios::beg);
mtltexfile.read(texDim, 0x0004);
}
else
{
cout << "Unable to open " << mtlName << ".tga" << endl;
}
mtltexfile.close();
return texDim;
}
void mapToObj (string filename, int numblocks, int dimension)
{
ifstream mapfile (filename + ".map", ios::in|ios::binary); //Input file
ofstream objfile (filename + ".obj", ios::out); //Output file
int totalVertices = 0;
int totalVTs = 0;
if (mapfile.is_open() && objfile.is_open())
{
objfile << "mtllib tgalib.mtl" << endl;
int * meshloc = new int;
int * lzssize = new int;
for (int i = 0; i < numblocks; i++) //There are 69 Blocks in wm0.map, ignores last 6 blocks (see below)
{
//Calculate north-south block (nsblock) and east-west (ewblock) offsets of block (7x9 grid of blocks = 63)
//Block size is 8192*4 = 32768x32768 coordinate units
//Note that blocks 63, 64, 65, 66, 67 and 68 replace blocks 50, 41, 42, 60, 47 and 48 (respectively), according to the story of the game.
int ewblock = i % dimension; //take mod to get 0-8
int nsblock = floor ((float)(i / dimension)); //take floor to get 0-6
//cout << i << " " << ewblock << " " << nsblock << endl;
for (int j = 0; j < 16; j++) //There are 16 meshes in each block
{
//Calculate north-south (ns) and east-west (ew) offsets of mesh (4x4 grid of meshes = 16)
//Mesh size is 8192x8192 coordinate units
int ew = j % 4; //take mod to get 0-3
int ns = floor ((float)(j / 4)); //take floor to get 0-3
int pos = i * 0xB800 + j * 0x0004;
mapfile.seekg (pos, ios::beg); //Block i, Mesh j
mapfile.read ((char *) meshloc, 0x0004); //Read location of mesh (pointer is 4 bytes)
pos = i * 0xB800 + *meshloc;
mapfile.seekg (pos, ios::beg); //Mesh LZS starts here
mapfile.read ((char *) lzssize, 0x0004); //Size is 1st 4 bytes
mapfile.seekg (-0x0004, ios::cur); //Go back 4 bytes because .lzs file includes size
char * memblock = new char [*lzssize + 0x0004];
mapfile.read (memblock, *lzssize + 0x0004); //Read the compressed mesh to memory (don't forget to read the size header)
ofstream lzsfile ("mesh.lzs", ios::out|ios::binary); //Write compressed mesh to mesh.lzs file
if (lzsfile.is_open())
{
lzsfile.write (memblock, *lzssize + 0x0004);
}
lzsfile.close();
//system("lzs.exe -d -q mesh.lzs"); //Decompress mesh with Ficedula's LZS decompressor
system("lzscdec.exe mesh.lzs D mesh.dec"); //Decompress mesh with NFITC1's LZS decompressor
remove ("mesh.lzs");
fstream::pos_type meshsize; //Copy data from mesh.dec to memory
ifstream meshfile ("mesh.dec", ios::in|ios::binary|ios::ate);
if (meshfile.is_open())
{
meshsize = meshfile.tellg();
delete[] memblock;
memblock = new char [meshsize];
meshfile.seekg (0, ios::beg);
meshfile.read (memblock, meshsize); //Read size header? Just need #triangles & #vertices
}
meshfile.close();
remove ("mesh.dec");
//Here we add the mesh in memblock to the .obj file
//May need to add offsets for block # because map is 9x7 grid of 63 blocks.
unsigned char * mem_ptr;
unsigned short numTriangles, numVertices;
mem_ptr = (unsigned char *)(memblock);
numTriangles = *mem_ptr;
numTriangles += *(mem_ptr + 0x0001) << 8;
mem_ptr = (unsigned char *)(memblock + 0x0002);
numVertices = *mem_ptr;
numVertices += *(mem_ptr + 0x0001) << 8;
//cout << numTriangles << " " << numVertices << endl;
for (int f = 0; f < numTriangles; f++) //Triangle (face) data. Each triangle consists of 12 bytes. The first 3 are vertex indices.
{
pos = (int) (memblock + 0x0004 + 0x000C * f);
mem_ptr = (unsigned char *)(pos);
unsigned char v0 = *mem_ptr;
mem_ptr += 0x0001;
unsigned char v1 = *mem_ptr;
mem_ptr += 0x0001;
unsigned char v2 = *mem_ptr;
mem_ptr += 0x0002;
unsigned char u0 = *mem_ptr;
mem_ptr += 0x0001;
unsigned char vt0 = *mem_ptr;
mem_ptr += 0x0001;
unsigned char u1 = *mem_ptr;
mem_ptr += 0x0001;
unsigned char vt1 = *mem_ptr;
mem_ptr += 0x0001;
unsigned char u2 = *mem_ptr;
mem_ptr += 0x0001;
unsigned char vt2 = *mem_ptr;
mem_ptr += 0x0001;
unsigned short mtlIndex = *mem_ptr; //Texture is 2 bytes read lower then upper, but only lower 9 bits
mtlIndex += *(mem_ptr + 0x0001) << 8; //For that reason we throw out the top 7 bits
//Debug
//char upper7 = mtlIndex >> 9; //Upper 7 bits (unknown)
//Debug
mtlIndex = mtlIndex << 7;
mtlIndex = mtlIndex >> 7;
mtlIndex++;
//cout << getMtl (mtlIndex) << endl; //Material name
string mtlName = getMtl(mtlIndex);
objfile << "usemtl " << mtlName << endl; //Use texture from tgalib.mtl
objfile << "f " << (int) v0 + totalVertices + 1 << "/" << totalVTs + 1 << "/" << (int) v0 + totalVertices + 1 << " "; //face line format:
objfile << (int) v1 + totalVertices + 1 << "/" << totalVTs + 2 << "/" << (int) v1 + totalVertices + 1 << " "; //f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3
objfile << (int) v2 + totalVertices + 1 << "/" << totalVTs + 3 << "/" << (int) v2 + totalVertices + 1 << endl; //Vertex and Vertex/Normal values are same
//Easier to write the UVs here since they are considered part of a triangle (face) in the .map file. Obj file doesn't care where they are, just their order
//Get texture dimensions given mtlName
char * texDim = new char[4];
texDim = getTexDims (mtlName);
short texWidth = (texDim[1] << 8) + texDim[0];
if (texWidth < 0) texWidth *= -1;
short texHeight = (texDim[3] << 8) + texDim[2];
if (texHeight < 0) texHeight *= -1;
//cout << texWidth << " " << texHeight << endl;
objfile << "vt " << (float)((float) u0 / texWidth) << " " << (float)((float) vt0 * -1 / texHeight) << endl; //Note that V's are flipped
objfile << "vt " << (float)((float) u1 / texWidth) << " " << (float)((float) vt1 * -1 / texHeight) << endl; //All UVs must be divided by texheight/width for normalization
objfile << "vt " << (float)((float) u2 / texWidth) << " " << (float)((float) vt2 * -1 / texHeight) << endl;
totalVTs += 3;
}
for (int v = 0; v < numVertices; v++) //Vertex data. Each vertex consists of 8 bytes. The first 6 are coordinates (2 each).
{
pos = (int) (memblock + 0x0004 + 0x000C * numTriangles + 0x0008 * v);
mem_ptr = (unsigned char *)(pos);
short x = *mem_ptr;
x += *(mem_ptr + 0x0001) << 8;
mem_ptr += 0x0002;
short y = *mem_ptr;
y += *(mem_ptr + 0x0001) << 8;
mem_ptr += 0x0002;
short z = *mem_ptr;
z += *(mem_ptr + 0x0001) << 8;
objfile << "v " << (int) x + ewblock * 32768 + ew * 8192 << " " << (int) y << " " << (int) z + nsblock * 32768 + ns * 8192 << endl;
}
for (int vn = 0; vn < numVertices; vn++) //Vertex/Normal data. Each vertex/normal consists of 8 bytes. The first 6 are coordinates (2 each).
{
pos = (int) (memblock + 0x0004 + 0x000C * numTriangles + 0x0008 * numVertices + 0x0008 * vn);
mem_ptr = (unsigned char *)(pos);
short x = *mem_ptr;
x += *(mem_ptr + 0x0001) << 8;
mem_ptr += 0x0002;
short y = *mem_ptr;
y += *(mem_ptr + 0x0001) << 8;
mem_ptr += 0x0002;
short z = *mem_ptr;
z += *(mem_ptr + 0x0001) << 8;
objfile << "vn " << (int) x + ewblock * 32768 + ew * 8192 << " " << (int) y << " " << (int) z + nsblock * 32768 + ns * 8192 << endl;
}
totalVertices += numVertices;
//Mesh added to .obj file.
delete[] memblock;
//Report progress
system("cls");
cout << "ff7MapToObj by Omzy" << endl << endl;
cout << "Converting " << filename << ".map..." << endl;
cout << "Progress: Block: " << i + 1 << "/" << numblocks << " Mesh: " << j + 1 << "/16" << endl;
}
}
delete[] meshloc;
delete[] lzssize;
}
else cout << "Unable to open" << filename << ".map" << endl;
mapfile.close();
objfile.close();
return;
}
int main () {
mapToObj ("wm0", 63, 9);
mapToObj ("wm2", 12, 3);
mapToObj ("wm3", 4, 2);
system("Pause");
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment