From c2cee59fdaa3d3846c0affa58e3af35d5aae450c Mon Sep 17 00:00:00 2001 From: ParantezTech <12572227+par274@users.noreply.github.com> Date: Wed, 29 Jul 2026 15:39:01 +0300 Subject: [PATCH] [ime] Add default profile --- src/SharpEmu.GUI/GuiSettings.cs | 21 +++ src/SharpEmu.GUI/Languages/en.json | 2 + src/SharpEmu.GUI/Languages/tr.json | 2 + src/SharpEmu.GUI/MainWindow.axaml | 10 ++ src/SharpEmu.GUI/MainWindow.axaml.cs | 15 ++ src/SharpEmu.Libs/Ime/ImeDialogExports.cs | 175 ++++++++++++++++++++++ 6 files changed, 225 insertions(+) create mode 100644 src/SharpEmu.Libs/Ime/ImeDialogExports.cs diff --git a/src/SharpEmu.GUI/GuiSettings.cs b/src/SharpEmu.GUI/GuiSettings.cs index 73e98be2..2634830c 100644 --- a/src/SharpEmu.GUI/GuiSettings.cs +++ b/src/SharpEmu.GUI/GuiSettings.cs @@ -45,6 +45,9 @@ public sealed class GuiSettings /// UI language, matching a file code under Languages/ (e.g. "en", "tr"). public string Language { get; set; } = "en"; + /// Default text-entry profile exposed to games. + public string DefaultProfile { get; set; } = "Sharp"; + /// Publish launcher/game status to Discord Rich Presence. public bool DiscordRichPresence { get; set; } = true; @@ -112,6 +115,18 @@ public sealed class GuiSettings settings.EnvironmentToggles = FilterNullOrEmpty(settings.EnvironmentToggles); settings.LogLevel ??= "Info"; settings.Language ??= "en"; + var legacyProfile = settings.EnvironmentToggles + .Select(entry => entry.Split('=', 2, StringSplitOptions.TrimEntries)) + .FirstOrDefault(parts => + parts.Length == 2 && + string.Equals(parts[0], "SHARPEMU_DEFAULT_PROFILE", StringComparison.OrdinalIgnoreCase)); + settings.EnvironmentToggles.RemoveAll(entry => + string.Equals( + entry.Split('=', 2, StringSplitOptions.TrimEntries)[0], + "SHARPEMU_DEFAULT_PROFILE", + StringComparison.OrdinalIgnoreCase)); + settings.DefaultProfile = NormalizeDefaultProfile( + legacyProfile is { Length: 2 } ? legacyProfile[1] : settings.DefaultProfile); settings.DiscordClientId ??= "1525606762248540221"; if (settings.RenderResolutionScale <= 0 || settings.RenderResolutionScale > 2.0) { @@ -152,6 +167,12 @@ public sealed class GuiSettings return $"{width}x{height}"; } + internal static string NormalizeDefaultProfile(string? value) + { + var trimmed = value?.Trim(); + return string.IsNullOrEmpty(trimmed) ? "Sharp" : trimmed; + } + public void Save() { try diff --git a/src/SharpEmu.GUI/Languages/en.json b/src/SharpEmu.GUI/Languages/en.json index fc5cc2a8..8a59c75e 100644 --- a/src/SharpEmu.GUI/Languages/en.json +++ b/src/SharpEmu.GUI/Languages/en.json @@ -38,6 +38,8 @@ "Options.Env.LogIo.Desc": "Log file open, read, and path-resolve activity to the console.\nUse when a game cannot find its data files during boot.", "Options.Env.LogNp.Desc": "Log NP (PlayStation Network) library calls to the console.", "Options.Env.GuestImageCpuSync.Desc": "Re-upload guest surfaces the game's own CPU code rewrites.\nLeave off normally. Turn on for titles whose CPU-drawn surfaces never reach the screen.\nCosts performance and regresses some titles, such as GTA V.", + "Options.DefaultProfile.Label": "Default profile name", + "Options.DefaultProfile.Desc": "Name used when a game asks for text input. Defaults to Sharp.", "Options.Section.Emulation": "EMULATION", "Options.Section.Logging": "LOGGING", "Options.Section.Launcher": "LAUNCHER", diff --git a/src/SharpEmu.GUI/Languages/tr.json b/src/SharpEmu.GUI/Languages/tr.json index aa6fa458..3a26330a 100644 --- a/src/SharpEmu.GUI/Languages/tr.json +++ b/src/SharpEmu.GUI/Languages/tr.json @@ -173,6 +173,8 @@ "Options.Env.LogIo.Desc": "Dosya açma, okuma ve yol çözümleme etkinliğini konsola günlükle.\nBir oyun açılışta veri dosyalarını bulamıyorsa kullanın.", "Options.Env.LogNp.Desc": "NP (PlayStation Network) kütüphane çağrılarını konsola günlükle.", "Options.Env.GuestImageCpuSync.Desc": "Oyunun kendi CPU kodunun yeniden yazdığı misafir yüzeyleri tekrar yükler.\nNormalde kapalı bırakın. CPU ile çizilen yüzeyleri ekrana ulaşmayan oyunlarda açın.\nPerformansa mal olur ve GTA V gibi bazı oyunlarda soruna yol açar.", + "Options.DefaultProfile.Label": "Varsayilan profil adi", + "Options.DefaultProfile.Desc": "Oyun metin girisi istediginde kullanilacak ad. Varsayilan deger Sharp'tir.", "Common.Save": "Kaydet", "Common.Cancel": "İptal", "PerGame.Title": "Oyuna özel ayarlar — {0} ({1})", diff --git a/src/SharpEmu.GUI/MainWindow.axaml b/src/SharpEmu.GUI/MainWindow.axaml index f398c3a8..74ead40a 100644 --- a/src/SharpEmu.GUI/MainWindow.axaml +++ b/src/SharpEmu.GUI/MainWindow.axaml @@ -346,6 +346,15 @@ SPDX-License-Identifier: GPL-2.0-or-later + + + + @@ -646,6 +655,7 @@ SPDX-License-Identifier: GPL-2.0-or-later OffContent="{Binding [Common.Off], Source={x:Static local:Localization.Instance}}" VerticalAlignment="Center" /> + diff --git a/src/SharpEmu.GUI/MainWindow.axaml.cs b/src/SharpEmu.GUI/MainWindow.axaml.cs index 086a5dca..8188e75a 100644 --- a/src/SharpEmu.GUI/MainWindow.axaml.cs +++ b/src/SharpEmu.GUI/MainWindow.axaml.cs @@ -298,6 +298,8 @@ public partial class MainWindow : Window SetEnvironmentToggle( "SHARPEMU_GUEST_IMAGE_CPU_SYNC", EnvGuestImageCpuSyncToggle.IsChecked == true); + DefaultProfileBox.TextChanged += (_, _) => + _settings.DefaultProfile = GuiSettings.NormalizeDefaultProfile(DefaultProfileBox.Text); LanguageBox.SelectionChanged += (_, _) => OnLanguageChanged(); GameList.AddHandler(ContextRequestedEvent, OnGameContextRequested, RoutingStrategies.Tunnel); @@ -883,6 +885,7 @@ public partial class MainWindow : Window EnvLogNpToggle.IsChecked = _settings.EnvironmentToggles.Contains("SHARPEMU_LOG_NP"); EnvGuestImageCpuSyncToggle.IsChecked = _settings.EnvironmentToggles.Contains("SHARPEMU_GUEST_IMAGE_CPU_SYNC"); + DefaultProfileBox.Text = _settings.DefaultProfile; WindowModeBox.SelectedIndex = ChoiceIndex(_settings.WindowMode, "Windowed", "Borderless", "Exclusive"); LoadHostDisplayOptions(); ScalingModeBox.SelectedIndex = ChoiceIndex(_settings.ScalingMode, "Fit", "Cover", "Stretch", "Integer"); @@ -1109,6 +1112,8 @@ public partial class MainWindow : Window } } + private const string DefaultProfileEnvironmentName = "SHARPEMU_DEFAULT_PROFILE"; + private string SelectedLogLevel() { return LogLevelBox.SelectedIndex switch @@ -2006,10 +2011,20 @@ public partial class MainWindow : Window continue; } + if (string.Equals(name, DefaultProfileEnvironmentName, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + Environment.SetEnvironmentVariable(name, value); _appliedEnvironmentVariables.Add(name); } + Environment.SetEnvironmentVariable( + DefaultProfileEnvironmentName, + GuiSettings.NormalizeDefaultProfile(_settings.DefaultProfile)); + _appliedEnvironmentVariables.Add(DefaultProfileEnvironmentName); + Environment.SetEnvironmentVariable( "SHARPEMU_RENDER_SCALE", _settings.RenderResolutionScale.ToString( diff --git a/src/SharpEmu.Libs/Ime/ImeDialogExports.cs b/src/SharpEmu.Libs/Ime/ImeDialogExports.cs new file mode 100644 index 00000000..bd756b9a --- /dev/null +++ b/src/SharpEmu.Libs/Ime/ImeDialogExports.cs @@ -0,0 +1,175 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using System; +using System.Buffers.Binary; +using System.Text; +using SharpEmu.HLE; + +namespace SharpEmu.Libs.Ime; + +public static class ImeDialogExports +{ + private const int StatusNone = 0; + private const int StatusRunning = 1; + private const int StatusFinished = 2; + + private const int EndStatusOk = 0; + + private const ulong ParamMaxTextLengthOffset = 0x24; + private const ulong ParamInputTextBufferOffset = 0x28; + + private const int ImeDialogErrorInvalidAddress = unchecked((int)0x80BC0001); + + private const string DefaultInputText = "Sharp"; + + private static readonly object _gate = new(); + private static int _status = StatusNone; + + [SysAbiExport( + Nid = "NUeBrN7hzf0", + ExportName = "sceImeDialogInit", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceImeDialog")] + public static int ImeDialogInit(CpuContext ctx) + { + var parameterAddress = ctx[CpuRegister.Rdi]; + if (parameterAddress == 0) + { + return SetReturn(ctx, ImeDialogErrorInvalidAddress); + } + + TryWriteInputText(ctx, parameterAddress); + + lock (_gate) + { + _status = StatusFinished; + } + + return SetReturn(ctx, 0); + } + + [SysAbiExport( + Nid = "IADmD4tScBY", + ExportName = "sceImeDialogGetStatus", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceImeDialog")] + public static int ImeDialogGetStatus(CpuContext ctx) + { + int status; + lock (_gate) + { + status = _status; + } + + ctx[CpuRegister.Rax] = unchecked((ulong)(long)status); + return status; + } + + [SysAbiExport( + Nid = "x01jxu+vxlc", + ExportName = "sceImeDialogGetResult", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceImeDialog")] + public static int ImeDialogGetResult(CpuContext ctx) + { + var resultAddress = ctx[CpuRegister.Rdi]; + if (resultAddress == 0) + { + return SetReturn(ctx, ImeDialogErrorInvalidAddress); + } + + Span result = stackalloc byte[8]; + result.Clear(); + BinaryPrimitives.WriteInt32LittleEndian(result, EndStatusOk); + ctx.Memory.TryWrite(resultAddress, result); + + return SetReturn(ctx, 0); + } + + [SysAbiExport( + Nid = "oBmw4xrmfKs", + ExportName = "sceImeDialogAbort", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceImeDialog")] + public static int ImeDialogAbort(CpuContext ctx) + { + lock (_gate) + { + _status = StatusFinished; + } + + return SetReturn(ctx, 0); + } + + [SysAbiExport( + Nid = "gyTyVn+bXMw", + ExportName = "sceImeDialogTerm", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceImeDialog")] + public static int ImeDialogTerm(CpuContext ctx) + { + lock (_gate) + { + _status = StatusNone; + } + + return SetReturn(ctx, 0); + } + + private static void TryWriteInputText(CpuContext ctx, ulong parameterAddress) + { + Span field = stackalloc byte[8]; + if (!ctx.Memory.TryRead(parameterAddress + ParamInputTextBufferOffset, field)) + { + return; + } + + var bufferAddress = BinaryPrimitives.ReadUInt64LittleEndian(field); + if (bufferAddress == 0) + { + return; + } + + var maxTextLength = 16u; + Span lengthField = stackalloc byte[4]; + if (ctx.Memory.TryRead(parameterAddress + ParamMaxTextLengthOffset, lengthField)) + { + var declared = BinaryPrimitives.ReadUInt32LittleEndian(lengthField); + if (declared is > 0 and <= 256) + { + maxTextLength = declared; + } + } + + var text = Environment.GetEnvironmentVariable("SHARPEMU_DEFAULT_PROFILE"); + if (string.IsNullOrEmpty(text)) + { + text = DefaultInputText; + } + + if ((uint)text.Length > maxTextLength) + { + text = text[..(int)maxTextLength]; + } + + var encoded = Encoding.Unicode.GetBytes(text); + Span payload = stackalloc byte[encoded.Length + 2]; + encoded.CopyTo(payload); + payload[^2] = 0; + payload[^1] = 0; + + if (ctx.Memory.TryWrite(bufferAddress, payload)) + { + Console.Error.WriteLine( + $"[LOADER][WARN] ime.dialog_autofill buf=0x{bufferAddress:X16} " + + $"max={maxTextLength} text=\"{text}\""); + } + } + + private static int SetReturn(CpuContext ctx, int result) + { + ctx[CpuRegister.Rax] = unchecked((ulong)(long)result); + return result; + } +}