Archived
1
0
forked from MeloNX/MeloNX

Implemented game info data

This commit is contained in:
Daniil Vinogradov 2025-03-04 17:50:21 +01:00
parent 0b39a44220
commit ddd77c97eb
154 changed files with 487 additions and 382 deletions

View File

@ -3,4 +3,4 @@
-keep class kotlin.jvm.functions.** {*;}
-keep class com.sun.jna.** { *; }
-keep class * implements com.sun.jna.** { *; }
-keep class melo.nx.** { *; }
-keep class melonx.module.** { *; }

View File

@ -7,7 +7,6 @@
<!-- permissions needed for using the internet or an embedded WebKit browser -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> -->
<application
@ -15,10 +14,9 @@
android:name=".AndroidAppMain"
android:supportsRtl="true"
android:allowBackup="true"
android:isGame="true"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:name="MainActivity"
android:exported="true"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|mnc|colorMode|density|fontScale|fontWeightAdjustment|keyboard|layoutDirection|locale|mcc|navigation|smallestScreenSize|touchscreen|uiMode"
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"
@ -29,7 +27,7 @@
</intent-filter>
</activity>
<provider
android:name=".scoped.FolderProvider"
android:name="melo.nx.scoped.FolderProvider"
android:authorities="com.xitrix.melonx.scoped.gamefolder"
android:exported="true"
android:grantUriPermissions="true"

View File

@ -27,8 +27,9 @@ import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import melo.nx.BuildConfig;
import melo.nx.R;
import melonx.module.BuildConfig;
import melonx.module.R;
//import melo.nx.R;
/**

View File

@ -1,4 +1,4 @@
package melo.nx
package melonx.module
import skip.lib.*
import skip.model.*
@ -20,7 +20,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.core.app.ActivityCompat
internal val logger: SkipLogger = SkipLogger(subsystem = "melo.nx", category = "MeloNX")
internal val logger: SkipLogger = SkipLogger(subsystem = "melonx.module", category = "melonx")
/// AndroidAppMain is the `android.app.Application` entry point, and must match `application android:name` in the AndroidMainfest.xml file.
open class AndroidAppMain: Application {
@ -70,32 +70,32 @@ open class MainActivity: AppCompatActivity {
override fun onStart() {
super.onStart()
MeloNXAppDelegate.shared.onStart(this)
melonxAppDelegate.shared.onStart(this)
}
override fun onResume() {
super.onResume()
MeloNXAppDelegate.shared.onResume(this)
melonxAppDelegate.shared.onResume(this)
}
override fun onPause() {
super.onPause()
MeloNXAppDelegate.shared.onPause(this)
melonxAppDelegate.shared.onPause(this)
}
override fun onStop() {
super.onStop()
MeloNXAppDelegate.shared.onStop(this)
melonxAppDelegate.shared.onStop(this)
}
override fun onDestroy() {
super.onDestroy()
MeloNXAppDelegate.shared.onDestroy(this)
melonxAppDelegate.shared.onDestroy(this)
}
override fun onLowMemory() {
super.onLowMemory()
MeloNXAppDelegate.shared.onLowMemory(this)
melonxAppDelegate.shared.onLowMemory(this)
}
override fun onRestart() {
@ -126,7 +126,7 @@ internal fun PresentationRootView(context: ComposeContext) {
PresentationRoot(defaultColorScheme = colorScheme, context = context) { ctx ->
val contentContext = ctx.content()
Box(modifier = ctx.modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
MeloNXRootView().Compose(context = contentContext)
melonxRootView().Compose(context = contentContext)
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1003 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1003 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 937 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -1,5 +1,5 @@
import SwiftUI
import MeloNX
import melonx
/// The entry point to the app simply loads the App implementation from SPM module.
@main struct AppMain: App {
@ -8,7 +8,7 @@ import MeloNX
var body: some Scene {
WindowGroup {
MeloNXRootView()
melonxRootView()
}
.onChange(of: scenePhase) { oldPhase, newPhase in
switch newPhase {
@ -25,7 +25,7 @@ import MeloNX
}
}
typealias AppDelegate = MeloNXAppDelegate
typealias AppDelegate = melonxAppDelegate
#if canImport(UIKit)
typealias AppDelegateAdaptor = UIApplicationDelegateAdaptor
typealias AppMainDelegateBase = UIApplicationDelegate

View File

@ -0,0 +1 @@
melonx

View File

@ -7,8 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
49231BAC2AC5BCEF00F98ADF /* MeloNXApp in Frameworks */ = {isa = PBXBuildFile; productRef = 49231BAB2AC5BCEF00F98ADF /* MeloNXApp */; };
49231BAD2AC5BCEF00F98ADF /* MeloNXApp in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 49231BAB2AC5BCEF00F98ADF /* MeloNXApp */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
49231BAC2AC5BCEF00F98ADF /* melonxApp in Frameworks */ = {isa = PBXBuildFile; productRef = 49231BAB2AC5BCEF00F98ADF /* melonxApp */; };
49231BAD2AC5BCEF00F98ADF /* melonxApp in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 49231BAB2AC5BCEF00F98ADF /* melonxApp */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
496BDBEE2B8A7E9C00C09264 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 496BDBED2B8A7E9C00C09264 /* Localizable.xcstrings */; };
499CD43B2AC5B799001AE8D8 /* Main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49F90C2B2A52156200F06D93 /* Main.swift */; };
499CD4402AC5B799001AE8D8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49F90C2F2A52156300F06D93 /* Assets.xcassets */; };
@ -21,7 +21,7 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
49231BAD2AC5BCEF00F98ADF /* MeloNXApp in Embed Frameworks */,
49231BAD2AC5BCEF00F98ADF /* melonxApp in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@ -29,12 +29,12 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
493609562A6B7EAE00C401E2 /* MeloNX */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = MeloNX; path = ..; sourceTree = "<group>"; };
496BDBEB2B89A47800C09264 /* MeloNX.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MeloNX.app; sourceTree = BUILT_PRODUCTS_DIR; };
493609562A6B7EAE00C401E2 /* melonx */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = melonx; path = ..; sourceTree = "<group>"; };
496BDBEB2B89A47800C09264 /* melonx.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = melonx.app; sourceTree = BUILT_PRODUCTS_DIR; };
4900101C2BACEA710000DE33 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "Info.plist"; sourceTree = "<group>"; };
496BDBED2B8A7E9C00C09264 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = ../Sources/MeloNX/Resources/Localizable.xcstrings; sourceTree = "<group>"; };
496BDBED2B8A7E9C00C09264 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = ../Sources/melonx/Resources/Localizable.xcstrings; sourceTree = "<group>"; };
496EB72F2A6AE4DE00C1253A /* Skip.env */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Skip.env; path = ../Skip.env; sourceTree = "<group>"; };
496EB72F2A6AE4DE00C1253B /* MeloNX.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = MeloNX.xcconfig; sourceTree = "<group>"; };
496EB72F2A6AE4DE00C1253B /* melonx.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = melonx.xcconfig; sourceTree = "<group>"; };
496EB72F2A6AE4DE00C1253C /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
499AB9082B0581F4005E8330 /* plugins */ = {isa = PBXFileReference; lastKnownFileType = folder; name = plugins; path = ../../../SourcePackages/plugins; sourceTree = BUILT_PRODUCTS_DIR; };
49F90C2B2A52156200F06D93 /* Main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Main.swift; path = Sources/Main.swift; sourceTree = SOURCE_ROOT; };
@ -47,7 +47,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
49231BAC2AC5BCEF00F98ADF /* MeloNXApp in Frameworks */,
49231BAC2AC5BCEF00F98ADF /* melonxApp in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -57,7 +57,7 @@
496BDBEC2B89A47800C09264 /* Products */ = {
isa = PBXGroup;
children = (
496BDBEB2B89A47800C09264 /* MeloNX.app */,
496BDBEB2B89A47800C09264 /* melonx.app */,
);
name = Products;
sourceTree = "<group>";
@ -75,9 +75,9 @@
children = (
496EB72F2A6AE4DE00C1253C /* README.md */,
496EB72F2A6AE4DE00C1253A /* Skip.env */,
496EB72F2A6AE4DE00C1253B /* MeloNX.xcconfig */,
496EB72F2A6AE4DE00C1253B /* melonx.xcconfig */,
496BDBED2B8A7E9C00C09264 /* Localizable.xcstrings */,
493609562A6B7EAE00C401E2 /* MeloNX */,
493609562A6B7EAE00C401E2 /* melonx */,
49F90C2A2A52156200F06D93 /* App */,
49AB54462B066A7E007B79B2 /* SkipStone */,
496BDBEC2B89A47800C09264 /* Products */,
@ -98,9 +98,9 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
499CD4382AC5B799001AE8D8 /* MeloNX */ = {
499CD4382AC5B799001AE8D8 /* melonx */ = {
isa = PBXNativeTarget;
buildConfigurationList = 499CD4412AC5B799001AE8D8 /* Build configuration list for PBXNativeTarget "MeloNX" */;
buildConfigurationList = 499CD4412AC5B799001AE8D8 /* Build configuration list for PBXNativeTarget "melonx" */;
buildPhases = (
499CD43A2AC5B799001AE8D8 /* Sources */,
499CD43C2AC5B799001AE8D8 /* Frameworks */,
@ -112,12 +112,12 @@
);
dependencies = (
);
name = MeloNX;
name = melonx;
packageProductDependencies = (
49231BAB2AC5BCEF00F98ADF /* MeloNXApp */,
49231BAB2AC5BCEF00F98ADF /* melonxApp */,
);
productName = App;
productReference = 496BDBEB2B89A47800C09264 /* MeloNX.app */;
productReference = 496BDBEB2B89A47800C09264 /* melonx.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
@ -130,7 +130,7 @@
LastSwiftUpdateCheck = 1430;
LastUpgradeCheck = 1540;
};
buildConfigurationList = 49F90C232A52156200F06D93 /* Build configuration list for PBXProject "MeloNX" */;
buildConfigurationList = 49F90C232A52156200F06D93 /* Build configuration list for PBXProject "melonx" */;
compatibilityVersion = "Xcode 14.0";
developmentRegion = en;
hasScannedForEncodings = 0;
@ -148,7 +148,7 @@
projectDirPath = "";
projectRoot = "";
targets = (
499CD4382AC5B799001AE8D8 /* MeloNX */,
499CD4382AC5B799001AE8D8 /* melonx */,
);
};
/* End PBXProject section */
@ -201,7 +201,7 @@
/* Begin XCBuildConfiguration section */
499CD4422AC5B799001AE8D8 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 496EB72F2A6AE4DE00C1253B /* MeloNX.xcconfig */;
baseConfigurationReference = 496EB72F2A6AE4DE00C1253B /* melonx.xcconfig */;
buildSettings = {
ENABLE_PREVIEWS = YES;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
@ -211,7 +211,7 @@
};
499CD4432AC5B799001AE8D8 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 496EB72F2A6AE4DE00C1253B /* MeloNX.xcconfig */;
baseConfigurationReference = 496EB72F2A6AE4DE00C1253B /* melonx.xcconfig */;
buildSettings = {
ENABLE_PREVIEWS = YES;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
@ -258,7 +258,7 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
499CD4412AC5B799001AE8D8 /* Build configuration list for PBXNativeTarget "MeloNX" */ = {
499CD4412AC5B799001AE8D8 /* Build configuration list for PBXNativeTarget "melonx" */ = {
isa = XCConfigurationList;
buildConfigurations = (
499CD4422AC5B799001AE8D8 /* Debug */,
@ -267,7 +267,7 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
49F90C232A52156200F06D93 /* Build configuration list for PBXProject "MeloNX" */ = {
49F90C232A52156200F06D93 /* Build configuration list for PBXProject "melonx" */ = {
isa = XCConfigurationList;
buildConfigurations = (
49F90C4B2A52156300F06D93 /* Debug */,
@ -279,9 +279,9 @@
/* End XCConfigurationList section */
/* Begin XCSwiftPackageProductDependency section */
49231BAB2AC5BCEF00F98ADF /* MeloNXApp */ = {
49231BAB2AC5BCEF00F98ADF /* melonxApp */ = {
isa = XCSwiftPackageProductDependency;
productName = MeloNXApp;
productName = melonxApp;
};
/* End XCSwiftPackageProductDependency section */
};

View File

@ -0,0 +1,46 @@
// swift-tools-version: 5.9
// This is a Skip (https://skip.tools) package,
// containing a Swift Package Manager project
// that will use the Skip build plugin to transpile the
// Swift Package, Sources, and Tests into an
// Android Gradle Project with Kotlin sources and JUnit tests.
import PackageDescription
let package = Package(
name: "melonx-native",
defaultLocalization: "en",
platforms: [.iOS(.v17), .macOS(.v14), .tvOS(.v17), .watchOS(.v10), .macCatalyst(.v17)],
products: [
.library(name: "melonxApp", type: .dynamic, targets: ["melonx"]),
.library(name: "MeloNXModel", type: .dynamic, targets: ["MeloNXModel"]),
],
dependencies: [
.package(url: "https://source.skip.tools/skip.git", from: "1.3.0"),
.package(url: "https://source.skip.tools/skip-ui.git", from: "1.0.0"),
.package(url: "https://source.skip.tools/skip-fuse.git", "0.0.0"..<"2.0.0"),
.package(url: "https://source.skip.tools/skip-model.git", from: "1.0.0"),
// .package(url: "https://source.skip.tools/skip-unit.git", from: "1.0.0")
],
targets: [
.target(name: "melonx", dependencies: [
"MeloNXModel",
.product(name: "SkipUI", package: "skip-ui")
], resources: [.process("Resources")], plugins: [.plugin(name: "skipstone", package: "skip")]),
.target(name: "MeloNXModel", dependencies: [
"LibRyujinx",
.product(name: "SkipFuse", package: "skip-fuse"),
.product(name: "SkipModel", package: "skip-model")
], plugins: [.plugin(name: "skipstone", package: "skip")]),
.target(name: "LibRyujinx", dependencies: [],
sources: ["src"],
plugins: []),
.testTarget(name: "melonxTests", dependencies: [
"melonx",
.product(name: "SkipTest", package: "skip")
], resources: [.process("Resources")], plugins: [.plugin(name: "skipstone", package: "skip")]),
.testTarget(name: "MeloNXModelTests", dependencies: [
"MeloNXModel",
.product(name: "SkipTest", package: "skip")
], plugins: [.plugin(name: "skipstone", package: "skip")]),
]
)

View File

@ -1,4 +1,4 @@
# MeloNX
# melonx
This is a [Skip](https://skip.tools) dual-platform app project.
It builds a native app for both iOS and Android.
@ -35,7 +35,7 @@ An Android emulator must already be running, which can be launched from
Android Studio's Device Manager.
To run both the Swift and Kotlin apps simultaneously,
launch the MeloNXApp target from Xcode.
launch the melonxApp target from Xcode.
A build phases runs the "Launch Android APK" script that
will deploy the transpiled app a running Android emulator or connected device.
Logging output for the iOS app can be viewed in the Xcode console, and in

View File

@ -1,11 +1,11 @@
// The configuration file for your Skip App (https://skip.tools).
// Properties specified here are shared between
// Darwin/MeloNX.xcconfig and Android/settings.gradle.kts
// Darwin/melonx.xcconfig and Android/settings.gradle.kts
// and will be included in the app's metadata files
// Info.plist and AndroidManifest.xml
// PRODUCT_NAME is the default title of the app, which must match the app's Swift module name
PRODUCT_NAME = MeloNX
PRODUCT_NAME = melonx
// PRODUCT_BUNDLE_IDENTIFIER is the unique id for both the iOS and Android app
PRODUCT_BUNDLE_IDENTIFIER = com.xitrix.melonx
@ -17,4 +17,4 @@ MARKETING_VERSION = 0.0.1
CURRENT_PROJECT_VERSION = 1
// The package name for the Android entry point, referenced by the AndroidManifest.xml
ANDROID_PACKAGE_NAME = melo.nx
ANDROID_PACKAGE_NAME = melonx.module

View File

@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 3.3)
project(cproject, LANGUAGES C)
file(GLOB SOURCES src/*.c)
add_library(ryujinx SHARED ${SOURCES})
include_directories(ryujinx PUBLIC include)
# include(libs/OpenSSL.cmake)
# add_dependencies(ryujinx openssl)

View File

@ -44,7 +44,7 @@ int main_ryujinx_sdl(int argc, char **argv);
int get_current_fps();
void initialize_android(char* path);
void initialize_android(const char* path);
int demo_number();

View File

@ -11,3 +11,7 @@
int demo_number() {
return 123;
}
#ifdef __APPLE__
void initialize_android(const char* path) {}
#endif

View File

@ -0,0 +1,94 @@
//
// Game.swift
// melonx-native
//
// Created by Даниил Виноградов on 04.03.2025.
//
//import SwiftUI
//import UniformTypeIdentifiers
import Foundation
import LibRyujinx
public struct Game: Identifiable, Equatable, Hashable {
public var id: URL { fileURL }
public var containerFolder: URL
// var fileType: UTType
public var fileURL: URL
public var titleName: String
public var titleId: String
public var developer: String
public var version: String
public var icon: Data?
// public init(title: String, developer: String) {
// self.titleName = title
// self.developer = developer
// }
static func convertGameInfoToGame(gameInfo: GameInfo, url: URL) -> Game {
var gameInfo = gameInfo
var gameTemp = Game(containerFolder: url.deletingLastPathComponent(), fileURL: url, titleName: "", titleId: "", developer: "", version: "")
gameTemp.titleName = withUnsafePointer(to: &gameInfo.TitleName) {
$0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: $0)) {
let title = String(cString: $0)
logger.info("Title name: \(title)")
return title
}
}
gameTemp.developer = withUnsafePointer(to: &gameInfo.Developer) {
$0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: $0)) {
String(cString: $0)
}
}
gameTemp.titleId = withUnsafePointer(to: &gameInfo.TitleId) {
$0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: $0)) {
String(cString: $0)
}
}
gameTemp.version = withUnsafePointer(to: &gameInfo.Version) {
$0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: $0)) {
String(cString: $0)
}
}
let imageSize = Int(gameInfo.ImageSize)
if imageSize > 0, imageSize <= 1024 * 1024 {
let imageData = Data(bytes: gameInfo.ImageData, count: imageSize)
gameTemp.icon = imageData // UIImage(data: imageData)
} else {
logger.error("Invalid image size.")
}
return gameTemp
}
// func createImage(from gameInfo: GameInfo) -> UIImage? {
// // Access the struct
// let gameInfoValue = gameInfo
//
// // Get the image data
// let imageSize = Int(gameInfoValue.ImageSize)
// guard imageSize > 0, imageSize <= 1024 * 1024 else {
// print("Invalid image size.")
// return nil
// }
//
// // Convert the ImageData byte array to Swift's Data
// let imageData = Data(bytes: gameInfoValue.ImageData, count: imageSize)
//
// // Create a UIImage (or NSImage on macOS)
// print(imageData)
//
// return UIImage(data: imageData)
// }
}

View File

@ -0,0 +1,91 @@
//
// Ryujinx.swift
// melonx-native
//
// Created by Даниил Виноградов on 04.03.2025.
//
//import LibRyujinx
import Foundation
import Observation
#if os(Android)
import Android
import SkipFuse
#else
import Darwin
#endif
@Observable public class Ryujinx {
public var games: [Game] = []
public var firmwareversion = "0"
public static let shared = Ryujinx()
private init() {}
private var basePath: URL!
}
public extension Ryujinx {
func initialize(basePath: URL) {
self.basePath = basePath
RyujinxLib.shared.initialize(basePath: basePath.path())
}
func fetchFirmwareVersion() -> String {
firmwareversion = RyujinxLib.shared.installedFirmwareVersion()
return firmwareversion
}
func installFirmware(firmwarePath: String) {
RyujinxLib.shared.installFirmware(firmwarePath)
let version = fetchFirmwareVersion()
if !version.isEmpty {
self.firmwareversion = version
}
}
func loadGames(){
let fileManager = FileManager.default
let romsDirectory = basePath.appendingPathComponent("roms")
if (!fileManager.fileExists(atPath: romsDirectory.path)) {
do {
try fileManager.createDirectory(at: romsDirectory, withIntermediateDirectories: true, attributes: nil)
} catch {
logger.error("Failed to create roms directory: \(error)")
}
}
var games: [Game] = []
do {
let files = try fileManager.contentsOfDirectory(at: romsDirectory, includingPropertiesForKeys: nil)
for fileURLCandidate in files {
if fileURLCandidate.pathExtension == "zip" {
continue
}
do {
logger.debug("Parsing game path: \(fileURLCandidate)")
let handle = try FileHandle(forReadingFrom: fileURLCandidate)
guard let gameInfo = RyujinxLib.shared.getGameInfo(handle.fileDescriptor, fileURLCandidate.pathExtension)
else { continue }
let game = Game.convertGameInfoToGame(gameInfo: gameInfo, url: fileURLCandidate)
games.append(game)
} catch {
print(error)
}
}
return self.games = games
} catch {
logger.error("Error loading games from roms folder: \(error)")
self.games = []
}
}
}

View File

@ -0,0 +1,75 @@
//
// RyujinxLib.swift
// melonx-native
//
// Created by Даниил Виноградов on 04.03.2025.
//
import Foundation
import LibRyujinx
#if os(Android)
import Android
#else
import Darwin
#endif
class RyujinxLib {
public static let shared = RyujinxLib()
private init() {
handle = dlopen("libRyujinx.Headless.SDL2.so", RTLD_NOW)
}
deinit { dlclose(handle!) }
private let handle: UnsafeMutableRawPointer?
}
extension RyujinxLib {
func initialize(basePath: String) {
guard let pathCString = basePath.cString(using: .utf8)
else { return }
let sym = dlsym(handle, "initialize_android")
typealias initializeFunc = @convention(c) (UnsafePointer<CChar>) -> ()
let f = unsafeBitCast(sym, to: initializeFunc.self)
f(pathCString)
}
func installedFirmwareVersion() -> String {
let sym = dlsym(handle, "installed_firmware_version")
typealias initializeFunc = @convention(c) () -> (UnsafePointer<CChar>)
let f = unsafeBitCast(sym, to: initializeFunc.self)
let version = f()
return String(cString: version)
}
func installFirmware(_ path: String) {
guard let pathCString = path.cString(using: .utf8)
else { return }
let sym = dlsym(handle, "install_firmware")
typealias initializeFunc = @convention(c) (UnsafePointer<CChar>) -> ()
let f = unsafeBitCast(sym, to: initializeFunc.self)
f(pathCString)
}
func getGameInfo(_ fileDescriptor: Int32, _ fileURLCandidate: String) -> GameInfo? {
guard let fileURLCandidateCString = fileURLCandidate.cString(using: .utf8)
else { return nil }
let sym = dlsym(handle, "get_game_info")
typealias initializeFunc = @convention(c) (Int32, UnsafePointer<CChar>) -> (GameInfo)
let f = unsafeBitCast(sym, to: initializeFunc.self)
return f(fileDescriptor, fileURLCandidateCString)
}
}
//public struct GameInfo {
//
//}

View File

@ -0,0 +1,13 @@
# Configuration file for https://skip.tools project
#
# Kotlin dependencies and Gradle build options for this module can be configured here
#build:
# contents:
# - block: 'dependencies'
# contents:
# - 'implementation("androidx.compose.runtime:runtime")'
# this is a natively-compiled module
skip:
mode: 'native'
bridging: true

View File

@ -1,8 +1,8 @@
import Foundation
import Observation
import OSLog
import SkipFuse
fileprivate let logger: Logger = Logger(subsystem: "MeloNX", category: "MeloNX")
let logger: Logger = Logger(subsystem: "MeloNXModel", category: "MeloNXModel")
/// The Observable ViewModel used by the application.
@Observable public class ViewModel {

View File

@ -1,5 +1,5 @@
import SwiftUI
import SkipKit
import MeloNXModel
enum ContentTab: String, Hashable {
case games, home, settings
@ -10,10 +10,6 @@ struct ContentView: View {
@State var viewModel = ViewModel()
@State var appearance = ""
init() {
initializeSDL()
}
var body: some View {
TabView(selection: $tab) {
NavigationStack {
@ -33,6 +29,9 @@ struct ContentView: View {
}
.environment(viewModel)
.preferredColorScheme(appearance == "dark" ? .dark : appearance == "light" ? .light : nil)
.onAppear {
initializeSDL()
}
}
// MARK: - Helper Methods
@ -42,6 +41,8 @@ struct ContentView: View {
// SDL_SetMainReady() // Sets SDL Ready
// SDL_iPhoneSetEventPump(SDL_TRUE) // Set iOS Event Pump to true
// SDL_Init(SdlInitFlags) // Initialises SDL2
_ = Ryujinx.shared
let path = URL.documentsDirectory
Ryujinx.shared.initialize(basePath: path)
Ryujinx.shared.loadGames()
}
}

View File

@ -57,10 +57,10 @@
}
}
},
"Firmware Version: %@" : {
"Firmware" : {
},
"Framework" : {
"Firmware Version: %@" : {
},
"Games" : {

View File

@ -5,15 +5,9 @@
// Created by Даниил Виноградов on 02.03.2025.
//
import MeloNXModel
import SwiftUI
//struct Game: Identifiable {
// var id: String { title }
//
// var title: String
// var developer: String
//}
struct GamesView: View {
@State var firmwareversion = "0"
@State var searchQuery: String = ""
@ -36,7 +30,7 @@ struct GamesView: View {
guard !searchQuery.isEmpty else { return Ryujinx.shared.games }
return Ryujinx.shared.games.filter {
$0.titleName.lowercased() == searchQuery.lowercased() ||
$0.developer.lowercased() == searchQuery.lowercased()
$0.developer.lowercased() == searchQuery.lowercased()
}
}
@ -61,13 +55,6 @@ struct GamesView: View {
}
}
ToolbarItem(placement: .topBarLeading) {
// Menu {
// Button("Option 1") { }
// Button("Option 2") { }
// Button("Option 3") { }
// } label: {
// Image(systemName: "ellipsis")
// }
Menu {
Text("Firmware Version: \(firmwareversion)")
.tint(.white)
@ -84,7 +71,7 @@ struct GamesView: View {
// TODO: - Edit action
}
} label: {
Label("Framework", systemImage: "chevron.right")
Label("Firmware", systemImage: "chevron.right")
}
Button("Show MeloNX Folder") {
// TODO: - Edit action
@ -107,14 +94,20 @@ struct GameView: View {
var body: some View {
HStack(spacing: 16) {
ZStack {
RoundedRectangle(cornerRadius: 8)
.fill(Color.gray)
.frame(width: 45, height: 45)
Image(systemName: "gamecontroller.fill")
.font(.system(size: 20))
.foregroundColor(.gray)
if let iconData = game.icon,
let icon = UIImage(data: iconData)
{
Image(uiImage: icon)
.resizable()
} else {
Image(systemName: "gamecontroller.fill")
.font(.system(size: 20))
.foregroundColor(.gray)
.background(Color.gray)
}
}
.frame(width: 45, height: 45)
.clipShape(RoundedRectangle(cornerRadius: 8))
// Game Info
VStack(alignment: .leading, spacing: 2) {

View File

@ -6,6 +6,7 @@
//
import SwiftUI
import MeloNXModel
struct SettingsView : View {
@Environment(ViewModel.self) var viewModel: ViewModel

View File

@ -2,7 +2,7 @@ import Foundation
import OSLog
import SwiftUI
fileprivate let logger: Logger = Logger(subsystem: "com.xitrix.melonx", category: "MeloNX")
fileprivate let logger: Logger = Logger(subsystem: "com.xitrix.melonx", category: "melonx")
/// The Android SDK number we are running against, or `nil` if not running on Android
let androidSDK = ProcessInfo.processInfo.environment["android.os.Build.VERSION.SDK_INT"].flatMap({ Int($0) })
@ -10,8 +10,8 @@ let androidSDK = ProcessInfo.processInfo.environment["android.os.Build.VERSION.S
/// The shared top-level view for the app, loaded from the platform-specific App delegates below.
///
/// The default implementation merely loads the `ContentView` for the app and logs a message.
public struct MeloNXRootView : View {
@ObservedObject var appDelegate = MeloNXAppDelegate.shared
public struct melonxRootView : View {
@ObservedObject var appDelegate = melonxAppDelegate.shared
public init() {
}
@ -29,8 +29,8 @@ public struct MeloNXRootView : View {
///
/// This functions can update a shared observable object to communicate app state changes to interested views.
/// The sender for each of these functions will be either a `UIApplication` (iOS) or `AppCompatActivity` (Android)
public class MeloNXAppDelegate: ObservableObject {
public static let shared = MeloNXAppDelegate()
public class melonxAppDelegate: ObservableObject {
public static let shared = melonxAppDelegate()
private init() {
}

View File

@ -0,0 +1,33 @@
import XCTest
import OSLog
import Foundation
import SkipBridge
@testable import MeloNXModel
let logger: Logger = Logger(subsystem: "MeloNXModel", category: "Tests")
@available(macOS 13, *)
final class MeloNXModelTests: XCTestCase {
override func setUp() {
#if os(Android)
// needed to load the compiled bridge from the transpiled tests
loadPeerLibrary(packageName: "melonx-native", moduleName: "MeloNXModel")
#endif
}
func testMeloNXModel() throws {
logger.log("running testMeloNXModel")
XCTAssertEqual(1 + 2, 3, "basic test")
}
func testViewModel() async throws {
let vm = ViewModel()
vm.items.append(Item(title: "ABC"))
XCTAssertFalse(vm.items.isEmpty)
XCTAssertEqual("ABC", vm.items.last?.title)
vm.clear()
XCTAssertTrue(vm.items.isEmpty)
}
}

Some files were not shown because too many files have changed in this diff Show More