aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfusion32 <marcopuzziello@gmail.com>2025-05-22 01:02:13 -0300
committerfusion32 <marcopuzziello@gmail.com>2025-05-22 09:59:14 -0300
commitde848a4131beb288335b6aaa29c2a6a439c50e0e (patch)
tree64aceaf19d47fc3ff3b26663cb36fd1de0fcdccf
parent91d7adad320c34ff0cb25aa21b16d59b67387c43 (diff)
downloadgame-de848a4131beb288335b6aaa29c2a6a439c50e0e.tar.gz
game-de848a4131beb288335b6aaa29c2a6a439c50e0e.zip
implement script lexer
-rw-r--r--TODO.md7
-rw-r--r--build.bat3
-rw-r--r--reference/game.c1050
-rw-r--r--reference/types.hh36
-rw-r--r--src/config.cc2
-rw-r--r--src/config.hh61
-rw-r--r--src/main.cc1
-rw-r--r--src/main.hh44
-rw-r--r--src/script.cc617
-rw-r--r--src/script.hh94
-rw-r--r--src/util.cc2
11 files changed, 787 insertions, 1130 deletions
diff --git a/TODO.md b/TODO.md
index 9e80c27..8551046 100644
--- a/TODO.md
+++ b/TODO.md
@@ -7,3 +7,10 @@
## Estimate
The decompiled file has ~115K lines of C. If we take ~15K lines to be rubbish, this can be round to ~100K. Considering a low estimate of 200 lines per day, the whole process could take up to 500 days which is quite a bit but not impossible. Now considering a high estimate of 1K lines per day, it could take 100 days which is also quite a bit.
+
+## TODO AFTER FIRST PASS
+- Trim some rough edges.
+- Avoid unsafe libc functions like `strcpy`, `strncpy`, `strcat`, `sprintf` etc...
+- Handle connections inline with `poll` (probably?).
+- Review signal usage for timing (SIGALRM, etc...).
+- Support Windows.
diff --git a/build.bat b/build.bat
index ad4e303..ccee552 100644
--- a/build.bat
+++ b/build.bat
@@ -10,7 +10,8 @@ set OUTPUTEXE=out.exe
set OUTPUTPDB=out.pdb
set INSTALLDIR=..\bin
-set SRC="../src/creature.cc" "../src/main.cc" "../src/player.cc" "../src/skill.cc"
+@REM set SRC="../src/creature.cc" "../src/main.cc" "../src/player.cc" "../src/skill.cc"
+set SRC="../src/script.cc" "../src/util.cc"
cl -W3 -WX -Zi -we4244 -we4456 -we4457 -wd4996 -std:c++14 -utf-8 -MP8 -DBUILD_DEBUG=1 -DARCH_X64=1 -D_CRT_SECURE_NO_WARNINGS=1 -Fe:"%OUTPUTEXE%" -Fd:"%OUTPUTPDB%" %* %SRC% /link -subsystem:console -incremental:no -opt:ref -dynamicbase "ws2_32.lib"
if errorlevel 1 exit /b errorlevel
diff --git a/reference/game.c b/reference/game.c
index dc91196..1affc5c 100644
--- a/reference/game.c
+++ b/reference/game.c
@@ -1955,198 +1955,6 @@ void _GLOBAL__I_BeADaemon(void)
return;
}
-bool isSpace(int c)
-
-{
- bool bVar1;
-
- bVar1 = false;
- if ((((c == 0x20) || (c == 0xc)) || (c == 10)) || (((c == 0xd || (c == 9)) || (c == 0xb)))) {
- bVar1 = true;
- }
- return bVar1;
-}
-
-
-
-bool isAlpha(int c)
-
-{
- bool bVar1;
-
- bVar1 = false;
- if ((((((c - 0x61U < 0x1a) || (c - 0x41U < 0x1a)) || (c == -0x1c)) || ((c == -10 || (c == -4))))
- || ((c == -0x3c || ((c == -0x2a || (c == -0x24)))))) || (c == -0x21)) {
- bVar1 = true;
- }
- return bVar1;
-}
-
-
-
-bool isEngAlpha(int c)
-
-{
- bool bVar1;
-
- bVar1 = false;
- if ((c - 0x61U < 0x1a) || (c - 0x41U < 0x1a)) {
- bVar1 = true;
- }
- return bVar1;
-}
-
-
-
-bool isDigit(int c)
-
-{
- return c - 0x30U < 10;
-}
-
-
-
-int toLower(int c)
-
-{
- if ((c - 0x41U < 0x1a) || ((byte)((char)(c - 0x41U) + 0x81U) < 0x1f)) {
- c = c + 0x20;
- }
- return c;
-}
-
-
-
-int toUpper(int c)
-
-{
- if ((c - 0x61U < 0x1a) || ((byte)((char)(c - 0x61U) + 0x81U) < 0x1f)) {
- c = c + -0x20;
- }
- return c;
-}
-
-
-
-char * strLower(char *s)
-
-{
- char cVar1;
- int i;
- int iVar2;
-
- iVar2 = 0;
- cVar1 = *s;
- while (cVar1 != '\0') {
- if (((int)cVar1 - 0x41U < 0x1a) || ((byte)((char)((int)cVar1 - 0x41U) + 0x81U) < 0x1f)) {
- cVar1 = cVar1 + ' ';
- }
- s[iVar2] = cVar1;
- iVar2 = iVar2 + 1;
- cVar1 = s[iVar2];
- }
- return s;
-}
-
-
-
-char * strUpper(char *s)
-
-{
- char cVar1;
- int i;
- int iVar2;
-
- iVar2 = 0;
- cVar1 = *s;
- while (cVar1 != '\0') {
- if (((int)cVar1 - 0x61U < 0x1a) || ((byte)((char)((int)cVar1 - 0x61U) + 0x81U) < 0x1f)) {
- cVar1 = cVar1 + -0x20;
- }
- s[iVar2] = cVar1;
- iVar2 = iVar2 + 1;
- cVar1 = s[iVar2];
- }
- return s;
-}
-
-
-
-int stricmp(char *s1,char *s2,int Pos)
-
-{
- char cVar1;
- int iVar2;
- int iVar3;
- int i;
- int iVar4;
-
- iVar4 = 0;
- while( true ) {
- if (iVar4 == Pos) {
- return 0;
- }
- cVar1 = s1[iVar4];
- iVar2 = (int)cVar1;
- if ((iVar2 - 0x41U < 0x1a) || ((byte)((char)(iVar2 - 0x41U) + 0x81U) < 0x1f)) {
- iVar2 = iVar2 + 0x20;
- }
- iVar3 = (int)s2[iVar4];
- if ((iVar3 - 0x41U < 0x1a) || ((byte)((char)(iVar3 - 0x41U) + 0x81U) < 0x1f)) {
- iVar3 = iVar3 + 0x20;
- }
- if (iVar3 < iVar2) break;
- iVar2 = (int)cVar1;
- if ((iVar2 - 0x41U < 0x1a) || ((byte)((char)(iVar2 - 0x41U) + 0x81U) < 0x1f)) {
- iVar2 = iVar2 + 0x20;
- }
- iVar3 = (int)s2[iVar4];
- if ((iVar3 - 0x41U < 0x1a) || ((byte)((char)(iVar3 - 0x41U) + 0x81U) < 0x1f)) {
- iVar3 = iVar3 + 0x20;
- }
- if (iVar2 < iVar3) {
- return -1;
- }
- if (cVar1 == '\0') {
- return 0;
- }
- iVar4 = iVar4 + 1;
- }
- return 1;
-}
-
-
-
-char * findFirst(char *s,char c)
-
-{
- char *pcVar1;
-
- pcVar1 = strchr(s,(int)c);
- return pcVar1;
-}
-
-
-
-char * findLast(char *s,char c)
-
-{
- char *pcVar1;
- char *p;
- char *pcVar2;
-
- pcVar2 = (char *)0x0;
- while( true ) {
- pcVar1 = strchr(s,(int)c);
- if (pcVar1 == (char *)0x0) break;
- s = pcVar1 + 1;
- pcVar2 = pcVar1;
- }
- return pcVar2;
-}
-
-
-
void SetPrintFunction(TPrintFunction *Function)
{
@@ -3786,864 +3594,6 @@ LAB_0804eb1e:
return;
}
-
-
-// DWARF original prototype: void TReadScriptFile(TReadScriptFile * this)
-
-void __thiscall TReadScriptFile::TReadScriptFile(TReadScriptFile *this)
-
-{
- this->RecursionDepth = -1;
- this->Bytes = (uchar *)this->String;
- return;
-}
-
-
-
-// DWARF original prototype: void TReadScriptFile(TReadScriptFile * this)
-
-void __thiscall TReadScriptFile::TReadScriptFile(TReadScriptFile *this)
-
-{
- this->RecursionDepth = -1;
- this->Bytes = (uchar *)this->String;
- return;
-}
-
-
-
-// DWARF original prototype: void ~TReadScriptFile(TReadScriptFile * this, int __in_chrg)
-
-void __thiscall TReadScriptFile::~TReadScriptFile(TReadScriptFile *this,int __in_chrg)
-
-{
- int iVar1;
- int *piVar2;
-
- if (this->RecursionDepth != -1) {
- ::error("TReadScriptFile::~TReadScriptFile: Datei ist noch offen.\n");
- iVar1 = this->RecursionDepth;
- while (iVar1 != -1) {
- iVar1 = fclose((FILE *)this->File[iVar1]);
- if (iVar1 != 0) {
- piVar2 = __errno_location();
- ::error(&DAT_080ef4e0,*piVar2);
- }
- this->RecursionDepth = this->RecursionDepth + -1;
- iVar1 = this->RecursionDepth;
- }
- }
- return;
-}
-
-
-
-// DWARF original prototype: void ~TReadScriptFile(TReadScriptFile * this, int __in_chrg)
-
-void __thiscall TReadScriptFile::~TReadScriptFile(TReadScriptFile *this,int __in_chrg)
-
-{
- int iVar1;
- int *piVar2;
-
- if (this->RecursionDepth != -1) {
- ::error("TReadScriptFile::~TReadScriptFile: Datei ist noch offen.\n");
- iVar1 = this->RecursionDepth;
- while (iVar1 != -1) {
- iVar1 = fclose((FILE *)this->File[iVar1]);
- if (iVar1 != 0) {
- piVar2 = __errno_location();
- ::error(&DAT_080ef4e0,*piVar2);
- }
- this->RecursionDepth = this->RecursionDepth + -1;
- iVar1 = this->RecursionDepth;
- }
- }
- return;
-}
-
-
-
-// DWARF original prototype: void open(TReadScriptFile * this, char * FileName)
-
-int __thiscall TReadScriptFile::open(TReadScriptFile *this,char *__file,int __oflag,...)
-
-{
- int iVar1;
- char *p;
- int iVar2;
- char *pcVar3;
- char (*__dest) [4096];
- FILE *pFVar4;
- int *piVar5;
- undefined4 *puVar6;
- int ErrorCode;
-
- iVar1 = this->RecursionDepth;
- iVar2 = iVar1 + 1;
- this->RecursionDepth = iVar2;
- if (iVar2 == 3) {
- ::error(&DAT_080ef560);
- puVar6 = (undefined4 *)__cxa_allocate_exception(4);
- *puVar6 = "Recursion depth too high";
- goto LAB_0804ee56;
- }
- if ((iVar2 < 1) || (*__file == '/')) {
-LAB_0804ee70:
- __dest = this->Filename + this->RecursionDepth;
- }
- else {
- strcpy(this->Filename[this->RecursionDepth],this->Filename[iVar1]);
- // try { // try from 0804eda1 to 0804ee91 has its CatchHandler @ 0804eea6
- pcVar3 = findLast(this->Filename[this->RecursionDepth],'/');
- if (pcVar3 == (char *)0x0) goto LAB_0804ee70;
- __dest = (char (*) [4096])(pcVar3 + 1);
- }
- strcpy(*__dest,__file);
- iVar1 = this->RecursionDepth;
- pFVar4 = fopen(this->Filename[this->RecursionDepth],"rb");
- iVar2 = this->RecursionDepth;
- this->File[iVar1] = (FILE *)pFVar4;
- if (this->File[iVar2] != (FILE *)0x0) {
- this->Line[iVar2] = 1;
- return 1;
- }
- piVar5 = __errno_location();
- iVar1 = *piVar5;
- ::error(&DAT_080ef520,this->Filename + iVar2);
- pcVar3 = strerror(iVar1);
- ::error("Fehler %d: %s.\n",iVar1,pcVar3);
- this->RecursionDepth = this->RecursionDepth + -1;
- puVar6 = (undefined4 *)__cxa_allocate_exception(4);
- *puVar6 = "Cannot open script-file";
-LAB_0804ee56:
- // WARNING: Subroutine does not return
- __cxa_throw(puVar6,char_const*::typeinfo,0);
-}
-
-
-
-// DWARF original prototype: void close(TReadScriptFile * this)
-
-int __thiscall TReadScriptFile::close(TReadScriptFile *this,int __fd)
-
-{
- int iVar1;
- int *piVar2;
-
- iVar1 = this->RecursionDepth;
- if (iVar1 != -1) {
- iVar1 = fclose((FILE *)this->File[iVar1]);
- if (iVar1 != 0) {
- piVar2 = __errno_location();
- iVar1 = *piVar2;
- ::error(&DAT_080ef4e0,iVar1);
- }
- this->RecursionDepth = this->RecursionDepth + -1;
- return iVar1;
- }
- ::error("TReadScriptFile::close: Keine Datei offen.\n");
- return iVar1;
-}
-
-
-
-// DWARF original prototype: void error(TReadScriptFile * this, char * Text)
-
-void __thiscall TReadScriptFile::error(TReadScriptFile *this,char *Text)
-
-{
- char *pcVar1;
- int iVar2;
- int *piVar3;
- undefined4 *puVar4;
- char *p;
- char (*pacVar5) [4096];
-
- // try { // try from 0804ef52 to 0804f008 has its CatchHandler @ 0804f020
- pcVar1 = findLast(this->Filename[this->RecursionDepth],'/');
- if (pcVar1 == (char *)0x0) {
- iVar2 = this->RecursionDepth;
- pacVar5 = this->Filename + iVar2;
- }
- else {
- iVar2 = this->RecursionDepth;
- pacVar5 = (char (*) [4096])(pcVar1 + 1);
- }
- snprintf(ErrorString,100,"error in script-file \"%s\", line %d: %s",pacVar5,this->Line[iVar2],
- Text);
- iVar2 = this->RecursionDepth;
- while (iVar2 != -1) {
- iVar2 = fclose((FILE *)this->File[iVar2]);
- if (iVar2 != 0) {
- piVar3 = __errno_location();
- ::error(&DAT_080ef4e0,*piVar3);
- }
- this->RecursionDepth = this->RecursionDepth + -1;
- iVar2 = this->RecursionDepth;
- }
- puVar4 = (undefined4 *)__cxa_allocate_exception(4);
- *puVar4 = ErrorString;
- // WARNING: Subroutine does not return
- __cxa_throw(puVar4,char*::typeinfo,0);
-}
-
-
-
-// WARNING: Variable defined which should be unmapped: pos
-// DWARF original prototype: void nextToken(TReadScriptFile * this)
-
-void __thiscall TReadScriptFile::nextToken(TReadScriptFile *this)
-
-{
- char *__file;
- FILE *__stream;
- bool bVar1;
- uint uVar2;
- int iVar3;
- int *piVar4;
- int State;
- undefined4 uVar5;
- int c;
- char *pcVar6;
- int in_stack_ffffffcc;
- int local_1c;
- int Sign;
- FILE *f;
- int pos;
-
- if (this->RecursionDepth == -1) {
- ::error(&DAT_080ef660);
-LAB_0804f216:
- this->Token = ENDOFFILE;
- return;
- }
- __file = this->String;
-LAB_0804f062:
- f = (FILE *)0x0;
- uVar5 = 0;
- uVar2 = 4000;
- pcVar6 = __file;
- if (((uint)__file & 4) != 0) {
- pcVar6 = this->String + 4;
- uVar2 = 0xf9c;
- this->String[0] = '\0';
- this->String[1] = '\0';
- this->String[2] = '\0';
- this->String[3] = '\0';
- }
- for (uVar2 = uVar2 >> 2; uVar2 != 0; uVar2 = uVar2 - 1) {
- pcVar6[0] = '\0';
- pcVar6[1] = '\0';
- pcVar6[2] = '\0';
- pcVar6[3] = '\0';
- pcVar6 = pcVar6 + 4;
- }
- this->Number = 0;
- __stream = (FILE *)this->File[this->RecursionDepth];
- local_1c = 1;
-LAB_0804f0b6:
- if (f == (FILE *)0xf9f) {
- error(this,"string too long");
- }
- switch(uVar5) {
- case 0:
- iVar3 = getc(__stream);
- if (iVar3 != -1) {
- if (iVar3 == 10) {
- this->Line[this->RecursionDepth] = this->Line[this->RecursionDepth] + 1;
- }
- else {
- // try { // try from 0804f0f5 to 0804f92b has its CatchHandler @ 0804f931
- bVar1 = isSpace(iVar3);
- if (((!bVar1) && (uVar5 = 1, iVar3 != 0x23)) && (uVar5 = 0x1e, iVar3 != 0x40)) {
- bVar1 = isAlpha(iVar3);
- if (bVar1) {
- uVar5 = 2;
- this->String[(int)f] = (char)iVar3;
- f = (FILE *)((int)&f->_flags + 1);
- }
- else {
- bVar1 = isDigit(iVar3);
- if (bVar1) {
- this->Number = iVar3 + -0x30;
- uVar5 = 3;
- }
- else {
- uVar5 = 6;
- if (((iVar3 != 0x22) && (uVar5 = 0xb, iVar3 != 0x5b)) &&
- ((uVar5 = 0x16, iVar3 != 0x3c &&
- ((uVar5 = 0x19, iVar3 != 0x3e && (uVar5 = 0x1b, iVar3 != 0x2d))))))
- {
- uVar5 = 10;
- this->Special = (char)iVar3;
- }
- }
- }
- }
- }
- goto LAB_0804f0b6;
- }
-LAB_0804f1c2:
- iVar3 = this->RecursionDepth;
- if (iVar3 < 1) goto LAB_0804f216;
- if (iVar3 == -1) {
- ::error("TReadScriptFile::close: Keine Datei offen.\n");
- }
- else {
- iVar3 = fclose((FILE *)this->File[iVar3]);
- if (iVar3 != 0) {
- piVar4 = __errno_location();
- ::error(&DAT_080ef4e0,*piVar4);
- }
- this->RecursionDepth = this->RecursionDepth + -1;
- }
- goto LAB_0804f062;
- case 1:
- iVar3 = getc(__stream);
- if (iVar3 == -1) goto LAB_0804f1c2;
- if (iVar3 == 10) {
- this->Line[this->RecursionDepth] = this->Line[this->RecursionDepth] + 1;
- goto LAB_0804f24a;
- }
- goto LAB_0804f0b6;
- case 2:
- iVar3 = getc(__stream);
- if (f == (FILE *)0x1e) {
- error(this,"identifier too long");
- }
- if (iVar3 == -1) {
-LAB_0804f2aa:
- this->Token = IDENTIFIER;
- return;
- }
- bVar1 = isAlpha(iVar3);
- if (((!bVar1) && (bVar1 = isDigit(iVar3), !bVar1)) && (iVar3 != 0x5f)) {
- ungetc(iVar3,__stream);
- goto LAB_0804f2aa;
- }
- goto LAB_0804f275;
- case 3:
- iVar3 = getc(__stream);
- if (iVar3 == -1) {
-LAB_0804f31a:
- this->Token = NUMBER;
- return;
- }
- bVar1 = isDigit(iVar3);
- if (bVar1) goto LAB_0804f325;
- if (iVar3 != 0x2d) {
- ungetc(iVar3,__stream);
- goto LAB_0804f31a;
- }
- goto LAB_0804f2ea;
- case 4:
- iVar3 = getc(__stream);
- if (iVar3 == -1) {
- error(this,"unexpected end of file");
- }
- bVar1 = isDigit(iVar3);
- if (!bVar1) {
- error(this,"syntax error");
- goto switchD_0804f0cc_caseD_5;
- }
- uVar5 = 5;
- this->Number = iVar3 + -0x30;
- goto LAB_0804f0b6;
- case 5:
-switchD_0804f0cc_caseD_5:
- iVar3 = getc(__stream);
- if (iVar3 == -1) {
-LAB_0804f3a8:
- this->Bytes[(int)(f->_shortbuf + -0x47)] = (uchar)this->Number;
- this->Token = BYTES;
- return;
- }
- bVar1 = isDigit(iVar3);
- if (bVar1) goto LAB_0804f325;
- if (iVar3 != 0x2d) {
- ungetc(iVar3,__stream);
- goto LAB_0804f3a8;
- }
-LAB_0804f2ea:
- this->Bytes[(int)(f->_shortbuf + -0x47)] = (uchar)this->Number;
- f = (FILE *)((int)&f->_flags + 1);
- uVar5 = 4;
- goto LAB_0804f0b6;
- case 6:
- iVar3 = getc(__stream);
- if (iVar3 == -1) {
- error(this,"unexpected end of file");
- }
- if (iVar3 == 0x22) {
- uVar5 = 8;
- }
- else if (iVar3 == 0x5c) {
- uVar5 = 7;
- }
- else {
- if (iVar3 == 10) {
- this->Line[this->RecursionDepth] = this->Line[this->RecursionDepth] + 1;
- }
-LAB_0804f275:
- this->String[(int)f] = (char)iVar3;
- f = (FILE *)((int)&f->_flags + 1);
- }
- goto LAB_0804f0b6;
- case 7:
- iVar3 = getc(__stream);
- if (iVar3 == -1) {
- error(this,"unexpected end of file");
- }
- if (iVar3 == 0x6e) {
- this->String[(int)f] = '\n';
- }
- else {
- this->String[(int)f] = (char)iVar3;
- }
- f = (FILE *)((int)&f->_flags + 1);
- uVar5 = 6;
- goto LAB_0804f0b6;
- case 8:
- this->Token = STRING;
- return;
- default:
- ::error(&DAT_080ef620);
-LAB_0804f24a:
- uVar5 = 0;
- goto LAB_0804f0b6;
- case 10:
- break;
- case 0xb:
- iVar3 = getc(__stream);
- this->Special = '[';
- if (iVar3 == -1) break;
- bVar1 = isDigit(iVar3);
- if (bVar1) {
- local_1c = 1;
- this->Number = iVar3 + -0x30;
- }
- else {
- if (iVar3 != 0x2d) goto LAB_0804f507;
- local_1c = -1;
- this->Number = 0;
- }
- uVar5 = 0xc;
- goto LAB_0804f0b6;
- case 0xc:
- iVar3 = getc(__stream);
- if (iVar3 == -1) {
- error(this,"unexpected end of file");
- }
- bVar1 = isDigit(iVar3);
- if (bVar1) goto LAB_0804f325;
- if (iVar3 != 0x2c) {
- error(this,"syntax error");
- goto switchD_0804f0cc_caseD_d;
- }
- uVar5 = 0xd;
- this->CoordX = local_1c * this->Number;
- goto LAB_0804f0b6;
- case 0xd:
-switchD_0804f0cc_caseD_d:
- iVar3 = getc(__stream);
- if (iVar3 == -1) {
- error(this,"unexpected end of file");
- }
- bVar1 = isDigit(iVar3);
- if (bVar1) {
- local_1c = 1;
- this->Number = iVar3 + -0x30;
- }
- else {
- if (iVar3 != 0x2d) {
- error(this,"syntax error");
- goto switchD_0804f0cc_caseD_e;
- }
- local_1c = -1;
- this->Number = 0;
- }
- uVar5 = 0xe;
- goto LAB_0804f0b6;
- case 0xe:
-switchD_0804f0cc_caseD_e:
- iVar3 = getc(__stream);
- if (iVar3 == -1) {
- error(this,"unexpected end of file");
- }
- bVar1 = isDigit(iVar3);
- if (bVar1) goto LAB_0804f325;
- if (iVar3 != 0x2c) {
- error(this,"syntax error");
- goto switchD_0804f0cc_caseD_f;
- }
- uVar5 = 0xf;
- this->CoordY = local_1c * this->Number;
- goto LAB_0804f0b6;
- case 0xf:
-switchD_0804f0cc_caseD_f:
- iVar3 = getc(__stream);
- if (iVar3 == -1) {
- error(this,"unexpected end of file");
- }
- bVar1 = isDigit(iVar3);
- if (bVar1) {
- local_1c = 1;
- this->Number = iVar3 + -0x30;
- }
- else {
- if (iVar3 != 0x2d) {
- error(this,"syntax error");
- goto switchD_0804f0cc_caseD_10;
- }
- local_1c = -1;
- this->Number = 0;
- }
- uVar5 = 0x10;
- goto LAB_0804f0b6;
- case 0x10:
-switchD_0804f0cc_caseD_10:
- iVar3 = getc(__stream);
- if (iVar3 == -1) {
- error(this,"unexpected end of file");
- }
- bVar1 = isDigit(iVar3);
- if (bVar1) {
-LAB_0804f325:
- this->Number = iVar3 + -0x30 + this->Number * 10;
- }
- else {
- if (iVar3 != 0x5d) {
- error(this,"syntax error");
-switchD_0804f0cc_caseD_11:
- this->Token = COORDINATE;
- return;
- }
- uVar5 = 0x11;
- this->CoordZ = local_1c * this->Number;
- }
- goto LAB_0804f0b6;
- case 0x11:
- goto switchD_0804f0cc_caseD_11;
- case 0x16:
- iVar3 = getc(__stream);
- this->Special = '<';
- if (iVar3 == -1) break;
- uVar5 = 0x17;
- if ((iVar3 != 0x3d) && (uVar5 = 0x18, iVar3 != 0x3e)) goto LAB_0804f507;
- goto LAB_0804f0b6;
- case 0x17:
- this->Special = 'L';
- break;
- case 0x18:
- this->Special = 'N';
- break;
- case 0x19:
- iVar3 = getc(__stream);
- this->Special = '>';
- if (iVar3 == -1) break;
- uVar5 = 0x1a;
- if (iVar3 != 0x3d) goto LAB_0804f507;
- goto LAB_0804f0b6;
- case 0x1a:
- this->Special = 'G';
- break;
- case 0x1b:
- iVar3 = getc(__stream);
- this->Special = '-';
- if (iVar3 == -1) break;
- uVar5 = 0x1c;
- if (iVar3 == 0x3e) goto LAB_0804f0b6;
-LAB_0804f507:
- ungetc(iVar3,__stream);
- break;
- case 0x1c:
- this->Special = 'I';
- break;
- case 0x1e:
- iVar3 = getc(__stream);
- if (iVar3 == -1) {
- error(this,"unexpected end of file");
- }
- if (iVar3 != 0x22) {
- error(this,"syntax error");
- goto switchD_0804f0cc_caseD_1f;
- }
- uVar5 = 0x1f;
- goto LAB_0804f0b6;
- case 0x1f:
-switchD_0804f0cc_caseD_1f:
- iVar3 = getc(__stream);
- if (iVar3 == -1) {
- error(this,"unexpected end of file");
- }
- if (iVar3 != 0x22) goto LAB_0804f275;
- uVar5 = 0x20;
- goto LAB_0804f0b6;
- case 0x20:
- goto switchD_0804f0cc_caseD_20;
- }
- this->Token = SPECIAL;
- return;
-switchD_0804f0cc_caseD_20:
- open(this,__file,in_stack_ffffffcc);
- goto LAB_0804f062;
-}
-
-
-
-// DWARF original prototype: char * readIdentifier(TReadScriptFile * this)
-
-char * __thiscall TReadScriptFile::readIdentifier(TReadScriptFile *this)
-
-{
- // try { // try from 0804f95f to 0804f978 has its CatchHandler @ 0804f9b8
- nextToken(this);
- if (this->Token != IDENTIFIER) {
- error(this,"identifier expected");
- }
- if (this->Token != IDENTIFIER) {
- // try { // try from 0804f989 to 0804f99b has its CatchHandler @ 0804f9a5
- error(this,"identifier expected");
- }
- strLower(this->String);
- return this->String;
-}
-
-
-
-// DWARF original prototype: int readNumber(TReadScriptFile * this)
-
-int __thiscall TReadScriptFile::readNumber(TReadScriptFile *this)
-
-{
- TOKEN TVar1;
- int sgn;
- int iVar2;
-
- // try { // try from 0804f9e2 to 0804fa07 has its CatchHandler @ 0804fa70
- nextToken(this);
- TVar1 = this->Token;
- iVar2 = 1;
- if ((TVar1 == SPECIAL) && (this->Special == '-')) {
- iVar2 = -1;
- // try { // try from 0804fa46 to 0804fa4a has its CatchHandler @ 0804fa70
- nextToken(this);
- TVar1 = this->Token;
- }
- if (TVar1 != NUMBER) {
- error(this,"number expected");
- }
- if (this->Token != NUMBER) {
- // try { // try from 0804fa18 to 0804fa1c has its CatchHandler @ 0804fa4f
- error(this,"number expected");
- }
- return iVar2 * this->Number;
-}
-
-
-
-// DWARF original prototype: char * readString(TReadScriptFile * this)
-
-char * __thiscall TReadScriptFile::readString(TReadScriptFile *this)
-
-{
- // try { // try from 0804fa8f to 0804faa8 has its CatchHandler @ 0804fae0
- nextToken(this);
- if (this->Token != STRING) {
- error(this,"string expected");
- }
- if (this->Token != STRING) {
- // try { // try from 0804fab9 to 0804fabd has its CatchHandler @ 0804facb
- error(this,"string expected");
- }
- return this->String;
-}
-
-
-
-// DWARF original prototype: uchar * readBytesequence(TReadScriptFile * this)
-
-uchar * __thiscall TReadScriptFile::readBytesequence(TReadScriptFile *this)
-
-{
- // try { // try from 0804faff to 0804fb18 has its CatchHandler @ 0804fb50
- nextToken(this);
- if (this->Token != BYTES) {
- error(this,"byte-sequence expected");
- }
- if (this->Token != BYTES) {
- // try { // try from 0804fb29 to 0804fb2d has its CatchHandler @ 0804fb3b
- error(this,"byte-sequence expected");
- }
- return this->Bytes;
-}
-
-
-
-// DWARF original prototype: void readCoordinate(TReadScriptFile * this, int * x, int * y, int * z)
-
-void __thiscall TReadScriptFile::readCoordinate(TReadScriptFile *this)
-
-{
- int *in_stack_00000008;
- int *in_stack_0000000c;
- int *in_stack_00000010;
-
- // try { // try from 0804fb6f to 0804fb88 has its CatchHandler @ 0804fbe0
- nextToken(this);
- if (this->Token != COORDINATE) {
- error(this,"coordinates expected");
- }
- if (this->Token != COORDINATE) {
- // try { // try from 0804fb99 to 0804fb9d has its CatchHandler @ 0804fbc6
- error(this,"coordinates expected");
- }
- *in_stack_00000008 = this->CoordX;
- *in_stack_0000000c = this->CoordY;
- *in_stack_00000010 = this->CoordZ;
- return;
-}
-
-
-
-// DWARF original prototype: char readSpecial(TReadScriptFile * this)
-
-char __thiscall TReadScriptFile::readSpecial(TReadScriptFile *this)
-
-{
- // try { // try from 0804fbff to 0804fc18 has its CatchHandler @ 0804fc50
- nextToken(this);
- if (this->Token != SPECIAL) {
- error(this,"special-char expected");
- }
- if (this->Token != SPECIAL) {
- // try { // try from 0804fc29 to 0804fc2d has its CatchHandler @ 0804fc3c
- error(this,"special-char expected");
- }
- return this->Special;
-}
-
-
-
-// WARNING: Variable defined which should be unmapped: Symbol_local
-// DWARF original prototype: void readSymbol(TReadScriptFile * this, char Symbol)
-
-void __thiscall TReadScriptFile::readSymbol(TReadScriptFile *this,char Symbol)
-
-{
- char Symbol_local;
-
- // try { // try from 0804fc76 to 0804fc8f has its CatchHandler @ 0804fcf0
- nextToken(this);
- if (this->Token != SPECIAL) {
- error(this,"special-char expected");
- if (this->Token != SPECIAL) {
- // try { // try from 0804fca0 to 0804fca4 has its CatchHandler @ 0804fcd3
- error(this,"special-char expected");
- }
- }
- if (Symbol != this->Special) {
- // try { // try from 0804fcc7 to 0804fccb has its CatchHandler @ 0804fcf0
- error(this,"special-char expected");
- }
- return;
-}
-
-
-
-// DWARF original prototype: char * getIdentifier(TReadScriptFile * this)
-
-char * __thiscall TReadScriptFile::getIdentifier(TReadScriptFile *this)
-
-{
- if (this->Token != IDENTIFIER) {
- // try { // try from 0804fd1c to 0804fd2e has its CatchHandler @ 0804fd38
- error(this,"identifier expected");
- }
- strLower(this->String);
- return this->String;
-}
-
-
-
-// DWARF original prototype: int getNumber(TReadScriptFile * this)
-
-int __thiscall TReadScriptFile::getNumber(TReadScriptFile *this)
-
-{
- if (this->Token != NUMBER) {
- // try { // try from 0804fd6c to 0804fd70 has its CatchHandler @ 0804fd80
- error(this,"number expected");
- }
- return this->Number;
-}
-
-
-
-// DWARF original prototype: char * getString(TReadScriptFile * this)
-
-char * __thiscall TReadScriptFile::getString(TReadScriptFile *this)
-
-{
- if (this->Token != STRING) {
- // try { // try from 0804fdbc to 0804fdc0 has its CatchHandler @ 0804fdd0
- error(this,"string expected");
- }
- return this->String;
-}
-
-
-
-// DWARF original prototype: uchar * getBytesequence(TReadScriptFile * this)
-
-uchar * __thiscall TReadScriptFile::getBytesequence(TReadScriptFile *this)
-
-{
- if (this->Token != BYTES) {
- // try { // try from 0804fe0c to 0804fe10 has its CatchHandler @ 0804fe20
- error(this,"byte-sequence expected");
- }
- return this->Bytes;
-}
-
-
-
-// DWARF original prototype: void getCoordinate(TReadScriptFile * this, int * x, int * y, int * z)
-
-void __thiscall TReadScriptFile::getCoordinate(TReadScriptFile *this)
-
-{
- int *in_stack_00000008;
- int *in_stack_0000000c;
- int *in_stack_00000010;
-
- if (this->Token != COORDINATE) {
- // try { // try from 0804fe5c to 0804fe60 has its CatchHandler @ 0804fe90
- error(this,"coordinates expected");
- }
- *in_stack_00000008 = this->CoordX;
- *in_stack_0000000c = this->CoordY;
- *in_stack_00000010 = this->CoordZ;
- return;
-}
-
-
-
-// DWARF original prototype: char getSpecial(TReadScriptFile * this)
-
-char __thiscall TReadScriptFile::getSpecial(TReadScriptFile *this)
-
-{
- if (this->Token != SPECIAL) {
- // try { // try from 0804fecc to 0804fed0 has its CatchHandler @ 0804fee0
- error(this,"special-char expected");
- }
- return this->Special;
-}
-
-
-
// DWARF original prototype: void TWriteScriptFile(TWriteScriptFile * this)
void __thiscall TWriteScriptFile::TWriteScriptFile(TWriteScriptFile *this)
diff --git a/reference/types.hh b/reference/types.hh
index e66ea72..5bfc014 100644
--- a/reference/types.hh
+++ b/reference/types.hh
@@ -96,15 +96,6 @@ struct TDatabasePoolConnection {
bool TransactionRunning;
};
-struct TDatabaseSettings {
- char Product[30];
- char Database[30];
- char Login[30];
- char Password[30];
- char Host[30];
- char Port[6];
-};
-
struct Semaphore {
int value;
struct pthread_mutex_t mutex;
@@ -828,12 +819,6 @@ struct TWriteBinaryFile {
char Filename[4096];
};
-struct TWriteScriptFile {
- FILE *File;
- char Filename[4096];
- int Line;
-};
-
struct TReadBinaryFile {
struct TReadStream super_TReadStream;
undefined field1_0x1;
@@ -844,21 +829,6 @@ struct TReadBinaryFile {
int FileSize;
};
-struct TReadScriptFile {
- enum TOKEN Token;
- FILE *File[3];
- char Filename[3][4096];
- int Line[3];
- char String[4000];
- int RecursionDepth;
- uchar *Bytes;
- int Number;
- int CoordX;
- int CoordY;
- int CoordZ;
- char Special;
-};
-
struct TNPC {
struct TNonplayer super_TNonplayer;
ulong Interlocutor;
@@ -891,12 +861,6 @@ struct TQueryManagerConnection {
undefined field8_0x2f;
};
-struct TQueryManagerSettings {
- char Host[50];
- int Port;
- char Password[30];
-};
-
struct TQueryManagerPoolConnection {
struct TQueryManagerConnectionPool *QueryManagerConnectionPool;
struct TQueryManagerConnection *QueryManagerConnection;
diff --git a/src/config.cc b/src/config.cc
index f30b2b6..bae583a 100644
--- a/src/config.cc
+++ b/src/config.cc
@@ -1,4 +1,4 @@
-#include "main.hh"
+#include "config.hh"
char BINPATH[4096];
char DATAPATH[4096];
diff --git a/src/config.hh b/src/config.hh
new file mode 100644
index 0000000..c2f1e14
--- /dev/null
+++ b/src/config.hh
@@ -0,0 +1,61 @@
+#ifndef TIBIA_CONFIG_HH_
+#define TIBIA_CONFIG_HH_ 1
+
+#include "main.hh"
+#include "enums.hh"
+
+struct TDatabaseSettings {
+ char Product[30];
+ char Database[30];
+ char Login[30];
+ char Password[30];
+ char Host[30];
+ char Port[6];
+};
+
+struct TQueryManagerSettings {
+ char Host[50];
+ int Port;
+ char Password[30];
+};
+
+extern char BINPATH[4096];
+extern char DATAPATH[4096];
+extern char LOGPATH[4096];
+extern char MAPPATH[4096];
+extern char MONSTERPATH[4096];
+extern char NPCPATH[4096];
+extern char ORIGMAPPATH[4096];
+extern char SAVEPATH[4096];
+extern char USERPATH[4096];
+extern int SHMKey;
+extern int AdminPort;
+extern int GamePort;
+extern int QueryManagerPort;
+extern char AdminAddress[16];
+extern char GameAddress[16];
+extern char QueryManagerAddress[16];
+extern char QueryManagerAdminPW[9];
+extern char QueryManagerGamePW[9];
+extern char QueryManagerWebPW[9];
+extern int DebugLevel;
+extern bool PrivateWorld;
+extern TWorldType WorldType;
+extern char WorldName[30];
+extern int MaxPlayers;
+extern int MaxNewbies;
+extern int PremiumPlayerBuffer;
+extern int PremiumNewbieBuffer;
+extern int Beat;
+extern int RebootTime;
+extern TDatabaseSettings ADMIN_DATABASE;
+extern TDatabaseSettings VOLATILE_DATABASE;
+extern TDatabaseSettings WEB_DATABASE;
+extern TDatabaseSettings FORUM_DATABASE;
+extern TDatabaseSettings MANAGER_DATABASE;
+extern int NumberOfQueryManagers;
+extern TQueryManagerSettings QUERY_MANAGER[10];
+
+void ReadConfig(void);
+
+#endif //TIBIA_CONFIG_HH_
diff --git a/src/main.cc b/src/main.cc
index 0b6e8c0..a930aef 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -1,4 +1,5 @@
#include "main.hh"
+#include "config.hh"
#include <signal.h>
#include <sys/time.h>
diff --git a/src/main.hh b/src/main.hh
index f888521..7aa329a 100644
--- a/src/main.hh
+++ b/src/main.hh
@@ -80,49 +80,11 @@ STATIC_ASSERT(sizeof(int) == 4);
// be too difficult either. Overall this design is outdated and should be reviewed.
// Nevertheless, we should focus on getting it working as intended, on the target
// platform (which is Linux 32-bits) before attempting to refine it.
-STATIC_ASSERT(OS_LINUX);
-#include <sys/types.h>
+//STATIC_ASSERT(OS_LINUX);
+//#include <sys/types.h>
+typedef int pid_t;
//
-// config.cc
-extern char BINPATH[4096];
-extern char DATAPATH[4096];
-extern char LOGPATH[4096];
-extern char MAPPATH[4096];
-extern char MONSTERPATH[4096];
-extern char NPCPATH[4096];
-extern char ORIGMAPPATH[4096];
-extern char SAVEPATH[4096];
-extern char USERPATH[4096];
-extern int SHMKey;
-extern int AdminPort;
-extern int GamePort;
-extern int QueryManagerPort;
-extern char AdminAddress[16];
-extern char GameAddress[16];
-extern char QueryManagerAddress[16];
-extern char QueryManagerAdminPW[9];
-extern char QueryManagerGamePW[9];
-extern char QueryManagerWebPW[9];
-extern int DebugLevel;
-extern bool PrivateWorld;
-extern TWorldType WorldType;
-extern char WorldName[30];
-extern int MaxPlayers;
-extern int MaxNewbies;
-extern int PremiumPlayerBuffer;
-extern int PremiumNewbieBuffer;
-extern int Beat;
-extern int RebootTime;
-extern TDatabaseSettings ADMIN_DATABASE;
-extern TDatabaseSettings VOLATILE_DATABASE;
-extern TDatabaseSettings WEB_DATABASE;
-extern TDatabaseSettings FORUM_DATABASE;
-extern TDatabaseSettings MANAGER_DATABASE;
-extern int NumberOfQueryManagers;
-extern TQueryManagerSettings QUERY_MANAGER[10];
-void ReadConfig(void);
-
// shm.cc
void StartGame(void);
void CloseGame(void);
diff --git a/src/script.cc b/src/script.cc
new file mode 100644
index 0000000..d3e8134
--- /dev/null
+++ b/src/script.cc
@@ -0,0 +1,617 @@
+#include "script.hh"
+
+// =============================================================================
+// =============================================================================
+// TODO(fusion): Move to `util.cc`?
+
+static bool isSpace(int c){
+ return c == ' '
+ || c == '\t'
+ || c == '\n'
+ || c == '\r'
+ || c == '\v'
+ || c == '\f';
+}
+
+static bool isAlpha(int c){
+ // TODO(fusion): This is most likely wrong! We're assuming a direct conversion
+ // from `char` to `int` which will cause sign extension for negative values. This
+ // wouldn't be a problem if we expected to parse only streams of `char[]` but can
+ // be problematic for the output of `getc` which returns bytes as `unsigned char`
+ // converted to `int`.
+ // TLDR: The parameter `c` should be `uint8`.
+ return ('A' <= c && c <= 'Z')
+ || ('a' <= c && c <= 'z')
+ || c == -0x1C // E4 => ä
+ || c == -0x0A // F6 => ö
+ || c == -0x04 // FC => ü
+ || c == -0x3C // C4 => Ä
+ || c == -0x2A // D6 => Ö
+ || c == -0x24 // DC => Ü
+ || c == -0x21; // DF => ß
+}
+
+static bool isEngAlpha(int c){
+ return ('A' <= c && c <= 'Z')
+ || ('a' <= c && c <= 'z');
+}
+
+static bool isDigit(int c){
+ return ('0' <= c && c <= '9');
+}
+
+static int toLower(int c){
+ // TODO(fusion): Same problem as `isAlpha`.
+ if(('A' <= c && c <= 'Z') || (0xC0 <= c && c <= 0xDE && c != 0xD7)){
+ c += 32;
+ }
+ return c;
+}
+
+static int toUpper(int c){
+ // TODO(fusion): Same problem as `isAlpha`.
+ if(('A' <= c && c <= 'Z') || (0xE0 <= c && c <= 0xFE && c != 0xF7)){
+ c -= 32;
+ }
+ return c;
+}
+
+static char *strLower(char *s){
+ for(int i = 0; s[i] != 0; i += 1){
+ s[i] = (char)toLower(s[i]);
+ }
+ return s;
+}
+
+static char *strUpper(char *s){
+ for(int i = 0; s[i] != 0; i += 1){
+ s[i] = (char)toUpper(s[i]);
+ }
+ return s;
+}
+
+static int stricmp(const char *s1, const char *s2, int Pos){
+ for(int i = 0; i < Pos; i += 1){
+ int c1 = toLower(s1[i]);
+ int c2 = toLower(s2[i]);
+ if(c1 > c2){
+ return 1;
+ }else if(c1 < c2){
+ return -1;
+ }else{
+ ASSERT(c1 == c2);
+ if(c1 == 0){
+ return 0;
+ }
+ }
+ }
+}
+
+static char *findFirst(char *s, char c){
+ return strchr(s, (int)c);
+}
+
+static char *findLast(char *s, char c){
+ char *Current = s;
+ char *Last = NULL;
+ while(true){
+ Current = strchr(Current, (int)c);
+ if(Current == NULL)
+ break;
+ Last = Current;
+ Current += 1; // skip character
+ }
+ return Last;
+}
+
+// =============================================================================
+// =============================================================================
+
+TReadScriptFile::TReadScriptFile(void){
+ this->RecursionDepth = -1;
+ this->Bytes = (uint8*)this->String;
+}
+
+TReadScriptFile::~TReadScriptFile(void){
+ if(this->RecursionDepth != -1){
+ ::error("TReadScriptFile::~TReadScriptFile: Datei ist noch offen.\n");
+ for(int Depth = this->RecursionDepth; Depth >= 0; Depth -= 1){
+ // TODO(fusion): Probably `TReadScriptFile::close` inlined?
+ if(fclose(this->File[Depth]) != 0){
+ ::error("TReadScriptFile::close: Fehler %d beim Schließen der Datei.\n", errno);
+ }
+ }
+ this->RecursionDepth = -1;
+ }
+}
+
+void TReadScriptFile::open(const char *FileName){
+ int Depth = this->RecursionDepth + 1;
+ if((Depth + 1) >= NARRAY(this->File)){
+ ::error("TReadScriptFile::open: Rekursionstiefe zu groß.\n");
+ throw "Recursion depth too high";
+ }
+
+ ASSERT(Depth >= 0);
+
+ // TODO(fusion): More `strcpy`s...
+ if(Depth > 0 && FileName[0] != '/'){
+ strcpy(this->Filename[Depth], this->Filename[Depth - 1]);
+ if(char *Slash = findLast(this->Filename[Depth], '/')){
+ strcpy(Slash + 1, FileName);
+ }else{
+ strcpy(this->Filename[Depth], FileName);
+ }
+ }else{
+ strcpy(this->Filename[Depth], FileName);
+ }
+
+ this->File[Depth] = fopen(this->Filename[Depth], "rb");
+ if(this->File[Depth] == NULL){
+ ::error("TReadScriptFile::open: Kann Datei %s nicht öffnen.\n", this->Filename[Depth]);
+ ::error("Fehler %d: %s.\n", errno, strerror(errno));
+ throw "Cannot open script-file";
+ }
+
+ this->Line[Depth] = 1;
+ this->RecursionDepth = Depth;
+}
+
+void TReadScriptFile::close(void){
+ int Depth = this->RecursionDepth;
+ if(Depth <= -1){
+ ::error("TReadScriptFile::close: Keine Datei offen.\n");
+ return;
+ }
+
+ ASSERT(Depth < NARRAY(this->File));
+ if(fclose(this->File[Depth]) != 0){
+ ::error("TReadScriptFile::close: Fehler %d beim Schließen der Datei.\n", errno);
+ }
+ this->RecursionDepth -= 1;
+}
+
+void TReadScriptFile::error(const char *Text){
+ static char ErrorString[100];
+
+ int Depth = this->RecursionDepth;
+ ASSERT(Depth >= 0 && Depth <= NARRAY(this->File));
+
+ const char *Filename = this->Filename[Depth];
+ if(const char *Slash = findLast(this->Filename[Depth], '/')){
+ Filename = Slash + 1;
+ }
+
+ snprintf(ErrorString, sizeof(ErrorString),
+ "error in script-file \"%s\", line %d: %s",
+ Filename, this->Line[Depth], Text);
+
+ // TODO(fusion): Reset? Also seems like `TReadScriptFile::close` was inlined.
+ for(; Depth >= 0; Depth -= 1){
+ if(fclose(this->File[Depth]) != 0){
+ ::error("TReadScriptFile::close: Fehler %d beim Schließen der Datei.\n", errno);
+ }
+ }
+ this->RecursionDepth = -1;
+
+ throw ErrorString;
+}
+
+// NOTE(fusion): This function in particular was in pretty bad shape. It should
+// be one of the few that doesn't roughly match the original version. Nevertheless,
+// I think we should properly split this into a lexer and parser. We find ourselves
+// parsing strings and numbers multiple times here, whereas a simple lexer and
+// parser would simplify the work tremendously.
+// TODO(fusion): This needs to be tested to make sure it behaves like the original.
+void TReadScriptFile::nextToken(void){
+ if(this->RecursionDepth == -1){
+ ::error("TReadScriptFile::nextToken: Kein Skript zum Lesen geöffnet.\n");
+ this->Token = ENDOFFILE;
+ return;
+ }
+
+ // NOTE(fusion): Reset any previous token state.
+ memset(this->String, 0, sizeof(this->String));
+ this->Number = 0;
+ this->CoordX = 0;
+ this->CoordY = 0;
+ this->CoordZ = 0;
+ this->Special = 0;
+
+ // NOTE(fusion): `TReadScriptFile::error` will format and throw an error
+ // string that must be handled up the call stack. It must be considered
+ // an exit point for parsing errors in this function.
+
+ while(true){
+ int Depth = this->RecursionDepth;
+ ASSERT(Depth >= 0 && Depth < NARRAY(this->File));
+ FILE *File = this->File[Depth];
+
+ int c;
+ do{
+ c = getc(File);
+ if(c == '\n'){
+ this->Line[Depth] += 1;
+ }
+ }while(isSpace(c));
+
+ if(c == EOF){
+ if(Depth == 0){
+ this->Token = ENDOFFILE;
+ return;
+ }
+
+ this->close();
+ continue;
+ }
+
+ switch(c){
+ case '#':{ // COMMENT
+ while(true){
+ int next = getc(File);
+ if(next == '\n' || next == EOF){
+ if(next == '\n'){
+ this->Line[Depth] += 1;
+ }
+ break;
+ }
+ }
+ break;
+ }
+
+ case '@':{ // INCLUDE
+ int next = getc(File);
+ if(next == EOF){
+ this->error("unexpected end of file");
+ }else if(next != '"'){
+ this->error("syntax error");
+ }
+
+ int StringLength = 0;
+ while(true){
+ // TODO(fusion): I don't think new lines are even allowed in
+ // file names but we should keep track of the line number even
+ // if they happen here.
+ next = getc(File);
+ if(next == EOF){
+ this->error("unexpected end of file");
+ }else if(next == '"'){
+ break;
+ }
+
+ if(StringLength >= (NARRAY(this->String) - 1)){
+ this->error("string too long");
+ }
+ this->String[StringLength] = (char)next;
+ StringLength += 1;
+ }
+
+ // TODO(fusion): Maybe check if the path is empty?
+ this->open(this->String);
+
+ // NOTE(fusion): This is the only place we parse a string without
+ // returning it as a token. We need to reset it to make sure any
+ // subsequent string-like token will be properly parsed.
+ memset(this->String, 0, sizeof(this->String));
+
+ break;
+ }
+
+ case '"':{ // STRING
+ int StringLength = 0;
+ while(true){
+ int next = getc(File);
+ if(next == EOF){
+ this->error("unexpected end of file");
+ }else if(next == '\\'){
+ next = getc(File);
+ if(next == EOF){
+ this->error("unexpected end of file");
+ }else if(next == 'n'){
+ next = '\n';
+ }
+ }else if(next == '\n'){
+ // TODO(fusion): Shouldn't we prevent non-escaped new
+ // lines inside strings?
+ this->Line[Depth] += 1;
+ }else if(next == '"'){
+ break;
+ }
+
+ if(StringLength >= (NARRAY(this->String) - 1)){
+ this->error("string too long");
+ }
+ this->String[StringLength] = (char)next;
+ StringLength += 1;
+ }
+ this->Token = STRING;
+ return;
+ }
+
+ case '[':{ // COORDINATE
+ // NOTE(fusion): X-Coordinate or SPECIAL '['.
+ int Sign = -1;
+ int Coord = 0;
+ int next = getc(File);
+ if(isDigit(next)){
+ Sign = 1;
+ Coord = next - '0';
+ }else if(next != '-'){
+ this->Token = SPECIAL;
+ this->Special = '[';
+ if(next != EOF){
+ ungetc(next, File);
+ }
+ return;
+ }
+
+ while(true){
+ next = getc(File);
+ if(next == EOF){
+ this->error("unexpected end of file");
+ }else if(isDigit(next)){
+ Coord *= 10;
+ Coord += next - '0';
+ }else if(next == ','){
+ break;
+ }else{
+ this->error("syntax error");
+ }
+ }
+
+ this->CoordX = Sign * Coord;
+
+ // NOTE(fusion): Y-Coordinate.
+ Sign = -1;
+ Coord = 0;
+ next = getc(File);
+ if(isDigit(next)){
+ Sign = 1;
+ Coord = next - '0';
+ }else if(next != '-'){
+ this->error("syntax error");
+ }
+
+ while(true){
+ next = getc(File);
+ if(next == EOF){
+ this->error("unexpected end of file");
+ }else if(isDigit(next)){
+ Coord *= 10;
+ Coord += next - '0';
+ }else if(next == ','){
+ break;
+ }else{
+ this->error("syntax error");
+ }
+ }
+
+ this->CoordY = Sign * Coord;
+
+ // NOTE(fusion): Z-Coordinate.
+ Sign = -1;
+ Coord = 0;
+ next = getc(File);
+ if(isDigit(next)){
+ Sign = 1;
+ Coord = next - '0';
+ }else if(next != '-'){
+ this->error("syntax error");
+ }
+
+ while(true){
+ next = getc(File);
+ if(next == EOF){
+ this->error("unexpected end of file");
+ }else if(isDigit(next)){
+ Coord *= 10;
+ Coord += next - '0';
+ }else if(next == ']'){
+ break;
+ }else{
+ this->error("syntax error");
+ }
+ }
+
+ this->CoordZ = Sign * Coord;
+ this->Token = COORDINATE;
+ return;
+ }
+
+ case '<':{
+ int next = getc(File);
+ if(next == '='){
+ this->Special = 'L';
+ }else if(next == '>'){
+ this->Special = 'N';
+ }else{
+ this->Special = '<';
+ if(next != EOF){
+ ungetc(next, File);
+ }
+ }
+ this->Token = SPECIAL;
+ return;
+ }
+
+ case '>':{
+ int next = getc(File);
+ if(next == '='){
+ this->Special = 'G';
+ }else{
+ this->Special = '>';
+ if(next != EOF){
+ ungetc(next, File);
+ }
+ }
+ this->Token = SPECIAL;
+ return;
+ }
+
+ case '-':{
+ int next = getc(File);
+ if(next == '>'){
+ this->Special = 'I';
+ }else{
+ this->Special = '-';
+ if(next != EOF){
+ ungetc(next, File);
+ }
+ }
+ this->Token = SPECIAL;
+ return;
+ }
+
+ default:{
+ if(isAlpha(c)){
+ int IdentLength = 1;
+ this->String[0] = (char)c;
+ while(true){
+ int next = getc(File);
+ if(isAlpha(next) || isDigit(next) || next == '_'){
+ if(IdentLength >= 30){
+ this->error("identifier too long");
+ }
+ this->String[IdentLength] = (char)next;
+ IdentLength += 1;
+ }else{
+ if(next != EOF){
+ ungetc(next, File);
+ }
+ break;
+ }
+ }
+ this->Token = IDENTIFIER;
+ }else if(isDigit(c)){
+ // NOTE(fusion): `this->Bytes` points to `this->String` (set
+ // in the constructor) which is why we check against the size
+ // of the `this->String` array. It doesn't seem to store the
+ // number of bytes but I've only seen it used with fixed size
+ // arrays like outfit colors or sector offsets.
+ // Also, it doesn't make sense to have a null terminator in
+ // a byte string so the capacity check doesn't include it as
+ // in the case of regular strings.
+
+ // TODO(fusion): We're not checking for integer overflow at all.
+ int BytesLength = 0;
+ int Number = c - '0';
+ while(true){
+ int next = getc(File);
+ if(isDigit(next)){
+ Number *= 10;
+ Number += next - '0';
+ }else if(next == '-'){
+ if(BytesLength >= NARRAY(this->String)){
+ this->error("too many bytes");
+ }
+ this->Bytes[BytesLength] = (uint8)Number;
+ BytesLength += 1;
+
+ // NOTE(fusion): If there is a '-' after a number, there
+ // better be a second number.
+ next = getc(File);
+ if(next == EOF){
+ this->error("unexpected end of file");
+ }else if(!isDigit(next)){
+ this->error("syntax error");
+ }
+ Number = next - '0';
+ }else{
+ if(next != EOF){
+ ungetc(next, File);
+ }
+
+ if(BytesLength <= 0){
+ this->Token = NUMBER;
+ this->Number = Number;
+ }else{
+ if(BytesLength >= NARRAY(this->String)){
+ this->error("too many bytes");
+ }
+ this->Token = BYTES;
+ this->Bytes[BytesLength] = (uint8)Number;
+ BytesLength += 1;
+ }
+ break;
+ }
+ }
+ }else{
+ this->Token = SPECIAL;
+ this->Special = (char)c;
+ }
+ return;
+ }
+ }
+ }
+}
+
+char *TReadScriptFile::getIdentifier(void){
+ if(this->Token != IDENTIFIER){
+ this->error("identifier expected");
+ }
+ strLower(this->String);
+ return this->String;
+}
+
+int TReadScriptFile::getNumber(void){
+ if(this->Token != NUMBER){
+ this->error("number expected");
+ }
+ return this->Number;
+}
+
+char *TReadScriptFile::getString(void){
+ if(this->Token != STRING){
+ this->error("string expected");
+ }
+ return this->String;
+}
+
+uint8 *TReadScriptFile::getBytesequence(void){
+ if(this->Token != BYTES){
+ this->error("byte-sequence expected");
+ }
+ return this->Bytes;
+}
+
+void TReadScriptFile::getCoordinate(int *x, int *y, int *z){
+ if(this->Token != COORDINATE){
+ this->error("coordinates expected");
+ }
+ *x = this->CoordX;
+ *y = this->CoordY;
+ *z = this->CoordZ;
+}
+
+char TReadScriptFile::getSpecial(void){
+ if(this->Token != SPECIAL){
+ this->error("special-char expected");
+ }
+ return this->Special;
+}
+
+// TODO(fusion): REMOVE. This is a snippet to test the script lexer.
+int main(int argc, char **argv){
+ TReadScriptFile Script;
+ Script.open("local/alexander.npc");
+ while(true){
+ Script.nextToken();
+ if(Script.Token == ENDOFFILE){
+ printf("EOF\n");
+ Script.close();
+ break;
+ }
+
+ switch(Script.Token){
+ case IDENTIFIER: printf("IDENTIFIER \"%s\"\n", Script.String); break;
+ case NUMBER: printf("NUMBER %d\n", Script.Number); break;
+ case STRING: printf("STRING \"%s\"\n", Script.String); break;
+ case BYTES: printf("BYTES %u-%u-%u-%u-%u\n", Script.Bytes[0], Script.Bytes[1], Script.Bytes[2], Script.Bytes[3], Script.Bytes[4]); break;
+ case COORDINATE: printf("COORD [%d,%d,%d]\n", Script.CoordX, Script.CoordY, Script.CoordZ); break;
+ case SPECIAL: printf("SPECIAL '%c'\n", Script.Special); break;
+ default: printf("UNKNOWN TOKEN %d\n", Script.Token); break;
+ }
+ }
+
+ return EXIT_FAILURE;
+}
diff --git a/src/script.hh b/src/script.hh
new file mode 100644
index 0000000..2825d6d
--- /dev/null
+++ b/src/script.hh
@@ -0,0 +1,94 @@
+#ifndef TIBIA_SCRIPT_HH_
+#define TIBIA_SCRIPT_HH_ 1
+
+#include "main.hh"
+#include "enums.hh"
+
+struct TReadScriptFile {
+ // REGULAR FUNCTIONS
+ // =========================================================================
+ TReadScriptFile(void);
+ ~TReadScriptFile(void);
+ void open(const char *FileName);
+ void close(void);
+ void error(const char *Text);
+ void nextToken(void);
+ char *getIdentifier(void);
+ int getNumber(void);
+ char *getString(void);
+ uint8 *getBytesequence(void);
+ void getCoordinate(int *x, int *y, int *z);
+ char getSpecial(void);
+
+ char *readIdentifier(void){
+ this->nextToken();
+ return this->getIdentifier();
+ }
+
+ int readNumber(void){
+ this->nextToken();
+
+ int Sign = 1;
+ if(this->Token == SPECIAL && this->Special == '-'){
+ Sign = -1;
+ this->nextToken();
+ }
+
+ return Sign * this->getNumber();
+ }
+
+ char *readString(void){
+ this->nextToken();
+ return this->getString();
+ }
+
+ uint8 *readBytesequence(void){
+ this->nextToken();
+ return this->getBytesequence();
+ }
+
+ void readCoordinate(int *x, int *y, int *z){
+ this->nextToken();
+ this->getCoordinate(x, y, z);
+ }
+
+ char readSpecial(void){
+ this->nextToken();
+ return this->getSpecial();
+ }
+
+ void TReadScriptFile::readSymbol(char Symbol){
+ if(this->readSpecial() != Symbol){
+ this->error("symbol mismatch");
+ }
+ }
+
+ // DATA
+ // =========================================================================
+ TOKEN Token;
+ FILE *File[3];
+ char Filename[3][4096];
+ int Line[3];
+ char String[4000];
+ int RecursionDepth;
+ uint8 *Bytes;
+ int Number;
+ int CoordX;
+ int CoordY;
+ int CoordZ;
+ char Special;
+};
+
+struct TWriteScriptFile {
+ // REGULAR FUNCTIONS
+ // =========================================================================
+ //TWriteScriptFile(void);
+
+ // DATA
+ // =========================================================================
+ FILE *File;
+ char Filename[4096];
+ int Line;
+};
+
+#endif //TIBIA_SCRIPT_HH_
diff --git a/src/util.cc b/src/util.cc
index f2ebc48..4f74c80 100644
--- a/src/util.cc
+++ b/src/util.cc
@@ -1,4 +1,4 @@
-#include "util.hh"
+#include "main.hh"
static void (*ErrorFunction)(const char *Text) = NULL;
static void (*PrintFunction)(int Level, const char *Text) = NULL;