Add an extension to handle binary data. MsdFile is now
8-bit clean.
This commit is contained in:
+162
-94
@@ -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<iBytesRead; i++ )
|
||||
int i = 0;
|
||||
while(i < len)
|
||||
{
|
||||
switch( szFileString[i] )
|
||||
if(!strncmp(buf+i, "//", 2))
|
||||
{
|
||||
case '#': // begins a new value
|
||||
{
|
||||
/* Unfortunately, many of these files are missing ;'s.
|
||||
* For now, if we get a # when we thought we were inside
|
||||
* a value/parameter, assume we missed the ;. Back up,
|
||||
* delete whitespace and end the value. */
|
||||
if(ReadingValue) {
|
||||
/* 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", 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] != '#' && i<iBytesRead )
|
||||
i++;
|
||||
ReadingValue=false;
|
||||
|
||||
if(!FirstChar) {
|
||||
/* Oops, we're not; handle this like a regular character. */
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case '/':
|
||||
if( szFileString[i+1] == '/' )
|
||||
|
||||
AddParam(buf+value_start, j - value_start);
|
||||
ReadingValue=false;
|
||||
}
|
||||
|
||||
/* # starts a new value. */
|
||||
if(!ReadingValue && buf[i] == '#') {
|
||||
AddValue();
|
||||
ReadingValue=true;
|
||||
}
|
||||
|
||||
if(!ReadingValue)
|
||||
{
|
||||
i++;
|
||||
continue; /* nothing else is meaningful outside of a value */
|
||||
}
|
||||
|
||||
/* : and ; end the current param, if any. */
|
||||
if(value_start != -1 && (buf[i] == ':' || buf[i] == ';'))
|
||||
AddParam(buf+value_start, i - value_start);
|
||||
|
||||
/* # and : begin new params. */
|
||||
if(buf[i] == '#' || buf[i] == ':')
|
||||
{
|
||||
i++; /* skip */
|
||||
value_start = i;
|
||||
if(buf[value_start] == '\001')
|
||||
{
|
||||
// advance until new line
|
||||
for( ; i<iBytesRead; i++ )
|
||||
value_start++;
|
||||
|
||||
/* Binary param. Expect digits followed by a comma. */
|
||||
if(!isdigit(buf[value_start]))
|
||||
{
|
||||
if( szFileString[i] == '\n' )
|
||||
break;
|
||||
else
|
||||
szFileString[i] = ' ';
|
||||
LOG->Trace("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<m_iNumValues; x++ )
|
||||
{
|
||||
for( unsigned y=0; y<m_iNumParams[x]; y++ )
|
||||
{
|
||||
m_sParams[x][y] = szParams[x][y];
|
||||
}
|
||||
}
|
||||
/* ; ends the current value. */
|
||||
if(buf[i] == ';')
|
||||
ReadingValue=false;
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if successful, false otherwise
|
||||
bool MsdFile::ReadFile( CString sNewPath )
|
||||
{
|
||||
int fd;
|
||||
|
||||
/* Open a file */
|
||||
if( (fd = open(sNewPath, _O_RDONLY, 0)) == -1 )
|
||||
return false;
|
||||
|
||||
int iBufferSize = _filelength( fd ) + 1000; // +1000 because sometimes the bytes read is > 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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user