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

View File

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

View File

@ -1,4 +1,4 @@
package melo.nx package melonx.module
import skip.lib.* import skip.lib.*
import skip.model.* import skip.model.*
@ -20,7 +20,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.core.app.ActivityCompat 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. /// AndroidAppMain is the `android.app.Application` entry point, and must match `application android:name` in the AndroidMainfest.xml file.
open class AndroidAppMain: Application { open class AndroidAppMain: Application {
@ -70,32 +70,32 @@ open class MainActivity: AppCompatActivity {
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
MeloNXAppDelegate.shared.onStart(this) melonxAppDelegate.shared.onStart(this)
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
MeloNXAppDelegate.shared.onResume(this) melonxAppDelegate.shared.onResume(this)
} }
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
MeloNXAppDelegate.shared.onPause(this) melonxAppDelegate.shared.onPause(this)
} }
override fun onStop() { override fun onStop() {
super.onStop() super.onStop()
MeloNXAppDelegate.shared.onStop(this) melonxAppDelegate.shared.onStop(this)
} }
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
MeloNXAppDelegate.shared.onDestroy(this) melonxAppDelegate.shared.onDestroy(this)
} }
override fun onLowMemory() { override fun onLowMemory() {
super.onLowMemory() super.onLowMemory()
MeloNXAppDelegate.shared.onLowMemory(this) melonxAppDelegate.shared.onLowMemory(this)
} }
override fun onRestart() { override fun onRestart() {
@ -126,7 +126,7 @@ internal fun PresentationRootView(context: ComposeContext) {
PresentationRoot(defaultColorScheme = colorScheme, context = context) { ctx -> PresentationRoot(defaultColorScheme = colorScheme, context = context) { ctx ->
val contentContext = ctx.content() val contentContext = ctx.content()
Box(modifier = ctx.modifier.fillMaxSize(), contentAlignment = Alignment.Center) { 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 SwiftUI
import MeloNX import melonx
/// The entry point to the app simply loads the App implementation from SPM module. /// The entry point to the app simply loads the App implementation from SPM module.
@main struct AppMain: App { @main struct AppMain: App {
@ -8,7 +8,7 @@ import MeloNX
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
MeloNXRootView() melonxRootView()
} }
.onChange(of: scenePhase) { oldPhase, newPhase in .onChange(of: scenePhase) { oldPhase, newPhase in
switch newPhase { switch newPhase {
@ -25,7 +25,7 @@ import MeloNX
} }
} }
typealias AppDelegate = MeloNXAppDelegate typealias AppDelegate = melonxAppDelegate
#if canImport(UIKit) #if canImport(UIKit)
typealias AppDelegateAdaptor = UIApplicationDelegateAdaptor typealias AppDelegateAdaptor = UIApplicationDelegateAdaptor
typealias AppMainDelegateBase = UIApplicationDelegate typealias AppMainDelegateBase = UIApplicationDelegate

View File

@ -0,0 +1 @@
melonx

View File

@ -7,8 +7,8 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
49231BAC2AC5BCEF00F98ADF /* MeloNXApp in Frameworks */ = {isa = PBXBuildFile; productRef = 49231BAB2AC5BCEF00F98ADF /* MeloNXApp */; }; 49231BAC2AC5BCEF00F98ADF /* melonxApp in Frameworks */ = {isa = PBXBuildFile; productRef = 49231BAB2AC5BCEF00F98ADF /* melonxApp */; };
49231BAD2AC5BCEF00F98ADF /* MeloNXApp in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 49231BAB2AC5BCEF00F98ADF /* MeloNXApp */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 49231BAD2AC5BCEF00F98ADF /* melonxApp in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 49231BAB2AC5BCEF00F98ADF /* melonxApp */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
496BDBEE2B8A7E9C00C09264 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 496BDBED2B8A7E9C00C09264 /* Localizable.xcstrings */; }; 496BDBEE2B8A7E9C00C09264 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 496BDBED2B8A7E9C00C09264 /* Localizable.xcstrings */; };
499CD43B2AC5B799001AE8D8 /* Main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49F90C2B2A52156200F06D93 /* Main.swift */; }; 499CD43B2AC5B799001AE8D8 /* Main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49F90C2B2A52156200F06D93 /* Main.swift */; };
499CD4402AC5B799001AE8D8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49F90C2F2A52156300F06D93 /* Assets.xcassets */; }; 499CD4402AC5B799001AE8D8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49F90C2F2A52156300F06D93 /* Assets.xcassets */; };
@ -21,7 +21,7 @@
dstPath = ""; dstPath = "";
dstSubfolderSpec = 10; dstSubfolderSpec = 10;
files = ( files = (
49231BAD2AC5BCEF00F98ADF /* MeloNXApp in Embed Frameworks */, 49231BAD2AC5BCEF00F98ADF /* melonxApp in Embed Frameworks */,
); );
name = "Embed Frameworks"; name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -29,12 +29,12 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
493609562A6B7EAE00C401E2 /* MeloNX */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = MeloNX; path = ..; sourceTree = "<group>"; }; 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; }; 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>"; }; 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>"; }; 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>"; }; 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; }; 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; }; 49F90C2B2A52156200F06D93 /* Main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Main.swift; path = Sources/Main.swift; sourceTree = SOURCE_ROOT; };
@ -47,7 +47,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
49231BAC2AC5BCEF00F98ADF /* MeloNXApp in Frameworks */, 49231BAC2AC5BCEF00F98ADF /* melonxApp in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -57,7 +57,7 @@
496BDBEC2B89A47800C09264 /* Products */ = { 496BDBEC2B89A47800C09264 /* Products */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
496BDBEB2B89A47800C09264 /* MeloNX.app */, 496BDBEB2B89A47800C09264 /* melonx.app */,
); );
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
@ -75,9 +75,9 @@
children = ( children = (
496EB72F2A6AE4DE00C1253C /* README.md */, 496EB72F2A6AE4DE00C1253C /* README.md */,
496EB72F2A6AE4DE00C1253A /* Skip.env */, 496EB72F2A6AE4DE00C1253A /* Skip.env */,
496EB72F2A6AE4DE00C1253B /* MeloNX.xcconfig */, 496EB72F2A6AE4DE00C1253B /* melonx.xcconfig */,
496BDBED2B8A7E9C00C09264 /* Localizable.xcstrings */, 496BDBED2B8A7E9C00C09264 /* Localizable.xcstrings */,
493609562A6B7EAE00C401E2 /* MeloNX */, 493609562A6B7EAE00C401E2 /* melonx */,
49F90C2A2A52156200F06D93 /* App */, 49F90C2A2A52156200F06D93 /* App */,
49AB54462B066A7E007B79B2 /* SkipStone */, 49AB54462B066A7E007B79B2 /* SkipStone */,
496BDBEC2B89A47800C09264 /* Products */, 496BDBEC2B89A47800C09264 /* Products */,
@ -98,9 +98,9 @@
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
499CD4382AC5B799001AE8D8 /* MeloNX */ = { 499CD4382AC5B799001AE8D8 /* melonx */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 499CD4412AC5B799001AE8D8 /* Build configuration list for PBXNativeTarget "MeloNX" */; buildConfigurationList = 499CD4412AC5B799001AE8D8 /* Build configuration list for PBXNativeTarget "melonx" */;
buildPhases = ( buildPhases = (
499CD43A2AC5B799001AE8D8 /* Sources */, 499CD43A2AC5B799001AE8D8 /* Sources */,
499CD43C2AC5B799001AE8D8 /* Frameworks */, 499CD43C2AC5B799001AE8D8 /* Frameworks */,
@ -112,12 +112,12 @@
); );
dependencies = ( dependencies = (
); );
name = MeloNX; name = melonx;
packageProductDependencies = ( packageProductDependencies = (
49231BAB2AC5BCEF00F98ADF /* MeloNXApp */, 49231BAB2AC5BCEF00F98ADF /* melonxApp */,
); );
productName = App; productName = App;
productReference = 496BDBEB2B89A47800C09264 /* MeloNX.app */; productReference = 496BDBEB2B89A47800C09264 /* melonx.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
}; };
/* End PBXNativeTarget section */ /* End PBXNativeTarget section */
@ -130,7 +130,7 @@
LastSwiftUpdateCheck = 1430; LastSwiftUpdateCheck = 1430;
LastUpgradeCheck = 1540; LastUpgradeCheck = 1540;
}; };
buildConfigurationList = 49F90C232A52156200F06D93 /* Build configuration list for PBXProject "MeloNX" */; buildConfigurationList = 49F90C232A52156200F06D93 /* Build configuration list for PBXProject "melonx" */;
compatibilityVersion = "Xcode 14.0"; compatibilityVersion = "Xcode 14.0";
developmentRegion = en; developmentRegion = en;
hasScannedForEncodings = 0; hasScannedForEncodings = 0;
@ -148,7 +148,7 @@
projectDirPath = ""; projectDirPath = "";
projectRoot = ""; projectRoot = "";
targets = ( targets = (
499CD4382AC5B799001AE8D8 /* MeloNX */, 499CD4382AC5B799001AE8D8 /* melonx */,
); );
}; };
/* End PBXProject section */ /* End PBXProject section */
@ -201,7 +201,7 @@
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
499CD4422AC5B799001AE8D8 /* Debug */ = { 499CD4422AC5B799001AE8D8 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 496EB72F2A6AE4DE00C1253B /* MeloNX.xcconfig */; baseConfigurationReference = 496EB72F2A6AE4DE00C1253B /* melonx.xcconfig */;
buildSettings = { buildSettings = {
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
@ -211,7 +211,7 @@
}; };
499CD4432AC5B799001AE8D8 /* Release */ = { 499CD4432AC5B799001AE8D8 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 496EB72F2A6AE4DE00C1253B /* MeloNX.xcconfig */; baseConfigurationReference = 496EB72F2A6AE4DE00C1253B /* melonx.xcconfig */;
buildSettings = { buildSettings = {
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
@ -258,7 +258,7 @@
/* End XCBuildConfiguration section */ /* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */ /* Begin XCConfigurationList section */
499CD4412AC5B799001AE8D8 /* Build configuration list for PBXNativeTarget "MeloNX" */ = { 499CD4412AC5B799001AE8D8 /* Build configuration list for PBXNativeTarget "melonx" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
499CD4422AC5B799001AE8D8 /* Debug */, 499CD4422AC5B799001AE8D8 /* Debug */,
@ -267,7 +267,7 @@
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
49F90C232A52156200F06D93 /* Build configuration list for PBXProject "MeloNX" */ = { 49F90C232A52156200F06D93 /* Build configuration list for PBXProject "melonx" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
49F90C4B2A52156300F06D93 /* Debug */, 49F90C4B2A52156300F06D93 /* Debug */,
@ -279,9 +279,9 @@
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCSwiftPackageProductDependency section */ /* Begin XCSwiftPackageProductDependency section */
49231BAB2AC5BCEF00F98ADF /* MeloNXApp */ = { 49231BAB2AC5BCEF00F98ADF /* melonxApp */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
productName = MeloNXApp; productName = melonxApp;
}; };
/* End XCSwiftPackageProductDependency section */ /* 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. This is a [Skip](https://skip.tools) dual-platform app project.
It builds a native app for both iOS and Android. 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. Android Studio's Device Manager.
To run both the Swift and Kotlin apps simultaneously, 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 A build phases runs the "Launch Android APK" script that
will deploy the transpiled app a running Android emulator or connected device. 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 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). // The configuration file for your Skip App (https://skip.tools).
// Properties specified here are shared between // 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 // and will be included in the app's metadata files
// Info.plist and AndroidManifest.xml // 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 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 is the unique id for both the iOS and Android app
PRODUCT_BUNDLE_IDENTIFIER = com.xitrix.melonx PRODUCT_BUNDLE_IDENTIFIER = com.xitrix.melonx
@ -17,4 +17,4 @@ MARKETING_VERSION = 0.0.1
CURRENT_PROJECT_VERSION = 1 CURRENT_PROJECT_VERSION = 1
// The package name for the Android entry point, referenced by the AndroidManifest.xml // 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(); int get_current_fps();
void initialize_android(char* path); void initialize_android(const char* path);
int demo_number(); int demo_number();

View File

@ -11,3 +11,7 @@
int demo_number() { int demo_number() {
return 123; 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 Foundation
import Observation 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. /// The Observable ViewModel used by the application.
@Observable public class ViewModel { @Observable public class ViewModel {

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@ import Foundation
import OSLog import OSLog
import SwiftUI 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 /// 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) }) 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 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. /// The default implementation merely loads the `ContentView` for the app and logs a message.
public struct MeloNXRootView : View { public struct melonxRootView : View {
@ObservedObject var appDelegate = MeloNXAppDelegate.shared @ObservedObject var appDelegate = melonxAppDelegate.shared
public init() { 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. /// 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) /// The sender for each of these functions will be either a `UIApplication` (iOS) or `AppCompatActivity` (Android)
public class MeloNXAppDelegate: ObservableObject { public class melonxAppDelegate: ObservableObject {
public static let shared = MeloNXAppDelegate() public static let shared = melonxAppDelegate()
private init() { 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