mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2026-06-26 14:19:04 +00:00
Compare commits
5 Commits
Canary-1.3
...
Canary-1.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
737b951ee9 | ||
|
|
5566e752a4 | ||
|
|
a5f72136b2 | ||
|
|
aa5d32a7b1 | ||
|
|
be5881f100 |
@@ -3,11 +3,11 @@
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Avalonia" Version="11.3.17" />
|
||||
<PackageVersion Include="Avalonia" Version="11.3.18" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.13" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.17" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.17" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.17" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.18" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.18" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.18" />
|
||||
<PackageVersion Include="SharpCompress" Version="0.49.1" />
|
||||
<PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.9.5" />
|
||||
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.9.5" />
|
||||
|
||||
@@ -9606,7 +9606,7 @@
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"el_GR": "",
|
||||
"en_US": "Sends more data to the controller for better rumble.\n\nCurrently only supports first-party Nintendo Switch controllers.\n\nLeave ON if you're using JoyCons or a Pro Controller.",
|
||||
"en_US": "EXPERIMENTAL.\n\nSends more data to the controller for better rumble.\n\nCurrently only supports first-party Nintendo Switch controllers.\n\nLeave OFF if unsure.",
|
||||
"es_ES": "",
|
||||
"fr_FR": "",
|
||||
"he_IL": "",
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
private readonly bool[] _supportedPlayers;
|
||||
private VibrationValue _neutralVibrationValue = new()
|
||||
{
|
||||
AmplitudeLow = 0f,
|
||||
AmplitudeLow = 0.01f,
|
||||
FrequencyLow = 160f,
|
||||
AmplitudeHigh = 0f,
|
||||
FrequencyHigh = 320f,
|
||||
|
||||
@@ -13,84 +13,98 @@ namespace Ryujinx.Input.SDL3
|
||||
{
|
||||
private readonly SDL_hid_device* _hidHandle;
|
||||
|
||||
private byte[] _buffer;
|
||||
private static ushort _vendor;
|
||||
private static ushort _product;
|
||||
|
||||
private int _globalCount;
|
||||
private ulong _lastWriteTicks;
|
||||
|
||||
private NpadHdRumble(SDL_hid_device* hidHandle)
|
||||
{
|
||||
_hidHandle = hidHandle;
|
||||
InitializeDevice();
|
||||
}
|
||||
|
||||
public static NpadHdRumble Create(SDL_Gamepad* gamepadHandle)
|
||||
{
|
||||
ushort vendor = SDL_GetGamepadVendor(gamepadHandle);
|
||||
if (vendor != 0x057e)
|
||||
_vendor = SDL_GetGamepadVendor(gamepadHandle);
|
||||
if (!Enum.IsDefined(typeof(HDRumbleSupportedVendor), _vendor))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
ushort product = SDL_GetGamepadProduct(gamepadHandle);
|
||||
if (!Enum.IsDefined(typeof(HDRumbleSupported), product))
|
||||
_product = SDL_GetGamepadProduct(gamepadHandle);
|
||||
if (!Enum.IsDefined(typeof(HDRumbleSupportedProduct), _product))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new NpadHdRumble(SDL_hid_open(vendor, product, 0));
|
||||
int serialNumber = 0;
|
||||
string? serial = SDL_GetGamepadSerial(gamepadHandle);
|
||||
if (serial is not null)
|
||||
{
|
||||
int.TryParse(serial, out serialNumber);
|
||||
}
|
||||
|
||||
return new NpadHdRumble(SDL_hid_open(_vendor, _product, serialNumber));
|
||||
}
|
||||
|
||||
// Some of the code was translated from https://github.com/MIZUSHIKI/JoyShockLibrary-plus-HDRumble
|
||||
private bool WriteHdRumble(
|
||||
int encLeftLowFreq, int encLeftLowAmp,
|
||||
int encLeftHighFreq, int encLeftHighAmp,
|
||||
int encRightLowFreq, int encRightLowAmp,
|
||||
int encRightHighFreq, int encRightHighAmp)
|
||||
private bool WriteNintendoHdRumble(VibrationValue left, VibrationValue right)
|
||||
{
|
||||
byte[] buf = new byte[10];
|
||||
|
||||
buf[0] = 0x10;
|
||||
buf[1] = (byte)((++_globalCount) & 0xF);
|
||||
|
||||
buf[2] = (byte)(encLeftHighFreq & 0xFF);
|
||||
buf[3] = (byte)(encLeftHighAmp + ((encLeftHighFreq >> 8) & 0xFF));
|
||||
buf[4] = (byte)(encLeftLowFreq + ((encLeftLowAmp >> 8) & 0xFF));
|
||||
buf[5] = (byte)(encLeftLowAmp & 0xFF);
|
||||
|
||||
buf[6] = (byte)(encRightHighFreq & 0xFF);
|
||||
buf[7] = (byte)(encRightHighAmp + ((encRightHighFreq >> 8) & 0xFF));
|
||||
buf[8] = (byte)(encRightLowFreq + ((encRightLowAmp >> 8) & 0xFF));
|
||||
buf[9] = (byte)(encRightLowAmp & 0xFF);
|
||||
|
||||
int leftLowAmp = EncodeLowAmp(left.AmplitudeLow);
|
||||
int leftLowFreq = EncodeLowFreq(left.FrequencyLow) + (leftLowAmp >> 8);
|
||||
int leftHighFreq = EncodeHighFreq(left.FrequencyHigh);
|
||||
int leftHighAmp = EncodeHighAmp(left.AmplitudeHigh) + (leftHighFreq >> 8);
|
||||
|
||||
int rightLowAmp = EncodeLowAmp(right.AmplitudeLow);
|
||||
int rightLowFreq = EncodeLowFreq(right.FrequencyLow) + (rightLowAmp >> 8);
|
||||
int rightHighFreq = EncodeHighFreq(right.FrequencyHigh);
|
||||
int rightHighAmp = EncodeHighAmp(right.AmplitudeHigh) + (rightHighFreq >> 8);
|
||||
|
||||
_buffer[0] = 0x10;
|
||||
_buffer[1] = (byte)((_globalCount++) & 0xF);
|
||||
|
||||
// Left LRA
|
||||
_buffer[2] = (byte)(leftLowFreq & 0xFF);
|
||||
_buffer[3] = (byte)(leftHighAmp & 0xFF);
|
||||
_buffer[4] = (byte)(leftHighFreq & 0xFF);
|
||||
_buffer[5] = (byte)(leftLowAmp & 0xFF);
|
||||
|
||||
// Right LRA
|
||||
_buffer[6] = (byte)(rightLowFreq & 0xFF);
|
||||
_buffer[7] = (byte)(rightHighAmp & 0xFF);
|
||||
_buffer[8] = (byte)(rightHighFreq & 0xFF);
|
||||
_buffer[9] = (byte)(rightLowAmp & 0xFF);
|
||||
|
||||
if (_globalCount > 0xF)
|
||||
{
|
||||
_globalCount = 0x0;
|
||||
}
|
||||
|
||||
fixed (byte* ptr = buf)
|
||||
|
||||
fixed (byte* ptr = _buffer)
|
||||
{
|
||||
if (SendHDRumble(ptr, (nuint)buf.Length) >= 0)
|
||||
if (SendHdRumble(ptr, (nuint)_buffer.Length) >= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(SDL_GetError()))
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.Hid, SDL_GetError());
|
||||
SDL_ClearError();
|
||||
}
|
||||
return false;
|
||||
Logger.Error?.PrintMsg(LogClass.Hid, SDL_GetError());
|
||||
SDL_ClearError();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int EncodeLowFreq(float lowFreq)
|
||||
{
|
||||
float lf = Math.Clamp(lowFreq, 40.875885f, 626.286133f);
|
||||
return (int) Math.Round(32 * Math.Log2(lf * 0.1f) - 0x40);
|
||||
return (int)Math.Clamp(32 * Math.Log2(lowFreq * 0.1f) - 0x40, 81.75177f, 1252.572266f);
|
||||
}
|
||||
|
||||
private static int EncodeHighFreq(float highFreq)
|
||||
{
|
||||
float hf = Math.Clamp(highFreq, 81.75177f, 1252.572266f);
|
||||
return (int) Math.Round((32 * Math.Log2(hf * 0.1f) - 0x60) * 4);
|
||||
return (int)Math.Clamp(32 * Math.Log2(highFreq * 0.1f) - 0x60, 81.75177f, 1252.572266f);
|
||||
}
|
||||
|
||||
private static int EncodeLowAmp(float rawAmp)
|
||||
@@ -98,23 +112,20 @@ namespace Ryujinx.Input.SDL3
|
||||
double encodedAmp = 0;
|
||||
|
||||
if (rawAmp is > 0 and < 0.012f)
|
||||
{
|
||||
encodedAmp = 1;
|
||||
}
|
||||
|
||||
else if (rawAmp is >= 0.012f and < 0.112f)
|
||||
{
|
||||
encodedAmp = 4 * Math.Log2(rawAmp * 110f);
|
||||
}
|
||||
|
||||
else if (rawAmp is >= 0.112f and < 0.225f)
|
||||
{
|
||||
encodedAmp = 16 * Math.Log2(rawAmp * 17f);
|
||||
}
|
||||
|
||||
else if (rawAmp is >= 0.225f and <= 1f)
|
||||
{
|
||||
encodedAmp = 32 * Math.Log2(rawAmp * 8.7f);
|
||||
}
|
||||
|
||||
return (int)Math.Floor(encodedAmp / 2.0) + 64;
|
||||
|
||||
encodedAmp = Math.Round((encodedAmp / 2.0) + 64.0);
|
||||
encodedAmp = Math.Clamp(encodedAmp, 0.0, 100.2867);
|
||||
return (int)Math.Round(encodedAmp);
|
||||
}
|
||||
|
||||
private static int EncodeHighAmp(float rawAmp)
|
||||
@@ -122,82 +133,156 @@ namespace Ryujinx.Input.SDL3
|
||||
double encodedAmp = 0;
|
||||
|
||||
if (rawAmp is > 0 and < 0.012f)
|
||||
{
|
||||
encodedAmp = 1;
|
||||
}
|
||||
|
||||
else if (rawAmp is >= 0.012f and < 0.112f)
|
||||
{
|
||||
encodedAmp = 4 * Math.Log2(rawAmp * 110f);
|
||||
}
|
||||
|
||||
else if (rawAmp is >= 0.112f and < 0.225f)
|
||||
{
|
||||
encodedAmp = 16 * Math.Log2(rawAmp * 17f);
|
||||
}
|
||||
|
||||
else if (rawAmp is >= 0.225f and <= 1f)
|
||||
{
|
||||
encodedAmp = 32 * Math.Log2(rawAmp * 8.7f);
|
||||
}
|
||||
|
||||
return (int) Math.Round(encodedAmp * 2);
|
||||
|
||||
encodedAmp = Math.Round(encodedAmp / 2.0);
|
||||
encodedAmp = Math.Clamp(encodedAmp, 0.0, 100.2867);
|
||||
return (int)encodedAmp;
|
||||
}
|
||||
|
||||
public bool HdRumble(VibrationValue left, VibrationValue right)
|
||||
{
|
||||
return WriteHdRumble(EncodeLowFreq(left.FrequencyLow),
|
||||
EncodeLowAmp(left.AmplitudeLow),
|
||||
EncodeHighFreq(left.FrequencyHigh),
|
||||
EncodeHighAmp(left.AmplitudeHigh),
|
||||
EncodeLowFreq(right.FrequencyLow),
|
||||
EncodeLowAmp(right.AmplitudeLow),
|
||||
EncodeHighFreq(right.FrequencyHigh),
|
||||
EncodeHighAmp(right.AmplitudeHigh));
|
||||
if(_product is (ushort) HDRumbleSupportedProduct.ProController
|
||||
or (ushort) HDRumbleSupportedProduct.JoyconLeft
|
||||
or (ushort) HDRumbleSupportedProduct.JoyconRight
|
||||
or (ushort) HDRumbleSupportedProduct.JoyconPair
|
||||
or (ushort) HDRumbleSupportedProduct.JoyconGrip)
|
||||
{
|
||||
return WriteNintendoHdRumble(left, right);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private int SendHDRumble(byte* data, nuint length)
|
||||
private int SendHdRumble(byte* data, nuint length)
|
||||
{
|
||||
int result = 0;
|
||||
ulong currentTicks = SDL_GetTicks();
|
||||
|
||||
// Ditch rumble if we haven't hit the poll-rate yet.
|
||||
// TODO: figure out a better way to do this
|
||||
// While the polling check makes the rumble accurate, it also causes it to miss signals.
|
||||
if ((currentTicks - _lastWriteTicks) < 8) // https://docs.handheldlegend.com/s/progcc-3/doc/lag-comparison-aAR1mV3JLX
|
||||
if ((currentTicks - _lastWriteTicks) <= GetPollRate())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
SDL_LockJoysticks();
|
||||
result = SDL_hid_write(_hidHandle, data, length);
|
||||
if (result >= 0)
|
||||
{
|
||||
// Fun fact: Mario Kart 8 Deluxe sends rumble packets
|
||||
// where the amplitude is zero, but the frequency isn't.
|
||||
result = SDL_hid_write(_hidHandle, data, length);
|
||||
if (result >= 0)
|
||||
{
|
||||
_lastWriteTicks = currentTicks;
|
||||
}
|
||||
_lastWriteTicks = currentTicks;
|
||||
}
|
||||
SDL_UnlockJoysticks();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void InitializeDevice()
|
||||
{
|
||||
if (_vendor is (ushort)HDRumbleSupportedVendor.Nintendo)
|
||||
{
|
||||
_buffer = new byte[10];
|
||||
byte[] init = new byte[64];
|
||||
|
||||
// Pro Controller and Charge Grip
|
||||
if (_product
|
||||
is (ushort)HDRumbleSupportedProduct.ProController
|
||||
or (ushort)HDRumbleSupportedProduct.JoyconGrip)
|
||||
{
|
||||
SDL_LockJoysticks();
|
||||
fixed (byte* ptr = init)
|
||||
{
|
||||
init[0] = 0x80;
|
||||
init[1] = 0x05; // Allow bluetooth timeout TODO: use 0x04 to force USB only (toggle?)
|
||||
SDL_hid_write(_hidHandle, ptr, 64);
|
||||
}
|
||||
SDL_UnlockJoysticks();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Joycons
|
||||
if (_product
|
||||
is (ushort)HDRumbleSupportedProduct.JoyconLeft
|
||||
or (ushort)HDRumbleSupportedProduct.JoyconRight
|
||||
or (ushort)HDRumbleSupportedProduct.JoyconPair)
|
||||
{
|
||||
|
||||
SDL_LockJoysticks();
|
||||
fixed (byte* ptr = init)
|
||||
{
|
||||
// we could write data to the controller here (see above)
|
||||
}
|
||||
SDL_UnlockJoysticks();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ulong GetPollRate()
|
||||
{
|
||||
ulong pollRate = 0;
|
||||
if (_vendor is (ushort)HDRumbleSupportedVendor.Nintendo)
|
||||
{
|
||||
// https://docs.handheldlegend.com/s/progcc-3/doc/lag-comparison-aAR1mV3JLX
|
||||
pollRate = (ulong) 16.67;
|
||||
if (_product is (ushort)HDRumbleSupportedProduct.ProController
|
||||
&& SDL_hid_get_device_info(_hidHandle)->bus_type == SDL_hid_bus_type.SDL_HID_API_BUS_USB)
|
||||
{
|
||||
pollRate = (ulong) 8.33;
|
||||
}
|
||||
}
|
||||
|
||||
return pollRate;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
SDL_hid_close(_hidHandle);
|
||||
}
|
||||
}
|
||||
|
||||
public enum HDRumbleSupported : ushort
|
||||
public enum HDRumbleSupportedVendor : ushort
|
||||
{
|
||||
JoyConLeft = 0x2006,
|
||||
JoyConRight = 0x2007,
|
||||
Nintendo = 0x057e,
|
||||
Valve = 0x28de,
|
||||
Sony = 0x054c
|
||||
}
|
||||
|
||||
public enum HDRumbleSupportedProduct : ushort
|
||||
{
|
||||
// TODO: Currently, HD Rumble only supports the Pro Controller and JoyCons.
|
||||
// We need to initialize and report to each device differently.
|
||||
|
||||
// Nintendo Switch: 0x057e
|
||||
JoyconLeft = 0x2006,
|
||||
JoyconRight = 0x2007,
|
||||
JoyconPair = 0x2008,
|
||||
ProController = 0x2009,
|
||||
JoyconGrip = 0x200e,
|
||||
|
||||
// Nintendo Switch 2: 0x057e
|
||||
Joycon2Right = 0x2066,
|
||||
Joycon2Left = 0x2067,
|
||||
Joycon2Pair = 0x2068,
|
||||
Switch2ProController = 0x2069,
|
||||
GamecubeController = 0x2073
|
||||
GamecubeController = 0x2073,
|
||||
|
||||
// Valve Steam Family: 0x28de
|
||||
// https://github.com/libsdl-org/SDL/issues/9148
|
||||
SteamDeck = 0x11ff,
|
||||
SteamDeckVirtualDevice = 0x1205,
|
||||
SteamController = 0x1106,
|
||||
|
||||
// PlayStation Dualsense: 0x054c
|
||||
Dualsense = 0x0ce6
|
||||
}
|
||||
}
|
||||
|
||||
@@ -582,12 +582,12 @@ namespace Ryujinx.Input.HLE
|
||||
Logger.Debug?.Print(LogClass.Hid, $"Effect for {controllerConfig.PlayerIndex} " +
|
||||
// Value=value/multiplier * multiplier (result)
|
||||
$"L.low.amp={leftVibrationValue.AmplitudeLow / controllerConfig.Rumble.WeakRumble} * {controllerConfig.Rumble.WeakRumble} ({leftVibrationValue.AmplitudeLow}), " +
|
||||
$"L.high.amp={leftVibrationValue.AmplitudeHigh / controllerConfig.Rumble.WeakRumble} * {controllerConfig.Rumble.WeakRumble} ({leftVibrationValue.AmplitudeHigh}), " +
|
||||
$"L.high.amp={leftVibrationValue.AmplitudeHigh / controllerConfig.Rumble.StrongRumble} * {controllerConfig.Rumble.StrongRumble} ({leftVibrationValue.AmplitudeHigh}), " +
|
||||
$"L.low.freq={leftVibrationValue.FrequencyLow / controllerConfig.Rumble.WeakRumble} * {controllerConfig.Rumble.WeakRumble} ({leftVibrationValue.FrequencyLow}), " +
|
||||
$"L.high.freq={leftVibrationValue.FrequencyHigh / controllerConfig.Rumble.WeakRumble} * {controllerConfig.Rumble.WeakRumble} ({leftVibrationValue.FrequencyHigh}), " +
|
||||
$"R.low.amp={rightVibrationValue.AmplitudeLow / controllerConfig.Rumble.StrongRumble} * {controllerConfig.Rumble.StrongRumble} ({rightVibrationValue.AmplitudeLow}), " +
|
||||
$"L.high.freq={leftVibrationValue.FrequencyHigh / controllerConfig.Rumble.StrongRumble} * {controllerConfig.Rumble.StrongRumble} ({leftVibrationValue.FrequencyHigh}), " +
|
||||
$"R.low.amp={rightVibrationValue.AmplitudeLow / controllerConfig.Rumble.WeakRumble} * {controllerConfig.Rumble.WeakRumble} ({rightVibrationValue.AmplitudeLow}), " +
|
||||
$"R.high.amp={rightVibrationValue.AmplitudeHigh / controllerConfig.Rumble.StrongRumble} * {controllerConfig.Rumble.StrongRumble} ({rightVibrationValue.AmplitudeHigh}), " +
|
||||
$"R.low.freq={rightVibrationValue.FrequencyLow / controllerConfig.Rumble.StrongRumble} * {controllerConfig.Rumble.StrongRumble} ({rightVibrationValue.FrequencyLow}), " +
|
||||
$"R.low.freq={rightVibrationValue.FrequencyLow / controllerConfig.Rumble.WeakRumble} * {controllerConfig.Rumble.WeakRumble} ({rightVibrationValue.FrequencyLow}), " +
|
||||
$"R.high.freq={rightVibrationValue.FrequencyHigh / controllerConfig.Rumble.StrongRumble} * {controllerConfig.Rumble.StrongRumble} ({rightVibrationValue.FrequencyHigh})");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ namespace Ryujinx.Input
|
||||
StrongRumble = 1f,
|
||||
WeakRumble = 1f,
|
||||
EnableRumble = false,
|
||||
UseHDRumble = true,
|
||||
UseHDRumble = false
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
216
src/Ryujinx/Assets/PlayReports/nsmbud.json
Normal file
216
src/Ryujinx/Assets/PlayReports/nsmbud.json
Normal file
@@ -0,0 +1,216 @@
|
||||
{
|
||||
"mario": {
|
||||
"1": {
|
||||
"1": "Acorn Plains Way",
|
||||
"2": "Tilted Tunnel",
|
||||
"21": "Crushing-Cogs Tower",
|
||||
"3": "Yoshi Hill",
|
||||
"4": "Mushroom Heights",
|
||||
"5": "Rise of the Piranha Plants",
|
||||
"23": "Lemmy's Swingback Castle",
|
||||
"13": "Blooper's Secret Lair"
|
||||
},
|
||||
"2": {
|
||||
"1": "Stone-Eye Zone",
|
||||
"2": "Perilous Pokey Cave",
|
||||
"3": "Fire Snake Cavern",
|
||||
"21": "Stoneslide Tower",
|
||||
"4": "Spike's Spouting Sands",
|
||||
"5": "Dry Desert Mushrooms",
|
||||
"6": "Blooming Lakitus",
|
||||
"23": "Morton's Compactor Castle",
|
||||
"14": "Piranha Plants on Ice"
|
||||
},
|
||||
"3": {
|
||||
"1": "Waterspout Beach",
|
||||
"2": "Tropical Refresher",
|
||||
"21": "Giant Skewer Tower",
|
||||
"20": "Haunted Shipwreck",
|
||||
"3": "Above the Cheep Cheep Seas",
|
||||
"4": "Urchin Shoals",
|
||||
"5": "Dragoneel's Undersea Grotto",
|
||||
"23": "Larry's Torpedo Castle",
|
||||
"15": "Skyward Stalk"
|
||||
},
|
||||
"4": {
|
||||
"1": "Spinning-Star Sky",
|
||||
"2": "Cooligan Fields",
|
||||
"21": "Freezing-Rain Tower",
|
||||
"3": "Prickly Goombas!",
|
||||
"4": "Scaling the Mountainside",
|
||||
"5": "Icicle Caverns",
|
||||
"20": "Swaying Ghost House",
|
||||
"23": "Wendy's Shifting Castle",
|
||||
"16": "Fliprus Lake"
|
||||
},
|
||||
"5": {
|
||||
"37": "The Mighty Cannonship",
|
||||
"1": "Jungle of the Giants",
|
||||
"2": "Bridge over Poisoned Waters",
|
||||
"3": "Bramball Woods",
|
||||
"21": "Snake Block Tower",
|
||||
"20": "Which-Way Labyrinth",
|
||||
"4": "Painted Swampland",
|
||||
"5": "Deepsea Ruins",
|
||||
"6": "Seesaw Bridge",
|
||||
"7": "Wiggler Stampede",
|
||||
"23": "Iggy's Volcanic Castle",
|
||||
"17": "Flight of the Para-Beetles"
|
||||
},
|
||||
"6": {
|
||||
"1": "Fuzzy Clifftop",
|
||||
"2": "Porcupuffer Falls",
|
||||
"21": "Grinding-Stone Tower",
|
||||
"3": "Wadlewing's Nest",
|
||||
"4": "Light Blocks, Dark Tower",
|
||||
"5": "Walking Piranha Plants!",
|
||||
"6": "Thrilling Spine Coaster",
|
||||
"22": "Screwtop Tower",
|
||||
"7": "Shifting-Floor Cave",
|
||||
"23": "Roy's Conveyor Castle"
|
||||
},
|
||||
"7": {
|
||||
"1": "Land of Flying Blocks",
|
||||
"2": "Seesaw Shrooms",
|
||||
"3": "Switchback Hill",
|
||||
"21": "Slide Lift Tower",
|
||||
"20": "Spinning Spirit House",
|
||||
"4": "Bouncy Cloud Boomerangs",
|
||||
"5": "A Quick Dip in the Sky",
|
||||
"6": "Snaking above Mist Valley",
|
||||
"23": "Ludwig's Clockwork Castle",
|
||||
"37": "Boarding the Airship"
|
||||
|
||||
},
|
||||
"8": {
|
||||
"1": "Meteor Moat",
|
||||
"2": "Magma-River Cruise",
|
||||
"3": "Rising Tides of Lava",
|
||||
"4": "firefall Cliffs",
|
||||
"42": "Red-Hot Elevator Ride",
|
||||
"43": "The Final Battle"
|
||||
},
|
||||
"9": {
|
||||
"1": "Spine-Tingling Spine Coaster",
|
||||
"2": "Run for It",
|
||||
"3": "Swim for Your Life!",
|
||||
"4": "Hammerswing Caverns",
|
||||
"5": "Spinning Platforms of Doom",
|
||||
"6": "Fire Bar Cliffs",
|
||||
"7": "Lakitu! Lakitu! Lakitu!",
|
||||
"8": "Pendulum Castle",
|
||||
"9": "Follow That Shell!"
|
||||
},
|
||||
"11": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": "",
|
||||
"7": "",
|
||||
"8": ""
|
||||
}
|
||||
},
|
||||
"luigi": {
|
||||
"1": {
|
||||
"1": "Waddlewing Warning!",
|
||||
"2": "Crooked Cavern",
|
||||
"21": "Flame-Gear Tower",
|
||||
"3": "Rolling Yoshi Hills",
|
||||
"4": "Piranha Heights",
|
||||
"5": "Piranha Gardens",
|
||||
"23": "Lemmy's Lights-Out Castle",
|
||||
"13": "Cheep Chomp Chase"
|
||||
},
|
||||
"2": {
|
||||
"1": "Spike's Tumbling Desert",
|
||||
"2": "Underground Grrrols",
|
||||
"3": "Piranhas in the Dark",
|
||||
"21": "Wind-Up Tower",
|
||||
"4": "The Walls Have Eyes",
|
||||
"5": "Stone Spike Conveyors",
|
||||
"6": "Spinning Sandstones",
|
||||
"23": "Morton's Lava-Block Castle",
|
||||
"14": "Slippery Rope Ladders"
|
||||
},
|
||||
"3": {
|
||||
"1": "Huckit Beach Resort",
|
||||
"2": "Urchin Reef Romp",
|
||||
"21": "Shish-Kebab Tower",
|
||||
"20": "Haunted Cargo Hold",
|
||||
"3": "Waterspout Sprint",
|
||||
"4": "The Great Geysers",
|
||||
"5": "Dragoneel Depths",
|
||||
"23": "Larry's Trigger-Happy Castle",
|
||||
"15": "Beanstalk Jungle"
|
||||
},
|
||||
"4": {
|
||||
"1": "Broozers and Barrels",
|
||||
"2": "Cooligan Shrooms",
|
||||
"21": "Icicle Tower",
|
||||
"3": "Fire and Ice",
|
||||
"4": "Weighty Waddlewings",
|
||||
"5": "Ice-Slide Expressway",
|
||||
"20": "Peek-a-Boo Ghost House",
|
||||
"23": "Wendy's Thwomp Castle",
|
||||
"16": "Fliprus Floes"
|
||||
},
|
||||
"5": {
|
||||
"1": "Giant Swing-Along",
|
||||
"2": "Dancing Blocks, Poison Swamp",
|
||||
"3": "Heart of Bramball Woods",
|
||||
"21": "Stone-Snake Tower",
|
||||
"20": "Boo's Favorite Haunt",
|
||||
"4": "Painted Pipeworks",
|
||||
"5": "Deepsea Stone-Eyes",
|
||||
"6": "Sumo Bro Bridge",
|
||||
"7": "Wiggler Floodlands",
|
||||
"23": "Iggy's Swinging-Chains Castle",
|
||||
"17": "Para-Beetle Parade"
|
||||
},
|
||||
"6": {
|
||||
"1": "Mount Fuzzy",
|
||||
"2": "Porcupuffer Cavern",
|
||||
"21": "Smashing-Stone Tower",
|
||||
"3": "Spike's Seesaws",
|
||||
"4": "Light-Up-Lift Tower",
|
||||
"5": "Rising Piranhas",
|
||||
"6": "Spine Coaster Stowaways",
|
||||
"22": "Sumo Bro's Spinning Tower",
|
||||
"7": "Switch-Lift Express",
|
||||
"23": "Roy's Ironclad Castle"
|
||||
},
|
||||
"7": {
|
||||
"1": "Frozen Fuzzies",
|
||||
"2": "Wiggler Rodeo",
|
||||
"3": "Rainbow Skywalk",
|
||||
"21": "Stonecrush Tower",
|
||||
"20": "Vanishing Ghost House",
|
||||
"4": "Above The Bouncy Clouds",
|
||||
"5": "Flame Chomp Ferris Wheel",
|
||||
"6": "Three-Headed Snake Block",
|
||||
"23": "Ludwig's Block-Press Castle",
|
||||
"37": "Bowser Jr. Showdown"
|
||||
},
|
||||
"8": {
|
||||
"1": "Magma Moat",
|
||||
"2": "Magmaw River Cruise",
|
||||
"3": "Hot Cogs",
|
||||
"4": "Firefall Rising",
|
||||
"42": "Current Event",
|
||||
"43": "The Final Battle"
|
||||
},
|
||||
"9": {
|
||||
"1": "Spine Coaster Connections",
|
||||
"2": "P Switch Peril",
|
||||
"3": "Star Coin Deep Dive",
|
||||
"4": "Hammerswing Hideout",
|
||||
"5": "Under Construction",
|
||||
"6": "Fire Bar Sprint",
|
||||
"7": "Cloudy Capers",
|
||||
"8": "Impossible Pendulums",
|
||||
"9": "Flying Squirrel Ovation"
|
||||
}
|
||||
}
|
||||
}
|
||||
98
src/Ryujinx/Assets/Splashes.json
Normal file
98
src/Ryujinx/Assets/Splashes.json
Normal file
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"Locales": {
|
||||
"ar_SA": [],
|
||||
"de_DE": [],
|
||||
"el_GR": [],
|
||||
"en_US": [
|
||||
"Ryubing is my middle name.",
|
||||
"Giving it 110 percent!",
|
||||
"I don't think therefore I don't am!",
|
||||
"All hail Egg.",
|
||||
"Insert cringy joke here.",
|
||||
"ITS RYUBINGING TIME!",
|
||||
"I hate Mondays...",
|
||||
"Fantastical!",
|
||||
"Now with 100% more humor!",
|
||||
"'Not S&P approved' has been approved by S&P.",
|
||||
"ARE YOU NOT ENTERTAINED?",
|
||||
"It's an emulator!",
|
||||
"Now the real game begins...",
|
||||
"Cooked fresh since 2018!",
|
||||
"Must've been the wind...",
|
||||
"I used to be an adventurer like you before I took an arrow to the knee.",
|
||||
"Ryubing!",
|
||||
"May contain nuts!",
|
||||
"May include occasional pop culture references!",
|
||||
"100% organically grown!",
|
||||
"Have a nice day : )",
|
||||
"Spoats car!",
|
||||
"Bottom text",
|
||||
"Im sorry Dave. I'm afraid I can't do that.",
|
||||
"That's no moon...",
|
||||
"Sir, finishing this fight.",
|
||||
"I see how it is...",
|
||||
"Space! The final frontier!",
|
||||
"If you could not tell already, I love making bad jokes : )",
|
||||
"this.",
|
||||
"Probably contains no baked beans.",
|
||||
"Y'all ready for this?",
|
||||
"Removed Herobrine.",
|
||||
"Right to repair!",
|
||||
"Programmed in C#!",
|
||||
"Forgejo has dethroned Gitlab!",
|
||||
"Any ideas what to put here?",
|
||||
"Good morning!",
|
||||
"Good afternoon!",
|
||||
"Good evening!",
|
||||
"I hope you are having a great day!",
|
||||
"Please insert disc two!",
|
||||
"I... AM RYUBING!",
|
||||
"Ryubingin' it up",
|
||||
"bing bing wahoo.",
|
||||
"egg",
|
||||
"No, lossless scaling is NOT supported.",
|
||||
"How do you people do anything?",
|
||||
"One dollar.",
|
||||
"Somebody once told me!",
|
||||
"Its that time of the year again!",
|
||||
"Brewed from only the finest memes.",
|
||||
"Async shader compilation would destroy my soul : (",
|
||||
"Trans rights are human rights!",
|
||||
":3",
|
||||
"Patched ':3' splash replication glitch.",
|
||||
"Please connect a controller!",
|
||||
"Never gonna give you up!",
|
||||
"The game was rigged from the start.",
|
||||
"Ganon is watching you!",
|
||||
"Now with 100% more JSON in the splash code!",
|
||||
"Countless hours of fun!",
|
||||
"Sorry, Link. I can't give credit. Come back when you're a little... mmmmmm... richer!",
|
||||
"Do a barrel roll!",
|
||||
"You've met with a terrible fate, haven't you?",
|
||||
"Yahaha! You found me!",
|
||||
"I would've been in real trouble if you hadn't shown up when you did, goro.",
|
||||
"Stay fresh!",
|
||||
"Yellow, black. Yellow, black. Yellow, black. Yellow, black. Ooh, black and yellow! Let's shake it up a little.",
|
||||
"Whaaa? You came to see me again? That makes Beedle SO HAPPY!",
|
||||
"Don't get cooked, stay off the hook!",
|
||||
"Now with 100% more good vibes in the splash code!",
|
||||
"It is Wednesday my dudes!"
|
||||
],
|
||||
"es_ES": [],
|
||||
"fr_FR": [],
|
||||
"he_IL": [],
|
||||
"it_IT": [],
|
||||
"ja_JP": [],
|
||||
"ko_KR": [],
|
||||
"no_NO": [],
|
||||
"pl_PL": [],
|
||||
"pt_BR": [],
|
||||
"ru_RU": [],
|
||||
"sv_SE": [],
|
||||
"th_TH": [],
|
||||
"tr_TR": [],
|
||||
"uk_UA": [],
|
||||
"zh_CN": [],
|
||||
"zh_TW": []
|
||||
}
|
||||
}
|
||||
64
src/Ryujinx/Common/SplashTextHelper.cs
Normal file
64
src/Ryujinx/Common/SplashTextHelper.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System.Collections.Generic;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Gommon;
|
||||
using Ryujinx.Ava.Systems.Configuration;
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
public class SplashTextHelper
|
||||
{
|
||||
public static void PrintSplash()
|
||||
{
|
||||
Logger.Notice.Print(LogClass.Application, " ___ __ _ ");
|
||||
Logger.Notice.Print(LogClass.Application, @" / _ \ __ __ __ __ / / (_) ___ ___ _");
|
||||
Logger.Notice.Print(LogClass.Application, @" / , _/ / // // // / / _ \ / / / _ \ / _ `/");
|
||||
Logger.Notice.Print(LogClass.Application, @"/_/|_| \_, / \_,_/ /_.__//_/ /_//_/ \_, / ");
|
||||
Logger.Notice.Print(LogClass.Application, " /___/ /___/ ");
|
||||
Logger.Notice.Print(LogClass.Application, "");
|
||||
Logger.Notice.Print(LogClass.Application, GetSplash());
|
||||
Logger.Notice.Print(LogClass.Application, "");
|
||||
}
|
||||
|
||||
private static string s_finalSplash = "";
|
||||
|
||||
public static string GetSplash()
|
||||
{
|
||||
if (string.IsNullOrEmpty(s_finalSplash))
|
||||
{
|
||||
s_finalSplash = GetLangJson();
|
||||
if (string.IsNullOrEmpty(s_finalSplash))
|
||||
{
|
||||
s_finalSplash = "Splash Text";
|
||||
}
|
||||
}
|
||||
|
||||
return $"{s_finalSplash}";
|
||||
}
|
||||
|
||||
private static SplashLocales s_splashJson;
|
||||
|
||||
private static string GetLangJson()
|
||||
{
|
||||
try
|
||||
{
|
||||
string data;
|
||||
data = EmbeddedResources.ReadAllText("Ryujinx/Assets/Splashes.json");
|
||||
s_splashJson = JsonSerializer.Deserialize<SplashLocales>(data);
|
||||
return s_splashJson.Locales[ConfigurationState.Instance.UI.LanguageCode.Value].GetRandomElement();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private struct SplashLocales
|
||||
{
|
||||
public Dictionary<string, List<string>> Locales { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -437,13 +437,9 @@ namespace Ryujinx.Ava
|
||||
|
||||
internal static void PrintSystemInfo()
|
||||
{
|
||||
Logger.Notice.Print(LogClass.Application, " ___ __ _ ");
|
||||
Logger.Notice.Print(LogClass.Application, @" / _ \ __ __ __ __ / / (_) ___ ___ _");
|
||||
Logger.Notice.Print(LogClass.Application, @" / , _/ / // // // / / _ \ / / / _ \ / _ `/");
|
||||
Logger.Notice.Print(LogClass.Application, @"/_/|_| \_, / \_,_/ /_.__//_/ /_//_/ \_, / ");
|
||||
Logger.Notice.Print(LogClass.Application, " /___/ /___/ ");
|
||||
|
||||
|
||||
// Print the ryubing logo + joke splash
|
||||
SplashTextHelper.PrintSplash();
|
||||
|
||||
Logger.Notice.Print(LogClass.Application, $"{RyujinxApp.FullAppName} Version: {Version}");
|
||||
Logger.Notice.Print(LogClass.Application, $".NET Runtime: {RuntimeInformation.FrameworkDescription}");
|
||||
SystemInfo.Gather().Print();
|
||||
|
||||
@@ -175,6 +175,8 @@
|
||||
<EmbeddedResource Include="Assets\UIImages\Logo_Ryujinx.png" />
|
||||
<EmbeddedResource Include="Assets\UIImages\Logo_Forgejo.png" />
|
||||
<EmbeddedResource Include="Assets\UIImages\Logo_Ryujinx_AntiAlias.png" />
|
||||
<EmbeddedResource Include="Assets\PlayReports\*.json" />
|
||||
<EmbeddedResource Include="Assets\Splashes.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="..\..\assets\Locales\*.json" />
|
||||
|
||||
@@ -336,7 +336,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
EnableRumble = false,
|
||||
StrongRumble = 1f,
|
||||
WeakRumble = 1f,
|
||||
UseHDRumble = true
|
||||
UseHDRumble = false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using Gommon;
|
||||
using Humanizer;
|
||||
using MsgPack;
|
||||
using Ryujinx.Common;
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Ryujinx.Ava.Systems.PlayReport
|
||||
{
|
||||
@@ -1116,5 +1118,87 @@ namespace Ryujinx.Ava.Systems.PlayReport
|
||||
_ => "Wandering"
|
||||
};
|
||||
}
|
||||
|
||||
private static FormattedValue NsmbudRpc(SparseMultiValue values)
|
||||
{
|
||||
if (values.Matched.TryGetValue("WorldNo", out Value world) && values.Matched.TryGetValue("CourseNo", out Value course) | values.Matched.TryGetValue("GameModeType", out Value gamemode))
|
||||
{
|
||||
string worldstr = world.ToString();
|
||||
string coursestr = course.ToString();
|
||||
int courseint = Int32.Parse(coursestr);
|
||||
string gamemodestr = gamemode.ToString();
|
||||
|
||||
try
|
||||
{
|
||||
Dictionary<string, Dictionary<string, Dictionary<string, string>>> output;
|
||||
string data;
|
||||
data = EmbeddedResources.ReadAllText("Ryujinx/Assets/PlayReports/nsmbud.json");
|
||||
output = JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, Dictionary<string, string>>>>(data);
|
||||
if (SpecialMapNames(courseint) == "Hazard")
|
||||
{
|
||||
return $"Last Played: Course {worldstr}-Hazard";
|
||||
}
|
||||
string outputloc = output[MarioOrLuigiGamemode(gamemodestr)][worldstr][coursestr];
|
||||
return $"Last Played: Course {worldstr}-{SpecialMapNames(courseint)} | {outputloc}";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return FormattedValue.ForceReset;
|
||||
}
|
||||
}
|
||||
|
||||
if (values.Matched.TryGetValue("RlId", out Value RlId) | values.Matched.TryGetValue("TotalPlayTime", out Value TotalPlayTime))
|
||||
{
|
||||
return "At the main menu";
|
||||
}
|
||||
|
||||
static string MarioOrLuigiGamemode(string? gamemode) => gamemode switch
|
||||
{
|
||||
"0" => "mario",
|
||||
"1" => "luigi",
|
||||
"4" => "mario",
|
||||
"5" => "mario",
|
||||
_ => gamemode
|
||||
};
|
||||
|
||||
static string OtherGameMode(string? gamemode) => gamemode switch
|
||||
{
|
||||
"2" => "Boost Rush",
|
||||
"3" => "Challenges",
|
||||
"4" => "Coin Battle",
|
||||
"5" => "Coin Battle Editor",
|
||||
_ => ""
|
||||
};
|
||||
|
||||
static string SpecialMapNames(int? course) => course switch
|
||||
{
|
||||
>= 1 and <= 9 => course.ToString(),
|
||||
13 => "Shortcut",
|
||||
14 => "Shortcut",
|
||||
15 => "Shortcut",
|
||||
16 => "Shortcut",
|
||||
17 => "Shortcut",
|
||||
20 => "Ghost",
|
||||
21 => "Tower",
|
||||
22 => "Tower",
|
||||
23 => "Castle",
|
||||
37 => "Airship",
|
||||
42 => "Castle",
|
||||
43 => "Castle",
|
||||
_ => "Hazard"
|
||||
};
|
||||
|
||||
// For future reference
|
||||
// Tower course = 21, Castle course = 23,Haunted Mansion/ship = 20
|
||||
// Tower course 2 (rock candy) = 22
|
||||
// Peach castle 1 = 42, Peach final battle = 43
|
||||
// airship = 37, jungle beetles = 17
|
||||
// Glacier seals = 16, water leaf = 15
|
||||
// desert ice = 14, acorn squid = 13
|
||||
// all other course numbers are to be considered a hazard
|
||||
|
||||
return "";
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +138,12 @@ namespace Ryujinx.Ava.Systems.PlayReport
|
||||
.WithDescription("based on gold count, report info only in the mii selector, and gamestage (progression)")
|
||||
.AddSparseMultiValueFormatter(["gold", "secret", "stage"], MiitopiaRPC)
|
||||
)
|
||||
.AddSpec(
|
||||
"0100ea80032ea000", // New Super Mario Bros U Deluxe
|
||||
spec => spec
|
||||
.WithDescription("based on world map return info.")
|
||||
.AddSparseMultiValueFormatter(["WorldNo", "CourseNo", "RlId", "TotalPlayTime", "GameModeType"], NsmbudRpc)
|
||||
)
|
||||
);
|
||||
|
||||
private static string Playing(string game) => $"Playing {game}";
|
||||
|
||||
@@ -76,6 +76,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
[ObservableProperty] public partial string LoadHeading { get; set; }
|
||||
|
||||
[ObservableProperty] public partial string CacheLoadStatus { get; set; }
|
||||
|
||||
[ObservableProperty] public partial string Splash { get; set; }
|
||||
|
||||
[ObservableProperty] public partial string DockedStatusText { get; set; }
|
||||
|
||||
@@ -1256,6 +1258,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
break;
|
||||
case ShaderCacheLoadingState shaderCacheState:
|
||||
CacheLoadStatus = $"{current} / {total}";
|
||||
Splash = $"\"{SplashTextHelper.GetSplash()}\"";
|
||||
switch (shaderCacheState)
|
||||
{
|
||||
case ShaderCacheLoadingState.Start:
|
||||
|
||||
@@ -135,7 +135,7 @@
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding ShowLoadProgress}" RowDefinitions="Auto,Auto,Auto">
|
||||
IsVisible="{Binding ShowLoadProgress}" RowDefinitions="Auto,Auto,Auto,Auto">
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Margin="10"
|
||||
@@ -179,6 +179,16 @@
|
||||
Text="{Binding CacheLoadStatus}"
|
||||
TextAlignment="Start"
|
||||
MaxWidth="500" />
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Margin="10"
|
||||
FontSize="14"
|
||||
FontStyle="Oblique"
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Text="{Binding Splash}"
|
||||
Foreground="LightGray"
|
||||
TextAlignment="Start"
|
||||
MaxWidth="500" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
Reference in New Issue
Block a user