Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c075162f2 | ||
|
|
947ba44245 |
@@ -47,6 +47,7 @@ android {
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +97,7 @@ dependencies {
|
||||
implementation(libs.ui.graphics)
|
||||
implementation(libs.ui.tooling.preview)
|
||||
implementation(libs.material3)
|
||||
implementation(project(mapOf("path" to ":mpcore")))
|
||||
testImplementation(libs.junit.v412)
|
||||
//testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.test.ext.junit)
|
||||
@@ -104,7 +106,7 @@ dependencies {
|
||||
androidTestImplementation(libs.ui.test.junit4)
|
||||
debugImplementation(libs.ui.tooling)
|
||||
debugImplementation(libs.ui.test.manifest)
|
||||
implementation(project(mapOf("path" to ":devmenu")))
|
||||
//implementation(project(mapOf("path" to ":devmenu")))
|
||||
|
||||
implementation ("commons-codec:commons-codec:1.15")
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.ea.EAMIO.StorageDirectory
|
||||
import com.ea.easp.EASPHandler
|
||||
import com.ea.games.nfs13_na.BuildConfig
|
||||
import com.ea.nimble.ApplicationLifecycle
|
||||
import ru.megboyzz.mpcore.NativeLib
|
||||
import java.io.BufferedReader
|
||||
import java.io.File
|
||||
import java.io.FileReader
|
||||
@@ -42,6 +43,7 @@ class GameActivity : ComponentActivity(), DrawFrameListener {
|
||||
init {
|
||||
System.loadLibrary("nimble")
|
||||
System.loadLibrary("app")
|
||||
System.loadLibrary("mpcore")
|
||||
}
|
||||
|
||||
private var splashDelay: Long = 0
|
||||
|
||||
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
package com.megboyzz.devmenu.domain.entities
|
||||
|
||||
enum class GameLang(private val lang: String) {
|
||||
enum class GameLang(val lang: String) {
|
||||
System("sys"),
|
||||
Chinese("cn"),
|
||||
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 cafe.adriel.voyager.navigator.Navigator
|
||||
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.screens.HomeScreen
|
||||
|
||||
@@ -39,7 +40,9 @@ class MainActivity : ComponentActivity() {
|
||||
BottomSheetNavigator(
|
||||
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 com.megboyzz.devmenu.di.modules.DatabaseModule
|
||||
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.SetGameLangUseCase
|
||||
import com.megboyzz.devmenu.domain.usecases.settings.SetTrackingSaveFileUseCase
|
||||
@@ -24,8 +26,12 @@ interface SettingsUseCaseComponent {
|
||||
|
||||
val setGameLangUseCase: SetGameLangUseCase
|
||||
|
||||
val getGameLangUseCase: GetGameLangUseCase
|
||||
|
||||
val setTrackingSaveFileUseCase: SetTrackingSaveFileUseCase
|
||||
|
||||
val getTrackingStateUseCase: GetTrackingStateUseCase
|
||||
|
||||
val turnOffDevMenuUseCase: TurnOffDevMenuUseCase
|
||||
|
||||
@Component.Builder
|
||||
|
||||
@@ -29,7 +29,7 @@ class SettingsRepositoryImpl @Inject constructor(
|
||||
|
||||
if(!localeFile.exists()){
|
||||
localeFile.createNewFile()
|
||||
localeFile.writeBytes(byteArrayOf(0x02, 0x0) + systemLang.name.toByteArray())
|
||||
localeFile.writeBytes(byteArrayOf(0x02, 0x0) + systemLang.lang.toByteArray())
|
||||
return systemLang
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ class SettingsRepositoryImpl @Inject constructor(
|
||||
|
||||
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
|
||||
get() {
|
||||
val enabled = sharedPreferences.getBoolean(trackingStateKey, false)
|
||||
val path = sharedPreferences.getString(trackingStateKey, defaultPathToSave)?: defaultPathToSave
|
||||
val path = sharedPreferences.getString(trackingSavePath, defaultPathToSave)?: defaultPathToSave
|
||||
|
||||
return TrackingData(
|
||||
enabled = enabled,
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
package com.megboyzz.devmenu.ui
|
||||
|
||||
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.height
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import com.megboyzz.devmenu.MainActivity
|
||||
import java.text.CharacterIterator
|
||||
import java.text.SimpleDateFormat
|
||||
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 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>
|
||||
* например есть функция
|
||||
*
|
||||
@@ -62,5 +99,4 @@ fun formatDate(date: Date) = SimpleDateFormat("dd.MM.yyyy HH:mm:ss").format(date
|
||||
*
|
||||
* example(a = SomeType().asComposableLambda)
|
||||
*/
|
||||
val <T> T.asComposableLambda: @Composable () -> T
|
||||
get() = { this }
|
||||
val <T> T.asComposableLambda: @Composable () -> T get() = { this }
|
||||
@@ -6,6 +6,7 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.megboyzz.devmenu.ui.models.FsElementModel
|
||||
|
||||
@@ -13,7 +14,7 @@ import com.megboyzz.devmenu.ui.models.FsElementModel
|
||||
fun FsElement(
|
||||
fsElementEntity: FsElementModel,
|
||||
onClick: () -> Unit,
|
||||
onLongPressClick: () -> Unit
|
||||
onLongPressClick: () -> Unit = {}
|
||||
) {
|
||||
|
||||
Column(
|
||||
@@ -25,6 +26,9 @@ fun FsElement(
|
||||
)
|
||||
) {
|
||||
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
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
@@ -39,10 +38,20 @@ fun CoreBar(folders: List<String>) {
|
||||
|
||||
@Composable
|
||||
fun StatusBarInCurFolder(
|
||||
path: String
|
||||
path: String,
|
||||
internalPath: String = "",
|
||||
externalPath: String = ""
|
||||
) {
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
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.Row
|
||||
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.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.megboyzz.devmenu.ui.SpacerHeight
|
||||
import com.megboyzz.devmenu.ui.theme.DevMenuTheme
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -42,7 +45,7 @@ fun MainScaffold(
|
||||
searchValue: String,
|
||||
onSearchValueChanged: (String) -> Unit,
|
||||
onFloatingButtonClick: () -> Unit,
|
||||
content: @Composable (PaddingValues) -> Unit
|
||||
content: @Composable ColumnScope.() -> Unit
|
||||
) {
|
||||
|
||||
var searchValue by remember { mutableStateOf(searchValue) }
|
||||
@@ -66,11 +69,13 @@ fun MainScaffold(
|
||||
shape = CircleShape,
|
||||
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
|
||||
|
||||
import com.megboyzz.devmenu.domain.entities.GameLang
|
||||
|
||||
enum class GameLangModel(private val lang: String) {
|
||||
System("sys"),
|
||||
Chinese("cn"),
|
||||
@@ -16,4 +18,8 @@ enum class GameLangModel(private val lang: String) {
|
||||
|
||||
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 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.models.FsElementModel
|
||||
|
||||
class FileContextMenuScreen(
|
||||
private val fsElementModel: FsElementModel,
|
||||
private val onRemoveFile: () -> Unit,
|
||||
private val onUpdateType: (ElementType) -> ElementType?
|
||||
) : Screen {
|
||||
@Composable
|
||||
override fun Content() = FileContextMenu(
|
||||
fsElementEntity = fsElementModel,
|
||||
onRemoveElement = onRemoveFile,
|
||||
onUpdateType = {null}
|
||||
onUpdateType = onUpdateType
|
||||
)
|
||||
}
|
||||
@@ -1,61 +1,115 @@
|
||||
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.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.DisposableEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.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.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.bottomSheet.LocalBottomSheetNavigator
|
||||
import com.megboyzz.devmenu.MainActivity
|
||||
import com.megboyzz.devmenu.ui.SpacerHeight
|
||||
import com.megboyzz.devmenu.ui.components.ElementType
|
||||
import com.megboyzz.devmenu.ui.components.FsElement
|
||||
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.registerOnBackPressedAction
|
||||
import com.megboyzz.devmenu.ui.screenmodels.HomeScreenModel
|
||||
import java.util.Date
|
||||
|
||||
class HomeScreen(
|
||||
private val onFloatingButtonClick: () -> Unit
|
||||
): Screen {
|
||||
@SuppressLint("StateFlowValueCalledInComposition")
|
||||
@Composable
|
||||
override fun Content() {
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
val bottomSheetNavigator = LocalBottomSheetNavigator.current
|
||||
|
||||
var searchValue by remember{ mutableStateOf("") }
|
||||
val navigator = LocalNavigator.current
|
||||
|
||||
val fsElementModel = FsElementModel(
|
||||
name = "my_txt.txt",
|
||||
type = ElementType.isHidden,
|
||||
size = 123,
|
||||
date = Date()
|
||||
)
|
||||
var searchValue by remember { mutableStateOf("") }
|
||||
|
||||
val homeScreenModel = rememberScreenModel { HomeScreenModel(context) }
|
||||
|
||||
val fsState by homeScreenModel.folderFiles.collectAsState()
|
||||
|
||||
val currentFolder by homeScreenModel.currentFolder.collectAsState()
|
||||
|
||||
context.registerOnBackPressedAction {
|
||||
homeScreenModel.backFolder()
|
||||
}
|
||||
|
||||
MainScaffold(
|
||||
onSettingsClick = { /*TODO Переход к настройкам */ },
|
||||
onSettingsClick = { navigator?.push(SettingsScreen()) },
|
||||
onSearchValueChanged = { searchValue = it },
|
||||
searchValue = searchValue,
|
||||
onFloatingButtonClick = onFloatingButtonClick
|
||||
) {
|
||||
Column {
|
||||
SpacerHeight(height = it.calculateTopPadding())
|
||||
FsElement(
|
||||
fsElementEntity = fsElementModel,
|
||||
onClick = { /*TODO*/ },
|
||||
onLongPressClick = {
|
||||
bottomSheetNavigator.show(
|
||||
FileContextMenuScreen(
|
||||
fsElementModel = fsElementModel,
|
||||
onRemoveFile = {}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
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(
|
||||
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
|
||||
|
||||
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.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 {
|
||||
@Composable
|
||||
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 = {
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
#Wed Nov 08 15:56:25 MSK 2023
|
||||
#Sun Mar 16 20:04:17 MSK 2025
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -6,11 +6,15 @@ plugins {
|
||||
|
||||
android {
|
||||
namespace = "ru.megboyzz.mpcore"
|
||||
compileSdk = 33
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
|
||||
ndk {
|
||||
abiFilters.add("armeabi-v7a")
|
||||
}
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
externalNativeBuild {
|
||||
|
||||
@@ -3,7 +3,9 @@ cmake_minimum_required(VERSION 3.22.1)
|
||||
project("mpcore")
|
||||
|
||||
add_library(${CMAKE_PROJECT_NAME} SHARED
|
||||
mpcore.cpp)
|
||||
main.cpp
|
||||
util/util.cpp
|
||||
util/armhook.cpp)
|
||||
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME}
|
||||
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 {
|
||||
|
||||
external fun stringFromJNI(): String
|
||||
|
||||
companion object {
|
||||
init {
|
||||
System.loadLibrary("mpcore")
|
||||
//System.loadLibrary("mpcore")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+2
-2
@@ -21,6 +21,6 @@ dependencyResolutionManagement {
|
||||
|
||||
rootProject.name = "NFS Most Wanted"
|
||||
include(":app")
|
||||
include(":devmenu")
|
||||
include(":devmenu:domain")
|
||||
//include(":devmenu")
|
||||
//include(":devmenu:domain")
|
||||
include(":mpcore")
|
||||
|
||||
Reference in New Issue
Block a user