
#ifndef _DMGHLI_H_
#define _DMGHLI_H_

#include "DMGHL.h"
#include "DMGHLt.h"
#include "SizeVuff.h"
#include "DMlzwD.h"
#include <crtdbg.h>

#define DMGHLi_MAX_ENTRIES (6144U)
#define DMGHLi_MAX_WIDTH 320
#define DMGHLi_MAX_HEIGHT 200
#define DMGHLi_MAX_TRACK 16

struct DMGHLiFIE
{	// 
	int nPos;
	// 
	int nLen;
	// 
	int nUncompressed;
	// 
	int w0, w2;

	// 
	DMGHLiFIE(): nPos(0), nLen(0), nUncompressed(0), w0(0), w2(0) { }
};

class DMGHLiFio
{
	// 
	FILE *f;
	// 
	bool fLE;

	// 
	void swap(UI16 &x)
	{
		if (fLE) return;

		union {
			UI16 w;
			UI8 b[2];
		}	s, t;
		s.w = x;
		t.b[0] = s.b[1];
		t.b[1] = s.b[0];
		x = t.w;
	}
	// 
	void swap(UI32 &x)
	{
		if (fLE) return;

		union {
			UI32 dw;
			UI8 b[2];
		}	s, t;
		s.dw = x;
		t.b[0] = s.b[3];
		t.b[1] = s.b[2];
		t.b[2] = s.b[1];
		t.b[3] = s.b[0];
		x = t.dw;
	}

public:
	// 
	DMGHLiFio(): f(NULL)
	{

	}
	// 
	~DMGHLiFio()
	{
		Close();
	}
	// 
	void Close()
	{
		if (f) {
			fclose(f); f = NULL;
		}
	}
	// 
	bool OpenRead(LPCSTR pszFileIn, bool fLE)
	{
		Close();

		if ((f = fopen(pszFileIn, "rb")) != NULL) {
			this->fLE = fLE;
			return true;
		}
		return false;
	}
	// 
	int ReadByte()
	{
		UI8 x;
		if (fread(&x, 1, 1, f) != 1)
			return -1;
		return x;
	}
	// 
	int ReadWord()
	{
		UI16 x;
		if (fread(&x, 2, 1, f) != 1)
			return -1;
		swap(x);
		return x;
	}
	// 
	int ReadDWord()
	{
		UI32 x;
		if (fread(&x, 4, 1, f) != 1)
			return -1;
		swap(x);
		return x;
	}
	// 
	int ReadBytes(void *pv, int cb)
	{
		int r = fread(pv, 1, cb, f);
		return r;
	}
	// 
	int GetLength()
	{
		int i = ftell(f);
		fseek(f, 0, SEEK_END);
		int n = ftell(f);
		fseek(f, i, SEEK_SET);
		return n;
	}
	// 
	bool SeekTo(int i)
	{
		if (fseek(f, i, SEEK_SET) == 0)
			return true;
		return false;
	}
};

struct DMGHLiPAL
{
	// 
	COLORREF color[16];

	// 
	void UseDM1(int i);
	// 
	void UseDM2From16Bytes(const UI8 *p16);
};

struct DMGHLiIMG8
{
	// 8bpp pixels
	UI8 cBits[DMGHLi_MAX_HEIGHT][DMGHLi_MAX_WIDTH];
	// 
	int cx, cy;

	// 
	inline UI8 GetPixel(int x, int y) const
	{
		return cBits[y][x];
	}
	// 
	inline void SetPixel(int x, int y, UI8 p)
	{
		cBits[y][x] = p;
	}
};

struct DMGHLiIMG8PixelWriter
{	// 
	DMGHLiIMG8 &img8;
	// 
	int cx, cy, x, y;

	// 
	DMGHLiIMG8PixelWriter(DMGHLiIMG8 &img8): img8(img8), cx(img8.cx), cy(img8.cy), x(0), y(0)
	{

	}
	// 
	bool WritePixels(int n, UI8 p)
	{
		while (n > 0 && y < cy)
		{
			img8.SetPixel(x, y, p);
			x++;
			if (x >= cx)
			{
				x -= cx; y++;
			}
			n--;
		}
		return (n == 0);
	}
	// 
	bool ProjectPixels(int nBack, int nLength)
	{
		int iPixel = y * cx + x;
		if (iPixel < nBack) return false;
		int nPixels = cx * cy;
		if (nPixels < iPixel + nLength) return false;

		iPixel -= nBack;

		int tx = iPixel % cx;
		int ty = iPixel / cx;

		while (nLength > 0 && y < cy)
		{
			img8.SetPixel(x, y, img8.GetPixel(tx, ty));

			x++;
			if (x >= cx)
			{
				x -= cx; y++;
			}

			tx++;
			if (tx >= cx)
			{
				tx -= cx; ty++;
			}

			nLength--;
		}
		return (nLength == 0);
	}
	// 
	bool IsEndOfImage()
	{
		return x == 0 && y == cy;
	}

};

struct DMGHLiIMG4
{	// 0000 PPPP
	// ||||
	// |||+-- 0x10: Transparent
	// ||+--- 0x20: Copy from previous line
	// |+---- 0x40: Pixeled by 1 of 6 most used colors
	// +----- 0x80: -
	UI8 cBits[DMGHLi_MAX_HEIGHT][DMGHLi_MAX_WIDTH];
	// 
	int cx, cy;
	// 
	DMGHLiPAL pal;

	// 
	inline UI8 GetPixel(int x, int y) const
	{
		return cBits[y][x];
	}
	// 
	inline void SetPixel(int x, int y, UI8 p)
	{
		cBits[y][x] = p;
	}
};

struct DMGHLiIMG4PixelWriter
{	// 
	DMGHLiIMG4 &img4;
	// 
	int cx, cy, x, y;

	// 
	DMGHLiIMG4PixelWriter(DMGHLiIMG4 &img4): img4(img4), cx(img4.cx), cy(img4.cy), x(0), y(0)
	{

	}
	// 
	bool WritePixels(int n, UI8 p)
	{
		while (n > 0 && y < cy)
		{
			img4.SetPixel(x, y, p);
			x++;
			if (x >= cx)
			{
				x -= cx; y++;
			}
			n--;
		}
		return (n == 0);
	}
	// 
	bool SpillPixels(int n)
	{
		while (n > 0 && y < cy && 0 < y)
		{
			img4.SetPixel(x, y, img4.GetPixel(x, y - 1));
			x++;
			if (x >= cx)
			{
				x -= cx; y++;
			}
			n--;
		}
		return (n == 0);
	}
	// 
	bool IsEndOfImage()
	{
		return x == 0 && y == cy;
	}

};

class DMGHLi8Bits
{
	// 
	const UI8 *pbPos;
	// 
	const UI8 *pbEnd;
	// 
	const UI8 *pbBegin;

public:
	// 
	DMGHLi8Bits(const void *pv, int cb)
	{
		pbEnd = (pbBegin = pbPos = (const UI8 *)pv) + cb;
	}
	// 
	int ReadByte()
	{
		int r = -1;
		if (pbPos < pbEnd) {
			r = *pbPos;
			pbPos++;
		}
		return r;
	}
	// 
	int GetLength()
	{
		return pbEnd - pbBegin;
	}
	// 
	int GetPosition()
	{
		return pbPos - pbBegin;
	}
};

class DMGHLiL2R4Bits
{
	// 
	const UI8 *pbPos;
	// 
	const UI8 *pbEnd;
	// 
	const UI8 *pbBegin;
	// 
	int nValue;
	// 
	int nBits;

	// 
	int ReadByte()
	{
		int r = -1;
		if (pbPos < pbEnd) {
			r = *pbPos;
			pbPos++;
		}
		return r;
	}

public:
	// 
	DMGHLiL2R4Bits(const void *pv, int cb)
	{
		pbEnd = (pbBegin = pbPos = (const UI8 *)pv) + cb;
		nBits = 0;
	}
	// 
	int Read4Bits()
	{
		int r = -1;
		if (nBits == 0)
		{
			if ((nValue = ReadByte()) < 0) return -1;
			nBits = 4;
			r = (nValue >> 4) & 0xF;
		}
		else
		{
			nBits = 0;
			r = (nValue >> 0) & 0xF;
		}
		return r;
	}
	// 
	int GetLength()
	{
		return pbEnd - pbBegin;
	}
	// 
	int GetPosition()
	{
		return pbPos - pbBegin;
	}
};

class DMGHLiDM2CT
{
	// 
	typedef struct {
		// 
		int wIdx;
		// 
		int wPrior;

	}	Tbl;
	// 
	Tbl cTable[4096];
	// 
	int nTableSize;

	// 
	void ReverseArray(int nArray[], int n)
	{
		// 12345
		// 54321 --- 5/2 => 2 Times

		// 1234
		// 4321  --- 4/2 => 2 Times
		int m = n / 2;
		for (int i = 0; i < m; i++) {
			Swap(nArray[i], nArray[n -i -1]);
		}
	}
	// 
	void Swap(int &x, int &y)
	{
		int z = x;
		x = y;
		y = z;
	}

public:
	// 
	DMGHLiDM2CT(): nTableSize(0)
	{

	}
	// 
	void Init(const void *pv, int cb, bool fLE)
	{
		const UI8 *pbPos = (const UI8 *)pv;
		const UI8 *pbEnd = pbPos + cb;

		nTableSize = 0;
		for (; pbPos + 4 <= pbEnd; pbPos += 4, nTableSize++) {
			cTable[nTableSize].wIdx   = fLE ? (pbPos[0] | pbPos[1] * 256) : (pbPos[0] * 256 | pbPos[1]);
			cTable[nTableSize].wPrior = fLE ? (pbPos[2] | pbPos[3] * 256) : (pbPos[2] * 256 | pbPos[3]);
		}
	}
	// 
	void Track(int iIdx, int nTracked[DMGHLi_MAX_TRACK])
	{
		int iTrackPos = 0;
		nTracked[0] = iIdx;
		iTrackPos++;
		for (; iTrackPos < DMGHLi_MAX_TRACK-1; ) {
			int iLoop = 0;
			for (; iLoop < nTableSize; iLoop++) {
				if (cTable[iLoop].wIdx != iIdx) continue;

				iIdx = nTracked[iTrackPos] = (int)cTable[iLoop].wPrior;
				iTrackPos++;
				break;
			}
			if (iLoop != nTableSize) continue;
			break;
		}

		nTracked[iTrackPos] = 0;
		ReverseArray(nTracked, iTrackPos);
	}
};

class DMGHLiF
{
public:
	// 
	DMGHLiF()
	{
		m_nEntries = 0;
	}
	// 
	void Close()
	{
		m_file.Close();

		m_nEntries = 0;
	}
	// 
	bool OpenRead(LPCSTR pszFileIn, bool fLE)
	{
		if (m_file.OpenRead(pszFileIn, fLE)) {
			this->fLE = fLE;
			if (InitRead()) {
				return true;
			}
			m_file.Close();
		}
		return false;
	}
	// 
	virtual bool InitRead() = 0;
	// 
	virtual bool IsEntryBinaryEncoded(int i) = 0;
	// 
	virtual bool DecodeEntryBinary(int i, SizeBuff &rSrc, SizeBuff &rDest) = 0;
	// 
	bool ReadEntry(int i, SizeBuff &rBuff)
	{
		if (i < 0 || i >= m_nEntries) return false;

		if (!IsEntryBinaryEncoded(i))
			return ReadEntryPure(i, rBuff);

		SizeBuff cBuff;
		if (!ReadEntryPure(i, cBuff)) return false;
		if (!DecodeEntryBinary(i, cBuff, rBuff)) return false;

		return true;
	}
	// 
	bool ReadEntryPure(int i, SizeBuff &rBuff)
	{
		if (i < 0 || i >= m_nEntries) return false;
        if (!m_file.SeekTo(m_cEntries[i].nPos)) return false;
		int n;
		if (!rBuff.Alloc(n = m_cEntries[i].nLen)) return false;
		if (m_file.ReadBytes(rBuff.GetData(), n) != n) return false;
		return true;
	}
	// 
	virtual bool ReadImg4(int i, DMGHLiIMG4 &img4) = 0;
	// 
	virtual bool ReadImg8(int i, DMGHLiIMG8 &img8) = 0;

protected:
	// 
	DMGHLiFIE m_cEntries[DMGHLi_MAX_ENTRIES];
	// 
	int m_nEntries;
	// 
	DMGHLiFio m_file;
	// 
	bool fLE;

};

class DMGHLiFDMCSB1 : public DMGHLiF
{
	// 
	virtual bool InitRead()
	{
		int i;
		int n = (m_nEntries = m_file.ReadWord());
		if (n < 1 || n > DMGHLi_MAX_ENTRIES) return false;

		int iPos = 2;

		for (i = 0; i < n; i++, iPos += 2) {
			int t;
			m_cEntries[i].nLen = (t = m_file.ReadWord());
		}
		for (i = 0; i < n; i++, iPos += 2) {
			int t;
			m_cEntries[i].nUncompressed = (t = m_file.ReadWord());
		}
		for (i = 0; i < n; i++) {
			m_cEntries[i].nPos = iPos;

			iPos += m_cEntries[i].nLen;
		}
		if (iPos > m_file.GetLength()) return false;

		return true;
	}
	// 
	virtual bool IsEntryBinaryEncoded(int i)
	{
		if (m_cEntries[i].nLen != m_cEntries[i].nUncompressed)
			return true;
		return false;
	}
	// 
	virtual bool DecodeEntryBinary(int i, SizeBuff &rSrc, SizeBuff &rDest)
	{
		int nMax = m_cEntries[i].nUncompressed, nDest;
		if (!rDest.Alloc(nMax)) return false;
		DMlzwD D;
		if (!D.D(rSrc.GetData(), rSrc.GetSize(), rDest.GetData(), &(nDest = rDest.GetSize()), nMax)) return false;
		if (nDest != nMax) return false;

		return true;
	}
	// 
	virtual bool ReadImg4(int i, DMGHLiIMG4 &img4)
	{
		if (ReadImgType1(i, img4))
			return true;
		return false;
	}
	// 
	virtual bool ReadImg8(int i, DMGHLiIMG8 &img8)
	{
		return false;
	}
	// 
	virtual bool ReadImgType1(int i, DMGHLiIMG4 &img4)
	{
		SizeBuff cSrc;
		if (!ReadEntry(i, cSrc)) return false;

		int cbSrc = cSrc.GetSize();
		if (cbSrc < 4) return false;
		UI8 *pbPos = cSrc.GetData();
		UI8 *pbEnd = pbPos + cbSrc;
		int cx = fLE ? (pbPos[0] | pbPos[1] * 256) : (pbPos[0] * 256 | pbPos[1]);
		int cy = fLE ? (pbPos[2] | pbPos[3] * 256) : (pbPos[2] * 256 | pbPos[3]);
		if (cx > DMGHLi_MAX_WIDTH || cx < 1 || cy > DMGHLi_MAX_HEIGHT || cy < 1) return false;
		img4.cx = cx;
		img4.cy = cy;
		img4.pal.UseDM1(0);
		DMGHLiIMG4PixelWriter pw(img4);
		DMGHLiL2R4Bits thruIn(cSrc.GetData() + 4, cSrc.GetSize() - 4);

		while (!pw.IsEndOfImage()) {
			int n1 = thruIn.Read4Bits();
			if (n1 < 0) return false;
			switch (n1)
			{
				case 0:
				case 1:
				case 2:
				case 3:
				case 4:
				case 5:
				case 6:
				case 7:
				{
					int n2 = thruIn.Read4Bits();

					int n = n1 + 1;
					if (!pw.WritePixels(n, (UI8)n2)) return false;
					break;
				}
				case 8:
				{
					int n2 = thruIn.Read4Bits();

					int n = (0
						| thruIn.Read4Bits() * 16
						| thruIn.Read4Bits()
						) + 1;
					if (!pw.WritePixels(n, (UI8)n2)) return false;
					break;
				}
				case 9:
				{
					int n2 = thruIn.Read4Bits();

					int n = (0
						| thruIn.Read4Bits() * 16
						| thruIn.Read4Bits()
						);
					if ((n & 1) == 0)
					{
						if (!pw.WritePixels(1, (UI8)n2)) return false;

						for (int t = 0; t < n; t++)
						{
							int s = thruIn.Read4Bits();
							if (!pw.WritePixels(1, (UI8)s)) return false;
						}
					}
					else
					{
						n++;
						for (int t = 0; t < n; t++)
						{
							int s = thruIn.Read4Bits();
							if (!pw.WritePixels(1, (UI8)s)) return false;
						}
					}
					break;
				}
				case 10:
				{
					int n2 = thruIn.Read4Bits();

					int n = n2 + 1;
					if (!pw.WritePixels(n, (UI8)0x10)) return false;
					break;
				}
				case 11:
				{
					int n2 = thruIn.Read4Bits();

					int n = (0
						| thruIn.Read4Bits() * 16
						| thruIn.Read4Bits()
						) + 1;
					if (!pw.SpillPixels(n)) return false;
					if (!pw.WritePixels(1, (UI8)n2)) return false;
					break;
				}
				case 12:
				{
					int n2 = thruIn.Read4Bits();

					int n = (0
						| thruIn.Read4Bits() * 4096
						| thruIn.Read4Bits() * 256
						| thruIn.Read4Bits() * 16
						| thruIn.Read4Bits()
						) + 1;
					if (!pw.WritePixels(n, (UI8)n2)) return false;
					break;
				}
				case 13:
				{
					break;
				}
				case 14:
				{
					int n2 = thruIn.Read4Bits();

					int n = 17 + n2;
					switch (n2)
					{
						case 13:
							n = (0
								| thruIn.Read4Bits() * 16
								| thruIn.Read4Bits()
								) + 1;
							break;
						case 14:
							n = (0
								| thruIn.Read4Bits() * 16
								| thruIn.Read4Bits()
								) + 257;
							break;
						case 15:
							n = (0
								| thruIn.Read4Bits() * 4096
								| thruIn.Read4Bits() * 256
								| thruIn.Read4Bits() * 16
								| thruIn.Read4Bits()
								) + 1;
							break;
					}
					if (!pw.WritePixels(n, (UI8)0x10)) return false;
					break;
				}
				case 15:
				{
					int n2 = thruIn.Read4Bits();

					int n = (0
						| thruIn.Read4Bits() * 4096
						| thruIn.Read4Bits() * 256
						| thruIn.Read4Bits() * 16
						| thruIn.Read4Bits()
						) + 1;
					if (!pw.SpillPixels(n)) return false;
					if (!pw.WritePixels(1, (UI8)n2)) return false;
					break;
				}
			}
		}
		return thruIn.GetPosition() == thruIn.GetLength();
	}
};

class DMGHLiFDMCSB2 : public DMGHLiFDMCSB1
{
	// 
	static int ReadDuration(DMGHLiL2R4Bits &thruIn)
	{
		int nDuration;
		int v = thruIn.Read4Bits();
		if (v < 0) return -1;
		if (v == 0xF) { // F
			int v = thruIn.Read4Bits();
			if (v < 0) return -1;
			if (v == 0xF) { // FF
				int v = thruIn.Read4Bits();
				if (v < 0) return -1;
				if (v == 0xF) { // FFF
					int v4[4] = {
						thruIn.Read4Bits(),
						thruIn.Read4Bits(),
						thruIn.Read4Bits(),
						thruIn.Read4Bits(),
					};
					if (v4[0] < 0 || v4[1] < 0 || v4[2] < 0 || v4[3] < 0) return false;

					nDuration = 0
						| v4[0] * 4096
						| v4[1] * 256
						| v4[2] * 16
						| v4[3]
						;
				} else { // FF?
					nDuration = v + 257;
				}
			} else { // F?
				int v1 = thruIn.Read4Bits();
				if (v1 < 0) return false;

				nDuration = 17 + v * 16 + v1;
			}
		} else { // ?
			nDuration = v + 2;
		}
		return nDuration;
	}

	// 
	virtual bool InitRead()
	{
		int s = (m_nEntries = m_file.ReadWord());
		if (s != 0x8001) return false;
		int i;
		int n = (m_nEntries = m_file.ReadWord());
		if (n < 1 || n > DMGHLi_MAX_ENTRIES) return false;

		int iPos = 4;

		for (i = 0; i < n; i++, iPos += 2) {
			int t;
			m_cEntries[i].nLen = (t = m_file.ReadWord());
		}
		for (i = 0; i < n; i++, iPos += 2) {
			int t;
			m_cEntries[i].nUncompressed = (t = m_file.ReadWord());
		}
		for (i = 0; i < n; i++, iPos += 4) {
			m_cEntries[i].w0 = m_file.ReadWord();
			m_cEntries[i].w2 = m_file.ReadWord();
		}
		for (i = 0; i < n; i++) {
			m_cEntries[i].nPos = iPos;

			iPos += m_cEntries[i].nLen;
		}
		if (iPos > m_file.GetLength()) return false;

		return true;
	}
	// 
	virtual bool ReadImgType3(int i, DMGHLiIMG4 &img4)
	{
		SizeBuff cSrc;
		if (!ReadEntry(i, cSrc)) return false;

		int cbSrc = cSrc.GetSize();
		if (cbSrc < 7) return false;
		UI8 *pbPos = cSrc.GetData();
		UI8 *pbEnd = pbPos + cbSrc;
		int cx = (fLE ? (pbPos[0] | pbPos[1] * 256) : (pbPos[0] * 256 | pbPos[1])) & 0xFF;
		int cy = (fLE ? (pbPos[2] | pbPos[3] * 256) : (pbPos[2] * 256 | pbPos[3])) & 0xFF;
		if (cx > DMGHLi_MAX_WIDTH || cx < 1 || cy > DMGHLi_MAX_HEIGHT || cy < 1) return false;
		img4.cx = cx;
		img4.cy = cy;
		img4.pal.UseDM1(0);
		DMGHLiIMG4PixelWriter pw(img4);
		DMGHLiL2R4Bits thruIn(cSrc.GetData() + 4, cSrc.GetSize() - 4);
		int iLocalPal6[6] = {
			thruIn.Read4Bits(),
			thruIn.Read4Bits(),
			thruIn.Read4Bits(),
			thruIn.Read4Bits(),
			thruIn.Read4Bits(),
			thruIn.Read4Bits(),
		};

		int iPal;
		int n1, n2;

		while (!pw.IsEndOfImage()) {
			if ((n1 = thruIn.Read4Bits()) < 0) return false;

			switch (n1 & 7) {
			case 0:
			case 1:
			case 2:
			case 3:
			case 4:
			case 5:
				iPal = iLocalPal6[n1 & 7];
				break;
			case 6:
				iPal = 0x20;
				break;
			case 7:
				{
				if ((n2 = thruIn.Read4Bits()) < 0) return false;

				iPal = n2;
				break;
				}
			}
			int nDuration = 1;
			if (n1 & 8) {
				nDuration = ReadDuration(thruIn);
			}

			if (!pw.WritePixels(nDuration, (UI8)iPal)) return false;
		}
		return thruIn.GetPosition() == thruIn.GetLength();
	}
	// 
	virtual bool ReadImg4(int i, DMGHLiIMG4 &img4)
	{
		if (ReadImgType3(i, img4))
			return true;
		return false;
	}
	// 
	virtual bool ReadImg8(int i, DMGHLiIMG8 &img8)
	{
		return false;
	}
};

class DMGHLiFDM2 : public DMGHLiF
{
	// 
	enum ImgTyp {
		typeNo,
		typeExtDM1,
		typeDM2PCC8,
		typeU4,
		typeU8,
	};

	// 
	bool fTraditional;
	// 
	DMGHLiDM2CT chainImgTrack;

	// 
	ImgTyp DecideType(int i)
	{
		SizeBuff cSrc;
		if (!ReadEntry(i, cSrc)) return typeNo;
		if (cSrc.GetSize() < 4) return typeNo;
		const UI8 *pbPos = cSrc.GetData();
		int cx = fLE ? (pbPos[0] | pbPos[1] * 256) : (pbPos[0] * 256 | pbPos[1]);
		int cy = fLE ? (pbPos[2] | pbPos[3] * 256) : (pbPos[2] * 256 | pbPos[3]);

		if (fTraditional) {
			switch (cy & 0xFC00) {
			case 0x8000:
				return typeU4;
			case 0x0000:
				return typeExtDM1;
			}
		} else {
			switch (cy & 0xFC00) {
			case 0x8000:
				return typeU8;
			case 0x7C00:
				return typeDM2PCC8;
			case 0x0000:
				return typeExtDM1;
			}
		}
		return typeNo;
	}
	// 
	bool ReadImgU8(int i, DMGHLiIMG8 &img8)
	{
		SizeBuff cSrc;
		if (!ReadEntry(i, cSrc)) return false;

		int cbSrc = cSrc.GetSize();
		if (cbSrc < 10) return false;
		UI8 *pbPos = cSrc.GetData();
		UI8 *pbEnd = pbPos + cbSrc;
		int cx = (fLE ? (pbPos[0] | pbPos[1] * 256) : (pbPos[0] * 256 | pbPos[1])) & 0x03FF;
		int cy = (fLE ? (pbPos[2] | pbPos[3] * 256) : (pbPos[2] * 256 | pbPos[3])) & 0x03FF;
		if (cx > DMGHLi_MAX_WIDTH || cx < 1 || cy > DMGHLi_MAX_HEIGHT || cy < 1) return false;
		if (cbSrc < 10 + cx * cy) return false;
		img8.cx = cx;
		img8.cy = cy;
		DMGHLiIMG8PixelWriter pw(img8);
		DMGHLi8Bits thruIn(pbPos +10, pbEnd -pbPos -10);

		for (int y=0; y<cy; y++) {
			for (int x=0; x<cx; x++) {
				pw.WritePixels(1, thruIn.ReadByte());
			}
		}

		return true;
	}
	// 
	bool ReadImgU4(int i, DMGHLiIMG4 &img4)
	{
		SizeBuff cSrc;
		if (!ReadEntry(i, cSrc)) return false;

		int cbSrc = cSrc.GetSize();
		if (cbSrc < 10+16) return false;
		UI8 *pbPos = cSrc.GetData();
		UI8 *pbEnd = pbPos + cbSrc;
		int cx = (fLE ? (pbPos[0] | pbPos[1] * 256) : (pbPos[0] * 256 | pbPos[1])) & 0x03FF;
		int cy = (fLE ? (pbPos[2] | pbPos[3] * 256) : (pbPos[2] * 256 | pbPos[3])) & 0x03FF;
		if (cx > DMGHLi_MAX_WIDTH || cx < 1 || cy > DMGHLi_MAX_HEIGHT || cy < 1) return false;
		if (cbSrc < 10 + (cx+1)/2 * cy + 16) return false;
		img4.cx = cx;
		img4.cy = cy;
		img4.pal.UseDM2From16Bytes(pbEnd - 16);
		DMGHLiIMG4PixelWriter pw(img4);
		DMGHLiL2R4Bits thruIn(cSrc.GetData() +10, cSrc.GetSize() -10 -16);

		for (int y=0; y<cy; y++) {
			for (int x=0; x<cx; x++) {
				pw.WritePixels(1, thruIn.Read4Bits());
			}
			if (x & 1) thruIn.Read4Bits(); // each scanline is byte-aligned
		}

		return true;
	}
	// 
	bool ReadImgDM2PCC8(int i, DMGHLiIMG8 &img8)
	{
		SizeBuff cSrc;
		if (!ReadEntry(i, cSrc)) return false;

		int cbSrc = cSrc.GetSize();
		if (cbSrc < 7+16) return false;
		UI8 *pbPos = cSrc.GetData();
		UI8 *pbEnd = pbPos + cbSrc;
		int cx = (fLE ? (pbPos[0] | pbPos[1] * 256) : (pbPos[0] * 256 | pbPos[1])) & 0x03FF;
		int cy = (fLE ? (pbPos[2] | pbPos[3] * 256) : (pbPos[2] * 256 | pbPos[3])) & 0x03FF;
		if (cx > DMGHLi_MAX_WIDTH || cx < 1 || cy > DMGHLi_MAX_HEIGHT || cy < 1) return false;
		img8.cx = cx;
		img8.cy = cy;
		DMGHLiIMG8PixelWriter pw(img8);
		DMGHLi8Bits thruIn(pbPos +8, pbEnd -pbPos -8);

		int iPal;
		bool fIsType3 = (pbPos[6] == 3);

		static const int nTblBits[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};

		while (!pw.IsEndOfImage()) {
			int nCmdBits = thruIn.ReadByte();
			for (int iCmd = 0; iCmd < 8 && !pw.IsEndOfImage(); iCmd++) {
				if (nCmdBits & nTblBits[iCmd]) {
					if ((iPal = thruIn.ReadByte()) < 0) return false;
					if (!pw.WritePixels(1, iPal)) return false;
				} else {
					int n1 = thruIn.ReadByte();
					int n2 = thruIn.ReadByte();
					if (n1 < 0 || n2 < 0) return false;
					int w = fLE ? (n1 | n2 * 256) : (n1 * 256 | n2);
					int nLen = (fIsType3 ? (w & 31) : (w & 15)) + 3;
					int nBack = fIsType3 ? (w >> 5) : (w >> 4);
					if (!pw.ProjectPixels(nBack, nLen)) return false;
				}
			}
		}
		return thruIn.GetPosition() == thruIn.GetLength();
	}
	// 
	bool ReadImgExtDM1(int i, DMGHLiIMG4 &img4, bool fPrimaryPlane)
	{
		SizeBuff cSrc;
		if (!ReadEntry(i, cSrc)) return false;

		int cbSrc = cSrc.GetSize();
		if (cbSrc < 7+16) return false;
		UI8 *pbPos = cSrc.GetData();
		UI8 *pbEnd = pbPos + cbSrc;
		int cx = (fLE ? (pbPos[0] | pbPos[1] * 256) : (pbPos[0] * 256 | pbPos[1])) & 0x03FF;
		int cy = (fLE ? (pbPos[2] | pbPos[3] * 256) : (pbPos[2] * 256 | pbPos[3])) & 0x03FF;
		if (cx > DMGHLi_MAX_WIDTH || cx < 1 || cy > DMGHLi_MAX_HEIGHT || cy < 1) return false;
		img4.cx = cx;
		img4.cy = cy;
		img4.pal.UseDM2From16Bytes(pbEnd - 16);
		DMGHLiIMG4PixelWriter pw(img4);
		DMGHLiL2R4Bits thruIn(cSrc.GetData() +4, cSrc.GetSize() -4 -16);
		int iLocalPal6[6] = {
			thruIn.Read4Bits(),
			thruIn.Read4Bits(),
			thruIn.Read4Bits(),
			thruIn.Read4Bits(),
			thruIn.Read4Bits(),
			fPrimaryPlane ? thruIn.Read4Bits() : 0,
		};

		int iPal;
		int n1, n2;
		UI8 nTransparentState = fPrimaryPlane ? 0x00 : 0x10;

		while (!pw.IsEndOfImage()) {
			if ((n1 = thruIn.Read4Bits()) < 0) return false;

			switch (n1 & 7) {
			case 0:
			case 1:
			case 2:
			case 3:
			case 4:
				iPal = iLocalPal6[n1 & 7] | 0x40;
				break;
			case 5:
				iPal = iLocalPal6[n1 & 7] | nTransparentState;
				break;
			case 6:
				iPal = 0x20;
				break;
			case 7:
				{
				if ((n2 = thruIn.Read4Bits()) < 0) return false;

				iPal = n2;
				break;
				}
			}
			int nDuration = 1;
			if (n1 & 8) {
				nDuration = ReadDuration(thruIn);
			}
			//_RPT2(_CRT_WARN, "%X,%5u\n", iPal&15, nDuration);

			if (!pw.WritePixels(nDuration, (UI8)iPal)) return false;
		}
		return thruIn.GetPosition() == thruIn.GetLength();
	}
	// 
	static int ReadDuration(DMGHLiL2R4Bits &thruIn)
	{
		int nDuration;
		int v = thruIn.Read4Bits();
		if (v < 0) return -1;
		if (v == 0xF) { // F
			int v = thruIn.Read4Bits();
			if (v < 0) return -1;
			if (v == 0xF) { // FF
				int v = thruIn.Read4Bits();
				if (v < 0) return -1;
				if (v == 0xF) { // FFF
					int v4[4] = {
						thruIn.Read4Bits(),
						thruIn.Read4Bits(),
						thruIn.Read4Bits(),
						thruIn.Read4Bits(),
					};
					if (v4[0] < 0 || v4[1] < 0 || v4[2] < 0 || v4[3] < 0) return false;

					nDuration = 0
						| v4[0] * 4096
						| v4[1] * 256
						| v4[2] * 16
						| v4[3]
						;
				} else { // FF?
					nDuration = v + 257;
				}
			} else { // F?
				int v1 = thruIn.Read4Bits();
				if (v1 < 0) return false;

				nDuration = 17 + v * 16 + v1;
			}
		} else { // ?
			nDuration = v + 2;
		}
		return nDuration;
	}
	// 
	static void CascadeImg4Into(DMGHLiIMG4 &rDest, DMGHLiIMG4 &rSrc, bool fPrimaryPlane)
	{
		rDest.pal = rSrc.pal;
		rDest.cx = rSrc.cx;
		rDest.cy = rSrc.cy;

		int cx = rSrc.cx;
		int cy = rSrc.cy;
		for (int y=0; y<cy; y++) {
			for (int x=0; x<cx; x++) {
				UI8 c = rSrc.GetPixel(x, y);
				if (c & 0x10) {

				} else if (c & 0x20 && y > 0) {
					rDest.SetPixel(x, y, rDest.GetPixel(x, y - 1));
				} else {
					rDest.SetPixel(x, y, c);
				}
			}
		}
	}

	// 
	virtual bool InitRead()
	{
		int s = m_file.ReadWord();
		if (s != 0x8005) return false;
		int i;
		int n = (m_nEntries = m_file.ReadWord());
		if (n < 1 || n > DMGHLi_MAX_ENTRIES) return false;

		int iPos = 4;

		for (i = 0; i < n; i++) {
			int t;
			if (i != 0)
					m_cEntries[i].nLen = (t = m_file.ReadWord()), iPos += 2;
			else	m_cEntries[i].nLen = (t = m_file.ReadDWord()),iPos += 4;

			m_cEntries[i].nUncompressed = m_cEntries[i].nLen;
		}
		for (i = 0; i < n; i++) {
			m_cEntries[i].nPos = iPos;

			iPos += m_cEntries[i].nLen;
		}
		if (iPos > m_file.GetLength()) return false;

		if (fTraditional) {
			SizeBuff e1;
			if (!ReadEntryPure(1, e1)) return false;
			chainImgTrack.Init(e1.GetData(), e1.GetSize(), fLE);
		}

		return true;
	}
	// 
	virtual bool IsEntryBinaryEncoded(int i)
	{
		return false;
	}
	// 
	virtual bool DecodeEntryBinary(int i, SizeBuff &rSrc, SizeBuff &rDest)
	{
		(void)i, (void)rSrc, (void)rDest;
		return false;
	}
	// 
	virtual bool ReadImg4(int i, DMGHLiIMG4 &img4)
	{
		ImgTyp it = DecideType(i);

		switch (it) {
		case typeExtDM1:
		{
			int nImgs[DMGHLi_MAX_TRACK];
			chainImgTrack.Track(i, nImgs);
			int iTrack = 0;
			for (; nImgs[iTrack] != 0; iTrack++) {
				int iImg = nImgs[iTrack];
				DMGHLiIMG4 temp;
				if (!ReadImgExtDM1(iImg, temp, iTrack == 0)) return false;
				CascadeImg4Into(img4, temp, iTrack == 0);
			}
			return true;
		}
		case typeU4:
		{
			if (!ReadImgU4(i, img4)) return false;
			return true;
		}
		}
		return false;
	}
	// 
	virtual bool ReadImg8(int i, DMGHLiIMG8 &img8)
	{
		ImgTyp it = DecideType(i);

		switch (it) {
		case typeDM2PCC8:
		{
			if (!ReadImgDM2PCC8(i, img8)) return false;
			return true;
		}
		case typeU8:
		{
			if (!ReadImgU8(i, img8)) return false;
			return true;
		}
		}
		return false;
	}

public:
	// 
	DMGHLiFDM2(bool fIsTraditional): fTraditional(fIsTraditional)
	{

	}
};

extern bool DMGHLiSetUp(DMGHLiIMG4 &img4, PDMGHLIMGF pImpf);
extern bool DMGHLiSetUp(DMGHLiIMG8 &img8, PDMGHLIMGF pImpf);

#endif // #define _DMGHLI_H_
