diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 0b47a82..23ca160 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -2,7 +2,7 @@ name: .NET env: PACK_ID: Sims1WidescreenPatcher - PACK_VER: 3.12.0 + PACK_VER: 3.13.0 on: push: diff --git a/Sims1WidescreenPatcher.Core/Events/NewProgressEventArgs.cs b/Sims1WidescreenPatcher.Core/Events/NewProgressEventArgs.cs index 5945f6f..c4c1ecf 100644 --- a/Sims1WidescreenPatcher.Core/Events/NewProgressEventArgs.cs +++ b/Sims1WidescreenPatcher.Core/Events/NewProgressEventArgs.cs @@ -6,9 +6,6 @@ public class NewProgressEventArgs public string Status { get; } public string Status2 { get; } - public NewProgressEventArgs(double progress) - : this(progress, string.Empty, string.Empty) { } - public NewProgressEventArgs(double progress, string status, string status2) { Progress = progress; diff --git a/Sims1WidescreenPatcher.Core/Events/NewUninstallEventArgs.cs b/Sims1WidescreenPatcher.Core/Events/NewUninstallEventArgs.cs deleted file mode 100644 index 833f200..0000000 --- a/Sims1WidescreenPatcher.Core/Events/NewUninstallEventArgs.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Sims1WidescreenPatcher.Core.Events; - -public class NewUninstallEventArgs : EventArgs { } diff --git a/Sims1WidescreenPatcher.Core/Factories/CareerEditorViewModelFactory.cs b/Sims1WidescreenPatcher.Core/Factories/CareerEditorViewModelFactory.cs new file mode 100644 index 0000000..bd2102e --- /dev/null +++ b/Sims1WidescreenPatcher.Core/Factories/CareerEditorViewModelFactory.cs @@ -0,0 +1,28 @@ +using Sims.Far; +using Sims1WidescreenPatcher.Core.Models; +using Sims1WidescreenPatcher.Core.Services; +using Sims1WidescreenPatcher.Core.ViewModels; + +namespace Sims1WidescreenPatcher.Core.Factories; + +public interface ICareerEditorViewModelFactory +{ + CareerEditorDialogViewModel Create(); +} + +public class CareerEditorViewModelFactory : ICareerEditorViewModelFactory +{ + private readonly IAppState _appState; + private readonly IFar _far; + private readonly IIffService _iffService; + + public CareerEditorViewModelFactory(IAppState appState, IFar far, IIffService iffService) + { + _appState = appState; + _far = far; + _iffService = iffService; + } + + public CareerEditorDialogViewModel Create() => + new CareerEditorDialogViewModel(_appState, _far, _iffService); +} diff --git a/Sims1WidescreenPatcher.Core/Models/AppState.cs b/Sims1WidescreenPatcher.Core/Models/AppState.cs index b4997e2..d0d4b71 100644 --- a/Sims1WidescreenPatcher.Core/Models/AppState.cs +++ b/Sims1WidescreenPatcher.Core/Models/AppState.cs @@ -2,6 +2,15 @@ namespace Sims1WidescreenPatcher.Core.Models; +public interface IAppState +{ + string? SimsExePath { get; set; } + bool SimsExePathExists { get; } + Resolution? Resolution { get; set; } + string SimsBackupPath { get; } + bool SimsBackupExists { get; } +} + public class AppState : ReactiveObject, IAppState { private string? _simsExePath; diff --git a/Sims1WidescreenPatcher.Core/Models/CheckboxSelectionSnapshot.cs b/Sims1WidescreenPatcher.Core/Models/CheckboxSelectionSnapshot.cs index 14e297a..a52dc1f 100644 --- a/Sims1WidescreenPatcher.Core/Models/CheckboxSelectionSnapshot.cs +++ b/Sims1WidescreenPatcher.Core/Models/CheckboxSelectionSnapshot.cs @@ -2,46 +2,51 @@ public class CheckboxSelectionSnapshot : IEquatable { - private readonly bool[] _vms; - - public CheckboxSelectionSnapshot(params bool[] vms) + public CheckboxSelectionSnapshot(params ValueTuple[] values) { - _vms = vms; + States = new Dictionary(); + foreach (var vt in values) + { + States.Add(vt.Item1, vt.Item2); + } } + public readonly Dictionary States; + public bool Equals(CheckboxSelectionSnapshot? other) { - if (ReferenceEquals(null, other)) + if (other is null) return false; if (ReferenceEquals(this, other)) return true; - if (_vms.Length != other._vms.Length) - return false; - - for (int i = 0; i < _vms.Length; i++) + foreach (var state in States) { - if (_vms[i] != other._vms[i]) + if (!other.States.ContainsKey(state.Key)) { return false; } - } + if (other.States[state.Key] != state.Value) + { + return false; + } + } return true; } public override bool Equals(object? obj) { - if (ReferenceEquals(null, obj)) + if (obj is null) return false; if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) + if (obj.GetType() != GetType()) return false; return Equals((CheckboxSelectionSnapshot)obj); } public override int GetHashCode() { - return _vms.GetHashCode(); + return States.GetHashCode(); } } diff --git a/Sims1WidescreenPatcher.Core/Models/IAppState.cs b/Sims1WidescreenPatcher.Core/Models/IAppState.cs deleted file mode 100644 index 25faa6d..0000000 --- a/Sims1WidescreenPatcher.Core/Models/IAppState.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Sims1WidescreenPatcher.Core.Models; - -public interface IAppState -{ - string? SimsExePath { get; set; } - bool SimsExePathExists { get; } - Resolution? Resolution { get; set; } - string SimsBackupPath { get; } - bool SimsBackupExists { get; } -} diff --git a/Sims1WidescreenPatcher.Core/Resources/dd_domcal.iff b/Sims1WidescreenPatcher.Core/Resources/dd_domcal.iff new file mode 100644 index 0000000..3fff3b8 Binary files /dev/null and b/Sims1WidescreenPatcher.Core/Resources/dd_domcal.iff differ diff --git a/Sims1WidescreenPatcher.Core/Services/CheatsService.cs b/Sims1WidescreenPatcher.Core/Services/CheatsService.cs index 8dd1322..4f75d50 100644 --- a/Sims1WidescreenPatcher.Core/Services/CheatsService.cs +++ b/Sims1WidescreenPatcher.Core/Services/CheatsService.cs @@ -3,6 +3,20 @@ namespace Sims1WidescreenPatcher.Core.Services; +public interface ICheatsService +{ + bool CheatsEnabled(); + + /// + /// Determine if the sims exe can be patched to enable all cheats + /// + /// + bool CanEnableCheats(); + + void EnableCheats(); + void DisableCheats(); +} + public class CheatsService : ICheatsService { private readonly IAppState _appState; diff --git a/Sims1WidescreenPatcher.Core/Services/CheckDDrawCompatIniService.cs b/Sims1WidescreenPatcher.Core/Services/CheckDDrawCompatIniService.cs deleted file mode 100644 index 329d4ef..0000000 --- a/Sims1WidescreenPatcher.Core/Services/CheckDDrawCompatIniService.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Sims1WidescreenPatcher.Core.Services; - -public static class CheckDDrawCompatIniService -{ - public static string DDrawCompatSettingsExist(string pathToSimsExe) - { - var dir = Path.GetDirectoryName(pathToSimsExe); - if (string.IsNullOrWhiteSpace(dir)) - { - throw new ArgumentException($"Invalid directory: {pathToSimsExe}"); - } - - var ddrawSettingsPath = Path.Combine(dir, "DDrawCompat.ini"); - return File.Exists(ddrawSettingsPath) ? ddrawSettingsPath : string.Empty; - } -} diff --git a/Sims1WidescreenPatcher.Core/Services/DDrawCompatSettingsService.cs b/Sims1WidescreenPatcher.Core/Services/DDrawCompatSettingsService.cs index d699b52..512d77c 100644 --- a/Sims1WidescreenPatcher.Core/Services/DDrawCompatSettingsService.cs +++ b/Sims1WidescreenPatcher.Core/Services/DDrawCompatSettingsService.cs @@ -14,7 +14,8 @@ params DDrawCompatEnums[] settings { throw new DirectoryNotFoundException($"Directory not found: {dir}"); } - using var sw = new StreamWriter(Path.Combine(dir, "DDrawCompat.ini")); + + await using var sw = new StreamWriter(Path.Combine(dir, "DDrawCompat.ini")); foreach (var setting in settings) { switch (setting) @@ -26,12 +27,27 @@ params DDrawCompatEnums[] settings await sw.WriteLineAsync("FullscreenMode=exclusive"); await sw.WriteLineAsync("DisplayResolution=app"); break; - default: - break; } } await sw.WriteLineAsync("CPUAffinity=all"); // the default was changed to 1 in 0.4.0 which was a culprit for the major issues, crashes, and lag await sw.WriteLineAsync("DisplayRefreshRate=desktop"); // removes erroneous lock to 60fps on higher-than-60hz displays when vsync is enabled await sw.WriteLineAsync("AltTabFix=keepvidmem"); // fixes crashes/bugs when using Alt+Tab } + + public static string DDrawCompatSettingsExist(string pathToSimsExe) + { + var dir = Path.GetDirectoryName(pathToSimsExe); + if (string.IsNullOrWhiteSpace(dir)) + { + throw new ArgumentException($"Invalid directory: {pathToSimsExe}"); + } + + var ddrawSettingsPath = Path.Combine(dir, "DDrawCompat.ini"); + return File.Exists(ddrawSettingsPath) ? ddrawSettingsPath : string.Empty; + } + + public static void Remove(string pathToSettings) + { + File.Delete(pathToSettings); + } } diff --git a/Sims1WidescreenPatcher.Core/Services/DomCalService.cs b/Sims1WidescreenPatcher.Core/Services/DomCalService.cs new file mode 100644 index 0000000..871e04a --- /dev/null +++ b/Sims1WidescreenPatcher.Core/Services/DomCalService.cs @@ -0,0 +1,246 @@ +using System.Reflection; +using Sims.Far; +using Sims1WidescreenPatcher.Core.Models; +using Sims1WidescreenPatcher.Core.ViewModels.Sims_Iff.ResourceContent.CARR; + +namespace Sims1WidescreenPatcher.Core.Services; + +public interface IDomCalService +{ + bool IsInstalled(); + Task Install(); + void Uninstall(); + bool CanInstall(); + void IncreaseSalaries(); + void DecreaseSalaries(); + bool IsSalariesEdited(); +} + +public class DomCalService : IDomCalService +{ + private const string DomcalName = "dd_domcal.iff"; + private readonly IAppState _appState; + private readonly IFar _far; + private readonly IIffService _iffService; + + public DomCalService(IAppState appState, IFar far, IIffService iffService) + { + _appState = appState; + _far = far; + _iffService = iffService; + } + + public bool CanInstall() + { + var simsDir = Path.GetDirectoryName(_appState.SimsExePath); + if (string.IsNullOrWhiteSpace(simsDir) || !Directory.Exists(simsDir)) + { + return false; + } + var expansionSharedFolder = Path.Combine(simsDir, "ExpansionShared"); + return Directory.Exists(expansionSharedFolder); + } + + public bool IsInstalled() + { + var simsDir = Path.GetDirectoryName(_appState.SimsExePath); + if (string.IsNullOrWhiteSpace(simsDir) || !Directory.Exists(simsDir)) + { + return false; + } + var expansionSharedFolder = Path.Combine(simsDir, "ExpansionShared"); + if (!Directory.Exists(expansionSharedFolder)) + { + return false; + } + var domCalFile = Path.Combine(expansionSharedFolder, DomcalName); + return File.Exists(domCalFile); + } + + public async Task Install() + { + var simsDir = Path.GetDirectoryName(_appState.SimsExePath); + if (string.IsNullOrWhiteSpace(simsDir) || !Directory.Exists(simsDir)) + { + throw new DirectoryNotFoundException(simsDir); + } + var expansionSharedFolder = Path.Combine(simsDir, "ExpansionShared"); + if (!Directory.Exists(expansionSharedFolder)) + { + throw new DirectoryNotFoundException(expansionSharedFolder); + } + var domCalFile = Path.Combine(expansionSharedFolder, DomcalName); + if (File.Exists(domCalFile)) + { + throw new InvalidOperationException($"Duplicate file: {domCalFile}"); + } + + const string resourceStream = "Sims1WidescreenPatcher.Core.Resources.dd_domcal.iff"; + await using var stream = Assembly + .GetExecutingAssembly() + .GetManifestResourceStream(resourceStream); + if (stream is null) + { + throw new InvalidOperationException( + $"Failed to load embedded resource: {resourceStream}" + ); + } + await using var fs = File.Create(domCalFile); + await stream.CopyToAsync(fs); + } + + public void Uninstall() + { + var simsDir = Path.GetDirectoryName(_appState.SimsExePath); + if (string.IsNullOrWhiteSpace(simsDir) || !Directory.Exists(simsDir)) + { + throw new DirectoryNotFoundException(simsDir); + } + var expansionSharedFolder = Path.Combine(simsDir, "ExpansionShared"); + if (!Directory.Exists(expansionSharedFolder)) + { + throw new DirectoryNotFoundException(expansionSharedFolder); + } + var domCalFile = Path.Combine(expansionSharedFolder, DomcalName); + if (File.Exists(domCalFile)) + { + File.Delete(domCalFile); + } + else + { + throw new InvalidOperationException( + $"Tried to delete {domCalFile} but it doesn't exist." + ); + } + } + + public void IncreaseSalaries() + { + var simsDir = Path.GetDirectoryName(_appState.SimsExePath); + if (string.IsNullOrWhiteSpace(simsDir) || !Directory.Exists(simsDir)) + { + throw new InvalidOperationException($"SimsDir {simsDir} does not exist."); + } + var expansionSharedFolder = Path.Combine(simsDir, "ExpansionShared"); + if (!Directory.Exists(expansionSharedFolder)) + { + throw new InvalidOperationException($"SimsDir {expansionSharedFolder} does not exist."); + } + + var workIffFile = Path.Combine(expansionSharedFolder, "work.iff"); + if (!File.Exists(workIffFile)) + { + var expansionSharedFile = Path.Combine(expansionSharedFolder, "ExpansionShared.far"); + if (!File.Exists(expansionSharedFile)) + { + throw new FileNotFoundException(expansionSharedFile); + } + _far.PathToFar = expansionSharedFile; + _far.ParseFar(); + _far.Extract("work.iff", expansionSharedFolder); + if (!File.Exists(workIffFile)) + { + throw new FileNotFoundException(workIffFile); + } + } + + var salaryFile = Path.Combine(expansionSharedFolder, "salaries.txt"); + if (File.Exists(salaryFile)) + { + throw new InvalidOperationException($"Salary file {salaryFile} already exists."); + } + using var salaryFs = File.Create(salaryFile); + using var salarySw = new StreamWriter(salaryFs); + salarySw.WriteLine("File generated by Sims1WidescreenPatcher, do not modify or delete."); + var workIff = _iffService.Load(workIffFile); + var carrViewModels = workIff + .Resources.Where(r => r.TypeCode.Value == "CARR") + .Select(c => c.Content) + .Cast(); + foreach (var cvm in carrViewModels) + { + foreach (var jivm in cvm.JobInfos) + { + salarySw.WriteLine( + $"{cvm.CareerInfo.CareerName}:{jivm.JobName}={jivm.Salary.Value}" + ); + jivm.Salary.Value = (int)Math.Round(jivm.Salary.Value * 1.3); + } + } + _iffService.Write(workIffFile, workIff); + } + + public void DecreaseSalaries() + { + var simsDir = Path.GetDirectoryName(_appState.SimsExePath); + if (string.IsNullOrWhiteSpace(simsDir) || !Directory.Exists(simsDir)) + { + throw new InvalidOperationException($"SimsDir {simsDir} does not exist."); + } + + var expansionSharedFolder = Path.Combine(simsDir, "ExpansionShared"); + if (!Directory.Exists(expansionSharedFolder)) + { + throw new InvalidOperationException($"SimsDir {expansionSharedFolder} does not exist."); + } + + var workIffFile = Path.Combine(expansionSharedFolder, "work.iff"); + if (!File.Exists(workIffFile)) + { + throw new FileNotFoundException(workIffFile); + } + var salaryFile = Path.Combine(expansionSharedFolder, "salaries.txt"); + if (!File.Exists(salaryFile)) + { + throw new FileNotFoundException(salaryFile); + } + + var originalSalaries = File.ReadAllLines(salaryFile)[1..] + .Select(l => l.Split('=')) + .GroupBy(x => x[0]) + .ToDictionary(l => l.Key, l => int.Parse(l.First()[1])); + var workIff = _iffService.Load(workIffFile); + var carrViewModels = workIff + .Resources.Where(r => r.TypeCode.Value == "CARR") + .Select(c => c.Content) + .Cast(); + foreach (var cvm in carrViewModels) + { + foreach (var jivm in cvm.JobInfos) + { + jivm.Salary.Value = originalSalaries[$"{cvm.CareerInfo.CareerName}:{jivm.JobName}"]; + } + } + _iffService.Write(workIffFile, workIff); + File.Delete(salaryFile); + } + + public bool IsSalariesEdited() + { + var simsDir = Path.GetDirectoryName(_appState.SimsExePath); + if (string.IsNullOrWhiteSpace(simsDir) || !Directory.Exists(simsDir)) + { + return false; + } + + var expansionSharedFolder = Path.Combine(simsDir, "ExpansionShared"); + if (!Directory.Exists(expansionSharedFolder)) + { + return false; + } + + var workIffFile = Path.Combine(expansionSharedFolder, "work.iff"); + if (!File.Exists(workIffFile)) + { + return false; + } + + var salaryFile = Path.Combine(expansionSharedFolder, "salaries.txt"); + if (!File.Exists(salaryFile)) + { + return false; + } + + return true; + } +} diff --git a/Sims1WidescreenPatcher.Core/Services/IffService.cs b/Sims1WidescreenPatcher.Core/Services/IffService.cs new file mode 100644 index 0000000..cb00f25 --- /dev/null +++ b/Sims1WidescreenPatcher.Core/Services/IffService.cs @@ -0,0 +1,32 @@ +using System.Runtime.Serialization; +using sims_iff.Models; +using Sims1WidescreenPatcher.Core.ViewModels.Sims_Iff; +using Sims1WidescreenPatcher.Core.ViewModels.Sims_Iff.ResourceContent.CARR; + +namespace Sims1WidescreenPatcher.Core.Services; + +public interface IIffService +{ + IffViewModel Load(string pathToIff); + void Write(string pathToIff, IffViewModel viewModel); + Task LoadAsync(string pathToIff); +} + +public class IffService : IIffService +{ + public IffViewModel Load(string pathToIff) + { + return new IffViewModel(Iff.Read(pathToIff)); + } + + public async Task LoadAsync(string pathToIff) + { + return await Task.Run(() => Load(pathToIff)); + } + + public void Write(string pathToIff, IffViewModel viewModel) + { + var iff = viewModel.MapToIff(); + iff.Write(pathToIff); + } +} diff --git a/Sims1WidescreenPatcher.Core/Services/ImagesService.cs b/Sims1WidescreenPatcher.Core/Services/ImagesService.cs index f805a3c..7b3e63f 100644 --- a/Sims1WidescreenPatcher.Core/Services/ImagesService.cs +++ b/Sims1WidescreenPatcher.Core/Services/ImagesService.cs @@ -4,6 +4,12 @@ namespace Sims1WidescreenPatcher.Core.Services; +public interface IImagesService +{ + void Install(); + void Uninstall(); +} + public class ImagesService : IImagesService { private string? _uiGraphicsPath; diff --git a/Sims1WidescreenPatcher.Core/Services/Interfaces/ICheatsService.cs b/Sims1WidescreenPatcher.Core/Services/Interfaces/ICheatsService.cs deleted file mode 100644 index 603b1de..0000000 --- a/Sims1WidescreenPatcher.Core/Services/Interfaces/ICheatsService.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Sims1WidescreenPatcher.Core.Services.Interfaces; - -public interface ICheatsService -{ - bool CheatsEnabled(); - - /// - /// Determine if the sims exe can be patched to enable all cheats - /// - /// - bool CanEnableCheats(); - - void EnableCheats(); - void DisableCheats(); -} diff --git a/Sims1WidescreenPatcher.Core/Services/Interfaces/IImagesService.cs b/Sims1WidescreenPatcher.Core/Services/Interfaces/IImagesService.cs deleted file mode 100644 index 2dadebd..0000000 --- a/Sims1WidescreenPatcher.Core/Services/Interfaces/IImagesService.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Sims1WidescreenPatcher.Core.Services.Interfaces; - -public interface IImagesService -{ - void Install(); - void Uninstall(); -} diff --git a/Sims1WidescreenPatcher.Core/Services/Interfaces/IPatchFileService.cs b/Sims1WidescreenPatcher.Core/Services/Interfaces/IPatchFileService.cs deleted file mode 100644 index 2c4cb0a..0000000 --- a/Sims1WidescreenPatcher.Core/Services/Interfaces/IPatchFileService.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Sims1WidescreenPatcher.Core.Services.Interfaces; - -public interface IPatchFileService -{ - void WriteChanges(string simsExePath, byte[] bytes); - (bool found, long offset, byte[]? bytes) FindPattern(string simsExePath, string pattern); -} diff --git a/Sims1WidescreenPatcher.Core/Services/Interfaces/IProgressService.cs b/Sims1WidescreenPatcher.Core/Services/Interfaces/IProgressService.cs deleted file mode 100644 index a353a45..0000000 --- a/Sims1WidescreenPatcher.Core/Services/Interfaces/IProgressService.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Reactive; -using Sims1WidescreenPatcher.Core.Events; - -namespace Sims1WidescreenPatcher.Core.Services.Interfaces -{ - public interface IProgressService - { - void UpdateProgress(double pct); - void UpdateProgress(double pct, string status, string status2); - void UpdateUninstall(); - IObservable Uninstall { get; } - IObservable NewProgressObservable { get; } - } -} diff --git a/Sims1WidescreenPatcher.Core/Services/Interfaces/IResolutionPatchService.cs b/Sims1WidescreenPatcher.Core/Services/Interfaces/IResolutionPatchService.cs deleted file mode 100644 index 958b306..0000000 --- a/Sims1WidescreenPatcher.Core/Services/Interfaces/IResolutionPatchService.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Sims1WidescreenPatcher.Core.Services.Interfaces; - -public interface IResolutionPatchService -{ - bool CanPatchResolution(); - bool BackupExists(); - void CreateBackup(); - void EditSimsExe(); -} diff --git a/Sims1WidescreenPatcher.Core/Services/Interfaces/IUninstallService.cs b/Sims1WidescreenPatcher.Core/Services/Interfaces/IUninstallService.cs deleted file mode 100644 index e2b23e3..0000000 --- a/Sims1WidescreenPatcher.Core/Services/Interfaces/IUninstallService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Sims1WidescreenPatcher.Core.Services.Interfaces; - -public interface IUninstallService -{ - void Uninstall(); -} diff --git a/Sims1WidescreenPatcher.Core/Services/Interfaces/IWrapperService.cs b/Sims1WidescreenPatcher.Core/Services/Interfaces/IWrapperService.cs deleted file mode 100644 index 6faa7da..0000000 --- a/Sims1WidescreenPatcher.Core/Services/Interfaces/IWrapperService.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Sims1WidescreenPatcher.Core.Models; - -namespace Sims1WidescreenPatcher.Core.Services; - -public interface IWrapperService -{ - List GetWrappers(); - Task Install(IWrapper wrapper); - void Uninstall(); -} diff --git a/Sims1WidescreenPatcher.Core/Services/PatchFileService.cs b/Sims1WidescreenPatcher.Core/Services/PatchFileService.cs index ea68122..f25e811 100644 --- a/Sims1WidescreenPatcher.Core/Services/PatchFileService.cs +++ b/Sims1WidescreenPatcher.Core/Services/PatchFileService.cs @@ -3,6 +3,12 @@ namespace Sims1WidescreenPatcher.Core.Services; +public interface IPatchFileService +{ + void WriteChanges(string simsExePath, byte[] bytes); + (bool found, long offset, byte[]? bytes) FindPattern(string simsExePath, string pattern); +} + public class PatchFileService : IPatchFileService { public void WriteChanges(string simsExePath, byte[] bytes) diff --git a/Sims1WidescreenPatcher.Core/Services/ProgressService.cs b/Sims1WidescreenPatcher.Core/Services/ProgressService.cs index fae1bd3..4c8a88f 100644 --- a/Sims1WidescreenPatcher.Core/Services/ProgressService.cs +++ b/Sims1WidescreenPatcher.Core/Services/ProgressService.cs @@ -6,6 +6,15 @@ namespace Sims1WidescreenPatcher.Core.Services; +public interface IProgressService +{ + void UpdateProgress(double pct); + void UpdateProgress(double pct, string status, string status2); + void UpdateUninstall(); + IObservable Uninstall { get; } + IObservable NewProgressObservable { get; } +} + public class ProgressService : IProgressService { private readonly Subject _uninstallSubject = new(); diff --git a/Sims1WidescreenPatcher.Core/Services/RemoveDDrawCompatSettingsService.cs b/Sims1WidescreenPatcher.Core/Services/RemoveDDrawCompatSettingsService.cs deleted file mode 100644 index 2d32584..0000000 --- a/Sims1WidescreenPatcher.Core/Services/RemoveDDrawCompatSettingsService.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Sims1WidescreenPatcher.Core.Services; - -public static class RemoveDDrawCompatSettingsService -{ - public static void Remove(string pathToSettings) - { - File.Delete(pathToSettings); - } -} diff --git a/Sims1WidescreenPatcher.Core/Services/ResolutionPatchService.cs b/Sims1WidescreenPatcher.Core/Services/ResolutionPatchService.cs index f1a4d73..18b6049 100644 --- a/Sims1WidescreenPatcher.Core/Services/ResolutionPatchService.cs +++ b/Sims1WidescreenPatcher.Core/Services/ResolutionPatchService.cs @@ -3,6 +3,14 @@ namespace Sims1WidescreenPatcher.Core.Services; +public interface IResolutionPatchService +{ + bool CanPatchResolution(); + bool BackupExists(); + void CreateBackup(); + void EditSimsExe(); +} + public class ResolutionPatchService : IResolutionPatchService { private readonly IAppState _appState; diff --git a/Sims1WidescreenPatcher.Core/Services/UninstallService.cs b/Sims1WidescreenPatcher.Core/Services/UninstallService.cs index da06856..cc46032 100644 --- a/Sims1WidescreenPatcher.Core/Services/UninstallService.cs +++ b/Sims1WidescreenPatcher.Core/Services/UninstallService.cs @@ -3,6 +3,11 @@ namespace Sims1WidescreenPatcher.Core.Services; +public interface IUninstallService +{ + void Uninstall(); +} + public class UninstallService : IUninstallService { private readonly IAppState _appState; diff --git a/Sims1WidescreenPatcher.Core/Services/WrapperService.cs b/Sims1WidescreenPatcher.Core/Services/WrapperService.cs index 14aef0a..375d811 100644 --- a/Sims1WidescreenPatcher.Core/Services/WrapperService.cs +++ b/Sims1WidescreenPatcher.Core/Services/WrapperService.cs @@ -4,6 +4,13 @@ namespace Sims1WidescreenPatcher.Core.Services; +public interface IWrapperService +{ + List GetWrappers(); + Task Install(IWrapper wrapper); + void Uninstall(); +} + public class WrapperService : IWrapperService { private readonly IAppState _appState; @@ -13,10 +20,16 @@ public WrapperService(IAppState appState) _appState = appState; } - private static string[] _ddrawCompat054Resources = { @"DDrawCompat._0._5._4.ddraw.dll" }; - private static string[] _ddrawCompat032Resources = { @"DDrawCompat._0._3._2.ddraw.dll" }; + private static readonly string[] DdrawCompat054Resources = + { + @"DDrawCompat._0._5._4.ddraw.dll", + }; + private static readonly string[] DdrawCompat032Resources = + { + @"DDrawCompat._0._3._2.ddraw.dll", + }; - private static string[] _dgvoodooResources = + private static readonly string[] DgvoodooResources = { @"DgVoodoo2.D3D8.dll", @"DgVoodoo2.D3DImm.dll", @@ -66,10 +79,10 @@ public async Task Install(IWrapper wrapper) { case DDrawCompatWrapper w: resources = - w.Version == "0.5.4" ? _ddrawCompat054Resources : _ddrawCompat032Resources; + w.Version == "0.5.4" ? DdrawCompat054Resources : DdrawCompat032Resources; break; case DgVoodoo2Wrapper: - resources = _dgvoodooResources; + resources = DgvoodooResources; break; case NoneWrapper: break; @@ -95,9 +108,9 @@ public void Uninstall() { var simsInstallDir = GetSimsInstallDirectory(); foreach ( - var item in _ddrawCompat054Resources - .Concat(_ddrawCompat032Resources) - .Concat(_dgvoodooResources) + var item in DdrawCompat054Resources + .Concat(DdrawCompat032Resources) + .Concat(DgvoodooResources) ) { var itemSplit = item.Split('.'); diff --git a/Sims1WidescreenPatcher.Core/Sims1WidescreenPatcher.Core.csproj b/Sims1WidescreenPatcher.Core/Sims1WidescreenPatcher.Core.csproj index 1aa9977..42af044 100644 --- a/Sims1WidescreenPatcher.Core/Sims1WidescreenPatcher.Core.csproj +++ b/Sims1WidescreenPatcher.Core/Sims1WidescreenPatcher.Core.csproj @@ -25,7 +25,9 @@ + + @@ -36,6 +38,8 @@ + + diff --git a/Sims1WidescreenPatcher.Core/Tabs/ExtrasTabViewModel.cs b/Sims1WidescreenPatcher.Core/Tabs/ExtrasTabViewModel.cs index a08db34..408dd25 100644 --- a/Sims1WidescreenPatcher.Core/Tabs/ExtrasTabViewModel.cs +++ b/Sims1WidescreenPatcher.Core/Tabs/ExtrasTabViewModel.cs @@ -1,17 +1,33 @@ using System.Reactive; using System.Reactive.Linq; using ReactiveUI; +using sims_iff.Models; using Sims1WidescreenPatcher.Core.Factories; using Sims1WidescreenPatcher.Core.Models; -using Sims1WidescreenPatcher.Core.Services.Interfaces; +using Sims1WidescreenPatcher.Core.Services; using Sims1WidescreenPatcher.Core.ViewModels; namespace Sims1WidescreenPatcher.Core.Tabs; +public interface IExtrasTabViewModel +{ + ReactiveCommand ApplyCommand { get; } + CheckboxViewModel UnlockCheatsViewModel { get; } + bool ApplyBtnVisible { get; } + ReactiveCommand ShowCareerEditorDialogCmd { get; set; } + IInteraction ShowCareerEditorDialogInteraction { get; set; } +} + public class ExtrasTabViewModel : ViewModelBase, IExtrasTabViewModel { + private const string UnlockCheatsKey = "UnlockCheats"; + private const string DomCalKey = "DomCal"; + private const string DomCalSalaryKey = "DomCalSalary"; private readonly ICheatsService _cheatsService; + private readonly ICareerEditorViewModelFactory _careerEditorViewModelFactory; + private readonly IDomCalService _domCalService; private readonly ObservableAsPropertyHelper _applyBtnVisible; + private CheckboxSelectionSnapshot _previousSnapshot; private IAppState AppState { get; } @@ -19,28 +35,59 @@ public ExtrasTabViewModel( CheckboxViewModelFactory creator, ICheatsService cheatsService, IAppState appState, - IProgressService progressService + IProgressService progressService, + ICareerEditorViewModelFactory careerEditorViewModelFactory, + IDomCalService domCalService ) { _cheatsService = cheatsService; + _careerEditorViewModelFactory = careerEditorViewModelFactory; + _domCalService = domCalService; AppState = appState; UnlockCheatsViewModel = (CheckboxViewModel)creator.Create("Unlock Cheats"); UnlockCheatsViewModel.ToolTipText = "Unlock cheats that were disabled in the release build of the game."; UnlockCheatsViewModel.Checked = _cheatsService.CheatsEnabled(); - _previousSnapshot = new CheckboxSelectionSnapshot(UnlockCheatsViewModel.Checked); + + DomcalCheckboxViewModel = (CheckboxViewModel)creator.Create("Add Weekends"); + DomcalCheckboxViewModel.ToolTipText = + "DomCal mod by Damon\nNo work and school on the weekends.\nThis adds a hacked calendar that you can purchase that removes the need to work every 6th and 7th day."; + DomcalCheckboxViewModel.Checked = domCalService.IsInstalled(); + + DomcalAdjustCareerSalariesViewModel = (CheckboxViewModel)creator.Create("Adjust Salaries"); + DomcalAdjustCareerSalariesViewModel.ToolTipText = + "This increases the salaries of jobs by about 30% when using the DomCal mod to even out the days lost from not working."; + DomcalAdjustCareerSalariesViewModel.Checked = _domCalService.IsSalariesEdited(); + + _previousSnapshot = new CheckboxSelectionSnapshot( + (UnlockCheatsKey, UnlockCheatsViewModel.Checked), + (DomCalKey, DomcalCheckboxViewModel.Checked), + (DomCalSalaryKey, DomcalAdjustCareerSalariesViewModel.Checked) + ); // make apply button visible/invisible if it is different from the previous state _applyBtnVisible = this.WhenAnyValue( x => x.UnlockCheatsViewModel.Checked, + x => x.DomcalCheckboxViewModel.Checked, + x => x.DomcalAdjustCareerSalariesViewModel.Checked, x => x.PreviousSnapshot, x => x.AppState.SimsExePath, - (x, previousSnapshot, simsExePath) => - (new CheckboxSelectionSnapshot(x), previousSnapshot, simsExePath) + (unlockCheats, domcal, domCalSalary, previousSnapshot, simsExePath) => + ( + new CheckboxSelectionSnapshot( + (UnlockCheatsKey, unlockCheats), + (DomCalKey, domcal), + (DomCalSalaryKey, domCalSalary) + ), + previousSnapshot, + simsExePath + ) ) .Select(x => { if (!AppState.SimsExePathExists) + { return false; + } return !x.Item1.Equals(x.previousSnapshot); }) .ToProperty(this, x => x.ApplyBtnVisible); @@ -49,29 +96,92 @@ IProgressService progressService .Select(_ => AppState.SimsExePathExists && _cheatsService.CanEnableCheats()) .Subscribe(x => UnlockCheatsViewModel.IsEnabled = x); + this.WhenAnyValue(x => x.AppState.SimsExePath) + .Select(_ => AppState.SimsExePathExists && domCalService.CanInstall()) + .Subscribe(x => DomcalCheckboxViewModel.IsEnabled = x); + + this.WhenAnyValue(x => x.DomcalCheckboxViewModel.Checked) + .Subscribe(x => DomcalAdjustCareerSalariesViewModel.IsEnabled = x); + progressService.Uninstall.Subscribe(_ => { UnlockCheatsViewModel.Checked = _cheatsService.CheatsEnabled(); - PreviousSnapshot = new CheckboxSelectionSnapshot(UnlockCheatsViewModel.Checked); + DomcalCheckboxViewModel.Checked = domCalService.IsInstalled(); + PreviousSnapshot = new CheckboxSelectionSnapshot( + (UnlockCheatsKey, UnlockCheatsViewModel.Checked), + (DomCalKey, DomcalCheckboxViewModel.Checked), + (DomCalSalaryKey, DomcalAdjustCareerSalariesViewModel.Checked) + ); }); ApplyCommand = ReactiveCommand.CreateFromTask(OnApplyClickedAsync); + + ShowCareerEditorDialogInteraction = new Interaction(); + ShowCareerEditorDialogCmd = ReactiveCommand.CreateFromTask(ShowCareerEditorDialogAsync); } + private async Task ShowCareerEditorDialogAsync() + { + var res = await ShowCareerEditorDialogInteraction.Handle( + _careerEditorViewModelFactory.Create() + ); + return Unit.Default; + } + + public ReactiveCommand ShowCareerEditorDialogCmd { get; set; } + public IInteraction< + ICareerEditorTabViewModel, + Iff? + > ShowCareerEditorDialogInteraction { get; set; } + private async Task OnApplyClickedAsync() { await Task.Run(() => { - if (UnlockCheatsViewModel.Checked) + if (UnlockCheatsViewModel.Checked != PreviousSnapshot.States[UnlockCheatsKey]) { - _cheatsService.EnableCheats(); + if (UnlockCheatsViewModel.Checked) + { + _cheatsService.EnableCheats(); + } + else + { + _cheatsService.DisableCheats(); + } } - else + + if (DomcalCheckboxViewModel.Checked != PreviousSnapshot.States[DomCalKey]) + { + if (DomcalCheckboxViewModel.Checked) + { + _domCalService.Install(); + } + else + { + _domCalService.Uninstall(); + } + } + + if ( + DomcalAdjustCareerSalariesViewModel.Checked + != PreviousSnapshot.States[DomCalSalaryKey] + ) { - _cheatsService.DisableCheats(); + if (DomcalAdjustCareerSalariesViewModel.Checked) + { + _domCalService.IncreaseSalaries(); + } + else + { + _domCalService.DecreaseSalaries(); + } } }); - PreviousSnapshot = new CheckboxSelectionSnapshot(UnlockCheatsViewModel.Checked); + PreviousSnapshot = new CheckboxSelectionSnapshot( + (UnlockCheatsKey, UnlockCheatsViewModel.Checked), + (DomCalKey, DomcalCheckboxViewModel.Checked), + (DomCalSalaryKey, DomcalAdjustCareerSalariesViewModel.Checked) + ); } private CheckboxSelectionSnapshot PreviousSnapshot @@ -83,6 +193,8 @@ private CheckboxSelectionSnapshot PreviousSnapshot public ReactiveCommand ApplyCommand { get; } public CheckboxViewModel UnlockCheatsViewModel { get; } + public CheckboxViewModel DomcalCheckboxViewModel { get; } + public CheckboxViewModel DomcalAdjustCareerSalariesViewModel { get; } public bool ApplyBtnVisible => _applyBtnVisible.Value; } diff --git a/Sims1WidescreenPatcher.Core/Tabs/IExtrasTabViewModel.cs b/Sims1WidescreenPatcher.Core/Tabs/IExtrasTabViewModel.cs deleted file mode 100644 index bb66c2e..0000000 --- a/Sims1WidescreenPatcher.Core/Tabs/IExtrasTabViewModel.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Reactive; -using ReactiveUI; -using Sims1WidescreenPatcher.Core.ViewModels; - -namespace Sims1WidescreenPatcher.Core.Tabs; - -public interface IExtrasTabViewModel -{ - ReactiveCommand ApplyCommand { get; } - CheckboxViewModel UnlockCheatsViewModel { get; } - bool ApplyBtnVisible { get; } -} diff --git a/Sims1WidescreenPatcher.Core/Tabs/IMainTabViewModel.cs b/Sims1WidescreenPatcher.Core/Tabs/IMainTabViewModel.cs deleted file mode 100644 index bd889b1..0000000 --- a/Sims1WidescreenPatcher.Core/Tabs/IMainTabViewModel.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Reactive; -using System.Windows.Input; -using Avalonia.Collections; -using Avalonia.Platform.Storage; -using ReactiveUI; -using Sims1WidescreenPatcher.Core.Models; -using Sims1WidescreenPatcher.Core.ViewModels; - -namespace Sims1WidescreenPatcher.Core.Tabs; - -public interface IMainTabViewModel -{ - ICommand PatchCommand { get; } - ICommand UninstallCommand { get; } - ICommand OpenFile { get; } - Interaction ShowOpenFileDialog { get; } - ICommand CustomResolutionCommand { get; } - Interaction ShowCustomResolutionDialog { get; } - Interaction ShowCustomYesNoDialog { get; } - Interaction ShowCustomInformationDialog { get; } - string Path { get; set; } - AspectRatio? SelectedAspectRatio { get; set; } - ReadOnlyObservableCollection AspectRatios { get; } - ReadOnlyObservableCollection FilteredResolutions { get; } - Resolution? SelectedResolution { get; set; } - AvaloniaList Wrappers { get; } - int SelectedWrapperIndex { get; set; } - double Progress { get; } - public ICheckboxViewModel ResolutionsColoredCbVm { get; } - public ICheckboxViewModel SortByAspectRatioCbVm { get; } -} diff --git a/Sims1WidescreenPatcher.Core/Tabs/MainTabViewModel.cs b/Sims1WidescreenPatcher.Core/Tabs/MainTabViewModel.cs index e42a0ed..3768887 100644 --- a/Sims1WidescreenPatcher.Core/Tabs/MainTabViewModel.cs +++ b/Sims1WidescreenPatcher.Core/Tabs/MainTabViewModel.cs @@ -1,5 +1,6 @@ using System.Collections.ObjectModel; using System.Reactive; +using System.Reactive.Disposables; using System.Reactive.Linq; using System.Windows.Input; using Avalonia.Collections; @@ -8,6 +9,7 @@ using DynamicData.Binding; using ReactiveUI; using Sims1WidescreenPatcher.Core.Enums; +using Sims1WidescreenPatcher.Core.Events; using Sims1WidescreenPatcher.Core.Factories; using Sims1WidescreenPatcher.Core.Models; using Sims1WidescreenPatcher.Core.Services; @@ -17,6 +19,28 @@ namespace Sims1WidescreenPatcher.Core.Tabs; +public interface IMainTabViewModel +{ + ReactiveCommand? PatchCommand { get; } + ReactiveCommand? UninstallCommand { get; } + ReactiveCommand? OpenFile { get; } + Interaction? ShowOpenFileDialog { get; } + ReactiveCommand? CustomResolutionCommand { get; } + Interaction? ShowCustomResolutionDialog { get; } + Interaction? ShowCustomYesNoDialog { get; } + Interaction? ShowCustomInformationDialog { get; } + string Path { get; set; } + AspectRatio? SelectedAspectRatio { get; set; } + ReadOnlyObservableCollection? AspectRatios { get; } + ReadOnlyObservableCollection? FilteredResolutions { get; } + Resolution? SelectedResolution { get; set; } + AvaloniaList? Wrappers { get; } + int SelectedWrapperIndex { get; set; } + double Progress { get; } + public ICheckboxViewModel? ResolutionsColoredCbVm { get; } + public ICheckboxViewModel? SortByAspectRatioCbVm { get; } +} + public class MainTabViewModel : ViewModelBase, IMainTabViewModel { #region Fields @@ -29,13 +53,13 @@ public class MainTabViewModel : ViewModelBase, IMainTabViewModel private AspectRatio? _selectedSelectedAspectRatio; private string _path = ""; private bool _isBusy; - private readonly ObservableAsPropertyHelper _hasBackup; - private readonly ObservableAsPropertyHelper _isValidSimsExe; - private readonly List _previouslyPatched = new() { }; - private double _progressPct; - private string? _progressStatus; - private string? _progressStatus2; - private readonly SourceList _resolutionSource = new(); + private readonly ObservableAsPropertyHelper? _hasBackup; + private readonly ObservableAsPropertyHelper? _isValidSimsExe; + private readonly List? _previouslyPatched; + private readonly ObservableAsPropertyHelper _progressPct; + private readonly ObservableAsPropertyHelper _progressStatus; + private readonly ObservableAsPropertyHelper _progressStatus2; + private readonly SourceList _resolutionSource; private readonly IProgressService _progressService; private readonly IWrapperService _wrapperService; private readonly ReadOnlyObservableCollection _filteredResolutions; @@ -43,7 +67,11 @@ public class MainTabViewModel : ViewModelBase, IMainTabViewModel private readonly IResolutionPatchService _resolutionPatchService; private readonly IUninstallService _uninstallService; private readonly IImagesService _imagesService; - private IAppState AppState { get; } + private readonly ObservableAsPropertyHelper _patchButtonToolTipTxt; + private IAppState? AppState { get; } + + private const string IncompatibleSimsExeTxt = + "Your Sims.exe is not able to be patched.\nYou need to be using a cracked/nocd Sims.exe that has not been patched to a custom resolution."; #endregion @@ -63,6 +91,8 @@ public MainTabViewModel( IWrapperService wrapperService ) { + _previouslyPatched = new List(); + _resolutionSource = new SourceList(); AppState = appState; _progressService = progressService; _wrapperService = wrapperService; @@ -76,14 +106,85 @@ IWrapperService wrapperService SortByAspectRatioCbVm.ToolTipText = "Sort the resolutions by their aspect ratio"; _customYesNoDialogViewModel = customYesNoDialogViewModel; _customResolutionDialogViewModel = customResolutionDialogViewModel; - this.WhenAnyValue(x => x.Path).Subscribe(x => AppState.SimsExePath = x); - this.WhenAnyValue(x => x.SelectedResolution).Subscribe(x => AppState.Resolution = x); + + OpenFile = ReactiveCommand.CreateFromTask(OpenFileAsync); + ShowOpenFileDialog = new Interaction(); + + SelectedWrapperIndex = 0; + SelectedAspectRatio = null; + ShowCustomResolutionDialog = + new Interaction(); + CustomResolutionCommand = ReactiveCommand.CreateFromTask(OpenCustomResolutionDialogAsync); + ShowCustomYesNoDialog = + new Interaction(); + ShowCustomInformationDialog = new Interaction(); + + ShowBadSimsExeInfoDialog = ReactiveCommand.CreateFromTask(async () => + { + await OpenCustomInformationDialogAsync("Information", IncompatibleSimsExeTxt); + }); + + var resolutionFilter = this.WhenAnyValue(x => x.SelectedAspectRatio) + .Select(CreateResolutionPredicate); + var resolutionSort = this.WhenAnyValue(x => x.SortByAspectRatioCbVm!.Checked) + .Select(x => + x + ? SortExpressionComparer + .Ascending(r => r.AspectRatio.Numerator) + .ThenByAscending(r => r.AspectRatio.Denominator) + .ThenByAscending(r => r.Width) + .ThenByAscending(r => r.Height) + : SortExpressionComparer + .Ascending(r => r.Width) + .ThenByAscending(r => r.Height) + ); + + if (_resolutionSource.Count < 1) + { + _resolutionSource.AddRange(resolutionsService.GetResolutions()); + } + + if (string.IsNullOrWhiteSpace(Path)) + { + Path = findSimsPathService.FindSimsPath(); + } + + this.WhenAnyValue(x => x.Path) + .Subscribe(x => + { + if (AppState != null) + { + AppState.SimsExePath = x; + } + }); + this.WhenAnyValue(x => x.SelectedResolution) + .Subscribe(x => + { + if (AppState != null) + { + AppState.Resolution = x; + } + }); _hasBackup = this.WhenAnyValue(x => x.Path, x => x.IsBusy) .Select(_ => _resolutionPatchService.BackupExists()) .ToProperty(this, x => x.HasBackup, deferSubscription: true); _isValidSimsExe = this.WhenAnyValue(x => x.Path) .Select(_ => _resolutionPatchService.CanPatchResolution()) .ToProperty(this, x => x.IsValidSimsExe, deferSubscription: true); + + _resolutionSource + .Connect() + .Filter(resolutionFilter) + .Sort(resolutionSort) + .Bind(out _filteredResolutions) + .Subscribe(GetNewSelectedResolution); + _resolutionSource + .Connect() + .DistinctValues(x => x.AspectRatio) + .Sort(Comparer.Default) + .Bind(out _aspectRatios) + .Subscribe(); + var canPatch = this.WhenAnyValue( x => x.IsBusy, x => x.Path, @@ -107,77 +208,83 @@ IWrapperService wrapperService .DistinctUntilChanged(); PatchCommand = ReactiveCommand.CreateFromTask(OnClickedPatch, canPatch); UninstallCommand = ReactiveCommand.CreateFromTask(OnClickedUninstall, canUninstall); - OpenFile = ReactiveCommand.CreateFromTask(OpenFileAsync); - ShowOpenFileDialog = new Interaction(); - var resolutionFilter = this.WhenAnyValue(x => x.SelectedAspectRatio) - .Select(CreateResolutionPredicate); - var resolutionSort = this.WhenAnyValue(x => x.SortByAspectRatioCbVm.Checked) - .Select(x => - x - ? SortExpressionComparer - .Ascending(r => r.AspectRatio.Numerator) - .ThenByAscending(r => r.AspectRatio.Denominator) - .ThenByAscending(r => r.Width) - .ThenByAscending(r => r.Height) - : SortExpressionComparer - .Ascending(r => r.Width) - .ThenByAscending(r => r.Height) - ); - _resolutionSource - .Connect() - .Filter(resolutionFilter) - .Sort(resolutionSort) - .Bind(out _filteredResolutions) - .Subscribe(GetNewSelectedResolution); - _resolutionSource - .Connect() - .DistinctValues(x => x.AspectRatio) - .Sort(Comparer.Default) - .Bind(out _aspectRatios) - .Subscribe(); - _resolutionSource.AddRange(resolutionsService.GetResolutions()); - SelectedResolution = FilteredResolutions.First(); - SelectedWrapperIndex = 0; - SelectedAspectRatio = null; - ShowCustomResolutionDialog = - new Interaction(); - CustomResolutionCommand = ReactiveCommand.CreateFromTask(OpenCustomResolutionDialogAsync); - ShowCustomYesNoDialog = new Interaction(); - ShowCustomInformationDialog = new Interaction(); - Path = findSimsPathService.FindSimsPath(); - - progressService.NewProgressObservable.Subscribe(x => + this.WhenAnyValue( + x => x.IsValidSimsExe, + x => x.Path, + x => x.HasBackup, + selector: (validExe, path, hasBackup) => + !validExe && !hasBackup && !string.IsNullOrWhiteSpace(path) + ) + .ObserveOn(RxApp.MainThreadScheduler) + // this is terrible. Without this, the command is invoked before the view has a chance to bind an + // interaction for ShowCustomInformationDialog, causing the app to crash + .DelaySubscription(TimeSpan.FromSeconds(1)) + .Where(x => x) + .Select(_ => Unit.Default) + .InvokeCommand(ShowBadSimsExeInfoDialog); + _patchButtonToolTipTxt = this.WhenAnyValue( + x => x.IsValidSimsExe, + x => x.Path, + x => x.HasBackup, + selector: (validExe, path, hasBackup) => + !validExe && !hasBackup && !string.IsNullOrWhiteSpace(path) + ) + .ObserveOn(RxApp.MainThreadScheduler) + .DelaySubscription(TimeSpan.FromSeconds(1)) + .Where(x => x) + .Select(_ => IncompatibleSimsExeTxt) + .ToProperty(this, x => x.PatchButtonToolTipTxt); + + _progressPct = progressService + .NewProgressObservable.ObserveOn(RxApp.MainThreadScheduler) + .Select(x => x.Progress) + .ToProperty(this, x => x.Progress); + _progressStatus = progressService + .NewProgressObservable.ObserveOn(RxApp.MainThreadScheduler) + .Select(x => x.Status) + .ToProperty(this, x => x.ProgressStatus); + _progressStatus2 = progressService + .NewProgressObservable.ObserveOn(RxApp.MainThreadScheduler) + .Select(x => x.Status2) + .ToProperty(this, x => x.ProgressStatus2); + + if (Wrappers is null || !Wrappers.Any()) { - Progress = x.Progress; - ProgressStatus = x.Status; - ProgressStatus2 = x.Status2; - }); + Wrappers = new AvaloniaList(_wrapperService.GetWrappers()); + } + + SelectedResolution ??= FilteredResolutions.First(); } #endregion #region Commands - public ICommand PatchCommand { get; } - public ICommand UninstallCommand { get; } - public ICommand OpenFile { get; } + public ReactiveCommand ShowBadSimsExeInfoDialog { get; } + public ReactiveCommand PatchCommand { get; } + public ReactiveCommand UninstallCommand { get; } + public ReactiveCommand OpenFile { get; } public Interaction ShowOpenFileDialog { get; } - public ICommand CustomResolutionCommand { get; } + public ReactiveCommand CustomResolutionCommand { get; } + public Interaction< - ICustomResolutionDialogViewModel, + ICustomResolutionDialogViewModel?, Resolution? > ShowCustomResolutionDialog { get; } public Interaction< - CustomYesNoDialogViewModel, + CustomYesNoDialogViewModel?, YesNoDialogResponse? > ShowCustomYesNoDialog { get; } + public Interaction ShowCustomInformationDialog { get; } #endregion #region Properties + public string? PatchButtonToolTipTxt => _patchButtonToolTipTxt.Value; + [RequiredAlt] [FileExists] public string Path @@ -192,8 +299,8 @@ private bool IsBusy set => this.RaiseAndSetIfChanged(ref _isBusy, value); } - private bool HasBackup => _hasBackup.Value; - private bool IsValidSimsExe => _isValidSimsExe.Value; + private bool HasBackup => _hasBackup?.Value ?? false; + private bool IsValidSimsExe => _isValidSimsExe?.Value ?? false; public AspectRatio? SelectedAspectRatio { @@ -201,9 +308,9 @@ public AspectRatio? SelectedAspectRatio set => this.RaiseAndSetIfChanged(ref _selectedSelectedAspectRatio, value); } - public ICheckboxViewModel ResolutionsColoredCbVm { get; set; } + public ICheckboxViewModel? ResolutionsColoredCbVm { get; } - public ICheckboxViewModel SortByAspectRatioCbVm { get; set; } + public ICheckboxViewModel? SortByAspectRatioCbVm { get; } public ReadOnlyObservableCollection AspectRatios => _aspectRatios; @@ -223,7 +330,7 @@ public Resolution? SelectedResolution } } - public AvaloniaList Wrappers => new(_wrapperService.GetWrappers()); + public AvaloniaList Wrappers { get; } public int SelectedWrapperIndex { @@ -231,23 +338,11 @@ public int SelectedWrapperIndex set => this.RaiseAndSetIfChanged(ref _selectedWrapperIndex, value); } - public double Progress - { - get => _progressPct; - set => this.RaiseAndSetIfChanged(ref _progressPct, value); - } + public double Progress => _progressPct.Value; - public string? ProgressStatus - { - get => _progressStatus; - set => this.RaiseAndSetIfChanged(ref _progressStatus, value); - } + public string? ProgressStatus => _progressStatus.Value; - public string? ProgressStatus2 - { - get => _progressStatus2; - set => this.RaiseAndSetIfChanged(ref _progressStatus2, value); - } + public string? ProgressStatus2 => _progressStatus2.Value; #endregion @@ -269,7 +364,10 @@ private void GetNewSelectedResolution(IChangeSet changeSet) SelectedResolution = change.Item.Current; break; case ListChangeReason.AddRange: - if (FilteredResolutions.All(x => x != SelectedResolution)) + if ( + FilteredResolutions != null + && FilteredResolutions.All(x => x != SelectedResolution) + ) { SelectedResolution = change.Range.First(); return; @@ -281,7 +379,7 @@ private void GetNewSelectedResolution(IChangeSet changeSet) case ListChangeReason.Remove: break; case ListChangeReason.RemoveRange: - SelectedResolution = FilteredResolutions.First(); + SelectedResolution = FilteredResolutions?.First(); return; case ListChangeReason.Refresh: break; @@ -380,7 +478,7 @@ await DDrawCompatSettingsService.CreateDDrawCompatSettingsFile( await Task.Run(() => _wrapperService.Install(selectedWrapper)); } - _previouslyPatched.Add(Path); + _previouslyPatched?.Add(Path); await OpenCustomInformationDialogAsync( "Progress", @@ -394,7 +492,7 @@ await OpenCustomInformationDialogAsync( private async Task OnClickedUninstall() { IsBusy = true; - var dDrawSettingsPath = CheckDDrawCompatIniService.DDrawCompatSettingsExist(Path); + var dDrawSettingsPath = DDrawCompatSettingsService.DDrawCompatSettingsExist(Path); if (!string.IsNullOrWhiteSpace(dDrawSettingsPath)) { var result = await OpenCustomYesNoDialogAsync( @@ -411,12 +509,12 @@ await DDrawCompatSettingsService.CreateDDrawCompatSettingsFile( if (result is not null && result.Result) { - RemoveDDrawCompatSettingsService.Remove(dDrawSettingsPath); + DDrawCompatSettingsService.Remove(dDrawSettingsPath); } } await Task.Run(() => _uninstallService.Uninstall()); - _previouslyPatched.Remove(Path); + _previouslyPatched?.Remove(Path); IsBusy = false; await OpenCustomInformationDialogAsync("Progress", "Uninstalled"); } diff --git a/Sims1WidescreenPatcher.Core/Validations/FileExistsAttribute.cs b/Sims1WidescreenPatcher.Core/Validations/FileExistsAttribute.cs index b33906f..e4734f5 100644 --- a/Sims1WidescreenPatcher.Core/Validations/FileExistsAttribute.cs +++ b/Sims1WidescreenPatcher.Core/Validations/FileExistsAttribute.cs @@ -13,11 +13,8 @@ public class FileExistsAttribute : ValidationAttribute return ValidationResult.Success; } - if (File.Exists(path)) - { - return ValidationResult.Success; - } - - return new ValidationResult($"Path to Sims.exe does not exist"); + return File.Exists(path) + ? ValidationResult.Success + : new ValidationResult($"Path to Sims.exe does not exist"); } } diff --git a/Sims1WidescreenPatcher.Core/Validations/RequiredAltAttribute.cs b/Sims1WidescreenPatcher.Core/Validations/RequiredAltAttribute.cs index 63da8d6..18397fc 100644 --- a/Sims1WidescreenPatcher.Core/Validations/RequiredAltAttribute.cs +++ b/Sims1WidescreenPatcher.Core/Validations/RequiredAltAttribute.cs @@ -11,12 +11,11 @@ public class RequiredAltAttribute : RequiredAttribute protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) { - if (!_visited) + if (_visited) { - _visited = true; - return ValidationResult.Success; + return base.IsValid(value, validationContext); } - - return base.IsValid(value, validationContext); + _visited = true; + return ValidationResult.Success; } } diff --git a/Sims1WidescreenPatcher.Core/ViewModels/CareerEditorDialogViewModel.cs b/Sims1WidescreenPatcher.Core/ViewModels/CareerEditorDialogViewModel.cs new file mode 100644 index 0000000..b7eb6b3 --- /dev/null +++ b/Sims1WidescreenPatcher.Core/ViewModels/CareerEditorDialogViewModel.cs @@ -0,0 +1,391 @@ +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Reactive; +using System.Reactive.Linq; +using System.Runtime.InteropServices; +using Avalonia.Platform.Storage; +using DynamicData; +using DynamicData.Binding; +using ReactiveUI; +using sims_iff.Models.ResourceContent.Str; +using Sims.Far; +using Sims1WidescreenPatcher.Core.Models; +using Sims1WidescreenPatcher.Core.Services; +using Sims1WidescreenPatcher.Core.ViewModels.Sims_Iff; +using Sims1WidescreenPatcher.Core.ViewModels.Sims_Iff.ResourceContent.CARR; + +namespace Sims1WidescreenPatcher.Core.ViewModels; + +public interface ICareerEditorTabViewModel { } + +public class CareerEditorDialogViewModel : ViewModelBase, ICareerEditorTabViewModel +{ + private const string AboutLink = + "https://github.com/FaithBeam/Sims-1-Complete-Collection-Widescreen-Patcher/wiki/Career-Editor"; + private IAppState AppState { get; } + private readonly IFar _far; + private readonly ObservableAsPropertyHelper _pathToExpansionShared; + private readonly ObservableAsPropertyHelper _pathToExpansionSharedFar; + private readonly ObservableAsPropertyHelper _pathToWorkIff; + private readonly ReadOnlyObservableCollection _careers; + private readonly ReadOnlyObservableCollection _jobs; + private ResourceViewModel? _selectedCareer; + private JobInfoViewModel? _selectedJob; + private readonly ObservableAsPropertyHelper? _workIff; + private readonly ObservableAsPropertyHelper _windowTitle; + + private readonly ObservableAsPropertyHelper _shiftLength; + private readonly ObservableAsPropertyHelper _shiftHungerDecay; + private readonly ObservableAsPropertyHelper _shiftComfortDecay; + private readonly ObservableAsPropertyHelper _shiftHygieneDecay; + private readonly ObservableAsPropertyHelper _shiftBladderDecay; + private readonly ObservableAsPropertyHelper _shiftEnergyDecay; + private readonly ObservableAsPropertyHelper _shiftFunDecay; + private readonly ObservableAsPropertyHelper _shiftSocialDecay; + + public CareerEditorDialogViewModel(IAppState appState, IFar far, IIffService iffService) + { + AppState = appState; + _far = far; + + _pathToExpansionShared = this.WhenAnyValue(x => x.AppState.SimsExePath) + .Select(GetPathToExpansionSharedDir) + .ToProperty(this, x => x.PathToExpansionShared); + _pathToExpansionSharedFar = this.WhenAnyValue(x => x.PathToExpansionShared) + .Select(x => + string.IsNullOrWhiteSpace(x) ? null : Path.Combine(x, "ExpansionShared.far") + ) + .ToProperty(this, x => x.PathToExpansionSharedFar); + var pathToWorkIffObs = this.WhenAnyValue(x => x.PathToExpansionSharedFar) + .Select(ExtractWorkIff); + + ShowOpenFileDialogInteraction = new Interaction(); + ShowOpenFileDialogCmd = ReactiveCommand.CreateFromTask( + async () => (await ShowOpenFileDialogInteraction.Handle(Unit.Default))?.Path.LocalPath + ); + + var showFileDialogObs = this.WhenAnyObservable(x => x.ShowOpenFileDialogCmd).WhereNotNull(); + var mergedPathToWorkIffChangedObs = pathToWorkIffObs.Merge(showFileDialogObs); + _pathToWorkIff = mergedPathToWorkIffChangedObs.ToProperty(this, x => x.PathToWorkIff); + var workIffObs = this.WhenAnyValue(x => x.PathToWorkIff) + .WhereNotNull() + .Select(x => + string.IsNullOrWhiteSpace(x) || !File.Exists(x) ? null : iffService.Load(x) + ) + .WhereNotNull(); + var canExecuteResetCmd = this.WhenAnyValue( + x => x.PathToWorkIff, + selector: p => !string.IsNullOrWhiteSpace(p) && File.Exists(p) + ); + ResetCmd = ReactiveCommand.Create( + () => iffService.Load(PathToWorkIff!), + canExecuteResetCmd + ); + var resetCmdObs = this.WhenAnyObservable(x => x.ResetCmd); + workIffObs = workIffObs.Merge(resetCmdObs); + _workIff = workIffObs.ToProperty(this, x => x.WorkIff); + + var canExecuteOpenWorkIffFolder = this.WhenAnyValue( + x => x.PathToWorkIff, + selector: p => !string.IsNullOrWhiteSpace(p) + ); + OpenWorkIffFolderCmd = ReactiveCommand.Create( + () => + { + var processStartInfo = new ProcessStartInfo + { + Arguments = Path.GetDirectoryName(PathToWorkIff), + }; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + processStartInfo.FileName = "explorer.exe"; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + processStartInfo.FileName = "xdg-open"; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + processStartInfo.FileName = "open"; + } + else + { + throw new Exception("Unsupported platform"); + } + + processStartInfo.UseShellExecute = true; + Process.Start(processStartInfo); + }, + canExecuteOpenWorkIffFolder + ); + + ShowSaveFileDialogInteraction = new Interaction(); + var canExecuteSaveAs = this.WhenAnyValue(x => x.WorkIff, selector: x => x is not null); + SaveAsCmd = ReactiveCommand.CreateFromTask( + async () => + { + var path = (await ShowSaveFileDialogInteraction.Handle(Unit.Default)) + ?.Path + .LocalPath; + if (!string.IsNullOrWhiteSpace(path) && WorkIff is not null) + { + iffService.Write(path, WorkIff); + } + return path; + }, + canExecuteSaveAs + ); + var saveAsCmdObs = this.WhenAnyObservable(x => x.SaveAsCmd).WhereNotNull(); + mergedPathToWorkIffChangedObs = mergedPathToWorkIffChangedObs.Merge(saveAsCmdObs); + _pathToWorkIff = mergedPathToWorkIffChangedObs.ToProperty(this, x => x.PathToWorkIff); + + SourceCache iffSourceCache = new(x => x.Id); + SourceList jobInfoSourceList = new(); + var myOp = iffSourceCache + .Connect() + .Filter(x => x.TypeCode.Value == "CARR") + .SortAndBind( + out _careers, + SortExpressionComparer.Ascending(x => + ((CarrViewModel)x.Content).CareerInfo.CareerName + ) + ) + .Subscribe(); + this.WhenAnyValue(x => x.SelectedCareer) + .Select(x => ((CarrViewModel?)x?.Content)?.JobInfos) + .Subscribe(x => + { + jobInfoSourceList.Edit(updater => + { + updater.Clear(); + if (x is not null) + { + updater.AddRange(x); + } + }); + }); + this.WhenAnyValue(x => x.WorkIff) + .Select(x => x?.Resources) + .Subscribe(x => + iffSourceCache.Edit(updater => + { + updater.Clear(); + if (x is not null) + { + updater.AddOrUpdate(x); + } + }) + ); + this.WhenAnyValue(x => x.WorkIff).Subscribe(_ => SelectedCareer = null); + var op = jobInfoSourceList.Connect().Bind(out _jobs).Subscribe(); + + var canExecuteSave = this.WhenAnyValue( + x => x.PathToWorkIff, + x => x.WorkIff, + selector: (path, workIff) => !string.IsNullOrWhiteSpace(path) && workIff is not null + ); + SaveCmd = ReactiveCommand.Create( + () => iffService.Write(PathToWorkIff!, WorkIff!), + canExecuteSave + ); + + _windowTitle = this.WhenAnyValue(x => x.PathToWorkIff) + .Select(x => $"Career Editor{(string.IsNullOrWhiteSpace(x) ? "" : $": {x}")}") + .ToProperty(this, x => x.WindowTitle); + + AboutCmd = ReactiveCommand.Create(() => OpenUrl(AboutLink)); + + _shiftLength = this.WhenAnyValue(x => x.SelectedJob) + .Select(CalculateShiftLength) + .ToProperty(this, x => x.ShiftLength); + _shiftHungerDecay = this.WhenAnyValue( + x => x.SelectedJob, + x => x.SelectedJob!.HungerDecay.Value + ) + .Select(x => CalculateShiftLength(x.Item1) * x.Item2) + .ToProperty(this, x => x.ShiftHungerDecay); + _shiftComfortDecay = this.WhenAnyValue( + x => x.SelectedJob, + x => x.SelectedJob!.ComfortDecay.Value + ) + .Select(x => CalculateShiftLength(x.Item1) * x.Item2) + .ToProperty(this, x => x.ShiftComfortDecay); + _shiftHygieneDecay = this.WhenAnyValue( + x => x.SelectedJob, + x => x.SelectedJob!.HygieneDecay.Value + ) + .Select(x => CalculateShiftLength(x.Item1) * x.Item2) + .ToProperty(this, x => x.ShiftHygieneDecay); + _shiftBladderDecay = this.WhenAnyValue( + x => x.SelectedJob, + x => x.SelectedJob!.BladderDecay.Value + ) + .Select(x => CalculateShiftLength(x.Item1) * x.Item2) + .ToProperty(this, x => x.ShiftBladderDecay); + _shiftEnergyDecay = this.WhenAnyValue( + x => x.SelectedJob, + x => x.SelectedJob!.EnergyDecay.Value + ) + .Select(x => CalculateShiftLength(x.Item1) * x.Item2) + .ToProperty(this, x => x.ShiftEnergyDecay); + _shiftFunDecay = this.WhenAnyValue(x => x.SelectedJob, x => x.SelectedJob!.FunDecay.Value) + .Select(x => CalculateShiftLength(x.Item1) * x.Item2) + .ToProperty(this, x => x.ShiftFunDecay); + _shiftSocialDecay = this.WhenAnyValue( + x => x.SelectedJob, + x => x.SelectedJob!.SocialDecay.Value + ) + .Select(x => CalculateShiftLength(x.Item1) * x.Item2) + .ToProperty(this, x => x.ShiftSocialDecay); + } + + private static int? CalculateShiftLength(JobInfoViewModel? vm) + { + if (vm is null) + { + return null; + } + var startTime = vm.StartTime.Value; + var endTime = vm.EndTime.Value; + if (startTime > endTime) + { + endTime += 24; + } + return endTime - startTime; + } + + public ReadOnlyObservableCollection Careers => _careers; + public ReadOnlyObservableCollection Jobs => _jobs; + + private string? PathToExpansionShared => _pathToExpansionShared.Value; + private string? PathToWorkIff => _pathToWorkIff.Value; + private string? PathToExpansionSharedFar => _pathToExpansionSharedFar.Value; + + private IffViewModel? WorkIff => _workIff?.Value; + public ResourceViewModel? SelectedCareer + { + get => _selectedCareer; + set => this.RaiseAndSetIfChanged(ref _selectedCareer, value); + } + + public JobInfoViewModel? SelectedJob + { + get => _selectedJob; + set => this.RaiseAndSetIfChanged(ref _selectedJob, value); + } + + public string WindowTitle => _windowTitle.Value; + + public int? ShiftLength => _shiftLength.Value; + public int? ShiftHungerDecay => _shiftHungerDecay.Value; + public int? ShiftComfortDecay => _shiftComfortDecay.Value; + public int? ShiftHygieneDecay => _shiftHygieneDecay.Value; + public int? ShiftBladderDecay => _shiftBladderDecay.Value; + public int? ShiftEnergyDecay => _shiftEnergyDecay.Value; + public int? ShiftFunDecay => _shiftFunDecay.Value; + public int? ShiftSocialDecay => _shiftSocialDecay.Value; + + public List CarTypes { get; } = + new() + { + CarType.Bentley, + CarType.Circus, + CarType.ClownCar, + CarType.Coupe, + CarType.Cruiser, + CarType.Jeep, + CarType.Junker, + CarType.Limo, + CarType.Sedan, + CarType.Suv, + CarType.TownCar, + CarType.Truck, + }; + + public ReactiveCommand SaveCmd { get; } + public IInteraction ShowSaveFileDialogInteraction { get; } + public ReactiveCommand SaveAsCmd { get; } + public ReactiveCommand AboutCmd { get; } + + public ReactiveCommand ResetCmd { get; } + public ReactiveCommand ShowOpenFileDialogCmd { get; init; } + public IInteraction ShowOpenFileDialogInteraction { get; } + public ReactiveCommand OpenWorkIffFolderCmd { get; } + + private string? ExtractWorkIff(string? pathToExpansionSharedFar) + { + if ( + string.IsNullOrWhiteSpace(pathToExpansionSharedFar) + || !File.Exists(pathToExpansionSharedFar) + ) + { + return null; + } + + var pathToExpansionShared = Path.GetDirectoryName(pathToExpansionSharedFar); + if ( + string.IsNullOrWhiteSpace(pathToExpansionShared) + || !Directory.Exists(pathToExpansionShared) + ) + { + return null; + } + + var pathToWorkIff = Path.Combine(pathToExpansionShared, "work.iff"); + if (File.Exists(pathToWorkIff)) + { + return pathToWorkIff; + } + + _far.PathToFar = PathToExpansionSharedFar; + _far.ParseFar(); + _far.Extract( + _far.Manifest.ManifestEntries.First(x => x.Filename == "work.iff"), + pathToExpansionShared + ); + return pathToWorkIff; + } + + private static string? GetPathToExpansionSharedDir(string? pathToSimsExe) + { + if (string.IsNullOrWhiteSpace(pathToSimsExe)) + { + return null; + } + var directory = Path.GetDirectoryName(pathToSimsExe); + return string.IsNullOrWhiteSpace(directory) + ? null + : Path.Combine(directory, "ExpansionShared"); + } + + // https://stackoverflow.com/a/43232486 + private void OpenUrl(string url) + { + try + { + Process.Start(url); + } + catch + { + // hack because of this: https://github.com/dotnet/corefx/issues/10361 + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + url = url.Replace("&", "^&"); + Process.Start(new ProcessStartInfo(url) { UseShellExecute = true }); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + Process.Start("xdg-open", url); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + Process.Start("open", url); + } + else + { + throw; + } + } + } +} diff --git a/Sims1WidescreenPatcher.Core/ViewModels/CheckboxViewModel.cs b/Sims1WidescreenPatcher.Core/ViewModels/CheckboxViewModel.cs index 1bfc34b..30fd1cd 100644 --- a/Sims1WidescreenPatcher.Core/ViewModels/CheckboxViewModel.cs +++ b/Sims1WidescreenPatcher.Core/ViewModels/CheckboxViewModel.cs @@ -2,6 +2,14 @@ namespace Sims1WidescreenPatcher.Core.ViewModels; +public interface ICheckboxViewModel +{ + string Label { get; set; } + string ToolTipText { get; set; } + bool Checked { get; set; } + bool IsEnabled { get; } +} + public class CheckboxViewModel : ReactiveObject, ICheckboxViewModel { private bool _checked; diff --git a/Sims1WidescreenPatcher.Core/ViewModels/CustomResolutionDialogViewModel.cs b/Sims1WidescreenPatcher.Core/ViewModels/CustomResolutionDialogViewModel.cs index e9482be..194d097 100644 --- a/Sims1WidescreenPatcher.Core/ViewModels/CustomResolutionDialogViewModel.cs +++ b/Sims1WidescreenPatcher.Core/ViewModels/CustomResolutionDialogViewModel.cs @@ -5,6 +5,14 @@ namespace Sims1WidescreenPatcher.Core.ViewModels; +public interface ICustomResolutionDialogViewModel +{ + ReactiveCommand OkCommand { get; } + string Width { get; set; } + string Height { get; set; } + AspectRatio? AspectRatio { get; } +} + public class CustomResolutionDialogViewModel : ViewModelBase, ICustomResolutionDialogViewModel { #region Fields diff --git a/Sims1WidescreenPatcher.Core/ViewModels/ICheckboxViewModel.cs b/Sims1WidescreenPatcher.Core/ViewModels/ICheckboxViewModel.cs deleted file mode 100644 index 36d9bcf..0000000 --- a/Sims1WidescreenPatcher.Core/ViewModels/ICheckboxViewModel.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Sims1WidescreenPatcher.Core.ViewModels; - -public interface ICheckboxViewModel -{ - string Label { get; set; } - string ToolTipText { get; set; } - bool Checked { get; set; } - bool IsEnabled { get; } -} diff --git a/Sims1WidescreenPatcher.Core/ViewModels/ICustomResolutionDialogViewModel.cs b/Sims1WidescreenPatcher.Core/ViewModels/ICustomResolutionDialogViewModel.cs deleted file mode 100644 index 3071d33..0000000 --- a/Sims1WidescreenPatcher.Core/ViewModels/ICustomResolutionDialogViewModel.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.ComponentModel; -using System.Reactive; -using ReactiveUI; -using Sims1WidescreenPatcher.Core.Models; - -namespace Sims1WidescreenPatcher.Core.ViewModels; - -public interface ICustomResolutionDialogViewModel -{ - ReactiveCommand OkCommand { get; } - string Width { get; set; } - string Height { get; set; } - AspectRatio? AspectRatio { get; } - IObservable> Changing { get; } - IObservable> Changed { get; } - IObservable ThrownExceptions { get; } - IDisposable SuppressChangeNotifications(); - bool AreChangeNotificationsEnabled(); - IDisposable DelayChangeNotifications(); - event PropertyChangingEventHandler? PropertyChanging; - event PropertyChangedEventHandler? PropertyChanged; -} diff --git a/Sims1WidescreenPatcher.Core/ViewModels/IMainWindowViewModel.cs b/Sims1WidescreenPatcher.Core/ViewModels/IMainWindowViewModel.cs deleted file mode 100644 index c6d12ae..0000000 --- a/Sims1WidescreenPatcher.Core/ViewModels/IMainWindowViewModel.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Sims1WidescreenPatcher.Core.Tabs; - -namespace Sims1WidescreenPatcher.Core.ViewModels; - -public interface IMainWindowViewModel -{ - IMainTabViewModel? MainTabViewModel { get; } - IExtrasTabViewModel? ExtrasTabViewModel { get; } - INotificationViewModel NotificationViewModel { get; } -} diff --git a/Sims1WidescreenPatcher.Core/ViewModels/INotificationViewModel.cs b/Sims1WidescreenPatcher.Core/ViewModels/INotificationViewModel.cs deleted file mode 100644 index 5f199e7..0000000 --- a/Sims1WidescreenPatcher.Core/ViewModels/INotificationViewModel.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Reactive; -using ReactiveUI; - -namespace Sims1WidescreenPatcher.Core.ViewModels; - -public interface INotificationViewModel -{ - ReactiveCommand WikiCommand { get; } - bool IsVisible { get; set; } - bool HasBeenShown { get; set; } - ReactiveCommand OkCommand { get; } -} diff --git a/Sims1WidescreenPatcher.Core/ViewModels/MainWindowViewModel.cs b/Sims1WidescreenPatcher.Core/ViewModels/MainWindowViewModel.cs index b5d1554..3d6f3a8 100644 --- a/Sims1WidescreenPatcher.Core/ViewModels/MainWindowViewModel.cs +++ b/Sims1WidescreenPatcher.Core/ViewModels/MainWindowViewModel.cs @@ -3,6 +3,13 @@ namespace Sims1WidescreenPatcher.Core.ViewModels; +public interface IMainWindowViewModel +{ + IMainTabViewModel? MainTabViewModel { get; } + IExtrasTabViewModel? ExtrasTabViewModel { get; } + INotificationViewModel NotificationViewModel { get; } +} + public class MainWindowViewModel : ViewModelBase, IMainWindowViewModel { public MainWindowViewModel( diff --git a/Sims1WidescreenPatcher.Core/ViewModels/NotificationViewModel.cs b/Sims1WidescreenPatcher.Core/ViewModels/NotificationViewModel.cs index f6175ed..ddcd433 100644 --- a/Sims1WidescreenPatcher.Core/ViewModels/NotificationViewModel.cs +++ b/Sims1WidescreenPatcher.Core/ViewModels/NotificationViewModel.cs @@ -5,6 +5,14 @@ namespace Sims1WidescreenPatcher.Core.ViewModels; +public interface INotificationViewModel +{ + ReactiveCommand WikiCommand { get; } + bool IsVisible { get; set; } + bool HasBeenShown { get; set; } + ReactiveCommand OkCommand { get; } +} + public class NotificationViewModel : ViewModelBase, INotificationViewModel { private bool _isVisible; diff --git a/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/IffViewModel.cs b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/IffViewModel.cs new file mode 100644 index 0000000..d424a8e --- /dev/null +++ b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/IffViewModel.cs @@ -0,0 +1,41 @@ +using sims_iff.Models; + +namespace Sims1WidescreenPatcher.Core.ViewModels.Sims_Iff; + +using System.Collections.Generic; +using ReactiveUI; + +public class IffViewModel : ReactiveObject +{ + private string _signature; + private int _offsetToResourceMap; + private List _resources; + + public string Signature + { + get => _signature; + set => this.RaiseAndSetIfChanged(ref _signature, value); + } + + public int OffsetToResourceMap + { + get => _offsetToResourceMap; + set => this.RaiseAndSetIfChanged(ref _offsetToResourceMap, value); + } + + public List Resources + { + get => _resources; + set => this.RaiseAndSetIfChanged(ref _resources, value); + } + + public IffViewModel(Iff iff) + { + _signature = iff.Signature; + _offsetToResourceMap = iff.OffsetToResourceMap; + _resources = iff.Resources.Select(x => new ResourceViewModel(x)).ToList(); + } + + public Iff MapToIff() => + new(Signature, OffsetToResourceMap, Resources.Select(x => x.MapToResource()).ToList()); +} diff --git a/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/CARR/CARRViewModel.cs b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/CARR/CARRViewModel.cs new file mode 100644 index 0000000..f0df5b3 --- /dev/null +++ b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/CARR/CARRViewModel.cs @@ -0,0 +1,62 @@ +using ReactiveUI; +using sims_iff.Interfaces; +using sims_iff.Models.ResourceContent.CARR; + +namespace Sims1WidescreenPatcher.Core.ViewModels.Sims_Iff.ResourceContent.CARR; + +public class CarrViewModel : ReactiveObject, IResourceContentViewModel +{ + private int _field1; + private int _field2; + private string _rrac; + private CareerInfoViewModel _careerInfo; + private List _jobInfos; + + public int Field1 + { + get => _field1; + set => this.RaiseAndSetIfChanged(ref _field1, value); + } + + public int Field2 + { + get => _field2; + set => this.RaiseAndSetIfChanged(ref _field2, value); + } + + public string Rrac + { + get => _rrac; + set => this.RaiseAndSetIfChanged(ref _rrac, value); + } + + public CareerInfoViewModel CareerInfo + { + get => _careerInfo; + set => this.RaiseAndSetIfChanged(ref _careerInfo, value); + } + + public List JobInfos + { + get => _jobInfos; + set => this.RaiseAndSetIfChanged(ref _jobInfos, value); + } + + public CarrViewModel(Carr carr) + { + _field1 = carr.Field1; + _field2 = carr.Field2; + _rrac = carr.Rrac; + _careerInfo = new CareerInfoViewModel(carr.CareerInfo); + _jobInfos = carr.JobInfos.Select(x => new JobInfoViewModel(x)).ToList(); + } + + public IResourceContent MapToResourceContent() => + new Carr( + Field1, + Field2, + Rrac, + CareerInfo.MapToCareerInfo(), + JobInfos.Select(x => x.MapToJobInfo()).ToList() + ); +} diff --git a/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/CARR/CareerInfoViewModel.cs b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/CARR/CareerInfoViewModel.cs new file mode 100644 index 0000000..52e0af4 --- /dev/null +++ b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/CARR/CareerInfoViewModel.cs @@ -0,0 +1,40 @@ +using sims_iff.Models.ResourceContent.CARR; + +namespace Sims1WidescreenPatcher.Core.ViewModels.Sims_Iff.ResourceContent.CARR; + +using ReactiveUI; + +public class CareerInfoViewModel : ReactiveObject +{ + private byte _compressionCode; + private string _careerName; + private FieldViewModel _numberJobLevels; + + public byte CompressionCode + { + get => _compressionCode; + set => this.RaiseAndSetIfChanged(ref _compressionCode, value); + } + + public string CareerName + { + get => _careerName; + set => this.RaiseAndSetIfChanged(ref _careerName, value); + } + + public FieldViewModel NumberJobLevels + { + get => _numberJobLevels; + set => this.RaiseAndSetIfChanged(ref _numberJobLevels, value); + } + + public CareerInfoViewModel(CareerInfo careerInfo) + { + _compressionCode = careerInfo.CompressionCode; + _careerName = careerInfo.CareerName; + _numberJobLevels = new FieldViewModel(careerInfo.NumberJobLevels); + } + + public CareerInfo MapToCareerInfo() => + new(CompressionCode, CareerName, NumberJobLevels.MapToField()); +} diff --git a/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/CARR/FieldViewModel.cs b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/CARR/FieldViewModel.cs new file mode 100644 index 0000000..f919502 --- /dev/null +++ b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/CARR/FieldViewModel.cs @@ -0,0 +1,23 @@ +using sims_iff.Models.ResourceContent.CARR; + +namespace Sims1WidescreenPatcher.Core.ViewModels.Sims_Iff.ResourceContent.CARR; + +using ReactiveUI; + +public class FieldViewModel : ReactiveObject +{ + private int _value; + + public int Value + { + get => _value; + set => this.RaiseAndSetIfChanged(ref _value, value); + } + + public FieldViewModel(Field field) + { + _value = field.Value; + } + + public Field MapToField() => new(Value); +} diff --git a/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/CARR/JobInfoViewModel.cs b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/CARR/JobInfoViewModel.cs new file mode 100644 index 0000000..bb6c456 --- /dev/null +++ b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/CARR/JobInfoViewModel.cs @@ -0,0 +1,252 @@ +using sims_iff.Models.ResourceContent.CARR; +using sims_iff.Models.ResourceContent.Str; + +namespace Sims1WidescreenPatcher.Core.ViewModels.Sims_Iff.ResourceContent.CARR; + +using ReactiveUI; + +public class JobInfoViewModel : ReactiveObject +{ + private FieldViewModel _friendsRequired; + private FieldViewModel _cookingSkillRequired; + private FieldViewModel _mechanicalSkillRequired; + private FieldViewModel _charismaRequired; + private FieldViewModel _bodySkillRequired; + private FieldViewModel _logicSkillRequired; + private FieldViewModel _creativitySkillRequired; + private FieldViewModel _unknown1; + private FieldViewModel _unknown2; + private FieldViewModel _unknown3; + private FieldViewModel _hungerDecay; + private FieldViewModel _comfortDecay; + private FieldViewModel _hygieneDecay; + private FieldViewModel _bladderDecay; + private FieldViewModel _energyDecay; + private FieldViewModel _funDecay; + private FieldViewModel _socialDecay; + private FieldViewModel _salary; + private FieldViewModel _startTime; + private FieldViewModel _endTime; + private CarType _carType; + private string _jobName; + private string _maleUniformMesh; + private string _femaleUniformMesh; + private string _uniformSkin; + private string _unknown4; + + public FieldViewModel FriendsRequired + { + get => _friendsRequired; + set => this.RaiseAndSetIfChanged(ref _friendsRequired, value); + } + + public FieldViewModel CookingSkillRequired + { + get => _cookingSkillRequired; + set => this.RaiseAndSetIfChanged(ref _cookingSkillRequired, value); + } + + public FieldViewModel MechanicalSkillRequired + { + get => _mechanicalSkillRequired; + set => this.RaiseAndSetIfChanged(ref _mechanicalSkillRequired, value); + } + + public FieldViewModel CharismaRequired + { + get => _charismaRequired; + set => this.RaiseAndSetIfChanged(ref _charismaRequired, value); + } + + public FieldViewModel BodySkillRequired + { + get => _bodySkillRequired; + set => this.RaiseAndSetIfChanged(ref _bodySkillRequired, value); + } + + public FieldViewModel LogicSkillRequired + { + get => _logicSkillRequired; + set => this.RaiseAndSetIfChanged(ref _logicSkillRequired, value); + } + + public FieldViewModel CreativitySkillRequired + { + get => _creativitySkillRequired; + set => this.RaiseAndSetIfChanged(ref _creativitySkillRequired, value); + } + + public FieldViewModel Unknown1 + { + get => _unknown1; + set => this.RaiseAndSetIfChanged(ref _unknown1, value); + } + + public FieldViewModel Unknown2 + { + get => _unknown2; + set => this.RaiseAndSetIfChanged(ref _unknown2, value); + } + + public FieldViewModel Unknown3 + { + get => _unknown3; + set => this.RaiseAndSetIfChanged(ref _unknown3, value); + } + + public FieldViewModel HungerDecay + { + get => _hungerDecay; + set => this.RaiseAndSetIfChanged(ref _hungerDecay, value); + } + + public FieldViewModel ComfortDecay + { + get => _comfortDecay; + set => this.RaiseAndSetIfChanged(ref _comfortDecay, value); + } + + public FieldViewModel HygieneDecay + { + get => _hygieneDecay; + set => this.RaiseAndSetIfChanged(ref _hygieneDecay, value); + } + + public FieldViewModel BladderDecay + { + get => _bladderDecay; + set => this.RaiseAndSetIfChanged(ref _bladderDecay, value); + } + + public FieldViewModel EnergyDecay + { + get => _energyDecay; + set => this.RaiseAndSetIfChanged(ref _energyDecay, value); + } + + public FieldViewModel FunDecay + { + get => _funDecay; + set => this.RaiseAndSetIfChanged(ref _funDecay, value); + } + + public FieldViewModel SocialDecay + { + get => _socialDecay; + set => this.RaiseAndSetIfChanged(ref _socialDecay, value); + } + + public FieldViewModel Salary + { + get => _salary; + set => this.RaiseAndSetIfChanged(ref _salary, value); + } + + public FieldViewModel StartTime + { + get => _startTime; + set => this.RaiseAndSetIfChanged(ref _startTime, value); + } + + public FieldViewModel EndTime + { + get => _endTime; + set => this.RaiseAndSetIfChanged(ref _endTime, value); + } + + public CarType CarType + { + get => _carType; + set => this.RaiseAndSetIfChanged(ref _carType, value); + } + + public string JobName + { + get => _jobName; + set => this.RaiseAndSetIfChanged(ref _jobName, value); + } + + public string MaleUniformMesh + { + get => _maleUniformMesh; + set => this.RaiseAndSetIfChanged(ref _maleUniformMesh, value); + } + + public string FemaleUniformMesh + { + get => _femaleUniformMesh; + set => this.RaiseAndSetIfChanged(ref _femaleUniformMesh, value); + } + + public string UniformSkin + { + get => _uniformSkin; + set => this.RaiseAndSetIfChanged(ref _uniformSkin, value); + } + + public string Unknown4 + { + get => _unknown4; + set => this.RaiseAndSetIfChanged(ref _unknown4, value); + } + + public JobInfoViewModel(JobInfo jobInfo) + { + _friendsRequired = new FieldViewModel(jobInfo.FriendsRequired); + _cookingSkillRequired = new FieldViewModel(jobInfo.CookingSkillRequired); + _mechanicalSkillRequired = new FieldViewModel(jobInfo.MechanicalSkillRequired); + _charismaRequired = new FieldViewModel(jobInfo.CharismaRequired); + _bodySkillRequired = new FieldViewModel(jobInfo.BodySkillRequired); + _logicSkillRequired = new FieldViewModel(jobInfo.LogicSkillRequired); + _creativitySkillRequired = new FieldViewModel(jobInfo.CreativitySkillRequired); + _unknown1 = new FieldViewModel(jobInfo.Unknown1); + _unknown2 = new FieldViewModel(jobInfo.Unknown2); + _unknown3 = new FieldViewModel(jobInfo.Unknown3); + _hungerDecay = new FieldViewModel(jobInfo.HungerDecay); + _comfortDecay = new FieldViewModel(jobInfo.ComfortDecay); + _hygieneDecay = new FieldViewModel(jobInfo.HygieneDecay); + _bladderDecay = new FieldViewModel(jobInfo.BladderDecay); + _energyDecay = new FieldViewModel(jobInfo.EnergyDecay); + _funDecay = new FieldViewModel(jobInfo.FunDecay); + _socialDecay = new FieldViewModel(jobInfo.SocialDecay); + _salary = new FieldViewModel(jobInfo.Salary); + _startTime = new FieldViewModel(jobInfo.StartTime); + _endTime = new FieldViewModel(jobInfo.EndTime); + _carType = jobInfo.CarType; + _jobName = jobInfo.JobName; + _maleUniformMesh = jobInfo.MaleUniformMesh; + _femaleUniformMesh = jobInfo.FemaleUniformMesh; + _uniformSkin = jobInfo.UniformSkin; + _unknown4 = jobInfo.Unknown4; + } + + public JobInfo MapToJobInfo() => + new( + FriendsRequired.MapToField(), + CookingSkillRequired.MapToField(), + MechanicalSkillRequired.MapToField(), + CharismaRequired.MapToField(), + BodySkillRequired.MapToField(), + LogicSkillRequired.MapToField(), + CreativitySkillRequired.MapToField(), + Unknown1.MapToField(), + Unknown2.MapToField(), + Unknown3.MapToField(), + HungerDecay.MapToField(), + ComfortDecay.MapToField(), + HygieneDecay.MapToField(), + BladderDecay.MapToField(), + EnergyDecay.MapToField(), + FunDecay.MapToField(), + SocialDecay.MapToField(), + Salary.MapToField(), + StartTime.MapToField(), + EndTime.MapToField(), + CarType, + JobName, + MaleUniformMesh, + FemaleUniformMesh, + UniformSkin, + Unknown4 + ); +} diff --git a/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/IResourceContentViewModel.cs b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/IResourceContentViewModel.cs new file mode 100644 index 0000000..0cd4f1d --- /dev/null +++ b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/IResourceContentViewModel.cs @@ -0,0 +1,8 @@ +using sims_iff.Interfaces; + +namespace Sims1WidescreenPatcher.Core.ViewModels.Sims_Iff.ResourceContent; + +public interface IResourceContentViewModel +{ + public IResourceContent MapToResourceContent(); +} diff --git a/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/IStrViewModel.cs b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/IStrViewModel.cs new file mode 100644 index 0000000..8e57bf0 --- /dev/null +++ b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/IStrViewModel.cs @@ -0,0 +1,3 @@ +namespace Sims1WidescreenPatcher.Core.ViewModels.Sims_Iff.ResourceContent; + +public interface IStrViewModel : IResourceContentViewModel { } diff --git a/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/Rsmp/ListEntryViewModel.cs b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/Rsmp/ListEntryViewModel.cs new file mode 100644 index 0000000..14455c2 --- /dev/null +++ b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/Rsmp/ListEntryViewModel.cs @@ -0,0 +1,63 @@ +using sims_iff.Models.ResourceContent.Rsmp; + +namespace Sims1WidescreenPatcher.Core.ViewModels.Sims_Iff.ResourceContent.Rsmp; + +using ReactiveUI; + +public class ListEntryViewModel : ReactiveObject +{ + private int _offset; + private ushort _id; + private int _version; + private ushort? _unknownField; + private ushort _flags; + private string _name; + + public int Offset + { + get => _offset; + set => this.RaiseAndSetIfChanged(ref _offset, value); + } + + public ushort Id + { + get => _id; + set => this.RaiseAndSetIfChanged(ref _id, value); + } + + public int Version + { + get => _version; + set => this.RaiseAndSetIfChanged(ref _version, value); + } + + public ushort? UnknownField + { + get => _unknownField; + set => this.RaiseAndSetIfChanged(ref _unknownField, value); + } + + public ushort Flags + { + get => _flags; + set => this.RaiseAndSetIfChanged(ref _flags, value); + } + + public string Name + { + get => _name; + set => this.RaiseAndSetIfChanged(ref _name, value); + } + + public ListEntryViewModel(ListEntry listEntry) + { + _offset = listEntry.Offset; + _id = listEntry.Id; + _version = listEntry.Version; + _unknownField = listEntry.UnknownField; + _flags = listEntry.Flags; + _name = listEntry.Name; + } + + public ListEntry MapToListEntry() => new(Offset, Id, Version, UnknownField, Flags, Name); +} diff --git a/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/Rsmp/ResourceMapViewModel.cs b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/Rsmp/ResourceMapViewModel.cs new file mode 100644 index 0000000..dc724a2 --- /dev/null +++ b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/Rsmp/ResourceMapViewModel.cs @@ -0,0 +1,73 @@ +using sims_iff.Interfaces; +using sims_iff.Models.ResourceContent.Rsmp; + +namespace Sims1WidescreenPatcher.Core.ViewModels.Sims_Iff.ResourceContent.Rsmp; + +using System.Collections.Generic; +using ReactiveUI; + +public class ResourceMapViewModel : ReactiveObject, IResourceContentViewModel +{ + private int _field1; + private int _version; + private int _pmsr; + private int _size; + private int _numberOfTypes; + private List _typeLists; + + public int Field1 + { + get => _field1; + set => this.RaiseAndSetIfChanged(ref _field1, value); + } + + public int Version + { + get => _version; + set => this.RaiseAndSetIfChanged(ref _version, value); + } + + public int Pmsr + { + get => _pmsr; + set => this.RaiseAndSetIfChanged(ref _pmsr, value); + } + + public int Size + { + get => _size; + set => this.RaiseAndSetIfChanged(ref _size, value); + } + + public int NumberOfTypes + { + get => _numberOfTypes; + set => this.RaiseAndSetIfChanged(ref _numberOfTypes, value); + } + + public List TypeLists + { + get => _typeLists; + set => this.RaiseAndSetIfChanged(ref _typeLists, value); + } + + public ResourceMapViewModel(ResourceMap resourceMap) + { + _field1 = resourceMap.Field1; + _version = resourceMap.Version; + _pmsr = resourceMap.Pmsr; + _size = resourceMap.Size; + _numberOfTypes = resourceMap.NumberOfTypes; + _typeLists = resourceMap.TypeLists.Select(x => new TypeListViewModel(x)).ToList(); + } + + public IResourceContent MapToResourceContent() => + new ResourceMap( + Field1, + Version, + Pmsr, + Size, + NumberOfTypes, + TypeLists.Select(x => x.MapToTypeList()).ToList() + ); +} diff --git a/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/Rsmp/TypeListViewModel.cs b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/Rsmp/TypeListViewModel.cs new file mode 100644 index 0000000..c5f2291 --- /dev/null +++ b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/Rsmp/TypeListViewModel.cs @@ -0,0 +1,45 @@ +using sims_iff.Models.ResourceContent.Rsmp; + +namespace Sims1WidescreenPatcher.Core.ViewModels.Sims_Iff.ResourceContent.Rsmp; + +using System.Collections.Generic; +using ReactiveUI; + +public class TypeListViewModel : ReactiveObject +{ + private TypeCodeViewModel _typeCode; + private int _numberListEntries; + private List _listEntries; + + public TypeCodeViewModel TypeCode + { + get => _typeCode; + set => this.RaiseAndSetIfChanged(ref _typeCode, value); + } + + public int NumberListEntries + { + get => _numberListEntries; + set => this.RaiseAndSetIfChanged(ref _numberListEntries, value); + } + + public List ListEntries + { + get => _listEntries; + set => this.RaiseAndSetIfChanged(ref _listEntries, value); + } + + public TypeListViewModel(TypeList typelist) + { + _typeCode = new TypeCodeViewModel(typelist.TypeCode); + _numberListEntries = typelist.NumberListEntries; + _listEntries = typelist.ListEntries.Select(x => new ListEntryViewModel(x)).ToList(); + } + + public TypeList MapToTypeList() => + new( + TypeCode.MapToTypeCode(), + NumberListEntries, + ListEntries.Select(x => x.MapToListEntry()).ToList() + ); +} diff --git a/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/Str/CodeStringPairViewModel.cs b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/Str/CodeStringPairViewModel.cs new file mode 100644 index 0000000..76a4fe1 --- /dev/null +++ b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/Str/CodeStringPairViewModel.cs @@ -0,0 +1,32 @@ +using sims_iff.Enums; +using sims_iff.Models.ResourceContent.Str; + +namespace Sims1WidescreenPatcher.Core.ViewModels.Sims_Iff.ResourceContent.Str; + +using ReactiveUI; + +public class CodeStringPairViewModel : ReactiveObject +{ + private LanguageCode _languageCode; + private StringPairViewModel _stringPair; + + public LanguageCode LanguageCode + { + get => _languageCode; + set => this.RaiseAndSetIfChanged(ref _languageCode, value); + } + + public StringPairViewModel StringPair + { + get => _stringPair; + set => this.RaiseAndSetIfChanged(ref _stringPair, value); + } + + public CodeStringPairViewModel(CodeStringPair codeStringPair) + { + _languageCode = codeStringPair.LanguageCode; + _stringPair = new StringPairViewModel(codeStringPair.StringPair); + } + + public CodeStringPair MapToCodeStringPair() => new(LanguageCode, StringPair.MapToStringPair()); +} diff --git a/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/Str/Format/FdffViewModel.cs b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/Str/Format/FdffViewModel.cs new file mode 100644 index 0000000..71d5b1e --- /dev/null +++ b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/Str/Format/FdffViewModel.cs @@ -0,0 +1,58 @@ +using sims_iff.Enums; +using sims_iff.Interfaces; +using sims_iff.Models.ResourceContent.Str.Format; + +namespace Sims1WidescreenPatcher.Core.ViewModels.Sims_Iff.ResourceContent.Str.Format; + +using System.Collections.Generic; +using ReactiveUI; + +public class FdffViewModel : ReactiveObject, IStrViewModel +{ + private StrFormat _format; + private short _numberEntries; + private List _codeStringPairs; + private int[] _extraData; + + public StrFormat Format + { + get => _format; + set => this.RaiseAndSetIfChanged(ref _format, value); + } + + public short NumberEntries + { + get => _numberEntries; + set => this.RaiseAndSetIfChanged(ref _numberEntries, value); + } + + public List CodeStringPairs + { + get => _codeStringPairs; + set => this.RaiseAndSetIfChanged(ref _codeStringPairs, value); + } + + public int[] ExtraData + { + get => _extraData; + set => this.RaiseAndSetIfChanged(ref _extraData, value); + } + + public FdffViewModel(Fdff fdff) + { + _format = fdff.Format; + _numberEntries = fdff.NumberEntries; + _codeStringPairs = fdff + .CodeStringPairs.Select(x => new CodeStringPairViewModel(x)) + .ToList(); + _extraData = fdff.ExtraData; + } + + public IResourceContent MapToResourceContent() => + new Fdff( + Format, + NumberEntries, + CodeStringPairs.Select(x => x.MapToCodeStringPair()).ToList(), + ExtraData + ); +} diff --git a/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/Str/StringPairViewModel.cs b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/Str/StringPairViewModel.cs new file mode 100644 index 0000000..fdd2c78 --- /dev/null +++ b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/Str/StringPairViewModel.cs @@ -0,0 +1,32 @@ +using sims_iff.Models.ResourceContent.Str; + +namespace Sims1WidescreenPatcher.Core.ViewModels.Sims_Iff.ResourceContent.Str; + +using System.Reactive; +using ReactiveUI; + +public class StringPairViewModel : ReactiveObject +{ + private string? _data; + private string? _notes; + + public string? Data + { + get => _data; + set => this.RaiseAndSetIfChanged(ref _data, value); + } + + public string? Notes + { + get => _notes; + set => this.RaiseAndSetIfChanged(ref _notes, value); + } + + public StringPairViewModel(StringPair stringPair) + { + _data = stringPair.Data; + _notes = stringPair.Notes; + } + + public StringPair MapToStringPair() => new(Data, Notes); +} diff --git a/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/TypeCodeViewModel.cs b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/TypeCodeViewModel.cs new file mode 100644 index 0000000..5731e38 --- /dev/null +++ b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceContent/TypeCodeViewModel.cs @@ -0,0 +1,31 @@ +using sims_iff.Enums; + +namespace Sims1WidescreenPatcher.Core.ViewModels.Sims_Iff.ResourceContent; + +using ReactiveUI; + +public class TypeCodeViewModel : ReactiveObject +{ + private string _value; + private Endianness _endianness; + + public string Value + { + get => _value; + set => this.RaiseAndSetIfChanged(ref _value, value); + } + + public Endianness Endianness + { + get => _endianness; + set => this.RaiseAndSetIfChanged(ref _endianness, value); + } + + public TypeCodeViewModel(sims_iff.Models.ResourceContent.TypeCode typeCode) + { + _value = typeCode.Value; + _endianness = typeCode.Endianness; + } + + public sims_iff.Models.ResourceContent.TypeCode MapToTypeCode() => new(Value, Endianness); +} diff --git a/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceViewModel.cs b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceViewModel.cs new file mode 100644 index 0000000..a6ee399 --- /dev/null +++ b/Sims1WidescreenPatcher.Core/ViewModels/Sims-Iff/ResourceViewModel.cs @@ -0,0 +1,92 @@ +using sims_iff.Interfaces; +using sims_iff.Models; +using sims_iff.Models.ResourceContent.CARR; +using sims_iff.Models.ResourceContent.Rsmp; +using sims_iff.Models.ResourceContent.Str.Format; +using Sims1WidescreenPatcher.Core.ViewModels.Sims_Iff.ResourceContent; +using Sims1WidescreenPatcher.Core.ViewModels.Sims_Iff.ResourceContent.CARR; +using Sims1WidescreenPatcher.Core.ViewModels.Sims_Iff.ResourceContent.Rsmp; +using Sims1WidescreenPatcher.Core.ViewModels.Sims_Iff.ResourceContent.Str.Format; + +namespace Sims1WidescreenPatcher.Core.ViewModels.Sims_Iff; + +using ReactiveUI; + +public class ResourceViewModel : ReactiveObject +{ + private TypeCodeViewModel _typeCode; + private int _size; + private ushort _id; + private ushort _flags; + private string _name; + private IResourceContentViewModel _content; + + public TypeCodeViewModel TypeCode + { + get => _typeCode; + set => this.RaiseAndSetIfChanged(ref _typeCode, value); + } + + public int Size + { + get => _size; + set => this.RaiseAndSetIfChanged(ref _size, value); + } + + public ushort Id + { + get => _id; + set => this.RaiseAndSetIfChanged(ref _id, value); + } + + public ushort Flags + { + get => _flags; + set => this.RaiseAndSetIfChanged(ref _flags, value); + } + + public string Name + { + get => _name; + set => this.RaiseAndSetIfChanged(ref _name, value); + } + + public IResourceContentViewModel Content + { + get => _content; + set => this.RaiseAndSetIfChanged(ref _content, value); + } + + public ResourceViewModel(Resource resource) + { + _typeCode = new TypeCodeViewModel(resource.TypeCode); + _size = resource.Size; + _id = resource.Id; + _flags = resource.Flags; + _name = resource.Name; + _content = MapResourceContent(resource.Content); + } + + public Resource MapToResource() => + new Resource( + TypeCode.MapToTypeCode(), + Size, + Id, + Flags, + Name, + Content.MapToResourceContent() + ); + + private IResourceContentViewModel MapResourceContent(IResourceContent resourceContent) => + resourceContent switch + { + Carr carr => new CarrViewModel(carr), + ResourceMap rsmp => new ResourceMapViewModel(rsmp), + Fdff fdff => new FdffViewModel(fdff), + _ => throw new ArgumentOutOfRangeException( + nameof(resourceContent), + resourceContent, + null + ), + }; +} diff --git a/Sims1WidescreenPatcher.Linux/Services/ResolutionServiceWayland.cs b/Sims1WidescreenPatcher.Linux/Services/ResolutionServiceWayland.cs index 8c33036..92588a8 100644 --- a/Sims1WidescreenPatcher.Linux/Services/ResolutionServiceWayland.cs +++ b/Sims1WidescreenPatcher.Linux/Services/ResolutionServiceWayland.cs @@ -18,7 +18,7 @@ public IEnumerable GetResolutions() throw new DirectoryNotFoundException($"Could not find directory {DrmDir}"); } - HashSet resolutions = []; + HashSet resolutions = new(); var modeFiles = Directory.EnumerateFiles( DrmDir, "modes", diff --git a/Sims1WidescreenPatcher.Linux/Sims1WidescreenPatcher.Linux.csproj b/Sims1WidescreenPatcher.Linux/Sims1WidescreenPatcher.Linux.csproj index 146bca0..e609e9d 100644 --- a/Sims1WidescreenPatcher.Linux/Sims1WidescreenPatcher.Linux.csproj +++ b/Sims1WidescreenPatcher.Linux/Sims1WidescreenPatcher.Linux.csproj @@ -35,6 +35,7 @@ + diff --git a/Sims1WidescreenPatcher.MacOS/Sims1WidescreenPatcher.MacOS.csproj b/Sims1WidescreenPatcher.MacOS/Sims1WidescreenPatcher.MacOS.csproj index e557bb1..9616593 100644 --- a/Sims1WidescreenPatcher.MacOS/Sims1WidescreenPatcher.MacOS.csproj +++ b/Sims1WidescreenPatcher.MacOS/Sims1WidescreenPatcher.MacOS.csproj @@ -15,6 +15,7 @@ + diff --git a/Sims1WidescreenPatcher.UI/App.axaml b/Sims1WidescreenPatcher.UI/App.axaml index 15a8092..6d47a0d 100644 --- a/Sims1WidescreenPatcher.UI/App.axaml +++ b/Sims1WidescreenPatcher.UI/App.axaml @@ -5,5 +5,6 @@ + diff --git a/Sims1WidescreenPatcher.UI/Dialogs/CareerEditorDialog.axaml b/Sims1WidescreenPatcher.UI/Dialogs/CareerEditorDialog.axaml new file mode 100644 index 0000000..8e3f22d --- /dev/null +++ b/Sims1WidescreenPatcher.UI/Dialogs/CareerEditorDialog.axaml @@ -0,0 +1,253 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + diff --git a/Sims1WidescreenPatcher.UI/Tabs/MainTab.axaml.cs b/Sims1WidescreenPatcher.UI/Tabs/MainTab.axaml.cs index d1e377a..0a1ce55 100644 --- a/Sims1WidescreenPatcher.UI/Tabs/MainTab.axaml.cs +++ b/Sims1WidescreenPatcher.UI/Tabs/MainTab.axaml.cs @@ -1,23 +1,30 @@ using System; using System.Linq; using System.Reactive; +using System.Reactive.Disposables; using System.Threading.Tasks; +using Avalonia; using Avalonia.Controls; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Templates; +using Avalonia.Data; using Avalonia.Input; +using Avalonia.Markup.Xaml.MarkupExtensions; using Avalonia.Platform.Storage; using Avalonia.ReactiveUI; +using Avalonia.Styling; using ReactiveUI; using Sims1WidescreenPatcher.Core.Models; using Sims1WidescreenPatcher.Core.Tabs; using Sims1WidescreenPatcher.Core.ViewModels; -using Sims1WidescreenPatcher.UI.Views; +using Sims1WidescreenPatcher.UI.Converters; using CustomInformationDialog = Sims1WidescreenPatcher.UI.Dialogs.CustomInformationDialog; using CustomResolutionDialog = Sims1WidescreenPatcher.UI.Dialogs.CustomResolutionDialog; using CustomYesNoDialog = Sims1WidescreenPatcher.UI.Dialogs.CustomYesNoDialog; namespace Sims1WidescreenPatcher.UI.Tabs; -public partial class MainTab : ReactiveUserControl +public partial class MainTab : ReactiveUserControl { private TopLevel? _topLevel; private Window? _window; @@ -25,13 +32,17 @@ public partial class MainTab : ReactiveUserControl public MainTab() { InitializeComponent(); + this.WhenActivated(d => { if (ViewModel == null) + { return; + } _topLevel = TopLevel.GetTopLevel(this); _window = (Window)_topLevel!; d(ViewModel.ShowOpenFileDialog.RegisterHandler(ShowOpenFileDialogAsync)); + d( ViewModel.ShowCustomResolutionDialog.RegisterHandler( ShowCustomResolutionDialogAsync @@ -59,7 +70,7 @@ IInteractionContext interaction } private async Task ShowCustomYesNoDialogAsync( - IInteractionContext interaction + IInteractionContext interaction ) { var dialog = new CustomYesNoDialog { DataContext = interaction.Input }; @@ -71,7 +82,7 @@ private async Task ShowCustomYesNoDialogAsync( } private async Task ShowCustomResolutionDialogAsync( - IInteractionContext interaction + IInteractionContext interaction ) { var dialog = new CustomResolutionDialog { DataContext = interaction.Input }; diff --git a/Sims1WidescreenPatcher.Windows/Sims1WidescreenPatcher.Windows.csproj b/Sims1WidescreenPatcher.Windows/Sims1WidescreenPatcher.Windows.csproj index 882f764..1583f59 100644 --- a/Sims1WidescreenPatcher.Windows/Sims1WidescreenPatcher.Windows.csproj +++ b/Sims1WidescreenPatcher.Windows/Sims1WidescreenPatcher.Windows.csproj @@ -39,6 +39,7 @@ + diff --git a/Sims1WidescreenPatcher/DependencyInjection/FactoryBootstrapper.cs b/Sims1WidescreenPatcher/DependencyInjection/FactoryBootstrapper.cs index 4d57f76..c23f2db 100644 --- a/Sims1WidescreenPatcher/DependencyInjection/FactoryBootstrapper.cs +++ b/Sims1WidescreenPatcher/DependencyInjection/FactoryBootstrapper.cs @@ -7,6 +7,8 @@ public static class FactoryBootstrapper { public static void RegisterFactories(IServiceCollection services) { - services.AddScoped(); + services + .AddScoped() + .AddScoped(); } } diff --git a/Sims1WidescreenPatcher/DependencyInjection/ServicesBootstrapper.cs b/Sims1WidescreenPatcher/DependencyInjection/ServicesBootstrapper.cs index 625c7f5..d1a762b 100644 --- a/Sims1WidescreenPatcher/DependencyInjection/ServicesBootstrapper.cs +++ b/Sims1WidescreenPatcher/DependencyInjection/ServicesBootstrapper.cs @@ -6,60 +6,61 @@ using Sims1WidescreenPatcher.MacOS.Services; using Sims1WidescreenPatcher.Windows.Services; -namespace Sims1WidescreenPatcher.DependencyInjection +namespace Sims1WidescreenPatcher.DependencyInjection; + +public static class ServicesBootstrapper { - public static class ServicesBootstrapper + public static void RegisterServices(IServiceCollection services) { - public static void RegisterServices(IServiceCollection services) + RegisterCommonServices(services); + RegisterPlatformSpecificServices(services); + } + + private static void RegisterPlatformSpecificServices(IServiceCollection services) + { + if (OperatingSystem.IsWindowsVersionAtLeast(5)) { - RegisterCommonServices(services); - RegisterPlatformSpecificServices(services); + services + .AddScoped() + .AddScoped(); } - - private static void RegisterPlatformSpecificServices(IServiceCollection services) + else if (OperatingSystem.IsMacOS()) { - if (OperatingSystem.IsWindowsVersionAtLeast(5)) - { - services - .AddScoped() - .AddScoped(); - } - else if (OperatingSystem.IsMacOS()) - { - services - .AddScoped() - .AddScoped(); - } - else if (OperatingSystem.IsLinux()) + services + .AddScoped() + .AddScoped(); + } + else if (OperatingSystem.IsLinux()) + { + if (Environment.GetEnvironmentVariable("XDG_SESSION_TYPE") == "wayland") { - if (Environment.GetEnvironmentVariable("XDG_SESSION_TYPE") == "wayland") - { - services.AddScoped(); - } - else - { - services.AddScoped(); - } - - services.AddScoped(); + services.AddScoped(); } else { - throw new InvalidOperationException("Unknown platform"); + services.AddScoped(); } - } - private static void RegisterCommonServices(IServiceCollection services) + services.AddScoped(); + } + else { - services - .AddScoped() - .AddScoped() - .AddScoped() - .AddScoped() - .AddScoped() - .AddScoped() - .AddScoped() - .AddScoped(); + throw new InvalidOperationException("Unknown platform"); } } + + private static void RegisterCommonServices(IServiceCollection services) + { + services + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped(); + } } diff --git a/Sims1WidescreenPatcher/DependencyInjection/ViewModelBootstrapper.cs b/Sims1WidescreenPatcher/DependencyInjection/ViewModelBootstrapper.cs index 8387a24..2347e7d 100644 --- a/Sims1WidescreenPatcher/DependencyInjection/ViewModelBootstrapper.cs +++ b/Sims1WidescreenPatcher/DependencyInjection/ViewModelBootstrapper.cs @@ -2,19 +2,18 @@ using Sims1WidescreenPatcher.Core.Tabs; using Sims1WidescreenPatcher.Core.ViewModels; -namespace Sims1WidescreenPatcher.DependencyInjection +namespace Sims1WidescreenPatcher.DependencyInjection; + +public static class ViewModelBootstrapper { - public static class ViewModelBootstrapper + public static void RegisterViewModels(IServiceCollection services) { - public static void RegisterViewModels(IServiceCollection services) - { - services - .AddScoped() - .AddScoped() - .AddScoped() - .AddScoped() - .AddScoped() - .AddScoped(); - } + services + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped(); } } diff --git a/Sims1WidescreenPatcher/Sims1WidescreenPatcher.csproj b/Sims1WidescreenPatcher/Sims1WidescreenPatcher.csproj index 8fc6f08..ef6acb3 100644 --- a/Sims1WidescreenPatcher/Sims1WidescreenPatcher.csproj +++ b/Sims1WidescreenPatcher/Sims1WidescreenPatcher.csproj @@ -13,25 +13,25 @@ - + - - - - - - - - + + + + + + + + - - - - + + + + @@ -39,8 +39,8 @@ Sims1WidescreenPatcher com.example - 3.12.0 - 3.12.0 + 3.13.0 + 3.13.0 APPL Sims1WidescreenPatcher SimsICO.ico diff --git a/global.json b/global.json new file mode 100644 index 0000000..d6c2c37 --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "6.0.100", + "rollForward": "latestFeature" + } +} \ No newline at end of file