mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-25 20:28:48 +08:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 85cc2b9892 | |||
| 293194c40b | |||
| 1f09de8896 | |||
| ddc452b4fc |
@@ -0,0 +1,129 @@
|
||||
{
|
||||
"_languageName": "Español",
|
||||
|
||||
"Page.Library": "Biblioteca",
|
||||
"Page.Options": "Opciones",
|
||||
"Page.GameCount.One": "1 juego",
|
||||
"Page.GameCount.Other": "{0} juegos",
|
||||
|
||||
"Library.SearchWatermark": "Buscar en la biblioteca…",
|
||||
"Library.AddFolder": "+ Añadir carpeta",
|
||||
"Library.Rescan": "⟳ Volver a escanear",
|
||||
"Library.OpenFile": "Abrir archivo…",
|
||||
|
||||
"Library.Context.Launch": "Iniciar",
|
||||
"Library.Context.OpenFolder": "Abrir carpeta de juegos",
|
||||
"Library.Context.CopyPath": "Copiar ruta",
|
||||
"Library.Context.CopyTitleId": "Copiar ID del título",
|
||||
"Library.Context.Remove": "Eliminar de la biblioteca",
|
||||
|
||||
"Library.Empty.Title": "Tu biblioteca está vacía",
|
||||
"Library.Empty.Hint": "Añade una carpeta que contenga tus juegos para empezar.",
|
||||
"Library.Empty.SearchTitle": "Ningún juego coincide con la búsqueda",
|
||||
"Library.Empty.SearchHint": "No se ha encontrado nada en la biblioteca que coincida con “{0}”.",
|
||||
"Library.Empty.AddFolder": "+ Añadir carpeta de juegos",
|
||||
|
||||
"Library.Loading": "Cargando biblioteca…",
|
||||
|
||||
"Options.General": "General",
|
||||
"Options.Section.Emulation": "EMULACIÓN",
|
||||
"Options.Section.Logging": "LOGS",
|
||||
"Options.Section.Launcher": "LAUNCHER",
|
||||
|
||||
"Options.CpuEngine.Label": "Motor de CPU",
|
||||
"Options.CpuEngine.Desc": "Motor utilizado para ejecutar el código del juego.",
|
||||
"Options.CpuEngine.Native": "Nativo",
|
||||
|
||||
"Options.Strict.Label": "Resolución estricta de dynlib (Bibliotecas dinámicas)",
|
||||
"Options.Strict.Desc": "Detener la ejecución cuando un símbolo importado no se pueda resolver.",
|
||||
|
||||
"Options.LogLevel.Label": "Nivel de Log",
|
||||
"Options.LogLevel.Desc": "Verbosidad de la salida en consola del emulador.",
|
||||
"Options.LogLevel.Trace": "Trazas",
|
||||
"Options.LogLevel.Debug": "Depuración",
|
||||
"Options.LogLevel.Info": "Información",
|
||||
"Options.LogLevel.Warning": "Advertencia",
|
||||
"Options.LogLevel.Error": "Error",
|
||||
"Options.LogLevel.Critical": "Crítico",
|
||||
|
||||
"Options.TraceImports.Label": "Límite de trazado de importaciones",
|
||||
"Options.TraceImports.Desc": "Trazar las primeras N importaciones por módulo (0 = off).",
|
||||
|
||||
"Options.LogToFile.Label": "Registrar log en archivo",
|
||||
"Options.LogToFile.Desc": "Duplicar la salida del emulador en un archivo de logs.",
|
||||
|
||||
"Options.LogFilePath.Label": "Ruta del archivo de Log",
|
||||
"Options.LogFilePath.Default": "Sin ruta personalizada — los logs van a user/logs al lado del emulador.",
|
||||
"Options.LogFilePath.Select": "Seleccionar…",
|
||||
|
||||
"Options.OverrideLogFile.Label": "Sobreescribir archivo de logs",
|
||||
"Options.OverrideLogFile.Desc": "Utilizar la misma ruta para el archivo de logs en vez de añadir la ID del título y marca de tiempo.",
|
||||
|
||||
"Options.TitleMusic.Label": "Música del título",
|
||||
"Options.TitleMusic.Desc": "Repetir en bucle la preview de la música del juego seleccionado en la biblioteca.",
|
||||
|
||||
"Options.Discord.Label": "Actividad de Discord",
|
||||
"Options.Discord.Desc": "Mostrar juego en ejecución en tu perfil de Discord.",
|
||||
|
||||
"Options.Language.Label": "Idioma del emulador",
|
||||
"Options.Language.Desc": "Idioma utilizado en todo el launcher. Se aplica inmediatamente.",
|
||||
|
||||
"Common.On": "Encendido",
|
||||
"Common.Off": "Apagado",
|
||||
|
||||
"Console.Title": "CONSOLA",
|
||||
"Console.SearchWatermark": "Buscar...",
|
||||
"Console.AutoScroll": "Desplazamiento automático",
|
||||
"Console.Split": "Desacoplar",
|
||||
"Console.Copy": "Copiar",
|
||||
"Console.Clear": "Limpiar",
|
||||
"Console.WindowTitle": "Consola SharpEmu",
|
||||
|
||||
"Launch.NoGameSelected": "No hay ningún juego seleccionado",
|
||||
"Launch.NoGameHint": "Selecciona un juego de la biblioteca o abre un eboot.bin directamente.",
|
||||
"Launch.Idle": "Inactivo",
|
||||
"Launch.Console": "≡ Consola",
|
||||
"Launch.Launch": "▶ Iniciar",
|
||||
"Launch.Stop": "■ Detener",
|
||||
"Launch.Running": "En ejecución — {0}",
|
||||
"Launch.Stopping": "Deteniendo…",
|
||||
"Launch.Exited": "Finalizó con el código {0} ({1})",
|
||||
"Launch.ExeNotFound": "No se ha encontrado el ejecutable de SharpEmu. Compila previamente el proyecto SharpEmu.CLI (dotnet build).",
|
||||
"Launch.LogFile": "Archivo de Log: {0}",
|
||||
"Launch.Command": "$ SharpEmu {0}",
|
||||
"Launch.StartFailed": "Error al iniciar el emulador: {0}",
|
||||
"Launch.ProcessExited": "El proceso finalizó con el código {0} ({1}).",
|
||||
|
||||
"Exit.Ok": "OK",
|
||||
"Exit.InvalidArguments": "argumentos no válidos",
|
||||
"Exit.EbootNotFound": "no se encontró eboot",
|
||||
"Exit.RuntimeException": "excepción en tiempo de ejecución",
|
||||
"Exit.EmulationError": "error de emulación",
|
||||
"Exit.Unknown": "desconocido",
|
||||
|
||||
"Status.EmulatorLocating": "Emulador: localizando…",
|
||||
"Status.EmulatorPath": "Emulador: {0}",
|
||||
"Status.EmulatorNotFound": "Emulador: No se encontró el ejecutable de SharpEmu — compila previamente SharpEmu.CLI.",
|
||||
"Status.ScanningLibrary": "Escaneando biblioteca…",
|
||||
"Status.AddFolderPrompt": "Añade una carpeta de juegos para poblar la biblioteca.",
|
||||
"Status.LibraryScanned": "Biblioteca escaneada: Se encontraron {0} juego(s) en {1} carpeta(s).",
|
||||
"Status.CouldNotOpenFolder": "No se ha podido abrir la carpeta: {0}",
|
||||
"Status.CopiedToClipboard": "{0} copiado al portapapeles.",
|
||||
"Status.RemovedFromLibrary": "Se eliminó “{0}” de la biblioteca. Vuelve a añadir su carpeta para restaurarlo.",
|
||||
"Status.Running": "Ejecutando {0}",
|
||||
"Status.Stopping": "Deteniendo…",
|
||||
"Status.Idle": "Inactivo",
|
||||
|
||||
"Clipboard.Path": "Ruta",
|
||||
"Clipboard.TitleId": "ID del título",
|
||||
|
||||
"Discord.Playing": "Jugando a {0}",
|
||||
"Discord.Browsing": "Navegando en la biblioteca, buscando un juego para divertirse.",
|
||||
|
||||
"Dialog.ChooseGameFolder": "Selecciona una carpeta que contenga juegos",
|
||||
"Dialog.OpenExecutable": "Abrir un ejecutable para iniciar",
|
||||
"Dialog.PsExecutables": "Ejecutables de PS",
|
||||
"Dialog.SaveLogFile": "Selecciona dónde guardar el archivo de Logs",
|
||||
"Dialog.PlainTextFiles": "Archivos en texto plano",
|
||||
"Dialog.LogFiles": "Archivos de Log"
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
{
|
||||
"_languageName": "Français",
|
||||
|
||||
"Page.Library": "Bibliothèque",
|
||||
"Page.Options": "Options",
|
||||
"Page.GameCount.One": "1 jeu",
|
||||
"Page.GameCount.Other": "{0} jeux",
|
||||
|
||||
"Library.SearchWatermark": "Rechercher dans la bibliothèque…",
|
||||
"Library.AddFolder": "+ Ajouter un dossier",
|
||||
"Library.Rescan": "⟳ Analyser à nouveau",
|
||||
"Library.OpenFile": "Ouvrir un fichier…",
|
||||
|
||||
"Library.Context.Launch": "Lancer",
|
||||
"Library.Context.OpenFolder": "Ouvrir le dossier du jeu",
|
||||
"Library.Context.CopyPath": "Copier le chemin",
|
||||
"Library.Context.CopyTitleId": "Copier l’identifiant du jeu",
|
||||
"Library.Context.Remove": "Retirer de la bibliothèque",
|
||||
|
||||
"Library.Empty.Title": "Votre bibliothèque est vide",
|
||||
"Library.Empty.Hint": "Ajoutez un dossier contenant vos jeux pour commencer.",
|
||||
"Library.Empty.SearchTitle": "Aucun jeu ne correspond à votre recherche",
|
||||
"Library.Empty.SearchHint": "Aucun élément de la bibliothèque ne correspond à « {0} ».",
|
||||
"Library.Empty.AddFolder": "+ Ajouter un dossier de jeux",
|
||||
|
||||
"Library.Loading": "Chargement de la bibliothèque…",
|
||||
|
||||
"Options.General": "Général",
|
||||
"Options.Section.Emulation": "ÉMULATION",
|
||||
"Options.Section.Logging": "JOURNALISATION",
|
||||
"Options.Section.Launcher": "LANCEUR",
|
||||
|
||||
"Options.CpuEngine.Label": "Moteur CPU",
|
||||
"Options.CpuEngine.Desc": "Moteur d’exécution utilisé pour exécuter le code du jeu.",
|
||||
"Options.CpuEngine.Native": "Natif",
|
||||
|
||||
"Options.Strict.Label": "Résolution stricte des bibliothèques dynamiques",
|
||||
"Options.Strict.Desc": "Interrompre le lancement lorsqu’un symbole importé ne peut pas être résolu.",
|
||||
|
||||
"Options.LogLevel.Label": "Niveau de journalisation",
|
||||
"Options.LogLevel.Desc": "Niveau de détail des messages affichés dans la console de l’émulateur.",
|
||||
"Options.LogLevel.Trace": "Traçage",
|
||||
"Options.LogLevel.Debug": "Débogage",
|
||||
"Options.LogLevel.Info": "Informations",
|
||||
"Options.LogLevel.Warning": "Avertissements",
|
||||
"Options.LogLevel.Error": "Erreurs",
|
||||
"Options.LogLevel.Critical": "Erreurs critiques",
|
||||
|
||||
"Options.TraceImports.Label": "Limite de traçage des imports",
|
||||
"Options.TraceImports.Desc": "Tracer les N premiers imports de chaque module (0 = désactivé).",
|
||||
|
||||
"Options.LogToFile.Label": "Enregistrer dans un fichier",
|
||||
"Options.LogToFile.Desc": "Copier la sortie de l’émulateur dans un fichier journal.",
|
||||
|
||||
"Options.LogFilePath.Label": "Chemin du fichier journal",
|
||||
"Options.LogFilePath.Default": "Aucun chemin personnalisé — les journaux sont enregistrés dans user/logs à côté de l’émulateur.",
|
||||
"Options.LogFilePath.Select": "Sélectionner…",
|
||||
|
||||
"Options.OverrideLogFile.Label": "Remplacer le fichier journal",
|
||||
"Options.OverrideLogFile.Desc": "Utiliser exactement ce chemin au lieu d’ajouter l’identifiant du jeu et l’horodatage.",
|
||||
|
||||
"Options.TitleMusic.Label": "Musique du jeu",
|
||||
"Options.TitleMusic.Desc": "Lire en boucle la musique d’aperçu du jeu sélectionné dans la bibliothèque.",
|
||||
|
||||
"Options.Discord.Label": "Présence Discord",
|
||||
"Options.Discord.Desc": "Afficher le jeu en cours d’exécution sur votre profil Discord.",
|
||||
|
||||
"Options.Language.Label": "Langue de l’émulateur",
|
||||
"Options.Language.Desc": "Langue utilisée dans l’ensemble du lanceur. Le changement est immédiat.",
|
||||
|
||||
"Common.On": "Activé",
|
||||
"Common.Off": "Désactivé",
|
||||
|
||||
"Console.Title": "CONSOLE",
|
||||
"Console.SearchWatermark": "Rechercher…",
|
||||
"Console.AutoScroll": "Défilement automatique",
|
||||
"Console.Split": "Détacher",
|
||||
"Console.Copy": "Copier",
|
||||
"Console.Clear": "Effacer",
|
||||
"Console.WindowTitle": "Console SharpEmu",
|
||||
|
||||
"Launch.NoGameSelected": "Aucun jeu sélectionné",
|
||||
"Launch.NoGameHint": "Choisissez un jeu dans la bibliothèque ou ouvrez directement un fichier eboot.bin.",
|
||||
"Launch.Idle": "Inactif",
|
||||
"Launch.Console": "≡ Console",
|
||||
"Launch.Launch": "▶ Lancer",
|
||||
"Launch.Stop": "■ Arrêter",
|
||||
"Launch.Running": "En cours d’exécution — {0}",
|
||||
"Launch.Stopping": "Arrêt en cours…",
|
||||
"Launch.Exited": "Processus terminé avec le code {0} ({1})",
|
||||
"Launch.ExeNotFound": "L’exécutable SharpEmu est introuvable. Compilez d’abord le projet SharpEmu.CLI (dotnet build).",
|
||||
"Launch.LogFile": "Fichier journal : {0}",
|
||||
"Launch.Command": "$ SharpEmu {0}",
|
||||
"Launch.StartFailed": "Impossible de démarrer l’émulateur : {0}",
|
||||
"Launch.ProcessExited": "Le processus s’est terminé avec le code {0} ({1}).",
|
||||
|
||||
"Exit.Ok": "OK",
|
||||
"Exit.InvalidArguments": "arguments non valides",
|
||||
"Exit.EbootNotFound": "eboot introuvable",
|
||||
"Exit.RuntimeException": "exception d’exécution",
|
||||
"Exit.EmulationError": "erreur d’émulation",
|
||||
"Exit.Unknown": "inconnu",
|
||||
|
||||
"Status.EmulatorLocating": "Émulateur : recherche en cours…",
|
||||
"Status.EmulatorPath": "Émulateur : {0}",
|
||||
"Status.EmulatorNotFound": "Émulateur : l’exécutable SharpEmu est introuvable — compilez d’abord SharpEmu.CLI.",
|
||||
"Status.ScanningLibrary": "Analyse de la bibliothèque…",
|
||||
"Status.AddFolderPrompt": "Ajoutez un dossier de jeux pour remplir la bibliothèque.",
|
||||
"Status.LibraryScanned": "Bibliothèque analysée : {0} jeu(x) dans {1} dossier(s).",
|
||||
"Status.CouldNotOpenFolder": "Impossible d’ouvrir le dossier : {0}",
|
||||
"Status.CopiedToClipboard": "{0} copié dans le presse-papiers.",
|
||||
"Status.RemovedFromLibrary": "« {0} » a été retiré de la bibliothèque. Ajoutez à nouveau son dossier pour le restaurer.",
|
||||
"Status.Running": "Exécution de {0}",
|
||||
"Status.Stopping": "Arrêt en cours…",
|
||||
"Status.Idle": "Inactif",
|
||||
|
||||
"Clipboard.Path": "Chemin",
|
||||
"Clipboard.TitleId": "Identifiant du jeu",
|
||||
|
||||
"Discord.Playing": "Joue à {0}",
|
||||
"Discord.Browsing": "Parcourt la bibliothèque",
|
||||
|
||||
"Dialog.ChooseGameFolder": "Choisir un dossier contenant des jeux",
|
||||
"Dialog.OpenExecutable": "Ouvrir un exécutable à lancer",
|
||||
"Dialog.PsExecutables": "Exécutables PlayStation",
|
||||
"Dialog.SaveLogFile": "Choisir l’emplacement du fichier journal",
|
||||
"Dialog.PlainTextFiles": "Fichiers texte brut",
|
||||
"Dialog.LogFiles": "Fichiers journaux"
|
||||
}
|
||||
@@ -802,8 +802,15 @@ internal static class Gen5ShaderTranslator
|
||||
0x14 => "VCmpxGtF32",
|
||||
0x15 => "VCmpxLgF32",
|
||||
0x16 => "VCmpxGeF32",
|
||||
0x17 => "VCmpxOF32",
|
||||
0x18 => "VCmpxUF32",
|
||||
0x19 => "VCmpxNgeF32",
|
||||
0x1A => "VCmpxNlgF32",
|
||||
0x1B => "VCmpxNgtF32",
|
||||
0x1C => "VCmpxNleF32",
|
||||
0x1D => "VCmpxNeqF32",
|
||||
0x1E => "VCmpxNltF32",
|
||||
0x1F => "VCmpxTruF32",
|
||||
0x80 => "VCmpFI32",
|
||||
0x81 => "VCmpLtI32",
|
||||
0x82 => "VCmpEqI32",
|
||||
|
||||
@@ -861,6 +861,25 @@ internal static partial class Gen5SpirvTranslator
|
||||
{
|
||||
condition = _module.ConstantBool(true);
|
||||
}
|
||||
else if (opcode is "VCmpOF32" or "VCmpxOF32" or "VCmpUF32" or "VCmpxUF32")
|
||||
{
|
||||
// The ordered/unordered predicates only test whether either
|
||||
// operand is NaN. SPIR-V's OpOrdered/OpUnordered are Kernel-only,
|
||||
// so build the same result from OpIsNan, which needs no extra
|
||||
// capability: unordered = isnan(a) || isnan(b), ordered = !that.
|
||||
var left = GetFloatSource(instruction, 0);
|
||||
var right = GetFloatSource(instruction, 1);
|
||||
var nanLeft = _module.AddInstruction(SpirvOp.IsNan, _boolType, left);
|
||||
var nanRight = _module.AddInstruction(SpirvOp.IsNan, _boolType, right);
|
||||
var unordered = _module.AddInstruction(
|
||||
SpirvOp.LogicalOr,
|
||||
_boolType,
|
||||
nanLeft,
|
||||
nanRight);
|
||||
condition = opcode is "VCmpUF32" or "VCmpxUF32"
|
||||
? unordered
|
||||
: _module.AddInstruction(SpirvOp.LogicalNot, _boolType, unordered);
|
||||
}
|
||||
else if (opcode is not ("VCmpClassF32" or "VCmpxClassF32") &&
|
||||
opcode.EndsWith("F32", StringComparison.Ordinal))
|
||||
{
|
||||
@@ -875,6 +894,7 @@ internal static partial class Gen5SpirvTranslator
|
||||
"VCmpLgF32" or "VCmpxLgF32" => SpirvOp.FOrdNotEqual,
|
||||
"VCmpGeF32" or "VCmpxGeF32" => SpirvOp.FOrdGreaterThanEqual,
|
||||
"VCmpNeqF32" or "VCmpxNeqF32" => SpirvOp.FUnordNotEqual,
|
||||
"VCmpNlgF32" or "VCmpxNlgF32" => SpirvOp.FUnordEqual,
|
||||
"VCmpNltF32" or "VCmpxNltF32" => SpirvOp.FUnordGreaterThanEqual,
|
||||
"VCmpNleF32" or "VCmpxNleF32" => SpirvOp.FUnordGreaterThan,
|
||||
"VCmpNgtF32" or "VCmpxNgtF32" => SpirvOp.FUnordLessThanEqual,
|
||||
@@ -925,7 +945,8 @@ internal static partial class Gen5SpirvTranslator
|
||||
condition = _module.AddInstruction(operation, _boolType, left, right);
|
||||
}
|
||||
|
||||
StoreWaveMask(106, condition);
|
||||
// On gfx10, VCmpx writes EXEC only and preserves VCC; the sdst
|
||||
// operand was removed from the cmpx encodings on this generation.
|
||||
if (opcode.StartsWith("VCmpx", StringComparison.Ordinal))
|
||||
{
|
||||
var active = _module.AddInstruction(
|
||||
@@ -935,6 +956,10 @@ internal static partial class Gen5SpirvTranslator
|
||||
condition);
|
||||
StoreWaveMask(126, active);
|
||||
}
|
||||
else
|
||||
{
|
||||
StoreWaveMask(106, condition);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -192,9 +192,43 @@ public static class PadExports
|
||||
LibraryName = "libScePad")]
|
||||
public static int PadSetTriggerEffect(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var parameterAddress = ctx[CpuRegister.Rsi];
|
||||
if (handle != PrimaryPadHandle)
|
||||
{
|
||||
return ctx.SetReturn(OrbisPadErrorInvalidHandle);
|
||||
}
|
||||
|
||||
if (parameterAddress == 0)
|
||||
{
|
||||
return ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
Span<byte> parameter = stackalloc byte[120];
|
||||
if (!ctx.Memory.TryRead(parameterAddress, parameter))
|
||||
{
|
||||
return ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
var triggerMask = parameter[0];
|
||||
XInputReader.SetTriggerRumble(
|
||||
(triggerMask & 0x01) != 0 ? DecodeTriggerVibration(parameter[8..64]) : null,
|
||||
(triggerMask & 0x02) != 0 ? DecodeTriggerVibration(parameter[64..120]) : null);
|
||||
return ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
|
||||
private static byte DecodeTriggerVibration(ReadOnlySpan<byte> command)
|
||||
{
|
||||
var mode = BinaryPrimitives.ReadUInt32LittleEndian(command);
|
||||
var amplitude = mode switch
|
||||
{
|
||||
3 when command[10] != 0 => command[9],
|
||||
6 when command[8] != 0 => command[9..19].ToArray().Max(),
|
||||
_ => (byte)0,
|
||||
};
|
||||
return (byte)(Math.Min(amplitude, (byte)8) * 255 / 8);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "yFVnOdGxvZY",
|
||||
ExportName = "scePadSetVibration",
|
||||
|
||||
@@ -39,6 +39,8 @@ internal static class XInputReader
|
||||
private static int _slot = -1; // connected XInput user index, -1 when none
|
||||
private static byte _motorLeft;
|
||||
private static byte _motorRight;
|
||||
private static byte _triggerLeft;
|
||||
private static byte _triggerRight;
|
||||
|
||||
/// <summary>Starts the background reader once; safe to call repeatedly.</summary>
|
||||
internal static void EnsureStarted()
|
||||
@@ -99,6 +101,31 @@ internal static class XInputReader
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Approximates per-trigger vibration on the two XInput body motors.</summary>
|
||||
internal static void SetTriggerRumble(byte? leftTrigger, byte? rightTrigger)
|
||||
{
|
||||
lock (Gate)
|
||||
{
|
||||
var changed = false;
|
||||
if (leftTrigger is { } left)
|
||||
{
|
||||
changed |= _triggerLeft != left;
|
||||
_triggerLeft = left;
|
||||
}
|
||||
|
||||
if (rightTrigger is { } right)
|
||||
{
|
||||
changed |= _triggerRight != right;
|
||||
_triggerRight = right;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
SendRumbleLocked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void SendRumbleLocked()
|
||||
{
|
||||
if (_slot < 0)
|
||||
@@ -108,8 +135,8 @@ internal static class XInputReader
|
||||
|
||||
var vibration = new XInputVibration
|
||||
{
|
||||
LeftMotorSpeed = (ushort)(_motorLeft * 257), // 0..255 -> 0..65535
|
||||
RightMotorSpeed = (ushort)(_motorRight * 257),
|
||||
LeftMotorSpeed = (ushort)(Math.Max(_motorLeft, _triggerLeft) * 257),
|
||||
RightMotorSpeed = (ushort)(Math.Max(_motorRight, _triggerRight) * 257),
|
||||
};
|
||||
_ = XInputSetState((uint)_slot, ref vibration);
|
||||
}
|
||||
@@ -147,6 +174,8 @@ internal static class XInputReader
|
||||
_slot = -1;
|
||||
_motorLeft = 0;
|
||||
_motorRight = 0;
|
||||
_triggerLeft = 0;
|
||||
_triggerRight = 0;
|
||||
_state = default;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user