Browse Source

formatting changes

ci-test
parent
commit
865a1e9ef0
Signed by: govanify GPG Key ID: DE62E1E2A6145556
3 changed files with 191 additions and 174 deletions
  1. +1
    -0
      .clang-format
  2. +30
    -23
      client.cpp
  3. +160
    -151
      pcsx2_ipc.h

+ 1
- 0
.clang-format View File

@ -10,5 +10,6 @@ SpacesInCStyleCastParentheses: 'false'
SpacesInContainerLiterals: 'false'
SpacesInParentheses: 'false'
UseTab: Never
AlwaysBreakTemplateDeclarations: 'Yes'
...

+ 30
- 23
client.cpp View File

@ -1,34 +1,36 @@
#include "pcsx2_ipc.h"
#include <stdio.h>
#include <ostream>
#include <iostream>
#include <ostream>
#include <stdio.h>
// a portable sleep function
void msleep(int sleepMs) {
auto msleep(int sleepMs) -> void {
#ifdef _WIN32
Sleep(sleepMs);
#else
usleep(sleepMs * 1000);
usleep(sleepMs * 1000);
#endif
}
// this function is an infinite loop reading a value in memory, this shows you
// how timer can work
void read_background(PCSX2Ipc *ipc) {
auto read_background(PCSX2Ipc *ipc) -> void {
while (true) {
// you can go slower but go higher at your own risk
msleep(100);
// we read a 32 bit value from memory address 0x00347D34
try {
// those comments calculate a rough approximation of the latency time of
// socket IPC, in µs, if you want to have an idea.
// those comments calculate a rough approximation of the latency
// time of socket IPC, in µs, if you want to have an idea.
//auto t1 = std::chrono::high_resolution_clock::now();
// auto t1 = std::chrono::high_resolution_clock::now();
uint32_t value = ipc->Read<uint32_t>(0x00347D34);
//auto t2 = std::chrono::high_resolution_clock::now();
//auto duration = std::chrono::duration_cast<std::chrono::microseconds>( t2 - t1 ).count();
//std::cout << "execution time: " << duration << std::endl;
// auto t2 = std::chrono::high_resolution_clock::now();
// auto duration =
// std::chrono::duration_cast<std::chrono::microseconds>( t2 - t1
// ).count(); std::cout << "execution time: " << duration <<
// std::endl;
printf("PCSX2Ipc::Read<uint32_t>(0x00347D34) : %u\n", value);
} catch (...) {
// if the operation failed
@ -38,7 +40,7 @@ void read_background(PCSX2Ipc *ipc) {
}
// the main function that is executed at the start of our program
int main(int argc, char *argv[]) {
auto main(int argc, char *argv[]) -> int {
// we instantiate a new PCSX2Ipc object. It should be shared across all your
// threads.
@ -53,17 +55,19 @@ int main(int argc, char *argv[]) {
// a normal write can be done this way
ipc->Write<uint8_t>(0x00347D34, 0x5);
// if you need to make a lot of IPC requests at once(eg >50/16ms) it is recommended
// to build a batch message: you should build this message at the start
// of your thread once and keep the command/ret combo defined below to
// avoid wasting time recreating this IPC packet.
// if you need to make a lot of IPC requests at once(eg >50/16ms) it is
// recommended to build a batch message: you should build this message
// at the start of your thread once and keep the command/ret combo
// defined below to avoid wasting time recreating this IPC packet.
ipc->InitializeBatch();
ipc->Write<uint8_t, true>(0x00347D34, 0xFF);
ipc->Write<uint8_t, true>(0x00347D33, 0xEF);
ipc->Write<uint8_t, true>(0x00347D32, 0xDF);
auto res = ipc->FinalizeBatch();
std::pair<int,char*> command = std::make_pair((int)std::get<0>(res), std::get<1>(res));
std::pair<int,char*> ret = std::make_pair((int)std::get<2>(res), std::get<3>(res));
std::pair<int, char *> command =
std::make_pair((int)std::get<0>(res), std::get<1>(res));
std::pair<int, char *> ret =
std::make_pair((int)std::get<2>(res), std::get<3>(res));
// our batch ipc packet is now saved and ready to be used whenever! When
// we need it we just fire up a SendCommand:
ipc->SendCommand(command, ret);
@ -75,8 +79,10 @@ int main(int argc, char *argv[]) {
ipc->Read<uint8_t, true>(0x00347D33);
ipc->Read<uint8_t, true>(0x00347D32);
auto resr = ipc->FinalizeBatch();
std::pair<int,char*> commandr = std::make_pair((int)std::get<0>(resr), std::get<1>(resr));
std::pair<int,char*> retr = std::make_pair((int)std::get<2>(resr), std::get<3>(resr));
std::pair<int, char *> commandr =
std::make_pair((int)std::get<0>(resr), std::get<1>(resr));
std::pair<int, char *> retr =
std::make_pair((int)std::get<2>(resr), std::get<3>(resr));
// same as before
ipc->SendCommand(commandr, retr);
@ -84,16 +90,17 @@ int main(int argc, char *argv[]) {
// FinalizeBatch tells us where the replies are stored in the return
// buffer, so let's save this value in replies, and try to get the reply
// of the second function, in our case Read(0x00347D32)
// we first get the last argument of FinalizeBatch
unsigned int* replies = std::get<4>(resr);
unsigned int *replies = std::get<4>(resr);
// retrieve the location of the 2nd reply
unsigned int result = replies[2];
// and use this location to get the reply from our return buffer!
// NB: you'll have to read the doc to verify what the return value of
// the command is and convert it accordingly, in our case Read<uint8_t>
// returns an uint8_t, so this isn't very hard :p
printf("PCSX2Ipc::Read<uint8_t>(0x00347D32) : %u\n", ipc->FromArray<uint8_t>(retr.second, result));
printf("PCSX2Ipc::Read<uint8_t>(0x00347D32) : %u\n",
ipc->FromArray<uint8_t>(retr.second, result));
} catch (...) {
// if the operation failed
printf("ERROR!!!!!\n");


+ 160
- 151
pcsx2_ipc.h View File

@ -1,12 +1,12 @@
#pragma once
#include <mutex>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <thread>
#include <unistd.h>
#include <mutex>
#ifdef _WIN32
#define read_portable(a, b, c) (recv(a, b, c, 0))
@ -22,148 +22,148 @@
#endif
/**
* The PCSX2Ipc API.
* This is the client side implementation of PCSX2 IPC.
* The PCSX2Ipc API. @n
* This is the client side implementation of PCSX2 IPC. @n
* It allows for a three
* way communication between the emulated game, the emulator and an external
* tool, using the external tool as a relay for all communication.
* It is a socket based IPC that is _very_ fast.
* tool, using the external tool as a relay for all communication. @n
* It is a socket based IPC that is _very_ fast. @n
*
* If you want to draw comparisons you can think of this as an equivalent of the
* BizHawk LUA API, although with the logic out of the core and in an external
* tool.
* tool. @n
* While BizHawk would run a lua script at each frame in the core of the
* emulator we opt instead to keep the entire logic out of the emulator to make
* it more easily extensible, more portable, require less code and be more
* performant.
* performant. @n
*
* Event based commands such as ExecuteOnFrameEnd (not yet implemented) can thus
* be a blocking socket event, which is then noticed by our API, executes out
* IPC commands and then tells the game to resume. Thanks to the speed of the
* IPC even complex events can be outsourced from the emulator, thus keeping
* the main codebase lean and minimal.
* the main codebase lean and minimal. @n
*
* Have fun!
* Have fun! @n
* -Gauvain "GovanifY" Roussel-Tarbouriech, 2020
*/
class PCSX2Ipc {
protected:
#if defined(_WIN32) || defined(DOXYGEN)
#if defined(_WIN32) || defined(DOXYGEN)
/**
* TCP socket port.
* Used by the IPC on platforms with TCP sockets.
* TCP socket port. @n
* Used by the IPC on platforms with TCP sockets. @n
* Currently Windows only.
*/
const uint16_t PORT = 28011;
#endif
#if !defined(_WIN32) || defined(DOXYGEN)
/**
* Unix socket name.
* The name of the unix socket used on platforms with unix socket support.
* Currently everything except Windows.
* Unix socket name. @n
* The name of the unix socket used on platforms with unix socket support.
* @n Currently everything except Windows.
*/
const char *SOCKET_NAME = "/tmp/pcsx2.sock";
#endif
/**
* IPC return buffer.
* IPC return buffer. @n
* A preallocated buffer used to store all IPC replies. Currently allocated
* to the size of 50.000 MsgWrite64 IPC calls.
* to the size of 50.000 MsgWrite64 IPC calls. @n
* WARNING: No checks are executed client or server-side about the size of
* this buffer, to ensure a fast implementation.
* this buffer, to ensure a fast implementation. @n
* It is assumed you're not an absolutely insane person and you
* won't try to send in batch more than 50k IPC calls in a single batch.
* won't try to send in batch more than 50k IPC calls in a single batch.
* @see ipc_buffer
*/
char* ret_buffer;
char *ret_buffer;
/**
* IPC messages buffer.
* IPC messages buffer. @n
* A preallocated buffer used to store all IPC messages. Currently allocated
* to the size of 50.000 MsgWrite64 IPC calls.
* to the size of 50.000 MsgWrite64 IPC calls. @n
* WARNING: No checks are executed client or server-side about the size of
* this buffer, to ensure a fast implementation.
* this buffer, to ensure a fast implementation. @n
* It is assumed you're not an absolutely insane person and you
* won't try to send in batch more than 50k IPC calls in a single batch.
* won't try to send in batch more than 50k IPC calls in a single batch.
* @see ret_buffer
*/
char* ipc_buffer;
char *ipc_buffer;
/**
* Length of the batch IPC request.
* Length of the batch IPC request. @n
* This is used when chaining multiple IPC commands in one go by using
* MsgMultiCommand to store the length of the entire IPC message.
* @see IPCCommand
* MsgMultiCommand to store the length of the entire IPC message.
* @see IPCCommand
*/
uint16_t batch_len = 0;
/**
* Length of the reply of the batch IPC request.
* Length of the reply of the batch IPC request. @n
* This is used when chaining multiple IPC commands in one go by using
* MsgMultiCommand to store the length of the reply of the IPC message.
* @see IPCCommand
* MsgMultiCommand to store the length of the reply of the IPC message.
* @see IPCCommand
*/
unsigned int reply_len = 0;
/**
* Number of IPC messages of the batch IPC request.
* Number of IPC messages of the batch IPC request. @n
* This is used when chaining multiple IPC commands in one go by using
* MsgMultiCommand to store the number of IPC messages chained together.
* @see IPCCommand
* MsgMultiCommand to store the number of IPC messages chained together.
* @see IPCCommand
*/
unsigned int arg_cnt = 0;
/**
* Position of the batch arguments.
* Position of the batch arguments. @n
* This is used when chaining multiple IPC commands in one go by using
* MsgMultiCommand. Stores the location of each message reply in the buffer
* sent by FinalizeBatch.
* @see FinalizeBatch
* @see IPCCommand
* @see IPCCommand
*/
unsigned int* batch_arg_place;
unsigned int *batch_arg_place;
/**
* Sets the state of the batch command building.
* Sets the state of the batch command building. @n
* This is used when chaining multiple IPC commands in one go by using
* MsgMultiCommand.
* MsgMultiCommand. @n
* As we cannot build multiple batch IPC commands at the same time because
* of state keeping issues we block the initialization of another batch
* request until the other ends.
* request until the other ends.
*/
std::mutex batch_blocking;
/**
* Sets the state of the IPC message building.
* Sets the state of the IPC message building. @n
* As we cannot build multiple batch IPC commands at the same time because
* of state keeping issues we block the initialization of another message
* request until the other ends.
* of state keeping issues we block the initialization of another message
* request until the other ends.
*/
std::mutex ipc_blocking;
/**
* IPC Command messages opcodes.
* A list of possible operations possible by the IPC.
* IPC Command messages opcodes. @n
* A list of possible operations possible by the IPC. @n
* Each one of them is what we call an "opcode" and is the first
* byte sent by the IPC to differentiate between commands.
* byte sent by the IPC to differentiate between commands.
*/
enum IPCCommand {
MsgRead8 = 0, /**< Read 8 bit value to memory. */
MsgRead16 = 1, /**< Read 16 bit value to memory. */
MsgRead32 = 2, /**< Read 32 bit value to memory. */
MsgRead64 = 3, /**< Read 64 bit value to memory. */
MsgWrite8 = 4, /**< Write 8 bit value to memory. */
MsgWrite16 = 5, /**< Write 16 bit value to memory. */
MsgWrite32 = 6, /**< Write 32 bit value to memory. */
MsgWrite64 = 7, /**< Write 64 bit value to memory. */
MsgMultiCommand = 0xFF /**< Treats multiple IPC commands in batch. */
MsgRead8 = 0, /**< Read 8 bit value to memory. */
MsgRead16 = 1, /**< Read 16 bit value to memory. */
MsgRead32 = 2, /**< Read 32 bit value to memory. */
MsgRead64 = 3, /**< Read 64 bit value to memory. */
MsgWrite8 = 4, /**< Write 8 bit value to memory. */
MsgWrite16 = 5, /**< Write 16 bit value to memory. */
MsgWrite32 = 6, /**< Write 32 bit value to memory. */
MsgWrite64 = 7, /**< Write 64 bit value to memory. */
MsgMultiCommand = 0xFF /**< Treats multiple IPC commands in batch. */
};
/**
* IPC result codes.
* A list of possible result codes the IPC can send back.
* IPC result codes. @n
* A list of possible result codes the IPC can send back. @n
* Each one of them is what we call an "opcode" or "tag" and is the
* first byte sent by the IPC to differentiate between results.
* first byte sent by the IPC to differentiate between results.
*/
enum IPCResult {
IPC_OK = 0, /**< IPC command successfully completed. */
@ -171,7 +171,7 @@ class PCSX2Ipc {
};
/**
* Formats an IPC buffer.
* Formats an IPC buffer. @n
* Creates a new buffer with IPC opcode set and first address argument
* currently used for memory IPC commands.
* @param size The size of the array to allocate.
@ -180,22 +180,24 @@ class PCSX2Ipc {
* @see IPCCommand
* @return The IPC buffer.
*/
char *FormatBeginning(char* cmd, uint32_t address, IPCCommand command) {
auto FormatBeginning(char *cmd, uint32_t address, IPCCommand command)
-> char * {
cmd[0] = (unsigned char)command;
return ToArray(cmd, address, 1);
}
public:
/**
* Converts an uint to an char* in little endian.
* Converts an uint to an char* in little endian.
* @param res_array The array to modify.
* @param res The value to convert.
* @param i When to insert it into the array
* @return res_array
*/
template <typename T> static char *ToArray(char *res_array, T res, int i) {
memcpy((res_array + i), (char*)&res, sizeof(T));
return res_array;
template <typename T>
static auto ToArray(char *res_array, T res, int i) -> char * {
memcpy((res_array + i), (char *)&res, sizeof(T));
return res_array;
}
/**
@ -204,12 +206,13 @@ class PCSX2Ipc {
* @param i When to load it from the array.
* @return The converted value.
*/
template <typename T> static T FromArray(char *arr, int i) {
return *(T*)(arr + i);
template <typename T>
static auto FromArray(char *arr, int i) -> T {
return *(T *)(arr + i);
}
/**
* Result code of the IPC operation.
* Result code of the IPC operation. @n
* A list of result codes that should be returned, or thrown, depending
* on the state of the result of an IPC command.
*/
@ -219,17 +222,16 @@ class PCSX2Ipc {
Unknown /**< Unknown if the command completed successfully or not. */
};
/**
* Sends an IPC command to PCSX2.
* Sends an IPC command to PCSX2. @n
* Fails if the IPC cannot be sent or if PCSX2 returns IPC_FAIL.
* Throws an IPCStatus on failure.
* Throws an IPCStatus on failure.
* @param command A pair containing the IPC command size and buffer.
* @param ret A pair containing the IPC return size and buffer.
* @see IPCResult
*/
void SendCommand(std::pair<int, char *> command,
std::pair<int, char *> ret) {
auto SendCommand(std::pair<int, char *> command, std::pair<int, char *> ret)
-> void {
#ifdef _WIN32
SOCKET sock;
struct sockaddr_in server;
@ -284,23 +286,23 @@ class PCSX2Ipc {
}
/**
* Initializes a MsgMultiCommand IPC message.
* Initializes a MsgMultiCommand IPC message. @n
* Batch IPC messages are preferred when dealing with a lot of IPC messages
* in a quick fashion. They are _very_ fast, 1000 Write<uint8_t> are as fast
* as one Read<uint32_t> in non-batch mode, give or take, which is about
* 100µs.
* 100µs. @n
* You'll have to build the IPC messages in advance, with this function and
* FinalizeBatch, and will have to send the command yourself, along with
* dealing with the extraction of return values, if need there is.
* dealing with the extraction of return values, if need there is.
* It is a little bit less convenient than the standard IPC but has, at the
* very least, a 1000x speedup on big commands.
* very least, a 1000x speedup on big commands.
* @see batch_blocking
* @see batch_len
* @see reply_len
* @see arg_cnt
* @see FinalizeBatch
*/
void InitializeBatch() {
auto InitializeBatch() -> void {
batch_blocking.lock();
ipc_blocking.lock();
ipc_buffer[0] = (unsigned char)MsgMultiCommand;
@ -310,36 +312,38 @@ class PCSX2Ipc {
}
/**
* Finalizes a MsgMultiCommand IPC message.
* @return A tuple with, in order:
* * The IPC command length.
* * The IPC message.
* * The IPC reply length.
* * A buffer to store the IPC reply.
* * A buffer storing the offset of the argument for each function in
* the IPC reply buffer.
* NB: ownership of the buffers is delegated to the calling thread.
* It is your duty to free them when done! (or not, I'm a
* documentation, not a cop).
* Finalizes a MsgMultiCommand IPC message.
* @return A tuple with, in order:
* * The IPC command length.
* * The IPC message.
* * The IPC reply length.
* * A buffer to store the IPC reply.
* * A buffer storing the offset of the argument for each function
* in the IPC reply buffer.
* NB: ownership of the buffers is delegated to the
* calling thread. It is your duty to free them when done! (or not, I'm a
* documentation, not a cop).
* @see batch_blocking
* @see batch_len
* @see reply_len
* @see arg_cnt
* @see InitializeBatch
*/
std::tuple<uint16_t, char*, unsigned int, char*, unsigned int*> FinalizeBatch() {
auto FinalizeBatch()
-> std::tuple<uint16_t, char *, unsigned int, char *, unsigned int *> {
// save size in IPC message header.
ToArray<uint16_t>(ipc_buffer, arg_cnt, 1);
// we copy our arrays to unblock the IPC class.
uint16_t bl = batch_len;
int rl = reply_len;
char *c_cmd = (char*)malloc(batch_len*sizeof(char));
memcpy(c_cmd, ipc_buffer, batch_len*sizeof(char));
char *c_ret = (char*)malloc(reply_len*sizeof(char));
memcpy(c_ret, ret_buffer, reply_len*sizeof(char));
unsigned int *arg_place = (unsigned int*)malloc(arg_cnt*sizeof(unsigned int));
memcpy(arg_place, batch_arg_place, arg_cnt*sizeof(unsigned int));
char *c_cmd = (char *)malloc(batch_len * sizeof(char));
memcpy(c_cmd, ipc_buffer, batch_len * sizeof(char));
char *c_ret = (char *)malloc(reply_len * sizeof(char));
memcpy(c_ret, ret_buffer, reply_len * sizeof(char));
unsigned int *arg_place =
(unsigned int *)malloc(arg_cnt * sizeof(unsigned int));
memcpy(arg_place, batch_arg_place, arg_cnt * sizeof(unsigned int));
// we unblock the mutex
batch_blocking.unlock();
@ -350,36 +354,37 @@ class PCSX2Ipc {
}
/**
* Reads a value from PCSX2 game memory.
* On error throws an IPCStatus.
* Format: XX YY YY YY YY
* Legend: XX = IPC Tag, YY = Address.
* Return: (ZZ*??)
* Legend: ZZ = Value read.
* Reads a value from PCSX2 game memory. @n
* On error throws an IPCStatus. @n
* Format: XX YY YY YY YY @n
* Legend: XX = IPC Tag, YY = Address. @n
* Return: (ZZ*??) @n
* Legend: ZZ = Value read.
* @see IPCCommand
* @see IPCStatus
* @param address The address to read.
* @param T Flag to enable batch processing or not.
* @return The value read in memory. If in batch mode the IPC message.
*/
template <typename Y, bool T = false> auto Read(uint32_t address) {
template <typename Y, bool T = false>
auto Read(uint32_t address) {
// deduce ipc tag
IPCCommand tag;
switch(sizeof(Y)) {
case 1:
tag = MsgRead8;
break;
case 2:
tag = MsgRead16;
break;
case 4:
tag = MsgRead32;
break;
case 8:
tag = MsgRead64;
break;
default:
throw Fail;
switch (sizeof(Y)) {
case 1:
tag = MsgRead8;
break;
case 2:
tag = MsgRead16;
break;
case 4:
tag = MsgRead32;
break;
case 8:
tag = MsgRead64;
break;
default:
throw Fail;
}
// batch mode
@ -393,54 +398,58 @@ class PCSX2Ipc {
} else {
// we are already locked in batch mode
std::lock_guard<std::mutex> lock(ipc_blocking);
SendCommand(std::make_pair(5, FormatBeginning(ipc_buffer, address, tag)),
std::make_pair(1 + sizeof(Y), ret_buffer));
SendCommand(
std::make_pair(5, FormatBeginning(ipc_buffer, address, tag)),
std::make_pair(1 + sizeof(Y), ret_buffer));
return FromArray<Y>(ret_buffer, 1);
}
}
/**
* Writes a value to PCSX2 game memory.
* On error throws an IPCStatus.
* Format: XX YY YY YY YY (ZZ*??)
* Legend: XX = IPC Tag, YY = Address, ZZ = Value.
* Writes a value to PCSX2 game memory. @n
* On error throws an IPCStatus. @n
* Format: XX YY YY YY YY (ZZ*??) @n
* Legend: XX = IPC Tag, YY = Address, ZZ = Value.
* @see IPCCommand
* @see IPCStatus
* @param address The address to write to.
* @param value The value to write.
* @return If in batch mode the IPC message otherwise void.
*/
template <typename Y, bool T = false> auto Write(uint32_t address, Y value) {
template <typename Y, bool T = false>
auto Write(uint32_t address, Y value) {
// deduce ipc tag
IPCCommand tag;
switch(sizeof(Y)) {
case 1:
tag = MsgWrite8;
break;
case 2:
tag = MsgWrite16;
break;
case 4:
tag = MsgWrite32;
break;
case 8:
tag = MsgWrite64;
break;
default:
throw Fail;
switch (sizeof(Y)) {
case 1:
tag = MsgWrite8;
break;
case 2:
tag = MsgWrite16;
break;
case 4:
tag = MsgWrite32;
break;
case 8:
tag = MsgWrite64;
break;
default:
throw Fail;
}
// batch mode
if constexpr (T) {
char *cmd = ToArray<Y>(FormatBeginning(&ipc_buffer[batch_len], address, tag), value, 5);
char *cmd = ToArray<Y>(
FormatBeginning(&ipc_buffer[batch_len], address, tag), value,
5);
batch_len += 5 + sizeof(Y);
arg_cnt += 1;
return cmd;
}
else {
} else {
// we are already locked in batch mode
std::lock_guard<std::mutex> lock(ipc_blocking);
char *cmd = ToArray(FormatBeginning(ipc_buffer, address, tag), value, 5);
char *cmd =
ToArray(FormatBeginning(ipc_buffer, address, tag), value, 5);
SendCommand(std::make_pair(5 + sizeof(Y), cmd),
std::make_pair(1, ret_buffer));
return;
@ -456,14 +465,14 @@ class PCSX2Ipc {
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
#endif
// for the sake of speed we malloc once a return buffer and reuse it by just
// cropping its size when needed, it is 450k long which is the size of 50k
// MsgWrite64 replies, should be good enough even if we implement batch IPC
// processing. Coincidentally 650k is the size of 50k MsgWrite64 REQUESTS so
// we just allocate a 1mb buffer in the end, lul
ret_buffer = (char*)malloc(450000 * sizeof(char));
ipc_buffer = (char*)malloc(650000 * sizeof(char));
batch_arg_place = (unsigned int*)malloc(50000 * sizeof(unsigned int));
// for the sake of speed we malloc once a return buffer and reuse it by
// just cropping its size when needed, it is 450k long which is the size
// of 50k MsgWrite64 replies, should be good enough even if we implement
// batch IPC processing. Coincidentally 650k is the size of 50k
// MsgWrite64 REQUESTS so we just allocate a 1mb buffer in the end, lul
ret_buffer = (char *)malloc(450000 * sizeof(char));
ipc_buffer = (char *)malloc(650000 * sizeof(char));
batch_arg_place = (unsigned int *)malloc(50000 * sizeof(unsigned int));
}
/**


Loading…
Cancel
Save