| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>APK Editor</title> |
| <style> |
| :root { |
| --primary-color: #4285f4; |
| --secondary-color: #34a853; |
| --danger-color: #ea4335; |
| --warning-color: #fbbc05; |
| --light-color: #f8f9fa; |
| --dark-color: #343a40; |
| } |
| |
| * { |
| box-sizing: border-box; |
| margin: 0; |
| padding: 0; |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
| } |
| |
| body { |
| background-color: #f5f5f5; |
| color: #333; |
| line-height: 1.6; |
| } |
| |
| .container { |
| max-width: 1200px; |
| margin: 0 auto; |
| padding: 20px; |
| } |
| |
| header { |
| background-color: var(--primary-color); |
| color: white; |
| padding: 20px 0; |
| text-align: center; |
| margin-bottom: 30px; |
| border-radius: 5px; |
| box-shadow: 0 2px 5px rgba(0,0,0,0.1); |
| } |
| |
| h1 { |
| margin-bottom: 10px; |
| } |
| |
| .card { |
| background-color: white; |
| border-radius: 5px; |
| box-shadow: 0 2px 10px rgba(0,0,0,0.1); |
| padding: 20px; |
| margin-bottom: 20px; |
| } |
| |
| .card h2 { |
| margin-bottom: 15px; |
| color: var(--primary-color); |
| border-bottom: 1px solid #eee; |
| padding-bottom: 10px; |
| } |
| |
| .form-group { |
| margin-bottom: 15px; |
| } |
| |
| label { |
| display: block; |
| margin-bottom: 5px; |
| font-weight: bold; |
| } |
| |
| input[type="file"] { |
| display: none; |
| } |
| |
| .file-upload { |
| border: 2px dashed #ccc; |
| padding: 20px; |
| text-align: center; |
| cursor: pointer; |
| margin-bottom: 10px; |
| border-radius: 5px; |
| transition: all 0.3s; |
| } |
| |
| .file-upload:hover { |
| border-color: var(--primary-color); |
| background-color: #f8f9fa; |
| } |
| |
| .btn { |
| display: inline-block; |
| background-color: var(--primary-color); |
| color: white; |
| padding: 10px 20px; |
| border: none; |
| border-radius: 5px; |
| cursor: pointer; |
| text-decoration: none; |
| font-size: 16px; |
| transition: background-color 0.3s; |
| } |
| |
| .btn:hover { |
| background-color: #3367d6; |
| } |
| |
| .btn-secondary { |
| background-color: var(--secondary-color); |
| } |
| |
| .btn-secondary:hover { |
| background-color: #2d9249; |
| } |
| |
| .btn-danger { |
| background-color: var(--danger-color); |
| } |
| |
| .btn-danger:hover { |
| background-color: #d33426; |
| } |
| |
| .progress-container { |
| width: 100%; |
| background-color: #f1f1f1; |
| border-radius: 5px; |
| margin: 10px 0; |
| display: none; |
| } |
| |
| .progress-bar { |
| height: 20px; |
| background-color: var(--primary-color); |
| border-radius: 5px; |
| width: 0%; |
| transition: width 0.3s; |
| text-align: center; |
| color: white; |
| line-height: 20px; |
| font-size: 12px; |
| } |
| |
| .status { |
| padding: 10px; |
| border-radius: 5px; |
| margin-bottom: 10px; |
| display: none; |
| } |
| |
| .status.success { |
| background-color: #d4edda; |
| color: #155724; |
| display: block; |
| } |
| |
| .status.error { |
| background-color: #f8d7da; |
| color: #721c24; |
| display: block; |
| } |
| |
| .status.info { |
| background-color: #d1ecf1; |
| color: #0c5460; |
| display: block; |
| } |
| |
| .icon-preview { |
| max-width: 100px; |
| max-height: 100px; |
| margin: 10px 0; |
| display: none; |
| } |
| |
| .step { |
| margin-bottom: 30px; |
| } |
| |
| .step-number { |
| display: inline-block; |
| background-color: var(--primary-color); |
| color: white; |
| width: 30px; |
| height: 30px; |
| text-align: center; |
| line-height: 30px; |
| border-radius: 50%; |
| margin-right: 10px; |
| } |
| |
| .hidden { |
| display: none; |
| } |
| |
| #downloadBtn { |
| display: none; |
| } |
| |
| @media (max-width: 768px) { |
| .container { |
| padding: 10px; |
| } |
| |
| .card { |
| padding: 15px; |
| } |
| } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
| <header> |
| <h1>APK Editor</h1> |
| <p>Modify and customize your Android APK files</p> |
| </header> |
| |
| <div class="card"> |
| <div class="step"> |
| <h2><span class="step-number">1</span>Upload APK</h2> |
| <div class="form-group"> |
| <label for="apkFile">Select APK File:</label> |
| <input type="file" id="apkFile" accept=".apk"> |
| <div class="file-upload" id="fileUpload"> |
| <p>Click to select APK file or drag and drop here</p> |
| <p id="fileName">No file selected</p> |
| </div> |
| <button class="btn" id="uploadBtn">Upload</button> |
| </div> |
| <div class="progress-container" id="uploadProgressContainer"> |
| <div class="progress-bar" id="uploadProgressBar">0%</div> |
| </div> |
| <div class="status" id="uploadStatus"></div> |
| </div> |
| |
| <div class="step hidden" id="decompileStep"> |
| <h2><span class="step-number">2</span>Decompile APK</h2> |
| <p>Decompile the APK to access its resources and manifest</p> |
| <button class="btn" id="decompileBtn">Decompile</button> |
| <div class="progress-container" id="decompileProgressContainer"> |
| <div class="progress-bar" id="decompileProgressBar">0%</div> |
| </div> |
| <div class="status" id="decompileStatus"></div> |
| <img id="appIcon" class="icon-preview" alt="App Icon"> |
| </div> |
| |
| <div class="step hidden" id="modifyStep"> |
| <h2><span class="step-number">3</span>Modify APK</h2> |
| <div class="form-group"> |
| <label for="appName">New App Name:</label> |
| <input type="text" id="appName" class="form-control" placeholder="Enter new app name"> |
| </div> |
| <div class="form-group"> |
| <label for="packageName">New Package Name:</label> |
| <input type="text" id="packageName" class="form-control" placeholder="com.example.newapp"> |
| </div> |
| <div class="form-group"> |
| <label for="versionCode">Version Code:</label> |
| <input type="number" id="versionCode" class="form-control" placeholder="1"> |
| </div> |
| <div class="form-group"> |
| <label for="versionName">Version Name:</label> |
| <input type="text" id="versionName" class="form-control" placeholder="1.0"> |
| </div> |
| <button class="btn" id="saveChangesBtn">Save Changes</button> |
| <div class="status" id="modifyStatus"></div> |
| </div> |
| |
| <div class="step hidden" id="recompileStep"> |
| <h2><span class="step-number">4</span>Recompile APK</h2> |
| <p>Recompile the modified APK</p> |
| <button class="btn" id="recompileBtn">Recompile</button> |
| <div class="progress-container" id="recompileProgressContainer"> |
| <div class="progress-bar" id="recompileProgressBar">0%</div> |
| </div> |
| <div class="status" id="recompileStatus"></div> |
| </div> |
| |
| <div class="step hidden" id="signStep"> |
| <h2><span class="step-number">5</span>Sign APK</h2> |
| <p>Sign the APK with a debug key</p> |
| <button class="btn" id="signBtn">Sign APK</button> |
| <div class="progress-container" id="signProgressContainer"> |
| <div class="progress-bar" id="signProgressBar">0%</div> |
| </div> |
| <div class="status" id="signStatus"></div> |
| </div> |
| |
| <div class="step hidden" id="downloadStep"> |
| <h2><span class="step-number">6</span>Download APK</h2> |
| <p>Your modified APK is ready!</p> |
| <a href="#" class="btn btn-secondary" id="downloadBtn">Download APK</a> |
| <div class="status" id="downloadStatus"></div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| |
| let currentApk = { |
| path: null, |
| decompiledPath: null, |
| modifiedPath: null, |
| signedPath: null |
| }; |
| |
| |
| const apkFileInput = document.getElementById('apkFile'); |
| const fileUpload = document.getElementById('fileUpload'); |
| const fileName = document.getElementById('fileName'); |
| const uploadBtn = document.getElementById('uploadBtn'); |
| const uploadProgressContainer = document.getElementById('uploadProgressContainer'); |
| const uploadProgressBar = document.getElementById('uploadProgressBar'); |
| const uploadStatus = document.getElementById('uploadStatus'); |
| |
| const decompileStep = document.getElementById('decompileStep'); |
| const decompileBtn = document.getElementById('decompileBtn'); |
| const decompileProgressContainer = document.getElementById('decompileProgressContainer'); |
| const decompileProgressBar = document.getElementById('decompileProgressBar'); |
| const decompileStatus = document.getElementById('decompileStatus'); |
| const appIcon = document.getElementById('appIcon'); |
| |
| const modifyStep = document.getElementById('modifyStep'); |
| const saveChangesBtn = document.getElementById('saveChangesBtn'); |
| const modifyStatus = document.getElementById('modifyStatus'); |
| |
| const recompileStep = document.getElementById('recompileStep'); |
| const recompileBtn = document.getElementById('recompileBtn'); |
| const recompileProgressContainer = document.getElementById('recompileProgressContainer'); |
| const recompileProgressBar = document.getElementById('recompileProgressBar'); |
| const recompileStatus = document.getElementById('recompileStatus'); |
| |
| const signStep = document.getElementById('signStep'); |
| const signBtn = document.getElementById('signBtn'); |
| const signProgressContainer = document.getElementById('signProgressContainer'); |
| const signProgressBar = document.getElementById('signProgressBar'); |
| const signStatus = document.getElementById('signStatus'); |
| |
| const downloadStep = document.getElementById('downloadStep'); |
| const downloadBtn = document.getElementById('downloadBtn'); |
| const downloadStatus = document.getElementById('downloadStatus'); |
| |
| |
| fileUpload.addEventListener('click', () => apkFileInput.click()); |
| |
| apkFileInput.addEventListener('change', (e) => { |
| if (e.target.files.length > 0) { |
| fileName.textContent = e.target.files[0].name; |
| } else { |
| fileName.textContent = 'No file selected'; |
| } |
| }); |
| |
| |
| fileUpload.addEventListener('dragover', (e) => { |
| e.preventDefault(); |
| fileUpload.style.borderColor = '#4285f4'; |
| fileUpload.style.backgroundColor = '#f8f9fa'; |
| }); |
| |
| fileUpload.addEventListener('dragleave', () => { |
| fileUpload.style.borderColor = '#ccc'; |
| fileUpload.style.backgroundColor = 'transparent'; |
| }); |
| |
| fileUpload.addEventListener('drop', (e) => { |
| e.preventDefault(); |
| fileUpload.style.borderColor = '#ccc'; |
| fileUpload.style.backgroundColor = 'transparent'; |
| |
| if (e.dataTransfer.files.length > 0) { |
| const file = e.dataTransfer.files[0]; |
| if (file.name.endsWith('.apk')) { |
| apkFileInput.files = e.dataTransfer.files; |
| fileName.textContent = file.name; |
| } else { |
| showStatus(uploadStatus, 'Only APK files are allowed', 'error'); |
| } |
| } |
| }); |
| |
| uploadBtn.addEventListener('click', uploadApk); |
| decompileBtn.addEventListener('click', decompileApk); |
| saveChangesBtn.addEventListener('click', saveChanges); |
| recompileBtn.addEventListener('click', recompileApk); |
| signBtn.addEventListener('click', signApk); |
| |
| |
| function showStatus(element, message, type) { |
| element.textContent = message; |
| element.className = 'status ' + type; |
| } |
| |
| function resetStatus(element) { |
| element.textContent = ''; |
| element.className = 'status'; |
| element.style.display = 'none'; |
| } |
| |
| function updateProgressBar(bar, container, percentage) { |
| bar.style.width = percentage + '%'; |
| bar.textContent = percentage + '%'; |
| container.style.display = 'block'; |
| } |
| |
| function hideProgressBar(container) { |
| container.style.display = 'none'; |
| } |
| |
| async function uploadApk() { |
| if (!apkFileInput.files || apkFileInput.files.length === 0) { |
| showStatus(uploadStatus, 'Please select an APK file first', 'error'); |
| return; |
| } |
| |
| const file = apkFileInput.files[0]; |
| const formData = new FormData(); |
| formData.append('file', file); |
| |
| resetStatus(uploadStatus); |
| updateProgressBar(uploadProgressBar, uploadProgressContainer, 0); |
| |
| try { |
| const response = await fetch('/api/upload', { |
| method: 'POST', |
| body: formData, |
| }); |
| |
| const data = await response.json(); |
| |
| if (response.ok) { |
| currentApk.path = data.filepath; |
| showStatus(uploadStatus, 'APK uploaded successfully!', 'success'); |
| updateProgressBar(uploadProgressBar, uploadProgressContainer, 100); |
| |
| |
| decompileStep.classList.remove('hidden'); |
| |
| |
| extractAppIcon(); |
| } else { |
| showStatus(uploadStatus, data.error || 'Upload failed', 'error'); |
| hideProgressBar(uploadProgressContainer); |
| } |
| } catch (error) { |
| showStatus(uploadStatus, 'Error uploading file: ' + error.message, 'error'); |
| hideProgressBar(uploadProgressContainer); |
| } |
| } |
| |
| async function extractAppIcon() { |
| try { |
| const response = await fetch('/api/extract_icon', { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify({ |
| apk_path: currentApk.path |
| }), |
| }); |
| |
| if (response.ok) { |
| const blob = await response.blob(); |
| const objectURL = URL.createObjectURL(blob); |
| appIcon.src = objectURL; |
| appIcon.style.display = 'block'; |
| } |
| } catch (error) { |
| console.error('Error extracting app icon:', error); |
| } |
| } |
| |
| async function decompileApk() { |
| if (!currentApk.path) { |
| showStatus(decompileStatus, 'No APK file available', 'error'); |
| return; |
| } |
| |
| resetStatus(decompileStatus); |
| updateProgressBar(decompileProgressBar, decompileProgressContainer, 0); |
| |
| try { |
| const response = await fetch('/api/decompile', { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify({ |
| apk_path: currentApk.path |
| }), |
| }); |
| |
| const data = await response.json(); |
| |
| if (response.ok) { |
| currentApk.decompiledPath = data.output_dir; |
| showStatus(decompileStatus, 'APK decompiled successfully!', 'success'); |
| updateProgressBar(decompileProgressBar, decompileProgressContainer, 100); |
| |
| |
| modifyStep.classList.remove('hidden'); |
| } else { |
| showStatus(decompileStatus, data.error || 'Decompilation failed', 'error'); |
| hideProgressBar(decompileProgressContainer); |
| } |
| } catch (error) { |
| showStatus(decompileStatus, 'Error decompiling APK: ' + error.message, 'error'); |
| hideProgressBar(decompileProgressContainer); |
| } |
| } |
| |
| async function saveChanges() { |
| const appName = document.getElementById('appName').value; |
| const packageName = document.getElementById('packageName').value; |
| const versionCode = document.getElementById('versionCode').value; |
| const versionName = document.getElementById('versionName').value; |
| |
| if (!appName && !packageName && !versionCode && !versionName) { |
| showStatus(modifyStatus, 'No changes specified', 'error'); |
| return; |
| } |
| |
| resetStatus(modifyStatus); |
| |
| try { |
| const response = await fetch('/api/modify_manifest', { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify({ |
| decompiled_dir: currentApk.decompiledPath, |
| modifications: [ |
| { |
| action: 'set_attribute', |
| path: '.', |
| attribute: 'package', |
| value: packageName || 'com.example.app' |
| }, |
| { |
| action: 'set_attribute', |
| path: '.', |
| attribute: 'android:versionCode', |
| value: versionCode || '1' |
| }, |
| { |
| action: 'set_attribute', |
| path: '.', |
| attribute: 'android:versionName', |
| value: versionName || '1.0' |
| }, |
| { |
| action: 'set_attribute', |
| path: 'application', |
| attribute: 'android:label', |
| value: appName || 'My App' |
| } |
| ] |
| }), |
| }); |
| |
| const data = await response.json(); |
| |
| if (response.ok) { |
| showStatus(modifyStatus, 'Changes saved successfully!', 'success'); |
| |
| |
| recompileStep.classList.remove('hidden'); |
| } else { |
| showStatus(modifyStatus, data.error || 'Failed to save changes', 'error'); |
| } |
| } catch (error) { |
| showStatus(modifyStatus, 'Error saving changes: ' + error.message, 'error'); |
| } |
| } |
| |
| async function recompileApk() { |
| if (!currentApk.decompiledPath) { |
| showStatus(recompileStatus, 'No decompiled APK available', 'error'); |
| return; |
| } |
| |
| resetStatus(recompileStatus); |
| updateProgressBar(recompileProgressBar, recompileProgressContainer, 0); |
| |
| try { |
| const response = await fetch('/api/recompile', { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify({ |
| decompiled_dir: currentApk.decompiledPath |
| }), |
| }); |
| |
| const data = await response.json(); |
| |
| if (response.ok) { |
| currentApk.modifiedPath = data.output_apk; |
| showStatus(recompileStatus, 'APK recompiled successfully!', 'success'); |
| updateProgressBar(recompileProgressBar, recompileProgressContainer, 100); |
| |
| |
| signStep.classList.remove('hidden'); |
| } else { |
| showStatus(recompileStatus, data.error || 'Recompilation failed', 'error'); |
| hideProgressBar(recompileProgressContainer); |
| } |
| } catch (error) { |
| showStatus(recompileStatus, 'Error recompiling APK: ' + error.message, 'error'); |
| hideProgressBar(recompileProgressContainer); |
| } |
| } |
| |
| async function signApk() { |
| if (!currentApk.modifiedPath) { |
| showStatus(signStatus, 'No modified APK available', 'error'); |
| return; |
| } |
| |
| resetStatus(signStatus); |
| updateProgressBar(signProgressBar, signProgressContainer, 0); |
| |
| try { |
| const response = await fetch('/api/sign', { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify({ |
| apk_path: currentApk.modifiedPath |
| }), |
| }); |
| |
| const data = await response.json(); |
| |
| if (response.ok) { |
| currentApk.signedPath = data.signed_apk; |
| showStatus(signStatus, 'APK signed successfully!', 'success'); |
| updateProgressBar(signProgressBar, signProgressContainer, 100); |
| |
| |
| downloadStep.classList.remove('hidden'); |
| downloadBtn.href = `/api/download?apk_path=${encodeURIComponent(currentApk.signedPath)}`; |
| downloadBtn.style.display = 'inline-block'; |
| } else { |
| showStatus(signStatus, data.error || 'Signing failed', 'error'); |
| hideProgressBar(signProgressContainer); |
| } |
| } catch (error) { |
| showStatus(signStatus, 'Error signing APK: ' + error.message, 'error'); |
| hideProgressBar(signProgressContainer); |
| } |
| } |
| </script> |
| </body> |
| </html> |