Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CrossPlatformUI/Services/FileDialogService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class FileDialogService(TopLevel? target) : IFileDialogService
{
var files = await target!.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions()
{
Title = "Open Folder",
Title = "Select ROM Output Folder",
AllowMultiple = false
});

Expand Down
25 changes: 24 additions & 1 deletion CrossPlatformUI/ViewModels/GenerateRomViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,30 @@ async void GenerateSeed()
{
basename = filename;
}
await files.SaveGeneratedBinaryFile(filename, output.romdata!, Main.OutputFilePath);
bool saved = false;
while (!saved)
{
try
{
await files.SaveGeneratedBinaryFile(filename, output.romdata!, Main.OutputFilePath);
saved = true;
}
catch (Exception e)
{
// DirectoryNotFoundException, UnauthorizedAccessException
// We could check for specific exceptions, but it's
// probably fine to do this for all errors while saving
var newFolder = await RandomizerViewModel.SelectSaveFolder();
if (!string.IsNullOrEmpty(newFolder) && newFolder != Main.OutputFilePath)
{
Main.OutputFilePath = newFolder;
}
else // user did not pick a new save folder
{
throw new UserFacingException("Unable to save ROM to folder", e.Message);
}
}
}
#if DEBUG
var debugfile = basename + ".mlb";
if (!string.IsNullOrEmpty(output.debuginfo))
Expand Down
17 changes: 14 additions & 3 deletions CrossPlatformUI/ViewModels/RandomizerViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Avalonia.Controls;
using ReactiveUI;
Expand Down Expand Up @@ -117,9 +118,7 @@ public RandomizerViewModel(MainViewModel main)

SaveFolder = ReactiveCommand.CreateFromTask(async () =>
{
var fileDialog = App.Current?.Services?.GetService<IFileDialogService>()!;
var folder = await fileDialog.OpenFolderAsync();
Main.OutputFilePath = folder?.Path.LocalPath ?? "";
Main.OutputFilePath = await SelectSaveFolder() ?? "";
});

CheckForUpdates = ReactiveCommand.CreateFromTask(async () =>
Expand Down Expand Up @@ -203,6 +202,18 @@ public RandomizerViewModel(MainViewModel main)
this.WhenActivated(OnActivate);
}

public static async Task<string?> SelectSaveFolder()
{
var fileDialog = App.Current?.Services?.GetService<IFileDialogService>()!;
var folder = await fileDialog.OpenFolderAsync();
Uri? path = folder?.Path;
// both LocalPath and TryGetLocalPath() throw for non-absoloute URIs
string? localPath = path?.IsAbsoluteUri == true
? path.LocalPath
: path?.OriginalString;
return localPath;
}

private void OnActivate(CompositeDisposable disposables)
{
var loadedFlags = Main.Config.SerializeFlags(); // this serializes the configuration
Expand Down
50 changes: 32 additions & 18 deletions CrossPlatformUI/ViewModels/RomFileViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Microsoft.Extensions.DependencyInjection;
using ReactiveUI;
using SD.Tools.BCLExtensions.CollectionsRelated;
using Z2Randomizer.RandomizerCore;
using CrossPlatformUI.Services;

namespace CrossPlatformUI.ViewModels;
Expand All @@ -25,6 +26,14 @@ public byte[] RomData
}
}

private string message { get; set; } = "Select your Zelda 2 ROM to get started!";
[JsonIgnore]
public string Message
{
get => message;
set { message = value; this.RaisePropertyChanged(); }
}

[JsonIgnore]
public IObservable<byte[]> RomDataObservable => this.WhenAnyValue(x => x.RomData);

Expand Down Expand Up @@ -59,31 +68,36 @@ private async Task OpenFileInternal(CancellationToken token)
if (fileprops.Size <= 1024 * 1024 * 1)
{
await using var readStream = await file.OpenReadAsync();
var tmp = new byte[(uint)fileprops.Size];
var read = await readStream.ReadAsync(tmp, token);
// TODO: Better validation
if (read == 1024 * 256 + 0x10)
byte[] fileData = new byte[(uint)fileprops.Size];
var read = await readStream.ReadAsync(fileData, token);
try
{
RomData = tmp;
if (OperatingSystem.IsBrowser())
{
// Manually save the state
await App.PersistState();
}
else
ROM.ValidateVanillaRom(ref fileData);
}
catch (UserFacingException e)
{
Message = e.Message;
return;
}
RomData = fileData;
if (OperatingSystem.IsBrowser())
{
// Manually save the state
await App.PersistState();
}
else
{
// This part crashes if run in the browser build
if ((Main.OutputFilePath ?? "") == "")
{
// This part crashes if run in the browser build
if ((Main.OutputFilePath ?? "") == "")
{
Main.OutputFilePath = new Uri(file.Path, ".").LocalPath;
}
Main.OutputFilePath = new Uri(file.Path, ".").LocalPath;
}
HostScreen.Router.NavigateBack.Execute();
}
HostScreen.Router.NavigateBack.Execute();
}
else
{
throw new Exception("File exceeded 1MB limit.");
Message = "File exceeded 1MB limit. Please provide an unmodified Zelda 2 ROM (US release) with or without header.";
}
}

Expand Down
2 changes: 1 addition & 1 deletion CrossPlatformUI/Views/RomFileView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<Grid RowDefinitions="*, Auto">
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock Text="Select your Zelda 2 ROM to get started!" />
<TextBlock Text="{Binding Message}" />
<Button Command="{Binding OpenFileCommand}">Choose ROM</Button>
</StackPanel>
<StackPanel Grid.Row="1" HorizontalAlignment="Right">
Expand Down
8 changes: 1 addition & 7 deletions RandomizerCore/Hyrule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -265,13 +265,7 @@ public async Task<RandomizerResult> Randomize(byte[] vanillaRomData, RandomizerC
reachableAreas = new HashSet<string>();
//areasByLocation = new SortedDictionary<string, List<Location>>();

byte[] correctVanillaHash = [0x76, 0x4D, 0x36, 0xFA, 0x8A, 0x24, 0x50, 0x83, 0x4D, 0xA5, 0xE8, 0x19, 0x42, 0x81, 0x03, 0x5A];
var vanillaRomHash = MD5Hash.ComputeHash(vanillaRomData);
if (!correctVanillaHash.SequenceEqual(vanillaRomHash))
{
throw new UserFacingException("Vanilla ROM checksum failure", "Please provide an unmodified Zelda 2 ROM (US release).");
}

ROM.ValidateVanillaRom(ref vanillaRomData);
// Make a copy of the vanilla data to prevent seed bleed
ROMData = new ROM(vanillaRomData.ToArray(), true);

Expand Down
32 changes: 32 additions & 0 deletions RandomizerCore/ROM.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2649,4 +2649,36 @@ public void UpdateItem(Collectable item, Room room)
logger.Warn($"Could not write Collectable {item} to Item room {room.GetDebuggerDisplay()} in palace {room.PalaceNumber}");
//throw new Exception("Could not write Collectable to Item room in palace " + PalaceNumber);
}

/// Validates the ROM. Tries to fix headerless or bad header ROMs.
public static void ValidateVanillaRom(ref byte[] vanillaRomData)
{
int correctVanillaLengthWithHeader = 1024 * 256 + 0x10;
byte[] iNesHeader = [0x4e, 0x45, 0x53, 0x1a, 0x08, 0x10, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; // (gets overriden with ROM expansion)
byte[] correctVanillaHash = [0x76, 0x4D, 0x36, 0xFA, 0x8A, 0x24, 0x50, 0x83, 0x4D, 0xA5, 0xE8, 0x19, 0x42, 0x81, 0x03, 0x5A];

if (vanillaRomData.Length != correctVanillaLengthWithHeader)
{
if (vanillaRomData.Length == correctVanillaLengthWithHeader - 0x10)
{
vanillaRomData = [.. iNesHeader, .. vanillaRomData];
}
else
{
throw new UserFacingException("Invalid ROM size", "Please provide an unmodified Zelda 2 ROM (US release) with or without header.");
}
}
else
{
for (int i = 0; i < 0x10; i++)
{
vanillaRomData[i] = iNesHeader[i];
}
}
var vanillaRomHash = MD5Hash.ComputeHash(vanillaRomData);
if (!correctVanillaHash.SequenceEqual(vanillaRomHash))
{
throw new UserFacingException("Vanilla ROM checksum failure", "Please provide an unmodified Zelda 2 ROM (US release).");
}
}
}
Loading