#include #include #include #include "common/common.h" #include // Actually must be included before os_functions #include "dynamic_libs/os_functions.h" #include #include #include "main.h" #include "dynamic_libs/socket_functions.h" #include "dynamic_libs/gx2_functions.h" #include "kernel/syscalls.h" #include "dynamic_libs/fs_functions.h" #include "common/fs_defs.h" #include "system/exception_handler.h" void *client; void *commandBlock; bool kernelCopyServiceStarted; struct pygecko_bss_t { int error, line; void *thread; unsigned char stack[0x6F00]; }; /* TCP Gecko Commands */ #define COMMAND_WRITE_8 0x01 #define COMMAND_WRITE_16 0x02 #define COMMAND_WRITE_32 0x03 #define COMMAND_READ_MEMORY 0x04 #define COMMAND_READ_MEMORY_KERNEL 0x05 #define COMMAND_VALIDATE_ADDRESS_RANGE 0x06 #define COMMAND_MEMORY_DISASSEMBLE 0x08 #define COMMAND_READ_MEMORY_COMPRESSED 0x09 // TODO Remove command when done and integrate in read memory #define COMMAND_KERNEL_WRITE 0x0B #define COMMAND_KERNEL_READ 0x0C #define COMMAND_TAKE_SCREEN_SHOT 0x0D // TODO Finish this #define COMMAND_UPLOAD_MEMORY 0x41 #define COMMAND_SERVER_STATUS 0x50 #define COMMAND_GET_DATA_BUFFER_SIZE 0x51 #define COMMAND_READ_FILE 0x52 #define COMMAND_READ_DIRECTORY 0x53 #define COMMAND_REPLACE_FILE 0x54 // TODO Test this #define COMMAND_GET_CODE_HANDLER_ADDRESS 0x55 #define COMMAND_READ_THREADS 0x56 #define COMMAND_ACCOUNT_IDENTIFIER 0x57 #define COMMAND_WRITE_SCREEN 0x58 // TODO Exception DSI #define COMMAND_FOLLOW_POINTER 0x60 #define COMMAND_RPC 0x70 #define COMMAND_GET_SYMBOL 0x71 #define COMMAND_MEMORY_SEARCH 0x73 // #define COMMAND_SYSTEM_CALL 0x80 #define COMMAND_EXECUTE_ASSEMBLY 0x81 #define COMMAND_SERVER_VERSION 0x99 #define COMMAND_OS_VERSION 0x9A #define COMMAND_RUN_KERNEL_COPY_SERVICE 0xCD #define CHECK_ERROR(cond) if (cond) { bss->line = __LINE__; goto error; } #define errno (*__gh_errno_ptr()) #define MSG_DONTWAIT 32 #define EWOULDBLOCK 6 #define FS_BUFFER_SIZE 0x1000 #define DATA_BUFFER_SIZE 0x5000 #define DISASSEMBLER_BUFFER_SIZE 0x1024 #define WRITE_SCREEN_MESSAGE_BUFFER_SIZE 100 #define SERVER_VERSION "02/25/2017" #define ONLY_ZEROS_READ 0xB0 #define NON_ZEROS_READ 0xBD #define ASSERT_MINIMUM_HOLDS(actual, minimum, variableName) \ if(actual < minimum) { \ char buffer[100] = {0}; \ __os_snprintf(buffer, 100, "%s: Limit exceeded (minimum: %i, actual: %i)", variableName, minimum, actual); \ OSFatal(buffer); \ } \ #define ASSERT_MAXIMUM_HOLDS(maximum, actual, variableName) \ if(actual > maximum) { \ char buffer[100] = {0}; \ __os_snprintf(buffer, 100, "%s: Limit exceeded (maximum: %i, actual: %i)", variableName, maximum, actual); \ OSFatal(buffer); \ } \ #define ASSERT_FUNCTION_SUCCEEDED(returnValue, functionName) \ if (returnValue < 0) { \ char buffer[100] = {0}; \ __os_snprintf(buffer, 100, "%s failed with return value: %i", functionName, returnValue); \ OSFatal(buffer); \ } \ #define ASSERT_VALID_EFFECTIVE_ADDRESS(effectiveAddress, message) \ if(!OSIsAddressValid((void *) effectiveAddress)) { \ char buffer[100] = {0}; \ __os_snprintf(buffer, 100, "Address %04x invalid: %s", effectiveAddress, message); \ OSFatal(buffer); \ } \ #define ASSERT_INTEGER(actual, expected, name) \ if(actual != expected) { \ char buffer[50] = {0}; \ __os_snprintf(buffer, 50, "%s assertion failed: %i == %i", name, actual, expected); \ OSFatal(buffer); \ } \ #define ASSERT_STRING(actual, expected) \ if(strcmp(actual, expected) != 0) { \ char buffer[50] = {0}; \ __os_snprintf(buffer, 50, "String assertion failed: \"%s\" == \"%s\"", actual, expected); \ OSFatal(buffer); \ } \ #define ASSERT_ALLOCATED(variable, name) \ if(variable == 0) { \ char buffer[50] = {0}; \ __os_snprintf(buffer, 50, "%s allocation failed", name); \ OSFatal(buffer); \ } \ ZEXTERN int ZEXPORT deflateEnd OF((z_streamp strm)); ZEXTERN int ZEXPORT deflateInit OF((z_streamp strm, int level )); ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush )); /*struct breakpoint { u32 address; u32 instruction; }; // 10 general breakpoints + 2 step breakpoints breakpoint breakpoints[12]; breakpoint *getBreakpoint(u32 address, int size) { breakpoint *breakpointsList = breakpoints; for (int breakpointIndex = 0; breakpointIndex < size; breakpointIndex++) { if (breakpointsList[breakpointIndex].address == address) { return &breakpointsList[breakpointIndex]; } } return 0; }*/ unsigned char *memcpy_buffer[DATA_BUFFER_SIZE]; void pygecko_memcpy(unsigned char *destinationBuffer, unsigned char *sourceBuffer, unsigned int length) { memcpy(memcpy_buffer, sourceBuffer, length); SC0x25_KernelCopyData((unsigned int) OSEffectiveToPhysical(destinationBuffer), (unsigned int) &memcpy_buffer, length); DCFlushRange(destinationBuffer, (u32) length); } unsigned char *kernelCopyBuffer[4]; void kernelCopy(unsigned char *destinationBuffer, unsigned char *sourceBuffer, unsigned int length) { memcpy(kernelCopyBuffer, sourceBuffer, length); unsigned int destinationAddress = (unsigned int) OSEffectiveToPhysical(destinationBuffer); SC0x25_KernelCopyData(destinationAddress, (unsigned int) &kernelCopyBuffer, length); DCFlushRange(destinationBuffer, (u32) length); } #define KERNEL_COPY_SOURCE_ADDRESS 0x10100000 int kernelCopyService(int argc, void *argv) { while (true) { // Read the destination address from the source address int destinationAddress = *(int *) KERNEL_COPY_SOURCE_ADDRESS; // Avoid crashing if (OSIsAddressValid((const void *) destinationAddress)) { // Perform memory copy unsigned char *valueBuffer = (unsigned char *) (KERNEL_COPY_SOURCE_ADDRESS + 4); kernelCopy((unsigned char *) destinationAddress, valueBuffer, 4); // "Consume" address and value for synchronization with the code handler for instance *(int *) KERNEL_COPY_SOURCE_ADDRESS = 0; *(((int *) KERNEL_COPY_SOURCE_ADDRESS) + 1) = 0; } } } void startKernelCopyService() { unsigned int stack = (unsigned int) memalign(0x40, 0x100); ASSERT_ALLOCATED(stack, "Kernel copy thread stack") stack += 0x100; void *thread = memalign(0x40, 0x1000); ASSERT_ALLOCATED(thread, "Kernel copy thread") int status = OSCreateThread(thread, kernelCopyService, 1, NULL, (u32) stack + sizeof(stack), sizeof(stack), 31, OS_THREAD_ATTR_AFFINITY_CORE1 | OS_THREAD_ATTR_PINNED_AFFINITY | OS_THREAD_ATTR_DETACH); ASSERT_INTEGER(status, 1, "Creating kernel copy thread") // OSSetThreadName(thread, "Kernel Copier"); OSResumeThread(thread); } /*Validates the address range (last address inclusive) but is SLOW on bigger ranges */ static int validateAddressRange(int starting_address, int ending_address) { return __OSValidateAddressSpaceRange(1, (void *) starting_address, ending_address - starting_address + 1); } static int recvwait(struct pygecko_bss_t *bss, int sock, void *buffer, int len) { int ret; while (len > 0) { ret = recv(sock, buffer, len, 0); CHECK_ERROR(ret < 0); len -= ret; buffer += ret; } return 0; error: bss->error = ret; return ret; } static int recvbyte(struct pygecko_bss_t *bss, int sock) { unsigned char buffer[1]; int ret; ret = recvwait(bss, sock, buffer, 1); if (ret < 0) return ret; return buffer[0]; } static int checkbyte(int sock) { unsigned char buffer[1]; int ret; ret = recv(sock, buffer, 1, MSG_DONTWAIT); if (ret < 0) return ret; if (ret == 0) return -1; return buffer[0]; } static int sendwait(struct pygecko_bss_t *bss, int sock, const void *buffer, int len) { int ret; while (len > 0) { ret = send(sock, buffer, len, 0); CHECK_ERROR(ret < 0); len -= ret; buffer += ret; } return 0; error: bss->error = ret; return ret; } static int sendbyte(struct pygecko_bss_t *bss, int sock, unsigned char byte) { unsigned char buffer[1]; buffer[0] = byte; return sendwait(bss, sock, buffer, 1); } /*void performSystemCall(int value) { // TODO Exception DSI? asm( "li 0, %0\n" "sc\n" "blr\n" : // No output :"r"(value) // Input :"0" // Overwritten register ); }*/ void writeScreen(char message[100], int secondsDelay) { // TODO Does nothing then crashes (in games)? OSScreenClearBufferEx(0, 0); OSScreenClearBufferEx(1, 0); OSScreenPutFontEx(0, 14, 1, message); OSScreenPutFontEx(1, 14, 1, message); sleep(secondsDelay); OSScreenFlipBuffersEx(0); OSScreenFlipBuffersEx(1); } void receiveString(struct pygecko_bss_t *bss, int clientfd, char *stringBuffer, int bufferSize) { // Receive the string length char lengthBuffer[4] = {0}; int ret = recvwait(bss, clientfd, lengthBuffer, 4); ASSERT_FUNCTION_SUCCEEDED(ret, "recvwait (string length)") int stringLength = ((int *) lengthBuffer)[0]; if (stringLength >= 0 && stringLength <= bufferSize) { // Receive the actual string ret = recvwait(bss, clientfd, stringBuffer, stringLength); ASSERT_FUNCTION_SUCCEEDED(ret, "recvwait (string)") } else { OSFatal("String buffer size exceeded"); } } void considerInitializingFileSystem() { if (!client) { // Initialize the file system int status = FSInit(); ASSERT_FUNCTION_SUCCEEDED(status, "FSInit") // Allocate the client client = malloc(FS_CLIENT_SIZE); ASSERT_ALLOCATED(client, "Client") // Register the client status = FSAddClientEx(client, 0, -1); ASSERT_FUNCTION_SUCCEEDED(status, "FSAddClientEx") // Allocate the command block commandBlock = malloc(FS_CMD_BLOCK_SIZE); ASSERT_ALLOCATED(commandBlock, "Command block") FSInitCmdBlock(commandBlock); } } char *disassemblerBuffer; void *disassemblerBufferPointer; void formatDisassembled(char *format, ...) { if (!disassemblerBuffer) { disassemblerBuffer = malloc(DISASSEMBLER_BUFFER_SIZE); ASSERT_ALLOCATED(disassemblerBuffer, "Disassembler buffer") disassemblerBufferPointer = disassemblerBuffer; } va_list variableArguments; va_start(variableArguments, format); char *temporaryBuffer; int printedBytesCount = vasprintf(&temporaryBuffer, format, variableArguments); ASSERT_ALLOCATED(temporaryBuffer, "Temporary buffer") ASSERT_MINIMUM_HOLDS(printedBytesCount, 1, "Printed bytes count") va_end(variableArguments); // Do not smash the buffer long projectedSize = (void *) disassemblerBuffer - disassemblerBufferPointer + printedBytesCount; if (projectedSize < DISASSEMBLER_BUFFER_SIZE) { memcpy(disassemblerBuffer, temporaryBuffer, printedBytesCount); disassemblerBuffer += printedBytesCount; } free(temporaryBuffer); } bool isValidDataAddress(int address) { return OSIsAddressValid((const void *) address) && address >= 0x10000000 && address < 0x50000000; } int roundUpToAligned(int number) { return (number + 3) & ~0x03; } #define ERROR_BUFFER_SIZE 100 void reportIllegalCommandByte(int commandByte) { char errorBuffer[ERROR_BUFFER_SIZE]; __os_snprintf(errorBuffer, ERROR_BUFFER_SIZE, "Illegal command byte received: 0x%02x\nServer Version: %s", commandByte, SERVER_VERSION); OSFatal(errorBuffer); } void writeInt(unsigned int address, unsigned int value) { pygecko_memcpy((unsigned char *) address, (unsigned char *) &value, 4); } #define MINIMUM_KERNEL_COMPARE_LENGTH 4 #define KERNEL_MEMORY_COMPARE_STEP_SIZE 1 int kernelMemoryCompare(const void *sourceBuffer, const void *destinationBuffer, int length) { if (length < MINIMUM_KERNEL_COMPARE_LENGTH) { ASSERT_MINIMUM_HOLDS(length, MINIMUM_KERNEL_COMPARE_LENGTH, "length"); } bool loopEntered = false; while (kern_read(sourceBuffer) == kern_read(destinationBuffer)) { loopEntered = true; sourceBuffer = (char *) sourceBuffer + KERNEL_MEMORY_COMPARE_STEP_SIZE; destinationBuffer = (char *) destinationBuffer + KERNEL_MEMORY_COMPARE_STEP_SIZE; length -= KERNEL_MEMORY_COMPARE_STEP_SIZE; if (length <= MINIMUM_KERNEL_COMPARE_LENGTH - 1) { break; } } if (loopEntered) { sourceBuffer -= KERNEL_MEMORY_COMPARE_STEP_SIZE; destinationBuffer -= KERNEL_MEMORY_COMPARE_STEP_SIZE; } return kern_read(sourceBuffer) - kern_read(destinationBuffer); } static int rungecko(struct pygecko_bss_t *bss, int clientfd) { int ret; // Hold the command and the data unsigned char buffer[1 + DATA_BUFFER_SIZE]; // Run the RPC server while (true) { ret = checkbyte(clientfd); if (ret < 0) { CHECK_ERROR(errno != EWOULDBLOCK); GX2WaitForVsync(); continue; } switch (ret) { case COMMAND_WRITE_8: { char *destinationAddress; ret = recvwait(bss, clientfd, buffer, 8); CHECK_ERROR(ret < 0); destinationAddress = ((char **) buffer)[0]; *destinationAddress = buffer[7]; DCFlushRange(destinationAddress, 1); break; } case COMMAND_WRITE_16: { short *destinationAddress; ret = recvwait(bss, clientfd, buffer, 8); CHECK_ERROR(ret < 0) destinationAddress = ((short **) buffer)[0]; *destinationAddress = ((short *) buffer)[3]; DCFlushRange(destinationAddress, 2); break; } case COMMAND_WRITE_32: { int destinationAddress, value; ret = recvwait(bss, clientfd, buffer, 8); CHECK_ERROR(ret < 0) destinationAddress = ((int *) buffer)[0]; value = ((int *) buffer)[1]; pygecko_memcpy((unsigned char *) destinationAddress, (unsigned char *) &value, 4); break; } case COMMAND_READ_MEMORY: { const unsigned char *startingAddress, *endingAddress; ret = recvwait(bss, clientfd, buffer, 2 * 4); CHECK_ERROR(ret < 0) startingAddress = ((const unsigned char **) buffer)[0]; endingAddress = ((const unsigned char **) buffer)[1]; while (startingAddress != endingAddress) { int length = (int) (endingAddress - startingAddress); // Do not smash the buffer if (length > DATA_BUFFER_SIZE) { length = DATA_BUFFER_SIZE; } // Figure out if all bytes are zero to possibly avoid sending them int rangeIterationIndex = 0; for (; rangeIterationIndex < length; rangeIterationIndex++) { int character = startingAddress[rangeIterationIndex]; if (character != 0) { break; } } if (rangeIterationIndex == length) { // No need to send all zero bytes for performance ret = sendbyte(bss, clientfd, ONLY_ZEROS_READ); CHECK_ERROR(ret < 0) } else { // TODO Compression of ptr, sending of status, compressed size and data, length: 1 + 4 + len(data) buffer[0] = NON_ZEROS_READ; memcpy(buffer + 1, startingAddress, length); ret = sendwait(bss, clientfd, buffer, length + 1); CHECK_ERROR(ret < 0) } /* No exit condition. We reconnect client-sided instead as a hacky work-around to gain a little more performance by avoiding the very rare search canceling */ startingAddress += length; } break; } case COMMAND_READ_MEMORY_KERNEL: { const unsigned char *startingAddress, *endingAddress, *useKernRead; ret = recvwait(bss, clientfd, buffer, 3 * 4); CHECK_ERROR(ret < 0) startingAddress = ((const unsigned char **) buffer)[0]; endingAddress = ((const unsigned char **) buffer)[1]; useKernRead = ((const unsigned char **) buffer)[2]; while (startingAddress != endingAddress) { int length = (int) (endingAddress - startingAddress); // Do not smash the buffer if (length > DATA_BUFFER_SIZE) { length = DATA_BUFFER_SIZE; } // Figure out if all bytes are zero to possibly avoid sending them int rangeIterationIndex = 0; for (; rangeIterationIndex < length; rangeIterationIndex++) { int character = useKernRead ? kern_read(startingAddress + rangeIterationIndex) : startingAddress[rangeIterationIndex]; if (character != 0) { break; } } if (rangeIterationIndex == length) { // No need to send all zero bytes for performance ret = sendbyte(bss, clientfd, ONLY_ZEROS_READ); CHECK_ERROR(ret < 0) } else { // TODO Compression of ptr, sending of status, compressed size and data, length: 1 + 4 + len(data) buffer[0] = NON_ZEROS_READ; if (useKernRead) { for (int offset = 0; offset < length; offset += 4) { *((int *) (buffer + 1) + offset / 4) = kern_read(startingAddress + offset); } } else { memcpy(buffer + 1, startingAddress, length); } ret = sendwait(bss, clientfd, buffer, length + 1); CHECK_ERROR(ret < 0) } /* No exit condition. We reconnect client-sided instead as a hacky work-around to gain a little more performance by avoiding the very rare search canceling */ startingAddress += length; } break; } case COMMAND_VALIDATE_ADDRESS_RANGE: { ret = recvwait(bss, clientfd, buffer, 8); CHECK_ERROR(ret < 0) // Retrieve the data int startingAddress = ((int *) buffer)[0]; int endingAddress = ((int *) buffer)[1]; int isAddressRangeValid = validateAddressRange(startingAddress, endingAddress); sendbyte(bss, clientfd, (unsigned char) isAddressRangeValid); break; } /*case COMMAND_DISASSEMBLE_RANGE: { // Receive the starting, ending address and the disassembler options ret = recvwait(bss, clientfd, buffer, 4 + 4 + 4); CHECK_ERROR(ret < 0) void *startingAddress = ((void **) buffer)[0]; void *endingAddress = ((void **) buffer)[1]; int disassemblerOptions = ((int *) buffer)[2]; // Disassemble DisassemblePPCRange(startingAddress, endingAddress, formatDisassembled, OSGetSymbolName, (u32) disassemblerOptions); // Send the disassembler buffer size int length = DISASSEMBLER_BUFFER_SIZE; ret = sendwait(bss, clientfd, &length, 4); ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (disassembler buffer size)") // Send the data ret = sendwait(bss, clientfd, disassemblerBufferPointer, length); ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (disassembler buffer)") // Place the pointer back to the beginning disassemblerBuffer = (char *) disassemblerBufferPointer; break; }*/ case COMMAND_MEMORY_DISASSEMBLE: { // Receive the starting address, ending address and disassembler options ret = recvwait(bss, clientfd, buffer, 4 + 4 + 4); CHECK_ERROR(ret < 0) int startingAddress = ((int *) buffer)[0]; int endingAddress = ((int *) buffer)[1]; int disassemblerOptions = ((int *) buffer)[2]; int currentAddress = startingAddress; int bufferSize = PPC_DISASM_MAX_BUFFER; int integerSize = 4; // Disassemble everything while (currentAddress < endingAddress) { int currentIntegerIndex = 0; while ((currentIntegerIndex < (DATA_BUFFER_SIZE / integerSize)) && (currentAddress < endingAddress)) { int value = *(int *) currentAddress; ((int *) buffer)[currentIntegerIndex++] = value; char *opCodeBuffer = malloc(bufferSize); bool status = DisassemblePPCOpcode((u32 *) currentAddress, opCodeBuffer, (u32) bufferSize, OSGetSymbolName, (u32) disassemblerOptions); ((int *) buffer)[currentIntegerIndex++] = status; if (status == 1) { // Send the length of the opCode buffer string int length = strlen(opCodeBuffer); ((int *) buffer)[currentIntegerIndex++] = length; // Send the opCode buffer itself memcpy(buffer + (currentIntegerIndex * integerSize), opCodeBuffer, length); currentIntegerIndex += (roundUpToAligned(length) / integerSize); } free(opCodeBuffer); currentAddress += integerSize; } int bytesToSend = currentIntegerIndex * integerSize; ret = sendwait(bss, clientfd, &bytesToSend, 4); ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (Buffer size)") // VALUE(4)|STATUS(4)|LENGTH(4)|DISASSEMBLED(LENGTH) ret = sendwait(bss, clientfd, buffer, bytesToSend); ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (Buffer)") } int bytesToSend = 0; ret = sendwait(bss, clientfd, &bytesToSend, 4); ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (No more bytes)") break; } case COMMAND_READ_MEMORY_COMPRESSED: { // Receive the starting address and length ret = recvwait(bss, clientfd, buffer, 4 + 4); CHECK_ERROR(ret < 0) int startingAddress = ((int *) buffer)[0]; unsigned int inputLength = ((unsigned int *) buffer)[1]; z_stream stream; memset(&stream, 0, sizeof(stream)); stream.zalloc = Z_NULL; stream.zfree = Z_NULL; stream.opaque = Z_NULL; // Initialize the stream struct ret = deflateInit(&stream, Z_BEST_COMPRESSION); ASSERT_INTEGER(ret, Z_OK, "deflateInit") // Supply the data stream.avail_in = inputLength; stream.next_in = (Bytef *) startingAddress; stream.avail_out = DATA_BUFFER_SIZE; void *outputBuffer = (void *) (&buffer + 4); stream.next_out = (Bytef *) outputBuffer; // Deflate ret = deflate(&stream, Z_FINISH); ASSERT_INTEGER(ret, Z_OK, "deflate"); // Finish ret = deflateEnd(&stream); ASSERT_INTEGER(ret, Z_OK, "deflateEnd"); // Send the compressed buffer size and content int deflatedSize = stream.total_out; ((int *) buffer)[0] = deflatedSize; ret = sendwait(bss, clientfd, buffer, 4 + deflatedSize); ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (Compressed data)") break; // https://www.gamedev.net/resources/_/technical/game-programming/in-memory-data-compression-and-decompression-r2279 /* // Setup compressed buffer unsigned int compressedBufferSize = length * 2; void *compressedBuffer = (void *) OSAllocFromSystem(compressedBufferSize, 0x4); ASSERT_ALLOCATED(compressedBuffer, "Compressed buffer") unsigned int zlib_handle; OSDynLoad_Acquire("zlib125.rpl", (u32 *) &zlib_handle); int (*compress2)(char *, int *, const char *, int, int); OSDynLoad_FindExport((u32) zlib_handle, 0, "compress2", &compress2); int destinationBufferSize; int status = compress2((char *) compressedBuffer, &destinationBufferSize, (const char *) rawBuffer, length, Z_DEFAULT_COMPRESSION); ret = sendwait(bss, clientfd, &status, 4); ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (status)") if (status == Z_OK) { // Send the compressed buffer size and content ((int *) buffer)[0] = destinationBufferSize; memcpy(buffer + 4, compressedBuffer, destinationBufferSize); ret = sendwait(bss, clientfd, buffer, 4 + destinationBufferSize); ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (Compressed data)") } free(rawBuffer); OSFreeToSystem(compressedBuffer); break;*/ } case COMMAND_KERNEL_WRITE: { void *ptr, *value; ret = recvwait(bss, clientfd, buffer, 8); CHECK_ERROR(ret < 0) ptr = ((void **) buffer)[0]; value = ((void **) buffer)[1]; kern_write(ptr, (uint32_t) value); break; } case COMMAND_KERNEL_READ: { void *ptr, *value; ret = recvwait(bss, clientfd, buffer, 4); CHECK_ERROR(ret < 0); ptr = ((void **) buffer)[0]; value = (void *) kern_read(ptr); *(void **) buffer = value; sendwait(bss, clientfd, buffer, 4); break; } case COMMAND_TAKE_SCREEN_SHOT: { GX2ColorBuffer colorBuffer; // TODO Initialize colorBuffer! GX2Surface surface = colorBuffer.surface; void *image_data = surface.image_data; u32 image_size = surface.image_size; // Send the image size so that the client knows how much to read ret = sendwait(bss, clientfd, &image_size, 4); ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (image size)") unsigned int imageBytesSent = 0; while (imageBytesSent < image_size) { int length = image_size - imageBytesSent; // Do not smash the buffer if (length > DATA_BUFFER_SIZE) { length = DATA_BUFFER_SIZE; } // Send the image bytes memcpy(buffer, image_data, length); ret = sendwait(bss, clientfd, buffer, length); ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (image bytes)") imageBytesSent += length; } break; } case COMMAND_UPLOAD_MEMORY: { // Receive the starting and ending addresses ret = recvwait(bss, clientfd, buffer, 8); CHECK_ERROR(ret < 0) unsigned char *current_address = ((unsigned char **) buffer)[0]; unsigned char *end_address = ((unsigned char **) buffer)[1]; while (current_address != end_address) { int length; length = (int) (end_address - current_address); if (length > DATA_BUFFER_SIZE) { length = DATA_BUFFER_SIZE; } ret = recvwait(bss, clientfd, buffer, length); CHECK_ERROR(ret < 0) pygecko_memcpy(current_address, buffer, (unsigned int) length); current_address += length; } break; } case COMMAND_GET_DATA_BUFFER_SIZE: { ((int *) buffer)[0] = DATA_BUFFER_SIZE; ret = sendwait(bss, clientfd, buffer, 4); CHECK_ERROR(ret < 0) break; } case COMMAND_READ_FILE: { char file_path[FS_MAX_FULLPATH_SIZE] = {0}; receiveString(bss, clientfd, file_path, FS_MAX_FULLPATH_SIZE); considerInitializingFileSystem(); int handle; int status = FSOpenFile(client, commandBlock, file_path, "r", &handle, FS_RET_ALL_ERROR); if (status == FS_STATUS_OK) { // Send the OK status ((int *) buffer)[0] = status; ret = sendwait(bss, clientfd, buffer, 4); ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (OK status)") // Retrieve the file statistics FSStat stat; ret = FSGetStatFile(client, commandBlock, handle, &stat, FS_RET_ALL_ERROR); ASSERT_FUNCTION_SUCCEEDED(ret, "FSGetStatFile") // Send the total bytes count int totalBytes = (int) stat.size; ((int *) buffer)[0] = totalBytes; ret = sendwait(bss, clientfd, buffer, 4); ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (total bytes)") // Allocate the file bytes buffer unsigned int file_buffer_size = 0x2000; char *fileBuffer = (char *) OSAllocFromSystem(file_buffer_size, FS_IO_BUFFER_ALIGN); ASSERT_ALLOCATED(fileBuffer, "File buffer") int totalBytesRead = 0; while (totalBytesRead < totalBytes) { int bytesRead = FSReadFile(client, commandBlock, fileBuffer, 1, file_buffer_size, handle, 0, FS_RET_ALL_ERROR); ASSERT_FUNCTION_SUCCEEDED(bytesRead, "FSReadFile") // Send file bytes ret = sendwait(bss, clientfd, fileBuffer, bytesRead); ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (file buffer)") totalBytesRead += bytesRead; } ret = FSCloseFile(client, commandBlock, handle, FS_RET_ALL_ERROR); ASSERT_FUNCTION_SUCCEEDED(ret, "FSCloseFile") OSFreeToSystem(fileBuffer); } else { // Send the error status ((int *) buffer)[0] = status; ret = sendwait(bss, clientfd, buffer, 4); ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (error status)") } break; } case COMMAND_READ_DIRECTORY: { char directory_path[FS_MAX_FULLPATH_SIZE] = {0}; receiveString(bss, clientfd, directory_path, FS_MAX_FULLPATH_SIZE); considerInitializingFileSystem(); int handle; FSDirEntry entry; ret = FSOpenDir(client, commandBlock, directory_path, &handle, FS_RET_ALL_ERROR); if (ret == FS_STATUS_OK) { // Send the success status ((int *) buffer)[0] = ret; ret = sendwait(bss, clientfd, buffer, 4); ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (success status)") int entrySize = sizeof(FSDirEntry); // Read every entry in the given directory while (FSReadDir(client, commandBlock, handle, &entry, -1) == FS_STATUS_OK) { // Let the client know how much data is going to be sent (even though this is constant) ((int *) buffer)[0] = entrySize; ret = sendwait(bss, clientfd, buffer, 4); ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (data coming)") // Send the struct ret = sendwait(bss, clientfd, &entry, entrySize); ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (directory entry)") } // No more data will be sent, hence a 0 byte ((int *) buffer)[0] = 0; ret = sendwait(bss, clientfd, buffer, 4); ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (no more data)") // Done, close the directory also ret = FSCloseDir(client, commandBlock, handle, FS_RET_ALL_ERROR); ASSERT_FUNCTION_SUCCEEDED(ret, "FSCloseDir") } else { // Send the status ((int *) buffer)[0] = ret; ret = sendwait(bss, clientfd, buffer, 4); ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (error status)") } break; } case COMMAND_REPLACE_FILE: { // TODO Write file // Receive the file path char file_path[FS_MAX_FULLPATH_SIZE] = {0}; receiveString(bss, clientfd, file_path, FS_MAX_FULLPATH_SIZE); considerInitializingFileSystem(); // Create an empty file for writing. Its contents will be erased int handle; int status = FSOpenFile(client, commandBlock, file_path, "w", &handle, FS_RET_ALL_ERROR); if (status == FS_STATUS_OK) { // Send the OK status ((int *) buffer)[0] = status; ret = sendwait(bss, clientfd, buffer, 4); ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (OK status)") // Set the file handle position to the beginning ret = FSSetPosFile(client, commandBlock, handle, 0, FS_RET_ALL_ERROR); ASSERT_FUNCTION_SUCCEEDED(ret, "FSSetPosFile") // Allocate the file bytes buffer unsigned int file_buffer_size = 0x2000; char *fileBuffer = (char *) OSAllocFromSystem(file_buffer_size, FS_IO_BUFFER_ALIGN); ASSERT_ALLOCATED(fileBuffer, "File buffer") // Send the maximum file buffer size ret = sendwait(bss, clientfd, &file_buffer_size, 4); ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (maximum file buffer size)") while (true) { // Receive the data bytes length unsigned int dataLength; ret = recvwait(bss, clientfd, &dataLength, 4); ASSERT_FUNCTION_SUCCEEDED(ret, "recvwait (File bytes length)") ASSERT_MAXIMUM_HOLDS(file_buffer_size, dataLength, "File buffer overrun attempted") if (dataLength > 0) { // Receive the data ret = recvwait(bss, clientfd, fileBuffer, dataLength); ASSERT_FUNCTION_SUCCEEDED(ret, "recvwait (File buffer)") // Write the data and advance file handle position ret = FSWriteFile(client, commandBlock, fileBuffer, 1, dataLength, handle, 0, FS_RET_ALL_ERROR); ASSERT_FUNCTION_SUCCEEDED(ret, "FSWriteFile") } else { // Done break; } } /*// Flush the file back ret = FSFlushFile(client, commandBlock, handle, FS_RET_ALL_ERROR); CHECK_FUNCTION_FAILED(ret, "FSFlushFile")*/ // Close the file ret = FSCloseFile(client, commandBlock, handle, FS_RET_ALL_ERROR); ASSERT_FUNCTION_SUCCEEDED(ret, "FSCloseFile") // Free the file buffer OSFreeToSystem(fileBuffer); } else { // Send the status ((int *) buffer)[0] = status; ret = sendwait(bss, clientfd, buffer, 4); ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (status)") } break; } case COMMAND_GET_CODE_HANDLER_ADDRESS: { ((int *) buffer)[0] = CODE_HANDLER_INSTALL_ADDRESS; ret = sendwait(bss, clientfd, buffer, 4); CHECK_ERROR(ret < 0) break; } case COMMAND_READ_THREADS: { int OS_THREAD_SIZE = 0x6A0; int currentThreadAddress = OSGetCurrentThread(); ASSERT_VALID_EFFECTIVE_ADDRESS(currentThreadAddress, "OSGetCurrentThread") int iterationThreadAddress = currentThreadAddress; int temporaryThreadAddress; // Follow "previous thread" pointers back to the beginning while ((temporaryThreadAddress = *(int *) (iterationThreadAddress + 0x390)) != 0) { iterationThreadAddress = temporaryThreadAddress; ASSERT_VALID_EFFECTIVE_ADDRESS(iterationThreadAddress, "iterationThreadAddress going backwards") } // Send all threads by following the "next thread" pointers while ((temporaryThreadAddress = *(int *) (iterationThreadAddress + 0x38C)) != 0) { // Send the starting thread's address ((int *) buffer)[0] = iterationThreadAddress; // Send the thread struct itself memcpy(buffer + 4, (void *) iterationThreadAddress, OS_THREAD_SIZE); ret = sendwait(bss, clientfd, buffer, 4 + OS_THREAD_SIZE); CHECK_ERROR(ret < 0) iterationThreadAddress = temporaryThreadAddress; ASSERT_VALID_EFFECTIVE_ADDRESS(iterationThreadAddress, "iterationThreadAddress going forwards") } // The previous while would skip the last thread so send it also ((int *) buffer)[0] = iterationThreadAddress; memcpy(buffer + 4, (void *) iterationThreadAddress, OS_THREAD_SIZE); ret = sendwait(bss, clientfd, buffer, 4 + OS_THREAD_SIZE); CHECK_ERROR(ret < 0) // Let the client know that no more threads are coming ((int *) buffer)[0] = 0; ret = sendwait(bss, clientfd, buffer, 4); CHECK_ERROR(ret < 0) break; } case COMMAND_ACCOUNT_IDENTIFIER: { // Acquire the RPL unsigned int nn_act_handle; OSDynLoad_Acquire("nn_act.rpl", &nn_act_handle); // Acquire the functions via their mangled file names int (*nn_act_Initialize)(void); OSDynLoad_FindExport(nn_act_handle, 0, "Initialize__Q2_2nn3actFv", &nn_act_Initialize); ASSERT_ALLOCATED(nn_act_Initialize, "nn_act_Initialize") unsigned char (*nn_act_GetSlotNo)(void); OSDynLoad_FindExport(nn_act_handle, 0, "GetSlotNo__Q2_2nn3actFv", &nn_act_GetSlotNo); ASSERT_ALLOCATED(nn_act_GetSlotNo, "nn_act_GetSlotNo") unsigned int (*nn_act_GetPersistentIdEx)(unsigned char); OSDynLoad_FindExport(nn_act_handle, 0, "GetPersistentIdEx__Q2_2nn3actFUc", &nn_act_GetPersistentIdEx); ASSERT_ALLOCATED(nn_act_GetPersistentIdEx, "nn_act_GetPersistentIdEx") int (*nn_act_Finalize)(void); OSDynLoad_FindExport(nn_act_handle, 0, "Finalize__Q2_2nn3actFv", &nn_act_Finalize); ASSERT_ALLOCATED(nn_act_Finalize, "nn_act_Finalize") // Get the identifier ret = nn_act_Initialize(); // ASSERT_INTEGER(ret, 1, "Initializing account library"); unsigned char slotNumber = nn_act_GetSlotNo(); unsigned int persistentIdentifier = nn_act_GetPersistentIdEx(slotNumber); ret = nn_act_Finalize(); ASSERT_FUNCTION_SUCCEEDED(ret, "nn_act_Finalize"); // Send it ret = sendwait(bss, clientfd, &persistentIdentifier, 4); ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (persistent identifier)") break; } case COMMAND_WRITE_SCREEN: { char message[WRITE_SCREEN_MESSAGE_BUFFER_SIZE]; ret = recvwait(bss, clientfd, buffer, 4); ASSERT_FUNCTION_SUCCEEDED(ret, "recvwait (write screen seconds)") int seconds = ((int *) buffer)[0]; receiveString(bss, clientfd, message, WRITE_SCREEN_MESSAGE_BUFFER_SIZE); writeScreen(message, seconds); break; } case COMMAND_FOLLOW_POINTER: { ret = recvwait(bss, clientfd, buffer, 8); ASSERT_FUNCTION_SUCCEEDED(ret, "recvwait (Pointer address and offsets count)") // Retrieve the pointer address and amount of offsets int baseAddress = ((int *) buffer)[0]; int offsetsCount = ((int *) buffer)[1]; // Receive the offsets ret = recvwait(bss, clientfd, buffer, offsetsCount * 4); ASSERT_FUNCTION_SUCCEEDED(ret, "recvwait (offsets)") int offsets[offsetsCount]; int offsetIndex = 0; for (; offsetIndex < offsetsCount; offsetIndex++) { offsets[offsetIndex] = ((int *) buffer)[offsetIndex]; } int destinationAddress = baseAddress; if (isValidDataAddress(destinationAddress)) { // Apply pointer offsets for (offsetIndex = 0; offsetIndex < offsetsCount; offsetIndex++) { int pointerValue = *(int *) destinationAddress; int offset = offsets[offsetIndex]; destinationAddress = pointerValue + offset; // Validate the pointer address bool isValidDestinationAddress = isValidDataAddress(destinationAddress); // Bail out if invalid if (!isValidDestinationAddress) { destinationAddress = -1; break; } } } else { destinationAddress = -1; } // Return the destination address ((int *) buffer)[0] = destinationAddress; ret = sendwait(bss, clientfd, buffer, 4); ASSERT_FUNCTION_SUCCEEDED(ret, "recvwait (destination address)") break; } case COMMAND_SERVER_STATUS: { ret = sendbyte(bss, clientfd, 1); CHECK_ERROR(ret < 0) break; } case COMMAND_RPC: { long long (*fun)(int, int, int, int, int, int, int, int); int r3, r4, r5, r6, r7, r8, r9, r10; long long result; ret = recvwait(bss, clientfd, buffer, 4 + 8 * 4); CHECK_ERROR(ret < 0); fun = ((void **) buffer)[0]; r3 = ((int *) buffer)[1]; r4 = ((int *) buffer)[2]; r5 = ((int *) buffer)[3]; r6 = ((int *) buffer)[4]; r7 = ((int *) buffer)[5]; r8 = ((int *) buffer)[6]; r9 = ((int *) buffer)[7]; r10 = ((int *) buffer)[8]; result = fun(r3, r4, r5, r6, r7, r8, r9, r10); ((long long *) buffer)[0] = result; ret = sendwait(bss, clientfd, buffer, 8); CHECK_ERROR(ret < 0) break; } case COMMAND_GET_SYMBOL: { int size = recvbyte(bss, clientfd); CHECK_ERROR(size < 0) ret = recvwait(bss, clientfd, buffer, size); CHECK_ERROR(ret < 0) /* Identify the RPL name and symbol name */ char *rplname = (char *) &((int *) buffer)[2]; char *symname = (char *) (&buffer[0] + ((int *) buffer)[1]); /* Get the symbol and store it in the buffer */ unsigned int module_handle, function_address; OSDynLoad_Acquire(rplname, &module_handle); char data = (char) recvbyte(bss, clientfd); OSDynLoad_FindExport(module_handle, data, symname, &function_address); ((int *) buffer)[0] = (int) function_address; ret = sendwait(bss, clientfd, buffer, 4); CHECK_ERROR(ret < 0) break; } case COMMAND_MEMORY_SEARCH: { // Receive the initial data ret = recvwait(bss, clientfd, buffer, 4 * 6); ASSERT_FUNCTION_SUCCEEDED(ret, "recvwait (memory search information)") int bufferIndex = 0; int startingAddress = ((int *) buffer)[bufferIndex++]; int length = ((int *) buffer)[bufferIndex++]; int kernelRead = ((int *) buffer)[bufferIndex++]; int resultsLimit = ((int *) buffer)[bufferIndex++]; int aligned = ((int *) buffer)[bufferIndex++]; int searchBytesCount = ((int *) buffer)[bufferIndex]; // Receive the search bytes char searchBytes[searchBytesCount]; ret = recvwait(bss, clientfd, searchBytes, searchBytesCount); ASSERT_FUNCTION_SUCCEEDED(ret, "recvwait (memory search bytes)") int iterationIncrement = aligned ? searchBytesCount : 1; int searchBytesOccurrences = 0; // Perform the bytes search and collect the results for (int currentAddress = startingAddress; currentAddress < startingAddress + length; currentAddress += iterationIncrement) { int comparisonResult; if (kernelRead) { comparisonResult = kernelMemoryCompare((void *) currentAddress, searchBytes, searchBytesCount); } else { comparisonResult = memcmp((void *) currentAddress, searchBytes, searchBytesCount); } if (comparisonResult == 0) { // Search bytes have been found ((int *) buffer)[1 + searchBytesOccurrences] = currentAddress; searchBytesOccurrences++; if ((resultsLimit == searchBytesOccurrences) || (searchBytesOccurrences == ((DATA_BUFFER_SIZE / 4) - 1))) { // We bail out break; } } } ((int *) buffer)[0] = searchBytesOccurrences * 4; ret = sendwait(bss, clientfd, buffer, 4 + (searchBytesOccurrences * 4)); ASSERT_FUNCTION_SUCCEEDED(ret, "recvwait (Sending search bytes occurrences)") break; } /*case COMMAND_SYSTEM_CALL: { ret = recvwait(bss, clientfd, buffer, 4); ASSERT_FUNCTION_SUCCEEDED(ret, "recvwait (system call)") int value = ((int *) buffer)[0]; performSystemCall(value); break; }*/ case COMMAND_EXECUTE_ASSEMBLY: { // Receive the assembly receiveString(bss, clientfd, (char *) buffer, DATA_BUFFER_SIZE); // Write the assembly to an executable code region int destinationAddress = 0x10000000 - DATA_BUFFER_SIZE; pygecko_memcpy((unsigned char *) destinationAddress, buffer, DATA_BUFFER_SIZE); // Execute the assembly from there void (*function)() = (void (*)()) destinationAddress; function(); // Clear the memory contents again memset((void *) buffer, 0, DATA_BUFFER_SIZE); pygecko_memcpy((unsigned char *) destinationAddress, buffer, DATA_BUFFER_SIZE); break; } case COMMAND_SERVER_VERSION: { char versionBuffer[50]; strcpy(versionBuffer, SERVER_VERSION); int versionLength = strlen(versionBuffer); ((int *) buffer)[0] = versionLength; memcpy(buffer + 4, versionBuffer, versionLength); // Send the length and the version string ret = sendwait(bss, clientfd, buffer, 4 + versionLength); ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (server version)"); break; } case COMMAND_OS_VERSION: { ((int *) buffer)[0] = (int) OS_FIRMWARE; ret = sendwait(bss, clientfd, buffer, 4); CHECK_ERROR(ret < 0) break; } case COMMAND_RUN_KERNEL_COPY_SERVICE: { if (!kernelCopyServiceStarted) { kernelCopyServiceStarted = true; startKernelCopyService(); } break; } default: reportIllegalCommandByte(ret); break; } } error: bss->error = ret; return 0; } static int start(int argc, void *argv) { int sockfd = -1, clientfd = -1, ret = 0, len; struct sockaddr_in addr; struct pygecko_bss_t *bss = argv; setup_os_exceptions(); socket_lib_init(); while (1) { addr.sin_family = AF_INET; addr.sin_port = 7331; addr.sin_addr.s_addr = 0; sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); CHECK_ERROR(sockfd == -1) ret = bind(sockfd, (void *) &addr, 16); CHECK_ERROR(ret < 0) ret = listen(sockfd, 20); CHECK_ERROR(ret < 0) while (1) { len = 16; clientfd = accept(sockfd, (void *) &addr, &len); CHECK_ERROR(clientfd == -1) ret = rungecko(bss, clientfd); CHECK_ERROR(ret < 0) socketclose(clientfd); clientfd = -1; } error: if (clientfd != -1) socketclose(clientfd); if (sockfd != -1) socketclose(sockfd); bss->error = ret; // Fix the console freezing when e.g. going to the friend list GX2WaitForVsync(); } return 0; } static int CCThread(int argc, void *argv) { struct pygecko_bss_t *bss; bss = memalign(0x40, sizeof(struct pygecko_bss_t)); if (bss == 0) return 0; memset(bss, 0, sizeof(struct pygecko_bss_t)); if (OSCreateThread(&bss->thread, start, 1, bss, (u32) bss->stack + sizeof(bss->stack), sizeof(bss->stack), 0, 0xc) == 1) { OSResumeThread(&bss->thread); } else { free(bss); } if (CCHandler == 1) { void (*entrypoint)() = (void *) CODE_HANDLER_INSTALL_ADDRESS; while (1) { usleep(9000); entrypoint(); } } return 0; } void start_pygecko() { // Force the debugger to be initialized by default // writeInt((unsigned int) (OSIsDebuggerInitialized + 0x1C), 0x38000001); // li r3, 1 unsigned int stack = (unsigned int) memalign(0x40, 0x100); ASSERT_ALLOCATED(stack, "TCP Gecko stack") stack += 0x100; void *thread = memalign(0x40, 0x1000); ASSERT_ALLOCATED(thread, "TCP Gecko thread") int status = OSCreateThread(thread, CCThread, 1, NULL, (u32) stack + sizeof(stack), sizeof(stack), 0, OS_THREAD_ATTR_AFFINITY_CORE1 | OS_THREAD_ATTR_PINNED_AFFINITY | OS_THREAD_ATTR_DETACH); ASSERT_INTEGER(status, 1, "Creating TCP Gecko thread") // OSSetThreadName(thread, "TCP Gecko"); OSResumeThread(thread); }