Implemented game info data
@ -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.** { *; }
|
@ -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"
|
@ -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;
|
||||
|
||||
/**
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 6.2 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 1003 B |
After Width: | Height: | Size: 1003 B |
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 6.1 KiB |
After Width: | Height: | Size: 6.1 KiB |
@ -0,0 +1 @@
|
||||
melonx
|
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 937 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 8.0 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 7.3 KiB |
After Width: | Height: | Size: 7.3 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 308 KiB |
After Width: | Height: | Size: 6.6 KiB |
@ -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
|
@ -0,0 +1 @@
|
||||
melonx
|
@ -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 */
|
||||
};
|
46
src/MeloNX-Skip/melonx-native/Package.swift
Normal 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")]),
|
||||
]
|
||||
)
|
@ -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
|
@ -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
|
@ -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)
|
@ -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();
|
||||
|
@ -11,3 +11,7 @@
|
||||
int demo_number() {
|
||||
return 123;
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
void initialize_android(const char* path) {}
|
||||
#endif
|
@ -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)
|
||||
// }
|
||||
}
|
@ -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 = []
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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 {
|
||||
//
|
||||
//}
|
@ -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
|
@ -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 {
|
@ -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()
|
||||
}
|
||||
}
|
@ -57,10 +57,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Firmware Version: %@" : {
|
||||
"Firmware" : {
|
||||
|
||||
},
|
||||
"Framework" : {
|
||||
"Firmware Version: %@" : {
|
||||
|
||||
},
|
||||
"Games" : {
|
@ -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) {
|
@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import MeloNXModel
|
||||
|
||||
struct SettingsView : View {
|
||||
@Environment(ViewModel.self) var viewModel: ViewModel
|
@ -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() {
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|