/* Emacs style mode select -*- C++ -*- *----------------------------------------------------------------------------- * * * PrBoom a Doom port merged with LxDoom and LSDLDoom * based on BOOM, a modified and improved DOOM engine * Copyright (C) 1999 by * id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman * Copyright (C) 1999-2000 by * Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze * * This program 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 2 * of the License, or (at your option) any later version. * * 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., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * DESCRIPTION: * Intermission screens. * *----------------------------------------------------------------------------- */ #include "doomstat.h" #include "m_random.h" #include "w_wad.h" #include "g_game.h" #include "r_main.h" #include "v_video.h" #include "wi_stuff.h" #include "s_sound.h" #include "sounds.h" #include "m_swap.h" #include "r_draw.h" // // Data needed to add patches to full screen intermission pics. // Patches are statistics messages, and animations. // Loads of by-pixel layout and placement, offsets etc. // // // Different vetween registered DOOM (1994) and // Ultimate DOOM - Final edition (retail, 1995?). // This is supposedly ignored for commercial // release (aka DOOM II), which had 34 maps // in one episode. So there. #define NUMEPISODES 4 #define NUMMAPS 9 // Not used // in tics //U #define PAUSELEN (TICRATE*2) //U #define SCORESTEP 100 //U #define ANIMPERIOD 32 // pixel distance from "(YOU)" to "PLAYER N" //U #define STARDIST 10 //U #define WK 1 // GLOBAL LOCATIONS #define WI_TITLEY 2 #define WI_SPACINGY 33 // SINGLE-PLAYER STUFF #define SP_STATSX 50 #define SP_STATSY 50 #define SP_TIMEX 8 // proff/nicolas 09/20/98 -- changed for hi-res #define SP_TIMEY 160 //#define SP_TIMEY (SCREENHEIGHT-32) // NET GAME STUFF #define NG_STATSY 50 #define NG_STATSX (32 + V_NamePatchWidth(star)/2 + 32*!dofrags) #define NG_SPACINGX 64 // Used to display the frags matrix at endgame // DEATHMATCH STUFF #define DM_MATRIXX 42 #define DM_MATRIXY 68 #define DM_SPACINGX 40 #define DM_TOTALSX 269 #define DM_KILLERSX 10 #define DM_KILLERSY 100 #define DM_VICTIMSX 5 #define DM_VICTIMSY 50 // These animation variables, structures, etc. are used for the // DOOM/Ultimate DOOM intermission screen animations. This is // totally different from any sprite or texture/flat animations typedef enum { ANIM_ALWAYS, // determined by patch entry ANIM_RANDOM, // occasional ANIM_LEVEL // continuous } animenum_t; typedef struct { int x; // x/y coordinate pair structure int y; } point_t; // // Animation. // There is another anim_t used in p_spec. // typedef struct { animenum_t type; // period in tics between animations int period; // number of animation frames int nanims; // location of animation point_t loc; // ALWAYS: n/a, // RANDOM: period deviation (<256), // LEVEL: level int data1; // ALWAYS: n/a, // RANDOM: random base period, // LEVEL: n/a int data2; /* actual graphics for frames of animations * cphipps - const */ const patch_t* p[3]; // following must be initialized to zero before use! // next value of bcnt (used in conjunction with period) int nexttic; // last drawn animation frame int lastdrawn; // next frame number to animate int ctr; // used by RANDOM and LEVEL when animating int state; } anim_t; static point_t lnodes[NUMEPISODES][NUMMAPS] = { // Episode 0 World Map { { 185, 164 }, // location of level 0 (CJ) { 148, 143 }, // location of level 1 (CJ) { 69, 122 }, // location of level 2 (CJ) { 209, 102 }, // location of level 3 (CJ) { 116, 89 }, // location of level 4 (CJ) { 166, 55 }, // location of level 5 (CJ) { 71, 56 }, // location of level 6 (CJ) { 135, 29 }, // location of level 7 (CJ) { 71, 24 } // location of level 8 (CJ) }, // Episode 1 World Map should go here { { 254, 25 }, // location of level 0 (CJ) { 97, 50 }, // location of level 1 (CJ) { 188, 64 }, // location of level 2 (CJ) { 128, 78 }, // location of level 3 (CJ) { 214, 92 }, // location of level 4 (CJ) { 133, 130 }, // location of level 5 (CJ) { 208, 136 }, // location of level 6 (CJ) { 148, 140 }, // location of level 7 (CJ) { 235, 158 } // location of level 8 (CJ) }, // Episode 2 World Map should go here { { 156, 168 }, // location of level 0 (CJ) { 48, 154 }, // location of level 1 (CJ) { 174, 95 }, // location of level 2 (CJ) { 265, 75 }, // location of level 3 (CJ) { 130, 48 }, // location of level 4 (CJ) { 279, 23 }, // location of level 5 (CJ) { 198, 48 }, // location of level 6 (CJ) { 140, 25 }, // location of level 7 (CJ) { 281, 136 } // location of level 8 (CJ) } }; // // Animation locations for episode 0 (1). // Using patches saves a lot of space, // as they replace 320x200 full screen frames. // static anim_t epsd0animinfo[] = { { ANIM_ALWAYS, TICRATE/3, 3, { 224, 104 }, 0, 0, { 0, 0, 0 }, 0, 0, 0, 0 }, { ANIM_ALWAYS, TICRATE/3, 3, { 184, 160 }, 0, 0, { 0, 0, 0 }, 0, 0, 0, 0 }, { ANIM_ALWAYS, TICRATE/3, 3, { 112, 136 }, 0, 0, { 0, 0, 0 }, 0, 0, 0, 0 }, { ANIM_ALWAYS, TICRATE/3, 3, { 72, 112 }, 0, 0, { 0, 0, 0 }, 0, 0, 0, 0 }, { ANIM_ALWAYS, TICRATE/3, 3, { 88, 96 }, 0, 0, { 0, 0, 0 }, 0, 0, 0, 0 }, { ANIM_ALWAYS, TICRATE/3, 3, { 64, 48 }, 0, 0, { 0, 0, 0 }, 0, 0, 0, 0 }, { ANIM_ALWAYS, TICRATE/3, 3, { 192, 40 }, 0, 0, { 0, 0, 0 }, 0, 0, 0, 0 }, { ANIM_ALWAYS, TICRATE/3, 3, { 136, 16 }, 0, 0, { 0, 0, 0 }, 0, 0, 0, 0 }, { ANIM_ALWAYS, TICRATE/3, 3, { 80, 16 }, 0, 0, { 0, 0, 0 }, 0, 0, 0, 0 }, { ANIM_ALWAYS, TICRATE/3, 3, { 64, 24 }, 0, 0, { 0, 0, 0 }, 0, 0, 0, 0 } }; static anim_t epsd1animinfo[] = { { ANIM_LEVEL, TICRATE/3, 1, { 128, 136 }, 1, 0, { 0, 0, 0 }, 0, 0, 0, 0 }, { ANIM_LEVEL, TICRATE/3, 1, { 128, 136 }, 2, 0, { 0, 0, 0 }, 0, 0, 0, 0 }, { ANIM_LEVEL, TICRATE/3, 1, { 128, 136 }, 3, 0, { 0, 0, 0 }, 0, 0, 0, 0 }, { ANIM_LEVEL, TICRATE/3, 1, { 128, 136 }, 4, 0, { 0, 0, 0 }, 0, 0, 0, 0 }, { ANIM_LEVEL, TICRATE/3, 1, { 128, 136 }, 5, 0, { 0, 0, 0 }, 0, 0, 0, 0 }, { ANIM_LEVEL, TICRATE/3, 1, { 128, 136 }, 6, 0, { 0, 0, 0 }, 0, 0, 0, 0 }, { ANIM_LEVEL, TICRATE/3, 1, { 128, 136 }, 7, 0, { 0, 0, 0 }, 0, 0, 0, 0 }, { ANIM_LEVEL, TICRATE/3, 3, { 192, 144 }, 8, 0, { 0, 0, 0 }, 0, 0, 0, 0 }, { ANIM_LEVEL, TICRATE/3, 1, { 128, 136 }, 8, 0, { 0, 0, 0 }, 0, 0, 0, 0 } }; static anim_t epsd2animinfo[] = { { ANIM_ALWAYS, TICRATE/3, 3, { 104, 168 }, 0, 0, { 0, 0, 0 }, 0, 0, 0, 0 }, { ANIM_ALWAYS, TICRATE/3, 3, { 40, 136 }, 0, 0, { 0, 0, 0 }, 0, 0, 0, 0 }, { ANIM_ALWAYS, TICRATE/3, 3, { 160, 96 }, 0, 0, { 0, 0, 0 }, 0, 0, 0, 0 }, { ANIM_ALWAYS, TICRATE/3, 3, { 104, 80 }, 0, 0, { 0, 0, 0 }, 0, 0, 0, 0 }, { ANIM_ALWAYS, TICRATE/3, 3, { 120, 32 }, 0, 0, { 0, 0, 0 }, 0, 0, 0, 0 }, { ANIM_ALWAYS, TICRATE/4, 3, { 40, 0 }, 0, 0, { 0, 0, 0 }, 0, 0, 0, 0 } }; static int NUMANIMS[NUMEPISODES] = { sizeof(epsd0animinfo)/sizeof(anim_t), sizeof(epsd1animinfo)/sizeof(anim_t), sizeof(epsd2animinfo)/sizeof(anim_t) }; static anim_t *anims[NUMEPISODES] = { epsd0animinfo, epsd1animinfo, epsd2animinfo }; // // GENERAL DATA // // // Locally used stuff. // #define FB 0 // States for single-player #define SP_KILLS 0 #define SP_ITEMS 2 #define SP_SECRET 4 #define SP_FRAGS 6 #define SP_TIME 8 #define SP_PAR ST_TIME #define SP_PAUSE 1 // in seconds #define SHOWNEXTLOCDELAY 4 //#define SHOWLASTLOCDELAY SHOWNEXTLOCDELAY // used to accelerate or skip a stage int acceleratestage; // killough 3/28/98: made global // wbs->pnum static int me; // specifies current state static stateenum_t state; // contains information passed into intermission static wbstartstruct_t* wbs; static wbplayerstruct_t* plrs; // wbs->plyr[] // used for general timing static int cnt; // used for timing of background animation static int bcnt; // signals to refresh everything for one frame static int firstrefresh; static int cnt_time; static int cnt_total_time; static int cnt_par; static int cnt_pause; // // GRAPHICS // // You Are Here graphic static const char* yah[2] = { "WIURH0", "WIURH1" }; // splat static const char* splat = "WISPLAT"; // %, : graphics static const char percent[] = {"WIPCNT"}; static const char colon[] = {"WICOLON"}; // 0-9 graphic static const patch_t * num[10]; // minus sign static const char wiminus[] = {"WIMINUS"}; // "Finished!" graphics static const char finished[] = {"WIF"}; // "Entering" graphic static const char entering[] = {"WIENTER"}; // "secret" static const char sp_secret[] = {"WISCRT2"}; // "Kills", "Scrt", "Items", "Frags" static const char kills[] = {"WIOSTK"}; static const char secret[] = {"WIOSTS"}; static const char items[] = {"WIOSTI"}; static const char frags[] = {"WIFRGS"}; // Time sucks. static const char time1[] = {"WITIME"}; static const char par[] = {"WIPAR"}; static const char sucks[] = {"WISUCKS"}; // "killers", "victims" static const char killers[] = {"WIKILRS"}; static const char victims[] = {"WIVCTMS"}; // "Total", your face, your dead face static const char total[] = {"WIMSTT"}; static const char star[] = {"STFST01"}; static const char bstar[] = {"STFDEAD0"}; // "red P[1..MAXPLAYERS]" static const char facebackp[] = {"STPB0"}; // // CODE // static void WI_endDeathmatchStats(void); static void WI_endNetgameStats(void); void WI_unloadData(void); #define WI_endStats WI_endNetgameStats /* ==================================================================== * WI_levelNameLump * Purpore: Returns the name of the graphic lump containing the name of * the given level. * Args: Episode and level, and buffer (must by 9 chars) to write to * Returns: void */ void WI_levelNameLump(int epis, int map, char* buf, int bsize) { if (gamemode == commercial) { snprintf(buf, bsize,"CWILV%s%d",(map/10>0?"":"0"), map); //ANOTHER ROCKHACK "CWILV%2.2d" //snprintf(buf,bsize, "CWILV%2.2d", map); } else { snprintf(buf,bsize, "WILV%d%d", epis, map); } } // ==================================================================== // WI_slamBackground // Purpose: Put the full-screen background up prior to patches // Args: none // Returns: void // static void WI_slamBackground(void) { char name[9]; // limited to 8 characters if (gamemode == commercial || (gamemode == retail && wbs->epsd == 3)) strcpy(name, "INTERPIC"); else snprintf(name, sizeof(name), "WIMAP%d", wbs->epsd); // background V_DrawNamePatch(0, 0, FB, name, CR_DEFAULT, VPT_STRETCH); } // ==================================================================== // WI_Responder // Purpose: Draw animations on intermission background screen // Args: ev -- event pointer, not actually used here. // Returns: False -- dummy routine // // The ticker is used to detect keys // because of timing issues in netgames. boolean WI_Responder(event_t* ev) { (void)ev; return false; } // ==================================================================== // WI_drawLF // Purpose: Draw the "Finished" level name before showing stats // Args: none // Returns: void // void WI_drawLF(void) { int y = WI_TITLEY; char lname[9]; // draw /* cph - get the graphic lump name and use it */ WI_levelNameLump(wbs->epsd, wbs->last, lname, sizeof(lname)); // CPhipps - patch drawing updated V_DrawNamePatch((320 - V_NamePatchWidth(lname))/2, y, FB, lname, CR_DEFAULT, VPT_STRETCH); // draw "Finished!" y += (5*V_NamePatchHeight(lname))/4; // CPhipps - patch drawing updated V_DrawNamePatch((320 - V_NamePatchWidth(finished))/2, y, FB, finished, CR_DEFAULT, VPT_STRETCH); } // ==================================================================== // WI_drawEL // Purpose: Draw introductory "Entering" and level name // Args: none // Returns: void // void WI_drawEL(void) { int y = WI_TITLEY; char lname[9]; /* cph - get the graphic lump name */ WI_levelNameLump(wbs->epsd, wbs->next, lname, sizeof(lname)); // draw "Entering" // CPhipps - patch drawing updated V_DrawNamePatch((320 - V_NamePatchWidth(entering))/2, y, FB, entering, CR_DEFAULT, VPT_STRETCH); // draw level y += (5*V_NamePatchHeight(lname))/4; // CPhipps - patch drawing updated V_DrawNamePatch((320 - V_NamePatchWidth(lname))/2, y, FB, lname, CR_DEFAULT, VPT_STRETCH); } /* ==================================================================== * WI_drawOnLnode * Purpose: Draw patches at a location based on episode/map * Args: n -- index to map# within episode * c[] -- array of names of patches to be drawn * Returns: void */ void WI_drawOnLnode // draw stuff at a location by episode/map# ( int n, const char* const c[] ) { int i; boolean fits = false; i = 0; do { int left; int top; int right; int bottom; int lump = W_GetNumForName(c[i]); const patch_t* p = W_CacheLumpNum(lump); left = lnodes[wbs->epsd][n].x - SHORT(p->leftoffset); top = lnodes[wbs->epsd][n].y - SHORT(p->topoffset); right = left + SHORT(p->width); bottom = top + SHORT(p->height); W_UnlockLumpNum(lump); if (left >= 0 && right < 320 && top >= 0 && bottom < 200) { fits = true; } else { i++; } } while (!fits && i!=2); if (fits && i<2) { // CPhipps - patch drawing updated V_DrawNamePatch(lnodes[wbs->epsd][n].x, lnodes[wbs->epsd][n].y, FB, c[i], CR_DEFAULT, VPT_STRETCH); } else { // DEBUG //jff 8/3/98 use logical output routine printf("Could not place patch on level %d", n+1); } } // ==================================================================== // WI_initAnimatedBack // Purpose: Initialize pointers and styles for background animation // Args: none // Returns: void // void WI_initAnimatedBack(void) { int i; anim_t* a; if (gamemode == commercial) // no animation for DOOM2 return; if (wbs->epsd > 2) return; for (i=0;iepsd];i++) { a = &anims[wbs->epsd][i]; // init variables a->ctr = -1; // specify the next time to draw it if (a->type == ANIM_ALWAYS) a->nexttic = bcnt + 1 + (M_Random()%a->period); else if (a->type == ANIM_RANDOM) a->nexttic = bcnt + 1 + a->data2+(M_Random()%a->data1); else if (a->type == ANIM_LEVEL) a->nexttic = bcnt + 1; } } // ==================================================================== // WI_updateAnimatedBack // Purpose: Figure out what animation we do on this iteration // Args: none // Returns: void // void WI_updateAnimatedBack(void) { int i; anim_t* a; if (gamemode == commercial) return; if (wbs->epsd > 2) return; for (i=0;iepsd];i++) { a = &anims[wbs->epsd][i]; if (bcnt == a->nexttic) { switch (a->type) { case ANIM_ALWAYS: if (++a->ctr >= a->nanims) a->ctr = 0; a->nexttic = bcnt + a->period; break; case ANIM_RANDOM: a->ctr++; if (a->ctr == a->nanims) { a->ctr = -1; a->nexttic = bcnt+a->data2+(M_Random()%a->data1); } else a->nexttic = bcnt + a->period; break; case ANIM_LEVEL: // gawd-awful hack for level anims if (!(state == StatCount && i == 7) && wbs->next == a->data1) { a->ctr++; if (a->ctr == a->nanims) a->ctr--; a->nexttic = bcnt + a->period; } break; } } } } // ==================================================================== // WI_drawAnimatedBack // Purpose: Actually do the animation (whew!) // Args: none // Returns: void // void WI_drawAnimatedBack(void) { int i; anim_t* a; if (gamemode==commercial) //jff 4/25/98 Someone forgot commercial an enum return; if (wbs->epsd > 2) return; for (i=0 ; iepsd] ; i++) { a = &anims[wbs->epsd][i]; if (a->ctr >= 0) // CPhipps - patch drawing updated V_DrawMemPatch(a->loc.x, a->loc.y, FB, a->p[a->ctr], CR_DEFAULT, VPT_STRETCH); } } // ==================================================================== // WI_drawNum // Purpose: Draws a number. If digits > 0, then use that many digits // minimum, otherwise only use as many as necessary // Args: x, y -- location // n -- the number to be drawn // digits -- number of digits minimum or zero // Returns: new x position after drawing (note we are going to the left) // CPhipps - static static int WI_drawNum (int x, int y, int n, int digits) { int fontwidth = SHORT(num[0]->width); int neg; int temp; if (digits < 0) { if (!n) { // make variable-length zeros 1 digit long digits = 1; } else { // figure out # of digits in # digits = 0; temp = n; while (temp) { temp /= 10; digits++; } } } neg = n < 0; if (neg) n = -n; // if non-number, do not draw it if (n == 1994) return 0; // draw the new number while (digits--) { x -= fontwidth; // CPhipps - patch drawing updated V_DrawMemPatch(x, y, FB, num[ n % 10 ], CR_DEFAULT, VPT_STRETCH); n /= 10; } // draw a minus sign if necessary if (neg) // CPhipps - patch drawing updated V_DrawNamePatch(x-=8, y, FB, wiminus, CR_DEFAULT, VPT_STRETCH); return x; } // ==================================================================== // WI_drawPercent // Purpose: Draws a percentage, really just a call to WI_drawNum // after putting a percent sign out there // Args: x, y -- location // p -- the percentage value to be drawn, no negatives // Returns: void // CPhipps - static static void WI_drawPercent(int x, int y, int p) { if (p < 0) return; // CPhipps - patch drawing updated V_DrawNamePatch(x, y, FB, percent, CR_DEFAULT, VPT_STRETCH); WI_drawNum(x, y, p, -1); } // ==================================================================== // WI_drawTime // Purpose: Draws the level completion time or par time, or "Sucks" // if 1 hour or more // Args: x, y -- location // t -- the time value to be drawn // Returns: void // // CPhipps - static // - largely rewritten to display hours and use slightly better algorithm static void WI_drawTime(int x, int y, int t) { int n; if (t<0) return; if (t < 100*60*60) for(;;) { n = t % 60; t /= 60; x = WI_drawNum(x, y, n, (t || n>9) ? 2 : 1) - V_NamePatchWidth(colon); // draw if (t) // CPhipps - patch drawing updated V_DrawNamePatch(x, y, FB, colon, CR_DEFAULT, VPT_STRETCH); else break; } else // "sucks" (maybe should be "addicted", even I've never had a 100 hour game ;) V_DrawNamePatch(x - V_NamePatchWidth(sucks), y, FB, sucks, CR_DEFAULT, VPT_STRETCH); } // ==================================================================== // WI_End // Purpose: Unloads data structures (inverse of WI_Start) // Args: none // Returns: void // void WI_End(void) { WI_unloadData(); if (deathmatch) WI_endDeathmatchStats(); else if (netgame) WI_endNetgameStats(); else WI_endStats(); } // ==================================================================== // WI_initNoState // Purpose: Clear state, ready for end of level activity // Args: none // Returns: void // void WI_initNoState(void) { state = NoState; acceleratestage = 0; cnt = 10; } // ==================================================================== // WI_drawTimeStats // Purpose: Put the times on the screen // Args: time, total time, par time, in seconds // Returns: void // // cph - pulled from WI_drawStats below static void WI_drawTimeStats(int cnt_time, int cnt_total_time, int cnt_par) { V_DrawNamePatch(SP_TIMEX, SP_TIMEY, FB, time1, CR_DEFAULT, VPT_STRETCH); WI_drawTime(320/2 - SP_TIMEX, SP_TIMEY, cnt_time); V_DrawNamePatch(SP_TIMEX, (SP_TIMEY+200)/2, FB, total, CR_DEFAULT, VPT_STRETCH); WI_drawTime(320/2 - SP_TIMEX, (SP_TIMEY+200)/2, cnt_total_time); // Ty 04/11/98: redid logic: should skip only if with pwad but // without deh patch // killough 2/22/98: skip drawing par times on pwads // Ty 03/17/98: unless pars changed with deh patch if (!(modifiedgame)) //&& !deh_pars)) { if (wbs->epsd < 3) { V_DrawNamePatch(320/2 + SP_TIMEX, SP_TIMEY, FB, par, CR_DEFAULT, VPT_STRETCH); WI_drawTime(320 - SP_TIMEX, SP_TIMEY, cnt_par); } } } // ==================================================================== // WI_updateNoState // Purpose: Cycle until end of level activity is done // Args: none // Returns: void // void WI_updateNoState(void) { WI_updateAnimatedBack(); if (!--cnt) G_WorldDone(); } static boolean snl_pointeron = false; // ==================================================================== // WI_initShowNextLoc // Purpose: Prepare to show the next level's location // Args: none // Returns: void // void WI_initShowNextLoc(void) { if ((gamemode != commercial) && (gamemap == 8)) { G_WorldDone(); return; } state = ShowNextLoc; acceleratestage = 0; cnt = SHOWNEXTLOCDELAY * TICRATE; WI_initAnimatedBack(); } // ==================================================================== // WI_updateShowNextLoc // Purpose: Prepare to show the next level's location // Args: none // Returns: void // void WI_updateShowNextLoc(void) { WI_updateAnimatedBack(); if (!--cnt || acceleratestage) WI_initNoState(); else snl_pointeron = (cnt & 31) < 20; } // ==================================================================== // WI_drawShowNextLoc // Purpose: Show the next level's location on animated backgrounds // Args: none // Returns: void // void WI_drawShowNextLoc(void) { int i; int last; WI_slamBackground(); // draw animated background WI_drawAnimatedBack(); if ( gamemode != commercial) { if (wbs->epsd > 2) { WI_drawEL(); // "Entering..." if not E1 or E2 return; } last = (wbs->last == 8) ? wbs->next - 1 : wbs->last; // draw a splat on taken cities. for (i=0 ; i<=last ; i++) WI_drawOnLnode(i, &splat); // splat the secret level? if (wbs->didsecret) WI_drawOnLnode(8, &splat); // draw flashing ptr if (snl_pointeron) WI_drawOnLnode(wbs->next, yah); } // draws which level you are entering.. if ( (gamemode != commercial) || wbs->next != 30) // check for MAP30 end game WI_drawEL(); } // ==================================================================== // WI_drawNoState // Purpose: Draw the pointer and next location // Args: none // Returns: void // void WI_drawNoState(void) { snl_pointeron = true; WI_drawShowNextLoc(); } // ==================================================================== // WI_fragSum // Purpose: Calculate frags for this player based on the current totals // of all the other players. Subtract self-frags. // Args: playernum -- the player to be calculated // Returns: the total frags for this player // int WI_fragSum(int playernum) { int i; int frags = 0; for (i=0 ; i 999) // Ty 03/17/98 3-digit frag count dm_frags[i][j] = 999; if (dm_frags[i][j] < -999) dm_frags[i][j] = -999; stillticking = true; } } dm_totals[i] = WI_fragSum(i); if (dm_totals[i] > 999) dm_totals[i] = 999; if (dm_totals[i] < -999) dm_totals[i] = -999; // Ty 03/17/98 end 3-digit frag count } } if (!stillticking) { S_StartSound(0, sfx_barexp); dm_state++; } } else if (dm_state == 4) { if (acceleratestage) { S_StartSound(0, sfx_slop); if ( gamemode == commercial) WI_initNoState(); else WI_initShowNextLoc(); } } else if (dm_state & 1) { if (!--cnt_pause) { dm_state++; cnt_pause = TICRATE; } } } // ==================================================================== // WI_drawDeathmatchStats // Purpose: Draw the stats on the screen in a matrix // Args: none // Returns: void // // proff/nicolas 09/20/98 -- changed for hi-res // CPhipps - patch drawing updated void WI_drawDeathmatchStats(void) { int i; int j; int x; int y; int w; int lh; // line height int halfface = V_NamePatchWidth(facebackp)/2; lh = WI_SPACINGY; WI_slamBackground(); // draw animated background WI_drawAnimatedBack(); WI_drawLF(); // draw stat titles (top line) V_DrawNamePatch(DM_TOTALSX-V_NamePatchWidth(total)/2, DM_MATRIXY-WI_SPACINGY+10, FB, total, CR_DEFAULT, VPT_STRETCH); V_DrawNamePatch(DM_KILLERSX, DM_KILLERSY, FB, killers, CR_DEFAULT, VPT_STRETCH); V_DrawNamePatch(DM_VICTIMSX, DM_VICTIMSY, FB, victims, CR_DEFAULT, VPT_STRETCH); // draw P? x = DM_MATRIXX + DM_SPACINGX; y = DM_MATRIXY; for (i=0 ; iwidth); for (i=0 ; imaxkills; cnt_items[i] = (plrs[i].sitems * 100) / wbs->maxitems; // killough 2/22/98: Make secrets = 100% if maxsecret = 0: cnt_secret[i] = wbs->maxsecret ? (plrs[i].ssecret * 100) / wbs->maxsecret : 100; if (dofrags) cnt_frags[i] = WI_fragSum(i); // we had frags } S_StartSound(0, sfx_barexp); // bang ng_state = 10; } if (ng_state == 2) { if (!(bcnt&3)) S_StartSound(0, sfx_pistol); // pop stillticking = false; for (i=0 ; i= (plrs[i].skills * 100) / wbs->maxkills) cnt_kills[i] = (plrs[i].skills * 100) / wbs->maxkills; else stillticking = true; // still got stuff to tally } if (!stillticking) { S_StartSound(0, sfx_barexp); ng_state++; } } else if (ng_state == 4) { if (!(bcnt&3)) S_StartSound(0, sfx_pistol); stillticking = false; for (i=0 ; i= (plrs[i].sitems * 100) / wbs->maxitems) cnt_items[i] = (plrs[i].sitems * 100) / wbs->maxitems; else stillticking = true; } if (!stillticking) { S_StartSound(0, sfx_barexp); ng_state++; } } else if (ng_state == 6) { if (!(bcnt&3)) S_StartSound(0, sfx_pistol); stillticking = false; for (i=0 ; i= (wbs->maxsecret ? (plrs[i].ssecret * 100) / wbs->maxsecret : compatibility_level < lxdoom_1_compatibility ? 0 : 100)) cnt_secret[i] = wbs->maxsecret ? (plrs[i].ssecret * 100) / wbs->maxsecret : 100; else stillticking = true; } if (!stillticking) { S_StartSound(0, sfx_barexp); ng_state += 1 + 2*!dofrags; } } else if (ng_state == 8) { if (!(bcnt&3)) S_StartSound(0, sfx_pistol); stillticking = false; for (i=0 ; i= (fsum = WI_fragSum(i))) cnt_frags[i] = fsum; else stillticking = true; } if (!stillticking) { S_StartSound(0, sfx_pldeth); ng_state++; } } else if (ng_state == 10) { if (acceleratestage) { S_StartSound(0, sfx_sgcock); if ( gamemode == commercial ) WI_initNoState(); else WI_initShowNextLoc(); } } else if (ng_state & 1) { if (!--cnt_pause) { ng_state++; cnt_pause = TICRATE; } } } // ==================================================================== // WI_drawNetgameStats // Purpose: Put the coop stats on the screen // Args: none // Returns: void // // proff/nicolas 09/20/98 -- changed for hi-res // CPhipps - patch drawing updated void WI_drawNetgameStats(void) { int i; int x; int y; int pwidth = V_NamePatchWidth(percent); int fwidth = V_NamePatchWidth(facebackp); WI_slamBackground(); // draw animated background WI_drawAnimatedBack(); WI_drawLF(); // draw stat titles (top line) V_DrawNamePatch(NG_STATSX+NG_SPACINGX-V_NamePatchWidth(kills), NG_STATSY, FB, kills, CR_DEFAULT, VPT_STRETCH); V_DrawNamePatch(NG_STATSX+2*NG_SPACINGX-V_NamePatchWidth(items), NG_STATSY, FB, items, CR_DEFAULT, VPT_STRETCH); V_DrawNamePatch(NG_STATSX+3*NG_SPACINGX-V_NamePatchWidth(secret), NG_STATSY, FB, secret, CR_DEFAULT, VPT_STRETCH); if (dofrags) V_DrawNamePatch(NG_STATSX+4*NG_SPACINGX-V_NamePatchWidth(frags), NG_STATSY, FB, frags, CR_DEFAULT, VPT_STRETCH); // draw stats y = NG_STATSY + V_NamePatchHeight(kills); for (i=0 ; itotaltimes / TICRATE, wbs->partime / TICRATE); } static int sp_state; // ==================================================================== // WI_initStats // Purpose: Get ready for single player stats // Args: none // Returns: void // Comment: Seems like we could do all these stats in a more generic // set of routines that weren't duplicated for dm, coop, sp // void WI_initStats(void) { state = StatCount; acceleratestage = 0; sp_state = 1; // CPhipps - allocate (awful code, I know, but saves changing it all) and initialise *(cnt_kills = malloc(sizeof(*cnt_kills))) = *(cnt_items = malloc(sizeof(*cnt_items))) = *(cnt_secret= malloc(sizeof(*cnt_secret))) = -1; cnt_time = cnt_par = cnt_total_time = -1; cnt_pause = TICRATE; WI_initAnimatedBack(); } // ==================================================================== // WI_updateStats // Purpose: Calculate solo stats // Args: none // Returns: void // void WI_updateStats(void) { WI_updateAnimatedBack(); if (acceleratestage && sp_state != 10) { acceleratestage = 0; cnt_kills[0] = (plrs[me].skills * 100) / wbs->maxkills; cnt_items[0] = (plrs[me].sitems * 100) / wbs->maxitems; // killough 2/22/98: Make secrets = 100% if maxsecret = 0: cnt_secret[0] = (wbs->maxsecret ? (plrs[me].ssecret * 100) / wbs->maxsecret : 100); cnt_total_time = wbs->totaltimes / TICRATE; cnt_time = plrs[me].stime / TICRATE; cnt_par = wbs->partime / TICRATE; S_StartSound(0, sfx_barexp); sp_state = 10; } if (sp_state == 2) { cnt_kills[0] += 2; if (!(bcnt&3)) S_StartSound(0, sfx_pistol); if (cnt_kills[0] >= (plrs[me].skills * 100) / wbs->maxkills) { cnt_kills[0] = (plrs[me].skills * 100) / wbs->maxkills; S_StartSound(0, sfx_barexp); sp_state++; } } else if (sp_state == 4) { cnt_items[0] += 2; if (!(bcnt&3)) S_StartSound(0, sfx_pistol); if (cnt_items[0] >= (plrs[me].sitems * 100) / wbs->maxitems) { cnt_items[0] = (plrs[me].sitems * 100) / wbs->maxitems; S_StartSound(0, sfx_barexp); sp_state++; } } else if (sp_state == 6) { cnt_secret[0] += 2; if (!(bcnt&3)) S_StartSound(0, sfx_pistol); // killough 2/22/98: Make secrets = 100% if maxsecret = 0: if ((!wbs->maxsecret && compatibility_level < lxdoom_1_compatibility) || cnt_secret[0] >= (wbs->maxsecret ? (plrs[me].ssecret * 100) / wbs->maxsecret : 100)) { cnt_secret[0] = (wbs->maxsecret ? (plrs[me].ssecret * 100) / wbs->maxsecret : 100); S_StartSound(0, sfx_barexp); sp_state++; } } else if (sp_state == 8) { if (!(bcnt&3)) S_StartSound(0, sfx_pistol); cnt_time += 3; if (cnt_time >= plrs[me].stime / TICRATE) cnt_time = plrs[me].stime / TICRATE; cnt_total_time += 3; if (cnt_total_time >= wbs->totaltimes / TICRATE) cnt_total_time = wbs->totaltimes / TICRATE; cnt_par += 3; if (cnt_par >= wbs->partime / TICRATE) { cnt_par = wbs->partime / TICRATE; if ((cnt_time >= plrs[me].stime / TICRATE) && (compatibility_level < lxdoom_1_compatibility || cnt_total_time >= wbs->totaltimes / TICRATE)) { S_StartSound(0, sfx_barexp); sp_state++; } } } else if (sp_state == 10) { if (acceleratestage) { S_StartSound(0, sfx_sgcock); if (gamemode == commercial) WI_initNoState(); else WI_initShowNextLoc(); } } else if (sp_state & 1) { if (!--cnt_pause) { sp_state++; cnt_pause = TICRATE; } } } // ==================================================================== // WI_drawStats // Purpose: Put the solo stats on the screen // Args: none // Returns: void // // proff/nicolas 09/20/98 -- changed for hi-res // CPhipps - patch drawing updated void WI_drawStats(void) { // line height int lh; lh = (3*SHORT(num[0]->height))/2; WI_slamBackground(); // draw animated background WI_drawAnimatedBack(); WI_drawLF(); V_DrawNamePatch(SP_STATSX, SP_STATSY, FB, kills, CR_DEFAULT, VPT_STRETCH); WI_drawPercent(320 - SP_STATSX, SP_STATSY, cnt_kills[0]); V_DrawNamePatch(SP_STATSX, SP_STATSY+lh, FB, items, CR_DEFAULT, VPT_STRETCH); WI_drawPercent(320 - SP_STATSX, SP_STATSY+lh, cnt_items[0]); V_DrawNamePatch(SP_STATSX, SP_STATSY+2*lh, FB, sp_secret, CR_DEFAULT, VPT_STRETCH); WI_drawPercent(320 - SP_STATSX, SP_STATSY+2*lh, cnt_secret[0]); WI_drawTimeStats(cnt_time, cnt_total_time, cnt_par); } // ==================================================================== // WI_checkForAccelerate // Purpose: See if the player has hit either the attack or use key // or mouse button. If so we set acceleratestage to 1 and // all those display routines above jump right to the end. // Args: none // Returns: void // void WI_checkForAccelerate(void) { int i; player_t *player; // check for button presses to skip delays for (i=0, player = players ; icmd.buttons & BT_ATTACK) { if (!player->attackdown) acceleratestage = 1; player->attackdown = true; } else player->attackdown = false; if (player->cmd.buttons & BT_USE) { if (!player->usedown) acceleratestage = 1; player->usedown = true; } else player->usedown = false; } } } // ==================================================================== // WI_Ticker // Purpose: Do various updates every gametic, for stats, animation, // checking that intermission music is running, etc. // Args: none // Returns: void // void WI_Ticker(void) { // counter for general background animation bcnt++; if (bcnt == 1) { // intermission music if ( gamemode == commercial ) S_ChangeMusic(mus_dm2int, true); else S_ChangeMusic(mus_inter, true); } WI_checkForAccelerate(); switch (state) { case StatCount: if (deathmatch) WI_updateDeathmatchStats(); else if (netgame) WI_updateNetgameStats(); else WI_updateStats(); break; case ShowNextLoc: WI_updateShowNextLoc(); break; case NoState: WI_updateNoState(); break; } } /* ==================================================================== * WI_loadData * Purpose: Initialize intermission data such as background graphics, * patches, map names, etc. * Args: none * Returns: void * * CPhipps - modified for new wad lump handling. * - no longer preload most graphics, other funcs can use * them by name */ void WI_loadData(void) { int i; int j; char name[9]; // limited to 8 characters anim_t* a; if (gamemode != commercial) { if (wbs->epsd < 3) { for (j=0;jepsd];j++) { a = &anims[wbs->epsd][j]; for (i=0;inanims;i++) { // MONDO HACK! if (wbs->epsd != 1 || j != 8) { // animations snprintf(name, sizeof(name),"WIA%d%s%d%s%d", wbs->epsd, (j/10>0?"":"0"), j,(i/10>0?"":"0"), i); //ANOTHER ROCKHACK //snprintf(name, sizeof(name),"WIA%d%.2d%.2d", wbs->epsd, j, i); a->p[i] = W_CacheLumpName(name); } else { // HACK ALERT! a->p[i] = anims[1][4].p[i]; } } } } } for (i=0;i<10;i++) { // numbers 0-9 snprintf(name,sizeof(name),"WINUM%d", i); num[i] = W_CacheLumpName(name); } } // ==================================================================== // WI_unloadData // Purpose: Free up the space allocated during WI_loadData // Args: none // Returns: void // // CPhipps - reverse of WI_loadData, goes through the same lumps, but unlocking void WI_unloadData(void) { int i,j; char name[9]; // limited to 8 characters // cph - unlock gamemode dependent stuff here if (gamemode != commercial) { if (wbs->epsd < 3) { for (j=0;jepsd];j++) { anim_t* a = &anims[wbs->epsd][j]; for (i=0; inanims; i++) { // MONDO HACK! if (wbs->epsd != 1 || j != 8) { // animations snprintf(name, sizeof(name),"WIA%d%s%d%s%d", wbs->epsd, (j/10>0?"":"0"), j,(i/10>0?"":"0"), i); //ANOTHER ROCKHACK //snprintf(name,sizeof(name), "WIA%d%.2d%.2d", wbs->epsd, j, i); W_UnlockLumpName(name); } } } } } for (i=0;i<10;i++) { // numbers 0-9 snprintf(name, sizeof(name),"WINUM%d", i); W_UnlockLumpName(name); } } // ==================================================================== // WI_Drawer // Purpose: Call the appropriate stats drawing routine depending on // what kind of game is being played (DM, coop, solo) // Args: none // Returns: void // void WI_Drawer (void) { switch (state) { case StatCount: if (deathmatch) WI_drawDeathmatchStats(); else if (netgame) WI_drawNetgameStats(); else WI_drawStats(); break; case ShowNextLoc: WI_drawShowNextLoc(); break; case NoState: WI_drawNoState(); break; } } // ==================================================================== // WI_initVariables // Purpose: Initialize the intermission information structure // Note: wbstartstruct_t is defined in d_player.h // Args: wbstartstruct -- pointer to the structure with the data // Returns: void // void WI_initVariables(wbstartstruct_t* wbstartstruct) { wbs = wbstartstruct; #ifdef RANGECHECKING if (gamemode != commercial) { if ( gamemode == retail ) RNGCHECK(wbs->epsd, 0, 3); else RNGCHECK(wbs->epsd, 0, 2); } else { RNGCHECK(wbs->last, 0, 8); RNGCHECK(wbs->next, 0, 8); } RNGCHECK(wbs->pnum, 0, MAXPLAYERS); RNGCHECK(wbs->pnum, 0, MAXPLAYERS); #endif acceleratestage = 0; cnt = bcnt = 0; firstrefresh = 1; me = wbs->pnum; plrs = wbs->plyr; if (!wbs->maxkills) wbs->maxkills = 1; // probably only useful in MAP30 if (!wbs->maxitems) wbs->maxitems = 1; if ( gamemode != retail ) if (wbs->epsd > 2) wbs->epsd -= 3; } // ==================================================================== // WI_Start // Purpose: Call the various init routines // Note: wbstartstruct_t is defined in d_player.h // Args: wbstartstruct -- pointer to the structure with the // intermission data // Returns: void // void WI_Start(wbstartstruct_t* wbstartstruct) { WI_initVariables(wbstartstruct); WI_loadData(); if (deathmatch) WI_initDeathmatchStats(); else if (netgame) WI_initNetgameStats(); else WI_initStats(); } id='n1374' href='#n1374'>1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493
/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2007 Nicolas Pennequin
 *
 * This program 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 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

#include "config.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "buffering.h"

#include "storage.h"
#include "system.h"
#include "thread.h"
#include "file.h"
#include "panic.h"
#include "memory.h"
#include "lcd.h"
#include "font.h"
#include "button.h"
#include "kernel.h"
#include "tree.h"
#include "debug.h"
#include "sprintf.h"
#include "settings.h"
#include "codecs.h"
#include "audio.h"
#include "mp3_playback.h"
#include "usb.h"
#include "status.h"
#include "screens.h"
#include "playlist.h"
#include "pcmbuf.h"
#include "buffer.h"
#include "bmp.h"
#include "appevents.h"
#include "metadata.h"
#ifdef HAVE_ALBUMART
#include "albumart.h"
#endif

#define GUARD_BUFSIZE   (32*1024)

/* Define LOGF_ENABLE to enable logf output in this file */
/*#define LOGF_ENABLE*/
#include "logf.h"

/* macros to enable logf for queues
   logging on SYS_TIMEOUT can be disabled */
#ifdef SIMULATOR
/* Define this for logf output of all queuing except SYS_TIMEOUT */
#define BUFFERING_LOGQUEUES
/* Define this to logf SYS_TIMEOUT messages */
/* #define BUFFERING_LOGQUEUES_SYS_TIMEOUT */
#endif

#ifdef BUFFERING_LOGQUEUES
#define LOGFQUEUE logf
#else
#define LOGFQUEUE(...)
#endif

#ifdef BUFFERING_LOGQUEUES_SYS_TIMEOUT
#define LOGFQUEUE_SYS_TIMEOUT logf
#else
#define LOGFQUEUE_SYS_TIMEOUT(...)
#endif

/* default point to start buffer refill */
#define BUFFERING_DEFAULT_WATERMARK      (1024*128)
/* amount of data to read in one read() call */
#define BUFFERING_DEFAULT_FILECHUNK      (1024*32)

#define BUF_HANDLE_MASK                  0x7FFFFFFF


/* Ring buffer helper macros */
/* Buffer pointer (p) plus value (v), wrapped if necessary */
#define RINGBUF_ADD(p,v) (((p)+(v))<buffer_len ? (p)+(v) : (p)+(v)-buffer_len)
/* Buffer pointer (p) minus value (v), wrapped if necessary */
#define RINGBUF_SUB(p,v) ((p>=v) ? (p)-(v) : (p)+buffer_len-(v))
/* How far value (v) plus buffer pointer (p1) will cross buffer pointer (p2) */
#define RINGBUF_ADD_CROSS(p1,v,p2) \
((p1<p2) ? (int)((p1)+(v))-(int)(p2) : (int)((p1)+(v)-(p2))-(int)buffer_len)
/* Bytes available in the buffer */
#define BUF_USED RINGBUF_SUB(buf_widx, buf_ridx)

/* assert(sizeof(struct memory_handle)%4==0) */
struct memory_handle {
    int id;                    /* A unique ID for the handle */
    enum data_type type;       /* Type of data buffered with this handle */
    char path[MAX_PATH];       /* Path if data originated in a file */
    int fd;                    /* File descriptor to path (-1 if closed) */
    size_t data;               /* Start index of the handle's data buffer */
    volatile size_t ridx;      /* Read pointer, relative to the main buffer */
    size_t widx;               /* Write pointer */
    size_t filesize;           /* File total length */
    size_t filerem;            /* Remaining bytes of file NOT in buffer */
    volatile size_t available; /* Available bytes to read from buffer */
    size_t offset;             /* Offset at which we started reading the file */
    struct memory_handle *next;
};
/* invariant: filesize == offset + available + filerem */

static char *buffer;
static char *guard_buffer;

static size_t buffer_len;

static volatile size_t buf_widx;  /* current writing position */
static volatile size_t buf_ridx;  /* current reading position */
/* buf_*idx are values relative to the buffer, not real pointers. */

/* Configuration */
static size_t conf_watermark = 0; /* Level to trigger filebuf fill */
#if MEM > 8
static size_t high_watermark = 0; /* High watermark for rebuffer */
#endif

/* current memory handle in the linked list. NULL when the list is empty. */
static struct memory_handle *cur_handle;
/* first memory handle in the linked list. NULL when the list is empty. */
static struct memory_handle *first_handle;

static int num_handles;  /* number of handles in the list */

static int base_handle_id;

static struct mutex llist_mutex;

/* Handle cache (makes find_handle faster).
   This is global so that move_handle and rm_handle can invalidate it. */
static struct memory_handle *cached_handle = NULL;

static struct {
    size_t remaining;   /* Amount of data needing to be buffered */
    size_t wasted;      /* Amount of space available for freeing */
    size_t buffered;    /* Amount of data currently in the buffer */
    size_t useful;      /* Amount of data still useful to the user */
} data_counters;


/* Messages available to communicate with the buffering thread */
enum {
    Q_BUFFER_HANDLE = 1, /* Request buffering of a handle, this should not be
                            used in a low buffer situation. */
    Q_RESET_HANDLE,      /* (internal) Request resetting of a handle to its
                            offset (the offset has to be set beforehand) */
    Q_CLOSE_HANDLE,      /* Request closing a handle */
    Q_BASE_HANDLE,       /* Set the reference handle for buf_useful_data */

    /* Configuration: */
    Q_START_FILL,        /* Request that the buffering thread initiate a buffer
                            fill at its earliest convenience */
    Q_HANDLE_ADDED,      /* Inform the buffering thread that a handle was added,
                            (which means the disk is spinning) */
};

/* Buffering thread */
static void buffering_thread(void);
static long buffering_stack[(DEFAULT_STACK_SIZE + 0x2000)/sizeof(long)];
static const char buffering_thread_name[] = "buffering";
static unsigned int buffering_thread_id = 0;
static struct event_queue buffering_queue;
static struct queue_sender_list buffering_queue_sender_list;



/*
LINKED LIST MANAGEMENT
======================

add_handle  : Add a handle to the list
rm_handle   : Remove a handle from the list
find_handle : Get a handle pointer from an ID
move_handle : Move a handle in the buffer (with or without its data)

These functions only handle the linked list structure. They don't touch the
contents of the struct memory_handle headers. They also change the buf_*idx
pointers when necessary and manage the handle IDs.

The first and current (== last) handle are kept track of.
A new handle is added at buf_widx and becomes the current one.
buf_widx always points to the current writing position for the current handle
buf_ridx always points to the location of the first handle.
buf_ridx == buf_widx means the buffer is empty.
*/


/* Add a new handle to the linked list and return it. It will have become the
   new current handle.
   data_size must contain the size of what will be in the handle.
   can_wrap tells us whether this type of data may wrap on buffer
   alloc_all tells us if we must immediately be able to allocate data_size
   returns a valid memory handle if all conditions for allocation are met.
           NULL if there memory_handle itself cannot be allocated or if the
           data_size cannot be allocated and alloc_all is set.  This function's
           only potential side effect is to allocate space for the cur_handle
           if it returns NULL.
   */
static struct memory_handle *add_handle(size_t data_size, bool can_wrap,
                                        bool alloc_all)
{
    /* gives each handle a unique id */
    static int cur_handle_id = 0;
    size_t shift;
    size_t new_widx;
    size_t len;
    int overlap;

    if (num_handles >= BUF_MAX_HANDLES)
        return NULL;

    mutex_lock(&llist_mutex);

    if (cur_handle && cur_handle->filerem > 0) {
        /* the current handle hasn't finished buffering. We can only add
           a new one if there is already enough free space to finish
           the buffering. */
        size_t req = cur_handle->filerem + sizeof(struct memory_handle);
        if (RINGBUF_ADD_CROSS(cur_handle->widx, req, buf_ridx) >= 0) {
            /* Not enough space */
            mutex_unlock(&llist_mutex);
            return NULL;
        } else {
            /* Allocate the remainder of the space for the current handle */
            buf_widx = RINGBUF_ADD(cur_handle->widx, cur_handle->filerem);
        }
    }

    /* align to 4 bytes up */
    new_widx = RINGBUF_ADD(buf_widx, 3) & ~3;

    len = data_size + sizeof(struct memory_handle);

    /* First, will the handle wrap? */
    /* If the handle would wrap, move to the beginning of the buffer,
     * or if the data must not but would wrap, move it to the beginning */
    if( (new_widx + sizeof(struct memory_handle) > buffer_len) ||
                   (!can_wrap && (new_widx + len > buffer_len)) ) {
        new_widx = 0;
    }

    /* How far we shifted buf_widx to align things, must be < buffer_len */
    shift = RINGBUF_SUB(new_widx, buf_widx);

    /* How much space are we short in the actual ring buffer? */
    overlap = RINGBUF_ADD_CROSS(buf_widx, shift + len, buf_ridx);
    if (overlap >= 0 && (alloc_all || (unsigned)overlap > data_size)) {
        /* Not enough space for required allocations */
        mutex_unlock(&llist_mutex);
        return NULL;
    }

    /* There is enough space for the required data, advance the buf_widx and
     * initialize the struct */
    buf_widx = new_widx;

    struct memory_handle *new_handle =
        (struct memory_handle *)(&buffer[buf_widx]);

    /* only advance the buffer write index of the size of the struct */
    buf_widx = RINGBUF_ADD(buf_widx, sizeof(struct memory_handle));

    new_handle->id = cur_handle_id;
    /* Wrap signed int is safe and 0 doesn't happen */
    cur_handle_id = (cur_handle_id + 1) & BUF_HANDLE_MASK;
    new_handle->next = NULL;
    num_handles++;

    if (!first_handle)
        /* the new handle is the first one */
        first_handle = new_handle;

    if (cur_handle)
        cur_handle->next = new_handle;

    cur_handle = new_handle;

    mutex_unlock(&llist_mutex);
    return new_handle;
}

/* Delete a given memory handle from the linked list
   and return true for success. Nothing is actually erased from memory. */
static bool rm_handle(const struct memory_handle *h)
{
    if (h == NULL)
        return true;

    mutex_lock(&llist_mutex);

    if (h == first_handle) {
        first_handle = h->next;
        if (h == cur_handle) {
            /* h was the first and last handle: the buffer is now empty */
            cur_handle = NULL;
            buf_ridx = buf_widx = 0;
        } else {
            /* update buf_ridx to point to the new first handle */
            buf_ridx = (void *)first_handle - (void *)buffer;
        }
    } else {
        struct memory_handle *m = first_handle;
        /* Find the previous handle */
        while (m && m->next != h) {
            m = m->next;
        }
        if (m && m->next == h) {
            m->next = h->next;
            if (h == cur_handle) {
                cur_handle = m;
                buf_widx = cur_handle->widx;
            }
        } else {
            mutex_unlock(&llist_mutex);
            return false;
        }
    }

    /* Invalidate the cache to prevent it from keeping the old location of h */
    if (h == cached_handle)
        cached_handle = NULL;

    num_handles--;

    mutex_unlock(&llist_mutex);
    return true;
}

/* Return a pointer to the memory handle of given ID.
   NULL if the handle wasn't found */
static struct memory_handle *find_handle(int handle_id)
{
    if (handle_id < 0)
        return NULL;

    mutex_lock(&llist_mutex);

    /* simple caching because most of the time the requested handle
    will either be the same as the last, or the one after the last */
    if (cached_handle)
    {
        if (cached_handle->id == handle_id) {
            mutex_unlock(&llist_mutex);
            return cached_handle;
        } else if (cached_handle->next &&
                   (cached_handle->next->id == handle_id)) {
            cached_handle = cached_handle->next;
            mutex_unlock(&llist_mutex);
            return cached_handle;
        }
    }

    struct memory_handle *m = first_handle;
    while (m && m->id != handle_id) {
        m = m->next;
    }
    /* This condition can only be reached with !m or m->id == handle_id */
    if (m)
        cached_handle = m;

    mutex_unlock(&llist_mutex);
    return m;
}

/* Move a memory handle and data_size of its data delta bytes along the buffer.
   delta maximum bytes available to move the handle.  If the move is performed
         it is set to the actual distance moved.
   data_size is the amount of data to move along with the struct.
   returns a valid memory_handle if the move is successful
           NULL if the handle is NULL, the  move would be less than the size of
           a memory_handle after correcting for wraps or if the handle is not
           found in the linked list for adjustment.  This function has no side
           effects if NULL is returned. */
static bool move_handle(struct memory_handle **h, size_t *delta,
                        size_t data_size, bool can_wrap)
{
    struct memory_handle *dest;
    const struct memory_handle *src;
    size_t newpos;
    size_t size_to_move;
    size_t final_delta = *delta;
    int overlap;

    if (h == NULL || (src = *h) == NULL)
        return false;

    size_to_move = sizeof(struct memory_handle) + data_size;

    /* Align to four bytes, down */
    final_delta &= ~3;
    if (final_delta < sizeof(struct memory_handle)) {
        /* It's not legal to move less than the size of the struct */
        return false;
    }

    mutex_lock(&llist_mutex);

    newpos = RINGBUF_ADD((void *)src - (void *)buffer, final_delta);
    overlap = RINGBUF_ADD_CROSS(newpos, size_to_move, buffer_len - 1);

    if (overlap > 0) {
        /* Some part of the struct + data would wrap, maybe ok */
        size_t correction = 0;
        /* If the overlap lands inside the memory_handle */
        if ((unsigned)overlap > data_size) {
            /* Correct the position and real delta to prevent the struct from
             * wrapping, this guarantees an aligned delta, I think */
            correction = overlap - data_size;
        } else if (!can_wrap) {
            /* Otherwise the overlap falls in the data area and must all be
             * backed out.  This may become conditional if ever we move
             * data that is allowed to wrap (ie audio) */
            correction = overlap;
            /* Align correction to four bytes, up */
            correction = (correction+3) & ~3;
        }
        if (correction) {
            if (final_delta < correction + sizeof(struct memory_handle)) {
                /* Delta cannot end up less than the size of the struct */
                mutex_unlock(&llist_mutex);
                return false;
            }

            newpos -= correction;
            overlap -= correction;/* Used below to know how to split the data */
            final_delta -= correction;
        }
    }

    dest = (struct memory_handle *)(&buffer[newpos]);

    if (src == first_handle) {
        first_handle = dest;
        buf_ridx = newpos;
    } else {
        struct memory_handle *m = first_handle;
        while (m && m->next != src) {
            m = m->next;
        }
        if (m && m->next == src) {
            m->next = dest;
        } else {
            mutex_unlock(&llist_mutex);
            return false;
        }
    }


    /* Update the cache to prevent it from keeping the old location of h */
    if (src == cached_handle)
        cached_handle = dest;

    /* the cur_handle pointer might need updating */
    if (src == cur_handle)
        cur_handle = dest;

    if (overlap > 0) {
        size_t first_part = size_to_move - overlap;
        memmove(dest, src, first_part);
        memmove(buffer, (const char *)src + first_part, overlap);
    } else {
        memmove(dest, src, size_to_move);
    }

    /* Update the caller with the new location of h and the distance moved */
    *h = dest;
    *delta = final_delta;
    mutex_unlock(&llist_mutex);
    return dest;
}


/*
BUFFER SPACE MANAGEMENT
=======================

update_data_counters: Updates the values in data_counters
buffer_is_low   : Returns true if the amount of useful data in the buffer is low
buffer_handle   : Buffer data for a handle
reset_handle    : Reset write position and data buffer of a handle to its offset
rebuffer_handle : Seek to a nonbuffered part of a handle by rebuffering the data
shrink_handle   : Free buffer space by moving a handle
fill_buffer     : Call buffer_handle for all handles that have data to buffer

These functions are used by the buffering thread to manage buffer space.
*/

static void update_data_counters(void)
{
    struct memory_handle *m = find_handle(base_handle_id);
    bool is_useful = m==NULL;

    size_t buffered = 0;
    size_t wasted = 0;
    size_t remaining = 0;
    size_t useful = 0;

    mutex_lock(&llist_mutex);

    m = first_handle;
    while (m) {
        buffered += m->available;
        wasted += RINGBUF_SUB(m->ridx, m->data);
        remaining += m->filerem;

        if (m->id == base_handle_id)
            is_useful = true;

        if (is_useful)
            useful += RINGBUF_SUB(m->widx, m->ridx);

        m = m->next;
    }

    mutex_unlock(&llist_mutex);

    data_counters.buffered = buffered;
    data_counters.wasted = wasted;
    data_counters.remaining = remaining;
    data_counters.useful = useful;
}

static inline bool buffer_is_low(void)
{
    update_data_counters();
    return data_counters.useful < (conf_watermark / 2);
}

/* Buffer data for the given handle.
   Return whether or not the buffering should continue explicitly.  */
static bool buffer_handle(int handle_id)
{
    logf("buffer_handle(%d)", handle_id);
    struct memory_handle *h = find_handle(handle_id);
    if (!h)
        return true;

    if (h->filerem == 0) {
        /* nothing left to buffer */
        return true;
    }

    if (h->fd < 0)  /* file closed, reopen */
    {
        if (*h->path)
            h->fd = open(h->path, O_RDONLY);

        if (h->fd < 0)
        {
            /* could not open the file, truncate it where it is */
            h->filesize -= h->filerem;
            h->filerem = 0;
            return true;
        }

        if (h->offset)
            lseek(h->fd, h->offset, SEEK_SET);
    }

    trigger_cpu_boost();

    if (h->type == TYPE_ID3)
    {
        if (!get_metadata((struct mp3entry *)(buffer + h->data), h->fd, h->path))
        {
            /* metadata parsing failed: clear the buffer. */
            memset(buffer + h->data, 0, sizeof(struct mp3entry));
        }
        close(h->fd);
        h->fd = -1;
        h->filerem = 0;
        h->available = sizeof(struct mp3entry);
        h->widx += sizeof(struct mp3entry);
        send_event(BUFFER_EVENT_FINISHED, &h->id);
        return true;
    }

    while (h->filerem > 0)
    {
        /* max amount to copy */
        size_t copy_n = MIN( MIN(h->filerem, BUFFERING_DEFAULT_FILECHUNK),
                             buffer_len - h->widx);

        /* stop copying if it would overwrite the reading position */
        if (RINGBUF_ADD_CROSS(h->widx, copy_n, buf_ridx) >= 0)
            return false;

        /* This would read into the next handle, this is broken */
        if (h->next && RINGBUF_ADD_CROSS(h->widx, copy_n,
                    (unsigned)((void *)h->next - (void *)buffer)) > 0) {
            /* Try to recover by truncating this file */
            copy_n = RINGBUF_ADD_CROSS(h->widx, copy_n,
                    (unsigned)((void *)h->next - (void *)buffer));
            h->filerem -= copy_n;
            h->filesize -= copy_n;
            logf("buf alloc short %ld", (long)copy_n);
            if (h->filerem)
                continue;
            else
                break;
        }

        /* rc is the actual amount read */
        int rc = read(h->fd, &buffer[h->widx], copy_n);

        if (rc < 0)
        {
            /* Some kind of filesystem error, maybe recoverable if not codec */
            if (h->type == TYPE_CODEC) {
                logf("Partial codec");
                break;
            }

            DEBUGF("File ended %ld bytes early\n", (long)h->filerem);
            h->filesize -= h->filerem;
            h->filerem = 0;
            break;
        }

        /* Advance buffer */
        h->widx = RINGBUF_ADD(h->widx, rc);
        if (h == cur_handle)
            buf_widx = h->widx;
        h->available += rc;
        h->filerem -= rc;

        /* If this is a large file, see if we need to break or give the codec
         * more time */
        if (h->type == TYPE_PACKET_AUDIO &&
            pcmbuf_is_lowdata() && !buffer_is_low())
        {
            sleep(1);
        }
        else
        {
            yield();
        }

        if (!queue_empty(&buffering_queue))
            break;
    }

    if (h->filerem == 0) {
        /* finished buffering the file */
        close(h->fd);
        h->fd = -1;
        send_event(BUFFER_EVENT_FINISHED, &h->id);
    }

    return true;
}

/* Reset writing position and data buffer of a handle to its current offset.
   Use this after having set the new offset to use. */
static void reset_handle(int handle_id)
{
    logf("reset_handle(%d)", handle_id);

    struct memory_handle *h = find_handle(handle_id);
    if (!h)
        return;

    h->ridx = h->widx = h->data;
    if (h == cur_handle)
        buf_widx = h->widx;
    h->available = 0;
    h->filerem = h->filesize - h->offset;

    if (h->fd >= 0) {
        lseek(h->fd, h->offset, SEEK_SET);
    }
}

/* Seek to a nonbuffered part of a handle by rebuffering the data. */
static void rebuffer_handle(int handle_id, size_t newpos)
{
    struct memory_handle *h = find_handle(handle_id);
    if (!h)
        return;

    /* When seeking foward off of the buffer, if it is a short seek don't
       rebuffer the whole track, just read enough to satisfy */
    if (newpos > h->offset && newpos - h->offset < BUFFERING_DEFAULT_FILECHUNK)
    {
        LOGFQUEUE("buffering >| Q_BUFFER_HANDLE %d", handle_id);
        queue_send(&buffering_queue, Q_BUFFER_HANDLE, handle_id);
        h->ridx = h->data + newpos;
        return;
    }

    h->offset = newpos;

    /* Reset the handle to its new offset */
    LOGFQUEUE("buffering >| Q_RESET_HANDLE %d", handle_id);
    queue_send(&buffering_queue, Q_RESET_HANDLE, handle_id);

    size_t next = (unsigned)((void *)h->next - (void *)buffer);
    if (RINGBUF_SUB(next, h->data) < h->filesize - newpos)
    {
        /* There isn't enough space to rebuffer all of the track from its new
           offset, so we ask the user to free some */
        DEBUGF("rebuffer_handle: space is needed\n");
        send_event(BUFFER_EVENT_REBUFFER, &handle_id);
    }

    /* Now we ask for a rebuffer */
    LOGFQUEUE("buffering >| Q_BUFFER_HANDLE %d", handle_id);
    queue_send(&buffering_queue, Q_BUFFER_HANDLE, handle_id);
}

static bool close_handle(int handle_id)
{
    struct memory_handle *h = find_handle(handle_id);

    /* If the handle is not found, it is closed */
    if (!h)
        return true;

    if (h->fd >= 0) {
        close(h->fd);
        h->fd = -1;
    }

    /* rm_handle returns true unless the handle somehow persists after exit */
    return rm_handle(h);
}

/* Free buffer space by moving the handle struct right before the useful
   part of its data buffer or by moving all the data. */
static void shrink_handle(struct memory_handle *h)
{
    size_t delta;

    if (!h)
        return;

    if (h->next && h->filerem == 0 &&
            (h->type == TYPE_ID3 || h->type == TYPE_CUESHEET ||
             h->type == TYPE_BITMAP || h->type == TYPE_CODEC ||
             h->type == TYPE_ATOMIC_AUDIO))
    {
        /* metadata handle: we can move all of it */
        size_t handle_distance =
            RINGBUF_SUB((unsigned)((void *)h->next - (void*)buffer), h->data);
        delta = handle_distance - h->available;

        /* The value of delta might change for alignment reasons */
        if (!move_handle(&h, &delta, h->available, h->type==TYPE_CODEC))
            return;

        size_t olddata = h->data;
        h->data = RINGBUF_ADD(h->data, delta);
        h->ridx = RINGBUF_ADD(h->ridx, delta);
        h->widx = RINGBUF_ADD(h->widx, delta);

        if (h->type == TYPE_ID3 && h->filesize == sizeof(struct mp3entry)) {
            /* when moving an mp3entry we need to readjust its pointers. */
            adjust_mp3entry((struct mp3entry *)&buffer[h->data],
                            (void *)&buffer[h->data],
                            (const void *)&buffer[olddata]);
        } else if (h->type == TYPE_BITMAP) {
            /* adjust the bitmap's pointer */
            struct bitmap *bmp = (struct bitmap *)&buffer[h->data];
            bmp->data = &buffer[h->data + sizeof(struct bitmap)];
        }
    }
    else
    {
        /* only move the handle struct */
        delta = RINGBUF_SUB(h->ridx, h->data);
        if (!move_handle(&h, &delta, 0, true))
            return;

        h->data = RINGBUF_ADD(h->data, delta);
        h->available -= delta;
        h->offset += delta;
    }
}

/* Fill the buffer by buffering as much data as possible for handles that still
   have data left to buffer
   Return whether or not to continue filling after this */
static bool fill_buffer(void)
{
    logf("fill_buffer()");
    struct memory_handle *m;
    shrink_handle(first_handle);
    m = first_handle;
    while (queue_empty(&buffering_queue) && m) {
        if (m->filerem > 0) {
            if (!buffer_handle(m->id)) {
                m = NULL;
                break;
            }
        }
        m = m->next;
    }

    if (m) {
        return true;
    }
    else
    {
        /* only spin the disk down if the filling wasn't interrupted by an
           event arriving in the queue. */
        storage_sleep();
        return false;
    }
}

#ifdef HAVE_ALBUMART
/* Given a file descriptor to a bitmap file, write the bitmap data to the
   buffer, with a struct bitmap and the actual data immediately following.
   Return value is the total size (struct + data). */
static int load_bitmap(int fd)
{
    int rc;
    struct bitmap *bmp = (struct bitmap *)&buffer[buf_widx];
    /* FIXME: alignment may be needed for the data buffer. */
    bmp->data = &buffer[buf_widx + sizeof(struct bitmap)];

#if (LCD_DEPTH > 1) || defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1)
    bmp->maskdata = NULL;
#endif

    int free = (int)MIN(buffer_len - BUF_USED, buffer_len - buf_widx)
                               - sizeof(struct bitmap);

    get_albumart_size(bmp);

    rc = read_bmp_fd(fd, bmp, free, FORMAT_NATIVE|FORMAT_DITHER|
                     FORMAT_RESIZE|FORMAT_KEEP_ASPECT, NULL);
    return rc + (rc > 0 ? sizeof(struct bitmap) : 0);
}
#endif


/*
MAIN BUFFERING API CALLS
========================

bufopen     : Request the opening of a new handle for a file
bufalloc    : Open a new handle for data other than a file.
bufclose    : Close an open handle
bufseek     : Set the read pointer in a handle
bufadvance  : Move the read pointer in a handle
bufread     : Copy data from a handle into a given buffer
bufgetdata  : Give a pointer to the handle's data

These functions are exported, to allow interaction with the buffer.
They take care of the content of the structs, and rely on the linked list
management functions for all the actual handle management work.
*/


/* Reserve space in the buffer for a file.
   filename: name of the file to open
   offset: offset at which to start buffering the file, useful when the first
           (offset-1) bytes of the file aren't needed.
   return value: <0 if the file cannot be opened, or one file already
   queued to be opened, otherwise the handle for the file in the buffer
*/
int bufopen(const char *file, size_t offset, enum data_type type)
{
    if (type == TYPE_ID3)
    {
        /* ID3 case: allocate space, init the handle and return. */

        struct memory_handle *h = add_handle(sizeof(struct mp3entry), false, true);
        if (!h)
            return ERR_BUFFER_FULL;

        h->fd = -1;
        h->filesize = sizeof(struct mp3entry);
        h->filerem = sizeof(struct mp3entry);
        h->offset = 0;
        h->data = buf_widx;
        h->ridx = buf_widx;
        h->widx = buf_widx;
        h->available = 0;
        h->type = type;
        strncpy(h->path, file, MAX_PATH);

        buf_widx += sizeof(struct mp3entry);  /* safe because the handle
                                                 can't wrap */

        /* Inform the buffering thread that we added a handle */
        LOGFQUEUE("buffering > Q_HANDLE_ADDED %d", h->id);
        queue_post(&buffering_queue, Q_HANDLE_ADDED, h->id);

        return h->id;
    }

    /* Other cases: there is a little more work. */

    int fd = open(file, O_RDONLY);
    if (fd < 0)
        return ERR_FILE_ERROR;

    size_t size = filesize(fd);
    bool can_wrap = type==TYPE_PACKET_AUDIO || type==TYPE_CODEC;

    size_t adjusted_offset = offset;
    if (adjusted_offset > size)
        adjusted_offset = 0;

    struct memory_handle *h = add_handle(size-adjusted_offset, can_wrap, false);
    if (!h)
    {
        DEBUGF("bufopen: failed to add handle\n");
        close(fd);
        return ERR_BUFFER_FULL;
    }

    strncpy(h->path, file, MAX_PATH);
    h->offset = adjusted_offset;
    h->ridx = buf_widx;
    h->data = buf_widx;
    h->type = type;

#ifdef HAVE_ALBUMART
    if (type == TYPE_BITMAP)
    {
        /* Bitmap file: we load the data instead of the file */
        int rc;
        mutex_lock(&llist_mutex); /* Lock because load_bitmap yields */
        rc = load_bitmap(fd);
        mutex_unlock(&llist_mutex);
        if (rc <= 0)
        {
            rm_handle(h);
            close(fd);
            return ERR_FILE_ERROR;
        }
        h->filerem = 0;
        h->filesize = rc;
        h->available = rc;
        h->widx = buf_widx + rc; /* safe because the data doesn't wrap */
        buf_widx += rc;  /* safe too */
    }
    else
#endif
    {
        h->filerem = size - adjusted_offset;
        h->filesize = size;
        h->available = 0;
        h->widx = buf_widx;
    }

    if (type == TYPE_CUESHEET) {
        h->fd = fd;
        /* Immediately start buffering those */
        LOGFQUEUE("buffering >| Q_BUFFER_HANDLE %d", h->id);
        queue_send(&buffering_queue, Q_BUFFER_HANDLE, h->id);
    } else {
        /* Other types will get buffered in the course of normal operations */
        h->fd = -1;
        close(fd);

        /* Inform the buffering thread that we added a handle */
        LOGFQUEUE("buffering > Q_HANDLE_ADDED %d", h->id);
        queue_post(&buffering_queue, Q_HANDLE_ADDED, h->id);
    }

    logf("bufopen: new hdl %d", h->id);
    return h->id;
}

/* Open a new handle from data that needs to be copied from memory.
   src is the source buffer from which to copy data. It can be NULL to simply
   reserve buffer space.
   size is the requested size. The call will only be successful if the
   requested amount of data can entirely fit in the buffer without wrapping.
   Return value is the handle id for success or <0 for failure.
*/
int bufalloc(const void *src, size_t size, enum data_type type)
{
    struct memory_handle *h = add_handle(size, false, true);

    if (!h)
        return ERR_BUFFER_FULL;

    if (src) {
        if (type == TYPE_ID3 && size == sizeof(struct mp3entry)) {
            /* specially take care of struct mp3entry */
            copy_mp3entry((struct mp3entry *)&buffer[buf_widx],
                          (const struct mp3entry *)src);
        } else {
            memcpy(&buffer[buf_widx], src, size);
        }
    }

    h->fd = -1;
    *h->path = 0;
    h->filesize = size;
    h->filerem = 0;
    h->offset = 0;
    h->ridx = buf_widx;
    h->widx = buf_widx + size; /* this is safe because the data doesn't wrap */
    h->data = buf_widx;
    h->available = size;
    h->type = type;

    buf_widx += size;  /* safe too */

    logf("bufalloc: new hdl %d", h->id);
    return h->id;
}

/* Close the handle. Return true for success and false for failure */
bool bufclose(int handle_id)
{
    logf("bufclose(%d)", handle_id);

    LOGFQUEUE("buffering >| Q_CLOSE_HANDLE %d", handle_id);
    return queue_send(&buffering_queue, Q_CLOSE_HANDLE, handle_id);
}

/* Set reading index in handle (relatively to the start of the file).
   Access before the available data will trigger a rebuffer.
   Return 0 for success and < 0 for failure:
     -1 if the handle wasn't found
     -2 if the new requested position was beyond the end of the file
*/
int bufseek(int handle_id, size_t newpos)
{
    struct memory_handle *h = find_handle(handle_id);
    if (!h)
        return ERR_HANDLE_NOT_FOUND;

    if (newpos > h->filesize) {
        /* access beyond the end of the file */
        return ERR_INVALID_VALUE;
    }
    else if (newpos < h->offset || h->offset + h->available < newpos) {
        /* access before or after buffered data. A rebuffer is needed. */
        rebuffer_handle(handle_id, newpos);
    }
    else {
        h->ridx = RINGBUF_ADD(h->data, newpos - h->offset);
    }
    return 0;
}

/* Advance the reading index in a handle (relatively to its current position).
   Return 0 for success and < 0 for failure */
int bufadvance(int handle_id, off_t offset)
{
    const struct memory_handle *h = find_handle(handle_id);
    if (!h)
        return ERR_HANDLE_NOT_FOUND;

    size_t newpos = h->offset + RINGBUF_SUB(h->ridx, h->data) + offset;
    return bufseek(handle_id, newpos);
}

/* Used by bufread and bufgetdata to prepare the buffer and retrieve the
 * actual amount of data available for reading.  This function explicitly
 * does not check the validity of the input handle.  It does do range checks
 * on size and returns a valid (and explicit) amount of data for reading */
static struct memory_handle *prep_bufdata(int handle_id, size_t *size,
                                          bool guardbuf_limit)
{
    struct memory_handle *h = find_handle(handle_id);
    if (!h)
        return NULL;

    size_t avail = RINGBUF_SUB(h->widx, h->ridx);

    if (avail == 0 && h->filerem == 0)
    {
        /* File is finished reading */
        *size = 0;
        return h;
    }

    if (*size == 0 || *size > avail + h->filerem)
        *size = avail + h->filerem;

    if (guardbuf_limit && h->type == TYPE_PACKET_AUDIO && *size > GUARD_BUFSIZE)
    {
        logf("data request > guardbuf");
        /* If more than the size of the guardbuf is requested and this is a
         * bufgetdata, limit to guard_bufsize over the end of the buffer */
        *size = MIN(*size, buffer_len - h->ridx + GUARD_BUFSIZE);
        /* this ensures *size <= buffer_len - h->ridx + GUARD_BUFSIZE */
    }

    if (h->filerem > 0 && avail < *size)
    {
        /* Data isn't ready. Request buffering */
        buf_request_buffer_handle(handle_id);
        /* Wait for the data to be ready */
        do
        {
            sleep(1);
            /* it is not safe for a non-buffering thread to sleep while
             * holding a handle */
            h = find_handle(handle_id);
            if (!h)
                return NULL;
            avail = RINGBUF_SUB(h->widx, h->ridx);
        }
        while (h->filerem > 0 && avail < *size);
    }

    *size = MIN(*size,avail);
    return h;
}

/* Copy data from the given handle to the dest buffer.
   Return the number of bytes copied or < 0 for failure (handle not found).
   The caller is blocked until the requested amount of data is available.
*/
ssize_t bufread(int handle_id, size_t size, void *dest)
{
    const struct memory_handle *h;
    size_t adjusted_size = size;

    h = prep_bufdata(handle_id, &adjusted_size, false);
    if (!h)
        return ERR_HANDLE_NOT_FOUND;

    if (h->ridx + adjusted_size > buffer_len)
    {
        /* the data wraps around the end of the buffer */
        size_t read = buffer_len - h->ridx;
        memcpy(dest, &buffer[h->ridx], read);
        memcpy(dest+read, buffer, adjusted_size - read);
    }
    else
    {
        memcpy(dest, &buffer[h->ridx], adjusted_size);
    }

    return adjusted_size;
}

/* Update the "data" pointer to make the handle's data available to the caller.
   Return the length of the available linear data or < 0 for failure (handle
   not found).
   The caller is blocked until the requested amount of data is available.
   size is the amount of linear data requested. it can be 0 to get as
   much as possible.
   The guard buffer may be used to provide the requested size. This means it's
   unsafe to request more than the size of the guard buffer.
*/
ssize_t bufgetdata(int handle_id, size_t size, void **data)
{
    const struct memory_handle *h;
    size_t adjusted_size = size;

    h = prep_bufdata(handle_id, &adjusted_size, true);
    if (!h)
        return ERR_HANDLE_NOT_FOUND;

    if (h->ridx + adjusted_size > buffer_len)
    {
        /* the data wraps around the end of the buffer :
           use the guard buffer to provide the requested amount of data. */
        size_t copy_n = h->ridx + adjusted_size - buffer_len;
        /* prep_bufdata ensures adjusted_size <= buffer_len - h->ridx + GUARD_BUFSIZE,
           so copy_n <= GUARD_BUFSIZE */
        memcpy(guard_buffer, (const unsigned char *)buffer, copy_n);
    }

    if (data)
        *data = &buffer[h->ridx];

    return adjusted_size;
}

ssize_t bufgettail(int handle_id, size_t size, void **data)
{
    size_t tidx;

    const struct memory_handle *h;

    h = find_handle(handle_id);

    if (!h)
        return ERR_HANDLE_NOT_FOUND;

    if (h->filerem)
        return ERR_HANDLE_NOT_DONE;

    /* We don't support tail requests of > guardbuf_size, for simplicity */
    if (size > GUARD_BUFSIZE)
        return ERR_INVALID_VALUE;

    tidx = RINGBUF_SUB(h->widx, size);

    if (tidx + size > buffer_len)
    {
        size_t copy_n = tidx + size - buffer_len;
        memcpy(guard_buffer, (const unsigned char *)buffer, copy_n);
    }

    *data = &buffer[tidx];
    return size;
}

ssize_t bufcuttail(int handle_id, size_t size)
{
    struct memory_handle *h;
    size_t adjusted_size = size;

    h = find_handle(handle_id);

    if (!h)
        return ERR_HANDLE_NOT_FOUND;

    if (h->filerem)
        return ERR_HANDLE_NOT_DONE;

    if (h->available < adjusted_size)
        adjusted_size = h->available;

    h->available -= adjusted_size;
    h->filesize -= adjusted_size;
    h->widx = RINGBUF_SUB(h->widx, adjusted_size);
    if (h == cur_handle)
        buf_widx = h->widx;

    return adjusted_size;
}


/*
SECONDARY EXPORTED FUNCTIONS
============================

buf_get_offset
buf_handle_offset
buf_request_buffer_handle
buf_set_base_handle
buf_used
register_buffering_callback
unregister_buffering_callback

These functions are exported, to allow interaction with the buffer.
They take care of the content of the structs, and rely on the linked list
management functions for all the actual handle management work.
*/

/* Get a handle offset from a pointer */
ssize_t buf_get_offset(int handle_id, void *ptr)
{
    const struct memory_handle *h = find_handle(handle_id);
    if (!h)
        return ERR_HANDLE_NOT_FOUND;

    return (size_t)ptr - (size_t)&buffer[h->ridx];
}

ssize_t buf_handle_offset(int handle_id)
{
    const struct memory_handle *h = find_handle(handle_id);
    if (!h)
        return ERR_HANDLE_NOT_FOUND;
    return h->offset;
}

void buf_request_buffer_handle(int handle_id)
{