diff --git a/r5dev/game/CMakeLists.txt b/r5dev/game/CMakeLists.txt
index 43efc0f3..4138ea05 100644
--- a/r5dev/game/CMakeLists.txt
+++ b/r5dev/game/CMakeLists.txt
@@ -19,6 +19,7 @@ add_sources( SOURCE_GROUP "Shared"
     "shared/shared_classnames.h"
     "shared/shareddefs.h"
     "shared/takedamageinfo.h"
+    "shared/usercmd.cpp"
     "shared/usercmd.h"
     "shared/util_shared.cpp"
     "shared/util_shared.h"
diff --git a/r5dev/game/shared/usercmd.cpp b/r5dev/game/shared/usercmd.cpp
new file mode 100644
index 00000000..c55d0e16
--- /dev/null
+++ b/r5dev/game/shared/usercmd.cpp
@@ -0,0 +1,43 @@
+//====== Copyright � 1996-2005, Valve Corporation, All rights reserved. =======//
+//
+// Purpose: 
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#include "usercmd.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: Read in a delta compressed usercommand.
+// Input  : *buf - 
+//			*move - 
+//			*from - 
+// Output : random seed
+//-----------------------------------------------------------------------------
+int ReadUserCmd(bf_read* buf, CUserCmd* move, CUserCmd* from)
+{
+	int seed = v_ReadUserCmd(buf, move, from);
+
+	// On the client, the frame time must be within 'usercmd_frametime_min'
+	// and 'usercmd_frametime_max'. Testing revealed that speed hacking could
+	// be achieved by sending bogus frametimes. Clamp the networked frame time
+	// to the exact values that the client should be using to make sure it
+	// couldn't be circumvented by busting out the client side clamps.
+	if (host_timescale->GetFloat() == 1.0f)
+		move->frametime = clamp(move->frametime,
+			usercmd_frametime_min->GetFloat(),
+			usercmd_frametime_max->GetFloat());
+
+	return seed;
+}
+
+//-----------------------------------------------------------------------------
+void VUserCmd::Attach() const
+{
+	DetourAttach(&v_ReadUserCmd, &ReadUserCmd);
+}
+
+void VUserCmd::Detach() const
+{
+	DetourDetach(&v_ReadUserCmd, &ReadUserCmd);
+}
diff --git a/r5dev/game/shared/usercmd.h b/r5dev/game/shared/usercmd.h
index d403aa58..c947eff5 100644
--- a/r5dev/game/shared/usercmd.h
+++ b/r5dev/game/shared/usercmd.h
@@ -10,6 +10,7 @@
 #ifdef _WIN32
 #pragma once
 #endif
+#include "tier1/bitbuf.h"
 #include "mathlib/vector.h"
 
 //-------------------------------------------------------------------------------------
@@ -23,6 +24,9 @@ inline auto v_CUserCmd__Reset = p_CUserCmd__Reset.RCast<void(*)(CUserCmd* pUserC
 inline CMemory p_CUserCmd__Copy;
 inline auto v_CUserCmd__Copy = p_CUserCmd__Copy.RCast<CUserCmd*(*)(CUserCmd* pDest, CUserCmd* pSource)>();
 
+inline CMemory p_ReadUserCmd;
+inline auto v_ReadUserCmd = p_ReadUserCmd.RCast<int (*)(bf_read* buf, CUserCmd* move, CUserCmd* from)>();
+
 //-------------------------------------------------------------------------------------
 // 
 //-------------------------------------------------------------------------------------
@@ -34,21 +38,15 @@ public:
 		Reset();
 	}
 
-	CUserCmd* Copy(CUserCmd* pSource)
-	{
-		return v_CUserCmd__Copy(this, pSource);
-	}
-
-	void Reset()
-	{
-		v_CUserCmd__Reset(this);
-	}
+	inline CUserCmd* Copy(CUserCmd* pSource) { return v_CUserCmd__Copy(this, pSource); }
+	inline void Reset() { v_CUserCmd__Reset(this); }
 
 	int32_t command_number;
 	int32_t tick_count;
 	float curtime;
 	QAngle viewangles;
-	char pad_0x0018[12];
+	float maybe;
+	char pad_0x0018[8];
 	float forwardmove;
 	float sidemove;
 	float upmove;
@@ -58,9 +56,11 @@ public:
 	char pad_0x0188[8];
 	Vector3D headposition;
 	float maxpitch;
-	char pad_0x01A0[60];
+	char pad_0x01A0[56];
+	float frametime;
 };
 
+static_assert(sizeof(CUserCmd) == 0x1DC);
 
 ///////////////////////////////////////////////////////////////////////////////
 class VUserCmd : public IDetour
@@ -69,6 +69,7 @@ class VUserCmd : public IDetour
 	{
 		LogFunAdr("CUserCmd::Reset", p_CUserCmd__Reset.GetPtr());
 		LogFunAdr("CUserCmd::Copy", p_CUserCmd__Copy.GetPtr());
+		LogFunAdr("ReadUserCmd", p_ReadUserCmd.GetPtr());
 	}
 	virtual void GetFun(void) const
 	{
@@ -77,11 +78,14 @@ class VUserCmd : public IDetour
 
 		p_CUserCmd__Copy = g_GameDll.FindPatternSIMD("E8 ?? ?? ?? ?? 4C 8B 9B ?? ?? ?? ??").FollowNearCallSelf();
 		v_CUserCmd__Copy = p_CUserCmd__Copy.RCast<CUserCmd* (*)(CUserCmd*, CUserCmd*)>();
+
+		p_ReadUserCmd = g_GameDll.FindPatternSIMD("E8 ?? ?? ?? ?? 4C 8B C6 48 81 C6 ?? ?? ?? ??").FollowNearCallSelf();
+		v_ReadUserCmd = p_ReadUserCmd.RCast<int (*)(bf_read*, CUserCmd*, CUserCmd*)>();
 	}
 	virtual void GetVar(void) const { }
 	virtual void GetCon(void) const { }
-	virtual void Attach(void) const { }
-	virtual void Detach(void) const { }
+	virtual void Attach(void) const;
+	virtual void Detach(void) const;
 };
 ///////////////////////////////////////////////////////////////////////////////