diff --git a/Ryujinx.sln.DotSettings b/Ryujinx.sln.DotSettings
index 049bdaf69..1e5b9807e 100644
--- a/Ryujinx.sln.DotSettings
+++ b/Ryujinx.sln.DotSettings
@@ -4,6 +4,8 @@
UseExplicitType
UseExplicitType
<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="I" Suffix="" Style="AaBb" /></Policy>
+ <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"><ElementKinds><Kind Name="NAMESPACE" /><Kind Name="CLASS" /><Kind Name="STRUCT" /><Kind Name="ENUM" /><Kind Name="DELEGATE" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="I" Suffix="" Style="AaBb" /></Policy></Policy>
+ True
True
True
True
diff --git a/global.json b/global.json
index 391ba3c2a..d3adbc477 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "8.0.100",
+ "version": "9.0.101",
"rollForward": "latestFeature"
}
}
diff --git a/src/MeloNX-Android/.gitignore b/src/MeloNX-Android/.gitignore
new file mode 100644
index 000000000..aa724b770
--- /dev/null
+++ b/src/MeloNX-Android/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/src/MeloNX-Android/.idea/.name b/src/MeloNX-Android/.idea/.name
new file mode 100644
index 000000000..c339e91e0
--- /dev/null
+++ b/src/MeloNX-Android/.idea/.name
@@ -0,0 +1 @@
+MeloNX
\ No newline at end of file
diff --git a/src/MeloNX-Android/.idea/compiler.xml b/src/MeloNX-Android/.idea/compiler.xml
new file mode 100644
index 000000000..b86273d94
--- /dev/null
+++ b/src/MeloNX-Android/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/MeloNX-Android/.idea/gradle.xml b/src/MeloNX-Android/.idea/gradle.xml
new file mode 100644
index 000000000..ae733f102
--- /dev/null
+++ b/src/MeloNX-Android/.idea/gradle.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/MeloNX-Android/.idea/kotlinc.xml b/src/MeloNX-Android/.idea/kotlinc.xml
new file mode 100644
index 000000000..6d0ee1c2a
--- /dev/null
+++ b/src/MeloNX-Android/.idea/kotlinc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/MeloNX-Android/.idea/migrations.xml b/src/MeloNX-Android/.idea/migrations.xml
new file mode 100644
index 000000000..f8051a6f9
--- /dev/null
+++ b/src/MeloNX-Android/.idea/migrations.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/MeloNX-Android/.idea/misc.xml b/src/MeloNX-Android/.idea/misc.xml
new file mode 100644
index 000000000..74dd639e4
--- /dev/null
+++ b/src/MeloNX-Android/.idea/misc.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/MeloNX-Android/.idea/runConfigurations.xml b/src/MeloNX-Android/.idea/runConfigurations.xml
new file mode 100644
index 000000000..16660f1d8
--- /dev/null
+++ b/src/MeloNX-Android/.idea/runConfigurations.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/MeloNX-Android/.idea/vcs.xml b/src/MeloNX-Android/.idea/vcs.xml
new file mode 100644
index 000000000..b2bdec2d7
--- /dev/null
+++ b/src/MeloNX-Android/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/MeloNX-Android/app/.gitignore b/src/MeloNX-Android/app/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/src/MeloNX-Android/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/src/MeloNX-Android/app/build.gradle.kts b/src/MeloNX-Android/app/build.gradle.kts
new file mode 100644
index 000000000..3f1a7e851
--- /dev/null
+++ b/src/MeloNX-Android/app/build.gradle.kts
@@ -0,0 +1,61 @@
+plugins {
+ alias(libs.plugins.android.application)
+ alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.kotlin.compose)
+}
+
+android {
+ namespace = "com.xitrix.melonx"
+ compileSdk = 35
+
+ defaultConfig {
+ applicationId = "com.xitrix.melonx"
+ minSdk = 29
+ targetSdk = 35
+ versionCode = 1
+ versionName = "1.0"
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+ buildFeatures {
+ compose = true
+ }
+}
+
+dependencies {
+
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.lifecycle.runtime.ktx)
+ implementation(libs.androidx.activity.compose)
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.androidx.ui)
+ implementation(libs.androidx.ui.graphics)
+ implementation(libs.androidx.ui.tooling.preview)
+ implementation(libs.androidx.material3)
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.junit)
+ androidTestImplementation(libs.androidx.espresso.core)
+ androidTestImplementation(platform(libs.androidx.compose.bom))
+ androidTestImplementation(libs.androidx.ui.test.junit4)
+ debugImplementation(libs.androidx.ui.tooling)
+ debugImplementation(libs.androidx.ui.test.manifest)
+ implementation(libs.androidx.navigation.compose)
+
+}
diff --git a/src/MeloNX-Android/app/proguard-rules.pro b/src/MeloNX-Android/app/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/src/MeloNX-Android/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/src/MeloNX-Android/app/src/androidTest/java/com/xitrix/melonx/ExampleInstrumentedTest.kt b/src/MeloNX-Android/app/src/androidTest/java/com/xitrix/melonx/ExampleInstrumentedTest.kt
new file mode 100644
index 000000000..d8b5307d9
--- /dev/null
+++ b/src/MeloNX-Android/app/src/androidTest/java/com/xitrix/melonx/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.xitrix.melonx
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.xitrix.melonx", appContext.packageName)
+ }
+}
diff --git a/src/MeloNX-Android/app/src/main/AndroidManifest.xml b/src/MeloNX-Android/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..95b93d56d
--- /dev/null
+++ b/src/MeloNX-Android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/MeloNX-Android/app/src/main/java/com/xitrix/melonx/HomeScreen.kt b/src/MeloNX-Android/app/src/main/java/com/xitrix/melonx/HomeScreen.kt
new file mode 100644
index 000000000..9ea4f36e4
--- /dev/null
+++ b/src/MeloNX-Android/app/src/main/java/com/xitrix/melonx/HomeScreen.kt
@@ -0,0 +1,34 @@
+package com.xitrix.melonx
+
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.navigation.NavHostController
+import com.xitrix.melonx.ui.theme.MeloNXTheme
+
+
+@Composable
+fun HomeScreen(
+ scaffoldState: ScaffoldState,
+ navController: NavHostController
+) {
+ Scaffold(
+ modifier = modifier
+ ) { padding ->
+ Text(
+ text = "Hello $name!",
+ modifier = Modifier.padding(padding)
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun HomeScreenPreview() {
+ MeloNXTheme {
+ HomeScreen("Android")
+ }
+}
diff --git a/src/MeloNX-Android/app/src/main/java/com/xitrix/melonx/MainActivity.kt b/src/MeloNX-Android/app/src/main/java/com/xitrix/melonx/MainActivity.kt
new file mode 100644
index 000000000..4afb1ce2e
--- /dev/null
+++ b/src/MeloNX-Android/app/src/main/java/com/xitrix/melonx/MainActivity.kt
@@ -0,0 +1,124 @@
+package com.xitrix.melonx
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.annotation.StringRes
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.*
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.NavigationBar
+import androidx.compose.material3.NavigationBarItem
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.navigation.compose.NavHost
+import com.xitrix.melonx.ui.theme.MeloNXTheme
+
+@Immutable
+data class ScaffoldViewState(
+ @StringRes val topAppBarTitle: Int? = null,
+ @StringRes val fabText: Int? = null,
+ // TODO : ...etc (top app bar actions, nav icon...)
+)
+
+class MainActivity : ComponentActivity() {
+ @OptIn(ExperimentalMaterial3Api::class)
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+ setContent {
+ MeloNXTheme {
+ val items = listOf(
+ BottomNavItem("Home", "home", Icons.Default.Home),
+ BottomNavItem("Settings", "settings", Icons.Default.Settings)
+ )
+ var currentRoute by remember { mutableStateOf("home") }
+ var scaffoldViewState by remember {
+ mutableStateOf(ScaffoldViewState())
+ }
+
+
+ Scaffold(
+
+// modifier = Modifier.fillMaxSize(),
+ topBar = {
+ TopAppBar(
+ title = {
+ Text(text = "Хуета")
+ }
+ )
+ },
+ bottomBar = {
+ BottomNavigationBar(
+ items = items,
+ currentRoute = currentRoute,
+ onItemClick = { currentRoute = it }
+ )
+ }) { innerPadding ->
+// NavHost {
+//// composable("a") {
+// LaunchedEffect(Unit) {
+//// scaffoldViewState = // TODO : choose the top app bar and fab appearance for this page
+// }
+//
+// AScreen()
+//// }
+// composable("b") {
+// LaunchedEffect(Unit) {
+//// scaffoldViewState = // TODO : choose the top app bar and fab appearance for this page
+// }
+//
+// BScreen()
+// }
+// }
+ when (currentRoute) {
+ "home" -> HomeScreen("Android", Modifier.padding(innerPadding))
+ "settings" -> SettingsScreen(innerPadding)
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun BottomNavigationBar(
+ items: List,
+ currentRoute: String,
+ onItemClick: (String) -> Unit
+) {
+ NavigationBar {
+ items.forEach { item ->
+ NavigationBarItem(
+ icon = { Icon(item.icon, contentDescription = item.title) },
+ label = { Text(item.title) },
+ selected = currentRoute == item.route,
+ onClick = { onItemClick(item.route) }
+ )
+ }
+ }
+}
+
+data class BottomNavItem(
+ val title: String,
+ val route: String,
+ val icon: ImageVector
+)
diff --git a/src/MeloNX-Android/app/src/main/java/com/xitrix/melonx/SettingsScreen.kt b/src/MeloNX-Android/app/src/main/java/com/xitrix/melonx/SettingsScreen.kt
new file mode 100644
index 000000000..24be7f8cb
--- /dev/null
+++ b/src/MeloNX-Android/app/src/main/java/com/xitrix/melonx/SettingsScreen.kt
@@ -0,0 +1,16 @@
+package com.xitrix.melonx
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+
+
+@Composable
+fun SettingsScreen(paddingValues: PaddingValues) {
+ Box(modifier = Modifier.padding(paddingValues)) {
+ Text(text = "Screen One")
+ }
+}
diff --git a/src/MeloNX-Android/app/src/main/java/com/xitrix/melonx/ui/theme/Color.kt b/src/MeloNX-Android/app/src/main/java/com/xitrix/melonx/ui/theme/Color.kt
new file mode 100644
index 000000000..c067b6f46
--- /dev/null
+++ b/src/MeloNX-Android/app/src/main/java/com/xitrix/melonx/ui/theme/Color.kt
@@ -0,0 +1,11 @@
+package com.xitrix.melonx.ui.theme
+
+import androidx.compose.ui.graphics.Color
+
+val Purple80 = Color(0xFFD0BCFF)
+val PurpleGrey80 = Color(0xFFCCC2DC)
+val Pink80 = Color(0xFFEFB8C8)
+
+val Purple40 = Color(0xFF6650a4)
+val PurpleGrey40 = Color(0xFF625b71)
+val Pink40 = Color(0xFF7D5260)
\ No newline at end of file
diff --git a/src/MeloNX-Android/app/src/main/java/com/xitrix/melonx/ui/theme/Theme.kt b/src/MeloNX-Android/app/src/main/java/com/xitrix/melonx/ui/theme/Theme.kt
new file mode 100644
index 000000000..9fceff816
--- /dev/null
+++ b/src/MeloNX-Android/app/src/main/java/com/xitrix/melonx/ui/theme/Theme.kt
@@ -0,0 +1,58 @@
+package com.xitrix.melonx.ui.theme
+
+import android.app.Activity
+import android.os.Build
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
+
+private val DarkColorScheme = darkColorScheme(
+ primary = Purple80,
+ secondary = PurpleGrey80,
+ tertiary = Pink80
+)
+
+private val LightColorScheme = lightColorScheme(
+ primary = Purple40,
+ secondary = PurpleGrey40,
+ tertiary = Pink40
+
+ /* Other default colors to override
+ background = Color(0xFFFFFBFE),
+ surface = Color(0xFFFFFBFE),
+ onPrimary = Color.White,
+ onSecondary = Color.White,
+ onTertiary = Color.White,
+ onBackground = Color(0xFF1C1B1F),
+ onSurface = Color(0xFF1C1B1F),
+ */
+)
+
+@Composable
+fun MeloNXTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ // Dynamic color is available on Android 12+
+ dynamicColor: Boolean = true,
+ content: @Composable () -> Unit
+) {
+ val colorScheme = when {
+ dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
+ val context = LocalContext.current
+ if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
+ }
+
+ darkTheme -> DarkColorScheme
+ else -> LightColorScheme
+ }
+
+ MaterialTheme(
+ colorScheme = colorScheme,
+ typography = Typography,
+ content = content
+ )
+}
diff --git a/src/MeloNX-Android/app/src/main/java/com/xitrix/melonx/ui/theme/Type.kt b/src/MeloNX-Android/app/src/main/java/com/xitrix/melonx/ui/theme/Type.kt
new file mode 100644
index 000000000..c677d22f7
--- /dev/null
+++ b/src/MeloNX-Android/app/src/main/java/com/xitrix/melonx/ui/theme/Type.kt
@@ -0,0 +1,34 @@
+package com.xitrix.melonx.ui.theme
+
+import androidx.compose.material3.Typography
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+
+// Set of Material typography styles to start with
+val Typography = Typography(
+ bodyLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.5.sp
+ )
+ /* Other default text styles to override
+ titleLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 22.sp,
+ lineHeight = 28.sp,
+ letterSpacing = 0.sp
+ ),
+ labelSmall = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Medium,
+ fontSize = 11.sp,
+ lineHeight = 16.sp,
+ letterSpacing = 0.5.sp
+ )
+ */
+)
\ No newline at end of file
diff --git a/src/MeloNX-Android/app/src/main/res/drawable/ic_launcher_background.xml b/src/MeloNX-Android/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 000000000..07d5da9cb
--- /dev/null
+++ b/src/MeloNX-Android/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/MeloNX-Android/app/src/main/res/drawable/ic_launcher_foreground.xml b/src/MeloNX-Android/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 000000000..7706ab9e6
--- /dev/null
+++ b/src/MeloNX-Android/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/MeloNX-Android/app/src/main/res/mipmap-anydpi/ic_launcher.xml b/src/MeloNX-Android/app/src/main/res/mipmap-anydpi/ic_launcher.xml
new file mode 100644
index 000000000..b3e26b4c6
--- /dev/null
+++ b/src/MeloNX-Android/app/src/main/res/mipmap-anydpi/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/src/MeloNX-Android/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/src/MeloNX-Android/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml
new file mode 100644
index 000000000..b3e26b4c6
--- /dev/null
+++ b/src/MeloNX-Android/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/src/MeloNX-Android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/src/MeloNX-Android/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 000000000..c209e78ec
Binary files /dev/null and b/src/MeloNX-Android/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/src/MeloNX-Android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/src/MeloNX-Android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..b2dfe3d1b
Binary files /dev/null and b/src/MeloNX-Android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/src/MeloNX-Android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/src/MeloNX-Android/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 000000000..4f0f1d64e
Binary files /dev/null and b/src/MeloNX-Android/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/src/MeloNX-Android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/src/MeloNX-Android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..62b611da0
Binary files /dev/null and b/src/MeloNX-Android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/src/MeloNX-Android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/src/MeloNX-Android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 000000000..948a3070f
Binary files /dev/null and b/src/MeloNX-Android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/src/MeloNX-Android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/src/MeloNX-Android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..1b9a6956b
Binary files /dev/null and b/src/MeloNX-Android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/src/MeloNX-Android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/src/MeloNX-Android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 000000000..28d4b77f9
Binary files /dev/null and b/src/MeloNX-Android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/src/MeloNX-Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/src/MeloNX-Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..9287f5083
Binary files /dev/null and b/src/MeloNX-Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/src/MeloNX-Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/src/MeloNX-Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 000000000..aa7d6427e
Binary files /dev/null and b/src/MeloNX-Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/src/MeloNX-Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/src/MeloNX-Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..9126ae37c
Binary files /dev/null and b/src/MeloNX-Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/src/MeloNX-Android/app/src/main/res/values/colors.xml b/src/MeloNX-Android/app/src/main/res/values/colors.xml
new file mode 100644
index 000000000..ca1931bca
--- /dev/null
+++ b/src/MeloNX-Android/app/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
diff --git a/src/MeloNX-Android/app/src/main/res/values/strings.xml b/src/MeloNX-Android/app/src/main/res/values/strings.xml
new file mode 100644
index 000000000..4333bc69b
--- /dev/null
+++ b/src/MeloNX-Android/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ MeloNX
+
\ No newline at end of file
diff --git a/src/MeloNX-Android/app/src/main/res/values/themes.xml b/src/MeloNX-Android/app/src/main/res/values/themes.xml
new file mode 100644
index 000000000..3567c22d4
--- /dev/null
+++ b/src/MeloNX-Android/app/src/main/res/values/themes.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/MeloNX-Android/app/src/main/res/xml/backup_rules.xml b/src/MeloNX-Android/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 000000000..148c18b65
--- /dev/null
+++ b/src/MeloNX-Android/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/src/MeloNX-Android/app/src/main/res/xml/data_extraction_rules.xml b/src/MeloNX-Android/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 000000000..0c4f95cab
--- /dev/null
+++ b/src/MeloNX-Android/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/src/MeloNX-Android/app/src/test/java/com/xitrix/melonx/ExampleUnitTest.kt b/src/MeloNX-Android/app/src/test/java/com/xitrix/melonx/ExampleUnitTest.kt
new file mode 100644
index 000000000..ef00791af
--- /dev/null
+++ b/src/MeloNX-Android/app/src/test/java/com/xitrix/melonx/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.xitrix.melonx
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/src/MeloNX-Android/build.gradle.kts b/src/MeloNX-Android/build.gradle.kts
new file mode 100644
index 000000000..5c98ad09e
--- /dev/null
+++ b/src/MeloNX-Android/build.gradle.kts
@@ -0,0 +1,6 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+ alias(libs.plugins.android.application) apply false
+ alias(libs.plugins.kotlin.android) apply false
+ alias(libs.plugins.kotlin.compose) apply false
+}
diff --git a/src/MeloNX-Android/gradle.properties b/src/MeloNX-Android/gradle.properties
new file mode 100644
index 000000000..132244e5b
--- /dev/null
+++ b/src/MeloNX-Android/gradle.properties
@@ -0,0 +1,23 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. For more details, visit
+# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
diff --git a/src/MeloNX-Android/gradle/libs.versions.toml b/src/MeloNX-Android/gradle/libs.versions.toml
new file mode 100644
index 000000000..b77678454
--- /dev/null
+++ b/src/MeloNX-Android/gradle/libs.versions.toml
@@ -0,0 +1,34 @@
+[versions]
+agp = "8.7.2"
+kotlin = "2.0.0"
+coreKtx = "1.15.0"
+junit = "4.13.2"
+junitVersion = "1.2.1"
+espressoCore = "3.6.1"
+lifecycleRuntimeKtx = "2.8.7"
+activityCompose = "1.10.1"
+composeBom = "2025.02.00"
+navigationCompose = "2.8.8"
+
+[libraries]
+androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
+androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
+junit = { group = "junit", name = "junit", version.ref = "junit" }
+androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
+androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
+androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
+androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
+androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
+androidx-ui = { group = "androidx.compose.ui", name = "ui" }
+androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
+androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
+androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
+androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
+androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
+androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "agp" }
+kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+
diff --git a/src/MeloNX-Android/gradle/wrapper/gradle-wrapper.jar b/src/MeloNX-Android/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..e708b1c02
Binary files /dev/null and b/src/MeloNX-Android/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/src/MeloNX-Android/gradle/wrapper/gradle-wrapper.properties b/src/MeloNX-Android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..05abcb79c
--- /dev/null
+++ b/src/MeloNX-Android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sun Mar 02 15:08:35 CET 2025
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/src/MeloNX-Android/gradlew b/src/MeloNX-Android/gradlew
new file mode 100755
index 000000000..4f906e0c8
--- /dev/null
+++ b/src/MeloNX-Android/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/src/MeloNX-Android/gradlew.bat b/src/MeloNX-Android/gradlew.bat
new file mode 100644
index 000000000..107acd32c
--- /dev/null
+++ b/src/MeloNX-Android/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/src/MeloNX-Android/settings.gradle.kts b/src/MeloNX-Android/settings.gradle.kts
new file mode 100644
index 000000000..1ac114702
--- /dev/null
+++ b/src/MeloNX-Android/settings.gradle.kts
@@ -0,0 +1,23 @@
+pluginManagement {
+ repositories {
+ google {
+ content {
+ includeGroupByRegex("com\\.android.*")
+ includeGroupByRegex("com\\.google.*")
+ includeGroupByRegex("androidx.*")
+ }
+ }
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.name = "MeloNX"
+include(":app")
diff --git a/src/MeloNX-Skip/melonx-skip/.gitignore b/src/MeloNX-Skip/melonx-skip/.gitignore
new file mode 100644
index 000000000..62c2594fa
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/.gitignore
@@ -0,0 +1,82 @@
+## User settings
+
+# vi
+.*.swp
+.*.swo
+
+# macOS
+.DS_Store
+
+# gradle properties
+local.properties
+.gradle/
+.android/
+.kotlin/
+Android/app/keystore.jks
+Android/app/keystore.properties
+
+xcodebuild*.log
+
+default.profraw
+*.mobileprovision
+*.cer
+
+
+# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
+# hence it is not needed unless you have added a package configuration file to your project
+.swiftpm
+.build/
+build/
+DerivedData/
+xcuserdata/
+xcodebuild*.log
+.idea/
+
+*.moved-aside
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+*.xcscmblueprint
+*.xccheckout
+
+## Obj-C/Swift specific
+*.hmap
+
+## App packaging
+*.ipa
+*.dSYM.zip
+*.dSYM
+
+## Playgrounds
+timeline.xctimeline
+playground.xcworkspace
+
+# Swift Package Manager
+#
+# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
+Packages/
+Package.pins
+Package.resolved
+#*.xcodeproj
+
+Carthage/Build/
+
+# fastlane
+#
+# It is recommended to not store the screenshots in the git repo.
+# Instead, use fastlane to re-generate the screenshots whenever they are needed.
+# For more information about the recommended setup visit:
+# https://docs.fastlane.tools/best-practices/source-control/#source-control
+
+**/fastlane/apikey.json
+**/fastlane/report.xml
+**/fastlane/README.md
+**/fastlane/Preview.html
+**/fastlane/screenshots/**/*.png
+**/fastlane/metadata/android/*/images/**/*.png
+**/fastlane/test_output
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/build.gradle.kts b/src/MeloNX-Skip/melonx-skip/Android/app/build.gradle.kts
new file mode 100644
index 000000000..eca4ae318
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Android/app/build.gradle.kts
@@ -0,0 +1,71 @@
+import java.util.Properties
+
+plugins {
+ alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.kotlin.compose)
+ alias(libs.plugins.android.application)
+ id("skip-build-plugin")
+}
+
+skip {
+}
+
+android {
+ namespace = group as String
+ compileSdk = libs.versions.android.sdk.compile.get().toInt()
+ compileOptions {
+ sourceCompatibility = JavaVersion.toVersion(libs.versions.jvm.get())
+ targetCompatibility = JavaVersion.toVersion(libs.versions.jvm.get())
+ }
+ kotlinOptions {
+ jvmTarget = libs.versions.jvm.get().toString()
+ }
+ packaging {
+ jniLibs {
+ keepDebugSymbols.add("**/*.so")
+ pickFirsts.add("**/*.so")
+ }
+ }
+
+ defaultConfig {
+ minSdk = libs.versions.android.sdk.min.get().toInt()
+ targetSdk = libs.versions.android.sdk.compile.get().toInt()
+ // skip.tools.skip-build-plugin will automatically use Skip.env properties for:
+ // applicationId = PRODUCT_BUNDLE_IDENTIFIER
+ // versionCode = CURRENT_PROJECT_VERSION
+ // versionName = MARKETING_VERSION
+ }
+
+ buildFeatures {
+ buildConfig = true
+ }
+
+ lintOptions {
+ disable.add("Instantiatable")
+ }
+
+ // default signing configuration tries to load from keystore.properties
+ signingConfigs {
+ val keystorePropertiesFile = file("keystore.properties")
+ if (keystorePropertiesFile.isFile) {
+ create("release") {
+ val keystoreProperties = Properties()
+ keystoreProperties.load(keystorePropertiesFile.inputStream())
+ keyAlias = keystoreProperties.getProperty("keyAlias")
+ keyPassword = keystoreProperties.getProperty("keyPassword")
+ storeFile = file(keystoreProperties.getProperty("storeFile"))
+ storePassword = keystoreProperties.getProperty("storePassword")
+ }
+ }
+ }
+
+ buildTypes {
+ release {
+ signingConfig = signingConfigs.findByName("release")
+ isMinifyEnabled = true
+ isShrinkResources = true
+ isDebuggable = false // can be set to true for debugging release build, but needs to be false when uploading to store
+ proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
+ }
+ }
+}
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/proguard-rules.pro b/src/MeloNX-Skip/melonx-skip/Android/app/proguard-rules.pro
new file mode 100644
index 000000000..ee52833fd
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Android/app/proguard-rules.pro
@@ -0,0 +1,6 @@
+-keeppackagenames **
+-keep class skip.** { *; }
+-keep class kotlin.jvm.functions.** {*;}
+-keep class com.sun.jna.** { *; }
+-keep class * implements com.sun.jna.** { *; }
+-keep class melo.nx.** { *; }
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/AndroidManifest.xml b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..a9838b590
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/java/melo/nx/scoped/FolderProvider.java b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/java/melo/nx/scoped/FolderProvider.java
new file mode 100644
index 000000000..1bdf8e28b
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/java/melo/nx/scoped/FolderProvider.java
@@ -0,0 +1,373 @@
+package melo.nx.scoped;
+
+import android.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.graphics.Point;
+import android.net.Uri;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
+import android.provider.DocumentsProvider;
+import android.util.Log;
+import android.webkit.MimeTypeMap;
+
+import androidx.annotation.Nullable;
+
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import melo.nx.BuildConfig;
+import melo.nx.R;
+//import melo.nx.R;
+
+/**
+ * A document provider for the Storage Access Framework which exposes the files in the
+ * $HOME/ directory to other apps.
+ *
+ * Note that this replaces providing an activity matching the ACTION_GET_CONTENT intent:
+ *
+ * "A document provider and ACTION_GET_CONTENT should be considered mutually exclusive. If you
+ * support both of them simultaneously, your app will appear twice in the system picker UI,
+ * offering two different ways of accessing your stored data. This would be confusing for users."
+ * - ...
+ */
+public class FolderProvider extends DocumentsProvider {
+
+ private static final String ALL_MIME_TYPES = "*/*";
+
+ private File BASE_DIR;
+
+ private ContentResolver mContentResolver;
+
+ private String mStorageProviderAuthortiy;
+
+ // The default columns to return information about a root if no specific
+ // columns are requested in a query.
+ private static final String[] DEFAULT_ROOT_PROJECTION = new String[]{
+ Root.COLUMN_ROOT_ID,
+ Root.COLUMN_MIME_TYPES,
+ Root.COLUMN_FLAGS,
+ Root.COLUMN_ICON,
+ Root.COLUMN_TITLE,
+ Root.COLUMN_SUMMARY,
+ Root.COLUMN_DOCUMENT_ID,
+ Root.COLUMN_AVAILABLE_BYTES
+ };
+
+ // The default columns to return information about a document if no specific
+ // columns are requested in a query.
+ private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[]{
+ Document.COLUMN_DOCUMENT_ID,
+ Document.COLUMN_MIME_TYPE,
+ Document.COLUMN_DISPLAY_NAME,
+ Document.COLUMN_LAST_MODIFIED,
+ Document.COLUMN_FLAGS,
+ Document.COLUMN_SIZE
+ };
+
+ @Override
+ public Cursor queryRoots(String[] projection) {
+ final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
+ final String applicationName = "MeloNX";
+
+ String summary = BuildConfig.VERSION_NAME;
+ if (BuildConfig.DEBUG) {
+// summary = "(" + getContext().getString(R.string.generic_debug) + ") " + summary;
+ }
+
+ final MatrixCursor.RowBuilder row = result.newRow();
+ row.add(Root.COLUMN_ROOT_ID, getDocIdForFile(BASE_DIR));
+ row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(BASE_DIR));
+ row.add(Root.COLUMN_SUMMARY, summary);
+ row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD);
+ row.add(Root.COLUMN_TITLE, applicationName);
+ row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES);
+ row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace());
+// row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher);
+ return result;
+ }
+
+ @Override
+ public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
+ final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
+ // Future-proofing in case if we implement realtime file watching
+ result.setNotificationUri(mContentResolver, createUriForDocId(documentId));
+ includeFile(result, documentId, null);
+ return result;
+ }
+
+ @Override
+ public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException {
+ final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
+ final File parent = getFileForDocId(parentDocumentId);
+ final File[] children = parent.listFiles();
+ if(children == null) throw new FileNotFoundException("Unable to list files in "+parent.getAbsolutePath());
+ for (File file : children) {
+ includeFile(result, null, file);
+ }
+ // Set the notification URI as that's what the "Files" app will be listening to in case of file deletion
+ result.setNotificationUri(mContentResolver, createUriForDocId(parentDocumentId));
+ return result;
+ }
+
+ @Override
+ public ParcelFileDescriptor openDocument(final String documentId, String mode, CancellationSignal signal) throws FileNotFoundException {
+ final File file = getFileForDocId(documentId);
+ final int accessMode = ParcelFileDescriptor.parseMode(mode);
+ return ParcelFileDescriptor.open(file, accessMode);
+ }
+
+ @Override
+ public AssetFileDescriptor openDocumentThumbnail(String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
+ final File file = getFileForDocId(documentId);
+ final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+ return new AssetFileDescriptor(pfd, 0, file.length());
+ }
+
+ @Override
+ public boolean onCreate() {
+// if(Tools.checkStorageRoot(getContext())) {
+// Tools.initStorageConstants(getContext());
+// }else {
+// return false;
+// }
+ BASE_DIR = getContext().getExternalFilesDir(null);
+ mContentResolver = getContext().getContentResolver();
+ mStorageProviderAuthortiy = "com.xitrix.melonx.scoped.gamefolder";
+ return true;
+ }
+
+ @Override
+ public String createDocument(String parentDocumentId, String mimeType, String displayName) throws FileNotFoundException {
+ File newFile = new File(parentDocumentId, displayName);
+ int noConflictId = 2;
+ while (newFile.exists()) {
+ newFile = new File(parentDocumentId, displayName + " (" + noConflictId++ + ")");
+ }
+ try {
+ boolean succeeded;
+ if (Document.MIME_TYPE_DIR.equals(mimeType)) {
+ succeeded = newFile.mkdir();
+ } else {
+ succeeded = newFile.createNewFile();
+ }
+ if (!succeeded) {
+ throw new FileNotFoundException("Failed to create document with id " + newFile.getPath());
+ }
+ } catch (IOException e) {
+ throw new FileNotFoundException("Failed to create document with id " + newFile.getPath());
+ }
+ // Notify the file manager that the parent directory has changed
+ notifyChange(createUriForDocId(parentDocumentId));
+ return newFile.getPath();
+ }
+
+ @Override
+ public String renameDocument(String documentId, String displayName) throws FileNotFoundException {
+ File sourceFile = getFileForDocId(documentId);
+ File sourceParent = sourceFile.getParentFile();
+ if(sourceParent == null) throw new FileNotFoundException("Cannot rename root");
+ File targetFile = new File(getDocIdForFile(sourceParent) + "/" + displayName);
+ if(!sourceFile.renameTo(targetFile)){
+ throw new FileNotFoundException("Couldn't rename the document with id" + documentId);
+ }
+ return getDocIdForFile(targetFile);
+ }
+
+ @Override
+ public String moveDocument(String sourceDocumentId, String sourceParentDocumentId, String targetParentDocumentId) throws FileNotFoundException {
+ File sourceFile = getFileForDocId(sourceParentDocumentId + sourceDocumentId);
+ File targetFile = new File(targetParentDocumentId + sourceDocumentId);
+ if(!sourceFile.renameTo(targetFile)){
+ throw new FileNotFoundException("Failed to move the document with id " + sourceFile.getPath());
+ }
+ return getDocIdForFile(targetFile);
+ }
+
+ @Override
+ public void removeDocument(String documentId, String parentDocumentId) throws FileNotFoundException {
+ deleteDocument(parentDocumentId + "/" + documentId);
+ }
+
+ @Override
+ public void deleteDocument(String documentId) throws FileNotFoundException {
+ File file = getFileForDocId(documentId);
+// if(file.isDirectory()){
+// try {
+// FileUtils.deleteDirectory(file);
+// } catch (IOException e) {
+// throw new FileNotFoundException("Failed to delete document with id " + documentId);
+// }
+// }else{
+ if (!file.delete()) {
+ throw new FileNotFoundException("Failed to delete document with id " + documentId);
+ }
+// }
+ // Notify the file manager that the parent directory has changed
+ notifyChange(createUriForFile(file.getParentFile()));
+ }
+
+ @Override
+ public String getDocumentType(String documentId) throws FileNotFoundException {
+ Log.i("FolderPRovider", "getDocumentType("+documentId+")");
+ File file = getFileForDocId(documentId);
+ return getMimeType(file);
+ }
+
+ @Override
+ public Cursor querySearchDocuments(String rootId, String query, String[] projection) throws FileNotFoundException {
+ final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
+ final File parent = getFileForDocId(rootId);
+
+ // This example implementation searches file names for the query and doesn't rank search
+ // results, so we can stop as soon as we find a sufficient number of matches. Other
+ // implementations might rank results and use other data about files, rather than the file
+ // name, to produce a match.
+ final LinkedList pending = new LinkedList<>();
+ pending.add(parent);
+
+ final int MAX_SEARCH_RESULTS = 50;
+ while (!pending.isEmpty() && result.getCount() < MAX_SEARCH_RESULTS) {
+ final File file = pending.removeFirst();
+ // Avoid directories outside the $HOME directory linked with symlinks (to avoid e.g. search
+ // through the whole SD card).
+ boolean isInsideHome;
+// try {
+// isInsideHome = file.getCanonicalPath().startsWith(Tools.DIR_GAME_HOME);
+// } catch (IOException e) {
+// isInsideHome = true;
+// }
+// if (isInsideHome) {
+// if (file.isDirectory()) {
+// File[] listing = file.listFiles();
+// if(listing != null) Collections.addAll(pending, listing);
+// } else {
+// if (file.getName().toLowerCase().contains(query)) {
+// includeFile(result, null, file);
+// }
+// }
+// }
+ }
+
+ return result;
+ }
+
+ @Override
+ public boolean isChildDocument(String parentDocumentId, String documentId) {
+ return documentId.startsWith(parentDocumentId);
+ }
+
+ /**
+ * Get the document id given a file. This document id must be consistent across time as other
+ * applications may save the ID and use it to reference documents later.
+ *
+ * The reverse of @{link #getFileForDocId}.
+ */
+ private static String getDocIdForFile(File file) {
+ return file.getAbsolutePath();
+ }
+
+ /**
+ * Get the file given a document id (the reverse of {@link #getDocIdForFile(File)}).
+ */
+ private static File getFileForDocId(String docId) throws FileNotFoundException {
+ final File f = new File(docId);
+ if (!f.exists()) throw new FileNotFoundException(f.getAbsolutePath() + " not found");
+ return f;
+ }
+
+ private static String getMimeType(File file) {
+ if (file.isDirectory()) {
+ return Document.MIME_TYPE_DIR;
+ } else {
+ final String name = file.getName();
+ final int lastDot = name.lastIndexOf('.');
+ if (lastDot >= 0) {
+ final String extension = name.substring(lastDot + 1).toLowerCase();
+ final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+ if (mime != null) return mime;
+ }
+ return "application/octet-stream";
+ }
+ }
+
+ /**
+ * Add a representation of a file to a cursor.
+ *
+ * @param result the cursor to modify
+ * @param docId the document ID representing the desired file (may be null if given file)
+ * @param file the File object representing the desired file (may be null if given docID)
+ */
+ private void includeFile(MatrixCursor result, String docId, File file)
+ throws FileNotFoundException {
+ if (docId == null) {
+ docId = getDocIdForFile(file);
+ } else {
+ file = getFileForDocId(docId);
+ }
+
+ int flags = 0;
+ if (file.isDirectory()) {
+ if (file.canWrite()) flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
+ } else if (file.canWrite()) {
+ flags |= Document.FLAG_SUPPORTS_WRITE;
+ }
+ File parent = file.getParentFile();
+ if(parent != null) { // Only fails in one case: when the parent is /, which you can't delete.
+ if(parent.canWrite()) flags |= Document.FLAG_SUPPORTS_DELETE;
+ }
+
+ final String displayName = file.getName();
+ final String mimeType = getMimeType(file);
+ if (mimeType.startsWith("image/")) flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
+
+ final MatrixCursor.RowBuilder row = result.newRow();
+ row.add(Document.COLUMN_DOCUMENT_ID, docId);
+ row.add(Document.COLUMN_DISPLAY_NAME, displayName);
+ row.add(Document.COLUMN_SIZE, file.length());
+ row.add(Document.COLUMN_MIME_TYPE, mimeType);
+ row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
+ row.add(Document.COLUMN_FLAGS, flags);
+ row.add(Document.COLUMN_ICON, R.mipmap.ic_launcher);
+ }
+
+ @Override
+ @TargetApi(26)
+ public DocumentsContract.Path findDocumentPath(@Nullable String parentDocumentId, String childDocumentId) throws FileNotFoundException {
+ File source = BASE_DIR;
+ if(parentDocumentId != null) source = getFileForDocId(parentDocumentId);
+ File destination = getFileForDocId(childDocumentId);
+ List pathIds = new ArrayList<>();
+ while(!source.equals(destination) && destination != null) {
+ pathIds.add(getDocIdForFile(destination));
+ destination = destination.getParentFile();
+ }
+ pathIds.add(getDocIdForFile(source));
+ Collections.reverse(pathIds);
+ Log.i("FolderProvider", pathIds.toString());
+ return new DocumentsContract.Path(getDocIdForFile(source), pathIds);
+ }
+
+ private Uri createUriForDocId(String documentId) throws FileNotFoundException {
+ return createUriForFile(getFileForDocId(documentId));
+ }
+
+ private Uri createUriForFile(File file) {
+ return DocumentsContract.buildDocumentUri(mStorageProviderAuthortiy, file.getAbsolutePath());
+ }
+
+ private void notifyChange(Uri uri) {
+ mContentResolver.notifyChange(uri, null);
+ }
+}
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/jniLibs/arm64-v8a/libRyujinx.Headless.SDL2.so b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/jniLibs/arm64-v8a/libRyujinx.Headless.SDL2.so
new file mode 100644
index 000000000..247b6e341
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/jniLibs/arm64-v8a/libRyujinx.Headless.SDL2.so differ
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/jniLibs/arm64-v8a/libcrypto.so b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/jniLibs/arm64-v8a/libcrypto.so
new file mode 100755
index 000000000..d91a5a937
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/jniLibs/arm64-v8a/libcrypto.so differ
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/jniLibs/arm64-v8a/libsdl2.so b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/jniLibs/arm64-v8a/libsdl2.so
new file mode 100755
index 000000000..2c7d59ffb
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/jniLibs/arm64-v8a/libsdl2.so differ
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/jniLibs/arm64-v8a/libssl.so b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/jniLibs/arm64-v8a/libssl.so
new file mode 100755
index 000000000..d7534576d
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/jniLibs/arm64-v8a/libssl.so differ
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/kotlin/Main.kt b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/kotlin/Main.kt
new file mode 100644
index 000000000..fed908124
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/kotlin/Main.kt
@@ -0,0 +1,132 @@
+package melo.nx
+
+import skip.lib.*
+import skip.model.*
+import skip.foundation.*
+import skip.ui.*
+
+import android.Manifest
+import android.app.Application
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.compose.setContent
+import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.saveable.rememberSaveableStateHolder
+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")
+
+/// AndroidAppMain is the `android.app.Application` entry point, and must match `application android:name` in the AndroidMainfest.xml file.
+open class AndroidAppMain: Application {
+ constructor() {
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+ logger.info("starting app")
+ ProcessInfo.launch(applicationContext)
+ }
+
+ companion object {
+ }
+}
+
+/// AndroidAppMain is initial `androidx.appcompat.app.AppCompatActivity`, and must match `activity android:name` in the AndroidMainfest.xml file.
+open class MainActivity: AppCompatActivity {
+ constructor() {
+ }
+
+ override fun onCreate(savedInstanceState: android.os.Bundle?) {
+ super.onCreate(savedInstanceState)
+ logger.info("starting activity")
+ UIApplication.launch(this)
+ enableEdgeToEdge()
+
+ setContent {
+ val saveableStateHolder = rememberSaveableStateHolder()
+ saveableStateHolder.SaveableStateProvider(true) {
+ PresentationRootView(ComposeContext())
+ SideEffect { saveableStateHolder.removeState(true) }
+ }
+ }
+
+ // Example of requesting permissions on startup.
+ // These must match the permissions in the AndroidManifest.xml file.
+ //let permissions = listOf(
+ // Manifest.permission.ACCESS_COARSE_LOCATION,
+ // Manifest.permission.ACCESS_FINE_LOCATION
+ // Manifest.permission.CAMERA,
+ // Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ //)
+ //let requestTag = 1
+ //ActivityCompat.requestPermissions(self, permissions.toTypedArray(), requestTag)
+ }
+
+ override fun onStart() {
+ super.onStart()
+ MeloNXAppDelegate.shared.onStart(this)
+ }
+
+ override fun onResume() {
+ super.onResume()
+ MeloNXAppDelegate.shared.onResume(this)
+ }
+
+ override fun onPause() {
+ super.onPause()
+ MeloNXAppDelegate.shared.onPause(this)
+ }
+
+ override fun onStop() {
+ super.onStop()
+ MeloNXAppDelegate.shared.onStop(this)
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ MeloNXAppDelegate.shared.onDestroy(this)
+ }
+
+ override fun onLowMemory() {
+ super.onLowMemory()
+ MeloNXAppDelegate.shared.onLowMemory(this)
+ }
+
+ override fun onRestart() {
+ logger.info("onRestart")
+ super.onRestart()
+ }
+
+ override fun onSaveInstanceState(bundle: android.os.Bundle): Unit = super.onSaveInstanceState(bundle)
+
+ override fun onRestoreInstanceState(bundle: android.os.Bundle) {
+ // Usually you restore your state in onCreate(). It is possible to restore it in onRestoreInstanceState() as well, but not very common. (onRestoreInstanceState() is called after onStart(), whereas onCreate() is called before onStart().
+ logger.info("onRestoreInstanceState")
+ super.onRestoreInstanceState(bundle)
+ }
+
+ override fun onRequestPermissionsResult(requestCode: Int, permissions: kotlin.Array, grantResults: IntArray) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ logger.info("onRequestPermissionsResult: ${requestCode}")
+ }
+
+ companion object {
+ }
+}
+
+@Composable
+internal fun PresentationRootView(context: ComposeContext) {
+ val colorScheme = if (isSystemInDarkTheme()) ColorScheme.dark else ColorScheme.light
+ PresentationRoot(defaultColorScheme = colorScheme, context = context) { ctx ->
+ val contentContext = ctx.content()
+ Box(modifier = ctx.modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ MeloNXRootView().Compose(context = contentContext)
+ }
+ }
+}
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-anydpi/ic_launcher.xml b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-anydpi/ic_launcher.xml
new file mode 100644
index 000000000..c1ce612b5
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-anydpi/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..739c072ba
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png
new file mode 100644
index 000000000..81c161892
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 000000000..113e3ea46
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png
new file mode 100644
index 000000000..113e3ea46
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..139ad2065
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png
new file mode 100644
index 000000000..cb5c6376e
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 000000000..99db60864
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png
new file mode 100644
index 000000000..99db60864
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..08d7f6b3b
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png
new file mode 100644
index 000000000..a93a64cb2
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 000000000..8371cb8f4
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png
new file mode 100644
index 000000000..8371cb8f4
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..7fb9812cf
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png
new file mode 100644
index 000000000..37d1d7277
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 000000000..772fe9ce6
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png
new file mode 100644
index 000000000..772fe9ce6
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..0ee407c43
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png
new file mode 100644
index 000000000..31286ead5
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 000000000..7416fc95e
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png
new file mode 100644
index 000000000..7416fc95e
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Android/fastlane/Appfile b/src/MeloNX-Skip/melonx-skip/Android/fastlane/Appfile
new file mode 100644
index 000000000..bbcb0fb51
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Android/fastlane/Appfile
@@ -0,0 +1,11 @@
+# This file contains the app distribution configuration
+# for the Android half of the Skip app.
+# You can find the documentation at https://docs.fastlane.tools
+
+# Load the shared Skip.env properties with the app info
+require('dotenv')
+Dotenv.load '../../Skip.env'
+package_name(ENV['PRODUCT_BUNDLE_IDENTIFIER'])
+
+# Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
+json_key_file("fastlane/apikey.json")
diff --git a/src/MeloNX-Skip/melonx-skip/Android/fastlane/Fastfile b/src/MeloNX-Skip/melonx-skip/Android/fastlane/Fastfile
new file mode 100644
index 000000000..c71d4d5e0
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Android/fastlane/Fastfile
@@ -0,0 +1,51 @@
+# This file contains the fastlane.tools configuration
+# for the Android half of the Skip app.
+# You can find the documentation at https://docs.fastlane.tools
+
+# Load the shared Skip.env properties with the app info
+require('dotenv')
+Dotenv.load '../../Skip.env'
+
+default_platform(:android)
+
+# use the Homebrew gradle rather than expecting a local gradlew
+gradle_bin = (ENV['HOMEBREW_PREFIX'] ? ENV['HOMEBREW_PREFIX'] : "/opt/homebrew") + "/bin/gradle"
+
+default_platform(:android)
+
+desc "Build Skip Android App"
+lane :build do |options|
+ build_config = (options[:release] ? "Release" : "Debug")
+ gradle(
+ task: "build${build_config}",
+ gradle_path: gradle_bin,
+ flags: "--warning-mode none -x lint"
+ )
+end
+
+desc "Test Skip Android App"
+lane :test do
+ gradle(
+ task: "test",
+ gradle_path: gradle_bin
+ )
+end
+
+desc "Assemble Skip Android App"
+lane :assemble do
+ gradle(
+ gradle_path: gradle_bin,
+ task: "bundleRelease"
+ )
+ # sh "your_script.sh"
+end
+
+desc "Deploy Skip Android App to Google Play"
+lane :release do
+
+ assemble
+
+ upload_to_play_store(
+ aab: '../.build/Android/app/outputs/bundle/release/app-release.aab'
+ )
+end
diff --git a/src/MeloNX-Skip/melonx-skip/Android/fastlane/metadata/android/en-US/full_description.txt b/src/MeloNX-Skip/melonx-skip/Android/fastlane/metadata/android/en-US/full_description.txt
new file mode 100644
index 000000000..c65ef9c52
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Android/fastlane/metadata/android/en-US/full_description.txt
@@ -0,0 +1 @@
+A great new app built with Skip!
diff --git a/src/MeloNX-Skip/melonx-skip/Android/fastlane/metadata/android/en-US/short_description.txt b/src/MeloNX-Skip/melonx-skip/Android/fastlane/metadata/android/en-US/short_description.txt
new file mode 100644
index 000000000..c65ef9c52
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Android/fastlane/metadata/android/en-US/short_description.txt
@@ -0,0 +1 @@
+A great new app built with Skip!
diff --git a/src/MeloNX-Skip/melonx-skip/Android/fastlane/metadata/android/en-US/title.txt b/src/MeloNX-Skip/melonx-skip/Android/fastlane/metadata/android/en-US/title.txt
new file mode 100644
index 000000000..7427ca2d3
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Android/fastlane/metadata/android/en-US/title.txt
@@ -0,0 +1 @@
+MeloNX
diff --git a/src/MeloNX-Skip/melonx-skip/Android/gradle.properties b/src/MeloNX-Skip/melonx-skip/Android/gradle.properties
new file mode 100644
index 000000000..1b8d0606a
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx4g
+android.useAndroidX=true
+kotlin.code.style=official
diff --git a/src/MeloNX-Skip/melonx-skip/Android/gradle/wrapper/gradle-wrapper.properties b/src/MeloNX-Skip/melonx-skip/Android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..de9161edb
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1 @@
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
diff --git a/src/MeloNX-Skip/melonx-skip/Android/settings.gradle.kts b/src/MeloNX-Skip/melonx-skip/Android/settings.gradle.kts
new file mode 100644
index 000000000..f51687925
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Android/settings.gradle.kts
@@ -0,0 +1,46 @@
+// This gradle project is part of a conventional Skip app project.
+// It invokes the shared build skip plugin logic, which included as part of the skip-unit buildSrc
+// When built from Android Studio, it uses the BUILT_PRODUCTS_DIR folder to share the same build outputs as Xcode, otherwise it uses SwiftPM's .build/ folder
+pluginManagement {
+ // local override of BUILT_PRODUCTS_DIR
+ if (System.getenv("BUILT_PRODUCTS_DIR") == null) {
+ //System.setProperty("BUILT_PRODUCTS_DIR", "${System.getProperty("user.home")}/Library/Developer/Xcode/DerivedData/MySkipProject-aqywrhrzhkbvfseiqgxuufbdwdft/Build/Products/Debug-iphonesimulator")
+ }
+
+ // the source for the plugin is linked as part of the SkipUnit transpilation
+ val skipOutput = System.getenv("BUILT_PRODUCTS_DIR") ?: System.getProperty("BUILT_PRODUCTS_DIR")
+
+ val outputExt = if (skipOutput != null) ".output" else "" // Xcode saves output in package-name.output; SPM has no suffix
+ val skipOutputs: File = if (skipOutput != null) {
+ // BUILT_PRODUCTS_DIR is set when building from Xcode, in which case we will use Xcode's DerivedData plugin output
+ file(skipOutput).resolve("../../../SourcePackages/plugins/")
+ } else {
+ exec {
+ // create transpiled Kotlin and generate Gradle projects from SwiftPM modules
+ commandLine("sh", "-c", "xcrun swift build --triple arm64-apple-ios --sdk $(xcrun --sdk iphoneos --show-sdk-path)")
+ workingDir = file("..")
+ }
+ // SPM output folder is a peer of the parent Package.swift
+ rootDir.resolve("../.build/plugins/outputs/")
+ }
+
+ // load the Skip plugin (part of the skip-unit project), which handles configuring the Android project
+ // because this path is a symlink, we need to use the canonical path or gradle will mis-interpret it as a different build source
+ var pluginSource = skipOutputs.resolve("skip-unit${outputExt}/SkipUnit/skipstone/buildSrc/").canonicalFile
+ if (!pluginSource.isDirectory) {
+ // check new SwiftPM6 plugin "destination" folder for command-line builds
+ pluginSource = skipOutputs.resolve("skip-unit${outputExt}/SkipUnit/destination/skipstone/buildSrc/").canonicalFile
+ }
+
+ if (!pluginSource.isDirectory) {
+ throw GradleException("Missing expected Skip output folder: ${pluginSource}. Run `swift build` in the root folder to create, or specify Xcode environment BUILT_PRODUCTS_DIR.")
+ }
+ includeBuild(pluginSource.path) {
+ name = "skip-plugins"
+ }
+}
+
+plugins {
+ id("skip-plugin") apply true
+}
+
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AccentColor.colorset/Contents.json b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 000000000..eb8789700
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x.png b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x.png
new file mode 100644
index 000000000..d9cb095dc
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x~ipad.png b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x~ipad.png
new file mode 100644
index 000000000..d9cb095dc
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x~ipad.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-20@3x.png b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-20@3x.png
new file mode 100644
index 000000000..7c88f0586
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-20@3x.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-20~ipad.png b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-20~ipad.png
new file mode 100644
index 000000000..c72f356a8
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-20~ipad.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29.png b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29.png
new file mode 100644
index 000000000..4695642a7
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x.png b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x.png
new file mode 100644
index 000000000..f0ac06f36
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x~ipad.png b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x~ipad.png
new file mode 100644
index 000000000..f0ac06f36
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x~ipad.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29@3x.png b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29@3x.png
new file mode 100644
index 000000000..3f1c9c30f
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29@3x.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29~ipad.png b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29~ipad.png
new file mode 100644
index 000000000..4695642a7
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29~ipad.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x.png b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x.png
new file mode 100644
index 000000000..952385db6
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x~ipad.png b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x~ipad.png
new file mode 100644
index 000000000..952385db6
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x~ipad.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-40@3x.png b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-40@3x.png
new file mode 100644
index 000000000..829fd1260
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-40@3x.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-40~ipad.png b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-40~ipad.png
new file mode 100644
index 000000000..d9cb095dc
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-40~ipad.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5@2x~ipad.png b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5@2x~ipad.png
new file mode 100644
index 000000000..989656e8e
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5@2x~ipad.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png
new file mode 100644
index 000000000..829fd1260
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon@2x~ipad.png b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon@2x~ipad.png
new file mode 100644
index 000000000..99e6c8814
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon@2x~ipad.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon@3x.png b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon@3x.png
new file mode 100644
index 000000000..c4649c3a9
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon@3x.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png
new file mode 100644
index 000000000..c314ac82f
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon~ipad.png b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon~ipad.png
new file mode 100644
index 000000000..44f76a4b1
Binary files /dev/null and b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon~ipad.png differ
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/Contents.json b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000..611a2bb93
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,122 @@
+{
+ "images" : [
+ {
+ "filename" : "AppIcon-20@2x.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "AppIcon-20@3x.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "AppIcon-29.png",
+ "idiom" : "iphone",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "AppIcon-29@2x.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "AppIcon-29@3x.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "AppIcon-40@2x.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "AppIcon-40@3x.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "AppIcon@2x.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "filename" : "AppIcon@3x.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "filename" : "AppIcon-20~ipad.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "AppIcon-20@2x~ipad.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "AppIcon-29~ipad.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "AppIcon-29@2x~ipad.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "AppIcon-40~ipad.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "AppIcon-40@2x~ipad.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "AppIcon~ipad.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "filename" : "AppIcon@2x~ipad.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "filename" : "AppIcon-83.5@2x~ipad.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "filename" : "AppIcon~ios-marketing.png",
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/Contents.json b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/Contents.json
new file mode 100644
index 000000000..73c00596a
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Darwin/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/Entitlements.plist b/src/MeloNX-Skip/melonx-skip/Darwin/Entitlements.plist
new file mode 100644
index 000000000..6631ffa6f
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Darwin/Entitlements.plist
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/Info.plist b/src/MeloNX-Skip/melonx-skip/Darwin/Info.plist
new file mode 100644
index 000000000..6d5f8f6f0
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Darwin/Info.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ ITSAppUsesNonExemptEncryption
+
+
+
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/MeloNX.xcconfig b/src/MeloNX-Skip/melonx-skip/Darwin/MeloNX.xcconfig
new file mode 100644
index 000000000..20ef51da8
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Darwin/MeloNX.xcconfig
@@ -0,0 +1,56 @@
+#include "../Skip.env"
+
+// Set the action that will be executed as part of the Xcode Run Script phase
+// Setting to "launch" will build and run the app in the first open Android emulator or device
+// Setting to "build" will just run gradle build, but will not launch the app
+SKIP_ACTION = launch
+//SKIP_ACTION = build
+
+ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon
+ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor
+
+INFOPLIST_FILE = Info.plist
+GENERATE_INFOPLIST_FILE = YES
+
+// The user-visible name of the app (localizable)
+//INFOPLIST_KEY_CFBundleDisplayName = App Name
+//INFOPLIST_KEY_LSApplicationCategoryType = public.app-category.utilities
+//INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "This app uses your location to …"
+
+// iOS-specific Info.plist property keys
+INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphone*] = YES
+INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphone*] = YES
+INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphone*] = YES
+INFOPLIST_KEY_UIStatusBarStyle[sdk=iphone*] = UIStatusBarStyleDefault
+INFOPLIST_KEY_UISupportedInterfaceOrientations[sdk=iphone*] = UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown
+
+IPHONEOS_DEPLOYMENT_TARGET = 17.0
+MACOSX_DEPLOYMENT_TARGET = 14.0
+SUPPORTS_MACCATALYST = NO
+
+// iPhone + iPad
+TARGETED_DEVICE_FAMILY = 1,2
+
+// iPhone only
+// TARGETED_DEVICE_FAMILY = 1
+
+SWIFT_EMIT_LOC_STRINGS = YES
+
+// the name of the product module; this can be anything, but cannot conflict with any Swift module names
+PRODUCT_MODULE_NAME = $(PRODUCT_NAME:c99extidentifier)App
+
+// On-device testing may need to override the bundle ID
+// PRODUCT_BUNDLE_IDENTIFIER[config=Debug][sdk=iphoneos*] = cool.beans.BundleIdentifer
+
+SDKROOT = auto
+SUPPORTED_PLATFORMS = iphoneos iphonesimulator macosx
+SWIFT_EMIT_LOC_STRINGS = YES
+
+SWIFT_VERSION = 5
+
+// Development team ID for on-device testing
+CODE_SIGNING_REQUIRED = NO
+CODE_SIGN_STYLE = Automatic
+CODE_SIGN_ENTITLEMENTS = Entitlements.plist
+//CODE_SIGNING_IDENTITY = -
+//DEVELOPMENT_TEAM =
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/MeloNX.xcodeproj/project.pbxproj b/src/MeloNX-Skip/melonx-skip/Darwin/MeloNX.xcodeproj/project.pbxproj
new file mode 100644
index 000000000..ad1ccc781
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Darwin/MeloNX.xcodeproj/project.pbxproj
@@ -0,0 +1,289 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 56;
+ 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, ); }; };
+ 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 */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ 499CD44A2AC5B9C6001AE8D8 /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ 49231BAD2AC5BCEF00F98ADF /* MeloNXApp in Embed Frameworks */,
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ 493609562A6B7EAE00C401E2 /* MeloNX */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = MeloNX; path = ..; sourceTree = ""; };
+ 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 = ""; };
+ 496BDBED2B8A7E9C00C09264 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = ../Sources/MeloNX/Resources/Localizable.xcstrings; sourceTree = ""; };
+ 496EB72F2A6AE4DE00C1253A /* Skip.env */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Skip.env; path = ../Skip.env; sourceTree = ""; };
+ 496EB72F2A6AE4DE00C1253B /* MeloNX.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = MeloNX.xcconfig; sourceTree = ""; };
+ 496EB72F2A6AE4DE00C1253C /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; };
+ 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; };
+ 49F90C2F2A52156300F06D93 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 49F90C312A52156300F06D93 /* Entitlements.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Entitlements.plist; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 499CD43C2AC5B799001AE8D8 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 49231BAC2AC5BCEF00F98ADF /* MeloNXApp in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 496BDBEC2B89A47800C09264 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 496BDBEB2B89A47800C09264 /* MeloNX.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 49AB54462B066A7E007B79B2 /* SkipStone */ = {
+ isa = PBXGroup;
+ children = (
+ 499AB9082B0581F4005E8330 /* plugins */,
+ );
+ name = SkipStone;
+ sourceTree = "";
+ };
+ 49F90C1F2A52156200F06D93 = {
+ isa = PBXGroup;
+ children = (
+ 496EB72F2A6AE4DE00C1253C /* README.md */,
+ 496EB72F2A6AE4DE00C1253A /* Skip.env */,
+ 496EB72F2A6AE4DE00C1253B /* MeloNX.xcconfig */,
+ 496BDBED2B8A7E9C00C09264 /* Localizable.xcstrings */,
+ 493609562A6B7EAE00C401E2 /* MeloNX */,
+ 49F90C2A2A52156200F06D93 /* App */,
+ 49AB54462B066A7E007B79B2 /* SkipStone */,
+ 496BDBEC2B89A47800C09264 /* Products */,
+ );
+ sourceTree = "";
+ };
+ 49F90C2A2A52156200F06D93 /* App */ = {
+ isa = PBXGroup;
+ children = (
+ 49F90C2B2A52156200F06D93 /* Main.swift */,
+ 49F90C2F2A52156300F06D93 /* Assets.xcassets */,
+ 49F90C312A52156300F06D93 /* Entitlements.plist */,
+ 4900101C2BACEA710000DE33 /* Info.plist */,
+ );
+ name = App;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 499CD4382AC5B799001AE8D8 /* MeloNX */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 499CD4412AC5B799001AE8D8 /* Build configuration list for PBXNativeTarget "MeloNX" */;
+ buildPhases = (
+ 499CD43A2AC5B799001AE8D8 /* Sources */,
+ 499CD43C2AC5B799001AE8D8 /* Frameworks */,
+ 499CD43E2AC5B799001AE8D8 /* Resources */,
+ 499CD4452AC5B869001AE8D8 /* Run skip gradle */,
+ 499CD44A2AC5B9C6001AE8D8 /* Embed Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = MeloNX;
+ packageProductDependencies = (
+ 49231BAB2AC5BCEF00F98ADF /* MeloNXApp */,
+ );
+ productName = App;
+ productReference = 496BDBEB2B89A47800C09264 /* MeloNX.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 49F90C202A52156200F06D93 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = 1;
+ LastSwiftUpdateCheck = 1430;
+ LastUpgradeCheck = 1540;
+ };
+ buildConfigurationList = 49F90C232A52156200F06D93 /* Build configuration list for PBXProject "MeloNX" */;
+ compatibilityVersion = "Xcode 14.0";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ es,
+ ja,
+ "zh-Hans",
+ );
+ mainGroup = 49F90C1F2A52156200F06D93;
+ packageReferences = (
+ );
+ productRefGroup = 496BDBEC2B89A47800C09264 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 499CD4382AC5B799001AE8D8 /* MeloNX */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 499CD43E2AC5B799001AE8D8 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 499CD4402AC5B799001AE8D8 /* Assets.xcassets in Resources */,
+ 496BDBEE2B8A7E9C00C09264 /* Localizable.xcstrings in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 499CD4452AC5B869001AE8D8 /* Run skip gradle */ = {
+ isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ );
+ name = "Run skip gradle";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = "/bin/sh -e";
+ shellScript = "if [ \"${SKIP_ZERO}\" != \"\" ]; then\n echo \"note: skipping skip due to SKIP_ZERO\"\n exit 0\nelif [ \"${ENABLE_PREVIEWS}\" == \"YES\" ]; then\n echo \"note: skipping skip due to ENABLE_PREVIEWS\"\n exit 0\nelif [ \"${ACTION}\" == \"install\" ]; then\n echo \"note: skipping skip due to archive install\"\n exit 0\nelse\n SKIP_ACTION=\"${SKIP_ACTION:-launch}\"\nfi\nPATH=${BUILD_ROOT}/Debug:${BUILD_ROOT}/../../SourcePackages/artifacts/skip/skip/skip.artifactbundle/macos:${PATH}:${HOMEBREW_PREFIX:-/opt/homebrew}/bin\necho \"note: running gradle build with: $(which skip) gradle -p ${PWD}/../Android ${SKIP_ACTION:-launch}${CONFIGURATION:-Debug}\"\nskip gradle -p ../Android ${SKIP_ACTION:-launch}${CONFIGURATION:-Debug}\n";
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 499CD43A2AC5B799001AE8D8 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 499CD43B2AC5B799001AE8D8 /* Main.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ 499CD4422AC5B799001AE8D8 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 496EB72F2A6AE4DE00C1253B /* MeloNX.xcconfig */;
+ buildSettings = {
+ ENABLE_PREVIEWS = YES;
+ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
+ "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
+ };
+ name = Debug;
+ };
+ 499CD4432AC5B799001AE8D8 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 496EB72F2A6AE4DE00C1253B /* MeloNX.xcconfig */;
+ buildSettings = {
+ ENABLE_PREVIEWS = YES;
+ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
+ "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
+ };
+ name = Release;
+ };
+ 49F90C4B2A52156300F06D93 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ 49F90C4C2A52156300F06D93 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 499CD4412AC5B799001AE8D8 /* Build configuration list for PBXNativeTarget "MeloNX" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 499CD4422AC5B799001AE8D8 /* Debug */,
+ 499CD4432AC5B799001AE8D8 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 49F90C232A52156200F06D93 /* Build configuration list for PBXProject "MeloNX" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 49F90C4B2A52156300F06D93 /* Debug */,
+ 49F90C4C2A52156300F06D93 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ 49231BAB2AC5BCEF00F98ADF /* MeloNXApp */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = MeloNXApp;
+ };
+/* End XCSwiftPackageProductDependency section */
+ };
+ rootObject = 49F90C202A52156200F06D93 /* Project object */;
+}
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/Sources/Main.swift b/src/MeloNX-Skip/melonx-skip/Darwin/Sources/Main.swift
new file mode 100644
index 000000000..583e91cef
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Darwin/Sources/Main.swift
@@ -0,0 +1,65 @@
+import SwiftUI
+import MeloNX
+
+/// The entry point to the app simply loads the App implementation from SPM module.
+@main struct AppMain: App {
+ @AppDelegateAdaptor(AppMainDelegate.self) var appDelegate
+ @Environment(\.scenePhase) private var scenePhase
+
+ var body: some Scene {
+ WindowGroup {
+ MeloNXRootView()
+ }
+ .onChange(of: scenePhase) { oldPhase, newPhase in
+ switch newPhase {
+ case .active:
+ AppDelegate.shared.onResume(appDelegate.application)
+ case .inactive:
+ AppDelegate.shared.onPause(appDelegate.application)
+ case .background:
+ AppDelegate.shared.onStop(appDelegate.application)
+ @unknown default:
+ print("unknown app phase: \(newPhase)")
+ }
+ }
+ }
+}
+
+typealias AppDelegate = MeloNXAppDelegate
+#if canImport(UIKit)
+typealias AppDelegateAdaptor = UIApplicationDelegateAdaptor
+typealias AppMainDelegateBase = UIApplicationDelegate
+typealias AppType = UIApplication
+#elseif canImport(AppKit)
+typealias AppDelegateAdaptor = NSApplicationDelegateAdaptor
+typealias AppMainDelegateBase = NSApplicationDelegate
+typealias AppType = NSApplication
+#endif
+
+class AppMainDelegate: NSObject, AppMainDelegateBase {
+ let application = AppType.shared
+
+ #if canImport(UIKit)
+ func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
+ AppDelegate.shared.onStart(application)
+ return true
+ }
+
+ func applicationWillTerminate(_ application: UIApplication) {
+ AppDelegate.shared.onDestroy(application)
+ }
+
+ func applicationDidReceiveMemoryWarning(_ application: UIApplication) {
+ AppDelegate.shared.onLowMemory(application)
+ }
+ #elseif canImport(AppKit)
+ func applicationWillFinishLaunching(_ notification: Notification) {
+ AppDelegate.shared.onStart(application)
+ }
+
+ func applicationWillTerminate(_ application: Notification) {
+ AppDelegate.shared.onDestroy(application)
+ }
+ #endif
+
+}
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/AppStore.xcconfig b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/AppStore.xcconfig
new file mode 100644
index 000000000..2dd4ae8fe
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/AppStore.xcconfig
@@ -0,0 +1,5 @@
+// Additional properties included by the Fastfile build_app
+
+// This file can be used to override various properties from Skip.env
+//PRODUCT_BUNDLE_IDENTIFIER =
+//DEVELOPMENT_TEAM =
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/Appfile b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/Appfile
new file mode 100644
index 000000000..2d9792e67
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/Appfile
@@ -0,0 +1,8 @@
+# For more information about the Appfile, see:
+# https://docs.fastlane.tools/advanced/#appfile
+
+require('dotenv')
+Dotenv.load '../../Skip.env'
+#app_identifier(ENV['PRODUCT_BUNDLE_IDENTIFIER'])
+
+# apple_id("my@email")
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/Deliverfile b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/Deliverfile
new file mode 100644
index 000000000..5400564b5
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/Deliverfile
@@ -0,0 +1,27 @@
+
+copyright "#{Time.now.year}"
+
+force(true) # Skip HTML report verification
+automatic_release(true)
+skip_screenshots(false)
+precheck_include_in_app_purchases(false)
+
+#skip_binary_upload(true)
+submit_for_review(true)
+
+submission_information({
+ add_id_info_serves_ads: false,
+ add_id_info_uses_idfa: false,
+ add_id_info_tracks_install: false,
+ add_id_info_tracks_action: false,
+ add_id_info_limits_tracking: false,
+ content_rights_has_rights: false,
+ content_rights_contains_third_party_content: false,
+ export_compliance_contains_third_party_cryptography: false,
+ export_compliance_encryption_updated: false,
+ export_compliance_platform: 'ios',
+ export_compliance_compliance_required: false,
+ export_compliance_uses_encryption: false,
+ export_compliance_is_exempt: false,
+ export_compliance_contains_proprietary_cryptography: false
+})
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/Fastfile b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/Fastfile
new file mode 100644
index 000000000..6772ad860
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/Fastfile
@@ -0,0 +1,36 @@
+# This file contains the fastlane.tools configuration
+# for the iOS half of the Skip app.
+# You can find the documentation at https://docs.fastlane.tools
+
+default_platform(:ios)
+
+lane :assemble do |options|
+ # only build the iOS side of the app
+ ENV["SKIP_ZERO"] = "true"
+ build_app(
+ sdk: "iphoneos",
+ xcconfig: "fastlane/AppStore.xcconfig",
+ xcargs: "-skipPackagePluginValidation -skipMacroValidation",
+ derived_data_path: "../.build/Darwin/DerivedData",
+ output_directory: "../.build/fastlane/Darwin",
+ skip_archive: ENV["FASTLANE_SKIP_ARCHIVE"] == "YES",
+ skip_codesigning: ENV["FASTLANE_SKIP_CODESIGNING"] == "YES"
+ )
+end
+
+lane :release do |options|
+ desc "Build and release app"
+
+ # if you have an apikey.json file (https://developer.apple.com/documentation/appstoreconnectapi/creating-api-keys-for-app-store-connect-api), fastlane can automatically fetch certificates and the ASC authentication information
+ #get_certificates(api_key_path: "fastlane/apikey.json")
+ #get_provisioning_profile(api_key_path: "fastlane/apikey.json")
+
+ assemble
+
+ upload_to_app_store(
+ api_key_path: "fastlane/apikey.json",
+ app_rating_config_path: "fastlane/metadata/rating.json",
+ release_notes: { default: "Fixes and improvements." }
+ )
+end
+
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/description.txt b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/description.txt
new file mode 100644
index 000000000..c65ef9c52
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/description.txt
@@ -0,0 +1 @@
+A great new app built with Skip!
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/keywords.txt b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/keywords.txt
new file mode 100644
index 000000000..0fb6d6cc4
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/keywords.txt
@@ -0,0 +1 @@
+app,key,words
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/privacy_url.txt b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/privacy_url.txt
new file mode 100644
index 000000000..575f00180
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/privacy_url.txt
@@ -0,0 +1 @@
+https://example.org/privacy/
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/release_notes.txt b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/release_notes.txt
new file mode 100644
index 000000000..2e531b654
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/release_notes.txt
@@ -0,0 +1 @@
+Bug fixes and performance improvements.
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/software_url.txt b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/software_url.txt
new file mode 100644
index 000000000..0ce62b71e
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/software_url.txt
@@ -0,0 +1 @@
+https://example.org/app/
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/subtitle.txt b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/subtitle.txt
new file mode 100644
index 000000000..2289704a7
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/subtitle.txt
@@ -0,0 +1 @@
+A new Skip app
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/support_url.txt b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/support_url.txt
new file mode 100644
index 000000000..fcfa1f770
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/support_url.txt
@@ -0,0 +1 @@
+https://example.org/support/
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/title.txt b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/title.txt
new file mode 100644
index 000000000..7427ca2d3
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/title.txt
@@ -0,0 +1 @@
+MeloNX
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/version_whats_new.txt b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/version_whats_new.txt
new file mode 100644
index 000000000..5d21c2423
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/en-US/version_whats_new.txt
@@ -0,0 +1 @@
+New features and better performance.
diff --git a/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/rating.json b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/rating.json
new file mode 100644
index 000000000..b00337917
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Darwin/fastlane/metadata/rating.json
@@ -0,0 +1,17 @@
+{
+ "alcoholTobaccoOrDrugUseOrReferences": "NONE",
+ "contests": "NONE",
+ "gamblingSimulated": "NONE",
+ "horrorOrFearThemes": "NONE",
+ "matureOrSuggestiveThemes": "NONE",
+ "medicalOrTreatmentInformation": "NONE",
+ "profanityOrCrudeHumor": "NONE",
+ "sexualContentGraphicAndNudity": "NONE",
+ "sexualContentOrNudity": "NONE",
+ "violenceCartoonOrFantasy": "NONE",
+ "violenceRealisticProlongedGraphicOrSadistic": "NONE",
+ "violenceRealistic": "NONE",
+ "gambling": false,
+ "seventeenPlus": false,
+ "unrestrictedWebAccess": false
+}
diff --git a/src/MeloNX-Skip/melonx-skip/Package.swift b/src/MeloNX-Skip/melonx-skip/Package.swift
new file mode 100644
index 000000000..4b36751b3
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Package.swift
@@ -0,0 +1,40 @@
+// 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-skip",
+ defaultLocalization: "en",
+ platforms: [.iOS(.v17), .macOS(.v14), .tvOS(.v17), .watchOS(.v10), .macCatalyst(.v17)],
+ products: [
+ .library(name: "MeloNXApp", type: .dynamic, targets: ["MeloNX"]),
+ ],
+ dependencies: [
+ .package(url: "https://source.skip.tools/skip.git", from: "1.3.0"),
+ .package(url: "https://source.skip.tools/skip-unit.git", from: "1.0.0"),
+ .package(url: "https://source.skip.tools/skip-ui.git", from: "1.0.0"),
+ .package(url: "https://source.skip.tools/skip-foundation.git", from: "1.0.0"),
+ .package(url: "https://source.skip.tools/skip-ffi.git", from: "1.0.0"),
+ .package(url: "https://source.skip.tools/skip-kit.git", from: "0.3.1")
+ ],
+ targets: [
+ .target(name: "MeloNX", dependencies: [
+ "LibCLibrary",
+ .product(name: "SkipUI", package: "skip-ui"),
+ .product(name: "SkipFoundation", package: "skip-foundation"),
+ .product(name: "SkipFFI", package: "skip-ffi"),
+ .product(name: "SkipKit", package: "skip-kit")
+ ], resources: [.process("Resources")], plugins: [.plugin(name: "skipstone", package: "skip")]),
+ .testTarget(name: "MeloNXTests", dependencies: [
+ "MeloNX",
+ .product(name: "SkipTest", package: "skip")
+ ], resources: [.process("Resources")], plugins: [.plugin(name: "skipstone", package: "skip")]),
+ .target(name: "LibCLibrary", dependencies: [
+ .product(name: "SkipUnit", package: "skip-unit")
+ ], sources: ["src"], plugins: [.plugin(name: "skipstone", package: "skip")])
+ ]
+)
diff --git a/src/MeloNX-Skip/melonx-skip/README.md b/src/MeloNX-Skip/melonx-skip/README.md
new file mode 100644
index 000000000..c7c4ade9a
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/README.md
@@ -0,0 +1,42 @@
+# MeloNX
+
+This is a [Skip](https://skip.tools) dual-platform app project.
+It builds a native app for both iOS and Android.
+
+## Building
+
+This project is both a stand-alone Swift Package Manager module,
+as well as an Xcode project that builds and transpiles the project
+into a Kotlin Gradle project for Android using the Skip plugin.
+
+Building the module requires that Skip be installed using
+[Homebrew](https://brew.sh) with `brew install skiptools/skip/skip`.
+
+This will also install the necessary transpiler prerequisites:
+Kotlin, Gradle, and the Android build tools.
+
+Installation prerequisites can be confirmed by running `skip checkup`.
+
+## Testing
+
+The module can be tested using the standard `swift test` command
+or by running the test target for the macOS destination in Xcode,
+which will run the Swift tests as well as the transpiled
+Kotlin JUnit tests in the Robolectric Android simulation environment.
+
+Parity testing can be performed with `skip test`,
+which will output a table of the test results for both platforms.
+
+## Running
+
+Xcode and Android Studio must be downloaded and installed in order to
+run the app in the iOS simulator / Android emulator.
+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.
+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
+Android Studio's logcat tab for the transpiled Kotlin app.
diff --git a/src/MeloNX-Skip/melonx-skip/Skip.env b/src/MeloNX-Skip/melonx-skip/Skip.env
new file mode 100644
index 000000000..3189b198b
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Skip.env
@@ -0,0 +1,20 @@
+// The configuration file for your Skip App (https://skip.tools).
+// Properties specified here are shared between
+// 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_BUNDLE_IDENTIFIER is the unique id for both the iOS and Android app
+PRODUCT_BUNDLE_IDENTIFIER = com.xitrix.melonx
+
+// The semantic version of the app
+MARKETING_VERSION = 0.0.1
+
+// The build number specifying the internal app version
+CURRENT_PROJECT_VERSION = 1
+
+// The package name for the Android entry point, referenced by the AndroidManifest.xml
+ANDROID_PACKAGE_NAME = melo.nx
diff --git a/src/MeloNX-Skip/melonx-skip/Sources/LibCLibrary/CMakeLists.txt b/src/MeloNX-Skip/melonx-skip/Sources/LibCLibrary/CMakeLists.txt
new file mode 100644
index 000000000..68222c43c
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Sources/LibCLibrary/CMakeLists.txt
@@ -0,0 +1,9 @@
+cmake_minimum_required(VERSION 3.3)
+project(cproject, LANGUAGES C)
+file(GLOB SOURCES src/*.c)
+add_library(clibrary SHARED ${SOURCES})
+include_directories(clibrary PUBLIC include)
+set_property(TARGET clibrary PROPERTY IMPORTED_LOCATION "/projectspath/LinkTest/TestLib/app/build/intermediates/cmake/debug/obj/armeabi-v7a/libtest-lib.so")
+
+# include(libs/OpenSSL.cmake)
+# add_dependencies(clibrary openssl)
diff --git a/src/MeloNX-Skip/melonx-skip/Sources/LibCLibrary/Skip/skip.yml b/src/MeloNX-Skip/melonx-skip/Sources/LibCLibrary/Skip/skip.yml
new file mode 100644
index 000000000..87cc16b4e
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Sources/LibCLibrary/Skip/skip.yml
@@ -0,0 +1,27 @@
+
+# This skip.yml file will generate a build.gradle.kts that uses the CMakeLists.txt for a native build.
+#
+# android {
+# namespace = group as String
+# compileSdk = libs.versions.android.sdk.compile.get().toInt()
+# defaultConfig {
+# minSdk = libs.versions.android.sdk.min.get().toInt()
+# }
+# externalNativeBuild {
+# cmake {
+# path = file("ext/CMakeLists.txt")
+# }
+# }
+# }
+
+# the blocks to add to the build.gradle.kts
+build:
+ contents:
+ - block: 'android'
+ export: false
+ contents:
+ - block: 'externalNativeBuild'
+ contents:
+ - block: 'cmake'
+ contents:
+ - 'path = file("ext/CMakeLists.txt")'
diff --git a/src/MeloNX-Skip/melonx-skip/Sources/LibCLibrary/include/demo.h b/src/MeloNX-Skip/melonx-skip/Sources/LibCLibrary/include/demo.h
new file mode 100644
index 000000000..937b60f19
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Sources/LibCLibrary/include/demo.h
@@ -0,0 +1,55 @@
+//
+// demo.h
+// melonx-skip
+//
+// Created by Даниил Виноградов on 02.03.2025.
+//
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct GameInfo {
+ long FileSize;
+ char TitleName[512];
+ char TitleId[32];
+ char Developer[256];
+ char Version[16];
+ unsigned char* ImageData;
+ unsigned int ImageSize;
+};
+
+struct DlcNcaListItem {
+ char Path[256];
+ unsigned long TitleId;
+};
+
+struct DlcNcaList {
+ int success;
+ unsigned int size;
+ struct DlcNcaListItem* items;
+};
+
+extern struct GameInfo get_game_info(int, char*);
+
+extern struct DlcNcaList get_dlc_nca_list(const char* titleIdPtr, const char* pathPtr);
+
+void install_firmware(const char* inputPtr);
+
+char* installed_firmware_version();
+
+void stop_emulation();
+
+int main_ryujinx_sdl(int argc, char **argv);
+
+int get_current_fps();
+
+void initialize_android(char* path);
+
+int demo_number();
+
+char* test_func();
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/MeloNX-Skip/melonx-skip/Sources/LibCLibrary/libs/OpenSSL.cmake b/src/MeloNX-Skip/melonx-skip/Sources/LibCLibrary/libs/OpenSSL.cmake
new file mode 100644
index 000000000..855632983
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Sources/LibCLibrary/libs/OpenSSL.cmake
@@ -0,0 +1,54 @@
+include(ExternalProject)
+
+find_package(Perl 5 REQUIRED)
+
+set(PROJECT_ENV "ANDROID_NDK_ROOT=${CMAKE_ANDROID_NDK}")
+
+if (CMAKE_HOST_WIN32)
+ set(ProgramFiles_x86 "$ENV{ProgramFiles\(x86\)}")
+ # https://github.com/microsoft/vswhere/wiki/Find-MSBuild
+ cmake_path(APPEND VSWHERE_BIN "${ProgramFiles_x86}" "Microsoft Visual Studio" "Installer" "vswhere.exe")
+ # FIXME: Hardcoded architecture, no way to specify the MSVC version
+ execute_process(
+ COMMAND ${VSWHERE_BIN} "-latest" "-find" "VC\\Tools\\MSVC\\*\\bin\\Hostx64\\x64\\nmake.exe"
+ OUTPUT_VARIABLE NMAKE_PATHS_OUTPUT
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ COMMAND_ERROR_IS_FATAL ANY
+ )
+ string(REPLACE "\n" ";" NMAKE_PATH_LIST "${NMAKE_PATHS_OUTPUT}")
+ list(GET NMAKE_PATH_LIST 0 NMAKE_PATH)
+ cmake_path(NATIVE_PATH NMAKE_PATH NORMALIZE MAKE_COMMAND)
+
+ set(PROJECT_CFG_PREFIX ${PERL_EXECUTABLE})
+ # Deal with semicolon-separated lists
+ set(PROJECT_PATH_LIST $ENV{Path})
+ cmake_path(CONVERT "${ANDROID_TOOLCHAIN_ROOT}\\bin" TO_NATIVE_PATH_LIST ANDROID_TOOLCHAIN_BIN NORMALIZE)
+ list(PREPEND PROJECT_PATH_LIST "${ANDROID_TOOLCHAIN_BIN}")
+ # Replace semicolons with "|"
+ list(JOIN PROJECT_PATH_LIST "|" PROJECT_PATH_STRING)
+ # Add the modified PATH string to PROJECT_ENV
+ list(APPEND PROJECT_ENV "Path=${PROJECT_PATH_STRING}")
+elseif (CMAKE_HOST_UNIX)
+ find_program(MAKE_COMMAND NAMES make REQUIRED)
+ list(APPEND PROJECT_ENV "PATH=${ANDROID_TOOLCHAIN_ROOT}/bin:$ENV{PATH}")
+else ()
+ message(WARNING "Host system (${CMAKE_HOST_SYSTEM_NAME}) not supported. Treating as unix.")
+ find_program(MAKE_COMMAND NAMES make REQUIRED)
+ list(APPEND PROJECT_ENV "PATH=${ANDROID_TOOLCHAIN_ROOT}/bin:$ENV{PATH}")
+endif ()
+
+ExternalProject_Add(
+ openssl
+ GIT_REPOSITORY https://github.com/openssl/openssl.git
+ GIT_TAG a7e992847de83aa36be0c399c89db3fb827b0be2 # openssl-3.2.1
+ LIST_SEPARATOR "|"
+ CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env ${PROJECT_ENV}
+ ${PROJECT_CFG_PREFIX} /Configure
+ android-${CMAKE_ANDROID_ARCH}
+ -D__ANDROID_API_=${CMAKE_SYSTEM_VERSION}
+ --prefix=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
+ --libdir=""
+ BUILD_COMMAND ${CMAKE_COMMAND} -E env ${PROJECT_ENV}
+ ${MAKE_COMMAND}
+ INSTALL_COMMAND ${MAKE_COMMAND} install_runtime_libs
+)
diff --git a/src/MeloNX-Skip/melonx-skip/Sources/LibCLibrary/src/demo.c b/src/MeloNX-Skip/melonx-skip/Sources/LibCLibrary/src/demo.c
new file mode 100644
index 000000000..a9848f190
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Sources/LibCLibrary/src/demo.c
@@ -0,0 +1,13 @@
+//
+// demo.c
+// melonx-skip
+//
+// Created by Даниил Виноградов on 02.03.2025.
+//
+
+#include "demo.h"
+#include
+
+int demo_number() {
+ return 123;
+}
diff --git a/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Models/Game.swift b/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Models/Game.swift
new file mode 100644
index 000000000..9066f27a4
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Models/Game.swift
@@ -0,0 +1,69 @@
+//
+// Game.swift
+// melonx-skip
+//
+// Created by Даниил Виноградов on 04.03.2025.
+//
+
+//import LibCLibrary
+import Foundation
+import UIKit
+
+public struct Game: Identifiable, Equatable, Hashable {
+ public var id: URL { fileURL }
+
+ var containerFolder: URL
+// var fileType: UTType
+ var fileURL: URL
+
+ var titleName: String
+ var titleId: String
+ var developer: String
+ var version: String
+ var icon: UIImage?
+
+
+ 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 = gameInfo.TitleName
+
+ gameTemp.developer = gameInfo.Developer
+
+ gameTemp.titleId = gameInfo.TitleId
+
+
+ gameTemp.version = gameInfo.Version
+
+ let imageSize = Int(gameInfo.ImageSize)
+ if imageSize > 0, imageSize <= 1024 * 1024 {
+// let imageData = Data(bytes: gameInfo.ImageData, count: imageSize)
+//
+// gameTemp.icon = UIImage(data: imageData)
+ } else {
+ print("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)
+// }
+}
diff --git a/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Resources/Localizable.xcstrings b/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Resources/Localizable.xcstrings
new file mode 100644
index 000000000..53e41063e
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Resources/Localizable.xcstrings
@@ -0,0 +1,324 @@
+{
+ "sourceLanguage" : "en",
+ "strings" : {
+ "Appearance" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Apariencia"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Apparence"
+ }
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "外観"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "外观"
+ }
+ }
+ }
+ },
+ "Dark" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Oscuro"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sombre"
+ }
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "ダーク"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "暗"
+ }
+ }
+ }
+ },
+ "Firmware Version: %@" : {
+
+ },
+ "Framework" : {
+
+ },
+ "Games" : {
+
+ },
+ "Hello [%@](https://skip.tools)!" : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "¡Hola [%@](https://skip.tools)!"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bonjour [%@](https://skip.tools)!"
+ }
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "こんにちは、[%@](https://skip.tools)!"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "你好,[%@](https://skip.tools)!"
+ }
+ }
+ }
+ },
+ "Home" : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Inicio"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Accueil"
+ }
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "ホーム"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "主页"
+ }
+ }
+ }
+ },
+ "Light" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Claro"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Clair"
+ }
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "ライト"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "光"
+ }
+ }
+ }
+ },
+ "Mii Maker" : {
+
+ },
+ "Name" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Nombre"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Nom"
+ }
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "名前"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "姓名"
+ }
+ }
+ }
+ },
+ "Open game from system" : {
+
+ },
+ "Powered by [Skip](https://skip.tools)" : {
+
+ },
+ "Powered by Skip and %@" : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Potenciado por %@"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Propulsé par %@"
+ }
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@動力"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "由%@提供动力"
+ }
+ }
+ }
+ },
+ "Remove Firmware" : {
+
+ },
+ "Settings" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Configuración"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Paramètres"
+ }
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "設定"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "设置"
+ }
+ }
+ }
+ },
+ "Show MeloNX Folder" : {
+
+ },
+ "System" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sistema"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Système"
+ }
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "システム"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "系统"
+ }
+ }
+ }
+ },
+ "Version %@ (%@)" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Version %1$@ (%2$@)"
+ }
+ }
+ }
+ },
+ "Welcome" : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bienvenido"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bienvenue"
+ }
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "ようこそ"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "欢迎"
+ }
+ }
+ }
+ }
+ },
+ "version" : "1.0"
+}
\ No newline at end of file
diff --git a/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Resources/Module.xcassets/Contents.json b/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Resources/Module.xcassets/Contents.json
new file mode 100644
index 000000000..73c00596a
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Resources/Module.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Ryujinx.swift b/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Ryujinx.swift
new file mode 100644
index 000000000..9924f8b70
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Ryujinx.swift
@@ -0,0 +1,95 @@
+//
+// Ryujinx.swift
+// melonx-skip
+//
+// Created by Даниил Виноградов on 02.03.2025.
+//
+
+import Foundation
+import Combine
+
+@Observable class Ryujinx {
+ var firmwareversion = "0"
+ var testMessage: String = ""
+ var games: [Game] = []
+
+ static let shared = Ryujinx()
+
+ private init() {
+ testMessage = RyujinxLib.instance.test_func()
+ games = loadGames()
+ initialize()
+ }
+}
+
+extension Ryujinx {
+ func initialize() {
+ let path = URL.documentsDirectory.path()
+ RyujinxLib.instance.initialize_android(path)
+ }
+
+ func fetchFirmwareVersion() -> String {
+ do {
+ firmwareversion = RyujinxLib.instance.installed_firmware_version()
+ } catch {
+ firmwareversion = "0"
+ }
+ return firmwareversion
+ }
+
+ func installFirmware(firmwarePath: String) {
+ RyujinxLib.instance.install_firmware(firmwarePath)
+
+ let version = fetchFirmwareVersion()
+ if !version.isEmpty {
+ self.firmwareversion = version
+ }
+ }
+
+ func loadGames() -> [Game] {
+ let fileManager = FileManager.default
+ let documentsDirectory = URL.documentsDirectory
+
+ let romsDirectory = documentsDirectory.appendingPathComponent("roms")
+
+ if (!fileManager.fileExists(atPath: romsDirectory.path)) {
+ do {
+ try fileManager.createDirectory(at: romsDirectory, withIntermediateDirectories: true, attributes: nil)
+ } catch {
+ print("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 {
+ let handle = try FileHandle(forReadingFrom: fileURLCandidate)
+// let fileExtension = (fileURLCandidate.pathExtension as NSString).utf8String
+// let extensionPtr = UnsafeMutablePointer(mutating: fileExtension)
+
+
+ let gameInfo = RyujinxLib.instance.get_game_info(handle.fileDescriptor, "nsp")
+//
+ let game = Game.convertGameInfoToGame(gameInfo: gameInfo, url: fileURLCandidate)
+//
+ games.append(game)
+ } catch {
+ print(error)
+ }
+ }
+
+ return games
+ } catch {
+ print("Error loading games from roms folder: \(error)")
+ return games
+ }
+
+ }
+}
diff --git a/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/RyujinxLib.swift b/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/RyujinxLib.swift
new file mode 100644
index 000000000..8ce178608
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/RyujinxLib.swift
@@ -0,0 +1,71 @@
+//
+// Ryujinx.swift
+// melonx-skip
+//
+// Created by Даниил Виноградов on 02.03.2025.
+//
+
+import Foundation
+import SkipFFI
+import OSLog
+#if !SKIP
+import LibCLibrary
+#endif
+
+//#if SKIP
+struct GameInfo {
+ var fileSize: Int
+ var TitleName: String
+ var TitleId: String
+ var Developer: String
+ var Version: String
+ var ImageData: Data
+ var ImageSize: UInt
+}
+//#endif
+
+let appLogger = Logger(subsystem: "MeloNX", category: "Application")
+
+/// `DemoLibrary` is a Swift encapsulation of the embedded C library's functions and structures.
+internal final class DemoLib {
+ /// The singleton library instance, registered using JNA to map the Kotlin functions to their native equivalents
+ static let instance = registerNatives(DemoLib(), frameworkName: "MeloNXApp", libraryName: "clibrary")
+
+ /* SKIP EXTERN */ public func demo_number() -> Int32 {
+ return LibCLibrary.demo_number()
+ }
+}
+
+/// `DemoLibrary` is a Swift encapsulation of the embedded C library's functions and structures.
+internal final class RyujinxLib {
+ /// The singleton library instance, registered using JNA to map the Kotlin functions to their native equivalents
+ static let instance = registerNatives(RyujinxLib(), frameworkName: "MeloNXApp", libraryName: "Ryujinx.Headless.SDL2")
+
+ /* SKIP EXTERN */ public func installed_firmware_version() -> String {
+#if SKIP
+ return String(cString: LibCLibrary.installed_firmware_version())
+#else
+ return "nil"
+#endif
+ }
+
+ /* SKIP EXTERN */ public func test_func() -> String {
+#if SKIP
+ return String(cString: LibCLibrary.test_func())
+#else
+ return "nil"
+#endif
+ }
+
+ /* SKIP EXTERN */ public func initialize_android(_ path: String) {}
+
+ /* SKIP EXTERN */ public func install_firmware(_ path: String) {}
+
+ /* SKIP EXTERN */ public func get_game_info(_ fileDescriptor: Int32, _ extension: String) -> GameInfo {
+#if SKIP
+ return String(cString: LibCLibrary.test_func())
+#else
+ return GameInfo(fileSize: 0, TitleName: "", TitleId: "", Developer: "", Version: "", ImageData: Data(), ImageSize: 0)
+#endif
+ }
+}
diff --git a/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Screens/ContentView.swift b/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Screens/ContentView.swift
new file mode 100644
index 000000000..29a9ad32c
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Screens/ContentView.swift
@@ -0,0 +1,47 @@
+import SwiftUI
+import SkipKit
+
+enum ContentTab: String, Hashable {
+ case games, home, settings
+}
+
+struct ContentView: View {
+ @AppStorage("tab") var tab = ContentTab.games
+ @State var viewModel = ViewModel()
+ @State var appearance = ""
+
+ init() {
+ initializeSDL()
+ }
+
+ var body: some View {
+ TabView(selection: $tab) {
+ NavigationStack {
+ NavigationStack {
+ GamesView()
+ }
+ }
+ .tabItem { Label("Games", systemImage: "house.fill") }
+ .tag(ContentTab.games)
+
+ NavigationStack {
+ SettingsView(appearance: $appearance)
+ .navigationTitle("Settings")
+ }
+ .tabItem { Label("Settings", systemImage: "gearshape.fill") }
+ .tag(ContentTab.settings)
+ }
+ .environment(viewModel)
+ .preferredColorScheme(appearance == "dark" ? .dark : appearance == "light" ? .light : nil)
+ }
+
+ // MARK: - Helper Methods
+// var SdlInitFlags: uint = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO | SDL_INIT_VIDEO; // Initialises SDL2 for Events, Game Controller, Joystick, Audio and Video.
+ private func initializeSDL() {
+// setMoltenVKSettings()
+// SDL_SetMainReady() // Sets SDL Ready
+// SDL_iPhoneSetEventPump(SDL_TRUE) // Set iOS Event Pump to true
+// SDL_Init(SdlInitFlags) // Initialises SDL2
+ _ = Ryujinx.shared
+ }
+}
diff --git a/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Screens/GamesView.swift b/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Screens/GamesView.swift
new file mode 100644
index 000000000..ac18eb976
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Screens/GamesView.swift
@@ -0,0 +1,138 @@
+//
+// GamesView.swift
+// melonx-skip
+//
+// Created by Даниил Виноградов on 02.03.2025.
+//
+
+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 = ""
+
+// let allGames: [Game] = [
+// .init(title: "\(DemoLib.instance.demo_number())", developer: "Pesesda"),
+// .init(title: "\(Ryujinx.shared.testMessage)", developer: "Bethesda"),
+// .init(title: "Skyrim", developer: "Bethesda"),
+// .init(title: "Naruto", developer: "Anime"),
+// .init(title: "Burnour", developer: "Paradise")
+// ]
+ var allGames: Binding<[Game]> {
+ Binding(
+ get: { Ryujinx.shared.games },
+ set: { Ryujinx.shared.games = $0 }
+ )
+ }
+
+ var games: [Game] {
+ guard !searchQuery.isEmpty else { return Ryujinx.shared.games }
+ return Ryujinx.shared.games.filter {
+ $0.titleName.lowercased() == searchQuery.lowercased() ||
+ $0.developer.lowercased() == searchQuery.lowercased()
+ }
+ }
+
+ var body: some View {
+ List {
+// Section {
+ ForEach(games) { game in
+ GameView(game: game)
+ }
+// }
+ }
+ .onAppear {
+// loadRecentGames()
+
+ let firmware = Ryujinx.shared.fetchFirmwareVersion()
+ firmwareversion = (firmware == "" ? "0" : firmware)
+ }
+ .toolbar {
+ ToolbarItem(placement: .topBarTrailing) {
+ Button {} label: {
+ Image(systemName: "plus")
+ }
+ }
+ ToolbarItem(placement: .topBarLeading) {
+// Menu {
+// Button("Option 1") { }
+// Button("Option 2") { }
+// Button("Option 3") { }
+// } label: {
+// Image(systemName: "ellipsis")
+// }
+ Menu {
+ Text("Firmware Version: \(firmwareversion)")
+ .tint(.white)
+ Menu {
+ Button("Remove Firmware") {
+ let firmwarePath = URL.documentsDirectory.path() + "/firmware.zip"
+ Ryujinx.shared.installFirmware(firmwarePath: firmwarePath)
+ firmwareversion = (Ryujinx.shared.fetchFirmwareVersion() == "" ? "0" : Ryujinx.shared.fetchFirmwareVersion())
+ }
+ Button("Mii Maker") {
+ // TODO: - Edit action
+ }
+ Button("Open game from system") {
+ // TODO: - Edit action
+ }
+ } label: {
+ Label("Framework", systemImage: "chevron.right")
+ }
+ Button("Show MeloNX Folder") {
+ // TODO: - Edit action
+ }
+ } label: {
+ Image(systemName: "ellipsis")
+ }
+ }
+ }
+ .animation(.easeInOut, value: searchQuery)
+ .navigationTitle("Games")
+ .navigationBarTitleDisplayMode(.large)
+ .searchable(text: $searchQuery)
+ }
+}
+
+struct GameView: View {
+ @State var game: Game
+
+ 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)
+ }
+
+ // Game Info
+ VStack(alignment: .leading, spacing: 2) {
+ Text(game.titleName)
+ .font(.body)
+ .foregroundColor(.primary)
+
+ Text(game.developer)
+ .font(.subheadline)
+ .foregroundColor(.secondary)
+ }
+
+ Spacer()
+
+ Image(systemName: "play.circle.fill")
+ .font(.title2)
+ .foregroundColor(.accentColor)
+ .opacity(0.8)
+ }
+ }
+}
diff --git a/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Screens/MeloNXApp.swift b/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Screens/MeloNXApp.swift
new file mode 100644
index 000000000..9277bc502
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Screens/MeloNXApp.swift
@@ -0,0 +1,61 @@
+import Foundation
+import OSLog
+import SwiftUI
+
+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) })
+
+/// 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 init() {
+ }
+
+ public var body: some View {
+ ContentView()
+ .task {
+ logger.info("Welcome to Skip on \(androidSDK != nil ? "Android" : "Darwin")!")
+ logger.info("Skip app logs are viewable in the Xcode console for iOS; Android logs can be viewed in Studio or using adb logcat")
+ }
+ }
+}
+
+/// Global application delegate functions.
+///
+/// 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()
+
+ private init() {
+ }
+
+ public func onStart(_ sender: Any) {
+ logger.debug("onStart")
+ }
+
+ public func onResume(_ sender: Any) {
+ logger.debug("onResume")
+ }
+
+ public func onPause(_ sender: Any) {
+ logger.debug("onPause")
+ }
+
+ public func onStop(_ sender: Any) {
+ logger.debug("onStop")
+ }
+
+ public func onDestroy(_ sender: Any) {
+ logger.debug("onDestroy")
+ }
+
+ public func onLowMemory(_ sender: Any) {
+ logger.debug("onLowMemory")
+ }
+}
diff --git a/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Screens/SettingsView.swift b/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Screens/SettingsView.swift
new file mode 100644
index 000000000..0bdb9081f
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Screens/SettingsView.swift
@@ -0,0 +1,42 @@
+//
+// SettingsView.swift
+// melonx-skip
+//
+// Created by Даниил Виноградов on 02.03.2025.
+//
+
+import SwiftUI
+
+struct SettingsView : View {
+ @Environment(ViewModel.self) var viewModel: ViewModel
+ @Binding var appearance: String
+
+ var body: some View {
+ @Bindable var viewModel = viewModel
+ Form {
+ TextField("Name", text: $viewModel.name)
+ Picker("Appearance", selection: $appearance) {
+ Text("System").tag("")
+ Text("Light").tag("light")
+ Text("Dark").tag("dark")
+ }
+ HStack {
+ #if SKIP
+ ComposeView { ctx in // Mix in Compose code!
+ androidx.compose.material3.Text("💚", modifier: ctx.modifier)
+ }
+ #else
+ Text(verbatim: "💙")
+ #endif
+ if let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String,
+ let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String {
+ Text("Version \(version) (\(buildNumber))")
+ .foregroundStyle(.gray)
+ }
+ Text("Powered by [Skip](https://skip.tools)")
+ }
+ .foregroundStyle(.gray)
+
+ }
+ }
+}
diff --git a/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Skip/skip.yml b/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Skip/skip.yml
new file mode 100644
index 000000000..f0bf5ee5d
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/Skip/skip.yml
@@ -0,0 +1,3 @@
+# Configuration file for https://skip.tools project
+build:
+ contents:
diff --git a/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/ViewModel.swift b/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/ViewModel.swift
new file mode 100644
index 000000000..a1e780ac2
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Sources/MeloNX/ViewModel.swift
@@ -0,0 +1,96 @@
+import Foundation
+import Observation
+import OSLog
+
+fileprivate let logger: Logger = Logger(subsystem: "MeloNX", category: "MeloNX")
+
+/// The Observable ViewModel used by the application.
+@Observable public class ViewModel {
+ public var name = "Skipper"
+ public var items: [Item] = loadItems() {
+ didSet { saveItems() }
+ }
+
+ public init() {
+ }
+
+ public func clear() {
+ items.removeAll()
+ }
+
+ public func isUpdated(_ item: Item) -> Bool {
+ item != items.first { i in
+ i.id == item.id
+ }
+ }
+
+ public func save(item: Item) {
+ items = items.map { i in
+ i.id == item.id ? item : i
+ }
+ }
+}
+
+/// An individual item held by the ViewModel
+public struct Item : Identifiable, Hashable, Codable {
+ public let id: UUID
+ public var date: Date
+ public var favorite: Bool
+ public var title: String
+ public var notes: String
+
+ public init(id: UUID = UUID(), date: Date = .now, favorite: Bool = false, title: String = "", notes: String = "") {
+ self.id = id
+ self.date = date
+ self.favorite = favorite
+ self.title = title
+ self.notes = notes
+ }
+
+ public var itemTitle: String {
+ !title.isEmpty ? title : dateString
+ }
+
+ public var dateString: String {
+ date.formatted(date: .complete, time: .omitted)
+ }
+
+ public var dateTimeString: String {
+ date.formatted(date: .abbreviated, time: .shortened)
+ }
+}
+
+/// Utilities for defaulting and persising the items in the list
+extension ViewModel {
+ private static let savePath = URL.applicationSupportDirectory.appendingPathComponent("appdata.json")
+
+ fileprivate static func loadItems() -> [Item] {
+ do {
+ let start = Date.now
+ let data = try Data(contentsOf: savePath)
+ defer {
+ let end = Date.now
+ logger.info("loaded \(data.count) bytes from \(Self.savePath.path) in \(end.timeIntervalSince(start)) seconds")
+ }
+ return try JSONDecoder().decode([Item].self, from: data)
+ } catch {
+ // perhaps the first launch, or the data could not be read
+ logger.warning("failed to load data from \(Self.savePath), using defaultItems: \(error)")
+ let defaultItems = (1...365).map { Date(timeIntervalSinceNow: Double($0 * 60 * 60 * 24 * -1)) }
+ return defaultItems.map({ Item(date: $0) })
+ }
+ }
+
+ fileprivate func saveItems() {
+ do {
+ let start = Date.now
+ let data = try JSONEncoder().encode(items)
+ try FileManager.default.createDirectory(at: URL.applicationSupportDirectory, withIntermediateDirectories: true)
+ try data.write(to: Self.savePath)
+ let end = Date.now
+ logger.info("saved \(data.count) bytes to \(Self.savePath.path) in \(end.timeIntervalSince(start)) seconds")
+ } catch {
+ logger.error("error saving data: \(error)")
+ }
+ }
+}
diff --git a/src/MeloNX-Skip/melonx-skip/Tests/MeloNXTests/MeloNXTests.swift b/src/MeloNX-Skip/melonx-skip/Tests/MeloNXTests/MeloNXTests.swift
new file mode 100644
index 000000000..988970835
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Tests/MeloNXTests/MeloNXTests.swift
@@ -0,0 +1,27 @@
+import XCTest
+import OSLog
+import Foundation
+@testable import MeloNX
+
+let logger: Logger = Logger(subsystem: "MeloNX", category: "Tests")
+
+@available(macOS 13, *)
+final class MeloNXTests: XCTestCase {
+
+ func testMeloNX() throws {
+ logger.log("running testMeloNX")
+ XCTAssertEqual(1 + 2, 3, "basic test")
+ }
+
+ func testDecodeType() throws {
+ // load the TestData.json file from the Resources folder and decode it into a struct
+ let resourceURL: URL = try XCTUnwrap(Bundle.module.url(forResource: "TestData", withExtension: "json"))
+ let testData = try JSONDecoder().decode(TestData.self, from: Data(contentsOf: resourceURL))
+ XCTAssertEqual("MeloNX", testData.testModuleName)
+ }
+
+}
+
+struct TestData : Codable, Hashable {
+ var testModuleName: String
+}
diff --git a/src/MeloNX-Skip/melonx-skip/Tests/MeloNXTests/Resources/TestData.json b/src/MeloNX-Skip/melonx-skip/Tests/MeloNXTests/Resources/TestData.json
new file mode 100644
index 000000000..490a0b8c1
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Tests/MeloNXTests/Resources/TestData.json
@@ -0,0 +1,3 @@
+{
+ "testModuleName": "MeloNX"
+}
\ No newline at end of file
diff --git a/src/MeloNX-Skip/melonx-skip/Tests/MeloNXTests/Skip/skip.yml b/src/MeloNX-Skip/melonx-skip/Tests/MeloNXTests/Skip/skip.yml
new file mode 100644
index 000000000..f7eb02105
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Tests/MeloNXTests/Skip/skip.yml
@@ -0,0 +1,3 @@
+# Configuration file for https://skip.tools project
+#build:
+# contents:
\ No newline at end of file
diff --git a/src/MeloNX-Skip/melonx-skip/Tests/MeloNXTests/XCSkipTests.swift b/src/MeloNX-Skip/melonx-skip/Tests/MeloNXTests/XCSkipTests.swift
new file mode 100644
index 000000000..b4a626386
--- /dev/null
+++ b/src/MeloNX-Skip/melonx-skip/Tests/MeloNXTests/XCSkipTests.swift
@@ -0,0 +1,28 @@
+import Foundation
+#if os(macOS) // Skip transpiled tests only run on macOS targets
+import SkipTest
+
+/// This test case will run the transpiled tests for the Skip module.
+@available(macOS 13, macCatalyst 16, *)
+final class XCSkipTests: XCTestCase, XCGradleHarness {
+ public func testSkipModule() async throws {
+ // Run the transpiled JUnit tests for the current test module.
+ // These tests will be executed locally using Robolectric.
+ // Connected device or emulator tests can be run by setting the
+ // `ANDROID_SERIAL` environment variable to an `adb devices`
+ // ID in the scheme's Run settings.
+ //
+ // Note that it isn't currently possible to filter the tests to run.
+ try await runGradleTests()
+ }
+}
+#endif
+
+/// True when running in a transpiled Java runtime environment
+let isJava = ProcessInfo.processInfo.environment["java.io.tmpdir"] != nil
+/// True when running within an Android environment (either an emulator or device)
+let isAndroid = isJava && ProcessInfo.processInfo.environment["ANDROID_ROOT"] != nil
+/// True is the transpiled code is currently running in the local Robolectric test environment
+let isRobolectric = isJava && !isAndroid
+/// True if the system's `Int` type is 32-bit.
+let is32BitInteger = Int64(Int.max) == Int64(Int32.max)
diff --git a/src/MeloNX/MeloNX.xcodeproj/project.pbxproj b/src/MeloNX/MeloNX.xcodeproj/project.pbxproj
index 567e8252e..0292f3475 100644
--- a/src/MeloNX/MeloNX.xcodeproj/project.pbxproj
+++ b/src/MeloNX/MeloNX.xcodeproj/project.pbxproj
@@ -633,7 +633,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "";
- DEVELOPMENT_TEAM = 95J8WZ4TN8;
+ DEVELOPMENT_TEAM = D59DHVRS87;
ENABLE_PREVIEWS = YES;
ENABLE_TESTABILITY = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -674,6 +674,8 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
+ "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
+ "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
);
GCC_OPTIMIZATION_LEVEL = fast;
GENERATE_INFOPLIST_FILE = YES;
@@ -761,9 +763,13 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
+ "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
+ "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
+ "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
+ "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
);
MARKETING_VERSION = 1.3.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
+ PRODUCT_BUNDLE_IDENTIFIER = com.xitrix.MeloNX;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h";
@@ -781,7 +787,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "";
- DEVELOPMENT_TEAM = 95J8WZ4TN8;
+ DEVELOPMENT_TEAM = D59DHVRS87;
ENABLE_PREVIEWS = YES;
ENABLE_TESTABILITY = YES;
FRAMEWORK_SEARCH_PATHS = (
@@ -822,6 +828,8 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
+ "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
+ "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
);
GCC_OPTIMIZATION_LEVEL = fast;
GENERATE_INFOPLIST_FILE = YES;
@@ -909,9 +917,13 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
+ "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
+ "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
+ "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
+ "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
);
MARKETING_VERSION = 1.3.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
+ PRODUCT_BUNDLE_IDENTIFIER = com.xitrix.MeloNX;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h";
diff --git a/src/Ryujinx.Common/Configuration/AppDataManager.cs b/src/Ryujinx.Common/Configuration/AppDataManager.cs
index 2e7765a15..3e45fe417 100644
--- a/src/Ryujinx.Common/Configuration/AppDataManager.cs
+++ b/src/Ryujinx.Common/Configuration/AppDataManager.cs
@@ -116,6 +116,7 @@ namespace Ryujinx.Common.Configuration
}
}
+ Logger.Info?.Print(LogClass.Application, $"Final BaseDirPath: {BaseDirPath}");
SetupBasePaths();
}
diff --git a/src/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs b/src/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs
index b1cffcb56..2ff49526a 100644
--- a/src/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs
+++ b/src/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs
@@ -3,7 +3,7 @@ using System.Text;
namespace Ryujinx.Common.Logging.Formatters
{
- internal class DefaultLogFormatter : ILogFormatter
+ public class DefaultLogFormatter : ILogFormatter
{
private static readonly ObjectPool _stringBuilderPool = SharedPools.Default();
diff --git a/src/Ryujinx.Common/Logging/Logger.cs b/src/Ryujinx.Common/Logging/Logger.cs
index f03a7fd8f..dc2399e07 100644
--- a/src/Ryujinx.Common/Logging/Logger.cs
+++ b/src/Ryujinx.Common/Logging/Logger.cs
@@ -126,11 +126,14 @@ namespace Ryujinx.Common.Logging
_time = Stopwatch.StartNew();
- // Logger should log to console by default
- AddTarget(new AsyncLogTargetWrapper(
- new ConsoleLogTarget("console"),
- 1000,
- AsyncLogTargetOverflowAction.Discard));
+ if (!Ryujinx.Common.PlatformInfo.IsBionic)
+ {
+ // Logger should log to console by default
+ AddTarget(new AsyncLogTargetWrapper(
+ new ConsoleLogTarget("console"),
+ 1000,
+ AsyncLogTargetOverflowAction.Discard));
+ }
Notice = new Log(LogLevel.Notice);
diff --git a/src/Ryujinx.Common/PlatformInfo.cs b/src/Ryujinx.Common/PlatformInfo.cs
new file mode 100644
index 000000000..613eb81b4
--- /dev/null
+++ b/src/Ryujinx.Common/PlatformInfo.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.Common
+{
+ public static class PlatformInfo
+ {
+ public static bool IsBionic { get; set; }
+ }
+}
diff --git a/src/Ryujinx.Headless.SDL2/Android/AndroidLogTarget.cs b/src/Ryujinx.Headless.SDL2/Android/AndroidLogTarget.cs
new file mode 100644
index 000000000..8e2d37e35
--- /dev/null
+++ b/src/Ryujinx.Headless.SDL2/Android/AndroidLogTarget.cs
@@ -0,0 +1,49 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Common.Logging.Formatters;
+using Ryujinx.Common.Logging.Targets;
+using System;
+
+namespace Ryujinx.Headless.SDL2.Android
+{
+ public class AndroidLogTarget : ILogTarget
+ {
+ private readonly string _name;
+ private readonly DefaultLogFormatter _formatter;
+
+ string ILogTarget.Name { get => _name; }
+
+ public AndroidLogTarget(string name)
+ {
+ _name = name;
+ _formatter = new DefaultLogFormatter();
+ }
+
+ public void Log(object sender, LogEventArgs args)
+ {
+ Logcat.AndroidLogPrint(GetLogLevel(args.Level), _name, _formatter.Format(args));
+ }
+
+ private static Logcat.LogLevel GetLogLevel(LogLevel logLevel)
+ {
+ return logLevel switch
+ {
+ LogLevel.Debug => Logcat.LogLevel.Debug,
+ LogLevel.Stub => Logcat.LogLevel.Info,
+ LogLevel.Info => Logcat.LogLevel.Info,
+ LogLevel.Warning => Logcat.LogLevel.Warn,
+ LogLevel.Error => Logcat.LogLevel.Error,
+ LogLevel.Guest => Logcat.LogLevel.Info,
+ LogLevel.AccessLog => Logcat.LogLevel.Info,
+ LogLevel.Notice => Logcat.LogLevel.Info,
+ LogLevel.Trace => Logcat.LogLevel.Verbose,
+ _ => throw new NotImplementedException(),
+
+ };
+ }
+
+ public void Dispose()
+ {
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/src/Ryujinx.Headless.SDL2/Android/JniExportedMethods.cs b/src/Ryujinx.Headless.SDL2/Android/JniExportedMethods.cs
new file mode 100644
index 000000000..a86fc5fc1
--- /dev/null
+++ b/src/Ryujinx.Headless.SDL2/Android/JniExportedMethods.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Headless.SDL2.Android
+{
+ public class JniExportedMethods
+ {
+ }
+
+ internal static partial class Logcat
+ {
+ [LibraryImport("liblog", StringMarshalling = StringMarshalling.Utf8)]
+ private static partial void __android_log_print(LogLevel level, string? tag, string format, string args,
+ IntPtr ptr);
+
+ internal static void AndroidLogPrint(LogLevel level, string? tag, string message) =>
+ __android_log_print(level, tag, "%s", message, IntPtr.Zero);
+
+ internal enum LogLevel
+ {
+ Unknown = 0x00,
+ Default = 0x01,
+ Verbose = 0x02,
+ Debug = 0x03,
+ Info = 0x04,
+ Warn = 0x05,
+ Error = 0x06,
+ Fatal = 0x07,
+ Silent = 0x08,
+ }
+ }
+}
diff --git a/src/Ryujinx.Headless.SDL2/Program.cs b/src/Ryujinx.Headless.SDL2/Program.cs
index 3fd96ecef..e8c5dbdbd 100644
--- a/src/Ryujinx.Headless.SDL2/Program.cs
+++ b/src/Ryujinx.Headless.SDL2/Program.cs
@@ -86,6 +86,7 @@ using LibHac.Ncm;
using LibHac.Tools.FsSystem.NcaUtils;
using Microsoft.Win32.SafeHandles;
using Ryujinx.Common.Logging;
+using Ryujinx.Headless.SDL2.Android;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.Input.HLE;
@@ -246,17 +247,47 @@ namespace Ryujinx.Headless.SDL2
}
- [UnmanagedCallersOnly(EntryPoint = "initialize")]
- public static unsafe void Initialize()
+ [UnmanagedCallersOnly(EntryPoint = "test_func")]
+ public static IntPtr TestFunc()
{
+ var result = "success";
+ return Marshal.StringToHGlobalAnsi(result);
+ }
- AppDataManager.Initialize(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments));
+
+ [UnmanagedCallersOnly(EntryPoint = "initialize_android")]
+ public static unsafe void InitializeAndroid(IntPtr inputPtr)
+ {
+ PlatformInfo.IsBionic = true;
+
+ string baseDirectory = Marshal.PtrToStringAnsi(inputPtr);
+
+ Logger.AddTarget(
+ new AsyncLogTargetWrapper(
+ new AndroidLogTarget("RyujinxLog"),
+ 1000,
+ AsyncLogTargetOverflowAction.Block
+ ));
+
+ CommonInitialize(baseDirectory);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "initialize")]
+ public static unsafe void Initialize()
+ {
+ CommonInitialize(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments));
+ }
+
+ public static unsafe void CommonInitialize(string baseDirPath)
+ {
+ Logger.Info?.Print(LogClass.Application, $"BaseBirPath {baseDirPath}");
+ AppDataManager.Initialize(baseDirPath);
if (_virtualFileSystem == null)
{
_virtualFileSystem = VirtualFileSystem.CreateInstance();
}
-
+
if (_libHacHorizonManager == null)
{
_libHacHorizonManager = new LibHacHorizonManager();
@@ -265,17 +296,17 @@ namespace Ryujinx.Headless.SDL2
_libHacHorizonManager.InitializeBcatServer();
_libHacHorizonManager.InitializeSystemClients();
}
-
+
if (_contentManager == null)
{
_contentManager = new ContentManager(_virtualFileSystem);
}
-
+
if (_accountManager == null)
{
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, "");
}
-
+
if (_userChannelPersistence == null)
{
_userChannelPersistence = new UserChannelPersistence();
@@ -385,7 +416,7 @@ namespace Ryujinx.Headless.SDL2
return version.VersionString;
}
- return String.Empty;
+ return "String.Empty";
}
[UnmanagedCallersOnly(EntryPoint = "stop_emulation")]
diff --git a/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj b/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj
index 059ba0d68..aa75ae4f5 100644
--- a/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj
+++ b/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj
@@ -17,67 +17,75 @@
Speed
+
+
+
+
+
+
+
+
-
- true
- true
- true
- /Applications/Xcode.app/Contents/Developer
- $([MSBuild]::EnsureTrailingSlash('$(XCodePath)'))
-
+
+
+
+
+
+
+
-
+
+
-
+
-
+
+
+
-
-
-
+
+
+
-
- $(XcodeSelect)
- $([MSBuild]::EnsureTrailingSlash('$(XCodePath)'))
-
+
+
+
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
+
-
-
+
+
+
-
-
+
+
-
+
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+