mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 00:22:57 -05:00
185 lines
8.1 KiB
C++
185 lines
8.1 KiB
C++
#include "prx/libSceVideoOut/include/DisplayWindow.hpp"
|
|
#include "prx/libSceVideoOut/include/Telemetry.hpp"
|
|
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <unistd.h>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <thread>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#undef main
|
|
|
|
namespace {
|
|
|
|
void check(bool condition, const std::string& reason) {
|
|
if (!condition) throw std::runtime_error(reason);
|
|
}
|
|
|
|
std::vector<std::string> readLines(const std::filesystem::path& path) {
|
|
std::ifstream input(path);
|
|
std::vector<std::string> lines;
|
|
for (std::string line; std::getline(input, line);) lines.push_back(line);
|
|
return lines;
|
|
}
|
|
|
|
long long field(const std::string& line, const std::string& name) {
|
|
const auto position = line.find("\"" + name + "\":");
|
|
check(position != std::string::npos, "missing field " + name + " in " + line);
|
|
return std::stoll(line.substr(position + name.size() + 3));
|
|
}
|
|
|
|
std::filesystem::path freshDirectory(const char* name) {
|
|
auto directory = std::filesystem::temp_directory_path() / (std::string("aps5-videoout-telemetry-") + name + "-" + std::to_string(::getpid()));
|
|
std::filesystem::remove_all(directory);
|
|
std::filesystem::create_directories(directory);
|
|
return directory;
|
|
}
|
|
|
|
void padChangeIgnoresSensors() {
|
|
VideoOutTelemetry::PadChangeFilter filter;
|
|
PadInputState state;
|
|
check(!filter.Changed(state), "idle state must not be a change");
|
|
state.hasMotion = true;
|
|
state.accel = {1.0f, 2.0f, 3.0f};
|
|
state.gyro = {0.5f, 0.5f, 0.5f};
|
|
state.touch[0] = {true, 100, 200, 1};
|
|
state.touchLeft = true;
|
|
check(!filter.Changed(state), "motion and touch must not be a change");
|
|
state.buttons = 0x4000;
|
|
check(filter.Changed(state), "a button must be a change");
|
|
check(filter.Changed(state), "an uncommitted change must stay a change");
|
|
filter.Commit(state);
|
|
check(!filter.Changed(state), "a repeated state must not be a change");
|
|
state.sticks[2] = 200;
|
|
check(filter.Changed(state), "a stick must be a change");
|
|
filter.Commit(state);
|
|
state.analogButtonsR2 = 10;
|
|
check(filter.Changed(state), "a trigger must be a change");
|
|
}
|
|
|
|
void padChangeSurvivesFullQueue() {
|
|
const auto directory = freshDirectory("fullqueue");
|
|
{
|
|
VideoOutTelemetry::Writer writer(directory.string(), 1, "test");
|
|
VideoOutTelemetry::PadChangeFilter filter;
|
|
PadInputState state;
|
|
state.buttons = 0x4000;
|
|
check(!VideoOutTelemetry::EmitPadIfChanged(writer, filter, 5, state), "a full queue must refuse the pad line");
|
|
check(filter.Changed(state), "a refused pad change must stay pending");
|
|
check(writer.Dropped() == 1, "the refused pad line must be counted");
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(600));
|
|
check(VideoOutTelemetry::EmitPadIfChanged(writer, filter, 6, state), "the retry must succeed once the queue drains");
|
|
check(!filter.Changed(state), "a written pad change must be committed");
|
|
}
|
|
const auto lines = readLines(directory / "videoout.jsonl");
|
|
bool found = false;
|
|
for (const auto& line : lines) found = found || (line.find("\"ev\":\"pad\"") != std::string::npos && field(line, "n") == 6 && field(line, "buttons") == 0x4000);
|
|
check(found, "the retried pad line must be in the file");
|
|
bool dropsLine = false;
|
|
for (const auto& line : lines) dropsLine = dropsLine || (line.find("\"ev\":\"drops\"") != std::string::npos && field(line, "dropped") == 1);
|
|
check(dropsLine, "the writer must report the refused line in a drops line");
|
|
std::filesystem::remove_all(directory);
|
|
}
|
|
|
|
void noEnvironmentOpensNothing() {
|
|
const auto directory = freshDirectory("noenv");
|
|
check(VideoOutTelemetry::OpenFromEnvironment(nullptr, "test") == nullptr, "unset variable must not open a writer");
|
|
check(VideoOutTelemetry::OpenFromEnvironment("", "test") == nullptr, "empty variable must not open a writer");
|
|
check(std::filesystem::is_empty(directory), "no file may exist without the variable");
|
|
std::filesystem::remove_all(directory);
|
|
}
|
|
|
|
void queueOverflowIsCounted() {
|
|
VideoOutTelemetry::BoundedLineQueue queue(2);
|
|
check(queue.TryPush("a"), "first line must fit");
|
|
check(queue.TryPush("b"), "second line must fit");
|
|
check(!queue.TryPush("c"), "third line must overflow");
|
|
check(!queue.TryPush("d"), "fourth line must overflow");
|
|
check(queue.Dropped() == 2, "two overflows must be counted");
|
|
check(queue.Drain().size() == 2, "the stored lines must survive");
|
|
check(queue.TryPush("e"), "a drained queue must accept lines");
|
|
}
|
|
|
|
void contractPadAndPresentLines() {
|
|
const auto directory = freshDirectory("contract");
|
|
{
|
|
auto writer = VideoOutTelemetry::OpenFromEnvironment(directory.c_str(), "abc123");
|
|
check(writer != nullptr, "variable must open a writer");
|
|
PadInputState state;
|
|
state.buttons = 0x4000;
|
|
writer->EmitPad(7, state);
|
|
writer->EmitPresent(30, 59.94);
|
|
}
|
|
const auto lines = readLines(directory / "videoout.jsonl");
|
|
check(lines.size() == 3, "expected contract, pad and present, got " + std::to_string(lines.size()));
|
|
check(lines[0].find("\"ev\":\"contract\"") != std::string::npos, "contract must be first");
|
|
check(lines[0].find("\"contract\":\"v1-pad\"") != std::string::npos, "contract must name the level");
|
|
check(lines[0].find("\"commit\":\"abc123\"") != std::string::npos, "contract must carry the commit");
|
|
check(lines[0].find("\"prx\":\"libSceVideoOut\"") != std::string::npos, "contract must name the prx");
|
|
check(field(lines[0], "pid") > 0, "contract must carry the pid");
|
|
check(lines[1].find("\"ev\":\"pad\"") != std::string::npos && field(lines[1], "n") == 7 && field(lines[1], "buttons") == 0x4000, "pad line");
|
|
check(lines[1].find("\"sticks\":[128,128,128,128]") != std::string::npos, "pad sticks");
|
|
check(lines[2].find("\"ev\":\"present\"") != std::string::npos && field(lines[2], "n") == 30, "present line");
|
|
std::filesystem::remove_all(directory);
|
|
}
|
|
|
|
int frameNumberMatchesCaption() {
|
|
const auto directory = freshDirectory("caption");
|
|
std::filesystem::create_directories(directory / "app0" / "sce_sys");
|
|
std::ofstream(directory / "app0" / "sce_sys" / "param.json") << R"({"titleId":"PPSA00000","localizedParameters":{"defaultLanguage":"en-US","en-US":{"titleName":"Telemetry Test"}}})";
|
|
std::filesystem::current_path(directory);
|
|
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
|
std::printf("skipped, no display or Vulkan device: SDL video driver: %s\n", SDL_GetError());
|
|
return 77;
|
|
}
|
|
DisplayWindow window;
|
|
try {
|
|
window.Ensure(640, 360);
|
|
} catch (const std::exception& error) {
|
|
std::printf("skipped, no display or Vulkan device: %s\n", error.what());
|
|
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
|
return 77;
|
|
}
|
|
check(window.FrameNumber() == 0, "no frame before the first title update");
|
|
for (std::uint64_t expected = 1; expected <= 65; ++expected) {
|
|
window.UpdateTitle();
|
|
check(window.FrameNumber() == expected, "FrameNumber must count title updates");
|
|
const std::string title = SDL_GetWindowTitle(window.Handle());
|
|
const auto open = title.rfind('(');
|
|
check(open != std::string::npos && std::stoull(title.substr(open + 1)) == expected, "caption counter differs from FrameNumber: " + title);
|
|
}
|
|
window.Destroy();
|
|
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
|
std::filesystem::current_path(directory.parent_path());
|
|
std::filesystem::remove_all(directory);
|
|
return 0;
|
|
}
|
|
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
try {
|
|
if (argc > 1 && std::string(argv[1]) == "caption") {
|
|
const int result = frameNumberMatchesCaption();
|
|
if (result == 0) std::printf("VideoOut caption counter test passed\n");
|
|
return result;
|
|
}
|
|
padChangeIgnoresSensors();
|
|
padChangeSurvivesFullQueue();
|
|
noEnvironmentOpensNothing();
|
|
queueOverflowIsCounted();
|
|
contractPadAndPresentLines();
|
|
} catch (const std::exception& error) {
|
|
std::fprintf(stderr, "FAIL: %s\n", error.what());
|
|
return 1;
|
|
}
|
|
std::printf("VideoOut telemetry tests passed\n");
|
|
return 0;
|
|
}
|