mirror of
https://github.com/neoStudiosLCE/neoLegacy.git
synced 2026-08-19 10:37:12 +00:00
Add VSync and fullscreen settings, fix swap chain resize and revert lighting changes
- Add VSync and Exclusive Fullscreen toggles to the graphics settings menu - Rewrite D3D11 swap chain to use DXGI flip model with tearing support - Fix black screen on resize by creating new swap chain instead of ResizeBuffers - Revert conditional lighting optimization in Level::setTileAndData back to unconditional checkLight - Revert deferred lightGaps flagging in LevelChunk::recalcHeight back to immediate lightGap calls - Add SWF/ARC editing tools used to add new UI checkboxes
This commit is contained in:
parent
0a343d2c8d
commit
4fffcac6e7
|
|
@ -52,6 +52,7 @@ set_target_properties(Minecraft.Client PROPERTIES
|
||||||
target_link_libraries(Minecraft.Client PRIVATE
|
target_link_libraries(Minecraft.Client PRIVATE
|
||||||
Minecraft.World
|
Minecraft.World
|
||||||
d3d11
|
d3d11
|
||||||
|
dxgi
|
||||||
d3dcompiler
|
d3dcompiler
|
||||||
XInput9_1_0
|
XInput9_1_0
|
||||||
wsock32
|
wsock32
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,8 @@ enum EGameHostOptionWorldSize
|
||||||
#define GAMESETTING_ANIMATEDCHARACTER 0x00008000
|
#define GAMESETTING_ANIMATEDCHARACTER 0x00008000
|
||||||
#define GAMESETTING_PS3EULAREAD 0x00010000
|
#define GAMESETTING_PS3EULAREAD 0x00010000
|
||||||
#define GAMESETTING_PSVITANETWORKMODEADHOC 0x00020000
|
#define GAMESETTING_PSVITANETWORKMODEADHOC 0x00020000
|
||||||
|
#define GAMESETTING_VSYNC 0x00040000
|
||||||
|
#define GAMESETTING_EXCLUSIVEFULLSCREEN 0x00080000
|
||||||
|
|
||||||
|
|
||||||
// defines for languages
|
// defines for languages
|
||||||
|
|
|
||||||
|
|
@ -178,6 +178,9 @@ enum eGameSetting
|
||||||
// PSVita
|
// PSVita
|
||||||
eGameSetting_PSVita_NetworkModeAdhoc,
|
eGameSetting_PSVita_NetworkModeAdhoc,
|
||||||
|
|
||||||
|
// PC
|
||||||
|
eGameSetting_VSync,
|
||||||
|
eGameSetting_ExclusiveFullscreen,
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1382,6 +1382,7 @@ void CMinecraftApp::ApplyGameSettingsChanged(int iPad)
|
||||||
ActionGameSettings(iPad,eGameSetting_AnimatedCharacter);
|
ActionGameSettings(iPad,eGameSetting_AnimatedCharacter);
|
||||||
|
|
||||||
ActionGameSettings(iPad,eGameSetting_PS3_EULA_Read);
|
ActionGameSettings(iPad,eGameSetting_PS3_EULA_Read);
|
||||||
|
ActionGameSettings(iPad,eGameSetting_VSync);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1617,6 +1618,22 @@ void CMinecraftApp::ActionGameSettings(int iPad,eGameSetting eVal)
|
||||||
case eGameSetting_PSVita_NetworkModeAdhoc:
|
case eGameSetting_PSVita_NetworkModeAdhoc:
|
||||||
//nothing to do here
|
//nothing to do here
|
||||||
break;
|
break;
|
||||||
|
case eGameSetting_VSync:
|
||||||
|
#ifdef _WINDOWS64
|
||||||
|
{
|
||||||
|
extern bool g_bVSync;
|
||||||
|
g_bVSync = (GetGameSettings(iPad, eGameSetting_VSync) != 0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
case eGameSetting_ExclusiveFullscreen:
|
||||||
|
#ifdef _WINDOWS64
|
||||||
|
{
|
||||||
|
extern void SetExclusiveFullscreen(bool enabled);
|
||||||
|
SetExclusiveFullscreen(GetGameSettings(iPad, eGameSetting_ExclusiveFullscreen) != 0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2328,6 +2345,38 @@ void CMinecraftApp::SetGameSettings(int iPad,eGameSetting eVal,unsigned char ucV
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case eGameSetting_VSync:
|
||||||
|
if(((GameSettingsA[iPad]->uiBitmaskValues&GAMESETTING_VSYNC)>>18)!=(ucVal&0x01))
|
||||||
|
{
|
||||||
|
if(ucVal==1)
|
||||||
|
{
|
||||||
|
GameSettingsA[iPad]->uiBitmaskValues|=GAMESETTING_VSYNC;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GameSettingsA[iPad]->uiBitmaskValues&=~GAMESETTING_VSYNC;
|
||||||
|
}
|
||||||
|
ActionGameSettings(iPad,eVal);
|
||||||
|
GameSettingsA[iPad]->bSettingsChanged=true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case eGameSetting_ExclusiveFullscreen:
|
||||||
|
if(((GameSettingsA[iPad]->uiBitmaskValues&GAMESETTING_EXCLUSIVEFULLSCREEN)>>19)!=(ucVal&0x01))
|
||||||
|
{
|
||||||
|
if(ucVal==1)
|
||||||
|
{
|
||||||
|
GameSettingsA[iPad]->uiBitmaskValues|=GAMESETTING_EXCLUSIVEFULLSCREEN;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GameSettingsA[iPad]->uiBitmaskValues&=~GAMESETTING_EXCLUSIVEFULLSCREEN;
|
||||||
|
}
|
||||||
|
ActionGameSettings(iPad,eVal);
|
||||||
|
GameSettingsA[iPad]->bSettingsChanged=true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2463,6 +2512,12 @@ unsigned char CMinecraftApp::GetGameSettings(int iPad,eGameSetting eVal)
|
||||||
case eGameSetting_PSVita_NetworkModeAdhoc:
|
case eGameSetting_PSVita_NetworkModeAdhoc:
|
||||||
return (GameSettingsA[iPad]->uiBitmaskValues&GAMESETTING_PSVITANETWORKMODEADHOC)>>17;
|
return (GameSettingsA[iPad]->uiBitmaskValues&GAMESETTING_PSVITANETWORKMODEADHOC)>>17;
|
||||||
|
|
||||||
|
case eGameSetting_VSync:
|
||||||
|
return (GameSettingsA[iPad]->uiBitmaskValues&GAMESETTING_VSYNC)>>18;
|
||||||
|
|
||||||
|
case eGameSetting_ExclusiveFullscreen:
|
||||||
|
return (GameSettingsA[iPad]->uiBitmaskValues&GAMESETTING_EXCLUSIVEFULLSCREEN)>>19;
|
||||||
|
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -5,6 +5,11 @@
|
||||||
#include "..\..\Options.h"
|
#include "..\..\Options.h"
|
||||||
#include "..\..\GameRenderer.h"
|
#include "..\..\GameRenderer.h"
|
||||||
|
|
||||||
|
#ifdef _WINDOWS64
|
||||||
|
extern bool g_bVSync;
|
||||||
|
extern void SetExclusiveFullscreen(bool enabled);
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
constexpr int FOV_MIN = 70;
|
constexpr int FOV_MIN = 70;
|
||||||
|
|
@ -62,8 +67,10 @@ UIScene_SettingsGraphicsMenu::UIScene_SettingsGraphicsMenu(int iPad, void *initD
|
||||||
m_checkboxClouds.init(app.GetString(IDS_CHECKBOX_RENDER_CLOUDS),eControl_Clouds,(app.GetGameSettings(m_iPad,eGameSetting_Clouds)!=0));
|
m_checkboxClouds.init(app.GetString(IDS_CHECKBOX_RENDER_CLOUDS),eControl_Clouds,(app.GetGameSettings(m_iPad,eGameSetting_Clouds)!=0));
|
||||||
m_checkboxBedrockFog.init(app.GetString(IDS_CHECKBOX_RENDER_BEDROCKFOG),eControl_BedrockFog,(app.GetGameSettings(m_iPad,eGameSetting_BedrockFog)!=0));
|
m_checkboxBedrockFog.init(app.GetString(IDS_CHECKBOX_RENDER_BEDROCKFOG),eControl_BedrockFog,(app.GetGameSettings(m_iPad,eGameSetting_BedrockFog)!=0));
|
||||||
m_checkboxCustomSkinAnim.init(app.GetString(IDS_CHECKBOX_CUSTOM_SKIN_ANIM),eControl_CustomSkinAnim,(app.GetGameSettings(m_iPad,eGameSetting_CustomSkinAnim)!=0));
|
m_checkboxCustomSkinAnim.init(app.GetString(IDS_CHECKBOX_CUSTOM_SKIN_ANIM),eControl_CustomSkinAnim,(app.GetGameSettings(m_iPad,eGameSetting_CustomSkinAnim)!=0));
|
||||||
|
m_checkboxVSync.init(L"VSync",eControl_VSync,(app.GetGameSettings(m_iPad,eGameSetting_VSync)!=0));
|
||||||
|
m_checkboxExclusiveFullscreen.init(L"Fullscreen",eControl_ExclusiveFullscreen,(app.GetGameSettings(m_iPad,eGameSetting_ExclusiveFullscreen)!=0));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
WCHAR TempString[256];
|
WCHAR TempString[256];
|
||||||
|
|
||||||
swprintf(TempString, 256, L"Render Distance: %d",app.GetGameSettings(m_iPad,eGameSetting_RenderDistance));
|
swprintf(TempString, 256, L"Render Distance: %d",app.GetGameSettings(m_iPad,eGameSetting_RenderDistance));
|
||||||
|
|
@ -82,9 +89,15 @@ UIScene_SettingsGraphicsMenu::UIScene_SettingsGraphicsMenu(int iPad, void *initD
|
||||||
|
|
||||||
doHorizontalResizeCheck();
|
doHorizontalResizeCheck();
|
||||||
|
|
||||||
|
#ifndef _WINDOWS64
|
||||||
|
// VSync and Exclusive Fullscreen are only available on PC
|
||||||
|
removeControl(&m_checkboxVSync, true);
|
||||||
|
removeControl(&m_checkboxExclusiveFullscreen, true);
|
||||||
|
#endif
|
||||||
|
|
||||||
const bool bInGame=(Minecraft::GetInstance()->level!=nullptr);
|
const bool bInGame=(Minecraft::GetInstance()->level!=nullptr);
|
||||||
const bool bIsPrimaryPad=(ProfileManager.GetPrimaryPad()==m_iPad);
|
const bool bIsPrimaryPad=(ProfileManager.GetPrimaryPad()==m_iPad);
|
||||||
// if we're not in the game, we need to use basescene 0
|
// if we're not in the game, we need to use basescene 0
|
||||||
if(bInGame)
|
if(bInGame)
|
||||||
{
|
{
|
||||||
// If the game has started, then you need to be the host to change the in-game gamertags
|
// If the game has started, then you need to be the host to change the in-game gamertags
|
||||||
|
|
@ -165,6 +178,12 @@ void UIScene_SettingsGraphicsMenu::handleInput(int iPad, int key, bool repeat, b
|
||||||
app.SetGameSettings(m_iPad,eGameSetting_Clouds,m_checkboxClouds.IsChecked()?1:0);
|
app.SetGameSettings(m_iPad,eGameSetting_Clouds,m_checkboxClouds.IsChecked()?1:0);
|
||||||
app.SetGameSettings(m_iPad,eGameSetting_BedrockFog,m_checkboxBedrockFog.IsChecked()?1:0);
|
app.SetGameSettings(m_iPad,eGameSetting_BedrockFog,m_checkboxBedrockFog.IsChecked()?1:0);
|
||||||
app.SetGameSettings(m_iPad,eGameSetting_CustomSkinAnim,m_checkboxCustomSkinAnim.IsChecked()?1:0);
|
app.SetGameSettings(m_iPad,eGameSetting_CustomSkinAnim,m_checkboxCustomSkinAnim.IsChecked()?1:0);
|
||||||
|
app.SetGameSettings(m_iPad,eGameSetting_VSync,m_checkboxVSync.IsChecked()?1:0);
|
||||||
|
app.SetGameSettings(m_iPad,eGameSetting_ExclusiveFullscreen,m_checkboxExclusiveFullscreen.IsChecked()?1:0);
|
||||||
|
#ifdef _WINDOWS64
|
||||||
|
g_bVSync = m_checkboxVSync.IsChecked();
|
||||||
|
SetExclusiveFullscreen(m_checkboxExclusiveFullscreen.IsChecked());
|
||||||
|
#endif
|
||||||
|
|
||||||
navigateBack();
|
navigateBack();
|
||||||
handled = true;
|
handled = true;
|
||||||
|
|
|
||||||
|
|
@ -12,18 +12,22 @@ private:
|
||||||
eControl_Clouds,
|
eControl_Clouds,
|
||||||
eControl_BedrockFog,
|
eControl_BedrockFog,
|
||||||
eControl_CustomSkinAnim,
|
eControl_CustomSkinAnim,
|
||||||
|
eControl_VSync,
|
||||||
|
eControl_ExclusiveFullscreen,
|
||||||
eControl_RenderDistance,
|
eControl_RenderDistance,
|
||||||
eControl_Gamma,
|
eControl_Gamma,
|
||||||
eControl_FOV,
|
eControl_FOV,
|
||||||
eControl_InterfaceOpacity
|
eControl_InterfaceOpacity
|
||||||
};
|
};
|
||||||
|
|
||||||
UIControl_CheckBox m_checkboxClouds, m_checkboxBedrockFog, m_checkboxCustomSkinAnim; // Checkboxes
|
UIControl_CheckBox m_checkboxClouds, m_checkboxBedrockFog, m_checkboxCustomSkinAnim, m_checkboxVSync, m_checkboxExclusiveFullscreen; // Checkboxes
|
||||||
UIControl_Slider m_sliderRenderDistance, m_sliderGamma, m_sliderFOV, m_sliderInterfaceOpacity; // Sliders
|
UIControl_Slider m_sliderRenderDistance, m_sliderGamma, m_sliderFOV, m_sliderInterfaceOpacity; // Sliders
|
||||||
UI_BEGIN_MAP_ELEMENTS_AND_NAMES(UIScene)
|
UI_BEGIN_MAP_ELEMENTS_AND_NAMES(UIScene)
|
||||||
UI_MAP_ELEMENT( m_checkboxClouds, "Clouds")
|
UI_MAP_ELEMENT( m_checkboxClouds, "Clouds")
|
||||||
UI_MAP_ELEMENT( m_checkboxBedrockFog, "BedrockFog")
|
UI_MAP_ELEMENT( m_checkboxBedrockFog, "BedrockFog")
|
||||||
UI_MAP_ELEMENT( m_checkboxCustomSkinAnim, "CustomSkinAnim")
|
UI_MAP_ELEMENT( m_checkboxCustomSkinAnim, "CustomSkinAnim")
|
||||||
|
UI_MAP_ELEMENT( m_checkboxVSync, "VSync")
|
||||||
|
UI_MAP_ELEMENT( m_checkboxExclusiveFullscreen, "ExclusiveFullscreen")
|
||||||
UI_MAP_ELEMENT( m_sliderRenderDistance, "RenderDistance")
|
UI_MAP_ELEMENT( m_sliderRenderDistance, "RenderDistance")
|
||||||
UI_MAP_ELEMENT( m_sliderGamma, "Gamma")
|
UI_MAP_ELEMENT( m_sliderGamma, "Gamma")
|
||||||
UI_MAP_ELEMENT(m_sliderFOV, "FOV")
|
UI_MAP_ELEMENT(m_sliderFOV, "FOV")
|
||||||
|
|
|
||||||
|
|
@ -467,6 +467,57 @@ D3D_FEATURE_LEVEL g_featureLevel = D3D_FEATURE_LEVEL_11_0;
|
||||||
ID3D11Device* g_pd3dDevice = nullptr;
|
ID3D11Device* g_pd3dDevice = nullptr;
|
||||||
ID3D11DeviceContext* g_pImmediateContext = nullptr;
|
ID3D11DeviceContext* g_pImmediateContext = nullptr;
|
||||||
IDXGISwapChain* g_pSwapChain = nullptr;
|
IDXGISwapChain* g_pSwapChain = nullptr;
|
||||||
|
bool g_bVSync = false;
|
||||||
|
static bool g_bTearingSupported = false;
|
||||||
|
static bool g_bPendingExclusiveFullscreen = false;
|
||||||
|
static bool g_bPendingExclusiveFullscreenValue = false;
|
||||||
|
|
||||||
|
// COM proxy for IDXGISwapChain — delegates all calls to the real swap chain,
|
||||||
|
// but overrides Present() to set SyncInterval=1 when VSync is enabled.
|
||||||
|
// Avoids vtable patching, which conflicts with the D3D11 debug layer.
|
||||||
|
static class SwapChainVSyncProxy : public IDXGISwapChain
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void SetTarget(IDXGISwapChain* pReal) { m_pReal = pReal; }
|
||||||
|
|
||||||
|
// IUnknown
|
||||||
|
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override { return m_pReal->QueryInterface(riid, ppvObject); }
|
||||||
|
ULONG STDMETHODCALLTYPE AddRef() override { return m_pReal->AddRef(); }
|
||||||
|
ULONG STDMETHODCALLTYPE Release() override { return m_pReal->Release(); }
|
||||||
|
|
||||||
|
// IDXGIObject
|
||||||
|
HRESULT STDMETHODCALLTYPE SetPrivateData(REFGUID Name, UINT DataSize, const void* pData) override { return m_pReal->SetPrivateData(Name, DataSize, pData); }
|
||||||
|
HRESULT STDMETHODCALLTYPE SetPrivateDataInterface(REFGUID Name, const IUnknown* pUnknown) override { return m_pReal->SetPrivateDataInterface(Name, pUnknown); }
|
||||||
|
HRESULT STDMETHODCALLTYPE GetPrivateData(REFGUID Name, UINT* pDataSize, void* pData) override { return m_pReal->GetPrivateData(Name, pDataSize, pData); }
|
||||||
|
HRESULT STDMETHODCALLTYPE GetParent(REFIID riid, void** ppParent) override { return m_pReal->GetParent(riid, ppParent); }
|
||||||
|
|
||||||
|
// IDXGIDeviceSubObject
|
||||||
|
HRESULT STDMETHODCALLTYPE GetDevice(REFIID riid, void** ppDevice) override { return m_pReal->GetDevice(riid, ppDevice); }
|
||||||
|
|
||||||
|
// IDXGISwapChain
|
||||||
|
HRESULT STDMETHODCALLTYPE Present(UINT SyncInterval, UINT Flags) override
|
||||||
|
{
|
||||||
|
if (g_bVSync)
|
||||||
|
return m_pReal->Present(1, Flags);
|
||||||
|
// DXGI_PRESENT_ALLOW_TEARING is only valid in windowed mode
|
||||||
|
if (g_bTearingSupported)
|
||||||
|
Flags |= DXGI_PRESENT_ALLOW_TEARING;
|
||||||
|
return m_pReal->Present(0, Flags);
|
||||||
|
}
|
||||||
|
HRESULT STDMETHODCALLTYPE GetBuffer(UINT Buffer, REFIID riid, void** ppSurface) override { return m_pReal->GetBuffer(Buffer, riid, ppSurface); }
|
||||||
|
HRESULT STDMETHODCALLTYPE SetFullscreenState(BOOL Fullscreen, IDXGIOutput* pTarget) override { return m_pReal->SetFullscreenState(Fullscreen, pTarget); }
|
||||||
|
HRESULT STDMETHODCALLTYPE GetFullscreenState(BOOL* pFullscreen, IDXGIOutput** ppTarget) override { return m_pReal->GetFullscreenState(pFullscreen, ppTarget); }
|
||||||
|
HRESULT STDMETHODCALLTYPE GetDesc(DXGI_SWAP_CHAIN_DESC* pDesc) override { return m_pReal->GetDesc(pDesc); }
|
||||||
|
HRESULT STDMETHODCALLTYPE ResizeBuffers(UINT BufferCount, UINT Width, UINT Height, DXGI_FORMAT NewFormat, UINT SwapChainFlags) override { return m_pReal->ResizeBuffers(BufferCount, Width, Height, NewFormat, SwapChainFlags); }
|
||||||
|
HRESULT STDMETHODCALLTYPE ResizeTarget(const DXGI_MODE_DESC* pNewTargetParameters) override { return m_pReal->ResizeTarget(pNewTargetParameters); }
|
||||||
|
HRESULT STDMETHODCALLTYPE GetContainingOutput(IDXGIOutput** ppOutput) override { return m_pReal->GetContainingOutput(ppOutput); }
|
||||||
|
HRESULT STDMETHODCALLTYPE GetFrameStatistics(DXGI_FRAME_STATISTICS* pStats) override { return m_pReal->GetFrameStatistics(pStats); }
|
||||||
|
HRESULT STDMETHODCALLTYPE GetLastPresentCount(UINT* pLastPresentCount) override { return m_pReal->GetLastPresentCount(pLastPresentCount); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
IDXGISwapChain* m_pReal = nullptr;
|
||||||
|
} g_swapChainProxy;
|
||||||
|
|
||||||
ID3D11RenderTargetView* g_pRenderTargetView = nullptr;
|
ID3D11RenderTargetView* g_pRenderTargetView = nullptr;
|
||||||
ID3D11DepthStencilView* g_pDepthStencilView = nullptr;
|
ID3D11DepthStencilView* g_pDepthStencilView = nullptr;
|
||||||
ID3D11Texture2D* g_pDepthStencilBuffer = nullptr;
|
ID3D11Texture2D* g_pDepthStencilBuffer = nullptr;
|
||||||
|
|
@ -817,19 +868,33 @@ HRESULT InitDevice()
|
||||||
};
|
};
|
||||||
UINT numFeatureLevels = ARRAYSIZE( featureLevels );
|
UINT numFeatureLevels = ARRAYSIZE( featureLevels );
|
||||||
|
|
||||||
|
// Check tearing support before device/swap chain creation
|
||||||
|
{
|
||||||
|
IDXGIFactory5* factory5 = nullptr;
|
||||||
|
if (SUCCEEDED(CreateDXGIFactory1(__uuidof(IDXGIFactory5), (void**)&factory5)))
|
||||||
|
{
|
||||||
|
BOOL allowTearing = FALSE;
|
||||||
|
if (SUCCEEDED(factory5->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allowTearing, sizeof(allowTearing))))
|
||||||
|
g_bTearingSupported = (allowTearing == TRUE);
|
||||||
|
factory5->Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DXGI_SWAP_CHAIN_DESC sd;
|
DXGI_SWAP_CHAIN_DESC sd;
|
||||||
ZeroMemory( &sd, sizeof( sd ) );
|
ZeroMemory( &sd, sizeof( sd ) );
|
||||||
sd.BufferCount = 1;
|
sd.BufferCount = 2;
|
||||||
sd.BufferDesc.Width = width;
|
sd.BufferDesc.Width = width;
|
||||||
sd.BufferDesc.Height = height;
|
sd.BufferDesc.Height = height;
|
||||||
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||||
sd.BufferDesc.RefreshRate.Numerator = 60;
|
sd.BufferDesc.RefreshRate.Numerator = 0;
|
||||||
sd.BufferDesc.RefreshRate.Denominator = 1;
|
sd.BufferDesc.RefreshRate.Denominator = 0;
|
||||||
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT;
|
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||||
sd.OutputWindow = g_hWnd;
|
sd.OutputWindow = g_hWnd;
|
||||||
sd.SampleDesc.Count = 1;
|
sd.SampleDesc.Count = 1;
|
||||||
sd.SampleDesc.Quality = 0;
|
sd.SampleDesc.Quality = 0;
|
||||||
sd.Windowed = TRUE;
|
sd.Windowed = TRUE;
|
||||||
|
sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
|
||||||
|
sd.Flags = g_bTearingSupported ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0;
|
||||||
|
|
||||||
for( UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++ )
|
for( UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++ )
|
||||||
{
|
{
|
||||||
|
|
@ -890,7 +955,8 @@ HRESULT InitDevice()
|
||||||
vp.TopLeftY = 0;
|
vp.TopLeftY = 0;
|
||||||
g_pImmediateContext->RSSetViewports( 1, &vp );
|
g_pImmediateContext->RSSetViewports( 1, &vp );
|
||||||
|
|
||||||
RenderManager.Initialise(g_pd3dDevice, g_pSwapChain);
|
g_swapChainProxy.SetTarget(g_pSwapChain);
|
||||||
|
RenderManager.Initialise(g_pd3dDevice, &g_swapChainProxy);
|
||||||
|
|
||||||
PostProcesser::GetInstance().Init();
|
PostProcesser::GetInstance().Init();
|
||||||
|
|
||||||
|
|
@ -906,7 +972,7 @@ void Render()
|
||||||
const float ClearColor[4] = { 0.0f, 0.125f, 0.3f, 1.0f }; //red,green,blue,alpha
|
const float ClearColor[4] = { 0.0f, 0.125f, 0.3f, 1.0f }; //red,green,blue,alpha
|
||||||
|
|
||||||
g_pImmediateContext->ClearRenderTargetView( g_pRenderTargetView, ClearColor );
|
g_pImmediateContext->ClearRenderTargetView( g_pRenderTargetView, ClearColor );
|
||||||
g_pSwapChain->Present( 0, 0 );
|
g_pSwapChain->Present(0, g_bTearingSupported ? DXGI_PRESENT_ALLOW_TEARING : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------
|
||||||
|
|
@ -981,59 +1047,61 @@ static bool ResizeD3D(int newW, int newH)
|
||||||
|
|
||||||
gdraw_D3D11_PreReset();
|
gdraw_D3D11_PreReset();
|
||||||
|
|
||||||
// Get IDXGIFactory from the existing device BEFORE destroying the old swap chain.
|
|
||||||
// If anything fails before we have a new swap chain, we abort without destroying
|
|
||||||
// the old one — leaving the Renderer in a valid (old-size) state.
|
|
||||||
IDXGISwapChain* pOldSwapChain = g_pSwapChain;
|
IDXGISwapChain* pOldSwapChain = g_pSwapChain;
|
||||||
bool success = false;
|
bool success = false;
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
|
|
||||||
IDXGIDevice* dxgiDevice = NULL;
|
// Create a brand-new swap chain instead of ResizeBuffers.
|
||||||
IDXGIAdapter* dxgiAdapter = NULL;
|
// ResizeBuffers requires ALL backbuffer refs released, but the closed-source
|
||||||
IDXGIFactory* dxgiFactory = NULL;
|
// Renderer holds hidden refs we can't track — causing DXGI_ERROR_INVALID_CALL
|
||||||
hr = g_pd3dDevice->QueryInterface(__uuidof(IDXGIDevice), (void**)&dxgiDevice);
|
// and leaving the Renderer with NULL views (black screen).
|
||||||
if (FAILED(hr)) goto postReset;
|
// Creating a new swap chain orphans the old backbuffer (tiny leak) but avoids
|
||||||
hr = dxgiDevice->GetParent(__uuidof(IDXGIAdapter), (void**)&dxgiAdapter);
|
// the need to release every hidden reference.
|
||||||
if (FAILED(hr)) { dxgiDevice->Release(); goto postReset; }
|
|
||||||
hr = dxgiAdapter->GetParent(__uuidof(IDXGIFactory), (void**)&dxgiFactory);
|
|
||||||
dxgiAdapter->Release();
|
|
||||||
dxgiDevice->Release();
|
|
||||||
if (FAILED(hr)) goto postReset;
|
|
||||||
|
|
||||||
// Create new swap chain at backbuffer size
|
|
||||||
{
|
{
|
||||||
|
IDXGIDevice* dxgiDevice = NULL;
|
||||||
|
IDXGIAdapter* dxgiAdapter = NULL;
|
||||||
|
IDXGIFactory* dxgiFactory = NULL;
|
||||||
|
hr = g_pd3dDevice->QueryInterface(__uuidof(IDXGIDevice), (void**)&dxgiDevice);
|
||||||
|
if (FAILED(hr)) goto postReset;
|
||||||
|
hr = dxgiDevice->GetParent(__uuidof(IDXGIAdapter), (void**)&dxgiAdapter);
|
||||||
|
dxgiDevice->Release();
|
||||||
|
if (FAILED(hr)) goto postReset;
|
||||||
|
hr = dxgiAdapter->GetParent(__uuidof(IDXGIFactory), (void**)&dxgiFactory);
|
||||||
|
dxgiAdapter->Release();
|
||||||
|
if (FAILED(hr)) goto postReset;
|
||||||
|
|
||||||
DXGI_SWAP_CHAIN_DESC sd = {};
|
DXGI_SWAP_CHAIN_DESC sd = {};
|
||||||
sd.BufferCount = 1;
|
sd.BufferCount = 2;
|
||||||
sd.BufferDesc.Width = bbW;
|
sd.BufferDesc.Width = bbW;
|
||||||
sd.BufferDesc.Height = bbH;
|
sd.BufferDesc.Height = bbH;
|
||||||
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||||
sd.BufferDesc.RefreshRate.Numerator = 60;
|
sd.BufferDesc.RefreshRate.Numerator = 0;
|
||||||
sd.BufferDesc.RefreshRate.Denominator = 1;
|
sd.BufferDesc.RefreshRate.Denominator = 0;
|
||||||
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT;
|
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||||
sd.OutputWindow = g_hWnd;
|
sd.OutputWindow = g_hWnd;
|
||||||
sd.SampleDesc.Count = 1;
|
sd.SampleDesc.Count = 1;
|
||||||
sd.SampleDesc.Quality = 0;
|
sd.SampleDesc.Quality = 0;
|
||||||
sd.Windowed = TRUE;
|
sd.Windowed = TRUE;
|
||||||
|
sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
|
||||||
|
sd.Flags = g_bTearingSupported ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0;
|
||||||
|
|
||||||
IDXGISwapChain* pNewSwapChain = NULL;
|
IDXGISwapChain* pNewSwapChain = NULL;
|
||||||
hr = dxgiFactory->CreateSwapChain(g_pd3dDevice, &sd, &pNewSwapChain);
|
hr = dxgiFactory->CreateSwapChain(g_pd3dDevice, &sd, &pNewSwapChain);
|
||||||
dxgiFactory->Release();
|
dxgiFactory->Release();
|
||||||
if (FAILED(hr) || pNewSwapChain == NULL)
|
if (FAILED(hr))
|
||||||
{
|
{
|
||||||
app.DebugPrintf("[RESIZE] CreateSwapChain FAILED hr=0x%08X — keeping old swap chain\n", (unsigned)hr);
|
app.DebugPrintf("[RESIZE] CreateSwapChain FAILED hr=0x%08X\n", (unsigned)hr);
|
||||||
goto postReset;
|
goto postReset;
|
||||||
}
|
}
|
||||||
|
|
||||||
// New swap chain created successfully — NOW destroy the old one.
|
// Destroy old, install new
|
||||||
// The Renderer's internal RTV/SRV still reference the old backbuffer —
|
|
||||||
// those COM objects become orphaned (tiny leak, harmless). We DON'T
|
|
||||||
// release them because unknown code may also hold refs.
|
|
||||||
pOldSwapChain->Release();
|
pOldSwapChain->Release();
|
||||||
g_pSwapChain = pNewSwapChain;
|
g_pSwapChain = pNewSwapChain;
|
||||||
|
g_swapChainProxy.SetTarget(g_pSwapChain);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patch Renderer's swap chain pointer
|
// Patch Renderer's swap chain pointer (use proxy so VSync override stays active)
|
||||||
*ppRM_SC = g_pSwapChain;
|
*ppRM_SC = &g_swapChainProxy;
|
||||||
|
|
||||||
// Create render target views from new backbuffer
|
// Create render target views from new backbuffer
|
||||||
{
|
{
|
||||||
|
|
@ -1218,6 +1286,29 @@ void ToggleFullscreen()
|
||||||
g_KBMInput.SetWindowFocused(true);
|
g_KBMInput.SetWindowFocused(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called from UI thread — defers the actual transition to the main game loop
|
||||||
|
void SetExclusiveFullscreen(bool enabled)
|
||||||
|
{
|
||||||
|
if (enabled == g_isFullscreen)
|
||||||
|
return;
|
||||||
|
g_bPendingExclusiveFullscreen = true;
|
||||||
|
g_bPendingExclusiveFullscreenValue = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uses borderless fullscreen (ToggleFullscreen) rather than DXGI SetFullscreenState.
|
||||||
|
// With DXGI_SWAP_EFFECT_FLIP_DISCARD + DXGI_PRESENT_ALLOW_TEARING, borderless
|
||||||
|
// fullscreen gets the same direct-flip path as exclusive fullscreen on Windows 10+ —
|
||||||
|
// identical latency and uncapped FPS. True DXGI exclusive fullscreen is blocked by
|
||||||
|
// the 4J Renderer holding hidden backbuffer references that prevent ResizeBuffers.
|
||||||
|
static void ApplyExclusiveFullscreen(bool enabled)
|
||||||
|
{
|
||||||
|
// Toggle into/out of borderless fullscreen if state doesn't match
|
||||||
|
if (enabled && !g_isFullscreen)
|
||||||
|
ToggleFullscreen();
|
||||||
|
else if (!enabled && g_isFullscreen)
|
||||||
|
ToggleFullscreen();
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------
|
||||||
// Clean up the objects we've created
|
// Clean up the objects we've created
|
||||||
//--------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------
|
||||||
|
|
@ -1801,6 +1892,13 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
|
||||||
ToggleFullscreen();
|
ToggleFullscreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply deferred exclusive fullscreen toggle
|
||||||
|
if (g_bPendingExclusiveFullscreen)
|
||||||
|
{
|
||||||
|
g_bPendingExclusiveFullscreen = false;
|
||||||
|
ApplyExclusiveFullscreen(g_bPendingExclusiveFullscreenValue);
|
||||||
|
}
|
||||||
|
|
||||||
// TAB opens game info menu. - Vvis :3 - Updated by detectiveren
|
// TAB opens game info menu. - Vvis :3 - Updated by detectiveren
|
||||||
if (g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_HOST_SETTINGS) && !ui.GetMenuDisplayed(0))
|
if (g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_HOST_SETTINGS) && !ui.GetMenuDisplayed(0))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@
|
||||||
// TODO: reference additional headers your program requires here
|
// TODO: reference additional headers your program requires here
|
||||||
#include <DirectXMath.h>
|
#include <DirectXMath.h>
|
||||||
#include <d3d11.h>
|
#include <d3d11.h>
|
||||||
|
#include <dxgi1_5.h>
|
||||||
using namespace DirectX;
|
using namespace DirectX;
|
||||||
|
|
||||||
#define HRESULT_SUCCEEDED(hr) (((HRESULT)(hr)) >= 0)
|
#define HRESULT_SUCCEEDED(hr) (((HRESULT)(hr)) >= 0)
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ set_target_properties(Minecraft.Server PROPERTIES
|
||||||
target_link_libraries(Minecraft.Server PRIVATE
|
target_link_libraries(Minecraft.Server PRIVATE
|
||||||
Minecraft.World
|
Minecraft.World
|
||||||
d3d11
|
d3d11
|
||||||
|
dxgi
|
||||||
d3dcompiler
|
d3dcompiler
|
||||||
XInput9_1_0
|
XInput9_1_0
|
||||||
wsock32
|
wsock32
|
||||||
|
|
|
||||||
|
|
@ -932,7 +932,6 @@ bool Level::setTileAndData(int x, int y, int z, int tile, int data, int updateFl
|
||||||
int old = c->getTile(x & 15, y, z & 15);
|
int old = c->getTile(x & 15, y, z & 15);
|
||||||
int olddata = c->getData( x & 15, y, z & 15);
|
int olddata = c->getData( x & 15, y, z & 15);
|
||||||
#endif
|
#endif
|
||||||
int prevTile = c->getTile(x & 15, y, z & 15);
|
|
||||||
result = c->setTileAndData(x & 15, y, z & 15, tile, data);
|
result = c->setTileAndData(x & 15, y, z & 15, tile, data);
|
||||||
if( updateFlags != Tile::UPDATE_INVISIBLE_NO_LIGHT)
|
if( updateFlags != Tile::UPDATE_INVISIBLE_NO_LIGHT)
|
||||||
{
|
{
|
||||||
|
|
@ -940,11 +939,7 @@ bool Level::setTileAndData(int x, int y, int z, int tile, int data, int updateFl
|
||||||
PIXBeginNamedEvent(0,"Checking light %d %d %d",x,y,z);
|
PIXBeginNamedEvent(0,"Checking light %d %d %d",x,y,z);
|
||||||
PIXBeginNamedEvent(0,"was %d, %d now %d, %d",old,olddata,tile,data);
|
PIXBeginNamedEvent(0,"was %d, %d now %d, %d",old,olddata,tile,data);
|
||||||
#endif
|
#endif
|
||||||
if (Tile::lightBlock[tile & 0xff] != Tile::lightBlock[prevTile & 0xff] ||
|
checkLight(x, y, z);
|
||||||
Tile::lightEmission[tile & 0xff] != Tile::lightEmission[prevTile & 0xff])
|
|
||||||
{
|
|
||||||
checkLight(x, y, z);
|
|
||||||
}
|
|
||||||
PIXEndNamedEvent();
|
PIXEndNamedEvent();
|
||||||
PIXEndNamedEvent();
|
PIXEndNamedEvent();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -887,16 +887,11 @@ void LevelChunk::recalcHeight(int x, int yStart, int z)
|
||||||
if (!level->dimension->hasCeiling)
|
if (!level->dimension->hasCeiling)
|
||||||
{
|
{
|
||||||
PIXBeginNamedEvent(0,"Light gaps");
|
PIXBeginNamedEvent(0,"Light gaps");
|
||||||
// Flag columns for gap rechecking — processed by recheckGaps() on next tick
|
lightGap(xOffs - 1, zOffs, y1, y2);
|
||||||
auto flagGap = [&](int wx, int wz) {
|
lightGap(xOffs + 1, zOffs, y1, y2);
|
||||||
LevelChunk *c = level->getChunkAt(wx, wz);
|
lightGap(xOffs, zOffs - 1, y1, y2);
|
||||||
if (c && !c->isEmpty()) c->lightGaps(wx & 15, wz & 15);
|
lightGap(xOffs, zOffs + 1, y1, y2);
|
||||||
};
|
lightGap(xOffs, zOffs, y1, y2);
|
||||||
flagGap(xOffs - 1, zOffs);
|
|
||||||
flagGap(xOffs + 1, zOffs);
|
|
||||||
flagGap(xOffs, zOffs - 1);
|
|
||||||
flagGap(xOffs, zOffs + 1);
|
|
||||||
flagGap(xOffs, zOffs);
|
|
||||||
PIXEndNamedEvent();
|
PIXEndNamedEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
BIN
tools/AddExclusiveFullscreenCheckbox.class
Normal file
BIN
tools/AddExclusiveFullscreenCheckbox.class
Normal file
Binary file not shown.
158
tools/AddExclusiveFullscreenCheckbox.java
Normal file
158
tools/AddExclusiveFullscreenCheckbox.java
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
import com.jpexs.decompiler.flash.SWF;
|
||||||
|
import com.jpexs.decompiler.flash.tags.*;
|
||||||
|
import com.jpexs.decompiler.flash.tags.base.*;
|
||||||
|
import com.jpexs.decompiler.flash.types.*;
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an "ExclusiveFullscreen" checkbox to SettingsGraphicsMenu SWF files.
|
||||||
|
* Clones the existing VSync checkbox, renames it "ExclusiveFullscreen",
|
||||||
|
* positions it below VSync, and shifts sliders down.
|
||||||
|
*/
|
||||||
|
public class AddExclusiveFullscreenCheckbox {
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
if (args.length < 1) {
|
||||||
|
System.out.println("Usage: AddExclusiveFullscreenCheckbox <swf_file> [output_file]");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
String inputPath = args[0];
|
||||||
|
String outputPath = args.length > 1 ? args[1] : inputPath;
|
||||||
|
|
||||||
|
SWF swf = new SWF(new FileInputStream(inputPath), false);
|
||||||
|
|
||||||
|
// Check if ExclusiveFullscreen already exists
|
||||||
|
for (Tag tag : swf.getTags()) {
|
||||||
|
if (tag instanceof PlaceObject3Tag) {
|
||||||
|
PlaceObject3Tag po = (PlaceObject3Tag) tag;
|
||||||
|
if ("ExclusiveFullscreen".equals(po.name)) {
|
||||||
|
System.out.println("ExclusiveFullscreen checkbox already exists in " + inputPath + ", skipping.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the VSync checkbox tag and all slider tags
|
||||||
|
PlaceObject3Tag vsyncTag = null;
|
||||||
|
PlaceObject3Tag customSkinAnimTag = null;
|
||||||
|
List<PlaceObject3Tag> sliderTags = new ArrayList<>();
|
||||||
|
int maxDepth = 0;
|
||||||
|
|
||||||
|
for (Tag tag : swf.getTags()) {
|
||||||
|
if (tag instanceof PlaceObject3Tag) {
|
||||||
|
PlaceObject3Tag po = (PlaceObject3Tag) tag;
|
||||||
|
if ("VSync".equals(po.name)) {
|
||||||
|
vsyncTag = po;
|
||||||
|
}
|
||||||
|
if ("CustomSkinAnim".equals(po.name)) {
|
||||||
|
customSkinAnimTag = po;
|
||||||
|
}
|
||||||
|
if (po.name != null && (po.name.equals("RenderDistance") || po.name.equals("Gamma")
|
||||||
|
|| po.name.equals("FOV") || po.name.equals("InterfaceOpacity"))) {
|
||||||
|
sliderTags.add(po);
|
||||||
|
}
|
||||||
|
if (po.depth > maxDepth) maxDepth = po.depth;
|
||||||
|
}
|
||||||
|
if (tag instanceof PlaceObject2Tag) {
|
||||||
|
PlaceObject2Tag po = (PlaceObject2Tag) tag;
|
||||||
|
if (po.depth > maxDepth) maxDepth = po.depth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vsyncTag == null) {
|
||||||
|
System.err.println("ERROR: Could not find VSync checkbox in " + inputPath);
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate checkbox Y-spacing from the gap between CustomSkinAnim and VSync
|
||||||
|
int checkboxSpacing;
|
||||||
|
if (customSkinAnimTag != null) {
|
||||||
|
checkboxSpacing = vsyncTag.matrix.translateY - customSkinAnimTag.matrix.translateY;
|
||||||
|
} else {
|
||||||
|
checkboxSpacing = vsyncTag.matrix.translateY > 8000 ? 1080 : 720;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("File: " + inputPath);
|
||||||
|
System.out.println(" VSync Y: " + vsyncTag.matrix.translateY);
|
||||||
|
System.out.println(" Checkbox spacing: " + checkboxSpacing);
|
||||||
|
|
||||||
|
// Create the ExclusiveFullscreen PlaceObject3Tag by copying fields from VSync
|
||||||
|
PlaceObject3Tag efTag = new PlaceObject3Tag(swf);
|
||||||
|
efTag.placeFlagHasClassName = vsyncTag.placeFlagHasClassName;
|
||||||
|
efTag.placeFlagHasName = true;
|
||||||
|
efTag.placeFlagHasMatrix = true;
|
||||||
|
efTag.placeFlagHasImage = vsyncTag.placeFlagHasImage;
|
||||||
|
efTag.placeFlagHasCharacter = vsyncTag.placeFlagHasCharacter;
|
||||||
|
efTag.placeFlagMove = vsyncTag.placeFlagMove;
|
||||||
|
efTag.className = vsyncTag.className;
|
||||||
|
efTag.name = "ExclusiveFullscreen";
|
||||||
|
efTag.depth = maxDepth + 1;
|
||||||
|
efTag.characterId = vsyncTag.characterId;
|
||||||
|
|
||||||
|
// Copy and offset the matrix
|
||||||
|
MATRIX m = new MATRIX(vsyncTag.matrix);
|
||||||
|
m.translateY += checkboxSpacing;
|
||||||
|
efTag.matrix = m;
|
||||||
|
|
||||||
|
efTag.setModified(true);
|
||||||
|
|
||||||
|
System.out.println(" ExclusiveFullscreen Y: " + m.translateY);
|
||||||
|
System.out.println(" ExclusiveFullscreen depth: " + efTag.depth);
|
||||||
|
|
||||||
|
// Shift all sliders down by checkboxSpacing
|
||||||
|
for (PlaceObject3Tag slider : sliderTags) {
|
||||||
|
System.out.println(" Shifting " + slider.name + " from Y=" + slider.matrix.translateY
|
||||||
|
+ " to Y=" + (slider.matrix.translateY + checkboxSpacing));
|
||||||
|
slider.matrix.translateY += checkboxSpacing;
|
||||||
|
slider.setModified(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expand the background panel height
|
||||||
|
for (Tag tag : swf.getTags()) {
|
||||||
|
if (tag instanceof PlaceObject2Tag) {
|
||||||
|
PlaceObject2Tag po = (PlaceObject2Tag) tag;
|
||||||
|
if ("BackgroundPanel".equals(po.name)) {
|
||||||
|
int charId = po.characterId;
|
||||||
|
CharacterTag ct = swf.getCharacter(charId);
|
||||||
|
if (ct instanceof DefineSpriteTag) {
|
||||||
|
DefineSpriteTag sprite = (DefineSpriteTag) ct;
|
||||||
|
for (Tag sub : sprite.getTags()) {
|
||||||
|
if (sub instanceof PlaceObject3Tag) {
|
||||||
|
PlaceObject3Tag gridTag = (PlaceObject3Tag) sub;
|
||||||
|
float oldScaleY = gridTag.matrix.scaleY;
|
||||||
|
gridTag.matrix.scaleY += (float) checkboxSpacing / 640.0f;
|
||||||
|
gridTag.setModified(true);
|
||||||
|
System.out.println(" Background panel scaleY: " + oldScaleY + " -> " + gridTag.matrix.scaleY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert ExclusiveFullscreen tag right after VSync
|
||||||
|
ArrayList<Tag> tagList = swf.getTags().toArrayList();
|
||||||
|
int insertIdx = -1;
|
||||||
|
for (int i = 0; i < tagList.size(); i++) {
|
||||||
|
if (tagList.get(i) == vsyncTag) {
|
||||||
|
insertIdx = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insertIdx >= 0) {
|
||||||
|
swf.addTag(insertIdx, efTag);
|
||||||
|
System.out.println(" Inserted ExclusiveFullscreen tag at index " + insertIdx);
|
||||||
|
} else {
|
||||||
|
System.err.println("ERROR: Could not find insertion point");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save
|
||||||
|
try (FileOutputStream fos = new FileOutputStream(outputPath)) {
|
||||||
|
swf.saveTo(fos);
|
||||||
|
}
|
||||||
|
System.out.println(" Saved to: " + outputPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
tools/AddVSyncCheckbox.class
Normal file
BIN
tools/AddVSyncCheckbox.class
Normal file
Binary file not shown.
160
tools/AddVSyncCheckbox.java
Normal file
160
tools/AddVSyncCheckbox.java
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
import com.jpexs.decompiler.flash.SWF;
|
||||||
|
import com.jpexs.decompiler.flash.tags.*;
|
||||||
|
import com.jpexs.decompiler.flash.tags.base.*;
|
||||||
|
import com.jpexs.decompiler.flash.types.*;
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a "VSync" checkbox to SettingsGraphicsMenu SWF files.
|
||||||
|
* Clones the existing CustomSkinAnim checkbox, renames it "VSync",
|
||||||
|
* positions it below CustomSkinAnim, and shifts sliders down.
|
||||||
|
*/
|
||||||
|
public class AddVSyncCheckbox {
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
if (args.length < 1) {
|
||||||
|
System.out.println("Usage: AddVSyncCheckbox <swf_file> [output_file]");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
String inputPath = args[0];
|
||||||
|
String outputPath = args.length > 1 ? args[1] : inputPath;
|
||||||
|
|
||||||
|
SWF swf = new SWF(new FileInputStream(inputPath), false);
|
||||||
|
|
||||||
|
// Check if VSync already exists
|
||||||
|
for (Tag tag : swf.getTags()) {
|
||||||
|
if (tag instanceof PlaceObject3Tag) {
|
||||||
|
PlaceObject3Tag po = (PlaceObject3Tag) tag;
|
||||||
|
if ("VSync".equals(po.name)) {
|
||||||
|
System.out.println("VSync checkbox already exists in " + inputPath + ", skipping.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the CustomSkinAnim checkbox tag and all slider tags
|
||||||
|
PlaceObject3Tag customSkinAnimTag = null;
|
||||||
|
PlaceObject3Tag bedrockFogTag = null;
|
||||||
|
List<PlaceObject3Tag> sliderTags = new ArrayList<>();
|
||||||
|
int maxDepth = 0;
|
||||||
|
|
||||||
|
for (Tag tag : swf.getTags()) {
|
||||||
|
if (tag instanceof PlaceObject3Tag) {
|
||||||
|
PlaceObject3Tag po = (PlaceObject3Tag) tag;
|
||||||
|
if ("CustomSkinAnim".equals(po.name)) {
|
||||||
|
customSkinAnimTag = po;
|
||||||
|
}
|
||||||
|
if ("BedrockFog".equals(po.name)) {
|
||||||
|
bedrockFogTag = po;
|
||||||
|
}
|
||||||
|
if (po.name != null && (po.name.equals("RenderDistance") || po.name.equals("Gamma")
|
||||||
|
|| po.name.equals("FOV") || po.name.equals("InterfaceOpacity"))) {
|
||||||
|
sliderTags.add(po);
|
||||||
|
}
|
||||||
|
if (po.depth > maxDepth) maxDepth = po.depth;
|
||||||
|
}
|
||||||
|
if (tag instanceof PlaceObject2Tag) {
|
||||||
|
PlaceObject2Tag po = (PlaceObject2Tag) tag;
|
||||||
|
if (po.depth > maxDepth) maxDepth = po.depth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customSkinAnimTag == null) {
|
||||||
|
System.err.println("ERROR: Could not find CustomSkinAnim checkbox in " + inputPath);
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate checkbox Y-spacing
|
||||||
|
int checkboxSpacing;
|
||||||
|
if (bedrockFogTag != null) {
|
||||||
|
checkboxSpacing = customSkinAnimTag.matrix.translateY - bedrockFogTag.matrix.translateY;
|
||||||
|
} else {
|
||||||
|
checkboxSpacing = customSkinAnimTag.matrix.translateY > 8000 ? 1080 : 720;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("File: " + inputPath);
|
||||||
|
System.out.println(" CustomSkinAnim Y: " + customSkinAnimTag.matrix.translateY);
|
||||||
|
System.out.println(" Checkbox spacing: " + checkboxSpacing);
|
||||||
|
|
||||||
|
// Create the VSync PlaceObject3Tag by copying fields from CustomSkinAnim
|
||||||
|
PlaceObject3Tag vsyncTag = new PlaceObject3Tag(swf);
|
||||||
|
vsyncTag.placeFlagHasClassName = customSkinAnimTag.placeFlagHasClassName;
|
||||||
|
vsyncTag.placeFlagHasName = true;
|
||||||
|
vsyncTag.placeFlagHasMatrix = true;
|
||||||
|
vsyncTag.placeFlagHasImage = customSkinAnimTag.placeFlagHasImage;
|
||||||
|
vsyncTag.placeFlagHasCharacter = customSkinAnimTag.placeFlagHasCharacter;
|
||||||
|
vsyncTag.placeFlagMove = customSkinAnimTag.placeFlagMove;
|
||||||
|
vsyncTag.className = customSkinAnimTag.className;
|
||||||
|
vsyncTag.name = "VSync";
|
||||||
|
vsyncTag.depth = maxDepth + 1;
|
||||||
|
vsyncTag.characterId = customSkinAnimTag.characterId;
|
||||||
|
|
||||||
|
// Copy and offset the matrix
|
||||||
|
MATRIX m = new MATRIX(customSkinAnimTag.matrix);
|
||||||
|
m.translateY += checkboxSpacing;
|
||||||
|
vsyncTag.matrix = m;
|
||||||
|
|
||||||
|
vsyncTag.setModified(true);
|
||||||
|
|
||||||
|
System.out.println(" VSync Y: " + m.translateY);
|
||||||
|
System.out.println(" VSync depth: " + vsyncTag.depth);
|
||||||
|
System.out.println(" VSync className: " + vsyncTag.className);
|
||||||
|
|
||||||
|
// Shift all sliders down by checkboxSpacing
|
||||||
|
for (PlaceObject3Tag slider : sliderTags) {
|
||||||
|
System.out.println(" Shifting " + slider.name + " from Y=" + slider.matrix.translateY
|
||||||
|
+ " to Y=" + (slider.matrix.translateY + checkboxSpacing));
|
||||||
|
slider.matrix.translateY += checkboxSpacing;
|
||||||
|
slider.setModified(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expand the background panel height
|
||||||
|
for (Tag tag : swf.getTags()) {
|
||||||
|
if (tag instanceof PlaceObject2Tag) {
|
||||||
|
PlaceObject2Tag po = (PlaceObject2Tag) tag;
|
||||||
|
if ("BackgroundPanel".equals(po.name)) {
|
||||||
|
int charId = po.characterId;
|
||||||
|
CharacterTag ct = swf.getCharacter(charId);
|
||||||
|
if (ct instanceof DefineSpriteTag) {
|
||||||
|
DefineSpriteTag sprite = (DefineSpriteTag) ct;
|
||||||
|
for (Tag sub : sprite.getTags()) {
|
||||||
|
if (sub instanceof PlaceObject3Tag) {
|
||||||
|
PlaceObject3Tag gridTag = (PlaceObject3Tag) sub;
|
||||||
|
float oldScaleY = gridTag.matrix.scaleY;
|
||||||
|
// The 9-grid base tile is 640 twips (32px * 20 twips/px)
|
||||||
|
gridTag.matrix.scaleY += (float) checkboxSpacing / 640.0f;
|
||||||
|
gridTag.setModified(true);
|
||||||
|
System.out.println(" Background panel scaleY: " + oldScaleY + " -> " + gridTag.matrix.scaleY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert VSync tag right after CustomSkinAnim
|
||||||
|
ArrayList<Tag> tagList = swf.getTags().toArrayList();
|
||||||
|
int insertIdx = -1;
|
||||||
|
for (int i = 0; i < tagList.size(); i++) {
|
||||||
|
if (tagList.get(i) == customSkinAnimTag) {
|
||||||
|
insertIdx = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insertIdx >= 0) {
|
||||||
|
swf.addTag(insertIdx, vsyncTag);
|
||||||
|
System.out.println(" Inserted VSync tag at index " + insertIdx);
|
||||||
|
} else {
|
||||||
|
System.err.println("ERROR: Could not find insertion point");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save
|
||||||
|
try (FileOutputStream fos = new FileOutputStream(outputPath)) {
|
||||||
|
swf.saveTo(fos);
|
||||||
|
}
|
||||||
|
System.out.println(" Saved to: " + outputPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
tools/DumpSwf.class
Normal file
BIN
tools/DumpSwf.class
Normal file
Binary file not shown.
40
tools/DumpSwf.java
Normal file
40
tools/DumpSwf.java
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import com.jpexs.decompiler.flash.SWF;
|
||||||
|
import com.jpexs.decompiler.flash.tags.*;
|
||||||
|
import com.jpexs.decompiler.flash.tags.base.*;
|
||||||
|
import com.jpexs.decompiler.flash.types.*;
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class DumpSwf {
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
String path = args[0];
|
||||||
|
SWF swf = new SWF(new FileInputStream(path), false);
|
||||||
|
|
||||||
|
System.out.println("=== Top-level Tags ===");
|
||||||
|
int idx = 0;
|
||||||
|
for (Tag tag : swf.getTags()) {
|
||||||
|
System.out.println("[" + idx + "] " + tag.getClass().getSimpleName() + " - " + tag);
|
||||||
|
dumpPlaceObject(tag, " ");
|
||||||
|
|
||||||
|
if (tag instanceof DefineSpriteTag) {
|
||||||
|
DefineSpriteTag sprite = (DefineSpriteTag) tag;
|
||||||
|
System.out.println(" spriteId=" + sprite.spriteId + " frames=" + sprite.frameCount);
|
||||||
|
int subIdx = 0;
|
||||||
|
for (Tag sub : sprite.getTags()) {
|
||||||
|
System.out.println(" [" + subIdx + "] " + sub.getClass().getSimpleName() + " - " + sub);
|
||||||
|
dumpPlaceObject(sub, " ");
|
||||||
|
subIdx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dumpPlaceObject(Tag tag, String indent) {
|
||||||
|
if (tag instanceof PlaceObjectTypeTag) {
|
||||||
|
PlaceObjectTypeTag po = (PlaceObjectTypeTag) tag;
|
||||||
|
System.out.println(indent + "depth=" + po.getDepth() + " charId=" + po.getCharacterId()
|
||||||
|
+ " name=" + po.getInstanceName() + " matrix=" + po.getMatrix());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
tools/RebuildArc.class
Normal file
BIN
tools/RebuildArc.class
Normal file
Binary file not shown.
168
tools/RebuildArc.java
Normal file
168
tools/RebuildArc.java
Normal file
|
|
@ -0,0 +1,168 @@
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rebuilds a MediaWindows64.arc archive file by replacing specific SWF files
|
||||||
|
* with updated versions from disk.
|
||||||
|
*
|
||||||
|
* Usage: RebuildArc <arc_file> <media_dir> [file1.swf file2.swf ...]
|
||||||
|
*
|
||||||
|
* If no specific files are given, replaces all SWF files found in media_dir.
|
||||||
|
* The arc format is Java DataOutputStream style: big-endian ints, modified UTF-8 strings.
|
||||||
|
*
|
||||||
|
* Archive format:
|
||||||
|
* int: numberOfFiles
|
||||||
|
* For each file:
|
||||||
|
* UTF: filename (prefixed with '*' if compressed)
|
||||||
|
* int: offset into data section
|
||||||
|
* int: filesize
|
||||||
|
* Raw file data follows the header
|
||||||
|
*/
|
||||||
|
public class RebuildArc {
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
if (args.length < 2) {
|
||||||
|
System.out.println("Usage: RebuildArc <arc_file> <media_dir> [file1.swf file2.swf ...]");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
String arcPath = args[0];
|
||||||
|
String mediaDir = args[1];
|
||||||
|
Set<String> replaceFiles = new HashSet<>();
|
||||||
|
for (int i = 2; i < args.length; i++) {
|
||||||
|
replaceFiles.add(args[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the original archive
|
||||||
|
DataInputStream dis = new DataInputStream(new FileInputStream(arcPath));
|
||||||
|
int numberOfFiles = dis.readInt();
|
||||||
|
|
||||||
|
System.out.println("Archive has " + numberOfFiles + " files");
|
||||||
|
|
||||||
|
// Read the index
|
||||||
|
List<String> filenames = new ArrayList<>();
|
||||||
|
List<Integer> offsets = new ArrayList<>();
|
||||||
|
List<Integer> sizes = new ArrayList<>();
|
||||||
|
List<Boolean> compressed = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < numberOfFiles; i++) {
|
||||||
|
String name = dis.readUTF();
|
||||||
|
int offset = dis.readInt();
|
||||||
|
int size = dis.readInt();
|
||||||
|
|
||||||
|
boolean isCompressed = false;
|
||||||
|
if (name.startsWith("*")) {
|
||||||
|
name = name.substring(1);
|
||||||
|
isCompressed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
filenames.add(name);
|
||||||
|
offsets.add(offset);
|
||||||
|
sizes.add(size);
|
||||||
|
compressed.add(isCompressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The header size is the current position - data offsets are relative to file start
|
||||||
|
// Read the entire file to get the raw data
|
||||||
|
dis.close();
|
||||||
|
byte[] arcData = Files.readAllBytes(Paths.get(arcPath));
|
||||||
|
|
||||||
|
// Build replacement data map
|
||||||
|
// For each file, either use the replacement or the original data
|
||||||
|
List<byte[]> fileData = new ArrayList<>();
|
||||||
|
int replacedCount = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < numberOfFiles; i++) {
|
||||||
|
String name = filenames.get(i);
|
||||||
|
String simpleName = name.contains("\\") ? name.substring(name.lastIndexOf('\\') + 1) : name;
|
||||||
|
|
||||||
|
boolean shouldReplace = false;
|
||||||
|
if (replaceFiles.isEmpty()) {
|
||||||
|
// Replace all SWFs found in mediaDir
|
||||||
|
File diskFile = new File(mediaDir, simpleName);
|
||||||
|
if (diskFile.exists() && simpleName.endsWith(".swf")) {
|
||||||
|
shouldReplace = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
shouldReplace = replaceFiles.contains(simpleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldReplace) {
|
||||||
|
File diskFile = new File(mediaDir, simpleName);
|
||||||
|
if (diskFile.exists()) {
|
||||||
|
byte[] newData = Files.readAllBytes(diskFile.toPath());
|
||||||
|
fileData.add(newData);
|
||||||
|
// Replaced files are NOT compressed (our SWFs are uncompressed)
|
||||||
|
compressed.set(i, false);
|
||||||
|
sizes.set(i, newData.length);
|
||||||
|
System.out.println(" Replacing: " + name + " (" + newData.length + " bytes)");
|
||||||
|
replacedCount++;
|
||||||
|
} else {
|
||||||
|
System.err.println(" WARNING: " + diskFile + " not found, keeping original");
|
||||||
|
byte[] original = new byte[sizes.get(i)];
|
||||||
|
System.arraycopy(arcData, offsets.get(i), original, 0, sizes.get(i));
|
||||||
|
fileData.add(original);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Keep original data
|
||||||
|
byte[] original = new byte[sizes.get(i)];
|
||||||
|
System.arraycopy(arcData, offsets.get(i), original, 0, sizes.get(i));
|
||||||
|
fileData.add(original);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (replacedCount == 0) {
|
||||||
|
System.out.println("No files were replaced!");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate new header size to compute data offsets
|
||||||
|
// Header: 4 bytes (numFiles) + for each file: (2 + UTF8 length + 4 + 4) bytes
|
||||||
|
ByteArrayOutputStream headerBuf = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream headerDos = new DataOutputStream(headerBuf);
|
||||||
|
headerDos.writeInt(numberOfFiles);
|
||||||
|
for (int i = 0; i < numberOfFiles; i++) {
|
||||||
|
String name = filenames.get(i);
|
||||||
|
if (compressed.get(i)) {
|
||||||
|
name = "*" + name;
|
||||||
|
}
|
||||||
|
headerDos.writeUTF(name);
|
||||||
|
headerDos.writeInt(0); // placeholder offset
|
||||||
|
headerDos.writeInt(0); // placeholder size
|
||||||
|
}
|
||||||
|
headerDos.flush();
|
||||||
|
int headerSize = headerBuf.size();
|
||||||
|
|
||||||
|
// Now compute real offsets
|
||||||
|
int currentOffset = headerSize;
|
||||||
|
List<Integer> newOffsets = new ArrayList<>();
|
||||||
|
for (int i = 0; i < numberOfFiles; i++) {
|
||||||
|
newOffsets.add(currentOffset);
|
||||||
|
currentOffset += fileData.get(i).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the final archive
|
||||||
|
String outputPath = arcPath; // overwrite in place
|
||||||
|
DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(outputPath)));
|
||||||
|
dos.writeInt(numberOfFiles);
|
||||||
|
for (int i = 0; i < numberOfFiles; i++) {
|
||||||
|
String name = filenames.get(i);
|
||||||
|
if (compressed.get(i)) {
|
||||||
|
name = "*" + name;
|
||||||
|
}
|
||||||
|
dos.writeUTF(name);
|
||||||
|
dos.writeInt(newOffsets.get(i));
|
||||||
|
dos.writeInt(fileData.get(i).length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write file data
|
||||||
|
for (int i = 0; i < numberOfFiles; i++) {
|
||||||
|
dos.write(fileData.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
dos.flush();
|
||||||
|
dos.close();
|
||||||
|
|
||||||
|
System.out.println("Rebuilt archive: " + outputPath + " (" + replacedCount + " files replaced)");
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue