From c48d7fe4f782bf08f05ee6ffe6e6bbbd2b0f1c35 Mon Sep 17 00:00:00 2001 From: MattEqualsCoder Date: Sun, 26 Apr 2026 21:14:15 -0400 Subject: [PATCH] Add hardware support --- .../Services/GameDbService.cs | 14 +- .../Services/IGameDbService.cs | 4 +- .../TrackerCouncil.Smz3.Data.csproj | 2 +- .../TrackerCouncil.Smz3.PatchBuilder.csproj | 2 +- ...0260424192344_hardware-support.Designer.cs | 746 ++++++++++++++++++ .../20260424192344_hardware-support.cs | 29 + .../RandomizerContextModelSnapshot.cs | 4 + .../Models/GeneratedRom.cs | 2 + .../TrackerCouncil.Smz3.Shared.csproj | 2 +- .../AutoTracking/AutoTracker.cs | 4 +- .../TrackerCouncil.Smz3.Tracking.csproj | 2 +- .../VoiceCommands/MsuModule.cs | 23 +- .../MultiplayerStatusWindowService.cs | 9 + .../Services/SharedCrossplatformService.cs | 147 +++- .../Services/SoloRomListService.cs | 13 + .../Services/TrackerWindowService.cs | 2 +- .../UploadRomToHardwareWindowService.cs | 33 + .../TrackerCouncil.Smz3.UI.csproj | 4 +- .../ViewModels/GeneratedRomViewModel.cs | 2 + .../UploadRomToHardwareWindowViewModel.cs | 14 + .../Views/MultiplayerStatusWindow.axaml | 5 + .../Views/MultiplayerStatusWindow.axaml.cs | 5 + .../Views/SoloRomListPanel.axaml | 1 + .../Views/SoloRomListPanel.axaml.cs | 22 + .../Views/UploadRomToHardwareWindow.axaml | 46 ++ .../Views/UploadRomToHardwareWindow.axaml.cs | 51 ++ .../msu-randomizer-settings.yml | 1 + 27 files changed, 1166 insertions(+), 23 deletions(-) create mode 100644 src/TrackerCouncil.Smz3.Shared/Migrations/20260424192344_hardware-support.Designer.cs create mode 100644 src/TrackerCouncil.Smz3.Shared/Migrations/20260424192344_hardware-support.cs create mode 100644 src/TrackerCouncil.Smz3.UI/Services/UploadRomToHardwareWindowService.cs create mode 100644 src/TrackerCouncil.Smz3.UI/ViewModels/UploadRomToHardwareWindowViewModel.cs create mode 100644 src/TrackerCouncil.Smz3.UI/Views/UploadRomToHardwareWindow.axaml create mode 100644 src/TrackerCouncil.Smz3.UI/Views/UploadRomToHardwareWindow.axaml.cs diff --git a/src/TrackerCouncil.Smz3.Data/Services/GameDbService.cs b/src/TrackerCouncil.Smz3.Data/Services/GameDbService.cs index a4f70e6ad..b165cc054 100644 --- a/src/TrackerCouncil.Smz3.Data/Services/GameDbService.cs +++ b/src/TrackerCouncil.Smz3.Data/Services/GameDbService.cs @@ -12,7 +12,7 @@ public class GameDbService(RandomizerContext context, OptionsFactory optionsFact { private readonly RandomizerOptions _options = optionsFactory.Create(); - public bool UpdateGeneratedRom(GeneratedRom rom, string? label = null) + public bool UpdateGeneratedRom(GeneratedRom rom, string? label = null, string? hardwarePath = null, string? msuPath = null) { var updated = false; @@ -22,6 +22,18 @@ public bool UpdateGeneratedRom(GeneratedRom rom, string? label = null) updated = true; } + if (hardwarePath != null && hardwarePath != rom.HardwarePath) + { + rom.HardwarePath = hardwarePath; + updated = true; + } + + if (msuPath != null && msuPath != rom.MsuPaths) + { + rom.MsuPaths = msuPath; + updated = true; + } + if (updated) { context.SaveChanges(); diff --git a/src/TrackerCouncil.Smz3.Data/Services/IGameDbService.cs b/src/TrackerCouncil.Smz3.Data/Services/IGameDbService.cs index 996b34913..ee7a9e6cc 100644 --- a/src/TrackerCouncil.Smz3.Data/Services/IGameDbService.cs +++ b/src/TrackerCouncil.Smz3.Data/Services/IGameDbService.cs @@ -13,8 +13,10 @@ public interface IGameDbService /// /// The GeneratedRom to update /// The value to update for the label + /// The path to the rom on hardware + /// The path to the msu for the rom /// True if the GeneratedRom was updated - public bool UpdateGeneratedRom(GeneratedRom rom, string? label = null); + public bool UpdateGeneratedRom(GeneratedRom rom, string? label = null, string? hardwarePath = null, string? msuPath = null); /// /// Retrieves the list of roms from the database diff --git a/src/TrackerCouncil.Smz3.Data/TrackerCouncil.Smz3.Data.csproj b/src/TrackerCouncil.Smz3.Data/TrackerCouncil.Smz3.Data.csproj index 3c43db784..f62e14e6c 100644 --- a/src/TrackerCouncil.Smz3.Data/TrackerCouncil.Smz3.Data.csproj +++ b/src/TrackerCouncil.Smz3.Data/TrackerCouncil.Smz3.Data.csproj @@ -25,7 +25,7 @@ - + diff --git a/src/TrackerCouncil.Smz3.PatchBuilder/TrackerCouncil.Smz3.PatchBuilder.csproj b/src/TrackerCouncil.Smz3.PatchBuilder/TrackerCouncil.Smz3.PatchBuilder.csproj index b34fac0c9..63bf041ce 100644 --- a/src/TrackerCouncil.Smz3.PatchBuilder/TrackerCouncil.Smz3.PatchBuilder.csproj +++ b/src/TrackerCouncil.Smz3.PatchBuilder/TrackerCouncil.Smz3.PatchBuilder.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/TrackerCouncil.Smz3.Shared/Migrations/20260424192344_hardware-support.Designer.cs b/src/TrackerCouncil.Smz3.Shared/Migrations/20260424192344_hardware-support.Designer.cs new file mode 100644 index 000000000..2e2136310 --- /dev/null +++ b/src/TrackerCouncil.Smz3.Shared/Migrations/20260424192344_hardware-support.Designer.cs @@ -0,0 +1,746 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using TrackerCouncil.Smz3.Shared.Models; + +#nullable disable + +namespace TrackerCouncil.Smz3.Shared.Migrations +{ + [DbContext(typeof(RandomizerContext))] + [Migration("20260424192344_hardware-support")] + partial class AddHardwareSupport + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.2"); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.GeneratedRom", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("GeneratorVersion") + .HasColumnType("INTEGER"); + + b.Property("HardwarePath") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Label") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("MsuPaths") + .HasColumnType("TEXT"); + + b.Property("MsuRandomizationStyle") + .HasColumnType("INTEGER"); + + b.Property("MsuShuffleStyle") + .HasColumnType("INTEGER"); + + b.Property("MultiplayerGameDetailsId") + .HasColumnType("INTEGER"); + + b.Property("RomPath") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Seed") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Settings") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SpoilerPath") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MultiplayerGameDetailsId"); + + b.HasIndex("TrackerStateId"); + + b.ToTable("GeneratedRoms"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.MultiplayerGameDetails", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ConnectionUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("GameGuid") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("GameUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("GeneratedRomId") + .HasColumnType("INTEGER"); + + b.Property("JoinedDate") + .HasColumnType("TEXT"); + + b.Property("PlayerGuid") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PlayerKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GeneratedRomId") + .IsUnique(); + + b.ToTable("MultiplayerGames"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerBossState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AutoTracked") + .HasColumnType("INTEGER"); + + b.Property("BossName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Defeated") + .HasColumnType("INTEGER"); + + b.Property("RegionName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("WorldId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TrackerStateId"); + + b.ToTable("TrackerBossStates"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerDungeonState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AutoTracked") + .HasColumnType("INTEGER"); + + b.Property("Cleared") + .HasColumnType("INTEGER"); + + b.Property("HasManuallyClearedTreasure") + .HasColumnType("INTEGER"); + + b.Property("MarkedMedallion") + .HasColumnType("INTEGER"); + + b.Property("MarkedReward") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RemainingTreasure") + .HasColumnType("INTEGER"); + + b.Property("RequiredMedallion") + .HasColumnType("INTEGER"); + + b.Property("Reward") + .HasColumnType("INTEGER"); + + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); + + b.Property("WorldId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TrackerStateId"); + + b.ToTable("TrackerDungeonStates"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerHintState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("HintState") + .HasColumnType("INTEGER"); + + b.Property("HintTileCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LocationKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LocationString") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LocationWorldId") + .HasColumnType("INTEGER"); + + b.Property("MedallionType") + .HasColumnType("INTEGER"); + + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("Usefulness") + .HasColumnType("INTEGER"); + + b.Property("WorldId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TrackerStateId"); + + b.ToTable("TrackerHintStates"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerHistoryEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("IsImportant") + .HasColumnType("INTEGER"); + + b.Property("IsUndone") + .HasColumnType("INTEGER"); + + b.Property("LocationId") + .HasColumnType("INTEGER"); + + b.Property("LocationName") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ObjectName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Time") + .HasColumnType("REAL"); + + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TrackerStateId"); + + b.ToTable("TrackerHistoryEvents"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerItemState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ItemName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); + + b.Property("TrackingState") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("WorldId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TrackerStateId"); + + b.ToTable("TrackerItemStates"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerLocationState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Autotracked") + .HasColumnType("INTEGER"); + + b.Property("Cleared") + .HasColumnType("INTEGER"); + + b.Property("Item") + .HasColumnType("INTEGER"); + + b.Property("ItemName") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ItemOwnerName") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ItemWorldId") + .HasColumnType("INTEGER"); + + b.Property("LocationId") + .HasColumnType("INTEGER"); + + b.Property("MarkedItem") + .HasColumnType("INTEGER"); + + b.Property("MarkedUsefulness") + .HasColumnType("INTEGER"); + + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); + + b.Property("WorldId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TrackerStateId"); + + b.ToTable("TrackerLocationStates"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerMarkedLocation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ItemName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LocationId") + .HasColumnType("INTEGER"); + + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TrackerStateId"); + + b.ToTable("TrackerMarkedLocations"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerPrerequisiteState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AutoTracked") + .HasColumnType("INTEGER"); + + b.Property("MarkedItem") + .HasColumnType("INTEGER"); + + b.Property("RegionName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RequiredItem") + .HasColumnType("INTEGER"); + + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); + + b.Property("WorldId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TrackerStateId"); + + b.ToTable("TrackerPrerequisiteStates"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerRegionState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Medallion") + .HasColumnType("INTEGER"); + + b.Property("Reward") + .HasColumnType("INTEGER"); + + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); + + b.Property("TypeName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TrackerStateId"); + + b.ToTable("TrackerRegionStates"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerRewardState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AutoTracked") + .HasColumnType("INTEGER"); + + b.Property("HasReceivedReward") + .HasColumnType("INTEGER"); + + b.Property("MarkedReward") + .HasColumnType("INTEGER"); + + b.Property("RegionName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RewardType") + .HasColumnType("INTEGER"); + + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); + + b.Property("WorldId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TrackerStateId"); + + b.ToTable("TrackerRewardStates"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AltGameModeState") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GanonCrystalCount") + .HasColumnType("INTEGER"); + + b.Property("GanonsTowerCrystalCount") + .HasColumnType("INTEGER"); + + b.Property("GiftedItemCount") + .HasColumnType("INTEGER"); + + b.Property("LocalWorldId") + .HasColumnType("INTEGER"); + + b.Property("MarkedGanonCrystalCount") + .HasColumnType("INTEGER"); + + b.Property("MarkedGanonsTowerCrystalCount") + .HasColumnType("INTEGER"); + + b.Property("MarkedTourianBossCount") + .HasColumnType("INTEGER"); + + b.Property("PercentageCleared") + .HasColumnType("INTEGER"); + + b.Property("SecondsElapsed") + .HasColumnType("REAL"); + + b.Property("StartDateTime") + .HasColumnType("TEXT"); + + b.Property("TourianBossCount") + .HasColumnType("INTEGER"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TrackerStates"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerTreasureState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("HasManuallyClearedTreasure") + .HasColumnType("INTEGER"); + + b.Property("RegionName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RemainingTreasure") + .HasColumnType("INTEGER"); + + b.Property("TotalTreasure") + .HasColumnType("INTEGER"); + + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); + + b.Property("WorldId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TrackerStateId"); + + b.ToTable("TrackerTreasureStates"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.GeneratedRom", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Models.MultiplayerGameDetails", "MultiplayerGameDetails") + .WithMany() + .HasForeignKey("MultiplayerGameDetailsId"); + + b.HasOne("TrackerCouncil.Smz3.Shared.Models.TrackerState", "TrackerState") + .WithMany() + .HasForeignKey("TrackerStateId"); + + b.Navigation("MultiplayerGameDetails"); + + b.Navigation("TrackerState"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.MultiplayerGameDetails", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Models.GeneratedRom", "GeneratedRom") + .WithOne() + .HasForeignKey("TrackerCouncil.Smz3.Shared.Models.MultiplayerGameDetails", "GeneratedRomId"); + + b.Navigation("GeneratedRom"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerBossState", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Models.TrackerState", "TrackerState") + .WithMany("BossStates") + .HasForeignKey("TrackerStateId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("TrackerState"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerDungeonState", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Models.TrackerState", "TrackerState") + .WithMany("DungeonStates") + .HasForeignKey("TrackerStateId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("TrackerState"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerHintState", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Models.TrackerState", "TrackerState") + .WithMany("Hints") + .HasForeignKey("TrackerStateId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("TrackerState"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerHistoryEvent", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Models.TrackerState", "TrackerState") + .WithMany("History") + .HasForeignKey("TrackerStateId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("TrackerState"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerItemState", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Models.TrackerState", "TrackerState") + .WithMany("ItemStates") + .HasForeignKey("TrackerStateId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("TrackerState"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerLocationState", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Models.TrackerState", "TrackerState") + .WithMany("LocationStates") + .HasForeignKey("TrackerStateId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("TrackerState"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerMarkedLocation", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Models.TrackerState", "TrackerState") + .WithMany("MarkedLocations") + .HasForeignKey("TrackerStateId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("TrackerState"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerPrerequisiteState", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Models.TrackerState", "TrackerState") + .WithMany("PrerequisiteStates") + .HasForeignKey("TrackerStateId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("TrackerState"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerRegionState", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Models.TrackerState", "TrackerState") + .WithMany("RegionStates") + .HasForeignKey("TrackerStateId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("TrackerState"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerRewardState", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Models.TrackerState", "TrackerState") + .WithMany("RewardStates") + .HasForeignKey("TrackerStateId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("TrackerState"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerTreasureState", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Models.TrackerState", "TrackerState") + .WithMany("TreasureStates") + .HasForeignKey("TrackerStateId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("TrackerState"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerState", b => + { + b.Navigation("BossStates"); + + b.Navigation("DungeonStates"); + + b.Navigation("Hints"); + + b.Navigation("History"); + + b.Navigation("ItemStates"); + + b.Navigation("LocationStates"); + + b.Navigation("MarkedLocations"); + + b.Navigation("PrerequisiteStates"); + + b.Navigation("RegionStates"); + + b.Navigation("RewardStates"); + + b.Navigation("TreasureStates"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/TrackerCouncil.Smz3.Shared/Migrations/20260424192344_hardware-support.cs b/src/TrackerCouncil.Smz3.Shared/Migrations/20260424192344_hardware-support.cs new file mode 100644 index 000000000..2bc6d09b4 --- /dev/null +++ b/src/TrackerCouncil.Smz3.Shared/Migrations/20260424192344_hardware-support.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TrackerCouncil.Smz3.Shared.Migrations +{ + /// + public partial class AddHardwareSupport : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "HardwarePath", + table: "GeneratedRoms", + type: "TEXT", + maxLength: 256, + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "HardwarePath", + table: "GeneratedRoms"); + } + } +} diff --git a/src/TrackerCouncil.Smz3.Shared/Migrations/RandomizerContextModelSnapshot.cs b/src/TrackerCouncil.Smz3.Shared/Migrations/RandomizerContextModelSnapshot.cs index 1b242dbb4..edb5a1811 100644 --- a/src/TrackerCouncil.Smz3.Shared/Migrations/RandomizerContextModelSnapshot.cs +++ b/src/TrackerCouncil.Smz3.Shared/Migrations/RandomizerContextModelSnapshot.cs @@ -29,6 +29,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("GeneratorVersion") .HasColumnType("INTEGER"); + b.Property("HardwarePath") + .HasMaxLength(256) + .HasColumnType("TEXT"); + b.Property("Label") .IsRequired() .HasColumnType("TEXT"); diff --git a/src/TrackerCouncil.Smz3.Shared/Models/GeneratedRom.cs b/src/TrackerCouncil.Smz3.Shared/Models/GeneratedRom.cs index 8f4f83b2b..1a9cb93c3 100644 --- a/src/TrackerCouncil.Smz3.Shared/Models/GeneratedRom.cs +++ b/src/TrackerCouncil.Smz3.Shared/Models/GeneratedRom.cs @@ -21,6 +21,8 @@ public class GeneratedRom [ForeignKey("MultiplayerGameDetails")] public long? MultiplayerGameDetailsId { get; set; } public string? MsuPaths { get; set; } + [MaxLength(256)] + public string? HardwarePath { get; set; } public MsuRandomizationStyle? MsuRandomizationStyle { get; set; } public MsuShuffleStyle? MsuShuffleStyle { get; set; } public virtual MultiplayerGameDetails? MultiplayerGameDetails { get; set; } diff --git a/src/TrackerCouncil.Smz3.Shared/TrackerCouncil.Smz3.Shared.csproj b/src/TrackerCouncil.Smz3.Shared/TrackerCouncil.Smz3.Shared.csproj index de3b2d8c6..9fddf0fe6 100644 --- a/src/TrackerCouncil.Smz3.Shared/TrackerCouncil.Smz3.Shared.csproj +++ b/src/TrackerCouncil.Smz3.Shared/TrackerCouncil.Smz3.Shared.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTracker.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTracker.cs index 8921dd782..73558aca5 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTracker.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTracker.cs @@ -70,10 +70,10 @@ private async void SnesConnectorServiceOnConnected(object? sender, EventArgs e) try { _logger.LogInformation("Connector connected"); - _validationCts = new CancellationTokenSource(); + var cts = _validationCts = new CancellationTokenSource(); await Task.Delay(TimeSpan.FromSeconds(2), _validationCts.Token); - if (!_validationCts.IsCancellationRequested) + if (!cts.IsCancellationRequested) { _logger.LogInformation("Connection validated"); TrackerBase.Say(x => x.AutoTracker.WhenConnected); diff --git a/src/TrackerCouncil.Smz3.Tracking/TrackerCouncil.Smz3.Tracking.csproj b/src/TrackerCouncil.Smz3.Tracking/TrackerCouncil.Smz3.Tracking.csproj index c8caeb336..680c4944c 100644 --- a/src/TrackerCouncil.Smz3.Tracking/TrackerCouncil.Smz3.Tracking.csproj +++ b/src/TrackerCouncil.Smz3.Tracking/TrackerCouncil.Smz3.Tracking.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MsuModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MsuModule.cs index c6f064e37..26186f264 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MsuModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MsuModule.cs @@ -9,6 +9,7 @@ using MSURandomizerLibrary.Models; using MSURandomizerLibrary.Services; using PySpeechService.Recognition; +using SnesConnectorLibrary.Responses; using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Data.Configuration; using TrackerCouncil.Smz3.Data.Configuration.ConfigFiles; @@ -99,7 +100,24 @@ private void InitializeLocalMsuSupport(MsuType msuType) return; } + _msuUserOptionsService.MsuUserOptions.MsuCurrentSongOutputFilePath = TrackerBase.Options.MsuTrackOutputPath; + _msuUserOptionsService.MsuUserOptions.TrackDisplayFormat = TrackerBase.Options.TrackDisplayFormat; + _msuUserOptionsService.MsuUserOptions.MsuShuffleStyle = + TrackerBase.Rom!.MsuShuffleStyle ?? MsuShuffleStyle.StandardShuffle; + var romFileInfo = new FileInfo(TrackerBase.RomPath); + + if (!string.IsNullOrEmpty(TrackerBase.Rom?.HardwarePath) && !string.IsNullOrEmpty(TrackerBase.Rom?.MsuPaths)) + { + _currentMsu = _msuLookupService.LoadMsu(TrackerBase.Rom!.MsuPaths, msuType, false, true, true); + if (_currentMsu != null) + { + _msuMonitorService.MsuTrackChanged += MsuMonitorServiceOnMsuTrackChanged; + _msuMonitorService.StartMonitor(_currentMsu, msuType); + } + return; + } + var msuPath = romFileInfo.FullName.Replace(romFileInfo.Extension, ".msu"); if (!File.Exists(msuPath)) @@ -123,11 +141,6 @@ private void InitializeLocalMsuSupport(MsuType msuType) return; } - _msuUserOptionsService.MsuUserOptions.MsuCurrentSongOutputFilePath = TrackerBase.Options.MsuTrackOutputPath; - _msuUserOptionsService.MsuUserOptions.TrackDisplayFormat = TrackerBase.Options.TrackDisplayFormat; - _msuUserOptionsService.MsuUserOptions.MsuShuffleStyle = - TrackerBase.Rom!.MsuShuffleStyle ?? MsuShuffleStyle.StandardShuffle; - var inputMsus = TrackerBase.Rom!.MsuPaths?.Split("|"); _msuShuffleRequest = new MsuSelectorRequest { diff --git a/src/TrackerCouncil.Smz3.UI/Services/MultiplayerStatusWindowService.cs b/src/TrackerCouncil.Smz3.UI/Services/MultiplayerStatusWindowService.cs index 09064abf1..2465fb3d9 100644 --- a/src/TrackerCouncil.Smz3.UI/Services/MultiplayerStatusWindowService.cs +++ b/src/TrackerCouncil.Smz3.UI/Services/MultiplayerStatusWindowService.cs @@ -139,6 +139,15 @@ public async Task LaunchRom() FinalizeLaunch(); } + public async Task UploadRomToHardware() + { + if (_model.GeneratedRom == null) + { + return; + } + await sharedCrossplatformService.UploadRomToHardware(_model.GeneratedRom, _window); + } + private void FinalizeLaunch() { if (_trackerWindow != null) diff --git a/src/TrackerCouncil.Smz3.UI/Services/SharedCrossplatformService.cs b/src/TrackerCouncil.Smz3.UI/Services/SharedCrossplatformService.cs index 1b3073f78..8840274a5 100644 --- a/src/TrackerCouncil.Smz3.UI/Services/SharedCrossplatformService.cs +++ b/src/TrackerCouncil.Smz3.UI/Services/SharedCrossplatformService.cs @@ -5,14 +5,18 @@ using System.Linq; using System.Threading.Tasks; using Avalonia.Controls; +using Avalonia.Threading; using AvaloniaControls; using AvaloniaControls.Controls; using AvaloniaControls.Models; using AvaloniaControls.Services; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using MSURandomizer.Views; using MSURandomizerLibrary; using MSURandomizerLibrary.Services; +using SnesConnectorLibrary; +using SnesConnectorLibrary.Requests; using TrackerCouncil.Smz3.Data; using TrackerCouncil.Smz3.Data.Options; using TrackerCouncil.Smz3.Data.ParsedRom; @@ -20,6 +24,7 @@ using TrackerCouncil.Smz3.SeedGenerator.Generation; using TrackerCouncil.Smz3.SeedGenerator.Infrastructure; using TrackerCouncil.Smz3.Shared.Models; +using TrackerCouncil.Smz3.Tracking; using TrackerCouncil.Smz3.Tracking.Services; using TrackerCouncil.Smz3.UI.Views; @@ -34,6 +39,7 @@ public class SharedCrossplatformService( Smz3GeneratedRomLoader smz3GeneratedRomLoader, IMsuTypeService msuTypeService, IMetadataService metadataService, + ISnesConnectorService snesConnectorService, ILogger logger) { private static TrackerWindow? s_trackerWindow; @@ -124,7 +130,7 @@ public void OpenSpoilerLog(GeneratedRom? rom) } } - public void PlayRom(GeneratedRom? rom) + public void PlayRom(GeneratedRom? rom, Window? parentWindow = null) { if (rom == null) { @@ -132,15 +138,59 @@ public void PlayRom(GeneratedRom? rom) return; } - try + if (string.IsNullOrEmpty(rom.HardwarePath)) { - romLauncherService.LaunchRom(rom); + try + { + romLauncherService.LaunchRom(rom); + } + catch (Exception e) + { + logger.LogError(e, "Could not launch rom"); + DisplayError( + "There was an issue launching the rom. Make sure the rom file still exists and that you have a valid application set to launch roms in either the randomizer options or your operating system."); + } } - catch (Exception e) + else { - logger.LogError(e, "Could not launch rom"); - DisplayError( - "There was an issue launching the rom. Make sure the rom file still exists and that you have a valid application set to launch roms in either the randomizer options or your operating system."); + // If we're connected to a connector that supports file access, boot the rom immediately + if (snesConnectorService is { IsConnected: true, CurrentConnectorFunctionality.CanAccessFiles: true }) + { + Task.Run(async () => + { + await Task.Delay(TimeSpan.FromSeconds(2)); + snesConnectorService.BootRom(new SnesBootRomRequest { Path = rom.HardwarePath }); + }); + } + // Else open the connector window and wait to be connected + else + { + var connectorWindow = new ConnectorWindow(); + connectorWindow.UpdateButtons("Continue"); + + // Detect if the connector connects while the window is open, close the window + var autoClosed = false; + snesConnectorService.Connected += (_, _) => + { + autoClosed = true; + Dispatcher.UIThread.Invoke(connectorWindow.Close); + }; + + // When the window is closed, boot the rom + connectorWindow.Closed += (_, _) => + { + if (connectorWindow.ClickedPrimaryButton || autoClosed) + { + Task.Run(async () => + { + await Task.Delay(TimeSpan.FromSeconds(2)); + snesConnectorService.BootRom(new SnesBootRomRequest { Path = rom.HardwarePath }); + }); + } + }; + + connectorWindow.ShowDialog(parentWindow ?? MessageWindow.GlobalParentWindow!); + } } } @@ -368,6 +418,89 @@ public void LookupMsus() }); } + public async Task UploadRomToHardware(GeneratedRom rom, Window? parentWindow = null) + { + parentWindow ??= ParentWindow; + + // Open the connector window and make sure the user is connected + var connectorWindow = new ConnectorWindow(); + connectorWindow.UpdateButtons("Browse Hardware Folders", "Select Hardware MSU(s)"); + if (!await connectorWindow.ShowDialog(parentWindow)) + { + return; + } + + string? hardwarePath = null; + string? msuPath = null; + + // Have the user browse all folders to select the destination for the rom + if (!connectorWindow.ClickedSecondaryButton) + { + var hardwareWindow = new HardwareDirectoriesWindow([".msu", ".sfc"]) { KeepConnectionAlive = true }; + var path = await hardwareWindow.ShowFileDialog(parentWindow); + if (!string.IsNullOrEmpty(path)) + { + if (path.EndsWith('/')) + { + var msu = hardwareWindow.AllPaths?.FirstOrDefault(x => + x.StartsWith(path) && x.EndsWith(".msu", StringComparison.InvariantCultureIgnoreCase)); + if (!string.IsNullOrEmpty(msu)) + { + hardwarePath = path.Replace(".msu", ".sfc", StringComparison.OrdinalIgnoreCase); + } + else + { + hardwarePath = path + Path.GetFileName(rom.RomPath); + } + } + else if (path.EndsWith(".sfc", StringComparison.OrdinalIgnoreCase)) + { + hardwarePath = path; + } + else if (path.EndsWith(".msu", StringComparison.OrdinalIgnoreCase)) + { + hardwarePath = path.Replace(".msu", ".sfc", StringComparison.OrdinalIgnoreCase); + } + } + } + // Open the MSU selection window in hardware mode to select the MSU(s) to use + else + { + var window = serviceProvider.GetRequiredService(); + window.WindowStartupLocation = WindowStartupLocation.CenterOwner; + window.HardwareMode = true; + await window.ShowDialog(parentWindow, false, Options.GeneralOptions.MsuPath); + + var selectedMsus = window.GetSelectedMsus().ToList(); + + if (selectedMsus.Count == 0) + { + return; + } + + var msu = selectedMsus.Random(); + hardwarePath = msu?.Replace(".msu", ".sfc", StringComparison.OrdinalIgnoreCase); + + var msuDetails = window.GetSelectedMsusDetails().FirstOrDefault(x => x.Path == msu); + msuPath = msuDetails?.LocalMsuForHardwareMsu; + } + + if (string.IsNullOrEmpty(hardwarePath)) + { + return; + } + + // Upload the rom to hardware and update the database entry + var uploadWindow = new UploadRomToHardwareWindow { WindowStartupLocation = WindowStartupLocation.CenterOwner }; + var romPath = Path.Combine(Options.RomOutputPath, rom.RomPath); + await uploadWindow.ShowDialog(parentWindow ?? ParentWindow, romPath, hardwarePath); + + if (uploadWindow.DidUploadSuccessfully) + { + gameDbService.UpdateGeneratedRom(rom, hardwarePath: hardwarePath, msuPath: msuPath); + } + } + private RandomizerOptions Options { get diff --git a/src/TrackerCouncil.Smz3.UI/Services/SoloRomListService.cs b/src/TrackerCouncil.Smz3.UI/Services/SoloRomListService.cs index 87ff969fb..8130e0732 100644 --- a/src/TrackerCouncil.Smz3.UI/Services/SoloRomListService.cs +++ b/src/TrackerCouncil.Smz3.UI/Services/SoloRomListService.cs @@ -8,10 +8,12 @@ using AvaloniaControls.Controls; using AvaloniaControls.Models; using AvaloniaControls.Services; +using Microsoft.Extensions.Logging; using TrackerCouncil.Smz3.Data.Interfaces; using TrackerCouncil.Smz3.Data.Options; using TrackerCouncil.Smz3.Data.Services; using TrackerCouncil.Smz3.SeedGenerator.Generation; +using TrackerCouncil.Smz3.Shared.Models; using TrackerCouncil.Smz3.UI.ViewModels; using TrackerCouncil.Smz3.UI.Views; @@ -21,6 +23,7 @@ public class SoloRomListService(IRomGenerationService romGenerationService, IGameDbService gameDbService, OptionsFactory optionsFactory, SharedCrossplatformService sharedCrossplatformService, + ILogger logger, Smz3RomParser smz3RomParser) : ControlService { private SoloRomListViewModel _model = new(); @@ -189,6 +192,16 @@ public async Task OpenArchipelagoModeAsync() return false; } + public async Task UploadRomToHardware(GeneratedRom rom) + { + await sharedCrossplatformService.UploadRomToHardware(rom); + } + + public void LogException(Exception e, string message) + { + logger.LogError(e, message); + } + private void OpenMessageWindow(string message, MessageWindowIcon icon = MessageWindowIcon.Error, MessageWindowButtons buttons = MessageWindowButtons.OK) { var window = new MessageWindow(new MessageWindowRequest() diff --git a/src/TrackerCouncil.Smz3.UI/Services/TrackerWindowService.cs b/src/TrackerCouncil.Smz3.UI/Services/TrackerWindowService.cs index 387e54178..f4b457613 100644 --- a/src/TrackerCouncil.Smz3.UI/Services/TrackerWindowService.cs +++ b/src/TrackerCouncil.Smz3.UI/Services/TrackerWindowService.cs @@ -916,7 +916,7 @@ public void LaunchRom() { try { - sharedCrossplatformService.PlayRom(tracker.Rom); + sharedCrossplatformService.PlayRom(tracker.Rom, _window); } catch (Exception e) { diff --git a/src/TrackerCouncil.Smz3.UI/Services/UploadRomToHardwareWindowService.cs b/src/TrackerCouncil.Smz3.UI/Services/UploadRomToHardwareWindowService.cs new file mode 100644 index 000000000..6f1c0b6e7 --- /dev/null +++ b/src/TrackerCouncil.Smz3.UI/Services/UploadRomToHardwareWindowService.cs @@ -0,0 +1,33 @@ +using AvaloniaControls.Services; +using SnesConnectorLibrary; +using SnesConnectorLibrary.Requests; +using TrackerCouncil.Smz3.UI.ViewModels; + +namespace TrackerCouncil.Smz3.UI.Services; + +public class UploadRomToHardwareWindowService(ISnesConnectorService snesConnectorService) : ControlService +{ + private UploadRomToHardwareWindowViewModel _model = new UploadRomToHardwareWindowViewModel(); + + public UploadRomToHardwareWindowViewModel GetViewModel() + { + return _model; + } + + public void StartUpload(string rom, string target) + { + snesConnectorService.UploadFile(new SnesUploadFileRequest() + { + LocalFilePath = rom, + TargetFilePath = target, + OnComplete = () => + { + _model.MainText = "Complete"; + _model.IsLoading = false; + _model.ButtonText = "Close"; + + snesConnectorService.Disconnect(); + } + }); + } +} diff --git a/src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj b/src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj index 4e08350ff..6127a22db 100644 --- a/src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj +++ b/src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj @@ -6,7 +6,7 @@ true app.manifest true - 10.0.0-beta.1 + 10.0.0-beta.2 SMZ3CasRandomizer Assets\smz3.ico $(MSBuildProjectName.Replace(" ", "_")) @@ -25,7 +25,7 @@ - + all diff --git a/src/TrackerCouncil.Smz3.UI/ViewModels/GeneratedRomViewModel.cs b/src/TrackerCouncil.Smz3.UI/ViewModels/GeneratedRomViewModel.cs index 28d7e00e3..50ffdd15e 100644 --- a/src/TrackerCouncil.Smz3.UI/ViewModels/GeneratedRomViewModel.cs +++ b/src/TrackerCouncil.Smz3.UI/ViewModels/GeneratedRomViewModel.cs @@ -26,6 +26,8 @@ public string LocationsLabel [Reactive] public partial bool IsEditTextBoxVisible { get; set; } + [Reactive] public partial bool IsUploadedToHardware { get; set; } = !string.IsNullOrEmpty(rom.HardwarePath); + public string TimeLabel { get diff --git a/src/TrackerCouncil.Smz3.UI/ViewModels/UploadRomToHardwareWindowViewModel.cs b/src/TrackerCouncil.Smz3.UI/ViewModels/UploadRomToHardwareWindowViewModel.cs new file mode 100644 index 000000000..b13b01bff --- /dev/null +++ b/src/TrackerCouncil.Smz3.UI/ViewModels/UploadRomToHardwareWindowViewModel.cs @@ -0,0 +1,14 @@ +using ReactiveUI.SourceGenerators; + +namespace TrackerCouncil.Smz3.UI.ViewModels; + +public partial class UploadRomToHardwareWindowViewModel : ViewModelBase +{ + [Reactive] + public partial string MainText { get; set; } = "Uploading..."; + + [Reactive] + public partial bool IsLoading { get; set; } = true; + + [Reactive] public partial string ButtonText { get; set; } = "Cancel"; +} diff --git a/src/TrackerCouncil.Smz3.UI/Views/MultiplayerStatusWindow.axaml b/src/TrackerCouncil.Smz3.UI/Views/MultiplayerStatusWindow.axaml index ff6f9bd3e..0670b4b37 100644 --- a/src/TrackerCouncil.Smz3.UI/Views/MultiplayerStatusWindow.axaml +++ b/src/TrackerCouncil.Smz3.UI/Views/MultiplayerStatusWindow.axaml @@ -112,6 +112,11 @@ Name="ViewSpoilerLogMenuItem" Click="ViewSpoilerLogMenuItem_OnClick" > + diff --git a/src/TrackerCouncil.Smz3.UI/Views/MultiplayerStatusWindow.axaml.cs b/src/TrackerCouncil.Smz3.UI/Views/MultiplayerStatusWindow.axaml.cs index b828b8f6f..d799a5a5c 100644 --- a/src/TrackerCouncil.Smz3.UI/Views/MultiplayerStatusWindow.axaml.cs +++ b/src/TrackerCouncil.Smz3.UI/Views/MultiplayerStatusWindow.axaml.cs @@ -115,5 +115,10 @@ private void TopLevel_OnClosed(object? sender, EventArgs e) { _service?.Dispose(); } + + private void UploadToHardwareMenuItem_OnClick(object? sender, RoutedEventArgs e) + { + _ = _service?.UploadRomToHardware(); + } } diff --git a/src/TrackerCouncil.Smz3.UI/Views/SoloRomListPanel.axaml b/src/TrackerCouncil.Smz3.UI/Views/SoloRomListPanel.axaml index 94f742852..725a416a6 100644 --- a/src/TrackerCouncil.Smz3.UI/Views/SoloRomListPanel.axaml +++ b/src/TrackerCouncil.Smz3.UI/Views/SoloRomListPanel.axaml @@ -22,6 +22,7 @@ + diff --git a/src/TrackerCouncil.Smz3.UI/Views/SoloRomListPanel.axaml.cs b/src/TrackerCouncil.Smz3.UI/Views/SoloRomListPanel.axaml.cs index a54b8fb9c..a82a6b8b3 100644 --- a/src/TrackerCouncil.Smz3.UI/Views/SoloRomListPanel.axaml.cs +++ b/src/TrackerCouncil.Smz3.UI/Views/SoloRomListPanel.axaml.cs @@ -1,11 +1,15 @@ +using System; +using System.IO; using System.Linq; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.LogicalTree; +using Avalonia.Threading; using AvaloniaControls.Controls; using AvaloniaControls.Services; +using MSURandomizer.Views; using TrackerCouncil.Smz3.UI.Services; using TrackerCouncil.Smz3.UI.ViewModels; @@ -231,5 +235,23 @@ await MessageWindow.ShowInfoDialog( await MessageWindow.ShowErrorDialog("Invalid ROM file. Please select a valid generated SMZ3 rom."); } } + + private async void HardwareMenuItem_OnClick(object? sender, RoutedEventArgs e) + { + try + { + if (!GetRomFromControl(sender, out var rom, out _) || _service == null || !string.IsNullOrEmpty(rom!.Rom.HardwarePath)) + { + return; + } + + await _service.UploadRomToHardware(rom.Rom); + rom.IsUploadedToHardware = !string.IsNullOrEmpty(rom.Rom.HardwarePath); + } + catch (Exception ex) + { + _service?.LogException(ex, "Error uploading rom"); + } + } } diff --git a/src/TrackerCouncil.Smz3.UI/Views/UploadRomToHardwareWindow.axaml b/src/TrackerCouncil.Smz3.UI/Views/UploadRomToHardwareWindow.axaml new file mode 100644 index 000000000..f3157f0c3 --- /dev/null +++ b/src/TrackerCouncil.Smz3.UI/Views/UploadRomToHardwareWindow.axaml @@ -0,0 +1,46 @@ + + + + + + + + +