diff --git a/nx/include/switch.h b/nx/include/switch.h index b1afb9f8..b6219077 100644 --- a/nx/include/switch.h +++ b/nx/include/switch.h @@ -32,6 +32,8 @@ extern "C" { #include #include +#include +#include #ifdef __cplusplus } diff --git a/nx/include/switch/gfx/gfx.h b/nx/include/switch/gfx/gfx.h index 74dca81a..7c431aab 100644 --- a/nx/include/switch/gfx/gfx.h +++ b/nx/include/switch/gfx/gfx.h @@ -2,3 +2,4 @@ void gfxInitDefault(void); void gfxExit(void); void gfxWaitForVsync(); +void gfxSwapBuffers(); diff --git a/nx/include/switch/gfx/gfxproducer.h b/nx/include/switch/gfx/gfxproducer.h new file mode 100644 index 00000000..5c2c1f09 --- /dev/null +++ b/nx/include/switch/gfx/gfxproducer.h @@ -0,0 +1,9 @@ +Result gfxproducerInitialize(binderSession *session); +void gfxproducerExit(); + +Result gfxproducerRequestBuffer(s32 bufferIdx); +Result gfxproducerDequeueBuffer(bool async, u32 width, u32 height, s32 format, u32 usage); +Result gfxproducerQueueBuffer(s32 buf, u8 input[0x5c]); +Result gfxproducerQuery(s32 what, s32* value); +Result gfxproducerConnect(s32 api, bool producerControlledByApp); +Result gfxproducerBufferInit(s32 buf, u8 input[0x178]); diff --git a/nx/include/switch/gfx/parcel.h b/nx/include/switch/gfx/parcel.h new file mode 100644 index 00000000..fdd69394 --- /dev/null +++ b/nx/include/switch/gfx/parcel.h @@ -0,0 +1,28 @@ +#include +#include + +typedef struct { + u8 ParcelData[0x400]; + u32 ParcelData_maxsize; + u32 ParcelData_size; + u32 ParcelData_pos; + + u8 *ParcelObjects; + u32 ParcelObjectsSize; +} parcelContext; + +void parcelInitializeContext(parcelContext *ctx); +Result parcelTransact(binderSession *session, u32 code, parcelContext *in_parcel, parcelContext *out_parcel); + +void* parcelWriteData(parcelContext *ctx, void* data, size_t data_size); +void* parcelReadData(parcelContext *ctx, void* data, size_t data_size); + +void parcelWriteInt32(parcelContext *ctx, s32 val); +void parcelWriteUInt32(parcelContext *ctx, u32 val); +void parcelWriteString16_fromchar(parcelContext *ctx, const char *str); + +void parcelWriteInterfaceToken(parcelContext *ctx, const char *interface); + +s32 parcelReadInt32(parcelContext *ctx); +u32 parcelReadUInt32(parcelContext *ctx); + diff --git a/nx/include/switch/result.h b/nx/include/switch/result.h index d3f802c3..59d21f6b 100644 --- a/nx/include/switch/result.h +++ b/nx/include/switch/result.h @@ -29,3 +29,4 @@ #define LIBNX_NOTFOUND 8 #define LIBNX_IOERROR 9 #define LIBNX_BADINPUT 10 +#define LIBNX_PARCEL_ERRBASE 100 diff --git a/nx/include/switch/services/binder.h b/nx/include/switch/services/binder.h index 375b3d47..e4c849b3 100644 --- a/nx/include/switch/services/binder.h +++ b/nx/include/switch/services/binder.h @@ -1,3 +1,5 @@ +#define BINDER_FIRST_CALL_TRANSACTION 0x1 + typedef struct { bool initialized; Handle sessionhandle; diff --git a/nx/include/switch/svc.h b/nx/include/switch/svc.h index 0206bad9..c361a125 100644 --- a/nx/include/switch/svc.h +++ b/nx/include/switch/svc.h @@ -45,6 +45,7 @@ Result svcWaitSynchronization(s32* index, const Handle* handles, s32 handleCount Result svcArbitrateLock(u32 wait_tag, u32* tag_location, u32 self_tag); Result svcArbitrateUnlock(u32* tag_location); Result svcConnectToNamedPort(Handle* session, const char* name); +u64 svcGetSystemTick(void); Result svcSendSyncRequest(Handle session); Result svcBreak(u32 breakReason, u64 inval1, u64 inval2); Result svcGetInfo(u64* out, u64 id0, Handle handle, u64 id1); diff --git a/nx/source/gfx/gfx.c b/nx/source/gfx/gfx.c index fff01c79..3d50abf7 100644 --- a/nx/source/gfx/gfx.c +++ b/nx/source/gfx/gfx.c @@ -9,6 +9,50 @@ static u8 g_gfxNativeWindow[0x100]; static u64 g_gfxNativeWindow_Size; static s32 g_gfxNativeWindow_ID; static binderSession g_gfxBinderSession; +static s32 g_gfxCurrentBuffer = 0; + +static u32 g_gfxQueueBufferData[0x5c>>2] = { +0x54, 0x0, +0x0, 0x0, //u64 timestamp +0x1, 0x0, 0x0, +0x0, 0x0, 0x0, 0x2, +0x0, 0x0, 0x1, 0x1, +0x42, +0x13f4, //Increased by 6/7 each time. +0xffffffff, 0x0, +0xffffffff, 0x0, 0xffffffff, 0x0}; + +static u32 g_gfxBufferInitData[0x178>>2] = { +0x1, 0x16c, 0x0, +0x47424652, +1280, 720, +1280, +0x1, 0xb00, 0x2a, 0x0, +0x0, 0x51, 0xffffffff, 0xcb8, +0x0, 0xdaffcaff, 0x2a, 0x0, +0xb00, 0x1, 0x1, 1280, +0x3c0000, 0x1, 0x0, 1280, +720, 0x532120, 0x1, 0x3, +0x1400, 0xcb8, +0x0, +0xfe, +0x4, 0x0, 0x0, 0x0, +0x0, 0x3c0000, 0x0, 0x0, +0x0, 0x0, 0x0, 0x0, +0x0, 0x0, 0x0, 0x0, +0x0, 0x0, 0x0, 0x0, +0x0, 0x0, 0x0, 0x0, +0x0, 0x0, 0x0, 0x0, +0x0, 0x0, 0x0, 0x0, +0x0, 0x0, 0x0, 0x0, +0x0, 0x0, 0x0, 0x0, +0x0, 0x0, 0x0, 0x0, +0x0, 0x0, 0x0, 0x0, +0x0, 0x0, 0x0, 0x0, +0x0, 0x0, 0x0, 0x0, +0x0, +0x0, 0x0 //Unknown, some timestamp perhaps? +}; static Result _gfxGetNativeWindowID(u8 *buf, u64 size, s32 *out_ID) { u32 *bufptr = (u32*)buf; @@ -25,13 +69,38 @@ static Result _gfxGetNativeWindowID(u8 *buf, u64 size, s32 *out_ID) { return 0; } +static Result _gfxDequeueBuffer() { + return gfxproducerDequeueBuffer(1, 1280, 720, 0, 0x300); +} + +static Result _gfxQueueBuffer(s32 buf) { + Result rc=0; + u64 *ptr64 = (u64*)&g_gfxQueueBufferData; + ptr64[1] = svcGetSystemTick();//Unknown what is actually used for timestamp, but shouldn't(?) matter. + + rc = gfxproducerQueueBuffer(buf, (u8*)g_gfxQueueBufferData); + if (R_FAILED(rc)) return rc; + + if(buf==0) { + g_gfxQueueBufferData[0x10]+= 0x6; + } else { + g_gfxQueueBufferData[0x10]+= 0x7; + } + + return rc; +} + static Result _gfxInit(viServiceType servicetype, const char *DisplayName, u32 LayerFlags, u64 LayerId) { Result rc=0; + s32 tmp=0; + u32 i=0; + u64 *ptr64 = (u64*)g_gfxQueueBufferData; if(g_gfxInitialized)return 0; g_gfxNativeWindow_ID = 0; g_gfxDisplayVsyncEvent = INVALID_HANDLE; + g_gfxCurrentBuffer = 0; rc = viInitialize(servicetype); if (R_FAILED(rc)) return rc; @@ -51,7 +120,36 @@ static Result _gfxInit(viServiceType servicetype, const char *DisplayName, u32 L rc = binderInitSession(&g_gfxBinderSession, 0x0f); } - //TODO: Send binder parcels. + if (R_SUCCEEDED(rc)) rc = gfxproducerInitialize(&g_gfxBinderSession); + + if (R_SUCCEEDED(rc)) rc = gfxproducerConnect(2, 0); + + if (R_SUCCEEDED(rc)) rc = gfxproducerQuery(2, &tmp);//"NATIVE_WINDOW_FORMAT" + + if (R_SUCCEEDED(rc)) { + for(i=0; i<2; i++) { + g_gfxBufferInitData[0xa] = i; + g_gfxBufferInitData[0x20] = 0x3c0000*i; + ptr64[0x170>>3] = svcGetSystemTick(); + rc = gfxproducerBufferInit(0, (u8*)g_gfxBufferInitData); + if (R_FAILED(rc)) break; + } + } + + if (R_SUCCEEDED(rc)) { + for(i=0; i<2; i++) { + rc = _gfxDequeueBuffer(); + if (R_FAILED(rc)) break; + + rc = gfxproducerRequestBuffer(i); + if (R_FAILED(rc)) break; + + rc = _gfxQueueBuffer(i); + if (R_FAILED(rc)) break; + } + } + + if (R_SUCCEEDED(rc)) rc = _gfxDequeueBuffer(); if (R_FAILED(rc)) { binderExitSession(&g_gfxBinderSession); @@ -73,6 +171,8 @@ void gfxInitDefault(void) { void gfxExit(void) { if(!g_gfxInitialized)return; + gfxproducerExit(); + binderExitSession(&g_gfxBinderSession); viCloseLayer(&g_gfxLayer); @@ -96,3 +196,14 @@ void gfxWaitForVsync() { svcWaitSynchronization(&tmpindex, &g_gfxDisplayVsyncEvent, 1, U64_MAX); } +void gfxSwapBuffers() { + Result rc=0; + + rc = _gfxQueueBuffer(g_gfxCurrentBuffer); + g_gfxCurrentBuffer ^= 1; + + if (R_SUCCEEDED(rc)) rc = _gfxDequeueBuffer(); + + if (R_FAILED(rc)) fatalSimple(rc); +} + diff --git a/nx/source/gfx/gfxproducer.c b/nx/source/gfx/gfxproducer.c new file mode 100644 index 00000000..0653944d --- /dev/null +++ b/nx/source/gfx/gfxproducer.c @@ -0,0 +1,175 @@ +#include +#include + +//This implements the version of Android IGraphicBufferProducer used by Switch, hence names/params etc here are based on Android IGraphicBufferProducer.cpp. + +//Based on an old version of the enum from the above .cpp. +//Unknown whether all of these are correct for Switch. +enum { + REQUEST_BUFFER = BINDER_FIRST_CALL_TRANSACTION, //0x1 + SET_BUFFER_COUNT, //0x2 + DEQUEUE_BUFFER, //0x3 + DETACH_BUFFER, //0x4 + DETACH_NEXT_BUFFER, //0x5 + ATTACH_BUFFER, //0x6 + QUEUE_BUFFER, //0x7 + CANCEL_BUFFER, //0x8 + QUERY, //0x9 + CONNECT, //0xA + DISCONNECT, //0xB + SET_SIDEBAND_STREAM, //0xC + ALLOCATE_BUFFERS, //0xD +}; + +static char _gfxproducer_InterfaceDescriptor[] = "android.gui.IGraphicBufferProducer"; + +static binderSession *g_gfxproducerBinderSession; + +Result gfxproducerInitialize(binderSession *session) { + g_gfxproducerBinderSession = session; + + return 0; +} + +void gfxproducerExit() { + g_gfxproducerBinderSession = NULL; +} + +Result gfxproducerRequestBuffer(s32 bufferIdx) { + Result rc; + parcelContext parcel, parcel_reply; + + if (g_gfxproducerBinderSession==NULL) return MAKERESULT(MODULE_LIBNX, LIBNX_NOTINITIALIZED); + + parcelInitializeContext(&parcel); + parcelInitializeContext(&parcel_reply); + + parcelWriteInterfaceToken(&parcel, _gfxproducer_InterfaceDescriptor); + parcelWriteInt32(&parcel, bufferIdx); + + rc = parcelTransact(g_gfxproducerBinderSession, REQUEST_BUFFER, &parcel, &parcel_reply); + if (R_FAILED(rc)) return rc; + + //TODO: parse reply + + return 0; +} + +Result gfxproducerDequeueBuffer(bool async, u32 width, u32 height, s32 format, u32 usage) { + Result rc; + parcelContext parcel, parcel_reply; + + if (g_gfxproducerBinderSession==NULL) return MAKERESULT(MODULE_LIBNX, LIBNX_NOTINITIALIZED); + + parcelInitializeContext(&parcel); + parcelInitializeContext(&parcel_reply); + + parcelWriteInterfaceToken(&parcel, _gfxproducer_InterfaceDescriptor); + + parcelWriteInt32(&parcel, async); + parcelWriteUInt32(&parcel, width); + parcelWriteUInt32(&parcel, height); + parcelWriteInt32(&parcel, format); + parcelWriteUInt32(&parcel, usage); + + rc = parcelTransact(g_gfxproducerBinderSession, DEQUEUE_BUFFER, &parcel, &parcel_reply); + if (R_FAILED(rc)) return rc; + + //TODO: parse reply + + return 0; +} + +Result gfxproducerQueueBuffer(s32 buf, u8 input[0x5c]) { + Result rc; + parcelContext parcel, parcel_reply; + + if (g_gfxproducerBinderSession==NULL) return MAKERESULT(MODULE_LIBNX, LIBNX_NOTINITIALIZED); + + parcelInitializeContext(&parcel); + parcelInitializeContext(&parcel_reply); + + parcelWriteInterfaceToken(&parcel, _gfxproducer_InterfaceDescriptor); + + parcelWriteInt32(&parcel, buf); + parcelWriteData(&parcel, input, 0x5c); + + rc = parcelTransact(g_gfxproducerBinderSession, DEQUEUE_BUFFER, &parcel, &parcel_reply); + if (R_FAILED(rc)) return rc; + + //TODO: parse reply + + return 0; +} + +Result gfxproducerQuery(s32 what, s32* value) { + Result rc; + parcelContext parcel, parcel_reply; + + if (g_gfxproducerBinderSession==NULL) return MAKERESULT(MODULE_LIBNX, LIBNX_NOTINITIALIZED); + + parcelInitializeContext(&parcel); + parcelInitializeContext(&parcel_reply); + + parcelWriteInterfaceToken(&parcel, _gfxproducer_InterfaceDescriptor); + + parcelWriteInt32(&parcel, what); + + rc = parcelTransact(g_gfxproducerBinderSession, QUERY, &parcel, &parcel_reply); + if (R_FAILED(rc)) return rc; + + *value = parcelReadInt32(&parcel_reply); + rc = parcelReadInt32(&parcel_reply); + if (rc) rc = MAKERESULT(MODULE_LIBNX, LIBNX_PARCEL_ERRBASE); + + return rc; +} + +Result gfxproducerConnect(s32 api, bool producerControlledByApp) { + Result rc; + parcelContext parcel, parcel_reply; + + if (g_gfxproducerBinderSession==NULL) return MAKERESULT(MODULE_LIBNX, LIBNX_NOTINITIALIZED); + + parcelInitializeContext(&parcel); + parcelInitializeContext(&parcel_reply); + + parcelWriteInterfaceToken(&parcel, _gfxproducer_InterfaceDescriptor); + + //Hard-code this as if listener==NULL, since that's not known to be used officially. + parcelWriteInt32(&parcel, 0); + + parcelWriteInt32(&parcel, api); + parcelWriteInt32(&parcel, producerControlledByApp); + + rc = parcelTransact(g_gfxproducerBinderSession, CONNECT, &parcel, &parcel_reply); + if (R_FAILED(rc)) return rc; + + //TODO: parse reply + + return 0; +} + +//Unknown what this is. +Result gfxproducerBufferInit(s32 buf, u8 input[0x178]) { + Result rc; + parcelContext parcel, parcel_reply; + + if (g_gfxproducerBinderSession==NULL) return MAKERESULT(MODULE_LIBNX, LIBNX_NOTINITIALIZED); + + parcelInitializeContext(&parcel); + parcelInitializeContext(&parcel_reply); + + parcelWriteInterfaceToken(&parcel, _gfxproducer_InterfaceDescriptor); + + parcelWriteInt32(&parcel, buf); + parcelWriteData(&parcel, input, 0x178); + + rc = parcelTransact(g_gfxproducerBinderSession, 0xE, &parcel, &parcel_reply); + if (R_FAILED(rc)) return rc; + + //TODO: parse reply + + return 0; +} + diff --git a/nx/source/gfx/parcel.c b/nx/source/gfx/parcel.c new file mode 100644 index 00000000..ffdc7ebb --- /dev/null +++ b/nx/source/gfx/parcel.c @@ -0,0 +1,123 @@ +#include +#include + +//This implements Android Parcel, hence names etc here are based on Android Parcel.cpp. + +/*u8 parcel_reply_log[0x10000] = {0}; +size_t parcel_reply_log_size = 0;*/ + +void parcelInitializeContext(parcelContext *ctx) { + memset(ctx, 0, sizeof(parcelContext)); + ctx->ParcelData_maxsize = sizeof(ctx->ParcelData); +} + +//outparcel is allzero with this. This is presumably invalid? +Result parcelTransact(binderSession *session, u32 code, parcelContext *in_parcel, parcelContext *out_parcel) { + Result rc=0; + u8 inparcel[0x400]; + static u8 outparcel[0x1000]; + size_t outparcel_size = 0x1000; + u32 *inparcel32 = (u32*)inparcel; + u32 *outparcel32 = (u32*)outparcel; + u32 ParcelDataSize = in_parcel->ParcelData_size; + u32 ParcelObjectsSize = in_parcel->ParcelObjectsSize; + + memset(inparcel, 0, sizeof(inparcel)); + memset(outparcel, 0, outparcel_size); + + if((size_t)ParcelDataSize >= sizeof(inparcel) || (size_t)ParcelObjectsSize >= sizeof(inparcel) || ((size_t)ParcelDataSize)+((size_t)ParcelObjectsSize)+0x10 >= sizeof(inparcel)) return MAKERESULT(MODULE_LIBNX, LIBNX_BADINPUT); + + inparcel32[0] = ParcelDataSize;//ParcelDataSize + inparcel32[1] = 0x10;//ParcelDataOffset + inparcel32[2] = ParcelObjectsSize;//ParcelObjectsSize + inparcel32[3] = 0x10+ParcelDataSize;//ParcelObjectsOffset + + if(in_parcel->ParcelData && ParcelDataSize)memcpy(&inparcel[inparcel32[1]], in_parcel->ParcelData, ParcelDataSize); + if(in_parcel->ParcelObjects && ParcelObjectsSize)memcpy(&inparcel[inparcel32[3]], in_parcel->ParcelObjects, ParcelObjectsSize); + + rc = binderTransactParcel(session, code, inparcel, ParcelDataSize+ParcelObjectsSize+0x10, outparcel, outparcel_size, 0); + if (R_FAILED(rc)) return rc; + + if((size_t)outparcel32[1] >= outparcel_size || ((size_t)outparcel32[0])+((size_t)outparcel32[1]) >= outparcel_size) return MAKERESULT(MODULE_LIBNX, LIBNX_BADINPUT); + if((size_t)outparcel32[2] >= outparcel_size || ((size_t)outparcel32[2])+((size_t)outparcel32[3]) >= outparcel_size) return MAKERESULT(MODULE_LIBNX, LIBNX_BADINPUT); + if((size_t)outparcel32[0] >= outparcel_size || (size_t)outparcel32[3] >= outparcel_size) return MAKERESULT(MODULE_LIBNX, LIBNX_BADINPUT); + + memcpy(out_parcel->ParcelData, &outparcel[outparcel32[1]], outparcel32[0]); + out_parcel->ParcelData_size = outparcel32[0]; + + /*memcpy(&parcel_reply_log[parcel_reply_log_size], out_parcel, outparcel_size); + parcel_reply_log_size+= outparcel_size;*/ + + return 0; +} + +void* parcelWriteData(parcelContext *ctx, void* data, size_t data_size) { + void* ptr = ctx->ParcelData; + + if(data_size & BIT(31)) return NULL; + data_size = (data_size+3) & ~3; + + if(ctx->ParcelData_size + data_size >= ctx->ParcelData_maxsize) return NULL; + + if(data)memcpy(&ctx->ParcelData[ctx->ParcelData_size], data, data_size); + ctx->ParcelData_size+= data_size; + + return ptr; +} + +void* parcelReadData(parcelContext *ctx, void* data, size_t data_size) { + void* ptr = ctx->ParcelData; + size_t aligned_data_size; + + if(data_size & BIT(31)) return NULL; + aligned_data_size = (data_size+3) & ~3; + + if(ctx->ParcelData_pos + aligned_data_size >= ctx->ParcelData_size) return NULL; + + if(data)memcpy(data, &ctx->ParcelData[ctx->ParcelData_pos], data_size); + ctx->ParcelData_pos+= aligned_data_size; + + return ptr; +} + +void parcelWriteInt32(parcelContext *ctx, s32 val) { + parcelWriteData(ctx, &val, sizeof(val)); +} + +void parcelWriteUInt32(parcelContext *ctx, u32 val) { + parcelWriteData(ctx, &val, sizeof(val)); +} + +void parcelWriteString16_fromchar(parcelContext *ctx, const char *str) { + u32 pos, len; + u16 *ptr; + + len = strlen(str); + parcelWriteInt32(ctx, len); + len++; + + ptr = parcelWriteData(ctx, NULL, len*2); + if(ptr==NULL)return; + + for(pos=0; pos