Compare commits
1
Commits
nimble_cut_poc
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
947ba44245 |
@@ -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"),
|
||||||
|
|||||||
+14
@@ -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
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
+13
@@ -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,61 +1,115 @@
|
|||||||
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
|
||||||
|
|
||||||
var searchValue by remember{ mutableStateOf("") }
|
val navigator = LocalNavigator.current
|
||||||
|
|
||||||
val fsElementModel = FsElementModel(
|
var searchValue by remember { mutableStateOf("") }
|
||||||
name = "my_txt.txt",
|
|
||||||
type = ElementType.isHidden,
|
|
||||||
size = 123,
|
|
||||||
date = Date()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
val homeScreenModel = rememberScreenModel { HomeScreenModel(context) }
|
||||||
|
|
||||||
|
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(
|
||||||
FsElement(
|
modifier = Modifier.padding(horizontal = 20.dp)
|
||||||
fsElementEntity = fsElementModel,
|
) {
|
||||||
onClick = { /*TODO*/ },
|
|
||||||
onLongPressClick = {
|
StatusBarInCurFolder(
|
||||||
bottomSheetNavigator.show(
|
path = currentFolder,
|
||||||
FileContextMenuScreen(
|
externalPath = context.getExternalAppDataDirectory(),
|
||||||
fsElementModel = fsElementModel,
|
internalPath = context.getInternalAppDataDirectory()
|
||||||
onRemoveFile = {}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(
|
||||||
|
fsElementEntity = it,
|
||||||
|
onClick = { homeScreenModel.enterFolder(it) },
|
||||||
|
onLongPressClick = {
|
||||||
|
bottomSheetNavigator.show(FileContextMenuScreen(
|
||||||
|
fsElementModel = it,
|
||||||
|
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 = {
|
||||||
|
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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,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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
*/
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
//Основная ссылка на начало основного процесса - libapp.so
|
||||||
|
extern uintptr_t libapp;
|
||||||
|
|
||||||
|
//Макрос для ссылок внутри памяти процесса
|
||||||
|
#define APP_ADDR(addr) (libapp + (addr))
|
||||||
@@ -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());
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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...);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user