#include #include #include #include #include #include "doomtype.h" #include "doomstat.h" #include "info.h" #include "d_dehack.h" #include "s_sound.h" #include "d_items.h" #include "g_level.h" #include "m_cheat.h" #include "cmdlib.h" #include "dstrings.h" #include "m_alloc.h" #include "m_misc.h" #include "w_wad.h" // Miscellaneous info that used to be constant struct DehInfo deh = { 100, // .StartHealth 50, // .StartBullets 100, // .MaxHealth 200, // .MaxArmor 1, // .GreenAC 2, // .BlueAC 200, // .MaxSoulsphere 100, // .SoulsphereHealth 200, // .MegasphereHealth 100, // .GodHealth 200, // .FAArmor 2, // .FAAC 200, // .KFAArmor 2, // .KFAAC 40, // .BFGCells 0, // .Infight }; // These are the original heights of every Doom 2 thing. They are used if a patch // specifies that a thing should be hanging from the ceiling but doesn't specify // a height for the thing, since these are the heights it probably wants. static byte OrgHeights[] = { 56, 56, 56, 56, 16, 56, 8, 16, 64, 8, 56, 56, 56, 56, 56, 64, 8, 64, 56, 100, 64, 110, 56, 56, 72, 16, 32, 32, 32, 16, 42, 8, 8, 8, 8, 8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 68, 84, 84, 68, 52, 84, 68, 52, 52, 68, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 88, 88, 64, 64, 64, 64, 16, 16, 16 }; #define LINESIZE 2048 #define CHECKKEY(a,b) if (!stricmp (Line1, (a))) (b) = atoi(Line2); static char *PatchFile, *PatchPt; static char *Line1, *Line2; static int dversion, pversion; static BOOL including, includenotext; static const char *unknown_str = "Unknown key %s encountered in %s %d.\n"; // This is an offset to be used for computing the text stuff. // Straight from the DeHackEd source which was // Written by Greg Lewis, gregl@umich.edu. static int toff[] = {129044, 129044, 129044, 129284, 129380}; // A conversion array to convert from the 448 code pointers to the 966 // Frames that exist. // Again taken from the DeHackEd source. static short codepconv[448] = { 1, 2, 3, 4, 6, 9, 10, 11, 12, 14, 16, 17, 18, 19, 20, 22, 29, 30, 31, 32, 33, 34, 36, 38, 39, 41, 43, 44, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 119, 127, 157, 159, 160, 166, 167, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 188, 190, 191, 195, 196, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 221, 223, 224, 228, 229, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 270, 272, 273, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 316, 317, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 344, 347, 348, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 387, 389, 390, 397, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 421, 423, 424, 430, 431, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 456, 458, 460, 463, 465, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 489, 491, 493, 502, 503, 504, 505, 506, 508, 511, 514, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 541, 543, 545, 548, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 570, 572, 574, 585, 586, 587, 588, 589, 590, 594, 596, 598, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 620, 621, 622, 631, 632, 633, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 650, 652, 653, 654, 659, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689, 690, 692, 696, 700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, 713, 715, 718, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, 741, 743, 745, 746, 750, 751, 766, 774, 777, 779, 780, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 801, 809, 811}; BOOL BackedUpData = false; // This is the original data before it gets replaced by a patch. //char *OrgSfxNames[NUMSFX]; char *OrgSprNames[NUMSPRITES]; actionf_t OrgActionPtrs[NUMSTATES]; // Sound equivalences. When a patch tries to change a sound, // use these sound names. char *SoundMap[] = { NULL, "weapons/pistol", "weapons/shotgf", "weapons/shotgr", "weapons/sshotf", "weapons/sshoto", "weapons/sshotc", "weapons/sshotl", "weapons/plasmaf", "weapons/bfgf", "weapons/sawup", "weapons/sawidle", "weapons/sawfull", "weapons/sawhit", "weapons/rocklf", "weapons/bfgx", "imp/attack", "imp/shotx", "plats/pt1_strt", "plats/pt1_stop", "doors/dr1_open", "doors/dr1_clos", "plats/pt1_mid", "switches/normbutn", "switches/exitbutn", "*pain100_1", "demon/pain", "grunt/pain", "vile/pain", "fatso/pain", "pain/pain", "misc/gibbed", "misc/i_pkup", "misc/w_pkup", "*land1", "misc/teleport", "grunt/sight1", "grunt/sight2", "grunt/sight3", "imp/sight1", "imp/sight2", "demon/sight", "caco/sight", "baron/sight", "cyber/sight", "spider/sight", "baby/sight", "knight/sight", "vile/sight", "fatso/sight", "pain/sight", "skull/melee", "demon/melee", "skeleton/melee", "vile/start", "imp/melee", "skeleton/swing", "*death1", "*xdeath1", "grunt/death1", "grunt/death2", "grunt/death3", "imp/death1", "imp/death2", "demon/death", "caco/death", "misc/unused", "baron/death", "cyber/death", "spider/death", "baby/death", "vile/death", "knight/death", "pain/death", "skeleton/death", "grunt/active", "imp/active", "demon/active", "baby/active", "baby/walk", "vile/active", "*grunt1", "world/barrelx", "*fist", "cyber/hoof", "spider/walk", "weapons/chngun", "misc/chat2", "doors/dr2_open", "doors/dr2_clos", "misc/spawn", "vile/firecrkl", "vile/firestrt", "misc/p_pkup", "brain/spit", "brain/cube", "brain/sight", "brain/pain", "brain/death", "fatso/attack", "gatso/death", "wolfss/sight", "wolfss/death", "keen/pain", "keen/death", "skeleton/active", "skeleton/sight", "skeleton/attack", "misc/chat" }; // Functions used in a .bex [CODEPTR] chunk void A_Light0(); void A_WeaponReady(); void A_Lower(); void A_Raise(); void A_Punch(); void A_ReFire(); void A_FirePistol(); void A_Light1(); void A_FireShotgun(); void A_Light2(); void A_FireShotgun2(); void A_CheckReload(); void A_OpenShotgun2(); void A_LoadShotgun2(); void A_CloseShotgun2(); void A_FireCGun(); void A_GunFlash(); void A_FireMissile(); void A_Saw(); void A_FirePlasma(); void A_BFGsound(); void A_FireBFG(); void A_BFGSpray(); void A_Explode(); void A_Pain(); void A_PlayerScream(); void A_Fall(); void A_XScream(); void A_Look(); void A_Chase(); void A_FaceTarget(); void A_PosAttack(); void A_Scream(); void A_SPosAttack(); void A_VileChase(); void A_VileStart(); void A_VileTarget(); void A_VileAttack(); void A_StartFire(); void A_Fire(); void A_FireCrackle(); void A_Tracer(); void A_SkelWhoosh(); void A_SkelFist(); void A_SkelMissile(); void A_FatRaise(); void A_FatAttack1(); void A_FatAttack2(); void A_FatAttack3(); void A_BossDeath(); void A_CPosAttack(); void A_CPosRefire(); void A_TroopAttack(); void A_SargAttack(); void A_HeadAttack(); void A_BruisAttack(); void A_SkullAttack(); void A_Metal(); void A_SpidRefire(); void A_BabyMetal(); void A_BspiAttack(); void A_Hoof(); void A_CyberAttack(); void A_PainAttack(); void A_PainDie(); void A_KeenDie(); void A_BrainPain(); void A_BrainScream(); void A_BrainDie(); void A_BrainAwake(); void A_BrainSpit(); void A_SpawnSound(); void A_SpawnFly(); void A_BrainExplode(); struct CodePtr { char *name; actionf_t func; }; static const struct CodePtr CodePtrs[] = { { "NULL", {(actionf_p1)NULL}, }, { "Light0", {(actionf_p1)A_Light0}, }, { "WeaponReady", {(actionf_p1)A_WeaponReady}, }, { "Lower", {(actionf_p1)A_Lower}, }, { "Raise", {(actionf_p1)A_Raise}, }, { "Punch", {(actionf_p1)A_Punch}, }, { "ReFire", {(actionf_p1)A_ReFire}, }, { "FirePistol", {(actionf_p1)A_FirePistol}, }, { "Light1", {(actionf_p1)A_Light1}, }, { "FireShotgun", {(actionf_p1)A_FireShotgun}, }, { "Light2", {(actionf_p1)A_Light2}, }, { "FireShotgun2", {(actionf_p1)A_FireShotgun2}, }, { "CheckReload", {(actionf_p1)A_CheckReload}, }, { "OpenShotgun2", {(actionf_p1)A_OpenShotgun2}, }, { "LoadShotgun2", {(actionf_p1)A_LoadShotgun2}, }, { "CloseShotgun2", {(actionf_p1)A_CloseShotgun2}, }, { "FireCGun", {(actionf_p1)A_FireCGun}, }, { "A_GunFlash", {(actionf_p1)A_GunFlash}, }, { "FireMissile", {(actionf_p1)A_FireMissile}, }, { "Saw", {(actionf_p1)A_Saw}, }, { "FirePlasma", {(actionf_p1)A_FirePlasma}, }, { "BFGsound", {(actionf_p1)A_BFGsound}, }, { "FireBFG", {(actionf_p1)A_FireBFG}, }, { "BFGSpray", {(actionf_p1)A_BFGSpray}, }, { "Explode", {(actionf_p1)A_Explode}, }, { "Pain", {(actionf_p1)A_Pain}, }, { "PlayerScream", {(actionf_p1)A_PlayerScream}, }, { "Fall", {(actionf_p1)A_Fall}, }, { "XScream", {(actionf_p1)A_XScream}, }, { "Look", {(actionf_p1)A_Look}, }, { "Chase", {(actionf_p1)A_Chase}, }, { "FaceTarget", {(actionf_p1)A_FaceTarget}, }, { "PosAttack", {(actionf_p1)A_PosAttack}, }, { "Scream", {(actionf_p1)A_Scream}, }, { "SPosAttack", {(actionf_p1)A_SPosAttack}, }, { "VileChase", {(actionf_p1)A_VileChase}, }, { "VileStart", {(actionf_p1)A_VileStart}, }, { "VileTarget", {(actionf_p1)A_VileTarget}, }, { "VileAttack", {(actionf_p1)A_VileAttack}, }, { "StartFire", {(actionf_p1)A_StartFire}, }, { "Fire", {(actionf_p1)A_Fire}, }, { "FireCrackle", {(actionf_p1)A_FireCrackle}, }, { "Tracer", {(actionf_p1)A_Tracer}, }, { "SkelWhoosh", {(actionf_p1)A_SkelWhoosh}, }, { "SkelFist", {(actionf_p1)A_SkelFist}, }, { "SkelMissile", {(actionf_p1)A_SkelMissile}, }, { "FatRaise", {(actionf_p1)A_FatRaise}, }, { "FatAttack1", {(actionf_p1)A_FatAttack1}, }, { "FatAttack2", {(actionf_p1)A_FatAttack2}, }, { "FatAttack3", {(actionf_p1)A_FatAttack3}, }, { "BossDeath", {(actionf_p1)A_BossDeath}, }, { "CPosAttack", {(actionf_p1)A_CPosAttack}, }, { "CPosRefire", {(actionf_p1)A_CPosRefire}, }, { "TroopAttack", {(actionf_p1)A_TroopAttack}, }, { "SargAttack", {(actionf_p1)A_SargAttack}, }, { "HeadAttack", {(actionf_p1)A_HeadAttack}, }, { "BruisAttack", {(actionf_p1)A_BruisAttack}, }, { "SkullAttack", {(actionf_p1)A_SkullAttack}, }, { "Metal", {(actionf_p1)A_Metal}, }, { "SpidRefire", {(actionf_p1)A_SpidRefire}, }, { "BabyMetal", {(actionf_p1)A_BabyMetal}, }, { "BspiAttack", {(actionf_p1)A_BspiAttack}, }, { "Hoof", {(actionf_p1)A_Hoof}, }, { "CyberAttack", {(actionf_p1)A_CyberAttack}, }, { "PainAttack", {(actionf_p1)A_PainAttack}, }, { "PainDie", {(actionf_p1)A_PainDie}, }, { "KeenDie", {(actionf_p1)A_KeenDie}, }, { "BrainPain", {(actionf_p1)A_BrainPain}, }, { "BrainScream", {(actionf_p1)A_BrainScream}, }, { "BrainDie", {(actionf_p1)A_BrainDie}, }, { "BrainAwake", {(actionf_p1)A_BrainAwake}, }, { "BrainSpit", {(actionf_p1)A_BrainSpit}, }, { "SpawnSound", {(actionf_p1)A_SpawnSound}, }, { "SpawnFly", {(actionf_p1)A_SpawnFly}, }, { "BrainExplode", {(actionf_p1)A_BrainExplode}, }, { NULL, } }; struct Key { char *name; ptrdiff_t offset; }; extern byte cheat_mus_seq[9]; extern byte cheat_choppers_seq[11]; extern byte cheat_god_seq[6]; extern byte cheat_ammo_seq[6]; extern byte cheat_ammonokey_seq[5]; extern byte cheat_noclip_seq[11]; extern byte cheat_commercial_noclip_seq[7]; extern byte cheat_powerup_seq[7][10]; extern byte cheat_clev_seq[10]; extern byte cheat_mypos_seq[8]; extern byte cheat_amap_seq[5]; static int PatchThing (int); static int PatchSound (int); static int PatchFrame (int); static int PatchSprite (int); static int PatchAmmo (int); static int PatchWeapon (int); static int PatchPointer (int); static int PatchCheats (int); static int PatchMisc (int); static int PatchText (int); static int PatchStrings (int); static int PatchPars (int); static int PatchCodePtrs (int); static int DoInclude (int); static const struct { char *name; int (*func)(int); } Modes[] = { // These appear in .deh and .bex files { "Thing", PatchThing }, { "Sound", PatchSound }, { "Frame", PatchFrame }, { "Sprite", PatchSprite }, { "Ammo", PatchAmmo }, { "Weapon", PatchWeapon }, { "Pointer", PatchPointer }, { "Cheat", PatchCheats }, { "Misc", PatchMisc }, { "Text", PatchText }, // These appear in .bex files { "include", DoInclude }, { "[STRINGS]", PatchStrings }, { "[PARS]", PatchPars }, { "[CODEPTR]", PatchCodePtrs }, { NULL, }, }; static int HandleMode (const char *mode, int num); static BOOL HandleKey (const struct Key *keys, void *structure, const char *key, int value); static void BackupData (void); static void ChangeCheat (byte *newcheat, byte *cheatseq, BOOL needsval); static BOOL ReadChars (char **stuff, int size); static char *igets (void); static int GetLine (void); static int HandleMode (const char *mode, int num) { int i = 0; while (Modes[i].name && stricmp (Modes[i].name, mode)) i++; if (Modes[i].name) return Modes[i].func (num); // Handle unknown or unimplemented data Printf ("Unknown chunk %s encountered. Skipping.\n", mode); do i = GetLine (); while (i == 1); return i; } static BOOL HandleKey (const struct Key *keys, void *structure, const char *key, int value) { while (keys->name && stricmp (keys->name, key)) keys++; if (keys->name) { *((int *)(((byte *)structure) + keys->offset)) = value; return false; } return true; } static void BackUpData (void) { int i; if (BackedUpData) return; // for (i = 0; i < NUMSFX; i++) // OrgSfxNames[i] = S_sfx[i].name; for (i = 0; i < NUMSPRITES; i++) OrgSprNames[i] = sprnames[i]; for (i = 0; i < NUMSTATES; i++) OrgActionPtrs[i] = states[i].action; } static void ChangeCheat (byte *newcheat, byte *cheatseq, BOOL needsval) { while (*cheatseq != 0xff && *cheatseq != 1 && *newcheat) { *cheatseq++ = SCRAMBLE(*newcheat); newcheat++; } if (needsval) { *cheatseq++ = 1; *cheatseq++ = 0; *cheatseq++ = 0; } *cheatseq = 0xff; } static BOOL ReadChars (char **stuff, int size) { char *str = *stuff; if (!size) { *str = 0; return true; } do { // Ignore carriage returns if (*PatchPt != '\r') *str++ = *PatchPt; else size++; PatchPt++; } while (--size); *str = 0; return true; } static void ReplaceSpecialChars (char *str) { char *p = str, c; int i; while (c = *p++) { if (c != '\\') { *str++ = c; } else { switch (*p) { case 'n': case 'N': *str++ = '\n'; break; case 't': case 'T': *str++ = '\t'; break; case 'r': case 'R': *str++ = '\r'; break; case 'x': case 'X': c = 0; p++; for (i = 0; i < 2; i++) { c <<= 4; if (*p >= '0' && *p <= '9') c += *p-'0'; else if (*p >= 'a' && *p <= 'f') c += 10 + *p-'a'; else if (*p >= 'A' && *p <= 'F') c += 10 + *p-'A'; else break; p++; } *str++ = c; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': c = 0; for (i = 0; i < 3; i++) { c <<= 3; if (*p >= '0' && *p <= '7') c += *p-'0'; else break; p++; } *str++ = c; break; default: *str++ = *p; break; } p++; } } *str = 0; } static char *skipwhite (char *str) { if (str) while (*str && isspace(*str)) *str++; return str; } static void stripwhite (char *str) { char *end = str + strlen(str) - 1; while (end >= str && isspace(*end)) end--; end[1] = '\0'; } static char *igets (void) { char *line; if (*PatchPt == '\0') return NULL; line = PatchPt; while (*PatchPt != '\n' && *PatchPt != '\0') PatchPt++; if (*PatchPt == '\n') *PatchPt++ = 0; return line; } static int GetLine (void) { char *line, *line2; do { while ( (line = igets ()) ) if (line[0] != '#') // Skip comment lines break; if (!line) return 0; Line1 = skipwhite (line); } while (Line1 && *Line1 == 0); // Loop until we get a line with // more than just whitespace. line = strchr (Line1, '='); if (line) { // We have an '=' in the input line line2 = line; while (--line2 >= Line1) if (*line2 > ' ') break; if (line2 < Line1) return 0; // Nothing before '=' *(line2 + 1) = 0; line++; while (*line && *line <= ' ') line++; if (*line == 0) return 0; // Nothing after '=' Line2 = line; return 1; } else { // No '=' in input line line = Line1 + 1; while (*line > ' ') line++; // Get beyond first word *line++ = 0; while (*line && *line <= ' ') line++; // Skip white space //.bex files don't have this restriction //if (*line == 0) // return 0; // No second word Line2 = line; return 2; } } static int PatchThing (int thingNum) { static const struct Key keys[] = { { "ID #", myoffsetof(mobjinfo_t,doomednum) }, { "Initial frame", myoffsetof(mobjinfo_t,spawnstate) }, { "Hit points", myoffsetof(mobjinfo_t,spawnhealth) }, { "First moving frame", myoffsetof(mobjinfo_t,seestate) }, { "Reaction time", myoffsetof(mobjinfo_t,reactiontime) }, { "Injury frame", myoffsetof(mobjinfo_t,painstate) }, { "Pain chance", myoffsetof(mobjinfo_t,painchance) }, { "Close attack frame", myoffsetof(mobjinfo_t,meleestate) }, { "Far attack frame", myoffsetof(mobjinfo_t,missilestate) }, { "Death frame", myoffsetof(mobjinfo_t,deathstate) }, { "Exploding frame", myoffsetof(mobjinfo_t,xdeathstate) }, { "Speed", myoffsetof(mobjinfo_t,speed) }, { "Width", myoffsetof(mobjinfo_t,radius) }, { "Height", myoffsetof(mobjinfo_t,height) }, { "Mass", myoffsetof(mobjinfo_t,mass) }, { "Missile damage", myoffsetof(mobjinfo_t,damage) }, { "Bits", myoffsetof(mobjinfo_t,flags) }, { "Respawn frame", myoffsetof(mobjinfo_t,raisestate) }, { NULL, } }; // a .bex extension: static const char *bitnames[32] = { "SPECIAL", "SOLID", "SHOOTABLE", "NOSECTOR", "NOBLOCKMAP", "AMBUSH", "JUSTHIT", "JUSTATTACKED", "SPAWNCEILING", "NOGRAVITY", "DROPOFF", "PICKUP", "NOCLIP", "SLIDE", "FLOAT", "TELEPORT", "MISSILE", "DROPPED", "SHADOW", "NOBLOOD", "CORPSE", "INFLOAT", "COUNTKILL", "COUNTITEM", "SKULLFLY", "NOTDMATCH", "TRANSLATION1", "TRANSLATION2", "STEALTH", "TRANSLUC25", "TRANSLUCENT", // (aka TRANSLUC50) BOOM actually has this as the last bit "UNUSED1" }; int result; mobjinfo_t *info, dummy; BOOL hadHeight = false; thingNum--; if (thingNum >= 0 && thingNum < NUMMOBJTYPES) { info = &mobjinfo[thingNum]; DPrintf ("Thing %d\n", thingNum); } else { info = &dummy; Printf ("Thing %d out of range.\n", thingNum + 1); } // Turn off transparency for the mobj, since original DOOM // didn't have any. If some is wanted, let the patch specify it. // Uncomment this if you want to make it so. //info->flags &= ~MF_TRANSLUCBITS; while ((result = GetLine ()) == 1) { int sndmap = atoi (Line2); if (sndmap >= sizeof(SoundMap)) sndmap = 0; if (HandleKey (keys, info, Line1, atoi (Line2))) { if (!stricmp (Line1, "Alert sound")) info->seesound = SoundMap[sndmap]; else if (!stricmp (Line1, "Attack sound")) info->attacksound = SoundMap[sndmap]; else if (!stricmp (Line1, "Pain sound")) info->painsound = SoundMap[sndmap]; else if (!stricmp (Line1, "Death sound")) info->deathsound = SoundMap[sndmap]; else if (!stricmp (Line1, "Action sound")) info->activesound = SoundMap[sndmap]; else Printf (unknown_str, Line1, "Thing", thingNum); } else if (!stricmp (Line1, "Height")) { hadHeight = true; } else if (!stricmp (Line1, "Bits")) { if (!IsNum (Line2)) { char *data = COM_Parse (Line2); while (data) { int i = 0; for (i = 0; i < 32; i++) if (!stricmp (bitnames[i], com_token)) break; if (i == 32) Printf ("Unknown bit %s\n", com_token); else info->flags += 1 << i; data = COM_Parse (data); } DPrintf ("Flags: %d\n", info->flags); } } } if (info->flags & MF_SPAWNCEILING && !hadHeight && thingNum < sizeof(OrgHeights)) info->height = OrgHeights[thingNum] * FRACUNIT; return result; } static int PatchSound (int soundNum) { int result; DPrintf ("Sound %d (no longer supported)\n", soundNum); /* sfxinfo_t *info, dummy; int offset = 0; if (soundNum >= 1 && soundNum <= NUMSFX) { info = &S_sfx[soundNum]; } else { info = &dummy; Printf ("Sound %d out of range.\n"); } */ while ((result = GetLine ()) == 1) { /* if (!stricmp ("Offset", Line1)) offset = atoi (Line2); else CHECKKEY ("Zero/One", info->singularity) else CHECKKEY ("Value", info->priority) else CHECKKEY ("Zero 1", info->link) else CHECKKEY ("Neg. One 1", info->pitch) else CHECKKEY ("Neg. One 2", info->volume) else CHECKKEY ("Zero 2", info->data) else CHECKKEY ("Zero 3", info->usefulness) else CHECKKEY ("Zero 4", info->lumpnum) else Printf (unknown_str, Line1, "Sound", soundNum); */ } /* if (offset) { // Calculate offset from start of sound names offset -= toff[dversion] + 21076; if (offset <= 64) // pistol .. bfg offset >>= 3; else if (offset <= 260) // sawup .. oof offset = (offset + 4) >> 3; else // telept .. skeatk offset = (offset + 8) >> 3; if (offset >= 0 && offset < NUMSFX) { S_sfx[soundNum].name = OrgSfxNames[offset + 1]; } else { Printf ("Sound name %d out of range.\n", offset + 1); } } */ return result; } static int PatchFrame (int frameNum) { static const struct Key keys[] = { { "Sprite number", myoffsetof(state_t,sprite) }, { "Sprite subnumber", myoffsetof(state_t,frame) }, { "Duration", myoffsetof(state_t,tics) }, { "Next frame", myoffsetof(state_t,nextstate) }, { "Unknown 1", myoffsetof(state_t,misc1) }, { "Unknown 2", myoffsetof(state_t,misc2) }, { NULL, } }; int result; state_t *info, dummy; if (frameNum >= 0 && frameNum < NUMSTATES) { info = &states[frameNum]; DPrintf ("Frame %d\n", frameNum); } else { info = &dummy; Printf ("Frame %d out of range\n", frameNum); } while ((result = GetLine ()) == 1) if (HandleKey (keys, info, Line1, atoi (Line2))) Printf (unknown_str, Line1, "Frame", frameNum); return result; } static int PatchSprite (int sprNum) { int result; int offset = 0; if (sprNum >= 0 && sprNum < NUMSPRITES) { DPrintf ("Sprite %d\n", sprNum); } else { Printf ("Sprite %d out of range.\n", sprNum); sprNum = -1; } while ((result = GetLine ()) == 1) { if (!stricmp ("Offset", Line1)) offset = atoi (Line2); else Printf (unknown_str, Line1, "Sprite", sprNum); } if (offset > 0 && sprNum != -1) { // Calculate offset from beginning of sprite names. offset = (offset - toff[dversion] - 22044) / 8; if (offset >= 0 && offset < NUMSPRITES) { sprnames[sprNum] = OrgSprNames[offset]; } else { Printf ("Sprite name %d out of range.\n", offset); } } return result; } static int PatchAmmo (int ammoNum) { extern int clipammo[NUMAMMO]; int result; int *max; int *per; int dummy; if (ammoNum >= 0 && ammoNum < NUMAMMO) { DPrintf ("Ammo %d.\n", ammoNum); max = &maxammo[ammoNum]; per = &clipammo[ammoNum]; } else { Printf ("Ammo %d out of range.\n", ammoNum); max = per = &dummy; } while ((result = GetLine ()) == 1) { CHECKKEY ("Max ammo", *max) else CHECKKEY ("Per ammo", *per) else Printf (unknown_str, Line1, "Ammo", ammoNum); } return result; } static int PatchWeapon (int weapNum) { static const struct Key keys[] = { { "Ammo type", myoffsetof(weaponinfo_t,ammo) }, { "Deselect frame", myoffsetof(weaponinfo_t,upstate) }, { "Select frame", myoffsetof(weaponinfo_t,downstate) }, { "Bobbing frame", myoffsetof(weaponinfo_t,readystate) }, { "Shooting frame", myoffsetof(weaponinfo_t,atkstate) }, { "Firing frame", myoffsetof(weaponinfo_t,flashstate) }, { NULL, } }; int result; weaponinfo_t *info, dummy; if (weapNum >= 0 && weapNum < NUMWEAPONS) { info = &weaponinfo[weapNum]; DPrintf ("Weapon %d\n", weapNum); } else { info = &dummy; Printf ("Weapon %d out of range.\n", weapNum); } while ((result = GetLine ()) == 1) if (HandleKey (keys, info, Line1, atoi (Line2))) Printf (unknown_str, Line1, "Weapon", weapNum); return result; } static int PatchPointer (int ptrNum) { int result; if (ptrNum >= 0 && ptrNum < 448) { DPrintf ("Pointer %d\n", ptrNum); } else { Printf ("Pointer %d out of range.\n", ptrNum); ptrNum = -1; } while ((result = GetLine ()) == 1) { if ((ptrNum != -1) && (!stricmp (Line1, "Codep Frame"))) states[codepconv[ptrNum]].action = OrgActionPtrs[atoi (Line2)]; else Printf (unknown_str, Line1, "Pointer", ptrNum); } return result; } static int PatchCheats (int dummy) { static const struct { char *name; byte *cheatseq; BOOL needsval; } keys[] = { { "Change music", cheat_mus_seq, true }, { "Chainsaw", cheat_choppers_seq, false }, { "God mode", cheat_god_seq, false }, { "Ammo & Keys", cheat_ammo_seq, false }, { "Ammo", cheat_ammonokey_seq, false }, { "No Clipping 1", cheat_noclip_seq, false }, { "No Clipping 2", cheat_commercial_noclip_seq, false }, { "Invincibility", cheat_powerup_seq[0], false }, { "Berserk", cheat_powerup_seq[1], false }, { "Invisibility", cheat_powerup_seq[2], false }, { "Radiation Suit", cheat_powerup_seq[3], false }, { "Auto-map", cheat_powerup_seq[4], false }, { "Lite-Amp Goggles", cheat_powerup_seq[5], false }, { "BEHOLD menu", cheat_powerup_seq[6], false }, { "Level Warp", cheat_clev_seq, true }, { "Player Position", cheat_mypos_seq, false }, { "Map cheat", cheat_amap_seq, false }, { NULL, } }; int result; DPrintf ("Cheats\n"); while ((result = GetLine ()) == 1) { int i = 0; while (keys[i].name && stricmp (keys[i].name, Line1)) i++; if (!keys[i].name) Printf ("Unknown cheat %s.\n", Line2); else ChangeCheat (Line2, keys[i].cheatseq, keys[i].needsval); } return result; } static int PatchMisc (int dummy) { static const struct Key keys[] = { { "Initial Health", myoffsetof(struct DehInfo,StartHealth) }, { "Initial Bullets", myoffsetof(struct DehInfo,StartBullets) }, { "Max Health", myoffsetof(struct DehInfo,MaxHealth) }, { "Max Armor", myoffsetof(struct DehInfo,MaxArmor) }, { "Green Armor Class", myoffsetof(struct DehInfo,GreenAC) }, { "Blue Armor Class", myoffsetof(struct DehInfo,BlueAC) }, { "Max Soulsphere", myoffsetof(struct DehInfo,BlueAC) }, { "Soulsphere Health", myoffsetof(struct DehInfo,SoulsphereHealth) }, { "Megasphere Health", myoffsetof(struct DehInfo,MegasphereHealth) }, { "God Mode Health", myoffsetof(struct DehInfo,GodHealth) }, { "IDFA Armor", myoffsetof(struct DehInfo,FAArmor) }, { "IDFA Armor Class", myoffsetof(struct DehInfo,FAAC) }, { "IDKFA Armor", myoffsetof(struct DehInfo,KFAArmor) }, { "IDKFA Armor Class", myoffsetof(struct DehInfo,KFAAC) }, { "BFG Cells/Shot", myoffsetof(struct DehInfo,BFGCells) }, { "Monsters Infight", myoffsetof(struct DehInfo,Infight) }, { NULL, } }; int result; gitem_t *item; DPrintf ("Misc\n"); while ((result = GetLine()) == 1) if (HandleKey (keys, &deh, Line1, atoi (Line2))) Printf ("Unknown miscellaneous info %s.\n", Line2); if ( (item = FindItem ("Basic Armor")) ) item->offset = deh.GreenAC; if ( (item = FindItem ("Mega Armor")) ) item->offset = deh.BlueAC; // 0xDD == enable infighting deh.Infight = deh.Infight == 0xDD ? 1 : 0; return result; } static int PatchPars (int dummy) { char *space, mapname[8], *moredata; level_info_t *info; int result, par; DPrintf ("[Pars]\n"); while ( (result = GetLine()) ) { // Argh! .bex doesn't follow the same rules as .deh if (result == 1) { Printf ("Unknown key in [PARS] section: %s\n", Line1); continue; } if (stricmp ("par", Line1)) return result; space = strchr (Line2, ' '); if (!space) { Printf ("Need data after par.\n"); continue; } *space++ = '\0'; while (*space && isspace(*space)) space++; moredata = strchr (space, ' '); if (moredata) { // At least 3 items on this line, must be E?M? format sprintf (mapname, "E%cM%c", *Line2, *space); par = atoi (moredata + 1); } else { // Only 2 items, must be MAP?? format sprintf (mapname, "MAP%02d", atoi(Line2) % 100); par = atoi (space); } if (!(info = FindLevelInfo (mapname)) ) { Printf ("No map %s\n", mapname); continue; } info->partime = par; DPrintf ("Par for %s changed to %d\n", mapname, par); } return result; } static int PatchCodePtrs (int dummy) { int result; DPrintf ("[CodePtr]\n"); while ((result = GetLine()) == 1) { if (!strnicmp ("Frame", Line1, 5) && isspace(Line1[5])) { int frame = atoi (Line1 + 5); if (frame < 0 || frame >= NUMSTATES) { Printf ("Frame %d out of range\n", frame); } else { int i = 0; char *data; COM_Parse (Line2); if ((com_token[0] == 'A' || com_token[0] == 'a') && com_token[1] == '_') data = com_token + 2; else data = com_token; while (CodePtrs[i].name && stricmp (CodePtrs[i].name, Line2)) i++; if (CodePtrs[i].name) { states[frame].action.acp1 = CodePtrs[i].func.acp1; DPrintf ("Frame %d set to %s\n", frame, CodePtrs[i].name); } else { states[frame].action.acp1 = NULL; DPrintf ("Unknown code pointer: %s\n", Line2); } } } } return result; } static int PatchText (int oldSize) { int newSize; char *oldStr; char *newStr; char *temp; BOOL good; int result; int i; temp = COM_Parse (Line2); // Skip old size, since we already have it if (!COM_Parse (temp)) { Printf ("Text chunk is missing size of new string.\n"); return 2; } newSize = atoi (com_token); oldStr = malloc (oldSize + 1); newStr = malloc (newSize + 1); if (!oldStr || !newStr) { Printf ("Out of memory.\n"); goto donewithtext; } good = ReadChars (&oldStr, oldSize); good += ReadChars (&newStr, newSize); if (!good) { free (newStr); free (oldStr); Printf ("Unexpected end-of-file.\n"); return 0; } if (includenotext) { Printf ("Skipping text chunk in included patch.\n"); goto donewithtext; } DPrintf ("Searching for text:\n%s\n", oldStr); good = false; // Search through sfx names #if 0 for (i = 0; i < NUMSFX; i++) { if (S_sfx[i].name) { if (!strcmp (S_sfx[i].name, oldStr)) { S_sfx[i].name = copystring (newStr); good = true; // Yes, we really need to check them all. A sound patch could // have created two sounds that point to the same name, and // stopping at the first would miss the change to the second. } } } if (good) goto donewithtext; #endif // Search through sprite names for (i = 0; i < NUMSPRITES; i++) { if (!strcmp (sprnames[i], oldStr)) { sprnames[i] = copystring (newStr); good = true; // See above. } } if (good) goto donewithtext; // Search through music names. // This is something of an even bigger hack // since I changed the way music is handled. if (oldSize < 7) { // Music names are never >6 chars if ( (temp = malloc (oldSize + 3)) ) { level_info_t *info = LevelInfos; sprintf (temp, "d_%s", oldStr); while (info->level_name) { if (!strnicmp (info->music, temp, 8)) { good = true; strncpy (info->music, temp, 8); } info++; } free (temp); } } if (good) goto donewithtext; // Search through most other texts for (i = 0; i < NUMSTRINGS; i++) { if (!stricmp (Strings[i].builtin, oldStr)) { ReplaceString (&Strings[i].string, newStr); Strings[i].type = str_patched; good = true; break; } } if (!good) DPrintf (" (Unmatched)\n"); donewithtext: if (newStr) free (newStr); if (oldStr) free (oldStr); // Fetch next identifier for main loop while ((result = GetLine ()) == 1) ; return result; } static int PatchStrings (int dummy) { static int maxstrlen = 128; static char *holdstring; int result; DPrintf ("[Strings]\n"); if (!holdstring) holdstring = Malloc (maxstrlen); while ((result = GetLine()) == 1) { int i; *holdstring = '\0'; do { while (maxstrlen < strlen (holdstring) + strlen (Line2)) { maxstrlen += 128; holdstring = Realloc (holdstring, maxstrlen); } strcat (holdstring, skipwhite (Line2)); stripwhite (holdstring); if (holdstring[strlen(holdstring)-1] == '\\') { holdstring[strlen(holdstring)-1] = '\0'; Line2 = igets (); } else Line2 = NULL; } while (Line2 && *Line2); for (i = 0; i < NUMSTRINGS; i++) if (!stricmp (Strings[i].name, Line1)) break; if (i == NUMSTRINGS) { Printf ("Unknown string: %s\n", Line1); } else { ReplaceSpecialChars (holdstring); ReplaceString (&Strings[i].string, copystring (holdstring)); Strings[i].type = str_patched; Printf ("%s set to:\n%s\n", Line1, holdstring); } } return result; } static int DoInclude (int dummy) { char *data; int savedversion, savepversion; char *savepatchfile, *savepatchpt; if (including) { Printf ("Sorry, can't nest includes\n"); goto endinclude; } data = COM_Parse (Line2); if (!stricmp (com_token, "notext")) { includenotext = true; data = COM_Parse (data); } if (!com_token[0]) { includenotext = false; Printf ("Include directive is missing filename\n"); goto endinclude; } DPrintf ("Including %s\n", com_token); savepatchfile = PatchFile; savepatchpt = PatchPt; savedversion = dversion; savepversion = pversion; including = true; DoDehPatch (com_token, false); DPrintf ("Done with include\n"); PatchFile = savepatchfile; PatchPt = savepatchpt; dversion = savedversion; pversion = savepversion; endinclude: including = false; includenotext = false; return GetLine(); } void DoDehPatch (char *patchfile, BOOL autoloading) { char file[256]; int cont; int filelen; int lump; BackUpData (); PatchFile = NULL; lump = W_CheckNumForName ("DEHACKED"); if (lump >= 0 && autoloading) { // Execute the DEHACKED lump as a patch. filelen = W_LumpLength (lump); if ( (PatchFile = malloc (filelen + 1)) ) { W_ReadLump (lump, PatchFile); } else { Printf ("Not enough memory to apply patch\n"); return; } } else if (patchfile) { // Try to use patchfile as a patch. FILE *deh; strcpy (file, patchfile); FixPathSeperator (file); DefaultExtension (file, ".deh"); if ( !(deh = fopen (file, "rb")) ) { strcpy (file, patchfile); FixPathSeperator (file); DefaultExtension (file, ".bex"); deh = fopen (file, "rb"); } if (deh) { filelen = Q_filelength (deh); if ( (PatchFile = malloc (filelen + 1)) ) { fread (PatchFile, 1, filelen, deh); fclose (deh); } } if (!PatchFile) { // Couldn't find it on disk, try reading it from a lump strcpy (file, patchfile); FixPathSeperator (file); ExtractFileBase (file, file); file[8] = 0; lump = W_CheckNumForName (file); if (lump >= 0) { filelen = W_LumpLength (lump); if ( (PatchFile = malloc (filelen + 1)) ) { W_ReadLump (lump, PatchFile); } else { Printf ("Not enough memory to apply patch\n"); return; } } } if (!PatchFile) { Printf ("Could not open DeHackEd patch \"%s\"\n", file); return; } } else { // Nothing to do. return; } // End file with a NULL for our parser PatchFile[filelen] = 0; dversion = pversion = -1; cont = 0; if (!strncmp (PatchFile, "Patch File for DeHackEd v", 25)) { PatchPt = strchr (PatchFile, '\n'); while ((cont = GetLine()) == 1) { CHECKKEY ("Doom version", dversion) else CHECKKEY ("Patch format", pversion) } if (!cont || dversion == -1 || pversion == -1) { free (PatchFile); Printf ("\"%s\" is not a DeHackEd patch file\n"); return; } } else { DPrintf ("Patch does not have DeHackEd signature. Assuming .bex\n"); dversion = 19; pversion = 6; PatchPt = PatchFile; while ((cont = GetLine()) == 1) ; } if (pversion != 6) { Printf ("DeHackEd patch version is %d.\nUnexpected results may occur.\n", pversion); } if (dversion == 16) dversion = 0; else if (dversion == 17) dversion = 2; else if (dversion == 19) dversion = 3; else if (dversion == 20) dversion = 1; else if (dversion == 21) dversion = 4; else { Printf ("Patch created with unknown DOOM version.\nAssuming version 1.9.\n"); dversion = 3; } do { if (cont == 1) { Printf ("Key %s encountered out of context\n", Line1); cont = 0; } else if (cont == 2) cont = HandleMode (Line1, atoi (Line2)); } while (cont); free (PatchFile); Printf ("Patch installed\n"); }