diff --git a/neo/framework/Common.cpp b/neo/framework/Common.cpp index 1cfeca43..f6979bcf 100644 --- a/neo/framework/Common.cpp +++ b/neo/framework/Common.cpp @@ -51,6 +51,8 @@ If you have questions concerning this license or the applicable additional terms #include "framework/Common.h" +#include "GameCallbacks_local.h" + #define MAX_PRINT_MSG_SIZE 4096 #define MAX_WARNING_LIST 256 @@ -162,6 +164,23 @@ public: virtual int ButtonState( int key ); virtual int KeyState( int key ); + // DG: hack to allow adding callbacks and exporting additional functions without breaking the game ABI + // see Common.h for longer explanation... + + // returns true if setting the callback was successful, else false + // When a game DLL is unloaded the callbacks are automatically removed from the Engine + // so you usually don't have to worry about that; but you can call this with cb = NULL + // and userArg = NULL to remove a callback manually (e.g. if userArg refers to an object you deleted) + virtual bool SetCallback(idCommon::CallbackType cbt, idCommon::FunctionPointer cb, void* userArg); + + // returns true if that function is available in this version of dhewm3 + // *out_fnptr will be the function (you'll have to cast it probably) + // *out_userArg will be an argument you have to pass to the function, if appropriate (else NULL) + // NOTE: this doesn't do anything yet, but allows to add ugly mod-specific hacks without breaking the Game interface + virtual bool GetAdditionalFunction(idCommon::FunctionType ft, idCommon::FunctionPointer* out_fnptr, void** out_userArg); + + // DG end + void InitGame( void ); void ShutdownGame( bool reloading ); @@ -2688,6 +2707,8 @@ void idCommonLocal::UnloadGameDLL( void ) { gameEdit = NULL; #endif + + gameCallbacks.Reset(); // DG: these callbacks are invalid now because DLL has been unloaded } /* @@ -3176,3 +3197,59 @@ void idCommonLocal::ShutdownGame( bool reloading ) { // shut down the file system fileSystem->Shutdown( reloading ); } + +// DG: below here are hacks to allow adding callbacks and exporting additional functions to the +// Game DLL without breaking the ABI. See Common.h for longer explanation... + + +// returns true if setting the callback was successful, else false +// When a game DLL is unloaded the callbacks are automatically removed from the Engine +// so you usually don't have to worry about that; but you can call this with cb = NULL +// and userArg = NULL to remove a callback manually (e.g. if userArg refers to an object you deleted) +bool idCommonLocal::SetCallback(idCommon::CallbackType cbt, idCommon::FunctionPointer cb, void* userArg) +{ + switch(cbt) + { + case idCommon::CB_ReloadImages: + gameCallbacks.reloadImagesCB = (idGameCallbacks::ReloadImagesCallback)cb; + gameCallbacks.reloadImagesUserArg = userArg; + return true; + + default: + Warning("Called idCommon::SetCallback() with unknown CallbackType %d!\n", cbt); + return false; + } +} + +// returns true if that function is available in this version of dhewm3 +// *out_fnptr will be the function (you'll have to cast it probably) +// *out_userArg will be an argument you have to pass to the function, if appropriate (else NULL) +bool idCommonLocal::GetAdditionalFunction(idCommon::FunctionType ft, idCommon::FunctionPointer* out_fnptr, void** out_userArg) +{ + if(out_userArg != NULL) + *out_userArg = NULL; + + if(out_fnptr == NULL) + { + Warning("Called idCommon::GetAdditionalFunction() with out_fnptr == NULL!\n"); + return false; + } + *out_fnptr = NULL; + + // NOTE: this doesn't do anything yet, but allows to later add ugly mod-specific hacks without breaking the Game interface + + return false; +} + + +idGameCallbacks gameCallbacks; + +idGameCallbacks::idGameCallbacks() +: reloadImagesCB(NULL), reloadImagesUserArg(NULL) +{} + +void idGameCallbacks::Reset() +{ + reloadImagesCB = NULL; + reloadImagesUserArg = NULL; +} diff --git a/neo/framework/Common.h b/neo/framework/Common.h index 98899b5e..b556e4bd 100644 --- a/neo/framework/Common.h +++ b/neo/framework/Common.h @@ -211,6 +211,67 @@ public: // Directly sample a keystate. virtual int KeyState( int key ) = 0; + + /* Some Mods (like Ruiner and DarkMod when it still was a mod) used "SourceHook" + * to override Doom3 Methods to call their own code before the original method + * was executed.. this is super ugly and probably not super portable either. + * + * So let's offer something that's slightly less ugly: A function pointer based + * interface to provide similar (but known!) hacks. + * For example, Ruiner used SourceHook to intercept idCmdSystem::BufferCommandText() + * and recreate some cooked rendering data in case reloadImages or vid_restart was executed. + * Now, instead of doing ugly hacks with SourceHook, Ruiner can just call + * common->SetCallback( idCommon::CB_ReloadImages, + * (idCommon::FunctionPointer)functionToCall, + * (void*)argForFunctionToCall ); + * + * (the Mod needs to check if SetCallback() returned true; if it didn't the used version + * of dhewm3 doesn't support the given CallBackType and the Mod must either error out + * or handle the case that the callback doesn't work) + * + * Of course this means that for every new SourceHook hack a Mod (that's ported to dhewm3) + * uses, a corresponding entry must be added to enum CallbackType and it must be handled, + * which implies that the Mod will only properly work with the latest dhewm3 git code + * or the next release.. + * I guess most mods don't need this hack though, so I think it's feasible. + * + * Note that this allows adding new types of callbacks without breaking the API and ABI + * between dhewm3 and the Game DLLs; the alternative would be something like + * idCommon::RegisterReloadImagesCallback(), and maybe other similar methods later, which + * would break the ABI and API each time and all Mods would have to be adjusted, even if + * they don't even need that functionality (because they never needed SourceHook or similar). + * + * Similar to SetCallback() I've also added GetAdditionalFunction() to get a function pointer + * from dhewm3 that Mods can call (and that's not exported via the normal interface classes). + * Right now GetAdditionalFunction() will always just return false and do nothing, but if + * some Mod needs some specific function in the future, it could be implemented with + * GetAdditionalFunction() - again without breaking the game API and ABI for all the other + * Mods that don't need that function. + */ + + typedef void* (*FunctionPointer)(void*); // needs to be cast to/from real type! + enum CallbackType { + // called on reloadImages and vid_restart commands (before anything "real" happens) + // expecting callback to be like void cb(void* userarg, const idCmdArgs& cmdArgs) + // where cmdArgs contains the command+arguments that was called + CB_ReloadImages = 1, + }; + + // returns true if setting the callback was successful, else false + // When a game DLL is unloaded the callbacks are automatically removed from the Engine + // so you usually don't have to worry about that; but you can call this with cb = NULL + // and userArg = NULL to remove a callback manually (e.g. if userArg refers to an object you deleted) + virtual bool SetCallback(CallbackType cbt, FunctionPointer cb, void* userArg) = 0; + + enum FunctionType { + // None yet.. + }; + + // returns true if that function is available in this version of dhewm3 + // *out_fnptr will be the function (you'll have to cast it probably) + // *out_userArg will be an argument you have to pass to the function, if appropriate (else NULL) + // NOTE: this doesn't do anything yet, but allows to add ugly mod-specific hacks without breaking the Game interface + virtual bool GetAdditionalFunction(FunctionType ft, FunctionPointer* out_fnptr, void** out_userArg) = 0; }; extern idCommon * common; diff --git a/neo/framework/Game.h b/neo/framework/Game.h index c62ef817..8a4c4e1c 100644 --- a/neo/framework/Game.h +++ b/neo/framework/Game.h @@ -324,7 +324,7 @@ extern idGameEdit * gameEdit; =============================================================================== */ -const int GAME_API_VERSION = 8; +const int GAME_API_VERSION = 9; typedef struct { diff --git a/neo/framework/GameCallbacks_local.h b/neo/framework/GameCallbacks_local.h new file mode 100644 index 00000000..51d9763a --- /dev/null +++ b/neo/framework/GameCallbacks_local.h @@ -0,0 +1,57 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. +Copyright (C) 2018 Daniel Gibson + +This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code 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 Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// Implementation details for idCommon::SetCallback() and idCommon::GetAdditionalFunction() +// Needed in different parts of the Engine (that are supposed to call the callbacks) +// but *not* part of the Game API exported to Game DLLs. +// Common.h (above idCommon::SetCallback()) has a lengthy explanation of what all this is good for.. + +#ifndef NEO_FRAMEWORK_GAMECALLBACKS_LOCAL_H_ +#define NEO_FRAMEWORK_GAMECALLBACKS_LOCAL_H_ + +#include "Common.h" + +struct idGameCallbacks { + + typedef void (*ReloadImagesCallback)(void* userArg, const idCmdArgs &args); + ReloadImagesCallback reloadImagesCB; + void* reloadImagesUserArg; + + + idGameCallbacks(); + + // called when Game DLL is unloaded (=> the registered callbacks become invalid) + void Reset(); + +}; + +extern idGameCallbacks gameCallbacks; + + +#endif /* NEO_FRAMEWORK_GAMECALLBACKS_LOCAL_H_ */ diff --git a/neo/renderer/Image_init.cpp b/neo/renderer/Image_init.cpp index 20deaf68..baed7c66 100644 --- a/neo/renderer/Image_init.cpp +++ b/neo/renderer/Image_init.cpp @@ -33,6 +33,8 @@ If you have questions concerning this license or the applicable additional terms #include "renderer/Image.h" +#include "framework/GameCallbacks_local.h" + const char *imageFilter[] = { "GL_LINEAR_MIPMAP_NEAREST", "GL_LINEAR_MIPMAP_LINEAR", @@ -1071,6 +1073,12 @@ void R_ReloadImages_f( const idCmdArgs &args ) { bool all; bool checkPrecompressed; + // DG: notify the game DLL about the reloadImages command + if(gameCallbacks.reloadImagesCB != NULL) + { + gameCallbacks.reloadImagesCB(gameCallbacks.reloadImagesUserArg, args); + } + // this probably isn't necessary... globalImages->ChangeTextureFilter(); diff --git a/neo/renderer/RenderSystem_init.cpp b/neo/renderer/RenderSystem_init.cpp index 669e1358..02b040e9 100644 --- a/neo/renderer/RenderSystem_init.cpp +++ b/neo/renderer/RenderSystem_init.cpp @@ -40,6 +40,8 @@ If you have questions concerning this license or the applicable additional terms #include "renderer/tr_local.h" +#include "framework/GameCallbacks_local.h" + // Vista OpenGL wrapper check #ifdef _WIN32 #include "sys/win32/win_local.h" @@ -1842,6 +1844,12 @@ void R_VidRestart_f( const idCmdArgs &args ) { return; } + // DG: notify the game DLL about the reloadImages and vid_restart commands + if(gameCallbacks.reloadImagesCB != NULL) + { + gameCallbacks.reloadImagesCB(gameCallbacks.reloadImagesUserArg, args); + } + bool full = true; bool forceWindow = false; for ( int i = 1 ; i < args.Argc() ; i++ ) {