Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

406
common/formats/cue.cpp Normal file
View File

@@ -0,0 +1,406 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/debug.h"
#include "common/stream.h"
#include "common/formats/cue.h"
namespace Common {
enum {
kContextHeader,
kContextFiles,
kContextTracks,
};
static String nexttok(const char *s, const char **newP = nullptr) {
String res;
// Scan first non-whitespace
while (*s && (*s == ' ' || *s == '\t')) // If we see a whitespace
s++;
if (*s == '"') { // If it is a string then scan till end quote
s++; // skip the quote
while (*s && *s != '"')
res += *s++;
if (*s == '"')
s++;
} else {
while (*s && *s != ' ' && *s != '\t' && *s != '\n' && *s != '\r')
res += *s++;
}
if (newP)
*newP = s;
return res;
}
CueSheet::CueSheet(const char *sheet) {
parse(sheet);
}
CueSheet::CueSheet(SeekableReadStream *stream) {
int size = stream->size();
char *data = (char *)calloc(size + 1, 1); // null-terminated string
stream->read(data, size);
parse(data);
free(data);
}
void CueSheet::parse(const char *sheet) {
String line;
_context = kContextHeader;
const char *s = sheet;
while (*s) {
line.clear();
// Get a line
while (*s && *s != '\n' && *s != '\r') // If we see a whitespace
line += *s++;
// Skip any newlines and line feeds
while (*s == '\n' || *s == '\r') {
if (*s == '\n')
_lineNum++;
s++;
}
if (line.empty())
continue;
String firstToken = nexttok(line.c_str()); // Peek into comments
// Skip non-conformant comments
if (firstToken[0] == ';' || (firstToken.size() > 1 && firstToken[0] == '/' && firstToken[1] == '/'))
continue;
switch (_context) {
case kContextHeader:
parseHeaderContext(line.c_str());
break;
case kContextFiles:
parseFilesContext(line.c_str());
break;
case kContextTracks:
parseTracksContext(line.c_str());
break;
default:
error("CueSheet: bad context %d at line %d", _context, _lineNum);
}
}
}
struct CueSheet::LookupTable {
const char *key;
int value;
};
int CueSheet::lookupInTable(const LookupTable *table, const char *key) {
while (table->key) {
if (!strcmp(key, table->key))
return table->value;
table++;
}
error("CueSheet::lookupInTable(): Unknown lookup token %s at line %d", key, _lineNum);
}
int CueSheet::parseMSF(const char *str) {
int min = 0, sec = 0, frm = 0;
if (sscanf(str, "%d:%d:%d", &min, &sec, &frm) != 3)
warning("Malformed MSF at line %d: %s", _lineNum, str);
return frm + 75 * (sec + 60 * min);
}
static const CueSheet::LookupTable fileTypes[] = {
{ "BINARY", CueSheet::kFileTypeBinary },
{ "AIFF", CueSheet::kFileTypeAIFF },
{ "WAVE", CueSheet::kFileTypeWave },
{ "MP3", CueSheet::kFileTypeMP3 },
{ "MOTOROLA", CueSheet::kFileTypeMotorola },
{ 0, 0 }
};
void CueSheet::parseHeaderContext(const char *line) {
const char *s = line;
String command = nexttok(s, &s);
if (command == "FILE") {
_files.push_back(CueFile());
_currentFile++;
_files[_currentFile].name = nexttok(s, &s);
String type = nexttok(s, &s);
_files[_currentFile].type = (FileType)lookupInTable(fileTypes, type.c_str());
_context = kContextFiles;
_currentTrack = -1;
debug(5, "File: %s, type: %s (%d)", _files[_currentFile].name.c_str(), type.c_str(), _files[_currentFile].type);
} else if (command == "TITLE") {
_metadata.title = nexttok(s, &s);
debug(5, "Title: %s", _metadata.title.c_str());
} else if (command == "PERFORMER") {
_metadata.performer = nexttok(s, &s);
debug(5, "Performer: %s", _metadata.performer.c_str());
} else if (command == "CATALOG") {
_metadata.catalog = nexttok(s, &s);
debug(5, "Catalog: %s", _metadata.catalog.c_str());
} else if (command == "REM") {
String subcommand = nexttok(s, &s);
if (subcommand == "DATE") {
_metadata.date = nexttok(s, &s);
debug(5, "Date: %s", _metadata.date.c_str());
} else if (subcommand == "GENRE") {
_metadata.genre = nexttok(s, &s);
debug(5, "Genre: %s", _metadata.genre.c_str());
} else if (subcommand == "COMMENT") {
debug(5, "Skipping Comment: %s", s);
} else {
warning("CueSheet: Unprocessed REM subcommand %s at line %d", subcommand.c_str(), _lineNum);
}
} else {
warning("CueSheet: Unprocessed command %s at line %d", command.c_str(), _lineNum);
}
}
static const CueSheet::LookupTable trackTypes[] = {
{ "AUDIO", CueSheet::kTrackTypeAudio }, // Audio (sector size: 2352)
{ "CDG", CueSheet::kTrackTypeCDG }, // Karaoke CD+G (sector size: 2448)
{ "MODE1_RAW", CueSheet::kTrackTypeMode1_Raw }, // CD-ROM Mode 1 data (raw) (sector size: 2352), used by cdrdao
{ "MODE1/2048", CueSheet::kTrackTypeMode1_2048 }, // CD-ROM Mode 1 data (cooked) (sector size: 2048)
{ "MODE1/2352", CueSheet::kTrackTypeMode1_2352 }, // CD-ROM Mode 1 data (raw) (sector size: 2352)
{ "MODE2_RAW", CueSheet::kTrackTypeMode2_Raw }, // CD-ROM Mode 2 data (raw) (sector size: 2352), used by cdrdao
{ "MODE2/2048", CueSheet::kTrackTypeMode2_2048 }, // CD-ROM Mode 2 XA form-1 data (sector size: 2048)
{ "MODE2/2324", CueSheet::kTrackTypeMode2_2324 }, // CD-ROM Mode 2 XA form-2 data (sector size: 2324)
{ "MODE2/2336", CueSheet::kTrackTypeMode2_2366 }, // CD-ROM Mode 2 data (sector size: 2336)
{ "MODE2/2352", CueSheet::kTrackTypeMode2_2352 }, // CD-ROM Mode 2 data (raw) (sector size: 2352)
{ "CDI/2336", CueSheet::kTrackTypeCDI_2336 }, // CDI Mode 2 data
{ "CDI/2352", CueSheet::kTrackTypeCDI_2352 }, // CDI Mode 2 data
{ 0, 0 }
};
static const CueSheet::LookupTable trackTypesSectorSizes[] = {
{ "AUDIO", 2352 },
{ "CDG", 2448 },
{ "MODE1_RAW", 2352 },
{ "MODE1/2048", 2048 },
{ "MODE1/2352", 2352 },
{ "MODE2_RAW", 2352 },
{ "MODE2/2048", 2048 },
{ "MODE2/2324", 2324 },
{ "MODE2/2336", 2336 },
{ "MODE2/2352", 2352 },
{ "CDI/2336", 2336 },
{ "CDI/2352", 2352 },
{ 0, 0 }
};
void CueSheet::parseFilesContext(const char *line) {
const char *s = line;
String command = nexttok(s, &s);
if (command == "TRACK") {
int trackNum = atoi(nexttok(s, &s).c_str());
String trackType = nexttok(s, &s);
// We have to add + 2 here because _currentTrack is a counter
// for the array, which is 0-indexed, while the disc's track
// numbers are 1-indexed. The next track, in disc numbering,
// will be two greater than _currentTrack.
if (trackNum < 0 || (_currentTrack > 0 && _currentTrack + 2 != trackNum)) {
warning("CueSheet: Incorrect track number. Expected %d but got %d at line %d", _currentTrack + 1, trackNum, _lineNum);
} else {
_tracks.push_back(CueTrack());
// Array is 0-indexed, track numbers are 1-indexed
_currentTrack = trackNum - 1;
_tracks[_currentTrack].number = trackNum;
_tracks[_currentTrack].type = (TrackType)lookupInTable(trackTypes, trackType.c_str());
_tracks[_currentTrack].size = lookupInTable(trackTypesSectorSizes, trackType.c_str());
_tracks[_currentTrack].file = _files[_currentFile];
debug(5, "Track: %d type: %s (%d)", trackNum, trackType.c_str(), _tracks[_currentTrack].type);
}
_context = kContextTracks;
} else {
warning("CueSheet: Unprocessed file command %s at line %d", command.c_str(), _lineNum);
}
}
static const CueSheet::LookupTable trackFlags[] = {
{ "4CH", CueSheet::kTrackFlag4ch },
{ "DCP", CueSheet::kTrackFlagDCP },
{ "PRE", CueSheet::kTrackFlagPre },
{ "SCMS", CueSheet::kTrackFlagSCMS },
{ 0, 0 }
};
void CueSheet::parseTracksContext(const char *line) {
const char *s = line;
String command = nexttok(s, &s);
if (command == "TRACK") {
parseFilesContext(line);
} else if (command == "TITLE") {
_tracks[_currentTrack].title = nexttok(s, &s);
debug(5, "Track title: %s", _tracks[_currentTrack].title.c_str());
} else if (command == "INDEX") {
int indexNum = atoi(nexttok(s, &s).c_str());
int frames = parseMSF(nexttok(s, &s).c_str());
for (int i = (int)_tracks[_currentTrack].indices.size(); i <= indexNum; i++) {
// -1 indicates "no index" to let callers guard against
// interpreting these as real values
_tracks[_currentTrack].indices.push_back(-1);
}
_tracks[_currentTrack].indices[indexNum] = frames;
debug(5, "Index: %d, frames: %d", indexNum, frames);
} else if (command == "PREGAP") {
_tracks[_currentTrack].pregap = parseMSF(nexttok(s, &s).c_str());
debug(5, "Track pregap: %d", _tracks[_currentTrack].pregap);
} else if (command == "FLAGS") {
String flag;
uint32 flags = 0;
while (*s) {
flag = nexttok(s, &s);
flags |= lookupInTable(trackFlags, flag.c_str());
}
_tracks[_currentTrack].flags = flags;
debug(5, "Track flags: %d", _tracks[_currentTrack].flags);
} else if (command == "FILE") {
parseHeaderContext(line);
} else if (command == "PERFORMER") {
_tracks[_currentTrack].performer = nexttok(s, &s);
debug(5, "Track performer: %s", _tracks[_currentTrack].performer.c_str());
} else {
warning("CueSheet: Unprocessed track command %s at line %d", command.c_str(), _lineNum);
}
}
Array<CueSheet::CueFile> CueSheet::files() {
return _files;
}
Array<CueSheet::CueTrack> CueSheet::tracks() {
return _tracks;
}
CueSheet::CueTrack *CueSheet::getTrack(int tracknum) {
for (uint i = 0; i < _tracks.size(); i++) {
if (_tracks[i].number == tracknum) {
return &_tracks[i];
}
}
return nullptr;
}
CueSheet::CueTrack *CueSheet::getTrackAtFrame(int frame) {
for (uint i = 0; i < _tracks.size(); i++) {
// Inside pregap
if (_tracks[i].indices[0] >= 0 && frame >= _tracks[i].indices[0] && frame < _tracks[i].indices.back()) {
debug(5, "CueSheet::getTrackAtFrame: Returning track %i (pregap)", _tracks[i].number);
return &_tracks[i];
}
// Between index 1 and the start of the subsequent track.
// Index 0 of the next track is the start of its pregap.
// For tracks which have pregaps, we want to use that as the
// frame to determine the edge of the track; otherwise, we'll
// need to use the start of index 1, eg the start of the track.
if (i+1 < _tracks.size()) {
int nextIndex;
CueSheet::CueTrack nextTrack = _tracks[i+1];
// If there's more than one index, *and* index 0 looks
// nullish, use index 1
if (nextTrack.indices.size() > 1 && nextTrack.indices[0] == -1) {
nextIndex = nextTrack.indices[1];
// Otherwise, use index 0
} else {
nextIndex = nextTrack.indices[0];
}
if (frame >= _tracks[i].indices.back() && frame < nextIndex) {
debug(5, "CueSheet::getTrackAtFrame: Returning track %i (inside content)", _tracks[i].number);
return &_tracks[i];
}
}
}
// Not found within any tracks, but could be in the final track.
// Note: this looks weird, but is correct (or correct-ish).
// The cuesheet tracks the *starting* index of a song, but not
// the *duration* of a track. Without having access to the raw
// disc data, we don't actually know how long this track is.
// As a result, if we see any frame that comes after the
// start of the final track, we need to assume it's
// a part of that track.
if (frame > _tracks.back().indices.back()) {
debug(5, "CueSheet::getTrackAtFrame: Returning final track");
return &_tracks.back();
}
// Still not found; could indicate a gap between indices
warning("CueSheet::getTrackAtFrame: Not returning a track; does the cuesheet have a gap between indices?");
return nullptr;
}
} // End of namespace Common

126
common/formats/cue.h Normal file
View File

@@ -0,0 +1,126 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COMMON_FORMATS_CUE_H
#define COMMON_FORMATS_CUE_H
#include "common/array.h"
namespace Common {
class SeekableReadStream;
/**
* A class able to load and parse Cue sheets
*/
class CueSheet {
public:
CueSheet(const char *sheet);
CueSheet(SeekableReadStream *stream);
public:
enum FileType {
kFileTypeBinary,
kFileTypeAIFF,
kFileTypeWave,
kFileTypeMP3,
kFileTypeMotorola,
};
enum TrackType {
kTrackTypeAudio, // Audio (sector size: 2352)
kTrackTypeCDG, // Karaoke CD+G (sector size: 2448)
kTrackTypeMode1_Raw, // CD-ROM Mode 1 data (raw) (sector size: 2352), used by cdrdao
kTrackTypeMode1_2048, // CD-ROM Mode 1 data (cooked) (sector size: 2048)
kTrackTypeMode1_2352, // CD-ROM Mode 1 data (raw) (sector size: 2352)
kTrackTypeMode2_Raw, // CD-ROM Mode 2 data (raw) (sector size: 2352), used by cdrdao
kTrackTypeMode2_2048, // CD-ROM Mode 2 XA form-1 data (sector size: 2048)
kTrackTypeMode2_2324, // CD-ROM Mode 2 XA form-2 data (sector size: 2324)
kTrackTypeMode2_2366, // CD-ROM Mode 2 data (sector size: 2336)
kTrackTypeMode2_2352, // CD-ROM Mode 2 data (raw) (sector size: 2352)
kTrackTypeCDI_2336, // CDI Mode 2 data
kTrackTypeCDI_2352, // CDI Mode 2 data
};
enum TrackFlags {
kTrackFlag4ch = 1 << 0, // Four channel audio
kTrackFlagDCP = 1 << 1, // Digital copy permitted
kTrackFlagPre = 1 << 2, // Pre-emphasis enabled, for audio tracks only
kTrackFlagSCMS = 1 << 3, // Serial copy management system
};
struct CueFile {
String name;
FileType type = kFileTypeBinary;
};
struct CueTrack {
int number = 0;
CueFile file;
TrackType type;
String title;
String performer;
Array<int> indices;
int pregap = 0;
uint32 flags;
int size = 2352;
};
struct CueMetadata {
String title;
String date;
String genre;
String performer;
String catalog;
};
struct LookupTable;
Array<CueFile> files();
Array<CueTrack> tracks();
CueTrack *getTrack(int tracknum);
CueTrack *getTrackAtFrame(int frame);
private:
void parse(const char *sheet);
int lookupInTable(const LookupTable *table, const char *key);
int parseMSF(const char *str);
void parseHeaderContext(const char *line);
void parseFilesContext(const char *line);
void parseTracksContext(const char *line);
private:
int _lineNum = 0;
int _context;
int _currentFile = -1;
int _currentTrack = -1;
Array<CueFile> _files;
Array<CueTrack> _tracks;
CueMetadata _metadata;
};
} // End of namespace Common
#endif

View File

@@ -0,0 +1,961 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/stream.h"
#include "common/substream.h"
#include "common/memstream.h"
#include "common/md5.h"
#include "common/algorithm.h"
#include "common/bitstream.h"
#include "common/formats/disk_image.h"
namespace Common {
const uint kNibTrackLen = 256 * 26;
static bool detectDOS33(Common::SeekableReadStream &f, uint size) {
uint count = 0;
uint dos32 = 0, dos33 = 0;
uint32 window = 0;
while (count++ < size) {
window &= 0xffff;
window <<= 8;
window |= f.readByte();
if (f.err() || f.eos())
return false;
if (window == 0xd5aa96)
++dos33;
else if (window == 0xd5aab5)
++dos32;
}
return dos33 > dos32;
}
static bool readSector_NIB(byte outBuf[], uint outBufSize, const byte inBuf[], uint inBufSize, uint &pos, const byte minNibble, const byte lookup[], const uint track, const uint sector) {
uint z = inBufSize - (pos % inBufSize);
if (z < outBufSize) {
memcpy(outBuf, inBuf + (pos % inBufSize), z);
memcpy(outBuf + z, inBuf, outBufSize - z);
} else
memcpy(outBuf, inBuf + (pos % inBufSize), outBufSize);
pos += outBufSize;
byte oldVal = 0;
for (uint n = 0; n < outBufSize; ++n) {
// expand
if (outBuf[n] == 0xd5) {
// Early end of block.
pos -= (outBufSize - n);
debug(2, "NIB: early end of block @ %x (%d, %d)", n, track, sector);
return false;
}
byte val = 0x40;
if (outBuf[n] >= minNibble)
val = lookup[outBuf[n] - minNibble];
if (val == 0x40) {
// Badly-encoded nibbles, stop trying to decode here.
pos -= (outBufSize - n);
debug(2, "NIB: bad nibble %02x @ %x (%d, %d)", outBuf[n], n, track, sector);
return false;
}
// undo checksum
oldVal = val ^ oldVal;
outBuf[n] = oldVal;
}
byte checksum = inBuf[pos++ % inBufSize];
if (checksum < minNibble || oldVal != lookup[checksum - minNibble]) {
debug(2, "NIB: checksum mismatch @ (%d, %d)", track, sector);
return false;
}
return true;
}
// 4-and-4 encoding (odd-even)
static uint8 read44(byte *buffer, uint size, uint &pos) {
// 1s in the other fields, so we can just AND
uint8 ret = buffer[pos++ % size];
return ((ret << 1) | 1) & buffer[pos++ % size];
}
static bool decodeTrack(Common::SeekableReadStream &stream, uint trackLen, bool dos33, byte *const diskImage, uint tracks, Common::BitArray &goodSectors) {
// starting at 0xaa, 64 is invalid (see below)
const byte c_5and3_lookup[] = { 64, 0, 64, 1, 2, 3, 64, 64, 64, 64, 64, 4, 5, 6, 64, 64, 7, 8, 64, 9, 10, 11, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 12, 13, 64, 64, 14, 15, 64, 16, 17, 18, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 19, 20, 64, 21, 22, 23, 64, 64, 64, 64, 64, 24, 25, 26, 64, 64, 27, 28, 64, 29, 30, 31 };
// starting at 0x96, 64 is invalid (see below)
const byte c_6and2_lookup[] = { 0, 1, 64, 64, 2, 3, 64, 4, 5, 6, 64, 64, 64, 64, 64, 64, 7, 8, 64, 64, 64, 9, 10, 11, 12, 13, 64, 64, 14, 15, 16, 17, 18, 19, 64, 20, 21, 22, 23, 24, 25, 26, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 27, 64, 28, 29, 30, 64, 64, 64, 31, 64, 64, 32, 33, 64, 34, 35, 36, 37, 38, 39, 40, 64, 64, 64, 64, 64, 41, 42, 43, 64, 44, 45, 46, 47, 48, 49, 50, 64, 64, 51, 52, 53, 54, 55, 56, 64, 57, 58, 59, 60, 61, 62, 63 };
const uint sectorsPerTrack = (dos33 ? 16 : 13);
const uint bytesPerSector = 256;
bool sawAddress = false;
uint8 volNo = 0, track = 0, sector = 0;
byte *const buffer = (byte *)malloc(trackLen);
uint firstGoodTrackPos = 0;
uint pos = 0;
if (stream.read(buffer, trackLen) < trackLen) {
free(buffer);
return false;
}
while (true) {
if (pos >= trackLen + firstGoodTrackPos)
break;
// Read until we find two sync bytes.
if (buffer[pos++ % trackLen] != 0xd5 || buffer[pos++ % trackLen] != 0xaa)
continue;
byte prologue = buffer[pos++ % trackLen];
if (sawAddress && prologue == (dos33 ? 0x96 : 0xb5)) {
sawAddress = false;
}
if (!sawAddress) {
sawAddress = true;
// We should always find the address field first.
if (prologue != (dos33 ? 0x96 : 0xb5)) {
// Accept a DOS 3.3(?) header at the start.
if (prologue != (dos33 ? 0xb5 : 0x96) && prologue != 0xad && prologue != 0xfd)
debug(2, "NIB: unknown field prologue %02x", prologue);
sawAddress = false;
continue;
}
volNo = read44(buffer, trackLen, pos);
track = read44(buffer, trackLen, pos);
sector = read44(buffer, trackLen, pos);
uint8 checksum = read44(buffer, trackLen, pos);
if ((volNo ^ track ^ sector) != checksum) {
debug(2, "NIB: invalid checksum (volNo %d, track %d, sector %d)", volNo, track, sector);
sawAddress = false;
continue;
}
if (track >= tracks || sector >= sectorsPerTrack) {
debug(2, "NIB: sector out of bounds @ (%d, %d)", track, sector);
sawAddress = false;
continue;
}
if (!firstGoodTrackPos)
firstGoodTrackPos = pos;
// Epilogue is de/aa plus a gap, but we don't care.
continue;
}
sawAddress = false;
// We should always find the data field after an address field.
// TODO: we ignore volNo?
byte *output = diskImage + (track * sectorsPerTrack + sector) * bytesPerSector;
if (dos33) {
// We hardcode the DOS 3.3 mapping here. TODO: Do we also need raw/prodos?
int raw2dos[16] = { 0, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 15 };
sector = raw2dos[sector];
output = diskImage + (track * sectorsPerTrack + sector) * bytesPerSector;
byte inbuffer[342];
if (!readSector_NIB(inbuffer, sizeof(inbuffer), buffer, trackLen, pos, 0x96, c_6and2_lookup, track, sector))
continue;
for (uint n = 0; n < 256; ++n) {
output[n] = inbuffer[86 + n] << 2;
if (n < 86) { // use first pair of bits
output[n] |= ((inbuffer[n] & 1) << 1);
output[n] |= ((inbuffer[n] & 2) >> 1);
} else if (n < 86*2) { // second pair
output[n] |= ((inbuffer[n-86] & 4) >> 1);
output[n] |= ((inbuffer[n-86] & 8) >> 3);
} else { // third pair
output[n] |= ((inbuffer[n-86*2] & 0x10) >> 3);
output[n] |= ((inbuffer[n-86*2] & 0x20) >> 5);
}
}
} else {
// 5-and-3 uses 410 on-disk bytes, decoding to just over 256 bytes
byte inbuffer[410];
if (!readSector_NIB(inbuffer, sizeof(inbuffer), buffer, trackLen, pos, 0xaa, c_5and3_lookup, track, sector))
continue;
// 8 bytes of nibbles expand to 5 bytes
// so we have 51 of these batches (255 bytes), plus 2 bytes of 'leftover' nibbles for byte 256
for (uint n = 0; n < 51; ++n) {
// e.g. figure 3.18 of Beneath Apple DOS
byte lowbits1 = inbuffer[51*3 - n];
byte lowbits2 = inbuffer[51*2 - n];
byte lowbits3 = inbuffer[51*1 - n];
byte lowbits4 = (lowbits1 & 2) << 1 | (lowbits2 & 2) | (lowbits3 & 2) >> 1;
byte lowbits5 = (lowbits1 & 1) << 2 | (lowbits2 & 1) << 1 | (lowbits3 & 1);
output[250 - 5*n] = (inbuffer[n + 51*3 + 1] << 3) | ((lowbits1 >> 2) & 0x7);
output[251 - 5*n] = (inbuffer[n + 51*4 + 1] << 3) | ((lowbits2 >> 2) & 0x7);
output[252 - 5*n] = (inbuffer[n + 51*5 + 1] << 3) | ((lowbits3 >> 2) & 0x7);
output[253 - 5*n] = (inbuffer[n + 51*6 + 1] << 3) | lowbits4;
output[254 - 5*n] = (inbuffer[n + 51*7 + 1] << 3) | lowbits5;
}
output[255] = (inbuffer[409] << 3) | (inbuffer[0] & 0x7);
}
goodSectors.set(track * sectorsPerTrack + sector);
}
free(buffer);
return true;
}
static void printGoodSectors(const Common::BitArray &goodSectors, uint sectorsPerTrack) {
if (gDebugLevel < 1) {
return;
}
bool foundBadSector = false;
for (uint i = 0; i < goodSectors.size(); ++i) {
if (!goodSectors.get(i)) {
if (!foundBadSector) {
debugN(1, "Bad/missing sectors:");
foundBadSector = true;
}
debugN(1, " (%d, %d)", i / sectorsPerTrack, i % sectorsPerTrack);
}
}
if (foundBadSector) {
debugN(1, "\n");
}
}
static bool readImage_NIB(
const char *filename,
Common::SeekableReadStream &f,
byte *diskImage,
bool dos33,
uint startTrack = 0,
uint tracksToRead = 35) {
if (f.size() != 35 * kNibTrackLen) {
warning("NIB: image '%s' has invalid size of %d bytes", filename, (int)f.size());
return false;
}
const uint sectorsPerTrack = (dos33 ? 16 : 13);
Common::BitArray goodSectors(35 * sectorsPerTrack);
f.seek(startTrack * kNibTrackLen);
uint endTrack = startTrack + tracksToRead - 1;
for (uint track = startTrack; track <= endTrack; ++track) {
if (!decodeTrack(f, kNibTrackLen, dos33, diskImage, 35, goodSectors)) {
warning("NIB: error decoding track %d in '%s'", track, filename);
return false;
}
}
printGoodSectors(goodSectors, sectorsPerTrack);
return true;
}
static Common::SeekableReadStream *readImageToStream_NIB(Common::File &f, bool dos33, uint tracks = 35) {
const uint sectorsPerTrack = (dos33 ? 16 : 13);
const uint imageSize = tracks * sectorsPerTrack * 256;
byte *const diskImage = (byte *)calloc(imageSize, 1);
if (!readImage_NIB(f.getName(), f, diskImage, dos33, 0, tracks)) {
warning("NIB: error reading '%s'", f.getName());
free(diskImage);
return nullptr;
}
return new Common::MemoryReadStream(diskImage, imageSize, DisposeAfterUse::YES);
}
static int getVersion_WOZ(const char *filename, Common::SeekableReadStream &f) {
f.seek(0);
const uint32 fileId = f.readUint32BE();
if (f.eos() || f.err()) {
warning("WOZ: error reading '%s'", filename);
return 0;
}
if (fileId == MKTAG('W', 'O', 'Z', '1'))
return 1;
else if (fileId == MKTAG('W', 'O', 'Z', '2'))
return 2;
warning("WOZ: unsupported ID '%s' found in '%s'", tag2str(fileId), filename);
return 0;
}
static Common::SeekableReadStream *readTrack_WOZ(const char *filename, Common::SeekableReadStream &f, uint track, bool woz2) {
f.seek(88 + track * 4);
const byte index = f.readByte();
if (index == 0xff) {
warning("WOZ: track %u not found in '%s', skipping", track, filename);
return nullptr;
}
uint32 offset, byteSize, bitSize;
if (woz2) {
f.seek(256 + index * 8);
offset = f.readUint16LE() << 9;
byteSize = f.readUint16LE() << 9;
bitSize = f.readUint32LE();
} else {
offset = 256 + index * 6656;
f.seek(offset + 6646);
byteSize = f.readUint16LE();
bitSize = f.readUint16LE();
}
f.seek(offset);
if (f.eos() || f.err() || byteSize == 0) {
warning("WOZ: failed to read track %u in '%s', aborting", track, filename);
return nullptr;
}
byte *inBuf = (byte *)malloc(byteSize);
byte *outBuf = (byte *)malloc(byteSize);
uint32 outSize = 0;
if (!inBuf || !outBuf) {
warning("WOZ: failed to create buffers of size %u for track %u in '%s'", byteSize, track, filename);
free(inBuf);
free(outBuf);
return nullptr;
}
if (f.read(inBuf, byteSize) < byteSize) {
warning("WOZ: error reading track %u in '%s'", track, filename);
free(inBuf);
free(outBuf);
return nullptr;
}
Common::BitStreamMemory8MSB bitStream(new Common::BitStreamMemoryStream(inBuf, byteSize, DisposeAfterUse::YES), DisposeAfterUse::YES);
byte nibble = 0;
bool stop = false;
for (;;) {
nibble = (nibble << 1) | bitStream.getBit();
if (nibble & 0x80) {
if (stop)
break;
nibble = 0;
}
if (bitStream.pos() == bitSize) {
bitStream.rewind();
if (stop) {
warning("WOZ: failed to find sync point for track %u in '%s'", track, filename);
break;
}
stop = true;
}
}
nibble = 0;
uint32 bitsRead = 0;
do {
nibble = (nibble << 1) | bitStream.getBit();
++bitsRead;
if (nibble & 0x80) {
outBuf[outSize++] = nibble;
nibble = 0;
}
if (bitStream.pos() == bitSize)
bitStream.rewind();
} while (bitsRead < bitSize);
if (nibble != 0)
warning("WOZ: failed to sync track %u in '%s'", track, filename);
if (outSize == 0) {
warning("WOZ: track %u in '%s' is empty", track, filename);
free(outBuf);
return nullptr;
}
return new Common::MemoryReadStream(outBuf, outSize, DisposeAfterUse::YES);
}
static bool readImage_WOZ(
const char *filename,
Common::SeekableReadStream &f,
byte *diskImage,
bool dos33,
uint startTrack = 0,
uint tracksToRead = 35) {
int version = getVersion_WOZ(filename, f);
if (version == 0) {
return false;
}
const uint sectorsPerTrack = (dos33 ? 16 : 13);
Common::BitArray goodSectors(35 * sectorsPerTrack);
uint endTrack = startTrack + tracksToRead - 1;
for (uint track = startTrack; track <= endTrack; ++track) {
StreamPtr stream(readTrack_WOZ(filename, f, track, version == 2));
if (stream) {
if (!decodeTrack(*stream, stream->size(), dos33, diskImage, 35, goodSectors)) {
warning("WOZ: error decoding track %d in '%s'", track, filename);
return false;
}
}
}
printGoodSectors(goodSectors, sectorsPerTrack);
return true;
}
static Common::SeekableReadStream *readImageToStream_WOZ(Common::File &f, bool dos33, uint tracks = 35) {
const uint sectorsPerTrack = (dos33 ? 16 : 13);
const uint imageSize = tracks * sectorsPerTrack * 256;
byte *const diskImage = (byte *)calloc(imageSize, 1);
if (!readImage_WOZ(f.getName(), f, diskImage, dos33, 0, tracks)) {
warning("WOZ: error reading '%s'", f.getName());
free(diskImage);
return nullptr;
}
return new Common::MemoryReadStream(diskImage, imageSize, DisposeAfterUse::YES);
}
bool DiskImage::open(const Common::Path &filename) {
Common::File *file = new Common::File();
debug(1, "Opening '%s'", filename.toString(Common::Path::kNativeSeparator).c_str());
if (file->open(filename)) {
if (open(filename.baseName(), file)) {
return true;
}
} else {
warning("Failed to open '%s'", filename.toString(Common::Path::kNativeSeparator).c_str());
}
delete file;
return false;
}
bool DiskImage::open(const Common::FSNode &node) {
Common::File *file = new Common::File();
debug(1, "Opening '%s'", node.getPath().toString(Common::Path::kNativeSeparator).c_str());
if (file->open(node)) {
if (open(node.getFileName(), file)) {
return true;
}
} else {
warning("Failed to open '%s'", node.getPath().toString(Common::Path::kNativeSeparator).c_str());
}
delete file;
return false;
}
bool DiskImage::open(const Common::String &name, Common::File *f) {
if (name.hasSuffixIgnoreCase(".dsk") ||
name.hasSuffixIgnoreCase(".do")) {
_tracks = 35;
_sectorsPerTrack = 16;
_bytesPerSector = 256;
_inputStream = f;
} else if (name.hasSuffixIgnoreCase(".d13")) {
_tracks = 35;
_sectorsPerTrack = 13;
_bytesPerSector = 256;
_inputStream = f;
} else if (name.hasSuffixIgnoreCase(".nib")) {
_tracks = 35;
if (detectDOS33(*f, kNibTrackLen))
_sectorsPerTrack = 16;
else
_sectorsPerTrack = 13;
_bytesPerSector = 256;
if (_lazyDecoding) {
// store the file stream and create an empty decode stream.
// tracks will be decoded into the decode stream as they're read.
uint32 imageSize = _tracks * _sectorsPerTrack * _bytesPerSector;
_inputStream = f;
_decodeBuffer = (byte *)calloc(imageSize, 1);
_decodeStream = new Common::MemoryReadStream(_decodeBuffer, imageSize, DisposeAfterUse::YES);
_decodedTracks.set_size(_tracks);
_encoding = DiskImageEncodingNib;
} else {
// decode the entire image
_inputStream = readImageToStream_NIB(*f, (_sectorsPerTrack == 16));
}
} else if (name.hasSuffixIgnoreCase(".woz")) {
_tracks = 35;
_sectorsPerTrack = 13;
_bytesPerSector = 256;
int version = getVersion_WOZ(name.c_str(), *f);
if (version > 0) {
StreamPtr bitStream(readTrack_WOZ(name.c_str(), *f, 0, version == 2));
if (bitStream) {
if (detectDOS33(*bitStream, bitStream->size()))
_sectorsPerTrack = 16;
if (_lazyDecoding) {
// store the file stream and create an empty decode stream.
// tracks will be decoded into the decode stream as they're read.
uint32 imageSize = _tracks * _sectorsPerTrack * _bytesPerSector;
_inputStream = f;
_decodeBuffer = (byte *)calloc(imageSize, 1);
_decodeStream = new Common::MemoryReadStream(_decodeBuffer, imageSize, DisposeAfterUse::YES);
_decodedTracks.set_size(_tracks);
_encoding = DiskImageEncodingWoz;
} else {
// decode the entire image
_inputStream = readImageToStream_WOZ(*f, (_sectorsPerTrack == 16), _tracks);
}
} else {
warning("WOZ: failed to load bitstream for track 0 in '%s'", name.c_str());
}
}
} else if (name.hasSuffixIgnoreCase(".xfd")) {
_tracks = 40;
_sectorsPerTrack = 18;
_bytesPerSector = 128;
_inputStream = f;
} else if (name.hasSuffixIgnoreCase(".img")) {
_tracks = 40;
_sectorsPerTrack = 8;
_bytesPerSector = 512;
_firstSector = 1;
_inputStream = f;
}
if (_inputStream == nullptr) {
return false;
}
int expectedSize = _tracks * _sectorsPerTrack * _bytesPerSector;
if (getDiskStream()->size() != expectedSize) {
warning("Unrecognized disk image '%s' of size %d bytes (expected %d bytes)", name.c_str(), (int)getDiskStream()->size(), expectedSize);
if (_inputStream != f) {
delete _inputStream;
}
_inputStream = nullptr;
delete _decodeStream;
_decodeStream = nullptr;
return false;
};
if (_inputStream != f) {
delete f;
}
_name = name;
return true;
}
const DataBlockPtr DiskImage::getDataBlock(uint track, uint sector, uint offset, uint size) const {
return DataBlockPtr(new DiskImage::DataBlock(this, track, sector, offset, size, _sectorLimit));
}
Common::SeekableReadStream *DiskImage::createReadStream(uint track, uint sector, uint offset, uint size, uint sectorLimit) const {
const uint bytesToRead = size * _bytesPerSector + _bytesPerSector - offset;
byte *const data = (byte *)malloc(bytesToRead);
uint dataOffset = 0;
if (sectorLimit == 0)
sectorLimit = _sectorsPerTrack;
if (sector < _firstSector || sector >= sectorLimit + _firstSector) {
warning("Sector %u is out of bounds for %u-sector %u-based reading", sector, sectorLimit, _firstSector);
free(data);
return nullptr;
}
// lazy decoding not supported for createReadStream(), because decoding
// tracks here requires removing way too many existing const keywords.
// if it's ever needed, enable this code and remove all those consts.
#if 0
if (_decodeBuffer != nullptr) {
// lazy decoding
uint32 bytesPerTrack = _sectorsPerTrack * _bytesPerSector;
uint32 endTrack = track + (((sector - _firstSector) * _bytesPerSector + offset + bytesToRead - 1) / bytesPerTrack);
for (uint32 t = track; t <= endTrack; t++) {
if (!_decodedTracks.get(t)) {
decodeTrack(t);
}
}
}
#endif
sector -= _firstSector;
Common::SeekableReadStream *stream = getDiskStream();
while (dataOffset < bytesToRead) {
uint bytesRemInTrack = (sectorLimit - 1 - sector) * _bytesPerSector + _bytesPerSector - offset;
stream->seek((track * _sectorsPerTrack + sector) * _bytesPerSector + offset);
if (bytesToRead - dataOffset < bytesRemInTrack)
bytesRemInTrack = bytesToRead - dataOffset;
if (stream->read(data + dataOffset, bytesRemInTrack) < bytesRemInTrack) {
warning("Error reading disk image at track %d; sector %d", track, sector);
free(data);
return nullptr;
}
++track;
sector = 0;
offset = 0;
dataOffset += bytesRemInTrack;
}
return new Common::MemoryReadStream(data, bytesToRead, DisposeAfterUse::YES);
}
Common::SeekableReadStream *DiskImage::releaseStream() {
Common::SeekableReadStream *stream = getDiskStream();
// reset class
if (stream != _inputStream) {
delete _inputStream;
}
_inputStream = nullptr;
_decodeStream = nullptr;
_decodeBuffer = nullptr;
_decodedTracks.clear();
_encoding = DiskImageEncodingNone;
_lazyDecoding = false;
_tracks = 0;
_sectorsPerTrack = 0;
_bytesPerSector = 0;
_sectorLimit = 0;
_firstSector = 0;
return stream;
}
uint32 DiskImage::read(void *dataPtr, uint32 diskPosition, uint32 dataSize) {
Common::SeekableReadStream *stream;
if (_decodeBuffer != nullptr) {
// lazy decoding
uint32 bytesPerTrack = _sectorsPerTrack * _bytesPerSector;
uint32 startTrack = diskPosition / bytesPerTrack;
uint32 endTrack = (diskPosition + dataSize - 1) / bytesPerTrack;
for (uint32 t = startTrack; t <= endTrack; t++) {
if (!_decodedTracks.get(t)) {
decodeTrack(t);
}
}
stream = _decodeStream;
}
else {
stream = _inputStream;
}
stream->seek(diskPosition);
return stream->read(dataPtr, dataSize);
}
void DiskImage::decodeTrack(uint track) {
switch (_encoding) {
case DiskImageEncodingNib:
readImage_NIB(_name.c_str(), *_inputStream, _decodeBuffer, (_sectorsPerTrack == 16), track, 1);
break;
case DiskImageEncodingWoz:
readImage_WOZ(_name.c_str(), *_inputStream, _decodeBuffer, (_sectorsPerTrack == 16), track, 1);
break;
default:
return;
}
_decodedTracks.set(track);
}
int32 computeMD5(const Common::FSNode &node, Common::String &md5, uint32 md5Bytes) {
Common::File f;
if (!f.open(node))
return -1;
const uint tracks = md5Bytes / (13 * 256) + 1;
if (node.getName().matchString("*.nib", true) && f.size() == 35 * kNibTrackLen) {
bool isDOS33 = detectDOS33(f, kNibTrackLen);
Common::SeekableReadStream *stream = readImageToStream_NIB(f, isDOS33, tracks);
if (stream) {
md5 = Common::computeStreamMD5AsString(*stream, md5Bytes);
delete stream;
return 35 * (isDOS33 ? 16 : 13) * 256;
}
return -1;
} else if (node.getName().matchString("*.woz", true)) {
int version = getVersion_WOZ(f.getName(), f);
if (version > 0) {
StreamPtr nibbles(readTrack_WOZ(f.getName(), f, 0, version == 2));
if (nibbles) {
bool isDOS33 = detectDOS33(*nibbles, nibbles->size());
StreamPtr stream(readImageToStream_WOZ(f, isDOS33, tracks));
if (stream) {
md5 = Common::computeStreamMD5AsString(*stream, md5Bytes);
return 35 * (isDOS33 ? 16 : 13) * 256;
}
}
}
return -1;
} else {
md5 = Common::computeStreamMD5AsString(f, md5Bytes);
return f.size();
}
}
const DataBlockPtr Files_Plain::getDataBlock(const Common::Path &filename, uint offset) const {
return Common::SharedPtr<Files::DataBlock>(new Files::DataBlock(this, filename, offset));
}
Common::SeekableReadStream *Files_Plain::createReadStream(const Common::Path &filename, uint offset) const {
Common::File *f(new Common::File());
if (!f->open(filename))
error("Failed to open '%s'", filename.toString(Common::Path::kNativeSeparator).c_str());
if (offset == 0)
return f;
else
return new Common::SeekableSubReadStream(f, offset, f->size(), DisposeAfterUse::YES);
}
Files_AppleDOS::~Files_AppleDOS() {
delete _disk;
}
Files_AppleDOS::Files_AppleDOS() :
_disk(nullptr) {
}
void Files_AppleDOS::readSectorList(TrackSector start, Common::Array<TrackSector> &list) {
TrackSector index = start;
while (index.track != 0) {
Common::ScopedPtr<Common::SeekableReadStream> stream(_disk->createReadStream(index.track, index.sector));
stream->readByte();
index.track = stream->readByte();
index.sector = stream->readByte();
stream->seek(9, SEEK_CUR);
// This only handles sequential files
TrackSector ts;
ts.track = stream->readByte();
ts.sector = stream->readByte();
while (ts.track != 0) {
list.push_back(ts);
ts.track = stream->readByte();
ts.sector = stream->readByte();
if (stream->err())
error("Error reading sector list");
if (stream->eos())
break;
}
}
}
void Files_AppleDOS::readVTOC() {
Common::ScopedPtr<Common::SeekableReadStream> stream(_disk->createReadStream(0x11, 0x00));
stream->readByte();
byte track = stream->readByte();
if (!track) {
// VTOC probably obfuscated, try track 0x10
stream.reset(_disk->createReadStream(0x10, 0x00));
stream->readByte();
track = stream->readByte();
}
if (!track)
error("VTOC not found");
byte sector = stream->readByte();
while (track != 0) {
char name[kFilenameLen + 1] = { };
stream.reset(_disk->createReadStream(track, sector));
stream->readByte();
track = stream->readByte();
sector = stream->readByte();
stream->seek(8, SEEK_CUR);
for (uint i = 0; i < 7; ++i) {
TOCEntry entry;
TrackSector sectorList;
sectorList.track = stream->readByte();
sectorList.sector = stream->readByte();
entry.type = stream->readByte();
stream->read(name, kFilenameLen);
// Convert to ASCII
for (uint j = 0; j < kFilenameLen; j++)
name[j] &= 0x7f;
// Strip trailing spaces
for (int j = kFilenameLen - 1; j >= 0; --j) {
if (name[j] == ' ')
name[j] = 0;
else
break;
}
entry.totalSectors = stream->readUint16BE();
// 0 is empty slot, 255 is deleted file
if (sectorList.track != 0 && sectorList.track != 255) {
readSectorList(sectorList, entry.sectors);
_toc[name] = entry;
}
}
}
}
const DataBlockPtr Files_AppleDOS::getDataBlock(const Common::Path &filename, uint offset) const {
return Common::SharedPtr<Files::DataBlock>(new Files::DataBlock(this, filename, offset));
}
Common::SeekableReadStream *Files_AppleDOS::createReadStreamText(const TOCEntry &entry) const {
byte *buf = (byte *)malloc(entry.sectors.size() * kSectorSize);
byte *p = buf;
for (uint i = 0; i < entry.sectors.size(); ++i) {
Common::ScopedPtr<Common::SeekableReadStream> stream(_disk->createReadStream(entry.sectors[i].track, entry.sectors[i].sector));
assert(stream->size() == kSectorSize);
while (true) {
byte textChar = stream->readByte();
if (stream->eos() || textChar == 0)
break;
if (stream->err())
error("Error reading text file");
*p++ = textChar;
}
}
return new Common::MemoryReadStream(buf, p - buf, DisposeAfterUse::YES);
}
Common::SeekableReadStream *Files_AppleDOS::createReadStreamBinary(const TOCEntry &entry) const {
byte *buf = (byte *)malloc(entry.sectors.size() * kSectorSize);
Common::ScopedPtr<Common::SeekableReadStream> stream(_disk->createReadStream(entry.sectors[0].track, entry.sectors[0].sector));
if (entry.type == kFileTypeBinary)
stream->readUint16LE(); // Skip start address
uint16 size = stream->readUint16LE();
uint16 offset = 0;
uint16 sectorIdx = 1;
while (true) {
offset += stream->read(buf + offset, size - offset);
if (offset == size)
break;
if (stream->err())
error("Error reading binary file");
assert(stream->eos());
if (sectorIdx == entry.sectors.size())
error("Not enough sectors for binary file size");
stream.reset(_disk->createReadStream(entry.sectors[sectorIdx].track, entry.sectors[sectorIdx].sector));
++sectorIdx;
}
return new Common::MemoryReadStream(buf, size, DisposeAfterUse::YES);
}
Common::SeekableReadStream *Files_AppleDOS::createReadStream(const Common::Path &filename, uint offset) const {
if (!_toc.contains(filename))
error("Failed to locate '%s'", filename.toString().c_str());
const TOCEntry &entry = _toc[filename];
Common::SeekableReadStream *stream;
switch(entry.type) {
case kFileTypeText:
stream = createReadStreamText(entry);
break;
case kFileTypeAppleSoft:
case kFileTypeBinary:
stream = createReadStreamBinary(entry);
break;
default:
error("Unsupported file type %i", entry.type);
}
return new Common::SeekableSubReadStream(stream, offset, stream->size(), DisposeAfterUse::YES);
}
bool Files_AppleDOS::open(const Common::Path &filename) {
_disk = new DiskImage();
if (!_disk->open(filename))
return false;
readVTOC();
return true;
}
} // End of namespace Common

232
common/formats/disk_image.h Normal file
View File

@@ -0,0 +1,232 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/bitarray.h"
#include "common/ptr.h"
#include "common/file.h"
#include "common/debug.h"
#ifndef COMMON_FORMATS_DISK_IMAGE_H
#define COMMON_FORMATS_DISK_IMAGE_H
namespace Common {
// Disk image parsers / decoders
//
// These classes handle floppy disk image files. Multiple formats are supported.
// An image's file name extension determines its format. DiskImage::open selects
// the format and expected disk size. Data can be read by track/sector/offset
// or by the calculated stream position. Several file systems can also be read.
//
// Supported image formats:
// .do Apple II disk sectors. 35 tracks, 16 sectors, no encoding.
// .dsk Same as .do. Note that alternative sector orders are not handled.
// Currently, if clients want to support images with a different sector
// order, then they will have to detect and handle it themselves.
// .d13 Apple II disk sectors. 35 tracks, 13 sectors, no encoding.
// .nib Apple II disk nibbles. 35 tracks, 13 or 16 sectors, nibble-encoded.
// .woz Apple II comprehensive disk bitstream. 35 tracks, 13 or 16 sectors.
// This encoding format takes a noticeable amount of time to decode.
// .img PC disk sectors. 40 tracks, 8 sectors, no encoding.
// .xfd Atari disk sectors. 40 tracks, 18 sectors, no encoding.
//
// For encoded formats, the default behavior is to decode every track when
// opening an image. Lazy decoding can also be enabled, causing tracks to be
// decoded when they are first accessed. This can significantly speed up access
// if the format is expensive to decode but only a little data is needed.
//
// This code was originally part of the ADL engine.
class SeekableReadStream;
class String;
/**
* Disk image formats that require decoding to read their sectors
*/
enum DiskImageEncoding {
DiskImageEncodingNone,
DiskImageEncodingNib,
DiskImageEncodingWoz
};
// Used for disk image detection
int32 computeMD5(const Common::FSNode &node, Common::String &md5, uint32 md5Bytes);
class DataBlock {
public:
virtual ~DataBlock() { }
virtual Common::SeekableReadStream *createReadStream() const = 0;
};
typedef Common::SharedPtr<DataBlock> DataBlockPtr;
typedef Common::ScopedPtr<Common::SeekableReadStream> StreamPtr;
class Files {
public:
virtual ~Files() { }
virtual const DataBlockPtr getDataBlock(const Common::Path &filename, uint offset = 0) const = 0;
virtual Common::SeekableReadStream *createReadStream(const Common::Path &filename, uint offset = 0) const = 0;
virtual bool exists(const Common::Path &filename) const = 0;
protected:
class DataBlock : public Common::DataBlock {
public:
DataBlock(const Files *files, const Common::Path &filename, uint offset) :
_files(files),
_filename(filename),
_offset(offset) { }
Common::SeekableReadStream *createReadStream() const override {
return _files->createReadStream(_filename, _offset);
}
private:
const Common::Path _filename;
uint _offset;
const Files *_files;
};
};
class DiskImage {
public:
DiskImage() :
_inputStream(nullptr),
_decodeStream(nullptr),
_decodeBuffer(nullptr),
_encoding(DiskImageEncodingNone),
_lazyDecoding(false),
_tracks(0),
_sectorsPerTrack(0),
_bytesPerSector(0),
_sectorLimit(0),
_firstSector(0) { }
~DiskImage() {
delete _inputStream;
delete _decodeStream; // frees _decodeBuffer
}
bool open(const Common::Path &filename);
bool open(const Common::FSNode &node);
const DataBlockPtr getDataBlock(uint track, uint sector, uint offset = 0, uint size = 0) const;
Common::SeekableReadStream *createReadStream(uint track, uint sector, uint offset = 0, uint size = 0, uint sectorsUsed = 0) const;
Common::SeekableReadStream *releaseStream();
Common::SeekableReadStream *getDiskStream() const { return _decodeBuffer ? _decodeStream : _inputStream; }
uint32 read(void *dataPtr, uint32 diskPosition, uint32 dataSize);
void setLazyDecoding(bool lazyDecoding) { _lazyDecoding = lazyDecoding; }
void setSectorLimit(uint sectorLimit) { _sectorLimit = sectorLimit; } // Maximum number of sectors to read per track before stepping
uint getBytesPerSector() const { return _bytesPerSector; }
uint getSectorsPerTrack() const { return _sectorsPerTrack; }
uint getTracks() const { return _tracks; }
protected:
class DataBlock : public Common::DataBlock {
public:
DataBlock(const DiskImage *disk, uint track, uint sector, uint offset, uint size, uint sectorLimit) :
_track(track),
_sector(sector),
_offset(offset),
_size(size),
_sectorLimit(sectorLimit),
_disk(disk) { }
Common::SeekableReadStream *createReadStream() const override {
return _disk->createReadStream(_track, _sector, _offset, _size, _sectorLimit);
}
private:
uint _track, _sector, _offset, _size;
uint _sectorLimit;
const DiskImage *_disk;
};
Common::String _name;
Common::SeekableReadStream *_inputStream;
Common::SeekableReadStream *_decodeStream;
byte *_decodeBuffer;
Common::BitArray _decodedTracks;
DiskImageEncoding _encoding;
bool _lazyDecoding;
uint _tracks, _sectorsPerTrack, _bytesPerSector, _firstSector;
uint _sectorLimit;
private:
bool open(const Common::String &name, Common::File *f);
void decodeTrack(uint track);
};
// Data in plain files
class Files_Plain : public Files {
public:
const DataBlockPtr getDataBlock(const Common::Path &filename, uint offset = 0) const override;
Common::SeekableReadStream *createReadStream(const Common::Path &filename, uint offset = 0) const override;
bool exists(const Common::Path &filename) const override { return Common::File::exists(filename); }
};
// Data in files contained in Apple DOS 3.3 disk image
class Files_AppleDOS : public Files {
public:
Files_AppleDOS();
~Files_AppleDOS() override;
bool open(const Common::Path &filename);
const DataBlockPtr getDataBlock(const Common::Path &filename, uint offset = 0) const override;
Common::SeekableReadStream *createReadStream(const Common::Path &filename, uint offset = 0) const override;
bool exists(const Common::Path &filename) const override { return _toc.contains(filename); }
private:
enum FileType {
kFileTypeText = 0,
kFileTypeAppleSoft = 2,
kFileTypeBinary = 4
};
enum {
kSectorSize = 256,
kFilenameLen = 30
};
struct TrackSector {
byte track;
byte sector;
};
struct TOCEntry {
byte type;
uint16 totalSectors;
Common::Array<TrackSector> sectors;
};
void readVTOC();
void readSectorList(TrackSector start, Common::Array<TrackSector> &list);
Common::SeekableReadStream *createReadStreamText(const TOCEntry &entry) const;
Common::SeekableReadStream *createReadStreamBinary(const TOCEntry &entry) const;
DiskImage *_disk;
Common::HashMap<Common::Path, TOCEntry, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> _toc;
};
} // End of namespace Common
#endif

View File

@@ -0,0 +1,152 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/formats/formatinfo.h"
#include "common/translation.h"
#include "common/util.h"
#include "image/bmp.h"
#include "image/png.h"
namespace Common {
namespace FormatInfo {
struct FormatTypeInfo {
const char *_shortExtension; ///< Extension limited to 3 characters
const char *_longExtension; ///< Extension with no length limit
};
const FormatTypeInfo g_formatTypeInfo[kCount] = {
{nullptr, nullptr},
// Image formats
{"bmp", nullptr},
{"png", nullptr},
{"jpg", nullptr},
{"pct", "pict"},
// Binary formats
{"application/octet-stream", nullptr},
};
ImageFormatCharacteristics::ImageFormatCharacteristics()
: _lossiness(kLossinessUnknown), _supportAlpha(false), _supportPalette(false), _maxBitDepth(0) {
}
ImageSaveProperties::ImageSaveProperties() : _qualityLevel(75) {
}
bool getImageFormatCharacteristics(FormatID format, ImageFormatCharacteristics &outCharacteristics) {
outCharacteristics = ImageFormatCharacteristics();
switch (format) {
case kPICT:
outCharacteristics._supportPalette = true;
outCharacteristics._maxBitDepth = 24;
outCharacteristics._lossiness = kLossinessEither;
break;
case kBMP:
outCharacteristics._supportPalette = true;
outCharacteristics._maxBitDepth = 24;
outCharacteristics._lossiness = kLossinessLossless;
break;
case kPNG:
outCharacteristics._supportPalette = true;
outCharacteristics._supportAlpha = true;
outCharacteristics._maxBitDepth = 32;
outCharacteristics._lossiness = kLossinessLossless;
break;
case kJPEG:
outCharacteristics._maxBitDepth = 24;
outCharacteristics._lossiness = kLossinessLossy;
break;
default:
return false;
}
return true;
}
const char *getFormatExtension(Common::FormatInfo::FormatID format, bool limitTo3Characters) {
const FormatTypeInfo &typeInfo = g_formatTypeInfo[format];
if (!limitTo3Characters && typeInfo._longExtension != nullptr)
return typeInfo._longExtension;
return typeInfo._shortExtension;
}
Common::U32String getFormatSaveDescription(Common::FormatInfo::FormatID format) {
switch (format) {
case kBMP:
return _("Bitmap Image File");
case kPNG:
return _("PNG Image File");
case kJPEG:
return _("JPEG Image File");
case kPICT:
return _("QuickDraw PICT Image File");
case kUntypedBinary:
default:
return _("Data File");
}
}
#ifdef USE_PNG
static bool savePNG(Common::WriteStream &stream, const Graphics::Surface &surf, const byte *palette, const ImageSaveProperties &props) {
return Image::writePNG(stream, surf, palette);
}
#endif
static bool saveBMP(Common::WriteStream &stream, const Graphics::Surface &surf, const byte *palette, const ImageSaveProperties &props) {
return Image::writeBMP(stream, surf, palette);
}
ImageSaveCallback_t getImageSaveFunction(FormatID format) {
switch (format) {
#ifdef USE_PNG
case kPNG:
return savePNG;
#endif
case kBMP:
return saveBMP;
default:
return nullptr;
}
}
FormatSupportLevel getFormatSupportLevel(Common::FormatInfo::FormatID format) {
switch (format) {
case kPICT:
return kFormatSupportLevelAvoid;
case kPNG:
case kJPEG:
return kFormatSupportLevelPreferred;
default:
return kFormatSupportLevelDefault;
}
}
} // End of namespace FormatInfo
} // End of namespace Common

119
common/formats/formatinfo.h Normal file
View File

@@ -0,0 +1,119 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COMMON_FORMATS_FORMATINFO_H
#define COMMON_FORMATS_FORMATINFO_H
#include "common/str.h"
#include "image/bmp.h"
namespace Graphics {
struct Surface;
} // End of namespace Graphics
namespace Common {
namespace FormatInfo {
enum Lossiness {
kLossinessUnknown,
kLossinessLossless,
kLossinessLossy,
kLossinessEither,
};
/**
* Values representing support for saving or sharing file types on the system.
*/
enum FormatSupportLevel {
kFormatSupportLevelNone, ///< Not supported at all
kFormatSupportLevelAvoid, ///< Can be saved/loaded but most likely unsupported even by third-party tools
kFormatSupportLevelDefault, ///< Not natively supported by the operating system, support by third-party tools is either available or unknown
kFormatSupportLevelSupported, ///< Well-supported by the operating system
kFormatSupportLevelPreferred, ///< Supported by operating system, preferred over other formats if lossless conversion is possible
};
enum FormatID {
kFormatUnknown,
// Image formats
kBMP, kFirstImageFormat = kBMP,
kPNG,
kJPEG,
kPICT, // Macintosh PICT
// Binary formats
kUntypedBinary, kFirstBinaryFormat = kUntypedBinary,
// End of list
kCount,
kLastImageFormat = kFirstBinaryFormat - 1,
kLastBinaryFormat = kCount - 1,
};
struct ImageFormatCharacteristics {
ImageFormatCharacteristics();
Lossiness _lossiness;
bool _supportAlpha;
bool _supportPalette;
uint _maxBitDepth;
};
struct ImageSaveProperties {
ImageSaveProperties();
uint8 _qualityLevel;
};
typedef bool (*ImageSaveCallback_t)(Common::WriteStream &stream, const Graphics::Surface &surf, const byte *palette, const ImageSaveProperties &saveProperties);
/**
* Returns the default file extension for a format
*
* @param format The file format
* @param limitTo3Characters If true, limit the extension to 3 characters
* @return
*/
const char *getFormatExtension(Common::FormatInfo::FormatID format, bool limitTo3Characters);
Common::U32String getFormatSaveDescription(Common::FormatInfo::FormatID format);
ImageSaveCallback_t getImageSaveFunction(FormatID format);
bool getImageFormatCharacteristics(FormatID format, ImageFormatCharacteristics &outCharacteristics);
/**
* Returns the OS's level of support of a file format.
*/
FormatSupportLevel getFormatSupportLevel(Common::FormatInfo::FormatID format);
} // End of namespace FormatInfo
} // End of namespace Common
#endif

View File

@@ -0,0 +1,124 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/formats/iff_container.h"
#include "common/substream.h"
#include "common/util.h"
namespace Common {
IFFParser::IFFParser(ReadStream *stream, bool disposeStream, IFF_ID formHeaderID) : _stream(stream), _disposeStream(disposeStream), _formHeaderID(formHeaderID) {
setInputStream(stream);
}
IFFParser::~IFFParser() {
if (_disposeStream) {
delete _stream;
}
_stream = nullptr;
}
void IFFParser::setInputStream(ReadStream *stream) {
assert(stream);
_formChunk.setInputStream(stream);
_chunk.setInputStream(stream);
_formChunk.readHeader();
if (_formChunk.id != _formHeaderID) {
error("IFFParser input is not a FORM type IFF file");
}
_formSize = _formChunk.size;
_formType = _formChunk.readUint32BE();
}
void IFFParser::parse(IFFCallback &callback) {
bool stop;
do {
_chunk.feed();
_formChunk.incBytesRead(_chunk.size);
if (_formChunk.hasReadAll()) {
break;
}
_formChunk.incBytesRead(8);
_chunk.readHeader();
// invoke the callback
SubReadStream stream(&_chunk, _chunk.size);
IFFChunk chunk(_chunk.id, _chunk.size, &stream);
stop = callback(chunk);
// eats up all the remaining data in the chunk
while (!stream.eos()) {
stream.readByte();
}
} while (!stop);
}
PackBitsReadStream::PackBitsReadStream(Common::ReadStream &input) : _input(&input) {
}
PackBitsReadStream::~PackBitsReadStream() {
}
bool PackBitsReadStream::eos() const {
return _input->eos();
}
uint32 PackBitsReadStream::read(void *dataPtr, uint32 dataSize) {
byte *out = (byte *)dataPtr;
uint32 left = dataSize;
uint32 lenR = 0, lenW = 0;
while (left > 0 && !_input->eos()) {
lenR = _input->readByte();
if (lenR == 128) {
// no-op
lenW = 0;
} else if (lenR <= 127) {
// literal run
lenR++;
lenW = MIN(lenR, left);
for (uint32 j = 0; j < lenW; j++) {
*out++ = _input->readByte();
}
for (; lenR > lenW; lenR--) {
_input->readByte();
}
} else { // len > 128
// expand run
lenW = MIN((256 - lenR) + 1, left);
byte val = _input->readByte();
memset(out, val, lenW);
out += lenW;
}
left -= lenW;
}
return dataSize - left;
}
} // End of namespace Common

View File

@@ -0,0 +1,284 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COMMON_IFF_CONTAINER_H
#define COMMON_IFF_CONTAINER_H
#include "common/scummsys.h"
#include "common/endian.h"
#include "common/func.h"
#include "common/stream.h"
#include "common/textconsole.h"
namespace Common {
/**
* @defgroup common_iff Interchange File Format (IFF)
* @ingroup common
*
* @brief API for operations on IFF container files.
*
*
* @{
*/
typedef uint32 IFF_ID;
#define ID_FORM MKTAG('F','O','R','M')
/* EA IFF 85 group identifier */
#define ID_CAT MKTAG('C','A','T',' ')
/* EA IFF 85 group identifier */
#define ID_LIST MKTAG('L','I','S','T')
/* EA IFF 85 group identifier */
#define ID_PROP MKTAG('P','R','O','P')
/* EA IFF 85 group identifier */
#define ID_END MKTAG('E','N','D',' ')
/* unofficial END-of-FORM identifier (see Amiga RKM Devices Ed.3
page 376) */
#define ID_ILBM MKTAG('I','L','B','M')
/* EA IFF 85 raster bitmap form */
#define ID_DEEP MKTAG('D','E','E','P')
/* Chunky pixel image files (Used in TV Paint) */
#define ID_RGB8 MKTAG('R','G','B','8')
/* RGB image forms, Turbo Silver (Impulse) */
#define ID_RGBN MKTAG('R','G','B','N')
/* RGB image forms, Turbo Silver (Impulse) */
#define ID_PBM MKTAG('P','B','M',' ')
/* 256-color chunky format (DPaint 2 ?) */
#define ID_ACBM MKTAG('A','C','B','M')
/* Amiga Contiguous Bitmap (AmigaBasic) */
#define ID_8SVX MKTAG('8','S','V','X')
/* Amiga 8 bits voice */
#define ID_DATA MKTAG('D','A','T','A')
/* Replaces FORM in Nancy Drew IFFs */
/* generic */
#define ID_FVER MKTAG('F','V','E','R')
/* AmigaOS version string */
#define ID_JUNK MKTAG('J','U','N','K')
/* always ignore this chunk */
#define ID_ANNO MKTAG('A','N','N','O')
/* EA IFF 85 Generic Annotation chunk */
#define ID_AUTH MKTAG('A','U','T','H')
/* EA IFF 85 Generic Author chunk */
#define ID_CHRS MKTAG('C','H','R','S')
/* EA IFF 85 Generic character string chunk */
#define ID_NAME MKTAG('N','A','M','E')
/* EA IFF 85 Generic Name of art, music, etc. chunk */
#define ID_TEXT MKTAG('T','E','X','T')
/* EA IFF 85 Generic unformatted ASCII text chunk */
#define ID_copy MKTAG('(','c',')',' ')
/* EA IFF 85 Generic Copyright text chunk */
/* IFF chunks */
#define ID_BMHD MKTAG('B','M','H','D')
/* IFF BitmapHeader */
#define ID_CMAP MKTAG('C','M','A','P')
/* IFF 8bit RGB colormap */
#define ID_GRAB MKTAG('G','R','A','B')
/* IFF "hotspot" coordiantes */
#define ID_DEST MKTAG('D','E','S','T')
/* IFF destination image info */
#define ID_SPRT MKTAG('S','P','R','T')
/* IFF sprite identifier */
#define ID_CAMG MKTAG('C','A','M','G')
/* Amiga viewportmodes */
#define ID_BODY MKTAG('B','O','D','Y')
/* IFF image data */
#define ID_CRNG MKTAG('C','R','N','G')
/* color cycling */
#define ID_CCRT MKTAG('C','C','R','T')
/* color cycling */
#define ID_CLUT MKTAG('C','L','U','T')
/* Color Lookup Table chunk */
#define ID_DPI MKTAG('D','P','I',' ')
/* Dots per inch chunk */
#define ID_DPPV MKTAG('D','P','P','V')
/* DPaint perspective chunk (EA) */
#define ID_DRNG MKTAG('D','R','N','G')
/* DPaint IV enhanced color cycle chunk (EA) */
#define ID_EPSF MKTAG('E','P','S','F')
/* Encapsulated Postscript chunk */
#define ID_CMYK MKTAG('C','M','Y','K')
/* Cyan, Magenta, Yellow, & Black color map (Soft-Logik) */
#define ID_CNAM MKTAG('C','N','A','M')
/* Color naming chunk (Soft-Logik) */
#define ID_PCHG MKTAG('P','C','H','G')
/* Line by line palette control information (Sebastiano Vigna) */
#define ID_PRVW MKTAG('P','R','V','W')
/* A mini duplicate IFF used for preview (Gary Bonham) */
#define ID_XBMI MKTAG('X','B','M','I')
/* eXtended BitMap Information (Soft-Logik) */
#define ID_CTBL MKTAG('C','T','B','L')
/* Newtek Dynamic Ham color chunk */
#define ID_DYCP MKTAG('D','Y','C','P')
/* Newtek Dynamic Ham chunk */
#define ID_SHAM MKTAG('S','H','A','M')
/* Sliced HAM color chunk */
#define ID_ABIT MKTAG('A','B','I','T')
/* ACBM body chunk */
#define ID_DCOL MKTAG('D','C','O','L')
/* unofficial direct color */
#define ID_DPPS MKTAG('D','P','P','S')
/* ? */
#define ID_TINY MKTAG('T','I','N','Y')
/* ? */
#define ID_DPPV MKTAG('D','P','P','V')
/* ? */
/* 8SVX chunks */
#define ID_VHDR MKTAG('V','H','D','R')
/* 8SVX Voice8Header */
/**
* Represents a IFF chunk available to client code.
*
* Client code must *not* deallocate _stream when done.
*/
struct IFFChunk {
IFF_ID _type;
uint32 _size;
ReadStream *_stream;
IFFChunk(IFF_ID type, uint32 size, ReadStream *stream) : _type(type), _size(size), _stream(stream) {
assert(_stream);
}
};
/**
* Parser for IFF containers.
*/
class IFFParser {
/**
* This private class implements IFF chunk navigation.
*/
class IFFChunkNav : public ReadStream {
protected:
ReadStream *_input;
uint32 _bytesRead;
public:
IFF_ID id;
uint32 size;
IFFChunkNav() : _input(nullptr) {
}
void setInputStream(ReadStream *input) {
_input = input;
size = _bytesRead = 0;
}
void incBytesRead(uint32 inc) {
_bytesRead += inc;
if (_bytesRead > size) {
error("Chunk overread");
}
}
void readHeader() {
id = _input->readUint32BE();
size = _input->readUint32BE();
_bytesRead = 0;
}
bool hasReadAll() const {
return (size - _bytesRead) == 0;
}
void feed() {
if (size % 2) {
size++;
}
while (!hasReadAll()) {
readByte();
}
}
// ReadStream implementation
bool eos() const { return _input->eos(); }
bool err() const { return _input->err(); }
void clearErr() { _input->clearErr(); }
uint32 read(void *dataPtr, uint32 dataSize) {
incBytesRead(dataSize);
return _input->read(dataPtr, dataSize);
}
};
protected:
IFFChunkNav _formChunk; ///< The root chunk of the file.
IFFChunkNav _chunk; ///< The current chunk.
uint32 _formSize;
IFF_ID _formType;
IFF_ID _formHeaderID;
ReadStream *_stream;
bool _disposeStream;
void setInputStream(ReadStream *stream);
public:
IFFParser(ReadStream *stream, bool disposeStream = false, IFF_ID formHeaderID = ID_FORM);
~IFFParser();
/**
* Callback type for the parser.
*/
typedef Functor1< IFFChunk&, bool > IFFCallback;
/**
* Parse the IFF container, invoking the callback on each chunk encountered.
* The callback can interrupt the parsing by returning 'true'.
*/
void parse(IFFCallback &callback);
};
/**
* Decode a given PackBits encoded stream.
*
* PackBits is an RLE compression algorithm introduced by Apple. It is also
* used to encode ILBM and PBM subtypes of IFF files, and some flavors of
* TIFF.
*
* As there is no compression across row boundaries in the above formats,
* read() will extract a *new* line on each call, discarding any alignment
* or padding.
*/
class PackBitsReadStream : public Common::ReadStream {
protected:
Common::ReadStream *_input;
public:
PackBitsReadStream(Common::ReadStream &input);
~PackBitsReadStream();
virtual bool eos() const;
uint32 read(void *dataPtr, uint32 dataSize);
};
/** @} */
} // namespace Common
#endif

499
common/formats/ini-file.cpp Normal file
View File

@@ -0,0 +1,499 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/formats/ini-file.h"
#include "common/file.h"
#include "common/savefile.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/macresman.h"
namespace Common {
bool INIFile::isValidChar(char c) const {
if (_allowNonEnglishCharacters) {
// Chars that can break parsing are never allowed
return !(c == '[' || c == ']' || c == '=' || c == '#' || c == '\r' || c == '\n');
} else {
// Only some chars are allowed
return isAlnum(c) || c == '-' || c == '_' || c == '.' || c == ' ' || c == ':';
}
}
bool INIFile::isValidName(const String &name) const {
if (name.empty())
return false;
const char *p = name.c_str();
while (*p && isValidChar(*p)) {
p++;
}
return *p == 0;
}
INIFile::INIFile() {
_allowNonEnglishCharacters = false;
_suppressValuelessLineWarning = false;
_requireKeyValueDelimiter = false;
}
void INIFile::clear() {
_sections.clear();
}
bool INIFile::loadFromFile(const Path &filename) {
File file;
if (file.open(filename))
return loadFromStream(file);
else
return false;
}
bool INIFile::loadFromFileOrDataFork(const Path &filename) {
SeekableReadStream *file = Common::MacResManager::openFileOrDataFork(filename);
if (file)
return loadFromStream(*file);
else
return false;
}
bool INIFile::loadFromSaveFile(const String &filename) {
assert(g_system);
SaveFileManager *saveFileMan = g_system->getSavefileManager();
SeekableReadStream *loadFile;
assert(saveFileMan);
if (!(loadFile = saveFileMan->openForLoading(filename)))
return false;
bool status = loadFromStream(*loadFile);
delete loadFile;
return status;
}
bool INIFile::loadFromStream(SeekableReadStream &stream) {
static const byte UTF8_BOM[] = {0xEF, 0xBB, 0xBF};
Section section;
KeyValue kv;
String comment;
int lineno = 0;
section.name = _defaultSectionName;
// TODO: Detect if a section occurs multiple times (or likewise, if
// a key occurs multiple times inside one section).
while (!stream.eos() && !stream.err()) {
lineno++;
// Read a line
String line = stream.readLine();
// Skip UTF-8 byte-order mark if added by a text editor.
if (lineno == 1 && memcmp(line.c_str(), UTF8_BOM, 3) == 0) {
line.erase(0, 3);
}
line.trim();
if (line.size() == 0) {
// Do nothing
} else if (line[0] == '#' || line[0] == ';' || line.hasPrefix("//")) {
// Accumulate comments here. Once we encounter either the start
// of a new section, or a key-value-pair, we associate the value
// of the 'comment' variable with that entity. The semicolon and
// C++-style comments are used for Living Books games in Mohawk.
comment += line;
comment += "\n";
} else if (line[0] == '(') {
// HACK: The following is a hack added by Kirben to support the
// "map.ini" used in the HE SCUMM game "SPY Fox in Hold the Mustard".
//
// It would be nice if this hack could be restricted to that game,
// but the current design of this class doesn't allow to do that
// in a nice fashion (a "isMustard" parameter is *not* a nice
// solution).
comment += line;
comment += "\n";
} else if (line[0] == '[') {
// It's a new section which begins here.
const char *p = line.c_str() + 1;
// Get the section name, and check whether it's valid (that
// is, verify that it only consists of alphanumerics,
// periods, dashes and underscores). Mohawk Living Books games
// can have periods in their section names.
// WinAGI games can have colons in their section names.
while (*p && ((_allowNonEnglishCharacters && *p != ']') || isAlnum(*p) || *p == '-' || *p == '_' || *p == '.' || *p == ' ' || *p == ':'))
p++;
if (*p == '\0') {
warning("INIFile::loadFromStream: missing ] in line %d", lineno);
return false;
}
else if (*p != ']') {
warning("INIFile::loadFromStream: Invalid character '%c' occurred in section name in line %d", *p, lineno);
return false;
}
// Previous section is finished now, store it.
if (!section.name.empty())
_sections.push_back(section);
section.name = String(line.c_str() + 1, p);
section.keys.clear();
section.comment = comment;
comment.clear();
if (!isValidName(section.name)) {
warning("Invalid section name: %s", section.name.c_str());
return false;
}
} else {
// This line should be a line with a 'key=value' pair, or an empty one.
// If no section has been set, this config file is invalid!
if (section.name.empty()) {
warning("INIFile::loadFromStream: Key/value pair found outside a section in line %d", lineno);
return false;
}
// Split string at '=' into 'key' and 'value'. First, find the "=" delimeter.
const char *p = strchr(line.c_str(), '=');
if (!p) {
if (!_suppressValuelessLineWarning)
warning("Config file buggy: Junk found in line %d: '%s'", lineno, line.c_str());
// there is no '=' on this line. skip if delimiter is required.
if (_requireKeyValueDelimiter)
continue;
kv.key = line;
kv.value.clear();
} else {
// Extract the key/value pair
kv.key = String(line.c_str(), p);
kv.value = String(p + 1);
}
// Trim of spaces
kv.key.trim();
kv.value.trim();
// Store comment
kv.comment = comment;
comment.clear();
if (!isValidName(kv.key)) {
warning("Invalid key name: %s", kv.key.c_str());
return false;
}
section.keys.push_back(kv);
}
}
// Save last section
if (!section.name.empty())
_sections.push_back(section);
return (!stream.err() || stream.eos());
}
bool INIFile::saveToFile(const Path &filename) {
DumpFile file;
if (file.open(filename))
return saveToStream(file);
else
return false;
}
bool INIFile::saveToSaveFile(const String &filename) {
assert(g_system);
SaveFileManager *saveFileMan = g_system->getSavefileManager();
WriteStream *saveFile;
assert(saveFileMan);
if (!(saveFile = saveFileMan->openForSaving(filename)))
return false;
bool status = saveToStream(*saveFile);
delete saveFile;
return status;
}
bool INIFile::saveToStream(WriteStream &stream) {
for (auto &curSection : _sections) {
// Write out the section comment, if any
if (!curSection.comment.empty()) {
stream.writeString(curSection.comment);
}
// Write out the section name
stream.writeByte('[');
stream.writeString(curSection.name);
stream.writeByte(']');
stream.writeByte('\n');
// Write out the key/value pairs
for (auto &kv : curSection.keys) {
// Write out the comment, if any
if (!kv.comment.empty()) {
stream.writeString(kv.comment);
}
// Write out the key/value pair
stream.writeString(kv.key);
stream.writeByte('=');
stream.writeString(kv.value);
stream.writeByte('\n');
}
}
stream.flush();
return !stream.err();
}
void INIFile::addSection(const String &section) {
if (!isValidName(section)) {
warning("Invalid section name: %s", section.c_str());
return;
}
Section *s = getSection(section);
if (s)
return;
Section newSection;
newSection.name = section;
_sections.push_back(newSection);
}
void INIFile::removeSection(const String &section) {
if (!isValidName(section)) {
warning("Invalid section name: %s", section.c_str());
return;
}
for (List<Section>::iterator i = _sections.begin(); i != _sections.end(); ++i) {
if (section.equalsIgnoreCase(i->name)) {
_sections.erase(i);
return;
}
}
}
bool INIFile::hasSection(const String &section) const {
if (!isValidName(section)) {
warning("Invalid section name: %s", section.c_str());
return false;
}
const Section *s = getSection(section);
return s != nullptr;
}
void INIFile::renameSection(const String &oldName, const String &newName) {
if (!isValidName(oldName)) {
warning("Invalid section name: %s", oldName.c_str());
return;
}
if (!isValidName(newName)) {
warning("Invalid section name: %s", newName.c_str());
return;
}
Section *os = getSection(oldName);
const Section *ns = getSection(newName);
if (os) {
// HACK: For now we just print a warning, for more info see the TODO
// below.
if (ns)
warning("INIFile::renameSection: Section name \"%s\" already used", newName.c_str());
else
os->name = newName;
}
// TODO: Check here whether there already is a section with the
// new name. Not sure how to cope with that case, we could:
// - simply remove the existing "newName" section
// - error out
// - merge the two sections "oldName" and "newName"
}
void INIFile::setDefaultSectionName(const String &name) {
_defaultSectionName = name;
}
bool INIFile::hasKey(const String &key, const String &section) const {
if (!isValidName(key)) {
warning("Invalid key name: %s", key.c_str());
return false;
}
if (!isValidName(section)) {
warning("Invalid section name: %s", section.c_str());
return false;
}
const Section *s = getSection(section);
if (!s)
return false;
return s->hasKey(key);
}
void INIFile::removeKey(const String &key, const String &section) {
if (!isValidName(key)) {
warning("Invalid key name: %s", key.c_str());
return;
}
if (!isValidName(section)) {
warning("Invalid section name: %s", section.c_str());
return;
}
Section *s = getSection(section);
if (s)
s->removeKey(key);
}
bool INIFile::getKey(const String &key, const String &section, String &value) const {
if (!isValidName(key)) {
warning("Invalid key name: %s", key.c_str());
return false;
}
if (!isValidName(section)) {
warning("Invalid section name: %s", section.c_str());
return false;
}
const Section *s = getSection(section);
if (!s)
return false;
const KeyValue *kv = s->getKey(key);
if (!kv)
return false;
value = kv->value;
return true;
}
void INIFile::setKey(const String &key, const String &section, const String &value) {
if (!isValidName(key)) {
warning("Invalid key name: %s", key.c_str());
return;
}
if (!isValidName(section)) {
warning("Invalid section name: %s", section.c_str());
return;
}
// TODO: Verify that value is valid, too. In particular, it shouldn't
// contain CR or LF...
Section *s = getSection(section);
if (!s) {
KeyValue newKV;
newKV.key = key;
newKV.value = value;
Section newSection;
newSection.name = section;
newSection.keys.push_back(newKV);
_sections.push_back(newSection);
} else {
s->setKey(key, value);
}
}
const INIFile::SectionKeyList INIFile::getKeys(const String &section) const {
const Section *s = getSection(section);
return s->getKeys();
}
INIFile::Section *INIFile::getSection(const String &section) {
for (auto &curSection : _sections) {
if (section.equalsIgnoreCase(curSection.name)) {
return &curSection;
}
}
return nullptr;
}
const INIFile::Section *INIFile::getSection(const String &section) const {
for (const auto &curSection : _sections) {
if (section.equalsIgnoreCase(curSection.name)) {
return &curSection;
}
}
return nullptr;
}
bool INIFile::Section::hasKey(const String &key) const {
return getKey(key) != nullptr;
}
const INIFile::KeyValue* INIFile::Section::getKey(const String &key) const {
for (const auto &curKey : keys) {
if (key.equalsIgnoreCase(curKey.key)) {
return &curKey;
}
}
return nullptr;
}
void INIFile::Section::setKey(const String &key, const String &value) {
for (auto &curKey : keys) {
if (key.equalsIgnoreCase(curKey.key)) {
curKey.value = value;
return;
}
}
KeyValue newKV;
newKV.key = key;
newKV.value = value;
keys.push_back(newKV);
}
void INIFile::Section::removeKey(const String &key) {
for (List<KeyValue>::iterator i = keys.begin(); i != keys.end(); ++i) {
if (key.equalsIgnoreCase(i->key)) {
keys.erase(i);
return;
}
}
}
void INIFile::allowNonEnglishCharacters() {
_allowNonEnglishCharacters = true;
}
void INIFile::suppressValuelessLineWarning() {
_suppressValuelessLineWarning = true;
}
void INIFile::requireKeyValueDelimiter() {
_requireKeyValueDelimiter = true;
}
} // End of namespace Common

165
common/formats/ini-file.h Normal file
View File

@@ -0,0 +1,165 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COMMON_INI_FILE_H
#define COMMON_INI_FILE_H
#include "common/hash-str.h"
#include "common/list.h"
#include "common/path.h"
#include "common/str.h"
namespace Common {
/**
* @defgroup common_ini_file INI files
* @ingroup common
*
* @brief API for operations on INI configuration files.
*
*
* @{
*/
class SeekableReadStream;
class WriteStream;
/**
* This class allows for reading and writing INI-style config files.
*
* Lines starting with a '#' are ignored (i.e. treated as comments).
* Some effort is made to preserve comments, though.
*
* This class makes no attempts to provide fast access to key/value pairs.
* In particular, it stores all sections and key/value pairs in lists, not
* in dictionaries/maps. This makes it very easy to read/write the data
* from/to files, but of course is not appropriate for fast access.
* The main reason is that this class is indeed geared toward doing precisely
* that.
*/
class INIFile {
public:
struct KeyValue {
String key; /*!< Key of the configuration entry, whitespace trimmed. */
String value; /*!< Value of the configuration entry, whitespace trimmed. */
String comment; /*!< Comment within an INI file, including #s and newlines. */
};
typedef List<KeyValue> SectionKeyList; /*!< A list of all key/value pairs in this section. */
/** A section in an INI file.
*
* Corresponds to the following:
* @code
* [mySection]
* key=value
* @endcode
* Comments are also stored, for convenience of users who prefer to edit
* INI files manually.
*/
struct Section {
String name; /*!< Name of the section, whitespace trimmed. */
List<KeyValue> keys; /*!< List of all keys in this section. */
String comment; /*!< Comment within the section, including #s and newlines. */
bool hasKey(const String &key) const; /*!< Check whether the section has a @p key. */
const KeyValue* getKey(const String &key) const; /*!< Get the value assigned to a @p key. */
void setKey(const String &key, const String &value); /*!< Assign a @p value to a @p key. */
void removeKey(const String &key); /*!< Remove a @p key from this section. */
const SectionKeyList getKeys() const { return keys; } /*!< Get a list of all keys in the section. */
};
typedef List<Section> SectionList; /*!< A list of all sections in this INI file. */
public:
INIFile();
~INIFile() {}
// TODO: Maybe add a copy constructor etc.?
/**
* Check whether the given string is a valid section or key name.
* For that, it must only consist of letters, numbers, dashes, and
* underscores. In particular, whitespace and "#", "=", "[", "]"
* are not valid.
*/
bool isValidName(const String &name) const;
/** Reset everything stored in this INI file. */
void clear();
bool loadFromFile(const Path &filename); /*!< Load configuration from a file. */
bool loadFromFileOrDataFork(const Path &filename); /*!< Load configuration from a file in MacBinary format. */
bool loadFromSaveFile(const String &filename); /*!< Load configuration from a save file. */
bool loadFromStream(SeekableReadStream &stream); /*!< Load configuration from a @ref SeekableReadStream. */
bool saveToFile(const Path &filename); /*!< Save the current configuration to a file. */
bool saveToSaveFile(const String &filename); /*!< Save the current configuration to a save file. */
bool saveToStream(WriteStream &stream); /*!< Save the current configuration to a @ref WriteStream. */
bool hasSection(const String &section) const; /*!< Check whether the INI file has a section with the specified name. */
void addSection(const String &section); /*!< Add a section with the specified name to the INI file. */
void removeSection(const String &section); /*!< Remove the @p section from the INI file. */
void renameSection(const String &oldName, const String &newName); /*!< Rename the INI file from @p oldName to @p newName. */
void setDefaultSectionName(const String &name); /*!< Set initial section name for section-less INI files. */
bool hasKey(const String &key, const String &section) const; /*!< Check whether the @p section has a @p key. */
bool getKey(const String &key, const String &section, String &value) const; /*!< Get the @p value of a @p key in a @p section. */
void setKey(const String &key, const String &section, const String &value); /*!< Assign a @p value to a @p key in a @p section. */
void removeKey(const String &key, const String &section); /*!< Remove a @p key from this @p section. */
const SectionList getSections() const { return _sections; } /*!< Get a list of sections in this INI file. */
const SectionKeyList getKeys(const String &section) const; /*!< Get a list of keys in a @p section. */
void listKeyValues(StringMap &kv); /*!< Get a list of all key/value pairs in this INI file. */
void allowNonEnglishCharacters(); /*!< Allow non-English characters in this INI file. */
void suppressValuelessLineWarning(); /*!< Disable warnings for lines that contain only keys. */
/**
* Requires that every key/value line have a delimiter character.
* Otherwise, lines that don't contain a delimiter are interpreted as
* if the entire line is a key with an empty value. This can cause
* unexpected junk lines to reach engines. (bug #13920)
*
* It may be better if instead this were the default behavior for
* clients to disable if needed, but first we would need to identify
* everything that depends on the current behavior.
*/
void requireKeyValueDelimiter();
private:
String _defaultSectionName;
SectionList _sections;
bool _allowNonEnglishCharacters;
bool _suppressValuelessLineWarning;
bool _requireKeyValueDelimiter;
Section *getSection(const String &section);
const Section *getSection(const String &section) const;
bool isValidChar(char c) const; /*!< Is given char allowed in section or key names*/
};
/** @} */
} // End of namespace Common
#endif

1234
common/formats/json.cpp Normal file

File diff suppressed because it is too large Load Diff

175
common/formats/json.h Normal file
View File

@@ -0,0 +1,175 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/*
* Files JSON.h and JSONValue.h part of the SimpleJSON Library - https://github.com/MJPA/SimpleJSON
*
* Copyright (C) 2010 Mike Anchor
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef COMMON_JSON_H
#define COMMON_JSON_H
#include "common/array.h"
#include "common/hashmap.h"
#include "common/hash-str.h"
#include "common/memstream.h"
#include "common/str.h"
// Win32 incompatibilities
#if (defined(WIN32) && !defined(__GNUC__))
static inline bool isnan(double x) {
return x != x;
}
static inline bool isinf(double x) {
return !isnan(x) && isnan(x - x);
}
#endif
// Simple function to check a string 's' has at least 'n' characters
static inline bool simplejson_wcsnlen(const char *s, size_t n) {
if (s == nullptr)
return false;
const char *save = s;
while (n-- > 0) {
if (*(save++) == 0) return false;
}
return true;
}
namespace Common {
// Custom types
class JSONValue;
typedef Array<JSONValue*> JSONArray;
typedef HashMap<String, JSONValue*> JSONObject;
class JSON;
enum JSONType { JSONType_Null, JSONType_String, JSONType_Bool, JSONType_Number, JSONType_IntegerNumber, JSONType_Array, JSONType_Object };
class JSONValue {
friend class JSON;
public:
JSONValue(/*NULL*/);
JSONValue(const char *charValue);
JSONValue(const String &stringValue);
JSONValue(bool boolValue);
JSONValue(double numberValue);
JSONValue(long long int numberValue);
JSONValue(const JSONArray &arrayValue);
JSONValue(const JSONObject &objectValue);
JSONValue(const JSONValue &source);
~JSONValue();
bool isNull() const;
bool isString() const;
bool isBool() const;
bool isNumber() const;
bool isIntegerNumber() const;
bool isArray() const;
bool isObject() const;
const String &asString() const;
bool asBool() const;
double asNumber() const;
long long int asIntegerNumber() const;
const JSONArray &asArray() const;
const JSONObject &asObject() const;
size_t countChildren() const;
bool hasChild(size_t index) const;
JSONValue *child(size_t index);
bool hasChild(const char *name) const;
JSONValue *child(const char *name);
Array<String> objectKeys() const;
String stringify(bool const prettyprint = false) const;
protected:
static JSONValue *parse(const char **data);
private:
static String stringifyString(const String &str);
static uint32 decodeUtf8Char(String::const_iterator &begin, const String::const_iterator &end);
static uint8 decodeUtf8Byte(uint8 state, uint32 &codepoint, uint8 byte);
String stringifyImpl(size_t const indentDepth) const;
static String indent(size_t depth);
JSONType _type;
union {
bool _boolValue;
double _numberValue;
long long int _integerValue;
String *_stringValue;
JSONArray *_arrayValue;
JSONObject *_objectValue;
};
};
class JSON {
friend class JSONValue;
public:
/** Prepares raw bytes in a given stream to be parsed with Common::JSON::parse(). */
static char *zeroTerminateContents(Common::MemoryWriteStreamDynamic &stream);
static JSONValue *parse(const Common::String &data) {
return parse(data.c_str());
}
static JSONValue *parse(const char *data);
static String stringify(const JSONValue *value);
protected:
static bool skipWhitespace(const char **data);
static bool extractString(const char **data, String &str);
static uint32 parseUnicode(const char **data);
static double parseInt(const char **data);
static double parseDecimal(const char **data);
private:
JSON();
};
} // End of namespace Common
#endif

3068
common/formats/markdown.cpp Normal file

File diff suppressed because it is too large Load Diff

154
common/formats/markdown.h Normal file
View File

@@ -0,0 +1,154 @@
/*
* Copyright (c) 2009, Natacha Porté
* Copyright (c) 2011, Vicent Marti
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef COMMON_FORMATS_MARKDOWN_H
#define COMMON_FORMATS_MARKDOWN_H
#include "common/scummsys.h"
namespace Common {
class String;
#define SUNDOWN_VERSION "1.16.0"
#define SUNDOWN_VER_MAJOR 1
#define SUNDOWN_VER_MINOR 16
#define SUNDOWN_VER_REVISION 0
/********************
* TYPE DEFINITIONS *
********************/
struct SDDataBuffer;
/* mkd_autolink - type of autolink */
enum MKDAutolink {
MKDA_NOT_AUTOLINK, /* used internally when it is not an autolink*/
MKDA_NORMAL, /* normal http/http/ftp/mailto/etc link */
MKDA_EMAIL, /* e-mail link without explit mailto: */
};
enum mkd_tableflags {
MKD_TABLE_ALIGN_L = 1,
MKD_TABLE_ALIGN_R = 2,
MKD_TABLE_ALIGN_CENTER = 3,
MKD_TABLE_ALIGNMASK = 3,
MKD_TABLE_HEADER = 4
};
enum mkd_extensions {
MKDEXT_NO_INTRA_EMPHASIS = (1 << 0),
MKDEXT_TABLES = (1 << 1),
MKDEXT_FENCED_CODE = (1 << 2),
MKDEXT_AUTOLINK = (1 << 3),
MKDEXT_STRIKETHROUGH = (1 << 4),
MKDEXT_SPACE_HEADERS = (1 << 6),
MKDEXT_SUPERSCRIPT = (1 << 7),
MKDEXT_LAX_SPACING = (1 << 8),
};
struct SDStack {
void **item;
size_t size;
size_t asize;
};
/* sd_callbacks - functions for rendering parsed data */
struct SDCallbacks {
/* block level callbacks - NULL skips the block */
void (*blockcode)(SDDataBuffer *ob, const SDDataBuffer *text, const SDDataBuffer *lang, void *opaque);
void (*blockquote)(SDDataBuffer *ob, const SDDataBuffer *text, void *opaque);
void (*blockhtml)(SDDataBuffer *ob,const SDDataBuffer *text, void *opaque);
void (*header)(SDDataBuffer *ob, const SDDataBuffer *text, int level, void *opaque);
void (*hrule)(SDDataBuffer *ob, void *opaque);
void (*list_start)(SDDataBuffer *ob, const SDDataBuffer *text, int flags, void *opaque);
void (*list)(SDDataBuffer *ob, const SDDataBuffer *text, int flags, void *opaque);
void (*listitem)(SDDataBuffer *ob, const SDDataBuffer *text, int flags, void *opaque);
void (*paragraph)(SDDataBuffer *ob, const SDDataBuffer *text, void *opaque);
void (*table)(SDDataBuffer *ob, const SDDataBuffer *header, const SDDataBuffer *body, void *opaque);
void (*table_row)(SDDataBuffer *ob, const SDDataBuffer *text, void *opaque);
void (*table_cell)(SDDataBuffer *ob, const SDDataBuffer *text, int flags, void *opaque);
/* span level callbacks - NULL or return 0 prints the span verbatim */
int (*autolink)(SDDataBuffer *ob, const SDDataBuffer *link, MKDAutolink type, void *opaque);
int (*codespan)(SDDataBuffer *ob, const SDDataBuffer *text, void *opaque);
int (*double_emphasis)(SDDataBuffer *ob, const SDDataBuffer *text, void *opaque);
int (*emphasis)(SDDataBuffer *ob, const SDDataBuffer *text, void *opaque);
int (*image)(SDDataBuffer *ob, const SDDataBuffer *link, const SDDataBuffer *title, const SDDataBuffer *alt, const SDDataBuffer *ext, void *opaque);
int (*linebreak)(SDDataBuffer *ob, void *opaque);
int (*link)(SDDataBuffer *ob, const SDDataBuffer *link, const SDDataBuffer *title, const SDDataBuffer *content, void *opaque);
int (*raw_html_tag)(SDDataBuffer *ob, const SDDataBuffer *tag, void *opaque);
int (*triple_emphasis)(SDDataBuffer *ob, const SDDataBuffer *text, void *opaque);
int (*strikethrough)(SDDataBuffer *ob, const SDDataBuffer *text, void *opaque);
int (*superscript)(SDDataBuffer *ob, const SDDataBuffer *text, void *opaque);
/* low level callbacks - NULL copies input directly into the output */
void (*entity)(SDDataBuffer *ob, const SDDataBuffer *entity, void *opaque);
void (*normal_text)(SDDataBuffer *ob, const SDDataBuffer *text, void *opaque);
/* header and footer */
void (*doc_header)(SDDataBuffer *ob, void *opaque);
void (*doc_footer)(SDDataBuffer *ob, void *opaque);
};
/*********
* FLAGS *
*********/
/* list/listitem flags */
#define MKD_LIST_ORDERED 1
#define MKD_LI_BLOCK 2 /* <li> containing block data */
#define REF_TABLE_SIZE 8
struct LinkRef;
class SDMarkdown {
public:
SDCallbacks _cb;
void *_opaque;
LinkRef *_refs[REF_TABLE_SIZE];
byte _active_char[256];
SDStack _work_bufs[2];
uint _ext_flags;
size_t _max_nesting;
int _in_link_body;
SDMarkdown(uint extensions, size_t max_nesting, const SDCallbacks *callbacks, void *opaque);
~SDMarkdown();
Common::String render(const byte *document, size_t doc_size);
void version(int *major, int *minor, int *revision);
};
/* SDDataBuffer: character array buffer */
struct SDDataBuffer {
byte *data; /* actual character data */
size_t size; /* size of the string */
size_t asize; /* allocated size (0 = volatile buffer) */
size_t unit; /* reallocation unit size (0 = read-only buffer) */
};
/* sd_bufput: appends raw data to a buffer */
void sd_bufput(SDDataBuffer *, const void *, size_t);
} // end of namespace Common
#endif // COMMON_FORMATS_MARKDOWN_H

19
common/formats/module.mk Normal file
View File

@@ -0,0 +1,19 @@
MODULE := common/formats
MODULE_OBJS := \
cue.o \
disk_image.o \
formatinfo.o \
iff_container.o \
ini-file.o \
json.o \
markdown.o \
prodos.o \
quicktime.o \
winexe.o \
winexe_ne.o \
winexe_pe.o \
xmlparser.o
# Include common rules
include $(srcdir)/rules.mk

432
common/formats/prodos.cpp Normal file
View File

@@ -0,0 +1,432 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/formats/prodos.h"
namespace Common {
// --- ProDOSFile methods ---
ProDOSFile::ProDOSFile(char name[16], uint8 type, uint16 tBlk, uint32 eof, uint16 bPtr, Common::File *disk)
: _type(type)
, _totalBlocks(tBlk)
, _eof(eof)
, _blockPtr(bPtr)
, _disk(disk) {
Common::strlcpy(_name, name, 16);
}
/* For debugging purposes, this prints the meta data of a file */
void ProDOSFile::printInfo() {
debug("File: %s", _name);
debug("Type: %02X", _type);
debug("data: %d", _blockPtr);
debug("Blocks: %d", _totalBlocks);
debug("Size: %u\n", _eof);
}
/* For Common::Archive, this method just returns a string of the name */
Common::String ProDOSFile::getName() const {
return Common::String(_name);
}
Common::String ProDOSFile::getFileName() const {
return Common::String(_name);
}
Common::Path ProDOSFile::getPathInArchive() const {
return Common::Path(_name);
}
/* This method is used to get a single block of data from the disk,
* but is not strictly 512 bytes. This is so that it can get only what
* it needs when in the final block. It then adds it into the allocated
* memory starting at memOffset
*/
void ProDOSFile::getDataBlock(byte *memOffset, int offset, int size) const {
// All this method needs to do is read (size) of data at (offset) into (memOffset)
_disk->seek(offset);
_disk->read(memOffset, size);
}
/* To put together a sapling file, you need to loop through the index
* block, adding to the file data one block at a time. This method also
* returns the size of data it got, just to make it a little simpler to
* determine the new position within the byte data.
*/
int ProDOSFile::parseIndexBlock(byte *memOffset, int blockNum, int rem) const {
int dataSize; // For most of the blocks, this will be kBlockSize, but the last one will be the calculated remainder
int readSize = 0; // This keeps track of the new pointer position to read data to, by updating the size of data read last
int dataOffset; // Where in the disk to read from
int diskPos; // Current position of cursor
for (int i = 0; i < blockNum; i++) {
dataSize = (i == (blockNum - 1)) ? rem : ProDOSDisk::kBlockSize;
dataOffset = _disk->readByte(); // Low byte is first
/* The cursor needs to know where to get the next pointer from in the index block,
* but it also needs to jump to the offset of data to read it, so we need to preserve
* the position in the index block it was in before.
*/
diskPos = _disk->pos();
_disk->skip(255); // The high bytes are stored at the end of the block
dataOffset = (dataOffset + (_disk->readByte() << 8)) * ProDOSDisk::kBlockSize; // High byte is second
getDataBlock(memOffset + readSize, dataOffset, dataSize);
readSize += dataSize;
// And now we resume the position before this call
_disk->seek(diskPos);
}
return readSize;
}
/* Extracting file data is a little tricky, as the blocks are spread out in the disk. There are 3 types
* of regular files. Seed, Sapling, and Tree. A Seed file only needs a single data block, while a
* Sapling needs an index block to manage up to 256 data blocks, and a Tree file needs an index block
* to manage up to 128 (only uses half the block) index blocks. This is also an Archive method as it
* returns a read stream of the file contents.
*/
Common::SeekableReadStream *ProDOSFile::createReadStream() const {
// We know the total byte size of the data, so we can allocate the full amount right away
byte *finalData = (byte *)malloc(_eof);
/* For a seed, this is a direct pointer to data. For a sapling it is an index file,
* and for a tree it is a master index file.
*/
int indexBlock = _blockPtr * ProDOSDisk::kBlockSize;
/* For a sapling or tree, the size needs to be calculated, as they are made from multiple blocks.
* _totalBlocks *includes* the index block, so the blocks before the oef block are _totalBlocks-2
*/
int remainder = _eof - ((_totalBlocks - 2) * ProDOSDisk::kBlockSize);
// For a seed file, the end of file value is also the size in the block, because it's just the one block
if (_type == kFileTypeSeed) {
getDataBlock(finalData, indexBlock, _eof);
} else if (_type == kFileTypeSapling) {
_disk->seek(indexBlock);
parseIndexBlock(finalData, _totalBlocks - 1, remainder);
} else {
// If it's not a seed and not a sapling, it's a tree.
_disk->seek(indexBlock);
/* A sapling can have an index block of up to 256, so if it is a tree,
* that means it has more than 256 blocks
*/
int indexNum = (_totalBlocks - 1) / 256;
int indexNumR = (_totalBlocks - 1) % 256;
/* However, to know how many index blocks there are, we need to know the remainder
* so we can figure out if it's ex. 2 index blocks, or 2 and some portion of a 3rd
*/
indexNum += indexNumR;
int blockNum;
int indexOffset;
int readSize = 0;
// Now we can loop through the master index file, parsing the individual index files similar to a sapling
for (int i = 0; i < indexNum; i++) {
blockNum = (i == indexNum - 1) ? indexNumR : 256;
indexOffset = _disk->readByte();
int diskPos = _disk->pos();
_disk->skip(255);
indexOffset = (indexOffset + (_disk->readByte() << 8)) * ProDOSDisk::kBlockSize;
_disk->seek(indexOffset);
readSize += parseIndexBlock(finalData + readSize, blockNum, remainder);
_disk->seek(diskPos);
}
}
return new Common::MemoryReadStream(finalData, _eof, DisposeAfterUse::YES);
}
Common::SeekableReadStream *ProDOSFile::createReadStreamForAltStream(Common::AltStreamType altStreamType) const {
return nullptr;
}
// --- ProDOSDisk methods ---
/* The time and date are compressed into 16bit words, so to make them useable
* we have to decompress them by masking the other bits and then shifting
* to the lowest bit so that they can be stored in 8 bits each.
*/
void ProDOSDisk::getDate(Date *d, uint16 date) {
d->_day = date & 0x001f;
d->_month = (date & 0x01e0) >> 5;
d->_year = (date & 0xfe00) >> 9;
}
void ProDOSDisk::getTime(Time *t, uint16 time) {
t->_minute = time & 0x003f;
t->_hour = (time & 0x1f00) >> 8;
}
/* Adds most of the header data to a directory header struct */
void ProDOSDisk::getHeader(DirHeader *h) {
/* The type and nameLen fields are stored in the same byte,
* so we need to split the byte, and shift the high bits to
* make it readable as an int
*/
uint8 tempByte = _disk.readByte();
h->_nameLen = tempByte & 0xf;
h->_type = (tempByte & 0xf0) >> 4;
/* The name field is stored in 15 bytes with no null character (unused chars default to 0).
* To make it easier to use the name, we will add a terminator regardless.
*/
_disk.read(h->_name, 15);
h->_name[15] = 0;
_disk.read(h->_reserved, 8);
// The time and date can be decompressed into structs right away
getDate(&(h->_date), _disk.readUint16LE());
getTime(&(h->_time), _disk.readUint16LE());
h->_ver = _disk.readByte();
h->_minVer = _disk.readByte();
h->_access = _disk.readByte();
h->_entryLen = _disk.readByte();
h->_entriesPerBlock = _disk.readByte();
h->_fileCount = _disk.readUint16LE();
}
/* Since a subdirectory header is mostly the same a volume header, we will reuse the code where we can */
void ProDOSDisk::getDirectoryHeader(DirHeader *h) {
getHeader(h);
h->_parentBlockPtr = _disk.readUint16LE();
h->_parentEntryIndex = _disk.readByte();
h->_parentEntryLen = _disk.readUint16LE();
}
/* This is a little sneaky, but since the bulk of the header is the same, we're just going to pretend the volume header
* is a directory header for the purose of filling it out with the same code
*/
void ProDOSDisk::getVolumeHeader(VolHeader *h) {
getHeader((DirHeader *)h);
h->_bitmapPtr = _disk.readUint16LE();
h->_volBlocks = _disk.readUint16LE();
_volBlocks = h->_volBlocks;
}
/* Getting a file entry header is very similar to getting a header, but with different data. */
void ProDOSDisk::getFileEntry(FileEntry *f) {
uint8 tempByte = _disk.readByte();
f->_nameLen = tempByte & 0xf;
f->_type = (tempByte & 0xf0) >> 4;
_disk.read(f->_name, 15);
f->_name[15] = 0;
f->_ext = _disk.readByte();
f->_blockPtr = _disk.readUint16LE();
f->_totalBlocks = _disk.readUint16LE();
// The file size in bytes is stored as a long (3 bytes), lowest to highest
f->_eof = _disk.readByte() + (_disk.readByte() << 8) + (_disk.readByte() << 16);
getDate(&(f->_date), _disk.readUint16LE());
getTime(&(f->_time), _disk.readUint16LE());
f->_ver = _disk.readByte();
f->_minVer = _disk.readByte();
f->_access = _disk.readByte();
f->_varUse = _disk.readUint16LE();
getDate(&(f->_modDate), _disk.readUint16LE());
getTime(&(f->_modTime), _disk.readUint16LE());
f->_dirHeadPtr = _disk.readUint16LE();
}
/* This is basically a loop based on the number of total files indicated by the header (including deleted file entries),
* which parses the file entry, and if it is a regular file (ie. active and not a pascal area) then it will create a file object.
* If it is instead a subdirectory file entry, it will use this same function to search in that directory creating files
* and continue like that until all directories have been explored. Along the way it puts together the current file path,
* which is stored with the file object so that the engine can search by path name.
*/
void ProDOSDisk::searchDirectory(DirHeader *h, uint16 p, uint16 n, Common::String path) {
// NB: p for previous set, but not currently used. This debug message silences any set-but-unused compiler warnings
debug(10, "searchDirectory(h:%p prev: %d next:%d, path:%s", (void *)h, p, n, path.c_str());
int currPos;
int parsedFiles = 0;
for (int i = 0; i < h->_fileCount; i++) {
// When we have read all the files for a given block (_entriesPerBlock), we need to change to the next block of the directory
if (parsedFiles == h->_entriesPerBlock) {
parsedFiles = 0;
_disk.seek(n * kBlockSize);
p = _disk.readUint16LE();
n = _disk.readUint16LE();
}
FileEntry fileEntry;
getFileEntry(&fileEntry);
parsedFiles++;
currPos = _disk.pos();
// It is a regular file if (dead < file type < pascal) and the file has a size
if ((kFileTypeDead < fileEntry._type) && (fileEntry._type < kFileTypePascal) && (fileEntry._eof > 0)) {
Common::String fileName = path + fileEntry._name;
ProDOSFile *currFile = new ProDOSFile(fileEntry._name, fileEntry._type, fileEntry._totalBlocks, fileEntry._eof, fileEntry._blockPtr, &_disk);
_files.setVal(fileName, Common::SharedPtr<ProDOSFile>(currFile));
_disk.seek(currPos);
// Otherwise, if it is a subdirectory, we want to explore that subdirectory
} else if (fileEntry._type == kFileTypeSubDir) {
_disk.seek(fileEntry._blockPtr * kBlockSize);
uint16 subP = _disk.readUint16LE();
uint16 subN = _disk.readUint16LE();
DirHeader subHead;
getDirectoryHeader(&subHead);
// Give it a temporary new path name by sticking the name of the subdirectory on to the end of the current path
Common::String subPath = Common::String(path + subHead._name + '/');
searchDirectory(&subHead, subP, subN, path);
_disk.seek(currPos);
}
}
}
/* The volume bitmap is a bitmap spanning as many blocks as is required to store 1 bit for every
* block on the disk. There are 8 bits per byte and 512 bytes per block, so it needs
* ((total_blocks / 4096) + 1 (if remainder)) * 512 bytes.
*/
void ProDOSDisk::getVolumeBitmap(VolHeader *h) {
int currPos = _disk.pos();
int bitmapSize;
bitmapSize = _volBlocks / 4096;
if ((_volBlocks % 4096) > 0) {
bitmapSize++;
}
_volBitmap = (byte *)malloc(bitmapSize * kBlockSize);
_disk.seek(h->_bitmapPtr * kBlockSize);
_disk.read(_volBitmap, bitmapSize);
_disk.seek(currPos);
}
/* Gets the volume information and parses the filesystem, adding file objects to a map as it goes */
bool ProDOSDisk::open(const Common::Path &filename) {
_disk.open(filename);
_disk.read(_loader1, kBlockSize);
_disk.read(_loader2, kBlockSize);
uint16 prev = _disk.readUint16LE(); // This is always going to be 0 for the volume header, but there's also no reason to skip it
uint16 next = _disk.readUint16LE();
VolHeader header;
getVolumeHeader(&header);
getVolumeBitmap(&header);
Common::String pathName; // This is so that the path name starts blank, and then for every directory searched it adds the directory name to the path
searchDirectory((DirHeader *)&header, prev, next, pathName);
return true; // When I get to error checking on this, the bool will be useful
}
/* Constructor simply calls open(), and if it is successful it prints a statement */
ProDOSDisk::ProDOSDisk(const Common::Path &filename) {
if (open(filename)) {
//debug("%s has been loaded", filename.c_str());
}
}
/* Destructor closes the disk and clears the map of files */
ProDOSDisk::~ProDOSDisk() {
_disk.close();
_files.clear();
free(_volBitmap); // Should this be free() or delete?
}
// --- Common::Archive methods ---
// Very simple, just checks if the dictionary contains the path name
bool ProDOSDisk::hasFile(const Common::Path &path) const {
Common::String name = path.toString();
return _files.contains(name);
}
/* To create a list of files in the Archive, we define an iterator for the object type
* used by the Archive member, and then loop through the hashmap, adding the object
* pointer returned as the value from the given path. This also returns the size.
*/
int ProDOSDisk::listMembers(Common::ArchiveMemberList &list) const {
int f = 0;
for (const auto &file : _files) {
list.push_back(Common::ArchiveMemberList::value_type(file._value));
++f;
}
return f;
}
// If the dictionary contains the path name (could probably call hasFile() instead), get the object
const Common::ArchiveMemberPtr ProDOSDisk::getMember(const Common::Path &path) const {
Common::String name = path.toString();
if (!_files.contains(name)) {
return Common::ArchiveMemberPtr();
}
return _files.getValOrDefault(name);
}
/* This method is called on Archive members as it searches for the correct one,
* so if this member is not the correct one, we return a null pointer.
*/
Common::SeekableReadStream *ProDOSDisk::createReadStreamForMember(const Common::Path &path) const {
Common::String name = path.toString();
if (!_files.contains(name)) {
return nullptr;
}
Common::SharedPtr<ProDOSFile> f = _files.getValOrDefault(name);
return f->createReadStream();
}
} // Namespace Common

218
common/formats/prodos.h Normal file
View File

@@ -0,0 +1,218 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COMMON_PRODOS_H
#define COMMON_PRODOS_H
#include "common/memstream.h"
#include "common/file.h"
#include "common/debug.h"
#include "common/error.h"
/* Quick note about ProDOS:
* This disk code handles inactive, seedling, sapling, tree, and subdirectory files.
* It does *not* handle sparse files at the moment. If a sparse file exists, it may not
* be read correctly. It also does not do anything with Pascal files, but those should not
* matter for game engines anyway.
*/
namespace Common {
// These values define for ProDOS how to read the file entry, and also whether it's a keyblock (if it is a directory header, it's the keyblock of that directory)
enum FileType : char {
kFileTypeDead = 0,
kFileTypeSeed = 1,
kFileTypeSapling = 2,
kFileTypeTree = 3,
kFileTypePascal = 4,
kFileTypeSubDir = 0x0D,
kFileTypeSubHead = 0x0E,
kFileTypeVolHead = 0x0F
};
/* File extensions for all the ProDOS supported file types
* NOTE: ProDOS user defined files are F1-F8. If they are ever required,
* they can be added to this enum.
*/
enum FileExt {
kFileExtNull = 0,
kFileExtBad = 1,
kFileExtTxt = 4,
kFileExtBin = 6,
kFileExtGfx = 8,
kFileExtDir = 0xF,
kFileExtDB = 0x19,
kFileExtWord = 0x1A,
kFileExtSpread = 0x1B,
kFileExtSTART = 0xB3,
kFileExtPascal = 0xEF,
kFileExtPDCI = 0xF0,
kFileExtPDRes = 0xF9,
kFileExtIBProg = 0xFA,
kFileExtIBVar = 0xFB,
kFileExtAPSProg = 0xFC,
kFileExtAPSVar = 0xFD,
kFileExtEDASM = 0xFE,
kFileExtSYS = 0xFF
};
/* A ProDOS file simply contains meta data about the file and the ability to
* find and put together the data blocks that make up the file contents.
* This implements Common::ArchiveMember so that it can be used directly in
* the Archive methods in ProDOSDisk.
*/
class ProDOSFile : public Common::ArchiveMember {
public:
ProDOSFile(char name[16], uint8 type, uint16 tBlk, uint32 eof, uint16 bPtr, Common::File *disk);
~ProDOSFile() {}; // File does not need a destructor, because the file it reads from is a pointer to Disk, and Disk has a destructor
// -- These are the Common::ArchiveMember related functions --
Common::String getName() const override; // Returns _name
Common::Path getPathInArchive() const override; // Returns _name
Common::String getFileName() const override; // Returns _name
Common::SeekableReadStream *createReadStream() const override; // This is what the archive needs to create a file
Common::SeekableReadStream *createReadStreamForAltStream(Common::AltStreamType altStreamType) const override;
void getDataBlock(byte *memOffset, int offset, int size) const; // Gets data up to the size of a single data block (512 bytes)
int parseIndexBlock(byte *memOffset, int blockNum, int cSize) const; // Uses getDataBlock() on every pointer in the index file, adding them to byte * memory block
// For debugging purposes, just prints the metadata
void printInfo();
private:
char _name[16];
uint8 _type; // As defined by enum FileType
uint16 _blockPtr; // Block index in volume of index block or data
uint16 _totalBlocks;
uint32 _eof; // End Of File, used generally as size (exception being sparse files)
Common::File *_disk; // This is a pointer because it is the same _disk as in ProDosDisk, passed to the file object
};
/* This class defines the entire disk volume. Upon using the open() method,
* it will parse the volume and add them to a hashmap where the file path
* returns a file object. This implements Common::Archive to allow regular
* file operations to work with it.
*/
class ProDOSDisk : public Common::Archive {
public:
static const int kBlockSize = 512; // A ProDOS block is always 512 bytes (should this be an enum?)
ProDOSDisk(const Common::Path &filename);
~ProDOSDisk(); // Frees the memory used in the dictionary and the volume bitmap
// Called from the constructor, it parses the volume and fills the hashmap with files
bool open(const Common::Path &filename);
// These are the Common::Archive related methods
bool hasFile(const Common::Path &path) const override;
int listMembers(Common::ArchiveMemberList &list) const override;
const Common::ArchiveMemberPtr getMember(const Common::Path &path) const override;
Common::SeekableReadStream *createReadStreamForMember(const Common::Path &path) const override;
private:
byte _loader1[kBlockSize]; // There's not much reason for these to be needed, but I included them just in case
byte _loader2[kBlockSize];
Common::String _name; // Name of volume
Common::File _disk; // The volume file itself
int _volBlocks; // Total blocks in volume
byte *_volBitmap; // This can determine if the volume is corrupt as it contains a bit for every block, where 0 = unused, 1 = used
Common::HashMap<Common::String, Common::SharedPtr<ProDOSFile>> _files; // Hashmap of files in the volume, where key=Path, Value=ProDOSFile
struct Date {
uint8 _day;
uint8 _month;
uint8 _year;
};
struct Time {
uint8 _hour;
uint8 _minute;
};
struct VolHeader {
uint8 _type; // Not really important for a volume header, as this will always be F
uint8 _nameLen;
char _name[16];
byte _reserved[8]; // Extra space reserved for possible future uses, not important
Date _date;
Time _time;
uint8 _ver;
uint8 _minVer; // Should pretty much always be 0 as far as I know
uint8 _access; // If this ends up useful, there should be an enum for the access values
uint8 _entryLen; // Always 27 in ProDOS 1.0
uint8 _entriesPerBlock; // Always 0D in ProDOS 1.0
uint16 _fileCount; // Number of files across all data blocks in this directory
uint16 _bitmapPtr; // Block pointer to the keyblock of the bitmap for the entire volume
uint16 _volBlocks; // Blocks in entire volume
};
struct DirHeader {
uint8 _type;
uint8 _nameLen;
char _name[16];
byte _reserved[8];
Date _date;
Time _time;
uint8 _ver;
uint8 _minVer;
uint8 _access;
uint8 _entryLen;
uint8 _entriesPerBlock;
uint16 _fileCount;
uint16 _parentBlockPtr; // These values allow ProDOS to navigate back out of a directory, but they aren't really needed by the class to navigate
uint8 _parentEntryIndex; // Index in the current directory
uint8 _parentEntryLen; // This is always 27 in ProDOS 1.0
};
struct FileEntry {
uint8 _type; // 0 = inactive, 1-3 = file, 4 = pascal area, 14 = subdirectory, 15 = volume directory
uint8 _nameLen;
char _name[16];
uint8 _ext; // File extension, uses the enum FileExt
uint16 _blockPtr; // Block pointer to data for seedling, index block for sapling, or master block for tree
uint16 _totalBlocks; // Really important to remember this is the total *including* the index block
uint32 _eof; // This is a long (3 bytes, read low to high) value representing the total readable data in a file (unless it's a sparse file, be careful!)
Date _date;
Time _time;
uint8 _ver;
uint8 _minVer;
uint8 _access;
uint16 _varUse;
Date _modDate;
Time _modTime;
uint16 _dirHeadPtr; // Pointer to the key block of the directory that contains this file entry
};
void getDate(Date *d, uint16 date); // Decompresses the date into a struct
void getTime(Time *t, uint16 time); // Decompresses the time into a struct
void getHeader(DirHeader *h); // Adds the main header values to the struct
void getDirectoryHeader(DirHeader *h); // Uses getHeader and then fills in the values for the parent directory
void getVolumeHeader(VolHeader *dir); // Uses getHeader and then fills in the volume related information (there is no parent directory to this one)
void getFileEntry(FileEntry *f); // Adds all of the file entry information to the struct
void searchDirectory(DirHeader *h, uint16 p, uint16 n, Common::String path); // Recursively searches all files within a directory, by calling itself for subdirectories
void getVolumeBitmap(VolHeader *h); // Puts together the volume bitmap
};
} // Namespace Common
#endif

1366
common/formats/quicktime.cpp Normal file

File diff suppressed because it is too large Load Diff

453
common/formats/quicktime.h Normal file
View File

@@ -0,0 +1,453 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//
// Heavily based on ffmpeg code.
//
// Copyright (c) 2001 Fabrice Bellard.
// First version by Francois Revol revol@free.fr
// Seek function by Gael Chardon gael.dev@4now.net
//
#ifndef COMMON_QUICKTIME_H
#define COMMON_QUICKTIME_H
#include "common/array.h"
#include "common/scummsys.h"
#include "common/path.h"
#include "common/stream.h"
#include "common/rational.h"
#include "common/types.h"
#include "common/rect.h"
namespace Common {
class MacResManager;
/**
* @defgroup common_quicktime Quicktime file parser
* @ingroup common
*
* @brief Parser for QuickTime/MPEG-4 files.
*
* @details File parser used in engines:
* - groovie
* - mohawk
* - mtropolis
* - sci
* @{
*/
class QuickTimeParser {
public:
QuickTimeParser();
virtual ~QuickTimeParser();
/**
* Load a QuickTime file
* @param filename the filename to load
*/
bool parseFile(const Path &filename);
/**
* Load a QuickTime file from a SeekableReadStream
* @param stream the stream to load
* @param disposeFileHandle whether to delete the stream after use
*/
bool parseStream(SeekableReadStream *stream, DisposeAfterUse::Flag disposeFileHandle = DisposeAfterUse::YES);
/**
* Close a QuickTime file
*/
void close();
/**
* Flattens edit lists to a single edit containing the first edit contiguously through the last edit.
* Used to work around bad edit offsets.
*/
void flattenEditLists();
/**
* Set the beginning offset of the video so we can modify the offsets in the stco
* atom of videos inside the Mohawk/mTropolis archives
* @param offset the beginning offset of the video
*/
void setChunkBeginOffset(uint32 offset) { _beginOffset = offset; }
/**
* Returns the movie time scale
*/
uint32 getTimeScale() const { return _timeScale; }
/** Find out if this parser has an open file handle */
bool isOpen() const { return _fd != nullptr; }
enum class QTVRType {
OTHER,
OBJECT,
PANORAMA
};
protected:
// This is the file handle from which data is read from. It can be the actual file handle or a decompressed stream.
SeekableReadStream *_fd;
struct TimeToSampleEntry {
int count;
int duration; // media time
};
struct SampleToChunkEntry {
uint32 first;
uint32 count;
uint32 id;
};
struct EditListEntry {
uint32 trackDuration; // movie time
uint32 timeOffset; // movie time
int32 mediaTime; // media time
Rational mediaRate;
};
public:
struct Track;
protected:
class SampleDesc {
public:
SampleDesc(Track *parentTrack, uint32 codecTag);
virtual ~SampleDesc();
uint32 getCodecTag() const { return _codecTag; }
SeekableReadStream *_extraData;
byte _objectTypeMP4;
protected:
Track *_parentTrack;
uint32 _codecTag;
};
public:
enum CodecType {
CODEC_TYPE_MOV_OTHER,
CODEC_TYPE_VIDEO,
CODEC_TYPE_AUDIO,
CODEC_TYPE_MIDI,
CODEC_TYPE_PANO
};
enum class GraphicsMode {
COPY = 0x0, // Directly copy the source image over the destination.
DITHER_COPY = 0x40, // Dither the image (if needed), otherwise copy.
BLEND = 0x20, // Blend source and destination pixel colors using opcolor values.
TRANSPARENT = 0x24, // Replace destination with source if not equal to opcolor.
STRAIGHT_ALPHA = 0x100, // Blend source and destination pixels, with the proportion controlled by the alpha channel.
PREMUL_WHITE_ALPHA = 0x101, // Blend after removing pre-multiplied white from the source.
PREMUL_BLACK_ALPHA = 0x102, // Blend after removing pre-multiplied black from the source.
STRAIGHT_ALPHA_BLEND = 0x104, // Similar to straight alpha, but the alpha for each channel is combined with the corresponding opcolor channel.
COMPOSITION = 0x103 // Render offscreen and then dither-copy to the main screen (tracks only).
};
struct PanoramaNode {
uint32 nodeID = 0;
uint32 timestamp = 0;
};
struct PanoramaInformation {
String name;
uint32 defNodeID = 0;
float defZoom = 0.0f;
Array<PanoramaNode> nodes;
};
struct PanoSampleHeader {
uint32 nodeID;
float defHPan;
float defVPan;
float defZoom;
// Constraints for this node; zero for default
float minHPan;
float minVPan;
float maxHPan;
float maxVPan;
float minZoom;
float maxZoom;
int32 nameStrOffset;
int32 commentStrOffset;
};
struct PanoHotSpot {
uint16 id;
uint32 type;
uint32 typeData; // for link and navg, the ID in the link and navg table
// Canonical view for this hotspot
float viewHPan;
float viewVPan;
float viewZoom;
Rect rect;
int32 mouseOverCursorID;
int32 mouseDownCursorID;
int32 mouseUpCursorID;
int32 nameStrOffset;
int32 commentStrOffset;
};
struct PanoHotSpotTable {
Array<PanoHotSpot> hotSpots;
PanoHotSpot *get(uint16 id) {
for (uint i = 0; i < hotSpots.size(); i++)
if (hotSpots[i].id == id)
return &hotSpots[i];
return nullptr;
}
};
struct PanoStringTable {
String strings;
String getString(int32 offset) const;
void debugPrint(int level, uint32 debugChannel, String prefix) const;
};
struct PanoLink {
uint16 id;
uint16 toNodeID;
// Values to set at the destination node
float toHPan;
float toVPan;
float toZoom;
int32 nameStrOffset;
int32 commentStrOffset;
};
struct PanoLinkTable {
Array<PanoLink> links;
PanoLink *get(uint16 id) {
for (uint i = 0; i < links.size(); i++)
if (links[i].id == id)
return &links[i];
return nullptr;
}
};
struct PanoNavigation {
uint16 id;
// Info for Navigable Movie Controller
float navgHPan; // the object's orientation in the scene
float navgVPan;
float navgZoom;
Rect zoomRect; // Starting rect for zoom out transitions
// Values to set at the destination node
int32 nameStrOffset;
int32 commentStrOffset;
};
struct PanoNavigationTable {
Array<PanoNavigation> navs;
PanoNavigation *get(uint16 id) {
for (uint i = 0; i < navs.size(); i++)
if (navs[i].id == id)
return &navs[i];
return nullptr;
}
};
struct PanoTrackSample {
PanoSampleHeader hdr;
PanoHotSpotTable hotSpotTable;
PanoStringTable strTable;
PanoLinkTable linkTable;
PanoNavigationTable navTable;
};
struct Track {
Track();
~Track();
uint32 chunkCount;
uint32 *chunkOffsets;
int timeToSampleCount;
TimeToSampleEntry *timeToSample;
uint32 sampleToChunkCount;
SampleToChunkEntry *sampleToChunk;
uint32 sampleSize;
uint32 sampleCount;
uint32 *sampleSizes;
uint32 keyframeCount;
uint32 *keyframes;
int32 timeScale; // media time
uint16 width;
uint16 height;
CodecType codecType;
Array<SampleDesc *> sampleDescs;
Common::Array<EditListEntry> editList;
uint32 frameCount; // from stts
uint32 duration; // movie time
uint32 mediaDuration; // media time
Rational scaleFactorX;
Rational scaleFactorY;
Common::String volume;
Common::String filename;
Common::String path;
Common::String directory;
int16 nlvlFrom;
int16 nlvlTo;
PanoramaInformation panoInfo;
Array<PanoTrackSample> panoSamples;
GraphicsMode graphicsMode; // Transfer mode
uint16 opcolor[3]; // RGB values used in the transfer mode specified by graphicsMode.
uint16 soundBalance; // Controls the sound mix between the computer's two speakers, usually set to 0.
uint targetTrack;
};
enum class MovieType {
kStandardObject = 1,
kOldNavigableMovieScene,
kObjectInScene
};
struct Navigation {
uint16 columns = 1;
uint16 rows = 1;
uint16 loop_size = 0; // Number of frames shot at each position
uint16 frame_duration = 1;
MovieType movie_type = MovieType::kStandardObject;
uint16 loop_ticks = 0; // Number of ticks before next frame of loop is displayed
float field_of_view = 1.0f;
float startHPan = 1.0f;
float startVPan = 1.0f;
float endHPan = 1.0f;
float endVPan = 1.0f;
float initialHPan = 1.0f;
float initialVPan = 1.0f;
};
protected:
virtual SampleDesc *readSampleDesc(Track *track, uint32 format, uint32 descSize) = 0;
uint32 _timeScale; // movie time
uint32 _duration; // movie time
Rational _scaleFactorX;
Rational _scaleFactorY;
Array<Track *> _tracks;
Navigation _nav;
QTVRType _qtvrType;
uint16 _winX;
uint16 _winY;
Track *_panoTrack;
void init();
private:
struct Atom {
uint32 type;
uint32 offset;
uint32 size;
};
struct ParseTable {
int (QuickTimeParser::*func)(Atom atom);
uint32 type;
};
DisposeAfterUse::Flag _disposeFileHandle;
const ParseTable *_parseTable;
uint32 _beginOffset;
MacResManager *_resFork;
bool _foundMOOV;
void initParseTable();
bool parsePanoramaAtoms();
int readDefault(Atom atom);
int readLeaf(Atom atom);
int readDREF(Atom atom);
int readELST(Atom atom);
int readHDLR(Atom atom);
int readMDHD(Atom atom);
int readMOOV(Atom atom);
int readMVHD(Atom atom);
int readTKHD(Atom atom);
int readTRAK(Atom atom);
int readSMHD(Atom atom);
int readSTCO(Atom atom);
int readSTSC(Atom atom);
int readSTSD(Atom atom);
int readSTSS(Atom atom);
int readSTSZ(Atom atom);
int readSTTS(Atom atom);
int readVMHD(Atom atom);
int readCMOV(Atom atom);
int readWAVE(Atom atom);
int readESDS(Atom atom);
int readSMI(Atom atom);
int readCTYP(Atom atom);
int readWLOC(Atom atom);
int readNAVG(Atom atom);
int readGMIN(Atom atom);
int readPINF(Atom atom);
int readPHDR(Atom atom);
int readPHOT(Atom atom);
int readSTRT(Atom atom);
int readPLNK(Atom atom);
int readPNAV(Atom atom);
};
/** @} */
} // End of namespace Common
#endif

272
common/formats/winexe.cpp Normal file
View File

@@ -0,0 +1,272 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/file.h"
#include "common/memstream.h"
#include "common/str.h"
#include "common/ustr.h"
#include "common/formats/winexe.h"
#include "common/formats/winexe_ne.h"
#include "common/formats/winexe_pe.h"
namespace Common {
WinResourceID &WinResourceID::operator=(const String &x) {
_name = x;
_idType = kIDTypeString;
return *this;
}
WinResourceID &WinResourceID::operator=(uint32 x) {
_id = x;
_idType = kIDTypeNumerical;
return *this;
}
bool WinResourceID::operator==(const String &x) const {
return _idType == kIDTypeString && _name.equalsIgnoreCase(x);
}
bool WinResourceID::operator==(const uint32 &x) const {
return _idType == kIDTypeNumerical && _id == x;
}
bool WinResourceID::operator==(const WinResourceID &x) const {
if (_idType != x._idType)
return false;
if (_idType == kIDTypeString)
return _name.equalsIgnoreCase(x._name);
if (_idType == kIDTypeNumerical)
return _id == x._id;
return true;
}
String WinResourceID::getString() const {
if (_idType != kIDTypeString)
return "";
return _name;
}
uint32 WinResourceID::getID() const {
if (_idType != kIDTypeNumerical)
return 0xffffffff;
return _id;
}
String WinResourceID::toString() const {
if (_idType == kIDTypeString)
return _name;
else if (_idType == kIDTypeNumerical)
return String::format("0x%08x", _id);
return "";
}
bool WinResources::loadFromEXE(const Path &fileName) {
if (fileName.empty())
return false;
File *file = new File();
if (!file->open(fileName)) {
delete file;
return false;
}
return loadFromEXE(file);
}
bool WinResources::loadFromCompressedEXE(const Path &fileName) {
// Based on https://www.cabextract.org.uk/libmspack/doc/szdd_kwaj_format.html
// TODO: Merge this with with loadFromEXE() so the handling of the compressed
// EXE's is transparent
File file;
if (!file.open(fileName))
return false;
// First part of the signature
if (file.readUint32BE() != MKTAG('S','Z','D','D'))
return false;
// Second part of the signature
if (file.readUint32BE() != 0x88F02733)
return false;
// Compression mode must be 'A'
if (file.readByte() != 'A')
return false;
file.readByte(); // file name character change
uint32 unpackedLength = file.readUint32LE();
byte *window = new byte[0x1000]();
int pos = 0x1000 - 16;
byte *unpackedData = (byte *)malloc(unpackedLength);
assert(unpackedData);
byte *dataPos = unpackedData;
uint32 remaining = unpackedLength;
// Apply simple LZSS decompression
for (;;) {
byte controlByte = file.readByte();
if (remaining == 0 || file.eos())
break;
for (byte i = 0; i < 8; i++) {
if (controlByte & (1 << i)) {
*dataPos++ = window[pos++] = file.readByte();
pos &= 0xFFF;
if (--remaining == 0)
break;
} else {
int matchPos = file.readByte();
int matchLen = file.readByte();
matchPos |= (matchLen & 0xF0) << 4;
matchLen = (matchLen & 0xF) + 3;
if ((uint32)matchLen > remaining)
matchLen = remaining;
remaining -= matchLen;
while (matchLen--) {
*dataPos++ = window[pos++] = window[matchPos++];
pos &= 0xFFF;
matchPos &= 0xFFF;
}
if (remaining == 0)
break;
}
}
}
delete[] window;
SeekableReadStream *stream = new MemoryReadStream(unpackedData, unpackedLength);
return loadFromEXE(stream);
}
WinResources *WinResources::createFromEXE(const Path &fileName) {
WinResources *exe;
// First try loading via the NE code
exe = new Common::NEResources();
if (exe->loadFromEXE(fileName)) {
return exe;
}
delete exe;
// Then try loading via the PE code
exe = new Common::PEResources();
if (exe->loadFromEXE(fileName)) {
return exe;
}
delete exe;
return nullptr;
}
WinResources *WinResources::createFromEXE(SeekableReadStream *stream) {
WinResources *exe;
// First try loading via the NE code
stream->seek(0);
exe = new Common::NEResources();
if (exe->loadFromEXE(stream, DisposeAfterUse::NO)) {
return exe;
}
delete exe;
// Then try loading via the PE code
stream->seek(0);
exe = new Common::PEResources();
if (exe->loadFromEXE(stream, DisposeAfterUse::NO)) {
return exe;
}
delete exe;
return nullptr;
}
WinResources::VersionInfo *WinResources::getVersionResource(const WinResourceID &id) {
VersionInfo *info = nullptr;
SeekableReadStream *res = getResource(kWinVersion, id);
if (res) {
info = parseVersionInfo(res);
delete res;
}
return info;
}
WinResources::VersionInfo::VersionInfo() {
fileVersion[0] = fileVersion[1] = fileVersion[2] = fileVersion[3] = 0;
productVersion[0] = productVersion[1] = productVersion[2] = productVersion[3] = 0;
fileFlagsMask = 0;
fileFlags = 0;
fileOS = 0;
fileType = 0;
fileSubtype = 0;
fileDate[0] = fileDate[1] = 0;
}
bool WinResources::VersionInfo::readVSVersionInfo(SeekableReadStream *res) {
// Signature check
if (res->readUint32LE() != 0xFEEF04BD)
return false;
res->readUint32LE(); // struct version
// The versions are stored a bit weird
fileVersion[1] = res->readUint16LE();
fileVersion[0] = res->readUint16LE();
fileVersion[3] = res->readUint16LE();
fileVersion[2] = res->readUint16LE();
productVersion[1] = res->readUint16LE();
productVersion[0] = res->readUint16LE();
productVersion[3] = res->readUint16LE();
productVersion[2] = res->readUint16LE();
fileFlagsMask = res->readUint32LE();
fileFlags = res->readUint32LE();
fileOS = res->readUint32LE();
fileType = res->readUint32LE();
fileSubtype = res->readUint32LE();
fileDate[0] = res->readUint32LE();
fileDate[1] = res->readUint32LE();
hash.setVal("File:", Common::U32String::format("%d.%d.%d.%d", fileVersion[0], fileVersion[1], fileVersion[2], fileVersion[3]));
hash.setVal("Prod:", Common::U32String::format("%d.%d.%d.%d", productVersion[0], productVersion[1], productVersion[2], productVersion[3]));
return true;
}
} // End of namespace Common

179
common/formats/winexe.h Normal file
View File

@@ -0,0 +1,179 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COMMON_WINEXE_H
#define COMMON_WINEXE_H
#include "common/hash-str.h"
#include "common/str.h"
#include "common/types.h"
namespace Common {
/**
* @defgroup common_winexe Windows resources
* @ingroup common
*
* @brief API for managing Windows resources.
*
* @{
*/
class Path;
class SeekableReadStream;
/** The default Windows resources. */
enum WinResourceType {
kWinCursor = 0x01,
kWinBitmap = 0x02,
kWinIcon = 0x03,
kWinMenu = 0x04,
kWinDialog = 0x05,
kWinString = 0x06,
kWinFontDir = 0x07,
kWinFont = 0x08,
kWinAccelerator = 0x09,
kWinRCData = 0x0A,
kWinMessageTable = 0x0B,
kWinGroupCursor = 0x0C,
kWinGroupIcon = 0x0E,
kWinNameTable = 0x0F,
kWinVersion = 0x10,
kWinDlgInclude = 0x11,
kWinPlugPlay = 0x13,
kWinVXD = 0x14,
kWinAniCursor = 0x15,
kWinAniIcon = 0x16,
kWinHTML = 0x17,
kWinManifest = 0x18
};
class WinResourceID {
public:
WinResourceID() { _idType = kIDTypeNull; _id = 0; }
WinResourceID(String x) { _idType = kIDTypeString; _name = x; }
WinResourceID(uint32 x) { _idType = kIDTypeNumerical; _id = x; }
WinResourceID &operator=(const String &x);
WinResourceID &operator=(uint32 x);
bool operator==(const String &x) const;
bool operator==(const uint32 &x) const;
bool operator==(const WinResourceID &x) const;
String getString() const;
uint32 getID() const;
String toString() const;
private:
/** An ID Type. */
enum IDType {
kIDTypeNull, ///< No type set
kIDTypeNumerical, ///< A numerical ID.
kIDTypeString ///< A string ID.
} _idType;
String _name; ///< The resource's string ID.
uint32 _id; ///< The resource's numerical ID.
};
struct WinResourceID_Hash {
uint operator()(const WinResourceID &id) const { return id.toString().hash(); }
};
struct WinResourceID_EqualTo {
bool operator()(const WinResourceID &id1, const WinResourceID &id2) const { return id1 == id2; }
};
/**
* A class able to load resources from a Windows Executable, such
* as cursors, bitmaps, and sounds.
*/
class WinResources {
public:
virtual ~WinResources() {};
/** Clear all information. */
virtual void clear() = 0;
/** Load from an EXE file. */
virtual bool loadFromEXE(const Path &fileName);
/** Load from a Windows compressed EXE file. */
virtual bool loadFromCompressedEXE(const Path &fileName);
/** Load from a stream. */
virtual bool loadFromEXE(SeekableReadStream *stream, DisposeAfterUse::Flag disposeFileHandle = DisposeAfterUse::YES) = 0;
/** Return a list of IDs for a given type. */
virtual const Array<WinResourceID> getIDList(const WinResourceID &type) const = 0;
/** Return a list of languages for a given type and ID. */
virtual const Array<WinResourceID> getLangList(const WinResourceID &type, const WinResourceID &id) const {
Array<WinResourceID> array;
return array;
}
/** Return a stream to the specified resource, taking the first language found (or 0 if non-existent). */
virtual SeekableReadStream *getResource(const WinResourceID &type, const WinResourceID &id) = 0;
/** Return a stream to the specified resource (or 0 if non-existent). */
virtual SeekableReadStream *getResource(const WinResourceID &type, const WinResourceID &id, const WinResourceID &lang) {
return getResource(type, id);
}
static WinResources *createFromEXE(const Path &fileName);
static WinResources *createFromEXE(SeekableReadStream *stream);
typedef Common::HashMap<Common::String, Common::U32String, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> VersionHash;
/** The structure of the version resource inside an NE EXE */
struct VersionInfo {
VersionInfo();
uint16 fileVersion[4];
uint16 productVersion[4];
uint32 fileFlagsMask;
uint32 fileFlags;
uint32 fileOS;
uint32 fileType;
uint32 fileSubtype;
uint32 fileDate[2];
VersionHash hash;
bool readVSVersionInfo(SeekableReadStream *res);
};
VersionInfo *getVersionResource(const WinResourceID &id);
/** Get a string from a string resource. */
virtual String loadString(uint32 stringID) = 0;
protected:
virtual VersionInfo *parseVersionInfo(SeekableReadStream *stream) = 0;
};
/** @} */
} // End of namespace Common
#endif

View File

@@ -0,0 +1,344 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/debug.h"
#include "common/str.h"
#include "common/stream.h"
#include "common/formats/winexe_ne.h"
namespace Common {
NEResources::NEResources() {
_exe = nullptr;
}
NEResources::~NEResources() {
clear();
}
void NEResources::clear() {
if (_exe) {
if (_disposeFileHandle == DisposeAfterUse::YES)
delete _exe;
_exe = nullptr;
}
_resources.clear();
}
bool NEResources::loadFromEXE(SeekableReadStream *stream, DisposeAfterUse::Flag disposeFileHandle) {
clear();
if (!stream)
return false;
_exe = stream;
_disposeFileHandle = disposeFileHandle;
uint32 offsetResourceTable = getResourceTableOffset();
if (offsetResourceTable == 0xFFFFFFFF)
return false;
if (offsetResourceTable == 0)
return true;
if (!readResourceTable(offsetResourceTable))
return false;
return true;
}
uint32 NEResources::getResourceTableOffset() {
if (!_exe)
return 0xFFFFFFFF;
if (!_exe->seek(0))
return 0xFFFFFFFF;
// 'MZ'
if (_exe->readUint16BE() != 0x4D5A)
return 0xFFFFFFFF;
if (!_exe->seek(60))
return 0xFFFFFFFF;
uint32 offsetSegmentEXE = _exe->readUint16LE();
if (!_exe->seek(offsetSegmentEXE))
return 0xFFFFFFFF;
// 'NE'
if (_exe->readUint16BE() != 0x4E45)
return 0xFFFFFFFF;
if (!_exe->seek(offsetSegmentEXE + 36))
return 0xFFFFFFFF;
uint32 offsetResourceTable = _exe->readUint16LE();
if (offsetResourceTable == 0)
// No resource table
return 0;
// Offset relative to the segment _exe header
offsetResourceTable += offsetSegmentEXE;
if (!_exe->seek(offsetResourceTable))
return 0xFFFFFFFF;
return offsetResourceTable;
}
/**
* A "resource ID -> name" mapping found in some NE executables, stored as a resource.
* Not described by any documentation I could find, but the corresponding #define can be encountered in some old header files.
* Mentioned by this blog post https://www.toptensoftware.com/blog/win3mu-5/ (section "The Mysterious Resource Type #15")
*
* The name table records have the following layout:
*
* struct
* {
* WORD lengthEntry; // length of this entry (including this field)
* WORD resourceType; // see WinResourceType constants
* WORD resourceId; // ordinal of resource | 0x8000
* BYTE paddingZero; // maybe ??
* CHAR szName[]; // null-terminated name
* }
*
*/
bool NEResources::readNameTable(uint32 offset, uint32 size) {
if (!_exe)
return false;
uint32 curPos = _exe->pos();
if (!_exe->seek(offset)) {
_exe->seek(curPos);
return false;
}
uint32 offsetEnd = offset + size;
while (_exe->pos() < offsetEnd) {
uint16 lengthEntry = _exe->readUint16LE();
if (lengthEntry == 0)
break;
uint16 resourceType = _exe->readUint16LE();
uint16 resourceId = _exe->readUint16LE();
resourceId &= 0x7FFF;
_exe->skip(1); // paddingZero
Common::String name = _exe->readString('\0', lengthEntry - 7);
if (name.size() + 8 != lengthEntry) {
warning("NEResources::readNameTable(): unexpected string length in name table (expected = %d, found = %d, string = %s)",
lengthEntry - 7, name.size() + 1, name.c_str());
}
debug(2, "NEResources::readNameTable(): resourceType = %d, resourceId = %d, name = %s",
resourceType, resourceId, name.c_str());
_nameTable[resourceType][resourceId] = name;
}
_exe->seek(curPos);
return true;
}
static const char *const s_resTypeNames[] = {
"", "cursor", "bitmap", "icon", "menu", "dialog", "string",
"font_dir", "font", "accelerator", "rc_data", "msg_table",
"group_cursor", "", "group_icon", "name_table", "version", "dlg_include",
"", "plug_play", "vxd", "ani_cursor", "ani_icon", "html",
"manifest"
};
bool NEResources::readResourceTable(uint32 offset) {
if (!_exe)
return false;
if (!_exe->seek(offset))
return false;
uint32 align = 1 << _exe->readUint16LE();
uint16 typeID = _exe->readUint16LE();
while (typeID != 0) {
// High bit of the type means integer type
WinResourceID type;
if (typeID & 0x8000)
type = typeID & 0x7FFF;
else
type = getResourceString(*_exe, offset + typeID);
uint16 resCount = _exe->readUint16LE();
debug(2, "%d resources in table", resCount);
_exe->skip(4); // reserved
if (resCount > 256) {
warning("NEResources::readResourceTable(): resource table looks malformed, %d entries > 256", resCount);
resCount = 256;
}
for (int i = 0; i < resCount; i++) {
Resource res;
// Resource properties
res.offset = _exe->readUint16LE() * align;
res.size = _exe->readUint16LE() * align;
res.flags = _exe->readUint16LE();
uint16 id = _exe->readUint16LE();
res.handle = _exe->readUint16LE();
res.usage = _exe->readUint16LE();
res.type = type;
// High bit means integer type
if (id & 0x8000)
res.id = id & 0x7FFF;
else
res.id = getResourceString(*_exe, offset + id);
if (typeID & 0x8000 && ((typeID & 0x7FFF) < ARRAYSIZE(s_resTypeNames)) && s_resTypeNames[typeID & 0x7FFF][0] != 0)
debug(2, "Found resource %s %s", s_resTypeNames[typeID & 0x7FFF], res.id.toString().c_str());
else
debug(2, "Found resource %s %s", type.toString().c_str(), res.id.toString().c_str());
_resources.push_back(res);
if (type == kWinNameTable)
readNameTable(res.offset, res.size);
}
typeID = _exe->readUint16LE();
}
return true;
}
String NEResources::getResourceString(SeekableReadStream &exe, uint32 offset) {
uint32 curPos = exe.pos();
if (!exe.seek(offset)) {
exe.seek(curPos);
return "";
}
uint8 length = exe.readByte();
String string;
for (uint16 i = 0; i < length; i++) {
char b = (char)exe.readByte();
// Do not read beyond end of string
if (!b) {
break;
}
string += b;
}
exe.seek(curPos);
return string;
}
const NEResources::Resource *NEResources::findResource(const WinResourceID &type, const WinResourceID &id) const {
for (const auto &resource : _resources) {
if (resource.type == type &&
(resource.id == id ||
(!_nameTable.empty() &&
_nameTable.contains(resource.type) &&
_nameTable[resource.type].contains(resource.id) &&
_nameTable[resource.type][resource.id] == id.toString())))
return &resource;
}
return nullptr;
}
SeekableReadStream *NEResources::getResource(const WinResourceID &type, const WinResourceID &id) {
const Resource *res = findResource(type, id);
if (!res)
return nullptr;
_exe->seek(res->offset);
return _exe->readStream(res->size);
}
const Array<WinResourceID> NEResources::getIDList(const WinResourceID &type) const {
Array<WinResourceID> idArray;
for (const auto &resource : _resources)
if (resource.type == type)
idArray.push_back(resource.id);
return idArray;
}
String NEResources::loadString(uint32 stringID) {
// This is how the resource ID is calculated
String string;
SeekableReadStream *stream = getResource(kWinString, (stringID >> 4) + 1);
if (!stream)
return string;
// Skip over strings we don't care about
uint32 startString = stringID & ~0xF;
for (uint32 i = startString; i < stringID; i++)
stream->skip(stream->readByte());
byte size = stream->readByte();
while (size--)
string += (char)stream->readByte();
delete stream;
return string;
}
WinResources::VersionInfo *NEResources::parseVersionInfo(SeekableReadStream *res) {
VersionInfo *info = new VersionInfo;
while (res->pos() < res->size() && !res->eos()) {
while (res->pos() % 4 && !res->eos()) // Pad to 4
res->readByte();
/* uint16 len = */ res->readUint16LE();
/* uint16 valLen = */ res->readUint16LE();
uint16 c;
Common::String key;
while ((c = res->readByte()) != 0 && !res->eos())
key += c;
while (res->pos() % 4 && !res->eos()) // Pad to 4
res->readByte();
if (res->eos())
break;
if (key == "VS_VERSION_INFO") {
if (!info->readVSVersionInfo(res))
return info;
}
}
return info;
}
} // End of namespace Common

119
common/formats/winexe_ne.h Normal file
View File

@@ -0,0 +1,119 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COMMON_WINEXE_NE_H
#define COMMON_WINEXE_NE_H
#include "common/list.h"
#include "common/str.h"
#include "common/formats/winexe.h"
namespace Common {
/**
* @defgroup common_winexe_ne Windows New Executable resources
* @ingroup common_winexe
*
* @brief API for managing Windows New Executable resources.
*
* @{
*/
template<class T> class Array;
class SeekableReadStream;
/**
* A class able to load resources from a Windows New Executable, such
* as cursors, bitmaps, and sounds.
*
* See https://en.wikipedia.org/wiki/New_Executable for more info.
*/
class NEResources : public WinResources {
public:
NEResources();
~NEResources();
/** Clear all information. */
void clear();
/** Load from an EXE file. */
using WinResources::loadFromEXE;
/** Load from a stream. */
bool loadFromEXE(SeekableReadStream *stream, DisposeAfterUse::Flag disposeFileHandle = DisposeAfterUse::YES);
/** Return a list of resources for a given type. */
const Array<WinResourceID> getIDList(const WinResourceID &type) const;
/** Return a stream to the specified resource (or 0 if non-existent). */
SeekableReadStream *getResource(const WinResourceID &type, const WinResourceID &id);
/** Get a string from a string resource. */
String loadString(uint32 stringID);
protected:
VersionInfo *parseVersionInfo(SeekableReadStream *stream);
private:
/** A resource. */
struct Resource {
WinResourceID id;
WinResourceID type; ///< Type of the resource.
uint32 offset; ///< Offset within the EXE.
uint32 size; ///< Size of the data.
uint16 flags;
uint16 handle;
uint16 usage;
};
SeekableReadStream *_exe; ///< Current file.
DisposeAfterUse::Flag _disposeFileHandle;
/** All resources. */
List<Resource> _resources;
typedef HashMap<WinResourceID, Common::String, WinResourceID_Hash, WinResourceID_EqualTo> IDMap;
typedef HashMap<WinResourceID, IDMap, WinResourceID_Hash, WinResourceID_EqualTo> TypeMap;
TypeMap _nameTable;
/** Read the offset to the resource table. */
uint32 getResourceTableOffset();
/** Read the resource table. */
bool readResourceTable(uint32 offset);
/** Read the name table. */
bool readNameTable(uint32 offset, uint32 size);
/** Find a specific resource. */
const Resource *findResource(const WinResourceID &type, const WinResourceID &id) const;
/** Read a resource string. */
static String getResourceString(SeekableReadStream &exe, uint32 offset);
};
/** @} */
} // End of namespace Common
#endif

View File

@@ -0,0 +1,310 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/array.h"
#include "common/debug.h"
#include "common/endian.h"
#include "common/str.h"
#include "common/stream.h"
#include "common/formats/winexe_pe.h"
namespace Common {
PEResources::PEResources() {
_exe = nullptr;
_disposeFileHandle = DisposeAfterUse::YES;
}
PEResources::~PEResources() {
clear();
}
void PEResources::clear() {
_sections.clear();
_resources.clear();
if (_exe) {
if (_disposeFileHandle == DisposeAfterUse::YES)
delete _exe;
_exe = nullptr;
}
}
bool PEResources::loadFromEXE(SeekableReadStream *stream, DisposeAfterUse::Flag disposeFileHandle) {
clear();
_exe = stream;
_disposeFileHandle = disposeFileHandle;
if (!stream)
return false;
if (stream->readUint16BE() != MKTAG16('M', 'Z'))
return false;
stream->skip(58);
uint32 peOffset = stream->readUint32LE();
if (!peOffset || peOffset >= (uint32)stream->size())
return false;
stream->seek(peOffset);
if (stream->readUint32BE() != MKTAG('P','E',0,0))
return false;
stream->skip(2);
uint16 sectionCount = stream->readUint16LE();
stream->skip(12);
uint16 optionalHeaderSize = stream->readUint16LE();
stream->skip(optionalHeaderSize + 2);
// Read in all the sections
for (uint16 i = 0; i < sectionCount; i++) {
char sectionName[9];
stream->read(sectionName, 8);
sectionName[8] = 0;
Section section;
stream->skip(4);
section.virtualAddress = stream->readUint32LE();
section.size = stream->readUint32LE();
section.offset = stream->readUint32LE();
stream->skip(16);
_sections[sectionName] = section;
}
// Currently, we require loading a resource section
if (!_sections.contains(".rsrc")) {
clear();
return false;
}
Section &resSection = _sections[".rsrc"];
parseResourceLevel(resSection, resSection.offset, 0);
return true;
}
void PEResources::parseResourceLevel(Section &section, uint32 offset, int level) {
_exe->seek(offset + 12);
uint16 namedEntryCount = _exe->readUint16LE();
uint16 intEntryCount = _exe->readUint16LE();
for (uint32 i = 0; i < (uint32)(namedEntryCount + intEntryCount); i++) {
uint32 value = _exe->readUint32LE();
WinResourceID id;
if (value & 0x80000000) {
value &= 0x7fffffff;
uint32 startPos = _exe->pos();
_exe->seek(section.offset + (value & 0x7fffffff));
// Read in the name, truncating from unicode to ascii
String name;
uint16 nameLength = _exe->readUint16LE();
while (nameLength--)
name += (char)(_exe->readUint16LE() & 0xff);
_exe->seek(startPos);
id = name;
} else {
id = value;
}
uint32 nextOffset = _exe->readUint32LE();
uint32 lastOffset = _exe->pos();
if (level == 0)
_curType = id;
else if (level == 1)
_curID = id;
else if (level == 2)
_curLang = id;
if (level < 2) {
// Time to dive down further
parseResourceLevel(section, section.offset + (nextOffset & 0x7fffffff), level + 1);
} else {
_exe->seek(section.offset + nextOffset);
Resource resource;
resource.offset = _exe->readUint32LE() + section.offset - section.virtualAddress;
resource.size = _exe->readUint32LE();
debug(4, "Found resource '%s' '%s' '%s' at %d of size %d", _curType.toString().c_str(),
_curID.toString().c_str(), _curLang.toString().c_str(), resource.offset, resource.size);
_resources[_curType][_curID][_curLang] = resource;
}
_exe->seek(lastOffset);
}
}
const Array<WinResourceID> PEResources::getTypeList() const {
Array<WinResourceID> array;
if (!_exe)
return array;
for (const auto &resource : _resources)
array.push_back(resource._key);
return array;
}
const Array<WinResourceID> PEResources::getIDList(const WinResourceID &type) const {
Array<WinResourceID> array;
if (!_exe || !_resources.contains(type))
return array;
const IDMap &idMap = _resources[type];
for (const auto &id : idMap)
array.push_back(id._key);
return array;
}
const Array<WinResourceID> PEResources::getLangList(const WinResourceID &type, const WinResourceID &id) const {
Array<WinResourceID> array;
if (!_exe || !_resources.contains(type))
return array;
const IDMap &idMap = _resources[type];
if (!idMap.contains(id))
return array;
const LangMap &langMap = idMap[id];
for (const auto &lang : langMap)
array.push_back(lang._key);
return array;
}
SeekableReadStream *PEResources::getResource(const WinResourceID &type, const WinResourceID &id) {
Array<WinResourceID> langList = getLangList(type, id);
if (langList.empty())
return nullptr;
const Resource &resource = _resources[type][id][langList[0]];
_exe->seek(resource.offset);
return _exe->readStream(resource.size);
}
SeekableReadStream *PEResources::getResource(const WinResourceID &type, const WinResourceID &id, const WinResourceID &lang) {
if (!_exe || !_resources.contains(type))
return nullptr;
const IDMap &idMap = _resources[type];
if (!idMap.contains(id))
return nullptr;
const LangMap &langMap = idMap[id];
if (!langMap.contains(lang))
return nullptr;
const Resource &resource = langMap[lang];
_exe->seek(resource.offset);
return _exe->readStream(resource.size);
}
String PEResources::loadString(uint32 stringID) {
String string;
SeekableReadStream *stream = getResource(kWinString, (stringID >> 4) + 1);
if (!stream)
return string;
// Skip over strings we don't care about
uint32 startString = stringID & ~0xF;
for (uint32 i = startString; i < stringID; i++)
stream->skip(stream->readUint16LE() * 2);
// HACK: Truncate UTF-16 down to ASCII
byte size = stream->readUint16LE();
while (size--)
string += (char)(stream->readUint16LE() & 0xFF);
delete stream;
return string;
}
WinResources::VersionInfo *PEResources::parseVersionInfo(SeekableReadStream *res) {
VersionInfo *info = new VersionInfo;
while (res->pos() < res->size() && !res->eos()) {
while (res->pos() % 4 && !res->eos()) // Pad to 4
res->readByte();
/* uint16 len = */ res->readUint16LE();
uint16 valLen = res->readUint16LE();
uint16 type = res->readUint16LE();
uint16 c;
if (res->eos())
break;
Common::U32String key;
while ((c = res->readUint16LE()) != 0 && !res->eos())
key += c;
while (res->pos() % 4 && !res->eos()) // Pad to 4
res->readByte();
if (res->eos())
break;
if (type != 0) { // text
Common::U32String value;
for (int j = 0; j < valLen; j++) {
uint16 ch = res->readUint16LE();
if (ch)
value += ch;
}
info->hash.setVal(key.encode(), value);
} else {
if (key == "VS_VERSION_INFO") {
if (!info->readVSVersionInfo(res))
return info;
}
}
}
return info;
}
} // End of namespace Common

114
common/formats/winexe_pe.h Normal file
View File

@@ -0,0 +1,114 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COMMON_WINEXE_PE_H
#define COMMON_WINEXE_PE_H
#include "common/hash-str.h"
#include "common/hashmap.h"
#include "common/str.h"
#include "common/formats/winexe.h"
namespace Common {
/**
* @defgroup common_winexe_ne Windows Portable Executable resources
* @ingroup common_winexe
*
* @brief API for managing Windows Portable Executable resources.
*
* @{
*/
template<class T> class Array;
class SeekableReadStream;
/**
* A class able to load resources from a Windows Portable Executable, such
* as cursors, bitmaps, and sounds.
*/
class PEResources : public WinResources {
public:
PEResources();
~PEResources();
/** Clear all information. */
void clear();
/** Load from an EXE file. */
using WinResources::loadFromEXE;
/** Load from a stream. */
bool loadFromEXE(SeekableReadStream *stream, DisposeAfterUse::Flag disposeFileHandle = DisposeAfterUse::YES);
/** Return a list of resource types. */
const Array<WinResourceID> getTypeList() const;
/** Return a list of IDs for a given type. */
const Array<WinResourceID> getIDList(const WinResourceID &type) const;
/** Return a list of languages for a given type and ID. */
const Array<WinResourceID> getLangList(const WinResourceID &type, const WinResourceID &id) const;
/** Return a stream to the specified resource, taking the first language found (or 0 if non-existent). */
SeekableReadStream *getResource(const WinResourceID &type, const WinResourceID &id);
/** Return a stream to the specified resource (or 0 if non-existent). */
SeekableReadStream *getResource(const WinResourceID &type, const WinResourceID &id, const WinResourceID &lang);
/** Get a string from a string resource. */
String loadString(uint32 stringID);
protected:
VersionInfo *parseVersionInfo(SeekableReadStream *stream);
private:
struct Section {
uint32 virtualAddress;
uint32 size;
uint32 offset;
};
HashMap<String, Section, IgnoreCase_Hash, IgnoreCase_EqualTo> _sections;
SeekableReadStream *_exe;
DisposeAfterUse::Flag _disposeFileHandle;
void parseResourceLevel(Section &section, uint32 offset, int level);
WinResourceID _curType, _curID, _curLang;
struct Resource {
uint32 offset;
uint32 size;
};
typedef HashMap<WinResourceID, Resource, WinResourceID_Hash, WinResourceID_EqualTo> LangMap;
typedef HashMap<WinResourceID, LangMap, WinResourceID_Hash, WinResourceID_EqualTo> IDMap;
typedef HashMap<WinResourceID, IDMap, WinResourceID_Hash, WinResourceID_EqualTo> TypeMap;
TypeMap _resources;
};
/** @} */
} // End of namespace Common
#endif

View File

@@ -0,0 +1,546 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/formats/xmlparser.h"
#include "common/archive.h"
#include "common/fs.h"
#include "common/memstream.h"
#include "common/system.h"
namespace Common {
XMLParser::~XMLParser() {
while (!_activeKey.empty())
freeNode(_activeKey.pop());
delete _XMLkeys;
delete _stream;
for (auto *layout : _layoutList)
delete layout;
_layoutList.clear();
}
bool XMLParser::loadFile(const Path &filename) {
_stream = SearchMan.createReadStreamForMember(filename);
if (!_stream)
return false;
_fileName = filename;
return true;
}
bool XMLParser::loadFile(const FSNode &node) {
_stream = node.createReadStream();
if (!_stream)
return false;
_fileName = node.getName();
return true;
}
bool XMLParser::loadBuffer(const byte *buffer, uint32 size, DisposeAfterUse::Flag disposable) {
_stream = new MemoryReadStream(buffer, size, disposable);
_fileName = "Memory Stream";
return true;
}
bool XMLParser::loadStream(SeekableReadStream *stream, const String &name) {
_stream = stream;
_fileName = name;
return _stream != nullptr;
}
void XMLParser::close() {
delete _stream;
_stream = nullptr;
}
bool XMLParser::parserError(const String &errStr) {
_state = kParserError;
const int startPosition = _stream->pos();
int currentPosition = startPosition;
int lineCount = 1;
char c = 0;
_stream->seek(0, SEEK_SET);
while (currentPosition--) {
c = _stream->readByte();
if (c == '\n' || c == '\r')
lineCount++;
}
assert(_stream->pos() == startPosition);
currentPosition = startPosition;
Common::String errorMessage = Common::String::format("\n File <%s>, line %d:\n", _fileName.toString().c_str(), lineCount);
if (startPosition > 1) {
int keyOpening = 0;
int keyClosing = 0;
while (currentPosition-- && keyOpening == 0) {
_stream->seek(-2, SEEK_CUR);
c = _stream->readByte();
if (c == '<')
keyOpening = currentPosition - 1;
else if (c == '>')
keyClosing = currentPosition;
}
_stream->seek(startPosition, SEEK_SET);
currentPosition = startPosition;
while (keyClosing == 0 && c && currentPosition++) {
c = _stream->readByte();
if (c == '>')
keyClosing = currentPosition;
}
currentPosition = (keyClosing - keyOpening);
_stream->seek(keyOpening, SEEK_SET);
while (currentPosition--)
errorMessage += (char)_stream->readByte();
}
errorMessage += "\n\nParser error: ";
errorMessage += errStr;
errorMessage += "\n\n";
g_system->logMessage(LogMessageType::kError, errorMessage.c_str());
return false;
}
bool XMLParser::parseXMLHeader(ParserNode *node) {
assert(node->header);
if (_activeKey.size() != 1)
return parserError("XML Header is expected in the global scope.");
if (!node->values.contains("version"))
return parserError("Missing XML version in XML header.");
if (node->values["version"] != "1.0")
return parserError("Unsupported XML version.");
return true;
}
bool XMLParser::parseActiveKey(bool closed) {
bool ignore = false;
assert(_activeKey.empty() == false);
ParserNode *key = _activeKey.top();
if (key->name == "xml" && key->header == true) {
assert(closed);
return parseXMLHeader(key) && closeKey();
}
XMLKeyLayout *layout = (_activeKey.size() == 1) ? _XMLkeys : getParentNode(key)->layout;
if (layout->children.contains(key->name)) {
key->layout = layout->children[key->name];
StringMap localMap = key->values;
int keyCount = localMap.size();
for (const auto &prop : key->layout->properties) {
if (prop.required && !localMap.contains(prop.name))
return parserError("Missing required property '" + prop.name + "' inside key '" + key->name + "'");
else if (localMap.contains(prop.name)) {
keyCount--;
localMap.erase(prop.name);
}
}
if (keyCount > 0) {
Common::String missingKeys;
for (auto i = localMap.begin(); i != localMap.end(); ++i)
missingKeys += i->_key + ' ';
return parserError(Common::String::format("Unhandled property inside key '%s' (%s, %d items).", key->name.c_str(), missingKeys.c_str(), keyCount));
}
} else {
if (!handleUnknownKey(key))
return parserError("Unexpected key in the active scope ('" + key->name + "').");
ignore = true;
}
// check if any of the parents must be ignored.
// if a parent is ignored, all children are too.
for (int i = _activeKey.size() - 1; i >= 0; --i) {
if (_activeKey[i]->ignore)
ignore = true;
}
if (ignore == false && keyCallback(key) == false) {
// HACK: People may be stupid and overlook the fact that
// when keyCallback() fails, a parserError() must be set.
// We set it manually in that case.
if (_state != kParserError)
parserError("Unhandled exception when parsing '" + key->name + "' key.");
return false;
}
if (closed)
return closeKey();
return true;
}
bool XMLParser::parseKeyValue(String keyName) {
assert(_activeKey.empty() == false);
if (_activeKey.top()->values.contains(keyName))
return false;
_token.clear();
char stringStart;
if (_char == '"' || _char == '\'') {
stringStart = _char;
_char = _stream->readByte();
while (_char && _char != stringStart) {
_token += _char;
_char = _stream->readByte();
}
if (_char == 0)
return false;
_char = _stream->readByte();
} else if (!parseToken()) {
return false;
}
_activeKey.top()->values[keyName] = _token;
return true;
}
bool XMLParser::parseIntegerKey(const char *key, int count, ...) {
bool result;
va_list args;
va_start(args, count);
result = vparseIntegerKey(key, count, args);
va_end(args);
return result;
}
bool XMLParser::parseIntegerKey(const String &key, int count, ...) {
bool result;
va_list args;
va_start(args, count);
result = vparseIntegerKey(key.c_str(), count, args);
va_end(args);
return result;
}
bool XMLParser::vparseIntegerKey(const char *key, int count, va_list args) {
char *parseEnd;
int *num_ptr;
while (count--) {
while (isSpace(*key))
key++;
num_ptr = va_arg(args, int*);
*num_ptr = strtol(key, &parseEnd, 10);
key = parseEnd;
while (isSpace(*key))
key++;
if (count && *key++ != ',')
return false;
}
return (*key == 0);
}
bool XMLParser::closeKey() {
bool ignore = false;
bool result = true;
for (int i = _activeKey.size() - 1; i >= 0; --i) {
if (_activeKey[i]->ignore)
ignore = true;
}
if (ignore == false)
result = closedKeyCallback(_activeKey.top());
freeNode(_activeKey.pop());
return result;
}
bool XMLParser::parse() {
if (_stream == nullptr)
return false;
// Make sure we are at the start of the stream.
_stream->seek(0, SEEK_SET);
if (_XMLkeys == nullptr)
buildLayout();
while (!_activeKey.empty())
freeNode(_activeKey.pop());
cleanup();
bool activeClosure = false;
bool activeHeader = false;
bool selfClosure;
_state = kParserNeedHeader;
_activeKey.clear();
_char = _stream->readByte();
while (_char && _state != kParserError) {
if (skipSpaces())
continue;
if (skipComments())
continue;
switch (_state) {
case kParserNeedHeader:
case kParserNeedKey:
if (_char != '<') {
if (_allowText) {
Common::String text;
do {
text += _char;
_char = _stream->readByte();
} while (_char != '<' && _char);
if (!_char) {
parserError("Unexpected end of file.");
break;
}
if (!textCallback(text)) {
parserError("Failed to process text segment.");
break;
}
} else {
parserError("Parser expecting key start.");
break;
}
}
if ((_char = _stream->readByte()) == 0) {
parserError("Unexpected end of file.");
break;
}
if (_state == kParserNeedHeader) {
if (_char != '?') {
parserError("Expecting XML header.");
break;
}
_char = _stream->readByte();
activeHeader = true;
} else if (_char == '/') {
_char = _stream->readByte();
activeClosure = true;
} else if (_char == '?') {
parserError("Unexpected header. There may only be one XML header per file.");
break;
}
_state = kParserNeedKeyName;
break;
case kParserNeedKeyName:
if (!parseToken()) {
parserError("Invalid key name.");
break;
}
if (activeClosure) {
if (_activeKey.empty() || _token != _activeKey.top()->name) {
parserError("Unexpected closure.");
break;
}
} else {
ParserNode *node = allocNode(); // new ParserNode;
node->name = _token;
node->ignore = false;
node->header = activeHeader;
node->depth = _activeKey.size();
node->layout = nullptr;
_activeKey.push(node);
}
_state = kParserNeedPropertyName;
break;
case kParserNeedPropertyName:
if (activeClosure) {
if (!closeKey()) {
parserError("Missing data when closing key '" + _activeKey.top()->name + "'.");
break;
}
activeClosure = false;
if (_char != '>')
parserError("Invalid syntax in key closure.");
else
_state = kParserNeedKey;
_char = _stream->readByte();
break;
}
selfClosure = false;
if (_char == '/' || (_char == '?' && activeHeader)) {
selfClosure = true;
_char = _stream->readByte();
}
if (_char == '>') {
if (activeHeader && !selfClosure) {
parserError("XML Header must be self-closed.");
} else if (parseActiveKey(selfClosure)) {
_char = _stream->readByte();
_state = kParserNeedKey;
}
activeHeader = false;
break;
}
if (selfClosure)
parserError("Expecting key closure after '/' symbol.");
else if (!parseToken())
parserError("Error when parsing key value.");
else
_state = kParserNeedPropertyOperator;
break;
case kParserNeedPropertyOperator:
if (_char != '=')
parserError("Syntax error after key name.");
else
_state = kParserNeedPropertyValue;
_char = _stream->readByte();
break;
case kParserNeedPropertyValue:
if (!parseKeyValue(_token))
parserError("Invalid key value.");
else
_state = kParserNeedPropertyName;
break;
default:
break;
}
}
if (_state == kParserError)
return false;
if (_state != kParserNeedKey || !_activeKey.empty())
return parserError("Unexpected end of file.");
return true;
}
bool XMLParser::skipSpaces() {
if (!isSpace(_char))
return false;
while (_char && isSpace(_char))
_char = _stream->readByte();
return true;
}
bool XMLParser::skipComments() {
if (_char == '<') {
_char = _stream->readByte();
if (_char != '!') {
_stream->seek(-1, SEEK_CUR);
_char = '<';
return false;
}
if (_stream->readByte() != '-' || _stream->readByte() != '-')
return parserError("Malformed comment syntax.");
_char = _stream->readByte();
while (_char) {
if (_char == '-') {
if (_stream->readByte() == '-') {
if (_stream->readByte() != '>')
return parserError("Malformed comment (double-hyphen inside comment body).");
_char = _stream->readByte();
return true;
}
}
_char = _stream->readByte();
}
return parserError("Comment has no closure.");
}
return false;
}
bool XMLParser::parseToken() {
_token.clear();
while (isValidNameChar(_char)) {
_token += _char;
_char = _stream->readByte();
}
return isSpace(_char) != 0 || _char == '>' || _char == '=' || _char == '/';
}
} // End of namespace Common

394
common/formats/xmlparser.h Normal file
View File

@@ -0,0 +1,394 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef XML_PARSER_H
#define XML_PARSER_H
#include "common/scummsys.h"
#include "common/types.h"
#include "common/fs.h"
#include "common/list.h"
#include "common/hashmap.h"
#include "common/hash-str.h"
#include "common/stack.h"
#include "common/memorypool.h"
namespace Common {
/**
* @defgroup common_xmlparser XML parser
* @ingroup common
*
* @brief The XML parser allows for parsing XML-like files.
*
* @{
*/
class SeekableReadStream;
#define MAX_XML_DEPTH 8
#define XML_KEY(keyName) {\
lay = new CustomXMLKeyLayout;\
lay->callback = (&kLocalParserName::parserCallback_##keyName);\
layout.top()->children[#keyName] = lay;\
layout.push(lay); \
_layoutList.push_back(lay);
#define XML_KEY_RECURSIVE(keyName) {\
layout.top()->children[#keyName] = layout.top();\
layout.push(layout.top());\
}
#define KEY_END() layout.pop(); }
#define XML_PROP(propName, req) {\
prop.name = #propName; \
prop.required = req; \
layout.top()->properties.push_back(prop); }
#define CUSTOM_XML_PARSER(parserName) \
protected: \
typedef parserName kLocalParserName; \
bool keyCallback(ParserNode *node) override {return node->layout->doCallback(this, node); }\
struct CustomXMLKeyLayout : public XMLKeyLayout {\
typedef bool (parserName::*ParserCallback)(ParserNode *node);\
ParserCallback callback;\
bool doCallback(XMLParser *parent, ParserNode *node) {return ((kLocalParserName *)parent->*callback)(node);} };\
void buildLayout() override { \
Common::Stack<XMLKeyLayout *> layout; \
CustomXMLKeyLayout *lay = 0; \
XMLKeyLayout::XMLKeyProperty prop; \
_XMLkeys = new CustomXMLKeyLayout; \
layout.push(_XMLkeys);
#define PARSER_END() layout.clear(); }
/**
* The base XMLParser class implements generic functionality for parsing
* XML-like files.
*
* In order to use it, it must be inherited with a child class that implements
* the XMLParser::keyCallback() function.
*
* @see XMLParser::keyCallback()
*/
class XMLParser {
public:
/**
* Parser constructor.
*/
XMLParser() : _XMLkeys(nullptr), _stream(nullptr), _allowText(false), _char(0) {}
virtual ~XMLParser();
/** Active state for the parser */
enum ParserState {
kParserNeedHeader,
kParserNeedKey,
kParserNeedKeyName,
kParserNeedPropertyName,
kParserNeedPropertyOperator,
kParserNeedPropertyValue,
kParserError
};
struct XMLKeyLayout;
struct ParserNode;
typedef HashMap<String, XMLParser::XMLKeyLayout*, IgnoreCase_Hash, IgnoreCase_EqualTo> ChildMap;
/** nested struct representing the layout of the XML file */
struct XMLKeyLayout {
struct XMLKeyProperty {
String name;
bool required;
};
List<XMLKeyProperty> properties;
ChildMap children;
virtual bool doCallback(XMLParser *parent, ParserNode *node) = 0;
virtual ~XMLKeyLayout() {
properties.clear();
}
};
XMLKeyLayout *_XMLkeys;
/** Struct representing a parsed node */
struct ParserNode {
String name;
StringMap values;
bool ignore;
bool header;
int depth;
XMLKeyLayout *layout;
};
ObjectPool<ParserNode, MAX_XML_DEPTH> _nodePool;
ParserNode *allocNode() {
return new (_nodePool) ParserNode;
}
void freeNode(ParserNode *node) {
_nodePool.deleteChunk(node);
}
/**
* Loads a file into the parser.
* Used for the loading of Theme Description files
* straight from the filesystem.
*
* @param filename Name of the file to load.
*/
bool loadFile(const Path &filename);
bool loadFile(const FSNode &node);
/**
* Loads a memory buffer into the parser.
* Used for loading the default theme fallback directly
* from memory if no themes can be found.
*
* @param buffer Pointer to the buffer.
* @param size Size of the buffer
* @param disposable Sets if the XMLParser owns the buffer,
* i.e. if it can be freed safely after it's
* no longer needed by the parser.
*/
bool loadBuffer(const byte *buffer, uint32 size, DisposeAfterUse::Flag disposable = DisposeAfterUse::NO);
bool loadStream(SeekableReadStream *stream, const String &name = "File Stream");
void close();
/**
* The actual parsing function.
* Parses the loaded data stream, returns true if successful.
*/
bool parse();
/**
* Returns the active node being parsed (the one on top of
* the node stack).
*/
ParserNode *getActiveNode() {
if (!_activeKey.empty())
return _activeKey.top();
return nullptr;
}
/**
* Returns the parent of a given node in the stack.
*/
ParserNode *getParentNode(ParserNode *child) {
return child->depth > 0 ? _activeKey[child->depth - 1] : 0;
}
/**
* Allow text nodes (eg <tag>this is a text node</tag>) to appear in the
* document.
*
* By default this parser does not allow text nodes and expects all data
* to appear in attributes.
*/
void setAllowText() {
_allowText = true;
}
protected:
/**
* The buildLayout function builds the layout for the parser to use
* based on a series of helper macros. This function is automatically
* generated by the CUSTOM_XML_PARSER() macro on custom parsers.
*
* See the documentation regarding XML layouts.
*/
virtual void buildLayout() = 0;
/**
* The keycallback function is automatically overloaded on custom parsers
* when using the CUSTOM_XML_PARSER() macro.
*
* Its job is to call the corresponding Callback function for the given node.
* A function for each key type must be declared separately. See the custom
* parser creation instructions.
*
* When parsing a key in such function, one may chose to skip it, e.g. because it's not needed
* on the current configuration. In order to ignore a key, you must set
* the "ignore" field of its KeyNode struct to "true": The key and all its children
* will then be automatically ignored by the parser.
*
* The callback function must return true if the key was properly handled (this includes the case when the
* key is being ignored). False otherwise. The return of keyCallback() is the same as
* the callback function's.
* See the sample implementation in GUI::ThemeParser.
*/
virtual bool keyCallback(ParserNode *node) = 0;
/**
* The closed key callback function MAY be overloaded by inheriting classes to
* implement parser-specific functions.
*
* The closedKeyCallback is issued once a key has been finished parsing, to let
* the parser verify that all the required subkeys, etc, were included.
*
* Unlike the keyCallbacks(), there's just a closedKeyCallback() for all keys.
* Use "node->name" to distinguish between each key type.
*
* Returns true if the key was properly closed, false otherwise.
* By default, all keys are properly closed.
*/
virtual bool closedKeyCallback(ParserNode *node) {
return true;
}
/**
* Called when a text node is found. This will only be called if
* setAllowText() has been called, otherwise text nodes are considered
* parse errors.
*/
virtual bool textCallback(const String &val) {
return true;
}
/**
* Called when a node is closed. Manages its cleanup and calls the
* closing callback function if needed.
*/
bool closeKey();
/**
* Parses the value of a given key. There's no reason to overload this.
*/
bool parseKeyValue(String keyName);
/**
* Called once a key has been parsed. It handles the closing/cleanup of the
* node stack and calls the keyCallback.
*/
bool parseActiveKey(bool closed);
/**
* Prints an error message when parsing fails and stops the parser.
* Parser error always returns "false" so we can pass the return value
* directly and break down the parsing.
*/
bool parserError(const String &errStr);
/**
* Skips spaces/whitelines etc.
* @return true if any spaces were skipped.
*/
bool skipSpaces();
/**
* Skips comment blocks and comment lines.
* @return true if any comments were skipped.
*/
bool skipComments();
/**
* Check if a given character can be part of a KEY or VALUE name.
* Overload this if you want to support keys with strange characters
* in their name.
*/
virtual inline bool isValidNameChar(char c) {
return isAlnum(c) || c == '_';
}
/**
* Parses a the first textual token found.
*/
bool parseToken();
/**
* Parses the values inside an integer key.
* The count parameter specifies the number of values inside
* the key, which are expected to be separated with commas.
*
* Sample usage:
* parseIntegerKey("255, 255, 255", 3, &red, &green, &blue);
* [will parse each field into its own integer]
*
* parseIntegerKey("1234", 1, &number);
* [will parse the single number into the variable]
*
* @param key String containing the integers to be parsed.
* @param count Number of comma-separated ints in the string.
* @param ... Integer variables to store the parsed ints, passed
* by reference.
* @returns True if the parsing succeeded.
*/
bool parseIntegerKey(const char *key, int count, ...);
bool parseIntegerKey(const String &keyStr, int count, ...);
bool vparseIntegerKey(const char *key, int count, va_list args);
bool parseXMLHeader(ParserNode *node);
/**
* Overload if your parser needs to support parsing the same file
* several times, so you can clean up the internal state of the
* parser before each parse.
*/
virtual void cleanup() {}
/**
* Overload if your parser wants to be notified of keys which haven't
* been explicitly declared.
*
* The functions should return true if the key was handled and parsing should
* continue, or false (default) to raise a parsing error.
*/
virtual bool handleUnknownKey(ParserNode *node) { return false; }
List<XMLKeyLayout *> _layoutList;
private:
char _char;
bool _allowText; /** Allow text nodes in the doc (default false) */
SeekableReadStream *_stream;
Path _fileName;
ParserState _state; /** Internal state of the parser */
String _error; /** Current error message */
String _token; /** Current text token */
Stack<ParserNode *> _activeKey; /** Node stack of the parsed keys */
};
/** @} */
} // End of namespace Common
#endif