Compare commits
14 commits
612cfb7877
...
65652d3a2a
Author | SHA1 | Date | |
---|---|---|---|
|
65652d3a2a | ||
|
952b184f10 | ||
|
b959059755 | ||
|
117da9d24a | ||
6ffaeb801a | |||
|
703db561bb | ||
|
f9d072ea5a | ||
|
b9b3c8ef81 | ||
8ba30675be | |||
|
be72994dc0 | ||
aed30481ea | |||
|
59ea084ff4 | ||
|
5f126e2b55 | ||
804e5fe911 |
15 changed files with 1103 additions and 425 deletions
BIN
Content/icons/sky-portal-icon-black.png
Normal file
BIN
Content/icons/sky-portal-icon-black.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
18
Content/icons/sky-portal-icon-black.svg
Normal file
18
Content/icons/sky-portal-icon-black.svg
Normal 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 |
BIN
Content/icons/sky-portal-icon-white.png
Normal file
BIN
Content/icons/sky-portal-icon-white.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
18
Content/icons/sky-portal-icon-white.svg
Normal file
18
Content/icons/sky-portal-icon-white.svg
Normal 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 |
|
@ -1,6 +1,7 @@
|
|||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "SkyPortal.h"
|
||||
#include "SkyPortalSubsystem.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FSkyPortalModule"
|
||||
|
||||
|
@ -16,5 +17,5 @@ void FSkyPortalModule::ShutdownModule()
|
|||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
|
||||
IMPLEMENT_MODULE(FSkyPortalModule, SkyPortal)
|
88
Source/SkyPortal/Private/SkyPortalFigure.cpp
Normal file
88
Source/SkyPortal/Private/SkyPortalFigure.cpp
Normal file
|
@ -0,0 +1,88 @@
|
|||
#pragma once
|
||||
|
||||
#include "SkyPortalFigure.h"
|
||||
|
||||
|
||||
uint32 FigureData::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 = data[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];
|
||||
*/
|
||||
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
void FigureData::ReadData(uint8 index)
|
||||
{
|
||||
ClearData();
|
||||
for (uint8 i = 0; i < 64; i++)
|
||||
{
|
||||
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()
|
||||
{
|
||||
}
|
320
Source/SkyPortal/Private/SkyPortalIO.cpp
Normal file
320
Source/SkyPortal/Private/SkyPortalIO.cpp
Normal file
|
@ -0,0 +1,320 @@
|
|||
#include "SkyPortalIO.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;
|
||||
};
|
||||
|
||||
|
||||
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"));
|
||||
pb->BytesTransferred = bytes_returned;
|
||||
|
||||
}
|
||||
|
||||
#else
|
||||
void 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;
|
||||
}
|
||||
#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 byte[0x20];
|
||||
hid_read(PortalDevice, output, 0x20);
|
||||
return output;
|
||||
}
|
||||
|
||||
TArray<uint8> USkyPortalIO::QueryBlock(uint8 characterIndex, uint8 block)
|
||||
{
|
||||
return TArray<uint8>();
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
FigureData USkyPortalIO::ReadFigureBlocks(uint8 FigureIndex)
|
||||
{
|
||||
|
||||
FigureData FigureData;
|
||||
FigureData.dataError = false; // Initialize error flag
|
||||
|
||||
FWriteBlock command;
|
||||
|
||||
// Loop over all 64 blocks
|
||||
for (uint8 BlockIndex = 0; BlockIndex < FIGURE_TOTAL_BLOCKS; ++BlockIndex)
|
||||
{
|
||||
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;
|
||||
uint8* output;
|
||||
do {
|
||||
Write(&command);
|
||||
output = Read();
|
||||
} while (output[0] != 'Q');
|
||||
|
||||
if (output[1] == (0x10 + 'Q')) // Successful query
|
||||
{
|
||||
// Extract the 16 bytes of block data
|
||||
for (int i = 3; i < 19; i++)
|
||||
{
|
||||
FigureData.data[BlockIndex][i] = output[i];
|
||||
}
|
||||
}
|
||||
else if (output[0] == 0x01) {
|
||||
FigureData.dataError = true;
|
||||
UE_LOG(LogSkyportalIO, Warning, TEXT("Error querying block %d from figure %d"), BlockIndex, FigureIndex);
|
||||
break;
|
||||
}
|
||||
|
||||
// Verify that the block index matches the requested block
|
||||
uint8 ReturnedBlockIndex = output[3];
|
||||
if (ReturnedBlockIndex != BlockIndex)
|
||||
{
|
||||
FigureData.dataError = 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);
|
||||
}
|
||||
|
||||
// Some verifications should happen. Like if dataError is set.
|
||||
|
||||
return FigureData; // Return the complete figure data
|
||||
}
|
105
Source/SkyPortal/Private/SkyPortalRunner.cpp
Normal file
105
Source/SkyPortal/Private/SkyPortalRunner.cpp
Normal file
|
@ -0,0 +1,105 @@
|
|||
#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)
|
||||
{
|
||||
// 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, Verbose, TEXT("Check portal"));
|
||||
if (PortalHandleRef && PortalHandleRef->bPortalReady)
|
||||
{
|
||||
// Call the subsystem function to get portal status
|
||||
uint8* output = PortalHandleRef->Read();
|
||||
EPortalCommand CommandResponse = GetPortalCommandFromChar(output[0]);
|
||||
FigureData figData;
|
||||
switch (CommandResponse)
|
||||
{
|
||||
case S:
|
||||
CurrentStatusData = ParsePortalStatus(output);
|
||||
UE_LOG(LogSkyportalIO, Verbose, 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) {
|
||||
//FigureDataBlock FigureData;
|
||||
switch (CurrentStatusData.StatusArray[i])
|
||||
{
|
||||
case EFigureStatus::NOT_PRESENT:
|
||||
|
||||
break;
|
||||
case EFigureStatus::PRESENT:
|
||||
//FigureData = ReadFigureBlocks(i);
|
||||
//subref->OnSkylanderAdded.Broadcast(GetFigureID(FigureData), i);
|
||||
break;
|
||||
case EFigureStatus::ADDED:
|
||||
figData = PortalHandleRef->ReadFigureBlocks(i);
|
||||
subref->OnSkylanderAdded.Broadcast(figData.GetFigureID(), 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, Verbose, TEXT("Portal Status:"));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -2,40 +2,41 @@
|
|||
|
||||
#include "SkyPortalSubsystem.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "SkyPortal.h"
|
||||
|
||||
#include "HAL/RunnableThread.h"
|
||||
#include "Misc/ScopeLock.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogHIDApi);
|
||||
DEFINE_LOG_CATEGORY(LogSkyportalIO);
|
||||
|
||||
void USkyPortalSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
|
||||
Super::Initialize(Collection);
|
||||
// Custom initialization logic
|
||||
// Start the status checker thread
|
||||
StatusChecker = new FPortalStatusChecker(this, RunnerInterval);
|
||||
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);
|
||||
if (PortalHandle) {
|
||||
PortalHandle->Close();
|
||||
}
|
||||
hid_exit();
|
||||
|
||||
|
@ -44,391 +45,195 @@ void USkyPortalSubsystem::Deinitialize()
|
|||
Super::Deinitialize();
|
||||
}
|
||||
|
||||
// Antenna up / activate
|
||||
void USkyPortalSubsystem::ActivatePortal(int active)
|
||||
|
||||
void USkyPortalSubsystem::PortalActivate(const bool bShouldActivate)
|
||||
{
|
||||
RWBlock req, res;
|
||||
FWriteBlock command;
|
||||
|
||||
memset(req.buf, 0, rw_buf_size);
|
||||
req.buf[1] = 'A';
|
||||
req.buf[2] = active;
|
||||
do { Write(&req); } while (CheckResponse(&res, 'A'));
|
||||
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');
|
||||
|
||||
}
|
||||
|
||||
// Start portal
|
||||
void USkyPortalSubsystem::RestartPortal()
|
||||
|
||||
|
||||
void USkyPortalSubsystem::PortalReady()
|
||||
{
|
||||
RWBlock req, res;
|
||||
FWriteBlock command;
|
||||
|
||||
memset(req.buf, 0, rw_buf_size);
|
||||
req.buf[1] = 'R';
|
||||
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');
|
||||
|
||||
do { Write(&req); } while (CheckResponse(&res, 'R'));
|
||||
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
|
||||
|
||||
}
|
||||
|
||||
FPortalStatusData USkyPortalSubsystem::PortalStatus() {
|
||||
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::OpenPortalHandle() {
|
||||
memset(command.data, 0, write_buf_size); //maybe not needed here
|
||||
command.data[1] = 'S';
|
||||
uint8* output;
|
||||
|
||||
//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;
|
||||
do {
|
||||
PortalHandle->Write(&command);
|
||||
output = PortalHandle->Read();
|
||||
} while (output[0] != 'S');
|
||||
|
||||
|
||||
|
||||
|
||||
// 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;
|
||||
return ParsePortalStatus(output);
|
||||
}
|
||||
//{ Region Color & light functions
|
||||
|
||||
SKYPORTAL_API bool USkyPortalSubsystem::RestartRunner()
|
||||
{
|
||||
if (StatusChecker)
|
||||
{
|
||||
StatusChecker->Stop();
|
||||
StatusCheckerThread->WaitForCompletion();
|
||||
|
||||
delete StatusCheckerThread;
|
||||
delete StatusChecker;
|
||||
StatusChecker = nullptr;
|
||||
StatusCheckerThread = nullptr;
|
||||
}
|
||||
|
||||
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"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#pragma region Color 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;
|
||||
FWriteBlock command;
|
||||
|
||||
memset(req.buf, 0, rw_buf_size);
|
||||
memset(command.data, 0, write_buf_size);
|
||||
|
||||
req.buf[1] = 'C';
|
||||
req.buf[2] = r; // R
|
||||
req.buf[3] = g; // G
|
||||
req.buf[4] = b; // B
|
||||
command.data[1] = 'C';
|
||||
command.data[2] = r; // R
|
||||
command.data[3] = g; // G
|
||||
command.data[4] = b; // B
|
||||
|
||||
// no response for this one.
|
||||
Write(&req);
|
||||
PortalHandle->Write(&command);
|
||||
}
|
||||
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"));
|
||||
|
||||
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;
|
||||
}
|
||||
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);
|
||||
|
||||
RWBlock req, res;
|
||||
|
||||
memset(req.buf, 0, rw_buf_size);
|
||||
|
||||
req.buf[1] = 'J';
|
||||
switch (PortalSide) {
|
||||
case EPortalSide::LEFT:
|
||||
req.buf[2] = 0x00;
|
||||
case EPortalSide::RIGHT:
|
||||
req.buf[2] = 0x02;
|
||||
case EPortalSide::BOTH:
|
||||
req.buf[2] = 0x01; //maybe that doesnt work, will give it a try
|
||||
}
|
||||
|
||||
req.buf[3] = r; // R
|
||||
req.buf[4] = g; // G
|
||||
req.buf[5] = b; // B
|
||||
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
|
||||
req.buf[6] = _time_low;
|
||||
req.buf[7] = _time_high;
|
||||
command.data[6] = _time_low;
|
||||
command.data[7] = _time_high;
|
||||
|
||||
do { Write(&req); } while (CheckResponse(&res, 'J'));
|
||||
uint8* output;
|
||||
do {
|
||||
PortalHandle->Write(&command);
|
||||
output = PortalHandle->Read();
|
||||
} while (output[0] != 'J');
|
||||
|
||||
}
|
||||
//}
|
||||
|
||||
|
||||
// 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)
|
||||
{
|
||||
if (!ensure(PortalDevice)) {
|
||||
UE_LOG(LogSkyportalIO, Error, TEXT("No Portal found"));
|
||||
return;
|
||||
if (PortalSide == EPortalSide::BOTH) {
|
||||
ChangePortalColorSide(Color, EPortalSide::RIGHT, BlendTime); // send a second command for the right side
|
||||
}
|
||||
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->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;
|
||||
}
|
||||
|
||||
pb->buf[0] = 0; // Use report 0
|
||||
#pragma endregion
|
||||
|
||||
|
||||
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));
|
||||
}
|
||||
#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)
|
||||
|
||||
bool USkyPortalSubsystem::PortalConnect()
|
||||
{
|
||||
if (!PortalDevice) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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"));
|
||||
return false;
|
||||
}
|
||||
|
||||
res->BytesTransferred = b;
|
||||
|
||||
/* this is here to debug the different responses from the portal.
|
||||
#if DEBUG
|
||||
SkylanderIO* skio;
|
||||
skio = new SkylanderIO();
|
||||
printf("<<<\n");
|
||||
skio->fprinthex(stdout, res->buf, 0x21);
|
||||
delete skio;
|
||||
#endif
|
||||
*/
|
||||
|
||||
// found wireless USB but portal is not connected
|
||||
if (res->buf[0] == 'Z')
|
||||
{
|
||||
UE_LOG(LogSkyportalIO, Error, TEXT("found wireless USB but portal is not connected"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Status says no skylander on portal
|
||||
if (res->buf[0] == 'Q' && res->buf[1] == 0) {
|
||||
UE_LOG(LogSkyportalIO, Warning, TEXT("Status says no skylander on portal"));
|
||||
}
|
||||
|
||||
if (res->buf[0] == 'R' && res->buf[1] == 0) {
|
||||
UE_LOG(LogSkyportalIO, Warning, TEXT("Status says no skylander on portal"));
|
||||
}
|
||||
|
||||
return (res->buf[0] != 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;
|
||||
}
|
||||
UE_LOG(LogSkyportalIO, Fatal, TEXT("failed to write block"));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool USkyPortalSubsystem::ReadBlock(unsigned int block, unsigned char data[0x10], int skylander) {
|
||||
RWBlock req, res; //request and response buffers
|
||||
unsigned char followup;
|
||||
|
||||
UE_LOG(LogSkyportalIO, Verbose, TEXT("PortalIO:ReadBlock :%X"), block);
|
||||
|
||||
|
||||
// Checking if the block is not out of range
|
||||
if (!ensure(block < 0x40)) {
|
||||
UE_LOG(LogSkyportalIO, Error, TEXT("PortalIO:ReadBlock failed, block out of range"));
|
||||
return false; // Early return instead of throwing an exception
|
||||
}
|
||||
|
||||
// Send query request
|
||||
|
||||
// Trying to read data 15x
|
||||
for (int attempt = 0; attempt < 15; attempt++)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
memset(req.buf, 0, rw_buf_size); // Clear the request buffer (initialize all bytes to zero)
|
||||
req.buf[1] = 'Q';
|
||||
followup = 0x10 + skylander;
|
||||
req.buf[2] = followup;
|
||||
if (block == 0) {
|
||||
req.buf[2] = followup + 0x10; // may not be needed
|
||||
}
|
||||
req.buf[3] = (unsigned char)block;
|
||||
|
||||
memset(&(res.buf), 0, rw_buf_size); // Clear the response buffer to prepare for incoming data
|
||||
|
||||
|
||||
do { Write(&req); } while (CheckResponse(&res, 'Q'));
|
||||
|
||||
if (res.buf[0] == 'Q' && res.buf[2] == (unsigned char)block) {
|
||||
// Got our query back
|
||||
if (res.buf[1] == followup) {
|
||||
// got the query back with no error
|
||||
memcpy(data, res.buf + 3, 0x10);
|
||||
UE_LOG(LogSkyportalIO, Verbose, TEXT("PortalIO:ReadBlock success"));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // retries
|
||||
|
||||
UE_LOG(LogSkyportalIO, Fatal, TEXT("PortalIO:ReadBlock failed after retries"));
|
||||
ensureMsgf(false, TEXT("PortalIO: Failed to read block after multiple retries"));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool USkyPortalSubsystem::ConnectPortal()
|
||||
{
|
||||
|
||||
bPortalConnected = OpenPortalHandle();
|
||||
if (bPortalConnected) {
|
||||
RestartPortal();
|
||||
ActivatePortal(1);
|
||||
PortalHandle = NewObject<USkyPortalIO>();
|
||||
if (IsValid(PortalHandle)) {
|
||||
UE_LOG(LogSkyportalIO, Log, TEXT("Portal connected: "));
|
||||
PortalReady();
|
||||
PortalActivate(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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
unsigned char USkyPortalSubsystem::PortalStatus()
|
||||
bool USkyPortalSubsystem::IsPortalReady()
|
||||
{
|
||||
RWBlock req, res;
|
||||
if (PortalHandle) {
|
||||
return PortalHandle->bPortalReady;
|
||||
}
|
||||
//maybe should send a status request ?
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(req.buf, 0, rw_buf_size);
|
||||
req.buf[1] = 'S';
|
||||
do { Write(&req); } while (CheckResponse(&res, 'S'));
|
||||
|
||||
return res.buf[1];
|
||||
}
|
||||
SKYPORTAL_API void USkyPortalSubsystem::PortalMusic(const USoundWave* Sound)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
#include "SkyPortalSubsystem.h"
|
||||
|
||||
class FSkyPortalModule : public IModuleInterface
|
||||
{
|
||||
|
|
83
Source/SkyPortal/Public/SkyPortalDefinitions.h
Normal file
83
Source/SkyPortal/Public/SkyPortalDefinitions.h
Normal 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);
|
||||
}
|
||||
};
|
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"
|
||||
|
||||
|
||||
#define FIGURE_TOTAL_BLOCKS 64
|
||||
#define FIGURE_BLOCK_SIZE 16
|
||||
|
||||
typedef struct {
|
||||
unsigned char blockdata[FIGURE_TOTAL_BLOCKS][FIGURE_BLOCK_SIZE]; bool error;
|
||||
} FigureDataBlock;
|
||||
|
||||
|
||||
|
||||
class FigureData {
|
||||
public:
|
||||
// Data Arrays
|
||||
uint8 data[64][16];
|
||||
uint8 decryptedData[64][16];
|
||||
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;
|
||||
int16 VariantID;
|
||||
#pragma region counters
|
||||
uint8 counter1;
|
||||
uint8 couter2;
|
||||
#pragma endregion
|
||||
FString Nickname;
|
||||
|
||||
FigureData()
|
||||
{
|
||||
ClearData();
|
||||
}
|
||||
|
||||
// Methods
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, meta = (Category = "SkyPortal|Figure"))
|
||||
uint32 GetFigureID();
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, meta = (Category = "SkyPortal|Figure"))
|
||||
uint32 GetFigureIdByIndex(uint32 index);
|
||||
|
||||
UFUNCTION()
|
||||
TArray<uint8> QueryBlock(uint8 characterIndex, uint8 block = 0x00);
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
|
94
Source/SkyPortal/Public/SkyPortalIO.h
Normal file
94
Source/SkyPortal/Public/SkyPortalIO.h
Normal file
|
@ -0,0 +1,94 @@
|
|||
#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);
|
||||
|
||||
/* 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 */
|
||||
void 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();
|
||||
|
||||
UFUNCTION()
|
||||
TArray<uint8> QueryBlock(uint8 characterIndex, uint8 block = 0x00);
|
||||
|
||||
FigureData 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;
|
||||
|
||||
};
|
||||
|
41
Source/SkyPortal/Public/SkyPortalRunner.h
Normal file
41
Source/SkyPortal/Public/SkyPortalRunner.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
|
||||
#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();
|
||||
};
|
||||
|
|
@ -1,114 +1,152 @@
|
|||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Subsystems/EngineSubsystem.h"
|
||||
#include "hidapi.h"
|
||||
|
||||
|
||||
#include "SkyPortalRunner.h"
|
||||
#include "SkyPortalIO.h"
|
||||
#include "SkyPortalSubsystem.generated.h"
|
||||
|
||||
#pragma region Definitions
|
||||
|
||||
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")
|
||||
};
|
||||
//// Delegates
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSkylanderAddedDelegate, int32, SkylanderID, int32, Index);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSkylanderRemovedDelegate, int32, SkylanderID, int32, Index);
|
||||
|
||||
#pragma endregion
|
||||
|
||||
|
||||
|
||||
/* 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 */
|
||||
/*
|
||||
* 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();
|
||||
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();
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
|
||||
bool bPortalConnected = false;
|
||||
|
||||
FString HidError;
|
||||
/* 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);
|
||||
|
||||
|
||||
/* Portal Actions*/
|
||||
/* 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);
|
||||
|
||||
// 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);
|
||||
/*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, 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
|
||||
/* 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
|
||||
* @param BlendTime Blend between current color and NextColor, in milliseconds
|
||||
*/
|
||||
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);
|
||||
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(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;
|
||||
|
||||
|
||||
unsigned char PortalStatus();
|
||||
|
||||
|
||||
|
||||
|
||||
private:
|
||||
|
||||
|
||||
typedef struct {
|
||||
unsigned char buf[rw_buf_size]; int BytesTransferred;
|
||||
} RWBlock;
|
||||
|
||||
|
||||
/* Sleep the system
|
||||
*
|
||||
* @param sleepMs Sleep time (in milliseconds)
|
||||
*/
|
||||
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 *);
|
||||
// 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