forked from MeloNX/MeloNX
Add title update functionality
This commit is contained in:
parent
27312d4f31
commit
7277e1fa9b
@ -43,6 +43,8 @@ int main_ryujinx_sdl(int argc, char **argv);
|
||||
|
||||
int get_current_fps();
|
||||
|
||||
void set_title_update(const char* titleIdPtr, const char* updatePathPtr);
|
||||
|
||||
void initialize();
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
@ -373,7 +373,18 @@ class Ryujinx {
|
||||
self.firmwareversion = version
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func setTitleUpdate(titleId: String, updatePath: String) {
|
||||
guard let titleIdPtr = titleId.cString(using: .utf8),
|
||||
let updatePathPtr = updatePath.cString(using: .utf8)
|
||||
else {
|
||||
print("Invalid firmware path")
|
||||
return
|
||||
}
|
||||
|
||||
set_title_update(titleIdPtr, updatePathPtr)
|
||||
}
|
||||
|
||||
private func generateGamepadId(joystickIndex: Int32) -> String? {
|
||||
let guid = SDL_JoystickGetDeviceGUID(joystickIndex)
|
||||
|
||||
|
@ -23,8 +23,6 @@ public struct Game: Identifiable, Equatable, Hashable {
|
||||
|
||||
|
||||
static func convertGameInfoToGame(gameInfo: GameInfo, url: URL) -> Game? {
|
||||
guard gameInfo.FileSize != 0 else { return nil }
|
||||
|
||||
var gameInfo = gameInfo
|
||||
var gameTemp = Game(containerFolder: url.deletingLastPathComponent(), fileType: .item, fileURL: url, titleName: "", titleId: "", developer: "", version: "")
|
||||
|
||||
|
@ -27,6 +27,7 @@ struct GameLibraryView: View {
|
||||
@State var startgame = false
|
||||
@State var isSelectingGameFile = false
|
||||
@State var isViewingGameInfo: Bool = false
|
||||
@State var isSelectingGameUpdate: Bool = false
|
||||
@State var gameInfo: Game?
|
||||
var games: Binding<[Game]> {
|
||||
Binding(
|
||||
@ -99,7 +100,7 @@ struct GameLibraryView: View {
|
||||
|
||||
LazyVStack(spacing: 2) {
|
||||
ForEach(filteredGames) { game in
|
||||
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, gameInfo: $gameInfo)
|
||||
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, gameInfo: $gameInfo)
|
||||
.onTapGesture {
|
||||
addToRecentGames(game)
|
||||
}
|
||||
@ -109,7 +110,7 @@ struct GameLibraryView: View {
|
||||
} else {
|
||||
LazyVStack(spacing: 2) {
|
||||
ForEach(filteredGames) { game in
|
||||
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, gameInfo: $gameInfo)
|
||||
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, gameInfo: $gameInfo)
|
||||
.onTapGesture {
|
||||
addToRecentGames(game)
|
||||
}
|
||||
@ -277,6 +278,36 @@ struct GameLibraryView: View {
|
||||
print("File import failed: \(err.localizedDescription)")
|
||||
}
|
||||
}
|
||||
.fileImporter(isPresented: $isSelectingGameUpdate, allowedContentTypes: [.nsp]) { result in
|
||||
switch result {
|
||||
case .success(let url):
|
||||
guard let gameInfo, 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 romUpdatedDirectory = documentsDirectory.appendingPathComponent("updates")
|
||||
|
||||
if !fileManager.fileExists(atPath: romUpdatedDirectory.path) {
|
||||
try fileManager.createDirectory(at: romUpdatedDirectory, withIntermediateDirectories: true, attributes: nil)
|
||||
}
|
||||
|
||||
let destinationURL = romUpdatedDirectory.appendingPathComponent(url.lastPathComponent)
|
||||
try? fileManager.copyItem(at: url, to: destinationURL)
|
||||
|
||||
Ryujinx.shared.setTitleUpdate(titleId: gameInfo.titleId, updatePath: destinationURL.path)
|
||||
Ryujinx.shared.games = Ryujinx.shared.loadGames()
|
||||
} catch {
|
||||
print("Error copying game file: \(error)")
|
||||
}
|
||||
case .failure(let err):
|
||||
print("File import failed: \(err.localizedDescription)")
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: Binding(
|
||||
get: { isViewingGameInfo && gameInfo != nil },
|
||||
set: { newValue in
|
||||
@ -421,6 +452,7 @@ struct GameListRow: View {
|
||||
@Binding var startemu: Game?
|
||||
@Binding var games: [Game] // Add this binding
|
||||
@Binding var isViewingGameInfo: Bool
|
||||
@Binding var isSelectingGameUpdate: Bool
|
||||
@Binding var gameInfo: Game?
|
||||
@State var gametoDelete: Game?
|
||||
@State var showGameDeleteConfirmation: Bool = false
|
||||
@ -486,6 +518,13 @@ struct GameListRow: View {
|
||||
} label: {
|
||||
Label("Game Info", systemImage: "info.circle")
|
||||
}
|
||||
|
||||
Button {
|
||||
gameInfo = game
|
||||
isSelectingGameUpdate.toggle()
|
||||
} label: {
|
||||
Label("Add Game Update", systemImage: "chevron.up.circle")
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
|
@ -115,6 +115,7 @@ namespace Ryujinx.Headless.SDL2
|
||||
private static bool _enableMouse;
|
||||
|
||||
private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
private static readonly TitleUpdateMetadataJsonSerializerContext _titleSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "main_ryujinx_sdl")]
|
||||
public static unsafe int MainExternal(int argCount, IntPtr* pArgs)
|
||||
@ -141,6 +142,20 @@ namespace Ryujinx.Headless.SDL2
|
||||
return 0;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "set_title_update")]
|
||||
public static unsafe void SetTitleUpdate(IntPtr titleIdPtr, IntPtr updatePathPtr) {
|
||||
var titleId = Marshal.PtrToStringAnsi(titleIdPtr);
|
||||
var updatePath = Marshal.PtrToStringAnsi(updatePathPtr);
|
||||
string _updateJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, titleId, "updates.json");
|
||||
|
||||
TitleUpdateMetadata _titleUpdateWindowData = new TitleUpdateMetadata
|
||||
{
|
||||
Selected = updatePath,
|
||||
Paths = new List<string>(),
|
||||
};
|
||||
JsonHelper.SerializeToFile(_updateJsonPath, _titleUpdateWindowData, _titleSerializerContext.TitleUpdateMetadata);
|
||||
}
|
||||
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "get_current_fps")]
|
||||
public static unsafe int GetFPS()
|
||||
@ -722,7 +737,7 @@ namespace Ryujinx.Headless.SDL2
|
||||
|
||||
if (File.Exists(titleUpdateMetadataPath))
|
||||
{
|
||||
// updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath).Selected;
|
||||
updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, _titleSerializerContext.TitleUpdateMetadata).Selected;
|
||||
|
||||
if (File.Exists(updatePath))
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user