Created
August 3, 2025 21:10
-
-
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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