converts between regular CSV files and the binary CSV format as used in Harmonix Forge engine games (RB4, RBVR, etc)
0

Configure Feed

Select the types of activity you want to include in your feed.

forgecsv / forgecsv.c
12 kB 364 lines
1#include <stdio.h> 2#include <stdint.h> 3#include <string.h> 4#include <stdlib.h> 5 6#include "csv.h" 7 8#define DEFAULT_SEPERATOR ',' 9 10char *current_string_table = NULL; 11uint32_t current_string_table_sz = 0; 12uint32_t current_string_table_ptr = 0; 13char current_seperator = DEFAULT_SEPERATOR; 14static char quote = '"'; 15 16typedef struct _CsvResourceHeader { 17 uint32_t mRevision; 18 uint32_t mUnk; 19 uint8_t mSeperator; 20 uint32_t mStringTableLength; 21} __attribute__((packed)) CsvResourceHeader; 22 23const char *get_string_from_table(uint32_t offset) { 24 if (current_string_table == NULL) { 25 fprintf(stderr, "%s: no string table loaded.\n", __func__); 26 return NULL; 27 } 28 if (offset > current_string_table_sz) { 29 fprintf(stderr, "%s: offset (0x%x) larger than string table (0x%x).\n", __func__, offset, current_string_table_sz); 30 return NULL; 31 } 32 return current_string_table + offset; 33} 34 35int binrow_to_txtrow(FILE *bin, FILE *txt) { 36 char newln[] = { '\r', '\n' }; 37 int r = 0; 38 uint32_t num_cols = 0; 39 uint32_t *columns = NULL; 40 41 // if we don't have a string table, bail out 42 if (current_string_table == NULL) { 43 fprintf(stderr, "%s: no string table loaded.\n", __func__); 44 return -1; 45 } 46 47 // read the number of columns from the file 48 num_cols = 0; 49 r = fread(&num_cols, sizeof(int), 1, bin); 50 if (r != 1 || num_cols < 0) { // sanity check 51 fprintf(stderr, "%s: failed to read number of columns.\n", __func__); 52 return -1; 53 } 54 55 // allocate enough room for the columns in memory temporarily 56 columns = malloc(sizeof(uint32_t) * num_cols); 57 if (columns == NULL) { 58 fprintf(stderr, "%s: failed to allocate memory for columns.\n", __func__); 59 return -1; 60 } 61 r = fread(columns, sizeof(uint32_t), num_cols, bin); 62 if (r != num_cols) { 63 fprintf(stderr, "%s: failed to read columns.\n", __func__); 64 free(columns); 65 return -1; 66 } 67 68 // write the column text data out to the text file 69 for (int i = 0; i < num_cols; i++) { 70 int str_esc_len = 0; 71 const char *str = NULL; 72 char *str_esc = NULL; 73 int j = 0; 74 int k = 0; 75 76 str = get_string_from_table(columns[i]); 77 if (str == NULL) { 78 fprintf(stderr, "%s: failed to read column from string table.\n", __func__); 79 free(columns); 80 return -1; 81 } 82 83 // get the size of the string when escaped 84 for (j=0; str[j]; str[j]==quote ? str_esc_len += 2 : str_esc_len++, j++); 85 // and allocate a buffer for it 86 str_esc = malloc(str_esc_len + 1); 87 if (str_esc == NULL) { 88 fprintf(stderr, "%s: failed to allocate buffer for escaped string.\n", __func__); 89 free(columns); 90 return -1; 91 } 92 // and copy the string, escaping it 93 k = 0; 94 for (j = 0; str[j]; j++) { 95 if (str[j] == quote) 96 str_esc[k++] = quote; 97 str_esc[k++] = str[j]; 98 } 99 str_esc[k] = '\0'; 100 101 // TODO(Emma): check if the writes fail here 102 fwrite(&quote, 1, sizeof(char), txt); 103 fwrite(str_esc, 1, strlen(str_esc), txt); 104 fwrite(&quote, 1, sizeof(char), txt); 105 // write the seperator if it isn't 106 if (i < (num_cols - 1)) 107 fwrite(&current_seperator, 1, sizeof(char), txt); 108 109 free(str_esc); 110 } 111 112 // write a CRLF newline 113 fwrite(newln, sizeof(newln), 1, txt); 114 115 // successfully read and output - free the buffer we made before 116 free(columns); 117 return 0; 118} 119 120int bincsv_to_csv(const char *bincsv_file, const char *csv_file) { 121 FILE *bincsv = NULL; 122 FILE *csv = NULL; 123 CsvResourceHeader resHdr = {0}; 124 int r = 0; 125 int num_rows = 0; 126 127 // open the files 128 bincsv = fopen(bincsv_file, "rb"); 129 if (bincsv == NULL) { 130 fprintf(stderr, "%s: failed to read from '%s'.\n", __func__, bincsv_file); 131 return -1; 132 } 133 csv = fopen(csv_file, "wb"); // load as binary; even tho it's text data, i don't trust it 134 if (csv == NULL) { 135 fprintf(stderr, "%s: failed to open '%s' for writing.\n", __func__, csv_file); 136 fclose(bincsv); 137 return -1; 138 } 139 140 // read the file header 141 r = fread(&resHdr, sizeof(CsvResourceHeader), 1, bincsv); 142 if (r != 1) { 143 fprintf(stderr, "%s: failed to read CsvResource header.\n", __func__); 144 fclose(bincsv); 145 fclose(csv); 146 return -1; 147 } 148 // check the version 149 if (resHdr.mRevision != 0x1 || resHdr.mUnk != 0x2) { 150 fprintf(stderr, "%s: unknown CsvResource type (0x%x, 0x%x)\n", __func__, resHdr.mRevision, resHdr.mUnk); 151 fclose(bincsv); 152 fclose(csv); 153 return -1; 154 } 155 current_seperator = resHdr.mSeperator; 156 157 // read the string table 158 current_string_table_sz = resHdr.mStringTableLength; 159 current_string_table = malloc(resHdr.mStringTableLength); 160 if (current_string_table == NULL) { 161 fprintf(stderr, "%s: failed to allocate string table of size 0x%x\n", __func__, current_string_table_sz); 162 fclose(bincsv); 163 fclose(csv); 164 return -1; 165 } 166 r = fread(current_string_table, 1, current_string_table_sz, bincsv); 167 if (r != current_string_table_sz) { 168 fprintf(stderr, "%s: failed to read string table from file.\n", __func__); 169 fclose(bincsv); 170 fclose(csv); 171 free(current_string_table); 172 current_string_table = NULL; 173 return -1; 174 } 175 176 // read the header row 177 if (binrow_to_txtrow(bincsv, csv) < 0) { 178 fprintf(stderr, "%s: failed to read header row from file.\n", __func__); 179 fclose(bincsv); 180 fclose(csv); 181 free(current_string_table); 182 current_string_table = NULL; 183 return -1; 184 } 185 186 // read out all the rowsnum_cols = 0; 187 r = fread(&num_rows, sizeof(int), 1, bincsv); 188 if (r != 1 || num_rows < 0) { // sanity check 189 fprintf(stderr, "%s: failed to read number of rows.\n", __func__); 190 return -1; 191 } 192 for (int i = 0; i < num_rows; i++) { 193 if (binrow_to_txtrow(bincsv, csv) < 0) { 194 fprintf(stderr, "%s: failed to read row %i from file.\n", __func__, i); 195 fclose(bincsv); 196 fclose(csv); 197 free(current_string_table); 198 current_string_table = NULL; 199 return -1; 200 } 201 } 202 203 fclose(bincsv); 204 fclose(csv); 205 free(current_string_table); 206 current_string_table = NULL; 207 return 0; 208} 209 210uint32_t add_string_to_table(const char *string) { 211 if ((current_string_table_ptr + strlen(string) + 1) > current_string_table_sz) { 212 fprintf(stderr, "%s: bounds check failed adding string of length 0x%lx (ptr 0x%x, sz 0x%x)\n", __func__, strlen(string) + 1, current_string_table_ptr, current_string_table_sz); 213 return 0xFFFFFFFF; 214 } 215 216 uint32_t ptr = current_string_table_ptr; 217 strcpy(current_string_table + current_string_table_ptr, string); 218 current_string_table_ptr += strlen(string) + 1; 219 return ptr; 220} 221 222#define MAX_COLS 16 223int txtrow_to_binrow(CsvHandle csv, char *row, FILE *bin) { 224 int num_cols = 0; 225 uint32_t cols[MAX_COLS] = {0}; // max 16 columns isn't great, but fuck it 226 const char *col = NULL; 227 while ((col = CsvReadNextCol(row, csv))) { 228 cols[num_cols] = add_string_to_table(col); 229 if (cols[num_cols] == 0xFFFFFFFF) { 230 fprintf(stderr, "%s: failed to add string to table!\n", __func__); 231 return -1; 232 } 233 num_cols++; 234 if (num_cols >= MAX_COLS) { 235 fprintf(stderr, "%s: exceeded maximum columns limit.\n", __func__); 236 return -1; 237 } 238 } 239 // TODO(Emma): check for errors when writing 240 fwrite(&num_cols, sizeof(int), 1, bin); 241 fwrite(cols, sizeof(uint32_t), num_cols, bin); 242 return 0; 243} 244 245int csv_to_bincsv(const char *csv_file, const char *bincsv_file) { 246 int r = 0; 247 // This sucks - we read the CSV in its entirety twice to get the size of symbol table, 248 // and number of rows, and then to actually insert those entries to the table. 249 char *row = NULL; 250 int num_rows = 0; 251 uint32_t str_table_size = 0; 252 CsvHandle csv = CsvOpen2(csv_file, current_seperator, '"', '\\'); 253 if (csv == NULL) { 254 fprintf(stderr, "%s: failed to open csv file from '%s'.\n", __func__, csv_file); 255 return -1; 256 } 257 while ((row = CsvReadNextRow(csv))) { 258 const char *col = NULL; 259 num_rows++; 260 while ((col = CsvReadNextCol(row, csv))) 261 str_table_size += strlen(col) + 1; 262 } 263 CsvClose(csv); 264 265 // actually allocate the string table 266 current_string_table_sz = str_table_size; 267 current_string_table = malloc(str_table_size); 268 if (current_string_table == NULL) { 269 fprintf(stderr, "%s: failed to allocate string table.\n", __func__); 270 return -1; 271 } 272 memset(current_string_table, 0, str_table_size); 273 274 FILE *bincsv = fopen(bincsv_file, "wb"); 275 if (bincsv == NULL) { 276 fprintf(stderr, "%s: failed to open '%s' for writing.\n", __func__, bincsv_file); 277 free(current_string_table); 278 current_string_table = NULL; 279 return -1; 280 } 281 282 // build the resource header and write it to the file 283 CsvResourceHeader resHdr = {0}; 284 resHdr.mRevision = 0x1; 285 resHdr.mUnk = 0x2; 286 resHdr.mSeperator = current_seperator; 287 resHdr.mStringTableLength = str_table_size; 288 r = fwrite(&resHdr, sizeof(CsvResourceHeader), 1, bincsv); 289 if (r != 1) { 290 fprintf(stderr, "%s: failed to write header to file.\n", __func__); 291 fclose(bincsv); 292 free(current_string_table); 293 current_string_table = NULL; 294 return -1; 295 } 296 // skip past the size of the string table - we'll write it later 297 fseek(bincsv, str_table_size, SEEK_CUR); 298 299 // read the CSV again and write out each of the lines 300 int num_rows_done = 0; 301 csv = CsvOpen2(csv_file, current_seperator, '"', '\\'); 302 if (csv == NULL) { 303 fprintf(stderr, "%s: failed to open csv file from '%s'.\n", __func__, csv_file); 304 fclose(bincsv); 305 free(current_string_table); 306 current_string_table = NULL; 307 return -1; 308 } 309 while ((row = CsvReadNextRow(csv))) { 310 if (txtrow_to_binrow(csv, row, bincsv) < 0) { 311 fprintf(stderr, "%s: failed on row %i ('%s')\n", __func__, num_rows_done, row); 312 CsvClose(csv); 313 fclose(bincsv); 314 free(current_string_table); 315 current_string_table = NULL; 316 return -1; 317 } 318 // after the header row, there's a count of the rest of the rows 319 if (num_rows_done == 0) { 320 int num_rows_file = num_rows - 1; // don't include header row 321 fwrite(&num_rows_file, sizeof(int), 1, bincsv); 322 } 323 num_rows_done++; 324 } 325 CsvClose(csv); 326 327 // write the string table to the file 328 fseek(bincsv, sizeof(CsvResourceHeader), SEEK_SET); 329 r = fwrite(current_string_table, 1, current_string_table_sz, bincsv); 330 if (r != current_string_table_sz) { 331 fprintf(stderr, "%s: failed to write string table to file.\n", __func__); 332 fclose(bincsv); 333 free(current_string_table); 334 current_string_table = NULL; 335 return -1; 336 } 337 338 free(current_string_table); 339 fclose(bincsv); 340 current_string_table = NULL; 341 return 0; 342} 343 344int print_usage(const char *filename) { 345 printf("usage:\n"); 346 printf(" %s bin2csv /path/to/input.csv[_xb1|_ps4|_pc] /path/to/output.csv\n", filename); 347 printf(" %s csv2bin /path/to/input.csv /path/to/output.csv[_xb1|_ps4|_pc]\n", filename); 348 return -1; 349} 350 351int main(int argc, const char **argv) { 352 if (argc < 4) 353 return print_usage(argv[0]); 354 355 const char *verb = argv[1]; 356 if (strcasecmp(verb, "bin2csv") == 0) { 357 return bincsv_to_csv(argv[2], argv[3]); 358 } else if (strcasecmp(verb, "csv2bin") == 0) { 359 return csv_to_bincsv(argv[2], argv[3]); 360 } else { 361 fprintf(stderr, "invalid verb '%s'\n", verb); 362 return print_usage(argv[0]); 363 } 364}