From 4e18434961ded978f6f17e4a1aa563a9330fc3f1 Mon Sep 17 00:00:00 2001 From: Maik Macho Date: Wed, 1 Nov 2017 19:19:57 +0100 Subject: [PATCH] Move FNA content hooks to ContentHelper.FNAHooks.Offline / Online --- XnaToFna.csproj | 3 + .../ContentTransformers/GZIPContentReader.cs | 39 +---- src/Content/FNAHooks.Offline.cs | 163 ++++++++++++++++++ src/Content/FNAHooks.Online.cs | 59 +++++++ src/Content/FNAHooks.cs | 75 ++++++++ src/Content/XNBContent.cs | 152 +--------------- 6 files changed, 310 insertions(+), 181 deletions(-) create mode 100644 src/Content/FNAHooks.Offline.cs create mode 100644 src/Content/FNAHooks.Online.cs create mode 100644 src/Content/FNAHooks.cs diff --git a/XnaToFna.csproj b/XnaToFna.csproj index 28ef887..6eaad80 100644 --- a/XnaToFna.csproj +++ b/XnaToFna.csproj @@ -51,6 +51,9 @@ + + + diff --git a/src/Content/ContentTransformers/GZIPContentReader.cs b/src/Content/ContentTransformers/GZIPContentReader.cs index 7f404be..9ad7d7a 100644 --- a/src/Content/ContentTransformers/GZIPContentReader.cs +++ b/src/Content/ContentTransformers/GZIPContentReader.cs @@ -22,44 +22,11 @@ namespace XnaToFna.ContentTransformers { public class GZipContentReader : ContentTypeReader { protected override ContentType Read(ContentReader input, ContentType existing) { - GZipContentReaderFNAHooks.Hook(); // We may need to refresh the hooks - who knows what the JIT's doing. - GZipContentReaderFNAHooks.ForcedStream = new GZipStream(input.BaseStream, CompressionMode.Decompress, true); + // We may need to refresh the hooks - who knows what the JIT's doing. + ContentHelper.FNAHooks.Online.Hook(); + ContentHelper.FNAHooks.Online.ForcedStream = new GZipStream(input.BaseStream, CompressionMode.Decompress, true); return input.ContentManager.Load("///XNATOFNA/gzip/" + input.AssetName); } } - - // Yo dawg, I heard you like patching... - public static class GZipContentReaderFNAHooks { - - public static bool Enabled = true; - private static bool IsHooked = false; - - public static void Hook() { - ContentHelper.FNAHooks.Hook(IsHooked, typeof(ContentManager), "OpenStream", ref orig_OpenStream); - IsHooked = true; - } - - public static Stream ForcedStream; - private delegate Stream d_OpenStream(ContentManager self, string name); - private static d_OpenStream orig_OpenStream; - private static Stream OpenStream(ContentManager self, string name) { - if (!Enabled) - return orig_OpenStream(self, name); - - bool isXTF = name.StartsWith("///XNATOFNA/"); - if (isXTF) - name = name.Substring(12); - - Stream stream; - if (isXTF && ForcedStream != null) { - stream = ForcedStream; - ForcedStream = stream; - } else { - stream = orig_OpenStream(self, name); - } - return stream; - } - - } } diff --git a/src/Content/FNAHooks.Offline.cs b/src/Content/FNAHooks.Offline.cs new file mode 100644 index 0000000..e986156 --- /dev/null +++ b/src/Content/FNAHooks.Offline.cs @@ -0,0 +1,163 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using Mono.Cecil; +using MonoMod; +using MonoMod.Detour; +using MonoMod.InlineRT; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using XnaToFna.ContentTransformers; + +namespace XnaToFna { + public static partial class ContentHelper { + // Yo dawg, I heard you like patching... + public static partial class FNAHooks { + public static class Offline { + + private static bool IsHooked = false; + public static bool Enabled = true; + + public static void Hook() { + if (!IsHooked) { + Dictionary> typeCreators = (Dictionary>) + typeof(ContentTypeReaderManager).GetField("typeCreators", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null); + typeCreators["Microsoft.Xna.Framework.Content.EffectReader, Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553"] + = () => new EffectTransformer(); + typeCreators["Microsoft.Xna.Framework.Content.SoundEffectReader"] // Games somehow don't use the full name for this one... + = () => new SoundEffectTransformer(); + } + + Hook(IsHooked, typeof(ContentManager), "GetContentReaderFromXnb", ref orig_GetContentReaderFromXnb); + + // Hooking constructors? Seems to work just fine on my machine(tm). + Hook(IsHooked, typeof(ContentReader), ".ctor[0]", ref orig_ctor_ContentReader); + + object gl = typeof(GraphicsDevice).GetField("GLDevice", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(Game.GraphicsDevice); + Type t_gl = gl.GetType(); + orig_SupportsDxt1 = (bool) t_gl.GetProperty("SupportsDxt1").GetValue(gl); + Hook(IsHooked, t_gl, "get_SupportsDxt1", ref orig_get_SupportsDxt1); + orig_SupportsS3tc = (bool) t_gl.GetProperty("SupportsS3tc").GetValue(gl); + Hook(IsHooked, t_gl, "get_SupportsS3tc", ref orig_get_SupportsS3tc); + + IsHooked = true; + } + + internal static MethodBase Find(Type type, string name) { + MethodBase found = type.GetMethod(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + if (found != null) + return found; + + if (name.StartsWith("get_") || name.StartsWith("set_")) { + PropertyInfo prop = type.GetProperty(name.Substring(4), BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + if (name[0] == 'g') + found = prop.GetGetMethod(true); + else + found = prop.GetSetMethod(true); + } + + return found; + } + internal static void Hook(bool isHooked, Type type, string name, ref T trampoline) { + MethodBase from; + MethodBase to; + if (name.StartsWith(".ctor[")) { + ConstructorInfo[] ctors = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + int index; + if (int.TryParse(name.Substring(6, name.Length - 6 - 1), out index)) + from = ctors[index]; + else + from = ctors[0]; + to = Find(typeof(T).DeclaringType, "ctor_" + type.Name); + } else { + from = Find(type, name); + to = Find(typeof(T).DeclaringType, name); + } + + // Keeps the detour intact. The JIT likes to revert our changes... + if (isHooked) { + RuntimeDetour.Refresh(from); + return; + } + + T tmp = + from + .Detour( + to + ); + if (trampoline == null) + trampoline = tmp; + } + + private delegate ContentReader d_GetContentReaderFromXnb(ContentManager self, string originalAssetName, ref Stream stream, BinaryReader xnbReader, char platform, Action recordDisposableObject); + private static d_GetContentReaderFromXnb orig_GetContentReaderFromXnb; + private static ContentReader GetContentReaderFromXnb(ContentManager self, string originalAssetName, ref Stream stream, BinaryReader xnbReader, char platform, Action recordDisposableObject) { + if (!Enabled) + return orig_GetContentReaderFromXnb(self, originalAssetName, ref stream, xnbReader, platform, recordDisposableObject); + + // output will be disposed with the ContentReader. + Stream output = File.OpenWrite(originalAssetName + ".tmp"); + + // We need to read the first 6 bytes ourselves (4 header bytes + version + flags). + long xnbPos = xnbReader.BaseStream.Position; + xnbReader.BaseStream.Seek(0, SeekOrigin.Begin); + + using (BinaryWriter writer = new BinaryWriter(output, Encoding.ASCII, true)) { + writer.Write(xnbReader.ReadBytes(5)); + + byte flags = xnbReader.ReadByte(); + flags = (byte) (flags & ~0x80); // Remove the compression flag. + writer.Write(flags); + + writer.Write(0); // Write size = 0 for now, will be updated later. + } + + xnbReader.BaseStream.Seek(xnbPos, SeekOrigin.Begin); + + ContentReader reader = orig_GetContentReaderFromXnb(self, originalAssetName, ref stream, xnbReader, platform, recordDisposableObject); + ((CopyingStream) reader.BaseStream).Output = output; + return reader; + } + + private delegate void d_ctor_ContentReader(ContentReader self, ContentManager manager, Stream stream, GraphicsDevice graphicsDevice, string assetName, int version, char platform, Action recordDisposableObject); + private static d_ctor_ContentReader orig_ctor_ContentReader; + private static void ctor_ContentReader(ContentReader self, ContentManager manager, Stream stream, GraphicsDevice graphicsDevice, string assetName, int version, char platform, Action recordDisposableObject) { + if (!Enabled) { + orig_ctor_ContentReader(self, manager, stream, graphicsDevice, assetName, version, platform, recordDisposableObject); + return; + } + + // What... what is this? Where am I? *Who* am I?! + stream = new CopyingStream(stream, null); + orig_ctor_ContentReader(self, manager, stream, graphicsDevice, assetName, version, platform, recordDisposableObject); + } + + public static bool? SupportsDxt1; + private static bool orig_SupportsDxt1; + private delegate bool d_get_SupportsDxt1(object self); + private static d_get_SupportsDxt1 orig_get_SupportsDxt1; + private static bool get_SupportsDxt1(object self) { + return SupportsDxt1 ?? orig_SupportsDxt1; + } + + public static bool? SupportsS3tc; + private static bool orig_SupportsS3tc; + private delegate bool d_get_SupportsS3tc(object self); + private static d_get_SupportsS3tc orig_get_SupportsS3tc; + private static bool get_SupportsS3tc(object self) { + return SupportsS3tc ?? orig_SupportsS3tc; + } + + } + } + } +} diff --git a/src/Content/FNAHooks.Online.cs b/src/Content/FNAHooks.Online.cs new file mode 100644 index 0000000..802b6f0 --- /dev/null +++ b/src/Content/FNAHooks.Online.cs @@ -0,0 +1,59 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using Mono.Cecil; +using MonoMod; +using MonoMod.Detour; +using MonoMod.InlineRT; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using XnaToFna.ContentTransformers; + +namespace XnaToFna { + public static partial class ContentHelper { + // Yo dawg, I heard you like patching... + public static partial class FNAHooks { + public static class Online { + + public static bool Enabled = true; + private static bool IsHooked = false; + + public static void Hook() { + ContentHelper.FNAHooks.Hook(IsHooked, typeof(ContentManager), "OpenStream", ref orig_OpenStream); + IsHooked = true; + } + + public static Stream ForcedStream; + private delegate Stream d_OpenStream(ContentManager self, string name); + private static d_OpenStream orig_OpenStream; + private static Stream OpenStream(ContentManager self, string name) { + if (!Enabled) + return orig_OpenStream(self, name); + + bool isXTF = name.StartsWith("///XNATOFNA/"); + if (isXTF) + name = name.Substring(17); // Removes //XNATOFNA/abcd/ + + Stream stream; + if (isXTF && ForcedStream != null) { + stream = ForcedStream; + ForcedStream = stream; + } else { + stream = orig_OpenStream(self, name); + } + return stream; + } + + } + } + } +} diff --git a/src/Content/FNAHooks.cs b/src/Content/FNAHooks.cs new file mode 100644 index 0000000..fa6bb9e --- /dev/null +++ b/src/Content/FNAHooks.cs @@ -0,0 +1,75 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using Mono.Cecil; +using MonoMod; +using MonoMod.Detour; +using MonoMod.InlineRT; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using XnaToFna.ContentTransformers; + +namespace XnaToFna { + public static partial class ContentHelper { + // Yo dawg, I heard you like patching... + public static partial class FNAHooks { + + internal static MethodBase Find(Type type, string name) { + MethodBase found = type.GetMethod(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + if (found != null) + return found; + + if (name.StartsWith("get_") || name.StartsWith("set_")) { + PropertyInfo prop = type.GetProperty(name.Substring(4), BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + if (name[0] == 'g') + found = prop.GetGetMethod(true); + else + found = prop.GetSetMethod(true); + } + + return found; + } + + internal static void Hook(bool isHooked, Type type, string name, ref T trampoline) { + MethodBase from; + MethodBase to; + if (name.StartsWith(".ctor[")) { + ConstructorInfo[] ctors = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + int index; + if (int.TryParse(name.Substring(6, name.Length - 6 - 1), out index)) + from = ctors[index]; + else + from = ctors[0]; + to = Find(typeof(T).DeclaringType, "ctor_" + type.Name); + } else { + from = Find(type, name); + to = Find(typeof(T).DeclaringType, name); + } + + // Keeps the detour intact. The JIT likes to revert our changes... + if (isHooked) { + RuntimeDetour.Refresh(from); + return; + } + + T tmp = + from + .Detour( + to + ); + if (trampoline == null) + trampoline = tmp; + } + + } + } +} diff --git a/src/Content/XNBContent.cs b/src/Content/XNBContent.cs index b644802..855ac10 100644 --- a/src/Content/XNBContent.cs +++ b/src/Content/XNBContent.cs @@ -34,7 +34,7 @@ public static void TransformContent(string path) { Log($"[TransformContent] Transforming {path}"); - FNAHooks.Hook(); + FNAHooks.Offline.Hook(); // This may fail horribly if the content is a ValueType (struct). object obj = Game.Content.Load(path/*.Substring(0, path.Length - 4)*/); @@ -80,14 +80,14 @@ public static void TransformContent(string path) { UpdateXNBSize(path); } - FNAHooks.Enabled = false; + FNAHooks.Offline.Enabled = false; // If we just loaded a texture, reload and dump it as PNG. /* if (obj is Texture2D) { // FNA can't save DXT1, DXT3 and DXT5 textures... unless we force conversion. - FNAContentManagerHooks.SupportsDxt1 = false; - FNAContentManagerHooks.SupportsS3tc = false; + FNAHooks.Offline.SupportsDxt1 = false; + FNAHooks.Offline.SupportsS3tc = false; using (Texture2D tex = Game.Content.Load(path)) { Log($"[TransformContent] Dumping texture, original format: {((Texture2D) obj).Format}"); if (File.Exists(path + ".png")) @@ -95,12 +95,12 @@ public static void TransformContent(string path) { using (Stream stream = File.OpenWrite(path + ".png")) tex.SaveAsPng(stream, tex.Width, tex.Height); } - FNAContentManagerHooks.SupportsDxt1 = null; - FNAContentManagerHooks.SupportsS3tc = null; + FNAHooks.Offline.SupportsDxt1 = null; + FNAHooks.Offline.SupportsS3tc = null; } */ - FNAHooks.Enabled = true; + FNAHooks.Offline.Enabled = true; } public static void UpdateXNBSize(string path, uint size = 0) { @@ -114,143 +114,5 @@ public static void UpdateXNBSize(string path, uint size = 0) { } } - // Yo dawg, I heard you like patching... - public static class FNAHooks { - - private static bool IsHooked = false; - public static bool Enabled = true; - - public static void Hook() { - if (!IsHooked) { - Dictionary> typeCreators = (Dictionary>) - typeof(ContentTypeReaderManager).GetField("typeCreators", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null); - typeCreators["Microsoft.Xna.Framework.Content.EffectReader, Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553"] - = () => new EffectTransformer(); - typeCreators["Microsoft.Xna.Framework.Content.SoundEffectReader"] // Games somehow don't use the full name for this one... - = () => new SoundEffectTransformer(); - } - - Hook(IsHooked, typeof(ContentManager), "GetContentReaderFromXnb", ref orig_GetContentReaderFromXnb); - - // Hooking constructors? Seems to work just fine on my machine(tm). - Hook(IsHooked, typeof(ContentReader), ".ctor[0]", ref orig_ctor_ContentReader); - - object gl = typeof(GraphicsDevice).GetField("GLDevice", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(Game.GraphicsDevice); - Type t_gl = gl.GetType(); - orig_SupportsDxt1 = (bool) t_gl.GetProperty("SupportsDxt1").GetValue(gl); - Hook(IsHooked, t_gl, "get_SupportsDxt1", ref orig_get_SupportsDxt1); - orig_SupportsS3tc = (bool) t_gl.GetProperty("SupportsS3tc").GetValue(gl); - Hook(IsHooked, t_gl, "get_SupportsS3tc", ref orig_get_SupportsS3tc); - - IsHooked = true; - } - - internal static MethodBase Find(Type type, string name) { - MethodBase found = type.GetMethod(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); - if (found != null) - return found; - - if (name.StartsWith("get_") || name.StartsWith("set_")) { - PropertyInfo prop = type.GetProperty(name.Substring(4), BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); - if (name[0] == 'g') - found = prop.GetGetMethod(true); - else - found = prop.GetSetMethod(true); - } - - return found; - } - internal static void Hook(bool isHooked, Type type, string name, ref T trampoline) { - MethodBase from; - MethodBase to; - if (name.StartsWith(".ctor[")) { - ConstructorInfo[] ctors = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - int index; - if (int.TryParse(name.Substring(6, name.Length - 6 - 1), out index)) - from = ctors[index]; - else - from = ctors[0]; - to = Find(typeof(T).DeclaringType, "ctor_" + type.Name); - } else { - from = Find(type, name); - to = Find(typeof(T).DeclaringType, name); - } - - // Keeps the detour intact. The JIT likes to revert our changes... - if (isHooked) { - RuntimeDetour.Refresh(from); - return; - } - - T tmp = - from - .Detour( - to - ); - if (trampoline == null) - trampoline = tmp; - } - - private delegate ContentReader d_GetContentReaderFromXnb(ContentManager self, string originalAssetName, ref Stream stream, BinaryReader xnbReader, char platform, Action recordDisposableObject); - private static d_GetContentReaderFromXnb orig_GetContentReaderFromXnb; - private static ContentReader GetContentReaderFromXnb(ContentManager self, string originalAssetName, ref Stream stream, BinaryReader xnbReader, char platform, Action recordDisposableObject) { - if (!Enabled) - return orig_GetContentReaderFromXnb(self, originalAssetName, ref stream, xnbReader, platform, recordDisposableObject); - - // output will be disposed with the ContentReader. - Stream output = File.OpenWrite(originalAssetName + ".tmp"); - - // We need to read the first 6 bytes ourselves (4 header bytes + version + flags). - long xnbPos = xnbReader.BaseStream.Position; - xnbReader.BaseStream.Seek(0, SeekOrigin.Begin); - - using (BinaryWriter writer = new BinaryWriter(output, Encoding.ASCII, true)) { - writer.Write(xnbReader.ReadBytes(5)); - - byte flags = xnbReader.ReadByte(); - flags = (byte) (flags & ~0x80); // Remove the compression flag. - writer.Write(flags); - - writer.Write(0); // Write size = 0 for now, will be updated later. - } - - xnbReader.BaseStream.Seek(xnbPos, SeekOrigin.Begin); - - ContentReader reader = orig_GetContentReaderFromXnb(self, originalAssetName, ref stream, xnbReader, platform, recordDisposableObject); - ((CopyingStream) reader.BaseStream).Output = output; - return reader; - } - - private delegate void d_ctor_ContentReader(ContentReader self, ContentManager manager, Stream stream, GraphicsDevice graphicsDevice, string assetName, int version, char platform, Action recordDisposableObject); - private static d_ctor_ContentReader orig_ctor_ContentReader; - private static void ctor_ContentReader(ContentReader self, ContentManager manager, Stream stream, GraphicsDevice graphicsDevice, string assetName, int version, char platform, Action recordDisposableObject) { - if (!Enabled) { - orig_ctor_ContentReader(self, manager, stream, graphicsDevice, assetName, version, platform, recordDisposableObject); - return; - } - - // What... what is this? Where am I? *Who* am I?! - stream = new CopyingStream(stream, null); - orig_ctor_ContentReader(self, manager, stream, graphicsDevice, assetName, version, platform, recordDisposableObject); - } - - public static bool? SupportsDxt1; - private static bool orig_SupportsDxt1; - private delegate bool d_get_SupportsDxt1(object self); - private static d_get_SupportsDxt1 orig_get_SupportsDxt1; - private static bool get_SupportsDxt1(object self) { - return SupportsDxt1 ?? orig_SupportsDxt1; - } - - public static bool? SupportsS3tc; - private static bool orig_SupportsS3tc; - private delegate bool d_get_SupportsS3tc(object self); - private static d_get_SupportsS3tc orig_get_SupportsS3tc; - private static bool get_SupportsS3tc(object self) { - return SupportsS3tc ?? orig_SupportsS3tc; - } - - } - } }