diff --git a/src/tools/fileio_scen.cpp b/src/tools/fileio_scen.cpp index a68db243..cea1d82c 100644 --- a/src/tools/fileio_scen.cpp +++ b/src/tools/fileio_scen.cpp @@ -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 static void port_shop_spec_node(cSpecial& spec, std::vector& 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) diff --git a/src/tools/graphtool.cpp b/src/tools/graphtool.cpp index f4117c4f..88115a34 100644 --- a/src/tools/graphtool.cpp +++ b/src/tools/graphtool.cpp @@ -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) { diff --git a/src/tools/porting.cpp b/src/tools/porting.cpp index 7ebd26e5..99ec6fe4 100644 --- a/src/tools/porting.cpp +++ b/src/tools/porting.cpp @@ -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) { diff --git a/src/tools/qdpict.cpp b/src/tools/qdpict.cpp index b7c04312..82f3842a 100644 --- a/src/tools/qdpict.cpp +++ b/src/tools/qdpict.cpp @@ -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 static typename std::make_unsigned::type to_unsigned(T val) { return *reinterpret_cast::type*>(&val); @@ -168,42 +174,75 @@ static legacy::Rect loadPixMapData(ptr_guard& picData, ptr_guard 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 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);