Compare commits

...

32 commits

Author SHA1 Message Date
27095ae6c7 Fix some crash on figure loading 2024-11-24 20:57:23 +01:00
8521e33d05 fix missing include 2024-11-24 20:44:59 +01:00
6998fb1e1e trying prevent crash at exit 2024-10-02 08:36:43 +02:00
ff10e884dc some QoL 2024-09-29 17:43:07 +02:00
0797c9488a Merge branch 'cryptography' 2024-09-29 17:41:14 +02:00
4a99882118 fix some memory issues 2024-09-29 15:00:36 +02:00
a377bf6f00 IT WORKS 2024-09-29 12:20:37 +02:00
67a5aa000e log & async 2024-09-29 00:52:33 +02:00
6c7cde03e9 update 2024-09-28 23:34:12 +02:00
3c7f3d02fd Logging the fuck out everything 2024-09-28 23:33:42 +02:00
f1988072a4 OpenSSL implementation 2024-09-27 01:39:33 +02:00
Lucas Peter
df5fd8b05b
Tried with Unreal base approach 2024-09-26 17:52:10 +02:00
Lucas Peter
65652d3a2a
Merge branch 'rework' 2024-09-26 17:51:38 +02:00
Lucas Peter
952b184f10
wip:regression on skylander status array fixed 2024-09-26 16:53:36 +02:00
Lucas Peter
b959059755
Cleaned up includes 2024-09-26 15:11:24 +02:00
Lucas Peter
117da9d24a
rework done
TODO: clean
2024-09-26 13:10:47 +02:00
6ffaeb801a Run, but status array regression 2024-09-26 00:19:16 +02:00
Lucas Peter
703db561bb
Add icons 2024-09-25 18:00:36 +02:00
Lucas Peter
f9d072ea5a
IT'S COMPILING NOW 2024-09-25 18:00:24 +02:00
Lucas Peter
b9b3c8ef81
wip 2024-09-25 13:47:11 +02:00
8ba30675be a lot of rework, doesnt compile yet 2024-09-25 00:19:21 +02:00
Lucas Peter
be72994dc0
wip
Based on https://github.com/mandar1jn/SkylandersToolkit/blob/master
2024-09-24 18:31:53 +02:00
aed30481ea wip:reading figure 2024-09-24 08:05:44 +02:00
Lucas Peter
59ea084ff4
Parse status and grab data 2024-09-23 18:19:18 +02:00
Lucas Peter
5f126e2b55
Add Status subsystem 2024-09-23 15:48:29 +02:00
804e5fe911
wip:cleanup 2024-09-23 13:51:27 +02:00
612cfb7877 Merge branch 'hdiapi-implementation' 2024-09-22 00:01:21 +02:00
209907ca59 some qOL 2024-09-22 00:01:15 +02:00
57bbaaa033 fix writing!! 2024-09-21 17:40:23 +02:00
a148964f31 i'm not sure... 2024-09-21 17:15:11 +02:00
Lucas Peter
ed7b37b4fe wip: implement portalIO
TODO : Fix the write IO timing issue
2024-09-21 17:15:11 +02:00
Lucas Peter
2360897978
wip: implement portalIO
TODO : Fix the write IO timing issue
2024-09-20 17:42:42 +02:00
19 changed files with 1377 additions and 108 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View file

@ -0,0 +1,18 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 404 404" width="404" height="404">
<title>sky-portal-icon</title>
<style>
.s0 { fill: #000000 }
</style>
<g>
<path id="Circle" class="s0" d="m168.6 0l23 46h20.8l23-46zm-9.5 21.1q-5 1.2-9.9 2.7-4.9 1.4-9.7 3.2-4.8 1.7-9.6 3.7-4.7 2.1-9.2 4.3l7.2 30.2q5.2-2.9 10.7-5.4 5.5-2.5 11.1-4.6 5.7-2 11.5-3.6 5.8-1.7 11.7-2.8zm86.1 0l-13.9 27.7q5.9 1.2 11.7 2.8 5.8 1.6 11.5 3.6 5.6 2.1 11.1 4.6 5.5 2.5 10.7 5.4l7.3-30.2q-4.6-2.2-9.3-4.2-4.7-2.1-9.6-3.8-4.8-1.8-9.7-3.2-4.9-1.5-9.8-2.7zm59.2 4l-11.9 50 16 13.4 47.2-20.5c0 0-51.3-42.9-51.3-42.9zm-204.6 0l-51.2 43 47.2 20.4 16-13.4c0 0-12-50-12-50zm249.7 65.3l-28.5 12.3q3.7 4.7 7.2 9.6 3.4 5 6.4 10.2 3 5.2 5.6 10.7 2.6 5.4 4.8 11l24.9-18.5q-2.1-4.7-4.4-9.2-2.3-4.6-4.8-9-2.6-4.5-5.4-8.7-2.8-4.3-5.8-8.4zm-294.7 0q-3 4.1-5.8 8.4-2.9 4.3-5.4 8.7-2.6 4.4-4.9 9-2.3 4.6-4.3 9.3l24.9 18.4q2.1-5.6 4.7-11 2.6-5.5 5.6-10.7 3.1-5.2 6.5-10.1 3.4-5 7.2-9.7zm337.5 41.2l-41.3 30.6 3.7 20.6 49.2 14.6c0 0-11.6-65.8-11.6-65.8zm-380.3 0l-11.6 65.9 49.3-14.7 3.6-20.5c0 0-41.3-30.7-41.3-30.7zm343.1 70.1c-0.3 16.5-3.2 32.4-8.3 47.2l30.9 1.9q1.5-5 2.6-9.9 1.2-5 2.1-10.1 0.9-5 1.5-10.1 0.6-5.1 0.9-10.2zm-306 0.1l-29.6 8.8q0.3 5.1 0.9 10.2 0.6 5.1 1.5 10.1 0.8 5 2 10 1.2 5 2.6 9.9l31-1.8c-5.2-14.9-8.1-30.7-8.4-47.2zm290.8 64.7l-10.5 18.1 28.4 42.9 33.4-57.9zm-275.4 0.1l-51.4 3.1 33.5 57.9 28.3-43zm253.2 32.8q-3 3.5-6.2 6.7-3.2 3.3-6.6 6.4-3.4 3-7 5.9-3.5 2.9-7.2 5.5l14.7 26.5q4-2.8 7.8-5.7 3.8-2.9 7.5-6.1 3.7-3.1 7.2-6.4 3.5-3.4 6.9-6.9zm-230.9 0.1l-17.2 25.9q3.4 3.5 6.9 6.8 3.5 3.4 7.2 6.5 3.6 3.1 7.5 6 3.8 3 7.8 5.7l14.7-26.5q-3.7-2.6-7.3-5.5-3.5-2.8-6.9-5.9-3.4-3-6.6-6.3-3.1-3.2-6.1-6.7zm13.3 79.7l62.9 22.9-5.9-51.1-19.6-7.1c0 0-37.4 35.3-37.4 35.3zm79.1 6.4q5.1 0.6 10.2 0.8 5.1 0.3 10.2 0.3 5.1 0 10.2-0.2 5.1-0.3 10.2-0.9l3.6-30.8q-6 0.9-12 1.4-6 0.5-12 0.4-6 0-12-0.4-6-0.5-12-1.5zm57.1 16.5l62.8-22.9-37.3-35.3-19.6 7.1c0 0-5.9 51.1-5.9 51.1z"/>
<g id="Vortex">
<path id="Vortex7" class="s0" d="m182.3 194.1c-2.1 3.5-19.8 50.8 46.8 73.2 50.6 11.2 85.3-32.6 85.3-32.6 0 0-14.6 66.8-95.6 54.5-73.9-23.5-54.9-83.4-36.5-95.1z"/>
<path id="Vortex6" class="s0" d="m197.4 181.2c-4.1 0.4-52.2 15.8-28.5 82 22.5 46.7 78.3 46.9 78.3 46.9 0 0-61.5 29.8-101.9-41.5-27.3-72.5 31.5-94.6 52.1-87.4z"/>
<path id="Vortex5" class="s0" d="m217.4 185.2c-2.8-3-44.2-32-82.6 26.9-23.5 46.1 10.1 90.7 10.1 90.7 0 0-61-30.9-28.7-106.2 41.3-65.6 94.5-32.2 101.2-11.4z"/>
<path id="Vortex4" class="s0" d="m182.4 212.2c1.1 3.9 23.5 49.2 85.4 15.9 42.8-29.2 34.6-84.5 34.6-84.5 0 0 38.7 56.4-25.7 107-67.7 37.9-98.3-17-94.3-38.4z"/>
<path id="Vortex3" class="s0" d="m197 225.2c3.7 1.6 52.8 13.2 66.6-55.7 4.7-51.6-43.1-80.5-43.1-80.5 0 0 68.1 6.1 66.1 88-13.9 76.2-75.7 64.9-89.6 48.2z"/>
<path id="Vortex2" class="s0" d="m218 220.7c3.5-2.1 40.5-36.4-9-86.3-40.1-32.8-90.8-9.3-90.8-9.3 0 0 43.1-53.1 109.9-5.6 55.5 54.1 11.6 99.1-10.1 101.2z"/>
<path id="Vortex1" class="s0" d="m226.2 203.4c0.6-4.1-1.6-54.5-71.7-49.1-50.9 9.4-65.8 63.3-65.8 63.3 0 0-12.6-67.2 66.8-87.5 77.2-7.2 83.1 55.3 70.7 73.3z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View file

@ -0,0 +1,18 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 404 404" width="404" height="404">
<title>sky-portal-icon</title>
<style>
.s0 { fill: #ffffff }
</style>
<g>
<path id="Circle" class="s0" d="m168.6 0l23 46h20.8l23-46zm-9.5 21.1q-5 1.2-9.9 2.7-4.9 1.4-9.7 3.2-4.8 1.7-9.6 3.7-4.7 2.1-9.2 4.3l7.2 30.2q5.2-2.9 10.7-5.4 5.5-2.5 11.1-4.6 5.7-2 11.5-3.6 5.8-1.7 11.7-2.8zm86.1 0l-13.9 27.7q5.9 1.2 11.7 2.8 5.8 1.6 11.5 3.6 5.6 2.1 11.1 4.6 5.5 2.5 10.7 5.4l7.3-30.2q-4.6-2.2-9.3-4.2-4.7-2.1-9.6-3.8-4.8-1.8-9.7-3.2-4.9-1.5-9.8-2.7zm59.2 4l-11.9 50 16 13.4 47.2-20.5c0 0-51.3-42.9-51.3-42.9zm-204.6 0l-51.2 43 47.2 20.4 16-13.4c0 0-12-50-12-50zm249.7 65.3l-28.5 12.3q3.7 4.7 7.2 9.6 3.4 5 6.4 10.2 3 5.2 5.6 10.7 2.6 5.4 4.8 11l24.9-18.5q-2.1-4.7-4.4-9.2-2.3-4.6-4.8-9-2.6-4.5-5.4-8.7-2.8-4.3-5.8-8.4zm-294.7 0q-3 4.1-5.8 8.4-2.9 4.3-5.4 8.7-2.6 4.4-4.9 9-2.3 4.6-4.3 9.3l24.9 18.4q2.1-5.6 4.7-11 2.6-5.5 5.6-10.7 3.1-5.2 6.5-10.1 3.4-5 7.2-9.7zm337.5 41.2l-41.3 30.6 3.7 20.6 49.2 14.6c0 0-11.6-65.8-11.6-65.8zm-380.3 0l-11.6 65.9 49.3-14.7 3.6-20.5c0 0-41.3-30.7-41.3-30.7zm343.1 70.1c-0.3 16.5-3.2 32.4-8.3 47.2l30.9 1.9q1.5-5 2.6-9.9 1.2-5 2.1-10.1 0.9-5 1.5-10.1 0.6-5.1 0.9-10.2zm-306 0.1l-29.6 8.8q0.3 5.1 0.9 10.2 0.6 5.1 1.5 10.1 0.8 5 2 10 1.2 5 2.6 9.9l31-1.8c-5.2-14.9-8.1-30.7-8.4-47.2zm290.8 64.7l-10.5 18.1 28.4 42.9 33.4-57.9zm-275.4 0.1l-51.4 3.1 33.5 57.9 28.3-43zm253.2 32.8q-3 3.5-6.2 6.7-3.2 3.3-6.6 6.4-3.4 3-7 5.9-3.5 2.9-7.2 5.5l14.7 26.5q4-2.8 7.8-5.7 3.8-2.9 7.5-6.1 3.7-3.1 7.2-6.4 3.5-3.4 6.9-6.9zm-230.9 0.1l-17.2 25.9q3.4 3.5 6.9 6.8 3.5 3.4 7.2 6.5 3.6 3.1 7.5 6 3.8 3 7.8 5.7l14.7-26.5q-3.7-2.6-7.3-5.5-3.5-2.8-6.9-5.9-3.4-3-6.6-6.3-3.1-3.2-6.1-6.7zm13.3 79.7l62.9 22.9-5.9-51.1-19.6-7.1c0 0-37.4 35.3-37.4 35.3zm79.1 6.4q5.1 0.6 10.2 0.8 5.1 0.3 10.2 0.3 5.1 0 10.2-0.2 5.1-0.3 10.2-0.9l3.6-30.8q-6 0.9-12 1.4-6 0.5-12 0.4-6 0-12-0.4-6-0.5-12-1.5zm57.1 16.5l62.8-22.9-37.3-35.3-19.6 7.1c0 0-5.9 51.1-5.9 51.1z"/>
<g id="Vortex">
<path id="Vortex7" class="s0" d="m182.3 194.1c-2.1 3.5-19.8 50.8 46.8 73.2 50.6 11.2 85.3-32.6 85.3-32.6 0 0-14.6 66.8-95.6 54.5-73.9-23.5-54.9-83.4-36.5-95.1z"/>
<path id="Vortex6" class="s0" d="m197.4 181.2c-4.1 0.4-52.2 15.8-28.5 82 22.5 46.7 78.3 46.9 78.3 46.9 0 0-61.5 29.8-101.9-41.5-27.3-72.5 31.5-94.6 52.1-87.4z"/>
<path id="Vortex5" class="s0" d="m217.4 185.2c-2.8-3-44.2-32-82.6 26.9-23.5 46.1 10.1 90.7 10.1 90.7 0 0-61-30.9-28.7-106.2 41.3-65.6 94.5-32.2 101.2-11.4z"/>
<path id="Vortex4" class="s0" d="m182.4 212.2c1.1 3.9 23.5 49.2 85.4 15.9 42.8-29.2 34.6-84.5 34.6-84.5 0 0 38.7 56.4-25.7 107-67.7 37.9-98.3-17-94.3-38.4z"/>
<path id="Vortex3" class="s0" d="m197 225.2c3.7 1.6 52.8 13.2 66.6-55.7 4.7-51.6-43.1-80.5-43.1-80.5 0 0 68.1 6.1 66.1 88-13.9 76.2-75.7 64.9-89.6 48.2z"/>
<path id="Vortex2" class="s0" d="m218 220.7c3.5-2.1 40.5-36.4-9-86.3-40.1-32.8-90.8-9.3-90.8-9.3 0 0 43.1-53.1 109.9-5.6 55.5 54.1 11.6 99.1-10.1 101.2z"/>
<path id="Vortex1" class="s0" d="m226.2 203.4c0.6-4.1-1.6-54.5-71.7-49.1-50.9 9.4-65.8 63.3-65.8 63.3 0 0-12.6-67.2 66.8-87.5 77.2-7.2 83.1 55.3 70.7 73.3z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -21,6 +21,5 @@
"LoadingPhase": "PostDefault"
}
],
"IsExperimentalVersion": false,
"Sealed": true
}

View file

@ -1,6 +1,7 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "SkyPortal.h"
#include "SkyPortalSubsystem.h"
#define LOCTEXT_NAMESPACE "FSkyPortalModule"
@ -13,8 +14,12 @@ void FSkyPortalModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
if (GEngine && GEngine->GetEngineSubsystem<USkyPortalSubsystem>()) {
GEngine->GetEngineSubsystem<USkyPortalSubsystem>()->Deinitialize();
}
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FSkyPortalModule, SkyPortal)

View file

@ -0,0 +1,57 @@
#pragma once
#include "SkyPortalFigure.h"
UFigureData::UFigureData()
{
ClearData();
GenerationID = EVariantIDGeneration::UNKNOW;
}
int32 UFigureData::GetFigureID()
{
if (ID == 0) {
int16_t OutFigureID = 0;
// The figure ID is stored in Block 1 of Sector 0 (i.e., data[1]), bytes 0 and 1 (16-bit integer, little-endian)
OutFigureID = decryptedData[1][0] | // Least significant byte
(data[1][1] << 8); // Most significant byte, shifted left by 8 bits
ID = OutFigureID;// Return the 16-bit figure ID
return OutFigureID;
}
else { return ID; }
/*
// 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];
*/
};
int32 UFigureData::GetVariantID() {
int16_t OutFigureID = 0;
// The figure ID is stored in Block 1 of Sector 0 (i.e., data[1]), bytes 0 and 1 (16-bit integer, little-endian)
OutFigureID = decryptedData[1][0] | // Least significant byte
(data[1][1] << 8); // Most significant byte, shifted left by 8 bits
VariantID = OutFigureID;// Return the 16-bit figure ID
return OutFigureID;
}
EVariantIDGeneration UFigureData::GetGeneration()
{
return static_cast<EVariantIDGeneration>(VariantID);
}
void UFigureData::ClearData()
{
}
bool UFigureData::Dump(bool decrypted, FString filePath)
{
return true;
}

View file

@ -0,0 +1,427 @@
#include "SkyPortalIO.h"
#include "Misc/AES.h"
#include "HAL/UnrealMemory.h"
#include "openssl/aes.h"
DEFINE_LOG_CATEGORY(LogHIDApi);
DEFINE_LOG_CATEGORY(LogSkyportalIO);
//Constructor
USkyPortalIO::USkyPortalIO()
{
bPortalReady = OpenPortalHandle();
}
bool USkyPortalIO::OpenPortalHandle() {
//reset
if (PortalDevice) {
hid_close(PortalDevice);
bPortalReady = false;
}
/*
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);
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;
};
bool USkyPortalIO::Write(FWriteBlock* pb)
{
if (!ensure(PortalDevice)) {
UE_LOG(LogSkyportalIO, Error, TEXT("No Portal found"));
return false;
}
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"));
pb->BytesTransferred = bytes_returned;
return res;
}
#else
bool USkyPortalIO::Write(FWriteBlock* pb) {
if (!ensure(PortalDevice)) {
UE_LOG(LogSkyportalIO, Error, TEXT("No Portal found"));
return;
}
pb->data[0] = 0; // Use report 0
res = hid_write(PortalDevice, pb->data, write_buf_size);
ensureMsgf(res != -1, TEXT("Unable to write to Portal, %s"), hid_error(PortalDevice));
pb->BytesTransferred = res;
return res;
}
#endif
void USkyPortalIO::WriteRaw(const TArray<uint8>* block)
{
;
int res = hid_write(PortalDevice, block->GetData(), block->GetAllocatedSize()); //return the number of byte written
if (res == -1)
{
UE_LOG(LogSkyportalIO, Error, TEXT("Unable to write raw data to Portal. error:\n %s"), hid_error(PortalDevice));
return;
}
}
uint8* USkyPortalIO::Read()
{
uint8* output = new uint8[0x20];
int result = hid_read(PortalDevice, output, 0x20);
if (result == -1) {
UE_LOG(LogSkyportalIO, Error, TEXT("Unable to read data from Portal. Error:\n%s"), hid_error(PortalDevice));
delete[] output; // Prevent memory leak
return nullptr;
}
if (result == 0) { // No data was read
UE_LOG(LogSkyportalIO, Warning, TEXT("No data read from portal"));
delete[] output;
return nullptr;
}
return output;
}
uint8* USkyPortalIO::QueryBlock(uint8 FigureIndex, uint8 BlockIndex)
{
UE_LOG(LogSkyportalIO, Verbose, TEXT("Querying block %d - Start"), BlockIndex);
FWriteBlock command;
memset(command.data, 0, write_buf_size); //maybe not needed here
command.data[1] = 'Q';
command.data[2] = FigureIndex; // Figure index (0x00-0x0F)
command.data[3] = BlockIndex;
unsigned char* output = nullptr;
int attempt=0;
bool write;
do {
attempt++;
UE_LOG(LogSkyportalIO, Verbose, TEXT("Trying to write... attempt:%d"), attempt);
write = Write(&command);
if (write) {
output = Read();
if (output != nullptr && output != 0) {
UE_LOG(LogSkyportalIO, Verbose, TEXT("Read success: output[0]=%d, output[1]=%d, output[2]=%d"), output[0], output[1], output[2]);
}else{
UE_LOG(LogSkyportalIO, Error, TEXT("Read failed, output is null"));
return 0;
}
}
} while (write == false && output && (output[0] != 'Q' || (output[1] % 0x10 != FigureIndex && output[1] != 0x01) || output[2] != BlockIndex) && attempt < 10);
if (output == nullptr) {
UE_LOG(LogSkyportalIO, Error, TEXT("Query failed after %d attempts"), attempt);
return 0; // Return early if the loop failed to get valid output
}
UE_LOG(LogSkyportalIO, Verbose, TEXT("Querying block %d - success"), BlockIndex);
UE_LOG(LogSkyportalIO, VeryVerbose, TEXT("Data block %d = \n %s"), BlockIndex,*OutputToString(output));
return output;
}
void USkyPortalIO::Close() {
if (PortalDevice) {
hid_close(PortalDevice);
}
bPortalReady = false;
}
/* TODO: Should not be here
bool USkyPortalIO::IsFalsePositive() const
{
int dif = FMath::Abs(CurrentStatusData.Counter - OldStatusData.Counter);
return (dif <= 2 || dif >= 254);
}
*/
EPortalCommand 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
}
}
FString DataToString(const uint8* data)
{
FString OutString;
for (int32 i = 0; i < 16; i++)
{
OutString += FString::Printf(TEXT("%02x"), data[i]);
}
return OutString;
}
FString OutputToString(const unsigned char* output) {
FString OutString;
for (int32 i = 0; i < 0x20; i++)
{
OutString += FString::Printf(TEXT("%02x"), output[i]);
}
return OutString;
}
FPortalStatusData ParsePortalStatus(const uint8* StatusResponse)
{
FPortalStatusData result;
result.Counter = StatusResponse[5];
result.bIsReady = (StatusResponse[6] == 0x01);
// 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 |= StatusResponse[1]; // 1st byte
FigureStatusArray |= (StatusResponse[2] << 8); // 2nd byte
FigureStatusArray |= (StatusResponse[3] << 16); // 3rd byte
FigureStatusArray |= (StatusResponse[4] << 24); // 4th byte
bool bChangeBitsSet = false;
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;
switch (StatusBits)
{
case 0b00:
FigureStatus = EFigureStatus::NOT_PRESENT;
break;
case 0b01:
FigureStatus = EFigureStatus::PRESENT;
break;
case 0b11:
FigureStatus = EFigureStatus::ADDED; // When it's a new NUID detected
break;
case 0b10:
FigureStatus = EFigureStatus::REMOVED; // New NUID removed
break;
default:
FigureStatus = EFigureStatus::NOT_PRESENT; // Default case
break;
}
// Add to the array of figure statuses
//PortalStatusData.StatusArray.Insert(FigureStatus, i);
tempArray[i] = FigureStatus;
}
result.StatusArray.SetNum(0);
result.StatusArray.Append(tempArray);
return result;
}
void DecryptAES128(uint8* OutData, const uint8* InData, const uint8* Key)
{
UE_LOG(LogSkyportalIO, VeryVerbose, TEXT("DECRYPT -- Starting decryting data %s with %s key"), *DataToString(InData), *DataToString(Key));
AES_KEY AesKey;
// Set the decryption key (16 bytes for AES-128)
AES_set_decrypt_key(Key, 128, &AesKey);
// Perform AES-128 decryption (ECB mode)
AES_ecb_encrypt(InData, OutData, &AesKey, AES_DECRYPT);
UE_LOG(LogSkyportalIO, VeryVerbose, TEXT("DECRYPT -- data %s with %s key decryted"), *DataToString(InData), *DataToString(Key));
}
UFigureData* USkyPortalIO::ReadFigureBlocks(uint8 FigureIndex)
{
UFigureData* TempFigureData = NewObject<UFigureData>();
TempFigureData->dataError = false; // Initialize error flag
// Loop over all 64 blocks
for (uint8 BlockIndex = 0; BlockIndex < FIGURE_TOTAL_BLOCKS; ++BlockIndex)
{
UE_LOG(LogSkyportalIO, VeryVerbose, TEXT("Reading block %d"), BlockIndex);
// Query the block from the portal
uint8* output = QueryBlock(FigureIndex, BlockIndex);
if (output == 0) {
--BlockIndex;
UE_LOG(LogSkyportalIO, Error, TEXT("Query error, reprocessing..."));
continue;
}
// Copy 16 bytes from the output, starting at the third byte
FMemory::Memcpy(TempFigureData->data[BlockIndex], output + 3, FIGURE_BLOCK_SIZE);
// Block 1 is sometimes a duplicate of block 0
if (BlockIndex == 1)
{
if (FMemory::Memcmp(TempFigureData->data[1], TempFigureData->data[0], FIGURE_BLOCK_SIZE) == 0)
{
// Decrement BlockIndex to reprocess block 1 if it's a duplicate of block 0
--BlockIndex;
UE_LOG(LogSkyportalIO, Verbose, TEXT("Block 1 is a duplicate of block 0, reprocessing..."));
continue;
}
}
if (((BlockIndex + 1) % 4 == 0) || BlockIndex < 8)
{
// Direct copy from data to decryptedData for certain blocks
FMemory::Memcpy(TempFigureData->decryptedData[BlockIndex], TempFigureData->data[BlockIndex], FIGURE_BLOCK_SIZE);
UE_LOG(LogSkyportalIO, Verbose, TEXT("Block %d doesnt need decryption"), BlockIndex);
}
else {
/***** MD5 Hash Calculation *****/
// Prepare the hash input buffer
UE_LOG(LogSkyportalIO, Verbose, TEXT("DECRYPT -- Starting decryting block %d"), BlockIndex);
uint8 hashIn[0x56];
FMemory::Memcpy(hashIn, TempFigureData->data[0], 16);
FMemory::Memcpy(hashIn + 0x10, TempFigureData->data[1], 16);
hashIn[0x20] = BlockIndex;
// Append the 35-byte constant
FMemory::Memcpy(hashIn + 0x21, HASH_CONST, sizeof(HASH_CONST));
// Compute the MD5 hash
FMD5 MD5;
MD5.Update(hashIn, sizeof(hashIn));
uint8 key[16]; // MD5 produces 16 bytes
MD5.Final(key);
// AES-128 ECB decryption
FAES::FAESKey AesKey;
FMemory::Memcpy(AesKey.Key, key, 16);
//TODO: FAES implementation in Unreal is AES-256. We need AES-128
//FAES::DecryptData(TempFigureData.decryptedData[BlockIndex], TempFigureData.data[BlockIndex], 16, AesKey);
DecryptAES128(TempFigureData->decryptedData[BlockIndex], TempFigureData->data[BlockIndex], key);
UE_LOG(LogSkyportalIO, Verbose, TEXT("DECRYPT -- block %d decrypted"), BlockIndex);
}
UE_LOG(LogSkyportalIO, VeryVerbose, TEXT("block %d readed with success"), BlockIndex);
}
// Some verifications should happen. Like if dataError is set.
return TempFigureData; // Return the complete figure data
}

View file

@ -0,0 +1,110 @@
#include "SkyPortalRunner.h"
#include "SkyPortalFigure.h"
#include "SkyPortalSubsystem.h"
FPortalStatusChecker::FPortalStatusChecker(UEngineSubsystem* InSubsystem, float InCheckInterval)
: SkyPortalSubsystemRef(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)
{
USkyPortalSubsystem* subref = Cast<USkyPortalSubsystem>(SkyPortalSubsystemRef);
if (subref && !(subref->bShouldPauseRunner)) {
// 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 (SkyPortalSubsystemRef && Cast<USkyPortalSubsystem>(SkyPortalSubsystemRef))
{
USkyPortalSubsystem* subref = Cast<USkyPortalSubsystem>(SkyPortalSubsystemRef);
USkyPortalIO* PortalHandleRef = subref->PortalHandle;
UE_LOG(LogSkyportalIO, VeryVerbose, TEXT("Check portal"));
if (PortalHandleRef && PortalHandleRef->bPortalReady)
{
// Call the subsystem function to get portal status
uint8* output = PortalHandleRef->Read();
EPortalCommand CommandResponse = GetPortalCommandFromChar(output[0]);
//UFigureData figData;
switch (CommandResponse)
{
case S:
CurrentStatusData = ParsePortalStatus(output);
UE_LOG(LogSkyportalIO, VeryVerbose, TEXT("Output Data: %s"), *BytesToHex(output, sizeof(output)));
//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) {
int32 figID = 0;
UFigureData* figData = NewObject<UFigureData>();
//FigureDataBlock FigureData;
switch (CurrentStatusData.StatusArray[i])
{
case EFigureStatus::NOT_PRESENT:
case EFigureStatus::PRESENT:
break;
case EFigureStatus::ADDED:
figData = PortalHandleRef->ReadFigureBlocks(i);
if (figData)
{
figID = figData->GetFigureID();
}
subref->OnSkylanderAdded.Broadcast(figID, i);
break;
case EFigureStatus::REMOVED:
subref->OnSkylanderRemoved.Broadcast(00, i);
break;
}
}
}
}
OldStatusData = CurrentStatusData;
subref->StatusData = CurrentStatusData;
}
break;
default:
return;
}
// Do something with the status (log, notify, etc.)
UE_LOG(LogSkyportalIO, VeryVerbose, TEXT("Portal Status:"));
}
}
}

View file

@ -1,120 +1,309 @@
// A lot of this code is made because of the work of https://github.com/capull0/SkyDumper
// A lot of this code is made because of the work of https://github.com/capull0/SkyDumper and https://github.com/silicontrip/SkyReader
#include "SkyPortalSubsystem.h"
#include "Engine/Engine.h"
DEFINE_LOG_CATEGORY(LogHIDApi);
#include "HAL/RunnableThread.h"
#include "SkyPortalFigure.h"
#include <Async/TaskGraphInterfaces.h>
#include <Async/Async.h>
void USkyPortalSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
// Custom initialization logic
UE_LOG(LogTemp, Warning, TEXT("SkyPortalSubsystem Initialized"));
// Initialize HIDAPI
int res = hid_init();
if (res == 0)
FigureArray.SetNum(16);
// Create UFigureData objects dynamically
for (uint8 i = 0; i < FigureArray.Num(); i++)
{
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);
FigureArray[i] = NewObject<UFigureData>(this);
}
UE_LOG(LogTemp, Log, TEXT("SkyPortalSubsystem Initialized"));
}
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);
if (PortalHandle) {
PortalHandle->Close();
}
hid_exit();
UE_LOG(LogTemp, Warning, TEXT("SkyPortalSubsystem Deinitialized"));
UE_LOG(LogTemp, Log, TEXT("SkyPortalSubsystem Deinitialized"));
Super::Deinitialize();
}
void USkyPortalSubsystem::PortalActivate(const bool bShouldActivate)
{
FWriteBlock command;
/*
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::ConnectPortal() {
/*
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;
memset(command.data, 0, write_buf_size);
command.data[1] = 'A';
command.data[2] = bShouldActivate;
uint8* output;
do {
PortalHandle->Write(&command);
output = PortalHandle->Read();
} while (output[0] != 'A');
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;
}
void USkyPortalSubsystem::PortalReady()
{
FWriteBlock command;
// Iterate through the linked list of devices
int vendorCount = sizeof(VendorIds) / sizeof(VendorIds[0]);
int productCount = sizeof(ProductIds) / sizeof(ProductIds[0]);
memset(command.data, 0, write_buf_size); //maybe not needed here
command.data[1] = 'R';
unsigned char* output;
do {
PortalHandle->Write(&command);
output = PortalHandle->Read();
} while (output[0] != 'R');
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);
unsigned char _PortalId[2] = { output[1],output[2] };
uint16_t BE_PortalId = (_PortalId[0] << 8) | _PortalId[1]; // Big-endian format
uint32 BEU_PortalId = (_PortalId[0] << 8) | _PortalId[1]; // Big-endian format unsigned
uint16_t LE_PortalId = (_PortalId[1] << 8) | _PortalId[0]; //little endian
//uint32 LEU_PortalId = (_PortalId[1] << 8) | _PortalId[0]; //little endian unsigned
PortalId = BEU_PortalId; //Need a conversion as uint16 is not supported in BP
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);
return true;
}
FPortalStatusData USkyPortalSubsystem::PortalStatus() {
FWriteBlock command;
break;
}
}
}
memset(command.data, 0, write_buf_size); //maybe not needed here
command.data[1] = 'S';
uint8* output;
// Move to the next device in the list
attributes = attributes->next;
do {
PortalHandle->Write(&command);
output = PortalHandle->Read();
} while (output[0] != 'S');
return ParsePortalStatus(output);
}
SKYPORTAL_API bool USkyPortalSubsystem::RestartRunner()
{
if (StatusChecker)
{
StatusChecker->Stop();
StatusCheckerThread->WaitForCompletion();
delete StatusCheckerThread;
delete StatusChecker;
StatusChecker = nullptr;
StatusCheckerThread = nullptr;
}
// Free the device list
hid_free_enumeration(list);
UE_LOG(LogHIDApi, Error, TEXT("No Portals found"));
StatusChecker = new FPortalStatusChecker(this, RunnerInterval); // Check every 10 milliseconds
StatusCheckerThread = FRunnableThread::Create(StatusChecker, TEXT("PortalStatusCheckerThread"), 0, TPri_AboveNormal);
UE_LOG(LogTemp, Log, TEXT("SkyPortalSubsystem Initialized"));
return true;
}
#pragma region Color functions
void USkyPortalSubsystem::ChangePortalColor(const FLinearColor& Color)
{
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);
FWriteBlock command;
memset(command.data, 0, write_buf_size);
command.data[1] = 'C';
command.data[2] = r; // R
command.data[3] = g; // G
command.data[4] = b; // B
// no response for this one.
PortalHandle->Write(&command);
}
void USkyPortalSubsystem::ChangePortalColorSide(const FLinearColor& Color, const EPortalSide PortalSide, const float BlendTime)
{
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;
FWriteBlock command;
memset(command.data, 0, write_buf_size);
if (PortalSide == EPortalSide::BOTH) {
_portalside = EPortalSide::LEFT;
}
else {
_portalside = PortalSide;
}
switch (_portalside) {
case EPortalSide::LEFT:
command.data[1] = 'J';
command.data[2] = 0x00;
case EPortalSide::RIGHT:
command.data[1] = 'J';
command.data[2] = 0x02;
case EPortalSide::TRAP:
command.data[1] = 'L';
command.data[2] = 0x01;
command.data[3] = FMath::Max3(r, g, b); // calculate brightness
PortalHandle->Write(&command); //since it's a color command for trap, only 3 bytes are needed, no response.
return;
}
command.data[3] = r; // R
command.data[4] = g; // G
command.data[5] = b; // B
//Convert the time in millisecond into two bytes
uint16_t _time = BlendTime;
uint8_t _time_low = _time & 0xFF; // Get the low byte by masking the least significant 8 bits
uint8_t _time_high = (_time >> 8) & 0xFF; // Get the high byte extracted by shifting the bits 8 positions to the right and masking the result
command.data[6] = _time_low;
command.data[7] = _time_high;
uint8* output;
do {
PortalHandle->Write(&command);
output = PortalHandle->Read();
} while (output[0] != 'J');
if (PortalSide == EPortalSide::BOTH) {
ChangePortalColorSide(Color, EPortalSide::RIGHT, BlendTime); // send a second command for the right side
}
}
#pragma endregion
void USkyPortalSubsystem::Sleep(int sleepMs) {
FPlatformProcess::Sleep(sleepMs * 0.0001);
}
bool USkyPortalSubsystem::PortalConnect()
{
if (StatusChecker)
{
StatusChecker->Stop();
StatusCheckerThread->WaitForCompletion();
delete StatusCheckerThread;
delete StatusChecker;
StatusChecker = nullptr;
StatusCheckerThread = nullptr;
}
PortalHandle = NewObject<USkyPortalIO>();
if (IsValid(PortalHandle) && PortalHandle->bPortalReady) {
UE_LOG(LogSkyportalIO, Log, TEXT("Portal connected: "));
PortalReady();
PortalActivate(1);
Sleep(500);
ChangePortalColor(FLinearColor(0.5, 0.5, 0.5));
// Start the status checker thread
StatusChecker = new FPortalStatusChecker(this, RunnerInterval);
StatusCheckerThread = FRunnableThread::Create(StatusChecker, TEXT("PortalStatusCheckerThread"), 0, TPri_AboveNormal);
return true;
}
return false;
}
void USkyPortalSubsystem::ReadPortal() {
if (PortalDevice) {
hid_read(PortalDevice, PortalRawData, 16);
//RawDataArray.Append(PortalRawData, 16);
bool USkyPortalSubsystem::IsPortalReady()
{
if (PortalHandle) {
return PortalHandle->bPortalReady;
}
//maybe should send a status request ?
return false;
}
void USkyPortalSubsystem::PortalMusic(const USoundWave* Sound)
{
return;
}
void USkyPortalSubsystem::PortalAnalyzeAsync(const uint8 Index)
{
bShouldPauseRunner = true;
UE_LOG(LogSkyportalIO, Log, TEXT("Starting async figure analysis for index %d..."), Index);
// Run the PortalAnalyze function asynchronously on a background thread
AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this, Index]()
{
UE_LOG(LogSkyportalIO, Verbose, TEXT("Async Task started for figure index %d"), Index);
// Perform the blocking operation on the background thread
PortalAnalyze(Index);
// Once done, switch back to the main game thread
AsyncTask(ENamedThreads::GameThread, [this, Index]()
{
// This will be executed on the game thread after the async task is complete
bShouldPauseRunner = false;
UE_LOG(LogSkyportalIO, Log, TEXT("Figure analysis complete for index: %d"), Index);
});
});
}
void USkyPortalSubsystem::PortalAnalyze(const uint8 index)
{
UE_LOG(LogSkyportalIO, Log, TEXT("Reading figure..."));
if (FigureArray.IsValidIndex(index) && StatusData.StatusArray[index] == EFigureStatus::PRESENT) {
if (IsValid(PortalHandle)) {
FigureArray[index] = PortalHandle->ReadFigureBlocks(index);
UE_LOG(LogSkyportalIO, Log, TEXT("Figure ID : %d"), FigureArray[index]->GetFigureID());
return;
}
UE_LOG(LogSkyportalIO, Error, TEXT("PortalHandle unreachable"));
return;
}
UE_LOG(LogSkyportalIO, Error, TEXT("Error in FigureArray"));
return;
}
void USkyPortalSubsystem::GetFigureArray() {
FigureArray;
for (int i = 0; 16; i++) {
FigureArray[i] = PortalHandle->ReadFigureBlocks(i);
}
return;
}

View file

@ -4,7 +4,6 @@
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
#include "SkyPortalSubsystem.h"
class FSkyPortalModule : public IModuleInterface
{

View file

@ -0,0 +1,83 @@
#pragma once
#include "CoreMinimal.h"
#include "SkyPortalDefinitions.generated.h"
// This file is not supposed to have a c++ counterpart. Use it as a library
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")
};
UENUM(BlueprintType)
enum EPortalSide {
LEFT UMETA(DisplayName = "Left side"),
RIGHT UMETA(DisplayName = "Right side"),
BOTH UMETA(DisplayName = "Both left and right"),
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")
};
USTRUCT(BlueprintType)
struct FPortalStatusData
{
GENERATED_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),
bIsReady(true)
{
// Initialisation du tableau StatusArray avec 16 éléments par défaut
StatusArray.Init(DefaultStatus, ArraySize);
}
// Overload the == operator
// Different only between
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);
}
};

View file

@ -0,0 +1,98 @@
#pragma once
#include "CoreMinimal.h"
#include "SkyPortalFigure.generated.h"
//Copyright (C) 2010 Activision.All Rights Reserved.
static const uint8 HASH_CONST[] = {
0x20, 0x43, 0x6F, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x43, 0x29, 0x20, 0x32, // Copyright (C) 2
0x30, 0x31, 0x30, 0x20, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x73, 0x69, 0x6F, 0x6E, 0x2E, 0x20, // 010 Activision.
0x41, 0x6C, 0x6C, 0x20, 0x52, 0x69, 0x67, 0x68, 0x74, 0x73, 0x20, 0x52, 0x65, 0x73, 0x65, 0x72, // All Rights Reser
0x76, 0x65, 0x64, 0x2E, 0x20 }; // ved.
#define FIGURE_TOTAL_BLOCKS 64
#define FIGURE_BLOCK_SIZE 16
UENUM(BlueprintType)
enum EVariantIDGeneration {
UNKNOW = -1,
SPYROS_ADVENTURE = 0,
GIANTS = 1,
SWAP_FORCE = 2,
TRAP_TEAM = 3,
SUPERCHARGERS = 4,
IMAGINATORS = 5
};
/*
typedef struct {
unsigned char blockdata[FIGURE_TOTAL_BLOCKS][FIGURE_BLOCK_SIZE]; bool error;
} FigureDataBlock;
*/
UCLASS(BlueprintType,Blueprintable)
class UFigureData : public UObject {
GENERATED_BODY()
public:
UFigureData();
// Data Arrays
uint8 data[64][16];
uint8 decryptedData[64][16];
UPROPERTY(BlueprintReadOnly)
bool bDecrypted;
UPROPERTY(BlueprintReadOnly)
bool dataError;
// 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; //Skylander ID
int16 VariantID; //Skylander Variant ID
EVariantIDGeneration GenerationID;
#pragma region counters
uint8 counter1;
uint8 counter2;
#pragma endregion
FString Nickname;
// Methods
UFUNCTION(BlueprintCallable,BlueprintPure, meta = (Category = "SkyPortal|Figure"))
int32 GetFigureID();
UFUNCTION(BlueprintCallable,BlueprintPure, meta = (Category = "SkyPortal|Figure"))
int32 GetVariantID();
UFUNCTION(BlueprintCallable, BlueprintPure, meta = (Category = "SkyPortal|Figure"))
EVariantIDGeneration GetGeneration();
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);
UFUNCTION(BlueprintCallable)
bool Dump(bool decrypted, FString filePath);
};

View file

@ -0,0 +1,99 @@
#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 "SkyPortalDefinitions.h"
#include "SkyPortalFigure.h"
#include "SkyPortalIO.generated.h"
#pragma region Definitions
DECLARE_LOG_CATEGORY_EXTERN(LogHIDApi, Log, All);
DECLARE_LOG_CATEGORY_EXTERN(LogSkyportalIO, Log, All);
constexpr auto write_buf_size = 0x21; //buffer size for all the write command on portal. Should always be 0x21 - 33
constexpr auto TIMEOUT = 30000; //milliseconds
/* Portal physicial device IDs */
const int VendorIds[4] = { 0x12ba, 0x54c, 0x1430, 0x1430 };
const int ProductIds[4] = { 0x150, 0x967, 0x1f17 };
#pragma endregion
FPortalStatusData ParsePortalStatus(const uint8* StatusResponse);
EPortalCommand GetPortalCommandFromChar(unsigned char Char);
FString DataToString(const uint8* data);
FString OutputToString(const unsigned char* output);
/* 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:
// Constructor
USkyPortalIO();
/* hidapi will write error message in here*/
UPROPERTY(BlueprintReadOnly)
FString HidError;
/*Portal ready to be listened or to receive commands*/
UPROPERTY(BlueprintReadOnly)
bool bPortalReady = false;
/* Connect to Portal - will write the PortalDevice var if success */
bool OpenPortalHandle();
/* Send block to portal. In windows, don't use the hidapi */
bool Write(FWriteBlock* Block);
/* Send raw command to portal */
void WriteRaw(const TArray<uint8>* block);
/* Listen to portal
* @return The data sended by the portal device
*/
uint8* Read();
/*Trying to read a figure
* @return
*/
unsigned char* QueryBlock(uint8 characterIndex, uint8 block = 0x00);
UFigureData* ReadFigureBlocks(uint8 FigureIndex);
/* Close connection to Portal*/
void Close();
private:
/*TODO: Should not be here
bool IsFalsePositive() const;
*/
/* Portal device ref, useful for hidapi commands */
hid_device* PortalDevice;
};

View file

@ -0,0 +1,42 @@
#pragma once
#include "SkyPortalDefinitions.h"
#include "HAL/Runnable.h"
class FPortalStatusChecker : public FRunnable {
public:
/* Constructor : pass the subsystem and desired check interval(in seconds)
*
* @param InSubsystem Should always be the SkyPortalSubsystem
*/
FPortalStatusChecker(UEngineSubsystem* InSubsystem, float InCheckInterval);
// FRunnable interface
virtual bool Init() override;
virtual uint32 Run() override;
virtual void Stop() override;
virtual void Exit() override;
UPROPERTY(BlueprintReadOnly)
FPortalStatusData CurrentStatusData;
UPROPERTY(BlueprintReadOnly)
FPortalStatusData OldStatusData;
private:
// Pointer to the subsystem that contains PortalStatus()
UEngineSubsystem* SkyPortalSubsystemRef;
// Time interval (in seconds) for status checking
float CheckInterval;
// Thread control variables
FThreadSafeBool bShouldRun;
// Helper function to check portal status
void CheckPortalStatus();
};

View file

@ -1,51 +1,170 @@
#pragma once
#include "CoreMinimal.h"
#include "SkyPortalFigure.h"
#include "SkyPortalIO.h"
#include "SkyPortalRunner.h"
#include "Subsystems/EngineSubsystem.h"
#include "hidapi.h"
#include "Delegates/DelegateCombinations.h"
#include <Containers/Array.h>
#include "SkyPortalSubsystem.generated.h"
/* Macro Definitions */
#define TIMEOUT 30000
DECLARE_LOG_CATEGORY_EXTERN(LogHIDApi, Log, All);
#pragma region Definitions
/* Subsystem */
UCLASS()
class SKYPORTAL_API USkyPortalSubsystem : public UEngineSubsystem
//// 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()
public:
// Override initialization and deinitialization methods
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
// Connect to Portal, return false if portal is not found
UFUNCTION(BlueprintCallable, CallInEditor)
bool ConnectPortal();
/***********************************************************/
/********************* Portal Actions *********************/
/***********************************************************/
/* 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 PortalConnect();
/* Make sure that the connection to the portal works and that the device that is connected is actually a portal.
*
* In case of success, will update the PortalId, wich is different for each portal model.
*/
UFUNCTION(BlueprintCallable, CallInEditor, meta = (Category = "SkyPortal|Commands"))
SKYPORTAL_API void PortalReady();
/* Activate all the main functions of the portal, like figure detection or wireless signal.
* This command should be used before any other commands with the exception of the Ready command
*
* When the portal is activated, it will periodically send out Status responses even when no Status request has been sent. If the portal has not been activated, or if it has been deactivated again, Status responses will still be sent when requested.
* If a Deactivate request is received by a portal, all LEDs will turn off. They can be turned on again by their respective commands.
*
* @param bShouldActivate Optional : If false, send a Deactivate command.
*/
UFUNCTION(BlueprintCallable, CallInEditor, meta = (Category = "SkyPortal|Commands"))
SKYPORTAL_API void PortalActivate(const bool bShouldActivate = true);
/* Change portal color, ideally should be called just at the start. For gameplay usage, use ChangePortalColorSide()
*
* @param NextColor New color
*/
UFUNCTION(BlueprintCallable, CallInEditor, meta = (AutoCreateRefTerm = "NextColor", Category = "SkyPortal|Commands|Cosmetic", HideAlphaChannel))
SKYPORTAL_API void ChangePortalColor(const FLinearColor& NextColor = FLinearColor::Green);
/*Send a **Status** command, to see if the portal is ready to receive new commands*/
UFUNCTION(BlueprintCallable, BlueprintPure, meta = (Category = "SkyPortal"))
SKYPORTAL_API bool IsPortalReady();
UFUNCTION(BlueprintCallable, meta = (Category = "SkyPortal|NOT FUNCTIONING"))
SKYPORTAL_API void PortalMusic(const USoundWave* Sound);
UFUNCTION(BlueprintCallable, meta = (Category = "SkyPortal|Figure|Debug"))
SKYPORTAL_API void PortalAnalyzeAsync(const uint8 index);
UFUNCTION(BlueprintCallable, meta = (Category = "SkyPortal|Commands|Figure"))
SKYPORTAL_API void GetFigureArray();
/**/
UFUNCTION(BlueprintCallable, CallInEditor, meta = (Category = "SkyPortal|Commands"))
SKYPORTAL_API FPortalStatusData PortalStatus();
/**/
UFUNCTION(BlueprintCallable, CallInEditor, meta = (Category = "SkyPortal"))
SKYPORTAL_API bool RestartRunner();
/* 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|Commands|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 StatusData;
UPROPERTY(BlueprintReadOnly)
TArray<UFigureData*> FigureArray;
UPROPERTY(BlueprintReadWrite)
float RunnerInterval = 0.01f; //In seconds
/* Different for each portal model.
*
* SSA :
* GIANTS :
* SWAP FORCE : 0000547 ? , 515
* TRAP TEAM :
* SUPERCHARGERS :
* IMAGINATORS :
*/
UPROPERTY(BlueprintReadOnly)
int PortalId;
UPROPERTY(BlueprintReadOnly)
USkyPortalIO* PortalHandle;
/**/
UPROPERTY()
bool bPortalConnected_DEPRECATED = PortalHandle ? PortalHandle->bPortalReady : false;
UPROPERTY()
bool bShouldPauseRunner;
FString HidError;
private:
//Portal ref used in the subsystem
hid_device* PortalDevice;
unsigned char* PortalRawData;
/* Sleep the system
*
* @param sleepMs Sleep time (in milliseconds)
*/
static void Sleep(int sleepMs);
void PortalAnalyze(const uint8 Index);
// Pointer to the status checker thread
FPortalStatusChecker* StatusChecker;
FRunnableThread* StatusCheckerThread;
FString RawData();
void ReadPortal();
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;
};

View file

@ -7,6 +7,7 @@ public class SkyPortal : ModuleRules
{
public SkyPortal(ReadOnlyTargetRules Target) : base(Target)
{
bPrecompile = true;
// Add the path to the hidapi include directory
PublicIncludePaths.Add(Path.Combine(PluginDirectory, "ThirdParty", "hidapi", "include"));
@ -61,9 +62,10 @@ public class SkyPortal : ModuleRules
{
"Core",
"CoreUObject",
"Engine"
// ... add other public dependencies that you statically link with here ...
}
"Engine",
"OpenSSL",
"SSL"
}
);
@ -74,7 +76,7 @@ public class SkyPortal : ModuleRules
"Engine",
"Slate",
"SlateCore",
// ... add private dependencies that you statically link with here ...
}
);

View file

@ -417,6 +417,10 @@ extern "C" {
-1 on error.
Call hid_error(dev) to get the failure reason.
*/
int HID_API_EXPORT HID_API_CALL hid_set_output_report(hid_device* device, const unsigned char* data, size_t length);
int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length);
/** @brief Get a feature report from a HID device.

View file

@ -46,7 +46,7 @@ extern "C" {
@ingroup API
@param dev A device handle returned from hid_open().
@param guid The device's container ID on return.
@param container_id The device's container ID on return.
@returns
This function returns 0 on success and -1 on error.