• Register

This group is created for modders. I would like to share my modding knowledge and to help modders getting started with their own mods for MechCommander. Greets RizZen

Post tutorial Report RSS Modding MechCommander: Adding new MechWarrior's voice-packs

This article explains how to create new Voice packages / *.PAK files for the SOUND folder in MechCommander 1 / Gold. Have fun modding MechCommander!

Posted by on - Basic Voice Acting


MechCommander 1 / Gold Modding

How to implement new Voice packages



H-E-U-R-E-K-A

I managed to create voice packages for MechCommander 1! This is how i found a solution that works at the front end:

Hi dear MCO community & magic,

sorry for gravedigging but i would like to report that this method works for MechCommander, too. Usually this should not be mentionable cause from neutral perspective MechCommander 1 / Gold should have pak-making tools when you look at the Modding Tools for it. But it turns out, that it had NO functioning exe file or working operator functions that allowed creating own voice- or sound packages for MechCommander.

At first i tried to create my voice package with Tigress tools. Tigress managed to create a small user interface for cMunsta's MC tools. Here a screenshot:


The MCG-Tools.exe of it contains the cMunsta tools:


To make it short: Tigress tools...


This tool should allow unpacking & repacking of PAK file source for MechCommander. But in fact, the version i own doesn't pack new files. It can export any content of any pak file but the function to create NEW PAK files doesn't work.

After research i found this on GitHub:

#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <string.h>

/*
-----------------------------------------------------------------
PAK file format (Nearly identical to the SOL/PKK/MPK/SAV file formats)
000000 DWORD PAK File Identifier (value is always 0xFEEDFACE)
000004 DWORD Offset where file data actually starts
000008 DWORD*n Table of offsets to the data in the file. (n is the table size)
- If an offset has a 0x40 in its high byte, it is compressed, and
the true offset is (Offset & ~0x40000000) and the first DWORD
at that offset is the uncompressed size.
- If the offset has 0xE0 in its high byte, this entry is effectively
empty.
Number of entries in table = ("DataStartOffset" - 8) / 4
-----------------------------------------------------------------
The following is actually just a quick modification of makesol.c, so if the comments
seem a bit odd at times, or the code a little redundant, you know why.
*/


typedef struct _PAK_Entry {
unsigned long Offset;
int compressed;
int empty;
struct _PAK_Entry *Next;
} PAK_Entry;


PAK_Entry *EntryList;
PAK_Entry *LastEntry;

long NumEntries;
long DataStartOffset;
unsigned long Checksum;

long tmpsize;


FILE *infile, *outfile, *tfile, *copyfile;

char cmd;
char filename[_MAX_PATH];
unsigned char *CopyBuffer;
unsigned char *CompressedBuffer;


extern long LZCompress(unsigned char *CompDataBuff, unsigned char *UncompDataBuff, unsigned long UncompDataSize);


void OutputAllData(void);
void AdjustDataForOutput(void);
void CreateTempDatafile(void);
void MakeNode(void);
void AddUncompressedFile(void);
void AddCompressedFile(void);
void AddEmptyEntry(void);


void main(int argc, char *argv[])
{
printf("-- makepak -- creates a PAK file from a file list --\n\n");

if(argc != 3)
{
printf("Usage: makepak <file list> <PAK file to create>\n");
exit(0);
}

if((infile = fopen(argv[1],"r")) == NULL)
{
printf("Can't open input file list '%s'\n",argv[1]);
exit(0);
}

if((outfile = fopen(argv[2], "wb+")) == NULL)
{
printf("Can't open output file '%s'\n",argv[2]);
exit(0);
}

if((tfile = fopen("$_temppak_$.dat","wb")) == NULL)
{
printf("Can't open temporary data file.\n");
exit(0);
}

CreateTempDatafile();

fclose(infile);

// Close and re-open the temporary file for reading
fclose(tfile);
if((tfile = fopen("$_temppak_$.dat","rb")) == NULL)
{
printf("Error -- Can't re-open temporary data file for reading\n");
exit(0);
}

AdjustDataForOutput();

fclose(tfile);

OutputAllData();

fclose(outfile);
}


void OutputAllData(void)
{
int x;
PAK_Entry *TOCEntry;

Checksum = 0xfeedface; // Don't have a checksum anymore, just an identifier of value "0xfeedface".
fwrite(&Checksum,4,1,outfile);
fwrite(&DataStartOffset,4,1,outfile);

TOCEntry = EntryList;
for(x=0;x<NumEntries;x++)
{
fwrite(&TOCEntry->Offset,4,1,outfile);

TOCEntry = TOCEntry->Next;
}

fwrite(CopyBuffer,1,tmpsize,outfile);
}


void AdjustDataForOutput(void)
{
int x;
long sizeread;
PAK_Entry *TOCEntry;

tmpsize = filelength(fileno(infile));

CopyBuffer = (unsigned char *)malloc(tmpsize);
if(CopyBuffer == NULL)
{
printf("Error -- Couldn't allocate buffer to copy the temp data\n");
exit(0);
}

sizeread = fread(CopyBuffer, 1,tmpsize, tfile);
if(sizeread != tmpsize)
{
printf("Error reading temporary data file into memory\n");
exit(0);
}

// Calc where the real data will start in the output file
DataStartOffset = (NumEntries * 4) + 8;

// Adjust the table of contents offsets, flag them as compressed or empty as needed.
TOCEntry = EntryList;
for(x=0; x<NumEntries; x++)
{
TOCEntry->Offset += DataStartOffset;

if(TOCEntry->compressed)
TOCEntry->Offset |= 0x40000000;

if(TOCEntry->empty)
TOCEntry->Offset |= 0xe0000000;

TOCEntry = TOCEntry->Next;
}
}


void CreateTempDatafile(void)
{
NumEntries = 0;
EntryList = NULL;
LastEntry = NULL;

while(!feof(infile))
{
fscanf(infile, "%c %s\n", &cmd, filename);

switch(cmd)
{
case 'c': // add as compressed file
AddCompressedFile();
break;

case 'u': // add as uncompressed file
AddUncompressedFile();
break;

case 'e': // add an empty entry to the TOC
AddEmptyEntry();
break;

default: // Unknown command, print error but keep going.
printf("Warning - Unknown command '%c'. Ignoring\n", cmd);
break;
}
}
}


void MakeNode(void)
{
PAK_Entry *NewNode;

NewNode = (PAK_Entry *)malloc(sizeof(PAK_Entry));
if(NewNode == NULL)
{
printf("Error allocating memory for TOC list\n");
exit(1);
}
NewNode->Next = NULL;
NewNode->Offset = 0;
NewNode->compressed = 0;
NewNode->empty = 0;

if(EntryList == NULL)
{
EntryList = NewNode;
}

if(LastEntry != NULL)
{
LastEntry->Next = NewNode;
}

LastEntry = NewNode;
}


void AddEmptyEntry(void)
{
MakeNode();

LastEntry->empty = 1;
LastEntry->compressed = 0;
LastEntry->Offset = ftell(tfile);

NumEntries++;
}


void AddUncompressedFile(void)
{
long fsize;
long sizeread;
long sizewritten;

if((copyfile = fopen(filename,"rb")) == NULL)
{
printf("Error -- can't open file %s\n", filename);
exit(1);
}

fsize = filelength(fileno(copyfile));

CopyBuffer = (unsigned char *)malloc(fsize);
if(CopyBuffer == NULL)
{
printf("Error -- Couldn't allocate buffer to copy %s.\n",filename);
exit(1);
}

sizeread = fread(CopyBuffer,1,fsize,copyfile);
if(sizeread != fsize)
{
printf("Error reading file %s\n",filename);
exit(1);
}

fclose(copyfile);

MakeNode();

LastEntry->compressed = 0;
LastEntry->empty = 0;
LastEntry->Offset = ftell(tfile);

sizewritten = fwrite(CopyBuffer,1,fsize,tfile);
if(sizewritten != fsize)
{
printf("Error writing %s to tempfile.\n",filename);
exit(1);
}

free(CopyBuffer);

NumEntries++;
}


void AddCompressedFile(void)
{
long fsize;
long sizeread;
long sizecompressed;
long sizewritten;

if((copyfile = fopen(filename,"rb")) == NULL)
{
printf("Error -- can't open file %s\n", filename);
exit(1);
}

fsize = filelength(fileno(copyfile));

CopyBuffer = (unsigned char *)malloc(fsize);
if(CopyBuffer == NULL)
{
printf("Error -- Couldn't allocate buffer to copy %s.\n",filename);
exit(1);
}

sizeread = fread(CopyBuffer,1,fsize,copyfile);
if(sizeread != fsize)
{
printf("Error reading file %s\n",filename);
exit(1);
}

fclose(copyfile);

// Since pretty much everything is a text file, we shouldn't have to worry about the
// data growing instead of shrinking, but it is technically possible.
CompressedBuffer = (unsigned char *)malloc(fsize);
if(CompressedBuffer == NULL)
{
printf("Error -- Couldn't allocate buffer to compress %s.\n",filename);
exit(1);
}

sizecompressed = LZCompress(CompressedBuffer, CopyBuffer, fsize);

MakeNode();

LastEntry->compressed = 1;
LastEntry->empty = 0;
LastEntry->Offset = ftell(tfile);

// Compressed files have the uncompressed file size stored in the first DWORD of the file data.
fwrite(&fsize,4,1,tfile);

sizewritten = fwrite(CompressedBuffer,1,sizecompressed,tfile);
if(sizewritten != sizecompressed)
{
printf("Error writing %s to tempfile.\n",filename);
exit(1);
}

free(CopyBuffer);
free(CompressedBuffer);

NumEntries++;
}

I have to repeat. Nice to have the source code for this exe file or function - but using it for creating my own BAT is - at the moment - out of my limits. My exe modding skills are hex-editing only.
The next step i took was searching for information about PAK files in general and found one solution that possibly could have work: Just compressing the voice files as *.zip / *.rar file, rename it to *.PAK... - this method although didn't work for MechCommander - and i found out why on gameextractor.sourceforge.net:

+---------------------+
| GENERAL INFORMATION |
+---------------------+

This file describes many different archive formats used by games. You can use these
specifications to help you create your own programs, or to give you an insight into
the information contained in a particular file.

** NOTE: Some specifications are not complete. **

There is absolutely NO GUARENTEE that the information within this document is correct!
All information has been obtained through experimentation and investigation only, no
information has been verified by the associated companies or game developers.

For better clarification of format structures, and for additional formats not listed
here, be sure to download the source code for Game Extractor (http://www.watto.org/extract)
as it contains fully commented Java code for reading and writing many game archives,
and is free for use by anyone.

If you have any information that you can add to this file, including new archives and
corrections, please contact WATTO at watto@watto.org - your help is greatly appreciated!


+-----------------------+
| HOW TO READ THIS FILE |
+-----------------------+

* General comments are indicated by a // at the start of the line
* Comments for a particular line are written within ( ) at the end of the line

* A number at the start of a line is the number of bytes used to store the value
* The text to the right of a number is a description of the value
* Information in ( ) to the right of a description, is the default value of the field
* Information in [ ] to the right of a description, is a calculation to apply to the field

* A ? in the description indicates that the description may not be correct
* An X at the start of the line (in place of a number) shows a variable number of bytes

* A line with "X - Filename (null)" indicates that the filename is read up to the first null byte
* A line with "# - Filename (null)" indicates that the filename has a maximum length of # bytes,
and the remaining space is filled with null bytes
* The statement (big) next to a game name indicates that the archive uses big-endian order
(all others use little-endian)
* The statement (chunk) next to a game name indicates the use of a variation on the EA IFF'85
chunk format

* In most directory-based archives, the file data follows the directory. Unless it is otherwise
indicated where the file data is located, this should be assumed, thus read the specs for the
game and the file data will follow directly after.

* X - File Data indicates that the data for each file is located in this area, with each
file's data stored one after the other

and for MechCommander 1 / 2 those value tables:

+----------------------+
| Mech Commander *.pak |
+----------------------+

4 - Header (206 250 237 254)
4 - First File Offset

// for each file
3 - File Offset
1 - Flags?

X - File Data


+------------------------+
| Mech Commander 2 *.fst |
+------------------------+

// Some or all files are compressed using ZLIB?

4 - Header (175 236 221 202)
4 - numFiles

// for each file
4 - File Offset
4 - File Size
4 - Unknown
4 - Unknown
250 - Filename (null)

X - File Data

... so i decided to add the missing hex lines into the PAK files. This didn't worked for the RAR/ZIP versions either. Then i compared the only functioning customermade voice package i have with the original PAK files and saw that PAK files are very familiar looking to *.PKK / *.MPK / *.SOL / *.SAV file extension compression methods. But i didn't wanted to create the pack file like this cause they have no prior header that would probably be used by the game engine. In order to not run into another dead-end...

... i started digging for information here in this thread and found the tools you have used for MechCommander 2 / OmniTech modding. For this i have to thanks Autohummer and of course... - again... magic for the work they have done!

So this solution works for MechCommander 1 / Gold - Voice Packages:

I have found and downloaded you MC2 tools

  • mkPilot_PAK.bat
  • unpackPilot_PAK.bat
  • XPList.exe


By using the mkPilot_PAK.bat & a simple response file list (Pilot.rsp)...

Pilot01.wav
Pilot02.wav
Pilot03.wav
Pilot04.wav
Pilot05.wav
Pilot06.wav
Pilot07.wav
Pilot08.wav
Pilot09.wav
Pilot10.wav
Pilot11.wav
Pilot12.wav
Pilot13.wav
Pilot14.wav
Pilot15.wav
Pilot16.wav
Pilot17.wav
Pilot18.wav
Pilot19.wav
Pilot20.wav
Pilot21.wav
Pilot22.wav
Pilot23.wav
Pilot24.wav
Pilot25.wav
Pilot26.wav
Pilot27.wav
Pilot28.wav
Pilot29.wav
Pilot30.wav
Pilot31.wav
Pilot32.wav
Pilot33.wav
Pilot34.wav
Pilot35.wav
Pilot36.wav
Pilot37.wav
Pilot38.wav
Pilot39.wav
Pilot40.wav
Pilot41.wav
Pilot42.wav
Pilot43.wav

Content of the "plaintext" response file called Pilot.rsp - for PAK tool of MC2.

... i could write a BEAST.PAK file. Yeah the bat file only creates soundpackages with this file name. But this is no big deal at all - i simply renamed (XXXX.PAK) the file into RizZ.PAK and implemented this into MechCommander. With the compression system of MC2 it works like a charme. I can hear my new pilots sounds ingame and it is absolutely working like a charme - AND with better sound quality cause MC2 used a bit higher technical settings for wav file compression.



So thank you again, dear modding community. This allows me to finally introduce one of the last missing elements in my Fullversion MechCommander Gold Darkest Hours - the implementation of 20 new voice packages for customermade MechWarriors. I have my own voice package containing cringe predator sounds hehe!

How to create the soundfiles from scratch - what must be done?

Regards RizZen

Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: