// "Build Engine & Tools" Copyright (c) 1993-1997 Ken Silverman // Ken Silverman's official web site: "http://www.advsys.net/ken" // See the included license file "BUILDLIC.TXT" for license info. // // This file has been modified from Ken Silverman's original release // by Jonathon Fowler (jf@jonof.id.au) // by the EDuke32 team (development@voidpoint.com) #include "compat.h" #include "build.h" #include "baselayer.h" #include "engine_priv.h" #include "cache1d.h" #include "lz4.h" #include "crc32.h" #include "vfs.h" void *pic = NULL; // The tile file number (tilesXXX <- this) of each tile: // 0 <= . < MAXARTFILES_BASE: tile is in a "base" ART file // MAXARTFILES_BASE <= . < MAXARTFILES_TOTAL: tile is in a map-specific ART file static uint8_t tilefilenum[MAXTILES]; EDUKE32_STATIC_ASSERT(MAXARTFILES_TOTAL <= 256); static int32_t tilefileoffs[MAXTILES]; // Backup tilefilenum[] and tilefileoffs[]. These get allocated only when // necessary (have per-map ART files). static uint8_t *g_bakTileFileNum; static int32_t *g_bakTileFileOffs; static vec2_16_t *g_bakTileSiz; static char *g_bakPicSiz; static char *g_bakWalock; static intptr_t *g_bakWaloff; static picanm_t *g_bakPicAnm; static char * g_bakFakeTile; static char ** g_bakFakeTileData; // NOTE: picsiz[] is not backed up, but recalculated when necessary. //static int32_t artsize = 0; static int32_t cachesize = 0; static char artfilename[BMAX_PATH]; static char artfilenameformat[BMAX_PATH]; static char mapartfilename[BMAX_PATH]; // map-specific ART file name static int32_t mapartfnXXofs; // byte offset to 'XX' (the number part) in the above static int32_t artfilnum, artfilplc; static buildvfs_kfd artfil; ////////// Per-map ART file loading ////////// // Some forward declarations. static const char *artGetIndexedFileName(int32_t tilefilei); static int32_t artReadIndexedFile(int32_t tilefilei); static inline void artClearMapArtFilename(void) { Bmemset(mapartfilename, 0, sizeof(mapartfilename)); mapartfnXXofs = 0; } static inline void artUpdateManifest(void) { for (bssize_t i=0; i static inline void RESTORE_MAPART_ARRAY(origar_t & origar, bakar_t & bakar) { EDUKE32_STATIC_ASSERT(sizeof(origar[0]) == sizeof(bakar[0])); Bmemcpy(origar, bakar, ARRAY_SIZE(origar) * sizeof(origar[0])); DO_FREE_AND_NULL(bakar); } template static inline void ALLOC_MAPART_ARRAY(origar_t & origar, bakar_t & bakar) { bakar = (bakar_t) Xmalloc(ARRAY_SIZE(origar) * sizeof(origar[0])); Bmemcpy(bakar, origar, ARRAY_SIZE(origar) * sizeof(origar[0])); } void artClearMapArt(void) { if (g_bakTileFileNum == NULL) return; // per-map ART N/A artClearMapArtFilename(); if (artfilnum >= MAXARTFILES_BASE) { kclose(artfil); artfil = buildvfs_kfd_invalid; artfilnum = -1; artfilplc = 0L; } for (bssize_t i=0; i= MAXARTFILES_BASE) { // XXX: OK way to free it? Better: cache1d API. CACHE1D_FREE walock[i] = 1; waloff[i] = 0; } } // Restore original per-tile arrays RESTORE_MAPART_ARRAY(tilefilenum, g_bakTileFileNum); RESTORE_MAPART_ARRAY(tilefileoffs, g_bakTileFileOffs); RESTORE_MAPART_ARRAY(tilesiz, g_bakTileSiz); RESTORE_MAPART_ARRAY(picsiz, g_bakPicSiz); RESTORE_MAPART_ARRAY(walock, g_bakWalock); RESTORE_MAPART_ARRAY(waloff, g_bakWaloff); RESTORE_MAPART_ARRAY(picanm, g_bakPicAnm); RESTORE_MAPART_ARRAY(faketile, g_bakFakeTile); for (size_t i = 0; i < MAXUSERTILES; ++i) { if (faketiledata[i] != g_bakFakeTileData[i]) { free(faketiledata[i]); faketiledata[i] = g_bakFakeTileData[i]; } } DO_FREE_AND_NULL(g_bakFakeTileData); artUpdateManifest(); #ifdef USE_OPENGL //POGOTODO: review this to ensure we're not invalidating more than we have to gltexinvalidatetype(INVALIDATE_ART); # ifdef POLYMER if (videoGetRenderMode() == REND_POLYMER) polymer_texinvalidate(); # endif #endif } void artSetupMapArt(const char *filename) { artClearMapArt(); if (Bstrlen(filename) + 7 >= sizeof(mapartfilename)) return; Bstrcpy(mapartfilename, filename); append_ext_UNSAFE(mapartfilename, "_XX.art"); mapartfnXXofs = Bstrlen(mapartfilename) - 6; // Check for first per-map ART file: if that one doesn't exist, don't load any. buildvfs_kfd fil = kopen4load(artGetIndexedFileName(MAXARTFILES_BASE), 0); if (fil == buildvfs_kfd_invalid) { artClearMapArtFilename(); return; } kclose(fil); // Allocate backup arrays. ALLOC_MAPART_ARRAY(tilefilenum, g_bakTileFileNum); ALLOC_MAPART_ARRAY(tilefileoffs, g_bakTileFileOffs); ALLOC_MAPART_ARRAY(tilesiz, g_bakTileSiz); ALLOC_MAPART_ARRAY(picsiz, g_bakPicSiz); ALLOC_MAPART_ARRAY(walock, g_bakWalock); ALLOC_MAPART_ARRAY(waloff, g_bakWaloff); ALLOC_MAPART_ARRAY(picanm, g_bakPicAnm); ALLOC_MAPART_ARRAY(faketile, g_bakFakeTile); ALLOC_MAPART_ARRAY(faketiledata, g_bakFakeTileData); for (bssize_t i=MAXARTFILES_BASE; i>3] |= pow2char[tile&7]; DO_FREE_AND_NULL(faketiledata[tile]); } static void tileSetDataSafe(int32_t const tile, int32_t tsiz, char const * const buffer) { int const compressed_tsiz = LZ4_compressBound(tsiz); char * newtile = (char *) Xmalloc(compressed_tsiz); if ((tsiz = LZ4_compress_default(buffer, newtile, tsiz, compressed_tsiz)) != -1) { faketiledata[tile] = (char *) Xrealloc(newtile, tsiz); faketile[tile>>3] |= pow2char[tile&7]; tilefilenum[tile] = MAXARTFILES_TOTAL; } else { free(newtile); } } void tileSetData(int32_t const tile, int32_t tsiz, char const * const buffer) { int const compressed_tsiz = LZ4_compressBound(tsiz); faketiledata[tile] = (char *) Xrealloc(faketiledata[tile], compressed_tsiz); if ((tsiz = LZ4_compress_default(buffer, faketiledata[tile], tsiz, compressed_tsiz)) != -1) { faketiledata[tile] = (char *) Xrealloc(faketiledata[tile], tsiz); faketile[tile>>3] |= pow2char[tile&7]; tilefilenum[tile] = MAXARTFILES_TOTAL; } else { DO_FREE_AND_NULL(faketiledata[tile]); faketile[tile>>3] &= ~pow2char[tile&7]; } } static void tileSoftDelete(int32_t const tile) { tilesiz[tile].x = 0; tilesiz[tile].y = 0; picsiz[tile] = 0; // CACHE1D_FREE walock[tile] = 1; waloff[tile] = 0; faketile[tile>>3] &= ~pow2char[tile&7]; Bmemset(&picanm[tile], 0, sizeof(picanm_t)); } void tileDelete(int32_t const tile) { tileSoftDelete(tile); DO_FREE_AND_NULL(faketiledata[tile]); vox_undefine(tile); #ifdef USE_OPENGL for (ssize_t i=MAXPALOOKUPS-1; i>=0; --i) hicclearsubst(tile, i); md_undefinetile(tile); #endif } void tileUpdatePicSiz(int32_t picnum) { int j = 15; while ((j > 1) && (pow2long[j] > tilesiz[picnum].x)) j--; picsiz[picnum] = j; j = 15; while ((j > 1) && (pow2long[j] > tilesiz[picnum].y)) j--; picsiz[picnum] |= j<<4; } void tileSetSize(int32_t picnum, int16_t dasizx, int16_t dasizy) { tilesiz[picnum].x = dasizx; tilesiz[picnum].y = dasizy; tileUpdatePicSiz(picnum); } int32_t artReadHeader(buildvfs_kfd const fil, char const * const fn, artheader_t * const local) { int32_t artversion; kread(fil, &artversion, 4); artversion = B_LITTLE32(artversion); if (artversion == B_LITTLE32(0x4c495542)) { kread(fil, &artversion, 4); artversion = B_LITTLE32(artversion); if (artversion == B_LITTLE32(0x54524144)) { kread(fil, &artversion, 4); artversion = B_LITTLE32(artversion); } else { initprintf("loadpics: Invalid art file, %s\n", fn); kclose(fil); return 1; } } if (artversion != 1) { initprintf("loadpics: Invalid art file version in %s\n", fn); kclose(fil); return 1; } int32_t numtiles_dummy; kread(fil, &numtiles_dummy, 4); kread(fil, &local->tilestart, 4); local->tilestart = B_LITTLE32(local->tilestart); kread(fil, &local->tileend, 4); local->tileend = B_LITTLE32(local->tileend); if ((uint32_t) local->tilestart >= MAXUSERTILES || (uint32_t) local->tileend >= MAXUSERTILES) { initprintf("loadpics: Invalid localtilestart or localtileend in %s\n", fn); kclose(fil); return 1; } if (local->tileend < local->tilestart) { initprintf("loadpics: localtileend < localtilestart in %s\n", fn); kclose(fil); return 1; } local->numtiles = (local->tileend-local->tilestart+1); return 0; } int32_t artReadHeaderFromBuffer(uint8_t const * const buf, artheader_t * const local) { int const artversion = B_LITTLE32(B_UNBUF32(&buf[0])); if (EDUKE32_PREDICT_FALSE(artversion != 1)) { initprintf("loadpics: Invalid art file version\n"); return 1; } local->tilestart = B_LITTLE32(B_UNBUF32(&buf[8])); local->tileend = B_LITTLE32(B_UNBUF32(&buf[12])); if (EDUKE32_PREDICT_FALSE((unsigned) local->tilestart >= MAXUSERTILES || (unsigned) local->tileend >= MAXUSERTILES)) { initprintf("loadpics: Invalid localtilestart or localtileend\n"); return 1; } if (EDUKE32_PREDICT_FALSE(local->tileend < local->tilestart)) { initprintf("loadpics: localtileend < localtilestart\n"); return 1; } local->numtiles = (local->tileend-local->tilestart+1); return 0; } int32_t artCheckUnitFileHeader(uint8_t const * const buf, int32_t length) { if (EDUKE32_PREDICT_FALSE(length <= ARTv1_UNITOFFSET)) return -1; artheader_t local; if (EDUKE32_PREDICT_FALSE(artReadHeaderFromBuffer(buf, &local) != 0)) return -2; if (EDUKE32_PREDICT_FALSE(local.numtiles != 1)) return -3; return 0; } void tileConvertAnimFormat(int32_t const picnum, int32_t const picanmdisk) { EDUKE32_STATIC_ASSERT(PICANM_ANIMTYPE_MASK == 192); // Unpack a 4 byte packed anim descriptor into the internal 5 byte format. picanm_t* const thispicanm = &picanm[picnum]; thispicanm->num = picanmdisk & 63; thispicanm->xofs = (picanmdisk >> 8) & 255; thispicanm->yofs = (picanmdisk >> 16) & 255; thispicanm->sf = ((picanmdisk >> 24) & 15) | (picanmdisk & 192); thispicanm->extra = (picanmdisk >> 28) & 15; } void artReadManifest(int32_t const fil, artheader_t const* const local) { int16_t* tilesizx = (int16_t*)Xmalloc(local->numtiles * sizeof(int16_t)); int16_t* tilesizy = (int16_t*)Xmalloc(local->numtiles * sizeof(int16_t)); kread(fil, tilesizx, local->numtiles * sizeof(int16_t)); kread(fil, tilesizy, local->numtiles * sizeof(int16_t)); for (bssize_t i = local->tilestart; i <= local->tileend; i++) { int32_t picanmdisk; tilesiz[i].x = B_LITTLE16(tilesizx[i - local->tilestart]); tilesiz[i].y = B_LITTLE16(tilesizy[i - local->tilestart]); kread(fil, &picanmdisk, sizeof(int32_t)); picanmdisk = B_LITTLE32(picanmdisk); tileConvertAnimFormat(i, picanmdisk); } DO_FREE_AND_NULL(tilesizx); DO_FREE_AND_NULL(tilesizy); } void artPreloadFile(buildvfs_kfd const fil, artheader_t const * const local) { char *buffer = NULL; int32_t buffersize = 0; for (bssize_t i=local->tilestart; i<=local->tileend; i++) { int const dasiz = tilesiz[i].x * tilesiz[i].y; if (dasiz == 0) { tileDelete(i); continue; } maybe_grow_buffer(&buffer, &buffersize, dasiz); kread(fil, buffer, dasiz); tileSetData(i, dasiz, buffer); } DO_FREE_AND_NULL(buffer); } static void artPreloadFileSafe(buildvfs_kfd const fil, artheader_t const * const local) { char *buffer = NULL; int32_t buffersize = 0; for (bssize_t i=local->tilestart; i<=local->tileend; i++) { int const dasiz = tilesiz[i].x * tilesiz[i].y; if (dasiz == 0) { tileSoftDelete(i); continue; } maybe_grow_buffer(&buffer, &buffersize, dasiz); kread(fil, buffer, dasiz); tileSetDataSafe(i, dasiz, buffer); } DO_FREE_AND_NULL(buffer); } static const char *artGetIndexedFileName(int32_t tilefilei) { if (tilefilei >= MAXARTFILES_BASE) { int32_t o = mapartfnXXofs; tilefilei -= MAXARTFILES_BASE; mapartfilename[o+1] = '0' + tilefilei%10; mapartfilename[o+0] = '0' + (tilefilei/10)%10; return mapartfilename; } else { Bsnprintf(artfilename, sizeof(artfilename), artfilenameformat, tilefilei); return artfilename; } } // Returns: // 0: successfully read ART file // >0: error with the ART file // -1: ART file does not exist //<-1: per-map ART issue static int32_t artReadIndexedFile(int32_t tilefilei) { const char *fn = artGetIndexedFileName(tilefilei); const int32_t permap = (tilefilei >= MAXARTFILES_BASE); // is it a per-map ART file? buildvfs_kfd fil; auto kopen4loadfunc = bloodhack == 2 ? kopen4loadfrommod : kopen4load; if ((fil = kopen4loadfunc(fn, 0)) != buildvfs_kfd_invalid) { artheader_t local; int const headerval = artReadHeader(fil, fn, &local); if (headerval != 0) { kclose(fil); return headerval; } if (permap) { // Check whether we can evict existing tiles to make place for // per-map ART ones. for (int i=local.tilestart; i<=local.tileend; i++) { // Tiles having dummytile replacements or those that are // cache1d-locked can't be replaced. if (faketile[i>>3] & pow2char[i&7] || walock[i] >= 200) { initprintf("loadpics: per-map ART file \"%s\": " "tile %d has dummytile or is locked\n", fn, i); kclose(fil); return -3; } } // Free existing tiles from the cache1d. CACHE1D_FREE Bmemset(&waloff[local.tilestart], 0, local.numtiles*sizeof(intptr_t)); Bmemset(&walock[local.tilestart], 1, local.numtiles*sizeof(walock[0])); } artReadManifest(fil, &local); #ifndef USE_PHYSFS if (cache1d_file_fromzip(fil)) #else if (1) #endif { if (permap) artPreloadFileSafe(fil, &local); else artPreloadFile(fil, &local); } else { int offscount = ktell(fil); for (bssize_t i=local.tilestart; i<=local.tileend; ++i) { int const dasiz = tilesiz[i].x * tilesiz[i].y; tilefilenum[i] = tilefilei; tilefileoffs[i] = offscount; offscount += dasiz; // artsize += ((dasiz+15)&0xfffffff0); } } #ifdef DEBUGGINGAIDS if (permap) initprintf("Read in per-map ART file \"%s\"\n", fn); #endif kclose(fil); return 0; } return -1; } // // loadpics // int32_t artLoadFiles(const char *filename, int32_t askedsize) { Bstrncpyz(artfilenameformat, filename, sizeof(artfilenameformat)); Bmemset(&tilesiz[0], 0, sizeof(vec2_16_t) * MAXTILES); Bmemset(picanm, 0, sizeof(picanm)); for (auto &rot : rottile) rot = { -1, -1 }; // artsize = 0; for (int tilefilei=0; tilefilei= (unsigned) MAXTILES) return 0; int const dasiz = tilesiz[tileNum].x*tilesiz[tileNum].y; if (dasiz <= 0) return 0; // Allocate storage if necessary. if (waloff[tileNum] == 0) { walock[tileNum] = 199; cacheAllocateBlock(&waloff[tileNum], dasiz, &walock[tileNum]); } tileLoadData(tileNum, dasiz, (char *) waloff[tileNum]); /* #ifdef USE_OPENGL if (videoGetRenderMode() >= REND_POLYMOST && in3dmode()) { //POGOTODO: this type stuff won't be necessary down the line -- review this int type; for (type = 0; type <= 1; ++type) { gltexinvalidate(tileNum, 0, (type ? DAMETH_CLAMPED : DAMETH_MASK) | PTH_INDEXED); texcache_fetch(tileNum, 0, 0, (type ? DAMETH_CLAMPED : DAMETH_MASK) | PTH_INDEXED); } } #endif */ tilePostLoad(tileNum); return (waloff[tileNum] != 0 && tilesiz[tileNum].x > 0 && tilesiz[tileNum].y > 0); } void tileMaybeRotate(int16_t tilenume) { auto &rot = rottile[tilenume]; auto &siz = tilesiz[rot.owner]; auto src = (char *)waloff[rot.owner]; auto dst = (char *)waloff[tilenume]; // the engine has a squarerotatetile() we could call, but it mirrors at the same time for (int x = 0; x < siz.x; ++x) { int const xofs = siz.x - x - 1; int const yofs = siz.y * x; for (int y = 0; y < siz.y; ++y) *(dst + y * siz.x + xofs) = *(src + y + yofs); } tileSetSize(tilenume, siz.y, siz.x); } void tileLoadData(int16_t tilenume, int32_t dasiz, char *buffer) { int const owner = rottile[tilenume].owner; if (owner != -1) { if (!waloff[owner]) tileLoad(owner); if (waloff[tilenume]) tileMaybeRotate(tilenume); return; } int const tfn = tilefilenum[tilenume]; // dummy tiles for highres replacements and tilefromtexture definitions if (faketile[tilenume>>3] & pow2char[tilenume&7]) { if (faketiledata[tilenume] != NULL) LZ4_decompress_fast(faketiledata[tilenume], buffer, dasiz); faketimerhandler(); return; } // Potentially switch open ART file. if (tfn != artfilnum) { if (artfil != buildvfs_kfd_invalid) kclose(artfil); char const *fn = artGetIndexedFileName(tfn); auto kopen4loadfunc = bloodhack == 2 ? kopen4loadfrommod : kopen4load; artfil = kopen4loadfunc(fn, 0); if (artfil == buildvfs_kfd_invalid) { initprintf("Failed opening ART file \"%s\"!\n", fn); engineUnInit(); Bexit(11); } artfilnum = tfn; artfilplc = 0L; faketimerhandler(); } // Seek to the right position. if (artfilplc != tilefileoffs[tilenume]) { klseek(artfil, tilefileoffs[tilenume], BSEEK_SET); faketimerhandler(); } kread(artfil, buffer, dasiz); faketimerhandler(); artfilplc = tilefileoffs[tilenume]+dasiz; } static void tilePostLoad(int16_t tilenume) { #if !defined DEBUG_TILESIZY_512 && !defined DEBUG_TILEOFFSETS UNREFERENCED_PARAMETER(tilenume); #endif #ifdef DEBUG_TILESIZY_512 if (tilesizy[tilenume] >= 512) { int32_t i; char *p = (char *) waloff[tilenume]; for (i=0; i= (unsigned)MAXTILES) return 0; int const dasiz = tilesiz[tileNum].x * tilesiz[tileNum].y; if (dasiz <= 0) return 0; data = (char *)Xmalloc(dasiz); tileLoadData(tileNum, dasiz, data); int32_t crc = Bcrc32((unsigned char *)data, (unsigned int)dasiz, 0); Xfree(data); return crc; } // Assumes pic has been initialized to zero. void artConvertRGB(palette_t * const pic, uint8_t const * const buf, int32_t const bufsizx, int32_t const sizx, int32_t const sizy) { for (bssize_t y = 0; y < sizy; ++y) { palette_t * const picrow = &pic[bufsizx * y]; for (bssize_t x = 0; x < sizx; ++x) { uint8_t index = buf[sizy * x + y]; if (index == 255) continue; index *= 3; // pic is BGRA picrow[x].r = palette[index+2]; picrow[x].g = palette[index+1]; picrow[x].b = palette[index]; picrow[x].f = 255; } } } // // allocatepermanenttile // intptr_t tileCreate(int16_t tilenume, int32_t xsiz, int32_t ysiz) { if (xsiz <= 0 || ysiz <= 0 || (unsigned) tilenume >= MAXTILES) return 0; int const dasiz = xsiz*ysiz; walock[tilenume] = 255; cacheAllocateBlock(&waloff[tilenume], dasiz, &walock[tilenume]); tileSetSize(tilenume, xsiz, ysiz); Bmemset(&picanm[tilenume], 0, sizeof(picanm_t)); return waloff[tilenume]; } // // copytilepiece // void tileCopySection(int32_t tilenume1, int32_t sx1, int32_t sy1, int32_t xsiz, int32_t ysiz, int32_t tilenume2, int32_t sx2, int32_t sy2) { char *ptr1, *ptr2, dat; int32_t xsiz1, ysiz1, xsiz2, ysiz2, i, j, x1, y1, x2, y2; xsiz1 = tilesiz[tilenume1].x; ysiz1 = tilesiz[tilenume1].y; xsiz2 = tilesiz[tilenume2].x; ysiz2 = tilesiz[tilenume2].y; if ((xsiz1 > 0) && (ysiz1 > 0) && (xsiz2 > 0) && (ysiz2 > 0)) { if (waloff[tilenume1] == 0) tileLoad(tilenume1); if (waloff[tilenume2] == 0) tileLoad(tilenume2); x1 = sx1; for (i=0; i= 0) && (y2 >= 0) && (x2 < xsiz2) && (y2 < ysiz2)) { ptr1 = (char *) (waloff[tilenume1] + x1*ysiz1 + y1); ptr2 = (char *) (waloff[tilenume2] + x2*ysiz2 + y2); dat = *ptr1; if (dat != 255) *ptr2 = *ptr1; } y1++; if (y1 >= ysiz1) y1 = 0; } x1++; if (x1 >= xsiz1) x1 = 0; } } if (playing_rr) tileInvalidate(tilenume2, -1, -1); } void Buninitart(void) { if (artfil != buildvfs_kfd_invalid) kclose(artfil); ALIGNED_FREE_AND_NULL(pic); }