#include <memory.h>
#include "MusicHook.h"
// Addresses of Outpost2 code and data
DWORD outpost2ModuleBase = 0x00400000; // Expected module base. Used for recalculating addresses
CRITICAL_SECTION *lpTimerCriticalSection = (CRITICAL_SECTION*)0x00565420; // Critical section controlling access to the timer for the music buffer filling
DWORD hookAddress = 0x00450ED9;
DWORD hookReturn = 0x00450F28;
DWORD clmLoadAddress = 0x00450340;
DWORD managePlayListAddress = 0x00450DC0;
DWORD firstPlayAddress = 0x00450E63;
DWORD memAllocAddr = 0x004C0F40;
DWORD musicManagerAddr = 0x00565390;
const unsigned char hookedCodeBytes[] = {
0xA1, 0x68, 0x54, 0x56, 0x00, // MOV EAX, MusicManager.currentSongFileIndex
0x8B, 0x0D, 0xAC, 0x53, 0x56, 0x00, // MOV ECX, MusicManager.headerData*
0xC1, 0xE0, 0x04, // SHL EAX, 4
0x8B, 0x15, 0x6C, 0x54, 0x56, 0x00, // MOV EDX, MusicManager.currentSongPosition
};
const unsigned char newCodeBytes[] = {
0x8B, 0x4C, 0x24, 0x10, // MOV ECX, [ESP+0x10] (AudioPtr1)
0x68, 0x00, 0x80, 0x00, 0x00, // PUSH 0x8000
0x51, // PUSH ECX (AudioPtr1)
0x68, // PUSH returnAddress
0x00, 0x00, 0x00, 0x00, // (returnAddress is written here)
0xE9, // JMP musicBufferFillingFunction
0x00, 0x00, 0x00, 0x00, // (musicBufferFillingFunction relative address is written here)
};
const unsigned char managePlayListBytes[] = {
0xA1, 0x6C, 0x54, 0x56, 0x00 // MOV EAX, MusicManager.currentSongPosition
};
const unsigned char newPlayListSkip[] = {
0xE9, 0xB9, 0x00, 0x00, 0x00, // JMP FillMusicBuffer
};
const unsigned char firstPlayBytes[] = {
0x8B, 0x14, 0x81, // MOV EDX, [ECX + EAX*4]
};
const unsigned char newFirstPlay[] = {
0x33, 0xD2, // XOR EDX, EDX
0x90 // NOP
};
const unsigned char clmLoadBytes[] = {
0x81, 0xEC, 0x58, 0x01, 0x00, 0x00, // SUB ESP, 0x158
};
const unsigned char newClmLoad[] = {
0xB8, 0x01, 0x00, 0x00, 0x00, // MOV EAX, 1
0xC3, // RETN
};
// **TODO** VERIFY THIS WORKS!
// **TODO** Find out if there is even much point? The hooked code contains memory addresses
// that are being verified against anyways. :(
// Used to update all expected memory addresses of things in Outpost2.exe
void RebaseCode(DWORD newBaseAddress)
{
// Calculate the offset between new and old addresses
DWORD offset = newBaseAddress - outpost2ModuleBase;
// Update current load address
outpost2ModuleBase += offset;
// Update the addresses
*(char**)&lpTimerCriticalSection += offset;
hookAddress += offset;
hookReturn += offset;
clmLoadAddress += offset;
managePlayListAddress += offset;
firstPlayAddress += offset;
memAllocAddr += offset;
musicManagerAddr += offset;
}
bool InitializeClmLoad()
{
int headerDataAddr;
// Allocate space for header data
_asm
{
PUSH 0x4C
CALL [memAllocAddr]
MOV headerDataAddr, EAX
}
if (headerDataAddr == NULL)
return false;
// Fill in a fake CLM header
memcpy((void*)headerDataAddr, "OP2 Clump File Version 1.0\0x1A\0x0\0\0\0\0", 32);
WAVEFORMATEX *waveFormat = (WAVEFORMATEX*)(headerDataAddr + 0x32);
waveFormat->wFormatTag = 1;
waveFormat->nChannels = 1;
waveFormat->nSamplesPerSec = 22050;
waveFormat->nAvgBytesPerSec = 22050*2;
waveFormat->nBlockAlign = 2;
waveFormat->wBitsPerSample = 16;
waveFormat->cbSize = 0;
*(short*)(headerDataAddr+0x32) = 0;
*(int*)(headerDataAddr+0x34) = 0x10000;
*(int*)(headerDataAddr+0x38) = 0; // numPackedFiles
// Fill in a fake index entry in the CLM header
memset((void*)(headerDataAddr + 0x3C), 0, 8);
*(int*)(headerDataAddr+0x44) = 0;
*(int*)(headerDataAddr+0x48) = 0;
// Fill in needed MusicManager fields
*(int*)(musicManagerAddr + 0x1C) = headerDataAddr; // char *headerData
*(int*)(musicManagerAddr + 0x20) = -1; // HANDLE hClmFile
*(int*)(musicManagerAddr + 0x24) = 0x4C; // int totalFileHeaderSize
// Fill in the song index table (point to only dummy index entry)
int i;
int *addr = (int*)(musicManagerAddr + 0x28);
for (i = 0; i < 26; i++)
addr[i] = 0;
return true;
}
int InstallMusicHook(MusicBufferFillingFunc *musicPump)
{
// Make sure the hooking location contains the correct code
if (memcmp((void*)hookAddress, hookedCodeBytes, sizeof(hookedCodeBytes)) != 0)
return 1; // Failed. Unexpected instructions at hook point.
// Make sure the clm loading location contains the correct code
if (memcmp((void*)clmLoadAddress, clmLoadBytes, sizeof(clmLoadBytes)) != 0)
return 2; // Failed. Unexpected instructions at hook point.
if (memcmp((void*)managePlayListAddress, managePlayListBytes, sizeof(managePlayListBytes)) != 0)
return 3; // Failed. Unexpected instructions at hook point.
if (memcmp((void*)firstPlayAddress, firstPlayBytes, sizeof(firstPlayBytes)) != 0)
return 4; // Failed. Unexpected instructions at hook point.
// Try to unprotect the code section
DWORD oldAttributes;
if (VirtualProtect((LPVOID)hookAddress, sizeof(hookedCodeBytes), PAGE_EXECUTE_READWRITE, &oldAttributes) == 0)
return 5; // Could not unprotect pages. Abort
if (VirtualProtect((LPVOID)clmLoadAddress, sizeof(clmLoadBytes), PAGE_EXECUTE_READWRITE, &oldAttributes) == 0)
return 6; // Could not unprotect pages. Abort
if (VirtualProtect((LPVOID)managePlayListAddress, sizeof(managePlayListBytes), PAGE_EXECUTE_READWRITE, &oldAttributes) == 0)
return 7; // Could not unprotect pages. Abort
if (VirtualProtect((LPVOID)firstPlayAddress, sizeof(firstPlayBytes), PAGE_EXECUTE_READWRITE, &oldAttributes) == 0)
return 8; // Could not unprotect pages. Abort
// Enter (timer) Critical Section
// Note: The code we are overwriting is inside a critical section. By entering
// the critical section ourselves, we ensure the code is not executed while we
// are in the process of overwriting it.
EnterCriticalSection(lpTimerCriticalSection);
// Insert the hook
// ***************
memcpy((void*)hookAddress, newCodeBytes, sizeof(newCodeBytes));
// Make sure the correct return address and function address are used
*(DWORD*)&(((char*)hookAddress)[11]) = hookReturn;
*(DWORD*)&(((char*)hookAddress)[16]) = (DWORD)musicPump - hookAddress - sizeof(newCodeBytes);
// Patch up the CLM load
memcpy((void*)clmLoadAddress, newClmLoad, sizeof(newClmLoad));
memcpy((void*)managePlayListAddress, newPlayListSkip, sizeof(newPlayListSkip));
memcpy((void*)firstPlayAddress, newFirstPlay, sizeof(newFirstPlay));
// Initialized CLM data for safety (since load code is now skipped)
InitializeClmLoad();
// Leave (timer) Critical Section
LeaveCriticalSection(lpTimerCriticalSection);
// Return success
return 0;
}
int UninstallMusicHook()
{
// Check for an installed hook
// Meh. Doesn't matter. Just overwriting with original code anyways
// Enter (timer) Critical Section
// Note: The code we are overwriting is inside a critical section. By entering
// the critical section ourselves, we ensure the code is not executed while we
// are in the process of overwriting it.
EnterCriticalSection(lpTimerCriticalSection);
// Remove the hook
memcpy((void*)hookAddress, hookedCodeBytes, sizeof(hookedCodeBytes));
memcpy((void*)clmLoadAddress, clmLoadBytes, sizeof(clmLoadBytes));
memcpy((void*)managePlayListAddress, managePlayListBytes, sizeof(managePlayListBytes));
memcpy((void*)firstPlayAddress, firstPlayBytes, sizeof(firstPlayBytes));
// Leave (timer) Critical Section
LeaveCriticalSection(lpTimerCriticalSection);
// Return success
return 0;
}
#include <dsound.h>
// Define a typedef for the callback function
typedef int __stdcall MusicBufferFillingFunc(char *musicBuffer, int bufferSize);
void RebaseCode(DWORD newBaseAddress);
int InstallMusicHook(MusicBufferFillingFunc *musicPump);
int UninstallMusicHook();
BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
if (ul_reason_for_call == DLL_PROCESS_ATTACH)
{
DisableThreadLibraryCalls((HMODULE)hModule);
InstallMusicHook(&FillMusicBuffer);
}
if (ul_reason_for_call == DLL_PROCESS_DETACH)
{
UninstallMusicHook();
}
return TRUE;
}
#include "MusicHook.h"
int __stdcall FillMusicBuffer(char *musicBuffer, int bufferSize);
#include "MusicHook.h"
#include "AudFileReader.h"
#include "AudDecoder.h"
// Note: This function fills the music buffer
// with 0x8000 bytes worth of PCM data
// Note: Data format is 16 bit signed, mono, at 22050 Hz
// Return: Nonzero on success, zero on error. If an error occurs,
// the music buffer is cleared to zero.
int __stdcall FillMusicBuffer(char *musicBuffer, int bufferSize)
{
DWORD numDecoded1;
DWORD numAudBytesRead;
static AudFileReader audReader;
static AudDecoder audDecoder;
static bool playNewAudFile = true;
const DWORD audBufferSize = 0x2000;
char audBuffer[audBufferSize];
// Check if a new Aud file needs to be decoded
if (playNewAudFile == true)
{
// **TODO** Make this work in the right way
audReader.Open("ARAKATAK.AUD");
audDecoder = AudDecoder();
playNewAudFile = false;
}
// Read a chunk of Aud data
numAudBytesRead = audReader.ReadAudData(audBuffer, audBufferSize);
// Fill sound buffer
// *****************
numDecoded1 = audDecoder.Decode(musicBuffer, bufferSize, audBuffer, numAudBytesRead);
memset(musicBuffer + numDecoded1, 0, bufferSize - numDecoded1); // Zero fill if short on data
// Return success
return 1;
}
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <fstream.h>
class AudFileReader
{
public:
AudFileReader();
~AudFileReader();
int Open(char *fileName); // Opens file and reads in the header
int ReadAudData(char *buffer, unsigned int bufferSize); // Returns a chunk of AUD data
// Wave format info
int GetFrequency();
int GetOutputSize();
int GetCompressionType();
int GetBitsPerSample();
int GetNumChannels();
private:
// Private data types
#pragma pack(push, 1)
// AUD File header
struct AudHeader
{
short samplesPerSec; // Frequency
int fileSize; // Size of file (without header)
int outputSize; // Size of output data
char flags; // Bit 0 = Stereo, Bit 1 = 16 bit
char type; // 1 = WW compressed, 99 = IMA ADPCM
};
// Chunk header
struct ChunkHeader
{
short size;
short outputSize;
int id;
};
#pragma pack(pop)
enum AudFlags
{
FlagStereo = 1,
Flag16Bit = 2,
};
// Private functions
int ReadChunk(char *buffer, int bufferSize);
// Class variables
//ifstream audFile;
HANDLE hFile;
AudHeader audHeader;
ChunkHeader currentChunkHeader;
int currentChunkOffset;
};
#include "AudFileReader.h"
AudFileReader::AudFileReader()
{
hFile = INVALID_HANDLE_VALUE;
currentChunkOffset = 0;
memset(&audHeader, 0, sizeof(audHeader));
memset(¤tChunkHeader, 0, sizeof(currentChunkHeader));
}
AudFileReader::~AudFileReader()
{
// Close the file if it is open
if (hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
}
// Opens an AUD file and reads in the file header
// Return: 0 on success, nonzero on failure.
int AudFileReader::Open(char *fileName)
{
DWORD numBytesRead;
// Try to open the file
hFile = CreateFile(fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
// Check for errors
if (hFile == INVALID_HANDLE_VALUE)
{
// Error opening the file
return 1;
}
// Read the file header
ReadFile(hFile, &audHeader, sizeof(audHeader), &numBytesRead, NULL);
// Check for errors
if (numBytesRead != sizeof(audHeader))
{
// Error reading AUD header
CloseHandle(hFile);
return 2;
}
// Return success
return 0;
}
// Fills the passed in buffer with AUD data, up to the maximum size of the buffer.
// Return: The number of bytes actually written to the buffer
int AudFileReader::ReadAudData(char *buffer, unsigned int bufferSize)
{
DWORD numBytesRead;
DWORD numBytesWritten = 0;
DWORD readSize;
// Try to fill the buffer with AUD data
while(bufferSize > numBytesWritten)
{
// Check if the current chunk has no more data left
if (currentChunkHeader.size <= currentChunkOffset)
{
// We need to read in a new chunk
ReadFile(hFile, ¤tChunkHeader, sizeof(currentChunkHeader), &numBytesRead, NULL);
// Check for errors
if (numBytesRead != sizeof(currentChunkHeader))
{
// Error reading new chunk header. Abort.
return numBytesWritten;
}
// Reset current chunk offset
currentChunkOffset = 0;
}
// Determine how many bytes to read from the current chunk
readSize = currentChunkHeader.size - currentChunkOffset;
if (readSize > bufferSize - numBytesWritten)
readSize = bufferSize - numBytesWritten;
// Read the chunk data
ReadFile(hFile, &buffer[numBytesWritten], readSize, &numBytesRead, NULL);
// Adjust the counters
numBytesWritten += readSize;
currentChunkOffset += readSize;
// Check for errors
if (numBytesRead != readSize)
{
// Error reading chunk data. Abort.
return numBytesWritten;
}
}
// Return the number of bytes written
return numBytesWritten;
}
int AudFileReader::GetFrequency()
{
return audHeader.samplesPerSec;
}
int AudFileReader::GetOutputSize()
{
return audHeader.outputSize;
}
int AudFileReader::GetCompressionType()
{
return audHeader.type;
}
int AudFileReader::GetBitsPerSample()
{
if ((audHeader.flags & Flag16Bit) == Flag16Bit)
return 16;
else
return 8;
}
int AudFileReader::GetNumChannels()
{
if ((audHeader.flags & FlagStereo) == FlagStereo)
return 2;
else
return 1;
}
class AudDecoder
{
public:
AudDecoder();
int Decode(void *pcmBuffer, int pcmBufferSize, char *audBuffer, int audBufferSize);
private:
// Audio decoding state
int index;
int curSample;
// Private static constant tables used during decompression
static const int indexAdjust[];
static const int stepTable[];
};
#include "AudDecoder.h"
// Define the static tables used during decompression
const int AudDecoder::indexAdjust[] = {-1,-1,-1,-1,2,4,6,8};
const int AudDecoder::stepTable[] = {
7, 8, 9, 10, 11, 12, 13, 14, 16,
17, 19, 21, 23, 25, 28, 31, 34, 37,
41, 45, 50, 55, 60, 66, 73, 80, 88,
97, 107, 118, 130, 143, 157, 173, 190, 209,
230, 253, 279, 307, 337, 371, 408, 449, 494,
544, 598, 658, 724, 796, 876, 963, 1060, 1166,
1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749,
3024, 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484,
7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, 15289,
16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
};
// Constructor
AudDecoder::AudDecoder()
{
// Initialize the decoding state
index = 0;
curSample = 0;
}
// Decompresses the data from the audBuffer into the pcmBuffer
// Return value: The number of bytes written to the pcm buffer.
// Parameters:
// pcmBuffer - a pointer to the buffer to fill with signed 16 bit PCM samples
// pcmBufferSize - size of the pcmBuffer in bytes. (not the size in shorts)
// audBuffer - a pointer to the input aud data (consisting of 4 bit samples)
// audBufferSize - size of the audBuffer in bytes. (not the number of samples)
// Note: The aud data is in 4 bit samples, and the pcm data is 16 bit signed samples
// Note: Data is assumed to be mono
int AudDecoder::Decode(void *pcmBuffer, int pcmBufferSize, char *audBuffer, int audBufferSize)
{
int numSamples;
int i;
int code;
bool signBit;
int delta;
// Determine maximum amount of data that can be processed
if (pcmBufferSize/2 >= audBufferSize*2)
numSamples = audBufferSize*2; // Number of samples is implicity even
else
numSamples = (pcmBufferSize/2) & ~1; // Round down to even number of samples
// Decode the chunk of data
for (i = 0; i < numSamples; i++)
{
// Get the current code (full byte)
code = audBuffer[i/2];
// Adjust for correct nibble
if ((i & 1) == 1)
code >>= 4;
// Get the sign of the nibble
signBit = ((code & 8) == 8);
// Truncate code to remaining bits
code &= 7;
// Calculate delta. Last part minimizes errors
delta =(stepTable[index]*code)/4 + stepTable[index]/8;
// Adjust for sign
if (signBit)
delta = -delta;
// Calculate the new sample
curSample += delta;
if (curSample < -32768) curSample = -32768;
else if (curSample > 32767) curSample = 32767;
// Write out the current sample
((short*)pcmBuffer)[i] = curSample;
// Adjust the index
index += indexAdjust[code];
if (index < 0) index = 0;
else if (index > 88) index = 88;
}
// Return the number of bytes written
return numSamples*2;
}