diff --git a/.github/workflows/dotnet-5.yml b/.github/workflows/dotnet-5.yml deleted file mode 100644 index 9bdeb9a..0000000 --- a/.github/workflows/dotnet-5.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: .NET 5 - -on: - push: - paths-ignore: - - '*.md' - pull_request: - paths-ignore: - - '*.md' - -jobs: - build: - - runs-on: windows-latest - env: - DOTNET_CLI_TELEMETRY_OPTOUT : true - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 # avoid shallow clone so nbgv can do its work. - - name: Setup .NET - uses: actions/setup-dotnet@v1 - with: - dotnet-version: '5.0.402' - - name: Install dependencies - run: dotnet restore - - name: Build - run: dotnet build --configuration Release --no-restore - - name: Test - run: dotnet test --no-restore --verbosity normal - - name: Publish - run: dotnet publish -c Release -r win-x64 -P:PublishSingleFile=true -p:PublishTrimmed=false --self-contained false - - name: Upload artifact - uses: actions/upload-artifact@v2 - with: - name: launcher - path: Launcher\bin\Release\net5.0-windows\win-x64\publish\ - - release: - if: | - github.event.action != 'pull_request' && - github.ref == 'refs/heads/master' && - github.repository == 'num0005/Osoyoos-Launcher' - needs: build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - uses: actions/download-artifact@v2 - with: - name: launcher - path: Launcher_win64 - - uses: ncipollo/release-action@v1 - with: - prerelease: true - token: ${{ secrets.GITHUB_TOKEN }} - artifacts: Launcher_win64/* - tag: ${{ github.run_id }} - diff --git a/.github/workflows/dotnet-6.yml b/.github/workflows/dotnet-6.yml index 36239d9..2dd105d 100644 --- a/.github/workflows/dotnet-6.yml +++ b/.github/workflows/dotnet-6.yml @@ -34,7 +34,7 @@ jobs: uses: actions/upload-artifact@v2 with: name: launcher - path: Launcher\bin\Release\net6.0-windows\win-x64\publish\ + path: Launcher\bin\Release\x64\net6.0-windows\win-x64\publish\ release: if: | diff --git a/.github/workflows/dotnet-next.yml b/.github/workflows/dotnet-next.yml index f55042d..7c2fda1 100644 --- a/.github/workflows/dotnet-next.yml +++ b/.github/workflows/dotnet-next.yml @@ -13,7 +13,7 @@ jobs: runs-on: windows-latest strategy: matrix: # https://stackoverflow.com/a/68940067/16608030 hopefully this still works - dotnet: [ {version: '5.0.x', path: net5.0-windows}, {version: '6.0.x', path: net6.0-windows} ] + dotnet: [ {version: '6.0.x', path: net6.0-windows}, {version: '8.0.x', path: net8.0-windows}, {version: '9.0.x', path: net9.0-windows} ] env: DOTNET_CLI_TELEMETRY_OPTOUT : true steps: @@ -37,6 +37,6 @@ jobs: uses: actions/upload-artifact@v2 with: name: launcher - path: Launcher\bin\Release\${{ matrix.dotnet.path}}\win-x64\publish\ + path: Launcher\bin\x64\Release\${{ matrix.dotnet.path}}\win-x64\publish\ diff --git a/.gitignore b/.gitignore index 4adafce..93cbf6a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,14 @@ -*/bin/* +/Launcher/bin/* */obj/* *.vs/* *.zip *.exe *.dll +!/OsoyoosMB/ref/ManagedBlam.dll !/packages/Resource.Embedder.2.1.1/tasks/net46/*.dll /icon_assets.7z /Launcher/Properties/launchSettings.json +/OsoyoosMB/.vs/* +/OsoyoosMB/OsoyoosMB/obj/* +/OsoyoosMB/OsoyoosMB/bin/* +/OsoyoosMB/OsoyoosMB/.vs/* \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md index baa3adc..129515f 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (c) 2017 num0005 +Copyright (c) 2017 num0005 Copyright (c) 2022 Osoyoos Launcher Contributors Unless otherwise noted files in this repository are licensed under the terms of the MIT license (reproduced below). diff --git a/Launcher/MainWindow.xaml b/Launcher/MainWindow.xaml index 369fced..b5c2ec7 100644 --- a/Launcher/MainWindow.xaml +++ b/Launcher/MainWindow.xaml @@ -30,6 +30,11 @@ + + + + + @@ -521,6 +526,17 @@ + + diff --git a/Launcher/MainWindow.xaml.cs b/Launcher/MainWindow.xaml.cs index ab7a0d9..1d0b378 100644 --- a/Launcher/MainWindow.xaml.cs +++ b/Launcher/MainWindow.xaml.cs @@ -16,6 +16,7 @@ using System.Xml.Linq; using System.Threading; using System.Reflection; +using System.Runtime.Serialization; namespace ToolkitLauncher { @@ -414,6 +415,41 @@ public enum reach_sound_import_type ui_music } + [TypeConverter(typeof(EnumDescriptionTypeConverter))] + public enum BitmapCompressionType + { + [Description("Default")] + [EnumMember(Value = "0")] + Default, + [Description("DXT5")] + [EnumMember(Value = "15")] + DXT5, + [Description("DXT1")] + [EnumMember(Value = "13")] + DXT1, + [Description("24-bit Color + 8-bit Alpha")] + [EnumMember(Value = "16")] + Color24BitAlpha8Bit, + [Description("Best Compressed Color")] + [EnumMember(Value = "1")] + BestCompressedColor, + [Description("Uncompressed")] + [EnumMember(Value = "2")] + Uncompressed + } + + // Return the associated EnumMember string value given the selected value of the compression type above + public static class EnumExtensions + { + public static string GetEnumMemberValue(this Enum enumValue) + { + var fieldInfo = enumValue.GetType().GetField(enumValue.ToString()); + var attribute = (EnumMemberAttribute)fieldInfo.GetCustomAttributes(typeof(EnumMemberAttribute), false)[0]; + + return attribute.Value; + } + } + /// /// Interaction logic for MainWindow.xaml /// @@ -1081,7 +1117,8 @@ class BitmapCompile private async void CompileImage(object sender, RoutedEventArgs e) { string listEntry = BitmapCompile.bitmapType[bitmap_compile_type.SelectedIndex]; - await toolkit.ImportBitmaps(compile_image_path.Text, listEntry, debug_plate.IsChecked is true); + BitmapCompressionType selectedComp = (BitmapCompressionType)bitmap_compression_type.SelectedItem; // Convert selected compression string to enum value + await toolkit.ImportBitmaps(compile_image_path.Text, listEntry, selectedComp.GetEnumMemberValue(), toolkit.GetDataDirectory(), debug_plate.IsChecked is true); } private async void PackageLevel(object sender, RoutedEventArgs e) diff --git a/Launcher/ToolkitInterface/H1AToolkit.cs b/Launcher/ToolkitInterface/H1AToolkit.cs index 40e3edd..a1af29e 100644 --- a/Launcher/ToolkitInterface/H1AToolkit.cs +++ b/Launcher/ToolkitInterface/H1AToolkit.cs @@ -170,7 +170,7 @@ public async Task JMSFromFBX(string fbxPath, string jmsPath, string geo_class) /// /// /// - override public async Task ImportBitmaps(string path, string type, bool debug_plate) + override public async Task ImportBitmaps(string path, string type, string compression, string data_path, bool debug_plate) { await RunTool(ToolType.Tool, new List() { "bitmaps", path, type, debug_plate.ToString() }); } diff --git a/Launcher/ToolkitInterface/H1Toolkit.cs b/Launcher/ToolkitInterface/H1Toolkit.cs index 5d0d79f..01e5836 100644 --- a/Launcher/ToolkitInterface/H1Toolkit.cs +++ b/Launcher/ToolkitInterface/H1Toolkit.cs @@ -78,7 +78,7 @@ public override async Task ImportSound(string path, string platform, string bitr await RunTool(ToolType.Tool, new List() { "sounds", path, platform, bitrate }); } - override public async Task ImportBitmaps(string path, string type, bool debug_plate) + override public async Task ImportBitmaps(string path, string type, string compression, string data_path, bool debug_plate) { await RunTool(ToolType.Tool, new List() { "bitmaps", path }); } diff --git a/Launcher/ToolkitInterface/H2AToolkit.cs b/Launcher/ToolkitInterface/H2AToolkit.cs index c170a5b..0cea224 100644 --- a/Launcher/ToolkitInterface/H2AToolkit.cs +++ b/Launcher/ToolkitInterface/H2AToolkit.cs @@ -60,7 +60,7 @@ protected override string sapienWindowClass get => "Sapien"; } - override public async Task ImportBitmaps(string path, string type, bool debug_plate) + override public async Task ImportBitmaps(string path, string type, string compression, string data_path, bool debug_plate) { await RunTool(ToolType.Tool, new() { "bitmaps", path, type, debug_plate.ToString() }); } diff --git a/Launcher/ToolkitInterface/H2Toolkit.cs b/Launcher/ToolkitInterface/H2Toolkit.cs index dfb7cd8..0c49981 100644 --- a/Launcher/ToolkitInterface/H2Toolkit.cs +++ b/Launcher/ToolkitInterface/H2Toolkit.cs @@ -19,7 +19,7 @@ protected virtual string sapienWindowClass get => "Sapien"; } - override public async Task ImportBitmaps(string path, string type, bool debug_plate) + override public async Task ImportBitmaps(string path, string type, string compression, string data_path, bool debug_plate) { string bitmaps_command = "bitmaps"; if (Profile.CommunityTools || Profile.BuildType == build_type.release_mcc) diff --git a/Launcher/ToolkitInterface/H3Toolkit.cs b/Launcher/ToolkitInterface/H3Toolkit.cs index 72bda74..c2ee455 100644 --- a/Launcher/ToolkitInterface/H3Toolkit.cs +++ b/Launcher/ToolkitInterface/H3Toolkit.cs @@ -23,9 +23,19 @@ protected string sapienWindowClass get => "Sapien"; } - override public async Task ImportBitmaps(string path, string type, bool debug_plate) + override public async Task ImportBitmaps(string path, string type, string compression, string data_path, bool debug_plate) { + // First import await RunTool(ToolType.Tool, new List() { debug_plate ? "bitmaps-debug" : "bitmaps", path }); + + // Call managedblam + bool MB_success = ManagedBlam.RunMBBitmaps(BaseDirectory, path, compression); + + if (MB_success) + { + // Reimport bitmaps + await RunTool(ToolType.Tool, new List() { debug_plate ? "bitmaps-debug" : "bitmaps", path }); + } } override public async Task ImportUnicodeStrings(string path) diff --git a/Launcher/ToolkitInterface/ToolkitBase.cs b/Launcher/ToolkitInterface/ToolkitBase.cs index 50883e3..b69f2e1 100644 --- a/Launcher/ToolkitInterface/ToolkitBase.cs +++ b/Launcher/ToolkitInterface/ToolkitBase.cs @@ -121,7 +121,7 @@ public record ImportArgs( /// Directory containing the bitmaps /// The type of bitmap to import it by /// MCC H1A: Whether or not we dump plate data to the data folder for inspection - public abstract Task ImportBitmaps(string path, string type, bool debug_plate = false); + public abstract Task ImportBitmaps(string path, string type, string compression, string data_path, bool debug_plate = false); /// /// How build cache will handle resources diff --git a/Launcher/ToolkitLauncher.csproj b/Launcher/ToolkitLauncher.csproj index 4fa7db2..05803c8 100644 --- a/Launcher/ToolkitLauncher.csproj +++ b/Launcher/ToolkitLauncher.csproj @@ -21,6 +21,7 @@ false true true + AnyCPU;x64;x86 icon.ico diff --git a/Launcher/Utility/ManagedBlam.cs b/Launcher/Utility/ManagedBlam.cs new file mode 100644 index 0000000..4675636 --- /dev/null +++ b/Launcher/Utility/ManagedBlam.cs @@ -0,0 +1,51 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Windows.Forms; + +namespace ToolkitLauncher.Utility +{ + public class ManagedBlam + { + public static bool RunMBBitmaps(string ek_path, string tag_path, string compression_type) + { + string exe_path = Path.Combine(ek_path, @"bin\OsoyoosMB.exe"); + + if (File.Exists(exe_path)) + { + ProcessStartInfo startInfo = new ProcessStartInfo + { + FileName = exe_path, + Arguments = $"getbitmapdata \"{ek_path}\" \"{tag_path.TrimEnd('\\')}\" \"{compression_type}\"", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + + try + { + // Start the process + using (System.Diagnostics.Process process = System.Diagnostics.Process.Start(startInfo)) + { + process.WaitForExit(); + return true; + } + } + catch + { + // Handle any errors that might occur + MessageBox.Show("Unspecified ManagedBlam error.\nBitmaps have still been imported, but settings will not be applied.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + return false; + } + } + else + { + // User likely hasnt put the second exe in the right place + MessageBox.Show($"Error: Cannot find \"{exe_path}\".\nMake sure the OsoyoosMB.exe is in your editing kit's \"bin\" folder.\nBitmaps have still been imported, but settings will not be applied.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + return false; + } + + } + } +} diff --git a/OsoyoosMB/OsoyoosMB/App.config b/OsoyoosMB/OsoyoosMB/App.config new file mode 100644 index 0000000..193aecc --- /dev/null +++ b/OsoyoosMB/OsoyoosMB/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/OsoyoosMB/OsoyoosMB/BitmapSettings.cs b/OsoyoosMB/OsoyoosMB/BitmapSettings.cs new file mode 100644 index 0000000..0be74ac --- /dev/null +++ b/OsoyoosMB/OsoyoosMB/BitmapSettings.cs @@ -0,0 +1,318 @@ +using Bungie; +using Bungie.Tags; +using OsoyoosMB.Utils; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace OsoyoosMB +{ + internal class BitmapSettings + { + public static void GetBitmapData(string ek_path, string tag_folder, string compress_value) + { + // Get all bitmaps + string tag_folder_full = Path.Combine(ek_path, "tags", tag_folder); + string[] all_bitmaps = Directory.GetFiles(tag_folder_full, "*.bitmap"); + + // Define bitmap name suffixes for anything non-diffuse + string[] normal_suffixes = + { + "_normal.bitmap", + "_normalmap.bitmap", + "_nm.bitmap", + "_n.bitmap", + "_zbump.bitmap" + }; + + string[] bump_suffixes = + { + "_bump.bitmap", + "_bmp.bitmap", + "_bp.bitmap", + "_b.bitmap" + }; + + string[] material_suffixes = + { + "_material.bitmap", + "_materialmap.bitmap", + "_mat.bitmap", + "_m.bitmap", + "_orm.bitmap", + "_ormh.bitmap", + "_rmo.bitmap", + "_rmoh.bitmap", + "_mro.bitmap", + "_mroh.bitmap" + }; + + List diffuse_bitmaps = new List(); + List normal_bitmaps = new List(); + List bump_bitmaps = new List(); + List material_bitmaps = new List(); + + foreach (string bitmap in all_bitmaps) + { + if (normal_suffixes.Any(suffix => bitmap.EndsWith(suffix))) + { + // Bitmap file is a normal map + normal_bitmaps.Add(bitmap); + } + else if (bump_suffixes.Any(suffix => bitmap.EndsWith(suffix))) + { + // Bitmap file is a bump map + bump_bitmaps.Add(bitmap); + } + else if (material_suffixes.Any(suffix => bitmap.EndsWith(suffix))) + { + // Bitmap file is a material map + material_bitmaps.Add(bitmap); + } + else + { + // Treat bitmap as diffuse + diffuse_bitmaps.Add(bitmap); + } + } + + ApplyBitmSettings(diffuse_bitmaps.ToArray(), normal_bitmaps.ToArray(), bump_bitmaps.ToArray(), material_bitmaps.ToArray(), ek_path, int.Parse(compress_value)); + } + + // These are the block/field names within the tag file + public static class TagFieldConstants + { + public const string Usage = "LongEnum:Usage"; + public const string CurveMode = "CharEnum:curve mode"; + public const string BitmapFormat = "ShortEnum:force bitmap format"; + public const string MipMapLevel = "CharInteger:max mipmap level"; + public const string UsageOverride = "Block:usage override"; + public const string Gamma = "Block:usage override[0]/Real:source gamma"; + public const string BitmapCurve = "Block:usage override[0]/LongEnum:bitmap curve"; + public const string Flags = "Block:usage override[0]/ByteFlags:flags"; + public const string MipLimit = "Block:usage override[0]/ShortInteger:mipmap limit"; + public const string UsageFormat = "Block:usage override[0]/LongEnum:bitmap format"; + public const string DicerFlags = "Block:usage override[0]/WordFlags:dicer flags"; + public const string Slicer = "Block:usage override[0]/CharEnum:slicer"; + } + + public static void ApplyBitmSettings(string[] diffuses, string[] normals, string[] bumps, string[] materials, string ek_path, int compress_value) + { + // EK "tags" folder location + string base_path = Path.Combine(ek_path, "tags"); + + // Initialize ManagedBlam + ManagedBlamSystem.InitializeProject(InitializationType.TagsOnly, ek_path); + + foreach (string bitmap_full in diffuses) + { + // Get correctly formatted path by only taking tags-relative path and removing extension + string bitmap_path = MBHelpers.GetBitmapRelativePath(base_path, bitmap_full); + + var tag_path = TagPath.FromPathAndType(bitmap_path, "bitm*"); + + using (var tagFile = new TagFile(tag_path)) + { + // Set curve mode to pretty + var curve = (TagFieldEnum)tagFile.SelectField(TagFieldConstants.CurveMode); + curve.Value = 2; + + // Set compression to UI-selected value + var compression = (TagFieldEnum)tagFile.SelectField(TagFieldConstants.BitmapFormat); + compression.Value = compress_value; + + // Set max mipmap to -1 + var mip_limit = (TagFieldElementInteger)tagFile.SelectField(TagFieldConstants.MipMapLevel); + mip_limit.Data = -1; + + // Check if bitmap already has overrides entry, if so remove it + int override_count = ((TagFieldBlock)tagFile.SelectField(TagFieldConstants.UsageOverride)).Elements.Count(); + if (override_count > 0) + { + ((TagFieldBlock)tagFile.SelectField(TagFieldConstants.UsageOverride)).RemoveAllElements(); + } + + // Add override entry + ((TagFieldBlock)tagFile.SelectField(TagFieldConstants.UsageOverride)).AddElement(); + + // Set gamma + var gamma = (TagFieldElementSingle)tagFile.SelectField(TagFieldConstants.Gamma); + gamma.Data = 2.2f; + + // Set bitmap curve to sRGB + var bitmap_curve = (TagFieldEnum)tagFile.SelectField(TagFieldConstants.BitmapCurve); + bitmap_curve.Value = 5; + + // Set ignore curve override flag + var flags = (TagFieldFlags)tagFile.SelectField(TagFieldConstants.Flags); + flags.RawValue = 1; + + // Set mipmap limit + var mip_limit_override = (TagFieldElementInteger)tagFile.SelectField(TagFieldConstants.MipLimit); + mip_limit_override.Data = -1; + + // Set compression to UI-selected value + var override_compression = (TagFieldEnum)tagFile.SelectField(TagFieldConstants.UsageFormat); + override_compression.Value = compress_value; + + tagFile.Save(); + } + } + + foreach (string bitmap_full in normals) + { + // Get correctly formatted path by only taking tags-relative path and removing extension + string bitmap_path = MBHelpers.GetBitmapRelativePath(base_path, bitmap_full); + + var tag_path = TagPath.FromPathAndType(bitmap_path, "bitm*"); + + using (var tagFile = new TagFile(tag_path)) + { + // Set usage to zbump + var usage = (TagFieldEnum)tagFile.SelectField(TagFieldConstants.Usage); + usage.Value = 17; + + // Set curve mode to pretty + var curve = (TagFieldEnum)tagFile.SelectField(TagFieldConstants.CurveMode); + curve.Value = 2; + + // Set compression to DXN + var compression = (TagFieldEnum)tagFile.SelectField(TagFieldConstants.BitmapFormat); + compression.Value = 49; + + // Set max mipmap to -1 + var mip_limit = (TagFieldElementInteger)tagFile.SelectField(TagFieldConstants.MipMapLevel); + mip_limit.Data = -1; + + // Check if bitmap already has overrides entry, if so remove it + int override_count = ((TagFieldBlock)tagFile.SelectField(TagFieldConstants.UsageOverride)).Elements.Count(); + if (override_count > 0) + { + ((TagFieldBlock)tagFile.SelectField(TagFieldConstants.UsageOverride)).RemoveAllElements(); + } + + // Add override entry + ((TagFieldBlock)tagFile.SelectField(TagFieldConstants.UsageOverride)).AddElement(); + + // Set gamma + var gamma = (TagFieldElementSingle)tagFile.SelectField(TagFieldConstants.Gamma); + gamma.Data = 1.0f; + + // Set bitmap curve to linear + var bitmap_curve = (TagFieldEnum)tagFile.SelectField(TagFieldConstants.BitmapCurve); + bitmap_curve.Value = 3; + + // Set ignore curve override flag + var flags = (TagFieldFlags)tagFile.SelectField(TagFieldConstants.Flags); + flags.RawValue = 1; + + // Set Unsigned flag + var dicer_flags = (TagFieldFlags)tagFile.SelectField(TagFieldConstants.DicerFlags); + dicer_flags.RawValue = 16; + + // Set mipmap limit + var mip_limit_override = (TagFieldElementInteger)tagFile.SelectField(TagFieldConstants.MipLimit); + mip_limit_override.Data = -1; + + // Set compression to DXN + var override_compression = (TagFieldEnum)tagFile.SelectField(TagFieldConstants.UsageFormat); + override_compression.Value = 49; + + tagFile.Save(); + } + } + + foreach (string bitmap_full in bumps) + { + // Get correctly formatted path by only taking tags-relative path and removing extension + string bitmap_path = MBHelpers.GetBitmapRelativePath(base_path, bitmap_full); + + var tag_path = TagPath.FromPathAndType(bitmap_path, "bitm*"); + + using (var tagFile = new TagFile(tag_path)) + { + // Set usage to bump + var usage = (TagFieldEnum)tagFile.SelectField(TagFieldConstants.Usage); + usage.Value = 2; + + // Set curve mode to pretty + var curve = (TagFieldEnum)tagFile.SelectField(TagFieldConstants.CurveMode); + curve.Value = 2; + + // Set compression to best compressed bump + var compression = (TagFieldEnum)tagFile.SelectField(TagFieldConstants.BitmapFormat); + compression.Value = 3; + + // Set max mipmap to -1 + var mip_limit = (TagFieldElementInteger)tagFile.SelectField(TagFieldConstants.MipMapLevel); + mip_limit.Data = -1; + + // Check if bitmap already has overrides entry, if so remove it + int override_count = ((TagFieldBlock)tagFile.SelectField(TagFieldConstants.UsageOverride)).Elements.Count(); + if (override_count > 0) + { + ((TagFieldBlock)tagFile.SelectField(TagFieldConstants.UsageOverride)).RemoveAllElements(); + } + + tagFile.Save(); + } + } + + foreach (string bitmap_full in materials) + { + // Get correctly formatted path by only taking tags-relative path and removing extension + string bitmap_path = MBHelpers.GetBitmapRelativePath(base_path, bitmap_full); + + var tag_path = TagPath.FromPathAndType(bitmap_path, "bitm*"); + + using (var tagFile = new TagFile(tag_path)) + { + // Set curve mode to pretty + var curve = (TagFieldEnum)tagFile.SelectField(TagFieldConstants.CurveMode); + curve.Value = 2; + + // Set compression to UI-selected value + var compression = (TagFieldEnum)tagFile.SelectField(TagFieldConstants.BitmapFormat); + compression.Value = compress_value; + + // Set max mipmap to -1 + var mip_limit = (TagFieldElementInteger)tagFile.SelectField(TagFieldConstants.MipMapLevel); + mip_limit.Data = -1; + + // Check if bitmap already has overrides entry, if so remove it + int override_count = ((TagFieldBlock)tagFile.SelectField(TagFieldConstants.UsageOverride)).Elements.Count(); + if (override_count > 0) + { + ((TagFieldBlock)tagFile.SelectField(TagFieldConstants.UsageOverride)).RemoveAllElements(); + } + + // Add override entry + ((TagFieldBlock)tagFile.SelectField(TagFieldConstants.UsageOverride)).AddElement(); + + // Set gamma + var gamma = (TagFieldElementSingle)tagFile.SelectField(TagFieldConstants.Gamma); + gamma.Data = 1.0f; + + // Set bitmap curve to linear + var bitmap_curve = (TagFieldEnum)tagFile.SelectField(TagFieldConstants.BitmapCurve); + bitmap_curve.Value = 3; + + // Set slicer to no slicing + var slicer = (TagFieldEnum)tagFile.SelectField(TagFieldConstants.Slicer); + slicer.Value = 1; + + // Set mipmap limit + var mip_limit_override = (TagFieldElementInteger)tagFile.SelectField(TagFieldConstants.MipLimit); + mip_limit_override.Data = -1; + + // Set compression to UI-selected value + var override_compression = (TagFieldEnum)tagFile.SelectField(TagFieldConstants.UsageFormat); + override_compression.Value = compress_value; + + tagFile.Save(); + } + } + } + } +} diff --git a/OsoyoosMB/OsoyoosMB/MBHandler.cs b/OsoyoosMB/OsoyoosMB/MBHandler.cs new file mode 100644 index 0000000..764e4e7 --- /dev/null +++ b/OsoyoosMB/OsoyoosMB/MBHandler.cs @@ -0,0 +1,54 @@ +/* +BUILD INFORMATION + +This project uses ManagedBlam.dll, but that can't be added to the GitHub repo. +In order to still be built, the project uses a Reference Assembly version +of the ManagedBlam DLL. This is generated using the NetBrains tool Refasmer. + +In theory it already contains all namespaces/methods etc present in the full DLL. +In case it needs to be regenerated in future howerver: + +Refasmer can be installed from the terminal with "dotnet tool install -g JetBrains.Refasmer.CliTool" +Once installed, run with "refasmer -v -O ref -c ManagedBlam.dll" + +When you need to debug this code, you need to switch the project reference to the "full" .dll or it +will crash at runtime. Don't forget to revert the reference back to version in the "ref" folder +before committing or release. +*/ +using System; + +namespace OsoyoosMB +{ + internal class MBHandler + { + + public static void Main(String[] args) + { + if (args.Length == 0) + { + Console.WriteLine("Do not run this manually, it is a helper executable for Osoyoos. This is not a standalone application.\nPress Enter to exit."); + Console.ReadLine(); + } + else + { + if (args[0] == "getbitmapdata" && args.Length >= 4) + { + Console.WriteLine("Running GetBitmapData"); + BitmapSettings.GetBitmapData(args[1], args[2], args[3]); + } + else + { + Console.WriteLine("Insufficient arguments"); + } + } + } + + /* + //Use this instead if you need to debug GetBitmapData(), can't debug when run from the main Osoyoos solution + public static void Main() + { + BitmapSettings.GetBitmapData(@"C:\Program Files (x86)\Steam\steamapps\common\H3EK", @"objects\scenery\minecraft_door\bitmaps", "Uncompressed"); + } + */ + } +} diff --git a/OsoyoosMB/OsoyoosMB/OsoyoosMB.csproj b/OsoyoosMB/OsoyoosMB/OsoyoosMB.csproj new file mode 100644 index 0000000..e8b2a04 --- /dev/null +++ b/OsoyoosMB/OsoyoosMB/OsoyoosMB.csproj @@ -0,0 +1,37 @@ + + + net48 + Exe + false + True + true + x64 + + + bin\x64\Debug\ + true + + + bin\x64\Release\ + true + + + OsoyoosMB.MBHandler + true + False + + + + False + ..\ref\ManagedBlam.dll + False + + + + + + + + + + \ No newline at end of file diff --git a/OsoyoosMB/OsoyoosMB/Utils/MBHelpers.cs b/OsoyoosMB/OsoyoosMB/Utils/MBHelpers.cs new file mode 100644 index 0000000..5f49bfd --- /dev/null +++ b/OsoyoosMB/OsoyoosMB/Utils/MBHelpers.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OsoyoosMB.Utils +{ + internal class MBHelpers + { + public static string GetBitmapRelativePath(string base_path, string full_path) + { + return Path.ChangeExtension(PathNetCore.GetRelativePath(base_path, full_path), null); + } + } +} diff --git a/OsoyoosMB/OsoyoosMB/Utils/PathNetCore.cs b/OsoyoosMB/OsoyoosMB/Utils/PathNetCore.cs new file mode 100644 index 0000000..d6df11a --- /dev/null +++ b/OsoyoosMB/OsoyoosMB/Utils/PathNetCore.cs @@ -0,0 +1,291 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +//Adapted from https://github.com/dotnet/corefx/blob/master/src/Common/src/CoreLib/System/IO/Path.cs#L697 for Windows +// by Anton Krouglov + +using System.Runtime.CompilerServices; +using System.Diagnostics; +using System.Text; + +// ReSharper disable once CheckNamespace +namespace System.IO +{ + // Provides methods for processing file system strings in a cross-platform manner. + // Most of the methods don't do a complete parsing (such as examining a UNC hostname), + // but they will handle most string operations. + public static class PathNetCore + { + + /// + /// Create a relative path from one path to another. Paths will be resolved before calculating the difference. + /// Default path comparison for the active platform will be used (OrdinalIgnoreCase for Windows or Mac, Ordinal for Unix). + /// + /// The source path the output should be relative to. This path is always considered to be a directory. + /// The destination path. + /// The relative path or if the paths don't share the same root. + /// Thrown if or is null or an empty string. + public static string GetRelativePath(string relativeTo, string path) + { + return GetRelativePath(relativeTo, path, StringComparison); + } + + private static string GetRelativePath(string relativeTo, string path, StringComparison comparisonType) + { + if (string.IsNullOrEmpty(relativeTo)) throw new ArgumentNullException(nameof(relativeTo)); + if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); + Debug.Assert(comparisonType == StringComparison.Ordinal || + comparisonType == StringComparison.OrdinalIgnoreCase); + + relativeTo = Path.GetFullPath(relativeTo); + path = Path.GetFullPath(path); + + // Need to check if the roots are different- if they are we need to return the "to" path. + if (!PathInternalNetCore.AreRootsEqual(relativeTo, path, comparisonType)) + return path; + + int commonLength = PathInternalNetCore.GetCommonPathLength(relativeTo, path, + ignoreCase: comparisonType == StringComparison.OrdinalIgnoreCase); + + // If there is nothing in common they can't share the same root, return the "to" path as is. + if (commonLength == 0) + return path; + + // Trailing separators aren't significant for comparison + int relativeToLength = relativeTo.Length; + if (PathInternalNetCore.EndsInDirectorySeparator(relativeTo)) + relativeToLength--; + + bool pathEndsInSeparator = PathInternalNetCore.EndsInDirectorySeparator(path); + int pathLength = path.Length; + if (pathEndsInSeparator) + pathLength--; + + // If we have effectively the same path, return "." + if (relativeToLength == pathLength && commonLength >= relativeToLength) return "."; + + // We have the same root, we need to calculate the difference now using the + // common Length and Segment count past the length. + // + // Some examples: + // + // C:\Foo C:\Bar L3, S1 -> ..\Bar + // C:\Foo C:\Foo\Bar L6, S0 -> Bar + // C:\Foo\Bar C:\Bar\Bar L3, S2 -> ..\..\Bar\Bar + // C:\Foo\Foo C:\Foo\Bar L7, S1 -> ..\Bar + + StringBuilder + sb = new StringBuilder(); //StringBuilderCache.Acquire(Math.Max(relativeTo.Length, path.Length)); + + // Add parent segments for segments past the common on the "from" path + if (commonLength < relativeToLength) + { + sb.Append(".."); + + for (int i = commonLength + 1; i < relativeToLength; i++) + { + if (PathInternalNetCore.IsDirectorySeparator(relativeTo[i])) + { + sb.Append(DirectorySeparatorChar); + sb.Append(".."); + } + } + } + else if (PathInternalNetCore.IsDirectorySeparator(path[commonLength])) + { + // No parent segments and we need to eat the initial separator + // (C:\Foo C:\Foo\Bar case) + commonLength++; + } + + // Now add the rest of the "to" path, adding back the trailing separator + int differenceLength = pathLength - commonLength; + if (pathEndsInSeparator) + differenceLength++; + + if (differenceLength > 0) + { + if (sb.Length > 0) + { + sb.Append(DirectorySeparatorChar); + } + + sb.Append(path, commonLength, differenceLength); + } + + return sb.ToString(); //StringBuilderCache.GetStringAndRelease(sb); + } + + // Public static readonly variant of the separators. The Path implementation itself is using + // internal const variant of the separators for better performance. + public static readonly char DirectorySeparatorChar = PathInternalNetCore.DirectorySeparatorChar; + public static readonly char AltDirectorySeparatorChar = PathInternalNetCore.AltDirectorySeparatorChar; + public static readonly char VolumeSeparatorChar = PathInternalNetCore.VolumeSeparatorChar; + public static readonly char PathSeparator = PathInternalNetCore.PathSeparator; + + /// Returns a comparison that can be used to compare file and directory names for equality. + internal static StringComparison StringComparison => StringComparison.OrdinalIgnoreCase; + } + + /// Contains internal path helpers that are shared between many projects. + internal static class PathInternalNetCore + { + internal const char DirectorySeparatorChar = '\\'; + internal const char AltDirectorySeparatorChar = '/'; + internal const char VolumeSeparatorChar = ':'; + internal const char PathSeparator = ';'; + + internal const string ExtendedDevicePathPrefix = @"\\?\"; + internal const string UncPathPrefix = @"\\"; + internal const string UncDevicePrefixToInsert = @"?\UNC\"; + internal const string UncExtendedPathPrefix = @"\\?\UNC\"; + internal const string DevicePathPrefix = @"\\.\"; + + //internal const int MaxShortPath = 260; + + // \\?\, \\.\, \??\ + internal const int DevicePrefixLength = 4; + + /// + /// Returns true if the two paths have the same root + /// + internal static bool AreRootsEqual(string first, string second, StringComparison comparisonType) + { + int firstRootLength = GetRootLength(first); + int secondRootLength = GetRootLength(second); + + return firstRootLength == secondRootLength + && string.Compare( + strA: first, + indexA: 0, + strB: second, + indexB: 0, + length: firstRootLength, + comparisonType: comparisonType) == 0; + } + + /// + /// Gets the length of the root of the path (drive, share, etc.). + /// + internal static int GetRootLength(string path) + { + int i = 0; + int volumeSeparatorLength = 2; // Length to the colon "C:" + int uncRootLength = 2; // Length to the start of the server name "\\" + + bool extendedSyntax = path.StartsWith(ExtendedDevicePathPrefix); + bool extendedUncSyntax = path.StartsWith(UncExtendedPathPrefix); + if (extendedSyntax) + { + // Shift the position we look for the root from to account for the extended prefix + if (extendedUncSyntax) + { + // "\\" -> "\\?\UNC\" + uncRootLength = UncExtendedPathPrefix.Length; + } + else + { + // "C:" -> "\\?\C:" + volumeSeparatorLength += ExtendedDevicePathPrefix.Length; + } + } + + if ((!extendedSyntax || extendedUncSyntax) && path.Length > 0 && IsDirectorySeparator(path[0])) + { + // UNC or simple rooted path (e.g. "\foo", NOT "\\?\C:\foo") + + i = 1; // Drive rooted (\foo) is one character + if (extendedUncSyntax || (path.Length > 1 && IsDirectorySeparator(path[1]))) + { + // UNC (\\?\UNC\ or \\), scan past the next two directory separators at most + // (e.g. to \\?\UNC\Server\Share or \\Server\Share\) + i = uncRootLength; + int n = 2; // Maximum separators to skip + while (i < path.Length && (!IsDirectorySeparator(path[i]) || --n > 0)) i++; + } + } + else if (path.Length >= volumeSeparatorLength && + path[volumeSeparatorLength - 1] == PathNetCore.VolumeSeparatorChar) + { + // Path is at least longer than where we expect a colon, and has a colon (\\?\A:, A:) + // If the colon is followed by a directory separator, move past it + i = volumeSeparatorLength; + if (path.Length >= volumeSeparatorLength + 1 && IsDirectorySeparator(path[volumeSeparatorLength])) i++; + } + + return i; + } + + /// + /// True if the given character is a directory separator. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsDirectorySeparator(char c) + { + return c == PathNetCore.DirectorySeparatorChar || c == PathNetCore.AltDirectorySeparatorChar; + } + + /// + /// Get the common path length from the start of the string. + /// + internal static int GetCommonPathLength(string first, string second, bool ignoreCase) + { + int commonChars = EqualStartingCharacterCount(first, second, ignoreCase: ignoreCase); + + // If nothing matches + if (commonChars == 0) + return commonChars; + + // Or we're a full string and equal length or match to a separator + if (commonChars == first.Length + && (commonChars == second.Length || IsDirectorySeparator(second[commonChars]))) + return commonChars; + + if (commonChars == second.Length && IsDirectorySeparator(first[commonChars])) + return commonChars; + + // It's possible we matched somewhere in the middle of a segment e.g. C:\Foodie and C:\Foobar. + while (commonChars > 0 && !IsDirectorySeparator(first[commonChars - 1])) + commonChars--; + + return commonChars; + } + + /// + /// Gets the count of common characters from the left optionally ignoring case + /// + internal static unsafe int EqualStartingCharacterCount(string first, string second, bool ignoreCase) + { + if (string.IsNullOrEmpty(first) || string.IsNullOrEmpty(second)) return 0; + + int commonChars = 0; + + fixed (char* f = first) + fixed (char* s = second) + { + char* l = f; + char* r = s; + char* leftEnd = l + first.Length; + char* rightEnd = r + second.Length; + + while (l != leftEnd && r != rightEnd + && (*l == *r || (ignoreCase && + char.ToUpperInvariant((*l)) == char.ToUpperInvariant((*r))))) + { + commonChars++; + l++; + r++; + } + } + + return commonChars; + } + + /// + /// Returns true if the path ends in a directory separator. + /// + internal static bool EndsInDirectorySeparator(string path) + => path.Length > 0 && IsDirectorySeparator(path[path.Length - 1]); + } +} \ No newline at end of file diff --git a/OsoyoosMB/ref/ManagedBlam.dll b/OsoyoosMB/ref/ManagedBlam.dll new file mode 100644 index 0000000..62063d3 Binary files /dev/null and b/OsoyoosMB/ref/ManagedBlam.dll differ diff --git a/README.md b/README.md index 49141ea..265f052 100644 --- a/README.md +++ b/README.md @@ -37,11 +37,14 @@ Launcher can do the following: ## Usage -0. Download and install the [.NET 5 Desktop Runtime x64](https://dotnet.microsoft.com/download/dotnet/5.0/runtime). It is *very* important that you download the **64-bit** **Desktop** runtime, otherwise the launcher won't start correctly. For convenience you can use the [direct download link]( https://download.visualstudio.microsoft.com/download/pr/2bfb80f2-b8f2-44b0-90c1-d3c8c1c8eac8/409dd3d3367feeeda048f4ff34b32e82/windowsdesktop-runtime-5.0.13-win-x64.exe) but it might point to an older version as this readme is not regularly revised. +0. Download and install the [.NET 6 Desktop Runtime x64](https://dotnet.microsoft.com/download/dotnet/6.0/runtime). It is *very* important that you download the **64-bit** **Desktop** runtime, otherwise the launcher won't start correctly. For convenience you can use the [direct download link]( https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-6.0.31-windows-x64-installer) but it might point to an older version as this readme is not regularly revised. 1. Download and run the launcher executable [from Github releases](https://github.com/num0005/Osoyoos-Launcher/releases). 2. Use the setup dialog and/or profile wizard to setup the paths for all toolkits you wish to use. +## Non-free content warning +The reference managedblam assembly is not covered by the MIT license and is instead covered by the MCC EULA and/or the fair dealing/fair use exemption. This reference assembly is automatically generated from the public interface of a closed source binary and contains no executable code and is only used for the purpose of interoperability. + ## Credits * Discord user num0005#8646 (https://github.com/num0005) diff --git a/ToolkitLauncher.sln b/ToolkitLauncher.sln index b8c94a8..7a4f8ac 100644 --- a/ToolkitLauncher.sln +++ b/ToolkitLauncher.sln @@ -1,26 +1,26 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30503.244 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34330.188 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ToolkitLauncher", "Launcher\ToolkitLauncher.csproj", "{EE9D5075-A31E-4393-BD62-59A3AD5C3FC5}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OsoyoosMB", "OsoyoosMB\OsoyoosMB\OsoyoosMB.csproj", "{A037E578-342F-4836-84C0-08A6394E94C2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x86 = Release|x86 + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {EE9D5075-A31E-4393-BD62-59A3AD5C3FC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EE9D5075-A31E-4393-BD62-59A3AD5C3FC5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EE9D5075-A31E-4393-BD62-59A3AD5C3FC5}.Debug|x86.ActiveCfg = Debug|Any CPU - {EE9D5075-A31E-4393-BD62-59A3AD5C3FC5}.Debug|x86.Build.0 = Debug|Any CPU - {EE9D5075-A31E-4393-BD62-59A3AD5C3FC5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EE9D5075-A31E-4393-BD62-59A3AD5C3FC5}.Release|Any CPU.Build.0 = Release|Any CPU - {EE9D5075-A31E-4393-BD62-59A3AD5C3FC5}.Release|x86.ActiveCfg = Release|Any CPU - {EE9D5075-A31E-4393-BD62-59A3AD5C3FC5}.Release|x86.Build.0 = Release|Any CPU + {EE9D5075-A31E-4393-BD62-59A3AD5C3FC5}.Debug|x64.ActiveCfg = Debug|x64 + {EE9D5075-A31E-4393-BD62-59A3AD5C3FC5}.Debug|x64.Build.0 = Debug|x64 + {EE9D5075-A31E-4393-BD62-59A3AD5C3FC5}.Release|x64.ActiveCfg = Release|x64 + {EE9D5075-A31E-4393-BD62-59A3AD5C3FC5}.Release|x64.Build.0 = Release|x64 + {A037E578-342F-4836-84C0-08A6394E94C2}.Debug|x64.ActiveCfg = Debug|x64 + {A037E578-342F-4836-84C0-08A6394E94C2}.Debug|x64.Build.0 = Debug|x64 + {A037E578-342F-4836-84C0-08A6394E94C2}.Release|x64.ActiveCfg = Release|x64 + {A037E578-342F-4836-84C0-08A6394E94C2}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE