mirror of
https://github.com/InstanceBrick/nightcap.git
synced 2026-10-17 17:00:55 +02:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f6847d6d7 |
No files matched your search
@@ -19,6 +19,13 @@ inline constexpr std::size_t kMaximumRobloxLaunchUriBytes = 64 * 1024;
|
||||
Status ParseRobloxLaunchUri(std::string_view uri,
|
||||
RobloxExperienceLaunchRequest* request);
|
||||
|
||||
// Converts a WebView launchGame command
|
||||
// ({"functionName":"launchGame","params":{"request":{...}}}) into the same
|
||||
// contract. Returns kUnsupported for other commands and kInvalidArgument for
|
||||
// a malformed launchGame request.
|
||||
Status ParseRobloxWebViewLaunchGame(std::string_view command,
|
||||
RobloxExperienceLaunchRequest* request);
|
||||
|
||||
} // namespace runtime
|
||||
} // namespace mocktail
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "jnivm/jnivm.h"
|
||||
#include "runtime/external_launch_broker.h"
|
||||
#include "runtime/roblox_launch_uri.h"
|
||||
#include "runtime/webview_helper_launcher.h"
|
||||
#include "window/window.h"
|
||||
|
||||
@@ -951,9 +952,23 @@ Status RobloxExperienceComposition::RouteCurrentWebSurfaceEvent(
|
||||
route = web_surface_route_;
|
||||
web_view_bridge = web_view_bridge_.get();
|
||||
}
|
||||
return event.type == WebViewHelperEventType::kRobloxCookie
|
||||
? AcceptWebViewRobloxCookie(event.payload)
|
||||
: RouteWebSurfaceEvent(route, event, web_view_bridge);
|
||||
if (event.type == WebViewHelperEventType::kRobloxCookie) {
|
||||
return AcceptWebViewRobloxCookie(event.payload);
|
||||
}
|
||||
if (event.type == WebViewHelperEventType::kExecuteRoblox) {
|
||||
// Roblox defers a signalled launchGame until the Lua app next navigates.
|
||||
// A parsed launchGame is launched here and never forwarded, so a deferred
|
||||
// copy cannot start a second join later.
|
||||
RobloxExperienceLaunchRequest request;
|
||||
if (ParseRobloxWebViewLaunchGame(event.payload, &request).ok() &&
|
||||
Dispatch(request).ok()) {
|
||||
std::fprintf(stderr,
|
||||
"[experience] WebView launchGame queued place_id=%lld\n",
|
||||
static_cast<long long>(request.place_id));
|
||||
return CloseWebSurface();
|
||||
}
|
||||
}
|
||||
return RouteWebSurfaceEvent(route, event, web_view_bridge);
|
||||
}
|
||||
|
||||
Status RobloxExperienceComposition::DrainPlatformEvents() {
|
||||
|
||||
@@ -640,5 +640,64 @@ Status ParseRobloxLaunchUri(std::string_view uri,
|
||||
return MakeRequest(fields, request);
|
||||
}
|
||||
|
||||
Status ParseRobloxWebViewLaunchGame(std::string_view command,
|
||||
RobloxExperienceLaunchRequest* request) {
|
||||
if (request == nullptr) {
|
||||
return Invalid("Roblox launchGame output is null");
|
||||
}
|
||||
*request = {};
|
||||
const nlohmann::json document =
|
||||
command.size() <= kMaximumRobloxLaunchUriBytes
|
||||
? nlohmann::json::parse(command, nullptr, false)
|
||||
: nlohmann::json();
|
||||
const auto function_name = document.is_object()
|
||||
? document.find("functionName")
|
||||
: document.end();
|
||||
if (!document.is_object() || function_name == document.end() ||
|
||||
*function_name != "launchGame") {
|
||||
return Status::Error(StatusCode::kUnsupported,
|
||||
"WebView command is not launchGame");
|
||||
}
|
||||
const auto params = document.find("params");
|
||||
if (params == document.end() || !params->is_object()) {
|
||||
return Invalid("Roblox launchGame has no params");
|
||||
}
|
||||
const auto launch = params->find("request");
|
||||
if (launch == params->end() || !launch->is_object()) {
|
||||
return Invalid("Roblox launchGame has no request");
|
||||
}
|
||||
if (launch->size() > kMaximumParameterCount) {
|
||||
return Invalid("Roblox launchGame request has too many fields");
|
||||
}
|
||||
LaunchFields fields;
|
||||
for (const auto& [key, value] : launch->items()) {
|
||||
std::string text;
|
||||
if (value.is_string()) {
|
||||
text = value.get<std::string>();
|
||||
} else if (value.is_number_unsigned()) {
|
||||
text = std::to_string(value.get<uint64_t>());
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
if (text.size() > kMaximumParameterValueBytes ||
|
||||
ContainsControlBytes(text)) {
|
||||
return Invalid("Roblox launchGame field is invalid");
|
||||
}
|
||||
Status status = AssignField(key == "requestType" ? "request" : key,
|
||||
std::move(text), false, &fields);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
if (fields.request_type.empty()) {
|
||||
return Invalid("Roblox launchGame request type is missing");
|
||||
}
|
||||
Status status = MakeRequest(fields, request);
|
||||
if (!status.ok()) {
|
||||
*request = {};
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
} // namespace runtime
|
||||
} // namespace mocktail
|
||||
@@ -322,6 +322,77 @@ TEST(RobloxLaunchUriTest, ClearsOutputAndRedactsTicketOnFailure) {
|
||||
EXPECT_FALSE(ParseRobloxLaunchUri(kModernPublicLaunchUri, nullptr).ok());
|
||||
}
|
||||
|
||||
TEST(RobloxWebViewLaunchGameTest, ParsesPrivateServerPlayButton) {
|
||||
constexpr char kCommand[] =
|
||||
R"({"moduleID":"Game","functionName":"launchGame",)"
|
||||
R"("callbackID":"b8cf5fbd-f6a8-cfba-2cae-84bce46f46b2",)"
|
||||
R"("params":{"request":{"requestType":"RequestPrivateGame",)"
|
||||
R"("browserTrackerId":"1790271319250009",)"
|
||||
R"("accessCode":"664b524f-19cc-4e28-a947-e8f5286f4179",)"
|
||||
R"("placeId":"920587237","joinAttemptId":"attempt-1",)"
|
||||
R"("joinAttemptOrigin":"privateServerListJoin",)"
|
||||
R"("isPlayTogetherGame":false}}})";
|
||||
RobloxExperienceLaunchRequest request;
|
||||
|
||||
const Status status = ParseRobloxWebViewLaunchGame(kCommand, &request);
|
||||
|
||||
ASSERT_TRUE(status.ok()) << status.message();
|
||||
EXPECT_EQ(request.place_id, 920587237);
|
||||
EXPECT_EQ(request.access_code, "664b524f-19cc-4e28-a947-e8f5286f4179");
|
||||
EXPECT_EQ(request.join_attempt_id, "attempt-1");
|
||||
EXPECT_EQ(request.join_attempt_origin, "privateServerListJoin");
|
||||
EXPECT_EQ(request.canonical_json.find("browserTrackerId"), std::string::npos);
|
||||
ExpectCanonicalRequestMatches(request);
|
||||
}
|
||||
|
||||
TEST(RobloxWebViewLaunchGameTest, ParsesServerJobWithNumericPlaceId) {
|
||||
constexpr char kCommand[] =
|
||||
R"({"moduleID":"Game","functionName":"launchGame",)"
|
||||
R"("params":{"request":{"requestType":"RequestGameJob",)"
|
||||
R"("placeId":1962086868,"gameId":"97ea-49e4"}}})";
|
||||
RobloxExperienceLaunchRequest request;
|
||||
|
||||
const Status status = ParseRobloxWebViewLaunchGame(kCommand, &request);
|
||||
|
||||
ASSERT_TRUE(status.ok()) << status.message();
|
||||
EXPECT_EQ(request.place_id, 1962086868);
|
||||
EXPECT_EQ(request.game_instance_id, "97ea-49e4");
|
||||
ExpectCanonicalRequestMatches(request);
|
||||
}
|
||||
|
||||
TEST(RobloxWebViewLaunchGameTest, ReportsOtherCommandsAsUnsupported) {
|
||||
RobloxExperienceLaunchRequest request;
|
||||
request.place_id = 123;
|
||||
request.canonical_json = "stale";
|
||||
|
||||
const Status status = ParseRobloxWebViewLaunchGame(
|
||||
R"({"moduleID":"Game","functionName":"getSystemInfo","params":{}})",
|
||||
&request);
|
||||
|
||||
EXPECT_EQ(status.code(), StatusCode::kUnsupported);
|
||||
EXPECT_EQ(request.place_id, 0);
|
||||
EXPECT_TRUE(request.canonical_json.empty());
|
||||
EXPECT_EQ(ParseRobloxWebViewLaunchGame("not json", &request).code(),
|
||||
StatusCode::kUnsupported);
|
||||
}
|
||||
|
||||
TEST(RobloxWebViewLaunchGameTest, RejectsMalformedLaunchGame) {
|
||||
const char* const kCommands[] = {
|
||||
R"({"functionName":"launchGame"})",
|
||||
R"({"functionName":"launchGame","params":{"request":"x"}})",
|
||||
R"({"functionName":"launchGame","params":{"request":{)"
|
||||
R"("requestType":"RequestPrivateGame","placeId":"920587237"}}})",
|
||||
R"({"functionName":"launchGame","params":{"request":{)"
|
||||
R"("requestType":"RequestGame","placeId":"-1"}}})",
|
||||
};
|
||||
for (const char* command : kCommands) {
|
||||
RobloxExperienceLaunchRequest request;
|
||||
const Status status = ParseRobloxWebViewLaunchGame(command, &request);
|
||||
EXPECT_EQ(status.code(), StatusCode::kInvalidArgument) << command;
|
||||
EXPECT_EQ(request.place_id, 0) << command;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace runtime
|
||||
} // namespace mocktail
|
||||
Reference in new issue
Block a user