import streamlit as st import os import subprocess import shutil import zipfile import requests # Function to debug APK file def debug_apk(input_path, output_dir): # Run apk-mitm command command = f"apk-mitm {input_path} -o {output_dir}" result = subprocess.run(command, shell=True, capture_output=True, text=True) if result.returncode == 0: # Extract the patched file name from stdout output_file_name = result.stdout.split("Patched file: ")[-1].strip() output_path = os.path.join(output_dir, output_file_name) return output_path else: st.error(f"Error debugging APK: {result.stderr}") return None # Function to process XAPK file def process_xapk(xapk_path): """ Process XAPK file: rename to ZIP, extract contents, merge with APKEditor.jar, and sign the APK. Args: xapk_path (str): Path to the XAPK file Returns: str: Path to the signed APK if successful, None if failed """ try: # Get the directory and filename without extension folder = os.path.dirname(xapk_path) name_without_ext = os.path.splitext(os.path.basename(xapk_path))[0] # Create paths for zip and extraction zip_path = os.path.join(folder, f"{name_without_ext}.zip") extract_dir = os.path.join(folder, name_without_ext) # Rename XAPK to ZIP and extract contents shutil.move(xapk_path, zip_path) with zipfile.ZipFile(zip_path, 'r') as zip_ref: zip_ref.extractall(extract_dir) # Remove the ZIP file after extraction os.remove(zip_path) # Run APKEditor.jar command to merge the extracted files command = f'java -jar APKEditor.jar m -i "{extract_dir}"' result = subprocess.run(command, shell=True, capture_output=True, text=True) if result.returncode == 0: # Define the path for the merged APK merged_apk_path = os.path.join(folder, f"{name_without_ext}_merged.apk") st.write(f"Merged APK created at: {merged_apk_path}") # Proceed to sign the merged APK signed_apk_path = process_sign(merged_apk_path) return signed_apk_path else: st.error(f"Error merging APK: {result.stderr}") return None except Exception as e: st.error(f"Error processing XAPK: {str(e)}") # Clean up in case of error shutil.rmtree(extract_dir, ignore_errors=True) return None # Function to sign APK file def process_sign(apk_path): """ Sign the APK file using uber-apk-signer. Args: apk_path (str): Path to the APK file to be signed. Returns: str: Path to the signed APK if successful, None if failed. """ folder = os.path.dirname(apk_path) command = f"java -jar uber-apk-signer.jar --apks {apk_path}" result = subprocess.run(command, shell=True, capture_output=True, text=True) if result.returncode == 0: # Extract the path of the signed APK from the output directory signed_apk_path = apk_path.replace('.apk', '-aligned-debugSigned.apk') # Check if the signed APK exists at the expected path if os.path.exists(signed_apk_path): st.write("APK signing successful!") return signed_apk_path else: st.error("APK signing completed, but the signed file could not be found.") return None else: # Display the error message in a more user-friendly way st.error(f"Error signing APK: {result.stderr}") return None # Streamlit app interface st.title("APK File Processor") # File upload uploaded_file = st.file_uploader("Upload APK file", type=['apk', 'xapk', 'apks']) # URL upload url_input = st.text_input("Or enter APK URL") # Radio buttons to select the processing type processing_option = st.radio("Choose the processing type:", ('Process XAPK', 'Sign APK', 'Debug APK')) if uploaded_file is not None or url_input: # Create directories if they don't exist upload_dir = "uploads" if not os.path.exists(upload_dir): os.makedirs(upload_dir) if uploaded_file is not None: # Save uploaded file input_path = os.path.join(upload_dir, uploaded_file.name) # Write the uploaded file to the filesystem with open(input_path, "wb") as f: f.write(uploaded_file.read()) # Now that the file is saved, rename it if it contains spaces if " " in uploaded_file.name: new_name = uploaded_file.name.replace(" ", "_") # Replace spaces with underscores new_input_path = os.path.join(upload_dir, new_name) # Only rename if the new name is different if new_input_path != input_path: os.rename(input_path, new_input_path) input_path = new_input_path # Update input_path to new name elif url_input: # Download APK from URL st.write("Downloading APK from URL...") response = requests.get(url_input) if response.status_code == 200: input_path = os.path.join(upload_dir, os.path.basename(url_input)) with open(input_path, "wb") as f: f.write(response.content) else: st.error("Failed to download APK from URL. Please check the URL and try again.") st.stop() # Define output directory for the processed APK output_dir = upload_dir # Execute the process based on user choice if processing_option == 'Process XAPK' and input_path.endswith('.xapk'): st.write("Processing XAPK...") output_path = process_xapk(input_path) elif processing_option == 'Sign APK' and input_path.endswith('.apk'): st.write("Signing APK...") output_path = process_sign(input_path) elif processing_option == 'Debug APK' and input_path.endswith('.apk'): st.write("Debugging APK...") output_path = debug_apk(input_path, output_dir) else: st.error("The selected process is not compatible with the uploaded file type. Please ensure the file type matches your selection.") output_path = None # Handle the result of processing if output_path: st.success("APK processed successfully!") st.write("Output path:") st.text(output_path) # Provide download link for the processed APK file if os.path.exists(output_path): with open(output_path, "rb") as f: file_data = f.read() st.download_button( label="Download Processed APK", data=file_data, file_name=os.path.basename(output_path), mime="application/vnd.android.package-archive" ) else: st.error("Processed APK file not found. Please try again.") else: st.error("Failed to process APK. Please try again.") # Clean up the uploaded and processed files def cleanup_files(): shutil.rmtree(upload_dir, ignore_errors=True) # Cleanup is done after download button appears to ensure the file is available for download cleanup_files()