Can load a specified script and run it through, pressing the buttons!

This commit is contained in:
danzel 2017-08-13 09:48:42 +12:00
parent cd10c543c3
commit 793082b74d
11 changed files with 236 additions and 16 deletions

View File

@ -152,6 +152,9 @@ void Config::ReadValues() {
Settings::values.gdbstub_port = Settings::values.gdbstub_port =
static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689)); static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689));
// Scripted Input
Settings::values.script_name = sdl2_config->Get("ScriptedInput", "script_name", "");
// Web Service // Web Service
Settings::values.telemetry_endpoint_url = sdl2_config->Get( Settings::values.telemetry_endpoint_url = sdl2_config->Get(
"WebService", "telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry"); "WebService", "telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry");

View File

@ -169,6 +169,10 @@ log_filter = *:Info
use_gdbstub=false use_gdbstub=false
gdbstub_port=24689 gdbstub_port=24689
[ScriptedInput]
# Script file to load for simulated input
script_name=
[WebService] [WebService]
# Endpoint URL for submitting telemetry data # Endpoint URL for submitting telemetry data
telemetry_endpoint_url = telemetry_endpoint_url =

View File

@ -7,6 +7,7 @@
#include "core/hle/service/hid/hid.h" #include "core/hle/service/hid/hid.h"
#include "core/hle/service/ir/ir.h" #include "core/hle/service/ir/ir.h"
#include "core/settings.h" #include "core/settings.h"
#include "scripted_input/scripted_input.h"
#include "video_core/video_core.h" #include "video_core/video_core.h"
#include "core/frontend/emu_window.h" #include "core/frontend/emu_window.h"
@ -34,6 +35,8 @@ void Apply() {
Service::HID::ReloadInputDevices(); Service::HID::ReloadInputDevices();
Service::IR::ReloadInputDevices(); Service::IR::ReloadInputDevices();
ScriptedInput::LoadScript(values.script_name);
} }
} // namespace } // namespace

View File

@ -127,6 +127,9 @@ struct Values {
bool use_gdbstub; bool use_gdbstub;
u16 gdbstub_port; u16 gdbstub_port;
// ScriptedInput
std::string script_name;
// WebService // WebService
std::string telemetry_endpoint_url; std::string telemetry_endpoint_url;
} extern values; } extern values;

View File

@ -1,14 +1,17 @@
set(SRCS set(SRCS
scripted_input.cpp scripted_input.cpp
scripted_buttons.cpp scripted_buttons.cpp
script_runner.cpp
) )
set(HEADERS set(HEADERS
scripted_input.h scripted_input.h
scripted_buttons.h scripted_buttons.h
script_runner.h
) )
create_directory_groups(${SRCS} ${HEADERS}) create_directory_groups(${SRCS} ${HEADERS})
add_library(scripted_input STATIC ${SRCS} ${HEADERS}) add_library(scripted_input STATIC ${SRCS} ${HEADERS})
target_link_libraries(scripted_input PUBLIC common core) target_link_libraries(scripted_input PUBLIC common core)
target_link_libraries(scripted_input PRIVATE glad)

View File

@ -0,0 +1,133 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <glad/glad.h>
#include "scripted_input/script_runner.h"
#include "scripted_input/scripted_buttons.h"
#define SCRIPT_MAX_LINE 200
namespace ScriptedInput {
void ScriptRunner::SetButtons(std::shared_ptr<ScriptedButtons> buttons) {
scripted_buttons = buttons;
}
void ScriptRunner::LoadScript(std::string script_name) {
//TODO: Load from a file specified in config
FILE* file = fopen(script_name.c_str(), "r");
if (!file) {
//TODO: Log error
return;
}
char line[SCRIPT_MAX_LINE];
int lineno = 0;
while (fgets(line, SCRIPT_MAX_LINE, file) != NULL) {
lineno++;
//Remove end of line bytes and comments
char terminal_chars[] = "\r\n#";
for (int i = 0; i < strlen(terminal_chars); i++) {
char* index = strchr(line, terminal_chars[i]);
if (index != NULL) {
*index = '\0';
}
}
ScriptItem item;
char* pch = strtok(line, " ");
while (pch != NULL) {
if (item.type == ScriptItemType::Undefined) {
//The first token is a number or "screenshot"
if (strcmp("screenshot", pch) == 0) {
item.type = ScriptItemType::Screenshot;
}
else {
int res = sscanf(pch, "%d", &item.frames);
if (res == 1 && item.frames > 0) {
item.type = ScriptItemType::Run;
}
else {
//TODO: Parse error
}
}
}
else {
//The following should be buttons
int index = IndexOfButton(pch);
if (index != -1) {
item.buttons_active.push_back(index);
}
else {
//TODO: Parse error
}
}
pch = strtok(NULL, " ,.-");
}
if (item.type != ScriptItemType::Undefined) {
script.push_back(item);
}
}
fclose(file);
}
void ScriptRunner::NotifyFrameFinished() {
frame_number++;
//Script already finished
if (script_index >= script.size()) {
//printf("already done\n");
return;
}
//If we're done with the current item, move to the next
script_frame++;
if (script_frame == script[script_index].frames) {
printf("At frame %i, progressing to index %i\n", frame_number, script_index + 1);
script_frame = 0;
script_index++;
//take screenshots if we hit a screenshot item
while (script_index < script.size() && script[script_index].type == ScriptItemType::Screenshot) {
//TODO: Screenshot
SaveScreenshot();
script_index++;
}
//Now we'll be at the next run item, so set the buttons up
if (script_index < script.size()) {
printf("changing button\n");
scripted_buttons.get()->SetActiveButtons(script[script_index].buttons_active);
}
}
}
void ScriptRunner::SaveScreenshot() {
const int w = 400;//render_window->GetActiveConfig().min_client_area_size.first;
const int h = 480;//render_window->GetActiveConfig().min_client_area_size.second;
unsigned char* pixels = new unsigned char[w * h * 3];
glReadPixels(0, 0, w, h, GL_BGR, GL_UNSIGNED_BYTE, pixels);
//BMP
//ref https://web.archive.org/web/20080912171714/http://www.fortunecity.com/skyscraper/windows/364/bmpffrmt.html
int image_bytes = (w * h * 3);
int size = 14 + 40 + image_bytes;
unsigned char file_header[14] = { 'B', 'M', size, size >> 8, size >> 16, size >> 24, 0, 0, 0, 0, 14 + 40, 0, 0, 0 };
unsigned char info_header[40] = { 40, 0, 0, 0, w, w >> 8, w >> 16, w >> 24, h, h >> 8, h >> 16, h >> 24, 1, 0, 24, 0, 0, 0, 0, 0, image_bytes, image_bytes >> 8, image_bytes >> 16, image_bytes >> 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
FILE *fp = fopen("out.bmp", "wb");
fwrite(file_header, sizeof(file_header), 1, fp);
fwrite(info_header, sizeof(info_header), 1, fp);
fwrite(pixels, w * h * 3, 1, fp);
fclose(fp);
delete[] pixels;
}
}

View File

@ -0,0 +1,47 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/frontend/input.h"
#include "scripted_input/scripted_buttons.h"
namespace ScriptedInput {
enum class ScriptItemType {
Undefined,
Run,
Screenshot
};
class ScriptItem {
public:
static ScriptItem Screenshot;
ScriptItemType type{ ScriptItemType::Undefined };
int frames {0};
std::vector<int> buttons_active;
};
/**
* A button device factory that returns inputs from a script file
*/
class ScriptRunner final {
public:
void SetButtons(std::shared_ptr<ScriptedButtons> buttons);
void LoadScript(std::string script_name);
void NotifyFrameFinished();
private:
std::vector<ScriptItem> script;
int frame_number {0};
int script_index {0};
int script_frame {0};
std::shared_ptr<ScriptedButtons> scripted_buttons;
void SaveScreenshot();
};
} // namespace InputCommon

View File

@ -2,8 +2,11 @@
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <atomic>
#include "scripted_input/scripted_buttons.h" #include "scripted_input/scripted_buttons.h"
const int button_count = 15;
std::string button_name_to_index[] = { std::string button_name_to_index[] = {
"a", "a",
"b", "b",
@ -22,8 +25,10 @@ std::string button_name_to_index[] = {
"home" "home"
}; };
namespace ScriptedInput {
int IndexOfButton(const std::string& button) { int IndexOfButton(const std::string& button) {
for (int i = 0; i < 15; i++) { for (int i = 0; i < button_count; i++) {
if (button_name_to_index[i] == button) { if (button_name_to_index[i] == button) {
return i; return i;
} }
@ -33,10 +38,6 @@ int IndexOfButton(const std::string& button) {
return -1; //home return -1; //home
} }
int frame = 0;
namespace ScriptedInput {
class ScriptedButton final : public Input::ButtonDevice { class ScriptedButton final : public Input::ButtonDevice {
bool GetStatus() const override { bool GetStatus() const override {
return status.load(); return status.load();
@ -50,7 +51,7 @@ private:
class ScriptedButtonList { class ScriptedButtonList {
//TODO: Do i need to memset(0) buttons? //TODO: Do i need to memset(0) buttons?
public: public:
ScriptedButton* buttons[15]; ScriptedButton* buttons[button_count];
}; };
@ -73,14 +74,23 @@ bool ScriptedButtons::IsInUse() {
return is_in_use; return is_in_use;
} }
void ScriptedButtons::NotifyFrameFinished() { void ScriptedButtons::SetActiveButtons(const std::vector<int>& buttons_active) {
frame++;
if (frame >= 10) {
printf("TOGGLING\r\n");
frame = 0;
auto button = scripted_button_list.get()->buttons[0]; for (int i = 0; i < button_count; i++) {
button->status.store(!button->GetStatus()); auto button = scripted_button_list.get()->buttons[i];
if (button) {
button->status.store(false);
}
}
for (int i = 0; i < buttons_active.size(); i++) {
auto button = scripted_button_list.get()->buttons[buttons_active[i]];
if (button) {
button->status.store(true);
}
else {
//TODO: Warning that button isn't mapped to scripting
}
} }
} }
} }

View File

@ -4,12 +4,13 @@
#pragma once #pragma once
#include <atomic>
#include <memory> #include <memory>
#include "core/frontend/input.h" #include "core/frontend/input.h"
namespace ScriptedInput { namespace ScriptedInput {
int IndexOfButton(const std::string& button);
class ScriptedButtonList; class ScriptedButtonList;
/** /**
@ -30,7 +31,7 @@ public:
*/ */
bool IsInUse(); bool IsInUse();
void NotifyFrameFinished(); void SetActiveButtons(const std::vector<int>& buttons_active);
private: private:
std::shared_ptr<ScriptedButtonList> scripted_button_list; std::shared_ptr<ScriptedButtonList> scripted_button_list;
bool is_in_use {false}; bool is_in_use {false};

View File

@ -3,6 +3,7 @@
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <memory> #include <memory>
#include "scripted_input/script_runner.h"
#include "scripted_input/scripted_buttons.h" #include "scripted_input/scripted_buttons.h"
#include "scripted_input/scripted_input.h" #include "scripted_input/scripted_input.h"
@ -11,10 +12,18 @@ namespace ScriptedInput {
static std::shared_ptr<ScriptedButtons> scripted_buttons; static std::shared_ptr<ScriptedButtons> scripted_buttons;
//TODO: static std::shared_ptr<ScriptedAnalog> scripted_analog; //TODO: static std::shared_ptr<ScriptedAnalog> scripted_analog;
static ScriptRunner script_runner;
void Init() { void Init() {
scripted_buttons = std::make_shared<ScriptedInput::ScriptedButtons>(); scripted_buttons = std::make_shared<ScriptedInput::ScriptedButtons>();
Input::RegisterFactory<Input::ButtonDevice>("scripted", scripted_buttons); Input::RegisterFactory<Input::ButtonDevice>("scripted", scripted_buttons);
script_runner.SetButtons(scripted_buttons);
}
void LoadScript(std::string script_name) {
if (script_name.length() > 0) {
script_runner.LoadScript(script_name);
}
} }
void Shutdown() { void Shutdown() {
@ -22,11 +31,13 @@ void Shutdown() {
} }
bool IsInUse() { bool IsInUse() {
//TODO return script_runner.HasScript();
return scripted_buttons.get()->IsInUse(); return scripted_buttons.get()->IsInUse();
} }
void NotifyFrameFinished() { void NotifyFrameFinished() {
scripted_buttons.get()->NotifyFrameFinished(); script_runner.NotifyFrameFinished();
//TODO? scripted_buttons.get()->NotifyFrameFinished();
} }
} // namespace ScriptedInput } // namespace ScriptedInput

View File

@ -11,6 +11,8 @@ namespace ScriptedInput {
/// Initializes and registers all built-in input device factories. /// Initializes and registers all built-in input device factories.
void Init(); void Init();
void LoadScript(std::string script_name);
/// Unresisters all build-in input device factories and shut them down. /// Unresisters all build-in input device factories and shut them down.
void Shutdown(); void Shutdown();