Skip to content
Open

apK #20

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
9b58c20
Add GitHub Actions workflow to build APK
saikyoupurochita-dot Aug 18, 2026
423f2de
Add GitHub Actions workflow for building APK
saikyoupurochita-dot Aug 18, 2026
a9a8c08
Add GitHub Actions workflow to build APK
saikyoupurochita-dot Aug 18, 2026
309e22a
Delete .github/build.yml
saikyoupurochita-dot Aug 18, 2026
6639010
Add files via upload
saikyoupurochita-dot Aug 18, 2026
feac119
Add files via upload
saikyoupurochita-dot Aug 18, 2026
b7b34f1
Add files via upload
saikyoupurochita-dot Aug 18, 2026
c47b4ff
Add files via upload
saikyoupurochita-dot Aug 18, 2026
313ae0d
Implement BlockStorage class for chunk management
saikyoupurochita-dot Aug 18, 2026
9342ea0
Add files via upload
saikyoupurochita-dot Aug 18, 2026
39a4ee8
Add BitArray interface for bit manipulation
saikyoupurochita-dot Aug 18, 2026
6f97486
Add files via upload
saikyoupurochita-dot Aug 18, 2026
e66a1a8
Add files via upload
saikyoupurochita-dot Aug 18, 2026
2191a0c
Add files via upload
saikyoupurochita-dot Aug 18, 2026
d50eb30
Add files via upload
saikyoupurochita-dot Aug 18, 2026
bb26142
Add files via upload
saikyoupurochita-dot Aug 18, 2026
ada0ac8
Add files via upload
saikyoupurochita-dot Aug 18, 2026
b894210
Add files via upload
saikyoupurochita-dot Aug 18, 2026
10368da
Add files via upload
saikyoupurochita-dot Aug 18, 2026
b138e36
Add files via upload
saikyoupurochita-dot Aug 18, 2026
9c29bea
Add files via upload
saikyoupurochita-dot Aug 19, 2026
12df4f6
Add files via upload
saikyoupurochita-dot Aug 19, 2026
f076d82
Add files via upload
saikyoupurochita-dot Aug 19, 2026
e6c65be
Add files via upload
saikyoupurochita-dot Aug 19, 2026
99912fa
Add files via upload
saikyoupurochita-dot Aug 19, 2026
aa311f8
Add files via upload
saikyoupurochita-dot Aug 19, 2026
ef2204e
Add files via upload
saikyoupurochita-dot Aug 19, 2026
aa71020
Fix build errors for bedrock-codec Beta12 (immutable packet fields) +…
saikyoupurochita-dot Aug 20, 2026
5dad400
Add files via upload
saikyoupurochita-dot Aug 20, 2026
e2e6ca2
Add files via upload
saikyoupurochita-dot Aug 20, 2026
ba10851
Delete relay/build.gradle-1.kts
saikyoupurochita-dot Aug 20, 2026
2f179fb
Add files via upload
saikyoupurochita-dot Aug 20, 2026
95f6522
Apply patch file via Termux
saikyoupurochita-dot Aug 20, 2026
2464c11
Add files via upload
saikyoupurochita-dot Aug 21, 2026
514cba0
Add files via upload
saikyoupurochita-dot Aug 21, 2026
99f1a73
Add files via upload
saikyoupurochita-dot Aug 21, 2026
4399126
Fix AuthWebView Kotlin type inference + bump client version to 1.26.40
saikyoupurochita-dot Aug 21, 2026
550e153
Bump bedrock-codec to Beta13-SNAPSHOT to actually get the v2168 (1.26…
saikyoupurochita-dot Aug 21, 2026
c851c77
Merge branch 'main' of https://github.com/saikyoupurochita-dot/WClien…
saikyoupurochita-dot Aug 21, 2026
413e2ee
Add files via upload
saikyoupurochita-dot Aug 21, 2026
b8570dc
Add files via upload
saikyoupurochita-dot Aug 21, 2026
6d0465e
Add files via upload
saikyoupurochita-dot Aug 21, 2026
92062f9
Add files via upload
saikyoupurochita-dot Aug 21, 2026
7c59a08
Add files via upload
saikyoupurochita-dot Aug 21, 2026
4b4d845
Add files via upload
saikyoupurochita-dot Aug 21, 2026
d1cb200
Add files via upload
saikyoupurochita-dot Aug 21, 2026
1a6e3dd
Add files via upload
saikyoupurochita-dot Aug 21, 2026
8b9b13b
Add files via upload
saikyoupurochita-dot Aug 21, 2026
c6d3937
Parse SubChunkPacket so PistonCrystal/Surround actually see real bloc…
saikyoupurochita-dot Aug 21, 2026
1fce22d
Merge branch 'main' of https://github.com/saikyoupurochita-dot/WClien…
saikyoupurochita-dot Aug 21, 2026
61172a0
Add files via upload
saikyoupurochita-dot Aug 21, 2026
7e4c5ec
Add files via upload
saikyoupurochita-dot Aug 21, 2026
7a16edd
Add files via upload
saikyoupurochita-dot Aug 21, 2026
86e4d8b
Add files via upload
saikyoupurochita-dot Aug 21, 2026
fd6313c
Fix block placement for PistonCrystal and Surround
saikyoupurochita-dot Aug 21, 2026
4e450f7
Fix BlockDefinition type mismatch in PistonCrystal and Surround
saikyoupurochita-dot Aug 21, 2026
6264568
Align PistonCrystal and Surround placement with latest WClient
saikyoupurochita-dot Aug 21, 2026
174f691
Use real ITEM_USE transaction template for Surround and PistonCrystal
saikyoupurochita-dot Aug 21, 2026
997f582
Add files via upload
saikyoupurochita-dot Aug 22, 2026
7ba157b
Add files via upload
saikyoupurochita-dot Aug 22, 2026
39f421f
Add files via upload
saikyoupurochita-dot Aug 22, 2026
15a006e
Add files via upload
saikyoupurochita-dot Aug 22, 2026
06137c1
Use server's own StartGamePacket block palette instead of outdated bu…
saikyoupurochita-dot Aug 22, 2026
ec6215e
Merge branch 'main' of https://github.com/saikyoupurochita-dot/WClien…
saikyoupurochita-dot Aug 22, 2026
f8ccc2e
Add files via upload
saikyoupurochita-dot Aug 22, 2026
74e14cf
Add files via upload
saikyoupurochita-dot Aug 22, 2026
8ba22ce
Add files via upload
saikyoupurochita-dot Aug 22, 2026
b644c17
Add files via upload
saikyoupurochita-dot Aug 22, 2026
eccaa0c
Add files via upload
saikyoupurochita-dot Aug 22, 2026
499ee5c
Add files via upload
saikyoupurochita-dot Aug 22, 2026
3398f66
Add temporary diagnostic logging to PistonCrystalModule to find where…
saikyoupurochita-dot Aug 22, 2026
eaae7cc
Merge branch 'main' of https://github.com/saikyoupurochita-dot/WClien…
saikyoupurochita-dot Aug 22, 2026
976e543
Wire up codecHelper.blockDefinitions to our block mapping (was never …
saikyoupurochita-dot Aug 22, 2026
71f4f4b
Add diagnostic logging to SurroundModule
saikyoupurochita-dot Aug 22, 2026
1212d65
Add files via upload
saikyoupurochita-dot Aug 23, 2026
014fef0
Add files via upload
saikyoupurochita-dot Aug 23, 2026
2871861
Add files via upload
saikyoupurochita-dot Aug 23, 2026
5299afb
Add files via upload
saikyoupurochita-dot Aug 23, 2026
aecfc13
Add files via upload
saikyoupurochita-dot Aug 23, 2026
17d8d08
Add files via upload
saikyoupurochita-dot Aug 24, 2026
b49c99c
Add files via upload
saikyoupurochita-dot Aug 24, 2026
f3c8cff
Fix local-prediction poisoning: stop trusting our own optimistic bloc…
saikyoupurochita-dot Aug 24, 2026
a303fd2
Merge branch 'main' of https://github.com/saikyoupurochita-dot/WClien…
saikyoupurochita-dot Aug 24, 2026
011d318
Add files via upload
saikyoupurochita-dot Aug 24, 2026
429c65d
Add files via upload
saikyoupurochita-dot Aug 24, 2026
f681b13
Add files via upload
saikyoupurochita-dot Aug 24, 2026
0c40ee1
Add files via upload
saikyoupurochita-dot Aug 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: Build APK
on: workflow_dispatch
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- run: chmod +x gradlew
- run: ./gradlew assembleRelease
- uses: actions/upload-artifact@v4
with:
name: apk
path: app/build/outputs/apk/**/*.apk
12 changes: 12 additions & 0 deletions PLACEMENT_FIX_NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Placement fix

This revision aligns PistonCrystal/Surround placement with the decompiled latest WClient:
- cache a real outgoing ITEM_USE placement transaction as a template;
- clone that transaction for module placements so newer protocol fields are preserved;
- use the protocol BlockDefinition from the held ItemData directly;
- sanitize ItemData net-id before sending;
- rebuild server-authoritative inventory actions;
- keep local block state updated immediately.

The decompiled latest WClient Scaffold (`com.retrivedmods.wclient.game.module.misc.a1`)
was used as the behavioral reference.
11 changes: 11 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@
-keep class io.netty.** { *; }
-keep class org.cloudburstmc.netty.** { *; }
-keep class org.cloudburstmc.protocol.bedrock.codec.** { *; }
# com.retrivedmods.wclient.util.PacketFieldUtil sets several packet fields (TextPacket.message,
# MovePlayerPacket.onGround, PlayerHotbarPacket.selectHotbarSlot, etc.) via raw reflection using
# string field names, because those fields no longer have public setters in the current Bedrock
# protocol library. Nothing else in the app references those fields by name, so without a keep
# rule R8 is free to rename or strip them in a release build - which is exactly what caused
# NoSuchFieldException crashes (e.g. "Field 'message' not found in ...") the moment a module that
# hits one of these reflective sets actually ran. Keeping the whole packet/data packages (not just
# the specific classes/fields used today) means future PacketFieldUtil usages don't silently
# reintroduce the same crash.
-keep class org.cloudburstmc.protocol.bedrock.packet.** { *; }
-keep class org.cloudburstmc.protocol.bedrock.data.** { *; }
-keep @io.netty.channel.ChannelHandler$Sharable class *
-keepclassmembers class * {
@com.google.gson.annotations.SerializedName <fields>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,22 +76,8 @@ class MainActivity : ComponentActivity() {
if (showLoading) {
LoadingScreen(
onDone = {
lifecycleScope.launch {
wclientId = VerificationManager.getWClientId(this@MainActivity)

if (VerificationManager.isWhitelisted(this@MainActivity, wclientId)) {
showLoading = false
return@launch
}

if (VerificationManager.isVerified(this@MainActivity, wclientId)) {
showLoading = false
return@launch
}

showLoading = false
showVerificationDialog = true
}
// WClient ID verification (ad-gate) disabled - go straight in.
showLoading = false
}
)
} else if (showVerificationDialog) {
Expand Down
214 changes: 70 additions & 144 deletions app/src/main/java/com/retrivedmods/wclient/game/AccountManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,46 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import com.google.gson.JsonParser
import com.retrivedmods.wclient.application.AppContext
import com.retrivedmods.wclient.game.RealmsAuthFlow
import com.retrivedmods.wclient.service.RealmsManager
import com.retrivedmods.wrelay.util.AuthUtils
import com.retrivedmods.wrelay.util.refresh
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.raphimc.minecraftauth.MinecraftAuth
import net.raphimc.minecraftauth.step.bedrock.session.StepFullBedrockSession.FullBedrockSession
import net.raphimc.minecraftauth.bedrock.BedrockAuthManager
import java.io.File
import java.util.concurrent.TimeUnit

object AccountManager {

// Bedrock game version reported to Mojang/Xbox services when negotiating a Minecraft
// session (net.raphimc.minecraftauth.bedrock.model.MinecraftSession). Keep this in sync with
// com.retrivedmods.wclient.util.MinecraftUtils.RECOMMENDED_VERSION.
const val GAME_VERSION = "1.26.40"

// BedrockAuthManager (5.x) is a live, self-refreshing manager object rather than the old
// immutable FullBedrockSession, so we pair it with a plain cached display name for UI use
// (looking up the name via the manager would be a network call if the token had expired).
data class WAccount(
val authManager: BedrockAuthManager,
val displayName: String
)

private val coroutineScope =
CoroutineScope(Dispatchers.IO + CoroutineName("AccountManagerCoroutine"))

private val _accounts: MutableList<FullBedrockSession> = mutableStateListOf()
private val _accounts: MutableList<WAccount> = mutableStateListOf()

val accounts: List<FullBedrockSession>
val accounts: List<WAccount>
get() = _accounts

var selectedAccount: FullBedrockSession? by mutableStateOf(null)
var selectedAccount: WAccount? by mutableStateOf(null)
private set

private val TOKEN_REFRESH_INTERVAL_MS = TimeUnit.MINUTES.toMillis(30)

private val TOKEN_REFRESH_THRESHOLD_MS = TimeUnit.HOURS.toMillis(2)

init {
val fetchedAccounts = fetchAccounts()

Expand All @@ -48,114 +57,92 @@ object AccountManager {
startTokenRefreshScheduler()
}

fun addAccount(fullBedrockSession: FullBedrockSession) {
val existingAccount = _accounts.find { it.mcChain.displayName == fullBedrockSession.mcChain.displayName }
fun addAccount(authManager: BedrockAuthManager) {
val displayName = authManager.minecraftCertificateChain.getUpToDate().identityDisplayName
val account = WAccount(authManager, displayName)

val existingAccount = _accounts.find { it.displayName == displayName }
if (existingAccount != null) {
_accounts.remove(existingAccount)
}

_accounts.add(fullBedrockSession)

coroutineScope.launch {
val file = File(AppContext.instance.cacheDir, "accounts")
file.mkdirs()
_accounts.add(account)

try {
val json = if (fullBedrockSession.realmsXsts != null) {
RealmsAuthFlow.BEDROCK_DEVICE_CODE_LOGIN_WITH_REALMS.toJson(fullBedrockSession)
} else {
println("No Realms token available, saving with regular auth flow")
MinecraftAuth.BEDROCK_DEVICE_CODE_LOGIN.toJson(fullBedrockSession)
}
file.resolve("${fullBedrockSession.mcChain.displayName}.json")
.writeText(AuthUtils.gson.toJson(json))
println("Successfully saved account: ${fullBedrockSession.mcChain.displayName} - Realms support: ${fullBedrockSession.realmsXsts != null}")
} catch (e: Exception) {
println("Failed to save account with Realms support, trying fallback: ${e.message}")
try {
val json = MinecraftAuth.BEDROCK_DEVICE_CODE_LOGIN.toJson(fullBedrockSession)
file.resolve("${fullBedrockSession.mcChain.displayName}.json")
.writeText(AuthUtils.gson.toJson(json))
println("Successfully saved account with fallback method: ${fullBedrockSession.mcChain.displayName}")
} catch (fallbackException: Exception) {
println("Failed to save account even with fallback: ${fallbackException.message}")
fallbackException.printStackTrace()
}
}
if (existingAccount == selectedAccount) {
selectAccount(account)
}

saveAccountToDisk(account)
}

fun removeAccount(fullBedrockSession: FullBedrockSession) {
_accounts.remove(fullBedrockSession)
fun removeAccount(account: WAccount) {
_accounts.remove(account)

coroutineScope.launch {
val file = File(AppContext.instance.cacheDir, "accounts")
file.mkdirs()

file.resolve("${fullBedrockSession.mcChain.displayName}.json")
.delete()
file.resolve("${account.displayName}.json").delete()
}
}

fun selectAccount(fullBedrockSession: FullBedrockSession?) {
selectedAccount = fullBedrockSession
fun selectAccount(account: WAccount?) {
selectedAccount = account

RealmsManager.updateSession(fullBedrockSession)
RealmsManager.updateSession(account)

coroutineScope.launch {
val file = File(AppContext.instance.cacheDir, "accounts")
file.mkdirs()

runCatching {
val selectedAccount = file.resolve("selectedAccount")
if (fullBedrockSession != null) {
selectedAccount.writeText(fullBedrockSession.mcChain.displayName)
val selectedAccountFile = file.resolve("selectedAccount")
if (account != null) {
selectedAccountFile.writeText(account.displayName)
} else {
selectedAccount.delete()
selectedAccountFile.delete()
}
}
}
}

private fun fetchAccounts(): List<FullBedrockSession> {
private fun fetchAccounts(): List<WAccount> {
val file = File(AppContext.instance.cacheDir, "accounts")
file.mkdirs()

val accounts = ArrayList<FullBedrockSession>()
val accounts = ArrayList<WAccount>()
val listFiles = file.listFiles() ?: emptyArray()
for (child in listFiles) {
runCatching {
if (child.isFile && child.extension == "json") {
val account = try {
RealmsAuthFlow.BEDROCK_DEVICE_CODE_LOGIN_WITH_REALMS
.fromJson(JsonParser.parseString(child.readText()).asJsonObject)
} catch (e: Exception) {
println("Failed to load account with Realms support from ${child.name}, trying legacy format: ${e.message}")
MinecraftAuth.BEDROCK_DEVICE_CODE_LOGIN
.fromJson(JsonParser.parseString(child.readText()).asJsonObject)
}
accounts.add(account)
println("Loaded account ${account.mcChain.displayName} - Realms support: ${account.realmsXsts != null}")
val httpClient = MinecraftAuth.createHttpClient()
val json = JsonParser.parseString(child.readText()).asJsonObject
val authManager = BedrockAuthManager.fromJson(httpClient, GAME_VERSION, json)
val displayName =
authManager.minecraftCertificateChain.getUpToDate().identityDisplayName
accounts.add(WAccount(authManager, displayName))
println("Loaded account $displayName")
}
}.onFailure {
println("Failed to load account from ${child.name}: ${it.message}")
it.printStackTrace()
}
}

return accounts
}

private fun fetchSelectedAccount(): FullBedrockSession? {
private fun fetchSelectedAccount(): WAccount? {
val file = File(AppContext.instance.cacheDir, "accounts")
file.mkdirs()

val selectedAccount = file.resolve("selectedAccount")
if (!selectedAccount.exists() || selectedAccount.isDirectory) {
val selectedAccountFile = file.resolve("selectedAccount")
if (!selectedAccountFile.exists() || selectedAccountFile.isDirectory) {
return null
}

val displayName = selectedAccount.readText()
return accounts.find { it.mcChain.displayName == displayName }
val displayName = selectedAccountFile.readText()
return accounts.find { it.displayName == displayName }
}

private fun startTokenRefreshScheduler() {
Expand All @@ -178,95 +165,34 @@ object AccountManager {
return
}

val accountsToRefresh = _accounts.filter { account ->
shouldRefreshToken(account)
}

if (accountsToRefresh.isNotEmpty()) {
println("Found ${accountsToRefresh.size} accounts that need token refresh")
}

accountsToRefresh.forEach { account ->
_accounts.forEach { account ->
try {
println("Refreshing token for account: ${account.mcChain.displayName}")
val httpClient = MinecraftAuth.createHttpClient()
httpClient.connectTimeout = 10000
httpClient.readTimeout = 10000

val refreshedAccount = try {
if (account.realmsXsts != null) {
RealmsAuthFlow.BEDROCK_DEVICE_CODE_LOGIN_WITH_REALMS.refresh(httpClient, account)
} else {
account.refresh()
}
} catch (e: Exception) {
println("Failed to refresh with Realms support, trying regular refresh: ${e.message}")
account.refresh()
}

val index = _accounts.indexOf(account)
if (index >= 0) {
_accounts[index] = refreshedAccount

if (selectedAccount == account) {
selectedAccount = refreshedAccount
RealmsManager.updateSession(refreshedAccount)
}

saveAccountToDisk(refreshedAccount)
}

println("Successfully refreshed token for: ${refreshedAccount.mcChain.displayName}")
// getUpToDate() only performs a network refresh when the cached value is
// missing/expired, so this is a no-op for accounts that are already fresh -
// no more manually tracking expiry thresholds or swapping session objects.
account.authManager.minecraftCertificateChain.getUpToDate()
account.authManager.playFabToken.getUpToDate()
saveAccountToDisk(account)
} catch (e: Exception) {
println("Failed to refresh token for ${account.mcChain.displayName}: ${e.message}")
println("Failed to refresh token for ${account.displayName}: ${e.message}")
e.printStackTrace()
}
}
}

private fun shouldRefreshToken(account: FullBedrockSession): Boolean {
val currentTime = System.currentTimeMillis()

val msaToken = account.mcChain.xblXsts.initialXblSession.msaToken
if (msaToken.expireTimeMs - currentTime < TOKEN_REFRESH_THRESHOLD_MS) {
return true
}

val xblExpireTime = account.mcChain.xblXsts.expireTimeMs
if (xblExpireTime - currentTime < TOKEN_REFRESH_THRESHOLD_MS) {
return true
}

val playFabExpireTime = account.playFabToken.expireTimeMs
if (playFabExpireTime - currentTime < TOKEN_REFRESH_THRESHOLD_MS) {
return true
}

return false
}

private fun saveAccountToDisk(account: FullBedrockSession) {
val file = File(AppContext.instance.cacheDir, "accounts")
file.mkdirs()
private fun saveAccountToDisk(account: WAccount) {
coroutineScope.launch {
val file = File(AppContext.instance.cacheDir, "accounts")
file.mkdirs()

try {
val json = if (account.realmsXsts != null) {
RealmsAuthFlow.BEDROCK_DEVICE_CODE_LOGIN_WITH_REALMS.toJson(account)
} else {
MinecraftAuth.BEDROCK_DEVICE_CODE_LOGIN.toJson(account)
}
file.resolve("${account.mcChain.displayName}.json")
.writeText(AuthUtils.gson.toJson(json))
} catch (e: Exception) {
println("Failed to save account with Realms support, trying fallback: ${e.message}")
try {
val json = MinecraftAuth.BEDROCK_DEVICE_CODE_LOGIN.toJson(account)
file.resolve("${account.mcChain.displayName}.json")
val json = BedrockAuthManager.toJson(account.authManager)
file.resolve("${account.displayName}.json")
.writeText(AuthUtils.gson.toJson(json))
} catch (fallbackException: Exception) {
println("Failed to save account even with fallback: ${fallbackException.message}")
fallbackException.printStackTrace()
} catch (e: Exception) {
println("Failed to save account ${account.displayName}: ${e.message}")
e.printStackTrace()
}
}
}
}
}
Loading