diff --git a/src/TrackerCouncil.Smz3.Data/Services/GameDbService.cs b/src/TrackerCouncil.Smz3.Data/Services/GameDbService.cs
index a4f70e6a..b165cc05 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 996b3491..ee7a9e6c 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 3c43db78..f62e14e6 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 b34fac0c..63bf041c 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 00000000..2e213631
--- /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 00000000..2bc6d09b
--- /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 1b242dbb..edb5a181 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 8f4f83b2..1a9cb93c 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 de3b2d8c..9fddf0fe 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 8921dd78..73558aca 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 c8caeb33..680c4944 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 c6f064e3..26186f26 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 09064abf..2465fb3d 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 1b3073f7..8840274a 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 87ff969f..8130e073 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 387e5417..f4b45761 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 00000000..6f1c0b6e
--- /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 4e08350f..6127a22d 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 28d7e00e..50ffdd15 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 00000000..b13b01bf
--- /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 ff6f9bd3..0670b4b3 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 b828b8f6..d799a5a5 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 94f74285..725a416a 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 a54b8fb9..a82a6b8b 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