Files
Descent3/Descent3/localization.cpp
2024-05-30 03:35:17 -04:00

776 lines
18 KiB
C++

/*
* Descent 3
* Copyright (C) 2024 Parallax Software
*
* 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 <http://www.gnu.org/licenses/>.
--- HISTORICAL COMMENTS FOLLOW ---
* $Logfile: /DescentIII/main/localization.cpp $
* $Revision: 18 $
* $Date: 11/05/99 4:05p $
* $Author: Jeff $
*
* Localization implementation
*
* $Log: /DescentIII/main/localization.cpp $
*
* 18 11/05/99 4:05p Jeff
* fixed bug with 'retry english' closing the file and then trying to read
* it
*
* 17 10/12/99 11:04a Jeff
* load english string table if foreign strings are not found
*
* 16 5/05/99 4:33p Jeff
* fixed Linux filename fix when opening file for second time
*
* 15 5/04/99 8:50p Jeff
* safety precautions
*
* 14 4/26/99 5:17p Jeff
* loading string tables is filename friendly (for Linux)...filename must
* be all lowercase
*
* 13 4/14/99 2:51a Jeff
* fixed some case mismatched #includes
*
* 12 2/22/99 3:19p Jeff
* added function to get current language
*
* 11 2/11/99 11:02p Jeff
* fixed bug with aux string table loading
*
* 10 2/11/99 10:35p Jeff
* added support for multiple languages in string table files
*
* 9 1/19/99 1:26p Jeff
* handled \r
*
* 8 1/18/99 12:03p Jeff
* return true if 0 strings are loaded from a correct string table, not
* false
*
* 7 1/13/99 4:05p Jeff
* when loading a script dll, a string table is looked for and
* automatically loaded and passed to the newly loaded dll on init for
* use.
*
* 6 6/26/98 4:52p Jeff
* fixed stupid
*
* 5 6/26/98 4:00p Jeff
* added parsing of escape characters for CreateStringTable()
*
* 4 6/17/98 3:28p Jeff
*
* 3 6/16/98 10:37a Jeff
* fixed bug in growstring class, parse escape chars
*
* 2 6/12/98 5:55p Jeff
* initial creation
*
* $NoKeywords: $
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include "game.h"
#include "descent.h"
#include "mono.h"
#include "cfile.h"
#include "localization.h"
#include "mem.h"
#include "ddio.h"
struct tLangTag {
const char *tag;
int length;
};
tLangTag Language_tags[] = {
{"!=!", -1}, // English
{"!G!", -1}, // German
{"!S!", -1}, // Spanish
{"!I!", -1}, // Italian
{"!F!", -1}, // French
};
int Num_languages = sizeof(Language_tags) / sizeof(tLangTag);
// The following data, in the anonymous namespace, are static to this file
namespace {
int Localization_language = -1;
int String_table_size = 0;
char **String_table = NULL;
// list of the string table files, they will be loaded in the order they are listed
const char *String_table_list[] = {"D3.STR", NULL};
const char *_Error_string = "!!ERROR MISSING STRING!!";
const char *_Empty_string = "\0";
} // namespace
void Localization_SetLanguage(int type) {
ASSERT(type >= 0 && type < Num_languages);
Localization_language = type;
}
int Localization_GetLanguage(void) { return Localization_language; }
#define COMMENT_TAG "!/!" // This line is to be ignored
#define STAG_CONTINUE -1 // this line is just a continuation of the last line
#define STAG_EMPTY -2 // empty line
#define STAG_COMMENT -3 // comment line
// 0 -> Num_languages means it's the start of a string that begins with that language
#define MAX_LINE_LENGTH 1024
#define MAX_STRING_LENGTH 8 * MAX_LINE_LENGTH
#define MAX_TAG_LENGTH 3
int GetTotalStringCount(void);
int LoadStringFile(const char *filename, int starting_offset);
int8_t _parse_line_information(char *line);
char *_parse_string_tag(char *buffer);
char *_parse_escape_chars(char *buffer);
// Call this to load up the string tables into memory
// Returns the number of strings loaded, if this is 0, then the program MUST not continue
int LoadStringTables(void) {
static bool called = false;
int old_language;
if (called) {
// Only call this guy once
Int3();
return 0;
}
called = true;
old_language = Localization_language;
int string_count = GetTotalStringCount();
if (string_count == 0) {
// attempt to load english version of language, so there is something
if (Localization_language != LANGUAGE_ENGLISH) {
Localization_language = LANGUAGE_ENGLISH;
string_count = GetTotalStringCount();
}
if (string_count == 0) {
return 0;
}
}
String_table_size = 0;
// malloc our array of char *
String_table = (char **)mem_malloc(sizeof(char *) * string_count);
if (!String_table) {
Localization_language = old_language;
return 0;
}
for (int tcount = 0; tcount < string_count; tcount++)
String_table[tcount] = NULL;
int runcount = 0;
int temp;
int index = 0;
while (String_table_list[index]) {
temp = LoadStringFile(String_table_list[index], runcount);
if (temp == 0) {
Localization_language = old_language;
return 0;
}
if (runcount + temp > string_count) {
Localization_language = old_language;
return 0;
}
runcount += temp;
index++;
}
if (runcount == 0) {
Localization_language = old_language;
return 0;
}
String_table_size = runcount;
Localization_language = old_language;
atexit(FreeStringTables);
return String_table_size;
}
// Deallocates all the memory used for the string tables
void FreeStringTables(void) {
DestroyStringTable(String_table, String_table_size);
String_table = NULL;
}
const char *GetStringFromTable(int index) {
if ((index < 0) || (index >= String_table_size))
return _Error_string;
if (!String_table[index])
return _Empty_string;
return String_table[index];
}
void FixFilenameCase(const char *filename, char *newfile) {
char path[_MAX_PATH], file[_MAX_FNAME], ext[256];
ddio_SplitPath(filename, path, file, ext);
char *p;
p = file;
while (*p) {
*p = tolower(*p);
p++;
};
p = ext;
while (*p) {
*p = tolower(*p);
p++;
};
strcat(file, ext);
if (strlen(path) > 0)
ddio_MakePath(newfile, path, file, NULL);
else
strcpy(newfile, file);
}
// Given a filename, pointer to a char * array and a pointer to an int,
// it will load the string table and fill in the information
// returns true on success
bool CreateStringTable(const char *filename, char ***table, int *size) {
ASSERT(filename);
ASSERT(Localization_language != -1);
if (!filename) {
if (table)
*table = NULL;
if (size)
*size = 0;
return false;
}
CFILE *file;
char fname[_MAX_PATH];
FixFilenameCase(filename, fname);
file = cfopen(fname, "rt");
if (!file) {
if (table)
*table = NULL;
if (size)
*size = 0;
return false;
}
int old_language, scount;
char tempbuffer[MAX_STRING_LENGTH];
old_language = Localization_language;
try_english:
// first found out how many strings total in the file
scount = 0;
while (!cfeof(file)) {
cf_ReadString(tempbuffer, MAX_LINE_LENGTH + 1, file);
if (_parse_line_information(tempbuffer) == Localization_language)
scount++;
}
cfclose(file);
if (scount == 0) {
// no strings found, if we are searching a non-English version, then
// load english versions
if (Localization_language != LANGUAGE_ENGLISH) {
Localization_language = LANGUAGE_ENGLISH;
// open the file again
file = cfopen(fname, "rt");
goto try_english;
}
// no strings found
Localization_language = old_language;
mprintf(0, "Localization: Warning, 0 strings found in %s\n", filename);
*table = NULL;
*size = 0;
return true;
}
*size = scount;
char **strtable;
// malloc our array of char *
*table = (char **)mem_malloc(sizeof(char *) * scount);
if (!*table) {
if (table)
*table = NULL;
if (size)
*size = 0;
Localization_language = old_language;
return false;
}
strtable = *table;
for (int tcount = 0; tcount < scount; tcount++)
strtable[tcount] = NULL;
// now load the strings
file = cfopen(fname, "rt");
if (!file) {
if (table)
*table = NULL;
if (size)
*size = 0;
Localization_language = old_language;
return false;
}
bool reading_string = false;
scount = 0;
GrowString string;
int line_info;
while (!cfeof(file)) {
cf_ReadString(tempbuffer, MAX_STRING_LENGTH, file);
if (scount >= 198)
scount = scount;
line_info = _parse_line_information(tempbuffer);
switch (line_info) {
case STAG_CONTINUE:
if (reading_string) {
// we need to add on to the working buffer
string += _parse_escape_chars(tempbuffer);
}
break;
case STAG_EMPTY:
// empty line !ACK! What should I do...do a new line char or skip?...skip for now
break;
case STAG_COMMENT:
// ignore this line
if (reading_string) {
// ok we're done with the string, finish it and put it in the string table
string.GetString(&strtable[scount]);
scount++;
string.Destroy();
}
reading_string = false;
break;
default: {
if (line_info >= 0 && line_info < Num_languages) {
if (reading_string) {
// ok we're done with the string, finish it and put it in the string table
string.GetString(&strtable[scount]);
scount++;
string.Destroy();
reading_string = false;
}
// see if we should read this string
if (line_info == Localization_language) {
reading_string = true;
// start filling in the buffer
string.Destroy();
string = _parse_escape_chars(_parse_string_tag(tempbuffer));
}
} else {
Int3(); // get Jeff
reading_string = false;
}
} break;
} // end switch
} // end while
if (reading_string) {
// we're at the end of the file and we're reading, so but it in the string table
string.GetString(&strtable[scount]);
scount++;
string.Destroy();
reading_string = false;
}
cfclose(file);
mprintf(0, "String Table (%s) loaded with %d strings\n", filename, *size);
Localization_language = old_language;
return (scount == (*size));
}
// Given a string table and it's count of strings, it will free up it's memory
void DestroyStringTable(char **table, int size) {
if ((size > 0) && (table)) {
for (int i = 0; i < size; i++) {
if (table[i])
mem_free(table[i]);
}
}
if (table)
mem_free(table);
}
// returns the total number of strings in all the string table files
// returns 0 on error
int GetTotalStringCount(void) {
int scount = 0;
int findex = 0;
CFILE *file;
char tempbuffer[MAX_LINE_LENGTH + 1];
ASSERT(Localization_language != -1);
while (String_table_list[findex]) {
// open the file up
char fname[_MAX_PATH];
FixFilenameCase(String_table_list[findex], fname);
file = cfopen(fname, "rt");
if (!file)
return 0;
while (!cfeof(file)) {
cf_ReadString(tempbuffer, MAX_LINE_LENGTH + 1, file);
if (_parse_line_information(tempbuffer) == Localization_language)
scount++;
}
cfclose(file);
findex++;
}
return scount;
}
// Loads a string table file, returns number of strings read if everything went ok,else 0
int LoadStringFile(const char *filename, int starting_offset) {
ASSERT(filename);
ASSERT(Localization_language != -1);
if (!filename)
return 0;
CFILE *file;
char fname[_MAX_PATH];
FixFilenameCase(filename, fname);
file = cfopen(fname, "rt");
if (!file)
return 0;
int scount = 0;
char buffer[MAX_STRING_LENGTH];
bool reading_string = false;
GrowString string;
int line_info;
while (!cfeof(file)) {
cf_ReadString(buffer, MAX_STRING_LENGTH, file);
if (scount >= 198)
scount = scount;
line_info = _parse_line_information(buffer);
switch (line_info) {
case STAG_CONTINUE:
if (reading_string) {
// we need to add on to the working buffer
string += _parse_escape_chars(buffer);
}
break;
case STAG_EMPTY:
// empty line !ACK! What should I do...do a new line char or skip?...skip for now
break;
case STAG_COMMENT:
// ignore this line
if (reading_string) {
// ok we're done with the string, finish it and put it in the string table
string.GetString(&String_table[scount + starting_offset]);
scount++;
string.Destroy();
}
reading_string = false;
break;
default: {
if (line_info >= 0 && line_info < Num_languages) {
if (reading_string) {
// ok we're done with the string, finish it and put it in the string table
string.GetString(&String_table[scount + starting_offset]);
scount++;
string.Destroy();
reading_string = false;
}
// see if we should read this string
if (line_info == Localization_language) {
reading_string = true;
// start filling in the buffer
string.Destroy();
string = _parse_escape_chars(_parse_string_tag(buffer));
}
} else {
Int3(); // get Jeff
reading_string = false;
}
} break;
} // end switch
} // end while
if (reading_string) {
// we're at the end of the file and we're reading, so but it in the string table
string.GetString(&String_table[scount + starting_offset]);
scount++;
string.Destroy();
reading_string = false;
}
cfclose(file);
return scount;
}
// returns STAG_* information about the line
int8_t _parse_line_information(char *line) {
for (int i = 0; i < Num_languages; i++) {
if (Language_tags[i].length == -1)
Language_tags[i].length = strlen(Language_tags[i].tag);
if (!strncmp(line, Language_tags[i].tag, Language_tags[i].length))
return i;
}
if (!strncmp(line, COMMENT_TAG, strlen(COMMENT_TAG)))
return STAG_COMMENT;
if (strlen(line) == 0)
return STAG_EMPTY;
return STAG_CONTINUE;
}
// parses a string_tag out
char *_parse_string_tag(char *buffer) {
int8_t i = _parse_line_information(buffer);
switch (i) {
case STAG_CONTINUE:
case STAG_EMPTY:
return buffer;
break;
case STAG_COMMENT:
return buffer + (strlen(COMMENT_TAG));
break;
default:
if (i >= 0 && i < Num_languages) {
// we have a specific language...
return buffer + (Language_tags[i].length);
}
Int3(); // Get Jeff
break;
}
return buffer;
}
// parses out escape chars...like /t,/n
char *_parse_escape_chars(char *buffer) {
char tempbuffer[MAX_STRING_LENGTH];
int t_index, b_index;
t_index = 0;
b_index = 0;
// search for a /
while (buffer[b_index] != '\0') {
// see if we are at a backslash
if (buffer[b_index] != '\\') {
tempbuffer[t_index] = buffer[b_index];
t_index++;
b_index++;
} else {
// we are at a backslash, so determine what to do
b_index++;
switch (toupper(buffer[b_index])) {
case 'T':
tempbuffer[t_index] = '\t';
break;
case 'N':
tempbuffer[t_index] = '\n';
break;
case 'R':
tempbuffer[t_index] = '\r';
break;
case '\"':
tempbuffer[t_index] = '\"';
break;
case '\\':
tempbuffer[t_index] = '\\';
break;
default:
// see if we have a number
if ((buffer[b_index] >= '0') && (buffer[b_index] <= '9')) {
// we have a number, so parse the value
uint8_t value = 0;
while ((buffer[b_index] >= '0') && (buffer[b_index] <= '9')) {
value = (value * 10) + buffer[b_index] - '0';
b_index++;
}
// adjust b_index
b_index--;
tempbuffer[t_index] = value;
}
break;
}
b_index++;
t_index++;
}
}
tempbuffer[t_index] = '\0';
strcpy(buffer, tempbuffer);
return buffer;
}
GrowString::GrowString() {
root.string_data = NULL;
root.next = NULL;
curr = &root;
}
GrowString::~GrowString() { Destroy(); }
void GrowString::operator+=(char *str) {
if (!str)
return;
if (root.string_data) {
tbufferinfo *node;
node = (tbufferinfo *)mem_malloc(sizeof(tbufferinfo));
if (!node)
return;
node->string_data = (char *)mem_malloc(strlen(str) + 2);
if (!node->string_data) {
mem_free(node);
return;
}
sprintf(node->string_data, "\n%s", str);
curr->next = node;
node->next = NULL;
curr = node;
} else {
root.string_data = (char *)mem_malloc(strlen(str) + 1);
if (!root.string_data)
return;
strcpy(root.string_data, str);
root.next = NULL;
}
}
void GrowString::Destroy(void) {
if (root.string_data)
mem_free(root.string_data);
root.string_data = NULL;
tbufferinfo *c, *next;
c = next = root.next;
while (c) {
next = c->next;
if (c->string_data)
mem_free(c->string_data);
mem_free(c);
c = next;
}
root.next = NULL;
root.string_data = NULL;
curr = &root;
}
GrowString GrowString::operator+(char *str) {
*this += str;
return *this;
}
GrowString GrowString::operator+(GrowString &gs) {
char *str = NULL;
gs.GetString(&str);
*this += str;
if (str)
mem_free(str);
return *this;
}
void GrowString::operator+=(GrowString &gs) {
char *str = NULL;
gs.GetString(&str);
*this += str;
if (str)
mem_free(str);
}
void GrowString::operator=(char *str) {
Destroy();
*this += str;
}
void GrowString::operator=(GrowString &gs) {
char *str = NULL;
gs.GetString(&str);
*this = str;
if (str)
mem_free(str);
}
void GrowString::GetString(char **str) {
*str = NULL;
int size = Length();
if (size == -1)
return;
tbufferinfo *next;
next = root.next;
*str = (char *)mem_malloc(size + 1);
strcpy(*str, root.string_data);
next = root.next;
while (next) {
if (next->string_data) {
strcat(*str, next->string_data);
}
next = next->next;
}
}
int GrowString::Length(void) {
if (!root.string_data)
return -1;
tbufferinfo *next;
next = root.next;
int size = strlen(root.string_data);
while (next) {
if (next->string_data)
size += strlen(next->string_data);
next = next->next;
}
return size;
}