mirror of https://github.com/n64decomp/mk64.git
252 lines
11 KiB
C++
252 lines
11 KiB
C++
#include <fstream>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "libmio0.h"
|
|
|
|
typedef struct {
|
|
uint32_t model_rom_offset;
|
|
uint32_t packed_dl_rom_offset;
|
|
std::string course_name;
|
|
} model_location;
|
|
|
|
std::vector<model_location> all_models = {
|
|
{ 0x88fa10, 0x899104, "mario_raceway" },
|
|
{ 0x89b510, 0x8A55C4, "choco_mountain" },
|
|
{ 0x8a7640, 0x8B59A8, "bowsers_castle" },
|
|
{ 0x8b9630, 0x8BFF18, "banshee_boardwalk" },
|
|
{ 0x8c2510, 0x8CA2A0, "yoshi_valley" },
|
|
{ 0x8cc900, 0x8D6624, "frappe_snowland" },
|
|
{ 0x8d8e50, 0x8E8BC8, "koopa_troopa_beach" },
|
|
{ 0x8ec390, 0x8FAFF0, "royal_raceway" },
|
|
{ 0x8fe640, 0x907E40, "luigi_raceway" },
|
|
{ 0x90b3e0, 0x918ECC, "moo_moo_farm" },
|
|
{ 0x91b980, 0x925F50, "toads_turnpike" },
|
|
{ 0x928c70, 0x934004, "kalimari_desert" },
|
|
{ 0x936fd0, 0x93B9C8, "sherbet_land" },
|
|
{ 0x93cc60, 0x9426BC, "rainbow_road" },
|
|
{ 0x9438c0, 0x94E28C, "wario_stadium" },
|
|
{ 0x951780, 0x953058, "block_fort" },
|
|
{ 0x953890, 0x954F08, "skyscraper" },
|
|
{ 0x955620, 0x9562F4, "double_deck" },
|
|
{ 0x956670, 0x960ACC, "dks_jungle_parkway" },
|
|
{ 0x963ef0, 0x965A74, "big_donut" },
|
|
};
|
|
|
|
typedef struct {
|
|
short ob[3]; /* x, y, z */
|
|
short tc[2]; /* texture coord */
|
|
char n[3]; /* color & alpha */
|
|
} mk64_Vtx_n;
|
|
|
|
std::ifstream & operator >> (std::ifstream &in, mk64_Vtx_n &vtx) {
|
|
// This is highly dependent on the `mk64_Vtx_n` type having no padding between its elements
|
|
// And that is the case, but its worth noting that its kind of brittle
|
|
in.read(reinterpret_cast<char*>(&vtx), sizeof(mk64_Vtx_n));
|
|
return in;
|
|
}
|
|
|
|
inline short bswap(short in) {
|
|
return ((in & 0xFF) << 8) | ((in >> 8) & 0xFF);
|
|
}
|
|
|
|
// argv[1] -> path to baserom file
|
|
int main(int argc, char **argv) {
|
|
int err;
|
|
mk64_Vtx_n vtx;
|
|
std::stringstream vtx_string;
|
|
std::stringstream vtx_tc_string;
|
|
std::stringstream vtx_norm_string;
|
|
std::stringstream model_string;
|
|
std::ofstream vtx_obj;
|
|
std::ofstream model_inc;
|
|
std::ifstream model_bytes;
|
|
|
|
std::string vtx_obj_path;
|
|
std::string model_inc_path;
|
|
// There HAS to be a better way to do this
|
|
std::string uncompressed_model = "./temp.model.bin";
|
|
|
|
for (auto model : all_models) {
|
|
vtx_obj_path = "./courses/" + model.course_name + "/course.obj";
|
|
model_inc_path = "./courses/" + model.course_name + "/course.vtx";
|
|
|
|
err = mio0_decode_file(argv[1], model.model_rom_offset, uncompressed_model.c_str());
|
|
|
|
if (err != 0) {
|
|
std::cout << "Something wrong occurred when decoding " << model.course_name << "'s model" << std::endl;
|
|
continue;
|
|
}
|
|
|
|
// Should probably confirm that the file open actually worked
|
|
model_bytes.open(uncompressed_model, std::ios::in | std::ios::binary);
|
|
while(model_bytes >> vtx) {
|
|
// I hate all forms of string manipulation in C/++
|
|
// If we could use gpp 13+ we could use the `fmt` feature to make this cleaner
|
|
// I also don't know if the bswap'ing is necessary everywhere or just on my machine
|
|
model_string << "{" \
|
|
<< "{ " << std::to_string(bswap(vtx.ob[0])) << ", " << std::to_string(bswap(vtx.ob[1])) << ", " << std::to_string(bswap(vtx.ob[2])) << " }, " \
|
|
<< "{ " << std::to_string(bswap(vtx.tc[0])) << ", " << std::to_string(bswap(vtx.tc[1])) << " }, " \
|
|
<< "{ " << std::to_string(vtx.n[0]) << ", " << std::to_string(vtx.n[1]) << ", " << std::to_string(vtx.n[2]) << " }" \
|
|
<< "}," << std::endl;
|
|
vtx_string << "v " \
|
|
<< std::to_string(bswap(vtx.ob[0])) << " " << std::to_string(bswap(vtx.ob[1])) << " " << std::to_string(bswap(vtx.ob[2])) \
|
|
<< std::endl;
|
|
// Further processing will be required to get these numbers correct
|
|
// vt needs to be converted to a floating point value between 0 and 1
|
|
// I'm not sure how to do that in this case
|
|
//vtx_tc_string << "vt " << std::to_string(bswap(vtx.tc[0])) << " " << std::to_string(bswap(vtx.tc[1])) << std::endl;
|
|
// Using signed chars to ensure the normals are signed before being divided
|
|
char n1 = vtx.n[0] & 0xFC;
|
|
char n2 = vtx.n[1] & 0xFC;
|
|
char n3 = vtx.n[2];
|
|
vtx_norm_string << "vn " \
|
|
<< std::to_string(n1 / 128.0f) << " " \
|
|
<< std::to_string(n2 / 128.0f) << " " \
|
|
<< std::to_string(n3 / 128.0f) \
|
|
<< std::endl;
|
|
}
|
|
model_bytes.close();
|
|
|
|
vtx_obj.open(vtx_obj_path, std::ios::out | std::ios::trunc);
|
|
vtx_obj << vtx_string.rdbuf() /*<< vtx_tc_string.rdbuf()*/ << vtx_norm_string.rdbuf();
|
|
vtx_obj.close();
|
|
|
|
model_inc.open(model_inc_path, std::ios::out | std::ios::trunc);
|
|
model_inc << model_string.rdbuf();
|
|
model_inc.close();
|
|
|
|
}
|
|
|
|
int num_tris;
|
|
uint8_t opcode;
|
|
uint16_t data_short;
|
|
short vtx_index;
|
|
short v1, v2, v3;
|
|
std::stringstream face_string;
|
|
int object_num = 0;
|
|
int face_group_num = 0;
|
|
|
|
for (auto model : all_models) {
|
|
vtx_obj_path = "./courses/" + model.course_name + "/course.obj";
|
|
|
|
// Should probably confirm that the file open actually worked
|
|
model_bytes.open(argv[1], std::ios::in | std::ios::binary);
|
|
model_bytes.seekg(model.packed_dl_rom_offset);
|
|
face_string << "o object" << std::to_string(object_num++) << std::endl;
|
|
while(model_bytes.read(reinterpret_cast<char*>(&opcode), 1)) {
|
|
if (opcode == 0xFF) {
|
|
break;
|
|
}
|
|
|
|
/**
|
|
* For unpack_vtx1/2, we are ignoring the fact that the v0 argument
|
|
* in gsSPVertex macro could be something other than 0.
|
|
* In our case it is always 0, but it is an assumption on our part.
|
|
* vtx_index will be incorrect if that assumption is ever broken.
|
|
**/
|
|
if ((0x33 <= opcode) && (opcode <= 0x52)) { // unpack_vtx2, gsSPVertex
|
|
// 3 bytes including opcode
|
|
model_bytes.read(reinterpret_cast<char*>(&data_short), 2);
|
|
vtx_index = data_short + 1;
|
|
face_string << "g face_group" << std::to_string(face_group_num++) << std::endl;
|
|
} else if (opcode == 0x28) { // unpack_vtx1, gsSPVertex
|
|
// 5 bytes including the opcode
|
|
model_bytes.read(reinterpret_cast<char*>(&data_short), 2);
|
|
vtx_index = data_short + 1;
|
|
// Only need the vertex "address", skip the other 2 bytes
|
|
model_bytes.seekg(2, std::ios_base::cur);
|
|
} else if ((opcode == 0x29) || (opcode == 0x58)) {
|
|
/**
|
|
* 0x29: unpack_triangle, gsSP1Triangle
|
|
* 3 bytes including opcode
|
|
* 0x58: unpack_quadrangle, gsSP2Triangles
|
|
* 5 bytes including opcode
|
|
**/
|
|
if (opcode == 0x29) {
|
|
num_tris = 1;
|
|
} else {
|
|
num_tris = 2;
|
|
}
|
|
// Each 2-byte holding the vertex indices looks like:
|
|
// Byte 2 Byte 1
|
|
// X3333322 22211111
|
|
for (auto tri = 0; tri < num_tris; tri++) {
|
|
model_bytes.read(reinterpret_cast<char*>(&data_short), 2);
|
|
v1 = data_short & 0x1F;
|
|
v2 = (data_short >> 5) & 7;
|
|
v2 |= ((data_short >> 8) & 3) << 3;
|
|
v3 = (data_short >> 10) & 0x1F;
|
|
v1 += vtx_index;
|
|
v2 += vtx_index;
|
|
v3 += vtx_index;
|
|
face_string << "f ";
|
|
// v index / vt index / vn index
|
|
face_string << std::to_string(v1) << "/" /*<< std::to_string(v1)*/ << "/" << std::to_string(v1) << " ";
|
|
face_string << std::to_string(v2) << "/" /*<< std::to_string(v2)*/ << "/" << std::to_string(v2) << " ";
|
|
face_string << std::to_string(v3) << "/" /*<< std::to_string(v3)*/ << "/" << std::to_string(v3) << " ";
|
|
face_string << std::endl;
|
|
}
|
|
} else if (
|
|
((0x00 <= opcode) && (opcode <= 0x19)) ||
|
|
(opcode == 0x26) || (opcode == 0x27) ||
|
|
(opcode == 0x2A) ||
|
|
((0x2D <= opcode) && (opcode <= 0x2F)) ||
|
|
((0x53 <= opcode) && (opcode <= 0x57))) {
|
|
/**
|
|
* 0x00 - 0x14: unpack_lights, gsSPNumLights
|
|
* 0x15: unpack_combine_mode1,
|
|
* 0x16: unpack_combine_mode2,
|
|
* 0x17: unpack_combine_mode_shade,
|
|
* 0x18: unpack_render_mode_opaque,
|
|
* 0x19: unpack_render_mode_tex_edge,
|
|
* 0x26: unpack_texture_on,
|
|
* 0x27: unpack_texture_off,
|
|
* 0x2A: unpack_end_displaylist,
|
|
* 0x2D: unpack_cull_displaylist,
|
|
* 0x2E: unpack_combine_mode4,
|
|
* 0x2F: unpack_render_mode_translucent,
|
|
* 0x53: unpack_combine_mode5,
|
|
* 0x54: unpack_render_mode_opaque_decal,
|
|
* 0x55: unpack_render_mode_translucent_decal,
|
|
* 0x56: unpack_set_geometry_mode,
|
|
* 0x57: unpack_clear_geometry_mode,
|
|
**/
|
|
// Only 1 byte, the opcode
|
|
if (opcode == 0x2A) {
|
|
face_string << "o object" << std::to_string(object_num++) << std::endl;
|
|
}
|
|
} else if (
|
|
((0x1A <= opcode) && (opcode <= 0x1F)) ||
|
|
(opcode == 0x2B) || (opcode == 0x2C)) {
|
|
/**
|
|
* 0x1A - 0x1F, 0x2C: unpack_tile_sync,
|
|
* 0x2B: unpack_displaylist,
|
|
**/
|
|
// 3 bytes including the opcode
|
|
model_bytes.seekg(2, std::ios_base::cur);
|
|
} else if (
|
|
((0x20 <= opcode) && (opcode <= 0x25)) ||
|
|
(opcode == 0x30)) {
|
|
/**
|
|
* 0x20 - 0x25: unpack_tile_load_sync,
|
|
* 0x30: unpack_spline_3D,
|
|
**/
|
|
// 4 bytes including the opcode
|
|
model_bytes.seekg(3, std::ios_base::cur);
|
|
} else {
|
|
std::cout << "WARNING UNEXPECTED opcode: " << std::to_string(opcode) << std::endl;
|
|
break;
|
|
}
|
|
}
|
|
model_bytes.close();
|
|
|
|
vtx_obj.open(vtx_obj_path, std::ios::out | std::ios::app);
|
|
vtx_obj << face_string.rdbuf();
|
|
vtx_obj.close();
|
|
}
|
|
return 0;
|
|
}
|