Re: [INFO] Reverse Engineering References
Well it's not meant to be a one click and your done, just a way to get a basic structure so you can fill in the blanks, without having to type and format everything yourself.
Quote:
Maybe you want to help with my Forge project, seeing as you are a very good coder. :downs: or maybe not.
Well i'm still busy working with OS (Post processing stuff) so I haven't the time for other projects (plus i'm probably not at the skill level you think i am :P).
Re: [INFO] Reverse Engineering References
This function was one I didn't care to reverse, but it caught my attention of some reason or another. This function is called in the player nametags function(as well as many other functions). I'm working on the nametags function because it has the engine world space to screen space, which I'm using for CE Forge. I will be posting that next when it's done. This is for Halo CE 1.08.
Thnx to OllyDbg, IDA Pro, Hexrays, and Kornman00.
Needed structs/globals:
Code:
typedef signed int int32; typedef unsigned int uint32;
typedef signed short int16; typedef unsigned short uint16;
typedef signed char int8; typedef unsigned char uint8;
#define TAGINDEX 0x00816D04
#define INVALID -1
struct Identity
{
union
{
uint32 Ident;
struct
{
int16 Index;
int16 Salt;
};
};
};
struct TagInstance
{
union
{
uint32 TagGroup; // 0x00
uint8 TagGroupC[4]; // 0x00
};
union
{
uint32 TagChild; // 0x04
uint8 TagChildC[4]; // 0x04
};
union
{
uint32 TagParent; // 0x08
uint8 TagParentC[4]; // 0x08
};
Identity Tag; // 0x0C
uint8 *Name; // 0x10
void *Address; // 0x14
uint32 Location; // 0x18
uint32 _Unused; // 0x1C
}; // Size = 32 bytes(0x20)
struct TagBlock
{
uint32 Count; // 0x00
uint32 Address; // 0x04
uint32 Padding; // 0x08
}; // Size = 12 bytes(0x0C)
struct TagReference
{
uint32 TagGroup; // 0x00
uint8* Name; // 0x04
uint32 NameLength; // 0x08
Identity Tag; // 0x0C
}; // Size = 16 bytes(0x10)
struct Bitmap // 'bitm'
{
// Sprite Processing
uint32 SpriteBudgetSize;
uint32 SpriteBudgetCount;
// Post Processing
uint32 DetailFadeFactor;
uint32 SharpenAmount;
uint32 BumpHeight;
// Usage
uint16 Usage;
// Format
uint16 Format;
// Color Plate
uint16 ColorPlateWidth;
uint16 ColorPlateHeight;
uint32 CompressedColorPlateData;
uint8 Unknown[24];
// Processed Pixel Data
uint32 ProcessedPixelData;
uint8 Unknown2[20];
// ...More Sprite Processing
uint32 SpriteSpacing;
TagBlock Sequences;
TagBlock Bitmaps;
};
//----------------------------------------------
struct BitmapSequence
{
uint8 Name[32];
uint16 FirstBitmapIndex;
uint16 BitmapCount;
uint8 Unknown[12];
TagBlock Sprites;
};
//----------------------------------------------
struct BitmapSequenceSprite
{
float Unknown;
uint32 BitmapIndex;
float Left;
float Right;
float Top;
float Bottom;
float RegistrationPoint[2];
};
//----------------------------------------------
struct BitmapBitmap
{
uint32 Signature; // 'bitm'
uint16 Width;
uint16 Height;
uint16 Depth;
uint16 Type;
uint16 Format;
uint16 Flags;
uint16 RegistrationPointX;
uint16 RegistrationPointY;
uint16 MipMapCount;
uint32 PixelsOffset;
uint32 Size;
Identity BitmapIdent; // Maybe??
uint8 Unknown[12];
};
Code:
BitmapBitmap* GetBitmapBySequence(Identity BitmapIdentity, uint32 SpriteIndex, uint32 SequenceIndex)
{
uint16 BitmapIndex;
if(BitmapIdentity.Ident != INVALID)
{
uint32 BitmInstOffset = sizeof(TagInstance) * BitmapIdentity.Index;
TagInstance* BitmInstance = (TagInstance*)((uint32)*(Bitmap**)TAGINDEX + BitmInstOffset);
Bitmap* pBitmap = (Bitmap*)BitmInstance->Address;
if(!pBitmap)
return NULL;
if(pBitmap->Sequences.Count > 0)
{
uint32 BitmSeqOffset = sizeof(BitmapSequence) * (SequenceIndex % pBitmap->Sequences.Count);
BitmapSequence* BitmSequence = (BitmapSequence*)(pBitmap->Sequences.Address + BitmSeqOffset);
if(BitmSequence->BitmapCount <= 0)
{
if(BitmSequence->Sprites.Count)
{
uint32 BitmSeqSpriteOffset = sizeof(BitmapSequenceSprite) * SpriteIndex;
BitmapSequenceSprite* BitmSeqSprite = (BitmapSequenceSprite*)((uint32)BitmSequence->Sprites.Address + BitmSeqSpriteOffset);
BitmapIndex = BitmSeqSprite->BitmapIndex;
}
else
BitmapIndex = SpriteIndex;
}
else
BitmapIndex = BitmSequence->FirstBitmapIndex + (SpriteIndex % BitmSequence->BitmapCount);
}
else
BitmapIndex = SpriteIndex;
if(BitmapIndex == INVALID)
BitmapIndex = SpriteIndex;
if(BitmapIndex >= 0 && BitmapIndex < pBitmap->Bitmaps.Count)
{
uint32 BitmBitmOffset = sizeof(BitmapBitmap) * BitmapIndex;
return (BitmapBitmap*)(pBitmap->Bitmaps.Address + BitmBitmOffset);
}
}
return NULL;
}
Original Disassembly:
Code:
CPU Disasm
Address Command
0043F290 PUSH EBX
0043F291 XOR EBX,EBX
0043F293 CMP EAX,-1
0043F296 JE 0043F325
0043F29C MOV ECX,DWORD PTR DS:[816D04]
0043F2A2 AND EAX,0000FFFF
0043F2A7 SHL EAX,5
0043F2AA PUSH ESI
0043F2AB MOV ESI,DWORD PTR DS:[ECX+EAX+14]
0043F2AF TEST ESI,ESI
0043F2B1 JE SHORT 0043F320
0043F2B3 MOV ECX,DWORD PTR DS:[ESI+54]
0043F2B6 TEST ECX,ECX
0043F2B8 PUSH EBP
0043F2B9 JLE SHORT 0043F300
0043F2BB MOVSX EAX,WORD PTR SS:[Arg1]
0043F2C0 CDQ
0043F2C1 IDIV ECX
0043F2C3 MOV EBP,DWORD PTR DS:[ESI+58]
0043F2C6 MOV ECX,EDX
0043F2C8 SHL ECX,6
0043F2CB MOV AX,WORD PTR DS:[EBP+ECX+22]
0043F2D0 ADD ECX,EBP
0043F2D2 TEST AX,AX
0043F2D5 JLE SHORT 0043F2E6
0043F2D7 MOVSX EBP,AX
0043F2DA MOVSX EAX,DI
0043F2DD CDQ
0043F2DE IDIV EBP
0043F2E0 ADD DX,WORD PTR DS:[ECX+20]
0043F2E4 JMP SHORT 0043F2FA
0043F2E6 MOV EAX,DWORD PTR DS:[ECX+34]
0043F2E9 TEST EAX,EAX
0043F2EB JE SHORT 0043F300
0043F2ED MOV EAX,DWORD PTR DS:[ECX+38]
0043F2F0 MOVSX EDX,DI
0043F2F3 SHL EDX,5
0043F2F6 MOV DX,WORD PTR DS:[EAX+EDX]
0043F2FA CMP DX,0FFFF
0043F2FE JNE SHORT 0043F302
0043F300 MOV EDX,EDI
0043F302 TEST DX,DX
0043F305 POP EBP
0043F306 JL SHORT 0043F320
0043F308 MOV ECX,DWORD PTR DS:[ESI+60]
0043F30B MOVSX EAX,DX
0043F30E CMP EAX,ECX
0043F310 JGE SHORT 0043F320
0043F312 MOV ECX,DWORD PTR DS:[ESI+64]
0043F315 LEA EAX,[EAX*2+EAX]
0043F318 SHL EAX,4
0043F31B POP ESI
0043F31C ADD EAX,ECX
0043F31E POP EBX
0043F31F RETN
0043F320 POP ESI
0043F321 MOV EAX,EBX
0043F323 POP EBX
0043F324 RETN
0043F325 MOV EAX,EBX
0043F327 POP EBX
0043F328 RETN
Re: [INFO] Reverse Engineering References
This function was reversed from Halo CE 1.08 using OllyDbg, IDA Pro, Hexrays and my brain.
This function clears a surface that can be a render target, a stencil buffer, or a depth buffer. Refer to IDirect3DDevice9 interface documentation on MSDN for more information on the Direct3d functions.
Note: pDevice should be the global device, defined outside of this function, but for no-compile error sake, I just threw it in there.
Code:
//----- (00530170) --------------------------------------------------------
HRESULT ClearSurface(WORD SurfaceIndex, D3DCOLOR Color, bool bClear)
{
IDirect3DDevice9* pDevice = NULL;
IDirect3DSurface9* pSurface = NULL;
D3DSURFACE_DESC* pDesc;
D3DVIEWPORT9 vp;
if(SurfaceIndex < 9)
{
if(SurfaceIndex >= 0)
pSurface = (IDirect3DSurface9*)((20 * SurfaceIndex) + (int)0x00638A2C);
}
pDevice->SetRenderTarget(0, pSurface);
*(short*)0x00638A18 = SurfaceIndex;
if(SurfaceIndex == 1)
{
vp.X = *(WORD*)0x0075C176;
vp.Y = *(WORD*)0x0075C174;
vp.Width = *(WORD*)0x0075C17A - *(WORD*)0x0075C176;
vp.Height = *(WORD*)0x0075C178 - *(WORD*)0x0075C174;
}
else
{
pSurface->GetDesc(pDesc);
vp.X = 0;
vp.Y = 0;
vp.Width = pDesc->Width;
vp.Height = pDesc->Height;
}
vp.MinZ = 0.0f;
vp.MaxZ = 1.0f;
pDevice->SetViewport(&vp);
if(bClear)
{
DWORD Flags = D3DCLEAR_TARGET;
if(SurfaceIndex == 1 || SurfaceIndex == 2)
Flags = D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL;
return pDevice->Clear(0, NULL, Flags, Color, 1.0f, 0);
}
return bClear;
}
Re: [INFO] Reverse Engineering References
Reversed from Halo CE 1.08 using OllyDbg, IDA Pro, Hexrays, and msdn.
Returns the size of a vertex for a flexible vertex format (FVF).
Code:
//----- (0058751A) --------------------------------------------------------
DWORD GetFVFVertexSize(DWORD fvf)
{
DWORD Size;
switch(fvf & D3DFVF_POSITION_MASK)
{
case D3DFVF_XYZ:
Size = 12;
break;
case D3DFVF_XYZRHW:
case D3DFVF_XYZB1:
Size = 16;
break;
case D3DFVF_XYZB2:
Size = 20;
break;
case D3DFVF_XYZB3:
Size = 24;
break;
case D3DFVF_XYZB4:
Size = 28;
break;
case D3DFVF_XYZB5:
Size = 32;
break;
default:
Size = 0;
break;
}
if(fvf & D3DFVF_NORMAL)
Size += 12;
if(fvf & D3DFVF_PSIZE)
Size += 4;
if(fvf & D3DFVF_DIFFUSE)
Size += 4;
if(fvf < 0)
Size += 4;
DWORD TexCount = (fvf & D3DFVF_TEXCOUNT_MASK) >> D3DFVF_TEXCOUNT_SHIFT;
DWORD TexCoords = (fvf >> D3DFVF_TEXCOUNT_SHIFT) >> D3DFVF_TEXCOUNT_SHIFT;
if(TexCoords)
{
if(TexCount >= 0)
{
for(TexCoords; TexCoords > 0; TexCoords--)
{
switch(TexCoords & 3)
{
case D3DFVF_TEXTUREFORMAT1:
Size += 4;
break;
case D3DFVF_TEXTUREFORMAT2:
Size += 8;
break;
case D3DFVF_TEXTUREFORMAT3:
Size += 12;
break;
case D3DFVF_TEXTUREFORMAT4:
Size += 16;
break;
}
TexCoords = TexCoords >> 2;
}
}
}
else
Size += TexCount * 8;
return Size;
}
Re: [INFO] Reverse Engineering References
Reversed from Halo CE 1.08 using OllyDbg, IDA Pro, Hexrays, and msdn.
This function releases resources used in Halo rendering code. There is a total of 9 textures and surfaces. Halo only uses one vertex buffer and one index buffer for optimization. They pack as much object data in one buffer, instead of creating one for each object or whatever.
Code:
struct s_RenderTarget
{
UINT Width;
UINT Height;
D3DFORMAT Format;
IDirect3DSurface9* pSurface;
IDirect3DBaseTexture9* pTexture;
};
Code:
//----- (00530100) --------------------------------------------------------
void ReleaseResources()
{
s_RenderTarget* RenderTarget = (s_RenderTarget*)0x00638A20;
IDirect3DIndexBuffer9* pIndexData = *(IDirect3DIndexBuffer9**)0x006B83D8;
IDirect3DVertexBuffer9* pVertexBuffer = *(IDirect3DVertexBuffer9**)0x006B83DC;
for(UINT i = 0; i < 9; i++)
{
if(RenderTarget[i].pSurface)
{
RenderTarget[i].pSurface->Release();
RenderTarget[i].pSurface = NULL;
}
if(RenderTarget[i].pTexture)
{
RenderTarget[i].pTexture->Release();
RenderTarget[i].pTexture = NULL;
}
}
*(WORD*)0x00638A18 = 0xFFFF;
if(pIndexData)
{
pIndexData->Release();
pIndexData = NULL;
}
if(pVertexBuffer)
{
pVertexBuffer->Release();
pVertexBuffer = NULL;
}
}
Re: [INFO] Reverse Engineering References
Reversed from Halo CE 1.08 using OllyDbg, IDA Pro, Hexrays, and msdn.
Creates a CRC table from all 256 possible ASCII characters. The only argument used in this function in Halo CE 1.08 is 0x00652968, where the table is stored. This is called only once when Halo first loads up.
The byte at 0x006B4CA0 is set to 1 after the Crc table is initialized.
Code:
//----- (004D3840) --------------------------------------------------------
#define Crc32Poly 0xEDB88320
void InitCrc32Table(DWORD Crc32Table[256])
{
for(DWORD i = 0; i < 256; i++)
{
DWORD Crc32 = i;
for(DWORD j = 8; j > 0; j--)
{
if(Crc32 & 1)
Crc32 = (Crc32 >> 1) ^ Crc32Poly;
else
Crc32 >>= 1;
}
Crc32Table[i] = Crc32;
}
}
Re: [INFO] Reverse Engineering References
Reversed from Halo CE 1.08 using OllyDbg, IDA Pro, Hexrays, and msdn.
Calculates a Crc32 from the inputed buffer, the buffer length, and the current Crc32 value. It first checks if a Crc32 lookup table has been created, then uses it in calculating the Crc32.
Code:
//----- (004D37E0) --------------------------------------------------------
bool *Crc32TableInit = (bool *)0x006B4CA0;
DWORD *Crc32Table = (DWORD *)0x00652968; // 256 dwords
void CalcCrc32(DWORD *Crc32, constchar *Buffer, DWORD Length)
{
DWORD Crc = *Crc32;
if(!*Crc32TableInit)
{
InitCrc32Table(Crc32Table);
*Crc32TableInit = true;
}
for(DWORD i = 0; i < Length; i++)
{
Crc = (*Crc32 >> 8) ^ Crc32Table[(*Buffer++ ^ *Crc32) & 0xFF];
}
*Crc32 = Crc;
}
Re: [INFO] Reverse Engineering References
Reversed from Halo CE 1.08 using OllyDbg, IDA Pro, Hexrays, and msdn.
Initializes a cache data header, calculates crc32 of the cache size, and updates the next available cache offset.
Code:
//----- (0053B780) --------------------------------------------------------
struct Identity
{
union
{
uint32 Ident;
struct
{
int16 Index;
int16 Salt;
};
};
};
struct DataHeader
{
unsigned char Name[32];
WORD Max;
WORD Size;
bool IsValid;
bool IdentifierZeroInvalid;
WORD Padding;
DWORD Signature;
short NextIndex;
short LastIndex;
Identity Next;
DWORD First;
};
DWORD *CacheBaseAddress = (DWORD *)0x0067DCC0;
DWORD *CacheNextAvailOffset = (DWORD *)0x0067DCC4;
DWORD *CacheCrc32 = (DWORD *)0x0067DCCC;
DWORD InitCacheDataHeader(DWORD ElementCount, DWORD ElementSize, constchar *CacheName)
{
DWORD CacheSize = sizeof(DataHeader) + (ElementCount * ElementSize);
DWORD CacheAddress = *CacheBaseAddress + *CacheNextAvailOffset;
*CacheNextAvailOffset += CacheSize;
CalcCrc32(CacheCrc32, (constchar*)&CacheSize, 4);
memset((void*)CacheAddress, 0, sizeof(DataHeader));
strncpy_s((char*)CacheAddress, 31, CacheName, 31);
//Sub005C8DA0(CacheAddress, CacheName, 31);
// Actual call^, IDA says it's stncpy.
// When compared in olly, they look similar and achieve same results
// Guess they wrote their own strncpy func? heh
DataHeader *newDataHeader = (DataHeader*)CacheAddress;
newDataHeader->Max = (WORD)ElementCount;
newDataHeader->Size = (WORD)ElementSize;
newDataHeader->Signature = 'd@t@';
newDataHeader->First = CacheAddress + sizeof(DataHeader);
newDataHeader->IsValid = false;
return CacheAddress;
}
Re: [INFO] Reverse Engineering References
Reversed from Halo CE 1.08 using OllyDbg, IDA Pro, Hexrays, and msdn.
Initializes the player data caches, including those without data headers. I provided a bunch of the needed structs, only thing extra you will need is CRC stuff from my other posts.
Code:
//----- (004763C0) --------------------------------------------------------
struct Identity
{
union
{
uint32 Ident;
struct
{
int16 Index;
int16 Salt;
};
};
};
struct DataHeader
{
unsigned char Name[32];
WORD Max;
WORD Size;
bool IsValid;
bool IdentifierZeroInvalid;
WORD Padding;
DWORD Signature;
short NextIndex;
short LastIndex;
Identity Next;
DWORD First;
};
struct Players
{
short PlayerID;
short IsLocal; // 0=Local(no bits set), -1=Other Client(All bits set)
wchar_t Name[12]; // Unicode
Identity UnknownIdent;
long Team; // 0=Red, 1=Blue
Identity SwapObject;
short SwapType;
short SwapSeat; // Warthog-Driver=0, Passenger=1, Gunner=2, Weapon=-1
long RespawnTimer; // Counts down when dead, Alive=0
long Unknown;
Identity CurrentBiped;
Identity PreviousBiped;
long ClusterIndex;
Identity UnknownIdent1;
long LastBulletShotTime; // since game start(0)
wchar_t Name1[12];
Identity UnknownIdent2;
long PlayerInfo;
long Unknown1;
float VelocityMultiplier;
Identity UnknownIdent3[4];
long Unknown2;
long LastDeathTime; // since game start(0)
char Unknown3[18];
short KillsCount;
char Unknown4[6];
short AssistsCount;
char Unknown5[8];
short BetrayedCount;
short DeathCount;
short SuicideCount;
char Unknown6[18];
short FlagStealCount;
short FlagReturnCount;
short FlagCaptureCount;
char Unknown7[6];
Identity UnknownIdent4;
char Unknown8[8];
short Ping;
char Unknown9[14];
Identity UnknownIdent5;
long Unknown10;
long SomeTime;
float World[3];
Identity UnknownIdent6;
char Unknown11[20];
char Melee : 1;
char Swap : 1;
char UnknownBit : 1;
char Flashlight : 1;
char UnknownBit1 : 4;
char UnknownBit2 : 5;
char Reload : 1;
char UnknownBit3 : 2;
char Unknown12[26];
float Rotation[2];
float VerticalVelocityMultiplier;
float HorizontalVelocityMultiplier;
float RateOfFireVelocityMultiplier;
char Unknown13[180];
};
struct s_PlayerData
{
DataHeader PlayerHeader;
Players Player[16];
};
struct Teams
{
unsignedchar Unknown[64];
};
struct s_TeamData
{
DataHeader TeamHeader;
Teams Team[16];
};
struct s_LocalPlayer
{
Identity UnknownIdent;
Identity PlayerIdent;
Identity UnknownIdent1;
unsignedshort Unknown;
char Unknown1[138];
};
struct s_LocalObject
{
char Unknown0[16];
Identity Biped;
char Unknown1[8];
float Rotation[2];
char Unknown2[36];
float PitchMinimum;
float PitchMaximum;
};
s_PlayerData *PlayerData = *(s_PlayerData**)0x008154E0;
s_TeamData *TeamData = *(s_TeamData**)0x008154DC;
s_LocalPlayer *LocalPlayer = *(s_LocalPlayer**)0x008154D8;
s_LocalObject *LocalObject = *(s_LocalObject**)0x0064C2C4;
void InitPlayerCaches()
{
DWORD LocalPlayerSize = sizeof(s_LocalPlayer);
DWORD LocalObjectSize = sizeof(s_LocalObject);
PlayerData = (s_PlayerData*)InitCacheHeader(16, 512, "players");
TeamData = (s_TeamData*)InitCacheHeader(16, 64, "teams");
LocalPlayer = (s_LocalPlayer*)(*CacheBaseAddress + *CacheNextAvailOffset);
*CacheNextAvailOffset += LocalPlayerSize;
CalcCrc32(CacheCrc32, (const char*)&LocalPlayerSize, 4);
LocalPlayer->UnknownIdent.Ident = -1;
LocalPlayer->PlayerIdent.Ident = -1;
LocalPlayer->Unknown = 0;
LocalObject = (s_LocalObject*)(*CacheBaseAddress + *CacheNextAvailOffset);
*CacheNextAvailOffset += LocalObjectSize;
CalcCrc32(CacheCrc32, (const char*)&LocalObjectSize, 4);
}
Re: [INFO] Reverse Engineering References
Halo uses frustum culling on sphere bounding volumes to determine object visibility. There is a sphere-tree in memory I found that contains all the necessary information to determine if an object is completely outside, partially inside, or completely inside the view frustum. All the frustum plane normals and positions are calculated and filled into the sphere-tree before the object rendering code is even called, not sure exactly how or where yet.
The sphere-tree is located at memory address 0x0075E2B0 for Halo CE 1.08, if you want to check it out. It is an array of 128? possible sphere volumes. The format is this:
Note: The plane normals, plane positions, and unknown reals are quaternions. There is a total of 6 planes in the view frustum.
Code:
struct BoundingSphere
{
uint32 Index;
real UnknownReal[4];
BoundingFrustum Frustum;
int8 padding[76];
};
Needed declarations for the function
Code:
#define OUT_FRUSTUM 0
#define PARTIAL_FRUSTUM 1
#define IN_FRUSTUM 2
#define DotProduct(a,b) ((a)[0] * (b)[0] + (a)[1] * (b)[1] + (a)[2] * (b)[2])
typedef unsigned int uint32;
typedef unsigned short uint16;
typedef float real;
struct BoundingFrustum
{
real UnknownReal[27];
real PlaneNorm[6][4];
real NearClippingDistance;
real FarClippingDistance;
real PlanePos[6][4];
};
Code:
uint16 GetObjectVisibility(real *SphereCentre, BoundingFrustum *Frustum, real SphereRadius)
{
real Distance[6];
if((SphereCentre[0] - SphereRadius) > Frustum->PlanePos[4][3]) return OUT_FRUSTUM;
if((SphereCentre[1] - SphereRadius) > Frustum->PlanePos[5][1]) return OUT_FRUSTUM;
if((SphereCentre[2] - SphereRadius) > Frustum->PlanePos[5][3]) return OUT_FRUSTUM;
if((SphereCentre[0] + SphereRadius) < Frustum->PlanePos[4][2]) return OUT_FRUSTUM;
if((SphereCentre[1] + SphereRadius) < Frustum->PlanePos[5][0]) return OUT_FRUSTUM;
if((SphereCentre[2] + SphereRadius) < Frustum->PlanePos[5][2]) return OUT_FRUSTUM;
for(uint32 i = 0; i < 6; i++)
{
Distance[i] = DotProduct(Frustum->PlaneNorm[i], SphereCentre) - Frustum->PlaneNorm[i][3];
if(Distance[i] > SphereRadius) return OUT_FRUSTUM;
}
for(uint32 i = 0; i < 6; i++)
{
if(i==4) continue; // doesn't check the 5th plane
if(Distance[i] > -SphereRadius) return PARTIAL_FRUSTUM;
}
return IN_FRUSTUM;
}