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