Files
BT412/engine/MUNGA/FILEUTIL.cpp
T
arcattackandClaude Opus 4.8 7b7d465e5e Initial commit: bt411 -- standalone Windows BattleTech (Tesla 4.10 port)
Clean, self-contained extraction of the BattleTech-specific work from the
reverse-engineering workspace -- engine + game + content + build, with nothing
from Red Planet or the raw archive dumps. Builds green (Win32) and runs the
single-player drive->animate->target->fire->damage->destroy loop out of the box.

Layout:
  engine/   MUNGA + MUNGA_L4 shared 2007 engine, carrying our BT render/loader
            work (bgfload/L4D3D/L4VIDEO: BSL bit-slice decode, LOD/ground/shadow
            models) + image codec; the minimal rp/ headers the audio HAL needs
  game/     reconstructed BT logic + surviving-original BT source + fwd shims
            + WinMain launcher
  content/  full runtime tree (BTL4.RES, VIDEO/, GAUGE/, AUDIO/, eggs, BTDPL.INI)
  docs/     format specs + reconstruction ledgers
  reference/ raw Ghidra pseudocode (recon source-of-truth) + decomp exporter
  tools/    MP console emulator + map/resource scanners

One top-level CMake builds munga_engine lib + bt410_l4 game lib + btl4.exe.
All paths relativized (186 fwd shims + ~437 CMake abs paths -> repo-relative);
DXSDK is the one external, overridable via -DDXSDK. Verified: builds to a
byte-identical 2.27MB exe and runs combat (TARGET DESTROYED, 0 crashes) against
the bundled content.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 21:03:40 -05:00

826 lines
16 KiB
C++

#include "munga.h"
#include "windows.h"
#pragma hdrstop
#include <direct.h>
#include "fileutil.h"
#include "notation.h"
char *WorkingDirectory = NULL;
//#############################################################################
const char *White_Space = " \t\n";
//#############################################################################
char
*SourceDirectory = NULL,
*ConfigDirectory = NULL,
*MaterialDirectory = NULL,
*TextureDirectory = NULL,
*AnimationDirectory = NULL,
*ProductionDirectory = NULL;
const char
*OutputDirectory = NULL;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
SetSourceDirectory(const char *input_file)
{
Check_Pointer(input_file);
static const char
* const ARTTOOLS_FILENAME = ".arttoolsrc",
* const DIRECTORIES_PAGE = "directories",
* const CFG_DIR_ENTRY = "cfg_dir",
* const MTL_DIR_ENTRY = "mtl_dir",
* const TEX_DIR_ENTRY = "tex_dir",
* const KIN_DIR_ENTRY = "kin_dir",
* const PRD_DIR_ENTRY = "prd_dir";
NotationFile
*tools_info;
char
*cwd;
const char
*cp,
*contents;
//------------------------------------------------
// parse the source directory from the input file
//------------------------------------------------
if ((cp = strrchr(input_file, '/')) == NULL)
{
//---------------------------------------------------
// set source directory to current working directory
//---------------------------------------------------
cwd = _getcwd(NULL, 256);
Verify( cwd );
SourceDirectory = new char[strlen(cwd) + 2];
Register_Pointer(SourceDirectory);
strcpy(SourceDirectory, cwd);
strcat(SourceDirectory, "/");
free(cwd);
}
else
{
//------------------------------------------------------
// set source directory to path specified in input file
//------------------------------------------------------
++cp;
SourceDirectory = new char[cp - input_file + 1];
Register_Pointer(SourceDirectory);
*SourceDirectory = '\0';
strncat(SourceDirectory, input_file, cp - input_file);
}
//-----------------------------------------------------------
// set other directories from tools config file (if present)
//-----------------------------------------------------------
tools_info = new NotationFile(SourceFile(ARTTOOLS_FILENAME));
Register_Object(tools_info);
if (tools_info->GetEntry(DIRECTORIES_PAGE, CFG_DIR_ENTRY, &contents))
{
ConfigDirectory = new char[strlen(contents) + 2];
Register_Pointer(ConfigDirectory);
strcpy(ConfigDirectory, contents);
cp = strchr(contents, '\0');
if (cp > contents && *--cp != '/')
{
strcat(ConfigDirectory, "/");
}
}
else
{
ConfigDirectory = SourceDirectory;
}
if (tools_info->GetEntry(DIRECTORIES_PAGE, MTL_DIR_ENTRY, &contents))
{
MaterialDirectory = new char[strlen(contents) + 2];
Register_Pointer(MaterialDirectory);
strcpy(MaterialDirectory, contents);
cp = strchr(contents, '\0');
if (cp > contents && *--cp != '/')
{
strcat(MaterialDirectory, "/");
}
}
else
{
MaterialDirectory = SourceDirectory;
}
if (tools_info->GetEntry(DIRECTORIES_PAGE, TEX_DIR_ENTRY, &contents))
{
TextureDirectory = new char[strlen(contents) + 2];
Register_Pointer(TextureDirectory);
strcpy(TextureDirectory, contents);
cp = strchr(contents, '\0');
if (cp > contents && *--cp != '/')
{
strcat(TextureDirectory, "/");
}
}
else
{
TextureDirectory = MaterialDirectory; // this one is different!
}
if (tools_info->GetEntry(DIRECTORIES_PAGE, KIN_DIR_ENTRY, &contents))
{
AnimationDirectory = new char[strlen(contents) + 2];
Register_Pointer(AnimationDirectory);
strcpy(AnimationDirectory, contents);
cp = strchr(contents, '\0');
if (cp > contents && *--cp != '/')
{
strcat(AnimationDirectory, "/");
}
}
else
{
AnimationDirectory = SourceDirectory;
}
if (tools_info->GetEntry(DIRECTORIES_PAGE, PRD_DIR_ENTRY, &contents))
{
ProductionDirectory = new char[strlen(contents) + 2];
Register_Pointer(ProductionDirectory);
strcpy(ProductionDirectory, contents);
cp = strchr(contents, '\0');
if (cp > contents && *--cp != '/')
{
strcat(ProductionDirectory, "/");
}
}
else
{
ProductionDirectory = SourceDirectory;
}
OutputDirectory = SourceDirectory;
Unregister_Object(tools_info);
delete tools_info;
return;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
SetOutputDirectory(const char *directory)
{
Check_Pointer(directory);
OutputDirectory = directory;
return;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
const char*
GetSourceDirectory()
{
return SourceDirectory;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
const char*
GetConfigDirectory()
{
return ConfigDirectory;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
const char*
GetMaterialDirectory()
{
return MaterialDirectory;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
const char*
GetTextureDirectory()
{
return TextureDirectory;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
const char*
GetAnimationDirectory()
{
return AnimationDirectory;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
const char*
GetProductionDirectory()
{
return ProductionDirectory;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
const char*
GetOutputDirectory()
{
return OutputDirectory;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
ClearDirectories()
{
//do not clear output directory
if (ProductionDirectory && ProductionDirectory != SourceDirectory)
{
Unregister_Pointer(ProductionDirectory);
delete ProductionDirectory;
}
if (AnimationDirectory && AnimationDirectory != SourceDirectory)
{
Unregister_Pointer(AnimationDirectory);
delete AnimationDirectory;
}
if (TextureDirectory && TextureDirectory != MaterialDirectory) //different!
{
Unregister_Pointer(TextureDirectory);
delete TextureDirectory;
}
if (MaterialDirectory && MaterialDirectory != SourceDirectory)
{
Unregister_Pointer(MaterialDirectory);
delete MaterialDirectory;
}
if (ConfigDirectory && ConfigDirectory != SourceDirectory)
{
Unregister_Pointer(ConfigDirectory);
delete ConfigDirectory;
}
if (SourceDirectory)
{
Unregister_Pointer(SourceDirectory);
delete SourceDirectory;
}
return;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
const char*
DirectoryPlusFilename(
const char *directory,
const char *filename,
const char *extension
)
{
//do not check directory here
Check_Pointer(filename);
//do not check extension here
static char
buffer[512];
int
length;
if (!directory) { directory = "\0"; }
Check_Pointer(directory);
length = strlen(directory);
if (extension)
{
Check_Pointer(extension);
length += strlen(StripExtension(StripDirectory(filename)));
length += *extension == '.'?0:1;
length += strlen(extension);
}
else
{
length += strlen(StripDirectory(filename));
}
if (length >= ELEMENTS(buffer))
{
DEBUG_STREAM <<
": Program Error - buffer[] too small in DirectoryPlusFilename().\n" << std::flush;
PostQuitMessage(AbortExitCodeID);
}
strcpy(buffer, directory);
if (extension)
{
strcat(buffer, StripExtension(StripDirectory(filename)));
if (*extension != '.')
{
strcat(buffer, ".");
}
strcat(buffer, extension);
}
else
{
strcat(buffer, StripDirectory(filename));
}
return buffer;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
const char*
SourceFile(
const char *filename,
const char *extension
)
{
return DirectoryPlusFilename(SourceDirectory, filename, extension);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
const char*
ConfigFile(
const char *filename,
const char *extension
)
{
return DirectoryPlusFilename(ConfigDirectory, filename, extension);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
const char*
MaterialFile(
const char *filename,
const char *extension
)
{
return DirectoryPlusFilename(MaterialDirectory, filename, extension);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
const char*
TextureFile(
const char *filename,
const char *extension
)
{
return DirectoryPlusFilename(TextureDirectory, filename, extension);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
const char*
AnimationFile(
const char *filename,
const char *extension
)
{
return DirectoryPlusFilename(AnimationDirectory, filename, extension);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
const char*
OutputFile(
const char *filename,
const char *extension
)
{
return DirectoryPlusFilename(OutputDirectory, filename, extension);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
const char*
StripDirectory(const char *filename)
{
Check_Pointer(filename);
if (DirectoryIsSpecified(filename))
{
const char
*p;
//--------------------------------------
// parse the filename from the filepath
//--------------------------------------
p = strchr(filename, '\0');
Verify( p );
while (*p != '\\' && p != filename)
{
--p;
}
if (*p == '\\')
{
++p;
}
filename = p;
}
return filename;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
const char*
StripExtension(const char *filename)
{
Check_Pointer(filename);
static char
buffer[512];
char
*p;
if (strlen(filename) >= sizeof(buffer))
{
//DEBUG_STREAM << ProgName <<
// ": Program Error - buffer[] too small in StripExtension()." << std::endl;
DEBUG_STREAM <<
": Program Error - buffer[] too small in StripExtension()." << std::endl << std::flush;
PostQuitMessage(AbortExitCodeID);
}
strcpy(buffer, filename);
//------------------------------------
// remove the extension from filename
//------------------------------------
p = strchr(buffer, '\0');
Verify( p );
while (*p != '.' && p != buffer)
{
--p;
}
if (*p == '.')
{
*p = '\0';
}
return buffer;
}
//#############################################################################
char*
Ind(int level)
{
const int buf_len = 128;
static char buf[buf_len];
//---------------------------------------------------------
// Return string of spaces used to indent an output stream
//---------------------------------------------------------
buf[0] = '\0';
if (level > 63) level = 63; // floor((buf_len - 1)/number of spaces per indent)
for (; level > 0; --level)
{
strcat(buf, " ");
}
return buf;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical GetLine(std::istream &stream, char *buffer, int size_of_buffer)
{
static char temp_buffer[512];
//-------------------------------------------------------------
// read next line(s) from input stream automatically resolving
// lines with a continuation character '\' at the end
//-------------------------------------------------------------
if (stream.getline(buffer, size_of_buffer))
{
char *p;
int i;
while (True)
{
if ((i = strlen(buffer)) > (size_of_buffer - 2))
{
//DEBUG_STREAM << ProgName << ": ERROR - Input line too long." << std::endl << std::flush;
DEBUG_STREAM << ": ERROR - Input line too long." << std::endl << std::flush;
break;
}
p = buffer + i;
while (p-- > buffer && isspace(*p)); // find last non-blank character
if (p >= buffer && *p == '\\')
{
if (stream.getline(temp_buffer, sizeof(temp_buffer)))
{
*p = ' ';
++p;
strncat(p, temp_buffer, size_of_buffer - (p - buffer));
}
else
{
break;
}
}
else
{
break;
}
}
return True;
}
else
{
*buffer = '\0';
return False;
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#if 0
char*
stripname(char filename[])
{
// Doesn't work
char
file2[200];
char
*filetosendback;
int
wordlength,
count,
countbackward,
length;
length = strlen(filename);
count = 0;
countbackward = length-1;
while (filename[countbackward-1] != '\\')
{
--countbackward;
}
wordlength = length - countbackward;
for (count = 0; count < wordlength; ++count)
{
file2[count] = filename[countbackward];
++countbackward;
}
file2[count] = '\0';
filetosendback = strdup(file2);
return filetosendback;
}
#endif
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Scalar
D2R(float degrees)
{
return degrees*(3.14159259f/180.0f);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
char*
MakeWorkingDirectory(
const char *file
)
{
char *directory;
const char *p;
int size;
p = file;
for (int i = (strlen(file) - 1 ); i >= 0; --i)
{
if ( (*p == '/') || (*p == '\\') )
{
size = strlen(file) - i + 1;
directory = new char[size];
strncpy(directory, file, size);
return directory;
}
++p;
}
//
// Else return local directory
//
#define BUFFER_SIZE (256)
char buffer[BUFFER_SIZE];
getcwd(buffer, BUFFER_SIZE);
#if 0
directory = strdup(buffer);
strcat(directory, "\\"); // ECH 1/29/96 - This is an overwrite
#else
Str_Cat(buffer, "\\", BUFFER_SIZE);
size = strlen(buffer) + 1;
directory = new char[size];
Str_Copy(directory, buffer, size);
#endif
//
// This is "\\" for DOS
//
return directory;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Logical
DirectoryIsSpecified(
const char *file
)
{
return strchr(file,'/') || strchr(file,':') || strchr(file,'\\');
#if 0
const char *p;
p = file;
for (int i = 0; i < strlen(file); ++i)
{
if ( (*p == '/') || (*p == '\\') )
{
return True;
}
++p;
}
return False;
#endif
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
char*
MakePathedFilename(
const char *directory,
const char *name
)
{
char *filename;
if (DirectoryIsSpecified(name))
{
filename = new char[ strlen(name) + 1];
strcpy(filename, name);
return filename;
}
if (directory == NULL)
{
filename = new char[strlen(name) + 1];
strcpy(filename, name);
}
else
{
filename = new char[strlen(directory) + strlen(name) + 1];
strcpy(filename, directory);
strcat(filename, name);
}
return filename;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void OpenOutput(std::ofstream &stream, const char *directory, const char *filename)
{
//do not check directory here
Check_Pointer(filename);
char
*file_to_open;
if (DirectoryIsSpecified(filename) || directory == NULL)
{
file_to_open = new char[strlen(filename) + 1];
Register_Pointer(file_to_open);
strcpy(file_to_open, filename);
}
else
{
Check_Pointer(directory);
file_to_open = new char[strlen(directory) + strlen(filename) + 1];
Register_Pointer(file_to_open);
strcpy(file_to_open, directory);
strcat(file_to_open, filename);
}
stream.open(file_to_open);
Unregister_Pointer(file_to_open);
delete file_to_open;
return;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void OpenInput(std::ifstream &stream, char *directory, const char *filename)
{
char tempfile[100];
if (DirectoryIsSpecified(filename))
{
strcpy(tempfile, filename);
}
else
{
if (directory != NULL)
{
strcpy(tempfile, directory);
strcat(tempfile, filename);
}
else
{
strcpy(tempfile, filename);
}
}
stream.open(tempfile);
return;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Logical
LabOnly(
NotationFile
*notation_file
)
{
if (notation_file->PageExists("LAB_ONLY"))
{
std::cout << "WARNING: LAB_ONLY" << std::endl;
return True;
}
else
{
return False;
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void
AddLabOnly(
NotationFile
*notation_file
)
{
if (!notation_file->PageExists("LAB_ONLY"))
{
notation_file->AppendEntry("LAB_ONLY", NULL, NULL);
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Logical
DoesNotationFileExist(
NotationFile
*notation_file
)
{
Check(notation_file);
Check_Pointer(notation_file->GetFileName());
std::ifstream notation_file_stream(notation_file->GetFileName());
return (notation_file_stream) ? True : False;
}
//=============================================================================