diff --git a/GNUmakefile b/GNUmakefile
index ace1416c8..e1727537b 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -778,6 +778,7 @@ blood_game_objs := \
aizomba.cpp \
aizombf.cpp \
asound.cpp \
+ barf.cpp \
callback.cpp \
choke.cpp \
common.cpp \
diff --git a/platform/Windows/nblood.vcxproj b/platform/Windows/nblood.vcxproj
index ae97ef0e5..93b802e91 100644
--- a/platform/Windows/nblood.vcxproj
+++ b/platform/Windows/nblood.vcxproj
@@ -195,6 +195,7 @@
+
@@ -299,6 +300,7 @@
+
diff --git a/platform/Windows/nblood.vcxproj.filters b/platform/Windows/nblood.vcxproj.filters
index 2fba2f06c..1e38b67a6 100644
--- a/platform/Windows/nblood.vcxproj.filters
+++ b/platform/Windows/nblood.vcxproj.filters
@@ -228,6 +228,9 @@
Source Files
+
+ Source Files
+
@@ -457,5 +460,8 @@
Header Files
+
+ Header Files
+
\ No newline at end of file
diff --git a/source/blood/src/barf.cpp b/source/blood/src/barf.cpp
new file mode 100644
index 000000000..f548dafc9
--- /dev/null
+++ b/source/blood/src/barf.cpp
@@ -0,0 +1,952 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 sirlemonhead, Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+
+#include "compat.h"
+#ifdef WITHKPLIB
+#include "kplib.h"
+#endif
+#include "common_game.h"
+#include "resource.h"
+#include "misc.h"
+#include "globals.h"
+#include "sound.h"
+
+static Resource gBarfRes;
+
+#define kMaxCmdLineDefines 5
+#define kMaxDefines 1000
+#define kMaxParseLevels 5
+static int nCmdDefines = 0;
+static int nDefines = 0;
+
+static int gParseLevel = 0;
+int dword_44CE0[kMaxParseLevels] = { 0, 0, 0, 0, 0 };
+
+// FIXME
+unsigned int nBytes = 0;
+char buffer[1024];
+int scriptValue = 0;
+
+char scriptBuffer[256];
+
+struct define_t
+{
+ char *_text;
+ int _value;
+};
+
+define_t gCmdDefines[kMaxCmdLineDefines];
+
+void sub_11DF0(char *pzScriptDir, char *fileName, char flags, int ID);
+void sub_11C10(char *pzScriptDir, char *fileName, char flags, int ID);
+
+struct tag_t {
+ const char *_value;
+ uint8_t _index;
+};
+
+enum eTags
+{
+ kTag0,
+ kTagEnd,
+ kTagString,
+ kTagConstant,
+ kTag4, // string constant?
+ kTagComma,
+ kTagSemiColon,
+ kTagColon,
+ kTagEquals,
+ kTagHash,
+ kTagComment,
+ kTagInclude,
+ kTagResource,
+ kTagAs,
+ kTagPreload,
+ kTagPrelock,
+ kTagData,
+ kTagLoad,
+ kTagEmit,
+ kTagIfDef,
+ kTagEndif,
+ kTagElse
+};
+
+tag_t tags[] =
+{
+ { ",", kTagComma },
+ { ";", kTagSemiColon },
+ { ":", kTagColon },
+ { "=", kTagEquals },
+ { "#", kTagHash },
+ { "//", kTagComment },
+ { "INCLUDE", kTagInclude },
+ { "RESOURCE", kTagResource },
+ { "AS", kTagAs },
+ { "PRELOAD", kTagPreload },
+ { "PRELOCK", kTagPrelock },
+ { "DATA", kTagData },
+ { "LOAD", kTagLoad },
+ { "EMIT", kTagEmit },
+ { "%ifdef", kTagIfDef },
+ { "%endif", kTagEndif },
+ { "%else", kTagElse }
+};
+
+const int kTagCount = sizeof(tags) / sizeof(tag_t);
+
+int qsort_compar(const void *a, const void *b)
+{
+ return stricmp((const char*)a, (const char*)b);
+}
+
+void SortTags()
+{
+ qsort(tags, kTagCount, sizeof(tag_t), qsort_compar);
+}
+
+void AddCmdDefine(char *text, int value)
+{
+ dassert(nCmdDefines < kMaxCmdLineDefines);
+
+ gCmdDefines[nCmdDefines]._text = (char*)Resource::Alloc(strlen(text) + 1);
+
+ strcpy(gCmdDefines[nCmdDefines]._text, text);
+ gCmdDefines[nCmdDefines]._value = value;
+
+ nCmdDefines++;
+}
+
+// 174 bytes
+struct RFS
+{
+private:
+ char *_ptr; // [0]
+ char _curChar; // [4]
+ char *_pUnknown2; // [5] - some sort of pointer into _ptr?
+ char *_pStartLine; // [9]
+ char *_pEnd; // [13]
+ char *_pMark; // [17]
+ char _unknown6; // [21]
+ int _unknown7; // [22]
+ int _curLine; // [26]
+ char _fileName[BMAX_PATH]; // [30]
+
+public:
+ void Open(const char *fileName);
+ void Close();
+ void Increment();
+ void SkipBeyondValue(char value);
+ uint8_t GetNextTag();
+ void ScriptError(const char *message);
+ void SetMark();
+ void UnsetMark();
+};
+
+void RFS::Open(const char *fileName)
+{
+ strcpy(_fileName, fileName);
+
+ int hFile = open(_fileName, O_BINARY);
+ if (hFile == -1) {
+ ThrowError("Error opening file %s", _fileName);
+ }
+
+ int fileSize = filelength(hFile);
+ _ptr = (char*)Resource::Alloc(fileSize);
+ if (_ptr == NULL) {
+ ThrowError("Not enough memory to read %s", _fileName);
+ }
+
+ read(hFile, _ptr, fileSize);
+ close(hFile);
+
+ _curLine = 0;
+ _pUnknown2 = _ptr;
+ _curChar = '\n';
+ _pEnd = &_ptr[fileSize - 1];
+}
+
+void RFS::Close()
+{
+ if (_ptr) {
+/* BUG - the original code called nfree but this should be a Resource::Free()
+ _nfree(_ptr);
+*/
+ Resource::Free(_ptr);
+ }
+}
+
+void RFS::Increment()
+{
+ if (_curChar == '\n') {
+ _curLine++;
+ _pStartLine = _pUnknown2;
+ }
+
+ if (_pUnknown2 >= _pEnd) {
+ _curChar = 0;
+ }
+ else {
+ _curChar = *_pUnknown2; // grabs the next char
+ _pUnknown2++; // increment pointer into char data
+ }
+}
+
+void RFS::SkipBeyondValue(char nVal)
+{
+ while (_curChar && _curChar != nVal) {
+ Increment();
+ }
+}
+
+void RFS::SetMark()
+{
+ _pMark = _pUnknown2;
+ _unknown6 = _curChar;
+ _unknown7 = _curLine;
+}
+
+// inverse of the above function
+void RFS::UnsetMark()
+{
+ _pUnknown2 = _pMark;
+ _curChar = _unknown6;
+ _curLine = _unknown7;
+}
+
+void RFS::ScriptError(const char *message)
+{
+ // TODO
+
+ char *p = _pStartLine;
+ while (*p != '\n')
+ {
+ if (isprint(*p))
+ putchar(*p);
+ else
+ putchar(' ');
+ p++;
+ }
+
+ putchar('\n');
+
+ p = _pStartLine;
+
+ while (p < _pMark)
+ {
+ putchar(' ');
+ p++;
+ }
+
+ puts("^");
+
+ initprintf("Error in %s line %d: %s\n\n", _fileName, _curLine, message);
+}
+
+uint8_t RFS::GetNextTag()
+{
+ // skip any space characters
+ do {
+ Increment();
+ } while (isspace(_curChar));
+
+ if (_curChar == '\0') {
+ return kTagEnd;
+ }
+
+ SetMark();
+
+ // Path A
+ if (_curChar == '"')
+ {
+ Increment();
+
+ int i = 0;
+
+ // section 1
+ while (1) {
+
+ if (_curChar == '\0' || _curChar == '"') {
+ scriptBuffer[i] = '\0';
+ return kTagString;
+ }
+
+ if (i == 256) {
+ ScriptError("String exceeds maximum string length");
+ break;
+ }
+
+ scriptBuffer[i] = _curChar;
+ i++;
+ Increment();
+ }
+
+ // section 2
+ while (1)
+ {
+ if (_curChar == '\0' || _curChar == '"') {
+ return kTag0;
+ }
+
+ Increment();
+ }
+ }
+ else
+ {
+ scriptValue = 0;
+ BOOL isNegative = FALSE; // or 'isSigned' ?
+
+ // is it a negative number?
+ if (_curChar == '-')
+ {
+ Increment();
+
+ isNegative = TRUE;
+
+ if (!isdigit(_curChar)) {
+ UnsetMark();
+ }
+ }
+
+ if (isdigit(_curChar))
+ {
+ // left path
+ if (_curChar == '0')
+ {
+ Increment();
+
+ // handle a hex value
+ if (toupper(_curChar) == 'X')
+ {
+ // orange loop
+ while (1)
+ {
+ Increment();
+ if (!isxdigit(_curChar)) { // isxdigit() checks for a hex value
+ break;
+ }
+
+ // hex version of atoi?
+ scriptValue *= 16;
+ if (!isdigit(_curChar)) {
+ scriptValue += toupper(_curChar) - 55;
+ }
+ else {
+ scriptValue += _curChar - '0';
+ }
+
+ SetMark();
+ }
+
+ UnsetMark();
+ if (isNegative) {
+ scriptValue = -scriptValue;
+ }
+
+ return kTagConstant;
+ }
+
+ UnsetMark();
+ }
+
+ // the loop
+ while (isdigit(_curChar))
+ {
+ // atoi implementation
+ scriptValue = scriptValue * 10 + _curChar - '0';
+ SetMark();
+ Increment();
+ }
+
+ UnsetMark();
+ if (isNegative) {
+ scriptValue = -scriptValue;
+ }
+
+ return kTagConstant;
+ }
+ else
+ {
+ // BLUEISH PATH
+ int ebp = 0; // v11
+ int i = 0;
+
+ // blue loop #1
+ while (1)
+ {
+ scriptBuffer[ebp] = _curChar;
+ ebp++;
+ int eax = -1;
+
+ // blue loop #2
+ for (i = 0; i < kTagCount; i++)
+ {
+ //if (eax >= 0) {
+ if (eax == 0) {
+ break;
+ }
+
+ // eax = strnicmp(tags[i]._value, scriptBuffer, ebp);
+ eax = strnicmp(scriptBuffer, tags[i]._value, ebp);
+
+ //if (eax >= 0) {
+ if (eax == 0) {
+ break;
+ }
+ }
+
+ if (eax > 0 || i == kTagCount) {
+ break;
+ }
+
+ if (eax == 0 && strlen(tags[i]._value) == ebp)
+ {
+ scriptBuffer[ebp] = 0;
+ return tags[i]._index;
+ }
+
+ Increment();
+ }
+
+ UnsetMark();
+
+ i = 0;
+
+ while (isalnum(_curChar))
+ {
+ scriptBuffer[i] = _curChar;
+ SetMark();
+ i++;
+ Increment();
+ }
+
+ UnsetMark();
+ scriptBuffer[i] = 0;
+ return kTag4;
+ }
+ }
+
+ // qAssert(1==0); // TODO - what to return here
+}
+
+void ParseScript(char *scriptFileName)
+{
+ char text[256];
+ char char256_1[256];
+ char char256_2[256];
+ char fileName[BMAX_PATH];
+ char inp[BMAX_PATH];
+ char zScriptDirectory[BMAX_PATH], zTemp1[BMAX_PATH], zTemp2[BMAX_PATH];
+
+ SplitPath(scriptFileName, zScriptDirectory, zTemp1, zTemp2);
+
+ RFS rfs;
+
+ // AddExtension(name, ".RFS");
+ rfs.Open(scriptFileName);
+
+ gParseLevel = 0;
+ dword_44CE0[0] = 0;
+
+ BOOL parsing = TRUE;
+
+ while (parsing)
+ {
+ // START LOOP. to be fixed later
+ START:
+
+ uint8_t tag = rfs.GetNextTag();
+
+ switch (tag)
+ {
+ default:
+ {
+ break;
+ }
+ case kTagEnd:
+ {
+ parsing = FALSE;
+ break;
+ }
+ case kTagComment:
+ {
+ // skip to next line
+ rfs.SkipBeyondValue('\n');
+ break;
+ }
+ case kTagEmit: // minty/light green colour
+ {
+ tag = rfs.GetNextTag();
+ if (tag != kTag4)
+ {
+ rfs.ScriptError("Symbol name expected");
+ rfs.SkipBeyondValue(';');
+ break;
+ }
+ else
+ {
+ strcpy(char256_2, scriptBuffer);
+ tag = rfs.GetNextTag();
+ if (tag != kTagEquals)
+ {
+ rfs.ScriptError("Missing '='");
+ rfs.SkipBeyondValue(';');
+ break;
+ }
+
+ tag = rfs.GetNextTag();
+ if (tag != kTagConstant)
+ {
+ rfs.ScriptError("Constant expected");
+ rfs.SkipBeyondValue(';');
+ break;
+ }
+ else
+ {
+ //AddDefine(char256_2, scriptValue);
+ rfs.SkipBeyondValue('\n');
+ }
+ }
+ }
+ case kTagResource: // really light blue..
+ {
+ if (kTagString != rfs.GetNextTag()) {
+ rfs.ScriptError("String constant exected");
+ rfs.SkipBeyondValue('\n');
+ break;
+ }
+
+ strcpy(inp, scriptBuffer);
+ char nFlags = 0;
+ int ID = 0;
+ BOOL isDefine = FALSE;
+
+ tag = rfs.GetNextTag();
+ if (tag == kTagAs)
+ {
+ tag = rfs.GetNextTag();
+ if (tag == kTag4)
+ {
+ strcpy(text, scriptBuffer);
+
+ if (rfs.GetNextTag() != kTagEquals)
+ {
+ rfs.ScriptError("Missing '='");
+ rfs.SkipBeyondValue(';');
+ break;
+ }
+
+ isDefine = TRUE;
+ tag = rfs.GetNextTag();
+ }
+
+ if (tag != kTagConstant)
+ {
+ rfs.ScriptError("Constant expected");
+ rfs.SkipBeyondValue(';');
+ break;
+ }
+
+ if (isDefine) {
+ //AddDefine(text, scriptValue);
+ }
+
+ ID = scriptValue;
+ nFlags |= DICT_ID;
+ tag = rfs.GetNextTag();
+ }
+
+ //if (!bNoEncrypt) {
+ // nFlags |= kResFlagIsEncrypted;
+ //}
+
+ while (tag == kTagComma)
+ {
+ tag = rfs.GetNextTag();
+
+ if (tag == kTagPreload) {
+ nFlags |= DICT_LOAD;
+ }
+ else if (tag == kTagPrelock) {
+ nFlags |= DICT_LOCK;
+ }
+ else {
+ rfs.ScriptError("Unrecognized flag");
+ rfs.SkipBeyondValue(';');
+ goto START; // FIXME
+ }
+
+ tag = rfs.GetNextTag();
+ }
+
+ if (tag != kTagSemiColon)
+ {
+ rfs.ScriptError("';' expected");
+ rfs.SkipBeyondValue(';');
+ break;
+ }
+
+ if (dword_44CE0[gParseLevel] == 0) {
+ sub_11C10(zScriptDirectory, inp, nFlags, ID);
+ }
+
+ break;
+ }
+ case kTagIfDef: // purplish colour
+ {
+ tag = rfs.GetNextTag();
+ if (tag != kTag4)
+ {
+ rfs.ScriptError("Parameter error in ifdef");
+ rfs.SkipBeyondValue('\n');
+ break;
+ }
+ else
+ {
+ rfs.SetMark();
+ strcpy(char256_1, scriptBuffer);
+
+ BOOL bGotDefine = FALSE;
+
+ // check if this was defined via command prompt arguments
+ for (int i = 0; i < nCmdDefines; i++)
+ {
+ if (stricmp(gCmdDefines[i]._text, char256_1) == 0) { // string is equivalent
+ bGotDefine = TRUE;
+ break;
+ }
+ }
+
+ // loc_11FC3:
+ gParseLevel++;
+ dassert(gParseLevel < kMaxParseLevels);
+
+ if (bGotDefine) {
+ dword_44CE0[gParseLevel] = dword_44CE0[gParseLevel - 1];
+ }
+ else {
+ dword_44CE0[gParseLevel] = 1;
+ }
+ }
+ break;
+ }
+ case kTagElse: // pinky colour
+ {
+ if (gParseLevel)
+ {
+ // loc_12066:
+ if (dword_44CE0[gParseLevel - 1] == 0) {
+ if (dword_44CE0[gParseLevel] == 0) {
+ dword_44CE0[gParseLevel] = 1;
+ }
+
+ rfs.SkipBeyondValue('\n');
+ break;
+ }
+ }
+ else {
+ rfs.ScriptError("Unexpected else");
+ rfs.SkipBeyondValue('\n');
+ break;
+ }
+ break;
+ }
+ case kTagEndif: // poo coloured
+ {
+ if (gParseLevel) {
+ gParseLevel--;
+ rfs.SkipBeyondValue('\n');
+ break;
+ }
+ else
+ {
+ rfs.ScriptError("Unexpected Endif");
+ rfs.SkipBeyondValue('\n');
+ break;
+ }
+ }
+ case kTagHash: // gold colour
+ {
+ tag = rfs.GetNextTag();
+ if (tag == kTagInclude)
+ {
+ tag = rfs.GetNextTag();
+ if (tag != kTagString)
+ {
+ rfs.ScriptError("String constant exected");
+ // why no SkipBeyondValue?
+ break;
+ }
+ else
+ {
+ ParseScript(scriptBuffer);
+ }
+ }
+ break;
+ }
+ case kTagData: // eg: data "AMB1.SFX" as 1: 80, 0x10000, 0x0, 1, -1, "amb1";
+ {
+ // green coloured section
+ if (rfs.GetNextTag() != kTagString) {
+ rfs.ScriptError("String constant expected");
+ rfs.SkipBeyondValue(';');
+ break;
+ }
+
+ // eg strcpy(fileName, "AMB1.SFX");
+ strcpy(fileName, scriptBuffer);
+
+ char nFlags = 0;
+ int ID = 0;
+
+ BOOL isDefine = FALSE;
+
+ tag = rfs.GetNextTag();
+
+ // process an ID section
+ if (tag == kTagAs)
+ {
+ tag = rfs.GetNextTag();
+ if (tag == kTag4)
+ {
+ strcpy(fileName, scriptBuffer);
+
+ tag = rfs.GetNextTag();
+ if (tag != kTagEquals) {
+ rfs.ScriptError("Missing '='");
+ rfs.SkipBeyondValue(';');
+ break;
+ }
+
+ isDefine = TRUE;
+ tag = rfs.GetNextTag();
+ }
+
+ if (tag != kTagConstant)
+ {
+ rfs.ScriptError("Constant Expected");
+ rfs.SkipBeyondValue(';');
+ break;
+ }
+ else {
+ //if (isDefine) {
+ // AddDefine(fileName, scriptValue);
+ //}
+
+ nFlags |= DICT_ID;
+ ID = scriptValue;
+ tag = rfs.GetNextTag();
+ }
+ }
+
+ if (tag == kTagComma)
+ {
+ // process all sections on this line that are comma separated
+ while (1)
+ {
+ tag = rfs.GetNextTag();
+ if (tag == kTagPreload)
+ {
+ nFlags |= DICT_LOAD;
+ tag = rfs.GetNextTag();
+
+ if (tag == kTagComma) {
+ continue;
+ }
+ }
+ else if (tag == kTagPrelock)
+ {
+ nFlags |= DICT_LOCK;
+ tag = rfs.GetNextTag();
+
+ if (tag == kTagComma) {
+ continue;
+ }
+ }
+ else
+ {
+ rfs.ScriptError("Unrecognized flag");
+ rfs.SkipBeyondValue(';');
+ goto START; // FIXME
+ }
+ }
+ }
+
+ // loc_12471:
+ if (tag != kTagColon) // marked orange in IDA
+ {
+ while (1)
+ {
+ if (tag == kTagPreload)
+ {
+ nFlags |= DICT_LOAD;
+ tag = rfs.GetNextTag();
+
+ if (tag == kTagColon) {
+ break;
+ }
+ }
+ else if (tag == kTagPrelock)
+ {
+ nFlags |= DICT_LOCK;
+ tag = rfs.GetNextTag();
+
+ if (tag == kTagColon) {
+ break;
+ }
+ }
+ else {
+ rfs.ScriptError("':' expected");
+ rfs.SkipBeyondValue(';');
+ goto START; // FIXME
+ }
+ }
+ }
+
+ nBytes = 0;
+
+ // yellow loop
+ while (1)
+ {
+ tag = rfs.GetNextTag();
+
+ switch (tag)
+ {
+ case kTagString:
+ {
+ memcpy(&buffer[nBytes], scriptBuffer, strlen(scriptBuffer) + 1);
+ nBytes += strlen(scriptBuffer) + 1;
+ break;
+ }
+ case kTagConstant:
+ {
+ memcpy(&buffer[nBytes], &scriptValue, sizeof(scriptValue));
+ nBytes += sizeof(scriptValue);
+ break;
+ }
+ default:
+ {
+ rfs.ScriptError("Constant expected");
+ rfs.SkipBeyondValue(';');
+ goto START; // FIXME
+ }
+ }
+
+ tag = rfs.GetNextTag();
+ if (tag != kTagComma) {
+ break;
+ }
+ }
+
+ if (tag != kTagSemiColon) {
+ rfs.ScriptError("Semicolon expected");
+ rfs.SkipBeyondValue(';');
+ break;
+ }
+ else
+ {
+ if (dword_44CE0[gParseLevel] == 0) {
+ sub_11DF0(zScriptDirectory, fileName, nFlags, ID);
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ //CreateHeader();
+ rfs.Close();
+}
+
+void sub_11C10(char *pzScriptDir, char *fileName, char flags, int ID)
+{
+ char zDirectory[BMAX_PATH];
+ char zFilename[BMAX_PATH];
+ char zType[BMAX_PATH];
+ BDIR* dirr;
+ struct Bdirent* dirent;
+ dirr = Bopendir("./");
+ if (dirr)
+ {
+ while (dirent = Breaddir(dirr))
+ {
+ if (!Bwildmatch(dirent->name, fileName))
+ continue;
+
+ SplitPath(dirent->name, zDirectory, zFilename, zType);
+
+ if (!Bstrcasecmp(zType, "RAW") || !Bstrcasecmp(zType, "SFX") || !Bstrcasecmp(zType, "MID") || !Bstrcasecmp(zType, "TMB"))
+ gSoundRes.AddExternalResource(zFilename, zType, ID, flags, dirent->name);
+ else
+ gSysRes.AddExternalResource(zFilename, zType, ID, flags, dirent->name);
+ }
+ Bclosedir(dirr);
+ }
+ dirr = Bopendir(pzScriptDir);
+ if (dirr)
+ {
+ while (dirent = Breaddir(dirr))
+ {
+ if (!Bwildmatch(dirent->name, fileName))
+ continue;
+
+ SplitPath(dirent->name, zDirectory, zFilename, zType);
+
+ if (!Bstrcasecmp(zType, "RAW") || !Bstrcasecmp(zType, "SFX") || !Bstrcasecmp(zType, "MID") || !Bstrcasecmp(zType, "TMB"))
+ gSoundRes.AddExternalResource(zFilename, zType, ID, flags, dirent->name);
+ else
+ gSysRes.AddExternalResource(zFilename, zType, ID, flags, dirent->name);
+ }
+ Bclosedir(dirr);
+ }
+}
+
+void sub_11DF0(char* pzScriptDir, char *filePath, char flags, int ID)
+{
+ char zDirectory[BMAX_PATH];
+ char zFilename[BMAX_PATH];
+ char zType[BMAX_PATH];
+ char zFilePath[BMAX_PATH];
+ buildvfs_fd handle;
+
+ ConcatPath(pzScriptDir, filePath, zFilePath);
+
+ handle = kopen4loadfrommod(zFilePath, 0);
+ if (handle == buildvfs_fd_invalid)
+ {
+ Bstrcpy(zFilePath, filePath);
+ handle = kopen4loadfrommod(zFilePath, 0);
+ if (handle == buildvfs_fd_invalid)
+ return;
+ }
+
+ kclose(handle);
+
+ SplitPath(zFilePath, zDirectory, zFilename, zType);
+
+ if (!Bstrcasecmp(zType, "RAW") || !Bstrcasecmp(zType, "SFX") || !Bstrcasecmp(zType, "MID") || !Bstrcasecmp(zType, "TMB"))
+ gSoundRes.AddExternalResource(zFilename, zType, ID, flags, zFilePath);
+ else
+ gSysRes.AddExternalResource(zFilename, zType, ID, flags, zFilePath);
+}
+
diff --git a/source/blood/src/barf.h b/source/blood/src/barf.h
new file mode 100644
index 000000000..e93bcc7d3
--- /dev/null
+++ b/source/blood/src/barf.h
@@ -0,0 +1,24 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 sirlemonhead, Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+
diff --git a/source/blood/src/misc.cpp b/source/blood/src/misc.cpp
index cc3e62503..cce0ce438 100644
--- a/source/blood/src/misc.cpp
+++ b/source/blood/src/misc.cpp
@@ -131,11 +131,80 @@ void ChangeExtension(char *pzFile, const char *pzExt)
_splitpath(pzFile, drive, dir, filename, NULL);
_makepath(pzFile, drive, dir, filename, pzExt);
#else
- char *pDot = strrchr(pzFile, '.');
- if (!pDot)
- pDot = pzFile + strlen(pzFile);
- else
- *pDot = 0;
- strcat(pDot, pzExt);
+ int const nLength = Bstrlen(pzFile);
+ char * pDot = pzFile+nLength;
+ for (int i = nLength-1; i >= 0; i--)
+ {
+ if (pzFile[i] == '/' || pzFile[i] == '\\')
+ break;
+ if (pzFile[i] == '.')
+ {
+ pDot = pzFile+i;
+ break;
+ }
+ }
+ *pDot = '\0';
+ Bstrcat(pDot, pzExt);
#endif
}
+
+void SplitPath(const char *pzPath, char *pzDirectory, char *pzFile, char *pzType)
+{
+ int const nLength = Bstrlen(pzFile);
+ const char *pDirectory = pzFile+nLength;
+ const char *pDot = NULL;
+ for (int i = nLength-1; i >= 0; i--)
+ {
+ if (pzFile[i] == '/' || pzFile[i] == '\\')
+ {
+ Bstrncpy(pzDirectory, pzPath, i);
+ pzDirectory[i] = 0;
+ if (!pDot)
+ {
+ Bstrcpy(pzFile, pzPath+i+1);
+ Bstrcpy(pzType, "");
+ }
+ else
+ {
+ Bstrncpy(pzFile, pzPath+i+1, pDot-(pzPath+i+1));
+ Bstrcpy(pzType, pDot+1);
+ }
+
+ return;
+ }
+ else if (pzFile[i] == '.')
+ {
+ pDot = pzFile+i;
+ }
+ }
+ Bstrcpy(pzDirectory, "/");
+ if (!pDot)
+ {
+ Bstrcpy(pzFile, pzPath);
+ Bstrcpy(pzType, "");
+ }
+ else
+ {
+ Bstrncpy(pzFile, pzPath, pDot-pzPath);
+ Bstrcpy(pzType, pDot+1);
+ }
+}
+
+void ConcatPath(const char *pzPath1, const char *pzPath2, char *pzConcatPath)
+{
+ int n1 = Bstrlen(pzPath1), n2 = Bstrlen(pzPath2);
+ int i = n1, j = 0;
+ while (i > 0 && (pzPath1[i-1] == '/' || pzPath1[i-1] == '\\'))
+ {
+ i--;
+ }
+ while (j < n2 && (pzPath2[j] == '/' || pzPath1[j] == '\\'))
+ {
+ j++;
+ }
+ Bstrncpy(pzConcatPath, pzPath1, i);
+ pzConcatPath[i] = 0;
+ Bstrcpy(pzConcatPath, pzPath2+j);
+}
+
+
diff --git a/source/blood/src/misc.h b/source/blood/src/misc.h
index ea6cd367b..8e83e82ac 100644
--- a/source/blood/src/misc.h
+++ b/source/blood/src/misc.h
@@ -30,3 +30,5 @@ unsigned int qrand(void);
int wrand(void);
void wsrand(int);
void ChangeExtension(char *pzFile, const char *pzExt);
+void SplitPath(const char *pzPath, char *pzDirectory, char *pzFile, char *pzType);
+void ConcatPath(const char* pzPath1, const char* pzPath2, char* pzConcatPath);
diff --git a/source/blood/src/resource.cpp b/source/blood/src/resource.cpp
index 3c386e6f7..71ae279f4 100644
--- a/source/blood/src/resource.cpp
+++ b/source/blood/src/resource.cpp
@@ -68,6 +68,8 @@ Resource::~Resource(void)
Free(dict[i].type);
if (dict[i].name)
Free(dict[i].name);
+ if (dict[i].path)
+ Free(dict[i].path);
}
Free(dict);
dict = NULL;
@@ -149,6 +151,7 @@ void Resource::Init(const char *filename)
dict[i].name = (char*)Alloc(nNameLength+1);
strncpy(dict[i].type, tdict[i].type, min(3, nTypeLength));
strncpy(dict[i].name, tdict[i].name, min(8, nNameLength));
+ dict[i].path = NULL;
dict[i].type[nTypeLength] = 0;
dict[i].name[nNameLength] = 0;
dict[i].id = B_LITTLE32(tdict[i].id);
@@ -362,14 +365,20 @@ void Resource::Grow(void)
Reindex();
}
-void Resource::AddExternalResource(const char *name, const char *type, int id)
+void Resource::AddExternalResource(const char *name, const char *type, int id, int flags, const char *pzDirectory)
{
- char name2[BMAX_PATH], type2[BMAX_PATH], filename[BMAX_PATH*2];
- //if (strlen(name) > 8 || strlen(type) > 3) return;
+ char name2[BMAX_PATH], type2[BMAX_PATH], filename[BMAX_PATH], path[BMAX_PATH];
+
if (Bstrlen(type) > 0)
- Bsprintf(filename, "%s.%s", name, type);
+ Bsnprintf(filename, BMAX_PATH-1, "%s.%s", name, type);
else
- Bsprintf(filename, "%s", name);
+ Bsnprintf(filename, BMAX_PATH-1, "%s", name);
+
+ if (pzDirectory)
+ Bsnprintf(path, BMAX_PATH-1, "%s/%s", pzDirectory, filename);
+ else
+ Bstrncpy(path, filename, BMAX_PATH-1);
+
int fhandle = kopen4loadfrommod(filename, 0);
if (fhandle == -1)
return;
@@ -404,17 +413,25 @@ void Resource::AddExternalResource(const char *name, const char *type, int id)
Free(node->name);
node->name = NULL;
}
+ if (node->path)
+ {
+ Free(node->path);
+ node->path = NULL;
+ }
int nTypeLength = strlen(type2);
int nNameLength = strlen(name2);
+ int nPathLength = strlen(path);
node->type = (char*)Alloc(nTypeLength+1);
node->name = (char*)Alloc(nNameLength+1);
+ node->path = (char*)Alloc(nPathLength+1);
strcpy(node->type, type2);
strcpy(node->name, name2);
+ strcpy(node->path, path);
}
node->size = size;
- node->flags |= DICT_EXTERNAL;
+ node->flags |= DICT_EXTERNAL | flags;
Flush(node);
- if (id != -1)
+ if (id >= 0)
{
index = Probe(id, type2);
dassert(index != NULL);
@@ -439,15 +456,23 @@ void Resource::AddExternalResource(const char *name, const char *type, int id)
Free(node->name);
node->name = NULL;
}
+ if (node->path)
+ {
+ Free(node->path);
+ node->path = NULL;
+ }
int nTypeLength = strlen(type2);
int nNameLength = strlen(name2);
+ int nPathLength = strlen(path);
node->type = (char*)Alloc(nTypeLength+1);
node->name = (char*)Alloc(nNameLength+1);
+ node->path = (char*)Alloc(nPathLength+1);
strcpy(node->type, type2);
strcpy(node->name, name2);
+ strcpy(node->path, path);
node->id = id;
node->size = size;
- node->flags |= DICT_EXTERNAL;
+ node->flags |= DICT_EXTERNAL | flags;
Flush(node);
}
}
@@ -550,7 +575,10 @@ void Resource::Read(DICTNODE *n, void *p)
dassert(n != NULL);
if (n->flags & DICT_EXTERNAL)
{
- sprintf(filename, "%s.%s", n->name, n->type);
+ if (n->path)
+ Bstrncpy(filename, n->path, BMAX_PATH-1);
+ else
+ Bsnprintf(filename, MAX_PATH-1, "%s.%s", n->name, n->type);
int fhandle = kopen4loadfrommod(filename, 0);
if (fhandle == -1 || (uint32_t)kread(fhandle, p, n->size) != n->size)
{
@@ -814,6 +842,11 @@ void Resource::RemoveNode(DICTNODE* pNode)
Free(pNode->type);
pNode->type = NULL;
}
+ if (pNode->path)
+ {
+ Free(pNode->path);
+ pNode->path = NULL;
+ }
*pNode = dict[--count];
Bmemset(&dict[count], 0, sizeof(DICTNODE));
if (pNode->ptr && !pNode->lockCount)
diff --git a/source/blood/src/resource.h b/source/blood/src/resource.h
index 18359291e..d5d5dcdcc 100644
--- a/source/blood/src/resource.h
+++ b/source/blood/src/resource.h
@@ -76,6 +76,7 @@ struct DICTNODE : CACHENODE
//char name[8];
char *type;
char *name;
+ char *path;
unsigned int id;
};
@@ -92,7 +93,7 @@ public:
DICTNODE **Probe(unsigned int id, const char *type);
void Reindex(void);
void Grow(void);
- void AddExternalResource(const char *name, const char *type, int id = -1);
+ void AddExternalResource(const char *name, const char *type, int id = 0, int flags = 0, const char* pzDirectory = NULL);
static void *Alloc(int nSize);
static void Free(void *p);
DICTNODE *Lookup(const char *name, const char *type);