Add Game names instead of filenames and remove "disable vsync" option

This commit is contained in:
Stossy11 2024-12-09 10:40:46 +11:00
parent 5163737886
commit fdbcc483b3
8 changed files with 693 additions and 24 deletions

View File

@ -15,9 +15,19 @@
extern "C" {
#endif
struct GameInfo {
long FileSize;
char TitleName[512];
long TitleId;
char Developer[256];
int Version;
};
extern struct GameInfo get_game_info(int, char*);
// Declare the main_ryujinx_sdl function, matching the signature
int main_ryujinx_sdl(int argc, char **argv);
void initialize();
const char* get_game_controllers();
#ifdef __cplusplus

View File

@ -50,7 +50,6 @@ class Ryujinx {
var listinputids: Bool
var fullscreen: Bool
var memoryManagerMode: String
var disableVSync: Bool
var disableShaderCache: Bool
var disableDockedMode: Bool
var enableTextureRecompression: Bool
@ -64,7 +63,6 @@ class Ryujinx {
listinputids: Bool = false,
fullscreen: Bool = true,
memoryManagerMode: String = "HostMapped",
disableVSync: Bool = false,
disableShaderCache: Bool = false,
disableDockedMode: Bool = false,
nintendoinput: Bool = true,
@ -79,7 +77,6 @@ class Ryujinx {
self.tracelogs = tracelogs
self.listinputids = listinputids
self.fullscreen = fullscreen
self.disableVSync = disableVSync
self.disableShaderCache = disableShaderCache
self.disableDockedMode = disableDockedMode
self.enableTextureRecompression = enableTextureRecompression
@ -157,10 +154,6 @@ class Ryujinx {
args.append("--correct-controller")
}
// Adding default args directly into additionalArgs
if config.disableVSync {
// args.append("--disable-vsync")
}
args.append("--disable-vsync")

View File

@ -130,6 +130,7 @@ struct ContentView: View {
SDL_SetMainReady()
SDL_iPhoneSetEventPump(SDL_TRUE)
SDL_Init(SdlInitFlags)
initialize()
}
private func setupEmulation() {

View File

@ -7,17 +7,33 @@
// MARK: - This will most likely not be used in prod
import SwiftUI
import UniformTypeIdentifiers
public struct Game: Identifiable, Equatable {
public var id = UUID()
var containerFolder: URL
var fileType: UTType
var fileURL: URL
var titleName: String
var titleId: String
var developer: String
var version: String
var icon: Image?
}
struct GameListView: View {
@Binding var startemu: URL?
@State private var games: [URL] = []
@State private var games: [Game] = []
var body: some View {
List(games, id: \.self) { game in
List($games, id: \.id) { $game in
Button {
startemu = game
startemu = $game.wrappedValue.fileURL
} label: {
Text(game.lastPathComponent)
Text(game.titleName)
}
}
.navigationTitle("Games")
@ -42,7 +58,41 @@ struct GameListView: View {
// Load games only from "roms" folder
do {
let files = try fileManager.contentsOfDirectory(at: romsDirectory, includingPropertiesForKeys: nil)
games = files
files.forEach { fileURLCandidate in
do {
let handle = try FileHandle(forReadingFrom: fileURLCandidate)
let fileExtension = (fileURLCandidate.pathExtension as NSString).utf8String
let extensionPtr = UnsafeMutablePointer<CChar>(mutating: fileExtension)
var gameInfo = get_game_info(handle.fileDescriptor, extensionPtr)
var game = Game(containerFolder: romsDirectory, fileType: .item, fileURL: fileURLCandidate, titleName: "", titleId: "", developer: "", version: "")
game.titleName = withUnsafePointer(to: &gameInfo.TitleName) {
$0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: $0)) {
String(cString: $0)
}
}
game.developer = withUnsafePointer(to: &gameInfo.Developer) {
$0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: $0)) {
String(cString: $0)
}
}
game.titleId = String(gameInfo.TitleId)
game.version = String(gameInfo.Version)
games.append(game)
} catch {
print(error)
}
}
} catch {
print("Error loading games from roms folder: \(error)")
}

View File

@ -27,7 +27,6 @@ struct SettingsView: View {
VStack {
Section(header: Title("Graphics and Performance")) {
Toggle("Ryujinx Fullscreen", isOn: $config.fullscreen)
Toggle("Disable V-Sync", isOn: $config.disableVSync)
Toggle("Disable Shader Cache", isOn: $config.disableShaderCache)
Toggle("Enable Texture Recompression", isOn: $config.enableTextureRecompression)
Toggle("Disable Docked Mode", isOn: $config.disableDockedMode)
@ -72,7 +71,6 @@ struct SettingsView: View {
//TextField("Game Path", text: $config.gamepath)
Text("PageSize \(String(Int(getpagesize())))")
Toggle("Ignore JIT Enabeld Popup", isOn: $ignoreJIT)
TextField("Additional Arguments", text: Binding(
get: {
config.additionalArgs.joined(separator: ", ")

View File

@ -29,6 +29,9 @@ using Ryujinx.Input;
using Ryujinx.Input.HLE;
using Ryujinx.Input.SDL2;
using Ryujinx.SDL2.Common;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Configuration.System;
using Ryujinx.Ui.Common;
using Silk.NET.Vulkan;
using System;
using System.Collections.Generic;
@ -40,6 +43,56 @@ using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.Gamepad
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
using Key = Ryujinx.Common.Configuration.Hid.Key;
using System.Linq;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.HOS;
using Ryujinx.Input.HLE;
using Ryujinx.HLE;
using System;
using System.Runtime.InteropServices;
using Ryujinx.Common.Configuration;
using LibHac.Tools.FsSystem;
using Ryujinx.Graphics.GAL.Multithreading;
using Ryujinx.Audio.Backends.Dummy;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Audio.Integration;
using Ryujinx.Audio.Backends.SDL2;
using System.IO;
using LibHac.Common.Keys;
using LibHac.Common;
using LibHac.Ns;
using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem.NcaUtils;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.Fs;
using Path = System.IO.Path;
using LibHac;
using Ryujinx.Common.Configuration.Multiplayer;
using Ryujinx.HLE.Loaders.Npdm;
using Ryujinx.Common.Utilities;
using System.Globalization;
using Ryujinx.Ui.Common.Configuration.System;
using Ryujinx.Common.Logging.Targets;
using System.Collections.Generic;
using LibHac.Bcat;
using Ryujinx.Ui.App.Common;
using System.Text;
using Ryujinx.HLE.Ui;
using ARMeilleure.Translation;
using LibHac.Ncm;
using LibHac.Tools.FsSystem.NcaUtils;
using Microsoft.Win32.SafeHandles;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.Input.HLE;
using Silk.NET.Vulkan;
using System;
using System.IO;
using System.Runtime.InteropServices;
public class GamepadInfo
{
@ -93,6 +146,42 @@ namespace Ryujinx.Headless.SDL2
return 0;
}
[UnmanagedCallersOnly(EntryPoint = "initialize")]
public static unsafe void Initialize()
{
AppDataManager.Initialize(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments));
if (_virtualFileSystem == null)
{
_virtualFileSystem = VirtualFileSystem.CreateInstance();
}
if (_libHacHorizonManager == null)
{
_libHacHorizonManager = new LibHacHorizonManager();
_libHacHorizonManager.InitializeFsServer(_virtualFileSystem);
_libHacHorizonManager.InitializeArpServer();
_libHacHorizonManager.InitializeBcatServer();
_libHacHorizonManager.InitializeSystemClients();
}
if (_contentManager == null)
{
_contentManager = new ContentManager(_virtualFileSystem);
}
if (_accountManager == null)
{
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, "");
}
if (_userChannelPersistence == null)
{
_userChannelPersistence = new UserChannelPersistence();
}
}
static void Main(string[] args)
{
// Make process DPI aware for proper window sizing on high-res screens.
@ -173,6 +262,465 @@ namespace Ryujinx.Headless.SDL2
return ptr;
}
[UnmanagedCallersOnly(EntryPoint = "get_game_info")]
public static GameInfoNative GetGameInfoNative(int descriptor, IntPtr extensionPtr)
{
if (_virtualFileSystem == null) {
_virtualFileSystem = VirtualFileSystem.CreateInstance();
}
var extension = Marshal.PtrToStringAnsi(extensionPtr);
var stream = OpenFile(descriptor);
var gameInfo = GetGameInfo(stream, extension);
return new GameInfoNative(0, gameInfo.TitleName, 0, gameInfo.Developer, 0);
}
public static GameInfo? GetGameInfo(Stream gameStream, string extension)
{
var gameInfo = new GameInfo
{
FileSize = gameStream.Length * 0.000000000931,
TitleName = "Unknown",
TitleId = "0000000000000000",
Developer = "Unknown",
Version = "0",
Icon = null
};
const Language TitleLanguage = Language.AmericanEnglish;
BlitStruct<ApplicationControlProperty> controlHolder = new(1);
try
{
try
{
if (extension == "nsp" || extension == "pfs0" || extension == "xci")
{
IFileSystem pfs;
bool isExeFs = false;
if (extension == "xci")
{
Xci xci = new(_virtualFileSystem.KeySet, gameStream.AsStorage());
pfs = xci.OpenPartition(XciPartitionType.Secure);
}
else
{
var pfsTemp = new PartitionFileSystem();
pfsTemp.Initialize(gameStream.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;
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*"))
{
if (Path.GetExtension(fileEntry.FullPath).ToLower() == ".nca")
{
using UniqueRef<IFile> ncaFile = new();
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = new(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
// Some main NCAs don't have a data partition, so check if the partition exists before opening it
if (nca.Header.ContentType == NcaContentType.Program && !(nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection()))
{
hasMainNca = true;
break;
}
}
else if (Path.GetFileNameWithoutExtension(fileEntry.FullPath) == "main")
{
isExeFs = true;
}
}
if (!hasMainNca && !isExeFs)
{
return null;
}
}
if (isExeFs)
{
using UniqueRef<IFile> npdmFile = new();
LibHac.Result result = pfs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read);
if (ResultFs.PathNotFound.Includes(result))
{
Npdm npdm = new(npdmFile.Get.AsStream());
gameInfo.TitleName = npdm.TitleName;
gameInfo.TitleId = npdm.Aci0.TitleId.ToString("x16");
}
}
else
{
GetControlFsAndTitleId(pfs, out IFileSystem? controlFs, out string? id);
gameInfo.TitleId = id;
if (controlFs == null)
{
Logger.Error?.Print(LogClass.Application, $"No control FS was returned. Unable to process game any further: {gameInfo.TitleName}");
return null;
}
// Check if there is an update available.
if (IsUpdateApplied(gameInfo.TitleId, out IFileSystem? updatedControlFs))
{
// Replace the original ControlFs by the updated one.
controlFs = updatedControlFs;
}
ReadControlData(controlFs, controlHolder.ByteSpan);
GetGameInformation(ref controlHolder.Value, out gameInfo.TitleName, out _, out gameInfo.Developer, out gameInfo.Version);
// Read the icon from the ControlFS and store it as a byte array
try
{
using UniqueRef<IFile> icon = new();
controlFs?.OpenFile(ref icon.Ref, $"/icon_{TitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure();
using MemoryStream stream = new();
icon.Get.AsStream().CopyTo(stream);
gameInfo.Icon = stream.ToArray();
}
catch (HorizonResultException)
{
foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*"))
{
if (entry.Name == "control.nacp")
{
continue;
}
using var icon = new UniqueRef<IFile>();
controlFs?.OpenFile(ref icon.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
using MemoryStream stream = new();
icon.Get.AsStream().CopyTo(stream);
gameInfo.Icon = stream.ToArray();
if (gameInfo.Icon != null)
{
break;
}
}
}
}
}
else if (extension == "nro")
{
BinaryReader reader = new(gameStream);
byte[] Read(long position, int size)
{
gameStream.Seek(position, SeekOrigin.Begin);
return reader.ReadBytes(size);
}
gameStream.Seek(24, SeekOrigin.Begin);
int assetOffset = reader.ReadInt32();
if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET")
{
byte[] iconSectionInfo = Read(assetOffset + 8, 0x10);
long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0);
long iconSize = BitConverter.ToInt64(iconSectionInfo, 8);
ulong nacpOffset = reader.ReadUInt64();
ulong nacpSize = reader.ReadUInt64();
// Reads and stores game icon as byte array
if (iconSize > 0)
{
gameInfo.Icon = Read(assetOffset + iconOffset, (int)iconSize);
}
// Read the NACP data
Read(assetOffset + (int)nacpOffset, (int)nacpSize).AsSpan().CopyTo(controlHolder.ByteSpan);
GetGameInformation(ref controlHolder.Value, out gameInfo.TitleName, out _, out gameInfo.Developer, out gameInfo.Version);
}
}
}
catch (MissingKeyException exception)
{
Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}");
}
catch (InvalidDataException exception)
{
Logger.Warning?.Print(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. {exception}");
}
catch (Exception exception)
{
Logger.Warning?.Print(LogClass.Application, $"The gameStream encountered was not of a valid type. Error: {exception}");
return null;
}
}
catch (IOException exception)
{
Logger.Warning?.Print(LogClass.Application, exception.Message);
}
void ReadControlData(IFileSystem? controlFs, Span<byte> outProperty)
{
using UniqueRef<IFile> controlFile = new();
controlFs?.OpenFile(ref controlFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
controlFile.Get.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure();
}
void GetGameInformation(ref ApplicationControlProperty controlData, out string? titleName, out string titleId, out string? publisher, out string? version)
{
_ = Enum.TryParse(TitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
if (controlData.Title.ItemsRo.Length > (int)desiredTitleLanguage)
{
titleName = controlData.Title[(int)desiredTitleLanguage].NameString.ToString();
publisher = controlData.Title[(int)desiredTitleLanguage].PublisherString.ToString();
}
else
{
titleName = null;
publisher = null;
}
if (string.IsNullOrWhiteSpace(titleName))
{
foreach (ref readonly var controlTitle in controlData.Title.ItemsRo)
{
if (!controlTitle.NameString.IsEmpty())
{
titleName = controlTitle.NameString.ToString();
break;
}
}
}
if (string.IsNullOrWhiteSpace(publisher))
{
foreach (ref readonly var controlTitle in controlData.Title.ItemsRo)
{
if (!controlTitle.PublisherString.IsEmpty())
{
publisher = controlTitle.PublisherString.ToString();
break;
}
}
}
if (controlData.PresenceGroupId != 0)
{
titleId = controlData.PresenceGroupId.ToString("x16");
}
else if (controlData.SaveDataOwnerId != 0)
{
titleId = controlData.SaveDataOwnerId.ToString();
}
else if (controlData.AddOnContentBaseId != 0)
{
titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("x16");
}
else
{
titleId = "0000000000000000";
}
version = controlData.DisplayVersionString.ToString();
}
void GetControlFsAndTitleId(IFileSystem pfs, out IFileSystem? controlFs, out string? titleId)
{
(_, _, Nca? controlNca) = GetGameData(_virtualFileSystem, pfs, 0);
if (controlNca == null)
{
Logger.Warning?.Print(LogClass.Application, "Control NCA is null. Unable to load control FS.");
}
// Return the ControlFS
controlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
titleId = controlNca?.Header.TitleId.ToString("x16");
}
(Nca? mainNca, Nca? patchNca, Nca? controlNca) GetGameData(VirtualFileSystem fileSystem, IFileSystem pfs, int programIndex)
{
Nca? mainNca = null;
Nca? patchNca = null;
Nca? controlNca = null;
fileSystem.ImportTickets(pfs);
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
{
using var ncaFile = new UniqueRef<IFile>();
Logger.Info?.Print(LogClass.Application, $"Loading file from PFS: {fileEntry.FullPath}");
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = new(fileSystem.KeySet, ncaFile.Release().AsStorage());
int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
if (ncaProgramIndex != programIndex)
{
continue;
}
if (nca.Header.ContentType == NcaContentType.Program)
{
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())
{
patchNca = nca;
}
else
{
mainNca = nca;
}
}
else if (nca.Header.ContentType == NcaContentType.Control)
{
controlNca = nca;
}
}
return (mainNca, patchNca, controlNca);
}
bool IsUpdateApplied(string? titleId, out IFileSystem? updatedControlFs)
{
updatedControlFs = null;
string? updatePath = "(unknown)";
if (_virtualFileSystem == null)
{
Logger.Error?.Print(LogClass.Application, "SwitchDevice was not initialized.");
return false;
}
try
{
(Nca? patchNca, Nca? controlNca) = GetGameUpdateData(_virtualFileSystem, titleId, 0, out updatePath);
if (patchNca != null && controlNca != null)
{
updatedControlFs = controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
return true;
}
}
catch (InvalidDataException)
{
Logger.Warning?.Print(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {updatePath}");
}
catch (MissingKeyException exception)
{
Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}. Errored File: {updatePath}");
}
return false;
}
(Nca? patch, Nca? control) GetGameUpdateData(VirtualFileSystem fileSystem, string? titleId, int programIndex, out string? updatePath)
{
updatePath = "";
if (ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase))
{
// Clear the program index part.
titleIdBase &= ~0xFUL;
// Load update information if exists.
string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json");
if (File.Exists(titleUpdateMetadataPath))
{
// updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath).Selected;
if (File.Exists(updatePath))
{
FileStream file = new(updatePath, FileMode.Open, FileAccess.Read);
PartitionFileSystem nsp = new();
nsp.Initialize(file.AsStorage()).ThrowIfFailure();
return GetGameUpdateDataFromPartition(fileSystem, nsp, titleIdBase.ToString("x16"), programIndex);
}
}
}
return (null, null);
}
(Nca? patchNca, Nca? controlNca) GetGameUpdateDataFromPartition(VirtualFileSystem fileSystem, PartitionFileSystem pfs, string titleId, int programIndex)
{
Nca? patchNca = null;
Nca? controlNca = null;
fileSystem.ImportTickets(pfs);
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
{
using var ncaFile = new UniqueRef<IFile>();
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = new(fileSystem.KeySet, ncaFile.Release().AsStorage());
int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
if (ncaProgramIndex != programIndex)
{
continue;
}
if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleId)
{
break;
}
if (nca.Header.ContentType == NcaContentType.Program)
{
patchNca = nca;
}
else if (nca.Header.ContentType == NcaContentType.Control)
{
controlNca = nca;
}
}
return (patchNca, controlNca);
}
return gameInfo;
}
private static InputConfig HandlePlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index, Options option)
{
if (inputId == null)
@ -207,7 +755,11 @@ namespace Ryujinx.Headless.SDL2
{
Logger.Error?.Print(LogClass.Application, $"{index} gamepad not found (\"{inputId}\")");
return null;
inputId = "0";
gamepad = _inputManager.KeyboardDriver.GetGamepad(inputId);
isKeyboard = true;
}
}
@ -408,17 +960,34 @@ namespace Ryujinx.Headless.SDL2
{
AppDataManager.Initialize(option.BaseDataDir);
if (_virtualFileSystem == null)
{
_virtualFileSystem = VirtualFileSystem.CreateInstance();
_libHacHorizonManager = new LibHacHorizonManager();
}
if (_libHacHorizonManager == null)
{
_libHacHorizonManager = new LibHacHorizonManager();
_libHacHorizonManager.InitializeFsServer(_virtualFileSystem);
_libHacHorizonManager.InitializeArpServer();
_libHacHorizonManager.InitializeBcatServer();
_libHacHorizonManager.InitializeSystemClients();
}
if (_contentManager == null)
{
_contentManager = new ContentManager(_virtualFileSystem);
}
if (_accountManager == null)
{
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, option.UserProfile);
}
if (_userChannelPersistence == null)
{
_userChannelPersistence = new UserChannelPersistence();
}
if (_inputManager == null)
{
@ -800,5 +1369,51 @@ namespace Ryujinx.Headless.SDL2
return true;
}
private static FileStream OpenFile(int descriptor)
{
var safeHandle = new SafeFileHandle(descriptor, false);
return new FileStream(safeHandle, FileAccess.ReadWrite);
}
public class GameInfo
{
public double FileSize;
public string? TitleName;
public string? TitleId;
public string? Developer;
public string? Version;
public byte[]? Icon;
}
public unsafe struct GameInfoNative
{
public ulong FileSize;
public fixed byte TitleName[512];
public ulong TitleId;
public fixed byte Developer[256];
public uint Version;
public GameInfoNative(ulong fileSize, string titleName, ulong titleId, string developer, uint version)
{
FileSize = fileSize;
TitleId = titleId;
Version = version;
fixed (byte* developerPtr = Developer)
fixed (byte* titleNamePtr = TitleName)
{
CopyStringToFixedArray(titleName, titleNamePtr, 512);
CopyStringToFixedArray(developer, developerPtr, 256);
}
}
private void CopyStringToFixedArray(string source, byte* destination, int length)
{
var span = new Span<byte>(destination, length);
Encoding.UTF8.GetBytes(source, span);
}
}
}
}

View File

@ -97,6 +97,8 @@
<ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" />
<ProjectReference Include="..\Ryujinx.Ui.Common\Ryujinx.Ui.Common.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" />
</ItemGroup>
<ItemGroup>