diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml
index e7b961602..e7052a6f6 100644
--- a/.forgejo/workflows/build.yml
+++ b/.forgejo/workflows/build.yml
@@ -1,7 +1,7 @@
name: Build PR
on:
- pull_request_target:
+ pull_request:
branches: [ master ]
paths:
- '**'
@@ -63,7 +63,7 @@ jobs:
- name: Change config filename
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
- if: forgejo.event_name == 'pull_request_target'
+ if: forgejo.event_name == 'pull_request'
- name: 'Cache: ~/.nuget/packages'
uses: actions/cache@v5
@@ -85,7 +85,7 @@ jobs:
- name: Publish Ryujinx
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ steps.version_info.outputs.result }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained
- if: forgejo.event_name == 'pull_request_target'
+ if: forgejo.event_name == 'pull_request'
- name: Packing Windows builds
if: contains(matrix.platform.name, 'win')
@@ -98,10 +98,10 @@ jobs:
with:
name: ryujinx-${{ matrix.configuration }}-${{ steps.version_info.outputs.result }}+${{ steps.version_info.outputs.git_short_hash }}-${{ matrix.platform.zip_os_name }}
path: artifact
- if: forgejo.event_name == 'pull_request_target' && contains(matrix.platform.name, 'win')
+ if: forgejo.event_name == 'pull_request' && contains(matrix.platform.name, 'win')
- name: Build AppImage
- if: forgejo.event_name == 'pull_request_target' && contains(matrix.platform.name, 'linux')
+ if: forgejo.event_name == 'pull_request' && contains(matrix.platform.name, 'linux')
run: |
chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh
@@ -134,7 +134,7 @@ jobs:
- name: Upload Ryujinx AppImage artifact
uses: actions/upload-artifact@v5
- if: forgejo.event_name == 'pull_request_target' && contains(matrix.platform.name, 'linux')
+ if: forgejo.event_name == 'pull_request' && contains(matrix.platform.name, 'linux')
with:
name: ryujinx-${{ matrix.configuration }}-${{ steps.version_info.outputs.result }}+${{ steps.version_info.outputs.git_short_hash }}-${{ matrix.platform.zip_os_name }}-AppImage
path: publish_appimage
@@ -182,7 +182,7 @@ jobs:
- name: Change config filename
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
- if: forgejo.event_name == 'pull_request_target'
+ if: forgejo.event_name == 'pull_request'
- name: 'Cache: ~/.nuget/packages'
uses: actions/cache@v5
@@ -201,47 +201,4 @@ jobs:
with:
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_target'
-
- post_comment:
- name: Post comment linking uploaded artifacts
- runs-on: ubuntu-latest
- needs:
- - build
- - build_macos
- steps:
- - uses: actions/github-script@v9
- env:
- COMMENTER_TOKEN: ${{ secrets.COMMENTER_TOKEN }}
- with:
- github-token: 'n/a'
- script: |
- const forgejo = getOctokit(process.env.COMMENTER_TOKEN, {
- baseUrl: 'https://git.ryujinx.app/api/v1'
- });
-
- const {owner, repo} = context.repo;
- const run_id = ${{ env.FORGEJO_RUN_ID }};
-
- const issue_number = ${{ forgejo.event.pull_request.number }};
- core.info(`Using run ID ${run_id} from pull request ${issue_number}`);
-
- const {data: {artifacts}} = await forgejo.rest.actions.listWorkflowRunArtifacts({owner, repo, run_id});
- if (artifacts == undefined || !artifacts.length) {
- return core.error(`No artifacts found for run ID`);
- }
- 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});
- }
+ if: forgejo.event_name == 'pull_request'
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 7c94ffe24..4466f2777 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -8,7 +8,7 @@
-
+
@@ -64,4 +64,4 @@
-
+
\ No newline at end of file
diff --git a/assets/Locales/Root.json b/assets/Locales/Root.json
index 0b1115cdc..eee476519 100644
--- a/assets/Locales/Root.json
+++ b/assets/Locales/Root.json
@@ -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": {
diff --git a/src/Ryujinx.Common/Logging/LogClass.cs b/src/Ryujinx.Common/Logging/LogClass.cs
index 7c6810599..0e02cfcc2 100644
--- a/src/Ryujinx.Common/Logging/LogClass.cs
+++ b/src/Ryujinx.Common/Logging/LogClass.cs
@@ -51,6 +51,7 @@ namespace Ryujinx.Common.Logging
ServiceNgct,
ServiceNifm,
ServiceNim,
+ ServiceNotification,
ServiceNs,
ServiceNsd,
ServiceNtc,
diff --git a/src/Ryujinx.Common/Logging/LogLevel.cs b/src/Ryujinx.Common/Logging/LogLevel.cs
index 282b07111..e8778191a 100644
--- a/src/Ryujinx.Common/Logging/LogLevel.cs
+++ b/src/Ryujinx.Common/Logging/LogLevel.cs
@@ -12,6 +12,7 @@ namespace Ryujinx.Common.Logging
Error,
Guest,
AccessLog,
+ NetLog,
Notice,
Trace,
}
diff --git a/src/Ryujinx.Common/Logging/Logger.cs b/src/Ryujinx.Common/Logging/Logger.cs
index b5450c94b..788d2ab3d 100644
--- a/src/Ryujinx.Common/Logging/Logger.cs
+++ b/src/Ryujinx.Common/Logging/Logger.cs
@@ -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;
diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
index 12ec23c8b..9b1df80dc 100644
--- a/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
@@ -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);
}
diff --git a/src/Ryujinx.Graphics.OpenGL/PersistentBuffers.cs b/src/Ryujinx.Graphics.OpenGL/PersistentBuffers.cs
index d5c02f4df..b77ac5f17 100644
--- a/src/Ryujinx.Graphics.OpenGL/PersistentBuffers.cs
+++ b/src/Ryujinx.Graphics.OpenGL/PersistentBuffers.cs
@@ -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);
}
}
diff --git a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs
index e58e6f2b9..5b1e63e3b 100644
--- a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs
@@ -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());
}
}
diff --git a/src/Ryujinx.Graphics.OpenGL/Program.cs b/src/Ryujinx.Graphics.OpenGL/Program.cs
index 608a03451..cb9933c10 100644
--- a/src/Ryujinx.Graphics.OpenGL/Program.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Program.cs
@@ -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;
}
diff --git a/src/Ryujinx.Graphics.OpenGL/Queries/BufferedQuery.cs b/src/Ryujinx.Graphics.OpenGL/Queries/BufferedQuery.cs
index f39829923..c9dbcdcf2 100644
--- a/src/Ryujinx.Graphics.OpenGL/Queries/BufferedQuery.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Queries/BufferedQuery.cs
@@ -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()
diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs
index fc02ea172..44c4d133a 100644
--- a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs
@@ -44,6 +44,22 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
return ResultCode.Success;
}
+
+ [CommandCmif(1)]
+ // PushOutData(object)
+ public ResultCode PushOutData(ServiceCtx context)
+ {
+ IStorage appletData = GetObject(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
diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs b/src/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs
index f47c663ed..651f92986 100644
--- a/src/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs
@@ -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(),
+ 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(),
+ 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(),
- 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(),
- 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)
diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs b/src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs
index 0723b57cc..2ccb7c598 100644
--- a/src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs
@@ -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();
diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs
index 71d1623f3..2f764e99f 100644
--- a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs
@@ -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(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;
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnRyu/LdnMasterProxyClient.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnRyu/LdnMasterProxyClient.cs
index f93b1c4cc..37fa722b7 100644
--- a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnRyu/LdnMasterProxyClient.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnRyu/LdnMasterProxyClient.cs
@@ -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();
diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Station.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Station.cs
index fa43f789e..b589c56a4 100644
--- a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Station.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Station.cs
@@ -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
diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/DatabaseImpl.cs b/src/Ryujinx.HLE/HOS/Services/Mii/DatabaseImpl.cs
index 4bb736992..cd2115132 100644
--- a/src/Ryujinx.HLE/HOS/Services/Mii/DatabaseImpl.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Mii/DatabaseImpl.cs
@@ -81,8 +81,10 @@ namespace Ryujinx.HLE.HOS.Services.Mii
return ResultCode.Success;
}
- public ResultCode UpdateLatest(DatabaseSessionMetadata metadata, IStoredData oldMiiData, SourceFlag flag, IStoredData newMiiData) where T : unmanaged
+ public ResultCode UpdateLatest(DatabaseSessionMetadata metadata, T oldMiiData, SourceFlag flag, out T newMiiData) where T : unmanaged, IStoredData
{
+ 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();
diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs b/src/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs
index 356d42a85..f5a173d8e 100644
--- a/src/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs
@@ -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))
diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/DatabaseServiceImpl.cs b/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/DatabaseServiceImpl.cs
index fc12e2533..c12517430 100644
--- a/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/DatabaseServiceImpl.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/DatabaseServiceImpl.cs
@@ -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);
+ }
}
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/IDatabaseService.cs b/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/IDatabaseService.cs
index 1a1c20d6e..3f9fad4fb 100644
--- a/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/IDatabaseService.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/IDatabaseService.cs
@@ -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();
+
+ return Append(charInfo);
+ }
+
private Span 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);
}
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Notification/INotificationServices.cs b/src/Ryujinx.HLE/HOS/Services/Notification/INotificationServices.cs
new file mode 100644
index 000000000..e44983fce
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Notification/INotificationServices.cs
@@ -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;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForApplication.cs b/src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForApplication.cs
index 29f8bfa85..498fc52fb 100644
--- a/src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForApplication.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForApplication.cs
@@ -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;
+ }
}
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForSystem.cs b/src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForSystem.cs
deleted file mode 100644
index c5946be84..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForSystem.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Notification
-{
- [Service("notif:s")] // 9.0.0+
- class INotificationServicesForSystem : IpcService
- {
- public INotificationServicesForSystem(ServiceCtx context) { }
- }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Notification/INotificationSystemEventAccessor.cs b/src/Ryujinx.HLE/HOS/Services/Notification/INotificationSystemEventAccessor.cs
new file mode 100644
index 000000000..8cca7cc6e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Notification/INotificationSystemEventAccessor.cs
@@ -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;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs
index e0edd2df5..900703f6e 100644
--- a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs
+++ b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs
@@ -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)
{
diff --git a/src/Ryujinx.Tests/HLE/CaptureManagerTests.cs b/src/Ryujinx.Tests/HLE/CaptureManagerTests.cs
new file mode 100644
index 000000000..8dd2b070f
--- /dev/null
+++ b/src/Ryujinx.Tests/HLE/CaptureManagerTests.cs
@@ -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);
+ }
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.Tests/HLE/MiiDatabaseTests.cs b/src/Ryujinx.Tests/HLE/MiiDatabaseTests.cs
new file mode 100644
index 000000000..b1a20bb93
--- /dev/null
+++ b/src/Ryujinx.Tests/HLE/MiiDatabaseTests.cs
@@ -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);
+ }
+ }
+}
diff --git a/src/Ryujinx/Headless/HeadlessRyujinx.cs b/src/Ryujinx/Headless/HeadlessRyujinx.cs
index bba505dbb..a6ff5da57 100644
--- a/src/Ryujinx/Headless/HeadlessRyujinx.cs
+++ b/src/Ryujinx/Headless/HeadlessRyujinx.cs
@@ -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)
{
diff --git a/src/Ryujinx/Headless/Options.cs b/src/Ryujinx/Headless/Options.cs
index 382294cf7..a2dd6e4d3 100644
--- a/src/Ryujinx/Headless/Options.cs
+++ b/src/Ryujinx/Headless/Options.cs
@@ -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; }
diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs
index f0fafb4e0..0b451eacb 100644
--- a/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs
+++ b/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs
@@ -17,7 +17,7 @@ namespace Ryujinx.Ava.Systems.Configuration
///
/// The current version of the file format
///
- public const int CurrentVersion = 71;
+ public const int CurrentVersion = 72;
///
/// Version of the configuration file format
@@ -113,6 +113,11 @@ namespace Ryujinx.Ava.Systems.Configuration
/// Enables printing FS access log messages
///
public bool LoggingEnableFsAccessLog { get; set; }
+
+ ///
+ /// Enables printing network log messages
+ ///
+ public bool LoggingEnableNetLog { get; set; }
///
/// Enables log messages from Avalonia
diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs b/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs
index 163b7e98f..90a045a67 100644
--- a/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs
+++ b/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs
@@ -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;
diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs b/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs
index 2b4c8f991..7775125d4 100644
--- a/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs
+++ b/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs
@@ -257,6 +257,11 @@ namespace Ryujinx.Ava.Systems.Configuration
/// Enables printing FS access log messages
///
public ReactiveObject EnableFsAccessLog { get; private set; }
+
+ ///
+ /// Enables printing network log messages
+ ///
+ public ReactiveObject EnableNetLog { get; private set; }
///
/// Enables log messages from Avalonia
@@ -289,6 +294,7 @@ namespace Ryujinx.Ava.Systems.Configuration
EnableTrace = new ReactiveObject();
EnableGuest = new ReactiveObject();
EnableFsAccessLog = new ReactiveObject();
+ EnableNetLog = new ReactiveObject();
EnableAvaloniaLog = new ReactiveObject();
FilteredClasses = new ReactiveObject();
EnableFileLog = new ReactiveObject();
diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationState.cs b/src/Ryujinx/Systems/Configuration/ConfigurationState.cs
index 0e2f6aaec..e4874963d 100644
--- a/src/Ryujinx/Systems/Configuration/ConfigurationState.cs
+++ b/src/Ryujinx/Systems/Configuration/ConfigurationState.cs
@@ -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;
diff --git a/src/Ryujinx/Systems/Configuration/LoggerModule.cs b/src/Ryujinx/Systems/Configuration/LoggerModule.cs
index e3d08ab8c..29c38b3d2 100644
--- a/src/Ryujinx/Systems/Configuration/LoggerModule.cs
+++ b/src/Ryujinx/Systems/Configuration/LoggerModule.cs
@@ -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) =>
{
diff --git a/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs b/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs
index 0ff7f5fde..adfa899ed 100644
--- a/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs
+++ b/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs
@@ -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);
diff --git a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs
index e5f085e0f..51229af72 100644
--- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs
+++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs
@@ -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;
diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs
index 57fd825b3..e488495d6 100644
--- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs
+++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs
@@ -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);
diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs
index abb284960..6904d4ebc 100644
--- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs
+++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs
@@ -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;
diff --git a/src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml
index fa3b4e866..325c4f599 100644
--- a/src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml
+++ b/src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml
@@ -70,6 +70,10 @@
ToolTip.Tip="{ext:Locale FileAccessLogTooltip}">
+
+
+