forked from MeloNX/MeloNX
Compare commits
3 Commits
500f3d5b9e
...
a346d2b1fd
Author | SHA1 | Date | |
---|---|---|---|
a346d2b1fd | |||
997deb53cf | |||
14eee7fadd |
@ -31,21 +31,8 @@ struct GameInfo {
|
|||||||
unsigned int ImageSize;
|
unsigned int ImageSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DlcNcaListItem {
|
|
||||||
char Path[256];
|
|
||||||
unsigned long TitleId;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DlcNcaList {
|
|
||||||
bool success;
|
|
||||||
unsigned int size;
|
|
||||||
struct DlcNcaListItem* items;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern struct GameInfo get_game_info(int, char*);
|
extern struct GameInfo get_game_info(int, char*);
|
||||||
|
|
||||||
extern struct DlcNcaList get_dlc_nca_list(const char* titleIdPtr, const char* pathPtr);
|
|
||||||
|
|
||||||
void install_firmware(const char* inputPtr);
|
void install_firmware(const char* inputPtr);
|
||||||
|
|
||||||
char* installed_firmware_version();
|
char* installed_firmware_version();
|
||||||
@ -56,6 +43,8 @@ int main_ryujinx_sdl(int argc, char **argv);
|
|||||||
|
|
||||||
int get_current_fps();
|
int get_current_fps();
|
||||||
|
|
||||||
|
void set_title_update(const char* titleIdPtr, const char* updatePathPtr);
|
||||||
|
|
||||||
void initialize();
|
void initialize();
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
@ -366,27 +366,16 @@ class Ryujinx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDlcNcaList(titleId: String, path: String) -> [DownloadableContentNca] {
|
|
||||||
guard let titleIdCString = titleId.cString(using: .utf8),
|
func setTitleUpdate(titleId: String, updatePath: String) {
|
||||||
let pathCString = path.cString(using: .utf8)
|
guard let titleIdPtr = titleId.cString(using: .utf8),
|
||||||
|
let updatePathPtr = updatePath.cString(using: .utf8)
|
||||||
else {
|
else {
|
||||||
print("Invalid path")
|
print("Invalid firmware path")
|
||||||
return []
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let listPointer = get_dlc_nca_list(titleIdCString, pathCString)
|
set_title_update(titleIdPtr, updatePathPtr)
|
||||||
print("DLC parcing success: \(listPointer.success)")
|
|
||||||
guard listPointer.success else { return [] }
|
|
||||||
|
|
||||||
let list = Array(UnsafeBufferPointer(start: listPointer.items, count: Int(listPointer.size)))
|
|
||||||
|
|
||||||
return list.map { item in
|
|
||||||
.init(fullPath: withUnsafePointer(to: item.Path) {
|
|
||||||
$0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: $0)) {
|
|
||||||
String(cString: $0)
|
|
||||||
}
|
|
||||||
}, titleId: item.TitleId, enabled: true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func generateGamepadId(joystickIndex: Int32) -> String? {
|
private func generateGamepadId(joystickIndex: Int32) -> String? {
|
||||||
|
@ -28,7 +28,6 @@ struct GameLibraryView: View {
|
|||||||
@State var isSelectingGameFile = false
|
@State var isSelectingGameFile = false
|
||||||
@State var isViewingGameInfo: Bool = false
|
@State var isViewingGameInfo: Bool = false
|
||||||
@State var isSelectingGameUpdate: Bool = false
|
@State var isSelectingGameUpdate: Bool = false
|
||||||
@State var isSelectingGameDLC: Bool = false
|
|
||||||
@State var gameInfo: Game?
|
@State var gameInfo: Game?
|
||||||
var games: Binding<[Game]> {
|
var games: Binding<[Game]> {
|
||||||
Binding(
|
Binding(
|
||||||
@ -93,7 +92,7 @@ struct GameLibraryView: View {
|
|||||||
|
|
||||||
LazyVStack(spacing: 2) {
|
LazyVStack(spacing: 2) {
|
||||||
ForEach(filteredGames) { game in
|
ForEach(filteredGames) { game in
|
||||||
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, isSelectingGameDLC: $isSelectingGameDLC, gameInfo: $gameInfo)
|
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, gameInfo: $gameInfo)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
addToRecentGames(game)
|
addToRecentGames(game)
|
||||||
}
|
}
|
||||||
@ -102,7 +101,7 @@ struct GameLibraryView: View {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ForEach(filteredGames) { game in
|
ForEach(filteredGames) { game in
|
||||||
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, isSelectingGameDLC: $isSelectingGameDLC, gameInfo: $gameInfo)
|
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, gameInfo: $gameInfo)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
addToRecentGames(game)
|
addToRecentGames(game)
|
||||||
}
|
}
|
||||||
@ -272,9 +271,6 @@ struct GameLibraryView: View {
|
|||||||
.sheet(isPresented: $isSelectingGameUpdate) {
|
.sheet(isPresented: $isSelectingGameUpdate) {
|
||||||
UpdateManagerSheet(game: $gameInfo)
|
UpdateManagerSheet(game: $gameInfo)
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $isSelectingGameDLC) {
|
|
||||||
DLCManagerSheet(game: $gameInfo)
|
|
||||||
}
|
|
||||||
.sheet(isPresented: Binding(
|
.sheet(isPresented: Binding(
|
||||||
get: { isViewingGameInfo && gameInfo != nil },
|
get: { isViewingGameInfo && gameInfo != nil },
|
||||||
set: { newValue in
|
set: { newValue in
|
||||||
@ -418,7 +414,6 @@ struct GameListRow: View {
|
|||||||
@Binding var games: [Game] // Add this binding
|
@Binding var games: [Game] // Add this binding
|
||||||
@Binding var isViewingGameInfo: Bool
|
@Binding var isViewingGameInfo: Bool
|
||||||
@Binding var isSelectingGameUpdate: Bool
|
@Binding var isSelectingGameUpdate: Bool
|
||||||
@Binding var isSelectingGameDLC: Bool
|
|
||||||
@Binding var gameInfo: Game?
|
@Binding var gameInfo: Game?
|
||||||
@State var gametoDelete: Game?
|
@State var gametoDelete: Game?
|
||||||
@State var showGameDeleteConfirmation: Bool = false
|
@State var showGameDeleteConfirmation: Bool = false
|
||||||
@ -481,22 +476,13 @@ struct GameListRow: View {
|
|||||||
} label: {
|
} label: {
|
||||||
Label("Game Info", systemImage: "info.circle")
|
Label("Game Info", systemImage: "info.circle")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Section {
|
|
||||||
Button {
|
Button {
|
||||||
gameInfo = game
|
gameInfo = game
|
||||||
isSelectingGameUpdate.toggle()
|
isSelectingGameUpdate.toggle()
|
||||||
} label: {
|
} label: {
|
||||||
Label("Game Update Manager", systemImage: "chevron.up.circle")
|
Label("Game Update Manager", systemImage: "chevron.up.circle")
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
|
||||||
gameInfo = game
|
|
||||||
isSelectingGameDLC.toggle()
|
|
||||||
} label: {
|
|
||||||
Label("Game DLC Manager", systemImage: "plus.viewfinder")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
|
@ -1,159 +0,0 @@
|
|||||||
//
|
|
||||||
// GameDLCManagerSheet.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by XITRIX on 16/02/2025.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import UniformTypeIdentifiers
|
|
||||||
|
|
||||||
struct DownloadableContentNca: Codable, Hashable {
|
|
||||||
var fullPath: String
|
|
||||||
var titleId: UInt
|
|
||||||
var enabled: Bool
|
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
|
||||||
case fullPath = "path"
|
|
||||||
case titleId = "title_id"
|
|
||||||
case enabled = "is_enabled"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DownloadableContentContainer: Codable, Hashable {
|
|
||||||
var containerPath: String
|
|
||||||
var downloadableContentNcaList: [DownloadableContentNca]
|
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
|
||||||
case containerPath = "path"
|
|
||||||
case downloadableContentNcaList = "dlc_nca_list"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DLCManagerSheet: View {
|
|
||||||
@Binding var game: Game!
|
|
||||||
@State private var isSelectingGameDLC = false
|
|
||||||
@State private var dlcs: [DownloadableContentContainer] = []
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
NavigationView {
|
|
||||||
let withIndex = dlcs.enumerated().map { $0 }
|
|
||||||
List(withIndex, id: \.element.containerPath) { index, dlc in
|
|
||||||
Button(action: {
|
|
||||||
let toggle = dlcs[index].downloadableContentNcaList.first?.enabled ?? true
|
|
||||||
dlcs[index].downloadableContentNcaList.mutableForEach { $0.enabled = !toggle }
|
|
||||||
Self.saveDlcs(game, dlc: dlcs)
|
|
||||||
}) {
|
|
||||||
HStack {
|
|
||||||
Text(dlc.containerPath)
|
|
||||||
.foregroundStyle(Color(uiColor: .label))
|
|
||||||
Spacer()
|
|
||||||
if dlc.downloadableContentNcaList.first?.enabled == true {
|
|
||||||
Image(systemName: "checkmark.circle.fill")
|
|
||||||
.foregroundStyle(Color.accentColor)
|
|
||||||
.font(.system(size: 24))
|
|
||||||
} else {
|
|
||||||
Image(systemName: "circle")
|
|
||||||
.foregroundStyle(Color(uiColor: .secondaryLabel))
|
|
||||||
.font(.system(size: 24))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.contextMenu {
|
|
||||||
Button {
|
|
||||||
let path = URL.documentsDirectory.appendingPathComponent(dlc.containerPath)
|
|
||||||
try? FileManager.default.removeItem(atPath: path.path)
|
|
||||||
dlcs.remove(at: index)
|
|
||||||
Self.saveDlcs(game, dlc: dlcs)
|
|
||||||
} label: {
|
|
||||||
Text("Remove DLC")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.navigationTitle("\(game.titleName) DLCs")
|
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
|
||||||
.toolbar {
|
|
||||||
Button("Add", systemImage: "plus") {
|
|
||||||
isSelectingGameDLC = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
dlcs = Self.loadDlc(game)
|
|
||||||
}
|
|
||||||
.fileImporter(isPresented: $isSelectingGameDLC, allowedContentTypes: [.item], allowsMultipleSelection: true) { result in
|
|
||||||
switch result {
|
|
||||||
case .success(let urls):
|
|
||||||
for url in urls {
|
|
||||||
guard url.startAccessingSecurityScopedResource() else {
|
|
||||||
print("Failed to access security-scoped resource")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer { url.stopAccessingSecurityScopedResource() }
|
|
||||||
|
|
||||||
do {
|
|
||||||
let fileManager = FileManager.default
|
|
||||||
let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
|
|
||||||
let dlcDirectory = documentsDirectory.appendingPathComponent("dlc")
|
|
||||||
let romDlcDirectory = dlcDirectory.appendingPathComponent(game.titleId)
|
|
||||||
|
|
||||||
if !fileManager.fileExists(atPath: dlcDirectory.path) {
|
|
||||||
try fileManager.createDirectory(at: dlcDirectory, withIntermediateDirectories: true, attributes: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !fileManager.fileExists(atPath: romDlcDirectory.path) {
|
|
||||||
try fileManager.createDirectory(at: romDlcDirectory, withIntermediateDirectories: true, attributes: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
let dlcContent = Ryujinx.shared.getDlcNcaList(titleId: game.titleId, path: url.path)
|
|
||||||
guard !dlcContent.isEmpty else { return }
|
|
||||||
|
|
||||||
let destinationURL = romDlcDirectory.appendingPathComponent(url.lastPathComponent)
|
|
||||||
try? fileManager.copyItem(at: url, to: destinationURL)
|
|
||||||
|
|
||||||
let container = DownloadableContentContainer(
|
|
||||||
containerPath: Self.relativeDlcDirectoryPath(for: game, dlcPath: destinationURL),
|
|
||||||
downloadableContentNcaList: dlcContent
|
|
||||||
)
|
|
||||||
dlcs.append(container)
|
|
||||||
|
|
||||||
Self.saveDlcs(game, dlc: dlcs)
|
|
||||||
} catch {
|
|
||||||
print("Error copying game file: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case .failure(let err):
|
|
||||||
print("File import failed: \(err.localizedDescription)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension DLCManagerSheet {
|
|
||||||
static func loadDlc(_ game: Game) -> [DownloadableContentContainer] {
|
|
||||||
let jsonURL = dlcJsonPath(for: game)
|
|
||||||
guard let data = try? Data(contentsOf: jsonURL),
|
|
||||||
var result = try? JSONDecoder().decode([DownloadableContentContainer].self, from: data)
|
|
||||||
else { return [] }
|
|
||||||
|
|
||||||
result = result.filter { container in
|
|
||||||
let path = URL.documentsDirectory.appendingPathComponent(container.containerPath)
|
|
||||||
return FileManager.default.fileExists(atPath: path.path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
static func saveDlcs(_ game: Game, dlc: [DownloadableContentContainer]) {
|
|
||||||
guard let data = try? JSONEncoder().encode(dlc) else { return }
|
|
||||||
try? data.write(to: dlcJsonPath(for: game))
|
|
||||||
}
|
|
||||||
|
|
||||||
static func relativeDlcDirectoryPath(for game: Game, dlcPath: URL) -> String {
|
|
||||||
"dlc/\(game.titleId)/\(dlcPath.lastPathComponent)"
|
|
||||||
}
|
|
||||||
|
|
||||||
static func dlcJsonPath(for game: Game) -> URL {
|
|
||||||
URL.documentsDirectory.appendingPathComponent("games").appendingPathComponent(game.titleId).appendingPathComponent("dlc.json")
|
|
||||||
}
|
|
||||||
}
|
|
@ -142,95 +142,34 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "get_dlc_nca_list")]
|
[UnmanagedCallersOnly(EntryPoint = "set_title_update")]
|
||||||
public static unsafe DlcNcaList GetDlcNcaList(IntPtr titleIdPtr, IntPtr pathPtr)
|
public static unsafe void SetTitleUpdate(IntPtr titleIdPtr, IntPtr updatePathPtr) {
|
||||||
{
|
|
||||||
var titleId = Marshal.PtrToStringAnsi(titleIdPtr);
|
var titleId = Marshal.PtrToStringAnsi(titleIdPtr);
|
||||||
var containerPath = Marshal.PtrToStringAnsi(pathPtr);
|
var updatePath = Marshal.PtrToStringAnsi(updatePathPtr);
|
||||||
|
string _updateJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, titleId, "updates.json");
|
||||||
|
|
||||||
if (!File.Exists(containerPath))
|
TitleUpdateMetadata _titleUpdateWindowData;
|
||||||
{
|
|
||||||
return new DlcNcaList { success = false };
|
|
||||||
}
|
|
||||||
|
|
||||||
using FileStream containerFile = File.OpenRead(containerPath);
|
if (File.Exists(_updateJsonPath)) {
|
||||||
|
_titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_updateJsonPath, _titleSerializerContext.TitleUpdateMetadata);
|
||||||
|
|
||||||
PartitionFileSystem pfs = new();
|
_titleUpdateWindowData.Paths ??= new List<string>();
|
||||||
pfs.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
if (!_titleUpdateWindowData.Paths.Contains(updatePath)) {
|
||||||
bool containsDlc = false;
|
_titleUpdateWindowData.Paths.Add(updatePath);
|
||||||
|
|
||||||
_virtualFileSystem.ImportTickets(pfs);
|
|
||||||
|
|
||||||
// TreeIter? parentIter = null;
|
|
||||||
|
|
||||||
List<DlcNcaListItem> listItems = new();
|
|
||||||
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 = TryCreateNca(ncaFile.Get.AsStorage(), containerPath);
|
|
||||||
|
|
||||||
if (nca == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nca.Header.ContentType == NcaContentType.PublicData)
|
_titleUpdateWindowData.Selected = updatePath;
|
||||||
{
|
} else {
|
||||||
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000).ToString("x16") != titleId)
|
_titleUpdateWindowData = new TitleUpdateMetadata {
|
||||||
{
|
Selected = updatePath,
|
||||||
break;
|
Paths = new List<string> { updatePath },
|
||||||
}
|
};
|
||||||
|
|
||||||
Logger.Warning?.Print(LogClass.Application, $"ContainerPath: {containerPath}");
|
|
||||||
Logger.Warning?.Print(LogClass.Application, $"TitleId: {nca.Header.TitleId}");
|
|
||||||
Logger.Warning?.Print(LogClass.Application, $"fileEntry.FullPath: {fileEntry.FullPath}");
|
|
||||||
|
|
||||||
// parentIter ??= ((TreeStore)_dlcTreeView.Model).AppendValues(true, "", containerPath);
|
|
||||||
// ((TreeStore)_dlcTreeView.Model).AppendValues(parentIter.Value, true, nca.Header.TitleId.ToString("X16"), fileEntry.FullPath);
|
|
||||||
|
|
||||||
DlcNcaListItem item = new();
|
|
||||||
CopyStringToFixedArray(fileEntry.FullPath, item.Path, 256);
|
|
||||||
item.TitleId = nca.Header.TitleId;
|
|
||||||
listItems.Add(item);
|
|
||||||
|
|
||||||
containsDlc = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!containsDlc)
|
JsonHelper.SerializeToFile(_updateJsonPath, _titleUpdateWindowData, _titleSerializerContext.TitleUpdateMetadata);
|
||||||
{
|
|
||||||
return new DlcNcaList { success = false };
|
|
||||||
// GtkDialog.CreateErrorDialog("The specified file does not contain DLC for the selected title!");
|
|
||||||
}
|
|
||||||
|
|
||||||
var list = new DlcNcaList { success = true, size = (uint) listItems.Count };
|
|
||||||
|
|
||||||
DlcNcaListItem[] items = listItems.ToArray();
|
|
||||||
|
|
||||||
fixed (DlcNcaListItem* p = &items[0])
|
|
||||||
{
|
|
||||||
list.items = p;
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Nca TryCreateNca(IStorage ncaStorage, string containerPath)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return new Nca(_virtualFileSystem.KeySet, ncaStorage);
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "get_current_fps")]
|
[UnmanagedCallersOnly(EntryPoint = "get_current_fps")]
|
||||||
public static unsafe int GetFPS()
|
public static unsafe int GetFPS()
|
||||||
@ -1579,19 +1518,6 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
public byte[]? Icon;
|
public byte[]? Icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe struct DlcNcaListItem
|
|
||||||
{
|
|
||||||
public fixed byte Path[256];
|
|
||||||
public ulong TitleId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe struct DlcNcaList
|
|
||||||
{
|
|
||||||
public bool success;
|
|
||||||
public uint size;
|
|
||||||
public unsafe DlcNcaListItem* items;
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe struct GameInfoNative
|
public unsafe struct GameInfoNative
|
||||||
{
|
{
|
||||||
public ulong FileSize;
|
public ulong FileSize;
|
||||||
@ -1639,13 +1565,14 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
ImageData = null;
|
ImageData = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void CopyStringToFixedArray(string source, byte* destination, int length)
|
||||||
|
{
|
||||||
|
var span = new Span<byte>(destination, length);
|
||||||
|
span.Clear();
|
||||||
|
Encoding.UTF8.GetBytes(source, span);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static unsafe void CopyStringToFixedArray(string source, byte* destination, int length)
|
|
||||||
{
|
|
||||||
var span = new Span<byte>(destination, length);
|
|
||||||
span.Clear();
|
|
||||||
Encoding.UTF8.GetBytes(source, span);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user