Last active
February 14, 2026 18:22
-
-
Save cw2k/f6d35659845ae049f44b8d5ed080acc9 to your computer and use it in GitHub Desktop.
Android: Developer Assistant - LicenseTool
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * LicenseTool for Developer Assistant | |
| * ----------------------------------- | |
| * Author: Jarosław Wiśniewski | |
| * App: https://play.google.com/store/apps/details?id=com.appsisle.developerassistant | |
| * | |
| * Description: | |
| * This standalone Java console tool can decrypt and re-encrypt license data | |
| * used by the Developer Assistant application. It performs the following steps: | |
| * | |
| * 1. Reads license.xml and crypto_preferences.xml from the current directory. | |
| * 2. Decrypts all license fields using the AES/CBC/PKCS5Padding scheme. | |
| * 3. Writes the decrypted values into license_decoded.txt. | |
| * 4. Opens the file in the system's default text editor. | |
| * 5. Waits for a key press. | |
| * 6. Reads the modified values from license_decoded.txt. | |
| * 7. Validates the status field (Permanent, Subscription, Evaluation, Expired, NotKnownYet, Invalid). | |
| * 8. Re-encrypts the values and writes licenseNew.xml. | |
| * | |
| * The tool is error-tolerant: malformed values, invalid status, | |
| * or formatting issues will not stop execution. Warnings are printed instead. | |
| * | |
| * Compilation: | |
| * javac LicenseTool.java | |
| * | |
| * Execution: | |
| * java LicenseTool | |
| * | |
| * Requirements: | |
| * - Java 11 or newer | |
| * - license.xml and crypto_preferences.xml must exist in the same directory | |
| */ | |
| import javax.crypto.Cipher; | |
| import javax.crypto.spec.IvParameterSpec; | |
| import javax.crypto.spec.SecretKeySpec; | |
| import javax.xml.parsers.DocumentBuilder; | |
| import javax.xml.parsers.DocumentBuilderFactory; | |
| import org.w3c.dom.*; | |
| import java.io.File; | |
| import java.nio.file.*; | |
| import java.security.MessageDigest; | |
| import java.time.*; | |
| import java.util.*; | |
| import java.util.Base64; | |
| public class LicenseTool { | |
| private static final String FIXED_PREFIX = "@TeL8362N%F3976"; | |
| private static final String FALLBACK_SEED = | |
| "FrdLFGcCo0bwn88FnLG+4PVccMVwUlqQoz5Ij2FBVKbM2XN8v59Tyw7k2X9sCavdbQi8RyCsXt8yrTwOUY9CJiIc86l16JFtpto6Uzc8xmu2iY+2XkrEftNQfJG0jnybKM3//Mls2cbslqxL7uPaqyvAQ0ROTzSWUyMIH0sSRifRP6WZ5l6+LUxosx9uuJNFNiLG4CJtW9LGFKnAKENd6tTJT7ALXEwZgCHY7acXAz3Thi50J9CCcSlBaRHYE4up4OyD6LE1xwbUsqeR+kr2ePrY+hF5NfS2dviZyVDYlog5OAgIUSIIV60VKLDx2E0ypveLzmjteAeHdRoKZHApGA"; | |
| private static final String[] VALID_STATUS = { | |
| "Permanent", | |
| "Subscription", | |
| "Evaluation", | |
| "Expired", | |
| "NotKnownYet", | |
| "Invalid" | |
| }; | |
| public static void main(String[] args) { | |
| System.out.println("=== LicenseTool for Developer Assistant ==="); | |
| try { | |
| decryptWorkflow(); | |
| System.out.println("\nEdit license_decoded.txt, then press ENTER in this window..."); | |
| System.in.read(); | |
| encryptWorkflow(); | |
| System.out.println("\nDone. New file: licenseNew.xml"); | |
| } catch (Exception e) { | |
| System.out.println("Unexpected error: " + e.getMessage()); | |
| } | |
| } | |
| // --------------------------------------------------------- | |
| // 1) DECRYPT WORKFLOW | |
| // --------------------------------------------------------- | |
| private static void decryptWorkflow() { | |
| try { | |
| Map<String, String> values = loadLicenseValues(); | |
| if (values.isEmpty()) { | |
| System.out.println("ERROR: license.xml could not be parsed or is missing."); | |
| return; | |
| } | |
| String seed = loadSeed(); | |
| String remain = safeDecrypt(values.get("remain"), seed); | |
| String expiryRaw = safeDecrypt(values.get("expiry"), seed); | |
| String renews = safeDecrypt(values.get("renews"), seed); | |
| String status = safeDecrypt(values.get("status"), seed); | |
| String expiryFormatted = "UNKNOWN"; | |
| try { | |
| long ms = Long.parseLong(expiryRaw); | |
| expiryFormatted = formatExpiry(ms); | |
| } catch (Exception e) { | |
| System.out.println("WARNING: expiry could not be formatted: " + e.getMessage()); | |
| } | |
| String out = | |
| "remain=" + remain + "\n" + | |
| "expiry=" + expiryRaw + "\n" + | |
| "expiry_formatted=" + expiryFormatted + "\n" + | |
| "renews=" + renews + "\n" + | |
| "status=" + status + "\n"; | |
| Files.writeString(Paths.get("license_decoded.txt"), out); | |
| openEditor("license_decoded.txt"); | |
| } catch (Exception e) { | |
| System.out.println("Error during decrypt workflow: " + e.getMessage()); | |
| } | |
| } | |
| // --------------------------------------------------------- | |
| // 2) ENCRYPT WORKFLOW | |
| // --------------------------------------------------------- | |
| private static void encryptWorkflow() { | |
| try { | |
| List<String> lines = Files.readAllLines(Paths.get("license_decoded.txt")); | |
| Map<String, String> map = new HashMap<>(); | |
| for (String line : lines) { | |
| if (!line.contains("=")) continue; | |
| String[] p = line.split("=", 2); | |
| map.put(p[0].trim(), p[1].trim()); | |
| } | |
| String seed = loadSeed(); | |
| String status = map.get("status"); | |
| if (!isValidStatus(status)) { | |
| System.out.println("WARNING: Invalid status \"" + status + "\""); | |
| System.out.println("Allowed: " + String.join(", ", VALID_STATUS)); | |
| } | |
| String remainEnc = safeEncrypt(map.get("remain"), seed); | |
| String expiryEnc = safeEncrypt(map.get("expiry"), seed); | |
| String renewsEnc = safeEncrypt(map.get("renews"), seed); | |
| String statusEnc = safeEncrypt(status, seed); | |
| String xml = | |
| "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" + | |
| "<map>\n" + | |
| " <string name=\"remain\">" + remainEnc + "</string>\n" + | |
| " <string name=\"expiry\">" + expiryEnc + "</string>\n" + | |
| " <string name=\"renews\">" + renewsEnc + "</string>\n" + | |
| " <string name=\"status\">" + statusEnc + "</string>\n" + | |
| "</map>\n"; | |
| Files.writeString(Paths.get("licenseNew.xml"), xml); | |
| } catch (Exception e) { | |
| System.out.println("Error during encrypt workflow: " + e.getMessage()); | |
| } | |
| } | |
| // --------------------------------------------------------- | |
| // XML PARSING | |
| // --------------------------------------------------------- | |
| private static Map<String, String> loadLicenseValues() { | |
| Map<String, String> map = new HashMap<>(); | |
| try { | |
| File file = new File("license.xml"); | |
| if (!file.exists()) { | |
| System.out.println("WARNING: license.xml not found."); | |
| return map; | |
| } | |
| DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); | |
| DocumentBuilder builder = factory.newDocumentBuilder(); | |
| Document doc = builder.parse(file); | |
| NodeList list = doc.getElementsByTagName("string"); | |
| for (int i = 0; i < list.getLength(); i++) { | |
| Element el = (Element) list.item(i); | |
| String name = el.getAttribute("name"); | |
| String value = el.getTextContent().trim(); | |
| if ("remain".equals(name) || | |
| "expiry".equals(name) || | |
| "renews".equals(name) || | |
| "status".equals(name)) { | |
| map.put(name, value); | |
| } | |
| } | |
| } catch (Exception e) { | |
| System.out.println("WARNING: Failed to parse license.xml: " + e.getMessage()); | |
| } | |
| return map; | |
| } | |
| private static String loadSeed() { | |
| try { | |
| File file = new File("crypto_preferences.xml"); | |
| if (!file.exists()) { | |
| System.out.println("WARNING: crypto_preferences.xml not found — using fallback seed"); | |
| return FALLBACK_SEED; | |
| } | |
| DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); | |
| DocumentBuilder builder = factory.newDocumentBuilder(); | |
| Document doc = builder.parse(file); | |
| NodeList list = doc.getElementsByTagName("string"); | |
| for (int i = 0; i < list.getLength(); i++) { | |
| Element el = (Element) list.item(i); | |
| if ("crypto_seed".equals(el.getAttribute("name"))) { | |
| String seed = el.getTextContent().trim(); | |
| if (seed.isEmpty()) { | |
| System.out.println("WARNING: crypto_seed is empty — using fallback seed"); | |
| return FALLBACK_SEED; | |
| } | |
| return seed; | |
| } | |
| } | |
| System.out.println("WARNING: crypto_seed not found in crypto_preferences.xml — using fallback seed"); | |
| return FALLBACK_SEED; | |
| } catch (Exception e) { | |
| System.out.println("WARNING: Failed to parse crypto_preferences.xml — using fallback seed"); | |
| return FALLBACK_SEED; | |
| } | |
| } | |
| // --------------------------------------------------------- | |
| // CRYPTO HELPERS | |
| // --------------------------------------------------------- | |
| private static String safeDecrypt(String base64, String seed) { | |
| if (base64 == null || base64.isEmpty()) return ""; | |
| try { | |
| return decrypt(base64, seed); | |
| } catch (Exception e) { | |
| System.out.println("WARNING: Could not decrypt value: " + e.getMessage()); | |
| return ""; | |
| } | |
| } | |
| private static String safeEncrypt(String plaintext, String seed) { | |
| if (plaintext == null) plaintext = ""; | |
| try { | |
| return encrypt(plaintext, seed); | |
| } catch (Exception e) { | |
| System.out.println("WARNING: Could not encrypt value: " + e.getMessage()); | |
| return ""; | |
| } | |
| } | |
| private static String decrypt(String base64, String seed) throws Exception { | |
| byte[] full = Base64.getDecoder().decode(base64); | |
| if (full.length < 17) throw new IllegalArgumentException("ciphertext too short"); | |
| byte[] iv = Arrays.copyOfRange(full, 0, 16); | |
| byte[] ciphertext = Arrays.copyOfRange(full, 16, full.length); | |
| SecretKeySpec key = deriveKey(seed); | |
| Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); | |
| cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); | |
| return new String(cipher.doFinal(ciphertext), "UTF-8"); | |
| } | |
| private static String encrypt(String plaintext, String seed) throws Exception { | |
| SecretKeySpec key = deriveKey(seed); | |
| byte[] iv = new byte[16]; | |
| new java.security.SecureRandom().nextBytes(iv); | |
| Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); | |
| cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv)); | |
| byte[] encrypted = cipher.doFinal(plaintext.getBytes("UTF-8")); | |
| byte[] full = new byte[iv.length + encrypted.length]; | |
| System.arraycopy(iv, 0, full, 0, iv.length); | |
| System.arraycopy(encrypted, 0, full, iv.length, encrypted.length); | |
| return Base64.getEncoder().encodeToString(full); | |
| } | |
| private static SecretKeySpec deriveKey(String seed) throws Exception { | |
| String keyString = FIXED_PREFIX + seed; | |
| MessageDigest sha = MessageDigest.getInstance("SHA-256"); | |
| byte[] keyBytes = sha.digest(keyString.getBytes("UTF-8")); | |
| return new SecretKeySpec(Arrays.copyOf(keyBytes, 32), "AES"); | |
| } | |
| // --------------------------------------------------------- | |
| // HELPERS | |
| // --------------------------------------------------------- | |
| private static boolean isValidStatus(String s) { | |
| if (s == null) return false; | |
| for (String v : VALID_STATUS) { | |
| if (v.equalsIgnoreCase(s)) return true; | |
| } | |
| return false; | |
| } | |
| private static String formatExpiry(long ms) { | |
| Instant instant = Instant.ofEpochMilli(ms); | |
| ZonedDateTime zdt = instant.atZone(ZoneId.systemDefault()); | |
| return zdt.toLocalDate() + " " + zdt.toLocalTime().withNano(0); | |
| } | |
| private static void openEditor(String file) { | |
| try { | |
| String os = System.getProperty("os.name").toLowerCase(); | |
| if (os.contains("win")) { | |
| Runtime.getRuntime().exec(new String[]{"cmd", "/c", "start", file}); | |
| } else if (os.contains("mac")) { | |
| Runtime.getRuntime().exec(new String[]{"open", file}); | |
| } else { | |
| Runtime.getRuntime().exec(new String[]{"xdg-open", file}); | |
| } | |
| } catch (Exception e) { | |
| System.out.println("WARNING: Could not open editor: " + e.getMessage()); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment