From de848a4131beb288335b6aaa29c2a6a439c50e0e Mon Sep 17 00:00:00 2001 From: fusion32 Date: Thu, 22 May 2025 01:02:13 -0300 Subject: implement script lexer --- TODO.md | 7 + build.bat | 3 +- reference/game.c | 1050 ---------------------------------------------------- reference/types.hh | 36 -- src/config.cc | 2 +- src/config.hh | 61 +++ src/main.cc | 1 + src/main.hh | 44 +-- src/script.cc | 617 ++++++++++++++++++++++++++++++ src/script.hh | 94 +++++ src/util.cc | 2 +- 11 files changed, 787 insertions(+), 1130 deletions(-) create mode 100644 src/config.hh create mode 100644 src/script.cc create mode 100644 src/script.hh 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 #include 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 +//STATIC_ASSERT(OS_LINUX); +//#include +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; -- cgit v1.2.3