diff --git a/module/action.sh b/module/action.sh index a17e23a..30cf682 100644 --- a/module/action.sh +++ b/module/action.sh @@ -97,6 +97,12 @@ sdk_version="$(getprop ro.build.version.sdk)" sdk_version="${sdk_version:-25}" echo "Device SDK version: $sdk_version" +# Preserve previous setting +spoofVending="$(grep -oE '"spoofVendingSdk": [01]' "$MODDIR/pif.json" | cut -d' ' -f2)" +if [ -z "$spoofVending" ] || [ "$spoofVending" != 1 ]; then + spoofVending=0 +fi + echo "- Dumping values to pif.json ..." cat < -
[+] Fetching pif.json, please wait...
+
+
+

+ Play Integrity Fix + +

+
+
+
+ Fetch pif.json +
+
+ Spoof sdk version to Play Store + +
+
+
+
+ output +
+ + clear +
+
+
+
+
\ No newline at end of file diff --git a/module/webroot/scripts.js b/module/webroot/scripts.js index dc9fe12..23aa49b 100644 --- a/module/webroot/scripts.js +++ b/module/webroot/scripts.js @@ -1,3 +1,9 @@ +let actionRunning = false; +let initialPinchDistance = null; +let currentFontSize = 14; +const MIN_FONT_SIZE = 8; +const MAX_FONT_SIZE = 24; + // Execute shell commands with ksu.exec async function execCommand(command) { const callbackName = `exec_callback_${Date.now()}`; @@ -10,34 +16,204 @@ async function execCommand(command) { }); } -// Function to run the script and display its output -async function runAction() { - const output = document.querySelector('.output'); - try { - const scriptOutput = await execCommand("sh /data/adb/modules/playintegrityfix/action.sh"); - output.innerHTML = ''; - const lines = scriptOutput.split('\n'); - lines.forEach(line => { - const lineElement = document.createElement('div'); - lineElement.style.whiteSpace = 'pre-wrap'; - lineElement.textContent = line; - if (line === '') { - lineElement.innerHTML = ' '; +// Apply button event listeners +function applyButtonEventListeners() { + const fetchButton = document.getElementById('fetch'); + const sdkVendingToggle = document.getElementById('sdk-vending-toggle-container'); + const clearButton = document.querySelector('.clear-terminal'); + + fetchButton.addEventListener('click', runAction); + sdkVendingToggle.addEventListener('click', async () => { + try { + const pifPath = await execCommand(` + [ ! -f /data/adb/modules/playintegrityfix/pif.json ] || echo /data/adb/modules/playintegrityfix/pif.json + [ ! -f /data/adb/pif.json ] || echo /data/adb/pif.json + `); + if (pifPath.trim() === "") { + appendToOutput("[!] No pif.json found"); + return; } - output.appendChild(lineElement); - }); - } catch (error) { - console.error('Script execution failed:', error); - if (typeof ksu !== 'undefined' && ksu.mmrl) { - output.innerHTML = '[!] Please allow permission in MMRL settings

[-] Settings
[-] Security
[-] Allow JavaScript API
[-] Play Integrity Fix
[-] Enable Allow Advanced KernelSU API'; - } else { - output.innerHTML = '[!] Error: Fail to execute action.sh'; + const isChecked = document.getElementById('toggle-sdk-vending').checked; + const paths = pifPath.trim().split('\n'); + for (const path of paths) { + if (path) { + await execCommand(`sed -i 's/"spoofVendingSdk": [01]/"spoofVendingSdk": ${isChecked ? 0 : 1}/' ${path}`); + } + } + appendToOutput(`[+] Successfully changed spoofVendingSdk to ${isChecked ? 0 : 1}`); + document.getElementById('toggle-sdk-vending').checked = !isChecked; + } catch (error) { + appendToOutput("[-] Failed to change spoofVendingSdk"); + console.error('Failed to toggle sdk vending:', error); } + }); + clearButton.addEventListener('click', () => { + const output = document.querySelector('.output-terminal-content'); + output.innerHTML = ''; + currentFontSize = 14; + updateFontSize(currentFontSize); + }); + + const terminal = document.querySelector('.output-terminal-content'); + + terminal.addEventListener('touchstart', (e) => { + if (e.touches.length === 2) { + e.preventDefault(); + initialPinchDistance = getDistance(e.touches[0], e.touches[1]); + } + }, { passive: false }); + + terminal.addEventListener('touchmove', (e) => { + if (e.touches.length === 2) { + e.preventDefault(); + const currentDistance = getDistance(e.touches[0], e.touches[1]); + + if (initialPinchDistance === null) { + initialPinchDistance = currentDistance; + return; + } + + const scale = currentDistance / initialPinchDistance; + const newFontSize = currentFontSize * scale; + updateFontSize(newFontSize); + initialPinchDistance = currentDistance; + } + }, { passive: false }); + + terminal.addEventListener('touchend', () => { + initialPinchDistance = null; + }); +} + +// Function to load the version from module.prop +async function loadVersionFromModuleProp() { + const versionElement = document.getElementById('version-text'); + try { + const version = await execCommand("grep '^version=' /data/adb/modules/playintegrityfix/module.prop | cut -d'=' -f2"); + versionElement.textContent = version.trim(); + } catch (error) { + appendToOutput("[-] Failed to read version from module.prop"); + console.error("Failed to read version from module.prop:", error); } } +// Function to load spoofVendingSdk config +async function loadSpoofVendingSdkConfig() { + const sdkVendingToggle = document.getElementById('toggle-sdk-vending'); + const isChecked = await execCommand(`grep -o '"spoofVendingSdk": [01]' /data/adb/modules/playintegrityfix/pif.json | cut -d' ' -f2`); + if (isChecked === '0') { + sdkVendingToggle.checked = false; + } else { + sdkVendingToggle.checked = true; + } +} + +// Function to append element in output terminal +function appendToOutput(content) { + const output = document.querySelector('.output-terminal-content'); + if (content.trim() === "") { + const lineBreak = document.createElement('br'); + output.appendChild(lineBreak); + } else { + const line = document.createElement('p'); + line.className = 'output-content'; + line.textContent = content; + output.appendChild(line); + } + output.scrollTop = output.scrollHeight; +} + +// Function to run the script and display its output +async function runAction() { + if (actionRunning) return; + actionRunning = true; + try { + appendToOutput("[+] Fetching pif.json..."); + await new Promise(resolve => setTimeout(resolve, 200)); + const scriptOutput = await execCommand("sh /data/adb/modules/playintegrityfix/action.sh"); + const lines = scriptOutput.split('\n'); + lines.forEach(line => { + appendToOutput(line) + }); + appendToOutput(""); + } catch (error) { + console.error('Script execution failed:', error); + if (typeof ksu !== 'undefined' && ksu.mmrl) { + appendToOutput(""); + appendToOutput("[!] Please allow permission in MMRL settings"); + appendToOutput("[-] Settings"); + appendToOutput("[-] Security"); + appendToOutput("[-] Allow JavaScript API"); + appendToOutput("[-] Play Integrity Fix"); + appendToOutput("[-] Enable Allow Advanced KernelSU API"); + appendToOutput(""); + } else { + appendToOutput("[!] Error: Fail to execute action.sh"); + appendToOutput(""); + } + } + actionRunning = false; +} + +// Function to apply ripple effect +function applyRippleEffect() { + document.querySelectorAll('.ripple-element').forEach(element => { + if (element.dataset.rippleListener !== "true") { + element.addEventListener("pointerdown", function (event) { + const ripple = document.createElement("span"); + ripple.classList.add("ripple"); + + // Calculate ripple size and position + const rect = element.getBoundingClientRect(); + const width = rect.width; + const size = Math.max(rect.width, rect.height); + const x = event.clientX - rect.left - size / 2; + const y = event.clientY - rect.top - size / 2; + + // Determine animation duration + let duration = 0.2 + (width / 800) * 0.4; + duration = Math.min(0.8, Math.max(0.2, duration)); + + // Set ripple styles + ripple.style.width = ripple.style.height = `${size}px`; + ripple.style.left = `${x}px`; + ripple.style.top = `${y}px`; + ripple.style.animationDuration = `${duration}s`; + ripple.style.transition = `opacity ${duration}s ease`; + + // Adaptive color + const computedStyle = window.getComputedStyle(element); + const bgColor = computedStyle.backgroundColor || "rgba(0, 0, 0, 0)"; + const textColor = computedStyle.color; + const isDarkColor = (color) => { + const rgb = color.match(/\d+/g); + if (!rgb) return false; + const [r, g, b] = rgb.map(Number); + return (r * 0.299 + g * 0.587 + b * 0.114) < 96; // Luma formula + }; + ripple.style.backgroundColor = isDarkColor(bgColor) ? "rgba(255, 255, 255, 0.2)" : ""; + + // Append ripple and handle cleanup + element.appendChild(ripple); + const handlePointerUp = () => { + ripple.classList.add("end"); + setTimeout(() => { + ripple.classList.remove("end"); + ripple.remove(); + }, duration * 1000); + element.removeEventListener("pointerup", handlePointerUp); + element.removeEventListener("pointercancel", handlePointerUp); + }; + element.addEventListener("pointerup", handlePointerUp); + element.addEventListener("pointercancel", handlePointerUp); + }); + element.dataset.rippleListener = "true"; + } + }); +} + // Function to check if running in MMRL -function checkMMRL() { +async function checkMMRL() { if (typeof ksu !== 'undefined' && ksu.mmrl) { // Set status bars theme based on device theme try { @@ -52,12 +228,40 @@ function checkMMRL() { } catch (error) { console.log("Error requesting API:", error); } + try { + await execCommand("whoami"); + } catch (error) { + appendToOutput(""); + appendToOutput("[!] Please allow permission in MMRL settings"); + appendToOutput("[-] Settings"); + appendToOutput("[-] Security"); + appendToOutput("[-] Allow JavaScript API"); + appendToOutput("[-] Play Integrity Fix"); + appendToOutput("[-] Enable Allow Advanced KernelSU API"); + appendToOutput(""); + } } else { console.log("Not running in MMRL environment."); } } +function getDistance(touch1, touch2) { + return Math.hypot( + touch1.clientX - touch2.clientX, + touch1.clientY - touch2.clientY + ); +} + +function updateFontSize(newSize) { + currentFontSize = Math.min(Math.max(newSize, MIN_FONT_SIZE), MAX_FONT_SIZE); + const terminal = document.querySelector('.output-terminal-content'); + terminal.style.fontSize = `${currentFontSize}px`; +} + document.addEventListener('DOMContentLoaded', async () => { checkMMRL(); - setTimeout(runAction, 200); + loadVersionFromModuleProp(); + loadSpoofVendingSdkConfig(); + applyButtonEventListeners(); + applyRippleEffect(); }); \ No newline at end of file diff --git a/module/webroot/styles.css b/module/webroot/styles.css index 0d15647..b79458d 100644 --- a/module/webroot/styles.css +++ b/module/webroot/styles.css @@ -6,17 +6,188 @@ } body { - font-family: 'Mono'; + background-color: #F5F5F5; padding-top: var(--window-inset-top); padding-bottom: var(--window-inset-bottom); } -.output { - font-size: 14px; - left: 10px; - width: calc(100vw - 20px); +.content { + padding-bottom: 10px; + display: flex; + flex-direction: column; + align-items: center; + height: 90vh; + gap: 15px; +} + +.header { + user-select: none; +} + +.button-box { + width: calc(85vw + 30px); + max-width: 800px; + flex-shrink: 0; + background-color: #fff; + border: none; + border-radius: 15px; + box-sizing: border-box; + box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1); + overflow: hidden; +} + +.toggle-list { + display: flex; + align-items: center; + background-color: #fff; + padding: 10px 20px; white-space: nowrap; - word-break: break-all; + text-align: left; + border-bottom: 1px solid #ccc; + position: relative; + overflow: hidden; +} + +.toggle-list:last-child { + border-bottom: none; + margin-bottom: 0; +} + +.toggle-text { + font-size: 16px; + font-weight: bold; + white-space: wrap; + max-width: calc(100% - 76px); + user-select: none; +} + +.toggle-switch { + position: relative; + display: inline-block; + margin-left: auto; + width: 40px; + height: 25px; +} + +.toggle-switch input { + opacity: 0; + width: 0; + height: 0; +} + +.slider { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + -webkit-transition: .4s; + transition: .4s; +} + +.slider:before { + position: absolute; + content: ""; + height: 19px; + width: 19px; + left: 3px; + bottom: 3px; + background-color: white; + transition: .4s; +} + +input:checked+.slider { + background-color: #007bff; +} + +input:focus+.slider { + box-shadow: 0 0 1px #007bff; +} + +input:checked+.slider:before { + transform: translateX(15px); +} + +.slider.round { + border-radius: 25px; +} + +.slider.round:before { + border-radius: 50%; +} + +.output-terminal { + width: calc(85vw + 30px); + max-width: 800px; + flex-grow: 1; + background-color: #fff; + box-sizing: border-box; + border-radius: 15px; + box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1); + overflow: hidden; +} + +.output-terminal-header { + display: flex; + padding: 5px 15px; + font-size: 14px; + justify-content: space-between; + background-color: #E0E0E0; + color: #333; + user-select: none; +} + +.clear-terminal { + display: flex; + align-items: center; +} + +.clear-terminal svg { + fill: #333; +} + +.output-terminal-content { + font-family: 'Mono'; + font-size: 14px; + padding: 10px; + width: calc(100% - 20px); + height: calc(100% - 50px); + overflow-y: scroll; +} + +.output-content { + position: relative; + width: 100%; + padding: 0; + margin: 0; + white-space: pre-wrap; + word-break: break-word; +} + +.ripple-element { + position: relative; + overflow: hidden; +} + +.ripple { + position: absolute; + border-radius: 50%; + transform: scale(0); + opacity: 1; + animation: ripple-animation ease-out forwards; + pointer-events: none; + background: rgba(0, 0, 0, 0.2); +} + +.ripple.end { + opacity: 0; +} + +@keyframes ripple-animation { + to { + transform: scale(3); + } } @media (prefers-color-scheme: dark) { @@ -24,4 +195,30 @@ body { background-color: #121212; color: #fff; } + + .toggle-list, + .button-box, + .output-terminal-header { + background-color: #343434; + } + + .output-terminal-header { + color: #ccc; + } + + .clear-terminal svg { + fill: #ccc; + } + + .output-terminal { + background-color: #232323; + } + + .toggle-list { + border-bottom: 1px solid #6E6E6E; + } + + .slider { + background-color: #6E6E6E; + } } \ No newline at end of file