├── README.md ├── sfo └── sfo.c /README.md: -------------------------------------------------------------------------------- 1 | # sfo.c 2 | 3 | sfo.c can be compiled into a command line program, which is faster than the old "sfo" Bash script (roughly by factor 30). It is still compatible with the "pkgrename" and "fw" scripts (https://github.com/hippie68/pkgrename, https://github.com/hippie68/fw), making their output faster. It can be used to query or modify param.sfo data or to build new param.sfo files from scratch. 4 | 5 | Usage: sfo [OPTIONS] FILE 6 | 7 | Reads a file to print or modify its SFO parameters. 8 | Supported file types: 9 | - PS4 param.sfo (print and modify) 10 | - PS4 disc param.sfo (print only) 11 | - PS4 PKG (print only) 12 | 13 | The modification options (-a/--add, -d/--delete, -e/--edit, -s/--set) can be 14 | used multiple times. Modifications are done in memory first, in the order in 15 | which they appear in the program's command line arguments. 16 | If any modification fails, all changes are discarded and no data is written: 17 | 18 | Modification Fail condition 19 | -------------------------------------- 20 | Add Parameter already exists 21 | Delete Parameter not found 22 | Edit Parameter not found 23 | Set None 24 | 25 | Options: 26 | -a, --add TYPE PARAMETER VALUE Add a new parameter, not overwriting existing 27 | data. TYPE must be either "int" or "str". 28 | -d, --delete PARAMETER Delete specified parameter. 29 | --debug Print debug information. 30 | --decimal Display integer values as decimal numerals. 31 | -e, --edit PARAMETER VALUE Change specified parameter's value. 32 | -f, --force Do not abort when modifications fail. Make 33 | option --new-file overwrite existing files. 34 | -h, --help Print usage information and quit. 35 | --new-file If FILE (see above) does not exist, create a 36 | new param.sfo file of the same name. 37 | -o, --output-file OUTPUT_FILE Save the final data to a new file of type 38 | "param.sfo", overwriting existing files. 39 | -q, --query PARAMETER Print a parameter's value and quit. 40 | If the parameter exists, the exit code is 0. 41 | -s, --set TYPE PARAMETER VALUE Set a parameter, whether it exists or not, 42 | overwriting existing data. 43 | -v, --verbose Increase verbosity. 44 | --version Print version information and quit. 45 | 46 | ### Examples 47 | 48 | Viewing SFO parameters: 49 | 50 | sfo param.sfo 51 | sfo example.pkg --verbose --decimal 52 | 53 | Modifying SFO parameters: 54 | 55 | sfo -e title "Super Mario Bros." -d title_00 -s int pubtool_ver 0x123 param.sfo 56 | 57 | Modifying SFO parameters but saving to a different file: 58 | 59 | sfo -e title "Super Mario Bros." -d title_00 -s int pubtool_ver 0x123 param.sfo --output-file test.sfo 60 | 61 | Extracting a param.sfo file from a PS4 PKG file: 62 | 63 | sfo example.pkg --output-file param.sfo 64 | 65 | Creating a new param.sfo file from scratch: 66 | 67 | sfo --new-file -a str app_ver 01.00 -a str category gdk -a int attribute 12 param.sfo 68 | 69 | Printing a single parameter: 70 | 71 | $ sfo -q title param.sfo 72 | Super Mario Bros. 73 | 74 | Querying will return 0 if the parameter exists: 75 | 76 | $ sfo -q title param.sfo 77 | Super Mario Bros. 78 | $ echo $? 79 | 0 80 | 81 | $ sfo -q asdf param.sfo 82 | $ echo $? 83 | 1 84 | 85 | Use querying to save parameters in your scripts/tools, for example (Bash): 86 | 87 | title=$(sfo -q title param.sfo) 88 | 89 | Or parse them all (Bash): 90 | 91 | i=0 92 | while read -r line; do 93 | param[i]=${line%%=*} 94 | value[i]=${line#*=} 95 | ((i++)) 96 | done < <(sfo param.sfo) 97 | 98 | ### How to compile 99 | 100 | gcc sfo.c -O3 -s -o sfo 101 | 102 | For Windows: 103 | 104 | x86_64-w64-mingw32-gcc-win32 sfo.c -O3 -s -o sfo.exe 105 | 106 | Windows binaries are available at https://github.com/hippie68/sfo/releases. 107 | 108 | ### Troubleshooting 109 | 110 | Option --debug prints the exact param.sfo file layout, making it easy to spot errors quickly. 111 | 112 | Please report bugs or request features at https://github.com/hippie68/sfo/issues. 113 | 114 | # sfo (old Bash script) 115 | 116 | Prints or modifies SFO parameters of a PS4 PKG or a param.sfo file. 117 | Providing a search string will output the value of that specific key only. 118 | Providing a replacement string will write it to the file (needs option -w). 119 | 120 | Usage: 121 | 122 | sfo [options] file [search] [replace] 123 | 124 | Options: 125 | 126 | -h Display help 127 | -w Enable write mode 128 | 129 | You can print all SFO info: 130 | 131 | sfo param.sfo 132 | sfo your-game.pkg 133 | 134 | You can search for a specific key (case-insensitive), for example: 135 | 136 | sfo param.sfo PUBTOOLINFO 137 | sfo your-game.pkg content_id 138 | 139 | You can also overwrite existing data, for example: 140 | 141 | sfo -w param.sfo app_type 2 142 | 143 | To overwrite integer data, you can use decimal or hexadecimal numbers. 144 | 145 | Known issues: 146 | 147 | - Overwriting PKG files directly ~~may~~ will result in a broken PKG. Writing the original values will restore the PKG. 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /sfo: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Prints SFO information of either a PS4 PKG or param.sfo file. 4 | # Optionally searches for (and replaces) a specific string. 5 | # Made with info from https://www.psdevwiki.com/ps4/Param.sfo 6 | 7 | show_usage() { 8 | echo "Usage: ${0##*/} [-hw] file [search] [replace]" >&2 9 | } 10 | 11 | show_help() { 12 | show_usage 2>&1 13 | echo " 14 | Prints SFO information of either a PS4 PKG or param.sfo file. 15 | Providing a search string will output the value of that specific key only. 16 | Providing a replacement string will write it to the file (needs option -w). 17 | 18 | Options: 19 | -h Display this help info 20 | -w Enable write mode" 21 | exit 22 | } 23 | 24 | if [[ $@ == "" ]]; then 25 | show_usage 26 | exit 1 27 | fi 28 | 29 | # Parse command line options 30 | while [[ $1 == "-"?* ]]; do 31 | for ((i=1;i<${#1};i++)); do 32 | case ${1:$i:1} in 33 | h) show_help ;; 34 | w) option_write_enabled=true ;; 35 | esac 36 | done 37 | shift 38 | done 39 | 40 | file=$1 41 | [[ -f "$file" ]] || { echo "Not a file: $file" >&2; exit 1; } 42 | search=${2,,} 43 | replace=$3 44 | if [[ $replace != "" && $option_write_enabled != true ]]; then 45 | echo "Use option -w to enable write mode." >&2 46 | exit 1 47 | fi 48 | 49 | getbytes() { 50 | xxd -u -p -s $(($1+pkgOffset)) -l "$2" "$file" 51 | } 52 | 53 | # Finds the param.sfo's offset inside a PKG file 54 | # https://www.psdevwiki.com/ps4/Package_Files 55 | get_pkg_offset() { 56 | local pkg_file_count pkg_table_offset full_table i current_datablock id 57 | pkg_file_count=$((0x$(getbytes 0x00C 4))) 58 | pkg_table_offset=0x$(getbytes 0x018 4) 59 | full_table=$(getbytes $pkg_table_offset $((32*pkg_file_count))) 60 | full_table=${full_table//$'\n'} # xxd adds newlines which we must remove 61 | for ((i=0;i&2 78 | exit 1 79 | fi 80 | elif [[ $magic == 00505346 ]]; then # param.sfo file 81 | pkgOffset=0 82 | else 83 | echo "$file: Not a PKG neither a param.sfo file!" >&2 84 | exit 1 85 | fi 86 | header=$(getbytes 0x00 20) 87 | keyTableOffset=${header:16:8} 88 | keyTableOffset=0x${keyTableOffset:6:2}${keyTableOffset:4:2}${keyTableOffset:2:2}${keyTableOffset:0:2} 89 | dataTableOffset=${header:24:8} 90 | dataTableOffset=0x${dataTableOffset:6:2}${dataTableOffset:4:2}${dataTableOffset:2:2}${dataTableOffset:0:2} 91 | indexTableEntries=${header:32:8} 92 | indexTableEntries=$((0x${indexTableEntries:6:2}${indexTableEntries:4:2}${indexTableEntries:2:2}${indexTableEntries:0:2})) 93 | 94 | # Get nullstring-delimited keys 95 | # https://www.psdevwiki.com/ps4/Param.sfo#Key_table 96 | i=0 97 | while read -r -d $'\0' line; do 98 | key[$i]="$line" 99 | ((i++)) 100 | [[ $i -gt $indexTableEntries ]] && break 101 | done < <(getbytes "$keyTableOffset" 5000 | xxd -r -p) 102 | 103 | # Get full index table 104 | # https://www.psdevwiki.com/ps4/Param.sfo#Index_table 105 | indexTable=$(getbytes 0x14 $((0x10*indexTableEntries))) 106 | indexTable=${indexTable//$'\n'} # xxd adds newlines which we must remove 107 | 108 | # Get full data table 109 | dataTable=$(getbytes "$dataTableOffset" 5000) 110 | dataTable=${dataTable//$'\n'} 111 | 112 | # Search data table data 113 | for ((i=0;i&2 134 | exit 1 135 | fi 136 | # Fill space with zeros 137 | printf "%0$((paramMaxLen*2))d" | xxd -r -p -seek \ 138 | $((pkgOffset+dataTableOffset+dataOffset)) - "$file" 139 | # Write new string to file 140 | printf "$replace" | xxd -o $((pkgOffset+dataTableOffset+dataOffset)) \ 141 | | xxd -r - "$file" 142 | # Write new paramLen to file 143 | newlen=$(printf "%08x" $((${#replace}+1))) 144 | newlen=${newlen:6:2}${newlen:4:2}${newlen:2:2}${newlen:0:2} 145 | printf "%x: %s" $((pkgOffset+0x14+i*16+4)) "$newlen" \ 146 | | xxd -r - "$file" 147 | ;; 148 | 0404) # Replace integer 149 | # Test if new value is an integer 150 | ((replace)) 2> /dev/null 151 | if [[ $? == 1 ]]; then 152 | echo "Error: Not an integer: \"$replace\"." >&2 153 | exit 1 154 | fi 155 | # Check new integer for valid size 156 | if [[ $replace == 0x* ]]; then 157 | newint=$(printf "%08d" "${replace#0x}") 158 | else 159 | newint=$(printf "%08x" "$replace" 2> /dev/null) 160 | fi 161 | if [[ ${#newint} -gt $((paramMaxLen*2)) ]]; then 162 | echo "Error: Replacement integer \"$replace\" larger than limit ($((0xFFFFFFFF)))." >&2 163 | exit 1 164 | fi 165 | # Fill space with zeros 166 | printf "%x: %04d" $((pkgOffset+dataTableOffset+dataOffset)) \ 167 | | xxd -r - "$file" 168 | # Write new integer to file 169 | if [[ $replace != 0x* ]]; then 170 | newint=${newint:6:2}${newint:4:2}${newint:2:2}${newint:0:2} 171 | fi 172 | printf "%x: %s" $((pkgOffset+dataTableOffset+dataOffset)) "$newint" \ 173 | | xxd -r - "$file" 174 | ;; 175 | *) echo "Cannot replace this type of data: $param_fmt."; exit 1 ;; 176 | esac 177 | exit 178 | fi 179 | 180 | # Read data 181 | data=${dataTable:$((dataOffset*2)):$((paramLen*2))} 182 | case $param_fmt in 183 | 0400) # UTF-8 special mode string 184 | echo "[SPECIAL MODE STRING]" 185 | ;; 186 | 0402) # UTF-8 string 187 | echo "${data%00}" | xxd -r -p 188 | echo 189 | ;; 190 | 0404) # Integer 191 | data=0x${data:6:2}${data:4:2}${data:2:2}${data:0:2} 192 | echo "$data" 193 | ;; 194 | *) echo "[UNKNOWN DATA TYPE]" 195 | esac 196 | 197 | [[ $search == "${key[$i],,}" ]] && exit 198 | done 199 | -------------------------------------------------------------------------------- /sfo.c: -------------------------------------------------------------------------------- 1 | /* Reads a file to print or modify its SFO parameters. 2 | * Supported file types: 3 | * - PS4 param.sfo (print and modify) 4 | * - PS4 disc param.sfo (print only) 5 | * - PS4 PKG (print only) 6 | * Made with info from https://www.psdevwiki.com/ps4/Param.sfo. 7 | * Get updates and Windows binaries at https://github.com/hippie68/sfo. */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #if __has_include("") 17 | #include 18 | #else 19 | // Replacement function for byteswap.h's bswap_32 20 | uint32_t bswap_32(uint32_t val) { 21 | val = ((val << 8) & 0xFF00FF00 ) | ((val >> 8) & 0x00FF00FF ); 22 | return (val << 16) | (val >> 16); 23 | } 24 | #endif 25 | 26 | // Global variables 27 | const char program_version[] = "1.02 (January 4, 2022)"; 28 | char *program_name; 29 | char *query_string; 30 | FILE *file; 31 | int option_debug; 32 | int option_decimal; 33 | int option_force; 34 | int option_new_file; 35 | int option_verbose; 36 | 37 | // Complete param.sfo file structure, 4 parts: 38 | // 1. header 39 | // 2. all entries 40 | // 3. key_table.content (with trailing 4-byte alignment) 41 | // 4. data_table.content 42 | 43 | struct header { 44 | uint32_t magic; 45 | uint32_t version; 46 | uint32_t key_table_offset; 47 | uint32_t data_table_offset; 48 | uint32_t entries_count; 49 | } header; 50 | 51 | struct index_table_entry { 52 | uint16_t key_offset; 53 | uint16_t param_fmt; 54 | uint32_t param_len; 55 | uint32_t param_max_len; 56 | uint32_t data_offset; 57 | } *entries; 58 | 59 | struct table { 60 | unsigned int size; 61 | char *content; 62 | } key_table, data_table; 63 | 64 | enum cmd {cmd_add, cmd_delete, cmd_edit, cmd_set}; 65 | 66 | struct command { 67 | enum cmd cmd; 68 | struct { 69 | char *type; 70 | char *key; 71 | char *value; 72 | } param; 73 | } *commands; 74 | int commands_count; 75 | 76 | void load_header(FILE *file) { 77 | if (fread(&header, sizeof(struct header), 1, file) != 1) { 78 | fprintf(stderr, "Could not read header.\n"); 79 | exit(1); 80 | } 81 | } 82 | 83 | void load_entries(FILE *file) { 84 | unsigned int size = sizeof(struct index_table_entry) * header.entries_count; 85 | entries = malloc(size); 86 | if (entries == NULL) { 87 | fprintf(stderr, "Could not allocate %u bytes of memory for index table.\n", 88 | size); 89 | exit(1); 90 | } 91 | if (size && fread(entries, size, 1, file) != 1) { 92 | fprintf(stderr, "Could not read index table entries.\n"); 93 | exit(1); 94 | } 95 | } 96 | 97 | void load_key_table(FILE *file) { 98 | key_table.size = header.data_table_offset - header.key_table_offset; 99 | key_table.content = malloc(key_table.size); 100 | if (key_table.content == NULL) { 101 | fprintf(stderr, "Could not allocate %u bytes of memory for key table.\n", 102 | key_table.size); 103 | exit(1); 104 | } 105 | if (key_table.size && fread(key_table.content, key_table.size, 1, file) != 1) { 106 | fprintf(stderr, "Could not read key table.\n"); 107 | exit(1); 108 | } 109 | } 110 | 111 | void load_data_table(FILE *file) { 112 | if (header.entries_count) { 113 | data_table.size = 114 | (entries[header.entries_count - 1].data_offset + 115 | entries[header.entries_count - 1].param_max_len); 116 | } else { 117 | data_table.size = 0; // For newly created, empty param.sfo files 118 | } 119 | data_table.content = malloc(data_table.size); 120 | if (data_table.content == NULL) { 121 | fprintf(stderr, "Could not allocate %u bytes of memory for data table.\n", 122 | data_table.size); 123 | exit(1); 124 | } 125 | if (data_table.size && fread(data_table.content, data_table.size, 1, file) != 1) { 126 | fprintf(stderr, "Could not read data table.\n"); 127 | exit(1); 128 | } 129 | } 130 | 131 | // Debug function that prints a byte array's content in hex editor style 132 | void hexprint(char *array, int array_len) { 133 | int offset = 0; 134 | fprintf(stderr, " 0 1 2 3 4 5 6 7 8 9 a b c d e f\n"); 135 | while (offset < array_len) { 136 | // Print starting offset 137 | fprintf(stderr, "%04x ", offset); 138 | // Print bytes 139 | for (int i = 0; i < 16 && offset + i < array_len; i++) { 140 | fprintf(stderr, "%02x ", array[offset + i]); 141 | } 142 | int remaining_bytes = array_len - offset; 143 | if (remaining_bytes < 16) { 144 | for (int i = 0; i < 16 - remaining_bytes; i++) { 145 | fprintf(stderr, " "); 146 | } 147 | } 148 | // Print characters 149 | for (int i = 0; i < 16 && offset + i < array_len; i++) { 150 | if (isprint(array[offset + i])) { 151 | fprintf(stderr, "%c", array[offset + i]); 152 | } else { 153 | fprintf(stderr, "."); 154 | } 155 | } 156 | fprintf(stderr, "\n"); 157 | offset += 16; 158 | } 159 | if (array_len > 64) fprintf(stderr, " 0 1 2 3 4 5 6 7 8 9 a b c d e f\n"); 160 | } 161 | 162 | // Debug function 163 | void print_header(void) { 164 | fprintf(stderr, "Header:\n"); 165 | fprintf(stderr, "Size: %d\n", sizeof(header)); 166 | fprintf(stderr, ".magic: %u\n", header.magic); 167 | fprintf(stderr, ".version: %u\n", header.version); 168 | fprintf(stderr, ".key_table_offset: %u\n", header.key_table_offset); 169 | fprintf(stderr, ".data_table_offset: %u\n", header.data_table_offset); 170 | fprintf(stderr, ".entries_count: %u\n", header.entries_count); 171 | fprintf(stderr, "\n"); 172 | } 173 | 174 | // Debug function 175 | void print_entries(void) { 176 | fprintf(stderr, "Index table:\n"); 177 | fprintf(stderr, "Size: %d\n", sizeof(struct index_table_entry) * header.entries_count); 178 | for (int i = 0; i < header.entries_count; i++) { 179 | fprintf(stderr, "Entry %d:\n", i); 180 | fprintf(stderr, " .key_offset: %u -> \"%s\"\n", entries[i].key_offset, 181 | &key_table.content[entries[i].key_offset]); 182 | fprintf(stderr, " .param_fmt: %u\n", entries[i].param_fmt); 183 | fprintf(stderr, " .param_len: %u\n", entries[i].param_len); 184 | fprintf(stderr, " .param_max_len: %u\n", entries[i].param_max_len); 185 | fprintf(stderr, " .data_offset: %u (0x%x)-> ", entries[i].data_offset, entries[i].data_offset); 186 | switch (entries[i].param_fmt) { 187 | case 516: 188 | case 1024: 189 | fprintf(stderr, "\"%s\"\n", &data_table.content[entries[i].data_offset]); 190 | break; 191 | case 1028: 192 | ; 193 | uint32_t *integer = (uint32_t *) &data_table.content[entries[i].data_offset]; 194 | fprintf(stderr, "0x%08x\n", *integer); 195 | break; 196 | } 197 | } 198 | fprintf(stderr, "\n"); 199 | } 200 | 201 | // Debug function 202 | void print_key_table(void) { 203 | fprintf(stderr, "Key table:\n"); 204 | fprintf(stderr, "Size: %d\n", key_table.size); 205 | if (key_table.size) { 206 | fprintf(stderr, "Content:\n"); 207 | for (int i = 0; i < key_table.size; i++) { 208 | if (isprint(key_table.content[i])) { 209 | fprintf(stderr, "%c", key_table.content[i]); 210 | } else { 211 | fprintf(stderr, "'\\%d'", key_table.content[i]); 212 | } 213 | } 214 | fprintf(stderr, "\n"); 215 | } 216 | fprintf(stderr, "\n"); 217 | } 218 | 219 | // Debug function 220 | void print_data_table(void) { 221 | fprintf(stderr, "Data table:\n"); 222 | fprintf(stderr, "Size: %d (0x%x)\n", data_table.size, data_table.size); 223 | if (data_table.size) { 224 | fprintf(stderr, "Content:\n"); 225 | hexprint(data_table.content, data_table.size); 226 | } 227 | fprintf(stderr, "\n"); 228 | } 229 | 230 | // Saves all 4 param.sfo parts to a param.sfo file 231 | void save_to_file(char *file_name) { 232 | FILE *file = fopen(file_name, "wb"); 233 | if (file == NULL) { 234 | fprintf(stderr, "Could not open file \"%s\" in write mode.\n", file_name); 235 | exit(1); 236 | } 237 | 238 | // Adjust header's table offsets before saving 239 | header.key_table_offset = sizeof(struct header) + 240 | sizeof(struct index_table_entry) * header.entries_count; 241 | header.data_table_offset = header.key_table_offset + key_table.size; 242 | 243 | if (fwrite(&header, sizeof(struct header), 1, file) != 1) { 244 | fprintf(stderr, "Could not write header to file \"%s\".\n", file_name); 245 | exit(1); 246 | } 247 | if (header.entries_count && fwrite(entries, 248 | sizeof(struct index_table_entry) * header.entries_count, 1, file) != 1) { 249 | fprintf(stderr, "Could not write index table to file \"%s\".\n", file_name); 250 | exit(1); 251 | } 252 | if (key_table.size && fwrite(key_table.content, key_table.size, 1, file) != 1) { 253 | fprintf(stderr, "Could not write key table to file \"%s\".\n", file_name); 254 | exit(1); 255 | } 256 | if (data_table.size && fwrite(data_table.content, data_table.size, 1, file) != 1) { 257 | fprintf(stderr, "Could not write data table to file \"%s\".\n", file_name); 258 | exit(1); 259 | } 260 | 261 | fclose(file); 262 | } 263 | 264 | // Prints a single parameter 265 | int print_param(char *key) { 266 | for (int i = 0; i < header.entries_count; i ++) { 267 | if (!strcmp(key, &key_table.content[entries[i].key_offset])) { 268 | switch(entries[i].param_fmt) { 269 | case 516: 270 | case 1024: 271 | printf("%s\n", &data_table.content[entries[i].data_offset]); 272 | return 0; 273 | case 1028: 274 | ; 275 | uint32_t *integer = (uint32_t *) &data_table.content[entries[i].data_offset]; 276 | if (option_decimal) { 277 | printf("%u\n", *integer); 278 | } else { 279 | printf("0x%08x\n", *integer); 280 | } 281 | return 0; 282 | } 283 | } 284 | } 285 | return 1; // Parameter not found 286 | } 287 | 288 | // Prints all parameters 289 | void print_params(void) { 290 | uint32_t *integer; 291 | if (option_verbose) { 292 | char version[6] = {0}; 293 | snprintf(version, 6, "%04x", header.version); 294 | version[4] = version[3]; 295 | version[3] = version[2]; 296 | version[2] = '.'; 297 | if (version[0] == '0') { 298 | printf("Param.sfo version: %s\n", &version[1]); 299 | } else { 300 | printf("Param.sfo version: %s\n", version); 301 | } 302 | printf("Number of parameters: %d\n", header.entries_count); 303 | } 304 | for (int i = 0; i < header.entries_count; i++) { 305 | switch (entries[i].param_fmt) { 306 | case 516: 307 | if (option_verbose) { 308 | printf("[%d] %s=\"%s\" (%d/%d bytes UTF-8 string)\n", i, 309 | &key_table.content[entries[i].key_offset], 310 | &data_table.content[entries[i].data_offset], 311 | entries[i].param_len, entries[i].param_max_len); 312 | } else { 313 | printf("%s=%s\n", &key_table.content[entries[i].key_offset], 314 | &data_table.content[entries[i].data_offset]); 315 | } 316 | break; 317 | case 1024: 318 | if (option_verbose) { 319 | printf("[%d] %s=\"%s\" (%d/%d bytes UTF-8 special mode string)\n", i, 320 | &key_table.content[entries[i].key_offset], 321 | &data_table.content[entries[i].data_offset], 322 | entries[i].param_len, entries[i].param_max_len); 323 | } else { 324 | printf("%s=%s\n", &key_table.content[entries[i].key_offset], 325 | &data_table.content[entries[i].data_offset]); 326 | } 327 | break; 328 | case 1028: 329 | integer = (uint32_t *) &data_table.content[entries[i].data_offset]; 330 | if (option_verbose) { 331 | if (option_decimal) { 332 | printf("[%d] %s=%u (%d/%d bytes unsigned integer)\n", i, 333 | &key_table.content[entries[i].key_offset], *integer, 334 | entries[i].param_len, entries[i].param_max_len); 335 | } else { 336 | printf("[%d] %s=0x%08x (%d/%d bytes unsigned integer)\n", i, 337 | &key_table.content[entries[i].key_offset], *integer, 338 | entries[i].param_len, entries[i].param_max_len); 339 | } 340 | } else { 341 | if (option_decimal) { 342 | printf("%s=%u\n", &key_table.content[entries[i].key_offset], *integer); 343 | } else { 344 | printf("%s=0x%08x\n", &key_table.content[entries[i].key_offset], *integer); 345 | } 346 | } 347 | break; 348 | } 349 | } 350 | } 351 | 352 | // Replacement for realloc() that exits on error 353 | static inline void *_realloc(void *ptr, unsigned int size) { 354 | if (size == 0) { // Avoid double free (which is implementation-dependant) 355 | if (ptr) free(ptr); 356 | ptr = NULL; 357 | } else if ((ptr = realloc(ptr, size)) == NULL) { 358 | fprintf(stderr, "Failed to reallocate memory.\n"); 359 | exit(1); 360 | } 361 | return ptr; 362 | } 363 | 364 | // Resizes the data table, starting at specified offset 365 | void expand_data_table(int offset, int additional_size) { 366 | data_table.size += additional_size; 367 | data_table.content = _realloc(data_table.content, data_table.size); 368 | // Move higher indexed data to make room for new data 369 | for (int i = data_table.size - 1; i >= offset + additional_size; i--) { 370 | data_table.content[i] = data_table.content[i - additional_size]; 371 | } 372 | // Set new memory to zero 373 | memset(&data_table.content[offset], 0, additional_size); 374 | } 375 | 376 | // Returns a parameter's index table position 377 | int get_index(char *key) { 378 | for (int i = 0; i < header.entries_count; i++) { 379 | if (strcmp(key, &key_table.content[entries[i].key_offset]) == 0) { 380 | return i; 381 | } 382 | } 383 | return -1; 384 | } 385 | 386 | // Edits a parameter in memory 387 | void edit_param(char *key, char *value, int no_fail) { 388 | int index = get_index(key); 389 | if (index < 0) { // Parameter not found 390 | if (no_fail) { 391 | return; 392 | } else { 393 | fprintf(stderr, "Could not edit \"%s\": parameter not found.\n", key); 394 | exit(1); 395 | } 396 | } 397 | 398 | switch (entries[index].param_fmt) { 399 | case 516: // String 400 | case 1024: // Special mode string 401 | entries[index].param_len = strlen(value) + 1; 402 | // Enlarge data table if new string is longer than allowed 403 | int diff = entries[index].param_len - entries[index].param_max_len; 404 | if (diff > 0) { 405 | int offset = entries[index].data_offset + entries[index].param_max_len; 406 | entries[index].param_max_len = entries[index].param_len; 407 | 408 | // 4-byte alignment 409 | while (entries[index].param_max_len % 4) { 410 | entries[index].param_max_len++; 411 | diff++; 412 | } 413 | 414 | expand_data_table(offset, diff); 415 | 416 | // Adjust follow-up index table entries' data offsets 417 | for (int i = index + 1; i < header.entries_count; i++) { 418 | entries[i].data_offset += diff; 419 | } 420 | } 421 | // Overwrite old data with zeros 422 | memset(&data_table.content[entries[index].data_offset], 0, 423 | entries[index].param_max_len); 424 | // Save new string to data table 425 | snprintf(&data_table.content[entries[index].data_offset], 426 | entries[index].param_max_len, "%s", value); 427 | break; 428 | case 1028: // Integer 429 | ; 430 | uint32_t integer = strtoul(value, NULL, 0); 431 | memcpy(&data_table.content[entries[index].data_offset], &integer, 4); 432 | break; 433 | } 434 | } 435 | 436 | // Pad a table to obey the 4-byte alignment rule 437 | // Currently only used for the key table 438 | void pad_table(struct table *table) { 439 | // Remove all trailing zeros 440 | while (table->size > 0 && table->content[table->size - 1] == '\0') { 441 | table->size--; 442 | } 443 | if (table->size) table->size++; // Re-add 1 zero if there are strings left 444 | 445 | table->content = _realloc(table->content, table->size); 446 | // Pad table with zeros 447 | while (table->size % 4) { 448 | table->size++; 449 | table->content = _realloc(table->content, table->size); 450 | table->content[table->size - 1] = '\0'; 451 | } 452 | } 453 | 454 | // Deletes a parameter from memory 455 | void delete_param(char *key, int no_fail) { 456 | int index = get_index(key); 457 | if (index < 0) { // Parameter not found 458 | if (no_fail) { 459 | return; 460 | } else { 461 | fprintf(stderr, "Could not delete \"%s\": parameter not found.\n", key); 462 | exit(1); 463 | } 464 | } 465 | 466 | // Delete parameter from key table 467 | for (int i = entries[index].key_offset; i < key_table.size - strlen(key) - 1; i++) { 468 | key_table.content[i] = key_table.content[i + strlen(key) + 1]; 469 | } 470 | 471 | // Resize key table 472 | key_table.size -= strlen(key) + 1; 473 | key_table.content = _realloc(key_table.content, key_table.size); 474 | pad_table(&key_table); 475 | 476 | // Delete parameter from data table 477 | for (int i = entries[index].data_offset; i < data_table.size - entries[index].param_max_len; i++) { 478 | data_table.content[i] = data_table.content[i + entries[index].param_max_len]; 479 | } 480 | 481 | // Resize data table 482 | data_table.size -= entries[index].param_max_len; 483 | if (data_table.size) { 484 | data_table.content = _realloc(data_table.content, data_table.size); 485 | } else { 486 | free(data_table.content); 487 | data_table.content = NULL; 488 | } 489 | 490 | // Delete parameter from index table 491 | int param_max_len = entries[index].param_max_len; 492 | for (int i = index; i < header.entries_count - 1; i++) { 493 | entries[i] = entries[i + 1]; 494 | entries[i].key_offset -= strlen(key) + 1; 495 | entries[i].data_offset -= param_max_len; 496 | } 497 | 498 | // Resize index table 499 | header.entries_count--; 500 | if (header.entries_count) { 501 | entries = _realloc(entries, 502 | sizeof(struct index_table_entry) * header.entries_count); 503 | } else { 504 | free(entries); 505 | entries = NULL; 506 | } 507 | } 508 | 509 | // Checks if key is reserved and returns its default length 510 | int get_reserved_string_len(char *key) { 511 | int len = 0; 512 | if (!strcmp(key, "CATEGORY") || !strcmp(key, "FORMAT")) { 513 | len = 4; 514 | } else if (!strcmp(key, "APP_VER") || !strcmp(key, "CONTENT_VER") || !strcmp(key, "VERSION")) { 515 | len = 8; 516 | } else if (!strcmp(key, "INSTALL_DIR_SAVEDATA") || !strcmp(key, "TITLE_ID")) { 517 | len = 12; 518 | } else if (!strcmp(key, "SERVICE_ID_ADDCONT_ADD_1") || 519 | !strcmp(key, "SERVICE_ID_ADDCONT_ADD_2") || 520 | !strcmp(key, "SERVICE_ID_ADDCONT_ADD_3") || 521 | !strcmp(key, "SERVICE_ID_ADDCONT_ADD_4") || 522 | !strcmp(key, "SERVICE_ID_ADDCONT_ADD_5") || 523 | !strcmp(key, "SERVICE_ID_ADDCONT_ADD_6") || 524 | !strcmp(key, "SERVICE_ID_ADDCONT_ADD_7")) { 525 | len = 20; 526 | } else if (!strcmp(key, "CONTENT_ID")) { 527 | len = 48; 528 | } else if (!strcmp(key, "PROVIDER") || !strcmp(key, "TITLE") || 529 | !strcmp(key, "PROVIDER_00") || !strcmp(key, "TITLE_00") || 530 | !strcmp(key, "PROVIDER_01") || !strcmp(key, "TITLE_01") || 531 | !strcmp(key, "PROVIDER_02") || !strcmp(key, "TITLE_02") || 532 | !strcmp(key, "PROVIDER_03") || !strcmp(key, "TITLE_03") || 533 | !strcmp(key, "PROVIDER_04") || !strcmp(key, "TITLE_04") || 534 | !strcmp(key, "PROVIDER_05") || !strcmp(key, "TITLE_05") || 535 | !strcmp(key, "PROVIDER_06") || !strcmp(key, "TITLE_06") || 536 | !strcmp(key, "PROVIDER_07") || !strcmp(key, "TITLE_07") || 537 | !strcmp(key, "PROVIDER_08") || !strcmp(key, "TITLE_08") || 538 | !strcmp(key, "PROVIDER_09") || !strcmp(key, "TITLE_09") || 539 | !strcmp(key, "PROVIDER_10") || !strcmp(key, "TITLE_10") || 540 | !strcmp(key, "PROVIDER_11") || !strcmp(key, "TITLE_11") || 541 | !strcmp(key, "PROVIDER_12") || !strcmp(key, "TITLE_12") || 542 | !strcmp(key, "PROVIDER_13") || !strcmp(key, "TITLE_13") || 543 | !strcmp(key, "PROVIDER_14") || !strcmp(key, "TITLE_14") || 544 | !strcmp(key, "PROVIDER_15") || !strcmp(key, "TITLE_15") || 545 | !strcmp(key, "PROVIDER_16") || !strcmp(key, "TITLE_16") || 546 | !strcmp(key, "PROVIDER_17") || !strcmp(key, "TITLE_17") || 547 | !strcmp(key, "PROVIDER_18") || !strcmp(key, "TITLE_18") || 548 | !strcmp(key, "PROVIDER_19") || !strcmp(key, "TITLE_19") || 549 | !strcmp(key, "PROVIDER_20") || !strcmp(key, "TITLE_20") || 550 | !strcmp(key, "TITLE_21") || !strcmp(key, "TITLE_22") || 551 | !strcmp(key, "TITLE_23") || !strcmp(key, "TITLE_24") || 552 | !strcmp(key, "TITLE_25") || !strcmp(key, "TITLE_26") || 553 | !strcmp(key, "TITLE_27") || !strcmp(key, "TITLE_28") || 554 | !strcmp(key, "TITLE_29")) { 555 | len = 128; 556 | } else if (!strcmp(key, "PUBTOOLINFO") || !strcmp(key, "PS3_TITLE_ID_LIST_FOR_BOOT") || 557 | !strcmp(key, "SAVE_DATA_TRANSFER_TITLE_ID_LIST_1") || 558 | !strcmp(key, "SAVE_DATA_TRANSFER_TITLE_ID_LIST_2") || 559 | !strcmp(key, "SAVE_DATA_TRANSFER_TITLE_ID_LIST_3") || 560 | !strcmp(key, "SAVE_DATA_TRANSFER_TITLE_ID_LIST_4") || 561 | !strcmp(key, "SAVE_DATA_TRANSFER_TITLE_ID_LIST_5") || 562 | !strcmp(key, "SAVE_DATA_TRANSFER_TITLE_ID_LIST_6") || 563 | !strcmp(key, "SAVE_DATA_TRANSFER_TITLE_ID_LIST_7")) { 564 | len = 512; 565 | } 566 | return len; 567 | } 568 | 569 | // Adds a new parameter to memory 570 | void add_param(char *type, char *key, char *value, int no_fail) { 571 | struct index_table_entry new_entry = {0}; 572 | int new_index = 0; 573 | 574 | // Get new entry's .param_len and .param_max_len 575 | if (!strcmp(type, "str")) { 576 | new_entry.param_fmt = 516; 577 | new_entry.param_max_len = get_reserved_string_len(key); 578 | new_entry.param_len = strlen(value) + 1; 579 | if (new_entry.param_max_len < new_entry.param_len) { 580 | new_entry.param_max_len = new_entry.param_len; 581 | // 4-byte alignment 582 | while (new_entry.param_max_len % 4) { 583 | new_entry.param_max_len++; 584 | } 585 | } 586 | } else { // "int" 587 | new_entry.param_fmt = 1028; 588 | new_entry.param_len = 4; 589 | new_entry.param_max_len = 4; 590 | } 591 | 592 | // Get new entry's index and offsets 593 | for (int i = 0; i < header.entries_count; i++) { 594 | int result = strcmp(key, &key_table.content[entries[i].key_offset]); 595 | if (result == 0) { // Parameter already exists 596 | if (no_fail) { 597 | return; 598 | } else { 599 | fprintf(stderr, "Could not add \"%s\": parameter already exists.\n", key); 600 | exit(1); 601 | } 602 | } else if (result < 0) { 603 | new_index = i; 604 | new_entry.key_offset = entries[i].key_offset; 605 | new_entry.data_offset = entries[i].data_offset; 606 | break; 607 | } else if (i == header.entries_count - 1) { 608 | new_index = i + 1; 609 | new_entry.key_offset = entries[i].key_offset + 610 | strlen(&key_table.content[entries[i].key_offset]) + 1; 611 | new_entry.data_offset = entries[i].data_offset + 612 | entries[i].param_max_len; 613 | break; 614 | } 615 | } 616 | 617 | // Make room for the new index table entry by moving the old ones 618 | header.entries_count++; 619 | entries = _realloc(entries, 620 | sizeof(struct index_table_entry) * header.entries_count); 621 | for (int i = header.entries_count - 1; i > new_index; i--) { 622 | entries[i] = entries[i - 1]; 623 | entries[i].key_offset += strlen(key) + 1; 624 | entries[i].data_offset += new_entry.param_max_len; 625 | } 626 | 627 | // Insert new index table entry 628 | memcpy(&entries[new_index], &new_entry, sizeof(struct index_table_entry)); 629 | 630 | // Resize key table 631 | key_table.size += strlen(key) + 1; 632 | key_table.content = _realloc(key_table.content, key_table.size); 633 | // Move higher indexed keys to make room for new key 634 | for (int i = key_table.size - 1; i > new_entry.key_offset + strlen(key); i--) { 635 | key_table.content[i] = key_table.content[i - strlen(key) - 1]; 636 | } 637 | // Insert new key 638 | memcpy(&key_table.content[new_entry.key_offset], key, strlen(key) + 1); 639 | pad_table(&key_table); 640 | 641 | // Resize data table 642 | expand_data_table(new_entry.data_offset, new_entry.param_max_len); 643 | 644 | // Insert new data 645 | if (!strcmp(type, "str")) { 646 | memset(&data_table.content[entries[new_index].data_offset], 0, 647 | new_entry.param_len); // Overwrite whole space with zeros first 648 | memcpy(&data_table.content[entries[new_index].data_offset], 649 | value, strlen(value) + 1); // Then copy new value 650 | } else if (!strcmp(type, "int")) { 651 | uint32_t new_value = strtoul(value, NULL, 0); 652 | memcpy(&data_table.content[entries[new_index].data_offset], 653 | &new_value, 4); 654 | } 655 | } 656 | 657 | // Overwrites an existing parameter or creates a new one 658 | void set_param(char *type, char *key, char *value) { 659 | delete_param(key, 1); 660 | add_param(type, key, value, 1); 661 | } 662 | 663 | // Returns a filename without its path 664 | char *basename(char *filename) { 665 | #if defined(_WIN32) || defined(_WIN64) 666 | char *base = strrchr(filename, '\\'); 667 | #else 668 | char *base = strrchr(filename, '/'); 669 | #endif 670 | if (base != NULL && strlen(base) > 1) { 671 | base++; 672 | return base; 673 | } else { 674 | return filename; 675 | } 676 | } 677 | 678 | // Prints usage information and exits with exit_code 679 | void print_usage(int exit_code) { 680 | FILE *output; 681 | if (exit_code) { 682 | output = stderr; 683 | } else { 684 | output = stdout; 685 | } 686 | fprintf(output, 687 | "Usage: %s [OPTIONS] FILE\n\n" 688 | "Reads a file to print or modify its SFO parameters.\n" 689 | "Supported file types:\n" 690 | " - PS4 param.sfo (print and modify)\n" 691 | " - PS4 disc param.sfo (print only)\n" 692 | " - PS4 PKG (print only)\n\n" 693 | "The modification options (-a/--add, -d/--delete, -e/--edit, -s/--set) can be\n" 694 | "used multiple times. Modifications are done in memory first, in the order in\n" 695 | "which they appear in the program's command line arguments.\n" 696 | "If any modification fails, all changes are discarded and no data is written:\n\n" 697 | " Modification Fail condition\n" 698 | " --------------------------------------\n" 699 | " Add Parameter already exists\n" 700 | " Delete Parameter not found\n" 701 | " Edit Parameter not found\n" 702 | " Set None\n\n" 703 | "Options:\n" 704 | " -a, --add TYPE PARAMETER VALUE Add a new parameter, not overwriting existing\n" 705 | " data. TYPE must be either \"int\" or \"str\".\n" 706 | " -d, --delete PARAMETER Delete specified parameter.\n" 707 | " --debug Print debug information.\n" 708 | " --decimal Display integer values as decimal numerals.\n" 709 | " -e, --edit PARAMETER VALUE Change specified parameter's value.\n" 710 | " -f, --force Do not abort when modifications fail. Make\n" 711 | " option --new-file overwrite existing files.\n" 712 | " -h, --help Print usage information and quit.\n" 713 | " --new-file If FILE (see above) does not exist, create a\n" 714 | " new param.sfo file of the same name.\n" 715 | " -o, --output-file OUTPUT_FILE Save the final data to a new file of type\n" 716 | " \"param.sfo\", overwriting existing files.\n" 717 | " -q, --query PARAMETER Print a parameter's value and quit.\n" 718 | " If the parameter exists, the exit code is 0.\n" 719 | " -s, --set TYPE PARAMETER VALUE Set a parameter, whether it exists or not,\n" 720 | " overwriting existing data.\n" 721 | " -v, --verbose Increase verbosity.\n" 722 | " --version Print version information and quit.\n" 723 | ,basename(program_name)); 724 | exit(exit_code); 725 | } 726 | 727 | void print_version(void) { 728 | printf("SFO v%s\n", program_version); 729 | printf("https://github.com/hippie68/sfo\n"); 730 | } 731 | 732 | // Finds the param.sfo's offset inside a PS4 PKG file 733 | long int get_ps4_pkg_offset() { 734 | uint32_t pkg_table_offset; 735 | uint32_t pkg_file_count; 736 | fseek(file, 0x00C, SEEK_SET); 737 | fread(&pkg_file_count, 4, 1, file); 738 | fseek(file, 0x018, SEEK_SET); 739 | fread(&pkg_table_offset, 4, 1, file); 740 | pkg_file_count = bswap_32(pkg_file_count); 741 | pkg_table_offset = bswap_32(pkg_table_offset); 742 | struct pkg_table_entry { 743 | uint32_t id; 744 | uint32_t filename_offset; 745 | uint32_t flags1; 746 | uint32_t flags2; 747 | uint32_t offset; 748 | uint32_t size; 749 | uint64_t padding; 750 | } pkg_table_entry[pkg_file_count]; 751 | fseek(file, pkg_table_offset, SEEK_SET); 752 | fread(pkg_table_entry, sizeof (struct pkg_table_entry), pkg_file_count, file); 753 | for (int i = 0; i < pkg_file_count; i++) { 754 | if (pkg_table_entry[i].id == 1048576) { // param.sfo ID 755 | return bswap_32(pkg_table_entry[i].offset); 756 | } 757 | } 758 | fprintf(stderr, "Could not find a param.sfo file inside the PS4 PKG.\n"); 759 | fclose(file); 760 | exit(1); 761 | } 762 | 763 | // Removes the leftmost argument from argv; decrements argc 764 | int shift(int *pargc, char **pargv[]) { 765 | // Exit and print usage information if there is nothing left to shift 766 | if (*pargc <= 0) { 767 | fprintf(stderr, "A required argument is missing.\n"); 768 | print_usage(1); 769 | exit(1); 770 | } 771 | 772 | (*pargv)++; 773 | (*pargc)--; 774 | return 0; 775 | } 776 | 777 | // Converts a string to uppercase 778 | void toupper_string(char *string) { 779 | while (*string) { 780 | string[0] = toupper(string[0]); 781 | string++; 782 | } 783 | } 784 | 785 | // Frees all previously malloc'ed pointers; used for memory leak tests 786 | void clean_exit(void) { 787 | if (commands) free(commands); 788 | if (entries) free(entries); 789 | if (key_table.content) free(key_table.content); 790 | if (data_table.content) free(data_table.content); 791 | if (file) fclose(file); 792 | } 793 | 794 | // Creates an empty param.sfo file 795 | void create_param_sfo(char *file_name) { 796 | header.magic = 1179865088; 797 | header.version = 257; 798 | header.key_table_offset = 20; 799 | header.data_table_offset = 20; 800 | header.entries_count = 0; 801 | save_to_file(file_name); 802 | } 803 | 804 | int main(int argc, char *argv[]) { 805 | atexit(clean_exit); 806 | 807 | char *input_file_name = NULL; 808 | char *output_file_name = NULL; 809 | 810 | // Parse command line arguments 811 | program_name = argv[0]; 812 | shift(&argc, &argv); 813 | while (argc) { 814 | // Parse file names 815 | if (argv[0][0] != '-') { 816 | if (input_file_name == NULL) { 817 | input_file_name = argv[0]; 818 | } else { 819 | fprintf(stderr, "Only 1 input file is allowed. Conflicting file names:\n" 820 | " \"%s\"\n \"%s\"\n", input_file_name, argv[0]); 821 | print_usage(1); 822 | } 823 | // Parse options 824 | } else if (!strcmp(argv[0], "-a") || !strcmp(argv[0], "--add")) { 825 | commands = _realloc(commands, sizeof(struct command) * (commands_count + 1)); 826 | commands[commands_count].cmd = cmd_add; 827 | shift(&argc, &argv); 828 | // TYPE 829 | if (strcmp(argv[0], "str") && strcmp(argv[0], "int")) { 830 | fprintf(stderr, "Option --add: TYPE must be \"int\" or \"str\".\n"); 831 | print_usage(1); 832 | } 833 | commands[commands_count].param.type = argv[0]; 834 | shift(&argc, &argv); 835 | // NAME 836 | toupper_string(argv[0]); 837 | commands[commands_count].param.key = argv[0]; 838 | shift(&argc, &argv); 839 | // VALUE 840 | commands[commands_count].param.value = argv[0]; 841 | commands_count++; 842 | } else if (!strcmp(argv[0], "--new-file")) { 843 | option_new_file = 1; 844 | } else if (!strcmp(argv[0], "-d") || !strcmp(argv[0], "--delete")) { 845 | commands = _realloc(commands, sizeof(struct command) * (commands_count + 1)); 846 | commands[commands_count].cmd = cmd_delete; 847 | shift(&argc, &argv); 848 | // NAME 849 | toupper_string(argv[0]); 850 | commands[commands_count].param.key = argv[0]; 851 | commands_count++; 852 | } else if (!strcmp(argv[0], "--debug")) { 853 | option_debug = 1; 854 | } else if (!strcmp(argv[0], "--decimal")) { 855 | option_decimal = 1; 856 | } else if (!strcmp(argv[0], "-e") || !strcmp(argv[0], "--edit")) { 857 | commands = _realloc(commands, sizeof(struct command) * (commands_count + 1)); 858 | commands[commands_count].cmd = cmd_edit; 859 | shift(&argc, &argv); 860 | // NAME 861 | toupper_string(argv[0]); 862 | commands[commands_count].param.key = argv[0]; 863 | shift(&argc, &argv); 864 | // VALUE 865 | commands[commands_count].param.value = argv[0]; 866 | commands_count++; 867 | } else if (!strcmp(argv[0], "-f") || !strcmp(argv[0], "--force")) { 868 | option_force = 1; 869 | } else if (!strcmp(argv[0], "-h") || !strcmp(argv[0], "--help")) { 870 | print_usage(0); 871 | } else if (!strcmp(argv[0], "-o") || !strcmp(argv[0], "--output-file")) { 872 | shift(&argc, &argv); 873 | output_file_name = argv[0]; 874 | } else if (!strcmp(argv[0], "-q") || !strcmp(argv[0], "--query")) { 875 | shift(&argc, &argv); 876 | toupper_string(argv[0]); 877 | if (!query_string) { 878 | query_string = argv[0]; 879 | } else { 880 | fprintf(stderr, "Only 1 search is allowed. Conflicting search strings:\n" 881 | " \"%s\"\n, \"%s\"\n.\n", query_string, argv[0]); 882 | exit(1); 883 | } 884 | } else if (!strcmp(argv[0], "-s") || !strcmp(argv[0], "--set")) { 885 | commands = _realloc(commands, sizeof(struct command) * 886 | (commands_count + 1)); 887 | commands[commands_count].cmd = cmd_set; 888 | shift(&argc, &argv); 889 | // TYPE 890 | if (strcmp(argv[0], "str") && strcmp(argv[0], "int")) { 891 | fprintf(stderr, "Option --set: TYPE must be \"int\" or \"str\".\n"); 892 | print_usage(1); 893 | } 894 | commands[commands_count].param.type = argv[0]; 895 | shift(&argc, &argv); 896 | // NAME 897 | toupper_string(argv[0]); 898 | commands[commands_count].param.key = argv[0]; 899 | shift(&argc, &argv); 900 | // VALUE 901 | commands[commands_count].param.value = argv[0]; 902 | commands_count++; 903 | } else if (!strcmp(argv[0], "-v") || !strcmp(argv[0], "--verbose")) { 904 | option_verbose = 1; 905 | } else if (!strcmp(argv[0], "--version")) { 906 | print_version(); 907 | exit(0); 908 | } else { 909 | fprintf(stderr, "Unknown option: %s\n", argv[0]); 910 | print_usage(1); 911 | } 912 | shift(&argc, &argv); 913 | } 914 | 915 | // DEBUG: Print parsing results 916 | if (option_debug) { 917 | fprintf(stderr, "Command line parsing results:\n\n"); 918 | if (input_file_name == NULL) { // printf + null pointer = undefined behavior 919 | fprintf(stderr, "input_file_name: NULL\n"); 920 | } else { 921 | fprintf(stderr, "input_file_name: \"%s\"\n", input_file_name); 922 | } 923 | if (output_file_name == NULL) { 924 | fprintf(stderr, "output_file_name: NULL\n"); 925 | } else { 926 | fprintf(stderr, "output_file_name: \"%s\"\n", output_file_name); 927 | } 928 | fprintf(stderr, "option_debug: %d\n", option_debug); 929 | fprintf(stderr, "option_decimal: %d\n", option_decimal); 930 | fprintf(stderr, "option_force: %d\n", option_force); 931 | fprintf(stderr, "option_new_file: %d\n", option_new_file); 932 | fprintf(stderr, "option_verbose: %d\n", option_verbose); 933 | if (query_string == NULL) { 934 | fprintf(stderr, "query_string: NULL\n"); 935 | } else { 936 | fprintf(stderr, "query_string: \"%s\"\n", query_string); 937 | } 938 | fprintf(stderr, "commands_count: %d\n", commands_count); 939 | for (int i = 0; i < commands_count; i++) { 940 | fprintf(stderr, "Command %d:\n", i); 941 | fprintf(stderr, " .cmd: %d (", commands[i].cmd); 942 | char *cmd; 943 | switch (commands[i].cmd) { 944 | case 0: cmd = "add"; break; 945 | case 1: cmd = "delete"; break; 946 | case 2: cmd = "edit"; break; 947 | case 3: cmd = "set"; break; 948 | } 949 | fprintf(stderr, "%s)\n", cmd); 950 | fprintf(stderr, " .param.type: %d\n", commands[i].param.type); 951 | if (commands[i].param.key) { 952 | fprintf(stderr, " .param.key: \"%s\"\n", commands[i].param.key); 953 | } else { 954 | fprintf(stderr, " .param.key: NULL\n", commands[i].param.key); 955 | } 956 | if (commands[i].param.value) { 957 | fprintf(stderr, " .param.value: \"%s\"\n", commands[i].param.value); 958 | } else { 959 | fprintf(stderr, " .param.value: NULL\n"); 960 | } 961 | } 962 | fprintf(stderr, "\n"); 963 | } 964 | 965 | // Open file 966 | if (!input_file_name) { 967 | fprintf(stderr, "Please specify a file name.\n"); 968 | print_usage(1); 969 | } 970 | // Optionally create file before opening it 971 | if (option_new_file) { 972 | if (!option_force && !access(input_file_name, F_OK)) { 973 | fprintf(stderr, "File \"%s\" already exists.\n", input_file_name); 974 | exit(1); 975 | } else { 976 | create_param_sfo(input_file_name); 977 | } 978 | } 979 | file = fopen(input_file_name, "rb"); // Read only 980 | if (file == NULL) { 981 | fprintf(stderr, "Could not open file \"%s\".\n", input_file_name); 982 | exit(1); 983 | } 984 | 985 | // Get SFO header offset 986 | uint32_t magic; 987 | fread(&magic, 4, 1, file); 988 | if (magic == 1414415231) { // PS4 PKG file 989 | fseek(file, get_ps4_pkg_offset(), SEEK_SET); 990 | } else if (magic == 1128612691) { // Disc param.sfo 991 | fseek(file, 0x800, SEEK_SET); 992 | } else if (magic == 1179865088) { // Param.sfo file 993 | rewind(file); 994 | } else { 995 | fprintf(stderr, "Param.sfo magic number not found.\n"); 996 | exit(1); 997 | } 998 | 999 | // Load file contents 1000 | load_header(file); 1001 | load_entries(file); 1002 | load_key_table(file); 1003 | load_data_table(file); 1004 | 1005 | if (option_debug) { 1006 | fprintf(stderr, "Memory before running commands:\n\n"); 1007 | print_header(); 1008 | print_entries(); 1009 | print_key_table(); 1010 | print_data_table(); 1011 | } 1012 | 1013 | // If there are any queued commands, run them and save the file 1014 | if (commands_count) { 1015 | if (magic == 1414415231) { 1016 | fprintf(stderr, "Cannot edit PKG files.\n"); 1017 | exit(1); 1018 | } 1019 | if (magic == 1128612691) { 1020 | fprintf(stderr, "Cannot edit disc param.sfo files.\n"); 1021 | exit(1); 1022 | } 1023 | 1024 | for (int i = 0; i < commands_count; i++) { 1025 | switch (commands[i].cmd) { 1026 | case cmd_add: 1027 | add_param(commands[i].param.type, commands[i].param.key, 1028 | commands[i].param.value, option_force); 1029 | break; 1030 | case cmd_delete: 1031 | delete_param(commands[i].param.key, option_force); 1032 | break; 1033 | case cmd_edit: 1034 | edit_param(commands[i].param.key, commands[i].param.value, 1035 | option_force); 1036 | break; 1037 | case cmd_set: 1038 | set_param(commands[i].param.type, commands[i].param.key, 1039 | commands[i].param.value); 1040 | break; 1041 | } 1042 | } 1043 | 1044 | if (option_debug) { 1045 | fprintf(stderr, "Memory after running commands:\n\n" 1046 | "Header's table offsets will be updated when saving the file.\n\n"); 1047 | print_header(); 1048 | print_entries(); 1049 | print_key_table(); 1050 | print_data_table(); 1051 | } 1052 | 1053 | if (output_file_name) { 1054 | save_to_file(output_file_name); 1055 | } else { 1056 | save_to_file(input_file_name); 1057 | } 1058 | 1059 | if (query_string) { 1060 | return print_param(query_string); 1061 | } 1062 | } else { 1063 | if (output_file_name) { 1064 | save_to_file(output_file_name); 1065 | } 1066 | 1067 | if (query_string) { 1068 | return print_param(query_string); 1069 | } else { 1070 | print_params(); 1071 | } 1072 | } 1073 | 1074 | return 0; 1075 | } 1076 | --------------------------------------------------------------------------------