- BORLAND/: Borland C++ 4.52 (chosen over 4.5 by byte-match: CODE/RP/CW32.LIB
is identical to 4.52's install lib). BCC32/TLINK32/TLIB/MAKE run natively on
Win11; CODE/BT/OPT.MAK is the shipped BTL4OPT.EXE's exact flag recipe
(extender = Borland PowerPack DPMI32, not Phar Lap TNT).
- restoration/source410/: the literal 1995-form reconstruction of the missing
BT game source (never mixed into CODE/). Round 1-3 state:
* 6 of 10 surviving original TUs COMPILE CLEAN under the period toolchain
(BTMSSN, BTCNSL, BTSCNRL, BTTEAM, BTL4MODE, BTL4ARND) - first builds
since 1996.
* BT_L4/BTL4APP.CPP pilot reconstruction: 12/12 functions, Fail() lands on
its binary-recorded line 400 exactly.
* BT/BTCNSL.HPP: console wire IDs recovered from the binary's ctors
(Killed=9, Damaged=10, ScoreUpdate=13, DeathWithoutHonor=15 [T1];
TeamScore=12 flagged [T4]).
* MUNGA/: 8 engine-header backfills back-dated from the BT412 WinTesla tree
(VDATA numbering decomp-verified; AUDREND's OpenAL-era virtual removed -
the period compiler is the drift detector).
* Tooling: backdate.py (WinTesla->1995 header transform), compile410.sh
(per-TU verification sweep under authentic OPT.MAK flags).
* README: corrected roadmap - MECH.HPP is the capstone grown with the mech
TU reconstructions; BTREG.CPP green = the header-family milestone.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
419 lines
14 KiB
C++
419 lines
14 KiB
C++
//----------------------------------------------------------------------------
|
|
// (C) Copyright 1994 by Borland International, All Rights Reserved
|
|
//
|
|
// TLocaleString implementation - localized name support
|
|
//
|
|
// NOTE: This code must reside in the same module that the strings are defined
|
|
// The cache, NativeLangId, HINSTANCE are managed on a per-module basis
|
|
// TLocaleString::NativeLangId may be user-implemented to symbol langid
|
|
// TLocaleString::Module may be reset from this to another resource DLL
|
|
//----------------------------------------------------------------------------
|
|
#if !defined(_Windows)
|
|
# define _Windows // pretend we are in windows to get the headers we need
|
|
#endif
|
|
#include <osl/locale.h>
|
|
#include <stdio.h>
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Module global default values - except for TLocaleString::NativeLangId
|
|
//
|
|
TLangId TLocaleString::SystemDefaultLangId = TLocaleString::GetSystemLangId();
|
|
TLangId TLocaleString::UserDefaultLangId = TLocaleString::GetUserLangId();
|
|
HINSTANCE TLocaleString::Module = _hInstance;
|
|
TLocaleString TLocaleString::Null = {""};
|
|
|
|
//----------------------------------------------------------------------------
|
|
// TLocaleCache definitions, private for implementation
|
|
//
|
|
|
|
#define AUTOLANG_CACHEDNEUT 0x02 // prefix indicates cache entry with neutral
|
|
#define AUTOLANG_CACHEDLOAD 0x01 // prefix indicates Neutral is not a string
|
|
const TLangId InvalidLangId = 0xFFFF;
|
|
|
|
struct TLocaleCache;
|
|
struct TLocaleCacheBase;
|
|
|
|
//
|
|
// Static object to hold destructable pointer
|
|
//
|
|
struct TLocaleCacheList {
|
|
TLocaleCacheList() : Next(0) {}
|
|
~TLocaleCacheList(); // releases cache entries
|
|
TLocaleCache* Lookup(const char* name); // returns cache entry, 0 if failed
|
|
TLocaleCacheBase* Next; // linked list of cached translations
|
|
};
|
|
|
|
//
|
|
// This base struct is used to cache failure to find language resource
|
|
//
|
|
struct TLocaleCacheBase {
|
|
long Hash; // hashed original string, for duplicate elimination
|
|
const char* Neutral; // original string, +1 if resource found and loaded
|
|
TLocaleCacheBase* Next;// linked list of cached strings, for search, cleanup
|
|
|
|
TLocaleCacheBase(const char* name, long hash);
|
|
static TLocaleCacheList CacheList;
|
|
};
|
|
|
|
//
|
|
// Buffer follows this header, sized for maximum string + null term.
|
|
//
|
|
struct TLocaleCache : public TLocaleCacheBase {
|
|
void* operator new(unsigned size, unsigned buflen);
|
|
TLocaleCache(const char* name, long hash, HRSRC rscHdl, HGLOBAL resData);
|
|
~TLocaleCache() {}
|
|
const char* Translate(TLangId lang); // (re)translate string
|
|
|
|
TLangId ActLangId; // actual language ID of cached string
|
|
TLangId ReqLangId; // requested language ID of cached string
|
|
HRSRC ResHdl; // handle returned from ::FindResource()
|
|
char Buf[1]; // first character is string type
|
|
};
|
|
|
|
//----------------------------------------------------------------------------
|
|
// TLocaleCache implementation
|
|
//
|
|
|
|
TLocaleCacheBase::TLocaleCacheBase(const char* name, long hash)
|
|
:
|
|
Neutral(name),
|
|
Hash(hash)
|
|
{
|
|
Next = CacheList.Next;
|
|
CacheList.Next = this;
|
|
}
|
|
|
|
void* TLocaleCache::operator new(unsigned size, unsigned buflen)
|
|
{
|
|
return ::operator new(size+buflen);
|
|
}
|
|
|
|
TLocaleCache::TLocaleCache(const char* name, long hash,
|
|
HRSRC resHdl, HGLOBAL resData)
|
|
:
|
|
TLocaleCacheBase(name, hash),
|
|
ResHdl(resHdl)
|
|
{
|
|
ReqLangId = ActLangId = InvalidLangId; // indicate initializing state
|
|
*(HGLOBAL*)(Buf+1) = resData; // store resource pointer temp in buffer
|
|
}
|
|
|
|
TLocaleCache* TLocaleCacheList::Lookup(const char* name)
|
|
{
|
|
const char* neut = name + 1; // skip over prefix flag char
|
|
long hash = 0;
|
|
const char* pc = name;
|
|
while (*pc)
|
|
hash = hash*2 ^ *pc++;
|
|
for (TLocaleCacheBase* entry = Next; entry; entry = entry->Next) {
|
|
if (hash == entry->Hash) {
|
|
const char* pc = entry->Neutral;
|
|
if (*pc != *neut) // Neutral points to prefix if lookup failed
|
|
pc++;
|
|
if (TLocaleString::CompareLang(pc,neut,TLocaleString::NativeLangId) != 0)
|
|
return pc == entry->Neutral ? (TLocaleCache*)entry : 0;
|
|
}
|
|
}
|
|
pc = name;
|
|
if (*name != AUTOLANG_RCID)
|
|
pc++; // '#' part of Id
|
|
HRSRC resHdl = ::FindResource(TLocaleString::Module, pc, RT_LOCALIZATION);
|
|
if (!resHdl) {
|
|
new TLocaleCacheBase(name, hash); // add cache entry for failed lookup
|
|
return 0;
|
|
}
|
|
HGLOBAL resData = ::LoadResource(TLocaleString::Module, resHdl);
|
|
if (!resData) {
|
|
return 0; // should throw exception on failure?!!
|
|
}
|
|
unsigned char far* pr = (unsigned char far*)::LockResource(resData);
|
|
int maxLen = sizeof(HGLOBAL); // scan for longest string, including null
|
|
unsigned char c = *pr; // check first byte of langid or neutral text
|
|
if (c == 0) { // check for empty resource string
|
|
::FreeResource(resData);
|
|
new TLocaleCacheBase(name, hash); // add failed cache entry if null or err
|
|
return 0;
|
|
}
|
|
if (c >= ' ') // check for unprefixed neutral string first
|
|
pr--; // cancel ++ in for loop initialization
|
|
else
|
|
pr++; // start to skip over 2-byte language id
|
|
do { // loop to check for maximum string length
|
|
unsigned char far* p = ++pr; // skip over id to start of translation
|
|
while ((c=*pr++) >= ' ') ; // skip over translation string
|
|
if ((int)(pr-p) > maxLen) // update maximum, including terminator
|
|
maxLen = (int)(pr-p);
|
|
} while(c);
|
|
TLocaleCache* cache = new(maxLen) TLocaleCache(neut, hash, resHdl, resData);
|
|
cache->Buf[0] = (*name == AUTOLANG_XLAT ? AUTOLANG_CACHEDNEUT
|
|
: AUTOLANG_CACHEDLOAD);
|
|
return cache;
|
|
}
|
|
|
|
const char* TLocaleCache::Translate(TLangId reqLang)
|
|
{
|
|
HGLOBAL resData;
|
|
if (ReqLangId == InvalidLangId) { // if first time called after construction
|
|
resData = *(HGLOBAL*)(Buf+1);
|
|
ReqLangId = reqLang;
|
|
} else {
|
|
if (Buf[0]==AUTOLANG_CACHEDNEUT && TLocaleString::IsNativeLangId(reqLang))
|
|
return Neutral;
|
|
if (reqLang == ActLangId)
|
|
return Buf+1;
|
|
if (reqLang == ReqLangId) {
|
|
if (ActLangId != InvalidLangId)
|
|
return Buf+1;
|
|
else if (Buf[0] == AUTOLANG_CACHEDNEUT)
|
|
return Neutral;
|
|
else
|
|
return 0;
|
|
}
|
|
if ((resData = ::LoadResource(TLocaleString::Module, ResHdl)) == 0)
|
|
return Neutral; // should throw exception on failure?!!
|
|
}
|
|
unsigned char far* resBuf = (unsigned char far*)::LockResource(resData);
|
|
unsigned char far* translation = 0;
|
|
unsigned char far* pr = resBuf;
|
|
int actLang = InvalidLangId;
|
|
unsigned char c;
|
|
int resLang;
|
|
while ((c = *pr) != 0) {
|
|
if (c > ' ') { // check for initial neutral string, used with CACHEDLOAD
|
|
actLang = resLang = TLocaleString::NativeLangId;
|
|
translation = pr; // lowest preference match
|
|
} else {
|
|
resLang = ((c - 1)<<10) | *++pr,pr++;
|
|
}
|
|
if (resLang == reqLang) { // exact match
|
|
translation = pr;
|
|
actLang = resLang;
|
|
break;
|
|
}
|
|
if ((char)resLang == (char)reqLang) { // base language match
|
|
if ((char)actLang != (char)reqLang || resLang == (reqLang & 0x00FF)) {
|
|
translation = pr;
|
|
actLang = resLang;
|
|
}
|
|
}
|
|
for ( ; *pr >= ' '; ++pr) ; // skip over translation string till next Id
|
|
}
|
|
const char* retVal;
|
|
if (translation) {
|
|
while (*translation < ' ') // skip over multiple language IDs
|
|
translation += 2;
|
|
if (actLang != ActLangId) { // if same as in buffer, leave alone
|
|
char* pc;
|
|
for (pr = translation, pc = Buf + 1; *pr >= ' '; )
|
|
*pc++ = *pr++;
|
|
*pc = 0;
|
|
ActLangId = actLang;
|
|
if (reqLang != ActLangId)
|
|
ReqLangId = reqLang;
|
|
}
|
|
retVal = Buf+1;
|
|
} else if (Buf[0] == AUTOLANG_CACHEDNEUT) {
|
|
retVal = Neutral;
|
|
} else {
|
|
retVal = 0;
|
|
}
|
|
::FreeResource(resData);
|
|
return retVal;
|
|
}
|
|
|
|
TLocaleCacheList TLocaleCacheBase::CacheList; // declare module-global cache
|
|
|
|
TLocaleCacheList::~TLocaleCacheList()
|
|
{
|
|
while (Next) {
|
|
TLocaleCacheBase* p = Next;
|
|
Next = Next->Next;
|
|
delete p;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// TLocaleString implementation, except for static int CompareLang(s1,s2,lang)
|
|
//
|
|
|
|
const char* TLocaleString::Translate(TLangId reqLang)
|
|
{
|
|
if (!Private) // check for null string pointer
|
|
return Private;
|
|
|
|
if (reqLang == LangNeutral)
|
|
reqLang = NativeLangId;
|
|
else if (reqLang == LangSysDefault)
|
|
reqLang = SystemDefaultLangId;
|
|
else if (reqLang == LangUserDefault)
|
|
reqLang = UserDefaultLangId;
|
|
|
|
TLocaleCache* cache;
|
|
switch (Private[0])
|
|
{
|
|
default: // untranslatable string, no prefix
|
|
return Private;
|
|
|
|
case AUTOLANG_XLAT: // not yet translated
|
|
if (IsNativeLangId(reqLang))
|
|
return Private+1; // resource name IS neutral or default name
|
|
if ((cache = TLocaleCacheBase::CacheList.Lookup(Private)) == 0)
|
|
return ++Private; // permanently bump pointer to make constant
|
|
Private = cache->Buf; // point to buffer in cache
|
|
return cache->Translate(reqLang);
|
|
|
|
case AUTOLANG_LOAD: // named resource not accessed yet
|
|
case AUTOLANG_RCID: // numeric resource not accessed yet
|
|
if ((cache = TLocaleCacheBase::CacheList.Lookup(Private)) == 0)
|
|
return (Private = 0); // permanently set pointer to null
|
|
Private = cache->Buf; // point to buffer in cache
|
|
return cache->Translate(reqLang);
|
|
|
|
case AUTOLANG_CACHEDNEUT: // string in cache with neutral pointer
|
|
case AUTOLANG_CACHEDLOAD: // string in cache with no neutral pointer
|
|
cache = (TLocaleCache*)(Private+1) - 1; // backup to point to header
|
|
return cache->Translate(reqLang);
|
|
}
|
|
}
|
|
|
|
TLocaleString::operator const char*()
|
|
{
|
|
if (Private == 0)
|
|
return 0;
|
|
|
|
switch (Private[0]) {
|
|
case AUTOLANG_XLAT: // not yet translated
|
|
case AUTOLANG_CACHEDNEUT: // translated string in cache
|
|
case AUTOLANG_CACHEDLOAD: // translated or neutral name in cache
|
|
return Private+1;
|
|
|
|
case AUTOLANG_RCID: // resource not accessed yet
|
|
case AUTOLANG_LOAD: // resource not accessed yet
|
|
return 0;
|
|
|
|
default: // untranslatable string, no prefix
|
|
return Private;
|
|
}
|
|
}
|
|
|
|
int TLocaleString::Compare(const char far* str, TLangId lang)
|
|
{
|
|
return CompareLang(this->Translate(lang), str, lang);
|
|
}
|
|
|
|
int TLocaleString::IsNativeLangId(TLangId lang)
|
|
{
|
|
return lang == NativeLangId || lang == (NativeLangId & 0x00FF);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Registration parameter structures and formatting functions
|
|
//
|
|
|
|
const char* TRegList::Lookup(const char* key, TLangId lang)
|
|
{
|
|
for (TRegItem* regItem = Items; regItem->Key != 0; regItem++) {
|
|
if (strcmp(regItem->Key, key) == 0)
|
|
if (regItem->Value.Private) // current can't test Value directly
|
|
return regItem->Value.Translate(lang);
|
|
else
|
|
return "";
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
TLocaleString& TRegList::LookupRef(const char* key)
|
|
{
|
|
for (TRegItem* regItem = Items; regItem->Key != 0; regItem++) {
|
|
if (strcmp(regItem->Key, key) == 0)
|
|
return regItem->Value;
|
|
}
|
|
return TLocaleString::Null;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
|
|
// Maximum string length for REGFORMAT w/ string arg. String is clipped if too
|
|
// long.
|
|
//
|
|
const int MaxFormatLen = 40;
|
|
|
|
char* TRegItem::RegFormat(int f, int a, int t, int d, TRegFormatHeap& heap)
|
|
{
|
|
// sprintf into sized auto buffer
|
|
// ints have a max of 11 digits: -2000000000. Add pad of 8 just in case
|
|
//
|
|
char temp[11+1+11+1+11+1+11+1 +8];
|
|
int len = sprintf(temp, "%d,%d,%d,%d", f,a,t,d);
|
|
|
|
// Check for potential overflow
|
|
//
|
|
char* str = heap.Data + heap.Used;
|
|
heap.Used += len + 1;
|
|
if (heap.Used > heap.Size)
|
|
return "";
|
|
|
|
// Copy into real static buffer & return it
|
|
//
|
|
return strcpy(str, temp);
|
|
}
|
|
|
|
char* TRegItem::RegFormat(const char* f, int a, int t, int d, TRegFormatHeap& heap)
|
|
{
|
|
// sprintf into sized auto buffer
|
|
//
|
|
char temp[MaxFormatLen+1+11+1+11+1+11+1 +8];
|
|
int len = sprintf(temp, "%.*s,%d,%d,%d", MaxFormatLen,
|
|
(char far*)f,a,t,d);
|
|
|
|
// Check for potential overflow
|
|
//
|
|
char* str = heap.Data + heap.Used;
|
|
heap.Used += len + 1;
|
|
if (heap.Used > heap.Size)
|
|
return "";
|
|
|
|
// Copy into real static buffer & return it
|
|
//
|
|
return strcpy(str, temp);
|
|
}
|
|
|
|
char* TRegItem::RegFlags(long flags, TRegFormatHeap& heap)
|
|
{
|
|
// sprintf into sized auto buffer
|
|
//
|
|
char temp[11+1 +8];
|
|
int len = sprintf(temp, "%ld", flags);
|
|
|
|
// Check for potential overflow
|
|
//
|
|
char* str = heap.Data + heap.Used;
|
|
heap.Used += len + 1;
|
|
if (heap.Used > heap.Size)
|
|
return "";
|
|
|
|
// Copy into real static buffer & return it
|
|
//
|
|
return strcpy(str, temp);
|
|
}
|
|
|
|
char* TRegItem::RegVerbOpt(int mf, int sf, TRegFormatHeap& heap)
|
|
{
|
|
// sprintf into sized auto buffer
|
|
//
|
|
char temp[11+1+11+1 +8];
|
|
int len = sprintf(temp, "%d,%d", mf, sf);
|
|
|
|
// Check for potential overflow
|
|
//
|
|
char* str = heap.Data + heap.Used;
|
|
heap.Used += len + 1;
|
|
if (heap.Used > heap.Size)
|
|
return "";
|
|
|
|
// Copy into real static buffer & return it
|
|
//
|
|
return strcpy(str, temp);
|
|
}
|