/*
** Command & Conquer Renegade(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see .
*/
/* Copyright (C) Electronic Arts Canada Inc. 1998-1999. All rights reserved. */
#include
#include
#include "gimex.h" /* for file and memory IO only */
#include "locale.h"
//#include "wnd_file.h"
#define ASSERT assert
#define VERIFY ASSERT
/*************************************************************************/
/* File Format Structures */
/*************************************************************************/
#define LOCALEFILE_HEADERCHUNKID 0x48434f4c /* 'LOCH' */
#define LOCALEFILE_INDEXCHUNKID 0x49434f4c /* 'LOCI' */
#define LOCALEFILE_LANGUAGECHUNKID 0x4c434f4c /* 'LOCL' */
typedef struct
{
unsigned int ChunkID; /* 'LOCH' LOCALEFILE_HEADERCHUNKID */
unsigned int ChunkSize; /* size of chunk in bytes */
unsigned int Flags; /* 0=no index chunk present,1=index chunk present */
unsigned int LanguageCount; /* number of language chunks in this file */
/* unsigned int LanguageOffset[LanguageCount]; \\ offsets in bytes from start of file to language chunk */
} LOCALEFILE_HEADERCHUNK;
/* offset LOCALEFILE_HEADERCHUNK_LANGUAGE_OFFSET bytes from the start of the chunk to the language offset table */
#define LOCALEFILE_HEADERCHUNK_LANGUAGE_OFFSET sizeof(LOCALEFILE_HEADERCHUNK)
typedef struct
{
unsigned int ChunkID; /* 'LOCI' LOCALEFILE_INDEXCHUNKID */
unsigned int ChunkSize; /* size of chunk in bytes */
unsigned int StringCount; /* number of string ids in this chunk (same value in all language chunks) */
unsigned int pad; /* must be zero */
/* STRINGID StringID[StringCount]; */
/* { */
/* unsigned short ID; \\ id that user gives to look up value */
/* unsigned short Index; \\ index to look up value in language chunks */
/* } */
} LOCALEFILE_INDEXCHUNK;
/* offset LOCALEFILE_INDEXCHUNK_STRINGID_OFFSET bytes from the start of the chunk to the string id table */
#define LOCALEFILE_INDEXCHUNK_STRINGID_OFFSET sizeof(LOCALEFILE_INDEXCHUNK)
typedef struct
{
unsigned int ChunkID; /* 'LOCL' LOCALEFILE_LANGUAGECHUNKID */
unsigned int ChunkSize; /* size of chunk in bytes including this header and all string data */
unsigned int LanguageID; /* language strings are in for this bank */
unsigned int StringCount; /* number of strings in this chunk */
/* unsigned int StringOffset[StringCount]; \\ offsets in bytes from start of chunk to string */
/* const char* Data[StringCount]; \\ StringCount null terminated strings */
} LOCALEFILE_LANGUAGECHUNK;
/* offset LOCALEFILE_LANGUAGECHUNK_STRING_OFFSETbytes from the start of the chunk to the string offset table */
#define LOCALEFILE_LANGUAGECHUNK_STRING_OFFSET sizeof(LOCALEFILE_LANGUAGECHUNK)
/*************************************************************************/
/* LOCALE_INSTANCE declaration */
/*************************************************************************/
typedef LOCALEFILE_HEADERCHUNK HEADER;
typedef LOCALEFILE_INDEXCHUNK INDEX;
typedef LOCALEFILE_LANGUAGECHUNK BANK;
typedef struct
{
int BankIndex; /* current language bank set (0..BANK_COUNT-1) */
BANK* pBank[LOCALE_BANK_COUNT]; /* array of string banks */
INDEX* pIndex[LOCALE_BANK_COUNT]; /* array of string indices */
} LOCALE_INSTANCE;
static LOCALE_INSTANCE *lx = NULL;
/*************************************************************************/
/* initialization/restore */
/*************************************************************************/
/* helper function to make assertions for initialization clearer */
int LOCALE_isinitialized( void )
{
if ( lx == NULL ) {
// TRACE("LOCALE API is not initialized - call LOCALE_init before calling LOCALE functions\n");
}
return( lx != NULL );
}
/*
;
; ABSTRACT
;
; LOCALE_init - Init the localization module
;
;
; SUMMARY
;
; #include "realfont.h"
;
; int LOCALE_init(void)
;
; DESCRIPTION
;
; Initilizes everything needed to use the locale API. Can only be called
; once until LOCALE_restore is called.
;
; Returns non-zero if everything went ok.
;
; SEE ALSO
;
; LOCALE_restore
;
;
; EXAMPLE
;
; locale_eg.c
;
; Download the source
; locale_eg.csv (example data)
;
;
; END ABSTRACT
;
*/
int LOCALE_init(void)
{
int ok = 0;
/* ensure locale module is NOT already initialized */
ASSERT(lx == NULL); /* can only call LOCALE_init after a restore or once, cannot double init locale API */
if( lx != NULL )
return ok;
/* allocate instance */
lx = (LOCALE_INSTANCE*)galloc(sizeof(LOCALE_INSTANCE));
if (lx != NULL) {
memset(lx, 0, sizeof(LOCALE_INSTANCE));
ok = 1;
}
return ok;
}
/*
;
; ABSTRACT
;
; LOCALE_restore - Free resources used by the locale module
;
;
; SUMMARY
;
; #include "realfont.h"
;
; void LOCALE_restore(void)
;
; DESCRIPTION
;
; Restores all resources used by the locale API. Can only be called after
; LOCALE_init, and only once.
;
; SEE ALSO
;
; LOCALE_init
;
;
; EXAMPLE
;
; locale_eg.c
;
; Download the source
; locale_eg.csv (example data)
;
;
; END ABSTRACT
;
*/
void LOCALE_restore(void)
{
int i;
ASSERT(LOCALE_isinitialized()); /* must call LOCALE_init before calling this function */
ASSERT(lx != NULL);
if( lx != NULL ) {
/* free any language tables */
for (i = 0; i < LOCALE_BANK_COUNT; i++) {
if (lx->pBank[i]) {
LOCALE_setbank(i);
LOCALE_freetable();
}
}
/* free instance */
gfree(lx);
lx = NULL;
}
}
/*************************************************************************/
/* attributes */
/*************************************************************************/
/*
;
; ABSTRACT
;
; LOCALE_setbank - Set the current bank
;
;
; SUMMARY
;
; #include "realfont.h"
;
; void LOCALE_setbank(BankIndex)
; int BankIndex; Number between 0 and LOCALE_BANK_COUNT - 1
;
; DESCRIPTION
;
; Sets the current bank to be active. All functions will now use this
; bank for strings. A bank is slot where a string table is loaded.
; More than one bank can have a table loaded but the locale functions
; only work on one bank at a time, the active bank set by this function.
;
; SEE ALSO
;
; LOCALE_getbank
;
;
; EXAMPLE
;
; locale_eg.c
;
; Download the source
; locale_eg.csv (example data)
;
;
; END ABSTRACT
;
*/
void LOCALE_setbank(int BankIndex)
{
ASSERT(LOCALE_isinitialized()); /* must call LOCALE_init before calling this function */
if( lx != NULL ) {
lx->BankIndex = BankIndex;
}
}
/*
;
; ABSTRACT
;
; LOCALE_getbank - Get the current bank
;
;
; SUMMARY
;
; #include "realfont.h"
;
; int LOCALE_getbank(void)
;
; DESCRIPTION
;
; Returns the bank index of the current bank.
;
; SEE ALSO
;
; LOCALE_setbank
;
;
; EXAMPLE
;
; locale_eg.c
;
; Download the source
; locale_eg.csv (example data)
;
;
; END ABSTRACT
;
*/
int LOCALE_getbank(void)
{
ASSERT(LOCALE_isinitialized()); /* must call LOCALE_init before calling this function */
if( lx != NULL ) {
return lx->BankIndex;
} else {
return -1;
}
}
/*
;
; ABSTRACT
;
; LOCALE_getbanklanguageid - Get the language id for the current bank
;
;
; SUMMARY
;
; #include "realfont.h"
;
; int LOCALE_getbanklanguageid(void)
;
; DESCRIPTION
;
; Returns the language id of the current bank. This id will match
; the lanugage id in the header file generated by Locomoto
;
; SEE ALSO
;
; LOCALE_loadtable
;
; EXAMPLE
;
; locale_eg.c
;
; Download the source
; locale_eg.csv (example data)
;
;
; END ABSTRACT
;
*/
int LOCALE_getbanklanguageid(void)
{
ASSERT(LOCALE_isinitialized()); /* must call LOCALE_init before calling this function */
ASSERT(lx->pBank[lx->BankIndex]); /* must load a table into bank before calling this function */
if( lx != NULL && lx->pBank[lx->BankIndex] != NULL ) {
return (int)(lx->pBank[lx->BankIndex]->LanguageID);
} else {
return -1;
}
}
/*
;
; ABSTRACT
;
; LOCALE_getbankstringcount - Get the string count for the current bank
;
;
; SUMMARY
;
; #include "realfont.h"
;
; int LOCALE_getbankstringcount(void)
;
; DESCRIPTION
;
; Returns the number of strings in the current bank. If zero is
; returned then this bank is empty.
;
; SEE ALSO
;
; LOCALE_loadtable
;
; EXAMPLE
;
; locale_eg.c
;
; Download the source
; locale_eg.csv (example data)
;
;
; END ABSTRACT
;
*/
int LOCALE_getbankstringcount(void)
{
int StringCount = 0;
ASSERT(LOCALE_isinitialized()); /* must call LOCALE_init before calling this function */
if (lx != NULL && lx->pBank[lx->BankIndex]) {
StringCount = lx->pBank[lx->BankIndex]->StringCount;
}
return StringCount;
}
/*************************************************************************/
/* operations */
/*************************************************************************/
/*
;
; ABSTRACT
;
; LOCALE_loadtable - Load a string table into the current bank
;
;
; SUMMARY
;
; #include "realfont.h"
;
; int LOCALE_loadtable(pathname, languageid)
;
; const char* pathname; // pathname of .loc file to load
; int languageid; // language id to load (from .h file)
;
; DESCRIPTION
;
; Loads the specified language from the string file into the
; current bank. Returns non zero if the operation was succesful.
; The bank must be free before you can call LOCALE_loadtable. To
; free a bank use the LOCALE_freetable function. To determine
; if the bank is free use the LOCALE_getbankstringcount function.
;
; The languageid value is available in the .h file created by
; locomoto.
;
; Returns non-zero if everthing is ok.
;
; SEE ALSO
;
; LOCALE_freetable, LOCALE_getbankstringcount
;
; EXAMPLE
;
; locale_eg.c
;
; Download the source
; locale_eg.csv (example data)
;
;
; END ABSTRACT
;
*/
static int readheader( GSTREAM* g )
{
int ok = 0;
/* read file header */
LOCALEFILE_HEADERCHUNK header;
int HeaderChunkSize = sizeof(LOCALEFILE_HEADERCHUNK);
// VERIFY(gread(g, &header, HeaderChunkSize) == HeaderChunkSize);
if( gread(g, &header, HeaderChunkSize) != HeaderChunkSize ) {
return ok;
}
ASSERT( header.ChunkID == LOCALEFILE_HEADERCHUNKID ); /* ensure that this is a valid .loc file */
if ( header.ChunkID != LOCALEFILE_HEADERCHUNKID ) {
return ok;
}
/* read index chunk if present */
if ( header.Flags == 1 ) {
int IndexChunkSize;
int IndexChunkPos = header.ChunkSize;
/* read index chunk size */
// VERIFY(gseek(g, IndexChunkPos + 4));
if( !gseek( g, IndexChunkPos + 4)) {
return ok;
}
// VERIFY(gread(g, &IndexChunkSize, 4) == 4);
if( gread( g, &IndexChunkSize, 4) != 4 ) {
return ok;
}
/* alloc and read index chunk */
lx->pIndex[lx->BankIndex] = (LOCALEFILE_INDEXCHUNK *)galloc((long)IndexChunkSize );
if (lx->pIndex[lx->BankIndex]) {
// VERIFY(gseek(g, IndexChunkPos));
gseek( g, IndexChunkPos );
// VERIFY(gread(g, lx->pIndex[lx->BankIndex], IndexChunkSize) == IndexChunkSize);
if ( gread(g, lx->pIndex[lx->BankIndex], IndexChunkSize ) != IndexChunkSize ) {
return ok;
}
ASSERT( lx->pIndex[lx->BankIndex]->ChunkID == LOCALEFILE_INDEXCHUNKID );
if( lx->pIndex[lx->BankIndex]->ChunkID == LOCALEFILE_INDEXCHUNKID ) {
ok = 1;
}
}
}
return ok;
}
/////////////////////////////////////////////////////////////////////////////
//
// readstrings
//
/////////////////////////////////////////////////////////////////////////////
static int readstrings( GSTREAM* g, int LanguageID )
{
int ok = 0;
int LanguageChunkOffsetPos = 16 + LanguageID*4;
int LanguageChunkPos = 0;
int LanguageChunkSize = -1;
/* read offset to language chunk */
// VERIFY(gseek(g, (int)LanguageChunkOffsetPos));
// VERIFY(gread(g, &LanguageChunkPos, 4) == 4);
if( !gseek( g, (int)LanguageChunkOffsetPos )) {
return ok;
}
if( gread( g, &LanguageChunkPos, 4 ) != 4 ) {
return ok;
}
/* read language chunk size */
// VERIFY(gseek(g, LanguageChunkPos + 4));
// VERIFY(gread(g, &LanguageChunkSize, 4) == 4);
if( !gseek( g, LanguageChunkPos + 4 )) {
return ok;
}
if( gread( g, &LanguageChunkSize, 4 ) != 4 ) {
return ok;
}
/* alloc and read language chunk */
lx->pBank[lx->BankIndex] = (LOCALEFILE_LANGUAGECHUNK *)galloc((long)LanguageChunkSize);
if (lx->pBank[lx->BankIndex]) {
// VERIFY(gseek(g, LanguageChunkPos));
// VERIFY(gread(g, lx->pBank[lx->BankIndex], LanguageChunkSize) == LanguageChunkSize);
if( !gseek( g, LanguageChunkPos )) {
return ok;
}
if( gread( g, lx->pBank[lx->BankIndex], LanguageChunkSize ) != LanguageChunkSize ) {
return ok;
}
ASSERT(lx->pBank[lx->BankIndex]->ChunkID == LOCALEFILE_LANGUAGECHUNKID);
ok = 1;
}
return ok;
}
/////////////////////////////////////////////////////////////////////////////
//
// LOCALE_loadtable
//
/////////////////////////////////////////////////////////////////////////////
int LOCALE_loadtable( const char* PathName, int LanguageID )
{
int ok = 0;
GSTREAM* g;
ASSERT(LOCALE_isinitialized()); /* must call LOCALE_init before calling this function */
ASSERT(lx->pBank[lx->BankIndex] == NULL); /* bank must be empty before loading a new table */
ASSERT(lx->pIndex[lx->BankIndex] == NULL); /* bank must be empty before loading a new table */
if ( !LOCALE_isinitialized())
return ok;
if( lx->pBank[lx->BankIndex] != NULL)
return ok;
if( lx->pIndex[lx->BankIndex] != NULL)
return ok;
g = gopen( PathName );
if( g != NULL ) {
if( readheader(g)) {
ok = readstrings( g, LanguageID );
}
gclose(g);
}
return ok;
}
/*
;
; ABSTRACT
;
; LOCALE_purgetable - OBSOLETE
;
; Make all references to LOCALE_freetable
;
; END ABSTRACT
;
*/
/*
;
; ABSTRACT
;
; LOCALE_freetable - Free the string table in the current bank
;
;
; SUMMARY
;
; #include "realfont.h"
;
; void LOCALE_freetable(void)
;
; DESCRIPTION
;
; Frees the table loaded in the current bank. There must be a
; table loaded in the current bank. Use LOCALE_getbankstringcount
; to determine if the bank is free or not.
;
; SEE ALSO
;
; LOCALE_loadtable, LOCALE_getbankstringcount
;
; EXAMPLE
;
; locale_eg.c
;
; Download the source
; locale_eg.csv (example data)
;
;
; END ABSTRACT
;
*/
void LOCALE_freetable(void)
{
if( lx != NULL ) {
ASSERT(LOCALE_isinitialized()); /* must call LOCALE_init before calling this function */
ASSERT(lx->pBank[lx->BankIndex]); /* table must be loaded before calling this function */
/* free string bank */
if( lx->pBank[lx->BankIndex] != NULL ) {
gfree(lx->pBank[lx->BankIndex]);
lx->pBank[lx->BankIndex] = NULL;
}
/* if the bank has an index loaded, free that as well */
if ( lx->pIndex[lx->BankIndex] != NULL ) {
gfree(lx->pIndex[lx->BankIndex]);
lx->pIndex[lx->BankIndex] = NULL;
}
}
}
/*
;
; ABSTRACT
;
; LOCALE_getstring - Return the specified string from the current bank
;
; SUMMARY
;
; #include "realfont.h"
;
; const char* LOCALE_getstring( StringID )
; int StringID; ID of string to return
;
; DESCRIPTION
;
; Returns the string specified from the current bank. There must
; be a string table loaded into the current bank. Use the String
; ID specified in the header file created by Locomoto. Note that
; the string pointer is a const pointer. Do not modify the string
; or the Locale library may return invalid results.
;
; If the .loc file was created with an index StringID can be any
; valid integer in the range 0..65535. If no index was created
; with the .loc file StringID will be a zero based array index.
;
; String is returned by const for a reason. Bad things will happen
; if you modify it. You have been warned.
;
; SEE ALSO
;
; LOCALE_loadtable, LOCALE_getbankstringcount
;
; EXAMPLE
;
; locale_eg.c
;
; Download the source
; locale_eg.csv (example data)
;
;
; END ABSTRACT
;
*/
#include // for bsearch function
/////////////////////////////////////////////////////////////////////////////
//
// compare
//
/////////////////////////////////////////////////////////////////////////////
static int compare ( const void* arg1, const void* arg2 )
{
const unsigned short* s1 = (const unsigned short*)(arg1);
const unsigned short* s2 = (const unsigned short*)(arg2);
return (*s1) - (*s2);
}
/////////////////////////////////////////////////////////////////////////////
//
// getstringbyindex
//
/////////////////////////////////////////////////////////////////////////////
static int getstringbyindex( unsigned short key, const INDEX* pIndex )
{
int index = 0;
unsigned short* result;
unsigned char* base; /* pointer to base of string id table */
ASSERT(LOCALE_isinitialized()); /* must call LOCALE_init before calling this function */
ASSERT(pIndex != NULL); /* index not loaded - .loc file must have index created (use -i option) */
if( !LOCALE_isinitialized())
return -1;
if( pIndex == NULL )
return -1;
base = ((unsigned char*)pIndex) + LOCALEFILE_INDEXCHUNK_STRINGID_OFFSET;
result = (unsigned short*)bsearch((unsigned char *)&key, base, pIndex->StringCount, 4, compare);
if (result != NULL) {
/* index is the second unsigned short */
++result;
index = *result;
} else {
index = -1;
}
return index;
}
/////////////////////////////////////////////////////////////////////////////
//
// LOCALE_getstring
//
/////////////////////////////////////////////////////////////////////////////
const char* LOCALE_getstring( int StringID )
{
const char* p; /* pointer to string, NULL if string cannot be found */
ASSERT( LOCALE_isinitialized()); /* must call LOCALE_init before calling this function */
if( !LOCALE_isinitialized())
return NULL;
/* get string array index from the index if it exists */
if ( lx->pIndex[ lx->BankIndex ] != NULL ) {
StringID = getstringbyindex((unsigned short)StringID, lx->pIndex[lx->BankIndex]);
}
if ((StringID >= 0) && (StringID < (int)(lx->pBank[lx->BankIndex]->StringCount ))) {
unsigned int offset;
p = (const char*)(lx->pBank[lx->BankIndex]);
offset = *(unsigned int*)(p + LOCALEFILE_LANGUAGECHUNK_STRING_OFFSET + StringID*4);
p += offset;
} else {
p = NULL;
}
return p;
}
/*
;
; ABSTRACT
;
; LOCALE_getstr - return selected string from the specified .loc file
;
;
; SUMMARY
;
; #include "realfont.h"
;
; const char* LOCALE_getstr(stringid)
;
; int stringid; // string id to return
;
; DESCRIPTION
;
; Returns the string identified by stringid from the specified
; .loc file. Use the string ID specified in the header file created
; by Locomoto. Note that the string pointer is a const pointer. Do
; not modify the string or the Locale library may return invalid results.
;
; If your strings are Unicode strings, cast the result to a const USTR *.
;
; If the .loc file was created with an index stringid can be any
; valid integer in the range 0..65535. If no index was created
; with the .loc file stringid will be a zero based array index.
;
; String is returned by const for a reason. Bad things will happen
; if you modify it. You have been warned.
;
; SEE ALSO
;
; EXAMPLE
;
; locale_eg.c
;
; Download the source
; locale_eg.csv (example data)
;
; END ABSTRACT
;
*/
int LOCALElanguageid = 0;
const char* LOCALE_getstr( const void* pLocFile, int StringID )
{
const char* p = NULL; /* pointer to string, NULL if string cannot be found */
HEADER* pHeader;
BANK* pBank;
ASSERT(pLocFile != NULL);
/* Must pass something in */
if( pLocFile == NULL ) {
return p;
}
pHeader = (LOCALEFILE_HEADERCHUNK*)(pLocFile);
ASSERT(pHeader->ChunkID == LOCALEFILE_HEADERCHUNKID);
ASSERT(pHeader->LanguageCount >= 1);
if( pHeader->Flags == 1 ) {
/* file has an index */
INDEX* pIndex = (INDEX*)((unsigned char*)(pLocFile) + pHeader->ChunkSize);
StringID = getstringbyindex((unsigned short)StringID, pIndex);
}
/* get pointer to string bank */
{
int offset = *((int*)(pLocFile) + 4 + LOCALElanguageid);
pBank = (BANK*)((unsigned char*)(pLocFile) + offset);
}
if ((StringID >= 0) && (StringID < (int)(pBank->StringCount))) {
unsigned int offset;
p = (const char*)(pBank);
offset = *(unsigned int*)(p + LOCALEFILE_LANGUAGECHUNK_STRING_OFFSET + StringID*4);
p += offset;
} else {
p = NULL;
}
return p;
}