Fix some issues with loading legacy scenarios and custom graphics

- Unfortunately, the PICT loader is still very fragile.
This commit is contained in:
2015-02-13 20:34:18 -05:00
parent d45ba59cce
commit a029836720
4 changed files with 95 additions and 31 deletions

View File

@@ -61,6 +61,7 @@ bool load_scenario(fs::path file_to_load, cScenario& scenario) {
}
scenario = cScenario();
std::string fname = file_to_load.filename().string();
std::transform(fname.begin(), fname.end(), fname.begin(), tolower);
size_t dot = fname.find_last_of('.');
if(fname.substr(dot) == ".boes")
return load_scenario_v2(file_to_load, scenario);
@@ -73,6 +74,9 @@ bool load_scenario(fs::path file_to_load, cScenario& scenario) {
template<typename Container> static void port_shop_spec_node(cSpecial& spec, std::vector<shop_info_t>& shops, Container strs) {
int which_shop;
if(spec.ex1b < 4) {
// Safeguard against invalid data
if(spec.ex1a < 0)
spec.ex1a = 1;
shops.push_back({eShopItemType(spec.ex1b + 1), spec.ex1a, spec.ex2a, strs[spec.m1]});
which_shop = shops.size() + 5;
} else if(spec.ex1b == 4)

View File

@@ -403,14 +403,23 @@ rectangle calc_rect(short i, short j){
return base_rect;
}
extern sf::Texture fields_gworld;
graf_pos cCustomGraphics::find_graphic(pic_num_t which_rect, bool party) {
static graf_pos dummy = {&fields_gworld, {72,0,108,28}};
if(party && !party_sheet) return dummy;
else if(!party && !is_old && (which_rect / 100) >= numSheets)
return dummy;
else if(numSheets == 0) return dummy;
short sheet = which_rect / 100;
if(is_old || party) sheet = 0;
else which_rect %= 100;
rectangle store_rect = {0,0,36,28};
store_rect.offset(28 * (which_rect % 10),36 * (which_rect / 10));
return std::make_pair(party ? party_sheet.get() : &sheets[sheet],store_rect);
sf::Texture* the_sheet = party ? party_sheet.get() : &sheets[sheet];
rectangle test(*the_sheet);
if((store_rect | test) != store_rect) return dummy;
return std::make_pair(the_sheet,store_rect);
}
size_t cCustomGraphics::count(bool party) {

View File

@@ -410,15 +410,10 @@ void flip_long(int32_t *s){
}
// This is needed in order to load in old legacy Windows scenarios, which stored their rects in a different order than Mac scenario
// This is needed in order to load in old legacy Windows scenarios, which stored their rects in a different order than Mac scenarios
static void alter_rect(legacy::Rect *r) {
short a;
a = r->top;
r->top = r->left;
r->left = a;
a = r->bottom;
r->bottom = r->right;
std::swap(r->top, r->left);
std::swap(r->bottom, r->right);
}
void flip_rect(legacy::Rect* s) {

View File

@@ -26,6 +26,12 @@ static int16_t extract_word(char* ptr) {
return s;
}
static int32_t extract_long(char* ptr) {
int32_t s = *(int32_t*) ptr;
flip_long(&s);
return s;
}
template<typename T>
static typename std::make_unsigned<T>::type to_unsigned(T val) {
return *reinterpret_cast<typename std::make_unsigned<T>::type*>(&val);
@@ -168,42 +174,75 @@ static legacy::Rect loadPixMapData(ptr_guard<char>& picData, ptr_guard<unsigned
return bounds;
}
static rectangle loadFromPictResource(Handle resHandle, unsigned char*& pixelStore) {
static rectangle loadFromPictResource(Handle resHandle, unsigned char*& pixelStore, int& error) {
HLock(resHandle);
// TODO: Use picSize to ensure I don't go out of bounds
size_t picSize = GetHandleSize(resHandle);
ptr_guard<char> picData(*resHandle, *resHandle + picSize);
picData += 2; // Skip picture size field
picData += sizeof(legacy::Rect); // Skip picture frame field (actual frame is stored later)
// Next is the bounding rect.
rectangle picFrame;
picFrame.top = extract_word(picData); picData += 2;
picFrame.left = extract_word(picData); picData += 2;
picFrame.bottom = extract_word(picData); picData += 2;
picFrame.right = extract_word(picData); picData += 2;
if(strncmp(picData, "\0\x11\x02\xff", 4) != 0) // QuickDraw version code (version 2)
oopsError(1);
picData += 4; // Skip version field
if(strncmp(picData, "\x0c\0", 2) != 0) // Header opcode
oopsError(2);
picData += 2 + 4; // Skip header opcode and picture size (which is -1)
rectangle picFrame;
// Next is the bounding rect with 4-byte components
// However, they are fixed-point, so the second 2 bytes are fractional
// We'll ignore that part.
picFrame.top = extract_word(picData); picData += 4;
picFrame.left = extract_word(picData); picData += 4;
picFrame.bottom = extract_word(picData); picData += 4;
picFrame.right = extract_word(picData); picData += 4;
// We're using this info solely to initialize the target data array.
// I'm not sure I have the components in the right order, but for this, it doesn't actually matter
picData += 2 + 24; // Skip header opcode and payload
// Initialize the target data array using the picture's bounding rect
size_t picDataSize = picFrame.height() * picFrame.width() * 4; // Four bytes per pixel
pixelStore = new unsigned char[picDataSize];
ptr_guard<unsigned char> pixels(pixelStore, pixelStore + picDataSize);
// Then 4 reserved bytes
picData += 4;
legacy::Rect bounds;
// Now we need to skip any superfluous opcodes until we get to pixel data - opcode 90, 91, 98, or 99
// We're assuming it's stored as pixel data, so any other opcodes will just be ignored
bool done = false;
while(!done) {
// All opcodes have a first byte of 0 (other than the header opcode)
// If we find one that doesn't, it's a reserved opcode, so just skip it.
if(*picData != 0) {
unsigned char hi = *(unsigned char*)picData++;
picData++; // low byte doesn't affect data size
if(hi == 0x01) picData += 2;
else if(hi >= 0x02 && hi <= 0x0B)
picData += 4;
else if(hi >= 0x0C && hi <= 0x7E)
picData += 24;
else if(hi == 0x7F) picData += 254;
else if(hi >= 0x81) {
unsigned long len = extract_long(picData);
if(hi == 0x82 && (picData[-1] == 0 || picData[-1] == 1)) {
// QuickTime image data (0 = compressed, 1 = uncompressed)
if(picData[-1] == 1) {
// This is relatively easy; it's encoded as a standard QD opcode
// So all we really need to do is skip the QT header data
picData += 4 + 2 + 36; // Fixed-length header data - length, version, matrix
size_t matteSize = extract_long(picData);
picData += 4 + 8; // matte size and rect
if(matteSize > 0) {
// TODO: Not quite sure about the size of the matte image description
picData += 4 + matteSize;
}
// At this point, we should be at a normal image opcode, so just end here and let the next loop grab it
} else {
// This is much harder - the image is compressed.
// I'm not sure how to handle it, so for now I'll put up an error.
error = 2;
HUnlock(resHandle);
return {0,0,0,0};
}
} else picData += 4 + len;
}
// There may be a byte of padding to align it to word boundaries.
picData.align(sizeof(int16_t), sizeof(int16_t));
continue;
}
picData++;
switch(*(unsigned char*)picData++) {
unsigned char code = *(unsigned char*)picData++;
switch(code) {
case 0x00: // Nop
case 0x1C: // HiliteMode
case 0x1E: // DefHilite
@@ -349,11 +388,18 @@ static rectangle loadFromPictResource(Handle resHandle, unsigned char*& pixelSto
case 0x13: // PnPixPat
case 0x14: // FillPixPat
// argument is a pixpat
// This is a complicated type that I don't want to deal with; I'll implement it if it ever causes problems.
std::cerr << "Unknown PICT opcode: " << std::hex << unsigned(picData[-1]) << std::dec << std::endl;
error = 1;
HUnlock(resHandle);
return {0,0,0,0};
case 0x1A: // RGBFgCol
case 0x1B: // RGBBkCol
case 0x1D: // HiliteColor
case 0x1F: // OpColor
// argument is an RGB colour
picData += 6;
break;
case 0x70: // FramePoly
case 0x71: // PaintPoly
case 0x72: // ErasePoly
@@ -361,11 +407,15 @@ static rectangle loadFromPictResource(Handle resHandle, unsigned char*& pixelSto
case 0x74: // FillPoly
case 0x75: case 0x76: case 0x77: // Reserved
// argument is a polygon
// First two bytes hold the data size of the polygon.
picData += extract_word(picData);
break;
default:
// These are mostly reserved opcodes whose first field is a two-byte length
// I don't want to deal with them, and it's highly unlikely they'll come up.
// Thus, just give an error if they do.
oopsError(9);
// These are reserved opcodes whose first field is a length
if((code >= 0xA2 && code <= 0xAF) || (code >= 0x9A && code <= 0x9F))
picData += extract_word(picData);
else if(code >= 0xD0 && code <= 0xFE)
picData += extract_long(picData);
break;
}
// There may be a byte of padding to align it to word boundaries.
@@ -378,6 +428,11 @@ static rectangle loadFromPictResource(Handle resHandle, unsigned char*& pixelSto
bool tryLoadPictFromResourceFile(fs::path& gpath, sf::Image& graphics_store); // Suppress "no prototype" warning
bool tryLoadPictFromResourceFile(fs::path& gpath, sf::Image& graphics_store) {
static const char*const noGraphics = "The game will still work without the custom graphics, but some things will not look right.";
static const std::string errStrings[] = {
"The resource did not contain any pixel data.",
"The resource contained a PixPat instruction, which isn't currently supported by the PICT loader.",
"The resource contained compressed QuickTime image data, which isn't currently supported by the PICT loader.",
};
// TODO: There's no way around it; I'll have to read resource files for this section.
FSRef file;
ResFileRefNum custRef;
@@ -410,11 +465,12 @@ bool tryLoadPictFromResourceFile(fs::path& gpath, sf::Image& graphics_store) {
return false;
}
unsigned char* data = NULL;
rectangle picFrame = loadFromPictResource(resHandle, data);
int error = 0;
rectangle picFrame = loadFromPictResource(resHandle, data, error);
CloseResFile(custRef);
if(picFrame.width() <= 0 || picFrame.height() <= 0) {
if(data != NULL) delete[] data;
giveError("An old-style .meg graphics file was found, but an error occurred while reading it.",noGraphics);
giveError("An old-style .meg graphics file was found, but an error occurred while reading it: " + errStrings[error],noGraphics);
return false;
}
graphics_store.create(picFrame.width(), picFrame.height(), data);