code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010 EDuke32 developers and contributors

This file is part of EDuke32.

EDuke32 is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program 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 this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------

#ifndef player_h_
#define player_h_

#include "inv.h"
#include "namesdyn.h"
#include "fix16.h"
#include "net.h"

#ifdef __cplusplus
extern "C" {
#endif

extern int32_t g_mostConcurrentPlayers;

#define MOVEFIFOSIZ                 256

#define NAM_GRENADE_LIFETIME        120
#define NAM_GRENADE_LIFETIME_VAR    30

#define HORIZ_MIN                   -99
#define HORIZ_MAX                   299
#define AUTO_AIM_ANGLE              48
#define PHEIGHT_DUKE                (38<<8)
#define PHEIGHT_RR                  (40<<8);
extern int32_t PHEIGHT;

#define TRIPBOMB_TRIPWIRE       0x00000001
#define TRIPBOMB_TIMER          0x00000002

#define PIPEBOMB_REMOTE         0x00000001
#define PIPEBOMB_TIMER          0x00000002

#define WEAPON_POS_LOWER            -9
#define WEAPON_POS_RAISE            10
#define WEAPON_POS_START             6

#define MAX_WEAPON_RECS             256

enum weaponflags_t {
    WEAPON_SPAWNTYPE1           = 0x00000000, // just spawn
    WEAPON_HOLSTER_CLEARS_CLIP  = 0x00000001, // 'holstering' clears the current clip
    WEAPON_GLOWS                = 0x00000002, // weapon 'glows' (shrinker and grower)
    WEAPON_AUTOMATIC            = 0x00000004, // automatic fire (continues while 'fire' is held down
    WEAPON_FIREEVERYOTHER       = 0x00000008, // during 'hold time' fire every frame
    WEAPON_FIREEVERYTHIRD       = 0x00000010, // during 'hold time' fire every third frame
    WEAPON_RANDOMRESTART        = 0x00000020, // restart for automatic is 'randomized' by RND 3
    WEAPON_AMMOPERSHOT          = 0x00000040, // uses ammo for each shot (for automatic)
    WEAPON_BOMB_TRIGGER         = 0x00000080, // weapon is the 'bomb' trigger
    WEAPON_NOVISIBLE            = 0x00000100, // weapon use does not cause user to become 'visible'
    WEAPON_THROWIT              = 0x00000200, // weapon 'throws' the 'shoots' item...
    WEAPON_CHECKATRELOAD        = 0x00000400, // check weapon availability at 'reload' time
    WEAPON_STANDSTILL           = 0x00000800, // player stops jumping before actual fire (like tripbomb in duke)
    WEAPON_SPAWNTYPE2           = 0x00001000, // spawn like shotgun shells
    WEAPON_SPAWNTYPE3           = 0x00002000, // spawn like chaingun shells
};

enum gamemode_t {
    MODE_MENU                   = 0x00000001,
    MODE_DEMO                   = 0x00000002,
    MODE_GAME                   = 0x00000004,
    MODE_EOL                    = 0x00000008,
    MODE_TYPE                   = 0x00000010,
    MODE_RESTART                = 0x00000020,
    MODE_SENDTOWHOM             = 0x00000040,
};

// Player Actions.
enum playeraction_t {
    pstanding                   = 0x00000001,
    pwalking                    = 0x00000002,
    prunning                    = 0x00000004,
    pducking                    = 0x00000008,
    pfalling                    = 0x00000010,
    pjumping                    = 0x00000020,
    phigher                     = 0x00000040,
    pwalkingback                = 0x00000080,
    prunningback                = 0x00000100,
    pkicking                    = 0x00000200,
    pshrunk                     = 0x00000400,
    pjetpack                    = 0x00000800,
    ponsteroids                 = 0x00001000,
    ponground                   = 0x00002000,
    palive                      = 0x00004000,
    pdead                       = 0x00008000,
    pfacing                     = 0x00010000
};

typedef struct {
    vec3_t pos;
    int16_t ang, sect;
} playerspawn_t;

typedef struct {
    int16_t got_access, last_extra, inv_amount[GET_MAX], curr_weapon, holoduke_on;
    int16_t last_weapon, weapon_pos, kickback_pic;
    int16_t ammo_amount[MAX_WEAPONS], frag[MAXPLAYERS];
    uint32_t gotweapon;
    char inven_icon, jetpack_on, heat_on;
} DukeStatus_t;

typedef struct {
    uint32_t bits;
    int16_t fvel, svel;
    fix16_t q16avel, q16horz;
    int8_t extbits;
} input_t;

#pragma pack(push,1)
// XXX: r1625 changed a lot types here, among others
//  * int32_t --> int16_t
//  * int16_t --> int8_t
//  * char --> int8_t
// Need to carefully think about implications!
// TODO: rearrange this if the opportunity arises!
// KEEPINSYNC lunatic/_defs_game.lua
typedef struct {
    vec3_t pos, opos, vel, npos;
    vec2_t bobpos, fric;

    fix16_t q16horiz, q16horizoff, oq16horiz, oq16horizoff;
    fix16_t q16ang, oq16ang, q16angvel;

    int32_t truefz, truecz, player_par;
    int32_t randomflamex, exitx, exity;
    int32_t runspeed, max_player_health, max_shield_amount;
    int32_t autostep, autostep_sbw;

    int32_t interface_toggle_flag;
    uint16_t max_actors_killed, actors_killed;
    uint32_t gotweapon;
    uint16_t zoom;

    int16_t loogiex[64], loogiey[64], sbs, sound_pitch;

    int16_t cursectnum, look_ang, last_extra, subweapon;
    int16_t max_ammo_amount[MAX_WEAPONS], ammo_amount[MAX_WEAPONS], inv_amount[GET_MAX];
    int16_t wackedbyactor, pyoff, opyoff;

    int16_t newowner, jumping_counter, airleft;
    int16_t fta, ftq, access_wallnum, access_spritenum;
    int16_t kickback_pic, got_access, weapon_ang, visibility;
    int16_t somethingonplayer, on_crane, i, one_parallax_sectnum;
    int16_t random_club_frame, one_eighty_count;
    int16_t dummyplayersprite, extra_extra8;
    int16_t actorsqu, timebeforeexit, customexitsound, last_pissed_time;

    int16_t weaprecs[MAX_WEAPON_RECS], weapon_sway, crack_time, bobcounter;

    int16_t orotscrnang, rotscrnang, dead_flag;   // JBF 20031220: added orotscrnang
    int16_t holoduke_on, pycount;
    int16_t transporter_hold/*, clipdist*/;

    uint8_t max_secret_rooms, secret_rooms;
    // XXX: 255 values for frag(gedself) seems too small.
    uint8_t frag, fraggedself, quick_kick, last_quick_kick;
    uint8_t return_to_center, reloading, weapreccnt;
    uint8_t aim_mode, auto_aim, weaponswitch, movement_lock, team;
    uint8_t tipincs, hbomb_hold_delay, frag_ps;

    uint8_t gm, on_warping_sector, footprintcount, hurt_delay;
    uint8_t hbomb_on, jumping_toggle, rapid_fire_hold, on_ground;
    uint8_t inven_icon, buttonpalette, over_shoulder_on, show_empty_weapon;

    uint8_t jetpack_on, spritebridge, lastrandomspot;
    uint8_t scuba_on, footprintpal, heat_on, invdisptime;

    uint8_t holster_weapon, falling_counter, footprintshade;
    uint8_t refresh_inventory, last_full_weapon;

    uint8_t toggle_key_flag, knuckle_incs, knee_incs, access_incs;
    uint8_t walking_snd_toggle, palookup, hard_landing, fist_incs;

    int8_t numloogs, loogcnt, scream_voice;
    int8_t last_weapon, cheat_phase, weapon_pos, wantweaponfire, curr_weapon;

    uint8_t palette;
    palette_t pals;

    int8_t last_used_weapon;

    int16_t recoil;
    int32_t stairs;
    int32_t hbomb_offset;
    int16_t hbomb_time;
    uint8_t shotgun_state[2];
    uint8_t make_noise;
    int32_t noise_x, noise_y, noise_radius;
    uint8_t keys[5];
    int16_t yehaa_timer;
    int16_t drink_amt, eat_amt, drink_ang, eat_ang;
    int32_t drink_timer, eat_timer;
    int16_t level_end_timer;
    int16_t moto_speed, tilt_status, moto_drink;
    uint8_t on_motorcycle, on_boat, moto_underwater, not_on_water, moto_on_ground;
    uint8_t moto_do_bump, moto_bump_fast, moto_on_oil, moto_on_mud;
    int16_t moto_bump, moto_bump_target, moto_turb;
    int16_t drug_stat[3];
    int32_t drug_aspect;
    uint8_t drug_mode, lotag800kill, sea_sick_stat;
    int32_t drug_timer;
    int32_t sea_sick;
    uint8_t hurt_delay2, nocheat;

    int32_t dhat60f, dhat613, dhat617, dhat61b, dhat61f;

    uint8_t dn64_36d, dn64_36e;

    int16_t dn64_370, dn64_372, dn64_374, dn64_376, dn64_378;

    uint8_t dn64_385;

    int32_t dn_388;

    int8_t crouch_toggle;
    int8_t padding_[1];
} DukePlayer_t;

// KEEPINSYNC lunatic/_defs_game.lua
typedef struct {
    DukePlayer_t *ps;
    input_t *inputBits;

    int32_t movefifoend, syncvalhead, myminlag;
    int32_t pcolor, pteam;
    // NOTE: wchoice[HANDREMOTE_WEAPON .. MAX_WEAPONS-1] unused
    uint8_t frags[MAXPLAYERS], wchoice[MAX_WEAPONS];

    char vote, gotvote, playerreadyflag, playerquitflag, connected;
    char user_name[32];
    char syncval[SYNCFIFOSIZ][MAXSYNCBYTES];
} playerdata_t;
#pragma pack(pop)

// KEEPINSYNC lunatic/con_lang.lua
typedef struct
{
    // NOTE: the member names must be identical to aplWeapon* suffixes.
    int32_t WorksLike;  // What the original works like
    int32_t Clip;  // number of items in magazine
    int32_t Reload;  // delay to reload (include fire)
    int32_t FireDelay;  // delay to fire
    int32_t TotalTime;  // The total time the weapon is cycling before next fire.
    int32_t HoldDelay;  // delay after release fire button to fire (0 for none)
    int32_t Flags;  // Flags for weapon
    int32_t Shoots;  // what the weapon shoots
    int32_t SpawnTime;  // the frame at which to spawn an item
    int32_t Spawn;  // the item to spawn
    int32_t ShotsPerBurst;  // number of shots per 'burst' (one ammo per 'burst')
    int32_t InitialSound;  // Sound made when weapon starts firing. zero for no sound
    int32_t FireSound;  // Sound made when firing (each time for automatic)
    int32_t Sound2Time;  // Alternate sound time
    int32_t Sound2Sound;  // Alternate sound sound ID
    int32_t FlashColor;  // Muzzle flash color
} weapondata_t;

#ifdef LUNATIC
# define PWEAPON(Player, Weapon, Wmember) (g_playerWeapon[Player][Weapon].Wmember)
extern weapondata_t g_playerWeapon[MAXPLAYERS][MAX_WEAPONS];
#else
# define PWEAPON(Player, Weapon, Wmember) (aplWeapon ## Wmember [Weapon][Player])
extern intptr_t         *aplWeaponClip[MAX_WEAPONS];            // number of items in clip
extern intptr_t         *aplWeaponReload[MAX_WEAPONS];          // delay to reload (include fire)
extern intptr_t         *aplWeaponFireDelay[MAX_WEAPONS];       // delay to fire
extern intptr_t         *aplWeaponHoldDelay[MAX_WEAPONS];       // delay after release fire button to fire (0 for none)
extern intptr_t         *aplWeaponTotalTime[MAX_WEAPONS];       // The total time the weapon is cycling before next fire.
extern intptr_t         *aplWeaponFlags[MAX_WEAPONS];           // Flags for weapon
extern intptr_t         *aplWeaponShoots[MAX_WEAPONS];          // what the weapon shoots
extern intptr_t         *aplWeaponSpawnTime[MAX_WEAPONS];       // the frame at which to spawn an item
extern intptr_t         *aplWeaponSpawn[MAX_WEAPONS];           // the item to spawn
extern intptr_t         *aplWeaponShotsPerBurst[MAX_WEAPONS];   // number of shots per 'burst' (one ammo per 'burst'
extern intptr_t         *aplWeaponWorksLike[MAX_WEAPONS];       // What original the weapon works like
extern intptr_t         *aplWeaponInitialSound[MAX_WEAPONS];    // Sound made when initialy firing. zero for no sound
extern intptr_t         *aplWeaponFireSound[MAX_WEAPONS];       // Sound made when firing (each time for automatic)
extern intptr_t         *aplWeaponSound2Time[MAX_WEAPONS];      // Alternate sound time
extern intptr_t         *aplWeaponSound2Sound[MAX_WEAPONS];     // Alternate sound sound ID
extern intptr_t         *aplWeaponFlashColor[MAX_WEAPONS];      // Color for polymer muzzle flash
#endif

// KEEPINSYNC lunatic/_defs_game.lua
typedef struct {
    int32_t cur, count;  // "cur" is the only member that is *used*
    int32_t gunposx, lookhalfang;  // weapon_xoffset, ps->look_ang>>1
    int32_t gunposy, lookhoriz;  // gun_pos, looking_arc
    int32_t shade;
} hudweapon_t;

extern input_t          inputfifo[MOVEFIFOSIZ][MAXPLAYERS];
extern playerspawn_t    g_playerSpawnPoints[MAXPLAYERS];
extern playerdata_t     *const g_player;
extern int16_t          WeaponPickupSprites[MAX_WEAPONS];
extern hudweapon_t      hudweap;
extern int32_t          g_levelTextTime;
extern int32_t          g_myAimMode;
extern int32_t          g_numObituaries;
extern int32_t          g_numSelfObituaries;
extern int32_t          mouseyaxismode;
extern int32_t          ticrandomseed;

#define SHOOT_HARDCODED_ZVEL INT32_MIN

int A_Shoot(int spriteNum, int projecTile);

static inline void P_PalFrom(DukePlayer_t *pPlayer, uint8_t f, uint8_t r, uint8_t g, uint8_t b)
{
    pPlayer->pals.f = f;
    pPlayer->pals.r = r;
    pPlayer->pals.g = g;
    pPlayer->pals.b = b;
}

void    P_AddKills(DukePlayer_t * pPlayer, uint16_t kills);
int32_t A_GetHitscanRange(int spriteNum);
void    P_GetInput(int playerNum);
void    P_GetInputMotorcycle(int playerNum);
void    P_GetInputBoat(int playerNum);
void    sub_299C0(void);
void    P_DHGetInput(int const playerNum);
void P_AddAmmo(DukePlayer_t * pPlayer, int weaponNum, int addAmount);
void    P_AddWeapon(DukePlayer_t *pPlayer, int weaponNum);
void    P_CheckWeapon(DukePlayer_t *pPlayer);
void    P_DisplayScuba(void);
void    P_DisplayWeapon(void);
void P_DropWeapon(int playerNum);
int     P_FindOtherPlayer(int playerNum, int32_t *pDist);
void    P_FragPlayer(int playerNum);
#ifdef YAX_ENABLE
void getzsofslope_player(int sectNum, int playerX, int playerY, int32_t *pCeilZ, int32_t *pFloorZ);
#endif
void    P_UpdatePosWhenViewingCam(DukePlayer_t *pPlayer);
void    P_ProcessInput(int playerNum);
void    P_DHProcessInput(int playerNum);
void    P_QuickKill(DukePlayer_t *pPlayer);
void    P_SelectNextInvItem(DukePlayer_t *pPlayer);
void    P_UpdateScreenPal(DukePlayer_t *pPlayer);
void    P_EndLevel(void);
void    P_CheckWeaponI(int playerNum);
int     P_GetHudPal(const DukePlayer_t *pPlayer);
int     P_GetKneePal(const DukePlayer_t *pPlayer);
#ifdef __cplusplus
}
int     P_GetKneePal(const DukePlayer_t *pPlayer, int hudPal);
extern "C" {
#endif
int     P_GetOverheadPal(const DukePlayer_t *pPlayer);
void P_MadeNoise(int playerNum);
int P_HasKey(int sectNum, int playerNum);
int P_NextWeapon(DukePlayer_t* p, int k, int j);

// Get the player index given an APLAYER sprite pointer.
static inline int P_GetP(const void *pSprite)
{
#if 0  // unprotected player index retrieval
    return spr->yvel;
#elif defined NETCODE_DISABLE
    UNREFERENCED_PARAMETER(pSprite);  // for NDEBUG build
    // NOTE: In the no-netcode build, there's no point to pass player indices
    // at all since there is ever only one player. However, merely returning 0
    // would mean making this build less strict than the normal one.
    Bassert(((const uspritetype *)pSprite)->yvel == 0);
    return 0;
#else
    int playerNum = ((const uspritetype *)pSprite)->yvel;
    // [JM] Check against MAXPLAYERS as opposed to g_mostConcurrentPlayers
    //      to prevent CON for disconnected/fake players from executing as playernum 0.
    if ((unsigned)playerNum >= MAXPLAYERS)
        playerNum = 0;
    return playerNum;
#endif
}

// Get the player index given an APLAYER sprite index.
static inline int P_Get(int32_t spriteNum) { return P_GetP((const uspritetype *)&sprite[spriteNum]); }

#ifdef __cplusplus
}
#endif

#endif

repo_name: orochi/nblood

license: mit


code:
/*
 * InterplayDecoder
 * Copyright (C) 2020 sirlemonhead
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

/* This code is based on interplayvideo.c, dpcm.c and ipmovie.c from the FFmpeg project which can be obtained
 * from http://www.ffmpeg.org/. Below is the license from interplayvideo.c
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/*
 * Interplay MVE Video Decoder
 * Copyright (C) 2003 The FFmpeg project
 *
 * This file is part of FFmpeg.
 *
 * FFmpeg is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * FFmpeg 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "playmve.h"
#include "filestream.h"
#include "control.h"
#include "joystick.h"
#include "function.h"

#define kAudioBlocks    20 // alloc a lot of blocks - need to store lots of audio data before video frames start.

static const int16_t delta_table[] = {
         0,      1,      2,      3,      4,      5,      6,      7,
         8,      9,     10,     11,     12,     13,     14,     15,
        16,     17,     18,     19,     20,     21,     22,     23,
        24,     25,     26,     27,     28,     29,     30,     31,
        32,     33,     34,     35,     36,     37,     38,     39,
        40,     41,     42,     43,     47,     51,     56,     61,
        66,     72,     79,     86,     94,    102,    112,    122,
       133,    145,    158,    173,    189,    206,    225,    245,
       267,    292,    318,    348,    379,    414,    452,    493,
       538,    587,    640,    699,    763,    832,    908,    991,
      1081,   1180,   1288,   1405,   1534,   1673,   1826,   1993,
      2175,   2373,   2590,   2826,   3084,   3365,   3672,   4008,
      4373,   4772,   5208,   5683,   6202,   6767,   7385,   8059,
      8794,   9597,  10472,  11428,  12471,  13609,  14851,  16206,
     17685,  19298,  21060,  22981,  25078,  27367,  29864,  32589,
    -29973, -26728, -23186, -19322, -15105, -10503,  -5481,     -1,
         1,      1,   5481,  10503,  15105,  19322,  23186,  26728,
     29973, -32589, -29864, -27367, -25078, -22981, -21060, -19298,
    -17685, -16206, -14851, -13609, -12471, -11428, -10472,  -9597,
     -8794,  -8059,  -7385,  -6767,  -6202,  -5683,  -5208,  -4772,
     -4373,  -4008,  -3672,  -3365,  -3084,  -2826,  -2590,  -2373,
     -2175,  -1993,  -1826,  -1673,  -1534,  -1405,  -1288,  -1180,
     -1081,   -991,   -908,   -832,   -763,   -699,   -640,   -587,
      -538,   -493,   -452,   -414,   -379,   -348,   -318,   -292,
      -267,   -245,   -225,   -206,   -189,   -173,   -158,   -145,
      -133,   -122,   -112,   -102,    -94,    -86,    -79,    -72,
       -66,    -61,    -56,    -51,    -47,    -43,    -42,    -41,
       -40,    -39,    -38,    -37,    -36,    -35,    -34,    -33,
       -32,    -31,    -30,    -29,    -28,    -27,    -26,    -25,
       -24,    -23,    -22,    -21,    -20,    -19,    -18,    -17,
       -16,    -15,    -14,    -13,    -12,    -11,    -10,     -9,
        -8,     -7,     -6,     -5,     -4,     -3,     -2,     -1
};

class InterplayDecoder
{
public:
    InterplayDecoder();
    ~InterplayDecoder();

    bool Open(const char* fileName);
    void Close();

    struct AudioBlock
    {
        int16_t buf[6000];
        uint32_t size;
    };

    struct AudioData
    {
        int hFx;
        int nChannels;
        uint16_t nSampleRate;
        uint8_t nBitDepth;

        AudioBlock block[kAudioBlocks];
        int nWrite;
        int nRead;
    };

    AudioData audio;
    mutex_t mutex;

private:
    struct DecodeMap
    {
        uint8_t* pData;
        uint32_t nSize;
    };

    struct Palette
    {
        uint8_t r;
        uint8_t g;
        uint8_t b;
    };

    bool Run();
    uint8_t* GetCurrentFrame();
    uint8_t* GetPreviousFrame();
    void SwapFrames();
    void CopyBlock(uint8_t* pDest, uint8_t* pSrc);
    void DecodeBlock0(int32_t offset);
    void DecodeBlock1(int32_t offset);
    void DecodeBlock2(int32_t offset);
    void DecodeBlock3(int32_t offset);
    void DecodeBlock4(int32_t offset);
    void DecodeBlock5(int32_t offset);
    void DecodeBlock7(int32_t offset);
    void DecodeBlock8(int32_t offset);
    void DecodeBlock9(int32_t offset);
    void DecodeBlock10(int32_t offset);
    void DecodeBlock11(int32_t offset);
    void DecodeBlock12(int32_t offset);
    void DecodeBlock13(int32_t offset);
    void DecodeBlock14(int32_t offset);
    void DecodeBlock15(int32_t offset);

    RedNukem::FileStream file;

    bool bIsPlaying, bAudioStarted;

    uint32_t nTimerRate, nTimerDiv;
    uint32_t nWidth, nHeight, nFrame;
    double nFps;
    uint32_t nFrameDuration;

    uint8_t* pVideoBuffers[2];
    uint32_t nCurrentVideoBuffer, nPreviousVideoBuffer;
    int32_t videoStride;

    DecodeMap decodeMap;

    Palette palette[256];
};

// macro to fetch 16-bit little-endian words from a bytestream
#define LE_16(x)  ((*x) | ((*(x+1)) << 8))

int ClipRange(int val, int min, int max)
{
    if (val < min)
        return min;
    else if (val > max)
        return max;
    else
        return val;
}

void ServeAudioSample(const char** ptr, uint32_t* length, void *userdata)
{
    InterplayDecoder* pId = (InterplayDecoder*)userdata;

    mutex_lock(&pId->mutex);

    uint32_t nSize = pId->audio.block[pId->audio.nRead].size;

    *ptr = (char*)pId->audio.block[pId->audio.nRead].buf;
    *length = nSize / 2;

    pId->audio.nRead++;
    if (pId->audio.nRead >= kAudioBlocks)
        pId->audio.nRead = 0;

    mutex_unlock(&pId->mutex);
}

InterplayDecoder::InterplayDecoder()
{
    bIsPlaying = false;
    bAudioStarted = false;

    mutex   = 0;
    nWidth  = 0;
    nHeight = 0;
    nFrame  = 0;

    memset(palette, 0, sizeof(palette));

    for (int i = 0; i < kAudioBlocks; i++) {
        memset(audio.block[i].buf, 0, sizeof(audio.block[i].buf));
        audio.block[i].size = 0;
    }

    nFps = 0.0;
    nFrameDuration = 0;
    nTimerRate = 0;
    nTimerDiv  = 0;

    audio.nChannels   = 0;
    audio.nSampleRate = 0;
    audio.nBitDepth   = 0;
    audio.nRead  = 0;
    audio.nWrite = 0;
    audio.hFx = 0;

    pVideoBuffers[0] = nullptr;
    pVideoBuffers[1] = nullptr;

    decodeMap.pData = nullptr;
    decodeMap.nSize = 0;

    nCurrentVideoBuffer  = 0;
    nPreviousVideoBuffer = 1;

    videoStride = 0;

    mutex_init(&mutex);
}

InterplayDecoder::~InterplayDecoder()
{
    file.Close();
    mutex_destroy(&mutex);

    if (decodeMap.pData) {
        delete[] decodeMap.pData;
    }

    if (pVideoBuffers[0]) {
        delete[] pVideoBuffers[0];
    }
    if (pVideoBuffers[1]) {
        delete[] pVideoBuffers[1];
    }
}

bool playmve(const char* filename)
{
    InterplayDecoder *id = new InterplayDecoder;
    id->Open(filename);

    delete id;

    return true;
}

void InterplayDecoder::SwapFrames()
{
    int t = nPreviousVideoBuffer;
    nPreviousVideoBuffer = nCurrentVideoBuffer;
    nCurrentVideoBuffer = t;
}

void InterplayDecoder::Close()
{
    bIsPlaying = false;

    if (audio.hFx > 0) {
        FX_StopSound(audio.hFx);
    }
}

bool InterplayDecoder::Open(const char* fileName)
{
    // open the file (read only)
    file.Open(fileName);
    if (!file.Is_Open())
    {
        initprintf("InterplayDecoder: Can't open file %s\n", fileName);
        return false;
    }

    char lsig[20];

    // check the file signature
    file.ReadBytes((uint8_t*)lsig, sizeof(lsig));
    if (memcmp(lsig, "Interplay MVE File\x1A\0", sizeof(lsig)) != 0)
    {
        initprintf("InterplayDecoder: Unknown MVE signature\n ");
        return false;
    }

    // skip the next 6 bytes
    file.Skip(6);

    Run();

    return true;
}

bool InterplayDecoder::Run()
{
    uint8_t chunkPreamble[CHUNK_PREAMBLE_SIZE];
    uint8_t opcodePreamble[OPCODE_PREAMBLE_SIZE];
    uint8_t opcodeType;
    uint8_t opcodeVersion;
    int opcodeSize, chunkSize;
    int chunkType = 0;

    auto const oyxaspect = yxaspect;

    int nScale = tabledivide32(scale(65536, ydim << 2, xdim * 3), ((max(nHeight, 240 + 1u) + 239) / 240));
    int nStat = 2|4|8|64|1024;
    renderSetAspect(viewingrange, 65536);

    uint32_t nNextFrameTime = (uint32_t)totalclock + nFrameDuration;

    bIsPlaying = true;

    // iterate through the chunks in the file
    while (chunkType != CHUNK_END && bIsPlaying)
    {
        handleevents();

        // handle timing - wait until we're ready to process the next frame.
        if (nNextFrameTime > (uint32_t)totalclock) {
            continue;
        }
        else {
            nNextFrameTime = (uint32_t)totalclock + nFrameDuration;
        }

        if (file.ReadBytes(chunkPreamble, CHUNK_PREAMBLE_SIZE) != CHUNK_PREAMBLE_SIZE) {
            initprintf("InterplayDecoder: could not read from file (EOF?)\n");
            return false;
        }

        chunkSize = LE_16(&chunkPreamble[0]);
        chunkType = LE_16(&chunkPreamble[2]);

        switch (chunkType)
        {
            case CHUNK_INIT_AUDIO:
                break;
            case CHUNK_AUDIO_ONLY:
                break;
            case CHUNK_INIT_VIDEO:
                break;
            case CHUNK_VIDEO:
                break;
            case CHUNK_SHUTDOWN:
                break;
            case CHUNK_END:
                bIsPlaying = false;
                break;
            default:
                break;
        }

        // iterate through individual opcodes
        while (chunkSize > 0)
        {
            handleevents();

            if (KB_KeyWaiting() || BUTTON(gamefunc_Fire)
                || (JOYSTICK_GetControllerButtons() & (1 << CONTROLLER_BUTTON_A))
                || (JOYSTICK_GetControllerButtons() & (1 << CONTROLLER_BUTTON_START)))
            {
                renderSetAspect(viewingrange, oyxaspect);
                Close();
                return true;
            }

            if (file.ReadBytes(opcodePreamble, OPCODE_PREAMBLE_SIZE) != OPCODE_PREAMBLE_SIZE)
            {
                initprintf("InterplayDecoder: could not read from file (EOF?)\n");
                return false;
            }

            opcodeSize = LE_16(&opcodePreamble[0]);
            opcodeType = opcodePreamble[2];
            opcodeVersion = opcodePreamble[3];

            chunkSize -= OPCODE_PREAMBLE_SIZE;
            chunkSize -= opcodeSize;

            switch (opcodeType)
            {
            case OPCODE_END_OF_STREAM:
            {
                file.Skip(opcodeSize);
                break;
            }

            case OPCODE_END_OF_CHUNK:
            {
                file.Skip(opcodeSize);
                break;
            }

            case OPCODE_CREATE_TIMER:
            {
                nTimerRate = file.ReadUint32LE();
                nTimerDiv  = file.ReadUint16LE();
                nFps = 1000000.0f / ((double)nTimerRate * nTimerDiv);
                nFrameDuration = 120.0f / nFps;
                break;
            }

            case OPCODE_INIT_AUDIO_BUFFERS:
            {
                file.Skip(2);
                uint16_t flags = file.ReadUint16LE();
                audio.nSampleRate = file.ReadUint16LE();

                uint32_t nBufferBytes;

                if (opcodeVersion == 0) {
                    nBufferBytes = file.ReadUint16LE();
                }
                else {
                    nBufferBytes = file.ReadUint32LE();
                }

                if (flags & 0x1) {
                    audio.nChannels = 2;
                }
                else {
                    audio.nChannels = 1;
                }
                if (flags & 0x2) {
                    audio.nBitDepth = 16;
                }
                else {
                    audio.nBitDepth = 8;
                }
                break;
            }

            case OPCODE_START_STOP_AUDIO:
            {
                if (!bAudioStarted)
                {
                    // start audio playback
                    audio.hFx = FX_StartDemandFeedPlayback(ServeAudioSample, audio.nBitDepth, audio.nChannels, audio.nSampleRate, 0, 128, 128, 128, FX_MUSIC_PRIORITY, fix16_one, -1, this);
                    bAudioStarted = true;
                }

                file.Skip(opcodeSize);
                break;
            }

            case OPCODE_INIT_VIDEO_BUFFERS:
            {
                assert(opcodeSize == 8);
                nWidth  = file.ReadUint16LE() * 8;
                nHeight = file.ReadUint16LE() * 8;

                int count = file.ReadUint16LE();
                int truecolour = file.ReadUint16LE();
                assert(truecolour == 0);

                pVideoBuffers[0] = new uint8_t[nWidth * nHeight];
                pVideoBuffers[1] = new uint8_t[nWidth * nHeight];

                videoStride = nWidth;

                uint8_t* pFrame = (uint8_t*)Xmalloc(nWidth * nHeight);
                memset(pFrame, 0, nWidth * nHeight);
                walock[kMVETile] = CACHE1D_PERMANENT;
                waloff[kMVETile] = (intptr_t)pFrame;
                tileSetSize(kMVETile, nHeight, nWidth);
                break;
            }

            case OPCODE_UNKNOWN_06:
            case OPCODE_UNKNOWN_0E:
            case OPCODE_UNKNOWN_10:
            case OPCODE_UNKNOWN_12:
            case OPCODE_UNKNOWN_13:
            case OPCODE_UNKNOWN_14:
            case OPCODE_UNKNOWN_15:
            {
                file.Skip(opcodeSize);
                break;
            }

            case OPCODE_SEND_BUFFER:
            {
                int nPalStart = file.ReadUint16LE();
                int nPalCount = file.ReadUint16LE();

                memcpy((char*)waloff[kMVETile], GetCurrentFrame(), nWidth * nHeight);
                tileInvalidate(kMVETile, -1, -1);

                nFrame++;
                SwapFrames();

                file.Skip(opcodeSize - 4);
                break;
            }

            case OPCODE_AUDIO_FRAME:
            {
                int nStart = file.GetPosition();
                uint16_t seqIndex   = file.ReadUint16LE();
                uint16_t streamMask = file.ReadUint16LE();
                uint16_t nSamples   = file.ReadUint16LE(); // number of samples this chunk

                int predictor[2];
                int i = 0;

                mutex_lock(&mutex);

                int16_t* pBuf = audio.block[audio.nWrite].buf;

                for (int ch = 0; ch < audio.nChannels; ch++)
                {
                    predictor[ch] = file.ReadUint16LE();
                    i++;

                    if (predictor[ch] & 0x8000) {
                        predictor[ch] |= 0xFFFF0000; // sign extend
                    }

                    *pBuf++ = predictor[ch];
                }

                int ch = 0;
                for (; i < (nSamples / 2); i++)
                {
                    predictor[ch] += delta_table[file.ReadByte()];
                    predictor[ch] = ClipRange(predictor[ch], -32768, 32768);

                    *pBuf++ = predictor[ch];

                    // toggle channel
                    ch ^= audio.nChannels - 1;
                }

                audio.block[audio.nWrite].size = nSamples / 2;
                audio.nWrite++;

                if (audio.nWrite >= kAudioBlocks)
                    audio.nWrite = 0;

                int nEnd = file.GetPosition();
                int nRead = nEnd - nStart;
                assert(opcodeSize == nRead);

                mutex_unlock(&mutex);
                break;
            }

            case OPCODE_SILENCE_FRAME:
            {
                uint16_t seqIndex = file.ReadUint16LE();
                uint16_t streamMask = file.ReadUint16LE();
                uint16_t nStreamLen = file.ReadUint16LE();
                break;
            }

            case OPCODE_INIT_VIDEO_MODE:
            {
                file.Skip(opcodeSize);
                break;
            }

            case OPCODE_CREATE_GRADIENT:
            {
                file.Skip(opcodeSize);
                initprintf("InterplayDecoder: Create gradient not supported.\n");
                break;
            }

            case OPCODE_SET_PALETTE:
            {
                if (opcodeSize > 0x304 || opcodeSize < 4) {
                    printf("set_palette opcode with invalid size\n");
                    chunkType = CHUNK_BAD;
                    break;
                }

                int nPalStart = file.ReadUint16LE();
                int nPalCount = file.ReadUint16LE();

                for (int i = nPalStart; i <= nPalCount; i++)
                {
                    palette[i].r = file.ReadByte() << 2;
                    palette[i].g = file.ReadByte() << 2;
                    palette[i].b = file.ReadByte() << 2;
                }

                paletteSetColorTable(kMVEPal, (uint8_t*)palette);
                videoSetPalette(0, kMVEPal, 0);
                break;
            }

            case OPCODE_SET_PALETTE_COMPRESSED:
            {
                file.Skip(opcodeSize);
                initprintf("InterplayDecoder: Set palette compressed not supported.\n");
                break;
            }

            case OPCODE_SET_DECODING_MAP:
            {
                if (!decodeMap.pData)
                {
                    decodeMap.pData = new uint8_t[opcodeSize];
                    decodeMap.nSize = opcodeSize;
                }
                else
                {
                    if (opcodeSize != decodeMap.nSize) {
                        delete[] decodeMap.pData;
                        decodeMap.pData = new uint8_t[opcodeSize];
                        decodeMap.nSize = opcodeSize;
                    }
                }

                int nRead = file.ReadBytes(decodeMap.pData, opcodeSize);
                assert(nRead == opcodeSize);
                break;
            }

            case OPCODE_VIDEO_DATA:
            {
                int nStart = file.GetPosition();

                // need to skip 14 bytes
                file.Skip(14);

                if (decodeMap.nSize)
                {
                    int i = 0;

                    for (uint32_t y = 0; y < nHeight; y += 8)
                    {
                        for (uint32_t x = 0; x < nWidth; x += 8)
                        {
                            uint32_t opcode;

                            // alternate between getting low and high 4 bits
                            if (i & 1) {
                                opcode = decodeMap.pData[i >> 1] >> 4;
                            }
                            else {
                                opcode = decodeMap.pData[i >> 1] & 0x0F;
                            }
                            i++;

                            int32_t offset = x + (y * videoStride);

                            switch (opcode)
                            {
                                default:
                                    break;
                                case 0:
                                    DecodeBlock0(offset);
                                    break;
                                case 1:
                                    DecodeBlock1(offset);
                                    break;
                                case 2:
                                    DecodeBlock2(offset);
                                    break;
                                case 3:
                                    DecodeBlock3(offset);
                                    break;
                                case 4:
                                    DecodeBlock4(offset);
                                    break;
                                case 5:
                                    DecodeBlock5(offset);
                                    break;
                                case 7:
                                    DecodeBlock7(offset);
                                    break;
                                case 8:
                                    DecodeBlock8(offset);
                                    break;
                                case 9:
                                    DecodeBlock9(offset);
                                    break;
                                case 10:
                                    DecodeBlock10(offset);
                                    break;
                                case 11:
                                    DecodeBlock11(offset);
                                    break;
                                case 12:
                                    DecodeBlock12(offset);
                                    break;
                                case 13:
                                    DecodeBlock13(offset);
                                    break;
                                case 14:
                                    DecodeBlock14(offset);
                                    break;
                                case 15:
                                    DecodeBlock15(offset);
                                    break;
                            }
                        }
                    }
                }

                int nEnd = file.GetPosition();
                int nSkipBytes = opcodeSize - (nEnd - nStart); // we can end up with 1 byte left we need to skip
                assert(nSkipBytes <= 1);

                file.Skip(nSkipBytes);
                break;
            }

            default:
                break;
            }
        }

        videoClearScreen(0);

        rotatesprite_fs(160 << 16, 100 << 16, nScale, 512, kMVETile, 0, 0, nStat);
        videoNextPage();
    }

    renderSetAspect(viewingrange, oyxaspect);

    Close();

    return true;
}

void InterplayDecoder::CopyBlock(uint8_t* pDest, uint8_t* pSrc)
{
    for (int y = 0; y < 8; y++)
    {
        memcpy(pDest, pSrc, 8);
        pSrc  += (intptr_t)videoStride;
        pDest += (intptr_t)videoStride;
    }
}

void InterplayDecoder::DecodeBlock0(int32_t offset)
{
    // copy from the same offset but from the previous frame
    uint8_t* pDest = GetCurrentFrame() + (intptr_t)offset;
    uint8_t* pSrc = GetPreviousFrame() + (intptr_t)offset;

    CopyBlock(pDest, pSrc);
}

void InterplayDecoder::DecodeBlock1(int32_t offset)
{
    // nothing to do for this.
}

void InterplayDecoder::DecodeBlock2(int32_t offset)
{
    // copy block from 2 frames ago using a motion vector; need 1 more byte
    uint8_t B = file.ReadByte();

    int x, y;

    if (B < 56) {
        x = 8 + (B % 7);
        y = B / 7;
    }
    else {
        x = -14 + ((B - 56) % 29);
        y = 8 + ((B - 56) / 29);
    }

    uint8_t* pDest = GetCurrentFrame() + (intptr_t)offset;
    uint8_t* pSrc  = GetCurrentFrame() + (intptr_t)(int64_t)offset + (int64_t)x + (int64_t(y) * (int64_t)videoStride);

    CopyBlock(pDest, pSrc);
}

void InterplayDecoder::DecodeBlock3(int32_t offset)
{
    // copy 8x8 block from current frame from an up/left block
    uint8_t B = file.ReadByte();

    int x, y;

    // need 1 more byte for motion
    if (B < 56) {
        x = -(8 + (B % 7));
        y = -(B / 7);
    }
    else {
        x = -(-14 + ((B - 56) % 29));
        y = -(8 + ((B - 56) / 29));
    }

    uint8_t* pDest = GetCurrentFrame() + (intptr_t)offset;
    uint8_t* pSrc  = GetCurrentFrame() + (intptr_t)(int64_t)offset + (int64_t)x + (int64_t(y) * (int64_t)videoStride);

    CopyBlock(pDest, pSrc);
}

void InterplayDecoder::DecodeBlock4(int32_t offset)
{
    // copy a block from the previous frame; need 1 more byte
    int x, y;
    uint8_t B, BL, BH;

    B = file.ReadByte();

    BL = B & 0x0F;
    BH = (B >> 4) & 0x0F;
    x = -8 + BL;
    y = -8 + BH;

    uint8_t* pDest = GetCurrentFrame() + (intptr_t)offset;
    uint8_t* pSrc = GetPreviousFrame() + (intptr_t)(int64_t)offset + (int64_t)x + (int64_t(y) * (int64_t)videoStride);

    CopyBlock(pDest, pSrc);
}

void InterplayDecoder::DecodeBlock5(int32_t offset)
{
    // copy a block from the previous frame using an expanded range; need 2 more bytes
    int8_t x = file.ReadByte();
    int8_t y = file.ReadByte();

    uint8_t* pDest = GetCurrentFrame() + (intptr_t)offset;
    uint8_t* pSrc = GetPreviousFrame() + (intptr_t)(int64_t)offset + (int64_t)x + (int64_t(y) * (int64_t)videoStride);

    CopyBlock(pDest, pSrc);
}

// Block6 is unknown and skipped

void InterplayDecoder::DecodeBlock7(int32_t offset)
{
    uint8_t* pBuffer = GetCurrentFrame() + (intptr_t)offset;
    uint32_t flags = 0;

    uint8_t P[2];
    P[0] = file.ReadByte();
    P[1] = file.ReadByte();

    // 2-color encoding
    if (P[0] <= P[1])
    {
        // need 8 more bytes from the stream
        for (int y = 0; y < 8; y++)
        {
            flags = file.ReadByte() | 0x100;
            for (; flags != 1; flags >>= 1) {
                *pBuffer++ = P[flags & 1];
            }
            pBuffer += (videoStride - 8);
        }
    }
    else
    {
        // need 2 more bytes from the stream
        flags = file.ReadUint16LE();

        for (int y = 0; y < 8; y += 2)
        {
            for (int x = 0; x < 8; x += 2, flags >>= 1)
            {
                pBuffer[x] =
                    pBuffer[x + 1] =
                    pBuffer[x + videoStride] =
                    pBuffer[x + 1 + videoStride] = P[flags & 1];
            }
            pBuffer += videoStride * 2;
        }
    }
}

void InterplayDecoder::DecodeBlock8(int32_t offset)
{
    uint8_t* pBuffer = GetCurrentFrame() + (intptr_t)offset;
    uint32_t flags = 0;
    uint8_t P[4];

    // 2-color encoding for each 4x4 quadrant, or 2-color encoding on either top and bottom or left and right halves
    P[0] = file.ReadByte();
    P[1] = file.ReadByte();

    if (P[0] <= P[1])
    {
        for (int y = 0; y < 16; y++)
        {
            // new values for each 4x4 block
            if (!(y & 3))
            {
                if (y) {
                    P[0] = file.ReadByte();
                    P[1] = file.ReadByte();
                }
                flags = file.ReadUint16LE();
            }

            for (int x = 0; x < 4; x++, flags >>= 1) {
                *pBuffer++ = P[flags & 1];
            }

            pBuffer += videoStride - 4;
            // switch to right half
            if (y == 7) pBuffer -= 8 * videoStride - 4;
        }
    }
    else
    {
        flags = file.ReadUint32LE();
        P[2] = file.ReadByte();
        P[3] = file.ReadByte();

        if (P[2] <= P[3])
        {
            // vertical split; left & right halves are 2-color encoded
            for (int y = 0; y < 16; y++)
            {
                for (int x = 0; x < 4; x++, flags >>= 1) {
                    *pBuffer++ = P[flags & 1];
                }

                pBuffer += videoStride - 4;

                // switch to right half
                if (y == 7) {
                    pBuffer -= 8 * videoStride - 4;
                    P[0] = P[2];
                    P[1] = P[3];
                    flags = file.ReadUint32LE();
                }
            }
        }
        else
        {
            // horizontal split; top & bottom halves are 2-color encoded
            for (int y = 0; y < 8; y++)
            {
                if (y == 4) {
                    P[0] = P[2];
                    P[1] = P[3];
                    flags = file.ReadUint32LE();
                }

                for (int x = 0; x < 8; x++, flags >>= 1)
                    *pBuffer++ = P[flags & 1];

                pBuffer += (videoStride - 8);
            }
        }
    }
}

void InterplayDecoder::DecodeBlock9(int32_t offset)
{
    uint8_t* pBuffer = GetCurrentFrame() + (intptr_t)offset;
    uint8_t P[4];

    file.ReadBytes(P, 4);

    // 4-color encoding
    if (P[0] <= P[1])
    {
        if (P[2] <= P[3])
        {
            // 1 of 4 colors for each pixel, need 16 more bytes
            for (int y = 0; y < 8; y++)
            {
                // get the next set of 8 2-bit flags
                int flags = file.ReadUint16LE();

                for (int x = 0; x < 8; x++, flags >>= 2) {
                    *pBuffer++ = P[flags & 0x03];
                }

                pBuffer += (videoStride - 8);
            }
        }
        else
        {
            // 1 of 4 colors for each 2x2 block, need 4 more bytes
            uint32_t flags = file.ReadUint32LE();

            for (int y = 0; y < 8; y += 2)
            {
                for (int x = 0; x < 8; x += 2, flags >>= 2)
                {
                    pBuffer[x] =
                        pBuffer[x + 1] =
                        pBuffer[x + videoStride] =
                        pBuffer[x + 1 + videoStride] = P[flags & 0x03];
                }

                pBuffer += videoStride * 2;
            }
        }
    }
    else
    {
        // 1 of 4 colors for each 2x1 or 1x2 block, need 8 more bytes
        uint64_t flags = file.ReadUint64LE();

        if (P[2] <= P[3])
        {
            for (int y = 0; y < 8; y++)
            {
                for (int x = 0; x < 8; x += 2, flags >>= 2)
                {
                    pBuffer[x] =
                        pBuffer[x + 1] = P[flags & 0x03];
                }
                pBuffer += videoStride;
            }
        }
        else
        {
            for (int y = 0; y < 8; y += 2)
            {
                for (int x = 0; x < 8; x++, flags >>= 2)
                {
                    pBuffer[x] =
                        pBuffer[x + videoStride] = P[flags & 0x03];
                }
                pBuffer += videoStride * 2;
            }
        }
    }
}

void InterplayDecoder::DecodeBlock10(int32_t offset)
{
    uint8_t* pBuffer = GetCurrentFrame() + (intptr_t)offset;
    uint8_t P[8];

    file.ReadBytes(P, 4);

    // 4-color encoding for each 4x4 quadrant, or 4-color encoding on either top and bottom or left and right halves
    if (P[0] <= P[1])
    {
        int flags = 0;

        // 4-color encoding for each quadrant; need 32 bytes
        for (int y = 0; y < 16; y++)
        {
            // new values for each 4x4 block
            if (!(y & 3)) {
                if (y) file.ReadBytes(P, 4);
                flags = file.ReadUint32LE();
            }

            for (int x = 0; x < 4; x++, flags >>= 2) {
                *pBuffer++ = P[flags & 0x03];
            }

            pBuffer += videoStride - 4;
            // switch to right half
            if (y == 7) pBuffer -= 8 * videoStride - 4;
        }
    }
    else
    {
        // vertical split?
        int vert;
        uint64_t flags = file.ReadUint64LE();

        file.ReadBytes(P + 4, 4);
        vert = P[4] <= P[5];

        // 4-color encoding for either left and right or top and bottom halves
        for (int y = 0; y < 16; y++)
        {
            for (int x = 0; x < 4; x++, flags >>= 2)
                *pBuffer++ = P[flags & 0x03];

            if (vert)
            {
                pBuffer += videoStride - 4;
                // switch to right half
                if (y == 7) pBuffer -= 8 * videoStride - 4;
            }
            else if (y & 1) pBuffer += (videoStride - 8);

            // load values for second half
            if (y == 7) {
                memcpy(P, P + 4, 4);
                flags = file.ReadUint64LE();
            }
        }
    }
}

void InterplayDecoder::DecodeBlock11(int32_t offset)
{
    // 64-color encoding (each pixel in block is a different color)
    uint8_t* pBuffer = GetCurrentFrame() + (intptr_t)offset;

    for (int y = 0; y < 8; y++)
    {
        file.ReadBytes(pBuffer, 8);
        pBuffer += videoStride;
    }
}

void InterplayDecoder::DecodeBlock12(int32_t offset)
{
    // 16-color block encoding: each 2x2 block is a different color
    uint8_t* pBuffer = GetCurrentFrame() + (intptr_t)offset;

    for (int y = 0; y < 8; y += 2)
    {
        for (int x = 0; x < 8; x += 2)
        {
            pBuffer[x] =
                pBuffer[x + 1] =
                pBuffer[x + videoStride] =
                pBuffer[x + 1 + videoStride] = file.ReadByte();
        }
        pBuffer += videoStride * 2;
    }
}

void InterplayDecoder::DecodeBlock13(int32_t offset)
{
    // 4-color block encoding: each 4x4 block is a different color
    uint8_t* pBuffer = GetCurrentFrame() + (intptr_t)offset;
    uint8_t P[2];

    for (int y = 0; y < 8; y++)
    {
        if (!(y & 3))
        {
            P[0] = file.ReadByte();
            P[1] = file.ReadByte();
        }

        memset(pBuffer,     P[0], 4);
        memset(pBuffer + 4, P[1], 4);
        pBuffer += videoStride;
    }
}

void InterplayDecoder::DecodeBlock14(int32_t offset)
{
    // 1-color encoding : the whole block is 1 solid color
    uint8_t* pBuffer = GetCurrentFrame() + (intptr_t)offset;
    uint8_t pix = file.ReadByte();

    for (int y = 0; y < 8; y++)
    {
        memset(pBuffer, pix, 8);
        pBuffer += videoStride;
    }
}

void InterplayDecoder::DecodeBlock15(int32_t offset)
{
    // dithered encoding
    uint8_t* pBuffer = GetCurrentFrame() + (intptr_t)offset;
    uint8_t P[2];

    P[0] = file.ReadByte();
    P[1] = file.ReadByte();

    for (int y = 0; y < 8; y++)
    {
        for (int x = 0; x < 8; x += 2)
        {
            *pBuffer++ = P[y & 1];
            *pBuffer++ = P[!(y & 1)];
        }
        pBuffer += (videoStride - 8);
    }
}

uint8_t* InterplayDecoder::GetCurrentFrame()
{
    return pVideoBuffers[nCurrentVideoBuffer];
}

uint8_t* InterplayDecoder::GetPreviousFrame()
{
    return pVideoBuffers[nPreviousVideoBuffer];
}


code:
/*
 * InterplayDecoder
 * Copyright (C) 2020 sirlemonhead
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

/* This code is based on interplayvideo.c, dpcm.c and ipmovie.c from the FFmpeg project which can be obtained
 * from http://www.ffmpeg.org/. Below is the license from interplayvideo.c
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/*
 * Interplay MVE Video Decoder
 * Copyright (C) 2003 The FFmpeg project
 *
 * This file is part of FFmpeg.
 *
 * FFmpeg is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * FFmpeg 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#pragma once

#ifndef playmve_h_
#define playmve_h_

#include "a.h"
#include "baselayer.h"
#include "build.h"
#include "cache1d.h"
#include "compat.h"
#include "fx_man.h"
#include "keyboard.h"
#include "pragmas.h"

bool playmve(const char* filename);

#define kMVETile    (MAXTILES-1)
#define kMVEPal     5

#define CHUNK_PREAMBLE_SIZE 4
#define OPCODE_PREAMBLE_SIZE 4

#define CHUNK_INIT_AUDIO   0x0000
#define CHUNK_AUDIO_ONLY   0x0001
#define CHUNK_INIT_VIDEO   0x0002
#define CHUNK_VIDEO        0x0003
#define CHUNK_SHUTDOWN     0x0004
#define CHUNK_END          0x0005
/* these last types are used internally */
#define CHUNK_DONE         0xFFFC
#define CHUNK_NOMEM        0xFFFD
#define CHUNK_EOF          0xFFFE
#define CHUNK_BAD          0xFFFF

#define OPCODE_END_OF_STREAM           0x00
#define OPCODE_END_OF_CHUNK            0x01
#define OPCODE_CREATE_TIMER            0x02
#define OPCODE_INIT_AUDIO_BUFFERS      0x03
#define OPCODE_START_STOP_AUDIO        0x04
#define OPCODE_INIT_VIDEO_BUFFERS      0x05
#define OPCODE_UNKNOWN_06              0x06
#define OPCODE_SEND_BUFFER             0x07
#define OPCODE_AUDIO_FRAME             0x08
#define OPCODE_SILENCE_FRAME           0x09
#define OPCODE_INIT_VIDEO_MODE         0x0A
#define OPCODE_CREATE_GRADIENT         0x0B
#define OPCODE_SET_PALETTE             0x0C
#define OPCODE_SET_PALETTE_COMPRESSED  0x0D
#define OPCODE_UNKNOWN_0E              0x0E
#define OPCODE_SET_DECODING_MAP        0x0F
#define OPCODE_UNKNOWN_10              0x10
#define OPCODE_VIDEO_DATA              0x11
#define OPCODE_UNKNOWN_12              0x12
#define OPCODE_UNKNOWN_13              0x13
#define OPCODE_UNKNOWN_14              0x14
#define OPCODE_UNKNOWN_15              0x15

#define PALETTE_COUNT 256

#endif