Compare commits

..

No commits in common. "master" and "v1.5" have entirely different histories.
master ... v1.5

7 changed files with 13 additions and 60 deletions

View File

@ -123,7 +123,7 @@ docker run --rm -v plexmediaserver_crack/linux:/src:rw glibc bash -c "mkdir -p b
* For intro/credit detection, go to Settings -> Library -> Marker source; and select the "local detection only" option. * 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. * 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://gitgud.io/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://github.com/yuv420p10le/plexpass_hook). The "Skip Credits" button will appear on all clients, including the free ones.
### Screenshots ### Screenshots

Binary file not shown.

Binary file not shown.

View File

@ -221,7 +221,6 @@ std::bitset<704>* g_feature_flags;
auto _is_feature_available = reinterpret_cast<decltype(&hook_is_feature_available)>(0); auto _is_feature_available = reinterpret_cast<decltype(&hook_is_feature_available)>(0);
auto _map_find = reinterpret_cast<decltype(&hook_map_find)>(0); auto _map_find = reinterpret_cast<decltype(&hook_map_find)>(0);
auto _bitset_init = reinterpret_cast<decltype(&hook_bitset_init)>(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() std::optional<std::tuple<uintptr_t, uintptr_t>> get_dottext_info()
{ {
@ -389,11 +388,6 @@ uint64_t hook_bitset_init(uintptr_t rcx)
return ret; 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() void hook()
{ {
auto info = get_dottext_info(); auto info = get_dottext_info();
@ -406,19 +400,11 @@ void hook()
const auto start = std::get<0>(info.value()); const auto start = std::get<0>(info.value());
const auto end = std::get<1>(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 // 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) 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); const uintptr_t addr = bitset.value() + 7 + *reinterpret_cast<uint32_t*>(bitset.value() + 3);
g_feature_flags = reinterpret_cast<std::bitset<704>*>(addr); g_feature_flags = reinterpret_cast<std::bitset<704>*>(addr + sizeof(uintptr_t));
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(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)
{ {

View File

@ -12,5 +12,4 @@ 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_map_find(uintptr_t* rcx, const char** str);
uint64_t hook_bitset_init(uintptr_t rcx); uint64_t hook_bitset_init(uintptr_t rcx);
bool hook_is_user_feature_set(uintptr_t rcx, int expected, int feature);
void hook(); void hook();

View File

@ -51,7 +51,6 @@ std::unordered_map<std::string, std::string> g_features =
{ "06798ab7-4fa5-4416-9db3-f313c4292f01", "community-p2-r2" }, { "06798ab7-4fa5-4416-9db3-f313c4292f01", "community-p2-r2" },
{ "626004b8-a8b8-4fb1-9adc-8a6277f98597", "community-p2-r3" }, { "626004b8-a8b8-4fb1-9adc-8a6277f98597", "community-p2-r3" },
{ "8f47a689-aa84-408f-bf6b-00015e9413e1", "community-p2-r4" }, { "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" }, { "7f46bf17-fabf-4f96-99a2-cf374f6eed71", "comments_and_replies_push_notifications" },
{ "c36a6985-eee3-4400-a394-c5787fad15b5", "friend_request_push_notifications" }, { "c36a6985-eee3-4400-a394-c5787fad15b5", "friend_request_push_notifications" },
{ "3f6baa76-7488-479a-9e4f-49ff2c0d3711", "community_access_plex_tv" }, { "3f6baa76-7488-479a-9e4f-49ff2c0d3711", "community_access_plex_tv" },
@ -139,6 +138,7 @@ std::unordered_map<std::string, std::string> g_features =
{ "3eb2789b-200c-4a15-91d2-dedfe560953c", "rate-limit-client-token" }, { "3eb2789b-200c-4a15-91d2-dedfe560953c", "rate-limit-client-token" },
{ "64adaa4e-aa7e-457d-b385-51438216d7fe", "shared_server_notification" }, { "64adaa4e-aa7e-457d-b385-51438216d7fe", "shared_server_notification" },
{ "6c4d66d9-729d-49dc-b70d-ab2652abf15a", "shared_source_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" }, { "644c4466-05fa-45e0-a478-c594cf81778f", "save-to-library" },
{ "ccef9d3a-537a-43d9-8161-4c7113c6e2bb", "scrobbling-service" }, { "ccef9d3a-537a-43d9-8161-4c7113c6e2bb", "scrobbling-service" },
{ "7b392594-6949-4736-9894-e57a9dfe4037", "scrobbling-service-plex-tv" }, { "7b392594-6949-4736-9894-e57a9dfe4037", "scrobbling-service-plex-tv" },
@ -167,6 +167,7 @@ std::unordered_map<std::string, std::string> g_features =
{ "1dd846ed-7cde-4dc5-8ef6-53d3ce8c4e9d", "visualizers" }, { "1dd846ed-7cde-4dc5-8ef6-53d3ce8c4e9d", "visualizers" },
{ "bbf73498-4912-4d80-9560-47c4fe212cec", "volume-leveling" }, { "bbf73498-4912-4d80-9560-47c4fe212cec", "volume-leveling" },
{ "07f804e6-28e6-4beb-b5c3-f2aefc88b938", "tunefind-clients" }, { "07f804e6-28e6-4beb-b5c3-f2aefc88b938", "tunefind-clients" },
{ "9b5a4bea-3bbe-45d2-b226-00a6ef4d8e65", "tvod" },
{ "5d80b92d-4ecf-4b0b-935f-5efc907bb2c1", "tvod_playback" }, { "5d80b92d-4ecf-4b0b-935f-5efc907bb2c1", "tvod_playback" },
{ "362c5ba7-41e8-400d-8354-18d53868e2d3", "tvod-rentals" }, { "362c5ba7-41e8-400d-8354-18d53868e2d3", "tvod-rentals" },
{ "e25d0e25-109e-4d6d-9a54-db0931af31c3", "tvod-wtw" }, { "e25d0e25-109e-4d6d-9a54-db0931af31c3", "tvod-wtw" },
@ -250,7 +251,6 @@ std::bitset<416>* g_feature_flags;
SafetyHookInline _is_feature_available{}; SafetyHookInline _is_feature_available{};
SafetyHookInline _map_find{}; SafetyHookInline _map_find{};
SafetyHookInline _is_user_feature_set{};
SafetyHookInline _feature_manager_init{}; SafetyHookInline _feature_manager_init{};
SafetyHookInline _bitset_init{}; SafetyHookInline _bitset_init{};
auto _feature_manager = reinterpret_cast<FeatureManager*>(0); auto _feature_manager = reinterpret_cast<FeatureManager*>(0);
@ -377,11 +377,9 @@ bool process_feature(const char* guid, [[maybe_unused]] uintptr_t caller)
return false; return false;
} }
uint64_t hook_is_feature_available(uintptr_t user, str_holder* feature) uint64_t hook_is_feature_available(uintptr_t user, const char** feature)
{ {
std::string_view str(feature->obj.length >= 16 ? feature->obj.str : feature->str); if(process_feature(*feature, reinterpret_cast<uintptr_t>(_ReturnAddress()) - get_current_process_handle()))
if(process_feature(str.data(), reinterpret_cast<uintptr_t>(_ReturnAddress()) - get_current_process_handle()))
{ {
return true; return true;
} }
@ -389,12 +387,10 @@ uint64_t hook_is_feature_available(uintptr_t user, str_holder* feature)
return _is_feature_available.call<uint64_t>(user, feature); return _is_feature_available.call<uint64_t>(user, feature);
} }
uint64_t* hook_map_find(uintptr_t* rcx, uintptr_t rdx, str_holder* str) uint64_t* hook_map_find(uintptr_t* rcx, uintptr_t rdx, const char** str)
{ {
std::string_view feature(str->obj.length >= 16 ? str->obj.str : str->str);
if(_feature_manager != nullptr && rcx == &_feature_manager->m_feature_map() && if(_feature_manager != nullptr && rcx == &_feature_manager->m_feature_map() &&
process_feature(feature.data(), reinterpret_cast<uintptr_t>(_ReturnAddress()) - get_current_process_handle())) process_feature(*str, reinterpret_cast<uintptr_t>(_ReturnAddress()) - get_current_process_handle()))
{ {
static uint64_t FAKE_PTR = 0; static uint64_t FAKE_PTR = 0;
@ -410,19 +406,10 @@ void hook_bitset_init(uintptr_t rcx, uintptr_t rdx)
g_feature_flags->set(); g_feature_flags->set();
#if _DEBUG #if _DEBUG
std::println("[INFO] [plexmediaserver_crack] Forced feature flags on {:08X}.", reinterpret_cast<uintptr_t>(g_feature_flags)); std::println("[INFO] [plexmediaserver_crack] Forced feature flags on.", reinterpret_cast<uintptr_t>(g_feature_flags));
#endif #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) FeatureManager* hook_feature_manager_init(FeatureManager* rcx)
{ {
return _feature_manager = _feature_manager_init.call<FeatureManager*>(rcx); return _feature_manager = _feature_manager_init.call<FeatureManager*>(rcx);
@ -449,18 +436,12 @@ void hook()
const auto start = std::get<0>(info.value()); const auto start = std::get<0>(info.value());
const auto end = std::get<1>(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 = 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 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 // Features are now enabled in std::atomic<std::bitset> as of 2024/08/13 PMS BETA
if(bitset && bitset_init) if(bitset && bitset_init)
{ {
const uintptr_t addr = bitset.value() + 5 + *reinterpret_cast<uint32_t*>(bitset.value() + 9); const uintptr_t addr = bitset.value() + 6 + *reinterpret_cast<uint32_t*>(bitset.value() + 9);
g_feature_flags = reinterpret_cast<std::bitset<416>*>(addr + sizeof(uintptr_t)); 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)); _bitset_init = safetyhook::create_inline(reinterpret_cast<void*>(bitset_init.value()), reinterpret_cast<void*>(hook_bitset_init));

View File

@ -24,24 +24,11 @@ public:
member_at(uintptr_t, m_map_offset, m_feature_map); 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(); uintptr_t get_current_process_handle();
std::optional<std::tuple<uintptr_t, uintptr_t>> get_section_info(std::string_view name); 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); 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, str_holder* guid); uint64_t hook_is_feature_available(uintptr_t rcx, const char** guid);
uint64_t* hook_map_find(uintptr_t* rcx, uintptr_t rdx, str_holder* str); uint64_t* hook_map_find(uintptr_t* rcx, uintptr_t rdx, const char** str);
void hook_bitset_init(uintptr_t rcx, uintptr_t rdx); 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); FeatureManager* hook_feature_manager_init(FeatureManager* rcx);
void hook(); void hook();