Beispiel #1
0
/* SImage::getIndexedData
 * Loads the image as index data into <mc>. Returns false if image is
 * invalid, true otherwise
 *******************************************************************/
bool SImage::getIndexedData(MemChunk& mc)
{
	// Check the image is valid
	if (!isValid())
		return false;

	// Init rgb data
	mc.reSize(width * height, false);

	// Cannot do this for trucolor graphics.
	if (type == RGBA)
		return false;

	else if (type == PALMASK)
	{
		mc.write(data, width * height);
		return true;
	}

	else if (type == ALPHAMAP)
	{
		mc.write(data, width * height);
		return true;
	}

	return false;	// Invalid image type
}
Beispiel #2
0
/* SImage::getRGBAData
 * Loads the image as RGBA data into <mc>. Returns false if image is
 * invalid, true otherwise
 *******************************************************************/
bool SImage::getRGBAData(MemChunk& mc, Palette8bit* pal)
{
	// Check the image is valid
	if (!isValid())
		return false;

	// Init rgba data
	mc.reSize(width * height * 4, false);

	// If data is already in RGBA format just return a copy
	if (type == RGBA)
	{
		mc.importMem(data, width * height * 4);
		return true;
	}

	// Convert paletted
	else if (type == PALMASK)
	{
		// Get palette to use
		if (has_palette || !pal)
			pal = &palette;

		uint8_t rgba[4];
		for (int a = 0; a < width * height; a++)
		{
			// Get colour
			rgba_t col = pal->colour(data[a]);

			// Set alpha
			if (mask)
				col.a = mask[a];
			else
				col.a = 255;

			col.write(rgba);	// Write colour to array
			mc.write(rgba, 4);	// Write array to MemChunk
		}

		return true;
	}

	// Convert if alpha map
	else if (type == ALPHAMAP)
	{
		uint8_t rgba[4];
		rgba_t col;
		for (int a = 0; a < width * height; a++)
		{
			// Get pixel as colour (greyscale)
			col.set(data[a], data[a], data[a], data[a]);

			col.write(rgba);	// Write colour to array
			mc.write(rgba, 4);	// Write array to MemChunk
		}
	}

	return false;	// Invalid image type
}
Beispiel #3
0
// -----------------------------------------------------------------------------
// Writes the wad archive to a MemChunk
// Returns true if successful, false otherwise
// -----------------------------------------------------------------------------
bool Wad2Archive::write(MemChunk& mc, bool update)
{
	// Determine directory offset & individual lump offsets
	uint32_t      dir_offset = 12;
	ArchiveEntry* entry      = nullptr;
	for (uint32_t l = 0; l < numEntries(); l++)
	{
		entry                   = entryAt(l);
		entry->exProp("Offset") = (int)dir_offset;
		dir_offset += entry->size();
	}

	// Clear/init MemChunk
	mc.clear();
	mc.seek(0, SEEK_SET);
	mc.reSize(dir_offset + numEntries() * 32);

	// Setup wad type
	char wad_type[4] = { 'W', 'A', 'D', '2' };
	if (wad3_)
		wad_type[3] = '3';

	// Write the header
	uint32_t num_lumps = numEntries();
	mc.write(wad_type, 4);
	mc.write(&num_lumps, 4);
	mc.write(&dir_offset, 4);

	// Write the lumps
	for (uint32_t l = 0; l < num_lumps; l++)
	{
		entry = entryAt(l);
		mc.write(entry->rawData(), entry->size());
	}

	// Write the directory
	for (uint32_t l = 0; l < num_lumps; l++)
	{
		entry = entryAt(l);

		// Setup directory entry
		Wad2Entry info;
		memset(info.name, 0, 16);
		memcpy(info.name, CHR(entry->name()), entry->name().Len());
		info.cmprs  = (bool)entry->exProp("W2Comp");
		info.dsize  = entry->size();
		info.size   = entry->size();
		info.offset = (int)entry->exProp("Offset");
		info.type   = (int)entry->exProp("W2Type");

		// Write it
		mc.write(&info, 32);

		if (update)
			entry->setState(ArchiveEntry::State::Unmodified);
	}

	return true;
}
Beispiel #4
0
/* GobArchive::write
 * Writes the gob archive to a MemChunk
 * Returns true if successful, false otherwise
 *******************************************************************/
bool GobArchive::write(MemChunk& mc, bool update)
{
	// Determine directory offset & individual lump offsets
	uint32_t dir_offset = 8;
	ArchiveEntry* entry = NULL;
	for (uint32_t l = 0; l < numEntries(); l++)
	{
		entry = getEntry(l);
		setEntryOffset(entry, dir_offset);
		dir_offset += entry->getSize();
	}

	// Clear/init MemChunk
	mc.clear();
	mc.seek(0, SEEK_SET);
	mc.reSize(dir_offset + 4 + numEntries() * 21);

	// Write the header
	uint32_t num_lumps = wxINT32_SWAP_ON_BE(numEntries());
	dir_offset = wxINT32_SWAP_ON_BE(dir_offset);
	char header[4] = { 'G', 'O', 'B', 0xA };
	mc.write(header, 4);
	mc.write(&dir_offset, 4);

	// Write the lumps
	for (uint32_t l = 0; l < numEntries(); l++)
	{
		entry = getEntry(l);
		mc.write(entry->getData(), entry->getSize());
	}

	// Write the directory
	mc.write(&num_lumps, 4);
	for (uint32_t l = 0; l < numEntries(); l++)
	{
		entry = getEntry(l);
		char name[13] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
		long offset = wxINT32_SWAP_ON_BE(getEntryOffset(entry));
		long size = wxINT32_SWAP_ON_BE(entry->getSize());

		for (size_t c = 0; c < entry->getName().length() && c < 13; c++)
			name[c] = entry->getName()[c];

		mc.write(&offset, 4);
		mc.write(&size, 4);
		mc.write(name, 13);

		if (update)
		{
			entry->setState(0);
			entry->exProp("Offset") = (int)offset;
		}
	}

	return true;
}
Beispiel #5
0
/* SImage::getRGBData
 * Loads the image as RGB data into <mc>. Returns false if image is
 * invalid, true otherwise
 *******************************************************************/
bool SImage::getRGBData(MemChunk& mc, Palette8bit* pal)
{
	// Check the image is valid
	if (!isValid())
		return false;

	// Init rgb data
	mc.reSize(width * height * 3, false);

	if (type == RGBA)
	{
		// RGBA format, remove alpha information
		for (int a = 0; a < width * height * 4; a += 4)
			mc.write(&data[a], 3);

		return true;
	}
	else if (type == PALMASK)
	{
		// Paletted, convert to RGB

		// Get palette to use
		if (has_palette || !pal)
			pal = &palette;

		// Build RGB data
		uint8_t rgba[4];
		for (int a = 0; a < width * height; a ++)
		{
			pal->colour(data[a]).write(rgba);
			mc.write(rgba, 3);
		}

		return true;
	}
	else if (type == ALPHAMAP)
	{
		// Alpha map, convert to RGB

		uint8_t rgba[4];
		rgba_t col;
		for (int a = 0; a < width * height; a++)
		{
			// Get pixel as colour (greyscale)
			col.set(data[a], data[a], data[a], data[a]);

			col.write(rgba);	// Write colour to array
			mc.write(rgba, 3);	// Write array to MemChunk
		}
	}

	return false;	// Invalid image type
}
Beispiel #6
0
/* HogArchive::write
 * Writes the hog archive to a MemChunk
 * Returns true if successful, false otherwise
 *******************************************************************/
bool HogArchive::write(MemChunk& mc, bool update)
{
	// Determine individual lump offsets
	uint32_t offset = 3;
	ArchiveEntry* entry = NULL;
	for (uint32_t l = 0; l < numEntries(); l++)
	{
		offset += 17;
		entry = getEntry(l);
		setEntryOffset(entry, offset);
		if (update)
		{
			entry->setState(0);
			entry->exProp("Offset") = (int)offset;
		}
		offset += entry->getSize();
	}

	// Clear/init MemChunk
	mc.clear();
	mc.seek(0, SEEK_SET);
	mc.reSize(offset);

	// Write the header
	char header[3] = { 'D', 'H', 'F' };
	mc.write(header, 3);

	// Write the lumps
	for (uint32_t l = 0; l < numEntries(); l++)
	{
		entry = getEntry(l);
		mc.write(entry->getData(), entry->getSize());
	}

	// Write the directory
	for (uint32_t l = 0; l < numEntries(); l++)
	{
		entry = getEntry(l);
		char name[13] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
		long size = wxINT32_SWAP_ON_BE(entry->getSize());

		for (size_t c = 0; c < entry->getName().length() && c < 13; c++)
			name[c] = entry->getName()[c];

		mc.write(name, 13);
		mc.write(&size, 4);
		mc.write(entry->getData(), entry->getSize());
	}

	return true;
}
Beispiel #7
0
/* MemChunk::exportMemChunk
 * Writes the MemChunk data to another MemChunk, starting from
 * [start] to [start+size]. If [size] is 0, writes from [start] to
 * the end of the data
 *******************************************************************/
bool MemChunk::exportMemChunk(MemChunk& mc, uint32_t start, uint32_t size)
{
	// Check data exists
	if (!hasData())
		return false;

	// Check parameters
	if (start >= this->size || start + size > this->size)
		return false;

	// Check size
	if (size == 0)
		size = this->size - start;

	// Write data to MemChunk
	mc.reSize(size, false);
	return mc.importMem(data+start, size);
}
Beispiel #8
0
// -----------------------------------------------------------------------------
// Writes the grp archive to a MemChunk
// Returns true if successful, false otherwise
// -----------------------------------------------------------------------------
bool GrpArchive::write(MemChunk& mc, bool update)
{
	// Clear/init MemChunk
	mc.clear();
	mc.seek(0, SEEK_SET);
	mc.reSize((1 + numEntries()) * 16);
	ArchiveEntry* entry;

	// Write the header
	uint32_t num_lumps = numEntries();
	mc.write("KenSilverman", 12);
	mc.write(&num_lumps, 4);

	// Write the directory
	for (uint32_t l = 0; l < num_lumps; l++)
	{
		entry         = entryAt(l);
		char name[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
		long size     = entry->size();

		for (size_t c = 0; c < entry->name().length() && c < 12; c++)
			name[c] = entry->name()[c];

		mc.write(name, 12);
		mc.write(&size, 4);

		if (update)
		{
			long offset = getEntryOffset(entry);
			entry->setState(ArchiveEntry::State::Unmodified);
			entry->exProp("Offset") = (int)offset;
		}
	}

	// Write the lumps
	for (uint32_t l = 0; l < num_lumps; l++)
	{
		entry = entryAt(l);
		mc.write(entry->rawData(), entry->size());
	}

	return true;
}
Beispiel #9
0
/* DatArchive::write
 * Writes the dat archive to a MemChunk
 * Returns true if successful, false otherwise
 *******************************************************************/
bool DatArchive::write(MemChunk& mc, bool update)
{
	// Only two bytes are used for storing entry amount,
	// so abort for excessively large files:
	if (numEntries() > 65535)
		return false;

	// Determine directory offset, name offsets & individual lump offsets
	uint32_t dir_offset = 10;
	uint16_t name_offset = numEntries() * 12;
	uint32_t name_size = 0;
	string previousname = "";
	uint16_t* nameoffsets = new uint16_t[numEntries()];
	ArchiveEntry* entry = NULL;
	for (uint16_t l = 0; l < numEntries(); l++)
	{
		entry = getEntry(l);
		setEntryOffset(entry, dir_offset);
		dir_offset += entry->getSize();

		// Does the entry has a name?
		string name = entry->getName();
		if (l > 0 && previousname.length() > 0 && name.length() > previousname.length() &&
		        !previousname.compare(0, previousname.length(), name, 0, previousname.length()) &&
		        name.at(previousname.length()) == '+')
		{
			// This is a fake name
			name = "";
			nameoffsets[l] = 0;
		}
		else
		{
			// This is a true name
			previousname = name;
			nameoffsets[l] = uint16_t(name_offset + name_size);
			name_size += name.length() + 1;
		}
	}

	// Clear/init MemChunk
	mc.clear();
	mc.seek(0, SEEK_SET);
	mc.reSize(dir_offset + name_size + numEntries() * 12);

	// Write the header
	uint16_t num_lumps = wxINT16_SWAP_ON_BE(numEntries());
	dir_offset = wxINT32_SWAP_ON_BE(dir_offset);
	uint32_t unknown = 0;
	mc.write(&num_lumps, 2);
	mc.write(&dir_offset, 4);
	mc.write(&unknown, 4);

	// Write the lumps
	for (uint16_t l = 0; l < numEntries(); l++)
	{
		entry = getEntry(l);
		mc.write(entry->getData(), entry->getSize());
	}

	// Write the directory
	for (uint16_t l = 0; l < num_lumps; l++)
	{
		entry = getEntry(l);

		uint32_t offset = wxINT32_SWAP_ON_BE(getEntryOffset(entry));
		uint32_t size = wxINT32_SWAP_ON_BE(entry->getSize());
		uint16_t nameofs = wxINT16_SWAP_ON_BE(nameoffsets[l]);
		uint16_t flags = wxINT16_SWAP_ON_BE((entry->isEncrypted() == ENC_SCRLE0) ? 1 : 0);

		mc.write(&offset,	4);		// Offset
		mc.write(&size,		4);		// Size
		mc.write(&nameofs,	2);		// Name offset
		mc.write(&flags,	2);		// Flags

		if (update)
		{
			entry->setState(0);
			entry->exProp("Offset") = (int)wxINT32_SWAP_ON_BE(offset);
		}
	}

	// Write the names
	for (uint16_t l = 0; l < num_lumps; l++)
	{
		uint8_t zero = 0;
		entry = getEntry(l);
		if (nameoffsets[l])
		{
			mc.write(CHR(entry->getName()), entry->getName().length());
			mc.write(&zero, 1);
		}
	}

	// Clean-up
	delete[] nameoffsets;

	// Finished!
	return true;
}
Beispiel #10
0
// -----------------------------------------------------------------------------
// Writes Chasm bin archive to a MemChunk
// Returns true if successful, false otherwise
// -----------------------------------------------------------------------------
bool ChasmBinArchive::write(MemChunk& mc, bool update)
{
	// Clear current data
	mc.clear();

	// Get archive tree as a list
	vector<ArchiveEntry*> entries;
	getEntryTreeAsList(entries);

	// Check limit of entries count
	const uint16_t num_entries = static_cast<uint16_t>(entries.size());

	if (num_entries > MAX_ENTRY_COUNT)
	{
		LOG_MESSAGE(1, "ChasmBinArchive::write: Bin archive can contain no more than %u entries", MAX_ENTRY_COUNT);
		Global::error = "Maximum number of entries exceeded for Chasm: The Rift bin archive";
		return false;
	}

	// Init data size
	static const uint32_t HEADER_TOC_SIZE = HEADER_SIZE + ENTRY_SIZE * MAX_ENTRY_COUNT;
	mc.reSize(HEADER_TOC_SIZE, false);
	mc.fillData(0);

	// Write header
	const char magic[4] = { 'C', 'S', 'i', 'd' };
	mc.seek(0, SEEK_SET);
	mc.write(magic, 4);
	mc.write(&num_entries, sizeof num_entries);

	// Write directory
	uint32_t offset = HEADER_TOC_SIZE;

	for (uint16_t i = 0; i < num_entries; ++i)
	{
		ArchiveEntry* const entry = entries[i];

		// Update entry
		if (update)
		{
			entry->setState(0);
			entry->exProp("Offset") = static_cast<int>(offset);
		}

		// Check entry name
		string  name        = entry->getName();
		uint8_t name_length = static_cast<uint8_t>(name.Length());

		if (name_length > NAME_SIZE - 1)
		{
			LOG_MESSAGE(1, "Warning: Entry %s name is too long, it will be truncated", name);
			name.Truncate(NAME_SIZE - 1);
			name_length = static_cast<uint8_t>(NAME_SIZE - 1);
		}

		// Write entry name
		char name_data[NAME_SIZE] = {};
		memcpy(name_data, &name_length, 1);
		memcpy(name_data + 1, CHR(name), name_length);
		mc.write(name_data, NAME_SIZE);

		// Write entry size
		const uint32_t size = entry->getSize();
		mc.write(&size, sizeof size);

		// Write entry offset
		mc.write(&offset, sizeof offset);

		// Increment/update offset
		offset += size;
	}

	// Write entry data
	mc.reSize(offset);
	mc.seek(HEADER_TOC_SIZE, SEEK_SET);

	for (uint16_t i = 0; i < num_entries; ++i)
	{
		ArchiveEntry* const entry = entries[i];
		mc.write(entry->getData(), entry->getSize());
	}

	return true;
}
Beispiel #11
0
/* EntryOperations::modifytRNSChunk
 * Add or remove the tRNS chunk from a PNG entry
 * Returns true if the entry was altered
 *******************************************************************/
bool EntryOperations::modifytRNSChunk(ArchiveEntry* entry, bool value)
{
	// Avoid NULL pointers, they're annoying.
	if (!entry || !entry->getType())
		return false;

	// Don't bother if the entry is locked.
	if (entry->isLocked())
		return false;

	// Check entry type
	if (!(entry->getType()->getFormat() == "img_png"))
	{
		wxLogMessage("Entry \"%s\" is of type \"%s\" rather than PNG", entry->getName(), entry->getTypeString());
		return false;
	}

	// Read width and height from IHDR chunk
	const uint8_t* data = entry->getData(true);
	const ihdr_t* ihdr = (ihdr_t*)(data + 12);

	// tRNS chunks are only valid for paletted PNGs, and must be before the first IDAT.
	// Specs say they must be after PLTE chunk as well, so to play it safe, we'll insert
	// them just before the first IDAT.
	uint32_t trns_start = 0;
	uint32_t trns_size	= 0;
	uint32_t idat_start = 0;
	for (uint32_t a = 0; a < entry->getSize(); a++)
	{

		// Check for 'tRNS' header
		if (data[a] == 't' && data[a + 1] == 'R' &&
		        data[a + 2] == 'N' && data[a + 3] == 'S')
		{
			trns_start = a - 4;
			trans_chunk_t* trns = (trans_chunk_t*)(data + a);
			trns_size = 12 + READ_B32(data, a-4);
		}

		// Stop when we get to the 'IDAT' chunk
		if (data[a] == 'I' && data[a + 1] == 'D' &&
		        data[a + 2] == 'A' && data[a + 3] == 'T')
		{
			idat_start = a - 4;
			break;
		}
	}

	// The IDAT chunk starts before the header is finished, this doesn't make sense, abort.
	if (idat_start < 33)
		return false;

	// We want to set tRNS, and it is already there: nothing to do.
	if (value && trns_start > 0)
		return false;

	// We want to unset tRNS, and it is already not there: nothing to do either.
	else if (!value && trns_start == 0)
		return false;

	// We want to set tRNS, which is missing: create it. We're just going to set index 0 to 0,
	// and leave the rest of the palette indices alone.
	else if (value && trns_start == 0)
	{
		// Build new PNG from the original w/ the new tRNS chunk
		MemChunk npng;

		// Init new png data size
		npng.reSize(entry->getSize() + 13);

		// Write PNG header stuff up to the first IDAT chunk
		npng.write(data, idat_start);

		// Create new tRNS chunk
		uint32_t csize = wxUINT32_SWAP_ON_LE(1);
		trans_chunk_t gc = { 't', 'R', 'N', 'S', '\0' };
		uint32_t dcrc = wxUINT32_SWAP_ON_LE(Misc::crc((uint8_t*)&gc, 5));

		// Write tRNS chunk
		npng.write(&csize, 4);
		npng.write(&gc, 5);
		npng.write(&dcrc, 4);

		// Write the rest of the PNG data
		uint32_t to_write = entry->getSize() - idat_start;
		npng.write(data + idat_start, to_write);

		// Load new png data to the entry
		entry->importMemChunk(npng);
	}

	// We want to unset tRNS, which is present: delete it.
	else if (!value && trns_start > 0)
	{
		// Build new PNG from the original without the tRNS chunk
		MemChunk npng;
		uint32_t rest_start = trns_start + trns_size;

		// Init new png data size
		npng.reSize(entry->getSize() - trns_size);

		// Write PNG header and stuff up to tRNS start
		npng.write(data, trns_start);

		// Write the rest of the PNG data
		uint32_t to_write = entry->getSize() - rest_start;
		npng.write(data + rest_start, to_write);

		// Load new png data to the entry
		entry->importMemChunk(npng);
	}

	// We don't know what we want, but it can't be good, so we do nothing.
	else
		return false;

	return true;
}
Beispiel #12
0
bool PaletteEntryPanel::generateColormaps()
{
	if (!entry || !entry->getParent() || ! palettes[0])
		return false;

	MemChunk mc;
	SImage img;
	MemChunk imc;
	mc.reSize(34*256);
	mc.seek(0, SEEK_SET);
	imc.reSize(34*256*4);
	imc.seek(0, SEEK_SET);
	uint8_t rgba[4];
	rgba[3] = 255;

	rgba_t rgb;
	float grey;
	// Generate 34 maps: the first 32 for diminishing light levels,
	// the 33th for the inverted grey map used by invulnerability.
	// The 34th colormap remains empty and black.
	for (size_t l = 0; l < 34; ++l)
	{
		for (size_t c = 0; c < 256; ++c)
		{
			rgb = palettes[0]->colour(c);
			if (l < 32)
			{
				// Generate light maps
				DIMINISH(rgb.r, l);
				DIMINISH(rgb.g, l);
				DIMINISH(rgb.b, l);
#if (0)
			}
			else if (l == GREENMAP)
			{
				// Point of mostly useless trivia: the green "light amp" colormap in the Press Release beta
				// have colors that, on average, correspond to a bit less than (R*75/256, G*225/256, B*115/256)
#endif
			}
			else if (l == GRAYMAP)
			{
				// Generate inverse map
				grey = ((float)rgb.r/256.0 * col_greyscale_r) + ((float)rgb.g/256.0 * col_greyscale_g) + ((float)rgb.b/256.0 * col_greyscale_b);
				grey = 1.0 - grey;
				// Clamp value: with Id Software's values, the sum is greater than 1.0 (0.299+0.587+0.144=1.030)
				// This means the negation above can give a negative value (for example, with RGB values of 247 or more),
				// which will not be converted correctly to unsigned 8-bit int in the rgba_t struct.
				if (grey < 0.0) grey = 0;
				rgb.r = rgb.g = rgb.b = grey*255;
			}
			else
			{
				// Fill with 0
				rgb = palettes[0]->colour(0);
			}
			rgba[0] = rgb.r; rgba[1] = rgb.g; rgba[2] = rgb.b;
			imc.write(&rgba, 4);
			mc[(256*l)+c] = palettes[0]->nearestColour(rgb);
		}
	}
#if 0
	// Create truecolor image
	uint8_t* imd = new uint8_t[256*34*4];
	memcpy(imd, imc.getData(), 256*34*4);
	img.setImageData(imd, 256, 34, RGBA);
	// imd will be freed by img's destructor
	ArchiveEntry* tcolormap;
	string name = entry->getName(true) + "-tcm.png";
	tcolormap = new ArchiveEntry(name);
	if (tcolormap)
	{
		entry->getParent()->addEntry(tcolormap);
		SIFormat::getFormat("png")->saveImage(img, tcolormap->getMCData());
		EntryType::detectEntryType(tcolormap);
	}
#endif
	// Now override or create new entry
	ArchiveEntry* colormap;
	colormap = entry->getParent()->getEntry("COLORMAP", true);
	bool preexisting = colormap != NULL;
	if (!colormap)
	{
		// We need to create this entry
		colormap = new ArchiveEntry("COLORMAP.lmp", 34*256);
	}
	if (!colormap)
		return false;
	colormap->importMemChunk(mc);
	if (!preexisting)
	{
		entry->getParent()->addEntry(colormap);
	}
	return true;
}
Beispiel #13
0
/* EntryOperations::setGfxOffsets
 * Changes the offsets of the given gfx entry. Returns false if the
 * entry is invalid or not an offset-supported format, true otherwise
 *******************************************************************/
bool EntryOperations::setGfxOffsets(ArchiveEntry* entry, int x, int y)
{
	if (entry == NULL || entry->getType() == NULL)
		return false;

	// Check entry type
	EntryType* type = entry->getType();
	string entryformat = type->getFormat();
	if (!(entryformat == "img_doom" || entryformat == "img_doom_arah" ||
		entryformat == "img_doom_alpha" || entryformat == "img_doom_beta" ||
		entryformat == "img_png"))
	{
		wxLogMessage("Entry \"%s\" is of type \"%s\" which does not support offsets", entry->getName(), entry->getType()->getName());
		return false;
	}

	// Doom gfx format, normal and beta version.
	// Also arah format from alpha 0.2 because it uses the same header format.
	if (entryformat == "img_doom" || entryformat == "img_doom_beta" || entryformat == "image_doom_arah")
	{
		// Get patch header
		patch_header_t header;
		entry->seek(0, SEEK_SET);
		entry->read(&header, 8);

		// Apply new offsets
		header.left = wxINT16_SWAP_ON_BE((int16_t)x);
		header.top = wxINT16_SWAP_ON_BE((int16_t)y);

		// Write new header to entry
		entry->seek(0, SEEK_SET);
		entry->write(&header, 8);
	}

	// Doom alpha gfx format
	else if (entryformat == "img_doom_alpha")
	{
		// Get patch header
		entry->seek(0, SEEK_SET);
		oldpatch_header_t header;
		entry->read(&header, 4);

		// Apply new offsets
		header.left = (int8_t)x;
		header.top = (int8_t)y;

		// Write new header to entry
		entry->seek(0, SEEK_SET);
		entry->write(&header, 4);
	}

	// PNG format
	else if (entryformat == "img_png")
	{
		// Find existing grAb chunk
		const uint8_t* data = entry->getData(true);
		uint32_t grab_start = 0;
		for (uint32_t a = 0; a < entry->getSize(); a++)
		{
			// Check for 'grAb' header
			if (data[a] == 'g' && data[a + 1] == 'r' &&
				data[a + 2] == 'A' && data[a + 3] == 'b')
			{
				grab_start = a - 4;
				break;
			}

			// Stop when we get to the 'IDAT' chunk
			if (data[a] == 'I' && data[a + 1] == 'D' &&
				data[a + 2] == 'A' && data[a + 3] == 'T')
				break;
		}

		// Create new grAb chunk
		uint32_t csize = wxUINT32_SWAP_ON_LE(8);
		grab_chunk_t gc ={ { 'g', 'r', 'A', 'b' }, wxINT32_SWAP_ON_LE(x), wxINT32_SWAP_ON_LE(y) };
		uint32_t dcrc = wxUINT32_SWAP_ON_LE(Misc::crc((uint8_t*)&gc, 12));

		// Build new PNG from the original w/ the new grAb chunk
		MemChunk npng;
		uint32_t rest_start = 33;

		// Init new png data size
		if (grab_start == 0)
			npng.reSize(entry->getSize() + 20);
		else
			npng.reSize(entry->getSize());

		// Write PNG header and IHDR chunk
		npng.write(data, 33);

		// If no existing grAb chunk was found, write new one here
		if (grab_start == 0)
		{
			npng.write(&csize, 4);
			npng.write(&gc, 12);
			npng.write(&dcrc, 4);
		}
		else
		{
			// Otherwise write any other data before the existing grAb chunk
			uint32_t to_write = grab_start - 33;
			npng.write(data + 33, to_write);
			rest_start = grab_start + 20;

			// And now write the new grAb chunk
			npng.write(&csize, 4);
			npng.write(&gc, 12);
			npng.write(&dcrc, 4);
		}

		// Write the rest of the PNG data
		uint32_t to_write = entry->getSize() - rest_start;
		npng.write(data + rest_start, to_write);

		// Load new png data to the entry
		entry->importMemChunk(npng);

		// Set its type back to png
		entry->setType(type);
	}
	else
		return false;

	return true;
}
Beispiel #14
0
/* EntryOperations::modifyalPhChunk
 * Add or remove the alPh chunk from a PNG entry
 *******************************************************************/
bool EntryOperations::modifyalPhChunk(ArchiveEntry* entry, bool value)
{
	if (!entry || !entry->getType())
		return false;

	// Don't bother if the entry is locked.
	if (entry->isLocked())
		return false;

	// Check entry type
	if (!(entry->getType()->getFormat() == "img_png"))
	{
		wxLogMessage("Entry \"%s\" is of type \"%s\" rather than PNG", entry->getName(), entry->getType()->getName());
		return false;
	}

	// Read width and height from IHDR chunk
	const uint8_t* data = entry->getData(true);
	const ihdr_t* ihdr = (ihdr_t*)(data + 12);

	// Find existing alPh chunk
	uint32_t alph_start = 0;
	for (uint32_t a = 0; a < entry->getSize(); a++)
	{
		// Check for 'alPh' header
		if (data[a] == 'a' && data[a + 1] == 'l' &&
		        data[a + 2] == 'P' && data[a + 3] == 'h')
		{
			alph_start = a - 4;
			break;
		}

		// Stop when we get to the 'IDAT' chunk
		if (data[a] == 'I' && data[a + 1] == 'D' &&
		        data[a + 2] == 'A' && data[a + 3] == 'T')
			break;
	}

	// We want to set alPh, and it is already there: nothing to do.
	if (value && alph_start > 0)
		return false;

	// We want to unset alPh, and it is already not there: nothing to do either.
	else if (!value && alph_start == 0)
		return false;

	// We want to set alPh, which is missing: create it.
	else if (value && alph_start == 0)
	{
		// Build new PNG from the original w/ the new alPh chunk
		MemChunk npng;

		// Init new png data size
		npng.reSize(entry->getSize() + 12);

		// Write PNG header and IHDR chunk
		npng.write(data, 33);

		// Create new alPh chunk
		uint32_t csize = wxUINT32_SWAP_ON_LE(0);
		alph_chunk_t gc = { 'a', 'l', 'P', 'h' };
		uint32_t dcrc = wxUINT32_SWAP_ON_LE(Misc::crc((uint8_t*)&gc, 4));

		// Create alPh chunk
		npng.write(&csize, 4);
		npng.write(&gc, 4);
		npng.write(&dcrc, 4);

		// Write the rest of the PNG data
		uint32_t to_write = entry->getSize() - 33;
		npng.write(data + 33, to_write);

		// Load new png data to the entry
		entry->importMemChunk(npng);
	}

	// We want to unset alPh, which is present: delete it.
	else if (!value && alph_start > 0)
	{
		// Build new PNG from the original without the alPh chunk
		MemChunk npng;
		uint32_t rest_start = alph_start + 12;

		// Init new png data size
		npng.reSize(entry->getSize() - 12);

		// Write PNG info before alPh chunk
		npng.write(data, alph_start);

		// Write the rest of the PNG data
		uint32_t to_write = entry->getSize() - rest_start;
		npng.write(data + rest_start, to_write);

		// Load new png data to the entry
		entry->importMemChunk(npng);
	}

	// We don't know what we want, but it can't be good, so we do nothing.
	else
		return false;

	return true;
}
Beispiel #15
0
/* WadArchive::write
 * Writes the wad archive to a MemChunk
 * Returns true if successful, false otherwise
 *******************************************************************/
bool WadArchive::write(MemChunk& mc, bool update)
{
	// Don't write if iwad
	if (iwad && iwad_lock)
	{
		Global::error = "IWAD saving disabled";
		return false;
	}

	// Determine directory offset & individual lump offsets
	uint32_t dir_offset = 12;
	ArchiveEntry* entry = NULL;
	for (uint32_t l = 0; l < numEntries(); l++)
	{
		entry = getEntry(l);
		setEntryOffset(entry, dir_offset);
		dir_offset += entry->getSize();
	}

	// Clear/init MemChunk
	mc.clear();
	mc.seek(0, SEEK_SET);
	mc.reSize(dir_offset + numEntries() * 16);

	// Setup wad type
	char wad_type[4] = { 'P', 'W', 'A', 'D' };
	if (iwad) wad_type[0] = 'I';

	// Write the header
	uint32_t num_lumps = numEntries();
	mc.write(wad_type, 4);
	mc.write(&num_lumps, 4);
	mc.write(&dir_offset, 4);

	// Write the lumps
	for (uint32_t l = 0; l < num_lumps; l++)
	{
		entry = getEntry(l);
		mc.write(entry->getData(), entry->getSize());
	}

	// Write the directory
	for (uint32_t l = 0; l < num_lumps; l++)
	{
		entry = getEntry(l);
		char name[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
		long offset = getEntryOffset(entry);
		long size = entry->getSize();

		for (size_t c = 0; c < entry->getName().length() && c < 8; c++)
			name[c] = entry->getName()[c];

		mc.write(&offset, 4);
		mc.write(&size, 4);
		mc.write(name, 8);

		if (update)
		{
			entry->setState(0);
			entry->exProp("Offset") = (int)offset;
		}
	}

	return true;
}
Beispiel #16
0
// -----------------------------------------------------------------------------
// Writes the disk archive to a MemChunk.
// Returns true if successful, false otherwise
// -----------------------------------------------------------------------------
bool DiskArchive::write(MemChunk& mc, bool update)
{
	// Clear current data
	mc.clear();

	// Get archive tree as a list
	vector<ArchiveEntry*> entries;
	getEntryTreeAsList(entries);

	// Process entry list
	uint32_t num_entries  = 0;
	uint32_t size_entries = 0;
	for (unsigned a = 0; a < entries.size(); a++)
	{
		// Ignore folder entries
		if (entries[a]->getType() == EntryType::folderType())
			continue;

		// Increment directory offset and size
		size_entries += entries[a]->getSize();
		++num_entries;
	}

	// Init data size
	uint32_t start_offset = 8 + (num_entries * 72);
	uint32_t offset       = start_offset;
	mc.reSize(size_entries + start_offset, false);

	// Write header
	num_entries = wxUINT32_SWAP_ON_LE(num_entries);
	mc.seek(0, SEEK_SET);
	mc.write(&num_entries, 4);

	// Write directory
	for (unsigned a = 0; a < entries.size(); a++)
	{
		// Skip folders
		if (entries[a]->getType() == EntryType::folderType())
			continue;

		// Update entry
		if (update)
		{
			entries[a]->setState(0);
			entries[a]->exProp("Offset") = (int)offset;
		}

		// Check entry name
		string name = entries[a]->getPath(true);
		name.Replace("/", "\\");
		// The leading "GAME:\" part of the name means there is only 58 usable characters for path
		if (name.Len() > 58)
		{
			LOG_MESSAGE(
				1, "Warning: Entry %s path is too long (> 58 characters), putting it in the root directory", name);
			wxFileName fn(name);
			name = fn.GetFullName();
			if (name.Len() > 57)
				name.Truncate(57);
			// Add leading "\"
			name = "\\" + name;
		}
		name = "GAME:" + name;

		DiskEntry dent;

		// Write entry name
		// The names field are padded with FD for doom.disk, FE for doom2.disk. No idea whether
		// a non-null padding is actually required, though. It probably should work with anything.
		memset(dent.name, 0xFE, 64);
		memcpy(dent.name, CHR(name), name.Length());
		dent.name[name.Length()] = 0;

		// Write entry offset
		dent.offset = wxUINT32_SWAP_ON_LE(offset - start_offset);

		// Write entry size
		dent.length = wxUINT32_SWAP_ON_LE(entries[a]->getSize());

		// Actually write stuff
		mc.write(&dent, 72);

		// Increment/update offset
		offset += wxUINT32_SWAP_ON_LE(dent.length);
	}

	// Finish writing header
	size_entries = wxUINT32_SWAP_ON_LE(size_entries);
	mc.write(&size_entries, 4);

	// Write entry data
	for (unsigned a = 0; a < entries.size(); a++)
	{
		// Skip folders
		if (entries[a]->getType() == EntryType::folderType())
			continue;

		// Write data
		mc.write(entries[a]->getData(), entries[a]->getSize());
	}

	return true;
}
Beispiel #17
0
/* WadArchive::open
 * Reads wad format data from a MemChunk
 * Returns true if successful, false otherwise
 *******************************************************************/
bool WadArchive::open(MemChunk& mc)
{
	// Check data was given
	if (!mc.hasData())
		return false;

	// Read wad header
	uint32_t	num_lumps = 0;
	uint32_t	dir_offset = 0;
	char		wad_type[4] = "";
	mc.seek(0, SEEK_SET);
	mc.read(&wad_type, 4);		// Wad type
	mc.read(&num_lumps, 4);		// No. of lumps in wad
	mc.read(&dir_offset, 4);	// Offset to directory

	// Byteswap values for big endian if needed
	num_lumps = wxINT32_SWAP_ON_BE(num_lumps);
	dir_offset = wxINT32_SWAP_ON_BE(dir_offset);

	// Check the header
	if (wad_type[1] != 'W' || wad_type[2] != 'A' || wad_type[3] != 'D')
	{
		wxLogMessage("WadArchive::openFile: File %s has invalid header", filename);
		Global::error = "Invalid wad header";
		return false;
	}

	// Check for iwad
	if (wad_type[0] == 'I')
		iwad = true;

	// Stop announcements (don't want to be announcing modification due to entries being added etc)
	setMuted(true);

	vector<uint32_t> offsets;

	// Read the directory
	mc.seek(dir_offset, SEEK_SET);
	theSplashWindow->setProgressMessage("Reading wad archive data");
	for (uint32_t d = 0; d < num_lumps; d++)
	{
		// Update splash window progress
		theSplashWindow->setProgress(((float)d / (float)num_lumps));

		// Read lump info
		char name[9] = "";
		uint32_t offset = 0;
		uint32_t size = 0;

		mc.read(&offset, 4);	// Offset
		mc.read(&size, 4);		// Size
		mc.read(name, 8);		// Name
		name[8] = '\0';

		// Byteswap values for big endian if needed
		offset = wxINT32_SWAP_ON_BE(offset);
		size = wxINT32_SWAP_ON_BE(size);

		// Check to catch stupid shit
		if (size > 0)
		{
			if (offset == 0)
			{
				LOG_MESSAGE(2, "No.");
				continue;
			}
			if (VECTOR_EXISTS(offsets, offset))
			{
				LOG_MESSAGE(1, "Ignoring entry %d: %s, is a clone of a previous entry", d, name);
				continue;
			}
			offsets.push_back(offset);
		}

		// Hack to open Operation: Rheingold WAD files
		if (size == 0 && offset > mc.getSize())
			offset = 0;

		// Is there a compression/encryption thing going on?
		bool jaguarencrypt = !!(name[0] & 0x80);	// look at high bit
		name[0] = name[0] & 0x7F;					// then strip it away

		// Look for encryption shenanigans
		size_t actualsize = size;
		if (jaguarencrypt)
		{
			if (d < num_lumps - 1)
			{
				size_t pos = mc.currentPos();
				uint32_t nextoffset = 0;
				for (int i = 0; i + d < num_lumps; ++i)
				{
					mc.read(&nextoffset, 4);
					if (nextoffset != 0) break;
					mc.seek(12, SEEK_CUR);
				}
				nextoffset = wxINT32_SWAP_ON_BE(nextoffset);
				if (nextoffset == 0) nextoffset = dir_offset;
				mc.seek(pos, SEEK_SET);
				actualsize = nextoffset - offset;
			}
			else
			{
				if (offset > dir_offset)
				{
					actualsize = mc.getSize() - offset;
				}
				else
				{
					actualsize = dir_offset - offset;
				}
			}
		}

		// If the lump data goes past the end of the file,
		// the wadfile is invalid
		if (offset + actualsize > mc.getSize())
		{
			wxLogMessage("WadArchive::open: Wad archive is invalid or corrupt");
			Global::error = S_FMT("Archive is invalid and/or corrupt (lump %d: %s data goes past end of file)", d, name);
			setMuted(false);
			return false;
		}

		// Create & setup lump
		ArchiveEntry* nlump = new ArchiveEntry(wxString::FromAscii(name), size);
		nlump->setLoaded(false);
		nlump->exProp("Offset") = (int)offset;
		nlump->setState(0);

		if (jaguarencrypt)
		{
			nlump->setEncryption(ENC_JAGUAR);
			nlump->exProp("FullSize") = (int)size;
		}

		// Add to entry list
		getRoot()->addEntry(nlump);
	}

	// Detect namespaces (needs to be done before type detection as some types
	// rely on being within certain namespaces)
	updateNamespaces();

	// Detect all entry types
	MemChunk edata;
	theSplashWindow->setProgressMessage("Detecting entry types");
	for (size_t a = 0; a < numEntries(); a++)
	{
		// Update splash window progress
		theSplashWindow->setProgress((((float)a / (float)numEntries())));

		// Get entry
		ArchiveEntry* entry = getEntry(a);

		// Read entry data if it isn't zero-sized
		if (entry->getSize() > 0)
		{
			// Read the entry data
			mc.exportMemChunk(edata, getEntryOffset(entry), entry->getSize());
			if (entry->isEncrypted())
			{
				if (entry->exProps().propertyExists("FullSize")
				        && (unsigned)(int)(entry->exProp("FullSize")) >  entry->getSize())
					edata.reSize((int)(entry->exProp("FullSize")), true);
				if (!JaguarDecode(edata))
					wxLogMessage("%i: %s (following %s), did not decode properly", a, entry->getName(), a>0?getEntry(a-1)->getName():"nothing");
			}
			entry->importMemChunk(edata);
		}

		// Detect entry type
		EntryType::detectEntryType(entry);

		// Unload entry data if needed
		if (!archive_load_data)
			entry->unloadData();

		// Set entry to unchanged
		entry->setState(0);
	}

	// Identify #included lumps (DECORATE, GLDEFS, etc.)
	detectIncludes();

	// Detect maps (will detect map entry types)
	theSplashWindow->setProgressMessage("Detecting maps");
	detectMaps();

	// Setup variables
	setMuted(false);
	setModified(false);
	//if (iwad && iwad_lock) read_only = true;
	announce("opened");

	theSplashWindow->setProgressMessage("");

	return true;
}
Beispiel #18
0
// -----------------------------------------------------------------------------
// Writes the pod archive to a MemChunk
// Returns true if successful, false otherwise
// -----------------------------------------------------------------------------
bool PodArchive::write(MemChunk& mc, bool update)
{
	// Get all entries
	vector<ArchiveEntry*> entries;
	putEntryTreeAsList(entries);

	// Process entries
	int      ndirs     = 0;
	uint32_t data_size = 0;
	for (auto& entry : entries)
	{
		if (entry->type() == EntryType::folderType())
			ndirs++;
		else
			data_size += entry->size();
	}

	// Init MemChunk
	mc.clear();
	mc.reSize(4 + 80 + (entries.size() * 40) + data_size, false);
	LOG_MESSAGE(5, "MC size %d", mc.size());

	// Write no. entries
	uint32_t n_entries = entries.size() - ndirs;
	LOG_MESSAGE(5, "n_entries %d", n_entries);
	mc.write(&n_entries, 4);

	// Write id
	LOG_MESSAGE(5, "id %s", id_);
	mc.write(id_, 80);

	// Write directory
	FileEntry fe;
	fe.offset = 4 + 80 + (n_entries * 40);
	for (auto& entry : entries)
	{
		if (entry->type() == EntryType::folderType())
			continue;

		// Name
		memset(fe.name, 0, 32);
		string path = entry->path(true);
		path.Replace("/", "\\");
		path = path.AfterFirst('\\');
		// LOG_MESSAGE(2, path);
		memcpy(fe.name, CHR(path), path.Len());

		// Size
		fe.size = entry->size();

		// Write directory entry
		mc.write(fe.name, 32);
		mc.write(&fe.size, 4);
		mc.write(&fe.offset, 4);
		LOG_MESSAGE(
			5,
			"entry %s: old=%d new=%d size=%d",
			fe.name,
			entry->exProp("Offset").intValue(),
			fe.offset,
			entry->size());

		// Next offset
		fe.offset += fe.size;
	}

	// Write entry data
	for (auto& entry : entries)
		if (entry->type() != EntryType::folderType())
			mc.write(entry->rawData(), entry->size());

	return true;
}
Beispiel #19
0
/* PakArchive::write
 * Writes the pak archive to a MemChunk
 * Returns true if successful, false otherwise
 *******************************************************************/
bool PakArchive::write(MemChunk& mc, bool update)
{
	// Clear current data
	mc.clear();

	// Get archive tree as a list
	vector<ArchiveEntry*> entries;
	getEntryTreeAsList(entries);

	// Process entry list
	long dir_offset = 12;
	long dir_size = 0;
	for (unsigned a = 0; a < entries.size(); a++)
	{
		// Ignore folder entries
		if (entries[a]->getType() == EntryType::folderType())
			continue;

		// Increment directory offset and size
		dir_offset += entries[a]->getSize();
		dir_size += 64;
	}

	// Init data size
	mc.reSize(dir_offset + dir_size, false);

	// Write header
	char pack[4] = { 'P', 'A', 'C', 'K' };
	mc.seek(0, SEEK_SET);
	mc.write(pack, 4);
	mc.write(&dir_offset, 4);
	mc.write(&dir_size, 4);

	// Write directory
	mc.seek(dir_offset, SEEK_SET);
	long offset = 12;
	for (unsigned a = 0; a < entries.size(); a++)
	{
		// Skip folders
		if (entries[a]->getType() == EntryType::folderType())
			continue;

		// Update entry
		if (update)
		{
			entries[a]->setState(0);
			entries[a]->exProp("Offset") = (int)offset;
		}

		// Check entry name
		string name = entries[a]->getPath(true);
		name.Remove(0, 1);	// Remove leading /
		if (name.Len() > 56)
		{
			wxLogMessage("Warning: Entry %s path is too long (> 56 characters), putting it in the root directory", name);
			wxFileName fn(name);
			name = fn.GetFullName();
			if (name.Len() > 56)
				name.Truncate(56);
		}

		// Write entry name
		char name_data[56];
		memset(name_data, 0, 56);
		memcpy(name_data, CHR(name), name.Length());
		mc.write(name_data, 56);

		// Write entry offset
		mc.write(&offset, 4);

		// Write entry size
		long size = entries[a]->getSize();
		mc.write(&size, 4);

		// Increment/update offset
		offset += size;
	}

	// Write entry data
	mc.seek(12, SEEK_SET);
	for (unsigned a = 0; a < entries.size(); a++)
	{
		// Skip folders
		if (entries[a]->getType() == EntryType::folderType())
			continue;

		// Write data
		mc.write(entries[a]->getData(), entries[a]->getSize());
	}

	return true;
}
Beispiel #20
0
/* LfdArchive::write
 * Writes the lfd archive to a MemChunk
 * Returns true if successful, false otherwise
 *******************************************************************/
bool LfdArchive::write(MemChunk& mc, bool update)
{
	// Determine total size
	uint32_t dir_size = (numEntries() + 1)<<4;
	uint32_t total_size = dir_size;
	ArchiveEntry* entry = NULL;
	for (uint32_t l = 0; l < numEntries(); l++)
	{
		entry = getEntry(l);
		total_size += 16;
		setEntryOffset(entry, total_size);
		if (update)
		{
			entry->setState(0);
			entry->exProp("Offset") = (int)total_size;
		}
		total_size += entry->getSize();
	}

	// Clear/init MemChunk
	mc.clear();
	mc.seek(0, SEEK_SET);
	mc.reSize(total_size);

	// Variables
	char type[5] = "RMAP";
	char name[9] = "resource";
	size_t size = wxINT32_SWAP_ON_BE(numEntries()<<4);


	// Write the resource map first
	mc.write(type, 4);
	mc.write(name, 8);
	mc.write(&size,4);
	for (uint32_t l = 0; l < numEntries(); l++)
	{
		entry = getEntry(l);
		for (int t = 0; t < 5; ++t) type[t] = 0;
		for (int n = 0; n < 9; ++n) name[n] = 0;
		size = wxINT32_SWAP_ON_BE(entry->getSize());
		wxFileName fn(entry->getName());

		for (size_t c = 0; c < fn.GetName().length() && c < 9; c++)
			name[c] = fn.GetName()[c];
		for (size_t c = 0; c < fn.GetExt().length() && c < 5; c++)
			type[c] = fn.GetExt()[c];

		mc.write(type, 4);
		mc.write(name, 8);
		mc.write(&size,4);
	}

	// Write the lumps
	for (uint32_t l = 0; l < numEntries(); l++)
	{
		entry = getEntry(l);
		for (int t = 0; t < 5; ++t) type[t] = 0;
		for (int n = 0; n < 9; ++n) name[n] = 0;
		size = wxINT32_SWAP_ON_BE(entry->getSize());
		wxFileName fn(entry->getName());

		for (size_t c = 0; c < fn.GetName().length() && c < 9; c++)
			name[c] = fn.GetName()[c];
		for (size_t c = 0; c < fn.GetExt().length() && c < 5; c++)
			type[c] = fn.GetExt()[c];

		mc.write(type, 4);
		mc.write(name, 8);
		mc.write(&size,4);
		mc.write(entry->getData(), entry->getSize());
	}

	return true;
}
Beispiel #21
0
/* GLTexture::loadImagePortion
 * Loads a portion of a SImage to the texture. Only used internally,
 * the portion must be 128x128 in size
 *******************************************************************/
bool GLTexture::loadImagePortion(SImage* image, rect_t rect, Palette8bit* pal, bool add)
{
	// Check image was given
	if (!image)
		return false;

	// Check image is valid
	if (!image->isValid())
		return false;

	// Check portion rect is valid
	if (rect.width() <= 0 || rect.height() <= 0)
		return false;

	// Get RGBA image data
	MemChunk rgba;
	image->getRGBAData(rgba, pal);

	// Init texture data
	MemChunk portion;
	portion.reSize(rect.width() * rect.height() * 4, false);
	portion.fillData(0);

	// Read portion of image if rect isn't completely outside the image
	if (!(rect.left() >= image->getWidth() || rect.right() < 0 || rect.top() >= image->getHeight() || rect.bottom() < 0))
	{
		// Determine start of each row to read
		uint32_t row_start = 0;
		if (rect.left() > 0)
			row_start = rect.left();

		// Determine width of each row to read
		uint32_t row_width = rect.right() - row_start;
		if (rect.right() >= image->getWidth())
			row_width = image->getWidth() - row_start;

		// Determine difference between the left of the portion and the left of the image
		uint32_t skip = 0;
		if (rect.left() < 0)
			skip = (0 - rect.left()) * 4;

		// Create temp row buffer
		uint8_t* buf = new uint8_t[rect.width() * 4];

		// Go through each row
		for (int32_t row = rect.top(); row < rect.bottom(); row++)
		{
			// Clear row buffer
			memset(buf, 0, rect.width() * 4);

			// Check that the current row is within the image
			if (row >= 0 && row < image->getHeight())
			{
				// Seek to current row in image data
				rgba.seek((row * image->getWidth() + row_start) * 4, SEEK_SET);

				// Copy the row data
				rgba.read(buf + skip, row_width * 4);
			}

			// Write the row
			portion.write(buf, rect.width() * 4);
		}

		// Free buffer
		delete[] buf;
	}
	scale_x = scale_y = 1.0;

	// Generate texture from rgba data
	return loadData(portion.getData(), rect.width(), rect.height(), add);
}