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>
288 lines
6.8 KiB
C++
288 lines
6.8 KiB
C++
#include <windows.h>
|
|
#include <assert.h>
|
|
#include "L4SERIAL.h"
|
|
|
|
HANDLE InitSerialPort(WORD rate, WORD format, const char* port, bool overlapped)
|
|
{
|
|
HANDLE hComm = INVALID_HANDLE_VALUE;
|
|
|
|
hComm = CreateFileA(port, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, overlapped ? FILE_FLAG_OVERLAPPED : NULL, NULL);
|
|
if(hComm == INVALID_HANDLE_VALUE)
|
|
{
|
|
DWORD err = GetLastError();
|
|
DEBUG_STREAM << "ERROR: CreateFileA() failed [GetLastError() = " << GetLastError() << "]!\n" << std::flush;
|
|
return INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
DCB control;
|
|
memset(&control, 0, sizeof(control));
|
|
control.DCBlength = sizeof(control);
|
|
control.EvtChar = (char)0xFF;
|
|
|
|
if(rate == PCS_300)
|
|
control.BaudRate = 300;
|
|
else if(rate == PCS_1200)
|
|
control.BaudRate = 1200;
|
|
else if(rate == PCS_2400)
|
|
control.BaudRate = 2400;
|
|
else if(rate == PCS_9600)
|
|
control.BaudRate = 9600;
|
|
else if(rate == PCS_19P2)
|
|
control.BaudRate = 19200;
|
|
else if(rate == PCS_38P4)
|
|
control.BaudRate = 38400;
|
|
else if(rate == PCS_115K)
|
|
control.BaudRate = 115200;
|
|
else
|
|
{
|
|
DEBUG_STREAM << "ERROR: Invalid Baud Rate!\n" << std::flush;
|
|
CloseHandle(hComm);
|
|
return INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
if((format & PCSMAP) == PCSNP)
|
|
control.Parity = NOPARITY;
|
|
else if((format & PCSMAP) == PCSOP)
|
|
control.Parity = ODDPARITY;
|
|
else if((format & PCSMAP) == PCSEP)
|
|
control.Parity = EVENPARITY;
|
|
else if((format & PCSMAP) == PCSMP)
|
|
control.Parity = MARKPARITY;
|
|
else if((format & PCSMAP) == PCSSP)
|
|
control.Parity = SPACEPARITY;
|
|
else
|
|
{
|
|
DEBUG_STREAM << "ERROR: Invalid Parity!\n" << std::flush;
|
|
CloseHandle(hComm);
|
|
return INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
if((format & PCSMD) == PCS8D)
|
|
control.ByteSize = 8;
|
|
else if((format & PCSMD) == PCS6D)
|
|
control.ByteSize = 6;
|
|
else if((format & PCSMD) == PCS7D)
|
|
control.ByteSize = 7;
|
|
else if((format & PCSMD) == PCS8D)
|
|
control.ByteSize = 8;
|
|
else
|
|
{
|
|
DEBUG_STREAM << "ERROR: Invalid Byte Size!\n" << std::flush;
|
|
CloseHandle(hComm);
|
|
return INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
if((format & PCSMS) == PCS1S)
|
|
control.StopBits = ONESTOPBIT;
|
|
else if((format & PCSMS) == PCS2S)
|
|
control.StopBits = TWOSTOPBITS;
|
|
else
|
|
{
|
|
DEBUG_STREAM << "ERROR: Invalid Stop Bits!\n" << std::flush;
|
|
CloseHandle(hComm);
|
|
return INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
if(!SetCommState(hComm, &control))
|
|
{
|
|
DEBUG_STREAM << "ERROR: SetCommState() failed [rate = " << control.BaudRate;
|
|
DEBUG_STREAM << ", dataBits = " << control.ByteSize;
|
|
DEBUG_STREAM << ", stopBits = " << control.StopBits;
|
|
DEBUG_STREAM << ", parity = " << control.Parity << "!\n";
|
|
CloseHandle(hComm);
|
|
return INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
//Non-blocking
|
|
COMMTIMEOUTS timeout;
|
|
timeout.ReadIntervalTimeout = MAXDWORD;
|
|
timeout.ReadTotalTimeoutConstant = 0;
|
|
timeout.ReadTotalTimeoutMultiplier = 0;
|
|
timeout.WriteTotalTimeoutConstant = 0;
|
|
timeout.WriteTotalTimeoutMultiplier = 0;
|
|
if(!SetCommTimeouts(hComm, &timeout))
|
|
{
|
|
DEBUG_STREAM << "ERROR: SetCommTimeouts() failed!\n";
|
|
CloseHandle(hComm);
|
|
return INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
PurgeComm(hComm, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
|
|
|
|
return hComm;
|
|
}
|
|
|
|
//Win32 Serial support: ADB 01/11/07
|
|
PCSerial::PCSerial(WORD rate, WORD format, const char* port)
|
|
//PCSerial(DWORD rate, BYTE dataBits, BYTE stopBits, BYTE parity, const WCHAR* port)
|
|
{
|
|
enabled = 0;
|
|
|
|
mOverlappedOb.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
|
|
mOverlappedOb.Internal = 0;
|
|
mOverlappedOb.InternalHigh = 0;
|
|
mOverlappedOb.Offset = 0;
|
|
mOverlappedOb.OffsetHigh = 0;
|
|
|
|
//PCSerialInit(this, rate, format, port, int_num);
|
|
hComm = InitSerialPort(rate, format, port, true);
|
|
|
|
if(hComm != INVALID_HANDLE_VALUE)
|
|
enabled = 1;
|
|
}
|
|
|
|
PCSerial::~PCSerial()
|
|
{
|
|
if (this == NULL)
|
|
{
|
|
std::cout << "~PCSerial has been passed a NULL pointer\n" << std::flush;
|
|
}
|
|
else
|
|
{
|
|
WaitForSingleObject(mOverlappedOb.hEvent,INFINITE);
|
|
CloseHandle(mOverlappedOb.hEvent);
|
|
CloseHandle(hComm);
|
|
hComm = INVALID_HANDLE_VALUE;
|
|
enabled = 0;
|
|
}
|
|
}
|
|
|
|
//Win32 Serial support: ADB 01/15/07
|
|
Logical PCSerial::TestInstance()
|
|
{
|
|
//return PCSerialTestInstance(this);
|
|
return 1;
|
|
}
|
|
|
|
//Win32 Serial support: ADB 01/15/07
|
|
int PCSerial::ReceiveString(char *destPtr, int maxLen) // returns length
|
|
{
|
|
//TODO: Do overlapped reads if we ever need this function again
|
|
assert(FALSE);
|
|
//return PCSerialReceive(this, destPtr, maxLen)
|
|
DWORD bytesRead;
|
|
ReadFile(hComm, (void *)destPtr, maxLen, &bytesRead, NULL);
|
|
return (int)bytesRead;
|
|
}
|
|
|
|
//Win32 Serial support: ADB 01/15/07
|
|
void PCSerial::FlowControlEnable(Logical onOff)
|
|
{
|
|
//STUBBED: Serial ADB 01/15/07
|
|
//VERIFY: Probably not used
|
|
//PCSerialFlowControl(this, (int) onOff);DCB control;
|
|
}
|
|
|
|
Logical PCSerial::CanDraw()
|
|
{
|
|
return (WaitForSingleObject(mOverlappedOb.hEvent, 0) == WAIT_OBJECT_0);
|
|
}
|
|
|
|
//Win32 Serial support: ADB 01/15/07
|
|
void PCSerial::SendString(char *srcPtr, int length)
|
|
{
|
|
//PCSerialSend(this, srcPtr, length);
|
|
WaitForSingleObject(mOverlappedOb.hEvent, INFINITE);
|
|
DWORD bytesWritten;
|
|
WriteFile(hComm, (void*)srcPtr, length, &bytesWritten, &mOverlappedOb);
|
|
}
|
|
|
|
//Win32 Serial support: ADB 01/15/07
|
|
int PCSerial::ReceiveCharCount()
|
|
{
|
|
//return PCSerialReceiveCount(this);
|
|
COMMPROP props;
|
|
if(!GetCommProperties(hComm, &props))
|
|
{
|
|
DEBUG_STREAM << "ERROR: GetCommProperties() failed in TransmitCharCount()! GetLastError() = " << GetLastError() << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
return props.dwCurrentRxQueue;
|
|
}
|
|
|
|
//Win32 Serial support: ADB 01/15/07
|
|
int PCSerial::TransmitCharCount()
|
|
{
|
|
//return PCSerialTransmitCount(this);
|
|
COMMPROP props;
|
|
if(!GetCommProperties(hComm, &props))
|
|
{
|
|
DEBUG_STREAM << "ERROR: GetCommProperties() failed in TransmitCharCount()! GetLastError() = " << GetLastError() << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
return props.dwCurrentTxQueue;
|
|
}
|
|
|
|
//Win32 Serial support: ADB 01/15/07
|
|
int PCSerial::Errors() // returns error value, clears it
|
|
{
|
|
//return PCSerialErrors(this);
|
|
DWORD errors;
|
|
if(!ClearCommError(hComm, &errors, NULL))
|
|
{
|
|
DEBUG_STREAM << "ERROR: GetCommError() failed!\n";
|
|
return PCS_ERR_OTHER;
|
|
}
|
|
|
|
int outError = 0;
|
|
if(errors & CE_OVERRUN)
|
|
{
|
|
outError |= PCS_ERR_OVERRUN;
|
|
errors ^= CE_OVERRUN;
|
|
}
|
|
if(errors & CE_RXPARITY)
|
|
{
|
|
outError |= PCS_ERR_PARITY;
|
|
errors ^= CE_RXPARITY;
|
|
}
|
|
if(errors & CE_FRAME)
|
|
{
|
|
outError |= PCS_ERR_FRAMING;
|
|
errors ^= CE_FRAME;
|
|
}
|
|
if(errors & CE_BREAK)
|
|
{
|
|
outError |= PCS_ERR_BREAK;
|
|
errors ^= CE_BREAK;
|
|
}
|
|
if(errors & CE_RXOVER)
|
|
{
|
|
outError |= PCS_ERR_RXOVER;
|
|
errors ^= CE_RXOVER;
|
|
}
|
|
if(errors & CE_TXFULL)
|
|
{
|
|
outError |= PCS_ERR_TXOVER;
|
|
errors ^= CE_TXFULL;
|
|
}
|
|
//other error
|
|
if(errors)
|
|
outError |= PCS_ERR_OTHER;
|
|
|
|
return outError;
|
|
}
|
|
|
|
//Win32 Serial support: ADB 01/15/07
|
|
int PCSerial::IsActive()
|
|
{
|
|
//return PCSerialActive(this);
|
|
return enabled;
|
|
}
|
|
|
|
|
|
//Win32 Serial support: ADB 01/15/07
|
|
int PCSerial::ModemStatus()
|
|
{
|
|
//return PCSerialModemStatus(this);
|
|
DWORD modem;
|
|
if(!GetCommModemStatus(hComm, &modem))
|
|
{
|
|
DEBUG_STREAM << "ERROR: GetCommModemStatus() failed!\n";
|
|
return 0;
|
|
}
|
|
|
|
return modem;
|
|
}
|