mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2026-05-14 17:25:46 +00:00
Compare commits
26 Commits
Canary-1.3
...
Canary-1.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dcac29680a | ||
|
|
ec07e51807 | ||
|
|
e7aa6775af | ||
|
|
49891ba7af | ||
|
|
89ea41ef84 | ||
|
|
f8167eb625 | ||
|
|
bab160d650 | ||
|
|
e8cc252d9a | ||
|
|
8065dec744 | ||
|
|
e9c31bea3b | ||
|
|
5511ff5686 | ||
|
|
bf7f978f9d | ||
|
|
1f9bfab923 | ||
|
|
5d8cb3e378 | ||
|
|
708186d8d2 | ||
|
|
ad34237fc6 | ||
|
|
bf083a716c | ||
|
|
2d2661298c | ||
|
|
c4788154fd | ||
|
|
49dd56953c | ||
|
|
722eb93554 | ||
|
|
b0179e6433 | ||
|
|
1d3d4197b7 | ||
|
|
d2b2d65061 | ||
|
|
e1dcaef709 | ||
|
|
b222f671f3 |
@@ -202,44 +202,3 @@ jobs:
|
||||
name: ryujinx-${{ matrix.configuration }}-${{ steps.version_info.outputs.result }}+${{ steps.version_info.outputs.git_short_hash }}-macos_universal
|
||||
path: "publish/*.tar.gz"
|
||||
if: forgejo.event_name == 'pull_request'
|
||||
|
||||
post_comment:
|
||||
name: Post comment linking uploaded artifacts
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
- build_macos
|
||||
steps:
|
||||
- uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const forgejo = getOctokit(process.env.FORGEJO_TOKEN, {
|
||||
baseUrl: 'https://git.ryujinx.app/api/v1'
|
||||
});
|
||||
|
||||
const {owner, repo} = context.repo;
|
||||
const run_id = ${{ forgejo.run_id }};
|
||||
const pull_head_sha = '${{ forgejo.event.workflow_run.head_sha }}';
|
||||
|
||||
const issue_number = ${{ forgejo.event.pull_request.number }};
|
||||
core.info(`Using pull request ${issue_number}`);
|
||||
|
||||
const {data: {artifacts}} = await forgejo.rest.actions.listWorkflowRunArtifacts({owner, repo, run_id});
|
||||
if (!artifacts.length) {
|
||||
return core.error(`No artifacts found`);
|
||||
}
|
||||
let body = `Download the artifacts for this pull request:\n`;
|
||||
for (const art of artifacts) {
|
||||
const url = `https://git.ryujinx.app/api/v1/repos/${owner}/${repo}/actions/artifacts/${art.id}/zip`;
|
||||
body += `\n* [${art.name}](${url})`;
|
||||
}
|
||||
|
||||
const {data: comments} = await forgejo.rest.issues.listComments({repo, owner, issue_number});
|
||||
const existing_comment = comments.find((c) => c.user.login === 'forgejo-actions');
|
||||
if (existing_comment) {
|
||||
core.info(`Updating comment ${existing_comment.id}`);
|
||||
await forgejo.rest.issues.updateComment({repo, owner, comment_id: existing_comment.id, body});
|
||||
} else {
|
||||
core.info(`Creating a comment`);
|
||||
await forgejo.rest.issues.createComment({repo, owner, issue_number, body});
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Avalonia" Version="11.3.14" />
|
||||
<PackageVersion Include="Avalonia" Version="11.3.15" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.13" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.14" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.14" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.14" />
|
||||
<PackageVersion Include="SharpCompress" Version="0.47.4" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.15" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.15" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.15" />
|
||||
<PackageVersion Include="SharpCompress" Version="0.48.0" />
|
||||
<PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.9.5" />
|
||||
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.9.5" />
|
||||
<PackageVersion Include="Microsoft.Build.Framework" Version="17.11.4" />
|
||||
@@ -64,4 +64,4 @@
|
||||
<PackageVersion Include="System.IO.Hashing" Version="9.0.15" />
|
||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.1.3" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -6100,6 +6100,31 @@
|
||||
"zh_TW": "檔案系統全域存取日誌模式:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "SettingsTabLoggingEnableNetLogs",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"el_GR": "",
|
||||
"en_US": "Enable Net Logs",
|
||||
"es_ES": "Habilitar registros de red.",
|
||||
"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": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "SettingsTabLoggingDeveloperOptions",
|
||||
"Translations": {
|
||||
@@ -17075,6 +17100,31 @@
|
||||
"zh_TW": "啟用檔案系統存取日誌輸出到控制台中。可能的模式為 0 到 3。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "NetLogTooltip",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"el_GR": "",
|
||||
"en_US": "Prints network log messages in the console.",
|
||||
"es_ES": "Imprimir registros de red en la consola.",
|
||||
"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": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "DeveloperOptionTooltip",
|
||||
"Translations": {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -51,6 +51,7 @@ namespace Ryujinx.Common.Logging
|
||||
ServiceNgct,
|
||||
ServiceNifm,
|
||||
ServiceNim,
|
||||
ServiceNotification,
|
||||
ServiceNs,
|
||||
ServiceNsd,
|
||||
ServiceNtc,
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace Ryujinx.Common.Logging
|
||||
Error,
|
||||
Guest,
|
||||
AccessLog,
|
||||
NetLog,
|
||||
Notice,
|
||||
Trace,
|
||||
}
|
||||
|
||||
@@ -119,6 +119,7 @@ namespace Ryujinx.Common.Logging
|
||||
public static Log? Error { get; private set; }
|
||||
public static Log? Guest { get; private set; }
|
||||
public static Log? AccessLog { get; private set; }
|
||||
public static Log? NetLog { get; private set; }
|
||||
public static Log? Stub { get; private set; }
|
||||
public static Log? Trace { get; private set; }
|
||||
public static Log Notice { get; } // Always enabled
|
||||
@@ -247,6 +248,7 @@ namespace Ryujinx.Common.Logging
|
||||
case LogLevel.Error : Error = enabled ? new Log(LogLevel.Error) : null; break;
|
||||
case LogLevel.Guest : Guest = enabled ? new Log(LogLevel.Guest) : null; break;
|
||||
case LogLevel.AccessLog : AccessLog = enabled ? new Log(LogLevel.AccessLog) : null; break;
|
||||
case LogLevel.NetLog : NetLog = enabled ? new Log(LogLevel.NetLog) : null; break;
|
||||
case LogLevel.Stub : Stub = enabled ? new Log(LogLevel.Stub) : null; break;
|
||||
case LogLevel.Trace : Trace = enabled ? new Log(LogLevel.Trace) : null; break;
|
||||
case LogLevel.Notice : break;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -551,7 +551,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
level,
|
||||
x,
|
||||
width,
|
||||
format.PixelFormat,
|
||||
(InternalFormat) format.PixelFormat,
|
||||
mipSize,
|
||||
data);
|
||||
}
|
||||
@@ -579,7 +579,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
layer,
|
||||
width,
|
||||
1,
|
||||
format.PixelFormat,
|
||||
(InternalFormat) format.PixelFormat,
|
||||
mipSize,
|
||||
data);
|
||||
}
|
||||
@@ -609,7 +609,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
format.PixelFormat,
|
||||
(InternalFormat) format.PixelFormat,
|
||||
mipSize,
|
||||
data);
|
||||
}
|
||||
@@ -643,7 +643,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
width,
|
||||
height,
|
||||
1,
|
||||
format.PixelFormat,
|
||||
(InternalFormat) format.PixelFormat,
|
||||
mipSize,
|
||||
data);
|
||||
}
|
||||
@@ -675,7 +675,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
format.PixelFormat,
|
||||
(InternalFormat) format.PixelFormat,
|
||||
mipSize,
|
||||
data);
|
||||
}
|
||||
@@ -744,7 +744,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
level,
|
||||
0,
|
||||
width,
|
||||
format.PixelFormat,
|
||||
(InternalFormat) format.PixelFormat,
|
||||
mipSize,
|
||||
data);
|
||||
}
|
||||
@@ -773,7 +773,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
0,
|
||||
width,
|
||||
height,
|
||||
format.PixelFormat,
|
||||
(InternalFormat) format.PixelFormat,
|
||||
mipSize,
|
||||
data);
|
||||
}
|
||||
@@ -807,7 +807,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
width,
|
||||
height,
|
||||
depth,
|
||||
format.PixelFormat,
|
||||
(InternalFormat) format.PixelFormat,
|
||||
mipSize,
|
||||
data);
|
||||
}
|
||||
@@ -843,7 +843,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
0,
|
||||
width,
|
||||
height,
|
||||
format.PixelFormat,
|
||||
(InternalFormat) format.PixelFormat,
|
||||
mipSize / 6,
|
||||
data + faceOffset);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
public void Map(BufferHandle handle, int size)
|
||||
{
|
||||
GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle);
|
||||
nint ptr = GL.MapBufferRange(BufferTarget.CopyWriteBuffer, nint.Zero, size, BufferAccessMask.MapReadBit | BufferAccessMask.MapPersistentBit);
|
||||
nint ptr = GL.MapBufferRange(BufferTarget.CopyWriteBuffer, nint.Zero, size, MapBufferAccessMask.MapReadBit | MapBufferAccessMask.MapPersistentBit);
|
||||
|
||||
_maps[handle] = ptr;
|
||||
}
|
||||
@@ -75,7 +75,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
GL.BindBuffer(BufferTarget.CopyWriteBuffer, _copyBufferHandle);
|
||||
GL.BufferStorage(BufferTarget.CopyWriteBuffer, requiredSize, nint.Zero, BufferStorageFlags.MapReadBit | BufferStorageFlags.MapPersistentBit);
|
||||
|
||||
_bufferMap = GL.MapBufferRange(BufferTarget.CopyWriteBuffer, nint.Zero, requiredSize, BufferAccessMask.MapReadBit | BufferAccessMask.MapPersistentBit);
|
||||
_bufferMap = GL.MapBufferRange(BufferTarget.CopyWriteBuffer, nint.Zero, requiredSize, MapBufferAccessMask.MapReadBit | MapBufferAccessMask.MapPersistentBit);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -924,8 +924,8 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
GL.Disable(EnableCap.CullFace);
|
||||
return;
|
||||
}
|
||||
|
||||
GL.CullFace(face.Convert());
|
||||
|
||||
GL.CullFace((TriangleFace) face.Convert());
|
||||
|
||||
GL.Enable(EnableCap.CullFace);
|
||||
}
|
||||
@@ -1085,12 +1085,12 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
{
|
||||
if (frontMode == backMode)
|
||||
{
|
||||
GL.PolygonMode(MaterialFace.FrontAndBack, frontMode.Convert());
|
||||
GL.PolygonMode((TriangleFace) MaterialFace.FrontAndBack, frontMode.Convert());
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.PolygonMode(MaterialFace.Front, frontMode.Convert());
|
||||
GL.PolygonMode(MaterialFace.Back, backMode.Convert());
|
||||
GL.PolygonMode((TriangleFace) MaterialFace.Front, frontMode.Convert());
|
||||
GL.PolygonMode((TriangleFace) MaterialFace.Back, backMode.Convert());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
GL.CompileShader(shaderHandle);
|
||||
break;
|
||||
case TargetLanguage.Spirv:
|
||||
GL.ShaderBinary(1, ref shaderHandle, (BinaryFormat)All.ShaderBinaryFormatSpirVArb, shader.BinaryCode, shader.BinaryCode.Length);
|
||||
GL.ShaderBinary(1, ref shaderHandle, ShaderBinaryFormat.ShaderBinaryFormatSpirV, shader.BinaryCode, shader.BinaryCode.Length);
|
||||
GL.SpecializeShader(shaderHandle, "main", 0, (int[])null, (int[])null);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Ryujinx.Graphics.OpenGL.Queries
|
||||
GL.BufferStorage(BufferTarget.QueryBuffer, sizeof(long), (nint)(&defaultValue), BufferStorageFlags.MapReadBit | BufferStorageFlags.MapWriteBit | BufferStorageFlags.MapPersistentBit);
|
||||
}
|
||||
|
||||
_bufferMap = GL.MapBufferRange(BufferTarget.QueryBuffer, nint.Zero, sizeof(long), BufferAccessMask.MapReadBit | BufferAccessMask.MapWriteBit | BufferAccessMask.MapPersistentBit);
|
||||
_bufferMap = GL.MapBufferRange(BufferTarget.QueryBuffer, nint.Zero, sizeof(long), MapBufferAccessMask.MapReadBit | MapBufferAccessMask.MapWriteBit | MapBufferAccessMask.MapPersistentBit);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
|
||||
@@ -176,9 +176,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This can somehow become -1.
|
||||
// Logger.Info?.PrintMsg(LogClass.Gpu, $"_referenceCount: {_referenceCount}");
|
||||
|
||||
Debug.Assert(_referenceCount >= 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,22 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1)]
|
||||
// PushOutData(object<nn::am::service::IStorage>)
|
||||
public ResultCode PushOutData(ServiceCtx context)
|
||||
{
|
||||
IStorage appletData = GetObject<IStorage>(context, 0);
|
||||
|
||||
if (appletData == null || appletData.Data.Length == 0) // is this necessary?
|
||||
{
|
||||
return ResultCode.NullObject;
|
||||
}
|
||||
|
||||
_appletStandalone.InputData.Enqueue(appletData.Data);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(11)]
|
||||
// GetLibraryAppletInfo() -> nn::am::service::LibraryAppletInfo
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.HLE.HOS.Services.Caps.Types;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -9,16 +11,20 @@ using System.Security.Cryptography;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Caps
|
||||
{
|
||||
class CaptureManager
|
||||
internal class CaptureManager
|
||||
{
|
||||
private readonly string _sdCardPath;
|
||||
public CaptureManager(Switch device)
|
||||
{
|
||||
_ = device;
|
||||
}
|
||||
private readonly string _sdCardPath = FileSystem.VirtualFileSystem.GetSdCardPath();
|
||||
|
||||
private uint _shimLibraryVersion;
|
||||
|
||||
public CaptureManager(Switch device)
|
||||
{
|
||||
_sdCardPath = FileSystem.VirtualFileSystem.GetSdCardPath();
|
||||
}
|
||||
private const int ScreenshotWidth = 1280;
|
||||
private const int ScreenshotHeight = 720;
|
||||
private const int ScreenshotBytesPerPixel = 4;
|
||||
private const int ScreenshotDataSize = ScreenshotWidth * ScreenshotHeight * ScreenshotBytesPerPixel; // 0x384000
|
||||
|
||||
public ResultCode SetShimLibraryVersion(ServiceCtx context)
|
||||
{
|
||||
@@ -53,84 +59,94 @@ namespace Ryujinx.HLE.HOS.Services.Caps
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
public ResultCode SaveScreenShot(byte[] screenshotData, ulong appletResourceUserId, ulong titleId, out ApplicationAlbumEntry applicationAlbumEntry)
|
||||
public ResultCode SaveScreenShot(
|
||||
byte[] screenshotData,
|
||||
ulong appletResourceUserId,
|
||||
ulong titleId,
|
||||
out ApplicationAlbumEntry applicationAlbumEntry)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceCaps, new
|
||||
{
|
||||
appletResourceUserId,
|
||||
titleId,
|
||||
screenshotDataLength = screenshotData?.Length ?? 0,
|
||||
});
|
||||
|
||||
applicationAlbumEntry = default;
|
||||
|
||||
if (screenshotData.Length == 0)
|
||||
if (screenshotData == null || screenshotData.Length == 0)
|
||||
{
|
||||
return ResultCode.NullInputBuffer;
|
||||
}
|
||||
|
||||
/*
|
||||
// NOTE: On our current implementation, appletResourceUserId starts at 0, disable it for now.
|
||||
if (appletResourceUserId == 0)
|
||||
if (screenshotData.Length < ScreenshotDataSize)
|
||||
{
|
||||
Logger.Warning?.PrintMsg(
|
||||
LogClass.ServiceCaps,
|
||||
$"Invalid screenshot buffer size 0x{screenshotData.Length:X}; expected at least 0x{ScreenshotDataSize:X}.");
|
||||
|
||||
return ResultCode.NullInputBuffer;
|
||||
}
|
||||
|
||||
DateTime currentDateTime = DateTime.Now;
|
||||
|
||||
applicationAlbumEntry = new ApplicationAlbumEntry()
|
||||
{
|
||||
Size = (ulong)Unsafe.SizeOf<ApplicationAlbumEntry>(),
|
||||
TitleId = titleId,
|
||||
AlbumFileDateTime = new AlbumFileDateTime()
|
||||
{
|
||||
Year = (ushort)currentDateTime.Year,
|
||||
Month = (byte)currentDateTime.Month,
|
||||
Day = (byte)currentDateTime.Day,
|
||||
Hour = (byte)currentDateTime.Hour,
|
||||
Minute = (byte)currentDateTime.Minute,
|
||||
Second = (byte)currentDateTime.Second,
|
||||
UniqueId = 0,
|
||||
},
|
||||
AlbumStorage = AlbumStorage.Sd,
|
||||
ContentType = ContentType.Screenshot,
|
||||
Padding = new Array5<byte>(),
|
||||
Unknown0x1f = 1,
|
||||
};
|
||||
|
||||
// NOTE: The hex hash is a HMAC-SHA256 (first 32 bytes) using a hardcoded secret key over the titleId, we can simulate it by hashing the titleId instead.
|
||||
string hash = Convert.ToHexString(SHA256.HashData(BitConverter.GetBytes(titleId)))[..0x20];
|
||||
|
||||
string folderPath = Path.Combine(
|
||||
_sdCardPath,
|
||||
"Nintendo",
|
||||
"Album",
|
||||
currentDateTime.Year.ToString("0000", CultureInfo.InvariantCulture),
|
||||
currentDateTime.Month.ToString("00", CultureInfo.InvariantCulture),
|
||||
currentDateTime.Day.ToString("00", CultureInfo.InvariantCulture));
|
||||
|
||||
string filePath = GenerateFilePath(folderPath, applicationAlbumEntry, currentDateTime, hash);
|
||||
|
||||
_ = Directory.CreateDirectory(folderPath);
|
||||
|
||||
while (File.Exists(filePath))
|
||||
{
|
||||
applicationAlbumEntry.AlbumFileDateTime.UniqueId++;
|
||||
filePath = GenerateFilePath(folderPath, applicationAlbumEntry, currentDateTime, hash);
|
||||
}
|
||||
|
||||
using SKBitmap bitmap = new(new SKImageInfo(ScreenshotWidth, ScreenshotHeight, SKColorType.Rgba8888));
|
||||
|
||||
IntPtr pixels = bitmap.GetPixels();
|
||||
|
||||
if (pixels == IntPtr.Zero)
|
||||
{
|
||||
return ResultCode.InvalidArgument;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
// Doesn't occur in our case.
|
||||
if (applicationAlbumEntry == null)
|
||||
{
|
||||
return ResultCode.NullOutputBuffer;
|
||||
}
|
||||
*/
|
||||
Marshal.Copy(screenshotData, 0, pixels, ScreenshotDataSize);
|
||||
|
||||
if (screenshotData.Length >= 0x384000)
|
||||
{
|
||||
DateTime currentDateTime = DateTime.Now;
|
||||
using SKData data = bitmap.Encode(SKEncodedImageFormat.Jpeg, 80);
|
||||
using FileStream file = File.OpenWrite(filePath);
|
||||
data.SaveTo(file);
|
||||
|
||||
applicationAlbumEntry = new ApplicationAlbumEntry()
|
||||
{
|
||||
Size = (ulong)Unsafe.SizeOf<ApplicationAlbumEntry>(),
|
||||
TitleId = titleId,
|
||||
AlbumFileDateTime = new AlbumFileDateTime()
|
||||
{
|
||||
Year = (ushort)currentDateTime.Year,
|
||||
Month = (byte)currentDateTime.Month,
|
||||
Day = (byte)currentDateTime.Day,
|
||||
Hour = (byte)currentDateTime.Hour,
|
||||
Minute = (byte)currentDateTime.Minute,
|
||||
Second = (byte)currentDateTime.Second,
|
||||
UniqueId = 0,
|
||||
},
|
||||
AlbumStorage = AlbumStorage.Sd,
|
||||
ContentType = ContentType.Screenshot,
|
||||
Padding = new Array5<byte>(),
|
||||
Unknown0x1f = 1,
|
||||
};
|
||||
|
||||
// NOTE: The hex hash is a HMAC-SHA256 (first 32 bytes) using a hardcoded secret key over the titleId, we can simulate it by hashing the titleId instead.
|
||||
string hash = Convert.ToHexString(SHA256.HashData(BitConverter.GetBytes(titleId)))[..0x20];
|
||||
string folderPath = Path.Combine(_sdCardPath, "Nintendo", "Album", currentDateTime.Year.ToString("00"), currentDateTime.Month.ToString("00"), currentDateTime.Day.ToString("00"));
|
||||
string filePath = GenerateFilePath(folderPath, applicationAlbumEntry, currentDateTime, hash);
|
||||
|
||||
// TODO: Handle that using the FS service implementation and return the right error code instead of throwing exceptions.
|
||||
Directory.CreateDirectory(folderPath);
|
||||
|
||||
while (File.Exists(filePath))
|
||||
{
|
||||
applicationAlbumEntry.AlbumFileDateTime.UniqueId++;
|
||||
|
||||
filePath = GenerateFilePath(folderPath, applicationAlbumEntry, currentDateTime, hash);
|
||||
}
|
||||
|
||||
// NOTE: The saved JPEG file doesn't have the limitation in the extra EXIF data.
|
||||
using SKBitmap bitmap = new(new SKImageInfo(1280, 720, SKColorType.Rgba8888, SKAlphaType.Premul));
|
||||
int dataLen = screenshotData.Length > bitmap.ByteCount ? bitmap.ByteCount : screenshotData.Length;
|
||||
|
||||
Marshal.Copy(screenshotData, 0, bitmap.GetPixels(), dataLen);
|
||||
|
||||
using SKData data = bitmap.Encode(SKEncodedImageFormat.Jpeg, 80);
|
||||
using FileStream file = File.OpenWrite(filePath);
|
||||
data.SaveTo(file);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
return ResultCode.NullInputBuffer;
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
private string GenerateFilePath(string folderPath, ApplicationAlbumEntry applicationAlbumEntry, DateTime currentDateTime, string hash)
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Services.Caps.Types;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Caps
|
||||
{
|
||||
[Service("caps:su")] // 6.0.0+
|
||||
class IScreenShotApplicationService : IpcService
|
||||
internal class IScreenShotApplicationService : IpcService
|
||||
{
|
||||
public IScreenShotApplicationService(ServiceCtx context) { }
|
||||
private const ulong ScreenshotDataSize = 0x384000;
|
||||
private const ulong ApplicationDataSize = 0x404;
|
||||
|
||||
public IScreenShotApplicationService(ServiceCtx context)
|
||||
{
|
||||
_ = context;
|
||||
}
|
||||
[CommandCmif(32)] // 7.0.0+
|
||||
// SetShimLibraryVersion(pid, u64, nn::applet::AppletResourceUserId)
|
||||
public ResultCode SetShimLibraryVersion(ServiceCtx context)
|
||||
@@ -33,6 +39,15 @@ namespace Ryujinx.HLE.HOS.Services.Caps
|
||||
ulong screenshotDataPosition = context.Request.SendBuff[0].Position;
|
||||
ulong screenshotDataSize = context.Request.SendBuff[0].Size;
|
||||
|
||||
if (screenshotDataSize < ScreenshotDataSize)
|
||||
{
|
||||
Logger.Warning?.PrintMsg(
|
||||
LogClass.ServiceCaps,
|
||||
$"Invalid screenshot buffer size 0x{screenshotDataSize:X}; expected at least 0x{ScreenshotDataSize:X}.");
|
||||
|
||||
return ResultCode.NullInputBuffer;
|
||||
}
|
||||
|
||||
byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray();
|
||||
|
||||
ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry);
|
||||
@@ -60,6 +75,24 @@ namespace Ryujinx.HLE.HOS.Services.Caps
|
||||
ulong screenshotDataPosition = context.Request.SendBuff[1].Position;
|
||||
ulong screenshotDataSize = context.Request.SendBuff[1].Size;
|
||||
|
||||
if (applicationDataSize != ApplicationDataSize)
|
||||
{
|
||||
Logger.Warning?.PrintMsg(
|
||||
LogClass.ServiceCaps,
|
||||
$"Invalid ApplicationData size 0x{applicationDataSize:X}; expected 0x{ApplicationDataSize:X}.");
|
||||
|
||||
return ResultCode.InvalidArgument;
|
||||
}
|
||||
|
||||
if (screenshotDataSize < ScreenshotDataSize)
|
||||
{
|
||||
Logger.Warning?.PrintMsg(
|
||||
LogClass.ServiceCaps,
|
||||
$"Invalid screenshot buffer size 0x{screenshotDataSize:X}; expected at least 0x{ScreenshotDataSize:X}.");
|
||||
|
||||
return ResultCode.NullInputBuffer;
|
||||
}
|
||||
|
||||
// TODO: Parse the application data: At 0x00 it's UserData (Size of 0x400), at 0x404 it's a uint UserDataSize (Always empty for now).
|
||||
_ = context.Memory.GetSpan(applicationDataPosition, (int)applicationDataSize).ToArray();
|
||||
|
||||
@@ -88,6 +121,23 @@ namespace Ryujinx.HLE.HOS.Services.Caps
|
||||
ulong screenshotDataPosition = context.Request.SendBuff[1].Position;
|
||||
ulong screenshotDataSize = context.Request.SendBuff[1].Size;
|
||||
|
||||
if (userIdListSize != 0x88)
|
||||
{
|
||||
Logger.Warning?.PrintMsg(
|
||||
LogClass.ServiceCaps,
|
||||
$"Invalid UserIdList size 0x{userIdListSize:X}; expected 0x88.");
|
||||
return ResultCode.InvalidArgument;
|
||||
}
|
||||
|
||||
if (screenshotDataSize < ScreenshotDataSize)
|
||||
{
|
||||
Logger.Warning?.PrintMsg(
|
||||
LogClass.ServiceCaps,
|
||||
$"Invalid screenshot buffer size 0x{screenshotDataSize:X}; expected at least 0x{ScreenshotDataSize:X}.");
|
||||
|
||||
return ResultCode.NullInputBuffer;
|
||||
}
|
||||
|
||||
// TODO: Parse the UserIdList.
|
||||
_ = context.Memory.GetSpan(userIdListPosition, (int)userIdListSize).ToArray();
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.Exceptions;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
@@ -15,7 +14,6 @@ using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
@@ -68,10 +66,11 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
{
|
||||
if (localCommunicationId == localCommunicationIdChecked)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,"CheckLocalCommumicationIdPermission: Checked!");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,"CheckLocalCommumicationIdPermission: Check failed!");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -82,7 +81,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
if (_nifmResultCode != ResultCode.Success)
|
||||
{
|
||||
context.ResponseData.Write((int)NetworkState.Error);
|
||||
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,$"GetState: _nifmResultCode = {_nifmResultCode.ToString()}");
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
@@ -114,12 +113,14 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
|
||||
if (_nifmResultCode != ResultCode.Success)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,$"GetNetworkInfo: _nifmResultCode = {_nifmResultCode.ToString()}");
|
||||
return _nifmResultCode;
|
||||
}
|
||||
|
||||
ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo);
|
||||
if (resultCode != ResultCode.Success)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,$"GetState: resultCode = {resultCode.ToString()}");
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
@@ -135,18 +136,22 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
if (_state == NetworkState.StationConnected)
|
||||
{
|
||||
networkInfo = _station.NetworkInfo;
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,"GetNetworkInfoImpl: _station");
|
||||
}
|
||||
else if (_state == NetworkState.AccessPointCreated)
|
||||
{
|
||||
networkInfo = _accessPoint.NetworkInfo;
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,"GetNetworkInfoImpl: _accessPoint");
|
||||
}
|
||||
else
|
||||
{
|
||||
networkInfo = new NetworkInfo();
|
||||
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,"GetNetworkInfoImpl: Invalid state!");
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,$"GetNetworkInfoImpl: networkInfo = {networkInfo}");
|
||||
return ResultCode.InvalidState;
|
||||
}
|
||||
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,$"GetNetworkInfoImpl: networkInfo = {networkInfo}");
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
@@ -198,7 +203,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Info?.Print(LogClass.ServiceLdn, $"Console's LDN IP is \"{unicastAddress.Address}\".");
|
||||
Logger.NetLog?.Print(LogClass.ServiceLdn, $"Console's LDN IP is \"{unicastAddress.Address}\".");
|
||||
|
||||
context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(unicastAddress.Address));
|
||||
context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(unicastAddress.IPv4Mask));
|
||||
@@ -206,7 +211,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Info?.Print(LogClass.ServiceLdn, $"LDN obtained proxy IP.");
|
||||
Logger.NetLog?.Print(LogClass.ServiceLdn, $"LDN obtained proxy IP.");
|
||||
|
||||
context.ResponseData.Write(config.ProxyIp);
|
||||
context.ResponseData.Write(config.ProxySubnetMask);
|
||||
@@ -227,7 +232,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
// NOTE: Returns ResultCode.InvalidArgument if _disconnectReason is null, doesn't occur in our case.
|
||||
|
||||
context.ResponseData.Write((short)_disconnectReason);
|
||||
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetDisconnectReason: {_disconnectReason}");
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
@@ -247,12 +252,14 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
{
|
||||
if (_nifmResultCode != ResultCode.Success)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetSecurityParameter: _nifmResultCode = {_nifmResultCode}");
|
||||
return _nifmResultCode;
|
||||
}
|
||||
|
||||
ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo);
|
||||
if (resultCode != ResultCode.Success)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetSecurityParameter: resultCode = {resultCode}");
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
@@ -263,7 +270,8 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
};
|
||||
|
||||
context.ResponseData.WriteStruct(securityParameter);
|
||||
|
||||
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetSecurityParameter: securityParameter = {securityParameter}");
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
@@ -273,12 +281,14 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
{
|
||||
if (_nifmResultCode != ResultCode.Success)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetNetworkConfig: _nifmResultCode = {_nifmResultCode}");
|
||||
return _nifmResultCode;
|
||||
}
|
||||
|
||||
ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo);
|
||||
if (resultCode != ResultCode.Success)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetNetworkConfig: resultCode = {resultCode}");
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
@@ -292,6 +302,8 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
};
|
||||
|
||||
context.ResponseData.WriteStruct(networkConfig);
|
||||
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetNetworkConfig: networkConfig = {networkConfig}");
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
@@ -322,12 +334,14 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
|
||||
if (_nifmResultCode != ResultCode.Success)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetNetworkInfoLatestUpdate: _nifmResultCode = {_nifmResultCode}");
|
||||
return _nifmResultCode;
|
||||
}
|
||||
|
||||
ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo);
|
||||
if (resultCode != ResultCode.Success)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetNetworkInfoLatestUpdate: resultCode = {resultCode}");
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
@@ -378,6 +392,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
|
||||
if (_nifmResultCode != ResultCode.Success)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ScanImpl: _nifmResultCode = {_nifmResultCode}");
|
||||
return _nifmResultCode;
|
||||
}
|
||||
|
||||
@@ -400,6 +415,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
{
|
||||
if (scanFilter.Ssid.Length <= 31)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ScanImpl: resultCode = {resultCode}");
|
||||
return resultCode;
|
||||
}
|
||||
}
|
||||
@@ -408,11 +424,13 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
{
|
||||
if (scanFilterFlag > ScanFilterFlag.All)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ScanImpl: resultCode = {resultCode}");
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
if (_state - 3 >= NetworkState.AccessPoint)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "ScanImpl: Invalid state!");
|
||||
resultCode = ResultCode.InvalidState;
|
||||
}
|
||||
else
|
||||
@@ -437,7 +455,8 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ScanImpl: resultCode = {resultCode}");
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
@@ -462,6 +481,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
}
|
||||
}
|
||||
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ScanInternal: availableGames = {availableGames}");
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
@@ -502,7 +522,8 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
throw new ArgumentException($"{GetType().FullName}: Protocol value is not 1 or 3!! Protocol value: {protocolValue}");
|
||||
}
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceLdn, $"Protocol value: {protocolValue}");
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"SetProtocol: protocolValue = {protocolValue}");
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceLdn, new { protocolValue});
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
@@ -512,11 +533,13 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
{
|
||||
if (_nifmResultCode != ResultCode.Success)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"OpenAccessPoint: _nifmResultCode = {_nifmResultCode}");
|
||||
return _nifmResultCode;
|
||||
}
|
||||
|
||||
if (_state != NetworkState.Initialized)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "OpenAccessPoint: Invalid state!");
|
||||
return ResultCode.InvalidState;
|
||||
}
|
||||
|
||||
@@ -538,6 +561,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
{
|
||||
if (_nifmResultCode != ResultCode.Success)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"CloseAccessPoint: _nifmResultCode = {_nifmResultCode}");
|
||||
return _nifmResultCode;
|
||||
}
|
||||
|
||||
@@ -547,6 +571,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "CloseAccessPoint: Invalid state!");
|
||||
return ResultCode.InvalidState;
|
||||
}
|
||||
|
||||
@@ -596,11 +621,13 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
bool isLocalCommunicationIdValid = CheckLocalCommunicationIdPermission(context, (ulong)networkConfig.IntentId.LocalCommunicationId);
|
||||
if (!isLocalCommunicationIdValid && NetworkClient.NeedsRealId)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "CreateNetworkImpl: Invalid object!");
|
||||
return ResultCode.InvalidObject;
|
||||
}
|
||||
|
||||
if (_nifmResultCode != ResultCode.Success)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"CreateNetworkImpl: _nifmResultCode = {_nifmResultCode}");
|
||||
return _nifmResultCode;
|
||||
}
|
||||
|
||||
@@ -629,16 +656,22 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
AddressList addressList = MemoryMarshal.Cast<byte, AddressList>(addressListBytes)[0];
|
||||
|
||||
_accessPoint.CreateNetworkPrivate(securityConfig, securityParameter, userConfig, networkConfig, addressList);
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"CreateNetworkImpl: Created private network! " +
|
||||
$"| securityConfig = {securityConfig} | securityParameter = {securityParameter} " +
|
||||
$"| userConfig = {userConfig} | networkConfig = {networkConfig} | addressList = {addressList}");
|
||||
}
|
||||
else
|
||||
{
|
||||
_accessPoint.CreateNetwork(securityConfig, userConfig, networkConfig);
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"CreateNetworkImpl: Created network! " +
|
||||
$"| securityConfig = {securityConfig} | userConfig = {userConfig} | networkConfig = {networkConfig}");
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "CreateNetworkImpl: Invalid state!");
|
||||
return ResultCode.InvalidState;
|
||||
}
|
||||
}
|
||||
@@ -660,6 +693,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
{
|
||||
if (_nifmResultCode != ResultCode.Success)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"DestroyNetworkImpl: _nifmResultCode = {_nifmResultCode}");
|
||||
return _nifmResultCode;
|
||||
}
|
||||
|
||||
@@ -676,9 +710,11 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
|
||||
CloseAccessPoint();
|
||||
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "DestroyNetworkImpl: Invalid state!");
|
||||
return ResultCode.InvalidState;
|
||||
}
|
||||
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "DestroyNetworkImpl: Invalid argument!");
|
||||
return ResultCode.InvalidArgument;
|
||||
}
|
||||
|
||||
@@ -695,14 +731,17 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
{
|
||||
if (_nifmResultCode != ResultCode.Success)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"RejectImpl: _nifmResultCode = {_nifmResultCode}");
|
||||
return _nifmResultCode;
|
||||
}
|
||||
|
||||
if (_state != NetworkState.AccessPointCreated)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "RejectImpl: Invalid state!");
|
||||
return ResultCode.InvalidState; // Must be network host to reject nodes.
|
||||
}
|
||||
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"RejectImpl: disconnectReason = {disconnectReason} | nodeId = {nodeId}");
|
||||
return NetworkClient.Reject(disconnectReason, nodeId);
|
||||
}
|
||||
|
||||
@@ -714,11 +753,13 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
|
||||
if (_nifmResultCode != ResultCode.Success)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"SetAdvertiseData: _nifmResultCode = {_nifmResultCode}");
|
||||
return _nifmResultCode;
|
||||
}
|
||||
|
||||
if (bufferSize is 0 or > LdnConst.AdvertiseDataSizeMax)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "SetAdvertiseData: Invalid argument!");
|
||||
return ResultCode.InvalidArgument;
|
||||
}
|
||||
|
||||
@@ -727,11 +768,12 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
byte[] advertiseData = new byte[bufferSize];
|
||||
|
||||
context.Memory.Read(bufferPosition, advertiseData);
|
||||
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"SetAdvertiseData: advertiseData = {advertiseData}");
|
||||
return _accessPoint.SetAdvertiseData(advertiseData);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "SetAdvertiseData: Invalid state!");
|
||||
return ResultCode.InvalidState;
|
||||
}
|
||||
}
|
||||
@@ -744,20 +786,24 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
|
||||
if (_nifmResultCode != ResultCode.Success)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"SetStationAcceptPolicy: _nifmResultCode = {_nifmResultCode}");
|
||||
return _nifmResultCode;
|
||||
}
|
||||
|
||||
if (acceptPolicy > AcceptPolicy.WhiteList)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "SetStationAcceptPolicy: Invalid argument!");
|
||||
return ResultCode.InvalidArgument;
|
||||
}
|
||||
|
||||
if (_state is NetworkState.AccessPoint or NetworkState.AccessPointCreated)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"SetStationAcceptPolicy: acceptPolicy = {acceptPolicy}");
|
||||
return _accessPoint.SetStationAcceptPolicy(acceptPolicy);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "SetStationAcceptPolicy: Invalid state!");
|
||||
return ResultCode.InvalidState;
|
||||
}
|
||||
}
|
||||
@@ -768,6 +814,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
{
|
||||
if (_nifmResultCode != ResultCode.Success)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"AddAcceptFilterEntry: _nifmResultCode = {_nifmResultCode}");
|
||||
return _nifmResultCode;
|
||||
}
|
||||
|
||||
@@ -782,6 +829,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
{
|
||||
if (_nifmResultCode != ResultCode.Success)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ClearAcceptFilter: _nifmResultCode = {_nifmResultCode}");
|
||||
return _nifmResultCode;
|
||||
}
|
||||
|
||||
@@ -796,11 +844,13 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
{
|
||||
if (_nifmResultCode != ResultCode.Success)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"OpenStation: _nifmResultCode = {_nifmResultCode}");
|
||||
return _nifmResultCode;
|
||||
}
|
||||
|
||||
if (_state != NetworkState.Initialized)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "OpenStation: Invalid state!");
|
||||
return ResultCode.InvalidState;
|
||||
}
|
||||
|
||||
@@ -813,6 +863,8 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
|
||||
// NOTE: Calls nifm service and returns related result codes.
|
||||
// Since we use our own implementation we can return ResultCode.Success.
|
||||
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"OpenStation: _station = {_station}");
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
@@ -823,6 +875,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
{
|
||||
if (_nifmResultCode != ResultCode.Success)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"CloseStation: _nifmResultCode = {_nifmResultCode}");
|
||||
return _nifmResultCode;
|
||||
}
|
||||
|
||||
@@ -832,11 +885,13 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "CloseStation: Invalid state!");
|
||||
return ResultCode.InvalidState;
|
||||
}
|
||||
|
||||
SetState(NetworkState.Initialized);
|
||||
|
||||
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "CloseStation: Closed.");
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
@@ -901,11 +956,13 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
bool isLocalCommunicationIdValid = CheckLocalCommunicationIdPermission(context, (ulong)networkInfo.NetworkId.IntentId.LocalCommunicationId);
|
||||
if (!isLocalCommunicationIdValid && NetworkClient.NeedsRealId)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "ConnectImpl: Invalid object!");
|
||||
return ResultCode.InvalidObject;
|
||||
}
|
||||
|
||||
if (_nifmResultCode != ResultCode.Success)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ConnectImpl: _nifmResultCode = {_nifmResultCode}");
|
||||
return _nifmResultCode;
|
||||
}
|
||||
|
||||
@@ -925,6 +982,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
{
|
||||
if (_state != NetworkState.Station)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "ConnectImpl: Invalid state!");
|
||||
resultCode = ResultCode.InvalidState;
|
||||
}
|
||||
else
|
||||
@@ -932,10 +990,16 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
if (isPrivate)
|
||||
{
|
||||
resultCode = _station.ConnectPrivate(securityConfig, securityParameter, userConfig, localCommunicationVersion, optionUnknown, networkConfig);
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ConnectImpl: Private connection established! " +
|
||||
$"| securityConfig = {securityConfig} | securityParameter = {securityParameter} | userConfig = {userConfig} " +
|
||||
$"| localCommunicationVersion = {localCommunicationVersion} | optionUnknown = {optionUnknown} | networkConfig = {networkConfig}");
|
||||
}
|
||||
else
|
||||
{
|
||||
resultCode = _station.Connect(securityConfig, userConfig, localCommunicationVersion, optionUnknown, networkInfo);
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ConnectImpl: Connection established! " +
|
||||
$"| securityConfig = {securityConfig} | userConfig = {userConfig} " +
|
||||
$"| localCommunicationVersion = {localCommunicationVersion} | optionUnknown = {optionUnknown} | networkConfig = {networkConfig}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -943,6 +1007,8 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
}
|
||||
}
|
||||
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ConnectImpl: resultCode = {resultCode}");
|
||||
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
@@ -957,6 +1023,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
{
|
||||
if (_nifmResultCode != ResultCode.Success)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"DisconnectImpl: _nifmResultCode = {_nifmResultCode}");
|
||||
return _nifmResultCode;
|
||||
}
|
||||
|
||||
@@ -970,14 +1037,17 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
|
||||
_disconnectReason = disconnectReason;
|
||||
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"DisconnectImpl: _disconnectReason = {_disconnectReason}");
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
CloseStation();
|
||||
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "DisconnectImpl: Invalid state!");
|
||||
return ResultCode.InvalidState;
|
||||
}
|
||||
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "DisconnectImpl: Invalid argument!");
|
||||
return ResultCode.InvalidArgument;
|
||||
}
|
||||
|
||||
@@ -994,6 +1064,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
{
|
||||
if (_nifmResultCode != ResultCode.Success)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"Finalize: _disconnectReason = {_disconnectReason}");
|
||||
return _nifmResultCode;
|
||||
}
|
||||
|
||||
@@ -1010,11 +1081,13 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
_stateChangeEventHandle = 0;
|
||||
}
|
||||
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"Finalize: resultCode = {resultCode}");
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
private ResultCode FinalizeImpl(bool isCausedBySystem)
|
||||
{
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "FinalizeImpl");
|
||||
DisconnectReason disconnectReason;
|
||||
|
||||
switch (_state)
|
||||
@@ -1138,7 +1211,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
NetworkClient.SetGameVersion(context.Device.Processes.ActiveApplication.ApplicationControlProperties.DisplayVersion);
|
||||
|
||||
resultCode = ResultCode.Success;
|
||||
|
||||
_nifmResultCode = resultCode;
|
||||
|
||||
SetState(NetworkState.Initialized);
|
||||
@@ -1152,6 +1224,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
}
|
||||
}
|
||||
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"InitializeImpl: resultCode = {resultCode}");
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu
|
||||
|
||||
protected override void OnConnected()
|
||||
{
|
||||
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"LDN TCP client connected a new session with Id {Id}");
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"LDN TCP client connected a new session with Id {Id}");
|
||||
|
||||
UpdatePassphraseIfNeeded();
|
||||
|
||||
@@ -141,7 +141,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu
|
||||
|
||||
protected override void OnDisconnected()
|
||||
{
|
||||
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"LDN TCP client disconnected a session with Id {Id}");
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"LDN TCP client disconnected a session with Id {Id}");
|
||||
|
||||
_passphrase = null;
|
||||
|
||||
@@ -174,7 +174,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu
|
||||
|
||||
protected override void OnError(SocketError error)
|
||||
{
|
||||
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"LDN TCP client caught an error with code {error}");
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"LDN TCP client caught an error with code {error}");
|
||||
|
||||
_error.Set();
|
||||
}
|
||||
@@ -428,7 +428,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Info?.Print(LogClass.ServiceLdn, $"Created a wireless P2P network on port {request.ExternalProxyPort}.");
|
||||
Logger.NetLog?.Print(LogClass.ServiceLdn, $"Created a wireless P2P network on port {request.ExternalProxyPort}.");
|
||||
_hostedProxy.Start();
|
||||
|
||||
(_, UnicastIPAddressInformation unicastAddress) = NetworkHelpers.GetLocalInterface();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
|
||||
@@ -36,10 +37,12 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
if (Connected)
|
||||
{
|
||||
_parent.SetState(NetworkState.StationConnected);
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,$"NetworkChanged: {NetworkInfo}");
|
||||
}
|
||||
else
|
||||
{
|
||||
_parent.SetDisconnectReason(e.DisconnectReasonOrDefault(DisconnectReason.DestroyedByUser));
|
||||
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,"NetworkChanged: Disconnected (DestroyedByUser)");
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -81,8 +81,10 @@ namespace Ryujinx.HLE.HOS.Services.Mii
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode UpdateLatest<T>(DatabaseSessionMetadata metadata, IStoredData<T> oldMiiData, SourceFlag flag, IStoredData<T> newMiiData) where T : unmanaged
|
||||
public ResultCode UpdateLatest<T>(DatabaseSessionMetadata metadata, T oldMiiData, SourceFlag flag, out T newMiiData) where T : unmanaged, IStoredData<T>
|
||||
{
|
||||
newMiiData = default;
|
||||
|
||||
if (!flag.HasFlag(SourceFlag.Database))
|
||||
{
|
||||
return ResultCode.NotFound;
|
||||
@@ -106,7 +108,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii
|
||||
|
||||
newMiiData.SetFromStoreData(storeData);
|
||||
|
||||
if (oldMiiData == newMiiData)
|
||||
if (oldMiiData.Equals(newMiiData))
|
||||
{
|
||||
return ResultCode.NotUpdated;
|
||||
}
|
||||
@@ -286,6 +288,18 @@ namespace Ryujinx.HLE.HOS.Services.Mii
|
||||
return result;
|
||||
}
|
||||
|
||||
public ResultCode Append(DatabaseSessionMetadata metadata, CharInfo charInfo)
|
||||
{
|
||||
ResultCode result = _miiDatabase.Append(metadata, _utilityImpl, charInfo);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
result = _miiDatabase.SaveDatabase();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public ResultCode ConvertCharInfoToCoreData(CharInfo charInfo, out CoreData coreData)
|
||||
{
|
||||
coreData = new CoreData();
|
||||
|
||||
@@ -449,6 +449,32 @@ namespace Ryujinx.HLE.HOS.Services.Mii
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode Append(DatabaseSessionMetadata metadata, UtilityImpl utilityImpl, CharInfo charInfo)
|
||||
{
|
||||
if (!charInfo.IsValid())
|
||||
{
|
||||
return ResultCode.InvalidCharInfo;
|
||||
}
|
||||
|
||||
if (charInfo.Type == 1)
|
||||
{
|
||||
return ResultCode.InvalidOperationOnSpecialMii;
|
||||
}
|
||||
|
||||
CoreData coreData = new();
|
||||
coreData.SetFromCharInfo(charInfo);
|
||||
|
||||
StoreData storeData;
|
||||
|
||||
do
|
||||
{
|
||||
storeData = StoreData.BuildFromCoreData(utilityImpl, coreData);
|
||||
}
|
||||
while (_database.GetIndexByCreatorId(out _, storeData.CreateId));
|
||||
|
||||
return AddOrReplace(metadata, storeData);
|
||||
}
|
||||
|
||||
public ResultCode Delete(DatabaseSessionMetadata metadata, CreateId createId)
|
||||
{
|
||||
if (!_database.GetIndexByCreatorId(out int index, createId))
|
||||
|
||||
@@ -54,9 +54,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii.StaticService
|
||||
|
||||
protected override ResultCode UpdateLatest(CharInfo oldCharInfo, SourceFlag flag, out CharInfo newCharInfo)
|
||||
{
|
||||
newCharInfo = default;
|
||||
|
||||
return _database.UpdateLatest(_metadata, oldCharInfo, flag, newCharInfo);
|
||||
return _database.UpdateLatest(_metadata, oldCharInfo, flag, out newCharInfo);
|
||||
}
|
||||
|
||||
protected override ResultCode BuildRandom(Age age, Gender gender, Race race, out CharInfo charInfo)
|
||||
@@ -113,14 +111,14 @@ namespace Ryujinx.HLE.HOS.Services.Mii.StaticService
|
||||
|
||||
protected override ResultCode UpdateLatest1(StoreData oldStoreData, SourceFlag flag, out StoreData newStoreData)
|
||||
{
|
||||
newStoreData = default;
|
||||
|
||||
if (!_isSystem)
|
||||
{
|
||||
newStoreData = default;
|
||||
|
||||
return ResultCode.PermissionDenied;
|
||||
}
|
||||
|
||||
return _database.UpdateLatest(_metadata, oldStoreData, flag, newStoreData);
|
||||
return _database.UpdateLatest(_metadata, oldStoreData, flag, out newStoreData);
|
||||
}
|
||||
|
||||
protected override ResultCode FindIndex(CreateId createId, bool isSpecial, out int index)
|
||||
@@ -262,5 +260,10 @@ namespace Ryujinx.HLE.HOS.Services.Mii.StaticService
|
||||
{
|
||||
return _database.ConvertCharInfoToCoreData(charInfo, out coreData);
|
||||
}
|
||||
|
||||
protected override ResultCode Append(CharInfo charInfo)
|
||||
{
|
||||
return _database.Append(_metadata, charInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,6 +340,15 @@ namespace Ryujinx.HLE.HOS.Services.Mii.StaticService
|
||||
return result;
|
||||
}
|
||||
|
||||
[CommandCmif(26)] // 10.2.0+
|
||||
// Append(nn::mii::CharInfo char_info)
|
||||
public ResultCode Append(ServiceCtx context)
|
||||
{
|
||||
CharInfo charInfo = context.RequestData.ReadStruct<CharInfo>();
|
||||
|
||||
return Append(charInfo);
|
||||
}
|
||||
|
||||
private Span<byte> CreateByteSpanFromBuffer(ServiceCtx context, IpcBuffDesc ipcBuff, bool isOutput)
|
||||
{
|
||||
byte[] rawData;
|
||||
@@ -421,5 +430,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii.StaticService
|
||||
protected abstract ResultCode ConvertCoreDataToCharInfo(CoreData coreData, out CharInfo charInfo);
|
||||
|
||||
protected abstract ResultCode ConvertCharInfoToCoreData(CharInfo charInfo, out CoreData coreData);
|
||||
|
||||
protected abstract ResultCode Append(CharInfo charInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Notification
|
||||
{
|
||||
[Service("notif:s")] // 9.0.0+
|
||||
class INotificationServices : IpcService
|
||||
{
|
||||
public INotificationServices(ServiceCtx context) { }
|
||||
|
||||
[CommandCmif(1000)] // 9.0.0+
|
||||
// GetNotificationCount() -> nn::notification::server::INotificationSystemEventAccessor
|
||||
public ResultCode GetNotificationCount(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new INotificationSystemEventAccessor(context));
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1040)] // 9.0.0+
|
||||
// GetNotificationSendingNotifier() -> nn::notification::server::INotificationSystemEventAccessor
|
||||
public ResultCode GetNotificationSendingNotifier(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new INotificationSystemEventAccessor(context));
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,33 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Notification
|
||||
{
|
||||
[Service("notif:a")] // 9.0.0+
|
||||
class INotificationServicesForApplication : IpcService
|
||||
{
|
||||
public INotificationServicesForApplication(ServiceCtx context) { }
|
||||
|
||||
// Leaving this here since I can never find it: https://switchbrew.org/wiki/Glue_services
|
||||
|
||||
[CommandCmif(520)] // 9.0.0+
|
||||
// ListAlarmSettings(nn::arp::ApplicationCertificate) -> s32 AlarmSettingsCount
|
||||
public ResultCode ListAlarmSettings(ServiceCtx context)
|
||||
{
|
||||
// TO-DO: Currently just returns 0. Should read in an ApplicationCertificate.
|
||||
int alarmSettingsCount = 0;
|
||||
context.ResponseData.Write(alarmSettingsCount);
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1000)] // 9.0.0+
|
||||
// Initialize(PID-descriptor, u64 pid_reserved)
|
||||
public ResultCode Intialize(ServiceCtx context)
|
||||
{
|
||||
ulong pid = context.Request.HandleDesc.PId;
|
||||
context.RequestData.ReadUInt64(); // pid placeholder, zero
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceNotification, new { pid });
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Notification
|
||||
{
|
||||
[Service("notif:s")] // 9.0.0+
|
||||
class INotificationServicesForSystem : IpcService
|
||||
{
|
||||
public INotificationServicesForSystem(ServiceCtx context) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Notification
|
||||
{
|
||||
class INotificationSystemEventAccessor : IpcService
|
||||
{
|
||||
|
||||
private readonly KEvent _getNotificationSendingNotifierEvent;
|
||||
private int _getNotificationSendingNotifierEventHandle;
|
||||
public INotificationSystemEventAccessor(ServiceCtx context) { }
|
||||
|
||||
[CommandCmif(0)] // 9.0.0+
|
||||
// GetNotificationSendingNotifier() -> nn::notification::server::INotificationSystemEventAccessor
|
||||
public ResultCode GetSystemEvent(ServiceCtx context)
|
||||
{
|
||||
if (_getNotificationSendingNotifierEventHandle == 0)
|
||||
{
|
||||
if (context.Process.HandleTable.GenerateHandle(_getNotificationSendingNotifierEvent.ReadableEvent, out _getNotificationSendingNotifierEventHandle) != Result.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_getNotificationSendingNotifierEventHandle);
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ namespace Ryujinx.HLE.Loaders.Processes
|
||||
|
||||
private ulong _latestPid;
|
||||
|
||||
#nullable enable
|
||||
public ProcessResult? ActiveApplication
|
||||
{
|
||||
get
|
||||
@@ -44,6 +45,7 @@ namespace Ryujinx.HLE.Loaders.Processes
|
||||
return value;
|
||||
}
|
||||
}
|
||||
#nullable disable
|
||||
|
||||
public ProcessLoader(Switch device)
|
||||
{
|
||||
|
||||
151
src/Ryujinx.Input.SDL3/NpadHdRumble.cs
Normal file
151
src/Ryujinx.Input.SDL3/NpadHdRumble.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
using Ryujinx.HLE.HOS.Services.Hid;
|
||||
using SDL;
|
||||
using static SDL.SDL3;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Input.SDL3
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages a HID handle of a gamepad to encode and write HD rumble commands for Nin controllers.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<ButtonMappingEntry>(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)
|
||||
|
||||
@@ -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<ButtonMappingEntry>(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)
|
||||
{
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
/// <summary>
|
||||
/// Starts an HD vibration effect on the gamepad if available.
|
||||
/// </summary>
|
||||
/// <param name="left">The vibration data for the left side</param>
|
||||
/// <param name="right">The vibration data for the right side</param>
|
||||
bool HDRumble(VibrationValue left, VibrationValue right)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a rumble effect on the gamepad.
|
||||
/// </summary>
|
||||
|
||||
187
src/Ryujinx.Tests/HLE/CaptureManagerTests.cs
Normal file
187
src/Ryujinx.Tests/HLE/CaptureManagerTests.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
using NUnit.Framework;
|
||||
using Ryujinx.HLE.HOS.Services.Caps;
|
||||
using Ryujinx.HLE.HOS.Services.Caps.Types;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Tests.HLE
|
||||
{
|
||||
public class CaptureManagerTests
|
||||
{
|
||||
private const int ScreenshotWidth = 1280;
|
||||
private const int ScreenshotHeight = 720;
|
||||
private const int BytesPerPixel = 4;
|
||||
|
||||
private const int ScreenshotDataSize = ScreenshotWidth * ScreenshotHeight * BytesPerPixel; // 0x384000
|
||||
private const int PaddedScreenshotDataSize = ScreenshotWidth * 768 * BytesPerPixel; // 0x3C0000
|
||||
|
||||
[Test]
|
||||
public void SaveScreenShotRejectsBufferSmallerThan720p()
|
||||
{
|
||||
using TempSdCard tempSdCard = new();
|
||||
|
||||
CaptureManager captureManager = CreateCaptureManager(tempSdCard.Path);
|
||||
byte[] screenshotData = new byte[ScreenshotDataSize - 1];
|
||||
|
||||
ResultCode result = captureManager.SaveScreenShot(
|
||||
screenshotData,
|
||||
appletResourceUserId: 0,
|
||||
titleId: 0x0100000000001000,
|
||||
out _);
|
||||
|
||||
Assert.That(result, Is.EqualTo(ResultCode.NullInputBuffer));
|
||||
Assert.That(Directory.Exists(Path.Combine(tempSdCard.Path, "Nintendo", "Album")), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SaveScreenShotAcceptsExact720pBuffer()
|
||||
{
|
||||
using TempSdCard tempSdCard = new();
|
||||
|
||||
CaptureManager captureManager = CreateCaptureManager(tempSdCard.Path);
|
||||
byte[] screenshotData = CreateTestPattern(ScreenshotDataSize);
|
||||
|
||||
ResultCode result = captureManager.SaveScreenShot(
|
||||
screenshotData,
|
||||
appletResourceUserId: 0,
|
||||
titleId: 0x0100000000001000,
|
||||
out ApplicationAlbumEntry applicationAlbumEntry);
|
||||
|
||||
string filePath = GetSingleAlbumFile(tempSdCard.Path);
|
||||
|
||||
using SKBitmap bitmap = SKBitmap.Decode(filePath);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(result, Is.EqualTo(ResultCode.Success));
|
||||
Assert.That(bitmap.Width, Is.EqualTo(ScreenshotWidth));
|
||||
Assert.That(bitmap.Height, Is.EqualTo(ScreenshotHeight));
|
||||
Assert.That(applicationAlbumEntry.TitleId, Is.EqualTo(0x0100000000001000));
|
||||
Assert.That(applicationAlbumEntry.AlbumStorage, Is.EqualTo(AlbumStorage.Sd));
|
||||
Assert.That(applicationAlbumEntry.ContentType, Is.EqualTo(ContentType.Screenshot));
|
||||
Assert.That(applicationAlbumEntry.Unknown0x1f, Is.EqualTo(1));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SaveScreenShotAcceptsBufferLargerThan720p()
|
||||
{
|
||||
using TempSdCard tempSdCard = new();
|
||||
|
||||
CaptureManager captureManager = CreateCaptureManager(tempSdCard.Path);
|
||||
byte[] screenshotData = CreateTestPattern(PaddedScreenshotDataSize);
|
||||
|
||||
ResultCode result = captureManager.SaveScreenShot(
|
||||
screenshotData,
|
||||
appletResourceUserId: 0,
|
||||
titleId: 0x0100000000001000,
|
||||
out ApplicationAlbumEntry applicationAlbumEntry);
|
||||
|
||||
string filePath = GetSingleAlbumFile(tempSdCard.Path);
|
||||
|
||||
using SKBitmap bitmap = SKBitmap.Decode(filePath);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(result, Is.EqualTo(ResultCode.Success));
|
||||
Assert.That(bitmap.Width, Is.EqualTo(ScreenshotWidth));
|
||||
Assert.That(bitmap.Height, Is.EqualTo(ScreenshotHeight));
|
||||
Assert.That(applicationAlbumEntry.TitleId, Is.EqualTo(0x0100000000001000));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SaveScreenShotCreatesUniqueFileNamesForRepeatedSaves()
|
||||
{
|
||||
using TempSdCard tempSdCard = new();
|
||||
|
||||
CaptureManager captureManager = CreateCaptureManager(tempSdCard.Path);
|
||||
byte[] screenshotData = CreateTestPattern(ScreenshotDataSize);
|
||||
|
||||
ResultCode firstResult = captureManager.SaveScreenShot(
|
||||
screenshotData,
|
||||
appletResourceUserId: 0,
|
||||
titleId: 0x0100000000001000,
|
||||
out _);
|
||||
|
||||
ResultCode secondResult = captureManager.SaveScreenShot(
|
||||
screenshotData,
|
||||
appletResourceUserId: 0,
|
||||
titleId: 0x0100000000001000,
|
||||
out _);
|
||||
|
||||
string[] files = Directory.GetFiles(
|
||||
Path.Combine(tempSdCard.Path, "Nintendo", "Album"),
|
||||
"*.jpg",
|
||||
SearchOption.AllDirectories);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(firstResult, Is.EqualTo(ResultCode.Success));
|
||||
Assert.That(secondResult, Is.EqualTo(ResultCode.Success));
|
||||
Assert.That(files, Has.Length.EqualTo(2));
|
||||
});
|
||||
}
|
||||
|
||||
private static CaptureManager CreateCaptureManager(string sdCardPath)
|
||||
{
|
||||
CaptureManager captureManager = (CaptureManager)RuntimeHelpers.GetUninitializedObject(typeof(CaptureManager));
|
||||
|
||||
typeof(CaptureManager)
|
||||
.GetField("_sdCardPath", BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
.SetValue(captureManager, sdCardPath);
|
||||
|
||||
return captureManager;
|
||||
}
|
||||
|
||||
private static string GetSingleAlbumFile(string sdCardPath)
|
||||
{
|
||||
string albumPath = Path.Combine(sdCardPath, "Nintendo", "Album");
|
||||
|
||||
string[] files = Directory.GetFiles(albumPath, "*.jpg", SearchOption.AllDirectories);
|
||||
|
||||
Assert.That(files, Has.Length.EqualTo(1));
|
||||
|
||||
return files.Single();
|
||||
}
|
||||
|
||||
private static byte[] CreateTestPattern(int size)
|
||||
{
|
||||
byte[] data = new byte[size];
|
||||
|
||||
int pixelCount = size / BytesPerPixel;
|
||||
|
||||
for (int i = 0; i < pixelCount; i++)
|
||||
{
|
||||
int x = i % ScreenshotWidth;
|
||||
int y = i / ScreenshotWidth;
|
||||
|
||||
data[(i * 4) + 0] = (byte)(x & 0xff);
|
||||
data[(i * 4) + 1] = (byte)(y & 0xff);
|
||||
data[(i * 4) + 2] = 0x80;
|
||||
data[(i * 4) + 3] = 0xff;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private sealed class TempSdCard : IDisposable
|
||||
{
|
||||
public string Path { get; } = System.IO.Path.Combine(
|
||||
TestContext.CurrentContext.WorkDirectory,
|
||||
"sdcard-" + Guid.NewGuid());
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Directory.Exists(Path))
|
||||
{
|
||||
Directory.Delete(Path, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
122
src/Ryujinx.Tests/HLE/MiiDatabaseTests.cs
Normal file
122
src/Ryujinx.Tests/HLE/MiiDatabaseTests.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using System.Reflection;
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.HOS.Services.Mii;
|
||||
using Ryujinx.HLE.HOS.Services.Mii.StaticService;
|
||||
using Ryujinx.HLE.HOS.Services.Mii.Types;
|
||||
|
||||
namespace Ryujinx.Tests.HLE
|
||||
{
|
||||
public class MiiDatabaseTests
|
||||
{
|
||||
[Test]
|
||||
public void UpdateLatestReturnsStoredCharInfo()
|
||||
{
|
||||
DatabaseImpl database = new();
|
||||
StoreData storedData = StoreData.BuildDefault(new UtilityImpl(new TickSource(19200000)), 0);
|
||||
MiiDatabaseManager databaseManager = GetDatabaseManager(database);
|
||||
|
||||
NintendoFigurineDatabase figurineDatabase = new();
|
||||
figurineDatabase.Format();
|
||||
figurineDatabase.Add(storedData);
|
||||
SetFigurineDatabase(databaseManager, figurineDatabase);
|
||||
|
||||
TestDatabaseService service = new(database);
|
||||
|
||||
CharInfo oldCharInfo = new();
|
||||
oldCharInfo.SetFromStoreData(storedData);
|
||||
oldCharInfo.Height--;
|
||||
|
||||
ResultCode result = service.UpdateLatestForTest(oldCharInfo, SourceFlag.Database, out CharInfo newCharInfo);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(result, Is.EqualTo(ResultCode.Success));
|
||||
Assert.That(newCharInfo.CreateId, Is.EqualTo(oldCharInfo.CreateId));
|
||||
Assert.That(newCharInfo.Height, Is.EqualTo(storedData.CoreData.Height));
|
||||
Assert.That(newCharInfo.IsValid(), Is.True);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AppendAddsRegularCharInfoToDatabase()
|
||||
{
|
||||
DatabaseImpl database = new();
|
||||
UtilityImpl utilityImpl = new(new TickSource(19200000));
|
||||
SetUtilityImpl(database, utilityImpl);
|
||||
MiiDatabaseManager databaseManager = GetDatabaseManager(database);
|
||||
SetFigurineDatabase(databaseManager, CreateFormattedDatabase());
|
||||
|
||||
StoreData defaultStoreData = StoreData.BuildDefault(utilityImpl, 0);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(defaultStoreData.CoreData.IsValid(), Is.True);
|
||||
Assert.That(defaultStoreData.IsValidDataCrc(), Is.True);
|
||||
Assert.That(defaultStoreData.IsValidDeviceCrc(), Is.True);
|
||||
Assert.That(defaultStoreData.IsValid(), Is.True);
|
||||
});
|
||||
|
||||
CharInfo charInfo = new();
|
||||
charInfo.SetFromStoreData(defaultStoreData);
|
||||
|
||||
DatabaseSessionMetadata metadata = database.CreateSessionMetadata(new SpecialMiiKeyCode());
|
||||
|
||||
ResultCode result = databaseManager.Append(metadata, utilityImpl, charInfo);
|
||||
|
||||
int count = databaseManager.GetCount(metadata);
|
||||
databaseManager.Get(metadata, 0, out StoreData storedData);
|
||||
|
||||
CoreData expectedCoreData = new();
|
||||
expectedCoreData.SetFromCharInfo(charInfo);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(result, Is.EqualTo(ResultCode.Success));
|
||||
Assert.That(count, Is.EqualTo(1));
|
||||
Assert.That(storedData.IsValid(), Is.True);
|
||||
Assert.That(storedData.CreateId, Is.Not.EqualTo(charInfo.CreateId));
|
||||
Assert.That(storedData.CoreData, Is.EqualTo(expectedCoreData));
|
||||
});
|
||||
}
|
||||
|
||||
private sealed class TestDatabaseService(DatabaseImpl database) : DatabaseServiceImpl(database, true, new SpecialMiiKeyCode())
|
||||
{
|
||||
public ResultCode UpdateLatestForTest(CharInfo oldCharInfo, SourceFlag flag, out CharInfo newCharInfo)
|
||||
{
|
||||
return UpdateLatest(oldCharInfo, flag, out newCharInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private static MiiDatabaseManager GetDatabaseManager(DatabaseImpl database)
|
||||
{
|
||||
return (MiiDatabaseManager)typeof(DatabaseImpl)
|
||||
.GetField("_miiDatabase", BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
.GetValue(database);
|
||||
}
|
||||
|
||||
private static void SetFigurineDatabase(MiiDatabaseManager databaseManager, NintendoFigurineDatabase figurineDatabase)
|
||||
{
|
||||
typeof(MiiDatabaseManager)
|
||||
.GetField("_database", BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
.SetValue(databaseManager, figurineDatabase);
|
||||
}
|
||||
|
||||
private static NintendoFigurineDatabase CreateFormattedDatabase()
|
||||
{
|
||||
NintendoFigurineDatabase figurineDatabase = new();
|
||||
figurineDatabase.Format();
|
||||
|
||||
return figurineDatabase;
|
||||
}
|
||||
|
||||
private static void SetUtilityImpl(DatabaseImpl database, UtilityImpl utilityImpl)
|
||||
{
|
||||
typeof(DatabaseImpl)
|
||||
.GetField("_utilityImpl", BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
.SetValue(database, utilityImpl);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -254,6 +254,7 @@ namespace Ryujinx.Headless
|
||||
Logger.SetEnable(LogLevel.Trace, option.LoggingEnableTrace);
|
||||
Logger.SetEnable(LogLevel.Guest, !option.LoggingDisableGuest);
|
||||
Logger.SetEnable(LogLevel.AccessLog, option.LoggingEnableFsAccessLog);
|
||||
Logger.SetEnable(LogLevel.NetLog, option.LoggingEnableFsAccessLog);
|
||||
|
||||
if (!option.DisableFileLog)
|
||||
{
|
||||
|
||||
@@ -108,6 +108,9 @@ namespace Ryujinx.Headless
|
||||
|
||||
if (NeedsOverride(nameof(LoggingEnableFsAccessLog)))
|
||||
LoggingEnableFsAccessLog = configurationState.Logger.EnableFsAccessLog;
|
||||
|
||||
if (NeedsOverride(nameof(LoggingEnableNetLog)))
|
||||
LoggingEnableNetLog = configurationState.Logger.EnableNetLog;
|
||||
|
||||
if (NeedsOverride(nameof(LoggingGraphicsDebugLevel)))
|
||||
LoggingGraphicsDebugLevel = configurationState.Logger.GraphicsDebugLevel;
|
||||
@@ -370,6 +373,9 @@ namespace Ryujinx.Headless
|
||||
|
||||
[Option("enable-fs-access-logs", Required = false, Default = false, HelpText = "Enables printing FS access log messages.")]
|
||||
public bool LoggingEnableFsAccessLog { get; set; }
|
||||
|
||||
[Option("enable-net-logs", Required = false, Default = false, HelpText = "Enables printing net log messages.")]
|
||||
public bool LoggingEnableNetLog { get; set; }
|
||||
|
||||
[Option("graphics-debug-level", Required = false, Default = GraphicsDebugLevel.None, HelpText = "Change Graphics API debug log level.")]
|
||||
public GraphicsDebugLevel LoggingGraphicsDebugLevel { get; set; }
|
||||
|
||||
@@ -622,15 +622,15 @@ namespace Ryujinx.Ava.Systems
|
||||
// If the GPU has no work and is cancelled, we need to handle that as well.
|
||||
|
||||
WaitHandle.WaitAny(new[] { _gpuDoneEvent, _gpuCancellationTokenSource.Token.WaitHandle });
|
||||
_gpuCancellationTokenSource.Dispose();
|
||||
|
||||
// Waiting for work to be finished before we dispose.
|
||||
|
||||
if (_renderingStarted)
|
||||
{
|
||||
// Waiting for work to be finished before we dispose.
|
||||
Device.Gpu.WaitUntilGpuReady();
|
||||
}
|
||||
|
||||
_gpuDoneEvent.Dispose();
|
||||
_gpuCancellationTokenSource.Dispose();
|
||||
|
||||
DisposeGpu();
|
||||
AppExit?.Invoke(this, EventArgs.Empty);
|
||||
@@ -1095,51 +1095,56 @@ namespace Ryujinx.Ava.Systems
|
||||
|
||||
Device.Gpu.Renderer.RunLoop(() =>
|
||||
{
|
||||
Device.Gpu.SetGpuThread();
|
||||
Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
|
||||
|
||||
_renderer.Window.ChangeVSyncMode(Device.VSyncMode);
|
||||
|
||||
while (_isActive)
|
||||
try
|
||||
{
|
||||
_ticks += _chrono.ElapsedTicks;
|
||||
Device.Gpu.SetGpuThread();
|
||||
Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
|
||||
|
||||
_chrono.Restart();
|
||||
_renderer.Window.ChangeVSyncMode(Device.VSyncMode);
|
||||
|
||||
if (Device.WaitFifo())
|
||||
while (_isActive)
|
||||
{
|
||||
Device.Statistics.RecordFifoStart();
|
||||
Device.ProcessFrame();
|
||||
Device.Statistics.RecordFifoEnd();
|
||||
}
|
||||
_ticks += _chrono.ElapsedTicks;
|
||||
|
||||
while (Device.ConsumeFrameAvailable())
|
||||
{
|
||||
if (!_renderingStarted)
|
||||
_chrono.Restart();
|
||||
|
||||
if (Device.WaitFifo())
|
||||
{
|
||||
_renderingStarted = true;
|
||||
_viewModel.SwitchToRenderer(false);
|
||||
InitStatus();
|
||||
Device.Statistics.RecordFifoStart();
|
||||
Device.ProcessFrame();
|
||||
Device.Statistics.RecordFifoEnd();
|
||||
}
|
||||
|
||||
Device.PresentFrame(() => (RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.SwapBuffers());
|
||||
}
|
||||
while (Device.ConsumeFrameAvailable())
|
||||
{
|
||||
if (!_renderingStarted)
|
||||
{
|
||||
_renderingStarted = true;
|
||||
_viewModel.SwitchToRenderer(false);
|
||||
InitStatus();
|
||||
}
|
||||
|
||||
if (_ticks >= _ticksPerFrame)
|
||||
{
|
||||
UpdateStatus();
|
||||
Device.PresentFrame(() =>
|
||||
(RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.SwapBuffers());
|
||||
}
|
||||
|
||||
if (_ticks >= _ticksPerFrame)
|
||||
{
|
||||
UpdateStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure all commands in the run loop are fully executed before leaving the loop.
|
||||
if (Device.Gpu.Renderer is ThreadedRenderer threaded)
|
||||
finally
|
||||
{
|
||||
Logger.Info?.PrintMsg(LogClass.Gpu, "Flushing threaded commands...");
|
||||
threaded.FlushThreadedCommands();
|
||||
Logger.Info?.PrintMsg(LogClass.Gpu, "Flushed!");
|
||||
// Make sure all commands in the run loop are fully executed before leaving the loop.
|
||||
if (Device.Gpu.Renderer is ThreadedRenderer threaded)
|
||||
{
|
||||
Logger.Info?.PrintMsg(LogClass.Gpu, "Flushing threaded commands...");
|
||||
threaded.FlushThreadedCommands();
|
||||
Logger.Info?.PrintMsg(LogClass.Gpu, "Flushed!");
|
||||
}
|
||||
_gpuDoneEvent.Set();
|
||||
}
|
||||
|
||||
_gpuDoneEvent.Set();
|
||||
});
|
||||
|
||||
(RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(true);
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
/// <summary>
|
||||
/// The current version of the file format
|
||||
/// </summary>
|
||||
public const int CurrentVersion = 71;
|
||||
public const int CurrentVersion = 72;
|
||||
|
||||
/// <summary>
|
||||
/// Version of the configuration file format
|
||||
@@ -113,6 +113,11 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
/// Enables printing FS access log messages
|
||||
/// </summary>
|
||||
public bool LoggingEnableFsAccessLog { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables printing network log messages
|
||||
/// </summary>
|
||||
public bool LoggingEnableNetLog { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables log messages from Avalonia
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media;
|
||||
using Gommon;
|
||||
using Ryujinx.Ava.Systems.Configuration.System;
|
||||
using Ryujinx.Ava.Systems.Configuration.UI;
|
||||
@@ -68,6 +68,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
Logger.EnableTrace.Value = cff.LoggingEnableTrace;
|
||||
Logger.EnableGuest.Value = cff.LoggingEnableGuest;
|
||||
Logger.EnableFsAccessLog.Value = cff.LoggingEnableFsAccessLog;
|
||||
Logger.EnableNetLog.Value = cff.LoggingEnableNetLog;
|
||||
Logger.FilteredClasses.Value = cff.LoggingFilteredClasses;
|
||||
Logger.GraphicsDebugLevel.Value = cff.LoggingGraphicsDebugLevel;
|
||||
|
||||
|
||||
@@ -257,6 +257,11 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
/// Enables printing FS access log messages
|
||||
/// </summary>
|
||||
public ReactiveObject<bool> EnableFsAccessLog { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables printing network log messages
|
||||
/// </summary>
|
||||
public ReactiveObject<bool> EnableNetLog { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables log messages from Avalonia
|
||||
@@ -289,6 +294,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
EnableTrace = new ReactiveObject<bool>();
|
||||
EnableGuest = new ReactiveObject<bool>();
|
||||
EnableFsAccessLog = new ReactiveObject<bool>();
|
||||
EnableNetLog = new ReactiveObject<bool>();
|
||||
EnableAvaloniaLog = new ReactiveObject<bool>();
|
||||
FilteredClasses = new ReactiveObject<LogClass[]>();
|
||||
EnableFileLog = new ReactiveObject<bool>();
|
||||
|
||||
@@ -47,6 +47,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
LoggingEnableTrace = Logger.EnableTrace,
|
||||
LoggingEnableGuest = Logger.EnableGuest,
|
||||
LoggingEnableFsAccessLog = Logger.EnableFsAccessLog,
|
||||
LoggingEnableNetLog = Logger.EnableNetLog,
|
||||
LoggingEnableAvalonia = Logger.EnableAvaloniaLog,
|
||||
LoggingFilteredClasses = Logger.FilteredClasses,
|
||||
LoggingGraphicsDebugLevel = Logger.GraphicsDebugLevel,
|
||||
@@ -176,6 +177,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
Logger.EnableTrace.Value = false;
|
||||
Logger.EnableGuest.Value = true;
|
||||
Logger.EnableFsAccessLog.Value = false;
|
||||
Logger.EnableNetLog.Value = false;
|
||||
Logger.EnableAvaloniaLog.Value = false;
|
||||
Logger.FilteredClasses.Value = [];
|
||||
Logger.GraphicsDebugLevel.Value = GraphicsDebugLevel.None;
|
||||
|
||||
@@ -26,6 +26,8 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
(_, e) => Logger.SetEnable(LogLevel.Guest, e.NewValue);
|
||||
ConfigurationState.Instance.Logger.EnableFsAccessLog.Event +=
|
||||
(_, e) => Logger.SetEnable(LogLevel.AccessLog, e.NewValue);
|
||||
ConfigurationState.Instance.Logger.EnableNetLog.Event +=
|
||||
(_, e) => Logger.SetEnable(LogLevel.NetLog, e.NewValue);
|
||||
|
||||
ConfigurationState.Instance.Logger.FilteredClasses.Event += (_, e) =>
|
||||
{
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Ryujinx.Ava.Systems.PlayReport
|
||||
|
||||
private static readonly Lazy<Analyzer> _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(
|
||||
|
||||
@@ -8,6 +8,12 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
internal partial class Win32NativeInterop
|
||||
{
|
||||
internal const int GWLP_WNDPROC = -4;
|
||||
internal const int GWL_STYLE = -16;
|
||||
internal const int GWL_EXSTYLE = -20;
|
||||
|
||||
internal const uint WS_OVERLAPPEDWINDOW = 0x00CF0000;
|
||||
internal const uint WS_POPUP = 0x80000000;
|
||||
internal const uint WS_VISIBLE = 0x10000000;
|
||||
|
||||
[Flags]
|
||||
public enum ClassStyles : uint
|
||||
@@ -107,9 +113,29 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
nint hInstance,
|
||||
nint lpParam);
|
||||
|
||||
[LibraryImport("user32.dll", SetLastError = true, EntryPoint = "GetWindowLongPtrW")]
|
||||
public static partial nint GetWindowLongPtrW(nint hWnd, int nIndex);
|
||||
|
||||
[LibraryImport("user32.dll", SetLastError = true)]
|
||||
public static partial nint SetWindowLongPtrW(nint hWnd, int nIndex, nint value);
|
||||
|
||||
[LibraryImport("user32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool SetWindowPos(
|
||||
nint hWnd,
|
||||
nint hWndInsertAfter,
|
||||
int x,
|
||||
int y,
|
||||
int cx,
|
||||
int cy,
|
||||
uint uFlags);
|
||||
|
||||
internal const uint SWP_NOZORDER = 0x0004;
|
||||
internal const uint SWP_NOACTIVATE = 0x0010;
|
||||
internal const uint SWP_FRAMECHANGED = 0x0020;
|
||||
internal const uint SWP_NOMOVE = 0x0002;
|
||||
internal const uint SWP_NOSIZE = 0x0001;
|
||||
|
||||
[LibraryImport("user32.dll", SetLastError = true)]
|
||||
public static partial ushort GetAsyncKeyState(int nVirtKey);
|
||||
|
||||
|
||||
@@ -574,7 +574,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
if (Devices.Any(controller => controller.Name == name))
|
||||
{
|
||||
controllerNumber++;
|
||||
name = GetGamepadName(gamepad, controllerNumber);
|
||||
name = GetUniqueGamepadName(gamepad, ref controllerNumber);
|
||||
}
|
||||
|
||||
return name;
|
||||
|
||||
@@ -5,6 +5,7 @@ using Avalonia.Input;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Threading;
|
||||
using System.Runtime.Versioning;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using DynamicData;
|
||||
@@ -2014,7 +2015,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
LastFullscreenToggle = Environment.TickCount64;
|
||||
|
||||
if (WindowState is not WindowState.Normal)
|
||||
if (WindowState is WindowState.FullScreen)
|
||||
{
|
||||
WindowState = WindowState.Normal;
|
||||
Window.TitleBar.ExtendsContentIntoTitleBar = !ConfigurationState.Instance.ShowOldUI;
|
||||
@@ -2023,21 +2024,74 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
ShowMenuAndStatusBar = true;
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
RestoreWindowFromFullscreen();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WindowState = WindowState.FullScreen;
|
||||
Window.TitleBar.ExtendsContentIntoTitleBar = true;
|
||||
|
||||
if (IsGameRunning)
|
||||
{
|
||||
ShowMenuAndStatusBar = false;
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
MakeWindowFullscreen();
|
||||
}
|
||||
else
|
||||
{
|
||||
WindowState = WindowState.FullScreen;
|
||||
}
|
||||
}
|
||||
|
||||
IsFullScreen = WindowState is WindowState.FullScreen;
|
||||
}
|
||||
|
||||
private nint _savedWindowStyle;
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
private void MakeWindowFullscreen()
|
||||
{
|
||||
nint hwnd = Window.TryGetPlatformHandle()?.Handle ?? nint.Zero;
|
||||
if (hwnd == nint.Zero) return;
|
||||
|
||||
// Save current style and placement
|
||||
_savedWindowStyle = Win32NativeInterop.GetWindowLongPtrW(hwnd, Win32NativeInterop.GWL_STYLE);
|
||||
|
||||
// Remove window chrome: WS_OVERLAPPEDWINDOW -> WS_POPUP | WS_VISIBLE
|
||||
Win32NativeInterop.SetWindowLongPtrW(hwnd, Win32NativeInterop.GWL_STYLE,
|
||||
unchecked((nint)(Win32NativeInterop.WS_POPUP | Win32NativeInterop.WS_VISIBLE)));
|
||||
|
||||
Avalonia.Platform.Screen? screen = Window.Screens.ScreenFromVisual(Window);
|
||||
int w = screen?.Bounds.Width ?? 0;
|
||||
int h = screen?.Bounds.Height ?? 0;
|
||||
|
||||
Win32NativeInterop.SetWindowPos(hwnd, nint.Zero, 0, 0, w, h,
|
||||
Win32NativeInterop.SWP_NOZORDER | Win32NativeInterop.SWP_NOACTIVATE | Win32NativeInterop.SWP_FRAMECHANGED);
|
||||
|
||||
WindowState = WindowState.FullScreen;
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
private void RestoreWindowFromFullscreen()
|
||||
{
|
||||
nint hwnd = Window.TryGetPlatformHandle()?.Handle ?? nint.Zero;
|
||||
if (hwnd == nint.Zero) return;
|
||||
|
||||
// Restore original window style
|
||||
Win32NativeInterop.SetWindowLongPtrW(hwnd, Win32NativeInterop.GWL_STYLE, _savedWindowStyle);
|
||||
|
||||
Win32NativeInterop.SetWindowPos(hwnd, nint.Zero, 0, 0, 0, 0,
|
||||
Win32NativeInterop.SWP_NOZORDER | Win32NativeInterop.SWP_NOACTIVATE |
|
||||
Win32NativeInterop.SWP_FRAMECHANGED | Win32NativeInterop.SWP_NOMOVE | Win32NativeInterop.SWP_NOSIZE);
|
||||
|
||||
}
|
||||
|
||||
public static void SaveConfig()
|
||||
{
|
||||
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||
|
||||
@@ -273,6 +273,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
public bool EnableTrace { get; set; }
|
||||
public bool EnableGuest { get; set; }
|
||||
public bool EnableFsAccessLog { get; set; }
|
||||
public bool EnableNetLog { get; set; }
|
||||
public bool EnableAvaloniaLog { get; set; }
|
||||
public bool EnableDebug { get; set; }
|
||||
public bool IsOpenAlEnabled { get; set; }
|
||||
@@ -725,6 +726,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
EnableGuest = config.Logger.EnableGuest;
|
||||
EnableDebug = config.Logger.EnableDebug;
|
||||
EnableFsAccessLog = config.Logger.EnableFsAccessLog;
|
||||
EnableNetLog = config.Logger.EnableNetLog;
|
||||
EnableAvaloniaLog = config.Logger.EnableAvaloniaLog;
|
||||
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
|
||||
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
|
||||
@@ -848,6 +850,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
config.Logger.EnableGuest.Value = EnableGuest;
|
||||
config.Logger.EnableDebug.Value = EnableDebug;
|
||||
config.Logger.EnableFsAccessLog.Value = EnableFsAccessLog;
|
||||
config.Logger.EnableNetLog.Value = EnableNetLog;
|
||||
config.Logger.EnableAvaloniaLog.Value = EnableAvaloniaLog;
|
||||
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
|
||||
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
|
||||
|
||||
@@ -70,6 +70,10 @@
|
||||
ToolTip.Tip="{ext:Locale FileAccessLogTooltip}">
|
||||
<TextBlock Text="{ext:Locale SettingsTabLoggingEnableFsAccessLogs}" />
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding EnableNetLog}"
|
||||
ToolTip.Tip="{ext:Locale NetLogTooltip}">
|
||||
<TextBlock Text="{ext:Locale SettingsTabLoggingEnableNetLogs}" />
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding EnableDebug}"
|
||||
ToolTip.Tip="{ext:Locale DebugLogTooltip}">
|
||||
<TextBlock Text="{ext:Locale SettingsTabLoggingEnableDebugLogs}" />
|
||||
|
||||
Reference in New Issue
Block a user