diff --git a/assets/Locales/Root.json b/assets/Locales/Root.json index eee476519..8e3d25049 100644 --- a/assets/Locales/Root.json +++ b/assets/Locales/Root.json @@ -596,7 +596,7 @@ "th_TH": "", "tr_TR": "", "uk_UA": "", - "zh_CN": "", + "zh_CN": "重启模拟", "zh_TW": "重新啟動模擬" } }, @@ -6121,7 +6121,7 @@ "th_TH": "", "tr_TR": "", "uk_UA": "", - "zh_CN": "", + "zh_CN": "开启网络日志", "zh_TW": "" } }, @@ -11371,7 +11371,7 @@ "th_TH": "", "tr_TR": "", "uk_UA": "", - "zh_CN": "", + "zh_CN": "保存", "zh_TW": "儲存" } }, @@ -17121,7 +17121,7 @@ "th_TH": "", "tr_TR": "", "uk_UA": "", - "zh_CN": "", + "zh_CN": "在控制台中显示网络日志", "zh_TW": "" } }, @@ -21496,7 +21496,7 @@ "th_TH": "", "tr_TR": "", "uk_UA": "", - "zh_CN": "", + "zh_CN": "控制台将会在下次启动 Ryujinx 时可用。", "zh_TW": "" } }, diff --git a/docs/compatibility.csv b/docs/compatibility.csv index fa804c909..a0a7e730a 100644 --- a/docs/compatibility.csv +++ b/docs/compatibility.csv @@ -1061,6 +1061,7 @@ 0100BCA016636000,"eBaseball Powerful Pro Yakyuu 2022",gpu;services-horizon;crash,nothing,2024-05-26 23:07:19 01001F20100B8000,"Eclipse: Edge of Light",,playable,2020-08-11 23:06:29 0100E0A0110F4000,"eCrossminton",,playable,2020-07-11 18:24:27 +010054601D54C000,"Emio – The Smiling Man: Famicom Detective Club (DEMO)",demo,playable,2026-05-13 18:32:12 0100ABE00DB4E000,"Edna & Harvey: Harvey's New Eyes",nvdec,playable,2021-01-26 14:36:08 01004F000B716000,"Edna & Harvey: The Breakout – Anniversary Edition",crash;nvdec,ingame,2022-08-01 16:59:56 01002550129F0000,"Effie",,playable,2022-10-27 14:36:39 @@ -1204,7 +1205,7 @@ 01003B200E440000,"Five Nights at Freddy's: Sister Location",,playable,2023-10-06 09:00:58 010038200E088000,"Flan",crash;regression,ingame,2021-11-17 07:39:28 01000A0004C50000,"FLASHBACK™",nvdec,playable,2020-05-14 13:57:29 -0100C53004C52000,"Flat Heroes",gpu,ingame,2022-07-26 19:37:37 +0100C53004C52000,"Flat Heroes",,playable,2026-02-27 17:00:00 0100B54012798000,"Flatland: Prologue",,playable,2020-12-11 20:41:12 0100307004B4C000,"Flinthook",online,playable,2021-03-25 20:42:29 010095A004040000,"Flip Wars",services;ldn-untested,ingame,2022-05-02 15:39:18 @@ -1394,6 +1395,7 @@ 0100c3c012718000,"Grand Theft Auto: III – The Definitive Edition",gpu;UE4,ingame,2022-10-31 20:13:52 0100182014022000,"Grand Theft Auto: Vice City – The Definitive Edition",gpu;UE4,ingame,2022-10-31 20:13:52 010065a014024000,"Grand Theft Auto: San Andreas – The Definitive Edition",gpu;UE4,ingame,2022-10-31 20:13:52 +0100EB500D92E000,"GROOVE COASTER WAI WAI PARTY!!!!",gpu,ingame,2026-05-13 18:32:12 0100822012D76000,"HAAK",gpu,ingame,2023-02-19 14:31:05 01007E100EFA8000,"Habroxia",,playable,2020-06-16 23:04:42 0100535012974000,"Hades",vulkan,playable,2022-10-05 10:45:21 @@ -1832,6 +1834,7 @@ 010055200E87E000,"Metamorphosis",UE4;audout;gpu;nvdec,ingame,2021-06-16 16:18:11 0100D4900E82C000,"Metro 2033 Redux",gpu,ingame,2022-11-09 10:53:13 0100F0400E850000,"Metro: Last Light Redux",slow;nvdec;vulkan-backend-bug,ingame,2023-11-01 11:53:52 +010019A01E2F2000,"Metroid Prime 4: Beyond",,ingame,2026-05-13 18:32:12 010012101468C000,"Metroid Prime™ Remastered",gpu;Needs Update;vulkan-backend-bug;opengl-backend-bug,ingame,2024-05-07 22:48:15 010093801237C000,"Metroid™ Dread",,playable,2023-11-13 04:02:36 0100A1200F20C000,"Midnight Evil",,playable,2022-10-18 22:55:19 @@ -1945,6 +1948,7 @@ 0100C3E00ACAA000,"Mutant Football League: Dynasty Edition",online-broken,playable,2022-08-05 17:01:51 01004BE004A86000,"Mutant Mudds Collection",,playable,2022-08-05 17:11:38 0100E6B00DEA4000,"Mutant Year Zero: Road to Eden - Deluxe Edition",nvdec;UE4,playable,2022-09-10 13:31:10 +010037501F864000,"Mute Crimson DX",,ingame,2026-05-13 18:32:12 0100161009E5C000,"MX Nitro: Unleashed",,playable,2022-09-27 22:34:33 0100218011E7E000,"MX vs ATV All Out",nvdec;UE4;vulkan-backend-bug,playable,2022-10-25 19:51:46 0100D940063A0000,"MXGP3 - The Official Motocross Videogame",UE4;gpu;nvdec,ingame,2020-12-16 14:00:20 @@ -2268,6 +2272,7 @@ 010086F0064CE000,"Poi: Explorer Edition",nvdec,playable,2021-01-21 19:32:00 0100EB6012FD2000,"Poison Control",,playable,2021-05-16 14:01:54 010072400E04A000,"Pokémon Café ReMix",,playable,2021-08-17 20:00:04 +01005B7008C52800,"Pokémon Champions",Needs Update;services;online-broke,menus,2026-05-13 18:32:12 010008c01e742000,"Pokémon Friends",crash;services,menus,2025-07-24 13:32:00 01003D200BAA2000,"Pokémon Mystery Dungeon™: Rescue Team DX",mac-bug,playable,2024-01-21 00:16:32 01008DB008C2C000,"Pokémon Shield + Pokémon Shield Expansion Pass",deadlock;crash;online-broken;ldn-works;LAN,ingame,2024-08-12 07:20:22 @@ -2275,6 +2280,8 @@ 01009AD008C4C000,"Pokémon: Let's Go, Pikachu! demo",slow;demo,playable,2023-11-26 11:23:20 0100000011D90000,"Pokémon™ Brilliant Diamond",gpu;ldn-works,ingame,2024-08-28 13:26:35 010018E011D92000,"Pokémon™ Shining Pearl",gpu;ldn-works,ingame,2024-08-28 13:26:35 +100554023408000,"Pokémon FireRed Version",crashes,nothing,2026-05-13 18:32:12 +010034D02340E000,"Pokémon LeafGreen Version",crashes,nothing,2026-05-13 18:32:12 010015F008C54000,"Pokémon™ HOME",Needs Update;crash;services,menus,2020-12-06 06:01:51 01001F5010DFA000,"Pokémon™ Legends: Arceus",gpu;Needs Update;ldn-works,ingame,2024-09-19 10:02:02 0100F43008C44000,"Pokémon™ Legends: Z-A",gpu;crash;ldn-works,ingame,2025-11-16 00:30:00 @@ -2866,7 +2873,7 @@ 0100277011F1A000,"Super Mario Bros.™ 35",online-broken,menus,2022-08-07 16:27:25 010015100B514000,"Super Mario Bros.™ Wonder",amd-vendor-bug,playable,2024-09-06 13:21:21 01009B90006DC000,"Super Mario Maker™ 2",online-broken;ldn-broken,playable,2024-08-25 11:05:19 -0100000000010000,"Super Mario Odyssey™",nvdec;intel-vendor-bug;mac-bug,playable,2024-08-25 01:32:34 +0100000000010000,"Super Mario Odyssey™",nvdec;intel-vendor-bug;mac-bug;amd-vendor-bug,playable,2026-05-13 18:32:12 010036B0034E4000,"Super Mario Party™",gpu;Needs Update;ldn-works,ingame,2024-06-21 05:10:16 0100965017338000,"Super Mario Party Jamboree",mac-bug;gpu,ingame,2025-02-17 02:09:20 0100BC0018138000,"Super Mario RPG™",gpu;audio;nvdec,ingame,2024-06-19 17:43:42 @@ -3163,6 +3170,8 @@ 0100E2E00CB14000,"Tokyo School Life",,playable,2022-09-16 20:25:54 010024601BB16000,"Tomb Raider I-III Remastered Starring Lara Croft",gpu;opengl,ingame,2024-09-27 12:32:04 0100D7F01E49C000,"Tomba! Special Edition",services-horizon,nothing,2024-09-15 21:59:54 +010051F0207B2000,"Tomodachi Life: Living the Dream",amd-vendor-bug;gpu;intel-vendor-bug;ldn-broken,ingame,2026-05-13 18:32:12 +0100CA502552A000,"Tomodachi Life: Living the Dream – Welcome Edtion",amd-vendor-bug;demo,playable,2026-05-13 18:32:12 0100D400100F8000,"Tonight We Riot",,playable,2021-02-26 15:55:09 0100CC00102B4000,"Tony Hawk's™ Pro Skater™ 1 + 2",gpu;Needs Update,ingame,2024-09-24 08:18:14 010093F00E818000,"Tools Up!",crash,ingame,2020-07-21 12:58:17 diff --git a/src/Ryujinx.Common/TitleIDs.cs b/src/Ryujinx.Common/TitleIDs.cs index c232cfd01..890cf2793 100644 --- a/src/Ryujinx.Common/TitleIDs.cs +++ b/src/Ryujinx.Common/TitleIDs.cs @@ -59,6 +59,7 @@ namespace Ryujinx.Common //Mario Franchise "010021d00812a000", // Arcade Archives VS. SUPER MARIO BROS. + "01007fe0221d8000", // Hello, Mario! "01006d0017f7a000", // Mario & Luigi: Brothership "010003000e146000", // Mario & Sonic at the Olympic Games Tokyo 2020 "010067300059a000", // Mario + Rabbids: Kingdom Battle @@ -70,6 +71,9 @@ namespace Ryujinx.Common "0100bde00862a000", // Mario Tennis Aces "0100b99019412000", // Mario vs. Donkey Kong "010049900f546000", // Super Mario 3D All-Stars + "010049900f546001", // Super Mario 3D All-Stars | Super Mario 64 + "010049900f546002", // Super Mario 3D All-Stars | Super Mario Sunshine + "010049900f546003", // Super Mario 3D All-Stars | Super Mario Galaxy "010028600ebda000", // Super Mario 3D World + Bowser's Fury "010049900F546001", // Super Mario 64 "0100ea80032ea000", // Super Mario Bros. U Deluxe @@ -107,6 +111,11 @@ namespace Ryujinx.Common "0100187003a36000", // Pokémon: Let's Go Eevee! "010003f003a34000", // Pokémon: Let's Go Pikachu! "0100f43008c44000", // Pokémon Legends: Z-A + "0100554023408000", // Pokémon FireRed Version (EN) + "01006fa0233f8000", // Pokémon FireRed Version (JP) + "0100fd6023430000", // Pokémon LeafGreen Version (DE) + "0100f1e0233fa000", // Pokémon LeafGreen Version (JP) + "01005b7008c52000", // Pokémon Champions //Splatoon Franchise "0100f8f0000a2000", // Splatoon 2 (EU) @@ -116,13 +125,14 @@ namespace Ryujinx.Common "0100ba0018500000", // Splatoon 3: Splatfest World Premiere //NSO Membership games + "0100d870045b6000", // NES - Nintendo Switch Online + "01008d300c50c000", // SNES - Nintendo Switch Online "0100c62011050000", // GB - Nintendo Switch Online "010012f017576000", // GBA - Nintendo Switch Online "0100c9a00ece6000", // N64 - Nintendo Switch Online "0100e0601c632000", // N64 - Nintendo Switch Online 18+ - "0100d870045b6000", // NES - Nintendo Switch Online "0100b3c014bda000", // SEGA Genesis - Nintendo Switch Online - "01008d300c50c000", // SNES - Nintendo Switch Online + "0100bfc01d976000", // Virtual Boy - Nintendo Switch Online "0100ccf019c8c000", // F-ZERO 99 "0100ad9012510000", // PAC-MAN 99 "010040600c5ce000", // Tetris 99 @@ -141,12 +151,17 @@ namespace Ryujinx.Common "0100704000B3A000", // Snipperclips "01006a800016e000", // Super Smash Bros. Ultimate "0100a9400c9c2000", // Tokyo Mirage Sessions #FE Encore + "0100ca502552a000", // Tomodachi Life: Living the Dream - Welcome Edition + "010051f0207b2000", // Tomodachi Life: Living the Dream //Bayonetta Franchise "010076f0049a2000", // Bayonetta "01007960049a0000", // Bayonetta 2 "01004a4010fea000", // Bayonetta 3 "0100cf5010fec000", // Bayonetta Origins: Cereza and the Lost Demon + + // Famicom Detective Club Franchise + "010054601d54c000", // Emio - The Smiling Man: Famicom Detective Series (DEMO) //Persona Franchise "0100dcd01525a000", // Persona 3 Portable @@ -171,7 +186,9 @@ namespace Ryujinx.Common "0100453019aa8000", // Xenoblade Chronicles: X Definitive Edition //Misc Games + "01003670066de000", // 36 Fragments of Midnight "010056e00853a000", // A Hat in Time + "0100c9f00aaee000", // Ascendence "0100fd1014726000", // Baldurs Gate: Dark Alliance "01008c2019598000", // Bluey: The Video Game "010096f00ff22000", // Borderlands 2: Game of the Year Edition @@ -185,8 +202,10 @@ namespace Ryujinx.Common "010027400cdc6000", // Divinity Original 2 - Definitive Edition "01008c8012920000", // Dying Light Platinum Edition "0100d11013e6a000", // Eschatos + "01000490067ae000", // Frederic 2: Evil Strikes Back "01001cc01b2d4000", // Goat Simulator 3 "01003620068ea000", // Hand of Fate 2 + "01007ac00e012000", // HEXAGRAVITY "0100f7e00c70e000", // Hogwarts Legacy "010013c00e930000", // Hollow Knight: Silksong "010085500130a000", // Lego City: Undercover @@ -196,6 +215,7 @@ namespace Ryujinx.Common "0100853015e86000", // No Man's Sky "0100f85014ed0000", // No More Heroes "0100463014ed4000", // No More Heroes 2 + "0100f7d00a1bc000", // NO THING "0100e570094e8000", // Owlboy "01007bb017812000", // Portal "0100abd01785c000", // Portal 2 @@ -204,11 +224,14 @@ namespace Ryujinx.Common "01008e200c5c2000", // Muse Dash "01005ff002e2a000", // Rayman Legends "01007820196a6000", // Red Dead Redemption + "01007a800d520000", // REFUNCT "0100e8300a67a000", // Risk "01002f7013224000", // Rune Factory 5 "01008d100d43e000", // Saints Row IV "0100de600beee000", // Saints Row: The Third - The Full Package "01001180021fa000", // Shovel Knight: Specter of Torment + "010079f00671c000", // Sparkle 2: Evo + "010077b00e046000", // Spyro: Reignited Trilogy "0100e1D01eb2e000", // Squeakross: Home Squeak Home "0100e65002bb8000", // Stardew Valley "0100d7a01b7a2000", // Star Wars: Bounty Hunter diff --git a/src/Ryujinx.Graphics.GAL/Capabilities.cs b/src/Ryujinx.Graphics.GAL/Capabilities.cs index 1eec80e51..4271c3d18 100644 --- a/src/Ryujinx.Graphics.GAL/Capabilities.cs +++ b/src/Ryujinx.Graphics.GAL/Capabilities.cs @@ -42,6 +42,7 @@ namespace Ryujinx.Graphics.GAL public readonly bool SupportsShaderBallot; public readonly bool SupportsShaderBarrierDivergence; public readonly bool SupportsShaderFloat64; + public readonly bool SupportsShaderNonUniformIndexing; public readonly bool SupportsTextureGatherOffsets; public readonly bool SupportsTextureShadowLod; public readonly bool SupportsVertexStoreAndAtomics; @@ -110,6 +111,7 @@ namespace Ryujinx.Graphics.GAL bool supportsShaderBallot, bool supportsShaderBarrierDivergence, bool supportsShaderFloat64, + bool supportsShaderNonUniformIndexing, bool supportsTextureGatherOffsets, bool supportsTextureShadowLod, bool supportsVertexStoreAndAtomics, @@ -172,6 +174,7 @@ namespace Ryujinx.Graphics.GAL SupportsShaderBallot = supportsShaderBallot; SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence; SupportsShaderFloat64 = supportsShaderFloat64; + SupportsShaderNonUniformIndexing = supportsShaderNonUniformIndexing; SupportsTextureGatherOffsets = supportsTextureGatherOffsets; SupportsTextureShadowLod = supportsTextureShadowLod; SupportsVertexStoreAndAtomics = supportsVertexStoreAndAtomics; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs index 6444b18e3..7b58e18c3 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs @@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMinor = 2; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; - private const uint CodeGenVersion = 7353; + private const uint CodeGenVersion = 7354; private const string SharedTocFileName = "shared.toc"; private const string SharedDataFileName = "shared.data"; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs index d89eebabf..2f8c329e5 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs @@ -231,6 +231,8 @@ namespace Ryujinx.Graphics.Gpu.Shader public bool QueryHostSupportsShaderFloat64() => _context.Capabilities.SupportsShaderFloat64; + public bool QueryHostSupportsShaderNonUniformIndexing() => _context.Capabilities.SupportsShaderNonUniformIndexing; + public bool QueryHostSupportsSnormBufferTextureFormat() => _context.Capabilities.SupportsSnormBufferTextureFormat; public bool QueryHostSupportsTextureGatherOffsets() => _context.Capabilities.SupportsTextureGatherOffsets; diff --git a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs index af1494bbe..acc0dbd68 100644 --- a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs +++ b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs @@ -184,6 +184,7 @@ namespace Ryujinx.Graphics.OpenGL supportsShaderBallot: HwCapabilities.SupportsShaderBallot, supportsShaderBarrierDivergence: !(intelWindows || intelUnix), supportsShaderFloat64: true, + supportsShaderNonUniformIndexing: false, supportsTextureGatherOffsets: true, supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod, supportsVertexStoreAndAtomics: true, diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs index 83b037c1c..9de806d89 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs @@ -587,6 +587,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv return OperationResult.Invalid; } + private static void MarkNonUniform(CodeGenContext context, SpvInstruction inst) + { + if (context.HostCapabilities.SupportsShaderNonUniformIndexing) + { + context.Decorate(inst, Decoration.NonUniform); + } + } + private static OperationResult GenerateImageAtomic(CodeGenContext context, AstOperation operation) { AstTextureOperation texOp = (AstTextureOperation)operation; @@ -613,6 +621,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv SpvInstruction textureIndex = Src(AggregateType.S32); image = context.AccessChain(imagePointerType, image, textureIndex); + MarkNonUniform(context, image); } int coordsCount = texOp.Type.Dimensions; @@ -683,15 +692,21 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv ImageDeclaration declaration = context.Images[texOp.GetTextureSetAndBinding()]; SpvInstruction image = declaration.Image; + bool isIndexed = declaration.IsIndexed; - if (declaration.IsIndexed) + if (isIndexed) { SpvInstruction textureIndex = Src(AggregateType.S32); image = context.AccessChain(declaration.ImagePointerType, image, textureIndex); + MarkNonUniform(context, image); } image = context.Load(declaration.ImageType, image); + if (isIndexed) + { + MarkNonUniform(context, image); + } int coordsCount = texOp.Type.Dimensions; @@ -740,15 +755,21 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv ImageDeclaration declaration = context.Images[texOp.GetTextureSetAndBinding()]; SpvInstruction image = declaration.Image; + bool isIndexed = declaration.IsIndexed; - if (declaration.IsIndexed) + if (isIndexed) { SpvInstruction textureIndex = Src(AggregateType.S32); image = context.AccessChain(declaration.ImagePointerType, image, textureIndex); + MarkNonUniform(context, image); } image = context.Load(declaration.ImageType, image); + if (isIndexed) + { + MarkNonUniform(context, image); + } int coordsCount = texOp.Type.Dimensions; @@ -1878,35 +1899,56 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv private static SpvInstruction GenerateSampledImageLoad(CodeGenContext context, AstTextureOperation texOp, SamplerDeclaration declaration, ref int srcIndex) { SpvInstruction image = declaration.Image; + bool imageIndexed = declaration.IsIndexed; - if (declaration.IsIndexed) + if (imageIndexed) { SpvInstruction textureIndex = context.Get(AggregateType.S32, texOp.GetSource(srcIndex++)); image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex); + MarkNonUniform(context, image); } if (texOp.IsSeparate) { image = context.Load(declaration.ImageType, image); + if (imageIndexed) + { + MarkNonUniform(context, image); + } SamplerDeclaration samplerDeclaration = context.Samplers[texOp.GetSamplerSetAndBinding()]; SpvInstruction sampler = samplerDeclaration.Image; + bool samplerIndexed = samplerDeclaration.IsIndexed; - if (samplerDeclaration.IsIndexed) + if (samplerIndexed) { SpvInstruction samplerIndex = context.Get(AggregateType.S32, texOp.GetSource(srcIndex++)); sampler = context.AccessChain(samplerDeclaration.SampledImagePointerType, sampler, samplerIndex); + MarkNonUniform(context, sampler); } sampler = context.Load(samplerDeclaration.ImageType, sampler); + if (samplerIndexed) + { + MarkNonUniform(context, sampler); + } + image = context.SampledImage(declaration.SampledImageType, image, sampler); + if (imageIndexed || samplerIndexed) + { + MarkNonUniform(context, image); + } } else { image = context.Load(declaration.SampledImageType, image); + if (imageIndexed) + { + MarkNonUniform(context, image); + } } return image; diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs index e1561446b..2dd7186ba 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs @@ -60,6 +60,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv context.AddCapability(Capability.Float64); } + if (parameters.HostCapabilities.SupportsShaderNonUniformIndexing) + { + context.AddExtension("SPV_EXT_descriptor_indexing"); + context.AddCapability(Capability.ShaderNonUniform); + context.AddCapability(Capability.SampledImageArrayNonUniformIndexing); + context.AddCapability(Capability.StorageImageArrayNonUniformIndexing); + } + if (parameters.Definitions.TransformFeedbackEnabled && parameters.Definitions.LastInVertexPipeline) { context.AddCapability(Capability.TransformFeedback); diff --git a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs index 4e6d6edf9..77953df05 100644 --- a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs +++ b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs @@ -336,6 +336,10 @@ namespace Ryujinx.Graphics.Shader { return true; } + bool QueryHostSupportsShaderNonUniformIndexing() + { + return false; + } /// /// Queries host GPU support for signed normalized buffer texture formats. diff --git a/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs b/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs index 11fe6599d..bfd85c158 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs @@ -9,6 +9,7 @@ namespace Ryujinx.Graphics.Shader.Translation public readonly bool SupportsShaderBallot; public readonly bool SupportsShaderBarrierDivergence; public readonly bool SupportsShaderFloat64; + public readonly bool SupportsShaderNonUniformIndexing; public readonly bool SupportsTextureShadowLod; public readonly bool SupportsViewportMask; @@ -20,6 +21,7 @@ namespace Ryujinx.Graphics.Shader.Translation bool supportsShaderBallot, bool supportsShaderBarrierDivergence, bool supportsShaderFloat64, + bool supportsShaderNonUniformIndexing, bool supportsTextureShadowLod, bool supportsViewportMask) { @@ -30,6 +32,7 @@ namespace Ryujinx.Graphics.Shader.Translation SupportsShaderBallot = supportsShaderBallot; SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence; SupportsShaderFloat64 = supportsShaderFloat64; + SupportsShaderNonUniformIndexing = supportsShaderNonUniformIndexing; SupportsTextureShadowLod = supportsTextureShadowLod; SupportsViewportMask = supportsViewportMask; } diff --git a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs index e1ca22610..c7c562947 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs @@ -364,6 +364,7 @@ namespace Ryujinx.Graphics.Shader.Translation GpuAccessor.QueryHostSupportsShaderBallot(), GpuAccessor.QueryHostSupportsShaderBarrierDivergence(), GpuAccessor.QueryHostSupportsShaderFloat64(), + GpuAccessor.QueryHostSupportsShaderNonUniformIndexing(), GpuAccessor.QueryHostSupportsTextureShadowLod(), GpuAccessor.QueryHostSupportsViewportMask()); diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs index 02c4e6873..9cd8f90d7 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs @@ -494,6 +494,8 @@ namespace Ryujinx.Graphics.Vulkan UniformBufferStandardLayout = supportedPhysicalDeviceVulkan12Features.UniformBufferStandardLayout, UniformAndStorageBuffer8BitAccess = supportedPhysicalDeviceVulkan12Features.UniformAndStorageBuffer8BitAccess, StorageBuffer8BitAccess = supportedPhysicalDeviceVulkan12Features.StorageBuffer8BitAccess, + ShaderSampledImageArrayNonUniformIndexing = supportedPhysicalDeviceVulkan12Features.ShaderSampledImageArrayNonUniformIndexing, + ShaderStorageImageArrayNonUniformIndexing = supportedPhysicalDeviceVulkan12Features.ShaderStorageImageArrayNonUniformIndexing, }; pExtendedFeatures = &featuresVk12; diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index 5f1c50b00..ccb541a34 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -775,6 +775,9 @@ namespace Ryujinx.Graphics.Vulkan supportsShaderBallot: false, supportsShaderBarrierDivergence: Vendor != Vendor.Intel, supportsShaderFloat64: Capabilities.SupportsShaderFloat64, + supportsShaderNonUniformIndexing: + featuresVk12.ShaderSampledImageArrayNonUniformIndexing && + featuresVk12.ShaderStorageImageArrayNonUniformIndexing, supportsTextureGatherOffsets: features2.Features.ShaderImageGatherExtended && !IsMoltenVk, supportsTextureShadowLod: false, supportsVertexStoreAndAtomics: features2.Features.VertexPipelineStoresAndAtomics, diff --git a/src/Ryujinx.Input.SDL3/NpadHdRumble.cs b/src/Ryujinx.Input.SDL3/NpadHdRumble.cs new file mode 100644 index 000000000..e367f6a9c --- /dev/null +++ b/src/Ryujinx.Input.SDL3/NpadHdRumble.cs @@ -0,0 +1,151 @@ +using Ryujinx.HLE.HOS.Services.Hid; +using SDL; +using static SDL.SDL3; +using System; + +namespace Ryujinx.Input.SDL3 +{ + /// + /// Manages a HID handle of a gamepad to encode and write HD rumble commands for Nin controllers. + /// + public unsafe class NpadHdRumble : IDisposable + { + private readonly SDL_hid_device* _hidHandle; + + private int _globalCount; + + private NpadHdRumble(SDL_hid_device* hidHandle) + { + _hidHandle = hidHandle; + } + + public static NpadHdRumble Create(SDL_Gamepad* gamepadHandle) + { + ushort vendor = SDL_GetGamepadVendor(gamepadHandle); + if (vendor != 0x057e) + { + return null; + } + + ushort product = SDL_GetGamepadProduct(gamepadHandle); + if (product != 0x2006 && product != 0x2007 && product != 0x2009 && product != 0x200e) + { + return null; + } + + return new NpadHdRumble(SDL_hid_open(vendor, product, 0)); + } + + // Some of the code was translated from https://github.com/MIZUSHIKI/JoyShockLibrary-plus-HDRumble + private void WriteHdRumble( + int encLeftLowFreq, int encLeftLowAmp, + int encLeftHighFreq, int encLeftHighAmp, + int encRightLowFreq, int encRightLowAmp, + int encRightHighFreq, int encRightHighAmp) + { + 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); + + if (_globalCount > 0xF) + { + _globalCount = 0x0; + } + + fixed (byte* ptr = buf) + { + SDL_hid_write(_hidHandle, ptr, (nuint)buf.Length); + } + } + + + 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; + } + + 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; + } + + private static int EncodeLowAmp(float rawAmp) + { + int encodedAmp = 0; + + if (rawAmp is > 0 and < 0.012f) + { + encodedAmp = 1; + } + else if (rawAmp is >= 0.012f and < 0.112f) + { + encodedAmp = (int)Math.Round(4 * Math.Log2(rawAmp * 110f)); + } + else if (rawAmp is >= 0.112f and < 0.225f) + { + encodedAmp = (int)Math.Round(16 * Math.Log2(rawAmp * 17f)); + } + else if (rawAmp is >= 0.225f and <= 1f) + { + encodedAmp = (int)Math.Round(32 * Math.Log2(rawAmp * 8.7f)); + } + + return (int)Math.Floor(encodedAmp / 2.0) + 64; + } + + private static int EncodeHighAmp(float rawAmp) + { + int encodedAmp = 0; + + if (rawAmp is > 0 and < 0.012f) + { + encodedAmp = 1; + } + else if (rawAmp is >= 0.012f and < 0.112f) + { + encodedAmp = (int)Math.Round(4 * Math.Log2(rawAmp * 110f)); + } + else if (rawAmp is >= 0.112f and < 0.225f) + { + encodedAmp = (int)Math.Round(16 * Math.Log2(rawAmp * 17f)); + } + else if (rawAmp is >= 0.225f and <= 1f) + { + encodedAmp = (int)Math.Round(32 * Math.Log2(rawAmp * 8.7f)); + } + + return encodedAmp * 2; + } + + public bool HdRumble(VibrationValue left, VibrationValue right) + { + 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)); + return true; + } + + public void Dispose() + { + SDL_hid_close(_hidHandle); + } + } +} diff --git a/src/Ryujinx.Input.SDL3/SDL3Gamepad.cs b/src/Ryujinx.Input.SDL3/SDL3Gamepad.cs index 4985d8eea..c23b64304 100644 --- a/src/Ryujinx.Input.SDL3/SDL3Gamepad.cs +++ b/src/Ryujinx.Input.SDL3/SDL3Gamepad.cs @@ -2,6 +2,7 @@ using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Logging; using Ryujinx.Common.Utilities; +using Ryujinx.HLE.HOS.Services.Hid; using System; using System.Collections.Generic; using System.Numerics; @@ -76,11 +77,14 @@ namespace Ryujinx.Input.SDL3 private SDL_Gamepad* _gamepadHandle; + private NpadHdRumble _hdRumble; + private float _triggerThreshold; public SDL3Gamepad(SDL_Gamepad* gamepadHandle, string driverId) { _gamepadHandle = gamepadHandle; + _hdRumble = NpadHdRumble.Create(gamepadHandle); _buttonsUserMapping = new List(20); Name = SDL_GetGamepadName(_gamepadHandle); @@ -165,6 +169,10 @@ namespace Ryujinx.Input.SDL3 protected virtual void Dispose(bool disposing) { + if (disposing && _hdRumble != null) + { + _hdRumble.Dispose(); + } if (disposing && _gamepadHandle != null) { SDL_CloseGamepad(_gamepadHandle); @@ -184,6 +192,11 @@ namespace Ryujinx.Input.SDL3 _triggerThreshold = triggerThreshold; } + public bool HDRumble(VibrationValue left, VibrationValue right) + { + return _hdRumble?.HdRumble(left, right) ?? false; + } + public void Rumble(float lowFrequency, float highFrequency, uint durationMs) { if ((Features & GamepadFeaturesFlag.Rumble) == 0) diff --git a/src/Ryujinx.Input.SDL3/SDL3JoyCon.cs b/src/Ryujinx.Input.SDL3/SDL3JoyCon.cs index 5311a256c..5d779518d 100644 --- a/src/Ryujinx.Input.SDL3/SDL3JoyCon.cs +++ b/src/Ryujinx.Input.SDL3/SDL3JoyCon.cs @@ -1,6 +1,7 @@ using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Hid; using System; using System.Collections.Generic; using System.Numerics; @@ -61,6 +62,8 @@ namespace Ryujinx.Input.SDL3 public GamepadFeaturesFlag Features { get; } private SDL_Gamepad* _gamepadHandle; + + private NpadHdRumble _hdRumble; private enum JoyConType { @@ -76,6 +79,7 @@ namespace Ryujinx.Input.SDL3 public SDL3JoyCon(SDL_Gamepad* gamepadHandle, string driverId) { _gamepadHandle = gamepadHandle; + _hdRumble = NpadHdRumble.Create(gamepadHandle); _buttonsUserMapping = new List(10); Name = SDL_GetGamepadName(_gamepadHandle); @@ -139,6 +143,10 @@ namespace Ryujinx.Input.SDL3 protected virtual void Dispose(bool disposing) { + if (disposing && _hdRumble != null) + { + _hdRumble.Dispose(); + } if (disposing && _gamepadHandle != null) { SDL_CloseGamepad(_gamepadHandle); @@ -156,6 +164,11 @@ namespace Ryujinx.Input.SDL3 { } + + public bool HDRumble(VibrationValue left, VibrationValue right) + { + return _hdRumble?.HdRumble(left, right) ?? false; + } public void Rumble(float lowFrequency, float highFrequency, uint durationMs) { diff --git a/src/Ryujinx.Input/HLE/NpadController.cs b/src/Ryujinx.Input/HLE/NpadController.cs index 29bc973f6..a46ff8daf 100644 --- a/src/Ryujinx.Input/HLE/NpadController.cs +++ b/src/Ryujinx.Input/HLE/NpadController.cs @@ -559,18 +559,29 @@ namespace Ryujinx.Input.HLE { VibrationValue leftVibrationValue = dualVibrationValue.Item1; VibrationValue rightVibrationValue = dualVibrationValue.Item2; - + float low = Math.Min(1f, (float)((rightVibrationValue.AmplitudeLow * 0.85 + rightVibrationValue.AmplitudeHigh * 0.15) * controllerConfig.Rumble.StrongRumble)); float high = Math.Min(1f, (float)((leftVibrationValue.AmplitudeLow * 0.15 + leftVibrationValue.AmplitudeHigh * 0.85) * controllerConfig.Rumble.WeakRumble)); + + leftVibrationValue.AmplitudeLow *= controllerConfig.Rumble.WeakRumble; + leftVibrationValue.AmplitudeHigh *= controllerConfig.Rumble.StrongRumble; + rightVibrationValue.AmplitudeLow *= controllerConfig.Rumble.WeakRumble; + rightVibrationValue.AmplitudeHigh *= controllerConfig.Rumble.StrongRumble; - _gamepad?.Rumble(low, high, uint.MaxValue); + if (_gamepad?.HDRumble(leftVibrationValue, rightVibrationValue) == false) + { + _gamepad?.Rumble(low, high, uint.MaxValue); + } Logger.Debug?.Print(LogClass.Hid, $"Effect for {controllerConfig.PlayerIndex} " + $"L.low.amp={leftVibrationValue.AmplitudeLow}, " + $"L.high.amp={leftVibrationValue.AmplitudeHigh}, " + + $"L.low.freq={leftVibrationValue.FrequencyLow}, " + + $"L.high.freq={leftVibrationValue.FrequencyHigh}, " + $"R.low.amp={rightVibrationValue.AmplitudeLow}, " + $"R.high.amp={rightVibrationValue.AmplitudeHigh} " + - $"--> ({low}, {high})"); + $"R.low.freq={rightVibrationValue.FrequencyLow}, " + + $"R.high.freq={rightVibrationValue.FrequencyHigh}"); } } } diff --git a/src/Ryujinx.Input/IGamepad.cs b/src/Ryujinx.Input/IGamepad.cs index 945ccfa8b..d45ac0444 100644 --- a/src/Ryujinx.Input/IGamepad.cs +++ b/src/Ryujinx.Input/IGamepad.cs @@ -1,5 +1,6 @@ using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Services.Hid; using System; using System.Numerics; using System.Runtime.CompilerServices; @@ -74,6 +75,16 @@ namespace Ryujinx.Input public void ClearLed() => SetLed(0); + /// + /// Starts an HD vibration effect on the gamepad if available. + /// + /// The vibration data for the left side + /// The vibration data for the right side + bool HDRumble(VibrationValue left, VibrationValue right) + { + return false; + } + /// /// Starts a rumble effect on the gamepad. /// diff --git a/src/Ryujinx/Systems/PlayReport/PlayReports.Formatters.cs b/src/Ryujinx/Systems/PlayReport/PlayReports.Formatters.cs index 5aeb923da..649c3cad6 100644 --- a/src/Ryujinx/Systems/PlayReport/PlayReports.Formatters.cs +++ b/src/Ryujinx/Systems/PlayReport/PlayReports.Formatters.cs @@ -1,5 +1,6 @@ using Gommon; using Humanizer; +using MsgPack; using System; using System.Buffers.Binary; using System.Collections.Generic; @@ -23,24 +24,382 @@ namespace Ryujinx.Ava.Systems.PlayReport private static FormattedValue SkywardSwordHD_Rupees(SingleValue value) => "rupee".ToQuantity(value.Matched.IntValue); - private static FormattedValue SuperMarioOdyssey_AssistMode(SingleValue value) - => value.Matched.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode"; + private static FormattedValue EchoesOfWisdom_Warp(SingleValue value) + { + FormattedValue locations = value.Matched.IntValue switch + { + // Hyrule Field + 23 => "Hyrule Field: Kakariko Village", + 43 => "Hyrule Field: West of Hyrule Ranch", + 45 => "Hyrule Field: North of Hyrule Ranch", + 25 => "Hyrule Field: Hyrule Ranch", + 26 => "Hyrule Field: West of Hyrule Castle", + 48 => "Hyrule Field: Haunted Grove", + 24 => "Hyrule Field: Hyrule Castle", + 27 => "Hyrule Field: Northern Sanctuary", + 28 => "Eastern Hyrule Field: Eastern Temple", + 41 => "Eastern Hyrule Field: Dampé Studio", + 22 => "Lake Hylia: Great Fairy Shrine", + // Eternal Forest + 47 => "Eternal Forest: Entrance", + 46 => "Eternal Forest: Great Deku Tree", + 752 => "Eternal Forest: Stilled Ancient Ruins (Halfway Point)", + 753 => "Eternal Forest: Stilled Ancient Ruins (Null)", + // Suthorn + 33 => "Suthorn Prairie: Lueburry's House", + 20 => "Suthorn Prairie: Suthorn Village", + 21 => "Suthorn Forest: Suthorn Ruins", + // Faron Wetlands + 13 => "Faron Wetlands: Entrance", + 15 => "Faron Wetlands: Scrubton", + 18 => "Faron Wetlands: Blossu's House", + 17 => "Faron Wetlands: Heart Lake", + 852 => "Faron Wetlands: Stilled Faron Wetlands", + 601 => "Faron Wetlands: Faron Temple 3F", + 602 => "Faron Wetlands: Faron Temple 2F (Underwater Entrance)", + 603 => "Faron Wetlands: Faron Temple 2F (West Entrance)", + 604 => "Faron Wetlands: Faron Temple 2F (Cliff Entrance)", + 605 => "Faron Wetlands: Faron Temple 1F (Diababa)", + 606 => "Faron Wetlands: Faron Temple 1F (Gohma)", + // Jabul Waters + 11 => "Jabul Waters: River Zora Village", + 9 => "Jabul Waters: Crossflows Plaza", + 8 => "Jabul Waters: Seesyde Village", + 12 => "Jabul Waters: Sea Zora Village", + 10 => "Jabul Waters: Lord Jabu-Jabu's Den", + 201 => "Jabul Waters: Jabul Ruins 1F (Entrance)", + 202 => "Jabul Waters: Jabul Ruins 1F (Vocavor)", + // Gerudo Desert + 40 => "Gerudo Desert: Entrance", + 29 => "Gerudo Desert: Oasis", + 32 => "Gerudo Desert: Ancestor's Cave Of Rest", + 30 => "Gerudo Desert: Gerudo Town", + 31 => "Gerudo Desert: Gerudo Sanctum", + 351 => "Gerudo Desert: Stilled Gerudo Sanctum", + 303 => "Gerudo Desert: Gerudo Sanctum 1F (West Entrance)", + 304 => "Gerudo Desert: Gerudo Sanctum 1F (East Entrance)", + 301 => "Gerudo Desert: Gerudo Sanctum 2F (The Key)", + 302 => "Gerudo Desert: Gerudo Sanctum 2F (The Elephant Room)", + 305 => "Gerudo Desert: Gerudo Sanctum 2F (Mogryph)", + // Eldin Volcano + 4 => "Eldin Volcano: Eldin Volcano Trail", + 44 => "Eldin Volcano: Lava Lake", + 3 => "Eldin Volcano: Goron City", + 5 => "Eldin Volcano: Rock Roast Volcano", + 49 => "Eldin Volcano: Crater Shortcut", + 552 => "Eldin Volcano: Stilled Eldin Volcano", + 501 => "Eldin Volcano: Eldin Temple 1F", + 503 => "Eldin Volcano: Eldin Temple 2F", + 502 => "Eldin Volcano: Eldin Temple 3F", + // Hebra Mountain + 34 => "Hebra Mountain: Hebra Mountain Passage (1)", + 35 => "Hebra Mountain: Sheltered Hot Spring", + 36 => "Hebra Mountain: Condé's House", + 38 => "Hebra Mountain: Hebra Mountain Passage (2)", + 37 => "Hebra Mountain: Hebra Mountain Passage (3)", + 39 => "Hebra Mountain: Summit", + 652 => "Hebra Mountain: Stilled Holy Mount Lanayru", + 801 => "Hebra Mountain: Lanayru Temple 1F", + 802 => "Hebra Mountain: Lanayru Temple B2", + 803 => "Hebra Mountain: Lanayru Temple B4", + _ => FormattedValue.ForceReset + }; - private static FormattedValue SuperMarioOdysseyChina_AssistMode(SingleValue value) - => value.Matched.BoxedValue is 1 ? "Playing in 帮助模式" : "Playing in 普通模式"; + return locations.Reset + ? FormattedValue.ForceReset + : $"Warped to {locations}"; + } + + private static FormattedValue SuperMario3DAllStars(SingleValue value) + { + // TODO: Is this really necessary? + FormattedValue title = value.Matched.IntValue switch + { + 1 => "Super Mario 64", + 2 => "Super Mario Sunshine", + 3 => "Super Mario Galaxy", + _ => FormattedValue.ForceReset + }; + + return title.Reset + ? FormattedValue.ForceReset + : $"Playing {title}"; + } + + private static FormattedValue SuperMario3DAllStars_MainMenu(MultiValue value) + { + int albumId = value.Matched[0].IntValue; + int songId = value.Matched[1].IntValue; + + string album = value.Matched[0].IntValue switch + { + 1 => "Super Mario 64 OST", + 2 => "Super Mario Sunshine OST", + 3 => "Super Mario Galaxy OST", + _ => "Listening to Super Mario 3D All-Stars" + }; + + string song = (albumId, songId) switch + { + // Super Mario 64 + (1, 0) => "It's a Me, Mario!", + (1, 1) => "Title Theme", + (1, 2) => "Peach's Message", + (1, 3) => "Opening", + (1, 4) => "Super Mario 64 Main Theme", + (1, 5) => "Slider", + (1, 6) => "Inside the Castle Walls", + (1, 7) => "Looping Steps", + (1, 8) => "Dire, Dire Docks", + (1, 9) => "Lethal Lava Land", + (1, 10) => "Snow Mountain", + (1, 11) => "Haunted House", + (1, 12) => "Merry-Go-Round", + (1, 13) => "Cave Dungeon", + (1, 14) => "Piranha Plant's Lullaby", + (1, 15) => "Powerful Mario", + (1, 16) => "Metallic Mario", + (1, 17) => "File Select", + (1, 18) => "Correct Solution", + (1, 19) => "Toad's Message", + (1, 20) => "Power Star", + (1, 21) => "Race Fanfare", + (1, 22) => "Star Catch Fanfare", + (1, 23) => "Game Start", + (1, 24) => "Course Clear", + (1, 25) => "Game Over", + (1, 26) => "Stage Boss", + (1, 27) => "Koopa's Message", + (1, 28) => "Koopa's Road", + (1, 29) => "Koopa's Theme", + (1, 30) => "Koopa Clear", + (1, 31) => "Ultimate Koopa", + (1, 32) => "Ultimate Koopa Clear", + (1, 33) => "Ending Demo", + (1, 34) => "Staff Roll", + (1, 35) => "Piranha Plant's Lullaby - Piano", + + // Super Mario Sunshine + (2, 0) => "Isle Delfino", + (2, 1) => "Delfino Airstrip", + (2, 2) => "Bianco Hills", + (2, 3) => "Ricco Harbor", + (2, 4) => "Gelato Beach", + (2, 5) => "Pinna Beach", + (2, 6) => "Pinna Park", + (2, 7) => "Sirena Beach", + (2, 8) => "Hotel Delfino", + (2, 9) => "Casino", + (2, 10) => "Noki Bay", + (2, 11) => "Noki Depths", + (2, 12) => "Pianta Village", + (2, 13) => "Pianta Hot Spring", + (2, 14) => "Pianta Rescue", + (2, 15) => "Pianta Village - Fluff Festival", + (2, 16) => "Underground", + (2, 17) => "Secret Course", + (2, 18) => "Secret Course - Sky and Sea", + (2, 19) => "Corona Mountain", + (2, 20) => "Mid-Boss", + (2, 21) => "Proto Piranha", + (2, 22) => "Phantamanta", + (2, 23) => "Boss Battle", + (2, 24) => "Gooper Blooper Intro", + (2, 25) => "Wiggler Intro", + (2, 26) => "Mecha-Bowser", + (2, 27) => "Bowser", + (2, 28) => "Shadow Mario", + (2, 29) => "Racing Il Piantissimo", + (2, 30) => "Event", + (2, 31) => "Timed Event", + (2, 32) => "Yoshi-Go-Round", + (2, 33) => "Title Screen", + (2, 34) => "Opening Demo", + (2, 35) => "Select Data", + (2, 36) => "Select Scenario", + (2, 37) => "Course Intro", + (2, 38) => "Course Intro - Shadow Mario", + (2, 39) => "A Shine Sprite Appears", + (2, 40) => "Shine!", + (2, 41) => "Race Fanfare", + (2, 42) => "Casino Fanfare", + (2, 43) => "Too Bad!", + (2, 44) => "Game Over", + (2, 45) => "Welcome to Isle Delfino (Movie)", + (2, 46) => "Icky Goop (Movie)", + (2, 47) => "Mario on Trial (Movie)", + (2, 48) => "How to Use FLUDD (Movie)", + (2, 49) => "Shadow Mario Appears (Movie)", + (2, 50) => "The Kidnapping of Princess Peach (Movie)", + (2, 51) => "Mecha-Bowser Rises (Movie)", + (2, 52) => "Meet Bowser Jr. (Movie)", + (2, 53) => "FLUDD Theft (Movie)", + (2, 54) => "Hot Tub Intrusion (Movie)", + (2, 55) => "Epilogue (Movie)", + (2, 56) => "Staff Credits", + (2, 57) => "Have a Relaxing Vacation!", + + // Super Mario Galaxy + (3, 0) => "Overture", + (3, 1) => "The Star Festival", + (3, 2) => "Attack of the Airships", + (3, 3) => "Catastrophe", + (3, 4) => "Peach's Castle Stolen", + (3, 5) => "Enter the Galaxy", + (3, 6) => "Egg Planet", + (3, 7) => "Rosaline in the Observatory 1", + (3, 8) => "The Honeyhive", + (3, 9) => "Space Junk Road", + (3, 10) => "Battlerock Galaxy", + (3, 11) => "Beach Bowl Galaxy", + (3, 12) => "Rosalina in the Observatory 2", + (3, 13) => "Enter Bowser Jr.!", + (3, 14) => "Waltz of the Boos", + (3, 15) => "Buoy Base Galaxy", + (3, 16) => "Gusty Garden Galaxy", + (3, 17) => "Rosaline in the Observatory 3", + (3, 18) => "King Bowser", + (3, 19) => "Melty Molten Galaxy", + (3, 20) => "The Galaxy Reactor", + (3, 21) => "Final Battle with Bowser", + (3, 22) => "A New Dawn", + (3, 23) => "Birth", + (3, 24) => "Super Mario Galaxy", + (3, 25) => "Purple Comet", + (3, 26) => "Blue Sky Athletic", + (3, 27) => "Super Mario 2007", + (3, 28) => "File Select", + (3, 29) => "Luma", + (3, 30) => "Gateway Galaxy", + (3, 31) => "Stolen Grand Star", + (3, 32) => "To the Observatory Grounds 1", + (3, 33) => "Observation Dome", + (3, 34) => "Course Select", + (3, 35) => "Dino Piranha", + (3, 36) => "A Chance to Grab a Star!", + (3, 37) => "A Tense Moment", + (3, 38) => "Big Bad Bugaboom", + (3, 39) => "King Kaliente", + (3, 40) => "The Toad Brigade", + (3, 41) => "Airship Armada", + (3, 42) => "Aquatic Race", + (3, 43) => "Space Fantasy", + (3, 44) => "Megaleg", + (3, 45) => "To The Observatory Grounds 2", + (3, 46) => "Space Athletic", + (3, 47) => "Speedy Comet", + (3, 48) => "Beach Bowl Galaxy - Undersea", + (3, 49) => "Interlude", + (3, 50) => "Bowser's Stronghold Appears", + (3, 51) => "The Fiery Stronghold", + (3, 52) => "The Big Staircase", + (3, 53) => "Bowser Appears", + (3, 54) => "Star Ball", + (3, 55) => "The Library", + (3, 56) => "Buoy Base Galaxy - Undersea", + (3, 57) => "Rainbow Mario", + (3, 58) => "Chase the Bunnies", + (3, 59) => "Help!", + (3, 60) => "Major Burrows", + (3, 61) => "Pipe Interior", + (3, 62) => "Cosmic Comet", + (3, 63) => "Drip Drop Galaxy", + (3, 64) => "Kingfin", + (3, 65) => "Boo Race", + (3, 66) => "Ice Mountain", + (3, 67) => "Ice Mario", + (3, 68) => "Lava Path", + (3, 69) => "Fire Mario", + (3, 70) => "Dusty Dune Galaxy", + (3, 71) => "Heavy Metal Mecha-Bowser", + (3, 72) => "A-wa-wa-wa!", + (3, 73) => "Deep Dark Galaxy", + (3, 74) => "Kamella", + (3, 75) => "Star Ball 2", + (3, 76) => "Sad Girl", + (3, 77) => "Flying Mario", + (3, 78) => "Star Child", + (3, 79) => "A Wish", + (3, 80) => "Family", + _ => "" + }; + + return string.IsNullOrEmpty(song) ? FormattedValue.ForceReset : $"{album} - {song}"; + } + + private static FormattedValue SuperMarioOdyssey(SingleValue value) + => value.Matched.LongValue switch + { + // TODO: Needs updated for sub-areas. + 2973331007 => "Cap Kingdom: Bonneton", + 2661781375 => "Cascade Kingdom: Fossil Falls", + 512560049 => "Sand Kingdom: Tostarena", + 3079659402 => "Wooded Kingdom: Steam Gardens", + 1941286268 => "Lake Kingdom: Lake Lamode", + 3098209122 => "Cloud Kingdom: Nimbus Arena", + 4088050842 => "Lost Kingdom: Forgotten Isle", + 53003352 => "Metro Kingdom: New Donk City", + 4265839612 => "Seaside Kingdom: Bubblaine", + 3288863344 => "Snow Kingdom: Shiveria", + 3180104973 => "Luncheon Kingdom: Mount Volbono", + 2284558980 => "Ruined Kingdom: Crumbleden", + 3024139598 => "Bowser's Kingdom: Bowser's Castle", + 1351608174 => "Moon Kingdom: Honeylune Ridge", + 1698750149 => "Dark Side: Rabbit Ridge", + 3206301958 => "Darker Side: Culmina Crater", + 3963002526 => "Mushroom Kingdom: Peach's Castle", + _ => FormattedValue.ForceReset + }; private static FormattedValue SuperMario3DWorldOrBowsersFury(SingleValue value) => value.Matched.BoxedValue is 0 ? "Playing Super Mario 3D World" : "Playing Bowser's Fury"; + private static FormattedValue SuperMarioWonder(SingleValue value) + { + // TODO: Needs updated for course names. + MessagePackObject messagePackObject = value.Matched.PackedValue; + MessagePackObjectDictionary messagePackObjectDictionary = messagePackObject.AsDictionary(); + + int worldNumber = messagePackObjectDictionary["world_no"].AsInt32(); + int courseNumber = 0; + + if (messagePackObjectDictionary.TryGetValue("course_no", out MessagePackObject courseNumberVariable)) + { + courseNumber = courseNumberVariable.AsInt32(); + } + + FormattedValue world = worldNumber switch + { + 1 => "Pipe-Rock Plateau", + 2 => "Petal Isles", + 3 => "Fluff-Puff Peaks", + 4 => "Shining Falls", + 5 => "Sunbaked Desert", + 6 => "Fungi Mines", + 7 => "Deep Magma Bog", + 9 => "Special World", + _ => FormattedValue.ForceReset + }; + + if (courseNumber == 0) + { + return FormattedValue.ForceReset; + } + + return world.Reset + ? FormattedValue.ForceReset + : $"{world}: {worldNumber}-{courseNumber}"; + } + private static FormattedValue MarioKart8Deluxe_Mode(SingleValue value) => value.Matched.StringValue switch { // Single Player "Single" => "Single Player", // Multiplayer - "Multi-2players" => "Multiplayer 2 Players", - "Multi-3players" => "Multiplayer 3 Players", - "Multi-4players" => "Multiplayer 4 Players", + "Multi-2players" => "Multiplayer: 2 Players", + "Multi-3players" => "Multiplayer: 3 Players", + "Multi-4players" => "Multiplayer: 4 Players", // Wireless/LAN Play "Local-Single" => "Wireless/LAN Play", "Local-2players" => "Wireless/LAN Play 2 Players", @@ -62,8 +421,9 @@ namespace Ryujinx.Ava.Systems.PlayReport private static FormattedValue PokemonSV(MultiValue values) { - - string playStatus = values.Matched[0].BoxedValue is 0 ? "Playing Alone" : "Playing in a group"; + string region = PokemonSV_Region(values.Matched[1].ToString()); + string union = values.Matched[0].BoxedValue is 0 ? "" : " with friends"; + string academyName = PokemonSV_AcademyName(values.Application.Title); FormattedValue locations = values.Matched[1].ToString() switch { @@ -89,18 +449,86 @@ namespace Ryujinx.Ava.Systems.PlayReport "a_w20" => "North Area Three", "a_w21" => "North Area One", "a_w22" => "North Area Two", - "a_w23" => "The Great Crater of Paldea", + "a_w23" => "Area Zero: The Great Crater of Paldea", "a_w24" => "South Paldean Sea", "a_w25" => "West Paldean Sea", "a_w26" => "East Paldean Sea", "a_w27" => "North Paldean Sea", - //TODO DLC Locations + // Naranja / Uva Academy + "a_sch_entrance01" => $"{academyName} Academy: Entrance", + "a_sch_cafe01" => $"{academyName} Academy: Cafeteria", + "a_sch_shop01" => $"{academyName} Academy: School Store", + "a_sch_room01" => $"{academyName} Academy: Home Ec Room", + "a_sch_room02" => $"{academyName} Academy: Art Room", + "a_sch_room03" => $"{academyName} Academy: Biology Lab", + "a_sch_room04" => $"{academyName} Academy: Staff Room", + "a_sch_office01" => $"{academyName} Academy: Director's Office", + "a_sch_office03" => $"{academyName} Academy: Nurse's Office", + "a_sch_ground01" => $"{academyName} Academy: School Yard", + "a_sch_class1a" => $"{academyName} Academy: Classroom 1-A", + "a_sch_class1d" => $"{academyName} Academy: Classroom 1-D", + "a_sch_class2g" => $"{academyName} Academy: Classroom 2-G", + "a_sch_dorm01" => $"{academyName} Academy: Dorm Room (Trainer)", + "a_sch_dorm02" => $"{academyName} Academy: Dorm Room (Nemona)", + "a_sch_dorm03" => $"{academyName} Academy: Dorm Room (Arven)", + "a_sch_dorm04" => $"{academyName} Academy: Dorm Room (Penny)", + // DLC + // Kitakami + "a_su0101" => "Mossui Town", + "a_su0102" => "Loyalty Plaza", + "a_su0103" => "Kitakami Hall", + "a_su0104" => "Oni Mountain", + "a_su0105" => "Infernal Pass", + "a_su0106" => "Crystal Pool", + "a_su0107" => "Wistful Fields", + "a_su0108" => "Mossfell Confluence", + "a_su0109" => "Fellhorn Gorge", + "a_su0110" => "Paradise Barrens", + "a_su0111" => "Timeless Woods", + // Blueberry Academy: School + "a_sch_2_entrance0" => "Blueberry Academy: Entrance", + "a_sch_2_clubroom" => "Blueberry Academy: League Clubroom", + "a_sch_2_class1" => "Blueberry Academy: Classroom 1-4", + "a_sch_2_class2" => "Blueberry Academy: Classroom 3-2", + "a_sch_2_shop01" => "Blueberry Academy: School Store", + "a_sch_2_cafe01" => "Blueberry Academy: Cafeteria", + "a_sch_2_dorm01" => "Blueberry Academy: Dorm Room (Trainer)", + "a_sch_2_dorm02" => "Blueberry Academy: Dorm Room (Carmine)", + // Blueberry Academy: Terrarium + "a_su0201" => "Savanna Biome", + "a_su0202" => "Coastal Biome", + "a_su0203" => "Canyon Biome", + "a_su0204" => "Polar Biome", _ => FormattedValue.ForceReset }; - - return locations.Reset - ? FormattedValue.ForceReset - : $"{playStatus} in {locations}"; + + return locations.Reset + ? FormattedValue.ForceReset + : $"Exploring {region}{union} | {locations}"; + } + + private static string PokemonSV_Region(string location) + { + if (location.Contains("a_su02") || location.Contains("a_sch_2")) return "Unova"; + if (location.Contains("a_su01")) return "Kitakami"; + return "Paldea"; + } + + private static string PokemonSV_AcademyName(string title) + { + // TODO: Is this even necessary? + if ( + title.Contains("Scarlet") + || title.Contains("Escarlata") + || title.Contains("Écarlate") + || title.Contains("Karmesin") + || title.Contains("Scarlatto") + || title.Contains("スカーレット") + || title.Contains("스칼렛") + || title.Contains("朱") + + ) { return "Naranja"; } + return "Uva"; } private static FormattedValue SuperSmashBrosUltimate_Mode(SparseMultiValue values) @@ -641,5 +1069,7 @@ namespace Ryujinx.Ava.Systems.PlayReport _ => FormattedValue.ForceReset }; + + } } diff --git a/src/Ryujinx/Systems/PlayReport/PlayReports.cs b/src/Ryujinx/Systems/PlayReport/PlayReports.cs index 628194b19..d483515bb 100644 --- a/src/Ryujinx/Systems/PlayReport/PlayReports.cs +++ b/src/Ryujinx/Systems/PlayReport/PlayReports.cs @@ -14,7 +14,7 @@ namespace Ryujinx.Ava.Systems.PlayReport private static readonly Lazy _analyzerLazy = new(() => new Analyzer() .AddSpec( - "01007ef00011e000", + "01007ef00011e000", // Breath of the Wild spec => spec .WithDescription("based on being in Master Mode.") .AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode) @@ -22,48 +22,74 @@ namespace Ryujinx.Ava.Systems.PlayReport .AddValueFormatter("AoCVer", FormattedValue.SingleAlwaysResets) ) .AddSpec( - "0100f2c0115b6000", + "0100f2c0115b6000", // Tears of the Kingdom spec => spec .WithDescription("based on where you are in Hyrule (Depths, Surface, Sky).") .AddValueFormatter("PlayerPosY", TearsOfTheKingdom_CurrentField)) .AddSpec( - "01002da013484000", + "01002da013484000", // Skyward Sword spec => spec .WithDescription("based on how many Rupees you have.") .AddValueFormatter("rupees", SkywardSwordHD_Rupees)) + .AddSpec( - "0100000000010000", + "01008cf01baac000", // Echoes of Wisdom spec => spec - .WithDescription("based on if you're playing with Assist Mode.") - .AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode) + .WithDescription("based on where you've warped.") + .AddValueFormatter("dest_index", EchoesOfWisdom_Warp) + ) + + .AddSpec( + "010049900f546000", // Super Mario 3D All Stars + spec => spec + .WithDescription("based on what album and track you're listening to.") + .AddMultiValueFormatter(["app_id","song_id"], SuperMario3DAllStars_MainMenu) ) .AddSpec( - "010075000ecbe000", + ["010049900f546001", "010049900f546002", "010049900F546003"], // Super Mario 3D All Stars spec => spec - .WithDescription("based on if you're playing with Assist Mode.") - .AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode) + .WithDescription("based on which game you've selected to play in the collection.") + .AddValueFormatter("program_id", SuperMario3DAllStars) ) .AddSpec( - "010028600ebda000", + "0100000000010000", // Super Mario Odyssey + spec => spec + .WithDescription("based on what kingdom you're in.") + .AddValueFormatter("stage_name", SuperMarioOdyssey) + ) + .AddSpec( + "010028600ebda000", // Super Mario 3D World + Bowser's Fury spec => spec .WithDescription("based on being in either Super Mario 3D World or Bowser's Fury.") .AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury) ) + .AddSpec( + ["010049900f546000", "010049900f546001", "010049900f546002", "010049900F546003"], + spec => spec + .WithDescription("based on which game you've selected to play in the collection.") + .AddValueFormatter("program_id", SuperMario3DAllStars) + ) + .AddSpec( + "010015100b514000", // Super Mario Bros. Wonder + spec => spec + .WithDescription("based on what world and course you're in.") + .AddValueFormatter("stage_info", SuperMarioWonder) + ) .AddSpec( // Global & China IDs - ["0100152000022000", "010075100e8ec000"], + ["0100152000022000", "010075100e8ec000"], // Mario Kart 8 Deluxe spec => spec .WithDescription( "based on what modes you're selecting in the menu & whether or not you're in a race.") .AddValueFormatter("To", MarioKart8Deluxe_Mode) ) .AddSpec( - ["0100a3d008c5c000", "01008f6008c5e000"], + ["0100a3d008c5c000", "01008f6008c5e000"], // Pokemon Scarlet/Violet spec => spec .WithDescription("based on if you're playing alone or in a group and what area of Paldea you're exploring.") .AddMultiValueFormatter(["team_circle", "area_no"], PokemonSV) ) .AddSpec( - "01006a800016e000", + "01006a800016e000", // Super Smash Bros. Ultimate spec => spec .WithDescription("based on what mode you're playing, who won, and what characters were present.") .AddSparseMultiValueFormatter( @@ -83,8 +109,10 @@ namespace Ryujinx.Ava.Systems.PlayReport ) .AddSpec( [ - "0100c9a00ece6000", "01008d300c50c000", "0100d870045b6000", - "010012f017576000", "0100c62011050000", "0100b3c014bda000" + "0100B4E00444C000", "0100d870045b6000", "01008d300c50c000", "0100c62011050000", "010012f017576000", + /*Famicom*/ /*NES*/ /*SNES*/ /*GBC*/ /*GBA*/ + "0100b3c014bda000", "0100c9a00ece6000", "0100e0601c632000", "0100bfc01d976000" + /*SEGA Genesis*/ /*N64*/ /*N64 MATURE*/ /*Virtual Boy*/ ], spec => spec .WithDescription(