Licensing

A paid tweak has to decide, on its own, whether it is licensed. It runs inside someone else's process with no network guarantee and no app running alongside it, so the decision is made from a file on disk that was signed by the store.

The receipt

After a purchase is activated, the store writes a receipt to /Library/TweakInject/Receipts/Entries/<packageID>.receipt. It names the package, the machine it was issued for, and when it expires, and it carries a P-256 ECDSA signature over those three values.

The tweak checks three things, and all three must hold:

  • The signature verifies against a public key compiled into the tweak. The private half never leaves the store, so a receipt cannot be written by anything else.
  • The machine matches. The receipt binds to a hash of this Mac's IOPlatformUUID, so copying the file to another machine produces a receipt that verifies but does not apply.
  • It has not expired. Receipts last 60 days and are renewed weekly by a background agent. A refunded or deactivated licence simply stops being renewed, and the tweak goes inert when the lease runs out.

That last point is worth being plain about: someone can copy a tweak's files before refunding. The 60 day lease is what handles it, not the file permissions. The copy keeps working until the receipt expires, and then stops.

The simple check

Include the header, and call TILicenseCheck with your package identifier.

Objective-C
#import "TILicense.h"

__attribute__((constructor))
static void init(void) {
    if (!TILicenseCheck("com.example.mytweak")) return;
    // ... install hooks here
}

This is correct, and it is also the first thing anyone patching your tweak will look for. One branch decides everything, and inverting a single conditional defeats it.

Making the check harder to remove

TI_Support ships macros that spread the decision through the work itself rather than gating it behind one branch. They are not unbreakable, and nothing running on someone else's machine can be. What they do is remove the single obvious place to cut.

Dispatch instead of branching

TIL_DISPATCH and its variants compute the address to call arithmetically, from the result of the licence check. A licensed run computes your function; an unlicensed one computes an inert stub. There is no conditional jump to invert and no direct reference to your function for a cross-reference search to land on.

Objective-C
TIL_DISPATCH("com.example.mytweak", install_my_hooks);

Hooks that only install when licensed

TIL_HOOK wraps a hook installation. When the licence checks out it forwards to MSHookFunction. When it does not, the replacement pointer is poisoned into non-canonical address space and the call goes to a stub, so the hook is never installed rather than being installed and then disabled.

Objective-C
TIL_HOOK("com.example.mytweak", target_fn, my_replacement, &orig_fn);

Guarding data pointers

TIL_GUARD_PTR passes a pointer through unchanged when licensed, and XORs it into an unmapped address when not, so an unlicensed path fails at the point of use rather than silently continuing with real data.

Where to put the check

Spread it. A single call in your constructor is one place to remove. The macros are cheap enough to use at several points, and a check inside the work is harder to excise than a check at the door.

The full reference for each macro, including the exact signature, is under TI_Support.