Skip to content

One-click Play Integrity Fix (PIFork) + honest integrity copy#55

Closed
RobThePCGuy wants to merge 1 commit into
masterfrom
feature/play-integrity
Closed

One-click Play Integrity Fix (PIFork) + honest integrity copy#55
RobThePCGuy wants to merge 1 commit into
masterfrom
feature/play-integrity

Conversation

@RobThePCGuy

Copy link
Copy Markdown
Owner

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 of PlayIntegrityFork-v17.zip (osm0sis), mirroring the ReZygisk payload — nothing vendored.
  • Magisk tab: "Install Play Integrity Fix" button, shown once the manager is installed (like ReZygisk). Flashes over ADB (fetch_moduleinstall_module). PIFork's autopif fetches a fresh Play-certified fingerprint, so the default-fingerprint-burned problem is handled.
  • Rewrites the integrity note to state the real shape and drop "impossible": 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.

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.

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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread pif_payload.py
Comment on lines +73 to +85
_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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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

Comment thread tests/test_pif_payload.py
Comment on lines +20 to +22
def _boom(*a, **k):
raise AssertionError("fetch_module downloaded despite a valid cached file")
monkeypatch.setattr(pp.urllib.request, "urlretrieve", _boom)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Update the mock to intercept urllib.request.urlopen instead of urlretrieve to align with the robust download implementation.

Suggested change
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)

Comment thread tests/test_pif_payload.py
Comment on lines +41 to +44
def fake_dl(url, dest):
with open(dest, "wb") as f:
f.write(good)
monkeypatch.setattr(pp.urllib.request, "urlretrieve", fake_dl)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Update the mock to return an io.BytesIO stream from urllib.request.urlopen instead of using urlretrieve.

Suggested change
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)

Comment thread tests/test_pif_payload.py
Comment on lines +53 to +56
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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Update the mock to return an io.BytesIO stream from urllib.request.urlopen instead of using urlretrieve.

Suggested change
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)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread views/magisk_page.py
self.manager_button.setVisible(installed and not manager)
self.remove_manager_button.setVisible(manager)
self.rezygisk_button.setVisible(manager)
self.pif_button.setVisible(manager)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@RobThePCGuy

Copy link
Copy Markdown
Owner Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant