Implement loading of .meg files using the Resource manager

- I extract pixel data from the resource without using QuickDraw
- Both resource-fork and data-fork .meg files work

Related changes:
- The custom graphics textures are now wrapped in a custom class
- bmp images are loaded if a meg is not found

Incidental changes:
- Various TODO notes
- Fix cScenario::spec_strs skipping several strings and potentially fetching out-of-bound entries
- oopsError now uses string streams instead of sprintf
This commit is contained in:
2014-04-12 01:09:37 -04:00
parent 4db8a0943b
commit a6a030052c
17 changed files with 658 additions and 186 deletions

View File

@@ -28,9 +28,10 @@
extern bool cur_scen_is_mac, mac_is_intel;
extern cScenario scenario;
extern sf::Texture spec_scen_g,items_gworld,tiny_obj_gworld,fields_gworld,roads_gworld,boom_gworld,missiles_gworld;
extern sf::Texture items_gworld,tiny_obj_gworld,fields_gworld,roads_gworld,boom_gworld,missiles_gworld;
extern sf::Texture dlogpics_gworld,monst_gworld[],terrain_gworld[],anim_gworld,talkfaces_gworld,pc_gworld;
extern sf::Texture status_gworld, vehicle_gworld, small_ter_gworld;
extern cCustomGraphics spec_scen_g;
extern cUniverse univ;
//extern unsigned char borders[4][50];
//extern cOutdoors current_terrain;
@@ -154,6 +155,7 @@ bool load_scenario(fs::path file_to_load){
port_item_list(item_data);
scenario = *temp_scenario;
scenario.append(*item_data);
// TODO: Consider skipping the fread and assignment when len is 0
for (i = 0; i < 270; i++) {
len = (long) (scenario.scen_str_len[i]);
n = fread(&(scenario.scen_strs(i)), len, 1, file_id);
@@ -701,48 +703,47 @@ bool load_outdoor_str(location which_out, short which_str, char* str){
return true;
}
#ifdef __APPLE__
bool tryLoadPictFromResourceFile(fs::path& gpath, sf::Image& graphics_store);
#endif
void load_spec_graphics()
{
static const char*const noGraphics = "The game will still work without the custom graphics, but some things will not look right.";
short i;
fs::path path(scenario.scen_file);
printf("Loading scenario graphics... (%s)\n",path.c_str());
path.replace_extension("exr");
// Tried path.replace_extension, but that only deleted the extension, so I have to do it manually
std::string filename = path.stem().string();
path = path.parent_path();
if(spec_scen_g) spec_scen_g.clear();
// TODO: Load new-style sheets
//if(err != noErr)
{
// for (i = 0; i < 63; i++)
// file_name[i] = whatever[i];
path.replace_extension("meg");
// TODO: There's no way around it; I'll have to read resource files for this section.
#if 0
err = FSPathMakeRef(path, &file, NULL);
err = FSOpenResourceFile(&file, 0, NULL, fsRdPerm /*fsRdWrPerm*/, &custRef);
//file_num = HOpen(file_to_load.vRefNum,file_to_load.parID,file_name,1);
//if (file_num < 0){
if(err != noErr){
//whatever = (char *) file_to_load.name;
printf("First attempt failed... (%s)\n",(char*)path);
for (i = 0; i < 250; i++) {
if (path[i] == '.') {
path[i + 1] = 'b';
path[i + 2] = 'm';
path[i + 3] = 'p';
//path[i + 4] = 0;
break;
}
}
err = FSPathMakeRef(path, &file, NULL);
FSSpec spec;
FSGetCatalogInfo(&file, kFSCatInfoNone, NULL, NULL, &spec, NULL);
err = FSpOpenDF(&spec, fsRdPerm, &custRef);
//spec_scen_g = load_bmp_from_file(file_name);
spec_scen_g = importPictureFileToGWorld(&spec);
if(spec_scen_g == NULL)printf("Scenario graphics not found (%s).\n",file_name);
}else{
spec_scen_g = load_pict(1);
CloseResFile(custRef);
}
static sf::Image graphics_store;
bool foundGraphics = false;
#ifdef __APPLE__
fs::path gpath = path/(filename + ".meg");
if(fs::exists(gpath))
foundGraphics = tryLoadPictFromResourceFile(gpath, graphics_store);
#endif
if(!foundGraphics) {
fs::path gpath = path/(filename + ".bmp");
if(fs::exists(gpath)) {
if(graphics_store.loadFromFile(gpath.string()))
foundGraphics = true;
else giveError("An old-style .bmp graphics file was found, but there was an error reading from the file.",noGraphics);
}
}
if(foundGraphics) {
// If we're here, we found old-style graphics.
// This means they need an alpha channel
graphics_store.createMaskFromColor(sf::Color::White);
spec_scen_g.is_old = true;
spec_scen_g.sheets = new sf::Texture[1];
spec_scen_g.sheets[0].loadFromImage(graphics_store);
} else {
// Check for new-style graphics
}
}//else{}
// Now load regular graphics

View File

@@ -573,20 +573,14 @@ RECT calc_rect(short i, short j){
return base_rect;
}
RECT get_custom_rect (short which_rect){
RECT store_rect = {0,0,36,28};
store_rect.offset(28 * (which_rect % 10),36 * (which_rect / 10));
return store_rect;
}
short get_custom_rect (short which_rect, RECT& store_rect){ // returns the number of the sheet to use
graf_pos cCustomGraphics::find_graphic(pic_num_t which_rect, bool party) {
short sheet = which_rect / 100;
which_rect %= 100;
store_rect = {0,0,28,36};
if(is_old) sheet = 0;
else which_rect %= 100;
RECT store_rect = {0,0,28,36};
store_rect.offset(28 * (which_rect % 10),36 * (which_rect / 10));
return sheet;
return std::make_pair(party ? this->party : &sheets[sheet],store_rect);
}
// TODO: This doesn't belong in this file

View File

@@ -72,6 +72,27 @@ public:
};
typedef unsigned short pic_num_t;
using graf_pos = std::pair<sf::Texture*,RECT>;
using graf_pos_ref = std::pair<sf::Texture*&,RECT&>;
struct cCustomGraphics {
sf::Texture* sheets = NULL;
sf::Texture* party = NULL;
bool is_old = false;
void clear() {
if(sheets != NULL) delete[] sheets;
}
~cCustomGraphics() {
if(party != NULL) delete party;
}
explicit operator bool() {
return sheets;
}
graf_pos find_graphic(pic_num_t pic, bool party = false);
};
void init_graph_tool(void (*redraw_callback)());
void clean_up_graphtool();
void set_cursor(cursor_type which_curs);
@@ -84,8 +105,6 @@ short string_length(const char *str);
//OSStatus flip_pict(OSType domain, OSType type, short id, void *ptr, UInt32 size, bool isNative, void *refcon);
//void draw_terrain();
RECT calc_rect(short i, short j);
RECT get_custom_rect (short which_rect);
short get_custom_rect (short which_rect, RECT& store_rect);
void setActiveRenderTarget(sf::RenderTarget& where);
void tileImage(sf::RenderTarget& target, RECT area, sf::Texture& img, sf::BlendMode mode = sf::BlendNone);
void tileImage(sf::RenderTarget& target, RECT area, sf::Texture& img, RECT srcRect, sf::BlendMode mode = sf::BlendNone);

427
osx/tools/qdpict.cpp Normal file
View File

@@ -0,0 +1,427 @@
//
// qdpict.cpp
// BoE
//
// Created by Celtic Minstrel on 14-04-11.
//
//
#ifndef __APPLE__
#error qdpict.cpp uses Apple APIs at the moment; try omitting it from the build
#endif
#include <cstdint>
#include <memory>
#include "porting.h"
#include "location.h"
#include "dlogutil.h"
// TODO: Don't use the resource manager
#include <CoreServices/CoreServices.h>
static int32_t extract_long(char* ptr) {
int32_t l = *(int32_t*) ptr;
flip_long(&l);
return l;
}
static int16_t extract_word(char* ptr) {
int16_t s = *(int16_t*) ptr;
flip_short(&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);
}
template<typename T> class ptr_guard {
T* the_ptr;
T*const guard;
void boundcheck() {
if(the_ptr > guard) oopsError(127, the_ptr - guard);
}
public:
ptr_guard(T* begin, T* end) : the_ptr(begin), guard(end) {}
ptr_guard<T>& operator+=(ptrdiff_t amt) {
the_ptr += amt;
boundcheck();
return *this;
}
ptr_guard<T> operator++(int) {
ptr_guard<T> tmp = *this;
the_ptr++;
boundcheck();
return tmp;
}
void align(size_t alignment, size_t sz) {
size_t spaceLeft = guard - the_ptr;
void* ptr = the_ptr;
the_ptr = (char*)std::align(alignment, sz, ptr, spaceLeft);
boundcheck();
}
T& operator[](size_t i) {
if(the_ptr + i > guard) oopsError(128, i - (guard - the_ptr));
return the_ptr[i];
}
template<typename T2> explicit operator T2*() {return (T2*)the_ptr;}
operator T*() {return the_ptr;}
T& operator*() {return *the_ptr;}
T* operator->() {return the_ptr;}
};
void loadColourTable(ptr_guard<char>& picData, sf::Color(& clut)[256], int numColours) {
while(numColours >= 0) {
int i = extract_word(picData);
if(i > 256) oopsError(5);
picData += 2;
clut[i].r = to_unsigned(extract_word(picData)) / 256;
picData += 2;
clut[i].g = to_unsigned(extract_word(picData)) / 256;
picData += 2;
clut[i].b = to_unsigned(extract_word(picData)) / 256;
picData += 2;
clut[i].a = 0xff;
numColours--;
}
}
void loadUnpackedPixels(ptr_guard<char>& picData,ptr_guard<unsigned char>& pixels,sf::Color(& clut)[256],legacy::Rect& bounds,int rowBytes) {
for(int i = 0; i < bounds.bottom - bounds.top; i++) {
for(int j = 0; j < bounds.right - bounds.left; j++) {
size_t pixel = i * rowBytes + j;
size_t iClr = to_unsigned(*picData++);
pixels[4 * pixel + 0] = clut[iClr].r;
pixels[4 * pixel + 1] = clut[iClr].g;
pixels[4 * pixel + 2] = clut[iClr].b;
pixels[4 * pixel + 3] = clut[iClr].a;
}
}
}
void loadPackedPixels(ptr_guard<char>& picData,ptr_guard<unsigned char>& pixels,sf::Color(& clut)[256],legacy::Rect& bounds,int rowBytes) {
// Packed data
int countSize = rowBytes > 250 ? 2 : 1;
for(int i = 0; i < bounds.bottom - bounds.top; i++) {
int byteCount = countSize == 2 ? extract_word(picData) : *picData;
picData += countSize;
char* start = picData;
int j = 0;
while(picData - start + 1 < byteCount) {
int repCount = *picData++;
if(repCount == -128) continue;
int k = j;
if(repCount >= 0) {
repCount++;
while(j - k < repCount) {
size_t pixel = i * rowBytes + j;
size_t iClr = to_unsigned(*picData++);
pixels[4 * pixel + 0] = clut[iClr].r;
pixels[4 * pixel + 1] = clut[iClr].g;
pixels[4 * pixel + 2] = clut[iClr].b;
pixels[4 * pixel + 3] = clut[iClr].a;
j++;
}
} else {
repCount = 1 - repCount;
size_t val = to_unsigned(*picData++);
while(j - k < repCount) {
size_t pixel = i * rowBytes + j;
pixels[4 * pixel + 0] = clut[val].r;
pixels[4 * pixel + 1] = clut[val].g;
pixels[4 * pixel + 2] = clut[val].b;
pixels[4 * pixel + 3] = clut[val].a;
j++;
}
}
}
}
}
legacy::Rect loadPixMapData(ptr_guard<char>& picData, ptr_guard<unsigned char>& pixels, int pixMapType) {
int rowBytes = extract_word(picData) & 0x7fff;
picData += 2; // Skip rowBytes; assume we have a v2 (colour) bitmap
legacy::Rect bounds = *(legacy::Rect*)picData;
flip_rect(&bounds);
picData += sizeof(legacy::Rect);
picData += 2 + 2 + 4; // Skip version, packType, and packSize
picData += 4 + 4; // Skip hRes and vRes
picData += 2 + 2; // Skip pixelType and pixelSize
if(picData[-1] != 8) oopsError(3);
picData += 2 + 2; // Skip cmpCount and cmpSize
picData += 4 + 4 + 4; // Skip planeBytes, pbTable, pmReserved
picData += 4 + 2; // Skip ctSeed and ctFlags
int numColours = extract_word(picData);
if(numColours >= 256) oopsError(4);
picData += 2;
sf::Color clut[256];
loadColourTable(picData, clut, numColours);
picData += 2 * sizeof(legacy::Rect); // Skip source and dest rect
picData += 2; // Skip mode
if(pixMapType == 0x91 || pixMapType == 0x99) {
// Same code as for Clip, above
// TODO: I'm skipping a byte here, but that might be part of the size
picData++;
// This size field includes the size field itself
picData += *picData - 1;
}
if(rowBytes < 8)
loadUnpackedPixels(picData, pixels, clut, bounds, rowBytes);
else loadPackedPixels(picData, pixels, clut, bounds, rowBytes);
return bounds;
}
rectangle loadFromPictResource(Handle resHandle, unsigned char*& pixelStore) {
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)
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
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;
int pixMapType = 0;
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)
picData++;
switch(*(unsigned char*)picData++) {
case 0x00: // Nop
case 0x1C: // HiliteMode
case 0x1E: // DefHilite
case 0x38: // FrameSameRect
case 0x39: // PaintSameRect
case 0x3A: // EraseSameRect
case 0x3B: // InvertSameRect
case 0x3C: // FillSameRect
case 0x48: // FrameSameRRect
case 0x49: // PaintSameRRect
case 0x4A: // EraseSameRRect
case 0x4B: // InvertSameRRect
case 0x4C: // FillSameRRect
case 0x58: // FrameSameOval
case 0x59: // PaintSameOval
case 0x5A: // EraseSameOval
case 0x5B: // InvertSameOval
case 0x5C: // FillSameOval
case 0x78: // FrameSameRect
case 0x79: // PaintSameRect
case 0x7A: // EraseSameRect
case 0x7B: // InvertSameRect
case 0x7C: // FillSameRect
case 0x88: // FrameRgn
case 0x89: // PaintRgn
case 0x8A: // EraseRgn
case 0x8B: // InvertRgn
case 0x8C: // FillRgn
case 0x3D: case 0x3E: case 0x3F: // Reserved
case 0x4D: case 0x4E: case 0x4F: // Reserved
case 0x5D: case 0x5E: case 0x5F: // Reserved
case 0x7D: case 0x7E: case 0x7F: // Reserved
case 0x8D: case 0x8E: case 0x8F: // Reserved
break;
case 0x04: // TxFace
case 0x11: // Version
picData++;
break;
case 0x03: // TxFont
case 0x05: // TxMode
case 0x08: // PnMode
case 0x0D: // TxSize
case 0x15: // PbLocHFrac
case 0x16: // ChExtra
case 0x23: // ShortLineFrom
case 0xA0: // ShortComment
picData += 2;
break;
case 0x06: // SpExtra
case 0x07: // PnSize
case 0x0B: // OvSize
case 0x0C: // Origin
case 0x0E: // FgColor
case 0x0F: // BkColor
case 0x21: // LineFrom
case 0x68: // FrameSameArc
case 0x69: // PaintSameArc
case 0x6A: // EraseSameArc
case 0x6B: // InvertSameArc
case 0x6C: // FillSameArc
case 0x6D: case 0x6E: case 0x6F: // Reserved
picData += 4;
break;
case 0x22: // ShortLine
picData += 6;
break;
case 0x02: // BkPat
case 0x09: // PnPat
case 0x0A: // FillPat
case 0x10: // TxRatio
case 0x20: // Line
case 0x30: // FrameRect
case 0x31: // PaintRect
case 0x32: // EraseRect
case 0x33: // InvertRect
case 0x34: // FillRect
case 0x40: // FrameRRect
case 0x41: // PaintRRect
case 0x42: // EraseRRect
case 0x43: // InvertRRect
case 0x44: // FillRRect
case 0x50: // FrameOval
case 0x51: // PaintOval
case 0x52: // EraseOval
case 0x53: // InvertOval
case 0x54: // FillOval
case 0x35: case 0x36: case 0x37: // Reserved
case 0x45: case 0x46: case 0x47: // Reserved
case 0x55: case 0x56: case 0x57: // Reserved
picData += 8;
break;
case 0x60: // FrameArc
case 0x61: // PaintArc
case 0x62: // EraseArc
case 0x63: // InvertArc
case 0x64: // FillArc
case 0x65: case 0x66: case 0x67: // Reserved
picData += 12;
break;
case 0x01: // Clip
case 0x80: // FrameRgn
case 0x81: // PaintRgn
case 0x82: // EraseRgn
case 0x83: // InvertRgn
case 0x84: // FillRgn
case 0x85: case 0x86: case 0x87:
// TODO: I'm skipping a byte here, but that might be part of the size
picData++;
// This size field includes the size field itself
picData += *picData - 1;
break;
case 0x28: // LongText
picData += 4;
picData += *picData + 1;
break;
case 0x29: // DHText
case 0x2A: // DVText
picData++;
picData += *picData + 1;
break;
case 0x2B: // DHDVText
case 0xA1: // LongComment
picData += 2;
picData += *picData + 1;
break;
// MARK: Main part of load function - the pixel data
case 0x90: // BitsRect
bounds = loadPixMapData(picData, pixels, 0x90);
break;
case 0x91: // BitsRgn
bounds = loadPixMapData(picData, pixels, 0x91);
break;
case 0x98: // PackBitsRect
bounds = loadPixMapData(picData, pixels, 0x98);
break;
case 0x99: // PackBitsRgn
bounds = loadPixMapData(picData, pixels, 0x99);
break;
case 0xFF:
done = true;
break;
case 0x12: // BkPixPat
case 0x13: // PnPixPat
case 0x14: // FillPixPat
// argument is a pixpat
case 0x1A: // RGBFgCol
case 0x1B: // RGBBkCol
case 0x1D: // HiliteColor
case 0x1F: // OpColor
// argument is an RGB colour
case 0x70: // FramePoly
case 0x71: // PaintPoly
case 0x72: // ErasePoly
case 0x73: // InvertPoly
case 0x74: // FillPoly
case 0x75: case 0x76: case 0x77: // Reserved
// argument is a polygon
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);
break;
}
// There may be a byte of padding to align it to word boundaries.
picData.align(sizeof(int16_t), sizeof(int16_t));
}
HUnlock(resHandle);
return rectangle(bounds.top, bounds.left, bounds.bottom, bounds.right);
}
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.";
// TODO: There's no way around it; I'll have to read resource files for this section.
FSRef file;
ResFileRefNum custRef;
OSErr err = FSPathMakeRef((const UInt8*)gpath.c_str(), &file, NULL);
err = FSOpenResourceFile(&file, 0, NULL, fsRdPerm, &custRef);
if(err != noErr) { // TODO: Is the error that would be returned if the resources were stored in the resource fork?
HFSUniStr255 rsrc;
err = FSGetResourceForkName(&rsrc);
err = FSOpenResourceFile(&file, rsrc.length, rsrc.unicode, fsRdPerm, &custRef);
if(err != noErr) {
giveError("An old-style .meg graphics file was found, but neither data nor resource fork could not be read.",noGraphics);
return false;
}
}
int nPicts = Count1Resources('PICT');
if(nPicts < 1) {
CloseResFile(custRef);
giveError("An old-style .meg graphics file was found, but it did not contain any PICT resources.",noGraphics);
return false;
}
Handle resHandle = Get1Resource('PICT', 1);
if(ResError() == resNotFound) {
CloseResFile(custRef);
giveError("An old-style .meg graphics file was found, but it did not contain a PICT resource of ID 1.",noGraphics);
return false;
}
if(resHandle == NULL) {
CloseResFile(custRef);
giveError("An old-style .meg graphics file was found, but an error occurred while loading it.",noGraphics);
return false;
}
unsigned char* data = NULL;
rectangle picFrame = loadFromPictResource(resHandle, data);
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);
return false;
}
graphics_store.create(picFrame.width(), picFrame.height(), data);
if(data != NULL) delete[] data;
return true;
}