Code: Select all
/*
** File: TA3D_hpi.h
** Notes:
** Cire: The original author of most of this code is Joe D, aka Kingbot
** I modified it to be more complain with C++ as it was mostly
** C based. I wrapped it neatly in a class, and exposed only
** those methods that the game engine need to access.
** TODO: If this is to be accessed by multiple threads it should be
** rewritten as it currently can not handle multiple calls.
** HPIITEM might be expanded to keep a reference track of
** how many times it gets asked to decode some file, if it
** reaches some point it might just keep that 'data' in its
** self and return it instead of having to decode each time.
*/
#pragma once
namespace TA3D
{
namespace UTILS
{
namespace HPI
{
#define HEX_HAPI 0x49504148
#define HPI_V1 0x00010000
#ifndef MAX_PATH
#define MAX_PATH 260
#endif
class cHPIHandler
{
// Member Declarations:
private:
#pragma pack(1) // Byte alignment.
struct HPIVERSION
{
sint32 HPIMarker; /* Must be HEX_HAPI */
sint32 Version; /* Must be HPI_V1 */
};
struct HPIHEADER
{
sint32 DirectorySize; /* Directory size */
sint32 Key; /* Decode key */
sint32 Start; /* Directory offset */
};
struct HPIENTRY
{
sint32 NameOffset;
sint32 CountOffset;
sint8 Flag;
};
struct HPICHUNK
{
sint32 Marker; /* always 0x48535153 (SQSH) */
sint8 Unknown1; /* I have no idea what these mean */
sint8 CompMethod; /* 1 = lz77, 2 = zlib */
sint8 Encrypt; /* Is the chunk encrypted? */
sint32 CompressedSize; /* the length of the compressed data */
sint32 DecompressedSize; /* the length of the decompressed data */
sint32 Checksum; /* check sum */
};
struct HPIFILEDATA
{
HPIHEADER H1;
sint8 Key;
std::string HPIFileName;
sint8 *Directory;
FILE *HPIFile;
};
struct HPIITEM
{
HPIFILEDATA *hfd;
HPIENTRY *E1;
sint32 IsDir;
sint8 *Name;
sint32 Size;
};
#pragma pack()
// Member Functions:
private:
void AddArchive( const std::string &FileName );
void LocateAndReadFiles( const std::string &Path, const std::string &FileSearch );
void ProcessRoot( HPIFILEDATA *hfd, const std::string &StartPath, sint32 offset );
void ProcessSubDir( HPIITEM *hi );
sint32 ReadAndDecrypt( sint32 fpos, byte *buff, sint32 buffsize, HPIFILEDATA *HPIInfo );
sint32 ZLibDecompress( byte *out, byte *in, HPICHUNK *Chunk );
sint32 LZ77Decompress( byte *out, byte *in, HPICHUNK *Chunk );
sint32 Decompress( byte *out, byte *in, HPICHUNK *Chunk );
void CloseHPIFile( HPIFILEDATA *hfd );
// void cHPIHandler::CloseCurrentFile( void );
byte *DecodeFileToMem( HPIITEM *hi );
public:
void SearchDirForArchives( const std::string &Path );
// constructor:
cHPIHandler::cHPIHandler( const std::string &Path )
{
SearchDirForArchives( Path );
}
sint32 cHPIHandler::GetFilelist( const std::string &Search, std::list<std::string> *li );
// DeConstructor
~cHPIHandler();
#if defined( DEBUG )
void ShowArchive(); // for debug only we don't need it.
#endif
byte *PullFromHPI( const std::string &FileName );
// Member Variables:
private:
std::string m_cDir; // used when building dir structurs.
// A map of dir/filenames for key, while value is a HPIITEM;
typedef std::map< std::string, HPIITEM *> HPIARCHIVE;
HPIARCHIVE m_Archive;
// A list of HPIFILEDATA, needed only for cleanup.
std::list< HPIFILEDATA * > m_HPIFiles;
}; // class cHPIHandler;
} // namespace HPI
} // namespace utils
} // namespace TA3D
Code: Select all
/*
** File: TA3D_hpi.cpp
** Notes:
** Cire: See notes in TA3D_hpp.h
*/
#include "stdafx.h"
#include "TA3D_hpi.h"
#include <algorithm>
#if defined TA3D_PLATFORM_WINDOWS
#include "tools/win32/include/zlib.h"
#pragma comment(lib, "tools/win32/libs/zlib.lib")
#elif
#include <zlib.h>
#endif
using namespace std;
using namespace TA3D::UTILS::HPI;
sint32 cHPIHandler::ReadAndDecrypt( sint32 fpos, byte *buff, sint32 buffsize, HPIFILEDATA *HPIInfo)
{
sint32 count, tkey, result;
fseek(HPIInfo->HPIFile, fpos, SEEK_SET);
result = (sint32)fread(buff, buffsize, 1, HPIInfo->HPIFile);
if (HPIInfo->Key)
{
for (count = 0; count < buffsize; count++)
{
tkey = (fpos + count) ^ HPIInfo->Key;
buff[count] = tkey ^ ~buff[count];
}
}
return result;
}
sint32 cHPIHandler::ZLibDecompress( byte *out, byte *in, HPICHUNK *Chunk)
{
z_stream zs;
sint32 result;
zs.next_in = in;
zs.avail_in = Chunk->CompressedSize;
zs.total_in = 0;
zs.next_out = out;
zs.avail_out = Chunk->DecompressedSize;
zs.total_out = 0;
zs.msg = NULL;
zs.state = NULL;
zs.zalloc = Z_NULL;
zs.zfree = Z_NULL;
zs.opaque = NULL;
zs.data_type = Z_BINARY;
zs.adler = 0;
zs.reserved = 0;
result = inflateInit(&zs);
if (result != Z_OK) {
//printf("Error on inflateInit %d\nMessage: %s\n", result, zs.msg);
return 0;
}
result = inflate(&zs, Z_FINISH);
if (result != Z_STREAM_END) {
//printf("Error on inflate %d\nMessage: %s\n", result, zs.msg);
zs.total_out = 0;
}
result = inflateEnd(&zs);
if (result != Z_OK) {
//printf("Error on inflateEnd %d\nMessage: %s\n", result, zs.msg);
return 0;
}
return zs.total_out;
}
sint32 cHPIHandler::LZ77Decompress( byte *out, byte *in, HPICHUNK *Chunk)
{
sint32 x, work1, work2, work3, inptr, outptr, done, DPtr;
schar DBuff[4096];
done = 0;
inptr = 0;
outptr = 0;
work1 = 1;
work2 = 1;
work3 = in[inptr++];
while (!done)
{
if ((work2 & work3) == 0)
{
out[outptr++] = in[inptr];
DBuff[work1] = in[inptr];
work1 = (work1 + 1) & 0xFFF;
inptr++;
}
else
{ int
count = *((uint16 *) (in+inptr));
inptr += 2;
DPtr = count >> 4;
if (DPtr == 0)
return outptr;
else
{
count = (count & 0x0f) + 2;
if (count >= 0)
{
for (x = 0; x < count; x++)
{
out[outptr++] = DBuff[DPtr];
DBuff[work1] = DBuff[DPtr];
DPtr = (DPtr + 1) & 0xFFF;
work1 = (work1 + 1) & 0xFFF;
}
}
}
}
work2 *= 2;
if (work2 & 0x0100)
{
work2 = 1;
work3 = in[inptr++];
}
}
return outptr;
}
sint32 cHPIHandler::Decompress( byte *out, byte *in, HPICHUNK *Chunk )
{
sint32 x, Checksum;
Checksum = 0;
for (x = 0; x < Chunk->CompressedSize; x++) {
Checksum += (byte) in[x];
if (Chunk->Encrypt)
in[x] = (in[x] - x) ^ x;
}
if (Chunk->Checksum != Checksum)
{
// GlobalDebugger-> Checksum error! Calculated: 0x%X Actual: 0x%X\n", Checksum, Chunk->Checksum);
return 0;
}
switch (Chunk->CompMethod) {
case 1 : return LZ77Decompress(out, in, Chunk);
case 2 : return ZLibDecompress(out, in, Chunk);
default : return 0;
}
}
byte *cHPIHandler::DecodeFileToMem(HPIITEM *hi)
{
sint32 DeCount,DeLen, x, WriteSize, WritePtr, Offset, Length, FileFlag, *DeSize;
byte *DeBuff, *WriteBuff;
HPIENTRY *Entry;
HPICHUNK *Chunk;
if (!hi)
return NULL;
Entry = hi->E1;
Offset = *((sint32 *) (hi->hfd->Directory + Entry->CountOffset));
Length = *((sint32 *) (hi->hfd->Directory + Entry->CountOffset + 4));
FileFlag = *(hi->hfd->Directory + Entry->CountOffset + 8);
WriteBuff = (byte *)GetMem(Length+1, 0);
if (!WriteBuff)
{
// NO NEED to global deubber this, get meme shoulda done it for us.
return NULL;
}
WriteBuff[Length] = 0;
if (FileFlag) {
DeCount = Length / 65536;
if (Length % 65536)
DeCount++;
DeLen = DeCount * sizeof(sint32);
DeSize = (sint32 *)GetMem(DeLen, 0);
ReadAndDecrypt(Offset, (byte *) DeSize, DeLen, hi->hfd );
Offset += DeLen;
WritePtr = 0;
for (x = 0; x < DeCount; x++) {
Chunk = (HPICHUNK *)GetMem(DeSize[x], 0);
ReadAndDecrypt(Offset, (byte *) Chunk, DeSize[x], hi->hfd);
Offset += DeSize[x];
DeBuff = (byte *) (Chunk+1);
WriteSize = Decompress(WriteBuff+WritePtr, DeBuff, Chunk);
WritePtr += WriteSize;
free(Chunk);
}
free(DeSize);
}
else {
// file not compressed
ReadAndDecrypt(Offset, WriteBuff, Length, hi->hfd );
}
return WriteBuff;
}
void cHPIHandler::CloseHPIFile(HPIFILEDATA *hfd)
{
if (!hfd)
return;
if (hfd->Directory)
{
free(hfd->Directory);
hfd->Directory = NULL;
}
if (hfd->HPIFile)
fclose(hfd->HPIFile);
hfd->HPIFile = NULL;
}
void cHPIHandler::ProcessSubDir( HPIITEM *hi )
{
sint32 *FileCount, *FileLength, *EntryOffset, *Entries, count;
schar *Name;
HPIENTRY *Base, *Entry;
HPIITEM *li;
Base = hi->E1;
if (Base)
Entries = (sint32 *) (hi->hfd->Directory + Base->CountOffset);
else
Entries = (sint32 *) (hi->hfd->Directory + hi->hfd->H1.Start);
EntryOffset = Entries + 1;
Entry = (HPIENTRY *) (hi->hfd->Directory + *EntryOffset);
for (count = 0; count < *Entries; count++) {
Name = hi->hfd->Directory + Entry->NameOffset;
FileCount = (sint32 *) (hi->hfd->Directory + Entry->CountOffset);
FileLength = FileCount + 1;
li = (HPIITEM *)GetMem(sizeof(HPIITEM), 1);
li->hfd = hi->hfd;
li->Name = Name;
li->E1 = Entry;
if (Entry->Flag == 1)
{
std::string sDir = m_cDir; // save directory
m_cDir += (char *)Name; // add new path to directory.
m_cDir += "\\"; // don't forget to end path with /
ProcessSubDir( li ); // process new directory
free( li ); // free the item.
li = NULL; // really free the item.
m_cDir = sDir; // restore dir with saved dir
} else {
std::string f = Lowercase( m_cDir + (char *)Name );
HPIARCHIVE::iterator iterFind = m_Archive.find( f );
if( iterFind != m_Archive.end() )
{ // its in our map, free the memory, erase it, add new one.
HPIITEM *hi = iterFind->second;
iterFind->second = NULL;
m_Archive.erase( iterFind );
free( hi );
hi = NULL;
}
li->Size = *FileLength;
m_Archive[f] = li;
}
Entry++;
}
}
void cHPIHandler::ProcessRoot(HPIFILEDATA *hfd, const std::string &StartPath, sint32 offset )
{
sint32 *Entries, *FileCount, *EntryOffset;
schar *Name;
std::string MyPath;
Entries = (sint32 *)(hfd->Directory + offset);
EntryOffset = Entries + 1;
HPIENTRY *Entry = (HPIENTRY *)(hfd->Directory + *EntryOffset);
for( sint32 count = 0; count < *Entries; count++ )
{
Name = hfd->Directory + Entry->NameOffset;
FileCount = (sint32 *) (hfd->Directory + Entry->CountOffset);
if (Entry->Flag == 1)
{
MyPath = StartPath;
if( MyPath.length() )
MyPath += "\\";
MyPath += (char *)Name;
m_cDir = MyPath + "\\";
HPIITEM *hi = (HPIITEM *)GetMem(sizeof(HPIITEM), 1);
hi->hfd = hfd;
hi->IsDir = 1;
hi->Size = 0;
hi->Name = Name;
hi->E1 = Entry;
ProcessSubDir( hi );
free( hi );
hi = NULL;
}
Entry++;
}
}
void cHPIHandler::AddArchive( const std::string &FileName )
{
HPIFILEDATA *hfd = (HPIFILEDATA *)GetMem(sizeof(HPIFILEDATA), 1);
if (!hfd)
return; // No Need to generate error here as getmem already did it.
hfd->HPIFileName = FileName;
#if defined TA3D_PLATFORM_WINDOWS
errno_t err;
if( ( err = fopen_s( &hfd->HPIFile, hfd->HPIFileName.c_str(), "rb" )) != 0 )
#else
hfd->HPIFile=fopen( hfd->HPIFileName.c_str(), "rb" );
if( !hfd->HPIFile )
#endif
{
CloseHPIFile(hfd);
free(hfd);
// GlobalDebugger-> Failed to open hpi file for reading.
return;
}
HPIVERSION hv;
fread(&hv, sizeof(HPIVERSION), 1, hfd->HPIFile);
if( hv.Version != HPI_V1 || hv.HPIMarker != HEX_HAPI )
{
CloseHPIFile(hfd);
free(hfd);
hfd = NULL;
return;
}
fread(&hfd->H1, sizeof(HPIHEADER), 1, hfd->HPIFile);
if (hfd->H1.Key)
hfd->Key = ~((hfd->H1.Key * 4) | (hfd->H1.Key >> 6));
else
hfd->Key = 0;
int start = hfd->H1.Start;
int size = hfd->H1.DirectorySize;
hfd->Directory = (sint8 *)GetMem(size, 1);
ReadAndDecrypt(start, (byte *)hfd->Directory + start, size - start, hfd );
m_cDir = "";
m_HPIFiles.push_front( hfd );
ProcessRoot(hfd, "", start);
}
void cHPIHandler::LocateAndReadFiles( const std::string &Path, const std::string &FileSearch )
{
al_ffblk search;
if( al_findfirst(FileSearch.c_str(), &search, FA_RDONLY | FA_ARCH ) ==0 )
{
do
{
AddArchive( Path + search.name );
} while( al_findnext( &search ) == 0 );
al_findclose(&search);
}
}
void cHPIHandler::SearchDirForArchives( const std::string &Path )
{
schar ext[4][6] = { "*.ufo", "*.hpi", "*.ccx", "*.gp3" };
for( uint32 i = 0; i < 4; i++ )
LocateAndReadFiles( Path, Path + (char *)ext[i] );
}
cHPIHandler::~cHPIHandler()
{
// Cleanup:
// Walk the map, setting value to NULL, and freeing HPIITEMS.
for( HPIARCHIVE::iterator iter = m_Archive.begin(); iter != m_Archive.end(); ++iter )
{
HPIITEM *hi = iter->second;
iter->second = NULL;
free( hi );
hi = NULL;
}
m_Archive.clear(); // Now do a clear which will purge keys.
// Now close and free hpi files.
if( m_HPIFiles.size() == 0 )
return;
std::list< HPIFILEDATA * >::iterator cur;
HPIFILEDATA *hfd;
do
{
cur = m_HPIFiles.begin();
hfd = (*cur);
m_HPIFiles.pop_front();
CloseHPIFile( hfd );
free( hfd );
hfd = NULL;
} while( m_HPIFiles.size() != 0 );
}
#if defined( DEBUG ) // no need for bloating in release builds.
void cHPIHandler::ShowArchive()
{
for(HPIARCHIVE::iterator iter = m_Archive.begin(); iter != m_Archive.end(); iter++)
printf( "(arch)%s\n", (*iter).first.c_str() );
}
#endif
byte *cHPIHandler::PullFromHPI( const std::string &FileName )
{
HPIARCHIVE::const_iterator iterFind = m_Archive.find( Lowercase( FileName ) );
return ( (iterFind != m_Archive.end()) ?
DecodeFileToMem( (HPIITEM *)iterFind->second ) : NULL );
}
sint32 cHPIHandler::GetFilelist( const std::string &Search, std::list<std::string> *li )
{
std::string first, last;
std::basic_string <char>::size_type iFind;
static const std::basic_string <char>::size_type NotFound = -1;
iFind = Search.find( "*" );
if( iFind != NotFound )
{
first = Lowercase( Search.substr(0,(iFind)) );
last = Lowercase( Search.substr( iFind+1) );
}
else
{
first = Lowercase( Search );
last = "";
}
for( HPIARCHIVE::iterator iter = m_Archive.begin(); iter != m_Archive.end(); ++iter )
{
std::string f = iter->first;
if( f.length() < first.length() || f.length() < last.length() )
continue;
if( f.substr(0,first.length() ) == first )
{
if( f.substr( f.length() - last.length(), last.length() ) == last )
li->push_back( f );
}
}
return 0;
}