diff --git a/Directory.Build.props b/Directory.Build.props index 18f9fc75..b02e2e6e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,54 @@ false + Debug;Release;Debug-Windows;Debug-Linux;Debug-macOS;Release-Windows;Release-Linux;Release-macOS + AnyCPU + + + + $(DefineConstants);DEBUG;TRACE;WINDOWS + win-x64 + false + true + full + + + + $(DefineConstants);DEBUG;TRACE;LINUX + linux-x64 + false + true + full + + + + $(DefineConstants);DEBUG;TRACE;MACOS + osx-arm64 + false + true + full + + + + + $(DefineConstants);TRACE;WINDOWS + win-x64 + true + + + + $(DefineConstants);TRACE;LINUX + linux-x64 + true + + + + $(DefineConstants);TRACE;MACOS + osx-arm64 + true + + all diff --git a/Flatpak/io.github.TeamWheelWizard.WheelWizard.metainfo.xml b/Flatpak/io.github.TeamWheelWizard.WheelWizard.metainfo.xml index f9fc8280..d20b9492 100644 --- a/Flatpak/io.github.TeamWheelWizard.WheelWizard.metainfo.xml +++ b/Flatpak/io.github.TeamWheelWizard.WheelWizard.metainfo.xml @@ -31,19 +31,19 @@ Wheel Wizard's home page - https://raw.githubusercontent.com/TeamWheelWizard/.github/d8eb58433cea25d438f4d6fb79bfececd5ccbc73/images/screenshots/home_page.png + https://raw.githubusercontent.com/TeamWheelWizard/.github/261be51961d07ac217d343f4a03231b57e603866/images/screenshots/home_page.png Wheel Wizard's room details page - https://raw.githubusercontent.com/TeamWheelWizard/.github/d8eb58433cea25d438f4d6fb79bfececd5ccbc73/images/screenshots/rooms_page.png + https://raw.githubusercontent.com/TeamWheelWizard/.github/d1c065020c1a526d80eb08f53423543f0f5c3439/images/screenshots/rooms_page.png Wheel Wizard's profile page - https://raw.githubusercontent.com/TeamWheelWizard/.github/d8eb58433cea25d438f4d6fb79bfececd5ccbc73/images/screenshots/profile_page.png + https://raw.githubusercontent.com/TeamWheelWizard/.github/d1c065020c1a526d80eb08f53423543f0f5c3439/images/screenshots/profile_page.png - Wheel Wizard's mod browser - https://raw.githubusercontent.com/TeamWheelWizard/.github/d8eb58433cea25d438f4d6fb79bfececd5ccbc73/images/screenshots/mods_browser.png + Wheel Wizard's Mii management page + https://raw.githubusercontent.com/TeamWheelWizard/.github/d1c065020c1a526d80eb08f53423543f0f5c3439/images/screenshots/mii_page.png @@ -55,6 +55,11 @@ + + + + + diff --git a/README.md b/README.md index ec6b4800..c89880fc 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ Feel free to join our community on [Discord](https://discord.gg/vZ7T2wJnsq) for Wheel Wizard Logo Wheel Wizard Logo Wheel Wizard Logo + Wheel Wizard Logo + Wheel Wizard Logo

--- diff --git a/WheelWizard.Test/Features/GameBananaTests.cs b/WheelWizard.Test/Features/GameBananaTests.cs index 6b20ef20..5d59fb34 100644 --- a/WheelWizard.Test/Features/GameBananaTests.cs +++ b/WheelWizard.Test/Features/GameBananaTests.cs @@ -89,7 +89,7 @@ public async Task GetModSearchResults_WithApiError_ReturnsFailure() _apiCaller .CallApiAsync(Arg.Any>>>()) - .Returns(Fail(expectedError)); + .Returns(Fail(expectedError)); // Act var result = await _service.GetModSearchResults(searchTerm, page); @@ -128,9 +128,7 @@ public async Task GetModDetails_WithApiError_ReturnsFailure() var modId = 123; var expectedError = "API Error"; - _apiCaller - .CallApiAsync(Arg.Any>>>()) - .Returns(Fail(expectedError)); + _apiCaller.CallApiAsync(Arg.Any>>>()).Returns(Fail(expectedError)); // Act var result = await _service.GetModDetails(modId); diff --git a/WheelWizard.Test/Features/MiiDbServiceTests.cs b/WheelWizard.Test/Features/MiiDbServiceTests.cs index 3d471544..b2cbafc1 100644 --- a/WheelWizard.Test/Features/MiiDbServiceTests.cs +++ b/WheelWizard.Test/Features/MiiDbServiceTests.cs @@ -1,8 +1,8 @@ using NSubstitute.ExceptionExtensions; using Testably.Abstractions; using WheelWizard.Shared; -using WheelWizard.WiiManagement; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.MiiManagement; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; namespace WheelWizard.Test.Features { @@ -30,13 +30,13 @@ private OperationResult CreateValidMii(uint id = 1, string name = "TestMii" var height = MiiScale.Create(60); var weight = MiiScale.Create(50); var miiFacial = MiiFacialFeatures.Create(MiiFaceShape.Bread, MiiSkinColor.Brown, MiiFacialFeature.Beard, false, false); - var miiHair = MiiHair.Create(1, HairColor.Black, false); - var miiEyebrows = MiiEyebrow.Create(3, 3, EyebrowColor.Black, 3, 3, 3); - var miiEyes = MiiEye.Create(3, 3, 3, EyeColor.Black, 3, 3); - var miiNose = MiiNose.Create(NoseType.Default, 3, 3); - var miiLips = MiiLip.Create(3, LipColor.Pink, 3, 3); - var miiGlasses = MiiGlasses.Create(GlassesType.None, GlassesColor.Blue, 3, 3); - var miiFacialHair = MiiFacialHair.Create(MustacheType.None, BeardType.None, MustacheColor.Black, 3, 3); + var miiHair = MiiHair.Create(1, MiiHairColor.Black, false); + var miiEyebrows = MiiEyebrow.Create(3, 3, MiiHairColor.Black, 3, 3, 3); + var miiEyes = MiiEye.Create(3, 3, 3, MiiEyeColor.Black, 3, 3); + var miiNose = MiiNose.Create(MiiNoseType.Default, 3, 3); + var miiLips = MiiLip.Create(3, MiiLipColor.Pink, 3, 3); + var miiGlasses = MiiGlasses.Create(MiiGlassesType.None, MiiGlassesColor.Blue, 3, 3); + var miiFacialHair = MiiFacialHair.Create(MiiMustacheType.None, MiiBeardType.None, MiiHairColor.Black, 3, 3); var miiMole = MiiMole.Create(true, 3, 3, 3); var creatorName = MiiName.Create("Creator"); var miiFavoriteColor = MiiFavoriteColor.Red; @@ -57,9 +57,7 @@ private OperationResult CreateValidMii(uint id = 1, string name = "TestMii" creatorName, }; if (EveryResult.Any(r => r.IsFailure)) - { - return Fail(EveryResult.First(r => r.IsFailure).Error); - } + return EveryResult.First(r => r.IsFailure).Error!; return Ok( new Mii @@ -68,7 +66,7 @@ private OperationResult CreateValidMii(uint id = 1, string name = "TestMii" MiiId = miiId, Height = height.Value, Weight = weight.Value, - MiiFacial = miiFacial.Value, + MiiFacialFeatures = miiFacial.Value, MiiHair = miiHair.Value, MiiEyebrows = miiEyebrows.Value, MiiEyes = miiEyes.Value, @@ -131,7 +129,6 @@ public void MiiSerializer_Deserialize_ShouldFail_ForInvalidDataLength() // Assert Assert.True(result.IsFailure); - Assert.Equal(result.IsFailure, true); } [Fact] @@ -145,7 +142,6 @@ public void MiiSerializer_Deserialize_ShouldFail_ForNullData() // Assert Assert.True(result.IsFailure); - Assert.Equal(result.IsFailure, true); } [Fact] @@ -280,7 +276,7 @@ public void GetByClientId_ShouldReturnFailure_WhenDeserializationFails() // Arrange uint targetId = 666; var badBytes = new byte[MiiSerializer.MiiBlockSize]; - for (int i = 0; i < badBytes.Length; i++) + for (var i = 0; i < badBytes.Length; i++) { badBytes[i] = (byte)(i % 256); } @@ -292,7 +288,6 @@ public void GetByClientId_ShouldReturnFailure_WhenDeserializationFails() // Assert Assert.True(result.IsFailure); - Assert.Equal(result.IsFailure, true); _repositoryService.Received(1).GetRawBlockByAvatarId(targetId); } @@ -350,7 +345,7 @@ public void Update_ShouldReturnFailure_WhenRepositoryUpdateFails() // Assert Assert.True(result.IsFailure); - Assert.Equal(repoError.Error, result.Error); // Propagate the exact error + Assert.Equal(repoError, result.Error); // Propagate the exact error _repositoryService.Received(1).UpdateBlockByClientId(miiToUpdate.MiiId, Arg.Is(b => b.SequenceEqual(expectedBytes))); } @@ -385,8 +380,8 @@ public void UpdateName_ShouldReturnSuccess_WhenGetAndUpdateSucceed() { // Arrange uint targetId = 333; - string oldName = "OldName"; - string newName = "NewName"; + var oldName = "OldName"; + var newName = "NewName"; var miiResult = CreateValidMii(targetId, oldName); Assert.True(miiResult.IsSuccess, "Setup Failed: Could not create original Mii"); var originalMii = miiResult.Value; @@ -420,7 +415,7 @@ public void UpdateName_ShouldReturnFailure_WhenGetByClientIdFails_NotFound() { // Arrange uint targetId = 404; - string newName = "NewName"; + var newName = "NewName"; _repositoryService.GetRawBlockByAvatarId(targetId).Returns((byte[]?)null); // Act @@ -428,7 +423,7 @@ public void UpdateName_ShouldReturnFailure_WhenGetByClientIdFails_NotFound() // Assert Assert.True(result.IsFailure); - Assert.Equal("Mii block not found or invalid.", result.Error.Message); // Error from GetByClientId + Assert.Equal("Mii block not found", result.Error.Message); // Error from GetByClientId _repositoryService.Received(1).GetRawBlockByAvatarId(targetId); _repositoryService.DidNotReceive().UpdateBlockByClientId(Arg.Any(), Arg.Any()); } @@ -438,7 +433,7 @@ public void UpdateName_ShouldReturnFailure_WhenGetByClientIdFails_Deserializatio { // Arrange uint targetId = 666; - string newName = "NewName"; + var newName = "NewName"; var badBytes = new byte[MiiSerializer.MiiBlockSize]; // Correct size, bad content _repositoryService.GetRawBlockByAvatarId(targetId).Returns(badBytes); @@ -457,8 +452,8 @@ public void UpdateName_ShouldReturnFailure_WhenNewNameIsInvalid() { // Arrange uint targetId = 555; - string oldName = "ValidOld"; - string invalidNewName = "ThisNameIsDefinitelyTooLongForTheMii"; // > 10 chars + var oldName = "ValidOld"; + var invalidNewName = "ThisNameIsDefinitelyTooLongForTheMii"; // > 10 chars var miiResult = CreateValidMii(targetId, oldName); Assert.True(miiResult.IsSuccess, "Setup Failed: Could not create original Mii"); var originalBytes = GetSerializedBytes(miiResult.Value); @@ -470,7 +465,6 @@ public void UpdateName_ShouldReturnFailure_WhenNewNameIsInvalid() // Assert Assert.True(result.IsFailure); - Assert.Equal(result.IsFailure, true); _repositoryService.Received(1).GetRawBlockByAvatarId(targetId); _repositoryService.DidNotReceive().UpdateBlockByClientId(Arg.Any(), Arg.Any()); } @@ -480,8 +474,8 @@ public void UpdateName_ShouldReturnFailure_WhenRepositoryUpdateFails() { // Arrange uint targetId = 777; - string oldName = "Old"; - string newName = "New"; + var oldName = "Old"; + var newName = "New"; var miiResult = CreateValidMii(targetId, oldName); Assert.True(miiResult.IsSuccess, "Setup Failed: Could not create original Mii"); var originalBytes = GetSerializedBytes(miiResult.Value); @@ -497,7 +491,7 @@ public void UpdateName_ShouldReturnFailure_WhenRepositoryUpdateFails() // Assert Assert.True(result.IsFailure); - Assert.Equal(repoError.Error, result.Error); // Error from the repository update propagated + Assert.Equal(repoError, result.Error); // Error from the repository update propagated _repositoryService.Received(1).GetRawBlockByAvatarId(targetId); _repositoryService.Received(1).UpdateBlockByClientId(targetId, Arg.Any()); } @@ -507,7 +501,7 @@ public void UpdateName_ShouldHandleExceptionDuringGet() { // Arrange uint targetId = 111; - string newName = "New"; + var newName = "New"; var expectedException = new TimeoutException("Timeout contacting repository"); _repositoryService.GetRawBlockByAvatarId(targetId).Throws(expectedException); @@ -523,8 +517,8 @@ public void UpdateName_ShouldHandleExceptionDuringUpdate() { // Arrange uint targetId = 222; - string oldName = "Old"; - string newName = "New"; + var oldName = "Old"; + var newName = "New"; var miiResult = CreateValidMii(targetId, oldName); Assert.True(miiResult.IsSuccess, "Setup Failed: Could not create original Mii"); var originalBytes = GetSerializedBytes(miiResult.Value); diff --git a/WheelWizard.Test/Features/MiiSerializerTests.cs b/WheelWizard.Test/Features/MiiSerializerTests.cs index f9101e58..f41fce5c 100644 --- a/WheelWizard.Test/Features/MiiSerializerTests.cs +++ b/WheelWizard.Test/Features/MiiSerializerTests.cs @@ -1,4 +1,5 @@ using WheelWizard.WiiManagement; +using WheelWizard.WiiManagement.MiiManagement; namespace WheelWizard.Test.Features; @@ -94,7 +95,7 @@ public void RoundTrip_Serialization_ShouldBeConsistent(string base64Data) Assert.Equal(mii.Name.ToString(), miiRoundTrip.Name.ToString()); Assert.Equal(mii.Height.Value, miiRoundTrip.Height.Value); Assert.Equal(mii.Weight.Value, miiRoundTrip.Weight.Value); - Assert.Equal(mii.MiiFacial.FaceShape, miiRoundTrip.MiiFacial.FaceShape); + Assert.Equal(mii.MiiFacialFeatures.FaceShape, miiRoundTrip.MiiFacialFeatures.FaceShape); Assert.Equal(mii.MiiEyes.Type, miiRoundTrip.MiiEyes.Type); Assert.Equal(mii.MiiGlasses.Type, miiRoundTrip.MiiGlasses.Type); Assert.Equal(mii.CreatorName.ToString(), miiRoundTrip.CreatorName.ToString()); diff --git a/WheelWizard.Test/Features/WhWzDataTests.cs b/WheelWizard.Test/Features/WhWzDataTests.cs index 952749b7..f1f9366b 100644 --- a/WheelWizard.Test/Features/WhWzDataTests.cs +++ b/WheelWizard.Test/Features/WhWzDataTests.cs @@ -41,7 +41,7 @@ public async Task GetStatusAsync_ReturnsFailure_WhenApiCallFails() // Arrange var expectedError = new OperationError { Message = "API call failed" }; - _apiCaller.CallApiAsync(Arg.Any>>>()).Returns(Fail(expectedError)); + _apiCaller.CallApiAsync(Arg.Any>>>()).Returns(expectedError); // Act var result = await _service.GetStatusAsync(); @@ -59,7 +59,7 @@ public async Task LoadBadgesAsync_ReturnsSuccess_WhenApiCallSucceeds() { { "FC1", [BadgeVariant.WhWzDev, BadgeVariant.Translator] }, { "FC2", [BadgeVariant.RrDev] }, - { "FC3", [BadgeVariant.None, BadgeVariant.GoldWinner] }, + { "FC3", [BadgeVariant.None, BadgeVariant.Firestarter_GoldWinner] }, }; _apiCaller.CallApiAsync(Arg.Any>>>>()).Returns(Ok(badgeData)); @@ -77,9 +77,7 @@ public async Task LoadBadgesAsync_ReturnsFailure_WhenApiCallFails() // Arrange var expectedError = new OperationError { Message = "API call failed" }; - _apiCaller - .CallApiAsync(Arg.Any>>>>()) - .Returns(Fail>(expectedError)); + _apiCaller.CallApiAsync(Arg.Any>>>>()).Returns(expectedError); // Act var result = await _service.LoadBadgesAsync(); @@ -162,7 +160,10 @@ public async Task LoadBadgesAsync_OverwritesExistingBadges_WhenCalledMultipleTim Assert.Contains(BadgeVariant.WhWzDev, initialBadges); // Arrange - Second load with different data - var updatedBadgeData = new Dictionary { { "FC1", [BadgeVariant.Translator, BadgeVariant.GoldWinner] } }; + var updatedBadgeData = new Dictionary + { + { "FC1", [BadgeVariant.Translator, BadgeVariant.Firestarter_GoldWinner] }, + }; _apiCaller .CallApiAsync(Arg.Any>>>>()) @@ -175,7 +176,7 @@ public async Task LoadBadgesAsync_OverwritesExistingBadges_WhenCalledMultipleTim var updatedBadges = _service.GetBadges("FC1"); Assert.Equal(2, updatedBadges.Length); Assert.Contains(BadgeVariant.Translator, updatedBadges); - Assert.Contains(BadgeVariant.GoldWinner, updatedBadges); + Assert.Contains(BadgeVariant.Firestarter_GoldWinner, updatedBadges); Assert.DoesNotContain(BadgeVariant.WhWzDev, updatedBadges); } } diff --git a/WheelWizard.Test/GlobalUsings.cs b/WheelWizard.Test/GlobalUsings.cs index c728b1f1..59a26d6c 100644 --- a/WheelWizard.Test/GlobalUsings.cs +++ b/WheelWizard.Test/GlobalUsings.cs @@ -1,2 +1,3 @@ global using NSubstitute; +global using static WheelWizard.Shared.OperationError; global using static WheelWizard.Shared.OperationResult; diff --git a/WheelWizard.Test/Shared/OperationResult/OperationResultTests.cs b/WheelWizard.Test/Shared/OperationResult/OperationResultTests.cs index cc906e10..c142b698 100644 --- a/WheelWizard.Test/Shared/OperationResult/OperationResultTests.cs +++ b/WheelWizard.Test/Shared/OperationResult/OperationResultTests.cs @@ -1,4 +1,6 @@ -using WheelWizard.Shared; +using WheelWizard.Helpers; +using WheelWizard.Shared; +using WheelWizard.Shared.MessageTranslations; namespace WheelWizard.Test.Shared.OperationResultTests; @@ -52,12 +54,29 @@ public void CreateFailureResult_ShouldHaveCorrectState() var error = new OperationError { Message = "Error message" }; // Act - var operationResult = Fail(error); + OperationResult operationResult = error; // Assert Assert.Equal(error, operationResult.Error); Assert.True(operationResult.IsFailure); Assert.False(operationResult.IsSuccess); + Assert.Null(operationResult.Error!.MessageTranslation); + } + + [Fact(DisplayName = "Create failure result, should have correct state with translation")] + public void CreateFailureResult_ShouldHaveCorrectStateWithTranslation() + { + const string errorMessage = "Error message"; + const MessageTranslation errorTranslation = MessageTranslation.Error_StanderdError; + + // Act + OperationResult operationResult = Fail(errorMessage, errorTranslation); + + // Assert + Assert.True(operationResult.IsFailure); + Assert.False(operationResult.IsSuccess); + Assert.Equal(errorMessage, operationResult.Error.Message); + Assert.Equal(errorTranslation, operationResult.Error.MessageTranslation); } [Fact(DisplayName = "Implicit result from error, should have correct state")] @@ -129,7 +148,7 @@ public void CreateFailureGenericResult_ShouldHaveCorrectState() var error = new OperationError { Message = "Error message" }; // Act - var operationResult = Fail(error); + OperationResult operationResult = error; // Assert Assert.Equal(error, operationResult.Error); @@ -150,6 +169,7 @@ public void ImplicitGenericResultFromError_ShouldHaveCorrectState() Assert.Equal(error, operationResult.Error); Assert.True(operationResult.IsFailure); Assert.False(operationResult.IsSuccess); + Assert.Null(operationResult.Error!.MessageTranslation); } [Fact(DisplayName = "Implicit generic result from value, should have correct state")] @@ -168,22 +188,6 @@ public void ImplicitGenericResultFromValue_ShouldHaveCorrectState() Assert.Equal(value, operationResult.Value); } - [Fact(DisplayName = "Implicit result from string, should have failed state")] - public void ImplicitResultFromString_ShouldHaveFailedState() - { - // Arrange - const string errorMessage = "Error message"; - - // Act - OperationResult operationResult = errorMessage; - - // Assert - Assert.NotNull(operationResult.Error); - Assert.True(operationResult.IsFailure); - Assert.False(operationResult.IsSuccess); - Assert.Equal(errorMessage, operationResult.Error?.Message); - } - [Fact(DisplayName = "Implicit result from exception, should have failed state")] public void ImplicitResultFromException_ShouldHaveFailedState() { @@ -198,22 +202,7 @@ public void ImplicitResultFromException_ShouldHaveFailedState() Assert.True(operationResult.IsFailure); Assert.False(operationResult.IsSuccess); Assert.Equal(exception.Message, operationResult.Error?.Message); - } - - [Fact(DisplayName = "Implicit generic result from string, should have correct failed state")] - public void ImplicitGenericResultFromString_ShouldHaveCorrectFailedState() - { - // Arrange - const string errorMessage = "Error message"; - - // Act - OperationResult operationResult = errorMessage; - - // Assert - Assert.NotNull(operationResult.Error); - Assert.True(operationResult.IsFailure); - Assert.False(operationResult.IsSuccess); - Assert.Equal(errorMessage, operationResult.Error?.Message); + Assert.Null(operationResult.Error!.MessageTranslation); } [Fact(DisplayName = "Implicit generic result from exception, should have correct failed state")] @@ -278,6 +267,27 @@ public void TryCatchWithExceptionWithOverride_ShouldHaveFailedStateWithMessage() Assert.True(result.IsFailure); Assert.Equal(errorMessage, result.Error?.Message); Assert.Equal(exception, result.Error?.Exception); + Assert.Null(result.Error?.MessageTranslation); + } + + [Fact(DisplayName = "Try catch with exception with override, should have failed state with message and translation")] + public void TryCatchWithExceptionWithOverride_ShouldHaveFailedStateWithMessageAndTranslation() + { + // Arrange + var exception = new Exception("Error message"); + const string errorMessage = "Custom error message"; + const MessageTranslation errorTranslation = MessageTranslation.Error_StanderdError; + + // Act + void Action() => throw exception; + + var result = TryCatch(Action, errorMessage, errorTranslation); + + // Assert + Assert.True(result.IsFailure); + Assert.Equal(errorMessage, result.Error?.Message); + Assert.Equal(exception, result.Error?.Exception); + Assert.Equal(errorTranslation, result.Error?.MessageTranslation); } [Fact(DisplayName = "Generic try catch without exception, should have correct success state")] diff --git a/WheelWizard.Test/WheelWizard.Test.csproj b/WheelWizard.Test/WheelWizard.Test.csproj index 4b17a4a4..98b25d31 100644 --- a/WheelWizard.Test/WheelWizard.Test.csproj +++ b/WheelWizard.Test/WheelWizard.Test.csproj @@ -23,7 +23,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/WheelWizard.sln b/WheelWizard.sln index 258e714b..1fa0f941 100644 --- a/WheelWizard.sln +++ b/WheelWizard.sln @@ -10,13 +10,13 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {DF73A1D9-B01E-4826-A0F3-A8105E66A931}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DF73A1D9-B01E-4826-A0F3-A8105E66A931}.Debug|Any CPU.Build.0 = Debug|Any CPU {DF73A1D9-B01E-4826-A0F3-A8105E66A931}.Release|Any CPU.ActiveCfg = Release|Any CPU {DF73A1D9-B01E-4826-A0F3-A8105E66A931}.Release|Any CPU.Build.0 = Release|Any CPU - {C2A6FF37-2E6B-4683-A8DA-AD965E1CC55A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C2A6FF37-2E6B-4683-A8DA-AD965E1CC55A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF73A1D9-B01E-4826-A0F3-A8105E66A931}.Debug|Any CPU.ActiveCfg = Debug-Windows|Any CPU + {DF73A1D9-B01E-4826-A0F3-A8105E66A931}.Debug|Any CPU.Build.0 = Debug-Windows|Any CPU {C2A6FF37-2E6B-4683-A8DA-AD965E1CC55A}.Release|Any CPU.ActiveCfg = Release|Any CPU {C2A6FF37-2E6B-4683-A8DA-AD965E1CC55A}.Release|Any CPU.Build.0 = Release|Any CPU + {C2A6FF37-2E6B-4683-A8DA-AD965E1CC55A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C2A6FF37-2E6B-4683-A8DA-AD965E1CC55A}.Debug|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection EndGlobal diff --git a/WheelWizard/Features/AutoUpdating/AutoUpdaterSingletonService.cs b/WheelWizard/Features/AutoUpdating/AutoUpdaterSingletonService.cs index ce43e2fd..05b965f7 100644 --- a/WheelWizard/Features/AutoUpdating/AutoUpdaterSingletonService.cs +++ b/WheelWizard/Features/AutoUpdating/AutoUpdaterSingletonService.cs @@ -35,14 +35,14 @@ public async Task CheckForUpdatesAsync() return; var latestVersion = SemVersion.Parse(latestRelease.TagName.TrimStart('v'), SemVersionStyles.Any); - var popupExtraText = Humanizer.ReplaceDynamic(Phrases.PopupText_NewVersionWhWz, latestVersion, CurrentVersion)!; + var popupExtraText = Humanizer.ReplaceDynamic(Phrases.Question_NewVersionWhWz_Extra, latestVersion, CurrentVersion)!; var shouldUpdate = false; await Dispatcher.UIThread.InvokeAsync(async () => { shouldUpdate = await new YesNoWindow() .SetButtonText(Common.Action_Update, Common.Action_MaybeLater) - .SetMainText(Phrases.PopupText_WhWzUpdateAvailable) + .SetMainText(Phrases.Question_NewVersionWhWz_Title) .SetExtraText(popupExtraText) .AwaitAnswer(); }); diff --git a/WheelWizard/Features/AutoUpdating/Platforms/LinuxUpdatePlatform.cs b/WheelWizard/Features/AutoUpdating/Platforms/LinuxUpdatePlatform.cs index 8041cc5b..da170500 100644 --- a/WheelWizard/Features/AutoUpdating/Platforms/LinuxUpdatePlatform.cs +++ b/WheelWizard/Features/AutoUpdating/Platforms/LinuxUpdatePlatform.cs @@ -28,13 +28,13 @@ public async Task ExecuteUpdateAsync(string downloadUrl) { var currentExecutablePath = Environment.ProcessPath; if (currentExecutablePath is null) - return Phrases.PopupText_UnableUpdateWhWz_ReasonLocation; + return Fail(Phrases.MessageWarning_UnableUpdateWhWz_Extra_ReasonLocation); var currentExecutableName = fileSystem.Path.GetFileName(currentExecutablePath); var currentFolder = fileSystem.Path.GetDirectoryName(currentExecutablePath); if (currentFolder is null) - return Phrases.PopupText_UnableUpdateWhWz_ReasonLocation; + return Fail(Phrases.MessageWarning_UnableUpdateWhWz_Extra_ReasonLocation); // Download the new executable to a temporary file. var newFilePath = fileSystem.Path.Combine(currentFolder, currentExecutableName + "_new"); @@ -44,8 +44,8 @@ public async Task ExecuteUpdateAsync(string downloadUrl) await DownloadHelper.DownloadToLocationAsync( downloadUrl, newFilePath, - Phrases.PopupText_UpdateWhWz, - Phrases.PopupText_LatestWhWzGithub, + Phrases.Progress_UpdateWhWz, + Phrases.Progress_LatestWhWzGithub, ForceGivenFilePath: true ); @@ -66,7 +66,7 @@ private OperationResult CreateAndRunShellScript(string currentFilePath, string n { var currentFolder = fileSystem.Path.GetDirectoryName(currentFilePath); if (currentFolder is null) - return Phrases.PopupText_UnableUpdateWhWz_ReasonLocation; + return Fail(Phrases.MessageWarning_UnableUpdateWhWz_Extra_ReasonLocation); var scriptFilePath = fileSystem.Path.Combine(currentFolder, "update.sh"); var originalFileName = fileSystem.Path.GetFileName(currentFilePath); diff --git a/WheelWizard/Features/AutoUpdating/Platforms/WindowsUpdatePlatform.cs b/WheelWizard/Features/AutoUpdating/Platforms/WindowsUpdatePlatform.cs index 3b09b075..98a648ec 100644 --- a/WheelWizard/Features/AutoUpdating/Platforms/WindowsUpdatePlatform.cs +++ b/WheelWizard/Features/AutoUpdating/Platforms/WindowsUpdatePlatform.cs @@ -24,8 +24,8 @@ public async Task ExecuteUpdateAsync(string downloadUrl) // Otherwise, ask if the user wants to restart as admin. var restartAsAdmin = await new YesNoWindow() - .SetMainText(Phrases.PopupText_UpdateAdmin) - .SetExtraText(Phrases.PopupText_UpdateAdminExplained) + .SetMainText(Phrases.Question_UpdateAdmin_Title) + .SetExtraText(Phrases.Question_UpdateAdmin_Extra) .AwaitAnswer(); if (!restartAsAdmin) @@ -50,7 +50,7 @@ private static OperationResult RestartAsAdmin() Process.Start(startInfo); Environment.Exit(0); }, - errorMessage: Phrases.PopupText_RestartAdminFail + errorMessage: Phrases.MessageError_RestartAdminFail_Extra ); } @@ -68,13 +68,13 @@ private async Task UpdateAsync(string downloadUrl) { var currentExecutablePath = Environment.ProcessPath; if (currentExecutablePath is null) - return Phrases.PopupText_UnableUpdateWhWz_ReasonLocation; + return Fail(Phrases.MessageWarning_UnableUpdateWhWz_Extra_ReasonLocation); var currentExecutableName = fileSystem.Path.GetFileNameWithoutExtension(currentExecutablePath); var currentFolder = fileSystem.Path.GetDirectoryName(currentExecutablePath); if (currentFolder is null) - return Phrases.PopupText_UnableUpdateWhWz_ReasonLocation; + return Fail(Phrases.MessageWarning_UnableUpdateWhWz_Extra_ReasonLocation); // Download new executable to a temporary file. var newFilePath = fileSystem.Path.Combine(currentFolder, currentExecutableName + "_new.exe"); @@ -84,8 +84,8 @@ private async Task UpdateAsync(string downloadUrl) await DownloadHelper.DownloadToLocationAsync( downloadUrl, newFilePath, - Phrases.PopupText_UpdateWhWz, - Phrases.PopupText_LatestWhWzGithub, + Phrases.Progress_UpdateWhWz, + Phrases.Progress_LatestWhWzGithub, ForceGivenFilePath: true ); @@ -106,7 +106,7 @@ private OperationResult CreateAndRunPowerShellScript(string currentFilePath, str { var currentFolder = fileSystem.Path.GetDirectoryName(currentFilePath); if (currentFolder is null) - return Phrases.PopupText_UnableUpdateWhWz_ReasonLocation; + return Fail(Phrases.MessageWarning_UnableUpdateWhWz_Extra_ReasonLocation); var scriptFilePath = fileSystem.Path.Combine(currentFolder, "update.ps1"); var originalFileName = fileSystem.Path.GetFileName(currentFilePath); diff --git a/WheelWizard/Features/CustomCharacters/CustomCharactersExtensions.cs b/WheelWizard/Features/CustomCharacters/CustomCharactersExtensions.cs new file mode 100644 index 00000000..22c01050 --- /dev/null +++ b/WheelWizard/Features/CustomCharacters/CustomCharactersExtensions.cs @@ -0,0 +1,11 @@ +namespace WheelWizard.CustomCharacters; + +public static class CustomCharactersExtensions +{ + public static IServiceCollection AddCustomCharacters(this IServiceCollection services) + { + services.AddSingleton(); + + return services; + } +} diff --git a/WheelWizard/Features/CustomCharacters/CustomCharactersService.cs b/WheelWizard/Features/CustomCharacters/CustomCharactersService.cs new file mode 100644 index 00000000..1c718603 --- /dev/null +++ b/WheelWizard/Features/CustomCharacters/CustomCharactersService.cs @@ -0,0 +1,215 @@ +namespace WheelWizard.CustomCharacters; + +public interface ICustomCharactersService +{ + /// + /// Gets the custom characters. + /// + List GetCustomCharacters(); + + /// + /// To clear the given string from all the custom characters, and map them to their ascii closest representation. + /// + string NormalizeToAscii(string str); +} + +public class CustomCharactersService : ICustomCharactersService +{ + public List GetCustomCharacters() + { + var charRanges = new List<(char, char)> + { + ((char)0x2460, (char)0x246e), + ((char)0xe000, (char)0xe01c), + ((char)0xf061, (char)0xf06d), + ((char)0xf074, (char)0xf07c), + ((char)0xf107, (char)0xf10f), // it actually goes up to 0xf12f, but for some reason it repeats the same 8 icons 6 times over + }; + + var chars = new List(); + foreach (var (start, end) in charRanges) + { + for (var i = start; i <= end; i++) + { + chars.Add(i); + } + } + + // All the left-over chars that we cant make easy groups out of + chars.AddRange( + [ + (char)0xe028, + (char)0xe068, + (char)0xe067, + (char)0xe06a, + (char)0xe06b, + (char)0xf030, + (char)0xf031, + (char)0xf034, + (char)0xf035, + (char)0xf038, + (char)0xf039, + (char)0xf041, + (char)0xf043, + (char)0xf044, + (char)0xf047, + (char)0xf050, + (char)0xf058, + (char)0xf05e, + (char)0xf05f, + (char)0xf103, + ] + ); + + return chars; + } + + public string NormalizeToAscii(string str) + { + var charRanges = new List<(char, char, string[])> + { + ((char)0x2460, (char)0x246e, SArr("0123456789:,/-+")), + ( + (char)0xe000, + (char)0xe01c, + [ + "(A)", + "(B)", + "(X)", + "(Y)", + "[L]", + "[R]", + "⁜", + "◷", + ":)", + ">:(", + "<:o", + ":|", + "⁕", + "@", + "◜|◝", + "{}", + "[!]", + "[?]", + "[v]", + "[x]", + "[+]", + "\u2660", + "\u2666", + "\u2665", + "\u2663", + "▶", + "◀", + "▲", + "▼", + ] + ), + ((char)0xf061, (char)0xf06d, SArr("⁎⁑⁂⨁⨁⨁⨁⨀⩉ŲŲŲ₩")), + ((char)0xf074, (char)0xf07c, SArr("⨁⨁⨁⨁ABCDE")), + ( + (char)0xf107, + (char)0xf12f, + [ + "[⁇]", + "▢▣▣▣", + "▣▢▣▣", + "▣▣▢▣", + "▣▣▣▢", + "○●●●", + "●○●●", + "●●○●", + "●●●○", + "▢▣▣▣", + "▣▢▣▣", + "▣▣▢▣", + "▣▣▣▢", + "○●●●", + "●○●●", + "●●○●", + "●●●○", + "▢▣▣▣", + "▣▢▣▣", + "▣▣▢▣", + "▣▣▣▢", + "○●●●", + "●○●●", + "●●○●", + "●●●○", + "▢▣▣▣", + "▣▢▣▣", + "▣▣▢▣", + "▣▣▣▢", + "○●●●", + "●○●●", + "●●○●", + "●●●○", + "▢▣▣▣", + "▣▢▣▣", + "▣▣▢▣", + "▣▣▣▢", + "○●●●", + "●○●●", + "●●○●", + "●●●○", + "▢▣▣▣", + "▣▢▣▣", + "▣▣▢▣", + "▣▣▣▢", + "○●●●", + "●○●●", + "●●○●", + "●●●○", + ] + ), + }; + + foreach (var (start, end, replacements) in charRanges) + { + for (var i = start; i <= end; i++) + { + var replacementIndex = i - start; + if (replacements.Length < replacementIndex) + continue; // If its here we did not account for 1 of the replacements + str = str.Replace($"{i}", replacements[replacementIndex]); + } + } + + (char, string)[] individualReplacements = + [ + ((char)0xe028, "✕"), + ((char)0xe068, "er"), + ((char)0xe067, "re"), + ((char)0xe06a, "e"), + ((char)0xe06b, "[?]"), + ((char)0xf030, "②"), + ((char)0xf031, "②"), + ((char)0xf034, "(A)"), + ((char)0xf035, "(A)"), + ((char)0xf038, "(a)"), + ((char)0xf039, "(a)"), + ((char)0xf041, "[B]"), + ((char)0xf043, "(1)"), + ((char)0xf044, "(+)"), + ((char)0xf047, "(+)"), + ((char)0xf050, "(b)"), + ((char)0xf058, "(B)"), + ((char)0xf05e, "(S)"), + ((char)0xf05f, "(s)"), + ((char)0xf103, "▣▣▣▣"), + // The once below are also not in teh actual app but should still be exported correctly + ((char)0xf03c, "(A)"), + ((char)0xf03d, "(A)"), + ((char)0xf102, " "), + ((char)0xf060, " "), + ]; + + foreach (var (c, replacement) in individualReplacements) + { + str = str.Replace($"{c}", replacement); + } + + return str; + } + + private static string[] SArr(string input) => input.ToCharArray().Select(c => $"{c}").ToArray(); +} diff --git a/WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs b/WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs new file mode 100644 index 00000000..1c296671 --- /dev/null +++ b/WheelWizard/Features/CustomDistributions/CustomDistributionSingletonService.cs @@ -0,0 +1,31 @@ +using System.IO.Abstractions; +using Microsoft.Extensions.Logging; +using WheelWizard.CustomDistributions.Domain; +using WheelWizard.Shared.Services; + +namespace WheelWizard.CustomDistributions; + +public interface ICustomDistributionSingletonService +{ + List GetAllDistributions(); + + // FIXME: Abstract this reference away. A generic Distributions service kinda loses its purpose when you still have to reference a distribution by name (like done here) + // Instead you would want something like DistService.GetCurrentDistro() + // The rest of the application should not have to know what distribution is currently active. + RetroRewind RetroRewind { get; } +} + +public class CustomDistributionSingletonService : ICustomDistributionSingletonService +{ + public RetroRewind RetroRewind { get; } + + public CustomDistributionSingletonService(IFileSystem fileSystem, IApiCaller api, ILogger logger) + { + RetroRewind = new RetroRewind(fileSystem, api, logger); + } + + public List GetAllDistributions() + { + return [RetroRewind]; + } +} diff --git a/WheelWizard/Features/CustomDistributions/CustomDistributionsExtentions.cs b/WheelWizard/Features/CustomDistributions/CustomDistributionsExtentions.cs new file mode 100644 index 00000000..3174beff --- /dev/null +++ b/WheelWizard/Features/CustomDistributions/CustomDistributionsExtentions.cs @@ -0,0 +1,16 @@ +using Refit; +using WheelWizard.CustomDistributions.Domain; +using WheelWizard.Services; + +namespace WheelWizard.CustomDistributions; + +public static class CustomDistributionsExtentions +{ + public static IServiceCollection AddCustomDistributionService(this IServiceCollection services) + { + services.AddWhWzRefitApi(Endpoints.RRUrl); + + services.AddSingleton(); + return services; + } +} diff --git a/WheelWizard/Features/CustomDistributions/Domain/RetroRewindApi.cs b/WheelWizard/Features/CustomDistributions/Domain/RetroRewindApi.cs new file mode 100644 index 00000000..5711db77 --- /dev/null +++ b/WheelWizard/Features/CustomDistributions/Domain/RetroRewindApi.cs @@ -0,0 +1,18 @@ +using Refit; + +namespace WheelWizard.CustomDistributions.Domain; + +public interface IRetroRewindApi +{ + [Get("/RetroRewind/RetroRewind.zip")] + Task DownloadRetroRewindZip(); + + [Get("/RetroRewind/RetroRewindVersion.txt")] + Task GetVersionFile(); + + [Get("/RetroRewind/RetroRewindDelete.txt")] + Task GetDeletionFile(); + + [Get("/")] + Task Ping(); // use to test server reachability +} diff --git a/WheelWizard/Features/CustomDistributions/IDistribution.cs b/WheelWizard/Features/CustomDistributions/IDistribution.cs new file mode 100644 index 00000000..d2398c78 --- /dev/null +++ b/WheelWizard/Features/CustomDistributions/IDistribution.cs @@ -0,0 +1,48 @@ +using Semver; +using WheelWizard.Models.Enums; +using WheelWizard.Views.Popups.Generic; + +namespace WheelWizard.CustomDistributions; + +//todo: we cannot make more distributions before we also write a mystuff service and a service to download using UI + +public interface IDistribution +{ + /// + /// The title of the given distribution. + /// + public string Title { get; } + + /// + /// The name of the primary folder where the distribution is installed within the wheelwizard folder. + /// + string FolderName { get; } + + /// + /// The name of the wiiDisc .xml file in XMLFolderName + /// + string XMLFileName { get; } + + /// + /// The name of the folder containing the distributions wiiDisc .xml file + /// + string XMLFolderName { get; } + + /// + /// Install the distribution. + /// + Task InstallAsync(ProgressWindow progressWindow); + + /// + /// Update the distribution. + /// + Task UpdateAsync(ProgressWindow progressWindow); + + Task RemoveAsync(ProgressWindow progressWindow); + + Task ReinstallAsync(ProgressWindow progressWindow); + + Task> GetCurrentStatusAsync(); + + SemVersion? GetCurrentVersion(); +} diff --git a/WheelWizard/Features/CustomDistributions/RetroRewind.cs b/WheelWizard/Features/CustomDistributions/RetroRewind.cs new file mode 100644 index 00000000..99a7961f --- /dev/null +++ b/WheelWizard/Features/CustomDistributions/RetroRewind.cs @@ -0,0 +1,595 @@ +using System.IO.Abstractions; +using System.IO.Compression; +using System.Text.RegularExpressions; +using Avalonia.Threading; +using Microsoft.Extensions.Logging; +using Semver; +using WheelWizard.CustomDistributions.Domain; +using WheelWizard.Helpers; +using WheelWizard.Models.Enums; +using WheelWizard.Resources.Languages; +using WheelWizard.Services; +using WheelWizard.Services.Settings; +using WheelWizard.Shared.Services; +using WheelWizard.Views.Popups.Generic; + +namespace WheelWizard.CustomDistributions; + +public class RetroRewind : IDistribution +{ + private readonly IFileSystem _fileSystem; + private readonly IApiCaller _api; + private readonly ILogger _logger; + + public RetroRewind(IFileSystem fileSystem, IApiCaller api, ILogger logger) + { + _api = api; + _fileSystem = fileSystem; + _logger = logger; + } + + public string Title => "Retro Rewind"; + + // Keep in mind, whenever we download update files from the server, they are actually 1 folder higher, so it contains this folder. + public string FolderName => "RetroRewind6"; + public string XMLFolderName => "riivolution"; + public string XMLFileName => "RetroRewind6"; + + public async Task InstallAsync(ProgressWindow progressWindow) + { + if (GetCurrentVersion() is not null) + { + var removeResult = await RemoveAsync(progressWindow); + if (removeResult.IsFailure) + return removeResult; + } + + if (HasOldRksys()) + { + var rksysQuestion = new YesNoWindow() + .SetMainText(Phrases.Question_OldRksysFound_Title) + .SetExtraText(Phrases.Question_OldRksysFound_Extra); + if (await rksysQuestion.AwaitAnswer()) + await BackupOldrksys(); + } + var serverResponse = await _api.CallApiAsync(api => api.Ping()); // actual response doesnt matter + if (serverResponse.IsFailure) + return Fail("Could not connect to the server"); + + var downloadResult = await DownloadAndExtractRetroRewind(progressWindow); + if (downloadResult.IsFailure) + return downloadResult; + + var updateResult = await UpdateAsync(progressWindow); + if (updateResult.IsFailure) + return updateResult; + + return Ok(); + } + + private async Task DownloadAndExtractRetroRewind(ProgressWindow progressWindow) + { + progressWindow.SetExtraText(Phrases.Progress_InstallingRRFirstTime); + // path to the downloaded .zip + var tempZipPath = PathManager.RetroRewindTempFile; + // where we'll do the extraction + var tempExtractionPath = PathManager.TempModsFolderPath; + + //where all distributions are stored + var destinationParentDir = _fileSystem.DirectoryInfo.New(PathManager.RiivolutionWhWzFolderPath); + + OperationResult? result = null; + try + { + // 1) Download + if (_fileSystem.Directory.Exists(tempExtractionPath)) + _fileSystem.Directory.Delete(tempExtractionPath, recursive: true); + _fileSystem.Directory.CreateDirectory(tempExtractionPath); + + //todo, service + await DownloadHelper.DownloadToLocationAsync(Endpoints.RRZipUrl, tempZipPath, progressWindow); + + // 2) Extract + progressWindow.SetExtraText(Common.State_Extracting); + + var extractResult = await Task.Run(() => ExtractZipFile(tempZipPath, tempExtractionPath, progressWindow)); + + if (extractResult.IsFailure) + { + result = extractResult; + throw extractResult.Error.Exception ?? new Exception(extractResult.Error.Message); + } + + // 3) Locate the extracted sub-folder + var sourceFolder = _fileSystem.Path.Combine(tempExtractionPath, FolderName); + if (!_fileSystem.Directory.Exists(sourceFolder)) + throw new DirectoryNotFoundException($"Could not find a '{FolderName}' folder inside {tempExtractionPath}"); + + // 4) Remove existing install, if any + var removeResult = await RemoveAsync(progressWindow); + if (removeResult.IsFailure) + { + result = removeResult; + throw removeResult.Error.Exception ?? new Exception(removeResult.Error.Message); + } + + // 5) Move over RetroRewind + var xmlFolderSource = _fileSystem.Path.Combine(tempExtractionPath, XMLFolderName); + var riivolutionFiles = _fileSystem.Directory.EnumerateFiles(xmlFolderSource, "*", SearchOption.AllDirectories); + var retroRewindFiles = _fileSystem.Directory.EnumerateFiles(sourceFolder, "*", SearchOption.AllDirectories); + foreach (var file in riivolutionFiles.Concat(retroRewindFiles)) + { + var destinationPath = _fileSystem.Path.Combine( + destinationParentDir.FullName, + _fileSystem.Path.GetRelativePath(tempExtractionPath, file) + ); + var destinationDirectoryName = _fileSystem.Path.GetDirectoryName(destinationPath); + if (destinationDirectoryName != null) + { + var directory = _fileSystem.DirectoryInfo.New(destinationDirectoryName); + if (!directory?.Exists ?? false) + directory?.Create(); + } + _fileSystem.File.Move(file, destinationPath, false); //skip existing files for safety + } + } + catch (Exception e) + { + result ??= Fail(e); + _logger.LogError(e, e.Message); + } + finally + { + if (_fileSystem.File.Exists(tempZipPath)) + _fileSystem.File.Delete(tempZipPath); + + if (_fileSystem.Directory.Exists(tempExtractionPath)) + _fileSystem.Directory.Delete(tempExtractionPath, recursive: true); + } + return result ?? Ok(); + } + + private async Task BackupOldrksys() + { + var rrWfc = GetOldRksys(); + if (!_fileSystem.Directory.Exists(rrWfc)) + return; + var rksysFiles = _fileSystem.Directory.GetFiles(rrWfc, "rksys.dat", SearchOption.AllDirectories); + if (rksysFiles.Length == 0) + return; + var sourceFile = rksysFiles[0]; + var regionFolder = _fileSystem.Path.GetDirectoryName(sourceFile); + var regionFolderName = _fileSystem.Path.GetFileName(regionFolder); + var datFileData = await _fileSystem.File.ReadAllBytesAsync(sourceFile); + if (regionFolderName == null) + return; + var destinationFolder = _fileSystem.Path.Combine(PathManager.SaveFolderPath, regionFolderName); + _fileSystem.Directory.CreateDirectory(destinationFolder); + var destinationFile = _fileSystem.Path.Combine(destinationFolder, "rksys.dat"); + await _fileSystem.File.WriteAllBytesAsync(destinationFile, datFileData); + } + + private bool HasOldRksys() + { + return !string.IsNullOrWhiteSpace(GetOldRksys()); + } + + private string GetOldRksys() + { + // todo, maybe we should check for the existence of the file instead of the folder? and also find the oldest one? + var rrWfcPaths = new[] + { + PathManager.SaveFolderPath, + // Also consider the folder with upper-case `Save` + _fileSystem.Path.Combine(PathManager.RiivolutionWhWzFolderPath, "riivolution", "Save", "RetroWFC"), + _fileSystem.Path.Combine(PathManager.LoadFolderPath, "Riivolution", "save", "RetroWFC"), + _fileSystem.Path.Combine(PathManager.LoadFolderPath, "Riivolution", "Save", "RetroWFC"), + _fileSystem.Path.Combine(PathManager.LoadFolderPath, "riivolution", "save", "RetroWFC"), + _fileSystem.Path.Combine(PathManager.LoadFolderPath, "riivolution", "Save", "RetroWFC"), + }; + + foreach (var rrWfc in rrWfcPaths) + { + if (!_fileSystem.Directory.Exists(rrWfc)) + continue; + var rksysFiles = _fileSystem.Directory.GetFiles(rrWfc, "rksys.dat", SearchOption.AllDirectories); + if (rksysFiles.Length > 0) + return rrWfc; + } + + return string.Empty; + } + + private async Task> IsRRUpToDate(SemVersion currentVersion) + { + var latestVersionResult = await LatestServerVersion(); + if (latestVersionResult.IsFailure) + return Fail("Failed to check for updates"); + + var latestVersion = latestVersionResult.Value; + var isUpToDate = currentVersion.ComparePrecedenceTo(latestVersion) >= 0; + return isUpToDate; + } + + private async Task> LatestServerVersion() + { + var response = await _api.CallApiAsync(api => api.GetVersionFile()); + if (!response.IsSuccess || String.IsNullOrWhiteSpace(response.Value)) + return Fail("Failed to check for updates"); + + var result = response.Value.Split('\n', StringSplitOptions.RemoveEmptyEntries).Last().Split(' ')[0]; + return SemVersion.Parse(result); + } + + public async Task UpdateAsync(ProgressWindow progressWindow) + { + try + { + var currentVersion = GetCurrentVersion(); + if (currentVersion == null) + return await InstallAsync(progressWindow); + + var isRRUpToDate = await IsRRUpToDate(currentVersion); + if (isRRUpToDate.IsFailure) + return isRRUpToDate; + + if (isRRUpToDate.Value) + return Ok(); + + //if current version is below 3.2.6 we need to do a full reinstall + if (currentVersion.ComparePrecedenceTo(new SemVersion(3, 2, 6)) < 0) + { + var result = await ReinstallAsync(progressWindow); + return result.IsSuccess ? Ok() : result; + } + return await ApplyUpdates(currentVersion, progressWindow); + } + catch (Exception e) + { + return e; + } + } + + private async Task ApplyUpdates(SemVersion currentVersion, ProgressWindow progressWindow) + { + var allVersions = await GetAllVersionData(); + var updatesToApply = GetUpdatesToApply(currentVersion, allVersions); + // Step 1: Get the version we are updating to + var targetVersion = updatesToApply.Any() ? updatesToApply.Last().Version : currentVersion; + + // Step 2: Apply file deletions for versions between current and targetVersion + var deleteSuccess = await ApplyFileDeletionsBetweenVersions(currentVersion, targetVersion); + if (deleteSuccess.IsFailure) + return Fail(Phrases.MessageError_AbortRR_Extra_FailedUpdateDelete); + + // Step 3: Download and apply the updates (if any) + for (var i = 0; i < updatesToApply.Count; i++) + { + var update = updatesToApply[i]; + + var success = await DownloadAndApplyUpdate(update, updatesToApply.Count, i + 1, progressWindow); + if (success.IsFailure) + return Fail(Phrases.MessageError_AbortRR_Extra_FailedUpdateApply); + + // Update the version file after each successful update + UpdateVersionFile(update.Version); + } + return Ok(); + } + + private void UpdateVersionFile(SemVersion newVersion) + { + var versionFilePath = _fileSystem.Path.Combine(PathManager.RiivolutionWhWzFolderPath, FolderName, "version.txt"); + _fileSystem.File.WriteAllText(versionFilePath, newVersion.ToString()); + } + + private async Task DownloadAndApplyUpdate( + UpdateData update, + int totalUpdates, + int currentUpdateIndex, + ProgressWindow popupWindow + ) + { + var tempZipPath = _fileSystem.Path.Combine(_fileSystem.Path.GetTempPath(), _fileSystem.Path.GetRandomFileName()); + try + { + popupWindow.SetExtraText($"{Common.Action_Update} {currentUpdateIndex}/{totalUpdates}: {update.Description}"); + var finalFile = await DownloadHelper.DownloadToLocationAsync(update.Url, tempZipPath, popupWindow); + + popupWindow.UpdateProgress(100); + popupWindow.SetExtraText(Common.State_Extracting); + var destinationDirectoryPath = PathManager.RiivolutionWhWzFolderPath; + _fileSystem.Directory.CreateDirectory(destinationDirectoryPath); + + if (finalFile == null) + return Fail("Failed to download update file"); + var extractResult = ExtractZipFile(finalFile, destinationDirectoryPath, popupWindow); + if (extractResult.IsFailure) + return extractResult; + + if (_fileSystem.File.Exists(finalFile)) + _fileSystem.File.Delete(finalFile); + } + finally + { + if (_fileSystem.File.Exists(tempZipPath)) + _fileSystem.File.Delete(tempZipPath); + } + + return Ok(); + } + + private OperationResult ExtractZipFile(string path, string destinationDirectory, ProgressWindow progressWindow) + { + using var archive = ZipFile.OpenRead(path); + + // 1) Compute total work units (we’ll treat each entry as one “unit”) + var entries = archive.Entries.Where(e => !e.FullName.EndsWith("desktop.ini", StringComparison.OrdinalIgnoreCase)).ToList(); + var total = entries.Count; + if (total == 0) + return Ok(); + + // Tell the UI what we’re doing, and set a “goal” so it can estimate MB or items + + Dispatcher.UIThread.Post(() => + { + progressWindow.SetExtraText(Common.State_Extracting).SetGoal($"Extracting {total} files"); + }); + + // Absolute path of the destination directory + var absoluteDestinationPath = _fileSystem.Path.GetFullPath(destinationDirectory + Path.AltDirectorySeparatorChar); + + for (var i = 0; i < total; i++) + { + var entry = entries[i]; + var destinationPath = _fileSystem.Path.GetFullPath(Path.Combine(destinationDirectory, entry.FullName)); + + // Directory traversal check + if (!destinationPath.StartsWith(absoluteDestinationPath, StringComparison.Ordinal)) + return Fail("The file path is outside the destination directory. Please contact the developers."); + + // If it’s a directory, create it + if (entry.FullName.EndsWith(Path.AltDirectorySeparatorChar)) + { + _fileSystem.Directory.CreateDirectory(destinationPath); + } + else + { + // Ensure folder exists + var dir = _fileSystem.Path.GetDirectoryName(destinationPath); + if (!string.IsNullOrEmpty(dir)) + _fileSystem.Directory.CreateDirectory(dir); + + // Ensure read permission is set + entry.ExternalAttributes |= Convert.ToInt32("644", 8) << 16; + // Extract the file + entry.ExtractToFile(destinationPath, overwrite: true); + } + + // Report incremental progress (0–100) + var percent = (int)(((i + 1) / (double)total) * 100); + Dispatcher.UIThread.Post(() => + { + progressWindow.UpdateProgress(percent); + }); + } + + return Ok(); + } + + private async Task ApplyFileDeletionsBetweenVersions(SemVersion currentVersion, SemVersion targetVersion) + { + try + { + var deleteListResult = await GetFileDeletionList(); + if (deleteListResult.IsFailure) + return Fail("Failed to get file deletion list"); + + var deleteList = deleteListResult.Value; + var deletionsToApply = GetDeletionsToApply(currentVersion, targetVersion, deleteList); + + foreach (var file in deletionsToApply) + { + var absoluteDestinationPath = _fileSystem.Path.GetFullPath( + PathManager.RiivolutionWhWzFolderPath + _fileSystem.Path.AltDirectorySeparatorChar + ); + var filePath = _fileSystem.Path.GetFullPath(_fileSystem.Path.Combine(absoluteDestinationPath, file.Path.TrimStart('/'))); + //because we are actually getting the path from the server, + //we need to make sure we are not getting hacked, so we check if the path is in the riivolution folder + var resolvedPath = _fileSystem.Path.GetFullPath(new FileInfo(filePath).FullName); + if ( + !resolvedPath.StartsWith(absoluteDestinationPath, StringComparison.Ordinal) + || !filePath.StartsWith(absoluteDestinationPath, StringComparison.Ordinal) + || file.Path.Contains("..") + ) + { + return Fail("Invalid file path detected. Please contact the developers.\n Server error: " + resolvedPath); + } + + if (_fileSystem.File.Exists(filePath)) + _fileSystem.File.Delete(filePath); + else if (_fileSystem.Directory.Exists(filePath)) + _fileSystem.Directory.Delete(filePath, recursive: true); + } + + return Ok(); + } + catch (Exception e) + { + return Fail($"Failed to delete files: {e.Message}"); + } + } + + private struct DeletionData + { + public SemVersion Version; + public string Path; + } + + private async Task>> GetFileDeletionList() + { + var deleteList = new List(); + + var deleteListOperation = await _api.CallApiAsync(api => api.GetDeletionFile()); + if (deleteListOperation.IsFailure) + return Fail("Failed to get file deletion list"); + + var lines = deleteListOperation.Value.Split('\n', StringSplitOptions.RemoveEmptyEntries); + + foreach (var line in lines) + { + var parts = line.Split(' ', 2); + if (parts.Length < 2) + continue; + var deletionVersion = parts[0].Trim(); + var path = parts[1].Trim(); + if (string.IsNullOrWhiteSpace(deletionVersion) || string.IsNullOrWhiteSpace(path)) + continue; + if (!SemVersion.TryParse(deletionVersion, out var parsedVersion)) + return Fail("Failed to parse version"); + + var deletionData = new DeletionData { Version = parsedVersion, Path = path }; + deleteList.Add(deletionData); + } + + return deleteList; + } + + private struct UpdateData + { + public SemVersion Version; + public string Url; + public string Description; + } + + private async Task> GetAllVersionData() + { + var versions = new List(); + + var allVersionsResult = await _api.CallApiAsync(api => api.GetVersionFile()); + if (allVersionsResult.IsFailure) + return new(); + var lines = allVersionsResult.Value.Split('\n', StringSplitOptions.RemoveEmptyEntries); + + foreach (var line in lines) + { + var parts = line.Split(' ', 4); + if (parts.Length < 4) + continue; + var version = parts[0].Trim(); + var url = parts[1].Trim(); + var path = parts[2].Trim(); // Path unused in our program since on pc we manually decide where to extract + var description = parts[3].Trim(); + if (string.IsNullOrWhiteSpace(version) || string.IsNullOrWhiteSpace(url) || string.IsNullOrWhiteSpace(path)) + continue; + if (!SemVersion.TryParse(version, out var _)) + continue; + var parsedVersion = SemVersion.Parse(version); + var updateData = new UpdateData + { + Version = parsedVersion, + Url = url, + Description = description, + }; + versions.Add(updateData); + } + return versions; + } + + //todo: see if we can make this generic to the point we dont have to split up deletions and updates + private static List GetUpdatesToApply(SemVersion currentVersion, List allVersions) + { + var updatesToApply = new List(); + foreach (var update in allVersions) + { + if (update.Version.ComparePrecedenceTo(currentVersion) > 0) + updatesToApply.Add(update); + } + return updatesToApply; + } + + private static List GetDeletionsToApply( + SemVersion currentVersion, + SemVersion targetVersion, + List allDeletions + ) + { + var deletionsToApply = new List(); + allDeletions = allDeletions + .OrderByDescending(d => d.Version, Comparer.Create((a, b) => a.ComparePrecedenceTo(b))) + .ToList(); + foreach (var deletion in allDeletions) + { + if (deletion.Version.ComparePrecedenceTo(currentVersion) > 0 && deletion.Version.ComparePrecedenceTo(targetVersion) <= 0) + deletionsToApply.Add(deletion); + } + + deletionsToApply.Reverse(); + return deletionsToApply; + } + + public Task RemoveAsync(ProgressWindow progressWindow) + { + //where the RR distribution lives + var distributionDataDestination = _fileSystem.Path.Combine(PathManager.RiivolutionWhWzFolderPath, FolderName); + //where the RR wiiDisc xml file lives + var riivolutionDiscXMLFile = _fileSystem.Path.Combine(PathManager.RiivolutionWhWzFolderPath, XMLFolderName, $"{XMLFileName}.xml"); + + if (_fileSystem.Directory.Exists(distributionDataDestination)) + _fileSystem.Directory.Delete(distributionDataDestination, recursive: true); + if (_fileSystem.File.Exists(riivolutionDiscXMLFile)) + _fileSystem.File.Delete(riivolutionDiscXMLFile); + + return Task.FromResult(Ok()); + } + + public async Task ReinstallAsync(ProgressWindow progressWindow) + { + //Remove and install + var removeResult = await RemoveAsync(progressWindow); + if (removeResult.IsFailure) + return removeResult; + + return await InstallAsync(progressWindow); + } + + public async Task> GetCurrentStatusAsync() + { + if (!SettingsHelper.PathsSetupCorrectly()) + return WheelWizardStatus.ConfigNotFinished; + + var serverEnabled = await _api.CallApiAsync(api => api.Ping()); + var rrInstalled = GetCurrentVersion() != null; + + if (serverEnabled.IsFailure) + return rrInstalled ? WheelWizardStatus.NoServerButInstalled : WheelWizardStatus.NoServer; + + if (!rrInstalled) + return WheelWizardStatus.NotInstalled; + + var currentVersion = GetCurrentVersion(); + if (currentVersion == null) + return WheelWizardStatus.NotInstalled; + + var retroRewindUpToDateResult = await IsRRUpToDate(currentVersion); + if (retroRewindUpToDateResult.IsFailure) + return Fail("Failed to check for updates"); + + var retroRewindUpToDate = retroRewindUpToDateResult.Value; + return !retroRewindUpToDate ? WheelWizardStatus.OutOfDate : WheelWizardStatus.Ready; + } + + public SemVersion? GetCurrentVersion() + { + var versionFilePath = _fileSystem.Path.Combine(PathManager.RiivolutionWhWzFolderPath, FolderName, "version.txt"); + if (!_fileSystem.File.Exists(versionFilePath)) + return null; + + var versionText = _fileSystem.File.ReadAllText(versionFilePath).Trim(); + var versionPattern = @"^\d+\.\d+\.\d+$"; + if (!Regex.IsMatch(versionText, versionPattern)) + return null; + + return SemVersion.Parse(versionText); + } +} diff --git a/WheelWizard/Features/MiiImages/Domain/IMiiIMagesApi.cs b/WheelWizard/Features/MiiImages/Domain/IMiiIMagesApi.cs index db9ccb11..50cf40ec 100644 --- a/WheelWizard/Features/MiiImages/Domain/IMiiIMagesApi.cs +++ b/WheelWizard/Features/MiiImages/Domain/IMiiIMagesApi.cs @@ -1,4 +1,3 @@ -using Avalonia.Media.Imaging; using Refit; namespace WheelWizard.MiiImages.Domain; diff --git a/WheelWizard/Features/MiiImages/Domain/MiiImageSpecifications.cs b/WheelWizard/Features/MiiImages/Domain/MiiImageSpecifications.cs index ef044f5a..bcaa94b8 100644 --- a/WheelWizard/Features/MiiImages/Domain/MiiImageSpecifications.cs +++ b/WheelWizard/Features/MiiImages/Domain/MiiImageSpecifications.cs @@ -1,5 +1,4 @@ using System.Numerics; -using Avalonia.Media; using Microsoft.Extensions.Caching.Memory; namespace WheelWizard.MiiImages.Domain; diff --git a/WheelWizard/Features/MiiImages/Domain/MiiImageVariants.cs b/WheelWizard/Features/MiiImages/Domain/MiiImageVariants.cs index d359f76f..080ac09d 100644 --- a/WheelWizard/Features/MiiImages/Domain/MiiImageVariants.cs +++ b/WheelWizard/Features/MiiImages/Domain/MiiImageVariants.cs @@ -1,5 +1,4 @@ -using System.Numerics; -using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Caching.Memory; namespace WheelWizard.MiiImages.Domain; diff --git a/WheelWizard/Features/MiiImages/MiiImagesExtensions.cs b/WheelWizard/Features/MiiImages/MiiImagesExtensions.cs index 082ed4de..80df3958 100644 --- a/WheelWizard/Features/MiiImages/MiiImagesExtensions.cs +++ b/WheelWizard/Features/MiiImages/MiiImagesExtensions.cs @@ -1,4 +1,3 @@ -using System.Numerics; using WheelWizard.MiiImages.Domain; using WheelWizard.Services; diff --git a/WheelWizard/Features/MiiImages/MiiImagesSingletonService.cs b/WheelWizard/Features/MiiImages/MiiImagesSingletonService.cs index c540266f..25f66d34 100644 --- a/WheelWizard/Features/MiiImages/MiiImagesSingletonService.cs +++ b/WheelWizard/Features/MiiImages/MiiImagesSingletonService.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.Caching.Memory; using WheelWizard.MiiImages.Domain; using WheelWizard.Shared.Services; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; namespace WheelWizard.MiiImages; @@ -27,7 +27,11 @@ public async Task> GetImageAsync(Mii? mii, MiiImageSpeci // Even tho we also check it in the semaphore section, we also check here if it's in the cache, just to be tad faster. if (cache.TryGetValue(miiConfigKey, out Bitmap? cachedValue)) - return cachedValue ?? Fail("Cached image is null."); + { + if (cachedValue != null) + return cachedValue; + return Fail("Cached image is null."); + } var requestSemaphore = _inFlightRequests.GetOrAdd(miiConfigKey, _ => new(1, 1)); @@ -39,7 +43,11 @@ public async Task> GetImageAsync(Mii? mii, MiiImageSpeci // Double-check the cache after acquiring the semaphore // Another thread might have completed the request while we were waiting if (cache.TryGetValue(miiConfigKey, out Bitmap? doubleCheckCached)) - return doubleCheckCached ?? Fail("Cached image is null."); + { + if (doubleCheckCached != null) + return doubleCheckCached; + return Fail("Cached image is null."); + } // If we get here, we're the first request and need to call the API var newImageResult = await apiCaller.CallApiAsync(api => GetBitmapAsync(api, data.Value, specifications)); @@ -55,7 +63,9 @@ public async Task> GetImageAsync(Mii? mii, MiiImageSpeci entry.Priority = specifications.CachePriority; } - return newImage ?? Fail("Failed to get new image."); + if (newImage != null) + return newImage; + return Fail("Failed to get new image."); } finally { diff --git a/WheelWizard/Features/MiiImages/MiiStudioDataSerializer.cs b/WheelWizard/Features/MiiImages/MiiStudioDataSerializer.cs index 37ea725a..ef4c5810 100644 --- a/WheelWizard/Features/MiiImages/MiiStudioDataSerializer.cs +++ b/WheelWizard/Features/MiiImages/MiiStudioDataSerializer.cs @@ -1,7 +1,8 @@ using System.Text; -using WheelWizard.Services.WiiManagement.SaveData; +using WheelWizard.Helpers; using WheelWizard.WiiManagement; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.MiiManagement; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; namespace WheelWizard.MiiImages; @@ -16,7 +17,7 @@ public class MiiStudioDataSerializer public static OperationResult Serialize(Mii? mii) { if (mii == null) - return Fail("Mii cannot be null."); + return Fail("Mii cannot be null."); // First we create a clone of the Mii that only contains features that are visual // This means no name, date or other non-visual features. @@ -34,7 +35,7 @@ public static OperationResult Serialize(Mii? mii) visualMiiClone.IsGirl = mii.IsGirl; visualMiiClone.MiiMole = mii.MiiMole; visualMiiClone.MiiFavoriteColor = mii.MiiFavoriteColor; - visualMiiClone.MiiFacial = mii.MiiFacial; + visualMiiClone.MiiFacialFeatures = mii.MiiFacialFeatures; // If id is 0, we keep it 0, any other ID will be set to 1. visualMiiClone.MiiId = (uint)(mii.MiiId == 0 ? 0 : 1); @@ -77,7 +78,7 @@ private static byte[] GenerateStudioDataArray(byte[] buf) // Offsets and logic match the 'else' block (Wii part) of miiFileRead // --- Basic Info --- - var tmpU16_0 = BigEndianBinaryReader.BufferToUint16(buf, 0); + var tmpU16_0 = BigEndianBinaryHelper.BufferToUint16(buf, 0); var isGirl = ((tmpU16_0 >> 14) & 1) == 1; var favColor = (int)((tmpU16_0 >> 1) & 0xF); int height = buf[0x16]; @@ -89,7 +90,7 @@ private static byte[] GenerateStudioDataArray(byte[] buf) studio[2] = (byte)weight; // Weight (mapped to index 2 in studio) // --- Face --- - var tmpU16_20 = BigEndianBinaryReader.BufferToUint16(buf, 0x20); + var tmpU16_20 = BigEndianBinaryHelper.BufferToUint16(buf, 0x20); var faceShape = (int)(tmpU16_20 >> 13); var skinColor = (int)((tmpU16_20 >> 10) & 7); var facialFeature = (int)((tmpU16_20 >> 6) & 0xF); // Note: JS uses 0xF mask here, map to makeup/wrinkles @@ -102,7 +103,7 @@ private static byte[] GenerateStudioDataArray(byte[] buf) studio[0x12] = (byte)makeup; // --- Hair --- - var tmpU16_22 = BigEndianBinaryReader.BufferToUint16(buf, 0x22); + var tmpU16_22 = BigEndianBinaryHelper.BufferToUint16(buf, 0x22); var hairStyle = (int)(tmpU16_22 >> 9); var hairColor = (int)((tmpU16_22 >> 6) & 7); var flipHair = (int)((tmpU16_22 >> 5) & 1); @@ -112,7 +113,7 @@ private static byte[] GenerateStudioDataArray(byte[] buf) studio[0x1C] = (byte)flipHair; // --- Eyebrows --- - var tmpU32_24 = BigEndianBinaryReader.BufferToUint32(buf, 0x24); + var tmpU32_24 = BigEndianBinaryHelper.BufferToUint32(buf, 0x24); var eyebrowStyle = (int)(tmpU32_24 >> 27); var eyebrowRotation = (int)((tmpU32_24 >> 22) & 0xF); // Note: JS uses 0xF mask var eyebrowColor = (int)((tmpU32_24 >> 13) & 7); @@ -130,7 +131,7 @@ private static byte[] GenerateStudioDataArray(byte[] buf) studio[0xF] = (byte)eyebrowXSpacing; // --- Eyes --- - var tmpU32_28 = BigEndianBinaryReader.BufferToUint32(buf, 0x28); + var tmpU32_28 = BigEndianBinaryHelper.BufferToUint32(buf, 0x28); var eyeStyle = (int)(tmpU32_28 >> 26); var eyeRotation = (int)((tmpU32_28 >> 21) & 7); // Note: JS uses 7 (0b111) mask var eyeYPosition = (int)((tmpU32_28 >> 16) & 0x1F); @@ -149,7 +150,7 @@ private static byte[] GenerateStudioDataArray(byte[] buf) studio[8] = (byte)eyeXSpacing; // --- Nose --- - var tmpU16_2C = BigEndianBinaryReader.BufferToUint16(buf, 0x2C); + var tmpU16_2C = BigEndianBinaryHelper.BufferToUint16(buf, 0x2C); var noseStyle = (int)(tmpU16_2C >> 12); var noseScale = (int)((tmpU16_2C >> 8) & 0xF); var noseYposition = (int)((tmpU16_2C >> 3) & 0x1F); @@ -160,7 +161,7 @@ private static byte[] GenerateStudioDataArray(byte[] buf) studio[0x2D] = (byte)noseYposition; // --- Mouth --- - var tmpU16_2E = BigEndianBinaryReader.BufferToUint16(buf, 0x2E); + var tmpU16_2E = BigEndianBinaryHelper.BufferToUint16(buf, 0x2E); var mouseStyle = (int)(tmpU16_2E >> 11); var mouseColor = (int)((tmpU16_2E >> 9) & 3); // Lip color (0-3) var mouseScale = (int)((tmpU16_2E >> 5) & 0xF); @@ -174,7 +175,7 @@ private static byte[] GenerateStudioDataArray(byte[] buf) studio[0x27] = (byte)mouseYPosition; // --- Beard / Mustache --- - var tmpU16_32 = BigEndianBinaryReader.BufferToUint16(buf, 0x32); + var tmpU16_32 = BigEndianBinaryHelper.BufferToUint16(buf, 0x32); var mustacheStyle = (int)(tmpU16_32 >> 14); var beardStyle = (int)((tmpU16_32 >> 12) & 3); var facialHairColor = (int)((tmpU16_32 >> 9) & 7); @@ -188,7 +189,7 @@ private static byte[] GenerateStudioDataArray(byte[] buf) studio[0x2A] = (byte)mustacheYPosition; // --- Glasses --- - var tmpU16_30 = BigEndianBinaryReader.BufferToUint16(buf, 0x30); + var tmpU16_30 = BigEndianBinaryHelper.BufferToUint16(buf, 0x30); var glassesStyle = (int)(tmpU16_30 >> 12); var glassesColor = (int)((tmpU16_30 >> 9) & 7); var glassesScale = (int)((tmpU16_30 >> 5) & 7); // Note: JS uses 7 mask @@ -208,7 +209,7 @@ private static byte[] GenerateStudioDataArray(byte[] buf) studio[0x1A] = (byte)glassesYPosition; // --- Mole --- - var tmpU16_34 = BigEndianBinaryReader.BufferToUint16(buf, 0x34); + var tmpU16_34 = BigEndianBinaryHelper.BufferToUint16(buf, 0x34); var enableMole = (int)(tmpU16_34 >> 15); var moleScale = (int)((tmpU16_34 >> 11) & 0xF); var moleYPosition = (int)((tmpU16_34 >> 6) & 0x1F); diff --git a/WheelWizard/Features/WheelWizardData/Domain/BadgeVariant.cs b/WheelWizard/Features/WheelWizardData/Domain/BadgeVariant.cs index 42f3dc4b..dc722982 100644 --- a/WheelWizard/Features/WheelWizardData/Domain/BadgeVariant.cs +++ b/WheelWizard/Features/WheelWizardData/Domain/BadgeVariant.cs @@ -7,7 +7,10 @@ public enum BadgeVariant RrDev, Translator, TranslatorLead, - GoldWinner, - SilverWinner, - BronzeWinner, + Firestarter_GoldWinner, + Firestarter_SilverWinner, + Firestarter_BronzeWinner, + SummitShowdown_GoldWinner, + SummitShowdown_SilverWinner, + SummitShowdown_BronzeWinner, } diff --git a/WheelWizard/Features/WiiManagement/Domain/Mii/Mii.cs b/WheelWizard/Features/WiiManagement/Domain/Mii/Mii.cs deleted file mode 100644 index 4d5e45cf..00000000 --- a/WheelWizard/Features/WiiManagement/Domain/Mii/Mii.cs +++ /dev/null @@ -1,64 +0,0 @@ -namespace WheelWizard.WiiManagement.Domain.Mii; - -public class Mii -{ - public bool IsInvalid { get; set; } - public bool IsGirl { get; set; } - public DateOnly Date { get; set; } = new(2000, 1, 1); - public MiiFavoriteColor MiiFavoriteColor { get; set; } = MiiFavoriteColor.Black; - public bool IsFavorite { get; set; } - - public MiiName Name { get; set; } = new("no name"); - public MiiScale Height { get; set; } = new(1); - public MiiScale Weight { get; set; } = new(1); - - public bool IsForeign => (MiiId1 >> 5) == 0b110; // checks if the top 3 bits are set to 110, if so it got blue pants - - //Mii ID is also refered as Avatar ID - public byte MiiId1 { get; set; } - public byte MiiId2 { get; set; } - public byte MiiId3 { get; set; } - public byte MiiId4 { get; set; } - - public uint MiiId - { - get => (uint)(MiiId1 << 24 | MiiId2 << 16 | MiiId3 << 8 | MiiId4); - set - { - MiiId1 = (byte)(value >> 24); - MiiId2 = (byte)(value >> 16); - MiiId3 = (byte)(value >> 8); - MiiId4 = (byte)(value); - } - } - - //This is also referred as Client ID - public byte SystemId0 { get; set; } - public byte SystemId1 { get; set; } - public byte SystemId2 { get; set; } - public byte SystemId3 { get; set; } - - public uint SystemId - { - get => (uint)(SystemId0 << 24 | SystemId1 << 16 | SystemId2 << 8 | SystemId3); - set - { - SystemId0 = (byte)(value >> 24); - SystemId1 = (byte)(value >> 16); - SystemId2 = (byte)(value >> 8); - SystemId3 = (byte)(value); - } - } - - public MiiFacialFeatures MiiFacial { get; set; } = new(MiiFaceShape.Bread, MiiSkinColor.Light, MiiFacialFeature.None, false, false); - - public MiiHair MiiHair { get; set; } = new(1, HairColor.Black, false); - public MiiEyebrow MiiEyebrows { get; set; } = new(1, 0, EyebrowColor.Black, 4, 10, 1); - public MiiEye MiiEyes { get; set; } = new(1, 6, 7, EyeColor.Black, 3, 6); - public MiiNose MiiNose { get; set; } = new(NoseType.Default, 6, 4); - public MiiLip MiiLips { get; set; } = new(1, LipColor.Skin, 4, 9); - public MiiGlasses MiiGlasses { get; set; } = new(GlassesType.None, GlassesColor.Dark, 4, 1); - public MiiFacialHair MiiFacialHair { get; set; } = new(MustacheType.None, BeardType.None, MustacheColor.Black, 1, 1); - public MiiMole MiiMole { get; set; } = new(false, 0, 0, 0); - public MiiName CreatorName { get; set; } = new("no name"); -} diff --git a/WheelWizard/Features/WiiManagement/Domain/Mii/MiiFacialHair.cs b/WheelWizard/Features/WiiManagement/Domain/Mii/MiiFacialHair.cs deleted file mode 100644 index ed215063..00000000 --- a/WheelWizard/Features/WiiManagement/Domain/Mii/MiiFacialHair.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace WheelWizard.WiiManagement.Domain.Mii; - -public class MiiFacialHair -{ - public MustacheType MustacheType { get; } - public BeardType BeardType { get; } - public MustacheColor Color { get; } - public int Size { get; } - public int Vertical { get; } - - public MiiFacialHair(MustacheType mustacheType, BeardType beardType, MustacheColor color, int size, int vertical) - { - if (size is < 0 or > 8) - throw new ArgumentException("Facial hair size invalid"); - if (vertical is < 0 or > 16) - throw new ArgumentException("Facial hair vertical position invalid"); - MustacheType = mustacheType; - BeardType = beardType; - Color = color; - Size = size; - Vertical = vertical; - } - - public static OperationResult Create( - MustacheType mustacheType, - BeardType beardType, - MustacheColor color, - int size, - int vertical - ) => TryCatch(() => new MiiFacialHair(mustacheType, beardType, color, size, vertical)); -} diff --git a/WheelWizard/Features/WiiManagement/Domain/Mii/MiiHair.cs b/WheelWizard/Features/WiiManagement/Domain/Mii/MiiHair.cs deleted file mode 100644 index 32a4ac89..00000000 --- a/WheelWizard/Features/WiiManagement/Domain/Mii/MiiHair.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace WheelWizard.WiiManagement.Domain.Mii; - -public class MiiHair -{ - public int HairType { get; } - public HairColor HairColor { get; } - public bool HairFlipped { get; } - - public MiiHair(int hairType, HairColor hairColor, bool hairFlipped) - { - if (hairType is < 0 or > 71) - throw new ArgumentException("HairType out of range"); - HairType = hairType; - HairColor = hairColor; - HairFlipped = hairFlipped; - } - - public static OperationResult Create(int hairType, HairColor hairColor, bool hairFlipped) => - TryCatch(() => new MiiHair(hairType, hairColor, hairFlipped)); -} diff --git a/WheelWizard/Models/GameData/FriendProfile.cs b/WheelWizard/Features/WiiManagement/GameLicense/Domain/FriendProfile.cs similarity index 84% rename from WheelWizard/Models/GameData/FriendProfile.cs rename to WheelWizard/Features/WiiManagement/GameLicense/Domain/FriendProfile.cs index c99395f0..e9ee0c42 100644 --- a/WheelWizard/Models/GameData/FriendProfile.cs +++ b/WheelWizard/Features/WiiManagement/GameLicense/Domain/FriendProfile.cs @@ -1,6 +1,6 @@ using WheelWizard.Helpers; -namespace WheelWizard.Models.GameData; +namespace WheelWizard.WiiManagement.GameLicense.Domain; public class FriendProfile : PlayerProfileBase { diff --git a/WheelWizard/Models/GameData/LicenseCollection.cs b/WheelWizard/Features/WiiManagement/GameLicense/Domain/LicenseCollection.cs similarity index 72% rename from WheelWizard/Models/GameData/LicenseCollection.cs rename to WheelWizard/Features/WiiManagement/GameLicense/Domain/LicenseCollection.cs index 80d1a212..2f700219 100644 --- a/WheelWizard/Models/GameData/LicenseCollection.cs +++ b/WheelWizard/Features/WiiManagement/GameLicense/Domain/LicenseCollection.cs @@ -1,4 +1,4 @@ -namespace WheelWizard.Models.GameData; +namespace WheelWizard.WiiManagement.GameLicense.Domain; public class LicenseCollection { diff --git a/WheelWizard/Models/GameData/LicenseProfile.cs b/WheelWizard/Features/WiiManagement/GameLicense/Domain/LicenseProfile.cs similarity index 55% rename from WheelWizard/Models/GameData/LicenseProfile.cs rename to WheelWizard/Features/WiiManagement/GameLicense/Domain/LicenseProfile.cs index d4e2f74b..bd20da4e 100644 --- a/WheelWizard/Models/GameData/LicenseProfile.cs +++ b/WheelWizard/Features/WiiManagement/GameLicense/Domain/LicenseProfile.cs @@ -1,8 +1,11 @@ -namespace WheelWizard.Models.GameData; +using WheelWizard.WiiManagement.GameLicense.Domain.Statistics; + +namespace WheelWizard.WiiManagement.GameLicense.Domain; public class LicenseProfile : PlayerProfileBase { public required uint TotalRaceCount { get; set; } public required uint TotalWinCount { get; set; } public List Friends { get; set; } = []; + public LicenseStatistics Statistics { get; set; } } diff --git a/WheelWizard/Models/GameData/PlayerProfileBase.cs b/WheelWizard/Features/WiiManagement/GameLicense/Domain/PlayerProfileBase.cs similarity index 92% rename from WheelWizard/Models/GameData/PlayerProfileBase.cs rename to WheelWizard/Features/WiiManagement/GameLicense/Domain/PlayerProfileBase.cs index 524d5744..ed8bad87 100644 --- a/WheelWizard/Models/GameData/PlayerProfileBase.cs +++ b/WheelWizard/Features/WiiManagement/GameLicense/Domain/PlayerProfileBase.cs @@ -2,9 +2,9 @@ using WheelWizard.Helpers; using WheelWizard.Services.LiveData; using WheelWizard.WheelWizardData.Domain; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; -namespace WheelWizard.Models.GameData; +namespace WheelWizard.WiiManagement.GameLicense.Domain; public abstract class PlayerProfileBase : INotifyPropertyChanged { diff --git a/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/BattleCompletions.cs b/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/BattleCompletions.cs new file mode 100644 index 00000000..eefd80b5 --- /dev/null +++ b/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/BattleCompletions.cs @@ -0,0 +1,9 @@ +namespace WheelWizard.WiiManagement.GameLicense.Domain.Statistics; + +public class BattleCompletions +{ + /// + /// Key = Battle Stage, Value = Amount of times the stage was played + /// + public Dictionary Stage { get; set; } = new(); +} diff --git a/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/LicenseStatisticEnums.cs b/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/LicenseStatisticEnums.cs new file mode 100644 index 00000000..a8170616 --- /dev/null +++ b/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/LicenseStatisticEnums.cs @@ -0,0 +1,150 @@ +namespace WheelWizard.WiiManagement.GameLicense.Domain.Statistics; + +public enum CupTrophyType +{ + Gold = 0, + Silver = 1, + Bronze = 2, + None = 3, +} + +public enum CupRank +{ + ThreeStars = 0, + TwoStars = 1, + OneStar = 2, + A = 3, + B = 4, + C = 5, + D = 6, + E = 7, + F = 8, +} + +// The 10 battle stages in the order they appear in RKPD (0x1A6 onward). +public enum Stage : byte +{ + DelfinoPier = 0, + BlockPlaza, + ChainChompWheel, + FunkyStadium, + ThwompDesert, + GCNCookieLand, + DSTwilightHouse, + SNESBattleCourse4, + GBABattleCourse3, + N64Skyscraper, +} + +public enum Course : byte +{ + MarioCircuit = 0, + MooMooMeadows, + MushroomGorge, + GrumbleVolcano, + ToadsFactory, + CoconutMall, + DKSummit, + WariosGoldMine, + LuigiCircuit, + DaisyCircuit, + MoonviewHighway, + MapleTreeway, + BowserCastle, + RainbowRoad, + DryDryRuins, + KoopaCape, + GCNPeachBeach, + GCNMarioCircuit, + GCNWaluigiStadium, + GCNDKMountain, + DSYoshiFalls, + DSDesertHills, + DSPeachGardens, + DSDelfinoSquare, + SNESMarioCircuit3, + SNESGhostValley2, + N64MarioRaceway, + N64SherbetLand, + N64BowsersCastle, + N64DKsJungleParkway, + GBABowserCastle3, + GBAShyGuyBeach, +} + +public enum Vehicle : byte +{ + StandardKartS = 0, + StandardKartM, + StandardKartL, + BoosterSeat, + ClassicDragster, + Offroader, + MiniBeast, + WildWing, + FlameFlyer, + CheepCharger, + SuperBlooper, + PiranhaProwler, + TinyTitan, + Daytripper, + Jetsetter, + BlueFalcon, + Sprinter, + Honeycoupe, + StandardBikeS, + StandardBikeM, + StandardBikeL, + BulletBike, + MachBike, + FlameRunner, + BitBike, + Sugarscoot, + WarioBike, + Quacker, + ZipZip, + ShootingStar, + Magikruiser, + Sneakster, + Spear, + JetBubble, + DolphinDasher, + Phantom, +} + +// The 24 playable characters in the order they appear in RKPD (0xEC onward). +public enum Character : byte +{ + Mario = 0, + BabyPeach, + Waluigi, + Bowser, + BabyDaisy, + DryBones, + BabyMario, + Luigi, + Toad, + DonkeyKong, + Yoshi, + Wario, + BabyLuigi, + Toadette, + KoopaTroopa, + Daisy, + Peach, + Birdo, + DiddyKong, + KingBoo, + BowserJr, + DryBowser, + FunkyKong, + Rosalina, + Mii = 23, +} + +public enum DriftType +{ + Standard = 0, + Manual = 1, + Automatic = 2, +} diff --git a/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/LicenseStatistics.cs b/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/LicenseStatistics.cs new file mode 100644 index 00000000..7da35ee7 --- /dev/null +++ b/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/LicenseStatistics.cs @@ -0,0 +1,13 @@ +namespace WheelWizard.WiiManagement.GameLicense.Domain.Statistics; + +public class LicenseStatistics +{ + public RaceTotals RaceTotals { get; set; } = new(); + public PreferredControls Controls { get; set; } = new(); + public Performance Performance { get; init; } = new(); + public RaceCompletions RaceCompletions { get; init; } = new(); + public PreferredControls PreferredControls { get; init; } = new(); + public BattleCompletions BattleCompletions { get; init; } = new(); + public ushort TotalCompetitions { get; init; } + public TrophyCabinet? Trophies { get; init; } +} diff --git a/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/Performance.cs b/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/Performance.cs new file mode 100644 index 00000000..d170e72a --- /dev/null +++ b/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/Performance.cs @@ -0,0 +1,25 @@ +namespace WheelWizard.WiiManagement.GameLicense.Domain.Statistics; + +public class Performance +{ + public int TricksPerformed { get; set; } // total amount of tricks performed + public int ItemHitsDealt { get; set; } // total amount of item hits dealt + public int ItemHitsReceived { get; set; } // total amount of item hits received + public int FirstPlaces { get; set; } // total amount of first places achieved + public float DistanceTotal { get; set; } // Total Distance driven + public float DistanceInFirstPlace { get; set; } // Total Distance driven in first place + public float DistanceVsRaces { get; set; } + + // percentage of time in 1st = floor(DistanceInFirstPlace / DistanceVsRaces * 100). + + public int PercentTimeInFirstPlace + { + get + { + if (DistanceVsRaces == 0) + return 0; + + return (int)Math.Floor(DistanceInFirstPlace / DistanceVsRaces * 100); + } + } +} diff --git a/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/PreferredControls.cs b/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/PreferredControls.cs new file mode 100644 index 00000000..83300f42 --- /dev/null +++ b/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/PreferredControls.cs @@ -0,0 +1,18 @@ +namespace WheelWizard.WiiManagement.GameLicense.Domain.Statistics; + +public class PreferredControls +{ + public int WiiWheelRaces { get; set; } // amount of matched played with the Wii Wheel + public int WiiWheelBattles { get; set; } // amount of matched played with the Wii Wheel in battles + public float WheelWheelUsageRatio + { + get + { + if (WiiWheelRaces + WiiWheelBattles == 0) + return 0f; + + return (float)WiiWheelRaces / (WiiWheelRaces + WiiWheelBattles); + } + } + public DriftType PreferredDriftType { get; set; } = DriftType.Standard; // preferred drift type, default is standard +} diff --git a/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/RaceCompletions.cs b/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/RaceCompletions.cs new file mode 100644 index 00000000..39c90273 --- /dev/null +++ b/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/RaceCompletions.cs @@ -0,0 +1,36 @@ +namespace WheelWizard.WiiManagement.GameLicense.Domain.Statistics; + +public class RaceCompletions +{ + public Dictionary CharacterCompletions { get; set; } = new(); + + public Character FavoriteCharacter + { + get + { + var currentFavorite = Character.Mario; // Default to Mario + var maxCompletions = 0; + foreach (var kvp in CharacterCompletions) + { + if (kvp.Value > maxCompletions) + { + maxCompletions = kvp.Value; + currentFavorite = kvp.Key; + } + } + return currentFavorite; + } + } + + /// + /// Key = Vehicle enum, Value = races completed count. + /// Uses 36 entries starting at 0x11E. + /// + public Dictionary Vehicle { get; init; } = new(); + + /// + /// Key = Course enum, Value = races completed count. + /// Uses 32 entries starting at 0x166. + /// + public Dictionary Course { get; init; } = new(); +} diff --git a/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/RaceTotals.cs b/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/RaceTotals.cs new file mode 100644 index 00000000..b7c2ce35 --- /dev/null +++ b/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/RaceTotals.cs @@ -0,0 +1,21 @@ +namespace WheelWizard.WiiManagement.GameLicense.Domain.Statistics; + +public class RaceTotals +{ + public uint AllRacesCount { get; set; } // total amount of races completed + + public uint OnlineRacesCount + { + get + { + return WinsVsLosses.OnlineVs.Wins + + WinsVsLosses.OnlineVs.Losses + + WinsVsLosses.OnlineBattle.Wins + + WinsVsLosses.OnlineBattle.Losses; + } + } + public uint BattleMatches { get; set; } // total amount of battle matches completed + public WinsVsLosses WinsVsLosses { get; set; } = new(); + public int GhostChallengesSent { get; set; } // total amount of ghost challenges sent + public int GhostChallengesReceived { get; set; } // total amount of ghost challenges received +} diff --git a/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/TrophyCabinet.cs b/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/TrophyCabinet.cs new file mode 100644 index 00000000..0a871519 --- /dev/null +++ b/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/TrophyCabinet.cs @@ -0,0 +1,9 @@ +namespace WheelWizard.WiiManagement.GameLicense.Domain.Statistics; + +public class TrophyCabinet +{ + /// + /// Key = “Mushroom Cup 50cc”, Value = TrophyInfo + /// + public Dictionary PerCup { get; init; } = new(); +} diff --git a/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/TrophyInfo.cs b/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/TrophyInfo.cs new file mode 100644 index 00000000..5eb30450 --- /dev/null +++ b/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/TrophyInfo.cs @@ -0,0 +1,11 @@ +namespace WheelWizard.WiiManagement.GameLicense.Domain.Statistics; + +/// +/// Single cup’s result: which trophy and what star rank. +/// +public class TrophyInfo +{ + public CupTrophyType CupType { get; set; } // Gold, Silver, Bronze, None + public CupRank Rank { get; set; } // ThreeStars, TwoStars, OneStar, A, B, C, D, E, F + public bool Completed { get; set; } // bit 0x52: 1 = completed +} diff --git a/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/WinLoss.cs b/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/WinLoss.cs new file mode 100644 index 00000000..81e3b9ee --- /dev/null +++ b/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/WinLoss.cs @@ -0,0 +1,7 @@ +namespace WheelWizard.WiiManagement.GameLicense.Domain.Statistics; + +public class WinLoss +{ + public uint Wins { get; set; } + public uint Losses { get; set; } +} diff --git a/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/WinsVsLosses.cs b/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/WinsVsLosses.cs new file mode 100644 index 00000000..30bb40fd --- /dev/null +++ b/WheelWizard/Features/WiiManagement/GameLicense/Domain/Statistics/WinsVsLosses.cs @@ -0,0 +1,9 @@ +namespace WheelWizard.WiiManagement.GameLicense.Domain.Statistics; + +public class WinsVsLosses +{ + public WinLoss OfflineVs { get; set; } = new(); + public WinLoss OfflineBattle { get; set; } = new(); + public WinLoss OnlineVs { get; set; } = new(); + public WinLoss OnlineBattle { get; set; } = new(); +} diff --git a/WheelWizard/Features/WiiManagement/GameLicenseService.cs b/WheelWizard/Features/WiiManagement/GameLicense/GameLicenseService.cs similarity index 84% rename from WheelWizard/Features/WiiManagement/GameLicenseService.cs rename to WheelWizard/Features/WiiManagement/GameLicense/GameLicenseService.cs index 4bc2bb37..5ad753d4 100644 --- a/WheelWizard/Features/WiiManagement/GameLicenseService.cs +++ b/WheelWizard/Features/WiiManagement/GameLicense/GameLicenseService.cs @@ -1,27 +1,29 @@ using System.IO.Abstractions; using System.Text; using System.Text.RegularExpressions; +using WheelWizard.Helpers; using WheelWizard.Models.Enums; -using WheelWizard.Models.GameData; +using WheelWizard.Models.Settings; using WheelWizard.Models.Settings; using WheelWizard.Services; using WheelWizard.Services.LiveData; using WheelWizard.Services.Other; using WheelWizard.Services.Settings; -using WheelWizard.Services.WiiManagement.SaveData; using WheelWizard.Utilities.Generators; using WheelWizard.Utilities.RepeatedTasks; using WheelWizard.WheelWizardData; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.GameLicense.Domain; +using WheelWizard.WiiManagement.MiiManagement; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; -namespace WheelWizard.WiiManagement; +namespace WheelWizard.WiiManagement.GameLicense; // big big thanks to https://kazuki-4ys.github.io/web_apps/FaceThief/ for the JS implementation // Also Refer to this documentation https://wiki.tockdom.com/wiki/Rksys.dat public interface IGameLicenseSingletonService { /// - /// Gets the currently loaded . + /// Gets the currently loaded . /// LicenseCollection LicenseCollection { get; } @@ -151,6 +153,7 @@ private static LicenseProfile CreateDummyLicense() Friends = [], RegionId = 10, // 10 => “unknown” IsOnline = false, + Statistics = new(), }; return dummyLicense; } @@ -196,18 +199,22 @@ private OperationResult ParseLicenseUser(int rkpdOffset) var friendCode = FriendCodeGenerator.GetFriendCode(_rksysData, rkpdOffset + 0x5C); var miiDataResult = ParseMiiData(rkpdOffset); var miiToUse = miiDataResult.IsFailure ? new() : miiDataResult.Value; + + var statistics = StatisticsSerializer.ParseStatistics(_rksysData, rkpdOffset); + var user = new LicenseProfile { Mii = miiToUse, FriendCode = friendCode, - Vr = BigEndianBinaryReader.BufferToUint16(_rksysData, rkpdOffset + 0xB0), - Br = BigEndianBinaryReader.BufferToUint16(_rksysData, rkpdOffset + 0xB2), - TotalRaceCount = BigEndianBinaryReader.BufferToUint32(_rksysData, rkpdOffset + 0xB4), - TotalWinCount = BigEndianBinaryReader.BufferToUint32(_rksysData, rkpdOffset + 0xDC), + Vr = BigEndianBinaryHelper.BufferToUint16(_rksysData, rkpdOffset + 0xB0), + Br = BigEndianBinaryHelper.BufferToUint16(_rksysData, rkpdOffset + 0xB2), + TotalRaceCount = BigEndianBinaryHelper.BufferToUint32(_rksysData, rkpdOffset + 0xB4), + TotalWinCount = BigEndianBinaryHelper.BufferToUint32(_rksysData, rkpdOffset + 0xDC), BadgeVariants = _whWzDataSingletonService.GetBadges(friendCode), // Region is often found near offset 0x23308 + 0x3802 in RKGD. This code is a partial guess. // In practice, region might be read differently depending on your rksys layout. - RegionId = BigEndianBinaryReader.BufferToUint16(_rksysData, 0x23308 + 0x3802) / 4096, + RegionId = (uint)BigEndianBinaryHelper.BufferToUint16(_rksysData, 0x23308 + 0x3802) / 4096, + Statistics = statistics, }; ParseFriends(user, rkpdOffset); @@ -221,11 +228,11 @@ private OperationResult ParseMiiData(int rkpdOffset) return new ArgumentNullException(nameof(_rksysData)); // licenseName is NOT always the same as mii name, could be useful - var licenseName = BigEndianBinaryReader.GetUtf16String(_rksysData, rkpdOffset + 0x14, 10); + var licenseName = BigEndianBinaryHelper.GetUtf16String(_rksysData, rkpdOffset + 0x14, 10); // id of mii - var avatarId = BigEndianBinaryReader.BufferToUint32(_rksysData, rkpdOffset + 0x28); + var avatarId = BigEndianBinaryHelper.BufferToUint32(_rksysData, rkpdOffset + 0x28); // id of the actual system - var clientId = BigEndianBinaryReader.BufferToUint32(_rksysData, rkpdOffset + 0x2C); + var clientId = BigEndianBinaryHelper.BufferToUint32(_rksysData, rkpdOffset + 0x2C); var rawMiiResult = _miiService.GetByAvatarId(avatarId); if (rawMiiResult.IsFailure) @@ -246,7 +253,7 @@ private void ParseFriends(LicenseProfile licenseProfile, int userOffset) if (!CheckForMiiData(currentOffset + 0x1A)) continue; - byte[] rawMiiBytes = _rksysData.AsSpan(currentOffset + 0x1A, MiiSize).ToArray(); + var rawMiiBytes = _rksysData.AsSpan(currentOffset + 0x1A, MiiSize).ToArray(); var friendCode = FriendCodeGenerator.GetFriendCode(_rksysData, currentOffset + 4); var miiResult = MiiSerializer.Deserialize(rawMiiBytes); if (miiResult.IsFailure) @@ -254,11 +261,11 @@ private void ParseFriends(LicenseProfile licenseProfile, int userOffset) var friend = new FriendProfile { - Vr = BigEndianBinaryReader.BufferToUint16(_rksysData, currentOffset + 0x16), - Br = BigEndianBinaryReader.BufferToUint16(_rksysData, currentOffset + 0x18), + Vr = BigEndianBinaryHelper.BufferToUint16(_rksysData, currentOffset + 0x16), + Br = BigEndianBinaryHelper.BufferToUint16(_rksysData, currentOffset + 0x18), FriendCode = friendCode, - Wins = BigEndianBinaryReader.BufferToUint16(_rksysData, currentOffset + 0x14), - Losses = BigEndianBinaryReader.BufferToUint16(_rksysData, currentOffset + 0x12), + Wins = BigEndianBinaryHelper.BufferToUint16(_rksysData, currentOffset + 0x14), + Losses = BigEndianBinaryHelper.BufferToUint16(_rksysData, currentOffset + 0x12), CountryCode = _rksysData[currentOffset + 0x68], RegionId = _rksysData[currentOffset + 0x69], BadgeVariants = _whWzDataSingletonService.GetBadges(friendCode), @@ -271,38 +278,38 @@ private void ParseFriends(LicenseProfile licenseProfile, int userOffset) public OperationResult ChangeMii(int userIndex, Mii? newMii) { if (newMii is null) - return "Mii cannot be null."; + return Fail("Mii cannot be null."); if (userIndex is < 0 or >= MaxPlayerNum) - return "Invalid license index. Please select a valid license."; + return Fail("Invalid license index. Please select a valid license."); var serialised = MiiSerializer.Serialize(newMii); if (serialised.IsFailure) - return serialised.Error!.Message; + return serialised.Error; var existing = _miiService.GetByAvatarId(newMii.MiiId); if (existing.IsFailure) - return existing.Error!.Message; + return existing.Error; var licence = Licenses.Users[userIndex]; licence.Mii = newMii; if (_rksysData is null || _rksysData.Length < RksysSize) - return "Invalid or unloaded rksys.dat data."; + return Fail("Invalid or unloaded rksys.dat data."); var rkpdOffset = 0x08 + userIndex * RkpdSize; - BigEndianBinaryReader.WriteUInt32BigEndian(_rksysData, rkpdOffset + 0x28, newMii.MiiId); // Avatar ID + BigEndianBinaryHelper.WriteUInt32BigEndian(_rksysData, rkpdOffset + 0x28, newMii.MiiId); // Avatar ID var systemid = newMii.SystemId0 << 24 | newMii.SystemId1 << 16 | newMii.SystemId2 << 8 | newMii.SystemId3; - BigEndianBinaryReader.WriteUInt32BigEndian(_rksysData, rkpdOffset + 0x2C, (uint)systemid); + BigEndianBinaryHelper.WriteUInt32BigEndian(_rksysData, rkpdOffset + 0x2C, (uint)systemid); var nameWrite = WriteLicenseNameToSaveData(userIndex, newMii.Name.ToString()); if (nameWrite.IsFailure) - return nameWrite.Error!.Message; + return nameWrite.Error; var saveResult = SaveRksysToFile(); if (saveResult.IsFailure) - return saveResult.Error!.Message; + return saveResult.Error; return Ok(); } @@ -331,7 +338,7 @@ private OperationResult ReadRksys() try { if (!_fileSystem.Directory.Exists(PathManager.SaveFolderPath)) - return "Save folder not found"; + return Fail("Save folder not found"); var currentRegion = (MarioKartWiiEnums.Regions)SettingsManager.RR_REGION.Get(); if (currentRegion == MarioKartWiiEnums.Regions.None) @@ -345,19 +352,19 @@ private OperationResult ReadRksys() } else { - return "No valid regions found"; + return Fail("No valid regions found"); } } var saveFileFolder = _fileSystem.Path.Combine(PathManager.SaveFolderPath, RRRegionManager.ConvertRegionToGameId(currentRegion)); var saveFile = _fileSystem.Directory.GetFiles(saveFileFolder, "rksys.dat", SearchOption.TopDirectoryOnly); if (saveFile.Length == 0) - return "rksys.dat not found"; + return Fail("rksys.dat not found"); return _fileSystem.File.ReadAllBytes(saveFile[0]); } catch { - return "Failed to load rksys.dat"; + return Fail("Failed to load rksys.dat"); } } @@ -398,47 +405,47 @@ public static void FixRksysCrc(byte[] rksysData) var newCrc = ComputeCrc32(rksysData, 0, lengthToCrc); // 2) Write CRC at offset 0x27FFC in big-endian. - BigEndianBinaryReader.WriteUInt32BigEndian(rksysData, 0x27FFC, newCrc); + BigEndianBinaryHelper.WriteUInt32BigEndian(rksysData, 0x27FFC, newCrc); } public OperationResult ChangeMiiName(int userIndex, string? newName) { if (string.IsNullOrWhiteSpace(newName)) - return "Cannot set name to an empty name."; + return Fail("Cannot set name to an empty name."); if (userIndex is < 0 or >= MaxPlayerNum) - return "Invalid license index. Please select a valid license."; + return Fail("Invalid license index. Please select a valid license."); var user = Licenses.Users[userIndex]; var miiIsEmptyOrNoName = IsNoNameOrEmptyMii(user); if (miiIsEmptyOrNoName) - return "This license has no Mii data or is incomplete.\n" + "Please use the Mii Channel to create a Mii first."; + return Fail("This license has no Mii data or is incomplete.\n" + "Please use the Mii Channel to create a Mii first."); if (user.Mii == null) - return "This license has no Mii data or is incomplete.\n" + "Please use the Mii Channel to create a Mii first."; + return Fail("This license has no Mii data or is incomplete.\n" + "Please use the Mii Channel to create a Mii first."); newName = Regex.Replace(newName, @"\s+", " "); // Basic checks if (newName.Length is > 10 or < 3) - return "Names must be between 3 and 10 characters long."; + return Fail("Names must be between 3 and 10 characters long."); if (newName.Length > 10) newName = newName.Substring(0, 10); var nameResult = MiiName.Create(newName); if (nameResult.IsFailure) - return nameResult.Error.Message; + return nameResult.Error; user.Mii.Name = nameResult.Value; var nameWrite = WriteLicenseNameToSaveData(userIndex, newName); if (nameWrite.IsFailure) - return nameWrite.Error.Message; + return nameWrite.Error; var updated = _miiService.UpdateName(user.Mii.MiiId, newName); if (updated.IsFailure) - return updated.Error.Message; + return updated.Error; var rksysSaveResult = SaveRksysToFile(); if (rksysSaveResult.IsFailure) - return rksysSaveResult.Error.Message; + return rksysSaveResult.Error; return Ok(); } @@ -464,7 +471,7 @@ private bool IsNoNameOrEmptyMii(LicenseProfile user) private OperationResult WriteLicenseNameToSaveData(int userIndex, string newName) { if (_rksysData == null || _rksysData.Length < RksysSize) - return "Invalid save data"; + return Fail("Invalid save data"); var rkpdOffset = 0x8 + userIndex * RkpdSize; var nameOffset = rkpdOffset + 0x14; var nameBytes = Encoding.BigEndianUnicode.GetBytes(newName); @@ -488,7 +495,7 @@ private OperationResult SaveRksysToFile() _fileSystem.File.WriteAllBytes(path, _rksysData); }); if (trySaveRksys.IsFailure) - return trySaveRksys.Error.Message; + return trySaveRksys.Error; return Ok(); } diff --git a/WheelWizard/Features/WiiManagement/GameLicense/StatisticsSerializer.cs b/WheelWizard/Features/WiiManagement/GameLicense/StatisticsSerializer.cs new file mode 100644 index 00000000..a4718999 --- /dev/null +++ b/WheelWizard/Features/WiiManagement/GameLicense/StatisticsSerializer.cs @@ -0,0 +1,172 @@ +using WheelWizard.Helpers; +using WheelWizard.WiiManagement.GameLicense.Domain.Statistics; + +namespace WheelWizard.WiiManagement.GameLicense; + +public static class StatisticsSerializer +{ + private static readonly string[] CupNames = { "Mushroom", "Flower", "Star", "Special", "Shell", "Banana", "Leaf", "Lightning" }; + + // On retro rewind these are different, but for the original game these are the engine classes. + // So whenever we display we should check if the game is retro rewind or not. + private static readonly string[] EngineClasses = { "50cc", "100cc", "150cc", "Mirror" }; + + public static LicenseStatistics ParseStatistics(byte[] rksysData, int rkpdOffset) + { + return new() + { + RaceTotals = ParseRaceTotals(rksysData, rkpdOffset), + Performance = ParsePerformance(rksysData, rkpdOffset), + PreferredControls = ParsePreferredControls(rksysData, rkpdOffset), + RaceCompletions = ParseRaceCompletions(rksysData, rkpdOffset), + BattleCompletions = ParseBattleCompletions(rksysData, rkpdOffset), + TotalCompetitions = (ushort)BigEndianBinaryHelper.BufferToUint16(rksysData, rkpdOffset + 0xE8), + Trophies = ParseTrophyCabinet(rksysData, rkpdOffset), + }; + } + + private static RaceTotals ParseRaceTotals(byte[] rksysData, int rkpdOffset) + { + return new() + { + AllRacesCount = BigEndianBinaryHelper.BufferToUint32(rksysData, rkpdOffset + 0xB4), + BattleMatches = BigEndianBinaryHelper.BufferToUint32(rksysData, rkpdOffset + 0xB8), + GhostChallengesSent = (int)BigEndianBinaryHelper.BufferToUint32(rksysData, rkpdOffset + 0xC8), + GhostChallengesReceived = (int)BigEndianBinaryHelper.BufferToUint32(rksysData, rkpdOffset + 0xCC), + WinsVsLosses = new() + { + OfflineVs = new() + { + Wins = BigEndianBinaryHelper.BufferToUint32(rksysData, rkpdOffset + 0x88), + Losses = BigEndianBinaryHelper.BufferToUint32(rksysData, rkpdOffset + 0x8C), + }, + OfflineBattle = new() + { + Wins = BigEndianBinaryHelper.BufferToUint32(rksysData, rkpdOffset + 0x90), + Losses = BigEndianBinaryHelper.BufferToUint32(rksysData, rkpdOffset + 0x94), + }, + OnlineVs = new() + { + Wins = BigEndianBinaryHelper.BufferToUint32(rksysData, rkpdOffset + 0x98), + Losses = BigEndianBinaryHelper.BufferToUint32(rksysData, rkpdOffset + 0x9C), + }, + OnlineBattle = new() + { + Wins = BigEndianBinaryHelper.BufferToUint32(rksysData, rkpdOffset + 0xA0), + Losses = BigEndianBinaryHelper.BufferToUint32(rksysData, rkpdOffset + 0xA4), + }, + }, + }; + } + + private static Performance ParsePerformance(byte[] rksysData, int rkpdOffset) + { + return new() + { + ItemHitsDealt = (int)BigEndianBinaryHelper.BufferToUint32(rksysData, rkpdOffset + 0xD0), + ItemHitsReceived = (int)BigEndianBinaryHelper.BufferToUint32(rksysData, rkpdOffset + 0xD4), + TricksPerformed = (int)BigEndianBinaryHelper.BufferToUint32(rksysData, rkpdOffset + 0xD8), + FirstPlaces = (int)BigEndianBinaryHelper.BufferToUint32(rksysData, rkpdOffset + 0xDC), + DistanceTotal = BigEndianBinaryHelper.BufferToFloat(rksysData, rkpdOffset + 0xC4), + DistanceInFirstPlace = BigEndianBinaryHelper.BufferToFloat(rksysData, rkpdOffset + 0xE0), + DistanceVsRaces = BigEndianBinaryHelper.BufferToFloat(rksysData, rkpdOffset + 0xE4), + }; + } + + private static PreferredControls ParsePreferredControls(byte[] rksysData, int rkpdOffset) + { + var driftByte = rksysData[rkpdOffset + 0xEA]; + var driftValue = (driftByte >> 0) & 0x03; // Get first 2 bits + var driftType = driftValue switch + { + 1 => DriftType.Manual, + 2 => DriftType.Automatic, + _ => DriftType.Standard, + }; + + return new PreferredControls + { + WiiWheelRaces = (int)BigEndianBinaryHelper.BufferToUint32(rksysData, rkpdOffset + 0xBC), + WiiWheelBattles = (int)BigEndianBinaryHelper.BufferToUint32(rksysData, rkpdOffset + 0xC0), + PreferredDriftType = driftType, + }; + } + + private static RaceCompletions ParseRaceCompletions(byte[] rksysData, int rkpdOffset) + { + var completions = new RaceCompletions(); + + // Favorite Character (0xEC, 24 entries) + var charBaseOffset = rkpdOffset + 0xEC; + foreach (Character character in Enum.GetValues(typeof(Character))) + { + var offset = charBaseOffset + (int)character * 2; + completions.CharacterCompletions[character] = (int)BigEndianBinaryHelper.BufferToUint16(rksysData, offset); + } + + // Favorite Vehicle (0x11E, 36 entries) + var vehicleBaseOffset = rkpdOffset + 0x11E; + foreach (Vehicle vehicle in Enum.GetValues(typeof(Vehicle))) + { + var offset = vehicleBaseOffset + (int)vehicle * 2; + completions.Vehicle[vehicle] = (int)BigEndianBinaryHelper.BufferToUint16(rksysData, offset); + } + + // Favorite Course (0x166, 32 entries) + var courseBaseOffset = rkpdOffset + 0x166; + foreach (Course course in Enum.GetValues(typeof(Course))) + { + var offset = courseBaseOffset + (int)course * 2; + completions.Course[course] = (int)BigEndianBinaryHelper.BufferToUint16(rksysData, offset); + } + + return completions; + } + + private static BattleCompletions ParseBattleCompletions(byte[] rksysData, int rkpdOffset) + { + var completions = new BattleCompletions(); + + // Favorite Stage (0x1A6, 10 entries) + var stageBaseOffset = rkpdOffset + 0x1A6; + foreach (Stage stage in Enum.GetValues(typeof(Stage))) + { + var offset = stageBaseOffset + (int)stage * 2; + completions.Stage[stage] = (int)BigEndianBinaryHelper.BufferToUint16(rksysData, offset); + } + return completions; + } + + private static TrophyCabinet ParseTrophyCabinet(byte[] rksysData, int rkpdOffset) + { + var cabinet = new TrophyCabinet(); + var cupDataStartOffset = rkpdOffset + 0x1C0; + const int cupBlockSize = 0x60; + + var cupIndex = 0; + foreach (var engineClass in EngineClasses) + { + foreach (var cupName in CupNames) + { + var cupOffset = cupDataStartOffset + (cupIndex * cupBlockSize); + + var trophyByte = rksysData[cupOffset + 0x4F]; + var rankByte = rksysData[cupOffset + 0x51]; + var completedByte = rksysData[cupOffset + 0x52]; + + var info = new TrophyInfo + { + CupType = (CupTrophyType)(trophyByte & 0x03), + Rank = (CupRank)((rankByte >> 4) & 0x0F), + Completed = (completedByte & 0x01) == 1, + }; + + var key = $"{cupName} Cup ({engineClass})"; + cabinet.PerCup[key] = info; + + cupIndex++; + } + } + return cabinet; + } +} diff --git a/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/Custom/CustomBitsCodec.cs b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/Custom/CustomBitsCodec.cs new file mode 100644 index 00000000..8e92b86d --- /dev/null +++ b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/Custom/CustomBitsCodec.cs @@ -0,0 +1,210 @@ +using System.Collections.Generic; +using System.Linq; +using WheelWizard.Helpers; + +namespace WheelWizard.WiiManagement.MiiManagement.Domain.Mii.Custom; + +/// +/// Handles the low-level reading and writing of the 24 custom bits spread across +/// different locations within the 74-byte raw Mii data format. +/// It ensures that only the designated "unknown" and usable bits are touched, preserving +/// the official Mii data structure and avoiding "DO NOT USE" bits. +/// +/// Extraction (`Extract`): Reads the relevant sections of the Mii data, isolates the specific +/// usable unused bits, and packs them together into a single 24-bit `uint`. +/// Injection (`Inject`): Takes a 24-bit `uint` payload, unpacks it into fragments, reads the +/// relevant Mii data sections, clears only the usable unused bits, and writes +/// the fragments back into their original locations. +/// +/// Usable bits mapping (LSB 0-indexed within their respective data chunks): +/// - 0x20–0x21 (Face Data, ushort): +/// - unknown_0 (3 bits): Bits 3,4,5. Mask 0x0038. +/// - unknown_1 (1 bit): Bit 1. Mask 0x0002. +/// - 0x22–0x23 (Hair Data, ushort): +/// - unknown_2 (5 bits): Bits 0,1,2,3,4. Mask 0x001F. +/// - 0x24–0x27 (Eyebrow Data, uint): +/// - unknown_4 (6 bits): Bits 16,17,18,19,20,21. Mask 0x003F0000. +/// (unknown_3 at bit 26 is DO NOT USE) +/// - 0x28–0x2B (Eye Data, uint): +/// - unknown_7 (5 bits): Bits 0,1,2,3,4. Mask 0x0000001F. +/// (unknown_5 at bits 24,25 and unknown_6 at bit 12 are DO NOT USE) +/// - 0x2C–0x2D (Nose Data, ushort): +/// - unknown_8 (3 bits): Bits 0,1,2. Mask 0x0007. +/// - 0x34–0x35 (Mole Data, ushort): +/// - unknown_10 (1 bit): Bit 0. Mask 0x0001. +/// +/// Total usable bits: 3 + 1 + 5 + 6 + 5 + 3 + 1 = 24 bits. +/// +internal static class CustomBitsCodec +{ + // Define constants for the byte offsets within the 74-byte Mii array + private const int OFS_FACE = 0x20; // unknown_0, unknown_1 + private const int OFS_HAIR = 0x22; // unknown_2 + private const int OFS_BROW = 0x24; // unknown_4 (unknown_3 is not used) + private const int OFS_EYE = 0x28; // unknown_7 (unknown_5, unknown_6 are not used) + private const int OFS_NOSE = 0x2C; // unknown_8 + private const int OFS_MOLE = 0x34; // unknown_10 + + // Masks for the USABLE unknown bits within their respective data chunks (ushort or uint) + // For Face Data (OFS_FACE): + private const ushort MASK_U0_FACE = 0x0038; // Bits 3,4,5 for unknown_0 + private const ushort MASK_U1_FACE = 0x0002; // Bit 1 for unknown_1 + private const ushort MASK_FACE_COMBINED = MASK_U0_FACE | MASK_U1_FACE; // 0x003A + + // For Hair Data (OFS_HAIR): + private const ushort MASK_U2_HAIR = 0x001F; // Bits 0-4 for unknown_2 + + // For Eyebrow Data (OFS_BROW): + private const uint MASK_U4_BROW = 0x003F0000; // Bits 16-21 for unknown_4 + + // For Eye Data (OFS_EYE): + private const uint MASK_U7_EYE = 0x0000001F; // Bits 0-4 for unknown_7 + + // For Nose Data (OFS_NOSE): + private const ushort MASK_U8_NOSE = 0x0007; // Bits 0-2 for unknown_8 + + // For Mole Data (OFS_MOLE): + private const ushort MASK_U10_MOLE = 0x0001; // Bit 0 for unknown_10 + + // Defines the width (in bits) of each USABLE unknown fragment, in the order they are packed. + // This MUST match the order of operations in Extract() and Inject(). + // Total width must sum to 24 bits. + // Order: U0, U1, U2, U4, U7, U8, U10 + private static readonly int[] _widths = { 3, 1, 5, 6, 5, 3, 1 }; // Sum = 24 + + /// + /// Extracts the 24 designated usable unused bits from the raw Mii data and packs them + /// into a single right-aligned 24-bit unsigned integer. + /// + /// The 74-byte array containing Mii data. + internal static uint Extract(byte[] rawMiiBytes) + { + var faceData = BigEndianBinaryHelper.BufferToUint16(rawMiiBytes, OFS_FACE); + var hairData = BigEndianBinaryHelper.BufferToUint16(rawMiiBytes, OFS_HAIR); + var browData = BigEndianBinaryHelper.BufferToUint32(rawMiiBytes, OFS_BROW); + var eyeData = BigEndianBinaryHelper.BufferToUint32(rawMiiBytes, OFS_EYE); + var noseData = BigEndianBinaryHelper.BufferToUint16(rawMiiBytes, OFS_NOSE); + var moleData = BigEndianBinaryHelper.BufferToUint16(rawMiiBytes, OFS_MOLE); + + uint payload = 0; + var cursor = 0; // Tracks the next bit position to write into the payload (LSB). + + // Fragment 1: unknown_0 (3 bits from faceData, LSB bits 3-5) + var frag_u0 = (uint)((faceData & MASK_U0_FACE) >> 3); + WriteFragment(ref payload, ref cursor, frag_u0, _widths[0]); // 3 bits + + // Fragment 2: unknown_1 (1 bit from faceData, LSB bit 1) + var frag_u1 = (uint)((faceData & MASK_U1_FACE) >> 1); + WriteFragment(ref payload, ref cursor, frag_u1, _widths[1]); // 1 bit + + // Fragment 3: unknown_2 (5 bits from hairData, LSB bits 0-4) + var frag_u2 = (uint)(hairData & MASK_U2_HAIR); + WriteFragment(ref payload, ref cursor, frag_u2, _widths[2]); // 5 bits + + // Fragment 4: unknown_4 (6 bits from browData, LSB bits 16-21) + // (unknown_3 is skipped as it's DO NOT USE) + var frag_u4 = (browData & MASK_U4_BROW) >> 16; + WriteFragment(ref payload, ref cursor, frag_u4, _widths[3]); // 6 bits + + // Fragment 5: unknown_7 (5 bits from eyeData, LSB bits 0-4) + // (unknown_5 and unknown_6 are skipped as they are DO NOT USE) + var frag_u7 = eyeData & MASK_U7_EYE; + WriteFragment(ref payload, ref cursor, frag_u7, _widths[4]); // 5 bits + + // Fragment 6: unknown_8 (3 bits from noseData, LSB bits 0-2) + var frag_u8 = (uint)(noseData & MASK_U8_NOSE); + WriteFragment(ref payload, ref cursor, frag_u8, _widths[5]); // 3 bits + + // Fragment 7: unknown_10 (1 bit from moleData, LSB bit 0) + var frag_u10 = (uint)(moleData & MASK_U10_MOLE); + WriteFragment(ref payload, ref cursor, frag_u10, _widths[6]); // 1 bit + + return payload; + } + + /// + /// Injects a 24-bit custom data payload back into the designated usable unused bit locations + /// within a raw 74-byte Mii data array. Modifies the array in place. + /// Only bits corresponding to U0, U1, U2, U4, U7, U8, U10 are modified. + /// + /// The 74-byte array representing Mii data. This array will be modified. + /// The 24-bit custom data payload (stored in the lower bits of the uint). + internal static void Inject(byte[] rawMiiBytes, uint payload) + { + var fragments = Decompose(payload).ToArray(); + var fragIndex = 0; + + // Face Data (U0, U1) + var faceVal = (ushort)BigEndianBinaryHelper.BufferToUint16(rawMiiBytes, OFS_FACE); + faceVal = (ushort)( + (faceVal & ~MASK_FACE_COMBINED) // Clear U0 and U1 bits + | (fragments[fragIndex++] << 3) // Write U0 (3 bits) to LSB bits 3,4,5 + | (fragments[fragIndex++] << 1) + ); // Write U1 (1 bit) to LSB bit 1 + BigEndianBinaryHelper.WriteUInt16BigEndian(rawMiiBytes, OFS_FACE, faceVal); + + // Hair Data (U2) + var hairVal = (ushort)BigEndianBinaryHelper.BufferToUint16(rawMiiBytes, OFS_HAIR); + hairVal = (ushort)( + (hairVal & ~MASK_U2_HAIR) // Clear U2 bits + | fragments[fragIndex++] + ); // Write U2 (5 bits) to LSB bits 0-4 + BigEndianBinaryHelper.WriteUInt16BigEndian(rawMiiBytes, OFS_HAIR, hairVal); + + // Eyebrow Data (U4) - unknown_3 is NOT touched + var browVal = BigEndianBinaryHelper.BufferToUint32(rawMiiBytes, OFS_BROW); + browVal = + (browVal & ~MASK_U4_BROW) // Clear U4 bits + | (fragments[fragIndex++] << 16); // Write U4 (6 bits) to LSB bits 16-21 + BigEndianBinaryHelper.WriteUInt32BigEndian(rawMiiBytes, OFS_BROW, browVal); + + // Eye Data (U7) - unknown_5 and unknown_6 are NOT touched + var eyeVal = BigEndianBinaryHelper.BufferToUint32(rawMiiBytes, OFS_EYE); + eyeVal = + (eyeVal & ~MASK_U7_EYE) // Clear U7 bits + | fragments[fragIndex++]; // Write U7 (5 bits) to LSB bits 0-4 + BigEndianBinaryHelper.WriteUInt32BigEndian(rawMiiBytes, OFS_EYE, eyeVal); + + // Nose Data (U8) + var noseVal = BigEndianBinaryHelper.BufferToUint16(rawMiiBytes, OFS_NOSE); + noseVal = (ushort)( + (noseVal & ~MASK_U8_NOSE) // Clear U8 bits + | fragments[fragIndex++] + ); // Write U8 (3 bits) to LSB bits 0-2 + BigEndianBinaryHelper.WriteUInt16BigEndian(rawMiiBytes, OFS_NOSE, noseVal); + + // Mole Data (U10) + var moleVal = BigEndianBinaryHelper.BufferToUint16(rawMiiBytes, OFS_MOLE); + moleVal = (ushort)( + (moleVal & ~MASK_U10_MOLE) // Clear U10 bit + | fragments[fragIndex++] + ); // Write U10 (1 bit) to LSB bit 0 + BigEndianBinaryHelper.WriteUInt16BigEndian(rawMiiBytes, OFS_MOLE, moleVal); + } + + /// + /// Writes a fragment of data (value) of a specific width into a destination uint (dst) + /// at the current bit position (cursor). Used during the packing process in Extract(). + /// + private static void WriteFragment(ref uint dst, ref int cursor, uint value, int width) + { + var shiftedValue = value << cursor; + dst |= shiftedValue; + cursor += width; + } + + /// + /// Decomposes a packed payload (src) into its constituent fragments + /// based on the predefined widths in the `_widths` array. Used during the injection process. + /// + private static IEnumerable Decompose(uint src) + { + var cursor = 0; + foreach (var width in _widths) + { + var mask = (1u << width) - 1u; + yield return (src >> cursor) & mask; + cursor += width; + } + } +} diff --git a/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/Custom/CustomMiiData.cs b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/Custom/CustomMiiData.cs new file mode 100644 index 00000000..9c3a00a6 --- /dev/null +++ b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/Custom/CustomMiiData.cs @@ -0,0 +1,415 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace WheelWizard.WiiManagement.MiiManagement.Domain.Mii.Custom; + +/// +/// Provides a structured way to access and modify the 24 "unknown" or unused bits +/// found within the standard 74-byte Mii data format. This allows storing custom data +/// without breaking compatibility with standard Mii readers. +/// +/// The 24 bits are laid out as follows by this class: +/// • Bits 0–2 : Schema version. This helps manage different layouts of the custom data over time. +/// • Bits 3–24 : Available for custom fields defined as properties below. These are automatically +/// packed based on their declaration order and specified [BitField] width. +/// +public sealed class CustomMiiData +{ + // Defines the current version of the custom data layout. + // Increment this (1-7, then loop) if the layout of properties below changes, + // allowing older versions to recognize or ignore newer data formats. + private const int SchemaVersion = 1; + private const byte VersionCycleLength = 7; // 3-bit schema cycles over 1-7 + private const int MaxMigrationHops = 5; // beyond this, data is treated as stale + private const uint VersionMask = (1u << 3) - 1u; + + #region Layout Metadata + + // The total number of bits available for custom data within the Mii format's unused sections. + // This is a hard limit, Never change this! + private const int TotalBits = 24; + + // Stores the packed custom data as a 32-bit unsigned integer. + // Only the lower 24 bits are used. Bit 0 is the least significant bit (LSB). + private uint _payload; + + // A helper record to store metadata about each property marked with [BitField]. + // Prop: The reflection PropertyInfo object itself. + // Offset: The starting bit position of this field within the 24-bit payload (0-27). + // Width: The number of bits allocated to this field. + // Mask: A pre-calculated bitmask to easily isolate/find or clear this field's bits within the payload. + private sealed record FieldMeta(PropertyInfo Prop, int Offset, int Width, uint Mask); + + // A dictionary mapping property names (e.g., "Version", "IsCopyable") to their calculated metadata. + // This is built once using reflection in the static constructor. + private static readonly IReadOnlyDictionary _meta; + + // Static constructor: This runs once when the CustomMiiData class is first used. + // Its purpose is to automatically determine the layout of the custom bit fields. + static CustomMiiData() + { + // Get all public and non-public instance properties of this class. + var properties = typeof(CustomMiiData).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + + // Filter to find only properties that have the [BitField] attribute. + var bitFieldProperties = properties.Where(p => p.GetCustomAttribute() is not null); + + // Order properties primarily by the optional [BitField(Order = X)] value, + // falling back to declaration order (MetadataToken) to keep layout stable. + var orderedProperties = bitFieldProperties + .Select(p => new { Prop = p, Attr = p.GetCustomAttribute()! }) + .OrderBy(p => p.Attr.Order) + .ThenBy(p => p.Prop.MetadataToken) + .ToArray(); + + // Prepare a list to hold the metadata for each field. + var fieldMetadataList = new List(); + // Initialize a cursor to keep track of the next available bit position. + var currentBitOffset = 0; + + // Iterate through the ordered properties to calculate their position and mask. + foreach (var item in orderedProperties) + { + var prop = item.Prop; + var attr = item.Attr; + + // Get the width specified in the [BitField] attribute for this property. + var width = attr.Width; + + // Validate the specified width. + if (width is <= 0 or > TotalBits) + throw new InvalidOperationException($"Bit width for '{prop.Name}' must be between 1 and {TotalBits}."); + + // Check if adding this property exceeds the total available bits. + if (currentBitOffset + width > TotalBits) + throw new InvalidOperationException( + $"Layout overflow - Adding '{prop.Name}' ({width} bits) exceeds the {TotalBits}-bit budget. Current offset: {currentBitOffset}." + ); + + // Calculate the bitmask for this field. + // 1. Create a value with 'width' number of 1s (e.g., width 3 -> 0b111). + // This is done by shifting 1 left by 'width' (1 << width gives 0b1000 for width 3) + // and subtracting 1 ((1u << width) - 1u gives 0b0111). + // 2. Shift this mask left by 'currentBitOffset' to position it correctly within the payload. + // e.g., if offset is 5 and width is 3, mask is 0b0111 << 5 = 0b11100000. + var mask = ((1u << width) - 1u) << currentBitOffset; + + // Create the metadata record for this property and add it to the list. + fieldMetadataList.Add(new FieldMeta(prop, currentBitOffset, width, mask)); + + // Advance the cursor by the width of the current field. + currentBitOffset += width; + } + + // if not all 24 bits were used by the defined properties. + if (currentBitOffset < TotalBits) + { + //for now do nothing but we could add stuff here + } + + // Store the generated metadata list as a dictionary, keyed by property name for easy lookup. + _meta = fieldMetadataList.ToDictionary(m => m.Prop.Name, m => m); + } + + #endregion + + // ────────────────────────────────────────── PROPERTIES ────────────────────────────────────────── + // GO HERE IF YOU WANT TO CHANGE THE CUSTOM MII DATA WITHOUT WORRYING ABOUT THE INTERNALS!!!!!!!!!!! + + /// + /// Gets or sets the 3-bit schema version (bits 0-2). + /// This should always be the first field defined. + /// The setter is private to ensure its only set internally (e.g., in CreateEmpty). + /// + [BitField(3, Order = 0)] + public byte Version + { + get => (byte)GetField(); + set => SetField(value); + } + + /// + /// Gets or sets whether the Mii is allowed to be copied from other consoles (1 bit). + /// Uses a boolean for easy access, mapping to 1 (true) or 0 (false). + /// + [BitField(1, Order = 1)] + public bool IsCopyable + { + get => GetField() != 0; // Read the 1-bit value; non-zero means true. + set => SetField(value ? 1u : 0u); // Write 1 if true, 0 if false. + } + + /// + /// Gets or sets a sample 4-bit colour value (0-15). + /// The width could be changed if needed. + /// + [BitField(4, Order = 2)] + public MiiProfileColor AccentColor + { + get => (MiiProfileColor)GetField(); + set => SetField((uint)value); + } + + /// + /// Gets or sets eight individual feature flags packed into 3 bits + /// + [BitField(3, Order = 3)] + public MiiPreferredFacialExpression FacialExpression + { + get => (MiiPreferredFacialExpression)GetField(); + set => SetField((uint)value); + } + + [BitField(2, Order = 4)] + public MiiPreferredCameraAngle CameraAngle + { + get => (MiiPreferredCameraAngle)GetField(); + set => SetField((uint)value); + } + + [BitField(5, Order = 5)] + public MiiPreferredTagline Tagline + { + get => (MiiPreferredTagline)GetField(); + set => SetField((uint)value); + } + + // Add new properties here. + // Simply declare them with a [BitField(width)] attribute. + // They will be automatically allocated space in the payload after the 'Spare' field, + // provided the total width does not exceed 24 bits. The static constructor handles layout. + + [BitField(6, Order = 6)] + public ushort Spare + { + get => (ushort)GetField(); + set => SetField(value); + } + + #region Constructors + + /// + /// Private constructor used internally to create an instance with a given payload. + /// + /// The packed 24-bit data. + private CustomMiiData(uint payload) => _payload = payload; + + /// + /// Creates a instance by extracting the 24 custom bits + /// from a raw 74-byte Mii data block. + /// + public static CustomMiiData FromBytes(byte[] rawMiiBytes) + { + var result = CustomBitsCodec.Extract(rawMiiBytes); + return FromPayload(result); + } + + /// + /// Creates a instance by extracting the 24 custom bits + /// from a object. + /// This involves serializing the Mii object to bytes first. + /// + public static CustomMiiData FromMii(MiiManagement.Domain.Mii.Mii mii) + { + var serializeResult = MiiSerializer.Serialize(mii); + if (!serializeResult.IsSuccess) + throw new InvalidOperationException("Failed to serialize Mii object to extract custom data."); + return FromPayload(CustomBitsCodec.Extract(serializeResult.Value)); + } + + private static CustomMiiData FromPayload(uint rawpayload) + { + var diskVersion = (byte)(rawpayload & VersionMask); + + // Fast path: payload already matches our schema. + if (diskVersion == SchemaVersion) + return new(rawpayload); + + // Version byte missing/zero -> accept as-is; nothing to migrate. + if (diskVersion == 0) + return new(rawpayload); + + if (!TryComputeForwardDistance(diskVersion, (byte)SchemaVersion, out var distance)) + return CreateEmpty(); + + // If the payload is more than 5 migrations behind, treat it as stale and discard. + if (distance >= MaxMigrationHops) + return CreateEmpty(); + + // Walk forward version-by-version until we reach the current schema. + var migratedPayload = rawpayload; + var currentVersion = diskVersion; + + for (var step = 0; step < distance; step++) + { + if (!TryMigrateFromVersion(currentVersion, migratedPayload, out var nextPayload)) + return CreateEmpty(); + + migratedPayload = nextPayload; + currentVersion = NextVersion(currentVersion); + } + + // Ensure the version bits are aligned to the current schema. + migratedPayload = WithVersion(migratedPayload, (byte)SchemaVersion); + return new(migratedPayload); + } + + /// + /// Takes a payload encoded with “version N” and produces a payload encoded with “version N+1”. + /// Each case handles the bit‐shuffling or defaulting needed to move from one schema to the next. + /// + /// The 24‐bit data block from an older schema version. + /// The schema version of that payload. + private static bool TryMigrateFromVersion(byte oldVersion, uint oldPayload, out uint migratedPayload) + { + var nextVersion = NextVersion(oldVersion); + + switch (oldVersion) + { + case 1: // to version 2 + // No layout changes yet; simply bump the version. + migratedPayload = WithVersion(oldPayload, nextVersion); + return true; + + // case 2: // to version 3 + // // Migration logic for version 3 goes here... + // break; + + default: + migratedPayload = oldPayload; + return false; + } + } + + /// + /// Advances a version value, wrapping at 7 back to 1 (since the field is 3 bits wide). + /// + private static byte NextVersion(byte version) => version >= VersionCycleLength ? (byte)1 : (byte)(version + 1); + + /// + /// Calculates how many forward steps (wrapping 1→7→1) it takes to reach targetVersion from fromVersion. + /// Returns false for invalid inputs (zero) to signal unsupported/unknown payloads. + /// + private static bool TryComputeForwardDistance(byte fromVersion, byte targetVersion, out int distance) + { + distance = 0; + + if (fromVersion == 0 || targetVersion == 0) + return false; + + var cursor = fromVersion; + while (cursor != targetVersion && distance <= VersionCycleLength) + { + cursor = NextVersion(cursor); + distance++; + } + + return cursor == targetVersion; + } + + /// + /// Replaces the 3-bit version field inside a payload without touching other bits. + /// + private static uint WithVersion(uint payload, byte version) => (payload & ~VersionMask) | (version & VersionMask); + + /// + /// Creates a new instance with all custom bits initially set to zero, + /// but with the field automatically set to the current . + /// + /// A new, default-initialized instance. + public static CustomMiiData CreateEmpty() => new(0) { Version = SchemaVersion }; + + #endregion + + #region SerializeMethods + + /// + /// Applies the current custom data payload (_payload) to a given object. + /// This method returns a *new* Mii object instance with the changes applied, leaving the original untouched. + /// + /// The original Mii object. + public Mii ApplyTo(Mii mii) + { + // Serialize the input Mii to get its byte representation. + // The serializer should handle cloning or creating a safe copy. + var serializeResult = MiiSerializer.Serialize(mii); + if (!serializeResult.IsSuccess) + throw new InvalidOperationException("Failed to serialize Mii object to apply custom data."); // Or handle error + var bytes = serializeResult.Value; + CustomBitsCodec.Inject(bytes, _payload); + var deserializeResult = MiiSerializer.Deserialize(bytes); + if (!deserializeResult.IsSuccess || deserializeResult.Value is null) + throw new InvalidOperationException("Failed to deserialize Mii data after injecting custom payload."); // Or handle error + + return deserializeResult.Value; + } + + /// + /// Injects the current custom data payload (_payload) directly into a raw 74-byte Mii data block. + /// This method modifies the provided byte array in place. + /// + public void ApplyTo(byte[] rawMiiBlock) => CustomBitsCodec.Inject(rawMiiBlock, _payload); + #endregion + + #region HelperMethods + + + /// + /// Gets the value of the property that called this method. + /// It uses reflection metadata (_meta) to find the correct bits in the payload. + /// + /// The name of the calling property, automatically supplied by the compiler. + /// The value of the requested field, extracted from the _payload. + private uint GetField([CallerMemberName] string? propName = null) + { + // Look up the metadata (offset, width, mask) for the property. + var meta = _meta[propName!]; // Assumes propName is always valid due to CallerMemberName. + + // Apply the mask to the payload to isolate the bits for this field. + // (e.g., payload & 0b11100000 isolates bits 5-7 if that's the mask). + var isolatedBits = _payload & meta.Mask; + + // Right-shift the isolated bits by the field's offset to align them to the LSB. + // (e.g., if isolatedBits is 0b10100000 and offset is 5, result is 0b101). + return isolatedBits >> meta.Offset; + } + + /// + /// Sets the value of the property that called this method. + /// It uses reflection metadata (_meta) to place the value into the correct bits in the payload. + /// + /// The value to set for the field. + /// The name of the calling property, automatically supplied by the compiler. + private void SetField(uint value, [CallerMemberName] string? propName = null) + { + if (propName != nameof(Version) && GetField(nameof(Version)) == 0) + { + SetField(SchemaVersion, nameof(Version)); + } + // Look up the metadata for the property. + var meta = _meta[propName!]; + + // Validate that the provided value fits within the allocated number of bits. + // Calculate the maximum possible value for the given width (2^width - 1). + var maxValue = (1u << meta.Width) - 1; + if (value > maxValue) + { + // ReSharper disable once LocalizableElement + throw new ArgumentOutOfRangeException(propName, $"Value {value} exceeds the {meta.Width}-bit limit (max {maxValue})."); + } + + // Update the payload: + // 1. Clear the bits for this field in the current payload using the inverted mask. + // (e.g., _payload & ~0b11100000 clears bits 5-7). + var clearedPayload = _payload & ~meta.Mask; + + // 2. Left-shift the new value by the field's offset to position it correctly. + // (e.g., if value is 0b101 and offset is 5, shifted value is 0b10100000). + var shiftedValue = value << meta.Offset; + + // 3. Combine the cleared payload with the shifted new value using bitwise OR. + // (e.g., clearedPayload | 0b10100000 inserts the new value). + _payload = clearedPayload | shiftedValue; + } + #endregion +} diff --git a/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/Custom/FieldAttribute.cs b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/Custom/FieldAttribute.cs new file mode 100644 index 00000000..89785e4d --- /dev/null +++ b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/Custom/FieldAttribute.cs @@ -0,0 +1,26 @@ +namespace WheelWizard.WiiManagement.MiiManagement.Domain.Mii.Custom; + +/// +/// This custom attribute is used to mark properties within the CustomMiiData class. +/// It specifies how many bits that property occupies within the packed 28-bit custom data payload. +/// +[AttributeUsage(AttributeTargets.Property)] // Specifies that this attribute can only be applied to properties. +internal sealed class BitFieldAttribute : Attribute +{ + /// + /// Gets the number of bits allocated to the property decorated with this attribute. + /// + public int Width { get; } + + /// + /// Optional explicit ordering for packing; lower values are packed first. + /// Defaults to source/declaration order when not set. + /// + public int Order { get; set; } = int.MaxValue; + + /// + /// Initializes a new instance of the class. + /// + /// The number of bits the associated property will occupy. + public BitFieldAttribute(int width) => Width = width; +} diff --git a/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/Custom/MiiCustomEnums.cs b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/Custom/MiiCustomEnums.cs new file mode 100644 index 00000000..3791f431 --- /dev/null +++ b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/Custom/MiiCustomEnums.cs @@ -0,0 +1,84 @@ +namespace WheelWizard.WiiManagement.MiiManagement.Domain.Mii.Custom; + +//2 bits so only 4 values +public enum MiiPreferredCameraAngle : uint +{ + None = 0, + CameraAngle1 = 1, + CameraAngle2 = 2, + CameraAngle3 = 3, +} + +//this value is only stored in 3 bits, so there can be only 8 values +public enum MiiPreferredFacialExpression : uint +{ + None = 0, + FacialExpression1 = 1, + FacialExpression2 = 2, + FacialExpression3 = 3, + FacialExpression4 = 4, + FacialExpression5 = 5, + FacialExpression6 = 6, + FacialExpression7 = 7, +} + +/// +/// Enumeration representing the color of a Mii profile. +/// This only takes up 4 bits, so in total 15 colors are possible. (+1 for none) +/// +public enum MiiProfileColor : uint +{ + None = 0, + Color1 = 1, + Color2 = 2, + Color3 = 3, + Color4 = 4, + Color5 = 5, + Color6 = 6, + Color7 = 7, + Color8 = 8, + Color9 = 9, + Color10 = 10, + Color11 = 11, + Color12 = 12, + Color13 = 13, + Color14 = 14, + Color15 = 15, +} + +//this is 5 bits so it can be 0-31 +public enum MiiPreferredTagline +{ + None = 0, //Off + Tagline1 = 1, // Hey there! I am using WheelWizard. (default) + Tagline2 = 2, // "hello world" + Tagline3 = 3, // game on! + Tagline4 = 4, // gotta go fast + Tagline5 = 5, // UwU + Tagline6 = 6, // You found me! + Tagline7 = 7, // how did we get here? + Tagline8 = 8, // return to sender + Tagline9 = 9, // loading... + Tagline10 = 10, // Funky FTW + Tagline11 = 11, // meow meow + Tagline12 = 12, // Tonight... We steal the moon! + Tagline13 = 13, // ok + Tagline14 = 14, // BRB, grabbing snacks + Tagline15 = 15, // Ctrl + Alt + Mii + Tagline16 = 16, // Mii think therefore Mii am + Tagline17 = 17, // To Mii or not to Mii, that is the question + Tagline18 = 18, + Tagline19 = 19, + Tagline20 = 20, + Tagline21 = 21, + Tagline22 = 22, + Tagline23 = 23, + Tagline24 = 24, + Tagline25 = 25, + Tagline26 = 26, + Tagline27 = 27, + Tagline28 = 28, + Tagline29 = 29, + Tagline30 = 30, + Tagline31 = 31, +} diff --git a/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/Mii.cs b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/Mii.cs new file mode 100644 index 00000000..f28fe9bb --- /dev/null +++ b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/Mii.cs @@ -0,0 +1,188 @@ +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii.Custom; + +namespace WheelWizard.WiiManagement.MiiManagement.Domain.Mii; + +/* +Mii data Structure from the wii looks like this: +0x00-0x01 : + - Bit 0 : invalid + - Bit 1 : isGirl + - Bits 2–5 : month (1–12) + - Bits 6–10: day (1–31) + - Bits 11–14: favColor (0–11+) + - Bit 15 : isFavorite + +0x02–0x15 : name (10 UTF-16 characters, 20 bytes total) +0x16 : height (0–127) +0x17 : weight (0–127) + +(timestamp-based unique ID) includes "special" flags in the top 3 bits (maybe top 4 which would make the entire MiiID1 responsible for the Mii type) +0x18 : miiID1 (part 1) +0x19 : miiID2 (part 2) +0x1A : miiID3 (part 3; top 3 bits = special/foreign/regular) +0x1B : miiID4 (part 4) + +0x1C : systemID0 (MAC hash checksum byte) +0x1D : systemID1 (MAC byte 4) +0x1E : systemID2 (MAC byte 5) +0x1F : systemID3 (MAC byte 6) + +0x20–0x21 : + - Bits 0–2 : faceShape + - Bits 3–5 : skinColor + - Bits 6–9 : facialFeature + - Bits 10–12 : unknown_0 (3 bits long) + - Bit 13 : mingleOff (1 = don't mingle) + - Bit 14 : unknown_1 (1 bit long) + - Bit 15 : downloaded (1 = downloaded Mii) + +0x22–0x23 : + - Bits 0–6 : hairType (0–71) + - Bits 7–9 : hairColor (0–7) + - Bit 10 : hairPart (0 = normal, 1 = mirrored) + - Bits 11–15 : unknown_2 (5 bits long) + +0x24–0x27 : + - Bits 0–4 : eyebrowType (0–23) + - Bit 5 : unknown_3 (1 bit long) DO NOT USE! + - Bits 6–9 : eyebrowRotation (0–11) + - Bits 10–15 : unknown_4 (6 bits long) + - Bits 16–18 : eyebrowColor (0–7) + - Bits 19–22 : eyebrowSize (0–8) + - Bits 23–27 : eyebrowVertPos (3–18) + - Bits 28–31 : eyebrowHorizSpacing (0–12) + +0x28–0x2B : + - Bits 0–5 : eyeType (0–47) + - Bits 6–7 : unknown_5 (2 bits long) DO NOT USE! + - Bits 8–10 : eyeRotation (0–7) + - Bits 11–15 : eyeVertPos (0–18) + - Bits 16–18 : eyeColor (0–5) + - Bit 19 : unknown_6 (1 bit long) DO NOT USE! + - Bits 20–22 : eyeSize (0–7) + - Bits 23–26 : eyeHorizSpacing (0–12) + - Bits 27–31 : unknown_7 (5 bits long) + +0x2C–0x2D : + - Bits 0–3 : noseType (0–11) + - Bits 4–7 : noseSize (0–8) + - Bits 8–12 : noseVertPos (0–18) + - Bits 13–15 : unknown_8 (3 bits long) + +0x2E–0x2F : + - Bits 0–4 : lipType (0–23) + - Bits 5–6 : lipColor (0–2) + - Bits 7–10 : lipSize (0–8) + - Bits 11–15 : lipVertPos (0–18) + +0x30–0x31 : + - Bits 0–3 : glassesType (0–8) + - Bits 4–6 : glassesColor (0–5) + - Bit 7 : unknown_9 (if set, Mii may not render) so not counted in our total free bits + - Bits 8–10 : glassesSize (0–7) + - Bits 11–15 : glassesVertPos (0–20) + +0x32–0x33 : + - Bits 0–1 : mustacheType (0–3) + - Bits 2–3 : beardType (0–3) + - Bits 4–6 : facialHairColor (0–7) + - Bits 7–10 : mustacheSize (0–8) + - Bits 11–15 : mustacheVertPos (0–16) + +0x34–0x35 : + - Bit 0 : moleOn (1 = has mole) + - Bits 1–4 : moleSize (0–8) + - Bits 5–9 : moleVertPos (0–30) + - Bits 10–14 : moleHorizPos (0–16) + - Bit 15 : unknown_10 (1 bit long) + +0x36–0x49 : creatorName (10 UTF-16 characters, 20 bytes total) + + +We can use these Unknown bits to store extra data, Like the ability to say if you want your mii to be copiable or not + +Free bits available for custom use and what we will map them to: +This gives us a total of 28 bits to play with. +---------------------------------------------------- +0x20–0x21 : Bits 10–12 (unknown_0, 3 bits) + +0x20–0x21 : Bit 14 (unknown_1, 1 bit) +0x22–0x23 : Bits 11–15 (unknown_2, 5 bits) +0x24–0x27 : Bit 5 (unknown_3, 1 bit) DO NOT USE! +0x24–0x27 : Bits 10–15 (unknown_4, 6 bits) +0x28–0x2B : Bits 6–7 (unknown_5, 2 bits) DO NOT USE +0x28–0x2B : Bit 19 (unknown_6, 1 bit) DO NOT USE + +0x28–0x2B : Bits 27–31 (unknown_7, 5 bits) +0x2C–0x2D : Bits 13–15 (unknown_8, 3 bits) +0x34–0x35 : Bit 15 (unknown_10, 1 bit) +*/ + +public class Mii +{ + public bool IsInvalid { get; set; } + public bool IsGirl { get; set; } + public DateOnly Date { get; set; } = new(2000, 1, 1); + public MiiFavoriteColor MiiFavoriteColor { get; set; } = MiiFavoriteColor.Black; + public bool IsFavorite { get; set; } + + public MiiName Name { get; set; } = new("no name"); + public MiiScale Height { get; set; } = new(1); + public MiiScale Weight { get; set; } = new(1); + + public bool IsForeign => (MiiId1 >> 5) == 0b110; // checks if the top 3 bits are set to 110, if so it got blue pants + + //Mii ID is also refered as Avatar ID + public byte MiiId1 { get; set; } + public byte MiiId2 { get; set; } + public byte MiiId3 { get; set; } + public byte MiiId4 { get; set; } + + public uint MiiId + { + get => (uint)(MiiId1 << 24 | MiiId2 << 16 | MiiId3 << 8 | MiiId4); + set + { + MiiId1 = (byte)(value >> 24); + MiiId2 = (byte)(value >> 16); + MiiId3 = (byte)(value >> 8); + MiiId4 = (byte)(value); + } + } + + //This is also referred as Client ID + public byte SystemId0 { get; set; } + public byte SystemId1 { get; set; } + public byte SystemId2 { get; set; } + public byte SystemId3 { get; set; } + + public uint SystemId + { + get => (uint)(SystemId0 << 24 | SystemId1 << 16 | SystemId2 << 8 | SystemId3); + set + { + SystemId0 = (byte)(value >> 24); + SystemId1 = (byte)(value >> 16); + SystemId2 = (byte)(value >> 8); + SystemId3 = (byte)(value); + } + } + + /// + /// The 28 unknown Wii‑Mii bits, exposed through a high‑level wrapper. + /// Always initialised (never null). + /// + public CustomMiiData CustomData { get; set; } = CustomMiiData.CreateEmpty(); + public MiiFacialFeatures MiiFacialFeatures { get; set; } = + new(MiiFaceShape.Bread, MiiSkinColor.Light, MiiFacialFeature.None, false, false); + + public MiiHair MiiHair { get; set; } = new(1, MiiHairColor.Black, false); + public MiiEyebrow MiiEyebrows { get; set; } = new(1, 0, MiiHairColor.Black, 4, 10, 1); + public MiiEye MiiEyes { get; set; } = new(1, 6, 7, MiiEyeColor.Black, 3, 6); + public MiiNose MiiNose { get; set; } = new(MiiNoseType.Default, 6, 4); + public MiiLip MiiLips { get; set; } = new(1, MiiLipColor.Skin, 4, 9); + public MiiGlasses MiiGlasses { get; set; } = new(MiiGlassesType.None, MiiGlassesColor.Grey, 4, 1); + public MiiFacialHair MiiFacialHair { get; set; } = new(MiiMustacheType.None, MiiBeardType.None, MiiHairColor.Black, 1, 1); + public MiiMole MiiMole { get; set; } = new(false, 0, 0, 0); + public MiiName CreatorName { get; set; } = new("no name"); +} diff --git a/WheelWizard/Features/WiiManagement/Domain/Mii/MiiEnums.cs b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiEnums.cs similarity index 71% rename from WheelWizard/Features/WiiManagement/Domain/Mii/MiiEnums.cs rename to WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiEnums.cs index c70a8f47..2c6e7bab 100644 --- a/WheelWizard/Features/WiiManagement/Domain/Mii/MiiEnums.cs +++ b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiEnums.cs @@ -1,4 +1,4 @@ -namespace WheelWizard.WiiManagement.Domain.Mii; +namespace WheelWizard.WiiManagement.MiiManagement.Domain.Mii; public enum MiiFavoriteColor : uint { @@ -54,7 +54,7 @@ public enum MiiFacialFeature Wrinkles, } -public enum HairColor +public enum MiiHairColor { Black, Brown, @@ -63,22 +63,10 @@ public enum HairColor Grey, LightBrown, Blonde, - White, -} - -public enum EyebrowColor -{ - Black, - Brown, - Red, - LightRed, - Grey, - LightBrown, - Blonde, - White, + Gold, } -public enum EyeColor : uint +public enum MiiEyeColor : uint { Black, Grey, @@ -88,7 +76,7 @@ public enum EyeColor : uint Green, } -public enum NoseType +public enum MiiNoseType { Default, SemiCircle, @@ -104,16 +92,16 @@ public enum NoseType Tunnel, } -public enum LipColor +public enum MiiLipColor { Skin, Red, Pink, } -public enum GlassesColor +public enum MiiGlassesColor { - Dark, + Grey, DarkGold, Red, Blue, @@ -121,7 +109,7 @@ public enum GlassesColor White, } -public enum GlassesType +public enum MiiGlassesType { None, Square, @@ -134,19 +122,7 @@ public enum GlassesType CoolSunGlasses, } -public enum MustacheColor -{ - Black, - Brown, - Red, - LightRed, - Grey, - LightBrown, - Tan, - White, -} - -public enum MustacheType +public enum MiiMustacheType { None, Normal, @@ -154,7 +130,7 @@ public enum MustacheType Droopy, } -public enum BeardType +public enum MiiBeardType { None, Thin, diff --git a/WheelWizard/Features/WiiManagement/Domain/Mii/MiiEye.cs b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiEye.cs similarity index 78% rename from WheelWizard/Features/WiiManagement/Domain/Mii/MiiEye.cs rename to WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiEye.cs index 16cc1565..81e7779e 100644 --- a/WheelWizard/Features/WiiManagement/Domain/Mii/MiiEye.cs +++ b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiEye.cs @@ -1,15 +1,15 @@ -namespace WheelWizard.WiiManagement.Domain.Mii; +namespace WheelWizard.WiiManagement.MiiManagement.Domain.Mii; public class MiiEye { public int Type { get; } public int Rotation { get; } public int Vertical { get; } - public EyeColor Color { get; } + public MiiEyeColor Color { get; } public int Size { get; } public int Spacing { get; } - public MiiEye(int type, int rotation, int vertical, EyeColor color, int size, int spacing) + public MiiEye(int type, int rotation, int vertical, MiiEyeColor color, int size, int spacing) { if (type is < 0 or > 47) throw new ArgumentException("Eye type invalid"); @@ -29,6 +29,6 @@ public MiiEye(int type, int rotation, int vertical, EyeColor color, int size, in Spacing = spacing; } - public static OperationResult Create(int type, int rotation, int vertical, EyeColor color, int size, int spacing) => + public static OperationResult Create(int type, int rotation, int vertical, MiiEyeColor color, int size, int spacing) => TryCatch(() => new MiiEye(type, rotation, vertical, color, size, spacing)); } diff --git a/WheelWizard/Features/WiiManagement/Domain/Mii/MiiEyebrow.cs b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiEyebrow.cs similarity index 81% rename from WheelWizard/Features/WiiManagement/Domain/Mii/MiiEyebrow.cs rename to WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiEyebrow.cs index d5524134..093b4d5c 100644 --- a/WheelWizard/Features/WiiManagement/Domain/Mii/MiiEyebrow.cs +++ b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiEyebrow.cs @@ -1,15 +1,15 @@ -namespace WheelWizard.WiiManagement.Domain.Mii; +namespace WheelWizard.WiiManagement.MiiManagement.Domain.Mii; public class MiiEyebrow { public int Type { get; } public int Rotation { get; } - public EyebrowColor Color { get; } + public MiiHairColor Color { get; } public int Size { get; } public int Vertical { get; } public int Spacing { get; } - public MiiEyebrow(int type, int rotation, EyebrowColor color, int size, int vertical, int spacing) + public MiiEyebrow(int type, int rotation, MiiHairColor color, int size, int vertical, int spacing) { if (type is < 0 or > 23) throw new ArgumentException("Eyebrow type invalid"); @@ -29,6 +29,6 @@ public MiiEyebrow(int type, int rotation, EyebrowColor color, int size, int vert Spacing = spacing; } - public static OperationResult Create(int type, int rotation, EyebrowColor color, int size, int vertical, int spacing) => + public static OperationResult Create(int type, int rotation, MiiHairColor color, int size, int vertical, int spacing) => TryCatch(() => new MiiEyebrow(type, rotation, color, size, vertical, spacing)); } diff --git a/WheelWizard/Features/WiiManagement/Domain/Mii/MiiFacialFeatures.cs b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiFacialFeatures.cs similarity index 93% rename from WheelWizard/Features/WiiManagement/Domain/Mii/MiiFacialFeatures.cs rename to WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiFacialFeatures.cs index c9876a27..c09bbf74 100644 --- a/WheelWizard/Features/WiiManagement/Domain/Mii/MiiFacialFeatures.cs +++ b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiFacialFeatures.cs @@ -1,4 +1,4 @@ -namespace WheelWizard.WiiManagement.Domain.Mii; +namespace WheelWizard.WiiManagement.MiiManagement.Domain.Mii; public class MiiFacialFeatures { diff --git a/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiFacialHair.cs b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiFacialHair.cs new file mode 100644 index 00000000..d6388f82 --- /dev/null +++ b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiFacialHair.cs @@ -0,0 +1,31 @@ +namespace WheelWizard.WiiManagement.MiiManagement.Domain.Mii; + +public class MiiFacialHair +{ + public MiiMustacheType MiiMustacheType { get; } + public MiiBeardType MiiBeardType { get; } + public MiiHairColor Color { get; } + public int Size { get; } + public int Vertical { get; } + + public MiiFacialHair(MiiMustacheType miiMustacheType, MiiBeardType miiBeardType, MiiHairColor color, int size, int vertical) + { + if (size is < 0 or > 8) + throw new ArgumentException("Facial hair size invalid"); + if (vertical is < 0 or > 16) + throw new ArgumentException("Facial hair vertical position invalid"); + MiiMustacheType = miiMustacheType; + MiiBeardType = miiBeardType; + Color = color; + Size = size; + Vertical = vertical; + } + + public static OperationResult Create( + MiiMustacheType miiMustacheType, + MiiBeardType miiBeardType, + MiiHairColor color, + int size, + int vertical + ) => TryCatch(() => new MiiFacialHair(miiMustacheType, miiBeardType, color, size, vertical)); +} diff --git a/WheelWizard/Features/WiiManagement/Domain/Mii/MiiGlasses.cs b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiGlasses.cs similarity index 57% rename from WheelWizard/Features/WiiManagement/Domain/Mii/MiiGlasses.cs rename to WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiGlasses.cs index 288cc321..23336328 100644 --- a/WheelWizard/Features/WiiManagement/Domain/Mii/MiiGlasses.cs +++ b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiGlasses.cs @@ -1,13 +1,13 @@ -namespace WheelWizard.WiiManagement.Domain.Mii; +namespace WheelWizard.WiiManagement.MiiManagement.Domain.Mii; public class MiiGlasses { - public GlassesType Type { get; } - public GlassesColor Color { get; } + public MiiGlassesType Type { get; } + public MiiGlassesColor Color { get; } public int Size { get; } public int Vertical { get; } - public MiiGlasses(GlassesType type, GlassesColor color, int size, int vertical) + public MiiGlasses(MiiGlassesType type, MiiGlassesColor color, int size, int vertical) { if (size is < 0 or > 7) throw new ArgumentException("Glasses size invalid"); @@ -19,6 +19,6 @@ public MiiGlasses(GlassesType type, GlassesColor color, int size, int vertical) Vertical = vertical; } - public static OperationResult Create(GlassesType type, GlassesColor color, int size, int vertical) => + public static OperationResult Create(MiiGlassesType type, MiiGlassesColor color, int size, int vertical) => TryCatch(() => new MiiGlasses(type, color, size, vertical)); } diff --git a/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiHair.cs b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiHair.cs new file mode 100644 index 00000000..c6e59257 --- /dev/null +++ b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiHair.cs @@ -0,0 +1,20 @@ +namespace WheelWizard.WiiManagement.MiiManagement.Domain.Mii; + +public class MiiHair +{ + public int HairType { get; } + public MiiHairColor MiiHairColor { get; } + public bool HairFlipped { get; } + + public MiiHair(int hairType, MiiHairColor miiHairColor, bool hairFlipped) + { + if (hairType is < 0 or > 71) + throw new ArgumentException("HairType out of range"); + HairType = hairType; + MiiHairColor = miiHairColor; + HairFlipped = hairFlipped; + } + + public static OperationResult Create(int hairType, MiiHairColor miiHairColor, bool hairFlipped) => + TryCatch(() => new MiiHair(hairType, miiHairColor, hairFlipped)); +} diff --git a/WheelWizard/Features/WiiManagement/Domain/Mii/MiiLip.cs b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiLip.cs similarity index 68% rename from WheelWizard/Features/WiiManagement/Domain/Mii/MiiLip.cs rename to WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiLip.cs index d871e939..04d31d52 100644 --- a/WheelWizard/Features/WiiManagement/Domain/Mii/MiiLip.cs +++ b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiLip.cs @@ -1,13 +1,13 @@ -namespace WheelWizard.WiiManagement.Domain.Mii; +namespace WheelWizard.WiiManagement.MiiManagement.Domain.Mii; public class MiiLip { public int Type { get; } - public LipColor Color { get; } + public MiiLipColor Color { get; } public int Size { get; } public int Vertical { get; } - public MiiLip(int type, LipColor color, int size, int vertical) + public MiiLip(int type, MiiLipColor color, int size, int vertical) { if (type is < 0 or > 23) throw new ArgumentException("Lip type invalid"); @@ -22,6 +22,6 @@ public MiiLip(int type, LipColor color, int size, int vertical) Vertical = vertical; } - public static OperationResult Create(int type, LipColor color, int size, int vertical) => + public static OperationResult Create(int type, MiiLipColor color, int size, int vertical) => TryCatch(() => new MiiLip(type, color, size, vertical)); } diff --git a/WheelWizard/Features/WiiManagement/Domain/Mii/MiiMole.cs b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiMole.cs similarity index 93% rename from WheelWizard/Features/WiiManagement/Domain/Mii/MiiMole.cs rename to WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiMole.cs index f65fa121..79eb82dc 100644 --- a/WheelWizard/Features/WiiManagement/Domain/Mii/MiiMole.cs +++ b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiMole.cs @@ -1,4 +1,4 @@ -namespace WheelWizard.WiiManagement.Domain.Mii; +namespace WheelWizard.WiiManagement.MiiManagement.Domain.Mii; public class MiiMole { diff --git a/WheelWizard/Features/WiiManagement/Domain/Mii/MiiName.cs b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiName.cs similarity index 95% rename from WheelWizard/Features/WiiManagement/Domain/Mii/MiiName.cs rename to WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiName.cs index e2bb5fa7..13b6fcff 100644 --- a/WheelWizard/Features/WiiManagement/Domain/Mii/MiiName.cs +++ b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiName.cs @@ -1,6 +1,6 @@ using System.Text; -namespace WheelWizard.WiiManagement.Domain.Mii; +namespace WheelWizard.WiiManagement.MiiManagement.Domain.Mii; /// /// Represents a Mii name. diff --git a/WheelWizard/Features/WiiManagement/Domain/Mii/MiiNose.cs b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiNose.cs similarity index 63% rename from WheelWizard/Features/WiiManagement/Domain/Mii/MiiNose.cs rename to WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiNose.cs index 96561d4a..c020d20e 100644 --- a/WheelWizard/Features/WiiManagement/Domain/Mii/MiiNose.cs +++ b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiNose.cs @@ -1,12 +1,12 @@ -namespace WheelWizard.WiiManagement.Domain.Mii; +namespace WheelWizard.WiiManagement.MiiManagement.Domain.Mii; public class MiiNose { - public NoseType Type { get; } + public MiiNoseType Type { get; } public int Size { get; } public int Vertical { get; } - public MiiNose(NoseType type, int size, int vertical) + public MiiNose(MiiNoseType type, int size, int vertical) { if (size is < 0 or > 8) throw new ArgumentException("Nose size invalid"); @@ -17,6 +17,6 @@ public MiiNose(NoseType type, int size, int vertical) Vertical = vertical; } - public static OperationResult Create(NoseType type, int size, int vertical) => + public static OperationResult Create(MiiNoseType type, int size, int vertical) => TryCatch(() => new MiiNose(type, size, vertical)); } diff --git a/WheelWizard/Features/WiiManagement/Domain/Mii/MiiScale.cs b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiScale.cs similarity index 84% rename from WheelWizard/Features/WiiManagement/Domain/Mii/MiiScale.cs rename to WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiScale.cs index 46e6d15c..f8b7f655 100644 --- a/WheelWizard/Features/WiiManagement/Domain/Mii/MiiScale.cs +++ b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/Mii/MiiScale.cs @@ -1,4 +1,4 @@ -namespace WheelWizard.WiiManagement.Domain.Mii; +namespace WheelWizard.WiiManagement.MiiManagement.Domain.Mii; public class MiiScale { diff --git a/WheelWizard/Features/WiiManagement/MiiManagement/Domain/MiiColorMappings.cs b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/MiiColorMappings.cs new file mode 100644 index 00000000..2a5d8e42 --- /dev/null +++ b/WheelWizard/Features/WiiManagement/MiiManagement/Domain/MiiColorMappings.cs @@ -0,0 +1,80 @@ +using Avalonia.Media; +using WheelWizard.Views; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; + +namespace WheelWizard.WiiManagement.MiiManagement.Domain; + +public static class MiiColorMappings +{ + public static readonly Dictionary FavoriteColor = new() + { + [MiiFavoriteColor.Black] = Colors.Black, + [MiiFavoriteColor.Yellow] = Color.FromRgb(255, 237, 33), + [MiiFavoriteColor.Red] = Color.FromRgb(252, 33, 20), + [MiiFavoriteColor.Pink] = Color.FromRgb(255, 98, 126), + [MiiFavoriteColor.Blue] = Color.FromRgb(10, 80, 184), + [MiiFavoriteColor.Green] = Color.FromRgb(0, 130, 50), + [MiiFavoriteColor.Orange] = Color.FromRgb(255, 119, 27), + [MiiFavoriteColor.Purple] = Color.FromRgb(138, 42, 176), + [MiiFavoriteColor.LightBlue] = Color.FromRgb(71, 186, 225), + [MiiFavoriteColor.LightGreen] = Color.FromRgb(143, 240, 31), + [MiiFavoriteColor.Brown] = Color.FromRgb(87, 62, 23), + [MiiFavoriteColor.White] = Color.FromRgb(255, 255, 250), + }; + + public static readonly Dictionary SkinColor = new() + { + [MiiSkinColor.Light] = Color.FromRgb(255, 211, 157), + [MiiSkinColor.Yellow] = Color.FromRgb(255, 185, 99), + [MiiSkinColor.Red] = Color.FromRgb(222, 123, 61), + [MiiSkinColor.Pink] = Color.FromRgb(255, 171, 128), + [MiiSkinColor.DarkBrown] = Color.FromRgb(200, 83, 39), + [MiiSkinColor.Brown] = Color.FromRgb(117, 46, 23), + }; + + public static readonly Dictionary HairColor = new() + { + [MiiHairColor.Black] = Colors.Black, + [MiiHairColor.Brown] = Color.FromRgb(86, 45, 27), + [MiiHairColor.Red] = Color.FromRgb(120, 37, 21), + [MiiHairColor.LightRed] = Color.FromRgb(157, 74, 32), + [MiiHairColor.Grey] = Color.FromRgb(152, 139, 140), + [MiiHairColor.LightBrown] = Color.FromRgb(104, 78, 27), + [MiiHairColor.Blonde] = Color.FromRgb(171, 106, 36), + [MiiHairColor.Gold] = Color.FromRgb(255, 183, 87), + }; + + public static readonly Dictionary GlassesColor = new() + { + [MiiGlassesColor.Grey] = Color.FromRgb(144, 144, 144), + [MiiGlassesColor.Gold] = Color.FromRgb(255, 175, 71), + [MiiGlassesColor.DarkGold] = Color.FromRgb(202, 147, 102), + [MiiGlassesColor.Blue] = Color.FromRgb(123, 135, 189), + [MiiGlassesColor.Red] = Color.FromRgb(255, 87, 77), + [MiiGlassesColor.White] = Color.FromRgb(220, 197, 190), + }; + + public static readonly Dictionary LipBottomColor = new() + { + [MiiLipColor.Red] = Color.FromRgb(255, 18, 13), + [MiiLipColor.Pink] = Color.FromRgb(255, 83, 77), + [MiiLipColor.Skin] = Color.FromRgb(255, 93, 13), + }; + + public static readonly Dictionary LipTopColor = new() + { + [MiiLipColor.Red] = Color.FromRgb(154, 19, 18), + [MiiLipColor.Pink] = Color.FromRgb(175, 41, 47), + [MiiLipColor.Skin] = Color.FromRgb(167, 59, 30), + }; + + public static readonly Dictionary EyeColor = new() + { + [MiiEyeColor.Black] = Colors.Black, + [MiiEyeColor.Grey] = ViewUtils.Colors.Neutral700, + [MiiEyeColor.Brown] = Color.FromRgb(150, 72, 45), + [MiiEyeColor.Gold] = Color.FromRgb(165, 152, 55), + [MiiEyeColor.Blue] = Color.FromRgb(85, 93, 195), + [MiiEyeColor.Green] = Color.FromRgb(72, 143, 100), + }; +} diff --git a/WheelWizard/Features/WiiManagement/MiiDbService.cs b/WheelWizard/Features/WiiManagement/MiiManagement/MiiDbService.cs similarity index 87% rename from WheelWizard/Features/WiiManagement/MiiDbService.cs rename to WheelWizard/Features/WiiManagement/MiiManagement/MiiDbService.cs index f30a80c4..c66eaf45 100644 --- a/WheelWizard/Features/WiiManagement/MiiDbService.cs +++ b/WheelWizard/Features/WiiManagement/MiiManagement/MiiDbService.cs @@ -1,7 +1,8 @@ using Testably.Abstractions; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.Shared.MessageTranslations; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; -namespace WheelWizard.WiiManagement; +namespace WheelWizard.WiiManagement.MiiManagement; /// /// Provides high-level operations for managing Miis in the Wii Mii database. @@ -76,8 +77,10 @@ public List GetAllMiis() public OperationResult GetByAvatarId(uint avatarId) { var raw = repository.GetRawBlockByAvatarId(avatarId); - if (raw == null || raw.Length != MiiSerializer.MiiBlockSize) - return "Mii block not found or invalid."; + if (raw == null) + return Fail("Mii block not found", MessageTranslation.Error_UpdateMiiDb_NoBlockFound); + if (raw.Length != MiiSerializer.MiiBlockSize) + return Fail("Mii block size invalid.", MessageTranslation.Error_UpdateMiiDb_BlockSizeInvalid); return MiiSerializer.Deserialize(raw); } @@ -110,11 +113,11 @@ public OperationResult UpdateName(uint clientId, string newName) public OperationResult AddToDatabase(Mii? newMii, string macAddress) { if (newMii == null) - return Fail("Mii cannot be null or have an invalid ID."); + return Fail("Mii cannot be null", MessageTranslation.Error_MiiSerializer_MiiNotNull); var macParts = macAddress.Split(':'); if (macParts.Length != 6) - return Fail("Invalid MAC address format."); + return Fail("Invalid MAC address format.", MessageTranslation.Error_UpdateMiiDb_InvalidMac); newMii.IsInvalid = false; var getMacAddress = TryCatch(() => @@ -162,7 +165,7 @@ private static byte[] GenerateMiiId(bool isBlue = false) var now = DateTime.UtcNow; // Compute base tick (4‑second resolution) - uint baseCounter = (uint)((now - epoch).TotalSeconds / 4u); + var baseCounter = (uint)((now - epoch).TotalSeconds / 4u); uint actualCounter; lock (_miiIdLock) @@ -178,6 +181,7 @@ private static byte[] GenerateMiiId(bool isBlue = false) _lastCounter = baseCounter; _sequenceOffset = 0; } + actualCounter = baseCounter + _sequenceOffset; } @@ -192,14 +196,15 @@ private static byte[] GenerateMiiId(bool isBlue = false) public OperationResult Remove(uint clientId) { if (clientId == 0) - return Fail("Invalid client ID."); + return Fail("Invalid Client ID.", MessageTranslation.Error_UpdateMiiDb_InvalidClId); var emptyBlock = new byte[74]; return repository.UpdateBlockByClientId(clientId, emptyBlock); } - public static uint GenerateCustomMiiId() + /* TODO: Find out if we wanna keep this + public uint GenerateCustomMiiId() { - var rng = Random.Shared; + var rng = randomSystem.Random.Shared; // Byte 0: ensure high bit = 1 (so ID ≥ 0x80000000) var b0 = (byte)(rng.Next(0, 0x40) | 0x80); @@ -212,4 +217,5 @@ public static uint GenerateCustomMiiId() // Combine into big‑endian uint: return ((uint)b0 << 24) | ((uint)b1 << 16) | ((uint)b2 << 8) | (uint)b3; } + */ } diff --git a/WheelWizard/Features/WiiManagement/MiiManagement/MiiExtensions.cs b/WheelWizard/Features/WiiManagement/MiiManagement/MiiExtensions.cs new file mode 100644 index 00000000..d7e406ac --- /dev/null +++ b/WheelWizard/Features/WiiManagement/MiiManagement/MiiExtensions.cs @@ -0,0 +1,64 @@ +using WheelWizard.Services.Settings; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; + +namespace WheelWizard.WiiManagement.MiiManagement; + +public static class MiiExtensions +{ + public static bool IsTheSameAs(this Mii self, Mii? other) + { + if (other == null) + return false; + + var selfBytes = MiiSerializer.Serialize(self); + if (selfBytes.IsFailure) + return false; + + var otherBytes = MiiSerializer.Serialize(other); + if (otherBytes.IsFailure) + return false; + + return Convert.ToBase64String(selfBytes.Value) == Convert.ToBase64String(otherBytes.Value); + } + + public static OperationResult Clone(this Mii self) + { + // This is not the fastest way to clone, but it is the easiest way. + var miiId = self.MiiId; + self.MiiId = 1; // Make it 1 for the cloning process to ensure that that is not a reason for failure. + // That being said, we still reset the ID afterward to make sure that if the og is false, then the clone is also false. + + var selfBytes = MiiSerializer.Serialize(self); + self.MiiId = miiId; // IMPORTANT: Set the MiiId back, since you are changing the original record here, we don't want that. + if (selfBytes.IsFailure) + return selfBytes.Error; + + var cloneResult = MiiSerializer.Deserialize(selfBytes.Value); + if (cloneResult.IsFailure) + return cloneResult.Error; + + cloneResult.Value.MiiId = miiId; // IMPORTANT: Also make sure that the clone has the same id as the OG, it's a clone after all. + return cloneResult.Value; + } + + public static bool IsGlobal(this Mii self) + { + // If it has blue pants, then its definitely global + if ((self.MiiId1 >> 5) == 0b110) + return true; + + // But it can also be global if the mac address is not the same as your own address + var macAddressString = (string)SettingsManager.MACADDRESS.Get(); + var macParts = macAddressString.Split(':'); + var macBytes = new byte[6]; + for (var i = 0; i < 6; i++) + macBytes[i] = byte.Parse(macParts[i], System.Globalization.NumberStyles.HexNumber); + var systemId0 = (byte)((macBytes[0] + macBytes[1] + macBytes[2]) & 0xFF); + return ( + self?.SystemId0 != systemId0 + || self?.SystemId1 != macBytes[3] + || self?.SystemId2 != macBytes[4] + || self?.SystemId3 != macBytes[5] + ); + } +} diff --git a/WheelWizard/Features/WiiManagement/MiiManagement/MiiFactory.cs b/WheelWizard/Features/WiiManagement/MiiManagement/MiiFactory.cs new file mode 100644 index 00000000..7bc5d9ed --- /dev/null +++ b/WheelWizard/Features/WiiManagement/MiiManagement/MiiFactory.cs @@ -0,0 +1,86 @@ +using Testably.Abstractions.RandomSystem; +using WheelWizard.Resources.Languages; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; + +namespace WheelWizard.WiiManagement.MiiManagement; + +public static class MiiFactory +{ + private static Mii CreateDefaultBase() + { + return new Mii + { + Name = new(Common.Attribute_Mii_DefaultName), + CreatorName = new(""), + IsFavorite = false, + MiiFavoriteColor = MiiFavoriteColor.Red, + MiiFacialFeatures = new(MiiFaceShape.Teardrop, MiiSkinColor.Light, MiiFacialFeature.None, false, false), + MiiHair = new( + 30 /* bald >:) */ + , + MiiHairColor.Brown, + false + ), + MiiEyebrows = new(0, 6, MiiHairColor.Brown, 4, 10, 2), + MiiGlasses = new(MiiGlassesType.None, MiiGlassesColor.Grey, 4, 10), + MiiEyes = new(2, 4, 12, MiiEyeColor.Black, 4, 2), + MiiNose = new(MiiNoseType.SemiCircle, 4, 9), + MiiLips = new(23, MiiLipColor.Skin, 4, 13), + MiiFacialHair = new(MiiMustacheType.None, MiiBeardType.None, MiiHairColor.Black, 4, 10), + MiiMole = new(false, 4, 20, 2), + Height = new(63), + Weight = new(63), + MiiId = 1, + }; + } + + public static Mii CreateDefaultFemale() + { + var baseMii = CreateDefaultBase(); + baseMii.IsGirl = true; + baseMii.MiiHair = new(12, MiiHairColor.Brown, false); + baseMii.MiiEyebrows = new(0, 6, MiiHairColor.Brown, 4, 10, 2); + baseMii.MiiEyes = new(4, 3, 12, MiiEyeColor.Black, 4, 2); + return baseMii; + } + + public static Mii CreateDefaultMale() + { + var baseMii = CreateDefaultBase(); + baseMii.IsGirl = false; + baseMii.MiiHair = new(33, MiiHairColor.Brown, false); + baseMii.MiiEyebrows = new(6, 6, MiiHairColor.Brown, 4, 10, 2); + baseMii.MiiEyes = new(2, 4, 12, MiiEyeColor.Black, 4, 2); + return baseMii; + } + + public static Mii CreateRandomMii(IRandom random) + { + var baseMii = CreateDefaultBase(); + var hairColor = (MiiHairColor)(random.Next() % 8); + + baseMii.IsGirl = random.Next() % 2 == 0; + baseMii.MiiHair = new(random.Next() % 71, hairColor, random.Next() % 3 == 0); + baseMii.MiiEyebrows = new(random.Next() % 23, 6, hairColor, 4, 10, 2); + baseMii.MiiEyes = new(random.Next() % 47, 4, 12, (MiiEyeColor)(random.Next() % 6), 4, 2); + baseMii.MiiFavoriteColor = (MiiFavoriteColor)(random.Next() % 12); + baseMii.MiiFacialFeatures = new( + (MiiFaceShape)(random.Next() % 8), + (MiiSkinColor)(random.Next() % 6), + (MiiFacialFeature)(random.Next() % 12), + false, + false + ); + baseMii.MiiNose = new((MiiNoseType)(random.Next() % 12), 4, 9); + baseMii.MiiLips = new(random.Next() % 23, (MiiLipColor)(random.Next() % 3), 4, 13); + baseMii.MiiMole = new(random.Next() % 4 == 0, 4, 20, 2); + if (random.Next() % 4 == 0) + baseMii.MiiGlasses = new((MiiGlassesType)(random.Next() % 9), (MiiGlassesColor)(random.Next() % 6), 4, 10); + else + baseMii.MiiGlasses = new(MiiGlassesType.None, MiiGlassesColor.Grey, 4, 10); + + if (random.Next() % 4 == 0) + baseMii.MiiFacialHair = new((MiiMustacheType)(random.Next() % 4), (MiiBeardType)(random.Next() % 4), hairColor, 4, 10); + return baseMii; + } +} diff --git a/WheelWizard/Features/WiiManagement/MiiRepositoryService.cs b/WheelWizard/Features/WiiManagement/MiiManagement/MiiRepositoryService.cs similarity index 80% rename from WheelWizard/Features/WiiManagement/MiiRepositoryService.cs rename to WheelWizard/Features/WiiManagement/MiiManagement/MiiRepositoryService.cs index f0252a28..397cd733 100644 --- a/WheelWizard/Features/WiiManagement/MiiRepositoryService.cs +++ b/WheelWizard/Features/WiiManagement/MiiManagement/MiiRepositoryService.cs @@ -1,8 +1,9 @@ using System.IO.Abstractions; +using WheelWizard.Helpers; using WheelWizard.Services; -using WheelWizard.Services.WiiManagement.SaveData; +using WheelWizard.Shared.MessageTranslations; -namespace WheelWizard.WiiManagement; +namespace WheelWizard.WiiManagement.MiiManagement; public interface IMiiRepositoryService { @@ -88,18 +89,25 @@ public List LoadAllBlocks() public OperationResult SaveAllBlocks(List blocks) { if (!fileSystem.File.Exists(_miiDbFilePath)) - return "RFL_DB.dat not found."; + return Fail("RFL_DB.dat not found.", MessageTranslation.Error_UpdateMiiDb_RFLdbNotFound); var db = ReadDatabase(); if (db.Length >= CrcOffset + 2) { // compute CRC over everything before CrcOffset - ushort existingCrc = (ushort)((db[CrcOffset] << 8) | db[CrcOffset + 1]); - ushort calcCrc = CalculateCrc16(db, 0, CrcOffset); + var existingCrc = (ushort)((db[CrcOffset] << 8) | db[CrcOffset + 1]); + var calcCrc = CalculateCrc16(db, 0, CrcOffset); if (existingCrc != calcCrc) { - return Fail($"Corrupt Mii database (bad CRC 0x{existingCrc:X4}, expected 0x{calcCrc:X4})."); + var item1 = $"{existingCrc:X4}"; + var item2 = $"{calcCrc:X4}"; + return Fail( + $"Corrupt Mii database (bad CRC 0x{item1}, expected 0x{item2}).", + MessageTranslation.Error_UpdateMiiDb_CorruptDb, + null, + [item1, item2] + ); } } @@ -134,7 +142,7 @@ public OperationResult SaveAllBlocks(List blocks) if (block.Length != MiiLength) continue; - var thisId = BigEndianBinaryReader.BufferToUint32(block, 0x18); + var thisId = BigEndianBinaryHelper.BufferToUint32(block, 0x18); if (thisId == clientId) return block; } @@ -147,7 +155,7 @@ public OperationResult SaveAllBlocks(List blocks) public OperationResult ForceCreateDatabase() { if (fileSystem.File.Exists(_miiDbFilePath)) - return "Database already exists."; + return Fail("Database already exists.", MessageTranslation.Error_MiiDBAlreadyExists); var directory = Path.GetDirectoryName(_miiDbFilePath); if (!string.IsNullOrEmpty(directory) && !fileSystem.Directory.Exists(directory)) @@ -185,34 +193,31 @@ public OperationResult ForceCreateDatabase() public OperationResult UpdateBlockByClientId(uint clientId, byte[] newBlock) { if (clientId == 0) - return "Invalid ClientId."; + return Fail("Invalid Client ID.", MessageTranslation.Error_UpdateMiiDb_InvalidClId); if (newBlock.Length != MiiLength) - return "Mii block size invalid."; + return Fail("Mii block size invalid.", MessageTranslation.Error_UpdateMiiDb_BlockSizeInvalid); if (!fileSystem.File.Exists(_miiDbFilePath)) - return "RFL_DB.dat not found."; + return Fail("RFL_DB.dat not found.", MessageTranslation.Error_UpdateMiiDb_RFLdbNotFound); var allBlocks = LoadAllBlocks(); var updated = false; - for (int i = 0; i < allBlocks.Count; i++) + foreach (var t in allBlocks) { - var block = allBlocks[i]; + var block = t; if (block.Length != MiiLength) continue; - var thisId = BigEndianBinaryReader.BufferToUint32(block, 0x18); + var thisId = BigEndianBinaryHelper.BufferToUint32(block, 0x18); if (thisId != clientId) continue; - Array.Copy(newBlock, 0, allBlocks[i], 0, MiiLength); + Array.Copy(newBlock, 0, t, 0, MiiLength); updated = true; break; } - if (!updated) - return Fail("Mii not found."); - - return SaveAllBlocks(allBlocks); + return !updated ? Fail("Mii not found.", MessageTranslation.Error_UpdateMiiDb_MiiNotFound) : SaveAllBlocks(allBlocks); } private byte[] ReadDatabase() @@ -237,13 +242,14 @@ private static ushort CalculateCrc16(byte[] buf, int off, int len) for (var b = 0; b < 8; b++) crc = (crc & 0x8000) != 0 ? (ushort)((crc << 1) ^ poly) : (ushort)(crc << 1); } + return crc; } public OperationResult AddMiiToBlocks(byte[]? rawMiiData) { if (rawMiiData is not { Length: MiiLength }) - return "Invalid Mii block size."; + return Fail("Invalid Mii block size."); // Load all 100 blocks. var blocks = LoadAllBlocks(); @@ -260,10 +266,6 @@ public OperationResult AddMiiToBlocks(byte[]? rawMiiData) break; } - if (!inserted) - return "No empty Mii slot available."; - - // Save the updated blocks back to the database. - return SaveAllBlocks(blocks); + return !inserted ? Fail("No empty Mii slot available.") : SaveAllBlocks(blocks); } } diff --git a/WheelWizard/Features/WiiManagement/MiiSerializer.cs b/WheelWizard/Features/WiiManagement/MiiManagement/MiiSerializer.cs similarity index 74% rename from WheelWizard/Features/WiiManagement/MiiSerializer.cs rename to WheelWizard/Features/WiiManagement/MiiManagement/MiiSerializer.cs index 057b86be..6121503f 100644 --- a/WheelWizard/Features/WiiManagement/MiiSerializer.cs +++ b/WheelWizard/Features/WiiManagement/MiiManagement/MiiSerializer.cs @@ -1,8 +1,10 @@ using System.Text; -using WheelWizard.Services.WiiManagement.SaveData; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.Helpers; +using WheelWizard.Shared.MessageTranslations; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii.Custom; -namespace WheelWizard.WiiManagement; +namespace WheelWizard.WiiManagement.MiiManagement; public static class MiiSerializer { @@ -11,9 +13,9 @@ public static class MiiSerializer public static OperationResult Serialize(Mii? mii) { if (mii == null) - return Fail("Mii cannot be null."); + return Fail("Mii cannot be null.", MessageTranslation.Error_MiiSerializer_MiiNotNull); if (mii.MiiId == 0) - return Fail("Mii ID cannot be 0."); + return Fail("Mii ID cannot be 0.", MessageTranslation.Error_MiiSerializer_MiiId0); var data = new byte[MiiBlockSize]; @@ -39,7 +41,7 @@ public static OperationResult Serialize(Mii? mii) data[0x17] = mii.Weight.Value; // Mii ID (0x18 - 0x1B) - BigEndianBinaryReader.WriteUInt32BigEndian(data, 0x18, mii.MiiId); + BigEndianBinaryHelper.WriteUInt32BigEndian(data, 0x18, mii.MiiId); // System ID (0x1C - 0x1F) data[0x1C] = mii.SystemId0; @@ -49,18 +51,18 @@ public static OperationResult Serialize(Mii? mii) // Face (0x20 - 0x21) ushort face = 0; - face |= (ushort)(((int)mii.MiiFacial.FaceShape & 0x07) << 13); - face |= (ushort)(((int)mii.MiiFacial.SkinColor & 0x07) << 10); - face |= (ushort)(((int)mii.MiiFacial.FacialFeature & 0x0F) << 6); - face |= (ushort)((mii.MiiFacial.MingleOff ? 1 : 0) << 2); - face |= (ushort)((mii.MiiFacial.Downloaded ? 1 : 0)); + face |= (ushort)(((int)mii.MiiFacialFeatures.FaceShape & 0x07) << 13); + face |= (ushort)(((int)mii.MiiFacialFeatures.SkinColor & 0x07) << 10); + face |= (ushort)(((int)mii.MiiFacialFeatures.FacialFeature & 0x0F) << 6); + face |= (ushort)((mii.MiiFacialFeatures.MingleOff ? 1 : 0) << 2); + face |= (ushort)((mii.MiiFacialFeatures.Downloaded ? 1 : 0)); data[0x20] = (byte)(face >> 8); data[0x21] = (byte)(face & 0xFF); // Hair (0x22 - 0x23) ushort hair = 0; hair |= (ushort)((mii.MiiHair.HairType & 0x7F) << 9); - hair |= (ushort)(((int)mii.MiiHair.HairColor & 0x07) << 6); + hair |= (ushort)(((int)mii.MiiHair.MiiHairColor & 0x07) << 6); hair |= (ushort)((mii.MiiHair.HairFlipped ? 1 : 0) << 5); data[0x22] = (byte)(hair >> 8); data[0x23] = (byte)(hair & 0xFF); @@ -119,8 +121,8 @@ public static OperationResult Serialize(Mii? mii) // Facial hair (0x32 - 0x33) ushort facialHair = 0; - facialHair |= (ushort)(((int)mii.MiiFacialHair.MustacheType & 0x03) << 14); - facialHair |= (ushort)(((int)mii.MiiFacialHair.BeardType & 0x03) << 12); + facialHair |= (ushort)(((int)mii.MiiFacialHair.MiiMustacheType & 0x03) << 14); + facialHair |= (ushort)(((int)mii.MiiFacialHair.MiiBeardType & 0x03) << 12); facialHair |= (ushort)(((int)mii.MiiFacialHair.Color & 0x07) << 9); facialHair |= (ushort)((mii.MiiFacialHair.Size & 0x0F) << 5); facialHair |= (ushort)((mii.MiiFacialHair.Vertical & 0x1F)); @@ -139,6 +141,9 @@ public static OperationResult Serialize(Mii? mii) // Creator Name (0x36 - 0x49) Buffer.BlockCopy(mii.CreatorName.ToBytes(), 0, data, 0x36, 20); + // Apply the custom data from all those little bits :) + mii.CustomData.ApplyTo(data); + return data; } @@ -147,11 +152,11 @@ public static OperationResult Serialize(Mii? mii) public static OperationResult Deserialize(byte[]? data) { if (data == null || data.Length != 74) - return Fail("Invalid Mii data length."); + return Fail("Invalid Mii data length.", MessageTranslation.Error_MiiSerializer_MiiDataLength); //if the data only contains 0xFF or 0x00, return null if (data.All(b => b == 0xFF) || data.All(b => b == 0x00)) - return Fail("Mii data is empty."); + return Fail("Mii data is empty.", MessageTranslation.Error_MiiSerializer_MiiDataEmpty); var mii = new Mii(); @@ -164,7 +169,7 @@ public static OperationResult Deserialize(byte[]? data) mii.Date = new(2000, Math.Clamp(month, 1, 12), Math.Clamp(day, 1, 31)); var miiFavoriteColor = (uint)(header >> 1) & 0x0F; if (!Enum.IsDefined(typeof(MiiFavoriteColor), miiFavoriteColor)) - return new InvalidDataException("Invalid MiiFavoriteColor"); + return InvalidDataExc("MiiFavoriteColor"); mii.MiiFavoriteColor = (MiiFavoriteColor)miiFavoriteColor; mii.IsFavorite = (header & 0x01) != 0; @@ -172,7 +177,7 @@ public static OperationResult Deserialize(byte[]? data) // Name (0x02 - 0x15) var name = MiiName.FromBytes(data, 2); if (name.ToString() == "") - return new InvalidDataException("Invalid MiiName"); + return InvalidDataExc("MiiName"); mii.Name = name; // Height & Weight (0x16 - 0x17) @@ -186,7 +191,7 @@ public static OperationResult Deserialize(byte[]? data) mii.Weight = weight.Value; // Mii ID (0x18 - 0x1B) - mii.MiiId = BigEndianBinaryReader.BufferToUint32(data, 0x18); + mii.MiiId = BigEndianBinaryHelper.BufferToUint32(data, 0x18); // System ID (0x1C - 0x1F) mii.SystemId0 = data[0x1C]; @@ -204,11 +209,11 @@ public static OperationResult Deserialize(byte[]? data) var downloaded = (face & 0x01) != 0; if (!Enum.IsDefined(typeof(MiiFaceShape), faceShape)) - return new InvalidDataException("Invalid face shape value."); + return InvalidDataExc("FaceShape"); if (!Enum.IsDefined(typeof(MiiSkinColor), skinColor)) - return new InvalidDataException("Invalid SkinColor"); + return InvalidDataExc("SkinColor"); if (!Enum.IsDefined(typeof(MiiFacialFeature), facialFeature)) - return new InvalidDataException("Invalid FacialFeature"); + return InvalidDataExc("FacialFeature"); var miiFacialResult = new MiiFacialFeatures( (MiiFaceShape)faceShape, (MiiSkinColor)skinColor, @@ -216,14 +221,14 @@ public static OperationResult Deserialize(byte[]? data) mingleOff, downloaded ); - mii.MiiFacial = miiFacialResult; + mii.MiiFacialFeatures = miiFacialResult; // Hair (0x22 - 0x23) var hair = (ushort)((data[0x22] << 8) | data[0x23]); var hairColor = (hair >> 6) & 0x07; - if (!Enum.IsDefined(typeof(HairColor), hairColor)) - return new InvalidDataException("Invalid HairColor"); - var miiHairResult = MiiHair.Create((hair >> 9) & 0x7F, (HairColor)hairColor, ((hair >> 5) & 0x01) != 0); + if (!Enum.IsDefined(typeof(MiiHairColor), hairColor)) + return InvalidDataExc("(Hair)HairColor"); + var miiHairResult = MiiHair.Create((hair >> 9) & 0x7F, (MiiHairColor)hairColor, ((hair >> 5) & 0x01) != 0); if (miiHairResult.IsFailure) return miiHairResult.Error; mii.MiiHair = miiHairResult.Value; @@ -231,12 +236,12 @@ public static OperationResult Deserialize(byte[]? data) // Eyebrows (0x24 - 0x27) var brow = (uint)((data[0x24] << 24) | (data[0x25] << 16) | (data[0x26] << 8) | data[0x27]); var eyebrowColor = (int)((brow >> 13) & 0x07); - if (!Enum.IsDefined(typeof(EyebrowColor), eyebrowColor)) - return new InvalidDataException("Invalid EyebrowColor"); + if (!Enum.IsDefined(typeof(MiiHairColor), eyebrowColor)) + return InvalidDataExc("(Eyebrow)HairColor"); var miiEyebrowsResult = MiiEyebrow.Create( (int)((brow >> 27) & 0x1F), (int)((brow >> 22) & 0x0F), - (EyebrowColor)eyebrowColor, + (MiiHairColor)eyebrowColor, (int)((brow >> 9) & 0x0F), (int)((brow >> 4) & 0x1F), (int)(brow & 0x0F) @@ -248,13 +253,13 @@ public static OperationResult Deserialize(byte[]? data) // Eyes (0x28 - 0x2B) var eye = (uint)((data[0x28] << 24) | (data[0x29] << 16) | (data[0x2A] << 8) | data[0x2B]); var eyeColor = ((eye >> 13) & 0x07); - if (!Enum.IsDefined(typeof(EyeColor), eyeColor)) - return new InvalidDataException("Invalid EyeColor"); + if (!Enum.IsDefined(typeof(MiiEyeColor), eyeColor)) + return InvalidDataExc("EyeColor"); var miiEyesResult = MiiEye.Create( (int)((eye >> 26) & 0x3F), (int)((eye >> 21) & 0x07), (int)((eye >> 16) & 0x1F), - (EyeColor)(eyeColor), + (MiiEyeColor)(eyeColor), (int)((eye >> 9) & 0x07), (int)((eye >> 5) & 0x0F) ); @@ -265,9 +270,9 @@ public static OperationResult Deserialize(byte[]? data) // Nose (0x2C - 0x2D) var nose = (ushort)((data[0x2C] << 8) | data[0x2D]); var noseType = (nose >> 12) & 0x0F; - if (!Enum.IsDefined(typeof(NoseType), noseType)) - return new InvalidDataException("Invalid NoseType"); - var miiNoseResult = MiiNose.Create((NoseType)noseType, ((nose >> 8) & 0x0F), ((nose >> 3) & 0x1F)); + if (!Enum.IsDefined(typeof(MiiNoseType), noseType)) + return InvalidDataExc("NoseType"); + var miiNoseResult = MiiNose.Create((MiiNoseType)noseType, ((nose >> 8) & 0x0F), ((nose >> 3) & 0x1F)); if (miiNoseResult.IsFailure) return miiNoseResult.Error; mii.MiiNose = miiNoseResult.Value; @@ -275,9 +280,9 @@ public static OperationResult Deserialize(byte[]? data) // Lips (0x2E - 0x2F) var lip = (ushort)((data[0x2E] << 8) | data[0x2F]); var lipColor = ((lip >> 9) & 0x03); - if (!Enum.IsDefined(typeof(LipColor), lipColor)) - return new InvalidDataException("Invalid LipColor"); - var miiLipResult = MiiLip.Create(((lip >> 11) & 0x1F), (LipColor)lipColor, ((lip >> 5) & 0x0F), (lip & 0x1F)); + if (!Enum.IsDefined(typeof(MiiLipColor), lipColor)) + return InvalidDataExc("LipColor"); + var miiLipResult = MiiLip.Create(((lip >> 11) & 0x1F), (MiiLipColor)lipColor, ((lip >> 5) & 0x0F), (lip & 0x1F)); if (miiLipResult.IsFailure) return miiLipResult.Error; mii.MiiLips = miiLipResult.Value; @@ -285,14 +290,14 @@ public static OperationResult Deserialize(byte[]? data) // Glasses (0x30 - 0x31) var glasses = (ushort)((data[0x30] << 8) | data[0x31]); var glassesType = ((glasses >> 12) & 0x0F); - if (!Enum.IsDefined(typeof(GlassesType), glassesType)) - return new InvalidDataException("Invalid GlassesType"); + if (!Enum.IsDefined(typeof(MiiGlassesType), glassesType)) + return InvalidDataExc("GlassesType"); var glassesColor = ((glasses >> 9) & 0x07); - if (!Enum.IsDefined(typeof(GlassesColor), glassesColor)) - return new InvalidDataException("Invalid GlassesColor"); + if (!Enum.IsDefined(typeof(MiiGlassesColor), glassesColor)) + return InvalidDataExc("GlassesColor"); var miiGlassesResult = MiiGlasses.Create( - (GlassesType)glassesType, - (GlassesColor)glassesColor, + (MiiGlassesType)glassesType, + (MiiGlassesColor)glassesColor, ((glasses >> 5) & 0x07), (glasses & 0x1F) ); @@ -303,18 +308,18 @@ public static OperationResult Deserialize(byte[]? data) // Facial hair (0x32 - 0x33) var facial = (ushort)((data[0x32] << 8) | data[0x33]); var mustacheType = ((facial >> 14) & 0x03); - if (!Enum.IsDefined(typeof(MustacheType), mustacheType)) - return new InvalidDataException("Invalid MustacheType"); + if (!Enum.IsDefined(typeof(MiiMustacheType), mustacheType)) + return InvalidDataExc("MustacheType"); var beardType = ((facial >> 12) & 0x03); - if (!Enum.IsDefined(typeof(BeardType), beardType)) - return new InvalidDataException("Invalid BeardType"); + if (!Enum.IsDefined(typeof(MiiBeardType), beardType)) + return InvalidDataExc("BeardType"); var color = ((facial >> 9) & 0x07); - if (!Enum.IsDefined(typeof(MustacheColor), color)) - return new InvalidDataException("Invalid FacialHairColor"); + if (!Enum.IsDefined(typeof(MiiHairColor), color)) + return InvalidDataExc("(Facial)HairColor"); var miiFacialHairResult = MiiFacialHair.Create( - (MustacheType)mustacheType, - (BeardType)beardType, - (MustacheColor)color, + (MiiMustacheType)mustacheType, + (MiiBeardType)beardType, + (MiiHairColor)color, ((facial >> 5) & 0x0F), (facial & 0x1F) ); @@ -334,6 +339,12 @@ public static OperationResult Deserialize(byte[]? data) if (creatorNameResult.IsFailure) return creatorNameResult.Error; mii.CreatorName = creatorNameResult.Value; + mii.CustomData = CustomMiiData.FromBytes(data); return mii; } + + private static OperationResult InvalidDataExc(string data) + { + return Fail(new InvalidDataException($"Invalid {data}"), MessageTranslation.Error_MiiSerializer_InvalidMiiData, null, [data]); + } } diff --git a/WheelWizard/Features/WiiManagement/WiiManagementExtensions.cs b/WheelWizard/Features/WiiManagement/WiiManagementExtensions.cs index ee8f6230..38355e86 100644 --- a/WheelWizard/Features/WiiManagement/WiiManagementExtensions.cs +++ b/WheelWizard/Features/WiiManagement/WiiManagementExtensions.cs @@ -1,4 +1,5 @@ -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.GameLicense; +using WheelWizard.WiiManagement.MiiManagement; namespace WheelWizard.WiiManagement; @@ -11,33 +12,4 @@ public static IServiceCollection AddWiiManagement(this IServiceCollection servic services.AddSingleton(); return services; } - - public static bool IsTheSameAs(this Mii self, Mii? other) - { - if (other == null) - return false; - - var selfBytes = MiiSerializer.Serialize(self); - if (selfBytes.IsFailure) - return false; - - var otherBytes = MiiSerializer.Serialize(other); - if (otherBytes.IsFailure) - return false; - - return Convert.ToBase64String(selfBytes.Value) == Convert.ToBase64String(otherBytes.Value); - } - - public static OperationResult Clone(this Mii self) - { - // This is not the fastest way to clone, but it is the easiest way. - var selfBytes = MiiSerializer.Serialize(self); - if (selfBytes.IsFailure) - return selfBytes.Error; - var cloneResult = MiiSerializer.Deserialize(selfBytes.Value); - if (cloneResult.IsFailure) - return cloneResult.Error; - ; // watermark by wanttobeeme - return cloneResult.Value; - } } diff --git a/WheelWizard/GlobalUsings.cs b/WheelWizard/GlobalUsings.cs index 6c877c39..be465028 100644 --- a/WheelWizard/GlobalUsings.cs +++ b/WheelWizard/GlobalUsings.cs @@ -1,3 +1,4 @@ global using Microsoft.Extensions.DependencyInjection; global using WheelWizard.Shared; +global using static WheelWizard.Shared.OperationError; global using static WheelWizard.Shared.OperationResult; diff --git a/WheelWizard/Services/WiiManagement/SaveData/BigEndianBinaryReader.cs b/WheelWizard/Helpers/BigEndianBinaryHelper.cs similarity index 77% rename from WheelWizard/Services/WiiManagement/SaveData/BigEndianBinaryReader.cs rename to WheelWizard/Helpers/BigEndianBinaryHelper.cs index 6b8e5a9d..0cac230d 100644 --- a/WheelWizard/Services/WiiManagement/SaveData/BigEndianBinaryReader.cs +++ b/WheelWizard/Helpers/BigEndianBinaryHelper.cs @@ -1,8 +1,8 @@ using System.Text; -namespace WheelWizard.Services.WiiManagement.SaveData; +namespace WheelWizard.Helpers; -public static class BigEndianBinaryReader +public static class BigEndianBinaryHelper { //Helper functions to convert a buffer to an uint using big endian public static uint BufferToUint32(byte[] data, int offset) @@ -10,9 +10,9 @@ public static uint BufferToUint32(byte[] data, int offset) return (uint)((data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]); } - public static uint BufferToUint16(byte[] data, int offset) + public static ushort BufferToUint16(byte[] data, int offset) { - return (uint)((data[offset] << 8) | data[offset + 1]); + return (ushort)((data[offset] << 8) | data[offset + 1]); } //big endian get the string @@ -39,6 +39,15 @@ public static void WriteUInt32BigEndian(byte[] data, int offset, uint value) data[offset + 3] = (byte)(value & 0xFF); } + public static float BufferToFloat(byte[] data, int offset) + { + // for a big-endian float before converting. + var bytes = new byte[4]; + Array.Copy(data, offset, bytes, 0, 4); + Array.Reverse(bytes); + return BitConverter.ToSingle(bytes, 0); + } + public static void WriteUInt16BigEndian(byte[] data, int offset, ushort value) { data[offset] = (byte)(value >> 8); diff --git a/WheelWizard/Helpers/DownloadHelper.cs b/WheelWizard/Helpers/DownloadHelper.cs index 34c003d7..ef1c9864 100644 --- a/WheelWizard/Helpers/DownloadHelper.cs +++ b/WheelWizard/Helpers/DownloadHelper.cs @@ -2,6 +2,7 @@ namespace WheelWizard.Helpers; +// todo: Delete this static class and write a service for it. public static class DownloadHelper { private const int MaxRetries = 5; @@ -22,7 +23,7 @@ public static async Task DownloadToLocationAsync( return toLocationAsync; } - public static async Task DownloadToLocationAsync( + public static async Task DownloadToLocationAsync( string url, string tempFile, ProgressWindow progressPopupWindow, @@ -47,8 +48,6 @@ public static async Task DownloadToLocationAsync( response.EnsureSuccessStatusCode(); if (response.RequestMessage == null || response.RequestMessage.RequestUri == null) { - // Do we want this error? - // new MessageBoxWindow().SetTitleText("Failed to resolve final URL.").Show(); return null; } diff --git a/WheelWizard/Helpers/FileHelper.cs b/WheelWizard/Helpers/FileHelper.cs index 15ff5b2e..934ffdb5 100644 --- a/WheelWizard/Helpers/FileHelper.cs +++ b/WheelWizard/Helpers/FileHelper.cs @@ -1,5 +1,60 @@ +using System.Collections.Generic; + namespace WheelWizard.Helpers; +public enum DirectoryMoveOutcome +{ + NoOp, + Success, + CopyFailed, + VerificationFailed, + SourceDeletionFailed, +} + +public sealed class DirectoryMoveContentsResult +{ + public DirectoryMoveContentsResult( + DirectoryMoveOutcome outcome, + string sourcePath, + string destinationPath, + bool copyAttempted, + bool verificationAttempted, + bool deleteSourceRequested, + bool sourceDeletionSucceeded, + string? errorMessage = null, + Exception? exception = null, + IReadOnlyList? verificationFailures = null + ) + { + Outcome = outcome; + SourcePath = sourcePath; + DestinationPath = destinationPath; + CopyAttempted = copyAttempted; + VerificationAttempted = verificationAttempted; + DeleteSourceRequested = deleteSourceRequested; + SourceDeletionSucceeded = deleteSourceRequested ? sourceDeletionSucceeded : true; + ErrorMessage = errorMessage; + Exception = exception; + VerificationFailures = verificationFailures ?? Array.Empty(); + } + + public DirectoryMoveOutcome Outcome { get; } + public string SourcePath { get; } + public string DestinationPath { get; } + public bool CopyAttempted { get; } + public bool VerificationAttempted { get; } + public bool DeleteSourceRequested { get; } + public bool SourceDeletionSucceeded { get; } + public string? ErrorMessage { get; } + public Exception? Exception { get; } + public IReadOnlyList VerificationFailures { get; } + + public bool CopyCompleted => CopyAttempted && Outcome != DirectoryMoveOutcome.CopyFailed; + public bool VerificationSucceeded => !VerificationAttempted || Outcome != DirectoryMoveOutcome.VerificationFailed; + public bool IsSuccessful => Outcome is DirectoryMoveOutcome.Success or DirectoryMoveOutcome.NoOp; + public bool RequiresUserDecision => Outcome == DirectoryMoveOutcome.SourceDeletionFailed; +} + // From now on we to have this FileHelper as a middle man whenever we do anything file related. This makes // it easier to create helper methods, mock data, and most importantly, easy to make it multi-platform later on public static class FileHelper @@ -10,6 +65,79 @@ public static class FileHelper public static bool Exists(string path) => File.Exists(path) || Directory.Exists(path); + public static string Combine(params string[] paths) => Path.Combine(paths); + + public static string NormalizePath(string path) + { + if (string.IsNullOrWhiteSpace(path)) + throw new ArgumentException("Path cannot be empty.", nameof(path)); + + var fullPath = Path.GetFullPath(path); + return Path.TrimEndingDirectorySeparator(fullPath); + } + + public static bool IsRootDirectory(string path) + { + if (string.IsNullOrWhiteSpace(path)) + return false; + + string normalized; + try + { + normalized = NormalizePath(path); + } + catch + { + return false; + } + + var root = Path.GetPathRoot(normalized); + if (string.IsNullOrEmpty(root)) + return false; + + try + { + return PathsEqual(normalized, root); + } + catch + { + return false; + } + } + + public static bool IsDirectoryEmpty(string path) + { + if (!DirectoryExists(path)) + return true; + + foreach (var _ in Directory.EnumerateFileSystemEntries(path)) + return false; + + return true; + } + + public static string GetRelativePath(string relativeTo, string path) => Path.GetRelativePath(relativeTo, path); + + public static bool PathsEqual(string pathA, string pathB) + { + var comparison = OperatingSystem.IsWindows() ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; + return string.Equals(NormalizePath(pathA), NormalizePath(pathB), comparison); + } + + public static bool IsDescendantPath(string potentialDescendant, string potentialAncestor) + { + if (string.IsNullOrWhiteSpace(potentialDescendant) || string.IsNullOrWhiteSpace(potentialAncestor)) + return false; + + if (PathsEqual(potentialDescendant, potentialAncestor)) + return false; + + var normalizedAncestor = NormalizePath(potentialAncestor); + var normalizedDescendant = NormalizePath(potentialDescendant); + var relative = Path.GetRelativePath(normalizedAncestor, normalizedDescendant); + return !relative.StartsWith("..", StringComparison.Ordinal) && !Path.IsPathRooted(relative); + } + public static string ReadAllText(string path) => File.ReadAllText(path); public static string? ReadAllTextSafe(string path) => FileExists(path) ? ReadAllText(path) : null; @@ -42,6 +170,180 @@ public static void WriteAllLinesSafe(string path, IEnumerable contents) WriteAllLines(path, contents); } + public static void EnsureDirectory(string path) => Directory.CreateDirectory(path); + + public static DirectoryMoveContentsResult MoveDirectoryContents( + string sourcePath, + string destinationPath, + bool deleteSource = true, + IProgress? progress = null + ) + { + var normalizedSource = NormalizePath(sourcePath); + var normalizedDestination = NormalizePath(destinationPath); + + DirectoryMoveContentsResult CreateResult( + DirectoryMoveOutcome outcome, + bool copyAttempted = false, + bool verificationAttempted = false, + bool sourceDeletionSucceeded = false, + string? errorMessage = null, + Exception? exception = null, + IReadOnlyList? verificationFailures = null + ) => + new( + outcome, + normalizedSource, + normalizedDestination, + copyAttempted, + verificationAttempted, + deleteSource, + sourceDeletionSucceeded, + errorMessage, + exception, + verificationFailures + ); + + if (PathsEqual(normalizedSource, normalizedDestination)) + { + EnsureDirectory(normalizedDestination); + progress?.Report(1.0); + return CreateResult(DirectoryMoveOutcome.NoOp, sourceDeletionSucceeded: true); + } + + EnsureDirectory(normalizedDestination); + + if (!DirectoryExists(normalizedSource)) + { + progress?.Report(1.0); + return CreateResult(DirectoryMoveOutcome.NoOp, sourceDeletionSucceeded: true); + } + + progress?.Report(0.0); + + var directories = Directory.GetDirectories(normalizedSource, "*", SearchOption.AllDirectories); + var files = Directory.GetFiles(normalizedSource, "*", SearchOption.AllDirectories); + var totalSteps = directories.Length + files.Length; + var processedSteps = 0; + + void ReportProgress() + { + if (progress == null) + return; + + if (totalSteps <= 0) + { + progress.Report(1.0); + return; + } + + progress.Report(Math.Clamp((double)processedSteps / totalSteps, 0.0, 1.0)); + } + + var copyAttempted = directories.Length > 0 || files.Length > 0; + + try + { + foreach (var directory in directories) + { + var relative = GetRelativePath(normalizedSource, directory); + EnsureDirectory(Path.Combine(normalizedDestination, relative)); + processedSteps++; + ReportProgress(); + } + + foreach (var file in files) + { + var relative = GetRelativePath(normalizedSource, file); + var destinationFile = Path.Combine(normalizedDestination, relative); + var destinationDirectory = Path.GetDirectoryName(destinationFile); + if (!string.IsNullOrEmpty(destinationDirectory)) + EnsureDirectory(destinationDirectory); + + if (FileExists(destinationFile)) + File.Delete(destinationFile); + + File.Copy(file, destinationFile, overwrite: true); + processedSteps++; + ReportProgress(); + } + } + catch (Exception ex) + { + progress?.Report(1.0); + return CreateResult( + DirectoryMoveOutcome.CopyFailed, + copyAttempted: copyAttempted, + errorMessage: $"Failed to copy data: {ex.Message}", + exception: ex + ); + } + + var verificationAttempted = true; + var verificationFailures = new List(); + + foreach (var file in files) + { + var relative = GetRelativePath(normalizedSource, file); + var destinationFile = Path.Combine(normalizedDestination, relative); + + if (!FileExists(destinationFile)) + { + verificationFailures.Add($"{relative} (missing in destination)"); + continue; + } + + var sourceInfo = new FileInfo(file); + var destinationInfo = new FileInfo(destinationFile); + if (sourceInfo.Length != destinationInfo.Length) + { + verificationFailures.Add( + $"{relative} (size mismatch: expected {sourceInfo.Length} bytes, found {destinationInfo.Length} bytes)" + ); + } + } + + if (verificationFailures.Count > 0) + { + progress?.Report(1.0); + return CreateResult( + DirectoryMoveOutcome.VerificationFailed, + copyAttempted: copyAttempted, + verificationAttempted: verificationAttempted, + errorMessage: "One or more files failed verification after copying.", + verificationFailures: verificationFailures + ); + } + + if (deleteSource && DirectoryExists(normalizedSource)) + { + try + { + Directory.Delete(normalizedSource, true); + } + catch (Exception ex) + { + progress?.Report(1.0); + return CreateResult( + DirectoryMoveOutcome.SourceDeletionFailed, + copyAttempted: copyAttempted, + verificationAttempted: verificationAttempted, + sourceDeletionSucceeded: false, + errorMessage: $"Failed to delete source folder '{normalizedSource}': {ex.Message}", + exception: ex + ); + } + } + + progress?.Report(1.0); + return CreateResult( + DirectoryMoveOutcome.Success, + copyAttempted: copyAttempted, + verificationAttempted: verificationAttempted, + sourceDeletionSucceeded: true + ); + } + public static void Touch(string path, string defaultValue = "") { if (DirectoryExists(path)) diff --git a/WheelWizard/Helpers/Humanizer.cs b/WheelWizard/Helpers/Humanizer.cs index 89bdfc56..abc9aa0b 100644 --- a/WheelWizard/Helpers/Humanizer.cs +++ b/WheelWizard/Helpers/Humanizer.cs @@ -17,8 +17,8 @@ public static class Humanizer public static string HumanizeTimeSpan(TimeSpan timeSpan) { - // we use langauge to do the words like Phrases.Time_Days_1 or Phrases.Time_Days_x - // howver, the one with x has to be put in the method: ReplaceDynamic(Phrases.Time_Days_x, 10); + // we use language to do the words like Phrases.Time_Days_1 or Phrases.Time_Days_x + // however, the one with x has to be put in the method: ReplaceDynamic(Phrases.Time_Days_x, 10); // now e need to replace all the old with the new language versions @@ -66,14 +66,14 @@ public static string GetRegionName(uint regionID) { return regionID switch { - 0 => Online.Region_Japan, - 1 => Online.Region_America, - 2 => Online.Region_Europe, - 3 => Online.Region_Australia, - 4 => Online.Region_Taiwan, - 5 => Online.Region_SouthKorea, - 6 => Online.Region_China, - _ => Common.Term_Unknown, + 0 => Common.Region_Japan, + 1 => Common.Region_America, + 2 => Common.Region_Europe, + 3 => Common.Region_Australia, + 4 => Common.Region_Taiwan, + 5 => Common.Region_SouthKorea, + 6 => Common.Region_China, + _ => Common.State_Unknown, }; } diff --git a/WheelWizard/Logging.cs b/WheelWizard/Logging.cs index ef2ac18c..a1b4b8a5 100644 --- a/WheelWizard/Logging.cs +++ b/WheelWizard/Logging.cs @@ -13,15 +13,18 @@ public static class Logging /// Do not call this method multiple times. It is intended to be called once at application startup. /// Do not use the static logger instance other than for the application startup. /// - public static void CreateStaticLogger() + public static void CreateStaticLogger(bool logStartup = true) { try { + var logsDirectory = Path.Combine(PathManager.WheelWizardAppdataPath, "logs"); + Directory.CreateDirectory(logsDirectory); + Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .Enrich.FromLogContext() .WriteTo.Console(theme: AnsiConsoleTheme.Sixteen, applyThemeToRedirectedOutput: true) - .WriteTo.File(Path.Combine(PathManager.WheelWizardAppdataPath, "logs/log.txt"), rollingInterval: RollingInterval.Day) + .WriteTo.File(Path.Combine(logsDirectory, "log.txt"), rollingInterval: RollingInterval.Day) .CreateLogger(); } catch (Exception e) @@ -30,8 +33,20 @@ public static void CreateStaticLogger() throw; } - // Log the application start - LogPlatformInformation(); + if (logStartup) + { + // Log the application start + LogPlatformInformation(); + } + } + + /// + /// Recreates the static logger instance, flushing any existing loggers first. + /// + public static void RecreateStaticLogger() + { + Log.CloseAndFlush(); + CreateStaticLogger(logStartup: false); } /// diff --git a/WheelWizard/Models/RRInfo/RrPlayer.cs b/WheelWizard/Models/RRInfo/RrPlayer.cs index b36055d8..a3a69feb 100644 --- a/WheelWizard/Models/RRInfo/RrPlayer.cs +++ b/WheelWizard/Models/RRInfo/RrPlayer.cs @@ -1,5 +1,5 @@ using WheelWizard.WheelWizardData.Domain; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; namespace WheelWizard.Models.RRInfo; diff --git a/WheelWizard/Models/RRInfo/RrRoom.cs b/WheelWizard/Models/RRInfo/RrRoom.cs index 2622ee79..c598ddc5 100644 --- a/WheelWizard/Models/RRInfo/RrRoom.cs +++ b/WheelWizard/Models/RRInfo/RrRoom.cs @@ -1,5 +1,5 @@ using WheelWizard.Helpers; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; namespace WheelWizard.Models.RRInfo; @@ -22,40 +22,123 @@ public class RrRoom public string GameModeAbbrev => Rk switch { + // Retro Rewind "vs_10" => "RR", "vs_11" => "TT", "vs_12" => "200", + "vs_13" => "IR", + "vs_14" => "BT", + "vs_15" => "EBT", "vs_20" => "RR Ct", - "vs_21" => "TT Ct", - "vs_22" => "200 Ct", + "vs_21" => "RR V", - "vs_668" => "CTGP", + // CTGP_C + "vs_668" => "CTGP-C", + + // Insane Kart Wii "vs_69" => "IKW", + "vs_70" => "Ultras", + "vs_71" => "Crazy", + "vs_72" => "Bomb", + "vs_73" => "Accel", + "vs_74" => "Banana", + "vs_75" => "RndItm", + "vs_76" => "Unfair", + "vs_77" => "Blue", + "vs_78" => "Shroom", + "vs_79" => "Bumper", + "vs_80" => "Rampage", + "vs_81" => "Rain", + "vs_82" => "Break", + "vs_83" => "Riibal", + + // Luminous + "vs_666" => "Lumi", + "vs_667" => "Lumi TT", + // OptPack + "vs_875" => "OP 150", + "vs_876" => "OP TT", + "vs_877" => "OP R1", + "vs_878" => "OP R2", + "vs_879" => "OP R3", + "vs_880" => "OP R4", + + // WTP + "vs_1312" => "WTP 150", + "vs_1313" => "WTP 200", + "vs_1314" => "WTP TT", + "vs_1315" => "WTP IR", + "vs_1316" => "STYD", + + // Generic Versus "vs_751" => "VS", "vs_-1" => "Reg", + "vs" => "Reg", + _ => IsPublic ? "??" : "Lock", }; public string GameMode => Rk switch { - //Max Size:"-------------" + //Max Size:"----------------------" + // Retro Rewind "vs_10" => "RR 150CC", "vs_11" => "RR Time Tr", "vs_12" => "RR 200CC", + "vs_13" => "RR Item Rain", + "vs_14" => "RR Battle", + "vs_15" => "RR Elim Battle", "vs_20" => "RR 150CC CTs", - "vs_21" => "RR TT CTs", - "vs_22" => "RR 200CC CTs", + "vs_21" => "RR Vanilla", + // CTGP "vs_668" => "CTGP-C", + + // Insane Kart Wii "vs_69" => "Insane Kart", + "vs_70" => "Ultras VS", + "vs_71" => "Crazy Items", + "vs_72" => "Bob-omb Blast", + "vs_73" => "Inf Accel", + "vs_74" => "Banan Slip", + "vs_75" => "Rand Items", + "vs_76" => "Unfair Items", + "vs_77" => "Blue Madness", + "vs_78" => "Mush Dash", + "vs_79" => "Bumper Karts", + "vs_80" => "Item Rampage", + "vs_81" => "Item Rain", + "vs_82" => "Shell Break", + "vs_83" => "Riibalanced", + + // Luminous + "vs_666" => "Luminous", + "vs_667" => "Luminous TT", + + // OptPack + "vs_875" => "OP 150", + "vs_876" => "OP TT", + "vs_877" => "OP R1", + "vs_878" => "OP R2", + "vs_879" => "OP R3", + "vs_880" => "OP R4", + // WTP + "vs_1312" => "WTP 150CC", + "vs_1313" => "WTP 200CC", + "vs_1314" => "WTP Time Trial", + "vs_1315" => "WTP Item Rain", + "vs_1316" => "WTP STYD", + + // Generic "vs_751" => "Versus", "vs_-1" => "Regular", "vs" => "Regular", _ => IsPublic ? "Unknown Mode" : "Private Room", }; + public int AverageVr => PlayerCount == 0 ? 0 : Players.Sum(p => p.Value.Vr) / PlayerCount; public Mii? HostMii => !string.IsNullOrEmpty(Host) ? Players.GetValueOrDefault(Host)?.FirstMii : null; diff --git a/WheelWizard/Models/Settings/SettingConstants.cs b/WheelWizard/Models/Settings/SettingConstants.cs index 149329f8..bd1de544 100644 --- a/WheelWizard/Models/Settings/SettingConstants.cs +++ b/WheelWizard/Models/Settings/SettingConstants.cs @@ -30,19 +30,6 @@ public static class SettingValues { "OpenGL", "OGL" }, }; - public static readonly Dictionary> RrLanguages = new() - { - { 0, () => CreateLanguageString("English") }, - { 1, () => CreateLanguageString("Japanese") }, - { 2, () => CreateLanguageString("France") }, - { 3, () => CreateLanguageString("German") }, - { 4, () => CreateLanguageString("Dutch") }, - { 5, () => CreateLanguageString("Spanish") }, - { 6, () => CreateLanguageString("Italian") }, - { 7, () => CreateLanguageString("Korean") }, - { 8, () => CreateLanguageString("Russian") }, - }; - public static readonly Dictionary> WhWzLanguages = new() { { "en", () => CreateLanguageString("English") }, diff --git a/WheelWizard/Resources/Languages/Common.Designer.cs b/WheelWizard/Resources/Languages/Common.Designer.cs index 72aa9f12..47190ee5 100644 --- a/WheelWizard/Resources/Languages/Common.Designer.cs +++ b/WheelWizard/Resources/Languages/Common.Designer.cs @@ -122,6 +122,24 @@ public static string Action_DisableAll { } } + /// + /// Looks up a localized string similar to Do manually. + /// + public static string Action_DoManually { + get { + return ResourceManager.GetString("Action_DoManually", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Download and Install. + /// + public static string Action_DownloadAndInstall { + get { + return ResourceManager.GetString("Action_DownloadAndInstall", resourceCulture); + } + } + /// /// Looks up a localized string similar to Duplicate. /// @@ -158,6 +176,24 @@ public static string Action_Export { } } + /// + /// Looks up a localized string similar to Favorite. + /// + public static string Action_Favorite { + get { + return ResourceManager.GetString("Action_Favorite", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Gamebanana page. + /// + public static string Action_GamebananaPage { + get { + return ResourceManager.GetString("Action_GamebananaPage", resourceCulture); + } + } + /// /// Looks up a localized string similar to Import. /// @@ -185,6 +221,42 @@ public static string Action_Keep { } } + /// + /// Looks up a localized string similar to Launch Dolphin. + /// + public static string Action_LaunchDolphin { + get { + return ResourceManager.GetString("Action_LaunchDolphin", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Talk to us!. + /// + public static string Action_Link_Discord { + get { + return ResourceManager.GetString("Action_Link_Discord", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Source code. + /// + public static string Action_Link_Github { + get { + return ResourceManager.GetString("Action_Link_Github", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Support us!. + /// + public static string Action_Link_Support { + get { + return ResourceManager.GetString("Action_Link_Support", resourceCulture); + } + } + /// /// Looks up a localized string similar to Maybe Later. /// @@ -206,9 +278,9 @@ public static string Action_No { /// /// Looks up a localized string similar to Ok. /// - public static string Action_OK { + public static string Action_Ok { get { - return ResourceManager.GetString("Action_OK", resourceCulture); + return ResourceManager.GetString("Action_Ok", resourceCulture); } } @@ -239,6 +311,15 @@ public static string Action_PlayOffline { } } + /// + /// Looks up a localized string similar to Randomize. + /// + public static string Action_Randomize { + get { + return ResourceManager.GetString("Action_Randomize", resourceCulture); + } + } + /// /// Looks up a localized string similar to Rename. /// @@ -248,6 +329,15 @@ public static string Action_Rename { } } + /// + /// Looks up a localized string similar to Report. + /// + public static string Action_Report { + get { + return ResourceManager.GetString("Action_Report", resourceCulture); + } + } + /// /// Looks up a localized string similar to Revert. /// @@ -275,6 +365,33 @@ public static string Action_SaveExternalMii { } } + /// + /// Looks up a localized string similar to Submit. + /// + public static string Action_Submit { + get { + return ResourceManager.GetString("Action_Submit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unfavorite. + /// + public static string Action_Unfavorite { + get { + return ResourceManager.GetString("Action_Unfavorite", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Uninstall. + /// + public static string Action_UnInstall { + get { + return ResourceManager.GetString("Action_UnInstall", resourceCulture); + } + } + /// /// Looks up a localized string similar to Update. /// @@ -284,6 +401,15 @@ public static string Action_Update { } } + /// + /// Looks up a localized string similar to View Custom Characters. + /// + public static string Action_ViewCustomChars { + get { + return ResourceManager.GetString("Action_ViewCustomChars", resourceCulture); + } + } + /// /// Looks up a localized string similar to View Mii. /// @@ -302,6 +428,15 @@ public static string Action_ViewMod { } } + /// + /// Looks up a localized string similar to View Room. + /// + public static string Action_ViewRoom { + get { + return ResourceManager.GetString("Action_ViewRoom", resourceCulture); + } + } + /// /// Looks up a localized string similar to Yes. /// @@ -311,6 +446,51 @@ public static string Action_Yes { } } + /// + /// Looks up a localized string similar to Average VR. + /// + public static string Attribute_AverageRoomVr { + get { + return ResourceManager.GetString("Attribute_AverageRoomVr", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to BR. + /// + public static string Attribute_BrAbbreviation { + get { + return ResourceManager.GetString("Attribute_BrAbbreviation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Battle Rating. + /// + public static string Attribute_BrFull { + get { + return ResourceManager.GetString("Attribute_BrFull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Description. + /// + public static string Attribute_Description { + get { + return ResourceManager.GetString("Attribute_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Downloads. + /// + public static string Attribute_Downloads { + get { + return ResourceManager.GetString("Attribute_Downloads", resourceCulture); + } + } + /// /// Looks up a localized string similar to Enabled. /// @@ -320,6 +500,42 @@ public static string Attribute_Enabled { } } + /// + /// Looks up a localized string similar to Friend Code. + /// + public static string Attribute_FriendCode { + get { + return ResourceManager.GetString("Attribute_FriendCode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Gender. + /// + public static string Attribute_Gender { + get { + return ResourceManager.GetString("Attribute_Gender", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Female. + /// + public static string Attribute_Gender_Female { + get { + return ResourceManager.GetString("Attribute_Gender_Female", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Male. + /// + public static string Attribute_Gender_Male { + get { + return ResourceManager.GetString("Attribute_Gender_Male", resourceCulture); + } + } + /// /// Looks up a localized string similar to ID. /// @@ -330,263 +546,929 @@ public static string Attribute_Id { } /// - /// Looks up a localized string similar to Mods Name. + /// Looks up a localized string similar to Images. /// - public static string Attribute_ModName { + public static string Attribute_Images { get { - return ResourceManager.GetString("Attribute_ModName", resourceCulture); + return ResourceManager.GetString("Attribute_Images", resourceCulture); } } /// - /// Looks up a localized string similar to Name. + /// Looks up a localized string similar to Is Online. /// - public static string Attribute_Name { + public static string Attribute_IsOnline { get { - return ResourceManager.GetString("Attribute_Name", resourceCulture); + return ResourceManager.GetString("Attribute_IsOnline", resourceCulture); } } /// - /// Looks up a localized string similar to Title. + /// Looks up a localized string similar to Likes. /// - public static string Attribute_Title { + public static string Attribute_Likes { get { - return ResourceManager.GetString("Attribute_Title", resourceCulture); + return ResourceManager.GetString("Attribute_Likes", resourceCulture); } } /// - /// Looks up a localized string similar to Type. + /// Looks up a localized string similar to Losses. /// - public static string Attribute_Type { + public static string Attribute_Losses { get { - return ResourceManager.GetString("Attribute_Type", resourceCulture); + return ResourceManager.GetString("Attribute_Losses", resourceCulture); } } /// - /// Looks up a localized string similar to Friends. + /// Looks up a localized string similar to Message. /// - public static string PageTitle_Friends { + public static string Attribute_Message { get { - return ResourceManager.GetString("PageTitle_Friends", resourceCulture); + return ResourceManager.GetString("Attribute_Message", resourceCulture); } } /// - /// Looks up a localized string similar to Home. + /// Looks up a localized string similar to Beard Type. /// - public static string PageTitle_Home { + public static string Attribute_Mii_BeardType { get { - return ResourceManager.GetString("PageTitle_Home", resourceCulture); + return ResourceManager.GetString("Attribute_Mii_BeardType", resourceCulture); } } /// - /// Looks up a localized string similar to My Stuff. + /// Looks up a localized string similar to Creator Name. /// - public static string PageTitle_Mods { + public static string Attribute_Mii_CreatorName { get { - return ResourceManager.GetString("PageTitle_Mods", resourceCulture); + return ResourceManager.GetString("Attribute_Mii_CreatorName", resourceCulture); } } /// - /// Looks up a localized string similar to My Miis. + /// Looks up a localized string similar to New Mii. /// - public static string PageTitle_MyMiis { + public static string Attribute_Mii_DefaultName { get { - return ResourceManager.GetString("PageTitle_MyMiis", resourceCulture); + return ResourceManager.GetString("Attribute_Mii_DefaultName", resourceCulture); } } /// - /// Looks up a localized string similar to My profiles. + /// Looks up a localized string similar to Eyebrow Type. /// - public static string PageTitle_MyProfiles { + public static string Attribute_Mii_EyebrowType { get { - return ResourceManager.GetString("PageTitle_MyProfiles", resourceCulture); + return ResourceManager.GetString("Attribute_Mii_EyebrowType", resourceCulture); } } /// - /// Looks up a localized string similar to Room Details. + /// Looks up a localized string similar to Facial Feature. /// - public static string PageTitle_RoomDetails { + public static string Attribute_Mii_FacialFeature { get { - return ResourceManager.GetString("PageTitle_RoomDetails", resourceCulture); + return ResourceManager.GetString("Attribute_Mii_FacialFeature", resourceCulture); } } /// - /// Looks up a localized string similar to Rooms. + /// Looks up a localized string similar to Favorite Color. /// - public static string PageTitle_Rooms { + public static string Attribute_Mii_FavColor { get { - return ResourceManager.GetString("PageTitle_Rooms", resourceCulture); + return ResourceManager.GetString("Attribute_Mii_FavColor", resourceCulture); } } /// - /// Looks up a localized string similar to Settings. + /// Looks up a localized string similar to Gender. /// - public static string PageTitle_Settings { + public static string Attribute_Mii_Gender { get { - return ResourceManager.GetString("PageTitle_Settings", resourceCulture); + return ResourceManager.GetString("Attribute_Mii_Gender", resourceCulture); } } /// - /// Looks up a localized string similar to Config Not Finished. + /// Looks up a localized string similar to Female. /// - public static string State_ConfigNotFinished { + public static string Attribute_Mii_Gender_Female { get { - return ResourceManager.GetString("State_ConfigNotFinished", resourceCulture); + return ResourceManager.GetString("Attribute_Mii_Gender_Female", resourceCulture); } } /// - /// Looks up a localized string similar to Extracting files.... + /// Looks up a localized string similar to Male. /// - public static string State_Extracting { + public static string Attribute_Mii_Gender_Male { get { - return ResourceManager.GetString("State_Extracting", resourceCulture); + return ResourceManager.GetString("Attribute_Mii_Gender_Male", resourceCulture); } } /// - /// Looks up a localized string similar to Installing.... + /// Looks up a localized string similar to Glasses Color. /// - public static string State_Installing { + public static string Attribute_Mii_GlassesColor { get { - return ResourceManager.GetString("State_Installing", resourceCulture); + return ResourceManager.GetString("Attribute_Mii_GlassesColor", resourceCulture); } } /// - /// Looks up a localized string similar to Loading.... + /// Looks up a localized string similar to Glasses Type. /// - public static string State_Loading { + public static string Attribute_Mii_GlassesType { get { - return ResourceManager.GetString("State_Loading", resourceCulture); + return ResourceManager.GetString("Attribute_Mii_GlassesType", resourceCulture); } } /// - /// Looks up a localized string similar to No Server. + /// Looks up a localized string similar to Hair Color. /// - public static string State_NoServer { + public static string Attribute_Mii_HairColor { get { - return ResourceManager.GetString("State_NoServer", resourceCulture); + return ResourceManager.GetString("Attribute_Mii_HairColor", resourceCulture); } } /// - /// Looks up a localized string similar to Updating.... + /// Looks up a localized string similar to Hair Type. /// - public static string State_Updating { + public static string Attribute_Mii_HairType { get { - return ResourceManager.GetString("State_Updating", resourceCulture); + return ResourceManager.GetString("Attribute_Mii_HairType", resourceCulture); } } /// - /// Looks up a localized string similar to Error. + /// Looks up a localized string similar to Head Shape. /// - public static string Term_Error { + public static string Attribute_Mii_HeadShape { get { - return ResourceManager.GetString("Term_Error", resourceCulture); + return ResourceManager.GetString("Attribute_Mii_HeadShape", resourceCulture); } } /// - /// Looks up a localized string similar to General. + /// Looks up a localized string similar to Height. /// - public static string Term_General { + public static string Attribute_Mii_Height { get { - return ResourceManager.GetString("Term_General", resourceCulture); + return ResourceManager.GetString("Attribute_Mii_Height", resourceCulture); } } /// - /// Looks up a localized string similar to No. + /// Looks up a localized string similar to Horizontal Position (Left/Right). /// - public static string Term_No { + public static string Attribute_Mii_HorizontalPosition { get { - return ResourceManager.GetString("Term_No", resourceCulture); + return ResourceManager.GetString("Attribute_Mii_HorizontalPosition", resourceCulture); } } /// - /// Looks up a localized string similar to Offline. + /// Looks up a localized string similar to Eye Color. /// - public static string Term_Offline { + public static string Attribute_Mii_IrishColor { get { - return ResourceManager.GetString("Term_Offline", resourceCulture); + return ResourceManager.GetString("Attribute_Mii_IrishColor", resourceCulture); } } /// - /// Looks up a localized string similar to Online. + /// Looks up a localized string similar to Mirror Hair. /// - public static string Term_Online { + public static string Attribute_Mii_MirrorHair { get { - return ResourceManager.GetString("Term_Online", resourceCulture); + return ResourceManager.GetString("Attribute_Mii_MirrorHair", resourceCulture); } } /// - /// Looks up a localized string similar to Other. + /// Looks up a localized string similar to Mouth Type. /// - public static string Term_Other { + public static string Attribute_Mii_MouthType { get { - return ResourceManager.GetString("Term_Other", resourceCulture); + return ResourceManager.GetString("Attribute_Mii_MouthType", resourceCulture); } } /// - /// Looks up a localized string similar to Speed. + /// Looks up a localized string similar to Mustache Size. /// - public static string Term_Speed { + public static string Attribute_Mii_MustacheSize { get { - return ResourceManager.GetString("Term_Speed", resourceCulture); + return ResourceManager.GetString("Attribute_Mii_MustacheSize", resourceCulture); } } /// - /// Looks up a localized string similar to Success. + /// Looks up a localized string similar to Mustache Type. /// - public static string Term_Success { + public static string Attribute_Mii_MustacheType { get { - return ResourceManager.GetString("Term_Success", resourceCulture); + return ResourceManager.GetString("Attribute_Mii_MustacheType", resourceCulture); } } /// - /// Looks up a localized string similar to Unknown. + /// Looks up a localized string similar to Mustache Vertical Position (Up/Down). /// - public static string Term_Unknown { + public static string Attribute_Mii_MustacheVerticalPosition { get { - return ResourceManager.GetString("Term_Unknown", resourceCulture); + return ResourceManager.GetString("Attribute_Mii_MustacheVerticalPosition", resourceCulture); } } /// - /// Looks up a localized string similar to Warning. + /// Looks up a localized string similar to Mii Name. /// - public static string Term_Warning { + public static string Attribute_Mii_Name { get { - return ResourceManager.GetString("Term_Warning", resourceCulture); + return ResourceManager.GetString("Attribute_Mii_Name", resourceCulture); } } /// - /// Looks up a localized string similar to Yes. + /// Looks up a localized string similar to Nose Type. + /// + public static string Attribute_Mii_NoseType { + get { + return ResourceManager.GetString("Attribute_Mii_NoseType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Rotation (Rotate Left/Right). + /// + public static string Attribute_Mii_Rotation { + get { + return ResourceManager.GetString("Attribute_Mii_Rotation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Size. + /// + public static string Attribute_Mii_Size { + get { + return ResourceManager.GetString("Attribute_Mii_Size", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Skin Color. + /// + public static string Attribute_Mii_SkinColor { + get { + return ResourceManager.GetString("Attribute_Mii_SkinColor", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Spacing in between. + /// + public static string Attribute_Mii_SpaceBetween { + get { + return ResourceManager.GetString("Attribute_Mii_SpaceBetween", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Vertical Position (Up/Down). + /// + public static string Attribute_Mii_VerticalPosition { + get { + return ResourceManager.GetString("Attribute_Mii_VerticalPosition", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Width. + /// + public static string Attribute_Mii_Width { + get { + return ResourceManager.GetString("Attribute_Mii_Width", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Eyebrows. + /// + public static string Attribute_MiiSection_Eyebrows { + get { + return ResourceManager.GetString("Attribute_MiiSection_Eyebrows", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Eyes. + /// + public static string Attribute_MiiSection_Eyes { + get { + return ResourceManager.GetString("Attribute_MiiSection_Eyes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Face. + /// + public static string Attribute_MiiSection_Face { + get { + return ResourceManager.GetString("Attribute_MiiSection_Face", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Facial Hair. + /// + public static string Attribute_MiiSection_FacialHair { + get { + return ResourceManager.GetString("Attribute_MiiSection_FacialHair", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to General. + /// + public static string Attribute_MiiSection_General { + get { + return ResourceManager.GetString("Attribute_MiiSection_General", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Glasses. + /// + public static string Attribute_MiiSection_Glasses { + get { + return ResourceManager.GetString("Attribute_MiiSection_Glasses", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Hair. + /// + public static string Attribute_MiiSection_Hair { + get { + return ResourceManager.GetString("Attribute_MiiSection_Hair", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Lips. + /// + public static string Attribute_MiiSection_Lips { + get { + return ResourceManager.GetString("Attribute_MiiSection_Lips", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Mole. + /// + public static string Attribute_MiiSection_Mole { + get { + return ResourceManager.GetString("Attribute_MiiSection_Mole", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Nose. + /// + public static string Attribute_MiiSection_Nose { + get { + return ResourceManager.GetString("Attribute_MiiSection_Nose", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Mods Name. + /// + public static string Attribute_ModName { + get { + return ResourceManager.GetString("Attribute_ModName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Name. + /// + public static string Attribute_Name { + get { + return ResourceManager.GetString("Attribute_Name", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Players. + /// + public static string Attribute_PlayerCount { + get { + return ResourceManager.GetString("Attribute_PlayerCount", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Priority. + /// + public static string Attribute_Priority { + get { + return ResourceManager.GetString("Attribute_Priority", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Races played. + /// + public static string Attribute_RacesPlayed { + get { + return ResourceManager.GetString("Attribute_RacesPlayed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sort by. + /// + public static string Attribute_SortBy { + get { + return ResourceManager.GetString("Attribute_SortBy", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Speed. + /// + public static string Attribute_Speed { + get { + return ResourceManager.GetString("Attribute_Speed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Status. + /// + public static string Attribute_Status { + get { + return ResourceManager.GetString("Attribute_Status", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Offline. + /// + public static string Attribute_Status_Offline { + get { + return ResourceManager.GetString("Attribute_Status_Offline", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Online. + /// + public static string Attribute_Status_Online { + get { + return ResourceManager.GetString("Attribute_Status_Online", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Time Online. + /// + public static string Attribute_TimeOnline { + get { + return ResourceManager.GetString("Attribute_TimeOnline", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Title. + /// + public static string Attribute_Title { + get { + return ResourceManager.GetString("Attribute_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Total games played. + /// + public static string Attribute_TotalGamesPlayed { + get { + return ResourceManager.GetString("Attribute_TotalGamesPlayed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Total games won. + /// + public static string Attribute_TotalGamesWon { + get { + return ResourceManager.GetString("Attribute_TotalGamesWon", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Type. + /// + public static string Attribute_Type { + get { + return ResourceManager.GetString("Attribute_Type", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Views. + /// + public static string Attribute_Views { + get { + return ResourceManager.GetString("Attribute_Views", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to VR. + /// + public static string Attribute_VrAbbreviation { + get { + return ResourceManager.GetString("Attribute_VrAbbreviation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Versus Rating. + /// + public static string Attribute_VrFull { + get { + return ResourceManager.GetString("Attribute_VrFull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Wins. + /// + public static string Attribute_Wins { + get { + return ResourceManager.GetString("Attribute_Wins", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Friends. + /// + public static string ListTitle_Friends { + get { + return ResourceManager.GetString("ListTitle_Friends", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Miis. + /// + public static string ListTitle_Miis { + get { + return ResourceManager.GetString("ListTitle_Miis", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Players. + /// + public static string ListTitle_Players { + get { + return ResourceManager.GetString("ListTitle_Players", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Rooms. + /// + public static string ListTitle_Rooms { + get { + return ResourceManager.GetString("ListTitle_Rooms", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Friends. + /// + public static string PageTitle_Friends { + get { + return ResourceManager.GetString("PageTitle_Friends", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Home. + /// + public static string PageTitle_Home { + get { + return ResourceManager.GetString("PageTitle_Home", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to My Stuff. + /// + public static string PageTitle_Mods { + get { + return ResourceManager.GetString("PageTitle_Mods", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to My Miis. + /// + public static string PageTitle_MyMiis { + get { + return ResourceManager.GetString("PageTitle_MyMiis", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to My profiles. + /// + public static string PageTitle_MyProfiles { + get { + return ResourceManager.GetString("PageTitle_MyProfiles", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Room Details. + /// + public static string PageTitle_RoomDetails { + get { + return ResourceManager.GetString("PageTitle_RoomDetails", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Rooms. + /// + public static string PageTitle_Rooms { + get { + return ResourceManager.GetString("PageTitle_Rooms", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Settings. + /// + public static string PageTitle_Settings { + get { + return ResourceManager.GetString("PageTitle_Settings", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Mii Carousel. + /// + public static string PopupTitle_MiiCarousel { + get { + return ResourceManager.GetString("PopupTitle_MiiCarousel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Mii Editor. + /// + public static string PopupTitle_MiiEditor { + get { + return ResourceManager.GetString("PopupTitle_MiiEditor", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Mii Selector. + /// + public static string PopupTitle_MiiSelector { + get { + return ResourceManager.GetString("PopupTitle_MiiSelector", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Mod Browser. + /// + public static string PopupTitle_ModBrowser { + get { + return ResourceManager.GetString("PopupTitle_ModBrowser", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Mod Details. + /// + public static string PopupTitle_ModDetails { + get { + return ResourceManager.GetString("PopupTitle_ModDetails", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to America. + /// + public static string Region_America { + get { + return ResourceManager.GetString("Region_America", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Australia. + /// + public static string Region_Australia { + get { + return ResourceManager.GetString("Region_Australia", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to China. + /// + public static string Region_China { + get { + return ResourceManager.GetString("Region_China", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Europe. + /// + public static string Region_Europe { + get { + return ResourceManager.GetString("Region_Europe", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Japan. + /// + public static string Region_Japan { + get { + return ResourceManager.GetString("Region_Japan", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to South Korea. + /// + public static string Region_SouthKorea { + get { + return ResourceManager.GetString("Region_SouthKorea", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Taiwan. + /// + public static string Region_Taiwan { + get { + return ResourceManager.GetString("Region_Taiwan", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Config Not Finished. + /// + public static string State_ConfigNotFinished { + get { + return ResourceManager.GetString("State_ConfigNotFinished", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Custom. + /// + public static string State_Custom { + get { + return ResourceManager.GetString("State_Custom", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error. + /// + public static string State_Error { + get { + return ResourceManager.GetString("State_Error", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Extracting files.... + /// + public static string State_Extracting { + get { + return ResourceManager.GetString("State_Extracting", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Installed. + /// + public static string State_Installed { + get { + return ResourceManager.GetString("State_Installed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Installing.... + /// + public static string State_Installing { + get { + return ResourceManager.GetString("State_Installing", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Loading.... + /// + public static string State_Loading { + get { + return ResourceManager.GetString("State_Loading", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No license. + /// + public static string State_NoLicense { + get { + return ResourceManager.GetString("State_NoLicense", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No name. + /// + public static string State_NoName { + get { + return ResourceManager.GetString("State_NoName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No profiles. + /// + public static string State_NoProfiles { + get { + return ResourceManager.GetString("State_NoProfiles", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No Server. + /// + public static string State_NoServer { + get { + return ResourceManager.GetString("State_NoServer", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Success. + /// + public static string State_Success { + get { + return ResourceManager.GetString("State_Success", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unknown. + /// + public static string State_Unknown { + get { + return ResourceManager.GetString("State_Unknown", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Updating.... + /// + public static string State_Updating { + get { + return ResourceManager.GetString("State_Updating", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Warning. /// - public static string Term_Yes { + public static string State_Warning { get { - return ResourceManager.GetString("Term_Yes", resourceCulture); + return ResourceManager.GetString("State_Warning", resourceCulture); } } } diff --git a/WheelWizard/Resources/Languages/Common.cs.resx b/WheelWizard/Resources/Languages/Common.cs.resx index a4c5284f..1842c1ac 100644 --- a/WheelWizard/Resources/Languages/Common.cs.resx +++ b/WheelWizard/Resources/Languages/Common.cs.resx @@ -1,9 +1,10 @@ - + - + @@ -13,9 +14,391 @@ 1.3 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + + + Prohlížet + + + Zrušit + + + Smazat + + + Upravit + + + Exportovat + + + Importovat + + + Nainstalovat + + + Nechat + + + Možná Později + + + Ne + + + OK + + + Otevřít Složku + + + Hrát + + + Hrát Offline + + + Přejmenovat + + + Vrátit + + + Uložit + + + Aktualizovat + + + Zobrazit Mod + + + Ano + + + ID + + + Jméno Modu + + + Jméno + + + Název + + + Typ + + + Kamarádi + + + Domů + + + My Stuff + + + Moje profily + + + Místnosti + + + Nastavení + + + Instalování... + + + Načítání... + + + Žádný server + + + Aktualizování... + + + Chyba + + + Offline + + + Online + + + Rychlost + + + Varování + + + Kamarádi + + + Hráči + + + Místnosti + + + Žádný účet + + + Žádné jméno + + + Žádné profily + + + Amerika + + + Austrálie + + + Čína + + + Evropa + + + Japonsko + + + Jižní Korea + + + Tchaj-wan + + + Prohry + + + Hráči + + + Čas Online + + + Vyhry + + + Mluvte s námi! + + + Zpět + + + Stáhnout a Nainstalovat + + + Duplikovat + + + Dát do Oblíbených + + + Stránka Gamebanana + + + Spouštět Dolphin + + + Zdrojový kód + + + Podpořte nás! + + + Náhodně Rozdělit + + + Nahlásít + + + Odeslat + + + Odebrat z Oblíbených + + + Odinstalovat + + + Zobrazit Mii + + + BH + + + Bitevní Hodnocení + + + Popis + + + Stažení + + + Pohlaví + + + Žena + + + Muž + + + Obrázky + + + Je Online + + + Lajky + + + Vzkaz + + + Obočí + + + Oči + + + Obličej + + + Vousy + + + Obecný + + + Brýle + + + Vlasy + + + Rty + + + Mateřské Znaménko + + + Nos + + + Typ Vousů + + + Jméno Tvůrce + + + Nové Mii + + + Typ Obočí + + + Obličejové Rysy + + + Oblíbená Barva + + + Pohlaví + + + Žena + + + Muž + + + Barva Brýlí + + + Typ Brýlí + + + Barva Vlasů + + + Typ Vlasů + + + Tvar Hlavy + + + Výška + + + Barva Očí + + + Zrcadlové Vlasy + + + Typ Úst + + + Velikost Kníru + + + Typ Kníru + + + Mii Jmeno + + + Typ Nosu + + + Velikost + + + Barva Pleti + + + Šířka + + + Přednost + + + Seřadit podle + + + Status + + + Zhlédnutí + + + ZH + + + Závodní Hodnocení + + + Miiy + + + Moje Miiy + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Common.de.resx b/WheelWizard/Resources/Languages/Common.de.resx index f29ba628..5773387b 100644 --- a/WheelWizard/Resources/Languages/Common.de.resx +++ b/WheelWizard/Resources/Languages/Common.de.resx @@ -1,9 +1,10 @@ - + - + @@ -13,10 +14,14 @@ 1.3 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + Installation nicht abgeschlossen @@ -31,10 +36,10 @@ Spielen - Spiele Offline + Offline spielen - Aktualisierung + Aktualisieren Freunde @@ -54,15 +59,9 @@ Einstellungen - - Allgemein - - + Online - - Sonstiges - Installiert... @@ -85,7 +84,7 @@ Löschen - Alles Deaktiverien + Alle deaktivieren Alle aktivieren @@ -108,10 +107,10 @@ Titel - + Offline - + Unbekannt @@ -133,45 +132,39 @@ Typ - Raum Details + Raumdetails - + Fehler Nein - + Geschwindigkeit - + Warnung Ja - - Nein - - - Ja - - Vielleicht Später + Vielleicht später - + Ok Rückgängig machen - Mod Name + Mod-Name - Extrahiere Datein + Dateien werden extrahiert... - + Erfolgreich @@ -180,4 +173,325 @@ Mod ansehen + + Raum anzeigen + + + Kein Name + + + Freunde + + + Spieler + + + Keine Lizenz + + + Keine Profile + + + Amerika + + + Australien + + + China + + + Europa + + + Japan + + + Südkorea + + + Taiwan + + + Freundescode + + + Verlorene Rennen + + + Spieler + + + Gewonnene Rennen + + + Zeit online + + + Gespielte Rennen + + + Räume + + + Rede mit uns! + + + Quellcode + + + Unterstützt uns! + + + Zurück + + + Duplizieren + + + Favorisieren + + + Dolphin starten + + + Zufällig + + + Mii zu "Meine Miis" hinzufügen + + + Entfavorisieren + + + Mii ansehen + + + Durchschnittliche R-Punkte + + + Geschlecht + + + Männlich + + + Weiblich + + + Ist Online + + + Nachricht + + + Priorität + + + Sortieren nach + + + Status + + + Gesamte gewonnene Rennen + + + Gesamte verlorene Rennen + + + Miis + + + Meine Miis + + + RP + + + R-Punkte + + + Geschlecht + + + Männlich + + + Weiblich + + + Manuell durchführen + + + Herunterladen und Installieren + + + Gamebanana-Seite + + + Melden + + + Senden + + + Deinstallieren + + + Benutzerdefinierte Charaktere ansehen + + + Beschreibung + + + Downloads + + + Bilder + + + Likes + + + Augenbrauen + + + Augen + + + Gesicht + + + Gesichtsbehaarung + + + Brillen + + + Haare + + + Lippen + + + Leberfleck + + + Nase + + + Bart-Typ + + + Ersteller-Name + + + Neues Mii + + + Augenbrauenform + + + Gesichtsmerkmale + + + Lieblingsfarbe + + + Brillenfarbe + + + Brillenform + + + Haarfarbe + + + Frisur + + + Kopfform + + + Größe + + + Horizontale Position (links/rechts) + + + Augenfarbe + + + Frisur spiegeln + + + Mundform + + + Bartgröße + + + Bartform + + + Vertikale Bartposition (hoch/runter) + + + Mii Name + + + Nasenform + + + Drehen (links/rechts) + + + Größe + + + Hautfarbe + + + Abstand dazwischen + + + Vertikale Position (hoch/runter) + + + Breite + + + Aufrufe + + + Mii Preview + + + Mii Maker + + + Mii Auswähler + + + Mod Browser + + + Details zur Mod + + + Benutzerdefiniert + + + Installiert + + + Allgemein + + + W-Punkte + + + Wettkampf-Rang + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Common.es.resx b/WheelWizard/Resources/Languages/Common.es.resx index 5d4aedaf..13786a97 100644 --- a/WheelWizard/Resources/Languages/Common.es.resx +++ b/WheelWizard/Resources/Languages/Common.es.resx @@ -1,9 +1,10 @@ - + - + @@ -13,10 +14,14 @@ 1.3 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + Aplicar @@ -57,7 +62,7 @@ No - + Ok @@ -141,43 +146,172 @@ Actualizando... - + Error - - General - - - No - - + Desconectado - + En línea - - Otro - - + Velocidad - + Éxito - + Desconocido - + Aviso - - Si - Buscar Mirar Mod + + Mirar sala + + + Mirar Mii + + + Amigos + + + Jugadores + + + Salas + + + Sin licencia + + + Sin nombre + + + Sin perfiles + + + América + + + Australia + + + China + + + Europa + + + Japón + + + Corea del Sur + + + Taiwan + + + Clave de amigo + + + Derrotas + + + Jugadores + + + Carreras jugadas + + + Tiempo en línea + + + Victorias + + + ¡Apoyanos! + + + Codigo Fuente + + + ¡Habla con nosotros! + + + Atrás + + + Duplicar + + + Hacer favorito + + + Lanzar Dolphin + + + Aleatorizar + + + Añadir Mii a "Mis Miis" + + + Quitar de favoritos + + + PC Promedio + + + Género + + + Mujer + + + Hombre + + + Está Online + + + Mensaje + + + Prioridad + + + Ordernar por + + + Estado + + + Partidas jugadas + + + Partidas ganadas + + + Miis + + + Mis Miis + + + Género + + + Mujer + + + Hombre + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Common.fr.resx b/WheelWizard/Resources/Languages/Common.fr.resx index 01431560..a55f9a49 100644 --- a/WheelWizard/Resources/Languages/Common.fr.resx +++ b/WheelWizard/Resources/Languages/Common.fr.resx @@ -1,9 +1,10 @@ - + - + @@ -13,10 +14,14 @@ 1.3 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + Amis @@ -31,17 +36,11 @@ Salles - Paremètres - - - Général + Paramètres - + En Ligne - - Autre - Accueil @@ -129,27 +128,21 @@ Détail de la salle - + Erreur - - Non - - + Hors-Ligne - + Vitesse - + Inconnu - + Avertisemment - - Oui - Oui @@ -159,7 +152,7 @@ Peut être plus tard - + Ok @@ -171,7 +164,7 @@ Extraction des fichers... - + Succès @@ -180,4 +173,325 @@ Détail du Mod + + Voir la salle + + + Aucun nom + + + Salles + + + Amis + + + Joueurs + + + Aucun Permis + + + Aucun Profil + + + Amérique + + + Australie + + + Chine + + + Europe + + + Japon + + + Corée Du Sud + + + Taiwan + + + Code Ami + + + Défaites + + + Joueurs + + + Course Jouées + + + Temps en ligne + + + Victoire + + + Supportez-nous ! + + + Code Source + + + Parlez-nous-en ! + + + Retour + + + Dupliquer + + + Favoris + + + Lancer Dolphin + + + Aléatoire + + + Ajouter un Mii dans "Mes Miis" + + + Retirer des favoris + + + Voir le Mii + + + Point Course en moyenne + + + Genre + + + Femme + + + Homme + + + Est en ligne + + + Message + + + Priorité + + + Trier par + + + Status + + + Nombre de courses faites + + + Nombre de courses gagnées + + + Miis + + + Mes Miis + + + Genre + + + Femme + + + Homme + + + Faire manuellement + + + Télécharger et installer + + + Page Gamebanana + + + Signaler + + + Soumettre + + + Désinstaller + + + Voir les caractères spéciaux + + + PTS.B + + + Points Bataille + + + Description + + + Téléchargements + + + Images + + + J'aimes + + + Cils + + + Oeil + + + Tête + + + Pillosité faciale + + + Général + + + Lunettes + + + Cheveux + + + Lèvres + + + Grain de beauté + + + Nez + + + Type de barbe + + + Nom de l'auteur.rice + + + John Doe + + + Type de cils + + + Élément facial + + + Couleur favorite + + + Couleur des lunettes + + + Type de lunettes + + + Couleur des cheveux + + + Type des cheveux + + + Forme du visage + + + Taille + + + Position horizontale + + + Couleur des yeux + + + Cheveux en miroir + + + Type de bouches + + + Taille de la moustache + + + Type de moustache + + + Position verticale de la moustache + + + Nom du Mii + + + Type de nez + + + Rotation (Gauche / Droite) + + + Taille + + + Couleur de peau + + + Espace entre deux éléments + + + Position verticale + + + Largeur + + + Vues + + + PTS.C + + + Points Course + + + Vue d'ensemble du Mii + + + Éditeur de Mii + + + Sélection de Mii + + + Navigateur des mods + + + Détails du mod + + + Installé + + + Personnalisé + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Common.it.resx b/WheelWizard/Resources/Languages/Common.it.resx index a399773f..da815227 100644 --- a/WheelWizard/Resources/Languages/Common.it.resx +++ b/WheelWizard/Resources/Languages/Common.it.resx @@ -1,9 +1,10 @@ - + - + @@ -13,10 +14,14 @@ 1.3 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + Applica @@ -60,7 +65,7 @@ No - + Ok @@ -144,40 +149,349 @@ Aggiornando... - + Errore - - Generale - - - No - - + Offline - + Online - - Altro - - + Velocità - + Successo - + Sconosciuto - + Avviso - - - Visualizza + + Vedi la stanza + + + Amici + + + Giocatori + + + Stanze + + + Nessuna patente + + + Nessun nome + + + Nessun profilo + + + America + + + Australia + + + Cina + + + Europa + + + Giappone + + + Corea del Sud + + + Taiwan + + + Codice Amico + + + Sconfitte + + + Giocatori + + + Gare giocate + + + Tempo online + + + Vittorie + + + Supportaci! + + + Codice sorgente + + + Parla con noi! + + + Ritorna + + + Duplica + + + Favorito + + + Lancia Dolphin + + + Casuale + + + Aggiungi Mii a "I Miei Mii" + + + Rimuovi dai Favoriti + + + Vedi Mii + + + Media dei Punti Corsa + + + Genere + + + Femmina + + + Maschio + + + È Online + + + Messaggio + + + Priorità + + + Ordina per + + + Stato + + + Numero di partite giocate + + + Numero di partite vinte + + + Mii + + + I miei Mii + + + Genere + + + Femmina + + + Maschio + + + Fai manualmente + + + Scarica ed installa + + + Pagina di GameBanana + + + Segnala + + + Invia + + + Disinstalla + + + Vedi personaggi personalizzati + + + PB + + + Punti Battaglia + + + Descrizione + + + Download + + + Immagini + + + Mi Piace + + + Ciglia + + + Occhi + + + Faccia + + + Peli facciali + + + Generale + + + Occhiali + + + Capelli + + + Labbra + + + Neo + + + Naso + + + Tipo di barba + + + Nome dell'autore + + + Nuovo Mii + + + Tipo di ciglia + + + Caratteristiche facciali + + + Colore preferito + + + Colore degli occhiali + + + Tipo di occhiali + + + Colore dei capelli + + + Tipo di capelli + + + Forma del viso + + + Altezza + + + Posizione orizzontale (Sinistra/Destra) + + + Colore degli occhi + + + Capelli riflessi + + + Tipo di bocca + + + Grandezza dei baffi + + + Tipo di baffi + + + Posizione verticale dei baffi (Su/Giù) + + + Nome del Mii + + + Tipo di naso + + + Rotazione (Ruota a Sinistra/Destra) + + + Dimensione + + + Colore della pelle + + + Spaziatura nel mezzo + + + Posizione verticale (Su/Giù) + + + Larghezza + + + Visualizzazioni + + + PC + + + Punti Corsa + + + Visualizzatore Mii + + + Creatore Mii + + + Selettore Mi + + + Ricercatore delle mod + + + Dettagli della mod + + + Personalizzato + + + Installato + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Common.ja.resx b/WheelWizard/Resources/Languages/Common.ja.resx index d1663f52..6a3fdc75 100644 --- a/WheelWizard/Resources/Languages/Common.ja.resx +++ b/WheelWizard/Resources/Languages/Common.ja.resx @@ -1,9 +1,10 @@ - + - + @@ -13,10 +14,14 @@ 1.3 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + 削除 @@ -72,15 +77,9 @@ アップデート - - 一般 - - + オンライン - - その他 - インストール中... @@ -96,10 +95,10 @@ タイトル - + 不明 - + オフライン @@ -132,7 +131,7 @@ いいえ - + OK @@ -156,28 +155,343 @@ ファイル展開中... - + エラー - - いいえ - - + 速度 - + 成功 - + 警告 - - はい - 探す - Modを閲覧 + Modを見る + + + ルームを見る + + + フレンド + + + プレイヤー + + + ライセンスなし + + + Player + + + プロフィールなし + + + アメリカ合衆国 + + + オーストラリア + + + 中国 + + + ヨーロッパ + + + 日本 + + + 韓国 + + + 台湾 + + + フレンドコード + + + 負け + + + プレイヤー + + + プレイしたレース数 + + + 勝ち + + + ルーム + + + オンライン時間 + + + サポート + + + ソースコード + + + お問い合わせ + + + 戻る + + + コピー + + + お気に入り + + + Dolphinを起動する + + + ランダム + + + Miiを"自分のMii"に追加する + + + お気に入り解除 + + + Miiを見る + + + 平均VR + + + 性別 + + + + + + + + + オンラインかどうか + + + メッセージ + + + 優先度 + + + 並び替え + + + 状態 + + + プレイした総回数 + + + 勝利した総回数 + + + Mii + + + 自分のMii + + + 手動で行う + + + ダウンロードとインストール + + + Gamebananaのページ + + + 報告する + + + アンインストール + + + BR + + + バトルレーティング + + + 説明 + + + ダウンロード + + + 性別 + + + + + + + + + 画像 + + + いいね + + + 閲覧 + + + VR + + + バーサスレーティング + + + Miiスライダー + + + Miiエディター + + + Miiセレクター + + + Modブラウザー + + + Modの詳細 + + + カスタム + + + インストール済み + + + 提出 + + + 特殊記号を見る + + + 眉毛 + + + + + + + + + ひげ + + + 一般 + + + メガネ + + + + + + + + + ホクロ + + + + + + ひげの種類 + + + 作成者の名前 + + + 新しいMii + + + 眉毛の種類 + + + 顔立ち + + + 好きな色 + + + メガネの色 + + + メガネの種類 + + + 髪の色 + + + 髪の種類 + + + 顔の形 + + + 高さ + + + 横の位置 (左/右) + + + 目の色 + + + 髪型の反転 + + + 口の種類 + + + 口ひげの大きさ + + + 口ひげの種類 + + + 口ひげの縦の位置 (上/下) + + + Miiの名前 + + + 鼻の種類 + + + 回転 (左/右に回転する) + + + 大きさ + + + 肌の色 + + + 間隔 + + + 縦の位置 (上/下) + + + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Common.ko.resx b/WheelWizard/Resources/Languages/Common.ko.resx index 9db87e06..e0eb43eb 100644 --- a/WheelWizard/Resources/Languages/Common.ko.resx +++ b/WheelWizard/Resources/Languages/Common.ko.resx @@ -1,9 +1,10 @@ - + - + @@ -13,10 +14,14 @@ 1.3 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + 적용 @@ -60,7 +65,7 @@ 아니오 - + OK @@ -147,37 +152,307 @@ 업데이트 중... - + 에러 - - 일반 - - - 아니요 - - + 오프라인 - + 온라인 - - 기타 - - + 속도 - + 성공 - + 불명 - + 경고 - - + + 방을 보기 + + + 친구 + + + 플레이어 + + + + + + 라이센스 없음 + + + 이름 없음 + + + 프로필 없음 + + + 아메리카 + + + 호주 + + + 중국 + + + 유럽 + + + 일본 + + + 대한민국 + + + 대만 + + + 친구 코드 + + + 패배 + + + 플레이어 + + + 플레이한 레이스 수 + + + 온라인 시간 + + + 승리 + + + 후원하기! + + + 소스 코드 + + + 문의하기! + + + 뒤로 가기 + + + 복사 + + + 즐겨찾기 + + + Dolphin 실행 + + + 랜덤 + + + "나만의 Mii"에 Mii 추가하기 + + + 즐겨찾기 해제 + + + Mii 보기 + + + 평균 VR + + + 성별 + + + 여성 + + + 남성 + + + 온라인 상태 + + + 메세지 + + + 우선도 + + + 정렬 + + + 상태 + + + 총 플레이 횟수 + + + 총 승리 횟수 + + + Mii + + + 성별 + + + 여성 + + + 남성 + + + 직접 하기 + + + 다운로드 및 설치 + + + 게임바나나 페이지 + + + 신고 + + + 보내기 + + + 삭제 + + + 커스텀 캐릭터 보기 + + + BR + + + 배틀 레이팅 + + + 설명 + + + 다운로드수 + + + 이미지 + + + 좋아요 수 + + + 눈썹 + + + + + + 얼굴 + + + 얼굴 털 + + + 정보 + + + 안경 + + + 머리카락 + + + 입술 + + + 뾰루지 + + + + + + 수염 종류 + + + 제작자 이름 + + + 새 Mii + + + 눈썹 종류 + + + 얼굴 특징 + + + 좋아하는 색상 + + + 안경 색상 + + + 안경 종류 + + + 머리카락 색상 + + + 머리카락 종류 + + + 얼굴형 + + + + + + 수평 위치(왼쪽/오른쪽) + + + 눈 색상 + + + 머리카락 반전 + + + 입 종류 + + + 콧수염 크기 + + + 콧수염 종류 + + + 콧수염 수직 위치(위/아래) + + + Mii 이름 + + + 코 종류 + + + 회전(반시계/시계 방향) + + + 보기 + + + VR + + + VS레이팅 \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Common.nl.resx b/WheelWizard/Resources/Languages/Common.nl.resx index 91ba4d77..dc93d742 100644 --- a/WheelWizard/Resources/Languages/Common.nl.resx +++ b/WheelWizard/Resources/Languages/Common.nl.resx @@ -1,9 +1,10 @@ - + - + @@ -13,10 +14,14 @@ 1.3 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + My Stuff @@ -31,18 +36,12 @@ Startpagina - Lobbies + Kamers Vrienden - - Algemeen - - - Overig - - + Online @@ -55,7 +54,7 @@ Updaten... - Installeer + Installeren Speel @@ -64,28 +63,28 @@ Speel offline - Update + Updaten... - Instellingen niet compleet + Config niet voltooid Geen server - + Onbekend - Aan/Uit + Ingeschakeld Titel - Alles uit zetten + Alles uitschakelen - Alles aan zetten + Alles inschakelen Importeren @@ -94,28 +93,28 @@ Verwijderen - Open Map + Open map - Hernoem + Hernoemen - + Offline Naam - Bewerk + Bewerken - Annuleer + Annuleren Opslaan - - Error + + Fout ID @@ -127,12 +126,12 @@ Vriendcode kopiëren - Lobby Info + Kamerinformatie Exporteren - + Waarschuwing @@ -144,20 +143,20 @@ Nee - + Snelheid Behouden - - Oke + + Oké - Mod naam + Modnaam - - Success + + Succes Ongedaan maken @@ -168,17 +167,11 @@ Misschien later - - Nee - - - Ja - - Zoek + Zoeken - Bekijk Mod + Bekijk mod Mijn Mii's @@ -195,4 +188,310 @@ Terug + + Voeg toe aan favorieten + + + Verwijder uit favorieten + + + Bekijk kamer + + + Geen naam + + + Overwinningen + + + Verloren + + + Races gespeeld + + + Geen rijbewijs + + + Geen profielen + + + Europa + + + Japan + + + China + + + Zuid-Korea + + + Taiwan + + + Amerika + + + Australië + + + Vrienden + + + Vriendcode + + + Spelers + + + Spelers + + + Tijd online + + + Kamers + + + Mii's + + + Steun ons! + + + Source code + + + Kom gezellig praten! + + + Bericht + + + Gemiddelde rp. + + + Dolphin opstarten + + + Willekeurig + + + Geslacht + + + Vrouw + + + Man + + + Is online + + + Status + + + Prioriteit + + + Sorteren op + + + Totaal gespeelde wedstrijden + + + Totaal gewonnen wedstrijden + + + rp. + + + Racepunten + + + gp. + + + Gevechtspunten + + + Aangepast + + + Handmatig doen + + + Omschrijving + + + Downloads + + + Afbeeldingen + + + Weergaven + + + Vind-ik-leuks + + + Deïnstalleren + + + Melden + + + GameBanana-pagina + + + Geïnstalleerd + + + Download en installeren + + + Mod-details + + + Mod-browser + + + Mii-selector + + + Mii-editor + + + Ogen + + + Wenkbrauwen + + + Gezicht + + + Gezichtshaar + + + Algemeen + + + Bril + + + Haar + + + Lippen + + + Moedervlek + + + Neus + + + Baard + + + Gemaakt door + + + Wenkbrauw + + + Gezichtseigenschap + + + Lievelingskleur + + + Brilkleur + + + Brilvorm + + + Haarkleur + + + Haartype + + + Hoofdvorm + + + Lengte + + + Horizontale positie (links/rechts) + + + Oogkleur + + + Haar spiegelen + + + Mond + + + Snorgrootte + + + Snor + + + Verticale snorpositie (omhoog/omlaag) + + + Mii-naam + + + Neusvorm + + + Rotatie (links/rechts) + + + Grootte + + + Huidskleur + + + Tussenruimte + + + Verticale positie (omhoog/omlaag) + + + Breedte + + + Mii-carrousel + + + Nieuwe Mii + + + Geslacht + + + Vrouw + + + Man + + + Speciale tekens tonen + + + Bevestigen + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Common.pt.resx b/WheelWizard/Resources/Languages/Common.pt.resx index a4c5284f..0eb79bc7 100644 --- a/WheelWizard/Resources/Languages/Common.pt.resx +++ b/WheelWizard/Resources/Languages/Common.pt.resx @@ -18,4 +18,139 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Aplicar + + + Procurar + + + Cancelar + + + Copiar código de amigo + + + Apagar + + + Desativar tudo + + + Editar + + + Ativar tudo + + + Exportar + + + Importar + + + Instalar + + + Manter + + + Talvez mais tarde + + + Não + + + Ok + + + Abrir pasta + + + Jogar + + + Jogar offline + + + Mudar o nome + + + Reverter + + + Guardar + + + Atualizar + + + Ver Mod + + + Sim + + + Ativado + + + ID + + + Nome dos Mods + + + Nome + + + Título + + + Tipo + + + Amigos + + + Casa + + + My stuff + + + Os meus perfis + + + Detalhes da sala + + + Salas + + + Definições + + + Ver sala + + + Ver Mii + + + Amigos + + + Jogadores + + + Salas não econtradas + + + Sem licença + + + Sem nome + + + Sem perfil + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Common.resx b/WheelWizard/Resources/Languages/Common.resx index 8d5e6e8e..32b1de77 100644 --- a/WheelWizard/Resources/Languages/Common.resx +++ b/WheelWizard/Resources/Languages/Common.resx @@ -1,9 +1,10 @@ - + - + @@ -13,10 +14,14 @@ 1.3 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + Home @@ -36,13 +41,7 @@ Friends - - General - - - Other - - + Online @@ -72,7 +71,7 @@ No Server - + Unknown @@ -99,7 +98,7 @@ Open Folder - + Offline @@ -114,7 +113,7 @@ Save - + Error @@ -132,7 +131,7 @@ Export - + Warning @@ -144,19 +143,19 @@ No - + Speed Keep - + Ok Mods Name - + Success @@ -168,12 +167,6 @@ Maybe Later - - No - - - Yes - Browse @@ -195,4 +188,310 @@ Back + + Unfavorite + + + Favorite + + + View Room + + + Average VR + + + No name + + + Losses + + + Wins + + + Races played + + + No license + + + No profiles + + + Japan + + + Europe + + + China + + + South Korea + + + Taiwan + + + America + + + Australia + + + Friends + + + Friend Code + + + Players + + + Players + + + Time Online + + + Rooms + + + Miis + + + Talk to us! + + + Source code + + + Support us! + + + Message + + + Is Online + + + Launch Dolphin + + + Randomize + + + Male + + + Gender + + + Female + + + Status + + + Priority + + + Sort by + + + Total games won + + + Total games played + + + VR + + + BR + + + Battle Rating + + + Versus Rating + + + Custom + + + Do manually + + + Gamebanana page + + + Report + + + Description + + + Images + + + Likes + + + Views + + + Downloads + + + Uninstall + + + Installed + + + Download and Install + + + Mod Details + + + Mod Browser + + + Mii Selector + + + Mii Editor + + + Mii Carousel + + + General + + + Face + + + Hair + + + Eyebrows + + + Eyes + + + Nose + + + Lips + + + Glasses + + + Facial Hair + + + Mole + + + Vertical Position (Up/Down) + + + Size + + + Nose Type + + + Horizontal Position (Left/Right) + + + Mouth Type + + + Hair Color + + + Hair Type + + + Mirror Hair + + + Glasses Type + + + Glasses Color + + + Mii Name + + + Creator Name + + + Height + + + Width + + + Favorite Color + + + Skin Color + + + Head Shape + + + Facial Feature + + + Eye Color + + + Spacing in between + + + Rotation (Rotate Left/Right) + + + Eyebrow Type + + + Mustache Type + + + Mustache Vertical Position (Up/Down) + + + Mustache Size + + + Beard Type + + + New Mii + + + Gender + + + Female + + + Male + + + View Custom Characters + + + Submit + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Common.ru.resx b/WheelWizard/Resources/Languages/Common.ru.resx index a887fc55..37200085 100644 --- a/WheelWizard/Resources/Languages/Common.ru.resx +++ b/WheelWizard/Resources/Languages/Common.ru.resx @@ -1,9 +1,10 @@ - + - + @@ -13,10 +14,14 @@ 1.3 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + Применить @@ -34,13 +39,13 @@ Удалить - Отключить всё + Отключить все Редактировать - Включить всё + Включить все Экспортировать @@ -60,8 +65,8 @@ Нет - - ОК + + OK Открыть папку @@ -70,7 +75,7 @@ Играть - Играть оффлайн + Играть офлайн Переименовать @@ -94,7 +99,7 @@ ID - Название мода + Название модов Название @@ -106,10 +111,10 @@ Друзья - Заглавная + Главная - Моды + My Stuff Мои профили @@ -136,7 +141,7 @@ Распаковываем файлы... - Устанавливаем... + Установка... Загрузка... @@ -147,37 +152,343 @@ Обновляем... - + Ошибка - - Основное - - - Нет - - + Не в сети - + В сети - - Другое - - + Скорость - + Успех - + Неизвестно - + Внимание - - Да + + Просмотреть комнату + + + Друзья + + + Игроки + + + Комнаты + + + Нет удостоверения + + + Нет имени + + + Нет профилей + + + Америка + + + Австралия + + + Китай + + + Европа + + + Япония + + + Южная Корея + + + Тайвань + + + Код друга + + + Поражения + + + Игроки + + + Сыграно гонок + + + Время онлайн + + + Победы + + + Поддержать нас + + + Исходный код + + + Наш Discord-сервер + + + Назад + + + Сделать вручную + + + Загрузить и установить + + + Дублировать + + + Избранное + + + Страница на GameBanana + + + Запустить Dolphin + + + Случайный + + + Пожаловаться + + + Добавить Mii в раздел "Мои Mii" + + + Установлено + + + Подтведрить + + + Удалить + + + Показать Mii + + + Средний ГР + + + БР + + + Боевой рейтинг + + + Описание + + + Загрузки + + + Пол + + + Мужской + + + Женский + + + Изображения + + + В сети + + + Лайки + + + Сообщение + + + Брови + + + Глаза + + + Лицо + + + Усы и борода + + + Основное + + + Очки + + + Волосы + + + Губы + + + Родинка + + + Нос + + + Тип бороды + + + Имя автора + + + Новый Mii + + + Тип бровей + + + Детали лица + + + Любимый цвет + + + Пол + + + Женский + + + Мужской + + + Цвет очков + + + Тип очков + + + Цвет волос + + + Тип волос + + + Форма лица + + + Высота + + + По горизонтали (влево/вправо) + + + Цвет глаз + + + Отразить волосы + + + Тип рта + + + Размер усов + + + Тип усов + + + Усы по вертикали (вверх/вниз) + + + Имя Mii + + + Тип носа + + + Поворот (влево/вправо) + + + Размер + + + Цвет кожи + + + Пространство между глазами + + + По вертикали (вверх/вниз) + + + Ширина + + + Приоритет + + + Отсортировать + + + Статус + + + Всего сыграно игр + + + Всего выиграно игр + + + Просмотры + + + ГР + + + Гоночный рейтинг + + + Mii + + + Убрать из избранного + + + Показать пользовательских персонажей + + + Мои Mii + + + Карусель Mii + + + Редактор Mii + + + Выбор Mii + + + Браузер модов + + + Описание мода \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Common.tr.resx b/WheelWizard/Resources/Languages/Common.tr.resx index b0d80a3e..94cae8dd 100644 --- a/WheelWizard/Resources/Languages/Common.tr.resx +++ b/WheelWizard/Resources/Languages/Common.tr.resx @@ -1,9 +1,10 @@ - + - + @@ -13,10 +14,14 @@ 1.3 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + Uygula @@ -54,11 +59,11 @@ Hayır - + Tamam - Oynat + Oyna Çevrimdışı Oyna @@ -94,7 +99,7 @@ Etkin - Kimlik + ID Modların Adı @@ -115,7 +120,7 @@ Anasayfa - Benim Eşyalarım + My Stuff Profillerim @@ -147,37 +152,346 @@ Güncelleniyor... - + Hata - - Genel - - - Hayır - - + Çevrimdışı - + Çevrimiçi - - Diğer - - + Hız - + Başarı - + Bilinmiyor - + Uyarı - - Evet + + Odayı Görüntüle + + + Arkadaşlar + + + Oyuncular + + + Odalar + + + Lisans yok + + + Ad yok + + + Profil yok + + + Amerika + + + Avustralya + + + Çin + + + Avrupa + + + Japonya + + + Güney Kore + + + Tayvan + + + Arkadaş Kodu + + + Kayıplar + + + Oyuncular + + + Oynanan Yarışlar + + + Çevrimiçi Süre + + + Galibiyetler + + + Bizimle İletişime Geçin! + + + Kaynak Kodu + + + Bize Destek Olun! + + + Geri + + + Çoğalt + + + Favorile + + + Dolphin'i başlat + + + Mii'yi "Benim Mii'lerim"'e ekle + + + Favoriyi kaldır + + + Mii'yi Görüntüle + + + Ortalama KP + + + Cinsiyet + + + Kadın + + + Erkek + + + Online + + + Mesaj + + + Öncelik + + + Durum + + + Mii'ler + + + Cinsiyet + + + Kadın + + + Erkek + + + El ile yap + + + İndir ve Yükle + + + Gamebanana Sayfası + + + Rastgele Seç + + + Raporla + + + Gönder + + + Yüklemeye Kaldır + + + Özel Karakterleri Görüntüle + + + SP + + + Savaş Puanı + + + Açıklama + + + İndirilenler + + + Resimler + + + Beğeniler + + + Kaşlar + + + Gözler + + + Yüz + + + Sakal Bıyık + + + Genel + + + Gözlük + + + Saç + + + Dudaklar + + + Ben + + + Burun + + + Sakal Tipi + + + Yapımcı Adı + + + Yeni Mii + + + Kaş Tipi + + + Yüz Özelliği + + + En Sevdiği Renk + + + Gözlük Rengi + + + Gözlük Tipi + + + Saç Rengi + + + Saç Tipi + + + Kafa Şekli + + + Boy + + + Yatay Konum (Sağ/Sol) + + + Göz Rengi + + + Saç Yönünü Değiştir + + + Ağız Tipi + + + Bıyık Büyüklüğü + + + Bıyık Tipi + + + Bıyık Dikey Konumu (Yukarı/Aşağı) + + + Mii Adı + + + Burun Tipi + + + Çevir (Sağa/Sola Çevir) + + + Boyut + + + Ten Rengi + + + Arası Boşluk + + + Dikey Konum (Yukarı/Aşağı) + + + Genişlik + + + Sıralama Tipi + + + Toplam oynanan oyun + + + Toplam kazanılan oyun + + + Görüntülenme + + + KP + + + Karşılaşma Puanı + + + Benim Mii'lerim + + + Mii Görüntüleyicisi + + + Mii Düzenleyicisi + + + Mii Seçici + + + Mod Gezgini + + + Mod Detayları + + + Özel + + + Yüklendi \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Online.Designer.cs b/WheelWizard/Resources/Languages/Online.Designer.cs deleted file mode 100644 index e11ca3f7..00000000 --- a/WheelWizard/Resources/Languages/Online.Designer.cs +++ /dev/null @@ -1,269 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace WheelWizard.Resources.Languages { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - public class Online { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Online() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WheelWizard.Resources.Languages.Online", typeof(Online).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to View Room. - /// - public static string Button_ViewRoom { - get { - return ResourceManager.GetString("Button_ViewRoom", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Friends. - /// - public static string ListTitle_Friends { - get { - return ResourceManager.GetString("ListTitle_Friends", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Miis. - /// - public static string ListTitle_Miis { - get { - return ResourceManager.GetString("ListTitle_Miis", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Players. - /// - public static string ListTitle_Players { - get { - return ResourceManager.GetString("ListTitle_Players", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Rooms. - /// - public static string ListTitle_Rooms { - get { - return ResourceManager.GetString("ListTitle_Rooms", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No license. - /// - public static string NoLicense { - get { - return ResourceManager.GetString("NoLicense", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No name. - /// - public static string NoName { - get { - return ResourceManager.GetString("NoName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No profiles. - /// - public static string NoProfiles { - get { - return ResourceManager.GetString("NoProfiles", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to America. - /// - public static string Region_America { - get { - return ResourceManager.GetString("Region_America", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Australia. - /// - public static string Region_Australia { - get { - return ResourceManager.GetString("Region_Australia", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to China. - /// - public static string Region_China { - get { - return ResourceManager.GetString("Region_China", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Europe. - /// - public static string Region_Europe { - get { - return ResourceManager.GetString("Region_Europe", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Japan. - /// - public static string Region_Japan { - get { - return ResourceManager.GetString("Region_Japan", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to South Korea. - /// - public static string Region_SouthKorea { - get { - return ResourceManager.GetString("Region_SouthKorea", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Taiwan. - /// - public static string Region_Taiwan { - get { - return ResourceManager.GetString("Region_Taiwan", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avarage VR. - /// - public static string Stat_AverageRoomVr { - get { - return ResourceManager.GetString("Stat_AverageRoomVr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Friend Code. - /// - public static string Stat_FriendCode { - get { - return ResourceManager.GetString("Stat_FriendCode", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Losses. - /// - public static string Stat_Losses { - get { - return ResourceManager.GetString("Stat_Losses", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Players. - /// - public static string Stat_PlayerCount { - get { - return ResourceManager.GetString("Stat_PlayerCount", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Races played. - /// - public static string Stat_RacesPlayed { - get { - return ResourceManager.GetString("Stat_RacesPlayed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Room ID. - /// - public static string Stat_RoomID { - get { - return ResourceManager.GetString("Stat_RoomID", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Time Online. - /// - public static string Stat_TimeOnline { - get { - return ResourceManager.GetString("Stat_TimeOnline", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Wins. - /// - public static string Stat_Wins { - get { - return ResourceManager.GetString("Stat_Wins", resourceCulture); - } - } - } -} diff --git a/WheelWizard/Resources/Languages/Online.ar.resx b/WheelWizard/Resources/Languages/Online.ar.resx deleted file mode 100644 index a4c5284f..00000000 --- a/WheelWizard/Resources/Languages/Online.ar.resx +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Online.cs.resx b/WheelWizard/Resources/Languages/Online.cs.resx deleted file mode 100644 index a4c5284f..00000000 --- a/WheelWizard/Resources/Languages/Online.cs.resx +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Online.de.resx b/WheelWizard/Resources/Languages/Online.de.resx deleted file mode 100644 index 75a5ca9d..00000000 --- a/WheelWizard/Resources/Languages/Online.de.resx +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Kein Name - - - Raum Anzeigen - - - Freunde - - - Spieler - - - Keine Lizenz - - - Keine Profile - - - Amerika - - - Australien - - - China - - - Europa - - - Japan - - - Süd Korea - - - Taiwan - - - Freundescode - - - Verloren - - - Spieler - - - Gewinne - - - Zeit Online - - - Gespielte Rennen - - - Räume - - - Raum ID - - \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Online.es.resx b/WheelWizard/Resources/Languages/Online.es.resx deleted file mode 100644 index 198dfb46..00000000 --- a/WheelWizard/Resources/Languages/Online.es.resx +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Mirar sala - - - Amigos - - - Jugadores - - - Salas - - - Sin licencia - - - Sin nombre - - - Sin perfiles - - - América - - - Australia - - - China - - - Europa - - - Japón - - - Corea del Sur - - - Taiwan - - - Clave de amigo - - - Derrotas - - - Jugadores - - - Carreras jugadas - - - ID de la sala - - - Tiempo en línea - - - Victorias - - \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Online.fi.resx b/WheelWizard/Resources/Languages/Online.fi.resx deleted file mode 100644 index a4c5284f..00000000 --- a/WheelWizard/Resources/Languages/Online.fi.resx +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Online.fr.resx b/WheelWizard/Resources/Languages/Online.fr.resx deleted file mode 100644 index e88a5368..00000000 --- a/WheelWizard/Resources/Languages/Online.fr.resx +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Aucun nom - - - Salles - - - Voir la salle - - - Amis - - - Joueurs - - - Aucun Permis - - - Aucun Profil - - - Amérique - - - Australie - - - Chine - - - Europe - - - Japon - - - Corée Du Sud - - - Taiwan - - - Code Ami - - - Défaites - - - Joueurs - - - Course Jouées - - - ID de la salle - - - Temps en ligne - - - Victoire - - \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Online.it.resx b/WheelWizard/Resources/Languages/Online.it.resx deleted file mode 100644 index 566b35f3..00000000 --- a/WheelWizard/Resources/Languages/Online.it.resx +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Vedi la stanza - - - Amici - - - Giocatori - - - Stanze - - - Nessuna patente - - - Nessun nome - - - Nessun profilo - - - America - - - Australia - - - Cina - - - Europa - - - Giappone - - - Corea del Sud - - - Taiwan - - - Codice Amico - - - Sconfitte - - - Giocatori - - - Gare giocate - - - ID della stanza - - - Tempo online - - - Vittorie - - \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Online.ja.resx b/WheelWizard/Resources/Languages/Online.ja.resx deleted file mode 100644 index 1710c34c..00000000 --- a/WheelWizard/Resources/Languages/Online.ja.resx +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - ルームを見る - - - フレンド - - - プレイヤー - - - ライセンスなし - - - Player - - - プロフィールなし - - - アメリカ合衆国 - - - オーストラリア - - - 中国 - - - ヨーロッパ - - - 日本 - - - 韓国 - - - 台湾 - - - フレンドコード - - - 負け - - - プレイヤー - - - プレイしたレース数 - - - 勝ち - - - ルーム - - - ルームID - - - オンライン時間 - - \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Online.ko.resx b/WheelWizard/Resources/Languages/Online.ko.resx deleted file mode 100644 index 736c56e1..00000000 --- a/WheelWizard/Resources/Languages/Online.ko.resx +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 방을 보기 - - - 친구 - - - 플레이어 - - - - - - 라이센스 없음 - - - 이름 없음 - - - 프로필 없음 - - - 아메리카 - - - 호주 - - - 중국 - - - 유럽 - - - 일본 - - - 대한민국 - - - 대만 - - - 친구 코드 - - - 패배 - - - 플레이어 - - - 플레이한 레이스 수 - - - 룸 ID - - - 온라인 시간 - - - 승리 - - \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Online.nb.resx b/WheelWizard/Resources/Languages/Online.nb.resx deleted file mode 100644 index a4c5284f..00000000 --- a/WheelWizard/Resources/Languages/Online.nb.resx +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Online.nl.resx b/WheelWizard/Resources/Languages/Online.nl.resx deleted file mode 100644 index 951f7d79..00000000 --- a/WheelWizard/Resources/Languages/Online.nl.resx +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Geen naam - - - Gewonnen - - - Verloren - - - Aantal races - - - Bekijk lobby - - - Geen licentie - - - Geen Profielen - - - Europa - - - Japan - - - China - - - Zuid Korea - - - Taiwan - - - America - - - Australië - - - Vrienden - - - Vriendcode - - - Spelers - - - Spelers - - - Tijd online - - - Lobbies - - - Lobby ID - - - Mii's - - \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Online.pt.resx b/WheelWizard/Resources/Languages/Online.pt.resx deleted file mode 100644 index a4c5284f..00000000 --- a/WheelWizard/Resources/Languages/Online.pt.resx +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Online.resx b/WheelWizard/Resources/Languages/Online.resx deleted file mode 100644 index 438d7750..00000000 --- a/WheelWizard/Resources/Languages/Online.resx +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - No name - - - Losses - - - Wins - - - Races played - - - View Room - - - No license - - - No profiles - - - Japan - - - Europe - - - China - - - South Korea - - - Taiwan - - - America - - - Australia - - - Friends - - - Friend Code - - - Players - - - Players - - - Time Online - - - Rooms - - - Room ID - - - Avarage VR - - - Miis - - \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Online.ru.resx b/WheelWizard/Resources/Languages/Online.ru.resx deleted file mode 100644 index 3fc0cda4..00000000 --- a/WheelWizard/Resources/Languages/Online.ru.resx +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Просмотреть комнату - - - Друзья - - - Игроки - - - Комнаты - - - Нет лицензии - - - Нет имени - - - Нет профилей - - - Америка - - - Австралия - - - Китай - - - Европа - - - Япония - - - Южная Корея - - - Тайвань - - - Код друга - - - Поражения - - - Игроки - - - Сыграно гонок - - - ID комнаты - - - Время онлайн - - - Победы - - \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Online.tr.resx b/WheelWizard/Resources/Languages/Online.tr.resx deleted file mode 100644 index 13311cb3..00000000 --- a/WheelWizard/Resources/Languages/Online.tr.resx +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Odayı Görüntüle - - - Arkadaşlar - - - Oyuncular - - - Odalar - - - Lisans yok - - - Ad yok - - - Profil yok - - - Amerika - - - Avustralya - - - Çin - - - Avrupa - - - Japonya - - - Güney Kore - - - Tayvan - - - Arkadaş Kodu - - - Kayıplar - - - Oyuncular - - - Oynanan Yarışlar - - - Oda ID'si - - - Çevrimiçi Süre - - - Galibiyetler - - \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Online.zh.resx b/WheelWizard/Resources/Languages/Online.zh.resx deleted file mode 100644 index a4c5284f..00000000 --- a/WheelWizard/Resources/Languages/Online.zh.resx +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Phrases.Designer.cs b/WheelWizard/Resources/Languages/Phrases.Designer.cs index 175e7d6a..54a2396a 100644 --- a/WheelWizard/Resources/Languages/Phrases.Designer.cs +++ b/WheelWizard/Resources/Languages/Phrases.Designer.cs @@ -62,81 +62,117 @@ internal Phrases() { /// /// Looks up a localized string similar to You can add friends in-game. /// - public static string EmptyText_NoFriends { + public static string EmptyContent_NoFriends { get { - return ResourceManager.GetString("EmptyText_NoFriends", resourceCulture); + return ResourceManager.GetString("EmptyContent_NoFriends", resourceCulture); } } /// /// Looks up a localized string similar to No friends yet!. /// - public static string EmptyText_NoFriends_Title { + public static string EmptyContent_NoFriends_Title { get { - return ResourceManager.GetString("EmptyText_NoFriends_Title", resourceCulture); + return ResourceManager.GetString("EmptyContent_NoFriends_Title", resourceCulture); } } /// - /// Looks up a localized string similar to We cant read the Mii data from your system. Make sure you started the game at least once. + /// Looks up a localized string similar to We can't read the Mii data from your system. Make sure you started the game at least once. /// - public static string EmptyText_NoMiis { + public static string EmptyContent_NoMiis { get { - return ResourceManager.GetString("EmptyText_NoMiis", resourceCulture); + return ResourceManager.GetString("EmptyContent_NoMiis", resourceCulture); } } /// /// Looks up a localized string similar to No Miis yet!. /// - public static string EmptyText_NoMiis_Title { + public static string EmptyContent_NoMiis_Title { get { - return ResourceManager.GetString("EmptyText_NoMiis_Title", resourceCulture); + return ResourceManager.GetString("EmptyContent_NoMiis_Title", resourceCulture); } } /// /// Looks up a localized string similar to Mods can alter how the game works. Start importing your first mod by clicking the button below.. /// - public static string EmptyText_NoMods { + public static string EmptyContent_NoMods { get { - return ResourceManager.GetString("EmptyText_NoMods", resourceCulture); + return ResourceManager.GetString("EmptyContent_NoMods", resourceCulture); } } /// /// Looks up a localized string similar to No mods found. /// - public static string EmptyText_NoMods_Title { + public static string EmptyContent_NoMods_Title { get { - return ResourceManager.GetString("EmptyText_NoMods_Title", resourceCulture); + return ResourceManager.GetString("EmptyContent_NoMods_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Select a mod in the list to view its details. + /// + public static string EmptyContent_NoModSelected { + get { + return ResourceManager.GetString("EmptyContent_NoModSelected", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No mod selected. + /// + public static string EmptyContent_NoModSelected_Title { + get { + return ResourceManager.GetString("EmptyContent_NoModSelected_Title", resourceCulture); } } /// /// Looks up a localized string similar to You have to play the game at least once in order to see your profiles listed here. /// - public static string EmptyText_NoProfiles { + public static string EmptyContent_NoProfiles { get { - return ResourceManager.GetString("EmptyText_NoProfiles", resourceCulture); + return ResourceManager.GetString("EmptyContent_NoProfiles", resourceCulture); } } /// /// Looks up a localized string similar to You might not have internet connection, or no-one might be playing.. /// - public static string EmptyText_NoRooms { + public static string EmptyContent_NoRooms { get { - return ResourceManager.GetString("EmptyText_NoRooms", resourceCulture); + return ResourceManager.GetString("EmptyContent_NoRooms", resourceCulture); } } /// /// Looks up a localized string similar to No rooms found. /// - public static string EmptyText_NoRooms_Title { + public static string EmptyContent_NoRooms_Title { + get { + return ResourceManager.GetString("EmptyContent_NoRooms_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Creator name must be less than 11 characters long.. + /// + public static string HelperNote_CreatorNameLess11 { get { - return ResourceManager.GetString("EmptyText_NoRooms_Title", resourceCulture); + return ResourceManager.GetString("HelperNote_CreatorNameLess11", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Names must be between 3 and 10 characters long.. + /// + public static string HelperNote_NameMustBetween { + get { + return ResourceManager.GetString("HelperNote_NameMustBetween", resourceCulture); } } @@ -167,6 +203,15 @@ public static string Hover_FriendsOnline_x { } } + /// + /// Looks up a localized string similar to To add friends you need to add them in-game.. + /// + public static string Hover_FriendsPageDisclaimer { + get { + return ResourceManager.GetString("Hover_FriendsPageDisclaimer", resourceCulture); + } + } + /// /// Looks up a localized string similar to There are currently no players online. /// @@ -194,6 +239,24 @@ public static string Hover_PlayersOnline_x { } } + /// + /// Looks up a localized string similar to The primary profile is used as reference in the wheel wizard client. + /// + public static string Hover_PrimaryProfile { + get { + return ResourceManager.GetString("Hover_PrimaryProfile", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This only shows regions YOU have played on. + /// + public static string Hover_RegionUserSelection { + get { + return ResourceManager.GetString("Hover_RegionUserSelection", resourceCulture); + } + } + /// /// Looks up a localized string similar to There are currently no active rooms. /// @@ -232,506 +295,1037 @@ public static string Hover_RoomsPageDisclaimer { } /// - /// Looks up a localized string similar to Enter desired path here.... + /// Looks up a localized string similar to Failed to apply an update. Aborting.. /// - public static string Placeholder_EnterPath { + public static string MessageError_AbortRR_Extra_FailedUpdateApply { get { - return ResourceManager.GetString("Placeholder_EnterPath", resourceCulture); + return ResourceManager.GetString("MessageError_AbortRR_Extra_FailedUpdateApply", resourceCulture); } } /// - /// Looks up a localized string similar to Search For Players.... + /// Looks up a localized string similar to Failed to delete files for the update. Aborting.. /// - public static string Placeholder_SearchForPlayers { + public static string MessageError_AbortRR_Extra_FailedUpdateDelete { get { - return ResourceManager.GetString("Placeholder_SearchForPlayers", resourceCulture); + return ResourceManager.GetString("MessageError_AbortRR_Extra_FailedUpdateDelete", resourceCulture); } } /// - /// Looks up a localized string similar to There are already files in your RetroRewind Folder. Would you like to install?. + /// Looks up a localized string similar to Invalid file path detected. Please contact the developers.\n Server error: {$1}. /// - public static string PopupText_AlreadyFilesRR { + public static string MessageError_AbortRR_Extra_InvalidFilePath { get { - return ResourceManager.GetString("PopupText_AlreadyFilesRR", resourceCulture); + return ResourceManager.GetString("MessageError_AbortRR_Extra_InvalidFilePath", resourceCulture); } } /// - /// Looks up a localized string similar to Do you want to apply the new scale?. + /// Looks up a localized string similar to Aborting RR Update. /// - public static string PopupText_ApplyScale { + public static string MessageError_AbortRR_Title { get { - return ResourceManager.GetString("PopupText_ApplyScale", resourceCulture); + return ResourceManager.GetString("MessageError_AbortRR_Title", resourceCulture); } } /// - /// Looks up a localized string similar to Could not connect to the server. Please try again later.. + /// Looks up a localized string similar to Failed to Change Mii. /// - public static string PopupText_CouldNotConnectServer { + public static string MessageError_FailedChangeMii_Title { get { - return ResourceManager.GetString("PopupText_CouldNotConnectServer", resourceCulture); + return ResourceManager.GetString("MessageError_FailedChangeMii_Title", resourceCulture); } } /// - /// Looks up a localized string similar to Dolphin Emulator Folder Found. + /// Looks up a localized string similar to Failed to change name. /// - public static string PopupText_DolphinFound { + public static string MessageError_FailedChangeName_Title { get { - return ResourceManager.GetString("PopupText_DolphinFound", resourceCulture); + return ResourceManager.GetString("MessageError_FailedChangeName_Title", resourceCulture); } } /// - /// Looks up a localized string similar to Dolphin Emulator folder found. Would you like to use this folder? If you dont know what all of this means, just click yes :). + /// Looks up a localized string similar to Failed to create Mii database. /// - public static string PopupText_DolphinFoundText { + public static string MessageError_FailedCreateMiiDb_Title { get { - return ResourceManager.GetString("PopupText_DolphinFoundText", resourceCulture); + return ResourceManager.GetString("MessageError_FailedCreateMiiDb_Title", resourceCulture); } } /// - /// Looks up a localized string similar to Dolphin Emulator Folder Not Found. + /// Looks up a localized string similar to The installation of Dolphin Emulator failed. Please try manually installing Flatpak Dolphin.. + /// + public static string MessageError_FailedInstallDolphin_Extra { + get { + return ResourceManager.GetString("MessageError_FailedInstallDolphin_Extra", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to install Dolphin. /// - public static string PopupText_DolphinNotFound { + public static string MessageError_FailedInstallDolphin_Title { get { - return ResourceManager.GetString("PopupText_DolphinNotFound", resourceCulture); + return ResourceManager.GetString("MessageError_FailedInstallDolphin_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to retrieve mod info. + /// + public static string MessageError_FailedRetrieveMod_Title { + get { + return ResourceManager.GetString("MessageError_FailedRetrieveMod_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An error occurred.. + /// + public static string MessageError_GenericError_Title { + get { + return ResourceManager.GetString("MessageError_GenericError_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An error occurred during download: {$1}. + /// + public static string MessageError_ModDownloadFail_Extra { + get { + return ResourceManager.GetString("MessageError_ModDownloadFail_Extra", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Mod download failed.. + /// + public static string MessageError_ModDownloadFail_Title { + get { + return ResourceManager.GetString("MessageError_ModDownloadFail_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to move data. + /// + public static string MessageError_DataFolderMove_Title { + get { + return ResourceManager.GetString("MessageError_DataFolderMove_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Mod folder does not exist. + /// + public static string MessageError_NoModFolder_Extra { + get { + return ResourceManager.GetString("MessageError_NoModFolder_Extra", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to restart with administrator rights.. + /// + public static string MessageError_RestartAdminFail_Extra { + get { + return ResourceManager.GetString("MessageError_RestartAdminFail_Extra", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Mii changed successfully. + /// + public static string MessageSuccess_MiiChanged { + get { + return ResourceManager.GetString("MessageSuccess_MiiChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Retro Rewind is up to date.. + /// + public static string MessageSuccess_RRUpToDate_Title { + get { + return ResourceManager.GetString("MessageSuccess_RRUpToDate_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Settings saved successfully!. + /// + public static string MessageSuccess_SettingsSaved_Title { + get { + return ResourceManager.GetString("MessageSuccess_SettingsSaved_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Data folder updated. + /// + public static string MessageSuccess_DataFolderMoved_Title { + get { + return ResourceManager.GetString("MessageSuccess_DataFolderMoved_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Wheel Wizard data is now stored in:\n{$1}. + /// + public static string MessageSuccess_DataFolderMoved_Extra { + get { + return ResourceManager.GetString("MessageSuccess_DataFolderMoved_Extra", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to One or more of the selected Mii(s) is a favorite. Miis can only be deleted if they are not favorites to prevent accidental deletions.. + /// + public static string MessageWarning_CannotDeleteFavMii_Extra { + get { + return ResourceManager.GetString("MessageWarning_CannotDeleteFavMii_Extra", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot delete favorite Mii(s). + /// + public static string MessageWarning_CannotDeleteFavMii_Title { + get { + return ResourceManager.GetString("MessageWarning_CannotDeleteFavMii_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot view mod that was not installed through the mod browser.. + /// + public static string MessageWarning_CantViewMod_Extra_NotFromBrowser { + get { + return ResourceManager.GetString("MessageWarning_CantViewMod_Extra_NotFromBrowser", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Something went wrong when trying to open the selected mod.. + /// + public static string MessageWarning_CantViewMod_Extra_SomethingElse { + get { + return ResourceManager.GetString("MessageWarning_CantViewMod_Extra_SomethingElse", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot view this mod.. + /// + public static string MessageWarning_CantViewMod_Title { + get { + return ResourceManager.GetString("MessageWarning_CantViewMod_Title", resourceCulture); } } /// /// Looks up a localized string similar to Dolphin Emulator folder not automatically found. Please try and find the folder manually, click 'help' for more information.. /// - public static string PopupText_DolphinNotFoundText { + public static string MessageWarning_DolphinNotFound_Extra { get { - return ResourceManager.GetString("PopupText_DolphinNotFoundText", resourceCulture); + return ResourceManager.GetString("MessageWarning_DolphinNotFound_Extra", resourceCulture); } } /// - /// Looks up a localized string similar to Download Retro Rewind. + /// Looks up a localized string similar to Dolphin Emulator Folder Not Found. + /// + public static string MessageWarning_DolphinNotFound_Title { + get { + return ResourceManager.GetString("MessageWarning_DolphinNotFound_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Can't check for updates right now. You might not be connected to the internet or the server might be down.. + /// + public static string MessageWarning_FailCheckUpdates_Extra { + get { + return ResourceManager.GetString("MessageWarning_FailCheckUpdates_Extra", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to check for updates. + /// + public static string MessageWarning_FailCheckUpdates_Title { + get { + return ResourceManager.GetString("MessageWarning_FailCheckUpdates_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A mod with the name '{$1}' already exists.. + /// + public static string MessageWarning_InvalidName_Extra_ModNameExists { + get { + return ResourceManager.GetString("MessageWarning_InvalidName_Extra_ModNameExists", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Name not possible.. /// - public static string PopupText_DownloadRR { + public static string MessageWarning_InvalidName_Title { get { - return ResourceManager.GetString("PopupText_DownloadRR", resourceCulture); + return ResourceManager.GetString("MessageWarning_InvalidName_Title", resourceCulture); } } /// /// Looks up a localized string similar to Please ensure all paths are correct and try again.. /// - public static string PopupText_EnsurePathsExists { + public static string MessageWarning_InvalidPaths_Extra { get { - return ResourceManager.GetString("PopupText_EnsurePathsExists", resourceCulture); + return ResourceManager.GetString("MessageWarning_InvalidPaths_Extra", resourceCulture); } } /// - /// Looks up a localized string similar to Enter Mod Name:. + /// Looks up a localized string similar to Invalid Paths.. /// - public static string PopupText_EnterModName { + public static string MessageWarning_InvalidPaths_Title { get { - return ResourceManager.GetString("PopupText_EnterModName", resourceCulture); + return ResourceManager.GetString("MessageWarning_InvalidPaths_Title", resourceCulture); } } /// - /// Looks up a localized string similar to Enter Title:. + /// Looks up a localized string similar to Please provide a mod name.. /// - public static string PopupText_EnterTitle { + public static string MessageWarning_ModNameEmpty_Extra { get { - return ResourceManager.GetString("PopupText_EnterTitle", resourceCulture); + return ResourceManager.GetString("MessageWarning_ModNameEmpty_Extra", resourceCulture); } } /// - /// Looks up a localized string similar to Estimated time remaining:. + /// Looks up a localized string similar to Mod name can't be empty.. /// - public static string PopupText_EsimatedTimeRemaining { + public static string MessageWarning_ModNameEmpty_Title { get { - return ResourceManager.GetString("PopupText_EsimatedTimeRemaining", resourceCulture); + return ResourceManager.GetString("MessageWarning_ModNameEmpty_Title", resourceCulture); } } /// - /// Looks up a localized string similar to Failed to check for updates. + /// Looks up a localized string similar to Mod name contains invalid characters.. /// - public static string PopupText_FailCheckUpdates { + public static string MessageWarning_ModNameInvalid_Extra { get { - return ResourceManager.GetString("PopupText_FailCheckUpdates", resourceCulture); + return ResourceManager.GetString("MessageWarning_ModNameInvalid_Extra", resourceCulture); } } /// - /// Looks up a localized string similar to Failed to apply an update. Aborting.. + /// Looks up a localized string similar to Mod name Invalid.. /// - public static string PopupText_FailedUpdateApply { + public static string MessageWarning_ModNameInvalid_Title { get { - return ResourceManager.GetString("PopupText_FailedUpdateApply", resourceCulture); + return ResourceManager.GetString("MessageWarning_ModNameInvalid_Title", resourceCulture); } } /// - /// Looks up a localized string similar to Failed to delete files for the update. Aborting.. + /// Looks up a localized string similar to Multiple Files Selected. + /// + public static string MessageWarning_MultipleFilesSelected_Title { + get { + return ResourceManager.GetString("MessageWarning_MultipleFilesSelected_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not connect to the server. Please try again later.. + /// + public static string MessageWarning_NoConnectServer_Extra { + get { + return ResourceManager.GetString("MessageWarning_NoConnectServer_Extra", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not connect to the server. + /// + public static string MessageWarning_NoConnectServer_Title { + get { + return ResourceManager.GetString("MessageWarning_NoConnectServer_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not find Dolphin Emulator, please set the path in settings. + /// + public static string MessageWarning_NotFindDolphin_Extra { + get { + return ResourceManager.GetString("MessageWarning_NotFindDolphin_Extra", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not find the game, please set the path in settings. + /// + public static string MessageWarning_NotFindGame_Extra { + get { + return ResourceManager.GetString("MessageWarning_NotFindGame_Extra", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Files could not be found.. + /// + public static string MessageWarning_UnableDownloadMod_Extra { + get { + return ResourceManager.GetString("MessageWarning_UnableDownloadMod_Extra", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to download the mod. + /// + public static string MessageWarning_UnableDownloadMod_Title { + get { + return ResourceManager.GetString("MessageWarning_UnableDownloadMod_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to update Wheel Wizard. Please ensure the application is located in a folder that can be written to.\n Could not find current folder.. + /// + public static string MessageWarning_UnableUpdateWhWz_Extra_ReasonLocation { + get { + return ResourceManager.GetString("MessageWarning_UnableUpdateWhWz_Extra_ReasonLocation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to check if Wheel Wizard is up to date. \nYou might be experiencing network issues.. + /// + public static string MessageWarning_UnableUpdateWhWz_Extra_ReasonNetwork { + get { + return ResourceManager.GetString("MessageWarning_UnableUpdateWhWz_Extra_ReasonNetwork", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to update Wheel Wizard.. + /// + public static string MessageWarning_UnableUpdateWhWz_Title { + get { + return ResourceManager.GetString("MessageWarning_UnableUpdateWhWz_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enter Mii name.... + /// + public static string Placeholder_EnterMiiName { + get { + return ResourceManager.GetString("Placeholder_EnterMiiName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enter mod name.... + /// + public static string Placeholder_EnterModName { + get { + return ResourceManager.GetString("Placeholder_EnterModName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enter desired path here.... + /// + public static string Placeholder_EnterPath { + get { + return ResourceManager.GetString("Placeholder_EnterPath", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enter text here.... /// - public static string PopupText_FailedUpdateDelete { + public static string Placeholder_EnterTextHere { get { - return ResourceManager.GetString("PopupText_FailedUpdateDelete", resourceCulture); + return ResourceManager.GetString("Placeholder_EnterTextHere", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Search For Players.... + /// + public static string Placeholder_SearchForPlayers { + get { + return ResourceManager.GetString("Placeholder_SearchForPlayers", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Search for mods.... + /// + public static string Placeholder_SearchMod { + get { + return ResourceManager.GetString("Placeholder_SearchMod", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to downloading {$1} MB. + /// + public static string Progress_DownloadingMb { + get { + return ResourceManager.GetString("Progress_DownloadingMb", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Estimated time remaining:. + /// + public static string Progress_EstimatedTimeRemaining { + get { + return ResourceManager.GetString("Progress_EstimatedTimeRemaining", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Installing Dolphin Emulator. + /// + public static string Progress_InstallingDolphin { + get { + return ResourceManager.GetString("Progress_InstallingDolphin", resourceCulture); } } /// /// Looks up a localized string similar to Installing mods. /// - public static string PopupText_InstallingMods { + public static string Progress_InstallingMods { get { - return ResourceManager.GetString("PopupText_InstallingMods", resourceCulture); + return ResourceManager.GetString("Progress_InstallingMods", resourceCulture); } } /// /// Looks up a localized string similar to Installing {$1} mods. /// - public static string PopupText_InstallingModsCount { + public static string Progress_InstallingModsCount { get { - return ResourceManager.GetString("PopupText_InstallingModsCount", resourceCulture); + return ResourceManager.GetString("Progress_InstallingModsCount", resourceCulture); } } /// /// Looks up a localized string similar to Installing Retro Rewind. /// - public static string PopupText_InstallingRR { + public static string Progress_InstallingRR { get { - return ResourceManager.GetString("PopupText_InstallingRR", resourceCulture); + return ResourceManager.GetString("Progress_InstallingRR", resourceCulture); } } /// /// Looks up a localized string similar to Installing Retro Rewind for the first time. /// - public static string PopupText_InstallingRRFirstTime { + public static string Progress_InstallingRRFirstTime { get { - return ResourceManager.GetString("PopupText_InstallingRRFirstTime", resourceCulture); + return ResourceManager.GetString("Progress_InstallingRRFirstTime", resourceCulture); } } /// /// Looks up a localized string similar to Getting the latest Wheel Wizard from github releases. /// - public static string PopupText_LatestWhWzGithub { + public static string Progress_LatestWhWzGithub { get { - return ResourceManager.GetString("PopupText_LatestWhWzGithub", resourceCulture); + return ResourceManager.GetString("Progress_LatestWhWzGithub", resourceCulture); } } /// - /// Looks up a localized string similar to Do you want to combine all files into 1 mod?. + /// Looks up a localized string similar to Processing {$1} of {$2} files.... /// - public static string PopupText_ModCombineQuestion { + public static string Progress_ProcessingXofY { get { - return ResourceManager.GetString("PopupText_ModCombineQuestion", resourceCulture); + return ResourceManager.GetString("Progress_ProcessingXofY", resourceCulture); } } /// - /// Looks up a localized string similar to Mod name can't be empty. + /// Looks up a localized string similar to This may take a while depending on your internet connection.. /// - public static string PopupText_ModNameEmpty { + public static string Progress_ThisMayTakeAWhile { get { - return ResourceManager.GetString("PopupText_ModNameEmpty", resourceCulture); + return ResourceManager.GetString("Progress_ThisMayTakeAWhile", resourceCulture); } } /// - /// Looks up a localized string similar to A mod with the name '{$1}' already exists.. + /// Looks up a localized string similar to Updating Retro Rewind. /// - public static string PopupText_ModNameExists { + public static string Progress_UpdateRR { get { - return ResourceManager.GetString("PopupText_ModNameExists", resourceCulture); + return ResourceManager.GetString("Progress_UpdateRR", resourceCulture); } } /// - /// Looks up a localized string similar to Mods found. + /// Looks up a localized string similar to Updating Wheel Wizard. /// - public static string PopupText_ModsFound { + public static string Progress_UpdateWhWz { get { - return ResourceManager.GetString("PopupText_ModsFound", resourceCulture); + return ResourceManager.GetString("Progress_UpdateWhWz", resourceCulture); } } /// - /// Looks up a localized string similar to You are about to launch the game without any mods. Do you want to clear your my-stuff folder?. + /// Looks up a localized string similar to This change will revert in {$1} unless you decide to keep the change.. /// - public static string PopupText_ModsFoundQuestion { + public static string Question_ApplyScale_Extra { get { - return ResourceManager.GetString("PopupText_ModsFoundQuestion", resourceCulture); + return ResourceManager.GetString("Question_ApplyScale_Extra", resourceCulture); } } /// - /// Looks up a localized string similar to Multiple Files Selected. + /// Looks up a localized string similar to Do you want to apply the new scale?. /// - public static string PopupText_MultipleFilesSelected { + public static string Question_ApplyScale_Title { get { - return ResourceManager.GetString("PopupText_MultipleFilesSelected", resourceCulture); + return ResourceManager.GetString("Question_ApplyScale_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Move Wheel Wizard data?. + /// + public static string Question_MoveData_Title { + get { + return ResourceManager.GetString("Question_MoveData_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Wheel Wizard will move its files to:\n{$1}\nThis may take a while depending on the amount of data. + /// + public static string Question_MoveData_Extra { + get { + return ResourceManager.GetString("Question_MoveData_Extra", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Flatpak version of Dolphin Emulator does not appear to be installed. Would you like us to install it (system-wide)?. + /// + public static string Question_DolphinFlatpack_Extra { + get { + return ResourceManager.GetString("Question_DolphinFlatpack_Extra", resourceCulture); } } /// - /// Looks up a localized string similar to Version {$1} of Wheel Wizard is available (currently on {$2}). Do you want to update now?. + /// Looks up a localized string similar to Dolphin Flatpak Installation. /// - public static string PopupText_NewVersionWhWz { + public static string Question_DolphinFlatpack_Title { get { - return ResourceManager.GetString("PopupText_NewVersionWhWz", resourceCulture); + return ResourceManager.GetString("Question_DolphinFlatpack_Title", resourceCulture); } } /// - /// Looks up a localized string similar to Mod folder does not exist. + /// Looks up a localized string similar to If you dont know what all of this means, just click yes :) \nDolphin Emulator folder found. Would you like to use this folder?. /// - public static string PopupText_NoModFolder { + public static string Question_DolphinFound_Extra { get { - return ResourceManager.GetString("PopupText_NoModFolder", resourceCulture); + return ResourceManager.GetString("Question_DolphinFound_Extra", resourceCulture); } } /// - /// Looks up a localized string similar to Could not find Dolphin Emulator, please set the path in settings. + /// Looks up a localized string similar to Dolphin Emulator Folder Found. /// - public static string PopupText_NotFindDolphin { + public static string Question_DolphinFound_Title { get { - return ResourceManager.GetString("PopupText_NotFindDolphin", resourceCulture); + return ResourceManager.GetString("Question_DolphinFound_Title", resourceCulture); } } /// - /// Looks up a localized string similar to Could not find the game, please set the path in settings. + /// Looks up a localized string similar to Changing name from: {$1}. /// - public static string PopupText_NotFindGame { + public static string Question_EnterNewName_Extra { get { - return ResourceManager.GetString("PopupText_NotFindGame", resourceCulture); + return ResourceManager.GetString("Question_EnterNewName_Extra", resourceCulture); } } /// - /// Looks up a localized string similar to Old rksys.dat found. + /// Looks up a localized string similar to Enter new name. /// - public static string PopupText_OldRksysFound { + public static string Question_EnterNewName_Title { get { - return ResourceManager.GetString("PopupText_OldRksysFound", resourceCulture); + return ResourceManager.GetString("Question_EnterNewName_Title", resourceCulture); } } /// - /// Looks up a localized string similar to Old save data was found. Would you like to use it? (recommended). + /// Looks up a localized string similar to Do you want to donwload and install the mod: {$1}?. /// - public static string PopupText_OldRksysFoundExplained { + public static string Question_InstallMod_Title { get { - return ResourceManager.GetString("PopupText_OldRksysFoundExplained", resourceCulture); + return ResourceManager.GetString("Question_InstallMod_Title", resourceCulture); } } /// - /// Looks up a localized string similar to Old Retro Rewind found. + /// Looks up a localized string similar to You are about to launch the game without any mods. Do you want to clear your my-stuff folder?. /// - public static string PopupText_OldRRFound { + public static string Question_LaunchClearModsFound_Extra { get { - return ResourceManager.GetString("PopupText_OldRRFound", resourceCulture); + return ResourceManager.GetString("Question_LaunchClearModsFound_Extra", resourceCulture); } } /// - /// Looks up a localized string similar to Old Retro Rewind files were found. Would you like to move them to the new location?. + /// Looks up a localized string similar to Mods found. /// - public static string PopupText_OldRRFoundExplained { + public static string Question_LaunchClearModsFound_Title { get { - return ResourceManager.GetString("PopupText_OldRRFoundExplained", resourceCulture); + return ResourceManager.GetString("Question_LaunchClearModsFound_Title", resourceCulture); } } /// - /// Looks up a localized string similar to Processing {$1} of {$2} files.... + /// Looks up a localized string similar to Version {$1} of Wheel Wizard is available (currently on {$2}). Do you want to update now?. /// - public static string PopupText_ProcessingXofY { + public static string Question_NewVersionWhWz_Extra { get { - return ResourceManager.GetString("PopupText_ProcessingXofY", resourceCulture); + return ResourceManager.GetString("Question_NewVersionWhWz_Extra", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Wheel Wizard Update Available. + /// + public static string Question_NewVersionWhWz_Title { + get { + return ResourceManager.GetString("Question_NewVersionWhWz_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Old save data was found. Would you like to use it? (recommended). + /// + public static string Question_OldRksysFound_Extra { + get { + return ResourceManager.GetString("Question_OldRksysFound_Extra", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Old rksys.dat found. + /// + public static string Question_OldRksysFound_Title { + get { + return ResourceManager.GetString("Question_OldRksysFound_Title", resourceCulture); } } /// /// Looks up a localized string similar to Are you sure you want to reinstall Retro Rewind?. /// - public static string PopupText_ReinstallQuestion { + public static string Question_ReinstallRR_Extra { get { - return ResourceManager.GetString("PopupText_ReinstallQuestion", resourceCulture); + return ResourceManager.GetString("Question_ReinstallRR_Extra", resourceCulture); } } /// /// Looks up a localized string similar to Reinstall Retro Rewind. /// - public static string PopupText_ReinstallRR { + public static string Question_ReinstallRR_Title { get { - return ResourceManager.GetString("PopupText_ReinstallRR", resourceCulture); + return ResourceManager.GetString("Question_ReinstallRR_Title", resourceCulture); } } /// - /// Looks up a localized string similar to Failed to restart with administrator rights.. + /// Looks up a localized string similar to Your version of Retro Rewind could not be determined. Would you like to download Retro Rewind?. /// - public static string PopupText_RestartAdminFail { + public static string Question_RRNotDeterment_Extra { get { - return ResourceManager.GetString("PopupText_RestartAdminFail", resourceCulture); + return ResourceManager.GetString("Question_RRNotDeterment_Extra", resourceCulture); } } /// - /// Looks up a localized string similar to Your version of Retro Rewind could not be determined. Would you like to download Retro Rewind?. + /// Looks up a localized string similar to Download Retro Rewind. /// - public static string PopupText_RRNotDeterment { + public static string Question_RRNotDeterment_Title { get { - return ResourceManager.GetString("PopupText_RRNotDeterment", resourceCulture); + return ResourceManager.GetString("Question_RRNotDeterment_Title", resourceCulture); } } /// /// Looks up a localized string similar to Your version of Retro Rewind is too old to update. Would you like to reinstall Retro Rewind?. /// - public static string PopupText_RRToOld { + public static string Question_RRToOld_Extra { get { - return ResourceManager.GetString("PopupText_RRToOld", resourceCulture); + return ResourceManager.GetString("Question_RRToOld_Extra", resourceCulture); } } /// - /// Looks up a localized string similar to Retro Rewind is up to date.. + /// Looks up a localized string similar to The Retro Rewind version is too old.. /// - public static string PopupText_RRUpToDate { + public static string Question_RRToOld_Title { get { - return ResourceManager.GetString("PopupText_RRUpToDate", resourceCulture); + return ResourceManager.GetString("Question_RRToOld_Title", resourceCulture); } } /// - /// Looks up a localized string similar to Settings saved successfully!. + /// Looks up a localized string similar to Search for mods.... /// - public static string PopupText_SettingsSaved { + public static string Question_SearchMod { get { - return ResourceManager.GetString("PopupText_SettingsSaved", resourceCulture); + return ResourceManager.GetString("Question_SearchMod", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Deleting is permanent and cannot be undone.. + /// + public static string Question_SureDelete_Extra { + get { + return ResourceManager.GetString("Question_SureDelete_Extra", resourceCulture); } } /// /// Looks up a localized string similar to Are you sure you want to delete {$1}?. /// - public static string PopupText_SureDeleteQuestion { + public static string Question_SureDelete_Title { get { - return ResourceManager.GetString("PopupText_SureDeleteQuestion", resourceCulture); + return ResourceManager.GetString("Question_SureDelete_Title", resourceCulture); } } /// - /// Looks up a localized string similar to Unable to update Wheel Wizard. Please ensure the application is located in a folder that can be written to.\n Could not find current folder.. + /// Looks up a localized string similar to Are you sure you want to delete {$1} Miis?. /// - public static string PopupText_UnableUpdateWhWz_ReasonLocation { + public static string Question_SureDelete_Title_Miis { get { - return ResourceManager.GetString("PopupText_UnableUpdateWhWz_ReasonLocation", resourceCulture); + return ResourceManager.GetString("Question_SureDelete_Title_Miis", resourceCulture); } } /// - /// Looks up a localized string similar to Unable to check if Wheel Wizard is up to date. \nYou might be experiencing network issues.. + /// Looks up a localized string similar to Sometimes an update requires admin rights, do you want to active them for this update?. /// - public static string PopupText_UnableUpdateWhWz_ReasonNetwork { + public static string Question_UpdateAdmin_Extra { get { - return ResourceManager.GetString("PopupText_UnableUpdateWhWz_ReasonNetwork", resourceCulture); + return ResourceManager.GetString("Question_UpdateAdmin_Extra", resourceCulture); } } /// /// Looks up a localized string similar to Update using admin. /// - public static string PopupText_UpdateAdmin { + public static string Question_UpdateAdmin_Title { get { - return ResourceManager.GetString("PopupText_UpdateAdmin", resourceCulture); + return ResourceManager.GetString("Question_UpdateAdmin_Title", resourceCulture); } } /// - /// Looks up a localized string similar to Sometimes an update requires admin rights, do you want to active them for this update?. + /// Looks up a localized string similar to Failed to create Mii '{$1}'. /// - public static string PopupText_UpdateAdminExplained { + public static string SnackbarError_MiiFailureCreate { get { - return ResourceManager.GetString("PopupText_UpdateAdminExplained", resourceCulture); + return ResourceManager.GetString("SnackbarError_MiiFailureCreate", resourceCulture); } } /// - /// Looks up a localized string similar to Update Retro Rewind. + /// Looks up a localized string similar to Failed to deserialize Mii '{$1}'. /// - public static string PopupText_UpdateRR { + public static string SnackbarError_MiiFailureDeserialize { get { - return ResourceManager.GetString("PopupText_UpdateRR", resourceCulture); + return ResourceManager.GetString("SnackbarError_MiiFailureDeserialize", resourceCulture); } } /// - /// Looks up a localized string similar to Updating Wheel Wizard. + /// Looks up a localized string similar to Failed to duplicate Mii(s) '{$1}'. /// - public static string PopupText_UpdateWhWz { + public static string SnackbarError_MiiFailureDuplicate { get { - return ResourceManager.GetString("PopupText_UpdateWhWz", resourceCulture); + return ResourceManager.GetString("SnackbarError_MiiFailureDuplicate", resourceCulture); } } /// - /// Looks up a localized string similar to Wheel Wizard Update Available. + /// Looks up a localized string similar to Failed to get Mii '{$1}'. /// - public static string PopupText_WhWzUpdateAvailable { + public static string SnackbarError_MiiFailureGet { get { - return ResourceManager.GetString("PopupText_WhWzUpdateAvailable", resourceCulture); + return ResourceManager.GetString("SnackbarError_MiiFailureGet", resourceCulture); } } /// - /// Looks up a localized string similar to downloading {$1} MB. + /// Looks up a localized string similar to Failed to save Mii '{$1}'. + /// + public static string SnackbarError_MiiFailureSave { + get { + return ResourceManager.GetString("SnackbarError_MiiFailureSave", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to serialize Mii '{$1}'. + /// + public static string SnackbarError_MiiFailureSerialize { + get { + return ResourceManager.GetString("SnackbarError_MiiFailureSerialize", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to update Mii '{$1}'. + /// + public static string SnackbarError_MiiFailureUpdate { + get { + return ResourceManager.GetString("SnackbarError_MiiFailureUpdate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copied friend code to clipboard. + /// + public static string SnackbarSuccess_CopiedFC { + get { + return ResourceManager.GetString("SnackbarSuccess_CopiedFC", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Created duplicate of '{$1}'. + /// + public static string SnackbarSuccess_CreatedDuplicate { + get { + return ResourceManager.GetString("SnackbarSuccess_CreatedDuplicate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Created {$1} duplicate Miis. + /// + public static string SnackbarSuccess_CreatedDuplicatesMiis { + get { + return ResourceManager.GetString("SnackbarSuccess_CreatedDuplicatesMiis", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Deleted '{$1}'. + /// + public static string SnackbarSuccess_Deleted { + get { + return ResourceManager.GetString("SnackbarSuccess_Deleted", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Deleted {$1} Miis. /// - public static string PupupText_DownloadingMb { + public static string SnackbarSuccess_Deleted_Miis { get { - return ResourceManager.GetString("PupupText_DownloadingMb", resourceCulture); + return ResourceManager.GetString("SnackbarSuccess_Deleted_Miis", resourceCulture); } } /// - /// Looks up a localized string similar to Talk to us!. + /// Looks up a localized string similar to Mii has been added to your Miis. /// - public static string Sidebar_Link_Discord { + public static string SnackbarSuccess_MiiAdded { get { - return ResourceManager.GetString("Sidebar_Link_Discord", resourceCulture); + return ResourceManager.GetString("SnackbarSuccess_MiiAdded", resourceCulture); } } /// - /// Looks up a localized string similar to Source code. + /// Looks up a localized string similar to Successfully changed name to '{$1}'. /// - public static string Sidebar_Link_Github { + public static string SnackbarSuccess_NameChange { get { - return ResourceManager.GetString("Sidebar_Link_Github", resourceCulture); + return ResourceManager.GetString("SnackbarSuccess_NameChange", resourceCulture); } } /// - /// Looks up a localized string similar to Support us!. + /// Looks up a localized string similar to Set profile as primary. /// - public static string Sidebar_Link_Support { + public static string SnackbarSuccess_ProfileSetPrimary { get { - return ResourceManager.GetString("Sidebar_Link_Support", resourceCulture); + return ResourceManager.GetString("SnackbarSuccess_ProfileSetPrimary", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Saved Mii '{$1}' to file '{$2}'. + /// + public static string SnackbarSuccess_SavedMii { + get { + return ResourceManager.GetString("SnackbarSuccess_SavedMii", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Can't Save Mii. + /// + public static string SnackbarWarning_CantSaveMii { + get { + return ResourceManager.GetString("SnackbarWarning_CantSaveMii", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to It seems there where no Miis to delete. + /// + public static string SnackbarWarning_NoMiiDelete { + get { + return ResourceManager.GetString("SnackbarWarning_NoMiiDelete", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to It seems there where no Miis to export. + /// + public static string SnackbarWarning_NoMiiExport { + get { + return ResourceManager.GetString("SnackbarWarning_NoMiiExport", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This language is translated by: {$1}. + /// + public static string Text_LanguageTranslatedBy { + get { + return ResourceManager.GetString("Text_LanguageTranslatedBy", resourceCulture); } } @@ -744,6 +1338,15 @@ public static string Text_MadeByString { } } + /// + /// Looks up a localized string similar to Powered By GameBanana. + /// + public static string Text_PoweredGamebanana { + get { + return ResourceManager.GetString("Text_PoweredGamebanana", resourceCulture); + } + } + /// /// Looks up a localized string similar to Thanks a lot to all the translators:. /// diff --git a/WheelWizard/Resources/Languages/Phrases.cs.resx b/WheelWizard/Resources/Languages/Phrases.cs.resx index a4c5284f..d32cfa27 100644 --- a/WheelWizard/Resources/Languages/Phrases.cs.resx +++ b/WheelWizard/Resources/Languages/Phrases.cs.resx @@ -18,4 +18,154 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Můžeš přidat kamarádi ve hře + + + Zatím žádní kamarádi! + + + Stáhnout Retro Rewind + + + Instalování Retro Rewind + + + Instalování Retro Rewind poprvé + + + Přeinstalovat Retro Rewind + + + Retro Rewind je aktuální. + + + Aktualizování Retro Rewind + + + Aktualizování Wheel Wizard + + + Stahování {$1} MB + + + Vytvořili: {$1} \n a {$2} + + + Díky moc všem překladatelům: + + + Překlady pro tento jazyk jsou z {$1}% kompletní + + + 1 den + + + ($1) dny + + + 1 hodina + + + ($1) hodiny + + + 1 minuta + + + ($1) minuty + + + 1 sekunda + + + ($1) sekundy + + + Zatím žádné Miiy! + + + Vyber mod v seznamu pro zobrazení jeho podrobností + + + Možná nemáš připojení k internetu, nebo nikdo nehraje. + + + Jméno tvůrce musí být kratší než 11 znaků. + + + Jména musí mít délku 3 až 10 znaků. + + + Momentálně nejsou žádné aktivní místnosti + + + Momentálně je aktivní 1 místnost + + + Momentálně jsou aktivní {$1} místnosti + + + Došlo k chybě. + + + Během stahování došlo k chybě: {$1} + + + Stažení modu se nezdařilo. + + + Složka s mody neexistuje + + + Nastavení bylo úspěšně uloženo! + + + Jméno není možné. + + + Zkontroluj prosím, zda jsou všechny cesty správné, a zkus to znovu. + + + Prosím, uveď název modu. + + + Název modu nemůže být prázdný. + + + Název modu obsahuje neplatné znaky. + + + Neplatný název modu. + + + Nepodařilo se připojit k serveru. Zkus to prosím znovu později. + + + Nepodařilo se připojit k serveru. + + + Nepodařilo se najít Dolphin Emulator, prosím nastav cestu v nastavení. + + + Nepodařilo se najít hru, prosím nastav cestu v nastavení. + + + Soubory se nepodařilo nalézt. + + + Instalování mody + + + Instalování {$1} mody + + + Verze Retro Rewind je moc stará. + + + Tento jazyk překládá: {$1} + + + Poháněno společností GameBanana + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Phrases.de.resx b/WheelWizard/Resources/Languages/Phrases.de.resx index 91b635d1..5887eb5a 100644 --- a/WheelWizard/Resources/Languages/Phrases.de.resx +++ b/WheelWizard/Resources/Languages/Phrases.de.resx @@ -45,74 +45,59 @@ Zurzeit gibt es {$1} aktive Räume - - Rede mit uns! - - - Source Code - - - Unterstützt uns! - Erstellt von: {$1} \n und {$2} Vielen Dank an alle Übersetzer: - + Du kannst Freunde im Spiel hinzufügen - + Noch keine Freunde! - - Du musst das Spiel mindestens ein Mal gespielt haben, um deine Profile hier aufgelistet zu sehen + + Du musst das Spiel mindestens ein Mal gespielt haben, um deine Profile hier aufgelistet zu sehen. - - Gewünschten Pfad hier eingeben... - - - Mods können die Funktionsweise des Spiels verändern. Beginne mit dem Importerien deines ersten Mods, indem Du auf die Schaltfläche unten klickst. + + Mods können die Funktionsweise des Spiels verändern. Beginne mit dem Importieren deines ersten Mods, indem du auf die Schaltfläche unten klickst. - - Keine Mods gefunden + + Keine Mods gefunden. - - Möglicherweise hast Du keine Internetverbindung oder es spielt niemand. + + Möglicherweise hast du keine Internetverbindung oder es spielt niemand. - - Keine Räume gefunden + + Keine Räume gefunden. Bitte beachte, dass diese Seite keine Funktion bietet, um dich mit einem Raum direkt zu verbinden. -Um einem bestimmten Raum beizutreten, musst Du entweder über einen Freund treffen oder hoffen, ihm beizutreten, indem du in den Online-Servern beitrittst. +Um einem bestimmten Raum beizutreten, musst du entweder über einen Freund treffen oder hoffen, ihm beizutreten, indem du in den Online-Servern beitrittst. - - Suche nach Spielern - - + Geschätzte verbleibende Zeit: - + Installiere Mods - + Installiere {$1} Mods - + Mods gefunden - - Du bist dabei, das Spiel ohne Mods zu starten. Möchtest du deinen my-stuff ordner leeren? + + Du bist dabei, das Spiel ohne Mods zu starten. Möchtest du deinen My Stuff ordner leeren? - + Der Dolphin Emulator konnte nicht gefunden werden. Bitte lege den Pfad in den Einstellungen fest. - + Das Spiel konnte nicht gefunden werden. Bitte lege den Pfad in den Einstellungen fest. - + Herunterladen mit {$1} MB @@ -142,131 +127,340 @@ Um einem bestimmten Raum beizutreten, musst Du entweder über einen Freund treff {$1} Sekunden - - In Deinem RetroRewind-Ordner befinden sich bereits Dateien. willst du sie dir installieren? - - + Möchtest du die neue Skala anwenden? - - Es konnte keine Verbindung zum Server hergestellt werden. Bitte versuchen Sie es später noch einmal. + + Es konnte keine Verbindung zum Server hergestellt werden. Bitte versuche es später noch einmal. - - Dolphin Emulator Order gefunden + + Dolphin Emulator Ordner gefunden - + Wenn du nicht weißt, was das alles bedeutet, klick einfach auf „Ja“ :) \nDolphin Emulator-Ordner gefunden. Möchtest du diesen Ordner verwenden? - - Dolphin Emulator Order nicht gefunden + + Dolphin Emulator-Ordner nicht gefunden. - + Der Ordner „Dolphin Emulator“ wurde nicht automatisch gefunden. Versuche bitte, den Ordner manuell zu finden. Für weitere Informationen einfach auf „Hilfe“ klicken. - + Installiere Retro Rewind - - Bitte stell es sicher, dass alle Pfade korrekt sind, und versuche es erneut - - - - Gib den Mod-Namen ein: + + Bitte stell es sicher, dass alle Pfade korrekt sind, und versuche es erneut. - - Titel eingeben: + + Die Suche nach Updates ist fehlgeschlagen. - - Die Suche nach Updates ist fehlgeschlagen + + Das Update konnte nicht installiert werden. Abbruch. - - Das Update konnte nicht angewendet werden.Abbruch. - - + Dateien für das Update konnten nicht gelöscht werden. Abbruch. - + Installiere Retro Rewind - - Installiere Retro Rewind zum ersten mal + + Installiere Retro Rewind zum ersten Mal - + Beziehe die neueste Wheel Wizard-Version vom Github - - Möchtest du alle Dateien in einem Mod zusammenfassen? - - - Mod Name darf nicht leer sein - - - Eine Mod mit dem Namen '{$1}' existiert schon + + Eine Mod mit dem Namen '{$1}' existiert bereits. - - Mehrere Datein ausgewählt + + Mehrere Dateien ausgewählt - - Version {$1} von Wheel Wizard ist verfügbar (derzeit für {$2}). Möchtest Du jetzt updaten? + + Version {$1} von Wheel Wizard ist verfügbar (derzeit für {$2}). Möchtest du jetzt updaten? - - Mod Ordner existiert nicht + + Mod-Ordner existiert nicht. - + Alte rksys.dat gefunden - - Alte Save Dateien wurden gefunden. Möchtest du sie benutzen? (Empfohlen) + + Alte Speicherdaten wurden gefunden. Möchtest Du sie benutzen? (Empfohlen) - - Alte Retro Rewind Version gefunden - - - Es wurden alte Retro Rewind-Dateien gefunden. Möchtest Du sie an einem neuen Ort speichern? - - + {$1} von {$2} Dateien werden verarbeitet... - + Installiere Retro Rewind neu - + Neustart mit Admin-Rechten fehlgeschlagen. - - Deine Version von Retro Rewind konnte nicht ermittelt werden. Möchtest Du Retro Rewind herunterladen? + + Deine Version von Retro Rewind konnte nicht ermittelt werden. Möchtest du Retro Rewind herunterladen? - - Deine Version von Retro Rewind ist zu alt zum Aktualisieren. Möchtest Du Retro Rewind neu installieren? + + Deine Version von Retro Rewind ist zu alt zum Aktualisieren. Möchtest du Retro Rewind neu installieren? - + Retro Rewind ist auf dem neuesten Stand. - + Einstellungen erfolgreich gespeichert! - + Bist du sicher, dass du {$1} löschen möchtest? - - Wheel Wizard kann nicht aktualisiert werden. Bitte stell es sicher, dass sich die Anwendung in einem Ordner befindet, in den geschrieben werden kann.\n Der aktuelle Ordner konnte nicht gefunden werden. + + Wheel Wizard kann nicht aktualisiert werden. Bitte stelle sicher, dass sich die Anwendung in einem Ordner befindet, in den geschrieben werden kann.\n Der aktuelle Ordner konnte nicht gefunden werden. - + Es kann nicht überprüft werden, ob Wheel Wizard auf dem neuesten Stand ist. \nMöglicherweise treten derzeit Netzwerkprobleme auf. - + Update mit Admin - - Manchmal erfordert ein Update Admin rechte. Möchtest Du sie für diesen Update aktivieren? - - - Aktualisiere Retro Rewind + + Manchmal erfordert ein Update Adminrechte. Möchtest du sie für dieses Update aktivieren? - + Wheel Wizard wird aktualisiert - + Wheel Wizard Update verfügbar + + Die Mii-Daten können nicht von deinem System aus geladen werden. Stelle sicher dass du dein Spiel mind. 1 Mal neu startest. + + + Noch keine Miis erstellt! + + + Name des Ersteller muss weniger als 11 Zeichen lang sein. + + + Namen müssen zwischen 3 und 10 Zeichen lang sein. + + + Um Freunde hinzuzufügen musst du sie innerhalb des Spiels registrieren + + + Dein Hauptprofil wird als Vergleich im Wheel Wizard Client verwendet. + + + Dies zeigt dir Regionen, in denen DU gespielt hast. + + + Ungültiger Dateipfad wurde erkannt. Bitte kontaktiere die Entwickler. \n Server-Fehler: {$1} + + + Über das RR Update + + + Fehler beim Ändern des Miis. + + + Fehler beim Ändern des Namens. + + + Fehler beim Erstellen der Mii-Datenbank. + + + Ein Fehler ist aufgetreten. + + + Mii erfolgreich geändert. + + + Ein oder mehrere der ausgewählten Mii(s) ist ein Favorit. Miis die als Favorit makiert wurden können nur gelöscht werden wenn sie nicht mehr Favorit sind um zu verhindern dass aus Versehen Löschungen geschehen. + + + Mii(s) die als Favorit markiert sind, sind nicht löschbar. + + + Die Mod die über den Mod Browser heruntergeladen wurde kann nicht angesehen werden. + + + Etwas ist schief gelaufen beim Ansehen dieser Mod. + + + Du kannst diese Mod nicht ansehen. + + + Es ist nicht möglich gerade nach Updates zu suchen. Du bist vielleicht nicht mit dem Internet verbunden oder der Server hat gerade Schwierigkeiten. + + + Name nicht möglich. + + + Ungültige Pfade + + + Die Verbindung zum Server konnte nicht hergestellt werden, + + + Wheel Wizard konnte nicht aktualisiert werden. + + + Retro Rewind wird aktualisiert + + + Diese Änderung wird in {$1} zurückgesetzt, außer du wendest die Änderung an. + + + Bist du dir sicher, dass du Retro Rewind neu installieren möchtest? + + + Retro Rewind-Version ist zu alt. + + + Löschen ist permanent und kann nicht rückgängig gemacht werden + + + Bist du sicher, dass du {$1} Miis löschen möchtest? + + + Mii '{$1}' konnte nicht erstellt werden. + + + Daten von Mii '{$1}' konnten nicht verarbeitet werden. + + + Mii '{$1}' konnte nicht dupliziert werden. + + + Mii '{$1}' konnte nicht abgerufen werden. + + + Mii '{$1}' konnte nicht gespeichert werden. + + + Daten von Mii '{$1}' konnten nicht verarbeitet werden. + + + Mii '{$1}' konnte nicht aktualisiert werden. + + + Freundescode wurde in die Zwischenablage kopiert + + + {$1} wurde dupliziert + + + {$1} duplizierte Miis erstellt + + + {$1}' wurde gelöscht + + + {$1} Miis wurden gelöscht + + + Mii zu deinen Miis hinzugefügt + + + Name erfolgreich zu '{$1}' geändert + + + Profil als Hauptprofil festlegen + + + Mii '{$1}' wurde als Datei '{$2}' gespeichert + + + Mii konnte nicht gespeichert werden + + + Es gab keine Miis zum Löschen. + + + Es gab keine Miis zum Exportieren. + + + Mod-Name darf nicht leer sein. + + + Suche nach Spielern... + + + Gewünschten Pfad hier eingeben... + + + Wähle eine Mod aus der Liste um mehr Details zu sehen. + + + Keine Mod ausgewähltt. + + + Die Installation von Dolphin ist schiefgelaufen. Versuche manuell eine Flatpak Dolphin Version herunterzuladen. + + + Die Installation von Dolphin ist schiefgelaufen. + + + Es ist ein Fehler beim abrufen der Mod-Informationen aufgetreten. + + + Ein Fehler ist während dem Dowload aufgetreten. + + + Mod Download ist schiefgelaufen. + + + Bitte gib einen Namn für die Mod ein. + + + Mod Name kann keine ungültigen Zeichen beinhalten. + + + Mod Name nicht gültig. + + + Dateien konnten nicht gefunden werden. + + + Konnte die Mod nicht herunterladen. + + + Gib Mii Namen ein... + + + Gib Mod Namen ein... + + + Gib Text ein... + + + Suche nach Mods... + + + Installiere Dolphin Emulator + + + Dies könnte eine Weile dauern je nach dem wie schnell dein Internet ist. + + + Die Flatpak Dolphin Version scheint nicht installiert zu sein. Möchtest du sie von uns installieren lassen (ganzes System)? + + + Dolphin Flatpak Installation + + + Name von {$1} ändern + + + Einen neuen Namen eingeben + + + Du bist dabei das Spiel ohne Mods zu starten. Möchtest du deinen My-Stuff Ordner leeren? + + + Nach Mods suchen... + + + Diese Sprache wurde übersetzt von: {$1} + + + Von GameBanana unterstützt + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Phrases.es.resx b/WheelWizard/Resources/Languages/Phrases.es.resx index bb263f2a..d3ee46c3 100644 --- a/WheelWizard/Resources/Languages/Phrases.es.resx +++ b/WheelWizard/Resources/Languages/Phrases.es.resx @@ -18,25 +18,25 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + Puedes añadir amigos dentro del juego - + Aun no has agregado amigos - + Los mods pueden alterar el funcionamiento del juego. Importa tus mods apretando en el botón de abajo - + No se encontraron mods. - + Tienes que jugar el juego mínimo una vez para poder ver tus perfiles listados aca. - + Puedes no tener conexión a internet, o que nadie esté jugando actualmente. - + No se encontraron salas. @@ -69,171 +69,132 @@ Atención. Esta pagina no tiene la funcionalidad de unirte a una sala directamente. Para unirte a una sala en especifico, necesitará unirse mediante un amigo, o con suerte, encontrando la sala en VS Mundial. - - Ingrese la ruta deseada... - - - Buscando jugadores... - - - Ya hay archivos en tu carpeta de Retro Rewind. ¿Desea instalar? - - + ¿Desea aplicar la nueva escala? - + No se pudo conectar al servidor. Intente de nuevo más tarde - + Carpeta del Emulador Dolphin encontrado - + Ante la duda, solo marque si\n. Carpeta del emulador Dolphin encontrado. ¿Desea usar esta carpeta? - + Carpeta del Emulador Dolphin no encontrada - + La carpeta del Emulador Dolphin no se encontró automaticamente. Intentelo manualmente. Aprete 'Ayuda' para más información. - + Instalar Retro Rewind - + Asegurese de que todas las rutas sean correctas y vuelvalo a intentar. - - Ingrese nombre del mod: - - - Ingrese título: - - + Tiempo restante estimado: - + Error al buscar actualizaciones - + Error al aplicar una actualización. Cancelando. - + Error al borrar archivos para la actualización. Cancelando - + Instalando mods - + Instalando {$1} mods - + Instalando Retro Rewind - + Instalando Retro Rewind por primera vez - + Obteniendo la ultima versión de Wheel Wizard desde Github - - ¿Quiere combinar todos los archivos en un mod? - - - No puede dejar el nombre del mod en blanco. - - + Un mod con el nombre '{$1}' ya existe. - + Se encontraron mods - + Estas a punto de lanzar el juego sin ningún mod. ¿Quieres vaciar tu carpeta My-Stuff? - + Múltiples archivos seleccionados - + La version {$1} de WheelWizzard está disponible (Actualmente en {$2}). ¿Desea actualizar ahora? - + No existe una carpeta de mods - + No se encontró el Emulador Dolphin. Seleccione la ruta en ajustes. - + No se encontró el juego. Seleccione la ruta en ajustes. - + Antiguo rksys.dat encontrado - + Se encontró data guardada antigua. ¿Desea usarla? - - Version antigua de Retro Rewind encontrada. - - - Archivos antiguos de Retro Rewind fueron encontrados. ¿Desea moverlos a la nueva ubicación? - - + Procesando {$1} de {$2} archivos... - + Reinstalar Retro Rewind - + Error al reinstalar con derechos de administrador. - + No se pudo determinar tu versión de Retro Rewind. ¿Desea instalar Retro Rewind? - + Tu versión de Retro Rewind es muy antigua para actualizar. ¿Desea reinstalar Retro Rewind? - + Su versión de Retro Rewind es la última lanzada. - + Se guardaron correctamente los ajustes. - + ¿Estas seguro que deseas borrar {$1}? - + Incapaz de actualizar Wheel Wizard. Asegurate que la aplicación esta ubicada en una carpeta que se puede editar.\n No se encontró la carpeta actual. - + Incapaz de confirmar la actualidad de su versión de Wheel Wizard.\n Podrías estar experimentando problemas de conexión. - + Actualizando usando administrador - + A veces una actualización requiere derechos de administrador. ¿Desea activarlo para esta actualización? - - Actualizar Retro Rewind - - + Actualizando Wheel Wizard - + Actualización de Wheel Wizard disponible - + Descargando {$1} MB - - ¡Habla con nosotros! - - - Codigo Fuente - - - ¡Apoyanos! - Hecho por: {$1} y {$2} @@ -267,4 +228,160 @@ {$1} segundos + + No podemos leer la información de Miis de tu sistema. Asegurate que hayas lanzado el juego al menos una vez. + + + No hay Miis todavía! + + + El nombre del creador tiene que tener menos de 11 caracteres. + + + Los nombres tienen que tener entre 3 y 10 caracteres. + + + Para añadir a amigos necesitas añadirlos en el juego. + + + El perfil principal es usado como referencia en Wheel Wizard. + + + Esto solo muestra las regiones en las que TÚ has jugado. + + + Se detectó una ruta de archivo no válida. Contacte a los desarrolladores.\n Error del servidor: {$1} + + + Abortando actualización de RR + + + No se pudo cambiar el Mii + + + No se pudo cambiar el nombre + + + No se pudo crear la base de datos de Miis + + + Se produjo un error. + + + Mii cambiado con éxito + + + Uno o más de los Miis está en favoritos. Los Miis solo pueden ser borrados si no está en favoritos para prevenir borrar Miis accidentalmente. + + + No se pueden borrar Miis en favoritos + + + No se puede ver mod que no fue instalado por el navegador de mods. + + + Se produjo un error al abrir el mod seleccionado. + + + No se puede ver este mod. + + + No se pueden buscar actualizaciones en este momento. Es posible que no tengas conexión a internet o que el servidor esté inactivo. + + + Nombre no permitidio. + + + Rutas inválidas. + + + No se pudo conectar al servidor. + + + No se pudo actualizar Wheel Wizard. + + + Actualizando Retro Rewind + + + Este cambio se revertira en {$1} a menos de que decidas de quedarte con este cambio. + + + ¿Estas seguro que quieres reinstalar Retro Rewind? + + + La version de Retro Rewind es muy antigua. + + + Borrar es permanente y no se puede revertir. + + + ¿Estas seguro que deseas borrar {$1} Miis? + + + Se produjo un error al producir Mii '{$1}' + + + Se produjo un error al deserializar Mii '{$1}' + + + Se produjo un error al duplicar Mii '{$1}' + + + Se produjo un error al duplicar Mii '{$1}' + + + Se produjo un error al guardar Mii '{$1}' + + + Se produjo un error al serializar Mii '{$1}' + + + Se produjo un error al actualizar Mii '{$1}' + + + Codigo de amigo copiado al portapapeles + + + Se creó una duplica de '{$1}' + + + Se creó {$1} duplicas de Miis. + + + Se borró '{$1}' + + + Se borraron {$1} Miis + + + Mii ha sido añadido a tus Miis + + + Se cambió el nombre a '{$1}' con éxito + + + Establecer perfil como principal + + + Se guardó Mii '{$1}' a archivo '{$2}' + + + No se pudo guardar el Mii + + + Parace que no hay Miis para borrar + + + Parece que no hay Miis para exportar + + + No puede dejar el nombre del mod en blanco. + + + Buscando jugadores... + + + Ingrese la ruta deseada... + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Phrases.fr.resx b/WheelWizard/Resources/Languages/Phrases.fr.resx index 01ce1bdc..12baa874 100644 --- a/WheelWizard/Resources/Languages/Phrases.fr.resx +++ b/WheelWizard/Resources/Languages/Phrases.fr.resx @@ -18,15 +18,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Parlez-nous-en ! - - - Code Source - - - Supportez-nous ! - Fait par: {$1} \n et {$2} @@ -60,60 +51,54 @@ Merci à tous les traducteurs: - - Choisissez le chemin désiré ici... - - + Vous pouvez ajouter vos amis in-game - + Pas d'amis pour le moment ! - + Les mods permettent de modifer comment le jeu va se comporter. Commencez à importer votre premier mod en cliquant sur le bouton en dessous. - + Pas de mods trouvés - - Vous devez avoir lancé le jeu au moins une fois pour voir votre profil listé ici + + Vous devez avoir lancé le jeu au moins une fois pour voir vos profils listé ici - + Vous n'avez pas de connexion internet ou personne n'est en train de jouer. - + Aucune salles trouvées Attention, cete page ne propose pas la possibilité de rejoindre la salle directement. Pour rejoindre une salle spécifique, vous devez soit la rejoindre via un ami ou espérer de la rejoindre via les serveur en ligne. - - Recherche de joueurs... - - + Temps restant estimé: - + Installation de mods - + Installation de {$1} mods - + Mods trouvés - + Vous êtes sur le point de lancer le jeu sans aucun mods. Voulez vous supprimer votre dossier My-Stuff ? - + N'a pas pu trouver Dolphin Emulator, veuillez choisir le chemin dans les paramètres - + N'a pas pu trouver le jeu, veuillez chosir le chemin dans les paramètres - + Téléchargement de {$1} MB @@ -143,133 +128,342 @@ Voulez vous supprimer votre dossier My-Stuff ? {$1} secondes - - Il y a déjà des fichiers dans votre dossier RetroRewind. -Voulez-vous l'installer ? - - + Voulez-vous appliquez la nouvelle échelle ? - + N'a pas pu de se connecter au serveur. Veuillez réessayer plus tard. - + Dossier Dolphin Emulator trouvé - + Si vous ne savez pas ce que tous cela veut dire, juste cliquez sur oui :) \nDossier Dolphin Emulator trouvé. Voulez-vous utilser ce dossier ? - + Dossier Dolphin Emulator non trouvé - + Le dossier Dolphin Emulator n'a pas été automatiquement trouvé. Veuillez essayé de trouver le dossier manuellement, cliquez sur "Aide" pour plus d'information. - + Télécharger Retro Rewind - + Vérifiez bien si tous les chemins sont corrects et essayez à nouveau. - - Rentrez un nom pour le mod: - - - Rentrez un titre: - - + Échec de la vérification des mises à jour - + Échec d'application de la mise à jour. Annulation. - + Échec de la désinstallation de fichiers pour la mise à jour. Annulation - + Installation de Retro Rewind - + Installation de Retro Rewind pour la première fois - + Obtenez la dernière version de Wheel Wizard depuis Github - - Voulez-vous combiné tous les fichiers en 1 unique mod ? - - - Le nom du mod ne peut pas être vide - - + Un mod avec le nom "{$1}" existe déjà - + Plusieurs fichers sélectionnés - - La version {$1} de Wheel Wizard est disponible (actuellement en {$2}. Voulez-vous mettre à jour maintenant ? + + La version {$1} de Wheel Wizard est disponible (Version actuelle: {$2}). Voulez-vous mettre à jour maintenant ? - + Le dossier du mod n'existe pas - + Ancien rksys.dat trouvé - + Une ancienne sauvegarde a été trouvée. Voulez-vous l'utiliser ? (Recommandé) - - Ancienne version de Retro Rewind trouvée - - - Des anciens fichers de Retro Rewind ont été trouvés. Voulez-vous les déplacer vers un nouveau fichier ? - - + Traitement de {$1} sur {$2} fichiers - + Réinstaller Retro Rewind - + Échec du redémarrage avec les droits d'administrateur. - + Votre version de Retro Rewind n'a pas pu être déterminée. Voulez-vous installer Retro Rewind ? - + Votre version de Retro Rewind est trop vieille pour être mis à jour. Voulez-vous réinstaller Retro Rewind ? - + Retro Rewind est bien à jour. - + Paramètre sauvegarder avec succès ! - - Êtes-vous sur de vouloir supprimer {$1}? + + Êtes-vous sûr de vouloir supprimer {$1} ? - + Impossible de mettre à jour Wheel Wizard. Veuillez bien vérifier que l'application se trouve dans un dossier qui peut être écrit. \n N'a pas pu trouver le dossier actuel. - + Impossible de vérifier si Wheel Wizard est mis à jour. \nVous pourrez expérimenter des problèmes d'internet. - + Mettre à jour en Administrateur - - Des fois, une mise à jour demande les droits d'admin. Voulez-vous les activés pour cette mise à jour ? + + Des fois, une mise à jour demande les droits d'admin. Voulez-vous les activer pour cette mise à jour ? - - Mise à jour Retro Rewind + + Mise à jour de Wheel Wizard en cours... - - Mise à jour de Wheel Wizard en cours - - + Mise à jour Wheel Wizard disponible ! + + Nous ne pouvons pas lire les données du Mii de votre système. Veuillez vérifier que vous avez lancé le jeu au moins une fois + + + Aucun Mii! + + + Le nom de l'auteur doit faire moins de 11 caractères. + + + Les noms doivent être composés de 3 à 10 caractères. + + + Ajouter des amis que vous voulez dans le jeu. + + + Le profil principal est utilisé en tant que référence dans Wheel Wizard + + + Ceci montre les régions que VOUS avez jouez + + + Chemin vers un fichier incorrect. Veuillez contacter les développeurs de Wheel Wizard.\n Erreur Serveur: {$1} + + + Anuller la mise à jour de RR + + + Échec du changement de Mii + + + Échec du changement de nom + + + Échec de la création de la base de donnée des Miis + + + Une erreur s'est produite. + + + Changement du Mii avec succès + + + Un ou plusieurs des Miis choisis sont en favoris. Les Miis peuvent être supprimés seulement s'ils ne sont pas en favoris pour éviter une suppression involontaire. + + + Impossibilité de supprimer des Mii en favoris. + + + Impossibilité de voir un mod qui n'a pas été installé depuis GameBanana + + + Quelque chose a échoué en essayant d'ouvrir le mod choisi. + + + Impossibilité de voir ce mod. + + + Impossibilité de vérifier les mises à jour actuellement. Vous n'êtes probablement pas connecté à internet ou le serveur est probablement en maintenance + + + Nom impossible. + + + Chemin invalide + + + N'a pas pu se connecter au serveur + + + Impossible de mettre à jour Wheel Wizard. + + + Mise à jour de Retro Rewind en cours... + + + Cette modification va être rétablie dans {$1} sauf si vous choisissez de garder cette modification. + + + Êtes-vous sûr de vouloir réinstaller Retro Rewind ? + + + Votre version de Retro Rewind est trop vieille. + + + La suppresion est permanente et ne peut pas être inversée. + + + Êtes-vous sûr de vouloir supprimer {$1} Miis ? + + + Échec de la création du Mii '{$1}' + + + Échec de la désérialisation du Mii '{$1}' + + + Échec du clonage des Miis '{$1}' + + + Échec d'obtention du Mii '{$1}' + + + Échec de savegarde du Mii '{$1}' + + + Échec de la sérialisation du Mii '{$1}' + + + Échec de mise à jour du Mii '{$1}' + + + Code Ami copié dans le presse-papier + + + Clonage de '{$1}' + + + Clonage de {$1} Miis + + + Suppression de '{$1}' + + + Suppression de {$1} Miis + + + Le Mii a été ajouté a vos Miis + + + Changement du nom en '{$1}' avec succès + + + Choisir ce profil en profil principal + + + Mii '{$1}' sauvegardé au chemin suivant '{$2}' + + + Impossible de savegarder le Mii + + + Il semblerait qu'aucun Miis peuvent être supprimer + + + Il semblerait qu'aucun Miis peuvent être exporter + + + Le nom du mod ne peut pas être vide + + + Recherche de joueurs... + + + Choisissez le chemin désiré ici... + + + Sélectionner un mod dans la liste pour voir ces détails + + + Aucun mod sélectionné + + + L'installation de Dolphin Emulator a échoué. Veuillez essayer d'installer Flatpak Dolphin. + + + Échec de l'installation de Dolphin + + + Échec d'obtention des informations du mod + + + Une erreur s'est produite pendant le téléchargement du mod suivant: {$1} + + + Échec du téléchargement du mod. + + + Veuillez écrire un nom pour le mod. + + + Le nom du mod contient des caractères invalides. + + + Nom du mod invalide. + + + Les fichiers n'ont pas été trouvés. + + + Impossibilité de télécharger le mod + + + Entrez le nom du mii... + + + Entrez le nom du mod... + + + Entrez un texte ici... + + + Recherche de mods... + + + Installation de Dolphin Emulator + + + Cela peut prendre plus de temps en fonction de votre connexion internet. + + + La version Flatpak de Dolphin Emulator ne semble pas être installée. Veuillez-vous qu'on vous l'installe ? + + + Installation de Dolphin Flatpak... + + + Changer le nom actuel à: {$1} + + + Entrez un nouveau nom + + + Voulez-vous télécharger et installer le mod suivant: {$1} ? + + + Recherche de mods... + + + Cette langue a été traduite par: {$1} + + + Contribué par GameBanana + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Phrases.it.resx b/WheelWizard/Resources/Languages/Phrases.it.resx index 3ef14f41..9cdeca4c 100644 --- a/WheelWizard/Resources/Languages/Phrases.it.resx +++ b/WheelWizard/Resources/Languages/Phrases.it.resx @@ -18,25 +18,25 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + Puoi aggiungere amici all'interno del gioco - + Ancora nessun amico! - + Le mod possono alterare come funziona il gioco. Inizia importando la tua prima mod cliccando il pulsante sottostante. - + Nessuna mod trovata - + Devi aver giocato almeno una volta per vedere la lista dei tuoi profili qui - + Potresti non avere una connessione ad internet oppure nessuno sta giocando al momento. - + Nessuna stanza trovata @@ -70,173 +70,133 @@ Attenzione, questa pagina non offre alcuna funzionalità per unirsi ad una stanza direttamente. Per unirsi ad una specifica stanza, dovrai farlo tramite un amico oppure sperare di unirti tramite i server online. - - Inserisci qui l'indirizzo desiderato... - - - Cerca giocatori... - - - Ci sono già dei file nella tua cartella RetroRewind. -Vuoi installare? - - + Vuoi applicare le nuove dimensioni? - + Impossibile connettersi al server. Riprova più tardi. - + Cartella dell'Emulatore Dolphin trovata - + Se non sai cosa ciò vuol dire, clicca su sì :) \nCartella dell'Emulatore Dolphin trovata. Vuoi usare questa cartella? - + Cartella dell'Emulatore Dolphin non trovata - + Cartella dell'Emulatore Dolphin non trovata automaticamente. Prova a trovare la cartella manualmente, clicca "Aiuto" per ulteriori informazioni. - + Scarica Retro Rewind - + Assicurati che tutti gli indirizzi siano corretti e riprova. - - Inserisci il nome della mod: - - - Inserisci il titolo: - - + Tempo stimato rimanente: - + Controllo aggiornamenti fallito - + Fallita l'applicazione di un aggiornamento. Annullamento. - + Fallito il cancellamento dei file dell'aggiornamento. Annullamento. - + Installando delle mod - + Installando {$1} mod - + Installazione di Retro Rewind - + Installazione di Retro Rewind per la prima volta - + Ottenendo l'ultima versione di Wheel Wizard da GitHub - - Vuoi unire tutti i file in un'unica mod? - - - Il nome della mod non può essere vuoto - - + Una mod col nome '{$1}' esiste già. - + Mod trovate - + Stai per avviare il gioco senza alcuna mod. Vuoi svuotare la tua cartella My-Stuff? - + Più file selezionati - + La versione {$1} di Wheel Wizard è disponibile (al momento è {$2}). Vuoi aggiornare ora? - + La cartella della mod non esiste - + Non è stato possibile trovare l'Emulatore Dolphin, controlla l'indirizzo nelle impostazioni - + Non è stato possibile trovare il gioco, controlla l'indirizzo nelle impostazioni - + Un vecchio rksys.dat è stato trovato - + Dei vecchi dati di salvataggio sono stati trovati. Vuoi usarli? (Consigliato) - - Una vecchia versione di Retro Rewind è stata trovata - - - Dei vecchi file di Retro Rewind sono stati trovati. Vuoi muoverli nella nuova posizione? - - + Processando file {$1} di {$2}... - + Reinstalla Retro Rewind - + Errore riavviando con diritti di amministratore. - + La tua versione di Retro Rewind non può essere determinata. Vuoi scaricare Retro Rewind? - + La tua versione di Retro Rewind è troppo vecchia per essere aggiornata. Vuoi reinstallare Retro Rewind? - + Retro Rewind è aggiornato. - + Impostazioni salvate con successo! - + Sei sicuro di voler cancellare {$1}? - + Impossibile aggiornare Wheel Wizard. Assicurati che l'applicazione si trovi in una cartella con diritti di scrittura. \nNon è possibile trovare la cartella attuale. - - Impossibile controllare se Wheel Wizard è aggiornato. \nPotresti avere problemi di connessione. + + Impossibile verificare se Wheel Wizard è aggiornato. \nPotresti avere problemi di connessione. - + Aggiorna usando l'Amministratore - + A volte un aggiornamento può richiedere diritti di amministratore, vuoi attivarli per questo aggiornamento? - - Aggiorna Retro Rewind - - + Aggiornando Wheel Wizard - + Aggiornamento di Wheel Wizard disponibile - + Scaricando {$1} MB - - Parla con noi! - - - Codice sorgente - - - Supportaci! - Fatto da: {$1} \n e {$2} @@ -270,4 +230,238 @@ Vuoi svuotare la tua cartella My-Stuff? {$1} secondi + + Non possiamo leggere i dati dei Mii dal tuo sistema. Assicurati che hai avviato il gioco almeno una volta + + + Ancora nessun Mii! + + + Il nome dell'autore deve essere lungo al massimo 10 caratteri. + + + I nomi devono essere lunghi tra i 3 e i 10 caratteri. + + + Per aggiungere amici, devi farlo all'interno del gioco. + + + Il profilo primario è utilizzato come riferimento in Wheel Wizard + + + Questo fa vedere solo le regioni in cui TU hai giocato + + + Incontrato un percorso file invalido. Contatta gli sviluppatori.\n Errore server: {$1} + + + Annullando l'aggiornamento di RR + + + Cambio del Mii fallito + + + Cambio del nome fallito + + + Creazione del catalogo dei Mii fallita + + + È successo un errore. + + + Mii cambiato con successo + + + Uno o più dei Mii selezionati è favorito. I Mii possono solo essere eliminati se non sono favoriti per evitare cancellazioni involontarie. + + + Impossibile eliminare dei Mii favoriti. + + + Impossibile vedere una mod che non è stata installata tramite il browser delle mod. + + + Qualcosa è andato storto nel tentativo di aprire la mod selezionata. + + + Impossibile vedere questa mod. + + + Impossibile verificare gli aggiornamenti in questo momento. Potresti non avere una connessione ad internet oppure il server potrebbe essere offline. + + + Nome impossibile. + + + Percorsi non accettabili. + + + Impossibile connettersi al server + + + Impossibile aggiornare Wheel Wizard. + + + Aggiornando Retro Rewind + + + Questa modifica si ripristinerà in {$1} a meno che tu decida di mantenerla. + + + Sei veramente sicuro di voler reinstallare Retro Rewind? + + + La versione di Retro Rewind è datata. + + + L'eliminazione è permanente e non può essere recuperata. + + + Sei sicuro di voler cancellare {$1} Mii? + + + Creazione del Mii '{$1}' fallita + + + Deserializzazione del Mii '{$1}' fallita + + + Duplicazione del Mii '{$1}' fallita + + + Ottenimento del Mii '{$1}' fallito + + + Salvataggio del Mii '{$1}' fallito + + + Serializzazione del Mii '{$1}' fallita + + + Aggiornamento del Mii '{$1}' fallito + + + Copiato il codice amico negli appunti + + + Creato il duplicato di '{$1}' + + + Creati {$1} Mii duplicati + + + {$1}' cancellato + + + Cancellati {$1} Mii + + + Un Mii è stato aggiunto ai tuoi favoriti + + + Nome cambiato con successo in '{$1}' + + + Imposta il profilo come primario + + + Salvato il Mii '{$1}' nel file '{$2}' + + + Impossibile salvare il Mii + + + Sembrano non esserci Mii da eliminare + + + Sembrano non esserci Mii da esportare + + + Il nome della mod non può essere vuoto. + + + Cerca giocatori... + + + Inserisci qui l'indirizzo desiderato... + + + Seleziona una mod nella lista per vedere i suoi dettagli + + + Nessuna mod selezionata + + + L'installazione dell'emulatore Dolphin è fallita. Riprova manualmente installando Dolphin usando Flatpak. + + + Installazione di Dolphin fallita + + + Recupero delle informazioni della mod fallito + + + È successo un errore durante il download: {$1} + + + Download della mod fallito. + + + Dai un nome alla mod. + + + Il nome della mod contiene caratteri non validi. + + + Il nome della mod non è valido. + + + I file non sono stati trovati. + + + Impossibile scaricare la mod. + + + Inserisci il nome del Mii... + + + Inserisci il nome della mod... + + + Inserisci qui del testo... + + + Cerca delle mod... + + + Installando l'emulatore Dolphin + + + Ciò potrebbe richiedere un po' di tempo a seconda della tua connessione ad internet. + + + La versione Flatpak dell'emulatore Dolphin non sembra essere installata. Vuoi che venga installata nel tuo sistema? + + + Installazione di Dolphin tramite Flatpak + + + Cambiando nome da: {$1} + + + Inserisci il nuovo nome + + + Vuoi scaricare ed installare la mod: {$1}? + + + Cercando delle mod... + + + Questa lingua è stata tradotta da: {$1} + + + Offerto da GameBanana + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Phrases.ja.resx b/WheelWizard/Resources/Languages/Phrases.ja.resx index c8e78885..852accbb 100644 --- a/WheelWizard/Resources/Languages/Phrases.ja.resx +++ b/WheelWizard/Resources/Languages/Phrases.ja.resx @@ -18,13 +18,13 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + ゲーム内でフレンドを追加できます - + まだフレンドがいません! - + ここにプロフィールを載せるには一度プレイする必要があります @@ -54,194 +54,157 @@ 現在 開いているルームが {$1}部屋あります - - お問い合わせ - - - ソースコード - - - サポート - 作成者: {$1} \nと {$2} すべての翻訳者さんに感謝します: - + Retro Rewindをインストール (初回) - + Modでゲームを改変 (キャラクターの見た目など) できます。下のボタンを押してModのインポートを開始してください。 - + Modが見つかりませんでした - + インターネットに接続されていないか、誰もプレイしていない可能性があります。 - + ルームが見つかりませんでした ルームに直接合流する機能はないことに注意してください。 特定のルームに参加するには、フレンドを通して合流するか、オンラインサーバーに参加して合流する必要があります。 - - 希望のパスを入力してください... - - - プレイヤーを検索します... - - - 既にRetroRewindフォルダーにファイルがあります。 -インストールしますか? - - + 新しいウィンドウサイズを適用しますか? - + サーバーに接続できませんでした。後でもう一度試してください。 - + Dolphinエミュレーターのフォルダーが見つかりました - + 意味がよく分からなければ、「はい」をクリックしてください。\nDolphinエミュレーターのフォルダーが見つかりました。このフォルダーを使用しますか? - + Dolphinエミュレーターのフォルダーが見つかりませんでした - + Dolphinエミュレーターのフォルダーが自動では見つかりませんでした。 フォルダーを手動で見つけてください。詳細については「help」をクリックしてください。 - + Retro Rewindをダウンロードする - + すべてのパスが正しいことを確認して、もう一度お試しください。 - - Modの名前を入力: - - - タイトルを入力: - - + おおよその残り時間: - + アップデートの確認に失敗しました - + アップデートの適用に失敗しました。中止しています。 - + アップデートファイルの削除に失敗しました。中止しています。 - + Modをインストール - + {$1}個のModをインストール - + Retro Rewindをインストール - + githubから最新のWheel Wizardを入手 - - すべてのファイルを1つのModにまとめますか? - - - Modの名前は空にできません - - + 「{$1}」というModは既に存在しています。 - + Modが見つかりました - + Modなしでゲームを起動しようとしています。 My Stuffフォルダーをクリアしますか? - + 複数のファイルが選択されています - + Wheel Wizardのバージョン{$1} (現在のバージョンは{$2}) が利用できます。すぐにアップデートしますか? - + Modフォルダーがありません - + Dolphinエミュレーターが見つかりませんでした。設定でパスを設定してください。 - + ゲームファイルが見つかりませんでした。設定でパスを設定してください。 - + 古いrksys.datが見つかりました - + 古いセーブデータが見つかりました。このセーブデータを使用しますか?(おすすめ) - - 古いRetro Rewindが見つかりました - - - 古いRetro Rewindのファイルが見つかりました。新しい場所に移動しますか? - - + {$2}個中{$1} 個のファイルを処理しています... - + Retro Rewindの再インストール - + 管理者権限で再起動するのに失敗しました。 - + Retro Rewindのバージョンを判別できませんでした。Retro Rewindを再インストールしますか? - + Retro Rewindのバージョンが古すぎるため更新できません。Retro Rewindを再インストールしますか? - + Retro Rewindは最新です。 - + 設定が保存されました! - + {$1}を削除してもよろしいですか? - + Wheel Wizardが更新できません。アプリが書き込み可能なフォルダーにあることを確認してください。\n現在のフォルダーが見つかりません。 - + Wheel Wizardが最新かどうか確認できません。\nネットワークの問題が発生している可能性があります。 - + 管理者権限で更新する - + アップデートには管理者権限が必要な場合があります。今回のアップデートで管理者権限を有効にしますか? - + Retro Rewindをアップデート - + Wheel Wizardをアップデート - + Wheel Wizardのアップデートがあります - + {$1}MBをダウンロード中 @@ -271,4 +234,235 @@ My Stuffフォルダーをクリアしますか? {$1}秒 + + Miiのデータを読み込めません。必ず一度はゲームを起動してください。 + + + まだMiiがありません! + + + 作成者の名前の長さは10文字以下にしてください + + + 名前の長さは3文字から10文字にしてください + + + フレンドを追加するためにはゲーム内で追加する必要があります + + + メインのプロファイルはWheel Wizardクライアントで参照されます + + + これはあなたがプレイした地域のみを表示します + + + 無効なファイルパスが検知されました。開発者に連絡してください。\n サーバーエラー: {$1} + + + Retro Rewindのアップデートを中止 + + + Miiの変更に失敗しました + + + 名前の変更に失敗しました + + + Miiの情報の作成に失敗しました + + + エラーが発生しました + + + Miiが正しく変更されました + + + 選択したMiiのうち1体以上がお気に入りに設定されています。誤って削除してしまうのを防ぐため、お気に入りに設定されていないMiiのみ削除できます + + + お気に入りのMiiは削除できません + + + ModブラウザーでインストールしていないModは閲覧できません + + + 選択されたModを開こうとした際に問題が発生しました + + + このModは閲覧できません + + + 現在アップデートを確認できません。インターネットに接続されていないか、サーバーがダウンしている可能性があります + + + その名前にはできません + + + 無効なパスです + + + サーバーに接続できませんでした + + + Wheel Wizardのアップデートを無効にする + + + 変更の保存を選択しないと、この変更は{$1}で変更前に戻ります + + + Retro Rewindを再インストールしてもよろしいですか? + + + Retro Rewindのバージョンが古すぎます + + + 削除は永久的であり、元に戻すことはできません + + + {$1}Miiを削除してもよろしいですか? + + + Mii '{$1}'の作成に失敗しました + + + Mii '{$1}'の復元に失敗しました + + + Mii '{$1}'のコピーに失敗しました + + + Mii '{$1}'の取得に失敗しました + + + Mii '{$1}'の保存に失敗しました + + + Mii '{$1}'の変換に失敗しました + + + Mii '{$1}'のアップデートに失敗しました + + + クリップボードにフレンドコードをコピーしました + + + {$1}'のコピーを作成しました + + + {$1}Miiのコピーを作成しました + + + {$1}'を削除しました + + + {$1}Miiを削除しました + + + MiiはあなたのMiiに追加されました + + + 名前を'{$1}'に変更しました + + + プロファイルをメインに設定する + + + Mii '{$1}'をファイル '{$2}'に保存しました + + + Miiを保存できません + + + 削除するMiiが存在しないようです + + + エクスポートできるMiiがなかったようです + + + リスト内のModを選択して詳細を表示します + + + Modが選択されていません + + + Dolphinエミュレーターのインストールに失敗しました。Dolphinのflatpakバージョンのインストールを手動で行ってみてください。 + + + Dolphinのインストールに失敗 + + + Modの情報取得に失敗 + + + ダウンロード中にエラーが発生しました: {$1} + + + Modのダウンロードに失敗しました + + + Modの名前を入力してください + + + Modの名前は空にできません + + + Modの名前に無効な文字が含まれています + + + Modの名前が無効です + + + ファイルが見つかりませんでした + + + Modをダウンロードできません + + + Dolphinエミュレーターをインストール + + + インターネットの状況によっては時間がかかる場合があります + + + Dolphinエミュレーターのflatpakバージョンがインストールされていないようです。システム全体にインストールしますか? + + + Dolphin flatpakバージョンのインストール + + + Modのダウンロードとインストールを行いますか: {$1}? + + + Modを検索しています... + + + GameBananaによって提供されています + + + プレイヤーを検索します... + + + 希望のパスを入力してください... + + + Miiの名前を入力してください... + + + Modの名前を入力してください... + + + ここにテキストを入力してください... + + + Modを検索します... + + + 名前をこれから変更します: {$1} + + + 新しい名前を入力してください + + + この言語はこれらの方が翻訳しました: {$1} + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Phrases.ko.resx b/WheelWizard/Resources/Languages/Phrases.ko.resx index 88b1b3b0..81554fa1 100644 --- a/WheelWizard/Resources/Languages/Phrases.ko.resx +++ b/WheelWizard/Resources/Languages/Phrases.ko.resx @@ -18,25 +18,25 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + 게임에서 친구를 추가 할 수 있습니다. - + 아직 친구가 없어요! - + MOD는 게임 작동 방식을 바꿀 수 있습니다. 모드를 불러오기 위해 아래 버튼을 클릭해 주세요. - + MOD를 찾을 수 없습니다. - + 게임을 한 번 이상 실행해야 프로필을 볼 수 있습니다. - + 인터넷 연결이 끊어졌거나, 아무도 플레이하고 있지 않은 상태입니다. - + 방을 찾을 수 없습니다. @@ -70,171 +70,132 @@ 주의) 해당 페이지에선 방에 직접 합류하는 기능은 없습니다. 특정 방에 들어가려면, 친구를 통해 들어가거나 온라인 서버에 가입하여 들어가야 합니다. - - 원하는 경로를 입력하세요 - - - 플레이어 찾는 중... - - - Retro Rewind 폴더가 이미 존재합니다. 설치 하시겠습니까? - - + 새로운 해상도를 적용하시겠습니까? - + 서버에 연결할 수 없습니다. 잠시 후 다시 시도해 주십시오. - + 돌핀 에뮬레이터 폴더를 찾았습니다 - + 돌핀 에뮬레이터 폴더를 찾았습니다. 해당 폴더를 사용하시겠습니까? \n뭔 소린지 모르겠다면 그냥 "네"를 누르세요 ^_^ - + 돌핀 에뮬레이터 폴더를 찾지 못했습니다 - + 돌핀 에뮬레이터 폴더를 자동으로 찾지 못했습니다. 폴더를 수동으로 찾아야 합니다. "help"를 클릭해 자세한 설명을 보십시오. - + 레트로 리와인드 다운로드 - + 모든 경로가 정확한지 확인한 후 다시 시도해 주십시오. - - 모드 이름을 입력: - - - 제목 입력: - - + 추정된 남은 시간: - + 업데이트 확인 실패 - + 업데이트 적용 실패, 중단합니다. - + 업데이트 파일 삭제 실패, 중단합니다. - + 모드 설치 - + {$1}개의 모드를 설치 - + 레트로 리와인드 설치 - + 레트로 리와인드를 처음으로 설치 - + 최신 휠 위저드를 Github에서 받기 - - 모든 파일을 하나의 모드로 합치시겠습니까? - - - 모드의 이름을 공란으로 할 수 없습니다 - - + "{$1}" 이란 이름의 모드가 이미 존재합니다. - + 모드를 찾았습니다 - + 아무런 모드를 적용하지 않은체 게임을 실행시키려 시도하고 있습니다. My Stuff 폴더를 비우시겠습니까? - + 다수의 파일들이 선택됨 - + 휠 위저드 최신 버전 {$1}이 있습니다.(현재 버전 {$2}). 업데이트 하시겠습니까? - + 모드 폴더가 없습니다. - + 돌핀 에뮬레이터를 찾을 수 없습니다. 설정에서 경로를 지정해 주십시오. - + 게임을 찾을 수 없습니다. 설정에서 경로를 지정해 주십시오. - + 오래된 rksys.dat이 발견됨 - + 오래된 세이브 데이터가 발견됐습니다. 사용하시겠습니까?(사용 권장) - - 레트로 리와인드 구버전 발견 - - - 레트로 리와인드 구버전 파일들이 발견됐습니다. 새로운 위치에 해당 파일을 옮기시겠습니까? - - + {$2}개 중 {$1} 개의 파일 진행중... - + 레트로 리와인드 재설치 - + 관리자 권한으로 재시작하는 데 실패했습니다. - + 설치된 레트로 리와인드 버전을 확인 할 수 없습니다. 레트로 리와인드를 다운로드 하시겠습니까? - + 설치된 레트로 리와인드가 너무 구버전입니다. 레트로 리와인드를 재설치 하시겠습니까? - + 레트로 리와인드는 최신 버전입니다. - + 설정을 저장했습니다! - + {$1}을/를 지우시겠습니까? - + 휠 위저드를 업데이트할 수 없습니다. 프로그램이 쓰기 가능한 폴더에 있는지 확인하십시오.\n현재 폴더를 찾을 수 없습니다. - + 휠 위저드의 버전을 확인할 수 없습니다. \n네트워크 문제가 발생했을 수 있습니다. - + 관리자 권한으로 업데이트 - + 가끔, 업데이트에는 관리자 권한이 필요합니다. 이 업데이트를 위해 활성화 하시겠습니까? - - 레트로 리와인드 업데이트 - - + 휠 위저드를 업데이트 중 - + 휠 위저드의 새로운 업데이트가 있습니다. - + {$1} MB 다운로드 중 - - 문의하기! - - - 소스 코드 - - - 후원하기! - 만든이: {$1} \n 과 {$2} @@ -268,4 +229,79 @@ {$1}초 + + Mii 데이터를 읽을 수 없습니다. 게임을 한 번 이상 실행하십시오. + + + 아직 Mii가 없어요! + + + 작성자 이름은 반드시 10자 이내여야 합니다. + + + 이름은 반드시 3~10자 내여야 합니다. + + + 친구 추가는 게임 내에서 추가해야 합니다. + + + 메인 프로필은 휠 위저드 클라이언트에서 참조됩니다. + + + 플레이한 지역만 표시합니다. + + + 잘못된 파일 경로가 감지됐습니다. 개발자에게 연락하세요.\n 서버 에러: {$1} + + + 레트로 리와인드 업데이트 중단 + + + Mii 변경을 실패했습니다 + + + 이름 변경을 실패했습니다 + + + Mii 데이터베이스 작성에 실패했습니다. + + + 모드 이름을 입력: + + + 모드의 이름을 공란으로 할 수 없습니다 + + + 플레이어 찾는 중... + + + 원하는 경로를 입력하세요 + + + MOD를 선택해서 자세한 설명을 보세요 + + + 선택된 MOD가 없습니다. + + + 돌핀 에뮬레이터 설치에 실패했습니다. 직접 Flatpak 돌핀을 설치해주십시오. + + + 돌핀 설치 실패 + + + MOD 정보 불러오기 실패 + + + 오류가 발생했습니다. + + + 다운로드 중 오류가 발생했습니다: ($1) + + + MOD 다운로드 실패 + + + Mii 변경에 성공했습니다. + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Phrases.nl.resx b/WheelWizard/Resources/Languages/Phrases.nl.resx index b11d68a2..0ae03558 100644 --- a/WheelWizard/Resources/Languages/Phrases.nl.resx +++ b/WheelWizard/Resources/Languages/Phrases.nl.resx @@ -6,70 +6,65 @@ 1.3 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + - - Kom gezellig praten! - - - Source code - - - Steun ons - - Gemaakt door: {$1} \n en {$2} + Gemaakt door {$1} \n en {$2} Hartelijk dank aan alle vertalers: - Er zijn momenteel geen spelers online + Er zijn momenteel geen spelers online. - Er is momenteel 1 speler online + Er is momenteel 1 speler online. - Er zijn momenteeel {$1} spelers online + Er zijn momenteel {$1} spelers online. - Er is momenteel geen lobby actief + Er zijn momenteel geen kamers actief. - Er is momenteel 1 lobby actief + Er is momenteel 1 kamer actief. - Er zijn momenteel {$1} lobbies actief + Er zijn momenteel {$1} kamers actief. - Er zijn momenteel {$1} vrienden online + Er zijn momenteel {$1} vrienden online. - Er is momenteel 1 vriend online + Er is momenteel 1 vriend online. - Er zijn momenteel geen vrienden online + Er zijn momenteel geen vrienden online. - - Je moet het spel minimaal 1 keer hebben gespeeld om hier je profielen te kunnen bekijken + + Je moet het spel minimaal één keer hebben gespeeld om hier jouw profielen te kunnen bekijken. - - Je hebt nog geen vrienden + + Je hebt nog geen vrienden! - - Je kunt vrienden toevoegen door het spel op te starten + + Je kan vrienden in het spel toevoegen. - Vertalingen voor deze taal zijn {$1} compleet + Vertalingen voor deze taal zijn {$1}% voltooid. - - Je hebt nog geen mods + + Geen mods gevonden - - Mods kunnen de game aanpassen. Start met importeren van je eerste mod door op de Import knop te drukken. + + Mods kunnen de game aanpassen. Start met het importeren van jouw eerste mod door op de importeerknop te drukken. 1 dag @@ -81,7 +76,7 @@ 1 uur - {$1} uur + {$1} uren 1 minuut @@ -95,176 +90,374 @@ {$1} secondes - - Zoek voor spelers... - - - Vul hier uw bestands locatie in - - Let op dat deze pagina geen functionaliteit biedt om direct deel te nemen aan een lobbies. -Om aan een specifieke lobby, moet je deelnemen via een vriend, of hopen deel te nemen door deel te nemen aan de online servers. + Let op dat deze pagina er niet voor kan zorgen om direct deel te nemen aan een kamer. +Om aan een specifieke kamer deel te nemen, moet je dit óf via een vriend óf via de online servers doen. - - Momenteel geen lobbies + + Geen kamers gevonden - - Het kan zijn dat je momenteel geen internet hebt. Of er is momenteel niemand aan het spelen. + + Het kan zijn dat je momenteel geen internetverbinding hebt of er is momenteel niemand aan het spelen. - + Geschatte resterende tijd: - - downloading {$1} MB + + Downloaden van {$1} MB. - + Mods gevonden - - Je staat op het punt om het spel te starten zonder mods. Wil je de my-stuff folder leegmaken? + + Je staat op het punt om het spel te starten zonder mods. Wil je de My Stuff-map leegmaken? - + Mods installeren - + {$1} mods installeren - - Kan Dolphin Emulator niet vinden, vul eerst de corectly bestand locatie in in de instellingen - - - Kan hetspel bestand niet vinden, vul eerst de corectly bestand locatie in in de instellingen - - - Wil je alle bestanden combineren in 1 mod? - - - Meerdere bestanden geselecteerd - - - Er bestaat al een mod met de naam '{$1}' . + + Kan Dolphin-emulator niet vinden. Geef eerst de locatie op bij de instellingen. - - Voer modnaam in: + + Kan het spelbestand niet vinden. Geef eerst de locatie op bij de instellingen. - - De naam van de mod mag niet leeg zijn. + + Meerdere bestanden geselecteerd. - - Weet je zeker dat je {$1} wilt verwijderen? + + Er bestaat al een mod met de naam "{$1}". - - Mods folder bestaat niet + + Weet je zeker dat je {$1} wil verwijderen? - - Voer titel in: + + Mod-map bestaat niet. - - Verwerken van {$1} van {$2} bestanden... + + Verwerken van {$1} uit {$2} bestanden... - - Controleer of alle paden correct zijn en probeer het opnieuw. + + Controleer of alle locaties correct zijn en probeer het opnieuw. - + Instellingen succesvol opgeslagen! - - Will je de nieuwe schaal toepassen? + + Wil je de nieuwe schaal toepassen? - - Dolphin Emulator folder niet gevonden + + Dolphin-emulator-map niet gevonden. - - Dolphin Emulator folder gevonden + + Dolphin-emulator-map gevonden - - Dolphin Emulator kan niet automatish gevonden worden. Probeer het handmatig. + + De Dolphin-emulator-map is niet automatisch gevonden. Probeer de map handmatig te vinden. Klik op "hulp" voor meer informatie. - - Als je niet weet wat dit allemaal betekend, druk op ja :) \nEr is een Dolphin Emulator folder gevonden, wil je die gebruiken? + + Als je niet weet wat dit allemaal betekent, druk op ja :) \nDolphin-emulator-map gevonden. Wil je deze gebruiken? - - Het is niet gelukt om bestanden te verwijderen voor de update. Process word afgebroken. + + Het is niet gelukt om bestanden te verwijderen voor de update. Proces word afgebroken. - + Retro Rewind updaten - - Het is niet gelukt om te updaten, Process word afgebroken. + + Het is niet gelukt om te updaten. Proces word afgebroken. - + Retro Rewind is up-to-date. - - Zoeken naar updates mislukt + + Zoeken naar updates mislukt. - - Wheel Wizard Update Beschikbaar + + Wheel Wizard-update beschikbaar - - Update met behulp van admin + + Update met administrator - - Soms is er voor een update admin rechten nodig, wilt je deze voor deze update activeren? + + Soms zijn er voor een update administratorrechten nodig. Wil je deze activeren voor deze update? - + Kan niet controleren of Wheel Wizard up-to-date is. \nEr zijn mogelijk netwerkproblemen. - - Het is niet gelukt om opnieuw op te starten met admin rechten. + + Het is niet gelukt om opnieuw op te starten met administratorrechten. - - Versie {$1} van Wheel Wizard is beschikbaar (momenteel op {$2}). Wilt je nu updaten? + + Versie {$1} van Wheel Wizard is beschikbaar (momenteel op {$2}). Wil je nu updaten? - - De nieuwste Wheel Wizard ophalen van github-releases + + Ophalen van de nieuwste Wheel Wizard van GitHub-releases. - - Updating Wheel Wizard + + Wheel Wizard updaten - - Wheel Wizard kan niet worden ge-update. Zorg ervoor dat de applicatie zich in een folder bevindt waarnaar kan worden geschreven.\n Kan de huidige folder niet vinden. + + Wheel Wizard kan niet worden geüpdatet. Zorg ervoor dat de applicatie zich in een map bevindt waarnaar kan worden geschreven.\n Kan de huidige map niet vinden. - - Retro Rewind Her-installeren + + Retro Rewind herinstalleren - - Retro Rewind Installeren + + Retro Rewind installeren - - Retro Rewind voor de eerste keer aan het installeren + + Retro Rewind voor de eerste keer installeren. - - Er staan ​​al bestanden in je RetroRewind folder. Wilt u deze her-installeren? + + Kan geen verbinding maken met de server. Probeer het later opnieuw. - - Kon geen verbinding maken met de server. Probeer het later opnieuw. + + Oude spelgegevens zijn gevonden. Wil je deze gebruiken? (aanbevolen) - - Oude Retro Rewind gevonden - - - Er zijn oude Retro Rewind-bestanden gevonden. Wilt u deze verplaatsen naar de nieuwe locatie? - - - Oude 'save data' zijn gevonden. Wilt je deze gebruiken? (aanbevolen) - - + Oude rksys.dat gevonden - + Je versie van Retro Rewind is te oud om te updaten. Wil je Retro Rewind opnieuw installeren? - + Retro Rewind downloaden - + Je versie van Retro Rewind kon niet worden bepaald. Wil je Retro Rewind downloaden? - - Je hebt nog geen Mii's + + Je hebt nog geen Mii's! + + + We kunnen de Mii-data van jouw systeem niet aflezen. Zorg ervoor dat je het spel minimaal één keer hebt gespeeld. + + + Om vrienden toe te voegen moet je ze in het spel toevoegen. + + + Retro Rewind versie is te oud. + + + Ongeldige locaties. + + + Naam niet toegestaan. + + + Wheel Wizard kan niet worden geüpdatet. + + + Ongeldige bestandslocatie gedetecteerd. Neem contact op met de ontwikkelaars.\n Serverfout: {$1} + + + RR-update afgebroken. + + + Creëren van Mii-database mislukt. + + + Er is een fout opgetreden. + + + Kan momenteel niet op updates controleren. Mogelijk heb je geen internetverbinding of de server is offline. + + + Kan geen verbinding maken met de server. + + + Deze wijziging wordt over {$1} teruggedraaid, tenzij je ervoor kiest om de wijziging te behouden. + + + Weet je zeker dat je Retro Rewind opnieuw wilt installeren? + + + Verwijderen is permanent en kan niet ongedaan worden gemaakt. + + + Weet je zeker dat je {$1} Mii's wil verwijderen? + + + Creëren van Mii "{$1}" mislukt. + + + Verwerken van Mii "{$1}" mislukt. + + + Kopiëren van Mii "{$1}" mislukt. + + + Verkrijgen van Mii "{$1}" mislukt. + + + Opslaan van Mii "{$1}" mislukt. + + + Omzetten van Mii "{$1}" mislukt. + + + Updaten van Mii "{$1}" mislukt. + + + Vriendcode gekopieerd naar klembord. + + + {$1} kopieën gemaakt van Mii. + + + "{$1}" verwijderd. + + + Mii is toegevoegd aan "Mijn Mii's". + + + {$1} Mii's verwijderd. + + + Kopie gemaakt van "{$1}". + + + Er zijn geen Mii's om te exporteren. + + + Er zijn geen Mii's om te verwijderen. + + + Kan Mii niet opslaan. + + + Mii "{$1}" is opgeslagen in het bestand "{$2}". + + + Naam succesvol gewijzigd naar "{$1}". + + + Kan mod, die niet is geïnstalleerd via de mod-browser, niet weergeven. + + + Er is iets misgegaan bij het openen van de geselecteerde mod. + + + Kan deze mod niet weergegeven. + + + Kan favoriete Mii('s) niet verwijderen. + + + Eén of meer van de geselecteerde Mii's is een favoriet. Mii's kunnen alleen worden verwijderd als ze geen favoriet zijn, om onbedoelde verwijderingen te voorkomen. + + + Het hoofdprofiel wordt gebruikt als referentie in Wheel Wizard. + + + Dit toont alleen regio's waarop JIJ hebt gespeeld. + + + Profiel als hoofdprofiel geselecteerd. + + + Naam moet korter dan 11 tekens zijn. + + + Naam moet tussen de 3 en 10 tekens lang zijn. + + + Veranderen van Mii mislukt. + + + Veranderen van naam mislukt. + + + Mii succesvol veranderd. + + + Mod-naam + + + Geen mod geselecteerd + + + Mogelijk gemaakt door GameBanana. + + + Zoek voor mods... + + + Wil je de mod "{$1}" downloaden en installeren? + + + Vul mod-naam in... + + + Dolphin-emulator installeren + + + Taal vertaald door: {$1} + + + Vul Mii-naam in... + + + De installatie van de Dolphin-emulator is mislukt. Probeer handmatig Flatpak Dolphin te installeren. + + + Installeren van Dolphin mislukt. + + + Ophalen van mod-informatie mislukt. + + + Er is een fout opgetreden tijdens het downloaden van {$1}. + + + Downloaded van mod mislukt. + + + Geef een mod-naam op. + + + Mod-naam mag niet leeg zijn. + + + Mod-naam bevat ongeldige tekens. + + + Mod-naam ongeldig. + + + Bestanden konden niet worden gevonden. + + + Downloaden van mod mislukt. + + + Dit kan mogelijk lang duren afhankelijk van jouw internetverbinding. + + + De Flatpak-versie van de Dolphin-emulator is niet geïnstalleerd. Installeer dit voor het hele systeem? + + + Dolphin-Flatpak installeren + + + Zoeken naar mods... + + + Zoek voor spelers... + + + Vul bestandslocatie in... + + + Vul tekst in... + + + Naam aan het veranderen van "{$1}". - - We kunnen niet de Mii data van je systeem aflezen. Zorg dat je het spel minimaal 1 keer hebt opgestart + + Vul een nieuwe naam in \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Phrases.pt.resx b/WheelWizard/Resources/Languages/Phrases.pt.resx index a4c5284f..bafedf29 100644 --- a/WheelWizard/Resources/Languages/Phrases.pt.resx +++ b/WheelWizard/Resources/Languages/Phrases.pt.resx @@ -18,4 +18,193 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Podes adicionar amigos dentro do jogo + + + Ainda não adicionaste amigos + + + Mods podem alterar o funcionamento do jogo.Começa a importar o teu primeiro mod clicando no botão abaixo + + + Mods não encontrados + + + Tens de jogar o jogo pelo menos uma vez para poderes ver o teu perfil aqui escrito + + + Poderás estar sem conexão à internet, ou talvez ninguém esteja a jogar + + + Não se econtrarem salas + + + Não tens amigos online + + + Tens 1 amigo online + + + Tens {$1} amigos online + + + Não há jogadores online + + + Há 1 jogador online + + + Há {$1} jogadores online + + + Não há salas ativas + + + Há 1 sala ativa + + + Há {$1} salas ativas + + + Tem em conta que esta página não te oferece a funcionalidade de entrares diretamente numa sala. Para entrares numa sala em específico, terás de entrar através de um amigo, ou então esperas que entres na sala ao entrares nos servers online + + + Gostarias de aplicar a nova escala? + + + Falha a conectar ao servidor. Tente de novo mais tarde + + + Pasta Do Dolphin Emulator Encontrada + + + Se não sabes o que isto significa, clica só que sim :) \nPasta do Dolphin Emulator encontrada. Gostarias de usar esta pasta? + + + Pasta Do Emulador Dolphin Não Encontrada + + + A pasta Do Emulador Dolphin não foi automáticamente encontrada. Por favor tente encontrar a pasta manualmente, clica em 'ajuda' para obter mais informação- + + + Descarrega o Retro Rewind + + + Por favor verifica que todos os percursos estão corretos e tenta outra vez- + + + Tempo restante estimado: + + + Falha ao procurar atualizações + + + Falha a aplicar a atualização. Abortando. + + + Falha a apagar ficheiros para a atualização. Abortando. + + + Instalando mods + + + Instalando {$1} mods + + + Instalando Retro Rewind + + + Instalando Retro Rewind pela primeira vez + + + Obtendo a ultima versão do Wheel Wizard pelo github + + + Um mod com o nome '{$1}' já existe + + + Mods não encontrados + + + Tu estás prestes a abrir o jogo sem nenhum mod. Queres limpar a pasta my-stuff? + + + Multiplos Ficheiros selecionados + + + Versão {$1} do Wheel Wizard está disponível (de momento encontra-se na versão {$2}). Queres atualizar agora? + + + A pasta de mods não existe + + + O Dolphin Emulator não foi encontrado, por favor defina o caminho nas definições + + + O jogo não foi encontrado, por favor defina o caminho nas definições + + + O antifo rksys.dat foi encontrado + + + Foi encontrada uma gravação antiga. Queres usá-lá? (recomendado) + + + A processar {$1} de {$2} ficheiros... + + + Reinstalar Retro Rewind + + + Falha ao reiniciar com direitos de administrador + + + A tua versão do Retro Rewind não pode ser determinada. Gostarias de instalar o Retro Rewind + + + A tua versão do Retro Rewind é demasiado antiga par atualizar. Gostarias de reinstalar o Retro Rewind + + + A tua versão do Retro Rewind é a mais recente + + + Definições guardadas com sucesso + + + Tens a certeza que desejas apagar ($1)? + + + Incapaz de atualizar o Wheel Wizard. Garante que a aplicação se encontra numa pasta que pode ser localizada.\n Falha a encontar a pasta atual + + + Impossível verificar se o Wheel Wizard está atualizado. \nTalvez estejas a experienciar problemas de net + + + Atualiza usando administrados + + + Às vezes uma atualização requere direitos de administrador. Desejas usar direitos de administrador para esteja atualização? + + + Atualizar Retro Rewind + + + Atualizar Wheel Wizard + + + Atualização do Wheel Wizard Disponível + + + A transferir {$1} MB + + + Nome do mod não pode estar em branco + + + Procura Por Jogadores... + + + Introduz aqui o caminho desejado... + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Phrases.resx b/WheelWizard/Resources/Languages/Phrases.resx index eb79a57b..a0652883 100644 --- a/WheelWizard/Resources/Languages/Phrases.resx +++ b/WheelWizard/Resources/Languages/Phrases.resx @@ -1,9 +1,10 @@ - + - + @@ -13,20 +14,15 @@ 1.3 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + - - Talk to us! - - - Source code - - - Support us! - Made by: {$1} \n And {$2} @@ -60,22 +56,22 @@ There are currently {$1} friends online - + You have to play the game at least once in order to see your profiles listed here - + No friends yet! - + You can add friends in-game Translations for this language are {$1}% complete - + Mods can alter how the game works. Start importing your first mod by clicking the button below. - + No mods found @@ -102,179 +98,389 @@ {$1} seconds - - Search For Players... - - - Enter desired path here... - Please note that this page does not offer any functionality to join a room directly. To join a specific room, you'll need to either join through a friend, or hope to join it by joining the online servers. - + You might not have internet connection, or no-one might be playing. - + No rooms found - + Estimated time remaining: - + downloading {$1} MB - + Mods found - + You are about to launch the game without any mods. Do you want to clear your my-stuff folder? - + Installing mods - + Installing {$1} mods - + Could not find Dolphin Emulator, please set the path in settings - + Could not find the game, please set the path in settings - - Do you want to combine all files into 1 mod? - - + Multiple Files Selected - + A mod with the name '{$1}' already exists. - - Enter Mod Name: - - - Mod name can't be empty + + Enter mod name... - + Are you sure you want to delete {$1}? - + Mod folder does not exist - - Enter Title: - - + Processing {$1} of {$2} files... - + Please ensure all paths are correct and try again. - + Settings saved successfully! - + + Data folder updated + + + Wheel Wizard data is now stored in:\n{$1} + + Do you want to apply the new scale? - + + Move Wheel Wizard data? + + + Wheel Wizard will move its files to:\n{$1}\nThis may take a while depending on the amount of data. + + Dolphin Emulator Folder Found - + Dolphin Emulator Folder Not Found - - Dolphin Emulator folder found. Would you like to use this folder? If you dont know what all of this means, just click yes :) + + If you dont know what all of this means, just click yes :) \nDolphin Emulator folder found. Would you like to use this folder? - + Dolphin Emulator folder not automatically found. Please try and find the folder manually, click 'help' for more information. - + + Failed to move data + + Failed to delete files for the update. Aborting. - - Update Retro Rewind + + Updating Retro Rewind - + Failed to apply an update. Aborting. - + Retro Rewind is up to date. - + Failed to check for updates - + Wheel Wizard Update Available - + Update using admin - + Sometimes an update requires admin rights, do you want to active them for this update? - + Unable to check if Wheel Wizard is up to date. \nYou might be experiencing network issues. - + Failed to restart with administrator rights. - + Version {$1} of Wheel Wizard is available (currently on {$2}). Do you want to update now? - + Getting the latest Wheel Wizard from github releases - + Updating Wheel Wizard - + Unable to update Wheel Wizard. Please ensure the application is located in a folder that can be written to.\n Could not find current folder. - + Reinstall Retro Rewind - + Installing Retro Rewind - + Installing Retro Rewind for the first time - - There are already files in your RetroRewind Folder. Would you like to install? - - + Could not connect to the server. Please try again later. - - Old Retro Rewind found - - - Old Retro Rewind files were found. Would you like to move them to the new location? - - + Old rksys.dat found - + Old save data was found. Would you like to use it? (recommended) - + Your version of Retro Rewind is too old to update. Would you like to reinstall Retro Rewind? - + Download Retro Rewind - + Your version of Retro Rewind could not be determined. Would you like to download Retro Rewind? - + Are you sure you want to reinstall Retro Rewind? - + No Miis yet! - - We cant read the Mii data from your system. Make sure you started the game at least once + + We can't read the Mii data from your system. Make sure you started the game at least once + + + To add friends you need to add them in-game. + + + This change will revert in {$1} unless you decide to keep the change. + + + The Retro Rewind version is too old. + + + Could not connect to the server + + + Invalid Paths. + + + Can't check for updates right now. You might not be connected to the internet or the server might be down. + + + Aborting RR Update + + + Invalid file path detected. Please contact the developers.\n Server error: {$1} + + + Name not possible. + + + Unable to update Wheel Wizard. + + + An error occurred. + + + Can't Save Mii + + + Mii has been added to your Miis + + + Copied friend code to clipboard + + + Successfully changed name to '{$1}' + + + Failed to create Mii database + + + Failed to deserialize Mii '{$1}' + + + Failed to update Mii '{$1}' + + + Failed to save Mii '{$1}' + + + It seems there where no Miis to export + + + Failed to get Mii '{$1}' + + + Saved Mii '{$1}' to file '{$2}' + + + Failed to serialize Mii '{$1}' + + + It seems there where no Miis to delete + + + Deleting is permanent and cannot be undone. + + + Are you sure you want to delete {$1} Miis? + + + Deleted '{$1}' + + + Deleted {$1} Miis + + + Failed to create Mii '{$1}' + + + Failed to duplicate Mii(s) '{$1}' + + + Created duplicate of '{$1}' + + + Created {$1} duplicate Miis + + + Cannot view this mod. + + + Something went wrong when trying to open the selected mod. + + + Cannot view mod that was not installed through the mod browser. + + + Cannot delete favorite Mii(s) + + + One or more of the selected Mii(s) is a favorite. Miis can only be deleted if they are not favorites to prevent accidental deletions. + + + This only shows regions YOU have played on + + + The primary profile is used as reference in the wheel wizard client + + + Mii changed successfully + + + Set profile as primary + + + Names must be between 3 and 10 characters long. + + + Creator name must be less than 11 characters long. + + + Failed to change name + + + Failed to Change Mii + + + Installing Dolphin Emulator + + + This may take a while depending on your internet connection. + + + Dolphin Flatpak Installation + + + The Flatpak version of Dolphin Emulator does not appear to be installed. Would you like us to install it (system-wide)? + + + Failed to install Dolphin + + + The installation of Dolphin Emulator failed. Please try manually installing Flatpak Dolphin. + + + Powered By GameBanana + + + No mod selected + + + Select a mod in the list to view its details + + + Search for mods... + + + Do you want to donwload and install the mod: {$1}? + + + Failed to retrieve mod info + + + Mod download failed. + + + An error occurred during download: {$1} + + + Mod name Invalid. + + + Mod name contains invalid characters. + + + Mod name can't be empty. + + + Please provide a mod name. + + + Unable to download the mod + + + Files could not be found. + + + This language is translated by: {$1} + + + Enter Mii name... + + + Search for mods... + + + Enter text here... + + + Search For Players... + + + Enter desired path here... + + + Enter new name + + + Changing name from: {$1} - \ No newline at end of file + diff --git a/WheelWizard/Resources/Languages/Phrases.ru.resx b/WheelWizard/Resources/Languages/Phrases.ru.resx index 14934fa3..ff53c86f 100644 --- a/WheelWizard/Resources/Languages/Phrases.ru.resx +++ b/WheelWizard/Resources/Languages/Phrases.ru.resx @@ -18,224 +18,185 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Вы можете добавить друзей в игре + + Друзей можно добавить через игру - - Ещё нет друзей! + + У вас еще нет друзей! - - Моды могут изменять поведение игры. Чтобы импортировать свой мод, нажмите кнопку ниже. + + С помощью модов можно изменять поведение игры. Чтобы импортировать свой мод, нажмите кнопку ниже. - + Моды не найдены - - Вам нужно сыграть в игру как минимум один раз, чтобы видеть свои профиля здесь. + + Ваши профили появятся здесь, как только вы сыграете хотя бы раз. - - Возможно, у вас нет подключения к интернету, либо никто сейчас не играет. + + Возможно, у вас нет подключения к Интернету, либо никто сейчас не играет. - + Комнаты не найдены - Никто из ваших друзей в сети. + Друзей в сети: 0 - Один ваш друг сейчас в сети. + Друзей в сети: 1 - Сейчас, {$1} ваших друзей в сети. + Друзей в сети: {$1} - Сейчас в сети нет игроков. + Игроков в сети: 0 - 1 игрок сейчас в сети. + Игроков в сети: 1 - {$1} игроков сейчас в сети. + Игроков в сети: {$1} - Сейчас нет активных комнат. + Активных групп: 0 - Всего одна комната сейчас активна. + Активных групп: 1 - Сейчас активно {$1} комнат. + Активных групп: {$1} Вы не можете заходить в комнаты прямо через это меню. Чтобы зайти в определённую комнату, вам нужно либо зайти через друга, либо надеяться попасть в неё через онлайн сервера. - - Вставьте сюда путь к файлу... - - - Искать игроков... - - - В папке RetroRewind уже есть файлы. Желаете продолжить? - - + Хотите применить новый масштаб? - + Не удалось подключиться к серверу. Попробуйте чуть позже. - + Папка эмулятора Dolphin найдена - + Если ты понятия не имеешь что это значит, просто нажми да :D \nПапка эмулятора Dolphin найдена. Хотите её использовать? - + Папка эмулятора Dolphin не найдена - + Папка эмулятора Dolphin не была найдена автоматически. Пожалуйста, попробуйте найти её самостоятельно. Для подробностей, нажмите на "Помощь" - + Скачать Retro Rewind - + Убедитесь, что все пути правильны и попробуйте ещё раз. - - Укажите название мода: - - - Укажите заголовок: - - + Осталось примерно: - + Не удалось проверить обновления - + Не удалось применить обновление. Отменяем. - + Не удалось удалить файлы для обновления. Отменяем - + Устанавливаем моды - + Устанавливаем {$!} модов - + Устанавливаем Retro Rewind - + Устанавливаем Retro Rewind в первый раз - + Получаем последнюю версию Wheel Wizard из GitHub - - Хотите соеденить все файлы в один мод? - - - Название мода не может быть пустым. - - + Мод с названием {$1} уже существует. - + Моды найдены - + Прямо сейчас вы запускаете игру без модов. Желаете очистить папку my-stuff? - + Выбрано несколько файлов - + Версия {$1} Wheel Wizard'а доступна (прямо сейчас. вы на {$2}). Хотите произвести обновление? - + Папка с модами не существует - + Не удалось найти эмулятор Dolphin, пожалуйста, установите путь в настройках. - + Не удалось найти игру, пожалуйста, установить путь в настройках. - - Старый rksys.dat был найден + + Обнаружен старый rksys.dat - + Старые данные сохранения были найдена. Желаете их исполльзовать? (рекоммендовано) - - Старый Retro Rewind был найден. - - - Старые файлы Retro Rewind были найдены. Хотите переместить их в новую директорию? - - + Обработка {$1} из {$2} файлов... - + Переустановить Retro Rewind - + Не удалось перезапуститься с правами администратора. - - Не удалос определить вашу версию Retro Rewind. Хотите установить Retro Rewind? + + Не удалось определить вашу версию Retro Rewind. Хотите установить Retro Rewind? - - Ваша версия Retro Rewind слишком доисторическая. чтобы обновиться. Хотите переустановить Retro Rewind? + + Ваша версия Retro Rewind слишком устарела для обновлений. Хотите переустановить Retro Rewind? - + Retro Rewind на последней версии. - + Настройки успешно сохранены! - + Вы уверены что хотите удалить {$1}? - + Не удалось обновить Wheel Wizard. Пожалуйста убедитесь, что исполняемый файл находится в папке куда можно добавлять файлы.\n Не удалось найти текущую папку. - + Не удалось проверить, обновлён ли Wheel Wizard до последней версии? \nВы возможно испытываете проблемы с подключеним. - + Обновитесь через права администратора. - + Иногда, для обновления нужны права администратора. Хотите ли вы их активировать ради этого обновления? - - Обновить Retro Rewind - - + Обновляем Wheel Wizard - + Доступно обновление Wheel Wizard - - скачиваем {$1} МБ - - - Наш Discord сервер - - - Исходный код - - - Поддержать нас + + Загружено {$1} МБ - Сделано: {$1} \n и {$2} + Создатели: {$1}\nи {$2} Низкий поклон и спасибо переводчикам: @@ -247,24 +208,144 @@ 1 день - {$1} дней + {$1} дн. - 1 час + 1 ч - {$1} часов + {$1} ч - 1 минута + 1 мин - {$1} минут + {$1} мин - 1 секунда + 1 сек - {$1} секунд + {$1} сек + + + Название мода не может быть пустым. + + + Поиск игроков... + + + Вставьте сюда путь к файлу... + + + Не удалось загрузить мод + + + Не удалось обновить Wheel Wizard. + + + Введите новое название + + + Хотите установить этот мод: {$1}? + + + Вы уверены, что хотите переустановить Retro Rewind? + + + Вы уверены, что хотите удалить {$1} Mii? + + + Авторы перевода: {$1} + + + У вас еще нет Mii! + + + Моды не выбраны + + + Не удалось изменить Mii. + + + Не удалось изменить имя. + + + Не удалось создать базу Mii. + + + Не удалось удалить избранного(-ых) Mii + + + Не удалось найти файлы. + + + Не удалось создать Mii '{$1}' + + + Моды из GameBanana + + + Выберите мод из списка, чтобы узнать подробности. + + + Имя создателя должно быть длиной в меньше 11 символов. + + + Имена должны быть длиной от 3 до 10 символов. + + + Чтобы добавить друга нужно сначала подружить + + + Введите имя Mii... + + + Введите название мода... + + + Введите текст... + + + Поиск модов... + + + Установка эмулятора Dolphin + + + Обновляем Retro Rewind + + + Версия Retro Rewind слишком стара + + + Поиск модов... + + + Удаление является необратимым и не может быть отменено. + + + Не удалось импортировать Mii '{$1}' + + + Не удалось дублировать Mii '{$1}' + + + Не удалось получить Mii '{$1}' + + + Не удалось сохранить Mii '{$1}' + + + Не удалось экспортировать Mii '{$1}' + + + Не удалось обновить Mii '{$1}' + + + Код друга сохранён в буфер обмена + + + Создан дубликат '{$1}' \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Phrases.tr.resx b/WheelWizard/Resources/Languages/Phrases.tr.resx index 4077354e..5e5eff8a 100644 --- a/WheelWizard/Resources/Languages/Phrases.tr.resx +++ b/WheelWizard/Resources/Languages/Phrases.tr.resx @@ -18,25 +18,25 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + Oyunda arkadaş ekleyebilirsiniz - + Henüz arkadaş yok! - + Modlar oyunun işleyişini değiştirebilir. İlk modunuzu içe aktarmaya başlamak için aşağıdaki butona tıklayın. - + Mod bulunamadı - + Profillerinizi burada görmek için oyunu en az bir kez oynamanız gerekiyor. - + İnternet bağlantınız olmayabilir veya kimse oyun oynamıyor olabilir. - + Oda bulunamadı @@ -70,171 +70,132 @@ Lütfen bu sayfanın doğrudan bir odaya katılma işlevi sunmadığını unutmayın. Belirli bir odaya katılmak için bir arkadaşınız aracılığıyla katılmanız veya çevrimiçi sunuculara katılarak bu odaya girmeyi ummanız gerekecek. - - İstenen yolu buraya girin... - - - Oyuncuları Ara... - - - RetroRewind Klasörünüzde zaten dosyalar var. Yüklemek ister misiniz? - - + Yeni ölçeği uygulamak istiyor musunuz? - + Sunucuya bağlanılamadı. Lütfen daha sonra tekrar deneyin. - + Dolphin Emulator Klasörü Bulundu - + Eğer bunların ne anlama geldiğini bilmiyorsanız, evet diyebilirsiniz :) \nDolphin Emulator klasörü bulundu. Bu klasörü kullanmak ister misiniz? - + Dolphin Emulator Klasörü Bulunamadı - + Dolphin Emulator klasörü otomatik olarak bulunamadı. Lütfen klasörü manuel olarak bulmayı deneyin, daha fazla bilgi için 'yardım'ı tıklayın. - + Retro Rewind İndir - + Lütfen tüm yolların doğru olduğundan emin olun ve tekrar deneyin. - - Mod Adını Girin: - - - Başlık Girin: - - + Kalan Tahmini Süre: - + Güncellemeleri kontrol etme başarısız oldu - + Güncellemeyi uygulamak başarısız oldu. İşlem iptal ediliyor. - + Güncelleme dosyalarını silmek başarısız oldu. İşlem iptal ediliyor. - + Modlar Yükleniyor - + {$1} modları yükleniyor - + Retro Rewind Yükleniyor - + Retro Rewind ilk kez yükleniyor - + Github sürümlerinden en son Wheel Wizard alınıyor - - Tüm dosyaları 1 modda birleştirmek ister misiniz? - - - Mod adı boş olamaz - - + {$1}' adıyla zaten bir mod mevcut. - + Modlar bulundu - + Mod olmadan oyunu başlatmak üzeresiniz. My-stuff klasörünüzü temizlemek ister misiniz? - + Birden Fazla Dosya Seçildi - + Wheel Wizard'ın {$1} sürümü mevcut (şu anda {$2}'de). Şimdi güncellemek ister misiniz? - + Mod klasörü mevcut değil - + Dolphin Emulator bulunamadı, lütfen yolu ayarlarda belirleyin - + Oyun bulunamadı, lütfen yolu ayarlarda belirleyin - + Eski rksys.dat bulundu - + Eski kayıt verisi bulundu. Kullanmak ister misiniz? (önerilir) - - Eski Retro Rewind bulundu - - - Eski Retro Rewind dosyaları bulundu. Bunları yeni konuma taşımak ister misiniz? - - + {$1} dosyasından {$2} işleniyor... - + Retro Rewind'ı yeniden yükle - + Yöneticilik haklarıyla yeniden başlatılamadı. - + Retro Rewind sürümünüz belirlenemedi. Retro Rewind'ı indirmek ister misiniz? - + Retro Rewind sürümünüz güncellemeye çok eski. Retro Rewind'ı yeniden yüklemek ister misiniz? - + Retro Rewind güncel. - + Ayarlar başarıyla kaydedildi! - + {$1}'i silmek istediğinizden emin misiniz? - - Wheel Wizard güncellenemedi. Uygulamanın yazılabilir bir klasörde bulunduğundan emin olun. \nGeçerli klasör bulunamadı. + + Wheel Wizard güncellenemedi. Uygulamanın erişilebilir bir klasörde bulunduğundan emin olun. \n İstenilen klasör bulunamadı. - + Wheel Wizard'ın güncel olup olmadığını kontrol edilemedi. \nAğ sorunları yaşıyor olabilirsiniz. - + Yönetici kullanarak güncelle - + Bazen bir güncelleme yönetici hakları gerektirir, bu güncelleme için onları aktif etmek ister misiniz? - - Retro Rewind'ı güncelle - - + Wheel Wizard güncelleniyor - + Wheel Wizard Güncellemesi Mevcut - + {$1} MB indiriliyor - - Bizimle İletişime Geçin! - - - Kaynak Kodu - - - Bize Destek Olun! - Yapımcı: {$1} \n Ve {$2} @@ -268,4 +229,238 @@ Belirli bir odaya katılmak için bir arkadaşınız aracılığıyla katılman {$1} saniye + + Sisteminizden Mii bilgisini okuyamıyoruz. Oyunu en az bir kez başlattığınıza emin olun. + + + Şimdilik Mii yok! + + + Yapımcı adı 11 karakterden kısa olması gerekiyor. + + + Adın 3 ile 10 karakter arasında olması gerekiyor. + + + Arkadaş eklemek için onları oyunun içinde eklemeniz gerekiyor. + + + Ana profil, Wheel Wizard için referans olarak kullanılır. + + + Bu sadece SİZİN oynadığıınız bölgeleri gösterir. + + + Yanlış dosya yolu tespit edildi. Lütfen geliştiricilere bildirin.\n Sunucu hatası: {$1} + + + Retro Rewind güncellemesi iptal ediliyor + + + Mii değiştirme başarısız + + + Ad değiştirme başarısız + + + Mii Veritabanı oluşturulamadı + + + Bir hata oluştu + + + Mii başarıyla değiştirildi + + + Seçilen bir ya da daha fazla Mii favori olarak ayarlanmış. Mii'ler favori iken yanlışlıkla silmeyi engellemek için silinemezler + + + Favori Mii silinemiyor + + + Mod tarayıcısından indirilmemiş modlar görüntülenemez. + + + Seçilen modu açmaya çalışırken bir hata oluştu + + + Bu mod görüntülenemiyor + + + Şu an güncellemeler kontrol edilemiyor. İnternete bağlı olmayabilirsiniz veya sunucular çalışmıyor olabilir. + + + Ada izin verilmiyor. + + + Geçersiz yol. + + + Sunucuya bağlanılamıyor. + + + Wheel Wizard güncellenemedi. + + + Retro Rewind Güncelleniyor + + + Bu değişiklik onaylamadığınız takdirde {$1} içinde eski haline dönecek. + + + Retro Rewind'ı tekrar yüklemek istediğinize emin misiniz? + + + Retro Rewind sürümü çok eski. + + + Silmek kalıcıdır ve geri alınamaz. + + + {$1} tane Mii'yi silmek istediğinize emin misiniz? + + + Mii '{$1}' oluşturulamadı. + + + Mii '{$1}' işlenemedi. + + + Mii '{$1}' çoğaltılamadı. + + + Mii '{$1}' alınamadı. + + + Mii '{$1}' kaydedilemedi. + + + Mii '{$1}' işlenemedi. + + + Mii '{$1}' güncellenemedi. + + + Arkadaş kodu panoya kopyalandı. + + + {$1}' nin kopyası oluşturuldu. + + + {$1}' Mii kopyası oluşturuldu. + + + {$1}' silindi. + + + {$1}' Mii silindi + + + Mii, Mii'lerinize eklendi. + + + Ad, başarıyla yeni ada değiştirildi. '{$1}' + + + Profil birincil olarak ayarlandı. + + + Mii '{$1}' '{$2}' dosyasına kaydedildi. + + + Mii kaydedilemiyor. + + + Silinecek Mii bulunamadı. + + + Aktarılıcak Mii bulunamadı + + + Mod adı boş olamaz + + + Oyuncuları Ara... + + + İstenen yolu buraya girin... + + + Detaylarını görmek için listeden bir mod seçin + + + Hiçbir mod seçilmedi + + + Dolphin Emulator yüklemesi başarısız oldu. Lütfen manuel bir şekilde Flatpak Dolphin'i yüklemeyi deneyin. + + + Dolphin yüklemesi başarısız oldu. + + + Mod bilgisi alınamadı. + + + İndirme sırasında bir hata oluştu: {$1} + + + Mod indirmesi başarısız oldu. + + + Lütfen bir mod adı girin. + + + Mod adında geçersiz karakter bulunuyor. + + + Mod adı geçersiz. + + + Dosyalar bulunamadı. + + + Mod indirilemedi. + + + Mii adı girin... + + + Mod adı girin... + + + Metni buraya girin... + + + Mod arayın... + + + Dolphin Emulator yükleniyor + + + Bu, internetinize bağlı biraz uzun sürebilir. + + + Bilgisayarınızda Dolphin Emulator'ın Flatpak sürümü bulunamadı. Yüklememizi ister misiniz? (tüm kullanıcılar için) + + + Dolphin Flatpak Yüklemesi + + + Ad değiştiriliyor. (Eski ad: {$1}) + + + Yeni adı girin: + + + Modu indirip kurmak ister misiniz: {$1}? + + + Modları ara... + + + Bu dili çevirenler: {$1} + + + GameBanana tarafından + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Settings.Designer.cs b/WheelWizard/Resources/Languages/Settings.Designer.cs index 3ba0c9fc..4a02b922 100644 --- a/WheelWizard/Resources/Languages/Settings.Designer.cs +++ b/WheelWizard/Resources/Languages/Settings.Designer.cs @@ -69,92 +69,146 @@ public static string Category_About { } /// - /// Looks up a localized string similar to Video. + /// Looks up a localized string similar to General. /// - public static string Category_Video { + public static string Category_General { get { - return ResourceManager.GetString("Category_Video", resourceCulture); + return ResourceManager.GetString("Category_General", resourceCulture); } } /// - /// Looks up a localized string similar to 100. + /// Looks up a localized string similar to Other. + /// + public static string Category_Other { + get { + return ResourceManager.GetString("Category_Other", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Video. /// - public static string CompletePercentage { + public static string Category_Video { get { - return ResourceManager.GetString("CompletePercentage", resourceCulture); + return ResourceManager.GetString("Category_Video", resourceCulture); } } /// /// Looks up a localized string similar to This setting will force the game to run in 30 FPS (default is 60). /// - public static string InfoText_30FPS { + public static string HelperText_30FPS { get { - return ResourceManager.GetString("InfoText_30FPS", resourceCulture); + return ResourceManager.GetString("HelperText_30FPS", resourceCulture); } } /// - /// Looks up a localized string similar to This enables the animations in the the Wheel Wizard app. + /// Looks up a localized string similar to This enables the animations in the Wheel Wizard app. /// - public static string InfoText_EnableAnimations { + public static string HelperText_EnableAnimations { get { - return ResourceManager.GetString("InfoText_EnableAnimations", resourceCulture); + return ResourceManager.GetString("HelperText_EnableAnimations", resourceCulture); } } /// /// Looks up a localized string similar to Path must end with .exe. /// - public static string InfoText_EndWithExe { + public static string HelperText_EndWithExe { + get { + return ResourceManager.GetString("HelperText_EndWithExe", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Choose where Wheel Wizard stores its files. Moving may take a while.. + /// + public static string HelperText_WheelWizardDataFolder { + get { + return ResourceManager.GetString("HelperText_WheelWizardDataFolder", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Path can end with:. + /// + public static string HelperText_EndWithX { get { - return ResourceManager.GetString("InfoText_EndWithExe", resourceCulture); + return ResourceManager.GetString("HelperText_EndWithX", resourceCulture); } } /// /// Looks up a localized string similar to This setting disables Wiimote ingame, but enables it for the Wii channel. /// - public static string InfoText_ForceDisableWiimote { + public static string HelperText_ForceDisableWiimote { get { - return ResourceManager.GetString("InfoText_ForceDisableWiimote", resourceCulture); + return ResourceManager.GetString("HelperText_ForceDisableWiimote", resourceCulture); } } /// /// Looks up a localized string similar to Will launch dolphins main window along with the game. /// - public static string InfoText_LaunchWithDolphin { + public static string HelperText_LaunchWithDolphin { + get { + return ResourceManager.GetString("HelperText_LaunchWithDolphin", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You must set these 3 paths before you can start playing Retro Rewind. + /// + public static string HelperText_MustSetPaths { + get { + return ResourceManager.GetString("HelperText_MustSetPaths", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Optional. + /// + public static string HelperText_Optional { get { - return ResourceManager.GetString("InfoText_LaunchWithDolphin", resourceCulture); + return ResourceManager.GetString("HelperText_Optional", resourceCulture); } } /// /// Looks up a localized string similar to This setting will change certain dolphin settings to reduce stuttering and lag spikes. /// - public static string InfoText_Recommended { + public static string HelperText_Recommended { get { - return ResourceManager.GetString("InfoText_Recommended", resourceCulture); + return ResourceManager.GetString("HelperText_Recommended", resourceCulture); } } /// /// Looks up a localized string similar to 1x is the native resolution. /// - public static string InfoText_ResolutionScale { + public static string HelperText_ResolutionScale { get { - return ResourceManager.GetString("InfoText_ResolutionScale", resourceCulture); + return ResourceManager.GetString("HelperText_ResolutionScale", resourceCulture); } } /// /// Looks up a localized string similar to Settings are disabled, complete the location settings first. /// - public static string InfoText_Topbar_LocationWarning { + public static string HelperText_Topbar_LocationWarning { + get { + return ResourceManager.GetString("HelperText_Topbar_LocationWarning", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This only changes the language for Wheel Wizard. To change the in-game language, you’ll need to do that within the game settings.. + /// + public static string HelperText_WhWzLanguage { get { - return ResourceManager.GetString("InfoText_Topbar_LocationWarning", resourceCulture); + return ResourceManager.GetString("HelperText_WhWzLanguage", resourceCulture); } } @@ -229,7 +283,25 @@ public static string Option_OpenConfig { return ResourceManager.GetString("Option_OpenConfig", resourceCulture); } } - + + /// + /// Looks up a localized string similar to Wheel Wizard data folder. + /// + public static string Option_WheelWizardDataFolder { + get { + return ResourceManager.GetString("Option_WheelWizardDataFolder", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use default location. + /// + public static string Option_ResetDataFolder { + get { + return ResourceManager.GetString("Option_ResetDataFolder", resourceCulture); + } + } + /// /// Looks up a localized string similar to Open Game Folder. /// @@ -284,15 +356,6 @@ public static string Option_Renderer { } } - /// - /// Looks up a localized string similar to Retro Rewind Language. - /// - public static string Option_RRLanguage { - get { - return ResourceManager.GetString("Option_RRLanguage", resourceCulture); - } - } - /// /// Looks up a localized string similar to Show FPS. /// @@ -402,7 +465,7 @@ public static string Section_Wii { } /// - /// Looks up a localized string similar to . + /// Looks up a localized string similar to Arabic. /// public static string Value_Language_Arabic { get { @@ -420,7 +483,7 @@ public static string Value_Language_ArabicOg { } /// - /// Looks up a localized string similar to . + /// Looks up a localized string similar to Chinese. /// public static string Value_Language_Chinese { get { @@ -438,7 +501,16 @@ public static string Value_Language_ChineseOg { } /// - /// Looks up a localized string similar to . + /// Looks up a localized string similar to 100. + /// + public static string Value_Language_CompletePercentage { + get { + return ResourceManager.GetString("Value_Language_CompletePercentage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Czech. /// public static string Value_Language_Czech { get { @@ -600,7 +672,7 @@ public static string Value_Language_KoreanOg { } /// - /// Looks up a localized string similar to . + /// Looks up a localized string similar to Norwegian. /// public static string Value_Language_Norwegian { get { @@ -618,7 +690,7 @@ public static string Value_Language_NorwegianOg { } /// - /// Looks up a localized string similar to . + /// Looks up a localized string similar to Portuguese. /// public static string Value_Language_Portuguese { get { @@ -688,5 +760,41 @@ public static string Value_Language_TurkishOg { return ResourceManager.GetString("Value_Language_TurkishOg", resourceCulture); } } + + /// + /// Looks up a localized string similar to -. + /// + public static string Value_Language_zTranslators { + get { + return ResourceManager.GetString("Value_Language_zTranslators", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Using custom location.. + /// + public static string Status_DataFolder_Custom { + get { + return ResourceManager.GetString("Status_DataFolder_Custom", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Using default location.. + /// + public static string Status_DataFolder_Default { + get { + return ResourceManager.GetString("Status_DataFolder_Default", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Moving data, please wait.... + /// + public static string Status_DataFolder_Moving { + get { + return ResourceManager.GetString("Status_DataFolder_Moving", resourceCulture); + } + } } } diff --git a/WheelWizard/Resources/Languages/Settings.ar.resx b/WheelWizard/Resources/Languages/Settings.ar.resx index a4c5284f..da1a16cc 100644 --- a/WheelWizard/Resources/Languages/Settings.ar.resx +++ b/WheelWizard/Resources/Languages/Settings.ar.resx @@ -18,4 +18,10 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 0 + + + Yawshi, A̷bd̷αllαh̷ ⢸ ـبدَالله؏ + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Settings.cs.resx b/WheelWizard/Resources/Languages/Settings.cs.resx index a4c5284f..78f2aeb5 100644 --- a/WheelWizard/Resources/Languages/Settings.cs.resx +++ b/WheelWizard/Resources/Languages/Settings.cs.resx @@ -18,4 +18,103 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Video + + + Doporučené Nastavení + + + Odstranit Rozmazání + + + Renderování + + + Zobrazit FPS + + + VSync + + + Jazyk Wheel Wizard + + + Měřítko Okna + + + Jazyk + + + Nastavení Umístění + + + Rozlišení + + + Nastavení Wii + + + Arabština + + + Čínština + + + Čeština + + + Nizozemština + + + Angličtina + + + Finština + + + Francouzština + + + Němčina + + + Italština + + + Japonština + + + Korejština + + + Norština + + + Portugalština + + + Ruština + + + Španělština + + + Obecný + + + Pattyrix + + + Soubor Hry Mario Kart Wii + + + Instalace + + + Turečtina + + + 57 + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Settings.de.resx b/WheelWizard/Resources/Languages/Settings.de.resx index 911c129f..ae498992 100644 --- a/WheelWizard/Resources/Languages/Settings.de.resx +++ b/WheelWizard/Resources/Languages/Settings.de.resx @@ -24,32 +24,32 @@ Video - + Diese Einstellung erzwingt, dass das Spiel mit 30 FPS läuft (Standard ist 60). - - Dateipfad muss mit .exe ende + + Dateipfad muss mit .exe enden. - + Diese Einstellung deaktiviert die Wiimote im Spiel, aber nutzt sie für den Wii-Kanal - + Wird Dolphins Hauptfenster mit dem Spiel starten - + Diese Einstellung wird bestimmte Dolphin Einstellungen verändern, um Ruckeln und Lagspikes zu vermindern - + 1x ist die native Auflösung - + Einstellungen sind deaktiviert, komplettiere erst den Dateispeicherort - Dolphin Emulator executeable + Dolphin Emulator Programm-Datei - Dolphin Benutzer Ordner + Dolphin Benutzer-Ordner Wiimote ausschalten erzwingen @@ -58,7 +58,7 @@ Starte das Spiel mit dem Dolphin Fenster zusammen - Mario Kart Wii Spiel Datei + Mario Kart Wii Spiel-Datei Konfigurationsordner öffnen @@ -69,11 +69,8 @@ Renderer - - Retro Rewind Sprache - - Zeige FPS an + FPS anzeigen VSync @@ -103,7 +100,7 @@ Wheel Wizard Aussehen - Wii Einstellung + Wii Einstellungen Niederländisch @@ -126,14 +123,14 @@ Türkisch - + 100 Hauptprofil - - Dies aktiviert die Animationen in der Wheel Wizard App + + Dies aktiviert die Animationen in der Wheel Wizard App. Aktiviere Animationen @@ -150,4 +147,55 @@ Russisch + + Arabisch + + + Chinesisch + + + Tschechisch + + + Norwegisch + + + Portugisisch + + + Sonstiges + + + Allgemein + + + Über + + + Spielordner öffnen + + + Speicherdatenordner öffnen + + + RR neu installieren + + + Installation + + + Weebo, kytronix, Gab, ToadetteHackFan + + + Pfad kann enden mit: + + + Du musst erst diese 3 Dateipfade bestimmen um Retro Rewind spielen zu können. + + + Optional + + + Dies ändert nur die Sprache innerhalb von Wheel Wizard. Um die Sprache innerhalb des Spiels zu ändern musst du das in den Spieleinstellungen vornehmen + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Settings.es.resx b/WheelWizard/Resources/Languages/Settings.es.resx index 6960f664..83894c7a 100644 --- a/WheelWizard/Resources/Languages/Settings.es.resx +++ b/WheelWizard/Resources/Languages/Settings.es.resx @@ -21,31 +21,31 @@ Español - - 100 + + 75 Video - + Este ajuste forzará al juego a correr en 30 FPS (Lo normal es 60) - + La ruta debe terminar en un .exe - + Este ajuste desactiva el Wiimote en juego, pero lo activa para el Canal Wii - + Se lanzará la ventana principal de Dolphin junto con el juego - + Este ajuste cambiará ciertas configuraciones de Dolphin para reducir problemas de lag - + 1x es la resolución nativa - + Ajustes desactivados, complete la ubicación de los ajustes primero @@ -75,9 +75,6 @@ Renderizar - - Idioma de Retro Rewind - Mostrar FPS @@ -129,7 +126,7 @@ Japonés - + Esto activa las animaciones en la aplicación Wheel Wizard @@ -150,4 +147,43 @@ Turco + + Arabe + + + Chino + + + Checo + + + Noruego + + + Portugués + + + Otro + + + General + + + Acerca de + + + Abrir carpeta de juegos + + + Abrir carpeta de archivos de juego guardados + + + Reinstalar RR + + + Instalación + + + Frix, JGavidia + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Settings.fi.resx b/WheelWizard/Resources/Languages/Settings.fi.resx index 58653d72..e216e3e1 100644 --- a/WheelWizard/Resources/Languages/Settings.fi.resx +++ b/WheelWizard/Resources/Languages/Settings.fi.resx @@ -18,7 +18,10 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + 0 + + Clavey + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Settings.fr.resx b/WheelWizard/Resources/Languages/Settings.fr.resx index d21d8a9f..a86b3986 100644 --- a/WheelWizard/Resources/Languages/Settings.fr.resx +++ b/WheelWizard/Resources/Languages/Settings.fr.resx @@ -24,25 +24,25 @@ Vidéo - + Ce paramètre va forcer le jeu à tourner en 30 FPS (60 est par défaut) - + Le chemin doit se finir en .exe - - Ce paramètre ira désactiver les Wiimotes dans le jeu, mais les actives dans la chaîne Wii + + Ce paramètre ira désactiver les Wiimotes dans le jeu, mais les activés dans le menu Wii - - Va lancer la fennêtre d'acceuil de Dolphin en plus du jeu + + Ira lancer la fenêtre d'accueil de Dolphin en plus du jeu - + Ce paramètre va changer certain paramètre Dolphin pour réduire les saccades et lag spikes - + 1x est la résolution native - + Les paramètres sont désactivés, complétez d'abord les paramètres de localisation @@ -69,9 +69,6 @@ Rendu - - Langue de Retro Rewind - Montrer les FPS @@ -88,10 +85,10 @@ Langue - paramètres de localisation + Paramètres de localisation - Paramètres perfomance + Paramètres de perfomance Paramètres de rendu @@ -126,13 +123,13 @@ Turc - + 100 Défini par défaut - + Ceci active les animations dans l'application Wheel Wizard @@ -150,4 +147,55 @@ Russe + + Arabe + + + Chinois + + + Tchèque + + + Norvégien + + + Portugais + + + Autre + + + Général + + + À propos + + + Ouvrir le dossier du jeu + + + Ouvrir le dossier de sauvegarde + + + Réinstaller Retro Rewind + + + Installation + + + Eppe, Yawshi + + + Le chemin doit se finir avec: + + + Vous devez impérativement définir ces 3 chemins afin de jouer à Retro Rewind + + + Optionnel + + + Ceci change uniquement la langue dans Wheel Wizard. Pour changer la langue dans le jeu. Veuillez le faire depuis les paramètres de Retro Rewind. + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Settings.it.resx b/WheelWizard/Resources/Languages/Settings.it.resx index dd9d08e7..ec2b8a03 100644 --- a/WheelWizard/Resources/Languages/Settings.it.resx +++ b/WheelWizard/Resources/Languages/Settings.it.resx @@ -18,34 +18,34 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + 100 Video - + Questa impostazione forzerà il gioco a girare a 30FPS (di default è 60) - + Questa impostazione abiliterà le animazioni nell'applicazione Wheel Wizard - + L'indirizzo deve terminare con .exe - + Questa impostazione disattiverà i Wiimote durante il gioco, ma li attiverà nel canale Wii - + Aprirà la finestra principale di Dolphin assieme al gioco - + Questa impostazione cambierà alcune impostazioni di Dolphin per ridurre problemi di lag - + 1x è la risoluzione nativa - + Le impostazioni sono disattivate, completa le impostazioni della localizzazione @@ -58,7 +58,7 @@ Attiva le animazioni - Forza la disattivazione dei Wiimote + Disattiva forzatamente i Wiimote Apri il gioco con la finestra di Dolphin @@ -81,9 +81,6 @@ Renderizzatore - - Lingua di Retro Rewind - Mostra gli FPS @@ -148,6 +145,57 @@ Russo - Turko + Turco + + + Arabo + + + Cinese + + + Ceco + + + Norvegese + + + Portoghese + + + Generale + + + Altro + + + A riguardo + + + Apri la cartella del gioco + + + Apri la cartella del salvataggio + + + Reinstalla Retro Rewind + + + Installazione + + + Ismy, Sep + + + L'indirizzo può terminare con: + + + Devi impostare questi 3 indirizzi prima di poter iniziare a giocare a Retro Rewind + + + Opzionale + + + Questo cambia soltanto la lingua di Wheel Wizard. Per cambiare la lingua all'interno del gioco, dovrai farlo dalle impostazioni del gioco stesso. \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Settings.ja.resx b/WheelWizard/Resources/Languages/Settings.ja.resx index 3228e26c..e8c70b61 100644 --- a/WheelWizard/Resources/Languages/Settings.ja.resx +++ b/WheelWizard/Resources/Languages/Settings.ja.resx @@ -21,25 +21,25 @@ 映像 - + ゲームが30FPSで動作するようになります(通常は60FPS) - + パスの拡張子は必ず .exe にしてください - + ゲーム内ではWiiリモコンを無効にしますが、Wiiチャンネルでは有効にします - + ゲームと一緒にDolphinのメインウィンドウを起動します - + スタッタリングやラグスパイクなどを軽減するために、特定のDolphinの設定を変更します - + デフォルト解像度は1xです - + 設定が無効になっています。最初にパス設定を完了させてください @@ -69,9 +69,6 @@ レンダラー - - Retro Rewindの言語 - FPSの表示 @@ -129,10 +126,10 @@ トルコ語 - + 100 - + Wheel Wizard内のアニメーションを有効にします @@ -150,4 +147,55 @@ ロシア語 + + アラビア語 + + + 中国語 + + + チェコ語 + + + ノルウェー語 + + + ポルトガル語 + + + その他 + + + 一般 + + + 情報 + + + ゲームフォルダーを開く + + + セーブフォルダーを開く + + + Retro Rewindの再インストール + + + インストール + + + Haru00007, Mosgis, Mossan + + + パスは次のもので終わります: + + + Retro Rewindをプレイする前に、これらの3つのパスを設定する必要があります + + + 任意 + + + これはWheel Wizardの言語のみ変更します。ゲーム内の言語を変更したい場合、ゲーム設定内で変更する必要があります + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Settings.ko.resx b/WheelWizard/Resources/Languages/Settings.ko.resx index 33615ffa..75df4c3e 100644 --- a/WheelWizard/Resources/Languages/Settings.ko.resx +++ b/WheelWizard/Resources/Languages/Settings.ko.resx @@ -18,31 +18,31 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - 100 + + 80 비디오 - + 이 설정은 게임을 강제로 30프레임으로 구동시킵니다.(기본: 60) - + Wheel Wizard 앱의 애니메이션을 활성화 합니다. - - 경로는 ".exe"로 끝나야 합니다. + + 파일 경로는 ".exe"로 끝나야 합니다. - + 이 설정은 게임 내 위모트를 비활성 합니다. Wii 채널에선 활성화됩니다. - + 게임과 함께 Dolphin 메인 창을 킵니다. - + 이 설정은 랙을 줄이기 위해 Dolphin의 일부 설정을 바꿉니다. - + 1x는 네이티브 해상도입니다 @@ -54,7 +54,7 @@ 위모트 강제 비활성 - + 설정이 잠겨있습니다. 지역 설정을 마치면 해금됩니다. @@ -81,9 +81,6 @@ 렌더링 - - 레트로 리와인드(Retro Rewind) 언어 - FPS 표시 @@ -150,4 +147,43 @@ 튀르키예어 + + 아랍어 + + + 중국어 + + + 체코어 + + + 노르웨이어 + + + 포르투갈어 + + + 기타 + + + 일반 + + + 정보 + + + StationAlpha, Sinseiga, 주녕 + + + 파일 경로는 다음과 같이 끝날 수 있습니다: + + + Retro Rewind 실행 전 3개의 경로를 설정해야 합니다. + + + 옵션 + + + 이 설정은 Wheel Wizard의 언어만 변경합니다. 게임의 언어를 변경하기 위해서는 게임 설정에서 변경해야 합니다. + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Settings.nb.resx b/WheelWizard/Resources/Languages/Settings.nb.resx index 0db19739..0e8a4859 100644 --- a/WheelWizard/Resources/Languages/Settings.nb.resx +++ b/WheelWizard/Resources/Languages/Settings.nb.resx @@ -11,4 +11,10 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 0 + + + Kinsey, Pluto_games + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Settings.nl.resx b/WheelWizard/Resources/Languages/Settings.nl.resx index 2e4e9374..f582b417 100644 --- a/WheelWizard/Resources/Languages/Settings.nl.resx +++ b/WheelWizard/Resources/Languages/Settings.nl.resx @@ -1,9 +1,10 @@ - + - + @@ -13,49 +14,50 @@ 1.3 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + - Wheel Wizard Taal - - - Retro Rewind Taal + Wheel Wizard-taal Taal - Wii Instellingen + Wii-Instellingen - Wiimote uitzetten forceren + Forceer uitschakelen Wii-afstandsbediening - Start spel met een Dolphin Venster + Start spel met Dolphin-venster - - Instellingen zijn op slot, maak eerste de locatie instellingen compleet + + Instellingen zijn uitgeschakeld, voltooi eerst de locatie-instellingen. Resolutie - - 1x is de orginele resolutie + + 1× is de orginele resolutie. - Optimalisatie Instellingen + Prestatie-instellingen - Rendering Instellingen + Renderer-instellingen VSync - Aanbevolen Instellingen + Aanbevolen instellingen FPS weergeven @@ -64,43 +66,43 @@ Renderer - Beeld + Video - Locatie Instellingen + Locatie-instellingen - Wheel Wizard uiterlijk + Wheel Wizard-weergave - Open instellings map + Open config-map - Dolphin gebruikers map + Dolphin-gebruiker-map - Mario Kart Wii spel bestand + Mario Kart Wii-spelbestand - Dolphin emulator executable locatie + Dolphin-emulator-executable - - Locatie moet eindigen met .exe + + Locatie moet eindigen met ".exe". - Venster grootte + Venstergrootte - - Met deze instelling wordt de Wiimote in de game uitgeschakeld, maar voor het Wii-kanaal ingeschakeld. + + Deze instelling schakelt de Wii-afstandsbediening uit in het spel, maar niet in het Wii-menu. - - Opent de dolphin venster samen met het spel + + Opent het Dolphin-hoofdvenster met het spel erbij. - - Met deze instelling worden bepaalde dolpin-instellingen gewijzigd voor de meest geoptimalizeerde speel ervaring + + Deze instelling verandert sommige Dolphin-instellingen voor een optimale ervaring. - - Met deze instelling wordt het spel gedwongen om te draaien op 30 FPS (standaard is 60) + + Met deze instelling wordt het spel gedwongen om te draaien op 30 FPS (standaard is 60). Nederlands @@ -127,36 +129,78 @@ Turks - Zet als hoofd profiel + Maak hoofdprofiel - - 100 + + 90 - Verwijder wazigheid + Verwijder waas - Animaties aanzetten + Animaties inschakelen - - Deze instelling zet de animaties aan in deze app + + Dit schakelt de animaties in de Wheel Wizard-app in. Fins - Korean + Koreaans Russisch - Open spel map + Open spelmap - Open opslag map + Open opslagmap - Info + Over + + + Arabisch + + + Chinees + + + Tsjechisch + + + Noors + + + Portugees + + + Overig + + + Algemeen + + + Herinstalleer RR + + + Installatie + + + Locatie kan eindigen met: + + + Dit verandert alleen de taal van Wheel Wizard. Om de taal in het spel zelf te wijzigen, moet je dit doen via de instellingen van het spel. + + + WantToBeeMe, Noël, Krummers + + + Optioneel + + + Je moet deze 3 locaties instellen voordat je Retro Rewind kunt spelen. \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Settings.pt.resx b/WheelWizard/Resources/Languages/Settings.pt.resx index a4c5284f..2f7319b5 100644 --- a/WheelWizard/Resources/Languages/Settings.pt.resx +++ b/WheelWizard/Resources/Languages/Settings.pt.resx @@ -18,4 +18,82 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Video + + + Esta definição vai forçar o jogo a correr a 30 FPS (normalmente é 60) + + + Isto ativa as animações na app Wheel Wizard + + + O caminho deve acabar com .exe + + + Esta definição desativa o Wiimote para o jogo, mas ativa-o para o canal Wii + + + Irá abrir a página principal do dolphin juntamento com o jogo + + + Esta definição vai alterar algumas definições do dolphin para reduzir stutters e picos de lag + + + 1x a resolução nativa + + + As definições estão desativadas, completa as definições de localização primeiro + + + Executável do Dolphin Emulator + + + Pasta do Utilizador do Dolphin + + + Ativar Animações + + + Força a desativar o Wiimote + + + Abrir o jogo com a janela do Dolphin + + + Tornar Primário + + + Ficheiro de Jogo do Mario Kart Wii + + + Abrir a pasta Config + + + Definições recomensadas + + + Remover Blur + + + Renderizar + + + Mostrar FPS + + + VSync + + + Idioma do Wheel Wizard + + + Escala da janela + + + 0 + + + JMM, Gui_C__Tuga + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Settings.resx b/WheelWizard/Resources/Languages/Settings.resx index f0ef06b0..adee40eb 100644 --- a/WheelWizard/Resources/Languages/Settings.resx +++ b/WheelWizard/Resources/Languages/Settings.resx @@ -1,9 +1,10 @@ - + - + @@ -13,17 +14,18 @@ 1.3 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + Wheel Wizard Language - - Retro Rewind Language - Force Disable Wiimote @@ -36,19 +38,19 @@ Wii Settings - + Settings are disabled, complete the location settings first - + Will launch dolphins main window along with the game - + This setting disables Wiimote ingame, but enables it for the Wii channel Resolution - + 1x is the native resolution @@ -69,10 +71,10 @@ Renderer - + This setting will change certain dolphin settings to reduce stuttering and lag spikes - + This setting will force the game to run in 30 FPS (default is 60) @@ -90,6 +92,24 @@ Open Config Folder + + Wheel Wizard data folder + + + Choose where Wheel Wizard stores its files. Moving may take a while. + + + Use default location + + + Using default location. + + + Using custom location. + + + Moving data, please wait... + Dolphin User Folder @@ -99,7 +119,7 @@ Dolphin Emulator executable - + Path must end with .exe @@ -153,7 +173,7 @@ Make Primary - + 100 @@ -174,8 +194,8 @@ Enable Animations - - This enables the animations in the the Wheel Wizard app + + This enables the animations in the Wheel Wizard app Russian @@ -184,31 +204,31 @@ Русский - + Arabic - - + Czech - - + Norwegian - - + Chinese - - + Portuguese - @@ -228,4 +248,25 @@ About - \ No newline at end of file + + Other + + + General + + + Path can end with: + + + This only changes the language for Wheel Wizard. To change the in-game language, you’ll need to do that within the game settings. + + + - + + + Optional + + + You must set these 3 paths before you can start playing Retro Rewind + + diff --git a/WheelWizard/Resources/Languages/Settings.ru.resx b/WheelWizard/Resources/Languages/Settings.ru.resx index adc9e900..816db17a 100644 --- a/WheelWizard/Resources/Languages/Settings.ru.resx +++ b/WheelWizard/Resources/Languages/Settings.ru.resx @@ -18,34 +18,34 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - 100 + + 88 Видео - - Эта настройка поставит максимальное количество ФПС нв 30 (стандарт - 60) + + Эта настройка поставит максимальное количество FPS на 30 (стандарт - 60) - + Эта настройка включает анимации в лаунчере Wheel Wizard. - + Путь должен заканчиваться на .exe - + Эта настройка отключает Wii Remote в игре, но включает для канала Wii. - + Откроет основное окно Dolphin'а вместе с игрой - + Это изменит некоторые некоторые настройки Dolphin'а, чтобы избежать фризов и лагов. - + 1x это нативное разрешение - + Настройки отключены, сначала завершите настройки локации. @@ -81,9 +81,6 @@ Движок рендера - - Язык Retro Rewind - Показывать FPS @@ -150,4 +147,46 @@ Турецкий + + Арабский + + + Китайский + + + Чешский + + + Норвежский + + + Португальский + + + Другое + + + Основное + + + CaXaP, StHydrated, OrangeCake + + + Открыть папку с игрой + + + Открыть папку сохранения + + + Установка + + + О нас + + + Путь может заканчиваться на: + + + Переустановить Retro Rewind + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Settings.tr.resx b/WheelWizard/Resources/Languages/Settings.tr.resx index db328420..8c8cfb1a 100644 --- a/WheelWizard/Resources/Languages/Settings.tr.resx +++ b/WheelWizard/Resources/Languages/Settings.tr.resx @@ -18,34 +18,34 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + 100 Video - - Bu ayar, oyunu 30 FPS'de çalışmaya zorlar (varsayılan 60'tır). + + Bu ayar, oyunu 30 FPS'de çalışmaya zorlar (varsayılan 60). - + Bu, Wheel Wizard uygulamasındaki animasyonları etkinleştirir. - + Yol, .exe ile bitmelidir. - + Bu ayar, Wiimote'u oyun içinde devre dışı bırakır, ancak Wii kanalında etkinleştirir. - + Oyunun yanı sıra Dolphin'in ana penceresini de başlatacak. - + Bu ayar, takılmaları ve gecikme dalgalanmalarını azaltmak için belirli Dolphin ayarlarını değiştirir. - + 1x yerel çözünürlüktür. - + Ayarlar devre dışı, önce konum ayarlarını tamamlayın. @@ -70,7 +70,7 @@ Mario Kart Wii Oyun Dosyası - Yapılandırma Klasörünü Aç + Yapılandırma Klasörü Tavsiye Edilen Ayarlar @@ -81,9 +81,6 @@ Render - - Retro Rewind Dili - FPS Göster @@ -150,4 +147,55 @@ Türkçe + + Arapça + + + Çince + + + Çekçe + + + Norveççe + + + Portekizce + + + Genel + + + Diğer + + + Hakkında + + + Oyun Klasörünü Aç + + + Kayıt Klasörünü Aç + + + Retro Rewind'ı Tekrar Kur + + + Kurulum + + + wrkus, Weebo + + + Yol, bunlar ile bitebilir: + + + Retro Rewind'ı oynayabilmek için önce bu üç yolu ayarlamanız gerekiyor + + + Opsiyonel + + + Bu sadece Wheel Wizard'ın dilini değiştirir. Oyun içi dili değiştirmek için oyundaki ayarları kullanın. + \ No newline at end of file diff --git a/WheelWizard/Resources/Languages/Settings.zh.resx b/WheelWizard/Resources/Languages/Settings.zh.resx index a4c5284f..b7714137 100644 --- a/WheelWizard/Resources/Languages/Settings.zh.resx +++ b/WheelWizard/Resources/Languages/Settings.zh.resx @@ -18,4 +18,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 0 + \ No newline at end of file diff --git a/WheelWizard/Services/Installation/RetroRewindInstaller.cs b/WheelWizard/Services/Installation/RetroRewindInstaller.cs deleted file mode 100644 index 60bd6740..00000000 --- a/WheelWizard/Services/Installation/RetroRewindInstaller.cs +++ /dev/null @@ -1,175 +0,0 @@ -using System.IO.Compression; -using System.Text.RegularExpressions; -using WheelWizard.Helpers; -using WheelWizard.Resources.Languages; -using WheelWizard.Views.Popups.Generic; - -namespace WheelWizard.Services.Installation; - -public static class RetroRewindInstaller -{ - private static readonly string NotInstalledVersion = "Not Installed"; - - public static bool IsRetroRewindInstalled() => CurrentRRVersion() != NotInstalledVersion; - - public static string CurrentRRVersion() - { - var versionFilePath = PathManager.RetroRewindVersionFile; - if (!File.Exists(versionFilePath)) - return NotInstalledVersion; - - var versionText = File.ReadAllText(versionFilePath).Trim(); - var versionPattern = @"^\d+\.\d+\.\d+$"; - if (!Regex.IsMatch(versionText, versionPattern)) - return NotInstalledVersion; - - return versionText; - } - - public static async Task HandleNotInstalled() - { - var result = await new YesNoWindow() - .SetMainText(Phrases.PopupText_RRNotDeterment) - .SetExtraText(Phrases.PopupText_DownloadRR) - .AwaitAnswer(); - - if (!result) - return false; - - await InstallRetroRewind(); - return true; - } - - public static async Task HandleOldVersion() - { - var result = await new YesNoWindow() - .SetMainText(Phrases.PopupText_RRToOld) - .SetExtraText(Phrases.PopupText_ReinstallRR) - .AwaitAnswer(); - - if (!result) - return false; - - await InstallRetroRewind(); - return true; - } - - public static async Task InstallRetroRewind() - { - if (IsRetroRewindInstalled()) - DeleteExistingRetroRewind(); - - if (HasOldRksys()) - { - var rksysQuestion = new YesNoWindow() - .SetMainText(Phrases.PopupText_OldRksysFound) - .SetExtraText(Phrases.PopupText_OldRksysFoundExplained); - if (await rksysQuestion.AwaitAnswer()) - await BackupOldrksys(); - } - var serverResponse = await HttpClientHelper.GetAsync(Endpoints.RRUrl); - if (!serverResponse.Succeeded) - { - await new MessageBoxWindow() - .SetMessageType(MessageBoxWindow.MessageType.Warning) - .SetTitleText("Could not connect to the server") - .SetInfoText(Phrases.PopupText_CouldNotConnectServer) - .ShowDialog(); - return; - } - await DownloadAndExtractRetroRewind(PathManager.RetroRewindTempFile); - await RetroRewindUpdater.UpdateRR(); - } - - public static async Task ReinstallRR() - { - var result = await new YesNoWindow() - .SetMainText(Phrases.PopupText_ReinstallRR) - .SetExtraText(Phrases.PopupText_ReinstallQuestion) - .AwaitAnswer(); - - if (!result) - return; - - DeleteExistingRetroRewind(); - await InstallRetroRewind(); - } - - private static async Task DownloadAndExtractRetroRewind(string tempZipPath) - { - var progressWindow = new ProgressWindow(Phrases.PopupText_InstallingRR); - progressWindow.SetExtraText(Phrases.PopupText_InstallingRRFirstTime); - progressWindow.Show(); - - try - { - await DownloadHelper.DownloadToLocationAsync(Endpoints.RRZipUrl, tempZipPath, progressWindow); - progressWindow.SetExtraText(Common.State_Extracting); - var extractionPath = PathManager.RiivolutionWhWzFolderPath; - ZipFile.ExtractToDirectory(tempZipPath, extractionPath, true); - } - finally - { - progressWindow.Close(); - if (File.Exists(tempZipPath)) - File.Delete(tempZipPath); - } - } - - private static bool HasOldRksys() - { - return !string.IsNullOrWhiteSpace(GetOldRksys()); - } - - private static string GetOldRksys() - { - var rrWfcPaths = new[] - { - Path.Combine(PathManager.SaveFolderPath), - // Also consider the folder with upper-case `Save` - Path.Combine(PathManager.RiivolutionWhWzFolderPath, "riivolution", "Save", "RetroWFC"), - Path.Combine(PathManager.LoadFolderPath, "Riivolution", "save", "RetroWFC"), - Path.Combine(PathManager.LoadFolderPath, "Riivolution", "Save", "RetroWFC"), - Path.Combine(PathManager.LoadFolderPath, "riivolution", "save", "RetroWFC"), - Path.Combine(PathManager.LoadFolderPath, "riivolution", "Save", "RetroWFC"), - }; - - foreach (var rrWfc in rrWfcPaths) - { - if (!Directory.Exists(rrWfc)) - continue; - var rksysFiles = Directory.GetFiles(rrWfc, "rksys.dat", SearchOption.AllDirectories); - if (rksysFiles.Length > 0) - return rrWfc; - } - - return string.Empty; - } - - private static async Task BackupOldrksys() - { - var rrWfc = Path.Combine(GetOldRksys()); - if (!Directory.Exists(rrWfc)) - return; - var rksysFiles = Directory.GetFiles(rrWfc, "rksys.dat", SearchOption.AllDirectories); - if (rksysFiles.Length == 0) - return; - var sourceFile = rksysFiles[0]; - var regionFolder = Path.GetDirectoryName(sourceFile); - var regionFolderName = Path.GetFileName(regionFolder); - var datFileData = await File.ReadAllBytesAsync(sourceFile); - if (regionFolderName == null) - return; - var destinationFolder = Path.Combine(PathManager.SaveFolderPath, regionFolderName); - Directory.CreateDirectory(destinationFolder); - var destinationFile = Path.Combine(destinationFolder, "rksys.dat"); - await File.WriteAllBytesAsync(destinationFile, datFileData); - } - - private static void DeleteExistingRetroRewind() - { - var retroRewindPath = PathManager.RetroRewind6FolderPath; - if (Directory.Exists(retroRewindPath)) - Directory.Delete(retroRewindPath, true); - } -} diff --git a/WheelWizard/Services/Installation/RetroRewindUpdater.cs b/WheelWizard/Services/Installation/RetroRewindUpdater.cs index b009cba5..5f282702 100644 --- a/WheelWizard/Services/Installation/RetroRewindUpdater.cs +++ b/WheelWizard/Services/Installation/RetroRewindUpdater.cs @@ -1,309 +1 @@ -using System.IO.Compression; -using WheelWizard.Helpers; -using WheelWizard.Resources.Languages; -using WheelWizard.Views.Popups.Generic; - -namespace WheelWizard.Services.Installation; - -public static class RetroRewindUpdater -{ - public static async Task IsRRUpToDate(string currentVersion) - { - var latestVersion = await GetLatestVersionString(); - return currentVersion.Trim() == latestVersion.Trim(); - } - - private static async Task GetLatestVersionString() - { - var response = await HttpClientHelper.GetAsync(Endpoints.RRVersionUrl); - if (response.Succeeded && response.Content != null) - return response.Content.Split('\n').Last().Split(' ')[0]; - new YesNoWindow().SetMainText(Phrases.PopupText_FailCheckUpdates).AwaitAnswer(); - return "Failed to check for updates"; - } - - public static async Task UpdateRR() - { - try - { - if (!RetroRewindInstaller.IsRetroRewindInstalled()) - return await RetroRewindInstaller.HandleNotInstalled(); - - var currentVersion = RetroRewindInstaller.CurrentRRVersion(); - if (await IsRRUpToDate(currentVersion)) - { - await new MessageBoxWindow() - .SetMessageType(MessageBoxWindow.MessageType.Message) - .SetTitleText(Phrases.PopupText_RRUpToDate) - .SetInfoText(Phrases.PopupText_RRUpToDate) - .ShowDialog(); - return true; - } - - //if current version is below 3.2.6 we need to do a full reinstall - if (CompareVersions(currentVersion, "3.2.6") < 0) - return await RetroRewindInstaller.HandleOldVersion(); - return await ApplyUpdates(currentVersion); - } - catch (Exception e) - { - AbortingUpdate($"Reason: {e.Message}"); - return false; - } - } - - private static async Task ApplyUpdates(string currentVersion) - { - var allVersions = await GetAllVersionData(); - var updatesToApply = GetUpdatesToApply(currentVersion, allVersions); - - var progressWindow = new ProgressWindow(Phrases.PopupText_UpdateRR); - progressWindow.Show(); - - // Step 1: Get the version we are updating to - var targetVersion = updatesToApply.Any() ? updatesToApply.Last().Version : currentVersion; - - // Step 2: Apply file deletions for versions between current and targetVersion - var deleteSuccess = await ApplyFileDeletionsBetweenVersions(currentVersion, targetVersion); - if (!deleteSuccess) - { - AbortingUpdate(Phrases.PopupText_FailedUpdateDelete); - progressWindow.Close(); - return false; - } - - // Step 3: Download and apply the updates (if any) - for (var i = 0; i < updatesToApply.Count; i++) - { - var update = updatesToApply[i]; - - var success = await DownloadAndApplyUpdate(update, updatesToApply.Count, i + 1, progressWindow); - if (!success) - { - progressWindow.Close(); - AbortingUpdate(Phrases.PopupText_FailedUpdateApply); - return false; - } - - // Update the version file after each successful update - UpdateVersionFile(update.Version); - } - - progressWindow.Close(); - return true; - } - - private static async Task ApplyFileDeletionsBetweenVersions(string currentVersion, string targetVersion) - { - try - { - var deleteList = await GetFileDeletionList(); - var deletionsToApply = GetDeletionsToApply(currentVersion, targetVersion, deleteList); - - foreach (var file in deletionsToApply) - { - var filePath = Path.GetFullPath(Path.Combine(PathManager.RiivolutionWhWzFolderPath, file.Path.TrimStart('/'))); - //because we are actually getting the path from the server, - //we need to make sure we are not getting hacked, so we check if the path is in the riivolution folder - var resolvedPath = Path.GetFullPath(new FileInfo(filePath).FullName); - if ( - !resolvedPath.StartsWith(PathManager.RiivolutionWhWzFolderPath, StringComparison.OrdinalIgnoreCase) - || !filePath.StartsWith(PathManager.RiivolutionWhWzFolderPath, StringComparison.OrdinalIgnoreCase) - || filePath.Contains("..") - ) - { - AbortingUpdate("Invalid file path detected. Please contact the developers.\n Server error: " + resolvedPath); - return false; - } - - if (File.Exists(filePath)) - File.Delete(filePath); - else if (Directory.Exists(filePath)) - Directory.Delete(filePath, recursive: true); - } - - return true; - } - catch (Exception e) - { - AbortingUpdate($"Failed to delete files: {e.Message}"); - return false; - } - } - - private static List<(string Version, string Path)> GetDeletionsToApply( - string currentVersion, - string targetVersion, - List<(string Version, string Path)> allDeletions - ) - { - var deletionsToApply = new List<(string Version, string Path)>(); - allDeletions.Sort((a, b) => CompareVersions(b.Version, a.Version)); // Sort in descending order - foreach (var deletion in allDeletions) - { - if (CompareVersions(deletion.Version, currentVersion) > 0 && CompareVersions(deletion.Version, targetVersion) <= 0) - { - deletionsToApply.Add(deletion); - } - } - - deletionsToApply.Reverse(); - return deletionsToApply; - } - - private static async Task> GetFileDeletionList() - { - var deleteList = new List<(string Version, string Path)>(); - - using var httpClient = new HttpClient(); - var deleteListText = await httpClient.GetStringAsync(Endpoints.RRVersionDeleteUrl); - var lines = deleteListText.Split('\n', StringSplitOptions.RemoveEmptyEntries); - - foreach (var line in lines) - { - var parts = line.Split(' ', 2); - if (parts.Length < 2) - continue; - deleteList.Add((parts[0].Trim(), parts[1].Trim())); - } - - return deleteList; - } - - private static void UpdateVersionFile(string newVersion) - { - var versionFilePath = Path.Combine(PathManager.RetroRewind6FolderPath, "version.txt"); - File.WriteAllText(versionFilePath, newVersion); - } - - private static async Task> GetAllVersionData() - { - var versions = new List<(string Version, string Url, string Path, string Description)>(); - - using var httpClient = new HttpClient(); - var allVersionsText = await httpClient.GetStringAsync(Endpoints.RRVersionUrl); - var lines = allVersionsText.Split('\n', StringSplitOptions.RemoveEmptyEntries); - - foreach (var line in lines) - { - var parts = line.Split(' ', 4); - if (parts.Length < 4) - continue; - versions.Add((parts[0].Trim(), parts[1].Trim(), parts[2].Trim(), parts[3].Trim())); - } - - return versions; - } - - private static List<(string Version, string Url, string Path, string Description)> GetUpdatesToApply( - string currentVersion, - List<(string Version, string Url, string Path, string Description)> allVersions - ) - { - var updatesToApply = new List<(string Version, string Url, string Path, string Description)>(); - allVersions.Sort((a, b) => CompareVersions(b.Version, a.Version)); // Sort in descending order - foreach (var version in allVersions) - { - if (CompareVersions(version.Version, currentVersion) > 0) - updatesToApply.Add(version); - else - break; - } - - updatesToApply.Reverse(); - return updatesToApply; - } - - private static int CompareVersions(string v1, string v2) - { - var parts1 = v1.Split('.').Select(int.Parse).ToArray(); - var parts2 = v2.Split('.').Select(int.Parse).ToArray(); - for (var i = 0; i < Math.Max(parts1.Length, parts2.Length); i++) - { - var p1 = i < parts1.Length ? parts1[i] : 0; - var p2 = i < parts2.Length ? parts2[i] : 0; - if (p1 != p2) - return p1.CompareTo(p2); - } - - return 0; - } - - private static async Task DownloadAndApplyUpdate( - (string Version, string Url, string Path, string Description) update, - int totalUpdates, - int currentUpdateIndex, - ProgressWindow popupWindow - ) - { - var tempZipPath = Path.GetTempFileName(); - try - { - popupWindow.SetExtraText($"{Common.Action_Update} {currentUpdateIndex}/{totalUpdates}: {update.Description}"); - var finalFile = await DownloadHelper.DownloadToLocationAsync(update.Url, tempZipPath, popupWindow); - - popupWindow.UpdateProgress(100); - popupWindow.SetExtraText(Common.State_Extracting); - var destinationDirectoryPath = PathManager.RiivolutionWhWzFolderPath; - Directory.CreateDirectory(destinationDirectoryPath); - ExtractZipFile(finalFile, destinationDirectoryPath); - if (File.Exists(finalFile)) - File.Delete(finalFile); - } - finally - { - if (File.Exists(tempZipPath)) - File.Delete(tempZipPath); - } - - return true; - } - - private static void ExtractZipFile(string path, string destinationDirectory) - { - using var archive = ZipFile.OpenRead(path); - - // Absolute path of the destination directory - var absoluteDestinationPath = Path.GetFullPath(destinationDirectory + Path.AltDirectorySeparatorChar); - - foreach (var entry in archive.Entries) - { - if (entry.FullName.EndsWith("desktop.ini", StringComparison.OrdinalIgnoreCase)) - continue; // Skip the desktop.ini file - - // Get the full path of the file - var destinationPath = Path.GetFullPath(Path.Combine(destinationDirectory, entry.FullName)); - - // Check for directory traversal attacks - if (!destinationPath.StartsWith(absoluteDestinationPath, StringComparison.Ordinal)) - { - AbortingUpdate("The file path is outside the destination directory. Please contact the developers."); - return; - } - - // If the entry is a directory, create it - if (entry.FullName.EndsWith(Path.AltDirectorySeparatorChar)) - { - Directory.CreateDirectory(destinationPath); - continue; - } - - // Create directory if it doesn't exist - var directoryName = Path.GetDirectoryName(destinationPath); - if (!string.IsNullOrEmpty(directoryName)) - Directory.CreateDirectory(directoryName); - - // Extract the file - entry.ExtractToFile(destinationPath, overwrite: true); - } - } - - public static void AbortingUpdate(string reason) - { - new MessageBoxWindow() - .SetMessageType(MessageBoxWindow.MessageType.Error) - .SetTitleText("Aborting RR Update") - .SetInfoText(reason) - .Show(); - } -} + \ No newline at end of file diff --git a/WheelWizard/Services/Launcher/Helpers/DolphinLaunchHelper.cs b/WheelWizard/Services/Launcher/Helpers/DolphinLaunchHelper.cs index 0ded58c1..bb4989fb 100644 --- a/WheelWizard/Services/Launcher/Helpers/DolphinLaunchHelper.cs +++ b/WheelWizard/Services/Launcher/Helpers/DolphinLaunchHelper.cs @@ -91,7 +91,7 @@ void AddFilesystemPerm(string newFilesystemPerm, string mode = "") var flatpakRunCommand = "flatpak run"; fixedFlatpakDolphinLocation = fixedFlatpakDolphinLocation.Replace( flatpakRunCommand, - $"{flatpakRunCommand} --filesystem={EnvHelper.SingleQuotePath(Path.GetFullPath(newFilesystemPerm))}{mode}" + $"{flatpakRunCommand} --filesystem={EnvHelper.QuotePath(Path.GetFullPath(newFilesystemPerm))}{mode}" ); } diff --git a/WheelWizard/Services/Launcher/Helpers/ModsLaunchHelper.cs b/WheelWizard/Services/Launcher/Helpers/ModsLaunchHelper.cs index ad73303f..f023d190 100644 --- a/WheelWizard/Services/Launcher/Helpers/ModsLaunchHelper.cs +++ b/WheelWizard/Services/Launcher/Helpers/ModsLaunchHelper.cs @@ -20,8 +20,8 @@ public static async Task PrepareModsForLaunch() { var modsFoundQuestion = new YesNoWindow() .SetButtonText(Common.Action_Delete, Common.Action_Keep) - .SetMainText(Phrases.PopupText_ModsFound) - .SetExtraText(Phrases.PopupText_ModsFoundQuestion); + .SetMainText(Phrases.Question_LaunchClearModsFound_Title) + .SetExtraText(Phrases.Question_LaunchClearModsFound_Extra); if (await modsFoundQuestion.AwaitAnswer()) Directory.Delete(MyStuffFolderPath, true); @@ -67,8 +67,8 @@ public static async Task PrepareModsForLaunch() } var totalFiles = finalFiles.Count; - var progressWindow = new ProgressWindow(Phrases.PopupText_InstallingMods).SetGoal( - Humanizer.ReplaceDynamic(Phrases.PopupText_InstallingModsCount, totalFiles)! + var progressWindow = new ProgressWindow(Phrases.Progress_InstallingMods).SetGoal( + Humanizer.ReplaceDynamic(Phrases.Progress_InstallingModsCount, totalFiles)! ); progressWindow.Show(); diff --git a/WheelWizard/Services/Launcher/Helpers/RetroRewindLaunchHelper.cs b/WheelWizard/Services/Launcher/Helpers/RetroRewindLaunchHelper.cs index ba5914e0..148a76fe 100644 --- a/WheelWizard/Services/Launcher/Helpers/RetroRewindLaunchHelper.cs +++ b/WheelWizard/Services/Launcher/Helpers/RetroRewindLaunchHelper.cs @@ -12,7 +12,6 @@ public static class RetroRewindLaunchHelper public static void GenerateLaunchJson() { - var language = (int)SettingsManager.RR_LANGUAGE.Get(); var removeBlur = (bool)SettingsManager.REMOVE_BLUR.Get(); var launchConfig = new LaunchConfig @@ -40,12 +39,6 @@ public static void GenerateLaunchJson() SectionName = "Retro Rewind", }, new() - { - Choice = language, - OptionName = "Language", - SectionName = "Retro Rewind", - }, - new() { Choice = removeBlur ? 1 : 0, OptionName = "Remove Blur", diff --git a/WheelWizard/Services/Launcher/RrLauncher.cs b/WheelWizard/Services/Launcher/RrLauncher.cs index 26ceb75e..9a18eb36 100644 --- a/WheelWizard/Services/Launcher/RrLauncher.cs +++ b/WheelWizard/Services/Launcher/RrLauncher.cs @@ -1,4 +1,5 @@ using Avalonia.Threading; +using WheelWizard.CustomDistributions; using WheelWizard.Helpers; using WheelWizard.Models.Enums; using WheelWizard.Resources.Languages; @@ -6,6 +7,8 @@ using WheelWizard.Services.Launcher.Helpers; using WheelWizard.Services.Settings; using WheelWizard.Services.WiiManagement; +using WheelWizard.Shared.DependencyInjection; +using WheelWizard.Views; using WheelWizard.Views.Popups.Generic; namespace WheelWizard.Services.Launcher; @@ -15,6 +18,10 @@ public class RrLauncher : ILauncher public string GameTitle { get; } = "Retro Rewind"; private static string RrLaunchJsonFilePath => PathManager.RrLaunchJsonFilePath; + [Inject] + private ICustomDistributionSingletonService CustomDistributionSingletonService { get; set; } = + App.Services.GetRequiredService(); + public async Task Launch() { try @@ -30,7 +37,7 @@ public async Task Launch() new MessageBoxWindow() .SetMessageType(MessageBoxWindow.MessageType.Warning) .SetTitleText("Invalid game path") - .SetInfoText(Phrases.PopupText_NotFindGame) + .SetInfoText(Phrases.MessageWarning_NotFindGame_Extra) .Show(); }); return; @@ -39,7 +46,7 @@ public async Task Launch() RetroRewindLaunchHelper.GenerateLaunchJson(); var dolphinLaunchType = (bool)SettingsManager.LAUNCH_WITH_DOLPHIN.Get() ? "" : "-b"; DolphinLaunchHelper.LaunchDolphin( - $"{dolphinLaunchType} -e {EnvHelper.QuotePath(Path.GetFullPath(RrLaunchJsonFilePath))} --config=Dolphin.Core.EnableCheats=False" + $"{dolphinLaunchType} -e {EnvHelper.QuotePath(Path.GetFullPath(RrLaunchJsonFilePath))} --config=Dolphin.Core.EnableCheats=False --config=Achievements.Achievements.Enabled=False" ); } catch (Exception ex) @@ -55,25 +62,39 @@ public async Task Launch() } } - public Task Install() => RetroRewindInstaller.InstallRetroRewind(); + public async Task Install() + { + var progressWindow = new ProgressWindow(); + progressWindow.Show(); + var installResult = await CustomDistributionSingletonService.RetroRewind.InstallAsync(progressWindow); + progressWindow.Close(); + if (installResult.IsFailure) + { + await new MessageBoxWindow() + .SetMessageType(MessageBoxWindow.MessageType.Error) + .SetTitleText("Unable to install RetroRewind") + .SetInfoText(installResult.Error.Message) + .ShowDialog(); + } + } - public Task Update() => RetroRewindUpdater.UpdateRR(); + public async Task Update() + { + var progressWindow = new ProgressWindow(); + progressWindow.Show(); + await CustomDistributionSingletonService.RetroRewind.UpdateAsync(progressWindow); + progressWindow.Close(); + } public async Task GetCurrentStatus() { - if (!SettingsHelper.PathsSetupCorrectly()) - return WheelWizardStatus.ConfigNotFinished; - - var serverEnabled = await HttpClientHelper.GetAsync(Endpoints.RRUrl); - var rrInstalled = RetroRewindInstaller.IsRetroRewindInstalled(); - - if (!serverEnabled.Succeeded) - return rrInstalled ? WheelWizardStatus.NoServerButInstalled : WheelWizardStatus.NoServer; - - if (!rrInstalled) + if (CustomDistributionSingletonService == null) + { return WheelWizardStatus.NotInstalled; - - var retroRewindUpToDate = await RetroRewindUpdater.IsRRUpToDate(RetroRewindInstaller.CurrentRRVersion()); - return !retroRewindUpToDate ? WheelWizardStatus.OutOfDate : WheelWizardStatus.Ready; + } + var statusResult = await CustomDistributionSingletonService.RetroRewind.GetCurrentStatusAsync(); + if (statusResult.IsFailure) + return WheelWizardStatus.NotInstalled; + return statusResult.Value; } } diff --git a/WheelWizard/Services/LiveData/RRLiveRooms.cs b/WheelWizard/Services/LiveData/RRLiveRooms.cs index 698451b6..0d19b9bf 100644 --- a/WheelWizard/Services/LiveData/RRLiveRooms.cs +++ b/WheelWizard/Services/LiveData/RRLiveRooms.cs @@ -4,7 +4,8 @@ using WheelWizard.Views; using WheelWizard.WheelWizardData; using WheelWizard.WiiManagement; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.MiiManagement; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; namespace WheelWizard.Services.LiveData; diff --git a/WheelWizard/Services/ModManager.cs b/WheelWizard/Services/ModManager.cs index 33853bb7..0821f28a 100644 --- a/WheelWizard/Services/ModManager.cs +++ b/WheelWizard/Services/ModManager.cs @@ -189,6 +189,7 @@ public void ToggleAllMods(bool enable) // TODO: Use this validation method when refactoring the ModManager public OperationResult ValidateModName(string? oldName, string newName) { + newName = newName?.Trim(); if (string.IsNullOrWhiteSpace(newName)) return Fail("Mod name cannot be empty."); @@ -204,6 +205,9 @@ public OperationResult ValidateModName(string? oldName, string newName) if (newName.Any(x => _illegalChars.Contains(x))) return Fail("Mod name contains illegal characters."); + if (newName.Any(x => _illegalChars.Contains(x))) + return Fail("Mod name contains illegal characters."); + return Ok(); } @@ -276,7 +280,7 @@ public async void RenameMod(Mod selectedMod) public async void DeleteMod(Mod selectedMod) { var areTheySure = await new YesNoWindow() - .SetMainText(Humanizer.ReplaceDynamic(Phrases.PopupText_SureDeleteQuestion, selectedMod.Title)) + .SetMainText(Humanizer.ReplaceDynamic(Phrases.Question_SureDelete_Title, selectedMod.Title)!) .AwaitAnswer(); if (!areTheySure) return; @@ -331,7 +335,7 @@ public void OpenModFolder(Mod selectedMod) } else { - ErrorOccurred(Phrases.PopupText_NoModFolder); + ErrorOccurred(Phrases.MessageError_NoModFolder_Extra); } } @@ -368,14 +372,14 @@ private bool IsValidName(string? name) { if (string.IsNullOrWhiteSpace(name)) { - ErrorOccurred(Phrases.PopupText_ModNameEmpty); + ErrorOccurred(Phrases.MessageWarning_ModNameEmpty_Title); return false; } if (!ModInstallation.ModExists(Mods, name)) return true; - ErrorOccurred(Humanizer.ReplaceDynamic(Phrases.PopupText_ModNameExists, name)); + ErrorOccurred(Humanizer.ReplaceDynamic(Phrases.MessageWarning_InvalidName_Extra_ModNameExists, name)); return false; } diff --git a/WheelWizard/Services/PathManager.cs b/WheelWizard/Services/PathManager.cs index 7ef11c3e..1831cc36 100644 --- a/WheelWizard/Services/PathManager.cs +++ b/WheelWizard/Services/PathManager.cs @@ -12,8 +12,24 @@ public static class PathManager // IMPORTANT: To keep things consistent all paths should be Attrib expressions, // and either end with `FilePath` or `FolderPath` + private const string WheelWizardFolderName = "CT-MKWII"; +#if WINDOWS + private const string WindowsAppDataOverrideRegistryKeyPath = @"Software\\WheelWizard"; + private const string WindowsAppDataOverrideRegistryValueName = "AppDataLocation"; +#endif + private static readonly object WheelWizardAppdataLock = new(); + // Portable WheelWizard config only makes sense on non-Flatpak WheelWizard private static readonly bool IsPortableWhWz = !IsFlatpakSandboxed() && File.Exists("portable-ww.txt"); + private static readonly string DefaultWheelWizardAppdataPath = FileHelper.NormalizePath( + FileHelper.Combine(IsPortableWhWz ? string.Empty : AppDataFolder, WheelWizardFolderName) + ); + private static string? _wheelWizardAppdataOverride; + + static PathManager() + { + _wheelWizardAppdataOverride = LoadSavedWheelWizardAppdataOverride(); + } public static string HomeFolderPath => Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); @@ -24,34 +40,477 @@ public static class PathManager private static string AppDataFolder => Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); private static string LocalAppDataFolder => Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + private static string UnixAppDataOverrideFilePath => + Path.Combine(IsPortableWhWz ? string.Empty : AppDataFolder, "wheelwizard-appdata-location"); - // Wheel wizard's appdata paths (dont have to be expressions since they dont depend on user input like the others)f - public static readonly string WheelWizardAppdataPath = Path.Combine(IsPortableWhWz ? string.Empty : AppDataFolder, "CT-MKWII"); - public static readonly string WheelWizardConfigFilePath = Path.Combine(WheelWizardAppdataPath, "config.json"); - public static readonly string RrLaunchJsonFilePath = Path.Combine(WheelWizardAppdataPath, "RR.json"); - public static readonly string ModsFolderPath = Path.Combine(WheelWizardAppdataPath, "Mods"); - public static readonly string ModConfigFilePath = Path.Combine(ModsFolderPath, "modconfig.json"); - public static readonly string TempModsFolderPath = Path.Combine(ModsFolderPath, "Temp"); - public static readonly string RetroRewindTempFile = Path.Combine(TempModsFolderPath, "RetroRewind.zip"); - public static string RetroRewindVersionFile => Path.Combine(RetroRewind6FolderPath, "version.txt"); + // Wheel wizard's appdata paths (don't have to be expressions since they don't depend on user input like the others) + public static string WheelWizardAppdataPath + { + get + { + lock (WheelWizardAppdataLock) + { + return _wheelWizardAppdataOverride ?? DefaultWheelWizardAppdataPath; + } + } + } + + public static string DefaultWheelWizardAppdataFolderPath => DefaultWheelWizardAppdataPath; + public static bool IsUsingCustomWheelWizardAppdataPath + { + get + { + lock (WheelWizardAppdataLock) + { + return _wheelWizardAppdataOverride != null; + } + } + } + + public static string WheelWizardConfigFilePath => Path.Combine(WheelWizardAppdataPath, "config.json"); + public static string RrLaunchJsonFilePath => Path.Combine(WheelWizardAppdataPath, "RR.json"); + public static string ModsFolderPath => Path.Combine(WheelWizardAppdataPath, "Mods"); + public static string ModConfigFilePath => Path.Combine(ModsFolderPath, "modconfig.json"); + public static string TempModsFolderPath => Path.Combine(ModsFolderPath, "Temp"); + public static string RetroRewindTempFile => Path.Combine(TempModsFolderPath, "RetroRewind.zip"); public static string WiiDbFolder => Path.Combine(WiiFolderPath, "shared2", "menu", "FaceLib"); public static string MiiDbFile => Path.Combine(WiiDbFolder, "RFL_DB.dat"); + #region Wheel Wizard Appdata Override + + private static string? LoadSavedWheelWizardAppdataOverride() + { + try + { + var storedPath = LoadPersistedWheelWizardAppdataOverride(); + if (string.IsNullOrWhiteSpace(storedPath)) + return null; + + var normalized = FileHelper.NormalizePath(storedPath); + return FileHelper.PathsEqual(normalized, DefaultWheelWizardAppdataPath) ? null : normalized; + } + catch + { + return null; + } + } + + private static string? LoadPersistedWheelWizardAppdataOverride() + { +#if WINDOWS + if (OperatingSystem.IsWindows()) + { + try + { + using var key = Registry.CurrentUser.OpenSubKey(WindowsAppDataOverrideRegistryKeyPath, writable: false); + if (key != null) + { + var value = key.GetValue(WindowsAppDataOverrideRegistryValueName) as string; + if (!string.IsNullOrWhiteSpace(value)) + return value; + } + } + catch + { + // ignored; fall back to other persistence mechanisms + } + } +#endif + + if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux()) + { + var storedPath = FileHelper.ReadAllTextSafe(UnixAppDataOverrideFilePath); + if (!string.IsNullOrWhiteSpace(storedPath)) + return storedPath; + } + + return null; + } + + public static bool TrySetWheelWizardAppdataPath( + string requestedPath, + out string errorMessage, + out DirectoryMoveContentsResult moveResult, + IProgress? progress = null + ) + { + errorMessage = string.Empty; + moveResult = new DirectoryMoveContentsResult( + DirectoryMoveOutcome.NoOp, + string.Empty, + string.Empty, + copyAttempted: false, + verificationAttempted: false, + deleteSourceRequested: false, + sourceDeletionSucceeded: true + ); + + if ( + !TryValidateWheelWizardAppdataTarget( + requestedPath, + out var normalizedTarget, + out var currentPath, + out errorMessage, + out var requiresMove + ) + ) + return false; + + if (!requiresMove) + { + moveResult = new DirectoryMoveContentsResult( + DirectoryMoveOutcome.NoOp, + currentPath, + normalizedTarget, + copyAttempted: false, + verificationAttempted: false, + deleteSourceRequested: false, + sourceDeletionSucceeded: true + ); + return true; + } + + if (!FileHelper.DirectoryExists(normalizedTarget)) + { + try + { + FileHelper.EnsureDirectory(normalizedTarget); + } + catch (Exception ex) + { + errorMessage = $"Unable to create the selected folder: {ex.Message}"; + return false; + } + } + else if (!FileHelper.IsDirectoryEmpty(normalizedTarget)) + { + errorMessage = "The selected folder must be empty. Please choose an empty folder."; + return false; + } + + var newOverrideValue = FileHelper.PathsEqual(normalizedTarget, DefaultWheelWizardAppdataPath) ? null : normalizedTarget; + + try + { + moveResult = FileHelper.MoveDirectoryContents(currentPath, normalizedTarget, progress: progress); + } + catch (Exception ex) + { + errorMessage = $"Failed to move Wheel Wizard files: {ex.Message}"; + return false; + } + + switch (moveResult.Outcome) + { + case DirectoryMoveOutcome.CopyFailed: + errorMessage = moveResult.ErrorMessage ?? "Failed to copy Wheel Wizard files."; + return false; + case DirectoryMoveOutcome.VerificationFailed: + var failureMessage = moveResult.ErrorMessage ?? "Failed to verify Wheel Wizard files."; + if (moveResult.VerificationFailures.Count > 0) + failureMessage += $"\n{string.Join("\n", moveResult.VerificationFailures)}"; + errorMessage = failureMessage; + return false; + case DirectoryMoveOutcome.NoOp: + case DirectoryMoveOutcome.Success: + case DirectoryMoveOutcome.SourceDeletionFailed: + break; + default: + errorMessage = "Unknown outcome while moving Wheel Wizard files."; + return false; + } + + // Update the setting even if persistence fails, since files were successfully moved or intentionally skipped + lock (WheelWizardAppdataLock) + { + _wheelWizardAppdataOverride = newOverrideValue; + } + + try + { + PersistWheelWizardAppdataOverride(newOverrideValue); + } + catch (Exception ex) + { + // Log the persistence failure but don't fail the operation since files are already moved + // and the in-memory setting is updated + errorMessage = $"Warning: Files were moved successfully, but failed to persist the setting: {ex.Message}"; + // Still return true since the operation succeeded where it matters + } + + if (moveResult.Outcome == DirectoryMoveOutcome.SourceDeletionFailed) + { + var deletionWarning = + moveResult.ErrorMessage + ?? $"Files were moved successfully, but the old folder '{currentPath}' could not be deleted. You may need to remove it manually."; + + errorMessage = string.IsNullOrEmpty(errorMessage) ? deletionWarning : $"{errorMessage} {deletionWarning}"; + } + + return true; + } + + public static bool TryResetWheelWizardAppdataPath(out string errorMessage) => + TrySetWheelWizardAppdataPath(DefaultWheelWizardAppdataPath, out errorMessage, out _); + + public static bool TryRevertWheelWizardAppdataMove(string previousPath, string newPath, out string errorMessage) + { + errorMessage = string.Empty; + + string normalizedPrevious; + string normalizedNew; + + try + { + normalizedPrevious = FileHelper.NormalizePath(previousPath); + normalizedNew = FileHelper.NormalizePath(newPath); + } + catch (Exception ex) + { + errorMessage = $"Invalid folder path: {ex.Message}"; + return false; + } + + var previousOverrideValue = FileHelper.PathsEqual(normalizedPrevious, DefaultWheelWizardAppdataPath) ? null : normalizedPrevious; + + lock (WheelWizardAppdataLock) + { + _wheelWizardAppdataOverride = previousOverrideValue; + } + + try + { + PersistWheelWizardAppdataOverride(previousOverrideValue); + } + catch (Exception ex) + { + errorMessage = $"Failed to persist Wheel Wizard data folder setting: {ex.Message}"; + return false; + } + + if (FileHelper.DirectoryExists(normalizedNew)) + { + try + { + Directory.Delete(normalizedNew, true); + } + catch (Exception ex) + { + errorMessage = $"Reverted to the previous folder, but failed to remove the new folder '{normalizedNew}': {ex.Message}"; + return false; + } + } + + return true; + } + + public static bool TryCleanupPartialWheelWizardAppdataMove(string destinationPath, out string errorMessage) + { + errorMessage = string.Empty; + + string normalizedDestination; + try + { + normalizedDestination = FileHelper.NormalizePath(destinationPath); + } + catch (Exception ex) + { + errorMessage = $"Invalid folder path: {ex.Message}"; + return false; + } + + if (!FileHelper.DirectoryExists(normalizedDestination)) + return true; + + try + { + Directory.Delete(normalizedDestination, true); + } + catch (Exception ex) + { + errorMessage = $"Failed to remove the partially copied folder '{normalizedDestination}': {ex.Message}"; + return false; + } + + return true; + } + + public static bool TryValidateWheelWizardAppdataTarget( + string requestedPath, + out string normalizedTarget, + out string currentPath, + out string errorMessage, + out bool requiresMove + ) + { + normalizedTarget = string.Empty; + currentPath = string.Empty; + errorMessage = string.Empty; + requiresMove = false; + + if (string.IsNullOrWhiteSpace(requestedPath)) + { + errorMessage = "Please select a valid folder."; + return false; + } + + try + { + normalizedTarget = FileHelper.NormalizePath(requestedPath); + } + catch (Exception ex) + { + errorMessage = $"Invalid folder path: {ex.Message}"; + return false; + } + + lock (WheelWizardAppdataLock) + { + currentPath = _wheelWizardAppdataOverride ?? DefaultWheelWizardAppdataPath; + } + + if (FileHelper.PathsEqual(currentPath, normalizedTarget)) + return true; + + if (FileHelper.IsDescendantPath(normalizedTarget, currentPath)) + { + errorMessage = "The selected folder is inside the current Wheel Wizard data folder. Please choose a different folder."; + return false; + } + + if (FileHelper.IsDescendantPath(currentPath, normalizedTarget)) + { + errorMessage = "The selected folder contains the current Wheel Wizard data folder. Please choose a different folder."; + return false; + } + + if (FileHelper.FileExists(normalizedTarget)) + { + errorMessage = "The selected path points to a file. Please choose an empty folder instead."; + return false; + } + + if (FileHelper.IsRootDirectory(normalizedTarget)) + { + errorMessage = "Selecting a drive or root directory is not allowed. Please choose an empty folder."; + return false; + } + + if (FileHelper.DirectoryExists(normalizedTarget) && !FileHelper.IsDirectoryEmpty(normalizedTarget)) + { + errorMessage = "The selected folder must be empty. Please choose an empty folder."; + return false; + } + + requiresMove = true; + return true; + } + + private static void PersistWheelWizardAppdataOverride(string? overridePath) + { + if (string.IsNullOrWhiteSpace(overridePath) || FileHelper.PathsEqual(overridePath, DefaultWheelWizardAppdataPath)) + { + ClearWheelWizardAppdataOverride(); + return; + } + + try + { + var normalizedOverride = FileHelper.NormalizePath(overridePath); + SaveWheelWizardAppdataOverride(normalizedOverride); + } + catch + { + // ignored + } + } + + private static void ClearWheelWizardAppdataOverride() + { +#if WINDOWS + if (OperatingSystem.IsWindows()) + { + try + { + using var key = Registry.CurrentUser.OpenSubKey(WindowsAppDataOverrideRegistryKeyPath, writable: true); + key?.DeleteValue(WindowsAppDataOverrideRegistryValueName, throwOnMissingValue: false); + } + catch + { + // ignored + } + + return; + } +#endif + + if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux()) + { + TryDeleteFileSilently(UnixAppDataOverrideFilePath); + } + } + + private static void SaveWheelWizardAppdataOverride(string overridePath) + { +#if WINDOWS + if (OperatingSystem.IsWindows()) + { + try + { + using var key = Registry.CurrentUser.CreateSubKey(WindowsAppDataOverrideRegistryKeyPath, writable: true); + key?.SetValue(WindowsAppDataOverrideRegistryValueName, overridePath, RegistryValueKind.String); + return; + } + catch + { + // Fall back to other persistence mechanisms + } + } +#endif + + if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux()) + { + FileHelper.WriteAllTextSafe(UnixAppDataOverrideFilePath, overridePath); + } + } + + private static void TryDeleteFileSilently(string path) + { + try + { + if (FileHelper.FileExists(path)) + File.Delete(path); + } + catch + { + // ignored + } + } + + private static DirectoryMoveContentsResult MoveWheelWizardAppdataContents(string sourcePath, string destinationPath) => + FileHelper.MoveDirectoryContents(sourcePath, destinationPath); + + #endregion + //In case it is unclear, the mods folder is a folder with mods that are desired to be installed (if enabled) //When launching we want to move the mods from the Mods folder to the MyStuff folder since that is the folder the game uses //Also remember that mods may not be in a subfolder, all mod files must be located in /MyStuff directly // Helper paths for folders used across multiple files - public static string MyStuffFolderPath => Path.Combine(RetroRewind6FolderPath, "MyStuff"); + + //todo: before we can actually add more distributions, we will have to rewrite the MyStuff as a service aswell + public static string MyStuffFolderPath => Path.Combine(RiivolutionWhWzFolderPath, "RetroRewind6", "MyStuff"); public static string GetModDirectoryPath(string modName) => Path.Combine(ModsFolderPath, modName); public static string RiivolutionWhWzFolderPath => Path.Combine(LoadFolderPath, "Riivolution", "WheelWizard"); - public static string RetroRewind6FolderPath => Path.Combine(RiivolutionWhWzFolderPath, "RetroRewind6"); + + // public static string RetroRewind6FolderPath => Path.Combine(RiivolutionWhWzFolderPath, "RetroRewind6"); // This is not the folder your save file is located in, but its the folder where every Region folder is, so the save file is in SaveFolderPath/Region public static string SaveFolderPath => Path.Combine(RiivolutionWhWzFolderPath, "riivolution", "save", "RetroWFC"); - public static string XmlFilePath => Path.Combine(RiivolutionWhWzFolderPath, "riivolution", "RetroRewind6.xml"); + public static string RiivolutionXmlFolderPath => Path.Combine(RiivolutionWhWzFolderPath, "riivolution"); + public static string XmlFilePath => Path.Combine(RiivolutionXmlFolderPath, "RetroRewind6.xml"); private static string PortableUserFolderPath => Path.Combine(GetDolphinExeDirectory(), RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "user" : "User"); @@ -136,7 +595,14 @@ public static bool IsLinuxDolphinConfigSplit() public static string LoadFolderPath { - get { return Path.Combine(UserFolderPath, "Load"); } + get + { + if (SettingsManager.LOAD_PATH.IsValid()) + { + return (string)SettingsManager.LOAD_PATH.Get(); + } + return Path.Combine(UserFolderPath, "Load"); + } } public static string ConfigFolderPath @@ -147,7 +613,7 @@ public static string ConfigFolderPath { try { - string determinedLinuxDolphinConfigDir = SplitLinuxDolphinConfigDir; + var determinedLinuxDolphinConfigDir = SplitLinuxDolphinConfigDir; if (!string.IsNullOrWhiteSpace(determinedLinuxDolphinConfigDir)) return determinedLinuxDolphinConfigDir; } @@ -163,7 +629,14 @@ public static string ConfigFolderPath public static string WiiFolderPath { - get { return Path.Combine(UserFolderPath, "Wii"); } + get + { + if (SettingsManager.NAND_ROOT_PATH.IsValid()) + { + return (string)SettingsManager.NAND_ROOT_PATH.Get(); + } + return Path.Combine(UserFolderPath, "Wii"); + } } public static bool IsFlatpakDolphinFilePath(string filePath) @@ -173,15 +646,18 @@ public static bool IsFlatpakDolphinFilePath(string filePath) // Prioritize Flatpak Dolphin installation if no file path has been saved yet, so return true return true; } - string flatpakRunCommand = "flatpak run"; - string dolphinAppId = "org.DolphinEmu.dolphin-emu"; + var flatpakRunCommand = "flatpak run"; + var dolphinAppId = "org.DolphinEmu.dolphin-emu"; string[] possibleFlatpakDolphinCommands = [ $"{flatpakRunCommand} {dolphinAppId}", $"{flatpakRunCommand} --system {dolphinAppId}", $"{flatpakRunCommand} --user {dolphinAppId}", + $"{flatpakRunCommand} -p {dolphinAppId}", + $"{flatpakRunCommand} --system -p {dolphinAppId}", + $"{flatpakRunCommand} --user -p {dolphinAppId}", ]; - foreach (string possibleFlatpakDolphinCommand in possibleFlatpakDolphinCommands) + foreach (var possibleFlatpakDolphinCommand in possibleFlatpakDolphinCommands) { if (possibleFlatpakDolphinCommand.Equals(filePath, StringComparison.Ordinal)) return true; @@ -198,7 +674,7 @@ private static string GetContainingBaseDirectorySafe(string path) { try { - return Path.GetDirectoryName(Path.GetFullPath(path)); + return Path.GetDirectoryName(Path.GetFullPath(path)) ?? string.Empty; } catch { @@ -216,33 +692,31 @@ private static bool HasWindowsLocalUserConfigSet() #if WINDOWS try { - string dolphinRegistryPath = @"Software\Dolphin Emulator"; - string localUserConfigValueName = "LocalUserConfig"; - bool local = false; - using (RegistryKey key = Registry.CurrentUser.OpenSubKey(dolphinRegistryPath)) + var dolphinRegistryPath = @"Software\Dolphin Emulator"; + var localUserConfigValueName = "LocalUserConfig"; + var local = false; + using var key = Registry.CurrentUser.OpenSubKey(dolphinRegistryPath); + if (key == null) + return local; + + var localUserConfigValue = key.GetValue(localUserConfigValueName); + if (localUserConfigValue == null) + return local; + + if (localUserConfigValue is string localUserConfigValueString) { - if (key == null) - return local; - - object localUserConfigValue = key.GetValue(localUserConfigValueName); - if (localUserConfigValue == null) - return local; - - if (localUserConfigValue is string localUserConfigValueString) - { - if (localUserConfigValueString.Equals("1", StringComparison.Ordinal)) - local = true; - } - else if (localUserConfigValue is int localUserConfigValueInt) - { - if (localUserConfigValueInt == 1) - local = true; - } - else if (localUserConfigValue is long localUserConfigValueLong) - { - if (localUserConfigValueLong == 1) - local = true; - } + if (localUserConfigValueString.Equals("1", StringComparison.Ordinal)) + local = true; + } + else if (localUserConfigValue is int localUserConfigValueInt) + { + if (localUserConfigValueInt == 1) + local = true; + } + else if (localUserConfigValue is long localUserConfigValueLong) + { + if (localUserConfigValueLong == 1) + local = true; } return local; } @@ -260,22 +734,20 @@ private static string TryFindRegistryUserConfigPath() #if WINDOWS try { - string dolphinRegistryPath = @"Software\Dolphin Emulator"; - string userConfigPathValueName = "UserConfigPath"; - string userConfigPath = string.Empty; - using (RegistryKey key = Registry.CurrentUser.OpenSubKey(dolphinRegistryPath)) - { - if (key == null) - return userConfigPath; - - string foundUserConfigPath = (string)key.GetValue(userConfigPathValueName); - // We need to replace `/` with `\` here since Dolphin writes mismatching separators to the registry - if (FileHelper.DirectoryExists(foundUserConfigPath)) - userConfigPath = foundUserConfigPath.Replace( - Path.AltDirectorySeparatorChar.ToString(), - Path.DirectorySeparatorChar.ToString() - ); - } + var dolphinRegistryPath = @"Software\Dolphin Emulator"; + var userConfigPathValueName = "UserConfigPath"; + var userConfigPath = string.Empty; + using var key = Registry.CurrentUser.OpenSubKey(dolphinRegistryPath); + if (key == null) + return userConfigPath; + + var foundUserConfigPath = key.GetValue(userConfigPathValueName) as string; + // We need to replace `/` with `\` here since Dolphin writes mismatching separators to the registry + if (!string.IsNullOrWhiteSpace(foundUserConfigPath) && FileHelper.DirectoryExists(foundUserConfigPath)) + userConfigPath = foundUserConfigPath.Replace( + Path.AltDirectorySeparatorChar.ToString(), + Path.DirectorySeparatorChar.ToString() + ); return userConfigPath; } catch diff --git a/WheelWizard/Services/Settings/SettingsManager.cs b/WheelWizard/Services/Settings/SettingsManager.cs index 9baa77af..ff5ab985 100644 --- a/WheelWizard/Services/Settings/SettingsManager.cs +++ b/WheelWizard/Services/Settings/SettingsManager.cs @@ -2,7 +2,6 @@ using WheelWizard.Helpers; using WheelWizard.Models.Enums; using WheelWizard.Models.Settings; -using WheelWizard.Services.Other; namespace WheelWizard.Services.Settings; @@ -27,14 +26,6 @@ public class SettingsManager // inside the data directory and not use the XDG config directory, leading to two different configs). if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && PathManager.IsLinuxDolphinConfigSplit()) { - // Only verify the folders in this situation since Load/Wii and Config are split - string[] requiredSubdirectories = [PathManager.LoadFolderPath, PathManager.ConfigFolderPath, PathManager.WiiFolderPath]; - foreach (string requiredSubdirectory in requiredSubdirectories) - { - if (!FileHelper.DirectoryExists(requiredSubdirectory)) - return false; - } - // In this case, Dolphin would use `EMBEDDED_USER_DIR` which is the portable `user` directory // in the current directory (the directory of the WheelWizard executable). // This means a split dolphin user folder and config cannot work... @@ -77,7 +68,7 @@ public class SettingsManager return false; } } - return FileHelper.FileExists(pathOrCommand) || EnvHelper.IsValidUnixCommand(pathOrCommand); + return EnvHelper.IsValidUnixCommand(pathOrCommand); } return FileHelper.FileExists(pathOrCommand); @@ -97,21 +88,22 @@ public class SettingsManager public static Setting SAVED_WINDOW_SCALE = new WhWzSetting(typeof(double), "WindowScale", 1.0).SetValidation(value => (double)(value ?? -1) >= 0.5 && (double)(value ?? -1) <= 2.0 ); - public static Setting RR_LANGUAGE = new WhWzSetting(typeof(int), "RR_Language", 0).SetValidation(value => - SettingValues.RrLanguages.ContainsKey((int)(value ?? -1)) - ); public static Setting REMOVE_BLUR = new WhWzSetting(typeof(bool), "REMOVE_BLUR", true); - public static Setting RR_REGION = new WhWzSetting( - typeof(MarioKartWiiEnums.Regions), - "RR_Region", - RRRegionManager.GetValidRegions().First() - ); + public static Setting RR_REGION = new WhWzSetting(typeof(MarioKartWiiEnums.Regions), "RR_Region", MarioKartWiiEnums.Regions.None); public static Setting WW_LANGUAGE = new WhWzSetting(typeof(string), "WW_Language", "en").SetValidation(value => SettingValues.WhWzLanguages.ContainsKey((string)value!) ); #endregion #region Dolphin Settings + public static Setting NAND_ROOT_PATH = new DolphinSetting(typeof(string), ("Dolphin.ini", "General", "NANDRootPath"), "").SetValidation( + value => Directory.Exists(value as string ?? string.Empty) + ); + + public static Setting LOAD_PATH = new DolphinSetting(typeof(string), ("Dolphin.ini", "General", "LoadPath"), "").SetValidation(value => + Directory.Exists(value as string ?? string.Empty) + ); + public static Setting VSYNC = new DolphinSetting(typeof(bool), ("GFX.ini", "Hardware", "VSync"), false); public static Setting INTERNAL_RESOLUTION = new DolphinSetting( typeof(int), diff --git a/WheelWizard/Services/Storage/CustomFilePickerFileType.cs b/WheelWizard/Services/Storage/CustomFilePickerFileType.cs index 4bdf0e9a..28f9d808 100644 --- a/WheelWizard/Services/Storage/CustomFilePickerFileType.cs +++ b/WheelWizard/Services/Storage/CustomFilePickerFileType.cs @@ -13,4 +13,11 @@ public static class CustomFilePickerFileType AppleUniformTypeIdentifiers = ["com.wheelwizard.mods"], // Honestly no idea how it works MimeTypes = ["application/mods"], // Honestly no idea how it works }; + public static FilePickerFileType Miis { get; } = + new("Miis") + { + Patterns = ["*.mii", "*.miigx", "*.mae"], + AppleUniformTypeIdentifiers = ["com.wheelwizard.miis"], + MimeTypes = ["application/miis"], + }; } diff --git a/WheelWizard/Services/Storage/FilePickerHelper.cs b/WheelWizard/Services/Storage/FilePickerHelper.cs index fc1a00bc..9d9570af 100644 --- a/WheelWizard/Services/Storage/FilePickerHelper.cs +++ b/WheelWizard/Services/Storage/FilePickerHelper.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.IO; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; @@ -34,7 +35,8 @@ public static async Task> OpenFilePickerAsync( var selectedFiles = await storageProvider.MainWindow.StorageProvider.OpenFilePickerAsync(options); - return selectedFiles?.Select(file => file.Path.LocalPath).ToList() ?? []; + return selectedFiles?.Select(TryResolveLocalPath).Where(path => !string.IsNullOrWhiteSpace(path)).Select(path => path!).ToList() + ?? []; } public static async Task OpenSingleFileAsync(string title, IEnumerable fileTypes) @@ -56,7 +58,17 @@ public static async Task> OpenFilePickerAsync( } ); - return files?.FirstOrDefault()?.Path.LocalPath; + if (files == null) + return null; + + foreach (var file in files) + { + var path = TryResolveLocalPath(file); + if (!string.IsNullOrWhiteSpace(path)) + return path; + } + + return null; } public static async Task> OpenMultipleFilesAsync(string title, IEnumerable fileTypes) @@ -78,7 +90,7 @@ public static async Task> OpenMultipleFilesAsync(string title, IEnu } ); - return files?.Select(file => file.Path.LocalPath).ToList() ?? []; + return files?.Select(TryResolveLocalPath).Where(path => !string.IsNullOrWhiteSpace(path)).Select(path => path!).ToList() ?? []; } public static async Task> SelectFolderAsync(string title, IStorageFolder? suggestedStartLocation = null) @@ -158,6 +170,48 @@ public static void OpenFolderInFileManager(string folderPath) } ); - return file?.Path.LocalPath; + if (file == null) + return null; + + return TryResolveLocalPath(file); + } + + public static string? TryResolveLocalPath(IStorageItem? item) + { + if (item == null) + return null; + + try + { + var localPath = item.TryGetLocalPath(); + if (!string.IsNullOrWhiteSpace(localPath)) + return localPath; + } + catch + { + // Some platforms might throw if local paths are unsupported; ignore and fall back to URI inspection. + } + + var uri = item.Path; + if (uri != null) + { + if (uri.IsAbsoluteUri) + { + try + { + return uri.LocalPath; + } + catch (InvalidOperationException) + { + // Ignore and fall through to raw string handling. + } + } + + var raw = uri.ToString(); + if (!string.IsNullOrWhiteSpace(raw) && Path.IsPathRooted(raw)) + return raw; + } + + return null; } } diff --git a/WheelWizard/SetupExtensions.cs b/WheelWizard/SetupExtensions.cs index 77086d9c..47f44225 100644 --- a/WheelWizard/SetupExtensions.cs +++ b/WheelWizard/SetupExtensions.cs @@ -4,6 +4,8 @@ using Testably.Abstractions; using WheelWizard.AutoUpdating; using WheelWizard.Branding; +using WheelWizard.CustomCharacters; +using WheelWizard.CustomDistributions; using WheelWizard.GameBanana; using WheelWizard.GitHub; using WheelWizard.MiiImages; @@ -11,6 +13,7 @@ using WheelWizard.Shared.Services; using WheelWizard.WheelWizardData; using WheelWizard.WiiManagement; +using WheelWizard.WiiManagement.MiiManagement; namespace WheelWizard; @@ -22,6 +25,7 @@ public static class SetupExtensions public static void AddWheelWizardServices(this IServiceCollection services) { // Features + services.AddCustomCharacters(); services.AddAutoUpdating(); services.AddBranding(); services.AddGitHub(); @@ -30,6 +34,7 @@ public static void AddWheelWizardServices(this IServiceCollection services) services.AddWiiManagement(); services.AddGameBanana(); services.AddMiiImages(); + services.AddCustomDistributionService(); // IO Abstractions services.AddSingleton(); diff --git a/WheelWizard/Shared/MessageTranslations/MessageTranslationHelper.cs b/WheelWizard/Shared/MessageTranslations/MessageTranslationHelper.cs new file mode 100644 index 00000000..af332637 --- /dev/null +++ b/WheelWizard/Shared/MessageTranslations/MessageTranslationHelper.cs @@ -0,0 +1,239 @@ +using WheelWizard.Helpers; +using WheelWizard.Resources.Languages; +using WheelWizard.Views.Popups.Generic; + +namespace WheelWizard.Shared.MessageTranslations; + +// MAKE SURE EVERY ENUM VALUE HAS A VALUE +// 0xxx = Successes +// 1xxx = Warnings (without tag) +// 2xxx = Warnings (with tag) +// 3xxx = Errors +// ANY OTHER VALUE BELOW 1000 OR ABOVE 3999 is not valid + +// When determining if something is an error or a warning, keep in mind the following questions: +// - Is it the user's fault? (e.g. the user entering a wrong path, that's their fault) +// - Is this something that we as WhWz can do anything about? (e.g. we can't do anything about internet related issues, but a missing file that we should have created is our fault) +// If any of the questions above can be answered with "yes", then it is a warning, otherwise it is an error. + +// Note that you can safely re-organize the actual enum numbers. They might be serialized in the future, or screenshots might be taken from the numbers. So those will then be outdated, but thats not a problem +// as longs as wheel wizard it-self never reads and converts the numbers back to the enum values. +public enum MessageTranslation +{ + #region Successes + + Success_StanderdSuccess = 0000, + Success_PathSettingsSaved = 0001, + + #endregion + + #region Warnings + + // Warning starting with 1 have NO error code displayed, once starting with 2 DO HAVE an error code displayed, like the tag + // If warning makes sense on its own what the user did wrong, then no tag is needed (1xxx) + Warning_StanderdWarning = 2000, + Warning_InvalidPathSettings = 1001, + Warning_UnkownRendererSelected = 2002, + Warning_CouldNotFindRoom = 2003, + Warning_CantDeleteFavMii = 1004, + Warning_CantViewMod_NotFromBrowser = 1005, + Warning_CantViewMod_SomethingWrong = 2006, + Warning_NoMiisFound = 1007, + Warning_DolphinNotFound = 2008, + Warning_ModNameCantEmpty = 1009, + Warning_ModNameInvalid = 1010, + Warning_UnableToDownloadMod_Files = 1011, + + #endregion + + #region Errors + + // 30xx = random errors + // 31xx = Mii (related) Errors + // - 310x = Mii Repository/DB Error + // - 311x = Mii Serializer Error + // - 312x = Mii Editor + // 32xx = External program Errors + // - 320x = Dolphin related Errors + Error_StanderdError = 3000, + Error_ModDownloadFailed = 3001, + + Error_MiiDBAlreadyExists = 3100, + Error_UpdateMiiDb_InvalidClId = 3101, + Error_UpdateMiiDb_BlockSizeInvalid = 3102, + Error_UpdateMiiDb_NoBlockFound = 3103, + Error_UpdateMiiDb_MiiNotFound = 3104, + Error_UpdateMiiDb_InvalidMac = 3105, + Error_UpdateMiiDb_RFLdbNotFound = 3106, + Error_UpdateMiiDb_CorruptDb = 3107, + + Error_MiiSerializer_MiiNotNull = 3110, + Error_MiiSerializer_MiiId0 = 3111, + Error_MiiSerializer_MiiDataLength = 3112, + Error_MiiSerializer_MiiDataEmpty = 3113, + Error_MiiSerializer_InvalidMiiData = 3114, + Error_MiiEditor_CantOpenEditor = 3120, + + Error_FailedCopyMii = 3200, + Error_FailedInstallDolphin = 3201, + + #endregion +} + +public static class MessageTranslationHelper +{ + /// + /// Returns the translation related to this message enum + /// + /// (Title, additional information) + public static (string, string?) GetTranslationText(MessageTranslation msg) + { + return msg switch + { + #region Successes + + MessageTranslation.Success_StanderdSuccess => ("Success", "Completed successfully!"), + MessageTranslation.Success_PathSettingsSaved => ( + Phrases.MessageSuccess_SettingsSaved_Title, + Phrases.MessageSuccess_SettingsSaved_Title + ), + + #endregion + + #region Warnings + + MessageTranslation.Warning_StanderdWarning => ("Warning", "Something went wrong!"), + MessageTranslation.Warning_InvalidPathSettings => ( + Phrases.MessageWarning_InvalidPaths_Title, + Phrases.MessageWarning_InvalidPaths_Extra + ), + MessageTranslation.Warning_UnkownRendererSelected => ("Unknown renderer selected", "Unknown renderer selected: {$1}"), + MessageTranslation.Warning_CouldNotFindRoom => ( + "Couldn't find the room", + "Whoops, could not find the room that this player is supposedly playing in" + ), + MessageTranslation.Warning_CantDeleteFavMii => ( + Phrases.MessageWarning_CannotDeleteFavMii_Title, + Phrases.MessageWarning_CannotDeleteFavMii_Extra + ), + + MessageTranslation.Warning_CantViewMod_SomethingWrong => ( + Phrases.MessageWarning_CantViewMod_Title, + Phrases.MessageWarning_CantViewMod_Extra_SomethingElse + ), + MessageTranslation.Warning_CantViewMod_NotFromBrowser => ( + Phrases.MessageWarning_CantViewMod_Title, + Phrases.MessageWarning_CantViewMod_Extra_NotFromBrowser + ), + MessageTranslation.Warning_NoMiisFound => ("No Miis Found", "There are no other Miis available to select."), + MessageTranslation.Warning_DolphinNotFound => ( + Phrases.MessageWarning_DolphinNotFound_Title, + Phrases.MessageWarning_DolphinNotFound_Extra + ), + MessageTranslation.Warning_ModNameCantEmpty => ( + Phrases.MessageWarning_ModNameEmpty_Title, + Phrases.MessageWarning_ModNameEmpty_Extra + ), + MessageTranslation.Warning_ModNameInvalid => ( + Phrases.MessageWarning_ModNameInvalid_Title, + Phrases.MessageWarning_ModNameInvalid_Extra + ), + MessageTranslation.Warning_UnableToDownloadMod_Files => ( + Phrases.MessageWarning_UnableDownloadMod_Title, + Phrases.MessageWarning_UnableDownloadMod_Extra + ), + + #endregion + + #region Errors + + MessageTranslation.Error_StanderdError => ("Standard Error", "Something went wrong!"), + MessageTranslation.Error_ModDownloadFailed => ( + Phrases.MessageError_ModDownloadFail_Title, + Phrases.MessageError_ModDownloadFail_Extra + ), + MessageTranslation.Error_FailedCopyMii => ("Failed to copy Mii", "{$1}"), + MessageTranslation.Error_MiiDBAlreadyExists => (Phrases.MessageError_FailedCreateMiiDb_Title, "Database already exists."), + MessageTranslation.Error_UpdateMiiDb_InvalidClId => ("Invalid Client ID.", "The client ID attached to this Mii is invalid."), + MessageTranslation.Error_UpdateMiiDb_BlockSizeInvalid => ("Mii block size invalid.", null), + MessageTranslation.Error_UpdateMiiDb_NoBlockFound => ("Mii block not found.", null), + MessageTranslation.Error_UpdateMiiDb_MiiNotFound => ("Mii not found", null), + MessageTranslation.Error_UpdateMiiDb_InvalidMac => ("Invalid MAC Address", "The MAC attached to this Mii is invalid."), + MessageTranslation.Error_UpdateMiiDb_RFLdbNotFound => ("RFL_DB.dat not found", "The RFL_DB.dat file could not be found."), + MessageTranslation.Error_UpdateMiiDb_CorruptDb => ( + "Corrupt Mii Database", + "Corrupt Mii database (bad CRC 0x{$1}, expected 0x{$2})." + ), + MessageTranslation.Error_MiiSerializer_MiiNotNull => ("Mii cannot be null", null), + MessageTranslation.Error_MiiSerializer_MiiId0 => ("Mii ID cannot be 0", null), + MessageTranslation.Error_MiiSerializer_MiiDataLength => ("Invalid Mii data length.", null), + MessageTranslation.Error_MiiSerializer_MiiDataEmpty => ("Mii data is empty.", null), + MessageTranslation.Error_MiiSerializer_InvalidMiiData => ("Invalid Mii data", "The Mii '{$1}' is invalid."), + + MessageTranslation.Error_MiiEditor_CantOpenEditor => ("Cant open Mii Editor", "{$1}"), + + MessageTranslation.Error_FailedInstallDolphin => ( + Phrases.MessageError_FailedInstallDolphin_Title, + Phrases.MessageError_FailedInstallDolphin_Extra + ), + + #endregion + + _ => ("Message", $"Unknown translation for: {msg.ToString()}"), + }; + } + + #region Base Stuff + + /// + /// Shows a message box with the given message enum. + /// + public static void ShowMessage(MessageTranslation msg, object[]? titleReplacements = null, object[]? extraReplacements = null) => + CreateMessageBox(msg, titleReplacements, extraReplacements).Show(); + + public static void ShowMessage(OperationError error) => CreateMessageBox(error).Show(); + + public static Task AwaitMessageAsync(MessageTranslation msg, object[]? titleReplacements = null, object[]? extraReplacements = null) => + CreateMessageBox(msg, titleReplacements, extraReplacements).ShowDialog(); + + public static Task AwaitMessageAsync(OperationError error) => CreateMessageBox(error).ShowDialog(); + + private static MessageBoxWindow CreateMessageBox( + MessageTranslation msg, + object[]? titleReplacements = null, + object[]? extraReplacements = null + ) + { + var (title, extraText) = GetTranslationText(msg); + var type = + (int)msg < 1000 ? MessageBoxWindow.MessageType.Message + : (int)msg < 3000 ? MessageBoxWindow.MessageType.Warning + : MessageBoxWindow.MessageType.Error; + var box = new MessageBoxWindow() + .SetMessageType(type) + .SetTitleText(Humanizer.ReplaceDynamic(title, titleReplacements ?? []) ?? title); + if (extraText == null) + box.SetInfoText(Humanizer.ReplaceDynamic(title, titleReplacements ?? []) ?? title); + else + box.SetInfoText(Humanizer.ReplaceDynamic(extraText, extraReplacements ?? []) ?? extraText); + + if ((int)msg >= 2000) + box.SetTag($"{(int)msg}"); + + return box; + } + + private static MessageBoxWindow CreateMessageBox(OperationError error) + { + if (error.MessageTranslation != null) + return CreateMessageBox((MessageTranslation)error.MessageTranslation, error.TitleReplacements, error.ExtraReplacements); + + return new MessageBoxWindow() + .SetMessageType(MessageBoxWindow.MessageType.Error) + .SetTag("Unk") + .SetTitleText(Phrases.MessageError_GenericError_Title) + .SetInfoText(error.Message); + } + + #endregion +} diff --git a/WheelWizard/Shared/OperationResult/OperationError.cs b/WheelWizard/Shared/OperationResult/OperationError.cs index 134f30f8..5e14dc89 100644 --- a/WheelWizard/Shared/OperationResult/OperationError.cs +++ b/WheelWizard/Shared/OperationResult/OperationError.cs @@ -1,4 +1,6 @@ -namespace WheelWizard.Shared; +using WheelWizard.Shared.MessageTranslations; + +namespace WheelWizard.Shared; /// /// Represents an error that occurred during an operation. @@ -15,6 +17,56 @@ public class OperationError /// public Exception? Exception { get; init; } + /// + /// The translation applied to this error result for better visualization in the UI. + /// + public MessageTranslation? MessageTranslation { get; set; } + + /// + /// The objects to replace the keys in the translation title + /// + public object[]? TitleReplacements { get; set; } + + /// + /// The objects to replace the keys in the translation extra information + /// + public object[]? ExtraReplacements { get; set; } + + // Note that the MessageTranslation can NOT be used to retrieve the message. + // This is because the translation fo the MessageTranslation is localized, while the actual Message MUST be in English. + + /// + /// Creates a new instance of the class that indicates success. + /// + /// The error that occurred during the operation. + /// The type of the value. + /// A new instance of the class. + public static OperationError Fail(string error) => error; + + public static OperationError Fail(Exception error) => error; + + /// + /// Creates a new instance of the class that indicates success. + /// + /// The error that occurred during the operation. + /// The translation for this specific error. + /// The objects to replace the keys in the translation title + /// The objects to replace the keys in the translation extra info + /// The type of the value. + /// A new instance of the class. + public static OperationError Fail( + OperationError error, + MessageTranslation? translation, + object[]? titleReplacements = null, + object[]? extraReplacements = null + ) + { + error.MessageTranslation = translation; + error.TitleReplacements = titleReplacements; + error.ExtraReplacements = extraReplacements; + return error; + } + #region Implicit Operators public static implicit operator OperationError(string errorMessage) => new() { Message = errorMessage }; diff --git a/WheelWizard/Shared/OperationResult/OperationResult.cs b/WheelWizard/Shared/OperationResult/OperationResult.cs index 96c82213..cf8548cd 100644 --- a/WheelWizard/Shared/OperationResult/OperationResult.cs +++ b/WheelWizard/Shared/OperationResult/OperationResult.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using WheelWizard.Shared.MessageTranslations; namespace WheelWizard.Shared; @@ -43,27 +44,12 @@ public OperationResult(OperationError error) #region Creation Methods - /// - /// Creates a new instance of the class with the specified error. - /// - /// The error that occurred during the operation. - /// A new instance of the class. - public static OperationResult Fail(OperationError error) => new(error); - /// /// Creates a new instance of the class that indicates success. /// /// A new instance of the class. public static OperationResult Ok() => new(); - /// - /// Creates a new instance of the class that indicates success. - /// - /// The error that occurred during the operation. - /// The type of the value. - /// A new instance of the class. - public static OperationResult Fail(OperationError error) => new(error); - /// /// Creates a new instance of the class that indicates success. /// @@ -78,9 +64,10 @@ public OperationResult(OperationError error) /// /// The function to execute. /// The error message to return if the function fails. + /// The translation for this specific error. /// The type of the value. /// An that indicates the result of the operation. - public static OperationResult TryCatch(Func func, string? errorMessage = null) + public static OperationResult TryCatch(Func func, string? errorMessage = null, MessageTranslation? translation = null) { try { @@ -89,7 +76,14 @@ public static OperationResult TryCatch(Func func, string? errorMessage } catch (Exception ex) { - return Fail(new() { Message = errorMessage ?? ex.Message, Exception = ex }); + return new OperationError() + { + Message = errorMessage ?? ex.Message, + Exception = ex, + MessageTranslation = translation, + TitleReplacements = [ex.Message], + ExtraReplacements = [ex.Message], + }; } } @@ -99,9 +93,14 @@ public static OperationResult TryCatch(Func func, string? errorMessage /// /// The function to execute. /// The error message to return if the function fails. + /// The translation for this specific error. /// The type of the value. /// An that indicates the result of the operation. - public static async Task> TryCatch(Func> func, string? errorMessage = null) + public static async Task> TryCatch( + Func> func, + string? errorMessage = null, + MessageTranslation? translation = null + ) { try { @@ -110,7 +109,14 @@ public static async Task> TryCatch(Func> func, str } catch (Exception ex) { - return Fail(new() { Message = errorMessage ?? ex.Message, Exception = ex }); + return new OperationError() + { + Message = errorMessage ?? ex.Message, + Exception = ex, + MessageTranslation = translation, + TitleReplacements = [ex.Message], + ExtraReplacements = [ex.Message], + }; } } @@ -120,8 +126,9 @@ public static async Task> TryCatch(Func> func, str /// /// The action to execute. /// The error message to return if the function fails. + /// The translation for this specific error. /// An that indicates the result of the operation. - public static OperationResult TryCatch(Action action, string? errorMessage = null) + public static OperationResult TryCatch(Action action, string? errorMessage = null, MessageTranslation? translation = null) { try { @@ -130,7 +137,14 @@ public static OperationResult TryCatch(Action action, string? errorMessage = nul } catch (Exception ex) { - return Fail(new() { Message = errorMessage ?? ex.Message, Exception = ex }); + return new OperationError() + { + Message = errorMessage ?? ex.Message, + Exception = ex, + MessageTranslation = translation, + TitleReplacements = [ex.Message], + ExtraReplacements = [ex.Message], + }; } } @@ -140,8 +154,13 @@ public static OperationResult TryCatch(Action action, string? errorMessage = nul /// /// The action to execute. /// The error message to return if the function fails. + /// The translation for this specific error. /// An that indicates the result of the operation. - public static async Task TryCatch(Func action, string? errorMessage = null) + public static async Task TryCatch( + Func action, + string? errorMessage = null, + MessageTranslation? translation = null + ) { try { @@ -150,7 +169,14 @@ public static async Task TryCatch(Func action, string? er } catch (Exception ex) { - return Fail(new() { Message = errorMessage ?? ex.Message, Exception = ex }); + return new OperationError() + { + Message = errorMessage ?? ex.Message, + Exception = ex, + MessageTranslation = translation, + TitleReplacements = [ex.Message], + ExtraReplacements = [ex.Message], + }; } } @@ -158,9 +184,7 @@ public static async Task TryCatch(Func action, string? er #region Implicit Operators - public static implicit operator OperationResult(OperationError error) => Fail(error); - - public static implicit operator OperationResult(string errorMessage) => Fail(errorMessage); + public static implicit operator OperationResult(OperationError error) => new(error); public static implicit operator OperationResult(Exception exception) => Fail(exception); diff --git a/WheelWizard/Shared/OperationResult/OperationResult`1.cs b/WheelWizard/Shared/OperationResult/OperationResult`1.cs index 6df18b07..90546fff 100644 --- a/WheelWizard/Shared/OperationResult/OperationResult`1.cs +++ b/WheelWizard/Shared/OperationResult/OperationResult`1.cs @@ -37,11 +37,9 @@ public OperationResult(OperationError error) public static implicit operator OperationResult(T value) => Ok(value); - public static implicit operator OperationResult(OperationError error) => Fail(error); + public static implicit operator OperationResult(OperationError error) => new(error); - public static implicit operator OperationResult(string errorMessage) => Fail(errorMessage); - - public static implicit operator OperationResult(Exception exception) => Fail(exception); + public static implicit operator OperationResult(Exception exception) => Fail(exception); #endregion } diff --git a/WheelWizard/Utilities/Generators/FriendCodeGenerator.cs b/WheelWizard/Utilities/Generators/FriendCodeGenerator.cs index c40186a2..b8090b64 100644 --- a/WheelWizard/Utilities/Generators/FriendCodeGenerator.cs +++ b/WheelWizard/Utilities/Generators/FriendCodeGenerator.cs @@ -1,6 +1,6 @@ using System.Security.Cryptography; using System.Text; -using WheelWizard.Services.WiiManagement.SaveData; +using WheelWizard.Helpers; namespace WheelWizard.Utilities.Generators; @@ -8,7 +8,7 @@ public class FriendCodeGenerator { public static string GetFriendCode(byte[] data, int offset) { - var pid = BigEndianBinaryReader.BufferToUint32(data, offset); + var pid = BigEndianBinaryHelper.BufferToUint32(data, offset); if (pid == 0) return string.Empty; diff --git a/WheelWizard/Utilities/Mockers/MiiFactory.cs b/WheelWizard/Utilities/Mockers/MiiFactory.cs index 6963f762..8474323f 100644 --- a/WheelWizard/Utilities/Mockers/MiiFactory.cs +++ b/WheelWizard/Utilities/Mockers/MiiFactory.cs @@ -1,5 +1,6 @@ using WheelWizard.WiiManagement; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.MiiManagement; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; namespace WheelWizard.Utilities.Mockers; diff --git a/WheelWizard/Views/App.axaml b/WheelWizard/Views/App.axaml index 723e5d53..c8b10f02 100644 --- a/WheelWizard/Views/App.axaml +++ b/WheelWizard/Views/App.axaml @@ -54,6 +54,7 @@ + @@ -67,5 +68,6 @@ + \ No newline at end of file diff --git a/WheelWizard/Views/App.axaml.cs b/WheelWizard/Views/App.axaml.cs index 6c51aff6..e1d16884 100644 --- a/WheelWizard/Views/App.axaml.cs +++ b/WheelWizard/Views/App.axaml.cs @@ -8,6 +8,7 @@ using WheelWizard.Services.UrlProtocol; using WheelWizard.WheelWizardData; using WheelWizard.WiiManagement; +using WheelWizard.WiiManagement.GameLicense; namespace WheelWizard.Views; diff --git a/WheelWizard/Views/BehaviorComponent/CurrentUserProfile.axaml.cs b/WheelWizard/Views/BehaviorComponent/CurrentUserProfile.axaml.cs index 4fa816d7..5682e6b1 100644 --- a/WheelWizard/Views/BehaviorComponent/CurrentUserProfile.axaml.cs +++ b/WheelWizard/Views/BehaviorComponent/CurrentUserProfile.axaml.cs @@ -5,7 +5,8 @@ using WheelWizard.Shared.DependencyInjection; using WheelWizard.Views.Pages; using WheelWizard.WiiManagement; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.GameLicense; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; namespace WheelWizard.Views.BehaviorComponent; @@ -58,9 +59,9 @@ public CurrentUserProfile() var name = currentUser.NameOfMii; if (name == SettingValues.NoName) - name = Online.NoName; + name = Common.State_NoName; if (name == SettingValues.NoLicense) - name = Online.NoLicense; + name = Common.State_NoLicense; UserName = name; FriendCode = currentUser.FriendCode; diff --git a/WheelWizard/Views/BehaviorComponent/MiiImages/BaseMiiImage.cs b/WheelWizard/Views/BehaviorComponent/MiiImages/BaseMiiImage.cs index 2b07ab07..e617028d 100644 --- a/WheelWizard/Views/BehaviorComponent/MiiImages/BaseMiiImage.cs +++ b/WheelWizard/Views/BehaviorComponent/MiiImages/BaseMiiImage.cs @@ -1,13 +1,11 @@ using System.Collections.ObjectModel; using System.ComponentModel; -using System.Threading; using Avalonia; using Avalonia.Media.Imaging; -using Microsoft.Extensions.Logging; using WheelWizard.MiiImages; using WheelWizard.MiiImages.Domain; using WheelWizard.Shared.DependencyInjection; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; namespace WheelWizard.Views.BehaviorComponent; diff --git a/WheelWizard/Views/BehaviorComponent/MiiImages/MiiCarousel.axaml.cs b/WheelWizard/Views/BehaviorComponent/MiiImages/MiiCarousel.axaml.cs index 2fad534e..0de563ac 100644 --- a/WheelWizard/Views/BehaviorComponent/MiiImages/MiiCarousel.axaml.cs +++ b/WheelWizard/Views/BehaviorComponent/MiiImages/MiiCarousel.axaml.cs @@ -3,10 +3,9 @@ using Avalonia.Interactivity; using Avalonia.Media; using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.Logging; using WheelWizard.MiiImages; using WheelWizard.MiiImages.Domain; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; namespace WheelWizard.Views.BehaviorComponent; diff --git a/WheelWizard/Views/BehaviorComponent/MiiImages/MiiImageLoader.axaml.cs b/WheelWizard/Views/BehaviorComponent/MiiImages/MiiImageLoader.axaml.cs index 16ef394d..eef04bec 100644 --- a/WheelWizard/Views/BehaviorComponent/MiiImages/MiiImageLoader.axaml.cs +++ b/WheelWizard/Views/BehaviorComponent/MiiImages/MiiImageLoader.axaml.cs @@ -1,10 +1,9 @@ -using System.Numerics; using Avalonia; using Avalonia.Media; using Microsoft.Extensions.Caching.Memory; using WheelWizard.MiiImages; using WheelWizard.MiiImages.Domain; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; namespace WheelWizard.Views.BehaviorComponent; diff --git a/WheelWizard/Views/Components/StandardLibrary/LoadingIcon.axaml.cs b/WheelWizard/Views/Components/StandardLibrary/LoadingIcon.axaml.cs index 7d8cc98b..ed8ab48e 100644 --- a/WheelWizard/Views/Components/StandardLibrary/LoadingIcon.axaml.cs +++ b/WheelWizard/Views/Components/StandardLibrary/LoadingIcon.axaml.cs @@ -1,4 +1,3 @@ -using Avalonia; using Avalonia.Controls.Primitives; namespace WheelWizard.Views.Components; diff --git a/WheelWizard/Views/Components/StandardLibrary/MultiIconRadioButton.axaml b/WheelWizard/Views/Components/StandardLibrary/MultiIconRadioButton.axaml index 28322082..c902bb12 100644 --- a/WheelWizard/Views/Components/StandardLibrary/MultiIconRadioButton.axaml +++ b/WheelWizard/Views/Components/StandardLibrary/MultiIconRadioButton.axaml @@ -5,28 +5,28 @@ - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WheelWizard/Views/Components/StandardLibrary/OptionButton.axaml.cs b/WheelWizard/Views/Components/StandardLibrary/OptionButton.axaml.cs new file mode 100644 index 00000000..80fdad1e --- /dev/null +++ b/WheelWizard/Views/Components/StandardLibrary/OptionButton.axaml.cs @@ -0,0 +1,67 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Input; +using Avalonia.Media; + +namespace WheelWizard.Views.Components; + +public class OptionButton : Avalonia.Controls.Button // Change to TemplatedControl +{ + private Border? _hoverEffect; + public static readonly StyledProperty IconDataProperty = AvaloniaProperty.Register(nameof(IconData)); + + public static readonly StyledProperty IconSizeProperty = AvaloniaProperty.Register( + nameof(IconSize), + 20.0 + ); + + public static readonly StyledProperty TextProperty = AvaloniaProperty.Register(nameof(Text)); + + public Geometry IconData + { + get => GetValue(IconDataProperty); + set => SetValue(IconDataProperty, value); + } + + public double IconSize + { + get => GetValue(IconSizeProperty); + set => SetValue(IconSizeProperty, value); + } + + public string Text + { + get => GetValue(TextProperty); + set => SetValue(TextProperty, value); + } + + public OptionButton() + { + FontSize = 36; + Width = 150; + Height = 150; + IconSize = 70; + Margin = new(6); + } + + protected override void OnPointerMoved(PointerEventArgs e) + { + base.OnPointerMoved(e); + if (_hoverEffect == null) + return; + + var position = e.GetPosition(this); + + var left = position.X - (_hoverEffect.Width / 2); + var top = position.Y - (_hoverEffect.Height / 2); + + _hoverEffect.Margin = new(left, top, 0, 0); + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + _hoverEffect = e.NameScope.Find("PART_HoverEffect"); + } +} diff --git a/WheelWizard/Views/Components/WhWzLibrary/Badge.axaml b/WheelWizard/Views/Components/WhWzLibrary/Badge.axaml index 2fdf9c05..0c784e8d 100644 --- a/WheelWizard/Views/Components/WhWzLibrary/Badge.axaml +++ b/WheelWizard/Views/Components/WhWzLibrary/Badge.axaml @@ -18,13 +18,23 @@ - + - + - + + + + + + + + + + + @@ -144,7 +154,71 @@ - + + + + + + + - - - - - - - - + + + + + + - + - - + + - - + + - + - - - + + + - + - + Text="{x:Static lang:Common.Action_ViewRoom}" + IconData="{StaticResource Road}" Margin="5,10" /> + - - + + - - + + @@ -147,14 +157,15 @@ - - - - - + \ No newline at end of file diff --git a/WheelWizard/Views/Components/WhWzLibrary/FriendsListItem.axaml.cs b/WheelWizard/Views/Components/WhWzLibrary/FriendsListItem.axaml.cs index 4903d6f7..ea242eae 100644 --- a/WheelWizard/Views/Components/WhWzLibrary/FriendsListItem.axaml.cs +++ b/WheelWizard/Views/Components/WhWzLibrary/FriendsListItem.axaml.cs @@ -3,7 +3,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Interactivity; using WheelWizard.WheelWizardData; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; namespace WheelWizard.Views.Components; diff --git a/WheelWizard/Views/Components/WhWzLibrary/MiiBlock.axaml.cs b/WheelWizard/Views/Components/WhWzLibrary/MiiBlock.axaml.cs index 8328fd22..7f305309 100644 --- a/WheelWizard/Views/Components/WhWzLibrary/MiiBlock.axaml.cs +++ b/WheelWizard/Views/Components/WhWzLibrary/MiiBlock.axaml.cs @@ -3,7 +3,9 @@ using Avalonia.Input; using Avalonia.Interactivity; using WheelWizard.Services.Settings; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement; +using WheelWizard.WiiManagement.MiiManagement; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; namespace WheelWizard.Views.Components; diff --git a/WheelWizard/Views/Components/WhWzLibrary/PlayerListItem.axaml b/WheelWizard/Views/Components/WhWzLibrary/PlayerListItem.axaml index f6f859d7..2ceee171 100644 --- a/WheelWizard/Views/Components/WhWzLibrary/PlayerListItem.axaml +++ b/WheelWizard/Views/Components/WhWzLibrary/PlayerListItem.axaml @@ -1,5 +1,6 @@  @@ -9,31 +10,31 @@ FriendCode="1234-5678-9012" UserName="Lum Patch" Vr="5000" Br="5000" - HasBadges="True"/> - + HasBadges="True" /> + - + HasBadges="True" /> + - + HasBadges="False" /> + - + HasBadges="False" /> + + HasBadges="False" /> @@ -42,48 +43,51 @@ - + ImageVariant="{x:Static miiVars:MiiImageVariants.OnlinePlayerSmall}" /> - - - - + + + Margin="10,0,0,0" /> - - - - - + + + Margin="10,0,0,0" /> - - - - + + + Margin="10,0,0,0" /> - + + CornerRadius="6" Height="40" BorderThickness="1" + BorderBrush="{StaticResource Neutral600}"> - + - + \ No newline at end of file diff --git a/WheelWizard/Views/Components/WhWzLibrary/PlayerListItem.axaml.cs b/WheelWizard/Views/Components/WhWzLibrary/PlayerListItem.axaml.cs index cbdb65aa..43692c74 100644 --- a/WheelWizard/Views/Components/WhWzLibrary/PlayerListItem.axaml.cs +++ b/WheelWizard/Views/Components/WhWzLibrary/PlayerListItem.axaml.cs @@ -2,7 +2,7 @@ using Avalonia.Controls; using Avalonia.Controls.Primitives; using WheelWizard.WheelWizardData; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; namespace WheelWizard.Views.Components; diff --git a/WheelWizard/Views/Components/WhWzLibrary/WheelTrail.axaml b/WheelWizard/Views/Components/WhWzLibrary/WheelTrail.axaml new file mode 100644 index 00000000..2225082a --- /dev/null +++ b/WheelWizard/Views/Components/WhWzLibrary/WheelTrail.axaml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WheelWizard/Views/Components/WhWzLibrary/WheelTrail.axaml.cs b/WheelWizard/Views/Components/WhWzLibrary/WheelTrail.axaml.cs new file mode 100644 index 00000000..a98b93a8 --- /dev/null +++ b/WheelWizard/Views/Components/WhWzLibrary/WheelTrail.axaml.cs @@ -0,0 +1,93 @@ +using Avalonia; +using Avalonia.Controls.Primitives; +using Avalonia.Media; + +namespace WheelWizard.Views.Components +{ + public class WheelTrail : TemplatedControl + { + public static readonly StyledProperty AngleProperty = AvaloniaProperty.Register(nameof(Angle)); + + public double Angle + { + get => GetValue(AngleProperty); + set => SetValue(AngleProperty, value); + } + + public static readonly StyledProperty WheelRotationProperty = AvaloniaProperty.Register( + nameof(WheelRotation) + ); + + public double WheelRotation + { + get => GetValue(WheelRotationProperty); + set => SetValue(WheelRotationProperty, value); + } + + public static readonly StyledProperty RelativeLengthProperty = AvaloniaProperty.Register( + nameof(RelativeLength), + 6.0 + ); + public double RelativeLength + { + get => GetValue(RelativeLengthProperty); + set => SetValue(RelativeLengthProperty, value); + } + + public static readonly StyledProperty XProperty = AvaloniaProperty.Register(nameof(X)); + + public double X + { + get => GetValue(XProperty); + set => SetValue(XProperty, value); + } + + public static readonly StyledProperty YProperty = AvaloniaProperty.Register(nameof(Y)); + + public double Y + { + get => GetValue(YProperty); + set => SetValue(YProperty, value); + } + + public static readonly StyledProperty ExtendedHeightProperty = AvaloniaProperty.Register( + nameof(ExtendedHeight) + ); + + public double ExtendedHeight + { + get => GetValue(ExtendedHeightProperty); + set => SetValue(ExtendedHeightProperty, value); + } + + private readonly RotateTransform _rotateTransform = new RotateTransform { CenterX = 0.5, CenterY = 0.5 }; + private readonly TranslateTransform _translateTransform = new TranslateTransform { X = 0, Y = 0 }; + + public WheelTrail() + { + var group = new TransformGroup(); + group.Children.Add(_rotateTransform); + group.Children.Add(_translateTransform); + this.RenderTransform = group; + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == AngleProperty) + { + _rotateTransform.Angle = (double)(change.NewValue ?? 0.0); + } + + if (change.Property == XProperty) + { + _translateTransform.X = (double)(change.NewValue ?? 0.0); + } + if (change.Property == YProperty) + { + _translateTransform.Y = (double)(change.NewValue ?? 0.0); + } + } + } +} diff --git a/WheelWizard/Views/Converters/BrushColorConverters.cs b/WheelWizard/Views/Converters/BrushColorConverters.cs new file mode 100644 index 00000000..120ae5f8 --- /dev/null +++ b/WheelWizard/Views/Converters/BrushColorConverters.cs @@ -0,0 +1,28 @@ +using Avalonia.Data.Converters; +using Avalonia.Media; + +namespace WheelWizard.Views.Converters; + +// Note that this is static, which means you dont have to add it as a converter +public static class BrushColorConverters +{ + public static readonly IValueConverter TransparentColor = new FuncValueConverter(x => + { + if (x is ISolidColorBrush brush) + return new Color(0, brush.Color.R, brush.Color.G, brush.Color.B); + if (x is Color c) + return new Color(0, c.R, c.G, c.B); + + return (Colors.Transparent); + }); + + public static readonly IValueConverter BrushToColor = new FuncValueConverter(x => + { + if (x is ISolidColorBrush brush) + return brush.Color; + if (x is IGradientBrush gradientBrush) + return gradientBrush.GradientStops[0].Color; + + return (Colors.Transparent); + }); +} diff --git a/WheelWizard/Views/Converters/DoubleToThicknessConverters.cs b/WheelWizard/Views/Converters/DoubleToThicknessConverters.cs new file mode 100644 index 00000000..c84853fc --- /dev/null +++ b/WheelWizard/Views/Converters/DoubleToThicknessConverters.cs @@ -0,0 +1,13 @@ +using Avalonia; +using Avalonia.Data.Converters; +using Avalonia.Media; + +namespace WheelWizard.Views.Converters; + +public class DoubleToThicknessConverters +{ + public static readonly IValueConverter DoubleToTop = new FuncValueConverter(x => new Thickness(0, x, 0, 0)); + public static readonly IValueConverter DoubleToBottom = new FuncValueConverter(x => new Thickness(0, 0, 0, x)); + public static readonly IValueConverter DoubleToLeft = new FuncValueConverter(x => new Thickness(x, 0, 0, 0)); + public static readonly IValueConverter DoubleToRight = new FuncValueConverter(x => new Thickness(0, 0, x, 0)); +} diff --git a/WheelWizard/Views/Converters/FallbackConverters.cs b/WheelWizard/Views/Converters/FallbackConverters.cs index 3df99c91..342f3908 100644 --- a/WheelWizard/Views/Converters/FallbackConverters.cs +++ b/WheelWizard/Views/Converters/FallbackConverters.cs @@ -1,6 +1,5 @@ using Avalonia.Data.Converters; using Avalonia.Media; -using Avalonia.Media.Imaging; namespace WheelWizard.Views.Converters; diff --git a/WheelWizard/Views/Converters/NumberConverters.cs b/WheelWizard/Views/Converters/NumberConverters.cs index 25759437..4362aaac 100644 --- a/WheelWizard/Views/Converters/NumberConverters.cs +++ b/WheelWizard/Views/Converters/NumberConverters.cs @@ -8,6 +8,16 @@ public static class NumberConverters public static readonly IValueConverter DoubleToInt = new FuncValueConverter(x => (int)x); public static readonly IValueConverter FloatToInt = new FuncValueConverter(x => (int)x); + public static readonly IMultiValueConverter MultiplyDouble = new FuncMultiValueConverter(x => + { + double result = 1; + foreach (var brush in x) + { + result *= brush; + } + return result; + }); + // TODO, find out if you can have a third parameter so that its not always 0, but instead that you can specify the value public static readonly IValueConverter Equals0 = new FuncValueConverter(x => x == 0); @@ -15,6 +25,4 @@ public static class NumberConverters public static readonly IValueConverter SmallerThan0 = new FuncValueConverter(x => x < 0); public static readonly IValueConverter GreaterThanOrEqual0 = new FuncValueConverter(x => x >= 0); public static readonly IValueConverter SmallerThanOrEqual0 = new FuncValueConverter(x => x <= 0); - - public static readonly IValueConverter AlwaysFalse = new FuncValueConverter(x => false); } diff --git a/WheelWizard/Views/Layout.axaml b/WheelWizard/Views/Layout.axaml index d383e0e7..db31cd3a 100644 --- a/WheelWizard/Views/Layout.axaml +++ b/WheelWizard/Views/Layout.axaml @@ -56,9 +56,11 @@ - - + + + + - + - + + @@ -135,7 +138,8 @@ PageType="{x:Type pages:FriendsPage}" BoxText="0/0" /> - + + @@ -146,7 +150,7 @@ @@ -23,7 +23,7 @@ + HorizontalAlignment="Right" VerticalAlignment="Top" /> - + + - + - + - + + + + @@ -85,7 +90,7 @@ + Title="{x:Static lang:Phrases.EmptyContent_NoFriends_Title}" + BodyText="{x:Static lang:Phrases.EmptyContent_NoFriends}" /> \ No newline at end of file diff --git a/WheelWizard/Views/Pages/FriendsPage.axaml.cs b/WheelWizard/Views/Pages/FriendsPage.axaml.cs index 68c5e9db..45169622 100644 --- a/WheelWizard/Views/Pages/FriendsPage.axaml.cs +++ b/WheelWizard/Views/Pages/FriendsPage.axaml.cs @@ -2,14 +2,15 @@ using System.ComponentModel; using Avalonia.Controls; using Avalonia.Interactivity; -using WheelWizard.Models.GameData; +using WheelWizard.Resources.Languages; using WheelWizard.Services.LiveData; -using WheelWizard.Services.Settings; using WheelWizard.Shared.DependencyInjection; +using WheelWizard.Shared.MessageTranslations; using WheelWizard.Utilities.RepeatedTasks; -using WheelWizard.Views.Popups.Generic; using WheelWizard.Views.Popups.MiiManagement; -using WheelWizard.WiiManagement; +using WheelWizard.WiiManagement.GameLicense; +using WheelWizard.WiiManagement.GameLicense.Domain; +using WheelWizard.WiiManagement.MiiManagement; namespace WheelWizard.Views.Pages; @@ -105,12 +106,12 @@ private void PopulateSortingList() var name = type switch { // TODO: Should be replaced with actual translations - ListOrderCondition.VR => "Vr", - ListOrderCondition.BR => "Br", - ListOrderCondition.NAME => "Name", - ListOrderCondition.WINS => "Total Wins", - ListOrderCondition.TOTAL_RACES => "Total Races", - ListOrderCondition.IS_ONLINE => "Is Online", + ListOrderCondition.VR => Common.Attribute_VrFull, + ListOrderCondition.BR => Common.Attribute_BrFull, + ListOrderCondition.NAME => Common.Attribute_Name, + ListOrderCondition.WINS => Common.Attribute_Wins, + ListOrderCondition.TOTAL_RACES => Common.Attribute_RacesPlayed, + ListOrderCondition.IS_ONLINE => Common.Attribute_IsOnline, }; SortByDropdown.Items.Add(name); @@ -140,7 +141,7 @@ private void CopyFriendCode_OnClick(object sender, RoutedEventArgs e) if (FriendsListView.SelectedItem is not FriendProfile selectedPlayer) return; TopLevel.GetTopLevel(this)?.Clipboard?.SetTextAsync(selectedPlayer.FriendCode); - ViewUtils.ShowSnackbar("Copied friend code to clipboard"); + ViewUtils.ShowSnackbar(Phrases.SnackbarSuccess_CopiedFC); } private void OpenCarousel_OnClick(object sender, RoutedEventArgs e) @@ -163,18 +164,14 @@ private void ViewRoom_OnClick(string friendCode) return; } - new MessageBoxWindow() - .SetTitleText("Couldn't find the room") - .SetInfoText("Whoops, could not find the room that this player is supposedly playing in") - .SetMessageType(MessageBoxWindow.MessageType.Warning) - .Show(); + MessageTranslationHelper.ShowMessage(MessageTranslation.Warning_CouldNotFindRoom); } private void SaveMii_OnClick(object sender, RoutedEventArgs e) { if (!MiiDbService.Exists()) { - ViewUtils.ShowSnackbar("Cant save Mii", ViewUtils.SnackbarType.Warning); + ViewUtils.ShowSnackbar(Phrases.SnackbarWarning_CantSaveMii, ViewUtils.SnackbarType.Warning); return; } @@ -183,6 +180,11 @@ private void SaveMii_OnClick(object sender, RoutedEventArgs e) if (selectedPlayer.Mii == null) return; + if (selectedPlayer.Mii?.CustomData.IsCopyable != true) + { + ViewUtils.ShowSnackbar("This player doesn't want you to copy their Mii", ViewUtils.SnackbarType.Warning); + return; + } var desiredMii = selectedPlayer.Mii; //We set the miiId to 0 so it will be added as a new Mii @@ -192,15 +194,11 @@ private void SaveMii_OnClick(object sender, RoutedEventArgs e) var databaseResult = MiiDbService.AddToDatabase(desiredMii, macAddress); if (databaseResult.IsFailure) { - new MessageBoxWindow() - .SetTitleText("Failed to Copy Mii") - .SetInfoText(databaseResult.Error!.Message) - .SetMessageType(MessageBoxWindow.MessageType.Error) - .Show(); + MessageTranslationHelper.ShowMessage(MessageTranslation.Error_FailedCopyMii, null, [databaseResult.Error!.Message]); return; } - ViewUtils.ShowSnackbar("Mii has been added to your Miis"); + ViewUtils.ShowSnackbar(Phrases.SnackbarSuccess_MiiAdded); } #region PropertyChanged @@ -213,4 +211,12 @@ protected virtual void OnPropertyChanged(string propertyName) } #endregion + + private void ContextMenu_OnOpening(object? sender, CancelEventArgs e) + { + if (FriendsListView.SelectedItem is not FriendProfile selectedPlayer) + return; + + CopyMiiButton.IsEnabled = selectedPlayer.Mii?.CustomData.IsCopyable == true; + } } diff --git a/WheelWizard/Views/Pages/HomePage.axaml b/WheelWizard/Views/Pages/HomePage.axaml index 773df3e0..59f0e2f7 100644 --- a/WheelWizard/Views/Pages/HomePage.axaml +++ b/WheelWizard/Views/Pages/HomePage.axaml @@ -5,10 +5,105 @@ xmlns:lang="clr-namespace:WheelWizard.Resources.Languages" xmlns:components="clr-namespace:WheelWizard.Views.Components" xmlns:behaviorComponent="clr-namespace:WheelWizard.Views.BehaviorComponent" - mc:Ignorable="d" d:DesignWidth="656" d:DesignHeight="876" + mc:Ignorable="d" d:DesignWidth="490" d:DesignHeight="830" ClipToBounds="False" x:Class="WheelWizard.Views.Pages.HomePage"> - + + + + + + + + + + + + + @@ -16,29 +111,43 @@ - - + + HorizontalAlignment="Right" VerticalAlignment="Top" /> - + VerticalAlignment="Top" /> - + + + + + + + Width="200" Height="200" Margin="0,25" /> + IsEnabled="True" + IconData="{StaticResource DolphinIcon}" + IconSize="30" + ToolTip.Tip="{x:Static lang:Common.Action_LaunchDolphin}" + ToolTip.Placement="Bottom" + ToolTip.ShowDelay="50" + Height="40" + Click="DolphinButton_OnClick" + Width="100" Margin="0,6,0,0" /> + + + \ No newline at end of file diff --git a/WheelWizard/Views/Pages/HomePage.axaml.cs b/WheelWizard/Views/Pages/HomePage.axaml.cs index 3c04c526..674c1135 100644 --- a/WheelWizard/Views/Pages/HomePage.axaml.cs +++ b/WheelWizard/Views/Pages/HomePage.axaml.cs @@ -1,13 +1,16 @@ using Avalonia; using Avalonia.Controls; +using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Media; using Avalonia.Threading; +using Testably.Abstractions; using WheelWizard.Models.Enums; using WheelWizard.Resources.Languages; using WheelWizard.Services.Launcher; using WheelWizard.Services.Launcher.Helpers; using WheelWizard.Services.Settings; +using WheelWizard.Views.Components; using WheelWizard.Views.Pages.Settings; using Button = WheelWizard.Views.Components.Button; @@ -18,6 +21,9 @@ public partial class HomePage : UserControlBase private static ILauncher currentLauncher => _launcherTypes[_launcherIndex]; private static int _launcherIndex = 0; // Make sure this index never goes over the list index + private WheelTrail[] _trails; // also used as a lock + private WheelTrailState _currentTrailState = WheelTrailState.Static_None; + private static List _launcherTypes = [ new RrLauncher(), @@ -50,6 +56,11 @@ public HomePage() InitializeComponent(); PopulateGameModeDropdown(); UpdatePage(); + + _trails = [HomeTrail1, HomeTrail2, HomeTrail3, HomeTrail4, HomeTrail5]; + App.Services.GetService()?.Random.Shared.Shuffle(_trails); + // We have to do it like `App.Service.GetService`. We cant make use of `private IRandomSystem Random { get; set; } = null!;` here + // This is because this HomePage is always loaded first } private void UpdatePage() @@ -87,7 +98,7 @@ private static async void Update() private void PlayButton_Click(object? sender, RoutedEventArgs e) { currentButtonState?.OnClick?.Invoke(); - + PlayActivateAnimation(); UpdateActionButton(); DisableAllButtonsTemporarily(); } @@ -128,7 +139,11 @@ private void DisableAllButtonsTemporarily() Task.Delay(2000) .ContinueWith(_ => { - Dispatcher.UIThread.InvokeAsync(() => CompleteGrid.IsEnabled = true); + Dispatcher.UIThread.InvokeAsync(() => + { + SetButtonState(currentButtonState); + return CompleteGrid.IsEnabled = true; + }); }); } @@ -140,8 +155,223 @@ private void SetButtonState(MainButtonState state) if (Application.Current != null && Application.Current.FindResource(state.IconName) is Geometry geometry) PlayButton.IconData = geometry; DolphinButton.IsEnabled = state.SubButtonsEnabled && SettingsHelper.PathsSetupCorrectly(); + + if (_status == WheelWizardStatus.Ready) + PlayEntranceAnimation(); + } + + #region WheelTrail Animations + // -------------------------- + // IMPORTANT + // -------------------------- + // When you are changing the animation, note that you are working with locks + // Note that the enum _currentTrailState is used to determine the state of the wheel trails, and that should only be read and changed under the influence of lock(_trails) + // Also note that for NO REASON WHATSOEVER you are permitted to put other logic in these animation code other than the animation itself. + // If for whatever reason the lock gets in to a deadlock, only the animation will freeze, the rest will continue to work. + + private async void PlayEntranceAnimation() + { + // If the animations are disabled, it will never play the entrance animation + // The entrance animation is also the only one that makes the wheels visible, meaning hat if this one does not play + // all the other animations are all also impossible to play + if (!(bool)SettingsManager.ENABLE_ANIMATIONS.Get()) + return; + + var allowedToRun = WaitForWheelTrailState( + WheelTrailState.Playing_Entrance, + c => c is WheelTrailState.Static_None + // even if there are 3 waiting, only 1 will go through, since there is an default check that it cant be the same + ); + if (await allowedToRun == null) + return; + + foreach (var t in _trails) + { + t.Classes.Add("EntranceTrail"); + await Task.Delay(80); + } + + await Task.Delay(600); + foreach (var t in _trails) + { + t.Classes.Remove("EntranceTrail"); + } + + lock (_trails) + { + _currentTrailState = WheelTrailState.Static_Visible; + } + } + + private async void PlayActivateAnimation() + { + if (!(bool)SettingsManager.ENABLE_ANIMATIONS.Get()) + return; + + var allowedToRun = WaitForWheelTrailState( + WheelTrailState.Playing_Activate, + c => + c + is WheelTrailState.Static_Hover + or WheelTrailState.Static_Visible + or WheelTrailState.Playing_HoverEnter + or WheelTrailState.Playing_HoverExit, + c => c is WheelTrailState.Static_None or WheelTrailState.Playing_Activate + ); + var oldState = await allowedToRun; + if (oldState == null) + return; + + foreach (var t in _trails) + { + t.Classes.Clear(); + if (oldState == WheelTrailState.Static_Hover) + t.Classes.Add("ActivateTrailFromHover"); + else + t.Classes.Add("ActivateTrailFromIdle"); + await Task.Delay(80); + } + + await Task.Delay(1000); + foreach (var t in _trails) + { + t.Classes.Remove("ActivateTrailFromIdle"); + t.Classes.Remove("ActivateTrailFromHover"); + await Task.Delay(40); + } + + lock (_trails) + { + _currentTrailState = WheelTrailState.Static_None; + } + } + + private async void PlayButton_OnPointerEntered(object? sender, PointerEventArgs e) + { + var allowedToRun = WaitForWheelTrailState( + WheelTrailState.Playing_HoverEnter, + c => c is WheelTrailState.Static_Visible or WheelTrailState.Playing_HoverExit, + c => c is WheelTrailState.Playing_HoverExit + ); + if (await allowedToRun == null) + return; + + foreach (var t in _trails) + { + // Making sure that if after these seconds the state changed ,that it will not apply the class anymore + lock (_trails) + { + if (_currentTrailState is not WheelTrailState.Playing_HoverEnter) + return; + } + + t.Classes.Remove("HoverExitTrail"); + if (!t.Classes.Contains("HoverEnterTrail")) + t.Classes.Add("HoverEnterTrail"); + await Task.Delay(20); + } + + await Task.Delay(300); + lock (_trails) + { + if (_currentTrailState is WheelTrailState.Playing_HoverEnter) + _currentTrailState = WheelTrailState.Static_Hover; + } + } + + private async void PlayButton_OnPointerExit(object? sender, PointerEventArgs e) + { + var allowedToRun = WaitForWheelTrailState( + WheelTrailState.Playing_HoverExit, + c => c is WheelTrailState.Static_Hover or WheelTrailState.Playing_HoverEnter, + c => c is not WheelTrailState.Static_Hover and not WheelTrailState.Playing_HoverEnter + ); + if (await allowedToRun == null) + return; + + foreach (var t in _trails) + { + lock (_trails) + { + if (_currentTrailState is not WheelTrailState.Playing_HoverExit) + return; + } + t.Classes.Remove("HoverEnterTrail"); + t.Classes.Add("HoverExitTrail"); + } + + await Task.Delay(350); + lock (_trails) + { + if (_currentTrailState is WheelTrailState.Playing_HoverExit) + _currentTrailState = WheelTrailState.Static_Visible; + } } + /// + /// Easier way to wait for a specific animation state + /// + /// the state that you are trying to set it to + /// the states when it is allowed to override the state and continue the code + /// the statues when it should abort trying to set the state. it then also should not continue + /// null = aborted, WheelTrailState = the old state that it was before the swap. This means success + private async Task WaitForWheelTrailState( + WheelTrailState changeStateTo, + Func acceptWhen, + Func? abortWhen = null + ) + { + bool accepted; + WheelTrailState? oldState = null; + lock (_trails) + { + accepted = acceptWhen(_currentTrailState); + if (accepted) + { + oldState = _currentTrailState; + _currentTrailState = changeStateTo; + } + } + + while (!accepted) + { + await Task.Delay(20); + bool abort; + lock (_trails) + { + abort = (abortWhen?.Invoke(_currentTrailState) ?? false) || _currentTrailState == changeStateTo; + } + if (abort) + return null; + + lock (_trails) + { + accepted = acceptWhen(_currentTrailState); + if (accepted) + { + oldState = _currentTrailState; + _currentTrailState = changeStateTo; + } + } + } + + return oldState; + } + + enum WheelTrailState + { + Static_None, // It is not in view + Static_Visible, // It is just doing nothing + Static_Hover, // It is just doing nothing while it is being hovered + + Playing_Entrance, // Animation for entrance is playing NOTHING is allowed to interrupt Playing_Entrance + Playing_Activate, // Animation for activation is playing NOTHING is allowed to interrupt Playing_Entrance + Playing_HoverEnter, // Hover Enter animation is playing can be interrupted + Playing_HoverExit, // Hover Exit animation is exiting can be interrupted + } + + #endregion + public class MainButtonState { public MainButtonState(string text, Button.ButtonsVariantType type, string iconName, Action? onClick, bool subButtonsEnables) => diff --git a/WheelWizard/Views/Pages/MiiListPage.axaml b/WheelWizard/Views/Pages/MiiListPage.axaml index edd70557..0029360e 100644 --- a/WheelWizard/Views/Pages/MiiListPage.axaml +++ b/WheelWizard/Views/Pages/MiiListPage.axaml @@ -26,13 +26,27 @@ + Title="{x:Static lang:Phrases.EmptyContent_NoMiis_Title}" + BodyText="{x:Static lang:Phrases.EmptyContent_NoMiis}" /> + + + + + + - + ChangeTopButtons(); miiBlock.ContextMenu = new ContextMenu(); + var favHeader = mii.IsFavorite ? Common.Action_Unfavorite : Common.Action_Favorite; + miiBlock.ContextMenu.Items.Add( + new MenuItem { Header = favHeader, Command = new MyCommand(() => ContextAction(mii, ToggleFavorite)) } + ); miiBlock.ContextMenu.Items.Add(new MenuItem { Header = Common.Action_Edit, Command = new MyCommand(() => EditMii(mii)) }); miiBlock.ContextMenu.Items.Add( new MenuItem { Header = "Duplicate", Command = new MyCommand(() => ContextAction(mii, DuplicateMii)) } ); miiBlock.ContextMenu.Items.Add( - new MenuItem { Header = Common.Action_Delete, Command = new MyCommand(() => ContextAction(mii, DeleteMii)) } + new MenuItem { Header = Common.Action_Export, Command = new MyCommand(() => ContextAction(mii, ExportMultipleMiiFiles)) } ); miiBlock.ContextMenu.Items.Add( - new MenuItem { Header = Common.Action_Export, Command = new MyCommand(() => ContextAction(mii, ExportMultipleMiiFiles)) } + new MenuItem { Header = Common.Action_Delete, Command = new MyCommand(() => ContextAction(mii, DeleteMii)) } ); + MiiList.Children.Add(miiBlock); } @@ -179,6 +191,8 @@ private void ReloadMiiList() private async void EditMii_OnClick(object? sender, RoutedEventArgs e) => EditMii(GetSelectedMiis()[0]); + private async void FavMii_OnClick(object? sender, RoutedEventArgs e) => ToggleFavorite(GetSelectedMiis()); + private async void ExportMii_OnClick(object? sender, RoutedEventArgs e) => ExportMultipleMiiFiles(GetSelectedMiis()); private async void DuplicateMii_OnClick(object? sender, RoutedEventArgs e) => DuplicateMii(GetSelectedMiis()); @@ -186,7 +200,7 @@ private void ReloadMiiList() private async void ImportMii_OnClick(object? sender, RoutedEventArgs e) { var miiFiles = await FilePickerHelper.OpenFilePickerAsync( - fileType: new FilePickerFileType("mii file") { Patterns = new[] { "*.mii" } }, + fileType: CustomFilePickerFileType.Miis, allowMultiple: true, title: "Select Mii file(s)" ); @@ -203,7 +217,10 @@ private async void ImportMii_OnClick(object? sender, RoutedEventArgs e) var result = MiiSerializer.Deserialize(miiData); if (result.IsFailure) { - ViewUtils.ShowSnackbar($"Failed to deserialize Mii '{result.Error.Message}'", ViewUtils.SnackbarType.Danger); + ViewUtils.ShowSnackbar( + Humanizer.ReplaceDynamic(Phrases.SnackbarError_MiiFailureDeserialize, result.Error.Message)!, + ViewUtils.SnackbarType.Danger + ); return; } @@ -214,10 +231,35 @@ private async void ImportMii_OnClick(object? sender, RoutedEventArgs e) var saveResult = MiiDbService.AddToDatabase(mii, macAddress); if (saveResult.IsFailure) { - ViewUtils.ShowSnackbar($"Failed to save Mii '{saveResult.Error.Message}'", ViewUtils.SnackbarType.Danger); + ViewUtils.ShowSnackbar( + Humanizer.ReplaceDynamic(Phrases.SnackbarError_MiiFailureSave, saveResult.Error.Message)!, + ViewUtils.SnackbarType.Danger + ); return; } } + + ReloadMiiList(); + } + + private async void ToggleFavorite(Mii[] miis) + { + var allFavorite = miis.All(m => m.IsFavorite); + + foreach (var mii in miis) + { + mii.IsFavorite = !allFavorite; + var result = MiiDbService.Update(mii); + if (result.IsFailure) + { + ViewUtils.ShowSnackbar( + Humanizer.ReplaceDynamic(Phrases.SnackbarError_MiiFailureUpdate, result.Error.Message)!, + ViewUtils.SnackbarType.Danger + ); + return; + } + } + ReloadMiiList(); } @@ -225,9 +267,10 @@ private async void ExportMultipleMiiFiles(Mii[] miis) { if (miis.Length == 0) { - ViewUtils.ShowSnackbar("It seems there where no Miis to export", ViewUtils.SnackbarType.Warning); + ViewUtils.ShowSnackbar(Phrases.SnackbarWarning_NoMiiExport, ViewUtils.SnackbarType.Warning); return; } + foreach (var mii in miis) { ExportMiiAsFile(mii); @@ -242,10 +285,10 @@ public static string ReplaceInvalidFileNameChars(string filename) private async void ExportMiiAsFile(Mii mii) { - var exportName = ReplaceInvalidFileNameChars(mii.Name.ToString()); + var exportName = ReplaceInvalidFileNameChars(CustomCharactersService.NormalizeToAscii(mii.Name.ToString())); var diaglog = await FilePickerHelper.SaveFileAsync( title: "Save Mii as file", - fileTypes: new[] { new FilePickerFileType("Mii file") { Patterns = new[] { "*.mii" } } }, + fileTypes: [CustomFilePickerFileType.Miis], defaultFileName: $"{exportName}" ); if (diaglog == null) @@ -253,17 +296,27 @@ private async void ExportMiiAsFile(Mii mii) var result = MiiDbService.GetByAvatarId(mii.MiiId); if (result.IsFailure) { - ViewUtils.ShowSnackbar($"Failed to get Mii '{result.Error.Message}'", ViewUtils.SnackbarType.Danger); + ViewUtils.ShowSnackbar( + Humanizer.ReplaceDynamic(Phrases.SnackbarError_MiiFailureGet, result.Error.Message)!, + ViewUtils.SnackbarType.Danger + ); return; } + var miiToExport = result.Value; var saveResult = SaveMiiToDisk(miiToExport, diaglog); if (saveResult.IsFailure) { - ViewUtils.ShowSnackbar($"Failed to save Mii '{saveResult.Error.Message}'", ViewUtils.SnackbarType.Danger); + ViewUtils.ShowSnackbar( + Humanizer.ReplaceDynamic(Phrases.SnackbarError_MiiFailureSave, saveResult.Error.Message)!, + ViewUtils.SnackbarType.Danger + ); return; } - ViewUtils.ShowSnackbar($"Exported Mii '{miiToExport.Name}' to file '{diaglog}'"); + + ViewUtils.ShowSnackbar( + Humanizer.ReplaceDynamic(Phrases.SnackbarSuccess_SavedMii, miiToExport.Name, diaglog) ?? "Saved Mii successfully" + ); } private OperationResult SaveMiiToDisk(Mii mii, string path) @@ -271,9 +324,13 @@ private OperationResult SaveMiiToDisk(Mii mii, string path) var miiData = MiiSerializer.Serialize(mii); if (miiData.IsFailure) { - ViewUtils.ShowSnackbar($"Failed to serialize Mii '{miiData.Error.Message}'", ViewUtils.SnackbarType.Danger); + ViewUtils.ShowSnackbar( + Humanizer.ReplaceDynamic(Phrases.SnackbarError_MiiFailureSerialize, miiData.Error.Message)!, + ViewUtils.SnackbarType.Danger + ); return miiData; } + var file = FileSystem.FileInfo.New(path); using var stream = file.Open(FileMode.Create, FileAccess.Write); using var writer = new BinaryWriter(stream); @@ -281,7 +338,9 @@ private OperationResult SaveMiiToDisk(Mii mii, string path) writer.Flush(); writer.Close(); stream.Close(); - ViewUtils.ShowSnackbar($"Saved Mii '{mii.Name}' to file '{file.FullName}'"); + ViewUtils.ShowSnackbar( + Humanizer.ReplaceDynamic(Phrases.SnackbarSuccess_SavedMii, mii.Name, file.FullName) ?? "Saved Mii successfully" + ); return Ok(); } @@ -289,25 +348,28 @@ private async void DeleteMii(Mii[] miis) { if (miis.Length == 0) { - ViewUtils.ShowSnackbar("It seems there where no Miis to delete", ViewUtils.SnackbarType.Warning); + ViewUtils.ShowSnackbar(Phrases.SnackbarWarning_NoMiiDelete, ViewUtils.SnackbarType.Warning); return; } - // TODO: add a check that you cant remove a Mii that is in use by a lisence, + // TODO: add a check that you cant remove a Mii that is in use by a licence, // I have no idea how tho - var mainText = $"Are you sure you want to delete {miis.Length} Miis?"; - var successMessage = $"Deleted {miis.Length} Miis"; + if (miis.Any(mii => mii.IsFavorite)) + { + await MessageTranslationHelper.AwaitMessageAsync(MessageTranslation.Warning_CantDeleteFavMii); + return; + } + + var mainText = Humanizer.ReplaceDynamic(Phrases.Question_SureDelete_Title_Miis, miis.Length) ?? $"Delete {miis.Length}?"; + var successMessage = Humanizer.ReplaceDynamic(Phrases.SnackbarSuccess_Deleted_Miis, miis.Length) ?? $"Deleted {miis.Length}"; if (miis.Length == 1) { - mainText = $"Are you sure you want to delete the Mii '{miis[0].Name}'?"; - successMessage = $"Deleted Mii '{miis[0].Name}'"; + mainText = Humanizer.ReplaceDynamic(Phrases.Question_SureDelete_Title, miis[0].Name) ?? $"Delete {miis[0].Name}?"; + successMessage = Humanizer.ReplaceDynamic(Phrases.SnackbarSuccess_Deleted, miis[0].Name) ?? $"Deleted {miis[0].Name}"; } - var result = await new YesNoWindow() - .SetMainText(mainText) - .SetExtraText("This action will permanently delete the Mii(s) and cannot be undone.") - .AwaitAnswer(); + var result = await new YesNoWindow().SetMainText(mainText).SetExtraText(Phrases.Question_SureDelete_Extra).AwaitAnswer(); if (!result) return; @@ -315,6 +377,7 @@ private async void DeleteMii(Mii[] miis) { MiiDbService.Remove(mii.MiiId); } + ReloadMiiList(); ViewUtils.ShowSnackbar(successMessage); } @@ -329,32 +392,28 @@ private async void EditMii(Mii mii) var result = MiiDbService.Update(window.Mii); if (result.IsFailure) { - ViewUtils.ShowSnackbar($"Failed to update Mii '{result.Error.Message}'", ViewUtils.SnackbarType.Danger); + ViewUtils.ShowSnackbar( + Humanizer.ReplaceDynamic(Phrases.SnackbarError_MiiFailureUpdate, result.Error.Message)!, + ViewUtils.SnackbarType.Danger + ); return; } + ReloadMiiList(); } private async void CreateNewMii() { - string[] presets = - [ - "liwAZgByADMAZAAAAAAAAAAAAAAAAFYXiRPnfsJmn7skBGWAYIAociBsKEATSLCNEIoAiiUEAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - "hDQAZwByAG8AbQBwAGEAAAAAAAAAAC8AiRPogsJmn7syxGkAGYCIoiyMCECESACNAIoIiiUEAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - "xCAAZABhAG4AaQBlAGwAbABlAAAAAGYaiRPo48Jmn7sARAjAAQBokniNaEBjUHiOAIsGiiUEAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - "0rIAZgBvAHoAaQBsAGwAYQAAAGUAAEBAiRPpIMJmn7sABBLAAUBooohsKECjSGiNAIoGiiUEAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - "hCgAZgByADAAZAAAAAAAAAAAAAAAAEBAiRPoU8Jmn7sABHDAWYBokoCLKEB0QIiMAIkGiiUEAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - ]; - - var randomIndex = (int)(Random.Random.Shared.NextDouble() * presets.Length); - var miiResult = MiiSerializer.Deserialize(presets[randomIndex]); - if (miiResult.IsFailure) - { - ViewUtils.ShowSnackbar($"Failed to create a new Mii, Please try again, or message a developer", ViewUtils.SnackbarType.Danger); + Mii? mii = null; + await new OptionsWindow() + .AddOption("Dice", Common.Action_Randomize, () => mii = MiiFactory.CreateRandomMii(Random.Random.Shared)) + .AddOption("PersonMale", Common.Attribute_Mii_Gender_Male, () => mii = MiiFactory.CreateDefaultMale()) + .AddOption("PersonFemale", Common.Attribute_Mii_Gender_Female, () => mii = MiiFactory.CreateDefaultFemale()) + .AwaitAnswer(); + if (mii == null) return; - } - var window = new MiiEditorWindow().SetMii(miiResult.Value); + var window = new MiiEditorWindow().SetMii(mii); var save = await window.AwaitAnswer(); if (!save) return; @@ -362,9 +421,13 @@ private async void CreateNewMii() var result = MiiDbService.AddToDatabase(window.Mii, (string)SettingsManager.MACADDRESS.Get()); if (result.IsFailure) { - ViewUtils.ShowSnackbar($"Failed to create Mii '{result.Error.Message}'", ViewUtils.SnackbarType.Danger); + ViewUtils.ShowSnackbar( + Humanizer.ReplaceDynamic(Phrases.SnackbarError_MiiFailureCreate, result.Error.Message)!, + ViewUtils.SnackbarType.Danger + ); return; } + ReloadMiiList(); } @@ -378,13 +441,16 @@ private void DuplicateMii(Mii[] miis) if (!result.IsFailure) continue; - ViewUtils.ShowSnackbar($"Failed to duplicate Mii(s) '{result.Error.Message}'", ViewUtils.SnackbarType.Danger); + ViewUtils.ShowSnackbar( + Humanizer.ReplaceDynamic(Phrases.SnackbarError_MiiFailureDuplicate, result.Error.Message)!, + ViewUtils.SnackbarType.Danger + ); return; } - var successMessage = $"Created {miis.Length} duplicate Miis"; + var successMessage = Humanizer.ReplaceDynamic(Phrases.SnackbarSuccess_CreatedDuplicatesMiis, miis.Length)!; if (miis.Length == 1) - successMessage = $"Created duplicate Mii '{miis[0].Name}'"; + successMessage = Humanizer.ReplaceDynamic(Phrases.SnackbarSuccess_CreatedDuplicate, miis[0].Name)!; ReloadMiiList(); ViewUtils.ShowSnackbar(successMessage); @@ -410,14 +476,20 @@ private void ChangeTopButtons() EditMiisButton.IsVisible = false; DuplicateMiisButton.IsVisible = false; ImportMiiButton.IsVisible = true; + FavoriteMiiButton.IsVisible = false; return; } + FavoriteMiiButton.IsVisible = true; EditMiisButton.IsVisible = selectedMiis.Length == 1; ImportMiiButton.IsVisible = false; DeleteMiisButton.IsVisible = true; ExportMiisButton.IsVisible = true; DuplicateMiisButton.IsVisible = true; + + FavoriteMiiButton.Classes.Remove("UnFav"); + if (selectedMiis.All(mii => mii.IsFavorite)) + FavoriteMiiButton.Classes.Add("UnFav"); } #region Command diff --git a/WheelWizard/Views/Pages/ModsPage.axaml b/WheelWizard/Views/Pages/ModsPage.axaml index 83e6ac94..040b6b4e 100644 --- a/WheelWizard/Views/Pages/ModsPage.axaml +++ b/WheelWizard/Views/Pages/ModsPage.axaml @@ -23,33 +23,33 @@ HorizontalAlignment="Left" VerticalAlignment="Bottom" Classes="PageTitleText" /> - - - + - - + - + Text="{x:Static lang:Common.Action_Browse}" IconData="{StaticResource Search}" + Click="BrowseMod_Click" HorizontalAlignment="Stretch" /> + Click="ImportMod_Click" HorizontalAlignment="Stretch" /> @@ -116,8 +116,11 @@ - + ModManager.Instance; + public ObservableCollection Mods => new( ModManager.Mods.Select(mod => new ModListItem( @@ -103,21 +105,13 @@ private void ViewMod_Click(object sender, RoutedEventArgs e) if (ModsListBox.SelectedItem is not ModListItem selectedMod) { // You actually never see this error, however, if for some unknown reason it happens, we don't want to disregard it - new MessageBoxWindow() - .SetMessageType(MessageBoxWindow.MessageType.Warning) - .SetTitleText("Cannot view the selected mod") - .SetInfoText("Something went wrong when trying to open the selected mod") - .Show(); + MessageTranslationHelper.ShowMessage(MessageTranslation.Warning_CantViewMod_SomethingWrong); return; } if (selectedMod.Mod.ModID == -1) { - new MessageBoxWindow() - .SetMessageType(MessageBoxWindow.MessageType.Warning) - .SetTitleText("Cannot view the selected mod") - .SetInfoText("Cannot view mod that was not installed through the mod browser.") - .Show(); + MessageTranslationHelper.ShowMessage(MessageTranslation.Warning_CantViewMod_NotFromBrowser); return; } diff --git a/WheelWizard/Views/Pages/RoomDetailsPage.axaml b/WheelWizard/Views/Pages/RoomDetailsPage.axaml index 41f92d5e..179afc8f 100644 --- a/WheelWizard/Views/Pages/RoomDetailsPage.axaml +++ b/WheelWizard/Views/Pages/RoomDetailsPage.axaml @@ -11,7 +11,7 @@ - + - - - - - - - + + + + + + + - + - - + + - + - + - - - - - + + + + + + + + - + - - - + + + + + - - - - - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + - - - - + + + + + + + + + + + + - - - - - - - - - - - - - + - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - \ No newline at end of file + + diff --git a/WheelWizard/Views/Pages/Settings/WhWzSettings.axaml.cs b/WheelWizard/Views/Pages/Settings/WhWzSettings.axaml.cs index eb430029..e26f02d9 100644 --- a/WheelWizard/Views/Pages/Settings/WhWzSettings.axaml.cs +++ b/WheelWizard/Views/Pages/Settings/WhWzSettings.axaml.cs @@ -1,16 +1,21 @@ +using System.IO; using System.Runtime.InteropServices; using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Media; using Avalonia.Platform.Storage; using Avalonia.Threading; +using HarfBuzzSharp; +using Serilog; using WheelWizard.Helpers; using WheelWizard.Models.Settings; using WheelWizard.Resources.Languages; using WheelWizard.Services; using WheelWizard.Services.Settings; +using WheelWizard.Shared.MessageTranslations; using WheelWizard.Views.Popups.Generic; using Button = WheelWizard.Views.Components.Button; +using SettingsResource = WheelWizard.Resources.Languages.Settings; namespace WheelWizard.Views.Pages.Settings; @@ -18,6 +23,7 @@ public partial class WhWzSettings : UserControl { private readonly bool _pageLoaded; private bool _editingScale; + private bool _isMovingAppData; public WhWzSettings() { @@ -25,15 +31,37 @@ public WhWzSettings() AutoFillPaths(); TogglePathSettings(false); LoadSettings(); + UpdateAppDataLocationUi(); _pageLoaded = true; + + MKGameFieldLabel.TipText = SettingsResource.HelperText_EndWithX + "Path can end with: .wbfs/.iso/.rvz"; + WhWzLanguageDropdown.SelectionChanged += WhWzLanguageDropdown_OnSelectionChanged; } private void LoadSettings() { // ----------------- - // Loading all the Window Scale settings + // Wheel Wizard Language Dropdown // ----------------- + WhWzLanguageDropdown.Items.Clear(); // Clear existing items + foreach (var lang in SettingValues.WhWzLanguages.Values) + { + WhWzLanguageDropdown.Items.Add(lang()); + } + + var currentWhWzLanguage = (string)SettingsManager.WW_LANGUAGE.Get(); + var whWzLanguageDisplayName = SettingValues.WhWzLanguages[currentWhWzLanguage]; + WhWzLanguageDropdown.SelectedItem = whWzLanguageDisplayName(); + + TranslationsPercentageText.Text = Humanizer.ReplaceDynamic( + Phrases.Text_LanguageTranslatedBy, + SettingsResource.Value_Language_zTranslators + ); + TranslationsPercentageText.IsVisible = SettingsResource.Value_Language_zTranslators != "-"; + // ----------------- + // Window Scale settings + // ----------------- // IMPORTANT: Make sure that the number and percentage is always the last word in the string, // If you don't want this, you should change the code below that parses the string back to an actual value @@ -47,7 +75,7 @@ private void LoadSettings() WindowScaleDropdown.Items.Add(selectedItemText); WindowScaleDropdown.SelectedItem = selectedItemText; - // EnableAnimations.IsChecked = (bool)SettingsManager.ENABLE_ANIMATIONS.Get(); + EnableAnimations.IsChecked = (bool)SettingsManager.ENABLE_ANIMATIONS.Get(); } private static string ScaleToString(double scale) @@ -56,7 +84,7 @@ private static string ScaleToString(double scale) if (SettingValues.WindowScales.Contains(scale)) return percentageString; - return "Custom: " + percentageString; + return Common.State_Custom + ": " + percentageString; } private void AutoFillPaths() @@ -98,17 +126,15 @@ private async void DolphinExeBrowse_OnClick(object sender, RoutedEventArgs e) if (!EnvHelper.IsFlatpakSandboxed() && !IsFlatpakDolphinInstalled()) { var wantsAutomaticInstall = await new YesNoWindow() - .SetMainText("Dolphin Flatpak Installation") - .SetExtraText( - "The flatpak version of Dolphin Emulator does not appear to be installed. Would you like us to install it (system-wide)?" - ) - .SetButtonText("Install", "Manual") + .SetMainText(Phrases.Question_DolphinFlatpack_Title) + .SetExtraText(Phrases.Question_DolphinFlatpack_Extra) + .SetButtonText(Common.Action_Install, Common.Action_DoManually) .AwaitAnswer(); if (wantsAutomaticInstall) { var progressWindow = new ProgressWindow() - .SetGoal("Installing Dolphin Emulator") - .SetExtraText("This may take a while depending on your internet connection."); + .SetGoal(Phrases.Progress_InstallingDolphin) + .SetExtraText(Phrases.Progress_ThisMayTakeAWhile); TogglePathSettings(true); progressWindow.Show(); var progress = new Progress(progressWindow.UpdateProgress); @@ -116,11 +142,7 @@ private async void DolphinExeBrowse_OnClick(object sender, RoutedEventArgs e) progressWindow.Close(); if (!success) { - await new MessageBoxWindow() - .SetMessageType(MessageBoxWindow.MessageType.Error) - .SetTitleText("Failed to install Dolphin") - .SetInfoText("The installation of Dolphin Emulator failed. Please try manually installing flatpak dolphin.") - .ShowDialog(); + await MessageTranslationHelper.AwaitMessageAsync(MessageTranslation.Error_FailedInstallDolphin); return; } @@ -136,8 +158,8 @@ private async void DolphinExeBrowse_OnClick(object sender, RoutedEventArgs e) if (!string.IsNullOrEmpty(dolphinAppPath)) { var result = await new YesNoWindow() - .SetMainText("Dolphin Emulator found.") - .SetExtraText($"{Phrases.PopupText_DolphinFoundText}\n{dolphinAppPath}") + .SetMainText(Phrases.Question_DolphinFound_Title) + .SetExtraText($"{Phrases.Question_DolphinFound_Extra}\n{dolphinAppPath}") .AwaitAnswer(); if (result) @@ -148,20 +170,21 @@ private async void DolphinExeBrowse_OnClick(object sender, RoutedEventArgs e) } else { - await new MessageBoxWindow() - .SetMessageType(MessageBoxWindow.MessageType.Warning) - .SetTitleText("Dolphin App not found in system/user application folders.") - .SetInfoText(Phrases.PopupText_DolphinNotFoundText) - .ShowDialog(); + await MessageTranslationHelper.AwaitMessageAsync(MessageTranslation.Warning_DolphinNotFound); } + // Fallback to manual selection - Console.WriteLine("Selecting folder on macOS"); var folders = await FilePickerHelper.SelectFolderAsync("Select Dolphin.app"); - if (folders.Count >= 1) + if (folders != null && folders.Count >= 1) { - var executablePath = Path.Combine(folders[0].Path.LocalPath, "Contents", "MacOS", "Dolphin"); + var resolvedFolder = await ResolveSelectedFolderPathAsync(folders[0]); + if (string.IsNullOrWhiteSpace(resolvedFolder)) + return; + + var executablePath = Path.Combine(resolvedFolder, "Contents", "MacOS", "Dolphin"); AssignWrappedDolphinExeInput(executablePath); } + return; // do not do normal selection for MacOS } @@ -204,8 +227,8 @@ private async void DolphinUserPathBrowse_OnClick(object sender, RoutedEventArgs { // Ask the user if they want to use the automatically found folder var result = await new YesNoWindow() - .SetMainText($"{Phrases.PopupText_DolphinFoundText}\n{folderPath}") - .SetExtraText(Phrases.PopupText_DolphinFound) + .SetMainText(Phrases.Question_DolphinFound_Title) + .SetExtraText($"{Phrases.Question_DolphinFound_Extra}\n{folderPath}") .AwaitAnswer(); if (result) @@ -216,11 +239,7 @@ private async void DolphinUserPathBrowse_OnClick(object sender, RoutedEventArgs } else { - await new MessageBoxWindow() - .SetMessageType(MessageBoxWindow.MessageType.Warning) - .SetTitleText("Dolphin Emulator folder not found.") - .SetInfoText(Phrases.PopupText_DolphinNotFoundText) - .ShowDialog(); + await MessageTranslationHelper.AwaitMessageAsync(MessageTranslation.Warning_DolphinNotFound); } var currentFolder = (string)SettingsManager.USER_FOLDER_PATH.Get(); @@ -231,8 +250,12 @@ private async void DolphinUserPathBrowse_OnClick(object sender, RoutedEventArgs var folder = await topLevel!.StorageProvider.TryGetFolderFromPathAsync(currentFolder); var folders = await FilePickerHelper.SelectFolderAsync("Select Dolphin User Path", folder); - if (folders.Count >= 1) - DolphinUserPathInput.Text = folders[0].Path.LocalPath; + if (folders != null && folders.Count >= 1) + { + var resolvedFolder = await ResolveSelectedFolderPathAsync(folders[0]); + if (!string.IsNullOrWhiteSpace(resolvedFolder)) + DolphinUserPathInput.Text = resolvedFolder; + } return; } else @@ -240,8 +263,12 @@ private async void DolphinUserPathBrowse_OnClick(object sender, RoutedEventArgs // Let the user manually select a folder var manualFolders = await FilePickerHelper.SelectFolderAsync("Select Dolphin User Path"); - if (manualFolders.Count >= 1) - DolphinUserPathInput.Text = manualFolders[0].Path.LocalPath; + if (manualFolders != null && manualFolders.Count >= 1) + { + var resolvedFolder = await ResolveSelectedFolderPathAsync(manualFolders[0]); + if (!string.IsNullOrWhiteSpace(resolvedFolder)) + DolphinUserPathInput.Text = resolvedFolder; + } } } @@ -257,20 +284,10 @@ private async void SaveButton_OnClick(object sender, RoutedEventArgs e) // These 3 lines is only saving the settings TogglePathSettings(false); if (!(SettingsHelper.PathsSetupCorrectly() && path1 && path2 && path3)) - { - await new MessageBoxWindow() - .SetMessageType(MessageBoxWindow.MessageType.Warning) - .SetTitleText("Invalid configuration.") - .SetInfoText(Phrases.PopupText_EnsurePathsExists) - .ShowDialog(); - } + await MessageTranslationHelper.AwaitMessageAsync(MessageTranslation.Warning_InvalidPathSettings); else { - await new MessageBoxWindow() - .SetMessageType(MessageBoxWindow.MessageType.Message) - .SetTitleText(Phrases.PopupText_SettingsSaved) - .SetInfoText(Phrases.PopupText_SettingsSaved) - .ShowDialog(); + await MessageTranslationHelper.AwaitMessageAsync(MessageTranslation.Success_PathSettingsSaved); // This is not really the best approach, but it works for now if (oldPath1 + oldPath2 + oldPath3 != DolphinExeInput.Text + MarioKartInput.Text + DolphinUserPathInput.Text) @@ -328,6 +345,274 @@ private void TogglePathSettings(bool enable) MarioKartInput.Text = PathManager.GameFilePath; DolphinUserPathInput.Text = PathManager.UserFolderPath; OpenGameFolderButton.IsEnabled = Directory.Exists(PathManager.RiivolutionWhWzFolderPath); + UpdateAppDataLocationUi(); + } + + private void UpdateAppDataLocationUi() + { + var currentPath = PathManager.WheelWizardAppdataPath; + AppDataLocationInput.Text = currentPath; + AppDataLocationInput.CaretIndex = currentPath.Length; + ToolTip.SetTip(AppDataLocationInput, currentPath); + + var statusText = + _isMovingAppData ? SettingsResource.Status_DataFolder_Moving + : PathManager.IsUsingCustomWheelWizardAppdataPath ? SettingsResource.Status_DataFolder_Custom + : SettingsResource.Status_DataFolder_Default; + + AppDataLocationStatus.Text = statusText; + AppDataLocationBrowseButton.IsEnabled = !_isMovingAppData; + AppDataLocationResetButton.IsEnabled = !_isMovingAppData && PathManager.IsUsingCustomWheelWizardAppdataPath; + ToolTip.SetTip(AppDataLocationResetButton, PathManager.DefaultWheelWizardAppdataFolderPath); + } + + private void SetAppDataLocationBusyState(bool isBusy) + { + _isMovingAppData = isBusy; + AppDataLocationBrowseButton.IsEnabled = !isBusy; + AppDataLocationResetButton.IsEnabled = !isBusy && PathManager.IsUsingCustomWheelWizardAppdataPath; + if (isBusy) + AppDataLocationStatus.Text = SettingsResource.Status_DataFolder_Moving; + } + + private async Task ConfirmAndMoveAppDataAsync(string targetPath) + { + if (string.IsNullOrWhiteSpace(targetPath)) + return false; + + var trimmedTarget = targetPath.Trim(); + + var validationSuccessful = PathManager.TryValidateWheelWizardAppdataTarget( + trimmedTarget, + out var normalizedTarget, + out _, + out var validationError, + out var requiresMove + ); + + if (!validationSuccessful) + { + await new MessageBoxWindow() + .SetMessageType(MessageBoxWindow.MessageType.Error) + .SetTitleText(Phrases.MessageError_DataFolderMove_Title) + .SetInfoText(validationError) + .ShowDialog(); + return false; + } + + if (!requiresMove) + return false; + + var extraText = + Humanizer.ReplaceDynamic(Phrases.Question_MoveData_Extra, normalizedTarget) + ?? $"Wheel Wizard will move its files to:\n{normalizedTarget}\nThis may take a while depending on the amount of data."; + + var confirmed = await new YesNoWindow() + .SetMainText(Phrases.Question_MoveData_Title) + .SetExtraText(extraText) + .SetButtonText(Common.Action_Yes, Common.Action_No) + .AwaitAnswer(); + + if (!confirmed) + return false; + + await MoveWheelWizardDataAsync(normalizedTarget); + return true; + } + + private async Task MoveWheelWizardDataAsync(string targetPath) + { + SetAppDataLocationBusyState(true); + Log.CloseAndFlush(); + + var progressWindow = new ProgressWindow(SettingsResource.Status_DataFolder_Moving) + .SetExtraText(SettingsResource.HelperText_WheelWizardDataFolder) + .SetGoal(SettingsResource.Status_DataFolder_Moving); + progressWindow.Show(); + + var progress = new Progress(value => + { + var percentage = (int)Math.Clamp(Math.Round(value * 100), 0, 100); + progressWindow.UpdateProgress(percentage); + }); + + (bool success, string errorMessage, DirectoryMoveContentsResult details) moveResult; + try + { + moveResult = await Task.Run(() => + { + var moveSuccessful = PathManager.TrySetWheelWizardAppdataPath(targetPath, out var error, out var moveDetails, progress); + return (moveSuccessful, error, moveDetails); + }); + } + catch (Exception ex) + { + progressWindow.Close(); + WheelWizard.Logging.RecreateStaticLogger(); + SetAppDataLocationBusyState(false); + UpdateAppDataLocationUi(); + + await new MessageBoxWindow() + .SetMessageType(MessageBoxWindow.MessageType.Error) + .SetTitleText(Phrases.MessageError_DataFolderMove_Title) + .SetInfoText(ex.Message) + .ShowDialog(); + return; + } + + progressWindow.Close(); + + WheelWizard.Logging.RecreateStaticLogger(); + + SetAppDataLocationBusyState(false); + UpdateAppDataLocationUi(); + + var (success, errorMessage, details) = moveResult; + + if (success) + { + await HandleSuccessfulAppdataMoveAsync(details, errorMessage); + } + else + { + await HandleFailedAppdataMoveAsync(details, errorMessage); + } + } + + private async Task HandleSuccessfulAppdataMoveAsync(DirectoryMoveContentsResult moveDetails, string warningMessage) + { + if (moveDetails.Outcome == DirectoryMoveOutcome.SourceDeletionFailed) + { + var prompt = new YesNoWindow() + .SetMainText("Unable to delete old data folder") + .SetExtraText( + "The previous Wheel Wizard data folder could not be removed." + + $"\n\nOld location:\n{moveDetails.SourcePath}\n\n" + + $"New location:\n{moveDetails.DestinationPath}\n\n" + + "Select Revert to undo the move or Continue to keep using the new folder and leave the old files." + ) + .SetButtonText("Revert", "Continue"); + + var revert = await prompt.AwaitAnswer(); + if (revert) + { + var revertSucceeded = PathManager.TryRevertWheelWizardAppdataMove( + moveDetails.SourcePath, + moveDetails.DestinationPath, + out var revertError + ); + WheelWizard.Logging.RecreateStaticLogger(); + UpdateAppDataLocationUi(); + + if (!revertSucceeded) + { + await new MessageBoxWindow() + .SetMessageType(MessageBoxWindow.MessageType.Error) + .SetTitleText(Phrases.MessageError_DataFolderMove_Title) + .SetInfoText(revertError) + .ShowDialog(); + } + else + { + await new MessageBoxWindow() + .SetMessageType(MessageBoxWindow.MessageType.Message) + .SetTitleText("Data folder move reverted") + .SetInfoText($"Wheel Wizard will continue using:\n{moveDetails.SourcePath}") + .ShowDialog(); + } + + return; + } + } + + var infoText = + Humanizer.ReplaceDynamic(Phrases.MessageSuccess_DataFolderMoved_Extra, PathManager.WheelWizardAppdataPath) + ?? $"Wheel Wizard data is now stored in:\n{PathManager.WheelWizardAppdataPath}"; + + if (!string.IsNullOrWhiteSpace(warningMessage)) + infoText += $"\n\n{warningMessage}"; + + await new MessageBoxWindow() + .SetMessageType(MessageBoxWindow.MessageType.Message) + .SetTitleText(Phrases.MessageSuccess_DataFolderMoved_Title) + .SetInfoText(infoText) + .ShowDialog(); + } + + private async Task HandleFailedAppdataMoveAsync(DirectoryMoveContentsResult moveDetails, string errorMessage) + { + var infoText = string.IsNullOrWhiteSpace(errorMessage) ? "Failed to move the Wheel Wizard data folder." : errorMessage; + + await new MessageBoxWindow() + .SetMessageType(MessageBoxWindow.MessageType.Error) + .SetTitleText(Phrases.MessageError_DataFolderMove_Title) + .SetInfoText(infoText) + .ShowDialog(); + + if (moveDetails.Outcome is DirectoryMoveOutcome.CopyFailed or DirectoryMoveOutcome.VerificationFailed) + { + var prompt = new YesNoWindow() + .SetMainText("Revert changes?") + .SetExtraText( + $"Wheel Wizard left a partial copy in:\n{moveDetails.DestinationPath}\n\n" + + "Choose Revert to delete it now, or Continue to leave the files in place." + ) + .SetButtonText("Revert", "Continue"); + + var revert = await prompt.AwaitAnswer(); + if (revert) + { + var cleaned = PathManager.TryCleanupPartialWheelWizardAppdataMove(moveDetails.DestinationPath, out var cleanupError); + if (!cleaned) + { + await new MessageBoxWindow() + .SetMessageType(MessageBoxWindow.MessageType.Error) + .SetTitleText("Unable to remove partial files") + .SetInfoText(cleanupError) + .ShowDialog(); + } + else + { + await new MessageBoxWindow() + .SetMessageType(MessageBoxWindow.MessageType.Message) + .SetTitleText("Partial files removed") + .SetInfoText($"Removed folder:\n{moveDetails.DestinationPath}") + .ShowDialog(); + } + } + } + } + + private async void AppDataLocationBrowse_OnClick(object sender, RoutedEventArgs e) + { + if (_isMovingAppData) + return; + + var topLevel = TopLevel.GetTopLevel(this); + IStorageFolder? suggestedStart = null; + + var currentPath = PathManager.WheelWizardAppdataPath; + if (!string.IsNullOrWhiteSpace(currentPath) && Directory.Exists(currentPath)) + suggestedStart = await topLevel!.StorageProvider.TryGetFolderFromPathAsync(currentPath); + + var folders = await FilePickerHelper.SelectFolderAsync("Select Wheel Wizard data folder", suggestedStart); + if (folders == null || folders.Count == 0) + return; + + var selected = folders[0]; + var resolvedPath = await ResolveSelectedFolderPathAsync(selected); + if (string.IsNullOrWhiteSpace(resolvedPath)) + return; + + await ConfirmAndMoveAppDataAsync(resolvedPath); + } + + private async void AppDataLocationReset_OnClick(object sender, RoutedEventArgs e) + { + if (_isMovingAppData || !PathManager.IsUsingCustomWheelWizardAppdataPath) + return; + + await ConfirmAndMoveAppDataAsync(PathManager.DefaultWheelWizardAppdataFolderPath); } private async void WindowScaleDropdown_OnSelectionChanged(object sender, SelectionChangedEventArgs e) @@ -336,25 +621,27 @@ private async void WindowScaleDropdown_OnSelectionChanged(object sender, Selecti return; _editingScale = true; - var selectedLanguage = WindowScaleDropdown.SelectedItem.ToString(); - var scale = double.Parse(selectedLanguage!.Split(" ").Last().Replace("%", "")) / 100; + var selectedScale = WindowScaleDropdown.SelectedItem?.ToString() ?? "1"; + var scale = double.Parse(selectedScale.Split(" ").Last().Replace("%", "")) / 100; SettingsManager.WINDOW_SCALE.Set(scale); var seconds = 10; - string ExtraText() => - $"This change will revert in {Humanizer.HumanizeSeconds(seconds)} " + $"unless you decide to keep the change."; + + string ExtraScaleText() => + Humanizer.ReplaceDynamic(Phrases.Question_ApplyScale_Extra, Humanizer.HumanizeSeconds(seconds)) + ?? $"This will apply the new scale in {Humanizer.HumanizeSeconds(seconds)} seconds. You can cancel this by clicking Revert."; var yesNoWindow = new YesNoWindow() .SetButtonText(Common.Action_Apply, Common.Action_Revert) - .SetMainText(Phrases.PopupText_ApplyScale) - .SetExtraText(ExtraText()); + .SetMainText(Phrases.Question_ApplyScale_Title) + .SetExtraText(ExtraScaleText()); // we want to now set up a timer every second to update the text, and at the last second close the window var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; timer.Tick += (_, args) => { seconds--; - yesNoWindow.SetExtraText(ExtraText()); + yesNoWindow.SetExtraText(ExtraScaleText()); if (seconds != 0) return; yesNoWindow.Close(); @@ -374,5 +661,59 @@ string ExtraText() => _editingScale = false; } - //private void EnableAnimations_OnClick(object sender, RoutedEventArgs e) => SettingsManager.ENABLE_ANIMATIONS.Set(EnableAnimations.IsChecked == true); + private async Task ResolveSelectedFolderPathAsync(IStorageFolder? folder) + { + if (folder == null) + return null; + + var resolved = FilePickerHelper.TryResolveLocalPath(folder); + if (!string.IsNullOrWhiteSpace(resolved)) + return resolved; + + await ShowFolderSelectionErrorAsync(); + return null; + } + + private Task ShowFolderSelectionErrorAsync() + { + return new MessageBoxWindow() + .SetMessageType(MessageBoxWindow.MessageType.Error) + .SetTitleText(Phrases.MessageError_DataFolderMove_Title) + .SetInfoText("Wheel Wizard couldn't resolve the selected folder. Please choose a different location.") + .ShowDialog(); + } + + private async void WhWzLanguageDropdown_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) + { + if (WhWzLanguageDropdown.SelectedItem == null) + return; + + var selectedLanguage = WhWzLanguageDropdown.SelectedItem.ToString(); + var key = SettingValues.WhWzLanguages.FirstOrDefault(x => x.Value() == selectedLanguage).Key; + + var currentLanguage = (string)SettingsManager.WW_LANGUAGE.Get(); + if (key == null || key == currentLanguage) + return; + + // TODO: translate this popup, but support multiple languages. So it should display both NL and FR when you try to switch from NL to FR + var yesNoWindow = await new YesNoWindow() + .SetMainText("Do you want to apply the new language settings?") + .SetExtraText("This will close the current window and open a new one with the new language settings.") + .SetButtonText(Common.Action_Apply, Common.Action_Cancel) + .AwaitAnswer(); + + if (!yesNoWindow) + { + var currentWhWzLanguage = (string)SettingsManager.WW_LANGUAGE.Get(); + var whWzLanguageDisplayName = SettingValues.WhWzLanguages[currentWhWzLanguage]; + WhWzLanguageDropdown.SelectedItem = whWzLanguageDisplayName; + return; // We only want to change the setting if we really apply this change + } + + SettingsManager.WW_LANGUAGE.Set(key); + ViewUtils.RefreshWindow(); + } + + private void EnableAnimations_OnClick(object sender, RoutedEventArgs e) => + SettingsManager.ENABLE_ANIMATIONS.Set(EnableAnimations.IsChecked == true); } diff --git a/WheelWizard/Views/Pages/UserProfilePage.axaml b/WheelWizard/Views/Pages/UserProfilePage.axaml index 2443cadc..48194124 100644 --- a/WheelWizard/Views/Pages/UserProfilePage.axaml +++ b/WheelWizard/Views/Pages/UserProfilePage.axaml @@ -31,26 +31,27 @@ + ToolTip.Tip="{x:Static lang:Phrases.Hover_RegionUserSelection}" ToolTip.Placement="Top" + ToolTip.ShowDelay="20"> @@ -67,7 +68,7 @@ - + \ No newline at end of file diff --git a/WheelWizard/Views/Popups/Generic/MessageBoxWindow.axaml.cs b/WheelWizard/Views/Popups/Generic/MessageBoxWindow.axaml.cs index cba540c0..cd6c47f5 100644 --- a/WheelWizard/Views/Popups/Generic/MessageBoxWindow.axaml.cs +++ b/WheelWizard/Views/Popups/Generic/MessageBoxWindow.axaml.cs @@ -1,4 +1,5 @@ using Avalonia.Interactivity; +using WheelWizard.Resources.Languages; using WheelWizard.Views.Popups.Base; using Button = WheelWizard.Views.Components.Button; @@ -16,7 +17,7 @@ public enum MessageType private MessageType messageType = MessageType.Message; public MessageBoxWindow() - : base(true, false, true, "Message") + : base(true, false, true, Common.Attribute_Message) { InitializeComponent(); SetMessageType(messageType); @@ -44,6 +45,13 @@ public MessageBoxWindow SetInfoText(string extraText) return this; } + public MessageBoxWindow SetTag(string extraText) + { + MessageTag.Text = extraText; + MessageTagBlock.IsVisible = true; + return this; + } + protected override void BeforeOpen() => PlaySound(messageType); private static void PlaySound(MessageType messageType) diff --git a/WheelWizard/Views/Popups/Generic/OptionsWindow.axaml b/WheelWizard/Views/Popups/Generic/OptionsWindow.axaml new file mode 100644 index 00000000..cb4e1456 --- /dev/null +++ b/WheelWizard/Views/Popups/Generic/OptionsWindow.axaml @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/WheelWizard/Views/Popups/Generic/OptionsWindow.axaml.cs b/WheelWizard/Views/Popups/Generic/OptionsWindow.axaml.cs new file mode 100644 index 00000000..412e14f6 --- /dev/null +++ b/WheelWizard/Views/Popups/Generic/OptionsWindow.axaml.cs @@ -0,0 +1,81 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Threading; +using WheelWizard.Views.Components; +using WheelWizard.Views.Popups.Base; + +namespace WheelWizard.Views.Popups.Generic; + +public partial class OptionsWindow : PopupContent +{ + public string? Result { get; private set; } = null; + private TaskCompletionSource _tcs; + + public OptionsWindow() + : base(true, false, true, "Wheel Wizard") + { + InitializeComponent(); + } + + public OptionsWindow SetWindowTitle(string title) + { + Window.WindowTitle = title; + return this; + } + + public OptionsWindow AddOption(Geometry icon, string title, Action onClick, bool enabled = true) + { + var button = new OptionButton() + { + IconData = icon, + Text = title, + IsEnabled = enabled, + }; + button.Click += (_, _) => + { + onClick.Invoke(); + Result = title; + _tcs.TrySetResult(title); + Close(); + }; + + OptionList.Children.Add(button); + + OptimizeColumns(); + return this; + } + + public OptionsWindow AddOption(string iconName, string title, Action onClick, bool enabled = true) + { + return AddOption((Geometry)Application.Current!.FindResource(iconName)!, title, onClick, enabled); + } + + private void OptimizeColumns() + { + var childCount = OptionList.Children.Count; + OptionList.Columns = childCount; + if (childCount <= 4) + return; + + // with 5, 6 or 9 items, a column width of 3 just looks better + OptionList.Columns = childCount is 5 or 6 or 9 ? 3 : 4; + } + + protected override void BeforeClose() + { + // If you want to return something different, then to the TrySetResult before you close it + _tcs.TrySetResult(null); + } + + public async Task AwaitAnswer() + { + if (!Dispatcher.UIThread.CheckAccess()) + { + return await Dispatcher.UIThread.InvokeAsync(() => AwaitAnswer()); + } + _tcs = new(); + Show(); // Or ShowDialog(parentWindow) if you need it to be modal + return await _tcs.Task; + } +} diff --git a/WheelWizard/Views/Popups/Generic/ProgressWindow.axaml.cs b/WheelWizard/Views/Popups/Generic/ProgressWindow.axaml.cs index 6a488f89..b1d98168 100644 --- a/WheelWizard/Views/Popups/Generic/ProgressWindow.axaml.cs +++ b/WheelWizard/Views/Popups/Generic/ProgressWindow.axaml.cs @@ -47,14 +47,14 @@ private void InternalUpdate() var elapsedSeconds = _stopwatch.Elapsed.TotalSeconds; var remainingSeconds = (100 - _progress) / (_progress / elapsedSeconds); - var remainingText = _progress <= 0 ? Common.Term_Unknown : Humanizer.HumanizeSeconds((int)remainingSeconds); + var remainingText = _progress <= 0 ? Common.State_Unknown : Humanizer.HumanizeSeconds((int)remainingSeconds); - var bottomText = $"{Phrases.PopupText_EsimatedTimeRemaining} {remainingText}"; + var bottomText = $"{Phrases.Progress_EstimatedTimeRemaining} {remainingText}"; if (_totalMb != null) { var downloadedMb = (_progress / 100.0) * (double)_totalMb; - bottomText = $"{Common.Term_Speed}: {downloadedMb / elapsedSeconds:F2} MB/s | {bottomText}"; + bottomText = $"{Common.Attribute_Speed}: {downloadedMb / elapsedSeconds:F2} MB/s | {bottomText}"; } LiveTextBlock.Text = bottomText; @@ -77,7 +77,7 @@ public ProgressWindow SetGoal(string extraText, double? megaBytes = null) public ProgressWindow SetGoal(double megaBytes) { _totalMb = megaBytes; - GoalTextBlock.Text = Humanizer.ReplaceDynamic(Phrases.PupupText_DownloadingMb, $"{megaBytes:F2}"); + GoalTextBlock.Text = Humanizer.ReplaceDynamic(Phrases.Progress_DownloadingMb, $"{megaBytes:F2}"); return this; } diff --git a/WheelWizard/Views/Popups/Generic/TextInputWindow.axaml b/WheelWizard/Views/Popups/Generic/TextInputWindow.axaml index 4bc3c6c1..36d3f054 100644 --- a/WheelWizard/Views/Popups/Generic/TextInputWindow.axaml +++ b/WheelWizard/Views/Popups/Generic/TextInputWindow.axaml @@ -3,9 +3,9 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - xmlns:self="clr-namespace:WheelWizard.Views.Popups" xmlns:components="clr-namespace:WheelWizard.Views.Components" xmlns:behaviorComp="clr-namespace:WheelWizard.Views.BehaviorComponent" + xmlns:lang="clr-namespace:WheelWizard.Resources.Languages" xmlns:base="clr-namespace:WheelWizard.Views.Popups.Base" x:Class="WheelWizard.Views.Popups.Generic.TextInputWindow"> @@ -19,7 +19,7 @@ - @@ -29,7 +29,7 @@ HoverForeground="{StaticResource Primary200}" IconData="{StaticResource ArrowDown}" IsIconLeft="False" - IsEnabled="True" Text="View Custom Characters" IsVisible="False" + IsEnabled="True" Text="{x:Static lang:Common.Action_ViewCustomChars}" IsVisible="False" Click="CustomCharsButton_Click" Margin="0,0,0,5" /> @@ -42,11 +42,11 @@ diff --git a/WheelWizard/Views/Popups/Generic/TextInputWindow.axaml.cs b/WheelWizard/Views/Popups/Generic/TextInputWindow.axaml.cs index 4f23aaa9..73fbf1f9 100644 --- a/WheelWizard/Views/Popups/Generic/TextInputWindow.axaml.cs +++ b/WheelWizard/Views/Popups/Generic/TextInputWindow.axaml.cs @@ -1,6 +1,8 @@ using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Layout; +using WheelWizard.CustomCharacters; +using WheelWizard.Shared.DependencyInjection; using WheelWizard.Views.Popups.Base; using Button = WheelWizard.Views.Components.Button; @@ -8,6 +10,9 @@ namespace WheelWizard.Views.Popups.Generic; public partial class TextInputWindow : PopupContent { + [Inject] + private ICustomCharactersService CustomCharactersService { get; set; } = null!; + private string? _result; private TaskCompletionSource? _tcs; private string? _initialText; @@ -15,7 +20,7 @@ public partial class TextInputWindow : PopupContent // Constructor with dynamic label parameter public TextInputWindow() - : base(true, false, true, "Text Field") + : base(true, false, true, "Wheel Wizard") { InitializeComponent(); InputField.TextChanged += InputField_TextChanged; @@ -41,9 +46,15 @@ public TextInputWindow SetExtraText(string extraText) return this; } - public TextInputWindow SetAllowCustomChars(bool allow) + public TextInputWindow SetAllowCustomChars(bool allow, bool initiallyOpen = false) { CustomCharsButton.IsVisible = allow; + + if (allow && initiallyOpen) + { + CustomChars.IsVisible = true; + CustomCharsButton.IsVisible = false; + } return this; } @@ -81,54 +92,8 @@ public TextInputWindow SetValidation(Func vali private void SetupCustomChars() { CustomChars.Children.Clear(); - // All the custom chars that are grouped together - var charRanges = new List<(char, char)> - { - ((char)0x2460, (char)0x246e), - ((char)0xe000, (char)0xe01c), - ((char)0xf061, (char)0xf06d), - ((char)0xf074, (char)0xf07c), - ((char)0xf107, (char)0xf12f), - }; - - var chars = new List(); - foreach (var (start, end) in charRanges) - { - for (var i = start; i <= end; i++) - { - chars.Add(i); - } - } - // All the left-over chars that we cant make easy groups out of - chars.AddRange( - [ - (char)0xe028, - (char)0xe068, - (char)0xe067, - (char)0xe06a, - (char)0xe06b, - (char)0xf030, - (char)0xf031, - (char)0xf034, - (char)0xf035, - (char)0xf038, - (char)0xf039, - (char)0xf03c, - (char)0xf03d, - (char)0xf041, - (char)0xf043, - (char)0xf044, - (char)0xf047, - (char)0xf050, - (char)0xf058, - (char)0xf05e, - (char)0xf05f, - (char)0xf103, - ] - ); - - foreach (var c in chars) + foreach (var c in CustomCharactersService.GetCustomCharacters()) { var button = new Button() { @@ -152,7 +117,7 @@ private void InputField_TextChanged(object sender, TextChangedEventArgs e) // Update the Submit button's IsEnabled property based on input private void UpdateSubmitButtonState() { - var inputText = GetTrimmedTextInput(); + var inputText = GetInputText(); var validationResultError = inputValidationFunc?.Invoke(_initialText, inputText!).Error?.Message; SubmitButton.IsEnabled = validationResultError == null; @@ -167,12 +132,12 @@ private void CustomCharsButton_Click(object sender, EventArgs e) private void SubmitButton_Click(object sender, RoutedEventArgs e) { - _result = GetTrimmedTextInput(); + _result = GetInputText(); _tcs?.TrySetResult(_result); // Set the result of the task Close(); } - private string? GetTrimmedTextInput() => InputField.Text?.Trim(); + private string? GetInputText() => InputField.Text; private void CancelButton_Click(object sender, RoutedEventArgs e) => Close(); diff --git a/WheelWizard/Views/Popups/Generic/YesNoWindow.axaml b/WheelWizard/Views/Popups/Generic/YesNoWindow.axaml index 785326b8..906ff9a8 100644 --- a/WheelWizard/Views/Popups/Generic/YesNoWindow.axaml +++ b/WheelWizard/Views/Popups/Generic/YesNoWindow.axaml @@ -1,9 +1,9 @@ @@ -22,11 +22,11 @@ diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiCarouselWindow.axaml.cs b/WheelWizard/Views/Popups/MiiManagement/MiiCarouselWindow.axaml.cs index b12fee8e..93ca5a0f 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiCarouselWindow.axaml.cs +++ b/WheelWizard/Views/Popups/MiiManagement/MiiCarouselWindow.axaml.cs @@ -1,12 +1,13 @@ -using WheelWizard.Views.Popups.Base; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.Resources.Languages; +using WheelWizard.Views.Popups.Base; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; namespace WheelWizard.Views.Popups.MiiManagement; public partial class MiiCarouselWindow : PopupContent { public MiiCarouselWindow() - : base(true, true, false, "Mii Carousel") + : base(true, true, false, Common.PopupTitle_MiiCarousel) { InitializeComponent(); } diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorBeardPage.axaml b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorBeardPage.axaml index 61a6593e..990bd943 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorBeardPage.axaml +++ b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorBeardPage.axaml @@ -9,7 +9,7 @@ - + - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorBeardPage.axaml.cs b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorBeardPage.axaml.cs index fcd1b60b..77bcc4c8 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorBeardPage.axaml.cs +++ b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorBeardPage.axaml.cs @@ -1,8 +1,7 @@ -using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Media; -using WheelWizard.Views.Components; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.MiiManagement.Domain; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; namespace WheelWizard.Views.Popups.MiiManagement.MiiEditor; @@ -17,43 +16,46 @@ public EditorFacialHair(MiiEditorWindow ew) : base(ew) { InitializeComponent(); - if (Editor?.Mii?.MiiFacialHair == null) - return; PopulateValues(); } private void PopulateValues() { + // Attribute: var currentFacialHair = Editor.Mii.MiiFacialHair; - GenerateMustacheButtons(); - GenerateBeardButtons(); //also known as goatee internally - // Populate Facial Hair Color ComboBox - MustacheColorBox.Items.Clear(); - foreach (var color in Enum.GetNames(typeof(MustacheColor))) // Using MustacheColor enum - { - MustacheColorBox.Items.Add(color); - if (color == currentFacialHair.Color.ToString()) - MustacheColorBox.SelectedItem = color; - } - // Populate TextBlocks - UpdateValueTexts(currentFacialHair); - } - - private void GenerateBeardButtons() - { + // Colors both used for mustache and beard var color1 = new SolidColorBrush(ViewUtils.Colors.Neutral50); // Skin Color var color2 = new SolidColorBrush(ViewUtils.Colors.Neutral300); // Skin border Color var color3 = new SolidColorBrush(ViewUtils.Colors.Black); // Hair Color - var color4 = new SolidColorBrush(ViewUtils.Colors.Danger400); + var color4 = new SolidColorBrush(ViewUtils.Colors.Danger400); // NONE Color var selectedColor4 = new SolidColorBrush(ViewUtils.Colors.Danger500); + + // Mustache: + SetButtons( + "MiiMustache", + 4, + MustacheTypesGrid, + (index, button) => + { + button.IsChecked = index == (int)currentFacialHair.MiiMustacheType; + button.Color1 = color1; + button.Color2 = color2; + button.Color3 = color3; + button.Color4 = color4; + button.SelectedColor4 = selectedColor4; + button.Click += (_, _) => SetMustacheType(index); + } + ); + + // Beards: (also known as goatee internally) SetButtons( "MiiGoatee", 4, BeardTypesGrid, (index, button) => { - button.IsChecked = index == (int)Editor.Mii.MiiFacialHair.BeardType; + button.IsChecked = index == (int)currentFacialHair.MiiBeardType; button.Color1 = color1; button.Color2 = color2; button.Color3 = color3; @@ -62,6 +64,22 @@ private void GenerateBeardButtons() button.Click += (_, _) => SetBeardType(index); } ); + + // Facial Hair Color: + SetColorButtons( + MiiColorMappings.HairColor.Count, + HairColorGrid, + (index, button) => + { + button.IsChecked = index == (int)Editor.Mii.MiiFacialHair.Color; + button.Color1 = new SolidColorBrush(MiiColorMappings.HairColor[(MiiHairColor)index]); + button.Click += (_, _) => SetHairColor(index); + } + ); + + // Transform attributes: + MustacheTransformOptions.IsVisible = Editor.Mii.MiiFacialHair.MiiMustacheType != MiiMustacheType.None; + UpdateTransformTextValues(currentFacialHair); } private void SetBeardType(int index) @@ -69,82 +87,50 @@ private void SetBeardType(int index) if (Editor?.Mii?.MiiFacialHair == null || !IsLoaded) return; var current = Editor.Mii.MiiFacialHair; - var beardType = (BeardType)index; - var result = MiiFacialHair.Create(current.MustacheType, beardType, current.Color, current.Size, current.Vertical); - if (result.IsSuccess) - { - Editor.Mii.MiiFacialHair = result.Value; - UpdateValueTexts(result.Value); // Update UI TextBlocks - } - else - { - // Reset the button to the current type if creation fails - foreach (var child in BeardTypesGrid.Children) - { - if (child is MultiIconRadioButton button && button.IsChecked == true) - { - button.IsChecked = false; - } - } - } + var beardType = (MiiBeardType)index; + var result = MiiFacialHair.Create(current.MiiMustacheType, beardType, current.Color, current.Size, current.Vertical); + if (result.IsFailure) + return; + Editor.Mii.MiiFacialHair = result.Value; Editor.RefreshImage(); } - private void GenerateMustacheButtons() + private void SetHairColor(int index) { - var color1 = new SolidColorBrush(ViewUtils.Colors.Neutral50); // Skin Color - var color2 = new SolidColorBrush(ViewUtils.Colors.Neutral300); // Skin border Color - var color3 = new SolidColorBrush(ViewUtils.Colors.Black); // Hair Color - var color4 = new SolidColorBrush(ViewUtils.Colors.Danger400); - var selectedColor4 = new SolidColorBrush(ViewUtils.Colors.Danger500); - SetButtons( - "MiiMustache", - 4, - MustacheTypesGrid, - (index, button) => - { - button.IsChecked = index == (int)Editor.Mii.MiiFacialHair.MustacheType; - button.Color1 = color1; - button.Color2 = color2; - button.Color3 = color3; - button.Color4 = color4; - button.SelectedColor4 = selectedColor4; - button.Click += (_, _) => SetMustacheType(index); - } - ); + if (Editor?.Mii?.MiiFacialHair == null || !IsLoaded) + return; + var current = Editor.Mii.MiiFacialHair; + var hairColor = (MiiHairColor)index; + var result = MiiFacialHair.Create(current.MiiMustacheType, current.MiiBeardType, hairColor, current.Size, current.Vertical); + if (result.IsFailure) + return; + + Editor.Mii.MiiFacialHair = result.Value; + Editor.RefreshImage(); } private void SetMustacheType(int index) { if (Editor?.Mii?.MiiFacialHair == null || !IsLoaded) return; + var current = Editor.Mii.MiiFacialHair; - var mustacheType = (MustacheType)index; - var result = MiiFacialHair.Create(mustacheType, current.BeardType, current.Color, current.Size, current.Vertical); - if (result.IsSuccess) - { - Editor.Mii.MiiFacialHair = result.Value; - UpdateValueTexts(result.Value); // Update UI TextBlocks - } - else - { - // Reset the button to the current type if creation fails - foreach (var child in MustacheTypesGrid.Children) - { - if (child is MultiIconRadioButton button && button.IsChecked == true) - { - button.IsChecked = false; - } - } - } + var mustacheType = (MiiMustacheType)index; + var result = MiiFacialHair.Create(mustacheType, current.MiiBeardType, current.Color, current.Size, current.Vertical); + if (result.IsFailure) + return; + MustacheTransformOptions.IsVisible = mustacheType != MiiMustacheType.None; + Editor.Mii.MiiFacialHair = result.Value; Editor.RefreshImage(); } - private void UpdateValueTexts(MiiFacialHair facialHair) + #region Transform + + private void UpdateTransformTextValues(MiiFacialHair facialHair) { - VerticalValueText.Text = facialHair.Vertical.ToString(); + VerticalValueText.Text = ((facialHair.Vertical - 10) * -1).ToString(); SizeValueText.Text = facialHair.Size.ToString(); VerticalDecreaseButton.IsEnabled = facialHair.Vertical > MinVertical; @@ -153,15 +139,8 @@ private void UpdateValueTexts(MiiFacialHair facialHair) SizeIncreaseButton.IsEnabled = facialHair.Size < MaxSize; } - // Enum to identify which property is being changed by buttons - private enum FacialHairProperty - { - Vertical, - Size, - } - // Consolidated helper method for button clicks - private void TryUpdateFacialHairValue(int change, FacialHairProperty property) + private void TryUpdateFacialHairValue(int change, MiiTransformProperty property) { if (Editor?.Mii?.MiiFacialHair == null || !IsLoaded) return; @@ -175,78 +154,61 @@ private void TryUpdateFacialHairValue(int change, FacialHairProperty property) // Determine current value, new value, and range based on property switch (property) { - case FacialHairProperty.Vertical: + case MiiTransformProperty.Vertical: currentValue = current.Vertical; min = MinVertical; max = MaxVertical; break; - case FacialHairProperty.Size: + case MiiTransformProperty.Size: currentValue = current.Size; min = MinSize; max = MaxSize; break; default: - throw new ArgumentOutOfRangeException(nameof(property), property, null); + throw new ArgumentException($"{property} is not an option that you can change in FacialHair"); } newValue = currentValue + change; if (newValue < min || newValue > max) - { return; // Value is out of range, do nothing - } - OperationResult result; - switch (property) + var result = property switch { - case FacialHairProperty.Vertical: - result = MiiFacialHair.Create(current.MustacheType, current.BeardType, current.Color, current.Size, newValue); // Note Vertical position - break; - case FacialHairProperty.Size: - result = MiiFacialHair.Create(current.MustacheType, current.BeardType, current.Color, newValue, current.Vertical); // Note Size position - break; - default: // Should be unreachable - return; - } + MiiTransformProperty.Vertical => MiiFacialHair.Create( + current.MiiMustacheType, + current.MiiBeardType, + current.Color, + current.Size, + newValue + ) // Note Vertical position + , + MiiTransformProperty.Size => MiiFacialHair.Create( + current.MiiMustacheType, + current.MiiBeardType, + current.Color, + newValue, + current.Vertical + ) // Note Size position + , + _ => throw new ArgumentException($"{property} is not an option that you can change in FacialHair"), + }; // Handle the result if (result.IsFailure) return; Editor.Mii.MiiFacialHair = result.Value; - UpdateValueTexts(result.Value); // Update UI TextBlocks + UpdateTransformTextValues(result.Value); // Update UI TextBlocks Editor.RefreshImage(); } - private void FacialHairColorBox_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) - { - if (!IsLoaded || MustacheColorBox.SelectedItem == null || Editor?.Mii?.MiiFacialHair == null) - return; - if (MustacheColorBox.SelectedItem is not string colorStr) - return; - - var newColor = (MustacheColor)Enum.Parse(typeof(MustacheColor), colorStr); - var current = Editor.Mii.MiiFacialHair; - if (newColor == current.Color) - return; - - var result = MiiFacialHair.Create(current.MustacheType, current.BeardType, newColor, current.Size, current.Vertical); - if (result.IsSuccess) - { - Editor.Mii.MiiFacialHair = result.Value; - } - else - { - MustacheColorBox.SelectedItem = current.Color.ToString(); - } - Editor.RefreshImage(); - } + private void VerticalDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateFacialHairValue(-1, MiiTransformProperty.Vertical); - // --- Button Click Handlers --- - private void VerticalDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateFacialHairValue(-1, FacialHairProperty.Vertical); + private void VerticalIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateFacialHairValue(+1, MiiTransformProperty.Vertical); - private void VerticalIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateFacialHairValue(+1, FacialHairProperty.Vertical); + private void SizeDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateFacialHairValue(-1, MiiTransformProperty.Size); - private void SizeDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateFacialHairValue(-1, FacialHairProperty.Size); + private void SizeIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateFacialHairValue(+1, MiiTransformProperty.Size); - private void SizeIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateFacialHairValue(+1, FacialHairProperty.Size); + #endregion } diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorEyebrows.axaml b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorEyebrows.axaml index b7249821..9b8521ef 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorEyebrows.axaml +++ b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorEyebrows.axaml @@ -8,7 +8,7 @@ x:Class="WheelWizard.Views.Popups.MiiManagement.MiiEditor.EditorEyebrows"> - + - - - - + + + + + + - + @@ -39,7 +40,7 @@ - + @@ -51,7 +52,7 @@ - + @@ -64,7 +65,7 @@ - + @@ -74,7 +75,7 @@ - + diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorEyebrows.axaml.cs b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorEyebrows.axaml.cs index 16104456..561aa25f 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorEyebrows.axaml.cs +++ b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorEyebrows.axaml.cs @@ -1,8 +1,7 @@ -using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Media; -using WheelWizard.Views.Components; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.MiiManagement.Domain; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; namespace WheelWizard.Views.Popups.MiiManagement.MiiEditor; @@ -22,30 +21,16 @@ public EditorEyebrows(MiiEditorWindow ew) : base(ew) { InitializeComponent(); - if (Editor?.Mii?.MiiEyebrows == null) - return; PopulateValues(); } private void PopulateValues() { + // Attribute: var currentEyebrows = Editor.Mii.MiiEyebrows; - CreateEyebrowButtons(); - EyebrowColorBox.Items.Clear(); - foreach (var color in Enum.GetNames(typeof(EyebrowColor))) - { - EyebrowColorBox.Items.Add(color); - if (color == currentEyebrows.Color.ToString()) - EyebrowColorBox.SelectedItem = color; - } - // Populate TextBlocks - UpdateValueTexts(currentEyebrows); - } - - private void CreateEyebrowButtons() - { - var color1 = new SolidColorBrush(ViewUtils.Colors.Danger400); + // Eyebrows: + var color1 = new SolidColorBrush(ViewUtils.Colors.Danger400); // NONE Color var selectedColor1 = new SolidColorBrush(ViewUtils.Colors.Danger500); var color2 = new SolidColorBrush(ViewUtils.Colors.Black); // Eyebrow Color SetButtons( @@ -54,13 +39,28 @@ private void CreateEyebrowButtons() EyebrowTypesGrid, (index, button) => { - button.IsChecked = index == Editor.Mii.MiiEyebrows.Type; + button.IsChecked = index == currentEyebrows.Type; button.Color1 = color1; button.SelectedColor1 = selectedColor1; button.Color2 = color2; button.Click += (_, _) => SetEyebrowType(index); } ); + + // Eyebrow Color: + SetColorButtons( + MiiColorMappings.HairColor.Count, + HairColorGrid, + (index, button) => + { + button.IsChecked = index == (int)Editor.Mii.MiiEyebrows.Color; + button.Color1 = new SolidColorBrush(MiiColorMappings.HairColor[(MiiHairColor)index]); + button.Click += (_, _) => SetEyebrowColor(index); + } + ); + + // Transform attributes: + UpdateTransformTextValues(currentEyebrows); } private void SetEyebrowType(int index) @@ -73,32 +73,43 @@ private void SetEyebrowType(int index) return; var result = MiiEyebrow.Create(index, current.Rotation, current.Color, current.Size, current.Vertical, current.Spacing); - if (result.IsSuccess) - { - Editor.Mii.MiiEyebrows = result.Value; - } - else - { - // Reset the button to the current type if creation fails - foreach (var child in EyebrowTypesGrid.Children) - { - if (child is MultiIconRadioButton button && button.IsChecked == true) - button.IsChecked = false; - } + if (result.IsFailure) + return; - var currentButton = EyebrowTypesGrid.Children[index] as MultiIconRadioButton; - currentButton.IsChecked = true; - } + Editor.Mii.MiiEyebrows = result.Value; + Editor.RefreshImage(); + } + + private void SetEyebrowColor(int index) + { + if (Editor?.Mii?.MiiEyebrows == null) + return; + + var current = Editor.Mii.MiiEyebrows; + if (index == current.Type) + return; + + var result = MiiEyebrow.Create( + current.Type, + current.Rotation, + (MiiHairColor)index, + current.Size, + current.Vertical, + current.Spacing + ); + if (result.IsFailure) + return; + + Editor.Mii.MiiEyebrows = result.Value; Editor.RefreshImage(); } - // Helper to update all value TextBlocks - private void UpdateValueTexts(MiiEyebrow eyebrows) + private void UpdateTransformTextValues(MiiEyebrow eyebrows) { SizeValueText.Text = eyebrows.Size.ToString(); - RotationValueText.Text = eyebrows.Rotation.ToString(); + RotationValueText.Text = (eyebrows.Rotation - 6).ToString(); SpacingValueText.Text = eyebrows.Spacing.ToString(); - VerticalValueText.Text = (eyebrows.Vertical - 3).ToString(); // lying a bit here, but then people are not questions why it goes from 3 instead of 0 + VerticalValueText.Text = ((eyebrows.Vertical - 10) * -1).ToString(); VerticalDecreaseButton.IsEnabled = eyebrows.Vertical > MinVertical; VerticalIncreaseButton.IsEnabled = eyebrows.Vertical < MaxVertical; @@ -110,15 +121,9 @@ private void UpdateValueTexts(MiiEyebrow eyebrows) SpacingIncreaseButton.IsEnabled = eyebrows.Spacing < MaxSpacing; } - private enum EyebrowProperty - { - Vertical, - Size, - Rotation, - Spacing, - } + #region Transform - private void TryUpdateEyebrowValue(int change, EyebrowProperty property) + private void TryUpdateEyebrowValue(int change, MiiTransformProperty property) { if (Editor?.Mii?.MiiEyebrows == null || !IsLoaded) return; @@ -132,28 +137,28 @@ private void TryUpdateEyebrowValue(int change, EyebrowProperty property) // Determine current value, new value, and range based on property switch (property) { - case EyebrowProperty.Vertical: + case MiiTransformProperty.Vertical: currentValue = current.Vertical; min = MinVertical; max = MaxVertical; break; - case EyebrowProperty.Size: + case MiiTransformProperty.Size: currentValue = current.Size; min = MinSize; max = MaxSize; break; - case EyebrowProperty.Rotation: + case MiiTransformProperty.Rotation: currentValue = current.Rotation; min = MinRotation; max = MaxRotation; break; - case EyebrowProperty.Spacing: + case MiiTransformProperty.Spacing: currentValue = current.Spacing; min = MinSpacing; max = MaxSpacing; break; default: - throw new ArgumentOutOfRangeException(nameof(property), property, null); + throw new ArgumentException($"{property} is not an option that you can change in Eyebrow"); } newValue = currentValue + change; @@ -162,70 +167,66 @@ private void TryUpdateEyebrowValue(int change, EyebrowProperty property) if (newValue < min || newValue > max) return; - OperationResult result; - switch (property) + var result = property switch { - case EyebrowProperty.Vertical: - result = MiiEyebrow.Create(current.Type, current.Rotation, current.Color, current.Size, newValue, current.Spacing); - break; - case EyebrowProperty.Size: - result = MiiEyebrow.Create(current.Type, current.Rotation, current.Color, newValue, current.Vertical, current.Spacing); - break; - case EyebrowProperty.Rotation: - result = MiiEyebrow.Create(current.Type, newValue, current.Color, current.Size, current.Vertical, current.Spacing); - break; - case EyebrowProperty.Spacing: - result = MiiEyebrow.Create(current.Type, current.Rotation, current.Color, current.Size, current.Vertical, newValue); - break; - default: - return; - } + MiiTransformProperty.Vertical => MiiEyebrow.Create( + current.Type, + current.Rotation, + current.Color, + current.Size, + newValue, + current.Spacing + ), + MiiTransformProperty.Size => MiiEyebrow.Create( + current.Type, + current.Rotation, + current.Color, + newValue, + current.Vertical, + current.Spacing + ), + MiiTransformProperty.Rotation => MiiEyebrow.Create( + current.Type, + newValue, + current.Color, + current.Size, + current.Vertical, + current.Spacing + ), + MiiTransformProperty.Spacing => MiiEyebrow.Create( + current.Type, + current.Rotation, + current.Color, + current.Size, + current.Vertical, + newValue + ), + _ => throw new ArgumentException($"{property} is not an option that you can change in Eyebrow"), + }; if (result.IsFailure) return; Editor.Mii.MiiEyebrows = result.Value; - UpdateValueTexts(result.Value); + UpdateTransformTextValues(result.Value); Editor.RefreshImage(); } - private void EyebrowColorBox_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) - { - if (!IsLoaded || EyebrowColorBox.SelectedItem == null || Editor?.Mii?.MiiEyebrows == null) - return; - if (EyebrowColorBox.SelectedItem is not string colorStr) - return; - - var newColor = (EyebrowColor)Enum.Parse(typeof(EyebrowColor), colorStr); - var current = Editor.Mii.MiiEyebrows; - if (newColor == current.Color) - return; - - var result = MiiEyebrow.Create(current.Type, current.Rotation, newColor, current.Size, current.Vertical, current.Spacing); - if (result.IsSuccess) - { - Editor.Mii.MiiEyebrows = result.Value; - } - else - { - EyebrowColorBox.SelectedItem = current.Color.ToString(); - } - Editor.RefreshImage(); - } + private void VerticalDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyebrowValue(-1, MiiTransformProperty.Vertical); - private void VerticalDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyebrowValue(-1, EyebrowProperty.Vertical); + private void VerticalIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyebrowValue(+1, MiiTransformProperty.Vertical); - private void VerticalIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyebrowValue(+1, EyebrowProperty.Vertical); + private void SizeDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyebrowValue(-1, MiiTransformProperty.Size); - private void SizeDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyebrowValue(-1, EyebrowProperty.Size); + private void SizeIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyebrowValue(+1, MiiTransformProperty.Size); - private void SizeIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyebrowValue(+1, EyebrowProperty.Size); + private void RotationDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyebrowValue(-1, MiiTransformProperty.Rotation); - private void RotationDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyebrowValue(-1, EyebrowProperty.Rotation); + private void RotationIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyebrowValue(+1, MiiTransformProperty.Rotation); - private void RotationIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyebrowValue(+1, EyebrowProperty.Rotation); + private void SpacingDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyebrowValue(-1, MiiTransformProperty.Spacing); - private void SpacingDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyebrowValue(-1, EyebrowProperty.Spacing); + private void SpacingIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyebrowValue(+1, MiiTransformProperty.Spacing); - private void SpacingIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyebrowValue(+1, EyebrowProperty.Spacing); + #endregion } diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorEyes.axaml b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorEyes.axaml index 14c4ad0a..5fcb7460 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorEyes.axaml +++ b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorEyes.axaml @@ -9,7 +9,7 @@ - + - - + + + + + - + @@ -46,7 +45,7 @@ - + @@ -58,7 +57,7 @@ - + @@ -70,7 +69,7 @@ - + diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorEyes.axaml.cs b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorEyes.axaml.cs index 8008b8b1..0bf29744 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorEyes.axaml.cs +++ b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorEyes.axaml.cs @@ -1,8 +1,7 @@ -using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Media; -using WheelWizard.Views.Components; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.MiiManagement.Domain; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; namespace WheelWizard.Views.Popups.MiiManagement.MiiEditor; @@ -21,27 +20,15 @@ public EditorEyes(MiiEditorWindow ew) : base(ew) { InitializeComponent(); - if (Editor?.Mii?.MiiEyes == null) - return; PopulateValues(); } private void PopulateValues() { + // Attribute: var currentEyes = Editor.Mii.MiiEyes; - GenerateEyeButtons(); - EyeColorBox.Items.Clear(); - foreach (var color in Enum.GetNames(typeof(EyeColor))) - { - EyeColorBox.Items.Add(color); - if (color == currentEyes.Color.ToString()) - EyeColorBox.SelectedItem = color; - } - UpdateValueTexts(currentEyes); - } - private void GenerateEyeButtons() - { + // Eyes: var color1 = new SolidColorBrush(ViewUtils.Colors.Neutral50); // Eye white Color var color2 = new SolidColorBrush(ViewUtils.Colors.Neutral950); // Eye border Color var selectedColor2 = new SolidColorBrush(ViewUtils.Colors.Black); @@ -53,49 +40,67 @@ private void GenerateEyeButtons() EyeTypesGrid, (index, button) => { - button.IsChecked = index == Editor.Mii.MiiEyes.Type; + button.IsChecked = index == currentEyes.Type; button.Color1 = color1; button.Color2 = color2; button.SelectedColor2 = selectedColor2; button.Color3 = color3; - button.Click += (_, _) => SetEyesType(index); + button.Click += (_, _) => SetEyeType(index); button.SelectedColor3 = selectedColor3; } ); + + // Eye Color: + SetColorButtons( + MiiColorMappings.EyeColor.Count, + EyeColorGrid, + (index, button) => + { + button.IsChecked = index == (int)Editor.Mii.MiiEyes.Color; + button.Color1 = new SolidColorBrush(MiiColorMappings.EyeColor[(MiiEyeColor)index]); + button.Click += (_, _) => SetEyeColor(index); + } + ); + + // Transform attributes: + UpdateTransformTextValues(currentEyes); } - private void SetEyesType(int index) + private void SetEyeType(int index) { - if (Editor?.Mii?.MiiEyes == null) + var current = Editor.Mii.MiiEyes; + if (index == current.Type) + return; + + var result = MiiEye.Create(index, current.Rotation, current.Vertical, current.Color, current.Size, current.Spacing); + if (result.IsFailure) return; + Editor.Mii.MiiEyes = result.Value; + Editor.RefreshImage(); + } + + private void SetEyeColor(int index) + { var current = Editor.Mii.MiiEyes; if (index == current.Type) return; - var result = MiiEye.Create(index, current.Rotation, current.Vertical, current.Color, current.Size, current.Spacing); - if (result.IsSuccess) - { - Editor.Mii.MiiEyes = result.Value; - UpdateValueTexts(result.Value); - } - else - { - // Reset to previous value - var currentType = current.Type; - foreach (var item in EyeTypesGrid.Children.OfType()) - { - item.IsChecked = item.IconData == GetMiiIconData($"Eyes{currentType:D2}"); - } - } + var result = MiiEye.Create(current.Type, current.Rotation, current.Vertical, (MiiEyeColor)index, current.Size, current.Spacing); + if (result.IsFailure) + return; + + Editor.Mii.MiiEyes = result.Value; Editor.RefreshImage(); } - private void UpdateValueTexts(MiiEye eyes) + #region Transform + + private void UpdateTransformTextValues(MiiEye eyes) { - VerticalValueText.Text = eyes.Vertical.ToString(); + VerticalValueText.Text = ((eyes.Vertical - 12) * -1).ToString(); SizeValueText.Text = eyes.Size.ToString(); - RotationValueText.Text = eyes.Rotation.ToString(); + RotationValueText.Text = (eyes.Rotation - 4).ToString(); SpacingValueText.Text = eyes.Spacing.ToString(); VerticalDecreaseButton.IsEnabled = eyes.Vertical > MinVertical; @@ -108,15 +113,7 @@ private void UpdateValueTexts(MiiEye eyes) SpacingIncreaseButton.IsEnabled = eyes.Spacing < MaxSpacing; } - private enum EyeProperty - { - Vertical, - Size, - Rotation, - Spacing, - } - - private void TryUpdateEyeValue(int change, EyeProperty property) + private void TryUpdateEyeValue(int change, MiiTransformProperty property) { if (Editor?.Mii?.MiiEyes == null || !IsLoaded) return; @@ -128,100 +125,94 @@ private void TryUpdateEyeValue(int change, EyeProperty property) max; switch (property) { - case EyeProperty.Vertical: + case MiiTransformProperty.Vertical: currentValue = current.Vertical; min = MinVertical; max = MaxVertical; break; - case EyeProperty.Size: + case MiiTransformProperty.Size: currentValue = current.Size; min = MinSize; max = MaxSize; break; - case EyeProperty.Rotation: + case MiiTransformProperty.Rotation: currentValue = current.Rotation; min = MinRotation; max = MaxRotation; break; - case EyeProperty.Spacing: + case MiiTransformProperty.Spacing: currentValue = current.Spacing; min = MinSpacing; max = MaxSpacing; break; default: - throw new ArgumentOutOfRangeException(nameof(property), property, null); + throw new ArgumentException($"{property} is not an option that you can change in Eye"); } newValue = currentValue + change; if (newValue < min || newValue > max) - { return; - } - OperationResult result; - switch (property) + var result = property switch { - case EyeProperty.Vertical: - result = MiiEye.Create(current.Type, current.Rotation, newValue, current.Color, current.Size, current.Spacing); // Note Vertical position - break; - case EyeProperty.Size: - result = MiiEye.Create(current.Type, current.Rotation, current.Vertical, current.Color, newValue, current.Spacing); // Note Size position - break; - case EyeProperty.Rotation: - result = MiiEye.Create(current.Type, newValue, current.Vertical, current.Color, current.Size, current.Spacing); // Note Rotation position - break; - case EyeProperty.Spacing: - result = MiiEye.Create(current.Type, current.Rotation, current.Vertical, current.Color, current.Size, newValue); // Note Spacing position - break; - default: - return; - } + MiiTransformProperty.Vertical => MiiEye.Create( + current.Type, + current.Rotation, + newValue, + current.Color, + current.Size, + current.Spacing + ), + MiiTransformProperty.Size => MiiEye.Create( + current.Type, + current.Rotation, + current.Vertical, + current.Color, + newValue, + current.Spacing + ), + MiiTransformProperty.Rotation => MiiEye.Create( + current.Type, + newValue, + current.Vertical, + current.Color, + current.Size, + current.Spacing + ), + MiiTransformProperty.Spacing => MiiEye.Create( + current.Type, + current.Rotation, + current.Vertical, + current.Color, + current.Size, + newValue + ), + _ => throw new ArgumentException($"{property} is not an option that you can change in Eye"), + }; if (result.IsFailure) return; Editor.Mii.MiiEyes = result.Value; - UpdateValueTexts(result.Value); + UpdateTransformTextValues(result.Value); Editor.RefreshImage(); } - private void EyeColorBox_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) - { - if (!IsLoaded || EyeColorBox.SelectedItem == null || Editor?.Mii?.MiiEyes == null) - return; - if (EyeColorBox.SelectedItem is not string colorStr) - return; - - var newColor = (EyeColor)Enum.Parse(typeof(EyeColor), colorStr); - var current = Editor.Mii.MiiEyes; - if (newColor == current.Color) - return; - - var result = MiiEye.Create(current.Type, current.Rotation, current.Vertical, newColor, current.Size, current.Spacing); - if (result.IsSuccess) - { - Editor.Mii.MiiEyes = result.Value; - } - else - { - EyeColorBox.SelectedItem = current.Color.ToString(); - } - Editor.RefreshImage(); - } + private void VerticalDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyeValue(-1, MiiTransformProperty.Vertical); - private void VerticalDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyeValue(-1, EyeProperty.Vertical); + private void VerticalIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyeValue(+1, MiiTransformProperty.Vertical); - private void VerticalIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyeValue(+1, EyeProperty.Vertical); + private void SizeDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyeValue(-1, MiiTransformProperty.Size); - private void SizeDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyeValue(-1, EyeProperty.Size); + private void SizeIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyeValue(+1, MiiTransformProperty.Size); - private void SizeIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyeValue(+1, EyeProperty.Size); + private void RotationDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyeValue(-1, MiiTransformProperty.Rotation); - private void RotationDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyeValue(-1, EyeProperty.Rotation); + private void RotationIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyeValue(+1, MiiTransformProperty.Rotation); - private void RotationIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyeValue(+1, EyeProperty.Rotation); + private void SpacingDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyeValue(-1, MiiTransformProperty.Spacing); - private void SpacingDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyeValue(-1, EyeProperty.Spacing); + private void SpacingIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyeValue(+1, MiiTransformProperty.Spacing); - private void SpacingIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateEyeValue(+1, EyeProperty.Spacing); + #endregion } diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorFace.axaml b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorFace.axaml index 0ba6cd31..82d98673 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorFace.axaml +++ b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorFace.axaml @@ -9,7 +9,7 @@ - + - + - + - + SkinColors = new() - { - [MiiSkinColor.Light] = (Color.FromRgb(255, 242, 226), Color.FromRgb(224, 194, 154)), - [MiiSkinColor.Yellow] = (Color.FromRgb(255, 208, 132), Color.FromRgb(229, 167, 80)), - [MiiSkinColor.Red] = (Color.FromRgb(222, 154, 91), Color.FromRgb(224, 118, 48)), - [MiiSkinColor.Pink] = (Color.FromRgb(255, 203, 195), Color.FromRgb(255, 173, 158)), - [MiiSkinColor.DarkBrown] = (Color.FromRgb(194, 106, 33), Color.FromRgb(154, 69, 18)), - [MiiSkinColor.Brown] = (Color.FromRgb(145, 98, 40), Color.FromRgb(58, 43, 11)), - }; - private void PopulateValues() { + // Attribute: + var currentFacial = Editor.Mii.MiiFacialFeatures; + + // Facial features: foreach (var feature in Enum.GetNames(typeof(MiiFacialFeature))) { FacialFeatureBox.Items.Add(feature); - if (feature == Editor.Mii.MiiFacial.FacialFeature.ToString()) + if (feature == currentFacial.FacialFeature.ToString()) FacialFeatureBox.SelectedItem = feature; } - CreateSkinColorButtons(); - CreateHeadShapeButtons(); - } - private void CreateHeadShapeButtons() - { - var color1 = new SolidColorBrush(ViewUtils.Colors.Neutral50); // Skin Color - var color2 = new SolidColorBrush(ViewUtils.Colors.Neutral300); // Skin border Color - var color3 = new SolidColorBrush(Colors.Transparent); // Face features - SetButtons( - "MiiFace", - 8, - HeadTypesGrid, + // Skin color: + SetColorButtons( + MiiColorMappings.SkinColor.Count, + SkinColorGrid, (index, button) => { - button.IsChecked = index == (int)Editor.Mii.MiiFacial.FaceShape; - button.Color1 = color1; - button.Color2 = color2; - button.Color3 = color3; - button.Click += (_, _) => SetFaceType(index); + button.IsChecked = index == (int)currentFacial.SkinColor; + button.Color1 = new SolidColorBrush(MiiColorMappings.SkinColor[(MiiSkinColor)index]); + button.Click += (_, _) => SetSkinColor(index); } ); - } - private void CreateSkinColorButtons() - { + // Head shape: + var headShapeColor1 = new SolidColorBrush(ViewUtils.Colors.Neutral50); // Skin Color + var headShapeColor2 = new SolidColorBrush(ViewUtils.Colors.Neutral300); // Skin border Color + var headShapeColor3 = new SolidColorBrush(Colors.Transparent); // Face features SetButtons( - "Color", - 6, - SkinColorGrid, + "MiiFace", + 8, + HeadTypesGrid, (index, button) => { - button.IsChecked = index == (int)Editor.Mii.MiiFacial.SkinColor; - button.Color1 = new SolidColorBrush(SkinColors[(MiiSkinColor)index].Item1); - button.Color2 = new SolidColorBrush(SkinColors[(MiiSkinColor)index].Item2); - button.Click += (_, _) => SetSkinColor(index); + button.IsChecked = index == (int)currentFacial.FaceShape; + button.Color1 = headShapeColor1; + button.Color2 = headShapeColor2; + button.Color3 = headShapeColor3; + button.Click += (_, _) => SetFaceType(index); } ); } private void SetFaceType(int index) { - var current = Editor.Mii.MiiFacial; + var current = Editor.Mii.MiiFacialFeatures; //face shape is an enum so when comparing if (index == (int)current.FaceShape) return; // No change @@ -87,13 +74,13 @@ private void SetFaceType(int index) if (result.IsFailure) return; - Editor.Mii.MiiFacial = result.Value; + Editor.Mii.MiiFacialFeatures = result.Value; Editor.RefreshImage(); } private void SetSkinColor(int index) { - var current = Editor.Mii.MiiFacial; + var current = Editor.Mii.MiiFacialFeatures; //face shape is an enum so when comparing if (index == (int)current.SkinColor) return; // No change @@ -107,7 +94,7 @@ private void SetSkinColor(int index) if (result.IsFailure) return; - Editor.Mii.MiiFacial = result.Value; + Editor.Mii.MiiFacialFeatures = result.Value; Editor.RefreshImage(); } @@ -118,7 +105,7 @@ private void FacialFeatureBox_OnSelectionChanged(object? sender, SelectionChange return; var selectedFeature = (MiiFacialFeature)Enum.Parse(typeof(MiiFacialFeature), value.ToString()!); - var currentFacial = Editor.Mii.MiiFacial; + var currentFacial = Editor.Mii.MiiFacialFeatures; if (selectedFeature == currentFacial.FacialFeature) return; // No change @@ -133,11 +120,11 @@ private void FacialFeatureBox_OnSelectionChanged(object? sender, SelectionChange if (result.IsFailure) { - ViewUtils.ShowSnackbar($"Error updating Facial Feature: {result.Error.Message}"); + ViewUtils.ShowSnackbar($"Error updating Facial Feature: {result.Error.Message}", ViewUtils.SnackbarType.Danger); return; } - Editor.Mii.MiiFacial = result.Value; + Editor.Mii.MiiFacialFeatures = result.Value; Editor.RefreshImage(); } } diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorGeneral.axaml b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorGeneral.axaml index 8bb577d1..b44eafcd 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorGeneral.axaml +++ b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorGeneral.axaml @@ -9,7 +9,7 @@ x:Class="WheelWizard.Views.Popups.MiiManagement.MiiEditor.EditorGeneral"> - + - - + + + + + + - - + + + + + + + - + - - + - + - + - - - - - - + + + + + + + + diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorGeneral.axaml.cs b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorGeneral.axaml.cs index 8e651c82..2f349c77 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorGeneral.axaml.cs +++ b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorGeneral.axaml.cs @@ -1,9 +1,13 @@ using Avalonia.Controls; using Avalonia.Controls.Primitives; -using Avalonia.Input; using Avalonia.Interactivity; +using Avalonia.Media; using Avalonia.Threading; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.Helpers; +using WheelWizard.Resources.Languages; +using WheelWizard.Views.Popups.Generic; +using WheelWizard.WiiManagement.MiiManagement.Domain; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; namespace WheelWizard.Views.Popups.MiiManagement.MiiEditor; @@ -23,17 +27,25 @@ public EditorGeneral(MiiEditorWindow ew) private void PopulateValues() { + // random attributes: MiiName.Text = Editor.Mii.Name.ToString(); CreatorName.Text = Editor.Mii.CreatorName.ToString(); GirlToggle.IsChecked = Editor.Mii.IsGirl; + AllowCopy.IsChecked = Editor.Mii.CustomData.IsCopyable; LengthSlider.Value = Editor.Mii.Height.Value; WidthSlider.Value = Editor.Mii.Weight.Value; - foreach (var color in Enum.GetNames(typeof(MiiFavoriteColor))) - { - FavoriteColorBox.Items.Add(color); - if (color == Editor.Mii.MiiFavoriteColor.ToString()) - FavoriteColorBox.SelectedItem = color; - } + + // Favorite color: + SetColorButtons( + MiiColorMappings.FavoriteColor.Count, + FavoriteColorGrid, + (index, button) => + { + button.IsChecked = index == (int)Editor.Mii.MiiFavoriteColor; + button.Color1 = new SolidColorBrush(MiiColorMappings.FavoriteColor[(MiiFavoriteColor)index]); + button.Click += (_, _) => SetSkinColor(index); + } + ); } protected override void BeforeBack() @@ -44,18 +56,22 @@ protected override void BeforeBack() if (!_hasCreatorNameError) Editor.Mii.CreatorName = new(CreatorName.Text); - // For now i put it here, since i dont thing we want each value to be set when you change length or width - // only when you stop moving that bar do we want that i think + // For nowI put it here, since I don't think we want each value to be set when you change length or width + // only when you stop moving that bar so we want that, I think at least Editor.RefreshImage(); } // We only have to check if it's a female, since if it's not, we already know the other option is going to be the male - private void IsGirl_OnIsCheckedChanged(object? sender, RoutedEventArgs e) => Editor.Mii.IsGirl = GirlToggle.IsChecked == true; + private void IsGirl_OnIsCheckedChanged(object? sender, RoutedEventArgs e) + { + Editor.Mii.IsGirl = GirlToggle.IsChecked == true; + Editor.RefreshImage(); + } private void Name_TextChanged(object sender, TextChangedEventArgs e) { // MiiName - var validationMiiNameResult = ValidateMiiName(MiiName.Text); + var validationMiiNameResult = ValidateMiiName(null, MiiName.Text); _hasMiiNameError = validationMiiNameResult.IsFailure; MiiName.ErrorMessage = validationMiiNameResult.Error?.Message ?? ""; @@ -65,18 +81,20 @@ private void Name_TextChanged(object sender, TextChangedEventArgs e) CreatorName.ErrorMessage = validationCreatorNameResult.Error?.Message ?? ""; } - private OperationResult ValidateMiiName(string newName) + private OperationResult ValidateMiiName(string? _, string newName) { + newName = newName?.Trim(); if (newName.Length is > 10 or < 3) - return Fail("Name must be between 3 and 10 characters long."); + return Fail(Phrases.HelperNote_NameMustBetween); return Ok(); } private OperationResult ValidateCreatorName(string newName) { + newName = newName?.Trim(); if (newName.Length > 10) - return Fail("Creator name must be less than 10 characters long."); + return Fail(Phrases.HelperNote_CreatorNameLess11); return Ok(); } @@ -115,15 +133,9 @@ private void Width_OnValueChanged(object? sender, RangeBaseValueChangedEventArgs RestartRefreshTimer(); } - private void FavoriteColorBox_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) + private void SetSkinColor(int index) { - var value = FavoriteColorBox.SelectedItem; - if (value is null) - return; - var color = (MiiFavoriteColor)Enum.Parse(typeof(MiiFavoriteColor), value.ToString()!); - if (color == Editor.Mii.MiiFavoriteColor) - return; - Editor.Mii.MiiFavoriteColor = color; + Editor.Mii.MiiFavoriteColor = (MiiFavoriteColor)index; Editor.RefreshImage(); } @@ -138,4 +150,23 @@ private void RefreshTimer_Tick(object? sender, EventArgs e) _refreshTimer.Stop(); Editor.RefreshImage(); } + + private async void ComplexName_OnClick(object? sender, RoutedEventArgs e) + { + var textPopup = new TextInputWindow() + .SetMainText(Phrases.Question_EnterNewName_Title) + .SetExtraText(Humanizer.ReplaceDynamic(Phrases.Question_EnterNewName_Extra, MiiName.Text)) + .SetAllowCustomChars(true, true) + .SetValidation(ValidateMiiName) + .SetInitialText(MiiName.Text) + .SetPlaceholderText(Phrases.Placeholder_EnterMiiName); + var newName = await textPopup.ShowDialog(); + + if (string.IsNullOrWhiteSpace(newName)) + return; + MiiName.Text = newName; + } + + private void AllowCopy_OnIsCheckedChanged(object? sender, RoutedEventArgs e) => + Editor.Mii.CustomData.IsCopyable = AllowCopy.IsChecked == true; } diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorGlasses.axaml b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorGlasses.axaml index 89ab2617..9c9fc8b0 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorGlasses.axaml +++ b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorGlasses.axaml @@ -9,7 +9,7 @@ - + - + @@ -31,17 +31,15 @@ - - + + + + - + @@ -53,7 +51,7 @@ - + diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorGlasses.axaml.cs b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorGlasses.axaml.cs index 29639f0c..f96b3977 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorGlasses.axaml.cs +++ b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorGlasses.axaml.cs @@ -1,8 +1,7 @@ -using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Media; -using WheelWizard.Views.Components; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.MiiManagement.Domain; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; namespace WheelWizard.Views.Popups.MiiManagement.MiiEditor; @@ -17,33 +16,19 @@ public EditorGlasses(MiiEditorWindow ew) : base(ew) { InitializeComponent(); - if (Editor?.Mii?.MiiGlasses == null) - return; PopulateValues(); } private void PopulateValues() { + // Attribute: var currentGlasses = Editor.Mii.MiiGlasses; - GlassesColorBox.Items.Clear(); - foreach (var color in Enum.GetNames(typeof(GlassesColor))) - { - GlassesColorBox.Items.Add(color); - if (color == currentGlasses.Color.ToString()) - GlassesColorBox.SelectedItem = color; - } - GenerateGlassesButtons(); - UpdateValueTexts(currentGlasses); - HideIfNoGlasses.IsVisible = Editor.Mii.MiiGlasses.Type != GlassesType.None; - } - - private void GenerateGlassesButtons() - { + // Glasses: var color1 = new SolidColorBrush(ViewUtils.Colors.Neutral50); // Skin Color var color2 = new SolidColorBrush(ViewUtils.Colors.Neutral300); // Skin border Color var color3 = new SolidColorBrush(ViewUtils.Colors.Neutral600); // Glass Color - var color4 = new SolidColorBrush(ViewUtils.Colors.Danger400); + var color4 = new SolidColorBrush(ViewUtils.Colors.Danger400); // NONE Color var selectedColor4 = new SolidColorBrush(ViewUtils.Colors.Danger500); SetButtons( "MiiGlasses", @@ -51,7 +36,7 @@ private void GenerateGlassesButtons() GlassesTypesGrid, (index, button) => { - button.IsChecked = index == (int)Editor.Mii.MiiGlasses.Type; + button.IsChecked = index == (int)currentGlasses.Type; button.Color1 = color1; button.Color2 = color2; button.Color3 = color3; @@ -60,41 +45,53 @@ private void GenerateGlassesButtons() button.Click += (_, _) => SetGlassesType(index); } ); + + // Glass color: + SetColorButtons( + MiiColorMappings.GlassesColor.Count, + GlassesColorGrid, + (index, button) => + { + button.IsChecked = index == (int)Editor.Mii.MiiGlasses.Color; + button.Color1 = new SolidColorBrush(MiiColorMappings.GlassesColor[(MiiGlassesColor)index]); + button.Click += (_, _) => SetGlassesColor(index); + } + ); + + // Transform attributes: + HideIfNoGlasses.IsVisible = Editor.Mii.MiiGlasses.Type != MiiGlassesType.None; + UpdateTransformTextValues(currentGlasses); } private void SetGlassesType(int index) { - if (Editor?.Mii?.MiiGlasses == null) + var current = Editor.Mii.MiiGlasses; + if (index == (int)current.Type) return; - var current = Editor.Mii.MiiGlasses; + var result = MiiGlasses.Create((MiiGlassesType)index, current.Color, current.Size, current.Vertical); + if (result.IsFailure) + return; - if (index == (int)current.Type) + Editor.Mii.MiiGlasses = result.Value; + HideIfNoGlasses.IsVisible = result.Value.Type != MiiGlassesType.None; + Editor.RefreshImage(); + } + + private void SetGlassesColor(int index) + { + var current = Editor.Mii.MiiGlasses; + var result = MiiGlasses.Create(current.Type, (MiiGlassesColor)index, current.Size, current.Vertical); + if (result.IsFailure) return; - var result = MiiGlasses.Create((GlassesType)index, current.Color, current.Size, current.Vertical); - if (result.IsSuccess) - { - Editor.Mii.MiiGlasses = result.Value; - UpdateValueTexts(result.Value); - HideIfNoGlasses.IsVisible = result.Value.Type != GlassesType.None; - } - else - { - foreach (var child in GlassesTypesGrid.Children) - { - if (child is MultiIconRadioButton button && button.IsChecked == true) - button.IsChecked = false; - } - var currentButton = GlassesTypesGrid.Children[index] as MultiIconRadioButton; - currentButton.IsChecked = true; - } + Editor.Mii.MiiGlasses = result.Value; Editor.RefreshImage(); } - private void UpdateValueTexts(MiiGlasses glasses) + private void UpdateTransformTextValues(MiiGlasses glasses) { - VerticalValueText.Text = glasses.Vertical.ToString(); + VerticalValueText.Text = ((glasses.Vertical - 10) * -1).ToString(); SizeValueText.Text = glasses.Size.ToString(); VerticalDecreaseButton.IsEnabled = glasses.Vertical > MinVertical; @@ -103,13 +100,9 @@ private void UpdateValueTexts(MiiGlasses glasses) SizeIncreaseButton.IsEnabled = glasses.Size < MaxSize; } - private enum GlassesProperty - { - Vertical, - Size, - } + #region Transfrom - private void TryUpdateGlassValue(int change, GlassesProperty property) + private void TryUpdateGlassValue(int change, MiiTransformProperty property) { if (Editor?.Mii?.MiiGlasses == null || !IsLoaded) return; @@ -122,18 +115,18 @@ private void TryUpdateGlassValue(int change, GlassesProperty property) switch (property) { - case GlassesProperty.Vertical: + case MiiTransformProperty.Vertical: currentValue = current.Vertical; min = MinVertical; max = MaxVertical; break; - case GlassesProperty.Size: + case MiiTransformProperty.Size: currentValue = current.Size; min = MinSize; max = MaxSize; break; default: - throw new ArgumentOutOfRangeException(nameof(property), property, null); + throw new ArgumentException($"{property} is not an option that you can change in Glasses"); } newValue = currentValue + change; @@ -141,56 +134,28 @@ private void TryUpdateGlassValue(int change, GlassesProperty property) if (newValue < min || newValue > max) return; - OperationResult result; - switch (property) + var result = property switch { - case GlassesProperty.Vertical: - result = MiiGlasses.Create(current.Type, current.Color, current.Size, newValue); // Note Vertical position - break; - case GlassesProperty.Size: - result = MiiGlasses.Create(current.Type, current.Color, newValue, current.Vertical); // Note Size position - break; - default: - return; - } + MiiTransformProperty.Vertical => MiiGlasses.Create(current.Type, current.Color, current.Size, newValue), + MiiTransformProperty.Size => MiiGlasses.Create(current.Type, current.Color, newValue, current.Vertical), + _ => throw new ArgumentException($"{property} is not an option that you can change in Glasses"), + }; if (result.IsFailure) return; Editor.Mii.MiiGlasses = result.Value; - UpdateValueTexts(result.Value); + UpdateTransformTextValues(result.Value); Editor.RefreshImage(); } - private void GlassesColorBox_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) - { - if (!IsLoaded || GlassesColorBox.SelectedItem == null || Editor?.Mii?.MiiGlasses == null) - return; - if (GlassesColorBox.SelectedItem is not string colorStr) - return; - - var newColor = (GlassesColor)Enum.Parse(typeof(GlassesColor), colorStr); - var current = Editor.Mii.MiiGlasses; - if (newColor == current.Color) - return; - - var result = MiiGlasses.Create(current.Type, newColor, current.Size, current.Vertical); - if (result.IsSuccess) - { - Editor.Mii.MiiGlasses = result.Value; - } - else - { - GlassesColorBox.SelectedItem = current.Color.ToString(); - } - Editor.RefreshImage(); - } + private void VerticalDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateGlassValue(-1, MiiTransformProperty.Vertical); - private void VerticalDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateGlassValue(-1, GlassesProperty.Vertical); + private void VerticalIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateGlassValue(+1, MiiTransformProperty.Vertical); - private void VerticalIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateGlassValue(+1, GlassesProperty.Vertical); + private void SizeDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateGlassValue(-1, MiiTransformProperty.Size); - private void SizeDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateGlassValue(-1, GlassesProperty.Size); + private void SizeIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateGlassValue(+1, MiiTransformProperty.Size); - private void SizeIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateGlassValue(+1, GlassesProperty.Size); + #endregion } diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorHair.axaml b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorHair.axaml index 1aeedc14..9b395dc3 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorHair.axaml +++ b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorHair.axaml @@ -9,7 +9,7 @@ - + + - - + + + + + - + - + diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorHair.axaml.cs b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorHair.axaml.cs index 6813ca08..863d3f49 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorHair.axaml.cs +++ b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorHair.axaml.cs @@ -1,8 +1,7 @@ -using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Media; -using WheelWizard.Views.Components; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.MiiManagement.Domain; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; namespace WheelWizard.Views.Popups.MiiManagement.MiiEditor; @@ -12,29 +11,15 @@ public EditorHair(MiiEditorWindow ew) : base(ew) { InitializeComponent(); - if (Editor?.Mii?.MiiHair == null) - return; PopulateValues(); } private void PopulateValues() { + // Attribute: var currentHair = Editor.Mii.MiiHair; - GenerateHairButtons(); - - HairColorBox.Items.Clear(); - foreach (var color in Enum.GetNames(typeof(HairColor))) - { - HairColorBox.Items.Add(color); - if (color == currentHair.HairColor.ToString()) - HairColorBox.SelectedItem = color; - } - HairFlippedCheck.IsChecked = currentHair.HairFlipped; - } - - private void GenerateHairButtons() - { + // Hair: var color1 = new SolidColorBrush(ViewUtils.Colors.Neutral100); // Skin Color var color2 = new SolidColorBrush(ViewUtils.Colors.Neutral300); // Skin border Color var color3 = new SolidColorBrush(ViewUtils.Colors.Black); // Hair Color @@ -46,7 +31,7 @@ private void GenerateHairButtons() HairTypesGrid, (index, button) => { - button.IsChecked = index == Editor.Mii.MiiHair.HairType; + button.IsChecked = index == currentHair.HairType; button.Color1 = color1; button.Color2 = color2; button.Color3 = color3; @@ -55,36 +40,32 @@ private void GenerateHairButtons() button.Click += (_, _) => SetHairType(index); } ); + + // Hair color: + SetColorButtons( + MiiColorMappings.HairColor.Count, + HairColorGrid, + (index, button) => + { + button.IsChecked = index == (int)Editor.Mii.MiiHair.MiiHairColor; + button.Color1 = new SolidColorBrush(MiiColorMappings.HairColor[(MiiHairColor)index]); + button.Click += (_, _) => SetHairColor(index); + } + ); + + // Hair Flipped: + HairFlippedCheck.IsChecked = currentHair.HairFlipped; } private void SetHairType(int type) { - Editor.Mii.MiiHair = new(type, Editor.Mii.MiiHair.HairColor, Editor.Mii.MiiHair.HairFlipped); + Editor.Mii.MiiHair = new(type, Editor.Mii.MiiHair.MiiHairColor, Editor.Mii.MiiHair.HairFlipped); Editor.RefreshImage(); } - private void HairColorBox_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) + private void SetHairColor(int color) { - if (Editor?.Mii?.MiiHair == null || !IsLoaded || HairColorBox.SelectedItem == null) - return; - - var value = HairColorBox.SelectedItem; - if (value is null) - return; - - var selectedColor = (HairColor)Enum.Parse(typeof(HairColor), value.ToString()!); - var currentHair = Editor.Mii.MiiHair; - if (selectedColor == currentHair.HairColor) - return; - - var result = MiiHair.Create(currentHair.HairType, selectedColor, currentHair.HairFlipped); - - if (result.IsFailure) - { - return; - } - - Editor.Mii.MiiHair = result.Value; + Editor.Mii.MiiHair = new(Editor.Mii.MiiHair.HairType, (MiiHairColor)color, Editor.Mii.MiiHair.HairFlipped); Editor.RefreshImage(); } @@ -99,7 +80,7 @@ private void HairFlippedCheck_OnIsCheckedChanged(object? sender, RoutedEventArgs if (isChecked == currentHair.HairFlipped) return; - var result = MiiHair.Create(currentHair.HairType, currentHair.HairColor, isChecked); // New value + var result = MiiHair.Create(currentHair.HairType, currentHair.MiiHairColor, isChecked); // New value if (result.IsFailure) return; diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorLips.axaml b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorLips.axaml index b2d21068..3b3bbacc 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorLips.axaml +++ b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorLips.axaml @@ -9,7 +9,7 @@ - + - + - + + + + - + @@ -46,7 +45,7 @@ - + @@ -56,7 +55,7 @@ - + diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorLips.axaml.cs b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorLips.axaml.cs index 2c8e0f9f..b50db788 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorLips.axaml.cs +++ b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorLips.axaml.cs @@ -1,15 +1,12 @@ -using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Media; -using WheelWizard.Views.Components; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.MiiManagement.Domain; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; namespace WheelWizard.Views.Popups.MiiManagement.MiiEditor; public partial class EditorLips : MiiEditorBaseControl { - private const int MinType = 0; - private const int MaxType = 23; private const int MinVertical = 0; private const int MaxVertical = 18; private const int MinSize = 0; @@ -19,28 +16,15 @@ public EditorLips(MiiEditorWindow ew) : base(ew) { InitializeComponent(); - if (Editor?.Mii?.MiiLips == null) - return; PopulateValues(); } private void PopulateValues() { + // Attribute: var currentLips = Editor.Mii.MiiLips; - var lipTypes = Enumerable.Range(MinType, MaxType - MinType + 1).Cast().ToList(); // 0 to 23 - LipColorBox.Items.Clear(); - foreach (var color in Enum.GetNames(typeof(LipColor))) - { - LipColorBox.Items.Add(color); - if (color == currentLips.Color.ToString()) - LipColorBox.SelectedItem = color; - } - GenerateMouthButtons(); - UpdateValueTexts(currentLips); - } - private void GenerateMouthButtons() - { + // Lips: var color1 = new SolidColorBrush(new Color(255, 165, 57, 29)); // Lip Top Color var color2 = new SolidColorBrush(new Color(255, 255, 93, 13)); // Lip bottom Color var color3 = new SolidColorBrush(Colors.Black); // LipLine Color @@ -51,7 +35,7 @@ private void GenerateMouthButtons() MouthTypesGrid, (index, button) => { - button.IsChecked = index == Editor.Mii.MiiLips.Type; + button.IsChecked = index == currentLips.Type; button.Color1 = color1; button.Color2 = color2; button.Color3 = color3; @@ -59,42 +43,56 @@ private void GenerateMouthButtons() button.Click += (_, _) => SetMouthType(index); } ); + + // Lip Colors: + SetColorButtons( + MiiColorMappings.LipBottomColor.Count, + LipColorGrid, + (index, button) => + { + button.IsChecked = index == (int)Editor.Mii.MiiLips.Color; + button.Color1 = new SolidColorBrush(MiiColorMappings.LipBottomColor[(MiiLipColor)index]); + button.Click += (_, _) => SetMouthColor(index); + } + ); + + // Transform attributes: + UpdateTransformTextValues(currentLips); } private void SetMouthType(int index) { - if (Editor?.Mii?.MiiLips == null) + var current = Editor.Mii.MiiLips; + if (index == current.Type) + return; + + // MiiLip.Create(int type, LipColor color, int size, int vertical) + var result = MiiLip.Create(index, current.Color, current.Size, current.Vertical); + if (result.IsFailure) return; + Editor.Mii.MiiLips = result.Value; + Editor.RefreshImage(); + } + + private void SetMouthColor(int index) + { var current = Editor.Mii.MiiLips; if (index == current.Type) return; // MiiLip.Create(int type, LipColor color, int size, int vertical) - var result = MiiLip.Create(index, current.Color, current.Size, current.Vertical); - if (result.IsSuccess) - { - Editor.Mii.MiiLips = result.Value; - UpdateValueTexts(result.Value); - } - else - { - foreach (var child in MouthTypesGrid.Children) - { - if (child is MultiIconRadioButton button && button.IsChecked == true) - { - button.IsChecked = false; - } - } - var currentButton = MouthTypesGrid.Children[index] as MultiIconRadioButton; - currentButton.IsChecked = true; - } + var result = MiiLip.Create(current.Type, (MiiLipColor)index, current.Size, current.Vertical); + if (result.IsFailure) + return; + + Editor.Mii.MiiLips = result.Value; Editor.RefreshImage(); } - private void UpdateValueTexts(MiiLip lips) + private void UpdateTransformTextValues(MiiLip lips) { - VerticalValueText.Text = lips.Vertical.ToString(); + VerticalValueText.Text = ((lips.Vertical - 13) * -1).ToString(); SizeValueText.Text = lips.Size.ToString(); VerticalDecreaseButton.IsEnabled = lips.Vertical > MinVertical; @@ -103,13 +101,9 @@ private void UpdateValueTexts(MiiLip lips) SizeIncreaseButton.IsEnabled = lips.Size < MaxSize; } - private enum LipProperty - { - Vertical, - Size, - } + #region Transform - private void TryUpdateLipValue(int change, LipProperty property) + private void TryUpdateLipValue(int change, MiiTransformProperty property) { if (Editor?.Mii?.MiiLips == null || !IsLoaded) return; @@ -122,77 +116,47 @@ private void TryUpdateLipValue(int change, LipProperty property) switch (property) { - case LipProperty.Vertical: + case MiiTransformProperty.Vertical: currentValue = current.Vertical; min = MinVertical; max = MaxVertical; break; - case LipProperty.Size: + case MiiTransformProperty.Size: currentValue = current.Size; min = MinSize; max = MaxSize; break; default: - throw new ArgumentOutOfRangeException(nameof(property), property, null); + throw new ArgumentException($"{property} is not an option that you can change in Lips"); } newValue = currentValue + change; if (newValue < min || newValue > max) - { return; - } - OperationResult result; - switch (property) + var result = property switch { - case LipProperty.Vertical: - result = MiiLip.Create(current.Type, current.Color, current.Size, newValue); // Note Vertical position - break; - case LipProperty.Size: - result = MiiLip.Create(current.Type, current.Color, newValue, current.Vertical); // Note Size position - break; - default: - return; - } + MiiTransformProperty.Vertical => MiiLip.Create(current.Type, current.Color, current.Size, newValue), + MiiTransformProperty.Size => MiiLip.Create(current.Type, current.Color, newValue, current.Vertical), + _ => throw new ArgumentException($"{property} is not an option that you can change in Lips"), + }; if (result.IsFailure) return; Editor.Mii.MiiLips = result.Value; - UpdateValueTexts(result.Value); + UpdateTransformTextValues(result.Value); Editor.RefreshImage(); } - private void LipColorBox_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) - { - if (!IsLoaded || LipColorBox.SelectedItem == null || Editor?.Mii?.MiiLips == null) - return; - if (LipColorBox.SelectedItem is not string colorStr) - return; - - var newColor = (LipColor)Enum.Parse(typeof(LipColor), colorStr); - var current = Editor.Mii.MiiLips; - if (newColor == current.Color) - return; - - var result = MiiLip.Create(current.Type, newColor, current.Size, current.Vertical); - if (result.IsSuccess) - { - Editor.Mii.MiiLips = result.Value; - } - else - { - LipColorBox.SelectedItem = current.Color.ToString(); - } - Editor.RefreshImage(); - } + private void VerticalDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateLipValue(-1, MiiTransformProperty.Vertical); - private void VerticalDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateLipValue(-1, LipProperty.Vertical); + private void VerticalIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateLipValue(+1, MiiTransformProperty.Vertical); - private void VerticalIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateLipValue(+1, LipProperty.Vertical); + private void SizeDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateLipValue(-1, MiiTransformProperty.Size); - private void SizeDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateLipValue(-1, LipProperty.Size); + private void SizeIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateLipValue(+1, MiiTransformProperty.Size); - private void SizeIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateLipValue(+1, LipProperty.Size); + #endregion } diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorMole.axaml b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorMole.axaml index b37ed403..a49b5ea9 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorMole.axaml +++ b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorMole.axaml @@ -9,7 +9,7 @@ - + - + - + - + @@ -62,7 +62,7 @@ - + diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorMole.axaml.cs b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorMole.axaml.cs index 8be3a1ca..c52c26b8 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorMole.axaml.cs +++ b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorMole.axaml.cs @@ -1,5 +1,5 @@ using Avalonia.Interactivity; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; namespace WheelWizard.Views.Popups.MiiManagement.MiiEditor; @@ -16,26 +16,27 @@ public EditorMole(MiiEditorWindow ew) : base(ew) { InitializeComponent(); - if (Editor?.Mii?.MiiMole == null) - return; PopulateValues(); } private void PopulateValues() { + // Attribute: var currentMole = Editor.Mii.MiiMole; + // Mole enabled: MoleEnabledCheck.IsChecked = currentMole.Exists; - MoleControlsPanel.IsVisible = currentMole.Exists; - UpdateValueTexts(currentMole); + // Transform attributes: + MoleControlsPanel.IsVisible = currentMole.Exists; + UpdateTransformTextValues(currentMole); } - private void UpdateValueTexts(MiiMole mole) + private void UpdateTransformTextValues(MiiMole mole) { - VerticalValueText.Text = mole.Vertical.ToString(); + VerticalValueText.Text = ((mole.Vertical - 20) * -1).ToString(); SizeValueText.Text = mole.Size.ToString(); - HorizontalValueText.Text = mole.Horizontal.ToString(); + HorizontalValueText.Text = (mole.Horizontal - 8).ToString(); // 8 is center of the face VerticalDecreaseButton.IsEnabled = mole.Vertical > MinVertical; VerticalIncreaseButton.IsEnabled = mole.Vertical < MaxVertical; @@ -45,14 +46,9 @@ private void UpdateValueTexts(MiiMole mole) HorizontalIncreaseButton.IsEnabled = mole.Horizontal < MaxHorizontal; } - private enum MoleProperty - { - Vertical, - Size, - Horizontal, - } + #region Transfrom - private void TryUpdateMoleValue(int change, MoleProperty property) + private void TryUpdateMoleValue(int change, MiiTransformProperty property) { if (Editor?.Mii?.MiiMole == null || !IsLoaded || MoleEnabledCheck.IsChecked != true) return; @@ -65,54 +61,43 @@ private void TryUpdateMoleValue(int change, MoleProperty property) switch (property) { - case MoleProperty.Vertical: + case MiiTransformProperty.Vertical: currentValue = current.Vertical; min = MinVertical; max = MaxVertical; break; - case MoleProperty.Size: + case MiiTransformProperty.Size: currentValue = current.Size; min = MinSize; max = MaxSize; break; - case MoleProperty.Horizontal: + case MiiTransformProperty.Horizontal: currentValue = current.Horizontal; min = MinHorizontal; max = MaxHorizontal; break; default: - throw new ArgumentOutOfRangeException(nameof(property), property, null); + throw new ArgumentException($"{property} is not an option that you can change in Mole"); } newValue = currentValue + change; if (newValue < min || newValue > max) - { - Console.WriteLine($"Mole {property} limit reached ({newValue})."); return; - } - OperationResult result; - switch (property) + var result = property switch { - case MoleProperty.Vertical: - result = MiiMole.Create(current.Exists, current.Size, newValue, current.Horizontal); - break; - case MoleProperty.Size: - result = MiiMole.Create(current.Exists, newValue, current.Vertical, current.Horizontal); - break; - case MoleProperty.Horizontal: - result = MiiMole.Create(current.Exists, current.Size, current.Vertical, newValue); - break; - default: - return; - } + MiiTransformProperty.Vertical => MiiMole.Create(current.Exists, current.Size, newValue, current.Horizontal), + MiiTransformProperty.Size => MiiMole.Create(current.Exists, newValue, current.Vertical, current.Horizontal), + MiiTransformProperty.Horizontal => MiiMole.Create(current.Exists, current.Size, current.Vertical, newValue), + _ => throw new ArgumentException($"{property} is not an option that you can change in Mole"), + }; if (result.IsFailure) return; Editor.Mii.MiiMole = result.Value; - UpdateValueTexts(result.Value); + UpdateTransformTextValues(result.Value); Editor.RefreshImage(); } @@ -121,7 +106,7 @@ private void MoleEnabledCheck_OnIsCheckedChanged(object? sender, RoutedEventArgs if (!IsLoaded || Editor?.Mii?.MiiMole == null) return; - bool isEnabled = MoleEnabledCheck.IsChecked == true; + var isEnabled = MoleEnabledCheck.IsChecked == true; var current = Editor.Mii.MiiMole; if (isEnabled == current.Exists) @@ -130,26 +115,25 @@ private void MoleEnabledCheck_OnIsCheckedChanged(object? sender, RoutedEventArgs var result = MiiMole.Create(isEnabled, current.Size, current.Vertical, current.Horizontal); if (result.IsSuccess) - { Editor.Mii.MiiMole = result.Value; - } else - { MoleEnabledCheck.IsChecked = current.Exists; - } + MoleControlsPanel.IsVisible = Editor.Mii.MiiMole.Exists; Editor.RefreshImage(); } - private void VerticalDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateMoleValue(-1, MoleProperty.Vertical); + private void VerticalDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateMoleValue(-1, MiiTransformProperty.Vertical); + + private void VerticalIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateMoleValue(+1, MiiTransformProperty.Vertical); - private void VerticalIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateMoleValue(+1, MoleProperty.Vertical); + private void SizeDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateMoleValue(-1, MiiTransformProperty.Size); - private void SizeDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateMoleValue(-1, MoleProperty.Size); + private void SizeIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateMoleValue(+1, MiiTransformProperty.Size); - private void SizeIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateMoleValue(+1, MoleProperty.Size); + private void HorizontalDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateMoleValue(-1, MiiTransformProperty.Horizontal); - private void HorizontalDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateMoleValue(-1, MoleProperty.Horizontal); + private void HorizontalIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateMoleValue(+1, MiiTransformProperty.Horizontal); - private void HorizontalIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateMoleValue(+1, MoleProperty.Horizontal); + #endregion } diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorNose.axaml b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorNose.axaml index c2fef731..ea69b105 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorNose.axaml +++ b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorNose.axaml @@ -9,7 +9,7 @@ - + - + @@ -36,7 +36,7 @@ - + @@ -47,7 +47,7 @@ - + diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorNose.axaml.cs b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorNose.axaml.cs index c999ce06..31ad68b9 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorNose.axaml.cs +++ b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorNose.axaml.cs @@ -1,8 +1,6 @@ -using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Media; -using WheelWizard.Views.Components; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; namespace WheelWizard.Views.Popups.MiiManagement.MiiEditor; @@ -17,20 +15,15 @@ public EditorNose(MiiEditorWindow ew) : base(ew) { InitializeComponent(); - if (Editor?.Mii?.MiiNose == null) - return; PopulateValues(); } private void PopulateValues() { + // Attribute: var currentNose = Editor.Mii.MiiNose; - GenerateNoseButtons(); - UpdateValueTexts(currentNose); - } - private void GenerateNoseButtons() - { + // Nose: var color1 = new SolidColorBrush(ViewUtils.Colors.Black); SetButtons( "MiiNose", @@ -38,11 +31,14 @@ private void GenerateNoseButtons() NoseTypesGrid, (index, button) => { - button.IsChecked = index == (int)Editor.Mii.MiiNose.Type; + button.IsChecked = index == (int)currentNose.Type; button.Color1 = color1; button.Click += (_, _) => SetNoseType(index); } ); + + // Transform attributes: + UpdateTransformTextValues(currentNose); } private void SetNoseType(int index) @@ -50,33 +46,18 @@ private void SetNoseType(int index) if (Editor?.Mii?.MiiNose == null || !IsLoaded) return; var current = Editor.Mii.MiiNose; - var noseType = (NoseType)index; + var noseType = (MiiNoseType)index; var result = MiiNose.Create(noseType, current.Size, current.Vertical); - if (result.IsSuccess) - { - Editor.Mii.MiiNose = result.Value; - UpdateValueTexts(result.Value); - } - else - { - // Reset the button to the current type if creation fails - foreach (var child in NoseTypesGrid.Children) - { - if (child is MultiIconRadioButton button && button.IsChecked == true) - { - button.IsChecked = false; - } - } + if (result.IsFailure) + return; - var currentButton = NoseTypesGrid.Children[index] as MultiIconRadioButton; - currentButton.IsChecked = true; - } + Editor.Mii.MiiNose = result.Value; Editor.RefreshImage(); } - private void UpdateValueTexts(MiiNose nose) + private void UpdateTransformTextValues(MiiNose nose) { - VerticalValueText.Text = nose.Vertical.ToString(); + VerticalValueText.Text = ((nose.Vertical - 9) * -1).ToString(); SizeValueText.Text = nose.Size.ToString(); VerticalDecreaseButton.IsEnabled = nose.Vertical > MinVertical; @@ -85,13 +66,9 @@ private void UpdateValueTexts(MiiNose nose) SizeIncreaseButton.IsEnabled = nose.Size < MaxSize; } - private enum NoseProperty - { - Vertical, - Size, - } + #region Transform - private void TryUpdateNoseValue(int change, NoseProperty property) + private void TryUpdateNoseValue(int change, MiiTransformProperty property) { if (Editor?.Mii?.MiiNose == null || !IsLoaded) return; @@ -104,53 +81,47 @@ private void TryUpdateNoseValue(int change, NoseProperty property) switch (property) { - case NoseProperty.Vertical: + case MiiTransformProperty.Vertical: currentValue = current.Vertical; min = MinVertical; max = MaxVertical; break; - case NoseProperty.Size: + case MiiTransformProperty.Size: currentValue = current.Size; min = MinSize; max = MaxSize; break; default: - throw new ArgumentOutOfRangeException(nameof(property), property, null); + throw new ArgumentException($"{property} is not an option that you can change in Nose"); } newValue = currentValue + change; if (newValue < min || newValue > max) - { return; - } - OperationResult result; - switch (property) + var result = property switch { - case NoseProperty.Vertical: - result = MiiNose.Create(current.Type, current.Size, newValue); - break; - case NoseProperty.Size: - result = MiiNose.Create(current.Type, newValue, current.Vertical); - break; - default: - return; - } + MiiTransformProperty.Vertical => MiiNose.Create(current.Type, current.Size, newValue), + MiiTransformProperty.Size => MiiNose.Create(current.Type, newValue, current.Vertical), + _ => throw new ArgumentException($"{property} is not an option that you can change in Nose"), + }; if (result.IsFailure) return; Editor.Mii.MiiNose = result.Value; - UpdateValueTexts(result.Value); + UpdateTransformTextValues(result.Value); Editor.RefreshImage(); } - private void VerticalDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateNoseValue(-1, NoseProperty.Vertical); + private void VerticalDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateNoseValue(-1, MiiTransformProperty.Vertical); + + private void VerticalIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateNoseValue(+1, MiiTransformProperty.Vertical); - private void VerticalIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateNoseValue(+1, NoseProperty.Vertical); + private void SizeDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateNoseValue(-1, MiiTransformProperty.Size); - private void SizeDecrease_Click(object? sender, RoutedEventArgs e) => TryUpdateNoseValue(-1, NoseProperty.Size); + private void SizeIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateNoseValue(+1, MiiTransformProperty.Size); - private void SizeIncrease_Click(object? sender, RoutedEventArgs e) => TryUpdateNoseValue(+1, NoseProperty.Size); + #endregion } diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorStartPage.axaml b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorStartPage.axaml index dff62909..8872c3d9 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorStartPage.axaml +++ b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorStartPage.axaml @@ -34,6 +34,9 @@ + - - - - - - - - - - diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorStartPage.axaml.cs b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorStartPage.axaml.cs index 6a200d7b..a12bcf26 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorStartPage.axaml.cs +++ b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorStartPage.axaml.cs @@ -1,10 +1,16 @@ using Avalonia.Interactivity; +using Testably.Abstractions; +using WheelWizard.Shared.DependencyInjection; using WheelWizard.Views.Components; +using MiiFactory = WheelWizard.WiiManagement.MiiManagement.MiiFactory; namespace WheelWizard.Views.Popups.MiiManagement.MiiEditor; public partial class EditorStartPage : MiiEditorBaseControl { + [Inject] + private IRandomSystem Random { get; set; } = null!; + public EditorStartPage(MiiEditorWindow ew) : base(ew) { @@ -25,6 +31,20 @@ private void PopupPageButton_OnClick(object? sender, RoutedEventArgs e) Editor.SetEditorPage(pageType); } + private void RandomizeMii_OnClick(object? sender, RoutedEventArgs e) + { + var oldMii = Editor.Mii; + var newMii = MiiFactory.CreateRandomMii(Random.Random.Shared); + newMii.Name = oldMii.Name; + newMii.IsFavorite = oldMii.IsFavorite; + newMii.MiiId = oldMii.MiiId; + newMii.SystemId = oldMii.SystemId; + newMii.CreatorName = oldMii.CreatorName; + + Editor.SetMii(newMii); + Editor.RefreshImage(); + } + private void CancelButton_OnClick(object? sender, RoutedEventArgs e) => Editor.Close(); private void SaveButton_OnClick(object? sender, RoutedEventArgs e) => Editor.SignalSaveMii(); diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/MiiEditorBaseControl.cs b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/MiiEditorBaseControl.cs index f8c33607..15961caf 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiEditor/MiiEditorBaseControl.cs +++ b/WheelWizard/Views/Popups/MiiManagement/MiiEditor/MiiEditorBaseControl.cs @@ -35,18 +35,43 @@ protected void SetButtons(string type, int count, UniformGrid addTo, Action modify) + { + var color2 = new SolidColorBrush(ViewUtils.Colors.Neutral950); + var selectedColor2 = new SolidColorBrush(ViewUtils.Colors.Neutral900); + + for (var i = 0; i < count; i++) + { + var index = i; + + var iconData = GetMiiIconData("Multi-PaintBrush"); + var button = new MultiIconRadioButton + { + Margin = new(6), + IconData = iconData, + Color2 = color2, + SelectedColor2 = selectedColor2, + }; + modify(index, button); + addTo.Children.Add(button); + } + } + + // Enum to identify which property is being changed by buttons + protected enum MiiTransformProperty + { + Vertical, + Horizontal, + Size, + Spacing, + Rotation, + } } diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiEditorWindow.axaml.cs b/WheelWizard/Views/Popups/MiiManagement/MiiEditorWindow.axaml.cs index 95657dfd..586d6170 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiEditorWindow.axaml.cs +++ b/WheelWizard/Views/Popups/MiiManagement/MiiEditorWindow.axaml.cs @@ -1,10 +1,14 @@ using System.ComponentModel; using Avalonia.Interactivity; using Avalonia.Threading; +using WheelWizard.Resources.Languages; +using WheelWizard.Shared.MessageTranslations; using WheelWizard.Views.Popups.Base; +using WheelWizard.Views.Popups.Generic; using WheelWizard.Views.Popups.MiiManagement.MiiEditor; using WheelWizard.WiiManagement; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.MiiManagement; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; namespace WheelWizard.Views.Popups.MiiManagement; @@ -31,12 +35,10 @@ private set private VisualizationType selectedVisualization = VisualizationType.Face; public MiiEditorWindow() - : base(true, false, false, "Mii Editor") + : base(true, false, false, Common.PopupTitle_MiiEditor) { InitializeComponent(); DataContext = this; - - Window.BetaFlag = true; } protected override void BeforeOpen() @@ -48,13 +50,20 @@ protected override void BeforeOpen() public void SetEditorPage(Type pageType) { EditorPresenter.Content = Activator.CreateInstance(pageType, this)!; - Window.WindowTitle = $"Mii Editor - {Mii.Name}"; + Window.WindowTitle = $"{Common.PopupTitle_MiiEditor} - {Mii.Name}"; } public MiiEditorWindow SetMii(Mii miiToEdit) { - Window.WindowTitle = $"Mii Editor - {miiToEdit.Name}"; + Window.WindowTitle = $"{Common.PopupTitle_MiiEditor} - {miiToEdit.Name}"; var miiResult = miiToEdit.Clone(); + if (miiResult.IsFailure) + { + DisableOpen(true); + MessageTranslationHelper.ShowMessage(MessageTranslation.Error_MiiEditor_CantOpenEditor, null, [miiResult.Error.Message]); + return this; + } + Mii = miiResult.Value; return this; } diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiSelectorWindow.axaml b/WheelWizard/Views/Popups/MiiManagement/MiiSelectorWindow.axaml index 21568199..8b1607e1 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiSelectorWindow.axaml +++ b/WheelWizard/Views/Popups/MiiManagement/MiiSelectorWindow.axaml @@ -1,8 +1,8 @@ diff --git a/WheelWizard/Views/Popups/MiiManagement/MiiSelectorWindow.axaml.cs b/WheelWizard/Views/Popups/MiiManagement/MiiSelectorWindow.axaml.cs index acc7028c..41e31b11 100644 --- a/WheelWizard/Views/Popups/MiiManagement/MiiSelectorWindow.axaml.cs +++ b/WheelWizard/Views/Popups/MiiManagement/MiiSelectorWindow.axaml.cs @@ -5,7 +5,8 @@ using WheelWizard.Views.Components; using WheelWizard.Views.Popups.Base; using WheelWizard.WiiManagement; -using WheelWizard.WiiManagement.Domain.Mii; +using WheelWizard.WiiManagement.MiiManagement; +using WheelWizard.WiiManagement.MiiManagement.Domain.Mii; namespace WheelWizard.Views.Popups.MiiManagement; @@ -15,7 +16,7 @@ public partial class MiiSelectorWindow : PopupContent private TaskCompletionSource _tcs; public MiiSelectorWindow() - : base(true, false, false, "Mii Selector") + : base(true, false, false, Common.PopupTitle_MiiSelector) { InitializeComponent(); SaveButton.Text = Common.Action_Save; diff --git a/WheelWizard/Views/Popups/ModManagement/ModBrowserWindow.axaml b/WheelWizard/Views/Popups/ModManagement/ModBrowserWindow.axaml index 879b2b25..2cd5bc62 100644 --- a/WheelWizard/Views/Popups/ModManagement/ModBrowserWindow.axaml +++ b/WheelWizard/Views/Popups/ModManagement/ModBrowserWindow.axaml @@ -4,9 +4,9 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" ClipToBounds="False" + xmlns:lang="clr-namespace:WheelWizard.Resources.Languages" xmlns:modManagement="clr-namespace:WheelWizard.Views.Popups.ModManagement" xmlns:components="clr-namespace:WheelWizard.Views.Components" - xmlns:gameBanana="clr-namespace:WheelWizard.GameBanana.Domain" xmlns:base="clr-namespace:WheelWizard.Views.Popups.Base" xmlns:self="clr-namespace:WheelWizard.Views.Popups.ModManagement" xmlns:wheelWizard="clr-namespace:WheelWizard" @@ -14,12 +14,12 @@ - - diff --git a/WheelWizard/Views/Popups/ModManagement/ModBrowserWindow.axaml.cs b/WheelWizard/Views/Popups/ModManagement/ModBrowserWindow.axaml.cs index 464c10ec..4e96220c 100644 --- a/WheelWizard/Views/Popups/ModManagement/ModBrowserWindow.axaml.cs +++ b/WheelWizard/Views/Popups/ModManagement/ModBrowserWindow.axaml.cs @@ -7,6 +7,7 @@ using Avalonia.Threading; using WheelWizard.GameBanana; using WheelWizard.GameBanana.Domain; +using WheelWizard.Resources.Languages; using WheelWizard.Shared.DependencyInjection; using WheelWizard.Views.Pages; using WheelWizard.Views.Popups.Base; @@ -38,7 +39,7 @@ public partial class ModBrowserWindow : PopupContent, INotifyPropertyChanged private string _currentSearchTerm = ""; public ModBrowserWindow() - : base(true, false, false, "Mod Browser") + : base(true, false, false, Common.PopupTitle_ModBrowser) { InitializeComponent(); DataContext = this; diff --git a/WheelWizard/Views/Popups/ModManagement/ModContent.axaml b/WheelWizard/Views/Popups/ModManagement/ModContent.axaml index 595e1b3c..baa1cda1 100644 --- a/WheelWizard/Views/Popups/ModManagement/ModContent.axaml +++ b/WheelWizard/Views/Popups/ModManagement/ModContent.axaml @@ -4,6 +4,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" ClipToBounds="False" + xmlns:lang="clr-namespace:WheelWizard.Resources.Languages" xmlns:html="clr-namespace:TheArtOfDev.HtmlRenderer.Avalonia;assembly=Avalonia.HtmlRenderer" xmlns:components="clr-namespace:WheelWizard.Views.Components" xmlns:modManagement="clr-namespace:WheelWizard.Views.Popups.ModManagement" @@ -13,7 +14,7 @@ - - - @@ -67,7 +68,7 @@ @@ -75,19 +76,19 @@ @@ -99,7 +100,7 @@ - + @@ -118,7 +119,7 @@ - + diff --git a/WheelWizard/Views/Popups/ModManagement/ModContent.axaml.cs b/WheelWizard/Views/Popups/ModManagement/ModContent.axaml.cs index 4cb18c8d..2541d98d 100644 --- a/WheelWizard/Views/Popups/ModManagement/ModContent.axaml.cs +++ b/WheelWizard/Views/Popups/ModManagement/ModContent.axaml.cs @@ -3,9 +3,11 @@ using WheelWizard.GameBanana; using WheelWizard.GameBanana.Domain; using WheelWizard.Helpers; +using WheelWizard.Resources.Languages; using WheelWizard.Services; using WheelWizard.Services.Installation; using WheelWizard.Shared.DependencyInjection; +using WheelWizard.Shared.MessageTranslations; using WheelWizard.Views.Popups.Generic; namespace WheelWizard.Views.Popups.ModManagement; @@ -27,6 +29,9 @@ public ModContent() InitializeComponent(); ResetVisibility(); UnInstallButton.IsVisible = false; + + DescriptionLabel.Text = Common.Attribute_Description + ":"; + ImageLabel.Text = Common.Attribute_Images + ":"; } private void ResetVisibility() @@ -80,7 +85,7 @@ public async Task LoadModDetailsAsync(int ModId, string? newDownloadUrl = { CurrentMod = null; OverrideDownloadUrl = null; - NoDetailsView.Title = "Failed to retrieve mod info"; + NoDetailsView.Title = Phrases.MessageError_FailedRetrieveMod_Title; NoDetailsView.BodyText = modDetailsResult.Error.Message; loading = false; @@ -159,7 +164,7 @@ public async Task LoadModDetailsAsync(int ModId, string? newDownloadUrl = private void UpdateDownloadButtonState(int modId) { var isInstalled = ModManager.Instance.IsModInstalled(modId); - InstallButton.Content = isInstalled ? "Installed" : "Download and Install"; + InstallButton.Content = isInstalled ? Common.State_Installed : Common.Action_DownloadAndInstall; InstallButton.IsEnabled = !isInstalled; UnInstallButton.IsVisible = isInstalled; } @@ -171,7 +176,7 @@ private void ClearDetails() { ImageCarousel.Items.Clear(); ModTitle.Text = string.Empty; - AuthorButton.Text = "Unknown"; + AuthorButton.Text = Common.State_Unknown; LikesCountBox.Text = ViewsCountBox.Text = DownloadsCountBox.Text = "0"; ModDescriptionHtmlPanel.Text = string.Empty; IsVisible = false; @@ -183,7 +188,7 @@ private async void Install_Click(object sender, RoutedEventArgs e) return; var confirmation = await new YesNoWindow() - .SetMainText($"Do you want to download and install the mod: {CurrentMod.Name}?") + .SetMainText(Humanizer.ReplaceDynamic(Phrases.Question_InstallMod_Title, CurrentMod.Name) ?? CurrentMod.Name) .AwaitAnswer(); if (!confirmation) return; @@ -194,17 +199,13 @@ private async void Install_Click(object sender, RoutedEventArgs e) var downloadUrls = OverrideDownloadUrl != null ? [OverrideDownloadUrl] : CurrentMod.Files.Select(f => f.DownloadUrl).ToList(); if (!downloadUrls.Any()) { - new MessageBoxWindow() - .SetMessageType(MessageBoxWindow.MessageType.Warning) - .SetTitleText("Unable to download the mod") - .SetInfoText("No downloadable files found for this mod.") - .Show(); + MessageTranslationHelper.ShowMessage(MessageTranslation.Warning_UnableToDownloadMod_Files); return; } var progressWindow = new ProgressWindow($"Downloading {CurrentMod.Name}"); progressWindow.Show(); - progressWindow.SetExtraText("Loading..."); + progressWindow.SetExtraText(Common.State_Loading); var url = downloadUrls.First(); var fileName = GetFileNameFromUrl(url); @@ -214,43 +215,31 @@ private async void Install_Click(object sender, RoutedEventArgs e) var file = Directory.GetFiles(PathManager.TempModsFolderPath).FirstOrDefault(); if (file == null) { - new MessageBoxWindow() - .SetMessageType(MessageBoxWindow.MessageType.Warning) - .SetTitleText("Unable to download the mod") - .SetInfoText("Downloaded file not found.") - .Show(); + MessageTranslationHelper.ShowMessage(MessageTranslation.Warning_UnableToDownloadMod_Files); return; } var author = CurrentMod.Author.Name; var modId = CurrentMod.Id; var popup = new TextInputWindow() - .SetMainText("Mod Name") + .SetMainText(Common.Attribute_Name) .SetInitialText(CurrentMod.Name) .SetValidation(ModManager.Instance.ValidateModName) - .SetPlaceholderText("Enter mod name..."); + .SetPlaceholderText(Phrases.Placeholder_EnterModName); var modName = await popup.ShowDialog(); if (modName == null) return; if (string.IsNullOrEmpty(modName)) { - new MessageBoxWindow() - .SetMessageType(MessageBoxWindow.MessageType.Warning) - .SetTitleText("Mod name can't be empty.") - .SetInfoText("Please provide a mod name.") - .Show(); + MessageTranslationHelper.ShowMessage(MessageTranslation.Warning_ModNameCantEmpty); return; } var invalidChars = Path.GetInvalidFileNameChars(); if (modName.Any(c => invalidChars.Contains(c))) { - new MessageBoxWindow() - .SetMessageType(MessageBoxWindow.MessageType.Warning) - .SetTitleText("Mod name Invalid.") - .SetInfoText("Mod name contains invalid characters.") - .Show(); + MessageTranslationHelper.ShowMessage(MessageTranslation.Warning_ModNameInvalid); Directory.Delete(PathManager.TempModsFolderPath, true); return; } @@ -260,11 +249,7 @@ private async void Install_Click(object sender, RoutedEventArgs e) } catch (Exception ex) { - new MessageBoxWindow() - .SetMessageType(MessageBoxWindow.MessageType.Error) - .SetTitleText("Download failed.") - .SetInfoText("An error occurred during download: " + ex.Message) - .Show(); + MessageTranslationHelper.ShowMessage(MessageTranslation.Error_ModDownloadFailed, null, [ex.Message]); } _ = LoadModDetailsAsync(CurrentMod.Id); diff --git a/WheelWizard/Views/Popups/ModManagement/ModIndependentWindow.axaml.cs b/WheelWizard/Views/Popups/ModManagement/ModIndependentWindow.axaml.cs index 47db974e..db508e3b 100644 --- a/WheelWizard/Views/Popups/ModManagement/ModIndependentWindow.axaml.cs +++ b/WheelWizard/Views/Popups/ModManagement/ModIndependentWindow.axaml.cs @@ -1,3 +1,4 @@ +using WheelWizard.Resources.Languages; using WheelWizard.Views.Pages; using WheelWizard.Views.Popups.Base; @@ -9,6 +10,8 @@ public ModIndependentWindow(string windowTitle = "Mod Details") : base(true, false, true, windowTitle) { InitializeComponent(); + if (Window.WindowTitle == "Mod Details") + Window.WindowTitle = Common.PopupTitle_ModDetails; } public async Task LoadModAsync(int modId, string? newDownloadUrl = null) diff --git a/WheelWizard/Views/Styles/Resources/Icons.axaml b/WheelWizard/Views/Styles/Resources/Icons.axaml index 7ed4283f..c9b629d8 100644 --- a/WheelWizard/Views/Styles/Resources/Icons.axaml +++ b/WheelWizard/Views/Styles/Resources/Icons.axaml @@ -89,6 +89,10 @@ + + + + @@ -177,4 +181,8 @@ M96 64a64 64 0 1 1 128 0A64 64 0 1 1 96 64zm48 320l0 96c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-192.2L59.1 321c-9.4 15-29.2 19.4-44.1 10S-4.5 301.9 4.9 287l39.9-63.3C69.7 184 113.2 160 160 160s90.3 24 115.2 63.6L315.1 287c9.4 15 4.9 34.7-10 44.1s-34.7 4.9-44.1-10L240 287.8 240 480c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-96-32 0z M256 80c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 144L48 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l144 0 0 144c0 17.7 14.3 32 32 32s32-14.3 32-32l0-144 144 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-144 0 0-144z M352 256c0 22.2-1.2 43.6-3.3 64l-185.3 0c-2.2-20.4-3.3-41.8-3.3-64s1.2-43.6 3.3-64l185.3 0c2.2 20.4 3.3 41.8 3.3 64zm28.8-64l123.1 0c5.3 20.5 8.1 41.9 8.1 64s-2.8 43.5-8.1 64l-123.1 0c2.1-20.6 3.2-42 3.2-64s-1.1-43.4-3.2-64zm112.6-32l-116.7 0c-10-63.9-29.8-117.4-55.3-151.6c78.3 20.7 142 77.5 171.9 151.6zm-149.1 0l-176.6 0c6.1-36.4 15.5-68.6 27-94.7c10.5-23.6 22.2-40.7 33.5-51.5C239.4 3.2 248.7 0 256 0s16.6 3.2 27.8 13.8c11.3 10.8 23 27.9 33.5 51.5c11.6 26 20.9 58.2 27 94.7zm-209 0L18.6 160C48.6 85.9 112.2 29.1 190.6 8.4C165.1 42.6 145.3 96.1 135.3 160zM8.1 192l123.1 0c-2.1 20.6-3.2 42-3.2 64s1.1 43.4 3.2 64L8.1 320C2.8 299.5 0 278.1 0 256s2.8-43.5 8.1-64zM194.7 446.6c-11.6-26-20.9-58.2-27-94.6l176.6 0c-6.1 36.4-15.5 68.6-27 94.6c-10.5 23.6-22.2 40.7-33.5 51.5C272.6 508.8 263.3 512 256 512s-16.6-3.2-27.8-13.8c-11.3-10.8-23-27.9-33.5-51.5zM135.3 352c10 63.9 29.8 117.4 55.3 151.6C112.2 482.9 48.6 426.1 18.6 352l116.7 0zm358.1 0c-30 74.1-93.6 130.9-171.9 151.6c25.5-34.2 45.2-87.7 55.3-151.6l116.7 0z + + M255.76 44.764c-6.176 0-12.353 1.384-17.137 4.152L85.87 137.276c-9.57 5.536-9.57 14.29 0 19.826l152.753 88.36c9.57 5.536 24.703 5.536 34.272 0l152.753-88.36c9.57-5.535 9.57-14.29 0-19.825l-152.753-88.36c-4.785-2.77-10.96-4.153-17.135-4.153zm.926 82.855a31.953 18.96 0 0 1 22.127 32.362 31.953 18.96 0 1 1-45.188-26.812 31.953 18.96 0 0 1 23.06-5.55zM75.67 173.84c-5.753-.155-9.664 4.336-9.664 12.28v157.696c0 11.052 7.57 24.163 17.14 29.69l146.93 84.848c9.57 5.526 17.14 1.156 17.14-9.895V290.76c0-11.052-7.57-24.16-17.14-29.688l-146.93-84.847c-2.69-1.555-5.225-2.327-7.476-2.387zm360.773.002c-2.25.06-4.783.83-7.474 2.385l-146.935 84.847c-9.57 5.527-17.14 18.638-17.14 29.69v157.7c0 11.05 7.57 15.418 17.14 9.89L428.97 373.51c9.57-5.527 17.137-18.636 17.137-29.688v-157.7c0-7.942-3.91-12.432-9.664-12.278zM89.297 195.77a31.236 18.008 58.094 0 1 33.818 41.183 31.236 18.008 58.094 1 1-45-25.98 31.236 18.008 58.094 0 1 11.182-15.203zm221.52 64.664A18.008 31.236 31.906 0 1 322 275.637a18.008 31.236 31.906 0 1-45 25.98 18.008 31.236 31.906 0 1 33.818-41.183zM145.296 289.1a31.236 18.008 58.094 0 1 33.818 41.183 31.236 18.008 58.094 0 1-45-25.98 31.236 18.008 58.094 0 1 11.182-15.203zm277.523 29.38A18.008 31.236 31.906 0 1 434 333.684a18.008 31.236 31.906 0 1-45 25.98 18.008 31.236 31.906 0 1 33.818-41.184zm-221.52 64.663a31.236 18.008 58.094 0 1 33.817 41.183 31.236 18.008 58.094 1 1-45-25.98 31.236 18.008 58.094 0 1 11.182-15.203z + M112 48a48 48 0 1 1 96 0 48 48 0 1 1 -96 0zm40 304l0 128c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-223.1L59.4 304.5c-9.1 15.1-28.8 20-43.9 10.9s-20-28.8-10.9-43.9l58.3-97c17.4-28.9 48.6-46.6 82.3-46.6l29.7 0c33.7 0 64.9 17.7 82.3 46.6l58.3 97c9.1 15.1 4.2 34.8-10.9 43.9s-34.8 4.2-43.9-10.9L232 256.9 232 480c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-128-16 0z + M160 0a48 48 0 1 1 0 96 48 48 0 1 1 0-96zM88 384l-17.8 0c-10.9 0-18.6-10.7-15.2-21.1L93.3 248.1 59.4 304.5c-9.1 15.1-28.8 20-43.9 10.9s-20-28.8-10.9-43.9l53.6-89.2c20.3-33.7 56.7-54.3 96-54.3l11.6 0c39.3 0 75.7 20.6 96 54.3l53.6 89.2c9.1 15.1 4.2 34.8-10.9 43.9s-34.8 4.2-43.9-10.9l-33.9-56.3L265 362.9c3.5 10.4-4.3 21.1-15.2 21.1L232 384l0 96c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-96-16 0 0 96c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-96z \ No newline at end of file diff --git a/WheelWizard/Views/Styles/Resources/MultiIcons.axaml b/WheelWizard/Views/Styles/Resources/MultiIcons.axaml index be0bb216..ce2a684a 100644 --- a/WheelWizard/Views/Styles/Resources/MultiIcons.axaml +++ b/WheelWizard/Views/Styles/Resources/MultiIcons.axaml @@ -10,10 +10,13 @@ - + @@ -35,8 +38,8 @@ - - + + @@ -51,4 +54,15 @@ + + + + + + + + + \ No newline at end of file diff --git a/WheelWizard/Views/Styles/Styles/ToggleButtonStyles.axaml b/WheelWizard/Views/Styles/Styles/ToggleButtonStyles.axaml index ac702149..a12850ff 100644 --- a/WheelWizard/Views/Styles/Styles/ToggleButtonStyles.axaml +++ b/WheelWizard/Views/Styles/Styles/ToggleButtonStyles.axaml @@ -6,15 +6,15 @@ - - + + - + - + - @@ -22,22 +22,23 @@ CheckBox: - - - - - + + + + + RadioButton: - + - + Switch: - - + + @@ -45,23 +46,24 @@ CheckBox: - - - - - + + + + + RadioButton: - - - - + + + + Switch: - - + + @@ -75,27 +77,27 @@ - + - + - + - - - - - + + + - - - - + + + - + - - - + @@ -226,8 +228,8 @@ - - + + - - - + @@ -265,56 +267,56 @@ - - + + - + - + - + \ No newline at end of file diff --git a/WheelWizard/WheelWizard.csproj b/WheelWizard/WheelWizard.csproj index 2997da77..915fdfeb 100644 --- a/WheelWizard/WheelWizard.csproj +++ b/WheelWizard/WheelWizard.csproj @@ -11,7 +11,7 @@ true - 2.2.0 + 2.3.3 This program will manage RetroRewind and mods :) GNU v3.0 https://github.com/patchzyy/WheelWizard @@ -19,15 +19,15 @@ - WINDOWS + $(DefineConstants);WINDOWS - LINUX + $(DefineConstants);LINUX - MACOS + $(DefineConstants);MACOS @@ -47,7 +47,7 @@ - + @@ -57,8 +57,8 @@ - all - runtime; build; native; contentfiles; analyzers; buildtransitive + all + runtime; build; native; contentfiles; analyzers; buildtransitive @@ -71,11 +71,6 @@ Common.resx - - - PublicResXFileCodeGenerator - Online.Designer.cs - Online.resx @@ -112,18 +107,13 @@ True Settings.resx - - True - True - Online.resx - - PopupListButton.axaml - Code + PopupListButton.axaml + Code - EditorEmpty.axaml - Code + EditorEmpty.axaml + Code @@ -141,7 +131,7 @@ - - + +