1
0
forked from MeloNX/MeloNX

Compare commits

..

3 Commits

8 changed files with 181 additions and 197 deletions

View File

@ -5,7 +5,6 @@
Before you begin, ensure you have the following installed: Before you begin, ensure you have the following installed:
- [**.NET 8.0**](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) - [**.NET 8.0**](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
- [**Xcode**](https://apps.apple.com/de/app/xcode/id497799835?l=en-GB&mt=12$0)
- A Mac running **macOS** - A Mac running **macOS**
## Compilation Steps ## Compilation Steps

View File

@ -19,7 +19,7 @@
# Compatibility # Compatibility
MeloNX works on iPhone XS/XR and later and iPad 8th Gen and later. Check out the Compatibility on the <a href="https://melonx.org/compatibility/" target="_blank">website</a>. MeloNX works on iPhone X and later and iPad 7th Gen and later. Check out the Compatibility on the <a href="https://melonx.org/compatibility/" target="_blank">website</a>.
# Usage # Usage

View File

@ -738,7 +738,7 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
); );
MARKETING_VERSION = 1.2.0; MARKETING_VERSION = 1.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX; PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
@ -862,7 +862,7 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
); );
MARKETING_VERSION = 1.2.0; MARKETING_VERSION = 1.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX; PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;

View File

@ -14,10 +14,7 @@ struct GameInfoSheet: View {
var body: some View { var body: some View {
iOSNav { iOSNav {
List { VStack {
Section {}
header: {
VStack(alignment: .center) {
if let icon = game.icon { if let icon = game.icon {
Image(uiImage: icon) Image(uiImage: icon)
.resizable() .resizable()
@ -39,27 +36,23 @@ struct GameInfoSheet: View {
.frame(width: 150, height: 150) .frame(width: 150, height: 150)
.padding() .padding()
} }
VStack(alignment: .center) {
VStack(alignment: .leading) {
VStack(alignment: .leading) {
Text("**\(game.titleName)** | \(game.titleId.capitalized)") Text("**\(game.titleName)** | \(game.titleId.capitalized)")
.multilineTextAlignment(.center)
Text(game.developer) Text(game.developer)
.font(.caption) .font(.caption)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
.padding(.vertical, 3) .padding(.vertical, 3)
}
.frame(maxWidth: .infinity)
}
Section { VStack(alignment: .leading, spacing: 5) {
HStack { Text("Information")
Text("**Version**") .font(.title2)
Spacer() .bold()
Text(game.version)
.foregroundStyle(Color.secondary) Text("**Version:** \(game.version)")
} Text("**Title ID:** \(game.titleId)")
HStack {
Text("**Title ID**")
.contextMenu { .contextMenu {
Button { Button {
UIPasteboard.general.string = game.titleId UIPasteboard.general.string = game.titleId
@ -67,32 +60,15 @@ struct GameInfoSheet: View {
Text("Copy Title ID") Text("Copy Title ID")
} }
} }
Text("**Game Size:** \(fetchFileSize(for: game.fileURL) ?? 0) bytes")
Text("**File Type:** .\(getFileType(game.fileURL))")
Text("**Game URL:** \(trimGameURL(game.fileURL))")
}
}
Spacer() Spacer()
Text(game.titleId)
.foregroundStyle(Color.secondary)
}
HStack {
Text("**Game Size**")
Spacer()
Text("\(fetchFileSize(for: game.fileURL) ?? 0) bytes")
.foregroundStyle(Color.secondary)
}
HStack {
Text("**File Type**")
Spacer()
Text(getFileType(game.fileURL))
.foregroundStyle(Color.secondary)
}
VStack(alignment: .leading, spacing: 4) {
Text("**Game URL**")
Text(trimGameURL(game.fileURL))
.foregroundStyle(Color.secondary)
}
} header: {
Text("Information")
}
.headerProminence(.increased)
} }
.padding(.horizontal, 5)
.navigationTitle(game.titleName) .navigationTitle(game.titleName)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbar { .toolbar {
@ -127,6 +103,10 @@ struct GameInfoSheet: View {
} }
func getFileType(_ url: URL) -> String { func getFileType(_ url: URL) -> String {
url.pathExtension let path = url.path
if let range = path.range(of: ".") {
return String(path[range.upperBound...])
}
return "Unknown"
} }
} }

View File

@ -48,7 +48,15 @@ struct GameLibraryView: View {
var body: some View { var body: some View {
iOSNav { iOSNav {
List { ScrollView {
LazyVStack(alignment: .leading, spacing: 20) {
if !isSearching {
Text("Games")
.font(.system(size: 34, weight: .bold))
.padding(.horizontal)
.padding(.top, 12)
}
if Ryujinx.shared.games.isEmpty { if Ryujinx.shared.games.isEmpty {
VStack(spacing: 16) { VStack(spacing: 16) {
Image(systemName: "gamecontroller.fill") Image(systemName: "gamecontroller.fill")
@ -100,6 +108,7 @@ struct GameLibraryView: View {
} }
} }
} else { } else {
LazyVStack(spacing: 2) {
ForEach(filteredGames) { game in ForEach(filteredGames) { game in
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, gameInfo: $gameInfo) GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, gameInfo: $gameInfo)
.onTapGesture { .onTapGesture {
@ -109,8 +118,7 @@ struct GameLibraryView: View {
} }
} }
} }
.navigationTitle("Games") }
.navigationBarTitleDisplayMode(.large)
.onAppear { .onAppear {
loadRecentGames() loadRecentGames()
@ -135,8 +143,9 @@ struct GameLibraryView: View {
print(error) print(error)
} }
} }
}
.toolbar { .toolbar {
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarLeading) {
Button { Button {
isSelectingGameFile.toggle() isSelectingGameFile.toggle()
} label: { } label: {
@ -206,6 +215,7 @@ struct GameLibraryView: View {
} }
} }
} }
.background(Color(.systemGroupedBackground))
.searchable(text: $searchText) .searchable(text: $searchText)
.onChange(of: searchText) { _ in .onChange(of: searchText) { _ in
isSearching = !searchText.isEmpty isSearching = !searchText.isEmpty
@ -286,6 +296,7 @@ struct GameLibraryView: View {
} }
} }
private func addToRecentGames(_ game: Game) { private func addToRecentGames(_ game: Game) {
recentGames.removeAll { $0.id == game.id } recentGames.removeAll { $0.id == game.id }
@ -318,7 +329,8 @@ struct GameLibraryView: View {
} }
} }
// MARK: - Delete Game Function
// MARK: - Delete Game Function
func deleteGame(game: Game) { func deleteGame(game: Game) {
let fileManager = FileManager.default let fileManager = FileManager.default
do { do {
@ -331,7 +343,7 @@ struct GameLibraryView: View {
} }
} }
// MARK: - Game Model // MARK: -Game Model
extension Game: Codable { extension Game: Codable {
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case titleName, titleId, developer, version, fileURL case titleName, titleId, developer, version, fileURL
@ -360,7 +372,7 @@ extension Game: Codable {
} }
} }
// MARK: - Recent Game Card // MARK: -Recent Game Card
struct RecentGameCard: View { struct RecentGameCard: View {
let game: Game let game: Game
@Binding var startemu: Game? @Binding var startemu: Game?
@ -407,7 +419,7 @@ struct RecentGameCard: View {
} }
} }
// MARK: - Game List Item // MARK: -Game List Item
struct GameListRow: View { struct GameListRow: View {
let game: Game let game: Game
@Binding var startemu: Game? @Binding var startemu: Game?
@ -462,6 +474,9 @@ struct GameListRow: View {
.foregroundColor(.accentColor) .foregroundColor(.accentColor)
.opacity(0.8) .opacity(0.8)
} }
.padding(.horizontal)
.padding(.vertical, 8)
.background(Color(.systemBackground))
.contextMenu { .contextMenu {
Section { Section {
Button { Button {
@ -518,3 +533,4 @@ struct GameListRow: View {
} }
} }
} }

View File

@ -18,22 +18,16 @@ struct UpdateManagerSheet: View {
var body: some View { var body: some View {
NavigationView { NavigationView {
List(paths, id: \..self, selection: $selectedItem) { item in VStack {
List(paths, id: \..self) { item in
Button(action: { Button(action: {
selectItem(item.lastPathComponent) selectItem(item.lastPathComponent)
}) { }) {
HStack { HStack {
Text(item.lastPathComponent) Text(item.lastPathComponent)
.foregroundStyle(Color(uiColor: .label)) if selectedItem == "\(game!.titleId)/\(item.lastPathComponent)" {
Spacer() Spacer()
if selectedItem == "updates/\(game!.titleId)/\(item.lastPathComponent)" { Image(systemName: "checkmark")
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(Color.accentColor)
.font(.system(size: 24))
} else {
Image(systemName: "circle")
.foregroundStyle(Color(uiColor: .secondaryLabel))
.font(.system(size: 24))
} }
} }
} }
@ -45,15 +39,15 @@ struct UpdateManagerSheet: View {
} }
} }
} }
.onAppear { }
.onAppear() {
print(URL.documentsDirectory.appendingPathComponent("games").appendingPathComponent(game!.titleId).appendingPathComponent("updates.json")) print(URL.documentsDirectory.appendingPathComponent("games").appendingPathComponent(game!.titleId).appendingPathComponent("updates.json"))
loadJSON(URL.documentsDirectory.appendingPathComponent("games").appendingPathComponent(game!.titleId).appendingPathComponent("updates.json")) loadJSON(URL.documentsDirectory.appendingPathComponent("games").appendingPathComponent(game!.titleId).appendingPathComponent("updates.json"))
} }
.navigationTitle("\(game!.titleName) Updates") .navigationTitle("\(game!.titleName) Updates")
.navigationBarTitleDisplayMode(.inline)
.toolbar { .toolbar {
Button("Add", systemImage: "plus") { Button("+") {
isSelectingGameUpdate = true isSelectingGameUpdate = true
} }
} }
@ -86,8 +80,7 @@ struct UpdateManagerSheet: View {
let destinationURL = romUpdatedDirectory.appendingPathComponent(url.lastPathComponent) let destinationURL = romUpdatedDirectory.appendingPathComponent(url.lastPathComponent)
try? fileManager.copyItem(at: url, to: destinationURL) try? fileManager.copyItem(at: url, to: destinationURL)
items.append("updates/" + gameInfo.titleId + "/" + url.lastPathComponent) Ryujinx.shared.setTitleUpdate(titleId: gameInfo.titleId, updatePath: "\(gameInfo.titleId)/" + url.lastPathComponent)
selectItem(url.lastPathComponent)
Ryujinx.shared.games = Ryujinx.shared.loadGames() Ryujinx.shared.games = Ryujinx.shared.loadGames()
loadJSON(jsonURL!) loadJSON(jsonURL!)
} catch { } catch {
@ -100,7 +93,7 @@ struct UpdateManagerSheet: View {
} }
func removeUpdate(_ game: URL) { func removeUpdate(_ game: URL) {
let gameString = "updates/\(self.game!.titleId)/\(game.lastPathComponent)" let gameString = "\(self.game!.titleId)/\(game.lastPathComponent)"
paths.removeAll { $0 == game } paths.removeAll { $0 == game }
items.removeAll { $0 == gameString } items.removeAll { $0 == gameString }
@ -115,7 +108,6 @@ struct UpdateManagerSheet: View {
} }
saveJSON(selectedItem: selectedItem ?? "") saveJSON(selectedItem: selectedItem ?? "")
Ryujinx.shared.games = Ryujinx.shared.loadGames()
} }
func saveJSON(selectedItem: String) { func saveJSON(selectedItem: String) {
@ -130,28 +122,26 @@ struct UpdateManagerSheet: View {
} }
func loadJSON(_ json: URL) { func loadJSON(_ json: URL) {
self.jsonURL = json
guard let jsonURL else { return } self.jsonURL = json
print("Failed to read JSO")
guard let jsonURL = jsonURL else { return }
print("Failed to read JSOK")
do { do {
let data = try Data(contentsOf: jsonURL) let data = try Data(contentsOf: jsonURL)
if let jsonDict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], if let jsonDict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
let list = jsonDict["paths"] as? [String] let list = jsonDict["paths"] as? [String] {
{ var urls: [URL] = []
let filteredList = list.filter { relativePath in for path in list {
let path = URL.documentsDirectory.appendingPathComponent(relativePath) urls.append(URL.documentsDirectory.appendingPathComponent("updates").appendingPathComponent(path))
return FileManager.default.fileExists(atPath: path.path)
} }
let urls: [URL] = filteredList.map { relativePath in self.items = list
URL.documentsDirectory.appendingPathComponent(relativePath) self.paths = urls
} self.selectedItem = jsonDict["selected"] as? String
items = filteredList
paths = urls
selectedItem = jsonDict["selected"] as? String
} }
} catch { } catch {
print("Failed to read JSON: \(error)") print("Failed to read JSON: \(error)")
@ -165,17 +155,17 @@ struct UpdateManagerSheet: View {
do { do {
let newData = try JSONSerialization.data(withJSONObject: defaultData, options: .prettyPrinted) let newData = try JSONSerialization.data(withJSONObject: defaultData, options: .prettyPrinted)
try newData.write(to: jsonURL) try newData.write(to: jsonURL)
items = [] self.items = []
selectedItem = "" self.selectedItem = ""
} catch { } catch {
print("Failed to create default JSON: \(error)") print("Failed to create default JSON: \(error)")
} }
} }
func selectItem(_ item: String) { func selectItem(_ item: String) {
let newSelection = "updates/\(game!.titleId)/\(item)" let newSelection = "\(game!.titleId)/\(item)"
guard let jsonURL else { return } guard let jsonURL = jsonURL else { return }
do { do {
let data = try Data(contentsOf: jsonURL) let data = try Data(contentsOf: jsonURL)
@ -185,7 +175,7 @@ struct UpdateManagerSheet: View {
jsonDict["selected"] = "" jsonDict["selected"] = ""
selectedItem = "" selectedItem = ""
} else { } else {
jsonDict["selected"] = "\(newSelection)" jsonDict["selected"] = newSelection
selectedItem = newSelection selectedItem = newSelection
} }
@ -193,9 +183,9 @@ struct UpdateManagerSheet: View {
let newData = try JSONSerialization.data(withJSONObject: jsonDict, options: .prettyPrinted) let newData = try JSONSerialization.data(withJSONObject: jsonDict, options: .prettyPrinted)
try newData.write(to: jsonURL) try newData.write(to: jsonURL)
Ryujinx.shared.games = Ryujinx.shared.loadGames()
} catch { } catch {
print("Failed to update JSON: \(error)") print("Failed to update JSON: \(error)")
} }
} }
} }

View File

@ -751,8 +751,7 @@ namespace Ryujinx.Headless.SDL2
if (File.Exists(titleUpdateMetadataPath)) if (File.Exists(titleUpdateMetadataPath))
{ {
string updatePathRelative = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, _titleSerializerContext.TitleUpdateMetadata).Selected; updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, _titleSerializerContext.TitleUpdateMetadata).Selected;
updatePath = Path.Combine(AppDataManager.BaseDirPath, updatePathRelative);
if (File.Exists(updatePath)) if (File.Exists(updatePath))
{ {