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/obj
|
||||
/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"
|
||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.1"
|
||||
|
@ -82,7 +76,7 @@ checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4"
|
|||
name = "fbcserver"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"cc",
|
||||
"minifb",
|
||||
]
|
||||
|
||||
|
@ -204,9 +198,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.164"
|
||||
version = "0.2.167"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
|
||||
checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
|
|
|
@ -4,5 +4,7 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.5.0"
|
||||
minifb = "0.27.0"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0.99"
|
|
@ -9,6 +9,7 @@ OBJDIR = obj/$(ARCH)/$(CONFIG)
|
|||
VPATH = src/crt \
|
||||
src/base \
|
||||
src/ \
|
||||
../shared/src
|
||||
|
||||
# Objects for crt
|
||||
OBJS_STARTUP := $(OBJDIR)/crt0.o \
|
||||
|
@ -21,6 +22,7 @@ OBJS_BASELIB :=
|
|||
OBJS := $(OBJDIR)/capture.o \
|
||||
$(OBJDIR)/capture_nvfbc.o \
|
||||
$(OBJDIR)/nvfbc_library.o \
|
||||
$(OBJDIR)/ivshmem.o \
|
||||
$(OBJDIR)/main.o \
|
||||
|
||||
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)/user32.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
|
||||
|
||||
|
@ -40,6 +42,11 @@ publish: $(BINDIR)/$(NAME).exe
|
|||
cp -rv bin /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:
|
||||
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
|
||||
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_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() {
|
||||
NvfbcDestroyInstance();
|
||||
nvfbcLib.close();
|
||||
nvfbcLib.Close();
|
||||
}
|
||||
|
||||
bool Initialize() override {
|
||||
if(!nvfbcLib.load()) {
|
||||
if(!nvfbcLib.Load()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -133,7 +133,7 @@ namespace hazelnut {
|
|||
blockSize = NVFBC_TOSYS_DIFFMAP_BLOCKSIZE_32X32;
|
||||
|
||||
// set up a session
|
||||
NVFBC_TOSYS_SETUP_PARAMS fbcSysSetupParams{};
|
||||
NVFBC_TOSYS_SETUP_PARAMS fbcSysSetupParams {};
|
||||
fbcSysSetupParams.dwVersion = NVFBC_TOSYS_SETUP_PARAMS_VER;
|
||||
fbcSysSetupParams.eMode = NVFBC_TOSYS_ARGB;
|
||||
fbcSysSetupParams.bWithHWCursor = true;
|
||||
|
@ -197,8 +197,6 @@ namespace hazelnut {
|
|||
auto* pSrcData = (u8*)&pRawFramebuffer[0];
|
||||
|
||||
for(u32 y = 0; y < grabInfo.dwHeight; ++y) {
|
||||
|
||||
|
||||
// Convert to BGRA
|
||||
// FIXME: Make this SIMD. I can't into this very well
|
||||
#if 0
|
||||
|
@ -224,8 +222,8 @@ namespace hazelnut {
|
|||
FramebufferInformation GetFramebufferInformation() override { return FramebufferInformation { convertedFramebuffer.data(), width, height }; }
|
||||
|
||||
DiffInformation GetDiffInformation() override {
|
||||
//diffmapWidth = (u32)ceil((f32)width / 32);
|
||||
//diffmapHeight = (u32)ceil((f32)height / 32);
|
||||
// diffmapWidth = (u32)ceil((f32)width / 32);
|
||||
// diffmapHeight = (u32)ceil((f32)height / 32);
|
||||
|
||||
diffmapWidth = DiffMapDimension(width, blockSize);
|
||||
diffmapHeight = DiffMapDimension(height, blockSize);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// clang-format off
|
||||
#include "ivshmem.hpp"
|
||||
#pragma comment(lib, "ws2_32.lib")
|
||||
#include <WinSock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
|
@ -11,170 +12,134 @@
|
|||
#include "Utils.hpp"
|
||||
// clang-format on
|
||||
|
||||
#pragma pack(push, 1)
|
||||
enum class MessageType : u32 {
|
||||
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)
|
||||
#include "atomic_spinlock.hpp"
|
||||
#include "ivshmem_protocol.hpp"
|
||||
|
||||
struct tileRect {
|
||||
u32 x, y, width, height;
|
||||
};
|
||||
|
||||
// client for streamserver
|
||||
class cStreamClient {
|
||||
SOCKET tcpSocket;
|
||||
|
||||
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);
|
||||
}
|
||||
struct Test {
|
||||
hazelnut::AtomicSpinlock lk {};
|
||||
std::atomic<u32> sessionId {};
|
||||
std::atomic<u32> pingPong {};
|
||||
};
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
WSADATA data;
|
||||
if(WSAStartup(MAKEWORD(2, 2), &data) != NO_ERROR) {
|
||||
hazelnut::IvshmemDevice dev;
|
||||
|
||||
if(!dev.Open()) {
|
||||
printf("Failed to open ivshmem device\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
cStreamClient client;
|
||||
auto size = dev.GetSize();
|
||||
auto ptr = (u8*)dev.GetPointer();
|
||||
|
||||
if(!client.Connect("192.168.1.149", 9438)) {
|
||||
printf("conn failed\n");
|
||||
printf("opened a %zu MB large ivshmem\n", (size / (1024 * 1024)));
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
// Create a capture interface
|
||||
|
@ -216,8 +181,7 @@ int main(int argc, char** argv) {
|
|||
continue;
|
||||
}
|
||||
|
||||
// send that to the server
|
||||
client.SendData(framebuffer.pFramebuffer, framebuffer.width, framebuffer.height, tiles, firstFrame == false);
|
||||
|
||||
|
||||
if(firstFrame)
|
||||
firstFrame = false;
|
||||
|
@ -227,16 +191,12 @@ int main(int argc, char** argv) {
|
|||
diff = capture->GetDiffInformation();
|
||||
firstFrame = true;
|
||||
|
||||
client.SendResize({ framebuffer.width, framebuffer.height });
|
||||
|
||||
// send empty frame
|
||||
tiles.clear();
|
||||
client.SendData(framebuffer.pFramebuffer, framebuffer.width, framebuffer.height, tiles, true);
|
||||
} else {
|
||||
printf("Failed to capture\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "nvfbc_library.hpp"
|
||||
|
||||
#include "NvFBC/nvFBCToSys.h"
|
||||
|
||||
namespace {
|
||||
|
@ -29,7 +30,7 @@ namespace {
|
|||
}
|
||||
|
||||
// pBuf must be valid from [0..MAX_PATH]
|
||||
void GetNvFBCDefaultPath(char* pBuf) {
|
||||
bool GetNvFBCLibraryPath(char* pBuf) {
|
||||
// clang-format off
|
||||
constexpr const char kPathFormatString[] =
|
||||
#ifdef _WIN64
|
||||
|
@ -41,83 +42,88 @@ namespace {
|
|||
#endif
|
||||
// clang-format on
|
||||
|
||||
if(pBuf == nullptr) [[unlikely]]
|
||||
return false;
|
||||
|
||||
// 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)
|
||||
char szWinDir[MAX_PATH] {};
|
||||
if(GetWindowsDirectoryA(&szWinDir[0], MAX_PATH) == 0) {
|
||||
strcpy(&pBuf[0], "its fucked");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
sprintf(&pBuf[0], kPathFormatString, szWinDir);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
NvFBCLibrary::~NvFBCLibrary() {
|
||||
if(m_handle != nullptr)
|
||||
close();
|
||||
Close();
|
||||
}
|
||||
|
||||
bool NvFBCLibrary::load() {
|
||||
bool NvFBCLibrary::Load() {
|
||||
if(m_handle != nullptr)
|
||||
return true;
|
||||
|
||||
char szDefaultPath[MAX_PATH] {};
|
||||
GetNvFBCDefaultPath(&szDefaultPath[0]);
|
||||
char szNvfbcLibPath[MAX_PATH] {};
|
||||
|
||||
m_handle = LoadLibraryA(szDefaultPath);
|
||||
if(!GetNvFBCLibraryPath(&szNvfbcLibPath[0]))
|
||||
return false;
|
||||
|
||||
m_handle = LoadLibraryA(szNvfbcLibPath);
|
||||
|
||||
if(m_handle == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load NvFBC exports
|
||||
pfn_create = reinterpret_cast<NvFBC_CreateFunctionExType>(GetProcAddress(m_handle, "NvFBC_CreateEx"));
|
||||
pfn_set_global_flags = reinterpret_cast<NvFBC_SetGlobalFlagsType>(GetProcAddress(m_handle, "NvFBC_SetGlobalFlags"));
|
||||
pfn_get_status = reinterpret_cast<NvFBC_GetStatusExFunctionType>(GetProcAddress(m_handle, "NvFBC_GetStatusEx"));
|
||||
pfn_enable = reinterpret_cast<NvFBC_EnableFunctionType>(GetProcAddress(m_handle, "NvFBC_Enable"));
|
||||
pNvFBC_CreateEx = reinterpret_cast<NvFBC_CreateFunctionExType>(GetProcAddress(m_handle, "NvFBC_CreateEx"));
|
||||
pNvFBC_SetGlobalFlags = reinterpret_cast<NvFBC_SetGlobalFlagsType>(GetProcAddress(m_handle, "NvFBC_SetGlobalFlags"));
|
||||
pNvFBC_GetStatusEx = reinterpret_cast<NvFBC_GetStatusExFunctionType>(GetProcAddress(m_handle, "NvFBC_GetStatusEx"));
|
||||
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)) {
|
||||
close();
|
||||
if((pNvFBC_CreateEx == nullptr) || (pNvFBC_SetGlobalFlags == nullptr) || (pNvFBC_GetStatusEx == nullptr) || (pNvFBC_Enable == nullptr)) {
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void NvFBCLibrary::close() {
|
||||
void NvFBCLibrary::Close() {
|
||||
if(m_handle != nullptr)
|
||||
FreeLibrary(m_handle);
|
||||
|
||||
m_handle = NULL;
|
||||
pfn_create = NULL;
|
||||
pfn_get_status = NULL;
|
||||
pfn_enable = NULL;
|
||||
pNvFBC_CreateEx = NULL;
|
||||
pNvFBC_GetStatusEx = NULL;
|
||||
pNvFBC_Enable = NULL;
|
||||
}
|
||||
|
||||
NVFBCRESULT NvFBCLibrary::getStatus(NvFBCStatusEx* status) {
|
||||
return pfn_get_status((void*)status);
|
||||
NVFBCRESULT NvFBCLibrary::GetStatus(NvFBCStatusEx* status) {
|
||||
return pNvFBC_GetStatusEx((void*)status);
|
||||
}
|
||||
|
||||
void NvFBCLibrary::setGlobalFlags(DWORD flags, int adapter) {
|
||||
setTargetAdapter(adapter);
|
||||
pfn_set_global_flags(flags);
|
||||
void NvFBCLibrary::SetGlobalFlags(DWORD flags, int adapter) {
|
||||
SetTargetAdapter(adapter);
|
||||
pNvFBC_SetGlobalFlags(flags);
|
||||
}
|
||||
|
||||
NVFBCRESULT NvFBCLibrary::createEx(NvFBCCreateParams* pParams) {
|
||||
return pfn_create((void*)pParams);
|
||||
NVFBCRESULT NvFBCLibrary::CreateEx(NvFBCCreateParams* 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)
|
||||
return NULL;
|
||||
|
||||
NVFBCRESULT res = NVFBC_SUCCESS;
|
||||
NvFBCStatusEx status{};
|
||||
NvFBCStatusEx status {};
|
||||
status.dwVersion = NVFBC_STATUS_VER;
|
||||
status.dwAdapterIdx = adapter;
|
||||
res = getStatus(&status);
|
||||
res = GetStatus(&status);
|
||||
|
||||
if(res != NVFBC_SUCCESS) {
|
||||
return nullptr;
|
||||
|
@ -131,31 +137,34 @@ void* NvFBCLibrary::create(DWORD type, DWORD* maxWidth, DWORD* maxHeight, int ad
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
NvFBCCreateParams createParams{};
|
||||
NvFBCCreateParams createParams {};
|
||||
|
||||
createParams.dwVersion = NVFBC_CREATE_PARAMS_VER;
|
||||
createParams.dwInterfaceType = type;
|
||||
createParams.pDevice = devicePtr;
|
||||
createParams.dwAdapterIdx = adapter;
|
||||
|
||||
res = pfn_create(&createParams);
|
||||
res = pNvFBC_CreateEx(&createParams);
|
||||
|
||||
*maxWidth = createParams.dwMaxDisplayWidth;
|
||||
*maxHeight = createParams.dwMaxDisplayHeight;
|
||||
if(maxWidth)
|
||||
*maxWidth = createParams.dwMaxDisplayWidth;
|
||||
|
||||
if(maxHeight)
|
||||
*maxHeight = createParams.dwMaxDisplayHeight;
|
||||
|
||||
return createParams.pNvFBC;
|
||||
}
|
||||
|
||||
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) {
|
||||
return pfn_enable(nvFBCState);
|
||||
NVFBCRESULT NvFBCLibrary::Enable(NVFBC_STATE nvFBCState) {
|
||||
return pNvFBC_Enable(nvFBCState);
|
||||
}
|
||||
|
||||
void NvFBCLibrary::setTargetAdapter(int adapter) {
|
||||
char targetAdapter[10]{};
|
||||
void NvFBCLibrary::SetTargetAdapter(int adapter) {
|
||||
char targetAdapter[10] {};
|
||||
_snprintf_s(targetAdapter, 10, 9, "%d", adapter);
|
||||
SetEnvironmentVariableA("NVFBC_TARGET_ADAPTER", targetAdapter);
|
||||
}
|
|
@ -18,37 +18,38 @@ class NvFBCLibrary {
|
|||
~NvFBCLibrary();
|
||||
|
||||
/// Tries to load the NvFBC dll
|
||||
bool load();
|
||||
bool Load();
|
||||
|
||||
/// Close the NvFBC DLL.
|
||||
void close();
|
||||
void Close();
|
||||
|
||||
/// Get the status for the provided adapter,
|
||||
/// 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.
|
||||
/// 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
|
||||
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);
|
||||
|
||||
/// enable/disable NVFBC
|
||||
NVFBCRESULT enable(NVFBC_STATE nvFBCState);
|
||||
NVFBCRESULT Enable(NVFBC_STATE nvFBCState);
|
||||
|
||||
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:
|
||||
HMODULE m_handle { nullptr };
|
||||
NvFBC_GetStatusExFunctionType pfn_get_status { nullptr };
|
||||
NvFBC_SetGlobalFlagsType pfn_set_global_flags { nullptr };
|
||||
NvFBC_CreateFunctionExType pfn_create { nullptr };
|
||||
NvFBC_EnableFunctionType pfn_enable { nullptr };
|
||||
NvFBC_GetStatusExFunctionType pNvFBC_GetStatusEx { nullptr };
|
||||
NvFBC_SetGlobalFlagsType pNvFBC_SetGlobalFlags { nullptr };
|
||||
NvFBC_CreateFunctionExType pNvFBC_CreateEx { 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
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
// common types
|
||||
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};
|
||||
|
||||
#[derive(Debug)]
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
mod hzclient;
|
||||
|
||||
fn main() {
|
||||
let listener = TcpListener::bind("192.168.1.149:9438").expect("fuck");
|
||||
let (mut socket, client_addr) = listener.accept().expect("FUCK!");
|
||||
|
||||
// disable nagles garbage bullshit
|
||||
socket.set_nodelay(true).expect("fuck tcp");
|
||||
let mut screen_width: u32 = 320;
|
||||
let mut screen_height: u32 = 200;
|
||||
|
||||
let mut window = Window::new("FbcServer", 320, 200, WindowOptions::default())
|
||||
.expect("you banned forever: rules do not");
|
||||
|
||||
let mut argb_buffer: Vec<u32> = Vec::new();
|
||||
let mut screen_width: u32 = 320;
|
||||
let mut screen_height: u32 = 200;
|
||||
let mut client = hzclient::HazelnutClient::new();
|
||||
|
||||
if !client.open("/dev/shm/lg-win7".into()) {
|
||||
println!("FUCK");
|
||||
} else {
|
||||
println!("Opened ivshmem!!!");
|
||||
}
|
||||
|
||||
//let mut argb_buffer: Vec<u32> = Vec::new();
|
||||
|
||||
loop {
|
||||
if let Some(message) =
|
||||
read_message(&mut socket, &mut argb_buffer, screen_width, screen_height)
|
||||
{
|
||||
match message {
|
||||
Message::Resize { width, height } => {
|
||||
println!("read message {:?}", message);
|
||||
screen_width = width;
|
||||
screen_height = height;
|
||||
match client.tick_one() {
|
||||
hzclient::ResultCode::Fail => break,
|
||||
hzclient::ResultCode::Changed => {
|
||||
let dims = client.dimensions();
|
||||
|
||||
if screen_width != dims.0 && screen_height != dims.1 {
|
||||
screen_width = dims.0;
|
||||
screen_height = dims.1;
|
||||
|
||||
window = Window::new(
|
||||
"FbcServer",
|
||||
width as usize,
|
||||
height as usize,
|
||||
screen_width as usize,
|
||||
screen_height as usize,
|
||||
WindowOptions::default(),
|
||||
)
|
||||
.expect("you banned forever: rules do not");
|
||||
}
|
||||
|
||||
argb_buffer.resize((screen_width * screen_height) as usize, 0);
|
||||
}
|
||||
Message::Data {} => {
|
||||
//println!("read DATA");
|
||||
window
|
||||
.update_with_buffer(
|
||||
&argb_buffer[..],
|
||||
screen_width as usize,
|
||||
screen_height as usize,
|
||||
)
|
||||
.expect("Failed to update screen");
|
||||
}
|
||||
window.update_with_buffer(
|
||||
&client.framebuffer(),
|
||||
screen_width as usize,
|
||||
screen_height as usize,
|
||||
).expect("well its done anyways");
|
||||
|
||||
client.unlock();
|
||||
}
|
||||
hzclient::ResultCode::Unchanged => {
|
||||
window.update();
|
||||
// Not needed, C++ unlocks us
|
||||
//client.unlock();
|
||||
}
|
||||
} else {
|
||||
println!("invalid message, termination.");
|
||||
break;
|
||||
}
|
||||
|
||||
//window.upd
|
||||
}
|
||||
|
||||
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