Compare commits
6 commits
612cfb7877
...
8ba30675be
Author | SHA1 | Date | |
---|---|---|---|
8ba30675be | |||
|
be72994dc0 | ||
aed30481ea | |||
|
59ea084ff4 | ||
|
5f126e2b55 | ||
804e5fe911 |
9 changed files with 950 additions and 271 deletions
|
@ -16,5 +16,5 @@ void FSkyPortalModule::ShutdownModule()
|
|||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
|
||||
IMPLEMENT_MODULE(FSkyPortalModule, SkyPortal)
|
81
Source/SkyPortal/Private/SkyPortalFigure.cpp
Normal file
81
Source/SkyPortal/Private/SkyPortalFigure.cpp
Normal file
|
@ -0,0 +1,81 @@
|
|||
#pragma once
|
||||
|
||||
#include "SkyPortalFigure.h"
|
||||
#include "SkyPortalSubsystem.h"
|
||||
|
||||
uint32 GetFigureID(const FigureDataBlock& DataBlock)
|
||||
{
|
||||
int16_t OutFigureID;
|
||||
// Figure ID is stored in Block 0, bytes 0 to 3 (32-bit integer, little-endian)
|
||||
OutFigureID = DataBlock.blockdata[0][0] |
|
||||
(DataBlock.blockdata[0][1] << 8) |
|
||||
(DataBlock.blockdata[0][2] << 16) |
|
||||
(DataBlock.blockdata[0][3] << 24);
|
||||
/*
|
||||
// Variant ID is stored in Block 0, bytes 4 to 5 (16-bit integer, little-endian)
|
||||
OutVariantID = (DataBlock.blockdata[0][5] << 8) |
|
||||
DataBlock.blockdata[0][4];
|
||||
*/
|
||||
return OutFigureID;
|
||||
};
|
||||
|
||||
|
||||
void FigureData::ReadData(uint8 index)
|
||||
{
|
||||
ClearData();
|
||||
for (uint8 i = 0; i < 64; i++)
|
||||
{
|
||||
USkyPortalSubsystem* SkySubsystem = GEngine->GetEngineSubsystem<USkyPortalSubsystem>();
|
||||
TArray<uint8> output = SkySubsystem->QueryBlock(index, i);
|
||||
|
||||
uint8[] blockData = new uint8[0x10];
|
||||
Array.Copy(output, 3, blockData, 0, 16);
|
||||
|
||||
// block 1 is sometimes a duplicate of block 0
|
||||
if (i == 1)
|
||||
{
|
||||
if (blockData.SequenceEqual(data[0]))
|
||||
{
|
||||
i -= 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Array.Copy(blockData, 0, data[i], 0, 16);
|
||||
|
||||
if (((i + 1) % 4 == 0) || i < 8)
|
||||
{
|
||||
Array.Copy(data[i], 0, decryptedData[i], 0, 16);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8[] hashIn = new byte[0x56];
|
||||
data[0].CopyTo(hashIn, 0);
|
||||
data[1].CopyTo(hashIn, 0x10);
|
||||
hashIn[0x20] = i;
|
||||
HASH_CONST.CopyTo(hashIn, 0x21);
|
||||
|
||||
uint8[] key = MD5.Create().ComputeHash(hashIn);
|
||||
|
||||
uint8[] decrypted = new byte[0x10];
|
||||
|
||||
using (Aes aesAlg = Aes.Create())
|
||||
{
|
||||
aesAlg.Key = key;
|
||||
aesAlg.Mode = CipherMode.ECB;
|
||||
aesAlg.Padding = PaddingMode.Zeros;
|
||||
|
||||
ICryptoTransform decryptor = aesAlg.CreateDecryptor();
|
||||
|
||||
decrypted = decryptor.TransformFinalBlock(data[i], 0, data[i].Length);
|
||||
|
||||
}
|
||||
|
||||
Array.Copy(decrypted, 0, decryptedData[i], 0, 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FigureData::ClearData()
|
||||
{
|
||||
}
|
166
Source/SkyPortal/Private/SkyPortalIO.cpp
Normal file
166
Source/SkyPortal/Private/SkyPortalIO.cpp
Normal file
|
@ -0,0 +1,166 @@
|
|||
#include "SkyPortalIO.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogHIDApi);
|
||||
DEFINE_LOG_CATEGORY(LogSkyportalIO);
|
||||
|
||||
|
||||
//Constructor
|
||||
USkyPortalIO::USkyPortalIO()
|
||||
{
|
||||
OpenPortalHandle();
|
||||
|
||||
|
||||
}
|
||||
|
||||
bool USkyPortalIO::OpenPortalHandle() {
|
||||
//reset
|
||||
if (PortalDevice) {
|
||||
hid_close(PortalDevice);
|
||||
}
|
||||
/*
|
||||
Declare two pointers to hold information about HID devices.
|
||||
"list" will point to the head of the linked list of devices,
|
||||
"attributes" will be used to iterate through the list.
|
||||
*/
|
||||
struct hid_device_info* list, * attributes;
|
||||
|
||||
list = hid_enumerate(0x0, 0x0);
|
||||
// If `list` is NULL, that means no devices were found or there was an error.
|
||||
// In this case, print an error message and terminate the program.
|
||||
if (!list) {
|
||||
UE_LOG(LogHIDApi, Error, TEXT("No devices found"));
|
||||
// Get the error message from the HIDAPI
|
||||
HidError = hid_error(NULL);
|
||||
UE_LOG(LogHIDApi, Error, TEXT("%s"), *HidError);
|
||||
return false;
|
||||
}
|
||||
attributes = list;
|
||||
|
||||
|
||||
|
||||
|
||||
// Iterate through the linked list of devices
|
||||
int vendorCount = sizeof(VendorIds) / sizeof(VendorIds[0]);
|
||||
int productCount = sizeof(ProductIds) / sizeof(ProductIds[0]);
|
||||
|
||||
while (attributes) {
|
||||
// Check if the devices match any of vendor_id and product_id
|
||||
for (int i = 0; i < vendorCount; i++) {
|
||||
for (int j = 0; j < productCount; j++) {
|
||||
if (attributes->vendor_id == VendorIds[i] && attributes->product_id == ProductIds[j]) {
|
||||
UE_LOG(LogHIDApi, Display, TEXT("Portal found"));
|
||||
UE_LOG(LogHIDApi, Log, TEXT("Vendor ID: 0x%x, Product ID: 0x%x"), attributes->vendor_id, attributes->product_id);
|
||||
|
||||
PortalDevice = hid_open(attributes->vendor_id, attributes->product_id, NULL);
|
||||
if (PortalDevice) {
|
||||
UE_LOG(LogHIDApi, Display, TEXT("Successful connection to Portal."));
|
||||
|
||||
// Free the device list
|
||||
hid_free_enumeration(list);
|
||||
//bPortalConnected = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move to the next device in the list
|
||||
attributes = attributes->next;
|
||||
}
|
||||
|
||||
// Free the device list
|
||||
hid_free_enumeration(list);
|
||||
UE_LOG(LogHIDApi, Error, TEXT("No Portals found"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// write function need to be different on windows, as hid_write doesn't work with the way windows handle I/O
|
||||
#if PLATFORM_WINDOWS
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#define HID_CTL_CODE(id) \
|
||||
CTL_CODE(FILE_DEVICE_KEYBOARD, (id), METHOD_NEITHER, FILE_ANY_ACCESS)
|
||||
#define HID_IN_CTL_CODE(id) \
|
||||
CTL_CODE(FILE_DEVICE_KEYBOARD, (id), METHOD_IN_DIRECT, FILE_ANY_ACCESS)
|
||||
#define IOCTL_HID_SET_OUTPUT_REPORT HID_IN_CTL_CODE(101)
|
||||
|
||||
struct hid_device_ {
|
||||
HANDLE device_handle;
|
||||
BOOL blocking;
|
||||
USHORT output_report_length;
|
||||
unsigned char* write_buf;
|
||||
size_t input_report_length;
|
||||
USHORT feature_report_length;
|
||||
unsigned char* feature_buf;
|
||||
wchar_t* last_error_str;
|
||||
BOOL read_pending;
|
||||
char* read_buf;
|
||||
OVERLAPPED ol;
|
||||
OVERLAPPED write_ol;
|
||||
struct hid_device_info* device_info;
|
||||
};
|
||||
|
||||
|
||||
void USkyPortalIO::Write(FWriteBlock* pb)
|
||||
{
|
||||
if (!ensure(PortalDevice)) {
|
||||
UE_LOG(LogSkyportalIO, Error, TEXT("No Portal found"));
|
||||
return;
|
||||
}
|
||||
BOOL res;
|
||||
|
||||
OVERLAPPED ol;
|
||||
memset(&ol, 0, sizeof(ol));
|
||||
|
||||
DWORD bytes_returned;
|
||||
|
||||
res = DeviceIoControl(PortalDevice->device_handle,
|
||||
IOCTL_HID_SET_OUTPUT_REPORT,
|
||||
(unsigned char*)pb->data, write_buf_size,
|
||||
(unsigned char*)pb->data, write_buf_size,
|
||||
&bytes_returned, &ol);
|
||||
ensureMsgf(res, TEXT("Unable to write to Portal"));
|
||||
}
|
||||
|
||||
#else
|
||||
void USkyPortalIO::Write(FWriteBlock* pb) {
|
||||
|
||||
if (!ensure(PortalDevice)) {
|
||||
UE_LOG(LogSkyportalIO, Error, TEXT("No Portal found"));
|
||||
return;
|
||||
}
|
||||
|
||||
pb->buf[0] = 0; // Use report 0
|
||||
|
||||
|
||||
ensureMsgf(hid_write(PortalDevice, pb->data, read_buf_size) != -1, TEXT("Unable to write to Portal, %s"), hid_error(PortalDevice));
|
||||
UE_LOG(LogSkyportalIO, Error, TEXT("Unable to write to Portal. error:\n %s"), hid_error(PortalDevice));
|
||||
}
|
||||
#endif
|
||||
|
||||
void USkyPortalIO::WriteRaw(FWriteBlock* block)
|
||||
{
|
||||
int res = hid_write(PortalDevice, block->data, write_buf_size);
|
||||
if (res == -1)
|
||||
{
|
||||
UE_LOG(LogSkyportalIO, Error, TEXT("Unable to write raw data to Portal. error:\n %s"), hid_error(PortalDevice));
|
||||
return;
|
||||
}
|
||||
block->BytesTransferred = res;
|
||||
}
|
||||
|
||||
uint8* USkyPortalIO::Read()
|
||||
{
|
||||
uint8* output = 0;
|
||||
hid_read_timeout(PortalDevice, output, 0x20, TIMEOUT);
|
||||
return output;
|
||||
}
|
||||
|
||||
void USkyPortalIO::Close() {
|
||||
if (PortalDevice) {
|
||||
hid_close(PortalDevice);
|
||||
}
|
||||
}
|
2
Source/SkyPortal/Private/SkyPortalRunner.cpp
Normal file
2
Source/SkyPortal/Private/SkyPortalRunner.cpp
Normal file
|
@ -0,0 +1,2 @@
|
|||
#include "SkyPortalRunner.h"
|
||||
|
|
@ -3,36 +3,37 @@
|
|||
#include "SkyPortalSubsystem.h"
|
||||
#include "Engine/Engine.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogHIDApi);
|
||||
DEFINE_LOG_CATEGORY(LogSkyportalIO);
|
||||
#include "HAL/RunnableThread.h"
|
||||
#include "Misc/ScopeLock.h"
|
||||
#include "SkyPortalIO.h"
|
||||
|
||||
|
||||
void USkyPortalSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
|
||||
Super::Initialize(Collection);
|
||||
// Custom initialization logic
|
||||
// Start the status checker thread
|
||||
StatusChecker = new FPortalStatusChecker(this, 0.1f); // Check every 5 seconds
|
||||
StatusCheckerThread = FRunnableThread::Create(StatusChecker, TEXT("PortalStatusCheckerThread"), 0, TPri_AboveNormal);
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("SkyPortalSubsystem Initialized"));
|
||||
|
||||
// Initialize HIDAPI
|
||||
/*May be not needed
|
||||
int res = hid_init();
|
||||
if (res == 0)
|
||||
{
|
||||
UE_LOG(LogHIDApi, Log, TEXT("HIDAPI initialized successfully."));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogHIDApi, Error, TEXT("Failed to initialize HIDAPI."));
|
||||
HidError = hid_error(NULL);
|
||||
UE_LOG(LogHIDApi, Error, TEXT("%s"), *HidError);
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
void USkyPortalSubsystem::Deinitialize()
|
||||
{
|
||||
// Stop the thread when the subsystem is deinitialized
|
||||
if (StatusChecker)
|
||||
{
|
||||
StatusChecker->Stop();
|
||||
StatusCheckerThread->WaitForCompletion();
|
||||
|
||||
delete StatusCheckerThread;
|
||||
delete StatusChecker;
|
||||
StatusChecker = nullptr;
|
||||
StatusCheckerThread = nullptr;
|
||||
}
|
||||
|
||||
// Disconnect portal
|
||||
if (PortalDevice) {
|
||||
hid_close(PortalDevice);
|
||||
|
@ -47,9 +48,9 @@ void USkyPortalSubsystem::Deinitialize()
|
|||
// Antenna up / activate
|
||||
void USkyPortalSubsystem::ActivatePortal(int active)
|
||||
{
|
||||
RWBlock req, res;
|
||||
FWriteBlock req, res;
|
||||
|
||||
memset(req.buf, 0, rw_buf_size);
|
||||
memset(req.data, 0, write_buf_size);
|
||||
req.buf[1] = 'A';
|
||||
req.buf[2] = active;
|
||||
do { Write(&req); } while (CheckResponse(&res, 'A'));
|
||||
|
@ -57,95 +58,31 @@ void USkyPortalSubsystem::ActivatePortal(int active)
|
|||
}
|
||||
|
||||
// Start portal
|
||||
void USkyPortalSubsystem::RestartPortal()
|
||||
void USkyPortalSubsystem::Ready()
|
||||
{
|
||||
RWBlock req, res;
|
||||
FWriteBlock command;
|
||||
|
||||
memset(req.buf, 0, rw_buf_size);
|
||||
req.buf[1] = 'R';
|
||||
//memset(command.data, 0, write_buf_size);
|
||||
command.data[1] = 'R';
|
||||
uint8* output;
|
||||
do {
|
||||
PortalHandle->Write(&command);
|
||||
output = PortalHandle->Read();
|
||||
} while (output[0] != 'R');
|
||||
|
||||
do { Write(&req); } while (CheckResponse(&res, 'R'));
|
||||
uint16 _PortalId = ((output[1] << 8) + output[2]);
|
||||
PortalId = _PortalId; //Need a conversion as uint16 is not supported in BP
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Number of endpoints : 2
|
||||
found an IN End Point 0 with attributes interrupt and address 0x1
|
||||
found an OUT End Point 1 with attributes interrupt and address 0x1
|
||||
*/
|
||||
bool USkyPortalSubsystem::OpenPortalHandle() {
|
||||
|
||||
//reset
|
||||
if (PortalDevice) {
|
||||
hid_close(PortalDevice);
|
||||
}
|
||||
|
||||
/*
|
||||
Declare two pointers to hold information about HID devices.
|
||||
"list" will point to the head of the linked list of devices,
|
||||
"attributes" will be used to iterate through the list.
|
||||
*/
|
||||
struct hid_device_info* list, * attributes;
|
||||
|
||||
list = hid_enumerate(0x0, 0x0);
|
||||
// If `list` is NULL, that means no devices were found or there was an error.
|
||||
// In this case, print an error message and terminate the program.
|
||||
if (!list) {
|
||||
UE_LOG(LogHIDApi, Error, TEXT("No devices found"));
|
||||
// Get the error message from the HIDAPI
|
||||
HidError = hid_error(NULL);
|
||||
UE_LOG(LogHIDApi, Error, TEXT("%s"), *HidError);
|
||||
return false;
|
||||
}
|
||||
attributes = list;
|
||||
|
||||
|
||||
|
||||
|
||||
// Iterate through the linked list of devices
|
||||
int vendorCount = sizeof(VendorIds) / sizeof(VendorIds[0]);
|
||||
int productCount = sizeof(ProductIds) / sizeof(ProductIds[0]);
|
||||
|
||||
while (attributes) {
|
||||
// Check if the devices match any of vendor_id and product_id
|
||||
for (int i = 0; i < vendorCount; i++) {
|
||||
for (int j = 0; j < productCount; j++) {
|
||||
if (attributes->vendor_id == VendorIds[i] && attributes->product_id == ProductIds[j]) {
|
||||
UE_LOG(LogHIDApi, Display, TEXT("Portal found"));
|
||||
UE_LOG(LogHIDApi, Log, TEXT("Vendor ID: 0x%x, Product ID: 0x%x"), attributes->vendor_id, attributes->product_id);
|
||||
|
||||
PortalDevice = hid_open(attributes->vendor_id, attributes->product_id, NULL);
|
||||
if (PortalDevice) {
|
||||
UE_LOG(LogHIDApi, Display, TEXT("Successful connection to Portal."));
|
||||
|
||||
// Free the device list
|
||||
hid_free_enumeration(list);
|
||||
//bPortalConnected = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move to the next device in the list
|
||||
attributes = attributes->next;
|
||||
}
|
||||
|
||||
// Free the device list
|
||||
hid_free_enumeration(list);
|
||||
UE_LOG(LogHIDApi, Error, TEXT("No Portals found"));
|
||||
return false;
|
||||
}
|
||||
//{ Region Color & light functions
|
||||
void USkyPortalSubsystem::ChangePortalColor(const FLinearColor& Color)
|
||||
{
|
||||
|
||||
unsigned char r = FMath::Clamp(Color.R * 100, 0.0f, 100.0f);
|
||||
unsigned char g = FMath::Clamp(Color.G * 100, 0.0f, 100.0f);
|
||||
unsigned char b = FMath::Clamp(Color.B * 100, 0.0f, 100.0f);
|
||||
unsigned char r = FMath::Clamp(Color.R * 100, 0.0f, 255.0f);
|
||||
unsigned char g = FMath::Clamp(Color.G * 100, 0.0f, 255.0f);
|
||||
unsigned char b = FMath::Clamp(Color.B * 100, 0.0f, 255.0f);
|
||||
|
||||
RWBlock req;
|
||||
|
||||
|
@ -159,30 +96,36 @@ void USkyPortalSubsystem::ChangePortalColor(const FLinearColor& Color)
|
|||
// no response for this one.
|
||||
Write(&req);
|
||||
}
|
||||
void USkyPortalSubsystem::ChangePortalColorside(const FLinearColor& Color, const EPortalSide PortalSide, const float BlendTime )
|
||||
void USkyPortalSubsystem::ChangePortalColorside(const FLinearColor& Color, const EPortalSide PortalSide, const float BlendTime)
|
||||
{
|
||||
if (PortalSide == EPortalSide::TRAP) {
|
||||
UE_LOG(LogSkyportalIO, Error, TEXT("This function should only be called for the left or right side of the portal"));
|
||||
return;
|
||||
}
|
||||
unsigned char r = FMath::Clamp(Color.R * 100, 0.0f, 100.0f);
|
||||
unsigned char g = FMath::Clamp(Color.G * 100, 0.0f, 100.0f);
|
||||
unsigned char b = FMath::Clamp(Color.B * 100, 0.0f, 100.0f);
|
||||
|
||||
unsigned char r = FMath::Clamp(Color.R * 100, 0, 255);
|
||||
unsigned char g = FMath::Clamp(Color.G * 100, 0, 255);
|
||||
unsigned char b = FMath::Clamp(Color.B * 100, 0, 255);
|
||||
EPortalSide _portalside;
|
||||
RWBlock req, res;
|
||||
|
||||
memset(req.buf, 0, rw_buf_size);
|
||||
|
||||
req.buf[1] = 'J';
|
||||
switch (PortalSide) {
|
||||
if (PortalSide == EPortalSide::BOTH) {
|
||||
_portalside = EPortalSide::LEFT;
|
||||
}
|
||||
|
||||
switch (_portalside) {
|
||||
case EPortalSide::LEFT:
|
||||
req.buf[1] = 'J';
|
||||
req.buf[2] = 0x00;
|
||||
case EPortalSide::RIGHT:
|
||||
req.buf[1] = 'J';
|
||||
req.buf[2] = 0x02;
|
||||
case EPortalSide::BOTH:
|
||||
req.buf[2] = 0x01; //maybe that doesnt work, will give it a try
|
||||
case EPortalSide::TRAP:
|
||||
req.buf[1] = 'L';
|
||||
req.buf[2] = 0x01;
|
||||
req.buf[3] = FMath::Max3(r, g, b); // calculate brightness
|
||||
Write(&req); //since it's a color command for trap, only 3 bytes are needed, no response.
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
req.buf[3] = r; // R
|
||||
req.buf[4] = g; // G
|
||||
req.buf[5] = b; // B
|
||||
|
@ -196,82 +139,183 @@ void USkyPortalSubsystem::ChangePortalColorside(const FLinearColor& Color, const
|
|||
|
||||
do { Write(&req); } while (CheckResponse(&res, 'J'));
|
||||
|
||||
if (PortalSide == EPortalSide::BOTH) {
|
||||
ChangePortalColorside(Color, EPortalSide::RIGHT, BlendTime); // send a second command for the right side
|
||||
}
|
||||
}
|
||||
//}
|
||||
|
||||
|
||||
// write function need to be different on windows, as hid_write doesn't work with the way windows handle I/O
|
||||
#if PLATFORM_WINDOWS
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#define HID_CTL_CODE(id) \
|
||||
CTL_CODE(FILE_DEVICE_KEYBOARD, (id), METHOD_NEITHER, FILE_ANY_ACCESS)
|
||||
#define HID_IN_CTL_CODE(id) \
|
||||
CTL_CODE(FILE_DEVICE_KEYBOARD, (id), METHOD_IN_DIRECT, FILE_ANY_ACCESS)
|
||||
#define IOCTL_HID_SET_OUTPUT_REPORT HID_IN_CTL_CODE(101)
|
||||
|
||||
struct hid_device_ {
|
||||
HANDLE device_handle;
|
||||
BOOL blocking;
|
||||
USHORT output_report_length;
|
||||
unsigned char* write_buf;
|
||||
size_t input_report_length;
|
||||
USHORT feature_report_length;
|
||||
unsigned char* feature_buf;
|
||||
wchar_t* last_error_str;
|
||||
BOOL read_pending;
|
||||
char* read_buf;
|
||||
OVERLAPPED ol;
|
||||
OVERLAPPED write_ol;
|
||||
struct hid_device_info* device_info;
|
||||
};
|
||||
|
||||
|
||||
void USkyPortalSubsystem::Write(RWBlock* pb)
|
||||
|
||||
FPortalStatusData USkyPortalSubsystem::ParsePortalStatus(const RWBlock& ResponseBlock)
|
||||
{
|
||||
if (!ensure(PortalDevice)) {
|
||||
UE_LOG(LogSkyportalIO, Error, TEXT("No Portal found"));
|
||||
return;
|
||||
}
|
||||
BOOL res;
|
||||
FPortalStatusData PortalStatusData;
|
||||
|
||||
OVERLAPPED ol;
|
||||
memset(&ol, 0, sizeof(ol));
|
||||
// Parse the figure status array (little-endian 32-bit integer)
|
||||
uint32 FigureStatusArray = 0;
|
||||
// Reading the 32-bit integer (character status array) from the buffer
|
||||
FigureStatusArray |= ResponseBlock.buf[1]; // 1st byte
|
||||
FigureStatusArray |= (ResponseBlock.buf[2] << 8); // 2nd byte
|
||||
FigureStatusArray |= (ResponseBlock.buf[3] << 16); // 3rd byte
|
||||
FigureStatusArray |= (ResponseBlock.buf[4] << 24); // 4th byte
|
||||
|
||||
DWORD bytes_returned;
|
||||
TStaticArray<EFigureStatus, 16> tempArray;
|
||||
// For each of the 16 entries, extract the 2-bit status and map it to EFigureStatus
|
||||
for (int32 i = 0; i < 16; ++i)
|
||||
{
|
||||
uint8 StatusBits = (FigureStatusArray >> (i * 2)) & 0b11; // Extract 2 bits
|
||||
EFigureStatus FigureStatus;
|
||||
|
||||
res = DeviceIoControl(PortalDevice->device_handle,
|
||||
IOCTL_HID_SET_OUTPUT_REPORT,
|
||||
(unsigned char*)pb->buf, 0x21,
|
||||
(unsigned char*)pb->buf, 0x21,
|
||||
&bytes_returned, &ol);
|
||||
ensureMsgf(res, TEXT("Unable to write to Portal"));
|
||||
}
|
||||
|
||||
#else
|
||||
void USkyPortalSubsystem::Write(RWBlock *pb) {
|
||||
|
||||
if (!ensure(PortalDevice)) {
|
||||
UE_LOG(LogSkyportalIO, Error, TEXT("No Portal found"));
|
||||
return;
|
||||
switch (StatusBits)
|
||||
{
|
||||
case 0b00:
|
||||
FigureStatus = EFigureStatus::NOT_PRESENT;
|
||||
break;
|
||||
case 0b01:
|
||||
FigureStatus = EFigureStatus::PRESENT;
|
||||
break;
|
||||
case 0b11:
|
||||
FigureStatus = EFigureStatus::ADDED;
|
||||
break;
|
||||
case 0b10:
|
||||
FigureStatus = EFigureStatus::REMOVED;
|
||||
break;
|
||||
default:
|
||||
FigureStatus = EFigureStatus::NOT_PRESENT; // Default case
|
||||
break;
|
||||
}
|
||||
|
||||
pb->buf[0] = 0; // Use report 0
|
||||
// Add to the array of figure statuses
|
||||
//PortalStatusData.StatusArray.Insert(FigureStatus, i);
|
||||
tempArray[i] = FigureStatus;
|
||||
}
|
||||
PortalStatusData.StatusArray.SetNum(0);
|
||||
PortalStatusData.StatusArray.Append(tempArray);
|
||||
// The next byte is the response counter
|
||||
PortalStatusData.Counter = ResponseBlock.buf[5];
|
||||
|
||||
// The last byte is the boolean indicating whether the portal is ready
|
||||
PortalStatusData.bIsReady = ResponseBlock.buf[6] != 0; // 0 means not ready, non-zero means ready
|
||||
|
||||
ensureMsgf(hid_write(PortalDevice, pb->buf, 0x21) != -1, TEXT("Unable to write to Portal, %s"), hid_error(PortalDevice));
|
||||
UE_LOG(LogSkyportalIO, Error, TEXT("Unable to write to Portal. error:\n %s"), hid_error(PortalDevice));
|
||||
return PortalStatusData;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
void USkyPortalSubsystem::Sleep(int sleepMs) {
|
||||
FPlatformProcess::Sleep(sleepMs * 0.0001);
|
||||
}
|
||||
|
||||
// TODO: Refacto this function to handle better the response/output from the portal
|
||||
bool USkyPortalSubsystem::CheckResponse(RWBlock *res, char expect)
|
||||
void USkyPortalSubsystem::CheckComplexResponse() {
|
||||
|
||||
if (!PortalDevice) {
|
||||
return;
|
||||
}
|
||||
|
||||
RWBlock req, res;
|
||||
|
||||
memset(req.buf, 0, rw_buf_size);
|
||||
req.buf[1] = 'S';
|
||||
Write(&req);
|
||||
|
||||
int BuffResponse = hid_read_timeout(PortalDevice, res.buf, rw_buf_size, TIMEOUT);
|
||||
if (BuffResponse < 0) {
|
||||
UE_LOG(LogSkyportalIO, Error, TEXT("Error.\n %s"), hid_error(PortalDevice));
|
||||
return;
|
||||
}
|
||||
EPortalCommand CommandResponse = GetPortalCommandFromChar(res.buf[0]);
|
||||
switch (CommandResponse)
|
||||
{
|
||||
case A:
|
||||
break;
|
||||
case C:
|
||||
break;
|
||||
case J:
|
||||
break;
|
||||
case L:
|
||||
break;
|
||||
case M:
|
||||
break;
|
||||
case Q:
|
||||
break;
|
||||
case R:
|
||||
break;
|
||||
case S:
|
||||
CurrentStatusData = ParsePortalStatus(res);
|
||||
|
||||
//Send delegate when new informations are received
|
||||
if (OldStatusData != CurrentStatusData) {
|
||||
|
||||
for (int i = 0; i < CurrentStatusData.StatusArray.Num(); i++) {
|
||||
if (CurrentStatusData.StatusArray[i] != OldStatusData.StatusArray[i]) {
|
||||
if (
|
||||
//!FalsePositive() //filter conflicting infos
|
||||
true) {
|
||||
FigureDataBlock FigureData;
|
||||
switch (CurrentStatusData.StatusArray[i])
|
||||
{
|
||||
case EFigureStatus::NOT_PRESENT:
|
||||
OnSkylanderRemoved.Broadcast(00, i);
|
||||
break;
|
||||
case EFigureStatus::PRESENT:
|
||||
FigureData = ReadFigureBlocks(i);
|
||||
OnSkylanderAdded.Broadcast(GetFigureID(FigureData), i);
|
||||
break;
|
||||
case EFigureStatus::ADDED:
|
||||
FigureData = ReadFigureBlocks(i);
|
||||
OnSkylanderAdded.Broadcast(GetFigureID(FigureData), i);
|
||||
case EFigureStatus::REMOVED:
|
||||
OnSkylanderRemoved.Broadcast(00, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
OldStatusData = CurrentStatusData;
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TArray<uint8> USkyPortalSubsystem::QueryBlock(uint8 characterIndex, uint8 block)
|
||||
{
|
||||
TArray<uint8> QueryCommand;
|
||||
QueryCommand.SetNum(0x21);
|
||||
QueryCommand[1] = 'Q';
|
||||
QueryCommand[2] = characterIndex;
|
||||
QueryCommand[3] = block;
|
||||
|
||||
TArray<uint8> Output;
|
||||
|
||||
do {
|
||||
portalConnection->Write(QueryCommand);
|
||||
output = portalConnection->Read();
|
||||
} while (output[0] != 'Q' || (output[1] % 0x10 != characterIndex && output[1] != 0x01) || output[2] != block);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool USkyPortalSubsystem::FalsePositive() const
|
||||
{
|
||||
int dif = FMath::Abs(CurrentStatusData.Counter - OldStatusData.Counter);
|
||||
return (dif <= 2 || dif >= 254);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Verify the command response, when only a character is sended by the portal.
|
||||
*
|
||||
*TODO: Refacto this function to handle better the response/output from the portal
|
||||
*/
|
||||
bool USkyPortalSubsystem::CheckResponse(RWBlock* res, char expect)
|
||||
{
|
||||
if (!PortalDevice) {
|
||||
return false;
|
||||
|
@ -280,7 +324,7 @@ bool USkyPortalSubsystem::CheckResponse(RWBlock *res, char expect)
|
|||
int b = hid_read_timeout(PortalDevice, res->buf, rw_buf_size, TIMEOUT);
|
||||
|
||||
if (b < 0) {
|
||||
UE_LOG(LogSkyportalIO, Error, TEXT("Unable to read Skylander from Portal.\n"));
|
||||
UE_LOG(LogSkyportalIO, Error, TEXT("Error.\n %s"), hid_error(PortalDevice));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -317,42 +361,38 @@ bool USkyPortalSubsystem::CheckResponse(RWBlock *res, char expect)
|
|||
|
||||
|
||||
|
||||
bool USkyPortalSubsystem::WriteBlock(unsigned int block, unsigned char data[0x10], int skylander) {
|
||||
RWBlock req, res; //request and response buffer
|
||||
unsigned char verify[0x10]; // A 16-byte array used to verify the data that was written to the portal.
|
||||
|
||||
UE_LOG(LogSkyportalIO, Verbose, TEXT("Trying to write the current block :%X\n"), block);
|
||||
|
||||
// Trying to write 3 times
|
||||
for (int retries = 0; retries < 3; retries++) {
|
||||
// Write request
|
||||
// W 57 10 <block number> <0x10 bytes of data>
|
||||
memset(req.buf, 0, rw_buf_size);//Reset request buffer
|
||||
req.buf[1] = 'W';
|
||||
req.buf[2] = 0x10 + skylander;
|
||||
req.buf[3] = (unsigned char)block;
|
||||
memcpy(req.buf + 4, data, 0x10);
|
||||
|
||||
do { Write(&req); } while (CheckResponse(&res, 'W'));
|
||||
|
||||
Sleep(100); //Wait 0.1 seconds for write to take effect
|
||||
|
||||
memset(verify, 0xCD, sizeof(verify)); // 0xCD is a placeholder value
|
||||
ReadBlock(block, verify, skylander);
|
||||
|
||||
if (memcmp(data, verify, sizeof(verify))) {
|
||||
UE_LOG(LogSkyportalIO, Error, TEXT("verification of the written block failed"));
|
||||
continue; //retry
|
||||
}
|
||||
UE_LOG(LogSkyportalIO, Verbose, TEXT("block successfully written"));
|
||||
return true;
|
||||
EPortalCommand USkyPortalSubsystem::GetPortalCommandFromChar(unsigned char Char)
|
||||
{
|
||||
switch (Char)
|
||||
{
|
||||
case 'A':
|
||||
return EPortalCommand::A;
|
||||
case 'C':
|
||||
return EPortalCommand::C;
|
||||
case 'J':
|
||||
return EPortalCommand::J;
|
||||
case 'L':
|
||||
return EPortalCommand::L;
|
||||
case 'M':
|
||||
return EPortalCommand::M;
|
||||
case 'Q':
|
||||
return EPortalCommand::Q;
|
||||
case 'R':
|
||||
return EPortalCommand::R;
|
||||
case 'S':
|
||||
return EPortalCommand::S;
|
||||
default:
|
||||
// Handle the case when the character doesn't match any enum
|
||||
// Return a default or invalid value, or handle the error
|
||||
UE_LOG(LogSkyportalIO, Warning, TEXT("Invalid character for Portal Command: %c"), TCHAR(Char));
|
||||
return EPortalCommand::S; // 'S' for Status as a default
|
||||
}
|
||||
UE_LOG(LogSkyportalIO, Fatal, TEXT("failed to write block"));
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
bool USkyPortalSubsystem::ReadBlock(unsigned int block, unsigned char data[0x10], int skylander) {
|
||||
bool USkyPortalSubsystem::ReadBlock(unsigned int block, unsigned char data[0x10], int skylanderIndex) {
|
||||
RWBlock req, res; //request and response buffers
|
||||
unsigned char followup;
|
||||
|
||||
|
@ -374,7 +414,7 @@ bool USkyPortalSubsystem::ReadBlock(unsigned int block, unsigned char data[0x10]
|
|||
|
||||
memset(req.buf, 0, rw_buf_size); // Clear the request buffer (initialize all bytes to zero)
|
||||
req.buf[1] = 'Q';
|
||||
followup = 0x10 + skylander;
|
||||
followup = 0x10 + skylanderIndex;
|
||||
req.buf[2] = followup;
|
||||
if (block == 0) {
|
||||
req.buf[2] = followup + 0x10; // may not be needed
|
||||
|
@ -406,29 +446,148 @@ bool USkyPortalSubsystem::ReadBlock(unsigned int block, unsigned char data[0x10]
|
|||
|
||||
bool USkyPortalSubsystem::ConnectPortal()
|
||||
{
|
||||
|
||||
bPortalConnected = OpenPortalHandle();
|
||||
if (bPortalConnected) {
|
||||
RestartPortal();
|
||||
PortalHandle = NewObject<USkyPortalIO>();
|
||||
if (IsValidLowLevelFast(PortalHandle)) {
|
||||
UE_LOG(LogSkyportalIO, Log, TEXT("Portal connected: "));
|
||||
Ready();
|
||||
ActivatePortal(1);
|
||||
|
||||
Sleep(500);
|
||||
ChangePortalColor(FLinearColor(0xC8, 0xC8, 0xC8));
|
||||
|
||||
UE_LOG(LogSkyportalIO, Log, TEXT("Portal Status: %d\n"), PortalStatus());
|
||||
ChangePortalColor(FLinearColor(0.5, 0.5, 0.5));
|
||||
return true;
|
||||
}
|
||||
return bPortalConnected;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool USkyPortalSubsystem::bIsPortalReady()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void USkyPortalSubsystem::SendPortalCommand(EPortalCommand Command)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void USkyPortalSubsystem::SendPortalSound(USoundWave* Sound)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
unsigned char USkyPortalSubsystem::PortalStatus()
|
||||
FPortalStatusChecker::FPortalStatusChecker(USkyPortalSubsystem* InSubsystem, float InCheckInterval)
|
||||
: SkyPortalSubsystem(InSubsystem), CheckInterval(InCheckInterval), bShouldRun(true)
|
||||
{
|
||||
}
|
||||
|
||||
bool FPortalStatusChecker::Init()
|
||||
{
|
||||
// Initialization logic, if necessary (e.g., logging)
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32 FPortalStatusChecker::Run()
|
||||
{
|
||||
// Main loop of the thread, runs until Stop() is called
|
||||
while (bShouldRun)
|
||||
{
|
||||
// Check the portal status
|
||||
CheckPortalStatus();
|
||||
|
||||
// Sleep for the specified interval
|
||||
FPlatformProcess::Sleep(CheckInterval);
|
||||
}
|
||||
|
||||
return 0; // Exit code for the thread
|
||||
}
|
||||
|
||||
void FPortalStatusChecker::Stop()
|
||||
{
|
||||
// Signal the thread to stop running
|
||||
bShouldRun = false;
|
||||
}
|
||||
|
||||
void FPortalStatusChecker::Exit()
|
||||
{
|
||||
// Cleanup after the thread has stopped
|
||||
}
|
||||
|
||||
void FPortalStatusChecker::CheckPortalStatus()
|
||||
{
|
||||
// Ensure the subsystem is valid
|
||||
if (SkyPortalSubsystem && SkyPortalSubsystem->bPortalConnected)
|
||||
{
|
||||
UE_LOG(LogSkyportalIO, Verbose, TEXT("Check portal"));
|
||||
|
||||
// Call the subsystem function to get portal status
|
||||
|
||||
SkyPortalSubsystem->CheckComplexResponse();
|
||||
|
||||
// Do something with the status (log, notify, etc.)
|
||||
UE_LOG(LogSkyportalIO, Verbose, TEXT("Portal Status:"));
|
||||
}
|
||||
}
|
||||
|
||||
FigureDataBlock USkyPortalSubsystem::ReadFigureBlocks(uint8 FigureIndex)
|
||||
{
|
||||
FigureDataBlock FigureData;
|
||||
FigureData.error = false; // Initialize error flag
|
||||
|
||||
RWBlock req, res;
|
||||
|
||||
memset(req.buf, 0, rw_buf_size);
|
||||
req.buf[1] = 'S';
|
||||
do { Write(&req); } while (CheckResponse(&res, 'S'));
|
||||
// Loop over all 64 blocks
|
||||
for (uint8 BlockIndex = 0; BlockIndex < FIGURE_TOTAL_BLOCKS; ++BlockIndex)
|
||||
{
|
||||
// Prepare the request buffer
|
||||
memset(req.buf, 0, rw_buf_size);
|
||||
req.buf[1] = 'Q'; // Command character
|
||||
req.buf[2] = FigureIndex; // Figure index (0x00 to 0x0F)
|
||||
req.buf[3] = BlockIndex; // Block index (0x00 to 0x3F)
|
||||
|
||||
return res.buf[1];
|
||||
// Send the request
|
||||
Write(&req);
|
||||
|
||||
// Read the response
|
||||
int BuffResponse = hid_read_timeout(PortalDevice, res.buf, rw_buf_size, TIMEOUT);
|
||||
|
||||
// Check if the response was received successfully
|
||||
if (BuffResponse < rw_buf_size)
|
||||
{
|
||||
FigureData.error = true; // Mark error flag
|
||||
UE_LOG(LogSkyportalIO, Warning, TEXT("Error receiving data for block %d of figure %d"), BlockIndex, FigureIndex);
|
||||
break;
|
||||
}
|
||||
|
||||
// Parse the response status
|
||||
uint8 StatusByte = res.buf[2];
|
||||
uint8 ReturnedBlockIndex = res.buf[3];
|
||||
|
||||
// Check for errors in the status byte
|
||||
if (StatusByte == 0x01) // Error in response
|
||||
{
|
||||
FigureData.error = true;
|
||||
UE_LOG(LogSkyportalIO, Warning, TEXT("Error reading block %d for figure %d"), BlockIndex, FigureIndex);
|
||||
break;
|
||||
}
|
||||
else if (StatusByte != (0x10 + 'Q')) // Unexpected status byte
|
||||
{
|
||||
FigureData.error = true;
|
||||
UE_LOG(LogSkyportalIO, Warning, TEXT("Unexpected status byte 0x%02X for block %d"), StatusByte, BlockIndex);
|
||||
break;
|
||||
}
|
||||
|
||||
// Verify that the block index matches the requested block
|
||||
if (ReturnedBlockIndex != BlockIndex)
|
||||
{
|
||||
FigureData.error = true;
|
||||
UE_LOG(LogSkyportalIO, Warning, TEXT("Mismatched block index. Expected %d, got %d"), BlockIndex, ReturnedBlockIndex);
|
||||
break;
|
||||
}
|
||||
|
||||
// Copy block data into FigureData
|
||||
memcpy(FigureData.blockdata[BlockIndex], &res.buf[4], FIGURE_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
return FigureData; // Return the complete figure data
|
||||
}
|
68
Source/SkyPortal/Public/SkyPortalFigure.h
Normal file
68
Source/SkyPortal/Public/SkyPortalFigure.h
Normal file
|
@ -0,0 +1,68 @@
|
|||
#pragma once
|
||||
#include "CoreMinimal.h"
|
||||
//#include "SkyPortalFigure.generated.h"
|
||||
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EFigureStatus : uint8
|
||||
{
|
||||
NOT_PRESENT = 0b00 UMETA(DisplayName = "Not Present"),
|
||||
PRESENT = 0b01 UMETA(DisplayName = "Present"),
|
||||
ADDED = 0b11 UMETA(DisplayName = "Added"),
|
||||
REMOVED = 0b10 UMETA(DisplayName = "Removed")
|
||||
};
|
||||
|
||||
#define FIGURE_TOTAL_BLOCKS 64
|
||||
#define FIGURE_BLOCK_SIZE 16
|
||||
|
||||
typedef struct {
|
||||
unsigned char blockdata[FIGURE_TOTAL_BLOCKS][FIGURE_BLOCK_SIZE]; bool error;
|
||||
} FigureDataBlock;
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, meta = (Category = "SkyPortal|Figure"))
|
||||
uint32 GetFigureID(const FigureDataBlock& DataBlock);
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, meta = (Category = "SkyPortal|Figure"))
|
||||
uint32 GetFigureIdByIndex(uint32 index);
|
||||
|
||||
class FigureData {
|
||||
public:
|
||||
// Data Arrays
|
||||
uint8 data[64][16];
|
||||
uint8 decryptedData[64][16];
|
||||
|
||||
// Properties
|
||||
#pragma region manufacturer
|
||||
uint32 NUID; // should under no circumstances be corrupted. 4-byte
|
||||
uint8 BCC; //Block Check Character. When this BCC does not match the NUID of the tag, the tag ceases to function. stored in block 0 at offset 0x4
|
||||
uint8 SAK = 0x81; //needs to be set to allow for the tag to be read correctly
|
||||
uint16 ATQA; //always set to 0x01 0x0F
|
||||
FString ProductionYear; //last 2 digits of the year that the tag was manufactured as Binary-coded decimal
|
||||
#pragma endregion
|
||||
#pragma region Toy code
|
||||
uint32 ToyCodeNumber1, ToyCodeNumber2;
|
||||
uint64 FullToyCodeNumber;
|
||||
FString ToyCode;
|
||||
#pragma endregion
|
||||
int16 ID;
|
||||
int16 VariantID;
|
||||
#pragma region counters
|
||||
uint8 counter1;
|
||||
uint8 couter2;
|
||||
#pragma endregion
|
||||
FString Nickname;
|
||||
|
||||
FigureData()
|
||||
{
|
||||
ClearData();
|
||||
}
|
||||
|
||||
// Methods
|
||||
void ReadData(uint8 index);
|
||||
void ClearData();
|
||||
uint8 GetByte(int block, int offset);
|
||||
void SetByte(int block, int offset, uint8 value);
|
||||
uint16 GetShort(int block, int offset);
|
||||
uint32 GetUInt(int block, int offset);
|
||||
void Dump(bool decrypted, FString filePath);
|
||||
};
|
71
Source/SkyPortal/Public/SkyPortalIO.h
Normal file
71
Source/SkyPortal/Public/SkyPortalIO.h
Normal file
|
@ -0,0 +1,71 @@
|
|||
#pragma once
|
||||
/*
|
||||
* This is the bridge between hidapi and unreal and contains all the rawdata of the portal
|
||||
*/
|
||||
#include "CoreMinimal.h"
|
||||
#include "hidapi.h"
|
||||
|
||||
#include "SkyPortalIO.generated.h"
|
||||
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogHIDApi, Log, All);
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogSkyportalIO, Log, All);
|
||||
|
||||
constexpr auto write_buf_size = 0x21;
|
||||
constexpr auto TIMEOUT = 30000; //milliseconds
|
||||
|
||||
/* Portal physicial device IDs */
|
||||
const int VendorIds[4] = { 0x12ba, 0x54c, 0x1430, 0x1430 };
|
||||
const int ProductIds[4] = { 0x150, 0x967, 0x1f17 };
|
||||
|
||||
/* WriteBlock
|
||||
*
|
||||
* Contain all the data that pass inside hidapi write + the number of bytes in case of successful write
|
||||
* @param data all the block data, should be 0x21 sized
|
||||
* @param BytesTransferred In case of successful write, print the number of byte transfered. Could be used for verifications
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FWriteBlock {
|
||||
GENERATED_BODY()
|
||||
unsigned char data[write_buf_size];
|
||||
int BytesTransferred;
|
||||
} ;
|
||||
|
||||
/* This class will contain and handle all the low-lewel functions
|
||||
* Should be able to be called anywhere
|
||||
*/
|
||||
UCLASS()
|
||||
class USkyPortalIO : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/* hidapi will write error message in here*/
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
FString HidError;
|
||||
|
||||
/*Portal ready to be listened or to receive commands*/
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
bool bPortalReady;
|
||||
|
||||
/* Connect to Portal - will write the PortalDevice var if success */
|
||||
bool OpenPortalHandle();
|
||||
|
||||
void Write(FWriteBlock* Block);
|
||||
void WriteRaw(FWriteBlock* Block);
|
||||
|
||||
/* Listen to portal
|
||||
* @return The data sended by the portal device
|
||||
*/
|
||||
UFUNCTION()
|
||||
uint8* Read();
|
||||
|
||||
void Close();
|
||||
private:
|
||||
|
||||
|
||||
|
||||
/* Portal ref used in the subsystem */
|
||||
hid_device* PortalDevice;
|
||||
|
||||
};
|
||||
|
81
Source/SkyPortal/Public/SkyPortalRunner.h
Normal file
81
Source/SkyPortal/Public/SkyPortalRunner.h
Normal file
|
@ -0,0 +1,81 @@
|
|||
#pragma once
|
||||
|
||||
#include "HAL/Runnable.h"
|
||||
#include "SkyPortalFigure.h"
|
||||
|
||||
class SkyPortalRunner
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FPortalStatusData
|
||||
{
|
||||
GENERATED_USTRUCT_BODY()
|
||||
|
||||
// Array of statuses
|
||||
UPROPERTY(BlueprintReadOnly, EditFixedSize, Category = "SkyPortal|Figure", meta = (EditFixedOrder))
|
||||
TArray<EFigureStatus> StatusArray;
|
||||
|
||||
// timestamp.
|
||||
// only one byte long. This means that after the value 0xFF, it overflows back to 0x00.
|
||||
// Since these are so far apart, it can be assumed that 0x00 is newer than anything in the range 0xF0 - 0xFF.
|
||||
UPROPERTY(BlueprintReadOnly, Category = "SkyPortal|Figure")
|
||||
uint8 Counter;
|
||||
|
||||
// Should always be true
|
||||
UPROPERTY(BlueprintReadOnly, Category = "SkyPortal|Figure")
|
||||
bool bIsReady;
|
||||
|
||||
explicit FPortalStatusData(uint8 ArraySize = 16, EFigureStatus DefaultStatus = EFigureStatus::NOT_PRESENT) :
|
||||
Counter(0), // Utilisation correcte de l'initialisation directe
|
||||
bIsReady(true) // Utilisation correcte de l'initialisation directe
|
||||
{
|
||||
// Initialisation du tableau StatusArray avec 16 éléments par défaut
|
||||
StatusArray.Init(DefaultStatus, ArraySize);
|
||||
}
|
||||
|
||||
// Overload the == operator
|
||||
bool operator==(const FPortalStatusData& Other) const
|
||||
{
|
||||
if (bIsReady == Other.bIsReady && Counter == Other.Counter) {
|
||||
if (StatusArray == Other.StatusArray) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Overload the != operator
|
||||
bool operator!=(const FPortalStatusData& Other) const
|
||||
{
|
||||
return !(*this == Other);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class FPortalStatusChecker : public FRunnable {
|
||||
public:
|
||||
// Constructor: pass the subsystem and desired check interval (in seconds)
|
||||
FPortalStatusChecker(USkyPortalSubsystem* InSubsystem, float InCheckInterval);
|
||||
|
||||
// FRunnable interface
|
||||
virtual bool Init() override;
|
||||
virtual uint32 Run() override;
|
||||
virtual void Stop() override;
|
||||
virtual void Exit() override;
|
||||
|
||||
private:
|
||||
// Pointer to the subsystem that contains PortalStatus()
|
||||
USkyPortalSubsystem* SkyPortalSubsystem;
|
||||
|
||||
// Time interval (in seconds) for status checking
|
||||
float CheckInterval;
|
||||
|
||||
// Thread control variables
|
||||
FThreadSafeBool bShouldRun;
|
||||
|
||||
// Helper function to check portal status
|
||||
void CheckPortalStatus();
|
||||
};
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Subsystems/EngineSubsystem.h"
|
||||
#include "hidapi.h"
|
||||
#include "SkyPortalRunner.h"
|
||||
|
||||
|
||||
#include "SkyPortalSubsystem.generated.h"
|
||||
|
||||
#pragma region Definitions
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum EPortalSide {
|
||||
|
@ -16,99 +16,150 @@ enum EPortalSide {
|
|||
TRAP UMETA(DisplayName = "Trap")
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum EPortalCommand {
|
||||
A UMETA(DisplayName = "Activate"),
|
||||
C UMETA(DisplayName = "Color"),
|
||||
J UMETA(DisplayName = "Advanced color"),
|
||||
L UMETA(DisplayName = "Trap color"),
|
||||
M UMETA(DisplayName = "Music"),
|
||||
Q UMETA(DisplayName = "Query"),
|
||||
R UMETA(DisplayName = "Ready"),
|
||||
S UMETA(DisplayName = "Status")
|
||||
};
|
||||
|
||||
|
||||
/* Macro Definitions */
|
||||
#define rw_buf_size 0x21
|
||||
#define TIMEOUT 30000
|
||||
#define DEBUG true
|
||||
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogHIDApi, Log, All);
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogSkyportalIO, Log, All);
|
||||
|
||||
/* Subsystem */
|
||||
|
||||
|
||||
|
||||
|
||||
//// Delegates
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSkylanderAddedDelegate, int32, SkylanderID, int32, Index);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSkylanderRemovedDelegate, int32, SkylanderID, int32, Index);
|
||||
|
||||
#pragma endregion
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Handle all the blueprints functions and game logic.
|
||||
*
|
||||
*/
|
||||
UCLASS(MinimalAPI)
|
||||
class USkyPortalSubsystem : public UEngineSubsystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
//Portal ref used in the subsystem
|
||||
hid_device* PortalDevice;
|
||||
|
||||
public:
|
||||
// Override initialization and deinitialization methods
|
||||
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||
virtual void Deinitialize() override;
|
||||
|
||||
|
||||
/********* Portal Actions *************/
|
||||
|
||||
// Connect to Portal, return false if portal is not found
|
||||
/* The first function to run, before anything else.
|
||||
* It will re-init and prepare the portal for receiving/sending inputs
|
||||
*
|
||||
* return false if portal is not found
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, CallInEditor, meta = (Category = "SkyPortal"))
|
||||
SKYPORTAL_API bool ConnectPortal();
|
||||
|
||||
|
||||
|
||||
UFUNCTION(BlueprintCallable, CallInEditor, meta = (Category = "SkyPortal|Commands"))
|
||||
SKYPORTAL_API void Ready();
|
||||
|
||||
/*Send a **Status** command, to see if the portal is ready to receive new commands*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, meta = (Category = "SkyPortal|NOT FUNCTIONING"))
|
||||
SKYPORTAL_API bool bIsPortalReady();
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (Category = "SkyPortal|NOT FUNCTIONING"))
|
||||
SKYPORTAL_API void SendPortalCommand(EPortalCommand Command);
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (Category = "SkyPortal|NOT FUNCTIONING"))
|
||||
SKYPORTAL_API void SendPortalSound(USoundWave* Sound);
|
||||
|
||||
|
||||
|
||||
/* Change portal color, ideally should be called just at the start.For gameplay usage, use ChangePortalColorside()*/
|
||||
UFUNCTION(BlueprintCallable, CallInEditor, meta = (AutoCreateRefTerm = "Color", Category = "SkyPortal|Cosmetic"))
|
||||
SKYPORTAL_API void ChangePortalColor(const FLinearColor& Color = FLinearColor::Green);
|
||||
|
||||
/**
|
||||
* Change the color of the portal, can separate side and even trap ligth
|
||||
* @param NextColor New color
|
||||
* @param PortalSide The actors to record
|
||||
* @param BlendTime Blend between current color and NextColor, in milliseconds
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, CallInEditor, meta = (AutoCreateRefTerm = "NextColor", Category = "SkyPortal|Cosmetic", HideAlphaChannel))
|
||||
SKYPORTAL_API void ChangePortalColorside(const FLinearColor& NextColor = FLinearColor::Green, const EPortalSide PortalSide = EPortalSide::BOTH, const float BlendTime = 500.0f);
|
||||
|
||||
|
||||
// Blueprint-assignable event property
|
||||
UPROPERTY(BlueprintAssignable, Category = "SkyPortal|Skylander")
|
||||
FOnSkylanderAddedDelegate OnSkylanderAdded;
|
||||
|
||||
// Blueprint-assignable event property
|
||||
UPROPERTY(BlueprintAssignable, Category = "SkyPortal|Skylander")
|
||||
FOnSkylanderRemovedDelegate OnSkylanderRemoved;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
FPortalStatusData CurrentStatusData;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
FPortalStatusData OldStatusData;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
uint32 PortalId;
|
||||
|
||||
EPortalCommand GetPortalCommandFromChar(unsigned char Char);
|
||||
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
|
||||
bool bPortalConnected = false;
|
||||
|
||||
FString HidError;
|
||||
|
||||
|
||||
/* Portal Actions*/
|
||||
|
||||
// Change portal color, ideally should be called just at the start. For gameplay usage, use ChangePortalColorside()
|
||||
UFUNCTION(BlueprintCallable, CallInEditor, meta = (AutoCreateRefTerm = "Color", Category = "SkyPortal|Cosmetic"))
|
||||
SKYPORTAL_API void ChangePortalColor(const FLinearColor& Color = FLinearColor::Green);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Change the color of the portal, can separate side and even trap ligth
|
||||
* @param PortalSide The actors to record
|
||||
* @param BlendTime Blend between current color and NextColor
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, CallInEditor, meta = (AutoCreateRefTerm = "NextColor", Category = "SkyPortal|Cosmetic"))
|
||||
SKYPORTAL_API void ChangePortalColorside(const FLinearColor& NextColor = FLinearColor::Green,const EPortalSide PortalSide = EPortalSide::BOTH,const float BlendTime=100.0f);
|
||||
|
||||
|
||||
unsigned char PortalStatus();
|
||||
|
||||
|
||||
|
||||
UFUNCTION()
|
||||
TArray<uint8> QueryBlock(uint8 characterIndex, uint8 block = 0x00);
|
||||
|
||||
USkyPortalIO* PortalHandle;
|
||||
|
||||
private:
|
||||
FPortalStatusData ParsePortalStatus(const WriteBlock& ResponseBlock);
|
||||
|
||||
|
||||
|
||||
typedef struct {
|
||||
unsigned char buf[rw_buf_size]; int BytesTransferred;
|
||||
} RWBlock;
|
||||
|
||||
|
||||
static void Sleep(int sleepMs);
|
||||
|
||||
|
||||
|
||||
void ActivatePortal(int active);
|
||||
|
||||
void RestartPortal();
|
||||
|
||||
|
||||
|
||||
// Block/byte related data write/read functions
|
||||
bool OpenPortalHandle();
|
||||
bool ReadBlock(unsigned int block, unsigned char data[0x10], int skylander);
|
||||
bool WriteBlock(unsigned int, unsigned char[0x10], int);
|
||||
bool CheckResponse(RWBlock *, char);
|
||||
void Write(RWBlock *);
|
||||
|
||||
|
||||
FigureDataBlock ReadFigureBlocks(uint8 FigureIndex);
|
||||
bool FalsePositive() const;
|
||||
|
||||
// Pointer to the status checker thread
|
||||
FPortalStatusChecker* StatusChecker;
|
||||
FRunnableThread* StatusCheckerThread;
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
//Constants
|
||||
const int VendorIds[4] = { 0x12ba, 0x54c, 0x1430, 0x1430 };
|
||||
const int ProductIds[4] = { 0x150, 0x967, 0x1f17 };
|
||||
|
||||
|
||||
|
||||
/////Defaults values, should not be used
|
||||
//const int VendorId = 5168;
|
||||
//const int ProductId = 336;
|
||||
};
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue