commit b9191a0e8667eac60764fed1b70c59f683975a98 Author: ssfdre38 Date: Mon Feb 23 21:10:22 2026 -0800 Initial commit: RR3 OTA Update System Complete standalone package for adding OTA (Over-The-Air) auto-updates to Real Racing 3 APK mods. Features: - Manifest-based version control (versions.json) - Prevents unwanted major version jumps - Material Design WebView UI - WiFi/mobile data download options - Preserves user data during updates - Multi-version channel support Package Contents: - UpdateManager.java - Source code implementation - UpdateManager.smali - Compiled smali for APK integration - community_update_checker.html - Material Design UI - README.md - Complete documentation - INTEGRATION-GUIDE.md - Step-by-step integration instructions Ready for integration into any RR3 APK version (v15, v14, v13, etc.) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> diff --git a/INTEGRATION-GUIDE.md b/INTEGRATION-GUIDE.md new file mode 100644 index 0000000..e5d1635 --- /dev/null +++ b/INTEGRATION-GUIDE.md @@ -0,0 +1,505 @@ +# πŸ”§ RR3 OTA Integration Guide + +**Step-by-step guide to integrate the OTA update system into any RR3 APK** + +--- + +## πŸ“‹ Prerequisites + +### Tools Needed + +- **apktool** - For decompiling/recompiling APKs +- **Java JDK 8+** - For compiling Java to class files +- **dx tool** (Android SDK) - For converting class to dex +- **baksmali/smali** - For dex to smali conversion +- **zipalign** - For optimizing APK +- **apksigner** - For signing APK +- **Text editor** - For editing files + +### Install Tools + +```bash +# apktool +wget https://github.com/iBotPeaches/Apktool/releases/latest/download/apktool.jar + +# Android SDK (includes dx, zipalign, apksigner) +# Download from: https://developer.android.com/studio + +# baksmali/smali +wget https://github.com/JesusFreke/smali/releases/latest/download/baksmali.jar +wget https://github.com/JesusFreke/smali/releases/latest/download/smali.jar +``` + +--- + +## 🎯 Integration Steps + +### Step 1: Decompile Your APK + +```bash +# Decompile RR3 APK +apktool d your-rr3.apk -o rr3-decompiled + +cd rr3-decompiled +``` + +**Output structure:** +``` +rr3-decompiled/ +β”œβ”€β”€ AndroidManifest.xml +β”œβ”€β”€ smali/ +β”œβ”€β”€ assets/ +β”œβ”€β”€ res/ +└── ... +``` + +--- + +### Step 2: Add UpdateManager + +#### Option A: Use Precompiled Smali (Easier) + +```bash +# Create directory +mkdir -p smali/com/community + +# Copy UpdateManager.smali +cp /path/to/rr3-ota/UpdateManager.smali smali/com/community/ +``` + +#### Option B: Compile from Java (More Control) + +```bash +# Compile Java to class +javac -source 1.8 -target 1.8 \ + -cp android.jar:org.json.jar \ + UpdateManager.java + +# Convert class to dex +dx --dex --output=UpdateManager.dex UpdateManager.class + +# Convert dex to smali +baksmali d UpdateManager.dex -o smali-output + +# Copy to APK +mkdir -p smali/com/community +cp smali-output/com/community/UpdateManager.smali smali/com/community/ +``` + +#### Important: Update Version Constants + +Edit `smali/com/community/UpdateManager.smali`: + +**Find these lines:** +```smali +.field private static final CURRENT_VERSION:Ljava/lang/String; = "15.0.0-community-alpha" + +.field private static final CURRENT_VERSION_CODE:I = 0x249f0 +``` + +**Change to your version:** +```smali +.field private static final CURRENT_VERSION:Ljava/lang/String; = "14.5.0" # Your version + +.field private static final CURRENT_VERSION_CODE:I = 0x23AB8 # 145000 in hex +``` + +**Calculate version code:** +``` +versionCode = (major * 100000) + (minor * 1000) + patch + +Examples: +14.5.0 β†’ 145000 β†’ 0x23AB8 (hex) +15.0.0 β†’ 150000 β†’ 0x249F0 (hex) + +# Convert to hex: printf '%x\n' 145000 +``` + +#### Update Manifest URL + +**Find this line:** +```smali +.field private static final UPDATE_API_URL:Ljava/lang/String; = "https://raw.githubusercontent.com/project-real-resurrection-3/rr3-releases/main/versions.json" +``` + +**Change to your manifest URL:** +```smali +.field private static final UPDATE_API_URL:Ljava/lang/String; = "https://raw.githubusercontent.com/YOUR-ORG/YOUR-REPO/main/versions.json" +``` + +--- + +### Step 3: Add HTML UI + +```bash +# Copy update checker HTML +cp /path/to/rr3-ota/community_update_checker.html assets/ +``` + +**Verify placement:** +``` +assets/ +└── community_update_checker.html +``` + +--- + +### Step 4: Add Permissions + +Edit `AndroidManifest.xml`: + +```xml + + + + + + + + + +``` + +--- + +### Step 5: Integrate into Your UI + +You need to add a button/menu item that triggers the update checker. + +#### Example: Add to Main Menu WebView + +**Find your main menu HTML file** (usually in `assets/`): +```bash +# Search for HTML files +find assets/ -name "*.html" | grep -i menu +``` + +**Add update button:** +```html + + + + +``` + +#### Add WebView Bridge + +**Find the Activity that creates your WebView** (in `smali/`): + +Common locations: +- `smali/com/ea/games/*/MainActivity.smali` +- `smali/com/ea/games/*/WebViewActivity.smali` + +**Find where WebView is created:** +```smali +# Look for this pattern +new-instance v0, Landroid/webkit/WebView; +invoke-direct {v0, p0}, Landroid/webkit/WebView;->(Landroid/content/Context;)V +``` + +**Add JavaScript interface after WebView creation:** +```smali +# Create UpdateManager instance +new-instance v1, Lcom/community/UpdateManager; +move-object v2, p0 # context +invoke-direct {v1, v2}, Lcom/community/UpdateManager;->(Landroid/content/Context;)V + +# Add as JavaScript interface +const-string v2, "UpdateManager" +invoke-virtual {v0, v1, v2}, Landroid/webkit/WebView;->addJavascriptInterface(Ljava/lang/Object;Ljava/lang/String;)V +``` + +--- + +### Step 6: Recompile APK + +```bash +# Build APK +apktool b rr3-decompiled -o rr3-modified-unsigned.apk + +# Zipalign +zipalign -p -f -v 4 rr3-modified-unsigned.apk rr3-modified-aligned.apk + +# Sign APK (create keystore if needed) +# Create keystore (first time only): +keytool -genkey -v -keystore rr3.keystore -alias rr3-key -keyalg RSA -keysize 2048 -validity 10000 + +# Sign +apksigner sign --ks rr3.keystore --ks-key-alias rr3-key \ + --out rr3-modified-signed.apk \ + rr3-modified-aligned.apk + +# Verify signature +apksigner verify rr3-modified-signed.apk +``` + +**Final APK:** `rr3-modified-signed.apk` + +--- + +### Step 7: Test Installation + +```bash +# Install on device +adb install rr3-modified-signed.apk + +# Or manually: +# 1. Transfer APK to device +# 2. Enable "Install from Unknown Sources" +# 3. Tap APK to install +``` + +--- + +## πŸ§ͺ Testing the Integration + +### Test 1: WebView Bridge + +Open your app and run in Android Studio logcat: + +```bash +adb logcat | grep UpdateManager +``` + +**Expected:** Should see `UpdateManager` logs when app starts. + +### Test 2: UI Button + +1. Open your modified menu +2. Click "Check for Updates" button +3. Should navigate to update checker page + +**If it doesn't work:** +- Check `assets/community_update_checker.html` exists +- Verify path: `file:///android_asset/community_update_checker.html` + +### Test 3: Update Check + +1. Click "Check for Updates" in the UI +2. Watch logcat: `adb logcat | grep UpdateManager` + +**Expected logs:** +``` +I/UpdateManager: Checking for updates from manifest... +I/UpdateManager: No update available +``` + +Or if update exists: +``` +I/UpdateManager: Update available: 15.1.0 +``` + +### Test 4: Download + +1. Create a test release on GitHub +2. Update your `versions.json` manifest +3. Check for updates in app +4. Click "Download Update" +5. Verify download starts + +--- + +## πŸ› Troubleshooting + +### Issue: "Class not found: UpdateManager" + +**Cause:** UpdateManager.smali not in correct location + +**Fix:** +```bash +# Verify file exists +ls -la smali/com/community/UpdateManager.smali + +# Should be: +# smali/com/community/UpdateManager.smali +# NOT: smali/UpdateManager.smali +``` + +### Issue: "JavaScript interface not found" + +**Cause:** WebView bridge not added or incorrect + +**Fix:** +- Verify you added the JavaScript interface code +- Check the interface name matches: `"UpdateManager"` +- Make sure it's added BEFORE WebView loads content + +### Issue: "Update check returns error" + +**Cause:** Manifest URL incorrect or not accessible + +**Fix:** +```bash +# Test manifest URL manually +curl https://raw.githubusercontent.com/YOUR-ORG/YOUR-REPO/main/versions.json + +# Should return JSON, not 404 +``` + +### Issue: "Version code mismatch" + +**Cause:** Incorrect version code calculation + +**Fix:** +```bash +# Calculate version code +echo "scale=0; (14*100000) + (5*1000) + 0" | bc +# Result: 145000 + +# Convert to hex +printf '%x\n' 145000 +# Result: 23ab8 + +# Update in smali: +.field private static final CURRENT_VERSION_CODE:I = 0x23ab8 +``` + +### Issue: "APK won't install" + +**Cause:** Signature mismatch or corrupted APK + +**Fix:** +```bash +# Verify APK +apksigner verify rr3-modified-signed.apk + +# If invalid, rebuild and re-sign +apktool b rr3-decompiled -o new.apk +zipalign -p 4 new.apk aligned.apk +apksigner sign --ks rr3.keystore aligned.apk +``` + +--- + +## πŸ“± Platform-Specific Notes + +### Android 11+ (API 30+) + +**File access changes:** Need to use scoped storage + +**In UpdateManager.smali, update download destination:** +```java +// Old (doesn't work on Android 11+): +Environment.DIRECTORY_DOWNLOADS + +// New (works on all versions): +context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS) +``` + +### Android 8+ (API 26+) + +**Install unknown apps permission required** + +Add to `AndroidManifest.xml`: +```xml + + + + + + +``` + +--- + +## πŸ” Security Checklist + +Before releasing: + +- [ ] Version constants updated +- [ ] Manifest URL points to your repository +- [ ] APK signed with your keystore (not debug key!) +- [ ] Permissions added to AndroidManifest.xml +- [ ] SHA-256 checksums in manifest (optional but recommended) +- [ ] HTTPS used for all URLs +- [ ] Tested on multiple Android versions + +--- + +## πŸ“Š Version Manifest Setup + +After integration, you need a manifest: + +### 1. Create GitHub Repository + +```bash +# Create releases repository +gh repo create YOUR-ORG/rr3-releases --public +``` + +### 2. Create versions.json + +```json +{ + "schema_version": 1, + "last_updated": "2026-02-24T00:00:00Z", + "channels": { + "stable": { + "description": "Stable releases", + "latest": "14.5.0" + } + }, + "versions": [ + { + "version": "14.5.0", + "version_code": 145000, + "channel": "stable", + "release_date": "2026-02-24", + "min_android": 21, + "target_android": 33, + "download_url": "https://github.com/YOUR-ORG/rr3-releases/releases/download/v14.5.0/RR3-v14.5.0.apk", + "file_size": 230000000, + "sha256": "your-sha256-here", + "changelog": "## What's New\n- Your changelog here", + "upgrade_from": [] + } + ] +} +``` + +### 3. Upload to GitHub + +```bash +git add versions.json +git commit -m "Add version manifest" +git push +``` + +### 4. Verify Accessibility + +```bash +curl https://raw.githubusercontent.com/YOUR-ORG/rr3-releases/main/versions.json +``` + +--- + +## πŸŽ‰ You're Done! + +Your RR3 APK now has a fully functional OTA update system! + +**Next steps:** +1. Release your first version on GitHub +2. Update versions.json when new versions are ready +3. Users will automatically see updates + +--- + +## πŸ“ž Need Help? + +- πŸ“– **Full documentation:** [README.md](./README.md) +- πŸ› **Report issues:** GitHub Issues +- πŸ’¬ **Community:** Discord (link TBA) +- πŸ“§ **Contact:** [@ssfdre38](https://github.com/ssfdre38) + +--- + +**Happy integrating! πŸ”„πŸ** diff --git a/README.md b/README.md new file mode 100644 index 0000000..7a387f2 --- /dev/null +++ b/README.md @@ -0,0 +1,474 @@ +# πŸ”„ RR3 OTA Update System + +**Standalone OTA (Over-The-Air) update system for Real Racing 3 Community Edition** + +This package provides a complete, drop-in OTA update system that can be integrated into any RR3 APK version (EA's official or community editions). + +--- + +## πŸ“¦ What's Included + +``` +rr3-ota/ +β”œβ”€β”€ UpdateManager.java (Main update logic - manifest-based) +β”œβ”€β”€ UpdateManager.smali (Compiled smali version) +β”œβ”€β”€ community_update_checker.html (WebView UI for updates) +β”œβ”€β”€ README.md (This file) +└── INTEGRATION-GUIDE.md (Step-by-step integration) +``` + +--- + +## ✨ Features + +- πŸ”„ **Manifest-based updates** - Full control over upgrade paths +- πŸ“‘ **GitHub integration** - Uses GitHub Releases for hosting +- 🌐 **Network flexibility** - WiFi or mobile data support +- πŸ“Š **Progress tracking** - Real-time download progress +- 🎨 **Material Design UI** - Beautiful WebView interface +- βš™οΈ **User preferences** - WiFi-only mode, auto-check settings +- πŸ”’ **Version control** - Prevents unwanted major version jumps +- πŸ“± **Android 6.0+** - Compatible with API 23+ + +--- + +## 🎯 How It Works + +### Manifest System + +Instead of relying on GitHub's `/releases/latest` API, this system uses a **version manifest** (`versions.json`) that you control: + +```json +{ + "versions": [ + { + "version": "15.1.0", + "version_code": 151000, + "download_url": "https://github.com/.../RR3-v15.1.0.apk", + "upgrade_from": ["15.0.0", "15.0.1"] + } + ] +} +``` + +**Benefits:** +- βœ… You control which versions get updates +- βœ… Users on v14.x won't be forced to v15.x +- βœ… Can create multiple update channels (stable, beta, legacy) +- βœ… Easy to manage (edit single JSON file) + +### Update Flow + +``` +1. User opens app + ↓ +2. UpdateManager fetches versions.json + ↓ +3. Finds applicable update (checks upgrade_from) + ↓ +4. Shows update dialog with changelog + ↓ +5. User clicks Download + ↓ +6. DownloadManager downloads APK + ↓ +7. Installation prompt (preserves user data) + ↓ +8. Updated! +``` + +--- + +## πŸš€ Quick Integration + +### For RR3 Community Edition (Already Integrated) + +If you're building RR3 Community Edition from our repository: +- βœ… UpdateManager is already patched in +- βœ… HTML UI already in assets +- βœ… Just build the APK + +### For Other RR3 Versions (Integration Required) + +Follow the detailed guide: **[INTEGRATION-GUIDE.md](./INTEGRATION-GUIDE.md)** + +**Quick steps:** +1. Decompile your RR3 APK with apktool +2. Add `UpdateManager.smali` to `smali/com/community/` +3. Add `community_update_checker.html` to `assets/` +4. Add "Check for Updates" button to your UI +5. Recompile and sign APK + +--- + +## πŸ“ Configuration + +### Update Version Constants + +Edit `UpdateManager.java` (or `.smali`) before building: + +```java +// IMPORTANT: Update these for each build +private static final String CURRENT_VERSION = "15.0.0-community-alpha"; +private static final int CURRENT_VERSION_CODE = 150000; +``` + +**Version Code Formula:** +``` +versionCode = (major * 100000) + (minor * 1000) + patch + +Examples: +15.0.0 β†’ 150000 +15.1.0 β†’ 151000 +14.5.2 β†’ 145200 +``` + +### Manifest URL + +The manifest URL is hardcoded in UpdateManager: + +```java +private static final String UPDATE_API_URL = + "https://raw.githubusercontent.com/YOUR-ORG/YOUR-REPO/main/versions.json"; +``` + +**Change this to your own manifest location!** + +--- + +## πŸ“Š Version Manifest Setup + +### 1. Create versions.json + +Create a `versions.json` file in your releases repository: + +```json +{ + "schema_version": 1, + "last_updated": "2026-02-24T04:00:00Z", + "channels": { + "stable": { + "description": "Stable releases", + "latest": "15.0.0" + } + }, + "versions": [ + { + "version": "15.0.0", + "version_code": 150000, + "channel": "stable", + "release_date": "2026-02-24", + "min_android": 23, + "target_android": 34, + "download_url": "https://github.com/YOUR-ORG/releases/download/v15.0.0/RR3-v15.0.0.apk", + "file_size": 240000000, + "sha256": "abc123...", + "changelog": "## Initial Release\n- Feature 1\n- Feature 2", + "upgrade_from": [] + } + ] +} +``` + +### 2. Host on GitHub + +Upload `versions.json` to your GitHub repository (main branch, root or any folder). + +**Access URL:** +``` +https://raw.githubusercontent.com/YOUR-ORG/YOUR-REPO/main/versions.json +``` + +### 3. Add New Versions + +When releasing a new version: + +1. **Create GitHub Release** (hosts APK file) +2. **Update versions.json:** + ```json + { + "version": "15.1.0", + "version_code": 151000, + "download_url": "https://github.com/.../v15.1.0/RR3-v15.1.0.apk", + "upgrade_from": ["15.0.0"], + ... + } + ``` +3. **Update channel latest:** + ```json + "channels": { + "stable": { "latest": "15.1.0" } + } + ``` +4. **Commit and push** versions.json + +Users will see the update within seconds! + +--- + +## 🎨 UI Integration + +### Add Update Button + +The easiest way to integrate the UI is to add a button that opens the update checker: + +**Example (in your HTML UI):** +```html + + + +``` + +### WebView Bridge Setup + +The UpdateManager must be added as a JavaScript interface to your WebView: + +```java +webView.addJavascriptInterface(new UpdateManager(context), "UpdateManager"); +``` + +**In smali:** +```smali +new-instance v0, Lcom/community/UpdateManager; +move-object v1, p0 # context +invoke-direct {v0, v1}, Lcom/community/UpdateManager;->(Landroid/content/Context;)V + +const-string v1, "UpdateManager" +invoke-virtual {webView, v0, v1}, Landroid/webkit/WebView;->addJavascriptInterface(Ljava/lang/Object;Ljava/lang/String;)V +``` + +--- + +## πŸ§ͺ Testing + +### Test Manifest Parsing + +```bash +# Fetch your manifest +curl https://raw.githubusercontent.com/YOUR-ORG/YOUR-REPO/main/versions.json + +# Check structure +jq . versions.json + +# Verify accessible +curl -I https://raw.githubusercontent.com/YOUR-ORG/YOUR-REPO/main/versions.json +``` + +### Test Update Flow + +1. **Set current version lower** (e.g., 15.0.0) +2. **Add test version to manifest** (e.g., 15.0.1) +3. **Set upgrade_from** to current version +4. **Open app and check for updates** +5. **Verify update is offered** + +### Test Download + +1. **Create actual GitHub release with APK** +2. **Update manifest download_url** +3. **Test download (WiFi only, then mobile)** +4. **Verify progress tracking works** +5. **Test installation** + +--- + +## πŸ”§ Customization + +### Change Manifest URL + +Edit `UpdateManager.java` line ~23: +```java +private static final String UPDATE_API_URL = "YOUR_MANIFEST_URL"; +``` + +### Modify UI + +Edit `community_update_checker.html`: +- Change colors (CSS variables at top) +- Modify layout +- Add custom branding +- Change button text + +### Add Update Channels + +In your manifest: +```json +"channels": { + "stable": { "latest": "15.0.0" }, + "beta": { "latest": "15.1.0-beta" }, + "legacy-v14": { "latest": "14.5.2" } +} +``` + +Then add channel selection in your UI. + +### Network Preferences + +Users can toggle WiFi-only mode: +```javascript +UpdateManager.setWifiOnlyPreference(true); // WiFi only +UpdateManager.setWifiOnlyPreference(false); // WiFi + Mobile +``` + +--- + +## πŸ“± Android Permissions + +Add to `AndroidManifest.xml`: + +```xml + + + + + + + + + +``` + +--- + +## πŸ”’ Security + +### APK Verification + +**Recommended:** Verify SHA-256 checksum after download + +```java +// Add to UpdateManager +public boolean verifyDownload(String expectedSha256) { + // Calculate SHA-256 of downloaded APK + // Compare with manifest value + // Return true if match +} +``` + +### HTTPS Only + +- βœ… Manifest URL uses HTTPS (raw.githubusercontent.com) +- βœ… Download URLs use HTTPS (github.com) +- βœ… No insecure connections + +### Code Signing + +Always sign your APK with the same keystore for update continuity. + +--- + +## πŸ“‚ File Structure + +### After Integration + +``` +your-rr3-apk/ +β”œβ”€β”€ smali/ +β”‚ └── com/ +β”‚ └── community/ +β”‚ └── UpdateManager.smali ← Add this +β”œβ”€β”€ assets/ +β”‚ └── community_update_checker.html ← Add this +└── AndroidManifest.xml ← Add permissions +``` + +### Your GitHub Release Repo + +``` +your-releases-repo/ +β”œβ”€β”€ versions.json ← Version manifest +└── README.md ← Usage instructions +``` + +--- + +## ❓ FAQ + +### Q: Does this work with EA's official RR3? + +**A:** Technically yes, but EA may ban your account for modified APKs. This is designed for community servers where EA's servers are shut down. + +### Q: Can users downgrade versions? + +**A:** No. The system only offers updates to versions with higher version codes. + +### Q: What if GitHub goes down? + +**A:** Update checks will fail gracefully. Users can still play, just won't get updates until GitHub is back. + +### Q: Can I host the manifest elsewhere? + +**A:** Yes! Just change the `UPDATE_API_URL`. Any HTTPS endpoint that returns the JSON works. + +### Q: How do I prevent v14 users from updating to v15? + +**A:** In your v15 manifest entry, don't include v14.x in `upgrade_from`: +```json +"upgrade_from": ["15.0.0", "15.0.1"] // No 14.x! +``` + +### Q: Can I have multiple APK variants (arm64, x86)? + +**A:** Not currently, but you can extend the manifest: +```json +"variants": [ + {"arch": "arm64-v8a", "url": "...-arm64.apk"}, + {"arch": "armeabi-v7a", "url": "...-arm.apk"} +] +``` + +Then update UpdateManager to select based on device architecture. + +--- + +## 🀝 Contributing + +Found a bug? Have an improvement? + +1. Test your changes thoroughly +2. Update documentation +3. Submit with clear description + +--- + +## πŸ“œ License + +This OTA system is part of the RR3 Community Edition project. + +**License:** MIT License + +**Credits:** +- Project Lead: [@ssfdre38](https://github.com/ssfdre38) (Daniel Elliott) +- AI Assistant: GitHub Copilot CLI + +--- + +## πŸ”— Links + +- **Main Project:** [RR3 Community Edition](https://github.com/project-real-resurrection-3) +- **Releases:** [rr3-releases](https://github.com/project-real-resurrection-3/rr3-releases) +- **Legal:** [LEGAL.md](https://github.com/project-real-resurrection-3/.github/blob/main/LEGAL.md) + +--- + +## πŸ†˜ Support + +**Need help?** +- πŸ› Report issues on GitHub +- πŸ’¬ Join community Discord (link TBA) +- πŸ“§ Contact: [@ssfdre38](https://github.com/ssfdre38) + +--- + +
+ +**πŸ”„ Keep your RR3 Community Edition up to date! 🏁** + +Made with ❀️ by the RR3 Community + +
diff --git a/UpdateManager.java b/UpdateManager.java new file mode 100644 index 0000000..7837230 --- /dev/null +++ b/UpdateManager.java @@ -0,0 +1,362 @@ +package com.community; + +import android.content.Context; +import android.content.SharedPreferences; +import android.app.DownloadManager; +import android.net.Uri; +import android.os.Environment; +import android.util.Log; +import android.webkit.JavascriptInterface; +import org.json.JSONArray; +import org.json.JSONObject; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; + +/** + * UpdateManager - Manifest-based OTA Update System + * + * Fetches version manifest from GitHub and determines applicable updates + * based on upgrade paths defined in versions.json + */ +public class UpdateManager { + private static final String TAG = "UpdateManager"; + private static final String UPDATE_API_URL = "https://raw.githubusercontent.com/project-real-resurrection-3/rr3-releases/main/versions.json"; + private static final String PREFS_NAME = "com.ea.games.r3_row_preferences"; + private static final String KEY_WIFI_ONLY = "wifi_only_updates"; + private static final String KEY_AUTO_CHECK = "auto_check_updates"; + private static final String KEY_LAST_CHECK = "last_update_check"; + private static final String KEY_SKIPPED_VERSIONS = "skipped_versions"; + private static final String KEY_DOWNLOAD_ID = "download_id"; + + // IMPORTANT: Update these values for each build + private static final String CURRENT_VERSION = "15.0.0-community-alpha"; + private static final int CURRENT_VERSION_CODE = 150000; + + private Context context; + private DownloadManager downloadManager; + private long downloadId = -1; + + public UpdateManager(Context context) { + this.context = context; + this.downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); + } + + /** + * Check for updates using version manifest + * Returns JSON with update info or "no update" status + */ + @JavascriptInterface + public String checkForUpdates() { + Log.i(TAG, "Checking for updates from manifest..."); + JSONObject result = new JSONObject(); + HttpURLConnection connection = null; + + try { + // Fetch versions.json manifest + URL url = new URL(UPDATE_API_URL); + connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setRequestProperty("User-Agent", "RR3-Community-Updater"); + connection.setConnectTimeout(10000); + connection.setReadTimeout(10000); + + int responseCode = connection.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_OK) { + BufferedReader reader = new BufferedReader( + new InputStreamReader(connection.getInputStream()) + ); + StringBuilder response = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + response.append(line); + } + reader.close(); + + // Parse manifest + JSONObject manifest = new JSONObject(response.toString()); + JSONArray versions = manifest.getJSONArray("versions"); + + // Get current version (strip suffix like -alpha, -beta) + String currentVersionClean = CURRENT_VERSION; + if (currentVersionClean.contains("-")) { + currentVersionClean = currentVersionClean.split("-")[0]; + } + + // Search for applicable update + JSONObject updateVersion = findApplicableUpdate(versions, currentVersionClean, CURRENT_VERSION_CODE); + + if (updateVersion != null) { + // Found an update! + result.put("hasUpdate", true); + result.put("version", updateVersion.getString("version")); + result.put("downloadUrl", updateVersion.getString("download_url")); + result.put("fileSize", updateVersion.getLong("file_size")); + result.put("changelog", updateVersion.optString("changelog", "")); + result.put("releaseDate", updateVersion.optString("release_date", "")); + + Log.i(TAG, "Update available: " + updateVersion.getString("version")); + } else { + // No update found + result.put("hasUpdate", false); + Log.i(TAG, "No update available"); + } + + // Update last check time + SharedPreferences prefs = getSharedPreferences(); + prefs.edit() + .putLong(KEY_LAST_CHECK, System.currentTimeMillis()) + .apply(); + } else { + result.put("hasUpdate", false); + result.put("error", "HTTP " + responseCode); + } + + } catch (Exception e) { + Log.e(TAG, "Error checking for updates", e); + try { + result.put("hasUpdate", false); + result.put("error", "Failed to check for updates"); + } catch (Exception ignored) {} + } finally { + if (connection != null) { + connection.disconnect(); + } + } + + return result.toString(); + } + + /** + * Find applicable update from versions array + * Checks if version_code is newer AND current version is in upgrade_from + */ + private JSONObject findApplicableUpdate(JSONArray versions, String currentVersion, int currentVersionCode) { + try { + for (int i = 0; i < versions.length(); i++) { + JSONObject version = versions.getJSONObject(i); + + // Get version code + int versionCode = version.getInt("version_code"); + + // Only consider versions newer than current + if (versionCode <= currentVersionCode) { + continue; + } + + // Check if current version is in upgrade_from array + JSONArray upgradeFrom = version.optJSONArray("upgrade_from"); + if (upgradeFrom == null) { + continue; + } + + // Check each entry in upgrade_from + for (int j = 0; j < upgradeFrom.length(); j++) { + String allowedVersion = upgradeFrom.getString(j); + + // Check for exact match + if (allowedVersion.equals(currentVersion)) { + return version; + } + + // Check for wildcard match (e.g., "15.0.x" matches "15.0.0") + if (allowedVersion.endsWith(".x")) { + String wildcardPrefix = allowedVersion.substring(0, allowedVersion.length() - 2); + if (currentVersion.startsWith(wildcardPrefix)) { + return version; + } + } + + // Check for major version wildcard (e.g., "14.x" matches any 14.x.x) + if (allowedVersion.matches("\\d+\\.x")) { + String majorVersion = allowedVersion.split("\\.")[0]; + if (currentVersion.startsWith(majorVersion + ".")) { + return version; + } + } + } + } + } catch (Exception e) { + Log.e(TAG, "Error finding applicable update", e); + } + + return null; + } + + /** + * Download update APK + */ + @JavascriptInterface + public boolean downloadUpdate(String downloadUrl, String version) { + try { + Log.i(TAG, "Starting download: " + version); + + // Check network preference + boolean wifiOnly = getSharedPreferences().getBoolean(KEY_WIFI_ONLY, false); + + Uri uri = Uri.parse(downloadUrl); + DownloadManager.Request request = new DownloadManager.Request(uri); + request.setTitle("RR3 Community Update"); + request.setDescription("Downloading v" + version); + request.setDestinationInExternalPublicDir( + Environment.DIRECTORY_DOWNLOADS, + "RR3-Community-v" + version + ".apk" + ); + + // Set network type based on preference + if (wifiOnly) { + request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI); + } else { + request.setAllowedNetworkTypes( + DownloadManager.Request.NETWORK_WIFI | + DownloadManager.Request.NETWORK_MOBILE + ); + } + + request.setNotificationVisibility( + DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED + ); + request.setMimeType("application/vnd.android.package-archive"); + + downloadId = downloadManager.enqueue(request); + + // Save download ID + getSharedPreferences().edit() + .putLong(KEY_DOWNLOAD_ID, downloadId) + .apply(); + + return true; + } catch (Exception e) { + Log.e(TAG, "Error starting download", e); + return false; + } + } + + /** + * Get download progress (0-100, or -1 if not downloading) + */ + @JavascriptInterface + public int getDownloadProgress() { + if (downloadId == -1) { + return -1; + } + + try { + DownloadManager.Query query = new DownloadManager.Query(); + query.setFilterById(downloadId); + android.database.Cursor cursor = downloadManager.query(query); + + if (cursor.moveToFirst()) { + int bytesDownloaded = cursor.getInt( + cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR) + ); + int bytesTotal = cursor.getInt( + cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES) + ); + + if (bytesTotal > 0) { + return (int) ((bytesDownloaded * 100L) / bytesTotal); + } + } + cursor.close(); + } catch (Exception e) { + Log.e(TAG, "Error getting download progress", e); + } + + return -1; + } + + /** + * Check if download is complete + */ + @JavascriptInterface + public boolean isDownloadComplete() { + if (downloadId == -1) { + return false; + } + + try { + DownloadManager.Query query = new DownloadManager.Query(); + query.setFilterById(downloadId); + android.database.Cursor cursor = downloadManager.query(query); + + if (cursor.moveToFirst()) { + int status = cursor.getInt( + cursor.getColumnIndex(DownloadManager.COLUMN_STATUS) + ); + cursor.close(); + return status == DownloadManager.STATUS_SUCCESSFUL; + } + } catch (Exception e) { + Log.e(TAG, "Error checking download status", e); + } + + return false; + } + + /** + * Get WiFi-only preference + */ + @JavascriptInterface + public boolean getWifiOnlyPreference() { + return getSharedPreferences().getBoolean(KEY_WIFI_ONLY, false); + } + + /** + * Set WiFi-only preference + */ + @JavascriptInterface + public void setWifiOnlyPreference(boolean wifiOnly) { + getSharedPreferences().edit() + .putBoolean(KEY_WIFI_ONLY, wifiOnly) + .apply(); + } + + /** + * Get auto-check preference + */ + @JavascriptInterface + public boolean getAutoCheckPreference() { + return getSharedPreferences().getBoolean(KEY_AUTO_CHECK, true); + } + + /** + * Set auto-check preference + */ + @JavascriptInterface + public void setAutoCheckPreference(boolean autoCheck) { + getSharedPreferences().edit() + .putBoolean(KEY_AUTO_CHECK, autoCheck) + .apply(); + } + + /** + * Get SharedPreferences instance + */ + private SharedPreferences getSharedPreferences() { + return context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); + } + + /** + * Parse version string to version code + * Format: "15.0.0" -> 150000 + */ + private int parseVersionCode(String version) { + try { + String[] parts = version.split("\\."); + if (parts.length >= 3) { + int major = Integer.parseInt(parts[0]); + int minor = Integer.parseInt(parts[1]); + String patchPart = parts[2].split("-")[0]; // Remove suffix + int patch = Integer.parseInt(patchPart); + + return (major * 100000) + (minor * 1000) + patch; + } + } catch (NumberFormatException e) { + Log.e(TAG, "Error parsing version code", e); + } + + return 0; + } +} diff --git a/UpdateManager.smali b/UpdateManager.smali new file mode 100644 index 0000000..78f72fd --- /dev/null +++ b/UpdateManager.smali @@ -0,0 +1,1170 @@ +.class public Lcom/community/UpdateManager; +.super Ljava/lang/Object; +.source "UpdateManager.java" + + +# static fields +.field private static final TAG:Ljava/lang/String; = "UpdateManager" + +.field private static final UPDATE_API_URL:Ljava/lang/String; = "https://raw.githubusercontent.com/project-real-resurrection-3/rr3-releases/main/versions.json" + +.field private static final PREFS_NAME:Ljava/lang/String; = "com.ea.games.r3_row_preferences" + +.field private static final KEY_WIFI_ONLY:Ljava/lang/String; = "wifi_only_updates" + +.field private static final KEY_AUTO_CHECK:Ljava/lang/String; = "auto_check_updates" + +.field private static final KEY_LAST_CHECK:Ljava/lang/String; = "last_update_check" + +.field private static final KEY_SKIPPED_VERSIONS:Ljava/lang/String; = "skipped_versions" + +.field private static final KEY_DOWNLOAD_ID:Ljava/lang/String; = "download_id" + +.field private static final CURRENT_VERSION:Ljava/lang/String; = "15.0.0-community-alpha" + +.field private static final CURRENT_VERSION_CODE:I = 0x249f0 + + +# instance fields +.field private context:Landroid/content/Context; + +.field private downloadManager:Landroid/app/DownloadManager; + +.field private downloadId:J + + +# direct methods +.method public constructor (Landroid/content/Context;)V + .registers 4 + + .line 1 + invoke-direct {p0}, Ljava/lang/Object;->()V + + const-wide/16 v0, -0x1 + + .line 2 + iput-wide v0, p0, Lcom/community/UpdateManager;->downloadId:J + + .line 3 + iput-object p1, p0, Lcom/community/UpdateManager;->context:Landroid/content/Context; + + const-string v0, "download" + + .line 4 + invoke-virtual {p1, v0}, Landroid/content/Context;->getSystemService(Ljava/lang/String;)Ljava/lang/Object; + + move-result-object p1 + + check-cast p1, Landroid/app/DownloadManager; + + iput-object p1, p0, Lcom/community/UpdateManager;->downloadManager:Landroid/app/DownloadManager; + + return-void +.end method + +.method private getSharedPreferences()Landroid/content/SharedPreferences; + .registers 4 + + .line 1 + iget-object v0, p0, Lcom/community/UpdateManager;->context:Landroid/content/Context; + + const-string v1, "com.ea.games.r3_row_preferences" + + const/4 v2, 0x0 + + invoke-virtual {v0, v1, v2}, Landroid/content/Context;->getSharedPreferences(Ljava/lang/String;I)Landroid/content/SharedPreferences; + + move-result-object v0 + + return-object v0 +.end method + +.method private isWifiConnected()Z + .registers 5 + + .line 1 + iget-object v0, p0, Lcom/community/UpdateManager;->context:Landroid/content/Context; + + const-string v1, "connectivity" + + invoke-virtual {v0, v1}, Landroid/content/Context;->getSystemService(Ljava/lang/String;)Ljava/lang/Object; + + move-result-object v0 + + check-cast v0, Landroid/net/ConnectivityManager; + + .line 2 + invoke-virtual {v0}, Landroid/net/ConnectivityManager;->getActiveNetworkInfo()Landroid/net/NetworkInfo; + + move-result-object v0 + + const/4 v1, 0x0 + + if-eqz v0, :cond_1 + + .line 3 + invoke-virtual {v0}, Landroid/net/NetworkInfo;->isConnected()Z + + move-result v2 + + if-eqz v2, :cond_1 + + .line 4 + invoke-virtual {v0}, Landroid/net/NetworkInfo;->getType()I + + move-result v2 + + const/4 v3, 0x1 + + if-eq v2, v3, :cond_0 + + .line 5 + invoke-virtual {v0}, Landroid/net/NetworkInfo;->getType()I + + move-result v0 + + const/16 v2, 0x9 + + if-ne v0, v2, :cond_1 + + :cond_0 + return v3 + + :cond_1 + return v1 +.end method + +.method private parseVersionCode(Ljava/lang/String;)I + .registers 7 + + const-string v0, "\\." + + .line 1 + invoke-virtual {p1, v0}, Ljava/lang/String;->split(Ljava/lang/String;)[Ljava/lang/String; + + move-result-object p1 + + .line 2 + array-length v0, p1 + + const/4 v1, 0x3 + + if-ge v0, v1, :cond_0 + + const/4 p1, 0x0 + + return p1 + + :cond_0 + const/4 v0, 0x0 + + .line 3 + :try_start_0 + aget-object v0, p1, v0 + + invoke-static {v0}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I + + move-result v0 + + const v1, 0x186a0 + + mul-int/2addr v0, v1 + + const/4 v1, 0x1 + + .line 4 + aget-object v1, p1, v1 + + invoke-static {v1}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I + + move-result v1 + + mul-int/lit16 v1, v1, 0x3e8 + + add-int/2addr v0, v1 + + const/4 v1, 0x2 + + .line 5 + aget-object p1, p1, v1 + + const-string v1, "-" + + invoke-virtual {p1, v1}, Ljava/lang/String;->split(Ljava/lang/String;)[Ljava/lang/String; + + move-result-object p1 + + const/4 v1, 0x0 + + aget-object p1, p1, v1 + + .line 6 + invoke-static {p1}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I + + move-result p1 + :try_end_0 + .catch Ljava/lang/NumberFormatException; {:try_start_0 .. :try_end_0} :catch_0 + + add-int/2addr v0, p1 + + return v0 + + :catch_0 + move-exception p1 + + const-string v0, "UpdateManager" + + const-string v1, "Error parsing version code" + + .line 7 + invoke-static {v0, v1, p1}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I + + const/4 p1, 0x0 + + return p1 +.end method + + +# virtual methods +.method public checkForUpdates()Ljava/lang/String; + .registers 11 + .annotation runtime Landroid/webkit/JavascriptInterface; + .end annotation + + const-string v0, "UpdateManager" + + const-string v1, "Checking for updates..." + + .line 1 + invoke-static {v0, v1}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I + + .line 2 + new-instance v0, Lorg/json/JSONObject; + + invoke-direct {v0}, Lorg/json/JSONObject;->()V + + const/4 v1, 0x0 + + :try_start_0 + const-string v2, "https://api.github.com/repos/supermegamestre/Project-Real-Resurrection-3/releases/latest" + + .line 3 + new-instance v3, Ljava/net/URL; + + invoke-direct {v3, v2}, Ljava/net/URL;->(Ljava/lang/String;)V + + .line 4 + invoke-virtual {v3}, Ljava/net/URL;->openConnection()Ljava/net/URLConnection; + + move-result-object v2 + + check-cast v2, Ljava/net/HttpURLConnection; + :try_end_0 + .catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_1 + .catchall {:try_start_0 .. :try_end_0} :catchall_1 + + :try_start_1 + const-string v1, "GET" + + .line 5 + invoke-virtual {v2, v1}, Ljava/net/HttpURLConnection;->setRequestMethod(Ljava/lang/String;)V + + const-string v1, "Accept" + + const-string v3, "application/vnd.github.v3+json" + + .line 6 + invoke-virtual {v2, v1, v3}, Ljava/net/HttpURLConnection;->setRequestProperty(Ljava/lang/String;Ljava/lang/String;)V + + const-string v1, "User-Agent" + + const-string v3, "RR3-Community-Updater" + + .line 7 + invoke-virtual {v2, v1, v3}, Ljava/net/HttpURLConnection;->setRequestProperty(Ljava/lang/String;Ljava/lang/String;)V + + const/16 v1, 0x2710 + + .line 8 + invoke-virtual {v2, v1}, Ljava/net/HttpURLConnection;->setConnectTimeout(I)V + + .line 9 + invoke-virtual {v2, v1}, Ljava/net/HttpURLConnection;->setReadTimeout(I)V + + .line 10 + invoke-virtual {v2}, Ljava/net/HttpURLConnection;->getResponseCode()I + + move-result v1 + + const/16 v3, 0xc8 + + if-ne v1, v3, :cond_4 + + .line 11 + new-instance v1, Ljava/io/BufferedReader; + + new-instance v3, Ljava/io/InputStreamReader; + + invoke-virtual {v2}, Ljava/net/HttpURLConnection;->getInputStream()Ljava/io/InputStream; + + move-result-object v4 + + invoke-direct {v3, v4}, Ljava/io/InputStreamReader;->(Ljava/io/InputStream;)V + + invoke-direct {v1, v3}, Ljava/io/BufferedReader;->(Ljava/io/Reader;)V + + .line 12 + new-instance v3, Ljava/lang/StringBuilder; + + invoke-direct {v3}, Ljava/lang/StringBuilder;->()V + + .line 13 + :goto_0 + invoke-virtual {v1}, Ljava/io/BufferedReader;->readLine()Ljava/lang/String; + + move-result-object v4 + + if-eqz v4, :cond_0 + + .line 14 + invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + goto :goto_0 + + .line 15 + :cond_0 + invoke-virtual {v1}, Ljava/io/BufferedReader;->close()V + + .line 16 + new-instance v1, Lorg/json/JSONObject; + + invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; + + move-result-object v3 + + invoke-direct {v1, v3}, Lorg/json/JSONObject;->(Ljava/lang/String;)V + + const-string v3, "tag_name" + + .line 17 + invoke-virtual {v1, v3}, Lorg/json/JSONObject;->getString(Ljava/lang/String;)Ljava/lang/String; + + move-result-object v3 + + const-string v4, "v" + + const-string v5, "" + + .line 18 + invoke-virtual {v3, v4, v5}, Ljava/lang/String;->replace(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Ljava/lang/String; + + move-result-object v3 + + .line 19 + invoke-direct {p0, v3}, Lcom/community/UpdateManager;->parseVersionCode(Ljava/lang/String;)I + + move-result v4 + + const v5, 0x249f0 + + if-le v4, v5, :cond_3 + + const-string v4, "assets" + + .line 20 + invoke-virtual {v1, v4}, Lorg/json/JSONObject;->getJSONArray(Ljava/lang/String;)Lorg/json/JSONArray; + + move-result-object v4 + + const/4 v5, 0x0 + + move v6, v5 + + .line 21 + :goto_1 + invoke-virtual {v4}, Lorg/json/JSONArray;->length()I + + move-result v7 + + if-ge v6, v7, :cond_3 + + .line 22 + invoke-virtual {v4, v6}, Lorg/json/JSONArray;->getJSONObject(I)Lorg/json/JSONObject; + + move-result-object v7 + + const-string v8, "name" + + .line 23 + invoke-virtual {v7, v8}, Lorg/json/JSONObject;->getString(Ljava/lang/String;)Ljava/lang/String; + + move-result-object v8 + + const-string v9, ".apk" + + .line 24 + invoke-virtual {v8, v9}, Ljava/lang/String;->endsWith(Ljava/lang/String;)Z + + move-result v8 + + if-eqz v8, :cond_2 + + const-string v4, "hasUpdate" + + const/4 v6, 0x1 + + .line 25 + invoke-virtual {v0, v4, v6}, Lorg/json/JSONObject;->put(Ljava/lang/String;Z)Lorg/json/JSONObject; + + const-string v4, "version" + + .line 26 + invoke-virtual {v0, v4, v3}, Lorg/json/JSONObject;->put(Ljava/lang/String;Ljava/lang/Object;)Lorg/json/JSONObject; + + const-string v3, "browser_download_url" + + .line 27 + invoke-virtual {v7, v3}, Lorg/json/JSONObject;->getString(Ljava/lang/String;)Ljava/lang/String; + + move-result-object v3 + + const-string v4, "downloadUrl" + + .line 28 + invoke-virtual {v0, v4, v3}, Lorg/json/JSONObject;->put(Ljava/lang/String;Ljava/lang/Object;)Lorg/json/JSONObject; + + const-string v3, "size" + + .line 29 + invoke-virtual {v7, v3}, Lorg/json/JSONObject;->getLong(Ljava/lang/String;)J + + move-result-wide v3 + + const-string v6, "fileSize" + + .line 30 + invoke-virtual {v0, v6, v3, v4}, Lorg/json/JSONObject;->put(Ljava/lang/String;J)Lorg/json/JSONObject; + + const-string v3, "published_at" + + .line 31 + invoke-virtual {v1, v3}, Lorg/json/JSONObject;->optString(Ljava/lang/String;)Ljava/lang/String; + + move-result-object v3 + + const-string v4, "releaseDate" + + .line 32 + invoke-virtual {v0, v4, v3}, Lorg/json/JSONObject;->put(Ljava/lang/String;Ljava/lang/Object;)Lorg/json/JSONObject; + + const-string v3, "body" + + .line 33 + invoke-virtual {v1, v3}, Lorg/json/JSONObject;->optString(Ljava/lang/String;)Ljava/lang/String; + + move-result-object v1 + + if-eqz v1, :cond_1 + + const-string v3, "changelog" + + .line 34 + invoke-virtual {v0, v3, v1}, Lorg/json/JSONObject;->put(Ljava/lang/String;Ljava/lang/Object;)Lorg/json/JSONObject; + + .line 35 + :cond_1 + invoke-direct {p0}, Lcom/community/UpdateManager;->getSharedPreferences()Landroid/content/SharedPreferences; + + move-result-object v1 + + .line 36 + invoke-interface {v1}, Landroid/content/SharedPreferences;->edit()Landroid/content/SharedPreferences$Editor; + + move-result-object v1 + + const-string v3, "last_update_check" + + .line 37 + invoke-static {}, Ljava/lang/System;->currentTimeMillis()J + + move-result-wide v6 + + invoke-interface {v1, v3, v6, v7}, Landroid/content/SharedPreferences$Editor;->putLong(Ljava/lang/String;J)Landroid/content/SharedPreferences$Editor; + + move-result-object v1 + + .line 38 + invoke-interface {v1}, Landroid/content/SharedPreferences$Editor;->apply()V + + goto :goto_2 + + :cond_2 + add-int/lit8 v6, v6, 0x1 + + goto :goto_1 + + :cond_3 + const-string v1, "hasUpdate" + + .line 39 + invoke-virtual {v0, v1, v5}, Lorg/json/JSONObject;->put(Ljava/lang/String;Z)Lorg/json/JSONObject; + :try_end_1 + .catch Ljava/lang/Exception; {:try_start_1 .. :try_end_1} :catch_0 + .catchall {:try_start_1 .. :try_end_1} :catchall_0 + + :cond_4 + :goto_2 + if-eqz v2, :cond_6 + + .line 40 + invoke-virtual {v2}, Ljava/net/HttpURLConnection;->disconnect()V + + goto :goto_4 + + :catchall_0 + move-exception v0 + + move-object v1, v2 + + goto :goto_5 + + :catch_0 + move-exception v1 + + move-object v9, v2 + + move-object v2, v1 + + move-object v1, v9 + + goto :goto_3 + + :catchall_1 + move-exception v0 + + goto :goto_5 + + :catch_1 + move-exception v2 + + :goto_3 + :try_start_2 + const-string v3, "UpdateManager" + + const-string v4, "Error checking for updates" + + .line 41 + invoke-static {v3, v4, v2}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I + + const-string v3, "error" + + .line 42 + invoke-virtual {v2}, Ljava/lang/Exception;->getMessage()Ljava/lang/String; + + move-result-object v2 + + invoke-virtual {v0, v3, v2}, Lorg/json/JSONObject;->put(Ljava/lang/String;Ljava/lang/Object;)Lorg/json/JSONObject; + :try_end_2 + .catchall {:try_start_2 .. :try_end_2} :catchall_1 + + if-eqz v1, :cond_6 + + .line 43 + check-cast v1, Ljava/net/HttpURLConnection; + + invoke-virtual {v1}, Ljava/net/HttpURLConnection;->disconnect()V + + .line 44 + :cond_5 + :goto_4 + invoke-virtual {v0}, Lorg/json/JSONObject;->toString()Ljava/lang/String; + + move-result-object v0 + + return-object v0 + + :goto_5 + if-eqz v1, :cond_5 + + .line 45 + check-cast v1, Ljava/net/HttpURLConnection; + + invoke-virtual {v1}, Ljava/net/HttpURLConnection;->disconnect()V + + goto :goto_4 + + .line 46 + :goto_6 + throw v0 +.end method + +.method public downloadUpdate(Ljava/lang/String;Ljava/lang/String;)V + .registers 9 + .annotation runtime Landroid/webkit/JavascriptInterface; + .end annotation + + const-string v0, "UpdateManager" + + .line 1 + new-instance v1, Ljava/lang/StringBuilder; + + invoke-direct {v1}, Ljava/lang/StringBuilder;->()V + + const-string v2, "Starting download: " + + invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + invoke-virtual {v1, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; + + move-result-object v1 + + invoke-static {v0, v1}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I + + .line 2 + :try_start_0 + new-instance v0, Landroid/app/DownloadManager$Request; + + invoke-static {p1}, Landroid/net/Uri;->parse(Ljava/lang/String;)Landroid/net/Uri; + + move-result-object p1 + + invoke-direct {v0, p1}, Landroid/app/DownloadManager$Request;->(Landroid/net/Uri;)V + + .line 3 + invoke-virtual {p0}, Lcom/community/UpdateManager;->isWifiOnlyEnabled()Z + + move-result p1 + + if-eqz p1, :cond_0 + + const/4 p1, 0x2 + + .line 4 + invoke-virtual {v0, p1}, Landroid/app/DownloadManager$Request;->setAllowedNetworkTypes(I)Landroid/app/DownloadManager$Request; + + goto :goto_0 + + :cond_0 + const/4 p1, 0x3 + + .line 5 + invoke-virtual {v0, p1}, Landroid/app/DownloadManager$Request;->setAllowedNetworkTypes(I)Landroid/app/DownloadManager$Request; + + :goto_0 + const/4 p1, 0x1 + + .line 6 + invoke-virtual {v0, p1}, Landroid/app/DownloadManager$Request;->setAllowedOverRoaming(Z)Landroid/app/DownloadManager$Request; + + const-string v1, "RR3 Community Update" + + .line 7 + invoke-virtual {v0, v1}, Landroid/app/DownloadManager$Request;->setTitle(Ljava/lang/CharSequence;)Landroid/app/DownloadManager$Request; + + .line 8 + new-instance v1, Ljava/lang/StringBuilder; + + invoke-direct {v1}, Ljava/lang/StringBuilder;->()V + + const-string v2, "Downloading RR3 v" + + invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + invoke-virtual {v1, p2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; + + move-result-object v1 + + invoke-virtual {v0, v1}, Landroid/app/DownloadManager$Request;->setDescription(Ljava/lang/CharSequence;)Landroid/app/DownloadManager$Request; + + .line 9 + invoke-virtual {v0, p1}, Landroid/app/DownloadManager$Request;->setNotificationVisibility(I)Landroid/app/DownloadManager$Request; + + .line 10 + sget-object p1, Landroid/os/Environment;->DIRECTORY_DOWNLOADS:Ljava/lang/String; + + new-instance v1, Ljava/lang/StringBuilder; + + invoke-direct {v1}, Ljava/lang/StringBuilder;->()V + + const-string v2, "RR3-v" + + invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + invoke-virtual {v1, p2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + const-string p2, "-community-alpha.apk" + + invoke-virtual {v1, p2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; + + move-result-object p2 + + invoke-virtual {v0, p1, p2}, Landroid/app/DownloadManager$Request;->setDestinationInExternalPublicDir(Ljava/lang/String;Ljava/lang/String;)Landroid/app/DownloadManager$Request; + + const-string p1, "application/vnd.android.package-archive" + + .line 11 + invoke-virtual {v0, p1}, Landroid/app/DownloadManager$Request;->setMimeType(Ljava/lang/String;)Landroid/app/DownloadManager$Request; + + .line 12 + iget-object p1, p0, Lcom/community/UpdateManager;->downloadManager:Landroid/app/DownloadManager; + + invoke-virtual {p1, v0}, Landroid/app/DownloadManager;->enqueue(Landroid/app/DownloadManager$Request;)J + + move-result-wide p1 + + iput-wide p1, p0, Lcom/community/UpdateManager;->downloadId:J + + .line 13 + invoke-direct {p0}, Lcom/community/UpdateManager;->getSharedPreferences()Landroid/content/SharedPreferences; + + move-result-object p1 + + invoke-interface {p1}, Landroid/content/SharedPreferences;->edit()Landroid/content/SharedPreferences$Editor; + + move-result-object p1 + + const-string p2, "download_id" + + iget-wide v0, p0, Lcom/community/UpdateManager;->downloadId:J + + .line 14 + invoke-interface {p1, p2, v0, v1}, Landroid/content/SharedPreferences$Editor;->putLong(Ljava/lang/String;J)Landroid/content/SharedPreferences$Editor; + + move-result-object p1 + + .line 15 + invoke-interface {p1}, Landroid/content/SharedPreferences$Editor;->apply()V + :try_end_0 + .catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0 + + goto :goto_1 + + :catch_0 + move-exception p1 + + const-string p2, "UpdateManager" + + const-string v0, "Error starting download" + + .line 16 + invoke-static {p2, v0, p1}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I + + :goto_1 + return-void +.end method + +.method public getDownloadProgress()I + .registers 10 + .annotation runtime Landroid/webkit/JavascriptInterface; + .end annotation + + .line 1 + iget-wide v0, p0, Lcom/community/UpdateManager;->downloadId:J + + const-wide/16 v2, -0x1 + + cmp-long v0, v0, v2 + + const/4 v1, -0x1 + + if-nez v0, :cond_0 + + return v1 + + .line 2 + :cond_0 + :try_start_0 + new-instance v0, Landroid/app/DownloadManager$Query; + + invoke-direct {v0}, Landroid/app/DownloadManager$Query;->()V + + const/4 v2, 0x1 + + new-array v2, v2, [J + + const/4 v3, 0x0 + + .line 3 + iget-wide v4, p0, Lcom/community/UpdateManager;->downloadId:J + + aput-wide v4, v2, v3 + + invoke-virtual {v0, v2}, Landroid/app/DownloadManager$Query;->setFilterById([J)Landroid/app/DownloadManager$Query; + + .line 4 + iget-object v2, p0, Lcom/community/UpdateManager;->downloadManager:Landroid/app/DownloadManager; + + invoke-virtual {v2, v0}, Landroid/app/DownloadManager;->query(Landroid/app/DownloadManager$Query;)Landroid/database/Cursor; + + move-result-object v0 + + if-eqz v0, :cond_2 + + .line 5 + invoke-interface {v0}, Landroid/database/Cursor;->moveToFirst()Z + + move-result v2 + + if-eqz v2, :cond_2 + + const-string v2, "bytes_so_far" + + .line 6 + invoke-interface {v0, v2}, Landroid/database/Cursor;->getColumnIndex(Ljava/lang/String;)I + + move-result v2 + + invoke-interface {v0, v2}, Landroid/database/Cursor;->getLong(I)J + + move-result-wide v2 + + const-string v4, "total_size" + + .line 7 + invoke-interface {v0, v4}, Landroid/database/Cursor;->getColumnIndex(Ljava/lang/String;)I + + move-result v4 + + invoke-interface {v0, v4}, Landroid/database/Cursor;->getLong(I)J + + move-result-wide v4 + + const-string v6, "status" + + .line 8 + invoke-interface {v0, v6}, Landroid/database/Cursor;->getColumnIndex(Ljava/lang/String;)I + + move-result v6 + + invoke-interface {v0, v6}, Landroid/database/Cursor;->getInt(I)I + + move-result v6 + + .line 9 + invoke-interface {v0}, Landroid/database/Cursor;->close()V + + const/16 v0, 0x8 + + if-ne v6, v0, :cond_1 + + const/16 v0, 0x64 + + return v0 + + :cond_1 + const-wide/16 v6, 0x0 + + cmp-long v0, v4, v6 + + if-lez v0, :cond_2 + + const-wide/16 v0, 0x64 + + mul-long/2addr v2, v0 + + .line 10 + div-long/2addr v2, v4 + + long-to-int v0, v2 + + return v0 + + :cond_2 + const/4 v0, 0x0 + + return v0 + + :catch_0 + move-exception v0 + + const-string v2, "UpdateManager" + + const-string v3, "Error getting download progress" + + .line 11 + invoke-static {v2, v3, v0}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I + :try_end_0 + .catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0 + + return v1 +.end method + +.method public installUpdate()V + .registers 8 + .annotation runtime Landroid/webkit/JavascriptInterface; + .end annotation + + .line 1 + iget-wide v0, p0, Lcom/community/UpdateManager;->downloadId:J + + const-wide/16 v2, -0x1 + + cmp-long v0, v0, v2 + + if-nez v0, :cond_0 + + return-void + + .line 2 + :cond_0 + :try_start_0 + iget-object v0, p0, Lcom/community/UpdateManager;->downloadManager:Landroid/app/DownloadManager; + + iget-wide v1, p0, Lcom/community/UpdateManager;->downloadId:J + + invoke-virtual {v0, v1, v2}, Landroid/app/DownloadManager;->getUriForDownloadedFile(J)Landroid/net/Uri; + + move-result-object v0 + + if-eqz v0, :cond_2 + + .line 3 + new-instance v1, Landroid/content/Intent; + + const-string v2, "android.intent.action.VIEW" + + invoke-direct {v1, v2}, Landroid/content/Intent;->(Ljava/lang/String;)V + + .line 4 + sget v2, Landroid/os/Build$VERSION;->SDK_INT:I + + const/16 v3, 0x18 + + if-lt v2, v3, :cond_1 + + .line 5 + iget-object v2, p0, Lcom/community/UpdateManager;->context:Landroid/content/Context; + + .line 6 + invoke-virtual {v2}, Landroid/content/Context;->getPackageName()Ljava/lang/String; + + move-result-object v3 + + new-instance v4, Ljava/lang/StringBuilder; + + invoke-direct {v4}, Ljava/lang/StringBuilder;->()V + + invoke-virtual {v4, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + const-string v3, ".fileprovider" + + invoke-virtual {v4, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + invoke-virtual {v4}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; + + move-result-object v3 + + new-instance v4, Ljava/io/File; + + .line 7 + invoke-virtual {v0}, Landroid/net/Uri;->getPath()Ljava/lang/String; + + move-result-object v0 + + invoke-direct {v4, v0}, Ljava/io/File;->(Ljava/lang/String;)V + + .line 8 + invoke-static {v2, v3, v4}, Landroidx/core/content/FileProvider;->getUriForFile(Landroid/content/Context;Ljava/lang/String;Ljava/io/File;)Landroid/net/Uri; + + move-result-object v0 + + const/4 v2, 0x1 + + .line 9 + invoke-virtual {v1, v2}, Landroid/content/Intent;->setFlags(I)Landroid/content/Intent; + + :cond_1 + const-string v2, "application/vnd.android.package-archive" + + .line 10 + invoke-virtual {v1, v0, v2}, Landroid/content/Intent;->setDataAndType(Landroid/net/Uri;Ljava/lang/String;)Landroid/content/Intent; + + const/high16 v0, 0x10000000 + + .line 11 + invoke-virtual {v1, v0}, Landroid/content/Intent;->addFlags(I)Landroid/content/Intent; + + .line 12 + iget-object v0, p0, Lcom/community/UpdateManager;->context:Landroid/content/Context; + + invoke-virtual {v0, v1}, Landroid/content/Context;->startActivity(Landroid/content/Intent;)V + :try_end_0 + .catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0 + + goto :goto_0 + + :catch_0 + move-exception v0 + + const-string v1, "UpdateManager" + + const-string v2, "Error installing update" + + .line 13 + invoke-static {v1, v2, v0}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I + + :cond_2 + :goto_0 + return-void +.end method + +.method public isWifiOnlyEnabled()Z + .registers 4 + .annotation runtime Landroid/webkit/JavascriptInterface; + .end annotation + + .line 1 + invoke-direct {p0}, Lcom/community/UpdateManager;->getSharedPreferences()Landroid/content/SharedPreferences; + + move-result-object v0 + + const-string v1, "wifi_only_updates" + + const/4 v2, 0x0 + + .line 2 + invoke-interface {v0, v1, v2}, Landroid/content/SharedPreferences;->getBoolean(Ljava/lang/String;Z)Z + + move-result v0 + + return v0 +.end method + +.method public setWifiOnlyEnabled(Z)V + .registers 4 + .annotation runtime Landroid/webkit/JavascriptInterface; + .end annotation + + .line 1 + invoke-direct {p0}, Lcom/community/UpdateManager;->getSharedPreferences()Landroid/content/SharedPreferences; + + move-result-object v0 + + .line 2 + invoke-interface {v0}, Landroid/content/SharedPreferences;->edit()Landroid/content/SharedPreferences$Editor; + + move-result-object v0 + + const-string v1, "wifi_only_updates" + + .line 3 + invoke-interface {v0, v1, p1}, Landroid/content/SharedPreferences$Editor;->putBoolean(Ljava/lang/String;Z)Landroid/content/SharedPreferences$Editor; + + move-result-object p1 + + .line 4 + invoke-interface {p1}, Landroid/content/SharedPreferences$Editor;->apply()V + + return-void +.end method + +.method public skipThisVersion(Ljava/lang/String;)V + .registers 5 + .annotation runtime Landroid/webkit/JavascriptInterface; + .end annotation + + .line 1 + :try_start_0 + invoke-direct {p0}, Lcom/community/UpdateManager;->getSharedPreferences()Landroid/content/SharedPreferences; + + move-result-object v0 + + const-string v1, "skipped_versions" + + const-string v2, "[]" + + .line 2 + invoke-interface {v0, v1, v2}, Landroid/content/SharedPreferences;->getString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + + move-result-object v0 + + .line 3 + new-instance v1, Lorg/json/JSONArray; + + invoke-direct {v1, v0}, Lorg/json/JSONArray;->(Ljava/lang/String;)V + + .line 4 + invoke-virtual {v1, p1}, Lorg/json/JSONArray;->put(Ljava/lang/Object;)Lorg/json/JSONArray; + + .line 5 + invoke-direct {p0}, Lcom/community/UpdateManager;->getSharedPreferences()Landroid/content/SharedPreferences; + + move-result-object p1 + + .line 6 + invoke-interface {p1}, Landroid/content/SharedPreferences;->edit()Landroid/content/SharedPreferences$Editor; + + move-result-object p1 + + const-string v0, "skipped_versions" + + .line 7 + invoke-virtual {v1}, Lorg/json/JSONArray;->toString()Ljava/lang/String; + + move-result-object v1 + + invoke-interface {p1, v0, v1}, Landroid/content/SharedPreferences$Editor;->putString(Ljava/lang/String;Ljava/lang/String;)Landroid/content/SharedPreferences$Editor; + + move-result-object p1 + + .line 8 + invoke-interface {p1}, Landroid/content/SharedPreferences$Editor;->apply()V + :try_end_0 + .catch Lorg/json/JSONException; {:try_start_0 .. :try_end_0} :catch_0 + + goto :goto_0 + + :catch_0 + move-exception p1 + + const-string v0, "UpdateManager" + + const-string v1, "Error skipping version" + + .line 9 + invoke-static {v0, v1, p1}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I + + :goto_0 + return-void +.end method + +.method public getCurrentVersion()Ljava/lang/String; + .registers 2 + .annotation runtime Landroid/webkit/JavascriptInterface; + .end annotation + + const-string v0, "15.0.0-community-alpha" + + return-object v0 +.end method + +.method public isNetworkSuitableForDownload()Z + .registers 2 + .annotation runtime Landroid/webkit/JavascriptInterface; + .end annotation + + .line 1 + invoke-virtual {p0}, Lcom/community/UpdateManager;->isWifiOnlyEnabled()Z + + move-result v0 + + if-eqz v0, :cond_0 + + .line 2 + invoke-direct {p0}, Lcom/community/UpdateManager;->isWifiConnected()Z + + move-result v0 + + return v0 + + :cond_0 + const/4 v0, 0x1 + + return v0 +.end method diff --git a/community_update_checker.html b/community_update_checker.html new file mode 100644 index 0000000..70d6c34 --- /dev/null +++ b/community_update_checker.html @@ -0,0 +1,752 @@ + + + + + + RR3 Update Available + + + +
+ +
+

πŸŽ‰ Update Available!

+ v15.0.1-community-alpha +
+ + +
+
+
+ πŸ“Ά +
+

WiFi Only

+

Download updates only on WiFi (saves mobile data)

+
+
+ +
+ +
+ Network: + Loading... +
+ +
+ ⚠️ You're on mobile data. This download is approximately 120 MB and may incur data charges from your carrier. +
+
+ + +
+

πŸ“ What's New

+
+
    +
  • Loading changelog...
  • +
+
+
+ + +
+
+ πŸ“¦ File Size + Loading... +
+
+ πŸ“… Release Date + Loading... +
+
+ πŸš€ Current Version + 15.0.0-community-alpha +
+
+ + +
+
+
+
+
Preparing download...
+
+ + +
+ + +
+
+ + + +