first commit for versioning
This commit is contained in:
1833
src/glad.c
Normal file
1833
src/glad.c
Normal file
File diff suppressed because it is too large
Load Diff
123
src/logger/gl_log.c
Normal file
123
src/logger/gl_log.c
Normal file
@@ -0,0 +1,123 @@
|
||||
//
|
||||
// Created by Tristan on 11/26/2025.
|
||||
//
|
||||
|
||||
#include <time.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include "gl_log.h"
|
||||
|
||||
#include "glad/glad.h"
|
||||
#define GL_LOG_FILE "gl.log"
|
||||
|
||||
bool restart_gl_log() {
|
||||
FILE* file = fopen(GL_LOG_FILE, "w");
|
||||
if (!file) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"ERROR: could not open GL_LOG_FILE log file %s for writing\n",
|
||||
GL_LOG_FILE);
|
||||
return false;
|
||||
};
|
||||
|
||||
time_t now = time(NULL);
|
||||
char *date = ctime(&now);
|
||||
fprintf(file, "GL_LOG_FILE log. local time %s\n", date);
|
||||
fclose(file);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool gl_log(const char *message, ...) {
|
||||
va_list argptr;
|
||||
FILE* file = fopen(GL_LOG_FILE, "a");
|
||||
if (!file) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"ERROR: could not open GL_LOG_FILE %s file for appending\n",
|
||||
GL_LOG_FILE);
|
||||
return false;
|
||||
};
|
||||
va_start(argptr, message);
|
||||
vfprintf(file, message, argptr);
|
||||
va_end(argptr);
|
||||
fclose(file);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool gl_log_err(const char *message, ...) {
|
||||
va_list argptr;
|
||||
FILE* file = fopen(GL_LOG_FILE, "a");
|
||||
if (!file) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"ERROR: could not open GL_LOG_FILE %s file for appending\n",
|
||||
GL_LOG_FILE);
|
||||
return false;
|
||||
}
|
||||
va_start(argptr, message);
|
||||
vfprintf(file, message, argptr);
|
||||
va_end(argptr);
|
||||
va_start(argptr, message);
|
||||
vfprintf(stderr, message, argptr);
|
||||
va_end(argptr);
|
||||
fclose(file);
|
||||
return true;
|
||||
}
|
||||
|
||||
void log_gl_params() {
|
||||
GLenum params[] = {
|
||||
GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,
|
||||
GL_MAX_CUBE_MAP_TEXTURE_SIZE,
|
||||
GL_MAX_DRAW_BUFFERS,
|
||||
GL_MAX_FRAGMENT_UNIFORM_COMPONENTS,
|
||||
GL_MAX_TEXTURE_IMAGE_UNITS,
|
||||
GL_MAX_TEXTURE_SIZE,
|
||||
GL_MAX_VARYING_FLOATS,
|
||||
GL_MAX_VERTEX_ATTRIBS,
|
||||
GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS,
|
||||
GL_MAX_VERTEX_UNIFORM_COMPONENTS,
|
||||
GL_MAX_VIEWPORT_DIMS,
|
||||
GL_STEREO,
|
||||
GL_MAX_SAMPLES,
|
||||
GL_SAMPLES,
|
||||
};
|
||||
const char* names[] = {
|
||||
"GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS",
|
||||
"GL_MAX_CUBE_MAP_TEXTURE_SIZE",
|
||||
"GL_MAX_DRAW_BUFFERS",
|
||||
"GL_MAX_FRAGMENT_UNIFORM_COMPONENTS",
|
||||
"GL_MAX_TEXTURE_IMAGE_UNITS",
|
||||
"GL_MAX_TEXTURE_SIZE",
|
||||
"GL_MAX_VARYING_FLOATS",
|
||||
"GL_MAX_VERTEX_ATTRIBS",
|
||||
"GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS",
|
||||
"GL_MAX_VERTEX_UNIFORM_COMPONENTS",
|
||||
"GL_MAX_VIEWPORT_DIMS",
|
||||
"GL_STEREO",
|
||||
"GL_MAX_SAMPLES",
|
||||
"GL_SAMPLES",
|
||||
};
|
||||
|
||||
gl_log("GL Context Params:\n");
|
||||
// integers - only works if the order is 0-10 integer return types
|
||||
for (int i = 0; i < 10; i++) {
|
||||
int v = 0;
|
||||
glGetIntegerv (params[i], &v);
|
||||
gl_log("%s %i\n", names[i], v);
|
||||
}
|
||||
|
||||
// others
|
||||
int v[2];
|
||||
v[0] = v[1] = 0;
|
||||
glGetIntegerv(params[10], v);
|
||||
gl_log("%s %i %i\n", names[10], v[0], v[1]);
|
||||
glGetIntegerv(params[12], v);
|
||||
gl_log("%s %i %i\n", names[12], v[0], v[1]);
|
||||
glGetIntegerv(params[13], v);
|
||||
gl_log("%s %i\n", names[13], v[0]);
|
||||
unsigned char s = 0;
|
||||
glGetBooleanv(params[11], &s);
|
||||
gl_log("%s %u\n", names[11], (unsigned int)s);
|
||||
gl_log("-----------------------------\n");
|
||||
}
|
||||
12
src/logger/gl_log.h
Normal file
12
src/logger/gl_log.h
Normal file
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// Created by Tristan on 11/26/2025.
|
||||
//
|
||||
|
||||
#ifndef GL_LOG_H
|
||||
#define GL_LOG_H
|
||||
|
||||
bool restart_gl_log();
|
||||
bool gl_log(const char *message, ...);
|
||||
bool gl_log_err(const char *message, ...);
|
||||
void log_gl_params();
|
||||
#endif //GL_LOG_H
|
||||
168
src/logger/log.c
Normal file
168
src/logger/log.c
Normal file
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright (c) 2020 rxi
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "log.h"
|
||||
|
||||
#define MAX_CALLBACKS 32
|
||||
|
||||
typedef struct {
|
||||
log_LogFn fn;
|
||||
void *udata;
|
||||
int level;
|
||||
} Callback;
|
||||
|
||||
static struct {
|
||||
void *udata;
|
||||
log_LockFn lock;
|
||||
int level;
|
||||
bool quiet;
|
||||
Callback callbacks[MAX_CALLBACKS];
|
||||
} L;
|
||||
|
||||
|
||||
static const char *level_strings[] = {
|
||||
"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"
|
||||
};
|
||||
|
||||
#ifdef LOG_USE_COLOR
|
||||
static const char *level_colors[] = {
|
||||
"\x1b[94m", "\x1b[36m", "\x1b[32m", "\x1b[33m", "\x1b[31m", "\x1b[35m"
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
static void stdout_callback(log_Event *ev) {
|
||||
char buf[16];
|
||||
buf[strftime(buf, sizeof(buf), "%H:%M:%S", ev->time)] = '\0';
|
||||
#ifdef LOG_USE_COLOR
|
||||
fprintf(
|
||||
ev->udata, "%s %s%-5s\x1b[0m \x1b[90m%s:%d:\x1b[0m ",
|
||||
buf, level_colors[ev->level], level_strings[ev->level],
|
||||
ev->file, ev->line);
|
||||
#else
|
||||
fprintf(
|
||||
ev->udata, "%s %-5s %s:%d: ",
|
||||
buf, level_strings[ev->level], ev->file, ev->line);
|
||||
#endif
|
||||
vfprintf(ev->udata, ev->fmt, ev->ap);
|
||||
fprintf(ev->udata, "\n");
|
||||
fflush(ev->udata);
|
||||
}
|
||||
|
||||
|
||||
static void file_callback(log_Event *ev) {
|
||||
char buf[64];
|
||||
buf[strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", ev->time)] = '\0';
|
||||
fprintf(
|
||||
ev->udata, "%s %-5s %s:%d: ",
|
||||
buf, level_strings[ev->level], ev->file, ev->line);
|
||||
vfprintf(ev->udata, ev->fmt, ev->ap);
|
||||
fprintf(ev->udata, "\n");
|
||||
fflush(ev->udata);
|
||||
}
|
||||
|
||||
|
||||
static void lock(void) {
|
||||
if (L.lock) { L.lock(true, L.udata); }
|
||||
}
|
||||
|
||||
|
||||
static void unlock(void) {
|
||||
if (L.lock) { L.lock(false, L.udata); }
|
||||
}
|
||||
|
||||
|
||||
const char* log_level_string(int level) {
|
||||
return level_strings[level];
|
||||
}
|
||||
|
||||
|
||||
void log_set_lock(log_LockFn fn, void *udata) {
|
||||
L.lock = fn;
|
||||
L.udata = udata;
|
||||
}
|
||||
|
||||
|
||||
void log_set_level(int level) {
|
||||
L.level = level;
|
||||
}
|
||||
|
||||
|
||||
void log_set_quiet(bool enable) {
|
||||
L.quiet = enable;
|
||||
}
|
||||
|
||||
|
||||
int log_add_callback(log_LogFn fn, void *udata, int level) {
|
||||
for (int i = 0; i < MAX_CALLBACKS; i++) {
|
||||
if (!L.callbacks[i].fn) {
|
||||
L.callbacks[i] = (Callback) { fn, udata, level };
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int log_add_fp(FILE *fp, int level) {
|
||||
return log_add_callback(file_callback, fp, level);
|
||||
}
|
||||
|
||||
|
||||
static void init_event(log_Event *ev, void *udata) {
|
||||
if (!ev->time) {
|
||||
time_t t = time(NULL);
|
||||
ev->time = localtime(&t);
|
||||
}
|
||||
ev->udata = udata;
|
||||
}
|
||||
|
||||
|
||||
void log_log(int level, const char *file, int line, const char *fmt, ...) {
|
||||
log_Event ev = {
|
||||
.fmt = fmt,
|
||||
.file = file,
|
||||
.line = line,
|
||||
.level = level,
|
||||
};
|
||||
|
||||
lock();
|
||||
|
||||
if (!L.quiet && level >= L.level) {
|
||||
init_event(&ev, stderr);
|
||||
va_start(ev.ap, fmt);
|
||||
stdout_callback(&ev);
|
||||
va_end(ev.ap);
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_CALLBACKS && L.callbacks[i].fn; i++) {
|
||||
Callback *cb = &L.callbacks[i];
|
||||
if (level >= cb->level) {
|
||||
init_event(&ev, cb->udata);
|
||||
va_start(ev.ap, fmt);
|
||||
cb->fn(&ev);
|
||||
va_end(ev.ap);
|
||||
}
|
||||
}
|
||||
|
||||
unlock();
|
||||
}
|
||||
49
src/logger/log.h
Normal file
49
src/logger/log.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2020 rxi
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the MIT license. See `log.c` for details.
|
||||
*/
|
||||
|
||||
#ifndef LOG_H
|
||||
#define LOG_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <time.h>
|
||||
|
||||
#define LOG_VERSION "0.1.0"
|
||||
|
||||
typedef struct {
|
||||
va_list ap;
|
||||
const char *fmt;
|
||||
const char *file;
|
||||
struct tm *time;
|
||||
void *udata;
|
||||
int line;
|
||||
int level;
|
||||
} log_Event;
|
||||
|
||||
typedef void (*log_LogFn)(log_Event *ev);
|
||||
typedef void (*log_LockFn)(bool lock, void *udata);
|
||||
|
||||
enum { LOG_TRACE, LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR, LOG_FATAL };
|
||||
|
||||
#define log_trace(...) log_log(LOG_TRACE, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define log_debug(...) log_log(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define log_info(...) log_log(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define log_warn(...) log_log(LOG_WARN, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define log_error(...) log_log(LOG_ERROR, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define log_fatal(...) log_log(LOG_FATAL, __FILE__, __LINE__, __VA_ARGS__)
|
||||
|
||||
const char* log_level_string(int level);
|
||||
void log_set_lock(log_LockFn fn, void *udata);
|
||||
void log_set_level(int level);
|
||||
void log_set_quiet(bool enable);
|
||||
int log_add_callback(log_LogFn fn, void *udata, int level);
|
||||
int log_add_fp(FILE *fp, int level);
|
||||
|
||||
void log_log(int level, const char *file, int line, const char *fmt, ...);
|
||||
|
||||
#endif
|
||||
1051
src/main.c
Normal file
1051
src/main.c
Normal file
File diff suppressed because it is too large
Load Diff
5
src/mpq/mpq.c
Normal file
5
src/mpq/mpq.c
Normal file
@@ -0,0 +1,5 @@
|
||||
//
|
||||
// Created by Tristan on 11/12/2025.
|
||||
//
|
||||
|
||||
#include "mpq.h"
|
||||
49
src/mpq/mpq.h
Normal file
49
src/mpq/mpq.h
Normal file
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// Created by Tristan on 11/12/2025.
|
||||
//
|
||||
|
||||
#ifndef MPQ_H
|
||||
#define MPQ_H
|
||||
|
||||
#include <StormLib.h>
|
||||
|
||||
/*
|
||||
typedef struct {
|
||||
HANDLE backup_enUS_MPQ;
|
||||
HANDLE base_enUS_MPQ;
|
||||
HANDLE expansion_locale_enUS_MPQ;
|
||||
HANDLE expansion_speech_enUS_MPQ;
|
||||
HANDLE lichking_locale_enUS_MPQ;
|
||||
HANDLE lichking_speech_enUS_MPQ;
|
||||
HANDLE locale_enUS_MPQ;
|
||||
HANDLE patch_enUS_2_MPQ;
|
||||
HANDLE patch_enUS_3_MPQ;
|
||||
HANDLE speech_enUS_MPQ;
|
||||
} LocaleArchives;
|
||||
|
||||
typedef struct {
|
||||
HANDLE common_MPQ;
|
||||
HANDLE common_2_MPQ;
|
||||
HANDLE expansion_MPQ;
|
||||
HANDLE lichking_MPQ;
|
||||
HANDLE patch_MPQ;
|
||||
HANDLE patch_2_MPQ;
|
||||
HANDLE patch_3_MPQ;
|
||||
HANDLE patch_4_MPQ;
|
||||
LocaleArchives;
|
||||
} ArchiveManager; */
|
||||
|
||||
typedef struct {
|
||||
HANDLE *archives; // array of handles pointing to archives
|
||||
size_t count; // num of handles
|
||||
size_t capacity; // size of array
|
||||
|
||||
char root_path[MAX_PATH]; // might need this for reconstruction of filepaths
|
||||
} ArchiveManager;
|
||||
|
||||
typedef struct {
|
||||
char path[MAX_PATH];
|
||||
int load_order_score;
|
||||
} ArchiveEntry;
|
||||
|
||||
#endif //MPQ_H
|
||||
216
src/renderer/matrix.c
Normal file
216
src/renderer/matrix.c
Normal file
@@ -0,0 +1,216 @@
|
||||
//
|
||||
// Created by Tristan on 12/10/2025.
|
||||
//
|
||||
|
||||
#include "matrix.h"
|
||||
#include <math.h>
|
||||
|
||||
mat4_t mat4_identity(void) {
|
||||
mat4_t m = {
|
||||
{
|
||||
1.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 1.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 1.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f
|
||||
}
|
||||
};
|
||||
return m;
|
||||
}
|
||||
|
||||
mat4_t mat4_make_scale(GLfloat sx, GLfloat sy, GLfloat sz) {
|
||||
mat4_t m = mat4_identity();
|
||||
m.m[0] = sx;
|
||||
m.m[5] = sy;
|
||||
m.m[10] = sz;
|
||||
return m;
|
||||
};
|
||||
|
||||
mat4_t mat4_make_rotation_x(GLfloat angle) {
|
||||
GLfloat c = cos(angle);
|
||||
GLfloat s = sin(angle);
|
||||
mat4_t m = mat4_identity();
|
||||
|
||||
m.m[5] = c;
|
||||
m.m[6] = -s;
|
||||
m.m[9] = s;
|
||||
m.m[10] = c;
|
||||
return m;
|
||||
}
|
||||
|
||||
mat4_t mat4_make_rotation_y(GLfloat angle) {
|
||||
GLfloat c = cos(angle);
|
||||
GLfloat s = sin(angle);
|
||||
mat4_t m = mat4_identity();
|
||||
|
||||
m.m[0] = c;
|
||||
m.m[2] = s;
|
||||
m.m[8] = -s;
|
||||
m.m[10] = c;
|
||||
return m;
|
||||
}
|
||||
|
||||
mat4_t mat4_make_rotation_z(GLfloat angle) {
|
||||
GLfloat c = cos(angle);
|
||||
GLfloat s = sin(angle);
|
||||
mat4_t m = mat4_identity();
|
||||
|
||||
m.m[0] = c;
|
||||
m.m[1] = -s;
|
||||
m.m[4] = s;
|
||||
m.m[5] = c;
|
||||
return m;
|
||||
}
|
||||
|
||||
mat4_t mat4_make_translation(GLfloat tx, GLfloat ty, GLfloat tz) {
|
||||
mat4_t m = mat4_identity();
|
||||
m.m[12] = tx;
|
||||
m.m[13] = ty;
|
||||
m.m[14] = tz;
|
||||
return m;
|
||||
}
|
||||
|
||||
mat4_t mat4_mul_mat4(mat4_t a, mat4_t b) {
|
||||
mat4_t m;
|
||||
for (int j = 0; j < 4; j++) { // cols
|
||||
// m.m[j*4 + i]
|
||||
for (int i = 0; i < 4; i++) { // rows
|
||||
GLfloat sum = 0.0f;
|
||||
for (int k = 0; k < 4; k++) {
|
||||
GLfloat element_a = a.m[k * 4 + i];
|
||||
GLfloat element_b = b.m[j * 4 + k];
|
||||
|
||||
sum += element_a * element_b;
|
||||
}
|
||||
m.m[j * 4 + i] = sum;
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
mat4_t mat4_make_perspective(float fov_rad, float aspect, float znear, float zfar) {
|
||||
/*
|
||||
Sx, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, Sy, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, Sz, -1.0f,
|
||||
0.0f, 0.0f, Pz, 0.0f
|
||||
*/
|
||||
float range = tan(fov_rad * 0.5f) * znear;
|
||||
float Sy = znear / range;
|
||||
float Sx = Sy / aspect;
|
||||
float Sz = -(zfar + znear) / (zfar - znear);
|
||||
float Pz = -(2.0f * zfar * znear) / (zfar - znear);
|
||||
|
||||
mat4_t m = { 0 }; // Initialize all to 0.0f
|
||||
|
||||
// Diagonal scaling
|
||||
m.m[0] = Sx;
|
||||
m.m[5] = Sy;
|
||||
m.m[10] = Sz;
|
||||
|
||||
// perspective divide (w-axis)
|
||||
m.m[11] = -1.0f;
|
||||
|
||||
// depth translation (Pz)
|
||||
m.m[14] = Pz;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
mat4_t mat4_make_quaternion(const GLfloat* q) {
|
||||
mat4_t m;
|
||||
float w = q[0];
|
||||
float x = q[1];
|
||||
float y = q[2];
|
||||
float z = q[3];
|
||||
|
||||
float x2 = x * x;
|
||||
float y2 = y * y;
|
||||
float z2 = z * z;
|
||||
|
||||
// col 1
|
||||
m.m[0] = 1.0f - (2.0f * y2) - (2.0f * z2);
|
||||
m.m[1] = (2.0f * x * y) + (2.0f * w * z);
|
||||
m.m[2] = (2 * x * z) - (2 * w * y);
|
||||
m.m[3] = 0.0f;
|
||||
|
||||
// col 2
|
||||
m.m[4] = (2.0f * x * y) - (2.0f * w * z);
|
||||
m.m[5] = 1.0f - (2 * x2) - (2 * z2);
|
||||
m.m[6] = (2.0f * y * z) + (2.0f * w * x);
|
||||
m.m[7] = 0.0f;
|
||||
|
||||
// col 3
|
||||
m.m[8] = (2.0f * x * z) + (2.0f * w * y);
|
||||
m.m[9] = (2.0f * y * z) - (2.0f * w * x);
|
||||
m.m[10] = 1.0f - (2.0f * x2) - (2.0 * y2);
|
||||
m.m[11] = 0.0f;
|
||||
|
||||
// col 4
|
||||
m.m[12] = 0.0f;
|
||||
m.m[13] = 0.0f;
|
||||
m.m[14] = 0.0f;
|
||||
m.m[15] = 1.0f;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
mat4_t mat4_look_at(float eyex, float eyey, float eyez,
|
||||
float targetx, float targety, float targetz,
|
||||
float upx, float upy, float upz) {
|
||||
// calculate the forward vector (direction from Eye to Target)
|
||||
float forward_x = targetx - eyex;
|
||||
float forward_y = targety - eyey;
|
||||
float forward_z = targetz - eyez;
|
||||
|
||||
// normalize forward vector
|
||||
float reciprocal_length_forward = 1.0f / sqrt(forward_x * forward_x + forward_y * forward_y + forward_z * forward_z);
|
||||
forward_x *= reciprocal_length_forward;
|
||||
forward_y *= reciprocal_length_forward;
|
||||
forward_z *= reciprocal_length_forward;
|
||||
|
||||
// calculate right vector (Forward x Up)
|
||||
// use the "world up" passed in (usually 0, 1, 0) to determine "right"
|
||||
float rightx = forward_z * upy - forward_y * upz;
|
||||
float righty = forward_x * upz - forward_z * upx;
|
||||
float rightz = forward_y * upx - forward_x * upy;
|
||||
|
||||
// normalize right vector
|
||||
float reciprocal_length_right = 1.0f / sqrt(rightx * rightx + righty * righty + rightz * rightz);
|
||||
rightx *= reciprocal_length_right;
|
||||
righty *= reciprocal_length_right;
|
||||
rightz *= reciprocal_length_right;
|
||||
|
||||
// re-calculate Up Vector (Right x Forward)
|
||||
// calculate the "True Up" perpendicular to the new Forward and Right vector
|
||||
upx = righty * forward_z - rightz * forward_y;
|
||||
upy = rightz * forward_x - rightx * forward_z;
|
||||
upz = rightx * forward_y - righty * forward_x;
|
||||
|
||||
// create the matrix
|
||||
mat4_t m = mat4_identity();
|
||||
|
||||
// row 0 (right vector)
|
||||
m.m[0] = rightx;
|
||||
m.m[4] = righty;
|
||||
m.m[8] = rightz;
|
||||
|
||||
// row 1 (up vector)
|
||||
m.m[1] = upx;
|
||||
m.m[5] = upy;
|
||||
m.m[9] = upz;
|
||||
|
||||
// row 2 (forward vector inverted for openGL camera)
|
||||
// invert Forward since we're looking down the negative Z axis with openGL
|
||||
m.m[2] = -forward_x;
|
||||
m.m[6] = -forward_y;
|
||||
m.m[10] = -forward_z;
|
||||
|
||||
// Dot product of rotation and negative eye position
|
||||
m.m[12] = -(rightx * eyex + righty * eyey + rightz * eyez);
|
||||
m.m[13] = -(upx * eyex + upy * eyey + upz * eyez);
|
||||
m.m[14] = -(-forward_x * eyex - forward_y * eyey - forward_z * eyez);
|
||||
|
||||
// row 3 (0, 0, 0, 1)
|
||||
|
||||
return m;
|
||||
}
|
||||
33
src/renderer/matrix.h
Normal file
33
src/renderer/matrix.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef MATRIX_H
|
||||
#define MATRIX_H
|
||||
#include "glad/glad.h"
|
||||
|
||||
#define M_PI 3.14159265358979323846
|
||||
#define ONE_DEG_IN_RAD (2.0 * M_PI) / 360.0 // 0.017444444
|
||||
#ifndef TO_RAD
|
||||
#define TO_RAD(deg) ((deg) * 3.14159265358979323846)
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
GLfloat m[16];
|
||||
} mat4_t;
|
||||
|
||||
typedef struct {
|
||||
float q[4];
|
||||
} versor_t;
|
||||
|
||||
mat4_t mat4_make_rotation_x(GLfloat m);
|
||||
mat4_t mat4_make_rotation_y(GLfloat m);
|
||||
mat4_t mat4_make_rotation_z(GLfloat m);
|
||||
mat4_t mat4_make_scale(GLfloat sx, GLfloat sy, GLfloat sz);
|
||||
mat4_t mat4_make_translation(GLfloat tx, GLfloat ty, GLfloat tz);
|
||||
mat4_t mat4_make_quaternion(const GLfloat* q, GLfloat* m);
|
||||
mat4_t mat4_make_perspective(float fov_rad, float aspect, float znear, float zfar);
|
||||
mat4_t mat4_mul_mat4(mat4_t a, mat4_t b);
|
||||
mat4_t mat4_identity(void);
|
||||
|
||||
mat4_t mat4_look_at(float eyex, float eyey, float eyez,
|
||||
float targetx, float targety, float targetz,
|
||||
float upx, float upy, float upz);
|
||||
|
||||
#endif //MATRIX_H
|
||||
173
src/renderer/mesh.c
Normal file
173
src/renderer/mesh.c
Normal file
@@ -0,0 +1,173 @@
|
||||
//
|
||||
// Created by Tristan on 11/24/2025.
|
||||
//
|
||||
|
||||
#include "mesh.h"
|
||||
|
||||
#include "../logger/log.h"
|
||||
|
||||
GroupMesh create_mesh_from_group(const WMOGroupData* group, const WMORootData* root_data) {
|
||||
GroupMesh mesh = {0};
|
||||
|
||||
if (!group->movt_data_ptr || !group->movi_data_ptr) {
|
||||
log_warn("Group has vertex flag but no MOVT/MOVI data");
|
||||
return mesh;
|
||||
}
|
||||
|
||||
size_t num_vertices = group->movt_size / sizeof(C3Vector);
|
||||
size_t num_indices = group->movi_size / sizeof(uint16_t);
|
||||
|
||||
Vertex* vertices = (Vertex*)malloc(num_vertices * sizeof(Vertex));
|
||||
if (!vertices) {
|
||||
log_error("Failed to allocate memory for mesh generation");
|
||||
return mesh;
|
||||
}
|
||||
|
||||
const C3Vector* positions = (const C3Vector*)group->movt_data_ptr;
|
||||
const C3Vector* normals = (const C3Vector*)group->monr_data_ptr;
|
||||
const C2Vector* texCoords = (const C2Vector*)group->motv_data_ptr;
|
||||
|
||||
GLfloat offset_x = group->header.boundingBox.min[0];
|
||||
GLfloat offset_y = group->header.boundingBox.min[1];
|
||||
GLfloat offset_z = group->header.boundingBox.min[2];
|
||||
|
||||
for (size_t i = 0; i < num_vertices; i++) {
|
||||
float wx = positions[i].x + offset_x;
|
||||
float wy = positions[i].y + offset_y;
|
||||
float wz = positions[i].z + offset_z;
|
||||
|
||||
vertices[i].position.x = wx;
|
||||
vertices[i].position.y = wy;
|
||||
vertices[i].position.z = wz;
|
||||
|
||||
if (normals) {
|
||||
vertices[i].normal.x = normals[i].x;
|
||||
vertices[i].normal.y = normals[i].y;
|
||||
vertices[i].normal.z = normals[i].z;
|
||||
} else {
|
||||
vertices[i].normal = (C3Vector){0.0f, 1.0f, 0.0f};
|
||||
}
|
||||
|
||||
if (texCoords) {
|
||||
vertices[i].texCoord.x = texCoords[i].x;
|
||||
vertices[i].texCoord.y = texCoords[i].y;
|
||||
} else {
|
||||
vertices[i].texCoord = (C2Vector){0.0f, 0.0f};
|
||||
}
|
||||
}
|
||||
|
||||
if (num_vertices > 0) {
|
||||
float minX = vertices[0].position.x, maxX = vertices[0].position.x;
|
||||
float minY = vertices[0].position.y, maxY = vertices[0].position.y;
|
||||
float minZ = vertices[0].position.z, maxZ = vertices[0].position.z;
|
||||
|
||||
for (size_t k = 0; k < num_vertices; k++) {
|
||||
if (vertices[k].position.x < minX) minX = vertices[k].position.x;
|
||||
if (vertices[k].position.x > maxX) maxX = vertices[k].position.x;
|
||||
if (vertices[k].position.y < minY) minY = vertices[k].position.y;
|
||||
if (vertices[k].position.y > maxY) maxY = vertices[k].position.y;
|
||||
if (vertices[k].position.z < minZ) minZ = vertices[k].position.z;
|
||||
if (vertices[k].position.z > maxZ) maxZ = vertices[k].position.z;
|
||||
}
|
||||
|
||||
log_info("MESH DEBUG: Group has %zu vertices", num_vertices);
|
||||
// This is the critical line. It tells us where the mesh actually IS.
|
||||
log_info("BOUNDS: X[%.1f to %.1f] Y[%.1f to %.1f] Z[%.1f to %.1f]",
|
||||
minX, maxX, minY, maxY, minZ, maxZ);
|
||||
}
|
||||
|
||||
if (group->moba_data_ptr && group->moba_size > 0) {
|
||||
const SMOBatch* wmo_batches = (const SMOBatch*)group->moba_data_ptr;
|
||||
size_t num_batches = group->moba_size / sizeof(SMOBatch);
|
||||
|
||||
mesh.batches = (RenderBatch*)malloc(num_batches * sizeof(RenderBatch));
|
||||
mesh.batchCount = num_batches;
|
||||
|
||||
for (size_t i = 0; i < num_batches; i++) {
|
||||
mesh.batches[i].indexOffset = wmo_batches[i].startIndex;
|
||||
mesh.batches[i].indexCount = wmo_batches[i].count;
|
||||
|
||||
// lookup texture id
|
||||
uint8_t mat_id = wmo_batches[i].material_id;
|
||||
|
||||
if (root_data && root_data->material_textures) {
|
||||
mesh.batches[i].textureID = root_data->material_textures[mat_id];
|
||||
} else {
|
||||
mesh.batches[i].textureID = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Fallback if none
|
||||
mesh.batchCount = 1;
|
||||
mesh.batches = (RenderBatch*)malloc(sizeof(RenderBatch));
|
||||
mesh.batches[0].indexOffset = 0;
|
||||
mesh.batches[0].indexCount = num_indices;
|
||||
mesh.batches[0].textureID = 0;
|
||||
}
|
||||
|
||||
// create buffers
|
||||
glGenVertexArrays(1, &mesh.VAO);
|
||||
glGenBuffers(1, &mesh.VBO);
|
||||
glGenBuffers(1, &mesh.EBO);
|
||||
|
||||
glBindVertexArray(mesh.VAO);
|
||||
|
||||
// vbo
|
||||
glBindBuffer(GL_ARRAY_BUFFER, mesh.VBO);
|
||||
glBufferData(GL_ARRAY_BUFFER, num_vertices * sizeof(Vertex), vertices, GL_STATIC_DRAW);
|
||||
|
||||
// ebo
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.EBO);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, group->movi_size, group->movi_data_ptr, GL_STATIC_DRAW);
|
||||
|
||||
// attributes
|
||||
// 0: Position
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
|
||||
glEnableVertexAttribArray(0);
|
||||
// 1: Normal
|
||||
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)sizeof(C3Vector));
|
||||
glEnableVertexAttribArray(1);
|
||||
// 2: TexCoord
|
||||
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)(2 * sizeof(C3Vector)));
|
||||
glEnableVertexAttribArray(2);
|
||||
|
||||
glBindVertexArray(0);
|
||||
|
||||
free(vertices);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
void draw_group_mesh(GroupMesh mesh) {
|
||||
if (mesh.VAO == 0)
|
||||
return;
|
||||
|
||||
glBindVertexArray(mesh.VAO);
|
||||
|
||||
for (size_t i = 0; i < mesh.batchCount; i++) {
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, mesh.batches[i].textureID);
|
||||
|
||||
// The last argument is the byte offset into the EBO
|
||||
// multiply indexOffset * sizeof(uint16_t) because OpenGL expects bytes
|
||||
void* offset = (void*)(uintptr_t)(mesh.batches[i].indexOffset * sizeof(uint16_t));
|
||||
glDrawElements(GL_TRIANGLES, (GLsizei)mesh.batches[i].indexCount, GL_UNSIGNED_SHORT, offset);
|
||||
}
|
||||
|
||||
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
void free_group_mesh(GroupMesh *mesh) {
|
||||
if (mesh->VAO) glDeleteVertexArrays(1, &mesh->VAO);
|
||||
if (mesh->VBO) glDeleteBuffers(1, &mesh->VBO);
|
||||
if (mesh->EBO) glDeleteBuffers(1, &mesh->EBO);
|
||||
|
||||
if (mesh->batches) {
|
||||
free(mesh->batches);
|
||||
mesh->batches = NULL;
|
||||
}
|
||||
|
||||
mesh->VAO = 0;
|
||||
mesh->batchCount = 0;
|
||||
}
|
||||
35
src/renderer/mesh.h
Normal file
35
src/renderer/mesh.h
Normal file
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// Created by Tristan on 11/24/2025.
|
||||
//
|
||||
|
||||
#ifndef MESH_H
|
||||
#define MESH_H
|
||||
#include <stdint.h>
|
||||
|
||||
#include "glad/glad.h"
|
||||
#include "../wmo/wmo_structs.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
C3Vector position;
|
||||
C3Vector normal;
|
||||
C2Vector texCoord;
|
||||
} Vertex;
|
||||
|
||||
typedef struct {
|
||||
uint32_t indexOffset; // where in the EBO this batch starts
|
||||
uint32_t indexCount; // how many indices to draw
|
||||
GLuint textureID; // the texture to bind to
|
||||
} RenderBatch;
|
||||
|
||||
typedef struct {
|
||||
GLuint VAO, VBO, EBO;
|
||||
RenderBatch* batches;
|
||||
size_t batchCount;
|
||||
} GroupMesh;
|
||||
|
||||
GroupMesh create_mesh_from_group(const WMOGroupData* group, const WMORootData* root_data);
|
||||
void draw_group_mesh(GroupMesh mesh);
|
||||
void free_group_mesh(GroupMesh *mesh);
|
||||
|
||||
#endif //MESH_H
|
||||
136
src/renderer/shader.c
Normal file
136
src/renderer/shader.c
Normal file
@@ -0,0 +1,136 @@
|
||||
//
|
||||
// Created by Tristan on 11/26/2025.
|
||||
//
|
||||
|
||||
#include "shader.h"
|
||||
#include "../util.h"
|
||||
#include "../logger/log.h"
|
||||
#include "../logger/gl_log.h"
|
||||
|
||||
|
||||
// creates a shader program from a vertex and fragment shader
|
||||
// vertex_shader_str - a null terminated string of text containing a vertex shader
|
||||
// fragment_shader_str - a null terminated string of text containing a fragment shader
|
||||
// returns a new, valid shader program handle, or 0 if there was an issue
|
||||
// assert on NULL parameters
|
||||
GLuint create_shader_program_from_strings(const char *vertex_shader_str, const char *fragment_shader_str) {
|
||||
assert(vertex_shader_str && fragment_shader_str);
|
||||
|
||||
GLuint shader_program = glCreateProgram();
|
||||
GLuint vertex_shader_handle = glCreateShader(GL_VERTEX_SHADER);
|
||||
GLuint fragment_shader_handle = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
|
||||
// compile shader and check for errors
|
||||
glShaderSource(vertex_shader_handle, 1, &vertex_shader_str, NULL);
|
||||
glCompileShader(vertex_shader_handle);
|
||||
int lparams = -1;
|
||||
glGetShaderiv(vertex_shader_handle, GL_COMPILE_STATUS, &lparams);
|
||||
|
||||
if (GL_TRUE != lparams) {
|
||||
log_error("ERROR: vertex shader index %u did not compile", vertex_shader_handle);
|
||||
|
||||
const int max_length = 2048;
|
||||
int actual_length = 0;
|
||||
char slog[2048];
|
||||
glGetShaderInfoLog(vertex_shader_handle, max_length, &actual_length, slog);
|
||||
log_error("shader info log for GL index %u:\n%s", vertex_shader_handle, slog);
|
||||
|
||||
glDeleteShader(vertex_shader_handle);
|
||||
glDeleteShader(fragment_shader_handle);
|
||||
glDeleteProgram(shader_program);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// compile shader and check for errors
|
||||
glShaderSource(fragment_shader_handle, 1, &fragment_shader_str, NULL);
|
||||
glCompileShader(fragment_shader_handle);
|
||||
lparams = -1;
|
||||
glGetShaderiv(fragment_shader_handle, GL_COMPILE_STATUS, &lparams);
|
||||
|
||||
if (GL_TRUE != lparams) {
|
||||
log_error("ERROR: fragment shader index %u did not compile", fragment_shader_handle);
|
||||
|
||||
const int max_length = 2048;
|
||||
int actual_length = 0;
|
||||
char slog[2048];
|
||||
glGetShaderInfoLog(fragment_shader_handle, max_length, &actual_length, slog);
|
||||
gl_log_err(slog);
|
||||
|
||||
glDeleteShader(vertex_shader_handle);
|
||||
glDeleteShader(fragment_shader_handle);
|
||||
glDeleteProgram(shader_program);
|
||||
return 0;
|
||||
}
|
||||
|
||||
glAttachShader(shader_program, fragment_shader_handle);
|
||||
glAttachShader(shader_program, vertex_shader_handle);
|
||||
|
||||
// link program and check for errors
|
||||
glLinkProgram(shader_program);
|
||||
glDeleteShader(vertex_shader_handle);
|
||||
glDeleteShader(fragment_shader_handle);
|
||||
lparams = -1;
|
||||
glGetProgramiv(shader_program, GL_LINK_STATUS, &lparams);
|
||||
|
||||
if (GL_TRUE != lparams) {
|
||||
log_error("ERROR: could not link shader program GL index %u", shader_program);
|
||||
|
||||
const int max_length = 2048;
|
||||
int actual_length = 0;
|
||||
char plog[2048];
|
||||
glGetProgramInfoLog(shader_program, max_length, &actual_length, plog);
|
||||
log_error("program info log for GL index %u:\n%s", shader_program, plog);
|
||||
|
||||
glDeleteProgram(shader_program);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return shader_program;
|
||||
}
|
||||
|
||||
GLuint create_shader_program_from_files(const char *vertex_shader_filename, const char *fragment_shader_filename) {
|
||||
assert(vertex_shader_filename && fragment_shader_filename);
|
||||
|
||||
log_info("loading shader from files `%s` and `%s`", vertex_shader_filename, fragment_shader_filename);
|
||||
|
||||
char vs_shader_str[MAX_SHADER_SZ];
|
||||
char fs_shader_str[MAX_SHADER_SZ];
|
||||
vs_shader_str[0] = fs_shader_str[0] = '\0';
|
||||
|
||||
// read the vertex shader file into a buffer
|
||||
FILE *fp = fopen(vertex_shader_filename, "r");
|
||||
if (!fp) {
|
||||
log_error("ERROR: could not open vertex shader file `%s`", vertex_shader_filename);
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t count = fread(vs_shader_str, 1, MAX_SHADER_SZ - 1, fp);
|
||||
assert(count < MAX_SHADER_SZ - 1); // file too long otherwise
|
||||
vs_shader_str[count] = '\0';
|
||||
fclose(fp);
|
||||
|
||||
// read fragment shader file into a buffer
|
||||
fp = fopen(fragment_shader_filename, "r");
|
||||
if (!fp) {
|
||||
log_error("ERROR: could not open fragment shader file `%s`", fragment_shader_filename);
|
||||
return 0;
|
||||
}
|
||||
|
||||
count = fread(fs_shader_str, 1, MAX_SHADER_SZ - 1, fp);
|
||||
assert(count < MAX_SHADER_SZ - 1); // file too long otherwise
|
||||
fs_shader_str[count] = '\0';
|
||||
fclose(fp);
|
||||
|
||||
return create_shader_program_from_strings(vs_shader_str, fs_shader_str);
|
||||
}
|
||||
|
||||
void shader_reload(GLuint *program, const char *vertex_shader_filename, const char *fragment_shader_filename) {
|
||||
assert(program && vertex_shader_filename && fragment_shader_filename);
|
||||
|
||||
GLuint reloaded_program = create_shader_program_from_files(vertex_shader_filename, fragment_shader_filename);
|
||||
|
||||
if (reloaded_program) {
|
||||
glDeleteProgram(*program);
|
||||
*program = reloaded_program;
|
||||
}
|
||||
}
|
||||
14
src/renderer/shader.h
Normal file
14
src/renderer/shader.h
Normal file
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// Created by Tristan on 11/26/2025.
|
||||
//
|
||||
|
||||
#ifndef SHADER_H
|
||||
#define SHADER_H
|
||||
#include "glad/glad.h"
|
||||
|
||||
#define MAX_SHADER_SZ 100000
|
||||
|
||||
void shader_reload(GLuint *program, const char *vertex_shader_filename, const char *fragment_shader_filename);
|
||||
GLuint create_shader_program_from_strings(const char *vertex_shader_str, const char *fragment_shader_str);
|
||||
GLuint create_shader_program_from_files(const char *vertex_shader_filename, const char *fragment_shader_filename);
|
||||
#endif //SHADER_H
|
||||
76
src/renderer/texture.c
Normal file
76
src/renderer/texture.c
Normal file
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// Created by Tristan on 1/4/2026.
|
||||
//
|
||||
|
||||
#include "texture.h"
|
||||
#include <string.h>
|
||||
#include "../logger/log.h"
|
||||
|
||||
GLuint texture_load_from_blp_memory(const uint8_t* file_data, size_t file_size) {
|
||||
if (!file_data || file_size < sizeof(BLPHeader)) {
|
||||
log_error("BLP texture data is empty or too small");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const BLPHeader* h = (const BLPHeader*)file_data;
|
||||
|
||||
if (memcmp(h->magic, "BLP2", 4) != 0) {
|
||||
log_error("Invalid BLP Magic. Expected BLP2, got %s", h->magic);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (h->compression != 2) {
|
||||
log_error("Unsupported BLP compression type: %d", h->compression);
|
||||
return 0;
|
||||
}
|
||||
|
||||
GLenum glFormat = 0;
|
||||
switch (h->pixelFormat) {
|
||||
case 0: // DXT1
|
||||
glFormat = (h->alphaBits > 0) ? GL_COMPRESSED_RGBA_S3TC_DXT1_EXT : GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
|
||||
break;
|
||||
case 1: // DXT3
|
||||
glFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
|
||||
break;
|
||||
case 7:
|
||||
glFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
|
||||
break;
|
||||
default:
|
||||
log_error("Unsupported DXT pixelFormat: %d", h->pixelFormat);
|
||||
return 0;
|
||||
}
|
||||
|
||||
GLuint textureID;
|
||||
glGenTextures(1, &textureID);
|
||||
glBindTexture(GL_TEXTURE_2D, textureID);
|
||||
|
||||
// WMO textures standard settings
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
uint32_t width = h->width;
|
||||
uint32_t height = h->height;
|
||||
int blockSize = (glFormat == GL_COMPRESSED_RGB_S3TC_DXT1_EXT || glFormat == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) ? 8 : 16;
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (h->mipOffset[i] == 0 || (width == 0 && height == 0)) break;
|
||||
|
||||
const uint8_t* dataPtr = file_data + h->mipOffset[i];
|
||||
uint32_t mipSize = ((width + 3) / 4) * ((height + 3) / 4) * blockSize;
|
||||
if (mipSize == 0)
|
||||
mipSize = blockSize;
|
||||
|
||||
glCompressedTexImage2D(GL_TEXTURE_2D, i, glFormat, width, height, 0, mipSize, dataPtr);
|
||||
|
||||
width /= 2;
|
||||
height /= 2;
|
||||
if (width == 0)
|
||||
width = 1;
|
||||
if (height == 0)
|
||||
height = 1;
|
||||
}
|
||||
|
||||
return textureID;
|
||||
}
|
||||
42
src/renderer/texture.h
Normal file
42
src/renderer/texture.h
Normal file
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// Created by Tristan on 1/4/2026.
|
||||
//
|
||||
|
||||
#ifndef TEXTURE_H
|
||||
#define TEXTURE_H
|
||||
|
||||
#include <glad/glad.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifndef GL_COMPRESSED_RGB_S3TC_DXT1_EXT
|
||||
#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0
|
||||
#endif
|
||||
#ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT
|
||||
#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1
|
||||
#endif
|
||||
#ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT
|
||||
#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2
|
||||
#endif
|
||||
#ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
|
||||
#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
|
||||
#endif
|
||||
|
||||
// BLP2 Header def
|
||||
typedef struct {
|
||||
char magic[4]; // "BLP2"
|
||||
uint32_t version; // always 1
|
||||
uint8_t compression; // 1=Raw/Paletted, 2=DXT
|
||||
uint8_t alphaBits; // 0, 1, 8
|
||||
uint8_t pixelFormat; // 0=DXT1, 1=DXT3, 7=DXT5
|
||||
uint8_t mipFlags;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t mipOffset[16];
|
||||
uint32_t mipSize[16];
|
||||
} BLPHeader;
|
||||
|
||||
// Loads a BLP file from memory directly to the GPU
|
||||
GLuint texture_load_from_blp_memory(const uint8_t* file_data, size_t file_size);
|
||||
|
||||
#endif //TEXTURE_H
|
||||
116
src/renderer/vector.c
Normal file
116
src/renderer/vector.c
Normal file
@@ -0,0 +1,116 @@
|
||||
//
|
||||
// Created by Tristan on 1/4/2026.
|
||||
//
|
||||
|
||||
#include <math.h>
|
||||
#include "vector.h"
|
||||
|
||||
|
||||
float vec3_length(vec3_t v) {
|
||||
return sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
|
||||
}
|
||||
|
||||
// Addition
|
||||
vec3_t vec3_add(vec3_t a, vec3_t b) {
|
||||
vec3_t result = {
|
||||
.x = a.x + b.x,
|
||||
.y = a.y + b.y,
|
||||
.z = a.z + b.z
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Subtraction
|
||||
vec3_t vec3_sub(vec3_t a, vec3_t b) {
|
||||
vec3_t result = {
|
||||
.x = a.x - b.x,
|
||||
.y = a.y - b.y,
|
||||
.z = a.z - b.z
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Multiplication
|
||||
vec3_t vec3_mul(vec3_t v, float factor) {
|
||||
vec3_t result = {
|
||||
.x = v.x * factor,
|
||||
.y = v.y * factor,
|
||||
.z = v.z * factor
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Division
|
||||
vec3_t vec3_div(vec3_t v, float factor) {
|
||||
vec3_t result = {
|
||||
.x = v.x / factor,
|
||||
.y = v.y / factor,
|
||||
.z = v.z / factor
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Cross Product - helps to find the perpendicular vector between two vectors
|
||||
vec3_t vec3_cross(vec3_t a, vec3_t b) {
|
||||
vec3_t result = {
|
||||
.x = a.y * b.z - a.z * b.y,
|
||||
.y = a.z * b.x - a.x * b.z,
|
||||
.z = a.x * b.y - a.y * b.x
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Dot Product - helps to find how aligned two vectors are to each other
|
||||
float vec3_dot(vec3_t a, vec3_t b) {
|
||||
return (a.x * b.x) + (a.y * b.y) + (a.z * b.z);
|
||||
}
|
||||
|
||||
// Normalize a vec3_t vector
|
||||
void vec3_normalize(vec3_t* v) {
|
||||
float length = sqrt((v->x * v->x) + (v->y * v->y) + (v->z * v->z));
|
||||
|
||||
// avoid dividing by 0
|
||||
if (length < 0.00001f) {
|
||||
v->x = 0;
|
||||
v->y = 0;
|
||||
v->z = 0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
v->x /= length;
|
||||
v->y /= length;
|
||||
v->z /= length;
|
||||
}
|
||||
|
||||
vec3_t vec3_rotate_x(vec3_t v, float angle) {
|
||||
vec3_t rotated_vector = {
|
||||
.x = v.x,
|
||||
.y = v.y * cos(angle) - v.z * sin(angle),
|
||||
.z = v.y * sin(angle) + v.z * cos(angle)
|
||||
};
|
||||
return rotated_vector;
|
||||
}
|
||||
|
||||
vec3_t vec3_rotate_y(vec3_t v, float angle) {
|
||||
vec3_t rotated_vector = {
|
||||
.x = v.x * cos(angle) - v.z * sin(angle),
|
||||
.y = v.y,
|
||||
.z = v.x * sin(angle) + v.z * cos(angle)
|
||||
};
|
||||
return rotated_vector;
|
||||
}
|
||||
|
||||
vec3_t vec3_rotate_z(vec3_t v, float angle) {
|
||||
vec3_t rotated_vector = {
|
||||
.x = v.x * cos(angle) - v.y * sin(angle),
|
||||
.y = v.x * sin(angle) + v.y * cos(angle),
|
||||
.z = v.z
|
||||
};
|
||||
return rotated_vector;
|
||||
}
|
||||
42
src/renderer/vector.h
Normal file
42
src/renderer/vector.h
Normal file
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// Created by Tristan on 1/4/2026.
|
||||
//
|
||||
|
||||
#ifndef VECTOR_H
|
||||
#define VECTOR_H
|
||||
|
||||
typedef struct {
|
||||
float x;
|
||||
float y;
|
||||
} vec2_t;
|
||||
|
||||
typedef struct {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
} vec3_t;
|
||||
|
||||
typedef struct {
|
||||
float x, y, z, w;
|
||||
} vec4_t;
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Vector 3D Functions
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
float vec3_length(vec3_t v);
|
||||
vec3_t vec3_add(vec3_t a, vec3_t b);
|
||||
vec3_t vec3_sub(vec3_t a, vec3_t b);
|
||||
vec3_t vec3_mul(vec3_t v, float factor);
|
||||
vec3_t vec3_div(vec3_t v, float factor);
|
||||
vec3_t vec3_cross(vec3_t a, vec3_t b);
|
||||
|
||||
vec3_t vec3_rotate_x(vec3_t v, float angle);
|
||||
vec3_t vec3_rotate_y(vec3_t v, float angle);
|
||||
vec3_t vec3_rotate_z(vec3_t v, float angle);
|
||||
vec3_t vec3_cross(vec3_t a, vec3_t b);
|
||||
float vec3_dot(vec3_t a, vec3_t b);
|
||||
void vec3_normalize(vec3_t* v);
|
||||
|
||||
|
||||
#endif //VECTOR_H
|
||||
803
src/util.c
Normal file
803
src/util.c
Normal file
@@ -0,0 +1,803 @@
|
||||
//
|
||||
// Created by Tristan on 11/6/2025.
|
||||
//
|
||||
|
||||
#include "util.h"
|
||||
#include "logger/log.h"
|
||||
#include "wmo/wmo.h"
|
||||
#include "mpq/mpq.h"
|
||||
|
||||
WMORootData out_wmo_data;
|
||||
|
||||
char *get_file_contents(const char *filename) {
|
||||
FILE *fp;
|
||||
long length;
|
||||
char *buffer = NULL;
|
||||
|
||||
fp = fopen(filename, "rb");
|
||||
if (!fp) {
|
||||
log_error("Could not open file '%s'. Reason: %s", filename, strerror(errno));
|
||||
}
|
||||
|
||||
fseek(fp, 0, SEEK_END);
|
||||
length = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
|
||||
buffer = (char *)malloc(length + 1);
|
||||
if (!buffer) {
|
||||
log_error("Memory allocation failed for file '%s'.", filename);
|
||||
fclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fread(buffer, 1, length, fp);
|
||||
buffer[length] ='\0';
|
||||
fclose(fp);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
int load_wmo_file(const char *filename, FILE **file_ptr_out) {
|
||||
*file_ptr_out = fopen(filename, "rb");
|
||||
|
||||
if (*file_ptr_out == NULL) {
|
||||
log_error("Error opening WMO file %s: %s", filename, GetLastError());
|
||||
return (int)GetLastError();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int create_dir_recursive(const char *full_path) {
|
||||
char temp_path[FILENAME_MAX];
|
||||
log_info("Full Path in craete_dir_recursive == '%s", full_path);
|
||||
strncpy(temp_path, full_path, FILENAME_MAX - 1);
|
||||
temp_path[FILENAME_MAX - 1] = '\0';
|
||||
|
||||
char *p = temp_path;
|
||||
|
||||
// Check for "C:\" or "/" prefix and move the pointer past
|
||||
if (p[0] == PATH_SEPARATOR || p[0] == '/')
|
||||
p++;
|
||||
#ifdef _WIN32
|
||||
if (p[1] == ':')
|
||||
p += 2; // skips (C:)
|
||||
#endif
|
||||
|
||||
while (*p != '\0') {
|
||||
if (*p == PATH_SEPARATOR || *p == '/') {
|
||||
// replace with null terminator to isolate
|
||||
char separator = *p;
|
||||
*p = '\0';
|
||||
|
||||
// Attempt to create dir
|
||||
if (MAKE_DIR(temp_path) != 0) {
|
||||
if (errno != EEXIST) {
|
||||
DWORD dwError = GetLastError();
|
||||
log_error("Failed to create directory '%s'. error: %s", temp_path, dwError);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Restore separator character
|
||||
*p = separator;
|
||||
}
|
||||
p++; // adv to next char
|
||||
}
|
||||
|
||||
if (MAKE_DIR(temp_path) != 0) {
|
||||
if (errno != EEXIST) {
|
||||
log_error("Failed to create final directory '%s'. Error: %lu", temp_path, GetLastError());
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO: might not need this honestly
|
||||
/*
|
||||
WMOData load_mver_data() {
|
||||
WMOChunkHeader mver_header;
|
||||
WMOData result = {NULL, 0};
|
||||
DWORD bytesRead = 0;
|
||||
// magic;
|
||||
// size;
|
||||
// version;
|
||||
// return;
|
||||
}*/
|
||||
|
||||
WMOData load_wmo_data(ArchiveManager *archives, const char *wmoFileName) {
|
||||
HANDLE hFile = get_file_in_archives(archives, wmoFileName);
|
||||
WMOData result = {NULL, 0};
|
||||
DWORD bytesRead = 0;
|
||||
|
||||
if (!hFile) {
|
||||
log_error("Failed to open WMO file '%s' inside MPQ. Error: %lu", wmoFileName, GetLastError());
|
||||
return result;
|
||||
}
|
||||
|
||||
result.size = get_file_size_in_mpq(hFile, wmoFileName);
|
||||
if (result.size == SFILE_INVALID_SIZE) {
|
||||
SFileCloseFile(hFile);
|
||||
return result;
|
||||
}
|
||||
|
||||
result.data = (char *)malloc(result.size);
|
||||
if (result.data == NULL) {
|
||||
log_error("Memory allocation failed for WMO file '%s'.\n", wmoFileName);
|
||||
SFileCloseFile(hFile);
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO: make into helper function
|
||||
if (!SFileReadFile(hFile, result.data, result.size, &bytesRead, NULL)) {
|
||||
log_error("Failed to read WMO data for '%s'. Error: %lu", wmoFileName, GetLastError());
|
||||
free(result.data);
|
||||
result.data = NULL;
|
||||
result.size = 0;
|
||||
} else if (bytesRead != result.size) {
|
||||
log_error("Read size mismatch for WMO file '%s'. Expected %lu, got %lu.",
|
||||
wmoFileName, result.size, bytesRead);
|
||||
free(result.data);
|
||||
result.data = NULL;
|
||||
result.size = 0;
|
||||
}
|
||||
|
||||
SFileCloseFile(hFile);
|
||||
|
||||
log_info("Successfully loaded WMO file '%s' (%lu bytes) into memory.", wmoFileName, result.size);
|
||||
return result;
|
||||
}
|
||||
|
||||
WMOData load_wmo_data_from_file(FILE **file_ptr_in) {
|
||||
//HANDLE hFile = get_file_in_mpq(hMPQ, wmoFileName);
|
||||
WMOData result = {NULL, 0};
|
||||
//DWORD bytesRead = 0;
|
||||
|
||||
if (!*file_ptr_in) {
|
||||
log_error("Failed to open WMO file. Error: %lu", GetLastError());
|
||||
return result;
|
||||
}
|
||||
|
||||
fseek(*file_ptr_in, 0, SEEK_END);
|
||||
long file_size = ftell(*file_ptr_in);
|
||||
|
||||
result.size = file_size;
|
||||
if (result.size == -1L) {
|
||||
log_error("Failed to determine WMO size.");
|
||||
fclose(*file_ptr_in);
|
||||
*file_ptr_in = NULL;
|
||||
return result;
|
||||
}
|
||||
|
||||
result.data = (char *)malloc(result.size);
|
||||
if (result.data == NULL) {
|
||||
log_error("Memory allocation failed for WMO file.\n");
|
||||
fclose(*file_ptr_in);
|
||||
*file_ptr_in = NULL;
|
||||
return result;
|
||||
}
|
||||
|
||||
fseek(*file_ptr_in, 0, SEEK_SET);
|
||||
|
||||
size_t items_read = fread(result.data, 1, result.size, *file_ptr_in);
|
||||
|
||||
if (items_read != result.size) {
|
||||
log_error("Error reading WMO file data. Expected %ld bytes, read %zu bytes.\n", result.size, items_read);
|
||||
free(result.data);
|
||||
result.data = NULL;
|
||||
result.size = 0;
|
||||
fclose(*file_ptr_in);
|
||||
*file_ptr_in = NULL;
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
// TODO: make into helper function
|
||||
if (!SFileReadFile(hFile, result.data, result.size, &bytesRead, NULL)) {
|
||||
log_error("Failed to read WMO data for '%s'. Error: %lu", wmoFileName, GetLastError());
|
||||
free(result.data);
|
||||
result.data = NULL;
|
||||
result.size = 0;
|
||||
} else if (bytesRead != result.size) {
|
||||
log_error("Read size mismatch for WMO file '%s'. Expected %lu, got %lu.",
|
||||
wmoFileName, result.size, bytesRead);
|
||||
free(result.data);
|
||||
result.data = NULL;
|
||||
result.size = 0;
|
||||
}
|
||||
|
||||
SFileCloseFile(hFile);*/
|
||||
fclose(*file_ptr_in);
|
||||
*file_ptr_in = NULL;
|
||||
log_info("Successfully loaded WMO file (%lu bytes) into memory.", result.size);
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t get_wmo_base_name(char *dest, size_t dest_size, const char *full_path) {
|
||||
size_t len = strlen(full_path);
|
||||
if (len >= dest_size) {
|
||||
//TODO: handle error
|
||||
return 0;
|
||||
}
|
||||
|
||||
strncpy (dest, full_path, len);
|
||||
dest[len] = '\0';
|
||||
if (len > 4 && strcmp(dest + len - 4, ".wmo") == 0) {
|
||||
dest[len - 4] = '\0';
|
||||
return len -4;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
void parse_wmo_chunks(ArchiveManager *archives, const char *wmo_buffer, DWORD total_size, WMORootData *out_wmo_data,
|
||||
const char *wmo_file_path) {
|
||||
const char *current_ptr = wmo_buffer;
|
||||
const char *end_ptr = wmo_buffer + total_size;
|
||||
// WMORootData out_wmo_data; not sure if should be defined here or outside of scope at top
|
||||
|
||||
// initialize struct passed into function
|
||||
memset(out_wmo_data, 0, sizeof(WMORootData));
|
||||
|
||||
log_info("Starting WMO chunk traversal...");
|
||||
|
||||
while (current_ptr < end_ptr) {
|
||||
// must be enough room for a header (8 bytes)
|
||||
if (current_ptr + sizeof(WMOChunkHeader) > end_ptr) {
|
||||
log_warn("End of file reached unexpectedly.");
|
||||
break;
|
||||
}
|
||||
|
||||
const WMOChunkHeader *header = (const WMOChunkHeader *)current_ptr;
|
||||
|
||||
log_info("Found chunk: %c%c%c%c, Size: %lu bytes",
|
||||
(char)(header->chunk_name >> 24),
|
||||
(char)(header->chunk_name >> 16),
|
||||
(char)(header->chunk_name >> 8),
|
||||
(char)(header->chunk_name),
|
||||
header->chunk_size);
|
||||
|
||||
const char *data_start = current_ptr + sizeof(WMOChunkHeader); // sizeof(WMOChunkHeader);
|
||||
|
||||
switch (header->chunk_name) {
|
||||
case MVER:
|
||||
out_wmo_data->mver_data_ptr = data_start;
|
||||
out_wmo_data->mver_size = header->chunk_size;
|
||||
log_info(" -> MVER Chunk found at offset %td.", out_wmo_data->mver_data_ptr - wmo_buffer);
|
||||
break;
|
||||
case MOHD:
|
||||
out_wmo_data->mohd_data_ptr = data_start;
|
||||
out_wmo_data->mohd_size = header->chunk_size;
|
||||
log_info(" -> MOHD Chunk found at offset %td.", out_wmo_data->mohd_data_ptr - wmo_buffer);
|
||||
parse_mohd_chunk(out_wmo_data);
|
||||
break;
|
||||
case MOGN:
|
||||
out_wmo_data->mogn_data_ptr = data_start;
|
||||
out_wmo_data->mogn_size = header->chunk_size;
|
||||
log_info(" -> MOGN Chunk found at offset %td.", out_wmo_data->mogn_data_ptr - wmo_buffer);
|
||||
break;
|
||||
case MOTX:
|
||||
out_wmo_data->motx_data_ptr = data_start;
|
||||
out_wmo_data->motx_size = header->chunk_size;
|
||||
log_info(" -> MOTX Chunk found at offset %td.", out_wmo_data->motx_data_ptr - wmo_buffer);
|
||||
break;
|
||||
case MOMT:
|
||||
out_wmo_data->momt_data_ptr = data_start;
|
||||
out_wmo_data->momt_size = header->chunk_size;
|
||||
log_info(" -> MOMT Chunk found at offset %td", out_wmo_data->momt_data_ptr - wmo_buffer);
|
||||
break;
|
||||
case MOGI:
|
||||
out_wmo_data->mogi_data_ptr = data_start;
|
||||
out_wmo_data->mogi_size = header->chunk_size;
|
||||
log_info(" -> MOGI Chunk found at offset %td", out_wmo_data->mogi_data_ptr - wmo_buffer);
|
||||
break;
|
||||
case MOSB:
|
||||
out_wmo_data->mosb_data_ptr = data_start;
|
||||
out_wmo_data->mosb_size = header->chunk_size;
|
||||
log_info(" -> MOSB Chunk found at offset %td", out_wmo_data->mosb_data_ptr - wmo_buffer);
|
||||
get_mosb_skybox(out_wmo_data);
|
||||
break;
|
||||
case MOPV:
|
||||
out_wmo_data->mopv_data_ptr = data_start;
|
||||
out_wmo_data->mopv_size = header->chunk_size;
|
||||
log_info(" -> MOPV Chunk found at offset %td", out_wmo_data->mopv_data_ptr - wmo_buffer);
|
||||
parse_mopv_chunk(out_wmo_data);
|
||||
break;
|
||||
case MOPT:
|
||||
out_wmo_data->mopt_data_ptr = data_start;
|
||||
out_wmo_data->mopt_size = header->chunk_size;
|
||||
log_info(" -> MOPT Chunk found at offset %td", out_wmo_data->mopt_data_ptr - wmo_buffer);
|
||||
parse_mopt_chunk(out_wmo_data);
|
||||
break;
|
||||
case MOPR:
|
||||
out_wmo_data->mopr_data_ptr = data_start;
|
||||
out_wmo_data->mopr_size = header->chunk_size;
|
||||
log_info(" -> MOPR Chunk found at offset %td", out_wmo_data->mopr_data_ptr - wmo_buffer);
|
||||
parse_mopr_chunk(out_wmo_data);
|
||||
break;
|
||||
case MOVV:
|
||||
out_wmo_data->movv_data_ptr = data_start;
|
||||
out_wmo_data->movv_size = header->chunk_size;
|
||||
log_info(" -> MOVV Chunk found at offset %td", out_wmo_data->movv_data_ptr - wmo_buffer);
|
||||
break;
|
||||
case MOVB:
|
||||
out_wmo_data->movb_data_ptr = data_start;
|
||||
out_wmo_data->movb_size = header->chunk_size;
|
||||
log_info(" -> MOVB Chunk found at offset %td", out_wmo_data->movb_data_ptr - wmo_buffer);
|
||||
break;
|
||||
case MOLT:
|
||||
out_wmo_data->molt_data_ptr = data_start;
|
||||
out_wmo_data->molt_size = header->chunk_size;
|
||||
log_info(" -> MOLT Chunk found at offset %td", out_wmo_data->molt_data_ptr - wmo_buffer);
|
||||
break;
|
||||
case MODS:
|
||||
out_wmo_data->mods_data_ptr = data_start;
|
||||
out_wmo_data->mods_size = header->chunk_size;
|
||||
log_info(" -> MODS Chunk found at offset %td", out_wmo_data->mods_data_ptr - wmo_buffer);
|
||||
break;
|
||||
case MODN:
|
||||
out_wmo_data->modn_data_ptr = data_start;
|
||||
out_wmo_data->modn_size = header->chunk_size;
|
||||
log_info(" -> MODN Chunk found at offset %td", out_wmo_data->modn_data_ptr - wmo_buffer);
|
||||
break;
|
||||
case MODD:
|
||||
out_wmo_data->modd_data_ptr = data_start;
|
||||
out_wmo_data->modd_size = header->chunk_size;
|
||||
log_info(" -> MODD Chunk found at offset %td", out_wmo_data->modd_data_ptr - wmo_buffer);
|
||||
break;
|
||||
case MFOG:
|
||||
out_wmo_data->mfog_data_ptr = data_start;
|
||||
out_wmo_data->mfog_size = header->chunk_size;
|
||||
log_info(" -> MFOG Chunk found at offset %td", out_wmo_data->mfog_data_ptr - wmo_buffer);
|
||||
break;
|
||||
default:
|
||||
log_info("TODO! %c%c%c%c",
|
||||
(char)(header->chunk_name >> 24),
|
||||
(char)(header->chunk_name >> 16),
|
||||
(char)(header->chunk_name >> 8),
|
||||
(char)(header->chunk_name));
|
||||
break;
|
||||
}
|
||||
current_ptr = data_start + header->chunk_size;
|
||||
}
|
||||
// TODO - actually setup proper loading of chunks before trying to parse NULL info LOL
|
||||
get_wmo_group_names(archives, out_wmo_data, wmo_file_path);
|
||||
}
|
||||
|
||||
// TODO: need to pass a proper handle to the file here, not just the array of handles
|
||||
// currently we just pass the whole iteration and hopefully one of them contains (slow!!!)
|
||||
BOOL extract_blp_file(HANDLE hMPQ, const char *mpqFilePath, const char *localOutPath) {
|
||||
HANDLE hFile = NULL;
|
||||
|
||||
if (hMPQ == NULL || hMPQ == INVALID_HANDLE_VALUE) {
|
||||
return FALSE; // Skip invalid handles
|
||||
}
|
||||
|
||||
if (!SFileOpenFileEx(hMPQ, mpqFilePath, 0, &hFile)) {
|
||||
log_error("Could not open file '%s' in MPQ, continuing...", hFile);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// if (!SFileFindFirstFile(hCurrentArchive, mpqFilePath, ))
|
||||
|
||||
if (SFileExtractFile(hMPQ, mpqFilePath, localOutPath, SFILE_OPEN_FROM_MPQ)) {
|
||||
log_info("Sucessfully extracted '%s' from archive to '%s'", mpqFilePath, localOutPath);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (GetLastError() != ERROR_FILE_NOT_FOUND) {
|
||||
log_warn("Extraction attempt from archive failed with unexpected error (%lu) for file '%s'. Continuing...",
|
||||
GetLastError(), mpqFilePath);
|
||||
}
|
||||
|
||||
log_error("Failed to extract file '%s'. File not found in any loaded archive.", mpqFilePath);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void parse_momt_and_extract_textures(ArchiveManager *archives, const WMORootData *wmo_data) {
|
||||
log_info("Sizeof(SMOMaterial) is '%lu'", sizeof(SMOMaterial));
|
||||
if (wmo_data->momt_size < sizeof(SMOMaterial)) {
|
||||
log_error("MOMT chunk size (%lu) is too small to contain a single material!");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t material_count = wmo_data->momt_size / sizeof(SMOMaterial);
|
||||
|
||||
const SMOMaterial *materials = (const SMOMaterial *)wmo_data->momt_data_ptr;
|
||||
|
||||
log_info("Parsing %zu materials from MOMT chunk...", material_count);
|
||||
|
||||
for (size_t i = 0; i < material_count; i++) {
|
||||
uint32_t offset = materials[i].texture_name_offset;
|
||||
|
||||
// Skip materials that dont reference a texture
|
||||
if (offset == 0) {
|
||||
log_info("Material [%zu]: No texture referenced (Offset 0)\n", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (offset >= wmo_data->motx_size) {
|
||||
log_error("Material [%zu]: Invalid texture offset %lu (outside MOTX boundaries).\n", i, offset);
|
||||
continue;
|
||||
}
|
||||
|
||||
const char *mpq_blp_path = wmo_data->motx_data_ptr + offset; // offset was throwing us off by 4 bytes it looked like
|
||||
|
||||
log_info("Material [%zu] references texture at offset %lu: %s\n", i, offset, mpq_blp_path);
|
||||
|
||||
size_t file_len = strlen(mpq_blp_path);
|
||||
char *local_path_buffer = (char *)malloc(file_len + 1);
|
||||
if (local_path_buffer == NULL) {
|
||||
log_error("Memory allocation failed for output path.\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
// use strncpy!!!
|
||||
strcpy(local_path_buffer, mpq_blp_path);
|
||||
char *last_separator = NULL;
|
||||
|
||||
// Find the last path separator ('\' or '/')
|
||||
char *last_sep_1 = strrchr(local_path_buffer, '\\');
|
||||
char *last_sep_2 = strrchr(local_path_buffer, '/');
|
||||
|
||||
// Use the one that is further into the string
|
||||
last_separator = (last_sep_1 > last_sep_2) ? last_sep_1 : last_sep_2;
|
||||
|
||||
// If a directory structure exists (e.g., not just "file.blp")
|
||||
if (last_separator != NULL) {
|
||||
char original_separator = *last_separator;
|
||||
|
||||
// Temporarily null-terminate to isolate the directory part
|
||||
*last_separator = '\0';
|
||||
|
||||
// Create the directory structure (e.g., "Textures\Doodad")
|
||||
if (create_dir_recursive(local_path_buffer) == 0) {
|
||||
// Restore the original path string
|
||||
*last_separator = original_separator;
|
||||
|
||||
HANDLE hCurrentArchive = NULL;
|
||||
for (size_t i = 0; i < archives->count; i++) {
|
||||
hCurrentArchive = archives->archives[i];
|
||||
|
||||
if (hCurrentArchive == NULL || hCurrentArchive == INVALID_HANDLE_VALUE) {
|
||||
continue; // Skip invalid handles
|
||||
}
|
||||
|
||||
// so we don't try to extract multiple archives where the file clearly doesnt exist!
|
||||
// TODO: I should check other for loops and see if they're needlessly sending data to be processed
|
||||
// when files don't exist!
|
||||
if (SFileVerifyFile(hCurrentArchive, mpq_blp_path, 0) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//extract_blp_file(hCurrentArchive, mpq_blp_path, local_path_buffer);
|
||||
}
|
||||
// Extract using the full path
|
||||
|
||||
}
|
||||
} else {
|
||||
// File is in the root; extract directly
|
||||
HANDLE hCurrentArchive = NULL;
|
||||
for (size_t i = 0; i < archives->count; i++) {
|
||||
hCurrentArchive = archives->archives[i];
|
||||
|
||||
if (hCurrentArchive == NULL || hCurrentArchive == INVALID_HANDLE_VALUE) {
|
||||
continue; // Skip invalid handles
|
||||
}
|
||||
|
||||
//extract_blp_file(hCurrentArchive, mpq_blp_path, local_path_buffer);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Cleanup the dynamically allocated path buffer
|
||||
free(local_path_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
HANDLE get_file_in_mpq(ArchiveManager *archives, const char *file_in_mpq_name) {
|
||||
HANDLE hFileInArchive = NULL;
|
||||
// DWORD dwError = 0;
|
||||
|
||||
if (archives->count == 0 || archives->archives[0] == NULL) {
|
||||
log_error("No archives are mounted. Cannot search for file '%s'", file_in_mpq_name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!SFileOpenFileEx(archives->archives[12], file_in_mpq_name, SFILE_OPEN_FROM_MPQ, &hFileInArchive)) {
|
||||
log_error("Failed to open file '%s' inside MPQ. Error: %lu", file_in_mpq_name, GetLastError());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return hFileInArchive;
|
||||
}*/
|
||||
|
||||
uint32_t get_file_size_in_mpq(HANDLE hFileInArchive, const char *file_in_mpq_name) {
|
||||
DWORD dwFileSize = 0;
|
||||
|
||||
if (SFileGetFileInfo(hFileInArchive, SFileInfoFileSize, &dwFileSize,
|
||||
sizeof(dwFileSize), NULL)) {
|
||||
log_info("File '%s' size: %lu bytes (0x%lX) or %.6lf megabytes ", file_in_mpq_name, dwFileSize, dwFileSize,
|
||||
(float)dwFileSize / 1000000);
|
||||
} else {
|
||||
log_error("Failed to get size for file '%s'. Error: %lu", file_in_mpq_name, GetLastError());
|
||||
}
|
||||
|
||||
// SFileCloseFile(hFileInArchive);
|
||||
return dwFileSize;
|
||||
}
|
||||
|
||||
HANDLE get_file_in_archives(ArchiveManager *archives, const char *file_in_mpq_name) {
|
||||
HANDLE hFile = NULL;
|
||||
|
||||
for (size_t i = 0; i < archives->count; i++) {
|
||||
HANDLE hCurrentArchive = archives->archives[i];
|
||||
|
||||
if (hCurrentArchive == NULL || hCurrentArchive == INVALID_HANDLE_VALUE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (SFileOpenFileEx (hCurrentArchive, file_in_mpq_name, SFILE_OPEN_FROM_MPQ, &hFile)) {
|
||||
// TODO: figure out stormlib hierachy linking, as this won't always return the newest file, just
|
||||
// a file found...
|
||||
return hFile;
|
||||
}
|
||||
}
|
||||
|
||||
log_error("Failed to open file '%s' in ANY mounted archive. Last Error: %lu", file_in_mpq_name, GetLastError());
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// pass by pointer
|
||||
void get_mpq_file_path(char *buffer, size_t buffer_size) {
|
||||
printf("Enter a file path (full or current dir): ");
|
||||
|
||||
if (fgets(buffer, buffer_size, stdin) == NULL){
|
||||
log_error("Error: Could not read from buffer for mpq_file_path()");
|
||||
return;
|
||||
}
|
||||
|
||||
// convert '\n\r' to null terminating byte '\0'
|
||||
buffer[strcspn(buffer, "\n\r")] = '\0';
|
||||
|
||||
printf("Opening MPQ at %s\n", buffer);
|
||||
}
|
||||
|
||||
// pass by pointer
|
||||
void get_wmo_file_path(char *buffer, size_t buffer_size) {
|
||||
printf("Enter a file path (full or current dir): ");
|
||||
|
||||
if (fgets(buffer, buffer_size, stdin) == NULL){
|
||||
log_error("Error: Could not read from buffer for wmo_file_path()");
|
||||
return;
|
||||
}
|
||||
|
||||
// convert '\n\r' to null terminating byte '\0'
|
||||
buffer[strcspn(buffer, "\n\r")] = '\0';
|
||||
|
||||
printf("Opening WMO at %s\n", buffer);
|
||||
}
|
||||
|
||||
void get_data_dir(char *buffer, size_t buffer_size) {
|
||||
printf("Enter your WoW 3.3.5a Data Directory (ex. World of Warcraft 3.3.5a\\Data): ");
|
||||
|
||||
if (fgets(buffer, buffer_size, stdin) == NULL) {
|
||||
log_error("Could not read from buffer for wmo_file_path()");
|
||||
return;
|
||||
}
|
||||
|
||||
// convert '\n\r' to null terminating byte '\0'
|
||||
buffer[strcspn(buffer, "\n\r")] = '\0';
|
||||
|
||||
}
|
||||
|
||||
void init_archive_manager(ArchiveManager* manager, const char* root_path) {
|
||||
manager->archives = (HANDLE*)malloc(INIT_CAPACITY * sizeof(HANDLE));
|
||||
|
||||
if (manager->archives == NULL) {
|
||||
log_error("Failed to allocate memory for ArchiveManager handles");
|
||||
return;
|
||||
}
|
||||
|
||||
manager->count = 0;
|
||||
manager->capacity = INIT_CAPACITY;
|
||||
|
||||
if (root_path != NULL) {
|
||||
strncpy(manager->root_path, root_path, MAX_PATH - 1);
|
||||
manager->root_path[MAX_PATH - 1] = '\0';
|
||||
} else {
|
||||
manager->root_path[0] = '\0';
|
||||
}
|
||||
|
||||
log_info("ArchiveManager initialized with capacity %zu", manager->capacity);
|
||||
}
|
||||
|
||||
void init_path_list(PathList *list) {
|
||||
list->entries = (ArchiveEntry*)malloc(INIT_CAPACITY * sizeof(ArchiveEntry));
|
||||
list->count = 0;
|
||||
list->capacity = INIT_CAPACITY;
|
||||
if (list->entries == NULL) {
|
||||
log_error("Cannot allocate memory for paths in PathList");
|
||||
}
|
||||
}
|
||||
|
||||
void expand_path_list(PathList* list) {
|
||||
list->capacity *= 2;
|
||||
list->entries = (ArchiveEntry*)realloc(list->entries, list->capacity * sizeof(ArchiveEntry));
|
||||
if (list->entries == NULL) {
|
||||
log_error("Cannot reallocate memory for paths in PathList");
|
||||
}
|
||||
}
|
||||
|
||||
void recursively_scan_directory(const char *current_dir, PathList* list) {
|
||||
char search_path[MAX_PATH];
|
||||
WIN32_FIND_DATA find_data;
|
||||
HANDLE hFind;
|
||||
|
||||
snprintf(search_path, MAX_PATH, "%s\\*.*", current_dir);
|
||||
|
||||
hFind = FindFirstFile(search_path, &find_data);
|
||||
if (hFind == INVALID_HANDLE_VALUE) {
|
||||
log_error("Invalid Handle: %lu", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
if (strcmp(find_data.cFileName, ".") == 0 || strcmp(find_data.cFileName, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char full_path[MAX_PATH];
|
||||
snprintf(full_path, MAX_PATH, "%s\\%s", current_dir, find_data.cFileName);
|
||||
|
||||
if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
recursively_scan_directory(full_path, list);
|
||||
} else {
|
||||
if (strstr(find_data.cFileName, ".MPQ") ||
|
||||
strstr(find_data.cFileName, ".IDX") ||
|
||||
strstr(find_data.cFileName, ".CASC")) {
|
||||
|
||||
// store the path
|
||||
if (list->count >= list->capacity) {
|
||||
expand_path_list(list);
|
||||
}
|
||||
|
||||
ArchiveEntry *new_entry = &list->entries[list->count];
|
||||
|
||||
snprintf(new_entry->path, MAX_PATH, "%s\\%s", current_dir, find_data.cFileName);
|
||||
|
||||
new_entry->load_order_score = assign_score(find_data.cFileName);
|
||||
|
||||
list->count++;
|
||||
|
||||
/*
|
||||
// allocate memory for the string
|
||||
list->entries[list->count] = (char*)malloc(strlen(full_path) + 1);
|
||||
if (list->entries[list->count] != NULL) {
|
||||
strcpy(list->entries[list->count], full_path);
|
||||
list->count++;
|
||||
// log_info("Found Archive: %s", full_path);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
} while (FindNextFile(hFind, &find_data) != 0);
|
||||
|
||||
FindClose(hFind);
|
||||
}
|
||||
|
||||
void free_path_list(PathList* list) {
|
||||
if (list == NULL)
|
||||
return;
|
||||
|
||||
/*
|
||||
for (int i = 0; i < list->count; i++) {
|
||||
if (list->entries[i] != NULL) {
|
||||
free(list->entries[i]);
|
||||
}
|
||||
}*/
|
||||
|
||||
if (list->entries != NULL) {
|
||||
free(list->entries);
|
||||
}
|
||||
}
|
||||
|
||||
int assign_score(const char *filename) {
|
||||
// TODO: Confirm loading order, I believe locales should be last! check titi discord photo
|
||||
if (strstr(filename, "base-enUS.MPQ") != 0) {
|
||||
return 10;
|
||||
}
|
||||
if (strstr(filename, "patch-enUS.MPQ") != 0) {
|
||||
return 20;
|
||||
}
|
||||
if (strstr(filename, "patch.MPQ") != 0) {
|
||||
return 30;
|
||||
}
|
||||
if (strstr(filename, "patch-enUS-2.MPQ") != 0) {
|
||||
return 40;
|
||||
}
|
||||
if (strstr(filename, "patch-enUS-3.MPQ") != 0) {
|
||||
return 50;
|
||||
}
|
||||
if (strstr(filename, "patch-2.MPQ") != 0) {
|
||||
return 60;
|
||||
}
|
||||
if (strstr(filename, "patch-3.MPQ") != 0) {
|
||||
return 70;
|
||||
}
|
||||
if (strstr(filename, "alternate.MPQ") != 0) {
|
||||
return 80;
|
||||
}
|
||||
if (strstr(filename, "expansion.MPQ") != 0) {
|
||||
return 90;
|
||||
}
|
||||
if (strstr(filename, "lichking.MPQ") != 0) {
|
||||
return 100;
|
||||
}
|
||||
if (strstr(filename, "common.MPQ") != 0) {
|
||||
return 110;
|
||||
}
|
||||
if (strstr(filename, "common-2.MPQ") != 0) {
|
||||
return 120;
|
||||
}
|
||||
if (strstr(filename, "locale-enUS.MPQ") != 0) {
|
||||
return 130;
|
||||
}
|
||||
if (strstr(filename, "speech-enUS.MPQ") != 0) {
|
||||
return 140;
|
||||
}
|
||||
if (strstr(filename, "expansion-locale-enUS.MPQ") != 0) {
|
||||
return 150;
|
||||
}
|
||||
if (strstr(filename, "lichking-locale-enUS.MPQ") != 0) {
|
||||
return 160;
|
||||
}
|
||||
if (strstr(filename, "expansion-speech-enUS.MPQ") != 0) {
|
||||
return 170;
|
||||
}
|
||||
if (strstr(filename, "lichking-speech-enUS.MPQ") != 0) {
|
||||
return 180;
|
||||
}
|
||||
|
||||
/*
|
||||
if (strstr(filename, "patch-")) {
|
||||
// get patch number
|
||||
const char *num_str = strstr(filename, "patch-") + 6; // adjusts pointer past '-'
|
||||
int patch_num = atoi(num_str); // TODO; use strtol() instead for error caching?
|
||||
|
||||
return 300 + (patch_num * 100);
|
||||
}*/
|
||||
|
||||
// anything else not recognized gets the lowest score
|
||||
return 1;
|
||||
}
|
||||
|
||||
int archive_comparator(const void *a, const void *b) {
|
||||
const ArchiveEntry *entryA = (const ArchiveEntry *)a;
|
||||
const ArchiveEntry *entryB = (const ArchiveEntry *)b;
|
||||
|
||||
// lowest score (older) to come first, newest overwrites
|
||||
if (entryA->load_order_score < entryB->load_order_score) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (entryA->load_order_score > entryB->load_order_score) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// use lexicographical comparison of the full path if scores are equal
|
||||
return strcmp(entryA->path, entryB->path);
|
||||
}
|
||||
|
||||
void expand_archive_manager(ArchiveManager *manager) {
|
||||
manager->capacity *= 2;
|
||||
manager->archives = (HANDLE*)realloc(manager->archives, manager->capacity * sizeof(HANDLE));
|
||||
|
||||
if (manager->archives == NULL) {
|
||||
log_error("Failed to reallocate ArchiveManager handles");
|
||||
return;
|
||||
}
|
||||
}
|
||||
111
src/util.h
Normal file
111
src/util.h
Normal file
@@ -0,0 +1,111 @@
|
||||
//
|
||||
// Created by Tristan on 11/6/2025.
|
||||
//
|
||||
|
||||
#ifndef UTIL_H
|
||||
#define UTIL_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "StormLib.h"
|
||||
#include "mpq/mpq.h"
|
||||
#include "wmo/wmo_structs.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <direct.h> // _mkdir
|
||||
//#define dwError GetLastError()
|
||||
#define MAKE_DIR(path) _mkdir(path)
|
||||
#else
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#define dwError errno
|
||||
#define MAKE_DIR(path) mkdir(path, 0777)
|
||||
#endif
|
||||
|
||||
#define DEV_MODE
|
||||
#define PATH_SEPARATOR '\\'
|
||||
|
||||
// Root Chunks
|
||||
#define MVER 0x4d564552
|
||||
#define MOTX 0x4d4f5458
|
||||
#define MOMT 0x4d4f4d54
|
||||
#define MOHD 0x4d4f4844
|
||||
#define MOGN 0x4d4f474e
|
||||
#define MOGI 0x4d4f4749
|
||||
#define MOSB 0x4d4f5342
|
||||
#define MOPV 0x4d4f5056
|
||||
#define MOPT 0x4d4f5054
|
||||
#define MOPR 0x4d4f5052
|
||||
#define MOVV 0x4d4f5656
|
||||
#define MOVB 0x4d4f5642
|
||||
#define MOLT 0x4d4f4c54
|
||||
#define MODS 0x4d4f4453
|
||||
#define MODN 0x4d4f444e
|
||||
#define MODD 0x4d4f4444
|
||||
#define MFOG 0x4d464f47
|
||||
|
||||
// Group Chunks
|
||||
#define MOGP 0x4d4f4750
|
||||
#define MOGX 0x4d4f4758
|
||||
#define MOPY 0x4d4f5059
|
||||
#define MPY2 0x4d505932
|
||||
#define MOVI 0x4d4f5649
|
||||
#define MOVT 0x4d4f5654
|
||||
#define MONR 0x4d4f4e52
|
||||
#define MOTV 0x4d4f5456
|
||||
#define MOBA 0x4d4f4241
|
||||
#define MOQG 0x4d4f4747
|
||||
#define MOLR 0x4d4f4c52
|
||||
#define MODR 0x4d4f4452
|
||||
#define MOBN 0x4d4f424e
|
||||
#define MOBR 0x4d4f4252
|
||||
#define MOCV 0x4d4f4356
|
||||
#define MOC2 0x4d4f4332
|
||||
#define MLIQ 0x4d4c4951
|
||||
#define MORI 0x4d4f5249
|
||||
|
||||
#define INIT_CAPACITY 19
|
||||
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint32_t chunk_name; // FourCC
|
||||
uint32_t chunk_size; // in bytes
|
||||
} WMOChunkHeader;
|
||||
|
||||
typedef struct {
|
||||
char *data;
|
||||
DWORD size;
|
||||
} WMOData;
|
||||
|
||||
typedef struct {
|
||||
ArchiveEntry* entries;
|
||||
int count;
|
||||
int capacity;
|
||||
} PathList;
|
||||
|
||||
WMOData load_wmo_data(ArchiveManager *archives, const char *wmoFileName);
|
||||
WMOData load_wmo_data_from_file(FILE **file_ptr_in);
|
||||
// HANDLE get_file_in_mpq(HANDLE hMPQ, const char *file_in_mpq_name);
|
||||
HANDLE get_file_in_archives(ArchiveManager *archives, const char *file_in_mpq_name);
|
||||
uint32_t get_file_size_in_mpq(HANDLE hFileInArchive, const char *file_in_mpq_name);
|
||||
size_t get_wmo_base_name(char *dest, size_t dest_size, const char *full_path);
|
||||
int load_wmo_file(const char *filename, FILE **file_ptr_out);
|
||||
void get_mpq_file_path(char *buffer, size_t buffer_size);
|
||||
void get_wmo_file_path(char *buffer, size_t buffer_size);
|
||||
void parse_wmo_chunks(ArchiveManager *archives, const char *wmo_buffer, DWORD total_size, WMORootData *out_wmo_data,
|
||||
const char *wmo_file_path);
|
||||
void parse_momt_and_extract_textures(ArchiveManager *archives, const WMORootData *wmo_data);
|
||||
void get_data_dir(char *buffer, size_t buffer_size);
|
||||
|
||||
void init_archive_manager (ArchiveManager *manager, const char *root_path);
|
||||
void init_path_list(PathList *list);
|
||||
void expand_path_list(PathList* list);
|
||||
void recursively_scan_directory(const char *current_dir, PathList* list);
|
||||
void free_path_list(PathList* list);
|
||||
int assign_score(const char *filename);
|
||||
int archive_comparator(const void *a, const void *b);
|
||||
void expand_archive_manager(ArchiveManager *manager);
|
||||
|
||||
char *get_file_contents(const char *filename);
|
||||
|
||||
#endif //UTIL_H
|
||||
703
src/wmo/wmo.c
Normal file
703
src/wmo/wmo.c
Normal file
@@ -0,0 +1,703 @@
|
||||
//
|
||||
// Created by Tristan on 11/11/2025.
|
||||
//
|
||||
#include <stdint.h>
|
||||
#include "../util.h"
|
||||
#include "wmo.h"
|
||||
#include "../logger/log.h"
|
||||
#include "../renderer/texture.h"
|
||||
|
||||
void parse_mohd_chunk(const WMORootData *wmo_root_data) {
|
||||
SMOHeader *header = (SMOHeader *)wmo_root_data->mohd_data_ptr;
|
||||
log_info("\t%d groups found in root file", header->nGroups);
|
||||
}
|
||||
|
||||
void get_wmo_group_names(ArchiveManager *archives, WMORootData *wmo_root_data, const char *wmo_file_path) {
|
||||
const SMOGroupInfo *groupInfo = (SMOGroupInfo* )wmo_root_data->mogi_data_ptr;
|
||||
size_t group_count = wmo_root_data->mogi_size / sizeof(SMOGroupInfo);
|
||||
|
||||
wmo_root_data->groups = (WMOGroupData *)calloc(group_count, sizeof(WMOGroupData));
|
||||
if (wmo_root_data->groups == NULL) {
|
||||
log_error("Failed to allocate memory for WMO groups");
|
||||
return;
|
||||
}
|
||||
wmo_root_data->group_count = group_count;
|
||||
|
||||
char wmo_base_name[MAX_PATH];
|
||||
if (get_wmo_base_name(wmo_base_name, MAX_PATH, wmo_file_path) == 0) {
|
||||
log_error("Could not determine WMO base path for group naming");
|
||||
}
|
||||
|
||||
char mpq_path_base[MAX_PATH];
|
||||
strncpy(mpq_path_base, wmo_file_path, MAX_PATH);
|
||||
size_t path_len = strlen(mpq_path_base);
|
||||
if (path_len > 4 && strcmp(mpq_path_base + path_len - 4, ".wmo") == 0) {
|
||||
mpq_path_base[path_len - 4] = '\0'; // strips .wmo
|
||||
} else {
|
||||
log_error("WMO file path missing .wmo extension. Cannot construct group file paths");
|
||||
}
|
||||
|
||||
log_info("Parsing %zu groups from MOGI chunk. Base path: %s", group_count, wmo_base_name);
|
||||
|
||||
if (archives == NULL) {
|
||||
log_error("HANDLE is NULL. Cannot verify WMO group file existence");
|
||||
return;
|
||||
}
|
||||
|
||||
int mpq_check_index = 0;
|
||||
size_t file_index_counter = 0;
|
||||
size_t file_index = 0;
|
||||
|
||||
for (size_t i = 0; i < group_count; i++) {
|
||||
int32_t offset = groupInfo[i].name_offset;
|
||||
|
||||
// Skip materials that dont reference a group
|
||||
if (offset == 0) {
|
||||
log_info("Group [%zu]: No Group referenced (Offset 0)", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// -1 means no name wowdev.wiki/WMO#MOGI
|
||||
if (offset == -1) {
|
||||
log_info("Group [%zu]: No group name referenced (Offset -1)", i);
|
||||
|
||||
}
|
||||
|
||||
if (offset >= wmo_root_data->mogi_size && offset != -1) {
|
||||
log_error("Group [%zu]: Invalid group offset %lu (outside MOGI boundaries).", i, offset);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (archives != NULL) {
|
||||
bool file_found = false;
|
||||
int found_file_index = -1;
|
||||
|
||||
while (mpq_check_index < 100) { // init_capacity is hardcoded, should dynamically check loaded mpqs...
|
||||
// need to pass struct pointer for that tho I think
|
||||
char file_to_check[MAX_PATH];
|
||||
snprintf(file_to_check, MAX_PATH, "%s_%03d.wmo", mpq_path_base, mpq_check_index);
|
||||
|
||||
HANDLE hFile = get_file_in_archives(archives, file_to_check);
|
||||
|
||||
if (hFile != NULL) {
|
||||
SFileCloseFile(hFile);
|
||||
found_file_index = mpq_check_index;
|
||||
file_found = true;
|
||||
mpq_check_index++;
|
||||
break;
|
||||
}
|
||||
|
||||
mpq_check_index++;
|
||||
}
|
||||
|
||||
if (!file_found) {
|
||||
log_error("MOGI Group [%zu] found, but could not find a matching WMO group file in MPQ. Skipping.", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
char group_file_path[MAX_PATH];
|
||||
int result = snprintf(group_file_path, MAX_PATH, "%s_%03d.wmo", mpq_path_base, found_file_index);
|
||||
|
||||
if (result >= MAX_PATH || result < 0) {
|
||||
log_error("Group path construction failed or truncated for index %zu", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
const char *mogn_name = (offset == -1) ? "No name referenced (Offset -1)" : (wmo_root_data->mogn_data_ptr + offset);
|
||||
|
||||
log_info("Group [%zu] file path: %s (MOGN: %s) Mapped to actual file index %03d", i, group_file_path, mogn_name, found_file_index);
|
||||
// if (get_file_in_mpq())
|
||||
|
||||
WMOGroupData group_data = parse_wmo_group_file(archives, group_file_path);
|
||||
|
||||
wmo_root_data->groups[i] = group_data;
|
||||
|
||||
if (wmo_root_data->groups[i].movt_data_ptr != NULL) {
|
||||
log_info("Successfully loaded group %zu: %lu vertices found!", i, wmo_root_data->groups[i].movt_size / sizeof(C3Vector));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void get_mosb_skybox(const WMORootData *wmo_root_data) {
|
||||
if (wmo_root_data->mosb_data_ptr == NULL || wmo_root_data->mosb_size == 0) {
|
||||
log_info("MOSB chunk not found in WMO root");
|
||||
}
|
||||
|
||||
const char *skybox_name = wmo_root_data->mosb_data_ptr;
|
||||
|
||||
log_info("Parsing MOSB chunk...");
|
||||
|
||||
if (*skybox_name != '\0') {
|
||||
log_info(" Skybox name found in MOSB chunk: %s", skybox_name);
|
||||
} else {
|
||||
log_info(" Skybox is not found for WMO. (first byte is 0)");
|
||||
}
|
||||
}
|
||||
|
||||
void parse_mopv_chunk(const WMORootData *wmo_root_data) {
|
||||
if (wmo_root_data->mopv_data_ptr == NULL || wmo_root_data->mopv_size == 0) {
|
||||
log_info("MOPV chunk not found in WMO root");
|
||||
}
|
||||
// Portal vertices, one entry is a float[3], usually 4 * 3 * float per portal (actual number of vertices given in portal entry)
|
||||
// 4 bytes * float(x,y,z) * number of vertices
|
||||
C3Vector *portalVertexList = (C3Vector *)wmo_root_data->mopv_data_ptr;
|
||||
|
||||
size_t portal_vertex_count = wmo_root_data->mopv_size / sizeof(C3Vector);
|
||||
|
||||
log_info("Found MOPV data (portals) with %zu vertices.", portal_vertex_count);
|
||||
|
||||
|
||||
for (size_t i = 0; i < portal_vertex_count; i++) {
|
||||
C3Vector current_vertex = portalVertexList[i];
|
||||
log_info("Vertex %zu: X=%f, Y=%f, Z=%f", i, current_vertex.x, current_vertex.y, current_vertex.z);
|
||||
}
|
||||
}
|
||||
|
||||
void parse_mopt_chunk(const WMORootData *wmo_root_data) {
|
||||
if (wmo_root_data->mopt_data_ptr == NULL || wmo_root_data->mopt_size == 0) {
|
||||
log_info("MOPT chunk not found in WMO root");
|
||||
}
|
||||
|
||||
SMOPortal *portalList = (SMOPortal *)wmo_root_data->mopt_data_ptr;
|
||||
|
||||
size_t portal_list_count = wmo_root_data->mopt_size / sizeof(SMOPortal);
|
||||
|
||||
log_info("Found MOPT data (portal lists) with %zu portals", portal_list_count);
|
||||
|
||||
for (size_t i = 0; i < portal_list_count; i++) {
|
||||
SMOPortal portal = portalList[i];
|
||||
log_info("Portal %zu: startVertex: %u, count: %u, plane: normal(X=%.2f, Y=%.2f, Z=%.2f), distance(%.2f)",
|
||||
i, portal.startVertex, portal.count, portal.plane.normal.x, portal.plane.normal.y, portal.plane.normal.z,
|
||||
portal.plane.distance);
|
||||
}
|
||||
}
|
||||
|
||||
void parse_mopr_chunk(const WMORootData *wmo_root_data) {
|
||||
if (wmo_root_data->mopr_data_ptr == NULL || wmo_root_data->mopr_size == 0) {
|
||||
log_info("MOPR chunk not found in WMO root");
|
||||
return;
|
||||
}
|
||||
|
||||
SMOPortalRef *portalRefList = (SMOPortalRef *)wmo_root_data->mopr_data_ptr;
|
||||
size_t portal_ref_list_count = wmo_root_data->mopr_size / sizeof(SMOPortalRef);
|
||||
|
||||
log_info("Found MOPR data (portal ref lists) with %zu references", portal_ref_list_count);
|
||||
|
||||
/* wmo_root_data->portal_references = (SMOPortalRef *)malloc(portal_ref_list_count * sizeof(SMOPortalRef));
|
||||
if (wmo_root_data->portal_references == NULL) {
|
||||
log_error("Failed to allocate memory for MOPR references.");
|
||||
return; // Handle allocation failure
|
||||
}*/
|
||||
//wmo_root_data->portal_ref_count = portal_ref_list_count;
|
||||
|
||||
//memcpy(wmo_root_data->portal_references, wmo_root_data->mopr_data_ptr, wmo_root_data->mopr_size);
|
||||
|
||||
for (size_t i = 0; i < portal_ref_list_count; i++) {
|
||||
SMOPortalRef portal_ref = portalRefList[i];
|
||||
log_info("Portal reference %zu: portalIndex: %u, groupIndex: %u, side: %d", i, portal_ref.portalIndex,
|
||||
portal_ref.groupIndex, portal_ref.side);
|
||||
}
|
||||
|
||||
// TODO - DOES THE CALLER OR THE FUNCTION FREE MALLOC()?
|
||||
}
|
||||
|
||||
WMOGroupData parse_wmo_group_file(ArchiveManager *archives, const char *group_file_path) {
|
||||
WMOGroupData group_data = {0};
|
||||
|
||||
WMOData file_data = load_wmo_data(archives, group_file_path);
|
||||
if (file_data.data == NULL) {
|
||||
log_error("Failed to load WMO group file: %s", group_file_path);
|
||||
return group_data;
|
||||
}
|
||||
|
||||
group_data.group_file_buffer = file_data.data;
|
||||
group_data.group_file_size = file_data.size;
|
||||
|
||||
const char *current_ptr = file_data.data;
|
||||
const char *end_ptr = file_data.data + file_data.size;
|
||||
|
||||
if (current_ptr + sizeof(WMOChunkHeader) > end_ptr) {
|
||||
log_error("Group file is too small to contain a chunk header");
|
||||
return group_data;
|
||||
}
|
||||
|
||||
const WMOChunkHeader *mver_header = (const WMOChunkHeader *)current_ptr;
|
||||
|
||||
|
||||
if (mver_header->chunk_name != MVER) {
|
||||
log_error("Expected MVER chunk as first chunk, found: %c%c%c%c",
|
||||
(char)(mver_header->chunk_name >> 24), (char)(mver_header->chunk_name >> 16),
|
||||
(char)(mver_header->chunk_name >> 8), (char)(mver_header->chunk_name));
|
||||
return group_data;
|
||||
}
|
||||
|
||||
current_ptr += sizeof(WMOChunkHeader) + mver_header->chunk_size;
|
||||
|
||||
if (current_ptr + sizeof(WMOChunkHeader) > end_ptr) {
|
||||
log_error("Group file ended before MOGP chunk was found");
|
||||
return group_data;
|
||||
}
|
||||
|
||||
const WMOChunkHeader *mogp_header = (const WMOChunkHeader *)current_ptr;
|
||||
const char *data_start = current_ptr + sizeof(WMOChunkHeader);
|
||||
|
||||
if (mogp_header->chunk_name != MOGP) {
|
||||
log_error("Expected MVER chunk as first chunk, found: %c%c%c%c",
|
||||
(char)(mogp_header->chunk_name >> 24), (char)(mogp_header->chunk_name >> 16),
|
||||
(char)(mogp_header->chunk_name >> 8), (char)(mogp_header->chunk_name));
|
||||
return group_data;
|
||||
}
|
||||
|
||||
log_info("Found MOGP chunk (size: %lu bytes) in group file", mogp_header->chunk_size);
|
||||
|
||||
parse_mogp_sub_chunks(data_start, mogp_header->chunk_size, &group_data);
|
||||
|
||||
/*
|
||||
if (file_data.data != NULL) {
|
||||
free(file_data.data);
|
||||
}*/
|
||||
|
||||
return group_data;
|
||||
}
|
||||
|
||||
void parse_moba_chunk(const WMOGroupData *wmo_group_data) {
|
||||
if (wmo_group_data->moba_data_ptr == NULL || wmo_group_data->moba_size == 0) {
|
||||
log_error("Could not find valid MOBA chunk inside WMO Group File");
|
||||
return;
|
||||
}
|
||||
|
||||
const SMOBatch *batches = (const SMOBatch *)wmo_group_data->moba_data_ptr;
|
||||
size_t num_batches = wmo_group_data->moba_size / sizeof(SMOBatch);
|
||||
|
||||
log_info("Found %zu batches in MOBA chunk (Total bytes: %lu)", num_batches, wmo_group_data->moba_size);
|
||||
|
||||
for (size_t i = 0; i < num_batches; i++) {
|
||||
const SMOBatch *batch = &batches[i];
|
||||
log_info("Batch: %zu, Material ID: %u, MOVI Start Index: %u, MOVI Index Count: %u, MOVT Vertex Range: %u to %u",
|
||||
i, batch->material_id, batch->startIndex, batch->count, batch->minIndex, batch->maxIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void parse_motv_chunk(const WMOGroupData *wmo_group_data) {
|
||||
if (wmo_group_data->motv_data_ptr == NULL || wmo_group_data->motv_size == 0) {
|
||||
log_error("Could not find valid MOTV chunk inside WMO Group File");
|
||||
return;
|
||||
}
|
||||
|
||||
const C2Vector *textureVertexList = (const C2Vector *)wmo_group_data->motv_data_ptr;
|
||||
size_t num_texture_vertexs = wmo_group_data->motv_size / sizeof(C2Vector);
|
||||
|
||||
log_info("Found %zu texture vertexs in MOTV chunk (Total bytes: %lu)", num_texture_vertexs, wmo_group_data->motv_size);
|
||||
/*
|
||||
for (size_t i = 0; i < num_texture_vertexs; i++) {
|
||||
float x = textureVertexList[i].x;
|
||||
float y = textureVertexList[i].y;
|
||||
|
||||
log_info("Texture Vertex %zu: (X: %.4f, Y: %.4f)", i, x, y);
|
||||
}*/
|
||||
}
|
||||
|
||||
void parse_molr_chunk(const WMOGroupData *wmo_group_data) {
|
||||
if (wmo_group_data->molr_data_ptr == NULL || wmo_group_data->molr_size == 0) {
|
||||
log_error("Could not find valid MOLR chunk inside WMO Group File");
|
||||
return;
|
||||
}
|
||||
|
||||
const uint16_t *overrideLightRefs = (const uint16_t *)wmo_group_data->molr_data_ptr;
|
||||
size_t num_light_refs = wmo_group_data->molr_size / sizeof(uint16_t);
|
||||
|
||||
log_info("Found %zu light references in MOLR chunk (Total bytes: %lu", num_light_refs, wmo_group_data->molr_size);
|
||||
///*
|
||||
for (size_t i = 0; i < num_light_refs; i++) {
|
||||
log_info("Light Reference %zu: %hu", i, overrideLightRefs[i]);
|
||||
} //*/
|
||||
|
||||
}
|
||||
|
||||
void parse_monr_chunk(const WMOGroupData *wmo_group_data) {
|
||||
if (wmo_group_data->monr_data_ptr == NULL || wmo_group_data->monr_size == 0) {
|
||||
log_error("Could not find valid MONR chunk inside WMO Group File");
|
||||
return;
|
||||
}
|
||||
|
||||
const C3Vector *normalList = (const C3Vector *)wmo_group_data->monr_data_ptr;
|
||||
size_t num_normals = wmo_group_data->monr_size / sizeof(C3Vector);
|
||||
|
||||
log_info("Found %zu normals in MONR chunk (Total bytes: %lu)", num_normals, wmo_group_data->monr_size);
|
||||
|
||||
/*
|
||||
for (size_t i = 0; i < num_normals; i++) {
|
||||
float x = normalList[i].x;
|
||||
float y = normalList[i].y;
|
||||
float z = normalList[i].z;
|
||||
|
||||
log_info("Normal %zu: (X: %.4f, Y: %.4f, Z: %.4f)", i, x, y, z);
|
||||
}*/
|
||||
}
|
||||
|
||||
void parse_movt_chunk(const WMOGroupData *wmo_group_data) {
|
||||
if (wmo_group_data->movt_data_ptr == NULL || wmo_group_data->movt_size == 0) {
|
||||
log_error("Could not find valid MOVT chunk inside WMO Group File");
|
||||
return;
|
||||
}
|
||||
|
||||
const C3Vector *vertexList = (const C3Vector *)wmo_group_data->movt_data_ptr;
|
||||
size_t num_vertexes = wmo_group_data->movt_size / sizeof(C3Vector);
|
||||
|
||||
log_info("Found %zu vertices in MOVT chunk (Total bytes: %lu)", num_vertexes, wmo_group_data->movt_size);
|
||||
|
||||
/*
|
||||
for (size_t i = 0; i < num_vertexs; i++) {
|
||||
float x = vertexList[i].x;
|
||||
float y = vertexList[i].y;
|
||||
float z = vertexList[i].z;
|
||||
|
||||
log_info("Vertex %zu: (X: %.4f, Y: %.4f, Z: %.4f)", i, x, y, z);
|
||||
}*/
|
||||
}
|
||||
|
||||
void parse_movi_chunk(const WMOGroupData *wmo_group_data) {
|
||||
if (wmo_group_data->movi_data_ptr == NULL || wmo_group_data->movi_size == 0) {
|
||||
log_error("Could not find valid MOVI chunk inside WMO Group File");
|
||||
return;
|
||||
}
|
||||
|
||||
const uint16_t *indices = (const uint16_t *)wmo_group_data->movi_data_ptr;
|
||||
size_t num_indices = wmo_group_data->movi_size / sizeof(uint16_t);
|
||||
size_t num_triangles = num_indices / 3;
|
||||
|
||||
log_info("Found %zu triangles from %zu indices", num_triangles, num_indices);
|
||||
|
||||
/*
|
||||
for (size_t i = 0; i < num_indices; i += 3) {
|
||||
uint16_t v1_idx = indices[i + 0];
|
||||
uint16_t v2_idx = indices[i + 1];
|
||||
uint16_t v3_idx = indices[i + 2];
|
||||
|
||||
log_info("Triangle %zu indices: %u, %u, %u", i/3, v1_idx, v2_idx, v3_idx);
|
||||
|
||||
// TODO: export f line for obj file?
|
||||
}*/
|
||||
|
||||
|
||||
}
|
||||
|
||||
void parse_mogp_sub_chunks(const char *mogp_data_buffer, DWORD mogp_data_size, WMOGroupData *out_group_data) {
|
||||
if (mogp_data_size < sizeof(SMOGroupHeader)) {
|
||||
log_error("MOGP chunk size is too small for SMOGroupHeader");
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(&out_group_data->header, mogp_data_buffer, sizeof(SMOGroupHeader));
|
||||
log_info("SMOGroupHeader successfully extracted (size: %zu bytes)", sizeof(SMOGroupHeader));
|
||||
|
||||
const char *current_ptr = mogp_data_buffer + sizeof(SMOGroupHeader);
|
||||
const char *end_ptr = mogp_data_buffer + mogp_data_size;
|
||||
|
||||
log_info("Starting WMO Group file sub-chunk traversal...");
|
||||
|
||||
while (current_ptr < end_ptr) {
|
||||
if (current_ptr + sizeof(WMOChunkHeader) > end_ptr) {
|
||||
log_warn("End of MOGP chunk reached unexpectedly");
|
||||
break;
|
||||
}
|
||||
|
||||
const WMOChunkHeader *header = (const WMOChunkHeader *)current_ptr;
|
||||
const char *data_start = current_ptr + sizeof(WMOChunkHeader);
|
||||
|
||||
switch (header->chunk_name) {
|
||||
case MOGX: // versions >10.0.0.46181
|
||||
out_group_data->mogx_data_ptr = data_start;
|
||||
out_group_data->mogx_size = header->chunk_size;
|
||||
log_info(" -> MOGX Chunk found at offset %td", out_group_data->mogx_data_ptr - mogp_data_buffer);
|
||||
break;
|
||||
case MOPY: // material info for triangles, two bytes per triangle. Size is twice number of triangles in WMO group
|
||||
out_group_data->mopy_data_ptr = data_start;
|
||||
out_group_data->mopy_size = header->chunk_size;
|
||||
log_info(" -> MOPY Chunk found at offset %td", out_group_data->mopy_data_ptr - mogp_data_buffer);
|
||||
break;
|
||||
case MPY2: // versions >10.0.0.46181
|
||||
out_group_data->mpy2_data_ptr = data_start;
|
||||
out_group_data->mpy2_size = header->chunk_size;
|
||||
log_info(" -> MPY2 Chunk found at offset %td", out_group_data->mpy2_data_ptr - mogp_data_buffer);
|
||||
break;
|
||||
case MOVI: // MapObject Vertex Indices
|
||||
out_group_data->movi_data_ptr = data_start;
|
||||
out_group_data->movi_size = header->chunk_size;
|
||||
log_info(" -> MOVI Chunk found at offset %td", out_group_data->movi_data_ptr - mogp_data_buffer);
|
||||
parse_movi_chunk(out_group_data);
|
||||
break;
|
||||
case MOVT:
|
||||
out_group_data->movt_data_ptr = data_start;
|
||||
out_group_data->movt_size = header->chunk_size;
|
||||
log_info(" -> MOVT Chunk found at offset %td", out_group_data->movt_data_ptr - mogp_data_buffer);
|
||||
parse_movt_chunk(out_group_data);
|
||||
break;
|
||||
case MONR:
|
||||
out_group_data->monr_data_ptr = data_start;
|
||||
out_group_data->monr_size = header->chunk_size;
|
||||
log_info(" -> MONR Chunk found at offset %td", out_group_data->monr_data_ptr - mogp_data_buffer);
|
||||
parse_monr_chunk(out_group_data);
|
||||
break;
|
||||
case MOTV:
|
||||
out_group_data->motv_data_ptr = data_start;
|
||||
out_group_data->motv_size = header->chunk_size;
|
||||
log_info(" -> MOTV Chunk found at offset %td", out_group_data->motv_data_ptr - mogp_data_buffer);
|
||||
parse_motv_chunk(out_group_data);
|
||||
break;
|
||||
case MOBA:
|
||||
out_group_data->moba_data_ptr = data_start;
|
||||
out_group_data->moba_size = header->chunk_size;
|
||||
log_info(" -> MOBA Chunk found at offset %td", out_group_data->moba_data_ptr - mogp_data_buffer);
|
||||
log_info("Sizeof SMOBatch: %zu", sizeof(SMOBatch));
|
||||
parse_moba_chunk(out_group_data);
|
||||
break;
|
||||
case MOQG: // versions >10.0.0.46181
|
||||
out_group_data->moqg_data_ptr = data_start;
|
||||
out_group_data->moqg_size = header->chunk_size;
|
||||
log_info(" -> MOQG Chunk found at offset %td", out_group_data->moqg_data_ptr - mogp_data_buffer);
|
||||
break;
|
||||
case MOLR:
|
||||
out_group_data->molr_data_ptr = data_start;
|
||||
out_group_data->molr_size = header->chunk_size;
|
||||
log_info(" -> MOLR Chunk found at offset %td", out_group_data->molr_data_ptr - mogp_data_buffer);
|
||||
parse_molr_chunk(out_group_data);
|
||||
break;
|
||||
case MODR:
|
||||
case MOBN:
|
||||
case MOBR:
|
||||
case MOCV:
|
||||
case MOC2:
|
||||
case MLIQ:
|
||||
case MORI:
|
||||
default:
|
||||
log_info("TODO! %c%c%c%c",
|
||||
(char)(header->chunk_name >> 24),
|
||||
(char)(header->chunk_name >> 16),
|
||||
(char)(header->chunk_name >> 8),
|
||||
(char)(header->chunk_name));
|
||||
break;
|
||||
}
|
||||
|
||||
current_ptr = data_start + header->chunk_size;
|
||||
}
|
||||
}
|
||||
|
||||
void extract_wmo_geometry(const WMORootData *wmo_root_data, FILE *output_file) {
|
||||
if (wmo_root_data->groups == NULL || wmo_root_data->group_count == 0) {
|
||||
log_error("No WMO group data found to extract geometry from");
|
||||
return;
|
||||
}
|
||||
|
||||
if (output_file == NULL) {
|
||||
log_error("Output file handle is NULL. Cannot log geometry details.");
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(output_file, "# WMO Geometry Data Extraction Log\n");
|
||||
fprintf(output_file, "# Root WMO contains %zu groups\n", wmo_root_data->group_count);
|
||||
|
||||
log_info("Starting geometry extraction for %zu WMO groups...", wmo_root_data->group_count);
|
||||
|
||||
if (wmo_root_data->mohd_data_ptr == NULL) {
|
||||
log_error("MOHD data is missing. Cannot calculate global center");
|
||||
return;
|
||||
}
|
||||
|
||||
const SMOHeader *header = (const SMOHeader *)wmo_root_data->mohd_data_ptr;
|
||||
|
||||
float center_x = (header->bounding_box.min[0] + header->bounding_box.max[0]) / 2.0f;
|
||||
float center_y = (header->bounding_box.min[1] + header->bounding_box.max[1]) / 2.0f;
|
||||
float center_z = (header->bounding_box.min[2] + header->bounding_box.max[2]) / 2.0f;
|
||||
|
||||
log_info("Global WMO Center calculated: (%.2f, %.2f, %.2f)", center_x, center_y, center_z);
|
||||
|
||||
size_t cumulative_vertex_count = 0;
|
||||
|
||||
for (size_t i = 0; i < wmo_root_data->group_count; i++) {
|
||||
const WMOGroupData *group = &wmo_root_data->groups[i];
|
||||
|
||||
if (group->movt_data_ptr == NULL) {
|
||||
fprintf(output_file, "g Group_%zu_SKIPPED\n", i);
|
||||
log_info("Group %zu skipped (no geometry data found)", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
fprintf(output_file, "\n# -------------------\n");
|
||||
fprintf(output_file, "g Group_%zu\n", i);
|
||||
|
||||
/* -----------------------------------------------------------------
|
||||
1. SETUP ALL CHUNKS AND OFFSETS
|
||||
----------------------------------------------------------------- */
|
||||
size_t num_vertices = group->movt_size / sizeof(C3Vector);
|
||||
const C3Vector *vertices = (const C3Vector *)group->movt_data_ptr;
|
||||
|
||||
// Pointers for new chunks
|
||||
const C3Vector *normals = (const C3Vector *)group->monr_data_ptr;
|
||||
const C2Vector *tex_coords = (const C2Vector *)group->motv_data_ptr;
|
||||
|
||||
// Offset (already calculated)
|
||||
float offset_x = group->header.boundingBox.min[0];
|
||||
float offset_y = group->header.boundingBox.min[1];
|
||||
float offset_z = group->header.boundingBox.min[2];
|
||||
|
||||
fprintf(output_file, "# Found %zu vertices (MOVT), normals (MONR), and texcoords (MOTV)\n", num_vertices);
|
||||
|
||||
/* -----------------------------------------------------------------
|
||||
2. VERTICES, NORMALS, AND TEXCOORDS (MOVT, MONR, MOTV) - ONE LOOP
|
||||
----------------------------------------------------------------- */
|
||||
for (size_t j = 0; j < num_vertices; j++) {
|
||||
// A. Vertices (v) - Apply offset and coordinate swap
|
||||
float x_wmo = vertices[j].x + offset_x;
|
||||
float y_wmo = vertices[j].y + offset_y;
|
||||
float z_wmo = vertices[j].z + offset_z;
|
||||
|
||||
// WMO -> OBJ Transformation
|
||||
float x_obj = x_wmo;
|
||||
float y_obj = z_wmo;
|
||||
float z_obj = -y_wmo;
|
||||
|
||||
x_obj -= center_x;
|
||||
y_obj -= center_z;
|
||||
z_obj -= center_y;
|
||||
|
||||
fprintf(output_file, "v %.6f %.6f %.6f\n", x_obj, y_obj, z_obj);
|
||||
|
||||
// B. Texture Coordinates (vt) - Apply V-flip
|
||||
float u_wmo = tex_coords[j].x;
|
||||
float v_wmo = tex_coords[j].y;
|
||||
float v_obj = 1.0f - v_wmo; // V-Flip is commonly required
|
||||
|
||||
fprintf(output_file, "vt %.6f %.6f\n", u_wmo, v_obj);
|
||||
|
||||
// C. Normals (vn) - Apply the SAME coordinate swap (NO offset for normals)
|
||||
// Normals are unit vectors and should NOT be translated.
|
||||
float nx_wmo = normals[j].x;
|
||||
float ny_wmo = normals[j].y;
|
||||
float nz_wmo = normals[j].z;
|
||||
|
||||
float nx_obj = nx_wmo;
|
||||
float ny_obj = nz_wmo;
|
||||
float nz_obj = -ny_wmo;
|
||||
|
||||
fprintf(output_file, "vn %.6f %.6f %.6f\n", nx_obj, ny_obj, nz_obj);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------
|
||||
3. FACES (MOVI & MOBA) - Second Loop
|
||||
----------------------------------------------------------------- */
|
||||
const uint16_t *all_indices = (const uint16_t *)group->movi_data_ptr;
|
||||
const SMOBatch *batches = (const SMOBatch *)group->moba_data_ptr;
|
||||
size_t num_batches = group->moba_size / sizeof(SMOBatch);
|
||||
|
||||
fprintf(output_file, "# Found %zu batches (MOBA) for face definitions.\n", num_batches);
|
||||
|
||||
for (size_t b = 0; b < num_batches; b++) {
|
||||
const SMOBatch *batch = &batches[b];
|
||||
|
||||
// Output material information (requires MTL file, but helps organize the OBJ)
|
||||
fprintf(output_file, "\n# Material Group ID: %u\n", batch->material_id);
|
||||
// In a full export, you would write: fprintf(output_file, "usemtl material_%u\n", batch->material_id);
|
||||
|
||||
uint32_t start_index = batch->startIndex;
|
||||
uint32_t index_count = batch->count;
|
||||
|
||||
// Iterate through the indices for this batch, three at a time (triangles)
|
||||
for (uint32_t k = start_index; k < start_index + index_count; k += 3) {
|
||||
|
||||
// Get the local (0-based) vertex indices
|
||||
uint16_t v1_local = all_indices[k + 0];
|
||||
uint16_t v2_local = all_indices[k + 1];
|
||||
uint16_t v3_local = all_indices[k + 2];
|
||||
|
||||
// Apply the GLOBAL offset and the 1-based OBJ requirement
|
||||
size_t v1_obj = (size_t)v1_local + 1 + cumulative_vertex_count;
|
||||
size_t v2_obj = (size_t)v2_local + 1 + cumulative_vertex_count;
|
||||
size_t v3_obj = (size_t)v3_local + 1 + cumulative_vertex_count;
|
||||
|
||||
// Write face line: f v/vt/vn v/vt/vn v/vt/vn
|
||||
// Since MOVT, MOTV, and MONR indices are 1:1, all three indices are the same.
|
||||
fprintf(output_file, "f %zu/%zu/%zu %zu/%zu/%zu %zu/%zu/%zu\n",
|
||||
v1_obj, v1_obj, v1_obj,
|
||||
v2_obj, v2_obj, v2_obj,
|
||||
v3_obj, v3_obj, v3_obj
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
// C. Update Cumulative Count for the NEXT Group
|
||||
// ----------------------------------------------------
|
||||
cumulative_vertex_count += num_vertices;
|
||||
}
|
||||
|
||||
log_info("Geometry data written to output file");
|
||||
}
|
||||
|
||||
// isTransFace: triangles flagged as TRANSITION. They blend lighting from exterior to interior
|
||||
char smopoly_is_trans_face(SMOPolyFlags flags) {
|
||||
return flags.f_unk && (flags.f_detail || flags.f_render);
|
||||
}
|
||||
// isColor: used to determine if the triangle has color/texture data (not just collision)
|
||||
char smopoly_is_color(SMOPolyFlags flags) {
|
||||
return !flags.f_collision;
|
||||
}
|
||||
// isRenderFace: triangles that should be visible
|
||||
char smopoly_is_render_face(SMOPolyFlags flags) {
|
||||
return flags.f_render && !flags.f_detail;
|
||||
}
|
||||
// isCollidable: Triangles that can be physically interacted with
|
||||
char smopoly_is_collidable(SMOPolyFlags flags) {
|
||||
return flags.f_collision || smopoly_is_render_face(flags);
|
||||
}
|
||||
|
||||
|
||||
void wmo_load_textures_to_gpu(ArchiveManager* manager, WMORootData* root) {
|
||||
if (!root->momt_data_ptr || !root->motx_data_ptr)
|
||||
// TODO: error logging
|
||||
return;
|
||||
|
||||
SMOMaterial *materials = (SMOMaterial *)root->momt_data_ptr;
|
||||
int num_materials = root->momt_size / sizeof(SMOMaterial);
|
||||
|
||||
// allocate opengl ID array
|
||||
root->material_textures = (GLuint*)calloc(num_materials, sizeof(GLuint));
|
||||
|
||||
log_info("Loading %d textures to GPU...", num_materials);
|
||||
|
||||
for (int i = 0; i < num_materials; i++) {
|
||||
uint32_t name_offset = materials[i].texture_name_offset;
|
||||
|
||||
/*
|
||||
if (name_offset == 0 || name_offset >= root->motx_size) {
|
||||
log_warn("Material %d has invalid texture offset", i);
|
||||
root->material_textures[i] = 0;
|
||||
continue;
|
||||
}*/
|
||||
|
||||
const char *filename = root->motx_data_ptr + name_offset;
|
||||
|
||||
HANDLE hFile = get_file_in_archives(manager, filename);
|
||||
|
||||
if (hFile) {
|
||||
DWORD fileSize = SFileGetFileSize(hFile, NULL);
|
||||
if (fileSize > 0) {
|
||||
uint8_t* buffer = (uint8_t*)malloc(fileSize);
|
||||
|
||||
DWORD bytesRead = 0;
|
||||
SFileReadFile(hFile, buffer, fileSize, &bytesRead, NULL);
|
||||
|
||||
root->material_textures[i] = texture_load_from_blp_memory(buffer, fileSize);
|
||||
|
||||
free(buffer);
|
||||
}
|
||||
SFileCloseFile(hFile);
|
||||
} else {
|
||||
log_warn("Failed to find texture: %s", filename);
|
||||
root->material_textures[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/wmo/wmo.h
Normal file
26
src/wmo/wmo.h
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Created by Tristan on 11/11/2025.
|
||||
//
|
||||
|
||||
#ifndef WMO_H
|
||||
#define WMO_H
|
||||
|
||||
#include "wmo_structs.h"
|
||||
|
||||
void get_wmo_group_names(HANDLE hMPQ, WMORootData *wmo_root_data, const char *wmo_file_path);
|
||||
void get_mosb_skybox(const WMORootData *wmo_root_data);
|
||||
void parse_mohd_chunk(const WMORootData *wmo_root_data);
|
||||
void parse_mopv_chunk(const WMORootData *wmo_root_data);
|
||||
void parse_mopt_chunk(const WMORootData *wmo_root_data);
|
||||
void parse_mopr_chunk(const WMORootData *wmo_root_data);
|
||||
void parse_mogp_sub_chunks(const char *mogp_data_buffer, DWORD mogp_data_size, WMOGroupData *out_group_data);
|
||||
WMOGroupData parse_wmo_group_file(ArchiveManager *archives, const char *group_file_path);
|
||||
void extract_wmo_geometry(const WMORootData *wmo_root_data, FILE *output_file);
|
||||
void wmo_load_textures_to_gpu(ArchiveManager* manager, WMORootData* root);
|
||||
|
||||
bool smopoly_is_trans_face(SMOPolyFlags flags);
|
||||
bool smopoly_is_color(SMOPolyFlags flags);
|
||||
bool smopoly_is_render_face(SMOPolyFlags flags);
|
||||
bool smopoly_is_collidable(SMOPolyFlags flags);
|
||||
|
||||
#endif //WMO_H
|
||||
297
src/wmo/wmo_structs.h
Normal file
297
src/wmo/wmo_structs.h
Normal file
@@ -0,0 +1,297 @@
|
||||
//
|
||||
// Created by Tristan on 11/11/2025.
|
||||
//
|
||||
|
||||
#ifndef WMO_STRUCTS_H
|
||||
#define WMO_STRUCTS_H
|
||||
#include "StormLib.h"
|
||||
#include "glad/glad.h"
|
||||
|
||||
/* ---------- Enums ---------- */
|
||||
typedef enum
|
||||
{
|
||||
Flag_XAxis = 0x0,
|
||||
Flag_YAxis = 0x1,
|
||||
Flag_ZAxis = 0x2,
|
||||
Flag_AxisMask = 0x3,
|
||||
Flag_Leaf = 0x4,
|
||||
Flag_NoChild = 0xFFFF,
|
||||
} Flags;
|
||||
|
||||
/* ---------- Helper Structs ---------- */
|
||||
|
||||
typedef struct {
|
||||
uint16_t flags; // See above enum (Flags). 4: leaf, 0 for YZ-plane, 1 for XZ-plane, 2 for XY-plane
|
||||
int16_t negChild; // index of bsp child node (right in this array)
|
||||
int16_t posChild;
|
||||
uint16_t nFaces; // num of triangle faces in MOBR
|
||||
uint32_t faceStart; // index of the first triangle index(in MOBR)
|
||||
float planeDist;
|
||||
} CAaBspNode;
|
||||
|
||||
// unions allow access to a single uint32 or component value
|
||||
typedef union {
|
||||
uint32_t raw_value;
|
||||
struct {
|
||||
uint8_t a, r, g, b; // TODO: check endianness
|
||||
} components;
|
||||
} CArgb;
|
||||
|
||||
// Axis-aligned bounding box
|
||||
typedef struct {
|
||||
float min[3];
|
||||
float max[3];
|
||||
} CAabox;
|
||||
|
||||
// TODO: keep C2/C3/C4 vector struct definitions or use the vec2_t/vec3_t/vec4_t structs
|
||||
// potentially could wrap them in a define and just have C2Vector/C3/C4 mean vec2/vec3/vec4 but thats not very ideal I imagine...
|
||||
typedef struct {
|
||||
float x;
|
||||
float y;
|
||||
} C2Vector;
|
||||
|
||||
typedef struct {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
} C3Vector;
|
||||
|
||||
typedef struct {
|
||||
C3Vector normal;
|
||||
float distance;
|
||||
} C4Plane;
|
||||
|
||||
// MOHD
|
||||
typedef struct {
|
||||
uint32_t nTextures; // 0x00: number of textures (BLP files)
|
||||
uint32_t nGroups; // 0x04: number of WMO groups
|
||||
uint32_t nPortals; // 0x08: number of portals
|
||||
uint32_t nLights; // 0x0C: number of lights
|
||||
uint32_t nModels; // 0x10: number of M2 models imported
|
||||
uint32_t nDoodadDefs; // 0x14: number of doodads (M2 instances)
|
||||
uint32_t nDoodadSets; // 0x18: number of doodad sets
|
||||
CArgb amColor; // 0x1C:
|
||||
uint32_t wmoID; // 0x20: Foreign key for WMOAreaTableRec::m_WMOID (column 2 in WMOAreaTable.dbc)
|
||||
CAabox bounding_box; // 0x24:
|
||||
uint32_t flags; // 0x3C: Combined 16-bit flags field
|
||||
uint32_t numLOD; // 0x3E:
|
||||
} SMOHeader;
|
||||
|
||||
typedef struct {
|
||||
uint32_t flags; // 0x00;
|
||||
CAabox bounding_box; // 0x04;
|
||||
int32_t name_offset; // 0x1C;
|
||||
} SMOGroupInfo;
|
||||
|
||||
typedef struct {
|
||||
uint32_t flags; // 0x00: Material Flags
|
||||
uint32_t shader; // 0x04: Shader Index
|
||||
uint32_t blendMode; // 0x08: Blending (EGxBlend)
|
||||
uint32_t texture_name_offset; // 0x0C: Offset into MOTX string block
|
||||
|
||||
uint32_t sidnColor; // 0x10: Emissive color (CImVector)
|
||||
uint32_t frameSidnColor; // 0x14: SIDN emissive color (CImVector)
|
||||
uint32_t texture_2_offset; // 0x18: Secondary texture offset/ID
|
||||
uint32_t diffColor; // 0x1C: Diffuse color (CImVector)
|
||||
uint32_t ground_type; // 0x20: TerrainTypeRec::m_ID
|
||||
|
||||
uint32_t texture_3_offset; // 0x24: Tertiary texture offset/ID
|
||||
uint32_t color_2; // 0x28:
|
||||
uint32_t flags_2; // 0x2C:
|
||||
uint32_t runTimeData[4]; // 0x30: 16 bytes of runtime data
|
||||
} SMOMaterial;
|
||||
|
||||
// MOPT
|
||||
typedef struct {
|
||||
uint16_t startVertex;
|
||||
uint16_t count;
|
||||
C4Plane plane;
|
||||
} SMOPortal;
|
||||
|
||||
// MOPR
|
||||
typedef struct {
|
||||
uint16_t portalIndex; // into MOPT
|
||||
uint16_t groupIndex; // the other one? (MOGI?)
|
||||
int16_t side; // positive or negative
|
||||
uint16_t padding; // filler
|
||||
} SMOPortalRef;
|
||||
|
||||
typedef struct {
|
||||
int16_t x, y, z;
|
||||
} SInt16Vector;
|
||||
|
||||
// MOBA
|
||||
typedef struct {
|
||||
SInt16Vector min_bound; // Part of the bounding box
|
||||
SInt16Vector max_bound; // Part of the bounding box
|
||||
uint32_t startIndex; // 0x0C: index of first face used in MOVI
|
||||
uint16_t count; // 0x10: number of MOVI indices used
|
||||
uint16_t minIndex; // 0x12: index of the first vertex used in MOVT
|
||||
uint16_t maxIndex; // 0x14: index of the last vertex used (batch includes this one)
|
||||
uint8_t flag_unk1; // 0x16:
|
||||
uint8_t material_id; // 0x17 index in MOMT
|
||||
} SMOBatch;
|
||||
|
||||
typedef struct {
|
||||
uint8_t f_unk : 1; // 0x01: Bit 0
|
||||
uint8_t f_noCamCollide : 1; // 0x02: Bit 1
|
||||
uint8_t f_detail : 1; // 0x04: Bit 2
|
||||
uint8_t f_collision : 1; // 0x08: Bit 3 turn off water rippling effects
|
||||
uint8_t f_hint : 1; // 0x10: Bit 4
|
||||
uint8_t f_render : 1; // 0x20: Bit 5
|
||||
uint8_t f_cullObjects : 1; // 0x40: Bit 6
|
||||
uint8_t f_collideHit : 1; // 0x80: Bit 7
|
||||
} SMOPolyFlags;
|
||||
|
||||
typedef struct {
|
||||
SMOPolyFlags flags;
|
||||
uint8_t material_id;
|
||||
} SMOPoly;
|
||||
|
||||
typedef struct {
|
||||
uint32_t bspTree : 1; // 0x01: Bit 0 (MOBN and MOBR chunk)
|
||||
uint32_t lightMap : 1; // 0x02: Bit 1 (MOLM, MOLD)
|
||||
uint32_t vertexColors : 1; // 0x04: Bit 3 (MOCV)
|
||||
uint32_t renderExterior : 1; // 0x08: Bit 4 (SMOGroup::EXTERIOR)
|
||||
uint32_t unused1 : 1; // 0x10: Bit 5
|
||||
uint32_t unused2 : 1; // 0x20: Bit 6
|
||||
uint32_t useExteriorLighting : 1; // 0x40: Bit 7
|
||||
uint32_t unreachable : 1; // 0x80: Bit 8
|
||||
uint32_t useExteriorSky : 1; // 0x100: Bit 9 (for interiors of city in stratholme_past.wmo)
|
||||
uint32_t hasLights : 1; // 0x200: Bit 10 (MOLR)
|
||||
uint32_t hasChunks : 1; // 0x400: Bit 11 (Has MPBV, MPBP, MPBI, MPBG chunks, doesnt actually use tho)
|
||||
uint32_t hasDoodads : 1; // 0x800: Bit 12
|
||||
uint32_t hasWater : 1; // 0x1000: Bit 13 (SMOGroup::LIQUIDSURFACE)
|
||||
uint32_t isIndoor : 1; // 0x2000: Bit 14 (SMOGroup::INTERIOR)
|
||||
uint32_t unsued3 : 1; // 0x4000: Bit 15
|
||||
uint32_t queryMountAllowed : 1; // 0x8000: Bit 16 QueryMountAllowed in pre-WOTLK
|
||||
uint32_t alwaysDraw : 1; // 0x10000: Bit 17 SMOGroup::ALWAYSDRAW -- clear 0x8 after CMapObjGroup::Create() in MOGP and MOGI
|
||||
uint32_t unsued4 : 1; // 0x20000: Bit 18 Has MORI and MORB chunks
|
||||
uint32_t showSkybox : 1; // 0x40000: Bit 19 unset automatically if MOSB not present (TODO)
|
||||
uint32_t isNotWaterButOcean : 1; // 0x80000: Bit 20 LiquidType related see (MLIQ chunk)
|
||||
uint32_t unk1 : 1; // 0x100000: Bit 21
|
||||
uint32_t isMountAllowed : 1; // 0x200000: Bit 22 IsMountAllowed
|
||||
uint32_t unused5 : 1; // 0x400000: Bit 23 Unused
|
||||
uint32_t hasSecondVertexColors : 1; // 0x01000000: Bit 24 (CVERTS2)
|
||||
uint32_t hasSecondTexCoords : 1; // 0x02000000: Bit 25 (TVERTS2)
|
||||
uint32_t isAntiPortal : 1; // 0x04000000: Bit 26 (ANTIPORTAL)
|
||||
uint32_t disableBatchRender : 1; // 0x08000000: Bit 27 (unk)
|
||||
uint32_t unused6 : 1; // 0x10000000: Bit 28 (UNUSED: 20740)
|
||||
uint32_t isExteriorCull : 1; // 0x20000000: Bit 29 (EXTERIOR_CULL)
|
||||
uint32_t hasThirdTexCoords : 1; // 0x40000000: Bit 30 (TVERTS3)
|
||||
uint32_t unk3 : 1; // 0x80000000: Bit 31
|
||||
} SMOGroupFlags;
|
||||
|
||||
typedef struct {
|
||||
uint32_t canCutTerrain : 1; // 0x01: Bit 0
|
||||
uint32_t unk2 : 1; // 0x02: Bit 1
|
||||
uint32_t unk4 : 1; // 0x04: Bit 2
|
||||
uint32_t unk8 : 1; // 0x08: Bit 3
|
||||
uint32_t unk0x10 : 1; // 0x10: Bit 4
|
||||
uint32_t unk0x20 : 1; // 0x20: Bit 5
|
||||
uint32_t padding : 26;
|
||||
} SMOGroupFlags2;
|
||||
|
||||
// TODO: setup flag parsing for flags, as well as for MOGI and WMORootData
|
||||
// MOGP -- contains all other chunks, variables are a header only, actual chunk size way larger
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
uint32_t groupName; // 0x00: offset into MOGN
|
||||
uint32_t descriptiveGroupName; // 0x04: offset into MOGN
|
||||
SMOGroupFlags flags; // 0x08: https://wowdev.wiki/WMO#MOGP_chunk
|
||||
CAabox boundingBox; // 0x0C: same as above, see group flags (same as in corresponding MOGI entry)
|
||||
uint16_t portalStart; // 0x24: index into MOPR
|
||||
uint16_t portalCount; // 0x26: number of MOPR items used after portalStart
|
||||
uint16_t transBatchCount; // 0x28:
|
||||
uint16_t intBatchCount; // 0x2A:
|
||||
uint16_t extBatchCount; // 0x2C:
|
||||
uint16_t padding_or_data; // 0x2E: wiki isn't sure if is padding or data... TODO
|
||||
uint8_t fogIds[4]; // 0x30: ids in MFOG
|
||||
uint32_t groupLiquid; // 0x34: see MLIQ chunk https://wowdev.wiki/WMO#MLIQ_chunk
|
||||
uint32_t wmoGroupID; // 0x38: Foreign key for WMOAreaTableRec::m_WMOGroupID (column 4 in WMOAreaTable.dbc) https://wowdev.wiki/DB/WMOAreaTable
|
||||
SMOGroupFlags2 flags2; // 0x3C:
|
||||
uint32_t unk; // 0x40: Unused 20740?
|
||||
} SMOGroupHeader;
|
||||
|
||||
typedef struct {
|
||||
uint16_t indices;
|
||||
} MOVIChunk;
|
||||
|
||||
typedef struct {
|
||||
SMOGroupHeader header;
|
||||
|
||||
const char *group_file_buffer;
|
||||
DWORD group_file_size;
|
||||
const char *mogx_data_ptr; //
|
||||
DWORD mogx_size;
|
||||
const char *mopy_data_ptr; //
|
||||
DWORD mopy_size;
|
||||
const char *mpy2_data_ptr; // dragonflight... 10.0.0.46181
|
||||
DWORD mpy2_size;
|
||||
const char *movi_data_ptr; // Indices
|
||||
DWORD movi_size;
|
||||
const char *movt_data_ptr; // Vertices
|
||||
DWORD movt_size;
|
||||
const char *monr_data_ptr; // Vertex normals
|
||||
DWORD monr_size;
|
||||
const char *motv_data_ptr; //
|
||||
DWORD motv_size;
|
||||
const char *moba_data_ptr; // Batch list (drawing regions)
|
||||
DWORD moba_size;
|
||||
const char *moqg_data_ptr; //
|
||||
DWORD moqg_size;
|
||||
const char *molr_data_ptr;
|
||||
DWORD molr_size;
|
||||
// TODO: chunks dependent on flag, need to add their ptr's
|
||||
} WMOGroupData;
|
||||
|
||||
|
||||
/* ---------- Root WMO Data Struct ---------- */
|
||||
typedef struct {
|
||||
// Material and Texture Data
|
||||
const char *mver_data_ptr;
|
||||
DWORD mver_size;
|
||||
const char *mohd_data_ptr;
|
||||
DWORD mohd_size;
|
||||
const char *motx_data_ptr;
|
||||
DWORD motx_size;
|
||||
const char *momt_data_ptr;
|
||||
DWORD momt_size;
|
||||
const char *mogn_data_ptr;
|
||||
DWORD mogn_size;
|
||||
const char *mogi_data_ptr;
|
||||
DWORD mogi_size;
|
||||
const char *mosb_data_ptr;
|
||||
DWORD mosb_size;
|
||||
const char *mopv_data_ptr;
|
||||
DWORD mopv_size;
|
||||
const char *mopt_data_ptr;
|
||||
DWORD mopt_size;
|
||||
const char *mopr_data_ptr;
|
||||
DWORD mopr_size;
|
||||
// for MOPR
|
||||
// SMOPortalRef *portal_references;
|
||||
// size_t portal_ref_count;
|
||||
const char *movv_data_ptr;
|
||||
DWORD movv_size;
|
||||
const char *movb_data_ptr;
|
||||
DWORD movb_size;
|
||||
const char *molt_data_ptr;
|
||||
DWORD molt_size;
|
||||
const char *mods_data_ptr;
|
||||
DWORD mods_size;
|
||||
const char *modn_data_ptr;
|
||||
DWORD modn_size;
|
||||
const char *modd_data_ptr;
|
||||
DWORD modd_size;
|
||||
const char *mfog_data_ptr;
|
||||
DWORD mfog_size;
|
||||
GLuint *material_textures; // Array of OpeNGL texture ids corresponding to MOMT entries
|
||||
WMOGroupData *groups;
|
||||
size_t group_count;
|
||||
} WMORootData;
|
||||
|
||||
extern WMORootData out_wmo_data;
|
||||
|
||||
|
||||
#endif //WMO_STRUCTS_H
|
||||
Reference in New Issue
Block a user