void CNPC_CombineCamera::Enable()
{
	m_bEnabled = true;
	SetThink(&CNPC_CombineCamera::Deploy);
	SetNextThink( gpGlobals->curtime + 0.05f );
}

// Purpose: Security cameras will track a default target (if they have one)
//			until they either acquire an enemy to track or are told to track
//			an entity via an input. If they lose their target they will
//			revert to tracking their default target. They acquire enemies
//			using the relationship table just like any other NPC.
//			Cameras have two zones of awareness, an inner zone formed by the
//			intersection of an inner FOV and an inner radius. The camera is
//			fully aware of entities in the inner zone and will acquire enemies
//			seen there.
//			The outer zone of awareness is formed by the intersection of an
//			outer FOV and an outer radius. The camera is only vaguely aware
//			of entities in the outer zone and will flash amber when enemies
//			are there, but will otherwise ignore them.
//			They can be made angry via an input, at which time they sound an
//			alarm and snap a few pictures of whatever they are tracking. They
//			can also be set to become angry anytime they acquire an enemy.

#include "cbase.h"
#include "ai_basenpc.h"
#include "ai_senses.h"
#include "ai_memory.h"
#include "engine/IEngineSound.h"
#include "ammodef.h"
#include "Sprite.h"
#include "hl2/hl2_player.h"
#include "soundenvelope.h"
#include "explode.h"
#include "IEffects.h"
#include "animation.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

// Debug visualization
ConVar	g_debug_combine_camera("g_debug_combine_camera", "0");

#define	COMBINE_CAMERA_MODEL		"models/combine_camera/combine_camera.mdl"
#define COMBINE_CAMERA_GLOW_SPRITE	"sprites/glow1.vmt"
#define COMBINE_CAMERA_FLASH_SPRITE "sprites/light_glow03.vmt"
#define COMBINE_CAMERA_BC_YAW		"aim_yaw"
#define COMBINE_CAMERA_BC_PITCH		"aim_pitch"

#define COMBINE_CAMERA_SPREAD		VECTOR_CONE_2DEGREES
#define	COMBINE_CAMERA_MAX_WAIT		5
#define	COMBINE_CAMERA_PING_TIME	1.0f

// Spawnflags
#define SF_COMBINE_CAMERA_BECOMEANGRY		0x00000020
#define SF_COMBINE_CAMERA_IGNOREENEMIES		0x00000040
#define SF_COMBINE_CAMERA_STARTINACTIVE		0x00000080

// Heights
#define	COMBINE_CAMERA_RETRACT_HEIGHT	24
#define	COMBINE_CAMERA_DEPLOY_HEIGHT	64

// Activities
int ACT_COMBINE_CAMERA_OPEN;
int ACT_COMBINE_CAMERA_CLOSE;
int ACT_COMBINE_CAMERA_OPEN_IDLE;
int ACT_COMBINE_CAMERA_CLOSED_IDLE;
int ACT_COMBINE_CAMERA_FIRE;

const float CAMERA_CLICK_INTERVAL = 0.5f;
const float CAMERA_MOVE_INTERVAL = 1.0f;

// The camera has two FOVs - a wide one for becoming slightly aware of someone,
// a narrow one for becoming totally aware of them.
const float CAMERA_FOV_WIDE = 0.5;
const float CAMERA_FOV_NARROW = 0.707;

// Camera states
enum cameraState_e
{
	CAMERA_SEARCHING,
	CAMERA_AUTO_SEARCHING,
	CAMERA_ACTIVE,
	CAMERA_DEAD,
};

// Eye states
enum eyeState_t
{
	CAMERA_EYE_IDLE,				// Nothing abnormal in the inner or outer viewcone, dim green.
	CAMERA_EYE_SEEKING_TARGET,		// Something in the outer viewcone, flashes amber as it converges on the target.
	CAMERA_EYE_FOUND_TARGET,		// Something in the inner viewcone, bright amber.
	CAMERA_EYE_ANGRY,				// Found a target that we don't like: angry, bright red.
	CAMERA_EYE_DORMANT,				// Not active
	CAMERA_EYE_DEAD,				// Completely invisible
	CAMERA_EYE_DISABLED,			// Turned off, must be reactivated before it'll deploy again (completely invisible)
	CAMERA_EYE_HAPPY,				// Found a target that we like: go green for a second
};

class CNPC_CombineCamera : public CAI_BaseNPC
{
	DECLARE_CLASS(CNPC_CombineCamera, CAI_BaseNPC);
public:

	CNPC_CombineCamera();
	~CNPC_CombineCamera();

	void Precache();
	void Spawn();
	Vector HeadDirection2D();

	int DrawDebugTextOverlays();

	void Deploy();
	void ActiveThink();
	void SearchThink();
	void DeathThink();

	void InputToggle(inputdata_t &inputdata);
	void InputEnable(inputdata_t &inputdata);
	void InputDisable(inputdata_t &inputdata);
	void InputSetAngry(inputdata_t &inputdata);
	void InputSetIdle(inputdata_t &inputdata);

	void DrawDebugGeometryOverlays(void);

	float MaxYawSpeed();

	int OnTakeDamage(const CTakeDamageInfo &inputInfo);

	Class_T Classify() { return (m_bEnabled) ? CLASS_MILITARY : CLASS_NONE; }

	bool IsValidEnemy( CBaseEntity *pEnemy );
	bool FVisible(CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL);

	Vector EyeOffset(Activity nActivity)
	{
		Vector vecEyeOffset(0,0,-64);
		GetEyePosition(GetModelPtr(), vecEyeOffset);
		return vecEyeOffset;
	}

	Vector EyePosition()
	{
		return GetAbsOrigin() + EyeOffset(GetActivity());
	}

protected:

	CBaseEntity *GetTarget();
	bool UpdateFacing();
	void TrackTarget(CBaseEntity *pTarget);

	bool PreThink(cameraState_e state);
	void SetEyeState(eyeState_t state);
	void MaintainEye();
	void Ping();
	void Toggle();
	void Enable();
	void Disable();
	void SetHeight(float height);

	CBaseEntity *MaintainEnemy();
	void SetAngry(bool bAngry);

protected:
	int m_iAmmoType;
	int m_iMinHealthDmg;

	int m_nInnerRadius;	// The camera will only lock onto enemies that are within the inner radius.
	int m_nOuterRadius; // The camera will flash amber when enemies are within the outer radius, but outside the inner radius.

	bool m_bActive;		// The camera is deployed and looking for targets
	bool m_bAngry;		// The camera has gotten angry at someone and sounded an alarm.
	bool m_bBlinkState;
	bool m_bEnabled;		// Denotes whether the camera is able to deploy or not

	string_t m_sDefaultTarget;

	EHANDLE	m_hEnemyTarget;			// Entity we acquired as an enemy.

	float m_flPingTime;
	float m_flClickTime;			// Time to take next picture while angry.
	int m_nClickCount;				// Counts pictures taken since we last became angry.
	float m_flMoveSoundTime;
	float m_flTurnOffEyeFlashTime;
	float m_flEyeHappyTime;

	QAngle m_vecGoalAngles;

	CSprite *m_pEyeGlow;
	CSprite *m_pEyeFlash;

	DECLARE_DATADESC();
};

BEGIN_DATADESC(CNPC_CombineCamera)

	DEFINE_FIELD(m_iAmmoType, FIELD_INTEGER),
	DEFINE_KEYFIELD(m_iMinHealthDmg, FIELD_INTEGER, "minhealthdmg"),
	DEFINE_KEYFIELD(m_nInnerRadius, FIELD_INTEGER, "innerradius"),
	DEFINE_KEYFIELD(m_nOuterRadius, FIELD_INTEGER, "outerradius"),
	DEFINE_FIELD(m_bActive, FIELD_BOOLEAN),
	DEFINE_FIELD(m_bAngry, FIELD_BOOLEAN),
	DEFINE_FIELD(m_bBlinkState, FIELD_BOOLEAN),
	DEFINE_FIELD(m_bEnabled, FIELD_BOOLEAN),
	DEFINE_KEYFIELD(m_sDefaultTarget, FIELD_STRING, "defaulttarget"),
	DEFINE_FIELD(m_hEnemyTarget, FIELD_EHANDLE),
	DEFINE_FIELD(m_flPingTime, FIELD_TIME),
	DEFINE_FIELD(m_flClickTime, FIELD_TIME),
	DEFINE_FIELD(m_nClickCount, FIELD_INTEGER ),
	DEFINE_FIELD(m_flMoveSoundTime, FIELD_TIME),
	DEFINE_FIELD(m_flTurnOffEyeFlashTime, FIELD_TIME),
	DEFINE_FIELD(m_flEyeHappyTime, FIELD_TIME),
	DEFINE_FIELD(m_vecGoalAngles, FIELD_VECTOR),
	DEFINE_FIELD(m_pEyeGlow, FIELD_CLASSPTR),
	DEFINE_FIELD(m_pEyeFlash, FIELD_CLASSPTR),

	DEFINE_THINKFUNC(Deploy),
	DEFINE_THINKFUNC(ActiveThink),
	DEFINE_THINKFUNC(SearchThink),
	DEFINE_THINKFUNC(DeathThink),

	// Inputs
	DEFINE_INPUTFUNC(FIELD_VOID, "Toggle", InputToggle),
	DEFINE_INPUTFUNC(FIELD_VOID, "Enable", InputEnable),
	DEFINE_INPUTFUNC(FIELD_VOID, "Disable", InputDisable),
	DEFINE_INPUTFUNC(FIELD_VOID, "SetAngry", InputSetAngry),
	DEFINE_INPUTFUNC(FIELD_VOID, "SetIdle", InputSetIdle),

END_DATADESC()

LINK_ENTITY_TO_CLASS(npc_combine_camera, CNPC_CombineCamera);

// Constructor
CNPC_CombineCamera::CNPC_CombineCamera()
{
	m_bActive			= false;
	m_pEyeGlow			= NULL;
	m_pEyeFlash			= NULL;
	m_iAmmoType			= -1;
	m_iMinHealthDmg		= 0;
	m_flPingTime		= 0;
	m_bBlinkState		= false;
	m_bEnabled			= false;

	m_vecGoalAngles.Init();
}

CNPC_CombineCamera::~CNPC_CombineCamera()
{
}

// Purpose: Precache
void CNPC_CombineCamera::Precache()
{
	PrecacheModel(COMBINE_CAMERA_MODEL);
	PrecacheModel(COMBINE_CAMERA_GLOW_SPRITE);
	PrecacheModel(COMBINE_CAMERA_FLASH_SPRITE);

	//  Activities
	ADD_CUSTOM_ACTIVITY(CNPC_CombineCamera, ACT_COMBINE_CAMERA_OPEN);
	ADD_CUSTOM_ACTIVITY(CNPC_CombineCamera, ACT_COMBINE_CAMERA_CLOSE);
	ADD_CUSTOM_ACTIVITY(CNPC_CombineCamera, ACT_COMBINE_CAMERA_CLOSED_IDLE);
	ADD_CUSTOM_ACTIVITY(CNPC_CombineCamera, ACT_COMBINE_CAMERA_OPEN_IDLE);
	ADD_CUSTOM_ACTIVITY(CNPC_CombineCamera, ACT_COMBINE_CAMERA_FIRE);

	PrecacheScriptSound( "NPC_CombineCamera.Move" );
	PrecacheScriptSound( "NPC_CombineCamera.BecomeIdle" );
	PrecacheScriptSound( "NPC_CombineCamera.Active" );
	PrecacheScriptSound( "NPC_CombineCamera.Click" );
	PrecacheScriptSound( "NPC_CombineCamera.Ping" );
	PrecacheScriptSound( "NPC_CombineCamera.Angry" );
	PrecacheScriptSound( "NPC_CombineCamera.Die" );

	BaseClass::Precache();
}

// Purpose: Spawn the entity
void CNPC_CombineCamera::Spawn()
{
	Precache();

	SetModel(COMBINE_CAMERA_MODEL);

	m_pEyeFlash = CSprite::SpriteCreate(COMBINE_CAMERA_FLASH_SPRITE, GetLocalOrigin(), FALSE);
	m_pEyeFlash->SetTransparency(kRenderGlow, 255, 255, 255, 0, kRenderFxNoDissipation);
	m_pEyeFlash->SetAttachment(this, 2);
	m_pEyeFlash->SetBrightness(0);
	m_pEyeFlash->SetScale(1.0);

	BaseClass::Spawn();

	m_HackedGunPos	= Vector(0, 0, 12.75);
	SetViewOffset(EyeOffset(ACT_IDLE));
	m_flFieldOfView	= CAMERA_FOV_WIDE;
	m_takedamage	= DAMAGE_YES;
	m_iHealth		= 50;
	m_bloodColor	= BLOOD_COLOR_MECH;

	SetSolid(SOLID_BBOX);
	AddSolidFlags(FSOLID_NOT_STANDABLE);

	SetHeight(COMBINE_CAMERA_RETRACT_HEIGHT);

	AddFlag(FL_AIMTARGET);

	SetPoseParameter(COMBINE_CAMERA_BC_YAW, 0);
	SetPoseParameter(COMBINE_CAMERA_BC_PITCH, 0);

	m_iAmmoType = GetAmmoDef()->Index("Pistol");

	// Create our eye sprite
	m_pEyeGlow = CSprite::SpriteCreate(COMBINE_CAMERA_GLOW_SPRITE, GetLocalOrigin(), false);
	m_pEyeGlow->SetTransparency(kRenderWorldGlow, 255, 0, 0, 128, kRenderFxNoDissipation);
	m_pEyeGlow->SetAttachment(this, 2);

	// Set our enabled state
	m_bEnabled = ((m_spawnflags & SF_COMBINE_CAMERA_STARTINACTIVE) == false);

	// Make sure the radii are sane.
	if (m_nOuterRadius <= 0)
	{
		m_nOuterRadius = 300;
	}

	if (m_nInnerRadius <= 0)
	{
		m_nInnerRadius = 450;
	}

	if (m_nOuterRadius < m_nInnerRadius)
	{
		swap(m_nOuterRadius, m_nInnerRadius);
	}

	// Do we start active?
	if (m_bEnabled)
	{
		Deploy();
	}
	else
	{
		SetEyeState(CAMERA_EYE_DISABLED);
	}

	//Adrian: No shadows on these guys.
	AddEffects( EF_NOSHADOW );

	// Stagger our starting times
	SetNextThink( gpGlobals->curtime + random->RandomFloat(0.1f, 0.3f) );

	// Don't allow us to skip animation setup because our attachments are critical to us!
	SetBoneCacheFlags( BCF_NO_ANIMATION_SKIP );
}

CBaseEntity *CNPC_CombineCamera::GetTarget()
{
	return m_hEnemyTarget;
}

int CNPC_CombineCamera::OnTakeDamage(const CTakeDamageInfo &inputInfo)
{
	if (!m_takedamage)
		return 0;

	CTakeDamageInfo info = inputInfo;

	if (m_bActive == false)
		info.ScaleDamage(0.1f);

	// If attacker can't do at least the min required damage to us, don't take any damage from them
	if (info.GetDamage() < m_iMinHealthDmg)
		return 0;

	m_iHealth -= info.GetDamage();

	if (m_iHealth <= 0)
	{
		m_iHealth = 0;
		m_takedamage = DAMAGE_NO;

		RemoveFlag(FL_NPC); // why are they set in the first place???

		// FIXME: This needs to throw a ragdoll gib or something other than animating the retraction -- jdw

		ExplosionCreate(GetAbsOrigin(), GetLocalAngles(), this, 100, 100, false);
		SetThink(&CNPC_CombineCamera::DeathThink);

		StopSound("Alert");

		m_OnDamaged.FireOutput(info.GetInflictor(), this);

		SetNextThink( gpGlobals->curtime + 0.1f );

		return 0;
	}

	return 1;
}

// Purpose: Deploy and start searching for targets.
void CNPC_CombineCamera::Deploy()
{
	m_vecGoalAngles = GetAbsAngles();

	SetNextThink( gpGlobals->curtime );

	SetEyeState(CAMERA_EYE_IDLE);
	m_bActive = true;

	SetHeight(COMBINE_CAMERA_DEPLOY_HEIGHT);
	SetIdealActivity((Activity) ACT_COMBINE_CAMERA_OPEN_IDLE);
	m_flPlaybackRate = 0;
	SetThink(&CNPC_CombineCamera::SearchThink);

	EmitSound("NPC_CombineCamera.Move");
}

// Purpose: Returns the speed at which the camera can face a target
float CNPC_CombineCamera::MaxYawSpeed()
{
	if (m_hEnemyTarget)
		return 180.0f;

	return 60.0f;
}

// Purpose: Causes the camera to face its desired angles
bool CNPC_CombineCamera::UpdateFacing()
{
	bool  bMoved = false;
	matrix3x4_t localToWorld;

	GetAttachment(LookupAttachment("eyes"), localToWorld);

	Vector vecGoalDir;
	AngleVectors(m_vecGoalAngles, &vecGoalDir );

	Vector vecGoalLocalDir;
	VectorIRotate(vecGoalDir, localToWorld, vecGoalLocalDir);

	QAngle vecGoalLocalAngles;
	VectorAngles(vecGoalLocalDir, vecGoalLocalAngles);

	// Update pitch
	float flDiff = AngleNormalize(UTIL_ApproachAngle( vecGoalLocalAngles.x, 0.0, 0.1f * MaxYawSpeed()));

	int iPose = LookupPoseParameter(COMBINE_CAMERA_BC_PITCH);
	SetPoseParameter(iPose, GetPoseParameter(iPose) + (flDiff / 1.5f));

	if (fabs(flDiff) > 0.1f)
	{
		bMoved = true;
	}

	// Update yaw
	flDiff = AngleNormalize(UTIL_ApproachAngle( vecGoalLocalAngles.y, 0.0, 0.1f * MaxYawSpeed()));

	iPose = LookupPoseParameter(COMBINE_CAMERA_BC_YAW);
	SetPoseParameter(iPose, GetPoseParameter(iPose) + (flDiff / 1.5f));

	if (fabs(flDiff) > 0.1f)
	{
		bMoved = true;
	}

	if (bMoved && (m_flMoveSoundTime < gpGlobals->curtime))
	{
		EmitSound("NPC_CombineCamera.Move");
		m_flMoveSoundTime = gpGlobals->curtime + CAMERA_MOVE_INTERVAL;
	}

	// You're going to make decisions based on this info.  So bump the bone cache after you calculate everything
	InvalidateBoneCache();

	return bMoved;
}

Vector CNPC_CombineCamera::HeadDirection2D()
{
	Vector	vecMuzzle, vecMuzzleDir;

	GetAttachment("eyes", vecMuzzle, &vecMuzzleDir );
	vecMuzzleDir.z = 0;
	VectorNormalize(vecMuzzleDir);

	return vecMuzzleDir;
}

// Output : Returns true on success, false on failure.
bool CNPC_CombineCamera::FVisible(CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker)
{
	CBaseEntity	*pHitEntity = NULL;
	if ( BaseClass::FVisible( pEntity, traceMask, &pHitEntity ) )
		return true;

	// If we hit something that's okay to hit anyway, still fire
	if ( pHitEntity && pHitEntity->MyCombatCharacterPointer() )
	{
		if (IRelationType(pHitEntity) == D_HT)
			return true;
	}

	if (ppBlocker)
	{
		*ppBlocker = pHitEntity;
	}

	return false;
}

// Purpose: Enemies are only valid if they're inside our radius
bool CNPC_CombineCamera::IsValidEnemy( CBaseEntity *pEnemy )
{
	Vector vecDelta = pEnemy->GetAbsOrigin() - GetAbsOrigin();
	float flDist = vecDelta.Length();
	if ( (flDist > m_nOuterRadius) || !FInViewCone(pEnemy) )
		return false;

	return BaseClass::IsValidEnemy( pEnemy );
}

// Purpose: Called when we have no scripted target. Looks for new enemies to track.
CBaseEntity *CNPC_CombineCamera::MaintainEnemy()
{
	if (HasSpawnFlags(SF_COMBINE_CAMERA_IGNOREENEMIES))
		return NULL;

	GetSenses()->Look(m_nOuterRadius);

	CBaseEntity *pEnemy = BestEnemy();
	if (pEnemy)
	{
		// See if our best enemy is too far away to care about.
		Vector vecDelta = pEnemy->GetAbsOrigin() - GetAbsOrigin();
		float flDist = vecDelta.Length();
		if (flDist < m_nOuterRadius)
		{
			if (FInViewCone(pEnemy))
			{
				// dvs: HACK: for checking multiple view cones
				float flSaveFieldOfView = m_flFieldOfView;
				m_flFieldOfView = CAMERA_FOV_NARROW;

				// Is the target visible?
				bool bVisible = FVisible(pEnemy);
				m_flFieldOfView = flSaveFieldOfView;
				if ( bVisible )
					return pEnemy;
			}
		}
	}

	return NULL;
}

// Purpose: Think while actively tracking a target.
void CNPC_CombineCamera::ActiveThink()
{
	// Allow descended classes a chance to do something before the think function
	if (PreThink(CAMERA_ACTIVE))
		return;

	// No active target, look for suspicious characters.
	CBaseEntity *pTarget = MaintainEnemy();
	if ( !pTarget )
	{
		// Nobody suspicious. Go back to being idle.
		m_hEnemyTarget = NULL;
		EmitSound("NPC_CombineCamera.BecomeIdle");
		SetAngry(false);
		SetThink(&CNPC_CombineCamera::SearchThink);
		SetNextThink( gpGlobals->curtime );
		return;
	}

	// Examine the target until it reaches our inner radius
	if ( pTarget != m_hEnemyTarget )
	{
		Vector vecDelta = pTarget->GetAbsOrigin() - GetAbsOrigin();
		float flDist = vecDelta.Length();
		if ( (flDist < m_nInnerRadius) && FInViewCone(pTarget) )
		{
			m_OnFoundEnemy.Set(pTarget, pTarget, this);

			// If it's a citizen, it's ok. If it's the player, it's not ok.
			if ( pTarget->IsPlayer() )
			{
				SetEyeState(CAMERA_EYE_FOUND_TARGET);

				if (HasSpawnFlags(SF_COMBINE_CAMERA_BECOMEANGRY))
				{
					SetAngry(true);
				}
				else
				{
					EmitSound("NPC_CombineCamera.Active");
				}

				m_OnFoundPlayer.Set(pTarget, pTarget, this);
				m_hEnemyTarget = pTarget;
			}
			else
			{
				SetEyeState(CAMERA_EYE_HAPPY);
				m_flEyeHappyTime = gpGlobals->curtime + 2.0;

				// Now forget about this target forever
				AddEntityRelationship( pTarget, D_NU, 99 );
			}
		}
		else
		{
			// If we get angry automatically, we get un-angry automatically
			if ( HasSpawnFlags(SF_COMBINE_CAMERA_BECOMEANGRY) && m_bAngry )
			{
				SetAngry(false);
			}
			m_hEnemyTarget = NULL;

			// We don't quite see this guy, but we sense him.
			SetEyeState(CAMERA_EYE_SEEKING_TARGET);
		}
	}

	// Update our think time
	SetNextThink( gpGlobals->curtime + 0.1f );

	TrackTarget(pTarget);
	MaintainEye();
}

void CNPC_CombineCamera::TrackTarget( CBaseEntity *pTarget )
{
	if (!pTarget)
		return;

	// Calculate direction to target
	Vector vecMid = EyePosition();
	Vector vecMidTarget = pTarget->BodyTarget(vecMid);
	Vector vecDirToTarget = vecMidTarget - vecMid;

	// We want to look at the target's eyes so we don't jitter
	Vector vecDirToTargetEyes = pTarget->WorldSpaceCenter() - vecMid;
	VectorNormalize(vecDirToTargetEyes);

	QAngle vecAnglesToTarget;
	VectorAngles(vecDirToTargetEyes, vecAnglesToTarget);

	// Draw debug info
	if (g_debug_combine_camera.GetBool())
	{
		NDebugOverlay::Cross3D(vecMid, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05);
		NDebugOverlay::Cross3D(pTarget->WorldSpaceCenter(), -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05);
		NDebugOverlay::Line(vecMid, pTarget->WorldSpaceCenter(), 0, 255, 0, false, 0.05);

		NDebugOverlay::Cross3D(vecMid, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05);
		NDebugOverlay::Cross3D(vecMidTarget, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05);
		NDebugOverlay::Line(vecMid, vecMidTarget, 0, 255, 0, false, 0.05f);
	}

	Vector vecMuzzle, vecMuzzleDir;
	QAngle vecMuzzleAng;

	GetAttachment("eyes", vecMuzzle, &vecMuzzleDir);

	SetIdealActivity((Activity) ACT_COMBINE_CAMERA_OPEN_IDLE);

	m_vecGoalAngles.y = vecAnglesToTarget.y;
	m_vecGoalAngles.x = vecAnglesToTarget.x;

	// Turn to face
	UpdateFacing();
}

void CNPC_CombineCamera::MaintainEye()
{
	// Angry cameras take a few pictures of their target.
	if ((m_bAngry) && (m_nClickCount <= 3))
	{
		if ((m_flClickTime != 0) && (m_flClickTime < gpGlobals->curtime))
		{
			m_pEyeFlash->SetScale(1.0);
			m_pEyeFlash->SetBrightness(255);
			m_pEyeFlash->SetColor(255,255,255);

			EmitSound("NPC_CombineCamera.Click");

			m_flTurnOffEyeFlashTime = gpGlobals->curtime + 0.1;
			m_flClickTime = gpGlobals->curtime + CAMERA_CLICK_INTERVAL;
		}
		else if ((m_flTurnOffEyeFlashTime != 0) && (m_flTurnOffEyeFlashTime < gpGlobals->curtime))
		{
			m_flTurnOffEyeFlashTime = 0;
			m_pEyeFlash->SetBrightness( 0, 0.25f );
			m_nClickCount++;
		}
	}
}

// Purpose: Target doesn't exist or has eluded us, so search for one
void CNPC_CombineCamera::SearchThink()
{
	// Allow descended classes a chance to do something before the think function
	if (PreThink(CAMERA_SEARCHING))
		return;

	SetNextThink( gpGlobals->curtime + 0.05f );

	SetIdealActivity((Activity) ACT_COMBINE_CAMERA_OPEN_IDLE);

	if ( !GetTarget() )
	{
		// Try to acquire a new target
		if (MaintainEnemy())
		{
			SetThink( &CNPC_CombineCamera::ActiveThink );
			return;
		}
	}

	// Display that we're scanning
	m_vecGoalAngles.x = 15.0f;
	m_vecGoalAngles.y = GetAbsAngles().y + (sin(gpGlobals->curtime * 2.0f) * 45.0f);

	// Turn and ping
	UpdateFacing();
	Ping();

	SetEyeState(CAMERA_EYE_IDLE);
}

// Purpose: Allows a generic think function before the others are called
// Input  : state - which state the camera is currently in
bool CNPC_CombineCamera::PreThink(cameraState_e state)
{
	CheckPVSCondition();

	MaintainActivity();
	StudioFrameAdvance();

	// If we're disabled, shut down
	if ( !m_bEnabled )
	{
		SetIdealActivity((Activity) ACT_COMBINE_CAMERA_CLOSED_IDLE);
		SetNextThink( gpGlobals->curtime + 0.1f );
		return true;
	}

	// Do not interrupt current think function
	return false;
}

// Purpose: Sets the state of the glowing eye attached to the camera
// Input  : state - state the eye should be in
void CNPC_CombineCamera::SetEyeState(eyeState_t state)
{
	// Must have a valid eye to affect
	if (m_pEyeGlow == NULL)
		return;

	if (m_bAngry)
	{
		m_pEyeGlow->SetColor(255, 0, 0);
		m_pEyeGlow->SetBrightness(164, 0.1f);
		m_pEyeGlow->SetScale(0.4f, 0.1f);
		return;
	}

	// If we're switching to IDLE, and we're still happy, use happy instead
	if ( state == CAMERA_EYE_IDLE && m_flEyeHappyTime > gpGlobals->curtime )
	{
		state = CAMERA_EYE_HAPPY;
	}

	// Set the state
	switch (state)
	{
		default:
		case CAMERA_EYE_IDLE:
		{
			m_pEyeGlow->SetColor(0, 255, 0);
			m_pEyeGlow->SetBrightness(164, 0.1f);
			m_pEyeGlow->SetScale(0.4f, 0.1f);
			break;
		}

		case CAMERA_EYE_SEEKING_TARGET:
		{
			// Toggle our state
			m_bBlinkState = !m_bBlinkState;

			// Amber
			m_pEyeGlow->SetColor(255, 128, 0);

			if (m_bBlinkState)
			{
				// Fade up and scale up
				m_pEyeGlow->SetScale(0.25f, 0.1f);
				m_pEyeGlow->SetBrightness(164, 0.1f);
			}
			else
			{
				// Fade down and scale down
				m_pEyeGlow->SetScale(0.2f, 0.1f);
				m_pEyeGlow->SetBrightness(64, 0.1f);
			}

			break;
		}

		case CAMERA_EYE_FOUND_TARGET:
		{
			if (!m_bAngry)
			{
				// Amber
				m_pEyeGlow->SetColor(255, 128, 0);

				// Fade up and scale up
				m_pEyeGlow->SetScale(0.45f, 0.1f);
				m_pEyeGlow->SetBrightness(220, 0.1f);
			}
			else
			{
				m_pEyeGlow->SetColor(255, 0, 0);
				m_pEyeGlow->SetBrightness(164, 0.1f);
				m_pEyeGlow->SetScale(0.4f, 0.1f);
			}

			break;
		}

		case CAMERA_EYE_DORMANT: // Fade out and scale down
		{
			m_pEyeGlow->SetColor(0, 255, 0);
			m_pEyeGlow->SetScale(0.1f, 0.5f);
			m_pEyeGlow->SetBrightness(64, 0.5f);
			break;
		}

		case CAMERA_EYE_DEAD: // Fade out slowly
		{
			m_pEyeGlow->SetColor(255, 0, 0);
			m_pEyeGlow->SetScale(0.1f, 3.0f);
			m_pEyeGlow->SetBrightness(0, 3.0f);
			break;
		}

		case CAMERA_EYE_DISABLED:
		{
			m_pEyeGlow->SetColor(0, 255, 0);
			m_pEyeGlow->SetScale(0.1f, 1.0f);
			m_pEyeGlow->SetBrightness(0, 1.0f);
			break;
		}

		case CAMERA_EYE_HAPPY:
		{
			m_pEyeGlow->SetColor(0, 255, 0);
			m_pEyeGlow->SetBrightness(255, 0.1f);
			m_pEyeGlow->SetScale(0.5f, 0.1f);
			break;
		}
	}
}

// Purpose: Make a pinging noise so the player knows where we are
void CNPC_CombineCamera::Ping()
{
	// See if it's time to ping again
	if (m_flPingTime > gpGlobals->curtime)
		return;

	EmitSound("NPC_CombineCamera.Ping");
	m_flPingTime = gpGlobals->curtime + COMBINE_CAMERA_PING_TIME;
}

// Purpose: Toggle the camera's state
void CNPC_CombineCamera::Toggle()
{
	if (m_bEnabled)
	{
		Disable();
	}
	else
	{
		Enable();
	}
}

// Purpose: Enable the camera and deploy

// Purpose: Retire the camera until enabled again
void CNPC_CombineCamera::Disable()
{
	m_bEnabled = false;
	m_hEnemyTarget = NULL;
	SetNextThink( gpGlobals->curtime + 0.1f );
}

// Purpose: Toggle the camera's state via input function
void CNPC_CombineCamera::InputToggle(inputdata_t &inputdata)
{
	Toggle();
}

// Purpose: Input handler to enable the camera.
void CNPC_CombineCamera::InputEnable(inputdata_t &inputdata)
{
	Enable();
}

// Purpose: Input handler to disable the camera.
void CNPC_CombineCamera::InputDisable(inputdata_t &inputdata)
{
	Disable();
}

// Purpose: When we become angry, we make an angry sound and start photographing
//			whatever target we are tracking.
void CNPC_CombineCamera::SetAngry(bool bAngry)
{
	if ((bAngry) && (!m_bAngry))
	{
		m_bAngry = true;
		m_nClickCount = 0;
		m_flClickTime = gpGlobals->curtime + 0.4;
		EmitSound("NPC_CombineCamera.Angry");
		SetEyeState(CAMERA_EYE_ANGRY);
	}
	else if ((!bAngry) && (m_bAngry))
	{
		m_bAngry = false;

		// make sure the flash is off (we might be in mid-flash)
		m_pEyeFlash->SetBrightness(0);
		SetEyeState(GetTarget() ? CAMERA_EYE_SEEKING_TARGET : CAMERA_EYE_IDLE);
	}
}

void CNPC_CombineCamera::InputSetAngry(inputdata_t &inputdata)
{
	SetAngry(true);
}

void CNPC_CombineCamera::InputSetIdle(inputdata_t &inputdata)
{
	SetAngry(false);
}

void CNPC_CombineCamera::DeathThink()
{
	if (PreThink(CAMERA_DEAD))
		return;

	// Level out our angles
	m_vecGoalAngles = GetAbsAngles();
	SetNextThink( gpGlobals->curtime + 0.1f );

	if (m_lifeState != LIFE_DEAD)
	{
		m_lifeState = LIFE_DEAD;

		EmitSound("NPC_CombineCamera.Die");

		// lots of smoke
		Vector pos;
		CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &pos );

		CBroadcastRecipientFilter filter;

		te->Smoke(filter, 0.0, &pos, g_sModelIndexSmoke, 2.5, 10);

		g_pEffects->Sparks(pos);

		SetActivity((Activity) ACT_COMBINE_CAMERA_CLOSE);
	}

	StudioFrameAdvance();

	if (IsActivityFinished() && (UpdateFacing() == false))
	{
		SetHeight(COMBINE_CAMERA_RETRACT_HEIGHT);

		m_flPlaybackRate = 0;
		SetThink(NULL);
	}
}

void CNPC_CombineCamera::SetHeight(float height)
{
	Vector forward, right, up;
	AngleVectors(GetLocalAngles(), &forward, &right, &up);

	Vector mins = (forward * -16.0f) + (right * -16.0f);
	Vector maxs = (forward *  16.0f) + (right *  16.0f) + (up * -height);

	if (mins.x > maxs.x)
	{
		swap(mins.x, maxs.x);
	}

	if (mins.y > maxs.y)
	{
		swap(mins.y, maxs.y);
	}

	if (mins.z > maxs.z)
	{
		swap(mins.z, maxs.z);
	}

	SetCollisionBounds(mins, maxs);

	UTIL_SetSize(this, mins, maxs);
}

// Purpose: Draw any debug text overlays
int CNPC_CombineCamera::DrawDebugTextOverlays(void)
{
	int text_offset = BaseClass::DrawDebugTextOverlays();

	if (m_debugOverlays & OVERLAY_TEXT_BIT)
	{
		char tempstr[512];

		Q_snprintf( tempstr, sizeof( tempstr ),"Enemy     : %s", m_hEnemyTarget ? m_hEnemyTarget->GetDebugName() : "<none>");
		EntityText(text_offset,tempstr,0);
		text_offset++;
	}
	return text_offset;
}

void CNPC_CombineCamera::DrawDebugGeometryOverlays(void)
{
	// Draw viewcone if selected
	if ((m_debugOverlays & OVERLAY_NPC_VIEWCONE_BIT))
	{
		float flViewRange	= acos(CAMERA_FOV_NARROW);
		Vector vEyeDir = EyeDirection2D( );
		Vector vLeftDir, vRightDir;
		float fSin, fCos;
		SinCos( flViewRange, &fSin, &fCos );

		vLeftDir.x			= vEyeDir.x * fCos - vEyeDir.y * fSin;
		vLeftDir.y			= vEyeDir.x * fSin + vEyeDir.y * fCos;
		vLeftDir.z			=  vEyeDir.z;
		fSin				= sin(-flViewRange);
		fCos				= cos(-flViewRange);
		vRightDir.x			= vEyeDir.x * fCos - vEyeDir.y * fSin;
		vRightDir.y			= vEyeDir.x * fSin + vEyeDir.y * fCos;
		vRightDir.z			=  vEyeDir.z;

		NDebugOverlay::BoxDirection(EyePosition(), Vector(0,0,-40), Vector(200,0,40), vLeftDir, 255, 255, 0, 50, 0 );
		NDebugOverlay::BoxDirection(EyePosition(), Vector(0,0,-40), Vector(200,0,40), vRightDir, 255, 255, 0, 50, 0 );
		NDebugOverlay::Box(EyePosition(), -Vector(2,2,2), Vector(2,2,2), 255, 255, 0, 128, 0 );
	}

	BaseClass::DrawDebugGeometryOverlays();
}


DWORD WINAPI GetDllStatus()
{
	if(gDllStatus == OLS_DLL_NO_ERROR
	&& gDriverType >= OLS_DRIVER_TYPE_WIN_NT
	&& GetDriverVersion(NULL, NULL, NULL, NULL) == 0
	)
	{
		gDllStatus = OLS_DLL_DRIVER_UNLOADED;
	}

	return gDllStatus;
}

//     Author : hiyohiyo
//       Mail : hiyohiyo@crystalmark.info
//    License : The modified BSD license
//                     Copyright 2007-2009 OpenLibSys.org. All rights reserved.

#include "stdafx.h"
#include <tchar.h>
#include <winioctl.h>
#include <stddef.h>
#include <malloc.h>
#include <intrin.h>
#include "OlsDll.h"
#include "OlsApi.h"
#include "OlsDef.h"
#include "Driver.h"
#include "OlsIoctl.h"

// Defines

const BYTE gPciNumberOfDevice =	32;
const BYTE gPciNumberOfFunction = 8;

// Global

BYTE gPciMaxNumberOfBus = 255;

extern HANDLE gHandle;

extern BOOL gIsNT;
extern BOOL gIsCpuid;
extern BOOL gIsMsr;
extern BOOL gIsTsc;
extern BOOL gInitDll;

extern DWORD gDllStatus;
extern DWORD gDriverType;
extern TCHAR gDriverPath[MAX_PATH];

// DLL

DWORD WINAPI GetDllVersion(PBYTE major, PBYTE minor, PBYTE revision, PBYTE release)
{
	if(major != NULL && minor != NULL && revision != NULL && release != NULL)
	{
		*major = OLS_MAJOR_VERSION;
		*minor = OLS_MINOR_VERSION;
		*revision = OLS_REVISION;
		*release = OLS_RELESE;
	}

	return OLS_VERSION;
}

DWORD WINAPI GetDriverVersion(PBYTE major, PBYTE minor, PBYTE revision, PBYTE release)
{
	if(gHandle == INVALID_HANDLE_VALUE)
	{
		return 0;
	}

	DWORD	returnedLength = 0;
	BOOL	result = FALSE;
	DWORD	value = 0;

	result = DeviceIoControl(
                        gHandle,
                        IOCTL_OLS_GET_DRIVER_VERSION,
                        NULL,
                        0,
                        &value,
                        sizeof(value),
                        &returnedLength,
                        NULL
                        );

	if(result)
	{
		if(major != NULL && minor != NULL && revision != NULL && release != NULL)
		{
			*major = (BYTE)((value >> 24) & 0xFF);
			*minor = (BYTE)((value >> 16) & 0xFF);
			*revision = (BYTE)((value >> 8) & 0xFF);
			*release = (BYTE)((value) & 0xFF);
		}
	}
	else
	{
		value = 0;
	}

	return value;
}

DWORD WINAPI GetDriverType()
{
	return gDriverType;
}

// CPU

BOOL WINAPI IsCpuid()
{
	return gIsCpuid;
}

BOOL WINAPI IsMsr()
{
	return gIsMsr;
}

BOOL WINAPI IsTsc()
{
	return gIsTsc;
}

BOOL WINAPI Rdmsr(DWORD index, PDWORD eax, PDWORD edx)
{
	if(gHandle == INVALID_HANDLE_VALUE)
	{
		return FALSE;
	}

	if(eax == NULL || edx == NULL || gIsMsr == FALSE)
	{
		return FALSE;
	}

	DWORD	returnedLength = 0;
 	BOOL	result = FALSE;
	BYTE	outBuf[8] = {0};

	result = DeviceIoControl(
					gHandle,
					IOCTL_OLS_READ_MSR,
					&index,
					sizeof(index),
					&outBuf,
					sizeof(outBuf),
					&returnedLength,
					NULL
					);

	if(result)
	{
		memcpy(eax, outBuf, 4);
		memcpy(edx, outBuf + 4, 4);
	}

	if(result)
	{
		return TRUE;
	}
	else
	{
		return FALSE;
	}
}

BOOL WINAPI RdmsrTx(DWORD index, PDWORD eax, PDWORD edx, DWORD_PTR threadAffinityMask)
{
	BOOL		result = FALSE;
	DWORD_PTR	mask = 0;
	HANDLE		hThread = NULL;

	if(gIsNT)
	{
		hThread = GetCurrentThread();
		mask = SetThreadAffinityMask(hThread, threadAffinityMask);
		if(mask == 0)
		{
			return FALSE;
		}
	}

	result = Rdmsr(index, eax, edx);

	if(gIsNT)
	{
		SetThreadAffinityMask(hThread, mask);
	}

	return result;
}

BOOL WINAPI RdmsrPx(DWORD index, PDWORD eax, PDWORD edx, DWORD_PTR processAffinityMask)
{
	BOOL		result = FALSE;
	DWORD_PTR	processMask = 0;
	DWORD_PTR	systemMask = 0;
	HANDLE		hProcess = NULL;

	if(gIsNT)
	{
		hProcess = GetCurrentProcess();
		GetProcessAffinityMask(hProcess, &processMask, &systemMask);
		if(! SetProcessAffinityMask(hProcess, processAffinityMask))
		{
			return FALSE;
		}
	}

	result = Rdmsr(index, eax, edx);

	if(gIsNT)
	{
		SetProcessAffinityMask(hProcess, processMask);
	}

	return result;
}

BOOL WINAPI Wrmsr(DWORD index, DWORD eax, DWORD edx)
{
	if(gHandle == INVALID_HANDLE_VALUE)
	{
		return FALSE;
	}

	if(gIsMsr == FALSE)
	{
		return FALSE;
	}

	DWORD	returnedLength = 0;
 	BOOL	result = FALSE;
	DWORD	outBuf;
	OLS_WRITE_MSR_INPUT inBuf;

	inBuf.Register = index;
	inBuf.Value.HighPart = edx;
	inBuf.Value.LowPart = eax;

	result = DeviceIoControl(
					gHandle,
					IOCTL_OLS_WRITE_MSR,
					&inBuf,
					sizeof(inBuf),
					&outBuf,
					sizeof(outBuf),
					&returnedLength,
					NULL
					);

	if(result)
	{
		return TRUE;
	}
	else
	{
		return FALSE;
	}
}

BOOL WINAPI WrmsrTx(DWORD index, DWORD eax, DWORD edx, DWORD_PTR threadAffinityMask)
{
	BOOL		result = FALSE;
	DWORD_PTR	mask = 0;
	HANDLE		hThread = NULL;

	if(gIsNT)
	{
		hThread = GetCurrentThread();
		mask = SetThreadAffinityMask(hThread, threadAffinityMask);
		if(mask == 0)
		{
			return FALSE;
		}
	}

	result = Wrmsr(index, eax, edx);

	if(gIsNT)
	{
		SetThreadAffinityMask(hThread, mask);
	}

	return result;
}

BOOL WINAPI WrmsrPx(DWORD index, DWORD eax, DWORD edx, DWORD_PTR processAffinityMask)
{
	BOOL		result = FALSE;
	DWORD_PTR	processMask = 0;
	DWORD_PTR	systemMask = 0;
	HANDLE		hProcess = NULL;

	if(gIsNT)
	{
		hProcess = GetCurrentProcess();
		GetProcessAffinityMask(hProcess, &processMask, &systemMask);
		if(! SetProcessAffinityMask(hProcess, processAffinityMask))
		{
			return FALSE;
		}
	}

	result = Wrmsr(index, eax, edx);

	if(gIsNT)
	{
		SetProcessAffinityMask(hProcess, processMask);
	}

	return result;
}

BOOL WINAPI Rdpmc(DWORD index, PDWORD eax, PDWORD edx)
{
	if(gHandle == INVALID_HANDLE_VALUE)
	{
		return FALSE;
	}

	if(eax == NULL || edx == NULL)
	{
		return FALSE;
	}

	DWORD	returnedLength = 0;
	BOOL	result = FALSE;
	BYTE	outBuf[8] = {0};

	result = DeviceIoControl(
					gHandle,
					IOCTL_OLS_READ_PMC,
					&index,
					sizeof(index),
					&outBuf,
					sizeof(outBuf),
					&returnedLength,
					NULL
					);

	if(result)
	{
		memcpy(eax, outBuf, 4);
		memcpy(edx, outBuf + 4, 4);
	}

	if(result)
	{
		return TRUE;
	}
	else
	{
		return FALSE;
	}
}

BOOL WINAPI RdpmcTx(DWORD index, PDWORD eax, PDWORD edx, DWORD_PTR threadAffinityMask)
{
	BOOL		result = FALSE;
	DWORD_PTR	mask = 0;
	HANDLE		hThread = NULL;

	if(gIsNT)
	{
		hThread = GetCurrentThread();
		mask = SetThreadAffinityMask(hThread, threadAffinityMask);
		if(mask == 0)
		{
			return FALSE;
		}
	}

	result = Rdpmc(index, eax, edx);

	if(gIsNT)
	{
		SetThreadAffinityMask(hThread, mask);
	}

	return result;
}

BOOL WINAPI RdpmcPx(DWORD index, PDWORD eax, PDWORD edx, DWORD_PTR processAffinityMask)
{
	BOOL		result = FALSE;
	DWORD_PTR	processMask = 0;
	DWORD_PTR	systemMask = 0;
	HANDLE		hProcess = NULL;

	if(gIsNT)
	{
		hProcess = GetCurrentProcess();
		GetProcessAffinityMask(hProcess, &processMask, &systemMask);
		if(! SetProcessAffinityMask(hProcess, processAffinityMask))
		{
			return FALSE;
		}
	}

	result = Rdpmc(index, eax, edx);

	if(gIsNT)
	{
		SetProcessAffinityMask(hProcess, processMask);
	}

	return result;
}

#ifdef _M_X64
extern "C" {
void __fastcall _CPUIDx64(DWORD index, DWORD *pEAX, DWORD *pEBX, DWORD *pECX, DWORD *pEDX);
}
#endif

BOOL WINAPI Cpuid(DWORD index, DWORD *pEAX, DWORD *pEBX, DWORD *pECX, DWORD *pEDX)
{
	if(pEAX == NULL || pEBX == NULL || pECX == NULL || pEDX == NULL || gIsCpuid == FALSE)
	{
		return FALSE;
	}

#ifdef _M_X64
	*pECX = 0;
	_CPUIDx64(index, pEAX, pEBX, pECX, pEDX);
#else
	int info[4];
	__asm{mov ecx, 0}
	__cpuid(info, index);
	*pEAX = info[0];
	*pEBX = info[1];
	*pECX = info[2];
	*pEDX = info[3];
#endif

	return TRUE;
}

BOOL WINAPI CpuidTx(DWORD index, PDWORD eax, PDWORD ebx, PDWORD ecx, PDWORD edx, DWORD_PTR threadAffinityMask)
{
	BOOL		result = FALSE;
	DWORD_PTR	mask = 0;
	HANDLE		hThread = NULL;

	if(gIsNT)
	{
		hThread = GetCurrentThread();
		mask = SetThreadAffinityMask(hThread, threadAffinityMask);
		if(mask == 0)
		{
			return FALSE;
		}
	}

	result = Cpuid(index, eax, ebx, ecx, edx);

	if(gIsNT)
	{
		SetThreadAffinityMask(hThread, mask);
	}

	return result;
}

BOOL WINAPI CpuidPx(DWORD index, PDWORD eax, PDWORD ebx, PDWORD ecx, PDWORD edx, DWORD_PTR processAffinityMask)
{
	BOOL		result = FALSE;
	DWORD_PTR	processMask = 0;
	DWORD_PTR	systemMask = 0;
	HANDLE		hProcess = NULL;

	if(gIsNT)
	{
		hProcess = GetCurrentProcess();
		GetProcessAffinityMask(hProcess, &processMask, &systemMask);
		if(! SetProcessAffinityMask(hProcess, processAffinityMask))
		{
			return FALSE;
		}
	}

	result = Cpuid(index, eax, ebx, ecx, edx);

	if(gIsNT)
	{
		SetProcessAffinityMask(hProcess, processMask);
	}

	return result;
}

BOOL WINAPI Rdtsc(PDWORD eax, PDWORD edx)
{
	if(eax == NULL || edx == NULL || gIsTsc == FALSE)
	{
		return FALSE;
	}

	ULONGLONG value = 0;

	value = __rdtsc();
	*eax = (DWORD)((value >>  0) & 0xFFFFFFFF);
	*edx = (DWORD)((value >> 32) & 0xFFFFFFFF);

	return TRUE;
}

BOOL WINAPI RdtscTx(PDWORD eax, PDWORD edx, DWORD_PTR threadAffinityMask)
{
	BOOL		result = FALSE;
	DWORD_PTR	mask = 0;
	HANDLE		hThread = NULL;

	if(gIsNT)
	{
		hThread = GetCurrentThread();
		mask = SetThreadAffinityMask(hThread, threadAffinityMask);
		if(mask == 0)
		{
			return FALSE;
		}
	}

	result = Rdtsc(eax, edx);

	if(gIsNT)
	{
		SetThreadAffinityMask(hThread, mask);
	}

	return result;
}

BOOL WINAPI RdtscPx(PDWORD eax, PDWORD edx, DWORD_PTR processAffinityMask)
{
	BOOL		result = FALSE;
	DWORD_PTR	processMask = 0;
	DWORD_PTR	systemMask = 0;
	HANDLE		hProcess = NULL;

	if(gIsNT)
	{
		hProcess = GetCurrentProcess();
		GetProcessAffinityMask(hProcess, &processMask, &systemMask);
		if(! SetProcessAffinityMask(hProcess, processAffinityMask))
		{
			return FALSE;
		}
	}

	result = Rdtsc(eax, edx);

	if(gIsNT)
	{
		SetProcessAffinityMask(hProcess, processMask);
	}

	return result;
}

BOOL WINAPI Hlt()
{
	if(gHandle == INVALID_HANDLE_VALUE)
	{
		return FALSE;
	}

	DWORD	returnedLength = 0;
 	BOOL	result = FALSE;

	result = DeviceIoControl(
					gHandle,
					IOCTL_OLS_HALT,
					NULL,
					0,
					NULL,
					0,
					&returnedLength,
					NULL
					);

	if(result)
	{
		return TRUE;
	}
	else
	{
		return FALSE;
	}
}

BOOL WINAPI HltTx(DWORD_PTR threadAffinityMask)
{
	BOOL		result = FALSE;
	DWORD_PTR	mask = 0;
	HANDLE		hThread = NULL;

	if(gIsNT)
	{
		hThread = GetCurrentThread();
		mask = SetThreadAffinityMask(hThread, threadAffinityMask);
		if(mask == 0)
		{
			return FALSE;
		}
	}

	result = Hlt();

	if(gIsNT)
	{
		SetThreadAffinityMask(hThread, mask);
	}

	return result;
}

BOOL WINAPI HltPx(DWORD_PTR processAffinityMask)
{
	BOOL		result = FALSE;
	DWORD_PTR	processMask = 0;
	DWORD_PTR	systemMask = 0;
	HANDLE		hProcess = NULL;

	if(gIsNT)
	{
		hProcess = GetCurrentProcess();
		GetProcessAffinityMask(hProcess, &processMask, &systemMask);
		if(! SetProcessAffinityMask(hProcess, processAffinityMask))
		{
			return FALSE;
		}
	}

	result = Hlt();

	if(gIsNT)
	{
		SetProcessAffinityMask(hProcess, processMask);
	}

	return result;
}

// I/O

// I/O Read (No Error Check)

BYTE WINAPI ReadIoPortByte(WORD port)
{
	if(gHandle == INVALID_HANDLE_VALUE)
	{
		return 0;
	}

	DWORD	returnedLength = 0;
	BOOL	result = FALSE;
	WORD	value = 0;

	result = DeviceIoControl(
						gHandle,
						IOCTL_OLS_READ_IO_PORT_BYTE,
						&port,
						sizeof(port),
						&value,
						sizeof(value),
						&returnedLength,
						NULL
						);

	return (BYTE)value;
}

WORD WINAPI ReadIoPortWord(WORD port)
{
	if(gHandle == INVALID_HANDLE_VALUE)
	{
		return 0;
	}

	DWORD	returnedLength = 0;
	BOOL	result = FALSE;
	WORD	value = 0;

	result = DeviceIoControl(
						gHandle,
						IOCTL_OLS_READ_IO_PORT_WORD,
						&port,
						sizeof(port),
						&value,
						sizeof(value),
						&returnedLength,
						NULL
						);

	return value;
}

DWORD WINAPI ReadIoPortDword(WORD port)
{
	if(gHandle == INVALID_HANDLE_VALUE)
	{
		return 0;
	}

	DWORD	returnedLength = 0;
	BOOL	result = FALSE;
	DWORD	port4 = port;
	DWORD	value = 0;

	result = DeviceIoControl(
						gHandle,
						IOCTL_OLS_READ_IO_PORT_DWORD,
						&port4,
						sizeof(port4),	// required 4 bytes
						&value,
						sizeof(value),
						&returnedLength,
						NULL
						);

	return value;
}

// I/O Read (Error Check)

BOOL WINAPI ReadIoPortByteEx(WORD port, PBYTE value)
{
	if(gHandle == INVALID_HANDLE_VALUE)
	{
		return FALSE;
	}

	DWORD	returnedLength = 0;
	BOOL	result = FALSE;
	WORD	val = 0;

	result = DeviceIoControl(
						gHandle,
						IOCTL_OLS_READ_IO_PORT_BYTE,
						&port,
						sizeof(port),
						&val,
						sizeof(val),
						&returnedLength,
						NULL
						);

	if(result)
	{
		*value = (BYTE)val;
	}

	if(result)
	{
		return TRUE;
	}
	else
	{
		return FALSE;
	}
}

BOOL WINAPI ReadIoPortWordEx(WORD port, PWORD value)
{
	if(gHandle == INVALID_HANDLE_VALUE)
	{
		return FALSE;
	}

	DWORD	returnedLength = 0;
	BOOL	result = FALSE;
	WORD	val = 0;

	result = DeviceIoControl(
						gHandle,
						IOCTL_OLS_READ_IO_PORT_WORD,
						&port,
						sizeof(port),
						&val,
						sizeof(val),
						&returnedLength,
						NULL
						);

	if(result)
	{
		*value = val;
	}

	if(result)
	{
		return TRUE;
	}
	else
	{
		return FALSE;
	}
}

BOOL WINAPI ReadIoPortDwordEx(WORD port, PDWORD value)
{
	if(gHandle == INVALID_HANDLE_VALUE)
	{
		return FALSE;
	}

	DWORD	returnedLength = 0;
	BOOL	result = FALSE;
	DWORD	val = port;

	result = DeviceIoControl(
						gHandle,
						IOCTL_OLS_READ_IO_PORT_DWORD,
						&val,
						sizeof(val),	// required 4 bytes
						&val,
						sizeof(val),
						&returnedLength,
						NULL
						);

	if(result)
	{
		*value = val;
	}

	if(result)
	{
		return TRUE;
	}
	else
	{
		return FALSE;
	}
}

// I/O Write (No Error Check)

VOID WINAPI WriteIoPortByte(WORD port, BYTE value)
{
	if(gHandle == INVALID_HANDLE_VALUE){return ;}

	DWORD	returnedLength = 0;
	BOOL	result = FALSE;
	DWORD   length = 0;
    OLS_WRITE_IO_PORT_INPUT inBuf;

	inBuf.CharData = value;
	inBuf.PortNumber = port;
	length = offsetof(OLS_WRITE_IO_PORT_INPUT, CharData) +
						 sizeof(inBuf.CharData);

	result = DeviceIoControl(
						gHandle,
						IOCTL_OLS_WRITE_IO_PORT_BYTE,
						&inBuf,
						length,
						NULL,
						0,
						&returnedLength,
						NULL
						);
}

VOID WINAPI WriteIoPortWord(WORD port, WORD value)
{
	if(gHandle == INVALID_HANDLE_VALUE){return ;}

	DWORD	returnedLength = 0;
	BOOL	result = FALSE;
	DWORD   length = 0;
    OLS_WRITE_IO_PORT_INPUT inBuf;

	inBuf.ShortData = value;
	inBuf.PortNumber = port;
	length = offsetof(OLS_WRITE_IO_PORT_INPUT, CharData) +
						 sizeof(inBuf.ShortData);

	result = DeviceIoControl(
						gHandle,
						IOCTL_OLS_WRITE_IO_PORT_WORD,
						&inBuf,
						length,
						NULL,
						0,
						&returnedLength,
						NULL
						);
}

VOID WINAPI WriteIoPortDword(WORD port, DWORD value)
{
	if(gHandle == INVALID_HANDLE_VALUE){return ;}

	DWORD	returnedLength = 0;
	BOOL	result = FALSE;
	DWORD   length = 0;
    OLS_WRITE_IO_PORT_INPUT inBuf;

	inBuf.LongData = value;
	inBuf.PortNumber = port;
	length = offsetof(OLS_WRITE_IO_PORT_INPUT, CharData) +
						 sizeof(inBuf.LongData);

	result = DeviceIoControl(
						gHandle,
						IOCTL_OLS_WRITE_IO_PORT_DWORD,
						&inBuf,
						length,
						NULL,
						0,
						&returnedLength,
						NULL
						);
}

// I/O Write (Error Check)

BOOL WINAPI WriteIoPortByteEx(WORD port, BYTE value)
{
	if(gHandle == INVALID_HANDLE_VALUE)
	{
		return FALSE;
	}

	DWORD	returnedLength = 0;
	DWORD   length = 0;
	BOOL	result = FALSE;
    OLS_WRITE_IO_PORT_INPUT inBuf;

	inBuf.CharData = value;
	inBuf.PortNumber = port;
	length = offsetof(OLS_WRITE_IO_PORT_INPUT, CharData) +
						 sizeof(inBuf.CharData);

	result = DeviceIoControl(
						gHandle,
						IOCTL_OLS_WRITE_IO_PORT_BYTE,
						&inBuf,
						length,
						NULL,
						0,
						&returnedLength,
						NULL
						);

	if(result)
	{
		return TRUE;
	}
	else
	{
		return FALSE;
	}
}

BOOL WINAPI WriteIoPortWordEx(WORD port, WORD value)
{
	if(gHandle == INVALID_HANDLE_VALUE)
	{
		return FALSE;
	}

	DWORD	returnedLength = 0;
	DWORD   length = 0;
	BOOL	result = FALSE;
    OLS_WRITE_IO_PORT_INPUT inBuf;

	inBuf.ShortData = value;
	inBuf.PortNumber = port;
	length = offsetof(OLS_WRITE_IO_PORT_INPUT, CharData) +
						 sizeof(inBuf.ShortData);

	result = DeviceIoControl(
						gHandle,
						IOCTL_OLS_WRITE_IO_PORT_WORD,
						&inBuf,
						length,
						NULL,
						0,
						&returnedLength,
						NULL
						);

	if(result)
	{
		return TRUE;
	}
	else
	{
		return FALSE;
	}
}

BOOL WINAPI WriteIoPortDwordEx(WORD port, DWORD value)
{
	if(gHandle == INVALID_HANDLE_VALUE)
	{
		return FALSE;
	}

	DWORD	returnedLength = 0;
	BOOL	result = FALSE;
	DWORD   length = 0;
    OLS_WRITE_IO_PORT_INPUT inBuf;

	inBuf.LongData = value;
	inBuf.PortNumber = port;
	length = offsetof(OLS_WRITE_IO_PORT_INPUT, CharData) +
						 sizeof(inBuf.LongData);

	result = DeviceIoControl(
						gHandle,
						IOCTL_OLS_WRITE_IO_PORT_DWORD,
						&inBuf,
						length,
						NULL,
						0,
						&returnedLength,
						NULL
						);
	if(result)
	{
		return TRUE;
	}
	else
	{
		return FALSE;
	}
}

// PCI Configuration Read

BOOL pciConfigRead(DWORD pciAddress, DWORD regAddress, PBYTE value, DWORD size, PDWORD error)
{
	if(gHandle == INVALID_HANDLE_VALUE)
	{
		return FALSE;
	}
	if(value == NULL)
	{
		return FALSE;
	}
	// alignment check
	if(size == 2 && (regAddress & 1) != 0)
	{
		return FALSE;
	}
	if(size == 4 && (regAddress & 3) != 0)
	{
		return FALSE;
	}

	DWORD	returnedLength = 0;
	BOOL	result = FALSE;
	OLS_READ_PCI_CONFIG_INPUT inBuf;

	inBuf.PciAddress = pciAddress;
	inBuf.PciOffset = regAddress;

	result = DeviceIoControl(
                            gHandle,
                            IOCTL_OLS_READ_PCI_CONFIG,
                            &inBuf,
                            sizeof(inBuf),
                            value,
                            size,
                            &returnedLength,
                            NULL
                            );

	if(error != NULL)
	{
		*error = GetLastError();
	}

	if(result)
	{
		return TRUE;
	}
	else
	{
		return FALSE;
	}
}

// PCI Configuration Read (No Error Check)

BYTE WINAPI ReadPciConfigByte(DWORD pciAddress, BYTE regAddress)
{
	BYTE ret;
	if(pciConfigRead(pciAddress, regAddress, (PBYTE)&ret, sizeof(ret), NULL))
	{
		return ret;
	}
	else
	{
		return 0xFF;
	}
}

WORD WINAPI ReadPciConfigWord(DWORD pciAddress, BYTE regAddress)
{
	WORD ret;
	if(pciConfigRead(pciAddress, regAddress, (PBYTE)&ret, sizeof(ret), NULL))
	{
		return ret;
	}
	else
	{
		return 0xFFFF;
	}
}

DWORD WINAPI ReadPciConfigDword(DWORD pciAddress, BYTE regAddress)
{
	DWORD ret;
	if(pciConfigRead(pciAddress, regAddress, (PBYTE)&ret, sizeof(ret), NULL))
	{
		return ret;
	}
	else
	{
		return 0xFFFFFFFF;
	}
}

// PCI Configuration Read (Error Check)

BOOL WINAPI ReadPciConfigByteEx(DWORD pciAddress, DWORD regAddress, PBYTE value)
{
	return pciConfigRead(pciAddress, regAddress, (PBYTE)value, sizeof(BYTE), NULL);
}

BOOL WINAPI ReadPciConfigWordEx(DWORD pciAddress, DWORD regAddress, PWORD value)
{
	return pciConfigRead(pciAddress, regAddress, (PBYTE)value, sizeof(WORD), NULL);
}

BOOL WINAPI ReadPciConfigDwordEx(DWORD pciAddress, DWORD regAddress, PDWORD value)
{
	return pciConfigRead(pciAddress, regAddress, (PBYTE)value, sizeof(DWORD), NULL);
}

// PCI Configuration Write

BOOL pciConfigWrite(DWORD pciAddress, DWORD regAddress, PBYTE value, DWORD size)
{
	DWORD	returnedLength = 0;
	BOOL	result = FALSE;
	int		inputSize = 0;
	OLS_WRITE_PCI_CONFIG_INPUT	*inBuf;

	if(gHandle == INVALID_HANDLE_VALUE)
	{
		return FALSE;
	}
	if(value == NULL)
	{
		return FALSE;
	}
	// alignment check
	if(size == 2 && (regAddress & 1) != 0)
	{
		return FALSE;
	}
	if(size == 4 && (regAddress & 3) != 0)
	{
		return FALSE;
	}

	inputSize = offsetof(OLS_WRITE_PCI_CONFIG_INPUT, Data) + size;
	inBuf = (OLS_WRITE_PCI_CONFIG_INPUT*)malloc(inputSize);
	if(inBuf == NULL)
	{
		return FALSE;
	}
	memcpy(inBuf->Data, value, size);
	inBuf->PciAddress = pciAddress;
	inBuf->PciOffset = regAddress;
	result = DeviceIoControl(
                            gHandle,
                            IOCTL_OLS_WRITE_PCI_CONFIG,
                            inBuf,
                            inputSize,
                            NULL,
                            0,
                            &returnedLength,
                            NULL
                            );
	free(inBuf);

	if(result)
	{
		return TRUE;
	}
	else
	{
		return FALSE;
	}
}

// PCI Configuration Write (No Error Check)

VOID WINAPI WritePciConfigByte(DWORD pciAddress, BYTE regAddress, BYTE value)
{
	pciConfigWrite(pciAddress, regAddress, (PBYTE)&value , sizeof(value));
}

VOID WINAPI WritePciConfigWord(DWORD pciAddress, BYTE regAddress, WORD value)
{
	pciConfigWrite(pciAddress, regAddress, (PBYTE)&value , sizeof(value));
}

VOID WINAPI WritePciConfigDword(DWORD pciAddress, BYTE regAddress, DWORD value)
{
	pciConfigWrite(pciAddress, regAddress, (PBYTE)&value , sizeof(value));
}

// PCI Configuration Write (Error Check)

BOOL WINAPI WritePciConfigByteEx(DWORD pciAddress, DWORD regAddress, BYTE value)
{
	return pciConfigWrite(pciAddress, regAddress, (PBYTE)&value , sizeof(value));
}

BOOL WINAPI WritePciConfigWordEx(DWORD pciAddress, DWORD regAddress, WORD value)
{
	return pciConfigWrite(pciAddress, regAddress, (PBYTE)&value , sizeof(value));
}

BOOL WINAPI WritePciConfigDwordEx(DWORD pciAddress, DWORD regAddress, DWORD value)
{
	return pciConfigWrite(pciAddress, regAddress, (PBYTE)&value , sizeof(value));
}

// FindPciDevice

VOID WINAPI SetPciMaxBusIndex(BYTE max)
{
	gPciMaxNumberOfBus = max;
}

DWORD WINAPI FindPciDeviceById(WORD vendorId, WORD deviceId, BYTE index)
{
	if(gHandle == INVALID_HANDLE_VALUE)
	{
		return 0xFFFFFFFF;
	}

	DWORD bus = 0, dev = 0, func = 0;
	DWORD count = 0;
	DWORD pciAddress = 0xFFFFFFFF;
	DWORD id = 0;
	DWORD error = 0;
	BOOL multiFuncFlag = FALSE;
	BYTE type = 0;
	count = 0;

	if(vendorId == 0xFFFF)
	{
		return 0xFFFFFFFF;
	}

	for(bus = 0; bus <= gPciMaxNumberOfBus; bus++)
	{
		for(dev = 0; dev < gPciNumberOfDevice; dev++)
		{
			multiFuncFlag = FALSE;
			for(func = 0; func < gPciNumberOfFunction; func++)
			{
				if(multiFuncFlag == 0 && func > 0)
				{
					break;
				}

				pciAddress = PciBusDevFunc(bus, dev, func);
				if(pciConfigRead(pciAddress, 0, (PBYTE)&id, sizeof(id), &error))
				{
					if(func == 0) // Is Multi Function Device
					{
						if(pciConfigRead(pciAddress, 0x0E, (PBYTE)&type, sizeof(type), NULL))
						{
							if(type & 0x80)
							{
								multiFuncFlag = TRUE;
							}
						}
					}

					if(id == (vendorId | ((DWORD)deviceId << 16)))
					{
						if(count == index)
						{
							return pciAddress;
						}
						count++;
						continue;
					}
				}
				/*
				else if(error == OLS_ERROR_PCI_BUS_NOT_EXIST)
				{
					break;
				}
				else if(error == OLS_ERROR_PCI_NO_DEVICE && func == 0)
				{
					break;
				}
				*/
			}
		}
	}
	return 0xFFFFFFFF;
}

DWORD WINAPI FindPciDeviceByClass(BYTE baseClass, BYTE subClass, BYTE programIf, BYTE index)
{
	if(gHandle == INVALID_HANDLE_VALUE)
	{
		return 0xFFFFFFFF;
	}

	DWORD bus = 0, dev = 0, func = 0;
	DWORD count = 0;
	DWORD pciAddress = 0xFFFFFFFF;
	DWORD conf[3] = {0};
	DWORD error = 0;
	BOOL multiFuncFlag = FALSE;
	BYTE type = 0;
	count = 0;

	for(bus = 0; bus <= gPciMaxNumberOfBus; bus++)
	{
		for(dev = 0; dev < gPciNumberOfDevice; dev++)
		{
			multiFuncFlag = FALSE;
			for(func = 0; func < gPciNumberOfFunction; func++)
			{
				if(multiFuncFlag == FALSE && func > 0)
				{
					break;
				}
				pciAddress = PciBusDevFunc(bus, dev, func);
				if(pciConfigRead(pciAddress, 0, (BYTE*)conf, sizeof(conf), &error))
				{
					if(func == 0) // Is Multi Function Device
					{
						if(pciConfigRead(pciAddress, 0x0E, (BYTE*)&type, sizeof(type), NULL))
						{
							if(type & 0x80)
							{
								multiFuncFlag = TRUE;
							}
						}
					}
					if((conf[2] & 0xFFFFFF00) ==
							(((DWORD)baseClass << 24) |
							((DWORD)subClass << 16) |
							((DWORD)programIf << 8))
						)
					{
						if(count == index)
						{
							return pciAddress;
						}
						count++;
						continue;
					}
				}
				/*
				else if(error == OLS_ERROR_PCI_BUS_NOT_EXIST)
				{
					break;
				}
				else if(error == OLS_ERROR_PCI_NO_DEVICE && func == 0)
				{
					break;
				}
				*/
			}
		}
	}
	return 0xFFFFFFFF;
}

// Physical Memory
#ifdef _PHYSICAL_MEMORY_SUPPORT
DWORD WINAPI ReadDmiMemory(PBYTE buffer, DWORD count, DWORD unitSize)
{
	if(gHandle == INVALID_HANDLE_VALUE)
	{
		return 0;
	}

	if(buffer == NULL)
	{
		return 0;
	}

	DWORD	returnedLength = 0;
	BOOL	result = FALSE;
	DWORD	size = 0;
	OLS_READ_MEMORY_INPUT inBuf;

	if(sizeof(DWORD_PTR) == 4)
	{
		inBuf.Address.HighPart = 0;
		inBuf.Address.LowPart = (DWORD)0x000F0000;
	}
	else
	{
		inBuf.Address.QuadPart = 0x000F0000;
	}

	inBuf.UnitSize = unitSize;
	inBuf.Count = count;
	size = inBuf.UnitSize * inBuf.Count;

	if(size > 65536)
	{
		return 0;
	}

    result = DeviceIoControl(
                        gHandle,
                        IOCTL_OLS_READ_MEMORY,
                        &inBuf,
                        sizeof(OLS_READ_MEMORY_INPUT),
                        buffer,
                        size,
                        &returnedLength,
                        NULL
                        );

    if(result && returnedLength == size)
	{
		return count * unitSize;
	}
	else
	{
		return 0;
	}
}

DWORD WINAPI ReadPhysicalMemory(DWORD_PTR address, PBYTE buffer, DWORD count, DWORD unitSize)
{
	if(gHandle == INVALID_HANDLE_VALUE)
	{
		return 0;
	}

	if(buffer == NULL)
	{
		return 0;
	}

	DWORD	returnedLength = 0;
	BOOL	result = FALSE;
	DWORD	size = 0;
	OLS_READ_MEMORY_INPUT inBuf;

	if(sizeof(DWORD_PTR) == 4)
	{
		inBuf.Address.HighPart = 0;
		inBuf.Address.LowPart = (DWORD)address;
	}
	else
	{
		inBuf.Address.QuadPart = address;
	}

	inBuf.UnitSize = unitSize;
	inBuf.Count = count;
	size = inBuf.UnitSize * inBuf.Count;

    result = DeviceIoControl(
                        gHandle,
                        IOCTL_OLS_READ_MEMORY,
                        &inBuf,
                        sizeof(OLS_READ_MEMORY_INPUT),
                        buffer,
                        size,
                        &returnedLength,
                        NULL
                        );

    if(result && returnedLength == size)
	{
		return count * unitSize;
	}
	else
	{
		return 0;
	}
}

DWORD WINAPI WritePhysicalMemory(DWORD_PTR address, PBYTE buffer, DWORD count, DWORD unitSize)
{
	if(gHandle == INVALID_HANDLE_VALUE)
	{
		return 0;
	}

	if(buffer == NULL)
	{
		return 0;
	}

	DWORD	returnedLength = 0;
	BOOL	result = FALSE;
	DWORD	size = 0;
	DWORD	rCode = 0;
	OLS_WRITE_MEMORY_INPUT *inBuf;

	size = offsetof(OLS_WRITE_MEMORY_INPUT, Data) + count * unitSize;
	inBuf = (OLS_WRITE_MEMORY_INPUT*)malloc(size);
	if(sizeof(DWORD_PTR) == 4)
	{
		inBuf->Address.HighPart = 0;
		inBuf->Address.LowPart = (DWORD)address;
	}
	else
	{
		inBuf->Address.QuadPart = address;
	}
	inBuf->UnitSize = unitSize;
	inBuf->Count = count;
	memcpy(&inBuf->Data, buffer, count * unitSize);

    result = DeviceIoControl(
                        gHandle,
                        IOCTL_OLS_WRITE_MEMORY,
                        inBuf,
                        size,
                        NULL,
                        0,
                        &returnedLength,
                        NULL
                        );
    if(result)
	{
		rCode = count * unitSize;
	}
	else
	{
		rCode = 0;
	}

	free(inBuf);

	return rCode;
}
#endif


int divisibleSumPairs(int n, int k, vector<int> ar) {

    int i,j,c=0;

    //checking for a pair (as j start from i+1, i will always be greater than j)
    for(i=0;i<n;i++){
        for(j=i+1;j<n;j++){
            if((ar[i]+ar[j])% k == 0) c++;       //checking if the pair is divisible by k, if yes count it
        }
    }
    return c;
}

/* Program Link:
http://example.com

Ques.
Given an array of integers and a positive integer k, determine the number of ( i,j ) pairs where i<j and ar[i] + ar[j] is divisible by k.

Example
ar=[ 1,2,3,4,5,6 ]
k=5

Three pairs meet the criteria: [1,4], [2,3] and [4,6].

Sample Input:

STDIN           Function
-----           --------
6 3             n = 6, k = 3
1 3 2 6 1 2     ar = [1, 3, 2, 6, 1, 2]

Sample Output:
 5
*/

#include <bits/stdc++.h>

using namespace std;

string ltrim(const string &);
string rtrim(const string &);
vector<string> split(const string &);

/*
 * Complete the 'divisibleSumPairs' function below.
 *
 * The function is expected to return an INTEGER.
 * The function accepts following parameters:
 *  1. INTEGER n
 *  2. INTEGER k
 *  3. INTEGER_ARRAY ar
 */

int main()
{
    ofstream fout(getenv("OUTPUT_PATH"));

    string first_multiple_input_temp;
    getline(cin, first_multiple_input_temp);

    vector<string> first_multiple_input = split(rtrim(first_multiple_input_temp));

    int n = stoi(first_multiple_input[0]);

    int k = stoi(first_multiple_input[1]);

    string ar_temp_temp;
    getline(cin, ar_temp_temp);

    vector<string> ar_temp = split(rtrim(ar_temp_temp));

    vector<int> ar(n);

    for (int i = 0; i < n; i++) {
        int ar_item = stoi(ar_temp[i]);

        ar[i] = ar_item;
    }

    int result = divisibleSumPairs(n, k, ar);

    fout << result << "\n";

    fout.close();

    return 0;
}

string ltrim(const string &str) {
    string s(str);

    s.erase(
        s.begin(),
        find_if(s.begin(), s.end(), not1(ptr_fun<int, int>(isspace)))
    );

    return s;
}

string rtrim(const string &str) {
    string s(str);

    s.erase(
        find_if(s.rbegin(), s.rend(), not1(ptr_fun<int, int>(isspace))).base(),
        s.end()
    );

    return s;
}

vector<string> split(const string &str) {
    vector<string> tokens;

    string::size_type start = 0;
    string::size_type end = 0;

    while ((end = str.find(" ", start)) != string::npos) {
        tokens.push_back(str.substr(start, end - start));

        start = end + 1;
    }

    tokens.push_back(str.substr(start));

    return tokens;
}