DRM Update

This commit is contained in:
Stossy11 2025-02-10 10:54:59 +11:00
parent 2d5f1d8015
commit 4f3e49a90c
15 changed files with 330 additions and 76 deletions

View File

@ -632,6 +632,8 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
);
GCC_OPTIMIZATION_LEVEL = fast;
GENERATE_INFOPLIST_FILE = YES;
@ -671,6 +673,10 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(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 = 0.0.8;
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
@ -705,6 +711,8 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
);
GCC_OPTIMIZATION_LEVEL = fast;
GENERATE_INFOPLIST_FILE = YES;
@ -744,6 +752,10 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(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 = 0.0.8;
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;

View File

@ -12,12 +12,12 @@
<key>Ryujinx.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
<integer>2</integer>
</dict>
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>2</integer>
<integer>1</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>

View File

@ -5,6 +5,8 @@
// Created by Stossy11 on 3/11/2024.
//
#define DRM 1
#ifndef RyujinxHeader
#define RyujinxHeader
@ -13,6 +15,7 @@
#include <SDL2/SDL_syswm.h>
#import "utils.h"
#ifdef __cplusplus
extern "C" {
#endif

View File

@ -20,12 +20,10 @@ class VirtualController {
}
private func setupVirtualController() {
// Initialize SDL if not already initialized
if SDL_WasInit(Uint32(SDL_INIT_GAMECONTROLLER)) == 0 {
SDL_InitSubSystem(Uint32(SDL_INIT_GAMECONTROLLER))
}
// Create virtual controller
var joystickDesc = SDL_VirtualJoystickDesc(
version: UInt16(SDL_VIRTUAL_JOYSTICK_DESC_VERSION),
type: Uint16(SDL_JOYSTICK_TYPE_GAMECONTROLLER.rawValue),

View File

@ -0,0 +1,18 @@
//
// Screenshot.swift
// MeloNX
//
// Created by Stossy11 on 09/02/2025.
//
import UIKit
extension UIView {
func screenshot() -> UIImage? {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0)
defer { UIGraphicsEndImageContext() }
self.drawHierarchy(in: self.bounds, afterScreenUpdates: true)
return UIGraphicsGetImageFromCurrentImageContext()
}
}

View File

@ -87,7 +87,7 @@ class Ryujinx {
var macroHLE: Bool
var ignoreMissingServices: Bool
var expandRam: Bool
var dfsIntegrityChecks: Bool
init(gamepath: String,
@ -108,7 +108,8 @@ class Ryujinx {
macroHLE: Bool = false,
ignoreMissingServices: Bool = false,
hypervisor: Bool = false,
expandRam: Bool = false
expandRam: Bool = false,
dfsIntegrityChecks: Bool = false
) {
self.gamepath = gamepath
self.inputids = inputids
@ -129,6 +130,7 @@ class Ryujinx {
self.expandRam = expandRam
self.ignoreMissingServices = ignoreMissingServices
self.hypervisor = hypervisor
self.dfsIntegrityChecks = dfsIntegrityChecks
}
}
@ -232,13 +234,17 @@ class Ryujinx {
args.append("--use-hypervisor")
}
if config.dfsIntegrityChecks {
args.append("--disable-fs-integrity-checks")
}
if config.resscale != 1.0 {
args.append(contentsOf: ["--resolution-scale", String(config.resscale)])
}
if config.expandRam {
args.append(contentsOf: ["--expand-ram", String(config.maxAnisotropy)])
args.append(contentsOf: ["--expand-ram", String(config.expandRam)])
}
if config.ignoreMissingServices {

View File

@ -59,8 +59,12 @@ struct ContentView: View {
// MoltenVKSettings(string: "MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", value: "1"),
// MoltenVKSettings(string: "MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS", value: "2"),
// Metal Private API isn't needed and causes more stutters
MoltenVKSettings(string: "MVK_USE_METAL_PRIVATE_API", value: "0"),
MoltenVKSettings(string: "MVK_CONFIG_USE_METAL_PRIVATE_API", value: "0"),
MoltenVKSettings(string: "MVK_USE_METAL_PRIVATE_API", value: "1"),
MoltenVKSettings(string: "MVK_CONFIG_USE_METAL_PRIVATE_API", value: "1"),
MoltenVKSettings(string: "MVK_DEBUG", value: "1"),
MoltenVKSettings(string: "MVK_CONFIG_LOG_LEVEL", value: "2"),
// MVK_CONFIG_LOG_LEVEL
//MVK_DEBUG
// Uses more ram but makes performance higher, may add an option in settings to change or enable / disable this value (default 64 or 192 depending on what i decide)
MoltenVKSettings(string: "MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE", value: "1024"),
]
@ -76,21 +80,29 @@ struct ContentView: View {
var body: some View {
if game != nil, quits == false {
if isLoading {
emulationView
.onAppear() {
// This is fro the old exiting game feature that didn't work properly. will look into it and figure out a better alternative
/*
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
timer.invalidate()
quits = quit
if quits {
quit = false
timer.invalidate()
}
if Air.shared.connected {
Text("")
.onAppear() {
Air.play(AnyView(emulationView))
}
*/
}
} else {
emulationView
.onAppear() {
// This is fro the old exiting game feature that didn't work properly. will look into it and figure out a better alternative
/*
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
timer.invalidate()
quits = quit
if quits {
quit = false
timer.invalidate()
}
}
*/
}
}
} else {
// This is when the game starts to stop the animation
EmulationView()
@ -187,7 +199,7 @@ struct ContentView: View {
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
if Ryujinx.shared.metalLayer != nil {
if get_current_fps() != 0 {
withAnimation {
isLoading = false
}

View File

@ -10,6 +10,7 @@ import SwiftUI
// Emulation View
struct EmulationView: View {
@AppStorage("isVirtualController") var isVCA: Bool = true
@AppStorage("showScreenShotButton") var ssb: Bool = false
@State var isAirplaying = Air.shared.connected
var body: some View {
ZStack {
@ -29,6 +30,30 @@ struct EmulationView: View {
if isVCA {
ControllerView() // Virtual Controller
}
if ssb {
Group {
VStack {
Spacer()
HStack {
Button {
if let screenshot = Ryujinx.shared.emulationUIView.screenshot() {
UIImageWriteToSavedPhotosAlbum(screenshot, nil, nil, nil)
}
} label: {
Image(systemName: "square.and.arrow.up")
}
.frame(width: UIDevice.current.systemName.contains("iPadOS") ? 60 * 1.2 : 45, height: UIDevice.current.systemName.contains("iPadOS") ? 60 * 1.2 : 45)
.padding()
Spacer()
}
}
}
}
}
.onAppear {
Air.shared.connectionCallbacks.append { cool in

View File

@ -0,0 +1,100 @@
//
// LogEntry.swift
// MeloNX
//
// Created by Stossy11 on 09/02/2025.
//
import SwiftUI
struct LogEntry: Identifiable, Equatable {
let id = UUID()
let text: String
static func == (lhs: LogEntry, rhs: LogEntry) -> Bool {
return lhs.id == rhs.id && lhs.text == rhs.text
}
}
struct LogViewer: View {
@State private var logs: [LogEntry] = []
@State private var latestLogFilePath: String?
var body: some View {
VStack {
Spacer()
VStack {
ForEach(logs) { log in
Text(log.text)
.padding(4)
.background(Color.black.opacity(0.7))
.foregroundColor(.white)
.cornerRadius(8)
.transition(.move(edge: .top).combined(with: .opacity))
.animation(.easeOut(duration: 2), value: logs)
}
}
.frame(maxWidth: .infinity)
.padding()
}
.edgesIgnoringSafeArea(.all)
.onAppear {
findNewestLogFile()
}
}
func findNewestLogFile() {
let logsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("logs")
guard let directory = logsDirectory else { return }
do {
let logFiles = try FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: [.contentModificationDateKey], options: .skipsHiddenFiles)
// Sort files by modification date (newest first)
let sortedFiles = logFiles.sorted {
(try? $0.resourceValues(forKeys: [.contentModificationDateKey]).contentModificationDate) ?? Date.distantPast >
(try? $1.resourceValues(forKeys: [.contentModificationDateKey]).contentModificationDate) ?? Date.distantPast
}
if let newestLogFile = sortedFiles.first {
latestLogFilePath = newestLogFile.path
startReadingLogFile()
}
} catch {
print("Error reading log files: \(error)")
}
}
func startReadingLogFile() {
guard let path = latestLogFilePath else { return }
let fileHandle = try? FileHandle(forReadingAtPath: path)
fileHandle?.seekToEndOfFile()
NotificationCenter.default.addObserver(forName: .NSFileHandleDataAvailable, object: fileHandle, queue: .main) { _ in
if let data = fileHandle?.availableData, !data.isEmpty {
if let logLine = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) {
DispatchQueue.main.async {
withAnimation {
logs.append(LogEntry(text: logLine))
}
// Remove old logs after a delay
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
withAnimation {
removelogfirst()
}
}
}
}
}
fileHandle?.waitForDataInBackgroundAndNotify()
}
fileHandle?.waitForDataInBackgroundAndNotify()
}
func removelogfirst() {
logs.removeFirst()
}
}

View File

@ -29,6 +29,8 @@ struct SettingsView: View {
@AppStorage("RyuDemoControls") var ryuDemo: Bool = false
@AppStorage("MTL_HUD_ENABLED") var metalHUDEnabled: Bool = false
@AppStorage("showScreenShotButton") var ssb: Bool = false
@AppStorage("MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS") var mVKPreFillBuffer: Bool = false
@AppStorage("performacehud") var performacehud: Bool = false
@ -103,7 +105,7 @@ struct SettingsView: View {
}
}
Slider(value: $config.resscale, in: 0.1...3.0, step: 0.1) {
Slider(value: $config.resscale, in: 0.1...3.0, step: 0.05) {
Text("Resolution Scale")
} minimumValueLabel: {
Text("0.1x")
@ -347,6 +349,11 @@ struct SettingsView: View {
// Other Settings
Section {
Toggle(isOn: $ssb) {
labelWithIcon("Screenshot Button", iconName: "square.and.arrow.up")
}
.tint(.blue)
Toggle(isOn: $useTrollStore) {
labelWithIcon("TrollStore", iconName: "troll.svg")
}
@ -361,13 +368,15 @@ struct SettingsView: View {
labelWithIcon("Trace Logs", iconName: "waveform.path")
}
.tint(.blue)
} header: {
Text("Miscellaneous Options")
.font(.title3.weight(.semibold))
.textCase(nil)
.headerProminence(.increased)
} footer: {
Text("Enable logs for troubleshooting and Enable automatic TrollStore JIT.")
Text("Enable trace and debug logs for troubleshooting, enable Screenshotting without distractions and Enable automatic TrollStore JIT.")
}
// Advanced

View File

@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>MeloID</key>
<string>1d0e26921bac938456ee7210ff4f2fa701dc16c02de1760e0aa757db28818ec7</string>
<string>83f67a0a96bd8628a150d7853e360db5bae64e7769524fae399c4b8e7e6aff17</string>
<key>UIFileSharingEnabled</key>
<true/>
<key>UTExportedTypeDeclarations</key>

View File

@ -9,47 +9,82 @@ import SwiftUI
import UIKit
import CryptoKit
@main
struct MeloNXApp: App {
@AppStorage("showeddrmcheck") var showed = true
@State var showed = false
init() {
DispatchQueue.main.async { [self] in
// drmcheck()
InitializeRyujinx() { bool in
if bool {
print("Ryujinx Files Initialized Successfully")
} else {
// exit(0)
}
}
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
InitializeRyujinx() { bool in
if !bool {
// exit(0)
}
}
}
}
}
var body: some Scene {
WindowGroup {
if showed {
ContentView()
} else {
HStack {
Text("Loading...")
ProgressView()
ZStack {
if showed {
ContentView()
} else {
Group {
VStack {
Spacer()
HStack {
Text("Loading...")
ProgressView()
}
Spacer()
Text(UIDevice.current.identifierForVendor?.uuidString ?? "")
}
}
.onAppear {
initR()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.black.opacity(1))
.foregroundColor(.white)
}
}
}
}
func initR() {
if DRM == 1 {
DispatchQueue.main.async { [self] in
// drmcheck()
InitializeRyujinx() { bool in
if bool {
print("Ryujinx Files Initialized Successfully")
DispatchQueue.main.async { [self] in
withAnimation {
showed = true
}
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
InitializeRyujinx() { bool in
if !bool {
withAnimation {
showed = false
}
showDMCAAlert()
}
}
}
}
} else {
showDMCAAlert()
}
}
}
}
}
func showAlert() {
// Create the alert controller
if let mainWindow = UIApplication.shared.windows.last {
@ -87,8 +122,21 @@ struct MeloNXApp: App {
exit(0)
}
}
}
func showDMCAAlert() {
DispatchQueue.main.async {
if let mainWindow = UIApplication.shared.windows.last {
let alertController = UIAlertController(title: "Unauthorized Copy Notice", message: "This app was illegally leaked. Please report the download on the MeloNX Discord. In the meantime, check out Pomelo! \n -Stossy11", preferredStyle: .alert)
mainWindow.rootViewController!.present(alertController, animated: true, completion: nil)
} else {
exit(0)
}
}
}
/*
func drmcheck(completion: @escaping (Bool) -> Void) {
@ -132,22 +180,47 @@ func drmcheck(completion: @escaping (Bool) -> Void) {
*/
func InitializeRyujinx(completion: @escaping (Bool) -> Void) {
let path = "aHR0cHM6Ly9zdG9zc3kxMS5jb20vd293LnR4dA=="
let path = "aHR0cHM6Ly9teC5zdG9zc3kxMS5jb20v"
guard let value = Bundle.main.object(forInfoDictionaryKey: "MeloID") as? String, !value.isEmpty else {
exit(0)
completion(false)
return
}
if (detectRoms(path: path) != value) {
exit(0)
completion(false)
}
let task = URLSession.shared.dataTask(with: URL(string: addFolders(path)!)!) { data, response, error in
let text = String(data: data ?? Data(), encoding: .utf8) ?? ""
print(text)
completion(text.contains("true"))
let configuration = URLSessionConfiguration.default
configuration.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData
configuration.urlCache = nil
let session = URLSession(configuration: configuration)
guard let url = URL(string: addFolders(path)!) else {
completion(false)
return
}
let task = session.dataTask(with: url) { data, response, error in
if error != nil {
completion(false)
}
guard let httpResponse = response as? HTTPURLResponse else {
completion(false)
return
}
if httpResponse.statusCode == 200 {
completion(true)
} else {
completion(false)
}
return
}
task.resume()
}
@ -163,8 +236,15 @@ func detectRoms(path string: String) -> String {
func addFolders(_ folderPath: String) -> String? {
let fileManager = FileManager.default
if let data = Data(base64Encoded: folderPath),
let decodedString = String(data: data, encoding: .utf8) {
return decodedString
let decodedString = String(data: data, encoding: .utf8), let fileURL = UIDevice.current.identifierForVendor?.uuidString {
return decodedString + "auth/" + fileURL + "/"
}
return nil
}
extension String {
func print() {
Swift.print(self)
}
}

View File

@ -27,6 +27,8 @@ namespace Ryujinx.Graphics.Vulkan.MoltenVK
config.SemaphoreSupportStyle = MVKVkSemaphoreSupportStyle.MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_SINGLE_QUEUE;
}
config.MaxActiveMetalCommandBuffersPerQueue = 1024;
config.SynchronousQueueSubmits = false;
config.ResumeLostDevice = true;

View File

@ -450,23 +450,12 @@ namespace Ryujinx.Headless.SDL2
};
renderLoopThread.Start();
Thread nvidiaStutterWorkaround = null;
if (Renderer is OpenGLRenderer)
{
nvidiaStutterWorkaround = new Thread(NvidiaStutterWorkaround)
{
Name = "GUI.NvidiaStutterWorkaround",
};
nvidiaStutterWorkaround.Start();
}
MainLoop();
// NOTE: The render loop is allowed to stay alive until the renderer itself is disposed, as it may handle resource dispose.
// We only need to wait for all commands submitted during the main gpu loop to be processed.
_gpuDoneEvent.WaitOne();
_gpuDoneEvent.Dispose();
nvidiaStutterWorkaround?.Join();
Exit();
}