├── .gitignore ├── Makefile └── source └── main.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.nro 2 | *.nacp 3 | *.elf 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #--------------------------------------------------------------------------------- 2 | .SUFFIXES: 3 | #--------------------------------------------------------------------------------- 4 | 5 | ifeq ($(strip $(DEVKITPRO)),) 6 | $(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") 7 | endif 8 | 9 | TOPDIR ?= $(CURDIR) 10 | include $(DEVKITPRO)/libnx/switch_rules 11 | 12 | #--------------------------------------------------------------------------------- 13 | # TARGET is the name of the output 14 | # BUILD is the directory where object files & intermediate files will be placed 15 | # SOURCES is a list of directories containing source code 16 | # DATA is a list of directories containing data files 17 | # INCLUDES is a list of directories containing header files 18 | # EXEFS_SRC is the optional input directory containing data copied into exefs, if anything this normally should only contain "main.npdm". 19 | # ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional) 20 | # 21 | # NO_ICON: if set to anything, do not use icon. 22 | # NO_NACP: if set to anything, no .nacp file is generated. 23 | # APP_TITLE is the name of the app stored in the .nacp file (Optional) 24 | # APP_AUTHOR is the author of the app stored in the .nacp file (Optional) 25 | # APP_VERSION is the version of the app stored in the .nacp file (Optional) 26 | # APP_TITLEID is the titleID of the app stored in the .nacp file (Optional) 27 | # ICON is the filename of the icon (.jpg), relative to the project folder. 28 | # If not set, it attempts to use one of the following (in this order): 29 | # - .jpg 30 | # - icon.jpg 31 | # - /default_icon.jpg 32 | #--------------------------------------------------------------------------------- 33 | TARGET := $(notdir $(CURDIR)) 34 | BUILD := build 35 | SOURCES := source 36 | DATA := data 37 | INCLUDES := include 38 | EXEFS_SRC := exefs_src 39 | #ROMFS := romfs 40 | 41 | #--------------------------------------------------------------------------------- 42 | # options for code generation 43 | #--------------------------------------------------------------------------------- 44 | ARCH := -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE 45 | 46 | CFLAGS := -g -Wall -O2 -ffunction-sections \ 47 | $(ARCH) $(DEFINES) 48 | 49 | CFLAGS += $(INCLUDE) -D__SWITCH__ 50 | 51 | CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions 52 | 53 | ASFLAGS := -g $(ARCH) 54 | LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) 55 | 56 | LIBS := -lnx 57 | 58 | #--------------------------------------------------------------------------------- 59 | # list of directories containing libraries, this must be the top level containing 60 | # include and lib 61 | #--------------------------------------------------------------------------------- 62 | LIBDIRS := $(PORTLIBS) $(LIBNX) 63 | 64 | 65 | #--------------------------------------------------------------------------------- 66 | # no real need to edit anything past this point unless you need to add additional 67 | # rules for different file extensions 68 | #--------------------------------------------------------------------------------- 69 | ifneq ($(BUILD),$(notdir $(CURDIR))) 70 | #--------------------------------------------------------------------------------- 71 | 72 | export OUTPUT := $(CURDIR)/$(TARGET) 73 | export TOPDIR := $(CURDIR) 74 | 75 | export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ 76 | $(foreach dir,$(DATA),$(CURDIR)/$(dir)) 77 | 78 | export DEPSDIR := $(CURDIR)/$(BUILD) 79 | 80 | CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) 81 | CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) 82 | SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) 83 | BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) 84 | 85 | #--------------------------------------------------------------------------------- 86 | # use CXX for linking C++ projects, CC for standard C 87 | #--------------------------------------------------------------------------------- 88 | ifeq ($(strip $(CPPFILES)),) 89 | #--------------------------------------------------------------------------------- 90 | export LD := $(CC) 91 | #--------------------------------------------------------------------------------- 92 | else 93 | #--------------------------------------------------------------------------------- 94 | export LD := $(CXX) 95 | #--------------------------------------------------------------------------------- 96 | endif 97 | #--------------------------------------------------------------------------------- 98 | 99 | export OFILES_BIN := $(addsuffix .o,$(BINFILES)) 100 | export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) 101 | export OFILES := $(OFILES_BIN) $(OFILES_SRC) 102 | export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) 103 | 104 | export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ 105 | $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ 106 | -I$(CURDIR)/$(BUILD) 107 | 108 | export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) 109 | 110 | export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC) 111 | 112 | ifeq ($(strip $(ICON)),) 113 | icons := $(wildcard *.jpg) 114 | ifneq (,$(findstring $(TARGET).jpg,$(icons))) 115 | export APP_ICON := $(TOPDIR)/$(TARGET).jpg 116 | else 117 | ifneq (,$(findstring icon.jpg,$(icons))) 118 | export APP_ICON := $(TOPDIR)/icon.jpg 119 | endif 120 | endif 121 | else 122 | export APP_ICON := $(TOPDIR)/$(ICON) 123 | endif 124 | 125 | ifeq ($(strip $(NO_ICON)),) 126 | export NROFLAGS += --icon=$(APP_ICON) 127 | endif 128 | 129 | ifeq ($(strip $(NO_NACP)),) 130 | export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp 131 | endif 132 | 133 | ifneq ($(APP_TITLEID),) 134 | export NACPFLAGS += --titleid=$(APP_TITLEID) 135 | endif 136 | 137 | ifneq ($(ROMFS),) 138 | export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS) 139 | endif 140 | 141 | .PHONY: $(BUILD) clean all 142 | 143 | #--------------------------------------------------------------------------------- 144 | all: $(BUILD) 145 | 146 | $(BUILD): 147 | @[ -d $@ ] || mkdir -p $@ 148 | @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile 149 | 150 | #--------------------------------------------------------------------------------- 151 | clean: 152 | @echo clean ... 153 | @rm -fr $(BUILD) $(TARGET).pfs0 $(TARGET).nso $(TARGET).nro $(TARGET).nacp $(TARGET).elf 154 | 155 | 156 | #--------------------------------------------------------------------------------- 157 | else 158 | .PHONY: all 159 | 160 | DEPENDS := $(OFILES:.o=.d) 161 | 162 | #--------------------------------------------------------------------------------- 163 | # main targets 164 | #--------------------------------------------------------------------------------- 165 | all : $(OUTPUT).nro 166 | 167 | $(OUTPUT).pfs0 : $(OUTPUT).nso 168 | 169 | $(OUTPUT).nso : $(OUTPUT).elf 170 | 171 | ifeq ($(strip $(NO_NACP)),) 172 | $(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp 173 | else 174 | $(OUTPUT).nro : $(OUTPUT).elf 175 | endif 176 | 177 | $(OUTPUT).elf : $(OFILES) 178 | 179 | $(OFILES_SRC) : $(HFILES_BIN) 180 | 181 | #--------------------------------------------------------------------------------- 182 | # you need a rule like this for each extension you use as binary data 183 | #--------------------------------------------------------------------------------- 184 | %.bin.o %_bin.h : %.bin 185 | #--------------------------------------------------------------------------------- 186 | @echo $(notdir $<) 187 | @$(bin2o) 188 | 189 | -include $(DEPENDS) 190 | 191 | #--------------------------------------------------------------------------------------- 192 | endif 193 | #--------------------------------------------------------------------------------------- 194 | -------------------------------------------------------------------------------- /source/main.c: -------------------------------------------------------------------------------- 1 | // Include the most common headers from the C standard library 2 | #include 3 | #include 4 | #include 5 | 6 | // Include the main libnx system header, for Switch development 7 | #include 8 | 9 | static Service g_grcdSrv; 10 | static Service g_grccSrv; 11 | static u64 g_refCnt; 12 | 13 | 14 | Result grcExit(void) { 15 | if (atomicDecrement64(&g_refCnt) == 0) { 16 | serviceClose(&g_grccSrv); 17 | serviceClose(&g_grcdSrv); 18 | } 19 | return 0; 20 | } 21 | 22 | Result grcInitialize(void) { 23 | Result rc = 0; 24 | 25 | atomicIncrement64(&g_refCnt); 26 | 27 | if (serviceIsActive(&g_grccSrv)) 28 | return 0; 29 | 30 | rc = smGetService(&g_grccSrv, "grc:c"); 31 | if (R_FAILED(rc)) grcExit(); 32 | rc = smGetService(&g_grcdSrv, "grc:d"); 33 | if (R_FAILED(rc)) grcExit(); 34 | 35 | return rc; 36 | } 37 | 38 | Result grcdCmd1() 39 | { 40 | IpcCommand c; 41 | ipcInitialize(&c); 42 | 43 | struct { 44 | u64 magic; 45 | u64 cmd_id; 46 | } *raw; 47 | 48 | raw = ipcPrepareHeader(&c, sizeof(*raw)); 49 | 50 | raw->magic = SFCI_MAGIC; 51 | raw->cmd_id = 1; 52 | 53 | Result rc = serviceIpcDispatch(&g_grcdSrv); 54 | if (R_SUCCEEDED(rc)) { 55 | IpcParsedCommand r; 56 | ipcParse(&r); 57 | struct { 58 | u64 magic; 59 | u64 result; 60 | } *resp = r.Raw; 61 | rc = resp->result; 62 | } 63 | return rc; 64 | } 65 | 66 | Result grcdCmd2(u32 i, void *buff, size_t len, u32 *n_frames_out, u64 *ts_out, size_t *size_out) 67 | { 68 | IpcCommand c; 69 | ipcInitialize(&c); 70 | ipcAddRecvBuffer(&c, buff, len, 0); 71 | 72 | struct { 73 | u64 magic; 74 | u64 cmd_id; 75 | u32 input; 76 | } PACKED *raw; 77 | 78 | raw = ipcPrepareHeader(&c, sizeof(*raw)); 79 | 80 | raw->magic = SFCI_MAGIC; 81 | raw->cmd_id = 2; 82 | raw->input = i; 83 | 84 | Result rc = serviceIpcDispatch(&g_grcdSrv); 85 | if (R_SUCCEEDED(rc)) { 86 | IpcParsedCommand r; 87 | ipcParse(&r); 88 | struct { 89 | u64 magic; 90 | u64 result; 91 | u32 n_frames; 92 | u32 size; 93 | u64 start_ts; 94 | } *resp = r.Raw; 95 | 96 | rc = resp->result; 97 | 98 | if(n_frames_out) *n_frames_out = resp->n_frames; 99 | if(size_out) *size_out = resp->size; 100 | if(ts_out) *ts_out = resp->start_ts; 101 | 102 | printf("got size=%u, %u frames, ts=%lu\n", resp->size, resp->n_frames, resp->start_ts); 103 | } 104 | return rc; 105 | } 106 | 107 | typedef struct ContinuousRecorder 108 | { 109 | TransferMemory tmem; 110 | Service s; 111 | } ContinuousRecorder; 112 | 113 | Result grcGetIContinuousRecorder(ContinuousRecorder* out) 114 | { 115 | IpcCommand c; 116 | ipcInitialize(&c); 117 | u64 grc_transfermem_size = 0x10000000; 118 | Result rc = tmemCreate(&out->tmem, grc_transfermem_size, Perm_None); 119 | 120 | ipcSendHandleCopy(&c, out->tmem.handle); 121 | struct { 122 | u64 magic; 123 | u64 cmd_id; 124 | 125 | u64 application_id; // 0 126 | u64 ar_uid; 127 | u32 unk_10; // bitrate? default 8000000 128 | u32 unk_14; // default 20000 129 | u32 unk_18; // default 1000 130 | u16 unk_1C; // fps-related? default 30 131 | u8 fps; 132 | u8 unk_1F; // unk, default 0 133 | u64 size_of_some_buffer; // default 0, but this gets set by other code 134 | char padding[64 - 8 - 8 - 4 - 4 - 4 - 2 - 1 - 1 - 8]; 135 | u64 your_transfer_memory_size; 136 | } PACKED *raw; 137 | 138 | raw = ipcPrepareHeader(&c, sizeof(*raw)); 139 | 140 | if(R_SUCCEEDED(rc)) 141 | { 142 | memset(raw, 0, sizeof(*raw)); 143 | raw->magic = SFCI_MAGIC; 144 | raw->cmd_id = 1; 145 | 146 | raw->application_id = 0; 147 | raw->ar_uid = 0; 148 | raw->unk_10 = 8000000; // Probably bitrate? 149 | raw->unk_14 = 20000; 150 | raw->unk_18 = 0; 151 | raw->unk_1C = 30; 152 | raw->fps = 30; 153 | raw->unk_1F = 0; 154 | raw->size_of_some_buffer = 8*1024*1024; 155 | 156 | raw->your_transfer_memory_size = grc_transfermem_size; 157 | 158 | rc = serviceIpcDispatch(&g_grccSrv); 159 | if (R_SUCCEEDED(rc)) { 160 | IpcParsedCommand r; 161 | ipcParse(&r); 162 | struct { 163 | u64 magic; 164 | u64 result; 165 | } *resp = r.Raw; 166 | rc = resp->result; 167 | 168 | if (R_SUCCEEDED(rc)) { 169 | serviceCreate(&out->s, r.Handles[0]); 170 | } 171 | } 172 | } 173 | 174 | if(R_FAILED(rc)) 175 | { 176 | tmemClose(&out->tmem); 177 | } 178 | 179 | return rc; 180 | } 181 | 182 | Result grcContinuousRecorder_cmd_1(ContinuousRecorder *rec) 183 | { 184 | IpcCommand c; 185 | ipcInitialize(&c); 186 | 187 | struct { 188 | u64 magic; 189 | u64 cmd_id; 190 | } PACKED *raw; 191 | 192 | raw = ipcPrepareHeader(&c, sizeof(*raw)); 193 | 194 | raw->magic = SFCI_MAGIC; 195 | raw->cmd_id = 1; 196 | 197 | Result rc = serviceIpcDispatch(&rec->s); 198 | if (R_SUCCEEDED(rc)) { 199 | IpcParsedCommand r; 200 | ipcParse(&r); 201 | struct { 202 | u64 magic; 203 | u64 result; 204 | } *resp = r.Raw; 205 | rc = resp->result; 206 | } 207 | return rc; 208 | } 209 | 210 | // Main program entrypoint 211 | int main(int argc, char* argv[]) 212 | { 213 | consoleInit(NULL); 214 | 215 | // ok. 216 | Result rc = grcInitialize(); 217 | printf("grcInitialize %08x\n", rc); 218 | 219 | // Other initialization goes here. As a demonstration, we print hello world. 220 | printf("Hello World!\n"); 221 | 222 | ContinuousRecorder rec; 223 | 224 | rc = grcGetIContinuousRecorder(&rec); 225 | if(R_FAILED(rc)) 226 | { 227 | printf("grcGetIContinuousRecorder failed %08x\n", rc); 228 | } 229 | 230 | rc = grcContinuousRecorder_cmd_1(&rec); 231 | if(R_FAILED(rc)) 232 | { 233 | printf("grcContinuousRecorder_cmd_1 %08x\n", rc); 234 | } 235 | 236 | // Call this once, ever! 237 | rc = grcdCmd1(); 238 | if(R_FAILED(rc)) 239 | { 240 | printf("grcdCmd1 %08x\n", rc); 241 | } 242 | 243 | unsigned int buff_size = 0x32000; 244 | char *buff = malloc(buff_size); 245 | 246 | FILE *cap_file = fopen("test_capture.h264", "wb"); 247 | 248 | // Main loop 249 | while (appletMainLoop()) 250 | { 251 | //printf("Hello World!\n"); 252 | // Scan all the inputs. This should be done once for each frame 253 | hidScanInput(); 254 | 255 | // hidKeysDown returns information about which buttons have been 256 | // just pressed in this frame compared to the previous one 257 | u64 kDown = hidKeysDown(CONTROLLER_P1_AUTO); 258 | 259 | if (kDown & KEY_PLUS) 260 | break; // break in order to return to hbmenu 261 | 262 | // Your code goes here 263 | 264 | // Update the console, sending a new frame to the display 265 | consoleUpdate(NULL); 266 | 267 | u32 n_frames; 268 | u64 ts; 269 | size_t actual_size; 270 | 271 | memset(buff, 0xff, buff_size); 272 | rc = grcdCmd2(0, buff, buff_size, &n_frames, &ts, &actual_size); 273 | printf("grcdCmd2 %08x\n", rc); 274 | for(int i = 0; i < 0x100; i++) 275 | { 276 | printf("%02x ", buff[i]); 277 | } 278 | 279 | int n = fwrite(buff, 1, actual_size, cap_file); 280 | printf("wrote %i bytes\n", n); 281 | 282 | svcSleepThread(1e9); 283 | } 284 | 285 | fclose(cap_file); 286 | free(buff); 287 | 288 | serviceClose(&rec.s); 289 | tmemClose(&rec.tmem); 290 | grcExit(); 291 | // Deinitialize and clean up resources used by the console (important!) 292 | consoleExit(NULL); 293 | return 0; 294 | } 295 | --------------------------------------------------------------------------------