16 changed files with 2910 additions and 3 deletions
-
68src/android/android/.gitignore
-
114src/android/android/app/src/main/AndroidManifest.xml
-
151src/android/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
-
476src/android/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
-
140src/android/android/app/src/main/java/org/yuzu/yuzu_emu/utils/LogFilter.kt
-
1584src/android/android/app/src/main/res/values/strings.xml
-
23src/android/android/build.gradle.kts
-
19src/android/android/gradle.properties
-
BINsrc/android/android/gradle/wrapper/gradle-wrapper.jar
-
6src/android/android/gradle/wrapper/gradle-wrapper.properties
-
175src/android/android/gradlew
-
87src/android/android/gradlew.bat
-
21src/android/android/settings.gradle.kts
-
10src/android/app/src/main/AndroidManifest.xml
-
30src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/LogFilter.kt
-
9src/android/app/src/main/res/xml/file_paths.xml
@ -0,0 +1,68 @@ |
|||||
|
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project |
||||
|
# SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
|
||||
|
# Built application files |
||||
|
*.apk |
||||
|
*.ap_ |
||||
|
|
||||
|
# Files for the ART/Dalvik VM |
||||
|
*.dex |
||||
|
|
||||
|
# Java class files |
||||
|
*.class |
||||
|
|
||||
|
# Generated files |
||||
|
bin/ |
||||
|
gen/ |
||||
|
out/ |
||||
|
|
||||
|
# Gradle files |
||||
|
.gradle/ |
||||
|
build/ |
||||
|
|
||||
|
# Local configuration file (sdk path, etc) |
||||
|
local.properties |
||||
|
|
||||
|
# Proguard folder generated by Eclipse |
||||
|
proguard/ |
||||
|
|
||||
|
# Log Files |
||||
|
*.log |
||||
|
|
||||
|
# Android Studio Navigation editor temp files |
||||
|
.navigation/ |
||||
|
|
||||
|
# Android Studio captures folder |
||||
|
captures/ |
||||
|
|
||||
|
# IntelliJ |
||||
|
*.iml |
||||
|
.idea/ |
||||
|
|
||||
|
# Keystore files |
||||
|
# Uncomment the following line if you do not want to check your keystore files in. |
||||
|
#*.jks |
||||
|
|
||||
|
# External native build folder generated in Android Studio 2.2 and later |
||||
|
.externalNativeBuild |
||||
|
|
||||
|
# CXX compile cache |
||||
|
app/.cxx |
||||
|
|
||||
|
# Google Services (e.g. APIs or Firebase) |
||||
|
google-services.json |
||||
|
|
||||
|
# Freeline |
||||
|
freeline.py |
||||
|
freeline/ |
||||
|
freeline_project_description.json |
||||
|
|
||||
|
# fastlane |
||||
|
fastlane/report.xml |
||||
|
fastlane/Preview.html |
||||
|
fastlane/screenshots |
||||
|
fastlane/test_output |
||||
|
fastlane/readme.md |
||||
|
|
||||
|
# Autogenerated library for vulkan validation layers |
||||
|
libVkLayer_khronos_validation.so |
||||
@ -0,0 +1,114 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
|
||||
|
<!-- |
||||
|
SPDX-FileCopyrightText: 2023 yuzu Emulator Project |
||||
|
SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
|
||||
|
SPDX-FileCopyrightText: 2025 Eden Emulator Project |
||||
|
SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
--> |
||||
|
<!-- |
||||
|
SPDX-FileCopyrightText: Eden Emulator Project |
||||
|
SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
--> |
||||
|
|
||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> |
||||
|
<uses-feature android:name="android.hardware.touchscreen" android:required="false" /> |
||||
|
<uses-feature android:name="android.hardware.gamepad" android:required="false" /> |
||||
|
<uses-feature android:name="android.software.leanback" android:required="false" /> |
||||
|
<uses-feature android:name="android.hardware.vulkan.version" android:version="0x401000" android:required="true" /> |
||||
|
|
||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> |
||||
|
<uses-permission android:name="android.permission.INTERNET" /> |
||||
|
<uses-permission android:name="android.permission.NFC" /> |
||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> |
||||
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> |
||||
|
<uses-permission android:name="android.permission.VIBRATE" /> |
||||
|
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" android:required="false" /> |
||||
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> |
||||
|
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> |
||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> |
||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" /> |
||||
|
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> |
||||
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" /> |
||||
|
|
||||
|
<application |
||||
|
android:name="org.yuzu.yuzu_emu.YuzuApplication" |
||||
|
android:label="@string/app_name_suffixed" |
||||
|
android:icon="@drawable/ic_launcher" |
||||
|
android:allowBackup="true" |
||||
|
android:hasFragileUserData="false" |
||||
|
android:supportsRtl="true" |
||||
|
android:isGame="true" |
||||
|
android:appCategory="game" |
||||
|
android:banner="@drawable/tv_banner" |
||||
|
android:fullBackupContent="@xml/data_extraction_rules" |
||||
|
android:dataExtractionRules="@xml/data_extraction_rules_api_31" |
||||
|
tools:targetApi="33" |
||||
|
android:enableOnBackInvokedCallback="true"> |
||||
|
|
||||
|
<meta-data android:name="com.samsung.android.gamehub" android:value="true" /> |
||||
|
<meta-data android:name="com.xiaomi.gamecenter.sdk.service.enabled" android:value="true" /> |
||||
|
<meta-data android:name="com.asus.gamecenter.gamebooster" android:value="true" /> |
||||
|
<meta-data android:name="com.oneplus.gamespace.gamebooster" android:value="true" /> |
||||
|
<meta-data android:name="android.game_mode_config" android:resource="@xml/game_mode_config" /> |
||||
|
|
||||
|
<activity |
||||
|
android:name="org.yuzu.yuzu_emu.ui.main.MainActivity" |
||||
|
android:exported="true" |
||||
|
android:theme="@style/Theme.Yuzu.Splash.Main"> |
||||
|
<intent-filter> |
||||
|
<action android:name="com.miui.gamecenter.GAME_BOOSTER_LAUNCH"/> |
||||
|
<action android:name="android.intent.action.MAIN" /> |
||||
|
<category android:name="android.intent.category.LAUNCHER" /> |
||||
|
<category android:name="android.intent.category.GAME" /> |
||||
|
<category android:name="android.intent.category.LEANBACK_LAUNCHER" /> |
||||
|
</intent-filter> |
||||
|
</activity> |
||||
|
|
||||
|
<activity |
||||
|
android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity" |
||||
|
android:theme="@style/Theme.Yuzu.Main" |
||||
|
android:label="@string/preferences_settings"/> |
||||
|
|
||||
|
<activity |
||||
|
android:name="org.yuzu.yuzu_emu.activities.EmulationActivity" |
||||
|
android:theme="@style/Theme.Yuzu.Main" |
||||
|
android:launchMode="singleTop" |
||||
|
android:supportsPictureInPicture="true" |
||||
|
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode" |
||||
|
android:exported="true"> |
||||
|
<intent-filter> |
||||
|
<action android:name="android.nfc.action.TECH_DISCOVERED" /> |
||||
|
<category android:name="android.intent.category.DEFAULT" /> |
||||
|
<data android:mimeType="application/octet-stream" /> |
||||
|
</intent-filter> |
||||
|
<intent-filter> |
||||
|
<action android:name="android.intent.action.VIEW" /> |
||||
|
<category android:name="android.intent.category.DEFAULT" /> |
||||
|
<data android:mimeType="application/octet-stream" android:scheme="content"/> |
||||
|
</intent-filter> |
||||
|
<intent-filter> |
||||
|
<action android:name="dev.eden.eden_emulator.LAUNCH_WITH_CUSTOM_CONFIG" /> |
||||
|
<category android:name="android.intent.category.DEFAULT" /> |
||||
|
</intent-filter> |
||||
|
<meta-data android:name="android.nfc.action.TECH_DISCOVERED" android:resource="@xml/nfc_tech_filter" /> |
||||
|
</activity> |
||||
|
|
||||
|
<service android:name="org.yuzu.yuzu_emu.utils.ForegroundService" android:foregroundServiceType="specialUse"> |
||||
|
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE" android:value="Keep emulation running in background"/> |
||||
|
</service> |
||||
|
|
||||
|
<provider |
||||
|
android:name=".features.DocumentProvider" |
||||
|
android:authorities="${applicationId}.user" |
||||
|
android:grantUriPermissions="true" |
||||
|
android:exported="true" |
||||
|
android:permission="android.permission.MANAGE_DOCUMENTS"> |
||||
|
<intent-filter> |
||||
|
<action android:name="android.content.action.DOCUMENTS_PROVIDER" /> |
||||
|
</intent-filter> |
||||
|
</provider> |
||||
|
|
||||
|
</application> |
||||
|
</manifest> |
||||
@ -0,0 +1,151 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
|
||||
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
package org.yuzu.yuzu_emu.adapters |
||||
|
|
||||
|
import android.view.LayoutInflater |
||||
|
import android.view.ViewGroup |
||||
|
import androidx.appcompat.app.AppCompatActivity |
||||
|
import androidx.core.content.res.ResourcesCompat |
||||
|
import androidx.lifecycle.LifecycleOwner |
||||
|
import org.yuzu.yuzu_emu.R |
||||
|
import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding |
||||
|
import org.yuzu.yuzu_emu.databinding.CardHomeOptionWithButtonBinding |
||||
|
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment |
||||
|
import org.yuzu.yuzu_emu.model.HomeSetting |
||||
|
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee |
||||
|
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible |
||||
|
import org.yuzu.yuzu_emu.utils.collect |
||||
|
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder |
||||
|
|
||||
|
class HomeSettingAdapter( |
||||
|
private val activity: AppCompatActivity, |
||||
|
private val viewLifecycle: LifecycleOwner, |
||||
|
options: List<HomeSetting>, |
||||
|
private val onFilterClick: (() -> Unit)? = null |
||||
|
) : AbstractListAdapter<HomeSetting, AbstractViewHolder<HomeSetting>>(options) { |
||||
|
|
||||
|
companion object { |
||||
|
private const val VIEW_TYPE_NORMAL = 0 |
||||
|
private const val VIEW_TYPE_WITH_BUTTON = 1 |
||||
|
} |
||||
|
|
||||
|
override fun getItemViewType(position: Int): Int { |
||||
|
val item = getItem(position) |
||||
|
return if (item.titleId == R.string.share_log) { |
||||
|
VIEW_TYPE_WITH_BUTTON |
||||
|
} else { |
||||
|
VIEW_TYPE_NORMAL |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AbstractViewHolder<HomeSetting> { |
||||
|
return when (viewType) { |
||||
|
VIEW_TYPE_WITH_BUTTON -> { |
||||
|
val binding = CardHomeOptionWithButtonBinding.inflate(LayoutInflater.from(parent.context), parent, false) |
||||
|
HomeOptionWithButtonViewHolder(binding) |
||||
|
} |
||||
|
else -> { |
||||
|
val binding = CardHomeOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false) |
||||
|
HomeOptionViewHolder(binding) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
inner class HomeOptionViewHolder(val binding: CardHomeOptionBinding) : |
||||
|
AbstractViewHolder<HomeSetting>(binding) { |
||||
|
override fun bind(model: HomeSetting) { |
||||
|
binding.optionTitle.text = activity.resources.getString(model.titleId) |
||||
|
binding.optionDescription.text = activity.resources.getString(model.descriptionId) |
||||
|
binding.optionIcon.setImageDrawable( |
||||
|
ResourcesCompat.getDrawable( |
||||
|
activity.resources, |
||||
|
model.iconId, |
||||
|
activity.theme |
||||
|
) |
||||
|
) |
||||
|
|
||||
|
if (!model.isEnabled.invoke()) { |
||||
|
binding.optionTitle.alpha = 0.5f |
||||
|
binding.optionDescription.alpha = 0.5f |
||||
|
binding.optionIcon.alpha = 0.5f |
||||
|
} |
||||
|
|
||||
|
model.details.collect(viewLifecycle) { updateOptionDetails(it) } |
||||
|
binding.optionDetail.marquee() |
||||
|
|
||||
|
binding.root.setOnClickListener { onClick(model) } |
||||
|
} |
||||
|
|
||||
|
private fun onClick(model: HomeSetting) { |
||||
|
if (model.isEnabled.invoke()) { |
||||
|
model.onClick.invoke() |
||||
|
} else { |
||||
|
MessageDialogFragment.newInstance( |
||||
|
activity, |
||||
|
titleId = model.disabledTitleId, |
||||
|
descriptionId = model.disabledMessageId |
||||
|
).show(activity.supportFragmentManager, MessageDialogFragment.TAG) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private fun updateOptionDetails(detailString: String) { |
||||
|
if (detailString.isNotEmpty()) { |
||||
|
binding.optionDetail.text = detailString |
||||
|
binding.optionDetail.setVisible(true) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
inner class HomeOptionWithButtonViewHolder(val binding: CardHomeOptionWithButtonBinding) : |
||||
|
AbstractViewHolder<HomeSetting>(binding) { |
||||
|
override fun bind(model: HomeSetting) { |
||||
|
binding.optionTitle.text = activity.resources.getString(model.titleId) |
||||
|
binding.optionDescription.text = activity.resources.getString(model.descriptionId) |
||||
|
binding.optionIcon.setImageDrawable( |
||||
|
ResourcesCompat.getDrawable( |
||||
|
activity.resources, |
||||
|
model.iconId, |
||||
|
activity.theme |
||||
|
) |
||||
|
) |
||||
|
|
||||
|
if (!model.isEnabled.invoke()) { |
||||
|
binding.optionTitle.alpha = 0.5f |
||||
|
binding.optionDescription.alpha = 0.5f |
||||
|
binding.optionIcon.alpha = 0.5f |
||||
|
binding.filterButton.alpha = 0.5f |
||||
|
} |
||||
|
|
||||
|
model.details.collect(viewLifecycle) { updateOptionDetails(it) } |
||||
|
binding.optionDetail.marquee() |
||||
|
|
||||
|
binding.root.setOnClickListener { onClick(model) } |
||||
|
binding.filterButton.setOnClickListener { |
||||
|
onFilterClick?.invoke() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private fun onClick(model: HomeSetting) { |
||||
|
if (model.isEnabled.invoke()) { |
||||
|
model.onClick.invoke() |
||||
|
} else { |
||||
|
MessageDialogFragment.newInstance( |
||||
|
activity, |
||||
|
titleId = model.disabledTitleId, |
||||
|
descriptionId = model.disabledMessageId |
||||
|
).show(activity.supportFragmentManager, MessageDialogFragment.TAG) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private fun updateOptionDetails(detailString: String) { |
||||
|
if (detailString.isNotEmpty()) { |
||||
|
binding.optionDetail.text = detailString |
||||
|
binding.optionDetail.setVisible(true) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,476 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
|
||||
|
package org.yuzu.yuzu_emu.fragments |
||||
|
|
||||
|
import android.Manifest |
||||
|
import android.content.ActivityNotFoundException |
||||
|
import android.content.Intent |
||||
|
import android.content.pm.PackageManager |
||||
|
import android.net.Uri |
||||
|
import android.os.Bundle |
||||
|
import android.provider.DocumentsContract |
||||
|
import android.view.LayoutInflater |
||||
|
import android.view.View |
||||
|
import android.view.ViewGroup |
||||
|
import android.widget.Toast |
||||
|
import androidx.appcompat.app.AppCompatActivity |
||||
|
import androidx.core.app.ActivityCompat |
||||
|
import androidx.core.app.NotificationCompat |
||||
|
import androidx.core.app.NotificationManagerCompat |
||||
|
import androidx.core.view.ViewCompat |
||||
|
import androidx.core.view.WindowInsetsCompat |
||||
|
import androidx.core.view.updatePadding |
||||
|
import androidx.documentfile.provider.DocumentFile |
||||
|
import androidx.fragment.app.Fragment |
||||
|
import androidx.fragment.app.activityViewModels |
||||
|
import androidx.navigation.findNavController |
||||
|
import androidx.navigation.fragment.findNavController |
||||
|
import androidx.recyclerview.widget.GridLayoutManager |
||||
|
import com.google.android.material.transition.MaterialSharedAxis |
||||
|
import org.yuzu.yuzu_emu.HomeNavigationDirections |
||||
|
import org.yuzu.yuzu_emu.NativeLibrary |
||||
|
import org.yuzu.yuzu_emu.R |
||||
|
import org.yuzu.yuzu_emu.YuzuApplication |
||||
|
import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter |
||||
|
import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding |
||||
|
import org.yuzu.yuzu_emu.features.DocumentProvider |
||||
|
import org.yuzu.yuzu_emu.features.fetcher.SpacingItemDecoration |
||||
|
import org.yuzu.yuzu_emu.features.settings.model.Settings |
||||
|
import org.yuzu.yuzu_emu.model.DriverViewModel |
||||
|
import org.yuzu.yuzu_emu.model.HomeSetting |
||||
|
import org.yuzu.yuzu_emu.model.HomeViewModel |
||||
|
import org.yuzu.yuzu_emu.ui.main.MainActivity |
||||
|
import org.yuzu.yuzu_emu.utils.FileUtil |
||||
|
import org.yuzu.yuzu_emu.utils.LogFilter |
||||
|
|
||||
|
class HomeSettingsFragment : Fragment() { |
||||
|
private var _binding: FragmentHomeSettingsBinding? = null |
||||
|
private val binding get() = _binding!! |
||||
|
|
||||
|
private lateinit var mainActivity: MainActivity |
||||
|
|
||||
|
private val homeViewModel: HomeViewModel by activityViewModels() |
||||
|
private val driverViewModel: DriverViewModel by activityViewModels() |
||||
|
|
||||
|
override fun onCreate(savedInstanceState: Bundle?) { |
||||
|
super.onCreate(savedInstanceState) |
||||
|
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) |
||||
|
} |
||||
|
|
||||
|
override fun onCreateView( |
||||
|
inflater: LayoutInflater, |
||||
|
container: ViewGroup?, |
||||
|
savedInstanceState: Bundle? |
||||
|
): View { |
||||
|
_binding = FragmentHomeSettingsBinding.inflate(layoutInflater) |
||||
|
return binding.root |
||||
|
} |
||||
|
|
||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
||||
|
super.onViewCreated(view, savedInstanceState) |
||||
|
homeViewModel.setStatusBarShadeVisibility(visible = true) |
||||
|
mainActivity = requireActivity() as MainActivity |
||||
|
|
||||
|
val optionsList: MutableList<HomeSetting> = mutableListOf<HomeSetting>().apply { |
||||
|
add( |
||||
|
HomeSetting( |
||||
|
R.string.advanced_settings, |
||||
|
R.string.settings_description, |
||||
|
R.drawable.ic_settings, |
||||
|
{ |
||||
|
val action = HomeNavigationDirections.actionGlobalSettingsActivity( |
||||
|
null, |
||||
|
Settings.MenuTag.SECTION_ROOT |
||||
|
) |
||||
|
binding.root.findNavController().navigate(action) |
||||
|
} |
||||
|
) |
||||
|
) |
||||
|
add( |
||||
|
HomeSetting( |
||||
|
R.string.app_settings, |
||||
|
R.string.app_settings_description, |
||||
|
R.drawable.ic_palette, |
||||
|
{ |
||||
|
val action = HomeNavigationDirections.actionGlobalSettingsActivity( |
||||
|
null, |
||||
|
Settings.MenuTag.SECTION_APP_SETTINGS |
||||
|
) |
||||
|
binding.root.findNavController().navigate(action) |
||||
|
} |
||||
|
) |
||||
|
) |
||||
|
add( |
||||
|
HomeSetting( |
||||
|
R.string.preferences_controls, |
||||
|
R.string.preferences_controls_description, |
||||
|
R.drawable.ic_controller, |
||||
|
{ |
||||
|
val action = HomeNavigationDirections.actionGlobalSettingsActivity( |
||||
|
null, |
||||
|
Settings.MenuTag.SECTION_INPUT |
||||
|
) |
||||
|
binding.root.findNavController().navigate(action) |
||||
|
} |
||||
|
) |
||||
|
) |
||||
|
add( |
||||
|
HomeSetting( |
||||
|
R.string.gpu_driver_manager, |
||||
|
R.string.install_gpu_driver_description, |
||||
|
R.drawable.ic_build, |
||||
|
{ |
||||
|
val action = HomeSettingsFragmentDirections |
||||
|
.actionHomeSettingsFragmentToDriverManagerFragment(null) |
||||
|
binding.root.findNavController().navigate(action) |
||||
|
}, |
||||
|
{ true }, |
||||
|
R.string.custom_driver_not_supported, |
||||
|
R.string.custom_driver_not_supported_description, |
||||
|
driverViewModel.selectedDriverTitle |
||||
|
) |
||||
|
) |
||||
|
add( |
||||
|
HomeSetting( |
||||
|
R.string.multiplayer, |
||||
|
R.string.multiplayer_description, |
||||
|
R.drawable.ic_two_users, |
||||
|
{ |
||||
|
mainActivity.displayMultiplayerDialog() |
||||
|
} |
||||
|
) |
||||
|
) |
||||
|
add( |
||||
|
HomeSetting( |
||||
|
R.string.applets, |
||||
|
R.string.applets_description, |
||||
|
R.drawable.ic_applet, |
||||
|
{ |
||||
|
binding.root.findNavController() |
||||
|
.navigate(R.id.action_homeSettingsFragment_to_appletLauncherFragment) |
||||
|
}, |
||||
|
{ NativeLibrary.isFirmwareAvailable() }, |
||||
|
R.string.applets_error_firmware, |
||||
|
R.string.applets_error_description |
||||
|
) |
||||
|
) |
||||
|
add( |
||||
|
HomeSetting( |
||||
|
R.string.manage_yuzu_data, |
||||
|
R.string.manage_yuzu_data_description, |
||||
|
R.drawable.ic_install, |
||||
|
{ |
||||
|
binding.root.findNavController() |
||||
|
.navigate(R.id.action_homeSettingsFragment_to_installableFragment) |
||||
|
} |
||||
|
) |
||||
|
) |
||||
|
add( |
||||
|
HomeSetting( |
||||
|
R.string.manage_game_folders, |
||||
|
R.string.select_games_folder_description, |
||||
|
R.drawable.ic_add, |
||||
|
{ |
||||
|
binding.root.findNavController() |
||||
|
.navigate(R.id.action_homeSettingsFragment_to_gameFoldersFragment) |
||||
|
} |
||||
|
) |
||||
|
) |
||||
|
add( |
||||
|
HomeSetting( |
||||
|
R.string.verify_installed_content, |
||||
|
R.string.verify_installed_content_description, |
||||
|
R.drawable.ic_check_circle, |
||||
|
{ |
||||
|
ProgressDialogFragment.newInstance( |
||||
|
requireActivity(), |
||||
|
titleId = R.string.verifying, |
||||
|
cancellable = true |
||||
|
) { progressCallback, _ -> |
||||
|
val result = NativeLibrary.verifyInstalledContents(progressCallback) |
||||
|
return@newInstance if (progressCallback.invoke(100, 100)) { |
||||
|
// Invoke the progress callback to check if the process was cancelled |
||||
|
MessageDialogFragment.newInstance( |
||||
|
titleId = R.string.verify_no_result, |
||||
|
descriptionId = R.string.verify_no_result_description |
||||
|
) |
||||
|
} else if (result.isEmpty()) { |
||||
|
MessageDialogFragment.newInstance( |
||||
|
titleId = R.string.verify_success, |
||||
|
descriptionId = R.string.operation_completed_successfully |
||||
|
) |
||||
|
} else { |
||||
|
val failedNames = result.joinToString("\n") |
||||
|
val errorMessage = YuzuApplication.appContext.getString( |
||||
|
R.string.verification_failed_for, |
||||
|
failedNames |
||||
|
) |
||||
|
MessageDialogFragment.newInstance( |
||||
|
titleId = R.string.verify_failure, |
||||
|
descriptionString = errorMessage |
||||
|
) |
||||
|
} |
||||
|
}.show(parentFragmentManager, ProgressDialogFragment.TAG) |
||||
|
} |
||||
|
) |
||||
|
) |
||||
|
add( |
||||
|
HomeSetting( |
||||
|
R.string.share_log, |
||||
|
R.string.share_log_description, |
||||
|
R.drawable.ic_log, |
||||
|
{ shareLog() } |
||||
|
) |
||||
|
) |
||||
|
add( |
||||
|
HomeSetting( |
||||
|
R.string.open_user_folder, |
||||
|
R.string.open_user_folder_description, |
||||
|
R.drawable.ic_folder_open, |
||||
|
{ openFileManager() } |
||||
|
) |
||||
|
) |
||||
|
add( |
||||
|
HomeSetting( |
||||
|
R.string.about, |
||||
|
R.string.about_description, |
||||
|
R.drawable.ic_info_outline, |
||||
|
{ |
||||
|
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) |
||||
|
parentFragmentManager.primaryNavigationFragment?.findNavController() |
||||
|
?.navigate(R.id.action_homeSettingsFragment_to_aboutFragment) |
||||
|
} |
||||
|
) |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
binding.homeSettingsList.apply { |
||||
|
layoutManager = |
||||
|
GridLayoutManager(requireContext(), resources.getInteger(R.integer.grid_columns)) |
||||
|
adapter = HomeSettingAdapter( |
||||
|
requireActivity() as AppCompatActivity, |
||||
|
viewLifecycleOwner, |
||||
|
optionsList |
||||
|
) { |
||||
|
// Filter button click callback |
||||
|
shareLogWithFilter(true) |
||||
|
} |
||||
|
val spacing = resources.getDimensionPixelSize(R.dimen.spacing_small) |
||||
|
addItemDecoration(SpacingItemDecoration(spacing)) |
||||
|
} |
||||
|
|
||||
|
setInsets() |
||||
|
} |
||||
|
|
||||
|
override fun onStart() { |
||||
|
super.onStart() |
||||
|
exitTransition = null |
||||
|
} |
||||
|
|
||||
|
override fun onResume() { |
||||
|
super.onResume() |
||||
|
driverViewModel.updateDriverNameForGame(null) |
||||
|
} |
||||
|
|
||||
|
override fun onDestroyView() { |
||||
|
super.onDestroyView() |
||||
|
_binding = null |
||||
|
} |
||||
|
|
||||
|
private fun openFileManager() { |
||||
|
// First, try to open the user data folder directly |
||||
|
try { |
||||
|
startActivity(getFileManagerIntentOnDocumentProvider(Intent.ACTION_VIEW)) |
||||
|
return |
||||
|
} catch (_: ActivityNotFoundException) { |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
startActivity(getFileManagerIntentOnDocumentProvider("android.provider.action.BROWSE")) |
||||
|
return |
||||
|
} catch (_: ActivityNotFoundException) { |
||||
|
} |
||||
|
|
||||
|
// Just try to open the file manager, try the package name used on "normal" phones |
||||
|
try { |
||||
|
startActivity(getFileManagerIntent("com.google.android.documentsui")) |
||||
|
showNoLinkNotification() |
||||
|
return |
||||
|
} catch (_: ActivityNotFoundException) { |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
// Next, try the AOSP package name |
||||
|
startActivity(getFileManagerIntent("com.android.documentsui")) |
||||
|
showNoLinkNotification() |
||||
|
return |
||||
|
} catch (_: ActivityNotFoundException) { |
||||
|
} |
||||
|
|
||||
|
Toast.makeText( |
||||
|
requireContext(), |
||||
|
resources.getString(R.string.no_file_manager), |
||||
|
Toast.LENGTH_LONG |
||||
|
).show() |
||||
|
} |
||||
|
|
||||
|
private fun getFileManagerIntent(packageName: String): Intent { |
||||
|
// Fragile, but some phones don't expose the system file manager in any better way |
||||
|
val intent = Intent(Intent.ACTION_MAIN) |
||||
|
intent.setClassName(packageName, "com.android.documentsui.files.FilesActivity") |
||||
|
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK |
||||
|
return intent |
||||
|
} |
||||
|
|
||||
|
private fun getFileManagerIntentOnDocumentProvider(action: String): Intent { |
||||
|
val authority = "${requireContext().packageName}.user" |
||||
|
val intent = Intent(action) |
||||
|
intent.addCategory(Intent.CATEGORY_DEFAULT) |
||||
|
intent.data = DocumentsContract.buildRootUri(authority, DocumentProvider.ROOT_ID) |
||||
|
intent.addFlags( |
||||
|
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or |
||||
|
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or |
||||
|
Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
||||
|
) |
||||
|
return intent |
||||
|
} |
||||
|
|
||||
|
private fun showNoLinkNotification() { |
||||
|
val builder = NotificationCompat.Builder( |
||||
|
requireContext(), |
||||
|
getString(R.string.notice_notification_channel_id) |
||||
|
) |
||||
|
.setSmallIcon(R.drawable.ic_stat_notification_logo) |
||||
|
.setContentTitle(getString(R.string.notification_no_directory_link)) |
||||
|
.setContentText(getString(R.string.notification_no_directory_link_description)) |
||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH) |
||||
|
.setAutoCancel(true) |
||||
|
// TODO: Make the click action for this notification lead to a help article |
||||
|
|
||||
|
with(NotificationManagerCompat.from(requireContext())) { |
||||
|
if (ActivityCompat.checkSelfPermission( |
||||
|
requireContext(), |
||||
|
Manifest.permission.POST_NOTIFICATIONS |
||||
|
) != PackageManager.PERMISSION_GRANTED |
||||
|
) { |
||||
|
Toast.makeText( |
||||
|
requireContext(), |
||||
|
resources.getString(R.string.notification_permission_not_granted), |
||||
|
Toast.LENGTH_LONG |
||||
|
).show() |
||||
|
return |
||||
|
} |
||||
|
notify(0, builder.build()) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Share the current log if we just returned from a game but share the old log |
||||
|
// if we just started the app and the old log exists. |
||||
|
private fun shareLog() { |
||||
|
shareLogWithFilter(false) |
||||
|
} |
||||
|
|
||||
|
private fun shareLogWithFilter(filter: Boolean) { |
||||
|
val currentLog = DocumentFile.fromSingleUri( |
||||
|
mainActivity, |
||||
|
DocumentsContract.buildDocumentUri( |
||||
|
DocumentProvider.AUTHORITY, |
||||
|
"${DocumentProvider.ROOT_ID}/log/eden_log.txt" |
||||
|
) |
||||
|
)!! |
||||
|
val oldLog = DocumentFile.fromSingleUri( |
||||
|
mainActivity, |
||||
|
DocumentsContract.buildDocumentUri( |
||||
|
DocumentProvider.AUTHORITY, |
||||
|
"${DocumentProvider.ROOT_ID}/log/eden_log.txt.old.txt" |
||||
|
) |
||||
|
)!! |
||||
|
|
||||
|
// Determine which log to use |
||||
|
val logToShare = if (!Log.gameLaunched && oldLog.exists()) { |
||||
|
oldLog.uri |
||||
|
} else if (currentLog.exists()) { |
||||
|
currentLog.uri |
||||
|
} else { |
||||
|
null |
||||
|
} |
||||
|
|
||||
|
if (logToShare == null) { |
||||
|
Toast.makeText( |
||||
|
requireContext(), |
||||
|
getText(R.string.share_log_missing), |
||||
|
Toast.LENGTH_SHORT |
||||
|
).show() |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Process the log and share it |
||||
|
processAndShareLog(logToShare, filter) |
||||
|
} |
||||
|
|
||||
|
private fun processAndShareLog(logUri: Uri, filter: Boolean) { |
||||
|
val shareUri = if (filter) { |
||||
|
// Create filtered log file first |
||||
|
val result = LogFilter.createFilteredLogFile(requireContext(), logUri) |
||||
|
if (result != null) { |
||||
|
val (filteredFile, filteredUri) = result |
||||
|
val filterSuccess = LogFilter.filterLogs(requireContext(), logUri, filteredFile) |
||||
|
if (filterSuccess) { |
||||
|
Toast.makeText( |
||||
|
requireContext(), |
||||
|
"Filtered log created successfully", |
||||
|
Toast.LENGTH_SHORT |
||||
|
).show() |
||||
|
filteredUri |
||||
|
} else { |
||||
|
Toast.makeText( |
||||
|
requireContext(), |
||||
|
"Failed to filter log, sharing original", |
||||
|
Toast.LENGTH_SHORT |
||||
|
).show() |
||||
|
Log.error("[HomeSettingsFragment] Failed to filter log file") |
||||
|
logUri |
||||
|
} |
||||
|
} else { |
||||
|
Toast.makeText( |
||||
|
requireContext(), |
||||
|
"Failed to create filtered file, sharing original", |
||||
|
Toast.LENGTH_SHORT |
||||
|
).show() |
||||
|
Log.error("[HomeSettingsFragment] Failed to create filtered log file") |
||||
|
logUri |
||||
|
} |
||||
|
} else { |
||||
|
// Use original log without filtering |
||||
|
logUri |
||||
|
} |
||||
|
|
||||
|
// Share the log |
||||
|
val intent = Intent(Intent.ACTION_SEND) |
||||
|
.setDataAndType(shareUri, FileUtil.TEXT_PLAIN) |
||||
|
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) |
||||
|
.putExtra(Intent.EXTRA_STREAM, shareUri) |
||||
|
|
||||
|
startActivity(Intent.createChooser(intent, getText(R.string.share_log))) |
||||
|
} |
||||
|
|
||||
|
private fun setInsets() = |
||||
|
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets -> |
||||
|
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
||||
|
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
||||
|
|
||||
|
binding.scrollViewSettings.updatePadding( |
||||
|
top = barInsets.top |
||||
|
) |
||||
|
|
||||
|
binding.homeSettingsList.updatePadding( |
||||
|
left = barInsets.left + cutoutInsets.left, |
||||
|
top = cutoutInsets.top, |
||||
|
right = barInsets.right + cutoutInsets.right, |
||||
|
bottom = barInsets.bottom |
||||
|
) |
||||
|
|
||||
|
windowInsets |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,140 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
|
||||
|
package org.yuzu.yuzu_emu.utils |
||||
|
|
||||
|
import android.content.Context |
||||
|
import android.net.Uri |
||||
|
import androidx.core.content.FileProvider |
||||
|
import androidx.documentfile.provider.DocumentFile |
||||
|
import java.io.BufferedReader |
||||
|
import java.io.File |
||||
|
import java.io.InputStreamReader |
||||
|
import java.io.OutputStreamWriter |
||||
|
import java.util.regex.Pattern |
||||
|
|
||||
|
object LogFilter { |
||||
|
|
||||
|
/** |
||||
|
* Filters log content to show only warnings, errors, and critical messages |
||||
|
* @param context Android context |
||||
|
* @param inputUri URI of the input log file |
||||
|
* @param outputUri URI of the output filtered log file |
||||
|
* @return true if filtering was successful, false otherwise |
||||
|
*/ |
||||
|
fun filterLogs(context: Context, inputUri: Uri, outputFile: File): Boolean { |
||||
|
return try { |
||||
|
val inputDocFile = DocumentFile.fromSingleUri(context, inputUri) |
||||
|
|
||||
|
if (inputDocFile == null || !inputDocFile.exists()) { |
||||
|
Log.error("[LogFilter] Input file does not exist: $inputUri") |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
Log.info("[LogFilter] Starting filtering from $inputUri to ${outputFile.absolutePath}") |
||||
|
|
||||
|
// Define the regex pattern for warnings, errors, and criticals |
||||
|
val pattern = Pattern.compile(".*<(?:Warning|Error|Critical)>.*", Pattern.CASE_INSENSITIVE) |
||||
|
|
||||
|
var filteredLines = 0 |
||||
|
var totalLines = 0 |
||||
|
|
||||
|
context.contentResolver.openInputStream(inputUri)?.use { inputStream -> |
||||
|
outputFile.outputStream().use { outputStream -> |
||||
|
val reader = BufferedReader(InputStreamReader(inputStream)) |
||||
|
val writer = OutputStreamWriter(outputStream) |
||||
|
|
||||
|
var line: String? |
||||
|
while (reader.readLine().also { line = it } != null) { |
||||
|
totalLines++ |
||||
|
if (pattern.matcher(line!!).matches()) { |
||||
|
writer.write(line) |
||||
|
writer.write("\n") |
||||
|
filteredLines++ |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
writer.flush() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Log.info("[LogFilter] Filtered $filteredLines lines out of $totalLines total lines") |
||||
|
Log.info("[LogFilter] Output file size: ${outputFile.length()} bytes") |
||||
|
true |
||||
|
} catch (e: Exception) { |
||||
|
Log.error("[LogFilter] Error filtering logs: ${e.message}") |
||||
|
e.printStackTrace() |
||||
|
false |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Gets the filtered log URI for a given log URI |
||||
|
* @param logUri Original log URI |
||||
|
* @return URI for the filtered log file |
||||
|
*/ |
||||
|
fun getFilteredLogUri(logUri: Uri): Uri { |
||||
|
val uriString = logUri.toString() |
||||
|
val filteredUriString = if (uriString.endsWith(".txt")) { |
||||
|
uriString.replace(".txt", "_filtered.txt") |
||||
|
} else { |
||||
|
"${uriString}_filtered" |
||||
|
} |
||||
|
return Uri.parse(filteredUriString) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Creates a filtered log file in the app's cache directory |
||||
|
* @param context Android context |
||||
|
* @param originalUri Original log file URI |
||||
|
* @return Pair of File and URI, or null if failed |
||||
|
*/ |
||||
|
fun createFilteredLogFile(context: Context, originalUri: Uri): Pair<File, Uri>? { |
||||
|
return try { |
||||
|
val originalFile = DocumentFile.fromSingleUri(context, originalUri) |
||||
|
if (originalFile == null || !originalFile.exists()) { |
||||
|
Log.error("[LogFilter] Original file does not exist: $originalUri") |
||||
|
return null |
||||
|
} |
||||
|
|
||||
|
// Create filtered file name |
||||
|
val originalName = originalFile.name ?: "eden_log.txt" |
||||
|
val filteredName = if (originalName.endsWith(".txt")) { |
||||
|
originalName.replace(".txt", "_filtered.txt") |
||||
|
} else { |
||||
|
"${originalName}_filtered" |
||||
|
} |
||||
|
|
||||
|
// Create file in app's cache directory |
||||
|
val cacheDir = File(context.cacheDir, "logs") |
||||
|
if (!cacheDir.exists()) { |
||||
|
cacheDir.mkdirs() |
||||
|
} |
||||
|
|
||||
|
val filteredFile = File(cacheDir, filteredName) |
||||
|
|
||||
|
// Delete existing file if it exists |
||||
|
if (filteredFile.exists()) { |
||||
|
filteredFile.delete() |
||||
|
} |
||||
|
|
||||
|
// Create the file |
||||
|
filteredFile.createNewFile() |
||||
|
|
||||
|
Log.info("[LogFilter] Created filtered file: ${filteredFile.absolutePath}") |
||||
|
|
||||
|
// Use FileProvider to get a content URI for the file |
||||
|
val uri = FileProvider.getUriForFile( |
||||
|
context, |
||||
|
"${context.packageName}.provider", |
||||
|
filteredFile |
||||
|
) |
||||
|
|
||||
|
Pair(filteredFile, uri) |
||||
|
} catch (e: Exception) { |
||||
|
Log.error("[LogFilter] Error creating filtered file: ${e.message}") |
||||
|
e.printStackTrace() |
||||
|
null |
||||
|
} |
||||
|
} |
||||
|
} |
||||
1584
src/android/android/app/src/main/res/values/strings.xml
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,23 @@ |
|||||
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
|
||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules. |
||||
|
plugins { |
||||
|
id("com.android.application") version "8.9.1" apply false |
||||
|
id("com.android.library") version "8.1.4" apply false |
||||
|
id("org.jetbrains.kotlin.android") version "1.9.20" apply false |
||||
|
} |
||||
|
|
||||
|
tasks.register("clean").configure { |
||||
|
delete(rootProject.buildDir) |
||||
|
} |
||||
|
|
||||
|
buildscript { |
||||
|
val agp_version by extra("8.9.1") |
||||
|
repositories { |
||||
|
google() |
||||
|
} |
||||
|
dependencies { |
||||
|
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.8.9") |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
# SPDX-FileCopyrightText: Copyright 2025 yuzu Emulator Project |
||||
|
# SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
|
||||
|
# 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=-Xms512m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 |
||||
|
android.useAndroidX=true |
||||
|
# Kotlin code style for this project: "official" or "obsolete": |
||||
|
kotlin.code.style=official |
||||
|
kotlin.parallel.tasks.in.project=true |
||||
|
# Android Gradle plugin 8.0.2 |
||||
|
android.suppressUnsupportedCompileSdk=34 |
||||
|
android.native.buildOutput=verbose |
||||
@ -0,0 +1,6 @@ |
|||||
|
#Sun Feb 21 18:16:59 EST 2021 |
||||
|
distributionBase=GRADLE_USER_HOME |
||||
|
distributionPath=wrapper/dists |
||||
|
zipStoreBase=GRADLE_USER_HOME |
||||
|
zipStorePath=wrapper/dists |
||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip |
||||
@ -0,0 +1,175 @@ |
|||||
|
#!/usr/bin/env sh |
||||
|
|
||||
|
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project |
||||
|
# SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
|
||||
|
############################################################################## |
||||
|
## |
||||
|
## 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="" |
||||
|
|
||||
|
# 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, switch paths to Windows format before running java |
||||
|
if $cygwin ; 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=$((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" |
||||
|
|
||||
|
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong |
||||
|
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then |
||||
|
cd "$(dirname "$0")" |
||||
|
fi |
||||
|
|
||||
|
exec "$JAVACMD" "$@" |
||||
@ -0,0 +1,87 @@ |
|||||
|
@rem SPDX-FileCopyrightText: 2023 yuzu Emulator Project |
||||
|
@rem SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
|
||||
|
@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 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= |
||||
|
|
||||
|
@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 init |
||||
|
|
||||
|
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 init |
||||
|
|
||||
|
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 |
||||
|
|
||||
|
:init |
||||
|
@rem Get command-line arguments, handling Windows variants |
||||
|
|
||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args |
||||
|
|
||||
|
:win9xME_args |
||||
|
@rem Slurp the command line arguments. |
||||
|
set CMD_LINE_ARGS= |
||||
|
set _SKIP=2 |
||||
|
|
||||
|
:win9xME_args_slurp |
||||
|
if "x%~1" == "x" goto execute |
||||
|
|
||||
|
set CMD_LINE_ARGS=%* |
||||
|
|
||||
|
: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 %CMD_LINE_ARGS% |
||||
|
|
||||
|
: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 |
||||
@ -0,0 +1,21 @@ |
|||||
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
|
||||
|
pluginManagement { |
||||
|
repositories { |
||||
|
gradlePluginPortal() |
||||
|
google() |
||||
|
mavenCentral() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Suppress("UnstableApiUsage") |
||||
|
dependencyResolutionManagement { |
||||
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) |
||||
|
repositories { |
||||
|
google() |
||||
|
mavenCentral() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
include(":app") |
||||
@ -0,0 +1,9 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<!-- |
||||
|
SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project |
||||
|
SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
--> |
||||
|
<paths xmlns:android="http://schemas.android.com/apk/res/android"> |
||||
|
<cache-path name="logs" path="logs/" /> |
||||
|
<external-cache-path name="external_logs" path="logs/" /> |
||||
|
</paths> |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue