code:
#pragma once

struct tMessage
{
	wchar *m_pText;
	uint16 m_nFlag;
	uint32 m_nTime;
	uint32 m_nStartTime;
	int32 m_nNumber[6];
	wchar *m_pString;
};

struct tBigMessage
{
	tMessage m_Stack[4];
};

struct tPreviousBrief
{
	wchar *m_pText;
	int32 m_nNumber[6];
	wchar *m_pString;
};

#define NUMBRIEFMESSAGES 8
#define NUMBIGMESSAGES 6
#define NUMPREVIOUSBRIEFS 5

class CMessages
{
public:
	static tMessage BriefMessages[NUMBRIEFMESSAGES];
	static tBigMessage BIGMessages[NUMBIGMESSAGES];
	static tPreviousBrief PreviousBriefs[NUMPREVIOUSBRIEFS];
	static char PreviousMissionTitle[16]; // unused
public:
	static void Init(void);
	static uint16 GetWideStringLength(wchar *src);
	static void WideStringCopy(wchar *dst, wchar *src, uint16 size);
	static bool WideStringCompare(wchar *str1, wchar *str2, uint16 size);
	static void Process(void);
	static void Display(void);
	static void AddMessage(wchar *key, uint32 time, uint16 pos);
	static void AddMessageJumpQ(wchar *key, uint32 time, uint16 pos);
	static void AddMessageSoon(wchar *key, uint32 time, uint16 pos);
	static void ClearMessages(void);
	static void ClearSmallMessagesOnly(void);
	static void AddBigMessage(wchar *key, uint32 time, uint16 pos);
	static void AddBigMessageQ(wchar *key, uint32 time, uint16 pos);
	static void AddToPreviousBriefArray(wchar *text, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6, wchar *string);
	static void InsertNumberInString(wchar *src, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6, wchar *dst);
	static void InsertStringInString(wchar *str1, wchar *str2);
	static void InsertPlayerControlKeysInString(wchar *src);
	static void AddMessageWithNumber(wchar *key, uint32 time, uint16 pos, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6);
	static void AddMessageJumpQWithNumber(wchar *key, uint32 time, uint16 pos, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6);
	static void AddMessageSoonWithNumber(wchar *key, uint32 time, uint16 pos, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6);
	static void AddBigMessageWithNumber(wchar *key, uint32 time, uint16 pos, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6);
	static void AddBigMessageWithNumberQ(wchar *key, uint32 time, uint16 pos, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6);
	static void AddMessageWithString(wchar *text, uint32 time, uint16 flag, wchar *str);
	static void AddMessageJumpQWithString(wchar *text, uint32 time, uint16 flag, wchar *str);
	static void ClearThisPrint(wchar *str);
	static void ClearThisBigPrint(wchar *str);
	static void ClearAllMessagesDisplayedByGame(void);

	// unused or cut
	//static void AddMessageSoonWithString(wchar*, uint32, uint16, wchar*);
	//static void CutString(int16, char*, char**);
	//static void PrintString(char*, int16, int16, int16);
};


code:
#include "common.h"

#include "Pager.h"
#include "Timer.h"
#include "Messages.h"
#include "Hud.h"
#include "Camera.h"

void
CPager::Init()
{
	ClearMessages();
	m_nNumDisplayLetters = 8;
}

void
CPager::Process()
{
	if (m_messages[0].m_pText != nil && m_messages[0].m_nCurrentPosition >= (int32)m_messages[0].m_nStringLength) {
		m_messages[0].m_pText = nil;
		uint16 i = 0;
		while (i < NUMPAGERMESSAGES-1) {
			if (m_messages[i + 1].m_pText == nil) break;
			m_messages[i] = m_messages[i + 1];
			i++;
		}
		m_messages[i].m_pText = nil;
		if (m_messages[0].m_pText != nil)
			CMessages::AddToPreviousBriefArray(
				m_messages[0].m_pText,
				m_messages[0].m_nNumber[0],
				m_messages[0].m_nNumber[1],
				m_messages[0].m_nNumber[2],
				m_messages[0].m_nNumber[3],
				m_messages[0].m_nNumber[4],
				m_messages[0].m_nNumber[5],
				0);
	}
	Display();
	if (m_messages[0].m_pText != nil) {
		if (TheCamera.m_WideScreenOn || !CHud::m_Wants_To_Draw_Hud || CHud::m_BigMessage[0][0] || CHud::m_BigMessage[2][0]) {
			RestartCurrentMessage();
		} else {
			if (CTimer::GetTimeInMilliseconds() > m_messages[0].m_nTimeToChangePosition) {
				m_messages[0].m_nCurrentPosition++;
				m_messages[0].m_nTimeToChangePosition = CTimer::GetTimeInMilliseconds() + m_messages[0].m_nSpeedMs;
			}
		}
	}
}

void
CPager::Display()
{
	wchar outstr1[256];
	wchar outstr2[260];

	wchar *pText = m_messages[0].m_pText;
	uint16 i = 0;
	if (pText != nil) {
		CMessages::InsertNumberInString(
			pText,
			m_messages[0].m_nNumber[0],
			m_messages[0].m_nNumber[1],
			m_messages[0].m_nNumber[2],
			m_messages[0].m_nNumber[3],
			m_messages[0].m_nNumber[4],
			m_messages[0].m_nNumber[5],
			outstr1);
		for (; i < m_nNumDisplayLetters; i++) {
			int pos = m_messages[0].m_nCurrentPosition + i;
			if (pos >= 0) {
				if (!outstr1[pos]) break;

				outstr2[i] = outstr1[pos];
			} else {
				outstr2[i] = ' ';
			}
		}
	}
	outstr2[i] = '\0';
	CHud::SetPagerMessage(outstr2);
}

void
CPager::AddMessage(wchar *str, uint16 speed, uint16 priority, uint16 a5)
{
	uint16 size = CMessages::GetWideStringLength(str);
	for (int32 i = 0; i < NUMPAGERMESSAGES; i++) {
		if (m_messages[i].m_pText) {
			if (m_messages[i].m_nPriority >= priority)
				continue;

			for (int j = NUMPAGERMESSAGES-1; j > i; j--)
				m_messages[j] = m_messages[j-1];

		}
		m_messages[i].m_pText = str;
		m_messages[i].m_nSpeedMs = speed;
		m_messages[i].m_nPriority = priority;
		m_messages[i].unused = a5;
		m_messages[i].m_nCurrentPosition = -(m_nNumDisplayLetters + 10);
		m_messages[i].m_nTimeToChangePosition = CTimer::GetTimeInMilliseconds() + speed;
		m_messages[i].m_nStringLength = size;
		m_messages[i].m_nNumber[0] = -1;
		m_messages[i].m_nNumber[1] = -1;
		m_messages[i].m_nNumber[2] = -1;
		m_messages[i].m_nNumber[3] = -1;
		m_messages[i].m_nNumber[4] = -1;
		m_messages[i].m_nNumber[5] = -1;

		if (i == 0)
			CMessages::AddToPreviousBriefArray(
				m_messages[0].m_pText,
				m_messages[0].m_nNumber[0],
				m_messages[0].m_nNumber[1],
				m_messages[0].m_nNumber[2],
				m_messages[0].m_nNumber[3],
				m_messages[0].m_nNumber[4],
				m_messages[0].m_nNumber[5],
				nil);
			return;
	}
}

void
CPager::AddMessageWithNumber(wchar *str, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6, uint16 speed, uint16 priority, uint16 a11)
{
	wchar nstr[520];

	CMessages::InsertNumberInString(str, n1, n2, n3, n4, n5, n6, nstr);
	uint16 size = CMessages::GetWideStringLength(nstr);
	for (int32 i = 0; i < NUMPAGERMESSAGES; i++) {
		if (m_messages[i].m_pText) {
			if (m_messages[i].m_nPriority >= priority)
				continue;

			for (int j = NUMPAGERMESSAGES-1; j > i; j--)
				m_messages[j] = m_messages[j - 1];

		}
		m_messages[i].m_pText = str;
		m_messages[i].m_nSpeedMs = speed;
		m_messages[i].m_nPriority = priority;
		m_messages[i].unused = a11;
		m_messages[i].m_nCurrentPosition = -(m_nNumDisplayLetters + 10);
		m_messages[i].m_nTimeToChangePosition = CTimer::GetTimeInMilliseconds() + speed;
		m_messages[i].m_nStringLength = size;
		m_messages[i].m_nNumber[0] = n1;
		m_messages[i].m_nNumber[1] = n2;
		m_messages[i].m_nNumber[2] = n3;
		m_messages[i].m_nNumber[3] = n4;
		m_messages[i].m_nNumber[4] = n5;
		m_messages[i].m_nNumber[5] = n6;

		if (i == 0)
			CMessages::AddToPreviousBriefArray(
				m_messages[0].m_pText,
				m_messages[0].m_nNumber[0],
				m_messages[0].m_nNumber[1],
				m_messages[0].m_nNumber[2],
				m_messages[0].m_nNumber[3],
				m_messages[0].m_nNumber[4],
				m_messages[0].m_nNumber[5],
				nil);
		return;
	}
}

void
CPager::ClearMessages()
{
	for (int32 i = 0; i < NUMPAGERMESSAGES; i++)
		m_messages[i].m_pText = nil;
}

void
CPager::RestartCurrentMessage()
{
	if (m_messages[0].m_pText != nil) {
		m_messages[0].m_nCurrentPosition = -(m_nNumDisplayLetters + 10);
		m_messages[0].m_nTimeToChangePosition = CTimer::GetTimeInMilliseconds() + m_messages[0].m_nSpeedMs;
	}
}

license: mit


code:
#pragma once

struct PagerMessage {
	wchar *m_pText;
	uint16 m_nSpeedMs;
	int16 m_nCurrentPosition;
	uint16 m_nStringLength;
	uint16 m_nPriority;
	uint32 m_nTimeToChangePosition;
	int16 unused; // but still set in SCM. importance? ringtone?
	int32 m_nNumber[6];
};

#define NUMPAGERMESSAGES 8

class CPager
{
	int16 m_nNumDisplayLetters;
	PagerMessage m_messages[NUMPAGERMESSAGES];
public:
	void Init();
	void Process();
	void Display();
	void AddMessage(wchar*, uint16, uint16, uint16);
	void AddMessageWithNumber(wchar *str, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6, uint16 speed, uint16 priority, uint16 a11);
	void ClearMessages();
	void RestartCurrentMessage();
};


code:
#include "common.h"

#include "FileMgr.h"
#ifdef MORE_LANGUAGES
#include "Game.h"
#endif
#include "Frontend.h"
#include "Messages.h"
#include "Text.h"

wchar WideErrorString[25];

CText TheText;

CText::CText(void)
{
	encoding = 'e';
	memset(WideErrorString, 0, sizeof(WideErrorString));
}

void
CText::Load(void)
{
	uint8 *filedata;
	char filename[32], type[4];
	ssize_t offset, length;
	size_t sectlen;

	Unload();
	filedata = new uint8[0x40000];

	CFileMgr::SetDir("TEXT");
	switch(CMenuManager::m_PrefsLanguage){
	case CMenuManager::LANGUAGE_AMERICAN:
		sprintf(filename, "AMERICAN.GXT");
		break;
	case CMenuManager::LANGUAGE_FRENCH:
		sprintf(filename, "FRENCH.GXT");
		break;
	case CMenuManager::LANGUAGE_GERMAN:
		sprintf(filename, "GERMAN.GXT");
		break;
	case CMenuManager::LANGUAGE_ITALIAN:
		sprintf(filename, "ITALIAN.GXT");
		break;
	case CMenuManager::LANGUAGE_SPANISH:
		sprintf(filename, "SPANISH.GXT");
		break;
#ifdef MORE_LANGUAGES
	case CMenuManager::LANGUAGE_POLISH:
		sprintf(filename, "POLISH.GXT");
		break;
	case CMenuManager::LANGUAGE_RUSSIAN:
		sprintf(filename, "RUSSIAN.GXT");
		break;
	case CMenuManager::LANGUAGE_JAPANESE:
		sprintf(filename, "JAPANESE.GXT");
		break;
#endif
	}

	length = CFileMgr::LoadFile(filename, filedata, 0x40000, "rb");
	CFileMgr::SetDir("");

	offset = 0;
	while(offset < length){
		type[0] = filedata[offset++];
		type[1] = filedata[offset++];
		type[2] = filedata[offset++];
		type[3] = filedata[offset++];
		sectlen = (int)filedata[offset+3]<<24 | (int)filedata[offset+2]<<16 |
			(int)filedata[offset+1]<<8 | (int)filedata[offset+0];
		offset += 4;
		if(sectlen != 0){
			if(strncmp(type, "TKEY", 4) == 0)
				keyArray.Load(sectlen, filedata, &offset);
			else if(strncmp(type, "TDAT", 4) == 0)
				data.Load(sectlen, filedata, &offset);
			else
				offset += sectlen;
		}
	}

	keyArray.Update(data.chars);

	delete[] filedata;
}

void
CText::Unload(void)
{
	CMessages::ClearAllMessagesDisplayedByGame();
	data.Unload();
	keyArray.Unload();
}

wchar*
CText::Get(const char *key)
{
#if defined (FIX_BUGS) || defined(FIX_BUGS_64)
	return keyArray.Search(key, data.chars);
#else
	return keyArray.Search(key);
#endif
}

wchar UpperCaseTable[128] = {
	128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138,
	139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
	150, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137,
	138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148,
	149, 173, 173, 175, 176, 177, 178, 179, 180, 181, 182,
	183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193,
	194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204,
	205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215,
	216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226,
	227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237,
	238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248,
	249, 250, 251, 252, 253, 254, 255
};

wchar FrenchUpperCaseTable[128] = {
	128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138,
	139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
	150, 65, 65, 65, 65, 132, 133, 69, 69, 69, 69, 73, 73,
	73, 73, 79, 79, 79, 79, 85, 85, 85, 85, 173, 173, 175,
	176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186,
	187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197,
	198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208,
	209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219,
	220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230,
	231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241,
	242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252,
	253, 254, 255
};

wchar
CText::GetUpperCase(wchar c)
{
	switch (encoding)
	{
	case 'e':
		if (c >= 'a' && c <= 'z')
			return c - 32;
		break;
	case 'f':
		if (c >= 'a' && c <= 'z')
			return c - 32;

		if (c >= 128 && c <= 255)
			return FrenchUpperCaseTable[c-128];
		break;
	case 'g':
	case 'i':
	case 's':
		if (c >= 'a' && c <= 'z')
			return c - 32;

		if (c >= 128 && c <= 255)
			return UpperCaseTable[c-128];
		break;
	default:
		break;
	}
	return c;
}

void
CText::UpperCase(wchar *s)
{
	while(*s){
		*s = GetUpperCase(*s);
		s++;
	}
}

void
CKeyArray::Load(size_t length, uint8 *data, ssize_t *offset)
{
	size_t i;
	uint8 *rawbytes;

	// You can make numEntries size_t if you want to exceed 32-bit boundaries, everything else should be ready.
	numEntries = (int)(length / sizeof(CKeyEntry));
	entries = new CKeyEntry[numEntries];
	rawbytes = (uint8*)entries;

	for(i = 0; i < length; i++)
		rawbytes[i] = data[(*offset)++];
}

void
CKeyArray::Unload(void)
{
	delete[] entries;
	entries = nil;
	numEntries = 0;
}

void
CKeyArray::Update(wchar *chars)
{
#if !defined(FIX_BUGS) && !defined(FIX_BUGS_64)
	int i;
	for(i = 0; i < numEntries; i++)
		entries[i].value = (wchar*)((uint8*)chars + (uintptr)entries[i].value);
#endif
}

CKeyEntry*
CKeyArray::BinarySearch(const char *key, CKeyEntry *entries, int16 low, int16 high)
{
	int mid;
	int diff;

	if(low > high)
		return nil;

	mid = (low + high)/2;
	diff = strcmp(key, entries[mid].key);
	if(diff == 0)
		return &entries[mid];
	if(diff < 0)
		return BinarySearch(key, entries, low, mid-1);
	if(diff > 0)
		return BinarySearch(key, entries, mid+1, high);
	return nil;
}

wchar*
#if defined (FIX_BUGS) || defined(FIX_BUGS_64)
CKeyArray::Search(const char *key, wchar *data)
#else
CKeyArray::Search(const char *key)
#endif
{
	CKeyEntry *found;
	char errstr[25];
	int i;

#if defined (FIX_BUGS) || defined(FIX_BUGS_64)
	found = BinarySearch(key, entries, 0, numEntries-1);
	if(found)
		return (wchar*)((uint8*)data + found->valueOffset);
#else
	found = BinarySearch(key, entries, 0, numEntries-1);
	if(found)
		return found->value;
#endif
	sprintf(errstr, "%s missing", key);
	for(i = 0; i < 25; i++)
		WideErrorString[i] = errstr[i];
	return WideErrorString;
}

void
CData::Load(size_t length, uint8 *data, ssize_t *offset)
{
	size_t i;
	uint8 *rawbytes;

	// You can make numChars size_t if you want to exceed 32-bit boundaries, everything else should be ready.
	numChars = (int)(length / sizeof(wchar));
	chars = new wchar[numChars];
	rawbytes = (uint8*)chars;

	for(i = 0; i < length; i++)
		rawbytes[i] = data[(*offset)++];
}

void
CData::Unload(void)
{
	delete[] chars;
	chars = nil;
	numChars = 0;
}

char*
UnicodeToAscii(wchar *src)
{
	static char aStr[256];
	int len;
	for(len = 0; *src != '\0' && len < 256-1; len++, src++)
#ifdef MORE_LANGUAGES
		if(*src < 128 || ((CGame::russianGame || CGame::japaneseGame) && *src < 256))
#else
		if(*src < 128)
#endif
			aStr[len] = *src;
		else
			aStr[len] = '#';
	aStr[len] = '\0';
	return aStr;
}

char*
UnicodeToAsciiForSaveLoad(wchar *src)
{
	static char aStr[256];
	int len;
	for(len = 0; *src != '\0' && len < 256; len++, src++)
		if(*src < 256)
			aStr[len] = *src;
		else
			aStr[len] = '#';
	aStr[len] = '\0';
	return aStr;
}

char*
UnicodeToAsciiForMemoryCard(wchar *src)
{
	static char aStr[256];
	int len;
	for(len = 0; *src != '\0' && len < 256; len++, src++)
		if(*src < 256)
			aStr[len] = *src;
		else
			aStr[len] = '#';
	aStr[len] = '\0';
	return aStr;
}

void
TextCopy(wchar *dst, const wchar *src)
{
	while((*dst++ = *src++) != '\0');
}


code:
#pragma once

char *UnicodeToAscii(wchar *src);
char *UnicodeToAsciiForSaveLoad(wchar *src);
char *UnicodeToAsciiForMemoryCard(wchar *src);
void TextCopy(wchar *dst, const wchar *src);

struct CKeyEntry
{
#if defined(FIX_BUGS) || defined(FIX_BUGS_64)
	uint32 valueOffset;
#else
	wchar *value;
#endif
	char key[8];
};

// If this fails, CKeyArray::Load will have to be fixed
VALIDATE_SIZE(CKeyEntry, 12);

class CKeyArray
{
public:
	CKeyEntry *entries;
	int numEntries;	// You can make this size_t if you want to exceed 32-bit boundaries, everything else should be ready.

	CKeyArray(void) : entries(nil), numEntries(0) {}
	~CKeyArray(void) { Unload(); }
	void Load(size_t length, uint8 *data, ssize_t *offset);
	void Unload(void);
	void Update(wchar *chars);
	CKeyEntry *BinarySearch(const char *key, CKeyEntry *entries, int16 low, int16 high);
#if defined (FIX_BUGS) || defined(FIX_BUGS_64)
	wchar *Search(const char *key, wchar *data);
#else
	wchar *Search(const char *key);
#endif
};

class CData
{
public:
	wchar *chars;
	int numChars; // You can make this size_t if you want to exceed 32-bit boundaries, everything else should be ready.

	CData(void) : chars(nil), numChars(0) {}
	~CData(void) { Unload(); }
	void Load(size_t length, uint8 *data, ssize_t *offset);
	void Unload(void);
};

class CText
{
	CKeyArray keyArray;
	CData data;
	char encoding;
public:
	CText(void);
	void Load(void);
	void Unload(void);
	wchar *Get(const char *key);
	wchar GetUpperCase(wchar c);
	void UpperCase(wchar *s);
};

extern CText TheText;