/* bpatch.c - Byte Patch: modify a binary file. BPatch is a complement to BDiff. BDiff produces output based on the differences of two files; BPatch uses that output to modify a file. Jason Hood, 28 December, 1998 to 2 January, 1999. Public Domain. 990401: Added Tell: & Quote: options; Use -f to specify the patch file; Select a file to patch from the patch file; Always apply/restore patches (previously, answering "No" would ignore the patch). 16 & 17 October, 2003, v1.00: Tell statement will also stop the query. */ #define PVERS "1.00" #define PDATE "17 October, 2003" #include #include #include #include #ifdef _WIN32 # include # ifndef utime # define utime _utime # endif #else # include #endif #ifdef __DJGPP__ void __crt0_load_environment_file( char* dummy ) { } char** __crt0_glob_function( char* dummy ) { return 0; } #endif int restore = 0; // Restore original bytes int update = 0; // Update timestamp int quiet = 0; // Warnings int yes = 0; // Auto-answer prompts char** files; // List of files to patch char* pfiles; // Was a file patched? int num_files; // Number of files to patch #define LINE_LEN 255 // Maximum length of the line/token char line[LINE_LEN + 1]; // Line from the patch file int line_no = 0; // Line number int literal; // Is it a literal token? int keep_line = 0; // Read the line again void patch_file( char* name ); void process_patch( FILE* patch, FILE* file ); char* read_line( FILE* file ); char* read_token( char* line, char* token ); char* read_literal( char* line, char* token ); void show_help( void ); #define ignore( file ) \ do { int ch_; do \ ch_ = getc( file );\ while (ch_ != '\n' && ch_ != EOF); } while (0) int main( int argc, char* argv[] ) { char* name = "crack"; // Name of the patch file int j, k; if (argc > 1 && (strcmp( argv[1], "/?" ) == 0 || strcmp( argv[1], "-?" ) == 0 || strcmp( argv[1], "--help" ) == 0)) { show_help(); return 0; } files = malloc( argc * sizeof(char*) ); pfiles = calloc( argc, 1 ); if (files == NULL || pfiles == NULL) { fputs( "Not enough memory.\n", stderr ); return 1; } num_files = 0; for (j = 1; j < argc; ++j) { if (argv[j][0] == '-') { for (k = 1; argv[j][k]; ++k) { switch (argv[j][k]) { case 'r': restore = 1; break; case 'u': update = 1; break; case 'q': quiet = 1; break; case 'y': yes = 1; break; case 'f': name = (argv[j][k+1] == '\0') ? argv[++j] : argv[j] + k+1; goto break_for; default: fprintf( stderr, "Unknown option: %c\n", argv[j][k] ); return 1; } } break_for: ; } else files[num_files++] = argv[j]; } patch_file( name ); if (!quiet) { k = 0; for (j = 0; j < num_files; ++j) { if (!pfiles[j]) { if (!k) k = fprintf( stderr, "\"%s\" does not contain: ", name ); else fprintf( stderr, "%*c", k, ' ' ); fputs( files[j], stderr ); putc( '\n', stderr ); } } } return 0; } // Open the file named by name and search for the file(s) to be patched. void patch_file( char* name ) { FILE* patch; // Patch file FILE* file; // File to be patched char token[LINE_LEN + 1]; char* rest; struct stat finfo; struct utimbuf tinfo; int j; if ((patch = fopen( name, "r" )) == NULL) { fprintf( stderr, "%s: File not found\n", name ); exit( 1 ); } while ((rest = read_line( patch )) != NULL) { rest = read_token( rest, token ); if (stricmp( token, "File:" ) == 0) { rest = read_token( rest, token ); if (num_files != 0) // Search the list { for (j = 0; j < num_files; ++j) if (stricmp( token, files[j] ) == 0) break; if (j == num_files) // Didn't find it continue; pfiles[j] = 1; } if ((file = fopen( token, "r+b" )) == NULL) { if (!quiet) fprintf( stderr, "%s: File not found\n", token ); } else { if (!update) stat( token, &finfo ); process_patch( patch, file ); fclose( file ); if (!update) { tinfo.actime = finfo.st_atime; tinfo.modtime = finfo.st_mtime; utime( token, &tinfo ); } } } } fclose( patch ); } // Patch file given the information in patch. void process_patch( FILE* patch, FILE* file ) { char token[LINE_LEN + 1]; char* rest; int ask = 0, process = 1; char* bp; long offset = 0; unsigned char new_byte[LINE_LEN / 2]; unsigned char old_byte[LINE_LEN / 2]; unsigned char cur_byte[LINE_LEN / 2]; int new_len, old_len; int match_new, match_old; int j; while ((rest = read_line( patch )) != NULL) { rest = read_token( rest, token ); if (stricmp( token, "Tell:" ) == 0) { rest = read_token( rest, token ); printf( "%sing %s.\n", (restore) ? "Restor" : "Patch", token ); ask = 0; continue; } if (stricmp( token, "Quote:" ) == 0) { rest = read_token( rest, token ); puts( token ); continue; } if (stricmp( token, "Ask:" ) == 0) { if (!yes) { ask = 1; rest = read_token( rest, token ); printf( "%s? ", token ); fflush( stdout ); *line = getchar(); if (*line != '\n') ignore( stdin ); process = (restore) ? (*line == 'y' || *line == 'Y') : (*line != 'n' && *line != 'N'); } continue; } if (stricmp( token, "NoAsk" ) == 0) { ask = 0; continue; } if (stricmp( token, "File:" ) == 0) { keep_line = 1; break; } if (!literal) { offset = strtol( token, &bp, 16 ); if (*bp == ':') { fseek( file, offset, SEEK_SET ); rest = read_token( rest, token ); } } new_len = old_len = 0; do { if (literal) { for (j = 0; token[j]; ++j) new_byte[new_len++] = token[j]; } else { new_byte[new_len++] = (unsigned char)strtol( token, NULL, 16 ); } rest = read_token( rest, token ); } while (*token != '[' && *token != '\0'); for (;;) { rest = read_token( rest, token ); if (*token == ']' || *token == '\0') break; if (literal) { for (j = 0; token[j]; ++j) old_byte[old_len++] = token[j]; } else { old_byte[old_len++] = (unsigned char)strtol( token, NULL, 16 ); } } if (new_len != old_len) fprintf( stderr, "Lengths differ; ignoring line %d\n", line_no ); else { if (fread( cur_byte, 1, old_len, file ) != old_len) fprintf( stderr, "Too many bytes; ignoring line %d\n", line_no ); else { match_old = (memcmp( old_byte, cur_byte, old_len ) == 0); match_new = (memcmp( new_byte, cur_byte, old_len ) == 0); j = 1; if (!match_old && !match_new) { printf( "Bytes differ (line %d); %s anyway? ", line_no, (restore) ? "restore" : "patch"); fflush( stdout ); *line = getchar(); if (*line != '\n') ignore( stdin ); if (*line != 'y' && *line != 'Y') j = 0; } if (j) { fseek( file, -old_len, SEEK_CUR ); fwrite( (yes || !ask) ? ((restore) ? old_byte : new_byte) /* (ask) */ : (process) ? new_byte : old_byte, 1, old_len, file ); fseek( file, 0, SEEK_CUR ); // fread can't immediately follow fwrite } } } } } // Read a line from a file, ignoring blank lines and comments. // Return NULL for EOF, pointer to first non-space otherwise. char* read_line( FILE* file ) { int ch, len; if (keep_line) { keep_line = 0; len = 1; // Just something != 0 } else { ++line_no; len = 0; while ((ch = getc( file )) != EOF) { if (len == 0) { if (ch == ' ' || ch == '\t') continue; if (ch == '\n') { ++line_no; continue; } if (ch == '#') { ignore( file ); ++line_no; continue; } } if (ch == '\n') break; if (len == LINE_LEN) { fprintf( stderr, "Warning: line %d truncated.\n", line_no ); ignore( file ); break; } line[len++] = ch; } line[len] = '\0'; } return (len == 0) ? NULL : line; } // Read a token from the line and point to the next. char* read_token( char* line, char* token ) { literal = 0; if (line == NULL) *token = '\0'; else { if (*line == '\"') { literal = 1; line = read_literal( line + 1, token ); } else { while (*line != ' ' && *line != '\t' && *line != '\0' && *line != '#') *token++ = *line++; *token = '\0'; } while (*line == ' ' || *line == '\t') ++line; if (*line == '\0' || *line == '#') line = NULL; } return line; } // Read a literal from the line and point to the character after closing quote. // Literals can contain quotes by doubling the quote ("Literal ""quotes"""). char* read_literal( char* line, char* token ) { while (*line != '\0') { if (*line == '\"') { ++line; if (*line != '\"') break; } *token++ = *line++; } *token = '\0'; return line; } void show_help( void ) { puts( "BPatch by Jason Hood .\n" "Version "PVERS" ("PDATE"). Public Domain.\n" "http://misc.adoxa.cjb.net/\n" "\n" "BPatch is a complement to BDiff. BDiff produces output based on the\n" "differences of two files; BPatch uses that output to modify a file.\n" "\n" "bpatch [file...] [-f patch] [-qruy]\n" "\n" " file the file(s) to modify (defaults to all files in patch)\n" " patch the file containing the patch(es) (defaults to \"crack\")\n" " -q suppress missing file warning\n" " -r restore the original bytes\n" " -u update the timestamp\n" " -y answer queries automatically\n" "\n" "Patch file format:\n" "\n" "# Comments extend to the end of the line\n" "File: filename # file to be patched\n" "Tell: \"message\" # display message (see below)\n" "Quote: \"Message.\" # display message\n" "Ask: \"Prompt\" # query the user (see below)\n" "NoAsk # stop the query\n" ": [ ] # offset & bytes in hexadecimal\n" " [ ] # strings in quotes\n" "\n" "The Tell message is prefixed with \"Patching \" or \"Restoring \" and\n" "suffixed with \".\"; the Ask prompt is suffixed with \"? \". The default\n" "answer is yes when patching, no when restoring. The answer will apply\n" "to all patches up to the next Tell, Ask, NoAsk or File. To include a\n" "quote in a string, double it (\"a \"\"quoted\"\" string\")." ); }