1 Commits
Author SHA1 Message Date
megboyzz 947ba44245 comeback commit! 2024-12-15 21:21:44 +03:00
30 changed files with 793 additions and 57 deletions
+1
View File
@@ -96,6 +96,7 @@ dependencies {
implementation(libs.ui.graphics) implementation(libs.ui.graphics)
implementation(libs.ui.tooling.preview) implementation(libs.ui.tooling.preview)
implementation(libs.material3) implementation(libs.material3)
implementation(project(mapOf("path" to ":mpcore")))
testImplementation(libs.junit.v412) testImplementation(libs.junit.v412)
//testImplementation(libs.junit) //testImplementation(libs.junit)
androidTestImplementation(libs.androidx.test.ext.junit) androidTestImplementation(libs.androidx.test.ext.junit)
@@ -24,6 +24,7 @@ import com.ea.EAMIO.StorageDirectory
import com.ea.easp.EASPHandler import com.ea.easp.EASPHandler
import com.ea.games.nfs13_na.BuildConfig import com.ea.games.nfs13_na.BuildConfig
import com.ea.nimble.ApplicationLifecycle import com.ea.nimble.ApplicationLifecycle
import ru.megboyzz.mpcore.NativeLib
import java.io.BufferedReader import java.io.BufferedReader
import java.io.File import java.io.File
import java.io.FileReader import java.io.FileReader
@@ -42,6 +43,7 @@ class GameActivity : ComponentActivity(), DrawFrameListener {
init { init {
System.loadLibrary("nimble") System.loadLibrary("nimble")
System.loadLibrary("app") System.loadLibrary("app")
System.loadLibrary("mpcore")
} }
private var splashDelay: Long = 0 private var splashDelay: Long = 0
Binary file not shown.
@@ -1,6 +1,6 @@
package com.megboyzz.devmenu.domain.entities package com.megboyzz.devmenu.domain.entities
enum class GameLang(private val lang: String) { enum class GameLang(val lang: String) {
System("sys"), System("sys"),
Chinese("cn"), Chinese("cn"),
Dutch("nl"), Dutch("nl"),
@@ -0,0 +1,14 @@
package com.megboyzz.devmenu.domain.usecases.settings
import com.megboyzz.devmenu.domain.entities.GameLang
import com.megboyzz.devmenu.domain.repository.SettingsRepository
import javax.inject.Inject
class GetGameLangUseCase @Inject constructor(
private val settingsRepository: SettingsRepository
) {
operator fun invoke() = settingsRepository.gameLang
}
@@ -0,0 +1,13 @@
package com.megboyzz.devmenu.domain.usecases.settings
import com.megboyzz.devmenu.domain.entities.TrackingData
import com.megboyzz.devmenu.domain.repository.TrackingRepository
import javax.inject.Inject
class GetTrackingStateUseCase @Inject constructor(
private val trackingRepository: TrackingRepository
) {
operator fun invoke() = trackingRepository.trackingData
}
@@ -8,6 +8,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.bottomSheet.BottomSheetNavigator import cafe.adriel.voyager.navigator.bottomSheet.BottomSheetNavigator
import cafe.adriel.voyager.transitions.SlideTransition
import com.megboyzz.devmenu.ui.components.MainScaffold import com.megboyzz.devmenu.ui.components.MainScaffold
import com.megboyzz.devmenu.ui.screens.HomeScreen import com.megboyzz.devmenu.ui.screens.HomeScreen
@@ -39,7 +40,9 @@ class MainActivity : ComponentActivity() {
BottomSheetNavigator( BottomSheetNavigator(
sheetShape = RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp) sheetShape = RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp)
) { ) {
Navigator(HomeScreen(::runGame)) Navigator(HomeScreen(::runGame)) {
SlideTransition(it)
}
} }
} }
} }
@@ -3,6 +3,8 @@ package com.megboyzz.devmenu.di.components
import android.content.Context import android.content.Context
import com.megboyzz.devmenu.di.modules.DatabaseModule import com.megboyzz.devmenu.di.modules.DatabaseModule
import com.megboyzz.devmenu.di.modules.RepositoryModule import com.megboyzz.devmenu.di.modules.RepositoryModule
import com.megboyzz.devmenu.domain.usecases.settings.GetGameLangUseCase
import com.megboyzz.devmenu.domain.usecases.settings.GetTrackingStateUseCase
import com.megboyzz.devmenu.domain.usecases.settings.RemoveGameDataUseCase import com.megboyzz.devmenu.domain.usecases.settings.RemoveGameDataUseCase
import com.megboyzz.devmenu.domain.usecases.settings.SetGameLangUseCase import com.megboyzz.devmenu.domain.usecases.settings.SetGameLangUseCase
import com.megboyzz.devmenu.domain.usecases.settings.SetTrackingSaveFileUseCase import com.megboyzz.devmenu.domain.usecases.settings.SetTrackingSaveFileUseCase
@@ -24,8 +26,12 @@ interface SettingsUseCaseComponent {
val setGameLangUseCase: SetGameLangUseCase val setGameLangUseCase: SetGameLangUseCase
val getGameLangUseCase: GetGameLangUseCase
val setTrackingSaveFileUseCase: SetTrackingSaveFileUseCase val setTrackingSaveFileUseCase: SetTrackingSaveFileUseCase
val getTrackingStateUseCase: GetTrackingStateUseCase
val turnOffDevMenuUseCase: TurnOffDevMenuUseCase val turnOffDevMenuUseCase: TurnOffDevMenuUseCase
@Component.Builder @Component.Builder
@@ -29,7 +29,7 @@ class SettingsRepositoryImpl @Inject constructor(
if(!localeFile.exists()){ if(!localeFile.exists()){
localeFile.createNewFile() localeFile.createNewFile()
localeFile.writeBytes(byteArrayOf(0x02, 0x0) + systemLang.name.toByteArray()) localeFile.writeBytes(byteArrayOf(0x02, 0x0) + systemLang.lang.toByteArray())
return systemLang return systemLang
} }
@@ -40,7 +40,7 @@ class SettingsRepositoryImpl @Inject constructor(
if(!localeFile.exists()) localeFile.createNewFile() if(!localeFile.exists()) localeFile.createNewFile()
localeFile.writeBytes(byteArrayOf(0x02, 0x0) + value.name.toByteArray()) localeFile.writeBytes(byteArrayOf(0x02, 0x0) + value.lang.toByteArray())
} }
@@ -29,7 +29,7 @@ class TrackingRepositoryImpl @Inject constructor(
override var trackingData: TrackingData override var trackingData: TrackingData
get() { get() {
val enabled = sharedPreferences.getBoolean(trackingStateKey, false) val enabled = sharedPreferences.getBoolean(trackingStateKey, false)
val path = sharedPreferences.getString(trackingStateKey, defaultPathToSave)?: defaultPathToSave val path = sharedPreferences.getString(trackingSavePath, defaultPathToSave)?: defaultPathToSave
return TrackingData( return TrackingData(
enabled = enabled, enabled = enabled,
@@ -1,14 +1,19 @@
package com.megboyzz.devmenu.ui package com.megboyzz.devmenu.ui
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context
import androidx.activity.ComponentActivity
import androidx.activity.OnBackPressedCallback
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import com.megboyzz.devmenu.MainActivity
import java.text.CharacterIterator import java.text.CharacterIterator
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.text.StringCharacterIterator import java.text.StringCharacterIterator
@@ -46,7 +51,39 @@ fun humanReadableByteCountSI(bytes: Long): String {
fun formatDate(date: Date) = SimpleDateFormat("dd.MM.yyyy HH:mm:ss").format(date) fun formatDate(date: Date) = SimpleDateFormat("dd.MM.yyyy HH:mm:ss").format(date)
fun Context.getInternalAppDataDirectory() = "/data/data/${packageName}"
fun Context.getExternalAppDataDirectory(): String {
val externalDir = getExternalFilesDir(null)
return externalDir?.absolutePath ?: ""
}
@Composable
fun Context.registerOnBackPressedAction(
action: () -> Unit
) {
val context = this
DisposableEffect(this) {
val callback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
action()
}
}
(context as ComponentActivity).onBackPressedDispatcher.addCallback(callback)
// Отменяем обратный вызов при уничтожении DisposableEffect
onDispose {
callback.remove()
}
}
}
/** /**
* ## Неработает((
*
* Возвращает лямбду того типа которая требуется в Composable контексте<br> * Возвращает лямбду того типа которая требуется в Composable контексте<br>
* например есть функция * например есть функция
* *
@@ -62,5 +99,4 @@ fun formatDate(date: Date) = SimpleDateFormat("dd.MM.yyyy HH:mm:ss").format(date
* *
* example(a = SomeType().asComposableLambda) * example(a = SomeType().asComposableLambda)
*/ */
val <T> T.asComposableLambda: @Composable () -> T val <T> T.asComposableLambda: @Composable () -> T get() = { this }
get() = { this }
@@ -6,6 +6,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.megboyzz.devmenu.ui.models.FsElementModel import com.megboyzz.devmenu.ui.models.FsElementModel
@@ -13,7 +14,7 @@ import com.megboyzz.devmenu.ui.models.FsElementModel
fun FsElement( fun FsElement(
fsElementEntity: FsElementModel, fsElementEntity: FsElementModel,
onClick: () -> Unit, onClick: () -> Unit,
onLongPressClick: () -> Unit onLongPressClick: () -> Unit = {}
) { ) {
Column( Column(
@@ -25,6 +26,9 @@ fun FsElement(
) )
) { ) {
FsElementIcon(type = fsElementEntity.type) FsElementIcon(type = fsElementEntity.type)
Text(text = fsElementEntity.name) Text(
text = fsElementEntity.name,
textAlign = TextAlign.Center
)
} }
} }
@@ -1,7 +1,6 @@
package com.megboyzz.devmenu.ui.components package com.megboyzz.devmenu.ui.components
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
@@ -39,10 +38,20 @@ fun CoreBar(folders: List<String>) {
@Composable @Composable
fun StatusBarInCurFolder( fun StatusBarInCurFolder(
path: String path: String,
internalPath: String = "",
externalPath: String = ""
) { ) {
if(path.isEmpty()) return if(path.isEmpty()) return
val pathAsArray = path.split("/").filter { it.isNotEmpty() } var pathProcess = path
if(internalPath.isNotEmpty() && pathProcess.contains(internalPath))
pathProcess = path.replace(internalPath, "Внутренее хранилище")
if(externalPath.isNotEmpty() && pathProcess.contains(externalPath))
pathProcess = path.replace(externalPath, "Внешнее хранилище")
val pathAsArray = pathProcess.split("/").filter { it.isNotEmpty() }
CoreBar(folders = pathAsArray) CoreBar(folders = pathAsArray)
} }
@@ -4,6 +4,8 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
@@ -33,6 +35,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.megboyzz.devmenu.ui.SpacerHeight
import com.megboyzz.devmenu.ui.theme.DevMenuTheme import com.megboyzz.devmenu.ui.theme.DevMenuTheme
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -42,7 +45,7 @@ fun MainScaffold(
searchValue: String, searchValue: String,
onSearchValueChanged: (String) -> Unit, onSearchValueChanged: (String) -> Unit,
onFloatingButtonClick: () -> Unit, onFloatingButtonClick: () -> Unit,
content: @Composable (PaddingValues) -> Unit content: @Composable ColumnScope.() -> Unit
) { ) {
var searchValue by remember { mutableStateOf(searchValue) } var searchValue by remember { mutableStateOf(searchValue) }
@@ -66,11 +69,13 @@ fun MainScaffold(
shape = CircleShape, shape = CircleShape,
content = {Icon(imageVector = Icons.Default.PlayArrow, contentDescription = null)} content = {Icon(imageVector = Icons.Default.PlayArrow, contentDescription = null)}
) )
}, }
){
content = content Column {
SpacerHeight(height = it.calculateTopPadding())
) content()
}
}
} }
@@ -1,5 +1,7 @@
package com.megboyzz.devmenu.ui.models package com.megboyzz.devmenu.ui.models
import com.megboyzz.devmenu.domain.entities.GameLang
enum class GameLangModel(private val lang: String) { enum class GameLangModel(private val lang: String) {
System("sys"), System("sys"),
Chinese("cn"), Chinese("cn"),
@@ -16,4 +18,8 @@ enum class GameLangModel(private val lang: String) {
override fun toString() = lang override fun toString() = lang
fun toDomain() = GameLang.values()[ordinal]
} }
fun GameLang.toModel() = GameLangModel.values()[ordinal]
@@ -0,0 +1,125 @@
package com.megboyzz.devmenu.ui.screenmodels
import android.content.Context
import android.util.Log
import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import com.megboyzz.devmenu.ui.components.ElementType
import com.megboyzz.devmenu.ui.getExternalAppDataDirectory
import com.megboyzz.devmenu.ui.getInternalAppDataDirectory
import com.megboyzz.devmenu.ui.models.FsElementModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import java.io.File
import java.util.Date
class HomeScreenModel(
private val context: Context
): ScreenModel {
private val TAG = this::class.simpleName
private val internalRef = FsElementModel(
"Внутренне хранилище",
ElementType.isDir,
0,
Date()
)
private val externalRef = FsElementModel(
"Внешнее хранилище",
ElementType.isDir,
0,
Date()
)
private val rootFs = listOf(
internalRef,
externalRef
)
val currentFolder = MutableStateFlow("")
val folderFiles = MutableStateFlow(listOf(internalRef, externalRef))
private fun isRoot() = currentFolder.value.isEmpty()
private fun getElements(path: String): List<FsElementModel>{
val here = File(path)
return if(here.exists()){
here.listFiles()!!.map {
FsElementModel(
name = it.name,
type = if(it.isFile) ElementType.isFile else ElementType.isDir,
size = it.length(),
date = Date(it.lastModified())
)
}
}else listOf()
}
fun enterRoot(){
screenModelScope.launch(Dispatchers.IO) {
currentFolder.emit("")
folderFiles.emit(rootFs)
}
}
fun enterFolder(elementModel: FsElementModel) {
if(isRoot()){
when(elementModel){
externalRef -> enterTheExternal()
internalRef -> enterTheInternal()
else -> Log.e(TAG, "enterFolder: error path! ${elementModel.name}")
}
}else{
screenModelScope.launch(Dispatchers.IO) {
currentFolder.emit(currentFolder.value + "/" + elementModel.name)
folderFiles.emit(getElements(currentFolder.value))
}
}
}
fun backFolder(){
val path = File(currentFolder.value)
val internal = context.getInternalAppDataDirectory()
val external = context.getExternalAppDataDirectory()
if(path.exists()){
screenModelScope.launch(Dispatchers.IO) {
when(currentFolder.value){
context.getInternalAppDataDirectory(), context.getExternalAppDataDirectory() -> enterRoot()
else -> {
currentFolder.emit(path.parentFile.absolutePath)
folderFiles.emit(getElements(currentFolder.value))
}
}
}
}
}
private fun enterTheInternal(){
screenModelScope.launch(Dispatchers.IO) {
currentFolder.emit(context.getInternalAppDataDirectory())
folderFiles.emit(getElements(currentFolder.value))
}
}
private fun enterTheExternal(){
screenModelScope.launch(Dispatchers.IO) {
currentFolder.emit(context.getExternalAppDataDirectory())
folderFiles.emit(getElements(currentFolder.value))
}
}
}
@@ -0,0 +1,62 @@
package com.megboyzz.devmenu.ui.screenmodels
import android.content.Context
import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import com.megboyzz.devmenu.App
import com.megboyzz.devmenu.MainActivity
import com.megboyzz.devmenu.ui.models.GameLangModel
import com.megboyzz.devmenu.ui.models.toModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
class SettingsScreenModel(
context: Context
): ScreenModel {
private val settingsUseCaseComponent = (((context as MainActivity).application) as App).settingsUseCaseComponent
val gameLangModel = MutableStateFlow(settingsUseCaseComponent.getGameLangUseCase().toModel())
val trackingIsEnabled = MutableStateFlow(false)
init {
screenModelScope.launch(Dispatchers.IO) {
update()
}
}
private suspend fun update() {
gameLangModel.emit(settingsUseCaseComponent.getGameLangUseCase().toModel())
trackingIsEnabled.emit(settingsUseCaseComponent.getTrackingStateUseCase().enabled)
}
fun changeLang(gameLangModel: GameLangModel){
screenModelScope.launch(Dispatchers.IO) {
settingsUseCaseComponent.setGameLangUseCase(gameLangModel.toDomain())
update()
}
}
fun changeTrackingState(state: Boolean) {
screenModelScope.launch(Dispatchers.IO) {
val trackingData = settingsUseCaseComponent.getTrackingStateUseCase()
trackingData.copy(enabled = state)
settingsUseCaseComponent.setTrackingSaveFileUseCase(trackingData.enabled, trackingData.pathToSave)
update()
}
}
fun changeTrackingPath(path: String) {
screenModelScope.launch(Dispatchers.IO) {
val state = settingsUseCaseComponent.getTrackingStateUseCase()
state.copy(pathToSave = path)
settingsUseCaseComponent.setTrackingSaveFileUseCase(state.enabled, state.pathToSave)
update()
}
}
}
@@ -2,17 +2,19 @@ package com.megboyzz.devmenu.ui.screens
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.Screen
import com.megboyzz.devmenu.ui.components.ElementType
import com.megboyzz.devmenu.ui.components.FileContextMenu import com.megboyzz.devmenu.ui.components.FileContextMenu
import com.megboyzz.devmenu.ui.models.FsElementModel import com.megboyzz.devmenu.ui.models.FsElementModel
class FileContextMenuScreen( class FileContextMenuScreen(
private val fsElementModel: FsElementModel, private val fsElementModel: FsElementModel,
private val onRemoveFile: () -> Unit, private val onRemoveFile: () -> Unit,
private val onUpdateType: (ElementType) -> ElementType?
) : Screen { ) : Screen {
@Composable @Composable
override fun Content() = FileContextMenu( override fun Content() = FileContextMenu(
fsElementEntity = fsElementModel, fsElementEntity = fsElementModel,
onRemoveElement = onRemoveFile, onRemoveElement = onRemoveFile,
onUpdateType = {null} onUpdateType = onUpdateType
) )
} }
@@ -1,60 +1,114 @@
package com.megboyzz.devmenu.ui.screens package com.megboyzz.devmenu.ui.screens
import android.annotation.SuppressLint
import androidx.activity.OnBackPressedCallback
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.bottomSheet.LocalBottomSheetNavigator import cafe.adriel.voyager.navigator.bottomSheet.LocalBottomSheetNavigator
import com.megboyzz.devmenu.MainActivity
import com.megboyzz.devmenu.ui.SpacerHeight import com.megboyzz.devmenu.ui.SpacerHeight
import com.megboyzz.devmenu.ui.components.ElementType import com.megboyzz.devmenu.ui.components.ElementType
import com.megboyzz.devmenu.ui.components.FsElement import com.megboyzz.devmenu.ui.components.FsElement
import com.megboyzz.devmenu.ui.components.MainScaffold import com.megboyzz.devmenu.ui.components.MainScaffold
import com.megboyzz.devmenu.ui.components.StatusBarInCurFolder
import com.megboyzz.devmenu.ui.getExternalAppDataDirectory
import com.megboyzz.devmenu.ui.getInternalAppDataDirectory
import com.megboyzz.devmenu.ui.models.FsElementModel import com.megboyzz.devmenu.ui.models.FsElementModel
import com.megboyzz.devmenu.ui.registerOnBackPressedAction
import com.megboyzz.devmenu.ui.screenmodels.HomeScreenModel
import java.util.Date import java.util.Date
class HomeScreen( class HomeScreen(
private val onFloatingButtonClick: () -> Unit private val onFloatingButtonClick: () -> Unit
): Screen { ): Screen {
@SuppressLint("StateFlowValueCalledInComposition")
@Composable @Composable
override fun Content() { override fun Content() {
val context = LocalContext.current
val bottomSheetNavigator = LocalBottomSheetNavigator.current val bottomSheetNavigator = LocalBottomSheetNavigator.current
val navigator = LocalNavigator.current
var searchValue by remember { mutableStateOf("") } var searchValue by remember { mutableStateOf("") }
val fsElementModel = FsElementModel( val homeScreenModel = rememberScreenModel { HomeScreenModel(context) }
name = "my_txt.txt",
type = ElementType.isHidden,
size = 123,
date = Date()
)
val fsState by homeScreenModel.folderFiles.collectAsState()
val currentFolder by homeScreenModel.currentFolder.collectAsState()
context.registerOnBackPressedAction {
homeScreenModel.backFolder()
}
MainScaffold( MainScaffold(
onSettingsClick = { /*TODO Переход к настройкам */ }, onSettingsClick = { navigator?.push(SettingsScreen()) },
onSearchValueChanged = { searchValue = it }, onSearchValueChanged = { searchValue = it },
searchValue = searchValue, searchValue = searchValue,
onFloatingButtonClick = onFloatingButtonClick onFloatingButtonClick = onFloatingButtonClick
) { ) {
Column {
SpacerHeight(height = it.calculateTopPadding()) Column(
modifier = Modifier.padding(horizontal = 20.dp)
) {
StatusBarInCurFolder(
path = currentFolder,
externalPath = context.getExternalAppDataDirectory(),
internalPath = context.getInternalAppDataDirectory()
)
}
val filteredList by remember(searchValue, fsState) {
derivedStateOf {
fsState.filter { it.name.contains(searchValue, ignoreCase = true) }
}
}
LazyVerticalGrid(
columns = GridCells.Adaptive(80.dp),
horizontalArrangement = Arrangement.spacedBy(40.dp),
verticalArrangement = Arrangement.spacedBy(40.dp),
contentPadding = PaddingValues(20.dp)
){
items(filteredList){
FsElement( FsElement(
fsElementEntity = fsElementModel, fsElementEntity = it,
onClick = { /*TODO*/ }, onClick = { homeScreenModel.enterFolder(it) },
onLongPressClick = { onLongPressClick = {
bottomSheetNavigator.show( bottomSheetNavigator.show(FileContextMenuScreen(
FileContextMenuScreen( fsElementModel = it,
fsElementModel = fsElementModel, onRemoveFile = {},
onRemoveFile = {} onUpdateType = {null}
) ))
)
} }
) )
} }
}
} }
@@ -1,11 +1,41 @@
package com.megboyzz.devmenu.ui.screens package com.megboyzz.devmenu.ui.screens
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
import com.megboyzz.devmenu.ui.components.SettingsScreenContent
import com.megboyzz.devmenu.ui.models.GameLangModel
import com.megboyzz.devmenu.ui.screenmodels.SettingsScreenModel
class SettingsScreen : Screen { class SettingsScreen : Screen {
@Composable @Composable
override fun Content() { override fun Content() {
TODO("Not yet implemented")
val navigator = LocalNavigator.current
val context = LocalContext.current
val settingsScreenModel = rememberScreenModel { SettingsScreenModel(context) }
val curLang by settingsScreenModel.gameLangModel.collectAsState()
val trackingEnabled by settingsScreenModel.trackingIsEnabled.collectAsState()
SettingsScreenContent(
onBackClick = { navigator?.pop() },
langModel = curLang,
onUpdateLanguage = { settingsScreenModel.changeLang(it) },
isTrackingSaveFile = trackingEnabled,
onUpdateTrackingSaveFileState = { settingsScreenModel.changeTrackingState(it) },
onChooseTrackingPathToSave = { },
onUploadSaveFile = { /*TODO*/ },
onDownloadSaveFile = { /*TODO*/ },
onDevMenuOff = {
})
} }
} }
+5 -1
View File
@@ -6,11 +6,15 @@ plugins {
android { android {
namespace = "ru.megboyzz.mpcore" namespace = "ru.megboyzz.mpcore"
compileSdk = 33 compileSdk = 34
defaultConfig { defaultConfig {
minSdk = 21 minSdk = 21
ndk {
abiFilters.add("armeabi-v7a")
}
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro") consumerProguardFiles("consumer-rules.pro")
externalNativeBuild { externalNativeBuild {
+3 -1
View File
@@ -3,7 +3,9 @@ cmake_minimum_required(VERSION 3.22.1)
project("mpcore") project("mpcore")
add_library(${CMAKE_PROJECT_NAME} SHARED add_library(${CMAKE_PROJECT_NAME} SHARED
mpcore.cpp) main.cpp
util/util.cpp
util/armhook.cpp)
target_link_libraries(${CMAKE_PROJECT_NAME} target_link_libraries(${CMAKE_PROJECT_NAME}
android android
+73
View File
@@ -0,0 +1,73 @@
#include <cstring>
#include <cstdio>
#include <android/log.h>
#include <jni.h>
#include <thread>
#include <chrono>
#include "main.h"
#include "util/util.h"
#include "util/armhook.h"
uintptr_t libapp = 0;
int (*print_internal_log)(int a1, const char *a2, ...);
void pending_thread(){
Log("Hello from thread!");
std::this_thread::sleep_for(std::chrono::milliseconds(10000));
int unk = APP_ADDR(0x00E4B8EC);
print_internal_log(unk, "Hello!");
unProtect(APP_ADDR(0x00E4A738));
}
jint JNI_OnLoad(JavaVM *vm, void *reserved){
libapp = FindLibrary("libapp.so");
Log("Hello! my base addr is 0x%08x", libapp);
//InitHookStuff();
//unProtect(APP_ADDR(0x00A522C4));
//*(int *)APP_ADDR(0x00A522C4) = 0xE320F000;
//WriteMemory(APP_ADDR(0x00E4BA94), (uintptr_t)"my_tag", 6);
raise(SIGSTOP);
return JNI_VERSION_1_6;
}
/*
Log("Hello! my base addr is 0x%08x", libapp);
Log("Address i wanna to get 0x%08x", APP_ADDR(0xC8C9D8));
const char* str = (char *)APP_ADDR(0xC8C9D8);
Log("i found it! %s", str);
unProtect(APP_ADDR(0xC8C9D8));
WriteMemory(APP_ADDR(0xC8C9D8), (uintptr_t)"APPLICATION_NO", 14);
str = (char *)APP_ADDR(0xC8C9D8);
Log("i change it! %s", str);
//makeNOP(APP_ADDR(0x00652EB4), 2);
//makeNOP(APP_ADDR(0x00652E3C), 2);
unProtect(APP_ADDR(0x00652EB4));
*(char *)APP_ADDR(0x00652EB4) = 0xE320F000;
unProtect(APP_ADDR(0x00652E3C));
*(char *)APP_ADDR(0x00652E3C) = 0xE320F000;
*/
+6
View File
@@ -0,0 +1,6 @@
//Основная ссылка на начало основного процесса - libapp.so
extern uintptr_t libapp;
//Макрос для ссылок внутри памяти процесса
#define APP_ADDR(addr) (libapp + (addr))
-10
View File
@@ -1,10 +0,0 @@
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_ru_megboyzz_mpcore_NativeLib_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
+198
View File
@@ -0,0 +1,198 @@
#include <unistd.h> // system api
#include <assert.h> // assert()
#include <dlfcn.h> // dlopen
#include <cerrno>
#include <cstring>
#include <exception>
#include "armhook.h"
#include "util.h"
#include "../main.h"
uintptr_t mmap_start = 0;
uintptr_t mmap_end = 0;
uintptr_t memlib_start = 0;
uintptr_t memlib_end = 0;
//#define PAGE_SIZE 0x8124
#define HOOK_PROC "\x01\xB4\x01\xB4\x01\x48\x01\x90\x01\xBD\x00\xBF\x00\x00\x00\x00"
void unProtect(uintptr_t ptr, size_t dwSize)
{
if(mprotect(reinterpret_cast<void*>(ptr & 0xFFFFF000), PAGE_SIZE, PROT_WRITE | PROT_READ | PROT_EXEC) == -1)
{
const auto err = errno;
if(err == EACCES)
{
mprotect(reinterpret_cast<void*>(ptr & 0xFFFFF000), PAGE_SIZE, PROT_WRITE | PROT_READ);
return;
}
}
}
void makeNOP(uintptr_t addr, unsigned int count)
{
Log("Hello from makeNOP!");
unProtect(addr);
for(uintptr_t ptr = addr; ptr != (addr+(count * 2)); ptr += 2)
{
Log("Hello from loop makeNOP!");
*(char*)ptr = 0x00;
*(char*)(ptr+1) = 0x46;
}
cacheflush(addr, (uintptr_t)(addr + count * 2), 0);
}
void WriteMemory(uintptr_t dest, uintptr_t src, size_t size)
{
unProtect(dest);
memcpy((void*)dest, (void*)src, size);
cacheflush(dest, dest+size, 0);
}
void WriteMemory(uintptr_t dest, void* src, size_t size)
{
unProtect(dest);
memcpy((void*)dest, src, size);
cacheflush(dest, dest+size, 0);
}
void SetPointer(uintptr_t addr, void* value)
{
WriteMemory(addr, &value, sizeof(value));
}
void ReadMemory(uintptr_t dest, uintptr_t src, size_t size)
{
unProtect(src);
memcpy((void*)dest, (void*)src, size);
}
void InitHookStuff()
{
memlib_start = APP_ADDR(0x000A1B44);
memlib_end = memlib_start + 0x1A36;
mmap_start = (uintptr_t)mmap(0, PAGE_SIZE, PROT_WRITE | PROT_READ | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
int code = mprotect((void*)(mmap_start & 0xFFFFF000), PAGE_SIZE, PROT_READ | PROT_EXEC | PROT_WRITE);
mmap_end = (mmap_start + PAGE_SIZE);
/*const auto err = errno;
Log("Status is %d && %d", err, code);
Log("mmap_stat = 0x%08x", mmap_start);
Log("mmap_end = 0x%08x", mmap_end);
Log("memlib_start = 0x%08x", memlib_start);
Log("memlib_end = 0x%08x", memlib_end);*/
//unProtect(APP_ADDR(0x1800AC));
}
void makeJMP(uintptr_t func, uintptr_t addr)
{
uint32_t code = ((addr - func - 4) >> 12) & 0x7FF | 0xF000 | ((((addr - func - 4) >> 1) & 0x7FF | 0xB800) << 16);
WriteMemory(func, (uintptr_t)&code, 4);
}
void makeBLX(uintptr_t func, uintptr_t addr)
{
uint32_t code = ((addr - func - 4) >> 12) & 0x7FF | ((addr - func - 4) << 15) | 0xF800F000;
WriteMemory(func, (uintptr_t)&code, 4);
}
void WriteHookProc(uintptr_t addr, uintptr_t func)
{
char code[16];
memcpy(code, HOOK_PROC, 16);
*(uint32_t*)&code[12] = (func | 1);
WriteMemory(addr, (uintptr_t)code, 16);
}
void InstallBLXHook(uintptr_t addr, uintptr_t func)
{
// memlib_start = 0
// memlib_end = 10
if(memlib_end < (memlib_start + 0x10) || mmap_end < (mmap_start + 0x20))
{
std::terminate();
}
makeBLX(addr, func);
WriteHookProc(addr, func);
memlib_start += 16;
}
void InstallJMPHook(uintptr_t addr, uintptr_t func)
{
if(memlib_end < (memlib_start + 0x10) || mmap_end < (mmap_start + 0x20))
{
std::terminate();
}
makeJMP(addr, func);
WriteHookProc(addr, func);
memlib_start += 16;
}
void InstallHook(uintptr_t addr, uintptr_t func, uintptr_t *orig)
{
if(memlib_end < (memlib_start + 0x10) || mmap_end < (mmap_start + 0x20))
{
Log("FAILED!");
std::terminate();
}
ReadMemory(mmap_start, addr, 4);
WriteHookProc(mmap_start+4, addr+4);
*orig = mmap_start+1;
mmap_start += 32;
makeJMP(addr, memlib_start);
WriteHookProc(memlib_start, func);
memlib_start += 16;
}
void RedirectCall(uintptr_t addr, uintptr_t func)
{
makeJMP(addr, memlib_start);
WriteHookProc(memlib_start, func);
memlib_start += 16;
}
void InstallMethodHook(uintptr_t addr, uintptr_t func)
{
unProtect(addr);
*(uintptr_t*)addr = func;
}
void CodeInject(uintptr_t addr, uintptr_t func, int reg)
{
char injectCode[12];
injectCode[0] = 0x01;
injectCode[1] = 0xA0 + reg;
injectCode[2] = (0x08 * reg) + reg;
injectCode[3] = 0x68;
injectCode[4] = 0x87 + (0x08 * reg);
injectCode[5] = 0x46;
injectCode[6] = injectCode[4];
injectCode[7] = injectCode[5];
*(uintptr_t*)&injectCode[8] = func;
WriteMemory(addr, (uintptr_t)injectCode, 12);
}
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#include <sys/mman.h>
extern uintptr_t mmap_start;
extern uintptr_t mmap_end;
extern uintptr_t memlib_start;
extern uintptr_t memlib_end;
void unProtect(uintptr_t ptr, size_t dwSize = 100);
void WriteMemory(uintptr_t dest, uintptr_t src, size_t size);
void ReadMemory(uintptr_t dest, uintptr_t src, size_t size);
void makeNOP(uintptr_t addr, unsigned int count);
void makeJMP(uintptr_t func, uintptr_t addr);
void makeBLX(uintptr_t func, uintptr_t addr);
void InitHookStuff();
void InstallHook(uintptr_t addr, uintptr_t func, uintptr_t *orig);
void InstallMethodHook(uintptr_t addr, uintptr_t func);
void InstallJMPHook(uintptr_t addr, uintptr_t func);
void InstallBLXHook(uintptr_t addr, uintptr_t func);
void CodeInject(uintptr_t addr, uintptr_t func, int reg);
void WriteMemory(uintptr_t dest, void* src, size_t size);
void SetPointer(uintptr_t addr, void* value);
template <typename Ret, typename... Args>
static Ret CallFunction(unsigned int address, Args... args)
{
return reinterpret_cast<Ret(__cdecl *)(Args...)>(address)(args...);
}
+52
View File
@@ -0,0 +1,52 @@
#include <cstring>
#include <unistd.h>
#include <cstdlib>
#include <android/log.h>
#include "util.h"
uintptr_t FindLibrary(const char *lib_name) {
char filename[0xFF] = {0},
buffer[2048] = {0};
FILE *fp = 0;
uintptr_t address = 0;
sprintf(filename, "/proc/%d/maps", getpid());
fp = fopen(filename, "rt");
if (fp == nullptr) {
Log("ERROR: can't open file %s", filename);
goto done;
}
while (fgets(buffer, sizeof(buffer), fp)) {
if (strstr(buffer, lib_name)) {
address = (uintptr_t) strtoul(buffer, 0, 16);
break;
}
}
done:
if (fp)
fclose(fp);
return address;
}
void Log(const char *fmt, ...) {
char buffer[0xFF];
static FILE *flLog = nullptr;
memset(buffer, 0, sizeof(buffer));
va_list arg;
va_start(arg, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, arg);
va_end(arg);
__android_log_write(ANDROID_LOG_INFO, "mpcore_log", buffer);
if (flLog == nullptr) return;
fprintf(flLog, "%s\n", buffer);
fflush(flLog);
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
uintptr_t FindLibrary(const char* lib_name);
void Log(const char *fmt, ...);
@@ -2,11 +2,10 @@ package ru.megboyzz.mpcore
class NativeLib { class NativeLib {
external fun stringFromJNI(): String
companion object { companion object {
init { init {
System.loadLibrary("mpcore") //System.loadLibrary("mpcore")
} }
} }
} }