mirror of
https://git.naxdy.org/Mirror/Ryujinx.git
synced 2025-01-12 13:49:12 +00:00
Merge branch 'master' into 4304-missing-updates
This commit is contained in:
commit
c0b2799b44
62 changed files with 1666 additions and 89 deletions
|
@ -18,12 +18,13 @@
|
|||
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
||||
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
||||
<PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.3.0-beta.4" />
|
||||
<PackageVersion Include="LibHac" Version="0.18.0" />
|
||||
<PackageVersion Include="LibHac" Version="0.19.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
|
||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||
<PackageVersion Include="NetCoreServer" Version="7.0.0" />
|
||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||
<PackageVersion Include="OpenTK.Core" Version="4.7.7" />
|
||||
|
|
|
@ -141,4 +141,5 @@ See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](distribution/legal/THIRDPARTY
|
|||
|
||||
- [LibHac](https://github.com/Thealexbarney/LibHac) is used for our file-system.
|
||||
- [AmiiboAPI](https://www.amiiboapi.com) is used in our Amiibo emulation.
|
||||
- [ldn_mitm](https://github.com/spacemeowx2/ldn_mitm) is used for one of our available multiplayer modes.
|
||||
- [ShellLink](https://github.com/securifybv/ShellLink) is used for Windows shortcut generation.
|
||||
|
|
|
@ -190,6 +190,7 @@ namespace Ryujinx.Ava
|
|||
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel;
|
||||
ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event += UpdateColorSpacePassthrough;
|
||||
|
||||
ConfigurationState.Instance.System.EnableInternetAccess.Event += UpdateEnableInternetAccessState;
|
||||
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState;
|
||||
ConfigurationState.Instance.Multiplayer.Mode.Event += UpdateMultiplayerModeState;
|
||||
|
||||
|
@ -408,6 +409,11 @@ namespace Ryujinx.Ava
|
|||
});
|
||||
}
|
||||
|
||||
private void UpdateEnableInternetAccessState(object sender, ReactiveEventArgs<bool> e)
|
||||
{
|
||||
Device.Configuration.EnableInternetAccess = e.NewValue;
|
||||
}
|
||||
|
||||
private void UpdateLanInterfaceIdState(object sender, ReactiveEventArgs<string> e)
|
||||
{
|
||||
Device.Configuration.MultiplayerLanInterfaceId = e.NewValue;
|
||||
|
|
|
@ -650,7 +650,7 @@
|
|||
"UserEditorTitle": "Edit User",
|
||||
"UserEditorTitleCreate": "Create User",
|
||||
"SettingsTabNetworkInterface": "Network Interface:",
|
||||
"NetworkInterfaceTooltip": "The network interface used for LAN features",
|
||||
"NetworkInterfaceTooltip": "The network interface used for LAN/LDN features",
|
||||
"NetworkInterfaceDefault": "Default",
|
||||
"PackagingShaders": "Packaging Shaders",
|
||||
"AboutChangelogButton": "View Changelog on GitHub",
|
||||
|
|
|
@ -173,7 +173,7 @@ namespace Ryujinx.Ava.Common
|
|||
string extension = Path.GetExtension(titleFilePath).ToLower();
|
||||
if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci")
|
||||
{
|
||||
PartitionFileSystem pfs;
|
||||
IFileSystem pfs;
|
||||
|
||||
if (extension == ".xci")
|
||||
{
|
||||
|
@ -181,7 +181,9 @@ namespace Ryujinx.Ava.Common
|
|||
}
|
||||
else
|
||||
{
|
||||
pfs = new PartitionFileSystem(file.AsStorage());
|
||||
var pfsTemp = new PartitionFileSystem();
|
||||
pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
|
||||
pfs = pfsTemp;
|
||||
}
|
||||
|
||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||
|
|
|
@ -126,7 +126,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
{
|
||||
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath);
|
||||
|
||||
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
|
||||
PartitionFileSystem partitionFileSystem = new();
|
||||
partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
||||
|
||||
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
||||
|
||||
|
@ -232,7 +233,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
|
||||
using FileStream containerFile = File.OpenRead(path);
|
||||
|
||||
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
|
||||
PartitionFileSystem partitionFileSystem = new();
|
||||
partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
||||
bool containsDownloadableContent = false;
|
||||
|
||||
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
||||
|
|
|
@ -170,7 +170,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
|
||||
try
|
||||
{
|
||||
(Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(VirtualFileSystem, new PartitionFileSystem(file.AsStorage()), TitleId.ToString("x16"), 0);
|
||||
var pfs = new PartitionFileSystem();
|
||||
pfs.Initialize(file.AsStorage()).ThrowIfFailure();
|
||||
(Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(VirtualFileSystem, pfs, TitleId.ToString("x16"), 0);
|
||||
|
||||
if (controlNca != null && patchNca != null)
|
||||
{
|
||||
|
|
|
@ -3,5 +3,6 @@
|
|||
public enum MultiplayerMode
|
||||
{
|
||||
Disabled,
|
||||
LdnMitm,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,13 @@ namespace Ryujinx.Common.SystemInfo
|
|||
{
|
||||
internal MacOSSystemInfo()
|
||||
{
|
||||
if (SysctlByName("kern.osversion", out string buildRevision) != 0)
|
||||
{
|
||||
buildRevision = "Unknown Build";
|
||||
}
|
||||
|
||||
OsDescription = $"macOS {Environment.OSVersion.Version} ({buildRevision}) ({RuntimeInformation.OSArchitecture})";
|
||||
|
||||
string cpuName = GetCpuidCpuName();
|
||||
|
||||
if (cpuName == null && SysctlByName("machdep.cpu.brand_string", out cpuName) != 0)
|
||||
|
|
|
@ -74,5 +74,10 @@ namespace Ryujinx.Common.Utilities
|
|||
{
|
||||
return ConvertIpv4Address(IPAddress.Parse(ipAddress));
|
||||
}
|
||||
|
||||
public static IPAddress ConvertUint(uint ipAddress)
|
||||
{
|
||||
return new IPAddress(new byte[] { (byte)((ipAddress >> 24) & 0xFF), (byte)((ipAddress >> 16) & 0xFF), (byte)((ipAddress >> 8) & 0xFF), (byte)(ipAddress & 0xFF) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -766,7 +766,10 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
flags |= offset == TexOffset.Ptp ? TextureFlags.Offsets : TextureFlags.Offset;
|
||||
}
|
||||
|
||||
if (!hasDepthCompare)
|
||||
{
|
||||
sourcesList.Add(Const((int)component));
|
||||
}
|
||||
|
||||
Operand[] sources = sourcesList.ToArray();
|
||||
Operand[] dests = new Operand[BitOperations.PopCount((uint)componentMask)];
|
||||
|
|
|
@ -198,7 +198,7 @@ namespace Ryujinx.HLE.FileSystem
|
|||
{
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
|
||||
fs.OpenFile(ref ncaFile.Ref, ncaPath.FullPath.ToU8Span(), OpenMode.Read);
|
||||
fs.OpenFile(ref ncaFile.Ref, ncaPath.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
var nca = new Nca(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
|
||||
if (nca.Header.ContentType != NcaContentType.Meta)
|
||||
{
|
||||
|
@ -210,7 +210,7 @@ namespace Ryujinx.HLE.FileSystem
|
|||
using var pfs0 = nca.OpenFileSystem(0, integrityCheckLevel);
|
||||
using var cnmtFile = new UniqueRef<IFile>();
|
||||
|
||||
pfs0.OpenFile(ref cnmtFile.Ref, pfs0.EnumerateEntries().Single().FullPath.ToU8Span(), OpenMode.Read);
|
||||
pfs0.OpenFile(ref cnmtFile.Ref, pfs0.EnumerateEntries().Single().FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
var cnmt = new Cnmt(cnmtFile.Get.AsStream());
|
||||
if (cnmt.Type != ContentMetaType.AddOnContent || (cnmt.TitleId & 0xFFFFFFFFFFFFE000) != aocBaseId)
|
||||
|
@ -220,7 +220,7 @@ namespace Ryujinx.HLE.FileSystem
|
|||
|
||||
string ncaId = Convert.ToHexString(cnmt.ContentEntries[0].NcaId).ToLower();
|
||||
|
||||
AddAocItem(cnmt.TitleId, containerPath, $"{ncaId}.nca", true);
|
||||
AddAocItem(cnmt.TitleId, containerPath, $"/{ncaId}.nca", true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -238,7 +238,8 @@ namespace Ryujinx.HLE.FileSystem
|
|||
if (!mergedToContainer)
|
||||
{
|
||||
using FileStream fileStream = File.OpenRead(containerPath);
|
||||
using PartitionFileSystem partitionFileSystem = new(fileStream.AsStorage());
|
||||
using PartitionFileSystem partitionFileSystem = new();
|
||||
partitionFileSystem.Initialize(fileStream.AsStorage()).ThrowIfFailure();
|
||||
|
||||
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
||||
}
|
||||
|
@ -259,17 +260,17 @@ namespace Ryujinx.HLE.FileSystem
|
|||
{
|
||||
var file = new FileStream(aoc.ContainerPath, FileMode.Open, FileAccess.Read);
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
PartitionFileSystem pfs;
|
||||
|
||||
switch (Path.GetExtension(aoc.ContainerPath))
|
||||
{
|
||||
case ".xci":
|
||||
pfs = new Xci(_virtualFileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure);
|
||||
pfs.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read);
|
||||
var xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure);
|
||||
xci.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
break;
|
||||
case ".nsp":
|
||||
pfs = new PartitionFileSystem(file.AsStorage());
|
||||
pfs.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read);
|
||||
var pfs = new PartitionFileSystem();
|
||||
pfs.Initialize(file.AsStorage());
|
||||
pfs.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
break;
|
||||
default:
|
||||
return false; // Print error?
|
||||
|
@ -606,11 +607,11 @@ namespace Ryujinx.HLE.FileSystem
|
|||
|
||||
if (filesystem.FileExists($"{path}/00"))
|
||||
{
|
||||
filesystem.OpenFile(ref file.Ref, $"{path}/00".ToU8Span(), mode);
|
||||
filesystem.OpenFile(ref file.Ref, $"{path}/00".ToU8Span(), mode).ThrowIfFailure();
|
||||
}
|
||||
else
|
||||
{
|
||||
filesystem.OpenFile(ref file.Ref, path.ToU8Span(), mode);
|
||||
filesystem.OpenFile(ref file.Ref, path.ToU8Span(), mode).ThrowIfFailure();
|
||||
}
|
||||
|
||||
return file.Release();
|
||||
|
|
|
@ -7,6 +7,7 @@ using LibHac.Fs.Shim;
|
|||
using LibHac.FsSrv;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Sdmmc;
|
||||
using LibHac.Spl;
|
||||
using LibHac.Tools.Es;
|
||||
using LibHac.Tools.Fs;
|
||||
|
@ -32,7 +33,7 @@ namespace Ryujinx.HLE.FileSystem
|
|||
|
||||
public KeySet KeySet { get; private set; }
|
||||
public EmulatedGameCard GameCard { get; private set; }
|
||||
public EmulatedSdCard SdCard { get; private set; }
|
||||
public SdmmcApi SdCard { get; private set; }
|
||||
public ModLoader ModLoader { get; private set; }
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, Stream> _romFsByPid;
|
||||
|
@ -198,15 +199,15 @@ namespace Ryujinx.HLE.FileSystem
|
|||
fsServerObjects.FsCreators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator();
|
||||
|
||||
GameCard = fsServerObjects.GameCard;
|
||||
SdCard = fsServerObjects.SdCard;
|
||||
SdCard = fsServerObjects.Sdmmc;
|
||||
|
||||
SdCard.SetSdCardInsertionStatus(true);
|
||||
SdCard.SetSdCardInserted(true);
|
||||
|
||||
var fsServerConfig = new FileSystemServerConfig
|
||||
{
|
||||
DeviceOperator = fsServerObjects.DeviceOperator,
|
||||
ExternalKeySet = KeySet.ExternalKeySet,
|
||||
FsCreators = fsServerObjects.FsCreators,
|
||||
StorageDeviceManagerFactory = fsServerObjects.StorageDeviceManagerFactory,
|
||||
RandomGenerator = randomGenerator,
|
||||
};
|
||||
|
||||
|
@ -263,7 +264,16 @@ namespace Ryujinx.HLE.FileSystem
|
|||
|
||||
if (result.IsSuccess())
|
||||
{
|
||||
Ticket ticket = new(ticketFile.Get.AsStream());
|
||||
// When reading a file from a Sha256PartitionFileSystem, you can't start a read in the middle
|
||||
// of the hashed portion (usually the first 0x200 bytes) of the file and end the read after
|
||||
// the end of the hashed portion, so we read the ticket file using a single read.
|
||||
byte[] ticketData = new byte[0x2C0];
|
||||
result = ticketFile.Get.Read(out long bytesRead, 0, ticketData);
|
||||
|
||||
if (result.IsFailure() || bytesRead != ticketData.Length)
|
||||
continue;
|
||||
|
||||
Ticket ticket = new(new MemoryStream(ticketData));
|
||||
var titleKey = ticket.GetTitleKey(KeySet);
|
||||
|
||||
if (titleKey != null)
|
||||
|
|
|
@ -101,7 +101,7 @@ namespace Ryujinx.HLE
|
|||
/// <summary>
|
||||
/// Control if the guest application should be told that there is a Internet connection available.
|
||||
/// </summary>
|
||||
internal readonly bool EnableInternetAccess;
|
||||
public bool EnableInternetAccess { internal get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Control LibHac's integrity check level.
|
||||
|
|
|
@ -533,7 +533,9 @@ namespace Ryujinx.HLE.HOS
|
|||
|
||||
Logger.Info?.Print(LogClass.ModLoader, "Using replacement ExeFS partition");
|
||||
|
||||
exefs = new PartitionFileSystem(mods.ExefsContainers[0].Path.OpenRead().AsStorage());
|
||||
var pfs = new PartitionFileSystem();
|
||||
pfs.Initialize(mods.ExefsContainers[0].Path.OpenRead().AsStorage()).ThrowIfFailure();
|
||||
exefs = pfs;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -26,7 +26,9 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
|
|||
try
|
||||
{
|
||||
LocalStorage storage = new(pfsPath, FileAccess.Read, FileMode.Open);
|
||||
using SharedRef<LibHac.Fs.Fsa.IFileSystem> nsp = new(new PartitionFileSystem(storage));
|
||||
var pfs = new PartitionFileSystem();
|
||||
using SharedRef<LibHac.Fs.Fsa.IFileSystem> nsp = new(pfs);
|
||||
pfs.Initialize(storage).ThrowIfFailure();
|
||||
|
||||
ImportTitleKeysFromNsp(nsp.Get, context.Device.System.KeySet);
|
||||
|
||||
|
@ -90,7 +92,8 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
|
|||
|
||||
try
|
||||
{
|
||||
PartitionFileSystem nsp = new(pfsFile.AsStorage());
|
||||
PartitionFileSystem nsp = new();
|
||||
nsp.Initialize(pfsFile.AsStorage()).ThrowIfFailure();
|
||||
|
||||
ImportTitleKeysFromNsp(nsp, context.Device.System.KeySet);
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using LibHac;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using Path = LibHac.FsSrv.Sf.Path;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
|
||||
|
@ -202,6 +203,16 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
|
|||
return (ResultCode)result.Value;
|
||||
}
|
||||
|
||||
[CommandCmif(16)]
|
||||
public ResultCode GetFileSystemAttribute(ServiceCtx context)
|
||||
{
|
||||
Result result = _fileSystem.Get.GetFileSystemAttribute(out FileSystemAttribute attribute);
|
||||
|
||||
context.ResponseData.Write(SpanHelpers.AsReadOnlyByteSpan(in attribute));
|
||||
|
||||
return (ResultCode)result.Value;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
if (isDisposing)
|
||||
|
|
|
@ -1380,7 +1380,10 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
|||
[CommandCmif(1016)]
|
||||
public ResultCode FlushAccessLogOnSdCard(ServiceCtx context)
|
||||
{
|
||||
return (ResultCode)_baseFileSystemProxy.Get.FlushAccessLogOnSdCard().Value;
|
||||
// Logging the access log to the SD card isn't implemented, meaning this function will be a no-op since
|
||||
// there's nothing to flush. Return success until it's implemented.
|
||||
// return (ResultCode)_baseFileSystemProxy.Get.FlushAccessLogOnSdCard().Value;
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1017)]
|
||||
|
|
12
src/Ryujinx.HLE/HOS/Services/Ldn/LdnConst.cs
Normal file
12
src/Ryujinx.HLE/HOS/Services/Ldn/LdnConst.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Ldn
|
||||
{
|
||||
static class LdnConst
|
||||
{
|
||||
public const int SsidLengthMax = 0x20;
|
||||
public const int AdvertiseDataSizeMax = 0x180;
|
||||
public const int UserNameBytesMax = 0x20;
|
||||
public const int NodeCountMax = 8;
|
||||
public const int StationCountMax = NodeCountMax - 1;
|
||||
public const int PassphraseLengthMax = 0x40;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
|
@ -48,7 +48,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
|||
{
|
||||
result[i].Reserved = new Array7<byte>();
|
||||
|
||||
if (i < 8)
|
||||
if (i < LdnConst.NodeCountMax)
|
||||
{
|
||||
result[i].State = array[i].State;
|
||||
array[i].State = NodeLatestUpdateFlags.None;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Network.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
|
@ -30,7 +29,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||
_parent.NetworkClient.NetworkChange -= NetworkChanged;
|
||||
}
|
||||
|
||||
private void NetworkChanged(object sender, RyuLdn.NetworkChangeEventArgs e)
|
||||
private void NetworkChanged(object sender, NetworkChangeEventArgs e)
|
||||
{
|
||||
LatestUpdates.CalculateLatestUpdate(NetworkInfo.Ldn.Nodes, e.Info.Ldn.Nodes);
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Network.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
{
|
||||
interface INetworkClient : IDisposable
|
||||
{
|
||||
bool NeedsRealId { get; }
|
||||
|
||||
event EventHandler<NetworkChangeEventArgs> NetworkChange;
|
||||
|
||||
void DisconnectNetwork();
|
|
@ -8,7 +8,7 @@ using Ryujinx.Cpu;
|
|||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
|
@ -395,7 +395,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||
}
|
||||
else
|
||||
{
|
||||
if (scanFilter.NetworkId.IntentId.LocalCommunicationId == -1)
|
||||
if (scanFilter.NetworkId.IntentId.LocalCommunicationId == -1 && NetworkClient.NeedsRealId)
|
||||
{
|
||||
// TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
|
||||
ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
|
||||
|
@ -546,7 +546,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||
context.RequestData.BaseStream.Seek(4, SeekOrigin.Current); // Alignment?
|
||||
NetworkConfig networkConfig = context.RequestData.ReadStruct<NetworkConfig>();
|
||||
|
||||
if (networkConfig.IntentId.LocalCommunicationId == -1)
|
||||
if (networkConfig.IntentId.LocalCommunicationId == -1 && NetworkClient.NeedsRealId)
|
||||
{
|
||||
// TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
|
||||
ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
|
||||
|
@ -555,7 +555,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||
}
|
||||
|
||||
bool isLocalCommunicationIdValid = CheckLocalCommunicationIdPermission(context, (ulong)networkConfig.IntentId.LocalCommunicationId);
|
||||
if (!isLocalCommunicationIdValid)
|
||||
if (!isLocalCommunicationIdValid && NetworkClient.NeedsRealId)
|
||||
{
|
||||
return ResultCode.InvalidObject;
|
||||
}
|
||||
|
@ -568,13 +568,13 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||
networkConfig.Channel = CheckDevelopmentChannel(networkConfig.Channel);
|
||||
securityConfig.SecurityMode = CheckDevelopmentSecurityMode(securityConfig.SecurityMode);
|
||||
|
||||
if (networkConfig.NodeCountMax <= 8)
|
||||
if (networkConfig.NodeCountMax <= LdnConst.NodeCountMax)
|
||||
{
|
||||
if ((((ulong)networkConfig.LocalCommunicationVersion) & 0x80000000) == 0)
|
||||
{
|
||||
if (securityConfig.SecurityMode <= SecurityMode.Retail)
|
||||
{
|
||||
if (securityConfig.Passphrase.Length <= 0x40)
|
||||
if (securityConfig.Passphrase.Length <= LdnConst.PassphraseLengthMax)
|
||||
{
|
||||
if (_state == NetworkState.AccessPoint)
|
||||
{
|
||||
|
@ -678,7 +678,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||
return _nifmResultCode;
|
||||
}
|
||||
|
||||
if (bufferSize == 0 || bufferSize > 0x180)
|
||||
if (bufferSize == 0 || bufferSize > LdnConst.AdvertiseDataSizeMax)
|
||||
{
|
||||
return ResultCode.InvalidArgument;
|
||||
}
|
||||
|
@ -848,10 +848,10 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||
|
||||
context.Memory.Read(bufferPosition, networkInfoBytes);
|
||||
|
||||
networkInfo = MemoryMarshal.Cast<byte, NetworkInfo>(networkInfoBytes)[0];
|
||||
networkInfo = MemoryMarshal.Read<NetworkInfo>(networkInfoBytes);
|
||||
}
|
||||
|
||||
if (networkInfo.NetworkId.IntentId.LocalCommunicationId == -1)
|
||||
if (networkInfo.NetworkId.IntentId.LocalCommunicationId == -1 && NetworkClient.NeedsRealId)
|
||||
{
|
||||
// TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
|
||||
ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
|
||||
|
@ -860,7 +860,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||
}
|
||||
|
||||
bool isLocalCommunicationIdValid = CheckLocalCommunicationIdPermission(context, (ulong)networkInfo.NetworkId.IntentId.LocalCommunicationId);
|
||||
if (!isLocalCommunicationIdValid)
|
||||
if (!isLocalCommunicationIdValid && NetworkClient.NeedsRealId)
|
||||
{
|
||||
return ResultCode.InvalidObject;
|
||||
}
|
||||
|
@ -1061,10 +1061,16 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||
if (System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable())
|
||||
{
|
||||
MultiplayerMode mode = context.Device.Configuration.MultiplayerMode;
|
||||
|
||||
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"Initializing with multiplayer mode: {mode}");
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case MultiplayerMode.LdnMitm:
|
||||
NetworkClient = new LdnMitmClient(context.Device.Configuration);
|
||||
break;
|
||||
case MultiplayerMode.Disabled:
|
||||
NetworkClient = new DisabledLdnClient();
|
||||
NetworkClient = new LdnDisabledClient();
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Network.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
{
|
||||
class DisabledLdnClient : INetworkClient
|
||||
class LdnDisabledClient : INetworkClient
|
||||
{
|
||||
public bool NeedsRealId => true;
|
||||
|
||||
public event EventHandler<NetworkChangeEventArgs> NetworkChange;
|
||||
|
||||
public NetworkError Connect(ConnectRequest request)
|
|
@ -0,0 +1,611 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm
|
||||
{
|
||||
internal class LanDiscovery : IDisposable
|
||||
{
|
||||
private const int DefaultPort = 11452;
|
||||
private const ushort CommonChannel = 6;
|
||||
private const byte CommonLinkLevel = 3;
|
||||
private const byte CommonNetworkType = 2;
|
||||
|
||||
private const int FailureTimeout = 4000;
|
||||
|
||||
private readonly LdnMitmClient _parent;
|
||||
private readonly LanProtocol _protocol;
|
||||
private bool _initialized;
|
||||
private readonly Ssid _fakeSsid;
|
||||
private ILdnTcpSocket _tcp;
|
||||
private LdnProxyUdpServer _udp, _udp2;
|
||||
private readonly List<LdnProxyTcpSession> _stations = new();
|
||||
private readonly object _lock = new();
|
||||
|
||||
private readonly AutoResetEvent _apConnected = new(false);
|
||||
|
||||
internal readonly IPAddress LocalAddr;
|
||||
internal readonly IPAddress LocalBroadcastAddr;
|
||||
internal NetworkInfo NetworkInfo;
|
||||
|
||||
public bool IsHost => _tcp is LdnProxyTcpServer;
|
||||
|
||||
private readonly Random _random = new();
|
||||
|
||||
// NOTE: Credit to https://stackoverflow.com/a/39338188
|
||||
private static IPAddress GetBroadcastAddress(IPAddress address, IPAddress mask)
|
||||
{
|
||||
uint ipAddress = BitConverter.ToUInt32(address.GetAddressBytes(), 0);
|
||||
uint ipMaskV4 = BitConverter.ToUInt32(mask.GetAddressBytes(), 0);
|
||||
uint broadCastIpAddress = ipAddress | ~ipMaskV4;
|
||||
|
||||
return new IPAddress(BitConverter.GetBytes(broadCastIpAddress));
|
||||
}
|
||||
|
||||
private static NetworkInfo GetEmptyNetworkInfo()
|
||||
{
|
||||
NetworkInfo networkInfo = new()
|
||||
{
|
||||
NetworkId = new NetworkId
|
||||
{
|
||||
SessionId = new Array16<byte>(),
|
||||
},
|
||||
Common = new CommonNetworkInfo
|
||||
{
|
||||
MacAddress = new Array6<byte>(),
|
||||
Ssid = new Ssid
|
||||
{
|
||||
Name = new Array33<byte>(),
|
||||
},
|
||||
},
|
||||
Ldn = new LdnNetworkInfo
|
||||
{
|
||||
NodeCountMax = LdnConst.NodeCountMax,
|
||||
SecurityParameter = new Array16<byte>(),
|
||||
Nodes = new Array8<NodeInfo>(),
|
||||
AdvertiseData = new Array384<byte>(),
|
||||
Reserved4 = new Array140<byte>(),
|
||||
},
|
||||
};
|
||||
|
||||
for (int i = 0; i < LdnConst.NodeCountMax; i++)
|
||||
{
|
||||
networkInfo.Ldn.Nodes[i] = new NodeInfo
|
||||
{
|
||||
MacAddress = new Array6<byte>(),
|
||||
UserName = new Array33<byte>(),
|
||||
Reserved2 = new Array16<byte>(),
|
||||
};
|
||||
}
|
||||
|
||||
return networkInfo;
|
||||
}
|
||||
|
||||
public LanDiscovery(LdnMitmClient parent, IPAddress ipAddress, IPAddress ipv4Mask)
|
||||
{
|
||||
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"Initialize LanDiscovery using IP: {ipAddress}");
|
||||
|
||||
_parent = parent;
|
||||
LocalAddr = ipAddress;
|
||||
LocalBroadcastAddr = GetBroadcastAddress(ipAddress, ipv4Mask);
|
||||
|
||||
_fakeSsid = new Ssid
|
||||
{
|
||||
Length = LdnConst.SsidLengthMax,
|
||||
};
|
||||
_random.NextBytes(_fakeSsid.Name.AsSpan()[..32]);
|
||||
|
||||
_protocol = new LanProtocol(this);
|
||||
_protocol.Accept += OnConnect;
|
||||
_protocol.SyncNetwork += OnSyncNetwork;
|
||||
_protocol.DisconnectStation += DisconnectStation;
|
||||
|
||||
NetworkInfo = GetEmptyNetworkInfo();
|
||||
|
||||
ResetStations();
|
||||
|
||||
if (!InitUdp())
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, "LanDiscovery Initialize: InitUdp failed.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
protected void OnSyncNetwork(NetworkInfo info)
|
||||
{
|
||||
bool updated = false;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (!NetworkInfo.Equals(info))
|
||||
{
|
||||
NetworkInfo = info;
|
||||
updated = true;
|
||||
|
||||
Logger.Debug?.PrintMsg(LogClass.ServiceLdn, $"Host IP: {NetworkHelpers.ConvertUint(info.Ldn.Nodes[0].Ipv4Address)}");
|
||||
}
|
||||
}
|
||||
|
||||
if (updated)
|
||||
{
|
||||
_parent.InvokeNetworkChange(info, true);
|
||||
}
|
||||
|
||||
_apConnected.Set();
|
||||
}
|
||||
|
||||
protected void OnConnect(LdnProxyTcpSession station)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
station.NodeId = LocateEmptyNode();
|
||||
|
||||
if (_stations.Count > LdnConst.StationCountMax || station.NodeId == -1)
|
||||
{
|
||||
station.Disconnect();
|
||||
station.Dispose();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_stations.Add(station);
|
||||
|
||||
UpdateNodes();
|
||||
}
|
||||
}
|
||||
|
||||
public void DisconnectStation(LdnProxyTcpSession station)
|
||||
{
|
||||
if (!station.IsDisposed)
|
||||
{
|
||||
if (station.IsConnected)
|
||||
{
|
||||
station.Disconnect();
|
||||
}
|
||||
|
||||
station.Dispose();
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_stations.Remove(station))
|
||||
{
|
||||
NetworkInfo.Ldn.Nodes[station.NodeId] = new NodeInfo()
|
||||
{
|
||||
MacAddress = new Array6<byte>(),
|
||||
UserName = new Array33<byte>(),
|
||||
Reserved2 = new Array16<byte>(),
|
||||
};
|
||||
|
||||
UpdateNodes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool SetAdvertiseData(byte[] data)
|
||||
{
|
||||
if (data.Length > LdnConst.AdvertiseDataSizeMax)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, "AdvertiseData exceeds size limit.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
data.CopyTo(NetworkInfo.Ldn.AdvertiseData.AsSpan());
|
||||
NetworkInfo.Ldn.AdvertiseDataSize = (ushort)data.Length;
|
||||
|
||||
// NOTE: Otherwise this results in SessionKeepFailed or MasterDisconnected
|
||||
lock (_lock)
|
||||
{
|
||||
if (NetworkInfo.Ldn.Nodes[0].IsConnected == 1)
|
||||
{
|
||||
UpdateNodes(true);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void InitNetworkInfo()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
NetworkInfo.Common.MacAddress = GetFakeMac();
|
||||
NetworkInfo.Common.Channel = CommonChannel;
|
||||
NetworkInfo.Common.LinkLevel = CommonLinkLevel;
|
||||
NetworkInfo.Common.NetworkType = CommonNetworkType;
|
||||
NetworkInfo.Common.Ssid = _fakeSsid;
|
||||
|
||||
NetworkInfo.Ldn.Nodes = new Array8<NodeInfo>();
|
||||
|
||||
for (int i = 0; i < LdnConst.NodeCountMax; i++)
|
||||
{
|
||||
NetworkInfo.Ldn.Nodes[i].NodeId = (byte)i;
|
||||
NetworkInfo.Ldn.Nodes[i].IsConnected = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Array6<byte> GetFakeMac(IPAddress address = null)
|
||||
{
|
||||
address ??= LocalAddr;
|
||||
|
||||
byte[] ip = address.GetAddressBytes();
|
||||
|
||||
var macAddress = new Array6<byte>();
|
||||
new byte[] { 0x02, 0x00, ip[0], ip[1], ip[2], ip[3] }.CopyTo(macAddress.AsSpan());
|
||||
|
||||
return macAddress;
|
||||
}
|
||||
|
||||
public bool InitTcp(bool listening, IPAddress address = null, int port = DefaultPort)
|
||||
{
|
||||
Logger.Debug?.PrintMsg(LogClass.ServiceLdn, $"LanDiscovery InitTcp: IP: {address}, listening: {listening}");
|
||||
|
||||
if (_tcp != null)
|
||||
{
|
||||
_tcp.DisconnectAndStop();
|
||||
_tcp.Dispose();
|
||||
_tcp = null;
|
||||
}
|
||||
|
||||
ILdnTcpSocket tcpSocket;
|
||||
|
||||
if (listening)
|
||||
{
|
||||
try
|
||||
{
|
||||
address ??= LocalAddr;
|
||||
|
||||
tcpSocket = new LdnProxyTcpServer(_protocol, address, port);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Failed to create LdnProxyTcpServer: {ex}");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!tcpSocket.Start())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (address == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
tcpSocket = new LdnProxyTcpClient(_protocol, address, port);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Failed to create LdnProxyTcpClient: {ex}");
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_tcp = tcpSocket;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool InitUdp()
|
||||
{
|
||||
_udp?.Stop();
|
||||
_udp2?.Stop();
|
||||
|
||||
try
|
||||
{
|
||||
// NOTE: Linux won't receive any broadcast packets if the socket is not bound to the broadcast address.
|
||||
// Windows only works if bound to localhost or the local address.
|
||||
// See this discussion: https://stackoverflow.com/questions/13666789/receiving-udp-broadcast-packets-on-linux
|
||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
_udp2 = new LdnProxyUdpServer(_protocol, LocalBroadcastAddr, DefaultPort);
|
||||
}
|
||||
|
||||
_udp = new LdnProxyUdpServer(_protocol, LocalAddr, DefaultPort);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Failed to create LdnProxyUdpServer: {ex}");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public NetworkInfo[] Scan(ushort channel, ScanFilter filter)
|
||||
{
|
||||
_udp.ClearScanResults();
|
||||
|
||||
if (_protocol.SendBroadcast(_udp, LanPacketType.Scan, DefaultPort) < 0)
|
||||
{
|
||||
return Array.Empty<NetworkInfo>();
|
||||
}
|
||||
|
||||
List<NetworkInfo> outNetworkInfo = new();
|
||||
|
||||
foreach (KeyValuePair<ulong, NetworkInfo> item in _udp.GetScanResults())
|
||||
{
|
||||
bool copy = true;
|
||||
|
||||
if (filter.Flag.HasFlag(ScanFilterFlag.LocalCommunicationId))
|
||||
{
|
||||
copy &= filter.NetworkId.IntentId.LocalCommunicationId == item.Value.NetworkId.IntentId.LocalCommunicationId;
|
||||
}
|
||||
|
||||
if (filter.Flag.HasFlag(ScanFilterFlag.SessionId))
|
||||
{
|
||||
copy &= filter.NetworkId.SessionId.AsSpan().SequenceEqual(item.Value.NetworkId.SessionId.AsSpan());
|
||||
}
|
||||
|
||||
if (filter.Flag.HasFlag(ScanFilterFlag.NetworkType))
|
||||
{
|
||||
copy &= filter.NetworkType == (NetworkType)item.Value.Common.NetworkType;
|
||||
}
|
||||
|
||||
if (filter.Flag.HasFlag(ScanFilterFlag.Ssid))
|
||||
{
|
||||
Span<byte> gameSsid = item.Value.Common.Ssid.Name.AsSpan()[item.Value.Common.Ssid.Length..];
|
||||
Span<byte> scanSsid = filter.Ssid.Name.AsSpan()[filter.Ssid.Length..];
|
||||
copy &= gameSsid.SequenceEqual(scanSsid);
|
||||
}
|
||||
|
||||
if (filter.Flag.HasFlag(ScanFilterFlag.SceneId))
|
||||
{
|
||||
copy &= filter.NetworkId.IntentId.SceneId == item.Value.NetworkId.IntentId.SceneId;
|
||||
}
|
||||
|
||||
if (copy)
|
||||
{
|
||||
if (item.Value.Ldn.Nodes[0].UserName[0] != 0)
|
||||
{
|
||||
outNetworkInfo.Add(item.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning?.PrintMsg(LogClass.ServiceLdn, "LanDiscovery Scan: Got empty Username. There might be a timing issue somewhere...");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return outNetworkInfo.ToArray();
|
||||
}
|
||||
|
||||
protected void ResetStations()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
foreach (LdnProxyTcpSession station in _stations)
|
||||
{
|
||||
station.Disconnect();
|
||||
station.Dispose();
|
||||
}
|
||||
|
||||
_stations.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private int LocateEmptyNode()
|
||||
{
|
||||
Array8<NodeInfo> nodes = NetworkInfo.Ldn.Nodes;
|
||||
|
||||
for (int i = 1; i < nodes.Length; i++)
|
||||
{
|
||||
if (nodes[i].IsConnected == 0)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
protected void UpdateNodes(bool forceUpdate = false)
|
||||
{
|
||||
int countConnected = 1;
|
||||
|
||||
foreach (LdnProxyTcpSession station in _stations.Where(station => station.IsConnected))
|
||||
{
|
||||
countConnected++;
|
||||
|
||||
station.OverrideInfo();
|
||||
|
||||
// NOTE: This is not part of the original implementation.
|
||||
NetworkInfo.Ldn.Nodes[station.NodeId] = station.NodeInfo;
|
||||
}
|
||||
|
||||
byte nodeCount = (byte)countConnected;
|
||||
|
||||
bool networkInfoChanged = forceUpdate || NetworkInfo.Ldn.NodeCount != nodeCount;
|
||||
|
||||
NetworkInfo.Ldn.NodeCount = nodeCount;
|
||||
|
||||
foreach (LdnProxyTcpSession station in _stations)
|
||||
{
|
||||
if (station.IsConnected)
|
||||
{
|
||||
if (_protocol.SendPacket(station, LanPacketType.SyncNetwork, SpanHelpers.AsSpan<NetworkInfo, byte>(ref NetworkInfo).ToArray()) < 0)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Failed to send {LanPacketType.SyncNetwork} to station {station.NodeId}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (networkInfoChanged)
|
||||
{
|
||||
_parent.InvokeNetworkChange(NetworkInfo, true);
|
||||
}
|
||||
}
|
||||
|
||||
protected NodeInfo GetNodeInfo(NodeInfo node, UserConfig userConfig, ushort localCommunicationVersion)
|
||||
{
|
||||
uint ipAddress = NetworkHelpers.ConvertIpv4Address(LocalAddr);
|
||||
|
||||
node.MacAddress = GetFakeMac();
|
||||
node.IsConnected = 1;
|
||||
node.UserName = userConfig.UserName;
|
||||
node.LocalCommunicationVersion = localCommunicationVersion;
|
||||
node.Ipv4Address = ipAddress;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
public bool CreateNetwork(SecurityConfig securityConfig, UserConfig userConfig, NetworkConfig networkConfig)
|
||||
{
|
||||
if (!InitTcp(true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
InitNetworkInfo();
|
||||
|
||||
NetworkInfo.Ldn.NodeCountMax = networkConfig.NodeCountMax;
|
||||
NetworkInfo.Ldn.SecurityMode = (ushort)securityConfig.SecurityMode;
|
||||
|
||||
NetworkInfo.Common.Channel = networkConfig.Channel == 0 ? (ushort)6 : networkConfig.Channel;
|
||||
|
||||
NetworkInfo.NetworkId.SessionId = new Array16<byte>();
|
||||
_random.NextBytes(NetworkInfo.NetworkId.SessionId.AsSpan());
|
||||
NetworkInfo.NetworkId.IntentId = networkConfig.IntentId;
|
||||
|
||||
NetworkInfo.Ldn.Nodes[0] = GetNodeInfo(NetworkInfo.Ldn.Nodes[0], userConfig, networkConfig.LocalCommunicationVersion);
|
||||
NetworkInfo.Ldn.Nodes[0].IsConnected = 1;
|
||||
NetworkInfo.Ldn.NodeCount++;
|
||||
|
||||
_parent.InvokeNetworkChange(NetworkInfo, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void DestroyNetwork()
|
||||
{
|
||||
if (_tcp != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_tcp.DisconnectAndStop();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_tcp.Dispose();
|
||||
_tcp = null;
|
||||
}
|
||||
}
|
||||
|
||||
ResetStations();
|
||||
}
|
||||
|
||||
public NetworkError Connect(NetworkInfo networkInfo, UserConfig userConfig, uint localCommunicationVersion)
|
||||
{
|
||||
_apConnected.Reset();
|
||||
|
||||
if (networkInfo.Ldn.NodeCount == 0)
|
||||
{
|
||||
return NetworkError.Unknown;
|
||||
}
|
||||
|
||||
IPAddress address = NetworkHelpers.ConvertUint(networkInfo.Ldn.Nodes[0].Ipv4Address);
|
||||
|
||||
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"Connecting to host: {address}");
|
||||
|
||||
if (!InitTcp(false, address))
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, "Could not initialize TCPClient");
|
||||
|
||||
return NetworkError.ConnectNotFound;
|
||||
}
|
||||
|
||||
if (!_tcp.Connect())
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, "Failed to connect.");
|
||||
|
||||
return NetworkError.ConnectFailure;
|
||||
}
|
||||
|
||||
NodeInfo myNode = GetNodeInfo(new NodeInfo(), userConfig, (ushort)localCommunicationVersion);
|
||||
if (_protocol.SendPacket(_tcp, LanPacketType.Connect, SpanHelpers.AsSpan<NodeInfo, byte>(ref myNode).ToArray()) < 0)
|
||||
{
|
||||
return NetworkError.Unknown;
|
||||
}
|
||||
|
||||
return _apConnected.WaitOne(FailureTimeout) ? NetworkError.None : NetworkError.ConnectTimeout;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_initialized)
|
||||
{
|
||||
DisconnectAndStop();
|
||||
ResetStations();
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
_protocol.Accept -= OnConnect;
|
||||
_protocol.SyncNetwork -= OnSyncNetwork;
|
||||
_protocol.DisconnectStation -= DisconnectStation;
|
||||
}
|
||||
|
||||
public void DisconnectAndStop()
|
||||
{
|
||||
if (_udp != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_udp.Stop();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_udp.Dispose();
|
||||
_udp = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (_udp2 != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_udp2.Stop();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_udp2.Dispose();
|
||||
_udp2 = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (_tcp != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_tcp.DisconnectAndStop();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_tcp.Dispose();
|
||||
_tcp = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,314 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Types;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm
|
||||
{
|
||||
internal class LanProtocol
|
||||
{
|
||||
private const uint LanMagic = 0x11451400;
|
||||
|
||||
public const int BufferSize = 2048;
|
||||
public const int TcpTxBufferSize = 0x800;
|
||||
public const int TcpRxBufferSize = 0x1000;
|
||||
public const int TxBufferSizeMax = 0x2000;
|
||||
public const int RxBufferSizeMax = 0x2000;
|
||||
|
||||
private readonly int _headerSize = Marshal.SizeOf<LanPacketHeader>();
|
||||
|
||||
private readonly LanDiscovery _discovery;
|
||||
|
||||
public event Action<LdnProxyTcpSession> Accept;
|
||||
public event Action<EndPoint, LanPacketType, byte[]> Scan;
|
||||
public event Action<NetworkInfo> ScanResponse;
|
||||
public event Action<NetworkInfo> SyncNetwork;
|
||||
public event Action<NodeInfo, EndPoint> Connect;
|
||||
public event Action<LdnProxyTcpSession> DisconnectStation;
|
||||
|
||||
public LanProtocol(LanDiscovery parent)
|
||||
{
|
||||
_discovery = parent;
|
||||
}
|
||||
|
||||
public void InvokeAccept(LdnProxyTcpSession session)
|
||||
{
|
||||
Accept?.Invoke(session);
|
||||
}
|
||||
|
||||
public void InvokeDisconnectStation(LdnProxyTcpSession session)
|
||||
{
|
||||
DisconnectStation?.Invoke(session);
|
||||
}
|
||||
|
||||
private void DecodeAndHandle(LanPacketHeader header, byte[] data, EndPoint endPoint = null)
|
||||
{
|
||||
switch (header.Type)
|
||||
{
|
||||
case LanPacketType.Scan:
|
||||
// UDP
|
||||
if (_discovery.IsHost)
|
||||
{
|
||||
Scan?.Invoke(endPoint, LanPacketType.ScanResponse, SpanHelpers.AsSpan<NetworkInfo, byte>(ref _discovery.NetworkInfo).ToArray());
|
||||
}
|
||||
break;
|
||||
case LanPacketType.ScanResponse:
|
||||
// UDP
|
||||
ScanResponse?.Invoke(MemoryMarshal.Cast<byte, NetworkInfo>(data)[0]);
|
||||
break;
|
||||
case LanPacketType.SyncNetwork:
|
||||
// TCP
|
||||
SyncNetwork?.Invoke(MemoryMarshal.Cast<byte, NetworkInfo>(data)[0]);
|
||||
break;
|
||||
case LanPacketType.Connect:
|
||||
// TCP Session / Station
|
||||
Connect?.Invoke(MemoryMarshal.Cast<byte, NodeInfo>(data)[0], endPoint);
|
||||
break;
|
||||
default:
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Decode error: Unhandled type {header.Type}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Read(scoped ref byte[] buffer, scoped ref int bufferEnd, byte[] data, int offset, int size, EndPoint endPoint = null)
|
||||
{
|
||||
if (endPoint != null && _discovery.LocalAddr.Equals(((IPEndPoint)endPoint).Address))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
while (index < size)
|
||||
{
|
||||
if (bufferEnd < _headerSize)
|
||||
{
|
||||
int copyable2 = Math.Min(size - index, Math.Min(size, _headerSize - bufferEnd));
|
||||
|
||||
Array.Copy(data, index + offset, buffer, bufferEnd, copyable2);
|
||||
|
||||
index += copyable2;
|
||||
bufferEnd += copyable2;
|
||||
}
|
||||
|
||||
if (bufferEnd >= _headerSize)
|
||||
{
|
||||
LanPacketHeader header = MemoryMarshal.Cast<byte, LanPacketHeader>(buffer)[0];
|
||||
if (header.Magic != LanMagic)
|
||||
{
|
||||
bufferEnd = 0;
|
||||
|
||||
Logger.Warning?.PrintMsg(LogClass.ServiceLdn, $"Invalid magic number in received packet. [magic: {header.Magic}] [EP: {endPoint}]");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int totalSize = _headerSize + header.Length;
|
||||
if (totalSize > BufferSize)
|
||||
{
|
||||
bufferEnd = 0;
|
||||
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Max packet size {BufferSize} exceeded.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int copyable = Math.Min(size - index, Math.Min(size, totalSize - bufferEnd));
|
||||
|
||||
Array.Copy(data, index + offset, buffer, bufferEnd, copyable);
|
||||
|
||||
index += copyable;
|
||||
bufferEnd += copyable;
|
||||
|
||||
if (totalSize == bufferEnd)
|
||||
{
|
||||
byte[] ldnData = new byte[totalSize - _headerSize];
|
||||
Array.Copy(buffer, _headerSize, ldnData, 0, ldnData.Length);
|
||||
|
||||
if (header.Compressed == 1)
|
||||
{
|
||||
if (Decompress(ldnData, out byte[] decompressedLdnData) != 0)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Decompress error:\n {header}, {_headerSize}\n {ldnData}, {ldnData.Length}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (decompressedLdnData.Length != header.DecompressLength)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Decompress error: length does not match. ({decompressedLdnData.Length} != {header.DecompressLength})");
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Decompress error data: '{string.Join("", decompressedLdnData.Select(x => (int)x).ToArray())}'");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ldnData = decompressedLdnData;
|
||||
}
|
||||
|
||||
DecodeAndHandle(header, ldnData, endPoint);
|
||||
|
||||
bufferEnd = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int SendBroadcast(ILdnSocket s, LanPacketType type, int port)
|
||||
{
|
||||
return SendPacket(s, type, Array.Empty<byte>(), new IPEndPoint(_discovery.LocalBroadcastAddr, port));
|
||||
}
|
||||
|
||||
public int SendPacket(ILdnSocket s, LanPacketType type, byte[] data, EndPoint endPoint = null)
|
||||
{
|
||||
byte[] buf = PreparePacket(type, data);
|
||||
|
||||
return s.SendPacketAsync(endPoint, buf) ? 0 : -1;
|
||||
}
|
||||
|
||||
public int SendPacket(LdnProxyTcpSession s, LanPacketType type, byte[] data)
|
||||
{
|
||||
byte[] buf = PreparePacket(type, data);
|
||||
|
||||
return s.SendAsync(buf) ? 0 : -1;
|
||||
}
|
||||
|
||||
private LanPacketHeader PrepareHeader(LanPacketHeader header, LanPacketType type)
|
||||
{
|
||||
header.Magic = LanMagic;
|
||||
header.Type = type;
|
||||
header.Compressed = 0;
|
||||
header.Length = 0;
|
||||
header.DecompressLength = 0;
|
||||
header.Reserved = new Array2<byte>();
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
private byte[] PreparePacket(LanPacketType type, byte[] data)
|
||||
{
|
||||
LanPacketHeader header = PrepareHeader(new LanPacketHeader(), type);
|
||||
header.Length = (ushort)data.Length;
|
||||
|
||||
byte[] buf;
|
||||
if (data.Length > 0)
|
||||
{
|
||||
if (Compress(data, out byte[] compressed) == 0)
|
||||
{
|
||||
header.DecompressLength = header.Length;
|
||||
header.Length = (ushort)compressed.Length;
|
||||
header.Compressed = 1;
|
||||
|
||||
buf = new byte[compressed.Length + _headerSize];
|
||||
|
||||
SpanHelpers.AsSpan<LanPacketHeader, byte>(ref header).ToArray().CopyTo(buf, 0);
|
||||
compressed.CopyTo(buf, _headerSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
buf = new byte[data.Length + _headerSize];
|
||||
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, "Compressing packet data failed.");
|
||||
|
||||
SpanHelpers.AsSpan<LanPacketHeader, byte>(ref header).ToArray().CopyTo(buf, 0);
|
||||
data.CopyTo(buf, _headerSize);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
buf = new byte[_headerSize];
|
||||
SpanHelpers.AsSpan<LanPacketHeader, byte>(ref header).ToArray().CopyTo(buf, 0);
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
private int Compress(byte[] input, out byte[] output)
|
||||
{
|
||||
List<byte> outputList = new();
|
||||
int i = 0;
|
||||
int maxCount = 0xFF;
|
||||
|
||||
while (i < input.Length)
|
||||
{
|
||||
byte inputByte = input[i++];
|
||||
int count = 0;
|
||||
|
||||
if (inputByte == 0)
|
||||
{
|
||||
while (i < input.Length && input[i] == 0 && count < maxCount)
|
||||
{
|
||||
count += 1;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (inputByte == 0)
|
||||
{
|
||||
outputList.Add(0);
|
||||
|
||||
if (outputList.Count == BufferSize)
|
||||
{
|
||||
output = null;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
outputList.Add((byte)count);
|
||||
}
|
||||
else
|
||||
{
|
||||
outputList.Add(inputByte);
|
||||
}
|
||||
}
|
||||
|
||||
output = outputList.ToArray();
|
||||
|
||||
return i == input.Length ? 0 : -1;
|
||||
}
|
||||
|
||||
private int Decompress(byte[] input, out byte[] output)
|
||||
{
|
||||
List<byte> outputList = new();
|
||||
int i = 0;
|
||||
|
||||
while (i < input.Length && outputList.Count < BufferSize)
|
||||
{
|
||||
byte inputByte = input[i++];
|
||||
|
||||
outputList.Add(inputByte);
|
||||
|
||||
if (inputByte == 0)
|
||||
{
|
||||
if (i == input.Length)
|
||||
{
|
||||
output = null;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int count = input[i++];
|
||||
|
||||
for (int j = 0; j < count; j++)
|
||||
{
|
||||
if (outputList.Count == BufferSize)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
outputList.Add(inputByte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output = outputList.ToArray();
|
||||
|
||||
return i == input.Length ? 0 : -1;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
|
||||
using System;
|
||||
using System.Net.NetworkInformation;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm
|
||||
{
|
||||
/// <summary>
|
||||
/// Client implementation for <a href="https://github.com/spacemeowx2/ldn_mitm">ldn_mitm</a>
|
||||
/// </summary>
|
||||
internal class LdnMitmClient : INetworkClient
|
||||
{
|
||||
public bool NeedsRealId => false;
|
||||
|
||||
public event EventHandler<NetworkChangeEventArgs> NetworkChange;
|
||||
|
||||
private readonly LanDiscovery _lanDiscovery;
|
||||
|
||||
public LdnMitmClient(HLEConfiguration config)
|
||||
{
|
||||
UnicastIPAddressInformation localIpInterface = NetworkHelpers.GetLocalInterface(config.MultiplayerLanInterfaceId).Item2;
|
||||
|
||||
_lanDiscovery = new LanDiscovery(this, localIpInterface.Address, localIpInterface.IPv4Mask);
|
||||
}
|
||||
|
||||
internal void InvokeNetworkChange(NetworkInfo info, bool connected, DisconnectReason reason = DisconnectReason.None)
|
||||
{
|
||||
NetworkChange?.Invoke(this, new NetworkChangeEventArgs(info, connected: connected, disconnectReason: reason));
|
||||
}
|
||||
|
||||
public NetworkError Connect(ConnectRequest request)
|
||||
{
|
||||
return _lanDiscovery.Connect(request.NetworkInfo, request.UserConfig, request.LocalCommunicationVersion);
|
||||
}
|
||||
|
||||
public NetworkError ConnectPrivate(ConnectPrivateRequest request)
|
||||
{
|
||||
// NOTE: This method is not implemented in ldn_mitm
|
||||
Logger.Stub?.PrintMsg(LogClass.ServiceLdn, "LdnMitmClient ConnectPrivate");
|
||||
|
||||
return NetworkError.None;
|
||||
}
|
||||
|
||||
public bool CreateNetwork(CreateAccessPointRequest request, byte[] advertiseData)
|
||||
{
|
||||
return _lanDiscovery.CreateNetwork(request.SecurityConfig, request.UserConfig, request.NetworkConfig);
|
||||
}
|
||||
|
||||
public bool CreateNetworkPrivate(CreateAccessPointPrivateRequest request, byte[] advertiseData)
|
||||
{
|
||||
// NOTE: This method is not implemented in ldn_mitm
|
||||
Logger.Stub?.PrintMsg(LogClass.ServiceLdn, "LdnMitmClient CreateNetworkPrivate");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void DisconnectAndStop()
|
||||
{
|
||||
_lanDiscovery.DisconnectAndStop();
|
||||
}
|
||||
|
||||
public void DisconnectNetwork()
|
||||
{
|
||||
_lanDiscovery.DestroyNetwork();
|
||||
}
|
||||
|
||||
public ResultCode Reject(DisconnectReason disconnectReason, uint nodeId)
|
||||
{
|
||||
// NOTE: This method is not implemented in ldn_mitm
|
||||
Logger.Stub?.PrintMsg(LogClass.ServiceLdn, "LdnMitmClient Reject");
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public NetworkInfo[] Scan(ushort channel, ScanFilter scanFilter)
|
||||
{
|
||||
return _lanDiscovery.Scan(channel, scanFilter);
|
||||
}
|
||||
|
||||
public void SetAdvertiseData(byte[] data)
|
||||
{
|
||||
_lanDiscovery.SetAdvertiseData(data);
|
||||
}
|
||||
|
||||
public void SetGameVersion(byte[] versionString)
|
||||
{
|
||||
// NOTE: This method is not implemented in ldn_mitm
|
||||
Logger.Stub?.PrintMsg(LogClass.ServiceLdn, "LdnMitmClient SetGameVersion");
|
||||
}
|
||||
|
||||
public void SetStationAcceptPolicy(AcceptPolicy acceptPolicy)
|
||||
{
|
||||
// NOTE: This method is not implemented in ldn_mitm
|
||||
Logger.Stub?.PrintMsg(LogClass.ServiceLdn, "LdnMitmClient SetStationAcceptPolicy");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_lanDiscovery.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy
|
||||
{
|
||||
internal interface ILdnSocket : IDisposable
|
||||
{
|
||||
bool SendPacketAsync(EndPoint endpoint, byte[] buffer);
|
||||
bool Start();
|
||||
bool Stop();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy
|
||||
{
|
||||
internal interface ILdnTcpSocket : ILdnSocket
|
||||
{
|
||||
bool Connect();
|
||||
void DisconnectAndStop();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy
|
||||
{
|
||||
internal class LdnProxyTcpClient : NetCoreServer.TcpClient, ILdnTcpSocket
|
||||
{
|
||||
private readonly LanProtocol _protocol;
|
||||
private byte[] _buffer;
|
||||
private int _bufferEnd;
|
||||
|
||||
public LdnProxyTcpClient(LanProtocol protocol, IPAddress address, int port) : base(address, port)
|
||||
{
|
||||
_protocol = protocol;
|
||||
_buffer = new byte[LanProtocol.BufferSize];
|
||||
OptionSendBufferSize = LanProtocol.TcpTxBufferSize;
|
||||
OptionReceiveBufferSize = LanProtocol.TcpRxBufferSize;
|
||||
OptionSendBufferLimit = LanProtocol.TxBufferSizeMax;
|
||||
OptionReceiveBufferLimit = LanProtocol.RxBufferSizeMax;
|
||||
}
|
||||
|
||||
protected override void OnConnected()
|
||||
{
|
||||
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyTCPClient connected!");
|
||||
}
|
||||
|
||||
protected override void OnReceived(byte[] buffer, long offset, long size)
|
||||
{
|
||||
_protocol.Read(ref _buffer, ref _bufferEnd, buffer, (int)offset, (int)size);
|
||||
}
|
||||
|
||||
public void DisconnectAndStop()
|
||||
{
|
||||
DisconnectAsync();
|
||||
|
||||
while (IsConnected)
|
||||
{
|
||||
Thread.Yield();
|
||||
}
|
||||
}
|
||||
|
||||
public bool SendPacketAsync(EndPoint endPoint, byte[] data)
|
||||
{
|
||||
if (endPoint != null)
|
||||
{
|
||||
Logger.Warning?.PrintMsg(LogClass.ServiceLdn, "LdnProxyTcpClient is sending a packet but endpoint is not null.");
|
||||
}
|
||||
|
||||
if (IsConnecting && !IsConnected)
|
||||
{
|
||||
Logger.Info?.PrintMsg(LogClass.ServiceLdn, "LdnProxyTCPClient needs to connect before sending packets. Waiting...");
|
||||
|
||||
while (IsConnecting && !IsConnected)
|
||||
{
|
||||
Thread.Yield();
|
||||
}
|
||||
}
|
||||
|
||||
return SendAsync(data);
|
||||
}
|
||||
|
||||
protected override void OnError(SocketError error)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyTCPClient caught an error with code {error}");
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposingManagedResources)
|
||||
{
|
||||
DisconnectAndStop();
|
||||
base.Dispose(disposingManagedResources);
|
||||
}
|
||||
|
||||
public override bool Connect()
|
||||
{
|
||||
// TODO: NetCoreServer has a Connect() method, but it currently leads to weird issues.
|
||||
base.ConnectAsync();
|
||||
|
||||
while (IsConnecting)
|
||||
{
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
|
||||
return IsConnected;
|
||||
}
|
||||
|
||||
public bool Start()
|
||||
{
|
||||
throw new InvalidOperationException("Start was called.");
|
||||
}
|
||||
|
||||
public bool Stop()
|
||||
{
|
||||
throw new InvalidOperationException("Stop was called.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
using NetCoreServer;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy
|
||||
{
|
||||
internal class LdnProxyTcpServer : TcpServer, ILdnTcpSocket
|
||||
{
|
||||
private readonly LanProtocol _protocol;
|
||||
|
||||
public LdnProxyTcpServer(LanProtocol protocol, IPAddress address, int port) : base(address, port)
|
||||
{
|
||||
_protocol = protocol;
|
||||
OptionReuseAddress = true;
|
||||
OptionSendBufferSize = LanProtocol.TcpTxBufferSize;
|
||||
OptionReceiveBufferSize = LanProtocol.TcpRxBufferSize;
|
||||
|
||||
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyTCPServer created a server for this address: {address}:{port}");
|
||||
}
|
||||
|
||||
protected override TcpSession CreateSession()
|
||||
{
|
||||
return new LdnProxyTcpSession(this, _protocol);
|
||||
}
|
||||
|
||||
protected override void OnError(SocketError error)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyTCPServer caught an error with code {error}");
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposingManagedResources)
|
||||
{
|
||||
Stop();
|
||||
base.Dispose(disposingManagedResources);
|
||||
}
|
||||
|
||||
public bool Connect()
|
||||
{
|
||||
throw new InvalidOperationException("Connect was called.");
|
||||
}
|
||||
|
||||
public void DisconnectAndStop()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
public bool SendPacketAsync(EndPoint endpoint, byte[] buffer)
|
||||
{
|
||||
throw new InvalidOperationException("SendPacketAsync was called.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy
|
||||
{
|
||||
internal class LdnProxyTcpSession : NetCoreServer.TcpSession
|
||||
{
|
||||
private readonly LanProtocol _protocol;
|
||||
|
||||
internal int NodeId;
|
||||
internal NodeInfo NodeInfo;
|
||||
|
||||
private byte[] _buffer;
|
||||
private int _bufferEnd;
|
||||
|
||||
public LdnProxyTcpSession(LdnProxyTcpServer server, LanProtocol protocol) : base(server)
|
||||
{
|
||||
_protocol = protocol;
|
||||
_protocol.Connect += OnConnect;
|
||||
_buffer = new byte[LanProtocol.BufferSize];
|
||||
OptionSendBufferSize = LanProtocol.TcpTxBufferSize;
|
||||
OptionReceiveBufferSize = LanProtocol.TcpRxBufferSize;
|
||||
OptionSendBufferLimit = LanProtocol.TxBufferSizeMax;
|
||||
OptionReceiveBufferLimit = LanProtocol.RxBufferSizeMax;
|
||||
}
|
||||
|
||||
public void OverrideInfo()
|
||||
{
|
||||
NodeInfo.NodeId = (byte)NodeId;
|
||||
NodeInfo.IsConnected = (byte)(IsConnected ? 1 : 0);
|
||||
}
|
||||
|
||||
protected override void OnConnected()
|
||||
{
|
||||
Logger.Info?.PrintMsg(LogClass.ServiceLdn, "LdnProxyTCPSession connected!");
|
||||
}
|
||||
|
||||
protected override void OnDisconnected()
|
||||
{
|
||||
Logger.Info?.PrintMsg(LogClass.ServiceLdn, "LdnProxyTCPSession disconnected!");
|
||||
|
||||
_protocol.InvokeDisconnectStation(this);
|
||||
}
|
||||
|
||||
protected override void OnReceived(byte[] buffer, long offset, long size)
|
||||
{
|
||||
_protocol.Read(ref _buffer, ref _bufferEnd, buffer, (int)offset, (int)size, this.Socket.RemoteEndPoint);
|
||||
}
|
||||
|
||||
protected override void OnError(SocketError error)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyTCPSession caught an error with code {error}");
|
||||
|
||||
Dispose();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposingManagedResources)
|
||||
{
|
||||
_protocol.Connect -= OnConnect;
|
||||
base.Dispose(disposingManagedResources);
|
||||
}
|
||||
|
||||
private void OnConnect(NodeInfo info, EndPoint endPoint)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (endPoint.Equals(this.Socket.RemoteEndPoint))
|
||||
{
|
||||
NodeInfo = info;
|
||||
_protocol.InvokeAccept(this);
|
||||
}
|
||||
}
|
||||
catch (System.ObjectDisposedException)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyTCPSession was disposed. [IP: {NodeInfo.Ipv4Address}]");
|
||||
|
||||
_protocol.InvokeDisconnectStation(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Types;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy
|
||||
{
|
||||
internal class LdnProxyUdpServer : NetCoreServer.UdpServer, ILdnSocket
|
||||
{
|
||||
private const long ScanFrequency = 1000;
|
||||
|
||||
private readonly LanProtocol _protocol;
|
||||
private byte[] _buffer;
|
||||
private int _bufferEnd;
|
||||
|
||||
private readonly object _scanLock = new();
|
||||
|
||||
private Dictionary<ulong, NetworkInfo> _scanResultsLast = new();
|
||||
private Dictionary<ulong, NetworkInfo> _scanResults = new();
|
||||
private readonly AutoResetEvent _scanResponse = new(false);
|
||||
private long _lastScanTime;
|
||||
|
||||
public LdnProxyUdpServer(LanProtocol protocol, IPAddress address, int port) : base(address, port)
|
||||
{
|
||||
_protocol = protocol;
|
||||
_protocol.Scan += HandleScan;
|
||||
_protocol.ScanResponse += HandleScanResponse;
|
||||
_buffer = new byte[LanProtocol.BufferSize];
|
||||
OptionReuseAddress = true;
|
||||
OptionReceiveBufferSize = LanProtocol.RxBufferSizeMax;
|
||||
OptionSendBufferSize = LanProtocol.TxBufferSizeMax;
|
||||
|
||||
Start();
|
||||
}
|
||||
|
||||
protected override Socket CreateSocket()
|
||||
{
|
||||
return new Socket(Endpoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp)
|
||||
{
|
||||
EnableBroadcast = true,
|
||||
};
|
||||
}
|
||||
|
||||
protected override void OnStarted()
|
||||
{
|
||||
ReceiveAsync();
|
||||
}
|
||||
|
||||
protected override void OnReceived(EndPoint endpoint, byte[] buffer, long offset, long size)
|
||||
{
|
||||
_protocol.Read(ref _buffer, ref _bufferEnd, buffer, (int)offset, (int)size, endpoint);
|
||||
ReceiveAsync();
|
||||
}
|
||||
|
||||
protected override void OnError(SocketError error)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyUdpServer caught an error with code {error}");
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposingManagedResources)
|
||||
{
|
||||
_protocol.Scan -= HandleScan;
|
||||
_protocol.ScanResponse -= HandleScanResponse;
|
||||
|
||||
_scanResponse.Dispose();
|
||||
|
||||
base.Dispose(disposingManagedResources);
|
||||
}
|
||||
|
||||
public bool SendPacketAsync(EndPoint endpoint, byte[] data)
|
||||
{
|
||||
return SendAsync(endpoint, data);
|
||||
}
|
||||
|
||||
private void HandleScan(EndPoint endpoint, LanPacketType type, byte[] data)
|
||||
{
|
||||
_protocol.SendPacket(this, type, data, endpoint);
|
||||
}
|
||||
|
||||
private void HandleScanResponse(NetworkInfo info)
|
||||
{
|
||||
Span<byte> mac = stackalloc byte[8];
|
||||
|
||||
info.Common.MacAddress.AsSpan().CopyTo(mac);
|
||||
|
||||
lock (_scanLock)
|
||||
{
|
||||
_scanResults[BitConverter.ToUInt64(mac)] = info;
|
||||
|
||||
_scanResponse.Set();
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearScanResults()
|
||||
{
|
||||
// Rate limit scans.
|
||||
|
||||
long timeMs = Stopwatch.GetTimestamp() / (Stopwatch.Frequency / 1000);
|
||||
long delay = ScanFrequency - (timeMs - _lastScanTime);
|
||||
|
||||
if (delay > 0)
|
||||
{
|
||||
Thread.Sleep((int)delay);
|
||||
}
|
||||
|
||||
_lastScanTime = timeMs;
|
||||
|
||||
lock (_scanLock)
|
||||
{
|
||||
var newResults = _scanResultsLast;
|
||||
newResults.Clear();
|
||||
|
||||
_scanResultsLast = _scanResults;
|
||||
_scanResults = newResults;
|
||||
|
||||
_scanResponse.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<ulong, NetworkInfo> GetScanResults()
|
||||
{
|
||||
// NOTE: Try to minimize waiting time for scan results.
|
||||
// After we receive the first response, wait a short time for follow-ups and return.
|
||||
// Responses that were too late to catch will appear in the next scan.
|
||||
|
||||
// ldn_mitm does not do this, but this improves latency for games that expect it to be low (it is on console).
|
||||
|
||||
if (_scanResponse.WaitOne(1000))
|
||||
{
|
||||
// Wait a short while longer in case there are some other responses.
|
||||
Thread.Sleep(33);
|
||||
}
|
||||
|
||||
lock (_scanLock)
|
||||
{
|
||||
var results = new Dictionary<ulong, NetworkInfo>();
|
||||
|
||||
foreach (KeyValuePair<ulong, NetworkInfo> last in _scanResultsLast)
|
||||
{
|
||||
results[last.Key] = last.Value;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<ulong, NetworkInfo> scan in _scanResults)
|
||||
{
|
||||
results[scan.Key] = scan.Value;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 12)]
|
||||
internal struct LanPacketHeader
|
||||
{
|
||||
public uint Magic;
|
||||
public LanPacketType Type;
|
||||
public byte Compressed;
|
||||
public ushort Length;
|
||||
public ushort DecompressLength;
|
||||
public Array2<byte> Reserved;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Types
|
||||
{
|
||||
internal enum LanPacketType : byte
|
||||
{
|
||||
Scan,
|
||||
ScanResponse,
|
||||
Connect,
|
||||
SyncNetwork,
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
{
|
||||
class NetworkChangeEventArgs : EventArgs
|
||||
{
|
|
@ -1,7 +1,6 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Network.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
|
@ -22,7 +21,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||
_parent.NetworkClient.NetworkChange += NetworkChanged;
|
||||
}
|
||||
|
||||
private void NetworkChanged(object sender, RyuLdn.NetworkChangeEventArgs e)
|
||||
private void NetworkChanged(object sender, NetworkChangeEventArgs e)
|
||||
{
|
||||
LatestUpdates.CalculateLatestUpdate(NetworkInfo.Ldn.Nodes, e.Info.Ldn.Nodes);
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0xBC)]
|
||||
struct ConnectPrivateRequest
|
|
@ -1,7 +1,7 @@
|
|||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Network.Types
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x4FC)]
|
||||
struct ConnectRequest
|
|
@ -1,7 +1,7 @@
|
|||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types
|
||||
{
|
||||
/// <remarks>
|
||||
/// Advertise data is appended separately (remaining data in the buffer).
|
|
@ -1,7 +1,7 @@
|
|||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Network.Types
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types
|
||||
{
|
||||
/// <remarks>
|
||||
/// Advertise data is appended separately (remaining data in the buffer).
|
|
@ -1,4 +1,4 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types
|
||||
{
|
||||
enum NetworkError : int
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x4)]
|
||||
struct NetworkErrorMessage
|
|
@ -20,7 +20,11 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
|||
private static readonly DownloadableContentJsonSerializerContext _contentSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
private static readonly TitleUpdateMetadataJsonSerializerContext _titleSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
internal static (bool, ProcessResult) TryLoad(this PartitionFileSystem partitionFileSystem, Switch device, string path, out string errorMessage)
|
||||
internal static (bool, ProcessResult) TryLoad<TMetaData, TFormat, THeader, TEntry>(this PartitionFileSystemCore<TMetaData, TFormat, THeader, TEntry> partitionFileSystem, Switch device, string path, out string errorMessage)
|
||||
where TMetaData : PartitionFileSystemMetaCore<TFormat, THeader, TEntry>, new()
|
||||
where TFormat : IPartitionFileSystemFormat
|
||||
where THeader : unmanaged, IPartitionFileSystemHeader
|
||||
where TEntry : unmanaged, IPartitionFileSystemEntry
|
||||
{
|
||||
errorMessage = null;
|
||||
|
||||
|
@ -91,7 +95,8 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
|||
string updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, _titleSerializerContext.TitleUpdateMetadata).Selected;
|
||||
if (File.Exists(updatePath))
|
||||
{
|
||||
PartitionFileSystem updatePartitionFileSystem = new(new FileStream(updatePath, FileMode.Open, FileAccess.Read).AsStorage());
|
||||
PartitionFileSystem updatePartitionFileSystem = new();
|
||||
updatePartitionFileSystem.Initialize(new FileStream(updatePath, FileMode.Open, FileAccess.Read).AsStorage()).ThrowIfFailure();
|
||||
|
||||
device.Configuration.VirtualFileSystem.ImportTickets(updatePartitionFileSystem);
|
||||
|
||||
|
|
|
@ -69,7 +69,8 @@ namespace Ryujinx.HLE.Loaders.Processes
|
|||
public bool LoadNsp(string path)
|
||||
{
|
||||
FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
||||
PartitionFileSystem partitionFileSystem = new(file.AsStorage());
|
||||
PartitionFileSystem partitionFileSystem = new();
|
||||
partitionFileSystem.Initialize(file.AsStorage()).ThrowIfFailure();
|
||||
|
||||
(bool success, ProcessResult processResult) = partitionFileSystem.TryLoad(_device, path, out string errorMessage);
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
using LibHac.Account;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Fs.Shim;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Loader;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Ns;
|
||||
|
@ -33,7 +33,7 @@ namespace Ryujinx.HLE.Loaders.Processes
|
|||
// TODO: Remove this workaround when ASLR is implemented.
|
||||
private const ulong CodeStartOffset = 0x500000UL;
|
||||
|
||||
public static LibHac.Result RegisterProgramMapInfo(Switch device, PartitionFileSystem partitionFileSystem)
|
||||
public static LibHac.Result RegisterProgramMapInfo(Switch device, IFileSystem partitionFileSystem)
|
||||
{
|
||||
ulong applicationId = 0;
|
||||
int programCount = 0;
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
<PackageReference Include="SixLabors.ImageSharp" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" />
|
||||
<PackageReference Include="NetCoreServer" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Due to Concentus. -->
|
||||
|
|
|
@ -65,7 +65,7 @@ namespace Ryujinx.Ui.App.Common
|
|||
|
||||
if (extension is ".nsp" or ".xci")
|
||||
{
|
||||
PartitionFileSystem pfs;
|
||||
IFileSystem pfs;
|
||||
|
||||
if (extension == ".xci")
|
||||
{
|
||||
|
@ -75,7 +75,9 @@ namespace Ryujinx.Ui.App.Common
|
|||
}
|
||||
else
|
||||
{
|
||||
pfs = new PartitionFileSystem(file.AsStorage());
|
||||
var pfsTemp = new PartitionFileSystem();
|
||||
pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
|
||||
pfs = pfsTemp;
|
||||
}
|
||||
|
||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||
|
|
|
@ -174,7 +174,7 @@ namespace Ryujinx.Ui.App.Common
|
|||
{
|
||||
try
|
||||
{
|
||||
PartitionFileSystem pfs;
|
||||
IFileSystem pfs;
|
||||
|
||||
bool isExeFs = false;
|
||||
|
||||
|
@ -186,7 +186,9 @@ namespace Ryujinx.Ui.App.Common
|
|||
}
|
||||
else
|
||||
{
|
||||
pfs = new PartitionFileSystem(file.AsStorage());
|
||||
var pfsTemp = new PartitionFileSystem();
|
||||
pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
|
||||
pfs = pfsTemp;
|
||||
|
||||
// If the NSP doesn't have a main NCA, decrement the number of applications found and then continue to the next application.
|
||||
bool hasMainNca = false;
|
||||
|
@ -500,7 +502,7 @@ namespace Ryujinx.Ui.App.Common
|
|||
ApplicationCountUpdated?.Invoke(null, e);
|
||||
}
|
||||
|
||||
private void GetControlFsAndTitleId(PartitionFileSystem pfs, out IFileSystem controlFs, out string titleId)
|
||||
private void GetControlFsAndTitleId(IFileSystem pfs, out IFileSystem controlFs, out string titleId)
|
||||
{
|
||||
(_, _, Nca controlNca) = GetGameData(_virtualFileSystem, pfs, 0);
|
||||
|
||||
|
@ -563,7 +565,7 @@ namespace Ryujinx.Ui.App.Common
|
|||
{
|
||||
try
|
||||
{
|
||||
PartitionFileSystem pfs;
|
||||
IFileSystem pfs;
|
||||
|
||||
bool isExeFs = false;
|
||||
|
||||
|
@ -575,7 +577,9 @@ namespace Ryujinx.Ui.App.Common
|
|||
}
|
||||
else
|
||||
{
|
||||
pfs = new PartitionFileSystem(file.AsStorage());
|
||||
var pfsTemp = new PartitionFileSystem();
|
||||
pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
|
||||
pfs = pfsTemp;
|
||||
|
||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*"))
|
||||
{
|
||||
|
@ -827,7 +831,7 @@ namespace Ryujinx.Ui.App.Common
|
|||
return false;
|
||||
}
|
||||
|
||||
public static (Nca main, Nca patch, Nca control) GetGameData(VirtualFileSystem fileSystem, PartitionFileSystem pfs, int programIndex)
|
||||
public static (Nca main, Nca patch, Nca control) GetGameData(VirtualFileSystem fileSystem, IFileSystem pfs, int programIndex)
|
||||
{
|
||||
Nca mainNca = null;
|
||||
Nca patchNca = null;
|
||||
|
@ -936,7 +940,8 @@ namespace Ryujinx.Ui.App.Common
|
|||
if (File.Exists(updatePath))
|
||||
{
|
||||
FileStream file = new(updatePath, FileMode.Open, FileAccess.Read);
|
||||
PartitionFileSystem nsp = new(file.AsStorage());
|
||||
PartitionFileSystem nsp = new();
|
||||
nsp.Initialize(file.AsStorage()).ThrowIfFailure();
|
||||
|
||||
return GetGameUpdateDataFromPartition(fileSystem, nsp, titleIdBase.ToString("x16"), programIndex);
|
||||
}
|
||||
|
|
|
@ -571,6 +571,7 @@ namespace Ryujinx.Ui.Common.Configuration
|
|||
{
|
||||
LanInterfaceId = new ReactiveObject<string>();
|
||||
Mode = new ReactiveObject<MultiplayerMode>();
|
||||
Mode.Event += static (_, e) => LogValueChange(e, nameof(MultiplayerMode));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1156,6 +1156,14 @@ namespace Ryujinx.Ui
|
|||
Graphics.Gpu.GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE;
|
||||
}
|
||||
|
||||
public void UpdateInternetAccess()
|
||||
{
|
||||
if (_gameLoaded)
|
||||
{
|
||||
_emulationContext.Configuration.EnableInternetAccess = ConfigurationState.Instance.System.EnableInternetAccess.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SaveConfig()
|
||||
{
|
||||
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||
|
|
|
@ -211,7 +211,7 @@ namespace Ryujinx.Ui.Widgets
|
|||
(System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".pfs0") ||
|
||||
(System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".xci"))
|
||||
{
|
||||
PartitionFileSystem pfs;
|
||||
IFileSystem pfs;
|
||||
|
||||
if (System.IO.Path.GetExtension(_titleFilePath) == ".xci")
|
||||
{
|
||||
|
@ -221,7 +221,9 @@ namespace Ryujinx.Ui.Widgets
|
|||
}
|
||||
else
|
||||
{
|
||||
pfs = new PartitionFileSystem(file.AsStorage());
|
||||
var pfsTemp = new PartitionFileSystem();
|
||||
pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
|
||||
pfs = pfsTemp;
|
||||
}
|
||||
|
||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||
|
|
|
@ -88,7 +88,8 @@ namespace Ryujinx.Ui.Windows
|
|||
|
||||
using FileStream containerFile = File.OpenRead(dlcContainer.ContainerPath);
|
||||
|
||||
PartitionFileSystem pfs = new(containerFile.AsStorage());
|
||||
PartitionFileSystem pfs = new();
|
||||
pfs.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
||||
|
||||
_virtualFileSystem.ImportTickets(pfs);
|
||||
|
||||
|
@ -153,7 +154,8 @@ namespace Ryujinx.Ui.Windows
|
|||
|
||||
using FileStream containerFile = File.OpenRead(containerPath);
|
||||
|
||||
PartitionFileSystem pfs = new(containerFile.AsStorage());
|
||||
PartitionFileSystem pfs = new();
|
||||
pfs.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
||||
bool containsDlc = false;
|
||||
|
||||
_virtualFileSystem.ImportTickets(pfs);
|
||||
|
|
|
@ -671,6 +671,8 @@ namespace Ryujinx.Ui.Windows
|
|||
}
|
||||
|
||||
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||
|
||||
_parent.UpdateInternetAccess();
|
||||
MainWindow.UpdateGraphicsConfig();
|
||||
ThemeHelper.ApplyTheme();
|
||||
}
|
||||
|
|
|
@ -2993,6 +2993,7 @@
|
|||
<property name="active-id">Disabled</property>
|
||||
<items>
|
||||
<item id="Disabled" translatable="yes">Disabled</item>
|
||||
<item id="LdnMitm" translatable="yes">ldn_mitm</item>
|
||||
</items>
|
||||
</object>
|
||||
<packing>
|
||||
|
@ -3064,7 +3065,7 @@
|
|||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="tooltip-text" translatable="yes">The network interface used for LAN features</property>
|
||||
<property name="tooltip-text" translatable="yes">The network interface used for LAN/LDN features</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="label" translatable="yes">Network Interface:</property>
|
||||
</object>
|
||||
|
@ -3079,7 +3080,7 @@
|
|||
<object class="GtkComboBoxText" id="_multiLanSelect">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="tooltip-text" translatable="yes">The network interface used for LAN features</property>
|
||||
<property name="tooltip-text" translatable="yes">The network interface used for LAN/LDN features</property>
|
||||
<property name="active-id">0</property>
|
||||
<items>
|
||||
<item id="0" translatable="yes">Default</item>
|
||||
|
|
|
@ -96,7 +96,8 @@ namespace Ryujinx.Ui.Windows
|
|||
{
|
||||
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
||||
|
||||
PartitionFileSystem nsp = new(file.AsStorage());
|
||||
PartitionFileSystem nsp = new();
|
||||
nsp.Initialize(file.AsStorage()).ThrowIfFailure();
|
||||
|
||||
try
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue