Something went wrong. Try again.
converts between regular CSV files and the binary CSV format as used in Harmonix Forge engine games (RB4, RBVR, etc)
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365#include <stdio.h>#include <stdint.h>#include <string.h>#include <stdlib.h>
#include "csv.h"
#define DEFAULT_SEPERATOR ','
char *current_string_table = NULL;uint32_t current_string_table_sz = 0;uint32_t current_string_table_ptr = 0;char current_seperator = DEFAULT_SEPERATOR;static char quote = '"';
typedef struct _CsvResourceHeader { uint32_t mRevision; uint32_t mUnk; uint8_t mSeperator; uint32_t mStringTableLength;} __attribute__((packed)) CsvResourceHeader;
const char *get_string_from_table(uint32_t offset) { if (current_string_table == NULL) { fprintf(stderr, "%s: no string table loaded.\n", __func__); return NULL; } if (offset > current_string_table_sz) { fprintf(stderr, "%s: offset (0x%x) larger than string table (0x%x).\n", __func__, offset, current_string_table_sz); return NULL; } return current_string_table + offset;}
int binrow_to_txtrow(FILE *bin, FILE *txt) { char newln[] = { '\r', '\n' }; int r = 0; uint32_t num_cols = 0; uint32_t *columns = NULL;
// if we don't have a string table, bail out if (current_string_table == NULL) { fprintf(stderr, "%s: no string table loaded.\n", __func__); return -1; }
// read the number of columns from the file num_cols = 0; r = fread(&num_cols, sizeof(int), 1, bin); if (r != 1 || num_cols < 0) { // sanity check fprintf(stderr, "%s: failed to read number of columns.\n", __func__); return -1; }
// allocate enough room for the columns in memory temporarily columns = malloc(sizeof(uint32_t) * num_cols); if (columns == NULL) { fprintf(stderr, "%s: failed to allocate memory for columns.\n", __func__); return -1; } r = fread(columns, sizeof(uint32_t), num_cols, bin); if (r != num_cols) { fprintf(stderr, "%s: failed to read columns.\n", __func__); free(columns); return -1; }
// write the column text data out to the text file for (int i = 0; i < num_cols; i++) { int str_esc_len = 0; const char *str = NULL; char *str_esc = NULL; int j = 0; int k = 0;
str = get_string_from_table(columns[i]); if (str == NULL) { fprintf(stderr, "%s: failed to read column from string table.\n", __func__); free(columns); return -1; }
// get the size of the string when escaped for (j=0; str[j]; str[j]==quote ? str_esc_len += 2 : str_esc_len++, j++); // and allocate a buffer for it str_esc = malloc(str_esc_len + 1); if (str_esc == NULL) { fprintf(stderr, "%s: failed to allocate buffer for escaped string.\n", __func__); free(columns); return -1; } // and copy the string, escaping it k = 0; for (j = 0; str[j]; j++) { if (str[j] == quote) str_esc[k++] = quote; str_esc[k++] = str[j]; } str_esc[k] = '\0';
// TODO(Emma): check if the writes fail here fwrite("e, 1, sizeof(char), txt); fwrite(str_esc, 1, strlen(str_esc), txt); fwrite("e, 1, sizeof(char), txt); // write the seperator if it isn't if (i < (num_cols - 1)) fwrite(¤t_seperator, 1, sizeof(char), txt); free(str_esc); }
// write a CRLF newline fwrite(newln, sizeof(newln), 1, txt);
// successfully read and output - free the buffer we made before free(columns); return 0;}
int bincsv_to_csv(const char *bincsv_file, const char *csv_file) { FILE *bincsv = NULL; FILE *csv = NULL; CsvResourceHeader resHdr = {0}; int r = 0; int num_rows = 0;
// open the files bincsv = fopen(bincsv_file, "rb"); if (bincsv == NULL) { fprintf(stderr, "%s: failed to read from '%s'.\n", __func__, bincsv_file); return -1; } csv = fopen(csv_file, "wb"); // load as binary; even tho it's text data, i don't trust it if (csv == NULL) { fprintf(stderr, "%s: failed to open '%s' for writing.\n", __func__, csv_file); fclose(bincsv); return -1; }
// read the file header r = fread(&resHdr, sizeof(CsvResourceHeader), 1, bincsv); if (r != 1) { fprintf(stderr, "%s: failed to read CsvResource header.\n", __func__); fclose(bincsv); fclose(csv); return -1; } // check the version if (resHdr.mRevision != 0x1 || resHdr.mUnk != 0x2) { fprintf(stderr, "%s: unknown CsvResource type (0x%x, 0x%x)\n", __func__, resHdr.mRevision, resHdr.mUnk); fclose(bincsv); fclose(csv); return -1; } current_seperator = resHdr.mSeperator;
// read the string table current_string_table_sz = resHdr.mStringTableLength; current_string_table = malloc(resHdr.mStringTableLength); if (current_string_table == NULL) { fprintf(stderr, "%s: failed to allocate string table of size 0x%x\n", __func__, current_string_table_sz); fclose(bincsv); fclose(csv); return -1; } r = fread(current_string_table, 1, current_string_table_sz, bincsv); if (r != current_string_table_sz) { fprintf(stderr, "%s: failed to read string table from file.\n", __func__); fclose(bincsv); fclose(csv); free(current_string_table); current_string_table = NULL; return -1; }
// read the header row if (binrow_to_txtrow(bincsv, csv) < 0) { fprintf(stderr, "%s: failed to read header row from file.\n", __func__); fclose(bincsv); fclose(csv); free(current_string_table); current_string_table = NULL; return -1; }
// read out all the rowsnum_cols = 0; r = fread(&num_rows, sizeof(int), 1, bincsv); if (r != 1 || num_rows < 0) { // sanity check fprintf(stderr, "%s: failed to read number of rows.\n", __func__); return -1; } for (int i = 0; i < num_rows; i++) { if (binrow_to_txtrow(bincsv, csv) < 0) { fprintf(stderr, "%s: failed to read row %i from file.\n", __func__, i); fclose(bincsv); fclose(csv); free(current_string_table); current_string_table = NULL; return -1; } }
fclose(bincsv); fclose(csv); free(current_string_table); current_string_table = NULL; return 0;}
uint32_t add_string_to_table(const char *string) { if ((current_string_table_ptr + strlen(string) + 1) > current_string_table_sz) { 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); return 0xFFFFFFFF; }
uint32_t ptr = current_string_table_ptr; strcpy(current_string_table + current_string_table_ptr, string); current_string_table_ptr += strlen(string) + 1; return ptr;}
#define MAX_COLS 16int txtrow_to_binrow(CsvHandle csv, char *row, FILE *bin) { int num_cols = 0; uint32_t cols[MAX_COLS] = {0}; // max 16 columns isn't great, but fuck it const char *col = NULL; while ((col = CsvReadNextCol(row, csv))) { cols[num_cols] = add_string_to_table(col); if (cols[num_cols] == 0xFFFFFFFF) { fprintf(stderr, "%s: failed to add string to table!\n", __func__); return -1; } num_cols++; if (num_cols >= MAX_COLS) { fprintf(stderr, "%s: exceeded maximum columns limit.\n", __func__); return -1; } } // TODO(Emma): check for errors when writing fwrite(&num_cols, sizeof(int), 1, bin); fwrite(cols, sizeof(uint32_t), num_cols, bin); return 0;}
int csv_to_bincsv(const char *csv_file, const char *bincsv_file) { int r = 0; // This sucks - we read the CSV in its entirety twice to get the size of symbol table, // and number of rows, and then to actually insert those entries to the table. char *row = NULL; int num_rows = 0; uint32_t str_table_size = 0; CsvHandle csv = CsvOpen2(csv_file, current_seperator, '"', '\\'); if (csv == NULL) { fprintf(stderr, "%s: failed to open csv file from '%s'.\n", __func__, csv_file); return -1; } while ((row = CsvReadNextRow(csv))) { const char *col = NULL; num_rows++; while ((col = CsvReadNextCol(row, csv))) str_table_size += strlen(col) + 1; } CsvClose(csv); // actually allocate the string table current_string_table_sz = str_table_size; current_string_table = malloc(str_table_size); if (current_string_table == NULL) { fprintf(stderr, "%s: failed to allocate string table.\n", __func__); return -1; } memset(current_string_table, 0, str_table_size);
FILE *bincsv = fopen(bincsv_file, "wb"); if (bincsv == NULL) { fprintf(stderr, "%s: failed to open '%s' for writing.\n", __func__, bincsv_file); free(current_string_table); current_string_table = NULL; return -1; }
// build the resource header and write it to the file CsvResourceHeader resHdr = {0}; resHdr.mRevision = 0x1; resHdr.mUnk = 0x2; resHdr.mSeperator = current_seperator; resHdr.mStringTableLength = str_table_size; r = fwrite(&resHdr, sizeof(CsvResourceHeader), 1, bincsv); if (r != 1) { fprintf(stderr, "%s: failed to write header to file.\n", __func__); fclose(bincsv); free(current_string_table); current_string_table = NULL; return -1; } // skip past the size of the string table - we'll write it later fseek(bincsv, str_table_size, SEEK_CUR);
// read the CSV again and write out each of the lines int num_rows_done = 0; csv = CsvOpen2(csv_file, current_seperator, '"', '\\'); if (csv == NULL) { fprintf(stderr, "%s: failed to open csv file from '%s'.\n", __func__, csv_file); fclose(bincsv); free(current_string_table); current_string_table = NULL; return -1; } while ((row = CsvReadNextRow(csv))) { if (txtrow_to_binrow(csv, row, bincsv) < 0) { fprintf(stderr, "%s: failed on row %i ('%s')\n", __func__, num_rows_done, row); CsvClose(csv); fclose(bincsv); free(current_string_table); current_string_table = NULL; return -1; } // after the header row, there's a count of the rest of the rows if (num_rows_done == 0) { int num_rows_file = num_rows - 1; // don't include header row fwrite(&num_rows_file, sizeof(int), 1, bincsv); } num_rows_done++; } CsvClose(csv);
// write the string table to the file fseek(bincsv, sizeof(CsvResourceHeader), SEEK_SET); r = fwrite(current_string_table, 1, current_string_table_sz, bincsv); if (r != current_string_table_sz) { fprintf(stderr, "%s: failed to write string table to file.\n", __func__); fclose(bincsv); free(current_string_table); current_string_table = NULL; return -1; }
free(current_string_table); fclose(bincsv); current_string_table = NULL; return 0;}
int print_usage(const char *filename) { printf("usage:\n"); printf(" %s bin2csv /path/to/input.csv[_xb1|_ps4|_pc] /path/to/output.csv\n", filename); printf(" %s csv2bin /path/to/input.csv /path/to/output.csv[_xb1|_ps4|_pc]\n", filename); return -1;}
int main(int argc, const char **argv) { if (argc < 4) return print_usage(argv[0]);
const char *verb = argv[1]; if (strcasecmp(verb, "bin2csv") == 0) { return bincsv_to_csv(argv[2], argv[3]); } else if (strcasecmp(verb, "csv2bin") == 0) { return csv_to_bincsv(argv[2], argv[3]); } else { fprintf(stderr, "invalid verb '%s'\n", verb); return print_usage(argv[0]); }}