Quake (2021)
Quake |
---|
Developers: id Software,
Night Dive Studios,
MachineGames This game has unused areas. |
Quake is a story about a homicidal man who likes to kill Lovecraft-inspired monsters through barely connected level sets. He also likes to murder his friends (who respawn as soon as they are killed) in castles that have a large Jesus crucified in them for some reason. This 25th Anniversary version adds improved graphics, a new expansion pack, cross-platform multiplayer and curated add-ons, including, uh, Quake 64. Oh yeah, and it's the first digital re-release of the game to finally include the Trent Reznor soundtrack.
To do:
|
Contents
- 1 Test Levels
- 1.1 test/mals_combatbox
- 1.2 test/test_aiming
- 1.3 test/test_barrierjump
- 1.4 test/test_button
- 1.5 test/test_characters
- 1.6 test/test_door
- 1.7 test/test_invis
- 1.8 test/test_laser
- 1.9 test/test_nodes
- 1.10 test/test_obstacles
- 1.11 test/test_powerup
- 1.12 test/test_rockets
- 1.13 test/test_shadow
- 1.14 test/test_walkoffledge
- 1.15 test_ctf
- 2 Unused Localization Strings
- 3 Unused Models
- 4 Texture WADs
- 5 Source Code
- 6 Additional mods
- 7 References
Test Levels
A number of test levels, largely intended for testing Bot AI behavior, are still present in the maps folder of id1/pak0.pak.
To do: Take new screenshots with sv_cheats 1 and nav_edit 1 enabled |
test/mals_combatbox
A square arena bordered by pillars, blocking the line of sight of various monsters seen from Episode 1, with two Enforcers right in front of the player back at the start. Some weapons, health and ammo are also present.
The "Mal" in the filename references John "Maleficus" Dean, an AI programmer at Id who wrote the remaster's bot system.
test/test_aiming
A seemingly-empty, square map.
test/test_barrierjump
A room with varyingly-tall barriers, leading up to a door-textured barrier you can't jump over. A Rocket Launcher sits in a second room beyond the barrier.
test/test_button
A corridor with a door, and a button for opening it. On the other wise of the door is a Rocket Launcher with a pile of ammo.
test/test_characters
A very large, empty room with a Rocket Launcher and a pile of ammo in the center.
test/test_door
Similar to test_button, but without the button. Instead, the door can be opened by shooting it.
test/test_invis
Similar to test_aiming, but littered with Rings of Shadows.
test/test_laser
An empty, unlit corridor with wall textures that have fullbright elements.
test/test_nodes
An empty, rectangular room with a devil-head texture on one side of the room. Presumably used for testing bot AI.
test/test_obstacles
An obstacle course! Features various obstacles to jump over, several invisible teleporters for if you fail a jump, and a jump pad right at the end. Walking into the slipgate teleports you back to the start.
test/test_powerup
An unlit, square room with two Quad Damages, and two Pentagrams of Protection, sitting ominously.
test/test_rockets
An empty, rectangular room with a Rocket Launcher and a pile of ammo at one end.
test/test_shadow
Two greyboxed areas showcasing the real-time lighting showcased in the Dimension of the Machine maps.
test/test_walkoffledge
A corridor with a short staircase with a ledge that drops down - not far enough to get hurt, but far enough that you can't jump back up naturally - leading to a Rocket Launcher and a pile of ammo.
test_ctf
Included in the PAK file for the Threewave CTF mode added on 18 August 2022 (ctf/pak0.pak). Just an empty square room with two flags. No bot AI nodes are present. Weirdly, you always seem to start inside the ceiling/sky, stuck and unable to move.
Unused Localization Strings
Ending Text
As found in QuakeEX.kpf/localization/loc_english.txt...
qc_finale_e1_shareware = "As the corpse of the monstrous entity\nChthon sinks back into the lava whence\nit rose, you grip the Rune of Earth\nMagic tightly. Now that you have\nconquered the Dimension of the Doomed,\nrealm of Earth Magic, you are ready to\ncomplete your task in the other three\nhaunted lands of Quake. Or are you? If\nyou don't register Quake, you'll never\nknow what awaits you in the Realm of\nBlack Magic, the Netherworld, and the\nElder World!"
The alternate version of Episode 1's finale from the original Shareware demo. The 2021 remaster does not have a demo, therefore...
map_unregistered = "You haven't registered Quake!\n\nCall 1-800-idgames to unlock\nthe full game from CD-ROM\nor for mail delivery."
Another shareware hangover from the START map.
qc_ks_dragon = "{0} was fried by a Dragon\n"
The unused obituary in the original Quake for getting killed by the unused Dragon enemy. Filed under strings for Scourge of Armagon, for some reason, despite Scourge not having a dragon.
qc_ks_shub = "{0} became one with Shub-Niggurath\n"
The unused obituary in the original Quake for somehow getting killed by the final boss, whose damage is instead handled by a generic trigger.
Interface Strings
Various strings intended for the menus and the Bethesda.net UI will never surface. Some of these strings were also unused in the 2020 Doom 64 remaster.
The separated game thread/render thread architecture of the KEX Engine was removed during development of Quake, rendering this would-be menu option obsolete:
m_game_thread = "Threaded Game Renderer"
An error intended to appear if a user's Bethesda.net email address could not be successfully retrieved. Instead the field will appear blank.
bnet_no_email = "Could not retrieve email address"
Messages intended for a control that would auto-populate a user's email address based on console account information. This was never implemented and does not appear in either the Xbox One nor the PlayStation 4 version of the game.
bnet_qa_af_tooltip_xb = "Pre-fill with your Microsoft Account email address for use in Bethesda.net account creation." bnet_qa_af_tooltip_xs = "Pre-fill with your Microsoft Account email address for use in Bethesda.net account creation." bnet_qa_af_tooltip_ws = "Pre-fill with your Microsoft Account email address for use in Bethesda.net account creation." bnet_qa_af_tooltip_ps = "Use your Account for PlayStation™Network email address. Your data will be sent to ZeniMax Media in the United States and used to link your account for PlayStation™Network to your Bethesda.net account." bnet_qa_af_tooltip_pr = "Use your Account for PlayStation™Network email address. Your data will be sent to ZeniMax Media in the United States and used to link your account for PlayStation™Network to your Bethesda.net account."
Strings used by the Slayers Club integration feature of Doom 64, which is not used by Quake; instead, rewards such as the Doom Eternal Quake skin are unlocked automatically when the user links their account.
bnet_sc_title = "SLAYERS CLUB" bnet_sc_body = "Thanks for logging into Bethesda.net! As a Slayers Club member you'll receive exclusive in-game items for DOOM Eternal. Choose the button button below to claim your club reward." bnet_sc_redeem = "Claim Your Reward" bnet_sc_success = "You're all set. Your exclusive digital items have been unlocked. Thanks for playing!"
Strings for a dialog invoked by a Bethesda Store DRM check, which is not used by Quake, even in the Bethesda Store version:
bnet_error_drm = "This game requires validation.\n\nPlease log in to the Bethesda.net Launcher with an active internet connection and try again." bnet_error_if_persists = "If the problem persists, please visit https://help.bethesda.net. bnet_error_validation = "Validation Required" bnet_retry = "Retry" bnet_exit_game = "Exit Game"
Strings for a profile conflict resolution dialog, which is never evoked; profile resolution occurs automatically and silently in the background instead:
bnet_profile_merge_prof1 = "PROFILE 1" bnet_profile_merge_prof2 = "PROFILE 2" bnet_profile_merge_time = "Time" bnet_profile_merge_points = "Points" bnet_profile_merge_title = "SELECT SESSION" bnet_profile_merge_desc = "Bethesda.net has detected an existing session for this profile. Please select the session you wish to keep. Information from the other session will be lost." bnet_profile_merge_confirm = "CONFIRM SELECTION"
Unused Models
To do: Add renders |
The remastered models in id1/pak0.pak/progs also include models for the health and ammo pickups. However, these don't appear to be loaded by the game, instead using their original BSP-based forms.
Of note is that the health boxes have their design slightly altered in these remasters, to replace the red cross with a similarly-sized glowing square. The health boxes in the final release are not altered, apparently due to a quirk of the Red Cross's copyright on their design (Apparently it has to be on a white background!)
Texture WADs
To do: Scour for any other unused junk |
A selection of texture WADs (used for level editors - BSP files embed their textures when compiled for ease of distribution) were mistakenly included in the PAK file and are present in id1/pak0.pak/gfx:
- all.wad
- base.wad
- items.wad
- jr_med.wad
- medieval.wad
- metal.wad
- rogue.wad
- start.wad
- textures.wad
- tim.wad
- wizard.wad
This unused placeholder texture, "backpack", is present in items.wad, and consists of a slightly squished Doom backpack, presumably from when the backpack item was still a BSP box instead of a .mdl model.
Source Code
In the included Quake 64 mod, q64/pak0.pak/src.txt contains a piece of source code for what appears to be an internal utility to read and work with the Quake 64 ROM data.
#include "kexlib.h" #include "quakeex.h" /* ============================================================================= DECOMPRESSION ============================================================================= */ int wad_numlumps2; lumpinfo_t* wad_lumps2; byte* wad_base2; /*=======*/ /* TYPES */ /*=======*/ typedef struct { int var0; int var1; int var2; int var3; kexArray<uint8_t> write; byte *read; byte *readPos; } decoder_t; /*=========*/ /* GLOBALS */ /*=========*/ static short ShiftTable[6] = {4, 6, 8, 10, 12, 14}; // 8005D8A0 static int tableVar01[18]; // 800B2250 static short *PtrEvenTbl; // 800B2298 static short *PtrOddTbl; // 800B229C static short *PtrNumTbl1; // 800B22A0 static short *PtrNumTbl2; // 800B22A4 static short DecodeTable[2524]; // 800B22A8 static short array01[1258]; // 800B3660 static decoder_t decoder; // 800B4034 static byte *allocPtr; // 800B4054 static int OVERFLOW_READ; // 800B4058 static int OVERFLOW_WRITE; // 800B405C /* ============================================================================ DECODE BASED ROUTINES ============================================================================ */ /* ======================== = = GetDecodeByte = ======================== */ static byte GetDecodeByte(void) // 8002D1D0 { if((int)(decoder.readPos - decoder.read) >= OVERFLOW_READ) { return -1; } return *decoder.readPos++; } /* ======================== = = WriteOutput = ======================== */ static void WriteOutput(byte outByte) // 8002D214 { decoder.write.Push(outByte); } /* ======================== = = WriteBinary = routine required for encoding = ======================== */ static void WriteBinary(int binary) // 8002D288 { decoder.var3 = (decoder.var3 << 1); if(binary != 0) { decoder.var3 = (decoder.var3 | 1); } decoder.var2 = (decoder.var2 + 1); if(decoder.var2 == 8) { WriteOutput((byte)decoder.var3); decoder.var2 = 0; } } /* ======================== = = DecodeScan = ======================== */ static int DecodeScan(void) // 8002D2F4 { int resultbyte; resultbyte = decoder.var0; decoder.var0 = (resultbyte - 1); if((resultbyte < 1)) { resultbyte = GetDecodeByte(); decoder.var1 = resultbyte; decoder.var0 = 7; } resultbyte = (0 < (decoder.var1 & 0x80)); decoder.var1 = (decoder.var1 << 1); return resultbyte; } /* ======================== = = MakeExtraBinary = routine required for encoding = ======================== */ static void MakeExtraBinary(int binary, int shift) // 8002D364 { int i; i = 0; if(shift > 0) { do { WriteBinary(binary & 1); binary = (binary >> 1); } while(++i != shift); } } /* ======================== = = RescanByte = ======================== */ static int RescanByte(int byte) // 8002D3B8 { int shift; int i; int resultbyte; resultbyte = 0; i = 0; shift = 1; if(byte <= 0) return resultbyte; do { if(DecodeScan() != 0) { resultbyte |= shift; } i++; shift = (shift << 1); } while(i != byte); return resultbyte; } /* ======================== = = WriteEndCode = routine required for encoding = ======================== */ static void WriteEndCode(void) // 8002D424 { if(decoder.var2 > 0) { WriteOutput((byte)(decoder.var3 << (8 - decoder.var2)) & 0xff); } } /* ======================== = = InitDecodeTable = ======================== */ static void InitDecodeTable(void) // 8002D468 { int evenVal, oddVal, incrVal; short *curArray; short *incrTbl; short *evenTbl; short *oddTbl; tableVar01[15] = 3; tableVar01[16] = 0; tableVar01[17] = 0; decoder.var0 = 0; decoder.var1 = 0; decoder.var2 = 0; decoder.var3 = 0; curArray = &array01[2]; incrTbl = &DecodeTable[0x4F2]; incrVal = 2; do { if(incrVal < 0) { *incrTbl = (short)((incrVal + 1) >> 1); } else { *incrTbl = (short)(incrVal >> 1); } *curArray++ = 1; incrTbl++; } while(++incrVal < 1258); oddTbl = &DecodeTable[0x279]; evenTbl = &DecodeTable[1]; evenVal = 2; oddVal = 3; do { *oddTbl++ = (short)oddVal; oddVal += 2; *evenTbl++ = (short)evenVal; evenVal += 2; } while(oddVal < 1259); tableVar01[0] = 0; incrVal = (1 << ShiftTable[0]); tableVar01[6] = (incrVal - 1); tableVar01[1] = incrVal; incrVal += (1 << ShiftTable[1]); tableVar01[7] = (incrVal - 1); tableVar01[2] = incrVal; incrVal += (1 << ShiftTable[2]); tableVar01[8] = (incrVal - 1); tableVar01[3] = incrVal; incrVal += (1 << ShiftTable[3]); tableVar01[9] = (incrVal - 1); tableVar01[4] = incrVal; incrVal += (1 << ShiftTable[4]); tableVar01[10] = (incrVal - 1); tableVar01[5] = incrVal; incrVal += (1 << ShiftTable[5]); tableVar01[11] = (incrVal - 1); tableVar01[12] = (incrVal - 1); tableVar01[13] = tableVar01[12] + 64; } /* ======================== = = CheckTable = ======================== */ static void CheckTable(int a0,int a1,int a2) // 8002D624 { int i; int idByte1; int idByte2; short *curArray; short *evenTbl; short *oddTbl; short *incrTbl; i = 0; evenTbl = &DecodeTable[0]; oddTbl = &DecodeTable[0x278]; incrTbl = &DecodeTable[0x4F0]; idByte1 = a0; do { idByte2 = incrTbl[idByte1]; array01[idByte2] = (array01[a1] + array01[a0]); a0 = idByte2; if(idByte2 != 1) { idByte1 = incrTbl[idByte2]; idByte2 = evenTbl[idByte1]; a1 = idByte2; if(a0 == idByte2) { a1 = oddTbl[idByte1]; } } idByte1 = a0; }while(a0 != 1); if(array01[1] != 0x7D0) { return; } array01[1] >>= 1; curArray = &array01[2]; do { curArray[3] >>= 1; curArray[2] >>= 1; curArray[1] >>= 1; curArray[0] >>= 1; curArray += 4; i += 4; } while(i != 1256); } /* ======================== = = DecodeByte = ======================== */ static void DecodeByte(int tblpos) // 8002D72C { int incrIdx; int evenVal; int idByte1; int idByte2; int idByte3; int idByte4; short *evenTbl; short *oddTbl; short *incrTbl; short *tmpIncrTbl; evenTbl = &DecodeTable[0]; oddTbl = &DecodeTable[0x278]; incrTbl = &DecodeTable[0x4F0]; idByte1 = (tblpos + 0x275); array01[idByte1] += 1; if(incrTbl[idByte1] != 1) { tmpIncrTbl = &incrTbl[idByte1]; idByte2 = *tmpIncrTbl; if(idByte1 == evenTbl[idByte2]) { CheckTable(idByte1, oddTbl[idByte2], idByte1); } else { CheckTable(idByte1, evenTbl[idByte2], idByte1); } do { incrIdx = incrTbl[idByte2]; evenVal = evenTbl[incrIdx]; if(idByte2 == evenVal) { idByte3 = oddTbl[incrIdx]; } else { idByte3 = evenVal; } if(array01[idByte3] < array01[idByte1]) { if(idByte2 == evenVal) { oddTbl[incrIdx] = (short)idByte1; } else { evenTbl[incrIdx] = (short)idByte1; } evenVal = evenTbl[idByte2]; if(idByte1 == evenVal) { idByte4 = oddTbl[idByte2]; evenTbl[idByte2] = (short)idByte3; } else { idByte4 = evenVal; oddTbl[idByte2] = (short)idByte3; } incrTbl[idByte3] = (short)idByte2; *tmpIncrTbl = (short)incrIdx; CheckTable(idByte3, idByte4, idByte4); tmpIncrTbl = &incrTbl[idByte3]; } idByte1 = *tmpIncrTbl; tmpIncrTbl = &incrTbl[idByte1]; idByte2 = *tmpIncrTbl; } while (idByte2 != 1); } } /* ======================== = = StartDecodeByte = ======================== */ static int StartDecodeByte(void) // 8002D904 { int lookup; short *evenTbl; short *oddTbl; lookup = 1; evenTbl = &DecodeTable[0]; oddTbl = &DecodeTable[0x278]; while(lookup < 0x275) { if(DecodeScan() == 0) { lookup = evenTbl[lookup]; } else { lookup = oddTbl[lookup]; } } lookup = (lookup + -0x275); DecodeByte(lookup); return lookup; } /* ======================== = = DecodeD64 = = Exclusive Doom 64 = ======================== */ void DecodeD64(uint8_t *input) // 8002DFA0 { int copyPos, storePos; int dec_byte, resc_byte; int incrBit, copyCnt, shiftPos, j; InitDecodeTable(); OVERFLOW_READ = D_MAXINT; OVERFLOW_WRITE = D_MAXINT; incrBit = 0; decoder.read = input; decoder.readPos = input; decoder.write.Empty(); allocPtr = (byte*)Z_Malloc(tableVar01[13]); dec_byte = StartDecodeByte(); while(dec_byte != 256) { if(dec_byte < 256) { /* Decode the data directly using binary data code */ WriteOutput((byte)(dec_byte & 0xff)); allocPtr[incrBit] = (byte)dec_byte; /* Resets the count once the memory limit is exceeded in allocPtr, so to speak resets it at startup for reuse */ incrBit += 1; if(incrBit == tableVar01[13]) { incrBit = 0; } } else { /* Decode the data using binary data code, a count is obtained for the repeated data, positioning itself in the root that is being stored in allocPtr previously. */ /* A number is obtained from a range from 0 to 5, necessary to obtain a shift value in the ShiftTable*/ shiftPos = (dec_byte + -257) / 62; /* get a count number for data to copy */ copyCnt = (dec_byte - (shiftPos * 62)) + -254; /* To start copying data, you receive a position number that you must sum with the position of table tableVar01 */ resc_byte = RescanByte(ShiftTable[shiftPos]); /* with this formula the exact position is obtained to start copying previously stored data */ copyPos = incrBit - ((tableVar01[shiftPos] + resc_byte) + copyCnt); if(copyPos < 0) { copyPos += tableVar01[13]; } storePos = incrBit; for(j = 0; j < copyCnt; j++) { /* write the copied data */ WriteOutput(allocPtr[copyPos]); /* save copied data at current position in memory allocPtr */ allocPtr[storePos] = allocPtr[copyPos]; storePos++; /* advance to next allocPtr memory block to store */ copyPos++; /* advance to next allocPtr memory block to copy */ /* reset the position of storePos once the memory limit is exceeded */ if(storePos == tableVar01[13]) { storePos = 0; } /* reset the position of copyPos once the memory limit is exceeded */ if(copyPos == tableVar01[13]) { copyPos = 0; } } /* Resets the count once the memory limit is exceeded in allocPtr, so to speak resets it at startup for reuse */ incrBit += copyCnt; if(incrBit >= tableVar01[13]) { incrBit -= tableVar01[13]; } } dec_byte = StartDecodeByte(); } Z_Free(allocPtr); } /* ============= W_GetLumpinfo2 ============= */ lumpinfo_t* W_GetLumpinfo2(char* name) { int i; lumpinfo_t* lump_p; char clean[16]; W_CleanupName(name, clean); for(lump_p=wad_lumps2, i=0 ; i<wad_numlumps2 ; i++,lump_p++) { if(!strcmp(clean, lump_p->name)) { return lump_p; } } Sys_Error("W_GetLumpinfo2: %s not found", name); return nullptr; } /* ============= W_GetLumpName2 ============= */ void* W_GetLumpName2(char* name) { lumpinfo_t* lump; lump = W_GetLumpinfo2(name); return (void*)(wad_base2 + lump->filepos); } /* ============= W_GetLumpNum2 ============= */ void* W_GetLumpNum2(int num) { lumpinfo_t* lump; if(num < 0 || num > wad_numlumps2) { Sys_Error("W_GetLumpNum2: bad number: %i", num); } lump = wad_lumps2 + num; return (void*)(wad_base2 + lump->filepos); } /* ============= W_CheckNumForName2 Returns -1 if name not found. ============= */ int W_CheckNumForName2(const char* name) { int i = -1; lumpinfo_t* lump_p; char clean[16]; W_CleanupName(name, clean); for(lump_p=wad_lumps2, i=0 ; i<wad_numlumps2 ; i++,lump_p++) { if(!kexStr::StrCaseCmp(clean, lump_p->name)) { return i; } } return -1; } /* ============= W_GetNumForName2 Calls W_CheckNumForName, but bombs out if not found. ============= */ int W_GetNumForName2(const char* name) { int i; i = W_CheckNumForName2(name); if(i == -1) { kexError("W_GetNumForName2: %s not found!", name); } return i; } /* ==================== W_LoadWadFile2 ==================== */ void W_LoadWadFile2(const char* filename) { lumpinfo_t* lump_p; wadinfo_t* header; int i; int infotableofs; kexPrintf("W_LoadWadFile2: Adding %s...\n", filename); wad_base2 = COM_LoadHunkFile(filename); if(!wad_base2) { kexWarning("W_LoadWadFile2: couldn't load %s\n", filename); return; } header = (wadinfo_t*)wad_base2; if(header->identification[0] != 'W' || header->identification[1] != 'A' || header->identification[2] != 'D' || header->identification[3] != '2') { kexWarning("Wad file %s doesn't have WAD2 id\n",filename); return; } wad_numlumps2 = kexEndian::SwapLE32(header->numlumps); infotableofs = kexEndian::SwapLE32(header->infotableofs); wad_lumps2 = (lumpinfo_t*)(wad_base2 + infotableofs); for(i=0, lump_p = wad_lumps ; i<wad_numlumps2 ; i++,lump_p++) { lump_p->filepos = kexEndian::SwapLE32(lump_p->filepos); lump_p->size = kexEndian::SwapLE32(lump_p->size); lump_p->disksize = kexEndian::SwapLE32(lump_p->disksize); W_CleanupName(lump_p->name, lump_p->name); } } struct qTexInfoEntry_s { qTexInfoEntry_s() : index(-1), n64texlump(-1), dwOffsetToDataBlob(uint32_t(-1)), dwFileSize(0) { } int32_t index; int32_t n64texlump; uint32_t dwOffsetToDataBlob; size_t dwFileSize; }; static void DescrambleTexture(byte* pData, const int width, const int height) { const int size = (width * height)/2; constexpr int mask = 1; byte* pRover = pData; for(int i = 0; i < height; ++i) { kexAssert(pRover < (pData + (width * height))); if(i & mask) { for(int x = 0; x < width; x += 4) { int* pTmp = (int*)&pRover[x]; *pTmp = kexEndian::SwapBE32(*pTmp); } } pRover += width; } pRover = pData; for(int i = 0; i < height; ++i) { kexAssert(pRover < (pData + (width * height))); if(i & mask) { for(int x = 0; x < width; x += 2) { int16_t* pTmp = (int16_t*)&pRover[x]; *pTmp = kexEndian::SwapBE16(*pTmp); } } pRover += width; } } static void BuildQuake64BSPLevel(const kexStr& strOutputPath, const int lump) { uint32_t dwDataPos[HEADER_LUMPS]; uint32_t dwDataSize[HEADER_LUMPS]; kexArray<byte> nLevelDataBlob; kexArray<byte> nTexturesDataBlob; kexBufferStreamGrowable cTexturesDataLump; kexTMap<qTexInfoEntry_s> nTexEntryMap; int32_t iCurrentTextureID; kexMemclr(dwDataPos); kexMemclr(dwDataSize); const int t_start = W_GetNumForName2("t_start")+1; const int scanLump = lump+1; iCurrentTextureID = 0; for(int i = scanLump; i < scanLump+14; ++i) { lumpinfo_t* pLump = &wad_lumps2[i]; DecodeD64((byte*)W_GetLumpNum2(i)); kexArray<byte> nData = decoder.write; int index = (i - lump)-1; if(index < HEADER_LUMPS) { if(index >= LUMP_TEXTURES) { index++; } if(index == LUMP_TEXINFO) { texinfo_t* pTexInfo = (texinfo_t*)nData.GetDataPtr(); size_t count = pLump->size / sizeof(*pTexInfo); for(int t = 0; t < count; ++t) { int texid = pTexInfo[t].miptex; if(texid == -1) { continue; } lumpinfo_t* pTexLump = &wad_lumps2[t_start+texid]; kexStr strTextureName = pTexLump->name; qTexInfoEntry_s* pEntry = nTexEntryMap.FindAdd(strTextureName); if(pEntry->index == -1) { DecodeD64((byte*)W_GetLumpNum2(t_start+texid)); kexArray<byte> nTexData = decoder.write; byte* pTexData = nTexData.GetDataPtr(); int16_t* pTmp = (int16_t*)pTexData; int width = kexEndian::SwapBE16(pTmp[0]); int height = kexEndian::SwapBE16(pTmp[1]); int shift = kexEndian::SwapBE16(pTmp[2]); int oldwidth = width; int oldheight = height; width >>= shift; height >>= shift; if(pTexLump->name[0] != '*') { DescrambleTexture(pTexData+8, width, height); } kexImage cImage(pTexData+8, width, height, RTPF_R8); const size_t dwMipTexSize = (44 + (width*height)); pEntry->dwOffsetToDataBlob = nTexturesDataBlob.Length(); nTexturesDataBlob.Resize(nTexturesDataBlob.Length() + dwMipTexSize); kexBufferStreamDirect cTexWrite(&nTexturesDataBlob[pEntry->dwOffsetToDataBlob], dwMipTexSize); for(size_t c = 0; c < strTextureName.Length(); ++c) { cTexWrite.Write8(strTextureName[c]); } for(size_t c = strTextureName.Length(); c < strTextureName.Length()+(16-strTextureName.Length()); ++c) { cTexWrite.Write8(0); } cTexWrite.Write32(width); cTexWrite.Write32(height); cTexWrite.Write32(shift); cTexWrite.Write32(44); cTexWrite.Write32(44); cTexWrite.Write32(44); cTexWrite.Write32(44); for(int c = 0; c < (width*height); ++c) { cTexWrite.Write8(cImage.Data()[c]); } pEntry->n64texlump = t_start+texid; pEntry->index = iCurrentTextureID; pEntry->dwFileSize = dwMipTexSize; iCurrentTextureID++; } } for(int t = 0; t < count; ++t) { int texid = pTexInfo[t].miptex; lumpinfo_t* pTexLump = &wad_lumps2[t_start+texid]; kexStr strTextureName = pTexLump->name; qTexInfoEntry_s* pEntry = nTexEntryMap.GetValue(strTextureName); if(!pEntry || pEntry->index <= -1) { pTexInfo[t].miptex = -1; continue; } pTexInfo[t].miptex = pEntry->index; } cTexturesDataLump.Write32(iCurrentTextureID); for(int t = 0; t < iCurrentTextureID; ++t) { typename kexTMap<qTexInfoEntry_s>::Iterator cItr(nTexEntryMap); typename kexTMap<qTexInfoEntry_s>::hashType_t* pKey = nullptr; while((pKey = cItr.GetNext())) { if(pKey->GetValue().index == t) { cTexturesDataLump.WriteU32(pKey->GetValue().dwOffsetToDataBlob + (4*iCurrentTextureID) + 4); break; } } } for(const auto& someByte : nTexturesDataBlob) { cTexturesDataLump.Write8(someByte); } kexAutoFileWrite cTexturesWrite(kexStr::Format("%s/%sTEXTURES.lmp", strOutputPath.c_str(), wad_lumps2[lump].name)); if(cTexturesWrite.IsValid()) { cTexturesWrite->Write(cTexturesDataLump.Buffer(), cTexturesDataLump.BufferLength()); } } dwDataPos[index] = nLevelDataBlob.Length() + 124; dwDataSize[index] = pLump->size; for(int j = 0; j < pLump->size; ++j) { nLevelDataBlob.Push(nData[j]); } } } kexAutoFileWrite cBspWrite(kexStr::Format("%s/%s.bsp", strOutputPath.c_str(), wad_lumps2[lump].name)); if(cBspWrite.IsValid()) { int32_t iOffsetFromTextureData = 0; int32_t iScratch = BSPVERSION_QUAKE64; cBspWrite->Write((byte*)&iScratch, 4); for(int j = 0; j < HEADER_LUMPS; ++j) { if(j == LUMP_TEXTURES) { iOffsetFromTextureData = cTexturesDataLump.BufferLength(); iScratch = dwDataPos[j+1]; cBspWrite->Write((byte*)&iScratch, 4); iScratch -= 124; nLevelDataBlob.Insert(iScratch, cTexturesDataLump.BufferLength()); kexMemcpy(&nLevelDataBlob[iScratch], cTexturesDataLump.Buffer(), cTexturesDataLump.BufferLength()); iScratch = (int32_t)cTexturesDataLump.BufferLength(); cBspWrite->Write((byte*)&iScratch, 4); continue; } iScratch = dwDataPos[j]; iScratch += iOffsetFromTextureData; cBspWrite->Write((byte*)&iScratch, 4); cBspWrite->Write((byte*)&dwDataSize[j], 4); } cBspWrite->Write(nLevelDataBlob.GetDataPtr(), nLevelDataBlob.Length()); } } COMMAND(loadquake64wad) { W_LoadWadFile2("Quake64.wad"); if(!wad_base2 || wad_numlumps2 <= 0) { return; } kexStr strOutputPath = kexStr::FormatPath("q64output"); if(!kexPlatform::cFile->MakeDirectory(strOutputPath.c_str())) { return; } for(int i = 0; i< wad_numlumps2 ; i++) { lumpinfo_t* lump = &wad_lumps2[i]; if( !strcmp(lump->name, "NEND") || (lump->name[0] == 'N' && (lump->name[0] == 'N' && lump->name[1] == 'E' && (lump->name[2] >= '1' && lump->name[2] <= '9') && lump->name[3] == 'M' && (lump->name[4] >= '1' && lump->name[4] <= '9') && lump->name[5] == '\0'))) { BuildQuake64BSPLevel(strOutputPath, i); continue; } else if(lump->size == 0) { continue; } } }
Additional mods
By inputting the command line parameter +ui_addonsBaseURL "https://kexquake.s3.amazonaws.com" it's possible to obtain more custom third-party mods for the game, possibly awaiting curation. (Last updated Oct 19, 2023)
Mod name | Status |
---|---|
Beyond Belief | Added on September 29, 2022[2] |
Contract Revoked | Added on July 13, 2023[3] |
Dark Triad | Not yet added |
Deathmatch Dimension | Added on August 10, 2023[4] |
Elder World Jam | Not yet added |
Empire of Disorder | Not yet added |
Epochs of Enmity | Not yet added |
Euclid's Nightmare | Not yet added |
Honey | Added on December 2, 2021[5] |
IKSPQ | Not yet added |
Insomnia | Not yet added |
Koohoo Retro Jam | Not yet added |
Map Jam X: Insomnia | Not yet added |
Operation: Urth Majik | Not yet added |
Quake 64 | Added with the base game |
Realm of Tiddles | Not yet added |
Rubicon 2 | Added on August 18, 2022[6] |
Sacrilege | Not yet added |
Spiritworld | Added on November 30, 2023[7] |
Squire of Time | Not yet added |
Terra | Added on March 29, 2022[8] |
The Punishment Due | Added on May 31, 2022[9] |
Underdark Overbright & Copper | Added on February 9, 2022[10] |
References
- ↑ Quake's free "next-gen" upgrade now available for Xbox Series X/S and PS5
- ↑ "New Add-on available: Beyond Belief for Quake"
- ↑ "New Add-on available: Contract Revoked for Quake"
- ↑ "New Add-on Available: Deathmatch Dimension for Quake"
- ↑ "Download the Honey Add-on free for our Quake re-release today!"
- ↑ "New Add-ons Available: Revolution! and Rubicon 2"
- ↑ New Add-on Available: Spiritworld for Quake
- ↑ "New Add-on available: Terra"
- ↑ "New Add-on available: The Punishment Due for Quake"
- ↑ "Underdark Overbright & Copper bring an awesome combo into a new Add-on to our Quake re-release!"
The Quake series
| |
---|---|
Windows | Quake (1996) (Prototypes) • Quake II (1997) (Prototype) • Quake III Arena (Prototypes) • Quake III Team Arena (Prototype) • Quake 4 • Quake Live • Quake Champions • Quake (2021) • Quake II (2023) |
Linux | Quake • Quake II • Quake III Arena (Prototypes) • Quake III Team Arena (Prototype) • Quake 4 |
Mac OS Classic | Quake • Quake II • Quake III Arena (Prototypes) • Quake III Team Arena (Prototype) |
Mac OS X | Quake III Arena (Prototypes) • Quake III Team Arena (Prototype) • Quake 4 |
DOS | Quake (Prototypes) |
Sega Saturn | Quake |
Nintendo 64 | Quake |
PlayStation | Quake II (Prototypes) |
Dreamcast | Quake III Arena |
PlayStation 2 | Quake III: Revolution |
Game Boy Advance | Quake |
Xbox 360 | Quake II • Quake 4 |
PlayStation 4, Xbox One, Nintendo Switch. PlayStation 5, Xbox Series X | Quake • Quake II |
- Pages missing developer references
- Games developed by id Software
- Games developed by Night Dive Studios
- Games developed by MachineGames
- Pages missing publisher references
- Games published by Bethesda Softworks
- Windows games
- PlayStation 4 games
- Xbox One games
- Nintendo Switch games
- PlayStation 5 games
- Xbox Series X games
- Pages missing date references
- Games released in 2021
- Games released in August
- Games released on August 20
- Games released in October
- Games released on October 12
- Games with unused areas
- Games with uncompiled source code
- Games with unused graphics
- Games with unused models
- Games with unused text
- To do
- Quake series
Cleanup > Pages missing date references
Cleanup > Pages missing developer references
Cleanup > Pages missing publisher references
Cleanup > To do
Games > Games by content > Games with uncompiled source code
Games > Games by content > Games with unused areas
Games > Games by content > Games with unused graphics
Games > Games by content > Games with unused models
Games > Games by content > Games with unused text
Games > Games by developer > Games developed by Atari, SA > Games developed by Night Dive Studios
Games > Games by developer > Games developed by MachineGames
Games > Games by developer > Games developed by id Software
Games > Games by platform > Nintendo Switch games
Games > Games by platform > PlayStation 4 games
Games > Games by platform > PlayStation 5 games
Games > Games by platform > Windows games
Games > Games by platform > Xbox One games
Games > Games by platform > Xbox Series X games
Games > Games by publisher > Games published by Microsoft > Games published by Bethesda Softworks
Games > Games by release date > Games released in 2021
Games > Games by release date > Games released in August
Games > Games by release date > Games released in August > Games released on August 20
Games > Games by release date > Games released in October
Games > Games by release date > Games released in October > Games released on October 12
Games > Games by series > Quake series