Compare commits
3 commits
599472a176
...
758df315a1
Author | SHA1 | Date | |
---|---|---|---|
758df315a1 | |||
7a6ef52144 | |||
11766f6385 |
24 changed files with 1019 additions and 339 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -5,3 +5,6 @@
|
||||||
/agent/bin
|
/agent/bin
|
||||||
/agent/obj
|
/agent/obj
|
||||||
/agent/compile_commands.json
|
/agent/compile_commands.json
|
||||||
|
|
||||||
|
# test stuff
|
||||||
|
/shared/src/test
|
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -26,12 +26,6 @@ version = "3.16.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "byteorder"
|
|
||||||
version = "1.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
|
@ -82,7 +76,7 @@ checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4"
|
||||||
name = "fbcserver"
|
name = "fbcserver"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"cc",
|
||||||
"minifb",
|
"minifb",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -204,9 +198,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.164"
|
version = "0.2.167"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
|
checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libloading"
|
name = "libloading"
|
||||||
|
|
|
@ -4,5 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
byteorder = "1.5.0"
|
|
||||||
minifb = "0.27.0"
|
minifb = "0.27.0"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
cc = "1.0.99"
|
|
@ -9,6 +9,7 @@ OBJDIR = obj/$(ARCH)/$(CONFIG)
|
||||||
VPATH = src/crt \
|
VPATH = src/crt \
|
||||||
src/base \
|
src/base \
|
||||||
src/ \
|
src/ \
|
||||||
|
../shared/src
|
||||||
|
|
||||||
# Objects for crt
|
# Objects for crt
|
||||||
OBJS_STARTUP := $(OBJDIR)/crt0.o \
|
OBJS_STARTUP := $(OBJDIR)/crt0.o \
|
||||||
|
@ -21,6 +22,7 @@ OBJS_BASELIB :=
|
||||||
OBJS := $(OBJDIR)/capture.o \
|
OBJS := $(OBJDIR)/capture.o \
|
||||||
$(OBJDIR)/capture_nvfbc.o \
|
$(OBJDIR)/capture_nvfbc.o \
|
||||||
$(OBJDIR)/nvfbc_library.o \
|
$(OBJDIR)/nvfbc_library.o \
|
||||||
|
$(OBJDIR)/ivshmem.o \
|
||||||
$(OBJDIR)/main.o \
|
$(OBJDIR)/main.o \
|
||||||
|
|
||||||
LINK_LIBS := $(VS2022_PATH)/ucrt/lib/$(ARCH)/libucrt$(D).lib \
|
LINK_LIBS := $(VS2022_PATH)/ucrt/lib/$(ARCH)/libucrt$(D).lib \
|
||||||
|
@ -30,7 +32,7 @@ LINK_LIBS := $(VS2022_PATH)/ucrt/lib/$(ARCH)/libucrt$(D).lib \
|
||||||
$(VS2022_PATH)/winsdk/lib/$(ARCH)/kernel32.lib \
|
$(VS2022_PATH)/winsdk/lib/$(ARCH)/kernel32.lib \
|
||||||
$(VS2022_PATH)/winsdk/lib/$(ARCH)/user32.lib \
|
$(VS2022_PATH)/winsdk/lib/$(ARCH)/user32.lib \
|
||||||
$(VS2022_PATH)/winsdk/lib/$(ARCH)/comctl32.lib \
|
$(VS2022_PATH)/winsdk/lib/$(ARCH)/comctl32.lib \
|
||||||
$(VS2022_PATH)/winsdk/lib/$(ARCH)/ws2_32.lib
|
$(VS2022_PATH)/winsdk/lib/$(ARCH)/setupapi.lib \
|
||||||
|
|
||||||
.PHONY: all dumpinfo clean matrix
|
.PHONY: all dumpinfo clean matrix
|
||||||
|
|
||||||
|
@ -40,6 +42,11 @@ publish: $(BINDIR)/$(NAME).exe
|
||||||
cp -rv bin /data/sda/shit/Release/clangent
|
cp -rv bin /data/sda/shit/Release/clangent
|
||||||
chmod -Rvv 777 /data/sda/shit/Release/clangent
|
chmod -Rvv 777 /data/sda/shit/Release/clangent
|
||||||
|
|
||||||
|
|
||||||
|
publish2: $(BINDIR)/$(NAME).exe
|
||||||
|
cp -rv bin/ /data/sda/shit/Release/ivshtest/
|
||||||
|
chmod -Rvv 777 /data/sda/shit/Release/ivshtest
|
||||||
|
|
||||||
dumpinfo:
|
dumpinfo:
|
||||||
echo -e "\e[96mBuilding configuration $(CONFIG) for arch $(ARCH) \e[0m"
|
echo -e "\e[96mBuilding configuration $(CONFIG) for arch $(ARCH) \e[0m"
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ SDK_INCLUDES := -isystem $(VS2022_PATH)/ucrt/include -isystem $(VS2022_PATH)/crt
|
||||||
|
|
||||||
# Windows 6.1
|
# Windows 6.1
|
||||||
BASE_CCFLAGS := $(SDK_INCLUDES) -D_WIN32_WINNT=0x0601 -Ires -std=c17
|
BASE_CCFLAGS := $(SDK_INCLUDES) -D_WIN32_WINNT=0x0601 -Ires -std=c17
|
||||||
BASE_CXXFLAGS := $(SDK_INCLUDES) -D_CRT_SECURE_NO_WARNINGS -fno-exceptions -fno-rtti -D_WIN32_WINNT=0x0601 -Isrc -Ires -std=c++23
|
BASE_CXXFLAGS := $(SDK_INCLUDES) -D_CRT_SECURE_NO_WARNINGS -fno-exceptions -fno-rtti -D_WIN32_WINNT=0x0601 -I../shared/src -Isrc -Ires -std=c++23
|
||||||
|
|
||||||
Release_Valid = yes
|
Release_Valid = yes
|
||||||
Release_CCFLAGS = -O3 -ffast-math -DNDEBUG
|
Release_CCFLAGS = -O3 -ffast-math -DNDEBUG
|
||||||
|
|
69
agent/sdk/inc/virtio/ivshmem.h
Normal file
69
agent/sdk/inc/virtio/ivshmem.h
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
#include <initguid.h>
|
||||||
|
|
||||||
|
DEFINE_GUID (GUID_DEVINTERFACE_IVSHMEM,
|
||||||
|
0xdf576976,0x569d,0x4672,0x95,0xa0,0xf5,0x7e,0x4e,0xa0,0xb2,0x10);
|
||||||
|
// {df576976-569d-4672-95a0-f57e4ea0b210}
|
||||||
|
|
||||||
|
typedef UINT16 IVSHMEM_PEERID;
|
||||||
|
typedef UINT64 IVSHMEM_SIZE;
|
||||||
|
|
||||||
|
#define IVSHMEM_CACHE_NONCACHED 0
|
||||||
|
#define IVSHMEM_CACHE_CACHED 1
|
||||||
|
#define IVSHMEM_CACHE_WRITECOMBINED 2
|
||||||
|
|
||||||
|
/*
|
||||||
|
This structure is for use with the IOCTL_IVSHMEM_REQUEST_MMAP IOCTL
|
||||||
|
*/
|
||||||
|
typedef struct IVSHMEM_MMAP_CONFIG
|
||||||
|
{
|
||||||
|
UINT8 cacheMode; // the caching mode of the mapping, see IVSHMEM_CACHE_* for options
|
||||||
|
}
|
||||||
|
IVSHMEM_MMAP_CONFIG, *PIVSHMEM_MMAP_CONFIG;
|
||||||
|
|
||||||
|
/*
|
||||||
|
This structure is for use with the IOCTL_IVSHMEM_REQUEST_MMAP IOCTL
|
||||||
|
*/
|
||||||
|
typedef struct IVSHMEM_MMAP
|
||||||
|
{
|
||||||
|
IVSHMEM_PEERID peerID; // our peer id
|
||||||
|
IVSHMEM_SIZE size; // the size of the memory region
|
||||||
|
PVOID ptr; // pointer to the memory region
|
||||||
|
UINT16 vectors; // the number of vectors available
|
||||||
|
}
|
||||||
|
IVSHMEM_MMAP, *PIVSHMEM_MMAP;
|
||||||
|
|
||||||
|
/*
|
||||||
|
This structure is for use with the IOCTL_IVSHMEM_RING_DOORBELL IOCTL
|
||||||
|
*/
|
||||||
|
typedef struct IVSHMEM_RING
|
||||||
|
{
|
||||||
|
IVSHMEM_PEERID peerID; // the id of the peer to ring
|
||||||
|
UINT16 vector; // the doorbell to ring
|
||||||
|
}
|
||||||
|
IVSHMEM_RING, *PIVSHMEM_RING;
|
||||||
|
|
||||||
|
/*
|
||||||
|
This structure is for use with the IOCTL_IVSHMEM_REGISTER_EVENT IOCTL
|
||||||
|
|
||||||
|
Please Note:
|
||||||
|
- The IVSHMEM driver has a hard limit of 32 events.
|
||||||
|
- Events that are singleShot are released after they have been set.
|
||||||
|
- At this time repeating events are only released when the driver device
|
||||||
|
handle is closed, closing the event handle doesn't release it from the
|
||||||
|
drivers list. While this won't cause a problem in the driver, it will
|
||||||
|
cause you to run out of event slots.
|
||||||
|
*/
|
||||||
|
typedef struct IVSHMEM_EVENT
|
||||||
|
{
|
||||||
|
UINT16 vector; // the vector that triggers the event
|
||||||
|
HANDLE event; // the event to trigger
|
||||||
|
BOOLEAN singleShot; // set to TRUE if you want the driver to only trigger this event once
|
||||||
|
}
|
||||||
|
IVSHMEM_EVENT, *PIVSHMEM_EVENT;
|
||||||
|
|
||||||
|
#define IOCTL_IVSHMEM_REQUEST_PEERID CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||||
|
#define IOCTL_IVSHMEM_REQUEST_SIZE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||||
|
#define IOCTL_IVSHMEM_REQUEST_MMAP CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||||
|
#define IOCTL_IVSHMEM_RELEASE_MMAP CTL_CODE(FILE_DEVICE_UNKNOWN, 0x803, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||||
|
#define IOCTL_IVSHMEM_RING_DOORBELL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x804, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||||
|
#define IOCTL_IVSHMEM_REGISTER_EVENT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x805, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
|
@ -79,11 +79,11 @@ namespace hazelnut {
|
||||||
|
|
||||||
void Shutdown() {
|
void Shutdown() {
|
||||||
NvfbcDestroyInstance();
|
NvfbcDestroyInstance();
|
||||||
nvfbcLib.close();
|
nvfbcLib.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Initialize() override {
|
bool Initialize() override {
|
||||||
if(!nvfbcLib.load()) {
|
if(!nvfbcLib.Load()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +133,7 @@ namespace hazelnut {
|
||||||
blockSize = NVFBC_TOSYS_DIFFMAP_BLOCKSIZE_32X32;
|
blockSize = NVFBC_TOSYS_DIFFMAP_BLOCKSIZE_32X32;
|
||||||
|
|
||||||
// set up a session
|
// set up a session
|
||||||
NVFBC_TOSYS_SETUP_PARAMS fbcSysSetupParams{};
|
NVFBC_TOSYS_SETUP_PARAMS fbcSysSetupParams {};
|
||||||
fbcSysSetupParams.dwVersion = NVFBC_TOSYS_SETUP_PARAMS_VER;
|
fbcSysSetupParams.dwVersion = NVFBC_TOSYS_SETUP_PARAMS_VER;
|
||||||
fbcSysSetupParams.eMode = NVFBC_TOSYS_ARGB;
|
fbcSysSetupParams.eMode = NVFBC_TOSYS_ARGB;
|
||||||
fbcSysSetupParams.bWithHWCursor = true;
|
fbcSysSetupParams.bWithHWCursor = true;
|
||||||
|
@ -197,8 +197,6 @@ namespace hazelnut {
|
||||||
auto* pSrcData = (u8*)&pRawFramebuffer[0];
|
auto* pSrcData = (u8*)&pRawFramebuffer[0];
|
||||||
|
|
||||||
for(u32 y = 0; y < grabInfo.dwHeight; ++y) {
|
for(u32 y = 0; y < grabInfo.dwHeight; ++y) {
|
||||||
|
|
||||||
|
|
||||||
// Convert to BGRA
|
// Convert to BGRA
|
||||||
// FIXME: Make this SIMD. I can't into this very well
|
// FIXME: Make this SIMD. I can't into this very well
|
||||||
#if 0
|
#if 0
|
||||||
|
@ -224,8 +222,8 @@ namespace hazelnut {
|
||||||
FramebufferInformation GetFramebufferInformation() override { return FramebufferInformation { convertedFramebuffer.data(), width, height }; }
|
FramebufferInformation GetFramebufferInformation() override { return FramebufferInformation { convertedFramebuffer.data(), width, height }; }
|
||||||
|
|
||||||
DiffInformation GetDiffInformation() override {
|
DiffInformation GetDiffInformation() override {
|
||||||
//diffmapWidth = (u32)ceil((f32)width / 32);
|
// diffmapWidth = (u32)ceil((f32)width / 32);
|
||||||
//diffmapHeight = (u32)ceil((f32)height / 32);
|
// diffmapHeight = (u32)ceil((f32)height / 32);
|
||||||
|
|
||||||
diffmapWidth = DiffMapDimension(width, blockSize);
|
diffmapWidth = DiffMapDimension(width, blockSize);
|
||||||
diffmapHeight = DiffMapDimension(height, blockSize);
|
diffmapHeight = DiffMapDimension(height, blockSize);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// clang-format off
|
// clang-format off
|
||||||
|
#include "ivshmem.hpp"
|
||||||
#pragma comment(lib, "ws2_32.lib")
|
#pragma comment(lib, "ws2_32.lib")
|
||||||
#include <WinSock2.h>
|
#include <WinSock2.h>
|
||||||
#include <ws2tcpip.h>
|
#include <ws2tcpip.h>
|
||||||
|
@ -11,170 +12,134 @@
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
#include "atomic_spinlock.hpp"
|
||||||
enum class MessageType : u32 {
|
#include "ivshmem_protocol.hpp"
|
||||||
Resize, // tResizeMessage
|
|
||||||
Data, // tDataMessage
|
|
||||||
};
|
|
||||||
|
|
||||||
struct tMessageHeader {
|
|
||||||
MessageType type;
|
|
||||||
u32 datalen;
|
|
||||||
// data
|
|
||||||
};
|
|
||||||
|
|
||||||
struct tResizeMessage {
|
|
||||||
u32 width;
|
|
||||||
u32 height;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct tTile {
|
|
||||||
u32 tile_x;
|
|
||||||
u32 tile_y;
|
|
||||||
|
|
||||||
u32 tile_width;
|
|
||||||
u32 tile_height;
|
|
||||||
|
|
||||||
// u32 tile_rgba[tile_width*tile_height]
|
|
||||||
};
|
|
||||||
|
|
||||||
struct tDataMessage {
|
|
||||||
u32 tileCount;
|
|
||||||
};
|
|
||||||
|
|
||||||
#pragma pack(pop)
|
|
||||||
|
|
||||||
struct tileRect {
|
struct tileRect {
|
||||||
u32 x, y, width, height;
|
u32 x, y, width, height;
|
||||||
};
|
};
|
||||||
|
|
||||||
// client for streamserver
|
struct Test {
|
||||||
class cStreamClient {
|
hazelnut::AtomicSpinlock lk {};
|
||||||
SOCKET tcpSocket;
|
std::atomic<u32> sessionId {};
|
||||||
|
std::atomic<u32> pingPong {};
|
||||||
public:
|
|
||||||
cStreamClient() = default;
|
|
||||||
|
|
||||||
~cStreamClient() {
|
|
||||||
if(tcpSocket != -1) {
|
|
||||||
closesocket(tcpSocket);
|
|
||||||
tcpSocket = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Connect(const char* address, int port) {
|
|
||||||
tcpSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
||||||
if(tcpSocket == -1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
sockaddr_in clientSvc {};
|
|
||||||
clientSvc.sin_family = AF_INET;
|
|
||||||
inet_pton(AF_INET, address, &clientSvc.sin_addr.s_addr);
|
|
||||||
clientSvc.sin_port = htons(port);
|
|
||||||
|
|
||||||
if(connect(tcpSocket, (SOCKADDR*)&clientSvc, sizeof(clientSvc)) == SOCKET_ERROR) {
|
|
||||||
printf("No connection socket. Fuck you\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// disable Nagle's alrogithm
|
|
||||||
int nodelay = 1;
|
|
||||||
setsockopt(tcpSocket, IPPROTO_TCP, TCP_NODELAY, (const char*)&nodelay, sizeof(nodelay));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SendResize(const tResizeMessage& resize) {
|
|
||||||
tMessageHeader header;
|
|
||||||
header.type = MessageType::Resize;
|
|
||||||
header.datalen = sizeof(tResizeMessage);
|
|
||||||
|
|
||||||
send(tcpSocket, (const char*)&header, sizeof(header), 0);
|
|
||||||
send(tcpSocket, (const char*)&resize, sizeof(resize), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SendData(const u32* pData, u32 width, u32 height, const std::vector<tileRect>& tiles, bool sendFull) {
|
|
||||||
tMessageHeader header;
|
|
||||||
header.type = MessageType::Data;
|
|
||||||
// header.datalen = data.get_size() * sizeof(UINT32);
|
|
||||||
header.datalen = sizeof(tDataMessage);
|
|
||||||
|
|
||||||
// send tile header
|
|
||||||
// bool sendFull = false;
|
|
||||||
tDataMessage dm;
|
|
||||||
|
|
||||||
// if(tiles.empty()) {
|
|
||||||
// sendFull = true;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if(sendFull) {
|
|
||||||
dm.tileCount = 1;
|
|
||||||
} else {
|
|
||||||
dm.tileCount = (u32)tiles.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*if(tiles.empty()) {
|
|
||||||
// Send the full screen as a "tile"
|
|
||||||
dm.tileCount = 1;
|
|
||||||
sendFull = true;
|
|
||||||
} else {
|
|
||||||
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// we have writev() at home
|
|
||||||
// writev() at home:
|
|
||||||
send(tcpSocket, (const char*)&header, sizeof(header), 0);
|
|
||||||
send(tcpSocket, (const char*)&dm, sizeof(dm), 0);
|
|
||||||
|
|
||||||
// send each tile
|
|
||||||
if(!sendFull) {
|
|
||||||
for(auto& tile : tiles) {
|
|
||||||
tTile tile_wire { tile.x, tile.y, tile.width, tile.height };
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
std::vector<u32> tileData;
|
|
||||||
tileData.resize(tile.width * tile.height);
|
|
||||||
|
|
||||||
for(u32 y = 0; y < tile.height; ++y) {
|
|
||||||
auto* pTileLineStart = &pData[(tile.y + y) * width + tile.x];
|
|
||||||
memcpy(&tileData[y * tile.width], pTileLineStart, tile.width * sizeof(u32));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// send header
|
|
||||||
send(tcpSocket, (const char*)&tile_wire, (i32)sizeof(tile_wire), 0);
|
|
||||||
// send(tcpSocket, (const char*)&tileData[0], (i32)tileData.size() * 4, 0);
|
|
||||||
|
|
||||||
// send data now
|
|
||||||
/*for (auto y = tile.y; y < tile.y + tile.height; ++y) {
|
|
||||||
auto* pTileLineStart = &pData[y * width + tile.x];
|
|
||||||
send(tcpSocket, (const char*)pTileLineStart, tile.width * sizeof(UINT32), 0);
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tTile tDummyTile { 0, 0, width, height };
|
|
||||||
send(tcpSocket, (const char*)&tDummyTile, sizeof(tDummyTile), 0);
|
|
||||||
// send(tcpSocket, (const char*)&pData[0], (i32)((width * height) * sizeof(u32)), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
send(tcpSocket, (const char*)&pData[0], (i32)((width * height) * sizeof(u32)), 0);
|
|
||||||
// send(tcpSocket, (const char*)&data.data()[0], data.get_size() * sizeof(UINT32), 0);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
WSADATA data;
|
hazelnut::IvshmemDevice dev;
|
||||||
if(WSAStartup(MAKEWORD(2, 2), &data) != NO_ERROR) {
|
|
||||||
|
if(!dev.Open()) {
|
||||||
|
printf("Failed to open ivshmem device\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
cStreamClient client;
|
auto size = dev.GetSize();
|
||||||
|
auto ptr = (u8*)dev.GetPointer();
|
||||||
|
|
||||||
if(!client.Connect("192.168.1.149", 9438)) {
|
printf("opened a %zu MB large ivshmem\n", (size / (1024 * 1024)));
|
||||||
printf("conn failed\n");
|
|
||||||
|
// wipe the first 1mb
|
||||||
|
memset(&ptr[0], 0, 1 * (1024 * 1024));
|
||||||
|
|
||||||
|
printf("wiped memory\n");
|
||||||
|
|
||||||
|
// sex
|
||||||
|
auto* pHeader = new(&ptr[0]) hazelnut::IvshHeader {};
|
||||||
|
auto* pFrameHeader = new(&ptr[0x1000]) hazelnut::FrameHeader {};
|
||||||
|
|
||||||
|
// reset pingpong counter
|
||||||
|
|
||||||
|
pHeader->serverSessionId.store(rand());
|
||||||
|
|
||||||
|
// Create a capture interface
|
||||||
|
auto capture = hazelnut::CreateDisplayCapture(hazelnut::GuessBestCaptureInterface());
|
||||||
|
if(!capture) {
|
||||||
|
printf("Failed to create a capture interface\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printf("Successfully created a framebuffer capture interface\n");
|
||||||
|
|
||||||
|
bool firstFrame = true;
|
||||||
|
std::vector<tileRect> tiles {};
|
||||||
|
hazelnut::FramebufferInformation framebuffer {};
|
||||||
|
hazelnut::DiffInformation diff {};
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
auto result = capture->CaptureFrame();
|
||||||
|
if(result == hazelnut::DisplayCaptureResult::Ok) {
|
||||||
|
tiles.clear();
|
||||||
|
#if 0
|
||||||
|
if(firstFrame == false) {
|
||||||
|
for(u32 y = 0; y < diff.diffMapHeight; ++y) {
|
||||||
|
for(u32 x = 0; x < diff.diffmapWidth; ++x) {
|
||||||
|
auto& bl = diff.pDiffMap[y * diff.diffmapWidth + x];
|
||||||
|
if(bl != 0) {
|
||||||
|
tiles.push_back(tileRect {
|
||||||
|
x * (framebuffer.width / diff.diffmapWidth), // x
|
||||||
|
y * (framebuffer.height / diff.diffMapHeight), // y
|
||||||
|
framebuffer.width / diff.diffmapWidth, // width
|
||||||
|
framebuffer.height / diff.diffMapHeight // height
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tiles.empty())
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
{
|
||||||
|
auto guard = pHeader->lock.lock();
|
||||||
|
|
||||||
|
pFrameHeader->serial.fetch_add(1);
|
||||||
|
pFrameHeader->width.store(framebuffer.width);
|
||||||
|
pFrameHeader->height.store(framebuffer.height);
|
||||||
|
|
||||||
|
if(framebuffer.pFramebuffer == nullptr)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
memcpy(pFrameHeader->bits(), &framebuffer.pFramebuffer[0], (framebuffer.width * framebuffer.height) * 4);
|
||||||
|
|
||||||
|
//printf("FRAME SERIAL %u loaded\n", pFrameHeader->serial.load());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(firstFrame)
|
||||||
|
firstFrame = false;
|
||||||
|
} else if(result == hazelnut::DisplayCaptureResult::OkButResized) {
|
||||||
|
// We resized. Notify of that
|
||||||
|
framebuffer = capture->GetFramebufferInformation();
|
||||||
|
diff = capture->GetDiffInformation();
|
||||||
|
firstFrame = true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
printf("Failed to capture\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
while(true) {
|
||||||
|
// lock
|
||||||
|
{
|
||||||
|
auto guard = pHeader->lk.lock();
|
||||||
|
Sleep(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
printf("pingpong %u\n", pHeader->pingPong.load());
|
||||||
|
|
||||||
|
if(tries++ == curTries) {
|
||||||
|
tries = 0;
|
||||||
|
pHeader->pingPong.fetch_add(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
#if 0
|
||||||
printf("Hazelnut agent\n");
|
printf("Hazelnut agent\n");
|
||||||
|
|
||||||
// Create a capture interface
|
// Create a capture interface
|
||||||
|
@ -216,8 +181,7 @@ int main(int argc, char** argv) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// send that to the server
|
|
||||||
client.SendData(framebuffer.pFramebuffer, framebuffer.width, framebuffer.height, tiles, firstFrame == false);
|
|
||||||
|
|
||||||
if(firstFrame)
|
if(firstFrame)
|
||||||
firstFrame = false;
|
firstFrame = false;
|
||||||
|
@ -227,16 +191,12 @@ int main(int argc, char** argv) {
|
||||||
diff = capture->GetDiffInformation();
|
diff = capture->GetDiffInformation();
|
||||||
firstFrame = true;
|
firstFrame = true;
|
||||||
|
|
||||||
client.SendResize({ framebuffer.width, framebuffer.height });
|
|
||||||
|
|
||||||
// send empty frame
|
|
||||||
tiles.clear();
|
|
||||||
client.SendData(framebuffer.pFramebuffer, framebuffer.width, framebuffer.height, tiles, true);
|
|
||||||
} else {
|
} else {
|
||||||
printf("Failed to capture\n");
|
printf("Failed to capture\n");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "nvfbc_library.hpp"
|
#include "nvfbc_library.hpp"
|
||||||
|
|
||||||
#include "NvFBC/nvFBCToSys.h"
|
#include "NvFBC/nvFBCToSys.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -29,7 +30,7 @@ namespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
// pBuf must be valid from [0..MAX_PATH]
|
// pBuf must be valid from [0..MAX_PATH]
|
||||||
void GetNvFBCDefaultPath(char* pBuf) {
|
bool GetNvFBCLibraryPath(char* pBuf) {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
constexpr const char kPathFormatString[] =
|
constexpr const char kPathFormatString[] =
|
||||||
#ifdef _WIN64
|
#ifdef _WIN64
|
||||||
|
@ -41,83 +42,88 @@ namespace {
|
||||||
#endif
|
#endif
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
|
if(pBuf == nullptr) [[unlikely]]
|
||||||
|
return false;
|
||||||
|
|
||||||
// Get the Windows system directory (there's probably a "nicer" way using
|
// Get the Windows system directory (there's probably a "nicer" way using
|
||||||
// the SH* APIs but, this was previously nastier and using %SystemRoot%. I think this is OK)
|
// the SH* APIs but, this was previously nastier and using %SystemRoot%. I think this is OK)
|
||||||
char szWinDir[MAX_PATH] {};
|
char szWinDir[MAX_PATH] {};
|
||||||
if(GetWindowsDirectoryA(&szWinDir[0], MAX_PATH) == 0) {
|
if(GetWindowsDirectoryA(&szWinDir[0], MAX_PATH) == 0) {
|
||||||
strcpy(&pBuf[0], "its fucked");
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sprintf(&pBuf[0], kPathFormatString, szWinDir);
|
sprintf(&pBuf[0], kPathFormatString, szWinDir);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
NvFBCLibrary::~NvFBCLibrary() {
|
NvFBCLibrary::~NvFBCLibrary() {
|
||||||
if(m_handle != nullptr)
|
if(m_handle != nullptr)
|
||||||
close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NvFBCLibrary::load() {
|
bool NvFBCLibrary::Load() {
|
||||||
if(m_handle != nullptr)
|
if(m_handle != nullptr)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
char szDefaultPath[MAX_PATH] {};
|
char szNvfbcLibPath[MAX_PATH] {};
|
||||||
GetNvFBCDefaultPath(&szDefaultPath[0]);
|
|
||||||
|
|
||||||
m_handle = LoadLibraryA(szDefaultPath);
|
if(!GetNvFBCLibraryPath(&szNvfbcLibPath[0]))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_handle = LoadLibraryA(szNvfbcLibPath);
|
||||||
|
|
||||||
if(m_handle == nullptr) {
|
if(m_handle == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load NvFBC exports
|
// Load NvFBC exports
|
||||||
pfn_create = reinterpret_cast<NvFBC_CreateFunctionExType>(GetProcAddress(m_handle, "NvFBC_CreateEx"));
|
pNvFBC_CreateEx = reinterpret_cast<NvFBC_CreateFunctionExType>(GetProcAddress(m_handle, "NvFBC_CreateEx"));
|
||||||
pfn_set_global_flags = reinterpret_cast<NvFBC_SetGlobalFlagsType>(GetProcAddress(m_handle, "NvFBC_SetGlobalFlags"));
|
pNvFBC_SetGlobalFlags = reinterpret_cast<NvFBC_SetGlobalFlagsType>(GetProcAddress(m_handle, "NvFBC_SetGlobalFlags"));
|
||||||
pfn_get_status = reinterpret_cast<NvFBC_GetStatusExFunctionType>(GetProcAddress(m_handle, "NvFBC_GetStatusEx"));
|
pNvFBC_GetStatusEx = reinterpret_cast<NvFBC_GetStatusExFunctionType>(GetProcAddress(m_handle, "NvFBC_GetStatusEx"));
|
||||||
pfn_enable = reinterpret_cast<NvFBC_EnableFunctionType>(GetProcAddress(m_handle, "NvFBC_Enable"));
|
pNvFBC_Enable = reinterpret_cast<NvFBC_EnableFunctionType>(GetProcAddress(m_handle, "NvFBC_Enable"));
|
||||||
|
|
||||||
if((pfn_create == nullptr) || (pfn_set_global_flags == nullptr) || (pfn_get_status == nullptr) || (pfn_enable == nullptr)) {
|
if((pNvFBC_CreateEx == nullptr) || (pNvFBC_SetGlobalFlags == nullptr) || (pNvFBC_GetStatusEx == nullptr) || (pNvFBC_Enable == nullptr)) {
|
||||||
close();
|
Close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NvFBCLibrary::close() {
|
void NvFBCLibrary::Close() {
|
||||||
if(m_handle != nullptr)
|
if(m_handle != nullptr)
|
||||||
FreeLibrary(m_handle);
|
FreeLibrary(m_handle);
|
||||||
|
|
||||||
m_handle = NULL;
|
m_handle = NULL;
|
||||||
pfn_create = NULL;
|
pNvFBC_CreateEx = NULL;
|
||||||
pfn_get_status = NULL;
|
pNvFBC_GetStatusEx = NULL;
|
||||||
pfn_enable = NULL;
|
pNvFBC_Enable = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
NVFBCRESULT NvFBCLibrary::getStatus(NvFBCStatusEx* status) {
|
NVFBCRESULT NvFBCLibrary::GetStatus(NvFBCStatusEx* status) {
|
||||||
return pfn_get_status((void*)status);
|
return pNvFBC_GetStatusEx((void*)status);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NvFBCLibrary::setGlobalFlags(DWORD flags, int adapter) {
|
void NvFBCLibrary::SetGlobalFlags(DWORD flags, int adapter) {
|
||||||
setTargetAdapter(adapter);
|
SetTargetAdapter(adapter);
|
||||||
pfn_set_global_flags(flags);
|
pNvFBC_SetGlobalFlags(flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
NVFBCRESULT NvFBCLibrary::createEx(NvFBCCreateParams* pParams) {
|
NVFBCRESULT NvFBCLibrary::CreateEx(NvFBCCreateParams* pParams) {
|
||||||
return pfn_create((void*)pParams);
|
return pNvFBC_CreateEx((void*)pParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
void* NvFBCLibrary::create(DWORD type, DWORD* maxWidth, DWORD* maxHeight, int adapter, void* devicePtr) {
|
void* NvFBCLibrary::CreateRaw(DWORD type, DWORD* maxWidth, DWORD* maxHeight, int adapter, void* devicePtr) {
|
||||||
if(NULL == m_handle)
|
if(NULL == m_handle)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
NVFBCRESULT res = NVFBC_SUCCESS;
|
NVFBCRESULT res = NVFBC_SUCCESS;
|
||||||
NvFBCStatusEx status{};
|
NvFBCStatusEx status {};
|
||||||
status.dwVersion = NVFBC_STATUS_VER;
|
status.dwVersion = NVFBC_STATUS_VER;
|
||||||
status.dwAdapterIdx = adapter;
|
status.dwAdapterIdx = adapter;
|
||||||
res = getStatus(&status);
|
res = GetStatus(&status);
|
||||||
|
|
||||||
if(res != NVFBC_SUCCESS) {
|
if(res != NVFBC_SUCCESS) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -131,31 +137,34 @@ void* NvFBCLibrary::create(DWORD type, DWORD* maxWidth, DWORD* maxHeight, int ad
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
NvFBCCreateParams createParams{};
|
NvFBCCreateParams createParams {};
|
||||||
|
|
||||||
createParams.dwVersion = NVFBC_CREATE_PARAMS_VER;
|
createParams.dwVersion = NVFBC_CREATE_PARAMS_VER;
|
||||||
createParams.dwInterfaceType = type;
|
createParams.dwInterfaceType = type;
|
||||||
createParams.pDevice = devicePtr;
|
createParams.pDevice = devicePtr;
|
||||||
createParams.dwAdapterIdx = adapter;
|
createParams.dwAdapterIdx = adapter;
|
||||||
|
|
||||||
res = pfn_create(&createParams);
|
res = pNvFBC_CreateEx(&createParams);
|
||||||
|
|
||||||
*maxWidth = createParams.dwMaxDisplayWidth;
|
if(maxWidth)
|
||||||
*maxHeight = createParams.dwMaxDisplayHeight;
|
*maxWidth = createParams.dwMaxDisplayWidth;
|
||||||
|
|
||||||
|
if(maxHeight)
|
||||||
|
*maxHeight = createParams.dwMaxDisplayHeight;
|
||||||
|
|
||||||
return createParams.pNvFBC;
|
return createParams.pNvFBC;
|
||||||
}
|
}
|
||||||
|
|
||||||
INvFBCToSys_v4* NvFBCLibrary::CreateToSys(DWORD* maxWidth, DWORD* maxHeight, int adapter) {
|
INvFBCToSys_v4* NvFBCLibrary::CreateToSys(DWORD* maxWidth, DWORD* maxHeight, int adapter) {
|
||||||
return static_cast<NvFBCToSys*>(create(NVFBC_TO_SYS, maxWidth, maxHeight, adapter, nullptr));
|
return static_cast<NvFBCToSys*>(CreateRaw(NVFBC_TO_SYS, maxWidth, maxHeight, adapter, nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
NVFBCRESULT NvFBCLibrary::enable(NVFBC_STATE nvFBCState) {
|
NVFBCRESULT NvFBCLibrary::Enable(NVFBC_STATE nvFBCState) {
|
||||||
return pfn_enable(nvFBCState);
|
return pNvFBC_Enable(nvFBCState);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NvFBCLibrary::setTargetAdapter(int adapter) {
|
void NvFBCLibrary::SetTargetAdapter(int adapter) {
|
||||||
char targetAdapter[10]{};
|
char targetAdapter[10] {};
|
||||||
_snprintf_s(targetAdapter, 10, 9, "%d", adapter);
|
_snprintf_s(targetAdapter, 10, 9, "%d", adapter);
|
||||||
SetEnvironmentVariableA("NVFBC_TARGET_ADAPTER", targetAdapter);
|
SetEnvironmentVariableA("NVFBC_TARGET_ADAPTER", targetAdapter);
|
||||||
}
|
}
|
|
@ -18,37 +18,38 @@ class NvFBCLibrary {
|
||||||
~NvFBCLibrary();
|
~NvFBCLibrary();
|
||||||
|
|
||||||
/// Tries to load the NvFBC dll
|
/// Tries to load the NvFBC dll
|
||||||
bool load();
|
bool Load();
|
||||||
|
|
||||||
/// Close the NvFBC DLL.
|
/// Close the NvFBC DLL.
|
||||||
void close();
|
void Close();
|
||||||
|
|
||||||
/// Get the status for the provided adapter,
|
/// Get the status for the provided adapter,
|
||||||
/// if no adapter is provided the default adapter is used.
|
/// if no adapter is provided the default adapter is used.
|
||||||
NVFBCRESULT getStatus(NvFBCStatusEx* status);
|
NVFBCRESULT GetStatus(NvFBCStatusEx* status);
|
||||||
|
|
||||||
/// Sets the global flags for the provided adapter.
|
/// Sets the global flags for the provided adapter.
|
||||||
/// If no adapter is provided, the default adapter is used
|
/// If no adapter is provided, the default adapter is used
|
||||||
void setGlobalFlags(DWORD flags, int adapter = 0);
|
void SetGlobalFlags(DWORD flags, int adapter = 0);
|
||||||
|
|
||||||
/// Creates an instance of the provided NvFBC type if possible
|
/// Creates an instance of the provided NvFBC type if possible
|
||||||
NVFBCRESULT createEx(NvFBCCreateParams* pParams);
|
NVFBCRESULT CreateEx(NvFBCCreateParams* pParams);
|
||||||
|
|
||||||
/// Creates an instance of the provided NvFBC type if possible.
|
|
||||||
void* create(DWORD type, DWORD* maxWidth, DWORD* maxHeight, int adapter = 0, void* devicePtr = NULL);
|
|
||||||
|
|
||||||
INvFBCToSys_v4* CreateToSys(DWORD* maxWidth, DWORD* maxHeight, int adapter = 0);
|
INvFBCToSys_v4* CreateToSys(DWORD* maxWidth, DWORD* maxHeight, int adapter = 0);
|
||||||
|
|
||||||
/// enable/disable NVFBC
|
/// enable/disable NVFBC
|
||||||
NVFBCRESULT enable(NVFBC_STATE nvFBCState);
|
NVFBCRESULT Enable(NVFBC_STATE nvFBCState);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void setTargetAdapter(int adapter = 0);
|
void SetTargetAdapter(int adapter = 0);
|
||||||
|
|
||||||
|
/// Creates an instance of the provided NvFBC type if possible.
|
||||||
|
void* CreateRaw(DWORD type, DWORD* maxWidth, DWORD* maxHeight, int adapter = 0, void* devicePtr = NULL);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
HMODULE m_handle { nullptr };
|
HMODULE m_handle { nullptr };
|
||||||
NvFBC_GetStatusExFunctionType pfn_get_status { nullptr };
|
NvFBC_GetStatusExFunctionType pNvFBC_GetStatusEx { nullptr };
|
||||||
NvFBC_SetGlobalFlagsType pfn_set_global_flags { nullptr };
|
NvFBC_SetGlobalFlagsType pNvFBC_SetGlobalFlags { nullptr };
|
||||||
NvFBC_CreateFunctionExType pfn_create { nullptr };
|
NvFBC_CreateFunctionExType pNvFBC_CreateEx { nullptr };
|
||||||
NvFBC_EnableFunctionType pfn_enable { nullptr };
|
NvFBC_EnableFunctionType pNvFBC_Enable { nullptr };
|
||||||
};
|
};
|
||||||
|
|
14
build.rs
Normal file
14
build.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
use cc;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut build = cc::Build::new();
|
||||||
|
|
||||||
|
build
|
||||||
|
.emit_rerun_if_env_changed(true)
|
||||||
|
.cpp(true)
|
||||||
|
.std("c++20")
|
||||||
|
.include("shared/src")
|
||||||
|
.file("shared/src/ivshmem.cpp")
|
||||||
|
.file("src/rust_wrapper.cpp")
|
||||||
|
.compile("rust_ivshmem_bare");
|
||||||
|
}
|
3
shared/README.md
Normal file
3
shared/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# shared code
|
||||||
|
|
||||||
|
This is shared between the two ends. It is cross platform C++20.
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
// common types
|
// common types
|
||||||
using u8 = std::uint8_t;
|
using u8 = std::uint8_t;
|
49
shared/src/atomic_spinlock.hpp
Normal file
49
shared/src/atomic_spinlock.hpp
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstdio> //debugging
|
||||||
|
|
||||||
|
#include "Utils.hpp"
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
|
||||||
|
namespace hazelnut {
|
||||||
|
|
||||||
|
/// A atomic spinlock. Can be held across threads, or virtual machines.
|
||||||
|
struct AtomicSpinlock {
|
||||||
|
struct LockGuard {
|
||||||
|
~LockGuard() { lock.unlock(); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
friend AtomicSpinlock;
|
||||||
|
LockGuard(AtomicSpinlock& lock) : lock(lock) {}
|
||||||
|
AtomicSpinlock& lock;
|
||||||
|
};
|
||||||
|
|
||||||
|
AtomicSpinlock() { __lock.store(0, std::memory_order::seq_cst); }
|
||||||
|
|
||||||
|
LockGuard lock() {
|
||||||
|
lock_manually();
|
||||||
|
return LockGuard(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void lock_manually() {
|
||||||
|
u32 expected = 0;
|
||||||
|
while(!__lock.compare_exchange_strong(expected, 1, std::memory_order::seq_cst)) {
|
||||||
|
//printf("LOCK CONTENDED\n");
|
||||||
|
expected = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool try_lock_manually() {
|
||||||
|
u32 expected = 0;
|
||||||
|
return __lock.compare_exchange_strong(expected, 1, std::memory_order::seq_cst);
|
||||||
|
}
|
||||||
|
|
||||||
|
void unlock() { __lock.store(0, std::memory_order::seq_cst); }
|
||||||
|
|
||||||
|
std::atomic_uint32_t __lock;
|
||||||
|
};
|
||||||
|
} // namespace hazelnut
|
||||||
|
|
||||||
|
#pragma pack(pop)
|
3
shared/src/build_test.sh
Executable file
3
shared/src/build_test.sh
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -xeuo pipefail
|
||||||
|
g++ -O3 -std=c++20 ivshmem.cpp test.cpp -o test
|
40
shared/src/ivshmem.cpp
Normal file
40
shared/src/ivshmem.cpp
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
#include "ivshmem.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include "ivshmem_windows.cpp"
|
||||||
|
#elif __linux__
|
||||||
|
#include "ivshmem_linux.cpp"
|
||||||
|
#else
|
||||||
|
#error YOU fucked
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace hazelnut {
|
||||||
|
|
||||||
|
IvshmemDevice::IvshmemDevice() {
|
||||||
|
pImpl = new Impl;
|
||||||
|
}
|
||||||
|
|
||||||
|
IvshmemDevice::~IvshmemDevice() {
|
||||||
|
Close();
|
||||||
|
delete pImpl;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IvshmemDevice::Open(const char* devName) {
|
||||||
|
return pImpl->Open(devName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IvshmemDevice::Close() {
|
||||||
|
pImpl->Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void* IvshmemDevice::GetPointer() {
|
||||||
|
return pImpl->GetPointer();
|
||||||
|
}
|
||||||
|
|
||||||
|
usize IvshmemDevice::GetSize() {
|
||||||
|
return pImpl->GetSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace hazelnut
|
31
shared/src/ivshmem.hpp
Normal file
31
shared/src/ivshmem.hpp
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Utils.hpp"
|
||||||
|
|
||||||
|
namespace hazelnut {
|
||||||
|
|
||||||
|
/// Cross-platform IVSHMEM.
|
||||||
|
struct IvshmemDevice {
|
||||||
|
IvshmemDevice();
|
||||||
|
|
||||||
|
// forbid copy/move
|
||||||
|
IvshmemDevice(const IvshmemDevice&) = delete;
|
||||||
|
IvshmemDevice(IvshmemDevice&&) = delete;
|
||||||
|
|
||||||
|
~IvshmemDevice();
|
||||||
|
|
||||||
|
/// Opens the device. On Linux [devName] MUST point to a SHMEM file.
|
||||||
|
bool Open(const char* devName = nullptr);
|
||||||
|
|
||||||
|
void Close();
|
||||||
|
|
||||||
|
/// Gets pointer to IVSHMEM memory.
|
||||||
|
void* GetPointer();
|
||||||
|
|
||||||
|
usize GetSize();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Impl;
|
||||||
|
Impl* pImpl {};
|
||||||
|
};
|
||||||
|
} // namespace hazelnut
|
82
shared/src/ivshmem_linux.cpp
Normal file
82
shared/src/ivshmem_linux.cpp
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "Utils.hpp"
|
||||||
|
|
||||||
|
namespace hazelnut {
|
||||||
|
|
||||||
|
struct IvshmemDevice::Impl {
|
||||||
|
int fd;
|
||||||
|
void* pMap = nullptr;
|
||||||
|
usize size = 0;
|
||||||
|
|
||||||
|
bool OpenDevice(const char* devName) {
|
||||||
|
// devName is not optional on Linux.
|
||||||
|
if(devName == nullptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int devFd = open(devName, O_RDWR, static_cast<mode_t>(0600));
|
||||||
|
|
||||||
|
if(devFd == -1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
fd = devFd;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MapMemory() {
|
||||||
|
// Device was opened, let's map memory
|
||||||
|
|
||||||
|
struct stat st {};
|
||||||
|
if(fstat(fd, &st) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
usize devSize = st.st_size;
|
||||||
|
|
||||||
|
void* map = mmap(0, devSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||||
|
if(map == MAP_FAILED) {
|
||||||
|
printf("map failed\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pMap = map;
|
||||||
|
size = devSize;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnmapMemory() {
|
||||||
|
// Unmap memory.
|
||||||
|
if(pMap)
|
||||||
|
munmap(pMap, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Open(const char* pDevName) {
|
||||||
|
if(!OpenDevice(pDevName))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(!MapMemory()) {
|
||||||
|
Close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
void Close() {
|
||||||
|
UnmapMemory();
|
||||||
|
close(fd);
|
||||||
|
fd = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* GetPointer() { return pMap; }
|
||||||
|
|
||||||
|
usize GetSize() { return size; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace hazelnut
|
42
shared/src/ivshmem_protocol.hpp
Normal file
42
shared/src/ivshmem_protocol.hpp
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
#pragma once
|
||||||
|
#include <atomic>
|
||||||
|
#include "atomic_spinlock.hpp"
|
||||||
|
|
||||||
|
namespace hazelnut {
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
|
||||||
|
|
||||||
|
/// Header for Hazelnut ivshmem. At 0x0 in the ivshmem memory space.
|
||||||
|
struct IvshHeader {
|
||||||
|
std::atomic<u32> magic;
|
||||||
|
|
||||||
|
std::atomic<u32> serverSessionId{};
|
||||||
|
|
||||||
|
// When this lock is held by the host, the host can read the rest of memory freely.
|
||||||
|
// We wait for the host to release before updating memory ourselves.
|
||||||
|
AtomicSpinlock lock;
|
||||||
|
|
||||||
|
// Immediately after is FrameHeader
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Stored at a page boundary
|
||||||
|
struct FrameHeader {
|
||||||
|
/// The serial of the frame. If this is unchanged between a
|
||||||
|
/// lock-grab-unlock cycle, the frame has not changed.
|
||||||
|
std::atomic<u32> serial{};
|
||||||
|
|
||||||
|
// size
|
||||||
|
std::atomic<u32> width{};
|
||||||
|
std::atomic<u32> height{};
|
||||||
|
|
||||||
|
// obtain bits at the next page boundary
|
||||||
|
u32* bits() {
|
||||||
|
return reinterpret_cast<u32*>(reinterpret_cast<u8*>(this) + 0x1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
}
|
168
shared/src/ivshmem_windows.cpp
Normal file
168
shared/src/ivshmem_windows.cpp
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
#include <windows.h>
|
||||||
|
#include <setupapi.h>
|
||||||
|
|
||||||
|
// IVSHMEM ioctls
|
||||||
|
#include <virtio/ivshmem.h>
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
|
||||||
|
namespace hazelnut {
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct free_deleter {
|
||||||
|
void operator()(T* ptr) {
|
||||||
|
if(ptr)
|
||||||
|
free(ptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// DOES NOT UNBLESS WITH ->~T() !!!
|
||||||
|
template <class T>
|
||||||
|
using unique_malloc_ptr = std::unique_ptr<T, free_deleter<T>>;
|
||||||
|
|
||||||
|
// does not bless with placement new
|
||||||
|
template <class T>
|
||||||
|
unique_malloc_ptr<T> unique_malloc(usize len) {
|
||||||
|
return { reinterpret_cast<T*>(malloc(len)), free_deleter<T> {} };
|
||||||
|
}
|
||||||
|
|
||||||
|
struct scoped_devinfo {
|
||||||
|
scoped_devinfo(HDEVINFO hDevInfo) : hDevInfo(hDevInfo) {}
|
||||||
|
|
||||||
|
~scoped_devinfo() { SetupDiDestroyDeviceInfoList(hDevInfo); }
|
||||||
|
|
||||||
|
scoped_devinfo(const scoped_devinfo&) = delete;
|
||||||
|
scoped_devinfo(scoped_devinfo&&) = delete;
|
||||||
|
|
||||||
|
operator HDEVINFO() { return hDevInfo; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
HDEVINFO hDevInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IvshmemDevice::Impl {
|
||||||
|
HANDLE hIvshmem { INVALID_HANDLE_VALUE };
|
||||||
|
IVSHMEM_MMAP ivshmemMmapStruct {};
|
||||||
|
|
||||||
|
bool OpenDevice() {
|
||||||
|
memset(&ivshmemMmapStruct, 0, sizeof(IVSHMEM_MMAP));
|
||||||
|
|
||||||
|
SP_DEVINFO_DATA devInfoData = {
|
||||||
|
.cbSize = sizeof(SP_DEVINFO_DATA),
|
||||||
|
};
|
||||||
|
|
||||||
|
SP_DEVICE_INTERFACE_DATA devInterfaceData { .cbSize = sizeof(SP_DEVICE_INTERFACE_DATA) };
|
||||||
|
|
||||||
|
auto hDevInfo =
|
||||||
|
scoped_devinfo { SetupDiGetClassDevsA(&GUID_DEVINTERFACE_IVSHMEM, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE) };
|
||||||
|
|
||||||
|
// just do the first one for now
|
||||||
|
SetupDiEnumDeviceInfo(hDevInfo, 0, &devInfoData);
|
||||||
|
|
||||||
|
if(auto le = GetLastError(); le != ERROR_SUCCESS && le != ERROR_NO_MORE_ITEMS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(SetupDiEnumDeviceInterfaces(hDevInfo, &devInfoData, &GUID_DEVINTERFACE_IVSHMEM, 0, &devInterfaceData) == FALSE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD dwReq = 0;
|
||||||
|
SetupDiGetDeviceInterfaceDetail(hDevInfo, &devInterfaceData, nullptr, 0, &dwReq, nullptr);
|
||||||
|
|
||||||
|
if(!dwReq) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pData = unique_malloc<SP_DEVICE_INTERFACE_DETAIL_DATA>(dwReq);
|
||||||
|
pData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
|
||||||
|
|
||||||
|
if(!SetupDiGetDeviceInterfaceDetail(hDevInfo, &devInterfaceData, pData.get(), dwReq, nullptr, nullptr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hIvshmem = CreateFile(pData->DevicePath, 0, 0, nullptr, OPEN_EXISTING, 0, nullptr);
|
||||||
|
|
||||||
|
if(hIvshmem == INVALID_HANDLE_VALUE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class TInput, class TOutput>
|
||||||
|
bool Ioctl(u32 ctlCode, TInput& in, TOutput& out) {
|
||||||
|
// Needed because some versions of the IVSHMEM driver, notably the last one
|
||||||
|
// that works on Windows 7, assumes return length is not nullptr, and will crash
|
||||||
|
// the calling process if it is nullptr. Red Hat quality code everyone; it doesn't
|
||||||
|
// just envelope the Linux ecosystem - it even gets to take over the Windows one too!
|
||||||
|
DWORD dwDummyRetLength = 0;
|
||||||
|
|
||||||
|
if(!DeviceIoControl(hIvshmem, ctlCode, reinterpret_cast<void*>(&in), sizeof(TInput), reinterpret_cast<void*>(&out), sizeof(TOutput),
|
||||||
|
&dwDummyRetLength, nullptr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ioctl() overload for bare
|
||||||
|
bool Ioctl(u32 ctlCode) {
|
||||||
|
DWORD dwDummyRetLength = 0;
|
||||||
|
if(!DeviceIoControl(hIvshmem, ctlCode, nullptr, 0, nullptr, 0, &dwDummyRetLength, nullptr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MapMemory() {
|
||||||
|
// Device was opened, let's map memory
|
||||||
|
|
||||||
|
DWORD dwDummyRetLength = 0;
|
||||||
|
|
||||||
|
// Yay, one less syscall.
|
||||||
|
IVSHMEM_MMAP_CONFIG config = { .cacheMode = IVSHMEM_CACHE_WRITECOMBINED };
|
||||||
|
return Ioctl(IOCTL_IVSHMEM_REQUEST_MMAP, config, this->ivshmemMmapStruct);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnmapMemory() {
|
||||||
|
// Unmap memory.
|
||||||
|
if(ivshmemMmapStruct.ptr) {
|
||||||
|
// We do not care if this fails since the device will eventually be closed and this is done only
|
||||||
|
// on agent exit, so the map will be released by the kernel anyhow.
|
||||||
|
static_cast<void>(Ioctl(IOCTL_IVSHMEM_RELEASE_MMAP));
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&ivshmemMmapStruct, 0, sizeof(IVSHMEM_MMAP));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Open(const char* pDevName) {
|
||||||
|
if(!OpenDevice())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(!MapMemory()) {
|
||||||
|
Close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
void Close() {
|
||||||
|
UnmapMemory();
|
||||||
|
CloseHandle(hIvshmem);
|
||||||
|
hIvshmem = INVALID_HANDLE_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* GetPointer() {
|
||||||
|
return ivshmemMmapStruct.ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
usize GetSize() {
|
||||||
|
return ivshmemMmapStruct.size;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace hazelnut
|
64
shared/src/test.cpp
Normal file
64
shared/src/test.cpp
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "atomic_spinlock.hpp"
|
||||||
|
#include "ivshmem.hpp"
|
||||||
|
|
||||||
|
#include "ivshmem_protocol.hpp"
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
|
||||||
|
|
||||||
|
hazelnut::IvshmemDevice dev;
|
||||||
|
|
||||||
|
if(!dev.Open("/dev/shm/lg-win7")) {
|
||||||
|
printf("Failed to open ivshmem device\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto size = dev.GetSize();
|
||||||
|
auto ptr = (u8*)dev.GetPointer();
|
||||||
|
|
||||||
|
printf("opened a %zu MB large ivshmem\n", (size / (1024 * 1024)));
|
||||||
|
|
||||||
|
auto* pHeader = (hazelnut::IvshHeader*)&ptr[0];;
|
||||||
|
auto* pFrameHeader = (hazelnut::FrameHeader*)&ptr[0x1000];
|
||||||
|
|
||||||
|
u64 initalizedSessionId = pHeader->serverSessionId.load();
|
||||||
|
u32 lastSerial = 0;
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
if(initalizedSessionId != pHeader->serverSessionId.load()) {
|
||||||
|
printf("Agent restarted. Closing\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// lock for a bit
|
||||||
|
{
|
||||||
|
if(pHeader->lock.try_lock_manually()) {
|
||||||
|
// failed to lock
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto current = pFrameHeader->serial.load();
|
||||||
|
|
||||||
|
if(current == lastSerial) {
|
||||||
|
pHeader->lock.unlock();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
lastSerial = current;
|
||||||
|
printf("Frame with serial %u. Width %ux%u\n", lastSerial, pFrameHeader->width.load(), pFrameHeader->height.load());
|
||||||
|
|
||||||
|
pHeader->lock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow the vm some time to do whatever it is it wants to do
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
89
src/hzclient.rs
Normal file
89
src/hzclient.rs
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
use std::{ffi, path::Path};
|
||||||
|
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum ResultCode {
|
||||||
|
Unchanged,
|
||||||
|
Changed,
|
||||||
|
Fail,
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
fn rust_new_hazelnut_client() -> *mut ffi::c_void;
|
||||||
|
fn rust_destroy_hazelnut_client(client: *mut ffi::c_void);
|
||||||
|
|
||||||
|
fn rust_hazelnut_client_open(client: *mut ffi::c_void, url: *const ffi::c_char) -> bool;
|
||||||
|
|
||||||
|
fn rust_hazelnut_client_tick(client: *mut ffi::c_void) -> ResultCode;
|
||||||
|
|
||||||
|
fn rust_hazelnut_client_lock(client: *mut ffi::c_void);
|
||||||
|
fn rust_hazelnut_client_unlock(client: *mut ffi::c_void);
|
||||||
|
|
||||||
|
fn rust_hazelnut_client_get_framebuffer(client: *mut ffi::c_void) -> *mut u32;
|
||||||
|
fn rust_hazelnut_client_get_width(client: *mut ffi::c_void) -> u32;
|
||||||
|
fn rust_hazelnut_client_get_height(client: *mut ffi::c_void) -> u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HazelnutClient(*mut std::ffi::c_void);
|
||||||
|
|
||||||
|
impl HazelnutClient {
|
||||||
|
pub fn new() -> HazelnutClient {
|
||||||
|
unsafe { Self(rust_new_hazelnut_client()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open(&mut self, path: String) -> bool {
|
||||||
|
let cstr = ffi::CString::new(path).expect("dumbass");
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
return rust_hazelnut_client_open(self.0, cstr.as_ptr());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick_one(&mut self) -> ResultCode {
|
||||||
|
unsafe {
|
||||||
|
return rust_hazelnut_client_tick(self.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lock(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
return rust_hazelnut_client_lock(self.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unlock(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
return rust_hazelnut_client_unlock(self.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// only use while lock is held!
|
||||||
|
pub fn dimensions(&mut self) -> (u32, u32) {
|
||||||
|
let tup = unsafe {
|
||||||
|
let width = rust_hazelnut_client_get_width(self.0);
|
||||||
|
let height = rust_hazelnut_client_get_height(self.0);
|
||||||
|
(width, height)
|
||||||
|
};
|
||||||
|
tup
|
||||||
|
}
|
||||||
|
|
||||||
|
/// only use while lock is held!
|
||||||
|
pub fn framebuffer(&mut self) -> &mut [u32] {
|
||||||
|
let sl = unsafe {
|
||||||
|
let fb_ptr = rust_hazelnut_client_get_framebuffer(self.0);
|
||||||
|
let dim = self.dimensions();
|
||||||
|
|
||||||
|
std::slice::from_raw_parts_mut(fb_ptr, ((dim.0 * dim.1) as usize * 4))
|
||||||
|
};
|
||||||
|
sl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for HazelnutClient {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
rust_destroy_hazelnut_client(self.0);
|
||||||
|
}
|
||||||
|
self.0 = std::ptr::null_mut();
|
||||||
|
}
|
||||||
|
}
|
149
src/main.rs
149
src/main.rs
|
@ -1,136 +1,57 @@
|
||||||
use std::{
|
|
||||||
io::Read,
|
|
||||||
net::{TcpListener, TcpStream},
|
|
||||||
};
|
|
||||||
|
|
||||||
use byteorder::{LittleEndian, ReadBytesExt};
|
|
||||||
use minifb::{Window, WindowOptions};
|
use minifb::{Window, WindowOptions};
|
||||||
|
|
||||||
#[derive(Debug)]
|
mod hzclient;
|
||||||
enum Message {
|
|
||||||
Resize { width: u32, height: u32 },
|
|
||||||
|
|
||||||
Data {},
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const MESSAGETYPE_RESIZE: u32 = 0;
|
|
||||||
pub const MESSAGETYPE_DATA: u32 = 1;
|
|
||||||
|
|
||||||
fn read_message(
|
|
||||||
stream: &mut TcpStream,
|
|
||||||
argb_buffer: &mut Vec<u32>,
|
|
||||||
width: u32,
|
|
||||||
height: u32,
|
|
||||||
) -> Option<Message> {
|
|
||||||
let message_type = stream.read_u32::<LittleEndian>().expect("fuck");
|
|
||||||
let message_len = stream.read_u32::<LittleEndian>().expect("fuck");
|
|
||||||
|
|
||||||
match message_type {
|
|
||||||
MESSAGETYPE_RESIZE => {
|
|
||||||
if message_len != 8 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let width = stream.read_u32::<LittleEndian>().expect("fuck");
|
|
||||||
let height = stream.read_u32::<LittleEndian>().expect("fuck");
|
|
||||||
Some(Message::Resize {
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
MESSAGETYPE_DATA => {
|
|
||||||
let tile_count = stream.read_u32::<LittleEndian>().expect("fuck");
|
|
||||||
|
|
||||||
//println!("{tile_count} tiles");
|
|
||||||
|
|
||||||
// tile data, painted directly onto the argb buffer. It's stupid
|
|
||||||
let argb_slice = unsafe {
|
|
||||||
std::slice::from_raw_parts_mut(
|
|
||||||
argb_buffer.as_mut_ptr() as *mut u8,
|
|
||||||
argb_buffer.len() * core::mem::size_of::<u32>(),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
for i in 0..tile_count {
|
|
||||||
// tile rect
|
|
||||||
let tile_x = stream.read_u32::<LittleEndian>().expect("fuck");
|
|
||||||
let tile_y = stream.read_u32::<LittleEndian>().expect("fuck");
|
|
||||||
let tile_width = stream.read_u32::<LittleEndian>().expect("fuck");
|
|
||||||
let tile_height = stream.read_u32::<LittleEndian>().expect("fuck");
|
|
||||||
|
|
||||||
//println!("tile{i}: {tile_x} {tile_y} {tile_width}x{tile_height}");
|
|
||||||
}
|
|
||||||
|
|
||||||
for y in 0..height {
|
|
||||||
//println!("{y} {tile_y} {tile_height}");
|
|
||||||
//for x in tile_
|
|
||||||
let dest_slice = &mut argb_slice
|
|
||||||
[((y * width) * 4) as usize..((y * width + (width)) * 4) as usize];
|
|
||||||
|
|
||||||
stream.read_exact(&mut dest_slice[..]).expect("FUCK");
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(Message::Data {})
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let listener = TcpListener::bind("192.168.1.149:9438").expect("fuck");
|
let mut screen_width: u32 = 320;
|
||||||
let (mut socket, client_addr) = listener.accept().expect("FUCK!");
|
let mut screen_height: u32 = 200;
|
||||||
|
|
||||||
// disable nagles garbage bullshit
|
|
||||||
socket.set_nodelay(true).expect("fuck tcp");
|
|
||||||
|
|
||||||
let mut window = Window::new("FbcServer", 320, 200, WindowOptions::default())
|
let mut window = Window::new("FbcServer", 320, 200, WindowOptions::default())
|
||||||
.expect("you banned forever: rules do not");
|
.expect("you banned forever: rules do not");
|
||||||
|
|
||||||
let mut argb_buffer: Vec<u32> = Vec::new();
|
let mut client = hzclient::HazelnutClient::new();
|
||||||
let mut screen_width: u32 = 320;
|
|
||||||
let mut screen_height: u32 = 200;
|
if !client.open("/dev/shm/lg-win7".into()) {
|
||||||
|
println!("FUCK");
|
||||||
|
} else {
|
||||||
|
println!("Opened ivshmem!!!");
|
||||||
|
}
|
||||||
|
|
||||||
|
//let mut argb_buffer: Vec<u32> = Vec::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if let Some(message) =
|
match client.tick_one() {
|
||||||
read_message(&mut socket, &mut argb_buffer, screen_width, screen_height)
|
hzclient::ResultCode::Fail => break,
|
||||||
{
|
hzclient::ResultCode::Changed => {
|
||||||
match message {
|
let dims = client.dimensions();
|
||||||
Message::Resize { width, height } => {
|
|
||||||
println!("read message {:?}", message);
|
if screen_width != dims.0 && screen_height != dims.1 {
|
||||||
screen_width = width;
|
screen_width = dims.0;
|
||||||
screen_height = height;
|
screen_height = dims.1;
|
||||||
|
|
||||||
window = Window::new(
|
window = Window::new(
|
||||||
"FbcServer",
|
"FbcServer",
|
||||||
width as usize,
|
screen_width as usize,
|
||||||
height as usize,
|
screen_height as usize,
|
||||||
WindowOptions::default(),
|
WindowOptions::default(),
|
||||||
)
|
)
|
||||||
.expect("you banned forever: rules do not");
|
.expect("you banned forever: rules do not");
|
||||||
|
}
|
||||||
|
|
||||||
argb_buffer.resize((screen_width * screen_height) as usize, 0);
|
window.update_with_buffer(
|
||||||
}
|
&client.framebuffer(),
|
||||||
Message::Data {} => {
|
screen_width as usize,
|
||||||
//println!("read DATA");
|
screen_height as usize,
|
||||||
window
|
).expect("well its done anyways");
|
||||||
.update_with_buffer(
|
|
||||||
&argb_buffer[..],
|
client.unlock();
|
||||||
screen_width as usize,
|
}
|
||||||
screen_height as usize,
|
hzclient::ResultCode::Unchanged => {
|
||||||
)
|
window.update();
|
||||||
.expect("Failed to update screen");
|
// Not needed, C++ unlocks us
|
||||||
}
|
//client.unlock();
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
println!("invalid message, termination.");
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//window.upd
|
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Hello, world!");
|
println!("Hello, world!");
|
||||||
|
|
130
src/rust_wrapper.cpp
Normal file
130
src/rust_wrapper.cpp
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
#include "ivshmem.hpp"
|
||||||
|
#include "ivshmem_protocol.hpp"
|
||||||
|
#include "Utils.hpp"
|
||||||
|
|
||||||
|
enum class ResultCode : u32 { Unchanged, Changed, Fail };
|
||||||
|
|
||||||
|
struct HazelnutIvshmemClient {
|
||||||
|
bool Open(const char* path) {
|
||||||
|
if(!ivshmemDevice.Open(path))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto* ptr = (u8*)ivshmemDevice.GetPointer();
|
||||||
|
|
||||||
|
pHeader = (hazelnut::IvshHeader*)&ptr[0];
|
||||||
|
pFrameHeader = (hazelnut::FrameHeader*)&ptr[0x1000];
|
||||||
|
|
||||||
|
serial = pHeader->serverSessionId.load();
|
||||||
|
lastFrameSerial = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS LEAVES THE LOCK HELD SO YOU CAN READ ON CHANGE!!!!
|
||||||
|
ResultCode TickOne() {
|
||||||
|
if(!pHeader)
|
||||||
|
return ResultCode::Fail;
|
||||||
|
|
||||||
|
if(serial != pHeader->serverSessionId.load())
|
||||||
|
return ResultCode::Fail;
|
||||||
|
|
||||||
|
if(pHeader->lock.try_lock_manually()) {
|
||||||
|
// failed to lock
|
||||||
|
return ResultCode::Unchanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto current = pFrameHeader->serial.load();
|
||||||
|
|
||||||
|
if(current == lastFrameSerial) {
|
||||||
|
pHeader->lock.unlock();
|
||||||
|
return ResultCode::Unchanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastFrameSerial = current;
|
||||||
|
// printf("Frame with serial %u. Width %ux%u\n", lastSerial, pFrameHeader->width.load(), pFrameHeader->height.load());
|
||||||
|
// pHeader->lock.unlock();
|
||||||
|
return ResultCode::Changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lock() { pHeader->lock.lock_manually(); }
|
||||||
|
|
||||||
|
void Unlock() { pHeader->lock.unlock(); }
|
||||||
|
|
||||||
|
u32* Framebuffer() {
|
||||||
|
if(pFrameHeader) {
|
||||||
|
return pFrameHeader->bits();
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
hazelnut::IvshmemDevice ivshmemDevice;
|
||||||
|
|
||||||
|
// Protocol/memory stuff
|
||||||
|
hazelnut::IvshHeader* pHeader;
|
||||||
|
hazelnut::FrameHeader* pFrameHeader;
|
||||||
|
|
||||||
|
u32 serial {};
|
||||||
|
|
||||||
|
u32 lastFrameSerial {};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Rust bindings
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
void* rust_new_hazelnut_client() {
|
||||||
|
return (void*)new HazelnutIvshmemClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rust_destroy_hazelnut_client(void* pClient) {
|
||||||
|
if(pClient)
|
||||||
|
delete(HazelnutIvshmemClient*)pClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rust_hazelnut_client_open(void* pClient, const char* pszPath) {
|
||||||
|
if(pClient) {
|
||||||
|
return ((HazelnutIvshmemClient*)pClient)->Open(pszPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rust_hazelnut_client_tick(void* pClient) {
|
||||||
|
if(pClient) {
|
||||||
|
return (int)((HazelnutIvshmemClient*)pClient)->TickOne();
|
||||||
|
}
|
||||||
|
return (int)ResultCode::Fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rust_hazelnut_client_lock(void* pClient) {
|
||||||
|
if(pClient) {
|
||||||
|
return ((HazelnutIvshmemClient*)pClient)->Lock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void rust_hazelnut_client_unlock(void* pClient) {
|
||||||
|
if(pClient) {
|
||||||
|
return ((HazelnutIvshmemClient*)pClient)->Unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u32* rust_hazelnut_client_get_framebuffer(void* pClient) {
|
||||||
|
if(pClient) {
|
||||||
|
return ((HazelnutIvshmemClient*)pClient)->Framebuffer();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 rust_hazelnut_client_get_width(void* pClient) {
|
||||||
|
if(pClient) {
|
||||||
|
return ((HazelnutIvshmemClient*)pClient)->pFrameHeader->width.load();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 rust_hazelnut_client_get_height(void* pClient) {
|
||||||
|
if(pClient) {
|
||||||
|
return ((HazelnutIvshmemClient*)pClient)->pFrameHeader->height.load();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue