559 lines
12 KiB
C++
559 lines
12 KiB
C++
//
|
|
// Half-Life Model Viewer (c) 1999 by Mete Ciragan
|
|
//
|
|
// file: pakviewer.cpp
|
|
// last modified: May 04 1999, Mete Ciragan
|
|
// copyright: The programs and associated files contained in this
|
|
// distribution were developed by Mete Ciragan. The programs
|
|
// are not in the public domain, but they are freely
|
|
// distributable without licensing fees. These programs are
|
|
// provided without guarantee or warrantee expressed or
|
|
// implied.
|
|
//
|
|
// version: 1.2
|
|
//
|
|
// email: mete@swissquake.ch
|
|
// web: http://www.swissquake.ch/chumbalum-soft/
|
|
//
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <mx.h>
|
|
#include "pakviewer.h"
|
|
#include "mdlviewer.h"
|
|
#include "GlWindow.h"
|
|
#include "StudioModel.h"
|
|
#include "ControlPanel.h"
|
|
#include "FileAssociation.h"
|
|
|
|
|
|
|
|
int
|
|
pak_ExtractFile (const char *pakFile, const char *lumpName, char *outFile)
|
|
{
|
|
FILE *file = fopen (pakFile, "rb");
|
|
if (!file)
|
|
return 0;
|
|
|
|
int ident, dirofs, dirlen;
|
|
|
|
fread (&ident, sizeof (int), 1, file);
|
|
if (ident != (int) (('K' << 24) + ('C' << 16) + ('A' << 8) + 'P'))
|
|
{
|
|
fclose (file);
|
|
return 0;
|
|
}
|
|
|
|
fread (&dirofs, sizeof (int), 1, file);
|
|
fread (&dirlen, sizeof (int), 1, file);
|
|
|
|
fseek (file, dirofs, SEEK_SET);
|
|
int numLumps = dirlen / 64;
|
|
|
|
for (int i = 0; i < numLumps; i++)
|
|
{
|
|
char name[56];
|
|
int filepos, filelen;
|
|
|
|
fread (name, 56, 1, file);
|
|
fread (&filepos, sizeof (int), 1, file);
|
|
fread (&filelen, sizeof (int), 1, file);
|
|
|
|
if (!mx_strcasecmp (name, lumpName))
|
|
{
|
|
FILE *out = fopen (outFile, "wb");
|
|
if (!out)
|
|
{
|
|
fclose (file);
|
|
return 0;
|
|
}
|
|
|
|
fseek (file, filepos, SEEK_SET);
|
|
|
|
while (filelen--)
|
|
fputc (fgetc (file), out);
|
|
|
|
fclose (out);
|
|
fclose (file);
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
fclose (file);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
PAKViewer::PAKViewer (mxWindow *window)
|
|
: mxWindow (window, 0, 0, 0, 0, "", mxWindow::Normal)
|
|
{
|
|
strcpy (d_pakFile, "");
|
|
strcpy (d_currLumpName, "");
|
|
|
|
tvPAK = new mxTreeView (this, 0, 0, 0, 0, IDC_PAKVIEWER);
|
|
pmMenu = new mxPopupMenu ();
|
|
pmMenu->add ("Load Model", 1);
|
|
pmMenu->addSeparator ();
|
|
pmMenu->add ("Load Background", 2);
|
|
pmMenu->add ("Load Ground", 3);
|
|
pmMenu->addSeparator ();
|
|
pmMenu->add ("Play Sound", 4);
|
|
pmMenu->addSeparator ();
|
|
pmMenu->add ("Extract File...", 5);
|
|
setLoadEntirePAK (true);
|
|
|
|
setVisible (false);
|
|
}
|
|
|
|
|
|
|
|
PAKViewer::~PAKViewer ()
|
|
{
|
|
tvPAK->remove (0);
|
|
closePAKFile ();
|
|
}
|
|
|
|
|
|
|
|
void
|
|
_makeTempFileName (char *str, const char *suffix)
|
|
{
|
|
strcpy (str, mx_gettemppath ());
|
|
|
|
strcat (str, "/hltempmodel");
|
|
strcat (str, suffix);
|
|
}
|
|
|
|
|
|
|
|
int
|
|
PAKViewer::handleEvent (mxEvent *event)
|
|
{
|
|
switch (event->event)
|
|
{
|
|
case mxEvent::Action:
|
|
{
|
|
switch (event->action)
|
|
{
|
|
case IDC_PAKVIEWER: // tvPAK
|
|
if (event->flags & mxEvent::RightClicked)
|
|
{
|
|
pmMenu->setEnabled (1, strstr (d_currLumpName, ".mdl") != 0);
|
|
pmMenu->setEnabled (2, strstr (d_currLumpName, ".tga") != 0 || strstr (d_currLumpName, ".bmp"));
|
|
pmMenu->setEnabled (3, strstr (d_currLumpName, ".tga") != 0 || strstr (d_currLumpName, ".bmp"));
|
|
pmMenu->setEnabled (4, strstr (d_currLumpName, ".wav") != 0);
|
|
int ret = pmMenu->popup (tvPAK, event->x, event->y);
|
|
switch (ret)
|
|
{
|
|
case 1:
|
|
OnLoadModel ();
|
|
break;
|
|
|
|
case 2:
|
|
OnLoadTexture (TEXTURE_BACKGROUND);
|
|
break;
|
|
|
|
case 3:
|
|
OnLoadTexture (TEXTURE_GROUND);
|
|
break;
|
|
|
|
case 4:
|
|
OnPlaySound ();
|
|
break;
|
|
|
|
case 5:
|
|
OnExtract ();
|
|
break;
|
|
}
|
|
}
|
|
else if (event->flags & mxEvent::DoubleClicked)
|
|
{
|
|
OnPAKViewer ();
|
|
char e[16];
|
|
|
|
strncpy (e, mx_getextension (d_currLumpName), 16);
|
|
int mode = g_FileAssociation->getMode (&e[1]);
|
|
if (mode == -1)
|
|
return 1;
|
|
|
|
char *program = g_FileAssociation->getProgram (&e[1]);
|
|
|
|
#ifdef WIN32
|
|
if (mode == 0)
|
|
{
|
|
char str[256];
|
|
_makeTempFileName (str, e);
|
|
if (!pak_ExtractFile (d_pakFile, d_currLumpName, str))
|
|
mxMessageBox (this, "Error extracting from PAK file.", g_appTitle, MX_MB_OK | MX_MB_ERROR);
|
|
else
|
|
{
|
|
if (program)
|
|
{
|
|
char path[256];
|
|
strcpy (path, program);
|
|
strcat (path, " ");
|
|
strcat (path, str);
|
|
if ((int) WinExec (path, SW_SHOW) <= 32)
|
|
mxMessageBox (this, "Error executing specified program.", g_appTitle, MX_MB_OK | MX_MB_ERROR);
|
|
}
|
|
}
|
|
}
|
|
|
|
// associated program
|
|
else if (mode == 1)
|
|
{
|
|
char str[256];
|
|
_makeTempFileName (str, e);
|
|
if (!pak_ExtractFile (d_pakFile, d_currLumpName, str))
|
|
mxMessageBox (this, "Error extracting from PAK file.", g_appTitle, MX_MB_OK | MX_MB_ERROR);
|
|
else
|
|
if ((int) ShellExecute ((HWND) getHandle (), "open", str, 0, 0, SW_SHOW) <= 32)
|
|
mxMessageBox (this, "Error executing document with associated program.", g_appTitle, MX_MB_OK | MX_MB_ERROR);
|
|
}
|
|
|
|
// HLMV default
|
|
else
|
|
#endif
|
|
if (mode == 2)
|
|
{
|
|
if (!mx_strcasecmp (e, ".mdl"))
|
|
OnLoadModel ();
|
|
|
|
else if (!mx_strcasecmp (e, ".tga") || !mx_strcasecmp (e, ".bmp"))
|
|
OnLoadTexture (TEXTURE_BACKGROUND);
|
|
|
|
else if (!mx_strcasecmp (e, ".wav"))
|
|
OnPlaySound ();
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return OnPAKViewer ();
|
|
} // event->action
|
|
} // mxEvent::Action
|
|
break;
|
|
|
|
case mxEvent::Size:
|
|
{
|
|
tvPAK->setBounds (0, 0, event->width, event->height);
|
|
} // mxEvent::Size
|
|
break;
|
|
|
|
} // event->event
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
int
|
|
PAKViewer::OnPAKViewer ()
|
|
{
|
|
mxTreeViewItem *tvi = tvPAK->getSelectedItem ();
|
|
if (tvi)
|
|
{
|
|
strcpy (d_currLumpName, tvPAK->getLabel (tvi));
|
|
|
|
// find the full lump name
|
|
mxTreeViewItem *tviParent = tvPAK->getParent (tvi);
|
|
char tmp[128];
|
|
while (tviParent)
|
|
{
|
|
strcpy (tmp, d_currLumpName);
|
|
strcpy (d_currLumpName, tvPAK->getLabel (tviParent));
|
|
strcat (d_currLumpName, "/");
|
|
strcat (d_currLumpName, tmp);
|
|
tviParent = tvPAK->getParent (tviParent);
|
|
}
|
|
|
|
if (!d_loadEntirePAK)
|
|
{
|
|
// finally insert "models/"
|
|
strcpy (tmp, d_currLumpName);
|
|
strcpy (d_currLumpName, "models/");
|
|
strcat (d_currLumpName, tmp);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
int
|
|
PAKViewer::OnLoadModel ()
|
|
{
|
|
static char str2[256];
|
|
char suffix[16];
|
|
|
|
strcpy (suffix, ".mdl");
|
|
_makeTempFileName (str2, suffix);
|
|
|
|
if (!pak_ExtractFile (d_pakFile, d_currLumpName, str2))
|
|
{
|
|
mxMessageBox (this, "Error extracting from PAK file.", g_appTitle, MX_MB_OK | MX_MB_ERROR);
|
|
return 1;
|
|
}
|
|
|
|
g_studioModel.FreeModel ();
|
|
studiohdr_t *hdr = g_studioModel.LoadModel (str2);
|
|
if (!hdr)
|
|
{
|
|
// mxMessageBox (this, "Error reading model header.", g_appTitle, MX_MB_OK | MX_MB_ERROR);
|
|
return 1;
|
|
}
|
|
|
|
if (hdr->numtextures == 0)
|
|
{
|
|
char texturename[256];
|
|
|
|
strcpy( texturename, d_currLumpName );
|
|
strcpy( &texturename[strlen(texturename) - 4], "T.mdl" );
|
|
|
|
strcpy (suffix, "T.mdl");
|
|
_makeTempFileName (str2, suffix);
|
|
|
|
if (!pak_ExtractFile (d_pakFile, texturename, str2))
|
|
{
|
|
g_studioModel.FreeModel ();
|
|
mxMessageBox (this, "Error extracting from PAK file 1.", g_appTitle, MX_MB_OK | MX_MB_ERROR);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (hdr->numseqgroups > 1)
|
|
{
|
|
for (int i = 1; i < hdr->numseqgroups; i++)
|
|
{
|
|
char seqgroupname[256];
|
|
|
|
strcpy( seqgroupname, d_currLumpName );
|
|
sprintf( &seqgroupname[strlen(seqgroupname) - 4], "%02d.mdl", i );
|
|
|
|
sprintf (suffix, "%02d.mdl", i);
|
|
_makeTempFileName (str2, suffix);
|
|
|
|
if (!pak_ExtractFile (d_pakFile, seqgroupname, str2))
|
|
{
|
|
g_studioModel.FreeModel ();
|
|
mxMessageBox (this, "Error extracting from PAK file 2.", g_appTitle, MX_MB_OK | MX_MB_ERROR);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
g_studioModel.FreeModel ();
|
|
|
|
strcpy (suffix, ".mdl");
|
|
_makeTempFileName (str2, suffix);
|
|
g_ControlPanel->loadModel (str2);
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
int
|
|
PAKViewer::OnLoadTexture (int pos)
|
|
{
|
|
static char str2[256];
|
|
char suffix[16] = "";
|
|
|
|
if (strstr (d_currLumpName, ".tga"))
|
|
sprintf (suffix, "%d%s", pos, ".tga");
|
|
else if (strstr (d_currLumpName, ".bmp"))
|
|
sprintf (suffix, "%d%s", pos, ".bmp");
|
|
|
|
_makeTempFileName (str2, suffix);
|
|
|
|
if (!pak_ExtractFile (d_pakFile, d_currLumpName, str2))
|
|
{
|
|
mxMessageBox (this, "Error extracting from PAK file.", g_appTitle, MX_MB_OK | MX_MB_ERROR);
|
|
return 1;
|
|
}
|
|
|
|
if (g_MDLViewer->getGlWindow ()->loadTexture (str2, pos))
|
|
{
|
|
if (pos == TEXTURE_BACKGROUND)
|
|
g_ControlPanel->setShowBackground (true);
|
|
else
|
|
g_ControlPanel->setShowGround (true);
|
|
}
|
|
else
|
|
mxMessageBox (this, "Error loading texture.", g_appTitle, MX_MB_OK | MX_MB_ERROR);
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
int
|
|
PAKViewer::OnPlaySound ()
|
|
{
|
|
#ifdef WIN32
|
|
static char str2[256];
|
|
char suffix[16] = "";
|
|
|
|
// stop any playing sound
|
|
PlaySound (0, 0, SND_FILENAME | SND_ASYNC);
|
|
|
|
if (strstr (d_currLumpName, ".wav"))
|
|
sprintf (suffix, "%d%s", 44, ".wav");
|
|
|
|
_makeTempFileName (str2, suffix);
|
|
|
|
if (!pak_ExtractFile (d_pakFile, d_currLumpName, str2))
|
|
{
|
|
mxMessageBox (this, "Error extracting from PAK file.", g_appTitle, MX_MB_OK | MX_MB_ERROR);
|
|
return 1;
|
|
}
|
|
|
|
PlaySound (str2, 0, SND_FILENAME | SND_ASYNC);
|
|
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
int
|
|
PAKViewer::OnExtract ()
|
|
{
|
|
char *ptr = (char *) mxGetSaveFileName (this, "", "*.*");
|
|
if (ptr)
|
|
{
|
|
if (!pak_ExtractFile (d_pakFile, d_currLumpName, ptr))
|
|
mxMessageBox (this, "Error extracting from PAK file.", g_appTitle, MX_MB_OK | MX_MB_ERROR);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
int
|
|
_compare(const void *arg1, const void *arg2)
|
|
{
|
|
if (strchr ((char *) arg1, '/') && !strchr ((char *) arg2, '/'))
|
|
return -1;
|
|
|
|
else if (!strchr ((char *) arg1, '/') && strchr ((char *) arg2, '/'))
|
|
return 1;
|
|
|
|
else
|
|
return strcmp ((char *) arg1, (char *) arg2);
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
PAKViewer::openPAKFile (const char *pakFile)
|
|
{
|
|
FILE *file = fopen (pakFile, "rb");
|
|
if (!file)
|
|
return false;
|
|
|
|
int ident, dirofs, dirlen;
|
|
|
|
// check for id
|
|
fread (&ident, sizeof (int), 1, file);
|
|
if (ident != (int) (('K' << 24) + ('C' << 16) + ('A' << 8) + 'P'))
|
|
{
|
|
fclose (file);
|
|
return false;
|
|
}
|
|
|
|
// load lumps
|
|
fread (&dirofs, sizeof (int), 1, file);
|
|
fread (&dirlen, sizeof (int), 1, file);
|
|
int numLumps = dirlen / 64;
|
|
|
|
fseek (file, dirofs, SEEK_SET);
|
|
lump_t *lumps = new lump_t[numLumps];
|
|
if (!lumps)
|
|
{
|
|
fclose (file);
|
|
return false;
|
|
}
|
|
|
|
fread (lumps, sizeof (lump_t), numLumps, file);
|
|
fclose (file);
|
|
|
|
qsort (lumps, numLumps, sizeof (lump_t), _compare);
|
|
|
|
// save pakFile for later
|
|
strcpy (d_pakFile, pakFile);
|
|
|
|
tvPAK->remove (0);
|
|
|
|
char namestack[32][32];
|
|
mxTreeViewItem *tvistack[32];
|
|
for (int k = 0; k < 32; k++)
|
|
{
|
|
strcpy (namestack[k], "");
|
|
tvistack[k] = 0;
|
|
}
|
|
|
|
for (int i = 0; i < numLumps; i++)
|
|
{
|
|
if (d_loadEntirePAK || !strncmp (lumps[i].name, "models", 6))
|
|
{
|
|
char *tok;
|
|
if (d_loadEntirePAK)
|
|
tok = &lumps[i].name[0];
|
|
else
|
|
tok = &lumps[i].name[7];
|
|
|
|
int i = 1;
|
|
while (tok)
|
|
{
|
|
char *end = strchr (tok, '/');
|
|
if (end)
|
|
*end = '\0';
|
|
|
|
if (strcmp (namestack[i], tok))
|
|
{
|
|
strcpy (namestack[i], tok);
|
|
/*
|
|
if (i == 0)
|
|
tvistack[i] = tvPAK->add (0, tok);
|
|
else*/
|
|
tvistack[i] = tvPAK->add (tvistack[i - 1], tok);
|
|
|
|
for (int j = i + 1; j < 32; j++)
|
|
{
|
|
strcpy (namestack[j], "");
|
|
tvistack[j] = 0;
|
|
}
|
|
}
|
|
|
|
++i;
|
|
|
|
if (end)
|
|
tok = end + 1;
|
|
else
|
|
tok = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
delete[] lumps;
|
|
|
|
setVisible (true);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
void
|
|
PAKViewer::closePAKFile ()
|
|
{
|
|
strcpy (d_pakFile, "");
|
|
setVisible (false);
|
|
}
|