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