├── dxsdk └── .copy_fxc_exe_here ├── .gitmodules ├── SaltAndSanctuary ├── shaders │ ├── MainEffectLightsBlur.fx │ ├── MainEffectLightsNoBlur.fx │ ├── MainEffectNoLightsBlur.fx │ ├── MainEffectNoLightsNoBlur.fx │ ├── ClearAlpha.fx │ ├── compile.bat │ ├── SimplifiedMapBlood.fx │ ├── SimplifiedParchment.fx │ ├── SimplifiedGaussianBlur.fx │ ├── SimplifiedTintBloom.fx │ ├── SimplifiedLight.fx │ ├── SimplifiedGameParchment.fx │ └── MainEffectCommon.ifx └── Makefile.inc ├── deps ├── FNA │ └── .extract_fna_here ├── Harmony │ └── .extract_harmony_here └── MonoMod │ └── .extract_monomod_here ├── Chasm ├── Makefile.inc └── ChasmPatches.cs ├── Celeste ├── Makefile.inc └── CelestePatches.cs ├── Anodyne ├── Makefile.inc ├── shaders │ ├── compile.bat │ └── bilinear-sharp.fx └── AnodynePatches.cs ├── Shipwreck ├── Makefile.inc └── ShipwreckPatches.cs ├── SteelAssault ├── Makefile.inc └── SteelAssaultCsPatches.cs ├── .gitignore ├── PanzerPaladin ├── Makefile.inc └── ParisEngine.PanzerPaladin.mm.cs ├── TMNTSR-Mods ├── Makefile.inc ├── TMNTSR-Mods.csproj └── ParisEngine.TMNTSRMods.mm.cs ├── TMNTSR ├── Makefile.inc ├── TMNT.TMNTSRPatches.mm.cs └── ParisEngine.TMNTSRPatches.mm.cs ├── Bleed ├── Makefile.inc └── Bleed.BleedPatches.mm.cs ├── Makefile ├── libs ├── Makefile └── fmodstudio_fix.c ├── LICENSE ├── Makefile.common ├── MMLoader.cs └── README.md /dxsdk/.copy_fxc_exe_here: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "VisFree"] 2 | path = VisFree 3 | url = https://github.com/JohnnyonFlame/VisFree 4 | -------------------------------------------------------------------------------- /SaltAndSanctuary/shaders/MainEffectLightsBlur.fx: -------------------------------------------------------------------------------- 1 | #define HAS_LIGHTS 1 2 | #define HAS_BLUR 1 3 | #include "MainEffectCommon.ifx" -------------------------------------------------------------------------------- /SaltAndSanctuary/shaders/MainEffectLightsNoBlur.fx: -------------------------------------------------------------------------------- 1 | #define HAS_LIGHTS 1 2 | #define HAS_BLUR 0 3 | #include "MainEffectCommon.ifx" -------------------------------------------------------------------------------- /SaltAndSanctuary/shaders/MainEffectNoLightsBlur.fx: -------------------------------------------------------------------------------- 1 | #define HAS_LIGHTS 0 2 | #define HAS_BLUR 1 3 | #include "MainEffectCommon.ifx" -------------------------------------------------------------------------------- /SaltAndSanctuary/shaders/MainEffectNoLightsNoBlur.fx: -------------------------------------------------------------------------------- 1 | #define HAS_LIGHTS 0 2 | #define HAS_BLUR 0 3 | #include "MainEffectCommon.ifx" -------------------------------------------------------------------------------- /deps/FNA/.extract_fna_here: -------------------------------------------------------------------------------- 1 | You should extract the necessary FNA build here, you should have the follwoing files: 2 | FNA.dll OpenAL-CS.dll SDL2-CS.dll -------------------------------------------------------------------------------- /deps/Harmony/.extract_harmony_here: -------------------------------------------------------------------------------- 1 | You should have the following structure here: 2 | Lib.Harmony.****.nupkg net35 net45 net472 net48 net5.0 netcoreapp3.0 netcoreapp3.1 -------------------------------------------------------------------------------- /Chasm/Makefile.inc: -------------------------------------------------------------------------------- 1 | SRC := $(MAKECMDGOALS)/ChasmPatches.cs 2 | ASM := $(SRC:%.cs=out/%.dll) 3 | DEPS_FOLDER ?= deps/$(MAKECMDGOALS) 4 | DEPS := $(DEPS_FOLDER)/Chasm.exe \ 5 | deps/FNA/FNA.dll \ 6 | deps/Harmony/net45/0Harmony.dll 7 | -------------------------------------------------------------------------------- /Celeste/Makefile.inc: -------------------------------------------------------------------------------- 1 | SRC := $(MAKECMDGOALS)/CelestePatches.cs 2 | ASM := $(SRC:%.cs=out/%.dll) 3 | DEPS_FOLDER ?= deps/$(MAKECMDGOALS) 4 | DEPS := $(DEPS_FOLDER)/Celeste.exe \ 5 | deps/FNA/FNA.dll \ 6 | deps/Harmony/net45/0Harmony.dll 7 | -------------------------------------------------------------------------------- /SaltAndSanctuary/Makefile.inc: -------------------------------------------------------------------------------- 1 | SRC := $(MAKECMDGOALS)/SaltPatches.cs 2 | ASM := $(SRC:%.cs=out/%.dll) 3 | DEPS_FOLDER ?= deps/$(MAKECMDGOALS) 4 | DEPS := $(DEPS_FOLDER)/Salt.exe \ 5 | deps/FNA/FNA.dll \ 6 | deps/Harmony/net45/0Harmony.dll 7 | -------------------------------------------------------------------------------- /Anodyne/Makefile.inc: -------------------------------------------------------------------------------- 1 | SRC := $(MAKECMDGOALS)/AnodynePatches.cs 2 | ASM := $(SRC:%.cs=out/%.dll) 3 | DEPS_FOLDER ?= deps/$(MAKECMDGOALS) 4 | DEPS := $(DEPS_FOLDER)/AnodyneSharp.exe \ 5 | deps/FNA/FNA.dll \ 6 | deps/Harmony/net45/0Harmony.dll 7 | -------------------------------------------------------------------------------- /Shipwreck/Makefile.inc: -------------------------------------------------------------------------------- 1 | SRC := $(MAKECMDGOALS)/ShipwreckPatches.cs 2 | ASM := $(SRC:%.cs=out/%.dll) 3 | DEPS_FOLDER ?= deps/$(MAKECMDGOALS) 4 | DEPS := $(DEPS_FOLDER)/Shipwreck.exe \ 5 | deps/Shipwreck/FNA.dll \ 6 | deps/Harmony/net45/0Harmony.dll 7 | -------------------------------------------------------------------------------- /SteelAssault/Makefile.inc: -------------------------------------------------------------------------------- 1 | SRC := $(MAKECMDGOALS)/SteelAssaultCsPatches.cs 2 | ASM := $(SRC:%.cs=out/%.dll) 3 | DEPS_FOLDER ?= deps/$(MAKECMDGOALS) 4 | DEPS := $(DEPS_FOLDER)/SteelAssaultCs.exe \ 5 | deps/FNA/FNA.dll \ 6 | deps/Harmony/net45/0Harmony.dll 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/bin/** 2 | **/obj/** 3 | out/** 4 | deps/** 5 | **/monomod/** 6 | **/*.dll 7 | **/*.exe 8 | !deps/*/.*here 9 | !deps/FNA 10 | !deps/Harmony 11 | !deps/MonoMod 12 | **/shaders/out/** 13 | dxsdk/* 14 | !dxsdk/.copy_fxc_exe_here 15 | shaders/out/* 16 | .vscode/** -------------------------------------------------------------------------------- /PanzerPaladin/Makefile.inc: -------------------------------------------------------------------------------- 1 | SRC := $(wildcard $(MAKECMDGOALS)/*.PanzerPaladin.mm.cs) 2 | ASM := $(SRC:%.cs=out/%.dll) 3 | DEPS_FOLDER ?= deps/$(MAKECMDGOALS) 4 | DEPS := $(DEPS_FOLDER)/PanzerPaladin.exe \ 5 | $(DEPS_FOLDER)/ParisEngine.dll \ 6 | deps/FNA/FNA.dll \ 7 | deps/MonoMod/MonoMod.exe 8 | -------------------------------------------------------------------------------- /SaltAndSanctuary/shaders/ClearAlpha.fx: -------------------------------------------------------------------------------- 1 | float4 ClearAlphaPS(float2 texCoord : TEXCOORD0) : COLOR0 2 | { 3 | return float4(1.0, 1.0, 1.0, 0.0); 4 | } 5 | 6 | technique ClearAlphaTechnique 7 | { 8 | pass Pass1 9 | { 10 | PixelShader = compile ps_3_0 ClearAlphaPS(); 11 | } 12 | } -------------------------------------------------------------------------------- /TMNTSR-Mods/Makefile.inc: -------------------------------------------------------------------------------- 1 | SRC := $(wildcard $(MAKECMDGOALS)/*.TMNTSRMods.mm.cs) 2 | ASM := $(SRC:%.cs=out/%.dll) 3 | DEPS_FOLDER ?= deps/$(MAKECMDGOALS) 4 | DEPS := $(DEPS_FOLDER)/TMNT.exe \ 5 | $(DEPS_FOLDER)/ParisEngine.dll \ 6 | deps/Cecil/*.dll \ 7 | deps/FNA/FNA.dll \ 8 | deps/MonoMod/MonoMod.exe 9 | -------------------------------------------------------------------------------- /TMNTSR/Makefile.inc: -------------------------------------------------------------------------------- 1 | SRC := $(wildcard $(MAKECMDGOALS)/*.TMNTSRPatches.mm.cs) 2 | ASM := $(SRC:%.cs=out/%.dll) 3 | DEPS_FOLDER ?= deps/$(MAKECMDGOALS) 4 | DEPS := $(DEPS_FOLDER)/TMNT.exe \ 5 | $(DEPS_FOLDER)/ParisEngine.dll \ 6 | deps/Cecil/*.dll \ 7 | deps/MonoMod/MonoMod.exe \ 8 | deps/FNA/FNA.dll 9 | -------------------------------------------------------------------------------- /Bleed/Makefile.inc: -------------------------------------------------------------------------------- 1 | SRC := $(MAKECMDGOALS)/Bleed.BleedPatches.mm.cs 2 | ASM := deps/$(MAKECMDGOALS)/MMHOOK_Bleed.dll $(SRC:%.cs=out/%.dll) 3 | DEPS_FOLDER ?= deps/$(MAKECMDGOALS) 4 | DEPS := $(DEPS_FOLDER)/Bleed.exe \ 5 | deps/$(MAKECMDGOALS)/MMHOOK_Bleed.dll \ 6 | deps/Cecil/*.dll \ 7 | deps/FNA/FNA.dll \ 8 | deps/MonoMod/MonoMod.exe \ 9 | deps/MonoMod/MonoMod.Utils.dll 10 | -------------------------------------------------------------------------------- /Anodyne/shaders/compile.bat: -------------------------------------------------------------------------------- 1 | mkdir out 2 | echo off 3 | 4 | rem ------------------------------- 5 | 6 | setlocal enabledelayedexpansion 7 | for %%f in (*.fx) do ( 8 | echo -- Compiling %%f -- 9 | ..\..\dxsdk\fxc.exe /nologo /T fx_2_0 /O3 %%f /Fo out/%%f%b 10 | if !errorlevel! neq 0 exit /b !errorlevel! 11 | ) 12 | 13 | echo ----------------------------- 14 | echo - Compilation Successful :) - 15 | echo ----------------------------- -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECTS:=$(wildcard */Makefile.inc) 2 | PROJECT_DIRS:=$(PROJECTS:%/Makefile.inc=%) 3 | 4 | all: $(PROJECTS) MMLoader libs 5 | 6 | # Suppress warnings when using the "clean" target. 7 | ifneq ($(MAKECMDGOALS), clean) 8 | $(MAKECMDGOALS): 9 | make -f Makefile.common $(MAKECMDGOALS) 10 | endif 11 | 12 | libs: 13 | make -C libs 14 | 15 | MMLoader: 16 | make -f Makefile.common MMLoader 17 | 18 | %/Makefile.inc: % 19 | make -f Makefile.common $< 20 | 21 | clean: 22 | @rm -rf out 23 | 24 | .PHONY: all $(PROJECT_DIRS) $(MAKECMDGOALS) libs -------------------------------------------------------------------------------- /SaltAndSanctuary/shaders/compile.bat: -------------------------------------------------------------------------------- 1 | mkdir out 2 | echo off 3 | 4 | rem -- Setup shader detail level -- 5 | 6 | rem - HAS_GRAIN: Number of maximum grain taps on parchmentEffect and gameParchEffect 7 | set HAS_GRAIN=0 8 | 9 | rem ------------------------------- 10 | 11 | setlocal enabledelayedexpansion 12 | for %%f in (*.fx) do ( 13 | echo -- Compiling %%f -- 14 | ..\..\dxsdk\fxc.exe /nologo /DHAS_GRAIN=%HAS_GRAIN% /T fx_2_0 /O3 %%f /Fo out/%%f%b 15 | if !errorlevel! neq 0 exit /b !errorlevel! 16 | ) 17 | 18 | echo ----------------------------- 19 | echo - Compilation Successful :) - 20 | echo ----------------------------- -------------------------------------------------------------------------------- /TMNTSR-Mods/TMNTSR-Mods.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | $(AssemblySearchPaths);{GAC};..\deps;..\deps\TMNTSR-Mods;..\deps\MonoMod;..\deps\Cecil 7 | .\deps;$(ReferencePath) 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /deps/MonoMod/.extract_monomod_here: -------------------------------------------------------------------------------- 1 | You should have the following files on this directory: 2 | 0Harmony.dll Mono.Cecil.Pdb.dll MonoMod.Common.dll MonoMod.DebugIL.exe.config 3 | 0Harmony.xml Mono.Cecil.Rocks.dll MonoMod.Common.xml MonoMod.DebugIL.xml 4 | Mono.Cecil.Mdb.dll Mono.Cecil.dll MonoMod.DebugIL.exe MonoMod.RuntimeDetour.HookGen.exe 5 | MonoMod.RuntimeDetour.HookGen.exe.config MonoMod.RuntimeDetour.xml MonoMod.Utils.dll MonoMod.exe.config 6 | MonoMod.RuntimeDetour.HookGen.xml MonoMod.UnitTest.dll MonoMod.Utils.xml MonoMod.xml 7 | MonoMod.RuntimeDetour.dll MonoMod.UnitTest.xml MonoMod.exe -------------------------------------------------------------------------------- /libs/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS?=-O3 -shared -rdynamic 2 | 3 | all: ../out/libs ../out/libs/fmodstudio_fix.so ../out/libs/EOSSDK-Win64-Shipping.so ../out/libs/libsteam_api.so 4 | 5 | clean: 6 | @rm -f ../out/libs/* 7 | 8 | ../out/libs: 9 | mkdir -p ../out/libs 10 | 11 | ../out/libs/fmodstudio_fix.so: ../out/libs fmodstudio_fix.c 12 | $(CC) -Wno-return-type $(CFLAGS) fmodstudio_fix.c -o ../out/libs/fmodstudio_fix.so 13 | 14 | ../out/libs/EOSSDK-Win64-Shipping.so: ../out/libs EOSSDK-Shipping-Stubs.c 15 | $(CC) -Wno-return-type $(CFLAGS) EOSSDK-Shipping-Stubs.c -o ../out/libs/EOSSDK-Win64-Shipping.so 16 | 17 | ../out/libs/libsteam_api.so: ../out/libs SteamStubs.c 18 | $(CC) -Wno-return-type $(CFLAGS) SteamStubs.c -o ../out/libs/libsteam_api.so 19 | @cp ../out/libs/libsteam_api.so ../out/libs/libsteam_api64.so 20 | @cp ../out/libs/libsteam_api.so ../out/libs/CSteamworks 21 | -------------------------------------------------------------------------------- /SaltAndSanctuary/shaders/SimplifiedMapBlood.fx: -------------------------------------------------------------------------------- 1 | // mapblood.fx 2 | // XNA 3 | 4 | sampler bloodSampler; 5 | float bloodAlpha; 6 | float bloodSat; 7 | 8 | struct PS_INPUT 9 | { 10 | float4 Color : COLOR0; 11 | float2 TexCoord : TEXCOORD0; 12 | float4 VPos : VPOS; 13 | }; 14 | 15 | float4 MapBlood(PS_INPUT Input) : COLOR0 16 | { 17 | float2 tex = Input.TexCoord; 18 | 19 | float4 blood = tex2D(bloodSampler, tex); 20 | float4 color; 21 | float c = blood.a * bloodAlpha * Input.Color.a * 0.75; 22 | color.rgb = blood.rgb * Input.Color.rgb * c; 23 | // Make the darker spots take more of the color away. Make it black. 24 | color.rgb += c * float3(0.35, 0.45, 0.45); 25 | 26 | return float4(color.rgb, 1.0); 27 | } 28 | 29 | technique PostBlood 30 | { 31 | pass P0 32 | { 33 | PixelShader = compile ps_3_0 MapBlood(); 34 | } 35 | } -------------------------------------------------------------------------------- /SaltAndSanctuary/shaders/SimplifiedParchment.fx: -------------------------------------------------------------------------------- 1 | // parchment.fx 2 | // XNA 3 | 4 | texture SampleTexture : register(t0); 5 | sampler2D t0_sampler = sampler_state { 6 | Texture = (SampleTexture); 7 | MagFilter = Linear; 8 | MinFilter = Linear; 9 | AddressU = Clamp; 10 | AddressV = Clamp; 11 | }; 12 | 13 | float pX; 14 | float pY; 15 | float pA; 16 | float r; 17 | float g; 18 | float b; 19 | 20 | float4 Parchment(float2 texCoord : TEXCOORD0) : COLOR0 21 | { 22 | float2 tex = texCoord; 23 | float4 col = float4(r, g, b, 0.0f); 24 | 25 | float dif; 26 | float2 vDif = texCoord - 0.5f; 27 | vDif *= vDif; 28 | dif = vDif.x + vDif.y; 29 | 30 | float2 texFrac = frac(tex + float2(pX, pY)); 31 | texFrac -= step(-texFrac, 0.0); 32 | #if HAS_GRAIN > 0 33 | col.a += tex2D(t0_sampler, texFrac).r * dif * pA; 34 | #else 35 | col.a += 0.55f * dif * pA; 36 | #endif 37 | return col; 38 | } 39 | 40 | technique ParchmentTechnique 41 | { 42 | pass P0 43 | { 44 | PixelShader = compile ps_3_0 Parchment(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /SaltAndSanctuary/shaders/SimplifiedGaussianBlur.fx: -------------------------------------------------------------------------------- 1 | // Pixel shader applies a one dimensional gaussian blur filter. 2 | // This is used twice by the bloom postprocess, first to 3 | // blur horizontally, and then again to blur vertically. 4 | 5 | texture SampleTexture : register(t0); 6 | sampler2D s0_sampler = sampler_state { 7 | Texture = (SampleTexture); 8 | MagFilter = Linear; 9 | MinFilter = Linear; 10 | AddressU = Clamp; 11 | AddressV = Clamp; 12 | }; 13 | 14 | #define SAMPLE_COUNT 7 15 | 16 | float2 SampleOffsets[SAMPLE_COUNT]; 17 | float SampleWeights[SAMPLE_COUNT]; 18 | 19 | float4 PixelShaderFunction(float2 texCoord : TEXCOORD0) : COLOR0 20 | { 21 | float4 c = 0; 22 | 23 | // Combine a number of weighted image filter taps. 24 | for (int i = 0; i < SAMPLE_COUNT; i++) 25 | { 26 | c += tex2D(s0_sampler, texCoord + SampleOffsets[i]) * SampleWeights[i]; 27 | } 28 | 29 | return c; 30 | } 31 | 32 | 33 | technique GaussianBlur 34 | { 35 | pass Pass1 36 | { 37 | PixelShader = compile ps_3_0 PixelShaderFunction(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Johnny on Flame. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Anodyne/shaders/bilinear-sharp.fx: -------------------------------------------------------------------------------- 1 | #define _vs(r) : register(vs, r) 2 | #define _ps(r) : register(ps, r) 3 | #define _cb(r) 4 | 5 | float4x4 MatrixTransform _vs(c0) _cb(c0); 6 | 7 | texture s0_texture : register(t0); 8 | sampler2D s0_sampler = sampler_state { 9 | Texture = (s0_texture); 10 | MagFilter = Linear; 11 | MinFilter = Linear; 12 | AddressU = Clamp; 13 | AddressV = Clamp; 14 | }; 15 | 16 | float2 TexSize; 17 | float2 Scale; 18 | 19 | float4 SpritePixelShader(float4 color : COLOR0, 20 | float2 texCoord : TEXCOORD0) : SV_Target0 21 | { 22 | float2 vTexCoord = (texCoord * TexSize); 23 | float2 texel_floored = floor(vTexCoord); 24 | float2 s = frac(vTexCoord); 25 | float2 region_range = 0.5 - 0.5 / Scale; 26 | float2 center_dist = s - 0.5; 27 | float2 f = (center_dist - clamp(center_dist, -region_range, region_range)) * Scale + 0.5; 28 | float2 mod_texel = texel_floored + f; 29 | float4 col = tex2D(s0_sampler, mod_texel / TexSize); 30 | return col; 31 | } 32 | 33 | technique SpriteBatch 34 | { 35 | pass P0 36 | { 37 | PixelShader = compile ps_3_0 SpritePixelShader(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Makefile.common: -------------------------------------------------------------------------------- 1 | MONO_PREFIX ?= /usr 2 | MONO ?= $(MONO_PREFIX)/bin/mono 3 | MCS ?= $(MONO_PREFIX)/bin/mcs 4 | MONO_PATH ?= $(MONO_PREFIX)/lib/mono/4.5 5 | COMMON_DEPS := $(MONO_PATH)/Facades/netstandard.dll $(MONO_PATH)/mscorlib.dll $(MONO_PATH)/Facades/System.Runtime.dll $(wildcard $(MONO_PATH)/*.dll) 6 | COMPILER ?= VisFree/bin/Release/VisFree.exe 7 | HOOK_GEN ?= MonoMod.RuntimeDetour.HookGen.exe 8 | 9 | -include $(MAKECMDGOALS)/Makefile.inc 10 | 11 | .PHONY: out/$(MAKECMDGOALS) MMLoader clean 12 | 13 | all: 14 | clean: 15 | rm -rf out/ 16 | 17 | $(MAKECMDGOALS): out/$(MAKECMDGOALS) $(ASM) 18 | 19 | out/$(MAKECMDGOALS): 20 | @mkdir -p out/$(MAKECMDGOALS)/ 21 | 22 | deps/$(MAKECMDGOALS)/MMHOOK_%.dll: deps/$(MAKECMDGOALS)/%.exe 23 | @cd deps/MonoMod && MONO_PATH=$(MONO_PATH) $(MONO) $(HOOK_GEN) ../../$^ 24 | 25 | deps/$(MAKECMDGOALS)/MMHOOK_%.dll: deps/$(MAKECMDGOALS)/%.dll 26 | @cd deps/MonoMod && MONO_PATH=$(MONO_PATH) $(MONO) $(HOOK_GEN) ../../$^ 27 | 28 | out/$(MAKECMDGOALS)/%.dll: $(MAKECMDGOALS)/%.cs 29 | @MONO_PATH=$(MONO_PATH) $(MONO) $(COMPILER) $@ $< -- $(DEPS) $(COMMON_DEPS) 30 | 31 | MMLoader: out/MMLoader out/MMLoader/MMLoader.exe 32 | out/MMLoader/MMLoader.exe: MMLoader.cs 33 | @MONO_PATH=$(MONO_PATH) $(MCS) -optimize MMLoader.cs /out:out/MMLoader/MMLoader.exe 34 | -------------------------------------------------------------------------------- /SaltAndSanctuary/shaders/SimplifiedTintBloom.fx: -------------------------------------------------------------------------------- 1 | struct InputStruct { 2 | float4 param0 : COLOR; 3 | float4 param1 : TEXCOORD; 4 | float4 param2 : POSITION; 5 | }; 6 | 7 | struct OutputStruct { 8 | float4 param0 : COLOR; 9 | float4 param1 : TEXCOORD; 10 | float4 Position : SV_Position; 11 | }; 12 | 13 | texture SampleTexture : register(t0); 14 | sampler2D s0_sampler = sampler_state { 15 | Texture = (SampleTexture); 16 | MagFilter = Linear; 17 | MinFilter = Linear; 18 | AddressU = Clamp; 19 | AddressV = Clamp; 20 | }; 21 | 22 | float4x4 MatrixTransform; 23 | float3 tintColor; 24 | float a; 25 | float tint; 26 | float sat; 27 | float screenSizeX; 28 | float screenSizeY; 29 | float detailAlpha; 30 | float detailX1; 31 | float detailX2; 32 | float detailY1; 33 | float detailY2; 34 | 35 | float4 PixelShaderFunction( 36 | float4 color : COLOR0, 37 | float2 texCoord : TEXCOORD0, 38 | float4 vpos : VPOS) : SV_Target0 39 | { 40 | float4 col = tex2D(s0_sampler, texCoord) * color; 41 | float stint = tint * a; 42 | col.rgb = lerp(col.rgb, tintColor, stint); 43 | 44 | float b = dot(col.rgb, 1 / 3.0f); 45 | float isat = saturate(1.0f - sat); 46 | 47 | // Mix color channels 48 | col.rgb = lerp(col.rgb, float3(b, b, b), isat); 49 | // Pre-multiplied alpha 50 | col.rgb *= (col.a * a); 51 | 52 | return col; 53 | } 54 | 55 | technique Technique1 56 | { 57 | pass Pass1 58 | { 59 | PixelShader = compile ps_3_0 PixelShaderFunction(); 60 | } 61 | } -------------------------------------------------------------------------------- /SaltAndSanctuary/shaders/SimplifiedLight.fx: -------------------------------------------------------------------------------- 1 | texture LightTexture : register(t0); 2 | sampler2D LightTextureSampler = sampler_state { 3 | Texture = (LightTexture); 4 | MagFilter = Linear; 5 | MinFilter = Linear; 6 | AddressU = Clamp; 7 | AddressV = Clamp; 8 | }; 9 | 10 | texture CloudsTexture : register(t1); 11 | sampler2D CloudsTextureSampler = sampler_state { 12 | Texture = (CloudsTexture); 13 | MagFilter = Linear; 14 | MinFilter = Linear; 15 | AddressU = Clamp; 16 | AddressV = Clamp; 17 | }; 18 | 19 | float2 scroll = { 0.0f, 0.0f }; 20 | float2 scroll2 = { 0.0f, 0.0f }; 21 | float2 scroll3 = { 0.0f, 0.0f }; 22 | 23 | float2 scrollDif = { 0.0f, 0.0f }; 24 | 25 | float alpha = 1.0f; 26 | float lightFac = 1.0f; 27 | 28 | float4 LightMap(float2 texCoord : TEXCOORD0) : COLOR0 29 | { 30 | float4 col = tex2D(LightTextureSampler, texCoord); 31 | 32 | float aFac = col.r; 33 | // if (aFac > 0.5f) 34 | // aFac = 1.0f - aFac; 35 | // aFac *= 4.0f; 36 | // if (aFac > 1.0f) 37 | // aFac = 1.0f; 38 | // OR 39 | // The original code created a downwards V 40 | aFac = saturate(-(abs(aFac-0.5f)-0.5f)*4.0f); 41 | // OR 42 | //aFac = smoothstep(0.0, 0.25, aFac) + smoothstep(1.0, 0.75, aFac) - 1; 43 | 44 | float c = dot(col, 1.0f / 3.0f); 45 | c += aFac * lightFac; 46 | //c = smoothstep(-0.1f, 0.96f, c); 47 | c = saturate(c); 48 | 49 | return float4(c, c, c, alpha); 50 | } 51 | 52 | technique LightMapTechnique 53 | { 54 | pass P0 55 | { 56 | PixelShader = compile ps_3_0 LightMap(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /libs/fmodstudio_fix.c: -------------------------------------------------------------------------------- 1 | // FMODStudio Fix 2 | // This is a wrapper that bridges FMOD 1.x and FMOD 2.x. 3 | // This is necessary, but not enough by itself, to run games such as Celeste on ARM64 systems. 4 | // You'll also need: 5 | // - The appropriate libfmodstudio.so.13 and libfmod.so.13 files (from https://www.fmod.com/download) 6 | // - Any necessary plugin to enable fmod to use your audio backend. (such as https://github.com/flibitijibibo/FMOD_SDL) 7 | // - The necessary mono .config file for your title, as an example, Celeste.exe.config: 8 | 9 | /* 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | */ 21 | 22 | #include 23 | #define FMOD_VERSION 0x00020206 24 | 25 | # define DECLSPEC __attribute__ ((visibility("default"))) 26 | DECLSPEC int _FMOD_Studio_EventInstance_SetParameterValue(void *system, const char *name, float value) { 27 | extern int FMOD_Studio_EventInstance_SetParameterByName(void *, const char *, float, int); 28 | return FMOD_Studio_EventInstance_SetParameterByName(system, name, value, 0); 29 | } 30 | 31 | DECLSPEC int _FMOD_Studio_System_Create(void *studiosystem, unsigned int headerversion) 32 | { 33 | // If this fails, check if the FMOD_VERSION number is correct. 34 | extern int FMOD_Studio_System_Create(void *, int); 35 | return FMOD_Studio_System_Create(studiosystem, FMOD_VERSION); 36 | } 37 | -------------------------------------------------------------------------------- /SaltAndSanctuary/shaders/SimplifiedGameParchment.fx: -------------------------------------------------------------------------------- 1 | // gameparch.fx 2 | // XNA 3 | 4 | texture SampleTexture : register(t0); 5 | sampler2D t0_sampler = sampler_state { 6 | Texture = (SampleTexture); 7 | MagFilter = Linear; 8 | MinFilter = Linear; 9 | AddressU = Clamp; 10 | AddressV = Clamp; 11 | }; 12 | 13 | float baseFac = 1.0f; 14 | float parchFac = 1.0f; 15 | float h; 16 | float frame = 0.0f; 17 | 18 | struct PS_INPUT 19 | { 20 | float2 TexCoord : TEXCOORD0; 21 | }; 22 | 23 | #define USE_GRAIN 1 24 | float4 Parchment(PS_INPUT Input) : COLOR0 25 | { 26 | float2 tex = Input.TexCoord; 27 | 28 | #if USE_GRAIN > 0 29 | // Check `fxc.exe SimplifiedGameParchment.fx /O3 /Cc /T fx_2_0` for why the code 30 | // is like this. Most of these calculations will be performed on the preshader, 31 | // effectively lowering this effect from three texel fetches to two, while also 32 | // reducing the amount of arithmetic instructions required. 33 | 34 | // Is the number lower than [0, 1]? 35 | float f1 = (1.0f - step(1.0f, frame)); 36 | 37 | // Is the number between [1, 2]? 38 | float f2 = (1.0f - step(2.0f, frame)) * (1.0 - f1); 39 | 40 | // Is the number between [2, 3]? 41 | float f3 = (1.0f - step(3.0f, frame)) * (1.0 - f2); 42 | 43 | // Select a pair of coordinates based on which group the number was contained within 44 | float4 f_tex = 0.0f; 45 | f_tex += f1 * float4(tex.x, tex.y, 1.0f - tex.x, tex.y); 46 | f_tex += f2 * float4(1.0f - tex.x, tex.y, tex.x, 1.0f - tex.y); 47 | f_tex += f3 * float4(tex.x, 1.0f - tex.y, tex.x, tex.y); 48 | 49 | // Calculate the coefficients 50 | float2 coef = float2(ceil(frame) - frame, frame - floor(frame)); 51 | #if USE_GRAIN == 2 52 | // Fetch the textures 53 | float r = tex2D(t0_sampler, f_tex.xy).r * (coef.x) + 54 | tex2D(t0_sampler, f_tex.zw).r * (coef.y); 55 | #else // USE_GRAIN == 2 56 | float r = tex2D(t0_sampler, f_tex.xy).r * (coef.x + coef.y); 57 | #endif // USE_GRAIN == 2 58 | #else // USE_GRAIN > 0 59 | float2 coef = float2(ceil(frame) - frame, frame - floor(frame)); 60 | // Grain texture average brightness is ~0.55f 61 | float r = 0.55f * (coef.x + coef.y); 62 | #endif // USE_GRAIN > 0 63 | float4 rVal = 0.0f; 64 | 65 | float aFac = 1.0f - (tex.y / h); 66 | 67 | rVal.a = aFac * baseFac; 68 | rVal.a += r * aFac * parchFac; 69 | rVal.rgb = 1.0f; 70 | 71 | return rVal; 72 | } 73 | 74 | technique ParchmentTechnique 75 | { 76 | pass P0 77 | { 78 | PixelShader = compile ps_3_0 Parchment(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /MMLoader.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Runtime.CompilerServices; 6 | using System.Runtime.InteropServices; 7 | using System.Security.Policy; 8 | using System.Threading; 9 | using System; 10 | 11 | public class MMLoader { 12 | public static void Main(string[] args) 13 | { 14 | if (args.Length < 1) 15 | { 16 | Console.Error.WriteLine("Syntax: MMLoader.exe [appBasePath] [-- appArgs...]"); 17 | Console.Error.WriteLine(" asssembly: Assembly to be loaded."); 18 | Console.Error.WriteLine(" appBasePath: Optionally Override AppDomain.CurrentDomain.BaseDirectory."); 19 | Console.Error.WriteLine(" '--': Anything afterwards is passed as arguments to the Assembly."); 20 | System.Environment.Exit(-1); 21 | } 22 | 23 | if (!File.Exists(args[0])) 24 | { 25 | Console.Error.WriteLine($"Unable to find assembly \"{args[0]}\"!"); 26 | System.Environment.Exit(-1); 27 | } 28 | 29 | // Gather Arguments 30 | string assemblyFilePath = Path.GetDirectoryName(Path.GetFullPath(args[0])); 31 | Assembly assembly = Assembly.ReflectionOnlyLoadFrom(args[0]); 32 | AssemblyName assemblyName = assembly.GetName(); 33 | string appBasePath = assemblyFilePath; 34 | string[] appArgs = new string[] { }; 35 | try 36 | { 37 | int i = 1; 38 | if ((string)args.GetValue(i)?.ToString() != "--") 39 | appBasePath = (string)args?.GetValue(i++); 40 | 41 | if ((string)args?.GetValue(i) == "--") 42 | appArgs = args?.Skip(i+1).ToArray(); 43 | } 44 | catch (Exception) { } 45 | 46 | Console.Out.WriteLine($"assemblyName: {assemblyName.Name}"); 47 | Console.Out.WriteLine($"appBasePath: {appBasePath}"); 48 | Console.Out.WriteLine($"appArgs: {{{string.Join(",", appArgs)}}}"); 49 | 50 | // Create domain and load the base assembly 51 | Evidence evidence = new Evidence(AppDomain.CurrentDomain.Evidence); 52 | AppDomain domain = AppDomain.CreateDomain(assemblyName.Name, evidence, appBasePath, appBasePath, false); 53 | // This can cause issue with Assemblies that use the assembly path, such as Logging libraries 54 | //domain.Load(File.ReadAllBytes(Path.GetFullPath(args[0]))); 55 | 56 | // See if we have MONOMODDED versions to load first... 57 | foreach (var asm in assembly.GetReferencedAssemblies()) 58 | { 59 | string asmFileName = $"MONOMODDED_{asm.Name}.dll"; 60 | if (File.Exists(asmFileName)) 61 | { 62 | Console.Out.WriteLine($"Redirecting {asm.Name} to {asmFileName}"); 63 | domain.Load(File.ReadAllBytes(Path.GetFullPath(asmFileName))); 64 | } 65 | } 66 | 67 | // Change current directory and jump into the assembly 68 | Directory.SetCurrentDirectory(appBasePath); 69 | domain.ExecuteAssembly(args[0], appArgs); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Bleed/Bleed.BleedPatches.mm.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework.Graphics; 2 | using Microsoft.Xna.Framework; 3 | using Mono.Cecil.Cil; 4 | using MonoMod.Cil; 5 | 6 | #pragma warning disable CS0626 7 | 8 | namespace TimeGuy 9 | { 10 | class patch_Program 11 | { 12 | // We're going to apply some Runtime Hooks before the init, so let's change the behavior of Program.Main: 13 | extern static void orig_Main(string[] args); 14 | private static void Main(string[] args) 15 | { 16 | // For this, you need to create the MMHOOK_Bleed.dll and reference it during build time. 17 | // Refer to the MonoMod.RuntimeDetour.HookGen.exe documentation. 18 | IL.TimeGuy.Game1.ctor += Hook_Game1_ctor; 19 | 20 | // Give execution back to the game 21 | orig_Main(args); 22 | } 23 | 24 | private static void Hook_Game1_ctor(ILContext il) 25 | { 26 | /* 27 | Consider the following line on Bleed's code: 28 | 29 | > ResolutionHelper.SetResolution( 30 | this.graphics.GraphicsDevice.DisplayMode.Width, 31 | this.graphics.GraphicsDevice.DisplayMode.Height, 32 | true); 33 | 34 | The `This.graphics.GraphicsDevice` member is NULL at this point on certain FNA+SDL2 configs, 35 | so to ensure it won't crash, we're going to use GraphicsAdapter.DefaultAdapter to query the 36 | default adapter's current display mode instead, so we need to patch the IL to do the following: 37 | 38 | > ResolutionHelper.SetResolution( 39 | GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width, 40 | GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width, 41 | true); 42 | */ 43 | ILCursor ilCursor = new ILCursor(il); 44 | if (ilCursor.TryGotoNext(MoveType.Before, 45 | i => i.MatchLdarg(0), 46 | i => i.MatchLdfld(typeof(TimeGuy.Game1), "graphics"), 47 | i => i.MatchCallvirt(typeof(GraphicsDeviceManager), "get_GraphicsDevice"), 48 | i => i.MatchCallvirt(typeof(GraphicsDevice), "get_DisplayMode"), 49 | i => i.MatchCallvirt(typeof(DisplayMode), "get_Width"), 50 | i => i.MatchLdarg(0), 51 | i => i.MatchLdfld(typeof(TimeGuy.Game1), "graphics"), 52 | i => i.MatchCallvirt(typeof(GraphicsDeviceManager), "get_GraphicsDevice"), 53 | i => i.MatchCallvirt(typeof(GraphicsDevice), "get_DisplayMode"), 54 | i => i.MatchCallvirt(typeof(DisplayMode), "get_Height") 55 | )) 56 | { 57 | // Remove the above instructions 58 | ilCursor = ilCursor.RemoveRange(10); 59 | 60 | // Patch our new behavior in. 61 | ilCursor.Emit(OpCodes.Call, typeof(GraphicsAdapter).GetMethod("get_DefaultAdapter")); 62 | ilCursor.Emit(OpCodes.Callvirt, typeof(GraphicsAdapter).GetMethod("get_CurrentDisplayMode")); 63 | ilCursor.Emit(OpCodes.Callvirt, typeof(DisplayMode).GetMethod("get_Width")); 64 | ilCursor.Emit(OpCodes.Call, typeof(GraphicsAdapter).GetMethod("get_DefaultAdapter")); 65 | ilCursor.Emit(OpCodes.Callvirt, typeof(GraphicsAdapter).GetMethod("get_CurrentDisplayMode")); 66 | ilCursor.Emit(OpCodes.Callvirt, typeof(DisplayMode).GetMethod("get_Height")); 67 | } 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /Anodyne/AnodynePatches.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.CompilerServices; 4 | using HarmonyLib; 5 | using Microsoft.Xna.Framework; 6 | using Microsoft.Xna.Framework.Graphics; 7 | using Microsoft.Xna.Framework.Content; 8 | using AnodyneSharp; 9 | using AnodyneSharp.Drawing; 10 | using AnodyneSharp.Registry; 11 | 12 | [assembly: IgnoresAccessChecksTo("AnodyneSharp")] 13 | [assembly: IgnoresAccessChecksTo("Microsoft.Xna.Framework")] 14 | [ModEntryPoint] 15 | public static class AnodynePatches 16 | { 17 | public static void Main() 18 | { 19 | Console.Out.WriteLine("Found AnodynePatches, running..."); 20 | try 21 | { 22 | new Harmony("com.github.johnnyonflame.AnodynePatches").PatchAll(Assembly.GetExecutingAssembly()); 23 | } 24 | catch (Exception e) 25 | { 26 | Console.Out.WriteLine($"Failed: {e.ToString()}"); 27 | throw e; 28 | } 29 | } 30 | 31 | [HarmonyPatch(typeof(SpriteDrawer), nameof(SpriteDrawer.Load))] 32 | public static class SpriteDrawer_Load 33 | { 34 | public static Effect bilinear_sharp; 35 | public static bool Prefix(ContentManager c) 36 | { 37 | bilinear_sharp = c.Load("effects/bilinear-sharp"); 38 | return true; 39 | } 40 | } 41 | 42 | [HarmonyPatch(typeof(SpriteDrawer), nameof(SpriteDrawer.Render))] 43 | public static class SpriteDrawer_Render 44 | { 45 | public static bool Prefix(Effect effect) 46 | { 47 | // We only override the behavior of Resolution.Stretch 48 | if (GlobalState.settings.resolution != Resolution.Stretch) 49 | return true; 50 | 51 | Rectangle r = new Rectangle(); 52 | r.Width = SpriteDrawer._graphicsDevice.PresentationParameters.BackBufferWidth; 53 | r.Height = SpriteDrawer._graphicsDevice.PresentationParameters.BackBufferHeight; 54 | float aspectBackbuffer = (float)r.Width / (float)r.Height; 55 | float aspectGameBuffer = 160.0f / 180.0f; 56 | 57 | if (aspectBackbuffer > aspectGameBuffer) { 58 | r.Width = (int)((float)r.Height * aspectGameBuffer); 59 | } else { 60 | r.Height = (int)((float)r.Width / aspectGameBuffer); 61 | } 62 | 63 | r.X = SpriteDrawer._graphicsDevice.PresentationParameters.BackBufferWidth / 2 - r.Width / 2; 64 | r.Y = SpriteDrawer._graphicsDevice.PresentationParameters.BackBufferHeight / 2 - r.Height / 2; 65 | 66 | SpriteDrawer_Load.bilinear_sharp.Parameters["TexSize"].SetValue(new Vector2(160.0f, 180.0f)); 67 | SpriteDrawer_Load.bilinear_sharp.Parameters["Scale"].SetValue(new Vector2(r.Width / 160.0f, r.Height / 180.0f)); 68 | 69 | SpriteDrawer._graphicsDevice.SetRenderTarget(null); 70 | SpriteDrawer._graphicsDevice.Clear(Color.Black); 71 | SpriteDrawer._spriteBatch.Begin(SpriteSortMode.Texture, BlendState.AlphaBlend, SpriteDrawer.SamplerState, null, null, SpriteDrawer_Load.bilinear_sharp, null); 72 | SpriteDrawer._spriteBatch.Draw(SpriteDrawer._render, r, SpriteDrawer.FullScreenFade); 73 | SpriteDrawer._spriteBatch.End(); 74 | return false; 75 | } 76 | } 77 | } 78 | 79 | namespace System.Runtime.CompilerServices 80 | { 81 | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] 82 | public class IgnoresAccessChecksToAttribute : Attribute 83 | { 84 | public IgnoresAccessChecksToAttribute(string assemblyName) 85 | { 86 | AssemblyName = assemblyName; 87 | } 88 | 89 | public string AssemblyName { get; } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### What is in this repository? 2 | ------------- 3 | 4 | This repository contains game-specific hacks and patches for Mono/FNA games on low power SBCs and handhelds, and the 5 | required tooling to comfortably develop and deploy said patches are provided here. 6 | 7 | - **FMODStudio Fix**: A dead simple FMOD Studio 1.x to FMOD Studio 2.x wrapper. 8 | - **SteamStubs**: A series of 0-stubs to allow DRM-Free games not to crash when Steam's libraries aren't present. 9 | - **EOSSDK-Shipping-Stubs**: A series of 0-stubs to allow games not to crash when Epic Online Services aren't present. 10 | - **ChasmPatches**: Small patches for Chasm fullscreen logic on very small resolutions. 11 | - **CelestePatches**: Extensions to allow loading ASTC4x4 textures and Graphical Settings. 12 | - **SteelAssaultCsPatches**: A series of optimizations targeting low-power devices. 13 | - **PanzerPaladinPatches**: Fixes breakage when running non-Steam, newer FNA builds and does general optimizations. 14 | - **BleedPatches**: Fixes startup failures on certain FNA+SDL2 combinations. 15 | - **TMNTSR-Mods**: Load custom sound assets on TMNT: Shredder's Revenge. 16 | - **TMNTSRPatches**: Provides Performance settings, fixes Deadlocks on OpenGL, fixes file paths on Linux and more. 17 | - **MMLoader**: Force loading applications with MONOMODDED_*.dll redirection, change the AppDomain's BaseDirectory and more. 18 | 19 | ### Usage: 20 | ------------- 21 | 22 | This isn't meant for end-users, but for development reference or contribution. Please refer to your favorite supported packaging system, such as [PortMaster](https://github.com/christianhaitian/PortMaster). 23 | 24 | ### Developing: 25 | ------------- 26 | 27 | What follows is a non-exhaustive guide on how to first build the patch, it won't go into details on how to actually 28 | work with Harmony or how to approach the patch development. If you're looking for those, read [Harmony's documentation](https://harmony.pardeike.net/articles/intro.html), [MonoMod's documentation](https://github.com/MonoMod/MonoMod#readme) and check out [ILSpy](https://github.com/icsharpcode/ILSpy). 29 | 30 | ### Building: 31 | ------------- 32 | 33 | - [Optional] Build mono with a local prefix. 34 | - Clone this repository recursively. 35 | - Build the VisFree compiler (check the project's README for instructions). 36 | - [Optional] Build the stubs and support libraries. 37 | - Copy any necessary dependencies (such as game assemblies) into the `deps` folder, refer to the dot files included. 38 | - Build one or all of the included projects, you have a few optional, configurable parameters: 39 | - `DEPS_FOLDER`: Where the referenced game assemblies are. 40 | - `MONO_PREFIX`: Where your mono install is. 41 | - `COMPILER`: Where your `VisFree.exe` assembly is. 42 | 43 | Example: 44 | 45 | ```bash 46 | export CONFIGURATION=Debug 47 | export COMPILER=VisFree/bin/Debug/VisFree.exe 48 | export MONO_PREFIX=~/mono-6.12.0.122/built 49 | export DEPS_FOLDER=~/.steam/steam/SteamApps/common/Celeste 50 | git clone --recursive https://github.com/JohnnyonFlame/FNAPatches 51 | (cd deps/Harmony && wget https://github.com/pardeike/Harmony/releases/download/v2.2.1.0/Harmony.2.2.1.0.zip && unzip Harmony.2.2.1.0.zip) 52 | (cd deps/MonoMod && wget https://github.com/MonoMod/MonoMod/releases/download/v22.05.01.01/MonoMod-22.05.01.01-net452.zip && unzip MonoMod-22.05.01.01-net452.zip) 53 | (cd deps/FNA && wget https://github.com/JohnnyonFlame/FNAHacks/releases/download/IntPtr-SoundEffect/FNA_3d954a5-aarch64.tar.gz && tar zxvf FNA_3d954a5-aarch64.tar.gz --strip-components=1 --wildcards dlls/\*.dll) 54 | make -C VisFree 55 | make libs 56 | make Celeste 57 | ``` 58 | 59 | ### LICENSE: 60 | ------------- 61 | 62 | The files in this repository are free software licensed under MIT, read the [License File](LICENSE) for reference. 63 | -------------------------------------------------------------------------------- /SaltAndSanctuary/shaders/MainEffectCommon.ifx: -------------------------------------------------------------------------------- 1 | // main.fx 2 | // XNA 3 | 4 | sampler samplerState : register(s0); 5 | sampler lightTex : register(s1); 6 | sampler bayerTex: register(s2); 7 | 8 | // main pass 9 | cbuffer MyBuffer 10 | { 11 | float3 tColor = float3(0.0f, 0.0f, 0.0f); 12 | float3 bColor = float3(0.0f, 0.0f, 0.0f); 13 | float3 burnColor = float3(0.0f, 0.0f, 0.0f); 14 | 15 | //float sat = 1.0f; 16 | float brite = 0.0f; 17 | 18 | float gamma = 0.0f; 19 | 20 | float k = -0.05f; 21 | float kcube = 0.25f; 22 | 23 | float alpha = 1.0f; 24 | float floorVal = 0.2f; 25 | float lightFac = 1.0f; 26 | float lightFacBloom = 1.0f; 27 | 28 | float BloomVignette; 29 | float BaseIntensity; 30 | float BaseSaturation; 31 | float lightThresh = 0.5f; 32 | float2 lightRedBlue = float2(0.5f, 3.0f); 33 | float lightDesat = 1.0f; 34 | float darkBlur = 0.5f; 35 | 36 | float2 Dimensions; 37 | float ditherMinPixel; 38 | }; 39 | 40 | // Helper for modifying the saturation of a color. 41 | float3 AdjustSaturation(float3 color, float saturation) 42 | { 43 | // The constants 0.3, 0.59, and 0.11 are chosen because the 44 | // human eye is more sensitive to green light, and less to blue. 45 | float grey = dot(color, float3(0.3, 0.59, 0.11)); 46 | 47 | return lerp(grey, color, saturation); 48 | } 49 | 50 | void SpriteVertexShader( 51 | inout float4 color : COLOR0, 52 | inout float2 texCoord : TEXCOORD0, 53 | inout float4 position : SV_Position) 54 | { 55 | } 56 | 57 | float4 Main( 58 | float4 color : COLOR0, 59 | float2 texCoord : TEXCOORD0) : SV_Target0 60 | { 61 | float4 col = tex2D(samplerState, texCoord); 62 | 63 | float2 vDif = texCoord - 0.5f; 64 | vDif = vDif * vDif; 65 | 66 | float dif = saturate(vDif.x + vDif.y); 67 | // Stock shader would've applied some dithering based on the noise texture 68 | // we don't do that. 69 | 70 | // Do mainEffect pass 71 | float3 gBurn = col.rgb * burnColor; 72 | col.rgb *= lerp(tColor, bColor, texCoord.y); 73 | col.rgb = lerp(col.rgb, gBurn, dif); 74 | col.rgb = saturate(gamma + col.rgb * brite); 75 | 76 | #if HAS_LIGHTS 77 | // Do light pass 78 | float4 lightCol = tex2D(lightTex, texCoord); 79 | float aFac = lightCol.r; 80 | 81 | // if (aFac > 0.5f) 82 | // aFac = 1.0f - aFac; 83 | // aFac *= 4.0f; 84 | // if (aFac > 1.0f) 85 | // aFac = 1.0f; 86 | // -- OR -- 87 | aFac = saturate(-(abs(aFac-0.5f)-0.5f)*4.0f); 88 | 89 | 90 | float preC = dot(lightCol.rgb, 1.0 / 3.0); 91 | float c = preC + aFac * lightFac; 92 | //if (c >= 0.9f) 93 | // c = 1.0f; 94 | //if (c <= 0.0f) 95 | // c = 0.0f; 96 | // -- OR -- 97 | c = saturate(c); 98 | 99 | // Perform the subtractive blending manually 100 | col.rgb = saturate(col.rgb - (c * alpha)); 101 | #else 102 | float preC = 1.0f; 103 | #endif 104 | 105 | #if HAS_BLUR == 0 106 | float lightBalance = 0.0f; 107 | float difVignette = length(texCoord - 0.5f); 108 | float tBase = BaseIntensity - difVignette * BloomVignette; 109 | float baseSat = BaseSaturation; 110 | 111 | float lightIntensity = 1.0f - preC; 112 | float satFac = 1.0f + (lightIntensity - 1.0f) * lightFacBloom * lightDesat; 113 | 114 | // Original code had some conditionals here, it's not necessary, just limit 115 | // lightFacBloom to not be negative and we're golden 116 | baseSat *= satFac; 117 | tBase -= (1.0f - lightIntensity) * lightFacBloom * darkBlur; 118 | lightBalance = (lightIntensity - lightThresh) * lightFacBloom; 119 | 120 | col.rgb = AdjustSaturation(col.rgb, baseSat) * tBase; 121 | 122 | //if (lightBalance < 0.0f) 123 | // col.b *= (1.0f + lightBalance * -lightBlue); 124 | //if (lightBalance > 0.0f) 125 | // col.r *= (1.0f + lightBalance * lightRed); 126 | // -- OR -- 127 | float direction = step(0.0f, lightBalance); 128 | float2 directions = float2(direction, 1-direction); 129 | col.rb *= (1.0f + lightBalance * lightRedBlue * directions); 130 | 131 | col.rgb = saturate(col.rgb); 132 | #endif 133 | float2 texCoordDither = texCoord * Dimensions; 134 | float ditherValue = tex2D(bayerTex, texCoordDither).a * ditherMinPixel; 135 | 136 | // Do subtractive blend 137 | return float4(col.rgb + ditherValue, 1.0f); 138 | } 139 | 140 | technique MainTechnique 141 | { 142 | pass P0 143 | { 144 | //VertexShader = compile vs_3_0 SpriteVertexShader(); 145 | PixelShader = compile ps_3_0 Main(); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /Shipwreck/ShipwreckPatches.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Reflection; 4 | using HarmonyLib; 5 | using Microsoft.Xna.Framework; 6 | using Microsoft.Xna.Framework.Graphics; 7 | using Microsoft.Xna.Framework.Content; 8 | using Shipwreck; 9 | using Shipwreck.Layers; 10 | 11 | [assembly: IgnoresAccessChecksTo("Shipwreck")] 12 | [assembly: IgnoresAccessChecksTo("Microsoft.Xna.Framework")] 13 | [ModEntryPoint] 14 | public static class ShipwreckPatches 15 | { 16 | private static Effect effect_scaler = null; 17 | 18 | public static void Main() 19 | { 20 | Console.Out.WriteLine("Found ShipwreckPatches, running..."); 21 | try 22 | { 23 | new Harmony("com.github.johnnyonflame.ShipwreckPatches").PatchAll(Assembly.GetExecutingAssembly()); 24 | } 25 | catch (Exception e) 26 | { 27 | Console.Out.WriteLine($"Failed: {e.ToString()}"); 28 | throw e; 29 | } 30 | } 31 | 32 | // Disable this, causes a feedback loop of going fullscreen and back due to SDL2 behavior 33 | [HarmonyPatch(typeof(ShipwreckGame), nameof(ShipwreckGame.Window_ClientSizeChanged))] 34 | internal static class internalShipwreckGame_Window_ClientSizeChanged 35 | { 36 | static bool Prefix(ref ShipwreckGame __instance, object sender, EventArgs e) 37 | { 38 | return false; 39 | } 40 | } 41 | 42 | // We're changing the fullscreen behavior, don't let resize happen. 43 | [HarmonyPatch(typeof(ShipwreckGame), nameof(ShipwreckGame.Resize))] 44 | internal static class ShipwreckGame_Resize 45 | { 46 | static bool Prefix(ref ShipwreckGame __instance) 47 | { 48 | return false; 49 | } 50 | } 51 | 52 | [HarmonyPatch(typeof(ShipwreckGame), nameof(ShipwreckGame.ChangeFullScreen))] 53 | internal static class ShipwreckGame_ChangeFullScreen 54 | { 55 | static bool Prefix(ref ShipwreckGame __instance) 56 | { 57 | // Always fullscreen, use the draw override for this. 58 | __instance._graphics.PreferredBackBufferWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width; 59 | __instance._graphics.PreferredBackBufferHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height; 60 | __instance._graphics.ApplyChanges(); 61 | return false; 62 | } 63 | } 64 | 65 | 66 | [HarmonyPatch] 67 | public class GameBase 68 | { 69 | [HarmonyReversePatch] 70 | [HarmonyPatch(typeof(Game), nameof(Game.Draw))] 71 | public static void Draw(Game instance, GameTime gameTime) => throw new NotImplementedException("stub"); 72 | 73 | } 74 | 75 | [HarmonyPatch(typeof(ShipwreckGame), nameof(ShipwreckGame.Draw))] 76 | internal class ShipwreckGame_Draw 77 | { 78 | 79 | static bool Prefix(ref ShipwreckGame __instance, GameTime gameTime) 80 | { 81 | ContentManager cm = (__instance as Game).Content; 82 | // Windowed mode means we're just using the default behavior 83 | if (!Settings.FullScreen) 84 | return true; 85 | 86 | LayerManager.PreDraw(); 87 | __instance.GraphicsDevice.SetRenderTarget(__instance._gameTarget); 88 | __instance.GraphicsDevice.Clear(__instance.ClearColor); 89 | 90 | __instance.Drawer.Begin(); 91 | LayerManager.Draw(__instance.Drawer); 92 | __instance.Drawer.End(); 93 | GameBase.Draw(__instance, gameTime); 94 | 95 | __instance.GraphicsDevice.SetRenderTarget(null); 96 | __instance.GraphicsDevice.Clear(Color.Black); 97 | 98 | if (ShipwreckPatches.effect_scaler == null) 99 | { 100 | ShipwreckPatches.effect_scaler = cm.Load("shimmerless"); 101 | } 102 | 103 | Rectangle r = new Rectangle(); 104 | r.Width = __instance._graphics.PreferredBackBufferWidth; 105 | r.Height = __instance._graphics.PreferredBackBufferHeight; 106 | float aspectBackbuffer = (float)r.Width / (float)r.Height; 107 | float aspectGameBuffer = __instance._gameTarget.Width / __instance._gameTarget.Height; 108 | 109 | if (aspectBackbuffer > aspectGameBuffer) { 110 | r.Width = (int)((float)r.Height * aspectGameBuffer); 111 | } else { 112 | r.Height = (int)((float)r.Width / aspectGameBuffer); 113 | } 114 | 115 | r.X = __instance._graphics.PreferredBackBufferWidth / 2 - r.Width / 2; 116 | r.Y = __instance._graphics.PreferredBackBufferHeight / 2 - r.Height / 2; 117 | 118 | ShipwreckPatches.effect_scaler.Parameters["TexSize"].SetValue(new Vector2(__instance._gameTarget.Width, __instance._gameTarget.Height)); 119 | ShipwreckPatches.effect_scaler.Parameters["InputSize"].SetValue(new Vector2(__instance._gameTarget.Width, __instance._gameTarget.Height)); 120 | ShipwreckPatches.effect_scaler.Parameters["OutputSize"].SetValue(new Vector2(r.Width, r.Height)); 121 | 122 | __instance.Drawer.SpriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque, SamplerState.PointClamp, null, null, effect_scaler); 123 | __instance.Drawer.SpriteBatch.Draw(__instance._gameTarget, r, Color.White); 124 | __instance.Drawer.SpriteBatch.End(); 125 | 126 | return false; 127 | } 128 | } 129 | } 130 | 131 | namespace System.Runtime.CompilerServices 132 | { 133 | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] 134 | public class IgnoresAccessChecksToAttribute : Attribute 135 | { 136 | public IgnoresAccessChecksToAttribute(string assemblyName) 137 | { 138 | AssemblyName = assemblyName; 139 | } 140 | 141 | public string AssemblyName { get; } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /PanzerPaladin/ParisEngine.PanzerPaladin.mm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.Threading; 5 | using MonoMod; 6 | using Paris.Engine.Context; 7 | using Paris.System.Helper; 8 | using Paris.Engine.Graphics; 9 | 10 | #pragma warning disable 1591 11 | #pragma warning disable CS0649 12 | #pragma warning disable CS0626 13 | 14 | namespace Paris.System.Helper { 15 | static class patch_PathManager 16 | { 17 | public extern static string orig_NormalizePath(string path); 18 | public static string NormalizePath(string path) 19 | { 20 | // Normalize paths by replacing backslashes. 21 | // This fixes failures to find assets on Linux when Steam isn't running. 22 | return path.Replace("\\", "/"); 23 | } 24 | } 25 | } 26 | 27 | namespace Paris.Engine { 28 | class patch_GameAnalytics : GameAnalytics 29 | { 30 | public extern void orig_Init(); 31 | new public void Init() 32 | { 33 | // Disable GameAnalytics 34 | } 35 | } 36 | 37 | class patch_ParisContentManager: ParisContentManager 38 | { 39 | public patch_ParisContentManager(IServiceProvider i_service, string i_sRoot) : base(i_service, i_sRoot) 40 | { 41 | } 42 | 43 | // Can't call double-inherited base functions... so we need this to call the original ContentManager Load method 44 | [MonoModLinkTo("Microsoft.Xna.Framework.Content.ContentManager", "Load")] 45 | [MonoModForceCall] 46 | [MonoModRemove] 47 | public extern T ContentManager_Load(string i_assetName); 48 | 49 | public extern T orig_Load(string i_assetName); 50 | public override T Load(string i_assetName) 51 | { 52 | bool isLockAcquired = false; 53 | T result; 54 | try 55 | { 56 | // We're changing how this lock works: 57 | // only the main thread can upload textures (see: ForceToMainThread in FNA3D_Driver_OpenGL.c), 58 | // if we let the entire method lock, then we basically get deadlocked by the main thread, or starve it 59 | // from this lock, since we'll be waiting for the main thread to consume the requests for texture uploads. 60 | // This change fixes deadlocks on preloading caused by using newer FNA builds. 61 | this.EnsureLock(); 62 | isLockAcquired = true; 63 | string text = i_assetName.ToLower(); 64 | if (typeof(T) == typeof(ReloadableTexture)) 65 | { 66 | text += "Reloadable"; 67 | } 68 | ParisContentManager.ObjectCacheItem objectCacheItem = null; 69 | if (!ContextManager.Singleton.Serializing && this.mloadedAssets.TryGetValue(text, out objectCacheItem)) 70 | { 71 | T t = (T)((object)objectCacheItem.Obj); 72 | if (t != null) 73 | { 74 | return t; 75 | } 76 | this.mloadedAssets.TryRemove(text, out objectCacheItem); 77 | } 78 | Stopwatch stopwatch = Stopwatch.StartNew(); 79 | i_assetName = PathManager.GetPlatformFilename(i_assetName); 80 | string text2 = i_assetName.Substring(i_assetName.LastIndexOf('\\') + 1); 81 | T t2 = default(T); 82 | if (!ContextManager.Singleton.Serializing) 83 | { 84 | objectCacheItem = new ParisContentManager.ObjectCacheItem(); 85 | objectCacheItem.Name = text2; 86 | ParisContentManager.ObjectCacheItem objectCacheItem2 = this.mLoadingStack.LastOrDefault(); 87 | if (objectCacheItem2 != null) 88 | { 89 | objectCacheItem2.Children.Add(new WeakReference(objectCacheItem)); 90 | } 91 | this.mLoadingStack.Add(objectCacheItem); 92 | objectCacheItem.Parent = new WeakReference(objectCacheItem2); 93 | } 94 | 95 | Monitor.Exit(ParisContentManager.mLock); 96 | isLockAcquired = false; 97 | 98 | if (t2 == null) 99 | { 100 | t2 = this.LoadFromDLC(i_assetName); 101 | } 102 | if (t2 == null && typeof(T) == typeof(ReloadableTexture)) 103 | { 104 | t2 = (T)((object)new ReloadableTexture(i_assetName)); 105 | } 106 | if (t2 == null) 107 | { 108 | t2 = (T)((object)this.LoadParisBinary(i_assetName)); 109 | } 110 | if (t2 == null) 111 | { 112 | t2 = this.LoadRawData(i_assetName); 113 | } 114 | if (t2 == null) 115 | { 116 | t2 = (T)((object)this.LoadCompressedJson(i_assetName)); 117 | } 118 | if (t2 == null) 119 | { 120 | t2 = ContentManager_Load(i_assetName); 121 | } 122 | 123 | this.EnsureLock(); 124 | isLockAcquired = true; 125 | if (objectCacheItem != null && t2 != null && typeof(T) != typeof(GameMenu)) 126 | { 127 | objectCacheItem.Obj = t2; 128 | objectCacheItem.Children = ( 129 | from x in objectCacheItem.Children 130 | where x.IsAlive 131 | select x).ToList(); 132 | this.mloadedAssets.TryAdd(text, objectCacheItem); 133 | objectCacheItem.Time = (double)stopwatch.ElapsedMilliseconds; 134 | } 135 | result = t2; 136 | } 137 | finally 138 | { 139 | if (isLockAcquired) 140 | Monitor.Exit(ParisContentManager.mLock); 141 | } 142 | return result; 143 | } 144 | } 145 | } 146 | 147 | namespace Paris.Engine.Audio 148 | { 149 | class patch_Song: Song 150 | { 151 | public patch_Song(string filename, string introFilename): base(filename, introFilename) 152 | { 153 | 154 | } 155 | 156 | private extern void orig_LoadMainSong(); 157 | private void LoadMainSong() 158 | { 159 | // First call the original function 160 | orig_LoadMainSong(); 161 | 162 | // Now let's make the decoded buffer size per request a bit more forgiving... 163 | // 1000.0ms is too much and causes periodic stuttering 164 | this.bufferSize = soundEffect.GetSampleSizeInBytes(TimeSpan.FromMilliseconds(33.2)); 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /TMNTSR-Mods/ParisEngine.TMNTSRMods.mm.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework.Audio; 2 | using Microsoft.Xna.Framework; 3 | using Mono.Cecil; 4 | using MonoMod.InlineRT; 5 | using Paris.Engine.Context; 6 | using Paris.Engine.Data; 7 | using System.Collections.Generic; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Runtime.CompilerServices; 11 | using System; 12 | 13 | #pragma warning disable 1591 14 | #pragma warning disable CS0649 15 | #pragma warning disable CS0626 16 | 17 | namespace MonoMod 18 | { 19 | public static class MonoModRules 20 | { 21 | static MonoModRules() 22 | { 23 | // Forcefully disable the access checks by creating the following: 24 | // [assembly: IgnoresAccessChecksTo("ParisEngine")] 25 | // [assembly: IgnoresAccessChecksTo("TMNT")] 26 | // [assembly: IgnoresAccessChecksTo("FNA")] 27 | 28 | CustomAttribute attribute; 29 | var mod = MonoModRule.Modder.Module; 30 | var ctor = typeof(IgnoresAccessChecksToAttribute).GetConstructor(new Type[] { typeof(string) }); 31 | var attribConstructor = mod.ImportReference(ctor); 32 | 33 | attribute = new CustomAttribute(attribConstructor); 34 | attribute.ConstructorArguments.Add(new CustomAttributeArgument(mod.ImportReference(typeof(string)), (object)"ParisEngine")); 35 | mod.Assembly.CustomAttributes.Add(attribute); 36 | 37 | attribute = new CustomAttribute(attribConstructor); 38 | attribute.ConstructorArguments.Add(new CustomAttributeArgument(mod.ImportReference(typeof(string)), (object)"TMNT")); 39 | mod.Assembly.CustomAttributes.Add(attribute); 40 | 41 | attribute = new CustomAttribute(attribConstructor); 42 | attribute.ConstructorArguments.Add(new CustomAttributeArgument(mod.ImportReference(typeof(string)), (object)"FNA")); 43 | mod.Assembly.CustomAttributes.Add(attribute); 44 | } 45 | } 46 | } 47 | 48 | namespace TMNTMods 49 | { 50 | static class Mods 51 | { 52 | private static string[] _SearchPath = null; 53 | public static string[] SearchPath { 54 | get { 55 | if (_SearchPath == null) 56 | { 57 | string searchPathEnv = Environment.GetEnvironmentVariable("MONOMOD_MODS"); 58 | if (searchPathEnv != null) 59 | _SearchPath = searchPathEnv.Split(new char [] { Path.PathSeparator }); 60 | else 61 | { 62 | string searchPathGlob = Path.Combine(new string[] { TitleLocation.Path, "Mods" }); 63 | _SearchPath = Directory.GetDirectories(searchPathGlob, "*"); 64 | } 65 | } 66 | 67 | return _SearchPath; 68 | } 69 | set { _SearchPath = value; } 70 | } 71 | } 72 | } 73 | 74 | namespace Paris.Engine 75 | { 76 | class patch_ParisContentManager: ParisContentManager 77 | { 78 | patch_ParisContentManager(IServiceProvider i_service, string i_sRoot) : base(i_service, i_sRoot) { } 79 | public extern T orig_Load(string i_assetName); 80 | public override T Load(string i_assetName) 81 | { 82 | foreach (var path in TMNTMods.Mods.SearchPath) 83 | { 84 | try 85 | { 86 | return orig_Load(Path.Combine(new string[] { path, i_assetName })); 87 | } 88 | catch (Exception) { } 89 | } 90 | 91 | return orig_Load(i_assetName); 92 | } 93 | } 94 | } 95 | 96 | namespace Paris.Engine.Audio 97 | { 98 | class patch_AudioManager: AudioManager 99 | { 100 | new unsafe public virtual void Init() 101 | { 102 | // Just standard AudioManager.Init()... 103 | if (this._initialized || AudioManager.NoSound) 104 | return; 105 | 106 | this._initialized = true; 107 | 108 | // Load metadata... 109 | SoundSettings soundSettings = ContextManager.Singleton.LoadContent(EngineSettings.Singleton.SoundsSettings, true, false); 110 | float num = soundSettings.Settings.MasterVolume / 100f; 111 | this._masterMusicVolume = num * (soundSettings.Settings.MasterMusicVolume / 100f); 112 | this._masterSFXVolume = num * (soundSettings.Settings.MasterSoundVolume / 100f); 113 | this.CurrentMusicName = ""; 114 | foreach (SoundSettings.SoundChannel soundChannel in soundSettings.Channels) 115 | { 116 | this._soundChannels.Add(new SFXChannel(soundChannel.Name, soundChannel.MaxSounds, soundChannel.Volume, soundChannel.PlayType)); 117 | } 118 | string path = string.Empty; 119 | if (ParisContentManager.RawDataMode) 120 | { 121 | path = Path.Combine(ContextManager.Singleton.GlobalContentManager.RootDirectory, "Audio", "SFXPack.pbn"); 122 | } 123 | else 124 | { 125 | path = ContextManager.Singleton.GlobalContentManager.GetFilePath(Path.Combine("Audio", "SFXPack")) + ".pbn"; 126 | } 127 | if (File.Exists(path) && !this.EditorMode) 128 | { 129 | using (FileStream bankStream = File.OpenRead(path)) 130 | using (BinaryReader bankReader = new BinaryReader(bankStream)) 131 | { 132 | for (int i = 0; i < soundSettings.Sounds.Count; i++) 133 | { 134 | SoundSettings.SoundInfo soundInfo = soundSettings.Sounds[i]; 135 | Console.Out.WriteLine($"Loading Tim Allen for {soundInfo.SoundID}"); 136 | try 137 | { 138 | int sndSize = bankReader.ReadInt32(); 139 | if (sndSize < 0) 140 | throw new Exception("sndSize < 0"); 141 | 142 | var list = 143 | from channelName in soundInfo.PlaybackChannels 144 | from sfxChannel in this._soundChannels where sfxChannel.Name == channelName 145 | select sfxChannel; 146 | 147 | byte[] data = null; 148 | foreach (string searchPath in TMNTMods.Mods.SearchPath) 149 | { 150 | string wavPath = Path.Combine(new string[] { searchPath, "Audio", soundInfo.SoundID} ) + ".wav"; 151 | if (File.Exists(wavPath)) 152 | { 153 | data = File.ReadAllBytes(wavPath); 154 | bankStream.Seek(sndSize, SeekOrigin.Current); 155 | } 156 | } 157 | 158 | if (data == null) 159 | data = bankReader.ReadBytes(sndSize); 160 | 161 | this._sounds.Add(new SFX( 162 | soundInfo.SoundID, 163 | data, 164 | soundInfo.Volume / 100f, 165 | soundInfo.PoolSize, 166 | soundInfo.LoopType, 167 | Enumerable.ToArray(list), 168 | soundInfo.CutOffType, 169 | new Types.Range(soundInfo.PitchMin, soundInfo.PitchMax), 170 | soundInfo.IsVO, 171 | soundInfo.Cooldown)); 172 | } 173 | catch (Exception ex) 174 | { 175 | this._sounds.Clear(); 176 | throw new Exception(ex.ToString()); 177 | } 178 | } 179 | } 180 | } 181 | if (this._voiceChat != null) 182 | { 183 | this._voiceChat.Play(); 184 | } 185 | for (int k = 0; k < this._voiceBuffer.Length; k++) 186 | { 187 | this._voiceBuffer[k] = 0; 188 | } 189 | for (int l = 0; l < EngineSettings.Singleton.MaxPlayers; l++) 190 | { 191 | this._voicePlayerMuted[l] = false; 192 | } 193 | 194 | this._initialized = true; 195 | } 196 | } 197 | } 198 | 199 | namespace System.Runtime.CompilerServices 200 | { 201 | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] 202 | public class IgnoresAccessChecksToAttribute : Attribute 203 | { 204 | public IgnoresAccessChecksToAttribute(string assemblyName) 205 | { 206 | AssemblyName = assemblyName; 207 | } 208 | 209 | public string AssemblyName { get; } 210 | } 211 | } -------------------------------------------------------------------------------- /Chasm/ChasmPatches.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Collections.Generic; 4 | using System.Runtime.CompilerServices; 5 | using System.Reflection; 6 | using System.Reflection.Emit; 7 | using HarmonyLib; 8 | using Microsoft.Xna.Framework; 9 | using Microsoft.Xna.Framework.Graphics; 10 | using Chasm; 11 | using Chasm.Platforms; 12 | using Chasm.Graphics; 13 | using Chasm.Menus; 14 | 15 | 16 | [assembly: IgnoresAccessChecksTo("Chasm")] 17 | [assembly: IgnoresAccessChecksTo("Microsoft.Xna.Framework")] 18 | [ModEntryPoint] 19 | public static class ChasmPatches 20 | { 21 | public static void Main() 22 | { 23 | Console.Out.WriteLine("Found ChasmPatches, running..."); 24 | 25 | try 26 | { 27 | new Harmony("com.github.johnnyonflame.ChasmPatches").PatchAll(Assembly.GetExecutingAssembly()); 28 | } 29 | catch (Exception e) 30 | { 31 | Console.Out.WriteLine($"Failed: {e.ToString()}"); 32 | throw e; 33 | } 34 | } 35 | 36 | [HarmonyPatch(typeof(ChasmGame), nameof(ChasmGame.LoadContent))] 37 | static class ChasmGame_LoadContent 38 | { 39 | public static bool Prefix(ref ChasmGame __instance) 40 | { 41 | // lambda can't capture ref... 42 | ChasmGame game = __instance; 43 | 44 | // We want to change the video mode whenever the resolution has changed! 45 | __instance.Window.ClientSizeChanged += (o, a) => game.SetFullScreen(); 46 | return true; 47 | } 48 | } 49 | 50 | [HarmonyPatch(typeof(ChasmGame), nameof(ChasmGame.CalculateUpscaleResolution))] 51 | static class ChasmGame_CalculateUpscaleResolution 52 | { 53 | public static bool Prefix(ref ChasmGame __instance, ref Point __result) 54 | { 55 | int x, y; 56 | //Point result = new Point((int)((float)GlobalShare.NativeResolution.X * GlobalShare.Scaling), (int)((float)GlobalShare.NativeResolution.Y * GlobalShare.Scaling)); 57 | Point result = new Point(GlobalShare.DeviceResolution.X, GlobalShare.DeviceResolution.Y); 58 | if (GlobalShare.FitToScreen) 59 | { 60 | __result = result; 61 | return false; 62 | } 63 | 64 | double scaleX = (double)GlobalShare.DeviceResolution.X / (double)GlobalShare.NativeResolution.X; 65 | double scaleY = (double)GlobalShare.DeviceResolution.Y / (double)GlobalShare.NativeResolution.Y; 66 | double scale = Math.Min(scaleX, scaleY); 67 | if (scaleX < scaleY) 68 | { 69 | x = GlobalShare.DeviceResolution.X; 70 | y = (int)Math.Round((double)GlobalShare.NativeResolution.Y * scale, MidpointRounding.AwayFromZero); 71 | } 72 | else 73 | { 74 | x = (int)Math.Round((double)GlobalShare.NativeResolution.X * scale, MidpointRounding.AwayFromZero); 75 | y = GlobalShare.DeviceResolution.Y; 76 | } 77 | __result = new Point(x, y); 78 | return false; 79 | } 80 | } 81 | 82 | // Fullscreen/Windowed logic is inverted 83 | // We remove the limitations that led low-resolution devices to not scale to the whole screen 84 | [HarmonyPatch(typeof(ChasmGame), nameof(ChasmGame.SetFullScreen))] 85 | static class ChasmGame_SetFullScreen 86 | { 87 | public static bool Prefix(ref ChasmGame __instance) 88 | { 89 | __instance._settingFullscreen = true; 90 | int num = ConfigReader.ReadInt("Scaling"); 91 | DisplayMode displayMode = GlobalShare.GraphicsDevice.DisplayMode; 92 | if (GlobalShare.Fullscreen) 93 | { 94 | displayMode = GraphicsAdapter.Adapters[GlobalShare.FullscreenIndex - 1].CurrentDisplayMode; 95 | } 96 | 97 | GlobalShare.DeviceResolution = new Point(displayMode.Width, displayMode.Height); 98 | 99 | Point deviceResolution = GlobalShare.DeviceResolution; 100 | if (!GlobalShare.FitToScreen) 101 | { 102 | double val = (double)deviceResolution.X / (double)GlobalShare.NativeResolution.X; 103 | double val2 = (double)deviceResolution.Y / (double)GlobalShare.NativeResolution.Y; 104 | int num2 = (int)Math.Min(val, val2); 105 | GlobalShare.Scaling = (float)num2; 106 | } 107 | else 108 | { 109 | GlobalShare.Scaling = (float)num; 110 | } 111 | GlobalShare.UpscaleResolution = __instance.CalculateUpscaleResolution(); 112 | GlobalShare.UpscaleRenderTarget = new RenderTarget2D(__instance.GraphicsDevice, GlobalShare.UpscaleResolution.X, GlobalShare.UpscaleResolution.Y); 113 | 114 | __instance._graphics.IsFullScreen = GlobalShare.Fullscreen; 115 | __instance._graphics.PreferredBackBufferWidth = deviceResolution.X; 116 | __instance._graphics.PreferredBackBufferHeight = deviceResolution.Y; 117 | __instance._graphics.ApplyChanges(); 118 | __instance._settingFullscreen = false; 119 | __instance.SetShaderResolution(); 120 | 121 | // Don't fall-through, we're overriding this method's behavior 122 | return false; 123 | } 124 | } 125 | 126 | [HarmonyPatch(typeof(ChasmGame), nameof(ChasmGame.Draw))] 127 | static class ChasmGame_Draw 128 | { 129 | static bool keepFs = false; 130 | static bool keepFit = false; 131 | //protected override void Draw(GameTime gameTime) 132 | 133 | // Fake the video mode so that we can always have the bilinear path, otherwise 134 | // text is unreadable 135 | private static bool Prefix(ChasmGame __instance) 136 | { 137 | keepFs = GlobalShare.Fullscreen; 138 | keepFit = GlobalShare.FitToScreen; 139 | GlobalShare.Fullscreen = true; 140 | GlobalShare.FitToScreen = true; 141 | return true; 142 | } 143 | 144 | private static void Postfix(ChasmGame __instance) 145 | { 146 | GlobalShare.Fullscreen = keepFs; 147 | GlobalShare.FitToScreen = keepFit; 148 | } 149 | } 150 | 151 | [HarmonyPatch(typeof(ChasmGame), nameof(ChasmGame.StartupInit))] 152 | static class ChasmGame_StartupInit 153 | { 154 | static IEnumerable Transpiler(IEnumerable instructions) 155 | { 156 | Console.WriteLine($"Patching ChasmGame at ChasmGame:StartupInit..."); 157 | var codes = new List(instructions); 158 | for (int i = 0; i < codes.Count-2; i++) 159 | { 160 | 161 | if ( 162 | codes[i].opcode == OpCodes.Newobj && codes[i].operand.ToString().Contains("Void .ctor") && // 2 000A newobj instance void Chasm.Platforms.Steam.SteamPlatform::.ctor() 163 | codes[i+1].opcode == OpCodes.Stsfld && codes[i+1].operand.ToString().Contains("Platform") // 3 000F stsfld class Chasm.Platforms.IPlatform Chasm.ChasmGame::Platform 164 | ) 165 | { 166 | codes[i] = new CodeInstruction(OpCodes.Newobj, AccessTools.Constructor(typeof(PCPlatform), new Type[] {})); 167 | Console.WriteLine($"Patched ChasmGame at ChasmGame:StartupInit+{i}"); 168 | break; 169 | } 170 | } 171 | 172 | return codes.AsEnumerable(); 173 | } 174 | } 175 | 176 | [HarmonyPatch(typeof(AcheivementMessage), nameof(AcheivementMessage.Draw))] 177 | static class AcheivementMessage_Draw 178 | { 179 | private static bool Prefix(AcheivementMessage __instance) 180 | { 181 | // Don't draw achievements... 182 | return false; 183 | } 184 | } 185 | 186 | [HarmonyPatch(typeof(MainMenu), nameof(MainMenu.Show))] 187 | static class MainMenu_Show 188 | { 189 | static IEnumerable Transpiler(IEnumerable instructions) 190 | { 191 | Console.WriteLine($"Patching ChasmGame at MainMenu:Show..."); 192 | var codes = new List(instructions); 193 | for (int i = 0; i < codes.Count-3; i++) 194 | { 195 | // Disable GOG_Galaxy query, we're logged in to nothing. 196 | if ( 197 | codes[i].opcode == OpCodes.Ldsfld && codes[i].operand.ToString().Contains("m_instance") && // 282 0432 ldsfld class Chasm.Platforms.GOG.GogPlatform Chasm.Platforms.GOG.GogPlatform::m_instance 198 | codes[i+1].opcode == OpCodes.Ldc_I4_0 && // 283 0437 ldc.i4.0 199 | codes[i+2].opcode == OpCodes.Callvirt && // 284 0438 callvirt instance bool Chasm.Platforms.GOG.GogPlatform::IsLoggedOn(bool) 200 | codes[i+3].opcode == OpCodes.Brfalse_S // 285 043D brfalse.s 301 (0477) ldarg.0 201 | ) 202 | { 203 | List newCodes = new List(); 204 | var br = (System.Reflection.Emit.Label)codes[i+3].operand; 205 | newCodes.Add(new CodeInstruction(OpCodes.Br_S, br)); 206 | codes.RemoveRange(i, 4); 207 | codes.InsertRange(i, newCodes); 208 | Console.WriteLine($"Patched ChasmGame at MainMenu:Show+{i}"); 209 | break; 210 | } 211 | } 212 | 213 | return codes.AsEnumerable(); 214 | } 215 | } 216 | } 217 | 218 | namespace System.Runtime.CompilerServices 219 | { 220 | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] 221 | public class IgnoresAccessChecksToAttribute : Attribute 222 | { 223 | public IgnoresAccessChecksToAttribute(string assemblyName) 224 | { 225 | AssemblyName = assemblyName; 226 | } 227 | 228 | public string AssemblyName { get; } 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /TMNTSR/TMNT.TMNTSRPatches.mm.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework.Audio; 2 | using Microsoft.Xna.Framework; 3 | using MonoMod; 4 | using Paris.Engine.Audio; 5 | using Paris.Engine.Context; 6 | using Paris.Engine.Cutscenes; 7 | using Paris.Engine.Graphics; 8 | using Paris.Engine.Menu.Control; 9 | using Paris.Engine.Save; 10 | using Paris.Engine.Scene; 11 | using Paris.Engine.System.Networking; 12 | using Paris.Engine; 13 | using Paris.Game.Data; 14 | using Paris.Game.System.Pathfinding; 15 | using Paris.Game; 16 | using Paris.System.Input; 17 | using System.IO; 18 | using System.Linq; 19 | using System.Reflection; 20 | using System; 21 | 22 | #pragma warning disable 1591 23 | #pragma warning disable CS0649 24 | #pragma warning disable CS0626 25 | 26 | namespace Paris.Game.Menu 27 | { 28 | public class patch_LogoScreen: LogoScreen 29 | { 30 | public override void Tick(float deltaTime) 31 | { 32 | GameContextManager singleton = GameContextManager.Singleton; 33 | if (singleton.Fadestate == FadeState.FadedIn) 34 | { 35 | if (!_startedSFX && _sfx != null) 36 | { 37 | _sfx.Play(); 38 | _sfx.Volume = AudioManager.Singleton.FinalSoundVolume; 39 | _startedSFX = true; 40 | } 41 | _animatedLogo.Tick(deltaTime); 42 | if (_logoTimer > 0f) 43 | { 44 | _logoTimer -= deltaTime; 45 | } 46 | ulong handle = 0uL; 47 | bool flag = InputManagerBase.Singleton.SomeonePressedSomething(true, false, out handle) != ParisInputType.None; 48 | if (_logoTimer <= 0f || (!SaveSystemBase.Singleton.Loading && flag)) 49 | { 50 | if (_sfx != null && _sfx.State == SoundState.Playing) 51 | { 52 | _sfx.Stop(); 53 | } 54 | _skippedLast = flag; 55 | if (HasNextLogo) 56 | { 57 | singleton.RequestFadeOut(_skippedLast ? 0.1f : 0.5f, _clearColor); 58 | return; 59 | } 60 | if (CheatManager.Singleton.IsActive(16)) 61 | { 62 | singleton.SwitchToContext(GameSettings.Singleton.GameContextMainMenu, _skippedLast ? 0.1f : 0.5f, _clearColor); 63 | return; 64 | } 65 | VideoContext.VideoPath = "Videos\\PATCH_TMNT_GameIntro"; 66 | VideoContext.NextContext = GameSettings.Singleton.GameContextMainMenu; 67 | singleton.SwitchToContext(typeof(VideoContext).AssemblyQualifiedName, _skippedLast ? 0.1f : 0.5f, _clearColor); 68 | } 69 | } 70 | else if (singleton.Fadestate == FadeState.FadedOut) 71 | { 72 | LoadNextLogo(); 73 | singleton.RequestFadeIn(_skippedLast ? 0.3f : 0.5f); 74 | _skippedLast = false; 75 | } 76 | } 77 | } 78 | 79 | public class patch_Options: Options 80 | { 81 | public static int shadowPreset = 1; 82 | public static int reducedRats = 1; 83 | private Control.OptionSelectionItem _shadowController; 84 | private Control.OptionSelectionItem _reducedRatsController; 85 | static private Type SaveSystem_t; 86 | static private MethodInfo SaveSystem_GetModConfigPath_m; 87 | 88 | private static string GetExtraConfigPath() 89 | { 90 | if (SaveSystem_t == null) 91 | { 92 | SaveSystem_t = Type.GetType("Paris.Engine.Save.SaveSystem, ParisEngine", true); 93 | SaveSystem_GetModConfigPath_m = SaveSystem_t.GetMethod("GetModConfigPath", BindingFlags.Static | BindingFlags.Public); 94 | } 95 | 96 | return (string)SaveSystem_GetModConfigPath_m.Invoke(null, new object[] {}); 97 | } 98 | 99 | public static void LoadModConfig() 100 | { 101 | string cfg_path = GetExtraConfigPath(); 102 | try 103 | { 104 | using (FileStream cfg = new FileStream(cfg_path, FileMode.Open)) 105 | using (BinaryReader cfg_reader = new BinaryReader(cfg)) 106 | { 107 | shadowPreset = cfg_reader.ReadInt32(); 108 | reducedRats = cfg_reader.ReadInt32(); 109 | } 110 | } 111 | catch(Exception e) 112 | { 113 | Console.Error.WriteLine(e.ToString()); 114 | } 115 | } 116 | 117 | public static void SaveModConfig() 118 | { 119 | try 120 | { 121 | using (FileStream cfg = new FileStream(GetExtraConfigPath(), FileMode.Create)) 122 | using (BinaryWriter cfg_writer = new BinaryWriter(cfg)) 123 | { 124 | cfg_writer.Write(shadowPreset); 125 | cfg_writer.Write(reducedRats); 126 | } 127 | } 128 | catch(Exception e) 129 | { 130 | Console.Error.WriteLine(e.ToString()); 131 | } 132 | } 133 | 134 | private Control.OptionSelectionItem InsertOptionsItemString(SelectionMenuControl selectionControl, string option) 135 | { 136 | int count = selectionControl.Items.Count; 137 | return (Control.OptionSelectionItem)selectionControl.Add(new Control.OptionSelectionItem(option, 340) 138 | { 139 | UnselectedFont = MainMenu.MENU_UNSELECTED_FONT, 140 | DisabledFont = MainMenu.MENU_DISABLED_FONT, 141 | ArrowLeftTexture = this._arrowLeftTexture, 142 | ArrowRightTexture = this._arrowRightTexture, 143 | ArrowLeftColor = GameColors.ArrowLeftColor, 144 | ArrowRightColor = GameColors.ArrowRightColor, 145 | OutlineSelectedColor = Color.Black, 146 | OutlineUnselectedColor = Color.Black, 147 | OutlineDisabledColor = Color.Black 148 | }, 0); 149 | } 150 | 151 | extern void orig_SetupOptions(Options.MenuSchemes menuScheme, SelectionMenuControl selectionControl); 152 | new public void SetupOptions(Options.MenuSchemes menuScheme, SelectionMenuControl selectionControl) 153 | { 154 | orig_SetupOptions(menuScheme, selectionControl); 155 | #if false 156 | // Disabled, does very little. 157 | this._shadowController = InsertOptionsItemString(selectionControl, "Shadows"); 158 | this._shadowController.AddOption("Off"); // 0 159 | this._shadowController.AddOption("Low"); // 1 160 | this._shadowController.AddOption("Normal"); // 2 161 | this._shadowController.Value = patch_Options.shadowPreset; 162 | this._shadowController.OptionChangedCallback = new SelectionItem.SelectionCallback((sel) => { 163 | patch_Options.shadowPreset = ((Control.OptionSelectionItem)sel).Value; 164 | SaveModConfig(); 165 | this.HaveChanged = true; 166 | }); 167 | #endif 168 | this._reducedRatsController = InsertOptionsItemString(selectionControl, "Rat Count"); 169 | this._reducedRatsController.AddOption("Normal"); 170 | this._reducedRatsController.AddOption("Low"); 171 | this._reducedRatsController.AddOption("Very Low"); 172 | this._reducedRatsController.Value = patch_Options.reducedRats; 173 | this._reducedRatsController.OptionChangedCallback = new SelectionItem.SelectionCallback((sel) => { 174 | patch_Options.reducedRats = ((Control.OptionSelectionItem)sel).Value; 175 | SaveModConfig(); 176 | this.HaveChanged = true; 177 | }); 178 | } 179 | } 180 | } 181 | 182 | namespace Paris 183 | { 184 | class patch_Paris: Paris 185 | { 186 | protected override void Uninit() 187 | { 188 | //base.Uninit(); 189 | GameInfo.Singleton.Uninit(); 190 | NetworkMessageSystem.Singleton.Uninit(); 191 | this.UninitLeaderboards(); 192 | this.UninitPlatform(); 193 | if (PathfindingManager.Singleton != null) 194 | PathfindingManager.Singleton.Uninit(); 195 | } 196 | 197 | // Ensure the dispose method doesn't hang the application... 198 | protected override void Dispose(bool disposing) 199 | { 200 | if (!base._disposed) 201 | { 202 | base._disposed = true; 203 | if (AudioManager.Singleton != null) 204 | { 205 | AudioManager.Singleton.Dispose(); 206 | } 207 | if (ContextManager.Singleton != null) 208 | { 209 | ContextManager.Singleton.Uninit(); 210 | } 211 | if (NetworkManagerBase.Singleton != null) 212 | { 213 | NetworkManagerBase.Singleton.Dispose(); 214 | } 215 | } 216 | } 217 | 218 | // Don't tick the Leaderboard, we no longer have it. 219 | private void UpdatePlatform(float deltaTime) 220 | { 221 | base.InitControllers(); 222 | base.UpdateFullScreenToggle(); 223 | } 224 | } 225 | 226 | static class patch_ParamManager 227 | { 228 | public extern static ParamFlag orig_ParseParams(String[] args); 229 | public static ParamFlag ParseParams(String[] args) 230 | { 231 | Game.Menu.patch_Options.LoadModConfig(); 232 | Console.Out.WriteLine("Params Set:"); 233 | ParamFlag flags = orig_ParseParams(args); 234 | if (flags == ParamFlag.None) 235 | { 236 | Console.Out.WriteLine("> None"); 237 | } 238 | else 239 | { 240 | foreach (var param in Enum.GetValues(typeof(ParamFlag)).Cast()) 241 | { 242 | if (param == 0) 243 | continue; 244 | 245 | if ((flags & param) == param) 246 | { 247 | Console.Out.WriteLine($"> {param}"); 248 | } 249 | } 250 | } 251 | 252 | Console.Out.WriteLine("Removing Program.CrashHandler handler..."); 253 | AppDomain.CurrentDomain.UnhandledException -= Program.CrashHandler; 254 | 255 | return flags; 256 | } 257 | } 258 | } 259 | 260 | // Lower the rat count a tiny bit on slower devices... 261 | namespace Paris.Game.Actor 262 | { 263 | public class patch_RatKing: RatKing 264 | { 265 | private void SpawnPiedPiperRatsAtPos(Vector3 position, int count, bool horizontalFlipped) 266 | { 267 | int reducedRats = 1 + Menu.patch_Options.reducedRats; 268 | for (int i = 0; i < count; i += reducedRats) 269 | { 270 | Rat rat = GameObjectPoolManager.Singleton.SpawnObject(this.RatPiedPiperTemplatePath, position, false, false, true) as Rat; 271 | rat.Owner = this; 272 | rat.HorizontalFlipped = horizontalFlipped; 273 | rat.SetupRatInStampede(); 274 | rat.Position += new Vector3(0f, ((float)i - (float)count / 2f) * 10f, 0f); 275 | rat.Position += new Vector3(((float)ParisMath.GetRandom() * 10f - 5f) * reducedRats, ((float)ParisMath.GetRandom() * 10f - 5f) * reducedRats, 0f); 276 | rat.Animator.CurrentFrameID = ParisMath.GetRandomInt() % rat.Animator.CurrentAnimation.Frames.Count; 277 | rat.Animator.Scale = 1f + ((float)ParisMath.GetRandom() - 0.5f) * 0.1f; 278 | rat.LocallyControlled = true; 279 | this._piedPiperRats.Add(rat); 280 | } 281 | } 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /SteelAssault/SteelAssaultCsPatches.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Reflection.Emit; 7 | using System.Threading; 8 | using HarmonyLib; 9 | using Microsoft.Xna.Framework; 10 | using SteelAssaultCs.Engine; 11 | using SteelAssaultCs.Engine.Objects.Background; 12 | using SteelAssaultCs.Engine.Objects.Effects; 13 | using SteelAssaultCs.Graphics.DrawCalls; 14 | 15 | [assembly: IgnoresAccessChecksTo("SteelAssaultCs")] 16 | [assembly: IgnoresAccessChecksTo("Microsoft.Xna.Framework")] 17 | [ModEntryPoint] 18 | public static class SteelAssaultCsPatches 19 | { 20 | public static void Main() 21 | { 22 | Console.Out.WriteLine("Found SteelAssaultCsPatches, running..."); 23 | try 24 | { 25 | new Harmony("com.github.johnnyonflame.SteelAssaultCsPatches").PatchAll(Assembly.GetExecutingAssembly()); 26 | } 27 | catch (Exception e) 28 | { 29 | Console.Out.WriteLine($"Failed: {e.ToString()}"); 30 | throw e; 31 | } 32 | } 33 | 34 | // Lower the water droplets from overhangs significantly 35 | [HarmonyPatch("DripSurface", "init_delayed")] 36 | private class DripSurface_init_delayed 37 | { 38 | private static void Prefix(ref DripSurface __instance, out int __state) 39 | { 40 | __state = __instance.width; 41 | __instance.width = Math.Max(4, __state / 7); 42 | } 43 | 44 | private static void Postfix(ref DripSurface __instance, int __state) 45 | { 46 | __instance.width = __state; 47 | } 48 | } 49 | 50 | // Cap the maximum water droplet splat effects to 1 every 20 frames 51 | [HarmonyPatch("RainSurface", "update")] 52 | private class RainSurface_update 53 | { 54 | static IEnumerable Transpiler(IEnumerable instructions) 55 | { 56 | var codes = new List(instructions); 57 | for (int i = 0; i < codes.Count; i++) 58 | { 59 | if (codes[i].opcode == OpCodes.Ldc_R8 && (double)codes[i].operand == 2.0) 60 | { 61 | if (codes[i-3].opcode != OpCodes.Ldc_R8 || (double)codes[i-3].operand != 50.0) 62 | continue; 63 | 64 | // Limit droplets to 1 every 20 frames. 65 | List newCodes = new List(); 66 | newCodes.Add(new CodeInstruction(OpCodes.Ldloc_2)); 67 | newCodes.Add(new CodeInstruction(OpCodes.Ldc_R8, 20.0)); 68 | newCodes.Add(new CodeInstruction(OpCodes.Call, ((Func) Math.Max).Method)); 69 | newCodes.Add(new CodeInstruction(OpCodes.Stloc_2)); 70 | codes.InsertRange(i+4, newCodes); 71 | System.Console.WriteLine($"Patched RainSurface at RainSurface:update+{i}!"); 72 | break; 73 | } 74 | } 75 | /* 76 | for (int i = 0; i < codes.Count; i++) 77 | { 78 | System.Console.WriteLine($"RainSurface:update+{i:000}: {codes[i].ToString()}"); 79 | } 80 | */ 81 | return codes.AsEnumerable(); 82 | } 83 | } 84 | 85 | // Lower the rain intensity globally 86 | [HarmonyPatch("RainEffect", "regenerateCalls")] 87 | private class RainEffect_regenerateCalls 88 | { 89 | private static void Prefix(ref RainEffect __instance, out double __state) 90 | { 91 | __state = __instance.intensity; 92 | __instance.intensity = __state * 0.125; 93 | } 94 | 95 | private static void Postfix(ref RainEffect __instance, double __state) 96 | { 97 | __instance.intensity = __state; 98 | } 99 | } 100 | 101 | // Lower the Mecha Agni attack particles by more than half 102 | [HarmonyPatch("FireballAttack", "update")] 103 | private class FireballAttack_update 104 | { 105 | static IEnumerable Transpiler(IEnumerable instructions) 106 | { 107 | var codes = new List(instructions); 108 | for (int i = 0; i < codes.Count; i++) 109 | { 110 | if (codes[i].opcode == OpCodes.Call && 111 | codes[i].operand.ToString().Contains( 112 | "Void Flamethrower(SteelAssaultCs.Geometry.Position, Double, Double, Int32, Double, Boolean, Boolean, Boolean, Int32)")) 113 | { 114 | if (codes[i-11].opcode != OpCodes.Ldc_I4_6) 115 | continue; 116 | 117 | codes[i-11].opcode = OpCodes.Ldc_I4_2; 118 | codes[i-5].opcode = OpCodes.Ldc_I4_8; 119 | System.Console.WriteLine($"Patched Flamethrower at FireballAttack:update+{i}!"); 120 | break; 121 | } 122 | } 123 | 124 | return codes.AsEnumerable(); 125 | } 126 | } 127 | 128 | // Lower the Mecha Agni attack particles by more than half 129 | [HarmonyPatch("SpitFlame", "update")] 130 | private class SpitFlame_update 131 | { 132 | static IEnumerable Transpiler(IEnumerable instructions) 133 | { 134 | var codes = new List(instructions); 135 | for (int i = 0; i < codes.Count; i++) 136 | { 137 | if (codes[i].opcode == OpCodes.Call && 138 | codes[i].operand.ToString().Contains( 139 | "Void Flamethrower(SteelAssaultCs.Geometry.Position, Double, Double, Int32, Double, Boolean, Boolean, Boolean, Int32)")) 140 | { 141 | if (codes[i-12].opcode != OpCodes.Ldc_I4_8) 142 | continue; 143 | 144 | codes[i-12].opcode = OpCodes.Ldc_I4_3; 145 | codes[i-5].opcode = OpCodes.Ldc_I4_8; 146 | System.Console.WriteLine($"Patched Flamethrower at SpitFlame:update+{i}!"); 147 | break; 148 | } 149 | } 150 | 151 | return codes.AsEnumerable(); 152 | } 153 | } 154 | 155 | // Lower the flameturret attack particles by half 156 | [HarmonyPatch("WaitAndFire", "update")] 157 | private class BCFlameTurret_WaitAndFire_update 158 | { 159 | static IEnumerable Transpiler(IEnumerable instructions) 160 | { 161 | var codes = new List(instructions); 162 | for (int i = 0; i < codes.Count; i++) 163 | { 164 | if (codes[i].opcode == OpCodes.Call && 165 | codes[i].operand.ToString().Contains( 166 | "Void Flamethrower(SteelAssaultCs.Geometry.Position, Double, Double, Int32, Double, Boolean, Boolean, Boolean, Int32)")) 167 | { 168 | if (codes[i-12].opcode != OpCodes.Ldc_I4_6) 169 | continue; 170 | 171 | codes[i-12].opcode = OpCodes.Ldc_I4_2; 172 | System.Console.WriteLine($"Patched BCFlameTurret at WaitAndFire:update+{i}!"); 173 | break; 174 | } 175 | } 176 | 177 | return codes.AsEnumerable(); 178 | } 179 | } 180 | 181 | // Lower the Flame Spider's front gun particles considerably 182 | [HarmonyPatch("FSGun", "update")] 183 | private class FlameSpider_FSGun_update 184 | { 185 | static IEnumerable Transpiler(IEnumerable instructions) 186 | { 187 | var codes = new List(instructions); 188 | for (int i = 0; i < codes.Count; i++) 189 | { 190 | if (codes[i].opcode == OpCodes.Call && 191 | codes[i].operand.ToString().Contains( 192 | "Void Flamethrower(SteelAssaultCs.Geometry.Position, Double, Double, Int32, Double, Boolean, Boolean, Boolean, Int32)")) 193 | { 194 | if (codes[i-11].opcode != OpCodes.Ldc_I4_6) 195 | continue; 196 | 197 | codes[i-11].opcode = OpCodes.Ldc_I4_2; 198 | System.Console.WriteLine($"Patched FlameSpider at FSGun:update+{i}!"); 199 | break; 200 | } 201 | } 202 | 203 | return codes.AsEnumerable(); 204 | } 205 | } 206 | 207 | [HarmonyPatch("TSBillboard", "draw")] 208 | static class TSBillboard_draw 209 | { 210 | static bool Prefix(ref TSBillboard __instance, ICollection ret) 211 | { 212 | if (!GameManager.Camera.onscreen(__instance, 320)) 213 | return false; 214 | 215 | __instance.calls.Clear(); 216 | switch (__instance.type) 217 | { 218 | case TSBillboard.AdType.NUMELTA: 219 | __instance.numelta(__instance.calls); 220 | break; 221 | case TSBillboard.AdType.NERIO: 222 | __instance.nerio(__instance.calls); 223 | break; 224 | case TSBillboard.AdType.METROGRAPH: 225 | __instance.metrograph(__instance.calls); 226 | break; 227 | case TSBillboard.AdType.BROADWAY: 228 | __instance.broadway(__instance.calls); 229 | break; 230 | case TSBillboard.AdType.DIRAC: 231 | __instance.dirac(__instance.calls); 232 | break; 233 | case TSBillboard.AdType.RENT: 234 | __instance.rent(__instance.calls); 235 | break; 236 | case TSBillboard.AdType.AVALYN: 237 | __instance.avalyn(__instance.calls); 238 | break; 239 | case TSBillboard.AdType.SOPHIE: 240 | __instance.sophie(__instance.calls); 241 | break; 242 | case TSBillboard.AdType.STONKS: 243 | __instance.stocks(__instance.calls); 244 | break; 245 | case TSBillboard.AdType.NEWS: 246 | __instance.news(__instance.calls); 247 | break; 248 | case TSBillboard.AdType.SOROBAN: 249 | __instance.soroban(__instance.calls); 250 | break; 251 | case TSBillboard.AdType.BOND: 252 | __instance.bond(__instance.calls); 253 | break; 254 | case TSBillboard.AdType.AXON: 255 | __instance.axon(__instance.calls); 256 | break; 257 | case TSBillboard.AdType.PLAY: 258 | __instance.play(__instance.calls); 259 | break; 260 | case TSBillboard.AdType.CAMPAIGN: 261 | __instance.campaign(__instance.calls); 262 | break; 263 | case TSBillboard.AdType.ZENOVIA: 264 | __instance.zenovia(__instance.calls); 265 | break; 266 | case TSBillboard.AdType.TRIBUTE: 267 | __instance.tribute(__instance.calls); 268 | break; 269 | } 270 | 271 | __instance.calls.ForEach(x => ret.Add(x)); 272 | return false; 273 | } 274 | } 275 | 276 | // Simplify the displaceSingleCall effect. 277 | [HarmonyPatch("TSBillboard", "displaceSingleCall")] 278 | static class TSBillboard_displaceSingleCall 279 | { 280 | static bool Prefix(ref TSBillboard __instance, List ret, TexturedDrawCall t) 281 | { 282 | ret.Add(t); 283 | return false; 284 | } 285 | } 286 | 287 | // Disabled on TIMES_SQUARE since that level has performance woes. 288 | [HarmonyPatch("MetroLightbeam", "draw")] 289 | static class MetroLightbeam_draw 290 | { 291 | static bool Prefix(ref MetroLightbeam __instance, ICollection ret) 292 | { 293 | Level level = GameManager.Level; 294 | if (level != null && level.currArea == Level.Area.TIMES_SQUARE) 295 | return false; 296 | 297 | return true; 298 | } 299 | } 300 | } 301 | 302 | namespace System.Runtime.CompilerServices 303 | { 304 | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] 305 | public class IgnoresAccessChecksToAttribute : Attribute 306 | { 307 | public IgnoresAccessChecksToAttribute(string assemblyName) 308 | { 309 | AssemblyName = assemblyName; 310 | } 311 | 312 | public string AssemblyName { get; } 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /TMNTSR/ParisEngine.TMNTSRPatches.mm.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework.Audio; 2 | using Microsoft.Xna.Framework; 3 | using MonoMod; 4 | using Paris.Engine.Context; 5 | using Paris.Engine.Graphics.Animations; 6 | using Paris.Engine.Graphics; 7 | using Paris.Engine.System.AssetPacks; 8 | using Paris.Engine.System.Helper; 9 | using Paris.Engine.System.Leaderboards; 10 | using Paris.Engine.System.Localisation; 11 | using Paris.Engine.System; 12 | using Paris.System.Helper; 13 | using Paris.Game.System.Progression; 14 | using Paris.Game.System; 15 | using System.Collections.Generic; 16 | using System.IO.MemoryMappedFiles; 17 | using System.IO; 18 | using System.Linq; 19 | using System.Reflection; 20 | using System.Threading; 21 | using System; 22 | 23 | #pragma warning disable 1591 24 | #pragma warning disable CS0649 25 | #pragma warning disable CS0626 26 | 27 | namespace Paris.Engine.Save 28 | { 29 | public class patch_SaveSystem: SaveSystem 30 | { 31 | public static string GetModConfigPath() 32 | { 33 | SaveSystemBase saveBase = SaveSystemBase.Singleton; 34 | return Path.Combine(SaveSystem.GetOSPath(), "Tribute Games", "TMNT", "ModSettings.dat"); 35 | } 36 | } 37 | } 38 | 39 | namespace Paris.Engine.System.AssetPacks 40 | { 41 | // Fix issues with Alt VO fails to be applied on Linux when using the Windows builds 42 | public class patch_AssetPack : AssetPack 43 | { 44 | private void PopulateReplacementCache() 45 | { 46 | this._contentPathCache.Clear(); 47 | string sfxFolder = Path.Combine(this.SourcePath, "Audio\\SFX\\"); 48 | sfxFolder = sfxFolder.Replace("/", "\\"); 49 | foreach (AssetPackItem assetPackItem in this.Items) 50 | { 51 | string originalPath = assetPackItem.OriginalPath; 52 | string replacementPath = assetPackItem.ReplacementPath; 53 | this._contentPathCache[originalPath] = replacementPath; 54 | if (originalPath.StartsWith("Audio\\SFX\\")) 55 | { 56 | string originalSfxPath = originalPath.Remove(0, "Audio\\SFX\\".Length); 57 | string replacementSfxPath = replacementPath; 58 | if (replacementPath.StartsWith("Audio\\SFX\\")) 59 | { 60 | replacementSfxPath = replacementPath.Remove(0, "Audio\\SFX\\".Length); 61 | } 62 | else if (replacementPath.StartsWith(sfxFolder)) 63 | { 64 | replacementSfxPath = replacementPath.Remove(0, sfxFolder.Length); 65 | } 66 | this._sfxPathCache[originalSfxPath] = replacementSfxPath; 67 | } 68 | } 69 | } 70 | } 71 | } 72 | 73 | namespace Paris.Engine.Context 74 | { 75 | public class patch_ContextManager: ContextManager 76 | { 77 | patch_ContextManager(Microsoft.Xna.Framework.Game game): base(game) { } 78 | 79 | // Due to issues with loading assets async, we will be loading quite a few of these in the main thread, 80 | // otherwise, we _will_ cause deadlocks. 81 | new public void StartPreLoadingGlobalAssets() 82 | { 83 | if (EditorMode || _preloadingState != 0) 84 | { 85 | return; 86 | } 87 | 88 | // Those can be loaded async, since they have no texture data 89 | Action[] array = new Action[4] { PreloadAudio, CacheJsonFiles, CacheReflectionInfo, PreloadPoolObjects }; 90 | foreach (Action action in array) 91 | { 92 | Thread thread = new Thread(action.Invoke); 93 | thread.Priority = ThreadPriority.BelowNormal; 94 | thread.IsBackground = true; 95 | thread.Name = string.Format("Preload : {0}", action.Method.Name); 96 | mGlobalLoadingThreads.Add(thread); 97 | thread.Start(); 98 | } 99 | 100 | // These resources contain texture data, we need to load them synchronously 101 | AttackList.Load(); 102 | PowerLevelList.Load(); 103 | SurvivalDimensionList.Load(); 104 | SurvivalWaveList.Load(); 105 | SurvivalPowerLevelList.Load(); 106 | CharacterManager.Singleton.Init(); 107 | 108 | // This has to be loaded by the main thread. 109 | LoadGlobalContent(); 110 | 111 | // These might have texture data, so they have to be loaded by the 112 | // main thread. 113 | foreach (Type loadableType in Assembly.GetCallingAssembly().GetLoadableTypes()) 114 | { 115 | if (typeof(IPreloadable).IsAssignableFrom(loadableType)) 116 | { 117 | IPreloadable parameter = (IPreloadable)Activator.CreateInstance(loadableType); 118 | PreloadCustom(parameter); 119 | } 120 | } 121 | 122 | _preloadingState = PreloadingState.Finished; 123 | } 124 | } 125 | } 126 | 127 | namespace Paris.Engine.System.Networking 128 | { 129 | // Fix story mode crashes 130 | class patch_NetworkManager: NetworkManager 131 | { 132 | public extern virtual void orig_Disconnect(bool transition = false); 133 | new public virtual void Disconnect(bool transition = false) 134 | { 135 | // try 136 | // { 137 | // orig_Disconnect(transition); 138 | // } 139 | // catch (Exception){ } 140 | } 141 | 142 | public extern virtual void orig_ChangeLobbyLocked(bool isLocked); 143 | new public virtual void ChangeLobbyLocked(bool isLocked) 144 | { 145 | // try 146 | // { 147 | // orig_ChangeLobbyLocked(isLocked); 148 | // } 149 | // catch (Exception){ } 150 | } 151 | 152 | 153 | } 154 | } 155 | 156 | namespace Paris.Engine { 157 | public class patch_ParisEngine: ParisEngine 158 | { 159 | public patch_ParisEngine() { } 160 | public static Thread frameworkDispatchWorker { get; private set; } = null; 161 | #if false 162 | static double elapsedAccum = 0.0f; 163 | static int elapsedCounter = 0; 164 | const int bufferFrames = 15; 165 | #endif 166 | public static void frameworkUpdateWork() 167 | { 168 | while (true) 169 | { 170 | FrameworkDispatcher.Update(); 171 | Thread.Sleep(16); 172 | } 173 | } 174 | 175 | new void Update(GameTime gameTime) 176 | { 177 | lock (this.updateableComponents) 178 | { 179 | for (int i = 0; i < this.updateableComponents.Count; i++) 180 | { 181 | this.currentlyUpdatingComponents.Add(this.updateableComponents[i]); 182 | } 183 | } 184 | 185 | foreach (IUpdateable updateable in this.currentlyUpdatingComponents) 186 | { 187 | if (updateable.Enabled) 188 | { 189 | updateable.Update(gameTime); 190 | } 191 | } 192 | 193 | // Big ugly. 194 | if (frameworkDispatchWorker == null) 195 | { 196 | frameworkDispatchWorker = new Thread(frameworkUpdateWork); 197 | frameworkDispatchWorker.Start(); 198 | } 199 | 200 | this.currentlyUpdatingComponents.Clear(); 201 | base.IsFixedTimeStep = !Renderer.Singleton.VSync; 202 | } 203 | } 204 | 205 | public class patch_Stat: Stat 206 | { 207 | patch_Stat(string id, StatType type): base(id, type) { } 208 | 209 | // Fix story mode crash in earlier versions 210 | extern private bool orig_StoreSteam(); 211 | private bool StoreSteam() 212 | { 213 | return false; 214 | } 215 | } 216 | 217 | class patch_GameAnalytics: GameAnalytics 218 | { 219 | public extern void orig_Init(); 220 | new public void Init() 221 | { 222 | // Disable GameAnalytics 223 | } 224 | } 225 | } 226 | 227 | namespace Paris.Engine.Audio 228 | { 229 | public class MMappedSFXBank { 230 | public string assetPath; 231 | public MemoryMappedFile mmapFile; 232 | public MemoryMappedViewStream mvStream; 233 | public IntPtr basePtr; 234 | 235 | public MMappedSFXBank(string assetPath) 236 | { 237 | this.assetPath = assetPath; 238 | mmapFile = MemoryMappedFile.CreateFromFile(assetPath, FileMode.Open, assetPath, 0, MemoryMappedFileAccess.Read); 239 | mvStream = mmapFile.CreateViewStream(0, 0, MemoryMappedFileAccess.Read); 240 | basePtr = mvStream.SafeMemoryMappedViewHandle.DangerousGetHandle(); 241 | } 242 | 243 | public void RewindStream() 244 | { 245 | mvStream.Seek(0, SeekOrigin.Begin); 246 | } 247 | }; 248 | 249 | class patch_AudioManager: AudioManager 250 | { 251 | public static List loadedBanks = new List{}; 252 | 253 | /* 254 | Replacement for SFX..ctor "public SFX(string assetName, byte[] data, float volume, 255 | int poolSize, SoundLoopType loopType, SFXChannel[] channels, SoundCutOffType cutOffType, 256 | Range randomPitch, bool isVO, float cooldown) : this(assetName, cooldown)" 257 | */ 258 | public static SFX SFXFromIntPtr(string assetName, IntPtr data, int length, float volume, int poolSize, SoundLoopType loopType, SFXChannel[] channels, SoundCutOffType cutOffType, Types.Range randomPitch, bool isVO, float cooldown) 259 | { 260 | SFX sfx = new SFX(assetName, cooldown); 261 | sfx._soundEffectInstance = new SoundEffectInstance[poolSize]; 262 | try 263 | { 264 | sfx._soundEffect = new SoundEffect(data, false, length, 48000, AudioChannels.Stereo); 265 | } 266 | catch (Exception) 267 | { 268 | sfx._soundEffect = null; 269 | } 270 | sfx._playVolume = volume; 271 | sfx.IsVO = isVO; 272 | int finalPoolSize = (loopType == SoundLoopType.NoLoop) ? poolSize : 1; 273 | for (int i = 0; i < finalPoolSize; i++) 274 | { 275 | if (sfx._soundEffect != null) 276 | { 277 | sfx._soundEffectInstance[i] = sfx._soundEffect.CreateInstance(); 278 | sfx._soundEffectInstance[i].IsLooped = (loopType > SoundLoopType.NoLoop); 279 | } 280 | } 281 | sfx._channels = channels; 282 | sfx._cutOffType = cutOffType; 283 | sfx._loopType = loopType; 284 | if (randomPitch.Min != 0f || randomPitch.Max != 0f) 285 | { 286 | sfx._pitch = new Types.Range?(randomPitch); 287 | } 288 | sfx.Volume = 1f; 289 | return sfx; 290 | } 291 | 292 | /* 293 | This patch implements Memory Mapped SFX support, SFXPack.pbn is a 300mb file that gets loaded 294 | in it's entirety to ram, this patch allows said file to remain resident in virtual memory as a 295 | memory mapped file, while not taking as much physical memory. 296 | */ 297 | new unsafe public void LoadSFXPack(List sounds, string packPath, AssetPackEnableFlags flags) 298 | { 299 | try 300 | { 301 | // Memory map the Voice Bank or reuse an already mapped one 302 | MMappedSFXBank bank = loadedBanks.Find(b => b.assetPath == packPath); 303 | if (bank == null) 304 | { 305 | bank = new MMappedSFXBank(packPath); 306 | loadedBanks.Add(bank); 307 | } 308 | else 309 | { 310 | bank.RewindStream(); 311 | } 312 | 313 | BinaryReader bankReader = new BinaryReader(bank.mvStream); 314 | 315 | foreach (SoundSettings.SoundInfo soundInfo in this._settings.Sounds) 316 | { 317 | int sndSize = bankReader.ReadInt32(); 318 | if (sndSize < 0) 319 | throw new Exception("sndSize < 0"); 320 | 321 | if (sndSize > 0) 322 | { 323 | // We'd usually read the entire sound effect into memory, here we just take the offset 324 | // then we move the reading head forwards. 325 | long offset = bank.mvStream.Position; 326 | bank.mvStream.Seek(sndSize, SeekOrigin.Current); 327 | 328 | IEnumerable channels = ( 329 | from channelName in soundInfo.PlaybackChannels 330 | from sfxChannel in this._soundChannels 331 | where sfxChannel.Name == channelName 332 | select sfxChannel 333 | ); 334 | 335 | if ((((flags & AssetPackEnableFlags.Voice) != (AssetPackEnableFlags)0) || !soundInfo.IsVO) && 336 | (((flags & AssetPackEnableFlags.Sound) != (AssetPackEnableFlags)0) || soundInfo.IsVO)) 337 | { 338 | IntPtr sfxPtr = new IntPtr((Int64)bank.basePtr + offset); 339 | sounds.Add(SFXFromIntPtr( 340 | soundInfo.SoundID, 341 | sfxPtr, 342 | sndSize, 343 | soundInfo.Volume / 100f, 344 | soundInfo.PoolSize, 345 | soundInfo.LoopType, 346 | Enumerable.ToArray(channels), 347 | soundInfo.CutOffType, 348 | new Types.Range(soundInfo.PitchMin, soundInfo.PitchMax), 349 | soundInfo.IsVO, 350 | soundInfo.Cooldown)); 351 | } 352 | } 353 | } 354 | } 355 | catch (Exception ex) 356 | { 357 | Console.Out.WriteLine("Invalid data being read in SFXPack using list of sounds from SoundsSettings; SoundsSettings & SFXPack are out of sync! Error message: " + ex.ToString()); 358 | sounds.Clear(); 359 | } 360 | } 361 | } 362 | } 363 | 364 | // Disable the Leaderboards, we're offline. 365 | [MonoModPatch("Paris.Engine.System.Leaderboards.Leaderboard")] 366 | public class patch_Leaderboard 367 | { 368 | [MonoModConstructor] 369 | public patch_Leaderboard(LocID name, string key, LeaderboardBase.SortMode sortMode, LeaderboardBase.DisplayType displayType) 370 | { 371 | } 372 | } 373 | 374 | // We don't have Steam present, and we want to be sure users aren't freeloading 375 | // on DLCs they did not purchase, we'll have to perform some silly heuristics 376 | // to check. 377 | namespace Paris.Engine.System.DLC 378 | { 379 | public class patch_DLCInfo: DLCInfo { 380 | public bool CheckOwnsShellshock() 381 | { 382 | // Having the "Aftermath" animation is a good smell to sniff out the 383 | // Shellshock DLC ownership status. 384 | try { 385 | ContextManager.Singleton.LoadContent("2d\\Animations\\BG\\Survival\\Aftermath\\Aftermath", true); 386 | State = LockState.Unlocked; 387 | return true; 388 | } catch (Exception) { } 389 | 390 | return false; 391 | } 392 | 393 | public patch_DLCInfo(int id) : base(id) { 394 | 395 | } 396 | 397 | public extern void orig_SetAppID(uint appid); 398 | new public void SetAppID(uint appid) 399 | { 400 | // AppID = new AppId_t(appid); 401 | orig_SetAppID(appid); 402 | switch (appid) { 403 | case 2348930: //Shellshock 404 | Console.Out.WriteLine($"Owns {appid}? {CheckOwnsShellshock()}"); 405 | break; 406 | default: 407 | Console.Out.WriteLine($"Unknown DLC {appid}"); 408 | break; 409 | } 410 | } 411 | 412 | new public bool IsInstalled() { 413 | CheckOwnsShellshock(); 414 | return State == LockState.Unlocked; 415 | } 416 | 417 | new public void UpdateState() 418 | { 419 | CheckOwnsShellshock(); 420 | } 421 | }; 422 | } -------------------------------------------------------------------------------- /Celeste/CelestePatches.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Reflection.Emit; 8 | using HarmonyLib; 9 | using Microsoft.Xna.Framework; 10 | using Microsoft.Xna.Framework.Graphics; 11 | using FMOD.Studio; 12 | using Celeste; 13 | using Monocle; 14 | 15 | [assembly: IgnoresAccessChecksTo("Celeste")] 16 | [assembly: IgnoresAccessChecksTo("Microsoft.Xna.Framework")] 17 | [ModEntryPoint] 18 | public static class CelestePatches 19 | { 20 | public static bool distortEnabled = false; 21 | public static bool bloomEnabled = false; 22 | public static byte lightingPreset = 0; 23 | public static bool particlesEnabled = false; 24 | public static bool dustEdgesEnabled = true; 25 | public static byte gaussianBlurPreset = 0; 26 | 27 | public static int gfxPreset = 1; 28 | public static void Main() 29 | { 30 | Console.Out.WriteLine("Found CelestePatches, running..."); 31 | try 32 | { 33 | new Harmony("com.github.johnnyonflame.CelestePatches").PatchAll(Assembly.GetExecutingAssembly()); 34 | } 35 | catch (Exception e) 36 | { 37 | Console.Out.WriteLine($"Failed: {e.ToString()}"); 38 | throw e; 39 | } 40 | 41 | loadPreferences(); 42 | Console.Out.WriteLine("Done."); 43 | } 44 | 45 | public static string GetCfgFilename() 46 | { 47 | return Path.Combine(UserIO.SavePath, "CelestePatches.dat"); 48 | } 49 | 50 | public static void savePreferences() 51 | { 52 | using (FileStream fs = File.OpenWrite(GetCfgFilename())) 53 | { 54 | BinaryWriter wr = new BinaryWriter(fs); 55 | wr.Write(distortEnabled); 56 | wr.Write(bloomEnabled); 57 | wr.Write(lightingPreset); 58 | wr.Write(particlesEnabled); 59 | wr.Write(dustEdgesEnabled); 60 | wr.Write(gaussianBlurPreset); 61 | wr.Write(gfxPreset); 62 | } 63 | } 64 | 65 | public static void loadPreferences() 66 | { 67 | try 68 | { 69 | using (FileStream fs = File.OpenRead(GetCfgFilename())) 70 | { 71 | BinaryReader rd = new BinaryReader(fs); 72 | distortEnabled = rd.ReadBoolean(); 73 | bloomEnabled = rd.ReadBoolean(); 74 | lightingPreset = rd.ReadByte(); 75 | particlesEnabled = rd.ReadBoolean(); 76 | dustEdgesEnabled = rd.ReadBoolean(); 77 | gaussianBlurPreset = rd.ReadByte(); 78 | gfxPreset = rd.ReadInt32(); 79 | } 80 | } 81 | catch (Exception e) 82 | { 83 | System.Console.WriteLine($"Unable to load mod config, {e.ToString()}"); 84 | }; 85 | } 86 | 87 | [HarmonyPatch(typeof(MenuOptions), nameof(MenuOptions.Create))] 88 | static class MenuOptions_Create 89 | { 90 | private static TextMenu.Option gfxPresetItem; 91 | private static TextMenu.Option distortItem; 92 | private static TextMenu.Option bloomItem; 93 | private static TextMenu.Option lightingItem; 94 | private static TextMenu.Option particlesItem; 95 | private static TextMenu.Option dustEdgesItem; 96 | private static TextMenu.Option gaussianBlurItem; 97 | 98 | private static void setGfxPreset(int preset) 99 | { 100 | gfxPreset = preset; 101 | switch (gfxPreset) { 102 | case 0: //return "Custom"; 103 | break; 104 | case 1: //return "Very Low"; 105 | distortEnabled = false; 106 | bloomEnabled = false; 107 | lightingPreset = 0; 108 | particlesEnabled = false; 109 | dustEdgesEnabled = true; 110 | gaussianBlurPreset = 0; 111 | break; 112 | case 2: //return "Low"; 113 | distortEnabled = false; 114 | bloomEnabled = false; 115 | lightingPreset = 1; 116 | particlesEnabled = true; 117 | dustEdgesEnabled = true; 118 | gaussianBlurPreset = 1; 119 | break; 120 | case 3: //return "Normal"; 121 | distortEnabled = true; 122 | bloomEnabled = true; 123 | lightingPreset = 3; 124 | particlesEnabled = true; 125 | dustEdgesEnabled = true; 126 | gaussianBlurPreset = 3; 127 | break; 128 | } 129 | 130 | distortItem.Index = distortEnabled ? 1 : 0; 131 | bloomItem.Index = bloomEnabled ? 1 : 0; 132 | lightingItem.Index = lightingPreset; 133 | particlesItem.Index = particlesEnabled ? 1 : 0; 134 | dustEdgesItem.Index = dustEdgesEnabled ? 1 : 0; 135 | gaussianBlurItem.Index = gaussianBlurPreset; 136 | savePreferences(); 137 | } 138 | 139 | public static void SetAndForceCustom(ref T _out, T value) 140 | { 141 | _out = value; 142 | 143 | // Set preset to "Custom" 144 | gfxPreset = 0; 145 | gfxPresetItem.Index = 0; 146 | savePreferences(); 147 | } 148 | 149 | public static void Postfix(bool inGame, EventInstance snapshot, ref TextMenu __result) 150 | { 151 | // Create a shallow copy of the original menu, then depopulate it 152 | List kept = MenuOptions.menu.items.ToList(); 153 | MenuOptions.menu.Clear(); 154 | 155 | // We're ridding ourselves of the header later, so recreate it 156 | MenuOptions.menu.Add(new TextMenu.Header(Dialog.Clean("options_title", null))); 157 | 158 | // Create our options first 159 | MenuOptions.menu.Add(new TextMenu.SubHeader("CelestePatches Options", true)); 160 | MenuOptions.menu.Add(gfxPresetItem = new TextMenu.Slider("Graphics Preset", i => { 161 | switch (i) { 162 | case 0: return "Custom"; 163 | case 1: return "Very Low"; 164 | case 2: return "Low"; 165 | case 3: return "Normal"; 166 | default: return "???"; 167 | } 168 | }, 0, 3, gfxPreset).Change(i => { setGfxPreset(i); })); 169 | 170 | MenuOptions.menu.Add(distortItem = new TextMenu.OnOff("Distort", distortEnabled).Change(i => { SetAndForceCustom(ref distortEnabled, i); })); 171 | MenuOptions.menu.Add(bloomItem = new TextMenu.OnOff("Bloom", bloomEnabled).Change(i => { SetAndForceCustom(ref bloomEnabled, i); })); 172 | MenuOptions.menu.Add(lightingItem = new TextMenu.Slider("Lighting", i => { 173 | switch (i) { 174 | case 0: return "Off"; 175 | case 1: return "Very Low"; 176 | case 2: return "Low"; 177 | case 3: return "Normal"; 178 | default: return "???"; 179 | }; 180 | }, 0, 3, (int)lightingPreset).Change(i => { SetAndForceCustom(ref lightingPreset, (byte)i); })); 181 | MenuOptions.menu.Add(particlesItem = new TextMenu.OnOff("Particles", particlesEnabled).Change(i => { SetAndForceCustom(ref particlesEnabled, i); })); 182 | MenuOptions.menu.Add(dustEdgesItem = new TextMenu.OnOff("Dust Edges", dustEdgesEnabled).Change(i => { SetAndForceCustom(ref dustEdgesEnabled, i); })); 183 | MenuOptions.menu.Add(gaussianBlurItem = new TextMenu.Slider("Max. Gaussian Blur", i => { 184 | switch (i) { 185 | case 0: return "Off"; 186 | case 1: return "3 Samples"; 187 | case 2: return "5 Samples"; 188 | case 3: return "9 Samples"; 189 | default: return "???"; 190 | }; 191 | }, 0, 3, (int)gaussianBlurPreset).Change(i => { SetAndForceCustom(ref gaussianBlurPreset, (byte)i); })); 192 | 193 | // Now copy the previous items back 194 | foreach (var item in kept.Skip(1)) 195 | { 196 | MenuOptions.menu.Add(item); 197 | } 198 | } 199 | } 200 | 201 | // ---------------- 202 | // Particle systems 203 | // ---------------- 204 | [HarmonyPatch(typeof(ParticleSystem), nameof(ParticleSystem.Simulate))] 205 | static class ParticleSystem_Simulate 206 | { 207 | public static bool Prefix(ref ParticleSystem __instance, float duration, float interval, Action emitter) 208 | { 209 | return particlesEnabled; 210 | } 211 | } 212 | 213 | [HarmonyPatch(typeof(ParticleSystem), nameof(ParticleSystem.Render))] 214 | [HarmonyPatch(new Type[] { })] 215 | static class ParticleSystem_Render1 216 | { 217 | public static bool Prefix(ref ParticleSystem __instance) 218 | { 219 | return particlesEnabled; 220 | } 221 | } 222 | 223 | [HarmonyPatch(typeof(ParticleSystem), nameof(ParticleSystem.Render))] 224 | [HarmonyPatch(new Type[] { typeof(float) })] 225 | static class ParticleSystem_Render2 226 | { 227 | public static bool Prefix(ref ParticleSystem __instance, float alpha) 228 | { 229 | return particlesEnabled; 230 | } 231 | } 232 | 233 | [HarmonyPatch(typeof(ParticleSystem), nameof(ParticleSystem.Update))] 234 | static class ParticleSystem_Update 235 | { 236 | public static bool Prefix(ref ParticleSystem __instance) 237 | { 238 | return particlesEnabled; 239 | } 240 | } 241 | 242 | [HarmonyPatch(typeof(LightingRenderer), nameof(LightingRenderer.BeforeRender))] 243 | static class LightingRenderer_BeforeRender 244 | { 245 | public static bool Prefix(ref LightingRenderer __instance, Scene scene) 246 | { 247 | return lightingPreset > 0; 248 | } 249 | 250 | public static void ApplyBlurPreset() 251 | { 252 | // Off: This isn't called 253 | // Very Low Preset: No blur 254 | // Low Preset: 3 samples 255 | // Normal Preset: 9 samples 256 | 257 | switch (lightingPreset) 258 | { 259 | case 3: 260 | GaussianBlur.Blur(GameplayBuffers.Light, GameplayBuffers.TempA, GameplayBuffers.Light, 0f, true, GaussianBlur.Samples.Nine, 1f, GaussianBlur.Direction.Both, 1f); 261 | break; 262 | case 2: 263 | GaussianBlur.Blur(GameplayBuffers.Light, GameplayBuffers.TempA, GameplayBuffers.Light, 0f, true, GaussianBlur.Samples.Three, 1f, GaussianBlur.Direction.Both, 1f); 264 | break; 265 | } 266 | } 267 | 268 | static IEnumerable Transpiler(IEnumerable instructions) 269 | { 270 | var codes = new List(instructions); 271 | for (int i = 0; i < codes.Count; i++) 272 | { 273 | if (codes[i].opcode == OpCodes.Call && codes[i].operand.ToString().Contains("Texture2D Blur")) 274 | { 275 | for (int j = 0; j < 11; j++) 276 | codes[i-j].opcode = OpCodes.Nop; 277 | 278 | codes[i+1].opcode = OpCodes.Nop; 279 | codes[i-10].opcode = OpCodes.Call; 280 | codes[i-10].operand = AccessTools.Method(typeof(LightingRenderer_BeforeRender), "ApplyBlurPreset", new Type[] { }); 281 | break; 282 | } 283 | } 284 | 285 | return codes.AsEnumerable(); 286 | } 287 | } 288 | 289 | [HarmonyPatch(typeof(LightingRenderer), nameof(LightingRenderer.Render))] 290 | static class LightingRenderer_Render 291 | { 292 | public static bool Prefix(ref LightingRenderer __instance, Scene scene) 293 | { 294 | return lightingPreset > 0; 295 | } 296 | } 297 | 298 | [HarmonyPatch(typeof(LightingRenderer), nameof(LightingRenderer.Update))] 299 | static class LightingRenderer_Update 300 | { 301 | public static bool Prefix(ref LightingRenderer __instance, Scene scene) 302 | { 303 | __instance.Visible = lightingPreset > 0; 304 | return lightingPreset > 0; 305 | } 306 | } 307 | 308 | // -------------- 309 | // Bloom Renderer 310 | // -------------- 311 | [HarmonyPatch(typeof(BloomRenderer), nameof(BloomRenderer.Apply))] 312 | static class BloomRenderer_Apply 313 | { 314 | public static bool Prefix(ref BloomRenderer __instance, VirtualRenderTarget target, Scene scene) 315 | { 316 | return bloomEnabled; 317 | } 318 | } 319 | 320 | // ------------------ 321 | // Distortion Effects 322 | // ------------------ 323 | [HarmonyPatch(typeof(Distort), nameof(Distort.Render))] 324 | static class Distort_Render 325 | { 326 | public static bool Prefix(Texture2D source, Texture2D map, bool hasDistortion) 327 | { 328 | if (!distortEnabled) { 329 | Draw.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, DepthStencilState.Default, RasterizerState.CullNone, null); 330 | Draw.SpriteBatch.Draw(source, Vector2.Zero, Color.White); 331 | Draw.SpriteBatch.End(); 332 | } 333 | 334 | return distortEnabled; 335 | } 336 | } 337 | 338 | // ------------------- 339 | // DustEdge Processing 340 | // ------------------- 341 | [HarmonyPatch(typeof(DustEdges), nameof(DustEdges.BeforeRender))] 342 | static class DustEdges_BeforeRender 343 | { 344 | public static bool Prefix(ref DustEdges __instance) 345 | { 346 | return dustEdgesEnabled; 347 | } 348 | } 349 | 350 | [HarmonyPatch(typeof(DustEdges), nameof(DustEdges.Render))] 351 | static class DustEdges_Render 352 | { 353 | public static bool Prefix(ref DustEdges __instance) 354 | { 355 | return dustEdgesEnabled; 356 | } 357 | } 358 | 359 | [HarmonyPatch(typeof(DustEdges), nameof(DustEdges.Update))] 360 | static class DustEdges_Update 361 | { 362 | public static bool Prefix(ref DustEdges __instance) 363 | { 364 | __instance.Visible = dustEdgesEnabled; 365 | return dustEdgesEnabled; 366 | } 367 | } 368 | 369 | // ------------------ 370 | // Gaussian Blur Pass 371 | // ------------------ 372 | [HarmonyPatch(typeof(GaussianBlur), nameof(GaussianBlur.Blur))] 373 | static class GaussianBlur_Blur 374 | { 375 | public static bool Prefix(Texture2D texture, VirtualRenderTarget temp, VirtualRenderTarget output, float fade, bool clear, 376 | ref GaussianBlur.Samples samples, float sampleScale, GaussianBlur.Direction direction, float alpha, ref Texture2D __result) 377 | { 378 | samples = (GaussianBlur.Samples)Math.Min((int)gaussianBlurPreset - 1, (int)samples); 379 | if (samples < 0) 380 | { 381 | Engine.Instance.GraphicsDevice.SetRenderTarget(output); 382 | if (clear) 383 | Engine.Instance.GraphicsDevice.Clear(Color.Transparent); 384 | 385 | __result = (Texture2D)output; 386 | return false; 387 | } 388 | 389 | return true; 390 | } 391 | } 392 | 393 | // -------------------------- 394 | // Texture Loading Extensions 395 | // -------------------------- 396 | [HarmonyPatch(typeof(VirtualTexture), nameof(VirtualTexture.Reload))] 397 | static class VirtualTexture_Reload 398 | { 399 | static bool Prefix(ref VirtualTexture __instance) 400 | { 401 | // Is this a texture? 402 | string ext = System.IO.Path.GetExtension(__instance.Path); 403 | if (ext == ".data") 404 | { 405 | string astcEncodedFile = System.IO.Path.ChangeExtension(__instance.Path, ".astc"); 406 | string astcEncodedFilePath = System.IO.Path.Combine(Engine.ContentDirectory, astcEncodedFile); 407 | 408 | // Is there an ASTC replacement file? 409 | if (System.IO.File.Exists(astcEncodedFilePath)) 410 | { 411 | using (FileStream stream = File.OpenRead(astcEncodedFilePath)) 412 | { 413 | // Usual flow won't be called, so we need to unload here since VirtualTexture.Reload would do so. 414 | __instance.Unload(); 415 | 416 | // Read and parse ASTC header 417 | byte[] astc_header = new byte[16]; 418 | stream.Read(astc_header, 0, 16); 419 | 420 | int block_x = astc_header[4]; 421 | int block_y = astc_header[5]; 422 | int width = astc_header[7] | astc_header[8] << 8 | astc_header[9] << 16; 423 | int height = astc_header[10] | astc_header[11] << 8 | astc_header[12] << 16; 424 | 425 | // Calculate payload size, then read it 426 | int bytes = (((width + block_x - 1) / block_x) * ((height + block_y - 1) / block_y)) * 16; 427 | if (stream.Read(VirtualTexture.buffer, 0, bytes) != bytes) 428 | { 429 | System.Console.WriteLine($"Unexpected end of ASTC stream."); 430 | } 431 | 432 | // Upload texture and set parameters 433 | __instance.Texture = new Texture2D(Engine.Graphics.GraphicsDevice, width, height, false, SurfaceFormat.Astc4x4EXT); 434 | __instance.Texture.SetData(VirtualTexture.buffer, 0, bytes); 435 | 436 | __instance.Texture.Width = width; 437 | __instance.Texture.Height = height; 438 | __instance.Width = width; 439 | __instance.Height = height; 440 | 441 | // Texture successfully loaded, don't allow the usual execution flow to proceed. 442 | return false; 443 | } 444 | } 445 | } 446 | 447 | // Fall-through to the usual execution flow to proceed. 448 | return true; 449 | } 450 | } 451 | } 452 | 453 | namespace System.Runtime.CompilerServices 454 | { 455 | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] 456 | public class IgnoresAccessChecksToAttribute : Attribute 457 | { 458 | public IgnoresAccessChecksToAttribute(string assemblyName) 459 | { 460 | AssemblyName = assemblyName; 461 | } 462 | 463 | public string AssemblyName { get; } 464 | } 465 | } 466 | --------------------------------------------------------------------------------