diff --git a/MilkyWayFix/Constants.cs b/MilkyWayFix/Constants.cs
new file mode 100644
--- /dev/null
+++ b/MilkyWayFix/Constants.cs
@@ -0,0 +1,9 @@
+namespace MilkyWayFix;
+
+public static class Constants
+{
+ ///
+ /// Mean obliquity of the ecliptic at J2000 (radians)
+ ///
+ public const float EclOb = 23.439291111f * (float)KSA.Constants.DEGREES_TO_RADIANS;
+}
\ No newline at end of file
diff --git a/MilkyWayFix/GalacticPlanePatch.cs b/MilkyWayFix/GalacticPlanePatch.cs
--- a/MilkyWayFix/GalacticPlanePatch.cs
+++ b/MilkyWayFix/GalacticPlanePatch.cs
@@ -10,31 +10,36 @@
[HarmonyPatch(typeof(GalacticPlane))]
public class GalacticPlanePatch
{
+ ///
+ /// Harmony postfix to generate the correct rotation matrix so the Milky Way appears in the
+ /// correct orientation in the sky (using the stock skybox and shader).
+ ///
+ /// Harmony injection for the GalacticPlane instance
+ /// Harmony injection for the GalacticPlane._rotation private field
[UsedImplicitly]
[HarmonyPatch(nameof(GalacticPlane.OnDataLoad))]
[HarmonyPostfix]
- [SuppressMessage("ReSharper", "InconsistentNaming")]
+ [SuppressMessage("ReSharper", "InconsistentNaming")] // yes, rider, the four underscores are there for a reason (we love harmony)
public static void OnDataLoadPostfix(GalacticPlane __instance, ref float4x4 ____rotation)
{
// Transform RA/dec of north galactic pole into ecliptic coordinates
- const double eclOb = 23.439291111 * Constants.DEGREES_TO_RADIANS; // Mean obliquity of the ecliptic at J2000
- var ra = (double)__instance.RightAscension;
- var dec = (double)__instance.Declination;
- var lon = double.Atan2(double.Sin(ra) * double.Cos(eclOb) + double.Tan(dec) * double.Sin(eclOb),
- double.Cos(ra));
- var lat = double.Asin(double.Sin(dec) * double.Cos(eclOb) -
- double.Cos(dec) * double.Sin(eclOb) * double.Sin(ra));
+ var ra = (float)__instance.RightAscension;
+ var dec = (float)__instance.Declination;
+ var lon = float.Atan2(float.Sin(ra) * float.Cos(Constants.EclOb) + float.Tan(dec) * float.Sin(Constants.EclOb),
+ float.Cos(ra));
+ var lat = float.Asin(float.Sin(dec) * float.Cos(Constants.EclOb) -
+ float.Cos(dec) * float.Sin(Constants.EclOb) * float.Sin(ra));
// Rotate about Z such that the points at which the ecliptic intersects the galactic equator are along the Y axis
// (This assumes that the Milky Way cubemap has the galactic centre on the positive X axis)
- var preRotationZ = float4x4.CreateRotationZ(-(float)((90 - 6.38398872) * Constants.DEGREES_TO_RADIANS));
+ var preRotationZ = float4x4.CreateRotationZ(-(float)((90 - 6.38398872) * KSA.Constants.DEGREES_TO_RADIANS));
// Rotate 90 degrees about Y, so the north galactic pole is facing the positive X axis
var preRotationY = float4x4.CreateRotationY(-float.Pi / 2f);
// Rotate by the longitude about Z, then rotate by the latitude about the transformed direction of Y
- var raRotation = float4x4.CreateRotationZ((float)lon);
+ var raRotation = float4x4.CreateRotationZ(lon);
var rotatedYAxis = raRotation * float4.UnitY;
- var decRotation = float4x4.CreateFromAxisAngle(rotatedYAxis.XYZ, (float)lat);
+ var decRotation = float4x4.CreateFromAxisAngle(rotatedYAxis.XYZ, lat);
____rotation = decRotation * raRotation * preRotationY * preRotationZ;
}
}
\ No newline at end of file
diff --git a/MilkyWayFix/MilkyWayFix.cs b/MilkyWayFix/MilkyWayFix.cs
--- a/MilkyWayFix/MilkyWayFix.cs
+++ b/MilkyWayFix/MilkyWayFix.cs
@@ -13,7 +13,7 @@
[UsedImplicitly]
[StarMapBeforeMain]
- [SuppressMessage("Performance", "CA1822:Mark members as static")]
+ [SuppressMessage("Performance", "CA1822:Mark members as static")] // StarMap hooks must be non-static
public void FullyLoaded()
{
Harmony.PatchAll(typeof(MilkyWayFix).Assembly);
diff --git a/MilkyWayFix/MilkyWayFix.csproj b/MilkyWayFix/MilkyWayFix.csproj
--- a/MilkyWayFix/MilkyWayFix.csproj
+++ b/MilkyWayFix/MilkyWayFix.csproj
@@ -7,7 +7,7 @@
- 0.1.0
+ 0.2.0
diff --git a/MilkyWayFix/ModPatch.cs b/MilkyWayFix/ModPatch.cs
new file mode 100644
--- /dev/null
+++ b/MilkyWayFix/ModPatch.cs
@@ -0,0 +1,82 @@
+using System.Reflection.Emit;
+using System.Security.Cryptography;
+using Brutal.Numerics;
+using HarmonyLib;
+using JetBrains.Annotations;
+using KSA;
+
+namespace MilkyWayFix;
+
+[UsedImplicitly]
+[HarmonyPatch(typeof(Mod))]
+public class ModPatch
+{
+ ///
+ /// SHA256 hash of the stock star binary (Content/Core/hip_main.bin as of 2026.6.8.4680).
+ ///
+ private const string StockBinaryHash = "f6e4edb59cfc38947be1b81a8f9f16abcf3b014a957d48138e84c81713cb4f20";
+
+ ///
+ /// Check whether a FileStream is the stock star binary by comparing its SHA-256 hash.
+ ///
+ /// (We only want to apply corrections to the stock star binary; if the user has replaced the star binary
+ /// with a custom one, it is assumed to already be in the correct (ecliptic) coordinate system.)
+ ///
+ /// FileStream of the star binary to check
+ /// True if the FileStream is the stock star binary, false otherwise
+ [UsedImplicitly]
+ private static bool IsStockStarBinary(FileStream stream)
+ {
+ stream.Position = 0;
+ using var sha256 = SHA256.Create();
+ var hash = sha256.ComputeHash(stream);
+ stream.Position = 0;
+
+ var hashString = Convert.ToHexStringLower(hash);
+ return hashString == StockBinaryHash;
+ }
+
+ ///
+ /// Correct the given star coordinates so the star appears in the right place in the sky.
+ /// This is achieved by swapping the Y and Z axes, then transforming from equatorial to ecliptic coordinates.
+ ///
+ /// The star coordinates to correct. Should be a unit vector.
+ /// If this parameter is false, this method simply returns coords unchanged.
+ ///
+ [UsedImplicitly]
+ private static float3 FixStarCoordinates(float3 coords, bool fix)
+ {
+ if (!fix) return coords;
+
+ return new float3(coords.X,
+ float.Cos(Constants.EclOb) * coords.Z + float.Sin(Constants.EclOb) * coords.Y,
+ -float.Sin(Constants.EclOb) * coords.Z + float.Cos(Constants.EclOb) * coords.Y);
+ }
+
+ ///
+ /// Harmony transpiler for Mod.LoadStarBinaries, which applies the correction above to all stars
+ /// in the stock star binary.
+ ///
+ [UsedImplicitly]
+ [HarmonyPatch(nameof(Mod.LoadStarBinaries))]
+ [HarmonyTranspiler]
+ public static IEnumerable LoadStarBinariesTranspiler(IEnumerable instructions, ILGenerator generator)
+ {
+ var matcher = new CodeMatcher(instructions, generator);
+ matcher.MatchStartForward(CodeMatch.Calls(() => File.OpenRead(null!)))
+ .ThrowIfInvalid("Could not find File.OpenRead() call")
+ .DeclareLocal(typeof(bool), out var localIsStockStarBinary)
+ .InsertAfterAndAdvance(
+ new CodeInstruction(OpCodes.Dup),
+ CodeInstruction.Call(() => IsStockStarBinary(null!)),
+ CodeInstruction.StoreLocal(localIsStockStarBinary.LocalIndex)
+ )
+ .MatchStartForward(CodeMatch.Calls(() => float3.Normalize(default)))
+ .ThrowIfInvalid("Could not find float3.Normalize() call")
+ .InsertAfterAndAdvance(
+ CodeInstruction.LoadLocal(localIsStockStarBinary.LocalIndex),
+ CodeInstruction.Call(() => FixStarCoordinates(default, false)));
+
+ return matcher.Instructions();
+ }
+}
\ No newline at end of file