- 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)
366 lines
16 KiB
Markdown
366 lines
16 KiB
Markdown
# 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).
|