mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-30 14:39:42 +08:00
[ime] Add default profile
This commit is contained in:
@@ -45,6 +45,9 @@ public sealed class GuiSettings
|
||||
/// <summary>UI language, matching a file code under Languages/ (e.g. "en", "tr").</summary>
|
||||
public string Language { get; set; } = "en";
|
||||
|
||||
/// <summary>Default text-entry profile exposed to games.</summary>
|
||||
public string DefaultProfile { get; set; } = "Sharp";
|
||||
|
||||
/// <summary>Publish launcher/game status to Discord Rich Presence.</summary>
|
||||
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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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})",
|
||||
|
||||
@@ -346,6 +346,15 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
</ComboBox>
|
||||
</local:SettingRow>
|
||||
|
||||
<local:SettingRow x:Name="DefaultProfileRow"
|
||||
Label="{Binding [Options.DefaultProfile.Label], Source={x:Static local:Localization.Instance}}"
|
||||
Description="{Binding [Options.DefaultProfile.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<TextBox x:Name="DefaultProfileBox"
|
||||
Width="180"
|
||||
MaxLength="16"
|
||||
VerticalAlignment="Center" />
|
||||
</local:SettingRow>
|
||||
|
||||
<local:SettingRow x:Name="TitleMusicRow"
|
||||
Label="{Binding [Options.TitleMusic.Label], Source={x:Static local:Localization.Instance}}"
|
||||
Description="{Binding [Options.TitleMusic.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
@@ -646,6 +655,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
OffContent="{Binding [Common.Off], Source={x:Static local:Localization.Instance}}"
|
||||
VerticalAlignment="Center" />
|
||||
</local:SettingRow>
|
||||
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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<byte> 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<byte> 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<byte> 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<byte> 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user