diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index fb8ec539..8c1385b8 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -780,10 +780,27 @@ GlobalScriptPaths=scripts\gl*.int,scripts\sfall\gl*.int ;Set to 1 to enable sfall debugging mode Enable=0 +;Set to 1 to give scripts direct access to Fallout's address space, and to make arbitrary calls into Fallout's code +;Set to 2 to also disable the memory address check in unsafe script functions +;Does not require sfall debugging mode +AllowUnsafeScripting=0 + +;If you're testing changes to the Fallout exe, you can override the CRC that sfall looks for here +;You can use several hex values, separated by commas +;Does not require sfall debugging mode +;ExtraCRC=0x00000000,0x00000000 + +;Set to 1 to skip the executable file size check +;Does not require sfall debugging mode +SkipSizeCheck=0 + +;Change to 1 to skip the compatibility mode check +SkipCompatModeCheck=0 + ;Fallout 2 Debug Patch ;Set to 1 to send debug output to the screen, 2 to a debug.log file, or 3 to both the screen and debug.log ;Does not require sfall debugging mode -;While you don't need to create an environment variable, you do still need to set the appropriate lines in fallout2.cfg: +;While you don't need to create an environment variable, you do still need to set the appropriate lines in fallout2.cfg ;------- ;[debug] ;mode=environment @@ -800,27 +817,10 @@ DebugMode=0 ;Set to 1 to hide error messages in debug output when a null value is passed to the function as an object HideObjIsNullMsg=0 -;Change to 1 to skip the compatibility mode check -SkipCompatModeCheck=0 - -;Set to 1 to skip the executable file size check -;Does not require sfall debugging mode -SkipSizeCheck=0 - -;If you're testing changes to the Fallout exe, you can override the CRC that sfall looks for here -;You can use several hex values, separated by commas -;Does not require sfall debugging mode -;ExtraCRC=0x00000000,0x00000000 - ;Set to 1 to stop Fallout from deleting non read-only protos at startup ;Has pretty nasty side effects when saving/reloading, so don't use for regular gameplay DontDeleteProtos=0 -;Set to 1 to give scripts direct access to Fallout's address space, and to make arbitrary calls into Fallout's code -;Set to 2 to also disable the memory address check in unsafe script functions -;Does not require sfall debugging mode -AllowUnsafeScripting=0 - ;Set to 1 to force sfall to inject all hooks code into the game, even if corresponding hook scripts don't exist InjectAllGameHooks=0 diff --git a/artifacts/scripting/sfall function notes.txt b/artifacts/scripting/sfall function notes.txt index 9865aa4f..3c6d9741 100644 --- a/artifacts/scripting/sfall function notes.txt +++ b/artifacts/scripting/sfall function notes.txt @@ -36,7 +36,7 @@ The mapper manual lists the functions 'world_map_x_pos' and 'world_map_y_pos', w set_pipboy_available will only accept 0 or 1 as an argument. Using any other value will cause the function to have no effect. Use 0 to disable the pipboy, and 1 to enable it. -get/set_critter_current_ap functions should only be used during the target critters turn while in combat. Calling them outside of combat typically returns the critters max ap, but don't rely on that behaviour. (Specifically, if the critter has never before entered combat, it will probably return the critters base ap ignoring any extra bonuses from perks etc.) Using set_critter_current_ap on the player will not automatically redraw the screen, so the ap bar will be incorrect until the player next clicks. +get/set_critter_current_ap functions should only be used during the target critters turn while in combat. Calling them outside of combat typically returns the critters max ap, but don't rely on that behaviour. (Specifically, if the critter has never before entered combat, it will probably return the critters base ap ignoring any extra bonuses from perks etc.) The 'type' value in the weapon knockback functions can be 0 or 1. If 0, the value becomes an absolute distance that targets will be knocked back. If 1, the value is multiplied by the distance they would normally have been knocked back. Weapon knockback modifiers are applied in the order weapon -> attacker -> target, so a x2 weapon wielded by an abs 6 attacker hitting a /2 target will knock the target back 3 squares. The knockback functions will not override the stonewall perk or knockdowns resulting from criticals. knockback values set on weapons or critters are not saved, and must be reset each time the player reloads. diff --git a/sfall/Modules/Graphics.cpp b/sfall/Modules/Graphics.cpp index ce557663..6abc9a9a 100644 --- a/sfall/Modules/Graphics.cpp +++ b/sfall/Modules/Graphics.cpp @@ -58,8 +58,8 @@ bool Graphics::PlayAviMovie = false; static BYTE* titlesBuffer = nullptr; -static DWORD yoffset, movieHeight = 0; -//static DWORD xoffset, movieWidth = 0; +static DWORD yoffset; +//static DWORD xoffset; static bool DeviceLost = false; @@ -222,7 +222,7 @@ static void ResetDevice(bool createNew) { GetDisplayMode(dispMode); params.BackBufferCount = 1; - params.BackBufferFormat = dispMode.Format; // (Graphics::mode == 4) ? D3DFMT_UNKNOWN : D3DFMT_X8R8G8B8; + params.BackBufferFormat = dispMode.Format; // (Graphics::mode != 4) ? D3DFMT_UNKNOWN : D3DFMT_X8R8G8B8; params.BackBufferWidth = gWidth; params.BackBufferHeight = gHeight; params.EnableAutoDepthStencil = false; @@ -341,9 +341,9 @@ static void Present() { RECT r, r2; r.left = windowLeft; - r.right = r.left + gWidth; + r.right = windowLeft + gWidth; r.top = windowTop; - r.bottom = r.top + gHeight; + r.bottom = windowTop + gHeight; AdjustWindowRect(&r, WS_CAPTION | WS_BORDER, false); r.right -= (r.left - windowLeft); @@ -671,24 +671,21 @@ public: /* 0x4868DA movie_MVE_ShowFrame_ */ - HRESULT _stdcall Blt(LPRECT a, LPDIRECTDRAWSURFACE b, LPRECT c, DWORD d, LPDDBLTFX e) { // used for game movies (w/o HRP) - //long addrs; - //__asm mov eax, dword ptr [ebp + 4]; - //__asm mov addrs, eax; - //dlog_f("\nBlt(0x%x)", DL_INIT, addrs); + HRESULT _stdcall Blt(LPRECT a, LPDIRECTDRAWSURFACE b, LPRECT c, DWORD d, LPDDBLTFX e) { // used for game movies (only for w/o HRP) + movieDesc.dwHeight = (a->bottom - a->top); + yoffset = (ResHeight - movieDesc.dwHeight) / 2; + movieDesc.lPitch = (a->right - a->left); + //xoffset = (ResWidth - movieDesc.lPitch) / 2; + + //dlog_f("\nMovieDesc: w:%d, h:%d", DL_INIT, movieDesc.lPitch, movieDesc.dwHeight); IsPlayMovie = true; if (Graphics::PlayAviMovie) return DD_OK; - //movieWidth = (a->right - a->left); - //xoffset = (ResWidth - movieWidth) / 2; - //movieHeight = (a->bottom - a->top); - yoffset = (ResHeight - movieDesc.dwHeight) / 2; - BYTE* lockTarget = ((FakeSurface2*)b)->lockTarget; D3DLOCKED_RECT dRect; Tex->LockRect(0, &dRect, a, 0); - DWORD width = movieDesc.lPitch; // the current size of the width of the mve movie //ResWidth; + DWORD width = movieDesc.lPitch; // the current size of the width of the mve movie int pitch = dRect.Pitch; if (Graphics::GPUBlt) { char* pBits = (char*)dRect.pBits; diff --git a/sfall/Modules/Movies.cpp b/sfall/Modules/Movies.cpp index 09905268..c60ba0d2 100644 --- a/sfall/Modules/Movies.cpp +++ b/sfall/Modules/Movies.cpp @@ -31,11 +31,6 @@ namespace sfall { -static DWORD MoviePtrs[MaxMovies]; -char MoviePaths[MaxMovies * 65]; - -static bool aviIsReadyToPlay = false; - class CAllocator : public IVMRSurfaceAllocator9, IVMRImagePresenter9 { #define SAFERELEASE(a) { if (a) { a->Release(); a = nullptr; } } @@ -45,13 +40,15 @@ private: IVMRSurfaceAllocatorNotify9 *pAllocNotify; std::vector surfaces; - IDirect3DTexture9* pTex; + //IDirect3DTexture9* pTex; IDirect3DTexture9* mTex; + bool isStartPresenting = false; + HRESULT __stdcall TerminateDevice(DWORD_PTR dwID) { dlog_f("\nTerminate Device id: %d\n", DL_INIT, dwID); - SAFERELEASE(pTex); + //SAFERELEASE(pTex); SAFERELEASE(mTex); for (auto &surface : surfaces) SAFERELEASE(surface); @@ -83,7 +80,7 @@ private: HRESULT __stdcall InitializeDevice(DWORD_PTR dwUserID, VMR9AllocationInfo *lpAllocInfo, DWORD *lpNumBuffers) { dlog("\nInitialize Device:", DL_INIT); - lpAllocInfo->dwFlags |= VMR9AllocFlag_TextureSurface; + lpAllocInfo->dwFlags |= VMR9AllocFlag_TextureSurface; // | VMR9AllocFlag_DXVATarget; lpAllocInfo->Pool = D3DPOOL_SYSTEMMEM; // Ask the VMR-9 to allocate the surfaces for us. @@ -93,15 +90,17 @@ private: HRESULT hr = pAllocNotify->AllocateSurfaceHelper(lpAllocInfo, lpNumBuffers, &surfaces[0]); if (FAILED(hr)) return hr; + #ifndef NDEBUG dlog_f(" Width: %d,", DL_INIT, lpAllocInfo->dwWidth); dlog_f(" Height: %d,", DL_INIT, lpAllocInfo->dwHeight); dlog_f(" Format: %d", DL_INIT, lpAllocInfo->Format); + #endif - hr = surfaces[0]->GetContainer(IID_IDirect3DTexture9, (void**)&pTex); - if (FAILED(hr)) { - TerminateDevice(-1); - return hr; - } + //hr = surfaces[0]->GetContainer(IID_IDirect3DTexture9, (void**)&pTex); + //if (FAILED(hr)) { + // TerminateDevice(-1); + // return hr; + //} if (d3d9Device->CreateTexture(lpAllocInfo->dwWidth, lpAllocInfo->dwHeight, 1, 0, lpAllocInfo->Format, D3DPOOL_DEFAULT, &mTex, nullptr) != D3D_OK) { dlog(" Failed to create movie texture!", DL_INIT); @@ -123,19 +122,24 @@ private: HRESULT __stdcall StartPresenting(DWORD_PTR dwUserID) { dlog("\nStart Presenting.", DL_INIT); Graphics::SetMovieTexture(mTex); + isStartPresenting = true; return S_OK; } HRESULT __stdcall StopPresenting(DWORD_PTR dwUserID) { dlog("\nStop Presenting.", DL_INIT); + isStartPresenting = false; return S_OK; } HRESULT __stdcall PresentImage(DWORD_PTR dwUserID, VMR9PresentationInfo *lpPresInfo) { - #ifndef NDEBUG - dlog("\nPresent Image.", DL_INIT); - #endif - d3d9Device->UpdateTexture(pTex, mTex); + //dlog_f("\nPresent Image %d", DL_INIT, isStartPresenting); + if (isStartPresenting) { + IDirect3DTexture9* tex; + lpPresInfo->lpSurf->GetContainer(IID_IDirect3DTexture9, (LPVOID*)&tex); + d3d9Device->UpdateTexture(tex, mTex); + Graphics::ShowMovieFrame(); + } return S_OK; } @@ -144,14 +148,14 @@ public: RefCount = 1; pAllocNotify = nullptr; mTex = nullptr; - pTex = nullptr; + //pTex = nullptr; } ULONG __stdcall AddRef() { return ++RefCount; } - ULONG _stdcall Release() { + ULONG __stdcall Release() { if (--RefCount == 0) { TerminateDevice(-2); if (pAllocNotify) { @@ -181,9 +185,8 @@ public: }; struct sDSTexture { - CAllocator *pMyAlloc; + CAllocator *pSFAlloc; IGraphBuilder *pGraph; - //ICaptureGraphBuilder2 *pBuild; IBaseFilter *pVmr; IVMRFilterConfig9 *pConfig; IVMRSurfaceAllocatorNotify9 *pAlloc; @@ -220,11 +223,10 @@ DWORD FreeMovie(sDSTexture* movie) { if (movie->pControl) movie->pControl->Release(); if (movie->pSeek) movie->pSeek->Release(); if (movie->pAlloc) movie->pAlloc->Release(); - if (movie->pMyAlloc) movie->pMyAlloc->Release(); + if (movie->pSFAlloc) movie->pSFAlloc->Release(); if (movie->pConfig) movie->pConfig->Release(); if (movie->pVmr) movie->pVmr->Release(); if (movie->pGraph) movie->pGraph->Release(); - //if (movie->pBuild) movie->pBuild->Release(); return 0; } @@ -233,18 +235,10 @@ DWORD CreateDSGraph(wchar_t* path, sDSTexture* movie) { ZeroMemory(movie, sizeof(sDSTexture)); - // Create the Capture Graph Builder. - //HRESULT hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, 0, CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2, (void**)&movie->pBuild); - //if (hr != S_OK) return FreeMovie(movie); - // Create the Filter Graph Manager. HRESULT hr = CoCreateInstance(CLSID_FilterGraph, 0, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void**)&movie->pGraph); if (hr != S_OK) return FreeMovie(movie); - // Initialize the Capture Graph Builder. - //hr = movie->pBuild->SetFiltergraph(movie->pGraph); - //if (hr != S_OK) return FreeMovie(movie); - hr = CoCreateInstance(CLSID_VideoMixingRenderer9, 0, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&movie->pVmr); if (hr != S_OK) return FreeMovie(movie); @@ -262,45 +256,47 @@ DWORD CreateDSGraph(wchar_t* path, sDSTexture* movie) { Custom Allocator-Presenter for VMR-9: https://docs.microsoft.com/en-us/windows/win32/directshow/supplying-a-custom-allocator-presenter-for-vmr-9 */ - movie->pMyAlloc = new CAllocator(); + movie->pSFAlloc = new CAllocator(); hr = movie->pVmr->QueryInterface(IID_IVMRSurfaceAllocatorNotify9, (void**)&movie->pAlloc); if (hr != S_OK) return FreeMovie(movie); - hr = movie->pAlloc->AdviseSurfaceAllocator(0, (IVMRSurfaceAllocator9*)movie->pMyAlloc); + hr = movie->pAlloc->AdviseSurfaceAllocator(0, (IVMRSurfaceAllocator9*)movie->pSFAlloc); if (hr != S_OK) return FreeMovie(movie); - hr = movie->pMyAlloc->AdviseNotify(movie->pAlloc); + hr = movie->pSFAlloc->AdviseNotify(movie->pAlloc); if (hr != S_OK) return FreeMovie(movie); /****************************************************/ hr = movie->pGraph->AddFilter(movie->pVmr, L"VMR9"); if (hr != S_OK) return FreeMovie(movie); - dlog_f("\nStart rendering file.", DL_INIT); + dlog("\nStart rendering file.", DL_INIT); hr = movie->pGraph->RenderFile(path, nullptr); if (hr != S_OK) dlog_f(" ERROR: %d", DL_INIT, hr); - #ifndef NDEBUG - if (movie->pMyAlloc->GetMovieTexture()) dlog_f("\nMovieTex: %d", DL_INIT, *(DWORD*)movie->pMyAlloc->GetMovieTexture()); - #endif - return (movie->pMyAlloc->GetMovieTexture()) ? 1 : 0; + return (hr == S_OK && movie->pSFAlloc->GetMovieTexture()) ? 1 : 0; } +static __int64 endMoviePosition; + // Movie play looping static DWORD PlayMovieLoop() { - Graphics::ShowMovieFrame(); + //dlog("\nPlay Movie Loop.", DL_INIT); + + if (*(DWORD*)FO_VAR_subtitles) __asm call fo::funcoffs::movieUpdate_; // for reading subtitles when playing mve if (GetAsyncKeyState(VK_ESCAPE)) { StopMovie(&movieInterface); return 0; // break play } - _int64 pos, end; - movieInterface.pSeek->GetCurrentPosition(&pos); - movieInterface.pSeek->GetStopPosition(&end); + Sleep(10); // idle delay - bool isPlayEnd = (end == pos); + __int64 pos; + movieInterface.pSeek->GetCurrentPosition(&pos); + + bool isPlayEnd = (endMoviePosition == pos); if (isPlayEnd) StopMovie(&movieInterface); return !isPlayEnd; // 0 - for breaking play @@ -309,7 +305,6 @@ static DWORD PlayMovieLoop() { static void __declspec(naked) gmovie_play_hook() { __asm { push ecx; - xor eax, eax; call fo::funcoffs::GNW95_process_message_; // windows message pump call PlayMovieLoop; pop ecx; @@ -317,18 +312,6 @@ static void __declspec(naked) gmovie_play_hook() { } } -static void __declspec(naked) gmovie_play_hook_wsub() { - __asm { - push ecx; - xor eax, eax; - call fo::funcoffs::GNW95_process_message_; // windows message pump - call fo::funcoffs::movieUpdate_; // for playing mve - call PlayMovieLoop; - pop ecx; - retn; - } -} - static void __declspec(naked) gmovie_play_hook_input() { __asm { xor eax,eax; @@ -340,6 +323,20 @@ static void __declspec(naked) gmovie_play_hook_input() { static DWORD backgroundVolume = 0; static DWORD __fastcall PreparePlayMovie(const DWORD id) { + static long isNotWMR = -1; + // Verify that the VMR exists on this system + if (isNotWMR == -1) { + IBaseFilter* pBF = nullptr; + if (SUCCEEDED(CoCreateInstance(CLSID_VideoMixingRenderer9, 0, CLSCTX_INPROC, IID_IBaseFilter, (LPVOID*)&pBF))) { + pBF->Release(); + isNotWMR = 0; + } else { + dlogr("Error: Video Mixing Renderer (VMR9) capabilities are required.", DL_MAIN); + isNotWMR = 1; + } + } + if (isNotWMR) return 0; + // Get file path in unicode wchar_t path[MAX_PATH]; char* master_patches = fo::var::patches; @@ -362,13 +359,13 @@ static DWORD __fastcall PreparePlayMovie(const DWORD id) { // Create texture and graph filter if (!CreateDSGraph(path, &movieInterface)) return FreeMovie(&movieInterface); - HookCall(0x44E949,gmovie_play_hook_input); // block get_input_ (if subtitles are disabled then mve videos will not be played) + HookCall(0x44E949, gmovie_play_hook_input); // block get_input_ (if subtitles are disabled then mve videos will not be played) + HookCall(0x44E937, gmovie_play_hook); // looping call moviePlaying_ + // patching gmovie_play_ for disabled game subtitles if (*(DWORD*)FO_VAR_subtitles == 0) { - HookCall(0x44E937, gmovie_play_hook); // looping call moviePlaying_ SafeWrite8(0x4CB850, 0xC3); // GNW95_ShowRect_ blocking image rendering from the 'descSurface' surface when subtitles are disabled (optional) } else { - HookCall(0x44E937, gmovie_play_hook_wsub); // looping call moviePlaying_ //SafeWrite8(0x486654, 0xC3); // blocking movie_MVE_ShowFrame_ //SafeWrite8(0x486900, 0xC3); // blocking movieShowFrame_ SafeWrite8(0x4F5F40, 0xC3); // blocking sfShowFrame_ for disabling the display of mve video frames @@ -382,14 +379,14 @@ static DWORD __fastcall PreparePlayMovie(const DWORD id) { //SafeWrite32(0x4C73B2, 0x2E); //BlockCall(0x48827E); //BlockCall(0x44E92B); - //aviIsReadyToPlay = true; + movieInterface.pSeek->GetStopPosition(&endMoviePosition); PlayMovie(&movieInterface); return 1; // play AVI } -static void _stdcall PlayMovieRestore() { +static void __stdcall PlayMovieRestore() { #ifndef NDEBUG dlog("\nPlay Movie Restore.", DL_INIT); #endif @@ -406,11 +403,10 @@ static void _stdcall PlayMovieRestore() { Graphics::SetMovieTexture(0); FreeMovie(&movieInterface); - //aviIsReadyToPlay = false; } -static const DWORD gmovie_play_addr = 0x44E695; static void __declspec(naked) gmovie_play_hack() { + static const DWORD gmovie_play_addr = 0x44E695; __asm { cmp eax, MaxMovies; jge failPlayAvi; @@ -424,7 +420,7 @@ static void __declspec(naked) gmovie_play_hack() { pop edx; pop ecx; jz failPlayAvi; - push offset return; // return here + push offset return; // return to here label failPlayAvi: push ebx; push ecx; @@ -443,10 +439,14 @@ return: /////////////////////////////////////////////////////////////////////////////// -static const DWORD Artimer1DaysCheckJmp = 0x4A3790; -static const DWORD Artimer1DaysCheckJmpLess = 0x4A37A9; +char MoviePaths[MaxMovies * 65]; + +static DWORD MoviePtrs[MaxMovies]; static DWORD Artimer1DaysCheckTimer; + static void __declspec(naked) Artimer1DaysCheckHack() { + static const DWORD Artimer1DaysCheckJmp = 0x4A3790; + static const DWORD Artimer1DaysCheckJmpLess = 0x4A37A9; __asm { cmp edx, Artimer1DaysCheckTimer; jl less; diff --git a/sfall/Modules/Scripting/Handlers/Stats.cpp b/sfall/Modules/Scripting/Handlers/Stats.cpp index ac799bc0..c4db319a 100644 --- a/sfall/Modules/Scripting/Handlers/Stats.cpp +++ b/sfall/Modules/Scripting/Handlers/Stats.cpp @@ -237,7 +237,7 @@ void sf_set_critter_current_ap(OpcodeContext& ctx) { if (ap < 0) ap = 0; obj->critter.movePoints = ap; - if (obj == fo::var::obj_dude) fo::func::intface_update_move_points(ap, *(DWORD*)FO_VAR_combat_free_move); + if (obj == fo::var::obj_dude) fo::func::intface_update_move_points(ap, fo::var::combat_free_move); } else { ctx.printOpcodeError(objNotCritter, ctx.getOpcodeName()); }