Compare commits

...

19 Commits
v1.3 ... master

Author SHA1 Message Date
yuv420p10le
1906022184 Fixed gitlab URL in the plexpass_hook reference. 2024-12-27 10:36:34 +02:00
yuv420p10le
a3001d1550 Replaced old URL. 2024-08-31 21:11:14 +03:00
yuv420p10le
1209483a5b Added FeatureManager::UserFeatureSet spoofing.
- Fixes intro/credit markers not being sent to clients in recent updates.
2024-08-31 16:45:30 +03:00
yuv420p10le
36046155d1 Fixed off by 1 bug causing some features like HDR tone mapping to not work. 2024-08-26 21:59:14 +03:00
yuv420p10le
7d5a79c019 Added str_holder to support <16 length strings. 2024-08-26 21:58:44 +03:00
yuv420p10le
d4f1d18bac Synced Plex Pass features 2024-08-26 21:58:11 +03:00
yuv420p10le
c5ef9d2c63 Added instructions to build with Alpine to the README. 2024-08-13 22:54:22 +03:00
yuv420p10le
d9ec635fbd Cracked PMS 1.40.5.8854 (2024/08/13) ahead of time. 2024-08-13 22:54:05 +03:00
yuv420p10le
8f89b436c1 New binaries. 2024-07-02 17:38:03 +03:00
yuv420p10le
a1d4357d74 Added build instructions. 2024-07-02 17:37:54 +03:00
yuv420p10le
0d3f011bba Added new features for Linux too. 2024-07-02 17:31:30 +03:00
yuv420p10le
e4fbde6a83 Updated Linux scripts. 2024-07-02 17:30:45 +03:00
yuv420p10le
3e706caf78 Added new default Plex Pass features. 2024-07-02 17:29:53 +03:00
yuv420p10le
edfc96ae60 Revamped README: Added patchelf installation, mentioned static builds for Docker, and removed outdated links. 2024-06-15 22:59:55 +03:00
yuv420p10le
87a6614a6c Added uniq to folder name grabbing (#2) 2024-06-15 00:04:05 +03:00
yuv420p10le
f1efa3884f Updated download link in crack_docker.sh. 2024-06-13 09:16:14 +03:00
yuv420p10le
bb9a6dd9c3 Added persistence instructions to Windows README. 2024-06-12 20:04:46 +03:00
yuv420p10le
c89c3cc7bf Oops.. fixed explicit hook on v8395. 2024-06-12 19:53:00 +03:00
yuv420p10le
735bec04bd Added binaries and added links to GitLab instead. 2024-06-11 21:09:50 +03:00
11 changed files with 202 additions and 40 deletions

2
.gitignore vendored
View File

@ -213,9 +213,7 @@ FodyWeavers.xsd
*.obj
*.gch
*.pch
*.so
*.dylib
*.dll
*.mod
*.smod
*.lai

View File

@ -8,16 +8,19 @@ Unlocking *most features* on (hardware transcoding, intro/credit detection, HEVC
Note: x86-64 only. If your Plex Media Server is in "Program Files (x86)", it's an indicator of running the 32-bit version of Plex Media Server, which is not supported.
1. Download `IPHLPAPI.dll` from [the latest release](https://github.com/yuv420p10le/plexmediaserver_crack/releases/latest/download/IPHLPAPI.dll) and put it in your Plex Media Server's installation folder. (e.g. `C:\Program Files\Plex\Plex Media Server`)
1. Download `IPHLPAPI.dll` from [the latest release](https://gitgud.io/yuv420p10le/plexmediaserver_crack/-/raw/master/binaries/IPHLPAPI.dll) and put it in your Plex Media Server's installation folder. (e.g. `C:\Program Files\Plex\Plex Media Server`)
2. Restart Plex Media Server. All features will be unlocked.
3. For persistence with updates (as long as the crack doesn't break as a result of them), set the file as read-only.
## Linux
x86-64 only.
First, install `patchelf` from your distribution's package manager or `pip`:
### Native
```bash
Install `patchelf`. Can be done either via `pip` or your distribution's package manager.
```sh
sudo apt update && sudo apt install patchelf # Debian/Ubuntu/etc
sudo pacman -S patchelf # Arch
sudo yum install patchelf # Fedora/RHEL/Centos/Rocky/OpenSUSE/etc
@ -25,16 +28,12 @@ sudo apk update && apk add --upgrade patchelf # Alpine
sudo pip install patchelf # pip; you might need --break-system-packages if installing if you're on an externally managed environment
```
And then:
### Native
The script assumes Plex Media Server is currently running.
Run the following command:
```bash
sudo sh -c "$(curl -sSL https://github.com/yuv420p10le/plexmediaserver_crack/releases/latest/download/crack_native.sh)"
sudo sh -c "$(curl -sSL https://gitgud.io/yuv420p10le/plexmediaserver_crack/-/raw/master/scripts/crack_native.sh)"
```
Your Plex Media Server should now be restarted with all features unlocked.
@ -43,6 +42,21 @@ For persistance with Plex updates, create the above as a bash script (run as roo
### Docker
First, install a static build of `patchelf`:
```bash
sudo pip install patchelf # pip; you might need --break-system-packages if installing if you're on an externally managed environment
# Install from provided binaries
wget https://github.com/NixOS/patchelf/releases/download/0.18.0/patchelf-0.18.0-x86_64.tar.gz -O /tmp/patchelf.tar.gz && \
mkdir /tmp/patchelf && \
tar -xvzf /tmp/patchelf.tar.gz -C /tmp/patchelf && \
sudo cp -r /tmp/patchelf/ / && \
rm -rf /tmp/patchelf
```
Installing `patchelf` from your distribution's package manager will provide you with a dynamically linked binary, which may fail if your Docker image has a too old version of glibc/glibcxx. You can try going with that approach, but do not ask for support if it doesn't work.
Tested on [linuxserver/docker-plex](https://github.com/linuxserver/docker-plex), but should work for all Docker setups.
The script assumes Plex Media Server is currently running, that you have a mounted `/config` volume in the container, and that your container is named `plex`.
@ -51,7 +65,7 @@ If your container is named differently or if your external volume is mounted els
Run the following command: (you can emit `sudo` if the executing user is in the `docker` group)
```bash
sudo sh -c "$(curl -sSL https://github.com/yuv420p10le/plexmediaserver_crack/releases/latest/download/crack_docker.sh)"
sudo sh -c "$(curl -sSL https://gitgud.io/yuv420p10le/plexmediaserver_crack/-/raw/master/scripts/crack_docker.sh)"
```
Your Plex Media Server should now be restarted with all features unlocked.
@ -59,12 +73,6 @@ Your Plex Media Server should now be restarted with all features unlocked.
This will NOT persist through Docker image updates, as rebuilding the container will copy libraries freshly from the image.
Setup the above commands as a script for an easy installation, and optionally, set it as a cronjob to run daily.
### Other configurations
- For an UNRAID (x86-64) setup guide, [check Mizz141's gist](https://gist.github.com/mizz141/608d21fbc2fe4480286c76cc421f40d3).
- For binary patching for Windows/Linux (x86-64/ARM64) instead of DLL sideloading/import hijacking, [check edde746's fork](https://github.com/edde746/plexmediaserver_crack).
### Building
#### Windows
@ -73,13 +81,49 @@ Build using Visual Studio 2022, C++20. You need [Zydis](https://github.com/zyant
#### Linux
[linuxserver/docker-plex](https://github.com/linuxserver/docker-plex)'s image uses GLIBC 2.35, and Debian stable (bookworm, as of now) is at 2.36. Use a host with an older version to build (e.g. Debian Bullseye).
[linuxserver/docker-plex](https://github.com/linuxserver/docker-plex)'s image uses GLIBC 2.35, and Debian stable (bookworm, as of now) is at 2.36. Use a host with an older version to build (e.g. Debian Bullseye.. or an Alpine container with older glibc such as 2.26).
`git` `cmake` `make` and a C++ compiler (tested with clang++) required.
```bash
git clone https://gitgud.io/yuv420p10le/plexmediaserver_crack.git && \
cd plexmediaserver_crack/linux && \
cmake . && \
make
```
Alpine:
```Dockerfile
FROM alpine:3.18
RUN apk --no-cache add wget ca-certificates libstdc++
ARG APK_GLIBC_VERSION=2.26-r0
ARG APK_GLIBC_FILE="glibc-${APK_GLIBC_VERSION}.apk"
ARG APK_GLIBC_BIN_FILE="glibc-bin-${APK_GLIBC_VERSION}.apk"
ARG APK_GLIBC_BASE_URL="https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${APK_GLIBC_VERSION}"
RUN wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub \
&& wget "${APK_GLIBC_BASE_URL}/${APK_GLIBC_FILE}" \
&& apk --force-overwrite --no-cache add "${APK_GLIBC_FILE}" \
&& wget "${APK_GLIBC_BASE_URL}/${APK_GLIBC_BIN_FILE}" \
&& apk --no-cache add "${APK_GLIBC_BIN_FILE}" \
&& rm glibc-*
RUN apk add --no-cache cmake make gcc g++ bash
WORKDIR /src
```
```bash
docker build -t glibc .
docker run --rm -v plexmediaserver_crack/linux:/src:rw glibc bash -c "mkdir -p build && cd build && cmake .. && make && chmod -R 777 /src/build"
# Binary will be in plexmediaserver_crack/linux/build/plexmediaserver_crack.so
```
`plexmediaserver_crack.so` should now appear in the same directory. Refer to the `scripts/crack_native.sh` or `scripts/crack_docker.sh` scripts to see how it's installed.
### Troubleshooting
* For intro/credit detection, go to Settings -> Library -> Marker source; and select the "local detection only" option.
* If hardware transcoding (or any other feature) does not work, it should not be related to this repository. Use Google to troubleshoot why said feature doesn't work on your setup specifically.
* The "Skip Intro" button will not be displayed on clients that don't have the Plex Pass. It's a client sided limitation. I wrote a crack for the Plex client on Windows to circumvent it, at [plexpass_hook](https://github.com/yuv420p10le/plexpass_hook). The "Skip Credits" button will appear on all clients, including the free ones.
* The "Skip Intro" button will not be displayed on clients that don't have the Plex Pass. It's a client sided limitation. I wrote a crack for the Plex client on Windows to circumvent it, at [plexpass_hook](https://gitgud.io/yuv420p10le/plexpass_hook). The "Skip Credits" button will appear on all clients, including the free ones.
### Screenshots

BIN
binaries/IPHLPAPI.dll Normal file

Binary file not shown.

1
binaries/README.md Normal file
View File

@ -0,0 +1 @@
GitLab is god awful and makes life hard when it comes to attaching binaries to a release. I'm just putting them here.

Binary file not shown.

View File

@ -6,6 +6,7 @@
#include <cstring>
#include <sstream>
#include <unordered_map>
#include <bitset>
#include <unistd.h>
#include <sys/mman.h>
#include "Zydis.h"
@ -33,6 +34,7 @@ std::unordered_map<std::string, std::string> g_features =
{ "6ab6677b-ad9b-444f-9ca1-b8027d05b3e1", "client-non-destructive-comskip" },
{ "5b6190a9-77a4-477e-9fbc-c8118e35a4c1", "client-radio-stations" },
{ "65152b75-13a9-408a-bd30-dbd23a259183", "cloudsync" },
{ "b25b878c-4f60-4337-9f6b-2d97ef41d036", "cloudflare-turnstile-required" },
{ "c4704b28-4e26-460a-bf2e-2576d0c2cb77", "community-new-user-onboarding" },
{ "7bb1ed71-a0a3-4362-aa08-7c3fa7241578", "community-friendships-management" },
{ "fc3e8322-5e6e-4f4a-9d71-728c6d5656bd", "community-phase0" },
@ -55,6 +57,7 @@ std::unordered_map<std::string, std::string> g_features =
{ "ce8f644e-87ce-4ba5-b165-fadd69778019", "disable_sharing_friendships" },
{ "6225c337-cd26-4ff0-b864-6c6dd84c9e0d", "disco-reported-issues" },
{ "d865f64a-ca06-472d-ae01-7a444aba6251", "disco-director-cast-crew-updates" },
{ "e9cc7ec1-be5a-4727-af7b-0f107af1a07c", "disco-epg-airings-on-detail-pages" },
{ "2131d3dc-56c8-45d0-acec-c4683fd9a027", "discover-genre-browsing" },
{ "cb0e4c75-b1cb-43e9-97ea-6b9bc66c717b", "discover-managed-related-vod" },
{ "807d9881-a846-40c3-8d54-84fc490b7ba9", "discover-managed-user-test" },
@ -119,6 +122,7 @@ std::unordered_map<std::string, std::string> g_features =
{ "2ea0e464-ea4f-4be2-97c1-ce6ed4b377dd", "photos-metadata-edition" },
{ "850f3d1e-3f38-44c1-9c0c-e3c9127b8b5a", "photosV6-edit" },
{ "3a2b0cb6-1519-4431-98e2-823c248c70eb", "photosV6-tv-albums" },
{ "068f4adf-43e5-4cc6-b5a1-1243e1be4c53", "playback-speed" },
{ "02da2909-ddfd-46be-9e42-65008a79fc05", "played_badges" },
{ "9aea4ca5-2095-4619-9339-88c1e662fde6", "pms_health" },
{ "222020fb-1504-492d-af33-a0b80a49558a", "premium-dashboard" },
@ -156,7 +160,10 @@ std::unordered_map<std::string, std::string> g_features =
{ "1dd846ed-7cde-4dc5-8ef6-53d3ce8c4e9d", "visualizers" },
{ "bbf73498-4912-4d80-9560-47c4fe212cec", "volume-leveling" },
{ "07f804e6-28e6-4beb-b5c3-f2aefc88b938", "tunefind-clients" },
{ "9b5a4bea-3bbe-45d2-b226-00a6ef4d8e65", "tvod" },
{ "5d80b92d-4ecf-4b0b-935f-5efc907bb2c1", "tvod_playback" },
{ "362c5ba7-41e8-400d-8354-18d53868e2d3", "tvod-rentals" },
{ "e25d0e25-109e-4d6d-9a54-db0931af31c3", "tvod-wtw" },
{ "06d14b9e-2af8-4c2b-a4a1-ea9d5c515824", "two-factor-authentication" },
{ "20824f5c-6dd9-4655-9970-e7701a73c02a", "two-factor-authentication-clients" },
{ "d14556be-ae6d-4407-89d0-b83953f4789a", "type-first" },
@ -170,6 +177,7 @@ std::unordered_map<std::string, std::string> g_features =
{ "1b870b8e-f1a7-497c-80b2-857d45f3123f", "vod-schema" },
{ "65faa2d0-f57e-4c63-a6b6-f1baa48951b1", "watch-together-20200520" },
{ "f83450e2-759a-4de4-8b31-e4a163896d43", "watch-together-invite" },
{ "236de47b-a757-4ed7-9003-507b296057b5", "watched-badges-v3" },
{ "f0c452ce-11e7-465f-be04-5fb0bf4bec48", "watchlist" },
{ "edd6039a-137c-4ace-b5d5-4e111ce9690b", "watchlist-source" },
{ "f0f40559-a43a-4b8f-85ef-bdb1de1a912a", "watchlist-rss" },
@ -209,8 +217,11 @@ std::unordered_map<std::string, std::string> g_features =
{ "a6f3f9b3-c10c-4b94-ad59-755e30ac6c90", "detect-commercials" },
};
std::bitset<704>* g_feature_flags;
auto _is_feature_available = reinterpret_cast<decltype(&hook_is_feature_available)>(0);
auto _map_find = reinterpret_cast<decltype(&hook_map_find)>(0);
auto _bitset_init = reinterpret_cast<decltype(&hook_bitset_init)>(0);
auto _is_user_feature_set = reinterpret_cast<decltype(&hook_is_user_feature_set)>(0);
std::optional<std::tuple<uintptr_t, uintptr_t>> get_dottext_info()
{
@ -348,9 +359,9 @@ bool process_feature(const char* guid)
return false;
}
uint64_t hook_is_feature_available(uintptr_t user, const char* feature)
uint64_t hook_is_feature_available(uintptr_t user, const char** feature)
{
if(process_feature(feature))
if(process_feature(*feature))
{
return true;
}
@ -370,6 +381,19 @@ uint64_t* hook_map_find(uintptr_t* rcx, const char** str)
return _map_find(rcx, str);
}
uint64_t hook_bitset_init(uintptr_t rcx)
{
auto ret = _bitset_init(rcx);
g_feature_flags->set();
return ret;
}
bool hook_is_user_feature_set([[maybe_unused]] uintptr_t rcx, [[maybe_unused]] int expected, [[maybe_unused]] int feature)
{
return static_cast<bool>(expected);
}
void hook()
{
auto info = get_dottext_info();
@ -382,6 +406,32 @@ void hook()
const auto start = std::get<0>(info.value());
const auto end = std::get<1>(info.value());
if(const auto is_user_feature_set = sig_scan(start, end, "55 48 89 E5 48 8B 07 48 85 C0 74 6A"); is_user_feature_set)
{
if(auto trampoline = create_hook(is_user_feature_set.value(), reinterpret_cast<uintptr_t>(hook_is_user_feature_set)); trampoline)
{
_is_user_feature_set = reinterpret_cast<decltype(_is_user_feature_set)>(trampoline.value());
}
}
// Features are now enabled in boost::atomic<std::bitset> as of 2024/08/13 PMS BETA
if(const auto bitset = sig_scan(start, end, "48 8D 0D ? ? ? ? 48 8B 94 05 90 FE FF FF"); bitset)
{
const uintptr_t addr = bitset.value() + 7 + *reinterpret_cast<uint32_t*>(bitset.value() + 3);
g_feature_flags = reinterpret_cast<std::bitset<704>*>(addr);
if(const auto bitset_init = sig_scan(start, end, "55 48 89 E5 41 57 41 56 41 55 41 54 53 48 81 EC ? ? 00 00 49 89 FE 48 8D 9D ? ? ? ? 48 89 DF E8 ? ? ? ? 48 8B 1B 48 85 DB"); bitset_init)
{
if(auto trampoline = create_hook(bitset_init.value(), reinterpret_cast<uintptr_t>(hook_bitset_init)); trampoline)
{
_bitset_init = reinterpret_cast<decltype(_bitset_init)>(trampoline.value());
// No reason to hook the rest
return;
}
}
}
if(const auto is_feature_available_ref = sig_scan(start, end, "E8 ? ? ? ? 86 43"); is_feature_available_ref)
{
const auto is_feature_available = follow_call_rel32(is_feature_available_ref.value());

View File

@ -9,6 +9,8 @@ std::optional<std::tuple<uintptr_t, uintptr_t>> get_dottext_info();
std::optional<uintptr_t> create_hook(uintptr_t from, uintptr_t to);
std::optional<uintptr_t> sig_scan(const uintptr_t start, const uintptr_t end, std::string_view pattern);
uintptr_t follow_call_rel32(const uintptr_t address);
uint64_t hook_is_feature_available(uintptr_t rcx, const char* guid);
uint64_t hook_is_feature_available(uintptr_t rcx, const char** guid);
uint64_t* hook_map_find(uintptr_t* rcx, const char** str);
uint64_t hook_bitset_init(uintptr_t rcx);
bool hook_is_user_feature_set(uintptr_t rcx, int expected, int feature);
void hook();

View File

@ -5,7 +5,7 @@
PLEX_CONFIG_DIR=/config
PLEX_CONTAINER_NAME=plex
PLEX_MEDIA_SERVER_DIR=$(ps aux | grep 'Plex Media Server' | grep -v grep | awk '{print $11}' | xargs dirname)
PLEX_MEDIA_SERVER_DIR=$(ps aux | grep 'Plex Media Server' | grep -v grep | awk '{print $11}' | xargs dirname | uniq)
if [ `id -u` -ne 0 ] && ! groups $(whoami) | grep -q '\bdocker\b'; then
echo "Run this script as root or through 'sudo'. Alternatively, add your user account to the 'docker' group. Script aborting."
@ -20,10 +20,10 @@ fi
rm -rf /tmp/plexmediaserver_crack
mkdir /tmp/plexmediaserver_crack
cd /tmp/plexmediaserver_crack
wget https://github.com/yuv420p10le/plexmediaserver_crack/releases/latest/download/plexmediaserver_crack.so
docker cp $(which patchelf) $PLEX_CONTAINER_NAME:$PLEX_CONFIG_DIR
docker cp plexmediaserver_crack.so $PLEX_CONTAINER_NAME:$PLEX_CONFIG_DIR
docker exec -it $PLEX_CONTAINER_NAME ln -s /config/plexmediaserver_crack.so $PLEX_MEDIA_SERVER_DIR/lib/plexmediaserver_crack.so
docker exec -it $PLEX_CONTAINER_NAME /config/patchelf --remove-needed plexmediaserver_crack.so $PLEX_MEDIA_SERVER_DIR/lib/libsoci_core.so
docker exec -it $PLEX_CONTAINER_NAME /config/patchelf --add-needed plexmediaserver_crack.so $PLEX_MEDIA_SERVER_DIR/lib/libsoci_core.so
wget https://gitgud.io/yuv420p10le/plexmediaserver_crack/-/raw/master/binaries/plexmediaserver_crack.so
docker cp $(which patchelf) $PLEX_CONTAINER_NAME:$PLEX_CONFIG_DIR/patchelf
docker cp plexmediaserver_crack.so $PLEX_CONTAINER_NAME:$PLEX_CONFIG_DIR/plexmediaserver_crack.so
docker exec $PLEX_CONTAINER_NAME ln -sf /config/plexmediaserver_crack.so $PLEX_MEDIA_SERVER_DIR/lib/plexmediaserver_crack.so
docker exec $PLEX_CONTAINER_NAME /config/patchelf --remove-needed plexmediaserver_crack.so $PLEX_MEDIA_SERVER_DIR/lib/libsoci_core.so
docker exec $PLEX_CONTAINER_NAME /config/patchelf --add-needed plexmediaserver_crack.so $PLEX_MEDIA_SERVER_DIR/lib/libsoci_core.so
docker restart $PLEX_CONTAINER_NAME

View File

@ -2,7 +2,7 @@
# The script assumes Plex Media Server is currently running.
PLEX_MEDIA_SERVER_DIR=$(ps aux | grep 'Plex Media Server' | grep -v grep | awk '{print $11}' | xargs dirname)
PLEX_MEDIA_SERVER_DIR=$(ps aux | grep 'Plex Media Server' | grep -v grep | awk '{print $11}' | xargs dirname | uniq)
if [ `id -u` -ne 0 ]; then
echo "Run this script as root or with sudo. Script aborting."
@ -17,9 +17,9 @@ fi
rm -rf /opt/plexmediaserver_crack
mkdir /opt/plexmediaserver_crack
cd /opt/plexmediaserver_crack
wget https://github.com/yuv420p10le/plexmediaserver_crack/releases/latest/download/plexmediaserver_crack.so
wget https://gitgud.io/yuv420p10le/plexmediaserver_crack/-/raw/master/binaries/plexmediaserver_crack.so
rm $PLEX_MEDIA_SERVER_DIR/lib/plexmediaserver_crack.so
ln -s /opt/plexmediaserver_crack/plexmediaserver_crack.so $PLEX_MEDIA_SERVER_DIR/lib/plexmediaserver_crack.so
ln -sf /opt/plexmediaserver_crack/plexmediaserver_crack.so $PLEX_MEDIA_SERVER_DIR/lib/plexmediaserver_crack.so
patchelf --remove-needed plexmediaserver_crack.so $PLEX_MEDIA_SERVER_DIR/lib/libsoci_core.so
patchelf --add-needed plexmediaserver_crack.so $PLEX_MEDIA_SERVER_DIR/lib/libsoci_core.so
systemctl restart plexmediaserver

View File

@ -16,6 +16,7 @@
#include <regex>
#include <print>
#endif
#include <bitset>
std::unordered_map<std::string, std::string> g_features =
{
@ -40,6 +41,7 @@ std::unordered_map<std::string, std::string> g_features =
{ "6ab6677b-ad9b-444f-9ca1-b8027d05b3e1", "client-non-destructive-comskip" },
{ "5b6190a9-77a4-477e-9fbc-c8118e35a4c1", "client-radio-stations" },
{ "65152b75-13a9-408a-bd30-dbd23a259183", "cloudsync" },
{ "b25b878c-4f60-4337-9f6b-2d97ef41d036", "cloudflare-turnstile-required" },
{ "c4704b28-4e26-460a-bf2e-2576d0c2cb77", "community-new-user-onboarding" },
{ "7bb1ed71-a0a3-4362-aa08-7c3fa7241578", "community-friendships-management" },
{ "fc3e8322-5e6e-4f4a-9d71-728c6d5656bd", "community-phase0" },
@ -49,6 +51,7 @@ std::unordered_map<std::string, std::string> g_features =
{ "06798ab7-4fa5-4416-9db3-f313c4292f01", "community-p2-r2" },
{ "626004b8-a8b8-4fb1-9adc-8a6277f98597", "community-p2-r3" },
{ "8f47a689-aa84-408f-bf6b-00015e9413e1", "community-p2-r4" },
{ "ad57040a-0b38-4862-bfcf-3fe116e9767b", "community-p3r1-reactions" },
{ "7f46bf17-fabf-4f96-99a2-cf374f6eed71", "comments_and_replies_push_notifications" },
{ "c36a6985-eee3-4400-a394-c5787fad15b5", "friend_request_push_notifications" },
{ "3f6baa76-7488-479a-9e4f-49ff2c0d3711", "community_access_plex_tv" },
@ -62,6 +65,7 @@ std::unordered_map<std::string, std::string> g_features =
{ "ce8f644e-87ce-4ba5-b165-fadd69778019", "disable_sharing_friendships" },
{ "6225c337-cd26-4ff0-b864-6c6dd84c9e0d", "disco-reported-issues" },
{ "d865f64a-ca06-472d-ae01-7a444aba6251", "disco-director-cast-crew-updates" },
{ "e9cc7ec1-be5a-4727-af7b-0f107af1a07c", "disco-epg-airings-on-detail-pages" },
{ "2131d3dc-56c8-45d0-acec-c4683fd9a027", "discover-genre-browsing" },
{ "cb0e4c75-b1cb-43e9-97ea-6b9bc66c717b", "discover-managed-related-vod" },
{ "807d9881-a846-40c3-8d54-84fc490b7ba9", "discover-managed-user-test" },
@ -126,6 +130,7 @@ std::unordered_map<std::string, std::string> g_features =
{ "2ea0e464-ea4f-4be2-97c1-ce6ed4b377dd", "photos-metadata-edition" },
{ "850f3d1e-3f38-44c1-9c0c-e3c9127b8b5a", "photosV6-edit" },
{ "3a2b0cb6-1519-4431-98e2-823c248c70eb", "photosV6-tv-albums" },
{ "068f4adf-43e5-4cc6-b5a1-1243e1be4c53", "playback-speed" },
{ "02da2909-ddfd-46be-9e42-65008a79fc05", "played_badges" },
{ "9aea4ca5-2095-4619-9339-88c1e662fde6", "pms_health" },
{ "222020fb-1504-492d-af33-a0b80a49558a", "premium-dashboard" },
@ -134,7 +139,6 @@ std::unordered_map<std::string, std::string> g_features =
{ "3eb2789b-200c-4a15-91d2-dedfe560953c", "rate-limit-client-token" },
{ "64adaa4e-aa7e-457d-b385-51438216d7fe", "shared_server_notification" },
{ "6c4d66d9-729d-49dc-b70d-ab2652abf15a", "shared_source_notification" },
{ "0cce52a7-0778-4781-9a07-712370fb6b8a", "require-plex-nonce" },
{ "644c4466-05fa-45e0-a478-c594cf81778f", "save-to-library" },
{ "ccef9d3a-537a-43d9-8161-4c7113c6e2bb", "scrobbling-service" },
{ "7b392594-6949-4736-9894-e57a9dfe4037", "scrobbling-service-plex-tv" },
@ -164,6 +168,8 @@ std::unordered_map<std::string, std::string> g_features =
{ "bbf73498-4912-4d80-9560-47c4fe212cec", "volume-leveling" },
{ "07f804e6-28e6-4beb-b5c3-f2aefc88b938", "tunefind-clients" },
{ "5d80b92d-4ecf-4b0b-935f-5efc907bb2c1", "tvod_playback" },
{ "362c5ba7-41e8-400d-8354-18d53868e2d3", "tvod-rentals" },
{ "e25d0e25-109e-4d6d-9a54-db0931af31c3", "tvod-wtw" },
{ "06d14b9e-2af8-4c2b-a4a1-ea9d5c515824", "two-factor-authentication" },
{ "20824f5c-6dd9-4655-9970-e7701a73c02a", "two-factor-authentication-clients" },
{ "d14556be-ae6d-4407-89d0-b83953f4789a", "type-first" },
@ -177,6 +183,7 @@ std::unordered_map<std::string, std::string> g_features =
{ "1b870b8e-f1a7-497c-80b2-857d45f3123f", "vod-schema" },
{ "65faa2d0-f57e-4c63-a6b6-f1baa48951b1", "watch-together-20200520" },
{ "f83450e2-759a-4de4-8b31-e4a163896d43", "watch-together-invite" },
{ "236de47b-a757-4ed7-9003-507b296057b5", "watched-badges-v3" },
{ "f0c452ce-11e7-465f-be04-5fb0bf4bec48", "watchlist" },
{ "edd6039a-137c-4ace-b5d5-4e111ce9690b", "watchlist-source" },
{ "f0f40559-a43a-4b8f-85ef-bdb1de1a912a", "watchlist-rss" },
@ -239,9 +246,13 @@ std::unordered_set<std::string> g_ignored_guids
"a548af72-b804-4d05-8569-52785952d31d", // Unknown, used in LibraryRequestHandler
};
std::bitset<416>* g_feature_flags;
SafetyHookInline _is_feature_available{};
SafetyHookInline _map_find{};
SafetyHookInline _is_user_feature_set{};
SafetyHookInline _feature_manager_init{};
SafetyHookInline _bitset_init{};
auto _feature_manager = reinterpret_cast<FeatureManager*>(0);
uintptr_t get_current_process_handle()
@ -366,9 +377,11 @@ bool process_feature(const char* guid, [[maybe_unused]] uintptr_t caller)
return false;
}
uint64_t hook_is_feature_available(uintptr_t user, const char* feature)
uint64_t hook_is_feature_available(uintptr_t user, str_holder* feature)
{
if(process_feature(feature, reinterpret_cast<uintptr_t>(_ReturnAddress()) - get_current_process_handle()))
std::string_view str(feature->obj.length >= 16 ? feature->obj.str : feature->str);
if(process_feature(str.data(), reinterpret_cast<uintptr_t>(_ReturnAddress()) - get_current_process_handle()))
{
return true;
}
@ -376,10 +389,12 @@ uint64_t hook_is_feature_available(uintptr_t user, const char* feature)
return _is_feature_available.call<uint64_t>(user, feature);
}
uint64_t* hook_map_find(uintptr_t* rcx, uintptr_t rdx, const char** str)
uint64_t* hook_map_find(uintptr_t* rcx, uintptr_t rdx, str_holder* str)
{
std::string_view feature(str->obj.length >= 16 ? str->obj.str : str->str);
if(_feature_manager != nullptr && rcx == &_feature_manager->m_feature_map() &&
process_feature(*str, reinterpret_cast<uintptr_t>(_ReturnAddress()) - get_current_process_handle()))
process_feature(feature.data(), reinterpret_cast<uintptr_t>(_ReturnAddress()) - get_current_process_handle()))
{
static uint64_t FAKE_PTR = 0;
@ -389,6 +404,25 @@ uint64_t* hook_map_find(uintptr_t* rcx, uintptr_t rdx, const char** str)
return _map_find.call<uint64_t*>(rcx, rdx, str);
}
void hook_bitset_init(uintptr_t rcx, uintptr_t rdx)
{
_bitset_init.call<void>(rcx, rdx);
g_feature_flags->set();
#if _DEBUG
std::println("[INFO] [plexmediaserver_crack] Forced feature flags on {:08X}.", reinterpret_cast<uintptr_t>(g_feature_flags));
#endif
}
uint64_t hook_is_user_feature_set([[maybe_unused]] int expected, [[maybe_unused]] int feature)
{
#if _DEBUG
std::println("[INFO] [plexmediaserver_crack] Spoofed feature {} to {}.", feature, expected);
#endif
return expected;
}
FeatureManager* hook_feature_manager_init(FeatureManager* rcx)
{
return _feature_manager = _feature_manager_init.call<FeatureManager*>(rcx);
@ -414,6 +448,25 @@ void hook()
const auto start = std::get<0>(info.value());
const auto end = std::get<1>(info.value());
const auto bitset = sig_scan(start, end, "8B 84 24 ? ? 00 00 87 05 ? ? ? ? 8B");
const auto bitset_init = sig_scan(start, end, "48 89 5C 24 18 48 89 74 24 20 57 41 54 41 55 41 56 41 57 48 81 EC 90 02 00 00 48 8B 05 ? ? ? ? 48 33 C4 48 89 84 24 88");
const auto is_user_feature_set = sig_scan(start, end, "48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 48 89 7C 24 20 41 56 48 83 EC 30 48 63");
if(is_user_feature_set)
{
_is_user_feature_set = safetyhook::create_inline(reinterpret_cast<void*>(is_user_feature_set.value()), reinterpret_cast<void*>(hook_is_user_feature_set));
}
// Features are now enabled in std::atomic<std::bitset> as of 2024/08/13 PMS BETA
if(bitset && bitset_init)
{
const uintptr_t addr = bitset.value() + 5 + *reinterpret_cast<uint32_t*>(bitset.value() + 9);
g_feature_flags = reinterpret_cast<std::bitset<416>*>(addr + sizeof(uintptr_t));
_bitset_init = safetyhook::create_inline(reinterpret_cast<void*>(bitset_init.value()), reinterpret_cast<void*>(hook_bitset_init));
return;
}
const auto is_feature_available = sig_scan(start, end, "41 54 41 56 41 57 48 83 EC 20 4C 8B F9 4C 8B F2");
const auto map_find = sig_scan(start, end, "48 8B C4 55 41 55");
const auto feature_manager_init = sig_scan(start, end, "48 89 5C 24 10 48 89 74 24 18 48 89 7C 24 20 55 41 54 41 55 41 56 41 57 48 8D AC 24 B0 EB");
@ -422,7 +475,7 @@ void hook()
if((!map_find && !is_feature_available) || !feature_manager_init || !feature_map_offset)
{
#if _DEBUG
std::println("[ERR] [plexmediaserver_crack] Couldn't find either is_feature_enabled or std::map<std::string, std::vector<float>>::find, FeatureManaget::Init, or the feature map offset; aborting.");
std::println("[ERR] [plexmediaserver_crack] Couldn't find either is_feature_enabled or std::map<std::string, std::vector<float>>::find, FeatureManager::Init, or the feature map offset; aborting.");
#endif
return;

View File

@ -24,10 +24,24 @@ public:
member_at(uintptr_t, m_map_offset, m_feature_map);
};
union str_holder
{
struct
{
const char* str;
uintptr_t pad[2];
size_t length;
} obj;
const char str[16];
};
uintptr_t get_current_process_handle();
std::optional<std::tuple<uintptr_t, uintptr_t>> get_section_info(std::string_view name);
std::optional<uintptr_t> sig_scan(const uintptr_t start, const uintptr_t end, std::string_view pattern);
uint64_t hook_is_feature_available(uintptr_t rcx, const char* guid);
uint64_t* hook_map_find(uintptr_t* rcx, uintptr_t rdx, const char** str);
uint64_t hook_is_feature_available(uintptr_t rcx, str_holder* guid);
uint64_t* hook_map_find(uintptr_t* rcx, uintptr_t rdx, str_holder* str);
void hook_bitset_init(uintptr_t rcx, uintptr_t rdx);
uint64_t hook_is_user_feature_set(int expected, int feature);
FeatureManager* hook_feature_manager_init(FeatureManager* rcx);
void hook();