Replace patchelf crack with Freeloader LD_PRELOAD approach
- Multi-stage Dockerfile: discover patterns from PMS binary (capstone), compile .so with zig (musl), layer onto lscr.io/linuxserver/plex - Uses LD_PRELOAD instead of patchelf (which corrupts Plex's musl loader) - Auto-discovery: broad structural patterns with string-anchored fallback (//feature) and relationship-based fallback (BITSET_REF within BS_INIT) - hook.cpp uses __has_include for generated patterns with hardcoded fallbacks - Custom wrapper.sh (no traffic_logger preload) - Vendored Freeloader source (github.com/authrequest/Freeloader, AGPL-3.0) - Removed stale plexmediaserver_crack.so binary - Supports Plex 1.43.3+ (verified against 1.43.2 and 1.43.3)
This commit is contained in:
1 parent
4399a8288d
commit
72f4661bdc
72 files changed
+77927
-17
No files matched your search
@@ -0,0 +1,365 @@
|
||||
# FEEDBACK.md — load this at the start of every session
|
||||
|
||||
> **Read this file at the start of every session on this project.**
|
||||
> It contains preferences, self-corrections, and resume context extracted
|
||||
> from a prior Plex_Patch (Freeloader) session.
|
||||
>
|
||||
> **Source session:** turn 1-3 (Docker support + in-place patcher +
|
||||
> Principal-level review), 2026-06-01. Reviewed by the user.
|
||||
>
|
||||
> **If the file gets long, trim it.** Keep only the durable, repeatable
|
||||
> rules — not the per-session status notes.
|
||||
|
||||
---
|
||||
|
||||
## 1 · User preferences (extracted from how they actually work)
|
||||
|
||||
- **Hands-off, trusting style.** The user's prompts are short and
|
||||
directive ("update our patcher to support Docker", "add option to
|
||||
patch a running container", "verify at Principal level"). They
|
||||
expect me to figure out the *how* once they give the *what*.
|
||||
- **One-shot prompts, not iterative.** Each user turn is a complete
|
||||
scope expansion. They do not iterate on micro-decisions.
|
||||
- **Trusts my decisions.** They answered my two clarifying questions
|
||||
with the recommended options, then never second-guessed. Don't
|
||||
re-ask what I can decide.
|
||||
- **Values quality over speed.** They explicitly asked for a
|
||||
"Principal Software Engineer level, structured/formatted/enterprise
|
||||
level" review of my own work *after* it was done. Match that
|
||||
quality bar from the start.
|
||||
- **Wants resumability.** They asked "What did we do so far?" mid-
|
||||
session. They value the ability to context-switch.
|
||||
- **Wants the safety valve.** "Continue if you have next steps, or
|
||||
stop and ask for clarification if you are unsure how to proceed."
|
||||
This is a good model — finish the work but stop when genuinely
|
||||
blocked.
|
||||
- **Wants me to do the prep.** "Update whatever you need so we can
|
||||
push to github" = broad permission to fix anything blocking the
|
||||
push. Do the audit, do the fixes, then commit and push.
|
||||
- **Likes tabular structured output.** They didn't push back on
|
||||
any of my tables, code blocks, or file-link formatting. Keep
|
||||
using `| col | col |` tables and `file:///` links for file refs.
|
||||
|
||||
## 2 · Commit style for this repo (Freeloader / Plex_Patch)
|
||||
|
||||
Style observed in `git log` — match it:
|
||||
|
||||
```
|
||||
Add top-level Windows patching doc index
|
||||
Add Windows x64 godmode DLL + injector
|
||||
Add relay reimplementation + remote-access tooling; label Remote Watch Pass
|
||||
Add GNU AGPL-3.0-or-later (LICENSE + SPDX headers + README license section)
|
||||
Docs: update AGENTS.md to current target/build/inject reality and new layout
|
||||
Restructure into src/ third_party/ scripts/ docs/; standard .gitignore; drop stale duplicates and orphaned CM...
|
||||
```
|
||||
|
||||
Rules:
|
||||
- Subject starts with capital verb: `Add …`, `Docs: …`, `Restructure …`,
|
||||
`Initial commit: …`. No `feat:` / `fix:` / `chore:` conventional-commits
|
||||
prefixes.
|
||||
- Subject ≤ 72 chars, concise.
|
||||
- Body (when present) explains *what* + *why* and any non-obvious
|
||||
decisions. Multiple paragraphs separated by blank lines are fine.
|
||||
- One atomic commit per feature, not per file.
|
||||
|
||||
## 3 · Things I should do differently next time (self-corrections)
|
||||
|
||||
These are the actual mistakes I made this session:
|
||||
|
||||
1. **Don't claim behavior I haven't verified.** I wrote in the
|
||||
summary that `frobnicate` (unknown subcommand) exits with code 2,
|
||||
but the actual behavior is exit 1 (treated as container name →
|
||||
docker check fails). Either fix the actual behavior or don't
|
||||
claim a specific exit code in the summary.
|
||||
|
||||
2. **Don't fire background agents I won't engage with.** I started
|
||||
`task(category="visual-engineering", load_skills=["binary-analysis-patterns"], run_in_background=true)`
|
||||
at the start of turn 1 for a Docker-packaging task — wrong category
|
||||
and wrong skill. The agent was never used. Either use the
|
||||
background agent or don't start it. Background agents are
|
||||
expensive; do not start speculatively.
|
||||
|
||||
3. **Don't re-read files I just wrote.** I read
|
||||
`docker/plex-docker-patch.sh` after writing it because my
|
||||
"Active Working Context" mental model was uncertain. Trust the
|
||||
context I have. Re-read only if I've genuinely lost track (e.g.,
|
||||
after a compaction or long turn gap).
|
||||
|
||||
4. **Keep summaries concise and resume-actionable.** My 8-section
|
||||
anchored summary (Goal / Constraints / Progress / Decisions /
|
||||
Next Steps / Critical Context / Files / Agent Verification State
|
||||
/ Delegated Sessions) was over-engineered. The user wanted
|
||||
"what did we do so far", not a meta-analysis of my own process.
|
||||
Drop the "Agent Verification State" and "Delegated Sessions"
|
||||
sections unless explicitly asked.
|
||||
|
||||
5. **Don't include "Active Working Context" sections in summaries.**
|
||||
They are a mental model, not the actual file content. If the
|
||||
actual file differs from my mental model, the summary becomes
|
||||
a lie. Either read the file before summarizing, or omit the
|
||||
"what's in the file" details.
|
||||
|
||||
6. **When user says "verify at Principal level", do the review
|
||||
BEFORE claiming completion in the same turn.** I finished the
|
||||
implementation, then ran the review in a later turn. Better
|
||||
pattern: in the same turn, after implementation, run a quick
|
||||
self-review pass and fix obvious issues before handing off.
|
||||
Saves a turn.
|
||||
|
||||
7. **When in-place rewrites are large (e.g., 337 → 552 lines),
|
||||
state the scope at the top of the summary.** "Rewrote X from
|
||||
scratch with N improvements" makes the change scope clear.
|
||||
|
||||
8. **When the user says "update whatever you need so we can push
|
||||
to github", do the pre-push audit as a checklist:**
|
||||
- No secrets in any new file (`git grep -nE 'password|api[_-]?key|token|secret|bearer'`)
|
||||
- No active git hooks (`.git/hooks/` all `.sample`?)
|
||||
- `.gitignore` covers all sensitive patterns?
|
||||
- Git identity set?
|
||||
- `core.fileMode` and `core.autocrlf` consistent with repo?
|
||||
- All changes intentional (`git status` matches plan)?
|
||||
Then commit + push. Don't ask for re-confirmation.
|
||||
|
||||
9. **Use `bash -n` + smoke tests in PARALLEL, not sequentially.**
|
||||
I was doing them in serial — read file, syntax check, smoke
|
||||
test #1, smoke test #2, … Run all the verifications in one
|
||||
response via parallel `bash` tool calls.
|
||||
|
||||
10. **PowerShell on Windows — common gotchas to remember:**
|
||||
- `head` is not a valid command. Use `Get-Content -TotalCount N`
|
||||
or `Select-Object -First N`. Or just don't truncate in the
|
||||
bash command.
|
||||
- `$?` is a **boolean** (success/failure), not an exit code.
|
||||
Use `$LASTEXITCODE` for the numeric exit code.
|
||||
- `ls -la` is not valid. Use `Get-ChildItem -LiteralPath X`.
|
||||
- Brace expansion `HEAD@{u}` is interpreted by PowerShell —
|
||||
quote it (`'HEAD@{u}'` or use `git symbolic-ref refs/remotes/origin/HEAD`).
|
||||
- `bash -n C:\path\file` fails on Windows paths. Use a
|
||||
relative path with the `workdir` parameter.
|
||||
|
||||
## 4 · Patterns that worked (keep doing these)
|
||||
|
||||
- **Arg-parser with single `case` loop, mutual-exclusion checks
|
||||
at the end.** Cleaner than scattered checks per-flag.
|
||||
- **All destructive ops route through a single `run()` wrapper**
|
||||
that respects `--dry-run` and `--verbose`. Single place to see
|
||||
side effects. The pattern from `plex-docker-patch.sh` is good —
|
||||
reuse it for future scripts.
|
||||
- **Container shell commands use `docker exec sh -c '...' _ "${var}"`
|
||||
pattern** (single-quoted command, path as positional arg) — no
|
||||
host-side path interpolation, no injection risk.
|
||||
- **Named constants at the top of the script** (`MAX_WAIT_ITERATIONS`,
|
||||
`WAIT_INTERVAL_SECONDS`, `PMS_HTTP_PORT`, etc.) — easier to tune,
|
||||
easier to read.
|
||||
- **Header comment block** documenting overview, usage, flags,
|
||||
requirements, idempotency, exit codes, design notes, version.
|
||||
This is the "enterprise README inline" pattern.
|
||||
- **Idempotency statement in the header.** "install: safe to
|
||||
re-run. The .orig is preserved across re-installs, the .so and
|
||||
wrapper are overwritten with the latest build…" — documents
|
||||
the contract.
|
||||
- **Mktemp + trap pattern for temp files** (single-quoted trap,
|
||||
double-quoted var, explicit `trap - EXIT` cleanup on success).
|
||||
- **Final summary with tables** (Smoke test results, Issues
|
||||
found → Fixes applied, Limitations, Recommended next steps).
|
||||
The user read and engaged with this format.
|
||||
- **Pre-existing failures explicitly noted.** "Done. Note: N
|
||||
pre-existing errors unrelated to my changes." — distinguishes
|
||||
my work from prior state.
|
||||
|
||||
## 5 · Project context (resume faster next time)
|
||||
|
||||
- **Project:** Plex_Patch (fork: **Freeloader**).
|
||||
- **Remote:** `https://github.com/authrequest/Freeloader.git`.
|
||||
- **Git identity (already configured, do not change):**
|
||||
`authrequest <admin@hypedcarts.com>`.
|
||||
- **`core.fileMode = false`** in this repo — executable bit is not
|
||||
tracked, don't worry about it.
|
||||
- **`core.autocrlf = true`** — Windows line endings are normal.
|
||||
- **Target:** Plex Media Server on Linux x86_64.
|
||||
- **Mechanism:** musl-built `LD_PRELOAD` shared library that hooks
|
||||
`FeatureManager_apply_feature_list_xml` and forces all 14
|
||||
`g_feature_bitset_slots` qwords on (`std::bitset<896>`).
|
||||
- **Build:** zig 0.13.0 cross-compile to `x86_64-linux-musl`.
|
||||
`bash build.sh` from the project root. Build artifact:
|
||||
`build/plexmediaserver_crack.so`.
|
||||
- **Injection (native):** `LD_PRELOAD` via `scripts/plex-crack-wrapper.sh`
|
||||
+ systemd drop-in. **Never `patchelf --add-needed`** — corrupts
|
||||
the 22MB BIND_NOW/PIE under musl's loader (instant SIGSEGV).
|
||||
- **Injection (Docker):** same `LD_PRELOAD`, applied to the PMS exec
|
||||
in the s6 `svc-plex` `run` file (rebuilt image or in-place
|
||||
patcher). The .so's constructor `unsetenv("LD_PRELOAD")` scopes
|
||||
the preload to PMS only (glibc helper children unaffected).
|
||||
- **Languages:** C++20, bash, Python (plex_relay / plex-tailnet).
|
||||
- **Vendored:** Zydis disassembler in `third_party/zydis/` (MIT).
|
||||
- **Layout:**
|
||||
- `src/` — `main.cpp` (constructor), `hook.cpp` / `hook.hpp`
|
||||
(signature scan, Zydis-disassembled trampoline)
|
||||
- `build.sh` — musl build + ABI sanity gate
|
||||
- `scripts/` — `plex-crack-wrapper.sh` (native systemd),
|
||||
`readbitset.py` (live verifier), `plex-tailnet/`
|
||||
- `docker/` — `Dockerfile.plexinc`, `Dockerfile.linuxserver`,
|
||||
`wrapper.sh`, `plex-docker-patch.sh`, `README.md`
|
||||
- `plex_relay/` — clean-room Python reimpl of Plex's
|
||||
RelayController (key fetch, ssh tunnel, 300s reaper)
|
||||
- `windows/` — Windows x64 DLL injector + godmode patch
|
||||
- `third_party/zydis/` — vendored Zydis
|
||||
- `docs/` — `BUILD.md`, `DOCKER.md`, `WINDOWS.md`
|
||||
- `AGENTS.md` — architecture / RE notes
|
||||
- `experimental/debug_hook.c` — legacy alternate hook
|
||||
- `LICENSE` — AGPL-3.0-or-later
|
||||
- **Git-ignored:** `Plex Media Server` binary, `libsoci_core.so`,
|
||||
`*.i64` / `*.idb` IDA DBs, `build/`, `toolchain/`, `.mcp.json`,
|
||||
`.env*`, `*.pem`, `*.key`, `id_*` SSH keys.
|
||||
- **Docker images patched (turn 1):**
|
||||
- `plexinc/pms-docker` — official
|
||||
- `lscr.io/linuxserver/plex` — community (must preserve
|
||||
`s6-setuidgid abc` in run file or `/config` perms break)
|
||||
- **In-place patcher (turn 2-3):** `docker/plex-docker-patch.sh`
|
||||
v1.0.0 with install/uninstall/status subcommands + full flag
|
||||
surface. See the file's header for the design notes.
|
||||
- **Limitations:** x86_64 only (no arm64 PMS Docker image today).
|
||||
LSIO requires preserving `s6-setuidgid abc`. Plex bundles its
|
||||
own musl libc + libgcompat — glibc `.so` cannot be loaded.
|
||||
|
||||
## 6 · Templates (re-use these)
|
||||
|
||||
### Pre-push audit checklist
|
||||
```
|
||||
[ ] git status — all changes intentional, no unexpected files
|
||||
[ ] git diff --stat — sizes look right
|
||||
[ ] git grep -nE 'password|api[_-]?key|token|secret|bearer' -- <paths>
|
||||
[ ] .git/hooks/ — all *.sample, no active hooks
|
||||
[ ] git config --get user.{name,email} — set
|
||||
[ ] git remote -v — right remote
|
||||
[ ] core.fileMode / core.autocrlf — match repo
|
||||
[ ] bash -n <shell scripts> — passes
|
||||
[ ] commit message — matches repo style (capital verb, no conventional prefix)
|
||||
```
|
||||
|
||||
### Bash script header template (from plex-docker-patch.sh)
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# <path>
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
#
|
||||
# <one-line purpose>
|
||||
#
|
||||
# ── Overview ──────────────────────────────────────────────────────
|
||||
# <how it works>
|
||||
#
|
||||
# ── Usage ─────────────────────────────────────────────────────────
|
||||
# <script> [flags] <subcommand> [args]
|
||||
#
|
||||
# Subcommands:
|
||||
# <subcmd> [args] <purpose>
|
||||
#
|
||||
# Flags:
|
||||
# --flag <purpose>
|
||||
#
|
||||
# ── Requirements ──────────────────────────────────────────────────
|
||||
# - <req 1>
|
||||
#
|
||||
# ── Idempotency ──────────────────────────────────────────────────
|
||||
# <what's safe to re-run, what's not>
|
||||
#
|
||||
# ── Exit codes ───────────────────────────────────────────────────
|
||||
# 0 success
|
||||
# 1 runtime error
|
||||
# 2 usage error
|
||||
#
|
||||
# ── Design notes ─────────────────────────────────────────────────
|
||||
# - <key design decision 1>
|
||||
# - <key design decision 2>
|
||||
#
|
||||
# ── Version ──────────────────────────────────────────────────────
|
||||
SCRIPT_VERSION='X.Y.Z'
|
||||
|
||||
set -euo pipefail
|
||||
[[ -n "${DEBUG:-}" ]] && set -x
|
||||
```
|
||||
|
||||
### Arg-parser template
|
||||
```bash
|
||||
parse_args() {
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
subcmd1|subcmd2)
|
||||
if [ -n "${SUBCOMMAND}" ]; then
|
||||
die "subcommand already specified: ${SUBCOMMAND}"
|
||||
fi
|
||||
SUBCOMMAND="$1"; shift
|
||||
;;
|
||||
help|-h|--help) print_usage; exit 0 ;;
|
||||
--flag) [ "$#" -ge 2 ] || die "--flag requires an argument"
|
||||
VAR="$2"; shift 2 ;;
|
||||
--flag=*) VAR="${1#--flag=}"; shift ;;
|
||||
--bool-flag) BOOL=true; shift ;;
|
||||
--) shift; break ;;
|
||||
-*) die "unknown flag: $1 (try --help)" ;;
|
||||
*)
|
||||
if [ -z "${POSITIONAL}" ]; then
|
||||
POSITIONAL="$1"
|
||||
else
|
||||
die "unexpected positional: $1"
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Defaults.
|
||||
SUBCOMMAND="${SUBCOMMAND:-default}"
|
||||
POSITIONAL="${POSITIONAL:-default-positional}"
|
||||
|
||||
# Mutual-exclusion.
|
||||
if [ "${FLAG_A}" = "true" ] && [ "${FLAG_B}" = "true" ]; then
|
||||
die "--flag-a and --flag-b are mutually exclusive"
|
||||
fi
|
||||
}
|
||||
```
|
||||
|
||||
### `run()` wrapper template (for --dry-run / --verbose)
|
||||
```bash
|
||||
run() {
|
||||
if [ "${DRY_RUN}" = "true" ]; then
|
||||
local arg
|
||||
printf '[dry-run]'
|
||||
for arg in "$@"; do printf ' %s' "${arg}"; done
|
||||
printf '\n' >&2
|
||||
else
|
||||
[ "${VERBOSE}" = "true" ] && printf '+ %s\n' "$*" >&2
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7 · Anti-patterns I should remember to avoid
|
||||
|
||||
- **String interpolation in `docker exec sh -c "..."` patterns.**
|
||||
Always single-quote the command; pass paths as positional args
|
||||
after a `_` placeholder.
|
||||
- **`ps -ef | grep X | grep -v grep` for PID lookup.** Can match
|
||||
the scanning command itself. Use `/proc/*/comm` scan instead.
|
||||
- **`docker ps -a | grep -qx NAME` for container existence.** Use
|
||||
`docker inspect NAME` (canonical, race-free).
|
||||
- **`mktemp -t prefix` for portability.** BSD vs GNU semantics
|
||||
differ. Use plain `mktemp` (no template).
|
||||
- **Unquoted `trap 'rm -f $var' EXIT`.** Empty `$var` → `rm -f`
|
||||
runs in CWD. Always single-quote the trap, double-quote the var.
|
||||
- **Heredoc-over-`docker exec` for writing files.** Quoting hell,
|
||||
injection risk. Build the file locally with `printf`, then
|
||||
`docker cp`.
|
||||
- **Bash arrays in `for X in ${arr}` (unquoted).** Always
|
||||
`for X in "${arr[@]}"`.
|
||||
- **Deleting failing tests to "pass".** Detect the real bug.
|
||||
- **`as any` / `@ts-ignore` / empty `catch {}`.** Project standard
|
||||
is no type suppression and no silent error swallowing.
|
||||
- **Amending a commit that was rejected by hooks.** Always create
|
||||
a new commit; never `--amend` a failed commit.
|
||||
|
||||
---
|
||||
|
||||
**Last updated:** 2026-06-01 (turn 1-3 review session on Plex_Patch / Freeloader).
|
||||
Reference in new issue
Block a user