Archived
1
0
forked from MeloNX/MeloNX

WIP: Message bridge

This commit is contained in:
Daniil Vinogradov 2025-03-01 11:26:18 +01:00
parent e924da52ec
commit 23be667c7b
19 changed files with 490 additions and 84 deletions

View File

@ -108,6 +108,10 @@
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib" = (
CodeSignOnCopy,
);
"Dependencies/Dynamic Libraries/RyujinxBridge.framework" = (
CodeSignOnCopy,
RemoveHeadersOnCopy,
);
"Dependencies/Dynamic Libraries/RyujinxKeyboard.framework" = (
CodeSignOnCopy,
RemoveHeadersOnCopy,
@ -169,6 +173,7 @@
"Dependencies/Dynamic Libraries/libavutil.dylib",
"Dependencies/Dynamic Libraries/libMoltenVK.dylib",
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib",
"Dependencies/Dynamic Libraries/RyujinxBridge.framework",
"Dependencies/Dynamic Libraries/RyujinxKeyboard.framework",
Dependencies/XCFrameworks/libavcodec.xcframework,
Dependencies/XCFrameworks/libavfilter.xcframework,
@ -633,7 +638,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = 95J8WZ4TN8;
DEVELOPMENT_TEAM = D59DHVRS87;
ENABLE_PREVIEWS = YES;
ENABLE_TESTABILITY = NO;
FRAMEWORK_SEARCH_PATHS = (
@ -763,7 +768,7 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
);
MARKETING_VERSION = 1.3.0;
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
PRODUCT_BUNDLE_IDENTIFIER = com.xitrix.MeloNX;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h";
@ -781,7 +786,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = 95J8WZ4TN8;
DEVELOPMENT_TEAM = D59DHVRS87;
ENABLE_PREVIEWS = YES;
ENABLE_TESTABILITY = YES;
FRAMEWORK_SEARCH_PATHS = (
@ -911,7 +916,7 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
);
MARKETING_VERSION = 1.3.0;
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
PRODUCT_BUNDLE_IDENTIFIER = com.xitrix.MeloNX;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h";

View File

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="UTF-8"?>
<Bucket
uuid = "56EBE536-1F4C-4BCE-853B-D3E000BE57CD"
type = "1"
version = "2.0">
<Breakpoints>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.SymbolicBreakpoint">
<BreakpointContent
uuid = "71A20387-E5D5-42A9-99A1-7D1B6BEB6A04"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "Yes"
symbolName = "UIApplicationMain"
moduleName = "">
<Actions>
<BreakpointActionProxy
ActionExtensionID = "Xcode.BreakpointAction.DebuggerCommand">
<ActionContent
consoleCommand = "process handle SIGUSR1 -n true -p true -s false">
</ActionContent>
</BreakpointActionProxy>
</Actions>
<Locations>
<Location
uuid = "71A20387-E5D5-42A9-99A1-7D1B6BEB6A04 - dc2f8c5e001177f0"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "UIApplicationMain"
moduleName = "UIKitCore"
usesParentBreakpointCondition = "Yes">
</Location>
</Locations>
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.SymbolicBreakpoint">
<BreakpointContent
uuid = "2A095BA1-FC06-4890-9989-2C4A83A0F028"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "Yes"
symbolName = "UIApplicationMain"
moduleName = "">
<Actions>
<BreakpointActionProxy
ActionExtensionID = "Xcode.BreakpointAction.DebuggerCommand">
<ActionContent
consoleCommand = "process handle SIGBUS -n true -p true -s false">
</ActionContent>
</BreakpointActionProxy>
</Actions>
<Locations>
<Location
uuid = "2A095BA1-FC06-4890-9989-2C4A83A0F028 - dc2f8c5e001177f0"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "UIApplicationMain"
moduleName = "UIKitCore"
usesParentBreakpointCondition = "Yes">
</Location>
</Locations>
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "A363121B-631E-4ADE-8A05-D3D9538EE31D"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "MeloNX/App/Core/Ryujinx/RyujinxBridge.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "43"
endingLineNumber = "43"
landmarkName = "parseMessage(_:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "482C1F2E-138F-49D2-A590-8B98AC9B790E"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "MeloNX/App/Core/Ryujinx/RyujinxBridge.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "32"
endingLineNumber = "32"
landmarkName = "parseMessage(_:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "FF32B294-2681-4A6E-9DFA-0D13DA242EAF"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "MeloNX/App/Core/Ryujinx/RyujinxBridge.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "30"
endingLineNumber = "30"
landmarkName = "parseMessage(_:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
</Breakpoints>
</Bucket>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>MeloNX.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
<key>Ryujinx.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>2</integer>
</dict>
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
</dict>
</dict>
</dict>
</plist>

View File

@ -8,6 +8,7 @@
import Foundation
import SwiftUI
import GameController
import RyujinxBridge
struct Controller: Identifiable, Hashable {
var id: String
@ -49,6 +50,13 @@ class Ryujinx {
static let shared = Ryujinx()
private init() {
messageDelegate = { messagePtr in
guard let messagePtr else { return }
let message = String(cString: messagePtr)
RyujinxBridgeHelper.parseMessage(message)
print("Message: \(message)")
}
self.games = loadGames()
}
@ -513,7 +521,6 @@ class Ryujinx {
var windowInfo = SDL_SysWMinfo()
SDL_GetWindowWMInfo(window, &windowInfo)
guard let uiWindow = windowInfo.info.uikit.window,
let rootView = uiWindow.takeUnretainedValue().rootViewController?.view else {
print("Unable to get root view")

View File

@ -0,0 +1,45 @@
//
// RyujinxBridge.swift
// MeloNX
//
// Created by Daniil Vinogradov on 17/02/2025.
//
import UIKit
struct BridgeAlertMessage: Codable {
var title: String?
var type: String?
var metadata: String?
}
struct BridgePayload<T> {
var type: String
var model: T
}
enum RyujinxBridgeHelper {
static func parseMessage(_ message: String) {
guard let data = message.data(using: .utf8),
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let type = json["type"] as? String,
let model = json["model"],
let modelData = try? JSONSerialization.data(withJSONObject: model)
else { return }
switch type {
case "BridgeAlertMessage":
guard let model = try? JSONDecoder().decode(BridgeAlertMessage.self, from: modelData)
else { return }
DispatchQueue.main.async {
// let alertVC = UIAlertController(title: model.title, message: model.type, preferredStyle: .alert)
// alertVC.addAction(.init(title: "OK", style: .cancel))
print("ALERT!!!! \(model.title) \(model.type) \(model.metadata)")
// UIApplication.shared.windows.first?.rootViewController?.present(alertVC, animated: true)
}
default: break
}
print("Type: \(type)")
}
}

View File

@ -6,7 +6,7 @@
//
import SwiftUI
import SwiftSVG
//import SwiftSVG
struct SettingsView: View {
@Binding var config: Ryujinx.Configuration
@ -439,36 +439,42 @@ struct SettingsView: View {
}
.tint(.blue)
if #available(iOS 17.0.1, *) {
Toggle(isOn: $jitStreamerEB) {
labelWithIcon("JitStreamer EB", iconName: "bolt.heart")
}
.tint(.blue)
.contextMenu {
Button {
if let mainWindow = UIApplication.shared.windows.last {
let alertController = UIAlertController(title: "About JitStreamer EB", message: "JitStreamer EB is an Amazing Application to Enable JIT on the go, made by one of the best iOS developers of all time jkcoxson <3", preferredStyle: .alert)
let learnMoreButton = UIAlertAction(title: "Learn More", style: .default) {_ in
UIApplication.shared.open(URL(string: "https://jkcoxson.com/jitstreamer")!)
}
alertController.addAction(learnMoreButton)
let doneButton = UIAlertAction(title: "Done", style: .cancel, handler: nil)
alertController.addAction(doneButton)
mainWindow.rootViewController?.present(alertController, animated: true)
}
} label: {
Text("About")
}
}
} else {
// if #available(iOS 17.0.1, *) {
// Toggle(isOn: $jitStreamerEB) {
// labelWithIcon("JitStreamer EB", iconName: "bolt.heart")
// }
// .tint(.blue)
// .contextMenu {
// Button {
// if let mainWindow = UIApplication.shared.windows.last {
// let alertController = UIAlertController(title: "About JitStreamer EB", message: "JitStreamer EB is an Amazing Application to Enable JIT on the go, made by one of the best iOS developers of all time jkcoxson <3", preferredStyle: .alert)
//
// let learnMoreButton = UIAlertAction(title: "Learn More", style: .default) {_ in
// UIApplication.shared.open(URL(string: "https://jkcoxson.com/jitstreamer")!)
// }
// alertController.addAction(learnMoreButton)
//
// let doneButton = UIAlertAction(title: "Done", style: .cancel, handler: nil)
// alertController.addAction(doneButton)
//
// mainWindow.rootViewController?.present(alertController, animated: true)
// }
// } label: {
// Text("About")
// }
// }
// } else {
Toggle(isOn: $useTrollStore) {
labelWithIcon("TrollStore JIT", iconName: "troll.svg")
HStack(spacing: 8) {
Image("troll")
.symbolRenderingMode(.hierarchical)
.foregroundStyle(.blue)
Text("TrollStore JIT")
}
.font(.body)
}
.tint(.blue)
}
// }
Toggle(isOn: $syncqsubmits) {
labelWithIcon("MVK: Synchronous Queue Submits", iconName: "line.diagonal")
@ -695,56 +701,11 @@ struct SettingsView: View {
@ViewBuilder
private func labelWithIcon(_ text: String, iconName: String, flipimage: Bool? = nil) -> some View {
HStack(spacing: 8) {
if iconName.hasSuffix(".svg"){
if let flipimage, flipimage {
SVGView(svgName: iconName, color: .blue)
.symbolRenderingMode(.hierarchical)
.frame(width: 20, height: 20)
.rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0))
} else {
SVGView(svgName: iconName, color: .blue)
.symbolRenderingMode(.hierarchical)
.frame(width: 20, height: 20)
}
} else if !iconName.isEmpty {
Image(systemName: iconName)
.symbolRenderingMode(.hierarchical)
.foregroundStyle(.blue)
}
Image(systemName: iconName)
.symbolRenderingMode(.hierarchical)
.foregroundStyle(.blue)
Text(text)
}
.font(.body)
}
}
struct SVGView: UIViewRepresentable {
var svgName: String
var color: Color = Color.black
func makeUIView(context: Context) -> UIView {
var svgName = svgName
var hammock = UIView()
if svgName.hasSuffix(".svg") {
svgName.removeLast(4)
}
let svgLayer = UIView(SVGNamed: svgName) { svgLayer in
svgLayer.fillColor = UIColor(color).cgColor // Apply the provided color
svgLayer.resizeToFit(hammock.frame)
hammock.layer.addSublayer(svgLayer)
}
return hammock
}
func updateUIView(_ uiView: UIView, context: Context) {
// Update the SVG view's fill color when the color changes
if let svgLayer = uiView.layer.sublayers?.first as? CAShapeLayer {
svgLayer.fillColor = UIColor(color).cgColor
}
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Troll-Face.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -0,0 +1,18 @@
//
// RyujinxBridge.h
// RyujinxBridge
//
// Created by Daniil Vinogradov on 17/02/2025.
//
#import <Foundation/Foundation.h>
//! Project version number for RyujinxBridge.
FOUNDATION_EXPORT double RyujinxBridgeVersionNumber;
//! Project version string for RyujinxBridge.
FOUNDATION_EXPORT const unsigned char RyujinxBridgeVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <RyujinxBridge/PublicHeader.h>
#import <RyujinxBridge/bridge.h>

View File

@ -0,0 +1,8 @@
//
// bridge.h
// RyujinxBridge
//
// Created by Daniil Vinogradov on 17/02/2025.
//
void (*messageDelegate)(const char*);

View File

@ -0,0 +1,6 @@
framework module RyujinxBridge {
umbrella header "RyujinxBridge.h"
export *
module * { export * }
}

View File

@ -0,0 +1,135 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>files</key>
<dict>
<key>Headers/RyujinxBridge.h</key>
<data>
DKD2r8aJ47TZL7v48UVEfod716g=
</data>
<key>Headers/bridge.h</key>
<data>
p75HJMB/G5CAZ+yTApMWmoQP7Lg=
</data>
<key>Info.plist</key>
<data>
mWbK0knhX+Q4WAm+hZd8SF0ioS0=
</data>
<key>Modules/module.modulemap</key>
<data>
+to1dvHz+3pPZcmBu4qsYsrvt4Y=
</data>
</dict>
<key>files2</key>
<dict>
<key>Headers/RyujinxBridge.h</key>
<dict>
<key>hash2</key>
<data>
xIPdWru4HW7sRYRg6G+ehIk9V4nX3Uv7kIlE2c/TnjM=
</data>
</dict>
<key>Headers/bridge.h</key>
<dict>
<key>hash2</key>
<data>
kA+OGSf2EzopJ1KM/+Lp8qHheuFQQYlkkOdMg/ywzH8=
</data>
</dict>
<key>Modules/module.modulemap</key>
<dict>
<key>hash2</key>
<data>
ZTC6KjLczI++298LFW6y9c8aoPQ1LrrsJrDJfrPnZUU=
</data>
</dict>
</dict>
<key>rules</key>
<dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^version.plist$</key>
<true/>
</dict>
<key>rules2</key>
<dict>
<key>.*\.dSYM($|/)</key>
<dict>
<key>weight</key>
<real>11</real>
</dict>
<key>^(.*/)?\.DS_Store$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>2000</real>
</dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^Info\.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^PkgInfo$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^embedded\.provisionprofile$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^version\.plist$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
</dict>
</dict>
</plist>

View File

@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.kernel.increased-debugging-memory-limit</key>
<true/>
<key>com.apple.developer.kernel.increased-memory-limit</key>
<true/>
</dict>

View File

@ -0,0 +1,41 @@
using Newtonsoft.Json;
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
namespace Ryujinx.Headless.SDL2
{
struct BridgeAlertMessage(string title, string message, string metadata = null)
{
[JsonProperty("title")]
string Title { get; set; } = title;
[JsonProperty("message")]
string Message { get; set; } = message;
[JsonProperty("metadata")]
string Metadata { get; set; } = metadata;
}
public static class MessageBridge
{
[DllImport("RyujinxBridge.framework/RyujinxBridge", CallingConvention = CallingConvention.Cdecl, EntryPoint="sendMessage")]
private static extern void SendMessage(string json);
public static void SendPayload<T>(T model)
{
string jsonString = JsonConvert.SerializeObject(new BridgePayload<T>(typeof(T).Name, model));
SendMessage(jsonString);
}
}
public struct BridgePayload<T>(string type, T model)
{
[JsonProperty("type")]
private string Type { get; set; } = type;
[JsonProperty("model")]
private T Model { get; set; } = model;
}
}

View File

@ -37,7 +37,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Threading;
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
@ -85,6 +84,7 @@ using ARMeilleure.Translation;
using LibHac.Ncm;
using LibHac.Tools.FsSystem.NcaUtils;
using Microsoft.Win32.SafeHandles;
using Newtonsoft.Json;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.SystemState;
@ -94,6 +94,7 @@ using System;
using System.IO;
using System.Runtime.InteropServices;
using SDL2;
using JsonException = System.Text.Json.JsonException;
namespace Ryujinx.Headless.SDL2
{
@ -117,6 +118,13 @@ namespace Ryujinx.Headless.SDL2
private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
private static readonly TitleUpdateMetadataJsonSerializerContext _titleSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
// [UnmanagedCallersOnly(EntryPoint = "get_dlc_nca_list")]
// public static unsafe void CDeclCombine(delegate* unmanaged[Cdecl]<void> combinator) =>
// combinator(left, right);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void TestDelegate(int a, int b);
[UnmanagedCallersOnly(EntryPoint = "main_ryujinx_sdl")]
public static unsafe int MainExternal(int argCount, IntPtr* pArgs)
{
@ -403,6 +411,8 @@ namespace Ryujinx.Headless.SDL2
[UnmanagedCallersOnly(EntryPoint = "get_game_info")]
public static GameInfoNative GetGameInfoNative(int descriptor, IntPtr extensionPtr)
{
MessageBridge.SendPayload(new BridgeAlertMessage("Text", "Alalallala"));
if (_virtualFileSystem == null) {
_virtualFileSystem = VirtualFileSystem.CreateInstance();
}

View File

@ -477,8 +477,8 @@ namespace Ryujinx.Headless.SDL2
public bool DisplayMessageDialog(string title, string message)
{
SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags.SDL_MESSAGEBOX_INFORMATION, title, message, WindowHandle);
// SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags.SDL_MESSAGEBOX_INFORMATION, title, message, WindowHandle);
MessageBridge.SendPayload(new BridgeAlertMessage(title, message, "controllerApplet"));
return true;
}