From 2fea61a21a5e66e9655f6f5f28a76707c14c4fb8 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 15 May 2026 16:13:03 +0000 Subject: [PATCH] Fix newly-created profile reappearing after restart when deleted A profile created via "New Profile" in MMLaunch holds an empty ProfileKeyName in memory. Picking an EXE triggers RegConfig.saveProfile, which lazily creates a registry subkey (e.g. Profile0000) but discarded its name via `ignore`, so the in-memory RunConfig stayed unsynced. The delete handler skipped RegConfig.removeProfile because ProfileKeyName was still "", leaving the orphan subkey to be re-enumerated by loadAll on next launch. saveProfile now returns the updated RunConfig with ProfileKeyName set to the bare subkey name, and ProfileModel.save assigns it back to its mutable config. This keeps in-memory state consistent with the registry so the existing delete guard works on the first try. --- MMLaunch/MainWindow.axaml.fs | 2 +- MMManaged/RegConfig.fs | 52 +++++++++++++++++++----------------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/MMLaunch/MainWindow.axaml.fs b/MMLaunch/MainWindow.axaml.fs index 515fbf0..739da47 100644 --- a/MMLaunch/MainWindow.axaml.fs +++ b/MMLaunch/MainWindow.axaml.fs @@ -137,7 +137,7 @@ type ProfileModel(config: ConfigTypes.RunConfig) = else config let save () = - try RegConfig.saveProfile config + try config <- RegConfig.saveProfile config with e -> ViewModelUtil.pushDialog (sprintf "%s" e.Message) let mutable iconSource: Bitmap option = diff --git a/MMManaged/RegConfig.fs b/MMManaged/RegConfig.fs index 2ab520a..d99ee57 100644 --- a/MMManaged/RegConfig.fs +++ b/MMManaged/RegConfig.fs @@ -207,8 +207,11 @@ module RegConfig = pName /// Save a profile using the values from the supplied config. At a minimum, - /// ExePath must be set in the config. - let saveProfile (conf:RunConfig) = + /// ExePath must be set in the config. Returns the updated config with + /// ProfileKeyName populated, so callers holding a stale in-memory copy + /// can stay in sync with the registry after the first save creates a + /// new subkey. + let saveProfile (conf:RunConfig):RunConfig = if not (File.Exists conf.ExePath) then failwithf "Exe path does not exist, cannot save profile: %A" conf.ExePath @@ -226,28 +229,29 @@ module RegConfig = let profSave k v = setProfileValue profKey k v - // this is a syntactic trick to make sure I get a compiler error if I forget to save a field - ignore - ({ - ProfileKeyName = profKey - ProfileName = profSave RegKeys.ProfName conf.ProfileName - ConfigTypes.RunConfig.ExePath = profSave RegKeys.ProfExePath conf.ExePath - RunModeFull = profSave RegKeys.ProfRunModeFull (boolAsDword conf.RunModeFull) |> dwordAsBool - LoadModsOnStart = profSave RegKeys.ProfLoadModsOnStart (boolAsDword conf.LoadModsOnStart) |> dwordAsBool - InputProfile = profSave RegKeys.ProfInputProfile conf.InputProfile - SnapshotProfile = profSave RegKeys.ProfSnapshotProfile conf.SnapshotProfile - - DocRoot = "" // custom doc root not yet supported - LaunchWindow = profSave RegKeys.ProfLaunchWindow conf.LaunchWindow - MinimumFPS = profSave RegKeys.ProfMinimumFPS conf.MinimumFPS - GameProfile = - { - ReverseNormals = profSave RegKeys.ProfGPReverseNormals (boolAsDword conf.GameProfile.ReverseNormals) |> dwordAsBool - UpdateTangentSpace = profSave RegKeys.ProfGPUpdateTangentSpace (boolAsDword conf.GameProfile.UpdateTangentSpace) |> dwordAsBool - CommandLineArguments = profSave RegKeys.ProfGPCommandLineArguments conf.GameProfile.CommandLineArguments - DataPathName = profSave RegKeys.ProfGPDataPathName conf.GameProfile.DataPathName - } - }) + // Store the bare subkey name (e.g. "Profile0000") to match loadFromProfileKey. + // The full path would cause the `regLoc.ProfRoot @@ conf.ProfileKeyName` branch + // above to double-prefix on subsequent saves. + { + ProfileKeyName = Path.GetFileName profKey + ProfileName = profSave RegKeys.ProfName conf.ProfileName + ConfigTypes.RunConfig.ExePath = profSave RegKeys.ProfExePath conf.ExePath + RunModeFull = profSave RegKeys.ProfRunModeFull (boolAsDword conf.RunModeFull) |> dwordAsBool + LoadModsOnStart = profSave RegKeys.ProfLoadModsOnStart (boolAsDword conf.LoadModsOnStart) |> dwordAsBool + InputProfile = profSave RegKeys.ProfInputProfile conf.InputProfile + SnapshotProfile = profSave RegKeys.ProfSnapshotProfile conf.SnapshotProfile + + DocRoot = "" // custom doc root not yet supported + LaunchWindow = profSave RegKeys.ProfLaunchWindow conf.LaunchWindow + MinimumFPS = profSave RegKeys.ProfMinimumFPS conf.MinimumFPS + GameProfile = + { + ReverseNormals = profSave RegKeys.ProfGPReverseNormals (boolAsDword conf.GameProfile.ReverseNormals) |> dwordAsBool + UpdateTangentSpace = profSave RegKeys.ProfGPUpdateTangentSpace (boolAsDword conf.GameProfile.UpdateTangentSpace) |> dwordAsBool + CommandLineArguments = profSave RegKeys.ProfGPCommandLineArguments conf.GameProfile.CommandLineArguments + DataPathName = profSave RegKeys.ProfGPDataPathName conf.GameProfile.DataPathName + } + } /// Remove a profile. Uses the profile key name in the config to locate the /// profile. Does not require ExePath to be set.