Skip to content

Commit

Permalink
Merge pull request #368 from Vivelin/msu-rando-integration
Browse files Browse the repository at this point in the history
Initial MSU Randomization Integration
  • Loading branch information
MattEqualsCoder authored Aug 21, 2023
2 parents 1507f7f + 45b9687 commit a236c5f
Show file tree
Hide file tree
Showing 34 changed files with 2,344 additions and 104 deletions.
29 changes: 29 additions & 0 deletions src/Randomizer.App/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
Expand All @@ -9,6 +11,10 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Win32;
using MSURandomizerLibrary;
using MSURandomizerLibrary.Models;
using MSURandomizerLibrary.Services;
using MSURandomizerUI;
using Randomizer.App.Controls;
using Randomizer.App.Windows;
using Randomizer.Data.Configuration;
Expand Down Expand Up @@ -131,6 +137,9 @@ protected static void ConfigureServices(IServiceCollection services)
services.AddTransient<MultiRomListPanel>();
services.AddWindows<App>();

// MSU Randomzer
services.AddMsuRandomizerServices();
services.AddMsuRandomizerUIServices();
}

private void Application_Startup(object sender, StartupEventArgs e)
Expand All @@ -155,6 +164,8 @@ private void Application_Startup(object sender, StartupEventArgs e)

ToolTipService.ShowDurationProperty.OverrideMetadata(typeof(DependencyObject), new FrameworkPropertyMetadata(int.MaxValue));

InitializeMsuRandomizer();

var mainWindow = _host.Services.GetRequiredService<RomListWindow>();
mainWindow.Show();
}
Expand Down Expand Up @@ -193,5 +204,23 @@ private void Application_DispatcherUnhandledException(object sender, System.Wind
e.Handled = true;
Environment.FailFast("Uncaught exception in Dispatcher", e.Exception);
}

private void InitializeMsuRandomizer()
{
var settingsStream = Assembly.GetExecutingAssembly()
.GetManifestResourceStream("Randomizer.App.msu-randomizer-settings.yml");
var typesStream = Assembly.GetExecutingAssembly()
.GetManifestResourceStream("Randomizer.App.msu-randomizer-types.json");
var msuInitializationRequest = new MsuRandomizerInitializationRequest()
{
MsuAppSettingsStream = settingsStream,
MsuTypeConfigStream = typesStream,
LookupMsus = false
};
#if DEBUG
msuInitializationRequest.UserOptionsPath = "%LocalAppData%\\SMZ3CasRandomizer\\msu-user-settings-debug.yml";
#endif
_host!.Services.GetRequiredService<IMsuRandomizerInitializationService>().Initialize(msuInitializationRequest);
}
}
}
5 changes: 5 additions & 0 deletions src/Randomizer.App/Controls/FileSystemInput.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ private void BrowseFile()
if (string.IsNullOrEmpty(FileValidationHash) || string.IsNullOrEmpty(FileValidationErrorMessage))
{
Path = dialog.FileName;
OnPathUpdated?.Invoke(this, EventArgs.Empty);
}
else
{
Expand All @@ -115,6 +116,7 @@ private void BrowseFile()
}

Path = dialog.FileName;
OnPathUpdated?.Invoke(this, EventArgs.Empty);
}
}
}
Expand All @@ -133,8 +135,11 @@ private void BrowseFolder()
if (dialog.ShowDialog(owner) == CommonFileDialogResult.Ok)
{
Path = dialog.FileName;
OnPathUpdated?.Invoke(this, EventArgs.Empty);
}
}

public event EventHandler? OnPathUpdated;

}
}
179 changes: 102 additions & 77 deletions src/Randomizer.App/MsuGeneratorService.cs
Original file line number Diff line number Diff line change
@@ -1,112 +1,137 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using Microsoft.WindowsAPICodePack.Dialogs;
using MSURandomizerLibrary;
using MSURandomizerLibrary.Models;
using MSURandomizerLibrary.Services;
using MSURandomizerUI;
using Randomizer.Data.Options;

namespace Randomizer.App;

public class MsuGeneratorService
{
/// <summary>
/// Enables MSU support for a rom
/// </summary>
/// <param name="msuPath">The path to the msu file</param>
/// <param name="romPath">The path to the rom file</param>
/// <param name="error">Any error that was ran into when updating the rom</param>
/// <returns>True if successful, false otherwise</returns>
public bool EnableMsu1Support(string msuPath, string romPath, out string error)
private readonly IMsuLookupService _msuLookupService;
private readonly IMsuUiFactory _msuUiFactory;
private readonly RandomizerOptions _options;
private readonly IMsuSelectorService _msuSelectorService;
private readonly IMsuTypeService _msuTypeService;

public MsuGeneratorService(OptionsFactory optionsFactory, IMsuLookupService msuLookupService, IMsuUiFactory msuUiFactory, IMsuSelectorService msuSelectorService, IMsuTypeService msuTypeService)
{
if (!File.Exists(msuPath))
_options = optionsFactory.Create();
_msuLookupService = msuLookupService;
_msuUiFactory = msuUiFactory;
_msuSelectorService = msuSelectorService;
_msuTypeService = msuTypeService;
}

public void OpenMsuWindow(Window parentWindow, SelectionMode selectionMode, MsuRandomizationStyle? randomizationStyle)
{
if (!VerifyMsuDirectory(parentWindow)) return;
if (!_msuUiFactory.OpenMsuWindow(selectionMode, true, out var options)) return;
if (options.SelectedMsus?.Any() != true) return;
_options.PatchOptions.MsuPaths = options.SelectedMsus.ToList();
_options.PatchOptions.MsuRandomizationStyle = randomizationStyle;
}

public bool LookupMsus()
{
if (!string.IsNullOrEmpty(_options.GeneralOptions.MsuPath) && Directory.Exists(_options.GeneralOptions.MsuPath))
{
error = "";
return false;
Task.Run(() =>
{
_msuLookupService.LookupMsus(_options.GeneralOptions.MsuPath);
});

return true;
}

var romDrive = Path.GetPathRoot(romPath);
var msuDrive = Path.GetPathRoot(msuPath);
if (romDrive?.Equals(msuDrive, StringComparison.OrdinalIgnoreCase) == false)
return false;
}

private bool VerifyMsuDirectory(Window parentWindow)
{
if (!string.IsNullOrEmpty(_options.GeneralOptions.MsuPath) && Directory.Exists(_options.GeneralOptions.MsuPath))
{
error = $"Due to technical limitations, the MSU-1 " +
$"pack and the ROM need to be on the same drive. MSU-1 " +
$"support cannot be enabled.\n\nPlease move or copy the MSU-1 " +
$"files to somewhere on {romDrive}, or change the ROM output " +
$"folder setting to be on the {msuDrive} drive.";
return false;
return true;
}

var romFolder = Path.GetDirectoryName(romPath);
var msuFolder = Path.GetDirectoryName(msuPath);
var romBaseName = Path.GetFileNameWithoutExtension(romPath);
var msuBaseName = Path.GetFileNameWithoutExtension(msuPath);

var swap = false;
MessageBox.Show(parentWindow, "Please select the parent folder than contains all of your MSUs. To preserve drive space, it is recommended that the Rom Output and MSU folders be on the same drive.", "MSU Path Needed",
MessageBoxButton.OK, MessageBoxImage.Exclamation);

var hasTrack41 = File.Exists(msuPath.Replace(".msu", "-41.pcm"));
var hasTrack141 = File.Exists(msuPath.Replace(".msu", "-141.pcm"));
var hasTrack33 = File.Exists(msuPath.Replace(".msu", "-33.pcm"));
var hasTrack133 = File.Exists(msuPath.Replace(".msu", "-133.pcm"));
using var dialog = new CommonOpenFileDialog();
dialog.EnsurePathExists = true;
dialog.Title = "Select MSU Path";
dialog.IsFolderPicker = true;

// Swap if we see the Skull Woods theme above 100 or if we see that the Zelda epilogue track number
// only exists above 100
if (hasTrack141 || (hasTrack133 && !hasTrack33))
if (dialog.ShowDialog(parentWindow) == CommonFileDialogResult.Ok)
{
swap = true;
_options.GeneralOptions.MsuPath = dialog.FileName;
}
// If we don't see Skull Woods below 100 and we don't see the Zelda epilogue track only existing below 100
// then try to check loop tracks as a last ditch effort to see if we need to swap
else if (!hasTrack41 && !(hasTrack33 && !hasTrack133))

if (string.IsNullOrEmpty(_options.GeneralOptions.MsuPath) ||
!Directory.Exists(_options.GeneralOptions.MsuPath))
{
var track33Loops = DoesPCMLoop(msuPath.Replace(".msu", "-33.pcm"));
var track130Loops = DoesPCMLoop(msuPath.Replace(".msu", "-130.pcm"));
if (track33Loops || track130Loops)
{
swap = true;
}
return false;
}

// Copy pcm files
foreach (var msuFile in Directory.EnumerateFiles(msuFolder!, $"{msuBaseName}*"))
Task.Run(() =>
{
var fileName = Path.GetFileName(msuFile);
var suffix = fileName.Replace(msuBaseName, "");
var link = Path.Combine(romFolder!, romBaseName + suffix);
_msuLookupService.LookupMsus(_options.GeneralOptions.MsuPath);
});

// Swap SM and Z3 tracks if needed
if (swap && suffix.Contains(".pcm"))
{
if (int.TryParse(suffix.Replace("-", "").Replace(".pcm", ""), out var trackNumber) && trackNumber > 0)
{
// Skip track 99 (SMZ3 combined credits) as it doesn't change
if (trackNumber < 99)
{
trackNumber += 100;
}
else if (trackNumber > 99)
{
trackNumber -= 100;
}

var oldLink = link;
link = Path.Combine(romFolder!, romBaseName + "-" + trackNumber + ".pcm");
}
}
NativeMethods.CreateHardLink(link, msuFile, IntPtr.Zero);
}
MessageBox.Show(parentWindow, "Updated MSU folder. If you want to change the MSU path in the future, you can do so in the Tools -> Options window", "MSU Path Updated",
MessageBoxButton.OK, MessageBoxImage.Information);

LookupMsus();

error = "";
return true;
}

private static bool DoesPCMLoop(string fileName)
public bool ApplyMsuOptions(string romPath)
{
if (!File.Exists(fileName))
if (!_options.PatchOptions.MsuPaths.Any())
{
return false;
}
using var fileStream = File.OpenRead(fileName);
fileStream.Seek(4, SeekOrigin.Begin);
var bytes = new byte[4];
fileStream.Read(bytes, 0, 4);
return bytes.Any(x => x != 0);

var romFileInfo = new FileInfo(romPath);
var outputPath = romFileInfo.FullName.Replace(romFileInfo.Extension, ".msu");

if (_options.PatchOptions.MsuRandomizationStyle == null)
{
_msuSelectorService.AssignMsu(new MsuSelectorRequest()
{
MsuPath = _options.PatchOptions.MsuPaths.First(),
OutputMsuType = _msuTypeService.GetSMZ3MsuType(),
OutputPath = outputPath,
});
}
else if (_options.PatchOptions.MsuRandomizationStyle == MsuRandomizationStyle.Single)
{
_msuSelectorService.PickRandomMsu(new MsuSelectorRequest()
{
MsuPaths = _options.PatchOptions.MsuPaths,
OutputMsuType = _msuTypeService.GetSMZ3MsuType(),
OutputPath = outputPath,
});
}
else
{
_msuSelectorService.CreateShuffledMsu(new MsuSelectorRequest()
{
MsuPaths = _options.PatchOptions.MsuPaths,
OutputMsuType = _msuTypeService.GetSMZ3MsuType(),
OutputPath = outputPath,
});
}

return true;
}

}
7 changes: 6 additions & 1 deletion src/Randomizer.App/Randomizer.App.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<TargetFramework>net7.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<ApplicationIcon>chozo20.ico</ApplicationIcon>
<Version>9.4.0-beta.1</Version>
<Version>9.4.0</Version>
<Title>SMZ3 Cas' Randomizer</Title>
<AssemblyTitle>SMZ3 Cas' Randomizer</AssemblyTitle>
<Authors>Vivelin</Authors>
Expand Down Expand Up @@ -68,10 +68,15 @@
<EmbeddedResource Include="Patches\HoldFire.ips" />
<None Remove="Patches\UnifiedAim.ips" />
<EmbeddedResource Include="Patches\UnifiedAim.ips" />
<None Remove="msu-randomizer-settings.yml" />
<EmbeddedResource Include="msu-randomizer-settings.yml" />
<None Remove="msu-randomizer-types.json" />
<EmbeddedResource Include="msu-randomizer-types.json" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="EntityFramework" Version="6.4.4" />
<PackageReference Include="MattEqualsCoder.MSURandomizer.UI" Version="1.0.1" />
<PackageReference Include="Microsoft-WindowsAPICodePack-Shell" Version="1.1.5" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
<PackageReference Include="NReco.Logging.File" Version="1.1.6" />
Expand Down
6 changes: 4 additions & 2 deletions src/Randomizer.App/RomGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ private async Task<GenerateRomResults> GenerateRomInternalAsync(SeedData seed, R
var fileSuffix = $"{DateTimeOffset.Now:yyyyMMdd-HHmmss}_{safeSeed}";
var romFileName = $"SMZ3_Cas_{fileSuffix}.sfc";
var romPath = Path.Combine(folderPath, romFileName);
_msuGeneratorService.EnableMsu1Support(options.PatchOptions.Msu1Path, romPath, out var msuError);
_msuGeneratorService.ApplyMsuOptions(romPath);
Rom.UpdateChecksum(bytes);
await File.WriteAllBytesAsync(romPath, bytes);

Expand All @@ -194,7 +194,7 @@ private async Task<GenerateRomResults> GenerateRomInternalAsync(SeedData seed, R
return new GenerateRomResults()
{
Rom = rom,
MsuError = msuError
MsuError = ""
};
}

Expand Down Expand Up @@ -426,6 +426,8 @@ protected async Task<GeneratedRom> SaveSeedToDatabaseAsync(RandomizerOptions opt
Settings = settingsString,
GeneratorVersion = Smz3Randomizer.Version.Major,
MultiplayerGameDetails = multiplayerGameDetails,
MsuRandomizationStyle = options.PatchOptions.MsuRandomizationStyle,
MsuPaths = string.Join("|", options.PatchOptions.MsuPaths)
};
_dbContext.GeneratedRoms.Add(rom);

Expand Down
Loading

0 comments on commit a236c5f

Please sign in to comment.