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>
531 lines
14 KiB
C++
531 lines
14 KiB
C++
#pragma once
|
|
|
|
#include "..\munga\style.h"
|
|
#include "..\munga\node.h"
|
|
#include "..\munga\schain.h"
|
|
#include "..\munga\audio.h"
|
|
#include "openal/al.h"
|
|
|
|
struct SourceSet
|
|
{
|
|
int count;
|
|
ALuint sources[5];
|
|
};
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MIDI types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
typedef unsigned char MIDIStatus;
|
|
typedef unsigned char MIDIChannel;
|
|
typedef unsigned char MIDIValue;
|
|
typedef int MIDIPitchBend;
|
|
typedef int MIDINRPNValue;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MIDI status ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
#define MIDI_CHANNEL_COUNT (16)
|
|
|
|
#define MIDI_NOTE_OFF_STATUS ((MIDIValue)0x8) // 8
|
|
#define MIDI_NOTE_ON_STATUS ((MIDIValue)0x9) // 9
|
|
#define MIDI_POLYKEY_STATUS ((MIDIValue)0xa) // 10
|
|
#define MIDI_CONTROLLER_STATUS ((MIDIValue)0xb) // 11
|
|
#define MIDI_PROGRAM_CHANGE_STATUS ((MIDIValue)0xc) // 12
|
|
#define MIDI_CHANNEL_PRESSURE_STATUS ((MIDIValue)0xd) // 13
|
|
#define MIDI_PITCH_BEND_STATUS ((MIDIValue)0xe) // 14
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~ MIDI controllers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
#define MIDI_MAX_CONTROL_VALUE ((MIDIValue)127)
|
|
#define MIDI_MIN_CONTROL_VALUE ((MIDIValue)0)
|
|
|
|
#define MIDI_LEFT_PAN_VALUE ((MIDIValue)0)
|
|
#define MIDI_CENTER_PAN_VALUE ((MIDIValue)63)
|
|
#define MIDI_RIGHT_PAN_VALUE ((MIDIValue)127)
|
|
|
|
#define MIDI_VOLUME_CONTROL ((MIDIValue)7)
|
|
#define MIDI_PAN_CONTROL ((MIDIValue)10)
|
|
#define MIDI_REVERB_CONTROL ((MIDIValue)91)
|
|
#define MIDI_CHORUS_CONTROL ((MIDIValue)93)
|
|
#define MIDI_CONTROL_RESET ((MIDIValue)121)
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MIDI notes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
#define MIDI_DEFAULT_NOTE ((MIDIValue)60)
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MIDI pitch ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
#define MIDI_PITCH_BEND_MAX ((MIDIPitchBend)8191)
|
|
#define MIDI_PITCH_BEND_RANGE ((MIDIValue)24)
|
|
#define MIDI_PITCH_BEND_CENTS (24*100)
|
|
#define MIDI_PITCH_PER_CENTS ((Scalar)MIDI_PITCH_BEND_MAX / (Scalar)MIDI_PITCH_BEND_CENTS)
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ AWE NRPN ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
#define AWE_VOL_ATTACK_TIME_NRPN ((MIDIValue)11)
|
|
#define AWE_PITCH_NRPN ((MIDIValue)16)
|
|
#define AWE_FILTER_CUTOFF_NRPN ((MIDIValue)21)
|
|
|
|
#define AWE_VOL_ATTACK_TIME_RANGE ((MIDINRPNValue)5940) // 0...5.9 secs
|
|
#define AWE_FILTER_CUTOFF_RANGE ((MIDINRPNValue)127) // 100...8000 Hz
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~ AWE effects ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
typedef enum
|
|
{
|
|
ChorusMIDIEffectType,
|
|
ReverbMIDIEffectType
|
|
} MIDIEffectType;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~ AWE resources ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
#define AWE_VOICE_COUNT (32)
|
|
#define AWE_CHANNEL_COUNT (16)
|
|
#define AWE_PERCUSSIVE_CHANNEL (9)
|
|
|
|
//
|
|
// Driver type to use
|
|
//
|
|
#define _MIDI_DRIVER_TYPE _MIDI_AWE32
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MIDIMessage ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
#define MIDI_MESSAGE_SIZE (3)
|
|
|
|
class MIDIMessage SIGNATURED
|
|
{
|
|
friend class AudioCard;
|
|
friend std::ostream& operator << (std::ostream &strm, const MIDIMessage &midi_message);
|
|
|
|
public:
|
|
MIDIMessage(MIDIStatus status, MIDIChannel channel, MIDIValue byte1, MIDIValue byte2 = 0);
|
|
~MIDIMessage() {}
|
|
|
|
Logical TestInstance() const;
|
|
|
|
private:
|
|
MIDIValue message[MIDI_MESSAGE_SIZE];
|
|
};
|
|
|
|
inline MIDIMessage::MIDIMessage(MIDIStatus status, MIDIChannel channel, MIDIValue byte1, MIDIValue byte2)
|
|
{
|
|
message[0] = (MIDIValue)((status << 4) | channel);
|
|
message[1] = byte1;
|
|
message[2] = byte2;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioChannel ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
typedef enum
|
|
{
|
|
InstrumentAudioChannelType = 0,
|
|
PercussionAudioChannelType
|
|
} AudioChannelType;
|
|
|
|
class AudioCard;
|
|
|
|
/*class AudioChannel : public Plug
|
|
{
|
|
public:
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Constructor, Destructor, Testing
|
|
//--------------------------------------------------------------------
|
|
//
|
|
AudioChannel(AudioChannelType audio_channel_type, MIDIChannel midi_channel, AudioCard *audio_card);
|
|
~AudioChannel();
|
|
|
|
Logical TestInstance() const;
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Accessors
|
|
//--------------------------------------------------------------------
|
|
//
|
|
AudioChannelType GetAudioChannelType() { return audioChannelType; }
|
|
MIDIChannel GetChannelNumber() { return midiChannel; }
|
|
void SetVoicesUsed(AudioVoiceCount voices_used) { voicesUsed = voices_used; }
|
|
AudioVoiceCount GetVoicesUsed() { return voicesUsed; }
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// MIDI methods
|
|
//--------------------------------------------------------------------
|
|
//
|
|
void SendNoteOn(MIDIValue key, MIDIValue velocity);
|
|
MIDIValue myPreset;
|
|
MIDIValue myBank;
|
|
void SendNoteOff(MIDIValue key, MIDIValue velocity);
|
|
void SendProgramChange(MIDIValue program);
|
|
void SendController(MIDIValue controller, MIDIValue value);
|
|
void SendPolyKeyPressure(MIDIValue key, MIDIValue value);
|
|
void SendChannelPressure(MIDIValue value);
|
|
void SendPitchBend(MIDIPitchBend pitch_bend);
|
|
void SendPitchBendRange(MIDIValue sensitivity);
|
|
void SendNRPN(MIDIValue paramNumber, MIDINRPNValue value);
|
|
void SelectEffect(MIDIEffectType type, MIDIValue effect);
|
|
void SelectBank(MIDIValue bank);
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Release
|
|
//--------------------------------------------------------------------
|
|
//
|
|
void Release();
|
|
|
|
private:
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Private data
|
|
//--------------------------------------------------------------------
|
|
//
|
|
AudioChannelType
|
|
audioChannelType;
|
|
MIDIChannel midiChannel;
|
|
AudioVoiceCount voicesUsed;
|
|
AudioCard *audioCard;
|
|
};*/
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioCard ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
/*class AudioCard : public Node
|
|
{
|
|
friend class AudioHardware;
|
|
|
|
public:
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Constructor, Destructor, Testing
|
|
//--------------------------------------------------------------------
|
|
//
|
|
AudioCard();
|
|
~AudioCard();
|
|
|
|
Logical TestInstance() const;
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Public methods
|
|
//--------------------------------------------------------------------
|
|
//
|
|
void Initialize();
|
|
void Close();
|
|
void GetEnvironmentSettings(char *env_str);
|
|
|
|
Logical InitMIDIReceive();
|
|
void CloseMIDIReceive();
|
|
Logical IsMIDIAvailable();
|
|
MIDIValue GetMIDIByte();
|
|
|
|
void InitializeChannels();
|
|
int LoadSBK(const char *file_name);
|
|
void ReleaseAllBanks();
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// MIDI methods
|
|
//--------------------------------------------------------------------
|
|
//
|
|
void SendNoteOn(MIDIChannel channel, MIDIValue key, MIDIValue velocity);
|
|
void SendNoteOff(MIDIChannel channel, MIDIValue key, MIDIValue velocity);
|
|
void SendProgramChange(MIDIChannel channel, MIDIValue program);
|
|
void SendController(MIDIChannel channel, MIDIValue controller, MIDIValue value);
|
|
void SendPolyKeyPressure(MIDIChannel channel, MIDIValue key, MIDIValue value);
|
|
void SendChannelPressure(MIDIChannel channel, MIDIValue value);
|
|
void SendPitchBendRange(MIDIChannel channel, MIDIValue sensitivity);
|
|
void SendPitchBend(MIDIChannel channel, MIDIPitchBend pitch_bend);
|
|
void SendSysex(void *data, size_t length);
|
|
void SendNRPN(MIDIChannel channel, MIDIValue paramNumber, MIDINRPNValue value);
|
|
void SelectEffect(MIDIEffectType type, MIDIValue effect);
|
|
void SelectBank(MIDIChannel channel, MIDIValue bank);
|
|
void SendMIDIMessage(MIDIMessage *midi_message, Word length);
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Channel methods
|
|
//--------------------------------------------------------------------
|
|
//
|
|
AudioChannel* RequestAudioChannel(AudioVoiceCount voice_count);
|
|
void ReleaseAudioChannel(AudioChannel *audio_channel);
|
|
|
|
private:
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Private methods
|
|
//--------------------------------------------------------------------
|
|
//
|
|
WORD GetHexWord(const char ** s);
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Private data
|
|
//--------------------------------------------------------------------
|
|
//
|
|
|
|
//
|
|
// Card addresses
|
|
//
|
|
WORD srcAddx;
|
|
WORD irqInterrupt;
|
|
WORD dmaChannel;
|
|
WORD mpuAddx;
|
|
WORD emuAddx;
|
|
|
|
//
|
|
// Structure for the MIDI driver initialization function
|
|
// Structure for the MIDI driver hardware
|
|
//
|
|
#ifdef AUDIO_HARDWARE
|
|
_SOS_MIDI_INIT_DRIVER sSOSMIDIInitDriver;
|
|
_SOS_MIDI_HARDWARE sSOSMIDIHardware;
|
|
#endif
|
|
|
|
//
|
|
// Handle for the loaded MIDI driver
|
|
//
|
|
WORD wDriverHandle;
|
|
|
|
//
|
|
// Number of free voices
|
|
//
|
|
AudioVoiceCount
|
|
idleVoices;
|
|
|
|
//
|
|
// Idle channels
|
|
//
|
|
ChainOf<AudioChannel*> idleChannelSocket;
|
|
ChainOf<AudioChannel*> percussionChannelSocket;
|
|
};*/
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioHardware ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
/*class AudioHardware : public Node
|
|
{
|
|
public:
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Constructor, Destructor, Testing
|
|
//--------------------------------------------------------------------
|
|
//
|
|
AudioHardware();
|
|
~AudioHardware();
|
|
|
|
Logical TestInstance() const;
|
|
static Logical TestClass();
|
|
static Logical ProfileClass();
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Public methods
|
|
//--------------------------------------------------------------------
|
|
//
|
|
void Initialize();
|
|
void Close();
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Card Access
|
|
//--------------------------------------------------------------------
|
|
//
|
|
AudioCard* GetFrontCard();
|
|
|
|
AudioCard* GetRearCard();
|
|
|
|
private:
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Private methods
|
|
//--------------------------------------------------------------------
|
|
//
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Private data
|
|
//--------------------------------------------------------------------
|
|
//
|
|
AudioCard frontCard;
|
|
AudioCard rearCard;
|
|
};*/
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioCard inlines ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
/*inline void AudioCard::SendMIDIMessage(MIDIMessage *midi_message, Word length)
|
|
{
|
|
#ifdef AUDIO_HARDWARE
|
|
SET_AUDIO_RENDERER_MIDI();
|
|
|
|
Check_Signature(midi_message);
|
|
#if DEBUG_LEVEL>0
|
|
switch (midi_message->message[0] >> 4)
|
|
{
|
|
case MIDI_NOTE_ON_STATUS:
|
|
case MIDI_NOTE_OFF_STATUS:
|
|
case MIDI_POLYKEY_STATUS:
|
|
case MIDI_CONTROLLER_STATUS:
|
|
case MIDI_PITCH_BEND_STATUS:
|
|
Verify(length == 3);
|
|
break;
|
|
|
|
case MIDI_PROGRAM_CHANGE_STATUS:
|
|
case MIDI_CHANNEL_PRESSURE_STATUS:
|
|
Verify(length == 2);
|
|
break;
|
|
default:
|
|
Fail("Should never reach here");
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
#if DEBUG_LEVEL>0
|
|
{
|
|
Word wError = sosMIDISendMIDIData(wDriverHandle, (char*)midi_message->message, length);
|
|
Verify(wError == _ERR_NO_ERROR);
|
|
}
|
|
#else
|
|
sosMIDISendMIDIData(wDriverHandle, (char*)midi_message->message, length);
|
|
#endif
|
|
CLEAR_AUDIO_RENDERER_MIDI();
|
|
#endif
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// MIDI methods
|
|
//--------------------------------------------------------------------
|
|
//
|
|
inline void AudioCard::SendNoteOn(MIDIChannel channel, MIDIValue key, MIDIValue velocity)
|
|
{
|
|
MIDIMessage message(MIDI_NOTE_ON_STATUS, channel, key, velocity);
|
|
SendMIDIMessage(&message, 3);
|
|
}
|
|
|
|
inline void AudioCard::SendNoteOff(MIDIChannel channel, MIDIValue key, MIDIValue velocity)
|
|
{
|
|
MIDIMessage message(MIDI_NOTE_OFF_STATUS, channel, key, velocity);
|
|
SendMIDIMessage(&message, 3);
|
|
}
|
|
|
|
inline void AudioCard::SendProgramChange(MIDIChannel channel, MIDIValue program)
|
|
{
|
|
MIDIMessage message(MIDI_PROGRAM_CHANGE_STATUS, channel, program);
|
|
SendMIDIMessage(&message, 2);
|
|
}
|
|
|
|
inline void AudioCard::SendController(MIDIChannel channel, MIDIValue controller, MIDIValue value)
|
|
{
|
|
MIDIMessage message(MIDI_CONTROLLER_STATUS, channel, controller, value);
|
|
SendMIDIMessage(&message, 3);
|
|
}
|
|
|
|
inline void AudioCard::SendPolyKeyPressure(MIDIChannel channel, MIDIValue key, MIDIValue value)
|
|
{
|
|
MIDIMessage message(MIDI_POLYKEY_STATUS, channel, key, value);
|
|
SendMIDIMessage(&message, 3);
|
|
}
|
|
|
|
inline void AudioCard::SendChannelPressure(MIDIChannel channel, MIDIValue value)
|
|
{
|
|
MIDIMessage message(MIDI_CHANNEL_PRESSURE_STATUS, channel, value);
|
|
SendMIDIMessage(&message, 2);
|
|
}
|
|
|
|
inline void AudioCard::SelectBank(MIDIChannel channel, MIDIValue bank)
|
|
{
|
|
SendController(channel, (MIDIValue)0, bank);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioChannel inlines ~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// MIDI methods
|
|
//--------------------------------------------------------------------
|
|
//
|
|
inline void AudioChannel::SendNoteOn(MIDIValue key, MIDIValue velocity)
|
|
{
|
|
DEBUG_STREAM << "Sending note " << (int)key << " for bank " << (int)myBank << ", preset " << (int)myPreset << "." << std::endl << std::flush;
|
|
//Check(audioCard);
|
|
//audioCard->SendNoteOn(midiChannel,key,velocity);
|
|
}
|
|
|
|
inline void AudioChannel::SendNoteOff(MIDIValue key, MIDIValue velocity)
|
|
{
|
|
Check(audioCard);
|
|
audioCard->SendNoteOff(midiChannel,key,velocity);
|
|
}
|
|
|
|
inline void AudioChannel::SendProgramChange(MIDIValue program)
|
|
{
|
|
Check(audioCard);
|
|
myPreset = program;
|
|
audioCard->SendProgramChange(midiChannel,program);
|
|
}
|
|
|
|
inline void AudioChannel::SendController(MIDIValue controller, MIDIValue value)
|
|
{
|
|
Check(audioCard);
|
|
audioCard->SendController(midiChannel,controller,value);
|
|
}
|
|
|
|
inline void AudioChannel::SendPolyKeyPressure(MIDIValue key, MIDIValue value)
|
|
{
|
|
Check(audioCard);
|
|
audioCard->SendPolyKeyPressure(midiChannel,key,value);
|
|
}
|
|
|
|
inline void AudioChannel::SendChannelPressure(MIDIValue value)
|
|
{
|
|
Check(audioCard);
|
|
audioCard->SendChannelPressure(midiChannel,value);
|
|
}
|
|
|
|
inline void AudioChannel::SendPitchBend(MIDIPitchBend pitch_bend)
|
|
{
|
|
Check(audioCard);
|
|
audioCard->SendPitchBend(midiChannel,pitch_bend);
|
|
}
|
|
|
|
inline void AudioChannel::SendPitchBendRange(MIDIValue sensitivity)
|
|
{
|
|
Check(audioCard);
|
|
audioCard->SendPitchBendRange(midiChannel,sensitivity);
|
|
}
|
|
|
|
inline void AudioChannel::SelectEffect(MIDIEffectType type, MIDIValue effect)
|
|
{
|
|
Check(audioCard);
|
|
audioCard->SelectEffect(type,effect);
|
|
}
|
|
|
|
inline void AudioChannel::SendNRPN(MIDIValue paramNumber, MIDINRPNValue value)
|
|
{
|
|
Check(audioCard);
|
|
audioCard->SendNRPN(midiChannel,paramNumber,value);
|
|
}
|
|
|
|
inline void AudioChannel::SelectBank(MIDIValue bank)
|
|
{
|
|
Check(audioCard);
|
|
myBank = bank;
|
|
audioCard->SelectBank(midiChannel,bank);
|
|
}
|
|
|
|
inline void AudioChannel::Release()
|
|
{
|
|
Check(audioCard);
|
|
audioCard->ReleaseAudioChannel(this);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~ AudioHardware inlines ~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
inline AudioCard* AudioHardware::GetFrontCard()
|
|
{
|
|
Check(this);
|
|
return &frontCard;
|
|
}
|
|
|
|
inline AudioCard* AudioHardware::GetRearCard()
|
|
{
|
|
Check(this);
|
|
return &rearCard;
|
|
}*/
|