diff --git a/Makefile.common b/Makefile.common
index 17c5e401853d..a550c791462a 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -2445,6 +2445,7 @@ endif
ifeq ($(HAVE_TEST_DRIVERS), 1)
DEFINES += -DHAVE_TEST_DRIVERS
OBJ += input/drivers_joypad/test_joypad.o
+ OBJ += input/drivers/test_input.o
endif
diff --git a/configuration.c b/configuration.c
index 2371165abf6d..4f51d4863e04 100644
--- a/configuration.c
+++ b/configuration.c
@@ -1669,7 +1669,8 @@ static struct config_path_setting *populate_settings_path(
#endif
#ifdef HAVE_TEST_DRIVERS
- SETTING_PATH("test_input_file_joypad", settings->paths.test_input_file_joypad, false, NULL, true);
+ SETTING_PATH("test_input_file_joypad", settings->paths.test_input_file_joypad, false, NULL, true);
+ SETTING_PATH("test_input_file_general", settings->paths.test_input_file_general, false, NULL, true);
#endif
SETTING_ARRAY("log_dir", settings->paths.log_dir, true, NULL, true);
diff --git a/configuration.h b/configuration.h
index 0d67a825fff5..e65fe8e0f3df 100644
--- a/configuration.h
+++ b/configuration.h
@@ -577,6 +577,7 @@ typedef struct settings
#endif
#ifdef HAVE_TEST_DRIVERS
char test_input_file_joypad[PATH_MAX_LENGTH];
+ char test_input_file_general[PATH_MAX_LENGTH];
#endif
char log_dir[PATH_MAX_LENGTH];
char app_icon[PATH_MAX_LENGTH];
diff --git a/griffin/griffin.c b/griffin/griffin.c
index e1bb908f5888..d1986ea63801 100644
--- a/griffin/griffin.c
+++ b/griffin/griffin.c
@@ -733,6 +733,7 @@ INPUT
#ifdef HAVE_TEST_DRIVERS
#include "../input/drivers_joypad/test_joypad.c"
+#include "../input/drivers/test_input.c"
#endif
/*============================================================
diff --git a/input/drivers/test_input.c b/input/drivers/test_input.c
new file mode 100644
index 000000000000..cd45862007c4
--- /dev/null
+++ b/input/drivers/test_input.c
@@ -0,0 +1,431 @@
+/* RetroArch - A frontend for libretro.
+ * Copyright (C) 2010-2014 - Hans-Kristian Arntzen
+ * Copyright (C) 2011-2017 - Daniel De Matteis
+ * Copyright (C) 2016-2019 - Brad Parker
+ *
+ * RetroArch is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with RetroArch.
+ * If not, see .
+ */
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include "../input_driver.h"
+#include "../input_keymaps.h"
+#include "../../verbosity.h"
+#include "../../gfx/video_driver.h"
+
+#define MAX_TEST_STEPS 200
+
+#define INPUT_TEST_COMMAND_PRESS_KEY 1
+#define INPUT_TEST_COMMAND_RELEASE_KEY 2
+
+/* TODO/FIXME - static globals */
+static uint16_t test_key_state[DEFAULT_MAX_PADS+1][RETROK_LAST];
+
+
+typedef struct
+{
+ unsigned frame;
+ unsigned action;
+ unsigned param_num;
+ char param_str[255];
+ bool handled;
+} input_test_step_t;
+
+static input_test_step_t input_test_steps[MAX_TEST_STEPS];
+
+static unsigned current_frame = 0;
+static unsigned next_teststep_frame = 0;
+static unsigned current_test_step = 0;
+static unsigned last_test_step = MAX_TEST_STEPS + 1;
+static uint32_t input_state_validated = 0;
+static uint32_t combo_state_validated = 0;
+static bool dump_state_blocked = false;
+
+/************************************/
+/* JSON Helpers for test input file */
+/************************************/
+
+typedef struct
+{
+ unsigned *current_entry_uint_val;
+ char **current_entry_str_val;
+ unsigned frame;
+ unsigned action;
+ unsigned param_num;
+ char *param_str;
+} KTifJSONContext;
+
+static bool KTifJSONObjectEndHandler(void* context)
+{
+ KTifJSONContext *pCtx = (KTifJSONContext*)context;
+
+ /* Too long input is handled elsewhere, it should not lead to parse error */
+ if (current_test_step >= MAX_TEST_STEPS)
+ return true;
+
+ /* Copy values read from JSON file + fill defaults */
+ if (pCtx->frame == 0xffff)
+ input_test_steps[current_test_step].frame = input_test_steps[current_test_step-1].frame + 60;
+ else
+ input_test_steps[current_test_step].frame = pCtx->frame;
+
+ input_test_steps[current_test_step].action = pCtx->action;
+ input_test_steps[current_test_step].param_num = pCtx->param_num;
+ input_test_steps[current_test_step].handled = false;
+
+ if (!string_is_empty(pCtx->param_str))
+ strlcpy(
+ input_test_steps[current_test_step].param_str, pCtx->param_str,
+ sizeof(input_test_steps[current_test_step].param_str));
+ else
+ input_test_steps[current_test_step].param_str[0] = '\0';
+
+ current_test_step++;
+ last_test_step = current_test_step;
+ pCtx->frame = 0xffff;
+ return true;
+}
+
+static bool KTifJSONObjectMemberHandler(void* context, const char *pValue, size_t length)
+{
+ KTifJSONContext *pCtx = (KTifJSONContext*)context;
+
+ /* something went wrong */
+ if (pCtx->current_entry_str_val)
+ return false;
+
+ if (length)
+ {
+ if (string_is_equal(pValue, "frame"))
+ pCtx->current_entry_uint_val = &pCtx->frame;
+ else if (string_is_equal(pValue, "action"))
+ pCtx->current_entry_uint_val = &pCtx->action;
+ else if (string_is_equal(pValue, "param_num"))
+ pCtx->current_entry_uint_val = &pCtx->param_num;
+ else if (string_is_equal(pValue, "param_str"))
+ pCtx->current_entry_str_val = &pCtx->param_str;
+ /* ignore unknown members */
+ }
+
+ return true;
+}
+
+static bool KTifJSONNumberHandler(void* context, const char *pValue, size_t length)
+{
+ KTifJSONContext *pCtx = (KTifJSONContext*)context;
+
+ if (pCtx->current_entry_uint_val && length && !string_is_empty(pValue))
+ *pCtx->current_entry_uint_val = string_to_unsigned(pValue);
+ /* ignore unknown members */
+
+ pCtx->current_entry_uint_val = NULL;
+
+ return true;
+}
+
+static bool KTifJSONStringHandler(void* context, const char *pValue, size_t length)
+{
+ KTifJSONContext *pCtx = (KTifJSONContext*)context;
+
+ if (pCtx->current_entry_str_val && length && !string_is_empty(pValue))
+ {
+ if (*pCtx->current_entry_str_val)
+ free(*pCtx->current_entry_str_val);
+
+ *pCtx->current_entry_str_val = strdup(pValue);
+ }
+ /* ignore unknown members */
+
+ pCtx->current_entry_str_val = NULL;
+
+ return true;
+}
+
+/* Parses test input file referenced by file_path.
+ * Does nothing if test input file does not exist. */
+static bool input_test_file_read(const char* file_path)
+{
+ bool success = false;
+ KTifJSONContext context = {0};
+ RFILE *file = NULL;
+ rjson_t* parser;
+
+ /* Sanity check */
+ if ( string_is_empty(file_path)
+ || !path_is_valid(file_path)
+ )
+ {
+ RARCH_DBG("[Test input driver]: No test input file supplied.\n");
+ return false;
+ }
+
+ /* Attempt to open test input file */
+ file = filestream_open(
+ file_path,
+ RETRO_VFS_FILE_ACCESS_READ,
+ RETRO_VFS_FILE_ACCESS_HINT_NONE);
+
+ if (!file)
+ {
+ RARCH_ERR("[Test input driver]: Failed to open test input file: \"%s\".\n",
+ file_path);
+ return false;
+ }
+
+ /* Initialise JSON parser */
+ if (!(parser = rjson_open_rfile(file)))
+ {
+ RARCH_ERR("[Test input driver]: Failed to create JSON parser.\n");
+ goto end;
+ }
+
+ /* Configure parser */
+ rjson_set_options(parser, RJSON_OPTION_ALLOW_UTF8BOM);
+
+ /* Read file */
+ if (rjson_parse(parser, &context,
+ KTifJSONObjectMemberHandler,
+ KTifJSONStringHandler,
+ KTifJSONNumberHandler,
+ NULL, KTifJSONObjectEndHandler, NULL, NULL, /* object/array handlers */
+ NULL, NULL) /* unused boolean/null handlers */
+ != RJSON_DONE)
+ {
+ if (rjson_get_source_context_len(parser))
+ {
+ RARCH_ERR(
+ "[Test input driver]: Error parsing chunk of test input file: %s\n---snip---\n%.*s\n---snip---\n",
+ file_path,
+ rjson_get_source_context_len(parser),
+ rjson_get_source_context_buf(parser));
+ }
+ RARCH_WARN(
+ "[Test input driver]: Error parsing test input file: %s\n",
+ file_path);
+ RARCH_ERR(
+ "[Test input driver]: Error: Invalid JSON at line %d, column %d - %s.\n",
+ (int)rjson_get_source_line(parser),
+ (int)rjson_get_source_column(parser),
+ (*rjson_get_error(parser) ? rjson_get_error(parser) : "format error"));
+ }
+
+ /* Free parser */
+ rjson_free(parser);
+
+ success = true;
+end:
+ /* Clean up leftover strings */
+ if (context.param_str)
+ free(context.param_str);
+
+ /* Close log file */
+ filestream_close(file);
+
+ if (last_test_step >= MAX_TEST_STEPS)
+ {
+ RARCH_WARN("[Test input driver]: too long test input json, maximum size: %d\n",MAX_TEST_STEPS);
+ }
+ for (current_test_step = 0; current_test_step < last_test_step; current_test_step++)
+ {
+ RARCH_DBG(
+ "[Test input driver]: test step %02d read from file: frame %d, action %x, num %x, str %s\n",
+ current_test_step,
+ input_test_steps[current_test_step].frame,
+ input_test_steps[current_test_step].action,
+ input_test_steps[current_test_step].param_num,
+ input_test_steps[current_test_step].param_str);
+ }
+ current_test_step = 0;
+ return success;
+}
+
+/********************************/
+/* Test input file handling end */
+/********************************/
+
+
+/*uint16_t *test_keyboard_state_get(unsigned port)
+{
+ return test_key_state[port];
+}*/
+
+static void test_keyboard_free(void)
+{
+ unsigned i, j;
+
+ for (i = 0; i < DEFAULT_MAX_PADS; i++)
+ for (j = 0; j < RETROK_LAST; j++)
+ test_key_state[i][j] = 0;
+}
+
+static int16_t test_input_state(
+ void *data,
+ const input_device_driver_t *joypad,
+ const input_device_driver_t *sec_joypad,
+ rarch_joypad_info_t *joypad_info,
+ const retro_keybind_set *binds,
+ bool keyboard_mapping_blocked,
+ unsigned port,
+ unsigned device,
+ unsigned idx,
+ unsigned id)
+{
+ if (port <= 0)
+ {
+ switch (device)
+ {
+/* TODO: something clear here */
+ case RETRO_DEVICE_JOYPAD:
+ if (id == RETRO_DEVICE_ID_JOYPAD_MASK)
+ {
+ unsigned i;
+ int16_t ret = 0;
+
+ if (!keyboard_mapping_blocked && id < RARCH_BIND_LIST_END)
+ {
+ for (i = 0; i < RARCH_FIRST_CUSTOM_BIND; i++)
+ {
+ if (binds[port][i].valid)
+ {
+ if ( (binds[port][i].key && binds[port][i].key < RETROK_LAST)
+ && test_key_state[DEFAULT_MAX_PADS][binds[port][id].key])
+ ret |= (1 << i);
+ }
+ }
+ }
+ return ret;
+ }
+
+ if (id < RARCH_BIND_LIST_END)
+ {
+ if (binds[port][id].valid)
+ {
+ if ( (binds[port][id].key && binds[port][id].key < RETROK_LAST)
+ && test_key_state[DEFAULT_MAX_PADS][binds[port][id].key]
+ && (id == RARCH_GAME_FOCUS_TOGGLE || !keyboard_mapping_blocked)
+ )
+ return 1;
+
+ }
+ }
+ break;
+
+
+ case RETRO_DEVICE_KEYBOARD:
+ if (id && id < RETROK_LAST)
+ return (test_key_state[DEFAULT_MAX_PADS][id]);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static void test_input_free_input(void *data)
+{
+ test_keyboard_free();
+}
+
+static void* test_input_init(const char *joypad_driver)
+{
+ settings_t *settings = config_get_ptr();
+ unsigned i;
+
+ RARCH_DBG("[Test input driver]: start\n");
+
+ input_test_file_read(settings->paths.test_input_file_general);
+ if (last_test_step > MAX_TEST_STEPS)
+ last_test_step = 0;
+
+ /* No need for keyboard mapping look-up table */
+ /* input_keymaps_init_keyboard_lut(rarch_key_map_test);*/
+ return (void*)-1;
+}
+
+static void test_input_poll(void *data)
+{
+ video_driver_state_t *video_st = video_state_get_ptr();
+ uint64_t curr_frame = video_st->frame_count;
+ unsigned i;
+
+ for (i=0; i input_test_steps[i].frame)
+ {
+ if( input_test_steps[i].action == INPUT_TEST_COMMAND_PRESS_KEY)
+ {
+ if(input_test_steps[i].param_num < RETROK_LAST)
+ {
+ test_key_state[DEFAULT_MAX_PADS][input_test_steps[i].param_num] = 1;
+ input_keyboard_event(true, input_test_steps[i].param_num, 0, 0, RETRO_DEVICE_KEYBOARD);
+ }
+ input_test_steps[i].handled = true;
+ RARCH_DBG(
+ "[Test input driver]: Pressing keyboard button %d at frame %d\n",
+ input_test_steps[i].param_num, curr_frame);
+ }
+ else if( input_test_steps[i].action == INPUT_TEST_COMMAND_RELEASE_KEY)
+ {
+ if(input_test_steps[i].param_num < RETROK_LAST)
+ {
+ test_key_state[DEFAULT_MAX_PADS][input_test_steps[i].param_num] = 0;
+ input_keyboard_event(false, input_test_steps[i].param_num, 0, 0, RETRO_DEVICE_KEYBOARD);
+ }
+ input_test_steps[i].handled = true;
+ RARCH_DBG(
+ "[Test input driver]: Releasing keyboard button %d at frame %d\n",
+ input_test_steps[i].param_num, curr_frame);
+ }
+ else
+ {
+ input_test_steps[i].handled = true;
+ RARCH_WARN(
+ "[Test input driver]: Unrecognized action %d in step %d, skipping\n",
+ input_test_steps[i].action,i);
+ }
+
+ }
+ }
+}
+
+
+static uint64_t test_input_get_capabilities(void *data)
+{
+ return
+ (1 << RETRO_DEVICE_JOYPAD)
+ | (1 << RETRO_DEVICE_ANALOG)
+ | (1 << RETRO_DEVICE_KEYBOARD)
+/* | (1 << RETRO_DEVICE_MOUSE)
+ | (1 << RETRO_DEVICE_POINTER)
+ | (1 << RETRO_DEVICE_LIGHTGUN)*/;
+}
+
+input_driver_t input_test = {
+ test_input_init,
+ test_input_poll,
+ test_input_state,
+ test_input_free_input,
+ NULL,
+ NULL,
+ test_input_get_capabilities,
+ "test",
+ NULL, /* grab_mouse */
+ NULL,
+ NULL
+};
diff --git a/input/drivers_joypad/test_joypad.c b/input/drivers_joypad/test_joypad.c
index ac9a87a9f4c4..9c7dcf1fcfa1 100644
--- a/input/drivers_joypad/test_joypad.c
+++ b/input/drivers_joypad/test_joypad.c
@@ -194,7 +194,7 @@ static bool input_test_file_read(const char* file_path)
|| !path_is_valid(file_path)
)
{
- RARCH_DBG("[Test input driver]: No test input file supplied.\n");
+ RARCH_DBG("[Test joypad driver]: No test input file supplied.\n");
return false;
}
@@ -206,7 +206,7 @@ static bool input_test_file_read(const char* file_path)
if (!file)
{
- RARCH_ERR("[Test input driver]: Failed to open test input file: \"%s\".\n",
+ RARCH_ERR("[Test joypad driver]: Failed to open test input file: \"%s\".\n",
file_path);
return false;
}
@@ -214,7 +214,7 @@ static bool input_test_file_read(const char* file_path)
/* Initialise JSON parser */
if (!(parser = rjson_open_rfile(file)))
{
- RARCH_ERR("[Test input driver]: Failed to create JSON parser.\n");
+ RARCH_ERR("[Test joypad driver]: Failed to create JSON parser.\n");
goto end;
}
@@ -233,16 +233,16 @@ static bool input_test_file_read(const char* file_path)
if (rjson_get_source_context_len(parser))
{
RARCH_ERR(
- "[Test input driver]: Error parsing chunk of test input file: %s\n---snip---\n%.*s\n---snip---\n",
+ "[Test joypad driver]: Error parsing chunk of test input file: %s\n---snip---\n%.*s\n---snip---\n",
file_path,
rjson_get_source_context_len(parser),
rjson_get_source_context_buf(parser));
}
RARCH_WARN(
- "[Test input driver]: Error parsing test input file: %s\n",
+ "[Test joypad driver]: Error parsing test input file: %s\n",
file_path);
RARCH_ERR(
- "[Test input driver]: Error: Invalid JSON at line %d, column %d - %s.\n",
+ "[Test joypad driver]: Error: Invalid JSON at line %d, column %d - %s.\n",
(int)rjson_get_source_line(parser),
(int)rjson_get_source_column(parser),
(*rjson_get_error(parser) ? rjson_get_error(parser) : "format error"));
@@ -262,12 +262,12 @@ static bool input_test_file_read(const char* file_path)
if (last_test_step >= MAX_TEST_STEPS)
{
- RARCH_WARN("[Test input driver]: too long test input json, maximum size: %d\n",MAX_TEST_STEPS);
+ RARCH_WARN("[Test joypad driver]: too long test input json, maximum size: %d\n",MAX_TEST_STEPS);
}
for (current_test_step = 0; current_test_step < last_test_step; current_test_step++)
{
RARCH_DBG(
- "[Test input driver]: test step %02d read from file: frame %d, action %x, num %x, str %s\n",
+ "[Test joypad driver]: test step %02d read from file: frame %d, action %x, num %x, str %s\n",
current_test_step,
input_test_steps[current_test_step].frame,
input_test_steps[current_test_step].action,
@@ -433,7 +433,7 @@ static void test_joypad_poll(void)
test_joypads[targetpad].button_state |= input_test_steps[i].param_num;
input_test_steps[i].handled = true;
RARCH_DBG(
- "[Test input driver]: Pressing device %d buttons %x, new state %x.\n",
+ "[Test joypad driver]: Pressing device %d buttons %x, new state %x.\n",
targetpad,input_test_steps[i].param_num,test_joypads[targetpad].button_state);
}
else if( input_test_steps[i].action >= JOYPAD_TEST_COMMAND_BUTTON_RELEASE_FIRST &&
@@ -443,7 +443,7 @@ static void test_joypad_poll(void)
test_joypads[targetpad].button_state &= ~input_test_steps[i].param_num;
input_test_steps[i].handled = true;
RARCH_DBG(
- "[Test input driver]: Releasing device %d buttons %x, new state %x.\n",
+ "[Test joypad driver]: Releasing device %d buttons %x, new state %x.\n",
targetpad,input_test_steps[i].param_num,test_joypads[targetpad].button_state);
}
else if( input_test_steps[i].action >= JOYPAD_TEST_COMMAND_BUTTON_AXIS_FIRST &&
@@ -457,19 +457,19 @@ static void test_joypad_poll(void)
test_joypads[targetpad].axis_state[targetaxis] = (int16_t) input_test_steps[i].param_num;
else
RARCH_WARN(
- "[Test input driver]: Decoded axis outside target range: action %d pad %d axis %d.\n",
+ "[Test joypad driver]: Decoded axis outside target range: action %d pad %d axis %d.\n",
input_test_steps[i].action, targetpad, targetaxis);
input_test_steps[i].handled = true;
RARCH_DBG(
- "[Test input driver]: Setting axis device %d axis %d value %d.\n",
+ "[Test joypad driver]: Setting axis device %d axis %d value %d.\n",
targetpad, targetaxis, (int16_t)input_test_steps[i].param_num);
}
else
{
input_test_steps[i].handled = true;
RARCH_WARN(
- "[Test input driver]: Unrecognized action %d in step %d, skipping\n",
+ "[Test joypad driver]: Unrecognized action %d in step %d, skipping\n",
input_test_steps[i].action,i);
}
diff --git a/input/input_driver.c b/input/input_driver.c
index 33fafc37d728..76a2c278cd3f 100644
--- a/input/input_driver.c
+++ b/input/input_driver.c
@@ -363,6 +363,9 @@ input_driver_t *input_drivers[] = {
#endif
#ifdef DJGPP
&input_dos,
+#endif
+#ifdef HAVE_TEST_DRIVERS
+ &input_test,
#endif
&input_null,
NULL,
@@ -4418,11 +4421,24 @@ bool video_driver_init_input(
void *new_data = NULL;
input_driver_t **input = &input_driver_st.current_driver;
if (*input)
+#if HAVE_TEST_DRIVERS
+ if (strcmp(settings->arrays.input_driver,"test") != 0)
+ /* Test driver not in use, keep selected driver */
+ return true;
+ else if (string_is_empty(settings->paths.test_input_file_general))
+ {
+ RARCH_LOG("[Input]: Test input driver selected, but no input file provided - falling back.\n");
+ return true;
+ }
+ else
+ RARCH_LOG("[Video]: Graphics driver initialized an input driver, but ignoring it as test input driver is in use.\n");
+#else
return true;
-
- /* Video driver didn't provide an input driver,
- * so we use configured one. */
- RARCH_LOG("[Video]: Graphics driver did not initialize an input driver."
+#endif
+ else
+ /* Video driver didn't provide an input driver,
+ * so we use configured one. */
+ RARCH_LOG("[Video]: Graphics driver did not initialize an input driver."
" Attempting to pick a suitable driver.\n");
if (tmp)
diff --git a/input/input_driver.h b/input/input_driver.h
index 3db9c79ebe65..50eb5d4ee692 100644
--- a/input/input_driver.h
+++ b/input/input_driver.h
@@ -1103,6 +1103,7 @@ extern input_driver_t input_rwebinput;
extern input_driver_t input_dos;
extern input_driver_t input_winraw;
extern input_driver_t input_wayland;
+extern input_driver_t input_test;
extern input_device_driver_t dinput_joypad;
extern input_device_driver_t linuxraw_joypad;
diff --git a/tests-other/all_binds_empty.cfg b/tests-other/all_binds_empty.cfg
index 82d867584fed..b672d927a123 100644
--- a/tests-other/all_binds_empty.cfg
+++ b/tests-other/all_binds_empty.cfg
@@ -5,6 +5,7 @@
input_exit_emulator = "escape"
input_max_users = "10"
+input_fps_toggle = "f3"
input_ai_service = "nul"
input_ai_service_axis = "nul"
@@ -62,7 +63,7 @@ input_enable_hotkey_mbtn = "nul"
input_exit_emulator_axis = "nul"
input_exit_emulator_btn = "nul"
input_exit_emulator_mbtn = "nul"
-input_fps_toggle = "nul"
+# input_fps_toggle = "nul"
input_fps_toggle_axis = "nul"
input_fps_toggle_btn = "nul"
input_fps_toggle_mbtn = "nul"
diff --git a/tests-other/test_input_keyboard.ratst b/tests-other/test_input_keyboard.ratst
new file mode 100644
index 000000000000..fffbec732791
--- /dev/null
+++ b/tests-other/test_input_keyboard.ratst
@@ -0,0 +1,26 @@
+[{
+ "action": 1,
+ "param_num": 284,
+ "frame": 330
+},{
+ "action": 2,
+ "param_num": 284
+},{
+ "action": 1,
+ "param_num": 284
+},{
+ "action": 2,
+ "param_num": 284
+},{
+ "action": 1,
+ "param_num": 27
+},{
+ "action": 2,
+ "param_num": 27
+},{
+ "action": 1,
+ "param_num": 27
+},{
+ "action": 2,
+ "param_num": 27
+}]