***************************************************************************** * The DOOM Hacker's Tool Kit v1.00 * ***************************************************************************** * Author: Joshua Jackson internet: joshjackson@delphi.com * * Release Date: 5/25/94 * * Language: Borland Pascal 7.0 (Mostly Object Oriented) * * Destination Platform: DOS real mode -OR- DPMI DOS protected mode * * * * The DOOM Hacker's Tool Kit is a product of Jackson Software * ***************************************************************************** Welcome fellow DOOM hacker! I have released this source code in hopes that someone out there with a bit more brains than I have can put it to good use. I am also making a request that anyone willing to trade source code and ideas, please email me at the above address. I am mostly interested in 3D rendering code, but am looking for anything that is not contained in this tool kit, or improves upon it. I am also considering setting up a DOOM Hacker's BBS. If you are interested in such a BBS, please let me know so. It will be setup on a File Point system with no ratio required for Long Distance callers. I would also accept UUENCODED source over the internet, which would then be placed onto the BBS. NOTE: This BBS would be for programs that include the SOURCE CODE only! I will also have a section for technical FAQs and the like... similiar to DMSPEC13.TXT All Languages would be accepted. This is a rather extensive set of utilities that I hacked up to access just about everything in the WAD file. Resource utilities include: Sprite Viewer Map Viewer (With object recognition/sprite viewing) Floor/Ceiling Texture Viewer Wall Texture Viewier Sound Player Resource Extractor Object Caching In addition, I have also included the source code for the DOOM Front End v1.31. The majority of the code here is written in Borland Pascal 7.0 and is mostly geared toward OOP (Object Oriented Programming). There is also some assembly language thrown into it here and there. Comments, Suggestions, Bashing, and Questions are more than welcome! (Please, don't write just to tell me that the code is not fully cleaned up! I do realize that there are declared variables that aren't used and things of that nature. This is because I wanted to get this program into your hands as soon as possible, and that this code, even though its version 1.0, has done some massive evolving since I first began construction of it. It was originally written in staight pascal, no objects that is, and was VERY pathetic! This tool kit has probably under gone several dozen revisions since I begin the project about a month ago.) Contacting me: Internet: joshjackson@delphi.com Snail Mail: Joshua Jackson 10506 Bayard Rd. Minerva, OH 44657 Phone: (216) 868-1169 !*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!* DISCLAIMER: The author will not accept any resposibility for damage resulting from the use of these programs, whether it be hardware, software, or mental. COPYING/USE of this code: All of the code in the DOOM Hacker's tool kit is placed in the public domain with the exception of DFE. The DFE source has been released simply for educational purposes only, I would appriciate if you would not modify and release and versions of this program. !*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!* NOTE: If you plan to compile this tool kit under DPMI, you should use the BGI256.BGI or BGI256.OBJ (for linking in the graphics driver). I obtained these drivers off of the official Borland BBS. Most public domain BGI drivers will cause a general protection fault under DPMI, BGI256 is designed to be compatible. Well, enough of that, lets get onto the part you probably are interested in reading. The following are descriptions of all of the Objects/Routines that are contained in the WAD Hacker's Toolkit. ------------------------------------------------------------------------------ Unit: WADDECL.PAS Purpose: Declarations Unit There is not much to this unit, it is simply the type declarations for many of the different structured variables in the tool kit... If you can't understand this unit, you may want to stop here! :) ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ Unit: WAD.PAS Purpose: Accessing a WAD file's directory This unit is the center of the tool kit. It contains an object that loads a given WAD file's directory into memory. Almost all of the other units rely on this unit for their access into the WAD files. The object contained in this unit (TWadDirectory) MUST be initialized before any other units can be initialized. The following is a listing of the TWadDirectory object declaration: type PWadDirectory=^TWadDirectory; TWadDirectory=object WadName :array[1..9] of char; WadFile :file; WadID :array[1..4] of char; DirEntries :longint; DirStart :longint; DirEntry :PWADDirList; Constructor Init(WadFileName:String); Procedure DisplayWadDir; Function FindObject(ObjName:ObjNameStr):word; Procedure SetWadPalette(PlayPalNum:integer); Procedure RestorePalette; Destructor Done; end; When calling the init constructor, the only needed parameter is the path and name of the WAD file that you wish to load. The following is an example program that will load a WAD file directory: uses Wad; var WDir:PWadDirectory; begin {Initialize WAD file directory for DOOM.WAD} WDir:=New(PWadDirectory, Init('C:\DOOM\DOOM.WAD')); {Your code goes here} {Dispose of the WAD file directory} Dispose(WDir, Done); end. You will also notice that I included the call to the object's done destructor when disposing of the object. This is mandatory unless you want a ton of garbage left on the heap. The other methods besides init and done are use mainly by the other units in the tool kit. However, if you would like you implement them in your own programs here is the syntax for them: Function FindObject(ObjName:ObjNameStr):word Returns the position in the directory array of the given object's name. NOTE: you may use use the WildCard value of '?' for any or all letters in the name if you want to do a "find first that matches" type search. Procedure DisplayWadDir displays the given WAD file's directory to the screen Procedure SetWadPalette(PlayPalNum:integer); Sets the current graphics palette to one of the 17 palettes available in the WAD file. The main play palette is 0. Procedure RestorePalette Restores the current graphics palette to the original palette before the first call to SetWadPalette Finally, the variable fields in this object are defined as follows: WadName :array[1..9] of char; The ASCIIZ name of the WAD file that this object contains the directory to. (NOTE: the .WAD extension is not included) WadFile :file; This file identifier used when accessing the WAD file. This field should NEVER be altered. WadID :array[1..4] of char; Indicated if the loaded file is a 'PWAD' or an 'IWAD' DirEntries :longint; The number of directory entires contained in the WAD file. DirStart :longint; The file offset of the begining of the directory structure. DirEntry :PWADDirList; The directory entries themselves. The number of entries will vary depending on the size of the WAD file. NOTE: The number of entries in this array is always equal to DirEntries. The structure for PWADDirList is contained in the WADDECL unit and has the following structure: PWADDirList=^TWADDirList; TWADDirList=array[1..MaxEntries] of TWADDirEntry; PWADDirEntry=^TWADDirEntry; TWADDirEntry=record ObjStart :longint; ObjLength:longint; ObjName :array[1..8] of char; end; Another NOTE: I used a little trickery to make the size of the directory array variable. Therefor, it is possible to address entries outside of the directory array itself. Be careful not to do this, especially when in DPMI (You will more than likely cause a segment overrun error) ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ Unit: OBJCACHE.PAS Purpose: WAD Object Cache This unit is use to optimize the speed at which object are loaded from the WAD file. I had originally written routines for loading sprites and the like based on the code contained in DEU. Their code works good, but is slow as a dog! When using the code based on DEU, I was able to load around one sprite per second of so. When using the Object Cache, the speed of loading sprites increased to around 10-15 sprites/sec! The following is a listing of the TObjectCache declaration: type PObjectCache=^TObjectCache; TObjectCache=Object Constructor Init(WDir:PWadDirectory;ObjNum:word); Procedure SetPos(NewPos:longint); Function CurPos:Longint; Procedure IncPos(IncVal:longint); Procedure CacheRead(var Dest;Count:word); Function Size:Longint; Destructor Done; private NumLumps:byte; Lump:array[1..MaxLumps] of PCacheLump; CachePos:longint; LumpPos:word; CurLump:byte; end; Basically, the object cache is simply a way to allocate more that 64k for loading an object into memory. When initializing the TObjectCache object, a WAD file directory (see WAD.PAS) and an object number are needed. The object number is obtained through a call to TWadDirectory.FindObject. Once you have initialized the object cache, you may read blocks from it using calls to CacheRead. CacheRead requires a viariable destination and the number of bytes that you wish to transfer. After the read, the cache pointer will move to the end of the transfered area (kind of like a file pointer). You may move the cache pointer around in the cache by making calls to IncPos and SetPos. IncPos moves the pointer IncVal bytes from the current pointer, where SetPos moves the pointer relative to the beginning of the cache. CurPos returns the current value of the cache pointer. Size returns the number of bytes allocated for the cache. Sample Code: uses Wad,ObjCache; var WDir:PWadDirectory; OC:PObjectCache; begin {Initialize WAD file directory} WDir:=New(PWadDirectory, Init('C:\DOOM\DOOM.WAD')); {Initialize Object Cache for Object TROOA1 (an imp)} OC:=New(PObjectCache, Init(WDir, WDir^.FindObject('TROOA1 '))); {Your Code Goes Here} {Clean Up the Heap} Dispose(OC, Done); Dispose(WDir, Done); end. ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ Unit: FLOORS.PAS Purpose: Loading and Displaying Floor Texures Cached: no This unit will load and display a floor texture out of the wad file. The following is a listing of the TFloorTexture object declaration: type PFloorTexture=^TFloorTexture; TFloorTexture=object Constructor Init(WDir:PWadDirectory;FloorName:ObjNameStr); Procedure Draw(Scale,XOffset,YOffset:word); Destructor Done; private FBuff:PFloorBuff; end; You may notice that this is one of the least complex objects in the tool kit. To Initialize the object, you will need an initialized WAD file directory and the texture name for the floor. Once initialized, you may call Draw to display the texture on any part of the screen at any scale. The scale factor used is percentage based (100 = 1:1). NOTE: this unit requires you to initialize the GRAPH unit in Borland or Turbo Pascal. I had orginally written the screen output routines to directly write to screen memory, however, I switched over to the graph unit's PutPixel, that way I could maintain compatability with SVGA screen modes. If you would like to use this routine in a High Speed application, feel free to remove the graph unit oriented code. Sample Code: uses Graph,Wad,Floors; var WDir:PWadDirectory; FT:PFloorTexture; gd,gm:integer; begin {Initalize WAD directory} WDir:=New(PWadDirectory, Init('C:\DOOM\DOOM.WAD')); {Initalize Floor Texture} FT:=New(PFloorTexture, Init(WDir, 'NUKAGE1 ')); gd:=InstallUserDriver('BGI256',Nil); {640x480x256} gm:=2; {Initialize Graphics} InitGraph(gd,gm,''); {Set the Play Palette} WDir^.SetWadPalette(0); {Draw the texture 2:1 scale at coords 5,5} FT^.Draw(200,5,5); {Wait for the ENTER key} readln; {Restore the palette} WDir^.RestorePalette; {Shut down graphics} CloseGraph; {Clean up the heap} Dispose(FT, Done); Dispose(WDir, Done); end. -------------------------------------------------------------------------------- ------------------------------------------------------------------------------ Unit: THINGS.PAS Purpose: Loading and Displaying Sprites Cached: Yes This unit will load and display a sprite out of the wad file. You can also use this routine to load and display the title pictures and fonts. The following is a listing of the TWadThing declaration: Type PWadThing=^TWadThing; TWadThing=object SBuff:PPictureBuff; Constructor Init(WDir:PWadDirectory;ThingName:ObjNameStr); Procedure Draw(Scale,XOffset,YOffset:word); Function Height:word; Function Width:word; Destructor Done; end; To Initialize the object, you will need an initialized WAD file directory and the thing name for the sprite/picture. Once initialized, you may call Draw to display the thing on any part of the screen at any scale. The scale factor used is percentage based (100 = 1:1). NOTE: this unit requires you to initialize the GRAPH unit in Borland or Turbo Pascal. I had orginally written the screen output routines to directly write to screen memory, however, I switched over to the graph unit's PutPixel, that way I could maintain compatability with SVGA screen modes. If you would like to use this routine in a High Speed application, feel free to remove the graph unit oriented code. Sample Code: uses Graph,Wad,Things; var WDir:PWadDirectory; T:PWadThing; gd,gm:integer; begin {Initalize WAD directory} WDir:=New(PWadDirectory, Init('C:\DOOM\DOOM.WAD')); {Initalize WadThing (an imp)} T:=New(PWadThing, Init(WDir, 'TROOA1 ')); gd:=InstallUserDriver('BGI256',Nil); {640x480x256} gm:=2; {Initialize Graphics} InitGraph(gd,gm,''); {Set the Play Palette} WDir^.SetWadPalette(0); {Draw the thing 2:1 scale at coords 5,5} T^.Draw(200,5,5); {Wait for the ENTER key} readln; {Restore the palette} WDir^.RestorePalette; {Shut down graphics} CloseGraph; {Clean up the heap} Dispose(T, Done); Dispose(WDir, Done); end. I think the the Height and Width functions should be pretty much self explanatory! ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ Unit: WALLS.PAS Purpose: loading and displaying Wall Textures Cached: Yes This unit will load and display a Wall texture out of the wad file. This is the only unit in this tool kit that I am still not real happy with. It still requires anywhere from 2-4 seconds to load complex textures. The code that I have written has the living daylights cached out of it, but I still can't get it to fly. If you have any ideas, let me know. NOTE: It may look as though I am going through a lot of unecessary crap in this routine, but it's not. The reason that I dispose of the texture cache and reload it is to avoid a "gap" in the heap. The routine might be a touch faster if I didn't do this, but the memory managment would REALLY hoot! The following is a listing of the TWallPatch object declaration: type PWallTexture=^TWallTexture; TWallTexture=object Name :objnamestr; Patches :word; Image :^BA; Width :word; Height :word; Constructor Init(WDir:PWadDirectory;TextName:ObjNameStr); Procedure Draw(Scale,XOfs,YOfs:integer); Destructor Done; end; To Initialize the object, you will need an initialized WAD file directory and the texture name for the Wall. Once initialized, you may call Draw to display the texture on any part of the screen at any scale. The scale factor used is percentage based (100 = 1:1). NOTE: this unit requires you to initialize the GRAPH unit in Borland or Turbo Pascal. I had orginally written the screen output routines to directly write to screen memory, however, I switched over to the graph unit's PutPixel, that way I could maintain compatability with SVGA screen modes. If you would like to use this routine in a High Speed application, feel free to remove the graph unit oriented code. Sample Code: uses Graph,Wad,Walls; var WDir:PWadDirectory; WT:PWallTexture; gd,gm:integer; begin {Initalize WAD directory} WDir:=New(PWadDirectory, Init('C:\DOOM\DOOM.WAD')); {Initalize Floor Texture} WT:=New(PWallTexture, Init(WDir, 'AASTINKY')); gd:=InstallUserDriver('BGI256',Nil); {640x480x256} gm:=2; {Initialize Graphics} InitGraph(gd,gm,''); {Set the Play Palette} WDir^.SetWadPalette(0); {Draw the texture 2:1 scale at coords 5,5} WT^.Draw(200,5,5); {Wait for the ENTER key} readln; {Restore the palette} WDir^.RestorePalette; {Shut down graphics} CloseGraph; {Clean up the heap} Dispose(WT, Done); Dispose(WDir, Done); end. ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ Unit: SOUNDS.PAS Purpose: Declarations Unit Cached: Yes Type PSoundDef=^TSoundDef; TSoundDef=object Constructor Init(WDir:PWadDirectory;SndName:ObjNameStr); Procedure PlaySound; Funciton IsComplete:boolean; Procedure EndSound; Destructor Done; Private SoundBuff:PSoundBuff; IsPlaying:Boolean; sbBuff:word; end; This units will play any sound (NOT SONG) from the WAD file. The sounds unit will work under protected mode!! This may not sound all that impressive to those of you who have never tried to access the DMA whe in protected mode, but its a little bit tricky! When the conditional symbol DPMI in Borland Pascal is defined (automatic when you compile for a Protected Mode Destination Platform) the unit automatically includes the unit DPMI. DPMI has routines for allocating memory in low, DOS memory. This makes DMA transfer a whole lot simpler. To intialize a sound, you will need an open Wad File Directory and the Sound name. Next, a call to PlaySound will play the sound and a call to EndSound terminates it. The Boolean function IsComplete will indicate true when the sound blaster card signals that it has completed the DMA transfer. Sample Code: uses Wad,Sounds; var WDir:PWadDirectory; S:PSoundDef; begin {Initialize the WAD directory} WDir:=New(PWadDirectory, Init('C:\DOOM\DOOM.WAD')); {Intialize the Sound Player} S:=New(PSoundDef, Init(WDir, 'DSSLOP ')); {Play the sound} S^.PlaySound; {Wait for completion} while not S^.IsComplete do; {Terminate Sound} S^.EndSound; {Clean Up Heap} Dispose(S, Done); Dispose(WDir, Done); end. ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ Unit: DOOMGUI.PAS, DOOMGUI.PAS, DOOMOBJ.PAS Purpose: Map Viewer Graphical User Interface Units This unit is used by MAPREAD (the map viewing unit for DFE) Since this unit is so far from complete and optimized, I will not go into a detailed description of it. At this point, the only relevant Object in the DOOMGUI unit is the TGraphGroup object (a descendant of the TGraphView object). The acutal map viewing object TMapViewer is of type TGraphGroup and the Map routines are of type TGraphView. ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ Unit: MAPS.PAS Purpose: Map Loading/drawing routines This object is by far the most complex of all the objects. It loads and displays a map and any offset and scale. It also places selected objects at their locations. The map viewer is currently designed to display the map to a 640x480 screen. Even though you can display it in any screen mode, the scaling parameters will be off. type PWadMap=^TWadMap; TWadMap=Object(TGraphView) LevelEntries:PLevelEntries; ThingList :PThingList; VertexList :PVertexList; LineDefList :PLineDefList; ViewerMask :word; ThingMask :word; ScaleInc,XOffset,YOffset:word; Constructor Init(WDir:PWadDirectory;LevelName:ObjNameStr); Procedure Draw; virtual; Procedure SetScale(NewScaleInc,NewXOffset,NewYOffset:word); Function GetThingInArea(x1,y1,x2,y2:word):word; Destructor Done; virtual; end; To initialize this object you will need an initialized WAD directory and the level name (eg E1M1). Once the Map is initialized, you should set the ViewerMask and ThingMask variables to indicate which objects you want the map viewer to diaplay. The format for these masks are as follows: Viewer Mask Bits: 15-8 7 6 5 4 3 2 1 0 | | | | | | | | | | | | | | | | | `--> Display Monsters | | | | | | | `----> Display Goodies (Bonuses & collectables) | | | | | | `------> Display Weapons | | | | | `--------> Display Objects on Skills 1&2 \ | | | | `----------> Display Objects on Skill 3 > Can be set together | | | `------------> Display Objects on Skills 4&5 / | | `--------------> Display Objects Only Avail in Multi Player Games | `----------------> Display Secret Linedefs as White `---------------------> Unused at this time Thing Mask Word: This word contains a thing ID number to display on map. The thing ID #'s are defined by DOOM. See the file DMSPEC13.TXT for a listing of these numbers. When the Thing Mask is set, it will only display Objects that have the same thing ID # as the thing mask. The next step is to set the scale and offset factors with a call to SetScale. the ScaleInc value is ratio based (1 = 1:1, 2= 2:2, etc). The scale factor is actually based on a full screen view, scale factor 1 is a full screen map. The offset factors are only useful when you have "ZOOMED IN" on the map, they allow you to move the map around on the screen. NOTE: See MAPREAD.PAS for a full example of creating a MapViewer. Now we are ready to draw the map. Simply call draw. NOTE: this unit requires you to initialize the GRAPH unit in Borland or Turbo Pascal. I had orginally written the screen output routines to directly write to screen memory, however, I switched over to the graph unit's PutPixel, that way I could maintain compatability with SVGA screen modes. If you would like to use this routine in a High Speed application, feel free to remove the graph unit oriented code. Sample Code: uses Graph,Wad,Maps; var WDir:PWadDirectory; WMap:PWadMap; gd,gm:integer; begin {Initalize WAD directory} WDir:=New(PWadDirectory, Init('C:\DOOM\DOOM.WAD')); {Initalize the map} WMap:=New(PWadMap, Init(WDir, 'E2M7')); gd:=InstallUserDriver('BGI256',Nil); {640x480x256} gm:=2; {Initialize Graphics} InitGraph(gd,gm,''); {Set the Play Palette} WDir^.SetWadPalette(0); {Viewer Mask = Monsters + Skill Level 3} WMap^.ViewerMask:=9; {Thing Mask = 0 (Display all)} WMap^.ThingMask:=0; {Set Scale factor 1 (Full Screen) and 0 the offsets} WMap^.SetScale(1,0,0); {Draw the Map); WMap^.Draw(200,5,5); {Wait for the ENTER key} readln; {Restore the palette} WDir^.RestorePalette; {Shut down graphics} CloseGraph; {Clean up the heap} Dispose(WMap, Done); Dispose(WDir, Done); end. The Method GetThingInArea can be used to determine if a given area of a 640x 480 screen contains an object. If the given are does contian an object, it will return the Sprite ID number. ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ Unit: MAPREAD.PAS Purpose: Map Viewer unit for DFE This unit is part of DFE, therefor it is not documented. ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ Unit: SPRITEVI.PAS Purpose: Sprite Viewer unit for DFE This unit is part of DFE, therefor it is not documented. ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ Unit: SWAP.PAS Purpose: Memory Swapping Routines This unit is part of DFE, therefor it is not documented. Note: This is a hacked routine from a source listing found in Turbo Pascal Internals by Michael Tischer ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ Unit: SWAPA.ASM Purpose: Assembly part of Swapping Routines This unit is part of DFE, therefor it is not documented. Note: This is a hacked routine from a source listing found in Turbo Pascal Internals by Michael Tischer ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ Unit: EMS.PAS Purpose: EMS Accessing Part of SWAP This unit is part of DFE, therefor it is not documented. Note: This is a hacked routine from a source listing found in Turbo Pascal Internals by Michael Tischer ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ Unit: SWAPA.ASM Purpose: Assembly part of Swapping Routines This unit is part of DFE, therefor it is not documented. Note: This is a hacked routine from a source listing found in Turbo Pascal Internals by Michael Tischer ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ Program: DFE.PAS Purpose: DOOM Front End Main Program This is the Pascal Source Code for the DOOM Front End (written in mostly Turbo Vision). If you compile DFE, please follow the following steps: 1. Select the Options Menu from Borland/Turbo Pascal 2. Select the Compiler Option 3. Enter the letters DFE in the Conditional Defines area 4. Select the Compile Menu 5. Select Build 6. It should now be ready to go! ------------------------------------------------------------------------------