diff --git a/source/core/maploader.cpp b/source/core/maploader.cpp index f6522fdbd..05ada6e2e 100644 --- a/source/core/maploader.cpp +++ b/source/core/maploader.cpp @@ -510,6 +510,170 @@ void loadMap(const char* filename, int flags, vec3_t* pos, int16_t* ang, int* cu } +//========================================================================== +// +// Decrypt +// +// Note that this is different from the general RFF encryption. +// +//========================================================================== + +static void Decrypt(void* to_, const void* from_, int len, int key) +{ + uint8_t* to = (uint8_t*)to_; + const uint8_t* from = (const uint8_t*)from_; + + for (int i = 0; i < len; ++i, ++key) + { + to[i] = from[i] ^ key; + } +} + + +//========================================================================== +// +// P_LoadBloodMap +// +// This was adapted from ZDoom's old Build map loader. +// +//========================================================================== + +static void P_LoadBloodMapWalls(uint8_t* data, size_t len, TArray& lwalls) +{ + uint8_t infoBlock[37]; + int mapver = data[5]; + uint32_t matt; + int i; + int k; + + if (mapver != 6 && mapver != 7) + { + return; + } + + matt = *(uint32_t*)(data + 28); + if (matt != 0 && + matt != MAKE_ID('M', 'a', 't', 't') && + matt != MAKE_ID('t', 't', 'a', 'M')) + { + Decrypt(infoBlock, data + 6, 37, 0x7474614d); + } + else + { + memcpy(infoBlock, data + 6, 37); + } + int numRevisions = *(uint32_t*)(infoBlock + 27); + int numSectors = *(uint16_t*)(infoBlock + 31); + int numWalls = *(uint16_t*)(infoBlock + 33); + int numSprites = *(uint16_t*)(infoBlock + 35); + int skyLen = 2 << *(uint16_t*)(infoBlock + 16); + + if (mapver == 7) + { + // Version 7 has some extra stuff after the info block. This + // includes a copyright, and I have no idea what the rest of + // it is. + data += 171; + } + else + { + data += 43; + } + + // Skip the sky info. + data += skyLen; + + lwalls.Reserve(numWalls); + + // Read sectors + k = numRevisions * sizeof(sectortypedisk); + for (i = 0; i < numSectors; ++i) + { + sectortypedisk bsec; + if (mapver == 7) + { + Decrypt(&bsec, data, sizeof(sectortypedisk), k); + } + else + { + memcpy(&bsec, data, sizeof(sectortypedisk)); + } + data += sizeof(sectortypedisk); + if (bsec.extra > 0) // skip Xsector + { + data += 60; + } + } + + // Read walls + k |= 0x7474614d; + for (i = 0; i < numWalls; ++i) + { + walltypedisk load; + if (mapver == 7) + { + Decrypt(&load, data, sizeof(walltypedisk), k); + } + else + { + memcpy(&load, data, sizeof(walltypedisk)); + } + // only copy what we need to draw the map preview. + + auto pWall = &lwalls[i]; + pWall->pos.X = LittleLong(load.x); + pWall->pos.Y = LittleLong(load.y); + pWall->point2 = LittleShort(load.point2); + pWall->nextwall = LittleShort(load.nextwall); + pWall->nextsector = LittleShort(load.nextsector); + + data += sizeof(walltypedisk); + if (load.extra > 0) // skip Xwall + { + data += 24; + } + } + +} + +TArray loadMapWalls(const char* filename) +{ + TArray lwalls; + FileReader fr = fileSystem.OpenFileReader(filename); + if (!fr.isOpen()) return lwalls; + + if (isBlood()) + { + auto data = fr.Read(); + P_LoadBloodMapWalls(data.Data(), data.Size(), lwalls); + return lwalls; + } + + + int mapversion = fr.ReadInt32(); + if (mapversion < 5 || mapversion > 9) return lwalls; + + fr.Seek(16, FileReader::SeekCur); + + // Get the basics out before loading the data so that we can set up the global storage. + unsigned numsectors = fr.ReadUInt16(); + fr.Seek((mapversion == 5 ? sectorsize5 : mapversion == 6 ? sectorsize6 : sectorsize7) * numsectors, FileReader::SeekCur); + unsigned numwalls = fr.ReadUInt16(); + + lwalls.Resize(numwalls); + for (unsigned i = 0; i < lwalls.Size(); i++) + { + switch (mapversion) + { + case 5: ReadWallV5(fr, lwalls[i]); break; + case 6: ReadWallV6(fr, lwalls[i]); break; + default: ReadWallV7(fr, lwalls[i]); break; + } + } + return lwalls; +} + + void qloadboard(const char* filename, char flags, vec3_t* dapos, int16_t* daang); diff --git a/source/core/maptypes.h b/source/core/maptypes.h index 5c2fd0a5b..5d86bd3e5 100644 --- a/source/core/maptypes.h +++ b/source/core/maptypes.h @@ -30,6 +30,7 @@ Prepared for public release: 03/21/2003 - Charlie Wiederhold, 3D Realms #include "ns.h" #include "tarray.h" #include "tflags.h" +#include "intvec.h" //============================================================================= // @@ -700,5 +701,6 @@ void allocateMapArrays(int numwall, int numsector, int numsprites); void validateSprite(spritetype& spr, int secno, int index); void fixSectors(); void loadMap(const char *filename, int flags, vec3_t *pos, int16_t *ang, int *cursectnum, SpawnSpriteDef& sprites); +TArray loadMapWalls(const char* filename); void loadMapBackup(const char* filename); void loadMapHack(const char* filename, const unsigned char*, SpawnSpriteDef& sprites); diff --git a/source/core/menu/usermap.cpp b/source/core/menu/usermap.cpp index 6b269a7f7..73ae1310e 100644 --- a/source/core/menu/usermap.cpp +++ b/source/core/menu/usermap.cpp @@ -47,6 +47,7 @@ #include "usermap.h" #include "gamecontrol.h" #include "mapinfo.h" +#include "v_draw.h" static FUsermapDirectory root; @@ -139,6 +140,25 @@ void ReadUserMaps() SortEntries(root); } +void LoadMapPreview(FUsermapEntry* entry) +{ + if (entry->wallsread) return; + entry->walls = loadMapWalls(entry->filename); +} + +void UnloadMapPreviews(FUsermapDirectory* dir) +{ + for (auto& entry : dir->entries) + { + if (entry.walls.Size() > 0) entry.wallsread = false; + entry.walls.Reset(); + } + for (auto& sub : dir->subdirectories) + { + UnloadMapPreviews(&sub); + } +} + DEFINE_FIELD(FUsermapEntry, filename); DEFINE_FIELD(FUsermapEntry, container); DEFINE_FIELD(FUsermapEntry, displayname); @@ -185,17 +205,51 @@ DEFINE_ACTION_FUNCTION(FUsermapDirectory, GetDirectory) ACTION_RETURN_POINTER(&self->subdirectories[num]); } -DEFINE_ACTION_FUNCTION(FUsermapDirectory, DrawPreview) +DEFINE_ACTION_FUNCTION(_UserMapMenu, DrawPreview) { - PARAM_SELF_STRUCT_PROLOGUE(FUsermapDirectory); - - PARAM_UINT(num); + PARAM_PROLOGUE; + PARAM_POINTER(entry, FUsermapEntry); PARAM_INT(left); PARAM_INT(top); PARAM_INT(width); PARAM_INT(height); - if (num >= self->entries.Size()) return 0; - // todo + if (!entry) return 0; + LoadMapPreview(entry); + if (entry->walls.Size() == 0) return 0; + int minx = INT_MAX, miny = INT_MAX, maxx = INT_MIN, maxy = INT_MIN; + for (auto& wal : entry->walls) + { + if (wal.pos.X < minx) minx = wal.pos.X; + if (wal.pos.X > maxx) maxx = wal.pos.X; + if (wal.pos.Y < miny) miny = wal.pos.Y; + if (wal.pos.Y > maxy) maxy = wal.pos.Y; + } + float scalex = float(width) / (maxx - minx); + float scaley = float(height) / (maxy - miny); + int centerx = (minx + maxx) >> 1; + int centery = (miny + maxy) >> 1; + int dcenterx = left + (width >> 1); + int dcentery = top + (height >> 1); + float scale = min(scalex, scaley); + float drawleft = dcenterx - (centerx - minx) * scale; + float drawtop = dcentery - (centery - miny) * scale; + + for (auto& wal : entry->walls) + { + if (wal.nextwall < 0) continue; + auto point2 = &entry->walls[wal.point2]; + twod->AddLine(dcenterx + (wal.pos.X - centerx) * scale, dcentery + (wal.pos.Y - centery) * scale, + dcenterx + (point2->pos.X - centerx) * scale, dcentery + (point2->pos.Y - centery) * scale, + -1, -1, INT_MAX, INT_MAX, 0xff808080); + } + for (auto& wal : entry->walls) + { + if (wal.nextwall >= 0) continue; + auto point2 = &entry->walls[wal.point2]; + twod->AddLine(dcenterx + (wal.pos.X - centerx) * scale, dcentery + (wal.pos.Y - centery) * scale, + dcenterx + (point2->pos.X - centerx) * scale, dcentery + (point2->pos.Y - centery) * scale, + -1, -1, INT_MAX, INT_MAX, 0xffffffff); + } return 0; } @@ -213,5 +267,6 @@ DEFINE_ACTION_FUNCTION(_UsermapMenu, StartMap) DoStartMap(entry->filename); M_ClearMenus(); + UnloadMapPreviews(&root); return 0; } diff --git a/source/core/menu/usermap.h b/source/core/menu/usermap.h index 2adcd6058..2bcb11e43 100644 --- a/source/core/menu/usermap.h +++ b/source/core/menu/usermap.h @@ -1,5 +1,6 @@ #pragma once +#include "maptypes.h" struct FUsermapEntry { @@ -7,7 +8,9 @@ struct FUsermapEntry FString container; FString filename; FString info; - int size; + int size = 0; + bool wallsread = false; + TArray walls; // for rendering a preview of the map }; struct FUsermapDirectory diff --git a/source/games/blood/src/db.cpp b/source/games/blood/src/db.cpp index bf2af7540..e76034bc5 100644 --- a/source/games/blood/src/db.cpp +++ b/source/games/blood/src/db.cpp @@ -119,58 +119,6 @@ const int nXSpriteSize = 56; const int nXWallSize = 24; -#pragma pack(push, 1) -// This is the on-disk format. Only Blood still needs this for its retarded encryption that has to read this in as a block so that it can be decoded. -// Keep it local so that the engine's sprite type is no longer limited by file format restrictions. -struct spritetypedisk -{ - int32_t x, y, z; - uint16_t cstat; - int16_t picnum; - int8_t shade; - uint8_t pal, clipdist, detail; - uint8_t xrepeat, yrepeat; - int8_t xoffset, yoffset; - int16_t sectnum, statnum; - int16_t ang, owner; - int16_t index, yvel, inittype; - int16_t type; - int16_t hitag; - int16_t extra; -}; - -struct sectortypedisk -{ - int16_t wallptr, wallnum; - int32_t ceilingz, floorz; - uint16_t ceilingstat, floorstat; - int16_t ceilingpicnum, ceilingheinum; - int8_t ceilingshade; - uint8_t ceilingpal, ceilingxpanning, ceilingypanning; - int16_t floorpicnum, floorheinum; - int8_t floorshade; - uint8_t floorpal, floorxpanning, floorypanning; - uint8_t visibility, fogpal; - int16_t type; - int16_t hitag; - int16_t extra; -}; - -struct walltypedisk -{ - int32_t x, y; - int16_t point2, nextwall, nextsector; - uint16_t cstat; - int16_t picnum, overpicnum; - int8_t shade; - uint8_t pal, xrepeat, yrepeat, xpanning, ypanning; - int16_t type; - int16_t hitag; - int16_t extra; -}; - -#pragma pack(pop) - void dbLoadMap(const char* pPath, int* pX, int* pY, int* pZ, short* pAngle, sectortype** ppSector, unsigned int* pCRC, BloodSpawnSpriteDef& sprites) { int16_t tpskyoff[256]; diff --git a/source/games/blood/src/mapstructs.h b/source/games/blood/src/mapstructs.h index 9bf21c6e5..007fc6706 100644 --- a/source/games/blood/src/mapstructs.h +++ b/source/games/blood/src/mapstructs.h @@ -2,8 +2,62 @@ #pragma once #include "ns.h" +#pragma pack(push, 1) +// This is the on-disk format. Only Blood still needs this for its retarded encryption that has to read this in as a block so that it can be decoded. +// Keep it local so that the engine's sprite type is no longer limited by file format restrictions. +struct spritetypedisk +{ + int32_t x, y, z; + uint16_t cstat; + int16_t picnum; + int8_t shade; + uint8_t pal, clipdist, detail; + uint8_t xrepeat, yrepeat; + int8_t xoffset, yoffset; + int16_t sectnum, statnum; + int16_t ang, owner; + int16_t index, yvel, inittype; + int16_t type; + int16_t hitag; + int16_t extra; +}; + +struct sectortypedisk +{ + int16_t wallptr, wallnum; + int32_t ceilingz, floorz; + uint16_t ceilingstat, floorstat; + int16_t ceilingpicnum, ceilingheinum; + int8_t ceilingshade; + uint8_t ceilingpal, ceilingxpanning, ceilingypanning; + int16_t floorpicnum, floorheinum; + int8_t floorshade; + uint8_t floorpal, floorxpanning, floorypanning; + uint8_t visibility, fogpal; + int16_t type; + int16_t hitag; + int16_t extra; +}; + +struct walltypedisk +{ + int32_t x, y; + int16_t point2, nextwall, nextsector; + uint16_t cstat; + int16_t picnum, overpicnum; + int8_t shade; + uint8_t pal, xrepeat, yrepeat, xpanning, ypanning; + int16_t type; + int16_t hitag; + int16_t extra; +}; + +#pragma pack(pop) + BEGIN_BLD_NS +TArray dbLoadMapWalls(const char* pPath); + class DBloodActor; struct AISTATE; diff --git a/wadsrc/static/zscript/usermapmenu.zs b/wadsrc/static/zscript/usermapmenu.zs index d3ed5ec49..c9b05ac76 100644 --- a/wadsrc/static/zscript/usermapmenu.zs +++ b/wadsrc/static/zscript/usermapmenu.zs @@ -53,7 +53,6 @@ struct UsermapDirectory native native int GetNumDirectories(); native UsermapEntry GetEntry(int num); native UsermapDirectory GetDirectory(int num); - native void DrawPreview(int num, int left, int top, int width, int height); String GetInfo(int Selected) { if (parent) Selected--; @@ -105,7 +104,7 @@ class UsermapMenu : ListMenu // private to this menu to prevent exploits. private native static void StartMap(UsermapEntry entry); - + private native static void DrawPreview(UsermapEntry entry, int left, int top, int width, int height); //============================================================================= // @@ -183,7 +182,13 @@ class UsermapMenu : ListMenu SetWindows(); DrawFrame(previewLeft, previewTop, previewWidth, previewHeight); screen.Dim(0, 0.6, previewLeft, previewTop, previewWidth, previewHeight); - currentDir.DrawPreview(Selected, previewLeft, previewTop, previewWidth, previewHeight); + + if (Selected >= numparent + numdirs) + { + let entry = currentDir.GetEntry(Selected - numparent - numdirs); + DrawPreview(entry, previewLeft, previewTop, previewWidth, previewHeight); + } + // Draw comment area DrawFrame (commentLeft, commentTop, commentWidth, commentHeight);