One-click Play Integrity Fix (PIFork) + honest integrity copy#55
One-click Play Integrity Fix (PIFork) + honest integrity copy#55RobThePCGuy wants to merge 1 commit into
Conversation
Adds a pinned Play Integrity Fork (PIFork v17, osm0sis) payload and an "Install Play Integrity Fix" button on the Magisk tab, gated on the manager like ReZygisk. Flashes over ADB (fetch_module -> install_module); PIFork's autopif fetches a fresh Play-certified fingerprint, so it reaches Play Integrity Basic + Device after a reboot. Rewrites the integrity note to drop "impossible" and state the real shape: Basic and Device pass with these modules; STRONG additionally needs a valid, unrevoked hardware keybox the user supplies -- this tool can't and doesn't provide one. - pif_payload.py: hash-locked download of PlayIntegrityFork-v17.zip (sha256 69115acd…), mirrors rezygisk_payload; nothing vendored. - magisk_page: pif_button + install_pif_requested, shown when the manager is installed. - main_window: _handle_install_pif, busy-disable wiring. 154 tests green.
There was a problem hiding this comment.
Code Review
This pull request introduces support for downloading and installing the Play Integrity Fork (PIFork) module. It adds pif_payload.py to handle downloading, SHA-256 verification, and caching of the module, and integrates an 'Install Play Integrity Fix' button into the Magisk page UI. Corresponding tests have also been added. The feedback recommends replacing the legacy urllib.request.urlretrieve with urllib.request.urlopen to support timeouts and improve error handling, along with updating the associated unit tests to mock the new implementation.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| _p("Downloading Play Integrity Fork (%s)..." % MODULE_NAME) | ||
| tmp = dest + ".part" | ||
| try: | ||
| urllib.request.urlretrieve(MODULE_URL, tmp) # pinned URL, hash-checked below | ||
| got = _sha256(tmp) | ||
| if got != MODULE_SHA256: | ||
| raise RuntimeError( | ||
| "PIFork module SHA-256 mismatch: got %s, expected %s" | ||
| % (got, MODULE_SHA256)) | ||
| os.replace(tmp, dest) | ||
| finally: | ||
| if os.path.isfile(tmp): | ||
| os.unlink(tmp) |
There was a problem hiding this comment.
Using urllib.request.urlretrieve is legacy and does not support setting a timeout, which can cause the application to hang indefinitely if the network connection stalls. Additionally, it does not raise exceptions for certain HTTP error status codes (like 404 or 500), which can lead to confusing SHA-256 mismatch errors instead of clear HTTP errors. Refactoring this to use urllib.request.urlopen with a defined timeout and a custom User-Agent header improves robustness and error reporting.
_p(f"Downloading Play Integrity Fork ({MODULE_NAME})...")
tmp = dest + ".part"
try:
req = urllib.request.Request(MODULE_URL, headers={"User-Agent": "Mozilla/5.0"})
with urllib.request.urlopen(req, timeout=30) as response, open(tmp, "wb") as out_file:
while True:
chunk = response.read(1 << 16)
if not chunk:
break
out_file.write(chunk)
got = _sha256(tmp)
if got != MODULE_SHA256:
raise RuntimeError(
f"PIFork module SHA-256 mismatch: got {got}, expected {MODULE_SHA256}"
)
os.replace(tmp, dest)
finally:
if os.path.isfile(tmp):
try:
os.unlink(tmp)
except OSError:
pass| def _boom(*a, **k): | ||
| raise AssertionError("fetch_module downloaded despite a valid cached file") | ||
| monkeypatch.setattr(pp.urllib.request, "urlretrieve", _boom) |
There was a problem hiding this comment.
Update the mock to intercept urllib.request.urlopen instead of urlretrieve to align with the robust download implementation.
| def _boom(*a, **k): | |
| raise AssertionError("fetch_module downloaded despite a valid cached file") | |
| monkeypatch.setattr(pp.urllib.request, "urlretrieve", _boom) | |
| def _boom(*a, **k): | |
| raise AssertionError("fetch_module downloaded despite a valid cached file") | |
| monkeypatch.setattr(pp.urllib.request, "urlopen", _boom) |
| def fake_dl(url, dest): | ||
| with open(dest, "wb") as f: | ||
| f.write(good) | ||
| monkeypatch.setattr(pp.urllib.request, "urlretrieve", fake_dl) |
There was a problem hiding this comment.
Update the mock to return an io.BytesIO stream from urllib.request.urlopen instead of using urlretrieve.
| def fake_dl(url, dest): | |
| with open(dest, "wb") as f: | |
| f.write(good) | |
| monkeypatch.setattr(pp.urllib.request, "urlretrieve", fake_dl) | |
| import io | |
| def fake_urlopen(req, timeout=None): | |
| return io.BytesIO(good) | |
| monkeypatch.setattr(pp.urllib.request, "urlopen", fake_urlopen) |
| def _fake_download(url, dest): | ||
| with open(dest, "wb") as f: | ||
| f.write(b"corrupted-or-tampered") | ||
| monkeypatch.setattr(pp.urllib.request, "urlretrieve", _fake_download) |
There was a problem hiding this comment.
Update the mock to return an io.BytesIO stream from urllib.request.urlopen instead of using urlretrieve.
| def _fake_download(url, dest): | |
| with open(dest, "wb") as f: | |
| f.write(b"corrupted-or-tampered") | |
| monkeypatch.setattr(pp.urllib.request, "urlretrieve", _fake_download) | |
| import io | |
| def fake_urlopen(req, timeout=None): | |
| return io.BytesIO(b"corrupted-or-tampered") | |
| monkeypatch.setattr(pp.urllib.request, "urlopen", fake_urlopen) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6b7580b489
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| self.manager_button.setVisible(installed and not manager) | ||
| self.remove_manager_button.setVisible(manager) | ||
| self.rezygisk_button.setVisible(manager) | ||
| self.pif_button.setVisible(manager) |
There was a problem hiding this comment.
Enforce the ReZygisk prerequisite before enabling PIF
When the manager is installed but ReZygisk has not been installed, this makes the PIF button visible and enabled anyway. Clicking it successfully flashes the module and instructs the user to reboot and check Basic/Device, but PIF is a Zygisk module and therefore remains inactive in this scenario. Track the ReZygisk component or install it as part of this action so the advertised one-click flow cannot finish without its required runtime.
Useful? React with 👍 / 👎.
|
Closing: cutting the Play Integrity Fix feature. On BlueStacks a green Play Integrity verdict isn't reachable (Google gates emulator integrity to MEETS_VIRTUAL_INTEGRITY, exclusive to Google Play Games), so shipping a PIF button would imply a capability the platform can't deliver. Replacing it with LSPosed (a real, working Xposed capability) in a new branch, plus honest integrity copy. |
Adds a pinned Play Integrity Fork (PIFork) module and an Install Play Integrity Fix button on the Magisk tab, so Play Integrity Basic + Device are a one-click flash on top of ReZygisk.
What it does
pif_payload.py: hash-locked, download-at-runtime fetch ofPlayIntegrityFork-v17.zip(osm0sis), mirroring the ReZygisk payload — nothing vendored.fetch_module→install_module). PIFork's autopif fetches a fresh Play-certified fingerprint, so the default-fingerprint-burned problem is handled.Not yet live-verified
The code mirrors the proven ReZygisk path, but PIFork passing Basic/Device on BlueStacks specifically hasn't been confirmed on a booted instance yet. Recommend a live flash + integrity check before merge.
154 tests green.