Initial commit of Command & Conquer Red Alert source code.

This commit is contained in:
LFeenanEA
2025-02-27 16:15:05 +00:00
parent b685cea758
commit 5e733d5dcc
2082 changed files with 797727 additions and 0 deletions

View File

@@ -0,0 +1,298 @@
;
; Command & Conquer Red Alert(tm)
; Copyright 2025 Electronic Arts Inc.
;
; This program is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with this program. If not, see <http://www.gnu.org/licenses/>.
;
;***************************************************************************
;** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S **
;***************************************************************************
;* *
;* Project Name : Profiler *
;* *
;* File Name : APROFILE.ASM *
;* *
;* Programmer : Steve Tall *
;* *
;* Start Date : November 17th, 1995 *
;* *
;* Last Update : November 20th, 1995 [ST] *
;* *
;*-------------------------------------------------------------------------*
;* Functions: *
;* __PRO -- must be called at the beginning of each function *
;* __EPI -- must be called at the end of each function *
;* Copy_CHL -- initialise the profiler asm data *
;* Profiler_Callback -- windows callback for millisecond timer *
;* *
;* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *
p386
model flat
ideal
jumps
MAX_PROFILE_TIME = 60*1 ;1 minute(s)
PROFILE_RATE = 1000 ;1000 samples per sec
;
; Externs
;
;
global C ProfileFunctionAddress:dword
global C ProfilePtr:dword
global C ProfileList:dword
global C Stop_Profiler:near
global New_Profiler_Callback_:near
global Old_Profiler_Callback_:near
global C Profile_Init:near
global C Profile_End:near
global ___begtext:near
global BaseAddress:dword
global __PRO:near
global __EPI:near
global MyStack:dword
global MyStackPtr:dword
global ProAddress:dword
global EpiAddress:dword
codeseg
;*********************************************************************************************
;* __PRO -- registers the current procedure *
;* *
;* INPUT: Nothing *
;* *
;* OUTPUT: none *
;* *
;* Warnings: *
;* Assumes that ss:Esp points to return address in function to be registered *
;* *
;* HISTORY: *
;* 11/20/95 4:39PM ST : Created. *
;*===========================================================================================*
proc __PRO near
jmp [ProAddress]
; safe version of prologue code
Pro_Start: push eax
mov eax,[MyStackPtr]
push [ProfileFunctionAddress]
pop [eax*4+MyStack]
inc [MyStackPtr]
pop eax
push [dword ss:esp]
pop [ProfileFunctionAddress]
Pro_End: ret
; unsafe (but much faster) prologue code
; pop [ProfileFunctionAddress]
; jmp [ProfileFunctionAddress]
endp __PRO
;*********************************************************************************************
;* __EPI -- Registers the privious procedure as current again *
;* *
;* INPUT: Nothing *
;* *
;* OUTPUT: none *
;* *
;* Warnings: *
;* Assumes that calling procedure will pop ebp immediately on return so we dont have to *
;* preserve it. *
;* *
;* HISTORY: *
;* 11/20/95 4:42PM ST : Created. *
;*===========================================================================================*
proc __EPI near
jmp [EpiAddress]
; Safe version of epilogue code. Uncomment the push and pop for ultimate safety
Epi_Start: dec [MyStackPtr]
; push ebp
mov ebp,[MyStackPtr]
mov ebp,[ebp*4+MyStack]
mov [ProfileFunctionAddress],ebp
; pop ebp
Epi_End: ret
; Unsafe (but much faster) epilogue code. Makes lots of assumptions.
; push [dword esp+8]
; pop [ProfileFunctionAddress]
; ret
endp __EPI
;*********************************************************************************************
;* Profile_Init -- Initialises the .asm data required for profile session *
;* *
;* INPUT: Nothing *
;* *
;* OUTPUT: none *
;* *
;* Warnings: *
;* Assumes that '___begtext' is the first label in the code segment and that its *
;* address is within 15 bytes of the start of the code segment *
;* *
;* HISTORY: *
;* 11/20/95 4:44PM ST : Created. *
;*===========================================================================================*
proc Profile_Init C near
mov eax,offset ___begtext
and eax,0fffffff0h
mov [BaseAddress],eax
;mov [MyStackPtr],0
mov [ProfileList],PROFILE_RATE
mov [ProfilePtr],1
mov [ProAddress],offset Pro_Start
mov [EpiAddress],offset Epi_Start
ret
endp Profile_Init
;*********************************************************************************************
;* Profile_End -- disables the __PRO and __EPI procedures *
;* *
;* INPUT: Nothing *
;* *
;* OUTPUT: none *
;* *
;* Warnings: *
;* *
;* HISTORY: *
;* 11/20/95 4:44PM ST : Created. *
;*===========================================================================================*
proc Profile_End C near
mov [ProAddress],offset Pro_End
mov [EpiAddress],offset Epi_End
ret
endp Profile_End
;*********************************************************************************************
;* New_Profiler_Callback -- Windows callback used to register function hits *
;* *
;* INPUT: Nothing *
;* *
;* OUTPUT: none *
;* *
;* Note: *
;* The frequency that this is called depends on MAX_PROFILE_RATE defined here and in *
;* profile.h *
;* *
;* HISTORY: *
;* 11/20/95 4:47PM ST : Created. *
;*===========================================================================================*
proc New_Profiler_Callback_ near
push eax
push esi
mov esi,[ProfilePtr]
cmp esi,MAX_PROFILE_TIME*PROFILE_RATE
jge @@out
mov eax,[ProfileFunctionAddress]
sub eax,[BaseAddress]
mov [ProfileList+esi*4],eax
inc [ProfilePtr]
pop esi
pop eax
ret
@@out: call Stop_Profiler
pop esi
pop eax
ret
endp New_Profiler_Callback_
;*********************************************************************************************
;* Old_Profiler_Callback -- Windows callback used to register function hits *
;* *
;* INPUT: Windows timer callback stuff - not used *
;* *
;* OUTPUT: none *
;* *
;* Note: *
;* The frequency that this is called depends on MAX_PROFILE_RATE defined here and in *
;* profile.h *
;* *
;* HISTORY: *
;* 11/20/95 4:47PM ST : Created. *
;*===========================================================================================*
proc Old_Profiler_Callback_ near
push eax
push esi
mov esi,[ProfilePtr]
cmp esi,MAX_PROFILE_TIME*PROFILE_RATE
jge @@out
mov eax,[ProfileFunctionAddress]
sub eax,[BaseAddress]
mov [ProfileList+esi*4],eax
inc [ProfilePtr]
pop esi
pop eax
ret 14h
@@out: call Stop_Profiler
pop esi
pop eax
ret 14h
endp Old_Profiler_Callback_
dataseg
align 4
ProfileFunctionAddress dd 0 ;Ptr to function we are currently in
BaseAddress dd 0 ;Address of the code segment start
MyStackPtr dd 0 ;offset into my stack table
ProAddress dd Pro_Start ;jmp ptr for __PRO procedure
EpiAddress dd Epi_Start ;jmp ptr for __EPI procedure
label MyStack dword ;my stack table
dd 16*1024 dup (?)
end

179
WIN32LIB/PROFILE/MAKEFILE Normal file
View File

@@ -0,0 +1,179 @@
#
# Command & Conquer Red Alert(tm)
# Copyright 2025 Electronic Arts Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#***************************************************************************
#** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S **
#***************************************************************************
#* *
#* Project Name : Westwood Library .LIB makefile *
#* *
#* File Name : MAKEFILE *
#* *
#* Programmer : Julio R. Jerez *
#* *
#* Start Date : Jan 26, 1995 *
#* *
#* *
#*-------------------------------------------------------------------------*
#* *
#* Required environment variables: *
#* WIN32LIB = your root WIN32LIB path *
#* WIN32VCS = root directory for wwlib version control archive *
#* COMPILER = your Watcom installation path *
#* *
#* Required changes to makefile: *
#* PROJ_NAME = name of the library you're building *
#* OBJECTS = list of objects in your library *
#* *
#* Optional changes to makefile: *
#* PROJ_DIR = full pathname of your working directory *
#* .path.xxx = full pathname where various file types live *
#* *
#***************************************************************************
#---------------------------------------------------------------------------
# Verify user's environment
#---------------------------------------------------------------------------
!ifndef %WIN32LIB
!error WIN32LIB Environment var not configured.
!endif
!ifndef %WIN32VCS
!error WIN32VCS Environment var not configured.
!endif
!ifndef %WATCOM
!error WATCOM Environment var not configured.
!endif
#===========================================================================
# User-defined section: the user should tailor this section for each project
#===========================================================================
PROJ_NAME = profile
PROJ_DIR = $(%WIN32LIB)\$(PROJ_NAME)
LIB_DIR = $(%WIN32LIB)\lib
!include $(%WIN32LIB)\project.cfg
#---------------------------------------------------------------------------
# Project-dependent variables
#---------------------------------------------------------------------------
OBJECTS = &
wprofile.obj &
aprofile.obj
#---------------------------------------------------------------------------
# Path macros: one path for each file type.
# These paths are used to tell make where to find/put each file type.
#---------------------------------------------------------------------------
.asm: $(PROJ_DIR)
.c: $(PROJ_DIR)
.cpp: $(PROJ_DIR)
.h: $(PROJ_DIR)
.obj: $(PROJ_DIR)
.lib: $(WWLIB)\lib
.exe: $(PROJ_DIR)
#===========================================================================
# Pre-defined section: there should be little need to modify this section.
#===========================================================================
#---------------------------------------------------------------------------
# Tools/commands
#---------------------------------------------------------------------------
C_CMD = wcc386
CPP_CMD = wpp386
LIB_CMD = wlib
LINK_CMD = wlink
ASM_CMD = tasm
#---------------------------------------------------------------------------
# Include & library paths
# If LIB & INCLUDE are already defined, they are used in addition to the
# WWLIB32 lib & include; otherwise, they're constructed from
# BCDIR & TNTDIR
#---------------------------------------------------------------------------
LIBPATH = $(%WIN32LIB)\LIB;$(%WATCOM)\LIB
INCLUDEPATH = $(%WIN32LIB)\INCLUDE;$(%WATCOM)\H
#---------------------------------------------------------------------------
# Implicit rules
# Compiler:
# ($< = full dependent with path)
# Assembler:
# output obj's are constructed from .obj: & the $& macro
# ($< = full dependent with path)
# tasm's cfg file is not invoked as a response file.
#---------------------------------------------------------------------------
.c.obj: $(%WIN32LIB)\project.cfg .AUTODEPEND
*$(C_CMD) $(CC_CFG) $<
.cpp.obj: $(%WIN32LIB)\project.cfg .AUTODEPEND
*$(CPP_CMD) $(CC_CFG) $(PROJ_DIR)\$^*.cpp
.asm.obj: $(%WIN32LIB)\project.cfg
$(ASM_CMD) $(ASM_CFG) $<
#---------------------------------------------------------------------------
# Default target: configuration files & library (in that order)
#---------------------------------------------------------------------------
all: $(LIB_DIR)\$(PROJ_NAME).lib .SYMBOLIC
#---------------------------------------------------------------------------
# Build the library
# The original library is deleted by the librarian
# Lib objects & -+ commands are constructed by substituting within the
# $^@ macro (which expands to all target dependents, separated with
# spaces)
# Tlib's cfg file is not invoked as a response file.
# All headers & source files are copied into WIN32LIB\SRCDEBUG, for debugging
#---------------------------------------------------------------------------
$(LIB_DIR)\$(PROJ_NAME).lib: $(OBJECTS) objects.lbc
copy *.h $(%WIN32LIB)\include
copy *.inc $(%WIN32LIB)\include
copy *.cpp $(%WIN32LIB)\srcdebug
copy *.asm $(%WIN32LIB)\srcdebug
$(LIB_CMD) $(LIB_CFG) $^@ @objects.lbc
#---------------------------------------------------------------------------
# Objects now have a link file which is NOT generated everytime. Instead
# it just has its own dependacy rule.
#---------------------------------------------------------------------------
objects.lbc : $(OBJECTS)
%create $^@
for %index in ($(OBJECTS)) do %append $^@ +%index
#---------------------------------------------------------------------------
# Create the test directory and make it.
#---------------------------------------------------------------------------
test:
mkdir test
cd test
copy $(%WWVCS)\$(PROJ_NAME)\test\vcs.cfg
update
wmake
cd ..
#**************************** End of makefile ******************************

View File

@@ -0,0 +1,96 @@
/*
** Command & Conquer Red Alert(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/***********************************************************************************************
*** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
***********************************************************************************************
* *
* Project Name : Library profiler *
* *
* File Name : PROFILE.CPP *
* *
* Programmer : Steve Tall *
* *
* Start Date : 11/17/95 *
* *
* Last Update : November 20th 1995 [ST] *
* *
*---------------------------------------------------------------------------------------------*
* Overview: *
* The profiler works by using the function prologue and epilogue hooks available in Watcom *
* to register the current functions address in a global variable and then sampling the *
* contents of the variable using a windows timer which runs at up to 1000 samples per second. *
* *
* Compile the code to be sampled with the -ep and -ee flags to enable the prologue (__PRO) *
* and epilogue (__EPI) calls to be generated. *
* At the beginning of the section to be profiled (just before main loop normally) call the *
* Start_Profiler function to start sampling. At the end of the section, call Stop_Profiler *
* which will stop the timer and write the profile data to disk in the PROFILE.BIN file. *
* Use *
* *
*---------------------------------------------------------------------------------------------*
* *
* Functions: *
* Start_Profiler -- initialises the profiler data and starts gathering data *
* Stop_Profiler -- stops the timer and writes the profile data to disk *
* *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#define WIN32
#ifndef _WIN32 // Denzil 6/2/98 Watcom 11.0 complains without this check
#define _WIN32
#endif // _WIN32
#include <windows.h>
#include <windowsx.h>
#include <wwstd.h>
#include <rawfile.h>
#include <file.h>
#include "profile.h"
extern "C"{
unsigned ProfileList [PROFILE_RATE*60*MAX_PROFILE_TIME];
unsigned ProfilePtr;
}
extern "C" void Profiler_Callback ( UINT, UINT , DWORD, DWORD, DWORD );
unsigned ProfilerEvent;
void Start_Profiler (void)
{
memset (&ProfileList[0],-1,PROFILE_RATE*60*MAX_PROFILE_TIME*4);
Copy_CHK();
ProfilerEvent = timeSetEvent (1000/PROFILE_RATE , 1 , (void CALLBACK (UINT,UINT,DWORD,DWORD,DWORD))Profiler_Callback , 0 , TIME_PERIODIC);
}
void Stop_Profiler (void)
{
if (ProfilerEvent){
timeKillEvent(ProfilerEvent);
ProfilerEvent=NULL;
int handle = Open_File ( "profile.bin" , WRITE );
if (handle != WW_ERROR){
Write_File (handle , &ProfileList[0] , ProfilePtr*4);
Close_File (handle);
}
}
}

105
WIN32LIB/PROFILE/PROFILE.H Normal file
View File

@@ -0,0 +1,105 @@
/*
** Command & Conquer Red Alert(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/***********************************************************************************************
*** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
***********************************************************************************************
* *
* Project Name : Library profiler *
* *
* File Name : PROFILE.H *
* *
* Programmer : Steve Tall *
* *
* Start Date : 11/17/95 *
* *
* Last Update : November 20th 1995 [ST] *
* *
*---------------------------------------------------------------------------------------------*
* Overview: *
* *
* New System *
* ~~~~~~~~~~~ *
* *
* The new profiler system creates a seperate thread and then starts a timer off there. The *
* timer in the second thread uses GetThreadContext to sample the IP address of each user *
* thread. This system has the advantage of being able to sample what is happening in all the *
* threads we own not just the main thread. Another advantage is that it doesnt require a *
* major recompilation. *
* The disadvantage is that we dont really know what is going on when the IP is outside the *
* scope of our threads - We could be in direct draw, direct sound or even something like the *
* VMM and there is no way to tell. *
* *
* *
* *
* Old System *
* ~~~~~~~~~~~ *
* *
* The profiler works by using the function prologue and epilogue hooks available in Watcom *
* to register the current functions address in a global variable and then sampling the *
* contents of the variable using a windows timer which runs at up to 1000 samples per second.*
* *
* Compile the code to be sampled with the -ep and -ee flags to enable the prologue (__PRO) *
* and epilogue (__EPI) calls to be generated. *
* At the beginning of the section to be profiled (just before main loop normally) call the *
* Start_Profiler function to start sampling. At the end of the section, call Stop_Profiler *
* which will stop the timer and write the profile data to disk in the PROFILE.BIN file. *
* *
* Use PROFILE.EXE to view the results of the session. *
* *
* The addition of prologue and epilogue code will slow down the product and the profiler *
* allocates a huge buffer for data so it should not be linked in unless it is going to be *
* used. *
* *
* The advantage of the prologue/epilogue approach is that all samples represent valid *
* addresses within our code so we get valid results we can use even when the IP is in system *
* code. *
* *
* *
*---------------------------------------------------------------------------------------------*
* *
* Functions: *
* *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#define MAX_PROFILE_TIME 60*1 //1 minute(s) @ 14.4 Mb per hour
#define PROFILE_RATE 1000 //samples per sec (max 1000)
/*
* Defines for choosing between the old and new profiler system
*
*/
#define OLD_PROFILE_SYSTEM 1
#define NEW_PROFILE_SYSTEM 2
//#define PROFILE_SYSTEM OLD_PROFILE_SYSTEM
#define PROFILE_SYSTEM NEW_PROFILE_SYSTEM
extern "C"{
void __cdecl Profile_Init(void);
void __cdecl Profile_End(void);
void __cdecl Start_Profiler(void);
void __cdecl Stop_Profiler(void);
}

View File

@@ -0,0 +1,44 @@
;
; Command & Conquer Red Alert(tm)
; Copyright 2025 Electronic Arts Inc.
;
; This program is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with this program. If not, see <http://www.gnu.org/licenses/>.
;
;USE_PROFILER =1
macro prologue
Ifdef USE_PROFILER
global __PRO:near
call __PRO
endif ;USE_PROFILER
endm
macro epilogue
ifdef USE_PROFILER
global __EPI:near
push ebp
call __EPI
pop ebp
endif ;USE_PROFILER
endm

View File

@@ -0,0 +1,891 @@
/*
** Command & Conquer Red Alert(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/***********************************************************************************************
*** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
***********************************************************************************************
* *
* Project Name : Library profiler *
* *
* File Name : PROFILE.CPP *
* *
* Programmer : Steve Tall *
* *
* Start Date : 11/17/95 *
* *
* Last Update : November 20th 1995 [ST] *
* *
*---------------------------------------------------------------------------------------------*
* Overview: *
* Uses a map file to match addresses of functions in the sample file with their names *
* *
* *
*---------------------------------------------------------------------------------------------*
* *
* Functions: *
* Start_Profiler -- initialises the profiler data and starts gathering data *
* Stop_Profiler -- stops the timer and writes the profile data to disk *
* *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include <string.h>
#include <stdio.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <fcntl.h>
#include <malloc.h>
#include <io.h>
#include <conio.h>
#define bool int
#define true 1
#define false 0
#define NAME_TABLE_SIZE 1000000 //Storage space for function names
#define SAMPLE_START 1 //Offset (in dwords) of sample data in sample file
/*
** Function prototypes
*/
void Print_My_Name(void);
void Print_Usage(void);
int Load_File(char *file_name , unsigned *file_ptr , unsigned mode);
bool Extract_Function_Addresses(void);
unsigned Get_Hex (char string[] , int length);
char *Search_For_Char (char character , char buffer_ptr[] , int buffer_length);
char *Search_For_String (char *string , char *buffer_ptr , int buffer_length);
void Map_Profiler_Hits (void);
void Sort_Functions(void);
void Sort_Functions_Again(void);
void Output_Profile(void);
char *SampleFile; //Ptr to sample file name
char *MapFile; //Ptr to map file name
unsigned *SampleFileBuffer; //Ptr to buffer that sample file is loaded in to
char *MapFileBuffer; //Ptr to buffer that map file is loaded in to
unsigned SampleFileLength; //Length of sample file
unsigned MapFileLength; //Length of map file
char FunctionNames[NAME_TABLE_SIZE]; //Buffer to store function names in
char *FunctionNamePtr=&FunctionNames[0]; //Ptr to end of last function name in buffer
int TotalFunctions; //Total number of functions extracted from map file
int SampleRate; //Number of samples/sec that data was collected at
unsigned EndCodeSegment; //Length of the sampled programs code segments
/*
** Structure for collating function data
*/
typedef struct tFunction {
unsigned FunctionAddress; //Address of function relative to start of code seg
char *FunctionName; //Ptr to name of function in FunctionNames buffer
int Hits; //Number of times function was 'hit' when sampling
} Function;
Function FunctionList[10000]; //max 10,000 functions in map file.
/***********************************************************************************************
* main -- program entry point *
* *
* *
* *
* INPUT: argc , argv *
* *
* OUTPUT: 0 *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 11/20/95 5:21PM ST : Created *
*=============================================================================================*/
int main(int argc, char *argv[])
{
Print_My_Name(); // print the programs name
/*
** If the arguments dont make sense then print the usage
*/
if (argc!=3 ||
!strcmpi(argv[1],"/?") ||
!strcmpi(argv[1],"/h") ||
!strcmpi(argv[1],"/help") ||
!strcmpi(argv[1],"-?") ||
!strcmpi(argv[1],"-h") ||
!strcmpi(argv[1],"-help") ||
!strcmpi(argv[1],"?") ||
!strcmpi(argv[1],"h") ||
!strcmpi(argv[1],"help")){
Print_Usage();
return(0);
}
/*
** Get the names of the files to load
*/
SampleFile=argv[1];
MapFile=argv[2];
/*
** Load the profile sample file
*/
SampleFileLength = Load_File (SampleFile , (unsigned*)&SampleFileBuffer , O_BINARY);
if (!SampleFileLength) return(0);
/*
** The sample rate is the 1st dword in the file
*/
SampleRate=*SampleFileBuffer;
/*
** Load the .map file
*/
MapFileLength = Load_File (MapFile , (unsigned*)&MapFileBuffer , O_BINARY);
if (!MapFileLength){
free (SampleFileBuffer);
return(0);
}
/*
** Get the function names from the map file
*/
cprintf ("Extracting function data from map file.\n");
if (!Extract_Function_Addresses()){
cprintf ("Error parsing .MAP file - aborting\n\n");
return (0);
}
/*
** Sort the functions into address order to make it easier to map the functions
*/
cprintf ("Sorting function list by address");
Sort_Functions();
/*
** Map the addresses in the sample file to the function addresses
*/
cprintf ("\nMapping profiler hits to functions");
Map_Profiler_Hits();
/*
** Sort the functions into order of usage for output
*/
cprintf ("\nSorting function list by activity");
Sort_Functions_Again();
cprintf ("\n\n");
/*
** Print the function usage statistics
*/
Output_Profile();
/*
** Cleanup and out
*/
free (SampleFileBuffer);
free (MapFileBuffer);
return(0);
}
/***********************************************************************************************
* Print_My_Name -- print the programs name and version *
* *
* *
* INPUT: Nothing *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 11/20/95 5:25PM ST : Created *
*=============================================================================================*/
void Print_My_Name(void)
{
cprintf("Westwood profile data analyzer.\n");
cprintf("V 1.0 - 11/17/95\n");
cprintf("Programmer - Steve Tall.\n\n");
}
/***********************************************************************************************
* Print_Usage -- print the instructions *
* *
* *
* INPUT: Nothing *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 11/20/95 5:26PM ST : Created *
*=============================================================================================*/
void Print_Usage (void)
{
cprintf("Usage: PROFILE <sample_file> <map_file)\n\n");
}
/***********************************************************************************************
* File_Error -- display a file error message *
* *
* *
* INPUT: name of file error occurred on *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 11/20/95 5:26PM ST : Created *
*=============================================================================================*/
void File_Error (char *file_name)
{
cprintf ("Error reading file:%s - aborting\n",file_name);
}
/***********************************************************************************************
* Memory_Error -- display an out of memory message *
* *
* *
* *
* INPUT: Nothing *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 11/20/95 5:27PM ST : Created *
*=============================================================================================*/
void Memory_Error (void)
{
cprintf ("Error - insufficient memory - aborting\n");
}
/***********************************************************************************************
* Load_File -- load an entire file into memory *
* *
* *
* *
* INPUT: File name *
* address to load at *
* read mode (text or binary) *
* *
* OUTPUT: number of bytes read *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 11/20/95 5:27PM ST : Created *
*=============================================================================================*/
int Load_File(char *file_name , unsigned *load_addr , unsigned mode)
{
int handle;
unsigned file_length;
void *buffer;
handle=open (file_name , O_RDONLY | mode);
if (handle==-1){
File_Error(file_name);
return (false);
}
file_length = filelength(handle);
if (file_length==-1) return (false);
buffer = malloc (file_length+10);
if (!buffer){
Memory_Error();
return (false);
}
if (read (handle , buffer , file_length)!=file_length){
File_Error(file_name);
free(buffer);
return (false);
}
close (handle);
*load_addr = (unsigned)buffer;
return (file_length);
}
/***********************************************************************************************
* Map_Profiler_Hits -- map function hits from sample file to functions in map file *
* *
* *
* *
* INPUT: Nothing *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: Map file functions must be sorted into address order 1st *
* *
* HISTORY: *
* 11/20/95 5:28PM ST : Created *
*=============================================================================================*/
void Map_Profiler_Hits (void)
{
unsigned *samples=(unsigned*)SampleFileBuffer;
unsigned function_hit;
for (int i=SAMPLE_START ; i<SampleFileLength/4 ; i++){
function_hit=*(samples+i);
if (1023==(1023 & i)){
cprintf (".");
}
for (int j=TotalFunctions-1 ; j>=0 ; j--){
if (FunctionList[j].FunctionAddress < function_hit){
FunctionList[j].Hits++;
break;
}
}
}
}
/***********************************************************************************************
* Sort_Functions -- hideous bubble sort of functions into address order *
* *
* *
* INPUT: Nothing *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 11/20/95 5:29PM ST : Created *
*=============================================================================================*/
void Sort_Functions (void)
{
Function address_swap;
if (TotalFunctions>1){
for (int outer=0 ; outer <TotalFunctions ; outer++){
address_swap.FunctionAddress=0;
if (127==(127 & outer)){
cprintf (".");
}
for (int inner=0 ; inner < TotalFunctions-1 ; inner++){
if (FunctionList[inner].FunctionAddress > FunctionList[inner+1].FunctionAddress ){
memcpy (&address_swap , &FunctionList[inner] , sizeof(Function));
memcpy (&FunctionList[inner] , &FunctionList[inner+1] , sizeof(Function));
memcpy (&FunctionList[inner+1] , &address_swap , sizeof(Function));
}
}
if (!address_swap.FunctionAddress) break;
}
}
}
/***********************************************************************************************
* Sort_Functions -- hideous bubble sort of functions into usage order *
* *
* *
* INPUT: Nothing *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 11/20/95 5:29PM ST : Created *
*=============================================================================================*/
void Sort_Functions_Again (void)
{
Function address_swap;
if (TotalFunctions>1){
for (int outer=0 ; outer <TotalFunctions ; outer++){
address_swap.FunctionAddress=0;
if (127==(127 & outer)){
cprintf (".");
}
for (int inner=0 ; inner < TotalFunctions-1 ; inner++){
if (FunctionList[inner].Hits < FunctionList[inner+1].Hits ){
memcpy (&address_swap , &FunctionList[inner] , sizeof(Function));
memcpy (&FunctionList[inner] , &FunctionList[inner+1] , sizeof(Function));
memcpy (&FunctionList[inner+1] , &address_swap , sizeof(Function));
}
}
if (!address_swap.FunctionAddress) break;
}
}
}
/***********************************************************************************************
* Output_Profile -- output the function data to the screen *
* *
* *
* INPUT: Nothing *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 11/20/95 5:31PM ST : Created *
*=============================================================================================*/
void Output_Profile(void)
{
double period=(((double)SampleFileLength/(double)4) - (double)SAMPLE_START) / (double)SampleRate;
double percentage;
printf ( "\n Profile information from %s and %s.\n\n",SampleFile,MapFile);
printf ( "Samples collected:%d\n" , SampleFileLength/4-SAMPLE_START);
printf ( "Sample rate :%d samples per second\n",SampleRate);
printf ( "Sample period :%f seconds\n\n\n" , period);
printf ( "Hits %% Function\n");
printf ( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
for (int i=0 ; i<TotalFunctions ; i++){
if (FunctionList[i].Hits){
percentage= ((double)FunctionList[i].Hits*(double)100) / ((double)SampleFileLength/(double)4-(double)SAMPLE_START);
printf ("%-6d %-3.3f%% %s\n",FunctionList[i].Hits , percentage , FunctionList[i].FunctionName);
}
}
}
/***********************************************************************************************
* Extract_Function_Addresses -- gets the addresses of all global functions from a map file *
* *
* *
* INPUT: Nothing *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: true if successfully extracted *
* *
* HISTORY: *
* 11/20/95 5:31PM ST : Created *
*=============================================================================================*/
bool Extract_Function_Addresses(void)
{
char *map_ptr;
char *segment_ptr;
char *end_str_ptr;
unsigned chars_left=MapFileLength;
int function_name_length;
unsigned end_of_last_code_segment;
unsigned code_segment_start;
unsigned code_segment_size;
char unknown[]={"Windows API or system code."};
/*
** Clear out the list of functions
*/
memset (&FunctionNames[0] , 0 , NAME_TABLE_SIZE);
/*
** Search for the 'Segments' header in the memory map
*/
segment_ptr = Search_For_String ("Segments" , MapFileBuffer , chars_left);
if (!segment_ptr) return (false);
chars_left = MapFileLength - ( (unsigned)segment_ptr - (unsigned)MapFileBuffer );
segment_ptr = Search_For_String ("+-----" , segment_ptr , chars_left);
segment_ptr +=2;
chars_left = MapFileLength - ( (unsigned)segment_ptr - (unsigned)MapFileBuffer );
/*
** Get the length of the segment section by searching for the start of the next section
*/
end_str_ptr = Search_For_String ("+-----" , segment_ptr , chars_left);
if (end_str_ptr){
chars_left = end_str_ptr - segment_ptr;
} else {
return (false);
}
EndCodeSegment = 0;
/*
** Find the end of the last code segment
*/
do {
/*
** Search for a code segment identifier
*/
chars_left = end_str_ptr - segment_ptr;
segment_ptr = Search_For_String ("CODE" , segment_ptr , chars_left);
if (!segment_ptr) break; //No more code segments so break
/*
** Search for the segment address which should always be 0001
*/
chars_left = end_str_ptr - segment_ptr;
segment_ptr = Search_For_String ("0001:" , segment_ptr , chars_left);
if (!segment_ptr) return (false); //Couldnt find the segment address - must be a problem so abort
/*
** Get the start address and length of the segment
*/
code_segment_start = Get_Hex(segment_ptr+5,8);
code_segment_size = Get_Hex(segment_ptr+16,8);
/*
** If this segment ends higher in memory than the previous highest then
** we have a new last segment
*/
if (code_segment_start+code_segment_size > EndCodeSegment){
EndCodeSegment = code_segment_start+code_segment_size;
}
chars_left = end_str_ptr - segment_ptr;
segment_ptr = Search_For_Char ( 13 , segment_ptr , chars_left );
chars_left = end_str_ptr - segment_ptr;
} while (chars_left > 0);
chars_left=MapFileLength;
/*
** Search for the 'Memory Map' segment of the map file
*/
map_ptr = Search_For_String ("Memory Map" , MapFileBuffer , chars_left);
if (!map_ptr){
return (false);
}
chars_left = MapFileLength - ( (unsigned)map_ptr - (unsigned)MapFileBuffer );
/*
** Get the length of the memory map segment by searching for the start of the next segment
*/
end_str_ptr = Search_For_String ("+-----" , map_ptr , chars_left);
if (end_str_ptr){
MapFileLength = ((unsigned)MapFileBuffer + MapFileLength) - (unsigned)end_str_ptr;
}
chars_left = MapFileLength - ( (unsigned)map_ptr - (unsigned)MapFileBuffer );
/*
** Reset the total number of functions found
*/
TotalFunctions = 0;
/*
**
** Find each occurrence of 0001: as all the functions we want are in the 1st segment
**
*/
do {
/*
** Find '0001:'
*/
map_ptr = Search_For_String ("0001:" , map_ptr , chars_left);
if (!map_ptr){
break;
}
chars_left = MapFileLength - ( (unsigned)map_ptr - (unsigned)MapFileBuffer );
/*
** Skip the '0001:' portion of the address and get the hext offset of the function
*/
map_ptr+=5;
FunctionList[TotalFunctions].FunctionAddress=Get_Hex(map_ptr,8);
/*
** Skip to the function name and get its length by searching for the end of the line
*/
map_ptr+=10;
chars_left = MapFileLength - ( (unsigned)map_ptr - (unsigned)MapFileBuffer );
end_str_ptr = Search_For_Char (13 , map_ptr , chars_left);
if (!end_str_ptr){
break;
}
function_name_length = (unsigned)end_str_ptr - (unsigned)map_ptr;
/*
** Copy the function name into the name list and keep a pointer to it
*/
memcpy (FunctionNamePtr , map_ptr , function_name_length);
FunctionList[TotalFunctions].FunctionName = FunctionNamePtr;
FunctionNamePtr += function_name_length+1; //Leave an extra 0 on the end as a terminator
FunctionList[TotalFunctions].Hits = 0; //We dont yet know how many times we hit it
TotalFunctions++;
} while (1);
/*
** Add in a dummy function at the highest address to represent unknown code hits
*/
FunctionList[TotalFunctions].FunctionAddress = EndCodeSegment;
memcpy (FunctionNamePtr , &unknown , sizeof (unknown));
FunctionList[TotalFunctions].FunctionName = FunctionNamePtr;
FunctionNamePtr += sizeof (unknown);
FunctionList[TotalFunctions].Hits = 0;
TotalFunctions++;
return (true);
}
/***********************************************************************************************
* Get_Hex -- nasty function to convert an ascii hex number to an unsigned int *
* I'm sure there must be a lovely 'c' way of doing this but I dont know what it is *
* *
* *
* INPUT: ptr to ascii hex string , number of digits in string *
* *
* OUTPUT: value of hex string *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 11/20/95 5:39PM ST : Created *
*=============================================================================================*/
unsigned Get_Hex (char string[] , int length)
{
unsigned hex_val=0;
int multiplier=1;
char hex_char;
for (int i=0 ; i<length ; i++){
hex_char=string[length-1-i];
switch (hex_char){
case '0':
hex_char=0;
break;
case '1':
hex_char=1;
break;
case '2':
hex_char=2;
break;
case '3':
hex_char=3;
break;
case '4':
hex_char=4;
break;
case '5':
hex_char=5;
break;
case '6':
hex_char=6;
break;
case '7':
hex_char=7;
break;
case '8':
hex_char=8;
break;
case '9':
hex_char=9;
break;
case 'A':
hex_char=10;
break;
case 'B':
hex_char=11;
break;
case 'C':
hex_char=12;
break;
case 'D':
hex_char=13;
break;
case 'E':
hex_char=14;
break;
case 'F':
hex_char=15;
break;
case 'a':
hex_char=10;
break;
case 'b':
hex_char=11;
break;
case 'c':
hex_char=12;
break;
case 'd':
hex_char=13;
break;
case 'e':
hex_char=14;
break;
case 'f':
hex_char=15;
break;
}
hex_val += hex_char * multiplier;
multiplier = multiplier<<4;
}
return (hex_val);
}
/***********************************************************************************************
* Search_For_Char -- search through ascii data for a particular character *
* *
* *
* *
* INPUT: character *
* ptr to buffer *
* length of buffer *
* *
* OUTPUT: ptr to char in buffer or NULL if not found *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 11/20/95 5:41PM ST : Created *
*=============================================================================================*/
char *Search_For_Char (char character , char buffer_ptr[] , int buffer_length)
{
for ( unsigned i=0 ; i<buffer_length ; i++){
if (buffer_ptr[i]==character){
return ((char*) (unsigned)buffer_ptr+i);
}
}
return (NULL);
}
/***********************************************************************************************
* Search_For_String -- search for a string of chars within a buffer *
* *
* *
* *
* INPUT: string *
* ptr to buffer to search in *
* length of buffer *
* *
* OUTPUT: ptr to string in buffer or NULL if not found *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 11/20/95 5:42PM ST : Created *
*=============================================================================================*/
char *Search_For_String (char *string , char *buffer_ptr , int buffer_length)
{
int j;
int string_length=strlen(string);
for (int i=0 ; i<buffer_length-string_length ; i++){
for (j=0 ; j<string_length ; j++){
if ( *(string+j) != *(buffer_ptr+i+j)) break;
}
if (j==string_length) return buffer_ptr+i;
}
return (NULL);
}

View File

@@ -0,0 +1,272 @@
/*
** Command & Conquer Red Alert(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/***********************************************************************************************
*** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
***********************************************************************************************
* *
* Project Name : Library profiler *
* *
* File Name : WPROFILE.CPP *
* *
* Programmer : Steve Tall *
* *
* Start Date : 11/17/95 *
* *
* Last Update : November 20th 1995 [ST] *
* *
*---------------------------------------------------------------------------------------------*
* Overview: *
* *
* New System *
* ~~~~~~~~~~~ *
* *
* The new profiler system creates a seperate thread and then starts a timer off there. The *
* timer in the second thread uses GetThreadContext to sample the IP address of each user *
* thread. This system has the advantage of being able to sample what is happening in all the *
* threads we own not just the main thread. Another advantage is that it doesnt require a *
* major recompilation. *
* The disadvantage is that we dont really know what is going on when the IP is outside the *
* scope of our threads - We could be in direct draw, direct sound or even something like the *
* VMM and there is no way to tell. *
* *
* *
* *
* Old System *
* ~~~~~~~~~~~ *
* *
* The profiler works by using the function prologue and epilogue hooks available in Watcom *
* to register the current functions address in a global variable and then sampling the *
* contents of the variable using a windows timer which runs at up to 1000 samples per second.*
* *
* Compile the code to be sampled with the -ep and -ee flags to enable the prologue (__PRO) *
* and epilogue (__EPI) calls to be generated. *
* At the beginning of the section to be profiled (just before main loop normally) call the *
* Start_Profiler function to start sampling. At the end of the section, call Stop_Profiler *
* which will stop the timer and write the profile data to disk in the PROFILE.BIN file. *
* *
* Use PROFILE.EXE to view the results of the session. *
* *
* The addition of prologue and epilogue code will slow down the product and the profiler *
* allocates a huge buffer for data so it should not be linked in unless it is going to be *
* used. *
* *
* The advantage of the prologue/epilogue approach is that all samples represent valid *
* addresses within our code so we get valid results we can use even when the IP is in system *
* code. *
* *
*---------------------------------------------------------------------------------------------*
* *
* Functions: *
* Start_Profiler -- initialises the profiler data and starts gathering data *
* Stop_Profiler -- stops the timer and writes the profile data to disk *
* *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#define WIN32
#ifndef _WIN32 // Denzil 6/2/98 Watcom 11.0 complains without this check
#define _WIN32
#endif // _WIN32
#include <windows.h>
#include <windowsx.h>
#include <wwstd.h>
#include <rawfile.h>
#include <file.h>
#include "profile.h"
#include <vdmdbg.h>
#include <timer.h>
#define PROFILE
extern "C"{
#ifdef PROFILE
unsigned ProfileList [PROFILE_RATE*MAX_PROFILE_TIME];
#else
unsigned ProfileList [2];
#endif
unsigned ProfilePtr;
}
extern "C" void Old_Profiler_Callback ( UINT, UINT , DWORD, DWORD, DWORD );
extern "C" void New_Profiler_Callback (void);
extern "C" {
extern unsigned ProfileFunctionAddress;
}
unsigned long ProfilerEvent; //Handle for profiler callback
unsigned long ProfilerThread; //Handle for profiler thread
HANDLE CCThreadHandle;
CONTEXT ThreadContext;
#if (PROFILE_SYSTEM == NEW_PROFILE_SYSTEM)
/***********************************************************************************************
* Thread_Callback -- gets the IP address of our thread and registers it *
* *
* *
* *
* INPUT: Windows timer callback parms - not used *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 1/2/96 6:37AM ST : Created *
*=============================================================================================*/
void CALLBACK Thread_Callback (UINT,UINT,DWORD,DWORD,DWORD)
{
ThreadContext.ContextFlags = VDMCONTEXT_CONTROL;
if (!InTimerCallback){
GetThreadContext ( CCThreadHandle , &ThreadContext );
}else{
GetThreadContext (TimerThreadHandle , &ThreadContext);
}
ProfileFunctionAddress = ThreadContext.Eip;
New_Profiler_Callback();
}
/***********************************************************************************************
* Profile_Thread -- this is the thread our profiler runs in. It just starts off a timer and *
* then buggers off into an infinite message loop. We shouldnt get messages *
* here as this isnt our primary thread *
* *
* INPUT: Nothing *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 1/2/96 6:39AM ST : Created *
*=============================================================================================*/
void Profile_Thread (void)
{
MSG msg;
ProfilerEvent = timeSetEvent (1000/PROFILE_RATE , 1 , Thread_Callback , 0 , TIME_PERIODIC);
//ProfilerEvent = timeSetEvent (100 , 1 , Thread_Callback , 0 , TIME_ONESHOT);
do {
GetMessage(&msg,NULL,0,0);
} while(1);
}
#endif //(PROFILE_SYSTEM == OLD_PROFILE_SYSTEM)
/***********************************************************************************************
* Start_Profiler -- initialises the profiler system and starts sampling *
* *
* *
* *
* INPUT: Nothing *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: There may be a pause when sampling starts as Win95 does some VMM stuff *
* *
* HISTORY: *
* 11/20/95 5:12PM ST : Created *
*=============================================================================================*/
void __cdecl Start_Profiler (void)
{
#ifdef PROFILE
if (!ProfilerEvent){
memset (&ProfileList[0],-1,PROFILE_RATE*MAX_PROFILE_TIME*4);
}
Profile_Init();
if (!ProfilerEvent){
#if (PROFILE_SYSTEM == OLD_PROFILE_SYSTEM)
/*
** Old profile system - just set up a timer to monitor the global variable based on
** the last place __PRO was called from
*/
ProfilerEvent = timeSetEvent (1000/PROFILE_RATE , 1 , (void CALLBACK (UINT,UINT,DWORD,DWORD,DWORD))Old_Profiler_Callback , 0 , TIME_PERIODIC);
#else
/*
** New profile system - create a second thread that will do all the profiling
** using GetThreadContext
*/
if ( DuplicateHandle( GetCurrentProcess(), GetCurrentThread() , GetCurrentProcess() ,&CCThreadHandle , 0 , TRUE , DUPLICATE_SAME_ACCESS) ){
ProfilerEvent= (unsigned)CreateThread(NULL,2048,(LPTHREAD_START_ROUTINE)&Profile_Thread,NULL,0,&ProfilerThread);
}
#endif
}
#else
ProfilerEvent = 0;
#endif
}
/***********************************************************************************************
* Stop_Profiler -- stops the sampling timer and writes the colledted data to disk *
* *
* *
* *
* INPUT: Nothing *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: Writes to file PROFILE.BIN *
* *
* HISTORY: *
* 11/20/95 5:13PM ST : Created *
*=============================================================================================*/
void __cdecl Stop_Profiler (void)
{
if (ProfilerEvent){
#if (PROFILE_SYSTEM == OLD_PROFILE_SYSTEM)
//
// Old system - just remove the timer event
//
timeKillEvent(ProfilerEvent);
#else
//
// New system - kill the profiling thread
//
TerminateThread((HANDLE)ProfilerThread,0);
#endif
ProfilerEvent=NULL;
Profile_End();
int handle = Open_File ( "profile.bin" , WRITE );
if (handle != WW_ERROR){
Write_File (handle , &ProfileList[0] , ProfilePtr*4);
Close_File (handle);
}
}
}