├── client ├── ctrrpc.py └── poke_gsp.py └── server ├── Makefile ├── data └── font.bin ├── include ├── gfx.h └── text.h └── source ├── gfx.c ├── main.c └── text.c /client/ctrrpc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2 2 | 3 | """ 4 | ctrrpc - A simple RPC client for poking the 3DS over the network. 5 | -plutoo 6 | 7 | Example (from a python2 terminal): 8 | > import ctrrpc 9 | > r = ctrrpc.ctrrpc(ip='') 10 | > r.querymem(0x100000) # Query kernel for info about memory mapped @ 0x100000. 11 | > r.r32(r.gettls()) # Read from thread-local-storage cmd-buffer. 12 | """ 13 | 14 | import socket 15 | import sys 16 | import struct 17 | 18 | class ctrrpc: 19 | s=None 20 | 21 | # Connect to rpc. 22 | def __init__(self, ip='65.22.33.112', port=8334, debug=False): 23 | self.s=socket.socket() 24 | self.s.connect((ip, port)) 25 | self.debug = debug 26 | 27 | # Decode response. 28 | def d(self, x): 29 | return struct.unpack(' 7: 35 | raise Exception('max len(args) == 7') 36 | while len(args) != 7: 37 | args.append(0) 38 | self.s.send(struct.pack(' 8: 86 | raise Exception('too long service name') 87 | name = name.encode('hex') 88 | while len(name) != 16: 89 | name = name + '00' 90 | namelo = int(name[0:8], 16) 91 | namehi = int(name[8:16], 16) 92 | 93 | namelo = struct.unpack('>I', struct.pack('I', struct.pack('devkitARM") 7 | endif 8 | 9 | ifeq ($(strip $(CTRULIB)),) 10 | # THIS IS TEMPORARY - in the future it should be at $(DEVKITPRO)/libctru 11 | $(error "Please set CTRULIB in your environment. export CTRULIB=libctru") 12 | endif 13 | 14 | include $(DEVKITARM)/3ds_rules 15 | 16 | #--------------------------------------------------------------------------------- 17 | # TARGET is the name of the output 18 | # BUILD is the directory where object files & intermediate files will be placed 19 | # SOURCES is a list of directories containing source code 20 | # DATA is a list of directories containing data files 21 | # INCLUDES is a list of directories containing header files 22 | # SPECS is the directory containing the important build and link files 23 | #--------------------------------------------------------------------------------- 24 | TARGET := $(notdir $(CURDIR)) 25 | BUILD := build 26 | SOURCES := source 27 | DATA := data 28 | INCLUDES := include 29 | 30 | 31 | #--------------------------------------------------------------------------------- 32 | # options for code generation 33 | #--------------------------------------------------------------------------------- 34 | ARCH := -marm 35 | 36 | CFLAGS := -g -Wall -O2 -mthumb-interwork -save-temps \ 37 | -mcpu=mpcore -mtune=mpcore -fomit-frame-pointer \ 38 | -mfpu=vfp -ffast-math -mword-relocations \ 39 | $(ARCH) 40 | 41 | CFLAGS += $(INCLUDE) -DARM11 -D_3DS -std=gnu99 42 | 43 | CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 44 | 45 | ASFLAGS := -g $(ARCH) 46 | LDFLAGS = -specs=3dsx.specs -g $(ARCH) \ 47 | -Wl,-d,-q,--use-blx,-Map,$(TARGET).map 48 | 49 | LIBS := -lctru 50 | 51 | #--------------------------------------------------------------------------------- 52 | # list of directories containing libraries, this must be the top level containing 53 | # include and lib 54 | #--------------------------------------------------------------------------------- 55 | LIBDIRS := $(CTRULIB) 56 | 57 | 58 | #--------------------------------------------------------------------------------- 59 | # no real need to edit anything past this point unless you need to add additional 60 | # rules for different file extensions 61 | #--------------------------------------------------------------------------------- 62 | ifneq ($(BUILD),$(notdir $(CURDIR))) 63 | #--------------------------------------------------------------------------------- 64 | 65 | export OUTPUT := $(CURDIR)/$(TARGET) 66 | 67 | export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ 68 | $(foreach dir,$(DATA),$(CURDIR)/$(dir)) 69 | 70 | export DEPSDIR := $(CURDIR)/$(BUILD) 71 | 72 | CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) 73 | CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) 74 | SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) 75 | BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) 76 | 77 | #--------------------------------------------------------------------------------- 78 | # use CXX for linking C++ projects, CC for standard C 79 | #--------------------------------------------------------------------------------- 80 | ifeq ($(strip $(CPPFILES)),) 81 | export LD := $(CC) 82 | else 83 | export LD := $(CXX) 84 | endif 85 | #--------------------------------------------------------------------------------- 86 | 87 | export OFILES := $(addsuffix .o,$(BINFILES)) \ 88 | $(CPPFILES:.cpp=.o) \ 89 | $(CFILES:.c=.o) \ 90 | $(SFILES:.s=.o) 91 | 92 | export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ 93 | $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ 94 | -I$(CURDIR)/$(BUILD) 95 | 96 | export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) 97 | 98 | .PHONY: $(BUILD) clean all 99 | 100 | #--------------------------------------------------------------------------------- 101 | all: $(BUILD) 102 | 103 | $(BUILD): 104 | @[ -d $@ ] || mkdir -p $@ 105 | @make --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile 106 | 107 | #--------------------------------------------------------------------------------- 108 | clean: 109 | @echo clean ... 110 | @rm -fr $(BUILD) $(TARGET).3dsx $(TARGET).elf 111 | 112 | 113 | #--------------------------------------------------------------------------------- 114 | else 115 | 116 | DEPENDS := $(OFILES:.o=.d) 117 | 118 | #--------------------------------------------------------------------------------- 119 | # main targets 120 | #--------------------------------------------------------------------------------- 121 | $(OUTPUT).3dsx: $(OUTPUT).elf 122 | $(OUTPUT).elf: $(OFILES) 123 | 124 | #--------------------------------------------------------------------------------- 125 | # you need a rule like this for each extension you use as binary data 126 | #--------------------------------------------------------------------------------- 127 | %.bin.o: %.bin 128 | #--------------------------------------------------------------------------------- 129 | @echo $(notdir $<) 130 | @$(bin2o) 131 | 132 | -include $(DEPENDS) 133 | 134 | #--------------------------------------------------------------------------------------- 135 | endif 136 | #--------------------------------------------------------------------------------------- 137 | -------------------------------------------------------------------------------- /server/data/font.bin: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /server/include/gfx.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //rendering stuff 4 | void gfxDrawText(gfxScreen_t screen, gfx3dSide_t side, const char *str, u16 x, u16 y); 5 | void gfxFillColor(gfxScreen_t screen, gfx3dSide_t side, u8 rgbColor[3]); 6 | -------------------------------------------------------------------------------- /server/include/text.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define CHAR_SIZE_X (8) 4 | #define CHAR_SIZE_Y (8) 5 | 6 | void drawString(u8 *fb, const char *str, u16 x, u16 y, u16 w, u16 h); 7 | -------------------------------------------------------------------------------- /server/source/gfx.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include <3ds.h> 5 | 6 | #include "gfx.h" 7 | #include "text.h" 8 | 9 | void 10 | gfxDrawText(gfxScreen_t screen, 11 | gfx3dSide_t side, 12 | const char *str, 13 | u16 x, 14 | u16 y) 15 | { 16 | if(!str) 17 | return; 18 | 19 | u16 fbWidth, fbHeight; 20 | u8 *fbAdr = gfxGetFramebuffer(screen, side, &fbWidth, &fbHeight); 21 | 22 | drawString(fbAdr, str, y, x-CHAR_SIZE_Y, fbHeight, fbWidth); 23 | } 24 | 25 | void 26 | gfxFillColor(gfxScreen_t screen, 27 | gfx3dSide_t side, 28 | u8 rgbColor[3]) 29 | { 30 | u16 fbWidth, fbHeight; 31 | u8 *fbAdr = gfxGetFramebuffer(screen, side, &fbWidth, &fbHeight); 32 | 33 | //TODO : optimize; use GX command ? 34 | int i; 35 | for(i = 0; i < fbWidth*fbHeight; ++i) 36 | { 37 | *(fbAdr++) = rgbColor[2]; 38 | *(fbAdr++) = rgbColor[1]; 39 | *(fbAdr++) = rgbColor[0]; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /server/source/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * ctrrpc - A simple RPC server for poking the 3DS over the network. 3 | * -plutoo 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include <3ds.h> 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | 19 | #include "gfx.h" 20 | 21 | #define PORT 8334 22 | #define MAX_LINES ((240-8)/8) 23 | #define __swap16(v) ((((v) & 0xFF) << 8) | ((v) >> 8)) 24 | 25 | typedef struct 26 | { 27 | char console[2048]; 28 | size_t lines; 29 | } console_t; 30 | 31 | static console_t top; 32 | static console_t bot; 33 | 34 | static void 35 | consoleClear(console_t *console) 36 | { 37 | memset(console->console, 0, sizeof(console->console)); 38 | console->lines = 0; 39 | } 40 | 41 | static void 42 | renderFrame(void) 43 | { 44 | u8 bluish[] = { 0, 0, 127 }; 45 | 46 | gfxFillColor(GFX_TOP, GFX_LEFT, bluish); 47 | gfxFillColor(GFX_BOTTOM, GFX_LEFT, bluish); 48 | 49 | gfxDrawText(GFX_TOP, GFX_LEFT, top.console, 240-8, 0); 50 | gfxDrawText(GFX_BOTTOM, GFX_LEFT, bot.console, 240-8, 0); 51 | 52 | gfxFlushBuffers(); 53 | gspWaitForVBlank(); 54 | gfxSwapBuffers(); 55 | } 56 | 57 | __attribute__((format(printf,2,3))) 58 | static void 59 | print(console_t *console, 60 | const char *fmt, ...) 61 | { 62 | static char buffer[256]; 63 | va_list ap; 64 | va_start(ap, fmt); 65 | vsiprintf(buffer, fmt, ap); 66 | va_end(ap); 67 | 68 | size_t num_lines = 0; 69 | const char *p = buffer; 70 | while((p = strchr(p, '\n')) != NULL) 71 | { 72 | ++num_lines; 73 | ++p; 74 | } 75 | 76 | if(console->lines + num_lines > MAX_LINES) 77 | { 78 | p = console->console; 79 | while(console->lines + num_lines > MAX_LINES) 80 | { 81 | p = strchr(p, '\n'); 82 | ++p; 83 | --console->lines; 84 | } 85 | 86 | memmove(console->console, p, strlen(p)+1); 87 | } 88 | 89 | strcat(console->console, buffer); 90 | console->lines = console->lines + num_lines; 91 | } 92 | 93 | int listen_socket; 94 | u64 thread_stack[0x1000/sizeof(u64)]; 95 | Handle new_cmd_event; 96 | Handle cmd_done_event; 97 | Handle thread; 98 | int thread_exit = 0; 99 | int last_cmd = 0; 100 | int last_cmd_result = 0; 101 | int sock; 102 | int enable_draw = 1; 103 | 104 | typedef struct { 105 | u8 type; 106 | u8 tmp[3]; 107 | u32 args[7]; 108 | } cmd_t; 109 | 110 | cmd_t resp; 111 | 112 | int execute_cmd(int sock, cmd_t* cmd) { 113 | memset(&resp, 0, sizeof(resp)); 114 | 115 | switch(cmd->type) { 116 | case 0: // exit 117 | return 0xDEAD; 118 | 119 | case 1: { // read u32 120 | u32* p = (u32*) cmd->args[0]; 121 | resp.args[0] = *p; 122 | break; 123 | } 124 | 125 | case 2: { // write u32 126 | u32* p = (u32*) cmd->args[0]; 127 | *p = cmd->args[1]; 128 | break; 129 | } 130 | 131 | case 3: { // get tls 132 | resp.args[0] = (u32) getThreadCommandBuffer(); 133 | break; 134 | } 135 | 136 | case 4: { // querymem 137 | MemInfo info; 138 | PageInfo flags; 139 | memset(&info, 0, sizeof(info)); 140 | memset(&flags, 0, sizeof(flags)); 141 | 142 | int ret = svcQueryMemory(&info, &flags, cmd->args[0]); 143 | resp.args[0] = ret; 144 | resp.args[1] = info.base_addr; 145 | resp.args[2] = info.size; 146 | resp.args[3] = info.perm; 147 | resp.args[4] = info.state; 148 | resp.args[5] = flags.flags; 149 | break; 150 | } 151 | 152 | case 5: { // creatememblock 153 | u32 handle = 0; 154 | int ret = svcCreateMemoryBlock(&handle, cmd->args[0], 155 | cmd->args[1], cmd->args[2], cmd->args[3]); 156 | 157 | resp.args[0] = ret; 158 | resp.args[1] = handle; 159 | break; 160 | } 161 | 162 | case 6: { // controlmem 163 | u32 outaddr = 0; 164 | int ret = svcControlMemory(&outaddr, cmd->args[0], cmd->args[1], 165 | cmd->args[2], cmd->args[3], cmd->args[4]); 166 | 167 | resp.args[0] = ret; 168 | resp.args[1] = outaddr; 169 | break; 170 | } 171 | 172 | case 7: { // getservicehandle 173 | Handle handle = 0; 174 | int ret = srvGetServiceHandle(&handle, (const char*) &cmd->args[0]); 175 | 176 | resp.args[0] = ret; 177 | resp.args[1] = handle; 178 | break; 179 | } 180 | 181 | case 8: { // syncrequest 182 | int ret = svcSendSyncRequest(cmd->args[0]); 183 | 184 | resp.args[0] = ret; 185 | break; 186 | } 187 | 188 | case 9: { // closehandle 189 | int ret = svcCloseHandle(cmd->args[0]); 190 | 191 | resp.args[0] = ret; 192 | break; 193 | } 194 | 195 | case 10: { // getctrulibhandle 196 | switch(cmd->args[0]) { 197 | 198 | case 0: { // gsp_handle 199 | extern Handle gspGpuHandle; 200 | resp.args[0] = gspGpuHandle; 201 | break; 202 | } 203 | } 204 | 205 | break; 206 | } 207 | 208 | case 11: { // malloc/free 209 | char* p = NULL; 210 | 211 | switch(cmd->args[0]) { 212 | case 0: { // normal-mem 213 | p = malloc(0x100); 214 | break; 215 | } 216 | 217 | case 1: { // linear-mem 218 | p = linearAlloc(0x100); 219 | break; 220 | } 221 | 222 | case 2: { // free normal-mem 223 | free((void*)cmd->args[1]); 224 | break; 225 | } 226 | 227 | case 3: { // free linear-mem 228 | linearFree((void*)cmd->args[1]); 229 | break; 230 | } 231 | } 232 | 233 | resp.args[0] = (u32) p; 234 | break; 235 | } 236 | 237 | case 12: { // enable/disable drawing 238 | enable_draw = cmd->args[0]; 239 | break; 240 | } 241 | 242 | default: 243 | return 0xDEAD; // unknown cmd 244 | } 245 | 246 | return 0; 247 | } 248 | 249 | cmd_t cmd; 250 | 251 | void conn_main() { 252 | APP_STATUS status; 253 | u32 it = 0; 254 | int ret = 0; 255 | int first = 1; 256 | int exiting = 0; 257 | 258 | while((status = aptGetStatus()) != APP_EXITING) 259 | { 260 | hidScanInput(); 261 | consoleClear(&bot); 262 | 263 | print(&bot, "frame: %08x\n", it); 264 | print(&bot, "ret: %08x\n", ret); 265 | print(&bot, "last_cmd: %02x\n", last_cmd & 0xFF); 266 | 267 | if(!first) { 268 | u32 bytes_read = 0; 269 | 270 | while(1) { 271 | ret = recv(sock, &cmd, sizeof(cmd), 0); 272 | if(ret < 0) { 273 | if(ret == -EWOULDBLOCK) 274 | continue; 275 | break; 276 | } 277 | 278 | bytes_read += ret; 279 | if(bytes_read == sizeof(cmd)) { 280 | svcSignalEvent(new_cmd_event); 281 | svcWaitSynchronization(cmd_done_event, U64_MAX); 282 | svcClearEvent(cmd_done_event); 283 | 284 | send(sock, &resp, sizeof(resp), 0); 285 | 286 | if(last_cmd_result == 0xDEAD) 287 | exiting = 1; 288 | break; 289 | } 290 | } 291 | } 292 | 293 | first = 0; 294 | it++; 295 | 296 | if(enable_draw) 297 | renderFrame(); 298 | 299 | u32 keys = hidKeysUp(); 300 | if(keys & KEY_A || exiting) 301 | break; 302 | } 303 | } 304 | 305 | void cmd_thread_func() { 306 | while(1) { 307 | svcWaitSynchronization(new_cmd_event, U64_MAX); 308 | svcClearEvent(new_cmd_event); 309 | 310 | if(thread_exit) svcExitThread(); 311 | 312 | last_cmd = cmd.type; 313 | last_cmd_result = execute_cmd(sock, &cmd); 314 | 315 | svcSignalEvent(cmd_done_event); 316 | } 317 | } 318 | 319 | 320 | /*----------------*/ 321 | int main(int argc, char *argv[]) 322 | { 323 | APP_STATUS status; 324 | 325 | srvInit(); 326 | aptInit(APPID_APPLICATION); 327 | gfxInit(); 328 | hidInit(NULL); 329 | fsInit(); 330 | 331 | svcCreateEvent(&new_cmd_event, 0); 332 | svcCreateEvent(&cmd_done_event, 0); 333 | svcCreateThread(&thread, cmd_thread_func, 0x0, 334 | (u32*)((char*)thread_stack + sizeof(thread_stack)), 335 | 0x31, 0xfffffffe); 336 | 337 | int where = 0; 338 | u32 ret = SOC_Initialize((u32*)0x08000000, 0x48000); 339 | 340 | if(ret == 0) { 341 | listen_socket = socket(AF_INET, SOCK_STREAM, 0); 342 | if(listen_socket == -1) { 343 | where = 1; 344 | ret = SOC_GetErrno(); 345 | } 346 | else { 347 | u32 tmp = fcntl(listen_socket, F_GETFL); 348 | fcntl(listen_socket, F_SETFL, tmp | O_NONBLOCK); 349 | 350 | struct sockaddr_in addr; 351 | addr.sin_family = AF_INET; 352 | addr.sin_port = __swap16(PORT); 353 | addr.sin_addr.s_addr = INADDR_ANY; 354 | 355 | ret = bind(listen_socket, (struct sockaddr *)&addr, sizeof(addr)); 356 | if(ret != 0) { 357 | where = 2; 358 | ret = SOC_GetErrno(); 359 | } 360 | else { 361 | ret = listen(listen_socket, 1); 362 | if(ret == -1) { 363 | ret = SOC_GetErrno(); 364 | where = 3; 365 | } 366 | } 367 | } 368 | 369 | } 370 | 371 | u32 it = 0; 372 | int accept_errno = 0; 373 | int first = 1; 374 | 375 | 376 | while((status = aptGetStatus()) != APP_EXITING) 377 | { 378 | hidScanInput(); 379 | consoleClear(&top); 380 | 381 | print(&top, "newver\n"); 382 | print(&top, "ret: %08x, where: %d\n", ret, where); 383 | print(&top, "frame: %08x\n", it); 384 | u32 ip = gethostid(); 385 | print(&top, "ip: %d.%d.%d.%d\n", ip & 0xFF, (ip>>8)&0xFF, (ip>>16)&0xFF, (ip>>24)&0xFF); 386 | 387 | if(accept_errno != 0) print(&top, "accept returned errno %d\n", accept_errno); 388 | 389 | if(!first) { 390 | int s = accept(listen_socket, NULL, NULL); 391 | if(s == -1) { 392 | int err = SOC_GetErrno(); 393 | 394 | if(err != -EWOULDBLOCK) 395 | accept_errno = err; 396 | } 397 | else { 398 | sock = s; 399 | conn_main(); 400 | closesocket(sock); 401 | } 402 | } 403 | 404 | it++; 405 | first = 0; 406 | if(enable_draw) 407 | renderFrame(); 408 | 409 | u32 keys = hidKeysUp(); 410 | if(keys & KEY_A) 411 | break; 412 | } 413 | 414 | thread_exit = 1; 415 | svcSignalEvent(new_cmd_event); 416 | svcWaitSynchronization(thread, U64_MAX); 417 | svcCloseHandle(thread); 418 | 419 | svcCloseHandle(new_cmd_event); 420 | svcCloseHandle(cmd_done_event); 421 | 422 | SOC_Shutdown(); 423 | fsExit(); 424 | hidExit(); 425 | gfxExit(); 426 | aptExit(); 427 | srvExit(); 428 | return 0; 429 | } 430 | -------------------------------------------------------------------------------- /server/source/text.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include <3ds.h> 5 | #include "text.h" 6 | #include "font_bin.h" 7 | 8 | const u8 *font = font_bin; 9 | 10 | static void 11 | drawCharacter(u8 *fb, 12 | char c, 13 | u16 x, 14 | u16 y, 15 | u16 w, 16 | u16 h) 17 | { 18 | if(c < ' ') 19 | return; 20 | 21 | if(x < 0 || y < 0 || x+CHAR_SIZE_X >= w || y+CHAR_SIZE_Y >= h) 22 | return; 23 | 24 | c -= ' '; 25 | 26 | u8 *charData=(u8*)&font_bin[CHAR_SIZE_X*CHAR_SIZE_Y*c]; 27 | 28 | fb += (x*h+y)*3; 29 | 30 | int i, j; 31 | for(i = 0; i < CHAR_SIZE_X; ++i) 32 | { 33 | for(j = 0; j < CHAR_SIZE_Y; ++j) 34 | { 35 | u8 v = *(charData++); 36 | if(v) 37 | fb[0] = fb[1] = fb[2] = (v==1) ? 0xFF : 0x00; 38 | fb += 3; 39 | } 40 | 41 | fb += (h-CHAR_SIZE_Y)*3; 42 | } 43 | } 44 | 45 | void 46 | drawString(u8 *fb, 47 | const char *str, 48 | u16 x, 49 | u16 y, 50 | u16 w, 51 | u16 h) 52 | { 53 | if(!fb || !str) 54 | return; 55 | 56 | int k, dx = 0, dy = 0; 57 | for(k = 0; k < strlen(str); ++k) 58 | { 59 | if(str[k] >= ' ' && str[k] <= '~') 60 | drawCharacter(fb, str[k], x+dx, y+dy, w, h); 61 | 62 | dx += 8; 63 | if(str[k]=='\n') 64 | { 65 | dx = 0; 66 | dy -= 8; 67 | } 68 | } 69 | } 70 | --------------------------------------------------------------------------------