/*
===========================================================================

Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.

This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").

Doom 3 Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Doom 3 Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

===========================================================================
*/



#include "tr_local.h"


// DG: get rid of libjpeg; as far as I can tell no roqs that actually use it exist
//#define ID_USE_LIBJPEG 1
#ifdef ID_USE_LIBJPEG
#include <jpeglib.h>
#include <jerror.h>
#endif

#define CIN_system	1
#define CIN_loop	2
#define	CIN_hold	4
#define CIN_silent	8
#define CIN_shader	16

class idCinematicLocal : public idCinematic {
public:
	idCinematicLocal();
	virtual					~idCinematicLocal();

	virtual bool			InitFromFile(const char* qpath, bool looping);
	virtual cinData_t		ImageForTime(int milliseconds);
	virtual int				AnimationLength();
	virtual void			Close();
	virtual void			ResetTime(int time);

private:
	size_t					mcomp[256];
	byte** qStatus[2];
	idStr					fileName;
	int						CIN_WIDTH, CIN_HEIGHT;
	idFile* iFile;
	cinStatus_t				status;
	int						tfps;
	int						RoQPlayed;
	int						ROQSize;
	unsigned int			RoQFrameSize;
	int						onQuad;
	int						numQuads;
	int						samplesPerLine;
	unsigned int			roq_id;
	int						screenDelta;
	byte* buf;
	int						samplesPerPixel;				// defaults to 2
	unsigned int			xsize, ysize, maxsize, minsize;
	int						normalBuffer0;
	int						roq_flags;
	int						roqF0;
	int						roqF1;
	int						t[2];
	int						roqFPS;
	int						drawX, drawY;

	int						animationLength;
	int						startTime;
	float					frameRate;

	byte* image;

	bool					looping;
	bool					dirty;
	bool					half;
	bool					smootheddouble;
	bool					inMemory;

	void					RoQ_init(void);
	void					blitVQQuad32fs(byte** status, unsigned char* data);
	void					RoQShutdown(void);
	void					RoQInterrupt(void);

	void					move8_32(byte* src, byte* dst, int spl);
	void					move4_32(byte* src, byte* dst, int spl);
	void					blit8_32(byte* src, byte* dst, int spl);
	void					blit4_32(byte* src, byte* dst, int spl);
	void					blit2_32(byte* src, byte* dst, int spl);

	unsigned short			yuv_to_rgb(int y, int u, int v);
	unsigned int			yuv_to_rgb24(int y, int u, int v);

	void					decodeCodeBook(byte* input, unsigned short roq_flags);
	void					recurseQuad(int startX, int startY, int quadSize, int xOff, int yOff);
	void					setupQuad(int xOff, int yOff);
	void					readQuadInfo(byte* qData);
	void					RoQPrepMcomp(int xoff, int yoff);
	void					RoQReset();
};

const int DEFAULT_CIN_WIDTH = 512;
const int DEFAULT_CIN_HEIGHT = 512;
const int MAXSIZE = 8;
const int MINSIZE = 4;

const int ROQ_FILE = 0x1084;
const int ROQ_QUAD = 0x1000;
const int ROQ_QUAD_INFO = 0x1001;
const int ROQ_CODEBOOK = 0x1002;
const int ROQ_QUAD_VQ = 0x1011;
const int ROQ_QUAD_JPEG = 0x1012;
const int ROQ_QUAD_HANG = 0x1013;
const int ROQ_PACKET = 0x1030;
const int ZA_SOUND_MONO = 0x1020;
const int ZA_SOUND_STEREO = 0x1021;

// temporary buffers used by all cinematics
static int				ROQ_YY_tab[256];
static int				ROQ_UB_tab[256];
static int				ROQ_UG_tab[256];
static int				ROQ_VG_tab[256];
static int				ROQ_VR_tab[256];
static byte* file = NULL;
static unsigned short* vq2 = NULL;
static unsigned short* vq4 = NULL;
static unsigned short* vq8 = NULL;



//===========================================

/*
==============
idCinematicLocal::InitCinematic
==============
*/
void idCinematic::InitCinematic(void) {
	float t_ub, t_vr, t_ug, t_vg;
	int i;

	// generate YUV tables
	t_ub = (1.77200f / 2.0f) * (float)(1 << 6) + 0.5f;
	t_vr = (1.40200f / 2.0f) * (float)(1 << 6) + 0.5f;
	t_ug = (0.34414f / 2.0f) * (float)(1 << 6) + 0.5f;
	t_vg = (0.71414f / 2.0f) * (float)(1 << 6) + 0.5f;
	for (i = 0; i < 256; i++) {
		float x = (float)(2 * i - 255);

		ROQ_UB_tab[i] = (int)((t_ub * x) + (1 << 5));
		ROQ_VR_tab[i] = (int)((t_vr * x) + (1 << 5));
		ROQ_UG_tab[i] = (int)((-t_ug * x));
		ROQ_VG_tab[i] = (int)((-t_vg * x) + (1 << 5));
		ROQ_YY_tab[i] = (int)((i << 6) | (i >> 2));
	}

	// RoQInterrupt reads RoQFrameSize + 8 bytes (chunk payload plus next chunk header)
	file = (byte*)Mem_Alloc(65536 + 8);
	vq2 = (word*)Mem_Alloc(256 * 16 * 4 * sizeof(word));
	vq4 = (word*)Mem_Alloc(256 * 64 * 4 * sizeof(word));
	vq8 = (word*)Mem_Alloc(256 * 256 * 4 * sizeof(word));
}

/*
==============
idCinematicLocal::ShutdownCinematic
==============
*/
void idCinematic::ShutdownCinematic(void) {
	Mem_Free(file);
	file = NULL;
	Mem_Free(vq2);
	vq2 = NULL;
	Mem_Free(vq4);
	vq4 = NULL;
	Mem_Free(vq8);
	vq8 = NULL;
}

/*
==============
idCinematicLocal::Alloc
==============
*/
idCinematic* idCinematic::Alloc() {
	return new idCinematicLocal;
}

/*
==============
idCinematicLocal::~idCinematic
==============
*/
idCinematic::~idCinematic() {
	Close();
}

/*
==============
idCinematicLocal::InitFromFile
==============
*/
bool idCinematic::InitFromFile(const char* qpath, bool looping) {
	return false;
}

/*
==============
idCinematicLocal::AnimationLength
==============
*/
int idCinematic::AnimationLength() {
	return 0;
}

/*
==============
idCinematicLocal::ResetTime
==============
*/
void idCinematic::ResetTime(int milliseconds) {
}

/*
==============
idCinematicLocal::ImageForTime
==============
*/
cinData_t idCinematic::ImageForTime(int milliseconds) {
	cinData_t c;
	memset(&c, 0, sizeof(c));
	return c;
}

/*
==============
idCinematicLocal::Close
==============
*/
void idCinematic::Close() {
}

//===========================================

/*
==============
idCinematicLocal::idCinematicLocal
==============
*/
idCinematicLocal::idCinematicLocal() {
	image = NULL;
	status = FMV_EOF;
	buf = NULL;
	iFile = NULL;
	numQuads = 0;
	ROQSize = 0;
	RoQFrameSize = 0;
	frameRate = 0.0f;
	startTime = 0;
	looping = false;
	// a failed-to-load object can still be forced back to FMV_PLAY (gui
	// resetCinematics); zeroed RoQPlayed/roq_id keep the first RoQInterrupt on
	// the default switch case so it parks at FMV_EOF instead of decoding garbage
	RoQPlayed = 0;
	roq_id = 0;
	roq_flags = 0;
	inMemory = false;
	CIN_WIDTH = 0;
	CIN_HEIGHT = 0;

	qStatus[0] = (byte**)Mem_Alloc(32768 * sizeof(byte*));
	qStatus[1] = (byte**)Mem_Alloc(32768 * sizeof(byte*));
}

/*
==============
idCinematicLocal::~idCinematicLocal
==============
*/
idCinematicLocal::~idCinematicLocal() {
	Close();

	Mem_Free(qStatus[0]);
	qStatus[0] = NULL;
	Mem_Free(qStatus[1]);
	qStatus[1] = NULL;
}

/*
==============
idCinematicLocal::InitFromFile
==============
*/
bool idCinematicLocal::InitFromFile(const char* qpath, bool amilooping) {
	unsigned short RoQID;

	Close();

	inMemory = 0;
	animationLength = 100000;

	if (strstr(qpath, "/") == NULL && strstr(qpath, "\\") == NULL) {
		fileName = "video/";
		fileName += qpath;
	}
	else {
		fileName = qpath;
	}

	iFile = fileSystem->OpenFileRead(fileName);

	if (!iFile) {
		return false;
	}

	ROQSize = iFile->Length();

	looping = amilooping;

	CIN_HEIGHT = DEFAULT_CIN_HEIGHT;
	CIN_WIDTH = DEFAULT_CIN_WIDTH;
	samplesPerPixel = 4;
	startTime = 0;	//Sys_Milliseconds();
	buf = NULL;

	iFile->Read(file, 16);

	RoQID = (unsigned short)(file[0]) + (unsigned short)(file[1]) * 256;

	frameRate = file[6];
	if (frameRate == 32.0f) {
		frameRate = 1000.0f / 32.0f;
	}

	if (RoQID == ROQ_FILE) {
		RoQ_init();
		status = FMV_PLAY;
		ImageForTime(0);
		status = (looping) ? FMV_PLAY : FMV_IDLE;
		return true;
	}

	RoQShutdown();
	return false;
}

/*
==============
idCinematicLocal::Close
==============
*/
void idCinematicLocal::Close() {
	if (image) {
		Mem_Free((void*)image);
		image = NULL;
		buf = NULL;
		status = FMV_EOF;
	}
	RoQShutdown();
}

/*
==============
idCinematicLocal::AnimationLength
==============
*/
int idCinematicLocal::AnimationLength() {
	return animationLength;
}

/*
==============
idCinematicLocal::ResetTime
==============
*/
void idCinematicLocal::ResetTime(int time) {
	startTime = time;
	status = FMV_PLAY;
}

/*
==============
idCinematicLocal::ImageForTime
==============
*/
cinData_t idCinematicLocal::ImageForTime(int thisTime) {
	cinData_t	cinData;

	if (thisTime < 0) {
		thisTime = 0;
	}

	memset(&cinData, 0, sizeof(cinData));


	if (status == FMV_EOF || status == FMV_IDLE) {
		return cinData;
	}

	if (buf == NULL || startTime == -1) {
		if (startTime == -1) {
			RoQReset();
		}
		startTime = thisTime;
	}

	tfps = ((thisTime - startTime) * frameRate) / 1000;

	if (tfps < 0) {
		tfps = 0;
	}

	if (tfps < numQuads) {
		RoQReset();
		buf = NULL;
		status = FMV_PLAY;
	}

	if (buf == NULL) {
		while (buf == NULL && status == FMV_PLAY) {
			RoQInterrupt();
		}
	}
	else {
		while ((tfps != numQuads && status == FMV_PLAY)) {
			RoQInterrupt();
		}
	}

	if (status == FMV_LOOPED) {
		status = FMV_PLAY;
		while (buf == NULL && status == FMV_PLAY) {
			RoQInterrupt();
		}
		startTime = thisTime;
	}

	if (status == FMV_EOF) {
		if (looping) {
			RoQReset();
			buf = NULL;
			if (status == FMV_LOOPED) {
				status = FMV_PLAY;
			}
			while (buf == NULL && status == FMV_PLAY) {
				RoQInterrupt();
			}
			startTime = thisTime;
		}
		else {
			status = FMV_IDLE;
			RoQShutdown();
		}
	}

	cinData.imageWidth = CIN_WIDTH;
	cinData.imageHeight = CIN_HEIGHT;
	cinData.status = status;
	cinData.image = buf;

	return cinData;
}

/*
==============
idCinematicLocal::move8_32
==============
*/
void idCinematicLocal::move8_32(byte* src, byte* dst, int spl) {
#if 1
	int* dsrc, * ddst;
	int dspl;

	dsrc = (int*)src;
	ddst = (int*)dst;
	dspl = spl >> 2;

	ddst[0 * dspl + 0] = dsrc[0 * dspl + 0];
	ddst[0 * dspl + 1] = dsrc[0 * dspl + 1];
	ddst[0 * dspl + 2] = dsrc[0 * dspl + 2];
	ddst[0 * dspl + 3] = dsrc[0 * dspl + 3];
	ddst[0 * dspl + 4] = dsrc[0 * dspl + 4];
	ddst[0 * dspl + 5] = dsrc[0 * dspl + 5];
	ddst[0 * dspl + 6] = dsrc[0 * dspl + 6];
	ddst[0 * dspl + 7] = dsrc[0 * dspl + 7];

	ddst[1 * dspl + 0] = dsrc[1 * dspl + 0];
	ddst[1 * dspl + 1] = dsrc[1 * dspl + 1];
	ddst[1 * dspl + 2] = dsrc[1 * dspl + 2];
	ddst[1 * dspl + 3] = dsrc[1 * dspl + 3];
	ddst[1 * dspl + 4] = dsrc[1 * dspl + 4];
	ddst[1 * dspl + 5] = dsrc[1 * dspl + 5];
	ddst[1 * dspl + 6] = dsrc[1 * dspl + 6];
	ddst[1 * dspl + 7] = dsrc[1 * dspl + 7];

	ddst[2 * dspl + 0] = dsrc[2 * dspl + 0];
	ddst[2 * dspl + 1] = dsrc[2 * dspl + 1];
	ddst[2 * dspl + 2] = dsrc[2 * dspl + 2];
	ddst[2 * dspl + 3] = dsrc[2 * dspl + 3];
	ddst[2 * dspl + 4] = dsrc[2 * dspl + 4];
	ddst[2 * dspl + 5] = dsrc[2 * dspl + 5];
	ddst[2 * dspl + 6] = dsrc[2 * dspl + 6];
	ddst[2 * dspl + 7] = dsrc[2 * dspl + 7];

	ddst[3 * dspl + 0] = dsrc[3 * dspl + 0];
	ddst[3 * dspl + 1] = dsrc[3 * dspl + 1];
	ddst[3 * dspl + 2] = dsrc[3 * dspl + 2];
	ddst[3 * dspl + 3] = dsrc[3 * dspl + 3];
	ddst[3 * dspl + 4] = dsrc[3 * dspl + 4];
	ddst[3 * dspl + 5] = dsrc[3 * dspl + 5];
	ddst[3 * dspl + 6] = dsrc[3 * dspl + 6];
	ddst[3 * dspl + 7] = dsrc[3 * dspl + 7];

	ddst[4 * dspl + 0] = dsrc[4 * dspl + 0];
	ddst[4 * dspl + 1] = dsrc[4 * dspl + 1];
	ddst[4 * dspl + 2] = dsrc[4 * dspl + 2];
	ddst[4 * dspl + 3] = dsrc[4 * dspl + 3];
	ddst[4 * dspl + 4] = dsrc[4 * dspl + 4];
	ddst[4 * dspl + 5] = dsrc[4 * dspl + 5];
	ddst[4 * dspl + 6] = dsrc[4 * dspl + 6];
	ddst[4 * dspl + 7] = dsrc[4 * dspl + 7];

	ddst[5 * dspl + 0] = dsrc[5 * dspl + 0];
	ddst[5 * dspl + 1] = dsrc[5 * dspl + 1];
	ddst[5 * dspl + 2] = dsrc[5 * dspl + 2];
	ddst[5 * dspl + 3] = dsrc[5 * dspl + 3];
	ddst[5 * dspl + 4] = dsrc[5 * dspl + 4];
	ddst[5 * dspl + 5] = dsrc[5 * dspl + 5];
	ddst[5 * dspl + 6] = dsrc[5 * dspl + 6];
	ddst[5 * dspl + 7] = dsrc[5 * dspl + 7];

	ddst[6 * dspl + 0] = dsrc[6 * dspl + 0];
	ddst[6 * dspl + 1] = dsrc[6 * dspl + 1];
	ddst[6 * dspl + 2] = dsrc[6 * dspl + 2];
	ddst[6 * dspl + 3] = dsrc[6 * dspl + 3];
	ddst[6 * dspl + 4] = dsrc[6 * dspl + 4];
	ddst[6 * dspl + 5] = dsrc[6 * dspl + 5];
	ddst[6 * dspl + 6] = dsrc[6 * dspl + 6];
	ddst[6 * dspl + 7] = dsrc[6 * dspl + 7];

	ddst[7 * dspl + 0] = dsrc[7 * dspl + 0];
	ddst[7 * dspl + 1] = dsrc[7 * dspl + 1];
	ddst[7 * dspl + 2] = dsrc[7 * dspl + 2];
	ddst[7 * dspl + 3] = dsrc[7 * dspl + 3];
	ddst[7 * dspl + 4] = dsrc[7 * dspl + 4];
	ddst[7 * dspl + 5] = dsrc[7 * dspl + 5];
	ddst[7 * dspl + 6] = dsrc[7 * dspl + 6];
	ddst[7 * dspl + 7] = dsrc[7 * dspl + 7];
#else
	double* dsrc, * ddst;
	int dspl;

	dsrc = (double*)src;
	ddst = (double*)dst;
	dspl = spl >> 3;

	ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3];
	dsrc += dspl; ddst += dspl;
	ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3];
	dsrc += dspl; ddst += dspl;
	ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3];
	dsrc += dspl; ddst += dspl;
	ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3];
	dsrc += dspl; ddst += dspl;
	ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3];
	dsrc += dspl; ddst += dspl;
	ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3];
	dsrc += dspl; ddst += dspl;
	ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3];
	dsrc += dspl; ddst += dspl;
	ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3];
#endif
}

/*
==============
idCinematicLocal::move4_32
==============
*/
void idCinematicLocal::move4_32(byte* src, byte* dst, int spl) {
#if 1
	int* dsrc, * ddst;
	int dspl;

	dsrc = (int*)src;
	ddst = (int*)dst;
	dspl = spl >> 2;

	ddst[0 * dspl + 0] = dsrc[0 * dspl + 0];
	ddst[0 * dspl + 1] = dsrc[0 * dspl + 1];
	ddst[0 * dspl + 2] = dsrc[0 * dspl + 2];
	ddst[0 * dspl + 3] = dsrc[0 * dspl + 3];

	ddst[1 * dspl + 0] = dsrc[1 * dspl + 0];
	ddst[1 * dspl + 1] = dsrc[1 * dspl + 1];
	ddst[1 * dspl + 2] = dsrc[1 * dspl + 2];
	ddst[1 * dspl + 3] = dsrc[1 * dspl + 3];

	ddst[2 * dspl + 0] = dsrc[2 * dspl + 0];
	ddst[2 * dspl + 1] = dsrc[2 * dspl + 1];
	ddst[2 * dspl + 2] = dsrc[2 * dspl + 2];
	ddst[2 * dspl + 3] = dsrc[2 * dspl + 3];

	ddst[3 * dspl + 0] = dsrc[3 * dspl + 0];
	ddst[3 * dspl + 1] = dsrc[3 * dspl + 1];
	ddst[3 * dspl + 2] = dsrc[3 * dspl + 2];
	ddst[3 * dspl + 3] = dsrc[3 * dspl + 3];
#else
	double* dsrc, * ddst;
	int dspl;

	dsrc = (double*)src;
	ddst = (double*)dst;
	dspl = spl >> 3;

	ddst[0] = dsrc[0]; ddst[1] = dsrc[1];
	dsrc += dspl; ddst += dspl;
	ddst[0] = dsrc[0]; ddst[1] = dsrc[1];
	dsrc += dspl; ddst += dspl;
	ddst[0] = dsrc[0]; ddst[1] = dsrc[1];
	dsrc += dspl; ddst += dspl;
	ddst[0] = dsrc[0]; ddst[1] = dsrc[1];
#endif
}

/*
==============
idCinematicLocal::blit8_32
==============
*/
void idCinematicLocal::blit8_32(byte* src, byte* dst, int spl) {
#if 1
	int* dsrc, * ddst;
	int dspl;

	dsrc = (int*)src;
	ddst = (int*)dst;
	dspl = spl >> 2;

	ddst[0 * dspl + 0] = dsrc[0];
	ddst[0 * dspl + 1] = dsrc[1];
	ddst[0 * dspl + 2] = dsrc[2];
	ddst[0 * dspl + 3] = dsrc[3];
	ddst[0 * dspl + 4] = dsrc[4];
	ddst[0 * dspl + 5] = dsrc[5];
	ddst[0 * dspl + 6] = dsrc[6];
	ddst[0 * dspl + 7] = dsrc[7];

	ddst[1 * dspl + 0] = dsrc[8];
	ddst[1 * dspl + 1] = dsrc[9];
	ddst[1 * dspl + 2] = dsrc[10];
	ddst[1 * dspl + 3] = dsrc[11];
	ddst[1 * dspl + 4] = dsrc[12];
	ddst[1 * dspl + 5] = dsrc[13];
	ddst[1 * dspl + 6] = dsrc[14];
	ddst[1 * dspl + 7] = dsrc[15];

	ddst[2 * dspl + 0] = dsrc[16];
	ddst[2 * dspl + 1] = dsrc[17];
	ddst[2 * dspl + 2] = dsrc[18];
	ddst[2 * dspl + 3] = dsrc[19];
	ddst[2 * dspl + 4] = dsrc[20];
	ddst[2 * dspl + 5] = dsrc[21];
	ddst[2 * dspl + 6] = dsrc[22];
	ddst[2 * dspl + 7] = dsrc[23];

	ddst[3 * dspl + 0] = dsrc[24];
	ddst[3 * dspl + 1] = dsrc[25];
	ddst[3 * dspl + 2] = dsrc[26];
	ddst[3 * dspl + 3] = dsrc[27];
	ddst[3 * dspl + 4] = dsrc[28];
	ddst[3 * dspl + 5] = dsrc[29];
	ddst[3 * dspl + 6] = dsrc[30];
	ddst[3 * dspl + 7] = dsrc[31];

	ddst[4 * dspl + 0] = dsrc[32];
	ddst[4 * dspl + 1] = dsrc[33];
	ddst[4 * dspl + 2] = dsrc[34];
	ddst[4 * dspl + 3] = dsrc[35];
	ddst[4 * dspl + 4] = dsrc[36];
	ddst[4 * dspl + 5] = dsrc[37];
	ddst[4 * dspl + 6] = dsrc[38];
	ddst[4 * dspl + 7] = dsrc[39];

	ddst[5 * dspl + 0] = dsrc[40];
	ddst[5 * dspl + 1] = dsrc[41];
	ddst[5 * dspl + 2] = dsrc[42];
	ddst[5 * dspl + 3] = dsrc[43];
	ddst[5 * dspl + 4] = dsrc[44];
	ddst[5 * dspl + 5] = dsrc[45];
	ddst[5 * dspl + 6] = dsrc[46];
	ddst[5 * dspl + 7] = dsrc[47];

	ddst[6 * dspl + 0] = dsrc[48];
	ddst[6 * dspl + 1] = dsrc[49];
	ddst[6 * dspl + 2] = dsrc[50];
	ddst[6 * dspl + 3] = dsrc[51];
	ddst[6 * dspl + 4] = dsrc[52];
	ddst[6 * dspl + 5] = dsrc[53];
	ddst[6 * dspl + 6] = dsrc[54];
	ddst[6 * dspl + 7] = dsrc[55];

	ddst[7 * dspl + 0] = dsrc[56];
	ddst[7 * dspl + 1] = dsrc[57];
	ddst[7 * dspl + 2] = dsrc[58];
	ddst[7 * dspl + 3] = dsrc[59];
	ddst[7 * dspl + 4] = dsrc[60];
	ddst[7 * dspl + 5] = dsrc[61];
	ddst[7 * dspl + 6] = dsrc[62];
	ddst[7 * dspl + 7] = dsrc[63];
#else
	double* dsrc, * ddst;
	int dspl;

	dsrc = (double*)src;
	ddst = (double*)dst;
	dspl = spl >> 3;

	ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3];
	dsrc += 4; ddst += dspl;
	ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3];
	dsrc += 4; ddst += dspl;
	ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3];
	dsrc += 4; ddst += dspl;
	ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3];
	dsrc += 4; ddst += dspl;
	ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3];
	dsrc += 4; ddst += dspl;
	ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3];
	dsrc += 4; ddst += dspl;
	ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3];
	dsrc += 4; ddst += dspl;
	ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3];
#endif
}

/*
==============
idCinematicLocal::blit4_32
==============
*/
void idCinematicLocal::blit4_32(byte* src, byte* dst, int spl) {
#if 1
	int* dsrc, * ddst;
	int dspl;

	dsrc = (int*)src;
	ddst = (int*)dst;
	dspl = spl >> 2;

	ddst[0 * dspl + 0] = dsrc[0];
	ddst[0 * dspl + 1] = dsrc[1];
	ddst[0 * dspl + 2] = dsrc[2];
	ddst[0 * dspl + 3] = dsrc[3];
	ddst[1 * dspl + 0] = dsrc[4];
	ddst[1 * dspl + 1] = dsrc[5];
	ddst[1 * dspl + 2] = dsrc[6];
	ddst[1 * dspl + 3] = dsrc[7];
	ddst[2 * dspl + 0] = dsrc[8];
	ddst[2 * dspl + 1] = dsrc[9];
	ddst[2 * dspl + 2] = dsrc[10];
	ddst[2 * dspl + 3] = dsrc[11];
	ddst[3 * dspl + 0] = dsrc[12];
	ddst[3 * dspl + 1] = dsrc[13];
	ddst[3 * dspl + 2] = dsrc[14];
	ddst[3 * dspl + 3] = dsrc[15];
#else
	double* dsrc, * ddst;
	int dspl;

	dsrc = (double*)src;
	ddst = (double*)dst;
	dspl = spl >> 3;

	ddst[0] = dsrc[0]; ddst[1] = dsrc[1];
	dsrc += 2; ddst += dspl;
	ddst[0] = dsrc[0]; ddst[1] = dsrc[1];
	dsrc += 2; ddst += dspl;
	ddst[0] = dsrc[0]; ddst[1] = dsrc[1];
	dsrc += 2; ddst += dspl;
	ddst[0] = dsrc[0]; ddst[1] = dsrc[1];
#endif
}

/*
==============
idCinematicLocal::blit2_32
==============
*/
void idCinematicLocal::blit2_32(byte* src, byte* dst, int spl) {
#if 1
	int* dsrc, * ddst;
	int dspl;

	dsrc = (int*)src;
	ddst = (int*)dst;
	dspl = spl >> 2;

	ddst[0 * dspl + 0] = dsrc[0];
	ddst[0 * dspl + 1] = dsrc[1];
	ddst[1 * dspl + 0] = dsrc[2];
	ddst[1 * dspl + 1] = dsrc[3];
#else
	double* dsrc, * ddst;
	int dspl;

	dsrc = (double*)src;
	ddst = (double*)dst;
	dspl = spl >> 3;

	ddst[0] = dsrc[0];
	ddst[dspl] = dsrc[1];
#endif
}

/*
==============
idCinematicLocal::blitVQQuad32fs
==============
*/
void idCinematicLocal::blitVQQuad32fs(byte** status, unsigned char* data) {
	unsigned short	newd, celdata, code;
	unsigned int	index, i;

	newd = 0;
	celdata = 0;
	index = 0;

	do {
		if (!newd) {
			newd = 7;
			celdata = data[0] + data[1] * 256;
			data += 2;
		}
		else {
			newd--;
		}

		code = (unsigned short)(celdata & 0xc000);
		celdata <<= 2;

		switch (code) {
		case	0x8000:													// vq code
			blit8_32((byte*)&vq8[(*data) * 128], status[index], samplesPerLine);
			data++;
			index += 5;
			break;
		case	0xc000:													// drop
			index++;													// skip 8x8
			for (i = 0; i < 4; i++) {
				if (!newd) {
					newd = 7;
					celdata = data[0] + data[1] * 256;
					data += 2;
				}
				else {
					newd--;
				}

				code = (unsigned short)(celdata & 0xc000); celdata <<= 2;

				switch (code) {											// code in top two bits of code
				case	0x8000:										// 4x4 vq code
					blit4_32((byte*)&vq4[(*data) * 32], status[index], samplesPerLine);
					data++;
					break;
				case	0xc000:										// 2x2 vq code
					blit2_32((byte*)&vq2[(*data) * 8], status[index], samplesPerLine);
					data++;
					blit2_32((byte*)&vq2[(*data) * 8], status[index] + 8, samplesPerLine);
					data++;
					blit2_32((byte*)&vq2[(*data) * 8], status[index] + samplesPerLine * 2, samplesPerLine);
					data++;
					blit2_32((byte*)&vq2[(*data) * 8], status[index] + samplesPerLine * 2 + 8, samplesPerLine);
					data++;
					break;
				case	0x4000:										// motion compensation
					move4_32(status[index] + mcomp[(*data)], status[index], samplesPerLine);
					data++;
					break;
				}
				index++;
			}
			break;
		case	0x4000:													// motion compensation
			move8_32(status[index] + mcomp[(*data)], status[index], samplesPerLine);
			data++;
			index += 5;
			break;
		case	0x0000:
			index += 5;
			break;
		}
	} while (status[index] != NULL);
}

#define VQ2TO4(a,b,c,d) { \
		*c++ = a[0];	\
	*d++ = a[0];	\
	*d++ = a[0];	\
	*c++ = a[1];	\
	*d++ = a[1];	\
	*d++ = a[1];	\
	*c++ = b[0];	\
	*d++ = b[0];	\
	*d++ = b[0];	\
	*c++ = b[1];	\
	*d++ = b[1];	\
	*d++ = b[1];	\
	*d++ = a[0];	\
	*d++ = a[0];	\
	*d++ = a[1];	\
	*d++ = a[1];	\
	*d++ = b[0];	\
	*d++ = b[0];	\
	*d++ = b[1];	\
	*d++ = b[1];	\
	a += 2; b += 2; }

#define VQ2TO2(a,b,c,d) { \
	*c++ = *a;	\
	*d++ = *a;	\
	*d++ = *a;	\
	*c++ = *b;	\
	*d++ = *b;	\
	*d++ = *b;	\
	*d++ = *a;	\
	*d++ = *a;	\
	*d++ = *b;	\
	*d++ = *b;	\
	a++; b++; }

/*
==============
idCinematicLocal::yuv_to_rgb
==============
*/
unsigned short idCinematicLocal::yuv_to_rgb(int y, int u, int v) {
	int r, g, b, YY = (int)(ROQ_YY_tab[(y)]);

	r = (YY + ROQ_VR_tab[v]) >> 9;
	g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 8;
	b = (YY + ROQ_UB_tab[u]) >> 9;

	if (r < 0) { r = 0; } if (g < 0) { g = 0; } if (b < 0) { b = 0; }
	if (r > 31) { r = 31; } if (g > 63) { g = 63; } if (b > 31) { b = 31; }

	return (unsigned short)((r << 11) + (g << 5) + (b));
}

/*
==============
idCinematicLocal::yuv_to_rgb24
==============
*/
unsigned int idCinematicLocal::yuv_to_rgb24(int y, int u, int v) {
	int r, g, b, YY = (int)(ROQ_YY_tab[(y)]);

	r = (YY + ROQ_VR_tab[v]) >> 6;
	g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 6;
	b = (YY + ROQ_UB_tab[u]) >> 6;

	if (r < 0) { r = 0; } if (g < 0) { g = 0; } if (b < 0) { b = 0; }
	if (r > 255) { r = 255; } if (g > 255) { g = 255; } if (b > 255) { b = 255; }

	return ((r)+(g << 8) + (b << 16));
}

/*
==============
idCinematicLocal::decodeCodeBook
==============
*/
void idCinematicLocal::decodeCodeBook(byte* input, unsigned short roq_flags) {
	int	i, j, two, four;
	unsigned short* aptr, * bptr, * cptr, * dptr;
	int	y0, y1, y2, y3, cr, cb;
	unsigned int* iaptr, * ibptr, * icptr, * idptr;

	if (!roq_flags) {
		two = four = 256;
	}
	else {
		two = roq_flags >> 8;
		if (!two) two = 256;
		four = roq_flags & 0xff;
	}

	four *= 2;

	bptr = (unsigned short*)vq2;

	if (!half) {
		if (!smootheddouble) {
			//
			// normal height
			//
			if (samplesPerPixel == 2) {
				for (i = 0; i < two; i++) {
					y0 = (int)*input++;
					y1 = (int)*input++;
					y2 = (int)*input++;
					y3 = (int)*input++;
					cr = (int)*input++;
					cb = (int)*input++;
					*bptr++ = yuv_to_rgb(y0, cr, cb);
					*bptr++ = yuv_to_rgb(y1, cr, cb);
					*bptr++ = yuv_to_rgb(y2, cr, cb);
					*bptr++ = yuv_to_rgb(y3, cr, cb);
				}

				cptr = (unsigned short*)vq4;
				dptr = (unsigned short*)vq8;

				for (i = 0; i < four; i++) {
					aptr = (unsigned short*)vq2 + (*input++) * 4;
					bptr = (unsigned short*)vq2 + (*input++) * 4;
					for (j = 0; j < 2; j++)
						VQ2TO4(aptr, bptr, cptr, dptr);
				}
			}
			else if (samplesPerPixel == 4) {
				ibptr = (unsigned int*)bptr;
				for (i = 0; i < two; i++) {
					y0 = (int)*input++;
					y1 = (int)*input++;
					y2 = (int)*input++;
					y3 = (int)*input++;
					cr = (int)*input++;
					cb = (int)*input++;
					*ibptr++ = yuv_to_rgb24(y0, cr, cb);
					*ibptr++ = yuv_to_rgb24(y1, cr, cb);
					*ibptr++ = yuv_to_rgb24(y2, cr, cb);
					*ibptr++ = yuv_to_rgb24(y3, cr, cb);
				}

				icptr = (unsigned int*)vq4;
				idptr = (unsigned int*)vq8;

				for (i = 0; i < four; i++) {
					iaptr = (unsigned int*)vq2 + (*input++) * 4;
					ibptr = (unsigned int*)vq2 + (*input++) * 4;
					for (j = 0; j < 2; j++)
						VQ2TO4(iaptr, ibptr, icptr, idptr);
				}
			}
		}
		else {
			//
			// double height, smoothed
			//
			if (samplesPerPixel == 2) {
				for (i = 0; i < two; i++) {
					y0 = (int)*input++;
					y1 = (int)*input++;
					y2 = (int)*input++;
					y3 = (int)*input++;
					cr = (int)*input++;
					cb = (int)*input++;
					*bptr++ = yuv_to_rgb(y0, cr, cb);
					*bptr++ = yuv_to_rgb(y1, cr, cb);
					*bptr++ = yuv_to_rgb(((y0 * 3) + y2) / 4, cr, cb);
					*bptr++ = yuv_to_rgb(((y1 * 3) + y3) / 4, cr, cb);
					*bptr++ = yuv_to_rgb((y0 + (y2 * 3)) / 4, cr, cb);
					*bptr++ = yuv_to_rgb((y1 + (y3 * 3)) / 4, cr, cb);
					*bptr++ = yuv_to_rgb(y2, cr, cb);
					*bptr++ = yuv_to_rgb(y3, cr, cb);
				}

				cptr = (unsigned short*)vq4;
				dptr = (unsigned short*)vq8;

				for (i = 0; i < four; i++) {
					aptr = (unsigned short*)vq2 + (*input++) * 8;
					bptr = (unsigned short*)vq2 + (*input++) * 8;
					for (j = 0; j < 2; j++) {
						VQ2TO4(aptr, bptr, cptr, dptr);
						VQ2TO4(aptr, bptr, cptr, dptr);
					}
				}
			}
			else if (samplesPerPixel == 4) {
				ibptr = (unsigned int*)bptr;
				for (i = 0; i < two; i++) {
					y0 = (int)*input++;
					y1 = (int)*input++;
					y2 = (int)*input++;
					y3 = (int)*input++;
					cr = (int)*input++;
					cb = (int)*input++;
					*ibptr++ = yuv_to_rgb24(y0, cr, cb);
					*ibptr++ = yuv_to_rgb24(y1, cr, cb);
					*ibptr++ = yuv_to_rgb24(((y0 * 3) + y2) / 4, cr, cb);
					*ibptr++ = yuv_to_rgb24(((y1 * 3) + y3) / 4, cr, cb);
					*ibptr++ = yuv_to_rgb24((y0 + (y2 * 3)) / 4, cr, cb);
					*ibptr++ = yuv_to_rgb24((y1 + (y3 * 3)) / 4, cr, cb);
					*ibptr++ = yuv_to_rgb24(y2, cr, cb);
					*ibptr++ = yuv_to_rgb24(y3, cr, cb);
				}

				icptr = (unsigned int*)vq4;
				idptr = (unsigned int*)vq8;

				for (i = 0; i < four; i++) {
					iaptr = (unsigned int*)vq2 + (*input++) * 8;
					ibptr = (unsigned int*)vq2 + (*input++) * 8;
					for (j = 0; j < 2; j++) {
						VQ2TO4(iaptr, ibptr, icptr, idptr);
						VQ2TO4(iaptr, ibptr, icptr, idptr);
					}
				}
			}
		}
	}
	else {
		//
		// 1/4 screen
		//
		if (samplesPerPixel == 2) {
			for (i = 0; i < two; i++) {
				y0 = (int)*input; input += 2;
				y2 = (int)*input; input += 2;
				cr = (int)*input++;
				cb = (int)*input++;
				*bptr++ = yuv_to_rgb(y0, cr, cb);
				*bptr++ = yuv_to_rgb(y2, cr, cb);
			}

			cptr = (unsigned short*)vq4;
			dptr = (unsigned short*)vq8;

			for (i = 0; i < four; i++) {
				aptr = (unsigned short*)vq2 + (*input++) * 2;
				bptr = (unsigned short*)vq2 + (*input++) * 2;
				for (j = 0; j < 2; j++) {
					VQ2TO2(aptr, bptr, cptr, dptr);
				}
			}
		}
		else if (samplesPerPixel == 4) {
			ibptr = (unsigned int*)bptr;
			for (i = 0; i < two; i++) {
				y0 = (int)*input; input += 2;
				y2 = (int)*input; input += 2;
				cr = (int)*input++;
				cb = (int)*input++;
				*ibptr++ = yuv_to_rgb24(y0, cr, cb);
				*ibptr++ = yuv_to_rgb24(y2, cr, cb);
			}

			icptr = (unsigned int*)vq4;
			idptr = (unsigned int*)vq8;

			for (i = 0; i < four; i++) {
				iaptr = (unsigned int*)vq2 + (*input++) * 2;
				ibptr = (unsigned int*)vq2 + (*input++) * 2;
				for (j = 0; j < 2; j++) {
					VQ2TO2(iaptr, ibptr, icptr, idptr);
				}
			}
		}
	}
}

/*
==============
idCinematicLocal::recurseQuad
==============
*/
void idCinematicLocal::recurseQuad(int startX, int startY, int quadSize, int xOff, int yOff) {
	byte* scroff;
	int bigx, bigy, lowx, lowy, useY;
	int offset;

	offset = screenDelta;

	lowx = lowy = 0;
	bigx = xsize;
	bigy = ysize;

	if (bigx > CIN_WIDTH) bigx = CIN_WIDTH;
	if (bigy > CIN_HEIGHT) bigy = CIN_HEIGHT;

	if ((startX >= lowx) && (startX + quadSize) <= (bigx) && (startY + quadSize) <= (bigy) && (startY >= lowy) && quadSize <= MAXSIZE) {
		useY = startY;
		scroff = image + (useY + ((CIN_HEIGHT - bigy) >> 1) + yOff) * (samplesPerLine)+(((startX + xOff)) * samplesPerPixel);

		qStatus[0][onQuad] = scroff;
		qStatus[1][onQuad++] = scroff + offset;
	}

	if (quadSize != MINSIZE) {
		quadSize >>= 1;
		recurseQuad(startX, startY, quadSize, xOff, yOff);
		recurseQuad(startX + quadSize, startY, quadSize, xOff, yOff);
		recurseQuad(startX, startY + quadSize, quadSize, xOff, yOff);
		recurseQuad(startX + quadSize, startY + quadSize, quadSize, xOff, yOff);
	}
}

/*
==============
idCinematicLocal::setupQuad
==============
*/
void idCinematicLocal::setupQuad(int xOff, int yOff) {
	int numQuadCels, i, x, y;
	byte* temp;

	numQuadCels = (CIN_WIDTH * CIN_HEIGHT) / (16);
	numQuadCels += numQuadCels / 4 + numQuadCels / 16;
	numQuadCels += 64;							  // for overflow

	numQuadCels = (xsize * ysize) / (16);
	numQuadCels += numQuadCels / 4;
	numQuadCels += 64;							  // for overflow

	onQuad = 0;

	for (y = 0; y < (int)ysize; y += 16)
		for (x = 0; x < (int)xsize; x += 16)
			recurseQuad(x, y, 16, xOff, yOff);

	temp = NULL;

	for (i = (numQuadCels - 64); i < numQuadCels; i++) {
		qStatus[0][i] = temp;			  // eoq
		qStatus[1][i] = temp;			  // eoq
	}
}

/*
==============
idCinematicLocal::readQuadInfo
==============
*/
void idCinematicLocal::readQuadInfo(byte* qData) {
	xsize = qData[0] + qData[1] * 256;
	ysize = qData[2] + qData[3] * 256;
	maxsize = qData[4] + qData[5] * 256;
	minsize = qData[6] + qData[7] * 256;

	CIN_HEIGHT = ysize;
	CIN_WIDTH = xsize;

	samplesPerLine = CIN_WIDTH * samplesPerPixel;
	screenDelta = CIN_HEIGHT * samplesPerLine;

	if (!image) {
		image = (byte*)Mem_Alloc(CIN_WIDTH * CIN_HEIGHT * samplesPerPixel * 2);
	}

	half = false;
	smootheddouble = false;

	t[0] = (0 - (ptrdiff_t)image) + (ptrdiff_t)image + screenDelta;
	t[1] = (0 - ((ptrdiff_t)image + screenDelta)) + (ptrdiff_t)image;

	drawX = CIN_WIDTH;
	drawY = CIN_HEIGHT;
}

/*
==============
idCinematicLocal::RoQPrepMcomp
==============
*/
void idCinematicLocal::RoQPrepMcomp(int xoff, int yoff) {
	int i, j, x, y, temp, temp2;

	i = samplesPerLine; j = samplesPerPixel;
	if (xsize == (ysize * 4) && !half) { j = j + j; i = i + i; }

	for (y = 0; y < 16; y++) {
		temp2 = (y + yoff - 8) * i;
		for (x = 0; x < 16; x++) {
			temp = (x + xoff - 8) * j;
			mcomp[(x * 16) + y] = normalBuffer0 - (temp2 + temp);
		}
	}
}

/*
==============
idCinematicLocal::RoQReset
==============
*/
void idCinematicLocal::RoQReset() {

	// the initial open may have failed, or Close()/RoQShutdown released the
	// file while the object stayed alive; reopen or stay inert
	if (iFile == NULL) {
		iFile = fileSystem->OpenFileRead(fileName);
		if (iFile == NULL) {
			status = FMV_EOF;
			return;
		}
	}

	iFile->Seek(0, FS_SEEK_SET);
	iFile->Read(file, 16);
	RoQ_init();
	status = FMV_LOOPED;
}

#ifdef ID_USE_LIBJPEG
/* jpeg error handling */
struct jpeg_error_mgr jerr;
#endif
int JPEGBlit(byte* wStatus, byte* data, int datasize)
{
#ifndef ID_USE_LIBJPEG
	// I don't think this code is actually used, because
	// * the jpeg encoder parts in the roq encoder are disabled with #if 0
	// * ffmpeg doesn't support ROQ_QUAD_JPEG and can decode all doom3 roqs anyway
	common->Warning("Contrary to Daniel's assumption, JPEGBlit() is actually called! Please report that as a dhewm3 bug!\n");

#else
	/* This struct contains the JPEG decompression parameters and pointers to
	 * working space (which is allocated as needed by the JPEG library).
	 */
	struct jpeg_decompress_struct cinfo;
	/* We use our private extension JPEG error handler.
	 * Note that this struct must live as long as the main JPEG parameter
	 * struct, to avoid dangling-pointer problems.
	 */
	 /* More stuff */
	JSAMPARRAY buffer;		/* Output row buffer */
	int row_stride;		/* physical row width in output buffer */

	/* Step 1: allocate and initialize JPEG decompression object */

	/* We set up the normal JPEG error routines, then override error_exit. */
	cinfo.err = jpeg_std_error(&jerr);

	/* Now we can initialize the JPEG decompression object. */
	jpeg_create_decompress(&cinfo);

	/* Step 2: specify data source (eg, a file) */

	jpeg_mem_src(&cinfo, data, datasize);

	/* Step 3: read file parameters with jpeg_read_header() */

	(void)jpeg_read_header(&cinfo, TRUE);
	/* We can ignore the return value from jpeg_read_header since
	 *   (a) suspension is not possible with the stdio data source, and
	 *   (b) we passed TRUE to reject a tables-only JPEG file as an error.
	 * See libjpeg.doc for more info.
	 */

	 /* Step 4: set parameters for decompression */

	 /* In this example, we don't need to change any of the defaults set by
	  * jpeg_read_header(), so we do nothing here.
	  */

	  /* Step 5: Start decompressor */

	cinfo.dct_method = JDCT_IFAST;
	cinfo.dct_method = JDCT_FASTEST;
	cinfo.dither_mode = JDITHER_NONE;
	cinfo.do_fancy_upsampling = FALSE;
	//	cinfo.out_color_space = JCS_GRAYSCALE;

	(void)jpeg_start_decompress(&cinfo);
	/* We can ignore the return value since suspension is not possible
	 * with the stdio data source.
	 */

	 /* We may need to do some setup of our own at this point before reading
	  * the data.  After jpeg_start_decompress() we have the correct scaled
	  * output image dimensions available, as well as the output colormap
	  * if we asked for color quantization.
	  * In this example, we need to make an output work buffer of the right size.
	  */
	  /* JSAMPLEs per row in output buffer */
	row_stride = cinfo.output_width * cinfo.output_components;

	/* Make a one-row-high sample array that will go away when done with image */
	buffer = (*cinfo.mem->alloc_sarray)
		((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1);

	/* Step 6: while (scan lines remain to be read) */
	/*           jpeg_read_scanlines(...); */

	/* Here we use the library's state variable cinfo.output_scanline as the
	 * loop counter, so that we don't have to keep track ourselves.
	 */

	wStatus += (cinfo.output_height - 1) * row_stride;
	while (cinfo.output_scanline < cinfo.output_height) {
		/* jpeg_read_scanlines expects an array of pointers to scanlines.
		 * Here the array is only one element long, but you could ask for
		 * more than one scanline at a time if that's more convenient.
		 */
		(void)jpeg_read_scanlines(&cinfo, &buffer[0], 1);

		/* Assume put_scanline_someplace wants a pointer and sample count. */
		memcpy(wStatus, &buffer[0][0], row_stride);
		/*
		int x;
		unsigned int *buf = (unsigned int *)&buffer[0][0];
		unsigned int *out = (unsigned int *)wStatus;
		for(x=0;x<cinfo.output_width;x++) {
			unsigned int pixel = buf[x];
			byte *roof = (byte *)&pixel;
			byte temp = roof[0];
			roof[0] = roof[2];
			roof[2] = temp;
			out[x] = pixel;
		}
		*/
		wStatus -= row_stride;
	}

	/* Step 7: Finish decompression */

	(void)jpeg_finish_decompress(&cinfo);
	/* We can ignore the return value since suspension is not possible
	 * with the stdio data source.
	 */

	 /* Step 8: Release JPEG decompression object */

	 /* This is an important step since it will release a good deal of memory. */
	jpeg_destroy_decompress(&cinfo);

	/* At this point you may want to check to see whether any corrupt-data
	 * warnings occurred (test whether jerr.pub.num_warnings is nonzero).
	 */
#endif
	 /* And we're done! */
	return 1;
}

/*
==============
idCinematicLocal::RoQInterrupt
==============
*/
void idCinematicLocal::RoQInterrupt(void) {
	byte* framedata;

	if (iFile == NULL) {
		status = FMV_EOF;
		return;
	}
	// RoQ_init parses RoQFrameSize from the header without any bounds check
	if (RoQFrameSize > 65536) {
		common->DPrintf("roq_size>65536\n");
		status = FMV_EOF;
		return;
	}
	iFile->Read(file, RoQFrameSize + 8);
	if (RoQPlayed >= ROQSize) {
		if (looping) {
			RoQReset();
		}
		else {
			status = FMV_EOF;
		}
		return;
	}

	framedata = file;
	//
	// new frame is ready
	//
redump:
	switch (roq_id)
	{
	case	ROQ_QUAD_VQ:
		if ((numQuads & 1)) {
			normalBuffer0 = t[1];
			RoQPrepMcomp(roqF0, roqF1);
			blitVQQuad32fs(qStatus[1], framedata);
			buf = image + screenDelta;
		}
		else {
			normalBuffer0 = t[0];
			RoQPrepMcomp(roqF0, roqF1);
			blitVQQuad32fs(qStatus[0], framedata);
			buf = image;
		}
		if (numQuads == 0) {		// first frame
			memcpy(image + screenDelta, image, samplesPerLine * ysize);
		}
		numQuads++;
		dirty = true;
		break;
	case	ROQ_CODEBOOK:
		decodeCodeBook(framedata, (unsigned short)roq_flags);
		break;
	case	ZA_SOUND_MONO:
		break;
	case	ZA_SOUND_STEREO:
		break;
	case	ROQ_QUAD_INFO:
		if (numQuads == -1) {
			readQuadInfo(framedata);
			setupQuad(0, 0);
		}
		if (numQuads != 1) numQuads = 0;
		break;
	case	ROQ_PACKET:
		inMemory = (roq_flags != 0);
		RoQFrameSize = 0;           // for header
		break;
	case	ROQ_QUAD_HANG:
		RoQFrameSize = 0;
		break;
	case	ROQ_QUAD_JPEG:
		if (!numQuads) {
			normalBuffer0 = t[0];
			JPEGBlit(image, framedata, RoQFrameSize);
			memcpy(image + screenDelta, image, samplesPerLine * ysize);
			numQuads++;
		}
		break;
	default:
		status = FMV_EOF;
		break;
	}
	//
	// read in next frame data
	//
	if (RoQPlayed >= ROQSize) {
		if (looping) {
			RoQReset();
		}
		else {
			status = FMV_EOF;
		}
		return;
	}

	framedata += RoQFrameSize;
	roq_id = framedata[0] + framedata[1] * 256;
	RoQFrameSize = framedata[2] + framedata[3] * 256 + framedata[4] * 65536;
	roq_flags = framedata[6] + framedata[7] * 256;
	roqF0 = (signed char)framedata[7];
	roqF1 = (signed char)framedata[6];

	if (RoQFrameSize > 65536 || roq_id == 0x1084) {
		common->DPrintf("roq_size>65536||roq_id==0x1084\n");
		status = FMV_EOF;
		if (looping) {
			RoQReset();
		}
		return;
	}
	if (inMemory && (status != FMV_EOF)) {
		inMemory = false; framedata += 8; goto redump;
	}
	//
	// one more frame hits the dust
	//
	//	assert(RoQFrameSize <= 65536);
	//	r = Sys_StreamedRead( file, RoQFrameSize+8, 1, iFile );
	RoQPlayed += RoQFrameSize + 8;
}

/*
==============
idCinematicLocal::RoQ_init
==============
*/
void idCinematicLocal::RoQ_init(void) {

	RoQPlayed = 24;

	/*	get frame rate */
	roqFPS = file[6] + file[7] * 256;

	if (!roqFPS) roqFPS = 30;

	numQuads = -1;

	roq_id = file[8] + file[9] * 256;
	RoQFrameSize = file[10] + file[11] * 256 + file[12] * 65536;
	roq_flags = file[14] + file[15] * 256;
}

/*
==============
idCinematicLocal::RoQShutdown
==============
*/
void idCinematicLocal::RoQShutdown(void) {
	if (status == FMV_IDLE) {
		return;
	}
	status = FMV_IDLE;

	if (iFile) {
		fileSystem->CloseFile(iFile);
		iFile = NULL;
	}

	// fileName is kept so RoQReset can reopen the file for replay
}

//===========================================

/*
==============
idSndWindow::InitFromFile
==============
*/
bool idSndWindow::InitFromFile(const char* qpath, bool looping) {
	idStr fname = qpath;

	fname.ToLower();
	if (!fname.Icmp("waveform")) {
		showWaveform = true;
	}
	else {
		showWaveform = false;
	}
	return true;
}

/*
==============
idSndWindow::ImageForTime
==============
*/
cinData_t idSndWindow::ImageForTime(int milliseconds) {
	return soundSystem->ImageForTime(milliseconds, showWaveform);
}

/*
==============
idSndWindow::AnimationLength
==============
*/
int idSndWindow::AnimationLength() {
	return -1;
}
