mirror of
https://github.com/citra-emu/citra.git
synced 2024-11-30 04:30:06 +00:00
Can load a specified script and run it through, pressing the buttons!
This commit is contained in:
parent
cd10c543c3
commit
793082b74d
@ -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");
|
||||||
|
@ -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 =
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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)
|
133
src/scripted_input/script_runner.cpp
Normal file
133
src/scripted_input/script_runner.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
47
src/scripted_input/script_runner.h
Normal file
47
src/scripted_input/script_runner.h
Normal 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
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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};
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user