A mod for Euro Truck Simulator 2 to support "haptic feedback devices" for "reasons"
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

486 lines
16 KiB

7 months ago
/**
* @brief Example of using a shared memory to pass a mostly fixed set of telemetry
* data to another process.
*
* Note that use of the shared memory reduces precision of the output and
* increases latency between event and possible reaction.
*/
// Windows stuff.
#define WINVER 0x0500
#define _WIN32_WINNT 0x0500
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdarg.h>
// SDK
#include "scssdk_telemetry.h"
#include "eurotrucks2/scssdk_eut2.h"
#include "eurotrucks2/scssdk_telemetry_eut2.h"
#include "amtrucks/scssdk_ats.h"
#include "amtrucks/scssdk_telemetry_ats.h"
#define UNUSED(x)
/**
* @name Callbacks remembered from the initialization info.
*/
//@{
scs_telemetry_register_for_channel_t register_for_channel = NULL;
scs_telemetry_unregister_from_channel_t unregister_from_channel = NULL;
scs_log_t game_log = NULL;
//@}
/**
* @brief Prints message to game log.
*/
void log_line(const scs_log_type_t type, const char *const text, ...)
{
if (! game_log) {
return;
}
char formated[1000];
va_list args;
va_start(args, text);
vsnprintf_s(formated, sizeof(formated), _TRUNCATE, text, args);
formated[sizeof(formated) - 1] = 0;
va_end(args);
game_log(type, formated);
}
const size_t MAX_SUPPORTED_WHEEL_COUNT = 8;
#pragma pack(push)
#pragma pack(1)
/**
* @brief The layout of the shared memory.
*/
struct telemetry_state_t
{
scs_u8_t running; // Is the telemetry running or it is paused?
scs_value_dplacement_t ws_truck_placement; // SCS_TELEMETRY_TRUCK_CHANNEL_world_placement
scs_float_t speedometer_speed; // SCS_TELEMETRY_TRUCK_CHANNEL_speed
scs_float_t rpm; // SCS_TELEMETRY_TRUCK_CHANNEL_engine_rpm
scs_s32_t gear; // SCS_TELEMETRY_TRUCK_CHANNEL_engine_gear
scs_float_t steering; // SCS_TELEMETRY_TRUCK_CHANNEL_effective_steering
scs_float_t throttle; // SCS_TELEMETRY_TRUCK_CHANNEL_effective_throttle
scs_float_t brake; // SCS_TELEMETRY_TRUCK_CHANNEL_effective_brake
scs_float_t clutch; // SCS_TELEMETRY_TRUCK_CHANNEL_effective_clutch
scs_value_fvector_t linear_valocity; // SCS_TELEMETRY_TRUCK_CHANNEL_local_linear_velocity
scs_value_fvector_t angular_velocity; // SCS_TELEMETRY_TRUCK_CHANNEL_local_angular_velocity
scs_value_fvector_t linear_acceleration; // SCS_TELEMETRY_TRUCK_CHANNEL_local_linear_acceleration
scs_value_fvector_t angular_acceleration; // SCS_TELEMETRY_TRUCK_CHANNEL_local_angular_acceleration
scs_value_fvector_t cabin_angular_velocity; // SCS_TELEMETRY_TRUCK_CHANNEL_cabin_angular_velocity
scs_value_fvector_t cabin_angular_acceleration; // SCS_TELEMETRY_TRUCK_CHANNEL_cabin_angular_acceleration
scs_u32_t wheel_count; // SCS_TELEMETRY_CONFIG_ATTRIBUTE_wheel_count
scs_float_t wheel_deflections[MAX_SUPPORTED_WHEEL_COUNT]; // SCS_TELEMETRY_TRUCK_CHANNEL_wheel_susp_deflection
};
#pragma pack(pop)
/**
* @brief Handle of the memory mapping.
*/
HANDLE memory_mapping = NULL;
/**
* @brief Block inside the shared memory.
*/
telemetry_state_t *shared_memory = NULL;
/**
* @brief Deinitialize the shared memory objects.
*/
void deinitialize_shared_memory(void)
{
if (shared_memory) {
UnmapViewOfFile(shared_memory);
shared_memory = NULL;
}
if (memory_mapping) {
CloseHandle(memory_mapping);
memory_mapping = NULL;
}
}
/**
* @brief Initialize the shared memory objects.
*/
bool initialize_shared_memory(void)
{
// Setup the mapping.
const DWORD memory_size = sizeof(telemetry_state_t);
memory_mapping = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE | SEC_COMMIT, 0, memory_size, "SCSTelemetryExample");
if (! memory_mapping) {
log_line(SCS_LOG_TYPE_error, "Unable to create shared memory %08X", GetLastError());
deinitialize_shared_memory();
return false;
}
if (GetLastError() == ERROR_ALREADY_EXISTS) {
log_line(SCS_LOG_TYPE_error, "Shared memory is already in use.");
deinitialize_shared_memory();
return false;
}
shared_memory = static_cast<telemetry_state_t *>(MapViewOfFile(memory_mapping, FILE_MAP_ALL_ACCESS, 0, 0, 0));
if (! shared_memory) {
log_line(SCS_LOG_TYPE_error, "Unable to map the view %08X", GetLastError());
deinitialize_shared_memory();
return false;
}
// Defaults in the structure.
memset(shared_memory, 0, memory_size);
// We are always initialized in the paused state.
shared_memory->running = 0;
// No wheels until we get corresponding configuration message.
shared_memory->wheel_count = 0;
return true;
}
/**
* @brief Float storage callback.
*
* Can be used together with SCS_TELEMETRY_CHANNEL_FLAG_no_value in which case it
* will store zero if the value is not available.
*/
SCSAPI_VOID telemetry_store_float(const scs_string_t name, const scs_u32_t index, const scs_value_t *const value, const scs_context_t context)
{
assert(context);
scs_float_t *const storage = static_cast<scs_float_t *>(context);
if (value) {
assert(value->type == SCS_VALUE_TYPE_float);
*storage = value->value_float.value;
}
else {
*storage = 0.0f;
}
}
/**
* @brief s32 storage callback.
*
* Can be used together with SCS_TELEMETRY_CHANNEL_FLAG_no_value in which case it
* will store zero if the value is not available.
*/
SCSAPI_VOID telemetry_store_s32(const scs_string_t name, const scs_u32_t index, const scs_value_t *const value, const scs_context_t context)
{
assert(context);
scs_s32_t *const storage = static_cast<scs_s32_t *>(context);
if (value) {
assert(value->type == SCS_VALUE_TYPE_s32);
*storage = value->value_s32.value;
}
else {
*storage = 0;
}
}
/**
* @brief Orientation storage callback.
*
* Can be used together with SCS_TELEMETRY_CHANNEL_FLAG_no_value in which case it
* will store zero if the value is not available.
*/
SCSAPI_VOID telemetry_store_orientation(const scs_string_t name, const scs_u32_t index, const scs_value_t *const value, const scs_context_t context)
{
assert(context);
scs_value_euler_t *const storage = static_cast<scs_value_euler_t *>(context);
if (value) {
assert(value->type == SCS_VALUE_TYPE_euler);
*storage = value->value_euler;
}
else {
storage->heading = 0.0f;
storage->pitch = 0.0f;
storage->roll = 0.0f;
}
}
/**
* @brief Vector storage callback.
*
* Can be used together with SCS_TELEMETRY_CHANNEL_FLAG_no_value in which case it
* will store zero if the value is not available.
*/
SCSAPI_VOID telemetry_store_fvector(const scs_string_t name, const scs_u32_t index, const scs_value_t *const value, const scs_context_t context)
{
assert(context);
scs_value_fvector_t *const storage = static_cast<scs_value_fvector_t *>(context);
if (value) {
assert(value->type == SCS_VALUE_TYPE_fvector);
*storage = value->value_fvector;
}
else {
storage->x = 0.0f;
storage->y = 0.0f;
storage->z = 0.0f;
}
}
/**
* @brief Placement storage callback.
*
* Can be used together with SCS_TELEMETRY_CHANNEL_FLAG_no_value in which case it
* will store zeros if the value is not available.
*/
SCSAPI_VOID telemetry_store_dplacement(const scs_string_t name, const scs_u32_t index, const scs_value_t *const value, const scs_context_t context)
{
assert(context);
scs_value_dplacement_t *const storage = static_cast<scs_value_dplacement_t *>(context);
if (value) {
assert(value->type == SCS_VALUE_TYPE_dplacement);
*storage = value->value_dplacement;
}
else {
storage->position.x = 0.0;
storage->position.y = 0.0;
storage->position.z = 0.0;
storage->orientation.heading = 0.0f;
storage->orientation.pitch = 0.0f;
storage->orientation.roll = 0.0f;
}
}
/**
* @brief Finds attribute with specified name in the configuration structure.
*
* Returns NULL if the attribute was not found or if it is not of the expected type.
*/
const scs_named_value_t *find_attribute(const scs_telemetry_configuration_t &configuration, const char *const name, const scs_u32_t index, const scs_value_type_t expected_type)
{
for (const scs_named_value_t *current = configuration.attributes; current->name; ++current) {
if ((current->index != index) || (strcmp(current->name, name) != 0)) {
continue;
}
if (current->value.type == expected_type) {
return current;
}
log_line(SCS_LOG_TYPE_error, "Attribute %s has unexpected type %u", name, static_cast<unsigned>(current->value.type));
break;
}
return NULL;
}
/**
* @brief Called whenever the game pauses or unpauses its telemetry output.
*/
SCSAPI_VOID telemetry_pause(const scs_event_t event, const void *const UNUSED(event_info), const scs_context_t UNUSED(context))
{
shared_memory->running = (event == SCS_TELEMETRY_EVENT_started) ? 1 : 0;
}
/**
* @brief Called whenever configuration changes.
*/
SCSAPI_VOID telemetry_configuration(const scs_event_t event, const void *const event_info, const scs_context_t UNUSED(context))
{
// We currently only care for the truck telemetry info.
const struct scs_telemetry_configuration_t *const info = static_cast<const scs_telemetry_configuration_t *>(event_info);
if (strcmp(info->id, SCS_TELEMETRY_CONFIG_truck) != 0) {
return;
}
// Determine number of wheels up to the number supported by our memory structure.
const scs_named_value_t *const wheel_count_attr = find_attribute(*info, SCS_TELEMETRY_CONFIG_ATTRIBUTE_wheel_count, SCS_U32_NIL, SCS_VALUE_TYPE_u32);
size_t wheel_count = wheel_count_attr ? wheel_count_attr->value.value_u32.value : 0;
if (wheel_count > MAX_SUPPORTED_WHEEL_COUNT) {
wheel_count = MAX_SUPPORTED_WHEEL_COUNT;
}
// Update the wheel-related channel registrations to match.
while (shared_memory->wheel_count > wheel_count) {
--shared_memory->wheel_count;
shared_memory->wheel_deflections[shared_memory->wheel_count] = 0.0f;
unregister_from_channel(SCS_TELEMETRY_TRUCK_CHANNEL_wheel_susp_deflection, shared_memory->wheel_count, SCS_VALUE_TYPE_float);
}
while (shared_memory->wheel_count < wheel_count) {
register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_wheel_susp_deflection, shared_memory->wheel_count, SCS_VALUE_TYPE_float, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_float, (shared_memory->wheel_deflections + shared_memory->wheel_count));
++shared_memory->wheel_count;
}
}
/**
* @brief Telemetry API initialization function.
*
* See scssdk_telemetry.h
*/
SCSAPI_RESULT scs_telemetry_init(const scs_u32_t version, const scs_telemetry_init_params_t *const params)
{
// We currently support only one version of the API.
if (version != SCS_TELEMETRY_VERSION_1_00) {
return SCS_RESULT_unsupported;
}
const scs_telemetry_init_params_v100_t *const version_params = static_cast<const scs_telemetry_init_params_v100_t *>(params);
game_log = version_params->common.log;
// Check application version.
log_line(SCS_LOG_TYPE_message, "Game '%s' %u.%u", version_params->common.game_id, SCS_GET_MAJOR_VERSION(version_params->common.game_version), SCS_GET_MINOR_VERSION(version_params->common.game_version));
if (strcmp(version_params->common.game_id, SCS_GAME_ID_EUT2) == 0) {
// Below the minimum version there might be some missing features (only minor change) or
// incompatible values (major change).
if (version_params->common.game_version < SCS_TELEMETRY_EUT2_GAME_VERSION_1_03) { // Fixed the wheels.count attribute
log_line(SCS_LOG_TYPE_error, "Too old version of the game");
game_log = NULL;
return SCS_RESULT_unsupported;
}
if (version_params->common.game_version < SCS_TELEMETRY_EUT2_GAME_VERSION_1_07) { // Fixed the angular acceleration calculation
log_line(SCS_LOG_TYPE_warning, "This version of the game has less precise output of angular acceleration of the cabin");
}
// Future versions are fine as long the major version is not changed.
const scs_u32_t IMPLEMENTED_VERSION = SCS_TELEMETRY_EUT2_GAME_VERSION_CURRENT;
if (SCS_GET_MAJOR_VERSION(version_params->common.game_version) > SCS_GET_MAJOR_VERSION(IMPLEMENTED_VERSION)) {
log_line(SCS_LOG_TYPE_warning, "Too new major version of the game, some features might behave incorrectly");
}
}
else if (strcmp(version_params->common.game_id, SCS_GAME_ID_ATS) == 0) {
// Below the minimum version there might be some missing features (only minor change) or
// incompatible values (major change).
const scs_u32_t MINIMAL_VERSION = SCS_TELEMETRY_ATS_GAME_VERSION_1_00;
if (version_params->common.game_version < MINIMAL_VERSION) {
log_line(SCS_LOG_TYPE_warning, "WARNING: Too old version of the game, some features might behave incorrectly");
}
// Future versions are fine as long the major version is not changed.
const scs_u32_t IMPLEMENTED_VERSION = SCS_TELEMETRY_ATS_GAME_VERSION_CURRENT;
if (SCS_GET_MAJOR_VERSION(version_params->common.game_version) > SCS_GET_MAJOR_VERSION(IMPLEMENTED_VERSION)) {
log_line(SCS_LOG_TYPE_warning, "WARNING: Too new major version of the game, some features might behave incorrectly");
}
}
else {
log_line(SCS_LOG_TYPE_warning, "Unsupported game, some features or values might behave incorrectly");
}
// Register for events. Note that failure to register those basic events
// likely indicates invalid usage of the api or some critical problem. As the
// example requires all of them, we can not continue if the registration fails.
const bool events_registered =
(version_params->register_for_event(SCS_TELEMETRY_EVENT_paused, telemetry_pause, NULL) == SCS_RESULT_ok) &&
(version_params->register_for_event(SCS_TELEMETRY_EVENT_started, telemetry_pause, NULL) == SCS_RESULT_ok) &&
(version_params->register_for_event(SCS_TELEMETRY_EVENT_configuration, telemetry_configuration, NULL) == SCS_RESULT_ok)
;
if (! events_registered) {
// Registrations created by unsuccessful initialization are
// cleared automatically so we can simply exit.
log_line(SCS_LOG_TYPE_error, "Unable to register event callbacks");
game_log = NULL;
return SCS_RESULT_generic_error;
}
// Initialize the shared memory.
if (! initialize_shared_memory()) {
log_line(SCS_LOG_TYPE_error, "Unable to initialize shared memory");
game_log = NULL;
return SCS_RESULT_generic_error;
}
// Register all changes we are interested in. Note that some wheel-related channels will be initialized when we
// receive a configuration event. The channel might be missing if the game does not support it (SCS_RESULT_not_found)
// or if does not support the requested type (SCS_RESULT_unsupported_type). For purpose of this example we ignore
// the failures so the unsupported channels will remain at theirs default value.
#define register_channel(name, index, type, field) version_params->register_for_channel(SCS_TELEMETRY_##name, index, SCS_VALUE_TYPE_##type, SCS_TELEMETRY_CHANNEL_FLAG_no_value, telemetry_store_##type, &shared_memory->field);
register_channel(TRUCK_CHANNEL_world_placement, SCS_U32_NIL, dplacement, ws_truck_placement);
register_channel(TRUCK_CHANNEL_speed, SCS_U32_NIL, float, speedometer_speed);
register_channel(TRUCK_CHANNEL_engine_rpm, SCS_U32_NIL, float, rpm);
register_channel(TRUCK_CHANNEL_engine_gear, SCS_U32_NIL, s32, gear);
register_channel(TRUCK_CHANNEL_effective_steering, SCS_U32_NIL, float, steering);
register_channel(TRUCK_CHANNEL_effective_throttle, SCS_U32_NIL, float, throttle);
register_channel(TRUCK_CHANNEL_effective_brake, SCS_U32_NIL, float, brake);
register_channel(TRUCK_CHANNEL_effective_clutch, SCS_U32_NIL, float, clutch);
register_channel(TRUCK_CHANNEL_local_linear_velocity, SCS_U32_NIL, fvector, linear_valocity);
register_channel(TRUCK_CHANNEL_local_angular_velocity, SCS_U32_NIL, fvector, angular_velocity);
register_channel(TRUCK_CHANNEL_local_linear_acceleration, SCS_U32_NIL, fvector, linear_acceleration);
register_channel(TRUCK_CHANNEL_local_angular_acceleration, SCS_U32_NIL, fvector, angular_acceleration);
register_channel(TRUCK_CHANNEL_cabin_angular_velocity, SCS_U32_NIL, fvector, cabin_angular_velocity);
register_channel(TRUCK_CHANNEL_cabin_angular_acceleration, SCS_U32_NIL, fvector, cabin_angular_acceleration);
#undef register_channel
// Remember other the functions we will use in the future.
register_for_channel = version_params->register_for_channel;
unregister_from_channel = version_params->unregister_from_channel;
// We are done.
log_line(SCS_LOG_TYPE_message, "Memory telemetry example initialized");
return SCS_RESULT_ok;
}
/**
* @brief Telemetry API deinitialization function.
*
* See scssdk_telemetry.h
*/
SCSAPI_VOID scs_telemetry_shutdown(void)
{
// Any cleanup needed. The registrations will be removed automatically
// so there is no need to do that manually.
deinitialize_shared_memory();
unregister_from_channel = NULL;
register_for_channel = NULL;
game_log = NULL;
}
// Cleanup
BOOL APIENTRY DllMain(
HMODULE module,
DWORD reason_for_call,
LPVOID reseved
)
{
return TRUE;
}
// EOF //