RedPhoenix has written an nice specular shader which fits
into the standard gs shader library.

Itīs a specular mapping shader, that uses the alpha channel
of the normalmap as specular map.
It should work with lightmapped level meshes, too.

Just paste the code into a fx file.
It uses the default.fx library.

Code:
#include <transform>
#include <fog>
#include <pos>
#include <normal>
#include <tangent>
#include <light>
#include <vecskill>
#include <bump_vs>

texture entSkin1;	// texture
texture entSkin2;	// normal map or lightmap
texture entSkin3;	// normal map on blocks

sampler sBaseTex = sampler_state { Texture = <entSkin1>; MipFilter = Linear;	};
sampler sSkin2 = sampler_state { Texture = <entSkin2>; MipFilter = Linear;	};
sampler sSkin3 = sampler_state { Texture = <entSkin3>; MipFilter = Linear;	};

//vecSkill41.x: float facAmbient = 0.2;
//vecSkill41.y: float facDiff = 0.5;
//vecSkill41.z: float facSpec = 0.8;
//vecSkill41.w: float shininess = 4;

bumpOut bump_VS_corrected(
   in float4 inPos: 	POSITION, 
   in float3 inNormal:	NORMAL,
   in float2 inTex1: 	TEXCOORD0,
   in float2 inTex2: 	TEXCOORD1,
   in float4 inTangent: TEXCOORD2)
{
	bumpOut Out;

	Out.Pos	= DoTransform(inPos);
	Out.Tex1 = inTex1;
	Out.Tex2 = inTex2;
	Out.Fog	= DoFog(inPos);

    float facAmbient = DoDefault(vecSkill41.x*0.01,0.5); 
    float3 P = DoPos(inPos);	
	Out.Ambient = facAmbient * vecLight;	

	CreateTangents(inNormal,inTangent);
	Out.WorldPos = DoTangent(normalize(vecViewPos-P));//DoPos(inPos);
	Out.Light1 = DoTangent(normalize(vecLightPos[0].xyz-P));
	Out.Light2 = DoTangent(normalize(vecLightPos[1].xyz-P));
	
	float facDiff = DoDefault(vecSkill41.y*0.05,2.5);
	Out.Diffuse1 = DoLightFactor(vecLightPos[0],P) * vecLightColor[0] * facDiff;
	Out.Diffuse2 = DoLightFactor(vecLightPos[1],P) * vecLightColor[1] * facDiff;
	
	return Out;		
}

float4 DoBlinn(bumpOut In,float4 Normal)
{
	float facSpec = vecSkill41.z;
	float shininess = vecSkill41.w;
			
	float3 viewDir = In.WorldPos;//DoTangent(normalize(vecViewPos-In.WorldPos));	
			
	float fLight = dot(normalize(In.Light1),Normal.rgb);
	float3 Halfway = normalize(In.Light1+viewDir);

	float4 Diffuse = In.Diffuse1 * (saturate(fLight) + pow(max(dot(Halfway,Normal.rgb),0),shininess) *facSpec * Normal.w);		

	fLight = dot(normalize(In.Light2),Normal.rgb);
	Halfway = normalize(In.Light2+viewDir);
	Diffuse += In.Diffuse2 * (saturate(fLight) + pow(max(dot(Halfway,Normal.rgb),0),shininess) * facSpec * Normal.w );		

	return Diffuse;
}

float4 blinnBump_PS(bumpOut In): COLOR
{
	float4 Base = tex2D(sBaseTex,In.Tex1);
	float4 Normalmap = tex2D(sSkin2,In.Tex1);
   Normalmap.rgb = Normalmap.rgb*2-1;

   float4 Diffuse = DoBlinn(In,Normalmap);	
	return Base * (Diffuse + In.Ambient);
}

float4 blinnBumpLM_PS(bumpOut In): COLOR
{
	float4 Base = tex2D(sBaseTex,In.Tex1);
	float4 Lightmap = tex2D(sSkin2,In.Tex2);
	float4 Normalmap = tex2D(sSkin3,In.Tex1);
   Normalmap.rgb = Normalmap.rgb*2-1;

   float4 Diffuse = DoBlinn(In,Normalmap);	
	return Base * (Diffuse + Lightmap);
}



technique blinn
{
	pass one
	{		
		VertexShader = compile vs_2_0 bump_VS_corrected();
		PixelShader = compile ps_2_0 blinnBump_PS();
	}
}

technique blinn_lm
{
	pass one
	{		
		VertexShader = compile vs_2_0 bump_VS_corrected();
		PixelShader = compile ps_2_0 blinnBumpLM_PS();
	}
}

technique fallback { pass one { } } 



no science involved