diff --git a/code/cgame/cg_draw.c b/code/cgame/cg_draw.c
index e29c10f..7d7126e 100644
--- a/code/cgame/cg_draw.c
+++ b/code/cgame/cg_draw.c
@@ -1650,6 +1650,16 @@ static void CG_DrawDisconnect( void ) {
const char *s;
int w; // bk010215 - FIXME char message[1024];
+ // server demo playback: detect pause from frozen snapshot time
+ if ( cg.svDemoPlayback ) {
+ if ( cg.nextSnap && cg.nextSnap->serverTime == cg.snap->serverTime ) {
+ s = "Playback Paused";
+ w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH;
+ CG_DrawBigString( 320 - w/2, 100, s, 1.0F );
+ }
+ return;
+ }
+
// draw the phone jack if we are completely past our buffers
cmdNum = trap_GetCurrentCmdNumber() - CMD_BACKUP + 1;
trap_GetUserCmd( cmdNum, &cmd );
diff --git a/code/cgame/cg_local.h b/code/cgame/cg_local.h
index 4dd5c9d..0c8e98a 100644
--- a/code/cgame/cg_local.h
+++ b/code/cgame/cg_local.h
@@ -457,6 +457,10 @@ typedef struct {
int clientNum;
qboolean demoPlayback;
+ qboolean svDemoPlayback; // server-side demo playback mode
+ qboolean svDemoFreeCamera; // free camera (client-owned movement)
+ playerState_t svDemoCameraPs; // local playerState for free camera
+ int svDemoCameraTime; // real time of last camera update
qboolean levelShot; // taking a level menu screenshot
int deferredPlayerLoading;
qboolean loading; // don't defer players at initial startup
@@ -1618,6 +1622,7 @@ qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd );
// used for the weapon select and zoom
void trap_SetUserCmdValue( int stateValue, float sensitivityScale );
+void trap_SetClientOrigin( qboolean hasOrigin, float x, float y, float z );
// aids for VM testing
void testPrintInt( char *string, int i );
diff --git a/code/cgame/cg_main.c b/code/cgame/cg_main.c
index aa9ec27..97f65a2 100644
--- a/code/cgame/cg_main.c
+++ b/code/cgame/cg_main.c
@@ -1896,6 +1896,13 @@ void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) {
// get the gamestate from the client system
trap_GetGameState( &cgs.gameState );
+ // detect server-side demo playback from configstring
+ cg.svDemoPlayback = ( atoi( CG_ConfigString( CS_SVDEMO ) ) != 0 );
+ if ( cg.svDemoPlayback ) {
+ cg.svDemoFreeCamera = qtrue;
+ // camera ps will be initialized from first snapshot in CG_PredictPlayerState
+ }
+
// check version
s = CG_ConfigString( CS_GAME_VERSION );
if ( strcmp( s, GAME_VERSION ) ) {
diff --git a/code/cgame/cg_predict.c b/code/cgame/cg_predict.c
index fec2cfb..ed0d72d 100644
--- a/code/cgame/cg_predict.c
+++ b/code/cgame/cg_predict.c
@@ -432,6 +432,59 @@ void CG_PredictPlayerState( void ) {
// demo playback just copies the moves
if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) ) {
CG_InterpolatePlayerState( qfalse );
+ // send followed player's origin for PVS during server demo follow mode
+ if ( cg.svDemoPlayback && (cg.snap->ps.pm_flags & PMF_FOLLOW) ) {
+ trap_SetClientOrigin( qtrue,
+ cg.snap->ps.origin[0],
+ cg.snap->ps.origin[1],
+ cg.snap->ps.origin[2] );
+ cg.svDemoFreeCamera = qfalse;
+ }
+ return;
+ }
+
+ // server-side demo free camera: run local PmoveSingle
+ if ( cg.svDemoPlayback && cg.svDemoFreeCamera ) {
+ int realTime = trap_Milliseconds();
+ int dt = realTime - cg.svDemoCameraTime;
+ pmove_t pm;
+
+ // lazy init: place camera at spectator spawn from first snapshot
+ if ( cg.svDemoCameraPs.speed == 0 ) {
+ cg.svDemoCameraPs = cg.snap->ps;
+ cg.svDemoCameraPs.pm_type = PM_SPECTATOR;
+ cg.svDemoCameraPs.speed = 480;
+ cg.svDemoCameraTime = realTime;
+ dt = 1;
+ }
+
+ if ( dt < 1 ) dt = 1;
+ if ( dt > 200 ) dt = 200;
+ cg.svDemoCameraTime = realTime;
+
+ // get latest usercmd for input
+ current = trap_GetCurrentCmdNumber();
+ trap_GetUserCmd( current, &latestCmd );
+
+ // set up pmove
+ memset( &pm, 0, sizeof(pm) );
+ pm.ps = &cg.svDemoCameraPs;
+ pm.cmd = latestCmd;
+ pm.cmd.serverTime = cg.svDemoCameraPs.commandTime + dt;
+ pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY;
+ pm.trace = CG_Trace;
+ pm.pointcontents = CG_PointContents;
+
+ Pmove( &pm );
+
+ // use as the predicted state for rendering
+ cg.predictedPlayerState = cg.svDemoCameraPs;
+
+ // send origin for PVS
+ trap_SetClientOrigin( qtrue,
+ cg.svDemoCameraPs.origin[0],
+ cg.svDemoCameraPs.origin[1],
+ cg.svDemoCameraPs.origin[2] );
return;
}
diff --git a/code/cgame/cg_public.h b/code/cgame/cg_public.h
index 8514143..d667ce2 100644
--- a/code/cgame/cg_public.h
+++ b/code/cgame/cg_public.h
@@ -164,6 +164,7 @@ typedef enum {
CG_R_INPVS,
// 1.32
CG_FS_SEEK,
+ CG_SETCLIENTORIGIN, // set client-owned origin for demo spectator PVS
/*
CG_LOADCAMERA,
diff --git a/code/cgame/cg_snapshot.c b/code/cgame/cg_snapshot.c
index add49f0..bce36bb 100644
--- a/code/cgame/cg_snapshot.c
+++ b/code/cgame/cg_snapshot.c
@@ -184,6 +184,22 @@ static void CG_TransitionSnapshot( void ) {
|| cg_nopredict.integer || cg_synchronousClients.integer ) {
CG_TransitionPlayerState( ps, ops );
}
+
+ // server demo: detect follow→free camera transition
+ if ( cg.svDemoPlayback ) {
+ qboolean wasFollowing = ( ops->pm_flags & PMF_FOLLOW ) != 0;
+ qboolean isFollowing = ( ps->pm_flags & PMF_FOLLOW ) != 0;
+ if ( wasFollowing && !isFollowing ) {
+ // exiting follow mode — init camera from last known position
+ cg.svDemoFreeCamera = qtrue;
+ cg.svDemoCameraTime = trap_Milliseconds();
+ VectorCopy( ops->origin, cg.svDemoCameraPs.origin );
+ VectorCopy( ops->viewangles, cg.svDemoCameraPs.viewangles );
+ cg.svDemoCameraPs.pm_type = PM_SPECTATOR;
+ cg.svDemoCameraPs.speed = 480;
+ cg.svDemoCameraPs.clientNum = cg.clientNum;
+ }
+ }
}
}
@@ -203,6 +219,15 @@ static void CG_SetNextSnap( snapshot_t *snap ) {
cg.nextSnap = snap;
+ // SNAPFLAG_RESET_ENTITIES: invalidate all entities so they are
+ // treated as new (no interpolation from old positions).
+ // Must happen before the entity loop below.
+ if ( snap->snapFlags & SNAPFLAG_RESET_ENTITIES ) {
+ for ( num = 0 ; num < MAX_GENTITIES ; num++ ) {
+ cg_entities[ num ].currentValid = qfalse;
+ }
+ }
+
BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].nextState, qfalse );
cg_entities[ cg.snap->ps.clientNum ].interpolate = qtrue;
@@ -241,6 +266,11 @@ static void CG_SetNextSnap( snapshot_t *snap ) {
cg.nextFrameTeleport = qtrue;
}
+ // entity reset also prevents playerstate interpolation
+ if ( cg.nextSnap->snapFlags & SNAPFLAG_RESET_ENTITIES ) {
+ cg.nextFrameTeleport = qtrue;
+ }
+
// sort out solid entities
CG_BuildSolidList();
}
@@ -372,8 +402,34 @@ void CG_ProcessSnapshots( void ) {
CG_SetNextSnap( snap );
- // if time went backwards, we have a level restart
+ // if time went backwards, we have a level restart or demo seek
if ( cg.nextSnap->serverTime < cg.snap->serverTime ) {
+ if ( cg.svDemoPlayback ) {
+ // demo seek — discard old snap, use nextSnap as current,
+ // and wait for another snapshot before rendering
+ cg.snap = cg.nextSnap;
+ cg.nextSnap = NULL;
+ cg.time = cg.snap->serverTime;
+ // reset all entity state and time-dependent fields
+ {
+ int e;
+ for ( e = 0; e < MAX_GENTITIES; e++ ) {
+ cg_entities[e].currentValid = qfalse;
+ cg_entities[e].interpolate = qfalse;
+ cg_entities[e].muzzleFlashTime = 0;
+ cg_entities[e].trailTime = 0;
+ cg_entities[e].dustTrailTime = 0;
+ cg_entities[e].miscTime = 0;
+ cg_entities[e].snapShotTime = 0;
+ cg_entities[e].previousEvent = 0;
+ cg_entities[e].teleportFlag = 0;
+ }
+ }
+ // clear local entities (particles, gibs, etc.)
+ // they reference old times and would render incorrectly
+ CG_InitLocalEntities();
+ break; // exit loop, wait for next snapshot
+ }
CG_Error( "CG_ProcessSnapshots: Server time went backwards" );
}
}
diff --git a/code/cgame/cg_syscalls.c b/code/cgame/cg_syscalls.c
index a7bf659..2446b4b 100644
--- a/code/cgame/cg_syscalls.c
+++ b/code/cgame/cg_syscalls.c
@@ -333,6 +333,10 @@ void trap_SetUserCmdValue( int stateValue, float sensitivityScale ) {
syscall( CG_SETUSERCMDVALUE, stateValue, PASSFLOAT(sensitivityScale) );
}
+void trap_SetClientOrigin( qboolean hasOrigin, float x, float y, float z ) {
+ syscall( CG_SETCLIENTORIGIN, hasOrigin, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(z) );
+}
+
void testPrintInt( char *string, int i ) {
syscall( CG_TESTPRINTINT, string, i );
}
diff --git a/code/client/cl_cgame.c b/code/client/cl_cgame.c
index 0b259d3..e399cef 100644
--- a/code/client/cl_cgame.c
+++ b/code/client/cl_cgame.c
@@ -457,6 +457,14 @@ int CL_CgameSystemCalls( int *args ) {
return 0;
case CG_FS_SEEK:
return FS_Seek( args[1], args[2], args[3] );
+ case CG_SETCLIENTORIGIN:
+ cl.cgameHasOrigin = args[1];
+ if ( args[1] ) {
+ cl.cgameOrigin[0] = VMF(2);
+ cl.cgameOrigin[1] = VMF(3);
+ cl.cgameOrigin[2] = VMF(4);
+ }
+ return 0;
case CG_SENDCONSOLECOMMAND:
Cbuf_AddText( VMA(1) );
return 0;
diff --git a/code/client/cl_input.c b/code/client/cl_input.c
index f450ee7..c3b4778 100644
--- a/code/client/cl_input.c
+++ b/code/client/cl_input.c
@@ -516,6 +516,12 @@ void CL_FinishMove( usercmd_t *cmd ) {
for (i=0 ; i<3 ; i++) {
cmd->angles[i] = ANGLE2SHORT(cl.viewangles[i]);
}
+
+ // client-owned origin for demo spectator PVS
+ cmd->hasOrigin = cl.cgameHasOrigin;
+ if ( cl.cgameHasOrigin ) {
+ VectorCopy( cl.cgameOrigin, cmd->origin );
+ }
}
diff --git a/code/client/cl_parse.c b/code/client/cl_parse.c
index 9de3588..8626b3f 100644
--- a/code/client/cl_parse.c
+++ b/code/client/cl_parse.c
@@ -288,17 +288,30 @@ void CL_ParseSnapshot( msg_t *msg ) {
cl.snapshots[oldMessageNum & PACKET_MASK].valid = qfalse;
}
- // copy to the current good spot
- cl.snap = newSnap;
- cl.snap.ping = 999;
- // calculate ping time
- for ( i = 0 ; i < PACKET_BACKUP ; i++ ) {
- packetNum = ( clc.netchan.outgoingSequence - 1 - i ) & PACKET_MASK;
- if ( cl.snap.ps.commandTime >= cl.outPackets[ packetNum ].p_serverTime ) {
- cl.snap.ping = cls.realtime - cl.outPackets[ packetNum ].p_realtime;
- break;
+ {
+ int oldSnapFlags = cl.snap.snapFlags;
+
+ // copy to the current good spot
+ cl.snap = newSnap;
+ cl.snap.ping = 999;
+ // calculate ping time
+ for ( i = 0 ; i < PACKET_BACKUP ; i++ ) {
+ packetNum = ( clc.netchan.outgoingSequence - 1 - i ) & PACKET_MASK;
+ if ( cl.snap.ps.commandTime >= cl.outPackets[ packetNum ].p_serverTime ) {
+ cl.snap.ping = cls.realtime - cl.outPackets[ packetNum ].p_realtime;
+ break;
+ }
+ }
+ // if server count changed (map_restart or demo unpause), force-reset
+ // the time delta so the client doesn't slowly drift back to sync
+ if ( ( oldSnapFlags ^ newSnap.snapFlags ) & SNAPFLAG_SERVERCOUNT ) {
+ cl.serverTimeDelta = cl.snap.serverTime - cls.realtime;
+ cl.oldServerTime = cl.snap.serverTime;
+ cl.serverTime = cl.snap.serverTime;
+ cl.oldFrameServerTime = cl.snap.serverTime; // prevent backwards time error on seek
}
}
+
// save the frame off in the backup array for later delta comparisons
cl.snapshots[cl.snap.messageNum & PACKET_MASK] = cl.snap;
diff --git a/code/client/client.h b/code/client/client.h
index 09e02a8..fe36445 100644
--- a/code/client/client.h
+++ b/code/client/client.h
@@ -106,6 +106,8 @@ typedef struct {
// cgame communicates a few values to the client system
int cgameUserCmdValue; // current weapon to add to usercmd_t
float cgameSensitivity;
+ qboolean cgameHasOrigin; // client-owned origin for demo spectator PVS
+ vec3_t cgameOrigin; // the origin to send
// cmds[cmdNumber] is the predicted command, [cmdNumber-1] is the last
// properly generated command
diff --git a/code/game/bg_public.h b/code/game/bg_public.h
index 0dd59db..5dc1cf2 100644
--- a/code/game/bg_public.h
+++ b/code/game/bg_public.h
@@ -79,6 +79,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define CS_FLAGSTATUS 23 // string indicating flag status in CTF
#define CS_SHADERSTATE 24
#define CS_BOTINFO 25
+#define CS_SVDEMO 26 // "1" during server-side demo playback
#define CS_ITEMS 27 // string of 0's and 1's that tell which items are present
diff --git a/code/game/g_active.c b/code/game/g_active.c
index 853f5fb..a1e7e82 100644
--- a/code/game/g_active.c
+++ b/code/game/g_active.c
@@ -1034,6 +1034,12 @@ void ClientThink( int clientNum ) {
// phone jack if they don't get any for a while
ent->client->lastCmdTime = level.time;
+ // demo playback: don't run ClientThink_real — cgame owns
+ // the camera movement, G_RunFrame handles buttons and PVS origin
+ if ( g_svDemoPlaying.integer ) {
+ return;
+ }
+
if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) {
ClientThink_real( ent );
}
diff --git a/code/game/g_client.c b/code/game/g_client.c
index 5a584d4..6d43d7c 100644
--- a/code/game/g_client.c
+++ b/code/game/g_client.c
@@ -1012,6 +1012,12 @@ void ClientBegin( int clientNum ) {
client->pers.enterTime = level.time;
client->pers.teamState.state = TEAM_BEGIN;
+ // demo playback: force all clients to spectator
+ if ( g_svDemoPlaying.integer ) {
+ client->sess.sessionTeam = TEAM_SPECTATOR;
+ ClientUserinfoChanged( ent->client - level.clients );
+ }
+
// save eflags around this, because changing teams will
// cause this to happen with a valid entity, and we
// want to make sure the teleport bit is set right
diff --git a/code/game/g_cmds.c b/code/game/g_cmds.c
index e72c80e..dc87e58 100644
--- a/code/game/g_cmds.c
+++ b/code/game/g_cmds.c
@@ -661,6 +661,20 @@ void Cmd_Team_f( gentity_t *ent ) {
int oldTeam;
char s[MAX_TOKEN_CHARS];
+ // demo playback: only allow "team spectator" (to exit follow mode)
+ if ( g_svDemoPlaying.integer ) {
+ char s2[MAX_TOKEN_CHARS];
+ if ( trap_Argc() == 2 ) {
+ trap_Argv( 1, s2, sizeof(s2) );
+ if ( !Q_stricmp( s2, "spectator" ) || !Q_stricmp( s2, "s" ) ) {
+ StopFollowing( ent );
+ return;
+ }
+ }
+ trap_SendServerCommand( ent-g_entities, "print \"Only spectating allowed during demo playback.\n\"" );
+ return;
+ }
+
if ( trap_Argc() != 2 ) {
oldTeam = ent->client->sess.sessionTeam;
switch ( oldTeam ) {
diff --git a/code/game/g_local.h b/code/game/g_local.h
index 1a55955..057722c 100644
--- a/code/game/g_local.h
+++ b/code/game/g_local.h
@@ -641,6 +641,7 @@ void ClientCommand( int clientNum );
// g_active.c
//
void ClientThink( int clientNum );
+void ClientThink_real( gentity_t *ent );
void ClientEndFrame( gentity_t *ent );
void G_RunClient( gentity_t *ent );
@@ -715,6 +716,7 @@ extern gentity_t g_entities[MAX_GENTITIES];
#define FOFS(x) ((int)&(((gentity_t *)0)->x))
extern vmCvar_t g_gametype;
+extern vmCvar_t g_svDemoPlaying;
extern vmCvar_t g_dedicated;
extern vmCvar_t g_cheats;
extern vmCvar_t g_maxclients; // allow this many total, including spectators
diff --git a/code/game/g_main.c b/code/game/g_main.c
index 9f60272..117f6d7 100644
--- a/code/game/g_main.c
+++ b/code/game/g_main.c
@@ -39,6 +39,7 @@ gentity_t g_entities[MAX_GENTITIES];
gclient_t g_clients[MAX_CLIENTS];
vmCvar_t g_gametype;
+vmCvar_t g_svDemoPlaying;
vmCvar_t g_dmflags;
vmCvar_t g_fraglimit;
vmCvar_t g_timelimit;
@@ -108,6 +109,7 @@ static cvarTable_t gameCvarTable[] = {
// latched vars
{ &g_gametype, "g_gametype", "0", CVAR_SERVERINFO | CVAR_USERINFO | CVAR_LATCH, 0, qfalse },
+ { &g_svDemoPlaying, "sv_demoplaying", "0", CVAR_ROM, 0, qfalse },
{ &g_maxclients, "sv_maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qfalse },
{ &g_maxGameClients, "g_maxGameClients", "0", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qfalse },
@@ -416,6 +418,11 @@ void G_InitGame( int levelTime, int randomSeed, int restart ) {
G_RegisterCvars();
+ // signal server-side demo mode to cgame via configstring
+ if ( g_svDemoPlaying.integer ) {
+ trap_SetConfigstring( CS_SVDEMO, "1" );
+ }
+
G_ProcessIPBans();
G_InitMemory();
@@ -764,6 +771,10 @@ void CalculateRanks( void ) {
int rank;
int score;
int newScore;
+
+ // (demo playback note: this runs normally so the spectator
+ // appears in sortedClients. Recorded players won't show here
+ // since they're not connected in the game module.)
gclient_t *cl;
level.follow1 = -1;
@@ -1729,6 +1740,89 @@ int start, end;
// get any cvar changes
G_UpdateCvars();
+ // demo playback: sync recorded player states and process spectator
+ if ( g_svDemoPlaying.integer ) {
+ gentity_t *specEnt = NULL;
+
+ // mark recorded players as connected based on their playerState.
+ // the server injected playerStates via SV_GameClientNum before
+ // calling G_RunFrame, so g_clients[i].ps is already populated.
+ for ( i = 0; i < level.maxclients; i++ ) {
+ gclient_t *cl = &level.clients[i];
+ gentity_t *e = &g_entities[i];
+
+ // find the spectator — use client-owned origin for PVS
+ if ( e->client && cl->pers.connected == CON_CONNECTED
+ && cl->sess.sessionTeam == TEAM_SPECTATOR ) {
+ // copy client-owned origin from usercmd for PVS culling.
+ // cgame runs its own PmoveSingle for camera movement.
+ if ( cl->pers.cmd.hasOrigin ) {
+ VectorCopy( cl->pers.cmd.origin, cl->ps.origin );
+ VectorCopy( cl->pers.cmd.origin, e->s.pos.trBase );
+ VectorCopy( cl->pers.cmd.origin, e->r.currentOrigin );
+ }
+ // process spectator buttons for follow mode switching
+ // (pers.cmd is updated by ClientThink which still runs)
+ {
+ int oldButtons = cl->buttons;
+ cl->oldbuttons = cl->buttons;
+ cl->buttons = cl->pers.cmd.buttons;
+ // attack cycles follow targets
+ if ( ( cl->buttons & BUTTON_ATTACK ) && !( oldButtons & BUTTON_ATTACK ) ) {
+ Cmd_FollowCycle_f( e, 1 );
+ }
+ }
+ specEnt = e;
+ continue;
+ }
+
+ // check if server injected a valid playerState for this slot
+ if ( cl->ps.commandTime > 0 ) {
+ cl->pers.connected = CON_CONNECTED;
+ cl->sess.sessionTeam = cl->ps.persistant[PERS_TEAM];
+ e->inuse = qtrue;
+ e->client = cl;
+ e->s.clientNum = i;
+ e->s.number = i;
+ } else if ( cl->pers.connected == CON_CONNECTED
+ && cl->sess.sessionTeam != TEAM_SPECTATOR ) {
+ // player left — mark disconnected
+ cl->pers.connected = CON_DISCONNECTED;
+ e->inuse = qfalse;
+ }
+ }
+
+ // evaluate trajectories and link all recorded entities for PVS.
+ // SVD_ReadFrame injected entityState_t but didn't link because
+ // the server doesn't have BG_EvaluateTrajectory.
+ // scan up to MAX_GENTITIES because recorded entities may exceed
+ // level.num_entities (which only counts game-module-spawned entities).
+ for ( i = 0; i < MAX_GENTITIES; i++ ) {
+ ent = &g_entities[i];
+ if ( ent->s.eType == 0 && !ent->inuse ) {
+ continue;
+ }
+ // skip spectator slot
+ if ( ent->client && ent->client->pers.connected == CON_CONNECTED
+ && ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
+ continue;
+ }
+ // compute currentOrigin from trajectory
+ BG_EvaluateTrajectory( &ent->s.pos, level.time, ent->r.currentOrigin );
+ ent->r.linked = qtrue;
+ trap_LinkEntity( ent );
+ }
+
+ // update rankings so follow mode can cycle through players
+ CalculateRanks();
+
+ // run end-of-frame for spectator (handles follow mode PS copy)
+ if ( specEnt ) {
+ ClientEndFrame( specEnt );
+ }
+ return;
+ }
+
//
// go through all allocated objects
//
diff --git a/code/game/g_session.c b/code/game/g_session.c
index e65c3e1..a8586f8 100644
--- a/code/game/g_session.c
+++ b/code/game/g_session.c
@@ -183,6 +183,12 @@ G_WriteSessionData
void G_WriteSessionData( void ) {
int i;
+ // don't persist demo spectator sessions — the forced TEAM_SPECTATOR
+ // would carry over to the next normal game
+ if ( g_svDemoPlaying.integer ) {
+ return;
+ }
+
trap_Cvar_Set( "session", va("%i", g_gametype.integer) );
for ( i = 0 ; i < level.maxclients ; i++ ) {
diff --git a/code/game/q_shared.h b/code/game/q_shared.h
index 99e5a67..32a772f 100644
--- a/code/game/q_shared.h
+++ b/code/game/q_shared.h
@@ -1085,6 +1085,7 @@ typedef enum {
#define SNAPFLAG_RATE_DELAYED 1
#define SNAPFLAG_NOT_ACTIVE 2 // snapshot used during connection and for zombies
#define SNAPFLAG_SERVERCOUNT 4 // toggled every map_restart so transitions can be detected
+#define SNAPFLAG_RESET_ENTITIES 16 // snap all entities to current position, no interpolation
//
// per-level limits
@@ -1252,8 +1253,11 @@ typedef struct usercmd_s {
int serverTime;
int angles[3];
int buttons;
- byte weapon; // weapon
+ byte weapon; // weapon
signed char forwardmove, rightmove, upmove;
+ // client-owned origin for demo spectator PVS (optional)
+ qboolean hasOrigin;
+ float origin[3];
} usercmd_t;
//===================================================================
diff --git a/code/qcommon/msg.c b/code/qcommon/msg.c
index 97110f8..e4f6696 100644
--- a/code/qcommon/msg.c
+++ b/code/qcommon/msg.c
@@ -702,18 +702,27 @@ void MSG_WriteDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t *
from->weapon == to->weapon) {
MSG_WriteBits( msg, 0, 1 ); // no change
oldsize += 7;
- return;
+ } else {
+ key ^= to->serverTime;
+ MSG_WriteBits( msg, 1, 1 );
+ MSG_WriteDeltaKey( msg, key, from->angles[0], to->angles[0], 16 );
+ MSG_WriteDeltaKey( msg, key, from->angles[1], to->angles[1], 16 );
+ MSG_WriteDeltaKey( msg, key, from->angles[2], to->angles[2], 16 );
+ MSG_WriteDeltaKey( msg, key, from->forwardmove, to->forwardmove, 8 );
+ MSG_WriteDeltaKey( msg, key, from->rightmove, to->rightmove, 8 );
+ MSG_WriteDeltaKey( msg, key, from->upmove, to->upmove, 8 );
+ MSG_WriteDeltaKey( msg, key, from->buttons, to->buttons, 16 );
+ MSG_WriteDeltaKey( msg, key, from->weapon, to->weapon, 8 );
+ }
+ // optional client-owned origin (always written, independent of field changes)
+ if ( to->hasOrigin ) {
+ MSG_WriteBits( msg, 1, 1 );
+ MSG_WriteFloat( msg, to->origin[0] );
+ MSG_WriteFloat( msg, to->origin[1] );
+ MSG_WriteFloat( msg, to->origin[2] );
+ } else {
+ MSG_WriteBits( msg, 0, 1 );
}
- key ^= to->serverTime;
- MSG_WriteBits( msg, 1, 1 );
- MSG_WriteDeltaKey( msg, key, from->angles[0], to->angles[0], 16 );
- MSG_WriteDeltaKey( msg, key, from->angles[1], to->angles[1], 16 );
- MSG_WriteDeltaKey( msg, key, from->angles[2], to->angles[2], 16 );
- MSG_WriteDeltaKey( msg, key, from->forwardmove, to->forwardmove, 8 );
- MSG_WriteDeltaKey( msg, key, from->rightmove, to->rightmove, 8 );
- MSG_WriteDeltaKey( msg, key, from->upmove, to->upmove, 8 );
- MSG_WriteDeltaKey( msg, key, from->buttons, to->buttons, 16 );
- MSG_WriteDeltaKey( msg, key, from->weapon, to->weapon, 8 );
}
@@ -748,6 +757,15 @@ void MSG_ReadDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t *t
to->buttons = from->buttons;
to->weapon = from->weapon;
}
+ // optional client-owned origin
+ if ( MSG_ReadBits( msg, 1 ) ) {
+ to->hasOrigin = qtrue;
+ to->origin[0] = MSG_ReadFloat( msg );
+ to->origin[1] = MSG_ReadFloat( msg );
+ to->origin[2] = MSG_ReadFloat( msg );
+ } else {
+ to->hasOrigin = qfalse;
+ }
}
/*
diff --git a/code/quake3.vcxproj b/code/quake3.vcxproj
index 340ba18..25d6f18 100644
--- a/code/quake3.vcxproj
+++ b/code/quake3.vcxproj
@@ -995,6 +995,8 @@
MaxSpeed
MaxSpeed
+
+
Disabled
true
diff --git a/code/server/lz4.c b/code/server/lz4.c
new file mode 100644
index 0000000..1af8ea4
--- /dev/null
+++ b/code/server/lz4.c
@@ -0,0 +1,2825 @@
+/*
+ LZ4 - Fast LZ compression algorithm
+ Copyright (c) Yann Collet. All rights reserved.
+
+ BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following disclaimer
+ in the documentation and/or other materials provided with the
+ distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ You can contact the author at :
+ - LZ4 homepage : http://www.lz4.org
+ - LZ4 source repository : https://github.com/lz4/lz4
+*/
+
+/*-************************************
+* Tuning parameters
+**************************************/
+/*
+ * LZ4_HEAPMODE :
+ * Select how stateless compression functions like `LZ4_compress_default()`
+ * allocate memory for their hash table,
+ * in memory stack (0:default, fastest), or in memory heap (1:requires malloc()).
+ */
+#ifndef LZ4_HEAPMODE
+# define LZ4_HEAPMODE 0
+#endif
+
+/*
+ * LZ4_ACCELERATION_DEFAULT :
+ * Select "acceleration" for LZ4_compress_fast() when parameter value <= 0
+ */
+#define LZ4_ACCELERATION_DEFAULT 1
+/*
+ * LZ4_ACCELERATION_MAX :
+ * Any "acceleration" value higher than this threshold
+ * get treated as LZ4_ACCELERATION_MAX instead (fix #876)
+ */
+#define LZ4_ACCELERATION_MAX 65537
+
+
+/*-************************************
+* CPU Feature Detection
+**************************************/
+/* LZ4_FORCE_MEMORY_ACCESS
+ * By default, access to unaligned memory is controlled by `memcpy()`, which is safe and portable.
+ * Unfortunately, on some target/compiler combinations, the generated assembly is sub-optimal.
+ * The below switch allow to select different access method for improved performance.
+ * Method 0 (default) : use `memcpy()`. Safe and portable.
+ * Method 1 : `__packed` statement. It depends on compiler extension (ie, not portable).
+ * This method is safe if your compiler supports it, and *generally* as fast or faster than `memcpy`.
+ * Method 2 : direct access. This method is portable but violate C standard.
+ * It can generate buggy code on targets which assembly generation depends on alignment.
+ * But in some circumstances, it's the only known way to get the most performance (ie GCC + ARMv6)
+ * See https://fastcompression.blogspot.fr/2015/08/accessing-unaligned-memory.html for details.
+ * Prefer these methods in priority order (0 > 1 > 2)
+ */
+#ifndef LZ4_FORCE_MEMORY_ACCESS /* can be defined externally */
+# if defined(__GNUC__) && \
+ ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) \
+ || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) \
+ || (defined(__riscv) && defined(__riscv_zicclsm)) )
+# define LZ4_FORCE_MEMORY_ACCESS 2
+# elif (defined(__INTEL_COMPILER) && !defined(_WIN32)) || defined(__GNUC__) || defined(_MSC_VER)
+# define LZ4_FORCE_MEMORY_ACCESS 1
+# endif
+#endif
+
+/*
+ * LZ4_FORCE_SW_BITCOUNT
+ * Define this parameter if your target system or compiler does not support hardware bit count
+ */
+#if defined(_MSC_VER) && defined(_WIN32_WCE) /* Visual Studio for WinCE doesn't support Hardware bit count */
+# undef LZ4_FORCE_SW_BITCOUNT /* avoid double def */
+# define LZ4_FORCE_SW_BITCOUNT
+#endif
+
+
+
+/*-************************************
+* Dependency
+**************************************/
+/*
+ * LZ4_SRC_INCLUDED:
+ * Amalgamation flag, whether lz4.c is included
+ */
+#ifndef LZ4_SRC_INCLUDED
+# define LZ4_SRC_INCLUDED 1
+#endif
+
+#ifndef LZ4_DISABLE_DEPRECATE_WARNINGS
+# define LZ4_DISABLE_DEPRECATE_WARNINGS /* due to LZ4_decompress_safe_withPrefix64k */
+#endif
+
+#ifndef LZ4_STATIC_LINKING_ONLY
+# define LZ4_STATIC_LINKING_ONLY
+#endif
+#include "lz4.h"
+/* see also "memory routines" below */
+
+
+/*-************************************
+* Compiler Options
+**************************************/
+#if defined(_MSC_VER) && (_MSC_VER >= 1400) /* Visual Studio 2005+ */
+# include /* only present in VS2005+ */
+# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */
+# pragma warning(disable : 6237) /* disable: C6237: conditional expression is always 0 */
+# pragma warning(disable : 6239) /* disable: C6239: ( && ) always evaluates to the result of */
+# pragma warning(disable : 6240) /* disable: C6240: ( && ) always evaluates to the result of */
+# pragma warning(disable : 6326) /* disable: C6326: Potential comparison of a constant with another constant */
+#endif /* _MSC_VER */
+
+#ifndef LZ4_FORCE_INLINE
+# if defined (_MSC_VER) && !defined (__clang__) /* MSVC */
+# define LZ4_FORCE_INLINE static __forceinline
+# else
+# if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */
+# if defined (__GNUC__) || defined (__clang__)
+# define LZ4_FORCE_INLINE static inline __attribute__((always_inline))
+# else
+# define LZ4_FORCE_INLINE static inline
+# endif
+# else
+# define LZ4_FORCE_INLINE static
+# endif /* __STDC_VERSION__ */
+# endif /* _MSC_VER */
+#endif /* LZ4_FORCE_INLINE */
+
+/* LZ4_FORCE_O2 and LZ4_FORCE_INLINE
+ * gcc on ppc64le generates an unrolled SIMDized loop for LZ4_wildCopy8,
+ * together with a simple 8-byte copy loop as a fall-back path.
+ * However, this optimization hurts the decompression speed by >30%,
+ * because the execution does not go to the optimized loop
+ * for typical compressible data, and all of the preamble checks
+ * before going to the fall-back path become useless overhead.
+ * This optimization happens only with the -O3 flag, and -O2 generates
+ * a simple 8-byte copy loop.
+ * With gcc on ppc64le, all of the LZ4_decompress_* and LZ4_wildCopy8
+ * functions are annotated with __attribute__((optimize("O2"))),
+ * and also LZ4_wildCopy8 is forcibly inlined, so that the O2 attribute
+ * of LZ4_wildCopy8 does not affect the compression speed.
+ */
+#if defined(__PPC64__) && defined(__LITTLE_ENDIAN__) && defined(__GNUC__) && !defined(__clang__)
+# define LZ4_FORCE_O2 __attribute__((optimize("O2")))
+# undef LZ4_FORCE_INLINE
+# define LZ4_FORCE_INLINE static __inline __attribute__((optimize("O2"),always_inline))
+#else
+# define LZ4_FORCE_O2
+#endif
+
+#if (defined(__GNUC__) && (__GNUC__ >= 3)) || (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 800)) || defined(__clang__)
+# define expect(expr,value) (__builtin_expect ((expr),(value)) )
+#else
+# define expect(expr,value) (expr)
+#endif
+
+#ifndef likely
+#define likely(expr) expect((expr) != 0, 1)
+#endif
+#ifndef unlikely
+#define unlikely(expr) expect((expr) != 0, 0)
+#endif
+
+/* Should the alignment test prove unreliable, for some reason,
+ * it can be disabled by setting LZ4_ALIGN_TEST to 0 */
+#ifndef LZ4_ALIGN_TEST /* can be externally provided */
+# define LZ4_ALIGN_TEST 1
+#endif
+
+
+/*-************************************
+* Memory routines
+**************************************/
+
+/*! LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION :
+ * Disable relatively high-level LZ4/HC functions that use dynamic memory
+ * allocation functions (malloc(), calloc(), free()).
+ *
+ * Note that this is a compile-time switch. And since it disables
+ * public/stable LZ4 v1 API functions, we don't recommend using this
+ * symbol to generate a library for distribution.
+ *
+ * The following public functions are removed when this symbol is defined.
+ * - lz4 : LZ4_createStream, LZ4_freeStream,
+ * LZ4_createStreamDecode, LZ4_freeStreamDecode, LZ4_create (deprecated)
+ * - lz4hc : LZ4_createStreamHC, LZ4_freeStreamHC,
+ * LZ4_createHC (deprecated), LZ4_freeHC (deprecated)
+ * - lz4frame, lz4file : All LZ4F_* functions
+ */
+#if defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION)
+# define ALLOC(s) lz4_error_memory_allocation_is_disabled
+# define ALLOC_AND_ZERO(s) lz4_error_memory_allocation_is_disabled
+# define FREEMEM(p) lz4_error_memory_allocation_is_disabled
+#elif defined(LZ4_USER_MEMORY_FUNCTIONS)
+/* memory management functions can be customized by user project.
+ * Below functions must exist somewhere in the Project
+ * and be available at link time */
+void* LZ4_malloc(size_t s);
+void* LZ4_calloc(size_t n, size_t s);
+void LZ4_free(void* p);
+# define ALLOC(s) LZ4_malloc(s)
+# define ALLOC_AND_ZERO(s) LZ4_calloc(1,s)
+# define FREEMEM(p) LZ4_free(p)
+#else
+# include /* malloc, calloc, free */
+# define ALLOC(s) malloc(s)
+# define ALLOC_AND_ZERO(s) calloc(1,s)
+# define FREEMEM(p) free(p)
+#endif
+
+#if ! LZ4_FREESTANDING
+# include /* memset, memcpy */
+#endif
+#if !defined(LZ4_memset)
+# define LZ4_memset(p,v,s) memset((p),(v),(s))
+#endif
+#define MEM_INIT(p,v,s) LZ4_memset((p),(v),(s))
+
+
+/*-************************************
+* Common Constants
+**************************************/
+#define MINMATCH 4
+
+#define WILDCOPYLENGTH 8
+#define LASTLITERALS 5 /* see ../doc/lz4_Block_format.md#parsing-restrictions */
+#define MFLIMIT 12 /* see ../doc/lz4_Block_format.md#parsing-restrictions */
+#define MATCH_SAFEGUARD_DISTANCE ((2*WILDCOPYLENGTH) - MINMATCH) /* ensure it's possible to write 2 x wildcopyLength without overflowing output buffer */
+#define FASTLOOP_SAFE_DISTANCE 64
+static const int LZ4_minLength = (MFLIMIT+1);
+
+#define KB *(1 <<10)
+#define MB *(1 <<20)
+#define GB *(1U<<30)
+
+#define LZ4_DISTANCE_ABSOLUTE_MAX 65535
+#if (LZ4_DISTANCE_MAX > LZ4_DISTANCE_ABSOLUTE_MAX) /* max supported by LZ4 format */
+# error "LZ4_DISTANCE_MAX is too big : must be <= 65535"
+#endif
+
+#define ML_BITS 4
+#define ML_MASK ((1U<=1)
+# include
+#else
+# ifndef assert
+# define assert(condition) ((void)0)
+# endif
+#endif
+
+#define LZ4_STATIC_ASSERT(c) { enum { LZ4_static_assert = 1/(int)(!!(c)) }; } /* use after variable declarations */
+
+#if defined(LZ4_DEBUG) && (LZ4_DEBUG>=2)
+# include
+ static int g_debuglog_enable = 1;
+# define DEBUGLOG(l, ...) { \
+ if ((g_debuglog_enable) && (l<=LZ4_DEBUG)) { \
+ fprintf(stderr, __FILE__ " %i: ", __LINE__); \
+ fprintf(stderr, __VA_ARGS__); \
+ fprintf(stderr, " \n"); \
+ } }
+#else
+# define DEBUGLOG(l, ...) {} /* disabled */
+#endif
+
+static int LZ4_isAligned(const void* ptr, size_t alignment)
+{
+ return ((size_t)ptr & (alignment -1)) == 0;
+}
+
+
+/*-************************************
+* Types
+**************************************/
+#include
+#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */)
+# include
+ typedef unsigned char BYTE; /*uint8_t not necessarily blessed to alias arbitrary type*/
+ typedef uint16_t U16;
+ typedef uint32_t U32;
+ typedef int32_t S32;
+ typedef uint64_t U64;
+ typedef uintptr_t uptrval;
+#else
+# if UINT_MAX != 4294967295UL
+# error "LZ4 code (when not C++ or C99) assumes that sizeof(int) == 4"
+# endif
+ typedef unsigned char BYTE;
+ typedef unsigned short U16;
+ typedef unsigned int U32;
+ typedef signed int S32;
+ typedef unsigned long long U64;
+ typedef size_t uptrval; /* generally true, except OpenVMS-64 */
+#endif
+
+#if defined(__x86_64__)
+ typedef U64 reg_t; /* 64-bits in x32 mode */
+#else
+ typedef size_t reg_t; /* 32-bits in x32 mode */
+#endif
+
+typedef enum {
+ notLimited = 0,
+ limitedOutput = 1,
+ fillOutput = 2
+} limitedOutput_directive;
+
+
+/*-************************************
+* Reading and writing into memory
+**************************************/
+
+/**
+ * LZ4 relies on memcpy with a constant size being inlined. In freestanding
+ * environments, the compiler can't assume the implementation of memcpy() is
+ * standard compliant, so it can't apply its specialized memcpy() inlining
+ * logic. When possible, use __builtin_memcpy() to tell the compiler to analyze
+ * memcpy() as if it were standard compliant, so it can inline it in freestanding
+ * environments. This is needed when decompressing the Linux Kernel, for example.
+ */
+#if !defined(LZ4_memcpy)
+# if defined(__GNUC__) && (__GNUC__ >= 4)
+# define LZ4_memcpy(dst, src, size) __builtin_memcpy(dst, src, size)
+# else
+# define LZ4_memcpy(dst, src, size) memcpy(dst, src, size)
+# endif
+#endif
+
+#if !defined(LZ4_memmove)
+# if defined(__GNUC__) && (__GNUC__ >= 4)
+# define LZ4_memmove __builtin_memmove
+# else
+# define LZ4_memmove memmove
+# endif
+#endif
+
+static unsigned LZ4_isLittleEndian(void)
+{
+ const union { U32 u; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */
+ return one.c[0];
+}
+
+#if defined(__GNUC__) || defined(__INTEL_COMPILER)
+#define LZ4_PACK( __Declaration__ ) __Declaration__ __attribute__((__packed__))
+#elif defined(_MSC_VER)
+#define LZ4_PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop))
+#endif
+
+#if defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==2)
+/* lie to the compiler about data alignment; use with caution */
+
+static U16 LZ4_read16(const void* memPtr) { return *(const U16*) memPtr; }
+static U32 LZ4_read32(const void* memPtr) { return *(const U32*) memPtr; }
+static reg_t LZ4_read_ARCH(const void* memPtr) { return *(const reg_t*) memPtr; }
+
+static void LZ4_write16(void* memPtr, U16 value) { *(U16*)memPtr = value; }
+static void LZ4_write32(void* memPtr, U32 value) { *(U32*)memPtr = value; }
+
+#elif defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==1)
+
+/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */
+/* currently only defined for gcc and icc */
+LZ4_PACK(typedef struct { U16 u16; }) LZ4_unalign16;
+LZ4_PACK(typedef struct { U32 u32; }) LZ4_unalign32;
+LZ4_PACK(typedef struct { reg_t uArch; }) LZ4_unalignST;
+
+static U16 LZ4_read16(const void* ptr) { return ((const LZ4_unalign16*)ptr)->u16; }
+static U32 LZ4_read32(const void* ptr) { return ((const LZ4_unalign32*)ptr)->u32; }
+static reg_t LZ4_read_ARCH(const void* ptr) { return ((const LZ4_unalignST*)ptr)->uArch; }
+
+static void LZ4_write16(void* memPtr, U16 value) { ((LZ4_unalign16*)memPtr)->u16 = value; }
+static void LZ4_write32(void* memPtr, U32 value) { ((LZ4_unalign32*)memPtr)->u32 = value; }
+
+#else /* safe and portable access using memcpy() */
+
+static U16 LZ4_read16(const void* memPtr)
+{
+ U16 val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val;
+}
+
+static U32 LZ4_read32(const void* memPtr)
+{
+ U32 val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val;
+}
+
+static reg_t LZ4_read_ARCH(const void* memPtr)
+{
+ reg_t val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val;
+}
+
+static void LZ4_write16(void* memPtr, U16 value)
+{
+ LZ4_memcpy(memPtr, &value, sizeof(value));
+}
+
+static void LZ4_write32(void* memPtr, U32 value)
+{
+ LZ4_memcpy(memPtr, &value, sizeof(value));
+}
+
+#endif /* LZ4_FORCE_MEMORY_ACCESS */
+
+
+static U16 LZ4_readLE16(const void* memPtr)
+{
+ if (LZ4_isLittleEndian()) {
+ return LZ4_read16(memPtr);
+ } else {
+ const BYTE* p = (const BYTE*)memPtr;
+ return (U16)((U16)p[0] | (p[1]<<8));
+ }
+}
+
+#ifdef LZ4_STATIC_LINKING_ONLY_ENDIANNESS_INDEPENDENT_OUTPUT
+static U32 LZ4_readLE32(const void* memPtr)
+{
+ if (LZ4_isLittleEndian()) {
+ return LZ4_read32(memPtr);
+ } else {
+ const BYTE* p = (const BYTE*)memPtr;
+ return (U32)p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24);
+ }
+}
+#endif
+
+static void LZ4_writeLE16(void* memPtr, U16 value)
+{
+ if (LZ4_isLittleEndian()) {
+ LZ4_write16(memPtr, value);
+ } else {
+ BYTE* p = (BYTE*)memPtr;
+ p[0] = (BYTE) value;
+ p[1] = (BYTE)(value>>8);
+ }
+}
+
+/* customized variant of memcpy, which can overwrite up to 8 bytes beyond dstEnd */
+LZ4_FORCE_INLINE
+void LZ4_wildCopy8(void* dstPtr, const void* srcPtr, void* dstEnd)
+{
+ BYTE* d = (BYTE*)dstPtr;
+ const BYTE* s = (const BYTE*)srcPtr;
+ BYTE* const e = (BYTE*)dstEnd;
+
+ do { LZ4_memcpy(d,s,8); d+=8; s+=8; } while (d= 16. */
+LZ4_FORCE_INLINE void
+LZ4_wildCopy32(void* dstPtr, const void* srcPtr, void* dstEnd)
+{
+ BYTE* d = (BYTE*)dstPtr;
+ const BYTE* s = (const BYTE*)srcPtr;
+ BYTE* const e = (BYTE*)dstEnd;
+
+ do { LZ4_memcpy(d,s,16); LZ4_memcpy(d+16,s+16,16); d+=32; s+=32; } while (d= dstPtr + MINMATCH
+ * - there is at least 12 bytes available to write after dstEnd */
+LZ4_FORCE_INLINE void
+LZ4_memcpy_using_offset(BYTE* dstPtr, const BYTE* srcPtr, BYTE* dstEnd, const size_t offset)
+{
+ BYTE v[8];
+
+ assert(dstEnd >= dstPtr + MINMATCH);
+
+ switch(offset) {
+ case 1:
+ MEM_INIT(v, *srcPtr, 8);
+ break;
+ case 2:
+ LZ4_memcpy(v, srcPtr, 2);
+ LZ4_memcpy(&v[2], srcPtr, 2);
+#if defined(_MSC_VER) && (_MSC_VER <= 1937) /* MSVC 2022 ver 17.7 or earlier */
+# pragma warning(push)
+# pragma warning(disable : 6385) /* warning C6385: Reading invalid data from 'v'. */
+#endif
+ LZ4_memcpy(&v[4], v, 4);
+#if defined(_MSC_VER) && (_MSC_VER <= 1937) /* MSVC 2022 ver 17.7 or earlier */
+# pragma warning(pop)
+#endif
+ break;
+ case 4:
+ LZ4_memcpy(v, srcPtr, 4);
+ LZ4_memcpy(&v[4], srcPtr, 4);
+ break;
+ default:
+ LZ4_memcpy_using_offset_base(dstPtr, srcPtr, dstEnd, offset);
+ return;
+ }
+
+ LZ4_memcpy(dstPtr, v, 8);
+ dstPtr += 8;
+ while (dstPtr < dstEnd) {
+ LZ4_memcpy(dstPtr, v, 8);
+ dstPtr += 8;
+ }
+}
+#endif
+
+
+/*-************************************
+* Common functions
+**************************************/
+static unsigned LZ4_NbCommonBytes (reg_t val)
+{
+ assert(val != 0);
+ if (LZ4_isLittleEndian()) {
+ if (sizeof(val) == 8) {
+# if defined(_MSC_VER) && (_MSC_VER >= 1800) && (defined(_M_AMD64) && !defined(_M_ARM64EC)) && !defined(LZ4_FORCE_SW_BITCOUNT)
+/*-*************************************************************************************************
+* ARM64EC is a Microsoft-designed ARM64 ABI compatible with AMD64 applications on ARM64 Windows 11.
+* The ARM64EC ABI does not support AVX/AVX2/AVX512 instructions, nor their relevant intrinsics
+* including _tzcnt_u64. Therefore, we need to neuter the _tzcnt_u64 code path for ARM64EC.
+****************************************************************************************************/
+# if defined(__clang__) && (__clang_major__ < 10)
+ /* Avoid undefined clang-cl intrinsics issue.
+ * See https://github.com/lz4/lz4/pull/1017 for details. */
+ return (unsigned)__builtin_ia32_tzcnt_u64(val) >> 3;
+# else
+ /* x64 CPUS without BMI support interpret `TZCNT` as `REP BSF` */
+ return (unsigned)_tzcnt_u64(val) >> 3;
+# endif
+# elif defined(_MSC_VER) && defined(_WIN64) && !defined(LZ4_FORCE_SW_BITCOUNT)
+ unsigned long r = 0;
+ _BitScanForward64(&r, (U64)val);
+ return (unsigned)r >> 3;
+# elif (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \
+ ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \
+ !defined(LZ4_FORCE_SW_BITCOUNT)
+ return (unsigned)__builtin_ctzll((U64)val) >> 3;
+# else
+ const U64 m = 0x0101010101010101ULL;
+ val ^= val - 1;
+ return (unsigned)(((U64)((val & (m - 1)) * m)) >> 56);
+# endif
+ } else /* 32 bits */ {
+# if defined(_MSC_VER) && (_MSC_VER >= 1400) && !defined(LZ4_FORCE_SW_BITCOUNT)
+ unsigned long r;
+ _BitScanForward(&r, (U32)val);
+ return (unsigned)r >> 3;
+# elif (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \
+ ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \
+ !defined(__TINYC__) && !defined(LZ4_FORCE_SW_BITCOUNT)
+ return (unsigned)__builtin_ctz((U32)val) >> 3;
+# else
+ const U32 m = 0x01010101;
+ return (unsigned)((((val - 1) ^ val) & (m - 1)) * m) >> 24;
+# endif
+ }
+ } else /* Big Endian CPU */ {
+ if (sizeof(val)==8) {
+# if (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \
+ ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \
+ !defined(__TINYC__) && !defined(LZ4_FORCE_SW_BITCOUNT)
+ return (unsigned)__builtin_clzll((U64)val) >> 3;
+# else
+#if 1
+ /* this method is probably faster,
+ * but adds a 128 bytes lookup table */
+ static const unsigned char ctz7_tab[128] = {
+ 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+ 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+ };
+ U64 const mask = 0x0101010101010101ULL;
+ U64 const t = (((val >> 8) - mask) | val) & mask;
+ return ctz7_tab[(t * 0x0080402010080402ULL) >> 57];
+#else
+ /* this method doesn't consume memory space like the previous one,
+ * but it contains several branches,
+ * that may end up slowing execution */
+ static const U32 by32 = sizeof(val)*4; /* 32 on 64 bits (goal), 16 on 32 bits.
+ Just to avoid some static analyzer complaining about shift by 32 on 32-bits target.
+ Note that this code path is never triggered in 32-bits mode. */
+ unsigned r;
+ if (!(val>>by32)) { r=4; } else { r=0; val>>=by32; }
+ if (!(val>>16)) { r+=2; val>>=8; } else { val>>=24; }
+ r += (!val);
+ return r;
+#endif
+# endif
+ } else /* 32 bits */ {
+# if (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \
+ ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \
+ !defined(LZ4_FORCE_SW_BITCOUNT)
+ return (unsigned)__builtin_clz((U32)val) >> 3;
+# else
+ val >>= 8;
+ val = ((((val + 0x00FFFF00) | 0x00FFFFFF) + val) |
+ (val + 0x00FF0000)) >> 24;
+ return (unsigned)val ^ 3;
+# endif
+ }
+ }
+}
+
+
+#define STEPSIZE sizeof(reg_t)
+LZ4_FORCE_INLINE
+unsigned LZ4_count(const BYTE* pIn, const BYTE* pMatch, const BYTE* pInLimit)
+{
+ const BYTE* const pStart = pIn;
+
+ if (likely(pIn < pInLimit-(STEPSIZE-1))) {
+ reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn);
+ if (!diff) {
+ pIn+=STEPSIZE; pMatch+=STEPSIZE;
+ } else {
+ return LZ4_NbCommonBytes(diff);
+ } }
+
+ while (likely(pIn < pInLimit-(STEPSIZE-1))) {
+ reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn);
+ if (!diff) { pIn+=STEPSIZE; pMatch+=STEPSIZE; continue; }
+ pIn += LZ4_NbCommonBytes(diff);
+ return (unsigned)(pIn - pStart);
+ }
+
+ if ((STEPSIZE==8) && (pIn<(pInLimit-3)) && (LZ4_read32(pMatch) == LZ4_read32(pIn))) { pIn+=4; pMatch+=4; }
+ if ((pIn<(pInLimit-1)) && (LZ4_read16(pMatch) == LZ4_read16(pIn))) { pIn+=2; pMatch+=2; }
+ if ((pIn compression run slower on incompressible data */
+
+
+/*-************************************
+* Local Structures and types
+**************************************/
+typedef enum { clearedTable = 0, byPtr, byU32, byU16 } tableType_t;
+
+/**
+ * This enum distinguishes several different modes of accessing previous
+ * content in the stream.
+ *
+ * - noDict : There is no preceding content.
+ * - withPrefix64k : Table entries up to ctx->dictSize before the current blob
+ * blob being compressed are valid and refer to the preceding
+ * content (of length ctx->dictSize), which is available
+ * contiguously preceding in memory the content currently
+ * being compressed.
+ * - usingExtDict : Like withPrefix64k, but the preceding content is somewhere
+ * else in memory, starting at ctx->dictionary with length
+ * ctx->dictSize.
+ * - usingDictCtx : Everything concerning the preceding content is
+ * in a separate context, pointed to by ctx->dictCtx.
+ * ctx->dictionary, ctx->dictSize, and table entries
+ * in the current context that refer to positions
+ * preceding the beginning of the current compression are
+ * ignored. Instead, ctx->dictCtx->dictionary and ctx->dictCtx
+ * ->dictSize describe the location and size of the preceding
+ * content, and matches are found by looking in the ctx
+ * ->dictCtx->hashTable.
+ */
+typedef enum { noDict = 0, withPrefix64k, usingExtDict, usingDictCtx } dict_directive;
+typedef enum { noDictIssue = 0, dictSmall } dictIssue_directive;
+
+
+/*-************************************
+* Local Utils
+**************************************/
+int LZ4_versionNumber (void) { return LZ4_VERSION_NUMBER; }
+const char* LZ4_versionString(void) { return LZ4_VERSION_STRING; }
+int LZ4_compressBound(int isize) { return LZ4_COMPRESSBOUND(isize); }
+int LZ4_sizeofState(void) { return sizeof(LZ4_stream_t); }
+
+
+/*-****************************************
+* Internal Definitions, used only in Tests
+*******************************************/
+#if defined (__cplusplus)
+extern "C" {
+#endif
+
+int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* dest, int srcSize);
+
+int LZ4_decompress_safe_forceExtDict(const char* source, char* dest,
+ int compressedSize, int maxOutputSize,
+ const void* dictStart, size_t dictSize);
+int LZ4_decompress_safe_partial_forceExtDict(const char* source, char* dest,
+ int compressedSize, int targetOutputSize, int dstCapacity,
+ const void* dictStart, size_t dictSize);
+#if defined (__cplusplus)
+}
+#endif
+
+/*-******************************
+* Compression functions
+********************************/
+LZ4_FORCE_INLINE U32 LZ4_hash4(U32 sequence, tableType_t const tableType)
+{
+ if (tableType == byU16)
+ return ((sequence * 2654435761U) >> ((MINMATCH*8)-(LZ4_HASHLOG+1)));
+ else
+ return ((sequence * 2654435761U) >> ((MINMATCH*8)-LZ4_HASHLOG));
+}
+
+LZ4_FORCE_INLINE U32 LZ4_hash5(U64 sequence, tableType_t const tableType)
+{
+ const U32 hashLog = (tableType == byU16) ? LZ4_HASHLOG+1 : LZ4_HASHLOG;
+ if (LZ4_isLittleEndian()) {
+ const U64 prime5bytes = 889523592379ULL;
+ return (U32)(((sequence << 24) * prime5bytes) >> (64 - hashLog));
+ } else {
+ const U64 prime8bytes = 11400714785074694791ULL;
+ return (U32)(((sequence >> 24) * prime8bytes) >> (64 - hashLog));
+ }
+}
+
+LZ4_FORCE_INLINE U32 LZ4_hashPosition(const void* const p, tableType_t const tableType)
+{
+ if ((sizeof(reg_t)==8) && (tableType != byU16)) return LZ4_hash5(LZ4_read_ARCH(p), tableType);
+
+#ifdef LZ4_STATIC_LINKING_ONLY_ENDIANNESS_INDEPENDENT_OUTPUT
+ return LZ4_hash4(LZ4_readLE32(p), tableType);
+#else
+ return LZ4_hash4(LZ4_read32(p), tableType);
+#endif
+}
+
+LZ4_FORCE_INLINE void LZ4_clearHash(U32 h, void* tableBase, tableType_t const tableType)
+{
+ switch (tableType)
+ {
+ default: /* fallthrough */
+ case clearedTable: { /* illegal! */ assert(0); return; }
+ case byPtr: { const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = NULL; return; }
+ case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = 0; return; }
+ case byU16: { U16* hashTable = (U16*) tableBase; hashTable[h] = 0; return; }
+ }
+}
+
+LZ4_FORCE_INLINE void LZ4_putIndexOnHash(U32 idx, U32 h, void* tableBase, tableType_t const tableType)
+{
+ switch (tableType)
+ {
+ default: /* fallthrough */
+ case clearedTable: /* fallthrough */
+ case byPtr: { /* illegal! */ assert(0); return; }
+ case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = idx; return; }
+ case byU16: { U16* hashTable = (U16*) tableBase; assert(idx < 65536); hashTable[h] = (U16)idx; return; }
+ }
+}
+
+/* LZ4_putPosition*() : only used in byPtr mode */
+LZ4_FORCE_INLINE void LZ4_putPositionOnHash(const BYTE* p, U32 h,
+ void* tableBase, tableType_t const tableType)
+{
+ const BYTE** const hashTable = (const BYTE**)tableBase;
+ assert(tableType == byPtr); (void)tableType;
+ hashTable[h] = p;
+}
+
+LZ4_FORCE_INLINE void LZ4_putPosition(const BYTE* p, void* tableBase, tableType_t tableType)
+{
+ U32 const h = LZ4_hashPosition(p, tableType);
+ LZ4_putPositionOnHash(p, h, tableBase, tableType);
+}
+
+/* LZ4_getIndexOnHash() :
+ * Index of match position registered in hash table.
+ * hash position must be calculated by using base+index, or dictBase+index.
+ * Assumption 1 : only valid if tableType == byU32 or byU16.
+ * Assumption 2 : h is presumed valid (within limits of hash table)
+ */
+LZ4_FORCE_INLINE U32 LZ4_getIndexOnHash(U32 h, const void* tableBase, tableType_t tableType)
+{
+ LZ4_STATIC_ASSERT(LZ4_MEMORY_USAGE > 2);
+ if (tableType == byU32) {
+ const U32* const hashTable = (const U32*) tableBase;
+ assert(h < (1U << (LZ4_MEMORY_USAGE-2)));
+ return hashTable[h];
+ }
+ if (tableType == byU16) {
+ const U16* const hashTable = (const U16*) tableBase;
+ assert(h < (1U << (LZ4_MEMORY_USAGE-1)));
+ return hashTable[h];
+ }
+ assert(0); return 0; /* forbidden case */
+}
+
+static const BYTE* LZ4_getPositionOnHash(U32 h, const void* tableBase, tableType_t tableType)
+{
+ assert(tableType == byPtr); (void)tableType;
+ { const BYTE* const* hashTable = (const BYTE* const*) tableBase; return hashTable[h]; }
+}
+
+LZ4_FORCE_INLINE const BYTE*
+LZ4_getPosition(const BYTE* p,
+ const void* tableBase, tableType_t tableType)
+{
+ U32 const h = LZ4_hashPosition(p, tableType);
+ return LZ4_getPositionOnHash(h, tableBase, tableType);
+}
+
+LZ4_FORCE_INLINE void
+LZ4_prepareTable(LZ4_stream_t_internal* const cctx,
+ const int inputSize,
+ const tableType_t tableType) {
+ /* If the table hasn't been used, it's guaranteed to be zeroed out, and is
+ * therefore safe to use no matter what mode we're in. Otherwise, we figure
+ * out if it's safe to leave as is or whether it needs to be reset.
+ */
+ if ((tableType_t)cctx->tableType != clearedTable) {
+ assert(inputSize >= 0);
+ if ((tableType_t)cctx->tableType != tableType
+ || ((tableType == byU16) && cctx->currentOffset + (unsigned)inputSize >= 0xFFFFU)
+ || ((tableType == byU32) && cctx->currentOffset > 1 GB)
+ || tableType == byPtr
+ || inputSize >= 4 KB)
+ {
+ DEBUGLOG(4, "LZ4_prepareTable: Resetting table in %p", (void*)cctx);
+ MEM_INIT(cctx->hashTable, 0, LZ4_HASHTABLESIZE);
+ cctx->currentOffset = 0;
+ cctx->tableType = (U32)clearedTable;
+ } else {
+ DEBUGLOG(4, "LZ4_prepareTable: Re-use hash table (no reset)");
+ }
+ }
+
+ /* Adding a gap, so all previous entries are > LZ4_DISTANCE_MAX back,
+ * is faster than compressing without a gap.
+ * However, compressing with currentOffset == 0 is faster still,
+ * so we preserve that case.
+ */
+ if (cctx->currentOffset != 0 && tableType == byU32) {
+ DEBUGLOG(5, "LZ4_prepareTable: adding 64KB to currentOffset");
+ cctx->currentOffset += 64 KB;
+ }
+
+ /* Finally, clear history */
+ cctx->dictCtx = NULL;
+ cctx->dictionary = NULL;
+ cctx->dictSize = 0;
+}
+
+/** LZ4_compress_generic_validated() :
+ * inlined, to ensure branches are decided at compilation time.
+ * The following conditions are presumed already validated:
+ * - source != NULL
+ * - inputSize > 0
+ */
+LZ4_FORCE_INLINE int LZ4_compress_generic_validated(
+ LZ4_stream_t_internal* const cctx,
+ const char* const source,
+ char* const dest,
+ const int inputSize,
+ int* inputConsumed, /* only written when outputDirective == fillOutput */
+ const int maxOutputSize,
+ const limitedOutput_directive outputDirective,
+ const tableType_t tableType,
+ const dict_directive dictDirective,
+ const dictIssue_directive dictIssue,
+ const int acceleration)
+{
+ int result;
+ const BYTE* ip = (const BYTE*)source;
+
+ U32 const startIndex = cctx->currentOffset;
+ const BYTE* base = (const BYTE*)source - startIndex;
+ const BYTE* lowLimit;
+
+ const LZ4_stream_t_internal* dictCtx = (const LZ4_stream_t_internal*) cctx->dictCtx;
+ const BYTE* const dictionary =
+ dictDirective == usingDictCtx ? dictCtx->dictionary : cctx->dictionary;
+ const U32 dictSize =
+ dictDirective == usingDictCtx ? dictCtx->dictSize : cctx->dictSize;
+ const U32 dictDelta =
+ (dictDirective == usingDictCtx) ? startIndex - dictCtx->currentOffset : 0; /* make indexes in dictCtx comparable with indexes in current context */
+
+ int const maybe_extMem = (dictDirective == usingExtDict) || (dictDirective == usingDictCtx);
+ U32 const prefixIdxLimit = startIndex - dictSize; /* used when dictDirective == dictSmall */
+ const BYTE* const dictEnd = dictionary ? dictionary + dictSize : dictionary;
+ const BYTE* anchor = (const BYTE*) source;
+ const BYTE* const iend = ip + inputSize;
+ const BYTE* const mflimitPlusOne = iend - MFLIMIT + 1;
+ const BYTE* const matchlimit = iend - LASTLITERALS;
+
+ /* the dictCtx currentOffset is indexed on the start of the dictionary,
+ * while a dictionary in the current context precedes the currentOffset */
+ const BYTE* dictBase = (dictionary == NULL) ? NULL :
+ (dictDirective == usingDictCtx) ?
+ dictionary + dictSize - dictCtx->currentOffset :
+ dictionary + dictSize - startIndex;
+
+ BYTE* op = (BYTE*) dest;
+ BYTE* const olimit = op + maxOutputSize;
+
+ U32 offset = 0;
+ U32 forwardH;
+
+ DEBUGLOG(5, "LZ4_compress_generic_validated: srcSize=%i, tableType=%u", inputSize, tableType);
+ assert(ip != NULL);
+ if (tableType == byU16) assert(inputSize= 1);
+
+ lowLimit = (const BYTE*)source - (dictDirective == withPrefix64k ? dictSize : 0);
+
+ /* Update context state */
+ if (dictDirective == usingDictCtx) {
+ /* Subsequent linked blocks can't use the dictionary. */
+ /* Instead, they use the block we just compressed. */
+ cctx->dictCtx = NULL;
+ cctx->dictSize = (U32)inputSize;
+ } else {
+ cctx->dictSize += (U32)inputSize;
+ }
+ cctx->currentOffset += (U32)inputSize;
+ cctx->tableType = (U32)tableType;
+
+ if (inputSizehashTable, byPtr);
+ } else {
+ LZ4_putIndexOnHash(startIndex, h, cctx->hashTable, tableType);
+ } }
+ ip++; forwardH = LZ4_hashPosition(ip, tableType);
+
+ /* Main Loop */
+ for ( ; ; ) {
+ const BYTE* match;
+ BYTE* token;
+ const BYTE* filledIp;
+
+ /* Find a match */
+ if (tableType == byPtr) {
+ const BYTE* forwardIp = ip;
+ int step = 1;
+ int searchMatchNb = acceleration << LZ4_skipTrigger;
+ do {
+ U32 const h = forwardH;
+ ip = forwardIp;
+ forwardIp += step;
+ step = (searchMatchNb++ >> LZ4_skipTrigger);
+
+ if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals;
+ assert(ip < mflimitPlusOne);
+
+ match = LZ4_getPositionOnHash(h, cctx->hashTable, tableType);
+ forwardH = LZ4_hashPosition(forwardIp, tableType);
+ LZ4_putPositionOnHash(ip, h, cctx->hashTable, tableType);
+
+ } while ( (match+LZ4_DISTANCE_MAX < ip)
+ || (LZ4_read32(match) != LZ4_read32(ip)) );
+
+ } else { /* byU32, byU16 */
+
+ const BYTE* forwardIp = ip;
+ int step = 1;
+ int searchMatchNb = acceleration << LZ4_skipTrigger;
+ do {
+ U32 const h = forwardH;
+ U32 const current = (U32)(forwardIp - base);
+ U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType);
+ assert(matchIndex <= current);
+ assert(forwardIp - base < (ptrdiff_t)(2 GB - 1));
+ ip = forwardIp;
+ forwardIp += step;
+ step = (searchMatchNb++ >> LZ4_skipTrigger);
+
+ if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals;
+ assert(ip < mflimitPlusOne);
+
+ if (dictDirective == usingDictCtx) {
+ if (matchIndex < startIndex) {
+ /* there was no match, try the dictionary */
+ assert(tableType == byU32);
+ matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32);
+ match = dictBase + matchIndex;
+ matchIndex += dictDelta; /* make dictCtx index comparable with current context */
+ lowLimit = dictionary;
+ } else {
+ match = base + matchIndex;
+ lowLimit = (const BYTE*)source;
+ }
+ } else if (dictDirective == usingExtDict) {
+ if (matchIndex < startIndex) {
+ DEBUGLOG(7, "extDict candidate: matchIndex=%5u < startIndex=%5u", matchIndex, startIndex);
+ assert(startIndex - matchIndex >= MINMATCH);
+ assert(dictBase);
+ match = dictBase + matchIndex;
+ lowLimit = dictionary;
+ } else {
+ match = base + matchIndex;
+ lowLimit = (const BYTE*)source;
+ }
+ } else { /* single continuous memory segment */
+ match = base + matchIndex;
+ }
+ forwardH = LZ4_hashPosition(forwardIp, tableType);
+ LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType);
+
+ DEBUGLOG(7, "candidate at pos=%u (offset=%u \n", matchIndex, current - matchIndex);
+ if ((dictIssue == dictSmall) && (matchIndex < prefixIdxLimit)) { continue; } /* match outside of valid area */
+ assert(matchIndex < current);
+ if ( ((tableType != byU16) || (LZ4_DISTANCE_MAX < LZ4_DISTANCE_ABSOLUTE_MAX))
+ && (matchIndex+LZ4_DISTANCE_MAX < current)) {
+ continue;
+ } /* too far */
+ assert((current - matchIndex) <= LZ4_DISTANCE_MAX); /* match now expected within distance */
+
+ if (LZ4_read32(match) == LZ4_read32(ip)) {
+ if (maybe_extMem) offset = current - matchIndex;
+ break; /* match found */
+ }
+
+ } while(1);
+ }
+
+ /* Catch up */
+ filledIp = ip;
+ assert(ip > anchor); /* this is always true as ip has been advanced before entering the main loop */
+ if ((match > lowLimit) && unlikely(ip[-1] == match[-1])) {
+ do { ip--; match--; } while (((ip > anchor) & (match > lowLimit)) && (unlikely(ip[-1] == match[-1])));
+ }
+
+ /* Encode Literals */
+ { unsigned const litLength = (unsigned)(ip - anchor);
+ token = op++;
+ if ((outputDirective == limitedOutput) && /* Check output buffer overflow */
+ (unlikely(op + litLength + (2 + 1 + LASTLITERALS) + (litLength/255) > olimit)) ) {
+ return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */
+ }
+ if ((outputDirective == fillOutput) &&
+ (unlikely(op + (litLength+240)/255 /* litlen */ + litLength /* literals */ + 2 /* offset */ + 1 /* token */ + MFLIMIT - MINMATCH /* min last literals so last match is <= end - MFLIMIT */ > olimit))) {
+ op--;
+ goto _last_literals;
+ }
+ if (litLength >= RUN_MASK) {
+ unsigned len = litLength - RUN_MASK;
+ *token = (RUN_MASK<= 255 ; len-=255) *op++ = 255;
+ *op++ = (BYTE)len;
+ }
+ else *token = (BYTE)(litLength< olimit)) {
+ /* the match was too close to the end, rewind and go to last literals */
+ op = token;
+ goto _last_literals;
+ }
+
+ /* Encode Offset */
+ if (maybe_extMem) { /* static test */
+ DEBUGLOG(6, " with offset=%u (ext if > %i)", offset, (int)(ip - (const BYTE*)source));
+ assert(offset <= LZ4_DISTANCE_MAX && offset > 0);
+ LZ4_writeLE16(op, (U16)offset); op+=2;
+ } else {
+ DEBUGLOG(6, " with offset=%u (same segment)", (U32)(ip - match));
+ assert(ip-match <= LZ4_DISTANCE_MAX);
+ LZ4_writeLE16(op, (U16)(ip - match)); op+=2;
+ }
+
+ /* Encode MatchLength */
+ { unsigned matchCode;
+
+ if ( (dictDirective==usingExtDict || dictDirective==usingDictCtx)
+ && (lowLimit==dictionary) /* match within extDict */ ) {
+ const BYTE* limit = ip + (dictEnd-match);
+ assert(dictEnd > match);
+ if (limit > matchlimit) limit = matchlimit;
+ matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, limit);
+ ip += (size_t)matchCode + MINMATCH;
+ if (ip==limit) {
+ unsigned const more = LZ4_count(limit, (const BYTE*)source, matchlimit);
+ matchCode += more;
+ ip += more;
+ }
+ DEBUGLOG(6, " with matchLength=%u starting in extDict", matchCode+MINMATCH);
+ } else {
+ matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, matchlimit);
+ ip += (size_t)matchCode + MINMATCH;
+ DEBUGLOG(6, " with matchLength=%u", matchCode+MINMATCH);
+ }
+
+ if ((outputDirective) && /* Check output buffer overflow */
+ (unlikely(op + (1 + LASTLITERALS) + (matchCode+240)/255 > olimit)) ) {
+ if (outputDirective == fillOutput) {
+ /* Match description too long : reduce it */
+ U32 newMatchCode = 15 /* in token */ - 1 /* to avoid needing a zero byte */ + ((U32)(olimit - op) - 1 - LASTLITERALS) * 255;
+ ip -= matchCode - newMatchCode;
+ assert(newMatchCode < matchCode);
+ matchCode = newMatchCode;
+ if (unlikely(ip <= filledIp)) {
+ /* We have already filled up to filledIp so if ip ends up less than filledIp
+ * we have positions in the hash table beyond the current position. This is
+ * a problem if we reuse the hash table. So we have to remove these positions
+ * from the hash table.
+ */
+ const BYTE* ptr;
+ DEBUGLOG(5, "Clearing %u positions", (U32)(filledIp - ip));
+ for (ptr = ip; ptr <= filledIp; ++ptr) {
+ U32 const h = LZ4_hashPosition(ptr, tableType);
+ LZ4_clearHash(h, cctx->hashTable, tableType);
+ }
+ }
+ } else {
+ assert(outputDirective == limitedOutput);
+ return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */
+ }
+ }
+ if (matchCode >= ML_MASK) {
+ *token += ML_MASK;
+ matchCode -= ML_MASK;
+ LZ4_write32(op, 0xFFFFFFFF);
+ while (matchCode >= 4*255) {
+ op+=4;
+ LZ4_write32(op, 0xFFFFFFFF);
+ matchCode -= 4*255;
+ }
+ op += matchCode / 255;
+ *op++ = (BYTE)(matchCode % 255);
+ } else
+ *token += (BYTE)(matchCode);
+ }
+ /* Ensure we have enough space for the last literals. */
+ assert(!(outputDirective == fillOutput && op + 1 + LASTLITERALS > olimit));
+
+ anchor = ip;
+
+ /* Test end of chunk */
+ if (ip >= mflimitPlusOne) break;
+
+ /* Fill table */
+ { U32 const h = LZ4_hashPosition(ip-2, tableType);
+ if (tableType == byPtr) {
+ LZ4_putPositionOnHash(ip-2, h, cctx->hashTable, byPtr);
+ } else {
+ U32 const idx = (U32)((ip-2) - base);
+ LZ4_putIndexOnHash(idx, h, cctx->hashTable, tableType);
+ } }
+
+ /* Test next position */
+ if (tableType == byPtr) {
+
+ match = LZ4_getPosition(ip, cctx->hashTable, tableType);
+ LZ4_putPosition(ip, cctx->hashTable, tableType);
+ if ( (match+LZ4_DISTANCE_MAX >= ip)
+ && (LZ4_read32(match) == LZ4_read32(ip)) )
+ { token=op++; *token=0; goto _next_match; }
+
+ } else { /* byU32, byU16 */
+
+ U32 const h = LZ4_hashPosition(ip, tableType);
+ U32 const current = (U32)(ip-base);
+ U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType);
+ assert(matchIndex < current);
+ if (dictDirective == usingDictCtx) {
+ if (matchIndex < startIndex) {
+ /* there was no match, try the dictionary */
+ assert(tableType == byU32);
+ matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32);
+ match = dictBase + matchIndex;
+ lowLimit = dictionary; /* required for match length counter */
+ matchIndex += dictDelta;
+ } else {
+ match = base + matchIndex;
+ lowLimit = (const BYTE*)source; /* required for match length counter */
+ }
+ } else if (dictDirective==usingExtDict) {
+ if (matchIndex < startIndex) {
+ assert(dictBase);
+ match = dictBase + matchIndex;
+ lowLimit = dictionary; /* required for match length counter */
+ } else {
+ match = base + matchIndex;
+ lowLimit = (const BYTE*)source; /* required for match length counter */
+ }
+ } else { /* single memory segment */
+ match = base + matchIndex;
+ }
+ LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType);
+ assert(matchIndex < current);
+ if ( ((dictIssue==dictSmall) ? (matchIndex >= prefixIdxLimit) : 1)
+ && (((tableType==byU16) && (LZ4_DISTANCE_MAX == LZ4_DISTANCE_ABSOLUTE_MAX)) ? 1 : (matchIndex+LZ4_DISTANCE_MAX >= current))
+ && (LZ4_read32(match) == LZ4_read32(ip)) ) {
+ token=op++;
+ *token=0;
+ if (maybe_extMem) offset = current - matchIndex;
+ DEBUGLOG(6, "seq.start:%i, literals=%u, match.start:%i",
+ (int)(anchor-(const BYTE*)source), 0, (int)(ip-(const BYTE*)source));
+ goto _next_match;
+ }
+ }
+
+ /* Prepare next loop */
+ forwardH = LZ4_hashPosition(++ip, tableType);
+
+ }
+
+_last_literals:
+ /* Encode Last Literals */
+ { size_t lastRun = (size_t)(iend - anchor);
+ if ( (outputDirective) && /* Check output buffer overflow */
+ (op + lastRun + 1 + ((lastRun+255-RUN_MASK)/255) > olimit)) {
+ if (outputDirective == fillOutput) {
+ /* adapt lastRun to fill 'dst' */
+ assert(olimit >= op);
+ lastRun = (size_t)(olimit-op) - 1/*token*/;
+ lastRun -= (lastRun + 256 - RUN_MASK) / 256; /*additional length tokens*/
+ } else {
+ assert(outputDirective == limitedOutput);
+ return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */
+ }
+ }
+ DEBUGLOG(6, "Final literal run : %i literals", (int)lastRun);
+ if (lastRun >= RUN_MASK) {
+ size_t accumulator = lastRun - RUN_MASK;
+ *op++ = RUN_MASK << ML_BITS;
+ for(; accumulator >= 255 ; accumulator-=255) *op++ = 255;
+ *op++ = (BYTE) accumulator;
+ } else {
+ *op++ = (BYTE)(lastRun< 0);
+ DEBUGLOG(5, "LZ4_compress_generic: compressed %i bytes into %i bytes", inputSize, result);
+ return result;
+}
+
+/** LZ4_compress_generic() :
+ * inlined, to ensure branches are decided at compilation time;
+ * takes care of src == (NULL, 0)
+ * and forward the rest to LZ4_compress_generic_validated */
+LZ4_FORCE_INLINE int LZ4_compress_generic(
+ LZ4_stream_t_internal* const cctx,
+ const char* const src,
+ char* const dst,
+ const int srcSize,
+ int *inputConsumed, /* only written when outputDirective == fillOutput */
+ const int dstCapacity,
+ const limitedOutput_directive outputDirective,
+ const tableType_t tableType,
+ const dict_directive dictDirective,
+ const dictIssue_directive dictIssue,
+ const int acceleration)
+{
+ DEBUGLOG(5, "LZ4_compress_generic: srcSize=%i, dstCapacity=%i",
+ srcSize, dstCapacity);
+
+ if ((U32)srcSize > (U32)LZ4_MAX_INPUT_SIZE) { return 0; } /* Unsupported srcSize, too large (or negative) */
+ if (srcSize == 0) { /* src == NULL supported if srcSize == 0 */
+ if (outputDirective != notLimited && dstCapacity <= 0) return 0; /* no output, can't write anything */
+ DEBUGLOG(5, "Generating an empty block");
+ assert(outputDirective == notLimited || dstCapacity >= 1);
+ assert(dst != NULL);
+ dst[0] = 0;
+ if (outputDirective == fillOutput) {
+ assert (inputConsumed != NULL);
+ *inputConsumed = 0;
+ }
+ return 1;
+ }
+ assert(src != NULL);
+
+ return LZ4_compress_generic_validated(cctx, src, dst, srcSize,
+ inputConsumed, /* only written into if outputDirective == fillOutput */
+ dstCapacity, outputDirective,
+ tableType, dictDirective, dictIssue, acceleration);
+}
+
+
+int LZ4_compress_fast_extState(void* state, const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration)
+{
+ LZ4_stream_t_internal* const ctx = & LZ4_initStream(state, sizeof(LZ4_stream_t)) -> internal_donotuse;
+ assert(ctx != NULL);
+ if (acceleration < 1) acceleration = LZ4_ACCELERATION_DEFAULT;
+ if (acceleration > LZ4_ACCELERATION_MAX) acceleration = LZ4_ACCELERATION_MAX;
+ if (maxOutputSize >= LZ4_compressBound(inputSize)) {
+ if (inputSize < LZ4_64Klimit) {
+ return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, 0, notLimited, byU16, noDict, noDictIssue, acceleration);
+ } else {
+ const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)source > LZ4_DISTANCE_MAX)) ? byPtr : byU32;
+ return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration);
+ }
+ } else {
+ if (inputSize < LZ4_64Klimit) {
+ return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, byU16, noDict, noDictIssue, acceleration);
+ } else {
+ const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)source > LZ4_DISTANCE_MAX)) ? byPtr : byU32;
+ return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, noDict, noDictIssue, acceleration);
+ }
+ }
+}
+
+/**
+ * LZ4_compress_fast_extState_fastReset() :
+ * A variant of LZ4_compress_fast_extState().
+ *
+ * Using this variant avoids an expensive initialization step. It is only safe
+ * to call if the state buffer is known to be correctly initialized already
+ * (see comment in lz4.h on LZ4_resetStream_fast() for a definition of
+ * "correctly initialized").
+ */
+int LZ4_compress_fast_extState_fastReset(void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration)
+{
+ LZ4_stream_t_internal* const ctx = &((LZ4_stream_t*)state)->internal_donotuse;
+ if (acceleration < 1) acceleration = LZ4_ACCELERATION_DEFAULT;
+ if (acceleration > LZ4_ACCELERATION_MAX) acceleration = LZ4_ACCELERATION_MAX;
+ assert(ctx != NULL);
+
+ if (dstCapacity >= LZ4_compressBound(srcSize)) {
+ if (srcSize < LZ4_64Klimit) {
+ const tableType_t tableType = byU16;
+ LZ4_prepareTable(ctx, srcSize, tableType);
+ if (ctx->currentOffset) {
+ return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, dictSmall, acceleration);
+ } else {
+ return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration);
+ }
+ } else {
+ const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32;
+ LZ4_prepareTable(ctx, srcSize, tableType);
+ return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration);
+ }
+ } else {
+ if (srcSize < LZ4_64Klimit) {
+ const tableType_t tableType = byU16;
+ LZ4_prepareTable(ctx, srcSize, tableType);
+ if (ctx->currentOffset) {
+ return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, dictSmall, acceleration);
+ } else {
+ return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, noDictIssue, acceleration);
+ }
+ } else {
+ const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32;
+ LZ4_prepareTable(ctx, srcSize, tableType);
+ return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, noDictIssue, acceleration);
+ }
+ }
+}
+
+
+int LZ4_compress_fast(const char* src, char* dest, int srcSize, int dstCapacity, int acceleration)
+{
+ int result;
+#if (LZ4_HEAPMODE)
+ LZ4_stream_t* const ctxPtr = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */
+ if (ctxPtr == NULL) return 0;
+#else
+ LZ4_stream_t ctx;
+ LZ4_stream_t* const ctxPtr = &ctx;
+#endif
+ result = LZ4_compress_fast_extState(ctxPtr, src, dest, srcSize, dstCapacity, acceleration);
+
+#if (LZ4_HEAPMODE)
+ FREEMEM(ctxPtr);
+#endif
+ return result;
+}
+
+
+int LZ4_compress_default(const char* src, char* dst, int srcSize, int dstCapacity)
+{
+ return LZ4_compress_fast(src, dst, srcSize, dstCapacity, 1);
+}
+
+
+/* Note!: This function leaves the stream in an unclean/broken state!
+ * It is not safe to subsequently use the same state with a _fastReset() or
+ * _continue() call without resetting it. */
+static int LZ4_compress_destSize_extState_internal(LZ4_stream_t* state, const char* src, char* dst, int* srcSizePtr, int targetDstSize, int acceleration)
+{
+ void* const s = LZ4_initStream(state, sizeof (*state));
+ assert(s != NULL); (void)s;
+
+ if (targetDstSize >= LZ4_compressBound(*srcSizePtr)) { /* compression success is guaranteed */
+ return LZ4_compress_fast_extState(state, src, dst, *srcSizePtr, targetDstSize, acceleration);
+ } else {
+ if (*srcSizePtr < LZ4_64Klimit) {
+ return LZ4_compress_generic(&state->internal_donotuse, src, dst, *srcSizePtr, srcSizePtr, targetDstSize, fillOutput, byU16, noDict, noDictIssue, acceleration);
+ } else {
+ tableType_t const addrMode = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32;
+ return LZ4_compress_generic(&state->internal_donotuse, src, dst, *srcSizePtr, srcSizePtr, targetDstSize, fillOutput, addrMode, noDict, noDictIssue, acceleration);
+ } }
+}
+
+int LZ4_compress_destSize_extState(void* state, const char* src, char* dst, int* srcSizePtr, int targetDstSize, int acceleration)
+{
+ int const r = LZ4_compress_destSize_extState_internal((LZ4_stream_t*)state, src, dst, srcSizePtr, targetDstSize, acceleration);
+ /* clean the state on exit */
+ LZ4_initStream(state, sizeof (LZ4_stream_t));
+ return r;
+}
+
+
+int LZ4_compress_destSize(const char* src, char* dst, int* srcSizePtr, int targetDstSize)
+{
+#if (LZ4_HEAPMODE)
+ LZ4_stream_t* const ctx = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */
+ if (ctx == NULL) return 0;
+#else
+ LZ4_stream_t ctxBody;
+ LZ4_stream_t* const ctx = &ctxBody;
+#endif
+
+ int result = LZ4_compress_destSize_extState_internal(ctx, src, dst, srcSizePtr, targetDstSize, 1);
+
+#if (LZ4_HEAPMODE)
+ FREEMEM(ctx);
+#endif
+ return result;
+}
+
+
+
+/*-******************************
+* Streaming functions
+********************************/
+
+#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION)
+LZ4_stream_t* LZ4_createStream(void)
+{
+ LZ4_stream_t* const lz4s = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t));
+ LZ4_STATIC_ASSERT(sizeof(LZ4_stream_t) >= sizeof(LZ4_stream_t_internal));
+ DEBUGLOG(4, "LZ4_createStream %p", (void*)lz4s);
+ if (lz4s == NULL) return NULL;
+ LZ4_initStream(lz4s, sizeof(*lz4s));
+ return lz4s;
+}
+#endif
+
+static size_t LZ4_stream_t_alignment(void)
+{
+#if LZ4_ALIGN_TEST
+ typedef struct { char c; LZ4_stream_t t; } t_a;
+ return sizeof(t_a) - sizeof(LZ4_stream_t);
+#else
+ return 1; /* effectively disabled */
+#endif
+}
+
+LZ4_stream_t* LZ4_initStream (void* buffer, size_t size)
+{
+ DEBUGLOG(5, "LZ4_initStream");
+ if (buffer == NULL) { return NULL; }
+ if (size < sizeof(LZ4_stream_t)) { return NULL; }
+ if (!LZ4_isAligned(buffer, LZ4_stream_t_alignment())) return NULL;
+ MEM_INIT(buffer, 0, sizeof(LZ4_stream_t_internal));
+ return (LZ4_stream_t*)buffer;
+}
+
+/* resetStream is now deprecated,
+ * prefer initStream() which is more general */
+void LZ4_resetStream (LZ4_stream_t* LZ4_stream)
+{
+ DEBUGLOG(5, "LZ4_resetStream (ctx:%p)", (void*)LZ4_stream);
+ MEM_INIT(LZ4_stream, 0, sizeof(LZ4_stream_t_internal));
+}
+
+void LZ4_resetStream_fast(LZ4_stream_t* ctx) {
+ LZ4_prepareTable(&(ctx->internal_donotuse), 0, byU32);
+}
+
+#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION)
+int LZ4_freeStream (LZ4_stream_t* LZ4_stream)
+{
+ if (!LZ4_stream) return 0; /* support free on NULL */
+ DEBUGLOG(5, "LZ4_freeStream %p", (void*)LZ4_stream);
+ FREEMEM(LZ4_stream);
+ return (0);
+}
+#endif
+
+
+typedef enum { _ld_fast, _ld_slow } LoadDict_mode_e;
+#define HASH_UNIT sizeof(reg_t)
+static int LZ4_loadDict_internal(LZ4_stream_t* LZ4_dict,
+ const char* dictionary, int dictSize,
+ LoadDict_mode_e _ld)
+{
+ LZ4_stream_t_internal* const dict = &LZ4_dict->internal_donotuse;
+ const tableType_t tableType = byU32;
+ const BYTE* p = (const BYTE*)dictionary;
+ const BYTE* const dictEnd = p + dictSize;
+ U32 idx32;
+
+ DEBUGLOG(4, "LZ4_loadDict (%i bytes from %p into %p)", dictSize, (void*)dictionary, (void*)LZ4_dict);
+
+ /* It's necessary to reset the context,
+ * and not just continue it with prepareTable()
+ * to avoid any risk of generating overflowing matchIndex
+ * when compressing using this dictionary */
+ LZ4_resetStream(LZ4_dict);
+
+ /* We always increment the offset by 64 KB, since, if the dict is longer,
+ * we truncate it to the last 64k, and if it's shorter, we still want to
+ * advance by a whole window length so we can provide the guarantee that
+ * there are only valid offsets in the window, which allows an optimization
+ * in LZ4_compress_fast_continue() where it uses noDictIssue even when the
+ * dictionary isn't a full 64k. */
+ dict->currentOffset += 64 KB;
+
+ if (dictSize < (int)HASH_UNIT) {
+ return 0;
+ }
+
+ if ((dictEnd - p) > 64 KB) p = dictEnd - 64 KB;
+ dict->dictionary = p;
+ dict->dictSize = (U32)(dictEnd - p);
+ dict->tableType = (U32)tableType;
+ idx32 = dict->currentOffset - dict->dictSize;
+
+ while (p <= dictEnd-HASH_UNIT) {
+ U32 const h = LZ4_hashPosition(p, tableType);
+ /* Note: overwriting => favors positions end of dictionary */
+ LZ4_putIndexOnHash(idx32, h, dict->hashTable, tableType);
+ p+=3; idx32+=3;
+ }
+
+ if (_ld == _ld_slow) {
+ /* Fill hash table with additional references, to improve compression capability */
+ p = dict->dictionary;
+ idx32 = dict->currentOffset - dict->dictSize;
+ while (p <= dictEnd-HASH_UNIT) {
+ U32 const h = LZ4_hashPosition(p, tableType);
+ U32 const limit = dict->currentOffset - 64 KB;
+ if (LZ4_getIndexOnHash(h, dict->hashTable, tableType) <= limit) {
+ /* Note: not overwriting => favors positions beginning of dictionary */
+ LZ4_putIndexOnHash(idx32, h, dict->hashTable, tableType);
+ }
+ p++; idx32++;
+ }
+ }
+
+ return (int)dict->dictSize;
+}
+
+int LZ4_loadDict(LZ4_stream_t* LZ4_dict, const char* dictionary, int dictSize)
+{
+ return LZ4_loadDict_internal(LZ4_dict, dictionary, dictSize, _ld_fast);
+}
+
+int LZ4_loadDictSlow(LZ4_stream_t* LZ4_dict, const char* dictionary, int dictSize)
+{
+ return LZ4_loadDict_internal(LZ4_dict, dictionary, dictSize, _ld_slow);
+}
+
+void LZ4_attach_dictionary(LZ4_stream_t* workingStream, const LZ4_stream_t* dictionaryStream)
+{
+ const LZ4_stream_t_internal* dictCtx = (dictionaryStream == NULL) ? NULL :
+ &(dictionaryStream->internal_donotuse);
+
+ DEBUGLOG(4, "LZ4_attach_dictionary (%p, %p, size %u)",
+ (void*)workingStream, (void*)dictionaryStream,
+ dictCtx != NULL ? dictCtx->dictSize : 0);
+
+ if (dictCtx != NULL) {
+ /* If the current offset is zero, we will never look in the
+ * external dictionary context, since there is no value a table
+ * entry can take that indicate a miss. In that case, we need
+ * to bump the offset to something non-zero.
+ */
+ if (workingStream->internal_donotuse.currentOffset == 0) {
+ workingStream->internal_donotuse.currentOffset = 64 KB;
+ }
+
+ /* Don't actually attach an empty dictionary.
+ */
+ if (dictCtx->dictSize == 0) {
+ dictCtx = NULL;
+ }
+ }
+ workingStream->internal_donotuse.dictCtx = dictCtx;
+}
+
+
+static void LZ4_renormDictT(LZ4_stream_t_internal* LZ4_dict, int nextSize)
+{
+ assert(nextSize >= 0);
+ if (LZ4_dict->currentOffset + (unsigned)nextSize > 0x80000000) { /* potential ptrdiff_t overflow (32-bits mode) */
+ /* rescale hash table */
+ U32 const delta = LZ4_dict->currentOffset - 64 KB;
+ const BYTE* dictEnd = LZ4_dict->dictionary + LZ4_dict->dictSize;
+ int i;
+ DEBUGLOG(4, "LZ4_renormDictT");
+ for (i=0; ihashTable[i] < delta) LZ4_dict->hashTable[i]=0;
+ else LZ4_dict->hashTable[i] -= delta;
+ }
+ LZ4_dict->currentOffset = 64 KB;
+ if (LZ4_dict->dictSize > 64 KB) LZ4_dict->dictSize = 64 KB;
+ LZ4_dict->dictionary = dictEnd - LZ4_dict->dictSize;
+ }
+}
+
+
+int LZ4_compress_fast_continue (LZ4_stream_t* LZ4_stream,
+ const char* source, char* dest,
+ int inputSize, int maxOutputSize,
+ int acceleration)
+{
+ const tableType_t tableType = byU32;
+ LZ4_stream_t_internal* const streamPtr = &LZ4_stream->internal_donotuse;
+ const char* dictEnd = streamPtr->dictSize ? (const char*)streamPtr->dictionary + streamPtr->dictSize : NULL;
+
+ DEBUGLOG(5, "LZ4_compress_fast_continue (inputSize=%i, dictSize=%u)", inputSize, streamPtr->dictSize);
+
+ LZ4_renormDictT(streamPtr, inputSize); /* fix index overflow */
+ if (acceleration < 1) acceleration = LZ4_ACCELERATION_DEFAULT;
+ if (acceleration > LZ4_ACCELERATION_MAX) acceleration = LZ4_ACCELERATION_MAX;
+
+ /* invalidate tiny dictionaries */
+ if ( (streamPtr->dictSize < 4) /* tiny dictionary : not enough for a hash */
+ && (dictEnd != source) /* prefix mode */
+ && (inputSize > 0) /* tolerance : don't lose history, in case next invocation would use prefix mode */
+ && (streamPtr->dictCtx == NULL) /* usingDictCtx */
+ ) {
+ DEBUGLOG(5, "LZ4_compress_fast_continue: dictSize(%u) at addr:%p is too small", streamPtr->dictSize, (void*)streamPtr->dictionary);
+ /* remove dictionary existence from history, to employ faster prefix mode */
+ streamPtr->dictSize = 0;
+ streamPtr->dictionary = (const BYTE*)source;
+ dictEnd = source;
+ }
+
+ /* Check overlapping input/dictionary space */
+ { const char* const sourceEnd = source + inputSize;
+ if ((sourceEnd > (const char*)streamPtr->dictionary) && (sourceEnd < dictEnd)) {
+ streamPtr->dictSize = (U32)(dictEnd - sourceEnd);
+ if (streamPtr->dictSize > 64 KB) streamPtr->dictSize = 64 KB;
+ if (streamPtr->dictSize < 4) streamPtr->dictSize = 0;
+ streamPtr->dictionary = (const BYTE*)dictEnd - streamPtr->dictSize;
+ }
+ }
+
+ /* prefix mode : source data follows dictionary */
+ if (dictEnd == source) {
+ if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset))
+ return LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, withPrefix64k, dictSmall, acceleration);
+ else
+ return LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, withPrefix64k, noDictIssue, acceleration);
+ }
+
+ /* external dictionary mode */
+ { int result;
+ if (streamPtr->dictCtx) {
+ /* We depend here on the fact that dictCtx'es (produced by
+ * LZ4_loadDict) guarantee that their tables contain no references
+ * to offsets between dictCtx->currentOffset - 64 KB and
+ * dictCtx->currentOffset - dictCtx->dictSize. This makes it safe
+ * to use noDictIssue even when the dict isn't a full 64 KB.
+ */
+ if (inputSize > 4 KB) {
+ /* For compressing large blobs, it is faster to pay the setup
+ * cost to copy the dictionary's tables into the active context,
+ * so that the compression loop is only looking into one table.
+ */
+ LZ4_memcpy(streamPtr, streamPtr->dictCtx, sizeof(*streamPtr));
+ result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, noDictIssue, acceleration);
+ } else {
+ result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingDictCtx, noDictIssue, acceleration);
+ }
+ } else { /* small data <= 4 KB */
+ if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) {
+ result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, dictSmall, acceleration);
+ } else {
+ result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, noDictIssue, acceleration);
+ }
+ }
+ streamPtr->dictionary = (const BYTE*)source;
+ streamPtr->dictSize = (U32)inputSize;
+ return result;
+ }
+}
+
+
+/* Hidden debug function, to force-test external dictionary mode */
+int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* dest, int srcSize)
+{
+ LZ4_stream_t_internal* const streamPtr = &LZ4_dict->internal_donotuse;
+ int result;
+
+ LZ4_renormDictT(streamPtr, srcSize);
+
+ if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) {
+ result = LZ4_compress_generic(streamPtr, source, dest, srcSize, NULL, 0, notLimited, byU32, usingExtDict, dictSmall, 1);
+ } else {
+ result = LZ4_compress_generic(streamPtr, source, dest, srcSize, NULL, 0, notLimited, byU32, usingExtDict, noDictIssue, 1);
+ }
+
+ streamPtr->dictionary = (const BYTE*)source;
+ streamPtr->dictSize = (U32)srcSize;
+
+ return result;
+}
+
+
+/*! LZ4_saveDict() :
+ * If previously compressed data block is not guaranteed to remain available at its memory location,
+ * save it into a safer place (char* safeBuffer).
+ * Note : no need to call LZ4_loadDict() afterwards, dictionary is immediately usable,
+ * one can therefore call LZ4_compress_fast_continue() right after.
+ * @return : saved dictionary size in bytes (necessarily <= dictSize), or 0 if error.
+ */
+int LZ4_saveDict (LZ4_stream_t* LZ4_dict, char* safeBuffer, int dictSize)
+{
+ LZ4_stream_t_internal* const dict = &LZ4_dict->internal_donotuse;
+
+ DEBUGLOG(5, "LZ4_saveDict : dictSize=%i, safeBuffer=%p", dictSize, (void*)safeBuffer);
+
+ if ((U32)dictSize > 64 KB) { dictSize = 64 KB; } /* useless to define a dictionary > 64 KB */
+ if ((U32)dictSize > dict->dictSize) { dictSize = (int)dict->dictSize; }
+
+ if (safeBuffer == NULL) assert(dictSize == 0);
+ if (dictSize > 0) {
+ const BYTE* const previousDictEnd = dict->dictionary + dict->dictSize;
+ assert(dict->dictionary);
+ LZ4_memmove(safeBuffer, previousDictEnd - dictSize, (size_t)dictSize);
+ }
+
+ dict->dictionary = (const BYTE*)safeBuffer;
+ dict->dictSize = (U32)dictSize;
+
+ return dictSize;
+}
+
+
+
+/*-*******************************
+ * Decompression functions
+ ********************************/
+
+typedef enum { decode_full_block = 0, partial_decode = 1 } earlyEnd_directive;
+
+#undef MIN
+#define MIN(a,b) ( (a) < (b) ? (a) : (b) )
+
+
+/* variant for decompress_unsafe()
+ * does not know end of input
+ * presumes input is well formed
+ * note : will consume at least one byte */
+static size_t read_long_length_no_check(const BYTE** pp)
+{
+ size_t b, l = 0;
+ do { b = **pp; (*pp)++; l += b; } while (b==255);
+ DEBUGLOG(6, "read_long_length_no_check: +length=%zu using %zu input bytes", l, l/255 + 1)
+ return l;
+}
+
+/* core decoder variant for LZ4_decompress_fast*()
+ * for legacy support only : these entry points are deprecated.
+ * - Presumes input is correctly formed (no defense vs malformed inputs)
+ * - Does not know input size (presume input buffer is "large enough")
+ * - Decompress a full block (only)
+ * @return : nb of bytes read from input.
+ * Note : this variant is not optimized for speed, just for maintenance.
+ * the goal is to remove support of decompress_fast*() variants by v2.0
+**/
+LZ4_FORCE_INLINE int
+LZ4_decompress_unsafe_generic(
+ const BYTE* const istart,
+ BYTE* const ostart,
+ int decompressedSize,
+
+ size_t prefixSize,
+ const BYTE* const dictStart, /* only if dict==usingExtDict */
+ const size_t dictSize /* note: =0 if dictStart==NULL */
+ )
+{
+ const BYTE* ip = istart;
+ BYTE* op = (BYTE*)ostart;
+ BYTE* const oend = ostart + decompressedSize;
+ const BYTE* const prefixStart = ostart - prefixSize;
+
+ DEBUGLOG(5, "LZ4_decompress_unsafe_generic");
+ if (dictStart == NULL) assert(dictSize == 0);
+
+ while (1) {
+ /* start new sequence */
+ unsigned token = *ip++;
+
+ /* literals */
+ { size_t ll = token >> ML_BITS;
+ if (ll==15) {
+ /* long literal length */
+ ll += read_long_length_no_check(&ip);
+ }
+ if ((size_t)(oend-op) < ll) return -1; /* output buffer overflow */
+ LZ4_memmove(op, ip, ll); /* support in-place decompression */
+ op += ll;
+ ip += ll;
+ if ((size_t)(oend-op) < MFLIMIT) {
+ if (op==oend) break; /* end of block */
+ DEBUGLOG(5, "invalid: literals end at distance %zi from end of block", oend-op);
+ /* incorrect end of block :
+ * last match must start at least MFLIMIT==12 bytes before end of output block */
+ return -1;
+ } }
+
+ /* match */
+ { size_t ml = token & 15;
+ size_t const offset = LZ4_readLE16(ip);
+ ip+=2;
+
+ if (ml==15) {
+ /* long literal length */
+ ml += read_long_length_no_check(&ip);
+ }
+ ml += MINMATCH;
+
+ if ((size_t)(oend-op) < ml) return -1; /* output buffer overflow */
+
+ { const BYTE* match = op - offset;
+
+ /* out of range */
+ if (offset > (size_t)(op - prefixStart) + dictSize) {
+ DEBUGLOG(6, "offset out of range");
+ return -1;
+ }
+
+ /* check special case : extDict */
+ if (offset > (size_t)(op - prefixStart)) {
+ /* extDict scenario */
+ const BYTE* const dictEnd = dictStart + dictSize;
+ const BYTE* extMatch = dictEnd - (offset - (size_t)(op-prefixStart));
+ size_t const extml = (size_t)(dictEnd - extMatch);
+ if (extml > ml) {
+ /* match entirely within extDict */
+ LZ4_memmove(op, extMatch, ml);
+ op += ml;
+ ml = 0;
+ } else {
+ /* match split between extDict & prefix */
+ LZ4_memmove(op, extMatch, extml);
+ op += extml;
+ ml -= extml;
+ }
+ match = prefixStart;
+ }
+
+ /* match copy - slow variant, supporting overlap copy */
+ { size_t u;
+ for (u=0; u= ipmax before start of loop. Returns initial_error if so.
+ * @error (output) - error code. Must be set to 0 before call.
+**/
+typedef size_t Rvl_t;
+static const Rvl_t rvl_error = (Rvl_t)(-1);
+LZ4_FORCE_INLINE Rvl_t
+read_variable_length(const BYTE** ip, const BYTE* ilimit,
+ int initial_check)
+{
+ Rvl_t s, length = 0;
+ assert(ip != NULL);
+ assert(*ip != NULL);
+ assert(ilimit != NULL);
+ if (initial_check && unlikely((*ip) >= ilimit)) { /* read limit reached */
+ return rvl_error;
+ }
+ s = **ip;
+ (*ip)++;
+ length += s;
+ if (unlikely((*ip) > ilimit)) { /* read limit reached */
+ return rvl_error;
+ }
+ /* accumulator overflow detection (32-bit mode only) */
+ if ((sizeof(length) < 8) && unlikely(length > ((Rvl_t)(-1)/2)) ) {
+ return rvl_error;
+ }
+ if (likely(s != 255)) return length;
+ do {
+ s = **ip;
+ (*ip)++;
+ length += s;
+ if (unlikely((*ip) > ilimit)) { /* read limit reached */
+ return rvl_error;
+ }
+ /* accumulator overflow detection (32-bit mode only) */
+ if ((sizeof(length) < 8) && unlikely(length > ((Rvl_t)(-1)/2)) ) {
+ return rvl_error;
+ }
+ } while (s == 255);
+
+ return length;
+}
+
+/*! LZ4_decompress_generic() :
+ * This generic decompression function covers all use cases.
+ * It shall be instantiated several times, using different sets of directives.
+ * Note that it is important for performance that this function really get inlined,
+ * in order to remove useless branches during compilation optimization.
+ */
+LZ4_FORCE_INLINE int
+LZ4_decompress_generic(
+ const char* const src,
+ char* const dst,
+ int srcSize,
+ int outputSize, /* If endOnInput==endOnInputSize, this value is `dstCapacity` */
+
+ earlyEnd_directive partialDecoding, /* full, partial */
+ dict_directive dict, /* noDict, withPrefix64k, usingExtDict */
+ const BYTE* const lowPrefix, /* always <= dst, == dst when no prefix */
+ const BYTE* const dictStart, /* only if dict==usingExtDict */
+ const size_t dictSize /* note : = 0 if noDict */
+ )
+{
+ if ((src == NULL) || (outputSize < 0)) { return -1; }
+
+ { const BYTE* ip = (const BYTE*) src;
+ const BYTE* const iend = ip + srcSize;
+
+ BYTE* op = (BYTE*) dst;
+ BYTE* const oend = op + outputSize;
+ BYTE* cpy;
+
+ const BYTE* const dictEnd = (dictStart == NULL) ? NULL : dictStart + dictSize;
+
+ const int checkOffset = (dictSize < (int)(64 KB));
+
+
+ /* Set up the "end" pointers for the shortcut. */
+ const BYTE* const shortiend = iend - 14 /*maxLL*/ - 2 /*offset*/;
+ const BYTE* const shortoend = oend - 14 /*maxLL*/ - 18 /*maxML*/;
+
+ const BYTE* match;
+ size_t offset;
+ unsigned token;
+ size_t length;
+
+
+ DEBUGLOG(5, "LZ4_decompress_generic (srcSize:%i, dstSize:%i)", srcSize, outputSize);
+
+ /* Special cases */
+ assert(lowPrefix <= op);
+ if (unlikely(outputSize==0)) {
+ /* Empty output buffer */
+ if (partialDecoding) return 0;
+ return ((srcSize==1) && (*ip==0)) ? 0 : -1;
+ }
+ if (unlikely(srcSize==0)) { return -1; }
+
+ /* LZ4_FAST_DEC_LOOP:
+ * designed for modern OoO performance cpus,
+ * where copying reliably 32-bytes is preferable to an unpredictable branch.
+ * note : fast loop may show a regression for some client arm chips. */
+#if LZ4_FAST_DEC_LOOP
+ if ((oend - op) < FASTLOOP_SAFE_DISTANCE) {
+ DEBUGLOG(6, "move to safe decode loop");
+ goto safe_decode;
+ }
+
+ /* Fast loop : decode sequences as long as output < oend-FASTLOOP_SAFE_DISTANCE */
+ DEBUGLOG(6, "using fast decode loop");
+ while (1) {
+ /* Main fastloop assertion: We can always wildcopy FASTLOOP_SAFE_DISTANCE */
+ assert(oend - op >= FASTLOOP_SAFE_DISTANCE);
+ assert(ip < iend);
+ token = *ip++;
+ length = token >> ML_BITS; /* literal length */
+ DEBUGLOG(7, "blockPos%6u: litLength token = %u", (unsigned)(op-(BYTE*)dst), (unsigned)length);
+
+ /* decode literal length */
+ if (length == RUN_MASK) {
+ size_t const addl = read_variable_length(&ip, iend-RUN_MASK, 1);
+ if (addl == rvl_error) {
+ DEBUGLOG(6, "error reading long literal length");
+ goto _output_error;
+ }
+ length += addl;
+ if (unlikely((uptrval)(op)+length<(uptrval)(op))) { goto _output_error; } /* overflow detection */
+ if (unlikely((uptrval)(ip)+length<(uptrval)(ip))) { goto _output_error; } /* overflow detection */
+
+ /* copy literals */
+ LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH);
+ if ((op+length>oend-32) || (ip+length>iend-32)) { goto safe_literal_copy; }
+ LZ4_wildCopy32(op, ip, op+length);
+ ip += length; op += length;
+ } else if (ip <= iend-(16 + 1/*max lit + offset + nextToken*/)) {
+ /* We don't need to check oend, since we check it once for each loop below */
+ DEBUGLOG(7, "copy %u bytes in a 16-bytes stripe", (unsigned)length);
+ /* Literals can only be <= 14, but hope compilers optimize better when copy by a register size */
+ LZ4_memcpy(op, ip, 16);
+ ip += length; op += length;
+ } else {
+ goto safe_literal_copy;
+ }
+
+ /* get offset */
+ offset = LZ4_readLE16(ip); ip+=2;
+ DEBUGLOG(6, "blockPos%6u: offset = %u", (unsigned)(op-(BYTE*)dst), (unsigned)offset);
+ match = op - offset;
+ assert(match <= op); /* overflow check */
+
+ /* get matchlength */
+ length = token & ML_MASK;
+ DEBUGLOG(7, " match length token = %u (len==%u)", (unsigned)length, (unsigned)length+MINMATCH);
+
+ if (length == ML_MASK) {
+ size_t const addl = read_variable_length(&ip, iend - LASTLITERALS + 1, 0);
+ if (addl == rvl_error) {
+ DEBUGLOG(5, "error reading long match length");
+ goto _output_error;
+ }
+ length += addl;
+ length += MINMATCH;
+ DEBUGLOG(7, " long match length == %u", (unsigned)length);
+ if (unlikely((uptrval)(op)+length<(uptrval)op)) { goto _output_error; } /* overflow detection */
+ if (op + length >= oend - FASTLOOP_SAFE_DISTANCE) {
+ goto safe_match_copy;
+ }
+ } else {
+ length += MINMATCH;
+ if (op + length >= oend - FASTLOOP_SAFE_DISTANCE) {
+ DEBUGLOG(7, "moving to safe_match_copy (ml==%u)", (unsigned)length);
+ goto safe_match_copy;
+ }
+
+ /* Fastpath check: skip LZ4_wildCopy32 when true */
+ if ((dict == withPrefix64k) || (match >= lowPrefix)) {
+ if (offset >= 8) {
+ assert(match >= lowPrefix);
+ assert(match <= op);
+ assert(op + 18 <= oend);
+
+ LZ4_memcpy(op, match, 8);
+ LZ4_memcpy(op+8, match+8, 8);
+ LZ4_memcpy(op+16, match+16, 2);
+ op += length;
+ continue;
+ } } }
+
+ if ( checkOffset && (unlikely(match + dictSize < lowPrefix)) ) {
+ DEBUGLOG(5, "Error : pos=%zi, offset=%zi => outside buffers", op-lowPrefix, op-match);
+ goto _output_error;
+ }
+ /* match starting within external dictionary */
+ if ((dict==usingExtDict) && (match < lowPrefix)) {
+ assert(dictEnd != NULL);
+ if (unlikely(op+length > oend-LASTLITERALS)) {
+ if (partialDecoding) {
+ DEBUGLOG(7, "partialDecoding: dictionary match, close to dstEnd");
+ length = MIN(length, (size_t)(oend-op));
+ } else {
+ DEBUGLOG(6, "end-of-block condition violated")
+ goto _output_error;
+ } }
+
+ if (length <= (size_t)(lowPrefix-match)) {
+ /* match fits entirely within external dictionary : just copy */
+ LZ4_memmove(op, dictEnd - (lowPrefix-match), length);
+ op += length;
+ } else {
+ /* match stretches into both external dictionary and current block */
+ size_t const copySize = (size_t)(lowPrefix - match);
+ size_t const restSize = length - copySize;
+ LZ4_memcpy(op, dictEnd - copySize, copySize);
+ op += copySize;
+ if (restSize > (size_t)(op - lowPrefix)) { /* overlap copy */
+ BYTE* const endOfMatch = op + restSize;
+ const BYTE* copyFrom = lowPrefix;
+ while (op < endOfMatch) { *op++ = *copyFrom++; }
+ } else {
+ LZ4_memcpy(op, lowPrefix, restSize);
+ op += restSize;
+ } }
+ continue;
+ }
+
+ /* copy match within block */
+ cpy = op + length;
+
+ assert((op <= oend) && (oend-op >= 32));
+ if (unlikely(offset<16)) {
+ LZ4_memcpy_using_offset(op, match, cpy, offset);
+ } else {
+ LZ4_wildCopy32(op, match, cpy);
+ }
+
+ op = cpy; /* wildcopy correction */
+ }
+ safe_decode:
+#endif
+
+ /* Main Loop : decode remaining sequences where output < FASTLOOP_SAFE_DISTANCE */
+ DEBUGLOG(6, "using safe decode loop");
+ while (1) {
+ assert(ip < iend);
+ token = *ip++;
+ length = token >> ML_BITS; /* literal length */
+ DEBUGLOG(7, "blockPos%6u: litLength token = %u", (unsigned)(op-(BYTE*)dst), (unsigned)length);
+
+ /* A two-stage shortcut for the most common case:
+ * 1) If the literal length is 0..14, and there is enough space,
+ * enter the shortcut and copy 16 bytes on behalf of the literals
+ * (in the fast mode, only 8 bytes can be safely copied this way).
+ * 2) Further if the match length is 4..18, copy 18 bytes in a similar
+ * manner; but we ensure that there's enough space in the output for
+ * those 18 bytes earlier, upon entering the shortcut (in other words,
+ * there is a combined check for both stages).
+ */
+ if ( (length != RUN_MASK)
+ /* strictly "less than" on input, to re-enter the loop with at least one byte */
+ && likely((ip < shortiend) & (op <= shortoend)) ) {
+ /* Copy the literals */
+ LZ4_memcpy(op, ip, 16);
+ op += length; ip += length;
+
+ /* The second stage: prepare for match copying, decode full info.
+ * If it doesn't work out, the info won't be wasted. */
+ length = token & ML_MASK; /* match length */
+ DEBUGLOG(7, "blockPos%6u: matchLength token = %u (len=%u)", (unsigned)(op-(BYTE*)dst), (unsigned)length, (unsigned)length + 4);
+ offset = LZ4_readLE16(ip); ip += 2;
+ match = op - offset;
+ assert(match <= op); /* check overflow */
+
+ /* Do not deal with overlapping matches. */
+ if ( (length != ML_MASK)
+ && (offset >= 8)
+ && (dict==withPrefix64k || match >= lowPrefix) ) {
+ /* Copy the match. */
+ LZ4_memcpy(op + 0, match + 0, 8);
+ LZ4_memcpy(op + 8, match + 8, 8);
+ LZ4_memcpy(op +16, match +16, 2);
+ op += length + MINMATCH;
+ /* Both stages worked, load the next token. */
+ continue;
+ }
+
+ /* The second stage didn't work out, but the info is ready.
+ * Propel it right to the point of match copying. */
+ goto _copy_match;
+ }
+
+ /* decode literal length */
+ if (length == RUN_MASK) {
+ size_t const addl = read_variable_length(&ip, iend-RUN_MASK, 1);
+ if (addl == rvl_error) { goto _output_error; }
+ length += addl;
+ if (unlikely((uptrval)(op)+length<(uptrval)(op))) { goto _output_error; } /* overflow detection */
+ if (unlikely((uptrval)(ip)+length<(uptrval)(ip))) { goto _output_error; } /* overflow detection */
+ }
+
+#if LZ4_FAST_DEC_LOOP
+ safe_literal_copy:
+#endif
+ /* copy literals */
+ cpy = op+length;
+
+ LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH);
+ if ((cpy>oend-MFLIMIT) || (ip+length>iend-(2+1+LASTLITERALS))) {
+ /* We've either hit the input parsing restriction or the output parsing restriction.
+ * In the normal scenario, decoding a full block, it must be the last sequence,
+ * otherwise it's an error (invalid input or dimensions).
+ * In partialDecoding scenario, it's necessary to ensure there is no buffer overflow.
+ */
+ if (partialDecoding) {
+ /* Since we are partial decoding we may be in this block because of the output parsing
+ * restriction, which is not valid since the output buffer is allowed to be undersized.
+ */
+ DEBUGLOG(7, "partialDecoding: copying literals, close to input or output end")
+ DEBUGLOG(7, "partialDecoding: literal length = %u", (unsigned)length);
+ DEBUGLOG(7, "partialDecoding: remaining space in dstBuffer : %i", (int)(oend - op));
+ DEBUGLOG(7, "partialDecoding: remaining space in srcBuffer : %i", (int)(iend - ip));
+ /* Finishing in the middle of a literals segment,
+ * due to lack of input.
+ */
+ if (ip+length > iend) {
+ length = (size_t)(iend-ip);
+ cpy = op + length;
+ }
+ /* Finishing in the middle of a literals segment,
+ * due to lack of output space.
+ */
+ if (cpy > oend) {
+ cpy = oend;
+ assert(op<=oend);
+ length = (size_t)(oend-op);
+ }
+ } else {
+ /* We must be on the last sequence (or invalid) because of the parsing limitations
+ * so check that we exactly consume the input and don't overrun the output buffer.
+ */
+ if ((ip+length != iend) || (cpy > oend)) {
+ DEBUGLOG(5, "should have been last run of literals")
+ DEBUGLOG(5, "ip(%p) + length(%i) = %p != iend (%p)", (void*)ip, (int)length, (void*)(ip+length), (void*)iend);
+ DEBUGLOG(5, "or cpy(%p) > (oend-MFLIMIT)(%p)", (void*)cpy, (void*)(oend-MFLIMIT));
+ DEBUGLOG(5, "after writing %u bytes / %i bytes available", (unsigned)(op-(BYTE*)dst), outputSize);
+ goto _output_error;
+ }
+ }
+ LZ4_memmove(op, ip, length); /* supports overlapping memory regions, for in-place decompression scenarios */
+ ip += length;
+ op += length;
+ /* Necessarily EOF when !partialDecoding.
+ * When partialDecoding, it is EOF if we've either
+ * filled the output buffer or
+ * can't proceed with reading an offset for following match.
+ */
+ if (!partialDecoding || (cpy == oend) || (ip >= (iend-2))) {
+ break;
+ }
+ } else {
+ LZ4_wildCopy8(op, ip, cpy); /* can overwrite up to 8 bytes beyond cpy */
+ ip += length; op = cpy;
+ }
+
+ /* get offset */
+ offset = LZ4_readLE16(ip); ip+=2;
+ match = op - offset;
+
+ /* get matchlength */
+ length = token & ML_MASK;
+ DEBUGLOG(7, "blockPos%6u: matchLength token = %u", (unsigned)(op-(BYTE*)dst), (unsigned)length);
+
+ _copy_match:
+ if (length == ML_MASK) {
+ size_t const addl = read_variable_length(&ip, iend - LASTLITERALS + 1, 0);
+ if (addl == rvl_error) { goto _output_error; }
+ length += addl;
+ if (unlikely((uptrval)(op)+length<(uptrval)op)) goto _output_error; /* overflow detection */
+ }
+ length += MINMATCH;
+
+#if LZ4_FAST_DEC_LOOP
+ safe_match_copy:
+#endif
+ if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) goto _output_error; /* Error : offset outside buffers */
+ /* match starting within external dictionary */
+ if ((dict==usingExtDict) && (match < lowPrefix)) {
+ assert(dictEnd != NULL);
+ if (unlikely(op+length > oend-LASTLITERALS)) {
+ if (partialDecoding) length = MIN(length, (size_t)(oend-op));
+ else goto _output_error; /* doesn't respect parsing restriction */
+ }
+
+ if (length <= (size_t)(lowPrefix-match)) {
+ /* match fits entirely within external dictionary : just copy */
+ LZ4_memmove(op, dictEnd - (lowPrefix-match), length);
+ op += length;
+ } else {
+ /* match stretches into both external dictionary and current block */
+ size_t const copySize = (size_t)(lowPrefix - match);
+ size_t const restSize = length - copySize;
+ LZ4_memcpy(op, dictEnd - copySize, copySize);
+ op += copySize;
+ if (restSize > (size_t)(op - lowPrefix)) { /* overlap copy */
+ BYTE* const endOfMatch = op + restSize;
+ const BYTE* copyFrom = lowPrefix;
+ while (op < endOfMatch) *op++ = *copyFrom++;
+ } else {
+ LZ4_memcpy(op, lowPrefix, restSize);
+ op += restSize;
+ } }
+ continue;
+ }
+ assert(match >= lowPrefix);
+
+ /* copy match within block */
+ cpy = op + length;
+
+ /* partialDecoding : may end anywhere within the block */
+ assert(op<=oend);
+ if (partialDecoding && (cpy > oend-MATCH_SAFEGUARD_DISTANCE)) {
+ size_t const mlen = MIN(length, (size_t)(oend-op));
+ const BYTE* const matchEnd = match + mlen;
+ BYTE* const copyEnd = op + mlen;
+ if (matchEnd > op) { /* overlap copy */
+ while (op < copyEnd) { *op++ = *match++; }
+ } else {
+ LZ4_memcpy(op, match, mlen);
+ }
+ op = copyEnd;
+ if (op == oend) { break; }
+ continue;
+ }
+
+ if (unlikely(offset<8)) {
+ LZ4_write32(op, 0); /* silence msan warning when offset==0 */
+ op[0] = match[0];
+ op[1] = match[1];
+ op[2] = match[2];
+ op[3] = match[3];
+ match += inc32table[offset];
+ LZ4_memcpy(op+4, match, 4);
+ match -= dec64table[offset];
+ } else {
+ LZ4_memcpy(op, match, 8);
+ match += 8;
+ }
+ op += 8;
+
+ if (unlikely(cpy > oend-MATCH_SAFEGUARD_DISTANCE)) {
+ BYTE* const oCopyLimit = oend - (WILDCOPYLENGTH-1);
+ if (cpy > oend-LASTLITERALS) { goto _output_error; } /* Error : last LASTLITERALS bytes must be literals (uncompressed) */
+ if (op < oCopyLimit) {
+ LZ4_wildCopy8(op, match, oCopyLimit);
+ match += oCopyLimit - op;
+ op = oCopyLimit;
+ }
+ while (op < cpy) { *op++ = *match++; }
+ } else {
+ LZ4_memcpy(op, match, 8);
+ if (length > 16) { LZ4_wildCopy8(op+8, match+8, cpy); }
+ }
+ op = cpy; /* wildcopy correction */
+ }
+
+ /* end of decoding */
+ DEBUGLOG(5, "decoded %i bytes", (int) (((char*)op)-dst));
+ return (int) (((char*)op)-dst); /* Nb of output bytes decoded */
+
+ /* Overflow error detected */
+ _output_error:
+ return (int) (-(((const char*)ip)-src))-1;
+ }
+}
+
+
+/*===== Instantiate the API decoding functions. =====*/
+
+LZ4_FORCE_O2
+int LZ4_decompress_safe(const char* source, char* dest, int compressedSize, int maxDecompressedSize)
+{
+ return LZ4_decompress_generic(source, dest, compressedSize, maxDecompressedSize,
+ decode_full_block, noDict,
+ (BYTE*)dest, NULL, 0);
+}
+
+LZ4_FORCE_O2
+int LZ4_decompress_safe_partial(const char* src, char* dst, int compressedSize, int targetOutputSize, int dstCapacity)
+{
+ dstCapacity = MIN(targetOutputSize, dstCapacity);
+ return LZ4_decompress_generic(src, dst, compressedSize, dstCapacity,
+ partial_decode,
+ noDict, (BYTE*)dst, NULL, 0);
+}
+
+LZ4_FORCE_O2
+int LZ4_decompress_fast(const char* source, char* dest, int originalSize)
+{
+ DEBUGLOG(5, "LZ4_decompress_fast");
+ return LZ4_decompress_unsafe_generic(
+ (const BYTE*)source, (BYTE*)dest, originalSize,
+ 0, NULL, 0);
+}
+
+/*===== Instantiate a few more decoding cases, used more than once. =====*/
+
+LZ4_FORCE_O2 /* Exported, an obsolete API function. */
+int LZ4_decompress_safe_withPrefix64k(const char* source, char* dest, int compressedSize, int maxOutputSize)
+{
+ return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize,
+ decode_full_block, withPrefix64k,
+ (BYTE*)dest - 64 KB, NULL, 0);
+}
+
+LZ4_FORCE_O2
+static int LZ4_decompress_safe_partial_withPrefix64k(const char* source, char* dest, int compressedSize, int targetOutputSize, int dstCapacity)
+{
+ dstCapacity = MIN(targetOutputSize, dstCapacity);
+ return LZ4_decompress_generic(source, dest, compressedSize, dstCapacity,
+ partial_decode, withPrefix64k,
+ (BYTE*)dest - 64 KB, NULL, 0);
+}
+
+/* Another obsolete API function, paired with the previous one. */
+int LZ4_decompress_fast_withPrefix64k(const char* source, char* dest, int originalSize)
+{
+ return LZ4_decompress_unsafe_generic(
+ (const BYTE*)source, (BYTE*)dest, originalSize,
+ 64 KB, NULL, 0);
+}
+
+LZ4_FORCE_O2
+static int LZ4_decompress_safe_withSmallPrefix(const char* source, char* dest, int compressedSize, int maxOutputSize,
+ size_t prefixSize)
+{
+ return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize,
+ decode_full_block, noDict,
+ (BYTE*)dest-prefixSize, NULL, 0);
+}
+
+LZ4_FORCE_O2
+static int LZ4_decompress_safe_partial_withSmallPrefix(const char* source, char* dest, int compressedSize, int targetOutputSize, int dstCapacity,
+ size_t prefixSize)
+{
+ dstCapacity = MIN(targetOutputSize, dstCapacity);
+ return LZ4_decompress_generic(source, dest, compressedSize, dstCapacity,
+ partial_decode, noDict,
+ (BYTE*)dest-prefixSize, NULL, 0);
+}
+
+LZ4_FORCE_O2
+int LZ4_decompress_safe_forceExtDict(const char* source, char* dest,
+ int compressedSize, int maxOutputSize,
+ const void* dictStart, size_t dictSize)
+{
+ DEBUGLOG(5, "LZ4_decompress_safe_forceExtDict");
+ return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize,
+ decode_full_block, usingExtDict,
+ (BYTE*)dest, (const BYTE*)dictStart, dictSize);
+}
+
+LZ4_FORCE_O2
+int LZ4_decompress_safe_partial_forceExtDict(const char* source, char* dest,
+ int compressedSize, int targetOutputSize, int dstCapacity,
+ const void* dictStart, size_t dictSize)
+{
+ dstCapacity = MIN(targetOutputSize, dstCapacity);
+ return LZ4_decompress_generic(source, dest, compressedSize, dstCapacity,
+ partial_decode, usingExtDict,
+ (BYTE*)dest, (const BYTE*)dictStart, dictSize);
+}
+
+LZ4_FORCE_O2
+static int LZ4_decompress_fast_extDict(const char* source, char* dest, int originalSize,
+ const void* dictStart, size_t dictSize)
+{
+ return LZ4_decompress_unsafe_generic(
+ (const BYTE*)source, (BYTE*)dest, originalSize,
+ 0, (const BYTE*)dictStart, dictSize);
+}
+
+/* The "double dictionary" mode, for use with e.g. ring buffers: the first part
+ * of the dictionary is passed as prefix, and the second via dictStart + dictSize.
+ * These routines are used only once, in LZ4_decompress_*_continue().
+ */
+LZ4_FORCE_INLINE
+int LZ4_decompress_safe_doubleDict(const char* source, char* dest, int compressedSize, int maxOutputSize,
+ size_t prefixSize, const void* dictStart, size_t dictSize)
+{
+ return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize,
+ decode_full_block, usingExtDict,
+ (BYTE*)dest-prefixSize, (const BYTE*)dictStart, dictSize);
+}
+
+/*===== streaming decompression functions =====*/
+
+#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION)
+LZ4_streamDecode_t* LZ4_createStreamDecode(void)
+{
+ LZ4_STATIC_ASSERT(sizeof(LZ4_streamDecode_t) >= sizeof(LZ4_streamDecode_t_internal));
+ return (LZ4_streamDecode_t*) ALLOC_AND_ZERO(sizeof(LZ4_streamDecode_t));
+}
+
+int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream)
+{
+ if (LZ4_stream == NULL) { return 0; } /* support free on NULL */
+ FREEMEM(LZ4_stream);
+ return 0;
+}
+#endif
+
+/*! LZ4_setStreamDecode() :
+ * Use this function to instruct where to find the dictionary.
+ * This function is not necessary if previous data is still available where it was decoded.
+ * Loading a size of 0 is allowed (same effect as no dictionary).
+ * @return : 1 if OK, 0 if error
+ */
+int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize)
+{
+ LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse;
+ lz4sd->prefixSize = (size_t)dictSize;
+ if (dictSize) {
+ assert(dictionary != NULL);
+ lz4sd->prefixEnd = (const BYTE*) dictionary + dictSize;
+ } else {
+ lz4sd->prefixEnd = (const BYTE*) dictionary;
+ }
+ lz4sd->externalDict = NULL;
+ lz4sd->extDictSize = 0;
+ return 1;
+}
+
+/*! LZ4_decoderRingBufferSize() :
+ * when setting a ring buffer for streaming decompression (optional scenario),
+ * provides the minimum size of this ring buffer
+ * to be compatible with any source respecting maxBlockSize condition.
+ * Note : in a ring buffer scenario,
+ * blocks are presumed decompressed next to each other.
+ * When not enough space remains for next block (remainingSize < maxBlockSize),
+ * decoding resumes from beginning of ring buffer.
+ * @return : minimum ring buffer size,
+ * or 0 if there is an error (invalid maxBlockSize).
+ */
+int LZ4_decoderRingBufferSize(int maxBlockSize)
+{
+ if (maxBlockSize < 0) return 0;
+ if (maxBlockSize > LZ4_MAX_INPUT_SIZE) return 0;
+ if (maxBlockSize < 16) maxBlockSize = 16;
+ return LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize);
+}
+
+/*
+*_continue() :
+ These decoding functions allow decompression of multiple blocks in "streaming" mode.
+ Previously decoded blocks must still be available at the memory position where they were decoded.
+ If it's not possible, save the relevant part of decoded data into a safe buffer,
+ and indicate where it stands using LZ4_setStreamDecode()
+*/
+LZ4_FORCE_O2
+int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int compressedSize, int maxOutputSize)
+{
+ LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse;
+ int result;
+
+ if (lz4sd->prefixSize == 0) {
+ /* The first call, no dictionary yet. */
+ assert(lz4sd->extDictSize == 0);
+ result = LZ4_decompress_safe(source, dest, compressedSize, maxOutputSize);
+ if (result <= 0) return result;
+ lz4sd->prefixSize = (size_t)result;
+ lz4sd->prefixEnd = (BYTE*)dest + result;
+ } else if (lz4sd->prefixEnd == (BYTE*)dest) {
+ /* They're rolling the current segment. */
+ if (lz4sd->prefixSize >= 64 KB - 1)
+ result = LZ4_decompress_safe_withPrefix64k(source, dest, compressedSize, maxOutputSize);
+ else if (lz4sd->extDictSize == 0)
+ result = LZ4_decompress_safe_withSmallPrefix(source, dest, compressedSize, maxOutputSize,
+ lz4sd->prefixSize);
+ else
+ result = LZ4_decompress_safe_doubleDict(source, dest, compressedSize, maxOutputSize,
+ lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize);
+ if (result <= 0) return result;
+ lz4sd->prefixSize += (size_t)result;
+ lz4sd->prefixEnd += result;
+ } else {
+ /* The buffer wraps around, or they're switching to another buffer. */
+ lz4sd->extDictSize = lz4sd->prefixSize;
+ lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize;
+ result = LZ4_decompress_safe_forceExtDict(source, dest, compressedSize, maxOutputSize,
+ lz4sd->externalDict, lz4sd->extDictSize);
+ if (result <= 0) return result;
+ lz4sd->prefixSize = (size_t)result;
+ lz4sd->prefixEnd = (BYTE*)dest + result;
+ }
+
+ return result;
+}
+
+LZ4_FORCE_O2 int
+LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode,
+ const char* source, char* dest, int originalSize)
+{
+ LZ4_streamDecode_t_internal* const lz4sd =
+ (assert(LZ4_streamDecode!=NULL), &LZ4_streamDecode->internal_donotuse);
+ int result;
+
+ DEBUGLOG(5, "LZ4_decompress_fast_continue (toDecodeSize=%i)", originalSize);
+ assert(originalSize >= 0);
+
+ if (lz4sd->prefixSize == 0) {
+ DEBUGLOG(5, "first invocation : no prefix nor extDict");
+ assert(lz4sd->extDictSize == 0);
+ result = LZ4_decompress_fast(source, dest, originalSize);
+ if (result <= 0) return result;
+ lz4sd->prefixSize = (size_t)originalSize;
+ lz4sd->prefixEnd = (BYTE*)dest + originalSize;
+ } else if (lz4sd->prefixEnd == (BYTE*)dest) {
+ DEBUGLOG(5, "continue using existing prefix");
+ result = LZ4_decompress_unsafe_generic(
+ (const BYTE*)source, (BYTE*)dest, originalSize,
+ lz4sd->prefixSize,
+ lz4sd->externalDict, lz4sd->extDictSize);
+ if (result <= 0) return result;
+ lz4sd->prefixSize += (size_t)originalSize;
+ lz4sd->prefixEnd += originalSize;
+ } else {
+ DEBUGLOG(5, "prefix becomes extDict");
+ lz4sd->extDictSize = lz4sd->prefixSize;
+ lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize;
+ result = LZ4_decompress_fast_extDict(source, dest, originalSize,
+ lz4sd->externalDict, lz4sd->extDictSize);
+ if (result <= 0) return result;
+ lz4sd->prefixSize = (size_t)originalSize;
+ lz4sd->prefixEnd = (BYTE*)dest + originalSize;
+ }
+
+ return result;
+}
+
+
+/*
+Advanced decoding functions :
+*_usingDict() :
+ These decoding functions work the same as "_continue" ones,
+ the dictionary must be explicitly provided within parameters
+*/
+
+int LZ4_decompress_safe_usingDict(const char* source, char* dest, int compressedSize, int maxOutputSize, const char* dictStart, int dictSize)
+{
+ if (dictSize==0)
+ return LZ4_decompress_safe(source, dest, compressedSize, maxOutputSize);
+ if (dictStart+dictSize == dest) {
+ if (dictSize >= 64 KB - 1) {
+ return LZ4_decompress_safe_withPrefix64k(source, dest, compressedSize, maxOutputSize);
+ }
+ assert(dictSize >= 0);
+ return LZ4_decompress_safe_withSmallPrefix(source, dest, compressedSize, maxOutputSize, (size_t)dictSize);
+ }
+ assert(dictSize >= 0);
+ return LZ4_decompress_safe_forceExtDict(source, dest, compressedSize, maxOutputSize, dictStart, (size_t)dictSize);
+}
+
+int LZ4_decompress_safe_partial_usingDict(const char* source, char* dest, int compressedSize, int targetOutputSize, int dstCapacity, const char* dictStart, int dictSize)
+{
+ if (dictSize==0)
+ return LZ4_decompress_safe_partial(source, dest, compressedSize, targetOutputSize, dstCapacity);
+ if (dictStart+dictSize == dest) {
+ if (dictSize >= 64 KB - 1) {
+ return LZ4_decompress_safe_partial_withPrefix64k(source, dest, compressedSize, targetOutputSize, dstCapacity);
+ }
+ assert(dictSize >= 0);
+ return LZ4_decompress_safe_partial_withSmallPrefix(source, dest, compressedSize, targetOutputSize, dstCapacity, (size_t)dictSize);
+ }
+ assert(dictSize >= 0);
+ return LZ4_decompress_safe_partial_forceExtDict(source, dest, compressedSize, targetOutputSize, dstCapacity, dictStart, (size_t)dictSize);
+}
+
+int LZ4_decompress_fast_usingDict(const char* source, char* dest, int originalSize, const char* dictStart, int dictSize)
+{
+ if (dictSize==0 || dictStart+dictSize == dest)
+ return LZ4_decompress_unsafe_generic(
+ (const BYTE*)source, (BYTE*)dest, originalSize,
+ (size_t)dictSize, NULL, 0);
+ assert(dictSize >= 0);
+ return LZ4_decompress_fast_extDict(source, dest, originalSize, dictStart, (size_t)dictSize);
+}
+
+
+/*=*************************************************
+* Obsolete Functions
+***************************************************/
+/* obsolete compression functions */
+int LZ4_compress_limitedOutput(const char* source, char* dest, int inputSize, int maxOutputSize)
+{
+ return LZ4_compress_default(source, dest, inputSize, maxOutputSize);
+}
+int LZ4_compress(const char* src, char* dest, int srcSize)
+{
+ return LZ4_compress_default(src, dest, srcSize, LZ4_compressBound(srcSize));
+}
+int LZ4_compress_limitedOutput_withState (void* state, const char* src, char* dst, int srcSize, int dstSize)
+{
+ return LZ4_compress_fast_extState(state, src, dst, srcSize, dstSize, 1);
+}
+int LZ4_compress_withState (void* state, const char* src, char* dst, int srcSize)
+{
+ return LZ4_compress_fast_extState(state, src, dst, srcSize, LZ4_compressBound(srcSize), 1);
+}
+int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_stream, const char* src, char* dst, int srcSize, int dstCapacity)
+{
+ return LZ4_compress_fast_continue(LZ4_stream, src, dst, srcSize, dstCapacity, 1);
+}
+int LZ4_compress_continue (LZ4_stream_t* LZ4_stream, const char* source, char* dest, int inputSize)
+{
+ return LZ4_compress_fast_continue(LZ4_stream, source, dest, inputSize, LZ4_compressBound(inputSize), 1);
+}
+
+/*
+These decompression functions are deprecated and should no longer be used.
+They are only provided here for compatibility with older user programs.
+- LZ4_uncompress is totally equivalent to LZ4_decompress_fast
+- LZ4_uncompress_unknownOutputSize is totally equivalent to LZ4_decompress_safe
+*/
+int LZ4_uncompress (const char* source, char* dest, int outputSize)
+{
+ return LZ4_decompress_fast(source, dest, outputSize);
+}
+int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize)
+{
+ return LZ4_decompress_safe(source, dest, isize, maxOutputSize);
+}
+
+/* Obsolete Streaming functions */
+
+int LZ4_sizeofStreamState(void) { return sizeof(LZ4_stream_t); }
+
+int LZ4_resetStreamState(void* state, char* inputBuffer)
+{
+ (void)inputBuffer;
+ LZ4_resetStream((LZ4_stream_t*)state);
+ return 0;
+}
+
+#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION)
+void* LZ4_create (char* inputBuffer)
+{
+ (void)inputBuffer;
+ return LZ4_createStream();
+}
+#endif
+
+char* LZ4_slideInputBuffer (void* state)
+{
+ /* avoid const char * -> char * conversion warning */
+ return (char *)(uptrval)((LZ4_stream_t*)state)->internal_donotuse.dictionary;
+}
+
+#endif /* LZ4_COMMONDEFS_ONLY */
diff --git a/code/server/lz4.h b/code/server/lz4.h
new file mode 100644
index 0000000..5b147b2
--- /dev/null
+++ b/code/server/lz4.h
@@ -0,0 +1,886 @@
+/*
+ * LZ4 - Fast LZ compression algorithm
+ * Header File
+ * Copyright (c) Yann Collet. All rights reserved.
+
+ BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following disclaimer
+ in the documentation and/or other materials provided with the
+ distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ You can contact the author at :
+ - LZ4 homepage : http://www.lz4.org
+ - LZ4 source repository : https://github.com/lz4/lz4
+*/
+#if defined (__cplusplus)
+extern "C" {
+#endif
+
+#ifndef LZ4_H_2983827168210
+#define LZ4_H_2983827168210
+
+/* --- Dependency --- */
+#include /* size_t */
+
+
+/**
+ Introduction
+
+ LZ4 is lossless compression algorithm, providing compression speed >500 MB/s per core,
+ scalable with multi-cores CPU. It features an extremely fast decoder, with speed in
+ multiple GB/s per core, typically reaching RAM speed limits on multi-core systems.
+
+ The LZ4 compression library provides in-memory compression and decompression functions.
+ It gives full buffer control to user.
+ Compression can be done in:
+ - a single step (described as Simple Functions)
+ - a single step, reusing a context (described in Advanced Functions)
+ - unbounded multiple steps (described as Streaming compression)
+
+ lz4.h generates and decodes LZ4-compressed blocks (doc/lz4_Block_format.md).
+ Decompressing such a compressed block requires additional metadata.
+ Exact metadata depends on exact decompression function.
+ For the typical case of LZ4_decompress_safe(),
+ metadata includes block's compressed size, and maximum bound of decompressed size.
+ Each application is free to encode and pass such metadata in whichever way it wants.
+
+ lz4.h only handle blocks, it can not generate Frames.
+
+ Blocks are different from Frames (doc/lz4_Frame_format.md).
+ Frames bundle both blocks and metadata in a specified manner.
+ Embedding metadata is required for compressed data to be self-contained and portable.
+ Frame format is delivered through a companion API, declared in lz4frame.h.
+ The `lz4` CLI can only manage frames.
+*/
+
+/*^***************************************************************
+* Export parameters
+*****************************************************************/
+/*
+* LZ4_DLL_EXPORT :
+* Enable exporting of functions when building a Windows DLL
+* LZ4LIB_VISIBILITY :
+* Control library symbols visibility.
+*/
+#ifndef LZ4LIB_VISIBILITY
+# if defined(__GNUC__) && (__GNUC__ >= 4)
+# define LZ4LIB_VISIBILITY __attribute__ ((visibility ("default")))
+# else
+# define LZ4LIB_VISIBILITY
+# endif
+#endif
+#if defined(LZ4_DLL_EXPORT) && (LZ4_DLL_EXPORT==1)
+# define LZ4LIB_API __declspec(dllexport) LZ4LIB_VISIBILITY
+#elif defined(LZ4_DLL_IMPORT) && (LZ4_DLL_IMPORT==1)
+# define LZ4LIB_API __declspec(dllimport) LZ4LIB_VISIBILITY /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/
+#else
+# define LZ4LIB_API LZ4LIB_VISIBILITY
+#endif
+
+/*! LZ4_FREESTANDING :
+ * When this macro is set to 1, it enables "freestanding mode" that is
+ * suitable for typical freestanding environment which doesn't support
+ * standard C library.
+ *
+ * - LZ4_FREESTANDING is a compile-time switch.
+ * - It requires the following macros to be defined:
+ * LZ4_memcpy, LZ4_memmove, LZ4_memset.
+ * - It only enables LZ4/HC functions which don't use heap.
+ * All LZ4F_* functions are not supported.
+ * - See tests/freestanding.c to check its basic setup.
+ */
+#if defined(LZ4_FREESTANDING) && (LZ4_FREESTANDING == 1)
+# define LZ4_HEAPMODE 0
+# define LZ4HC_HEAPMODE 0
+# define LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION 1
+# if !defined(LZ4_memcpy)
+# error "LZ4_FREESTANDING requires macro 'LZ4_memcpy'."
+# endif
+# if !defined(LZ4_memset)
+# error "LZ4_FREESTANDING requires macro 'LZ4_memset'."
+# endif
+# if !defined(LZ4_memmove)
+# error "LZ4_FREESTANDING requires macro 'LZ4_memmove'."
+# endif
+#elif ! defined(LZ4_FREESTANDING)
+# define LZ4_FREESTANDING 0
+#endif
+
+
+/*------ Version ------*/
+#define LZ4_VERSION_MAJOR 1 /* for breaking interface changes */
+#define LZ4_VERSION_MINOR 10 /* for new (non-breaking) interface capabilities */
+#define LZ4_VERSION_RELEASE 0 /* for tweaks, bug-fixes, or development */
+
+#define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE)
+
+#define LZ4_LIB_VERSION LZ4_VERSION_MAJOR.LZ4_VERSION_MINOR.LZ4_VERSION_RELEASE
+#define LZ4_QUOTE(str) #str
+#define LZ4_EXPAND_AND_QUOTE(str) LZ4_QUOTE(str)
+#define LZ4_VERSION_STRING LZ4_EXPAND_AND_QUOTE(LZ4_LIB_VERSION) /* requires v1.7.3+ */
+
+LZ4LIB_API int LZ4_versionNumber (void); /**< library version number; useful to check dll version; requires v1.3.0+ */
+LZ4LIB_API const char* LZ4_versionString (void); /**< library version string; useful to check dll version; requires v1.7.5+ */
+
+
+/*-************************************
+* Tuning memory usage
+**************************************/
+/*!
+ * LZ4_MEMORY_USAGE :
+ * Can be selected at compile time, by setting LZ4_MEMORY_USAGE.
+ * Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB)
+ * Increasing memory usage improves compression ratio, generally at the cost of speed.
+ * Reduced memory usage may improve speed at the cost of ratio, thanks to better cache locality.
+ * Default value is 14, for 16KB, which nicely fits into most L1 caches.
+ */
+#ifndef LZ4_MEMORY_USAGE
+# define LZ4_MEMORY_USAGE LZ4_MEMORY_USAGE_DEFAULT
+#endif
+
+/* These are absolute limits, they should not be changed by users */
+#define LZ4_MEMORY_USAGE_MIN 10
+#define LZ4_MEMORY_USAGE_DEFAULT 14
+#define LZ4_MEMORY_USAGE_MAX 20
+
+#if (LZ4_MEMORY_USAGE < LZ4_MEMORY_USAGE_MIN)
+# error "LZ4_MEMORY_USAGE is too small !"
+#endif
+
+#if (LZ4_MEMORY_USAGE > LZ4_MEMORY_USAGE_MAX)
+# error "LZ4_MEMORY_USAGE is too large !"
+#endif
+
+/*-************************************
+* Simple Functions
+**************************************/
+/*! LZ4_compress_default() :
+ * Compresses 'srcSize' bytes from buffer 'src'
+ * into already allocated 'dst' buffer of size 'dstCapacity'.
+ * Compression is guaranteed to succeed if 'dstCapacity' >= LZ4_compressBound(srcSize).
+ * It also runs faster, so it's a recommended setting.
+ * If the function cannot compress 'src' into a more limited 'dst' budget,
+ * compression stops *immediately*, and the function result is zero.
+ * In which case, 'dst' content is undefined (invalid).
+ * srcSize : max supported value is LZ4_MAX_INPUT_SIZE.
+ * dstCapacity : size of buffer 'dst' (which must be already allocated)
+ * @return : the number of bytes written into buffer 'dst' (necessarily <= dstCapacity)
+ * or 0 if compression fails
+ * Note : This function is protected against buffer overflow scenarios (never writes outside 'dst' buffer, nor read outside 'source' buffer).
+ */
+LZ4LIB_API int LZ4_compress_default(const char* src, char* dst, int srcSize, int dstCapacity);
+
+/*! LZ4_decompress_safe() :
+ * @compressedSize : is the exact complete size of the compressed block.
+ * @dstCapacity : is the size of destination buffer (which must be already allocated),
+ * presumed an upper bound of decompressed size.
+ * @return : the number of bytes decompressed into destination buffer (necessarily <= dstCapacity)
+ * If destination buffer is not large enough, decoding will stop and output an error code (negative value).
+ * If the source stream is detected malformed, the function will stop decoding and return a negative result.
+ * Note 1 : This function is protected against malicious data packets :
+ * it will never writes outside 'dst' buffer, nor read outside 'source' buffer,
+ * even if the compressed block is maliciously modified to order the decoder to do these actions.
+ * In such case, the decoder stops immediately, and considers the compressed block malformed.
+ * Note 2 : compressedSize and dstCapacity must be provided to the function, the compressed block does not contain them.
+ * The implementation is free to send / store / derive this information in whichever way is most beneficial.
+ * If there is a need for a different format which bundles together both compressed data and its metadata, consider looking at lz4frame.h instead.
+ */
+LZ4LIB_API int LZ4_decompress_safe (const char* src, char* dst, int compressedSize, int dstCapacity);
+
+
+/*-************************************
+* Advanced Functions
+**************************************/
+#define LZ4_MAX_INPUT_SIZE 0x7E000000 /* 2 113 929 216 bytes */
+#define LZ4_COMPRESSBOUND(isize) ((unsigned)(isize) > (unsigned)LZ4_MAX_INPUT_SIZE ? 0 : (isize) + ((isize)/255) + 16)
+
+/*! LZ4_compressBound() :
+ Provides the maximum size that LZ4 compression may output in a "worst case" scenario (input data not compressible)
+ This function is primarily useful for memory allocation purposes (destination buffer size).
+ Macro LZ4_COMPRESSBOUND() is also provided for compilation-time evaluation (stack memory allocation for example).
+ Note that LZ4_compress_default() compresses faster when dstCapacity is >= LZ4_compressBound(srcSize)
+ inputSize : max supported value is LZ4_MAX_INPUT_SIZE
+ return : maximum output size in a "worst case" scenario
+ or 0, if input size is incorrect (too large or negative)
+*/
+LZ4LIB_API int LZ4_compressBound(int inputSize);
+
+/*! LZ4_compress_fast() :
+ Same as LZ4_compress_default(), but allows selection of "acceleration" factor.
+ The larger the acceleration value, the faster the algorithm, but also the lesser the compression.
+ It's a trade-off. It can be fine tuned, with each successive value providing roughly +~3% to speed.
+ An acceleration value of "1" is the same as regular LZ4_compress_default()
+ Values <= 0 will be replaced by LZ4_ACCELERATION_DEFAULT (currently == 1, see lz4.c).
+ Values > LZ4_ACCELERATION_MAX will be replaced by LZ4_ACCELERATION_MAX (currently == 65537, see lz4.c).
+*/
+LZ4LIB_API int LZ4_compress_fast (const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
+
+
+/*! LZ4_compress_fast_extState() :
+ * Same as LZ4_compress_fast(), using an externally allocated memory space for its state.
+ * Use LZ4_sizeofState() to know how much memory must be allocated,
+ * and allocate it on 8-bytes boundaries (using `malloc()` typically).
+ * Then, provide this buffer as `void* state` to compression function.
+ */
+LZ4LIB_API int LZ4_sizeofState(void);
+LZ4LIB_API int LZ4_compress_fast_extState (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
+
+/*! LZ4_compress_destSize() :
+ * Reverse the logic : compresses as much data as possible from 'src' buffer
+ * into already allocated buffer 'dst', of size >= 'dstCapacity'.
+ * This function either compresses the entire 'src' content into 'dst' if it's large enough,
+ * or fill 'dst' buffer completely with as much data as possible from 'src'.
+ * note: acceleration parameter is fixed to "default".
+ *
+ * *srcSizePtr : in+out parameter. Initially contains size of input.
+ * Will be modified to indicate how many bytes where read from 'src' to fill 'dst'.
+ * New value is necessarily <= input value.
+ * @return : Nb bytes written into 'dst' (necessarily <= dstCapacity)
+ * or 0 if compression fails.
+ *
+ * Note : 'targetDstSize' must be >= 1, because it's the smallest valid lz4 payload.
+ *
+ * Note 2:from v1.8.2 to v1.9.1, this function had a bug (fixed in v1.9.2+):
+ * the produced compressed content could, in rare circumstances,
+ * require to be decompressed into a destination buffer
+ * larger by at least 1 byte than decompressesSize.
+ * If an application uses `LZ4_compress_destSize()`,
+ * it's highly recommended to update liblz4 to v1.9.2 or better.
+ * If this can't be done or ensured,
+ * the receiving decompression function should provide
+ * a dstCapacity which is > decompressedSize, by at least 1 byte.
+ * See https://github.com/lz4/lz4/issues/859 for details
+ */
+LZ4LIB_API int LZ4_compress_destSize(const char* src, char* dst, int* srcSizePtr, int targetDstSize);
+
+/*! LZ4_decompress_safe_partial() :
+ * Decompress an LZ4 compressed block, of size 'srcSize' at position 'src',
+ * into destination buffer 'dst' of size 'dstCapacity'.
+ * Up to 'targetOutputSize' bytes will be decoded.
+ * The function stops decoding on reaching this objective.
+ * This can be useful to boost performance
+ * whenever only the beginning of a block is required.
+ *
+ * @return : the number of bytes decoded in `dst` (necessarily <= targetOutputSize)
+ * If source stream is detected malformed, function returns a negative result.
+ *
+ * Note 1 : @return can be < targetOutputSize, if compressed block contains less data.
+ *
+ * Note 2 : targetOutputSize must be <= dstCapacity
+ *
+ * Note 3 : this function effectively stops decoding on reaching targetOutputSize,
+ * so dstCapacity is kind of redundant.
+ * This is because in older versions of this function,
+ * decoding operation would still write complete sequences.
+ * Therefore, there was no guarantee that it would stop writing at exactly targetOutputSize,
+ * it could write more bytes, though only up to dstCapacity.
+ * Some "margin" used to be required for this operation to work properly.
+ * Thankfully, this is no longer necessary.
+ * The function nonetheless keeps the same signature, in an effort to preserve API compatibility.
+ *
+ * Note 4 : If srcSize is the exact size of the block,
+ * then targetOutputSize can be any value,
+ * including larger than the block's decompressed size.
+ * The function will, at most, generate block's decompressed size.
+ *
+ * Note 5 : If srcSize is _larger_ than block's compressed size,
+ * then targetOutputSize **MUST** be <= block's decompressed size.
+ * Otherwise, *silent corruption will occur*.
+ */
+LZ4LIB_API int LZ4_decompress_safe_partial (const char* src, char* dst, int srcSize, int targetOutputSize, int dstCapacity);
+
+
+/*-*********************************************
+* Streaming Compression Functions
+***********************************************/
+typedef union LZ4_stream_u LZ4_stream_t; /* incomplete type (defined later) */
+
+/*!
+ Note about RC_INVOKED
+
+ - RC_INVOKED is predefined symbol of rc.exe (the resource compiler which is part of MSVC/Visual Studio).
+ https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros
+
+ - Since rc.exe is a legacy compiler, it truncates long symbol (> 30 chars)
+ and reports warning "RC4011: identifier truncated".
+
+ - To eliminate the warning, we surround long preprocessor symbol with
+ "#if !defined(RC_INVOKED) ... #endif" block that means
+ "skip this block when rc.exe is trying to read it".
+*/
+#if !defined(RC_INVOKED) /* https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros */
+#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION)
+LZ4LIB_API LZ4_stream_t* LZ4_createStream(void);
+LZ4LIB_API int LZ4_freeStream (LZ4_stream_t* streamPtr);
+#endif /* !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) */
+#endif
+
+/*! LZ4_resetStream_fast() : v1.9.0+
+ * Use this to prepare an LZ4_stream_t for a new chain of dependent blocks
+ * (e.g., LZ4_compress_fast_continue()).
+ *
+ * An LZ4_stream_t must be initialized once before usage.
+ * This is automatically done when created by LZ4_createStream().
+ * However, should the LZ4_stream_t be simply declared on stack (for example),
+ * it's necessary to initialize it first, using LZ4_initStream().
+ *
+ * After init, start any new stream with LZ4_resetStream_fast().
+ * A same LZ4_stream_t can be re-used multiple times consecutively
+ * and compress multiple streams,
+ * provided that it starts each new stream with LZ4_resetStream_fast().
+ *
+ * LZ4_resetStream_fast() is much faster than LZ4_initStream(),
+ * but is not compatible with memory regions containing garbage data.
+ *
+ * Note: it's only useful to call LZ4_resetStream_fast()
+ * in the context of streaming compression.
+ * The *extState* functions perform their own resets.
+ * Invoking LZ4_resetStream_fast() before is redundant, and even counterproductive.
+ */
+LZ4LIB_API void LZ4_resetStream_fast (LZ4_stream_t* streamPtr);
+
+/*! LZ4_loadDict() :
+ * Use this function to reference a static dictionary into LZ4_stream_t.
+ * The dictionary must remain available during compression.
+ * LZ4_loadDict() triggers a reset, so any previous data will be forgotten.
+ * The same dictionary will have to be loaded on decompression side for successful decoding.
+ * Dictionary are useful for better compression of small data (KB range).
+ * While LZ4 itself accepts any input as dictionary, dictionary efficiency is also a topic.
+ * When in doubt, employ the Zstandard's Dictionary Builder.
+ * Loading a size of 0 is allowed, and is the same as reset.
+ * @return : loaded dictionary size, in bytes (note: only the last 64 KB are loaded)
+ */
+LZ4LIB_API int LZ4_loadDict (LZ4_stream_t* streamPtr, const char* dictionary, int dictSize);
+
+/*! LZ4_loadDictSlow() : v1.10.0+
+ * Same as LZ4_loadDict(),
+ * but uses a bit more cpu to reference the dictionary content more thoroughly.
+ * This is expected to slightly improve compression ratio.
+ * The extra-cpu cost is likely worth it if the dictionary is re-used across multiple sessions.
+ * @return : loaded dictionary size, in bytes (note: only the last 64 KB are loaded)
+ */
+LZ4LIB_API int LZ4_loadDictSlow(LZ4_stream_t* streamPtr, const char* dictionary, int dictSize);
+
+/*! LZ4_attach_dictionary() : stable since v1.10.0
+ *
+ * This allows efficient re-use of a static dictionary multiple times.
+ *
+ * Rather than re-loading the dictionary buffer into a working context before
+ * each compression, or copying a pre-loaded dictionary's LZ4_stream_t into a
+ * working LZ4_stream_t, this function introduces a no-copy setup mechanism,
+ * in which the working stream references @dictionaryStream in-place.
+ *
+ * Several assumptions are made about the state of @dictionaryStream.
+ * Currently, only states which have been prepared by LZ4_loadDict() or
+ * LZ4_loadDictSlow() should be expected to work.
+ *
+ * Alternatively, the provided @dictionaryStream may be NULL,
+ * in which case any existing dictionary stream is unset.
+ *
+ * If a dictionary is provided, it replaces any pre-existing stream history.
+ * The dictionary contents are the only history that can be referenced and
+ * logically immediately precede the data compressed in the first subsequent
+ * compression call.
+ *
+ * The dictionary will only remain attached to the working stream through the
+ * first compression call, at the end of which it is cleared.
+ * @dictionaryStream stream (and source buffer) must remain in-place / accessible / unchanged
+ * through the completion of the compression session.
+ *
+ * Note: there is no equivalent LZ4_attach_*() method on the decompression side
+ * because there is no initialization cost, hence no need to share the cost across multiple sessions.
+ * To decompress LZ4 blocks using dictionary, attached or not,
+ * just employ the regular LZ4_setStreamDecode() for streaming,
+ * or the stateless LZ4_decompress_safe_usingDict() for one-shot decompression.
+ */
+LZ4LIB_API void
+LZ4_attach_dictionary(LZ4_stream_t* workingStream,
+ const LZ4_stream_t* dictionaryStream);
+
+/*! LZ4_compress_fast_continue() :
+ * Compress 'src' content using data from previously compressed blocks, for better compression ratio.
+ * 'dst' buffer must be already allocated.
+ * If dstCapacity >= LZ4_compressBound(srcSize), compression is guaranteed to succeed, and runs faster.
+ *
+ * @return : size of compressed block
+ * or 0 if there is an error (typically, cannot fit into 'dst').
+ *
+ * Note 1 : Each invocation to LZ4_compress_fast_continue() generates a new block.
+ * Each block has precise boundaries.
+ * Each block must be decompressed separately, calling LZ4_decompress_*() with relevant metadata.
+ * It's not possible to append blocks together and expect a single invocation of LZ4_decompress_*() to decompress them together.
+ *
+ * Note 2 : The previous 64KB of source data is __assumed__ to remain present, unmodified, at same address in memory !
+ *
+ * Note 3 : When input is structured as a double-buffer, each buffer can have any size, including < 64 KB.
+ * Make sure that buffers are separated, by at least one byte.
+ * This construction ensures that each block only depends on previous block.
+ *
+ * Note 4 : If input buffer is a ring-buffer, it can have any size, including < 64 KB.
+ *
+ * Note 5 : After an error, the stream status is undefined (invalid), it can only be reset or freed.
+ */
+LZ4LIB_API int LZ4_compress_fast_continue (LZ4_stream_t* streamPtr, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
+
+/*! LZ4_saveDict() :
+ * If last 64KB data cannot be guaranteed to remain available at its current memory location,
+ * save it into a safer place (char* safeBuffer).
+ * This is schematically equivalent to a memcpy() followed by LZ4_loadDict(),
+ * but is much faster, because LZ4_saveDict() doesn't need to rebuild tables.
+ * @return : saved dictionary size in bytes (necessarily <= maxDictSize), or 0 if error.
+ */
+LZ4LIB_API int LZ4_saveDict (LZ4_stream_t* streamPtr, char* safeBuffer, int maxDictSize);
+
+
+/*-**********************************************
+* Streaming Decompression Functions
+* Bufferless synchronous API
+************************************************/
+typedef union LZ4_streamDecode_u LZ4_streamDecode_t; /* tracking context */
+
+/*! LZ4_createStreamDecode() and LZ4_freeStreamDecode() :
+ * creation / destruction of streaming decompression tracking context.
+ * A tracking context can be re-used multiple times.
+ */
+#if !defined(RC_INVOKED) /* https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros */
+#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION)
+LZ4LIB_API LZ4_streamDecode_t* LZ4_createStreamDecode(void);
+LZ4LIB_API int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream);
+#endif /* !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) */
+#endif
+
+/*! LZ4_setStreamDecode() :
+ * An LZ4_streamDecode_t context can be allocated once and re-used multiple times.
+ * Use this function to start decompression of a new stream of blocks.
+ * A dictionary can optionally be set. Use NULL or size 0 for a reset order.
+ * Dictionary is presumed stable : it must remain accessible and unmodified during next decompression.
+ * @return : 1 if OK, 0 if error
+ */
+LZ4LIB_API int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize);
+
+/*! LZ4_decoderRingBufferSize() : v1.8.2+
+ * Note : in a ring buffer scenario (optional),
+ * blocks are presumed decompressed next to each other
+ * up to the moment there is not enough remaining space for next block (remainingSize < maxBlockSize),
+ * at which stage it resumes from beginning of ring buffer.
+ * When setting such a ring buffer for streaming decompression,
+ * provides the minimum size of this ring buffer
+ * to be compatible with any source respecting maxBlockSize condition.
+ * @return : minimum ring buffer size,
+ * or 0 if there is an error (invalid maxBlockSize).
+ */
+LZ4LIB_API int LZ4_decoderRingBufferSize(int maxBlockSize);
+#define LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize) (65536 + 14 + (maxBlockSize)) /* for static allocation; maxBlockSize presumed valid */
+
+/*! LZ4_decompress_safe_continue() :
+ * This decoding function allows decompression of consecutive blocks in "streaming" mode.
+ * The difference with the usual independent blocks is that
+ * new blocks are allowed to find references into former blocks.
+ * A block is an unsplittable entity, and must be presented entirely to the decompression function.
+ * LZ4_decompress_safe_continue() only accepts one block at a time.
+ * It's modeled after `LZ4_decompress_safe()` and behaves similarly.
+ *
+ * @LZ4_streamDecode : decompression state, tracking the position in memory of past data
+ * @compressedSize : exact complete size of one compressed block.
+ * @dstCapacity : size of destination buffer (which must be already allocated),
+ * must be an upper bound of decompressed size.
+ * @return : number of bytes decompressed into destination buffer (necessarily <= dstCapacity)
+ * If destination buffer is not large enough, decoding will stop and output an error code (negative value).
+ * If the source stream is detected malformed, the function will stop decoding and return a negative result.
+ *
+ * The last 64KB of previously decoded data *must* remain available and unmodified
+ * at the memory position where they were previously decoded.
+ * If less than 64KB of data has been decoded, all the data must be present.
+ *
+ * Special : if decompression side sets a ring buffer, it must respect one of the following conditions :
+ * - Decompression buffer size is _at least_ LZ4_decoderRingBufferSize(maxBlockSize).
+ * maxBlockSize is the maximum size of any single block. It can have any value > 16 bytes.
+ * In which case, encoding and decoding buffers do not need to be synchronized.
+ * Actually, data can be produced by any source compliant with LZ4 format specification, and respecting maxBlockSize.
+ * - Synchronized mode :
+ * Decompression buffer size is _exactly_ the same as compression buffer size,
+ * and follows exactly same update rule (block boundaries at same positions),
+ * and decoding function is provided with exact decompressed size of each block (exception for last block of the stream),
+ * _then_ decoding & encoding ring buffer can have any size, including small ones ( < 64 KB).
+ * - Decompression buffer is larger than encoding buffer, by a minimum of maxBlockSize more bytes.
+ * In which case, encoding and decoding buffers do not need to be synchronized,
+ * and encoding ring buffer can have any size, including small ones ( < 64 KB).
+ *
+ * Whenever these conditions are not possible,
+ * save the last 64KB of decoded data into a safe buffer where it can't be modified during decompression,
+ * then indicate where this data is saved using LZ4_setStreamDecode(), before decompressing next block.
+*/
+LZ4LIB_API int
+LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode,
+ const char* src, char* dst,
+ int srcSize, int dstCapacity);
+
+
+/*! LZ4_decompress_safe_usingDict() :
+ * Works the same as
+ * a combination of LZ4_setStreamDecode() followed by LZ4_decompress_safe_continue()
+ * However, it's stateless: it doesn't need any LZ4_streamDecode_t state.
+ * Dictionary is presumed stable : it must remain accessible and unmodified during decompression.
+ * Performance tip : Decompression speed can be substantially increased
+ * when dst == dictStart + dictSize.
+ */
+LZ4LIB_API int
+LZ4_decompress_safe_usingDict(const char* src, char* dst,
+ int srcSize, int dstCapacity,
+ const char* dictStart, int dictSize);
+
+/*! LZ4_decompress_safe_partial_usingDict() :
+ * Behaves the same as LZ4_decompress_safe_partial()
+ * with the added ability to specify a memory segment for past data.
+ * Performance tip : Decompression speed can be substantially increased
+ * when dst == dictStart + dictSize.
+ */
+LZ4LIB_API int
+LZ4_decompress_safe_partial_usingDict(const char* src, char* dst,
+ int compressedSize,
+ int targetOutputSize, int maxOutputSize,
+ const char* dictStart, int dictSize);
+
+#endif /* LZ4_H_2983827168210 */
+
+
+/*^*************************************
+ * !!!!!! STATIC LINKING ONLY !!!!!!
+ ***************************************/
+
+/*-****************************************************************************
+ * Experimental section
+ *
+ * Symbols declared in this section must be considered unstable. Their
+ * signatures or semantics may change, or they may be removed altogether in the
+ * future. They are therefore only safe to depend on when the caller is
+ * statically linked against the library.
+ *
+ * To protect against unsafe usage, not only are the declarations guarded,
+ * the definitions are hidden by default
+ * when building LZ4 as a shared/dynamic library.
+ *
+ * In order to access these declarations,
+ * define LZ4_STATIC_LINKING_ONLY in your application
+ * before including LZ4's headers.
+ *
+ * In order to make their implementations accessible dynamically, you must
+ * define LZ4_PUBLISH_STATIC_FUNCTIONS when building the LZ4 library.
+ ******************************************************************************/
+
+#ifdef LZ4_STATIC_LINKING_ONLY
+
+#ifndef LZ4_STATIC_3504398509
+#define LZ4_STATIC_3504398509
+
+#ifdef LZ4_PUBLISH_STATIC_FUNCTIONS
+# define LZ4LIB_STATIC_API LZ4LIB_API
+#else
+# define LZ4LIB_STATIC_API
+#endif
+
+
+/*! LZ4_compress_fast_extState_fastReset() :
+ * A variant of LZ4_compress_fast_extState().
+ *
+ * Using this variant avoids an expensive initialization step.
+ * It is only safe to call if the state buffer is known to be correctly initialized already
+ * (see above comment on LZ4_resetStream_fast() for a definition of "correctly initialized").
+ * From a high level, the difference is that
+ * this function initializes the provided state with a call to something like LZ4_resetStream_fast()
+ * while LZ4_compress_fast_extState() starts with a call to LZ4_resetStream().
+ */
+LZ4LIB_STATIC_API int LZ4_compress_fast_extState_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
+
+/*! LZ4_compress_destSize_extState() : introduced in v1.10.0
+ * Same as LZ4_compress_destSize(), but using an externally allocated state.
+ * Also: exposes @acceleration
+ */
+int LZ4_compress_destSize_extState(void* state, const char* src, char* dst, int* srcSizePtr, int targetDstSize, int acceleration);
+
+/*! In-place compression and decompression
+ *
+ * It's possible to have input and output sharing the same buffer,
+ * for highly constrained memory environments.
+ * In both cases, it requires input to lay at the end of the buffer,
+ * and decompression to start at beginning of the buffer.
+ * Buffer size must feature some margin, hence be larger than final size.
+ *
+ * |<------------------------buffer--------------------------------->|
+ * |<-----------compressed data--------->|
+ * |<-----------decompressed size------------------>|
+ * |<----margin---->|
+ *
+ * This technique is more useful for decompression,
+ * since decompressed size is typically larger,
+ * and margin is short.
+ *
+ * In-place decompression will work inside any buffer
+ * which size is >= LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize).
+ * This presumes that decompressedSize > compressedSize.
+ * Otherwise, it means compression actually expanded data,
+ * and it would be more efficient to store such data with a flag indicating it's not compressed.
+ * This can happen when data is not compressible (already compressed, or encrypted).
+ *
+ * For in-place compression, margin is larger, as it must be able to cope with both
+ * history preservation, requiring input data to remain unmodified up to LZ4_DISTANCE_MAX,
+ * and data expansion, which can happen when input is not compressible.
+ * As a consequence, buffer size requirements are much higher,
+ * and memory savings offered by in-place compression are more limited.
+ *
+ * There are ways to limit this cost for compression :
+ * - Reduce history size, by modifying LZ4_DISTANCE_MAX.
+ * Note that it is a compile-time constant, so all compressions will apply this limit.
+ * Lower values will reduce compression ratio, except when input_size < LZ4_DISTANCE_MAX,
+ * so it's a reasonable trick when inputs are known to be small.
+ * - Require the compressor to deliver a "maximum compressed size".
+ * This is the `dstCapacity` parameter in `LZ4_compress*()`.
+ * When this size is < LZ4_COMPRESSBOUND(inputSize), then compression can fail,
+ * in which case, the return code will be 0 (zero).
+ * The caller must be ready for these cases to happen,
+ * and typically design a backup scheme to send data uncompressed.
+ * The combination of both techniques can significantly reduce
+ * the amount of margin required for in-place compression.
+ *
+ * In-place compression can work in any buffer
+ * which size is >= (maxCompressedSize)
+ * with maxCompressedSize == LZ4_COMPRESSBOUND(srcSize) for guaranteed compression success.
+ * LZ4_COMPRESS_INPLACE_BUFFER_SIZE() depends on both maxCompressedSize and LZ4_DISTANCE_MAX,
+ * so it's possible to reduce memory requirements by playing with them.
+ */
+
+#define LZ4_DECOMPRESS_INPLACE_MARGIN(compressedSize) (((compressedSize) >> 8) + 32)
+#define LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize) ((decompressedSize) + LZ4_DECOMPRESS_INPLACE_MARGIN(decompressedSize)) /**< note: presumes that compressedSize < decompressedSize. note2: margin is overestimated a bit, since it could use compressedSize instead */
+
+#ifndef LZ4_DISTANCE_MAX /* history window size; can be user-defined at compile time */
+# define LZ4_DISTANCE_MAX 65535 /* set to maximum value by default */
+#endif
+
+#define LZ4_COMPRESS_INPLACE_MARGIN (LZ4_DISTANCE_MAX + 32) /* LZ4_DISTANCE_MAX can be safely replaced by srcSize when it's smaller */
+#define LZ4_COMPRESS_INPLACE_BUFFER_SIZE(maxCompressedSize) ((maxCompressedSize) + LZ4_COMPRESS_INPLACE_MARGIN) /**< maxCompressedSize is generally LZ4_COMPRESSBOUND(inputSize), but can be set to any lower value, with the risk that compression can fail (return code 0(zero)) */
+
+#endif /* LZ4_STATIC_3504398509 */
+#endif /* LZ4_STATIC_LINKING_ONLY */
+
+
+
+#ifndef LZ4_H_98237428734687
+#define LZ4_H_98237428734687
+
+/*-************************************************************
+ * Private Definitions
+ **************************************************************
+ * Do not use these definitions directly.
+ * They are only exposed to allow static allocation of `LZ4_stream_t` and `LZ4_streamDecode_t`.
+ * Accessing members will expose user code to API and/or ABI break in future versions of the library.
+ **************************************************************/
+#define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2)
+#define LZ4_HASHTABLESIZE (1 << LZ4_MEMORY_USAGE)
+#define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */
+
+#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */)
+# include
+ typedef int8_t LZ4_i8;
+ typedef unsigned char LZ4_byte;
+ typedef uint16_t LZ4_u16;
+ typedef uint32_t LZ4_u32;
+#else
+ typedef signed char LZ4_i8;
+ typedef unsigned char LZ4_byte;
+ typedef unsigned short LZ4_u16;
+ typedef unsigned int LZ4_u32;
+#endif
+
+/*! LZ4_stream_t :
+ * Never ever use below internal definitions directly !
+ * These definitions are not API/ABI safe, and may change in future versions.
+ * If you need static allocation, declare or allocate an LZ4_stream_t object.
+**/
+
+typedef struct LZ4_stream_t_internal LZ4_stream_t_internal;
+struct LZ4_stream_t_internal {
+ LZ4_u32 hashTable[LZ4_HASH_SIZE_U32];
+ const LZ4_byte* dictionary;
+ const LZ4_stream_t_internal* dictCtx;
+ LZ4_u32 currentOffset;
+ LZ4_u32 tableType;
+ LZ4_u32 dictSize;
+ /* Implicit padding to ensure structure is aligned */
+};
+
+#define LZ4_STREAM_MINSIZE ((1UL << (LZ4_MEMORY_USAGE)) + 32) /* static size, for inter-version compatibility */
+union LZ4_stream_u {
+ char minStateSize[LZ4_STREAM_MINSIZE];
+ LZ4_stream_t_internal internal_donotuse;
+}; /* previously typedef'd to LZ4_stream_t */
+
+
+/*! LZ4_initStream() : v1.9.0+
+ * An LZ4_stream_t structure must be initialized at least once.
+ * This is automatically done when invoking LZ4_createStream(),
+ * but it's not when the structure is simply declared on stack (for example).
+ *
+ * Use LZ4_initStream() to properly initialize a newly declared LZ4_stream_t.
+ * It can also initialize any arbitrary buffer of sufficient size,
+ * and will @return a pointer of proper type upon initialization.
+ *
+ * Note : initialization fails if size and alignment conditions are not respected.
+ * In which case, the function will @return NULL.
+ * Note2: An LZ4_stream_t structure guarantees correct alignment and size.
+ * Note3: Before v1.9.0, use LZ4_resetStream() instead
+**/
+LZ4LIB_API LZ4_stream_t* LZ4_initStream (void* stateBuffer, size_t size);
+
+
+/*! LZ4_streamDecode_t :
+ * Never ever use below internal definitions directly !
+ * These definitions are not API/ABI safe, and may change in future versions.
+ * If you need static allocation, declare or allocate an LZ4_streamDecode_t object.
+**/
+typedef struct {
+ const LZ4_byte* externalDict;
+ const LZ4_byte* prefixEnd;
+ size_t extDictSize;
+ size_t prefixSize;
+} LZ4_streamDecode_t_internal;
+
+#define LZ4_STREAMDECODE_MINSIZE 32
+union LZ4_streamDecode_u {
+ char minStateSize[LZ4_STREAMDECODE_MINSIZE];
+ LZ4_streamDecode_t_internal internal_donotuse;
+} ; /* previously typedef'd to LZ4_streamDecode_t */
+
+
+
+/*-************************************
+* Obsolete Functions
+**************************************/
+
+/*! Deprecation warnings
+ *
+ * Deprecated functions make the compiler generate a warning when invoked.
+ * This is meant to invite users to update their source code.
+ * Should deprecation warnings be a problem, it is generally possible to disable them,
+ * typically with -Wno-deprecated-declarations for gcc
+ * or _CRT_SECURE_NO_WARNINGS in Visual.
+ *
+ * Another method is to define LZ4_DISABLE_DEPRECATE_WARNINGS
+ * before including the header file.
+ */
+#ifdef LZ4_DISABLE_DEPRECATE_WARNINGS
+# define LZ4_DEPRECATED(message) /* disable deprecation warnings */
+#else
+# if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */
+# define LZ4_DEPRECATED(message) [[deprecated(message)]]
+# elif defined(_MSC_VER)
+# define LZ4_DEPRECATED(message) __declspec(deprecated(message))
+# elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ * 10 + __GNUC_MINOR__ >= 45))
+# define LZ4_DEPRECATED(message) __attribute__((deprecated(message)))
+# elif defined(__GNUC__) && (__GNUC__ * 10 + __GNUC_MINOR__ >= 31)
+# define LZ4_DEPRECATED(message) __attribute__((deprecated))
+# else
+# pragma message("WARNING: LZ4_DEPRECATED needs custom implementation for this compiler")
+# define LZ4_DEPRECATED(message) /* disabled */
+# endif
+#endif /* LZ4_DISABLE_DEPRECATE_WARNINGS */
+
+/*! Obsolete compression functions (since v1.7.3) */
+LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress (const char* src, char* dest, int srcSize);
+LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress_limitedOutput (const char* src, char* dest, int srcSize, int maxOutputSize);
+LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_withState (void* state, const char* source, char* dest, int inputSize);
+LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_limitedOutput_withState (void* state, const char* source, char* dest, int inputSize, int maxOutputSize);
+LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize);
+LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize, int maxOutputSize);
+
+/*! Obsolete decompression functions (since v1.8.0) */
+LZ4_DEPRECATED("use LZ4_decompress_fast() instead") LZ4LIB_API int LZ4_uncompress (const char* source, char* dest, int outputSize);
+LZ4_DEPRECATED("use LZ4_decompress_safe() instead") LZ4LIB_API int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize);
+
+/* Obsolete streaming functions (since v1.7.0)
+ * degraded functionality; do not use!
+ *
+ * In order to perform streaming compression, these functions depended on data
+ * that is no longer tracked in the state. They have been preserved as well as
+ * possible: using them will still produce a correct output. However, they don't
+ * actually retain any history between compression calls. The compression ratio
+ * achieved will therefore be no better than compressing each chunk
+ * independently.
+ */
+LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API void* LZ4_create (char* inputBuffer);
+LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API int LZ4_sizeofStreamState(void);
+LZ4_DEPRECATED("Use LZ4_resetStream() instead") LZ4LIB_API int LZ4_resetStreamState(void* state, char* inputBuffer);
+LZ4_DEPRECATED("Use LZ4_saveDict() instead") LZ4LIB_API char* LZ4_slideInputBuffer (void* state);
+
+/*! Obsolete streaming decoding functions (since v1.7.0) */
+LZ4_DEPRECATED("use LZ4_decompress_safe_usingDict() instead") LZ4LIB_API int LZ4_decompress_safe_withPrefix64k (const char* src, char* dst, int compressedSize, int maxDstSize);
+LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") LZ4LIB_API int LZ4_decompress_fast_withPrefix64k (const char* src, char* dst, int originalSize);
+
+/*! Obsolete LZ4_decompress_fast variants (since v1.9.0) :
+ * These functions used to be faster than LZ4_decompress_safe(),
+ * but this is no longer the case. They are now slower.
+ * This is because LZ4_decompress_fast() doesn't know the input size,
+ * and therefore must progress more cautiously into the input buffer to not read beyond the end of block.
+ * On top of that `LZ4_decompress_fast()` is not protected vs malformed or malicious inputs, making it a security liability.
+ * As a consequence, LZ4_decompress_fast() is strongly discouraged, and deprecated.
+ *
+ * The last remaining LZ4_decompress_fast() specificity is that
+ * it can decompress a block without knowing its compressed size.
+ * Such functionality can be achieved in a more secure manner
+ * by employing LZ4_decompress_safe_partial().
+ *
+ * Parameters:
+ * originalSize : is the uncompressed size to regenerate.
+ * `dst` must be already allocated, its size must be >= 'originalSize' bytes.
+ * @return : number of bytes read from source buffer (== compressed size).
+ * The function expects to finish at block's end exactly.
+ * If the source stream is detected malformed, the function stops decoding and returns a negative result.
+ * note : LZ4_decompress_fast*() requires originalSize. Thanks to this information, it never writes past the output buffer.
+ * However, since it doesn't know its 'src' size, it may read an unknown amount of input, past input buffer bounds.
+ * Also, since match offsets are not validated, match reads from 'src' may underflow too.
+ * These issues never happen if input (compressed) data is correct.
+ * But they may happen if input data is invalid (error or intentional tampering).
+ * As a consequence, use these functions in trusted environments with trusted data **only**.
+ */
+LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_partial() instead")
+LZ4LIB_API int LZ4_decompress_fast (const char* src, char* dst, int originalSize);
+LZ4_DEPRECATED("This function is deprecated and unsafe. Consider migrating towards LZ4_decompress_safe_continue() instead. "
+ "Note that the contract will change (requires block's compressed size, instead of decompressed size)")
+LZ4LIB_API int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int originalSize);
+LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_partial_usingDict() instead")
+LZ4LIB_API int LZ4_decompress_fast_usingDict (const char* src, char* dst, int originalSize, const char* dictStart, int dictSize);
+
+/*! LZ4_resetStream() :
+ * An LZ4_stream_t structure must be initialized at least once.
+ * This is done with LZ4_initStream(), or LZ4_resetStream().
+ * Consider switching to LZ4_initStream(),
+ * invoking LZ4_resetStream() will trigger deprecation warnings in the future.
+ */
+LZ4LIB_API void LZ4_resetStream (LZ4_stream_t* streamPtr);
+
+
+#endif /* LZ4_H_98237428734687 */
+
+
+#if defined (__cplusplus)
+}
+#endif
diff --git a/code/server/server.h b/code/server/server.h
index c32b2dc..6d59686 100644
--- a/code/server/server.h
+++ b/code/server/server.h
@@ -341,6 +341,29 @@ int SV_BotGetConsoleMessage( int client, char *buf, int size );
int BotImport_DebugPolygonCreate(int color, int numPoints, vec3_t *points);
void BotImport_DebugPolygonDelete(int id);
+//
+// sv_netdemo.c
+//
+void SVD_Record_f( void );
+void SVD_StopRecord_f( void );
+void SVD_RecordFrame( void );
+void SVD_ResetDeltaState( void );
+void SVD_AutoRecord( void );
+void SVD_CaptureServerCommand( const char *cmd );
+void SVD_Play_f( void );
+void SVD_StopPlay_f( void );
+void SVD_CleanupPlayback( void );
+void SVD_Stop_f( void );
+void SVD_Pause_f( void );
+void SVD_Seek_f( void );
+void SVD_SeekExact_f( void );
+qboolean SVD_PlaybackFrame( void );
+qboolean SVD_IsRecording( void );
+qboolean SVD_IsPlaying( void );
+qboolean SVD_IsPaused( void );
+qboolean SVD_IsStarting( void );
+qboolean SVD_ShouldPause( void );
+
//============================================================
//
// high level object sorting to reduce interaction tests
diff --git a/code/server/sv_ccmds.c b/code/server/sv_ccmds.c
index 62ae179..7f1370f 100644
--- a/code/server/sv_ccmds.c
+++ b/code/server/sv_ccmds.c
@@ -280,6 +280,9 @@ static void SV_MapRestart_f( void ) {
sv.state = SS_GAME;
sv.restarting = qfalse;
+ // reset demo delta state so next frame writes full entities
+ SVD_ResetDeltaState();
+
// connect and begin all the clients
for (i=0 ; iinteger ; i++) {
client = &svs.clients[i];
@@ -736,6 +739,14 @@ void SV_AddOperatorCommands( void ) {
if( com_dedicated->integer ) {
Cmd_AddCommand ("say", SV_ConSay_f);
}
+
+ // server-side demo recording/playback
+ Cmd_AddCommand ("svdemo_record", SVD_Record_f);
+ Cmd_AddCommand ("svdemo_stop", SVD_Stop_f);
+ Cmd_AddCommand ("svdemo_play", SVD_Play_f);
+ Cmd_AddCommand ("svdemo_pause", SVD_Pause_f);
+ Cmd_AddCommand ("svdemo_seek", SVD_Seek_f);
+ Cmd_AddCommand ("svdemo_seekexact", SVD_SeekExact_f);
}
/*
diff --git a/code/server/sv_client.c b/code/server/sv_client.c
index c3fdc92..84c2e3b 100644
--- a/code/server/sv_client.c
+++ b/code/server/sv_client.c
@@ -1419,7 +1419,12 @@ static void SV_UserMove( client_t *cl, msg_t *msg, qboolean delta ) {
// don't execute if this is an old cmd which is already executed
// these old cmds are included when cl_packetdup > 0
if ( cmds[i].serverTime <= cl->lastUsercmd.serverTime ) {
- continue;
+ // demo playback: still process the LAST cmd even with duplicate
+ // serverTime (paused = frozen time, all cmds have same time).
+ // Need buttons and origin from fresh usercmds.
+ if ( !SVD_IsPlaying() || i != cmdCount - 1 ) {
+ continue;
+ }
}
SV_ClientThink (cl, &cmds[ i ]);
}
diff --git a/code/server/sv_game.c b/code/server/sv_game.c
index 202994e..b227f3c 100644
--- a/code/server/sv_game.c
+++ b/code/server/sv_game.c
@@ -83,13 +83,20 @@ Sends a command string to a client
===============
*/
void SV_GameSendServerCommand( int clientNum, const char *text ) {
+ // capture for demo recording: broadcasts and per-client chat/tchat
+ if ( clientNum == -1 ) {
+ SVD_CaptureServerCommand( text );
+ } else if ( !strncmp( text, "chat", 4 ) || !strncmp( text, "tchat", 5 ) ) {
+ SVD_CaptureServerCommand( text );
+ }
+
if ( clientNum == -1 ) {
SV_SendServerCommand( NULL, "%s", text );
} else {
if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) {
return;
}
- SV_SendServerCommand( svs.clients + clientNum, "%s", text );
+ SV_SendServerCommand( svs.clients + clientNum, "%s", text );
}
}
diff --git a/code/server/sv_init.c b/code/server/sv_init.c
index b3d0c4d..8448195 100644
--- a/code/server/sv_init.c
+++ b/code/server/sv_init.c
@@ -351,6 +351,18 @@ void SV_SpawnServer( char *server, qboolean killBots ) {
char systemInfo[16384];
const char *p;
+ // stop any active demo recording/playback (one demo = one map).
+ // SVD_IsStarting() returns true when SVD_Play_f is calling devmap
+ // internally — don't stop our own playback.
+ if ( SVD_IsRecording() ) {
+ Com_Printf( "Map change — stopping demo recording.\n" );
+ SVD_StopRecord_f();
+ }
+ if ( SVD_IsPlaying() && !SVD_IsStarting() ) {
+ Com_Printf( "Map change — stopping demo playback.\n" );
+ SVD_CleanupPlayback();
+ }
+
// shut down the existing game if it is running
SV_ShutdownGameProgs();
@@ -546,6 +558,9 @@ void SV_SpawnServer( char *server, qboolean killBots ) {
Hunk_SetMark();
Com_Printf ("-----------------------------------\n");
+
+ // auto-record demo if enabled
+ SVD_AutoRecord();
}
/*
@@ -612,6 +627,11 @@ void SV_Init (void) {
sv_lanForceRate = Cvar_Get ("sv_lanForceRate", "1", CVAR_ARCHIVE );
sv_strictAuth = Cvar_Get ("sv_strictAuth", "1", CVAR_ARCHIVE );
+ // server-side demo settings
+ Cvar_Get ("svdemo_autorecord", "0", CVAR_ARCHIVE);
+ Cvar_Get ("svdemo_pauseEmpty", "1", CVAR_ARCHIVE);
+ Cvar_Get ("svdemo_keyframeInterval", "5", CVAR_ARCHIVE); // seconds, 0 = disabled
+
// initialize bot cvars so they are listed and can be set before loading the botlib
SV_BotInitCvars();
@@ -667,6 +687,15 @@ void SV_Shutdown( char *finalmsg ) {
Com_Printf( "----- Server Shutdown -----\n" );
+ // clean up any active demo recording/playback.
+ // skip if SVD_Play_f is calling SV_Shutdown internally.
+ if ( SVD_IsRecording() ) {
+ SVD_StopRecord_f();
+ }
+ if ( SVD_IsPlaying() && !SVD_IsStarting() ) {
+ SVD_CleanupPlayback();
+ }
+
if ( svs.clients && !com_errorEntered ) {
SV_FinalMessage( finalmsg );
}
diff --git a/code/server/sv_main.c b/code/server/sv_main.c
index 98c74ce..27834a3 100644
--- a/code/server/sv_main.c
+++ b/code/server/sv_main.c
@@ -831,18 +831,38 @@ void SV_Frame( int msec ) {
// run the game simulation in chunks
while ( sv.timeResidual >= frameMsec ) {
sv.timeResidual -= frameMsec;
+
+ if ( SVD_IsPaused() ) {
+ // demo paused: freeze svs.time so trajectories freeze
+ // and client doesn't see time jumps on unpause.
+ // still run game frame for spectator movement (at frozen time).
+ VM_Call( gvm, GAME_RUN_FRAME, svs.time );
+ continue;
+ }
+
svs.time += frameMsec;
+ if ( SVD_IsPlaying() ) {
+ // demo playback: read recorded entities instead of running game logic
+ SVD_PlaybackFrame();
+ // still call the game frame for spectator movement
+ }
+
// let everything in the world think and move
VM_Call( gvm, GAME_RUN_FRAME, svs.time );
+
+ // capture frame for demo recording
+ SVD_RecordFrame();
}
if ( com_speeds->integer ) {
time_game = Sys_Milliseconds () - startTime;
}
- // check timeouts
- SV_CheckTimeouts();
+ // check timeouts (skip during demo playback — zombie slots would be freed)
+ if ( !SVD_IsPlaying() ) {
+ SV_CheckTimeouts();
+ }
// send messages back to the clients
SV_SendClientMessages();
diff --git a/code/server/sv_netdemo.c b/code/server/sv_netdemo.c
new file mode 100644
index 0000000..ab5f82e
--- /dev/null
+++ b/code/server/sv_netdemo.c
@@ -0,0 +1,1263 @@
+/*
+===========================================================================
+Server-side demo recording and playback (netdemo).
+
+Records the full entity state array each server frame so that
+demos can be played back from any viewpoint. During playback the
+recorded entities are injected into sv.gentities and the normal
+snapshot pipeline delivers them to a spectator client.
+===========================================================================
+*/
+
+#include "server.h"
+
+// Cvar_Set2 not in public header but needed to force-set CVAR_ROM cvars
+extern cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force );
+
+// ---------------------------------------------------------------
+// File format
+// ---------------------------------------------------------------
+//
+// Header:
+// 4 bytes magic "SVDM"
+// 4 bytes version (1)
+// 4 bytes original sv_maxclients
+// 4 bytes original sv_fps
+// 64 bytes map name (null-padded)
+// Then: configstrings block
+// for each non-empty configstring:
+// 2 bytes index
+// 2 bytes string length (incl NUL)
+// N bytes string data
+// 2 bytes index = 0xFFFF (terminator)
+//
+// Per frame:
+// 4 bytes serverTime
+// 2 bytes numEntities (how many entity records follow)
+// for each entity:
+// 2 bytes entity number
+// entityState_t (fixed size, raw)
+// 4 bytes svFlags
+// 4 bytes linked
+// 12 bytes currentOrigin[3]
+// 12 bytes absmin[3]
+// 12 bytes absmax[3]
+// 2 bytes numConfigChanges
+// for each change:
+// 2 bytes index
+// 2 bytes string length (incl NUL)
+// N bytes string data
+//
+// Footer:
+// 4 bytes serverTime = -1 (end marker)
+//
+
+#define SVDEMO_MAGIC (('S') | ('V' << 8) | ('D' << 16) | ('M' << 24))
+#define SVDEMO_VERSION 3 // v3: removed PVS data, svFlags only
+#define SVDEMO_MAX_MAPNAME 64
+
+// header flags
+
+// ---------------------------------------------------------------
+// State
+// ---------------------------------------------------------------
+
+// per-entity data stored for delta compression
+typedef struct {
+ entityState_t es;
+ int svFlags;
+ qboolean active; // was this entity present last frame?
+} svdEntityState_t;
+
+// per-player state for delta compression
+typedef struct {
+ playerState_t ps;
+ qboolean active;
+} svdPlayerState_t;
+
+#define SVD_MAX_SERVERCMDS 64
+#define SVD_MAX_SERVERCMD_LEN 1024
+
+typedef struct {
+ // recording
+ fileHandle_t recordFile;
+ qboolean recording;
+ char *lastConfigstrings[MAX_CONFIGSTRINGS]; // for delta detection
+ svdEntityState_t prevEntities[MAX_GENTITIES]; // previous frame for delta
+ svdPlayerState_t prevPlayers[MAX_CLIENTS]; // previous frame player states
+
+ // buffered server commands for current frame
+ char serverCmds[SVD_MAX_SERVERCMDS][SVD_MAX_SERVERCMD_LEN];
+ int numServerCmds;
+ qboolean mapRestarted; // set by SVD_ResetDeltaState, written as frame flag
+ qboolean isKeyframe; // next frame is a keyframe (delta from baseline)
+ int keyframeInterval; // frames between keyframes (0 = disabled)
+ int framesSinceKeyframe; // counter for next keyframe
+
+ // playback
+ fileHandle_t playFile;
+ qboolean playing;
+ int playMaxClients; // original maxclients from the recording
+ int playFps; // original sv_fps
+ char *savedConfigstrings[MAX_CONFIGSTRINGS]; // from demo header, re-applied after map load
+ char playMapName[SVDEMO_MAX_MAPNAME];
+ qboolean endOfDemo;
+ qboolean needConfigstrings; // apply saved configstrings on first frame
+ qboolean starting; // SVD_Play_f is running devmap internally
+ qboolean paused;
+ qboolean seeked; // just seeked, next frame needs RESET
+ svdEntityState_t playPrevEntities[MAX_GENTITIES]; // previous frame for delta read
+ svdPlayerState_t playPrevPlayers[MAX_CLIENTS]; // previous frame player states
+
+ // keyframe index (shared by recording and playback)
+ int numKeyframes;
+ int maxKeyframes; // allocated size
+ int *keyframeTimes; // serverTime of each keyframe
+ int *keyframeOffsets; // file offset of each keyframe
+} svDemo_t;
+
+static svDemo_t demo;
+// ---------------------------------------------------------------
+// Recording helpers
+// ---------------------------------------------------------------
+
+static void SVD_WriteInt( fileHandle_t f, int v ) {
+ FS_Write( &v, 4, f );
+}
+
+static void SVD_WriteShort( fileHandle_t f, short v ) {
+ FS_Write( &v, 2, f );
+}
+
+static int SVD_ReadInt( fileHandle_t f ) {
+ int v = 0;
+ FS_Read( &v, 4, f );
+ return v;
+}
+
+static short SVD_ReadShort( fileHandle_t f ) {
+ short v = 0;
+ FS_Read( &v, 2, f );
+ return v;
+}
+
+// ---------------------------------------------------------------
+// Write header
+// ---------------------------------------------------------------
+
+static void SVD_WriteHeader( fileHandle_t f ) {
+ int i;
+ char mapBuf[SVDEMO_MAX_MAPNAME];
+
+ SVD_WriteInt( f, SVDEMO_MAGIC );
+ SVD_WriteInt( f, SVDEMO_VERSION );
+ SVD_WriteInt( f, 0 ); // flags (reserved)
+ SVD_WriteInt( f, sv_maxclients->integer );
+ SVD_WriteInt( f, sv_fps->integer );
+
+ // map name
+ memset( mapBuf, 0, sizeof(mapBuf) );
+ Q_strncpyz( mapBuf, sv.configstrings[CS_SERVERINFO], sizeof(mapBuf) );
+ // actually store the mapname from CS_SERVERINFO... or just the map name
+ {
+ const char *mapname = Cvar_VariableString("mapname");
+ memset( mapBuf, 0, sizeof(mapBuf) );
+ Q_strncpyz( mapBuf, mapname, sizeof(mapBuf) );
+ }
+ FS_Write( mapBuf, SVDEMO_MAX_MAPNAME, f );
+
+ // configstrings
+ for ( i = 0; i < MAX_CONFIGSTRINGS; i++ ) {
+ if ( sv.configstrings[i] && sv.configstrings[i][0] ) {
+ int len = strlen( sv.configstrings[i] ) + 1;
+ SVD_WriteShort( f, (short)i );
+ SVD_WriteShort( f, (short)len );
+ FS_Write( sv.configstrings[i], len, f );
+
+ // store initial copy for delta detection
+ if ( demo.lastConfigstrings[i] ) {
+ Z_Free( demo.lastConfigstrings[i] );
+ }
+ demo.lastConfigstrings[i] = CopyString( sv.configstrings[i] );
+ }
+ }
+ // terminator
+ SVD_WriteShort( f, (short)0xFFFF );
+}
+
+// ---------------------------------------------------------------
+// Write one frame
+// ---------------------------------------------------------------
+
+static void SVD_WriteFrame( fileHandle_t f ) {
+ int i;
+ sharedEntity_t *ent;
+ short numChanges;
+ msg_t msg;
+ static byte msgBuf[MAX_GENTITIES * 300]; // worst case: all entities full write from baseline
+
+ SVD_WriteInt( f, svs.time );
+ SVD_WriteShort( f, (short)sv.num_entities );
+
+ // frame flags: bit 0 = map restarted, bit 1 = keyframe
+ {
+ byte frameFlags = 0;
+ if ( demo.mapRestarted ) {
+ frameFlags |= 1;
+ demo.mapRestarted = qfalse;
+ }
+ if ( demo.isKeyframe ) {
+ frameFlags |= 2;
+ demo.isKeyframe = qfalse;
+ }
+ FS_Write( &frameFlags, 1, f );
+ }
+
+ // delta-compress all entities into a message buffer
+ MSG_Init( &msg, msgBuf, sizeof(msgBuf) );
+
+ for ( i = 0; i < sv.num_entities; i++ ) {
+ qboolean active;
+ ent = SV_GentityNum( i );
+ active = ( ent->r.linked || ent->s.eType != 0 );
+
+ if ( active ) {
+ entityState_t baseline;
+ entityState_t *from;
+ if ( demo.prevEntities[i].active ) {
+ from = &demo.prevEntities[i].es;
+ } else {
+ Com_Memset( &baseline, 0, sizeof(baseline) );
+ baseline.number = i;
+ from = &baseline;
+ }
+ MSG_WriteDeltaEntity( &msg, from, &ent->s, qtrue );
+
+ // write svFlags only if changed (rarely changes)
+ if ( ent->r.svFlags != demo.prevEntities[i].svFlags ) {
+ MSG_WriteBits( &msg, 1, 1 );
+ MSG_WriteLong( &msg, ent->r.svFlags );
+ } else {
+ MSG_WriteBits( &msg, 0, 1 );
+ }
+
+ // update prev state
+ demo.prevEntities[i].es = ent->s;
+ demo.prevEntities[i].svFlags = ent->r.svFlags;
+ demo.prevEntities[i].active = qtrue;
+ } else if ( demo.prevEntities[i].active ) {
+ // entity was removed — write a remove marker
+ MSG_WriteDeltaEntity( &msg, &demo.prevEntities[i].es, NULL, qtrue );
+ demo.prevEntities[i].active = qfalse;
+ }
+ // else: entity was inactive and still inactive — write nothing
+ }
+
+ // end of entities marker
+ MSG_WriteBits( &msg, (MAX_GENTITIES - 1), GENTITYNUM_BITS );
+
+ // write entity message to file
+ SVD_WriteInt( f, msg.cursize );
+ FS_Write( msg.data, msg.cursize, f );
+
+ // write player states (delta compressed)
+ {
+ msg_t psmsg;
+ static byte psBuf[MAX_CLIENTS * 600]; // worst case: full playerState from baseline
+ int psCount = 0;
+
+ MSG_Init( &psmsg, psBuf, sizeof(psBuf) );
+
+ for ( i = 0; i < sv_maxclients->integer; i++ ) {
+ playerState_t *ps = SV_GameClientNum( i );
+ client_t *cl = &svs.clients[i];
+ qboolean active = ( cl->state >= CS_ACTIVE );
+ qboolean isSpectator;
+ playerState_t specPs;
+
+ // detect spectators: free cam or follow mode
+ isSpectator = active && ( ps->pm_type == PM_SPECTATOR || (ps->pm_flags & PMF_FOLLOW) );
+
+ // for spectators, record a sanitized ps so they appear on
+ // the scoreboard as spectators (follow mode corrupts their ps
+ // with the followed player's data)
+ if ( isSpectator ) {
+ Com_Memset( &specPs, 0, sizeof(specPs) );
+ specPs.commandTime = ps->commandTime;
+ specPs.pm_type = PM_SPECTATOR;
+ specPs.persistant[PERS_TEAM] = TEAM_SPECTATOR;
+ specPs.clientNum = i;
+ ps = &specPs;
+ }
+
+ if ( active ) {
+ playerState_t *from = demo.prevPlayers[i].active ? &demo.prevPlayers[i].ps : NULL;
+ MSG_WriteBits( &psmsg, i, 6 ); // client number (0-63)
+ MSG_WriteBits( &psmsg, 1, 1 ); // active flag
+ MSG_WriteDeltaPlayerstate( &psmsg, from, ps );
+ demo.prevPlayers[i].ps = *ps;
+ demo.prevPlayers[i].active = qtrue;
+ psCount++;
+ } else if ( demo.prevPlayers[i].active ) {
+ MSG_WriteBits( &psmsg, i, 6 );
+ MSG_WriteBits( &psmsg, 0, 1 ); // inactive flag (player left)
+ demo.prevPlayers[i].active = qfalse;
+ psCount++;
+ }
+ }
+ // terminator
+ MSG_WriteBits( &psmsg, MAX_CLIENTS - 1, 6 );
+ MSG_WriteBits( &psmsg, 0, 1 );
+
+ SVD_WriteInt( f, psmsg.cursize );
+ FS_Write( psmsg.data, psmsg.cursize, f );
+ }
+
+ // configstring changes
+ numChanges = 0;
+ for ( i = 0; i < MAX_CONFIGSTRINGS; i++ ) {
+ const char *cur = sv.configstrings[i] ? sv.configstrings[i] : "";
+ const char *old = demo.lastConfigstrings[i] ? demo.lastConfigstrings[i] : "";
+ if ( strcmp( cur, old ) != 0 ) {
+ numChanges++;
+ }
+ }
+ SVD_WriteShort( f, numChanges );
+
+ if ( numChanges > 0 ) {
+ for ( i = 0; i < MAX_CONFIGSTRINGS; i++ ) {
+ const char *cur = sv.configstrings[i] ? sv.configstrings[i] : "";
+ const char *old = demo.lastConfigstrings[i] ? demo.lastConfigstrings[i] : "";
+ if ( strcmp( cur, old ) != 0 ) {
+ int len = strlen( cur ) + 1;
+ SVD_WriteShort( f, (short)i );
+ SVD_WriteShort( f, (short)len );
+ FS_Write( cur, len, f );
+
+ if ( demo.lastConfigstrings[i] ) {
+ Z_Free( demo.lastConfigstrings[i] );
+ }
+ demo.lastConfigstrings[i] = CopyString( cur );
+ }
+ }
+ }
+
+ // write buffered server commands (chat, prints, etc.)
+ SVD_WriteShort( f, (short)demo.numServerCmds );
+ for ( i = 0; i < demo.numServerCmds; i++ ) {
+ short len = (short)( strlen( demo.serverCmds[i] ) + 1 );
+ SVD_WriteShort( f, len );
+ FS_Write( demo.serverCmds[i], len, f );
+ }
+ demo.numServerCmds = 0;
+}
+
+// ---------------------------------------------------------------
+// Recording commands
+// ---------------------------------------------------------------
+
+/*
+Start recording a demo with the given name.
+Returns qtrue on success.
+*/
+static qboolean SVD_StartRecording( const char *demoname ) {
+ char path[MAX_OSPATH];
+
+ if ( demo.recording ) {
+ Com_Printf( "Already recording a server demo.\n" );
+ return qfalse;
+ }
+
+ if ( sv.state != SS_GAME ) {
+ Com_Printf( "Not running a server.\n" );
+ return qfalse;
+ }
+
+ Com_sprintf( path, sizeof(path), "svdemos/%s.svdm", demoname );
+
+ demo.recordFile = FS_FOpenFileWrite( path );
+ if ( !demo.recordFile ) {
+ Com_Printf( "ERROR: couldn't open %s for writing.\n", path );
+ return qfalse;
+ }
+
+ Com_Printf( "Recording server demo to %s\n", path );
+ demo.recording = qtrue;
+
+ // clear delta state for fresh recording
+ Com_Memset( demo.prevEntities, 0, sizeof(demo.prevEntities) );
+ Com_Memset( demo.prevPlayers, 0, sizeof(demo.prevPlayers) );
+
+ // keyframe interval from cvar (seconds to frames at sv_fps)
+ {
+ int secs = Cvar_VariableIntegerValue( "svdemo_keyframeInterval" );
+ if ( secs > 0 ) {
+ demo.keyframeInterval = secs * sv_fps->integer;
+ } else {
+ demo.keyframeInterval = 0;
+ }
+ // first frame is always a keyframe (makes beginning seekable)
+ demo.framesSinceKeyframe = demo.keyframeInterval;
+ demo.numKeyframes = 0;
+ }
+
+ SVD_WriteHeader( demo.recordFile );
+ return qtrue;
+}
+
+void SVD_Record_f( void ) {
+ char *s;
+
+ s = Cmd_Argv(1);
+ if ( !s[0] ) {
+ Com_Printf( "Usage: svdemo_record \n" );
+ return;
+ }
+
+ SVD_StartRecording( s );
+}
+
+/*
+Auto-record: called from SV_SpawnServer after the map is fully loaded.
+Generates a name from map name + timestamp.
+*/
+void SVD_AutoRecord( void ) {
+ char demoname[MAX_OSPATH];
+ const char *mapname;
+ qtime_t now;
+
+ if ( demo.recording || demo.playing ) {
+ return;
+ }
+
+ if ( !Cvar_VariableIntegerValue( "svdemo_autorecord" ) ) {
+ return;
+ }
+
+ if ( sv.state != SS_GAME ) {
+ return;
+ }
+
+ mapname = Cvar_VariableString( "mapname" );
+ Com_RealTime( &now );
+ Com_sprintf( demoname, sizeof(demoname), "%s_%04d%02d%02d_%02d%02d%02d",
+ mapname,
+ 1900 + now.tm_year, 1 + now.tm_mon, now.tm_mday,
+ now.tm_hour, now.tm_min, now.tm_sec );
+
+ SVD_StartRecording( demoname );
+}
+
+void SVD_StopRecord_f( void ) {
+ int i;
+
+ if ( !demo.recording ) {
+ Com_Printf( "Not recording a server demo.\n" );
+ return;
+ }
+
+ // write end marker
+ SVD_WriteInt( demo.recordFile, -1 );
+
+ // write keyframe index after the end marker.
+ // layout: [numKf][time0 off0 time1 off1 ...][numKf_copy]
+ // numKf_copy at the very end lets playback find the table
+ // by seeking to fileLen - 4.
+ {
+ int kf;
+ SVD_WriteInt( demo.recordFile, demo.numKeyframes );
+ for ( kf = 0; kf < demo.numKeyframes; kf++ ) {
+ SVD_WriteInt( demo.recordFile, demo.keyframeTimes[kf] );
+ SVD_WriteInt( demo.recordFile, demo.keyframeOffsets[kf] );
+ }
+ SVD_WriteInt( demo.recordFile, demo.numKeyframes ); // copy at end
+ Com_Printf( "Wrote %d keyframes.\n", demo.numKeyframes );
+ }
+
+ FS_FCloseFile( demo.recordFile );
+ demo.recordFile = 0;
+ demo.recording = qfalse;
+
+ // free configstring tracking
+ for ( i = 0; i < MAX_CONFIGSTRINGS; i++ ) {
+ if ( demo.lastConfigstrings[i] ) {
+ Z_Free( demo.lastConfigstrings[i] );
+ demo.lastConfigstrings[i] = NULL;
+ }
+ }
+
+ // free keyframe index
+ if ( demo.keyframeTimes ) { Z_Free( demo.keyframeTimes ); demo.keyframeTimes = NULL; }
+ if ( demo.keyframeOffsets ) { Z_Free( demo.keyframeOffsets ); demo.keyframeOffsets = NULL; }
+ demo.numKeyframes = demo.maxKeyframes = 0;
+
+ Com_Printf( "Server demo recording stopped.\n" );
+}
+
+/*
+Called from SV_Frame() after the game has run its frame.
+*/
+/*
+Reset delta compression state. Call on map_restart so the next
+recorded frame writes full entity/player states from baseline.
+*/
+/*
+Capture a broadcast server command for the current frame.
+Called from SV_SendServerCommand when cl == NULL (broadcast).
+*/
+void SVD_CaptureServerCommand( const char *cmd ) {
+ int i;
+
+ if ( !demo.recording ) {
+ return;
+ }
+ if ( demo.numServerCmds >= SVD_MAX_SERVERCMDS ) {
+ return; // overflow, drop command
+ }
+
+ // deduplicate: per-client chat is sent N times (once per client),
+ // only store the first occurrence
+ for ( i = 0; i < demo.numServerCmds; i++ ) {
+ if ( !strcmp( demo.serverCmds[i], cmd ) ) {
+ return;
+ }
+ }
+
+ Q_strncpyz( demo.serverCmds[demo.numServerCmds], cmd, SVD_MAX_SERVERCMD_LEN );
+ demo.numServerCmds++;
+}
+
+void SVD_ResetDeltaState( void ) {
+ if ( !demo.recording ) {
+ return;
+ }
+ Com_Memset( demo.prevEntities, 0, sizeof(demo.prevEntities) );
+ Com_Memset( demo.prevPlayers, 0, sizeof(demo.prevPlayers) );
+ demo.mapRestarted = qtrue; // signal next frame to write restart marker
+}
+
+void SVD_RecordFrame( void ) {
+ if ( !demo.recording ) {
+ return;
+ }
+
+ // periodic keyframe: reset delta state so this frame is decodable
+ // from baseline. record file offset for the keyframe index.
+ if ( demo.keyframeInterval > 0 ) {
+ demo.framesSinceKeyframe++;
+ if ( demo.framesSinceKeyframe >= demo.keyframeInterval ) {
+ demo.framesSinceKeyframe = 0;
+ demo.isKeyframe = qtrue;
+ Com_Memset( demo.prevEntities, 0, sizeof(demo.prevEntities) );
+ Com_Memset( demo.prevPlayers, 0, sizeof(demo.prevPlayers) );
+ // store keyframe: file offset before writing, serverTime
+ if ( demo.numKeyframes >= demo.maxKeyframes ) {
+ int newMax = demo.maxKeyframes ? demo.maxKeyframes * 2 : 256;
+ int *newTimes = Z_Malloc( newMax * sizeof(int) );
+ int *newOffsets = Z_Malloc( newMax * sizeof(int) );
+ if ( demo.keyframeTimes ) {
+ Com_Memcpy( newTimes, demo.keyframeTimes, demo.numKeyframes * sizeof(int) );
+ Com_Memcpy( newOffsets, demo.keyframeOffsets, demo.numKeyframes * sizeof(int) );
+ Z_Free( demo.keyframeTimes );
+ Z_Free( demo.keyframeOffsets );
+ }
+ demo.keyframeTimes = newTimes;
+ demo.keyframeOffsets = newOffsets;
+ demo.maxKeyframes = newMax;
+ }
+ demo.keyframeTimes[demo.numKeyframes] = svs.time;
+ demo.keyframeOffsets[demo.numKeyframes] = FS_FTell( demo.recordFile );
+ demo.numKeyframes++;
+ }
+ }
+
+ SVD_WriteFrame( demo.recordFile );
+}
+
+// ---------------------------------------------------------------
+// Playback: read header
+// ---------------------------------------------------------------
+
+static qboolean SVD_ReadHeader( fileHandle_t f ) {
+ int magic, version;
+
+ magic = SVD_ReadInt( f );
+ if ( magic != SVDEMO_MAGIC ) {
+ Com_Printf( "Not a valid server demo file.\n" );
+ return qfalse;
+ }
+
+ version = SVD_ReadInt( f );
+ if ( version != SVDEMO_VERSION ) {
+ Com_Printf( "Unsupported server demo version %d.\n", version );
+ return qfalse;
+ }
+
+ SVD_ReadInt( f ); // flags (reserved)
+
+ demo.playMaxClients = SVD_ReadInt( f );
+ demo.playFps = SVD_ReadInt( f );
+ FS_Read( demo.playMapName, SVDEMO_MAX_MAPNAME, f );
+ demo.playMapName[SVDEMO_MAX_MAPNAME - 1] = '\0';
+
+ // read configstrings — store for re-application after map load
+ {
+ short idx;
+ while (1) {
+ idx = SVD_ReadShort( f );
+ if ( idx == (short)0xFFFF ) {
+ break;
+ }
+ {
+ short len = SVD_ReadShort( f );
+ char buf[BIG_INFO_STRING];
+ if ( len > 0 && len < (short)sizeof(buf) ) {
+ FS_Read( buf, len, f );
+ buf[len - 1] = '\0';
+ if ( demo.savedConfigstrings[idx] ) {
+ Z_Free( demo.savedConfigstrings[idx] );
+ }
+ demo.savedConfigstrings[idx] = CopyString( buf );
+ }
+ }
+ }
+ }
+
+ return qtrue;
+}
+
+/*
+Re-apply recorded configstrings after the map has loaded.
+Called after devmap finishes in SVD_Play_f.
+*/
+static void SVD_ApplyConfigstrings( void ) {
+ int i;
+ for ( i = 0; i < MAX_CONFIGSTRINGS; i++ ) {
+ // skip CS_SERVERINFO and CS_SYSTEMINFO — they contain latched cvars
+ // (sv_maxclients, sv_pure, etc.) that would trigger a map restart
+ if ( i == CS_SERVERINFO || i == CS_SYSTEMINFO ) {
+ continue;
+ }
+ if ( demo.savedConfigstrings[i] && demo.savedConfigstrings[i][0] ) {
+ SV_SetConfigstring( i, demo.savedConfigstrings[i] );
+ }
+ }
+}
+
+// ---------------------------------------------------------------
+// Playback: read one frame, populate sv.gentities
+// ---------------------------------------------------------------
+
+static qboolean SVD_ReadFrame( fileHandle_t f ) {
+ int serverTime;
+ short numEnts, numChanges;
+ int i, entNum, blockLen;
+ sharedEntity_t *ent;
+ msg_t msg;
+ static byte msgBuf[MAX_GENTITIES * 300];
+ entityState_t newEs;
+
+ serverTime = SVD_ReadInt( f );
+ if ( serverTime == -1 ) {
+ return qfalse; // end of demo
+ }
+
+ // set svs.time to recorded time so entity trajectory interpolation
+ // works correctly (rockets, grenades, etc. use pos.trTime relative
+ // to server time). zombie timeout is already skipped during playback.
+ svs.time = serverTime;
+ numEnts = SVD_ReadShort( f );
+
+ // read frame flags
+ {
+ byte frameFlags;
+ FS_Read( &frameFlags, 1, f );
+ if ( frameFlags & 3 ) {
+ // bit 0 = map restart, bit 1 = keyframe.
+ // both mean: delta state was reset during recording,
+ // so reset playback delta state to decode from baseline.
+ Com_Memset( demo.playPrevEntities, 0, sizeof(demo.playPrevEntities) );
+ Com_Memset( demo.playPrevPlayers, 0, sizeof(demo.playPrevPlayers) );
+ }
+ if ( ( frameFlags & 1 ) || demo.seeked ) {
+ // map restart or seek: reset entity interpolation in cgame
+ svs.snapFlagServerBit |= SNAPFLAG_RESET_ENTITIES;
+ demo.seeked = qfalse;
+ }
+ }
+
+ // read entity message
+ blockLen = SVD_ReadInt( f );
+ if ( blockLen <= 0 || blockLen > (int)sizeof(msgBuf) ) {
+ return qfalse;
+ }
+ FS_Read( msgBuf, blockLen, f );
+ MSG_Init( &msg, msgBuf, sizeof(msgBuf) );
+ msg.cursize = blockLen;
+
+ // clear all entities (spectator's entity is recreated by ClientThink_real)
+ for ( i = 0; i < sv.num_entities; i++ ) {
+ ent = SV_GentityNum( i );
+ if ( ent->r.linked ) {
+ SV_UnlinkEntity( ent );
+ }
+ ent->s.eType = 0;
+ ent->s.number = i;
+ }
+
+ // parse delta-compressed entities
+ while ( 1 ) {
+ entNum = MSG_ReadBits( &msg, GENTITYNUM_BITS );
+ if ( entNum == MAX_GENTITIES - 1 ) {
+ break; // end of entities marker
+ }
+ if ( msg.readcount > msg.cursize ) {
+ break;
+ }
+
+ {
+ entityState_t baseline;
+ entityState_t *from;
+ Com_Memset( &newEs, 0, sizeof(newEs) );
+ if ( demo.playPrevEntities[entNum].active ) {
+ from = &demo.playPrevEntities[entNum].es;
+ } else {
+ Com_Memset( &baseline, 0, sizeof(baseline) );
+ baseline.number = entNum;
+ from = &baseline;
+ }
+ MSG_ReadDeltaEntity( &msg, from, &newEs, entNum );
+ }
+
+ if ( newEs.number == MAX_GENTITIES - 1 ) {
+ // entity was removed
+ demo.playPrevEntities[entNum].active = qfalse;
+ continue;
+ }
+
+ // read svFlags
+ if ( MSG_ReadBits( &msg, 1 ) ) {
+ demo.playPrevEntities[entNum].svFlags = MSG_ReadLong( &msg );
+ }
+
+ demo.playPrevEntities[entNum].es = newEs;
+ demo.playPrevEntities[entNum].active = qtrue;
+
+ // apply to server entity and link for PVS.
+ // use trBase as initial origin — G_RunFrame will refine with
+ // BG_EvaluateTrajectory for moving entities (rockets etc).
+ ent = SV_GentityNum( entNum );
+ ent->s = newEs;
+ ent->s.number = entNum;
+ ent->r.svFlags = demo.playPrevEntities[entNum].svFlags;
+ VectorCopy( newEs.pos.trBase, ent->r.currentOrigin );
+ ent->r.linked = qtrue;
+ SV_LinkEntity( ent );
+
+ if ( entNum + 1 > sv.num_entities ) {
+ sv.num_entities = entNum + 1;
+ }
+ }
+
+ // read player states (delta compressed)
+ {
+ msg_t psmsg;
+ static byte psBuf[MAX_CLIENTS * 600]; // worst case: full playerState from baseline
+ int psMsgLen;
+
+ psMsgLen = SVD_ReadInt( f );
+ if ( psMsgLen > 0 && psMsgLen <= (int)sizeof(psBuf) ) {
+ FS_Read( psBuf, psMsgLen, f );
+ MSG_Init( &psmsg, psBuf, sizeof(psBuf) );
+ psmsg.cursize = psMsgLen;
+
+ while ( 1 ) {
+ int clientNum = MSG_ReadBits( &psmsg, 6 );
+ int active = MSG_ReadBits( &psmsg, 1 );
+
+ if ( clientNum == MAX_CLIENTS - 1 && !active ) {
+ break; // terminator
+ }
+ if ( psmsg.readcount > psmsg.cursize ) {
+ break;
+ }
+
+ if ( active ) {
+ playerState_t newPs;
+ playerState_t *from;
+ playerState_t baseline;
+
+ if ( demo.playPrevPlayers[clientNum].active ) {
+ from = &demo.playPrevPlayers[clientNum].ps;
+ } else {
+ Com_Memset( &baseline, 0, sizeof(baseline) );
+ from = &baseline;
+ }
+
+ MSG_ReadDeltaPlayerstate( &psmsg, from, &newPs );
+ demo.playPrevPlayers[clientNum].ps = newPs;
+ demo.playPrevPlayers[clientNum].active = qtrue;
+
+ // inject into game module's client state
+ {
+ playerState_t *gamePs = SV_GameClientNum( clientNum );
+ *gamePs = newPs;
+ }
+ } else {
+ demo.playPrevPlayers[clientNum].active = qfalse;
+ // clear game playerState so G_RunFrame sees commandTime=0
+ {
+ playerState_t *gamePs = SV_GameClientNum( clientNum );
+ Com_Memset( gamePs, 0, sizeof(*gamePs) );
+ }
+ }
+ }
+ }
+ }
+
+ // read configstring changes (skip SERVERINFO/SYSTEMINFO to avoid latch restarts)
+ numChanges = SVD_ReadShort( f );
+ for ( i = 0; i < numChanges; i++ ) {
+ short idx = SVD_ReadShort( f );
+ short len = SVD_ReadShort( f );
+ char buf[BIG_INFO_STRING];
+ if ( len > 0 && len < (short)sizeof(buf) ) {
+ FS_Read( buf, len, f );
+ buf[len - 1] = '\0';
+ if ( idx != CS_SERVERINFO && idx != CS_SYSTEMINFO ) {
+ SV_SetConfigstring( idx, buf );
+ }
+ }
+ }
+
+ // read server commands (chat, prints, etc.) and replay to spectator
+ {
+ short numCmds = SVD_ReadShort( f );
+ for ( i = 0; i < numCmds; i++ ) {
+ short len = SVD_ReadShort( f );
+ char buf[SVD_MAX_SERVERCMD_LEN];
+ if ( len > 0 && len < (short)sizeof(buf) ) {
+ FS_Read( buf, len, f );
+ buf[len - 1] = '\0';
+ // broadcast — only the spectator is CS_ACTIVE, zombies are skipped
+ SV_SendServerCommand( NULL, "%s", buf );
+ }
+ }
+ }
+
+ return qtrue;
+}
+
+// ---------------------------------------------------------------
+// Playback commands
+// ---------------------------------------------------------------
+
+void SVD_Play_f( void ) {
+ char name[MAX_OSPATH];
+ char *s;
+ int len;
+
+ if ( demo.recording ) {
+ Com_Printf( "Stop recording first (svdemo_stop).\n" );
+ return;
+ }
+
+ s = Cmd_Argv(1);
+ if ( !s[0] ) {
+ Com_Printf( "Usage: svdemo_play \n" );
+ return;
+ }
+
+ // stop current playback if switching demos
+ if ( demo.playing ) {
+ SVD_CleanupPlayback();
+ }
+
+ Com_sprintf( name, sizeof(name), "svdemos/%s.svdm", s );
+
+ memset( &demo, 0, sizeof(demo) );
+
+ len = FS_FOpenFileRead( name, &demo.playFile, qtrue );
+ if ( !demo.playFile || len <= 0 ) {
+ Com_Printf( "ERROR: couldn't open %s.\n", name );
+ return;
+ }
+
+ if ( !SVD_ReadHeader( demo.playFile ) ) {
+ FS_FCloseFile( demo.playFile );
+ demo.playFile = 0;
+ return;
+ }
+
+ // read keyframe index from the end of the file.
+ // layout: [frames][-1][numKf][time0 off0 ...][numKf_copy]
+ // last 4 bytes of file = numKf_copy.
+ {
+ int frameStart = FS_FTell( demo.playFile );
+ int numKf, kf;
+
+ FS_Seek( demo.playFile, len - 4, FS_SEEK_SET );
+ numKf = SVD_ReadInt( demo.playFile );
+
+ if ( numKf > 0 && numKf < 1000000 ) {
+ // seek to start of keyframe table: end - 4 - numKf*8 - 4
+ int tableStart = len - 4 - numKf * 8 - 4;
+ FS_Seek( demo.playFile, tableStart + 4, FS_SEEK_SET ); // skip numKf
+
+ demo.numKeyframes = numKf;
+ demo.maxKeyframes = numKf;
+ demo.keyframeTimes = Z_Malloc( numKf * sizeof(int) );
+ demo.keyframeOffsets = Z_Malloc( numKf * sizeof(int) );
+
+ for ( kf = 0; kf < numKf; kf++ ) {
+ demo.keyframeTimes[kf] = SVD_ReadInt( demo.playFile );
+ demo.keyframeOffsets[kf] = SVD_ReadInt( demo.playFile );
+ }
+ Com_Printf( "Loaded %d keyframes.\n", numKf );
+ }
+
+ // seek back to start of frame data
+ FS_Seek( demo.playFile, frameStart, FS_SEEK_SET );
+ }
+
+ demo.playing = qtrue;
+ demo.endOfDemo = qfalse;
+ demo.needConfigstrings = qtrue;
+
+ Com_Printf( "Playing server demo: map=%s maxclients=%d fps=%d\n",
+ demo.playMapName, demo.playMaxClients, demo.playFps );
+
+ // Shut down current server first so no clients carry over to
+ // reserved slots. SV_Shutdown triggers our cleanup hook, but
+ // demo.starting prevents it from clearing our state.
+ demo.starting = qtrue;
+ if ( com_sv_running->integer ) {
+ SV_Shutdown( "Demo playback\n" );
+ }
+
+ // Set demo cvar BEFORE devmap so G_InitGame can read it.
+ // The previous server is gone so no old game module to conflict with.
+ Cvar_Set2( "sv_demoplaying", "1", qtrue );
+
+ Cbuf_ExecuteText( EXEC_NOW, va("set sv_maxclients %d\n", MAX_CLIENTS) );
+ Cbuf_ExecuteText( EXEC_NOW, va("set sv_fps %d\n", demo.playFps) );
+ Cbuf_ExecuteText( EXEC_NOW, va("devmap %s\n", demo.playMapName) );
+ demo.starting = qfalse;
+
+ // CS_SVDEMO configstring is set by G_InitGame from the cvar
+
+ // Reserve recorded player slots. Server is fresh (SV_Shutdown cleared
+ // old clients), local client hasn't connected yet.
+ {
+ int i;
+ for ( i = 0; i < demo.playMaxClients; i++ ) {
+ if ( svs.clients[i].state == CS_FREE ) {
+ svs.clients[i].state = CS_ZOMBIE;
+ svs.clients[i].rate = 10000;
+ svs.clients[i].nextSnapshotTime = 0x7FFFFFFF;
+ svs.clients[i].lastPacketTime = svs.time;
+ }
+ }
+ }
+}
+
+void SVD_CleanupPlayback( void ) {
+ int i;
+
+ if ( !demo.playing ) {
+ return;
+ }
+
+ FS_FCloseFile( demo.playFile );
+
+ // free saved configstrings
+ for ( i = 0; i < MAX_CONFIGSTRINGS; i++ ) {
+ if ( demo.savedConfigstrings[i] ) {
+ Z_Free( demo.savedConfigstrings[i] );
+ }
+ }
+
+ // free zombie client slots
+ for ( i = 0; i < demo.playMaxClients; i++ ) {
+ if ( svs.clients[i].state == CS_ZOMBIE ) {
+ svs.clients[i].state = CS_FREE;
+ }
+ }
+
+ // free keyframe index
+ if ( demo.keyframeTimes ) { Z_Free( demo.keyframeTimes ); }
+ if ( demo.keyframeOffsets ) { Z_Free( demo.keyframeOffsets ); }
+
+ memset( &demo, 0, sizeof(demo) );
+ Cvar_Set2( "sv_demoplaying", "0", qtrue );
+}
+
+void SVD_StopPlay_f( void ) {
+ if ( !demo.playing ) {
+ Com_Printf( "Not playing a server demo.\n" );
+ return;
+ }
+
+ SVD_CleanupPlayback();
+ Com_Printf( "Server demo playback stopped.\n" );
+
+ // disconnect to return to main menu
+ Cbuf_ExecuteText( EXEC_APPEND, "disconnect\n" );
+}
+
+/*
+Unified stop command: stops recording or playback, whichever is active.
+*/
+void SVD_Stop_f( void ) {
+ if ( demo.recording ) {
+ SVD_StopRecord_f();
+ } else if ( demo.playing ) {
+ SVD_StopPlay_f();
+ } else {
+ Com_Printf( "Not recording or playing a server demo.\n" );
+ }
+}
+
+void SVD_Pause_f( void ) {
+ if ( !demo.playing ) {
+ Com_Printf( "Not playing a server demo.\n" );
+ return;
+ }
+ demo.paused = !demo.paused;
+ if ( !demo.paused ) {
+ // resuming — toggle SERVERCOUNT to reset client snapshot timing
+ // (drifted during pause from identical serverTimes).
+ svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT;
+ }
+ Com_Printf( "Demo playback %s.\n", demo.paused ? "paused" : "resumed" );
+}
+
+void SVD_Seek_f( void ) {
+ int targetTime, i, bestKf;
+ float seconds;
+
+ if ( !demo.playing ) {
+ Com_Printf( "Not playing a server demo.\n" );
+ return;
+ }
+
+ if ( Cmd_Argc() < 2 ) {
+ Com_Printf( "Usage: svdemo_seek \n" );
+ return;
+ }
+
+ if ( demo.numKeyframes <= 0 ) {
+ Com_Printf( "No keyframes in this demo — seeking not available.\n" );
+ return;
+ }
+
+ seconds = atof( Cmd_Argv(1) );
+ targetTime = svs.time + (int)(seconds * 1000);
+
+ // find nearest keyframe at or before target time
+ bestKf = -1;
+ for ( i = 0; i < demo.numKeyframes; i++ ) {
+ if ( demo.keyframeTimes[i] <= targetTime ) {
+ bestKf = i;
+ } else {
+ break;
+ }
+ }
+
+ if ( bestKf < 0 ) {
+ // target is before the first keyframe — seek to first
+ bestKf = 0;
+ targetTime = demo.keyframeTimes[0];
+ }
+
+ // seek to keyframe file position
+ FS_Seek( demo.playFile, demo.keyframeOffsets[bestKf], FS_SEEK_SET );
+
+ // reset delta state (keyframe is encoded from baseline)
+ Com_Memset( demo.playPrevEntities, 0, sizeof(demo.playPrevEntities) );
+ Com_Memset( demo.playPrevPlayers, 0, sizeof(demo.playPrevPlayers) );
+
+ // set svs.time to the keyframe time so the SV_Frame loop
+ // doesn't advance from the old time before reading
+ svs.time = demo.keyframeTimes[bestKf];
+
+ // toggle SERVERCOUNT to reset client time delta
+ svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT;
+
+ demo.seeked = qtrue;
+ demo.endOfDemo = qfalse;
+
+ // read the keyframe directly (works even when paused)
+ svs.snapFlagServerBit &= ~SNAPFLAG_RESET_ENTITIES;
+ if ( !SVD_ReadFrame( demo.playFile ) ) {
+ demo.endOfDemo = qtrue;
+ }
+
+ // reset client snapshot timing
+ {
+ int j;
+ for ( j = 0; j < sv_maxclients->integer; j++ ) {
+ if ( svs.clients[j].state >= CS_ACTIVE ) {
+ svs.clients[j].nextSnapshotTime = svs.time;
+ }
+ }
+ }
+
+ // ensure one frame runs on next SV_Frame (for G_RunFrame + snapshot)
+ sv.timeResidual = 1000 / sv_fps->integer;
+
+ Com_Printf( "Seeked to time %d.\n", svs.time );
+}
+
+void SVD_SeekExact_f( void ) {
+ int targetTime, i, bestKf;
+ float seconds;
+
+ if ( !demo.playing ) {
+ Com_Printf( "Not playing a server demo.\n" );
+ return;
+ }
+
+ if ( Cmd_Argc() < 2 ) {
+ Com_Printf( "Usage: svdemo_seekexact \n" );
+ return;
+ }
+
+ if ( demo.numKeyframes <= 0 ) {
+ Com_Printf( "No keyframes in this demo.\n" );
+ return;
+ }
+
+ seconds = atof( Cmd_Argv(1) );
+ targetTime = svs.time + (int)(seconds * 1000);
+
+ // find nearest keyframe at or before target time
+ bestKf = -1;
+ for ( i = 0; i < demo.numKeyframes; i++ ) {
+ if ( demo.keyframeTimes[i] <= targetTime ) {
+ bestKf = i;
+ } else {
+ break;
+ }
+ }
+
+ if ( bestKf < 0 ) {
+ bestKf = 0;
+ targetTime = demo.keyframeTimes[0];
+ }
+
+ // seek to keyframe
+ FS_Seek( demo.playFile, demo.keyframeOffsets[bestKf], FS_SEEK_SET );
+ Com_Memset( demo.playPrevEntities, 0, sizeof(demo.playPrevEntities) );
+ Com_Memset( demo.playPrevPlayers, 0, sizeof(demo.playPrevPlayers) );
+ svs.time = demo.keyframeTimes[bestKf];
+ svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT;
+ demo.seeked = qtrue;
+ demo.endOfDemo = qfalse;
+
+ // read forward from keyframe to target time
+ while ( svs.time < targetTime ) {
+ if ( !SVD_ReadFrame( demo.playFile ) ) {
+ demo.endOfDemo = qtrue;
+ break;
+ }
+ }
+
+ // reset client snapshot timing
+ {
+ int j;
+ for ( j = 0; j < sv_maxclients->integer; j++ ) {
+ if ( svs.clients[j].state >= CS_ACTIVE ) {
+ svs.clients[j].nextSnapshotTime = svs.time;
+ }
+ }
+ }
+
+ sv.timeResidual = 1000 / sv_fps->integer;
+
+ Com_Printf( "Seeked to time %d (read forward %d ms from keyframe).\n",
+ svs.time, svs.time - demo.keyframeTimes[bestKf] );
+}
+
+/*
+Called from SV_Frame() to advance playback by one frame.
+Returns qtrue if a frame was read, qfalse if demo ended.
+*/
+qboolean SVD_PlaybackFrame( void ) {
+ if ( !demo.playing || demo.endOfDemo ) {
+ return qfalse;
+ }
+
+
+ // manual pause — don't consume demo data
+ if ( demo.paused ) {
+ return qfalse;
+ }
+
+ // wait for a spectator to be fully in-game before starting playback.
+ // the server keeps running frames (so the connection handshake completes)
+ // but no demo data is consumed until someone is CS_ACTIVE.
+ if ( SVD_ShouldPause() ) {
+ return qfalse;
+ }
+
+ // apply recorded configstrings once after map load
+ if ( demo.needConfigstrings ) {
+ SVD_ApplyConfigstrings();
+ demo.needConfigstrings = qfalse;
+ }
+
+ // clear one-shot reset flag from previous frame before reading new one
+ svs.snapFlagServerBit &= ~SNAPFLAG_RESET_ENTITIES;
+
+ if ( !SVD_ReadFrame( demo.playFile ) ) {
+ Com_Printf( "Server demo playback finished.\n" );
+ SVD_CleanupPlayback();
+ Cbuf_ExecuteText( EXEC_APPEND, "disconnect\n" );
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+// ---------------------------------------------------------------
+// Queries
+// ---------------------------------------------------------------
+
+qboolean SVD_IsRecording( void ) {
+ return demo.recording;
+}
+
+qboolean SVD_IsPlaying( void ) {
+ return demo.playing;
+}
+
+qboolean SVD_IsPaused( void ) {
+ return demo.playing && demo.paused;
+}
+
+qboolean SVD_IsStarting( void ) {
+ return demo.starting;
+}
+
+
+/*
+Returns qtrue if demo playback should pause (no active spectators).
+Controlled by svdemo_pauseEmpty cvar.
+*/
+qboolean SVD_ShouldPause( void ) {
+ int i;
+
+ if ( !Cvar_VariableIntegerValue( "svdemo_pauseEmpty" ) ) {
+ return qfalse;
+ }
+
+ for ( i = 0; i < sv_maxclients->integer; i++ ) {
+ if ( svs.clients[i].state == CS_ACTIVE ) {
+ return qfalse; // someone is in-game and watching
+ }
+ }
+
+ return qtrue; // nobody connected, pause
+}
+
diff --git a/netdemo-full.patch b/netdemo-full.patch
new file mode 100644
index 0000000..4acf1e8
--- /dev/null
+++ b/netdemo-full.patch
@@ -0,0 +1,6082 @@
+diff --git a/code/cgame/cg_draw.c b/code/cgame/cg_draw.c
+index e29c10f..7d7126e 100644
+--- a/code/cgame/cg_draw.c
++++ b/code/cgame/cg_draw.c
+@@ -1650,6 +1650,16 @@ static void CG_DrawDisconnect( void ) {
+ const char *s;
+ int w; // bk010215 - FIXME char message[1024];
+
++ // server demo playback: detect pause from frozen snapshot time
++ if ( cg.svDemoPlayback ) {
++ if ( cg.nextSnap && cg.nextSnap->serverTime == cg.snap->serverTime ) {
++ s = "Playback Paused";
++ w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH;
++ CG_DrawBigString( 320 - w/2, 100, s, 1.0F );
++ }
++ return;
++ }
++
+ // draw the phone jack if we are completely past our buffers
+ cmdNum = trap_GetCurrentCmdNumber() - CMD_BACKUP + 1;
+ trap_GetUserCmd( cmdNum, &cmd );
+diff --git a/code/cgame/cg_local.h b/code/cgame/cg_local.h
+index 4dd5c9d..0c8e98a 100644
+--- a/code/cgame/cg_local.h
++++ b/code/cgame/cg_local.h
+@@ -457,6 +457,10 @@ typedef struct {
+ int clientNum;
+
+ qboolean demoPlayback;
++ qboolean svDemoPlayback; // server-side demo playback mode
++ qboolean svDemoFreeCamera; // free camera (client-owned movement)
++ playerState_t svDemoCameraPs; // local playerState for free camera
++ int svDemoCameraTime; // real time of last camera update
+ qboolean levelShot; // taking a level menu screenshot
+ int deferredPlayerLoading;
+ qboolean loading; // don't defer players at initial startup
+@@ -1618,6 +1622,7 @@ qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd );
+
+ // used for the weapon select and zoom
+ void trap_SetUserCmdValue( int stateValue, float sensitivityScale );
++void trap_SetClientOrigin( qboolean hasOrigin, float x, float y, float z );
+
+ // aids for VM testing
+ void testPrintInt( char *string, int i );
+diff --git a/code/cgame/cg_main.c b/code/cgame/cg_main.c
+index aa9ec27..97f65a2 100644
+--- a/code/cgame/cg_main.c
++++ b/code/cgame/cg_main.c
+@@ -1896,6 +1896,13 @@ void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) {
+ // get the gamestate from the client system
+ trap_GetGameState( &cgs.gameState );
+
++ // detect server-side demo playback from configstring
++ cg.svDemoPlayback = ( atoi( CG_ConfigString( CS_SVDEMO ) ) != 0 );
++ if ( cg.svDemoPlayback ) {
++ cg.svDemoFreeCamera = qtrue;
++ // camera ps will be initialized from first snapshot in CG_PredictPlayerState
++ }
++
+ // check version
+ s = CG_ConfigString( CS_GAME_VERSION );
+ if ( strcmp( s, GAME_VERSION ) ) {
+diff --git a/code/cgame/cg_predict.c b/code/cgame/cg_predict.c
+index fec2cfb..ed0d72d 100644
+--- a/code/cgame/cg_predict.c
++++ b/code/cgame/cg_predict.c
+@@ -432,6 +432,59 @@ void CG_PredictPlayerState( void ) {
+ // demo playback just copies the moves
+ if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) ) {
+ CG_InterpolatePlayerState( qfalse );
++ // send followed player's origin for PVS during server demo follow mode
++ if ( cg.svDemoPlayback && (cg.snap->ps.pm_flags & PMF_FOLLOW) ) {
++ trap_SetClientOrigin( qtrue,
++ cg.snap->ps.origin[0],
++ cg.snap->ps.origin[1],
++ cg.snap->ps.origin[2] );
++ cg.svDemoFreeCamera = qfalse;
++ }
++ return;
++ }
++
++ // server-side demo free camera: run local PmoveSingle
++ if ( cg.svDemoPlayback && cg.svDemoFreeCamera ) {
++ int realTime = trap_Milliseconds();
++ int dt = realTime - cg.svDemoCameraTime;
++ pmove_t pm;
++
++ // lazy init: place camera at spectator spawn from first snapshot
++ if ( cg.svDemoCameraPs.speed == 0 ) {
++ cg.svDemoCameraPs = cg.snap->ps;
++ cg.svDemoCameraPs.pm_type = PM_SPECTATOR;
++ cg.svDemoCameraPs.speed = 480;
++ cg.svDemoCameraTime = realTime;
++ dt = 1;
++ }
++
++ if ( dt < 1 ) dt = 1;
++ if ( dt > 200 ) dt = 200;
++ cg.svDemoCameraTime = realTime;
++
++ // get latest usercmd for input
++ current = trap_GetCurrentCmdNumber();
++ trap_GetUserCmd( current, &latestCmd );
++
++ // set up pmove
++ memset( &pm, 0, sizeof(pm) );
++ pm.ps = &cg.svDemoCameraPs;
++ pm.cmd = latestCmd;
++ pm.cmd.serverTime = cg.svDemoCameraPs.commandTime + dt;
++ pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY;
++ pm.trace = CG_Trace;
++ pm.pointcontents = CG_PointContents;
++
++ Pmove( &pm );
++
++ // use as the predicted state for rendering
++ cg.predictedPlayerState = cg.svDemoCameraPs;
++
++ // send origin for PVS
++ trap_SetClientOrigin( qtrue,
++ cg.svDemoCameraPs.origin[0],
++ cg.svDemoCameraPs.origin[1],
++ cg.svDemoCameraPs.origin[2] );
+ return;
+ }
+
+diff --git a/code/cgame/cg_public.h b/code/cgame/cg_public.h
+index 8514143..d667ce2 100644
+--- a/code/cgame/cg_public.h
++++ b/code/cgame/cg_public.h
+@@ -164,6 +164,7 @@ typedef enum {
+ CG_R_INPVS,
+ // 1.32
+ CG_FS_SEEK,
++ CG_SETCLIENTORIGIN, // set client-owned origin for demo spectator PVS
+
+ /*
+ CG_LOADCAMERA,
+diff --git a/code/cgame/cg_snapshot.c b/code/cgame/cg_snapshot.c
+index add49f0..bce36bb 100644
+--- a/code/cgame/cg_snapshot.c
++++ b/code/cgame/cg_snapshot.c
+@@ -184,6 +184,22 @@ static void CG_TransitionSnapshot( void ) {
+ || cg_nopredict.integer || cg_synchronousClients.integer ) {
+ CG_TransitionPlayerState( ps, ops );
+ }
++
++ // server demo: detect follow→free camera transition
++ if ( cg.svDemoPlayback ) {
++ qboolean wasFollowing = ( ops->pm_flags & PMF_FOLLOW ) != 0;
++ qboolean isFollowing = ( ps->pm_flags & PMF_FOLLOW ) != 0;
++ if ( wasFollowing && !isFollowing ) {
++ // exiting follow mode — init camera from last known position
++ cg.svDemoFreeCamera = qtrue;
++ cg.svDemoCameraTime = trap_Milliseconds();
++ VectorCopy( ops->origin, cg.svDemoCameraPs.origin );
++ VectorCopy( ops->viewangles, cg.svDemoCameraPs.viewangles );
++ cg.svDemoCameraPs.pm_type = PM_SPECTATOR;
++ cg.svDemoCameraPs.speed = 480;
++ cg.svDemoCameraPs.clientNum = cg.clientNum;
++ }
++ }
+ }
+
+ }
+@@ -203,6 +219,15 @@ static void CG_SetNextSnap( snapshot_t *snap ) {
+
+ cg.nextSnap = snap;
+
++ // SNAPFLAG_RESET_ENTITIES: invalidate all entities so they are
++ // treated as new (no interpolation from old positions).
++ // Must happen before the entity loop below.
++ if ( snap->snapFlags & SNAPFLAG_RESET_ENTITIES ) {
++ for ( num = 0 ; num < MAX_GENTITIES ; num++ ) {
++ cg_entities[ num ].currentValid = qfalse;
++ }
++ }
++
+ BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].nextState, qfalse );
+ cg_entities[ cg.snap->ps.clientNum ].interpolate = qtrue;
+
+@@ -241,6 +266,11 @@ static void CG_SetNextSnap( snapshot_t *snap ) {
+ cg.nextFrameTeleport = qtrue;
+ }
+
++ // entity reset also prevents playerstate interpolation
++ if ( cg.nextSnap->snapFlags & SNAPFLAG_RESET_ENTITIES ) {
++ cg.nextFrameTeleport = qtrue;
++ }
++
+ // sort out solid entities
+ CG_BuildSolidList();
+ }
+@@ -372,8 +402,34 @@ void CG_ProcessSnapshots( void ) {
+ CG_SetNextSnap( snap );
+
+
+- // if time went backwards, we have a level restart
++ // if time went backwards, we have a level restart or demo seek
+ if ( cg.nextSnap->serverTime < cg.snap->serverTime ) {
++ if ( cg.svDemoPlayback ) {
++ // demo seek — discard old snap, use nextSnap as current,
++ // and wait for another snapshot before rendering
++ cg.snap = cg.nextSnap;
++ cg.nextSnap = NULL;
++ cg.time = cg.snap->serverTime;
++ // reset all entity state and time-dependent fields
++ {
++ int e;
++ for ( e = 0; e < MAX_GENTITIES; e++ ) {
++ cg_entities[e].currentValid = qfalse;
++ cg_entities[e].interpolate = qfalse;
++ cg_entities[e].muzzleFlashTime = 0;
++ cg_entities[e].trailTime = 0;
++ cg_entities[e].dustTrailTime = 0;
++ cg_entities[e].miscTime = 0;
++ cg_entities[e].snapShotTime = 0;
++ cg_entities[e].previousEvent = 0;
++ cg_entities[e].teleportFlag = 0;
++ }
++ }
++ // clear local entities (particles, gibs, etc.)
++ // they reference old times and would render incorrectly
++ CG_InitLocalEntities();
++ break; // exit loop, wait for next snapshot
++ }
+ CG_Error( "CG_ProcessSnapshots: Server time went backwards" );
+ }
+ }
+diff --git a/code/cgame/cg_syscalls.c b/code/cgame/cg_syscalls.c
+index a7bf659..2446b4b 100644
+--- a/code/cgame/cg_syscalls.c
++++ b/code/cgame/cg_syscalls.c
+@@ -333,6 +333,10 @@ void trap_SetUserCmdValue( int stateValue, float sensitivityScale ) {
+ syscall( CG_SETUSERCMDVALUE, stateValue, PASSFLOAT(sensitivityScale) );
+ }
+
++void trap_SetClientOrigin( qboolean hasOrigin, float x, float y, float z ) {
++ syscall( CG_SETCLIENTORIGIN, hasOrigin, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(z) );
++}
++
+ void testPrintInt( char *string, int i ) {
+ syscall( CG_TESTPRINTINT, string, i );
+ }
+diff --git a/code/client/cl_cgame.c b/code/client/cl_cgame.c
+index 0b259d3..e399cef 100644
+--- a/code/client/cl_cgame.c
++++ b/code/client/cl_cgame.c
+@@ -457,6 +457,14 @@ int CL_CgameSystemCalls( int *args ) {
+ return 0;
+ case CG_FS_SEEK:
+ return FS_Seek( args[1], args[2], args[3] );
++ case CG_SETCLIENTORIGIN:
++ cl.cgameHasOrigin = args[1];
++ if ( args[1] ) {
++ cl.cgameOrigin[0] = VMF(2);
++ cl.cgameOrigin[1] = VMF(3);
++ cl.cgameOrigin[2] = VMF(4);
++ }
++ return 0;
+ case CG_SENDCONSOLECOMMAND:
+ Cbuf_AddText( VMA(1) );
+ return 0;
+diff --git a/code/client/cl_input.c b/code/client/cl_input.c
+index f450ee7..c3b4778 100644
+--- a/code/client/cl_input.c
++++ b/code/client/cl_input.c
+@@ -516,6 +516,12 @@ void CL_FinishMove( usercmd_t *cmd ) {
+ for (i=0 ; i<3 ; i++) {
+ cmd->angles[i] = ANGLE2SHORT(cl.viewangles[i]);
+ }
++
++ // client-owned origin for demo spectator PVS
++ cmd->hasOrigin = cl.cgameHasOrigin;
++ if ( cl.cgameHasOrigin ) {
++ VectorCopy( cl.cgameOrigin, cmd->origin );
++ }
+ }
+
+
+diff --git a/code/client/cl_parse.c b/code/client/cl_parse.c
+index 9de3588..8626b3f 100644
+--- a/code/client/cl_parse.c
++++ b/code/client/cl_parse.c
+@@ -288,17 +288,30 @@ void CL_ParseSnapshot( msg_t *msg ) {
+ cl.snapshots[oldMessageNum & PACKET_MASK].valid = qfalse;
+ }
+
+- // copy to the current good spot
+- cl.snap = newSnap;
+- cl.snap.ping = 999;
+- // calculate ping time
+- for ( i = 0 ; i < PACKET_BACKUP ; i++ ) {
+- packetNum = ( clc.netchan.outgoingSequence - 1 - i ) & PACKET_MASK;
+- if ( cl.snap.ps.commandTime >= cl.outPackets[ packetNum ].p_serverTime ) {
+- cl.snap.ping = cls.realtime - cl.outPackets[ packetNum ].p_realtime;
+- break;
++ {
++ int oldSnapFlags = cl.snap.snapFlags;
++
++ // copy to the current good spot
++ cl.snap = newSnap;
++ cl.snap.ping = 999;
++ // calculate ping time
++ for ( i = 0 ; i < PACKET_BACKUP ; i++ ) {
++ packetNum = ( clc.netchan.outgoingSequence - 1 - i ) & PACKET_MASK;
++ if ( cl.snap.ps.commandTime >= cl.outPackets[ packetNum ].p_serverTime ) {
++ cl.snap.ping = cls.realtime - cl.outPackets[ packetNum ].p_realtime;
++ break;
++ }
++ }
++ // if server count changed (map_restart or demo unpause), force-reset
++ // the time delta so the client doesn't slowly drift back to sync
++ if ( ( oldSnapFlags ^ newSnap.snapFlags ) & SNAPFLAG_SERVERCOUNT ) {
++ cl.serverTimeDelta = cl.snap.serverTime - cls.realtime;
++ cl.oldServerTime = cl.snap.serverTime;
++ cl.serverTime = cl.snap.serverTime;
++ cl.oldFrameServerTime = cl.snap.serverTime; // prevent backwards time error on seek
+ }
+ }
++
+ // save the frame off in the backup array for later delta comparisons
+ cl.snapshots[cl.snap.messageNum & PACKET_MASK] = cl.snap;
+
+diff --git a/code/client/client.h b/code/client/client.h
+index 09e02a8..fe36445 100644
+--- a/code/client/client.h
++++ b/code/client/client.h
+@@ -106,6 +106,8 @@ typedef struct {
+ // cgame communicates a few values to the client system
+ int cgameUserCmdValue; // current weapon to add to usercmd_t
+ float cgameSensitivity;
++ qboolean cgameHasOrigin; // client-owned origin for demo spectator PVS
++ vec3_t cgameOrigin; // the origin to send
+
+ // cmds[cmdNumber] is the predicted command, [cmdNumber-1] is the last
+ // properly generated command
+diff --git a/code/game/bg_public.h b/code/game/bg_public.h
+index 0dd59db..5dc1cf2 100644
+--- a/code/game/bg_public.h
++++ b/code/game/bg_public.h
+@@ -79,6 +79,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ #define CS_FLAGSTATUS 23 // string indicating flag status in CTF
+ #define CS_SHADERSTATE 24
+ #define CS_BOTINFO 25
++#define CS_SVDEMO 26 // "1" during server-side demo playback
+
+ #define CS_ITEMS 27 // string of 0's and 1's that tell which items are present
+
+diff --git a/code/game/g_active.c b/code/game/g_active.c
+index 853f5fb..a1e7e82 100644
+--- a/code/game/g_active.c
++++ b/code/game/g_active.c
+@@ -1034,6 +1034,12 @@ void ClientThink( int clientNum ) {
+ // phone jack if they don't get any for a while
+ ent->client->lastCmdTime = level.time;
+
++ // demo playback: don't run ClientThink_real — cgame owns
++ // the camera movement, G_RunFrame handles buttons and PVS origin
++ if ( g_svDemoPlaying.integer ) {
++ return;
++ }
++
+ if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) {
+ ClientThink_real( ent );
+ }
+diff --git a/code/game/g_client.c b/code/game/g_client.c
+index 5a584d4..6d43d7c 100644
+--- a/code/game/g_client.c
++++ b/code/game/g_client.c
+@@ -1012,6 +1012,12 @@ void ClientBegin( int clientNum ) {
+ client->pers.enterTime = level.time;
+ client->pers.teamState.state = TEAM_BEGIN;
+
++ // demo playback: force all clients to spectator
++ if ( g_svDemoPlaying.integer ) {
++ client->sess.sessionTeam = TEAM_SPECTATOR;
++ ClientUserinfoChanged( ent->client - level.clients );
++ }
++
+ // save eflags around this, because changing teams will
+ // cause this to happen with a valid entity, and we
+ // want to make sure the teleport bit is set right
+diff --git a/code/game/g_cmds.c b/code/game/g_cmds.c
+index e72c80e..dc87e58 100644
+--- a/code/game/g_cmds.c
++++ b/code/game/g_cmds.c
+@@ -661,6 +661,20 @@ void Cmd_Team_f( gentity_t *ent ) {
+ int oldTeam;
+ char s[MAX_TOKEN_CHARS];
+
++ // demo playback: only allow "team spectator" (to exit follow mode)
++ if ( g_svDemoPlaying.integer ) {
++ char s2[MAX_TOKEN_CHARS];
++ if ( trap_Argc() == 2 ) {
++ trap_Argv( 1, s2, sizeof(s2) );
++ if ( !Q_stricmp( s2, "spectator" ) || !Q_stricmp( s2, "s" ) ) {
++ StopFollowing( ent );
++ return;
++ }
++ }
++ trap_SendServerCommand( ent-g_entities, "print \"Only spectating allowed during demo playback.\n\"" );
++ return;
++ }
++
+ if ( trap_Argc() != 2 ) {
+ oldTeam = ent->client->sess.sessionTeam;
+ switch ( oldTeam ) {
+diff --git a/code/game/g_local.h b/code/game/g_local.h
+index 1a55955..057722c 100644
+--- a/code/game/g_local.h
++++ b/code/game/g_local.h
+@@ -641,6 +641,7 @@ void ClientCommand( int clientNum );
+ // g_active.c
+ //
+ void ClientThink( int clientNum );
++void ClientThink_real( gentity_t *ent );
+ void ClientEndFrame( gentity_t *ent );
+ void G_RunClient( gentity_t *ent );
+
+@@ -715,6 +716,7 @@ extern gentity_t g_entities[MAX_GENTITIES];
+ #define FOFS(x) ((int)&(((gentity_t *)0)->x))
+
+ extern vmCvar_t g_gametype;
++extern vmCvar_t g_svDemoPlaying;
+ extern vmCvar_t g_dedicated;
+ extern vmCvar_t g_cheats;
+ extern vmCvar_t g_maxclients; // allow this many total, including spectators
+diff --git a/code/game/g_main.c b/code/game/g_main.c
+index 9f60272..117f6d7 100644
+--- a/code/game/g_main.c
++++ b/code/game/g_main.c
+@@ -39,6 +39,7 @@ gentity_t g_entities[MAX_GENTITIES];
+ gclient_t g_clients[MAX_CLIENTS];
+
+ vmCvar_t g_gametype;
++vmCvar_t g_svDemoPlaying;
+ vmCvar_t g_dmflags;
+ vmCvar_t g_fraglimit;
+ vmCvar_t g_timelimit;
+@@ -108,6 +109,7 @@ static cvarTable_t gameCvarTable[] = {
+
+ // latched vars
+ { &g_gametype, "g_gametype", "0", CVAR_SERVERINFO | CVAR_USERINFO | CVAR_LATCH, 0, qfalse },
++ { &g_svDemoPlaying, "sv_demoplaying", "0", CVAR_ROM, 0, qfalse },
+
+ { &g_maxclients, "sv_maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qfalse },
+ { &g_maxGameClients, "g_maxGameClients", "0", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qfalse },
+@@ -416,6 +418,11 @@ void G_InitGame( int levelTime, int randomSeed, int restart ) {
+
+ G_RegisterCvars();
+
++ // signal server-side demo mode to cgame via configstring
++ if ( g_svDemoPlaying.integer ) {
++ trap_SetConfigstring( CS_SVDEMO, "1" );
++ }
++
+ G_ProcessIPBans();
+
+ G_InitMemory();
+@@ -764,6 +771,10 @@ void CalculateRanks( void ) {
+ int rank;
+ int score;
+ int newScore;
++
++ // (demo playback note: this runs normally so the spectator
++ // appears in sortedClients. Recorded players won't show here
++ // since they're not connected in the game module.)
+ gclient_t *cl;
+
+ level.follow1 = -1;
+@@ -1729,6 +1740,89 @@ int start, end;
+ // get any cvar changes
+ G_UpdateCvars();
+
++ // demo playback: sync recorded player states and process spectator
++ if ( g_svDemoPlaying.integer ) {
++ gentity_t *specEnt = NULL;
++
++ // mark recorded players as connected based on their playerState.
++ // the server injected playerStates via SV_GameClientNum before
++ // calling G_RunFrame, so g_clients[i].ps is already populated.
++ for ( i = 0; i < level.maxclients; i++ ) {
++ gclient_t *cl = &level.clients[i];
++ gentity_t *e = &g_entities[i];
++
++ // find the spectator — use client-owned origin for PVS
++ if ( e->client && cl->pers.connected == CON_CONNECTED
++ && cl->sess.sessionTeam == TEAM_SPECTATOR ) {
++ // copy client-owned origin from usercmd for PVS culling.
++ // cgame runs its own PmoveSingle for camera movement.
++ if ( cl->pers.cmd.hasOrigin ) {
++ VectorCopy( cl->pers.cmd.origin, cl->ps.origin );
++ VectorCopy( cl->pers.cmd.origin, e->s.pos.trBase );
++ VectorCopy( cl->pers.cmd.origin, e->r.currentOrigin );
++ }
++ // process spectator buttons for follow mode switching
++ // (pers.cmd is updated by ClientThink which still runs)
++ {
++ int oldButtons = cl->buttons;
++ cl->oldbuttons = cl->buttons;
++ cl->buttons = cl->pers.cmd.buttons;
++ // attack cycles follow targets
++ if ( ( cl->buttons & BUTTON_ATTACK ) && !( oldButtons & BUTTON_ATTACK ) ) {
++ Cmd_FollowCycle_f( e, 1 );
++ }
++ }
++ specEnt = e;
++ continue;
++ }
++
++ // check if server injected a valid playerState for this slot
++ if ( cl->ps.commandTime > 0 ) {
++ cl->pers.connected = CON_CONNECTED;
++ cl->sess.sessionTeam = cl->ps.persistant[PERS_TEAM];
++ e->inuse = qtrue;
++ e->client = cl;
++ e->s.clientNum = i;
++ e->s.number = i;
++ } else if ( cl->pers.connected == CON_CONNECTED
++ && cl->sess.sessionTeam != TEAM_SPECTATOR ) {
++ // player left — mark disconnected
++ cl->pers.connected = CON_DISCONNECTED;
++ e->inuse = qfalse;
++ }
++ }
++
++ // evaluate trajectories and link all recorded entities for PVS.
++ // SVD_ReadFrame injected entityState_t but didn't link because
++ // the server doesn't have BG_EvaluateTrajectory.
++ // scan up to MAX_GENTITIES because recorded entities may exceed
++ // level.num_entities (which only counts game-module-spawned entities).
++ for ( i = 0; i < MAX_GENTITIES; i++ ) {
++ ent = &g_entities[i];
++ if ( ent->s.eType == 0 && !ent->inuse ) {
++ continue;
++ }
++ // skip spectator slot
++ if ( ent->client && ent->client->pers.connected == CON_CONNECTED
++ && ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
++ continue;
++ }
++ // compute currentOrigin from trajectory
++ BG_EvaluateTrajectory( &ent->s.pos, level.time, ent->r.currentOrigin );
++ ent->r.linked = qtrue;
++ trap_LinkEntity( ent );
++ }
++
++ // update rankings so follow mode can cycle through players
++ CalculateRanks();
++
++ // run end-of-frame for spectator (handles follow mode PS copy)
++ if ( specEnt ) {
++ ClientEndFrame( specEnt );
++ }
++ return;
++ }
++
+ //
+ // go through all allocated objects
+ //
+diff --git a/code/game/g_session.c b/code/game/g_session.c
+index e65c3e1..a8586f8 100644
+--- a/code/game/g_session.c
++++ b/code/game/g_session.c
+@@ -183,6 +183,12 @@ G_WriteSessionData
+ void G_WriteSessionData( void ) {
+ int i;
+
++ // don't persist demo spectator sessions — the forced TEAM_SPECTATOR
++ // would carry over to the next normal game
++ if ( g_svDemoPlaying.integer ) {
++ return;
++ }
++
+ trap_Cvar_Set( "session", va("%i", g_gametype.integer) );
+
+ for ( i = 0 ; i < level.maxclients ; i++ ) {
+diff --git a/code/game/q_shared.h b/code/game/q_shared.h
+index 99e5a67..32a772f 100644
+--- a/code/game/q_shared.h
++++ b/code/game/q_shared.h
+@@ -1085,6 +1085,7 @@ typedef enum {
+ #define SNAPFLAG_RATE_DELAYED 1
+ #define SNAPFLAG_NOT_ACTIVE 2 // snapshot used during connection and for zombies
+ #define SNAPFLAG_SERVERCOUNT 4 // toggled every map_restart so transitions can be detected
++#define SNAPFLAG_RESET_ENTITIES 16 // snap all entities to current position, no interpolation
+
+ //
+ // per-level limits
+@@ -1252,8 +1253,11 @@ typedef struct usercmd_s {
+ int serverTime;
+ int angles[3];
+ int buttons;
+- byte weapon; // weapon
++ byte weapon; // weapon
+ signed char forwardmove, rightmove, upmove;
++ // client-owned origin for demo spectator PVS (optional)
++ qboolean hasOrigin;
++ float origin[3];
+ } usercmd_t;
+
+ //===================================================================
+diff --git a/code/qcommon/msg.c b/code/qcommon/msg.c
+index 97110f8..e4f6696 100644
+--- a/code/qcommon/msg.c
++++ b/code/qcommon/msg.c
+@@ -702,18 +702,27 @@ void MSG_WriteDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t *
+ from->weapon == to->weapon) {
+ MSG_WriteBits( msg, 0, 1 ); // no change
+ oldsize += 7;
+- return;
++ } else {
++ key ^= to->serverTime;
++ MSG_WriteBits( msg, 1, 1 );
++ MSG_WriteDeltaKey( msg, key, from->angles[0], to->angles[0], 16 );
++ MSG_WriteDeltaKey( msg, key, from->angles[1], to->angles[1], 16 );
++ MSG_WriteDeltaKey( msg, key, from->angles[2], to->angles[2], 16 );
++ MSG_WriteDeltaKey( msg, key, from->forwardmove, to->forwardmove, 8 );
++ MSG_WriteDeltaKey( msg, key, from->rightmove, to->rightmove, 8 );
++ MSG_WriteDeltaKey( msg, key, from->upmove, to->upmove, 8 );
++ MSG_WriteDeltaKey( msg, key, from->buttons, to->buttons, 16 );
++ MSG_WriteDeltaKey( msg, key, from->weapon, to->weapon, 8 );
++ }
++ // optional client-owned origin (always written, independent of field changes)
++ if ( to->hasOrigin ) {
++ MSG_WriteBits( msg, 1, 1 );
++ MSG_WriteFloat( msg, to->origin[0] );
++ MSG_WriteFloat( msg, to->origin[1] );
++ MSG_WriteFloat( msg, to->origin[2] );
++ } else {
++ MSG_WriteBits( msg, 0, 1 );
+ }
+- key ^= to->serverTime;
+- MSG_WriteBits( msg, 1, 1 );
+- MSG_WriteDeltaKey( msg, key, from->angles[0], to->angles[0], 16 );
+- MSG_WriteDeltaKey( msg, key, from->angles[1], to->angles[1], 16 );
+- MSG_WriteDeltaKey( msg, key, from->angles[2], to->angles[2], 16 );
+- MSG_WriteDeltaKey( msg, key, from->forwardmove, to->forwardmove, 8 );
+- MSG_WriteDeltaKey( msg, key, from->rightmove, to->rightmove, 8 );
+- MSG_WriteDeltaKey( msg, key, from->upmove, to->upmove, 8 );
+- MSG_WriteDeltaKey( msg, key, from->buttons, to->buttons, 16 );
+- MSG_WriteDeltaKey( msg, key, from->weapon, to->weapon, 8 );
+ }
+
+
+@@ -748,6 +757,15 @@ void MSG_ReadDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t *t
+ to->buttons = from->buttons;
+ to->weapon = from->weapon;
+ }
++ // optional client-owned origin
++ if ( MSG_ReadBits( msg, 1 ) ) {
++ to->hasOrigin = qtrue;
++ to->origin[0] = MSG_ReadFloat( msg );
++ to->origin[1] = MSG_ReadFloat( msg );
++ to->origin[2] = MSG_ReadFloat( msg );
++ } else {
++ to->hasOrigin = qfalse;
++ }
+ }
+
+ /*
+diff --git a/code/quake3.vcxproj b/code/quake3.vcxproj
+index 340ba18..25d6f18 100644
+--- a/code/quake3.vcxproj
++++ b/code/quake3.vcxproj
+@@ -995,6 +995,8 @@
+ MaxSpeed
+ MaxSpeed
+
++
++
+
+ Disabled
+ true
+diff --git a/code/server/lz4.c b/code/server/lz4.c
+new file mode 100644
+index 0000000..1af8ea4
+--- /dev/null
++++ b/code/server/lz4.c
+@@ -0,0 +1,2825 @@
++/*
++ LZ4 - Fast LZ compression algorithm
++ Copyright (c) Yann Collet. All rights reserved.
++
++ BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
++
++ Redistribution and use in source and binary forms, with or without
++ modification, are permitted provided that the following conditions are
++ met:
++
++ * Redistributions of source code must retain the above copyright
++ notice, this list of conditions and the following disclaimer.
++ * Redistributions in binary form must reproduce the above
++ copyright notice, this list of conditions and the following disclaimer
++ in the documentation and/or other materials provided with the
++ distribution.
++
++ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
++ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
++ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
++ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
++ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
++ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
++ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
++ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
++ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
++ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
++ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
++
++ You can contact the author at :
++ - LZ4 homepage : http://www.lz4.org
++ - LZ4 source repository : https://github.com/lz4/lz4
++*/
++
++/*-************************************
++* Tuning parameters
++**************************************/
++/*
++ * LZ4_HEAPMODE :
++ * Select how stateless compression functions like `LZ4_compress_default()`
++ * allocate memory for their hash table,
++ * in memory stack (0:default, fastest), or in memory heap (1:requires malloc()).
++ */
++#ifndef LZ4_HEAPMODE
++# define LZ4_HEAPMODE 0
++#endif
++
++/*
++ * LZ4_ACCELERATION_DEFAULT :
++ * Select "acceleration" for LZ4_compress_fast() when parameter value <= 0
++ */
++#define LZ4_ACCELERATION_DEFAULT 1
++/*
++ * LZ4_ACCELERATION_MAX :
++ * Any "acceleration" value higher than this threshold
++ * get treated as LZ4_ACCELERATION_MAX instead (fix #876)
++ */
++#define LZ4_ACCELERATION_MAX 65537
++
++
++/*-************************************
++* CPU Feature Detection
++**************************************/
++/* LZ4_FORCE_MEMORY_ACCESS
++ * By default, access to unaligned memory is controlled by `memcpy()`, which is safe and portable.
++ * Unfortunately, on some target/compiler combinations, the generated assembly is sub-optimal.
++ * The below switch allow to select different access method for improved performance.
++ * Method 0 (default) : use `memcpy()`. Safe and portable.
++ * Method 1 : `__packed` statement. It depends on compiler extension (ie, not portable).
++ * This method is safe if your compiler supports it, and *generally* as fast or faster than `memcpy`.
++ * Method 2 : direct access. This method is portable but violate C standard.
++ * It can generate buggy code on targets which assembly generation depends on alignment.
++ * But in some circumstances, it's the only known way to get the most performance (ie GCC + ARMv6)
++ * See https://fastcompression.blogspot.fr/2015/08/accessing-unaligned-memory.html for details.
++ * Prefer these methods in priority order (0 > 1 > 2)
++ */
++#ifndef LZ4_FORCE_MEMORY_ACCESS /* can be defined externally */
++# if defined(__GNUC__) && \
++ ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) \
++ || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) \
++ || (defined(__riscv) && defined(__riscv_zicclsm)) )
++# define LZ4_FORCE_MEMORY_ACCESS 2
++# elif (defined(__INTEL_COMPILER) && !defined(_WIN32)) || defined(__GNUC__) || defined(_MSC_VER)
++# define LZ4_FORCE_MEMORY_ACCESS 1
++# endif
++#endif
++
++/*
++ * LZ4_FORCE_SW_BITCOUNT
++ * Define this parameter if your target system or compiler does not support hardware bit count
++ */
++#if defined(_MSC_VER) && defined(_WIN32_WCE) /* Visual Studio for WinCE doesn't support Hardware bit count */
++# undef LZ4_FORCE_SW_BITCOUNT /* avoid double def */
++# define LZ4_FORCE_SW_BITCOUNT
++#endif
++
++
++
++/*-************************************
++* Dependency
++**************************************/
++/*
++ * LZ4_SRC_INCLUDED:
++ * Amalgamation flag, whether lz4.c is included
++ */
++#ifndef LZ4_SRC_INCLUDED
++# define LZ4_SRC_INCLUDED 1
++#endif
++
++#ifndef LZ4_DISABLE_DEPRECATE_WARNINGS
++# define LZ4_DISABLE_DEPRECATE_WARNINGS /* due to LZ4_decompress_safe_withPrefix64k */
++#endif
++
++#ifndef LZ4_STATIC_LINKING_ONLY
++# define LZ4_STATIC_LINKING_ONLY
++#endif
++#include "lz4.h"
++/* see also "memory routines" below */
++
++
++/*-************************************
++* Compiler Options
++**************************************/
++#if defined(_MSC_VER) && (_MSC_VER >= 1400) /* Visual Studio 2005+ */
++# include /* only present in VS2005+ */
++# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */
++# pragma warning(disable : 6237) /* disable: C6237: conditional expression is always 0 */
++# pragma warning(disable : 6239) /* disable: C6239: ( && ) always evaluates to the result of */
++# pragma warning(disable : 6240) /* disable: C6240: ( && ) always evaluates to the result of */
++# pragma warning(disable : 6326) /* disable: C6326: Potential comparison of a constant with another constant */
++#endif /* _MSC_VER */
++
++#ifndef LZ4_FORCE_INLINE
++# if defined (_MSC_VER) && !defined (__clang__) /* MSVC */
++# define LZ4_FORCE_INLINE static __forceinline
++# else
++# if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */
++# if defined (__GNUC__) || defined (__clang__)
++# define LZ4_FORCE_INLINE static inline __attribute__((always_inline))
++# else
++# define LZ4_FORCE_INLINE static inline
++# endif
++# else
++# define LZ4_FORCE_INLINE static
++# endif /* __STDC_VERSION__ */
++# endif /* _MSC_VER */
++#endif /* LZ4_FORCE_INLINE */
++
++/* LZ4_FORCE_O2 and LZ4_FORCE_INLINE
++ * gcc on ppc64le generates an unrolled SIMDized loop for LZ4_wildCopy8,
++ * together with a simple 8-byte copy loop as a fall-back path.
++ * However, this optimization hurts the decompression speed by >30%,
++ * because the execution does not go to the optimized loop
++ * for typical compressible data, and all of the preamble checks
++ * before going to the fall-back path become useless overhead.
++ * This optimization happens only with the -O3 flag, and -O2 generates
++ * a simple 8-byte copy loop.
++ * With gcc on ppc64le, all of the LZ4_decompress_* and LZ4_wildCopy8
++ * functions are annotated with __attribute__((optimize("O2"))),
++ * and also LZ4_wildCopy8 is forcibly inlined, so that the O2 attribute
++ * of LZ4_wildCopy8 does not affect the compression speed.
++ */
++#if defined(__PPC64__) && defined(__LITTLE_ENDIAN__) && defined(__GNUC__) && !defined(__clang__)
++# define LZ4_FORCE_O2 __attribute__((optimize("O2")))
++# undef LZ4_FORCE_INLINE
++# define LZ4_FORCE_INLINE static __inline __attribute__((optimize("O2"),always_inline))
++#else
++# define LZ4_FORCE_O2
++#endif
++
++#if (defined(__GNUC__) && (__GNUC__ >= 3)) || (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 800)) || defined(__clang__)
++# define expect(expr,value) (__builtin_expect ((expr),(value)) )
++#else
++# define expect(expr,value) (expr)
++#endif
++
++#ifndef likely
++#define likely(expr) expect((expr) != 0, 1)
++#endif
++#ifndef unlikely
++#define unlikely(expr) expect((expr) != 0, 0)
++#endif
++
++/* Should the alignment test prove unreliable, for some reason,
++ * it can be disabled by setting LZ4_ALIGN_TEST to 0 */
++#ifndef LZ4_ALIGN_TEST /* can be externally provided */
++# define LZ4_ALIGN_TEST 1
++#endif
++
++
++/*-************************************
++* Memory routines
++**************************************/
++
++/*! LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION :
++ * Disable relatively high-level LZ4/HC functions that use dynamic memory
++ * allocation functions (malloc(), calloc(), free()).
++ *
++ * Note that this is a compile-time switch. And since it disables
++ * public/stable LZ4 v1 API functions, we don't recommend using this
++ * symbol to generate a library for distribution.
++ *
++ * The following public functions are removed when this symbol is defined.
++ * - lz4 : LZ4_createStream, LZ4_freeStream,
++ * LZ4_createStreamDecode, LZ4_freeStreamDecode, LZ4_create (deprecated)
++ * - lz4hc : LZ4_createStreamHC, LZ4_freeStreamHC,
++ * LZ4_createHC (deprecated), LZ4_freeHC (deprecated)
++ * - lz4frame, lz4file : All LZ4F_* functions
++ */
++#if defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION)
++# define ALLOC(s) lz4_error_memory_allocation_is_disabled
++# define ALLOC_AND_ZERO(s) lz4_error_memory_allocation_is_disabled
++# define FREEMEM(p) lz4_error_memory_allocation_is_disabled
++#elif defined(LZ4_USER_MEMORY_FUNCTIONS)
++/* memory management functions can be customized by user project.
++ * Below functions must exist somewhere in the Project
++ * and be available at link time */
++void* LZ4_malloc(size_t s);
++void* LZ4_calloc(size_t n, size_t s);
++void LZ4_free(void* p);
++# define ALLOC(s) LZ4_malloc(s)
++# define ALLOC_AND_ZERO(s) LZ4_calloc(1,s)
++# define FREEMEM(p) LZ4_free(p)
++#else
++# include /* malloc, calloc, free */
++# define ALLOC(s) malloc(s)
++# define ALLOC_AND_ZERO(s) calloc(1,s)
++# define FREEMEM(p) free(p)
++#endif
++
++#if ! LZ4_FREESTANDING
++# include /* memset, memcpy */
++#endif
++#if !defined(LZ4_memset)
++# define LZ4_memset(p,v,s) memset((p),(v),(s))
++#endif
++#define MEM_INIT(p,v,s) LZ4_memset((p),(v),(s))
++
++
++/*-************************************
++* Common Constants
++**************************************/
++#define MINMATCH 4
++
++#define WILDCOPYLENGTH 8
++#define LASTLITERALS 5 /* see ../doc/lz4_Block_format.md#parsing-restrictions */
++#define MFLIMIT 12 /* see ../doc/lz4_Block_format.md#parsing-restrictions */
++#define MATCH_SAFEGUARD_DISTANCE ((2*WILDCOPYLENGTH) - MINMATCH) /* ensure it's possible to write 2 x wildcopyLength without overflowing output buffer */
++#define FASTLOOP_SAFE_DISTANCE 64
++static const int LZ4_minLength = (MFLIMIT+1);
++
++#define KB *(1 <<10)
++#define MB *(1 <<20)
++#define GB *(1U<<30)
++
++#define LZ4_DISTANCE_ABSOLUTE_MAX 65535
++#if (LZ4_DISTANCE_MAX > LZ4_DISTANCE_ABSOLUTE_MAX) /* max supported by LZ4 format */
++# error "LZ4_DISTANCE_MAX is too big : must be <= 65535"
++#endif
++
++#define ML_BITS 4
++#define ML_MASK ((1U<=1)
++# include
++#else
++# ifndef assert
++# define assert(condition) ((void)0)
++# endif
++#endif
++
++#define LZ4_STATIC_ASSERT(c) { enum { LZ4_static_assert = 1/(int)(!!(c)) }; } /* use after variable declarations */
++
++#if defined(LZ4_DEBUG) && (LZ4_DEBUG>=2)
++# include
++ static int g_debuglog_enable = 1;
++# define DEBUGLOG(l, ...) { \
++ if ((g_debuglog_enable) && (l<=LZ4_DEBUG)) { \
++ fprintf(stderr, __FILE__ " %i: ", __LINE__); \
++ fprintf(stderr, __VA_ARGS__); \
++ fprintf(stderr, " \n"); \
++ } }
++#else
++# define DEBUGLOG(l, ...) {} /* disabled */
++#endif
++
++static int LZ4_isAligned(const void* ptr, size_t alignment)
++{
++ return ((size_t)ptr & (alignment -1)) == 0;
++}
++
++
++/*-************************************
++* Types
++**************************************/
++#include
++#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */)
++# include
++ typedef unsigned char BYTE; /*uint8_t not necessarily blessed to alias arbitrary type*/
++ typedef uint16_t U16;
++ typedef uint32_t U32;
++ typedef int32_t S32;
++ typedef uint64_t U64;
++ typedef uintptr_t uptrval;
++#else
++# if UINT_MAX != 4294967295UL
++# error "LZ4 code (when not C++ or C99) assumes that sizeof(int) == 4"
++# endif
++ typedef unsigned char BYTE;
++ typedef unsigned short U16;
++ typedef unsigned int U32;
++ typedef signed int S32;
++ typedef unsigned long long U64;
++ typedef size_t uptrval; /* generally true, except OpenVMS-64 */
++#endif
++
++#if defined(__x86_64__)
++ typedef U64 reg_t; /* 64-bits in x32 mode */
++#else
++ typedef size_t reg_t; /* 32-bits in x32 mode */
++#endif
++
++typedef enum {
++ notLimited = 0,
++ limitedOutput = 1,
++ fillOutput = 2
++} limitedOutput_directive;
++
++
++/*-************************************
++* Reading and writing into memory
++**************************************/
++
++/**
++ * LZ4 relies on memcpy with a constant size being inlined. In freestanding
++ * environments, the compiler can't assume the implementation of memcpy() is
++ * standard compliant, so it can't apply its specialized memcpy() inlining
++ * logic. When possible, use __builtin_memcpy() to tell the compiler to analyze
++ * memcpy() as if it were standard compliant, so it can inline it in freestanding
++ * environments. This is needed when decompressing the Linux Kernel, for example.
++ */
++#if !defined(LZ4_memcpy)
++# if defined(__GNUC__) && (__GNUC__ >= 4)
++# define LZ4_memcpy(dst, src, size) __builtin_memcpy(dst, src, size)
++# else
++# define LZ4_memcpy(dst, src, size) memcpy(dst, src, size)
++# endif
++#endif
++
++#if !defined(LZ4_memmove)
++# if defined(__GNUC__) && (__GNUC__ >= 4)
++# define LZ4_memmove __builtin_memmove
++# else
++# define LZ4_memmove memmove
++# endif
++#endif
++
++static unsigned LZ4_isLittleEndian(void)
++{
++ const union { U32 u; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */
++ return one.c[0];
++}
++
++#if defined(__GNUC__) || defined(__INTEL_COMPILER)
++#define LZ4_PACK( __Declaration__ ) __Declaration__ __attribute__((__packed__))
++#elif defined(_MSC_VER)
++#define LZ4_PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop))
++#endif
++
++#if defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==2)
++/* lie to the compiler about data alignment; use with caution */
++
++static U16 LZ4_read16(const void* memPtr) { return *(const U16*) memPtr; }
++static U32 LZ4_read32(const void* memPtr) { return *(const U32*) memPtr; }
++static reg_t LZ4_read_ARCH(const void* memPtr) { return *(const reg_t*) memPtr; }
++
++static void LZ4_write16(void* memPtr, U16 value) { *(U16*)memPtr = value; }
++static void LZ4_write32(void* memPtr, U32 value) { *(U32*)memPtr = value; }
++
++#elif defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==1)
++
++/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */
++/* currently only defined for gcc and icc */
++LZ4_PACK(typedef struct { U16 u16; }) LZ4_unalign16;
++LZ4_PACK(typedef struct { U32 u32; }) LZ4_unalign32;
++LZ4_PACK(typedef struct { reg_t uArch; }) LZ4_unalignST;
++
++static U16 LZ4_read16(const void* ptr) { return ((const LZ4_unalign16*)ptr)->u16; }
++static U32 LZ4_read32(const void* ptr) { return ((const LZ4_unalign32*)ptr)->u32; }
++static reg_t LZ4_read_ARCH(const void* ptr) { return ((const LZ4_unalignST*)ptr)->uArch; }
++
++static void LZ4_write16(void* memPtr, U16 value) { ((LZ4_unalign16*)memPtr)->u16 = value; }
++static void LZ4_write32(void* memPtr, U32 value) { ((LZ4_unalign32*)memPtr)->u32 = value; }
++
++#else /* safe and portable access using memcpy() */
++
++static U16 LZ4_read16(const void* memPtr)
++{
++ U16 val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val;
++}
++
++static U32 LZ4_read32(const void* memPtr)
++{
++ U32 val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val;
++}
++
++static reg_t LZ4_read_ARCH(const void* memPtr)
++{
++ reg_t val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val;
++}
++
++static void LZ4_write16(void* memPtr, U16 value)
++{
++ LZ4_memcpy(memPtr, &value, sizeof(value));
++}
++
++static void LZ4_write32(void* memPtr, U32 value)
++{
++ LZ4_memcpy(memPtr, &value, sizeof(value));
++}
++
++#endif /* LZ4_FORCE_MEMORY_ACCESS */
++
++
++static U16 LZ4_readLE16(const void* memPtr)
++{
++ if (LZ4_isLittleEndian()) {
++ return LZ4_read16(memPtr);
++ } else {
++ const BYTE* p = (const BYTE*)memPtr;
++ return (U16)((U16)p[0] | (p[1]<<8));
++ }
++}
++
++#ifdef LZ4_STATIC_LINKING_ONLY_ENDIANNESS_INDEPENDENT_OUTPUT
++static U32 LZ4_readLE32(const void* memPtr)
++{
++ if (LZ4_isLittleEndian()) {
++ return LZ4_read32(memPtr);
++ } else {
++ const BYTE* p = (const BYTE*)memPtr;
++ return (U32)p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24);
++ }
++}
++#endif
++
++static void LZ4_writeLE16(void* memPtr, U16 value)
++{
++ if (LZ4_isLittleEndian()) {
++ LZ4_write16(memPtr, value);
++ } else {
++ BYTE* p = (BYTE*)memPtr;
++ p[0] = (BYTE) value;
++ p[1] = (BYTE)(value>>8);
++ }
++}
++
++/* customized variant of memcpy, which can overwrite up to 8 bytes beyond dstEnd */
++LZ4_FORCE_INLINE
++void LZ4_wildCopy8(void* dstPtr, const void* srcPtr, void* dstEnd)
++{
++ BYTE* d = (BYTE*)dstPtr;
++ const BYTE* s = (const BYTE*)srcPtr;
++ BYTE* const e = (BYTE*)dstEnd;
++
++ do { LZ4_memcpy(d,s,8); d+=8; s+=8; } while (d= 16. */
++LZ4_FORCE_INLINE void
++LZ4_wildCopy32(void* dstPtr, const void* srcPtr, void* dstEnd)
++{
++ BYTE* d = (BYTE*)dstPtr;
++ const BYTE* s = (const BYTE*)srcPtr;
++ BYTE* const e = (BYTE*)dstEnd;
++
++ do { LZ4_memcpy(d,s,16); LZ4_memcpy(d+16,s+16,16); d+=32; s+=32; } while (d= dstPtr + MINMATCH
++ * - there is at least 12 bytes available to write after dstEnd */
++LZ4_FORCE_INLINE void
++LZ4_memcpy_using_offset(BYTE* dstPtr, const BYTE* srcPtr, BYTE* dstEnd, const size_t offset)
++{
++ BYTE v[8];
++
++ assert(dstEnd >= dstPtr + MINMATCH);
++
++ switch(offset) {
++ case 1:
++ MEM_INIT(v, *srcPtr, 8);
++ break;
++ case 2:
++ LZ4_memcpy(v, srcPtr, 2);
++ LZ4_memcpy(&v[2], srcPtr, 2);
++#if defined(_MSC_VER) && (_MSC_VER <= 1937) /* MSVC 2022 ver 17.7 or earlier */
++# pragma warning(push)
++# pragma warning(disable : 6385) /* warning C6385: Reading invalid data from 'v'. */
++#endif
++ LZ4_memcpy(&v[4], v, 4);
++#if defined(_MSC_VER) && (_MSC_VER <= 1937) /* MSVC 2022 ver 17.7 or earlier */
++# pragma warning(pop)
++#endif
++ break;
++ case 4:
++ LZ4_memcpy(v, srcPtr, 4);
++ LZ4_memcpy(&v[4], srcPtr, 4);
++ break;
++ default:
++ LZ4_memcpy_using_offset_base(dstPtr, srcPtr, dstEnd, offset);
++ return;
++ }
++
++ LZ4_memcpy(dstPtr, v, 8);
++ dstPtr += 8;
++ while (dstPtr < dstEnd) {
++ LZ4_memcpy(dstPtr, v, 8);
++ dstPtr += 8;
++ }
++}
++#endif
++
++
++/*-************************************
++* Common functions
++**************************************/
++static unsigned LZ4_NbCommonBytes (reg_t val)
++{
++ assert(val != 0);
++ if (LZ4_isLittleEndian()) {
++ if (sizeof(val) == 8) {
++# if defined(_MSC_VER) && (_MSC_VER >= 1800) && (defined(_M_AMD64) && !defined(_M_ARM64EC)) && !defined(LZ4_FORCE_SW_BITCOUNT)
++/*-*************************************************************************************************
++* ARM64EC is a Microsoft-designed ARM64 ABI compatible with AMD64 applications on ARM64 Windows 11.
++* The ARM64EC ABI does not support AVX/AVX2/AVX512 instructions, nor their relevant intrinsics
++* including _tzcnt_u64. Therefore, we need to neuter the _tzcnt_u64 code path for ARM64EC.
++****************************************************************************************************/
++# if defined(__clang__) && (__clang_major__ < 10)
++ /* Avoid undefined clang-cl intrinsics issue.
++ * See https://github.com/lz4/lz4/pull/1017 for details. */
++ return (unsigned)__builtin_ia32_tzcnt_u64(val) >> 3;
++# else
++ /* x64 CPUS without BMI support interpret `TZCNT` as `REP BSF` */
++ return (unsigned)_tzcnt_u64(val) >> 3;
++# endif
++# elif defined(_MSC_VER) && defined(_WIN64) && !defined(LZ4_FORCE_SW_BITCOUNT)
++ unsigned long r = 0;
++ _BitScanForward64(&r, (U64)val);
++ return (unsigned)r >> 3;
++# elif (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \
++ ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \
++ !defined(LZ4_FORCE_SW_BITCOUNT)
++ return (unsigned)__builtin_ctzll((U64)val) >> 3;
++# else
++ const U64 m = 0x0101010101010101ULL;
++ val ^= val - 1;
++ return (unsigned)(((U64)((val & (m - 1)) * m)) >> 56);
++# endif
++ } else /* 32 bits */ {
++# if defined(_MSC_VER) && (_MSC_VER >= 1400) && !defined(LZ4_FORCE_SW_BITCOUNT)
++ unsigned long r;
++ _BitScanForward(&r, (U32)val);
++ return (unsigned)r >> 3;
++# elif (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \
++ ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \
++ !defined(__TINYC__) && !defined(LZ4_FORCE_SW_BITCOUNT)
++ return (unsigned)__builtin_ctz((U32)val) >> 3;
++# else
++ const U32 m = 0x01010101;
++ return (unsigned)((((val - 1) ^ val) & (m - 1)) * m) >> 24;
++# endif
++ }
++ } else /* Big Endian CPU */ {
++ if (sizeof(val)==8) {
++# if (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \
++ ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \
++ !defined(__TINYC__) && !defined(LZ4_FORCE_SW_BITCOUNT)
++ return (unsigned)__builtin_clzll((U64)val) >> 3;
++# else
++#if 1
++ /* this method is probably faster,
++ * but adds a 128 bytes lookup table */
++ static const unsigned char ctz7_tab[128] = {
++ 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
++ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
++ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
++ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
++ 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
++ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
++ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
++ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
++ };
++ U64 const mask = 0x0101010101010101ULL;
++ U64 const t = (((val >> 8) - mask) | val) & mask;
++ return ctz7_tab[(t * 0x0080402010080402ULL) >> 57];
++#else
++ /* this method doesn't consume memory space like the previous one,
++ * but it contains several branches,
++ * that may end up slowing execution */
++ static const U32 by32 = sizeof(val)*4; /* 32 on 64 bits (goal), 16 on 32 bits.
++ Just to avoid some static analyzer complaining about shift by 32 on 32-bits target.
++ Note that this code path is never triggered in 32-bits mode. */
++ unsigned r;
++ if (!(val>>by32)) { r=4; } else { r=0; val>>=by32; }
++ if (!(val>>16)) { r+=2; val>>=8; } else { val>>=24; }
++ r += (!val);
++ return r;
++#endif
++# endif
++ } else /* 32 bits */ {
++# if (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \
++ ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \
++ !defined(LZ4_FORCE_SW_BITCOUNT)
++ return (unsigned)__builtin_clz((U32)val) >> 3;
++# else
++ val >>= 8;
++ val = ((((val + 0x00FFFF00) | 0x00FFFFFF) + val) |
++ (val + 0x00FF0000)) >> 24;
++ return (unsigned)val ^ 3;
++# endif
++ }
++ }
++}
++
++
++#define STEPSIZE sizeof(reg_t)
++LZ4_FORCE_INLINE
++unsigned LZ4_count(const BYTE* pIn, const BYTE* pMatch, const BYTE* pInLimit)
++{
++ const BYTE* const pStart = pIn;
++
++ if (likely(pIn < pInLimit-(STEPSIZE-1))) {
++ reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn);
++ if (!diff) {
++ pIn+=STEPSIZE; pMatch+=STEPSIZE;
++ } else {
++ return LZ4_NbCommonBytes(diff);
++ } }
++
++ while (likely(pIn < pInLimit-(STEPSIZE-1))) {
++ reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn);
++ if (!diff) { pIn+=STEPSIZE; pMatch+=STEPSIZE; continue; }
++ pIn += LZ4_NbCommonBytes(diff);
++ return (unsigned)(pIn - pStart);
++ }
++
++ if ((STEPSIZE==8) && (pIn<(pInLimit-3)) && (LZ4_read32(pMatch) == LZ4_read32(pIn))) { pIn+=4; pMatch+=4; }
++ if ((pIn<(pInLimit-1)) && (LZ4_read16(pMatch) == LZ4_read16(pIn))) { pIn+=2; pMatch+=2; }
++ if ((pIn compression run slower on incompressible data */
++
++
++/*-************************************
++* Local Structures and types
++**************************************/
++typedef enum { clearedTable = 0, byPtr, byU32, byU16 } tableType_t;
++
++/**
++ * This enum distinguishes several different modes of accessing previous
++ * content in the stream.
++ *
++ * - noDict : There is no preceding content.
++ * - withPrefix64k : Table entries up to ctx->dictSize before the current blob
++ * blob being compressed are valid and refer to the preceding
++ * content (of length ctx->dictSize), which is available
++ * contiguously preceding in memory the content currently
++ * being compressed.
++ * - usingExtDict : Like withPrefix64k, but the preceding content is somewhere
++ * else in memory, starting at ctx->dictionary with length
++ * ctx->dictSize.
++ * - usingDictCtx : Everything concerning the preceding content is
++ * in a separate context, pointed to by ctx->dictCtx.
++ * ctx->dictionary, ctx->dictSize, and table entries
++ * in the current context that refer to positions
++ * preceding the beginning of the current compression are
++ * ignored. Instead, ctx->dictCtx->dictionary and ctx->dictCtx
++ * ->dictSize describe the location and size of the preceding
++ * content, and matches are found by looking in the ctx
++ * ->dictCtx->hashTable.
++ */
++typedef enum { noDict = 0, withPrefix64k, usingExtDict, usingDictCtx } dict_directive;
++typedef enum { noDictIssue = 0, dictSmall } dictIssue_directive;
++
++
++/*-************************************
++* Local Utils
++**************************************/
++int LZ4_versionNumber (void) { return LZ4_VERSION_NUMBER; }
++const char* LZ4_versionString(void) { return LZ4_VERSION_STRING; }
++int LZ4_compressBound(int isize) { return LZ4_COMPRESSBOUND(isize); }
++int LZ4_sizeofState(void) { return sizeof(LZ4_stream_t); }
++
++
++/*-****************************************
++* Internal Definitions, used only in Tests
++*******************************************/
++#if defined (__cplusplus)
++extern "C" {
++#endif
++
++int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* dest, int srcSize);
++
++int LZ4_decompress_safe_forceExtDict(const char* source, char* dest,
++ int compressedSize, int maxOutputSize,
++ const void* dictStart, size_t dictSize);
++int LZ4_decompress_safe_partial_forceExtDict(const char* source, char* dest,
++ int compressedSize, int targetOutputSize, int dstCapacity,
++ const void* dictStart, size_t dictSize);
++#if defined (__cplusplus)
++}
++#endif
++
++/*-******************************
++* Compression functions
++********************************/
++LZ4_FORCE_INLINE U32 LZ4_hash4(U32 sequence, tableType_t const tableType)
++{
++ if (tableType == byU16)
++ return ((sequence * 2654435761U) >> ((MINMATCH*8)-(LZ4_HASHLOG+1)));
++ else
++ return ((sequence * 2654435761U) >> ((MINMATCH*8)-LZ4_HASHLOG));
++}
++
++LZ4_FORCE_INLINE U32 LZ4_hash5(U64 sequence, tableType_t const tableType)
++{
++ const U32 hashLog = (tableType == byU16) ? LZ4_HASHLOG+1 : LZ4_HASHLOG;
++ if (LZ4_isLittleEndian()) {
++ const U64 prime5bytes = 889523592379ULL;
++ return (U32)(((sequence << 24) * prime5bytes) >> (64 - hashLog));
++ } else {
++ const U64 prime8bytes = 11400714785074694791ULL;
++ return (U32)(((sequence >> 24) * prime8bytes) >> (64 - hashLog));
++ }
++}
++
++LZ4_FORCE_INLINE U32 LZ4_hashPosition(const void* const p, tableType_t const tableType)
++{
++ if ((sizeof(reg_t)==8) && (tableType != byU16)) return LZ4_hash5(LZ4_read_ARCH(p), tableType);
++
++#ifdef LZ4_STATIC_LINKING_ONLY_ENDIANNESS_INDEPENDENT_OUTPUT
++ return LZ4_hash4(LZ4_readLE32(p), tableType);
++#else
++ return LZ4_hash4(LZ4_read32(p), tableType);
++#endif
++}
++
++LZ4_FORCE_INLINE void LZ4_clearHash(U32 h, void* tableBase, tableType_t const tableType)
++{
++ switch (tableType)
++ {
++ default: /* fallthrough */
++ case clearedTable: { /* illegal! */ assert(0); return; }
++ case byPtr: { const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = NULL; return; }
++ case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = 0; return; }
++ case byU16: { U16* hashTable = (U16*) tableBase; hashTable[h] = 0; return; }
++ }
++}
++
++LZ4_FORCE_INLINE void LZ4_putIndexOnHash(U32 idx, U32 h, void* tableBase, tableType_t const tableType)
++{
++ switch (tableType)
++ {
++ default: /* fallthrough */
++ case clearedTable: /* fallthrough */
++ case byPtr: { /* illegal! */ assert(0); return; }
++ case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = idx; return; }
++ case byU16: { U16* hashTable = (U16*) tableBase; assert(idx < 65536); hashTable[h] = (U16)idx; return; }
++ }
++}
++
++/* LZ4_putPosition*() : only used in byPtr mode */
++LZ4_FORCE_INLINE void LZ4_putPositionOnHash(const BYTE* p, U32 h,
++ void* tableBase, tableType_t const tableType)
++{
++ const BYTE** const hashTable = (const BYTE**)tableBase;
++ assert(tableType == byPtr); (void)tableType;
++ hashTable[h] = p;
++}
++
++LZ4_FORCE_INLINE void LZ4_putPosition(const BYTE* p, void* tableBase, tableType_t tableType)
++{
++ U32 const h = LZ4_hashPosition(p, tableType);
++ LZ4_putPositionOnHash(p, h, tableBase, tableType);
++}
++
++/* LZ4_getIndexOnHash() :
++ * Index of match position registered in hash table.
++ * hash position must be calculated by using base+index, or dictBase+index.
++ * Assumption 1 : only valid if tableType == byU32 or byU16.
++ * Assumption 2 : h is presumed valid (within limits of hash table)
++ */
++LZ4_FORCE_INLINE U32 LZ4_getIndexOnHash(U32 h, const void* tableBase, tableType_t tableType)
++{
++ LZ4_STATIC_ASSERT(LZ4_MEMORY_USAGE > 2);
++ if (tableType == byU32) {
++ const U32* const hashTable = (const U32*) tableBase;
++ assert(h < (1U << (LZ4_MEMORY_USAGE-2)));
++ return hashTable[h];
++ }
++ if (tableType == byU16) {
++ const U16* const hashTable = (const U16*) tableBase;
++ assert(h < (1U << (LZ4_MEMORY_USAGE-1)));
++ return hashTable[h];
++ }
++ assert(0); return 0; /* forbidden case */
++}
++
++static const BYTE* LZ4_getPositionOnHash(U32 h, const void* tableBase, tableType_t tableType)
++{
++ assert(tableType == byPtr); (void)tableType;
++ { const BYTE* const* hashTable = (const BYTE* const*) tableBase; return hashTable[h]; }
++}
++
++LZ4_FORCE_INLINE const BYTE*
++LZ4_getPosition(const BYTE* p,
++ const void* tableBase, tableType_t tableType)
++{
++ U32 const h = LZ4_hashPosition(p, tableType);
++ return LZ4_getPositionOnHash(h, tableBase, tableType);
++}
++
++LZ4_FORCE_INLINE void
++LZ4_prepareTable(LZ4_stream_t_internal* const cctx,
++ const int inputSize,
++ const tableType_t tableType) {
++ /* If the table hasn't been used, it's guaranteed to be zeroed out, and is
++ * therefore safe to use no matter what mode we're in. Otherwise, we figure
++ * out if it's safe to leave as is or whether it needs to be reset.
++ */
++ if ((tableType_t)cctx->tableType != clearedTable) {
++ assert(inputSize >= 0);
++ if ((tableType_t)cctx->tableType != tableType
++ || ((tableType == byU16) && cctx->currentOffset + (unsigned)inputSize >= 0xFFFFU)
++ || ((tableType == byU32) && cctx->currentOffset > 1 GB)
++ || tableType == byPtr
++ || inputSize >= 4 KB)
++ {
++ DEBUGLOG(4, "LZ4_prepareTable: Resetting table in %p", (void*)cctx);
++ MEM_INIT(cctx->hashTable, 0, LZ4_HASHTABLESIZE);
++ cctx->currentOffset = 0;
++ cctx->tableType = (U32)clearedTable;
++ } else {
++ DEBUGLOG(4, "LZ4_prepareTable: Re-use hash table (no reset)");
++ }
++ }
++
++ /* Adding a gap, so all previous entries are > LZ4_DISTANCE_MAX back,
++ * is faster than compressing without a gap.
++ * However, compressing with currentOffset == 0 is faster still,
++ * so we preserve that case.
++ */
++ if (cctx->currentOffset != 0 && tableType == byU32) {
++ DEBUGLOG(5, "LZ4_prepareTable: adding 64KB to currentOffset");
++ cctx->currentOffset += 64 KB;
++ }
++
++ /* Finally, clear history */
++ cctx->dictCtx = NULL;
++ cctx->dictionary = NULL;
++ cctx->dictSize = 0;
++}
++
++/** LZ4_compress_generic_validated() :
++ * inlined, to ensure branches are decided at compilation time.
++ * The following conditions are presumed already validated:
++ * - source != NULL
++ * - inputSize > 0
++ */
++LZ4_FORCE_INLINE int LZ4_compress_generic_validated(
++ LZ4_stream_t_internal* const cctx,
++ const char* const source,
++ char* const dest,
++ const int inputSize,
++ int* inputConsumed, /* only written when outputDirective == fillOutput */
++ const int maxOutputSize,
++ const limitedOutput_directive outputDirective,
++ const tableType_t tableType,
++ const dict_directive dictDirective,
++ const dictIssue_directive dictIssue,
++ const int acceleration)
++{
++ int result;
++ const BYTE* ip = (const BYTE*)source;
++
++ U32 const startIndex = cctx->currentOffset;
++ const BYTE* base = (const BYTE*)source - startIndex;
++ const BYTE* lowLimit;
++
++ const LZ4_stream_t_internal* dictCtx = (const LZ4_stream_t_internal*) cctx->dictCtx;
++ const BYTE* const dictionary =
++ dictDirective == usingDictCtx ? dictCtx->dictionary : cctx->dictionary;
++ const U32 dictSize =
++ dictDirective == usingDictCtx ? dictCtx->dictSize : cctx->dictSize;
++ const U32 dictDelta =
++ (dictDirective == usingDictCtx) ? startIndex - dictCtx->currentOffset : 0; /* make indexes in dictCtx comparable with indexes in current context */
++
++ int const maybe_extMem = (dictDirective == usingExtDict) || (dictDirective == usingDictCtx);
++ U32 const prefixIdxLimit = startIndex - dictSize; /* used when dictDirective == dictSmall */
++ const BYTE* const dictEnd = dictionary ? dictionary + dictSize : dictionary;
++ const BYTE* anchor = (const BYTE*) source;
++ const BYTE* const iend = ip + inputSize;
++ const BYTE* const mflimitPlusOne = iend - MFLIMIT + 1;
++ const BYTE* const matchlimit = iend - LASTLITERALS;
++
++ /* the dictCtx currentOffset is indexed on the start of the dictionary,
++ * while a dictionary in the current context precedes the currentOffset */
++ const BYTE* dictBase = (dictionary == NULL) ? NULL :
++ (dictDirective == usingDictCtx) ?
++ dictionary + dictSize - dictCtx->currentOffset :
++ dictionary + dictSize - startIndex;
++
++ BYTE* op = (BYTE*) dest;
++ BYTE* const olimit = op + maxOutputSize;
++
++ U32 offset = 0;
++ U32 forwardH;
++
++ DEBUGLOG(5, "LZ4_compress_generic_validated: srcSize=%i, tableType=%u", inputSize, tableType);
++ assert(ip != NULL);
++ if (tableType == byU16) assert(inputSize= 1);
++
++ lowLimit = (const BYTE*)source - (dictDirective == withPrefix64k ? dictSize : 0);
++
++ /* Update context state */
++ if (dictDirective == usingDictCtx) {
++ /* Subsequent linked blocks can't use the dictionary. */
++ /* Instead, they use the block we just compressed. */
++ cctx->dictCtx = NULL;
++ cctx->dictSize = (U32)inputSize;
++ } else {
++ cctx->dictSize += (U32)inputSize;
++ }
++ cctx->currentOffset += (U32)inputSize;
++ cctx->tableType = (U32)tableType;
++
++ if (inputSizehashTable, byPtr);
++ } else {
++ LZ4_putIndexOnHash(startIndex, h, cctx->hashTable, tableType);
++ } }
++ ip++; forwardH = LZ4_hashPosition(ip, tableType);
++
++ /* Main Loop */
++ for ( ; ; ) {
++ const BYTE* match;
++ BYTE* token;
++ const BYTE* filledIp;
++
++ /* Find a match */
++ if (tableType == byPtr) {
++ const BYTE* forwardIp = ip;
++ int step = 1;
++ int searchMatchNb = acceleration << LZ4_skipTrigger;
++ do {
++ U32 const h = forwardH;
++ ip = forwardIp;
++ forwardIp += step;
++ step = (searchMatchNb++ >> LZ4_skipTrigger);
++
++ if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals;
++ assert(ip < mflimitPlusOne);
++
++ match = LZ4_getPositionOnHash(h, cctx->hashTable, tableType);
++ forwardH = LZ4_hashPosition(forwardIp, tableType);
++ LZ4_putPositionOnHash(ip, h, cctx->hashTable, tableType);
++
++ } while ( (match+LZ4_DISTANCE_MAX < ip)
++ || (LZ4_read32(match) != LZ4_read32(ip)) );
++
++ } else { /* byU32, byU16 */
++
++ const BYTE* forwardIp = ip;
++ int step = 1;
++ int searchMatchNb = acceleration << LZ4_skipTrigger;
++ do {
++ U32 const h = forwardH;
++ U32 const current = (U32)(forwardIp - base);
++ U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType);
++ assert(matchIndex <= current);
++ assert(forwardIp - base < (ptrdiff_t)(2 GB - 1));
++ ip = forwardIp;
++ forwardIp += step;
++ step = (searchMatchNb++ >> LZ4_skipTrigger);
++
++ if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals;
++ assert(ip < mflimitPlusOne);
++
++ if (dictDirective == usingDictCtx) {
++ if (matchIndex < startIndex) {
++ /* there was no match, try the dictionary */
++ assert(tableType == byU32);
++ matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32);
++ match = dictBase + matchIndex;
++ matchIndex += dictDelta; /* make dictCtx index comparable with current context */
++ lowLimit = dictionary;
++ } else {
++ match = base + matchIndex;
++ lowLimit = (const BYTE*)source;
++ }
++ } else if (dictDirective == usingExtDict) {
++ if (matchIndex < startIndex) {
++ DEBUGLOG(7, "extDict candidate: matchIndex=%5u < startIndex=%5u", matchIndex, startIndex);
++ assert(startIndex - matchIndex >= MINMATCH);
++ assert(dictBase);
++ match = dictBase + matchIndex;
++ lowLimit = dictionary;
++ } else {
++ match = base + matchIndex;
++ lowLimit = (const BYTE*)source;
++ }
++ } else { /* single continuous memory segment */
++ match = base + matchIndex;
++ }
++ forwardH = LZ4_hashPosition(forwardIp, tableType);
++ LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType);
++
++ DEBUGLOG(7, "candidate at pos=%u (offset=%u \n", matchIndex, current - matchIndex);
++ if ((dictIssue == dictSmall) && (matchIndex < prefixIdxLimit)) { continue; } /* match outside of valid area */
++ assert(matchIndex < current);
++ if ( ((tableType != byU16) || (LZ4_DISTANCE_MAX < LZ4_DISTANCE_ABSOLUTE_MAX))
++ && (matchIndex+LZ4_DISTANCE_MAX < current)) {
++ continue;
++ } /* too far */
++ assert((current - matchIndex) <= LZ4_DISTANCE_MAX); /* match now expected within distance */
++
++ if (LZ4_read32(match) == LZ4_read32(ip)) {
++ if (maybe_extMem) offset = current - matchIndex;
++ break; /* match found */
++ }
++
++ } while(1);
++ }
++
++ /* Catch up */
++ filledIp = ip;
++ assert(ip > anchor); /* this is always true as ip has been advanced before entering the main loop */
++ if ((match > lowLimit) && unlikely(ip[-1] == match[-1])) {
++ do { ip--; match--; } while (((ip > anchor) & (match > lowLimit)) && (unlikely(ip[-1] == match[-1])));
++ }
++
++ /* Encode Literals */
++ { unsigned const litLength = (unsigned)(ip - anchor);
++ token = op++;
++ if ((outputDirective == limitedOutput) && /* Check output buffer overflow */
++ (unlikely(op + litLength + (2 + 1 + LASTLITERALS) + (litLength/255) > olimit)) ) {
++ return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */
++ }
++ if ((outputDirective == fillOutput) &&
++ (unlikely(op + (litLength+240)/255 /* litlen */ + litLength /* literals */ + 2 /* offset */ + 1 /* token */ + MFLIMIT - MINMATCH /* min last literals so last match is <= end - MFLIMIT */ > olimit))) {
++ op--;
++ goto _last_literals;
++ }
++ if (litLength >= RUN_MASK) {
++ unsigned len = litLength - RUN_MASK;
++ *token = (RUN_MASK<= 255 ; len-=255) *op++ = 255;
++ *op++ = (BYTE)len;
++ }
++ else *token = (BYTE)(litLength< olimit)) {
++ /* the match was too close to the end, rewind and go to last literals */
++ op = token;
++ goto _last_literals;
++ }
++
++ /* Encode Offset */
++ if (maybe_extMem) { /* static test */
++ DEBUGLOG(6, " with offset=%u (ext if > %i)", offset, (int)(ip - (const BYTE*)source));
++ assert(offset <= LZ4_DISTANCE_MAX && offset > 0);
++ LZ4_writeLE16(op, (U16)offset); op+=2;
++ } else {
++ DEBUGLOG(6, " with offset=%u (same segment)", (U32)(ip - match));
++ assert(ip-match <= LZ4_DISTANCE_MAX);
++ LZ4_writeLE16(op, (U16)(ip - match)); op+=2;
++ }
++
++ /* Encode MatchLength */
++ { unsigned matchCode;
++
++ if ( (dictDirective==usingExtDict || dictDirective==usingDictCtx)
++ && (lowLimit==dictionary) /* match within extDict */ ) {
++ const BYTE* limit = ip + (dictEnd-match);
++ assert(dictEnd > match);
++ if (limit > matchlimit) limit = matchlimit;
++ matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, limit);
++ ip += (size_t)matchCode + MINMATCH;
++ if (ip==limit) {
++ unsigned const more = LZ4_count(limit, (const BYTE*)source, matchlimit);
++ matchCode += more;
++ ip += more;
++ }
++ DEBUGLOG(6, " with matchLength=%u starting in extDict", matchCode+MINMATCH);
++ } else {
++ matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, matchlimit);
++ ip += (size_t)matchCode + MINMATCH;
++ DEBUGLOG(6, " with matchLength=%u", matchCode+MINMATCH);
++ }
++
++ if ((outputDirective) && /* Check output buffer overflow */
++ (unlikely(op + (1 + LASTLITERALS) + (matchCode+240)/255 > olimit)) ) {
++ if (outputDirective == fillOutput) {
++ /* Match description too long : reduce it */
++ U32 newMatchCode = 15 /* in token */ - 1 /* to avoid needing a zero byte */ + ((U32)(olimit - op) - 1 - LASTLITERALS) * 255;
++ ip -= matchCode - newMatchCode;
++ assert(newMatchCode < matchCode);
++ matchCode = newMatchCode;
++ if (unlikely(ip <= filledIp)) {
++ /* We have already filled up to filledIp so if ip ends up less than filledIp
++ * we have positions in the hash table beyond the current position. This is
++ * a problem if we reuse the hash table. So we have to remove these positions
++ * from the hash table.
++ */
++ const BYTE* ptr;
++ DEBUGLOG(5, "Clearing %u positions", (U32)(filledIp - ip));
++ for (ptr = ip; ptr <= filledIp; ++ptr) {
++ U32 const h = LZ4_hashPosition(ptr, tableType);
++ LZ4_clearHash(h, cctx->hashTable, tableType);
++ }
++ }
++ } else {
++ assert(outputDirective == limitedOutput);
++ return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */
++ }
++ }
++ if (matchCode >= ML_MASK) {
++ *token += ML_MASK;
++ matchCode -= ML_MASK;
++ LZ4_write32(op, 0xFFFFFFFF);
++ while (matchCode >= 4*255) {
++ op+=4;
++ LZ4_write32(op, 0xFFFFFFFF);
++ matchCode -= 4*255;
++ }
++ op += matchCode / 255;
++ *op++ = (BYTE)(matchCode % 255);
++ } else
++ *token += (BYTE)(matchCode);
++ }
++ /* Ensure we have enough space for the last literals. */
++ assert(!(outputDirective == fillOutput && op + 1 + LASTLITERALS > olimit));
++
++ anchor = ip;
++
++ /* Test end of chunk */
++ if (ip >= mflimitPlusOne) break;
++
++ /* Fill table */
++ { U32 const h = LZ4_hashPosition(ip-2, tableType);
++ if (tableType == byPtr) {
++ LZ4_putPositionOnHash(ip-2, h, cctx->hashTable, byPtr);
++ } else {
++ U32 const idx = (U32)((ip-2) - base);
++ LZ4_putIndexOnHash(idx, h, cctx->hashTable, tableType);
++ } }
++
++ /* Test next position */
++ if (tableType == byPtr) {
++
++ match = LZ4_getPosition(ip, cctx->hashTable, tableType);
++ LZ4_putPosition(ip, cctx->hashTable, tableType);
++ if ( (match+LZ4_DISTANCE_MAX >= ip)
++ && (LZ4_read32(match) == LZ4_read32(ip)) )
++ { token=op++; *token=0; goto _next_match; }
++
++ } else { /* byU32, byU16 */
++
++ U32 const h = LZ4_hashPosition(ip, tableType);
++ U32 const current = (U32)(ip-base);
++ U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType);
++ assert(matchIndex < current);
++ if (dictDirective == usingDictCtx) {
++ if (matchIndex < startIndex) {
++ /* there was no match, try the dictionary */
++ assert(tableType == byU32);
++ matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32);
++ match = dictBase + matchIndex;
++ lowLimit = dictionary; /* required for match length counter */
++ matchIndex += dictDelta;
++ } else {
++ match = base + matchIndex;
++ lowLimit = (const BYTE*)source; /* required for match length counter */
++ }
++ } else if (dictDirective==usingExtDict) {
++ if (matchIndex < startIndex) {
++ assert(dictBase);
++ match = dictBase + matchIndex;
++ lowLimit = dictionary; /* required for match length counter */
++ } else {
++ match = base + matchIndex;
++ lowLimit = (const BYTE*)source; /* required for match length counter */
++ }
++ } else { /* single memory segment */
++ match = base + matchIndex;
++ }
++ LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType);
++ assert(matchIndex < current);
++ if ( ((dictIssue==dictSmall) ? (matchIndex >= prefixIdxLimit) : 1)
++ && (((tableType==byU16) && (LZ4_DISTANCE_MAX == LZ4_DISTANCE_ABSOLUTE_MAX)) ? 1 : (matchIndex+LZ4_DISTANCE_MAX >= current))
++ && (LZ4_read32(match) == LZ4_read32(ip)) ) {
++ token=op++;
++ *token=0;
++ if (maybe_extMem) offset = current - matchIndex;
++ DEBUGLOG(6, "seq.start:%i, literals=%u, match.start:%i",
++ (int)(anchor-(const BYTE*)source), 0, (int)(ip-(const BYTE*)source));
++ goto _next_match;
++ }
++ }
++
++ /* Prepare next loop */
++ forwardH = LZ4_hashPosition(++ip, tableType);
++
++ }
++
++_last_literals:
++ /* Encode Last Literals */
++ { size_t lastRun = (size_t)(iend - anchor);
++ if ( (outputDirective) && /* Check output buffer overflow */
++ (op + lastRun + 1 + ((lastRun+255-RUN_MASK)/255) > olimit)) {
++ if (outputDirective == fillOutput) {
++ /* adapt lastRun to fill 'dst' */
++ assert(olimit >= op);
++ lastRun = (size_t)(olimit-op) - 1/*token*/;
++ lastRun -= (lastRun + 256 - RUN_MASK) / 256; /*additional length tokens*/
++ } else {
++ assert(outputDirective == limitedOutput);
++ return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */
++ }
++ }
++ DEBUGLOG(6, "Final literal run : %i literals", (int)lastRun);
++ if (lastRun >= RUN_MASK) {
++ size_t accumulator = lastRun - RUN_MASK;
++ *op++ = RUN_MASK << ML_BITS;
++ for(; accumulator >= 255 ; accumulator-=255) *op++ = 255;
++ *op++ = (BYTE) accumulator;
++ } else {
++ *op++ = (BYTE)(lastRun< 0);
++ DEBUGLOG(5, "LZ4_compress_generic: compressed %i bytes into %i bytes", inputSize, result);
++ return result;
++}
++
++/** LZ4_compress_generic() :
++ * inlined, to ensure branches are decided at compilation time;
++ * takes care of src == (NULL, 0)
++ * and forward the rest to LZ4_compress_generic_validated */
++LZ4_FORCE_INLINE int LZ4_compress_generic(
++ LZ4_stream_t_internal* const cctx,
++ const char* const src,
++ char* const dst,
++ const int srcSize,
++ int *inputConsumed, /* only written when outputDirective == fillOutput */
++ const int dstCapacity,
++ const limitedOutput_directive outputDirective,
++ const tableType_t tableType,
++ const dict_directive dictDirective,
++ const dictIssue_directive dictIssue,
++ const int acceleration)
++{
++ DEBUGLOG(5, "LZ4_compress_generic: srcSize=%i, dstCapacity=%i",
++ srcSize, dstCapacity);
++
++ if ((U32)srcSize > (U32)LZ4_MAX_INPUT_SIZE) { return 0; } /* Unsupported srcSize, too large (or negative) */
++ if (srcSize == 0) { /* src == NULL supported if srcSize == 0 */
++ if (outputDirective != notLimited && dstCapacity <= 0) return 0; /* no output, can't write anything */
++ DEBUGLOG(5, "Generating an empty block");
++ assert(outputDirective == notLimited || dstCapacity >= 1);
++ assert(dst != NULL);
++ dst[0] = 0;
++ if (outputDirective == fillOutput) {
++ assert (inputConsumed != NULL);
++ *inputConsumed = 0;
++ }
++ return 1;
++ }
++ assert(src != NULL);
++
++ return LZ4_compress_generic_validated(cctx, src, dst, srcSize,
++ inputConsumed, /* only written into if outputDirective == fillOutput */
++ dstCapacity, outputDirective,
++ tableType, dictDirective, dictIssue, acceleration);
++}
++
++
++int LZ4_compress_fast_extState(void* state, const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration)
++{
++ LZ4_stream_t_internal* const ctx = & LZ4_initStream(state, sizeof(LZ4_stream_t)) -> internal_donotuse;
++ assert(ctx != NULL);
++ if (acceleration < 1) acceleration = LZ4_ACCELERATION_DEFAULT;
++ if (acceleration > LZ4_ACCELERATION_MAX) acceleration = LZ4_ACCELERATION_MAX;
++ if (maxOutputSize >= LZ4_compressBound(inputSize)) {
++ if (inputSize < LZ4_64Klimit) {
++ return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, 0, notLimited, byU16, noDict, noDictIssue, acceleration);
++ } else {
++ const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)source > LZ4_DISTANCE_MAX)) ? byPtr : byU32;
++ return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration);
++ }
++ } else {
++ if (inputSize < LZ4_64Klimit) {
++ return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, byU16, noDict, noDictIssue, acceleration);
++ } else {
++ const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)source > LZ4_DISTANCE_MAX)) ? byPtr : byU32;
++ return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, noDict, noDictIssue, acceleration);
++ }
++ }
++}
++
++/**
++ * LZ4_compress_fast_extState_fastReset() :
++ * A variant of LZ4_compress_fast_extState().
++ *
++ * Using this variant avoids an expensive initialization step. It is only safe
++ * to call if the state buffer is known to be correctly initialized already
++ * (see comment in lz4.h on LZ4_resetStream_fast() for a definition of
++ * "correctly initialized").
++ */
++int LZ4_compress_fast_extState_fastReset(void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration)
++{
++ LZ4_stream_t_internal* const ctx = &((LZ4_stream_t*)state)->internal_donotuse;
++ if (acceleration < 1) acceleration = LZ4_ACCELERATION_DEFAULT;
++ if (acceleration > LZ4_ACCELERATION_MAX) acceleration = LZ4_ACCELERATION_MAX;
++ assert(ctx != NULL);
++
++ if (dstCapacity >= LZ4_compressBound(srcSize)) {
++ if (srcSize < LZ4_64Klimit) {
++ const tableType_t tableType = byU16;
++ LZ4_prepareTable(ctx, srcSize, tableType);
++ if (ctx->currentOffset) {
++ return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, dictSmall, acceleration);
++ } else {
++ return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration);
++ }
++ } else {
++ const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32;
++ LZ4_prepareTable(ctx, srcSize, tableType);
++ return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration);
++ }
++ } else {
++ if (srcSize < LZ4_64Klimit) {
++ const tableType_t tableType = byU16;
++ LZ4_prepareTable(ctx, srcSize, tableType);
++ if (ctx->currentOffset) {
++ return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, dictSmall, acceleration);
++ } else {
++ return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, noDictIssue, acceleration);
++ }
++ } else {
++ const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32;
++ LZ4_prepareTable(ctx, srcSize, tableType);
++ return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, noDictIssue, acceleration);
++ }
++ }
++}
++
++
++int LZ4_compress_fast(const char* src, char* dest, int srcSize, int dstCapacity, int acceleration)
++{
++ int result;
++#if (LZ4_HEAPMODE)
++ LZ4_stream_t* const ctxPtr = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */
++ if (ctxPtr == NULL) return 0;
++#else
++ LZ4_stream_t ctx;
++ LZ4_stream_t* const ctxPtr = &ctx;
++#endif
++ result = LZ4_compress_fast_extState(ctxPtr, src, dest, srcSize, dstCapacity, acceleration);
++
++#if (LZ4_HEAPMODE)
++ FREEMEM(ctxPtr);
++#endif
++ return result;
++}
++
++
++int LZ4_compress_default(const char* src, char* dst, int srcSize, int dstCapacity)
++{
++ return LZ4_compress_fast(src, dst, srcSize, dstCapacity, 1);
++}
++
++
++/* Note!: This function leaves the stream in an unclean/broken state!
++ * It is not safe to subsequently use the same state with a _fastReset() or
++ * _continue() call without resetting it. */
++static int LZ4_compress_destSize_extState_internal(LZ4_stream_t* state, const char* src, char* dst, int* srcSizePtr, int targetDstSize, int acceleration)
++{
++ void* const s = LZ4_initStream(state, sizeof (*state));
++ assert(s != NULL); (void)s;
++
++ if (targetDstSize >= LZ4_compressBound(*srcSizePtr)) { /* compression success is guaranteed */
++ return LZ4_compress_fast_extState(state, src, dst, *srcSizePtr, targetDstSize, acceleration);
++ } else {
++ if (*srcSizePtr < LZ4_64Klimit) {
++ return LZ4_compress_generic(&state->internal_donotuse, src, dst, *srcSizePtr, srcSizePtr, targetDstSize, fillOutput, byU16, noDict, noDictIssue, acceleration);
++ } else {
++ tableType_t const addrMode = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32;
++ return LZ4_compress_generic(&state->internal_donotuse, src, dst, *srcSizePtr, srcSizePtr, targetDstSize, fillOutput, addrMode, noDict, noDictIssue, acceleration);
++ } }
++}
++
++int LZ4_compress_destSize_extState(void* state, const char* src, char* dst, int* srcSizePtr, int targetDstSize, int acceleration)
++{
++ int const r = LZ4_compress_destSize_extState_internal((LZ4_stream_t*)state, src, dst, srcSizePtr, targetDstSize, acceleration);
++ /* clean the state on exit */
++ LZ4_initStream(state, sizeof (LZ4_stream_t));
++ return r;
++}
++
++
++int LZ4_compress_destSize(const char* src, char* dst, int* srcSizePtr, int targetDstSize)
++{
++#if (LZ4_HEAPMODE)
++ LZ4_stream_t* const ctx = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */
++ if (ctx == NULL) return 0;
++#else
++ LZ4_stream_t ctxBody;
++ LZ4_stream_t* const ctx = &ctxBody;
++#endif
++
++ int result = LZ4_compress_destSize_extState_internal(ctx, src, dst, srcSizePtr, targetDstSize, 1);
++
++#if (LZ4_HEAPMODE)
++ FREEMEM(ctx);
++#endif
++ return result;
++}
++
++
++
++/*-******************************
++* Streaming functions
++********************************/
++
++#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION)
++LZ4_stream_t* LZ4_createStream(void)
++{
++ LZ4_stream_t* const lz4s = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t));
++ LZ4_STATIC_ASSERT(sizeof(LZ4_stream_t) >= sizeof(LZ4_stream_t_internal));
++ DEBUGLOG(4, "LZ4_createStream %p", (void*)lz4s);
++ if (lz4s == NULL) return NULL;
++ LZ4_initStream(lz4s, sizeof(*lz4s));
++ return lz4s;
++}
++#endif
++
++static size_t LZ4_stream_t_alignment(void)
++{
++#if LZ4_ALIGN_TEST
++ typedef struct { char c; LZ4_stream_t t; } t_a;
++ return sizeof(t_a) - sizeof(LZ4_stream_t);
++#else
++ return 1; /* effectively disabled */
++#endif
++}
++
++LZ4_stream_t* LZ4_initStream (void* buffer, size_t size)
++{
++ DEBUGLOG(5, "LZ4_initStream");
++ if (buffer == NULL) { return NULL; }
++ if (size < sizeof(LZ4_stream_t)) { return NULL; }
++ if (!LZ4_isAligned(buffer, LZ4_stream_t_alignment())) return NULL;
++ MEM_INIT(buffer, 0, sizeof(LZ4_stream_t_internal));
++ return (LZ4_stream_t*)buffer;
++}
++
++/* resetStream is now deprecated,
++ * prefer initStream() which is more general */
++void LZ4_resetStream (LZ4_stream_t* LZ4_stream)
++{
++ DEBUGLOG(5, "LZ4_resetStream (ctx:%p)", (void*)LZ4_stream);
++ MEM_INIT(LZ4_stream, 0, sizeof(LZ4_stream_t_internal));
++}
++
++void LZ4_resetStream_fast(LZ4_stream_t* ctx) {
++ LZ4_prepareTable(&(ctx->internal_donotuse), 0, byU32);
++}
++
++#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION)
++int LZ4_freeStream (LZ4_stream_t* LZ4_stream)
++{
++ if (!LZ4_stream) return 0; /* support free on NULL */
++ DEBUGLOG(5, "LZ4_freeStream %p", (void*)LZ4_stream);
++ FREEMEM(LZ4_stream);
++ return (0);
++}
++#endif
++
++
++typedef enum { _ld_fast, _ld_slow } LoadDict_mode_e;
++#define HASH_UNIT sizeof(reg_t)
++static int LZ4_loadDict_internal(LZ4_stream_t* LZ4_dict,
++ const char* dictionary, int dictSize,
++ LoadDict_mode_e _ld)
++{
++ LZ4_stream_t_internal* const dict = &LZ4_dict->internal_donotuse;
++ const tableType_t tableType = byU32;
++ const BYTE* p = (const BYTE*)dictionary;
++ const BYTE* const dictEnd = p + dictSize;
++ U32 idx32;
++
++ DEBUGLOG(4, "LZ4_loadDict (%i bytes from %p into %p)", dictSize, (void*)dictionary, (void*)LZ4_dict);
++
++ /* It's necessary to reset the context,
++ * and not just continue it with prepareTable()
++ * to avoid any risk of generating overflowing matchIndex
++ * when compressing using this dictionary */
++ LZ4_resetStream(LZ4_dict);
++
++ /* We always increment the offset by 64 KB, since, if the dict is longer,
++ * we truncate it to the last 64k, and if it's shorter, we still want to
++ * advance by a whole window length so we can provide the guarantee that
++ * there are only valid offsets in the window, which allows an optimization
++ * in LZ4_compress_fast_continue() where it uses noDictIssue even when the
++ * dictionary isn't a full 64k. */
++ dict->currentOffset += 64 KB;
++
++ if (dictSize < (int)HASH_UNIT) {
++ return 0;
++ }
++
++ if ((dictEnd - p) > 64 KB) p = dictEnd - 64 KB;
++ dict->dictionary = p;
++ dict->dictSize = (U32)(dictEnd - p);
++ dict->tableType = (U32)tableType;
++ idx32 = dict->currentOffset - dict->dictSize;
++
++ while (p <= dictEnd-HASH_UNIT) {
++ U32 const h = LZ4_hashPosition(p, tableType);
++ /* Note: overwriting => favors positions end of dictionary */
++ LZ4_putIndexOnHash(idx32, h, dict->hashTable, tableType);
++ p+=3; idx32+=3;
++ }
++
++ if (_ld == _ld_slow) {
++ /* Fill hash table with additional references, to improve compression capability */
++ p = dict->dictionary;
++ idx32 = dict->currentOffset - dict->dictSize;
++ while (p <= dictEnd-HASH_UNIT) {
++ U32 const h = LZ4_hashPosition(p, tableType);
++ U32 const limit = dict->currentOffset - 64 KB;
++ if (LZ4_getIndexOnHash(h, dict->hashTable, tableType) <= limit) {
++ /* Note: not overwriting => favors positions beginning of dictionary */
++ LZ4_putIndexOnHash(idx32, h, dict->hashTable, tableType);
++ }
++ p++; idx32++;
++ }
++ }
++
++ return (int)dict->dictSize;
++}
++
++int LZ4_loadDict(LZ4_stream_t* LZ4_dict, const char* dictionary, int dictSize)
++{
++ return LZ4_loadDict_internal(LZ4_dict, dictionary, dictSize, _ld_fast);
++}
++
++int LZ4_loadDictSlow(LZ4_stream_t* LZ4_dict, const char* dictionary, int dictSize)
++{
++ return LZ4_loadDict_internal(LZ4_dict, dictionary, dictSize, _ld_slow);
++}
++
++void LZ4_attach_dictionary(LZ4_stream_t* workingStream, const LZ4_stream_t* dictionaryStream)
++{
++ const LZ4_stream_t_internal* dictCtx = (dictionaryStream == NULL) ? NULL :
++ &(dictionaryStream->internal_donotuse);
++
++ DEBUGLOG(4, "LZ4_attach_dictionary (%p, %p, size %u)",
++ (void*)workingStream, (void*)dictionaryStream,
++ dictCtx != NULL ? dictCtx->dictSize : 0);
++
++ if (dictCtx != NULL) {
++ /* If the current offset is zero, we will never look in the
++ * external dictionary context, since there is no value a table
++ * entry can take that indicate a miss. In that case, we need
++ * to bump the offset to something non-zero.
++ */
++ if (workingStream->internal_donotuse.currentOffset == 0) {
++ workingStream->internal_donotuse.currentOffset = 64 KB;
++ }
++
++ /* Don't actually attach an empty dictionary.
++ */
++ if (dictCtx->dictSize == 0) {
++ dictCtx = NULL;
++ }
++ }
++ workingStream->internal_donotuse.dictCtx = dictCtx;
++}
++
++
++static void LZ4_renormDictT(LZ4_stream_t_internal* LZ4_dict, int nextSize)
++{
++ assert(nextSize >= 0);
++ if (LZ4_dict->currentOffset + (unsigned)nextSize > 0x80000000) { /* potential ptrdiff_t overflow (32-bits mode) */
++ /* rescale hash table */
++ U32 const delta = LZ4_dict->currentOffset - 64 KB;
++ const BYTE* dictEnd = LZ4_dict->dictionary + LZ4_dict->dictSize;
++ int i;
++ DEBUGLOG(4, "LZ4_renormDictT");
++ for (i=0; ihashTable[i] < delta) LZ4_dict->hashTable[i]=0;
++ else LZ4_dict->hashTable[i] -= delta;
++ }
++ LZ4_dict->currentOffset = 64 KB;
++ if (LZ4_dict->dictSize > 64 KB) LZ4_dict->dictSize = 64 KB;
++ LZ4_dict->dictionary = dictEnd - LZ4_dict->dictSize;
++ }
++}
++
++
++int LZ4_compress_fast_continue (LZ4_stream_t* LZ4_stream,
++ const char* source, char* dest,
++ int inputSize, int maxOutputSize,
++ int acceleration)
++{
++ const tableType_t tableType = byU32;
++ LZ4_stream_t_internal* const streamPtr = &LZ4_stream->internal_donotuse;
++ const char* dictEnd = streamPtr->dictSize ? (const char*)streamPtr->dictionary + streamPtr->dictSize : NULL;
++
++ DEBUGLOG(5, "LZ4_compress_fast_continue (inputSize=%i, dictSize=%u)", inputSize, streamPtr->dictSize);
++
++ LZ4_renormDictT(streamPtr, inputSize); /* fix index overflow */
++ if (acceleration < 1) acceleration = LZ4_ACCELERATION_DEFAULT;
++ if (acceleration > LZ4_ACCELERATION_MAX) acceleration = LZ4_ACCELERATION_MAX;
++
++ /* invalidate tiny dictionaries */
++ if ( (streamPtr->dictSize < 4) /* tiny dictionary : not enough for a hash */
++ && (dictEnd != source) /* prefix mode */
++ && (inputSize > 0) /* tolerance : don't lose history, in case next invocation would use prefix mode */
++ && (streamPtr->dictCtx == NULL) /* usingDictCtx */
++ ) {
++ DEBUGLOG(5, "LZ4_compress_fast_continue: dictSize(%u) at addr:%p is too small", streamPtr->dictSize, (void*)streamPtr->dictionary);
++ /* remove dictionary existence from history, to employ faster prefix mode */
++ streamPtr->dictSize = 0;
++ streamPtr->dictionary = (const BYTE*)source;
++ dictEnd = source;
++ }
++
++ /* Check overlapping input/dictionary space */
++ { const char* const sourceEnd = source + inputSize;
++ if ((sourceEnd > (const char*)streamPtr->dictionary) && (sourceEnd < dictEnd)) {
++ streamPtr->dictSize = (U32)(dictEnd - sourceEnd);
++ if (streamPtr->dictSize > 64 KB) streamPtr->dictSize = 64 KB;
++ if (streamPtr->dictSize < 4) streamPtr->dictSize = 0;
++ streamPtr->dictionary = (const BYTE*)dictEnd - streamPtr->dictSize;
++ }
++ }
++
++ /* prefix mode : source data follows dictionary */
++ if (dictEnd == source) {
++ if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset))
++ return LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, withPrefix64k, dictSmall, acceleration);
++ else
++ return LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, withPrefix64k, noDictIssue, acceleration);
++ }
++
++ /* external dictionary mode */
++ { int result;
++ if (streamPtr->dictCtx) {
++ /* We depend here on the fact that dictCtx'es (produced by
++ * LZ4_loadDict) guarantee that their tables contain no references
++ * to offsets between dictCtx->currentOffset - 64 KB and
++ * dictCtx->currentOffset - dictCtx->dictSize. This makes it safe
++ * to use noDictIssue even when the dict isn't a full 64 KB.
++ */
++ if (inputSize > 4 KB) {
++ /* For compressing large blobs, it is faster to pay the setup
++ * cost to copy the dictionary's tables into the active context,
++ * so that the compression loop is only looking into one table.
++ */
++ LZ4_memcpy(streamPtr, streamPtr->dictCtx, sizeof(*streamPtr));
++ result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, noDictIssue, acceleration);
++ } else {
++ result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingDictCtx, noDictIssue, acceleration);
++ }
++ } else { /* small data <= 4 KB */
++ if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) {
++ result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, dictSmall, acceleration);
++ } else {
++ result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, noDictIssue, acceleration);
++ }
++ }
++ streamPtr->dictionary = (const BYTE*)source;
++ streamPtr->dictSize = (U32)inputSize;
++ return result;
++ }
++}
++
++
++/* Hidden debug function, to force-test external dictionary mode */
++int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* dest, int srcSize)
++{
++ LZ4_stream_t_internal* const streamPtr = &LZ4_dict->internal_donotuse;
++ int result;
++
++ LZ4_renormDictT(streamPtr, srcSize);
++
++ if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) {
++ result = LZ4_compress_generic(streamPtr, source, dest, srcSize, NULL, 0, notLimited, byU32, usingExtDict, dictSmall, 1);
++ } else {
++ result = LZ4_compress_generic(streamPtr, source, dest, srcSize, NULL, 0, notLimited, byU32, usingExtDict, noDictIssue, 1);
++ }
++
++ streamPtr->dictionary = (const BYTE*)source;
++ streamPtr->dictSize = (U32)srcSize;
++
++ return result;
++}
++
++
++/*! LZ4_saveDict() :
++ * If previously compressed data block is not guaranteed to remain available at its memory location,
++ * save it into a safer place (char* safeBuffer).
++ * Note : no need to call LZ4_loadDict() afterwards, dictionary is immediately usable,
++ * one can therefore call LZ4_compress_fast_continue() right after.
++ * @return : saved dictionary size in bytes (necessarily <= dictSize), or 0 if error.
++ */
++int LZ4_saveDict (LZ4_stream_t* LZ4_dict, char* safeBuffer, int dictSize)
++{
++ LZ4_stream_t_internal* const dict = &LZ4_dict->internal_donotuse;
++
++ DEBUGLOG(5, "LZ4_saveDict : dictSize=%i, safeBuffer=%p", dictSize, (void*)safeBuffer);
++
++ if ((U32)dictSize > 64 KB) { dictSize = 64 KB; } /* useless to define a dictionary > 64 KB */
++ if ((U32)dictSize > dict->dictSize) { dictSize = (int)dict->dictSize; }
++
++ if (safeBuffer == NULL) assert(dictSize == 0);
++ if (dictSize > 0) {
++ const BYTE* const previousDictEnd = dict->dictionary + dict->dictSize;
++ assert(dict->dictionary);
++ LZ4_memmove(safeBuffer, previousDictEnd - dictSize, (size_t)dictSize);
++ }
++
++ dict->dictionary = (const BYTE*)safeBuffer;
++ dict->dictSize = (U32)dictSize;
++
++ return dictSize;
++}
++
++
++
++/*-*******************************
++ * Decompression functions
++ ********************************/
++
++typedef enum { decode_full_block = 0, partial_decode = 1 } earlyEnd_directive;
++
++#undef MIN
++#define MIN(a,b) ( (a) < (b) ? (a) : (b) )
++
++
++/* variant for decompress_unsafe()
++ * does not know end of input
++ * presumes input is well formed
++ * note : will consume at least one byte */
++static size_t read_long_length_no_check(const BYTE** pp)
++{
++ size_t b, l = 0;
++ do { b = **pp; (*pp)++; l += b; } while (b==255);
++ DEBUGLOG(6, "read_long_length_no_check: +length=%zu using %zu input bytes", l, l/255 + 1)
++ return l;
++}
++
++/* core decoder variant for LZ4_decompress_fast*()
++ * for legacy support only : these entry points are deprecated.
++ * - Presumes input is correctly formed (no defense vs malformed inputs)
++ * - Does not know input size (presume input buffer is "large enough")
++ * - Decompress a full block (only)
++ * @return : nb of bytes read from input.
++ * Note : this variant is not optimized for speed, just for maintenance.
++ * the goal is to remove support of decompress_fast*() variants by v2.0
++**/
++LZ4_FORCE_INLINE int
++LZ4_decompress_unsafe_generic(
++ const BYTE* const istart,
++ BYTE* const ostart,
++ int decompressedSize,
++
++ size_t prefixSize,
++ const BYTE* const dictStart, /* only if dict==usingExtDict */
++ const size_t dictSize /* note: =0 if dictStart==NULL */
++ )
++{
++ const BYTE* ip = istart;
++ BYTE* op = (BYTE*)ostart;
++ BYTE* const oend = ostart + decompressedSize;
++ const BYTE* const prefixStart = ostart - prefixSize;
++
++ DEBUGLOG(5, "LZ4_decompress_unsafe_generic");
++ if (dictStart == NULL) assert(dictSize == 0);
++
++ while (1) {
++ /* start new sequence */
++ unsigned token = *ip++;
++
++ /* literals */
++ { size_t ll = token >> ML_BITS;
++ if (ll==15) {
++ /* long literal length */
++ ll += read_long_length_no_check(&ip);
++ }
++ if ((size_t)(oend-op) < ll) return -1; /* output buffer overflow */
++ LZ4_memmove(op, ip, ll); /* support in-place decompression */
++ op += ll;
++ ip += ll;
++ if ((size_t)(oend-op) < MFLIMIT) {
++ if (op==oend) break; /* end of block */
++ DEBUGLOG(5, "invalid: literals end at distance %zi from end of block", oend-op);
++ /* incorrect end of block :
++ * last match must start at least MFLIMIT==12 bytes before end of output block */
++ return -1;
++ } }
++
++ /* match */
++ { size_t ml = token & 15;
++ size_t const offset = LZ4_readLE16(ip);
++ ip+=2;
++
++ if (ml==15) {
++ /* long literal length */
++ ml += read_long_length_no_check(&ip);
++ }
++ ml += MINMATCH;
++
++ if ((size_t)(oend-op) < ml) return -1; /* output buffer overflow */
++
++ { const BYTE* match = op - offset;
++
++ /* out of range */
++ if (offset > (size_t)(op - prefixStart) + dictSize) {
++ DEBUGLOG(6, "offset out of range");
++ return -1;
++ }
++
++ /* check special case : extDict */
++ if (offset > (size_t)(op - prefixStart)) {
++ /* extDict scenario */
++ const BYTE* const dictEnd = dictStart + dictSize;
++ const BYTE* extMatch = dictEnd - (offset - (size_t)(op-prefixStart));
++ size_t const extml = (size_t)(dictEnd - extMatch);
++ if (extml > ml) {
++ /* match entirely within extDict */
++ LZ4_memmove(op, extMatch, ml);
++ op += ml;
++ ml = 0;
++ } else {
++ /* match split between extDict & prefix */
++ LZ4_memmove(op, extMatch, extml);
++ op += extml;
++ ml -= extml;
++ }
++ match = prefixStart;
++ }
++
++ /* match copy - slow variant, supporting overlap copy */
++ { size_t u;
++ for (u=0; u= ipmax before start of loop. Returns initial_error if so.
++ * @error (output) - error code. Must be set to 0 before call.
++**/
++typedef size_t Rvl_t;
++static const Rvl_t rvl_error = (Rvl_t)(-1);
++LZ4_FORCE_INLINE Rvl_t
++read_variable_length(const BYTE** ip, const BYTE* ilimit,
++ int initial_check)
++{
++ Rvl_t s, length = 0;
++ assert(ip != NULL);
++ assert(*ip != NULL);
++ assert(ilimit != NULL);
++ if (initial_check && unlikely((*ip) >= ilimit)) { /* read limit reached */
++ return rvl_error;
++ }
++ s = **ip;
++ (*ip)++;
++ length += s;
++ if (unlikely((*ip) > ilimit)) { /* read limit reached */
++ return rvl_error;
++ }
++ /* accumulator overflow detection (32-bit mode only) */
++ if ((sizeof(length) < 8) && unlikely(length > ((Rvl_t)(-1)/2)) ) {
++ return rvl_error;
++ }
++ if (likely(s != 255)) return length;
++ do {
++ s = **ip;
++ (*ip)++;
++ length += s;
++ if (unlikely((*ip) > ilimit)) { /* read limit reached */
++ return rvl_error;
++ }
++ /* accumulator overflow detection (32-bit mode only) */
++ if ((sizeof(length) < 8) && unlikely(length > ((Rvl_t)(-1)/2)) ) {
++ return rvl_error;
++ }
++ } while (s == 255);
++
++ return length;
++}
++
++/*! LZ4_decompress_generic() :
++ * This generic decompression function covers all use cases.
++ * It shall be instantiated several times, using different sets of directives.
++ * Note that it is important for performance that this function really get inlined,
++ * in order to remove useless branches during compilation optimization.
++ */
++LZ4_FORCE_INLINE int
++LZ4_decompress_generic(
++ const char* const src,
++ char* const dst,
++ int srcSize,
++ int outputSize, /* If endOnInput==endOnInputSize, this value is `dstCapacity` */
++
++ earlyEnd_directive partialDecoding, /* full, partial */
++ dict_directive dict, /* noDict, withPrefix64k, usingExtDict */
++ const BYTE* const lowPrefix, /* always <= dst, == dst when no prefix */
++ const BYTE* const dictStart, /* only if dict==usingExtDict */
++ const size_t dictSize /* note : = 0 if noDict */
++ )
++{
++ if ((src == NULL) || (outputSize < 0)) { return -1; }
++
++ { const BYTE* ip = (const BYTE*) src;
++ const BYTE* const iend = ip + srcSize;
++
++ BYTE* op = (BYTE*) dst;
++ BYTE* const oend = op + outputSize;
++ BYTE* cpy;
++
++ const BYTE* const dictEnd = (dictStart == NULL) ? NULL : dictStart + dictSize;
++
++ const int checkOffset = (dictSize < (int)(64 KB));
++
++
++ /* Set up the "end" pointers for the shortcut. */
++ const BYTE* const shortiend = iend - 14 /*maxLL*/ - 2 /*offset*/;
++ const BYTE* const shortoend = oend - 14 /*maxLL*/ - 18 /*maxML*/;
++
++ const BYTE* match;
++ size_t offset;
++ unsigned token;
++ size_t length;
++
++
++ DEBUGLOG(5, "LZ4_decompress_generic (srcSize:%i, dstSize:%i)", srcSize, outputSize);
++
++ /* Special cases */
++ assert(lowPrefix <= op);
++ if (unlikely(outputSize==0)) {
++ /* Empty output buffer */
++ if (partialDecoding) return 0;
++ return ((srcSize==1) && (*ip==0)) ? 0 : -1;
++ }
++ if (unlikely(srcSize==0)) { return -1; }
++
++ /* LZ4_FAST_DEC_LOOP:
++ * designed for modern OoO performance cpus,
++ * where copying reliably 32-bytes is preferable to an unpredictable branch.
++ * note : fast loop may show a regression for some client arm chips. */
++#if LZ4_FAST_DEC_LOOP
++ if ((oend - op) < FASTLOOP_SAFE_DISTANCE) {
++ DEBUGLOG(6, "move to safe decode loop");
++ goto safe_decode;
++ }
++
++ /* Fast loop : decode sequences as long as output < oend-FASTLOOP_SAFE_DISTANCE */
++ DEBUGLOG(6, "using fast decode loop");
++ while (1) {
++ /* Main fastloop assertion: We can always wildcopy FASTLOOP_SAFE_DISTANCE */
++ assert(oend - op >= FASTLOOP_SAFE_DISTANCE);
++ assert(ip < iend);
++ token = *ip++;
++ length = token >> ML_BITS; /* literal length */
++ DEBUGLOG(7, "blockPos%6u: litLength token = %u", (unsigned)(op-(BYTE*)dst), (unsigned)length);
++
++ /* decode literal length */
++ if (length == RUN_MASK) {
++ size_t const addl = read_variable_length(&ip, iend-RUN_MASK, 1);
++ if (addl == rvl_error) {
++ DEBUGLOG(6, "error reading long literal length");
++ goto _output_error;
++ }
++ length += addl;
++ if (unlikely((uptrval)(op)+length<(uptrval)(op))) { goto _output_error; } /* overflow detection */
++ if (unlikely((uptrval)(ip)+length<(uptrval)(ip))) { goto _output_error; } /* overflow detection */
++
++ /* copy literals */
++ LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH);
++ if ((op+length>oend-32) || (ip+length>iend-32)) { goto safe_literal_copy; }
++ LZ4_wildCopy32(op, ip, op+length);
++ ip += length; op += length;
++ } else if (ip <= iend-(16 + 1/*max lit + offset + nextToken*/)) {
++ /* We don't need to check oend, since we check it once for each loop below */
++ DEBUGLOG(7, "copy %u bytes in a 16-bytes stripe", (unsigned)length);
++ /* Literals can only be <= 14, but hope compilers optimize better when copy by a register size */
++ LZ4_memcpy(op, ip, 16);
++ ip += length; op += length;
++ } else {
++ goto safe_literal_copy;
++ }
++
++ /* get offset */
++ offset = LZ4_readLE16(ip); ip+=2;
++ DEBUGLOG(6, "blockPos%6u: offset = %u", (unsigned)(op-(BYTE*)dst), (unsigned)offset);
++ match = op - offset;
++ assert(match <= op); /* overflow check */
++
++ /* get matchlength */
++ length = token & ML_MASK;
++ DEBUGLOG(7, " match length token = %u (len==%u)", (unsigned)length, (unsigned)length+MINMATCH);
++
++ if (length == ML_MASK) {
++ size_t const addl = read_variable_length(&ip, iend - LASTLITERALS + 1, 0);
++ if (addl == rvl_error) {
++ DEBUGLOG(5, "error reading long match length");
++ goto _output_error;
++ }
++ length += addl;
++ length += MINMATCH;
++ DEBUGLOG(7, " long match length == %u", (unsigned)length);
++ if (unlikely((uptrval)(op)+length<(uptrval)op)) { goto _output_error; } /* overflow detection */
++ if (op + length >= oend - FASTLOOP_SAFE_DISTANCE) {
++ goto safe_match_copy;
++ }
++ } else {
++ length += MINMATCH;
++ if (op + length >= oend - FASTLOOP_SAFE_DISTANCE) {
++ DEBUGLOG(7, "moving to safe_match_copy (ml==%u)", (unsigned)length);
++ goto safe_match_copy;
++ }
++
++ /* Fastpath check: skip LZ4_wildCopy32 when true */
++ if ((dict == withPrefix64k) || (match >= lowPrefix)) {
++ if (offset >= 8) {
++ assert(match >= lowPrefix);
++ assert(match <= op);
++ assert(op + 18 <= oend);
++
++ LZ4_memcpy(op, match, 8);
++ LZ4_memcpy(op+8, match+8, 8);
++ LZ4_memcpy(op+16, match+16, 2);
++ op += length;
++ continue;
++ } } }
++
++ if ( checkOffset && (unlikely(match + dictSize < lowPrefix)) ) {
++ DEBUGLOG(5, "Error : pos=%zi, offset=%zi => outside buffers", op-lowPrefix, op-match);
++ goto _output_error;
++ }
++ /* match starting within external dictionary */
++ if ((dict==usingExtDict) && (match < lowPrefix)) {
++ assert(dictEnd != NULL);
++ if (unlikely(op+length > oend-LASTLITERALS)) {
++ if (partialDecoding) {
++ DEBUGLOG(7, "partialDecoding: dictionary match, close to dstEnd");
++ length = MIN(length, (size_t)(oend-op));
++ } else {
++ DEBUGLOG(6, "end-of-block condition violated")
++ goto _output_error;
++ } }
++
++ if (length <= (size_t)(lowPrefix-match)) {
++ /* match fits entirely within external dictionary : just copy */
++ LZ4_memmove(op, dictEnd - (lowPrefix-match), length);
++ op += length;
++ } else {
++ /* match stretches into both external dictionary and current block */
++ size_t const copySize = (size_t)(lowPrefix - match);
++ size_t const restSize = length - copySize;
++ LZ4_memcpy(op, dictEnd - copySize, copySize);
++ op += copySize;
++ if (restSize > (size_t)(op - lowPrefix)) { /* overlap copy */
++ BYTE* const endOfMatch = op + restSize;
++ const BYTE* copyFrom = lowPrefix;
++ while (op < endOfMatch) { *op++ = *copyFrom++; }
++ } else {
++ LZ4_memcpy(op, lowPrefix, restSize);
++ op += restSize;
++ } }
++ continue;
++ }
++
++ /* copy match within block */
++ cpy = op + length;
++
++ assert((op <= oend) && (oend-op >= 32));
++ if (unlikely(offset<16)) {
++ LZ4_memcpy_using_offset(op, match, cpy, offset);
++ } else {
++ LZ4_wildCopy32(op, match, cpy);
++ }
++
++ op = cpy; /* wildcopy correction */
++ }
++ safe_decode:
++#endif
++
++ /* Main Loop : decode remaining sequences where output < FASTLOOP_SAFE_DISTANCE */
++ DEBUGLOG(6, "using safe decode loop");
++ while (1) {
++ assert(ip < iend);
++ token = *ip++;
++ length = token >> ML_BITS; /* literal length */
++ DEBUGLOG(7, "blockPos%6u: litLength token = %u", (unsigned)(op-(BYTE*)dst), (unsigned)length);
++
++ /* A two-stage shortcut for the most common case:
++ * 1) If the literal length is 0..14, and there is enough space,
++ * enter the shortcut and copy 16 bytes on behalf of the literals
++ * (in the fast mode, only 8 bytes can be safely copied this way).
++ * 2) Further if the match length is 4..18, copy 18 bytes in a similar
++ * manner; but we ensure that there's enough space in the output for
++ * those 18 bytes earlier, upon entering the shortcut (in other words,
++ * there is a combined check for both stages).
++ */
++ if ( (length != RUN_MASK)
++ /* strictly "less than" on input, to re-enter the loop with at least one byte */
++ && likely((ip < shortiend) & (op <= shortoend)) ) {
++ /* Copy the literals */
++ LZ4_memcpy(op, ip, 16);
++ op += length; ip += length;
++
++ /* The second stage: prepare for match copying, decode full info.
++ * If it doesn't work out, the info won't be wasted. */
++ length = token & ML_MASK; /* match length */
++ DEBUGLOG(7, "blockPos%6u: matchLength token = %u (len=%u)", (unsigned)(op-(BYTE*)dst), (unsigned)length, (unsigned)length + 4);
++ offset = LZ4_readLE16(ip); ip += 2;
++ match = op - offset;
++ assert(match <= op); /* check overflow */
++
++ /* Do not deal with overlapping matches. */
++ if ( (length != ML_MASK)
++ && (offset >= 8)
++ && (dict==withPrefix64k || match >= lowPrefix) ) {
++ /* Copy the match. */
++ LZ4_memcpy(op + 0, match + 0, 8);
++ LZ4_memcpy(op + 8, match + 8, 8);
++ LZ4_memcpy(op +16, match +16, 2);
++ op += length + MINMATCH;
++ /* Both stages worked, load the next token. */
++ continue;
++ }
++
++ /* The second stage didn't work out, but the info is ready.
++ * Propel it right to the point of match copying. */
++ goto _copy_match;
++ }
++
++ /* decode literal length */
++ if (length == RUN_MASK) {
++ size_t const addl = read_variable_length(&ip, iend-RUN_MASK, 1);
++ if (addl == rvl_error) { goto _output_error; }
++ length += addl;
++ if (unlikely((uptrval)(op)+length<(uptrval)(op))) { goto _output_error; } /* overflow detection */
++ if (unlikely((uptrval)(ip)+length<(uptrval)(ip))) { goto _output_error; } /* overflow detection */
++ }
++
++#if LZ4_FAST_DEC_LOOP
++ safe_literal_copy:
++#endif
++ /* copy literals */
++ cpy = op+length;
++
++ LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH);
++ if ((cpy>oend-MFLIMIT) || (ip+length>iend-(2+1+LASTLITERALS))) {
++ /* We've either hit the input parsing restriction or the output parsing restriction.
++ * In the normal scenario, decoding a full block, it must be the last sequence,
++ * otherwise it's an error (invalid input or dimensions).
++ * In partialDecoding scenario, it's necessary to ensure there is no buffer overflow.
++ */
++ if (partialDecoding) {
++ /* Since we are partial decoding we may be in this block because of the output parsing
++ * restriction, which is not valid since the output buffer is allowed to be undersized.
++ */
++ DEBUGLOG(7, "partialDecoding: copying literals, close to input or output end")
++ DEBUGLOG(7, "partialDecoding: literal length = %u", (unsigned)length);
++ DEBUGLOG(7, "partialDecoding: remaining space in dstBuffer : %i", (int)(oend - op));
++ DEBUGLOG(7, "partialDecoding: remaining space in srcBuffer : %i", (int)(iend - ip));
++ /* Finishing in the middle of a literals segment,
++ * due to lack of input.
++ */
++ if (ip+length > iend) {
++ length = (size_t)(iend-ip);
++ cpy = op + length;
++ }
++ /* Finishing in the middle of a literals segment,
++ * due to lack of output space.
++ */
++ if (cpy > oend) {
++ cpy = oend;
++ assert(op<=oend);
++ length = (size_t)(oend-op);
++ }
++ } else {
++ /* We must be on the last sequence (or invalid) because of the parsing limitations
++ * so check that we exactly consume the input and don't overrun the output buffer.
++ */
++ if ((ip+length != iend) || (cpy > oend)) {
++ DEBUGLOG(5, "should have been last run of literals")
++ DEBUGLOG(5, "ip(%p) + length(%i) = %p != iend (%p)", (void*)ip, (int)length, (void*)(ip+length), (void*)iend);
++ DEBUGLOG(5, "or cpy(%p) > (oend-MFLIMIT)(%p)", (void*)cpy, (void*)(oend-MFLIMIT));
++ DEBUGLOG(5, "after writing %u bytes / %i bytes available", (unsigned)(op-(BYTE*)dst), outputSize);
++ goto _output_error;
++ }
++ }
++ LZ4_memmove(op, ip, length); /* supports overlapping memory regions, for in-place decompression scenarios */
++ ip += length;
++ op += length;
++ /* Necessarily EOF when !partialDecoding.
++ * When partialDecoding, it is EOF if we've either
++ * filled the output buffer or
++ * can't proceed with reading an offset for following match.
++ */
++ if (!partialDecoding || (cpy == oend) || (ip >= (iend-2))) {
++ break;
++ }
++ } else {
++ LZ4_wildCopy8(op, ip, cpy); /* can overwrite up to 8 bytes beyond cpy */
++ ip += length; op = cpy;
++ }
++
++ /* get offset */
++ offset = LZ4_readLE16(ip); ip+=2;
++ match = op - offset;
++
++ /* get matchlength */
++ length = token & ML_MASK;
++ DEBUGLOG(7, "blockPos%6u: matchLength token = %u", (unsigned)(op-(BYTE*)dst), (unsigned)length);
++
++ _copy_match:
++ if (length == ML_MASK) {
++ size_t const addl = read_variable_length(&ip, iend - LASTLITERALS + 1, 0);
++ if (addl == rvl_error) { goto _output_error; }
++ length += addl;
++ if (unlikely((uptrval)(op)+length<(uptrval)op)) goto _output_error; /* overflow detection */
++ }
++ length += MINMATCH;
++
++#if LZ4_FAST_DEC_LOOP
++ safe_match_copy:
++#endif
++ if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) goto _output_error; /* Error : offset outside buffers */
++ /* match starting within external dictionary */
++ if ((dict==usingExtDict) && (match < lowPrefix)) {
++ assert(dictEnd != NULL);
++ if (unlikely(op+length > oend-LASTLITERALS)) {
++ if (partialDecoding) length = MIN(length, (size_t)(oend-op));
++ else goto _output_error; /* doesn't respect parsing restriction */
++ }
++
++ if (length <= (size_t)(lowPrefix-match)) {
++ /* match fits entirely within external dictionary : just copy */
++ LZ4_memmove(op, dictEnd - (lowPrefix-match), length);
++ op += length;
++ } else {
++ /* match stretches into both external dictionary and current block */
++ size_t const copySize = (size_t)(lowPrefix - match);
++ size_t const restSize = length - copySize;
++ LZ4_memcpy(op, dictEnd - copySize, copySize);
++ op += copySize;
++ if (restSize > (size_t)(op - lowPrefix)) { /* overlap copy */
++ BYTE* const endOfMatch = op + restSize;
++ const BYTE* copyFrom = lowPrefix;
++ while (op < endOfMatch) *op++ = *copyFrom++;
++ } else {
++ LZ4_memcpy(op, lowPrefix, restSize);
++ op += restSize;
++ } }
++ continue;
++ }
++ assert(match >= lowPrefix);
++
++ /* copy match within block */
++ cpy = op + length;
++
++ /* partialDecoding : may end anywhere within the block */
++ assert(op<=oend);
++ if (partialDecoding && (cpy > oend-MATCH_SAFEGUARD_DISTANCE)) {
++ size_t const mlen = MIN(length, (size_t)(oend-op));
++ const BYTE* const matchEnd = match + mlen;
++ BYTE* const copyEnd = op + mlen;
++ if (matchEnd > op) { /* overlap copy */
++ while (op < copyEnd) { *op++ = *match++; }
++ } else {
++ LZ4_memcpy(op, match, mlen);
++ }
++ op = copyEnd;
++ if (op == oend) { break; }
++ continue;
++ }
++
++ if (unlikely(offset<8)) {
++ LZ4_write32(op, 0); /* silence msan warning when offset==0 */
++ op[0] = match[0];
++ op[1] = match[1];
++ op[2] = match[2];
++ op[3] = match[3];
++ match += inc32table[offset];
++ LZ4_memcpy(op+4, match, 4);
++ match -= dec64table[offset];
++ } else {
++ LZ4_memcpy(op, match, 8);
++ match += 8;
++ }
++ op += 8;
++
++ if (unlikely(cpy > oend-MATCH_SAFEGUARD_DISTANCE)) {
++ BYTE* const oCopyLimit = oend - (WILDCOPYLENGTH-1);
++ if (cpy > oend-LASTLITERALS) { goto _output_error; } /* Error : last LASTLITERALS bytes must be literals (uncompressed) */
++ if (op < oCopyLimit) {
++ LZ4_wildCopy8(op, match, oCopyLimit);
++ match += oCopyLimit - op;
++ op = oCopyLimit;
++ }
++ while (op < cpy) { *op++ = *match++; }
++ } else {
++ LZ4_memcpy(op, match, 8);
++ if (length > 16) { LZ4_wildCopy8(op+8, match+8, cpy); }
++ }
++ op = cpy; /* wildcopy correction */
++ }
++
++ /* end of decoding */
++ DEBUGLOG(5, "decoded %i bytes", (int) (((char*)op)-dst));
++ return (int) (((char*)op)-dst); /* Nb of output bytes decoded */
++
++ /* Overflow error detected */
++ _output_error:
++ return (int) (-(((const char*)ip)-src))-1;
++ }
++}
++
++
++/*===== Instantiate the API decoding functions. =====*/
++
++LZ4_FORCE_O2
++int LZ4_decompress_safe(const char* source, char* dest, int compressedSize, int maxDecompressedSize)
++{
++ return LZ4_decompress_generic(source, dest, compressedSize, maxDecompressedSize,
++ decode_full_block, noDict,
++ (BYTE*)dest, NULL, 0);
++}
++
++LZ4_FORCE_O2
++int LZ4_decompress_safe_partial(const char* src, char* dst, int compressedSize, int targetOutputSize, int dstCapacity)
++{
++ dstCapacity = MIN(targetOutputSize, dstCapacity);
++ return LZ4_decompress_generic(src, dst, compressedSize, dstCapacity,
++ partial_decode,
++ noDict, (BYTE*)dst, NULL, 0);
++}
++
++LZ4_FORCE_O2
++int LZ4_decompress_fast(const char* source, char* dest, int originalSize)
++{
++ DEBUGLOG(5, "LZ4_decompress_fast");
++ return LZ4_decompress_unsafe_generic(
++ (const BYTE*)source, (BYTE*)dest, originalSize,
++ 0, NULL, 0);
++}
++
++/*===== Instantiate a few more decoding cases, used more than once. =====*/
++
++LZ4_FORCE_O2 /* Exported, an obsolete API function. */
++int LZ4_decompress_safe_withPrefix64k(const char* source, char* dest, int compressedSize, int maxOutputSize)
++{
++ return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize,
++ decode_full_block, withPrefix64k,
++ (BYTE*)dest - 64 KB, NULL, 0);
++}
++
++LZ4_FORCE_O2
++static int LZ4_decompress_safe_partial_withPrefix64k(const char* source, char* dest, int compressedSize, int targetOutputSize, int dstCapacity)
++{
++ dstCapacity = MIN(targetOutputSize, dstCapacity);
++ return LZ4_decompress_generic(source, dest, compressedSize, dstCapacity,
++ partial_decode, withPrefix64k,
++ (BYTE*)dest - 64 KB, NULL, 0);
++}
++
++/* Another obsolete API function, paired with the previous one. */
++int LZ4_decompress_fast_withPrefix64k(const char* source, char* dest, int originalSize)
++{
++ return LZ4_decompress_unsafe_generic(
++ (const BYTE*)source, (BYTE*)dest, originalSize,
++ 64 KB, NULL, 0);
++}
++
++LZ4_FORCE_O2
++static int LZ4_decompress_safe_withSmallPrefix(const char* source, char* dest, int compressedSize, int maxOutputSize,
++ size_t prefixSize)
++{
++ return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize,
++ decode_full_block, noDict,
++ (BYTE*)dest-prefixSize, NULL, 0);
++}
++
++LZ4_FORCE_O2
++static int LZ4_decompress_safe_partial_withSmallPrefix(const char* source, char* dest, int compressedSize, int targetOutputSize, int dstCapacity,
++ size_t prefixSize)
++{
++ dstCapacity = MIN(targetOutputSize, dstCapacity);
++ return LZ4_decompress_generic(source, dest, compressedSize, dstCapacity,
++ partial_decode, noDict,
++ (BYTE*)dest-prefixSize, NULL, 0);
++}
++
++LZ4_FORCE_O2
++int LZ4_decompress_safe_forceExtDict(const char* source, char* dest,
++ int compressedSize, int maxOutputSize,
++ const void* dictStart, size_t dictSize)
++{
++ DEBUGLOG(5, "LZ4_decompress_safe_forceExtDict");
++ return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize,
++ decode_full_block, usingExtDict,
++ (BYTE*)dest, (const BYTE*)dictStart, dictSize);
++}
++
++LZ4_FORCE_O2
++int LZ4_decompress_safe_partial_forceExtDict(const char* source, char* dest,
++ int compressedSize, int targetOutputSize, int dstCapacity,
++ const void* dictStart, size_t dictSize)
++{
++ dstCapacity = MIN(targetOutputSize, dstCapacity);
++ return LZ4_decompress_generic(source, dest, compressedSize, dstCapacity,
++ partial_decode, usingExtDict,
++ (BYTE*)dest, (const BYTE*)dictStart, dictSize);
++}
++
++LZ4_FORCE_O2
++static int LZ4_decompress_fast_extDict(const char* source, char* dest, int originalSize,
++ const void* dictStart, size_t dictSize)
++{
++ return LZ4_decompress_unsafe_generic(
++ (const BYTE*)source, (BYTE*)dest, originalSize,
++ 0, (const BYTE*)dictStart, dictSize);
++}
++
++/* The "double dictionary" mode, for use with e.g. ring buffers: the first part
++ * of the dictionary is passed as prefix, and the second via dictStart + dictSize.
++ * These routines are used only once, in LZ4_decompress_*_continue().
++ */
++LZ4_FORCE_INLINE
++int LZ4_decompress_safe_doubleDict(const char* source, char* dest, int compressedSize, int maxOutputSize,
++ size_t prefixSize, const void* dictStart, size_t dictSize)
++{
++ return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize,
++ decode_full_block, usingExtDict,
++ (BYTE*)dest-prefixSize, (const BYTE*)dictStart, dictSize);
++}
++
++/*===== streaming decompression functions =====*/
++
++#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION)
++LZ4_streamDecode_t* LZ4_createStreamDecode(void)
++{
++ LZ4_STATIC_ASSERT(sizeof(LZ4_streamDecode_t) >= sizeof(LZ4_streamDecode_t_internal));
++ return (LZ4_streamDecode_t*) ALLOC_AND_ZERO(sizeof(LZ4_streamDecode_t));
++}
++
++int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream)
++{
++ if (LZ4_stream == NULL) { return 0; } /* support free on NULL */
++ FREEMEM(LZ4_stream);
++ return 0;
++}
++#endif
++
++/*! LZ4_setStreamDecode() :
++ * Use this function to instruct where to find the dictionary.
++ * This function is not necessary if previous data is still available where it was decoded.
++ * Loading a size of 0 is allowed (same effect as no dictionary).
++ * @return : 1 if OK, 0 if error
++ */
++int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize)
++{
++ LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse;
++ lz4sd->prefixSize = (size_t)dictSize;
++ if (dictSize) {
++ assert(dictionary != NULL);
++ lz4sd->prefixEnd = (const BYTE*) dictionary + dictSize;
++ } else {
++ lz4sd->prefixEnd = (const BYTE*) dictionary;
++ }
++ lz4sd->externalDict = NULL;
++ lz4sd->extDictSize = 0;
++ return 1;
++}
++
++/*! LZ4_decoderRingBufferSize() :
++ * when setting a ring buffer for streaming decompression (optional scenario),
++ * provides the minimum size of this ring buffer
++ * to be compatible with any source respecting maxBlockSize condition.
++ * Note : in a ring buffer scenario,
++ * blocks are presumed decompressed next to each other.
++ * When not enough space remains for next block (remainingSize < maxBlockSize),
++ * decoding resumes from beginning of ring buffer.
++ * @return : minimum ring buffer size,
++ * or 0 if there is an error (invalid maxBlockSize).
++ */
++int LZ4_decoderRingBufferSize(int maxBlockSize)
++{
++ if (maxBlockSize < 0) return 0;
++ if (maxBlockSize > LZ4_MAX_INPUT_SIZE) return 0;
++ if (maxBlockSize < 16) maxBlockSize = 16;
++ return LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize);
++}
++
++/*
++*_continue() :
++ These decoding functions allow decompression of multiple blocks in "streaming" mode.
++ Previously decoded blocks must still be available at the memory position where they were decoded.
++ If it's not possible, save the relevant part of decoded data into a safe buffer,
++ and indicate where it stands using LZ4_setStreamDecode()
++*/
++LZ4_FORCE_O2
++int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int compressedSize, int maxOutputSize)
++{
++ LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse;
++ int result;
++
++ if (lz4sd->prefixSize == 0) {
++ /* The first call, no dictionary yet. */
++ assert(lz4sd->extDictSize == 0);
++ result = LZ4_decompress_safe(source, dest, compressedSize, maxOutputSize);
++ if (result <= 0) return result;
++ lz4sd->prefixSize = (size_t)result;
++ lz4sd->prefixEnd = (BYTE*)dest + result;
++ } else if (lz4sd->prefixEnd == (BYTE*)dest) {
++ /* They're rolling the current segment. */
++ if (lz4sd->prefixSize >= 64 KB - 1)
++ result = LZ4_decompress_safe_withPrefix64k(source, dest, compressedSize, maxOutputSize);
++ else if (lz4sd->extDictSize == 0)
++ result = LZ4_decompress_safe_withSmallPrefix(source, dest, compressedSize, maxOutputSize,
++ lz4sd->prefixSize);
++ else
++ result = LZ4_decompress_safe_doubleDict(source, dest, compressedSize, maxOutputSize,
++ lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize);
++ if (result <= 0) return result;
++ lz4sd->prefixSize += (size_t)result;
++ lz4sd->prefixEnd += result;
++ } else {
++ /* The buffer wraps around, or they're switching to another buffer. */
++ lz4sd->extDictSize = lz4sd->prefixSize;
++ lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize;
++ result = LZ4_decompress_safe_forceExtDict(source, dest, compressedSize, maxOutputSize,
++ lz4sd->externalDict, lz4sd->extDictSize);
++ if (result <= 0) return result;
++ lz4sd->prefixSize = (size_t)result;
++ lz4sd->prefixEnd = (BYTE*)dest + result;
++ }
++
++ return result;
++}
++
++LZ4_FORCE_O2 int
++LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode,
++ const char* source, char* dest, int originalSize)
++{
++ LZ4_streamDecode_t_internal* const lz4sd =
++ (assert(LZ4_streamDecode!=NULL), &LZ4_streamDecode->internal_donotuse);
++ int result;
++
++ DEBUGLOG(5, "LZ4_decompress_fast_continue (toDecodeSize=%i)", originalSize);
++ assert(originalSize >= 0);
++
++ if (lz4sd->prefixSize == 0) {
++ DEBUGLOG(5, "first invocation : no prefix nor extDict");
++ assert(lz4sd->extDictSize == 0);
++ result = LZ4_decompress_fast(source, dest, originalSize);
++ if (result <= 0) return result;
++ lz4sd->prefixSize = (size_t)originalSize;
++ lz4sd->prefixEnd = (BYTE*)dest + originalSize;
++ } else if (lz4sd->prefixEnd == (BYTE*)dest) {
++ DEBUGLOG(5, "continue using existing prefix");
++ result = LZ4_decompress_unsafe_generic(
++ (const BYTE*)source, (BYTE*)dest, originalSize,
++ lz4sd->prefixSize,
++ lz4sd->externalDict, lz4sd->extDictSize);
++ if (result <= 0) return result;
++ lz4sd->prefixSize += (size_t)originalSize;
++ lz4sd->prefixEnd += originalSize;
++ } else {
++ DEBUGLOG(5, "prefix becomes extDict");
++ lz4sd->extDictSize = lz4sd->prefixSize;
++ lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize;
++ result = LZ4_decompress_fast_extDict(source, dest, originalSize,
++ lz4sd->externalDict, lz4sd->extDictSize);
++ if (result <= 0) return result;
++ lz4sd->prefixSize = (size_t)originalSize;
++ lz4sd->prefixEnd = (BYTE*)dest + originalSize;
++ }
++
++ return result;
++}
++
++
++/*
++Advanced decoding functions :
++*_usingDict() :
++ These decoding functions work the same as "_continue" ones,
++ the dictionary must be explicitly provided within parameters
++*/
++
++int LZ4_decompress_safe_usingDict(const char* source, char* dest, int compressedSize, int maxOutputSize, const char* dictStart, int dictSize)
++{
++ if (dictSize==0)
++ return LZ4_decompress_safe(source, dest, compressedSize, maxOutputSize);
++ if (dictStart+dictSize == dest) {
++ if (dictSize >= 64 KB - 1) {
++ return LZ4_decompress_safe_withPrefix64k(source, dest, compressedSize, maxOutputSize);
++ }
++ assert(dictSize >= 0);
++ return LZ4_decompress_safe_withSmallPrefix(source, dest, compressedSize, maxOutputSize, (size_t)dictSize);
++ }
++ assert(dictSize >= 0);
++ return LZ4_decompress_safe_forceExtDict(source, dest, compressedSize, maxOutputSize, dictStart, (size_t)dictSize);
++}
++
++int LZ4_decompress_safe_partial_usingDict(const char* source, char* dest, int compressedSize, int targetOutputSize, int dstCapacity, const char* dictStart, int dictSize)
++{
++ if (dictSize==0)
++ return LZ4_decompress_safe_partial(source, dest, compressedSize, targetOutputSize, dstCapacity);
++ if (dictStart+dictSize == dest) {
++ if (dictSize >= 64 KB - 1) {
++ return LZ4_decompress_safe_partial_withPrefix64k(source, dest, compressedSize, targetOutputSize, dstCapacity);
++ }
++ assert(dictSize >= 0);
++ return LZ4_decompress_safe_partial_withSmallPrefix(source, dest, compressedSize, targetOutputSize, dstCapacity, (size_t)dictSize);
++ }
++ assert(dictSize >= 0);
++ return LZ4_decompress_safe_partial_forceExtDict(source, dest, compressedSize, targetOutputSize, dstCapacity, dictStart, (size_t)dictSize);
++}
++
++int LZ4_decompress_fast_usingDict(const char* source, char* dest, int originalSize, const char* dictStart, int dictSize)
++{
++ if (dictSize==0 || dictStart+dictSize == dest)
++ return LZ4_decompress_unsafe_generic(
++ (const BYTE*)source, (BYTE*)dest, originalSize,
++ (size_t)dictSize, NULL, 0);
++ assert(dictSize >= 0);
++ return LZ4_decompress_fast_extDict(source, dest, originalSize, dictStart, (size_t)dictSize);
++}
++
++
++/*=*************************************************
++* Obsolete Functions
++***************************************************/
++/* obsolete compression functions */
++int LZ4_compress_limitedOutput(const char* source, char* dest, int inputSize, int maxOutputSize)
++{
++ return LZ4_compress_default(source, dest, inputSize, maxOutputSize);
++}
++int LZ4_compress(const char* src, char* dest, int srcSize)
++{
++ return LZ4_compress_default(src, dest, srcSize, LZ4_compressBound(srcSize));
++}
++int LZ4_compress_limitedOutput_withState (void* state, const char* src, char* dst, int srcSize, int dstSize)
++{
++ return LZ4_compress_fast_extState(state, src, dst, srcSize, dstSize, 1);
++}
++int LZ4_compress_withState (void* state, const char* src, char* dst, int srcSize)
++{
++ return LZ4_compress_fast_extState(state, src, dst, srcSize, LZ4_compressBound(srcSize), 1);
++}
++int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_stream, const char* src, char* dst, int srcSize, int dstCapacity)
++{
++ return LZ4_compress_fast_continue(LZ4_stream, src, dst, srcSize, dstCapacity, 1);
++}
++int LZ4_compress_continue (LZ4_stream_t* LZ4_stream, const char* source, char* dest, int inputSize)
++{
++ return LZ4_compress_fast_continue(LZ4_stream, source, dest, inputSize, LZ4_compressBound(inputSize), 1);
++}
++
++/*
++These decompression functions are deprecated and should no longer be used.
++They are only provided here for compatibility with older user programs.
++- LZ4_uncompress is totally equivalent to LZ4_decompress_fast
++- LZ4_uncompress_unknownOutputSize is totally equivalent to LZ4_decompress_safe
++*/
++int LZ4_uncompress (const char* source, char* dest, int outputSize)
++{
++ return LZ4_decompress_fast(source, dest, outputSize);
++}
++int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize)
++{
++ return LZ4_decompress_safe(source, dest, isize, maxOutputSize);
++}
++
++/* Obsolete Streaming functions */
++
++int LZ4_sizeofStreamState(void) { return sizeof(LZ4_stream_t); }
++
++int LZ4_resetStreamState(void* state, char* inputBuffer)
++{
++ (void)inputBuffer;
++ LZ4_resetStream((LZ4_stream_t*)state);
++ return 0;
++}
++
++#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION)
++void* LZ4_create (char* inputBuffer)
++{
++ (void)inputBuffer;
++ return LZ4_createStream();
++}
++#endif
++
++char* LZ4_slideInputBuffer (void* state)
++{
++ /* avoid const char * -> char * conversion warning */
++ return (char *)(uptrval)((LZ4_stream_t*)state)->internal_donotuse.dictionary;
++}
++
++#endif /* LZ4_COMMONDEFS_ONLY */
+diff --git a/code/server/lz4.h b/code/server/lz4.h
+new file mode 100644
+index 0000000..5b147b2
+--- /dev/null
++++ b/code/server/lz4.h
+@@ -0,0 +1,886 @@
++/*
++ * LZ4 - Fast LZ compression algorithm
++ * Header File
++ * Copyright (c) Yann Collet. All rights reserved.
++
++ BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
++
++ Redistribution and use in source and binary forms, with or without
++ modification, are permitted provided that the following conditions are
++ met:
++
++ * Redistributions of source code must retain the above copyright
++ notice, this list of conditions and the following disclaimer.
++ * Redistributions in binary form must reproduce the above
++ copyright notice, this list of conditions and the following disclaimer
++ in the documentation and/or other materials provided with the
++ distribution.
++
++ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
++ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
++ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
++ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
++ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
++ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
++ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
++ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
++ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
++ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
++ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
++
++ You can contact the author at :
++ - LZ4 homepage : http://www.lz4.org
++ - LZ4 source repository : https://github.com/lz4/lz4
++*/
++#if defined (__cplusplus)
++extern "C" {
++#endif
++
++#ifndef LZ4_H_2983827168210
++#define LZ4_H_2983827168210
++
++/* --- Dependency --- */
++#include /* size_t */
++
++
++/**
++ Introduction
++
++ LZ4 is lossless compression algorithm, providing compression speed >500 MB/s per core,
++ scalable with multi-cores CPU. It features an extremely fast decoder, with speed in
++ multiple GB/s per core, typically reaching RAM speed limits on multi-core systems.
++
++ The LZ4 compression library provides in-memory compression and decompression functions.
++ It gives full buffer control to user.
++ Compression can be done in:
++ - a single step (described as Simple Functions)
++ - a single step, reusing a context (described in Advanced Functions)
++ - unbounded multiple steps (described as Streaming compression)
++
++ lz4.h generates and decodes LZ4-compressed blocks (doc/lz4_Block_format.md).
++ Decompressing such a compressed block requires additional metadata.
++ Exact metadata depends on exact decompression function.
++ For the typical case of LZ4_decompress_safe(),
++ metadata includes block's compressed size, and maximum bound of decompressed size.
++ Each application is free to encode and pass such metadata in whichever way it wants.
++
++ lz4.h only handle blocks, it can not generate Frames.
++
++ Blocks are different from Frames (doc/lz4_Frame_format.md).
++ Frames bundle both blocks and metadata in a specified manner.
++ Embedding metadata is required for compressed data to be self-contained and portable.
++ Frame format is delivered through a companion API, declared in lz4frame.h.
++ The `lz4` CLI can only manage frames.
++*/
++
++/*^***************************************************************
++* Export parameters
++*****************************************************************/
++/*
++* LZ4_DLL_EXPORT :
++* Enable exporting of functions when building a Windows DLL
++* LZ4LIB_VISIBILITY :
++* Control library symbols visibility.
++*/
++#ifndef LZ4LIB_VISIBILITY
++# if defined(__GNUC__) && (__GNUC__ >= 4)
++# define LZ4LIB_VISIBILITY __attribute__ ((visibility ("default")))
++# else
++# define LZ4LIB_VISIBILITY
++# endif
++#endif
++#if defined(LZ4_DLL_EXPORT) && (LZ4_DLL_EXPORT==1)
++# define LZ4LIB_API __declspec(dllexport) LZ4LIB_VISIBILITY
++#elif defined(LZ4_DLL_IMPORT) && (LZ4_DLL_IMPORT==1)
++# define LZ4LIB_API __declspec(dllimport) LZ4LIB_VISIBILITY /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/
++#else
++# define LZ4LIB_API LZ4LIB_VISIBILITY
++#endif
++
++/*! LZ4_FREESTANDING :
++ * When this macro is set to 1, it enables "freestanding mode" that is
++ * suitable for typical freestanding environment which doesn't support
++ * standard C library.
++ *
++ * - LZ4_FREESTANDING is a compile-time switch.
++ * - It requires the following macros to be defined:
++ * LZ4_memcpy, LZ4_memmove, LZ4_memset.
++ * - It only enables LZ4/HC functions which don't use heap.
++ * All LZ4F_* functions are not supported.
++ * - See tests/freestanding.c to check its basic setup.
++ */
++#if defined(LZ4_FREESTANDING) && (LZ4_FREESTANDING == 1)
++# define LZ4_HEAPMODE 0
++# define LZ4HC_HEAPMODE 0
++# define LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION 1
++# if !defined(LZ4_memcpy)
++# error "LZ4_FREESTANDING requires macro 'LZ4_memcpy'."
++# endif
++# if !defined(LZ4_memset)
++# error "LZ4_FREESTANDING requires macro 'LZ4_memset'."
++# endif
++# if !defined(LZ4_memmove)
++# error "LZ4_FREESTANDING requires macro 'LZ4_memmove'."
++# endif
++#elif ! defined(LZ4_FREESTANDING)
++# define LZ4_FREESTANDING 0
++#endif
++
++
++/*------ Version ------*/
++#define LZ4_VERSION_MAJOR 1 /* for breaking interface changes */
++#define LZ4_VERSION_MINOR 10 /* for new (non-breaking) interface capabilities */
++#define LZ4_VERSION_RELEASE 0 /* for tweaks, bug-fixes, or development */
++
++#define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE)
++
++#define LZ4_LIB_VERSION LZ4_VERSION_MAJOR.LZ4_VERSION_MINOR.LZ4_VERSION_RELEASE
++#define LZ4_QUOTE(str) #str
++#define LZ4_EXPAND_AND_QUOTE(str) LZ4_QUOTE(str)
++#define LZ4_VERSION_STRING LZ4_EXPAND_AND_QUOTE(LZ4_LIB_VERSION) /* requires v1.7.3+ */
++
++LZ4LIB_API int LZ4_versionNumber (void); /**< library version number; useful to check dll version; requires v1.3.0+ */
++LZ4LIB_API const char* LZ4_versionString (void); /**< library version string; useful to check dll version; requires v1.7.5+ */
++
++
++/*-************************************
++* Tuning memory usage
++**************************************/
++/*!
++ * LZ4_MEMORY_USAGE :
++ * Can be selected at compile time, by setting LZ4_MEMORY_USAGE.
++ * Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB)
++ * Increasing memory usage improves compression ratio, generally at the cost of speed.
++ * Reduced memory usage may improve speed at the cost of ratio, thanks to better cache locality.
++ * Default value is 14, for 16KB, which nicely fits into most L1 caches.
++ */
++#ifndef LZ4_MEMORY_USAGE
++# define LZ4_MEMORY_USAGE LZ4_MEMORY_USAGE_DEFAULT
++#endif
++
++/* These are absolute limits, they should not be changed by users */
++#define LZ4_MEMORY_USAGE_MIN 10
++#define LZ4_MEMORY_USAGE_DEFAULT 14
++#define LZ4_MEMORY_USAGE_MAX 20
++
++#if (LZ4_MEMORY_USAGE < LZ4_MEMORY_USAGE_MIN)
++# error "LZ4_MEMORY_USAGE is too small !"
++#endif
++
++#if (LZ4_MEMORY_USAGE > LZ4_MEMORY_USAGE_MAX)
++# error "LZ4_MEMORY_USAGE is too large !"
++#endif
++
++/*-************************************
++* Simple Functions
++**************************************/
++/*! LZ4_compress_default() :
++ * Compresses 'srcSize' bytes from buffer 'src'
++ * into already allocated 'dst' buffer of size 'dstCapacity'.
++ * Compression is guaranteed to succeed if 'dstCapacity' >= LZ4_compressBound(srcSize).
++ * It also runs faster, so it's a recommended setting.
++ * If the function cannot compress 'src' into a more limited 'dst' budget,
++ * compression stops *immediately*, and the function result is zero.
++ * In which case, 'dst' content is undefined (invalid).
++ * srcSize : max supported value is LZ4_MAX_INPUT_SIZE.
++ * dstCapacity : size of buffer 'dst' (which must be already allocated)
++ * @return : the number of bytes written into buffer 'dst' (necessarily <= dstCapacity)
++ * or 0 if compression fails
++ * Note : This function is protected against buffer overflow scenarios (never writes outside 'dst' buffer, nor read outside 'source' buffer).
++ */
++LZ4LIB_API int LZ4_compress_default(const char* src, char* dst, int srcSize, int dstCapacity);
++
++/*! LZ4_decompress_safe() :
++ * @compressedSize : is the exact complete size of the compressed block.
++ * @dstCapacity : is the size of destination buffer (which must be already allocated),
++ * presumed an upper bound of decompressed size.
++ * @return : the number of bytes decompressed into destination buffer (necessarily <= dstCapacity)
++ * If destination buffer is not large enough, decoding will stop and output an error code (negative value).
++ * If the source stream is detected malformed, the function will stop decoding and return a negative result.
++ * Note 1 : This function is protected against malicious data packets :
++ * it will never writes outside 'dst' buffer, nor read outside 'source' buffer,
++ * even if the compressed block is maliciously modified to order the decoder to do these actions.
++ * In such case, the decoder stops immediately, and considers the compressed block malformed.
++ * Note 2 : compressedSize and dstCapacity must be provided to the function, the compressed block does not contain them.
++ * The implementation is free to send / store / derive this information in whichever way is most beneficial.
++ * If there is a need for a different format which bundles together both compressed data and its metadata, consider looking at lz4frame.h instead.
++ */
++LZ4LIB_API int LZ4_decompress_safe (const char* src, char* dst, int compressedSize, int dstCapacity);
++
++
++/*-************************************
++* Advanced Functions
++**************************************/
++#define LZ4_MAX_INPUT_SIZE 0x7E000000 /* 2 113 929 216 bytes */
++#define LZ4_COMPRESSBOUND(isize) ((unsigned)(isize) > (unsigned)LZ4_MAX_INPUT_SIZE ? 0 : (isize) + ((isize)/255) + 16)
++
++/*! LZ4_compressBound() :
++ Provides the maximum size that LZ4 compression may output in a "worst case" scenario (input data not compressible)
++ This function is primarily useful for memory allocation purposes (destination buffer size).
++ Macro LZ4_COMPRESSBOUND() is also provided for compilation-time evaluation (stack memory allocation for example).
++ Note that LZ4_compress_default() compresses faster when dstCapacity is >= LZ4_compressBound(srcSize)
++ inputSize : max supported value is LZ4_MAX_INPUT_SIZE
++ return : maximum output size in a "worst case" scenario
++ or 0, if input size is incorrect (too large or negative)
++*/
++LZ4LIB_API int LZ4_compressBound(int inputSize);
++
++/*! LZ4_compress_fast() :
++ Same as LZ4_compress_default(), but allows selection of "acceleration" factor.
++ The larger the acceleration value, the faster the algorithm, but also the lesser the compression.
++ It's a trade-off. It can be fine tuned, with each successive value providing roughly +~3% to speed.
++ An acceleration value of "1" is the same as regular LZ4_compress_default()
++ Values <= 0 will be replaced by LZ4_ACCELERATION_DEFAULT (currently == 1, see lz4.c).
++ Values > LZ4_ACCELERATION_MAX will be replaced by LZ4_ACCELERATION_MAX (currently == 65537, see lz4.c).
++*/
++LZ4LIB_API int LZ4_compress_fast (const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
++
++
++/*! LZ4_compress_fast_extState() :
++ * Same as LZ4_compress_fast(), using an externally allocated memory space for its state.
++ * Use LZ4_sizeofState() to know how much memory must be allocated,
++ * and allocate it on 8-bytes boundaries (using `malloc()` typically).
++ * Then, provide this buffer as `void* state` to compression function.
++ */
++LZ4LIB_API int LZ4_sizeofState(void);
++LZ4LIB_API int LZ4_compress_fast_extState (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
++
++/*! LZ4_compress_destSize() :
++ * Reverse the logic : compresses as much data as possible from 'src' buffer
++ * into already allocated buffer 'dst', of size >= 'dstCapacity'.
++ * This function either compresses the entire 'src' content into 'dst' if it's large enough,
++ * or fill 'dst' buffer completely with as much data as possible from 'src'.
++ * note: acceleration parameter is fixed to "default".
++ *
++ * *srcSizePtr : in+out parameter. Initially contains size of input.
++ * Will be modified to indicate how many bytes where read from 'src' to fill 'dst'.
++ * New value is necessarily <= input value.
++ * @return : Nb bytes written into 'dst' (necessarily <= dstCapacity)
++ * or 0 if compression fails.
++ *
++ * Note : 'targetDstSize' must be >= 1, because it's the smallest valid lz4 payload.
++ *
++ * Note 2:from v1.8.2 to v1.9.1, this function had a bug (fixed in v1.9.2+):
++ * the produced compressed content could, in rare circumstances,
++ * require to be decompressed into a destination buffer
++ * larger by at least 1 byte than decompressesSize.
++ * If an application uses `LZ4_compress_destSize()`,
++ * it's highly recommended to update liblz4 to v1.9.2 or better.
++ * If this can't be done or ensured,
++ * the receiving decompression function should provide
++ * a dstCapacity which is > decompressedSize, by at least 1 byte.
++ * See https://github.com/lz4/lz4/issues/859 for details
++ */
++LZ4LIB_API int LZ4_compress_destSize(const char* src, char* dst, int* srcSizePtr, int targetDstSize);
++
++/*! LZ4_decompress_safe_partial() :
++ * Decompress an LZ4 compressed block, of size 'srcSize' at position 'src',
++ * into destination buffer 'dst' of size 'dstCapacity'.
++ * Up to 'targetOutputSize' bytes will be decoded.
++ * The function stops decoding on reaching this objective.
++ * This can be useful to boost performance
++ * whenever only the beginning of a block is required.
++ *
++ * @return : the number of bytes decoded in `dst` (necessarily <= targetOutputSize)
++ * If source stream is detected malformed, function returns a negative result.
++ *
++ * Note 1 : @return can be < targetOutputSize, if compressed block contains less data.
++ *
++ * Note 2 : targetOutputSize must be <= dstCapacity
++ *
++ * Note 3 : this function effectively stops decoding on reaching targetOutputSize,
++ * so dstCapacity is kind of redundant.
++ * This is because in older versions of this function,
++ * decoding operation would still write complete sequences.
++ * Therefore, there was no guarantee that it would stop writing at exactly targetOutputSize,
++ * it could write more bytes, though only up to dstCapacity.
++ * Some "margin" used to be required for this operation to work properly.
++ * Thankfully, this is no longer necessary.
++ * The function nonetheless keeps the same signature, in an effort to preserve API compatibility.
++ *
++ * Note 4 : If srcSize is the exact size of the block,
++ * then targetOutputSize can be any value,
++ * including larger than the block's decompressed size.
++ * The function will, at most, generate block's decompressed size.
++ *
++ * Note 5 : If srcSize is _larger_ than block's compressed size,
++ * then targetOutputSize **MUST** be <= block's decompressed size.
++ * Otherwise, *silent corruption will occur*.
++ */
++LZ4LIB_API int LZ4_decompress_safe_partial (const char* src, char* dst, int srcSize, int targetOutputSize, int dstCapacity);
++
++
++/*-*********************************************
++* Streaming Compression Functions
++***********************************************/
++typedef union LZ4_stream_u LZ4_stream_t; /* incomplete type (defined later) */
++
++/*!
++ Note about RC_INVOKED
++
++ - RC_INVOKED is predefined symbol of rc.exe (the resource compiler which is part of MSVC/Visual Studio).
++ https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros
++
++ - Since rc.exe is a legacy compiler, it truncates long symbol (> 30 chars)
++ and reports warning "RC4011: identifier truncated".
++
++ - To eliminate the warning, we surround long preprocessor symbol with
++ "#if !defined(RC_INVOKED) ... #endif" block that means
++ "skip this block when rc.exe is trying to read it".
++*/
++#if !defined(RC_INVOKED) /* https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros */
++#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION)
++LZ4LIB_API LZ4_stream_t* LZ4_createStream(void);
++LZ4LIB_API int LZ4_freeStream (LZ4_stream_t* streamPtr);
++#endif /* !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) */
++#endif
++
++/*! LZ4_resetStream_fast() : v1.9.0+
++ * Use this to prepare an LZ4_stream_t for a new chain of dependent blocks
++ * (e.g., LZ4_compress_fast_continue()).
++ *
++ * An LZ4_stream_t must be initialized once before usage.
++ * This is automatically done when created by LZ4_createStream().
++ * However, should the LZ4_stream_t be simply declared on stack (for example),
++ * it's necessary to initialize it first, using LZ4_initStream().
++ *
++ * After init, start any new stream with LZ4_resetStream_fast().
++ * A same LZ4_stream_t can be re-used multiple times consecutively
++ * and compress multiple streams,
++ * provided that it starts each new stream with LZ4_resetStream_fast().
++ *
++ * LZ4_resetStream_fast() is much faster than LZ4_initStream(),
++ * but is not compatible with memory regions containing garbage data.
++ *
++ * Note: it's only useful to call LZ4_resetStream_fast()
++ * in the context of streaming compression.
++ * The *extState* functions perform their own resets.
++ * Invoking LZ4_resetStream_fast() before is redundant, and even counterproductive.
++ */
++LZ4LIB_API void LZ4_resetStream_fast (LZ4_stream_t* streamPtr);
++
++/*! LZ4_loadDict() :
++ * Use this function to reference a static dictionary into LZ4_stream_t.
++ * The dictionary must remain available during compression.
++ * LZ4_loadDict() triggers a reset, so any previous data will be forgotten.
++ * The same dictionary will have to be loaded on decompression side for successful decoding.
++ * Dictionary are useful for better compression of small data (KB range).
++ * While LZ4 itself accepts any input as dictionary, dictionary efficiency is also a topic.
++ * When in doubt, employ the Zstandard's Dictionary Builder.
++ * Loading a size of 0 is allowed, and is the same as reset.
++ * @return : loaded dictionary size, in bytes (note: only the last 64 KB are loaded)
++ */
++LZ4LIB_API int LZ4_loadDict (LZ4_stream_t* streamPtr, const char* dictionary, int dictSize);
++
++/*! LZ4_loadDictSlow() : v1.10.0+
++ * Same as LZ4_loadDict(),
++ * but uses a bit more cpu to reference the dictionary content more thoroughly.
++ * This is expected to slightly improve compression ratio.
++ * The extra-cpu cost is likely worth it if the dictionary is re-used across multiple sessions.
++ * @return : loaded dictionary size, in bytes (note: only the last 64 KB are loaded)
++ */
++LZ4LIB_API int LZ4_loadDictSlow(LZ4_stream_t* streamPtr, const char* dictionary, int dictSize);
++
++/*! LZ4_attach_dictionary() : stable since v1.10.0
++ *
++ * This allows efficient re-use of a static dictionary multiple times.
++ *
++ * Rather than re-loading the dictionary buffer into a working context before
++ * each compression, or copying a pre-loaded dictionary's LZ4_stream_t into a
++ * working LZ4_stream_t, this function introduces a no-copy setup mechanism,
++ * in which the working stream references @dictionaryStream in-place.
++ *
++ * Several assumptions are made about the state of @dictionaryStream.
++ * Currently, only states which have been prepared by LZ4_loadDict() or
++ * LZ4_loadDictSlow() should be expected to work.
++ *
++ * Alternatively, the provided @dictionaryStream may be NULL,
++ * in which case any existing dictionary stream is unset.
++ *
++ * If a dictionary is provided, it replaces any pre-existing stream history.
++ * The dictionary contents are the only history that can be referenced and
++ * logically immediately precede the data compressed in the first subsequent
++ * compression call.
++ *
++ * The dictionary will only remain attached to the working stream through the
++ * first compression call, at the end of which it is cleared.
++ * @dictionaryStream stream (and source buffer) must remain in-place / accessible / unchanged
++ * through the completion of the compression session.
++ *
++ * Note: there is no equivalent LZ4_attach_*() method on the decompression side
++ * because there is no initialization cost, hence no need to share the cost across multiple sessions.
++ * To decompress LZ4 blocks using dictionary, attached or not,
++ * just employ the regular LZ4_setStreamDecode() for streaming,
++ * or the stateless LZ4_decompress_safe_usingDict() for one-shot decompression.
++ */
++LZ4LIB_API void
++LZ4_attach_dictionary(LZ4_stream_t* workingStream,
++ const LZ4_stream_t* dictionaryStream);
++
++/*! LZ4_compress_fast_continue() :
++ * Compress 'src' content using data from previously compressed blocks, for better compression ratio.
++ * 'dst' buffer must be already allocated.
++ * If dstCapacity >= LZ4_compressBound(srcSize), compression is guaranteed to succeed, and runs faster.
++ *
++ * @return : size of compressed block
++ * or 0 if there is an error (typically, cannot fit into 'dst').
++ *
++ * Note 1 : Each invocation to LZ4_compress_fast_continue() generates a new block.
++ * Each block has precise boundaries.
++ * Each block must be decompressed separately, calling LZ4_decompress_*() with relevant metadata.
++ * It's not possible to append blocks together and expect a single invocation of LZ4_decompress_*() to decompress them together.
++ *
++ * Note 2 : The previous 64KB of source data is __assumed__ to remain present, unmodified, at same address in memory !
++ *
++ * Note 3 : When input is structured as a double-buffer, each buffer can have any size, including < 64 KB.
++ * Make sure that buffers are separated, by at least one byte.
++ * This construction ensures that each block only depends on previous block.
++ *
++ * Note 4 : If input buffer is a ring-buffer, it can have any size, including < 64 KB.
++ *
++ * Note 5 : After an error, the stream status is undefined (invalid), it can only be reset or freed.
++ */
++LZ4LIB_API int LZ4_compress_fast_continue (LZ4_stream_t* streamPtr, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
++
++/*! LZ4_saveDict() :
++ * If last 64KB data cannot be guaranteed to remain available at its current memory location,
++ * save it into a safer place (char* safeBuffer).
++ * This is schematically equivalent to a memcpy() followed by LZ4_loadDict(),
++ * but is much faster, because LZ4_saveDict() doesn't need to rebuild tables.
++ * @return : saved dictionary size in bytes (necessarily <= maxDictSize), or 0 if error.
++ */
++LZ4LIB_API int LZ4_saveDict (LZ4_stream_t* streamPtr, char* safeBuffer, int maxDictSize);
++
++
++/*-**********************************************
++* Streaming Decompression Functions
++* Bufferless synchronous API
++************************************************/
++typedef union LZ4_streamDecode_u LZ4_streamDecode_t; /* tracking context */
++
++/*! LZ4_createStreamDecode() and LZ4_freeStreamDecode() :
++ * creation / destruction of streaming decompression tracking context.
++ * A tracking context can be re-used multiple times.
++ */
++#if !defined(RC_INVOKED) /* https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros */
++#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION)
++LZ4LIB_API LZ4_streamDecode_t* LZ4_createStreamDecode(void);
++LZ4LIB_API int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream);
++#endif /* !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) */
++#endif
++
++/*! LZ4_setStreamDecode() :
++ * An LZ4_streamDecode_t context can be allocated once and re-used multiple times.
++ * Use this function to start decompression of a new stream of blocks.
++ * A dictionary can optionally be set. Use NULL or size 0 for a reset order.
++ * Dictionary is presumed stable : it must remain accessible and unmodified during next decompression.
++ * @return : 1 if OK, 0 if error
++ */
++LZ4LIB_API int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize);
++
++/*! LZ4_decoderRingBufferSize() : v1.8.2+
++ * Note : in a ring buffer scenario (optional),
++ * blocks are presumed decompressed next to each other
++ * up to the moment there is not enough remaining space for next block (remainingSize < maxBlockSize),
++ * at which stage it resumes from beginning of ring buffer.
++ * When setting such a ring buffer for streaming decompression,
++ * provides the minimum size of this ring buffer
++ * to be compatible with any source respecting maxBlockSize condition.
++ * @return : minimum ring buffer size,
++ * or 0 if there is an error (invalid maxBlockSize).
++ */
++LZ4LIB_API int LZ4_decoderRingBufferSize(int maxBlockSize);
++#define LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize) (65536 + 14 + (maxBlockSize)) /* for static allocation; maxBlockSize presumed valid */
++
++/*! LZ4_decompress_safe_continue() :
++ * This decoding function allows decompression of consecutive blocks in "streaming" mode.
++ * The difference with the usual independent blocks is that
++ * new blocks are allowed to find references into former blocks.
++ * A block is an unsplittable entity, and must be presented entirely to the decompression function.
++ * LZ4_decompress_safe_continue() only accepts one block at a time.
++ * It's modeled after `LZ4_decompress_safe()` and behaves similarly.
++ *
++ * @LZ4_streamDecode : decompression state, tracking the position in memory of past data
++ * @compressedSize : exact complete size of one compressed block.
++ * @dstCapacity : size of destination buffer (which must be already allocated),
++ * must be an upper bound of decompressed size.
++ * @return : number of bytes decompressed into destination buffer (necessarily <= dstCapacity)
++ * If destination buffer is not large enough, decoding will stop and output an error code (negative value).
++ * If the source stream is detected malformed, the function will stop decoding and return a negative result.
++ *
++ * The last 64KB of previously decoded data *must* remain available and unmodified
++ * at the memory position where they were previously decoded.
++ * If less than 64KB of data has been decoded, all the data must be present.
++ *
++ * Special : if decompression side sets a ring buffer, it must respect one of the following conditions :
++ * - Decompression buffer size is _at least_ LZ4_decoderRingBufferSize(maxBlockSize).
++ * maxBlockSize is the maximum size of any single block. It can have any value > 16 bytes.
++ * In which case, encoding and decoding buffers do not need to be synchronized.
++ * Actually, data can be produced by any source compliant with LZ4 format specification, and respecting maxBlockSize.
++ * - Synchronized mode :
++ * Decompression buffer size is _exactly_ the same as compression buffer size,
++ * and follows exactly same update rule (block boundaries at same positions),
++ * and decoding function is provided with exact decompressed size of each block (exception for last block of the stream),
++ * _then_ decoding & encoding ring buffer can have any size, including small ones ( < 64 KB).
++ * - Decompression buffer is larger than encoding buffer, by a minimum of maxBlockSize more bytes.
++ * In which case, encoding and decoding buffers do not need to be synchronized,
++ * and encoding ring buffer can have any size, including small ones ( < 64 KB).
++ *
++ * Whenever these conditions are not possible,
++ * save the last 64KB of decoded data into a safe buffer where it can't be modified during decompression,
++ * then indicate where this data is saved using LZ4_setStreamDecode(), before decompressing next block.
++*/
++LZ4LIB_API int
++LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode,
++ const char* src, char* dst,
++ int srcSize, int dstCapacity);
++
++
++/*! LZ4_decompress_safe_usingDict() :
++ * Works the same as
++ * a combination of LZ4_setStreamDecode() followed by LZ4_decompress_safe_continue()
++ * However, it's stateless: it doesn't need any LZ4_streamDecode_t state.
++ * Dictionary is presumed stable : it must remain accessible and unmodified during decompression.
++ * Performance tip : Decompression speed can be substantially increased
++ * when dst == dictStart + dictSize.
++ */
++LZ4LIB_API int
++LZ4_decompress_safe_usingDict(const char* src, char* dst,
++ int srcSize, int dstCapacity,
++ const char* dictStart, int dictSize);
++
++/*! LZ4_decompress_safe_partial_usingDict() :
++ * Behaves the same as LZ4_decompress_safe_partial()
++ * with the added ability to specify a memory segment for past data.
++ * Performance tip : Decompression speed can be substantially increased
++ * when dst == dictStart + dictSize.
++ */
++LZ4LIB_API int
++LZ4_decompress_safe_partial_usingDict(const char* src, char* dst,
++ int compressedSize,
++ int targetOutputSize, int maxOutputSize,
++ const char* dictStart, int dictSize);
++
++#endif /* LZ4_H_2983827168210 */
++
++
++/*^*************************************
++ * !!!!!! STATIC LINKING ONLY !!!!!!
++ ***************************************/
++
++/*-****************************************************************************
++ * Experimental section
++ *
++ * Symbols declared in this section must be considered unstable. Their
++ * signatures or semantics may change, or they may be removed altogether in the
++ * future. They are therefore only safe to depend on when the caller is
++ * statically linked against the library.
++ *
++ * To protect against unsafe usage, not only are the declarations guarded,
++ * the definitions are hidden by default
++ * when building LZ4 as a shared/dynamic library.
++ *
++ * In order to access these declarations,
++ * define LZ4_STATIC_LINKING_ONLY in your application
++ * before including LZ4's headers.
++ *
++ * In order to make their implementations accessible dynamically, you must
++ * define LZ4_PUBLISH_STATIC_FUNCTIONS when building the LZ4 library.
++ ******************************************************************************/
++
++#ifdef LZ4_STATIC_LINKING_ONLY
++
++#ifndef LZ4_STATIC_3504398509
++#define LZ4_STATIC_3504398509
++
++#ifdef LZ4_PUBLISH_STATIC_FUNCTIONS
++# define LZ4LIB_STATIC_API LZ4LIB_API
++#else
++# define LZ4LIB_STATIC_API
++#endif
++
++
++/*! LZ4_compress_fast_extState_fastReset() :
++ * A variant of LZ4_compress_fast_extState().
++ *
++ * Using this variant avoids an expensive initialization step.
++ * It is only safe to call if the state buffer is known to be correctly initialized already
++ * (see above comment on LZ4_resetStream_fast() for a definition of "correctly initialized").
++ * From a high level, the difference is that
++ * this function initializes the provided state with a call to something like LZ4_resetStream_fast()
++ * while LZ4_compress_fast_extState() starts with a call to LZ4_resetStream().
++ */
++LZ4LIB_STATIC_API int LZ4_compress_fast_extState_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
++
++/*! LZ4_compress_destSize_extState() : introduced in v1.10.0
++ * Same as LZ4_compress_destSize(), but using an externally allocated state.
++ * Also: exposes @acceleration
++ */
++int LZ4_compress_destSize_extState(void* state, const char* src, char* dst, int* srcSizePtr, int targetDstSize, int acceleration);
++
++/*! In-place compression and decompression
++ *
++ * It's possible to have input and output sharing the same buffer,
++ * for highly constrained memory environments.
++ * In both cases, it requires input to lay at the end of the buffer,
++ * and decompression to start at beginning of the buffer.
++ * Buffer size must feature some margin, hence be larger than final size.
++ *
++ * |<------------------------buffer--------------------------------->|
++ * |<-----------compressed data--------->|
++ * |<-----------decompressed size------------------>|
++ * |<----margin---->|
++ *
++ * This technique is more useful for decompression,
++ * since decompressed size is typically larger,
++ * and margin is short.
++ *
++ * In-place decompression will work inside any buffer
++ * which size is >= LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize).
++ * This presumes that decompressedSize > compressedSize.
++ * Otherwise, it means compression actually expanded data,
++ * and it would be more efficient to store such data with a flag indicating it's not compressed.
++ * This can happen when data is not compressible (already compressed, or encrypted).
++ *
++ * For in-place compression, margin is larger, as it must be able to cope with both
++ * history preservation, requiring input data to remain unmodified up to LZ4_DISTANCE_MAX,
++ * and data expansion, which can happen when input is not compressible.
++ * As a consequence, buffer size requirements are much higher,
++ * and memory savings offered by in-place compression are more limited.
++ *
++ * There are ways to limit this cost for compression :
++ * - Reduce history size, by modifying LZ4_DISTANCE_MAX.
++ * Note that it is a compile-time constant, so all compressions will apply this limit.
++ * Lower values will reduce compression ratio, except when input_size < LZ4_DISTANCE_MAX,
++ * so it's a reasonable trick when inputs are known to be small.
++ * - Require the compressor to deliver a "maximum compressed size".
++ * This is the `dstCapacity` parameter in `LZ4_compress*()`.
++ * When this size is < LZ4_COMPRESSBOUND(inputSize), then compression can fail,
++ * in which case, the return code will be 0 (zero).
++ * The caller must be ready for these cases to happen,
++ * and typically design a backup scheme to send data uncompressed.
++ * The combination of both techniques can significantly reduce
++ * the amount of margin required for in-place compression.
++ *
++ * In-place compression can work in any buffer
++ * which size is >= (maxCompressedSize)
++ * with maxCompressedSize == LZ4_COMPRESSBOUND(srcSize) for guaranteed compression success.
++ * LZ4_COMPRESS_INPLACE_BUFFER_SIZE() depends on both maxCompressedSize and LZ4_DISTANCE_MAX,
++ * so it's possible to reduce memory requirements by playing with them.
++ */
++
++#define LZ4_DECOMPRESS_INPLACE_MARGIN(compressedSize) (((compressedSize) >> 8) + 32)
++#define LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize) ((decompressedSize) + LZ4_DECOMPRESS_INPLACE_MARGIN(decompressedSize)) /**< note: presumes that compressedSize < decompressedSize. note2: margin is overestimated a bit, since it could use compressedSize instead */
++
++#ifndef LZ4_DISTANCE_MAX /* history window size; can be user-defined at compile time */
++# define LZ4_DISTANCE_MAX 65535 /* set to maximum value by default */
++#endif
++
++#define LZ4_COMPRESS_INPLACE_MARGIN (LZ4_DISTANCE_MAX + 32) /* LZ4_DISTANCE_MAX can be safely replaced by srcSize when it's smaller */
++#define LZ4_COMPRESS_INPLACE_BUFFER_SIZE(maxCompressedSize) ((maxCompressedSize) + LZ4_COMPRESS_INPLACE_MARGIN) /**< maxCompressedSize is generally LZ4_COMPRESSBOUND(inputSize), but can be set to any lower value, with the risk that compression can fail (return code 0(zero)) */
++
++#endif /* LZ4_STATIC_3504398509 */
++#endif /* LZ4_STATIC_LINKING_ONLY */
++
++
++
++#ifndef LZ4_H_98237428734687
++#define LZ4_H_98237428734687
++
++/*-************************************************************
++ * Private Definitions
++ **************************************************************
++ * Do not use these definitions directly.
++ * They are only exposed to allow static allocation of `LZ4_stream_t` and `LZ4_streamDecode_t`.
++ * Accessing members will expose user code to API and/or ABI break in future versions of the library.
++ **************************************************************/
++#define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2)
++#define LZ4_HASHTABLESIZE (1 << LZ4_MEMORY_USAGE)
++#define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */
++
++#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */)
++# include
++ typedef int8_t LZ4_i8;
++ typedef unsigned char LZ4_byte;
++ typedef uint16_t LZ4_u16;
++ typedef uint32_t LZ4_u32;
++#else
++ typedef signed char LZ4_i8;
++ typedef unsigned char LZ4_byte;
++ typedef unsigned short LZ4_u16;
++ typedef unsigned int LZ4_u32;
++#endif
++
++/*! LZ4_stream_t :
++ * Never ever use below internal definitions directly !
++ * These definitions are not API/ABI safe, and may change in future versions.
++ * If you need static allocation, declare or allocate an LZ4_stream_t object.
++**/
++
++typedef struct LZ4_stream_t_internal LZ4_stream_t_internal;
++struct LZ4_stream_t_internal {
++ LZ4_u32 hashTable[LZ4_HASH_SIZE_U32];
++ const LZ4_byte* dictionary;
++ const LZ4_stream_t_internal* dictCtx;
++ LZ4_u32 currentOffset;
++ LZ4_u32 tableType;
++ LZ4_u32 dictSize;
++ /* Implicit padding to ensure structure is aligned */
++};
++
++#define LZ4_STREAM_MINSIZE ((1UL << (LZ4_MEMORY_USAGE)) + 32) /* static size, for inter-version compatibility */
++union LZ4_stream_u {
++ char minStateSize[LZ4_STREAM_MINSIZE];
++ LZ4_stream_t_internal internal_donotuse;
++}; /* previously typedef'd to LZ4_stream_t */
++
++
++/*! LZ4_initStream() : v1.9.0+
++ * An LZ4_stream_t structure must be initialized at least once.
++ * This is automatically done when invoking LZ4_createStream(),
++ * but it's not when the structure is simply declared on stack (for example).
++ *
++ * Use LZ4_initStream() to properly initialize a newly declared LZ4_stream_t.
++ * It can also initialize any arbitrary buffer of sufficient size,
++ * and will @return a pointer of proper type upon initialization.
++ *
++ * Note : initialization fails if size and alignment conditions are not respected.
++ * In which case, the function will @return NULL.
++ * Note2: An LZ4_stream_t structure guarantees correct alignment and size.
++ * Note3: Before v1.9.0, use LZ4_resetStream() instead
++**/
++LZ4LIB_API LZ4_stream_t* LZ4_initStream (void* stateBuffer, size_t size);
++
++
++/*! LZ4_streamDecode_t :
++ * Never ever use below internal definitions directly !
++ * These definitions are not API/ABI safe, and may change in future versions.
++ * If you need static allocation, declare or allocate an LZ4_streamDecode_t object.
++**/
++typedef struct {
++ const LZ4_byte* externalDict;
++ const LZ4_byte* prefixEnd;
++ size_t extDictSize;
++ size_t prefixSize;
++} LZ4_streamDecode_t_internal;
++
++#define LZ4_STREAMDECODE_MINSIZE 32
++union LZ4_streamDecode_u {
++ char minStateSize[LZ4_STREAMDECODE_MINSIZE];
++ LZ4_streamDecode_t_internal internal_donotuse;
++} ; /* previously typedef'd to LZ4_streamDecode_t */
++
++
++
++/*-************************************
++* Obsolete Functions
++**************************************/
++
++/*! Deprecation warnings
++ *
++ * Deprecated functions make the compiler generate a warning when invoked.
++ * This is meant to invite users to update their source code.
++ * Should deprecation warnings be a problem, it is generally possible to disable them,
++ * typically with -Wno-deprecated-declarations for gcc
++ * or _CRT_SECURE_NO_WARNINGS in Visual.
++ *
++ * Another method is to define LZ4_DISABLE_DEPRECATE_WARNINGS
++ * before including the header file.
++ */
++#ifdef LZ4_DISABLE_DEPRECATE_WARNINGS
++# define LZ4_DEPRECATED(message) /* disable deprecation warnings */
++#else
++# if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */
++# define LZ4_DEPRECATED(message) [[deprecated(message)]]
++# elif defined(_MSC_VER)
++# define LZ4_DEPRECATED(message) __declspec(deprecated(message))
++# elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ * 10 + __GNUC_MINOR__ >= 45))
++# define LZ4_DEPRECATED(message) __attribute__((deprecated(message)))
++# elif defined(__GNUC__) && (__GNUC__ * 10 + __GNUC_MINOR__ >= 31)
++# define LZ4_DEPRECATED(message) __attribute__((deprecated))
++# else
++# pragma message("WARNING: LZ4_DEPRECATED needs custom implementation for this compiler")
++# define LZ4_DEPRECATED(message) /* disabled */
++# endif
++#endif /* LZ4_DISABLE_DEPRECATE_WARNINGS */
++
++/*! Obsolete compression functions (since v1.7.3) */
++LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress (const char* src, char* dest, int srcSize);
++LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress_limitedOutput (const char* src, char* dest, int srcSize, int maxOutputSize);
++LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_withState (void* state, const char* source, char* dest, int inputSize);
++LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_limitedOutput_withState (void* state, const char* source, char* dest, int inputSize, int maxOutputSize);
++LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize);
++LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize, int maxOutputSize);
++
++/*! Obsolete decompression functions (since v1.8.0) */
++LZ4_DEPRECATED("use LZ4_decompress_fast() instead") LZ4LIB_API int LZ4_uncompress (const char* source, char* dest, int outputSize);
++LZ4_DEPRECATED("use LZ4_decompress_safe() instead") LZ4LIB_API int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize);
++
++/* Obsolete streaming functions (since v1.7.0)
++ * degraded functionality; do not use!
++ *
++ * In order to perform streaming compression, these functions depended on data
++ * that is no longer tracked in the state. They have been preserved as well as
++ * possible: using them will still produce a correct output. However, they don't
++ * actually retain any history between compression calls. The compression ratio
++ * achieved will therefore be no better than compressing each chunk
++ * independently.
++ */
++LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API void* LZ4_create (char* inputBuffer);
++LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API int LZ4_sizeofStreamState(void);
++LZ4_DEPRECATED("Use LZ4_resetStream() instead") LZ4LIB_API int LZ4_resetStreamState(void* state, char* inputBuffer);
++LZ4_DEPRECATED("Use LZ4_saveDict() instead") LZ4LIB_API char* LZ4_slideInputBuffer (void* state);
++
++/*! Obsolete streaming decoding functions (since v1.7.0) */
++LZ4_DEPRECATED("use LZ4_decompress_safe_usingDict() instead") LZ4LIB_API int LZ4_decompress_safe_withPrefix64k (const char* src, char* dst, int compressedSize, int maxDstSize);
++LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") LZ4LIB_API int LZ4_decompress_fast_withPrefix64k (const char* src, char* dst, int originalSize);
++
++/*! Obsolete LZ4_decompress_fast variants (since v1.9.0) :
++ * These functions used to be faster than LZ4_decompress_safe(),
++ * but this is no longer the case. They are now slower.
++ * This is because LZ4_decompress_fast() doesn't know the input size,
++ * and therefore must progress more cautiously into the input buffer to not read beyond the end of block.
++ * On top of that `LZ4_decompress_fast()` is not protected vs malformed or malicious inputs, making it a security liability.
++ * As a consequence, LZ4_decompress_fast() is strongly discouraged, and deprecated.
++ *
++ * The last remaining LZ4_decompress_fast() specificity is that
++ * it can decompress a block without knowing its compressed size.
++ * Such functionality can be achieved in a more secure manner
++ * by employing LZ4_decompress_safe_partial().
++ *
++ * Parameters:
++ * originalSize : is the uncompressed size to regenerate.
++ * `dst` must be already allocated, its size must be >= 'originalSize' bytes.
++ * @return : number of bytes read from source buffer (== compressed size).
++ * The function expects to finish at block's end exactly.
++ * If the source stream is detected malformed, the function stops decoding and returns a negative result.
++ * note : LZ4_decompress_fast*() requires originalSize. Thanks to this information, it never writes past the output buffer.
++ * However, since it doesn't know its 'src' size, it may read an unknown amount of input, past input buffer bounds.
++ * Also, since match offsets are not validated, match reads from 'src' may underflow too.
++ * These issues never happen if input (compressed) data is correct.
++ * But they may happen if input data is invalid (error or intentional tampering).
++ * As a consequence, use these functions in trusted environments with trusted data **only**.
++ */
++LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_partial() instead")
++LZ4LIB_API int LZ4_decompress_fast (const char* src, char* dst, int originalSize);
++LZ4_DEPRECATED("This function is deprecated and unsafe. Consider migrating towards LZ4_decompress_safe_continue() instead. "
++ "Note that the contract will change (requires block's compressed size, instead of decompressed size)")
++LZ4LIB_API int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int originalSize);
++LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_partial_usingDict() instead")
++LZ4LIB_API int LZ4_decompress_fast_usingDict (const char* src, char* dst, int originalSize, const char* dictStart, int dictSize);
++
++/*! LZ4_resetStream() :
++ * An LZ4_stream_t structure must be initialized at least once.
++ * This is done with LZ4_initStream(), or LZ4_resetStream().
++ * Consider switching to LZ4_initStream(),
++ * invoking LZ4_resetStream() will trigger deprecation warnings in the future.
++ */
++LZ4LIB_API void LZ4_resetStream (LZ4_stream_t* streamPtr);
++
++
++#endif /* LZ4_H_98237428734687 */
++
++
++#if defined (__cplusplus)
++}
++#endif
+diff --git a/code/server/server.h b/code/server/server.h
+index c32b2dc..6d59686 100644
+--- a/code/server/server.h
++++ b/code/server/server.h
+@@ -341,6 +341,29 @@ int SV_BotGetConsoleMessage( int client, char *buf, int size );
+ int BotImport_DebugPolygonCreate(int color, int numPoints, vec3_t *points);
+ void BotImport_DebugPolygonDelete(int id);
+
++//
++// sv_netdemo.c
++//
++void SVD_Record_f( void );
++void SVD_StopRecord_f( void );
++void SVD_RecordFrame( void );
++void SVD_ResetDeltaState( void );
++void SVD_AutoRecord( void );
++void SVD_CaptureServerCommand( const char *cmd );
++void SVD_Play_f( void );
++void SVD_StopPlay_f( void );
++void SVD_CleanupPlayback( void );
++void SVD_Stop_f( void );
++void SVD_Pause_f( void );
++void SVD_Seek_f( void );
++void SVD_SeekExact_f( void );
++qboolean SVD_PlaybackFrame( void );
++qboolean SVD_IsRecording( void );
++qboolean SVD_IsPlaying( void );
++qboolean SVD_IsPaused( void );
++qboolean SVD_IsStarting( void );
++qboolean SVD_ShouldPause( void );
++
+ //============================================================
+ //
+ // high level object sorting to reduce interaction tests
+diff --git a/code/server/sv_ccmds.c b/code/server/sv_ccmds.c
+index 62ae179..7f1370f 100644
+--- a/code/server/sv_ccmds.c
++++ b/code/server/sv_ccmds.c
+@@ -280,6 +280,9 @@ static void SV_MapRestart_f( void ) {
+ sv.state = SS_GAME;
+ sv.restarting = qfalse;
+
++ // reset demo delta state so next frame writes full entities
++ SVD_ResetDeltaState();
++
+ // connect and begin all the clients
+ for (i=0 ; iinteger ; i++) {
+ client = &svs.clients[i];
+@@ -736,6 +739,14 @@ void SV_AddOperatorCommands( void ) {
+ if( com_dedicated->integer ) {
+ Cmd_AddCommand ("say", SV_ConSay_f);
+ }
++
++ // server-side demo recording/playback
++ Cmd_AddCommand ("svdemo_record", SVD_Record_f);
++ Cmd_AddCommand ("svdemo_stop", SVD_Stop_f);
++ Cmd_AddCommand ("svdemo_play", SVD_Play_f);
++ Cmd_AddCommand ("svdemo_pause", SVD_Pause_f);
++ Cmd_AddCommand ("svdemo_seek", SVD_Seek_f);
++ Cmd_AddCommand ("svdemo_seekexact", SVD_SeekExact_f);
+ }
+
+ /*
+diff --git a/code/server/sv_client.c b/code/server/sv_client.c
+index c3fdc92..84c2e3b 100644
+--- a/code/server/sv_client.c
++++ b/code/server/sv_client.c
+@@ -1419,7 +1419,12 @@ static void SV_UserMove( client_t *cl, msg_t *msg, qboolean delta ) {
+ // don't execute if this is an old cmd which is already executed
+ // these old cmds are included when cl_packetdup > 0
+ if ( cmds[i].serverTime <= cl->lastUsercmd.serverTime ) {
+- continue;
++ // demo playback: still process the LAST cmd even with duplicate
++ // serverTime (paused = frozen time, all cmds have same time).
++ // Need buttons and origin from fresh usercmds.
++ if ( !SVD_IsPlaying() || i != cmdCount - 1 ) {
++ continue;
++ }
+ }
+ SV_ClientThink (cl, &cmds[ i ]);
+ }
+diff --git a/code/server/sv_game.c b/code/server/sv_game.c
+index 202994e..b227f3c 100644
+--- a/code/server/sv_game.c
++++ b/code/server/sv_game.c
+@@ -83,13 +83,20 @@ Sends a command string to a client
+ ===============
+ */
+ void SV_GameSendServerCommand( int clientNum, const char *text ) {
++ // capture for demo recording: broadcasts and per-client chat/tchat
++ if ( clientNum == -1 ) {
++ SVD_CaptureServerCommand( text );
++ } else if ( !strncmp( text, "chat", 4 ) || !strncmp( text, "tchat", 5 ) ) {
++ SVD_CaptureServerCommand( text );
++ }
++
+ if ( clientNum == -1 ) {
+ SV_SendServerCommand( NULL, "%s", text );
+ } else {
+ if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) {
+ return;
+ }
+- SV_SendServerCommand( svs.clients + clientNum, "%s", text );
++ SV_SendServerCommand( svs.clients + clientNum, "%s", text );
+ }
+ }
+
+diff --git a/code/server/sv_init.c b/code/server/sv_init.c
+index b3d0c4d..8448195 100644
+--- a/code/server/sv_init.c
++++ b/code/server/sv_init.c
+@@ -351,6 +351,18 @@ void SV_SpawnServer( char *server, qboolean killBots ) {
+ char systemInfo[16384];
+ const char *p;
+
++ // stop any active demo recording/playback (one demo = one map).
++ // SVD_IsStarting() returns true when SVD_Play_f is calling devmap
++ // internally — don't stop our own playback.
++ if ( SVD_IsRecording() ) {
++ Com_Printf( "Map change — stopping demo recording.\n" );
++ SVD_StopRecord_f();
++ }
++ if ( SVD_IsPlaying() && !SVD_IsStarting() ) {
++ Com_Printf( "Map change — stopping demo playback.\n" );
++ SVD_CleanupPlayback();
++ }
++
+ // shut down the existing game if it is running
+ SV_ShutdownGameProgs();
+
+@@ -546,6 +558,9 @@ void SV_SpawnServer( char *server, qboolean killBots ) {
+ Hunk_SetMark();
+
+ Com_Printf ("-----------------------------------\n");
++
++ // auto-record demo if enabled
++ SVD_AutoRecord();
+ }
+
+ /*
+@@ -612,6 +627,11 @@ void SV_Init (void) {
+ sv_lanForceRate = Cvar_Get ("sv_lanForceRate", "1", CVAR_ARCHIVE );
+ sv_strictAuth = Cvar_Get ("sv_strictAuth", "1", CVAR_ARCHIVE );
+
++ // server-side demo settings
++ Cvar_Get ("svdemo_autorecord", "0", CVAR_ARCHIVE);
++ Cvar_Get ("svdemo_pauseEmpty", "1", CVAR_ARCHIVE);
++ Cvar_Get ("svdemo_keyframeInterval", "5", CVAR_ARCHIVE); // seconds, 0 = disabled
++
+ // initialize bot cvars so they are listed and can be set before loading the botlib
+ SV_BotInitCvars();
+
+@@ -667,6 +687,15 @@ void SV_Shutdown( char *finalmsg ) {
+
+ Com_Printf( "----- Server Shutdown -----\n" );
+
++ // clean up any active demo recording/playback.
++ // skip if SVD_Play_f is calling SV_Shutdown internally.
++ if ( SVD_IsRecording() ) {
++ SVD_StopRecord_f();
++ }
++ if ( SVD_IsPlaying() && !SVD_IsStarting() ) {
++ SVD_CleanupPlayback();
++ }
++
+ if ( svs.clients && !com_errorEntered ) {
+ SV_FinalMessage( finalmsg );
+ }
+diff --git a/code/server/sv_main.c b/code/server/sv_main.c
+index 98c74ce..27834a3 100644
+--- a/code/server/sv_main.c
++++ b/code/server/sv_main.c
+@@ -831,18 +831,38 @@ void SV_Frame( int msec ) {
+ // run the game simulation in chunks
+ while ( sv.timeResidual >= frameMsec ) {
+ sv.timeResidual -= frameMsec;
++
++ if ( SVD_IsPaused() ) {
++ // demo paused: freeze svs.time so trajectories freeze
++ // and client doesn't see time jumps on unpause.
++ // still run game frame for spectator movement (at frozen time).
++ VM_Call( gvm, GAME_RUN_FRAME, svs.time );
++ continue;
++ }
++
+ svs.time += frameMsec;
+
++ if ( SVD_IsPlaying() ) {
++ // demo playback: read recorded entities instead of running game logic
++ SVD_PlaybackFrame();
++ // still call the game frame for spectator movement
++ }
++
+ // let everything in the world think and move
+ VM_Call( gvm, GAME_RUN_FRAME, svs.time );
++
++ // capture frame for demo recording
++ SVD_RecordFrame();
+ }
+
+ if ( com_speeds->integer ) {
+ time_game = Sys_Milliseconds () - startTime;
+ }
+
+- // check timeouts
+- SV_CheckTimeouts();
++ // check timeouts (skip during demo playback — zombie slots would be freed)
++ if ( !SVD_IsPlaying() ) {
++ SV_CheckTimeouts();
++ }
+
+ // send messages back to the clients
+ SV_SendClientMessages();
+diff --git a/code/server/sv_netdemo.c b/code/server/sv_netdemo.c
+new file mode 100644
+index 0000000..ab5f82e
+--- /dev/null
++++ b/code/server/sv_netdemo.c
+@@ -0,0 +1,1263 @@
++/*
++===========================================================================
++Server-side demo recording and playback (netdemo).
++
++Records the full entity state array each server frame so that
++demos can be played back from any viewpoint. During playback the
++recorded entities are injected into sv.gentities and the normal
++snapshot pipeline delivers them to a spectator client.
++===========================================================================
++*/
++
++#include "server.h"
++
++// Cvar_Set2 not in public header but needed to force-set CVAR_ROM cvars
++extern cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force );
++
++// ---------------------------------------------------------------
++// File format
++// ---------------------------------------------------------------
++//
++// Header:
++// 4 bytes magic "SVDM"
++// 4 bytes version (1)
++// 4 bytes original sv_maxclients
++// 4 bytes original sv_fps
++// 64 bytes map name (null-padded)
++// Then: configstrings block
++// for each non-empty configstring:
++// 2 bytes index
++// 2 bytes string length (incl NUL)
++// N bytes string data
++// 2 bytes index = 0xFFFF (terminator)
++//
++// Per frame:
++// 4 bytes serverTime
++// 2 bytes numEntities (how many entity records follow)
++// for each entity:
++// 2 bytes entity number
++// entityState_t (fixed size, raw)
++// 4 bytes svFlags
++// 4 bytes linked
++// 12 bytes currentOrigin[3]
++// 12 bytes absmin[3]
++// 12 bytes absmax[3]
++// 2 bytes numConfigChanges
++// for each change:
++// 2 bytes index
++// 2 bytes string length (incl NUL)
++// N bytes string data
++//
++// Footer:
++// 4 bytes serverTime = -1 (end marker)
++//
++
++#define SVDEMO_MAGIC (('S') | ('V' << 8) | ('D' << 16) | ('M' << 24))
++#define SVDEMO_VERSION 3 // v3: removed PVS data, svFlags only
++#define SVDEMO_MAX_MAPNAME 64
++
++// header flags
++
++// ---------------------------------------------------------------
++// State
++// ---------------------------------------------------------------
++
++// per-entity data stored for delta compression
++typedef struct {
++ entityState_t es;
++ int svFlags;
++ qboolean active; // was this entity present last frame?
++} svdEntityState_t;
++
++// per-player state for delta compression
++typedef struct {
++ playerState_t ps;
++ qboolean active;
++} svdPlayerState_t;
++
++#define SVD_MAX_SERVERCMDS 64
++#define SVD_MAX_SERVERCMD_LEN 1024
++
++typedef struct {
++ // recording
++ fileHandle_t recordFile;
++ qboolean recording;
++ char *lastConfigstrings[MAX_CONFIGSTRINGS]; // for delta detection
++ svdEntityState_t prevEntities[MAX_GENTITIES]; // previous frame for delta
++ svdPlayerState_t prevPlayers[MAX_CLIENTS]; // previous frame player states
++
++ // buffered server commands for current frame
++ char serverCmds[SVD_MAX_SERVERCMDS][SVD_MAX_SERVERCMD_LEN];
++ int numServerCmds;
++ qboolean mapRestarted; // set by SVD_ResetDeltaState, written as frame flag
++ qboolean isKeyframe; // next frame is a keyframe (delta from baseline)
++ int keyframeInterval; // frames between keyframes (0 = disabled)
++ int framesSinceKeyframe; // counter for next keyframe
++
++ // playback
++ fileHandle_t playFile;
++ qboolean playing;
++ int playMaxClients; // original maxclients from the recording
++ int playFps; // original sv_fps
++ char *savedConfigstrings[MAX_CONFIGSTRINGS]; // from demo header, re-applied after map load
++ char playMapName[SVDEMO_MAX_MAPNAME];
++ qboolean endOfDemo;
++ qboolean needConfigstrings; // apply saved configstrings on first frame
++ qboolean starting; // SVD_Play_f is running devmap internally
++ qboolean paused;
++ qboolean seeked; // just seeked, next frame needs RESET
++ svdEntityState_t playPrevEntities[MAX_GENTITIES]; // previous frame for delta read
++ svdPlayerState_t playPrevPlayers[MAX_CLIENTS]; // previous frame player states
++
++ // keyframe index (shared by recording and playback)
++ int numKeyframes;
++ int maxKeyframes; // allocated size
++ int *keyframeTimes; // serverTime of each keyframe
++ int *keyframeOffsets; // file offset of each keyframe
++} svDemo_t;
++
++static svDemo_t demo;
++// ---------------------------------------------------------------
++// Recording helpers
++// ---------------------------------------------------------------
++
++static void SVD_WriteInt( fileHandle_t f, int v ) {
++ FS_Write( &v, 4, f );
++}
++
++static void SVD_WriteShort( fileHandle_t f, short v ) {
++ FS_Write( &v, 2, f );
++}
++
++static int SVD_ReadInt( fileHandle_t f ) {
++ int v = 0;
++ FS_Read( &v, 4, f );
++ return v;
++}
++
++static short SVD_ReadShort( fileHandle_t f ) {
++ short v = 0;
++ FS_Read( &v, 2, f );
++ return v;
++}
++
++// ---------------------------------------------------------------
++// Write header
++// ---------------------------------------------------------------
++
++static void SVD_WriteHeader( fileHandle_t f ) {
++ int i;
++ char mapBuf[SVDEMO_MAX_MAPNAME];
++
++ SVD_WriteInt( f, SVDEMO_MAGIC );
++ SVD_WriteInt( f, SVDEMO_VERSION );
++ SVD_WriteInt( f, 0 ); // flags (reserved)
++ SVD_WriteInt( f, sv_maxclients->integer );
++ SVD_WriteInt( f, sv_fps->integer );
++
++ // map name
++ memset( mapBuf, 0, sizeof(mapBuf) );
++ Q_strncpyz( mapBuf, sv.configstrings[CS_SERVERINFO], sizeof(mapBuf) );
++ // actually store the mapname from CS_SERVERINFO... or just the map name
++ {
++ const char *mapname = Cvar_VariableString("mapname");
++ memset( mapBuf, 0, sizeof(mapBuf) );
++ Q_strncpyz( mapBuf, mapname, sizeof(mapBuf) );
++ }
++ FS_Write( mapBuf, SVDEMO_MAX_MAPNAME, f );
++
++ // configstrings
++ for ( i = 0; i < MAX_CONFIGSTRINGS; i++ ) {
++ if ( sv.configstrings[i] && sv.configstrings[i][0] ) {
++ int len = strlen( sv.configstrings[i] ) + 1;
++ SVD_WriteShort( f, (short)i );
++ SVD_WriteShort( f, (short)len );
++ FS_Write( sv.configstrings[i], len, f );
++
++ // store initial copy for delta detection
++ if ( demo.lastConfigstrings[i] ) {
++ Z_Free( demo.lastConfigstrings[i] );
++ }
++ demo.lastConfigstrings[i] = CopyString( sv.configstrings[i] );
++ }
++ }
++ // terminator
++ SVD_WriteShort( f, (short)0xFFFF );
++}
++
++// ---------------------------------------------------------------
++// Write one frame
++// ---------------------------------------------------------------
++
++static void SVD_WriteFrame( fileHandle_t f ) {
++ int i;
++ sharedEntity_t *ent;
++ short numChanges;
++ msg_t msg;
++ static byte msgBuf[MAX_GENTITIES * 300]; // worst case: all entities full write from baseline
++
++ SVD_WriteInt( f, svs.time );
++ SVD_WriteShort( f, (short)sv.num_entities );
++
++ // frame flags: bit 0 = map restarted, bit 1 = keyframe
++ {
++ byte frameFlags = 0;
++ if ( demo.mapRestarted ) {
++ frameFlags |= 1;
++ demo.mapRestarted = qfalse;
++ }
++ if ( demo.isKeyframe ) {
++ frameFlags |= 2;
++ demo.isKeyframe = qfalse;
++ }
++ FS_Write( &frameFlags, 1, f );
++ }
++
++ // delta-compress all entities into a message buffer
++ MSG_Init( &msg, msgBuf, sizeof(msgBuf) );
++
++ for ( i = 0; i < sv.num_entities; i++ ) {
++ qboolean active;
++ ent = SV_GentityNum( i );
++ active = ( ent->r.linked || ent->s.eType != 0 );
++
++ if ( active ) {
++ entityState_t baseline;
++ entityState_t *from;
++ if ( demo.prevEntities[i].active ) {
++ from = &demo.prevEntities[i].es;
++ } else {
++ Com_Memset( &baseline, 0, sizeof(baseline) );
++ baseline.number = i;
++ from = &baseline;
++ }
++ MSG_WriteDeltaEntity( &msg, from, &ent->s, qtrue );
++
++ // write svFlags only if changed (rarely changes)
++ if ( ent->r.svFlags != demo.prevEntities[i].svFlags ) {
++ MSG_WriteBits( &msg, 1, 1 );
++ MSG_WriteLong( &msg, ent->r.svFlags );
++ } else {
++ MSG_WriteBits( &msg, 0, 1 );
++ }
++
++ // update prev state
++ demo.prevEntities[i].es = ent->s;
++ demo.prevEntities[i].svFlags = ent->r.svFlags;
++ demo.prevEntities[i].active = qtrue;
++ } else if ( demo.prevEntities[i].active ) {
++ // entity was removed — write a remove marker
++ MSG_WriteDeltaEntity( &msg, &demo.prevEntities[i].es, NULL, qtrue );
++ demo.prevEntities[i].active = qfalse;
++ }
++ // else: entity was inactive and still inactive — write nothing
++ }
++
++ // end of entities marker
++ MSG_WriteBits( &msg, (MAX_GENTITIES - 1), GENTITYNUM_BITS );
++
++ // write entity message to file
++ SVD_WriteInt( f, msg.cursize );
++ FS_Write( msg.data, msg.cursize, f );
++
++ // write player states (delta compressed)
++ {
++ msg_t psmsg;
++ static byte psBuf[MAX_CLIENTS * 600]; // worst case: full playerState from baseline
++ int psCount = 0;
++
++ MSG_Init( &psmsg, psBuf, sizeof(psBuf) );
++
++ for ( i = 0; i < sv_maxclients->integer; i++ ) {
++ playerState_t *ps = SV_GameClientNum( i );
++ client_t *cl = &svs.clients[i];
++ qboolean active = ( cl->state >= CS_ACTIVE );
++ qboolean isSpectator;
++ playerState_t specPs;
++
++ // detect spectators: free cam or follow mode
++ isSpectator = active && ( ps->pm_type == PM_SPECTATOR || (ps->pm_flags & PMF_FOLLOW) );
++
++ // for spectators, record a sanitized ps so they appear on
++ // the scoreboard as spectators (follow mode corrupts their ps
++ // with the followed player's data)
++ if ( isSpectator ) {
++ Com_Memset( &specPs, 0, sizeof(specPs) );
++ specPs.commandTime = ps->commandTime;
++ specPs.pm_type = PM_SPECTATOR;
++ specPs.persistant[PERS_TEAM] = TEAM_SPECTATOR;
++ specPs.clientNum = i;
++ ps = &specPs;
++ }
++
++ if ( active ) {
++ playerState_t *from = demo.prevPlayers[i].active ? &demo.prevPlayers[i].ps : NULL;
++ MSG_WriteBits( &psmsg, i, 6 ); // client number (0-63)
++ MSG_WriteBits( &psmsg, 1, 1 ); // active flag
++ MSG_WriteDeltaPlayerstate( &psmsg, from, ps );
++ demo.prevPlayers[i].ps = *ps;
++ demo.prevPlayers[i].active = qtrue;
++ psCount++;
++ } else if ( demo.prevPlayers[i].active ) {
++ MSG_WriteBits( &psmsg, i, 6 );
++ MSG_WriteBits( &psmsg, 0, 1 ); // inactive flag (player left)
++ demo.prevPlayers[i].active = qfalse;
++ psCount++;
++ }
++ }
++ // terminator
++ MSG_WriteBits( &psmsg, MAX_CLIENTS - 1, 6 );
++ MSG_WriteBits( &psmsg, 0, 1 );
++
++ SVD_WriteInt( f, psmsg.cursize );
++ FS_Write( psmsg.data, psmsg.cursize, f );
++ }
++
++ // configstring changes
++ numChanges = 0;
++ for ( i = 0; i < MAX_CONFIGSTRINGS; i++ ) {
++ const char *cur = sv.configstrings[i] ? sv.configstrings[i] : "";
++ const char *old = demo.lastConfigstrings[i] ? demo.lastConfigstrings[i] : "";
++ if ( strcmp( cur, old ) != 0 ) {
++ numChanges++;
++ }
++ }
++ SVD_WriteShort( f, numChanges );
++
++ if ( numChanges > 0 ) {
++ for ( i = 0; i < MAX_CONFIGSTRINGS; i++ ) {
++ const char *cur = sv.configstrings[i] ? sv.configstrings[i] : "";
++ const char *old = demo.lastConfigstrings[i] ? demo.lastConfigstrings[i] : "";
++ if ( strcmp( cur, old ) != 0 ) {
++ int len = strlen( cur ) + 1;
++ SVD_WriteShort( f, (short)i );
++ SVD_WriteShort( f, (short)len );
++ FS_Write( cur, len, f );
++
++ if ( demo.lastConfigstrings[i] ) {
++ Z_Free( demo.lastConfigstrings[i] );
++ }
++ demo.lastConfigstrings[i] = CopyString( cur );
++ }
++ }
++ }
++
++ // write buffered server commands (chat, prints, etc.)
++ SVD_WriteShort( f, (short)demo.numServerCmds );
++ for ( i = 0; i < demo.numServerCmds; i++ ) {
++ short len = (short)( strlen( demo.serverCmds[i] ) + 1 );
++ SVD_WriteShort( f, len );
++ FS_Write( demo.serverCmds[i], len, f );
++ }
++ demo.numServerCmds = 0;
++}
++
++// ---------------------------------------------------------------
++// Recording commands
++// ---------------------------------------------------------------
++
++/*
++Start recording a demo with the given name.
++Returns qtrue on success.
++*/
++static qboolean SVD_StartRecording( const char *demoname ) {
++ char path[MAX_OSPATH];
++
++ if ( demo.recording ) {
++ Com_Printf( "Already recording a server demo.\n" );
++ return qfalse;
++ }
++
++ if ( sv.state != SS_GAME ) {
++ Com_Printf( "Not running a server.\n" );
++ return qfalse;
++ }
++
++ Com_sprintf( path, sizeof(path), "svdemos/%s.svdm", demoname );
++
++ demo.recordFile = FS_FOpenFileWrite( path );
++ if ( !demo.recordFile ) {
++ Com_Printf( "ERROR: couldn't open %s for writing.\n", path );
++ return qfalse;
++ }
++
++ Com_Printf( "Recording server demo to %s\n", path );
++ demo.recording = qtrue;
++
++ // clear delta state for fresh recording
++ Com_Memset( demo.prevEntities, 0, sizeof(demo.prevEntities) );
++ Com_Memset( demo.prevPlayers, 0, sizeof(demo.prevPlayers) );
++
++ // keyframe interval from cvar (seconds to frames at sv_fps)
++ {
++ int secs = Cvar_VariableIntegerValue( "svdemo_keyframeInterval" );
++ if ( secs > 0 ) {
++ demo.keyframeInterval = secs * sv_fps->integer;
++ } else {
++ demo.keyframeInterval = 0;
++ }
++ // first frame is always a keyframe (makes beginning seekable)
++ demo.framesSinceKeyframe = demo.keyframeInterval;
++ demo.numKeyframes = 0;
++ }
++
++ SVD_WriteHeader( demo.recordFile );
++ return qtrue;
++}
++
++void SVD_Record_f( void ) {
++ char *s;
++
++ s = Cmd_Argv(1);
++ if ( !s[0] ) {
++ Com_Printf( "Usage: svdemo_record \n" );
++ return;
++ }
++
++ SVD_StartRecording( s );
++}
++
++/*
++Auto-record: called from SV_SpawnServer after the map is fully loaded.
++Generates a name from map name + timestamp.
++*/
++void SVD_AutoRecord( void ) {
++ char demoname[MAX_OSPATH];
++ const char *mapname;
++ qtime_t now;
++
++ if ( demo.recording || demo.playing ) {
++ return;
++ }
++
++ if ( !Cvar_VariableIntegerValue( "svdemo_autorecord" ) ) {
++ return;
++ }
++
++ if ( sv.state != SS_GAME ) {
++ return;
++ }
++
++ mapname = Cvar_VariableString( "mapname" );
++ Com_RealTime( &now );
++ Com_sprintf( demoname, sizeof(demoname), "%s_%04d%02d%02d_%02d%02d%02d",
++ mapname,
++ 1900 + now.tm_year, 1 + now.tm_mon, now.tm_mday,
++ now.tm_hour, now.tm_min, now.tm_sec );
++
++ SVD_StartRecording( demoname );
++}
++
++void SVD_StopRecord_f( void ) {
++ int i;
++
++ if ( !demo.recording ) {
++ Com_Printf( "Not recording a server demo.\n" );
++ return;
++ }
++
++ // write end marker
++ SVD_WriteInt( demo.recordFile, -1 );
++
++ // write keyframe index after the end marker.
++ // layout: [numKf][time0 off0 time1 off1 ...][numKf_copy]
++ // numKf_copy at the very end lets playback find the table
++ // by seeking to fileLen - 4.
++ {
++ int kf;
++ SVD_WriteInt( demo.recordFile, demo.numKeyframes );
++ for ( kf = 0; kf < demo.numKeyframes; kf++ ) {
++ SVD_WriteInt( demo.recordFile, demo.keyframeTimes[kf] );
++ SVD_WriteInt( demo.recordFile, demo.keyframeOffsets[kf] );
++ }
++ SVD_WriteInt( demo.recordFile, demo.numKeyframes ); // copy at end
++ Com_Printf( "Wrote %d keyframes.\n", demo.numKeyframes );
++ }
++
++ FS_FCloseFile( demo.recordFile );
++ demo.recordFile = 0;
++ demo.recording = qfalse;
++
++ // free configstring tracking
++ for ( i = 0; i < MAX_CONFIGSTRINGS; i++ ) {
++ if ( demo.lastConfigstrings[i] ) {
++ Z_Free( demo.lastConfigstrings[i] );
++ demo.lastConfigstrings[i] = NULL;
++ }
++ }
++
++ // free keyframe index
++ if ( demo.keyframeTimes ) { Z_Free( demo.keyframeTimes ); demo.keyframeTimes = NULL; }
++ if ( demo.keyframeOffsets ) { Z_Free( demo.keyframeOffsets ); demo.keyframeOffsets = NULL; }
++ demo.numKeyframes = demo.maxKeyframes = 0;
++
++ Com_Printf( "Server demo recording stopped.\n" );
++}
++
++/*
++Called from SV_Frame() after the game has run its frame.
++*/
++/*
++Reset delta compression state. Call on map_restart so the next
++recorded frame writes full entity/player states from baseline.
++*/
++/*
++Capture a broadcast server command for the current frame.
++Called from SV_SendServerCommand when cl == NULL (broadcast).
++*/
++void SVD_CaptureServerCommand( const char *cmd ) {
++ int i;
++
++ if ( !demo.recording ) {
++ return;
++ }
++ if ( demo.numServerCmds >= SVD_MAX_SERVERCMDS ) {
++ return; // overflow, drop command
++ }
++
++ // deduplicate: per-client chat is sent N times (once per client),
++ // only store the first occurrence
++ for ( i = 0; i < demo.numServerCmds; i++ ) {
++ if ( !strcmp( demo.serverCmds[i], cmd ) ) {
++ return;
++ }
++ }
++
++ Q_strncpyz( demo.serverCmds[demo.numServerCmds], cmd, SVD_MAX_SERVERCMD_LEN );
++ demo.numServerCmds++;
++}
++
++void SVD_ResetDeltaState( void ) {
++ if ( !demo.recording ) {
++ return;
++ }
++ Com_Memset( demo.prevEntities, 0, sizeof(demo.prevEntities) );
++ Com_Memset( demo.prevPlayers, 0, sizeof(demo.prevPlayers) );
++ demo.mapRestarted = qtrue; // signal next frame to write restart marker
++}
++
++void SVD_RecordFrame( void ) {
++ if ( !demo.recording ) {
++ return;
++ }
++
++ // periodic keyframe: reset delta state so this frame is decodable
++ // from baseline. record file offset for the keyframe index.
++ if ( demo.keyframeInterval > 0 ) {
++ demo.framesSinceKeyframe++;
++ if ( demo.framesSinceKeyframe >= demo.keyframeInterval ) {
++ demo.framesSinceKeyframe = 0;
++ demo.isKeyframe = qtrue;
++ Com_Memset( demo.prevEntities, 0, sizeof(demo.prevEntities) );
++ Com_Memset( demo.prevPlayers, 0, sizeof(demo.prevPlayers) );
++ // store keyframe: file offset before writing, serverTime
++ if ( demo.numKeyframes >= demo.maxKeyframes ) {
++ int newMax = demo.maxKeyframes ? demo.maxKeyframes * 2 : 256;
++ int *newTimes = Z_Malloc( newMax * sizeof(int) );
++ int *newOffsets = Z_Malloc( newMax * sizeof(int) );
++ if ( demo.keyframeTimes ) {
++ Com_Memcpy( newTimes, demo.keyframeTimes, demo.numKeyframes * sizeof(int) );
++ Com_Memcpy( newOffsets, demo.keyframeOffsets, demo.numKeyframes * sizeof(int) );
++ Z_Free( demo.keyframeTimes );
++ Z_Free( demo.keyframeOffsets );
++ }
++ demo.keyframeTimes = newTimes;
++ demo.keyframeOffsets = newOffsets;
++ demo.maxKeyframes = newMax;
++ }
++ demo.keyframeTimes[demo.numKeyframes] = svs.time;
++ demo.keyframeOffsets[demo.numKeyframes] = FS_FTell( demo.recordFile );
++ demo.numKeyframes++;
++ }
++ }
++
++ SVD_WriteFrame( demo.recordFile );
++}
++
++// ---------------------------------------------------------------
++// Playback: read header
++// ---------------------------------------------------------------
++
++static qboolean SVD_ReadHeader( fileHandle_t f ) {
++ int magic, version;
++
++ magic = SVD_ReadInt( f );
++ if ( magic != SVDEMO_MAGIC ) {
++ Com_Printf( "Not a valid server demo file.\n" );
++ return qfalse;
++ }
++
++ version = SVD_ReadInt( f );
++ if ( version != SVDEMO_VERSION ) {
++ Com_Printf( "Unsupported server demo version %d.\n", version );
++ return qfalse;
++ }
++
++ SVD_ReadInt( f ); // flags (reserved)
++
++ demo.playMaxClients = SVD_ReadInt( f );
++ demo.playFps = SVD_ReadInt( f );
++ FS_Read( demo.playMapName, SVDEMO_MAX_MAPNAME, f );
++ demo.playMapName[SVDEMO_MAX_MAPNAME - 1] = '\0';
++
++ // read configstrings — store for re-application after map load
++ {
++ short idx;
++ while (1) {
++ idx = SVD_ReadShort( f );
++ if ( idx == (short)0xFFFF ) {
++ break;
++ }
++ {
++ short len = SVD_ReadShort( f );
++ char buf[BIG_INFO_STRING];
++ if ( len > 0 && len < (short)sizeof(buf) ) {
++ FS_Read( buf, len, f );
++ buf[len - 1] = '\0';
++ if ( demo.savedConfigstrings[idx] ) {
++ Z_Free( demo.savedConfigstrings[idx] );
++ }
++ demo.savedConfigstrings[idx] = CopyString( buf );
++ }
++ }
++ }
++ }
++
++ return qtrue;
++}
++
++/*
++Re-apply recorded configstrings after the map has loaded.
++Called after devmap finishes in SVD_Play_f.
++*/
++static void SVD_ApplyConfigstrings( void ) {
++ int i;
++ for ( i = 0; i < MAX_CONFIGSTRINGS; i++ ) {
++ // skip CS_SERVERINFO and CS_SYSTEMINFO — they contain latched cvars
++ // (sv_maxclients, sv_pure, etc.) that would trigger a map restart
++ if ( i == CS_SERVERINFO || i == CS_SYSTEMINFO ) {
++ continue;
++ }
++ if ( demo.savedConfigstrings[i] && demo.savedConfigstrings[i][0] ) {
++ SV_SetConfigstring( i, demo.savedConfigstrings[i] );
++ }
++ }
++}
++
++// ---------------------------------------------------------------
++// Playback: read one frame, populate sv.gentities
++// ---------------------------------------------------------------
++
++static qboolean SVD_ReadFrame( fileHandle_t f ) {
++ int serverTime;
++ short numEnts, numChanges;
++ int i, entNum, blockLen;
++ sharedEntity_t *ent;
++ msg_t msg;
++ static byte msgBuf[MAX_GENTITIES * 300];
++ entityState_t newEs;
++
++ serverTime = SVD_ReadInt( f );
++ if ( serverTime == -1 ) {
++ return qfalse; // end of demo
++ }
++
++ // set svs.time to recorded time so entity trajectory interpolation
++ // works correctly (rockets, grenades, etc. use pos.trTime relative
++ // to server time). zombie timeout is already skipped during playback.
++ svs.time = serverTime;
++ numEnts = SVD_ReadShort( f );
++
++ // read frame flags
++ {
++ byte frameFlags;
++ FS_Read( &frameFlags, 1, f );
++ if ( frameFlags & 3 ) {
++ // bit 0 = map restart, bit 1 = keyframe.
++ // both mean: delta state was reset during recording,
++ // so reset playback delta state to decode from baseline.
++ Com_Memset( demo.playPrevEntities, 0, sizeof(demo.playPrevEntities) );
++ Com_Memset( demo.playPrevPlayers, 0, sizeof(demo.playPrevPlayers) );
++ }
++ if ( ( frameFlags & 1 ) || demo.seeked ) {
++ // map restart or seek: reset entity interpolation in cgame
++ svs.snapFlagServerBit |= SNAPFLAG_RESET_ENTITIES;
++ demo.seeked = qfalse;
++ }
++ }
++
++ // read entity message
++ blockLen = SVD_ReadInt( f );
++ if ( blockLen <= 0 || blockLen > (int)sizeof(msgBuf) ) {
++ return qfalse;
++ }
++ FS_Read( msgBuf, blockLen, f );
++ MSG_Init( &msg, msgBuf, sizeof(msgBuf) );
++ msg.cursize = blockLen;
++
++ // clear all entities (spectator's entity is recreated by ClientThink_real)
++ for ( i = 0; i < sv.num_entities; i++ ) {
++ ent = SV_GentityNum( i );
++ if ( ent->r.linked ) {
++ SV_UnlinkEntity( ent );
++ }
++ ent->s.eType = 0;
++ ent->s.number = i;
++ }
++
++ // parse delta-compressed entities
++ while ( 1 ) {
++ entNum = MSG_ReadBits( &msg, GENTITYNUM_BITS );
++ if ( entNum == MAX_GENTITIES - 1 ) {
++ break; // end of entities marker
++ }
++ if ( msg.readcount > msg.cursize ) {
++ break;
++ }
++
++ {
++ entityState_t baseline;
++ entityState_t *from;
++ Com_Memset( &newEs, 0, sizeof(newEs) );
++ if ( demo.playPrevEntities[entNum].active ) {
++ from = &demo.playPrevEntities[entNum].es;
++ } else {
++ Com_Memset( &baseline, 0, sizeof(baseline) );
++ baseline.number = entNum;
++ from = &baseline;
++ }
++ MSG_ReadDeltaEntity( &msg, from, &newEs, entNum );
++ }
++
++ if ( newEs.number == MAX_GENTITIES - 1 ) {
++ // entity was removed
++ demo.playPrevEntities[entNum].active = qfalse;
++ continue;
++ }
++
++ // read svFlags
++ if ( MSG_ReadBits( &msg, 1 ) ) {
++ demo.playPrevEntities[entNum].svFlags = MSG_ReadLong( &msg );
++ }
++
++ demo.playPrevEntities[entNum].es = newEs;
++ demo.playPrevEntities[entNum].active = qtrue;
++
++ // apply to server entity and link for PVS.
++ // use trBase as initial origin — G_RunFrame will refine with
++ // BG_EvaluateTrajectory for moving entities (rockets etc).
++ ent = SV_GentityNum( entNum );
++ ent->s = newEs;
++ ent->s.number = entNum;
++ ent->r.svFlags = demo.playPrevEntities[entNum].svFlags;
++ VectorCopy( newEs.pos.trBase, ent->r.currentOrigin );
++ ent->r.linked = qtrue;
++ SV_LinkEntity( ent );
++
++ if ( entNum + 1 > sv.num_entities ) {
++ sv.num_entities = entNum + 1;
++ }
++ }
++
++ // read player states (delta compressed)
++ {
++ msg_t psmsg;
++ static byte psBuf[MAX_CLIENTS * 600]; // worst case: full playerState from baseline
++ int psMsgLen;
++
++ psMsgLen = SVD_ReadInt( f );
++ if ( psMsgLen > 0 && psMsgLen <= (int)sizeof(psBuf) ) {
++ FS_Read( psBuf, psMsgLen, f );
++ MSG_Init( &psmsg, psBuf, sizeof(psBuf) );
++ psmsg.cursize = psMsgLen;
++
++ while ( 1 ) {
++ int clientNum = MSG_ReadBits( &psmsg, 6 );
++ int active = MSG_ReadBits( &psmsg, 1 );
++
++ if ( clientNum == MAX_CLIENTS - 1 && !active ) {
++ break; // terminator
++ }
++ if ( psmsg.readcount > psmsg.cursize ) {
++ break;
++ }
++
++ if ( active ) {
++ playerState_t newPs;
++ playerState_t *from;
++ playerState_t baseline;
++
++ if ( demo.playPrevPlayers[clientNum].active ) {
++ from = &demo.playPrevPlayers[clientNum].ps;
++ } else {
++ Com_Memset( &baseline, 0, sizeof(baseline) );
++ from = &baseline;
++ }
++
++ MSG_ReadDeltaPlayerstate( &psmsg, from, &newPs );
++ demo.playPrevPlayers[clientNum].ps = newPs;
++ demo.playPrevPlayers[clientNum].active = qtrue;
++
++ // inject into game module's client state
++ {
++ playerState_t *gamePs = SV_GameClientNum( clientNum );
++ *gamePs = newPs;
++ }
++ } else {
++ demo.playPrevPlayers[clientNum].active = qfalse;
++ // clear game playerState so G_RunFrame sees commandTime=0
++ {
++ playerState_t *gamePs = SV_GameClientNum( clientNum );
++ Com_Memset( gamePs, 0, sizeof(*gamePs) );
++ }
++ }
++ }
++ }
++ }
++
++ // read configstring changes (skip SERVERINFO/SYSTEMINFO to avoid latch restarts)
++ numChanges = SVD_ReadShort( f );
++ for ( i = 0; i < numChanges; i++ ) {
++ short idx = SVD_ReadShort( f );
++ short len = SVD_ReadShort( f );
++ char buf[BIG_INFO_STRING];
++ if ( len > 0 && len < (short)sizeof(buf) ) {
++ FS_Read( buf, len, f );
++ buf[len - 1] = '\0';
++ if ( idx != CS_SERVERINFO && idx != CS_SYSTEMINFO ) {
++ SV_SetConfigstring( idx, buf );
++ }
++ }
++ }
++
++ // read server commands (chat, prints, etc.) and replay to spectator
++ {
++ short numCmds = SVD_ReadShort( f );
++ for ( i = 0; i < numCmds; i++ ) {
++ short len = SVD_ReadShort( f );
++ char buf[SVD_MAX_SERVERCMD_LEN];
++ if ( len > 0 && len < (short)sizeof(buf) ) {
++ FS_Read( buf, len, f );
++ buf[len - 1] = '\0';
++ // broadcast — only the spectator is CS_ACTIVE, zombies are skipped
++ SV_SendServerCommand( NULL, "%s", buf );
++ }
++ }
++ }
++
++ return qtrue;
++}
++
++// ---------------------------------------------------------------
++// Playback commands
++// ---------------------------------------------------------------
++
++void SVD_Play_f( void ) {
++ char name[MAX_OSPATH];
++ char *s;
++ int len;
++
++ if ( demo.recording ) {
++ Com_Printf( "Stop recording first (svdemo_stop).\n" );
++ return;
++ }
++
++ s = Cmd_Argv(1);
++ if ( !s[0] ) {
++ Com_Printf( "Usage: svdemo_play \n" );
++ return;
++ }
++
++ // stop current playback if switching demos
++ if ( demo.playing ) {
++ SVD_CleanupPlayback();
++ }
++
++ Com_sprintf( name, sizeof(name), "svdemos/%s.svdm", s );
++
++ memset( &demo, 0, sizeof(demo) );
++
++ len = FS_FOpenFileRead( name, &demo.playFile, qtrue );
++ if ( !demo.playFile || len <= 0 ) {
++ Com_Printf( "ERROR: couldn't open %s.\n", name );
++ return;
++ }
++
++ if ( !SVD_ReadHeader( demo.playFile ) ) {
++ FS_FCloseFile( demo.playFile );
++ demo.playFile = 0;
++ return;
++ }
++
++ // read keyframe index from the end of the file.
++ // layout: [frames][-1][numKf][time0 off0 ...][numKf_copy]
++ // last 4 bytes of file = numKf_copy.
++ {
++ int frameStart = FS_FTell( demo.playFile );
++ int numKf, kf;
++
++ FS_Seek( demo.playFile, len - 4, FS_SEEK_SET );
++ numKf = SVD_ReadInt( demo.playFile );
++
++ if ( numKf > 0 && numKf < 1000000 ) {
++ // seek to start of keyframe table: end - 4 - numKf*8 - 4
++ int tableStart = len - 4 - numKf * 8 - 4;
++ FS_Seek( demo.playFile, tableStart + 4, FS_SEEK_SET ); // skip numKf
++
++ demo.numKeyframes = numKf;
++ demo.maxKeyframes = numKf;
++ demo.keyframeTimes = Z_Malloc( numKf * sizeof(int) );
++ demo.keyframeOffsets = Z_Malloc( numKf * sizeof(int) );
++
++ for ( kf = 0; kf < numKf; kf++ ) {
++ demo.keyframeTimes[kf] = SVD_ReadInt( demo.playFile );
++ demo.keyframeOffsets[kf] = SVD_ReadInt( demo.playFile );
++ }
++ Com_Printf( "Loaded %d keyframes.\n", numKf );
++ }
++
++ // seek back to start of frame data
++ FS_Seek( demo.playFile, frameStart, FS_SEEK_SET );
++ }
++
++ demo.playing = qtrue;
++ demo.endOfDemo = qfalse;
++ demo.needConfigstrings = qtrue;
++
++ Com_Printf( "Playing server demo: map=%s maxclients=%d fps=%d\n",
++ demo.playMapName, demo.playMaxClients, demo.playFps );
++
++ // Shut down current server first so no clients carry over to
++ // reserved slots. SV_Shutdown triggers our cleanup hook, but
++ // demo.starting prevents it from clearing our state.
++ demo.starting = qtrue;
++ if ( com_sv_running->integer ) {
++ SV_Shutdown( "Demo playback\n" );
++ }
++
++ // Set demo cvar BEFORE devmap so G_InitGame can read it.
++ // The previous server is gone so no old game module to conflict with.
++ Cvar_Set2( "sv_demoplaying", "1", qtrue );
++
++ Cbuf_ExecuteText( EXEC_NOW, va("set sv_maxclients %d\n", MAX_CLIENTS) );
++ Cbuf_ExecuteText( EXEC_NOW, va("set sv_fps %d\n", demo.playFps) );
++ Cbuf_ExecuteText( EXEC_NOW, va("devmap %s\n", demo.playMapName) );
++ demo.starting = qfalse;
++
++ // CS_SVDEMO configstring is set by G_InitGame from the cvar
++
++ // Reserve recorded player slots. Server is fresh (SV_Shutdown cleared
++ // old clients), local client hasn't connected yet.
++ {
++ int i;
++ for ( i = 0; i < demo.playMaxClients; i++ ) {
++ if ( svs.clients[i].state == CS_FREE ) {
++ svs.clients[i].state = CS_ZOMBIE;
++ svs.clients[i].rate = 10000;
++ svs.clients[i].nextSnapshotTime = 0x7FFFFFFF;
++ svs.clients[i].lastPacketTime = svs.time;
++ }
++ }
++ }
++}
++
++void SVD_CleanupPlayback( void ) {
++ int i;
++
++ if ( !demo.playing ) {
++ return;
++ }
++
++ FS_FCloseFile( demo.playFile );
++
++ // free saved configstrings
++ for ( i = 0; i < MAX_CONFIGSTRINGS; i++ ) {
++ if ( demo.savedConfigstrings[i] ) {
++ Z_Free( demo.savedConfigstrings[i] );
++ }
++ }
++
++ // free zombie client slots
++ for ( i = 0; i < demo.playMaxClients; i++ ) {
++ if ( svs.clients[i].state == CS_ZOMBIE ) {
++ svs.clients[i].state = CS_FREE;
++ }
++ }
++
++ // free keyframe index
++ if ( demo.keyframeTimes ) { Z_Free( demo.keyframeTimes ); }
++ if ( demo.keyframeOffsets ) { Z_Free( demo.keyframeOffsets ); }
++
++ memset( &demo, 0, sizeof(demo) );
++ Cvar_Set2( "sv_demoplaying", "0", qtrue );
++}
++
++void SVD_StopPlay_f( void ) {
++ if ( !demo.playing ) {
++ Com_Printf( "Not playing a server demo.\n" );
++ return;
++ }
++
++ SVD_CleanupPlayback();
++ Com_Printf( "Server demo playback stopped.\n" );
++
++ // disconnect to return to main menu
++ Cbuf_ExecuteText( EXEC_APPEND, "disconnect\n" );
++}
++
++/*
++Unified stop command: stops recording or playback, whichever is active.
++*/
++void SVD_Stop_f( void ) {
++ if ( demo.recording ) {
++ SVD_StopRecord_f();
++ } else if ( demo.playing ) {
++ SVD_StopPlay_f();
++ } else {
++ Com_Printf( "Not recording or playing a server demo.\n" );
++ }
++}
++
++void SVD_Pause_f( void ) {
++ if ( !demo.playing ) {
++ Com_Printf( "Not playing a server demo.\n" );
++ return;
++ }
++ demo.paused = !demo.paused;
++ if ( !demo.paused ) {
++ // resuming — toggle SERVERCOUNT to reset client snapshot timing
++ // (drifted during pause from identical serverTimes).
++ svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT;
++ }
++ Com_Printf( "Demo playback %s.\n", demo.paused ? "paused" : "resumed" );
++}
++
++void SVD_Seek_f( void ) {
++ int targetTime, i, bestKf;
++ float seconds;
++
++ if ( !demo.playing ) {
++ Com_Printf( "Not playing a server demo.\n" );
++ return;
++ }
++
++ if ( Cmd_Argc() < 2 ) {
++ Com_Printf( "Usage: svdemo_seek \n" );
++ return;
++ }
++
++ if ( demo.numKeyframes <= 0 ) {
++ Com_Printf( "No keyframes in this demo — seeking not available.\n" );
++ return;
++ }
++
++ seconds = atof( Cmd_Argv(1) );
++ targetTime = svs.time + (int)(seconds * 1000);
++
++ // find nearest keyframe at or before target time
++ bestKf = -1;
++ for ( i = 0; i < demo.numKeyframes; i++ ) {
++ if ( demo.keyframeTimes[i] <= targetTime ) {
++ bestKf = i;
++ } else {
++ break;
++ }
++ }
++
++ if ( bestKf < 0 ) {
++ // target is before the first keyframe — seek to first
++ bestKf = 0;
++ targetTime = demo.keyframeTimes[0];
++ }
++
++ // seek to keyframe file position
++ FS_Seek( demo.playFile, demo.keyframeOffsets[bestKf], FS_SEEK_SET );
++
++ // reset delta state (keyframe is encoded from baseline)
++ Com_Memset( demo.playPrevEntities, 0, sizeof(demo.playPrevEntities) );
++ Com_Memset( demo.playPrevPlayers, 0, sizeof(demo.playPrevPlayers) );
++
++ // set svs.time to the keyframe time so the SV_Frame loop
++ // doesn't advance from the old time before reading
++ svs.time = demo.keyframeTimes[bestKf];
++
++ // toggle SERVERCOUNT to reset client time delta
++ svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT;
++
++ demo.seeked = qtrue;
++ demo.endOfDemo = qfalse;
++
++ // read the keyframe directly (works even when paused)
++ svs.snapFlagServerBit &= ~SNAPFLAG_RESET_ENTITIES;
++ if ( !SVD_ReadFrame( demo.playFile ) ) {
++ demo.endOfDemo = qtrue;
++ }
++
++ // reset client snapshot timing
++ {
++ int j;
++ for ( j = 0; j < sv_maxclients->integer; j++ ) {
++ if ( svs.clients[j].state >= CS_ACTIVE ) {
++ svs.clients[j].nextSnapshotTime = svs.time;
++ }
++ }
++ }
++
++ // ensure one frame runs on next SV_Frame (for G_RunFrame + snapshot)
++ sv.timeResidual = 1000 / sv_fps->integer;
++
++ Com_Printf( "Seeked to time %d.\n", svs.time );
++}
++
++void SVD_SeekExact_f( void ) {
++ int targetTime, i, bestKf;
++ float seconds;
++
++ if ( !demo.playing ) {
++ Com_Printf( "Not playing a server demo.\n" );
++ return;
++ }
++
++ if ( Cmd_Argc() < 2 ) {
++ Com_Printf( "Usage: svdemo_seekexact \n" );
++ return;
++ }
++
++ if ( demo.numKeyframes <= 0 ) {
++ Com_Printf( "No keyframes in this demo.\n" );
++ return;
++ }
++
++ seconds = atof( Cmd_Argv(1) );
++ targetTime = svs.time + (int)(seconds * 1000);
++
++ // find nearest keyframe at or before target time
++ bestKf = -1;
++ for ( i = 0; i < demo.numKeyframes; i++ ) {
++ if ( demo.keyframeTimes[i] <= targetTime ) {
++ bestKf = i;
++ } else {
++ break;
++ }
++ }
++
++ if ( bestKf < 0 ) {
++ bestKf = 0;
++ targetTime = demo.keyframeTimes[0];
++ }
++
++ // seek to keyframe
++ FS_Seek( demo.playFile, demo.keyframeOffsets[bestKf], FS_SEEK_SET );
++ Com_Memset( demo.playPrevEntities, 0, sizeof(demo.playPrevEntities) );
++ Com_Memset( demo.playPrevPlayers, 0, sizeof(demo.playPrevPlayers) );
++ svs.time = demo.keyframeTimes[bestKf];
++ svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT;
++ demo.seeked = qtrue;
++ demo.endOfDemo = qfalse;
++
++ // read forward from keyframe to target time
++ while ( svs.time < targetTime ) {
++ if ( !SVD_ReadFrame( demo.playFile ) ) {
++ demo.endOfDemo = qtrue;
++ break;
++ }
++ }
++
++ // reset client snapshot timing
++ {
++ int j;
++ for ( j = 0; j < sv_maxclients->integer; j++ ) {
++ if ( svs.clients[j].state >= CS_ACTIVE ) {
++ svs.clients[j].nextSnapshotTime = svs.time;
++ }
++ }
++ }
++
++ sv.timeResidual = 1000 / sv_fps->integer;
++
++ Com_Printf( "Seeked to time %d (read forward %d ms from keyframe).\n",
++ svs.time, svs.time - demo.keyframeTimes[bestKf] );
++}
++
++/*
++Called from SV_Frame() to advance playback by one frame.
++Returns qtrue if a frame was read, qfalse if demo ended.
++*/
++qboolean SVD_PlaybackFrame( void ) {
++ if ( !demo.playing || demo.endOfDemo ) {
++ return qfalse;
++ }
++
++
++ // manual pause — don't consume demo data
++ if ( demo.paused ) {
++ return qfalse;
++ }
++
++ // wait for a spectator to be fully in-game before starting playback.
++ // the server keeps running frames (so the connection handshake completes)
++ // but no demo data is consumed until someone is CS_ACTIVE.
++ if ( SVD_ShouldPause() ) {
++ return qfalse;
++ }
++
++ // apply recorded configstrings once after map load
++ if ( demo.needConfigstrings ) {
++ SVD_ApplyConfigstrings();
++ demo.needConfigstrings = qfalse;
++ }
++
++ // clear one-shot reset flag from previous frame before reading new one
++ svs.snapFlagServerBit &= ~SNAPFLAG_RESET_ENTITIES;
++
++ if ( !SVD_ReadFrame( demo.playFile ) ) {
++ Com_Printf( "Server demo playback finished.\n" );
++ SVD_CleanupPlayback();
++ Cbuf_ExecuteText( EXEC_APPEND, "disconnect\n" );
++ return qfalse;
++ }
++
++ return qtrue;
++}
++
++// ---------------------------------------------------------------
++// Queries
++// ---------------------------------------------------------------
++
++qboolean SVD_IsRecording( void ) {
++ return demo.recording;
++}
++
++qboolean SVD_IsPlaying( void ) {
++ return demo.playing;
++}
++
++qboolean SVD_IsPaused( void ) {
++ return demo.playing && demo.paused;
++}
++
++qboolean SVD_IsStarting( void ) {
++ return demo.starting;
++}
++
++
++/*
++Returns qtrue if demo playback should pause (no active spectators).
++Controlled by svdemo_pauseEmpty cvar.
++*/
++qboolean SVD_ShouldPause( void ) {
++ int i;
++
++ if ( !Cvar_VariableIntegerValue( "svdemo_pauseEmpty" ) ) {
++ return qfalse;
++ }
++
++ for ( i = 0; i < sv_maxclients->integer; i++ ) {
++ if ( svs.clients[i].state == CS_ACTIVE ) {
++ return qfalse; // someone is in-game and watching
++ }
++ }
++
++ return qtrue; // nobody connected, pause
++}
++
+diff --git a/svdemo.txt b/svdemo.txt
+new file mode 100644
+index 0000000..10e2480
+--- /dev/null
++++ b/svdemo.txt
+@@ -0,0 +1,196 @@
++===========================================================================
++ Серверные демо-записи (SVDEMO)
++ Руководство пользователя
++===========================================================================
++
++ОПИСАНИЕ
++--------
++
++Система серверных демо-записей позволяет записывать полное состояние
++игрового сервера (все сущности, все игроки) и воспроизводить запись
++с возможностью свободного перемещения камеры или просмотра от первого
++лица любого игрока.
++
++В отличие от обычных клиентских демо (запись только того, что видит
++один игрок), серверная запись содержит полную картину матча.
++
++
++КОНСОЛЬНЫЕ КОМАНДЫ
++------------------
++
++Запись:
++
++ svdemo_record <имя>
++ Начать запись серверного демо. Файл сохраняется в
++ svdemos/<имя>.svdm внутри игровой директории.
++
++ svdemo_stop
++ Остановить текущую запись или воспроизведение.
++ При остановке воспроизведения происходит отключение от сервера.
++
++Воспроизведение:
++
++ svdemo_play <имя>
++ Воспроизвести серверное демо. Загружает карту из записи,
++ подключает зрителя автоматически. Можно вызывать из меню,
++ из игры или во время просмотра другого демо.
++
++ svdemo_pause
++ Пауза / продолжение воспроизведения. Во время паузы камера
++ свободно перемещается, сущности заморожены.
++ На экране отображается "Playback Paused".
++
++ svdemo_seek <секунды>
++ Перемотка относительно текущей позиции. Положительное значение --
++ вперёд, отрицательное -- назад.
++ Перемещает к ближайшему ключевому кадру (точность ±5 сек).
++ Работает в паузе.
++ Пример: svdemo_seek -10 (назад на 10 секунд)
++
++ svdemo_seekexact <секунды>
++ Точная перемотка. Находит ближайший ключевой кадр, затем
++ прочитывает кадры до целевого времени. Точность до одного
++ серверного кадра (50 мс при sv_fps 20). Может занять долю
++ секунды на длинных перемотках. Работает в паузе.
++ Пример: svdemo_seekexact -10 (назад ровно на 10 секунд)
++
++
++НАСТРОЙКИ (CVARS)
++-----------------
++
++ svdemo_autorecord <0|1> (по умолчанию: 0)
++ Автоматическая запись демо при каждой загрузке карты.
++ Файлы именуются автоматически: <карта>_ГГГГММДД_ЧЧММСС.svdm
++ Пример: q3dm6_20260323_141530.svdm
++
++ svdemo_pauseEmpty <0|1> (по умолчанию: 1)
++ Пауза воспроизведения, когда нет подключённых зрителей.
++ Демо начинается с первого кадра при подключении зрителя.
++
++ svdemo_keyframeInterval <секунды> (по умолчанию: 5, 0 = выкл.)
++ Интервал ключевых кадров. Ключевые кадры позволяют перематывать
++ запись. Чем меньше интервал, тем точнее перемотка командой
++ svdemo_seek, но немного больше размер файла. Первый кадр
++ записи всегда является ключевым.
++
++ Все настройки сохраняются в конфигурации (CVAR_ARCHIVE).
++
++
++ЗАПИСЬ
++------
++
++1. Запустите сервер и начните игру как обычно:
++ devmap q3dm6
++
++2. Начните запись:
++ svdemo_record mymatch
++
++3. Играйте. Все действия всех игроков записываются.
++
++4. Остановите запись:
++ svdemo_stop
++
++ Запись также автоматически останавливается при:
++ - Смене карты (map, devmap, nextmap по таймлимиту/фраглимиту)
++ - Выключении сервера
++ Перезапуск карты (map_restart) НЕ прерывает запись.
++
++
++ВОСПРОИЗВЕДЕНИЕ
++---------------
++
++1. Запустите воспроизведение:
++ svdemo_play mymatch
++
++2. Карта загрузится автоматически. Вы подключитесь как зритель
++ со свободной камерой (полёт по карте).
++
++3. Управление зрителем:
++ - Свободная камера: перемещайтесь как обычный спектатор
++ - Следование за игроком: нажмите MOUSE1 (ATTACK) для входа
++ в режим следования и переключения между игроками
++ - Выход из следования: team spectator (можно забиндить)
++
++4. В режиме следования вы видите игру от первого лица выбранного
++ игрока с полным HUD: здоровье, броня, боеприпасы, оружие.
++
++5. Табло (TAB) показывает счёт записанных игроков.
++
++6. Управление воспроизведением:
++ - svdemo_pause -- пауза/продолжение
++ - svdemo_seek -10 -- быстрая перемотка назад на 10 сек
++ - svdemo_seek 30 -- быстрая перемотка вперёд на 30 сек
++ - svdemo_seekexact -10 -- точная перемотка назад на 10 сек
++ - svdemo_stop -- остановка и выход
++
++7. Воспроизведение останавливается автоматически при
++ достижении конца записи.
++
++
++ФОРМАТ ФАЙЛА
++-------------
++
++Расширение: .svdm
++Директория: svdemos/
++
++Файл содержит:
++- Заголовок: название карты, настройки сервера, конфигстроки
++ (имена игроков, модели, настройки игры)
++- Покадровые данные: дельта-сжатые состояния сущностей и игроков,
++ серверные команды (чат, принты), изменения конфигстрок
++- Ключевые кадры с индексом в конце файла
++
++Дельта-кодирование обеспечивает компактный размер файла.
++Типичный 10-минутный матч с 10 ботами занимает ~8 МБ.
++
++Одна запись = одна карта. При смене карты запись останавливается.
++
++
++ОГРАНИЧЕНИЯ
++-----------
++
++- Если в записанной игре было 64 игрока (MAX_CLIENTS), один
++ из них не будет виден при воспроизведении (его слот занят зрителем).
++- Воспроизведение требует наличия тех же pk3-файлов (карты, модели),
++ что использовались при записи.
++- Демо несовместимы между разными версиями движка, если изменился
++ формат сетевых структур.
++- При перемотке могут кратковременно отображаться визуальные артефакты
++ (частицы, вспышки) от локальных эффектов.
++
++
++ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ
++---------------------
++
++Автоматическая запись всех матчей:
++
++ set svdemo_autorecord 1
++ devmap q3dm17
++
++ (все матчи на этом сервере будут записываться автоматически)
++
++
++Запись конкретного матча:
++
++ devmap q3tourney2
++ svdemo_record duel_finals
++ (играть...)
++ svdemo_stop
++
++
++Просмотр записи:
++
++ svdemo_play duel_finals
++
++
++Просмотр с перемоткой:
++
++ svdemo_play duel_finals
++ (подождать 30 секунд)
++ svdemo_seekexact -20 (вернуться на 20 секунд назад)
++ svdemo_pause (поставить на паузу)
++ svdemo_seek -5 (ещё назад на 5 секунд, в паузе)
++ svdemo_pause (продолжить)
++
++
++===========================================================================
diff --git a/netdemo.patch b/netdemo.patch
new file mode 100644
index 0000000..6a31b35
--- /dev/null
+++ b/netdemo.patch
@@ -0,0 +1,1593 @@
+diff --git a/code/cgame/cg_snapshot.c b/code/cgame/cg_snapshot.c
+index add49f0..6bc3a0c 100644
+--- a/code/cgame/cg_snapshot.c
++++ b/code/cgame/cg_snapshot.c
+@@ -203,6 +203,15 @@ static void CG_SetNextSnap( snapshot_t *snap ) {
+
+ cg.nextSnap = snap;
+
++ // SNAPFLAG_RESET_ENTITIES: invalidate all entities so they are
++ // treated as new (no interpolation from old positions).
++ // Must happen before the entity loop below.
++ if ( snap->snapFlags & SNAPFLAG_RESET_ENTITIES ) {
++ for ( num = 0 ; num < MAX_GENTITIES ; num++ ) {
++ cg_entities[ num ].currentValid = qfalse;
++ }
++ }
++
+ BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].nextState, qfalse );
+ cg_entities[ cg.snap->ps.clientNum ].interpolate = qtrue;
+
+@@ -241,6 +250,11 @@ static void CG_SetNextSnap( snapshot_t *snap ) {
+ cg.nextFrameTeleport = qtrue;
+ }
+
++ // entity reset also prevents playerstate interpolation
++ if ( cg.nextSnap->snapFlags & SNAPFLAG_RESET_ENTITIES ) {
++ cg.nextFrameTeleport = qtrue;
++ }
++
+ // sort out solid entities
+ CG_BuildSolidList();
+ }
+diff --git a/code/game/g_client.c b/code/game/g_client.c
+index 5a584d4..719e382 100644
+--- a/code/game/g_client.c
++++ b/code/game/g_client.c
+@@ -1012,6 +1012,13 @@ void ClientBegin( int clientNum ) {
+ client->pers.enterTime = level.time;
+ client->pers.teamState.state = TEAM_BEGIN;
+
++ // demo playback: force all clients to spectator
++ // demo playback: force to spectator and update configstring
++ if ( g_svDemoPlaying.integer ) {
++ client->sess.sessionTeam = TEAM_SPECTATOR;
++ ClientUserinfoChanged( ent->client - level.clients );
++ }
++
+ // save eflags around this, because changing teams will
+ // cause this to happen with a valid entity, and we
+ // want to make sure the teleport bit is set right
+diff --git a/code/game/g_cmds.c b/code/game/g_cmds.c
+index e72c80e..20e7b58 100644
+--- a/code/game/g_cmds.c
++++ b/code/game/g_cmds.c
+@@ -661,6 +661,12 @@ void Cmd_Team_f( gentity_t *ent ) {
+ int oldTeam;
+ char s[MAX_TOKEN_CHARS];
+
++ // demo playback: spectators only
++ if ( trap_Cvar_VariableIntegerValue( "sv_demoplaying" ) ) {
++ trap_SendServerCommand( ent-g_entities, "print \"Team changes disabled during demo playback.\n\"" );
++ return;
++ }
++
+ if ( trap_Argc() != 2 ) {
+ oldTeam = ent->client->sess.sessionTeam;
+ switch ( oldTeam ) {
+diff --git a/code/game/g_local.h b/code/game/g_local.h
+index 1a55955..057722c 100644
+--- a/code/game/g_local.h
++++ b/code/game/g_local.h
+@@ -641,6 +641,7 @@ void ClientCommand( int clientNum );
+ // g_active.c
+ //
+ void ClientThink( int clientNum );
++void ClientThink_real( gentity_t *ent );
+ void ClientEndFrame( gentity_t *ent );
+ void G_RunClient( gentity_t *ent );
+
+@@ -715,6 +716,7 @@ extern gentity_t g_entities[MAX_GENTITIES];
+ #define FOFS(x) ((int)&(((gentity_t *)0)->x))
+
+ extern vmCvar_t g_gametype;
++extern vmCvar_t g_svDemoPlaying;
+ extern vmCvar_t g_dedicated;
+ extern vmCvar_t g_cheats;
+ extern vmCvar_t g_maxclients; // allow this many total, including spectators
+diff --git a/code/game/g_main.c b/code/game/g_main.c
+index 9f60272..a468854 100644
+--- a/code/game/g_main.c
++++ b/code/game/g_main.c
+@@ -39,6 +39,7 @@ gentity_t g_entities[MAX_GENTITIES];
+ gclient_t g_clients[MAX_CLIENTS];
+
+ vmCvar_t g_gametype;
++vmCvar_t g_svDemoPlaying;
+ vmCvar_t g_dmflags;
+ vmCvar_t g_fraglimit;
+ vmCvar_t g_timelimit;
+@@ -108,6 +109,7 @@ static cvarTable_t gameCvarTable[] = {
+
+ // latched vars
+ { &g_gametype, "g_gametype", "0", CVAR_SERVERINFO | CVAR_USERINFO | CVAR_LATCH, 0, qfalse },
++ { &g_svDemoPlaying, "sv_demoplaying", "0", CVAR_ROM, 0, qfalse },
+
+ { &g_maxclients, "sv_maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qfalse },
+ { &g_maxGameClients, "g_maxGameClients", "0", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qfalse },
+@@ -764,6 +766,10 @@ void CalculateRanks( void ) {
+ int rank;
+ int score;
+ int newScore;
++
++ // (demo playback note: this runs normally so the spectator
++ // appears in sortedClients. Recorded players won't show here
++ // since they're not connected in the game module.)
+ gclient_t *cl;
+
+ level.follow1 = -1;
+@@ -1729,6 +1735,51 @@ int start, end;
+ // get any cvar changes
+ G_UpdateCvars();
+
++ // demo playback: sync recorded player states and process spectator
++ if ( g_svDemoPlaying.integer ) {
++ gentity_t *specEnt = NULL;
++
++ // mark recorded players as connected based on their playerState.
++ // the server injected playerStates via SV_GameClientNum before
++ // calling G_RunFrame, so g_clients[i].ps is already populated.
++ for ( i = 0; i < level.maxclients; i++ ) {
++ gclient_t *cl = &level.clients[i];
++ gentity_t *e = &g_entities[i];
++
++ // find and process the spectator
++ if ( e->client && cl->pers.connected == CON_CONNECTED
++ && cl->sess.sessionTeam == TEAM_SPECTATOR ) {
++ ClientThink_real( e );
++ specEnt = e;
++ continue;
++ }
++
++ // check if server injected a valid playerState for this slot
++ if ( cl->ps.commandTime > 0 ) {
++ cl->pers.connected = CON_CONNECTED;
++ cl->sess.sessionTeam = cl->ps.persistant[PERS_TEAM];
++ e->inuse = qtrue;
++ e->client = cl;
++ e->s.clientNum = i;
++ e->s.number = i;
++ } else if ( cl->pers.connected == CON_CONNECTED
++ && cl->sess.sessionTeam != TEAM_SPECTATOR ) {
++ // player left — mark disconnected
++ cl->pers.connected = CON_DISCONNECTED;
++ e->inuse = qfalse;
++ }
++ }
++
++ // update rankings so follow mode can cycle through players
++ CalculateRanks();
++
++ // run end-of-frame for spectator (handles follow mode PS copy)
++ if ( specEnt ) {
++ ClientEndFrame( specEnt );
++ }
++ return;
++ }
++
+ //
+ // go through all allocated objects
+ //
+diff --git a/code/game/q_shared.h b/code/game/q_shared.h
+index 99e5a67..4834c94 100644
+--- a/code/game/q_shared.h
++++ b/code/game/q_shared.h
+@@ -1085,6 +1085,7 @@ typedef enum {
+ #define SNAPFLAG_RATE_DELAYED 1
+ #define SNAPFLAG_NOT_ACTIVE 2 // snapshot used during connection and for zombies
+ #define SNAPFLAG_SERVERCOUNT 4 // toggled every map_restart so transitions can be detected
++#define SNAPFLAG_RESET_ENTITIES 16 // snap all entities to current position, no interpolation
+
+ //
+ // per-level limits
+diff --git a/code/quake3.vcxproj b/code/quake3.vcxproj
+index 340ba18..f850f17 100644
+--- a/code/quake3.vcxproj
++++ b/code/quake3.vcxproj
+@@ -995,6 +995,10 @@
+ MaxSpeed
+ MaxSpeed
+
++
++
++
++
+
+ Disabled
+ true
+diff --git a/code/server/server.h b/code/server/server.h
+index c32b2dc..02f8ae8 100644
+--- a/code/server/server.h
++++ b/code/server/server.h
+@@ -341,6 +341,23 @@ int SV_BotGetConsoleMessage( int client, char *buf, int size );
+ int BotImport_DebugPolygonCreate(int color, int numPoints, vec3_t *points);
+ void BotImport_DebugPolygonDelete(int id);
+
++//
++// sv_netdemo.c
++//
++void SVD_Record_f( void );
++void SVD_StopRecord_f( void );
++void SVD_RecordFrame( void );
++void SVD_ResetDeltaState( void );
++void SVD_AutoRecord( void );
++void SVD_CaptureServerCommand( const char *cmd );
++void SVD_Play_f( void );
++void SVD_StopPlay_f( void );
++void SVD_Stop_f( void );
++qboolean SVD_PlaybackFrame( void );
++qboolean SVD_IsRecording( void );
++qboolean SVD_IsPlaying( void );
++qboolean SVD_ShouldPause( void );
++
+ //============================================================
+ //
+ // high level object sorting to reduce interaction tests
+diff --git a/code/server/sv_ccmds.c b/code/server/sv_ccmds.c
+index 62ae179..b55d15f 100644
+--- a/code/server/sv_ccmds.c
++++ b/code/server/sv_ccmds.c
+@@ -280,6 +280,9 @@ static void SV_MapRestart_f( void ) {
+ sv.state = SS_GAME;
+ sv.restarting = qfalse;
+
++ // reset demo delta state so next frame writes full entities
++ SVD_ResetDeltaState();
++
+ // connect and begin all the clients
+ for (i=0 ; iinteger ; i++) {
+ client = &svs.clients[i];
+@@ -736,6 +739,11 @@ void SV_AddOperatorCommands( void ) {
+ if( com_dedicated->integer ) {
+ Cmd_AddCommand ("say", SV_ConSay_f);
+ }
++
++ // server-side demo recording/playback
++ Cmd_AddCommand ("svdemo_record", SVD_Record_f);
++ Cmd_AddCommand ("svdemo_stop", SVD_Stop_f);
++ Cmd_AddCommand ("svdemo_play", SVD_Play_f);
+ }
+
+ /*
+diff --git a/code/server/sv_game.c b/code/server/sv_game.c
+index 202994e..b227f3c 100644
+--- a/code/server/sv_game.c
++++ b/code/server/sv_game.c
+@@ -83,13 +83,20 @@ Sends a command string to a client
+ ===============
+ */
+ void SV_GameSendServerCommand( int clientNum, const char *text ) {
++ // capture for demo recording: broadcasts and per-client chat/tchat
++ if ( clientNum == -1 ) {
++ SVD_CaptureServerCommand( text );
++ } else if ( !strncmp( text, "chat", 4 ) || !strncmp( text, "tchat", 5 ) ) {
++ SVD_CaptureServerCommand( text );
++ }
++
+ if ( clientNum == -1 ) {
+ SV_SendServerCommand( NULL, "%s", text );
+ } else {
+ if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) {
+ return;
+ }
+- SV_SendServerCommand( svs.clients + clientNum, "%s", text );
++ SV_SendServerCommand( svs.clients + clientNum, "%s", text );
+ }
+ }
+
+diff --git a/code/server/sv_init.c b/code/server/sv_init.c
+index b3d0c4d..e1ffb4b 100644
+--- a/code/server/sv_init.c
++++ b/code/server/sv_init.c
+@@ -351,6 +351,12 @@ void SV_SpawnServer( char *server, qboolean killBots ) {
+ char systemInfo[16384];
+ const char *p;
+
++ // stop any active demo recording (one demo = one map)
++ if ( SVD_IsRecording() ) {
++ Com_Printf( "Map change — stopping demo recording.\n" );
++ SVD_StopRecord_f();
++ }
++
+ // shut down the existing game if it is running
+ SV_ShutdownGameProgs();
+
+@@ -546,6 +552,9 @@ void SV_SpawnServer( char *server, qboolean killBots ) {
+ Hunk_SetMark();
+
+ Com_Printf ("-----------------------------------\n");
++
++ // auto-record demo if enabled
++ SVD_AutoRecord();
+ }
+
+ /*
+@@ -612,6 +621,11 @@ void SV_Init (void) {
+ sv_lanForceRate = Cvar_Get ("sv_lanForceRate", "1", CVAR_ARCHIVE );
+ sv_strictAuth = Cvar_Get ("sv_strictAuth", "1", CVAR_ARCHIVE );
+
++ // server-side demo settings
++ Cvar_Get ("svdemo_autorecord", "0", CVAR_ARCHIVE);
++ Cvar_Get ("svdemo_compress", "1", CVAR_ARCHIVE);
++ Cvar_Get ("svdemo_pauseEmpty", "1", CVAR_ARCHIVE);
++
+ // initialize bot cvars so they are listed and can be set before loading the botlib
+ SV_BotInitCvars();
+
+@@ -667,6 +681,14 @@ void SV_Shutdown( char *finalmsg ) {
+
+ Com_Printf( "----- Server Shutdown -----\n" );
+
++ // clean up any active demo recording/playback
++ if ( SVD_IsRecording() ) {
++ SVD_StopRecord_f();
++ }
++ if ( SVD_IsPlaying() ) {
++ SVD_StopPlay_f();
++ }
++
+ if ( svs.clients && !com_errorEntered ) {
+ SV_FinalMessage( finalmsg );
+ }
+diff --git a/code/server/sv_main.c b/code/server/sv_main.c
+index 98c74ce..da5e89a 100644
+--- a/code/server/sv_main.c
++++ b/code/server/sv_main.c
+@@ -833,16 +833,27 @@ void SV_Frame( int msec ) {
+ sv.timeResidual -= frameMsec;
+ svs.time += frameMsec;
+
++ if ( SVD_IsPlaying() ) {
++ // demo playback: read recorded entities instead of running game logic
++ SVD_PlaybackFrame();
++ // still call the game frame for spectator movement
++ }
++
+ // let everything in the world think and move
+ VM_Call( gvm, GAME_RUN_FRAME, svs.time );
++
++ // capture frame for demo recording
++ SVD_RecordFrame();
+ }
+
+ if ( com_speeds->integer ) {
+ time_game = Sys_Milliseconds () - startTime;
+ }
+
+- // check timeouts
+- SV_CheckTimeouts();
++ // check timeouts (skip during demo playback — zombie slots would be freed)
++ if ( !SVD_IsPlaying() ) {
++ SV_CheckTimeouts();
++ }
+
+ // send messages back to the clients
+ SV_SendClientMessages();
+diff --git a/code/server/sv_netdemo.c b/code/server/sv_netdemo.c
+new file mode 100644
+index 0000000..f87afc3
+--- /dev/null
++++ b/code/server/sv_netdemo.c
+@@ -0,0 +1,1056 @@
++/*
++===========================================================================
++Server-side demo recording and playback (netdemo).
++
++Records the full entity state array each server frame so that
++demos can be played back from any viewpoint. During playback the
++recorded entities are injected into sv.gentities and the normal
++snapshot pipeline delivers them to a spectator client.
++===========================================================================
++*/
++
++#include "server.h"
++#include "lz4.h"
++
++// ---------------------------------------------------------------
++// File format
++// ---------------------------------------------------------------
++//
++// Header:
++// 4 bytes magic "SVDM"
++// 4 bytes version (1)
++// 4 bytes original sv_maxclients
++// 4 bytes original sv_fps
++// 64 bytes map name (null-padded)
++// Then: configstrings block
++// for each non-empty configstring:
++// 2 bytes index
++// 2 bytes string length (incl NUL)
++// N bytes string data
++// 2 bytes index = 0xFFFF (terminator)
++//
++// Per frame:
++// 4 bytes serverTime
++// 2 bytes numEntities (how many entity records follow)
++// for each entity:
++// 2 bytes entity number
++// entityState_t (fixed size, raw)
++// 4 bytes svFlags
++// 4 bytes linked
++// 12 bytes currentOrigin[3]
++// 12 bytes absmin[3]
++// 12 bytes absmax[3]
++// 2 bytes numConfigChanges
++// for each change:
++// 2 bytes index
++// 2 bytes string length (incl NUL)
++// N bytes string data
++//
++// Footer:
++// 4 bytes serverTime = -1 (end marker)
++//
++
++#define SVDEMO_MAGIC (('S') | ('V' << 8) | ('D' << 16) | ('M' << 24))
++#define SVDEMO_VERSION 2 // v2: optional LZ4 compression
++#define SVDEMO_MAX_MAPNAME 64
++
++// header flags
++#define SVDEMO_FLAG_COMPRESSED 1 // per-frame data is LZ4 compressed
++
++// ---------------------------------------------------------------
++// State
++// ---------------------------------------------------------------
++
++// per-entity data stored for delta compression (entityState + PVS fields)
++typedef struct {
++ entityState_t es;
++ int svFlags;
++ qboolean linked;
++ vec3_t currentOrigin;
++ vec3_t absmin;
++ vec3_t absmax;
++ qboolean active; // was this entity present last frame?
++} svdEntityState_t;
++
++// per-player state for delta compression
++typedef struct {
++ playerState_t ps;
++ qboolean active;
++} svdPlayerState_t;
++
++#define SVD_MAX_SERVERCMDS 64
++#define SVD_MAX_SERVERCMD_LEN 1024
++
++typedef struct {
++ // recording
++ fileHandle_t recordFile;
++ qboolean recording;
++ qboolean compressed; // LZ4 compression enabled
++ char *lastConfigstrings[MAX_CONFIGSTRINGS]; // for delta detection
++ svdEntityState_t prevEntities[MAX_GENTITIES]; // previous frame for delta
++ svdPlayerState_t prevPlayers[MAX_CLIENTS]; // previous frame player states
++
++ // buffered server commands for current frame
++ char serverCmds[SVD_MAX_SERVERCMDS][SVD_MAX_SERVERCMD_LEN];
++ int numServerCmds;
++ qboolean mapRestarted; // set by SVD_ResetDeltaState, written as frame flag
++
++ // playback
++ fileHandle_t playFile;
++ qboolean playing;
++ int playMaxClients; // original maxclients from the recording
++ int playFps; // original sv_fps
++ char *savedConfigstrings[MAX_CONFIGSTRINGS]; // from demo header, re-applied after map load
++ char playMapName[SVDEMO_MAX_MAPNAME];
++ qboolean endOfDemo;
++ qboolean needConfigstrings; // apply saved configstrings on first frame
++ svdEntityState_t playPrevEntities[MAX_GENTITIES]; // previous frame for delta read
++ svdPlayerState_t playPrevPlayers[MAX_CLIENTS]; // previous frame player states
++} svDemo_t;
++
++static svDemo_t demo;
++
++// ---------------------------------------------------------------
++// Recording helpers
++// ---------------------------------------------------------------
++
++static void SVD_WriteInt( fileHandle_t f, int v ) {
++ FS_Write( &v, 4, f );
++}
++
++static void SVD_WriteShort( fileHandle_t f, short v ) {
++ FS_Write( &v, 2, f );
++}
++
++static int SVD_ReadInt( fileHandle_t f ) {
++ int v = 0;
++ FS_Read( &v, 4, f );
++ return v;
++}
++
++static short SVD_ReadShort( fileHandle_t f ) {
++ short v = 0;
++ FS_Read( &v, 2, f );
++ return v;
++}
++
++// ---------------------------------------------------------------
++// LZ4 block I/O: writes [uncompressed_size][compressed_size][data]
++// ---------------------------------------------------------------
++
++static void SVD_WriteBlock( fileHandle_t f, const byte *data, int len ) {
++ if ( demo.compressed && len > 0 ) {
++ int bound = LZ4_compressBound( len );
++ static byte compBuf[MAX_GENTITIES * 300];
++ int compLen;
++
++ compLen = LZ4_compress_default( (const char *)data, (char *)compBuf, len, bound );
++ if ( compLen > 0 ) {
++ SVD_WriteInt( f, len ); // original size
++ SVD_WriteInt( f, compLen ); // compressed size
++ FS_Write( compBuf, compLen, f );
++ return;
++ }
++ // fall through to uncompressed on failure
++ }
++ SVD_WriteInt( f, len ); // original size
++ SVD_WriteInt( f, 0 ); // 0 = not compressed
++ FS_Write( data, len, f );
++}
++
++static int SVD_ReadBlock( fileHandle_t f, byte *buf, int bufSize ) {
++ int origLen, compLen;
++
++ origLen = SVD_ReadInt( f );
++ compLen = SVD_ReadInt( f );
++
++ if ( origLen <= 0 || origLen > bufSize ) {
++ return -1;
++ }
++
++ if ( compLen > 0 ) {
++ // compressed
++ static byte compBuf[MAX_GENTITIES * 300];
++ if ( compLen > (int)sizeof(compBuf) ) {
++ return -1;
++ }
++ FS_Read( compBuf, compLen, f );
++ if ( LZ4_decompress_safe( (const char *)compBuf, (char *)buf, compLen, bufSize ) != origLen ) {
++ return -1;
++ }
++ } else {
++ // uncompressed
++ FS_Read( buf, origLen, f );
++ }
++
++ return origLen;
++}
++
++// ---------------------------------------------------------------
++// Write header
++// ---------------------------------------------------------------
++
++static void SVD_WriteHeader( fileHandle_t f ) {
++ int i;
++ char mapBuf[SVDEMO_MAX_MAPNAME];
++
++ SVD_WriteInt( f, SVDEMO_MAGIC );
++ SVD_WriteInt( f, SVDEMO_VERSION );
++ SVD_WriteInt( f, demo.compressed ? SVDEMO_FLAG_COMPRESSED : 0 );
++ SVD_WriteInt( f, sv_maxclients->integer );
++ SVD_WriteInt( f, sv_fps->integer );
++
++ // map name
++ memset( mapBuf, 0, sizeof(mapBuf) );
++ Q_strncpyz( mapBuf, sv.configstrings[CS_SERVERINFO], sizeof(mapBuf) );
++ // actually store the mapname from CS_SERVERINFO... or just the map name
++ {
++ const char *mapname = Cvar_VariableString("mapname");
++ memset( mapBuf, 0, sizeof(mapBuf) );
++ Q_strncpyz( mapBuf, mapname, sizeof(mapBuf) );
++ }
++ FS_Write( mapBuf, SVDEMO_MAX_MAPNAME, f );
++
++ // configstrings
++ for ( i = 0; i < MAX_CONFIGSTRINGS; i++ ) {
++ if ( sv.configstrings[i] && sv.configstrings[i][0] ) {
++ int len = strlen( sv.configstrings[i] ) + 1;
++ SVD_WriteShort( f, (short)i );
++ SVD_WriteShort( f, (short)len );
++ FS_Write( sv.configstrings[i], len, f );
++
++ // store initial copy for delta detection
++ if ( demo.lastConfigstrings[i] ) {
++ Z_Free( demo.lastConfigstrings[i] );
++ }
++ demo.lastConfigstrings[i] = CopyString( sv.configstrings[i] );
++ }
++ }
++ // terminator
++ SVD_WriteShort( f, (short)0xFFFF );
++}
++
++// ---------------------------------------------------------------
++// Write one frame
++// ---------------------------------------------------------------
++
++static void SVD_WriteFrame( fileHandle_t f ) {
++ int i;
++ sharedEntity_t *ent;
++ short numChanges;
++ msg_t msg;
++ static byte msgBuf[MAX_GENTITIES * 300]; // worst case: all entities full write from baseline
++
++ SVD_WriteInt( f, svs.time );
++ SVD_WriteShort( f, (short)sv.num_entities );
++
++ // frame flags: bit 0 = map restarted
++ {
++ byte frameFlags = 0;
++ if ( demo.mapRestarted ) {
++ frameFlags |= 1;
++ demo.mapRestarted = qfalse;
++ }
++ FS_Write( &frameFlags, 1, f );
++ }
++
++ // delta-compress all entities into a message buffer
++ MSG_Init( &msg, msgBuf, sizeof(msgBuf) );
++
++ for ( i = 0; i < sv.num_entities; i++ ) {
++ qboolean active;
++ ent = SV_GentityNum( i );
++ active = ( ent->r.linked || ent->s.eType != 0 );
++
++ if ( active ) {
++ entityState_t baseline;
++ entityState_t *from;
++ if ( demo.prevEntities[i].active ) {
++ from = &demo.prevEntities[i].es;
++ } else {
++ Com_Memset( &baseline, 0, sizeof(baseline) );
++ baseline.number = i;
++ from = &baseline;
++ }
++ MSG_WriteDeltaEntity( &msg, from, &ent->s, qtrue );
++
++ // write PVS fields only if changed from previous frame
++ {
++ qboolean pvsChanged = !demo.prevEntities[i].active
++ || ent->r.svFlags != demo.prevEntities[i].svFlags
++ || ent->r.linked != demo.prevEntities[i].linked
++ || !VectorCompare( ent->r.currentOrigin, demo.prevEntities[i].currentOrigin )
++ || !VectorCompare( ent->r.absmin, demo.prevEntities[i].absmin )
++ || !VectorCompare( ent->r.absmax, demo.prevEntities[i].absmax );
++
++ MSG_WriteBits( &msg, pvsChanged, 1 );
++ if ( pvsChanged ) {
++ MSG_WriteLong( &msg, ent->r.svFlags );
++ MSG_WriteLong( &msg, ent->r.linked );
++ MSG_WriteFloat( &msg, ent->r.currentOrigin[0] );
++ MSG_WriteFloat( &msg, ent->r.currentOrigin[1] );
++ MSG_WriteFloat( &msg, ent->r.currentOrigin[2] );
++ MSG_WriteFloat( &msg, ent->r.absmin[0] );
++ MSG_WriteFloat( &msg, ent->r.absmin[1] );
++ MSG_WriteFloat( &msg, ent->r.absmin[2] );
++ MSG_WriteFloat( &msg, ent->r.absmax[0] );
++ MSG_WriteFloat( &msg, ent->r.absmax[1] );
++ MSG_WriteFloat( &msg, ent->r.absmax[2] );
++ }
++ }
++
++ // update prev state
++ demo.prevEntities[i].es = ent->s;
++ demo.prevEntities[i].svFlags = ent->r.svFlags;
++ demo.prevEntities[i].linked = ent->r.linked;
++ VectorCopy( ent->r.currentOrigin, demo.prevEntities[i].currentOrigin );
++ VectorCopy( ent->r.absmin, demo.prevEntities[i].absmin );
++ VectorCopy( ent->r.absmax, demo.prevEntities[i].absmax );
++ demo.prevEntities[i].active = qtrue;
++ } else if ( demo.prevEntities[i].active ) {
++ // entity was removed — write a remove marker
++ MSG_WriteDeltaEntity( &msg, &demo.prevEntities[i].es, NULL, qtrue );
++ demo.prevEntities[i].active = qfalse;
++ }
++ // else: entity was inactive and still inactive — write nothing
++ }
++
++ // end of entities marker
++ MSG_WriteBits( &msg, (MAX_GENTITIES - 1), GENTITYNUM_BITS );
++
++ // write entity message to file (optionally LZ4 compressed)
++ SVD_WriteBlock( f, msg.data, msg.cursize );
++
++ // write player states (delta compressed)
++ {
++ msg_t psmsg;
++ static byte psBuf[MAX_CLIENTS * 600]; // worst case: full playerState from baseline
++ int psCount = 0;
++
++ MSG_Init( &psmsg, psBuf, sizeof(psBuf) );
++
++ for ( i = 0; i < sv_maxclients->integer; i++ ) {
++ playerState_t *ps = SV_GameClientNum( i );
++ client_t *cl = &svs.clients[i];
++ qboolean active = ( cl->state >= CS_ACTIVE );
++ qboolean isSpectator;
++ playerState_t specPs;
++
++ // detect spectators: free cam or follow mode
++ isSpectator = active && ( ps->pm_type == PM_SPECTATOR || (ps->pm_flags & PMF_FOLLOW) );
++
++ // for spectators, record a sanitized ps so they appear on
++ // the scoreboard as spectators (follow mode corrupts their ps
++ // with the followed player's data)
++ if ( isSpectator ) {
++ Com_Memset( &specPs, 0, sizeof(specPs) );
++ specPs.commandTime = ps->commandTime;
++ specPs.pm_type = PM_SPECTATOR;
++ specPs.persistant[PERS_TEAM] = TEAM_SPECTATOR;
++ specPs.clientNum = i;
++ ps = &specPs;
++ }
++
++ if ( active ) {
++ playerState_t *from = demo.prevPlayers[i].active ? &demo.prevPlayers[i].ps : NULL;
++ MSG_WriteBits( &psmsg, i, 6 ); // client number (0-63)
++ MSG_WriteBits( &psmsg, 1, 1 ); // active flag
++ MSG_WriteDeltaPlayerstate( &psmsg, from, ps );
++ demo.prevPlayers[i].ps = *ps;
++ demo.prevPlayers[i].active = qtrue;
++ psCount++;
++ } else if ( demo.prevPlayers[i].active ) {
++ MSG_WriteBits( &psmsg, i, 6 );
++ MSG_WriteBits( &psmsg, 0, 1 ); // inactive flag (player left)
++ demo.prevPlayers[i].active = qfalse;
++ psCount++;
++ }
++ }
++ // terminator
++ MSG_WriteBits( &psmsg, MAX_CLIENTS - 1, 6 );
++ MSG_WriteBits( &psmsg, 0, 1 );
++
++ SVD_WriteBlock( f, psmsg.data, psmsg.cursize );
++ }
++
++ // configstring changes
++ numChanges = 0;
++ for ( i = 0; i < MAX_CONFIGSTRINGS; i++ ) {
++ const char *cur = sv.configstrings[i] ? sv.configstrings[i] : "";
++ const char *old = demo.lastConfigstrings[i] ? demo.lastConfigstrings[i] : "";
++ if ( strcmp( cur, old ) != 0 ) {
++ numChanges++;
++ }
++ }
++ SVD_WriteShort( f, numChanges );
++
++ if ( numChanges > 0 ) {
++ for ( i = 0; i < MAX_CONFIGSTRINGS; i++ ) {
++ const char *cur = sv.configstrings[i] ? sv.configstrings[i] : "";
++ const char *old = demo.lastConfigstrings[i] ? demo.lastConfigstrings[i] : "";
++ if ( strcmp( cur, old ) != 0 ) {
++ int len = strlen( cur ) + 1;
++ SVD_WriteShort( f, (short)i );
++ SVD_WriteShort( f, (short)len );
++ FS_Write( cur, len, f );
++
++ if ( demo.lastConfigstrings[i] ) {
++ Z_Free( demo.lastConfigstrings[i] );
++ }
++ demo.lastConfigstrings[i] = CopyString( cur );
++ }
++ }
++ }
++
++ // write buffered server commands (chat, prints, etc.)
++ SVD_WriteShort( f, (short)demo.numServerCmds );
++ for ( i = 0; i < demo.numServerCmds; i++ ) {
++ short len = (short)( strlen( demo.serverCmds[i] ) + 1 );
++ SVD_WriteShort( f, len );
++ FS_Write( demo.serverCmds[i], len, f );
++ }
++ demo.numServerCmds = 0;
++}
++
++// ---------------------------------------------------------------
++// Recording commands
++// ---------------------------------------------------------------
++
++/*
++Start recording a demo with the given name.
++Returns qtrue on success.
++*/
++static qboolean SVD_StartRecording( const char *demoname ) {
++ char path[MAX_OSPATH];
++
++ if ( demo.recording ) {
++ Com_Printf( "Already recording a server demo.\n" );
++ return qfalse;
++ }
++
++ if ( sv.state != SS_GAME ) {
++ Com_Printf( "Not running a server.\n" );
++ return qfalse;
++ }
++
++ Com_sprintf( path, sizeof(path), "svdemos/%s.svdm", demoname );
++
++ demo.recordFile = FS_FOpenFileWrite( path );
++ if ( !demo.recordFile ) {
++ Com_Printf( "ERROR: couldn't open %s for writing.\n", path );
++ return qfalse;
++ }
++
++ Com_Printf( "Recording server demo to %s%s\n", path,
++ Cvar_VariableIntegerValue("svdemo_compress") ? " (LZ4)" : "" );
++ demo.recording = qtrue;
++ demo.compressed = Cvar_VariableIntegerValue("svdemo_compress") ? qtrue : qfalse;
++
++ // clear delta state for fresh recording
++ Com_Memset( demo.prevEntities, 0, sizeof(demo.prevEntities) );
++ Com_Memset( demo.prevPlayers, 0, sizeof(demo.prevPlayers) );
++
++ SVD_WriteHeader( demo.recordFile );
++ return qtrue;
++}
++
++void SVD_Record_f( void ) {
++ char *s;
++
++ s = Cmd_Argv(1);
++ if ( !s[0] ) {
++ Com_Printf( "Usage: svdemo_record \n" );
++ return;
++ }
++
++ SVD_StartRecording( s );
++}
++
++/*
++Auto-record: called from SV_SpawnServer after the map is fully loaded.
++Generates a name from map name + timestamp.
++*/
++void SVD_AutoRecord( void ) {
++ char demoname[MAX_OSPATH];
++ const char *mapname;
++ qtime_t now;
++
++ if ( demo.recording || demo.playing ) {
++ return;
++ }
++
++ if ( !Cvar_VariableIntegerValue( "svdemo_autorecord" ) ) {
++ return;
++ }
++
++ if ( sv.state != SS_GAME ) {
++ return;
++ }
++
++ mapname = Cvar_VariableString( "mapname" );
++ Com_RealTime( &now );
++ Com_sprintf( demoname, sizeof(demoname), "%s_%04d%02d%02d_%02d%02d%02d",
++ mapname,
++ 1900 + now.tm_year, 1 + now.tm_mon, now.tm_mday,
++ now.tm_hour, now.tm_min, now.tm_sec );
++
++ SVD_StartRecording( demoname );
++}
++
++void SVD_StopRecord_f( void ) {
++ int i;
++
++ if ( !demo.recording ) {
++ Com_Printf( "Not recording a server demo.\n" );
++ return;
++ }
++
++ // write end marker
++ SVD_WriteInt( demo.recordFile, -1 );
++
++ FS_FCloseFile( demo.recordFile );
++ demo.recordFile = 0;
++ demo.recording = qfalse;
++
++ // free configstring tracking
++ for ( i = 0; i < MAX_CONFIGSTRINGS; i++ ) {
++ if ( demo.lastConfigstrings[i] ) {
++ Z_Free( demo.lastConfigstrings[i] );
++ demo.lastConfigstrings[i] = NULL;
++ }
++ }
++
++ Com_Printf( "Server demo recording stopped.\n" );
++}
++
++/*
++Called from SV_Frame() after the game has run its frame.
++*/
++/*
++Reset delta compression state. Call on map_restart so the next
++recorded frame writes full entity/player states from baseline.
++*/
++/*
++Capture a broadcast server command for the current frame.
++Called from SV_SendServerCommand when cl == NULL (broadcast).
++*/
++void SVD_CaptureServerCommand( const char *cmd ) {
++ int i;
++
++ if ( !demo.recording ) {
++ return;
++ }
++ if ( demo.numServerCmds >= SVD_MAX_SERVERCMDS ) {
++ return; // overflow, drop command
++ }
++
++ // deduplicate: per-client chat is sent N times (once per client),
++ // only store the first occurrence
++ for ( i = 0; i < demo.numServerCmds; i++ ) {
++ if ( !strcmp( demo.serverCmds[i], cmd ) ) {
++ return;
++ }
++ }
++
++ Q_strncpyz( demo.serverCmds[demo.numServerCmds], cmd, SVD_MAX_SERVERCMD_LEN );
++ demo.numServerCmds++;
++}
++
++void SVD_ResetDeltaState( void ) {
++ if ( !demo.recording ) {
++ return;
++ }
++ Com_Memset( demo.prevEntities, 0, sizeof(demo.prevEntities) );
++ Com_Memset( demo.prevPlayers, 0, sizeof(demo.prevPlayers) );
++ demo.mapRestarted = qtrue; // signal next frame to write restart marker
++}
++
++void SVD_RecordFrame( void ) {
++ if ( !demo.recording ) {
++ return;
++ }
++ SVD_WriteFrame( demo.recordFile );
++}
++
++// ---------------------------------------------------------------
++// Playback: read header
++// ---------------------------------------------------------------
++
++static qboolean SVD_ReadHeader( fileHandle_t f ) {
++ int magic, version;
++
++ magic = SVD_ReadInt( f );
++ if ( magic != SVDEMO_MAGIC ) {
++ Com_Printf( "Not a valid server demo file.\n" );
++ return qfalse;
++ }
++
++ version = SVD_ReadInt( f );
++ if ( version != SVDEMO_VERSION ) {
++ Com_Printf( "Unsupported server demo version %d.\n", version );
++ return qfalse;
++ }
++
++ {
++ int flags = SVD_ReadInt( f );
++ demo.compressed = ( flags & SVDEMO_FLAG_COMPRESSED ) ? qtrue : qfalse;
++ }
++
++ demo.playMaxClients = SVD_ReadInt( f );
++ demo.playFps = SVD_ReadInt( f );
++ FS_Read( demo.playMapName, SVDEMO_MAX_MAPNAME, f );
++ demo.playMapName[SVDEMO_MAX_MAPNAME - 1] = '\0';
++
++ // read configstrings — store for re-application after map load
++ {
++ short idx;
++ while (1) {
++ idx = SVD_ReadShort( f );
++ if ( idx == (short)0xFFFF ) {
++ break;
++ }
++ {
++ short len = SVD_ReadShort( f );
++ char buf[BIG_INFO_STRING];
++ if ( len > 0 && len < (short)sizeof(buf) ) {
++ FS_Read( buf, len, f );
++ buf[len - 1] = '\0';
++ if ( demo.savedConfigstrings[idx] ) {
++ Z_Free( demo.savedConfigstrings[idx] );
++ }
++ demo.savedConfigstrings[idx] = CopyString( buf );
++ }
++ }
++ }
++ }
++
++ return qtrue;
++}
++
++/*
++Re-apply recorded configstrings after the map has loaded.
++Called after devmap finishes in SVD_Play_f.
++*/
++static void SVD_ApplyConfigstrings( void ) {
++ int i;
++ for ( i = 0; i < MAX_CONFIGSTRINGS; i++ ) {
++ // skip CS_SERVERINFO and CS_SYSTEMINFO — they contain latched cvars
++ // (sv_maxclients, sv_pure, etc.) that would trigger a map restart
++ if ( i == CS_SERVERINFO || i == CS_SYSTEMINFO ) {
++ continue;
++ }
++ if ( demo.savedConfigstrings[i] && demo.savedConfigstrings[i][0] ) {
++ SV_SetConfigstring( i, demo.savedConfigstrings[i] );
++ }
++ }
++}
++
++// ---------------------------------------------------------------
++// Playback: read one frame, populate sv.gentities
++// ---------------------------------------------------------------
++
++static qboolean SVD_ReadFrame( fileHandle_t f ) {
++ int serverTime;
++ short numEnts, numChanges;
++ int i, entNum, blockLen;
++ sharedEntity_t *ent;
++ msg_t msg;
++ static byte msgBuf[MAX_GENTITIES * 300];
++ entityState_t newEs;
++
++ serverTime = SVD_ReadInt( f );
++ if ( serverTime == -1 ) {
++ return qfalse; // end of demo
++ }
++
++ // set svs.time to recorded time so entity trajectory interpolation
++ // works correctly (rockets, grenades, etc. use pos.trTime relative
++ // to server time). zombie timeout is already skipped during playback.
++ svs.time = serverTime;
++ numEnts = SVD_ReadShort( f );
++
++ // read frame flags
++ {
++ byte frameFlags;
++ FS_Read( &frameFlags, 1, f );
++ if ( frameFlags & 1 ) {
++ // map was restarted — reset playback delta state to match
++ // the recording's reset (both now decode from zero baseline).
++ Com_Memset( demo.playPrevEntities, 0, sizeof(demo.playPrevEntities) );
++ Com_Memset( demo.playPrevPlayers, 0, sizeof(demo.playPrevPlayers) );
++ // set one-shot SNAPFLAG_RESET_ENTITIES so cgame snaps all
++ // entities to current position without interpolation.
++ // OR'd into snapFlagServerBit, cleared after one frame.
++ svs.snapFlagServerBit |= SNAPFLAG_RESET_ENTITIES;
++ }
++ }
++
++ // read entity message (optionally LZ4 compressed)
++ blockLen = SVD_ReadBlock( f, msgBuf, sizeof(msgBuf) );
++ if ( blockLen <= 0 ) {
++ return qfalse;
++ }
++ MSG_Init( &msg, msgBuf, sizeof(msgBuf) );
++ msg.cursize = blockLen;
++
++ // clear all entities (spectator's entity is recreated by ClientThink_real)
++ for ( i = 0; i < sv.num_entities; i++ ) {
++ ent = SV_GentityNum( i );
++ if ( ent->r.linked ) {
++ SV_UnlinkEntity( ent );
++ }
++ ent->s.eType = 0;
++ ent->s.number = i;
++ }
++
++ // parse delta-compressed entities
++ while ( 1 ) {
++ entNum = MSG_ReadBits( &msg, GENTITYNUM_BITS );
++ if ( entNum == MAX_GENTITIES - 1 ) {
++ break; // end of entities marker
++ }
++ if ( msg.readcount > msg.cursize ) {
++ break;
++ }
++
++ {
++ entityState_t baseline;
++ entityState_t *from;
++ Com_Memset( &newEs, 0, sizeof(newEs) );
++ if ( demo.playPrevEntities[entNum].active ) {
++ from = &demo.playPrevEntities[entNum].es;
++ } else {
++ Com_Memset( &baseline, 0, sizeof(baseline) );
++ baseline.number = entNum;
++ from = &baseline;
++ }
++ MSG_ReadDeltaEntity( &msg, from, &newEs, entNum );
++ }
++
++ if ( newEs.number == MAX_GENTITIES - 1 ) {
++ // entity was removed
++ demo.playPrevEntities[entNum].active = qfalse;
++ continue;
++ }
++
++ // read PVS fields
++ {
++ qboolean pvsChanged = MSG_ReadBits( &msg, 1 );
++ if ( pvsChanged ) {
++ demo.playPrevEntities[entNum].svFlags = MSG_ReadLong( &msg );
++ demo.playPrevEntities[entNum].linked = MSG_ReadLong( &msg );
++ demo.playPrevEntities[entNum].currentOrigin[0] = MSG_ReadFloat( &msg );
++ demo.playPrevEntities[entNum].currentOrigin[1] = MSG_ReadFloat( &msg );
++ demo.playPrevEntities[entNum].currentOrigin[2] = MSG_ReadFloat( &msg );
++ demo.playPrevEntities[entNum].absmin[0] = MSG_ReadFloat( &msg );
++ demo.playPrevEntities[entNum].absmin[1] = MSG_ReadFloat( &msg );
++ demo.playPrevEntities[entNum].absmin[2] = MSG_ReadFloat( &msg );
++ demo.playPrevEntities[entNum].absmax[0] = MSG_ReadFloat( &msg );
++ demo.playPrevEntities[entNum].absmax[1] = MSG_ReadFloat( &msg );
++ demo.playPrevEntities[entNum].absmax[2] = MSG_ReadFloat( &msg );
++ }
++ }
++
++ demo.playPrevEntities[entNum].es = newEs;
++ demo.playPrevEntities[entNum].active = qtrue;
++
++ // apply to server entity
++ ent = SV_GentityNum( entNum );
++ ent->s = newEs;
++ ent->s.number = entNum;
++ ent->r.svFlags = demo.playPrevEntities[entNum].svFlags;
++ ent->r.linked = demo.playPrevEntities[entNum].linked;
++ VectorCopy( demo.playPrevEntities[entNum].currentOrigin, ent->r.currentOrigin );
++ VectorCopy( demo.playPrevEntities[entNum].absmin, ent->r.absmin );
++ VectorCopy( demo.playPrevEntities[entNum].absmax, ent->r.absmax );
++
++ if ( ent->r.linked ) {
++ SV_LinkEntity( ent );
++ }
++
++ if ( entNum + 1 > sv.num_entities ) {
++ sv.num_entities = entNum + 1;
++ }
++ }
++
++ // read player states (delta compressed)
++ {
++ msg_t psmsg;
++ static byte psBuf[MAX_CLIENTS * 600]; // worst case: full playerState from baseline
++ int psMsgLen;
++
++ psMsgLen = SVD_ReadBlock( f, psBuf, sizeof(psBuf) );
++ if ( psMsgLen > 0 ) {
++ MSG_Init( &psmsg, psBuf, sizeof(psBuf) );
++ psmsg.cursize = psMsgLen;
++
++ while ( 1 ) {
++ int clientNum = MSG_ReadBits( &psmsg, 6 );
++ int active = MSG_ReadBits( &psmsg, 1 );
++
++ if ( clientNum == MAX_CLIENTS - 1 && !active ) {
++ break; // terminator
++ }
++ if ( psmsg.readcount > psmsg.cursize ) {
++ break;
++ }
++
++ if ( active ) {
++ playerState_t newPs;
++ playerState_t *from;
++ playerState_t baseline;
++
++ if ( demo.playPrevPlayers[clientNum].active ) {
++ from = &demo.playPrevPlayers[clientNum].ps;
++ } else {
++ Com_Memset( &baseline, 0, sizeof(baseline) );
++ from = &baseline;
++ }
++
++ MSG_ReadDeltaPlayerstate( &psmsg, from, &newPs );
++ demo.playPrevPlayers[clientNum].ps = newPs;
++ demo.playPrevPlayers[clientNum].active = qtrue;
++
++ // inject into game module's client state
++ {
++ playerState_t *gamePs = SV_GameClientNum( clientNum );
++ *gamePs = newPs;
++ }
++ } else {
++ demo.playPrevPlayers[clientNum].active = qfalse;
++ // clear game playerState so G_RunFrame sees commandTime=0
++ {
++ playerState_t *gamePs = SV_GameClientNum( clientNum );
++ Com_Memset( gamePs, 0, sizeof(*gamePs) );
++ }
++ }
++ }
++ }
++ }
++
++ // read configstring changes (skip SERVERINFO/SYSTEMINFO to avoid latch restarts)
++ numChanges = SVD_ReadShort( f );
++ for ( i = 0; i < numChanges; i++ ) {
++ short idx = SVD_ReadShort( f );
++ short len = SVD_ReadShort( f );
++ char buf[BIG_INFO_STRING];
++ if ( len > 0 && len < (short)sizeof(buf) ) {
++ FS_Read( buf, len, f );
++ buf[len - 1] = '\0';
++ if ( idx != CS_SERVERINFO && idx != CS_SYSTEMINFO ) {
++ SV_SetConfigstring( idx, buf );
++ }
++ }
++ }
++
++ // read server commands (chat, prints, etc.) and replay to spectator
++ {
++ short numCmds = SVD_ReadShort( f );
++ for ( i = 0; i < numCmds; i++ ) {
++ short len = SVD_ReadShort( f );
++ char buf[SVD_MAX_SERVERCMD_LEN];
++ if ( len > 0 && len < (short)sizeof(buf) ) {
++ FS_Read( buf, len, f );
++ buf[len - 1] = '\0';
++ // broadcast — only the spectator is CS_ACTIVE, zombies are skipped
++ SV_SendServerCommand( NULL, "%s", buf );
++ }
++ }
++ }
++
++ return qtrue;
++}
++
++// ---------------------------------------------------------------
++// Playback commands
++// ---------------------------------------------------------------
++
++void SVD_Play_f( void ) {
++ char name[MAX_OSPATH];
++ char *s;
++ int len;
++
++ if ( demo.playing ) {
++ Com_Printf( "Already playing a server demo.\n" );
++ return;
++ }
++
++ s = Cmd_Argv(1);
++ if ( !s[0] ) {
++ Com_Printf( "Usage: svdemo_play \n" );
++ return;
++ }
++
++ Com_sprintf( name, sizeof(name), "svdemos/%s.svdm", s );
++
++ memset( &demo, 0, sizeof(demo) );
++
++ len = FS_FOpenFileRead( name, &demo.playFile, qtrue );
++ if ( !demo.playFile || len <= 0 ) {
++ Com_Printf( "ERROR: couldn't open %s.\n", name );
++ return;
++ }
++
++ if ( !SVD_ReadHeader( demo.playFile ) ) {
++ FS_FCloseFile( demo.playFile );
++ demo.playFile = 0;
++ return;
++ }
++
++ demo.playing = qtrue;
++ demo.endOfDemo = qfalse;
++
++ Com_Printf( "Playing server demo: map=%s maxclients=%d fps=%d\n",
++ demo.playMapName, demo.playMaxClients, demo.playFps );
++
++ // Signal demo mode BEFORE map load so the game module knows
++ // during ClientConnect/ClientBegin to force spectator team.
++ Cvar_Set( "sv_demoplaying", "1" );
++
++ // Load the map with maxclients = MAX_CLIENTS to avoid entity slot collisions.
++ Cbuf_ExecuteText( EXEC_NOW, va("set sv_maxclients %d\n", MAX_CLIENTS) );
++ Cbuf_ExecuteText( EXEC_NOW, va("set sv_fps %d\n", demo.playFps) );
++ Cbuf_ExecuteText( EXEC_NOW, va("devmap %s\n", demo.playMapName) );
++
++ // Reserve recorded player slots so the connecting spectator
++ // doesn't land in slot 0 (which collides with recorded player 0).
++ // Mark as CS_ZOMBIE (non-free, won't be reused by SV_DirectConnect).
++ // Set rate and nextSnapshotTime so SV_SendClientMessages doesn't
++ // crash on division by zero or try to send them snapshots.
++ {
++ int i;
++ for ( i = 0; i < demo.playMaxClients; i++ ) {
++ if ( svs.clients[i].state == CS_FREE ) {
++ svs.clients[i].state = CS_ZOMBIE;
++ svs.clients[i].rate = 10000;
++ svs.clients[i].nextSnapshotTime = 0x7FFFFFFF; // never send
++ svs.clients[i].lastPacketTime = svs.time;
++ }
++ }
++ }
++
++ // Configstrings will be applied on the first playback frame,
++ // after the map has fully loaded.
++ demo.needConfigstrings = qtrue;
++}
++
++static void SVD_CleanupPlayback( void ) {
++ int i;
++
++ if ( !demo.playing ) {
++ return;
++ }
++
++ FS_FCloseFile( demo.playFile );
++
++ // free saved configstrings
++ for ( i = 0; i < MAX_CONFIGSTRINGS; i++ ) {
++ if ( demo.savedConfigstrings[i] ) {
++ Z_Free( demo.savedConfigstrings[i] );
++ }
++ }
++
++ // free zombie client slots
++ for ( i = 0; i < demo.playMaxClients; i++ ) {
++ if ( svs.clients[i].state == CS_ZOMBIE ) {
++ svs.clients[i].state = CS_FREE;
++ }
++ }
++
++ memset( &demo, 0, sizeof(demo) );
++ Cvar_Set( "sv_demoplaying", "0" );
++}
++
++void SVD_StopPlay_f( void ) {
++ if ( !demo.playing ) {
++ Com_Printf( "Not playing a server demo.\n" );
++ return;
++ }
++
++ SVD_CleanupPlayback();
++ Com_Printf( "Server demo playback stopped.\n" );
++
++ // disconnect to return to main menu
++ Cbuf_ExecuteText( EXEC_APPEND, "disconnect\n" );
++}
++
++/*
++Unified stop command: stops recording or playback, whichever is active.
++*/
++void SVD_Stop_f( void ) {
++ if ( demo.recording ) {
++ SVD_StopRecord_f();
++ } else if ( demo.playing ) {
++ SVD_StopPlay_f();
++ } else {
++ Com_Printf( "Not recording or playing a server demo.\n" );
++ }
++}
++
++/*
++Called from SV_Frame() to advance playback by one frame.
++Returns qtrue if a frame was read, qfalse if demo ended.
++*/
++qboolean SVD_PlaybackFrame( void ) {
++ if ( !demo.playing || demo.endOfDemo ) {
++ return qfalse;
++ }
++
++ // wait for a spectator to be fully in-game before starting playback.
++ // the server keeps running frames (so the connection handshake completes)
++ // but no demo data is consumed until someone is CS_ACTIVE.
++ if ( SVD_ShouldPause() ) {
++ return qfalse;
++ }
++
++ // apply recorded configstrings once after map load
++ if ( demo.needConfigstrings ) {
++ SVD_ApplyConfigstrings();
++ demo.needConfigstrings = qfalse;
++ }
++
++ // clear one-shot reset flag from previous frame before reading new one
++ svs.snapFlagServerBit &= ~SNAPFLAG_RESET_ENTITIES;
++
++ if ( !SVD_ReadFrame( demo.playFile ) ) {
++ Com_Printf( "Server demo playback finished.\n" );
++ SVD_CleanupPlayback();
++ Cbuf_ExecuteText( EXEC_APPEND, "disconnect\n" );
++ return qfalse;
++ }
++
++ return qtrue;
++}
++
++// ---------------------------------------------------------------
++// Queries
++// ---------------------------------------------------------------
++
++qboolean SVD_IsRecording( void ) {
++ return demo.recording;
++}
++
++qboolean SVD_IsPlaying( void ) {
++ return demo.playing;
++}
++
++/*
++Returns qtrue if demo playback should pause (no active spectators).
++Controlled by svdemo_pauseEmpty cvar.
++*/
++qboolean SVD_ShouldPause( void ) {
++ int i;
++
++ if ( !Cvar_VariableIntegerValue( "svdemo_pauseEmpty" ) ) {
++ return qfalse;
++ }
++
++ for ( i = 0; i < sv_maxclients->integer; i++ ) {
++ if ( svs.clients[i].state == CS_ACTIVE ) {
++ return qfalse; // someone is in-game and watching
++ }
++ }
++
++ return qtrue; // nobody connected, pause
++}
++
+diff --git a/svdemo.txt b/svdemo.txt
+new file mode 100644
+index 0000000..24dc836
+--- /dev/null
++++ b/svdemo.txt
+@@ -0,0 +1,160 @@
++===========================================================================
++ Серверные демо-записи (SVDEMO)
++ Руководство пользователя
++===========================================================================
++
++ОПИСАНИЕ
++--------
++
++Система серверных демо-записей позволяет записывать полное состояние
++игрового сервера (все сущности, все игроки) и воспроизводить запись
++с возможностью свободного перемещения камеры или просмотра от первого
++лица любого игрока.
++
++В отличие от обычных клиентских демо (запись только того, что видит
++один игрок), серверная запись содержит полную картину матча.
++
++
++КОНСОЛЬНЫЕ КОМАНДЫ
++------------------
++
++ svdemo_record <имя>
++ Начать запись серверного демо. Файл сохраняется в
++ svdemos/<имя>.svdm внутри игровой директории.
++
++ svdemo_play <имя>
++ Воспроизвести серверное демо. Загружает карту из записи,
++ подключает зрителя автоматически.
++
++ svdemo_stop
++ Остановить текущую запись или воспроизведение.
++ При остановке воспроизведения происходит отключение от сервера.
++
++
++НАСТРОЙКИ (CVARS)
++-----------------
++
++ svdemo_autorecord <0|1> (по умолчанию: 0)
++ Автоматическая запись демо при каждой загрузке карты.
++ Файлы именуются автоматически: <карта>_ГГГГММДД_ЧЧММСС.svdm
++ Пример: q3dm6_20260323_141530.svdm
++
++ Для включения: set svdemo_autorecord 1
++ Значение сохраняется в конфигурации (CVAR_ARCHIVE).
++
++ svdemo_compress <0|1> (по умолчанию: 1)
++ Сжатие данных демо алгоритмом LZ4. Уменьшает размер файла
++ при незначительных затратах на производительность.
++ Включено по умолчанию. Установите 0 для отключения.
++ Значение сохраняется в конфигурации (CVAR_ARCHIVE).
++ Влияет только на запись — воспроизведение автоматически
++ определяет, сжат файл или нет.
++
++
++ЗАПИСЬ
++------
++
++1. Запустите сервер и начните игру как обычно:
++ devmap q3dm6
++
++2. Начните запись:
++ svdemo_record mymatch
++
++3. Играйте. Все действия всех игроков записываются.
++
++4. Остановите запись:
++ svdemo_stop
++
++ Запись также автоматически останавливается при:
++ - Смене карты (map, devmap, nextmap по таймлимиту/фраглимиту)
++ - Выключении сервера
++ Перезапуск карты (map_restart) НЕ прерывает запись.
++
++
++ВОСПРОИЗВЕДЕНИЕ
++---------------
++
++1. Запустите воспроизведение:
++ svdemo_play mymatch
++
++2. Карта загрузится автоматически. Вы подключитесь как зритель
++ со свободной камерой (полёт по карте).
++
++3. Управление зрителем:
++ - Свободная камера: перемещайтесь как обычный спектатор
++ - Следование за игроком: нажмите MOUSE2 (USE) для входа
++ в режим следования
++ - Переключение между игроками: MOUSE1 (ATTACK)
++ - Выход из следования: MOUSE2 снова
++
++4. В режиме следования вы видите игру от первого лица выбранного
++ игрока с полным HUD: здоровье, броня, боеприпасы, оружие.
++
++5. Табло (TAB) показывает счёт записанных игроков.
++
++6. Остановка:
++ svdemo_stop
++ Также воспроизведение останавливается автоматически при
++ достижении конца записи.
++
++
++ФОРМАТ ФАЙЛА
++-------------
++
++Расширение: .svdm
++Директория: svdemos/
++
++Файл содержит:
++- Заголовок: название карты, настройки сервера, конфигстроки
++ (имена игроков, модели, настройки игры)
++- Покадровые данные: дельта-сжатые состояния сущностей и игроков,
++ изменения конфигстрок
++
++Используется двухуровневое сжатие:
++1. Дельта-кодирование: записываются только изменившиеся поля
++ сущностей и игроков между кадрами.
++2. LZ4-сжатие (опционально): дополнительно сжимает блоки данных
++ каждого кадра. Включено по умолчанию (svdemo_compress 1).
++
++Типичная 10-секундная запись занимает ~50 КБ.
++
++Одна запись = одна карта. При смене карты запись останавливается.
++
++
++ОГРАНИЧЕНИЯ
++-----------
++
++- Нельзя перематывать запись вперёд или назад.
++- Если в записанной игре было 64 игрока (MAX_CLIENTS), один
++ из них не будет виден при воспроизведении (его слот занят зрителем).
++- Воспроизведение требует наличия тех же pk3-файлов (карты, модели),
++ что использовались при записи.
++- Демо несовместимы между разными версиями движка, если изменился
++ формат сетевых структур.
++
++
++ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ
++---------------------
++
++Автоматическая запись всех матчей:
++
++ set svdemo_autorecord 1
++ devmap q3dm17
++
++ (все матчи на этом сервере будут записываться автоматически)
++
++
++Запись конкретного матча:
++
++ devmap q3tourney2
++ svdemo_record duel_finals
++ (играть...)
++ svdemo_stop
++
++
++Просмотр записи:
++
++ svdemo_play duel_finals
++
++
++===========================================================================
diff --git a/svdemo.txt b/svdemo.txt
new file mode 100644
index 0000000..10e2480
--- /dev/null
+++ b/svdemo.txt
@@ -0,0 +1,196 @@
+===========================================================================
+ Серверные демо-записи (SVDEMO)
+ Руководство пользователя
+===========================================================================
+
+ОПИСАНИЕ
+--------
+
+Система серверных демо-записей позволяет записывать полное состояние
+игрового сервера (все сущности, все игроки) и воспроизводить запись
+с возможностью свободного перемещения камеры или просмотра от первого
+лица любого игрока.
+
+В отличие от обычных клиентских демо (запись только того, что видит
+один игрок), серверная запись содержит полную картину матча.
+
+
+КОНСОЛЬНЫЕ КОМАНДЫ
+------------------
+
+Запись:
+
+ svdemo_record <имя>
+ Начать запись серверного демо. Файл сохраняется в
+ svdemos/<имя>.svdm внутри игровой директории.
+
+ svdemo_stop
+ Остановить текущую запись или воспроизведение.
+ При остановке воспроизведения происходит отключение от сервера.
+
+Воспроизведение:
+
+ svdemo_play <имя>
+ Воспроизвести серверное демо. Загружает карту из записи,
+ подключает зрителя автоматически. Можно вызывать из меню,
+ из игры или во время просмотра другого демо.
+
+ svdemo_pause
+ Пауза / продолжение воспроизведения. Во время паузы камера
+ свободно перемещается, сущности заморожены.
+ На экране отображается "Playback Paused".
+
+ svdemo_seek <секунды>
+ Перемотка относительно текущей позиции. Положительное значение --
+ вперёд, отрицательное -- назад.
+ Перемещает к ближайшему ключевому кадру (точность ±5 сек).
+ Работает в паузе.
+ Пример: svdemo_seek -10 (назад на 10 секунд)
+
+ svdemo_seekexact <секунды>
+ Точная перемотка. Находит ближайший ключевой кадр, затем
+ прочитывает кадры до целевого времени. Точность до одного
+ серверного кадра (50 мс при sv_fps 20). Может занять долю
+ секунды на длинных перемотках. Работает в паузе.
+ Пример: svdemo_seekexact -10 (назад ровно на 10 секунд)
+
+
+НАСТРОЙКИ (CVARS)
+-----------------
+
+ svdemo_autorecord <0|1> (по умолчанию: 0)
+ Автоматическая запись демо при каждой загрузке карты.
+ Файлы именуются автоматически: <карта>_ГГГГММДД_ЧЧММСС.svdm
+ Пример: q3dm6_20260323_141530.svdm
+
+ svdemo_pauseEmpty <0|1> (по умолчанию: 1)
+ Пауза воспроизведения, когда нет подключённых зрителей.
+ Демо начинается с первого кадра при подключении зрителя.
+
+ svdemo_keyframeInterval <секунды> (по умолчанию: 5, 0 = выкл.)
+ Интервал ключевых кадров. Ключевые кадры позволяют перематывать
+ запись. Чем меньше интервал, тем точнее перемотка командой
+ svdemo_seek, но немного больше размер файла. Первый кадр
+ записи всегда является ключевым.
+
+ Все настройки сохраняются в конфигурации (CVAR_ARCHIVE).
+
+
+ЗАПИСЬ
+------
+
+1. Запустите сервер и начните игру как обычно:
+ devmap q3dm6
+
+2. Начните запись:
+ svdemo_record mymatch
+
+3. Играйте. Все действия всех игроков записываются.
+
+4. Остановите запись:
+ svdemo_stop
+
+ Запись также автоматически останавливается при:
+ - Смене карты (map, devmap, nextmap по таймлимиту/фраглимиту)
+ - Выключении сервера
+ Перезапуск карты (map_restart) НЕ прерывает запись.
+
+
+ВОСПРОИЗВЕДЕНИЕ
+---------------
+
+1. Запустите воспроизведение:
+ svdemo_play mymatch
+
+2. Карта загрузится автоматически. Вы подключитесь как зритель
+ со свободной камерой (полёт по карте).
+
+3. Управление зрителем:
+ - Свободная камера: перемещайтесь как обычный спектатор
+ - Следование за игроком: нажмите MOUSE1 (ATTACK) для входа
+ в режим следования и переключения между игроками
+ - Выход из следования: team spectator (можно забиндить)
+
+4. В режиме следования вы видите игру от первого лица выбранного
+ игрока с полным HUD: здоровье, броня, боеприпасы, оружие.
+
+5. Табло (TAB) показывает счёт записанных игроков.
+
+6. Управление воспроизведением:
+ - svdemo_pause -- пауза/продолжение
+ - svdemo_seek -10 -- быстрая перемотка назад на 10 сек
+ - svdemo_seek 30 -- быстрая перемотка вперёд на 30 сек
+ - svdemo_seekexact -10 -- точная перемотка назад на 10 сек
+ - svdemo_stop -- остановка и выход
+
+7. Воспроизведение останавливается автоматически при
+ достижении конца записи.
+
+
+ФОРМАТ ФАЙЛА
+-------------
+
+Расширение: .svdm
+Директория: svdemos/
+
+Файл содержит:
+- Заголовок: название карты, настройки сервера, конфигстроки
+ (имена игроков, модели, настройки игры)
+- Покадровые данные: дельта-сжатые состояния сущностей и игроков,
+ серверные команды (чат, принты), изменения конфигстрок
+- Ключевые кадры с индексом в конце файла
+
+Дельта-кодирование обеспечивает компактный размер файла.
+Типичный 10-минутный матч с 10 ботами занимает ~8 МБ.
+
+Одна запись = одна карта. При смене карты запись останавливается.
+
+
+ОГРАНИЧЕНИЯ
+-----------
+
+- Если в записанной игре было 64 игрока (MAX_CLIENTS), один
+ из них не будет виден при воспроизведении (его слот занят зрителем).
+- Воспроизведение требует наличия тех же pk3-файлов (карты, модели),
+ что использовались при записи.
+- Демо несовместимы между разными версиями движка, если изменился
+ формат сетевых структур.
+- При перемотке могут кратковременно отображаться визуальные артефакты
+ (частицы, вспышки) от локальных эффектов.
+
+
+ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ
+---------------------
+
+Автоматическая запись всех матчей:
+
+ set svdemo_autorecord 1
+ devmap q3dm17
+
+ (все матчи на этом сервере будут записываться автоматически)
+
+
+Запись конкретного матча:
+
+ devmap q3tourney2
+ svdemo_record duel_finals
+ (играть...)
+ svdemo_stop
+
+
+Просмотр записи:
+
+ svdemo_play duel_finals
+
+
+Просмотр с перемоткой:
+
+ svdemo_play duel_finals
+ (подождать 30 секунд)
+ svdemo_seekexact -20 (вернуться на 20 секунд назад)
+ svdemo_pause (поставить на паузу)
+ svdemo_seek -5 (ещё назад на 5 секунд, в паузе)
+ svdemo_pause (продолжить)
+
+
+===========================================================================