From 74d1bb00a613ddbbd68afd5b9b10108e67030d96 Mon Sep 17 00:00:00 2001 From: Glenn Maynard Date: Sat, 14 Dec 2002 03:47:05 +0000 Subject: [PATCH] Add an extension to handle binary data. MsdFile is now 8-bit clean. --- stepmania/src/MsdFile.cpp | 256 ++++++++++++++++++++++++-------------- stepmania/src/MsdFile.h | 4 + 2 files changed, 166 insertions(+), 94 deletions(-) diff --git a/stepmania/src/MsdFile.cpp b/stepmania/src/MsdFile.cpp index d2d952766a..ef14fdd5a0 100644 --- a/stepmania/src/MsdFile.cpp +++ b/stepmania/src/MsdFile.cpp @@ -10,7 +10,34 @@ ----------------------------------------------------------------------------- */ +/* The original MSD format is simply: + * + * #PARAM0:PARAM1:PARAM2:PARAM3; + * #NEXTPARAM0:PARAM1:PARAM2:PARAM3; + * + * (The first field is typically an identifier, but doesn't have to be.) + * + * The semicolon is not optional, though if we hit a # on a new line, eg: + * #VALUE:PARAM1 + * #VALUE2:PARAM2 + * we'll recover. + * + * Extension: If : is followed by ASCII 1 (^A), we read a binary value, in this + * form: + * + * #VALUE:^A4,DATA:more tags; + * "4" is the number of bytes of data to expect, not including itself, and not + * including the comma separating it from the data. The data is completely unparsed + * and may contain colons, #, or whitespace without fear of corruption. The ^A + * character is used to avoid it showing up in real data. We only use this internally + * for caching; this is ugly, so don't use it in distributed data! + * + * TODO: Normal text fields need some way of escaping. We need to be able to escape + * colons and "//". Also, we should escape #s, so if we really want to put a # at the + * beginning of a line, we can. + */ #include "MsdFile.h" +#include "RageLog.h" #include "io.h" #include "fcntl.h" @@ -24,121 +51,162 @@ MsdFile::~MsdFile() { } -//returns true if successful, false otherwise -bool MsdFile::ReadFile( CString sNewPath ) +void MsdFile::AddParam( char *buf, int len ) { - int fh; + int valueno = m_iNumValues-1; + int paramno = m_iNumParams[valueno]; + ASSERT( paramno < MAX_PARAMS_PER_VALUE ); + m_iNumParams[valueno]++; + m_sParams[valueno][paramno].assign(buf, len); +} - /* Open a file */ - if( (fh = _open(sNewPath, _O_RDONLY, 0)) == -1 ) - return false; - - int iBufferSize = _filelength( fh ) + 1000; // +1000 because sometimes the bytes read is > filelength. Why? - _close( fh ); - - - FILE* fp = fopen(sNewPath, "r"); - if( fp == NULL ) - return false; - - // allocate a string to hold the file - char* szFileString = new char[iBufferSize]; - ASSERT( szFileString ); - - int iBytesRead = fread( szFileString, 1, iBufferSize, fp ); - ASSERT( iBufferSize > iBytesRead ); - szFileString[iBytesRead] = '\0'; - - char* szParams[MAX_VALUES][MAX_PARAMS_PER_VALUE]; +void MsdFile::AddValue() /* (no extra charge) */ +{ + m_iNumValues++; + ASSERT( m_iNumValues < MAX_VALUES ); +} +void MsdFile::ReadBuf( char *buf, int len ) +{ + int value_start = -1; + memset(m_iNumParams, 0, sizeof(m_iNumParams)); m_iNumValues = 0; bool ReadingValue=false; - int i; - for( i=0; i= 0 && !strchr("\r\n", szFileString[j]); --j) - { - if(szFileString[j] == ' ' || szFileString[j] == '\t') - continue; + /* //; erase with spaces until newline */ + do { + buf[i] = ' '; + i++; + } while(buf[i] != '\n'); - FirstChar = false; - break; - } - if(!FirstChar) { - /* Oops, we're not; handle this like a regular character. */ - break; - } + continue; + } - for(j = i-1; j >= 0 && isspace(szFileString[j]); --j) - szFileString[j] = 0; - } + if(!ReadingValue) { + // fast forward until the next '#' (optimization) + while(i < len && buf[i] != '#') i++; + } - ReadingValue=true; - m_iNumValues++; - ASSERT( m_iNumValues < MAX_VALUES ); - int iCurValueIndex = m_iNumValues-1; - m_iNumParams[iCurValueIndex] = 1; - szParams[iCurValueIndex][0] = &szFileString[i+1]; - } - break; - case ':': // begins a new parameter + if(ReadingValue && buf[i] == '#') { + /* Unfortunately, many of these files are missing ;'s. + * If we get a # when we thought we were inside a value, assume we + * missed the ;. Back up and end the value. */ + /* Make sure this # is the first non-whitespace character on the line. */ + bool FirstChar = true; + int j; + for(j = i-1; j >= 0 && !strchr("\r\n", buf[j]); --j) { - szFileString[i] = '\0'; - int iCurValueIndex = m_iNumValues-1; - m_iNumParams[iCurValueIndex] ++; - int iCurParamIndex = m_iNumParams[iCurValueIndex]-1; - szParams[iCurValueIndex][iCurParamIndex] = &szFileString[i+1]; + if(buf[j] == ' ' || buf[j] == '\t') + continue; + + FirstChar = false; + break; } - break; - case ';': // ends a value - { - szFileString[i] = '\0'; - // fast forward until just before the next '#' - while( szFileString[i+1] != '#' && iTrace("Expected digit at position %i", value_start); + ReadingValue = false; /* so we skip to the next value */ + continue; } + int bytes = atoi(buf+value_start); + if(bytes < 0) bytes = 0; /* sanity */ + while(isdigit(buf[value_start])) + value_start++; + if(buf[value_start] != ',') + { + LOG->Trace("Expected comma at position %i", value_start); + ReadingValue = false; /* so we skip to the next value */ + continue; + } + value_start++; + + /* Make sure we have enough data left. */ + if(len - value_start < bytes) + { + LOG->Trace("%i bytes not available at position %i", value_start); + ReadingValue = false; /* so we skip to the next value */ + continue; + } + + AddParam(buf+value_start, bytes); + i = value_start + bytes; + value_start = -1; } - // fall through. If we didn't take the if above, then this '/' is part of a parameter! - default: - ; // do nothing + continue; } - } - for( unsigned x=0; x filelength. Why? + + // allocate a string to hold the file + char* szFileString = new char[iBufferSize]; + + int iBytesRead = read( fd, szFileString, iBufferSize ); + close( fd ); + + ASSERT( iBufferSize > iBytesRead ); + szFileString[iBytesRead] = '\0'; + + ReadBuf(szFileString, iBytesRead); - fclose( fp ); delete [] szFileString; return true; diff --git a/stepmania/src/MsdFile.h b/stepmania/src/MsdFile.h index a59848ea79..dfb350434f 100644 --- a/stepmania/src/MsdFile.h +++ b/stepmania/src/MsdFile.h @@ -17,6 +17,10 @@ const unsigned MAX_PARAMS_PER_VALUE = 10; class MsdFile { + void ReadBuf( char *buf, int len ); + void AddParam( char *buf, int len ); + void AddValue(); + public: MsdFile();