Compare commits

..

10 Commits

Author SHA1 Message Date
Kavish Devar 6ac6700be6 android: format l2c_fcr_hook.cpp 2026-04-23 18:45:00 +05:30
Kavish Devar 113ee0a966 android: fallback to .dynsym when .gnu_debugdata fails 2026-04-23 18:45:00 +05:30
Kavish Devar d82e4e2427 android: bump version 2026-04-23 18:45:00 +05:30
Kavish Devar 481d5f13cf android: fix automatically pausing when media changes without vendorid hook enabled 2026-04-23 18:45:00 +05:30
Kavish Devar ef221af505 android: bump version 2026-04-23 18:45:00 +05:30
Kavish Devar c19190f031 android: fix convo detect not restoring volume when in Transparency mode 2026-04-23 18:45:00 +05:30
Kavish Devar d0b8574c68 android: hide disconnect when not wearing config on play builds 2026-04-23 18:45:00 +05:30
Kavish Devar 294d733e71 android: add 'required xposed' text to vendorid config toggle 2026-04-23 18:45:00 +05:30
Kavish Devar f6d7e97796 android: show price in buy button 2026-04-23 18:45:00 +05:30
Kavish Devar ae174bc9ea android: add confirmation step for unsupported devices 2026-04-23 18:45:00 +05:30
8 changed files with 199 additions and 176 deletions
+2 -2
View File
@@ -28,8 +28,8 @@ android {
applicationId = "me.kavishdevar.librepods"
minSdk = 33
targetSdk = 37
versionCode = 28
versionName = "0.2.0"
versionCode = 33
versionName = "0.2.2"
}
buildTypes {
release {
@@ -128,6 +128,7 @@ import me.kavishdevar.librepods.billing.BillingProviderFactory
import me.kavishdevar.librepods.data.AirPodsNotifications
import me.kavishdevar.librepods.data.ControlCommandRepository
import me.kavishdevar.librepods.presentation.components.ConfirmationDialog
import me.kavishdevar.librepods.presentation.components.StyledButton
import me.kavishdevar.librepods.presentation.components.StyledIconButton
import me.kavishdevar.librepods.presentation.screens.AccessibilitySettingsScreen
import me.kavishdevar.librepods.presentation.screens.AdaptiveStrengthScreen
@@ -221,20 +222,11 @@ class MainActivity : ComponentActivity() {
fun Main() {
val context = LocalContext.current
val sharedPreferences = context.getSharedPreferences("settings", MODE_PRIVATE)
if (false) { // !isSupported(sharedPreferences) && BuildConfig.PLAY_BUILD == true) {
if (!isSupported(sharedPreferences)) {
val showDialog = remember { mutableStateOf(false) }
val blockTouches = remember { mutableStateOf(false) }
val tapCount = remember { mutableIntStateOf(0) }
val lastTapTime = remember { mutableLongStateOf(0L) }
val hazeState = rememberHazeState()
LaunchedEffect(blockTouches) {
if (blockTouches.value) {
delay(500)
blockTouches.value = false
}
}
Box(
modifier = Modifier
.fillMaxSize()
@@ -245,26 +237,12 @@ fun Main() {
Box (
modifier = Modifier
.fillMaxSize()
.then(
if (blockTouches.value)
{
Modifier.pointerInput(Unit) {
awaitPointerEventScope {
while (true) {
val event = awaitPointerEvent(PointerEventPass.Initial)
event.changes.forEach { it.consume() }
}
}
}
}
else Modifier
)
)
Column (
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "Not supported",
text = stringResource(R.string.not_supported),
style = TextStyle(
fontFamily = FontFamily(Font(R.font.sf_pro)),
fontWeight = FontWeight.SemiBold,
@@ -275,25 +253,7 @@ fun Main() {
modifier = Modifier.fillMaxWidth()
)
Row (
modifier = Modifier.fillMaxWidth().pointerInput(Unit) {
detectTapGestures(
onTap = {
val now = System.currentTimeMillis()
if (now - lastTapTime.longValue > 400) {
tapCount.intValue = 0
}
tapCount.intValue++
lastTapTime.longValue = now
if (tapCount.intValue >= 7) {
showDialog.value = true
blockTouches.value = true
}
}
)
},
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
Text(
@@ -323,7 +283,7 @@ fun Main() {
)
}
Text(
text = "Check the repository for more info.",
text = stringResource(R.string.check_the_repository_for_more_info),
style = TextStyle(
fontFamily = FontFamily(Font(R.font.sf_pro)),
fontWeight = FontWeight.Medium,
@@ -333,19 +293,35 @@ fun Main() {
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
StyledButton(
onClick = { showDialog.value = true },
backdrop = rememberLayerBackdrop(),
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Text(
text = stringResource(R.string.bypass_compatibility_check),
style = TextStyle(
fontFamily = FontFamily(Font(R.font.sf_pro)),
fontWeight = FontWeight.Medium,
color = if (isSystemInDarkTheme()) Color.White else Color.Black,
fontSize = 16.sp
),
)
}
}
}
ConfirmationDialog(
showDialog = showDialog,
title = "Confirm device check bypass?",
message = "Are you sure your device is supported with LibrePods?",
title = stringResource(R.string.bypass_compatibility_check),
message = stringResource(R.string.bypass_compatiblity_check_confirmation),
confirmText = "Yes",
dismissText = "No",
onConfirm = {
showDialog.value = false
sharedPreferences.edit {
tapCount.intValue = 0
putBoolean("bypass_device_check", true)
val intent = Intent(context, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
@@ -217,7 +217,7 @@ fun AppSettingsScreen(
}
Spacer(modifier = Modifier.height(16.dp))
if (BuildConfig.FLAVOR == "xposed") {
if (!BuildConfig.PLAY_BUILD) {
StyledToggle(
title = stringResource(R.string.ear_detection),
label = stringResource(R.string.disconnect_when_not_wearing),
@@ -367,10 +367,8 @@ fun AppSettingsScreen(
Spacer(modifier = Modifier.height(16.dp))
val restartBluetoothText = stringResource(R.string.found_offset_restart_bluetooth)
StyledToggle(
label = stringResource(R.string.act_as_an_apple_device),
description = stringResource(R.string.act_as_an_apple_device_description) + "\n" + stringResource(
R.string.requires_xposed
).replaceFirstChar { if (it.isLowerCase()) it.titlecase(getDefault()) else it.toString() },
label = stringResource(R.string.act_as_an_apple_device) + " (${stringResource(R.string.requires_xposed)})",
description = stringResource(R.string.act_as_an_apple_device_description),
checked = state.vendorIdHook,
onCheckedChange = { enabled ->
Toast.makeText(context, restartBluetoothText, Toast.LENGTH_SHORT).show()
@@ -459,7 +459,7 @@ fun PurchaseScreen(
tint = if (isSystemInDarkTheme()) Color(0xFF916100) else Color(0xFFE59900)
) {
Text(
stringResource(R.string.buy),
stringResource(R.string.buy_price, state.price),
style = TextStyle(
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
@@ -69,7 +69,14 @@ import androidx.annotation.RequiresApi
import androidx.annotation.RequiresPermission
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat.START_STICKY
import androidx.core.app.ServiceCompat.startForeground
import androidx.core.content.ContextCompat.RECEIVER_EXPORTED
import androidx.core.content.ContextCompat.getSystemService
import androidx.core.content.ContextCompat.registerReceiver
import androidx.core.content.ContextCompat.startActivity
import androidx.core.content.edit
import com.google.android.datatransport.runtime.scheduling.persistence.EventStoreModule_PackageNameFactory.packageName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -128,6 +135,7 @@ import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
import kotlin.jvm.java
private const val TAG = "AirPodsService"
@@ -913,7 +921,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
if (conversationAwarenessNotification.status == 1.toByte() || conversationAwarenessNotification.status == 2.toByte()) {
MediaController.startSpeaking()
} else if (conversationAwarenessNotification.status == 8.toByte() || conversationAwarenessNotification.status == 9.toByte()) {
} else if (conversationAwarenessNotification.status == 6.toByte() ||conversationAwarenessNotification.status == 8.toByte() || conversationAwarenessNotification.status == 9.toByte()) {
MediaController.stopSpeaking()
}
@@ -2434,16 +2442,13 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
)
otherDeviceTookOver = false
}
val ownsConnection = aacpManager.getControlCommandStatus(AACPManager.Companion.ControlCommandIdentifiers.OWNS_CONNECTION)?.value?.get(0)?.toInt()
Log.d(
TAG, "owns connection: ${
aacpManager.getControlCommandStatus(AACPManager.Companion.ControlCommandIdentifiers.OWNS_CONNECTION)?.value?.get(
0
)?.toInt()
}"
TAG, "owns connection: $ownsConnection"
)
if (!::socket.isInitialized) return
if (socket.isConnected) {
if (BuildConfig.FLAVOR != "xposed") {
if (!XposedRemotePrefProvider.create().getBoolean("vendor_id_hook", false) || ownsConnection == 0) {
Log.d(TAG, "not taking over, vendorid is probably not set to apple")
return
}
@@ -39,6 +39,5 @@ fun isSupported(sharedPreferences: SharedPreferences): Boolean {
} else if (isOppoOrOnePlus) {
return true
}
return if (BuildConfig.FLAVOR == "xposed") true
else sharedPreferences.getBoolean("bypass_device_check", false)
return sharedPreferences.getBoolean("bypass_device_check", false)
}
+5 -1
View File
@@ -211,7 +211,7 @@
<string name="listening_mode_adaptive_description">Dynamically adjust external noise</string>
<string name="listening_mode_noise_cancellation_description">Blocks out external sounds</string>
<string name="unlock_advanced_features">Unlock advanced features</string>
<string name="buy">Buy</string>
<string name="buy_price">Buy %s</string>
<string name="restore_purchases">Restore purchases</string>
<string name="ear_detection_description">Automatically stop playing audio when you take them off, and resume playback when you put them back on.</string>
<string name="battery">Battery</string>
@@ -236,4 +236,8 @@
<string name="yes">Yes</string>
<string name="settings">Settings</string>
<string name="requires_xposed">requires xposed</string>
<string name="bypass_compatibility_check">Bypass compatibility check</string>
<string name="bypass_compatiblity_check_confirmation">Are you sure your device is supported natively/you have Xposed module enabled?</string>
<string name="not_supported">Not supported</string>
<string name="check_the_repository_for_more_info">Check the repository for more info.</string>
</resources>
+151 -110
View File
@@ -30,7 +30,7 @@
#include "l2c_fcr_hook.h"
extern "C" {
#include "xz.h"
#include "xz.h"
}
#define LOG_TAG "LibrePodsHook"
@@ -39,13 +39,13 @@ extern "C" {
static HookFunType hook_func = nullptr;
static uint8_t (*original_l2c_fcr_chk_chan_modes)(void*) = nullptr;
static tBTA_STATUS (*original_BTA_DmSetLocalDiRecord)(
tSDP_DI_RECORD*, uint32_t*) = nullptr;
static uint8_t (*original_l2c_fcr_chk_chan_modes)(void *) = nullptr;
static tBTA_STATUS (*original_BTA_DmSetLocalDiRecord)(tSDP_DI_RECORD *, uint32_t *) = nullptr;
static std::atomic<bool> enableSdpHook(false);
uint8_t fake_l2c_fcr_chk_chan_modes(void* p_ccb) {
uint8_t fake_l2c_fcr_chk_chan_modes(void *p_ccb) {
LOGI("fake_l2c_fcr_chk_chan_modes called");
uint8_t orig = 0;
if (original_l2c_fcr_chk_chan_modes)
@@ -55,13 +55,13 @@ uint8_t fake_l2c_fcr_chk_chan_modes(void* p_ccb) {
return 1;
}
tBTA_STATUS fake_BTA_DmSetLocalDiRecord(
tSDP_DI_RECORD* p_device_info,
uint32_t* p_handle) {
tBTA_STATUS fake_BTA_DmSetLocalDiRecord(tSDP_DI_RECORD *p_device_info, uint32_t *p_handle) {
LOGI("fake_BTA_DmSetLocalDiRecord called");
if (original_BTA_DmSetLocalDiRecord && enableSdpHook.load(std::memory_order_relaxed)) original_BTA_DmSetLocalDiRecord(p_device_info, p_handle);
if (original_BTA_DmSetLocalDiRecord &&
enableSdpHook.load(std::memory_order_relaxed))
original_BTA_DmSetLocalDiRecord(p_device_info, p_handle);
LOGI("fake_BTA_DmSetLocalDiRecord: modifying vendor to 0x004C, vendor_id_source to 0x0001");
@@ -70,14 +70,15 @@ tBTA_STATUS fake_BTA_DmSetLocalDiRecord(
p_device_info->vendor_id_source = 0x0001;
}
LOGI("fake_BTA_DmSetLocalDiRecord: returning status %d", original_BTA_DmSetLocalDiRecord ? original_BTA_DmSetLocalDiRecord(p_device_info, p_handle) : BTA_FAILURE);
return original_BTA_DmSetLocalDiRecord ? original_BTA_DmSetLocalDiRecord(p_device_info, p_handle) : BTA_FAILURE;
LOGI("fake_BTA_DmSetLocalDiRecord: returning status %d",
original_BTA_DmSetLocalDiRecord ? original_BTA_DmSetLocalDiRecord(p_device_info, p_handle)
: BTA_FAILURE);
return original_BTA_DmSetLocalDiRecord ? original_BTA_DmSetLocalDiRecord(p_device_info,
p_handle)
: BTA_FAILURE;
}
static bool decompressXZ(
const uint8_t* input,
size_t input_size,
std::vector<uint8_t>& output) {
static bool decompressXZ(const uint8_t *input, size_t input_size, std::vector<uint8_t> &output) {
LOGI("decompressXZ called with input_size: %zu", input_size);
@@ -86,7 +87,7 @@ static bool decompressXZ(
xz_crc64_init();
#endif
struct xz_dec* dec = xz_dec_init(XZ_DYNALLOC, 64U << 20);
struct xz_dec *dec = xz_dec_init(XZ_DYNALLOC, 64U << 20);
if (!dec) {
LOGE("decompressXZ: xz_dec_init failed");
return false;
@@ -106,7 +107,8 @@ static bool decompressXZ(
LOGI("decompressXZ: entering decompression loop");
while (true) {
LOGI("decompressXZ: xz_dec_run iteration, buf.in_pos: %zu, buf.out_pos: %zu", buf.in_pos, buf.out_pos);
LOGI("decompressXZ: xz_dec_run iteration, buf.in_pos: %zu, buf.out_pos: %zu", buf.in_pos,
buf.out_pos);
enum xz_ret ret = xz_dec_run(dec, &buf);
LOGI("decompressXZ: xz_dec_run returned %d", ret);
@@ -135,10 +137,10 @@ static bool decompressXZ(
return true;
}
static bool getLibraryPath(const char* name, std::string& out) {
static bool getLibraryPath(const char *name, std::string &out) {
LOGI("getLibraryPath called with name: %s", name);
FILE* fp = fopen("/proc/self/maps", "r");
FILE *fp = fopen("/proc/self/maps", "r");
if (!fp) {
LOGE("getLibraryPath: fopen failed");
return false;
@@ -150,7 +152,7 @@ static bool getLibraryPath(const char* name, std::string& out) {
while (fgets(line, sizeof(line), fp)) {
if (strstr(line, name)) {
LOGI("getLibraryPath: found line containing %s", name);
char* path = strchr(line, '/');
char *path = strchr(line, '/');
if (path) {
out = path;
out.erase(out.find('\n'));
@@ -166,10 +168,10 @@ static bool getLibraryPath(const char* name, std::string& out) {
return false;
}
static uintptr_t getModuleBase(const char* name) {
static uintptr_t getModuleBase(const char *name) {
LOGI("getModuleBase called with name: %s", name);
FILE* fp = fopen("/proc/self/maps", "r");
FILE *fp = fopen("/proc/self/maps", "r");
if (!fp) {
LOGE("getModuleBase: fopen failed");
return 0;
@@ -192,26 +194,78 @@ static uintptr_t getModuleBase(const char* name) {
return base;
}
static uint64_t findSymbolOffset(
const std::vector<uint8_t>& elf,
const char* symbol_substring) {
static uint64_t
findSymbolOffsetDynsym(const std::vector<uint8_t> &elf, const char *symbol_substring) {
LOGI("findSymbolOffsetDynsym called with %s", symbol_substring);
auto *eh = reinterpret_cast<const Elf64_Ehdr *>(elf.data());
auto *shdr = reinterpret_cast<const Elf64_Shdr *>(
elf.data() + eh->e_shoff);
const char *shstr = reinterpret_cast<const char *>(
elf.data() + shdr[eh->e_shstrndx].sh_offset);
const Elf64_Shdr *dynsym = nullptr;
const Elf64_Shdr *dynstr = nullptr;
for (int i = 0; i < eh->e_shnum; ++i) {
const char *secname = shstr + shdr[i].sh_name;
if (!strcmp(secname, ".dynsym"))
dynsym = &shdr[i];
if (!strcmp(secname, ".dynstr"))
dynstr = &shdr[i];
}
if (!dynsym || !dynstr) {
LOGE("findSymbolOffsetDynsym: dynsym or dynstr not found");
return 0;
}
auto *symbols = reinterpret_cast<const Elf64_Sym *>(
elf.data() + dynsym->sh_offset);
const char *strings = reinterpret_cast<const char *>(
elf.data() + dynstr->sh_offset);
size_t count = dynsym->sh_size / sizeof(Elf64_Sym);
LOGI("findSymbolOffsetDynsym: scanning %zu symbols", count);
for (size_t i = 0; i < count; ++i) {
const char *name = strings + symbols[i].st_name;
if (strstr(name, symbol_substring) && ELF64_ST_TYPE(symbols[i].st_info) == STT_FUNC) {
LOGI("findSymbolOffsetDynsym: matched %s @ 0x%lx", name,
(unsigned long) symbols[i].st_value);
return symbols[i].st_value;
}
}
LOGI("findSymbolOffsetDynsym: no match for %s", symbol_substring);
return 0;
}
static uint64_t findSymbolOffset(const std::vector<uint8_t> &elf, const char *symbol_substring) {
LOGI("findSymbolOffset called with symbol_substring: %s", symbol_substring);
auto* eh = reinterpret_cast<const Elf64_Ehdr*>(elf.data());
auto* shdr = reinterpret_cast<const Elf64_Shdr*>(
auto *eh = reinterpret_cast<const Elf64_Ehdr *>(elf.data());
auto *shdr = reinterpret_cast<const Elf64_Shdr *>(
elf.data() + eh->e_shoff);
const char* shstr =
reinterpret_cast<const char*>(
elf.data() + shdr[eh->e_shstrndx].sh_offset);
const char *shstr = reinterpret_cast<const char *>(
elf.data() + shdr[eh->e_shstrndx].sh_offset);
const Elf64_Shdr* symtab = nullptr;
const Elf64_Shdr* strtab = nullptr;
const Elf64_Shdr *symtab = nullptr;
const Elf64_Shdr *strtab = nullptr;
LOGI("findSymbolOffset: parsing ELF sections");
for (int i = 0; i < eh->e_shnum; ++i) {
const char* secname = shstr + shdr[i].sh_name;
const char *secname = shstr + shdr[i].sh_name;
if (!strcmp(secname, ".symtab"))
symtab = &shdr[i];
if (!strcmp(secname, ".strtab"))
@@ -224,23 +278,22 @@ static uint64_t findSymbolOffset(
}
LOGI("findSymbolOffset: found symtab and strtab");
auto* symbols = reinterpret_cast<const Elf64_Sym*>(
auto *symbols = reinterpret_cast<const Elf64_Sym *>(
elf.data() + symtab->sh_offset);
const char* strings =
reinterpret_cast<const char*>(
elf.data() + strtab->sh_offset);
const char *strings = reinterpret_cast<const char *>(
elf.data() + strtab->sh_offset);
size_t count = symtab->sh_size / sizeof(Elf64_Sym);
LOGI("findSymbolOffset: scanning %zu symbols", count);
for (size_t i = 0; i < count; ++i) {
const char* name = strings + symbols[i].st_name;
const char *name = strings + symbols[i].st_name;
if (strstr(name, symbol_substring) &&
ELF64_ST_TYPE(symbols[i].st_info) == STT_FUNC) {
if (strstr(name, symbol_substring) && ELF64_ST_TYPE(symbols[i].st_info) == STT_FUNC) {
LOGI("findSymbolOffset: matched symbol %s at 0x%lx", name, (unsigned long)symbols[i].st_value);
LOGI("findSymbolOffset: matched symbol %s at 0x%lx", name,
(unsigned long) symbols[i].st_value);
return symbols[i].st_value;
}
@@ -250,7 +303,7 @@ static uint64_t findSymbolOffset(
return 0;
}
static bool hookLibrary(const char* libname) {
static bool hookLibrary(const char *libname) {
LOGI("hookLibrary called with libname: %s", libname);
if (!hook_func) {
@@ -277,89 +330,78 @@ static bool hookLibrary(const char* libname) {
close(fd);
return false;
}
LOGI("hookLibrary: opened file, size: %lld", (long long)st.st_size);
LOGI("hookLibrary: opened file, size: %lld", (long long) st.st_size);
std::vector<uint8_t> file(st.st_size);
read(fd, file.data(), st.st_size);
close(fd);
auto* eh = reinterpret_cast<Elf64_Ehdr*>(file.data());
auto* shdr = reinterpret_cast<Elf64_Shdr*>(
auto *eh = reinterpret_cast<Elf64_Ehdr *>(file.data());
auto *shdr = reinterpret_cast<Elf64_Shdr *>(
file.data() + eh->e_shoff);
const char* shstr =
reinterpret_cast<const char*>(
file.data() + shdr[eh->e_shstrndx].sh_offset);
const char *shstr = reinterpret_cast<const char *>(
file.data() + shdr[eh->e_shstrndx].sh_offset);
uint64_t chk_offset = 0;
uint64_t sdp_offset = 0;
LOGI("hookLibrary: parsing ELF header and sections");
for (int i = 0; i < eh->e_shnum; ++i) {
if (!strcmp(shstr + shdr[i].sh_name, ".gnu_debugdata")) {
LOGI("hookLibrary: found .gnu_debugdata section");
std::vector<uint8_t> compressed(
file.begin() + shdr[i].sh_offset,
file.begin() + shdr[i].sh_offset + shdr[i].sh_size);
std::vector<uint8_t> compressed(file.begin() + shdr[i].sh_offset,
file.begin() + shdr[i].sh_offset + shdr[i].sh_size);
std::vector<uint8_t> decompressed;
if (!decompressXZ(
compressed.data(),
compressed.size(),
decompressed)) {
LOGE("hookLibrary: decompressXZ failed");
return false;
}
LOGI("hookLibrary: decompressed debug data, size: %zu", decompressed.size());
if (decompressXZ(compressed.data(), compressed.size(), decompressed)) {
uintptr_t base = getModuleBase(libname);
if (!base) {
LOGE("hookLibrary: getModuleBase failed");
return false;
}
LOGI("hookLibrary: module base: 0x%lx", base);
chk_offset = findSymbolOffset(decompressed, "l2c_fcr_chk_chan_modes");
uint64_t chk_offset =
findSymbolOffset(decompressed,
"l2c_fcr_chk_chan_modes");
uint64_t sdp_offset =
findSymbolOffset(decompressed,
"BTA_DmSetLocalDiRecord");
LOGI("hookLibrary: chk_offset: 0x%lx, sdp_offset: 0x%lx", chk_offset, sdp_offset);
if (chk_offset) {
void* target =
reinterpret_cast<void*>(base + chk_offset);
hook_func(target,
(void*)fake_l2c_fcr_chk_chan_modes,
(void**)&original_l2c_fcr_chk_chan_modes);
LOGI("hookLibrary: hooked l2c_fcr_chk_chan_modes");
sdp_offset = findSymbolOffset(decompressed, "BTA_DmSetLocalDiRecord");
} else {
LOGE("debugdata decompress failed");
}
if (sdp_offset) {
void* target =
reinterpret_cast<void*>(base + sdp_offset);
hook_func(target,
(void*)fake_BTA_DmSetLocalDiRecord,
(void**)&original_BTA_DmSetLocalDiRecord);
LOGI("hookLibrary: hooked BTA_DmSetLocalDiRecord");
}
return true;
break;
}
}
LOGI("hookLibrary: failed for %s", libname);
return false;
if (!chk_offset) {
LOGI("fallback dynsym chk");
chk_offset = findSymbolOffsetDynsym(file, "l2c_fcr_chk_chan_modes");
}
if (!sdp_offset) {
LOGI("fallback dynsym sdp");
sdp_offset = findSymbolOffsetDynsym(file, "BTA_DmSetLocalDiRecord");
}
uintptr_t base = getModuleBase(libname);
if (!base) {
LOGE("hookLibrary: getModuleBase failed");
return false;
}
if (chk_offset) {
void *target = reinterpret_cast<void *>(base + chk_offset);
hook_func(target, (void *) fake_l2c_fcr_chk_chan_modes,
(void **) &original_l2c_fcr_chk_chan_modes);
LOGI("hooked chk");
}
if (sdp_offset) {
void *target = reinterpret_cast<void *>(base + sdp_offset);
hook_func(target, (void *) fake_BTA_DmSetLocalDiRecord,
(void **) &original_BTA_DmSetLocalDiRecord);
LOGI("hooked sdp");
}
return chk_offset || sdp_offset;
}
static void on_library_loaded(const char* name, void*) {
static void on_library_loaded(const char *name, void *) {
LOGI("on_library_loaded called with name: %s", name);
if (strstr(name, "libbluetooth_jni.so")) {
@@ -373,20 +415,19 @@ static void on_library_loaded(const char* name, void*) {
}
}
extern "C"
[[gnu::visibility("default")]]
extern "C" [[gnu::visibility("default")]]
[[gnu::used]]
NativeOnModuleLoaded native_init(const NativeAPIEntries* entries) {
NativeOnModuleLoaded native_init(const NativeAPIEntries *entries) {
LOGI("native_init called with entries: %p", entries);
hook_func = (HookFunType)entries->hook_func;
LOGI("LibrePodsNativeHook initialized, sdp hook enabled: %d", enableSdpHook.load(std::memory_order_relaxed));
hook_func = (HookFunType) entries->hook_func;
LOGI("LibrePodsNativeHook initialized, sdp hook enabled: %d",
enableSdpHook.load(std::memory_order_relaxed));
return on_library_loaded;
}
extern "C"
JNIEXPORT void JNICALL
Java_me_kavishdevar_librepods_utils_NativeBridge_setSdpHook(
JNIEnv*, jobject thiz, jboolean enable) {
extern "C" JNIEXPORT void JNICALL
Java_me_kavishdevar_librepods_utils_NativeBridge_setSdpHook(JNIEnv *, jobject thiz,
jboolean enable) {
LOGI("setSdpHook called with enable: %d", enable);
enableSdpHook.store(enable, std::memory_order_relaxed);