From 6cdc9276aa775659656007592cb357c7bc9fa8b7 Mon Sep 17 00:00:00 2001 From: Maschell Date: Fri, 23 Oct 2015 22:33:18 +0200 Subject: [PATCH] added injection feature code still needs a lot of clean up. injection needs testing. todo: update readme --- .gitignore | 11 + saviine/client/main.c | 272 ++++++++++++++--- saviine/client/main.h | 33 ++- saviine/client/saviine.c | 218 ++++++++++++-- saviine/client/saviine532.ld | 9 +- saviine/installer/bin/code532.bin | Bin 7852 -> 7187 bytes saviine/installer/bin/saviine.o | Bin 9324 -> 9248 bytes saviine/server/src/Program.cs | 341 ++++++++++++++++++++-- saviine/server/src/bin/dump.bat | 1 + saviine/server/src/bin/inject.bat | 1 + saviine/server/src/bin/saviine_server.exe | Bin 0 -> 30208 bytes saviine/server/src/saviine_server.v12.suo | Bin 16896 -> 31232 bytes 12 files changed, 791 insertions(+), 95 deletions(-) create mode 100644 saviine/server/src/bin/dump.bat create mode 100644 saviine/server/src/bin/inject.bat create mode 100644 saviine/server/src/bin/saviine_server.exe diff --git a/.gitignore b/.gitignore index 96374c4..1a97e94 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,14 @@ $RECYCLE.BIN/ Network Trash Folder Temporary Items .apdisk +saviine/installer/bin/* +saviine/client/build/* +saviine/server/src/bin/logs/* +saviine/server/src/bin/saviine_root/* +saviine/server/src/bin/saviine_server.pdb +saviine/server/src/bin/saviine_server.vshost.exe.manifest +saviine/server/src/obj/x86/Debug/saviine_server.exe +saviine/server/src/obj/x86/Debug/saviine_server.pdb +saviine/server/src/bin/saviine_server.vshost.exe +saviine/server/src/bin/saviine_server.exe.config +saviine/server/src/bin/saviine_server.vshost.exe.config diff --git a/saviine/client/main.c b/saviine/client/main.c index 45ff416..ce65636 100644 --- a/saviine/client/main.c +++ b/saviine/client/main.c @@ -4,10 +4,7 @@ extern res name(__VA_ARGS__); \ res (* real_ ## name)(__VA_ARGS__) __attribute__((section(".magicptr"))); \ res my_ ## name(__VA_ARGS__) - -#define BYTE_LOG_STR 0xfb - - +#define DEBUG_LOG 0 extern FSStatus FSOpenDir(FSClient *pClient, FSCmdBlock *pCmd, const char *path, int *dh, FSRetFlag errHandling); extern FSStatus FSReadDir(FSClient *pClient, FSCmdBlock *pCmd, int dh, FSDirEntry *dir_entry, FSRetFlag errHandling); extern FSStatus FSChangeDir(FSClient *pClient, FSCmdBlock *pCmd, const char *path, FSRetFlag errHandling); @@ -15,17 +12,13 @@ extern FSStatus FSCloseDir(FSClient *pClient, FSCmdBlock *pCmd, int dh, FSRetFla extern FSStatus FSReadFile(FSClient *pClient, FSCmdBlock *pCmd, void *buffer, int size, int count, int fd, int flag, int error); extern FSStatus FSSetPosFile(FSClient *pClient, FSCmdBlock *pCmd, int fd, int pos, int error); extern FSStatus FSCloseFile (FSClient *pClient, FSCmdBlock *pCmd, int fd, int error); +extern FSStatus FSMakeDir(FSClient *pClient, FSCmdBlock *pCmd,const char *path, FSRetFlag errHandling); +extern FSStatus FSRemove(FSClient *pClient, FSCmdBlock *pCmd, const char *path, FSRetFlag errHandling); extern void OSDynLoad_Acquire (char* rpl, unsigned int *handle); extern void OSDynLoad_FindExport (unsigned int handle, int isdata, char *symbol, void *address); void GX2WaitForVsync(void); -static void dump_saves(void *pClient, void *pCmd,int error, int client); - -static int strlen(char* path) { - int i = 0; - while (path[i++]) - ; - return i; -} +static void handle_saves(void *pClient, void *pCmd,int error, int client); +static void hook(void * pClient,void * pCmd, int error, int client); DECL(int, FSAInit, void) { if ((int)bss_ptr == 0x0a000000) { @@ -56,6 +49,31 @@ DECL(int, FSADelClient, int client) { return real_FSADelClient(client); } +static int strlen(char* path) { + int i = 0; + while (path[i++]) + ; + return i; +} + +static int strcmp(const char *s1, const char *s2) +{ + while(*s1 && *s2) + { + if(*s1 != *s2) { + return -1; + } + s1++; + s2++; + } + + if(*s1 != *s2) { + return -1; + } + return 0; +} + + static int client_num_alloc(void *pClient) { int i; @@ -100,8 +118,17 @@ DECL(int, FSDelClient, void *pClient) { } return real_FSDelClient(pClient); } +DECL(int, FSWriteFile,FSClient *pClient, FSCmdBlock *pCmd, const void *source,int size, int count, int fileHandle, int flag,FSRetFlag error) { +return real_FSWriteFile(pClient,pCmd,source,size,count,fileHandle,flag,error); +} + +DECL(int, FSFlushQuota,FSClient *pClient,FSCmdBlock *pCmd,const char *path,FSRetFlag error) { +return real_FSFlushQuota(pClient,pCmd,path,error); +} + DECL(int, FSAddClientEx, void *r3, void *r4, void *r5) { int res = real_FSAddClientEx(r3, r4, r5); + if(bss.saveFolderChecked == 1) return res; if ((int)bss_ptr != 0x0a000000 && res >= 0) { //int client = client_num_alloc(r3); @@ -118,35 +145,77 @@ DECL(int, FSAddClientEx, void *r3, void *r4, void *r5) { FSAddClient(pClient, FS_RET_NO_ERROR); int client = client_num_alloc(pClient); if(client < MAX_CLIENT && client >= 0) { - cafiine_connect(&bss.socket_fs[client]); + cafiine_connect(&bss.socket_fs[client]); + bss.logsock = bss.socket_fs[client]; }else{ goto error; } // Init command block. FSInitCmdBlock(pCmd); - dump_saves(pClient, pCmd,-1, client); + hook(pClient, pCmd,-1, client); bss.saveFolderChecked = 2; - - - error: - real_FSDelClient(pClient); + + error: real_FSDelClient(pClient); free(pClient); free(pCmd); } } //cafiine_connect(&bss.socket_fs[client]); - //} + //} } + return res; } +static int remove_files_in_dir(void * pClient,void * pCmd, char * path, int handle){ + int ret = 0; + if ((ret = FSOpenDir(pClient, pCmd, path, &handle, FS_RET_ALL_ERROR)) == FS_STATUS_OK){ + char buffer[strlen(path) + 25]; + + __os_snprintf(buffer, sizeof(buffer), "remove files in dir %s",path); + log_string(bss.logsock, buffer, BYTE_LOG_STR); + FSDirEntry dir_entry; + while (FSReadDir(pClient, pCmd, handle, &dir_entry, FS_RET_ALL_ERROR) == FS_STATUS_OK) + { + char full_path[255]; + int i=0; + char *path_ptr = (char *)path; + while(*path_ptr) { + full_path[i++] = *path_ptr++; + } + full_path[i++] = '/'; + char *dir_name_ptr = (char *)dir_entry.name; + while(*dir_name_ptr) { + full_path[i++] = *dir_name_ptr++; + } + full_path[i++] = '\0'; + char buffer[strlen(full_path) + 50]; + __os_snprintf(buffer, sizeof(buffer), "deleting %s",full_path); + log_string(bss.logsock, buffer, BYTE_LOG_STR); + if((ret = FSRemove(pClient,pCmd,full_path,-1)) < 0){ + __os_snprintf(buffer, sizeof(buffer), "error: %d on %s",ret,full_path); + log_string(bss.logsock, buffer, BYTE_LOG_STR); + return -1; + } + } + if((FSCloseDir(pClient, pCmd, handle, FS_RET_NO_ERROR)) <=0 ){ + log_string(bss.logsock, "error while closing dir", BYTE_LOG_STR); + } + } + return 0; +} + +static void hook(void * pClient,void * pCmd, int error, int client){ + log_string(bss.logsock, "hook", BYTE_LOG_STR); + handle_saves(pClient, pCmd,-1, client); +} static void init_Save(){ int (*SAVEInit)(); unsigned int save_handle; OSDynLoad_Acquire("nn_save.rpl", &save_handle); - OSDynLoad_FindExport(save_handle, 0, "SAVEInit", &SAVEInit); + OSDynLoad_FindExport(save_handle, 0, "SAVEInit", (void **)&SAVEInit); SAVEInit(); } @@ -157,10 +226,10 @@ static long getPesistentID(){ void (*nn_Initialize)(void); void (*nn_Finalize)(void); OSDynLoad_Acquire("nn_act.rpl", &nn_act_handle); - OSDynLoad_FindExport(nn_act_handle, 0, "GetPersistentIdEx__Q2_2nn3actFUc", &GetPersistentIdEx); - OSDynLoad_FindExport(nn_act_handle, 0, "GetSlotNo__Q2_2nn3actFv", &GetSlotNo); - OSDynLoad_FindExport(nn_act_handle, 0, "Initialize__Q2_2nn3actFv", &nn_Initialize); - OSDynLoad_FindExport(nn_act_handle, 0, "Finalize__Q2_2nn3actFv", &nn_Finalize); + OSDynLoad_FindExport(nn_act_handle, 0, "GetPersistentIdEx__Q2_2nn3actFUc", (void **)&GetPersistentIdEx); + OSDynLoad_FindExport(nn_act_handle, 0, "GetSlotNo__Q2_2nn3actFv", (void **)&GetSlotNo); + OSDynLoad_FindExport(nn_act_handle, 0, "Initialize__Q2_2nn3actFv", (void **)&nn_Initialize); + OSDynLoad_FindExport(nn_act_handle, 0, "Finalize__Q2_2nn3actFv", (void **)&nn_Finalize); nn_Initialize(); // To be sure that it is really Initialized @@ -183,7 +252,7 @@ static int dump_dir(void *pClient,int client, void *pCmd, char *path, int error, char buffer[strlen(path) + 25]; __os_snprintf(buffer, sizeof(buffer), "open dir %s",path); - log_string(bss.socket_fsa[client], buffer, BYTE_LOG_STR); + log_string(bss.logsock, buffer, BYTE_LOG_STR); FSDirEntry dir_entry; while (FSReadDir(pClient, pCmd, dir_handle, &dir_entry, FS_RET_ALL_ERROR) == FS_STATUS_OK) { @@ -203,14 +272,14 @@ static int dump_dir(void *pClient,int client, void *pCmd, char *path, int error, if((dir_entry.stat.flag&FS_STAT_FLAG_IS_DIRECTORY) == FS_STAT_FLAG_IS_DIRECTORY){ - log_string(bss.socket_fsa[client], "-> dir", BYTE_LOG_STR); + log_string(bss.logsock, "-> dir", BYTE_LOG_STR); dump_dir(pClient,client, pCmd,full_path,-1,my_handle); }else{ //DUMP ret = FSOpenFile(pClient, pCmd, full_path, "r", &my_handle, FS_RET_ALL_ERROR); if (ret >= 0) { __os_snprintf(buffer, sizeof(buffer), "dumping %s",dir_entry.name); - log_string(bss.socket_fsa[client], buffer, BYTE_LOG_STR); + log_string(bss.logsock, buffer, BYTE_LOG_STR); int my_ret = cafiine_send_handle(bss.socket_fsa[client], client, full_path, my_handle); @@ -220,7 +289,7 @@ static int dump_dir(void *pClient,int client, void *pCmd, char *path, int error, int ret2; while ((ret2 = FSReadFile(pClient, pCmd, buffer, 1, size, my_handle, 0, 0)) > 0) cafiine_send_file(bss.socket_fsa[client], buffer, ret2, my_handle); - cafiine_fclose(bss.socket_fsa[client], &ret2, my_handle); + cafiine_fclose(bss.socket_fsa[client], &ret2, my_handle,1); FSSetPosFile(pClient, pCmd, my_handle, 0, FS_RET_ALL_ERROR); free(buffer); FSCloseFile(pClient, pCmd, my_handle, -1); @@ -228,7 +297,7 @@ static int dump_dir(void *pClient,int client, void *pCmd, char *path, int error, char type[2]; type[0] = '9' + ret; type[1] = '\0'; - log_string(bss.socket_fsa[client], type, BYTE_LOG_STR); + log_string(bss.logsock, type, BYTE_LOG_STR); } } @@ -238,22 +307,145 @@ static int dump_dir(void *pClient,int client, void *pCmd, char *path, int error, return 0; } -static void dump_saves(void *pClient, void *pCmd,int error, int client){ +static void handle_saves(void *pClient, void *pCmd,int error, int client){ + log_string(bss.logsock, "handle_saves", BYTE_LOG_STR); init_Save(); long id = getPesistentID(); - log_string(bss.socket_fsa[client], "dumping user savedata", BYTE_LOG_STR); + log_string(bss.logsock, "user savedata", BYTE_LOG_STR); if(id >= 0x80000000 && id <= 0x90000000){ char savepath[20]; - __os_snprintf(savepath, sizeof(savepath), "/vol/save/%08x",id); - dump_dir(pClient,client,pCmd,savepath,-1,50); + int mode; + __os_snprintf(savepath, sizeof(savepath), "/vol/save/%08x",id); + log_string(bss.logsock, "Getting mode!", BYTE_LOG_STR); + if(getMode(bss.socket_fsa[client],&mode)){ + if(mode == BYTE_MODE_D){ + log_string(bss.logsock, "dump mode!", BYTE_LOG_STR); + dump_dir(pClient,client,pCmd,savepath,-1,50); + }else if(mode == BYTE_MODE_I){ + log_string(bss.logsock, "inject mode", BYTE_LOG_STR); + log_string(bss.logsock, "deleting current save", BYTE_LOG_STR); + remove_files_in_dir(pClient,pCmd,savepath,0); + injectFiles(pClient,pCmd,savepath,"/",-1); + log_string(bss.logsock, "flushing quota", BYTE_LOG_STR); + FSFlushQuota(pClient,pCmd,savepath,-1); + } + } + } + /* + log_string(bss.logsock, "dumping common savedata", BYTE_LOG_STR); + dump_dir(pClient,client,pCmd,"/vol/save/common/",error,60); + log_string(bss.logsock, "done!", BYTE_LOG_STR);*/ +} + + +#define BUFFER_SIZE (1024)*100 +void injectFiles(void *pClient, void *pCmd, char * path,char * relativepath, int error){ + + //FSStatus (*FSWriteFileWithPos) (FSClient *pClient, FSCmdBlock *pCmd, const void *source,int size,int count,int fpos,int fileHandle,int flag,FSRetFlag errHandling); + int client = client_num(pClient); + + int type = 0; + log_string(bss.logsock, "injecting files", BYTE_LOG_STR); + char namebuffer[255]; + char logbugger[255]; + int filesize = 0; + + int buf_size = BUFFER_SIZE; + char * pBuffer; + do{ + buf_size -= 0x200; + if(buf_size < 0){ + log_string(bss.logsock, "error on buffer allocation", BYTE_LOG_STR); + return; + } + pBuffer = (char *)MEMAllocFromDefaultHeapEx(buf_size, 0x40); + }while(!pBuffer); + + __os_snprintf(logbugger, sizeof(logbugger), "buffer size: %d bytes",buf_size); + log_string(bss.logsock, logbugger, BYTE_LOG_STR); + + while(getFiles(bss.socket_fsa[client],path,namebuffer, &type,&filesize)){ + if(DEBUG_LOG)log_string(bss.logsock, "got a file", BYTE_LOG_STR); + char newpath[strlen(path) + 1 + strlen(namebuffer)]; + __os_snprintf(newpath, sizeof(newpath), "%s/%s",path,namebuffer); + if(type == BYTE_FILE){ + __os_snprintf(logbugger, sizeof(logbugger), "file: %s%s size: %d",relativepath,namebuffer,filesize); + log_string(bss.logsock, logbugger, BYTE_LOG_STR); + if(DEBUG_LOG) log_string(bss.logsock, "downloading it", BYTE_LOG_STR); + + int handle = 10; + if(FSOpenFile(pClient, pCmd, newpath,"w+",&handle,-1) >= 0){ + if(DEBUG_LOG) log_string(bss.logsock, "file opened and created", BYTE_LOG_STR); + if(filesize > 0){ + int myhandle; + int ret = 0; + if((cafiine_fopen(bss.socket_fsa[client], &ret, newpath, "r", &myhandle)) == 0 && ret == 0){ + if(DEBUG_LOG)__os_snprintf(logbugger, sizeof(logbugger), "cafiine_fopen with handle %d",myhandle); + if(DEBUG_LOG) log_string(bss.logsock, logbugger, BYTE_LOG_STR); + int size = sizeof(char); + int count = buf_size; + int retsize = 0; + int pos = 0; + while(pos < filesize){ + if(DEBUG_LOG) log_string(bss.logsock, "reading", BYTE_LOG_STR); + if(DEBUG_LOG)__os_snprintf(logbugger, sizeof(logbugger), "count %d",count); + if(DEBUG_LOG) log_string(bss.logsock, logbugger, BYTE_LOG_STR); + if(cafiine_fread(bss.socket_fsa[client], &retsize, pBuffer, count, myhandle) == 0){ + __os_snprintf(logbugger, sizeof(logbugger), "got %d",retsize); + if(DEBUG_LOG) log_string(bss.logsock, logbugger, BYTE_LOG_STR); + int fwrite = 0; + if((fwrite = my_FSWriteFile(pClient, pCmd, pBuffer,size,retsize,handle,0,0x0200)) >= 0){ + __os_snprintf(logbugger, sizeof(logbugger), "wrote %d",retsize); + if(DEBUG_LOG) log_string(bss.logsock, logbugger, BYTE_LOG_STR); + }else{ + __os_snprintf(logbugger, sizeof(logbugger), "my_FSWriteFile failed with error: %d",fwrite); + if(DEBUG_LOG) log_string(bss.logsock, logbugger, BYTE_LOG_STR); + log_string(bss.logsock, "error while FSWriteFile", BYTE_LOG_STR); + } + __os_snprintf(logbugger, sizeof(logbugger), "old p %d new p %d",pos,pos+retsize); + if(DEBUG_LOG) log_string(bss.logsock, logbugger, BYTE_LOG_STR); + pos += retsize; + }else{ + log_string(bss.logsock, "error while recieving file", BYTE_LOG_STR); + break; + } + } + + int result = 0; + if((cafiine_fclose(bss.socket_fsa[client], &result, myhandle,0)) == 0 && result == 0){ + if(DEBUG_LOG) log_string(bss.logsock, "cafiine_fclose success", BYTE_LOG_STR); + }else{ + log_string(bss.logsock, "cafiine_fclose failed", BYTE_LOG_STR); + } + - log_string(bss.socket_fsa[client], "dumping common savedata", BYTE_LOG_STR); - dump_dir(pClient,client,pCmd,"/vol/save/common/",error,60); - - log_string(bss.socket_fsa[client], "done!", BYTE_LOG_STR); + }else{ + log_string(bss.logsock, "cafiine_fopen failed", BYTE_LOG_STR); + } + } + + if((FSCloseFile (pClient, pCmd, handle, -1)) <= 0) + log_string(bss.logsock, "FSCloseFile failed", BYTE_LOG_STR); + } + + }else if( type == BYTE_FOLDER){ + __os_snprintf(logbugger, sizeof(logbugger), "dir: %s",namebuffer); + log_string(bss.logsock, logbugger, BYTE_LOG_STR); + if(DEBUG_LOG) log_string(bss.logsock, newpath, BYTE_LOG_STR); + if(FSMakeDir(pClient, pCmd, newpath, -1) == 0){ + char op_offset[strlen(relativepath) + strlen(namebuffer)+ 1 + 1]; + __os_snprintf(op_offset, sizeof(op_offset), "%s%s/",relativepath,namebuffer); + injectFiles(pClient, pCmd, newpath,op_offset,error); + }else{ + log_string(bss.logsock, "folder creation failed", BYTE_LOG_STR); + } + } + } + free(pBuffer); + log_string(bss.logsock, "getting files done", BYTE_LOG_STR); } #define MAKE_MAGIC(x) { x, my_ ## x, &real_ ## x } @@ -270,5 +462,9 @@ struct magic_t { MAKE_MAGIC(FSInit), MAKE_MAGIC(FSShutdown), MAKE_MAGIC(FSAddClientEx), - MAKE_MAGIC(FSDelClient), + MAKE_MAGIC(FSDelClient), + MAKE_MAGIC(FSWriteFile), + MAKE_MAGIC(FSFlushQuota), + + }; diff --git a/saviine/client/main.h b/saviine/client/main.h index 1a86373..a3eec3e 100644 --- a/saviine/client/main.h +++ b/saviine/client/main.h @@ -2,6 +2,31 @@ #include "../common/fs_defs.h" #define NULL ((void *)0) +#define BYTE_NORMAL 0xff +#define BYTE_SPECIAL 0xfe +#define BYTE_OPEN 0x00 +#define BYTE_READ 0x01 +#define BYTE_CLOSE 0x02 +#define BYTE_OK 0x03 +#define BYTE_SETPOS 0x04 +#define BYTE_STATFILE 0x05 +#define BYTE_EOF 0x06 +#define BYTE_GETPOS 0x07 +#define BYTE_REQUEST 0x08 +#define BYTE_REQUEST_SLOW 0x09 +#define BYTE_HANDLE 0x0A +#define BYTE_DUMP 0x0B +#define BYTE_PING 0x0C +#define BYTE_G_MODE 0x0D +#define BYTE_MODE_D 0x0E +#define BYTE_MODE_I 0x0F +#define BYTE_CLOSE_DUMP 0x10 +#define BYTE_LOG_STR 0xfb +#define BYTE_FILE 0xC0 +#define BYTE_FOLDER 0xC1 +#define BYTE_GET_FILES 0xCC +#define BYTE_END 0xfd + void *memcpy(void *dst, const void *src, int bytes); void *memset(void *dst, int val, int bytes); @@ -30,6 +55,8 @@ extern int connect(int socket, void *addr, int addrlen); extern int send(int socket, const void *buffer, int size, int flags); extern int recv(int socket, void *buffer, int size, int flags); extern int __os_snprintf(char* s, int n, const char * format, ...); +int getFiles(int sock, char * path,char * resultname, int * resulttype,int *filesize); +void injectFiles(void *pClient, void *pCmd, char * path,char * relativepath, int error); struct in_addr { unsigned int s_addr; @@ -55,6 +82,7 @@ struct bss_t { char save_path[255]; volatile int saveFolderChecked; volatile int lock; + int logsock; }; #define bss_ptr (*(struct bss_t **)0x100000e4) @@ -62,11 +90,12 @@ struct bss_t { void cafiine_connect(int *socket); void cafiine_disconnect(int socket); +int getMode(int sock, int * result); int cafiine_fopen(int socket, int *result, const char *path, const char *mode, int *handle); int cafiine_send_handle(int sock, int client, const char *path, int handle); void cafiine_send_file(int sock, char *file, int size, int fd); -int cafiine_fread(int socket, int *result, void *buffer, int size, int count, int fd); -int cafiine_fclose(int socket, int *result, int fd); +int cafiine_fread(int socket, int *result, void *buffer, int size, int fd); +int cafiine_fclose(int socket, int *result, int fd, int dumpclose); int cafiine_fsetpos(int socket, int *result, int fd, int set); int cafiine_fgetpos(int socket, int *result, int fd, int *pos); int cafiine_fstat(int sock, int *result, int fd, void *ptr); diff --git a/saviine/client/saviine.c b/saviine/client/saviine.c index 409a643..f862011 100644 --- a/saviine/client/saviine.c +++ b/saviine/client/saviine.c @@ -1,33 +1,22 @@ #include "main.h" static int recvwait(int sock, void *buffer, int len); +static int recvwaitlen(int sock, void *buffer, int len); + static int recvbyte(int sock); static int sendwait(int sock, const void *buffer, int len); - +static int sendbyte(int sock, unsigned char value); static int cafiine_handshake(int sock); #define CHECK_ERROR(cond) if (cond) { goto error; } -#define BYTE_NORMAL 0xff -#define BYTE_SPECIAL 0xfe -//#define BYTE_OPEN 0x00 -//#define BYTE_READ 0x01 -#define BYTE_CLOSE 0x02 -//#define BYTE_OK 0x03 -//#define BYTE_SETPOS 0x04 -//#define BYTE_STATFILE 0x05 -//#define BYTE_EOF 0x06 -//#define BYTE_GETPOS 0x07 -#define BYTE_REQUEST 0x08 -#define BYTE_REQUEST_SLOW 0x09 -#define BYTE_HANDLE 0x0A -#define BYTE_DUMP 0x0B -#define BYTE_PING 0x0C -#define BYTE_LOG_STR 0xfb void GX2WaitForVsync(void); + + + void cafiine_connect(int *psock) { extern unsigned int server_ip; struct sockaddr_in addr; @@ -79,6 +68,56 @@ error: return ret; } +int getMode(int sock,int * result) +{ + while (bss.lock) GX2WaitForVsync(); + bss.lock = 1; + + CHECK_ERROR(sock == -1); + int ret = 0; + + // create and send buffer with : [cmd id][fd][size][buffer data ...] + { + ret = sendbyte(sock, BYTE_G_MODE); + + // wait reply + ret = recvbyte(sock); + CHECK_ERROR(ret < 0); + if(ret == BYTE_MODE_D) *result = BYTE_MODE_D; + if(ret == BYTE_MODE_I) *result = BYTE_MODE_I; + ret = 1; + } +error: + bss.lock = 0; + return ret; +} + + +int cafiine_fsetpos(int sock, int *result, int fd, int set) { + while (bss.lock) GX2WaitForVsync(); + bss.lock = 1; + + CHECK_ERROR(sock == -1); + + int ret; + char buffer[1 + 8]; + buffer[0] = BYTE_SETPOS; + *(int *)(buffer + 1) = fd; + *(int *)(buffer + 5) = set; + ret = sendwait(sock, buffer, 1 + 8); + CHECK_ERROR(ret < 0); + ret = recvbyte(sock); + CHECK_ERROR(ret < 0); + CHECK_ERROR(ret == BYTE_NORMAL); + ret = recvwait(sock, result, 4); + CHECK_ERROR(ret < 0); + + bss.lock = 0; + return 0; +error: + bss.lock = 0; + return -1; +} int cafiine_send_handle(int sock, int client, const char *path, int handle) { @@ -122,6 +161,49 @@ error: bss.lock = 0; return -1; } +int cafiine_fopen(int sock, int *result, const char *path, const char *mode, int *handle) { + while (bss.lock) GX2WaitForVsync(); + bss.lock = 1; + + CHECK_ERROR(sock == -1); + + int final_ret = 0; + int ret; + int len_path = 0; + while (path[len_path++]); + int len_mode = 0; + while (mode[len_mode++]); + + // + { + char buffer[1 + 8 + len_path + len_mode]; + buffer[0] = BYTE_OPEN; + *(int *)(buffer + 1) = len_path; + *(int *)(buffer + 5) = len_mode; + for (ret = 0; ret < len_path; ret++) + buffer[9 + ret] = path[ret]; + for (ret = 0; ret < len_mode; ret++) + buffer[9 + len_path + ret] = mode[ret]; + + ret = sendwait(sock, buffer, 1 + 8 + len_path + len_mode); + } + CHECK_ERROR(ret < 0); + ret = recvbyte(sock); + CHECK_ERROR(ret < 0); + CHECK_ERROR(ret == BYTE_NORMAL); + + ret = recvwait(sock, result, 4); + CHECK_ERROR(ret < 0); + ret = recvwait(sock, handle, 4); + CHECK_ERROR(ret < 0); + +quit: + bss.lock = 0; + return final_ret; +error: + bss.lock = 0; + return -1; +} void cafiine_send_file(int sock, char *file, int size, int fd) { while (bss.lock) GX2WaitForVsync(); @@ -153,10 +235,36 @@ error: bss.lock = 0; return; } +int cafiine_fread(int sock, int *result, void *ptr, int size, int fd) { + while (bss.lock) GX2WaitForVsync(); + bss.lock = 1; + + CHECK_ERROR(sock == -1); + + int ret; + char buffer[1 + 8]; + buffer[0] = BYTE_READ; + *(int *)(buffer + 1) = size; + *(int *)(buffer + 5) = fd; + ret = sendwait(sock, buffer, 1 + 8); + ret = recvbyte(sock); + CHECK_ERROR(ret == BYTE_NORMAL); + int sz; + ret = recvwait(sock, &sz, 4); + + ret = recvwaitlen(sock, ptr, sz); + *result = sz - ret; + ret = sendbyte(sock, BYTE_OK); + + bss.lock = 0; + return 0; +error: + bss.lock = 0; + return -1; +} - -int cafiine_fclose(int sock, int *result, int fd) { +int cafiine_fclose(int sock, int *result, int fd,int dumpclose) { while (bss.lock) GX2WaitForVsync(); bss.lock = 1; @@ -165,11 +273,11 @@ int cafiine_fclose(int sock, int *result, int fd) { int ret; char buffer[1 + 4]; buffer[0] = BYTE_CLOSE; + if(dumpclose)buffer[0] = BYTE_CLOSE_DUMP; *(int *)(buffer + 1) = fd; ret = sendwait(sock, buffer, 1 + 4); CHECK_ERROR(ret < 0); - ret = recvbyte(sock); - CHECK_ERROR(ret < 0); + ret = recvbyte(sock); CHECK_ERROR(ret == BYTE_NORMAL); ret = recvwait(sock, result, 4); CHECK_ERROR(ret < 0); @@ -181,6 +289,55 @@ error: return -1; } +int getFiles(int sock, char * path,char * resultname, int * resulttype, int * filesize){ + while (bss.lock) GX2WaitForVsync(); + bss.lock = 1; + CHECK_ERROR(sock == -1); + int result = 0; + int ret; + // create and send buffer with : [cmd id][len_path][path][filesize] + { + int size = 0; + while (path[size++]); + char buffer[1+4+size]; + + buffer[0] = BYTE_GET_FILES; + *(int *)(buffer + 1) = size; + for (ret = 0; ret < size; ret++) + buffer[5 + ret] = path[ret]; + + // send buffer, wait for reply + ret = sendwait(sock, buffer, 1+4+size); + + // wait reply + ret = recvbyte(sock); + CHECK_ERROR(ret != BYTE_OK); + + ret = recvbyte(sock); + CHECK_ERROR(ret != BYTE_FILE && ret != BYTE_FOLDER); + *resulttype = ret; + size = 0; + ret = recvwait(sock, &size, 4); + CHECK_ERROR(ret < 0); + + ret = recvwait(sock, resultname, size+1); + CHECK_ERROR(ret < 0); + + size = 0; + ret = recvwait(sock, &size, 4); + CHECK_ERROR(ret < 0); + *filesize = size; + ret = recvbyte(sock); + CHECK_ERROR(ret < 0); + CHECK_ERROR(ret != BYTE_SPECIAL); + result = 1; + + } +error: + bss.lock = 0; + return result; +} + void cafiine_send_ping(int sock, int val1, int val2) { while (bss.lock) GX2WaitForVsync(); bss.lock = 1; @@ -211,6 +368,19 @@ static int recvwait(int sock, void *buffer, int len) { error: return ret; } +static int recvwaitlen(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: + return len; +} + static int recvbyte(int sock) { unsigned char buffer[1]; @@ -260,3 +430,9 @@ void log_string(int sock, const char* str, char flag_byte) { bss.lock = 0; } +static int sendbyte(int sock, unsigned char byte) { + unsigned char buffer[1]; + + buffer[0] = byte; + return sendwait(sock, buffer, 1); +} \ No newline at end of file diff --git a/saviine/client/saviine532.ld b/saviine/client/saviine532.ld index d8ac10e..8c39a58 100644 --- a/saviine/client/saviine532.ld +++ b/saviine/client/saviine532.ld @@ -35,7 +35,7 @@ PROVIDE(FSDelClient = 0x1068a08); PROVIDE(FSOpenFile = 0x106ef7c); PROVIDE(FSOpenFileAsync = 0x0106a434); PROVIDE(FSInitCmdBlock = 0x01068c54); - +PROVIDE(FSMakeDir = 0x0106f8e0); PROVIDE(FSCloseFile = 0x106f088); PROVIDE(FSReadFile = 0x106f108); PROVIDE(FSReadFileWithPos = 0x106f194); @@ -43,6 +43,9 @@ PROVIDE(FSGetPosFile = 0x106f4c0); PROVIDE(FSSetPosFile = 0x106f530); PROVIDE(FSGetStatFile = 0x106f5a0); PROVIDE(FSIsEof = 0x106f610); +PROVIDE(FSWriteFile = 0x106F228); +PROVIDE(FSFlushQuota = 0x106FAC8); + /* */ PROVIDE(FSGetStat = 0x0106fdc8); @@ -54,7 +57,7 @@ PROVIDE(FSReadDir = 0x0106f780); PROVIDE(FSCloseDir = 0x0106f700); PROVIDE(FSChangeDir = 0x0106eefc); PROVIDE(FSCloseDir = 0x0106f700); - +PROVIDE(FSRemove = 0x0106F960); /* GX2 methods */ PROVIDE(GX2WaitForVsync = 0x1151964); @@ -65,7 +68,7 @@ PROVIDE(socket = 0x10c21c8); PROVIDE(socketclose = 0x10c2314); PROVIDE(connect = 0x10c0828); PROVIDE(send = 0x10c16ac); -PROVIDE(recv = 0x10c0aec); +PROVIDE(recv = 0x10c0aec); /* Standard library methods */ PROVIDE(memcpy = 0x1035a68); diff --git a/saviine/installer/bin/code532.bin b/saviine/installer/bin/code532.bin index ea31e37212371d0cb7efc631918bcbd45c75edf7..33366d58d8fdc8b03211d811ee4b49dd88cd64bc 100644 GIT binary patch literal 7187 zcmbVQUu;{|89z=;aaRuv^CpuRdMXU~hSUTaBpca{L)|QP(l#OqindDeCAlGHj&0da z81LG444+Tv=gs!AXBuxV&Qqthe=8I!t)O?#tAd#HvqA+3{;*7ff9oqO)} zy|*?@d$8`k-}n2z^PTVSIX-tl&u{wruIzZ&yJtUV7dFTCHIjVbSNQ{fil?~8qbmOj zB=c?7RQj(RZ+Q=FzA@NH@#opro0Dw(663)1tIBs)Q)y2t{>Eb#ZjyO#CKg!){61Cr zSvbYOUwzEq%*-}SUE5X23*iYNBAH)BXJ+Qy%f}QSxz2U%)v3fku$uHcr9sUZ!UeNcnLHF6{A<#o) zKWd457c}<5!dS~*SAR8SwzpV@a0!Josss2aB=Y&E4;A58kFe#LsYZf<&lyXk*+8>k z)Gq7@LpI!Fjl0$wV{DT2n%}aXX0{hmr(@20Q+%IPokyG`|njpK;;Zco+3X_T9%Av5crQ6{6VXK!B*eA5_pMhOepMO_$fj% zus0&?#h%Ikko8JgYt}(_FUiT?5QUs0miQL=L$z$P=Nn@Hxp(K42S1Q{17XVEkKCeM3J84{*_^o+V(e??a=|k>7AL_c8q|K!b~>|gMWoDhET(K%N59_MrL={o?dZ#xIOXm)pI9`SssEbAj2 z@{4kTXnu+J5gxhCO&L1>ODvOEu@>^db!HhHHDv@Y%e;8{DgIKPA3Gw^Lv`ZfyR4#T%2@a-sUiDLg8 z{tU@?@^u*bM|TP<*7BS7Lipj<7G_lInMYx#)jOQLWxtk2H@u z;zRrq;+ZlyZ;2Sm_|gm#=R{!a>x$Y#OuF(D+;a|lq1+(-qgbEQ8Y#xO>ScW{8>kO` zjGF4IYa6yh&!{|u1&rLr*}d!8NS~Dc{{a5~rS$(*`2RX|TqmDPzxq;qK=>Ou-3HH( zc(W9LuA(aHDasq{ZIkHe=U#(XEQ7Z!d3oYNpAYiXi$vd}Z+bt2`Y?Ms?hSu4_+lm+ z`7hR5t*c7CBK47;VbeC!jdf{;Ql0oy@@@ibI(<=}BYhN~E;TV@#t}YmJI?@vdDj_$ z_8m%Od)f*q4&&W#+D-Y1 zIz|uEb>L`Tqb{tx@8q;8-#PbCFSPU+xvgLLAJ?JT&17E)IqypP$Fd(Jv5(Pivu;Nh z@eGSvLm!rXPt?A-FT`UDwYDm46!R9eqo7eOlHGk&@1RYCmX@@BNt*y|m}r+VC+8I} z;rc)mdj{=MNejYv?iu%`EyjMtjU(T}&7oXSV-b4I^B>2%&!yW>eA$~L(s$i(ly?z{bLxOk zU_bfHkt^`a2<@dgG}Ry;^~v#zy5%3(Yn>Tn?BhLhSl*Wz?#oQxm)QdD_K)NLp1VeG z-`!P}-CeQ9rT!00UMdZGZg-dLmc2PHpGnxXu)SB*ToS#3>S>{gyO43;9=t1h5AHEj z`hS|}H57vdtVR7_#D#i^)RmS`sw-1x20H2gDVxr)*QQhHq0^jilTzojP3JUp(*G`Y zo&Ju_yMOMXGYlQ^l+vbo$?+c;#WzWDvHBA3r^ub4W3Cf!(Y(hWzJ1>v*1=B)?&D+s zt~E1V)(mx7Q~p2KWlhvK_7H(x(h8zRmFKOc30U1$WbOxDM zJipPDNu~~ovvfC{AOBb0GiD+7Zk(&F{{l&OhYx?>&o}H?WW)10td+xBIo4;lQTiYM z5nKAbw*DmKI~Z#XJI3{&>bKSdLu%yk_)jAPR%v!2vPu1$$nxGjfO~Q%UBUKIXxm7%t zN#+%;w!UWj&ejaxoK{^clmG>aF@wgjjdJxAS7{4sy-+Uk6}3>*mScpO5U;#m=1XD? zUN;swF*^%_R?13WnBU*m>MyUatZ3EQ!jCn0z@%ei2Hi$GQqrQUWdy7&>Dq0kHunaV`GCI>)}+=JO9cwFO}GRPn?xuX{k ly%5uiE6B~#hFUBvbNYzYwFWI3tCpA5y2@f}>g$Ci_Fp*+kW2so literal 7852 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 diff --git a/saviine/installer/bin/saviine.o b/saviine/installer/bin/saviine.o index ac39b32a88961585b7ad5d487c666b165f8389e8..94ff1dff04b1ea903a396c0bbc654cc07e03e39e 100644 GIT binary patch literal 9248 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 1) @@ -86,11 +94,36 @@ namespace saviine_server { if (args[0].Equals("fastmode") || args[0].Equals("fast") || args[0].Equals("-fast")) { - fastmode = true; - Console.WriteLine("Now using fastmode"); + op_mode = BYTE_MODE_D; + fastmode = true; + }else if(args[0].Equals("inject")) { + op_mode = BYTE_MODE_I; } } + if (args.Length == 2) + { + if (args[0].Equals("dump")) { + op_mode = BYTE_MODE_D; + if (args[1].Equals("fastmode") || args[1].Equals("fast") || args[1].Equals("-fast")) + { + fastmode = true; + } + }else if(args[0].Equals("inject")) { + op_mode = BYTE_MODE_I; + } + } + + if (op_mode == BYTE_MODE_D) + { + Console.WriteLine("Dump mode"); + if(fastmode)Console.WriteLine("Now using fastmode"); + } + else if(op_mode == BYTE_MODE_I) + { + Console.WriteLine("Injection mode!"); + } + // Check for cafiine_root and logs folder if (!Directory.Exists(root)) { @@ -149,6 +182,21 @@ namespace saviine_server log.Flush(); Console.Write(str); } + public static int countDirectory(string targetDirectory) + { + int x = 0; + // Process the list of files found in the directory. + string [] fileEntries = Directory.GetFiles(targetDirectory); + foreach(string fileName in fileEntries) + x++; + + // Recurse into subdirectories of this directory. + string [] subdirectoryEntries = Directory.GetDirectories(targetDirectory); + foreach(string subdirectory in subdirectoryEntries) + x++; + + return x; + } static void Handle(object client_obj) { @@ -156,6 +204,8 @@ namespace saviine_server FileStream[] files = new FileStream[256]; Dictionary files_request = new Dictionary(); StreamWriter log = null; + Dictionary> dir_files = new Dictionary>(); + try { @@ -196,23 +246,231 @@ namespace saviine_server while (true) { - byte cmd_byte = reader.ReadByte(); + //Log(log, "cmd_byte"); + byte cmd_byte = reader.ReadByte(); switch (cmd_byte) - { - case BYTE_HANDLE: + { + case BYTE_OPEN: { + //Log(log, "BYTE_OPEN"); + bool request_slow = false; + + int len_path = reader.ReadInt32(); + int len_mode = reader.ReadInt32(); + string path = reader.ReadString(Encoding.ASCII, len_path - 1); + if (reader.ReadByte() != 0) throw new InvalidDataException(); + string mode = reader.ReadString(Encoding.ASCII, len_mode - 1); + if (reader.ReadByte() != 0) throw new InvalidDataException(); + + + if (File.Exists(LocalRoot + path)) + { + int handle = -1; + for (int i = 1; i < files.Length; i++) + { + if (files[i] == null) + { + handle = i; + break; + } + } + if (handle == -1) + { + Log(log, name + " Out of file handles!"); + writer.Write(BYTE_SPECIAL); + writer.Write(-19); + writer.Write(0); + break; + } + //Log(log, name + " -> fopen(\"" + path + "\", \"" + mode + "\") = " + handle.ToString()); + + files[handle] = new FileStream(LocalRoot + path, FileMode.Open, FileAccess.Read, FileShare.Read); + + writer.Write(BYTE_SPECIAL); + writer.Write(0); + writer.Write(handle); + + } + else { writer.Write(BYTE_NORMAL); } + + // Log(log, "No request found: " + LocalRoot + path); + + + break; + } + case BYTE_SETPOS: + { + //Log(log, "BYTE_SETPOS"); + int fd = reader.ReadInt32(); + int pos = reader.ReadInt32(); + if ((fd & 0x0fff00ff) == 0x0fff00ff) + { + int handle = (fd >> 8) & 0xff; + if (files[handle] == null) + { + writer.Write(BYTE_SPECIAL); + writer.Write(-38); + break; + } + FileStream f = files[handle]; + Log(log, "Postion was set to " + pos + "for handle " + handle); + f.Position = pos; + writer.Write(BYTE_SPECIAL); + writer.Write(0); + } + else + { + writer.Write(BYTE_NORMAL); + } + break; + } + case BYTE_GET_FILES: + { + int len_path = reader.ReadInt32(); + string path = reader.ReadString(Encoding.ASCII, len_path-1); + if (reader.ReadByte() != 0) throw new InvalidDataException(); + int x = 0; + if (path[0] == '/' && path[1] == '/') + { + path = path.Substring(2); + } + else if (path[0] == '/') + { + path = path.Substring(1); + } + path = LocalRoot + path; + + if(Directory.Exists(path)) { + x = countDirectory(path); + if (x > 0) + { + Dictionary value; + if (!dir_files.TryGetValue(path, out value)) + { + //Console.Write("found no \"" + path + "\" in dic \n"); + value = new Dictionary(); + string[] fileEntries = Directory.GetFiles(path); + foreach (string fn in fileEntries) + { + string fileName = Path.GetFileName(fn); + value.Add(fileName, BYTE_FILE); + } + string[] subdirectoryEntries = Directory.GetDirectories(path); + foreach (string sd in subdirectoryEntries) + { + string subdirectory = Path.GetFileName(sd); + value.Add(subdirectory, BYTE_FOLDER); + } + dir_files.Add(path, value); + //Console.Write("added \"" + path + "\" to dic \n"); + } + else + { + //Console.Write("dic for \"" + path + "\" ready \n"); + } + + if (value.Count > 0) + { + writer.Write(BYTE_OK); + //Console.Write("sent ok byte \n"); + foreach (var item in value) + { //Write + writer.Write(item.Value); + //Console.Write("type : " + item.Value); + writer.Write(item.Key.Length); + //Console.Write("length : " + item.Key.Length); + writer.Write(item.Key, Encoding.ASCII, true); + //Console.Write("filename : " + item.Key); + int length = 0; + if (item.Value == BYTE_FILE) length = (int)new System.IO.FileInfo(path + "/" + item.Key).Length; + writer.Write(length); + //Console.Write("filesize : " + length + " \n"); + value.Remove(item.Key); + //Console.Write("removed from list! " + value.Count + " remaining\n"); + break; + } + writer.Write(BYTE_SPECIAL); // + //Console.Write("file sent, wrote special byte \n"); + } + else + { + writer.Write(BYTE_END); // + //Console.Write("list was empty return BYTE_END \n"); + dir_files.Remove(path); + //Console.Write("removed \"" + path + "\" from dic \n"); + } + } + else + { + //Console.Write(path + "empty \n"); + writer.Write(BYTE_END); // + } + } + else + { + //Console.Write(path + " is not found\n"); + writer.Write(BYTE_END); // + } + //Console.Write("in break \n"); + break; + } + case BYTE_READ: + { + //Log(log,"BYTE_READ"); + int size = reader.ReadInt32(); + int fd = reader.ReadInt32(); + + + FileStream f = files[fd]; + + byte[] buffer = new byte[size]; + int sz = (int)f.Length; + int rd = 0; + + //Log(log, "want size:" + size + " for handle: " + fd); + + writer.Write(BYTE_SPECIAL); + + rd = f.Read(buffer, 0, buffer.Length); + //Log(log,"rd:" + rd); + writer.Write(rd); + writer.Write(buffer, 0, rd); + + int offset = (int)f.Position; + int progress = (int)(((float)offset / (float)sz) * 100); + string strProgress = progress.ToString().PadLeft(3, ' '); + string strSize = (sz / 1024).ToString(); + string strCurrent = (offset / 1024).ToString().PadLeft(strSize.Length, ' '); + Console.Write("\r\t--> {0}% ({1} kB / {2} kB)", strProgress, strCurrent, strSize); + log.Write("\r\t--> {0}% ({1} kB / {2} kB)", strProgress, strCurrent, strSize); + //Console.Write("send " + rd ); + if(offset == sz) Console.Write("\n"); + int ret = -5; + if ((ret =reader.ReadByte()) != BYTE_OK) + { + Console.Write("error, got " + ret + " instead of " + BYTE_OK); + //throw new InvalidDataException(); + } + + //Log(log, "break READ"); + + break; + } + case BYTE_HANDLE: + { + //Log(log,"BYTE_HANDLE"); // Read buffer params : fd, path length, path string int fd = reader.ReadInt32(); int len_path = reader.ReadInt32(); string path = reader.ReadString(Encoding.ASCII, len_path - 1); if (reader.ReadByte() != 0) throw new InvalidDataException(); - if (!Directory.Exists(LocalRoot + path)) + if (!Directory.Exists(LocalRoot + "dump" + path)) { - Directory.CreateDirectory(Path.GetDirectoryName(LocalRoot + path)); + Directory.CreateDirectory(Path.GetDirectoryName(LocalRoot + "dump" + path)); } // Add new file for incoming data - files_request.Add(fd, new FileStream(LocalRoot + path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write)); + files_request.Add(fd, new FileStream(LocalRoot + "dump" + path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write)); // Send response if (fastmode) { writer.Write(BYTE_REQUEST); @@ -227,7 +485,8 @@ namespace saviine_server break; } case BYTE_DUMP: - { + { + //Log(log,"BYTE_DUMP"); // Read buffer params : fd, size, file data int fd = reader.ReadInt32(); int sz = reader.ReadInt32(); @@ -257,25 +516,31 @@ namespace saviine_server } case BYTE_CLOSE: { + //Log(log, "BYTE_CLOSE"); int fd = reader.ReadInt32(); - if ((fd & 0x0fff00ff) == 0x0fff00ff) + + + if (files[fd] == null) { - int handle = (fd >> 8) & 0xff; - if (files[handle] == null) - { - writer.Write(BYTE_SPECIAL); - writer.Write(-38); - break; - } - Log(log, name + " close(" + handle.ToString() + ")"); - FileStream f = files[handle]; - writer.Write(BYTE_SPECIAL); - writer.Write(0); - f.Close(); - files[handle] = null; + writer.Write(-38); + break; } - else + //Log(log, name + " close(" + fd.ToString() + ")"); + FileStream f = files[fd]; + + writer.Write(BYTE_SPECIAL); + writer.Write(0); + f.Close(); + files[fd] = null; + + + break; + } + case BYTE_CLOSE_DUMP: + { + int fd = reader.ReadInt32(); + if ((fd & 0x0fff00ff) != 0x0fff00ff) { // Check if it is a file to dump foreach (var item in files_request) @@ -291,7 +556,6 @@ namespace saviine_server // Close file and remove from request list dump_file.Close(); files_request.Remove(fd); - break; } } @@ -300,18 +564,32 @@ namespace saviine_server writer.Write(BYTE_NORMAL); } break; - } + } case BYTE_PING: { + //Log(log, "BYTE_PING"); int val1 = reader.ReadInt32(); int val2 = reader.ReadInt32(); Log(log, name + " PING RECEIVED with values : " + val1.ToString() + " - " + val2.ToString()); break; } + case BYTE_G_MODE: + { + if (op_mode == BYTE_MODE_D) + { + writer.Write(BYTE_MODE_D); + } + else if (op_mode == BYTE_MODE_I) + { + writer.Write(BYTE_MODE_I); + } + break; + } case BYTE_LOG_STR: { + //Log(log, "BYTE_LOG_STR"); int len_str = reader.ReadInt32(); string str = reader.ReadString(Encoding.ASCII, len_str - 1); if (reader.ReadByte() != 0) throw new InvalidDataException(); @@ -320,6 +598,7 @@ namespace saviine_server break; } default: + Log(log, "xx" + cmd_byte); throw new InvalidDataException(); } } diff --git a/saviine/server/src/bin/dump.bat b/saviine/server/src/bin/dump.bat new file mode 100644 index 0000000..18bad22 --- /dev/null +++ b/saviine/server/src/bin/dump.bat @@ -0,0 +1 @@ +saviine_server.exe dump \ No newline at end of file diff --git a/saviine/server/src/bin/inject.bat b/saviine/server/src/bin/inject.bat new file mode 100644 index 0000000..3b2b394 --- /dev/null +++ b/saviine/server/src/bin/inject.bat @@ -0,0 +1 @@ +saviine_server.exe inject \ No newline at end of file diff --git a/saviine/server/src/bin/saviine_server.exe b/saviine/server/src/bin/saviine_server.exe new file mode 100644 index 0000000000000000000000000000000000000000..ae95be6b99e9f5f4d776b40fa3527e3d6892ee5c GIT binary patch literal 30208 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 z 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