fallout2-ce/src/art.cc

1282 lines
33 KiB
C++
Raw Normal View History

2022-05-19 01:51:26 -07:00
#include "art.h"
2022-09-15 02:38:23 -07:00
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
2022-05-19 01:51:26 -07:00
#include "animation.h"
#include "debug.h"
#include "draw.h"
#include "game.h"
2022-05-19 01:51:26 -07:00
#include "memory.h"
#include "object.h"
#include "proto.h"
#include "settings.h"
2022-06-08 12:01:25 -07:00
#include "sfall_config.h"
2022-05-19 01:51:26 -07:00
2022-09-23 05:43:44 -07:00
namespace fallout {
2022-06-18 23:02:26 -07:00
typedef struct ArtListDescription {
int flags;
char name[16];
char* fileNames; // dynamic array of null terminated strings 13 bytes long each
void* field_18;
int fileNamesLength; // number of entries in list
} ArtListDescription;
typedef struct HeadDescription {
int goodFidgetCount;
int neutralFidgetCount;
int badFidgetCount;
} HeadDescription;
static int artReadList(const char* path, char** out_arr, int* out_count);
static int artCacheGetFileSizeImpl(int a1, int* out_size);
static int artCacheReadDataImpl(int a1, int* a2, unsigned char* data);
static void artCacheFreeImpl(void* ptr);
2023-01-19 09:27:22 -08:00
static int artReadFrameData(unsigned char* data, File* stream, int count, int* paddingPtr);
2022-06-18 23:02:26 -07:00
static int artReadHeader(Art* art, File* stream);
2023-01-19 09:27:22 -08:00
static int artGetDataSize(Art* art);
static int paddingForSize(int size);
2022-06-18 23:02:26 -07:00
2022-06-08 12:01:25 -07:00
// 0x5002D8
2022-06-18 23:02:26 -07:00
static char gDefaultJumpsuitMaleFileName[] = "hmjmps";
2022-06-08 12:01:25 -07:00
// 0x05002E0
2022-06-18 23:02:26 -07:00
static char gDefaultJumpsuitFemaleFileName[] = "hfjmps";
2022-06-08 12:01:25 -07:00
// 0x5002E8
2022-06-18 23:02:26 -07:00
static char gDefaultTribalMaleFileName[] = "hmwarr";
2022-06-08 12:01:25 -07:00
// 0x5002F0
2022-06-18 23:02:26 -07:00
static char gDefaultTribalFemaleFileName[] = "hfprim";
2022-06-08 12:01:25 -07:00
2022-05-19 01:51:26 -07:00
// 0x510738
2022-06-18 23:02:26 -07:00
static ArtListDescription gArtListDescriptions[OBJ_TYPE_COUNT] = {
2022-05-19 01:51:26 -07:00
{ 0, "items", 0, 0, 0 },
{ 0, "critters", 0, 0, 0 },
{ 0, "scenery", 0, 0, 0 },
{ 0, "walls", 0, 0, 0 },
{ 0, "tiles", 0, 0, 0 },
{ 0, "misc", 0, 0, 0 },
{ 0, "intrface", 0, 0, 0 },
{ 0, "inven", 0, 0, 0 },
{ 0, "heads", 0, 0, 0 },
{ 0, "backgrnd", 0, 0, 0 },
{ 0, "skilldex", 0, 0, 0 },
};
// This flag denotes that localized arts should be looked up first. Used
// together with [gArtLanguage].
//
// 0x510898
2022-06-18 23:02:26 -07:00
static bool gArtLanguageInitialized = false;
2022-05-19 01:51:26 -07:00
// 0x51089C
2022-06-18 23:02:26 -07:00
static const char* _head1 = "gggnnnbbbgnb";
2022-05-19 01:51:26 -07:00
// 0x5108A0
2022-06-18 23:02:26 -07:00
static const char* _head2 = "vfngfbnfvppp";
2022-05-19 01:51:26 -07:00
// Current native look base fid.
//
// 0x5108A4
int _art_vault_guy_num = 0;
// Base fids for unarmored dude.
//
// Outfit file names:
// - tribal: "hmwarr", "hfprim"
// - jumpsuit: "hmjmps", "hfjmps"
//
// NOTE: This value could have been done with two separate arrays - one for
// tribal look, and one for jumpsuit look. However in this case it would have
// been accessed differently in 0x49F984, which clearly uses look type as an
// index, not gender.
//
// 0x5108A8
int _art_vault_person_nums[DUDE_NATIVE_LOOK_COUNT][GENDER_COUNT];
// Index of "grid001.frm" in tiles.lst.
//
// 0x5108B8
2022-06-18 23:02:26 -07:00
static int _art_mapper_blank_tile = 1;
2022-05-19 01:51:26 -07:00
// Non-english language name.
//
// This value is used as a directory name to display localized arts.
//
// 0x56C970
2022-06-18 23:02:26 -07:00
static char gArtLanguage[32];
2022-05-19 01:51:26 -07:00
// 0x56C990
Cache gArtCache;
// 0x56C9E4
2022-06-18 23:02:26 -07:00
static char _art_name[COMPAT_MAX_PATH];
2022-05-19 01:51:26 -07:00
// head_info
// 0x56CAE8
2022-06-18 23:02:26 -07:00
static HeadDescription* gHeadDescriptions;
2022-05-19 01:51:26 -07:00
// anon_alias
// 0x56CAEC
2022-06-18 23:02:26 -07:00
static int* _anon_alias;
2022-05-19 01:51:26 -07:00
// artCritterFidShouldRunData
// 0x56CAF0
2022-06-18 23:02:26 -07:00
static int* gArtCritterFidShoudRunData;
2022-05-19 01:51:26 -07:00
// 0x418840
int artInit()
{
2022-05-28 02:34:49 -07:00
char path[COMPAT_MAX_PATH];
2022-05-19 01:51:26 -07:00
File* stream;
char string[200];
2022-05-19 01:51:26 -07:00
int cacheSize = settings.system.art_cache_size;
2022-05-19 01:51:26 -07:00
if (!cacheInit(&gArtCache, artCacheGetFileSizeImpl, artCacheReadDataImpl, artCacheFreeImpl, cacheSize << 20)) {
debugPrint("cache_init failed in art_init\n");
return -1;
}
const char* language = settings.system.language.c_str();
if (compat_stricmp(language, ENGLISH) != 0) {
2022-05-19 01:51:26 -07:00
strcpy(gArtLanguage, language);
gArtLanguageInitialized = true;
}
2022-08-16 01:25:56 -07:00
bool critterDbSelected = false;
for (int objectType = 0; objectType < OBJ_TYPE_COUNT; objectType++) {
gArtListDescriptions[objectType].flags = 0;
2022-12-08 12:05:50 -08:00
snprintf(path, sizeof(path), "%s%s%s\\%s.lst", _cd_path_base, "art\\", gArtListDescriptions[objectType].name, gArtListDescriptions[objectType].name);
2022-05-19 01:51:26 -07:00
if (artReadList(path, &(gArtListDescriptions[objectType].fileNames), &(gArtListDescriptions[objectType].fileNamesLength)) != 0) {
2022-05-19 01:51:26 -07:00
debugPrint("art_read_lst failed in art_init\n");
cacheFree(&gArtCache);
return -1;
}
}
_anon_alias = (int*)internal_malloc(sizeof(*_anon_alias) * gArtListDescriptions[OBJ_TYPE_CRITTER].fileNamesLength);
2022-05-19 01:51:26 -07:00
if (_anon_alias == NULL) {
gArtListDescriptions[OBJ_TYPE_CRITTER].fileNamesLength = 0;
2022-05-19 01:51:26 -07:00
debugPrint("Out of memory for anon_alias in art_init\n");
cacheFree(&gArtCache);
return -1;
}
2022-05-21 08:22:03 -07:00
gArtCritterFidShoudRunData = (int*)internal_malloc(sizeof(*gArtCritterFidShoudRunData) * gArtListDescriptions[1].fileNamesLength);
2022-05-19 01:51:26 -07:00
if (gArtCritterFidShoudRunData == NULL) {
gArtListDescriptions[OBJ_TYPE_CRITTER].fileNamesLength = 0;
2022-05-19 01:51:26 -07:00
debugPrint("Out of memory for artCritterFidShouldRunData in art_init\n");
cacheFree(&gArtCache);
return -1;
}
for (int critterIndex = 0; critterIndex < gArtListDescriptions[OBJ_TYPE_CRITTER].fileNamesLength; critterIndex++) {
gArtCritterFidShoudRunData[critterIndex] = 0;
2022-05-19 01:51:26 -07:00
}
2022-12-08 12:05:50 -08:00
snprintf(path, sizeof(path), "%s%s%s\\%s.lst", _cd_path_base, "art\\", gArtListDescriptions[OBJ_TYPE_CRITTER].name, gArtListDescriptions[OBJ_TYPE_CRITTER].name);
2022-05-19 01:51:26 -07:00
stream = fileOpen(path, "rt");
if (stream == NULL) {
debugPrint("Unable to open %s in art_init\n", path);
cacheFree(&gArtCache);
return -1;
}
2022-06-08 13:15:30 -07:00
// SFALL: Modify player model settings.
2022-06-08 12:01:25 -07:00
char* jumpsuitMaleFileName = NULL;
configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_DUDE_NATIVE_LOOK_JUMPSUIT_MALE_KEY, &jumpsuitMaleFileName);
2022-06-08 13:15:30 -07:00
if (jumpsuitMaleFileName == NULL || jumpsuitMaleFileName[0] == '\0') {
2022-06-08 12:01:25 -07:00
jumpsuitMaleFileName = gDefaultJumpsuitMaleFileName;
}
char* jumpsuitFemaleFileName = NULL;
configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_DUDE_NATIVE_LOOK_JUMPSUIT_FEMALE_KEY, &jumpsuitFemaleFileName);
if (jumpsuitFemaleFileName == NULL || jumpsuitFemaleFileName[0] == '\0') {
jumpsuitFemaleFileName = gDefaultJumpsuitFemaleFileName;
}
char* tribalMaleFileName = NULL;
configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_DUDE_NATIVE_LOOK_TRIBAL_MALE_KEY, &tribalMaleFileName);
if (tribalMaleFileName == NULL || tribalMaleFileName[0] == '\0') {
tribalMaleFileName = gDefaultTribalMaleFileName;
}
char* tribalFemaleFileName = NULL;
2022-06-08 12:01:25 -07:00
configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_DUDE_NATIVE_LOOK_TRIBAL_FEMALE_KEY, &tribalFemaleFileName);
if (tribalFemaleFileName == NULL || tribalFemaleFileName[0] == '\0') {
tribalFemaleFileName = gDefaultTribalFemaleFileName;
}
char* critterFileNames = gArtListDescriptions[OBJ_TYPE_CRITTER].fileNames;
for (int critterIndex = 0; critterIndex < gArtListDescriptions[OBJ_TYPE_CRITTER].fileNamesLength; critterIndex++) {
2022-11-08 23:21:12 -08:00
if (compat_stricmp(critterFileNames, jumpsuitMaleFileName) == 0) {
_art_vault_person_nums[DUDE_NATIVE_LOOK_JUMPSUIT][GENDER_MALE] = critterIndex;
2022-11-08 23:21:12 -08:00
} else if (compat_stricmp(critterFileNames, jumpsuitFemaleFileName) == 0) {
_art_vault_person_nums[DUDE_NATIVE_LOOK_JUMPSUIT][GENDER_FEMALE] = critterIndex;
2022-05-19 01:51:26 -07:00
}
2022-11-08 23:21:12 -08:00
if (compat_stricmp(critterFileNames, tribalMaleFileName) == 0) {
_art_vault_person_nums[DUDE_NATIVE_LOOK_TRIBAL][GENDER_MALE] = critterIndex;
_art_vault_guy_num = critterIndex;
2022-11-08 23:21:12 -08:00
} else if (compat_stricmp(critterFileNames, tribalFemaleFileName) == 0) {
_art_vault_person_nums[DUDE_NATIVE_LOOK_TRIBAL][GENDER_FEMALE] = critterIndex;
2022-05-19 01:51:26 -07:00
}
critterFileNames += 13;
2022-05-19 01:51:26 -07:00
}
for (int critterIndex = 0; critterIndex < gArtListDescriptions[OBJ_TYPE_CRITTER].fileNamesLength; critterIndex++) {
if (!fileReadString(string, sizeof(string), stream)) {
2022-05-19 01:51:26 -07:00
break;
}
char* sep1 = strchr(string, ',');
if (sep1 != NULL) {
_anon_alias[critterIndex] = atoi(sep1 + 1);
2022-05-19 01:51:26 -07:00
char* sep2 = strchr(sep1 + 1, ',');
if (sep2 != NULL) {
gArtCritterFidShoudRunData[critterIndex] = atoi(sep2 + 1);
} else {
gArtCritterFidShoudRunData[critterIndex] = 0;
2022-05-19 01:51:26 -07:00
}
} else {
_anon_alias[critterIndex] = _art_vault_guy_num;
gArtCritterFidShoudRunData[critterIndex] = 1;
2022-05-19 01:51:26 -07:00
}
}
fileClose(stream);
char* tileFileNames = gArtListDescriptions[OBJ_TYPE_TILE].fileNames;
for (int tileIndex = 0; tileIndex < gArtListDescriptions[OBJ_TYPE_TILE].fileNamesLength; tileIndex++) {
if (compat_stricmp(tileFileNames, "grid001.frm") == 0) {
_art_mapper_blank_tile = tileIndex;
2022-05-19 01:51:26 -07:00
}
tileFileNames += 13;
2022-05-19 01:51:26 -07:00
}
gHeadDescriptions = (HeadDescription*)internal_malloc(sizeof(*gHeadDescriptions) * gArtListDescriptions[OBJ_TYPE_HEAD].fileNamesLength);
2022-05-19 01:51:26 -07:00
if (gHeadDescriptions == NULL) {
gArtListDescriptions[OBJ_TYPE_HEAD].fileNamesLength = 0;
2022-05-19 01:51:26 -07:00
debugPrint("Out of memory for head_info in art_init\n");
cacheFree(&gArtCache);
return -1;
}
2022-12-08 12:05:50 -08:00
snprintf(path, sizeof(path), "%s%s%s\\%s.lst", _cd_path_base, "art\\", gArtListDescriptions[OBJ_TYPE_HEAD].name, gArtListDescriptions[OBJ_TYPE_HEAD].name);
2022-05-19 01:51:26 -07:00
stream = fileOpen(path, "rt");
if (stream == NULL) {
debugPrint("Unable to open %s in art_init\n", path);
cacheFree(&gArtCache);
return -1;
}
for (int headIndex = 0; headIndex < gArtListDescriptions[OBJ_TYPE_HEAD].fileNamesLength; headIndex++) {
if (!fileReadString(string, sizeof(string), stream)) {
2022-05-19 01:51:26 -07:00
break;
}
char* sep1 = strchr(string, ',');
if (sep1 != NULL) {
*sep1 = '\0';
} else {
sep1 = string;
2022-05-19 01:51:26 -07:00
}
char* sep2 = strchr(sep1, ',');
if (sep2 != NULL) {
*sep2 = '\0';
} else {
sep2 = sep1;
}
2022-05-19 01:51:26 -07:00
gHeadDescriptions[headIndex].goodFidgetCount = atoi(sep1 + 1);
2022-05-19 01:51:26 -07:00
char* sep3 = strchr(sep2, ',');
if (sep3 != NULL) {
*sep3 = '\0';
} else {
sep3 = sep2;
}
2022-05-19 01:51:26 -07:00
gHeadDescriptions[headIndex].neutralFidgetCount = atoi(sep2 + 1);
2022-05-19 01:51:26 -07:00
char* sep4 = strpbrk(sep3 + 1, " ,;\t\n");
if (sep4 != NULL) {
*sep4 = '\0';
2022-05-19 01:51:26 -07:00
}
gHeadDescriptions[headIndex].badFidgetCount = atoi(sep3 + 1);
2022-05-19 01:51:26 -07:00
}
fileClose(stream);
return 0;
}
// 0x418EB8
void artReset()
{
}
// 0x418EBC
void artExit()
{
cacheFree(&gArtCache);
internal_free(_anon_alias);
internal_free(gArtCritterFidShoudRunData);
for (int index = 0; index < OBJ_TYPE_COUNT; index++) {
internal_free(gArtListDescriptions[index].fileNames);
gArtListDescriptions[index].fileNames = NULL;
internal_free(gArtListDescriptions[index].field_18);
gArtListDescriptions[index].field_18 = NULL;
}
internal_free(gHeadDescriptions);
}
// 0x418F1C
char* artGetObjectTypeName(int objectType)
{
return objectType >= OBJ_TYPE_ITEM && objectType < OBJ_TYPE_COUNT ? gArtListDescriptions[objectType].name : NULL;
2022-05-19 01:51:26 -07:00
}
// 0x418F34
int artIsObjectTypeHidden(int objectType)
{
return objectType >= OBJ_TYPE_ITEM && objectType < OBJ_TYPE_COUNT ? gArtListDescriptions[objectType].flags & 1 : 0;
2022-05-19 01:51:26 -07:00
}
// 0x418F7C
int artGetFidgetCount(int headFid)
{
if (FID_TYPE(headFid) != OBJ_TYPE_HEAD) {
2022-05-19 01:51:26 -07:00
return 0;
}
int head = headFid & 0xFFF;
if (head > gArtListDescriptions[OBJ_TYPE_HEAD].fileNamesLength) {
return 0;
}
HeadDescription* headDescription = &(gHeadDescriptions[head]);
int fidget = (headFid & 0xFF0000) >> 16;
switch (fidget) {
case FIDGET_GOOD:
return headDescription->goodFidgetCount;
case FIDGET_NEUTRAL:
return headDescription->neutralFidgetCount;
case FIDGET_BAD:
return headDescription->badFidgetCount;
}
return 0;
}
// 0x418FFC
void artRender(int fid, unsigned char* dest, int width, int height, int pitch)
{
// NOTE: Original code is different. For unknown reason it directly calls
// many art functions, for example instead of [artLock] it calls lower level
// [cacheLock], instead of [artGetWidth] is calls [artGetFrame], then get
// width from frame's struct field. I don't know if this was intentional or
// not. I've replaced these calls with higher level functions where
// appropriate.
CacheEntry* handle;
Art* frm = artLock(fid, &handle);
if (frm == NULL) {
return;
}
unsigned char* frameData = artGetFrameData(frm, 0, 0);
int frameWidth = artGetWidth(frm, 0, 0);
int frameHeight = artGetHeight(frm, 0, 0);
int remainingWidth = width - frameWidth;
int remainingHeight = height - frameHeight;
if (remainingWidth < 0 || remainingHeight < 0) {
if (height * frameWidth >= width * frameHeight) {
blitBufferToBufferStretchTrans(frameData,
frameWidth,
frameHeight,
frameWidth,
dest + pitch * ((height - width * frameHeight / frameWidth) / 2),
width,
width * frameHeight / frameWidth,
pitch);
} else {
blitBufferToBufferStretchTrans(frameData,
frameWidth,
frameHeight,
frameWidth,
dest + (width - height * frameWidth / frameHeight) / 2,
height * frameWidth / frameHeight,
height,
pitch);
}
} else {
blitBufferToBufferTrans(frameData,
frameWidth,
frameHeight,
frameWidth,
dest + pitch * (remainingHeight / 2) + remainingWidth / 2,
pitch);
}
artUnlock(handle);
}
// 0x419160
Art* artLock(int fid, CacheEntry** handlePtr)
{
if (handlePtr == NULL) {
return NULL;
}
Art* art = NULL;
cacheLock(&gArtCache, fid, (void**)&art, handlePtr);
return art;
}
// 0x419188
unsigned char* artLockFrameData(int fid, int frame, int direction, CacheEntry** handlePtr)
{
Art* art;
ArtFrame* frm;
art = NULL;
if (handlePtr) {
cacheLock(&gArtCache, fid, (void**)&art, handlePtr);
}
if (art != NULL) {
frm = artGetFrame(art, frame, direction);
if (frm != NULL) {
2022-05-21 10:05:34 -07:00
return (unsigned char*)frm + sizeof(*frm);
2022-05-19 01:51:26 -07:00
}
}
return NULL;
}
// 0x4191CC
unsigned char* artLockFrameDataReturningSize(int fid, CacheEntry** handlePtr, int* widthPtr, int* heightPtr)
{
*handlePtr = NULL;
Art* art;
cacheLock(&gArtCache, fid, (void**)&art, handlePtr);
if (art == NULL) {
return NULL;
}
// NOTE: Uninline.
*widthPtr = artGetWidth(art, 0, 0);
if (*widthPtr == -1) {
return NULL;
}
// NOTE: Uninline.
*heightPtr = artGetHeight(art, 0, 0);
if (*heightPtr == -1) {
return NULL;
}
// NOTE: Uninline.
return artGetFrameData(art, 0, 0);
}
// 0x419260
int artUnlock(CacheEntry* handle)
{
return cacheUnlock(&gArtCache, handle);
}
// 0x41927C
int artCacheFlush()
{
return cacheFlush(&gArtCache);
}
// 0x4192B0
int artCopyFileName(int objectType, int id, char* dest)
2022-05-19 01:51:26 -07:00
{
ArtListDescription* ptr;
if (objectType < OBJ_TYPE_ITEM && objectType >= OBJ_TYPE_COUNT) {
2022-05-19 01:51:26 -07:00
return -1;
}
ptr = &(gArtListDescriptions[objectType]);
2022-05-19 01:51:26 -07:00
if (id >= ptr->fileNamesLength) {
return -1;
}
strcpy(dest, ptr->fileNames + id * 13);
return 0;
}
// 0x419314
int _art_get_code(int animation, int weaponType, char* a3, char* a4)
{
if (weaponType < 0 || weaponType >= WEAPON_ANIMATION_COUNT) {
return -1;
}
if (animation >= ANIM_TAKE_OUT && animation <= ANIM_FIRE_CONTINUOUS) {
*a4 = 'c' + (animation - ANIM_TAKE_OUT);
if (weaponType == WEAPON_ANIMATION_NONE) {
return -1;
}
*a3 = 'd' + (weaponType - 1);
return 0;
} else if (animation == ANIM_PRONE_TO_STANDING) {
*a4 = 'h';
*a3 = 'c';
return 0;
} else if (animation == ANIM_BACK_TO_STANDING) {
*a4 = 'j';
*a3 = 'c';
return 0;
} else if (animation == ANIM_CALLED_SHOT_PIC) {
*a4 = 'a';
*a3 = 'n';
return 0;
} else if (animation >= FIRST_SF_DEATH_ANIM) {
*a4 = 'a' + (animation - FIRST_SF_DEATH_ANIM);
*a3 = 'r';
return 0;
} else if (animation >= FIRST_KNOCKDOWN_AND_DEATH_ANIM) {
*a4 = 'a' + (animation - FIRST_KNOCKDOWN_AND_DEATH_ANIM);
*a3 = 'b';
return 0;
} else if (animation == ANIM_THROW_ANIM) {
if (weaponType == WEAPON_ANIMATION_KNIFE) {
// knife
*a3 = 'd';
*a4 = 'm';
} else if (weaponType == WEAPON_ANIMATION_SPEAR) {
// spear
*a3 = 'g';
*a4 = 'm';
} else {
// other -> probably rock or grenade
*a3 = 'a';
*a4 = 's';
}
return 0;
} else if (animation == ANIM_DODGE_ANIM) {
if (weaponType <= 0) {
*a3 = 'a';
*a4 = 'n';
} else {
*a3 = 'd' + (weaponType - 1);
*a4 = 'e';
}
return 0;
}
*a4 = 'a' + animation;
if (animation <= ANIM_WALK && weaponType > 0) {
*a3 = 'd' + (weaponType - 1);
return 0;
}
*a3 = 'a';
return 0;
}
// 0x419428
char* artBuildFilePath(int fid)
{
int v1, v2, v3, v4, v5, type, v8, v10;
char v9, v11, v12;
v2 = fid;
v10 = (fid & 0x70000000) >> 28;
v1 = artAliasFid(fid);
2022-05-19 01:51:26 -07:00
if (v1 != -1) {
v2 = v1;
}
*_art_name = '\0';
v3 = v2 & 0xFFF;
v4 = FID_ANIM_TYPE(v2);
2022-05-19 01:51:26 -07:00
v5 = (v2 & 0xF000) >> 12;
type = FID_TYPE(v2);
2022-05-19 01:51:26 -07:00
if (v3 >= gArtListDescriptions[type].fileNamesLength) {
return NULL;
}
if (type < OBJ_TYPE_ITEM || type >= OBJ_TYPE_COUNT) {
2022-05-19 01:51:26 -07:00
return NULL;
}
v8 = v3 * 13;
if (type == 1) {
if (_art_get_code(v4, v5, &v11, &v12) == -1) {
return NULL;
}
if (v10) {
2022-12-08 12:05:50 -08:00
snprintf(_art_name, sizeof(_art_name), "%s%s%s\\%s%c%c.fr%c", _cd_path_base, "art\\", gArtListDescriptions[1].name, gArtListDescriptions[1].fileNames + v8, v11, v12, v10 + 47);
2022-05-19 01:51:26 -07:00
} else {
2022-12-08 12:05:50 -08:00
snprintf(_art_name, sizeof(_art_name), "%s%s%s\\%s%c%c.frm", _cd_path_base, "art\\", gArtListDescriptions[1].name, gArtListDescriptions[1].fileNames + v8, v11, v12);
2022-05-19 01:51:26 -07:00
}
} else if (type == 8) {
v9 = _head2[v4];
if (v9 == 'f') {
2022-12-08 12:05:50 -08:00
snprintf(_art_name, sizeof(_art_name), "%s%s%s\\%s%c%c%d.frm", _cd_path_base, "art\\", gArtListDescriptions[8].name, gArtListDescriptions[8].fileNames + v8, _head1[v4], 102, v5);
2022-05-19 01:51:26 -07:00
} else {
2022-12-08 12:05:50 -08:00
snprintf(_art_name, sizeof(_art_name), "%s%s%s\\%s%c%c.frm", _cd_path_base, "art\\", gArtListDescriptions[8].name, gArtListDescriptions[8].fileNames + v8, _head1[v4], v9);
2022-05-19 01:51:26 -07:00
}
} else {
2022-12-08 12:05:50 -08:00
snprintf(_art_name, sizeof(_art_name), "%s%s%s\\%s", _cd_path_base, "art\\", gArtListDescriptions[type].name, gArtListDescriptions[type].fileNames + v8);
2022-05-19 01:51:26 -07:00
}
return _art_name;
}
// art_read_lst
// 0x419664
static int artReadList(const char* path, char** artListPtr, int* artListSizePtr)
2022-05-19 01:51:26 -07:00
{
File* stream = fileOpen(path, "rt");
2022-05-19 01:51:26 -07:00
if (stream == NULL) {
return -1;
}
int count = 0;
char string[200];
while (fileReadString(string, sizeof(string), stream)) {
2022-05-19 01:51:26 -07:00
count++;
}
fileSeek(stream, 0, SEEK_SET);
*artListSizePtr = count;
2022-05-19 01:51:26 -07:00
char* artList = (char*)internal_malloc(13 * count);
*artListPtr = artList;
if (artList == NULL) {
fileClose(stream);
return -1;
2022-05-19 01:51:26 -07:00
}
while (fileReadString(string, sizeof(string), stream)) {
char* brk = strpbrk(string, " ,;\r\t\n");
2022-05-19 01:51:26 -07:00
if (brk != NULL) {
*brk = '\0';
}
strncpy(artList, string, 12);
artList[12] = '\0';
2022-05-19 01:51:26 -07:00
artList += 13;
2022-05-19 01:51:26 -07:00
}
fileClose(stream);
return 0;
}
// 0x419760
int artGetFramesPerSecond(Art* art)
{
if (art == NULL) {
return 10;
}
return art->framesPerSecond == 0 ? 10 : art->framesPerSecond;
}
// 0x419778
int artGetActionFrame(Art* art)
{
return art == NULL ? -1 : art->actionFrame;
}
// 0x41978C
int artGetFrameCount(Art* art)
{
return art == NULL ? -1 : art->frameCount;
}
// 0x4197A0
int artGetWidth(Art* art, int frame, int direction)
{
ArtFrame* frm;
frm = artGetFrame(art, frame, direction);
if (frm == NULL) {
return -1;
}
return frm->width;
}
// 0x4197B8
int artGetHeight(Art* art, int frame, int direction)
{
ArtFrame* frm;
frm = artGetFrame(art, frame, direction);
if (frm == NULL) {
return -1;
}
return frm->height;
}
// 0x4197D4
int artGetSize(Art* art, int frame, int direction, int* widthPtr, int* heightPtr)
{
ArtFrame* frm;
frm = artGetFrame(art, frame, direction);
if (frm == NULL) {
if (widthPtr != NULL) {
*widthPtr = 0;
}
if (heightPtr != NULL) {
*heightPtr = 0;
}
return -1;
}
if (widthPtr != NULL) {
*widthPtr = frm->width;
}
if (heightPtr != NULL) {
*heightPtr = frm->height;
}
return 0;
}
// 0x419820
int artGetFrameOffsets(Art* art, int frame, int direction, int* xPtr, int* yPtr)
{
ArtFrame* frm;
frm = artGetFrame(art, frame, direction);
if (frm == NULL) {
return -1;
}
*xPtr = frm->x;
*yPtr = frm->y;
return 0;
}
// 0x41984C
int artGetRotationOffsets(Art* art, int rotation, int* xPtr, int* yPtr)
{
if (art == NULL) {
return -1;
}
*xPtr = art->xOffsets[rotation];
*yPtr = art->yOffsets[rotation];
return 0;
}
// 0x419870
unsigned char* artGetFrameData(Art* art, int frame, int direction)
{
ArtFrame* frm;
frm = artGetFrame(art, frame, direction);
if (frm == NULL) {
return NULL;
}
2022-05-21 10:05:34 -07:00
return (unsigned char*)frm + sizeof(*frm);
2022-05-19 01:51:26 -07:00
}
// 0x419880
ArtFrame* artGetFrame(Art* art, int frame, int rotation)
{
if (rotation < 0 || rotation >= 6) {
return NULL;
}
if (art == NULL) {
return NULL;
}
if (frame < 0 || frame >= art->frameCount) {
return NULL;
}
2023-01-19 09:27:22 -08:00
ArtFrame* frm = (ArtFrame*)((unsigned char*)art + sizeof(*art) + art->dataOffsets[rotation] + art->padding[rotation]);
2022-05-19 01:51:26 -07:00
for (int index = 0; index < frame; index++) {
2023-01-19 09:27:22 -08:00
frm = (ArtFrame*)((unsigned char*)frm + sizeof(*frm) + frm->size + paddingForSize(frm->size));
2022-05-19 01:51:26 -07:00
}
return frm;
}
// 0x4198C8
bool artExists(int fid)
{
bool result = false;
2022-05-19 01:51:26 -07:00
char* filePath = artBuildFilePath(fid);
if (filePath != NULL) {
int fileSize;
if (dbGetFileSize(filePath, &fileSize) != -1) {
result = true;
}
2022-05-19 01:51:26 -07:00
}
return result;
}
2022-08-16 01:25:56 -07:00
// NOTE: Exactly the same implementation as `artExists`.
//
2022-05-19 01:51:26 -07:00
// 0x419930
bool _art_fid_valid(int fid)
{
2022-08-16 01:25:56 -07:00
bool result = false;
2022-05-19 01:51:26 -07:00
char* filePath = artBuildFilePath(fid);
2022-08-16 01:25:56 -07:00
if (filePath != NULL) {
int fileSize;
if (dbGetFileSize(filePath, &fileSize) != -1) {
result = true;
}
2022-05-19 01:51:26 -07:00
}
2022-08-16 01:25:56 -07:00
return result;
2022-05-19 01:51:26 -07:00
}
// 0x419998
int _art_alias_num(int index)
{
return _anon_alias[index];
}
// 0x4199AC
int artCritterFidShouldRun(int fid)
{
if (FID_TYPE(fid) == OBJ_TYPE_CRITTER) {
2022-05-19 01:51:26 -07:00
return gArtCritterFidShoudRunData[fid & 0xFFF];
}
return 0;
}
// 0x4199D4
int artAliasFid(int fid)
2022-05-19 01:51:26 -07:00
{
int type = FID_TYPE(fid);
int anim = FID_ANIM_TYPE(fid);
if (type == OBJ_TYPE_CRITTER) {
if (anim == ANIM_ELECTRIFY
|| anim == ANIM_BURNED_TO_NOTHING
|| anim == ANIM_ELECTRIFIED_TO_NOTHING
|| anim == ANIM_ELECTRIFY_SF
|| anim == ANIM_BURNED_TO_NOTHING_SF
|| anim == ANIM_ELECTRIFIED_TO_NOTHING_SF
|| anim == ANIM_FIRE_DANCE
|| anim == ANIM_CALLED_SHOT_PIC) {
// NOTE: Original code is slightly different. It uses many mutually
// mirrored bitwise operators. Probably result of some macros for
// getting/setting individual bits on fid.
return (fid & 0x70000000) | ((anim << 16) & 0xFF0000) | 0x1000000 | (fid & 0xF000) | (_anon_alias[fid & 0xFFF] & 0xFFF);
}
}
2022-05-19 01:51:26 -07:00
return -1;
2022-05-19 01:51:26 -07:00
}
// 0x419A78
2022-06-18 23:02:26 -07:00
static int artCacheGetFileSizeImpl(int fid, int* sizePtr)
2022-05-19 01:51:26 -07:00
{
int result = -1;
2022-05-19 01:51:26 -07:00
char* artFilePath = artBuildFilePath(fid);
if (artFilePath != NULL) {
bool loaded = false;
2023-01-19 09:27:22 -08:00
File* stream = NULL;
2022-05-19 01:51:26 -07:00
if (gArtLanguageInitialized) {
char* pch = strchr(artFilePath, '\\');
if (pch == NULL) {
pch = artFilePath;
2022-05-19 01:51:26 -07:00
}
char localizedPath[COMPAT_MAX_PATH];
2022-12-08 12:05:50 -08:00
snprintf(localizedPath, sizeof(localizedPath), "art\\%s\\%s", gArtLanguage, pch);
2022-05-19 01:51:26 -07:00
2023-01-19 09:27:22 -08:00
stream = fileOpen(localizedPath, "rb");
2022-05-19 01:51:26 -07:00
}
2023-01-19 09:27:22 -08:00
if (stream == NULL) {
stream = fileOpen(artFilePath, "rb");
2022-05-19 01:51:26 -07:00
}
2023-01-19 09:27:22 -08:00
if (stream != NULL) {
Art art;
if (artReadHeader(&art, stream) == 0) {
*sizePtr = artGetDataSize(&art);
result = 0;
}
fileClose(stream);
2022-05-19 01:51:26 -07:00
}
}
return result;
}
// 0x419B78
2022-06-18 23:02:26 -07:00
static int artCacheReadDataImpl(int fid, int* sizePtr, unsigned char* data)
2022-05-19 01:51:26 -07:00
{
int result = -1;
2022-05-19 01:51:26 -07:00
char* artFileName = artBuildFilePath(fid);
if (artFileName != NULL) {
bool loaded = false;
2022-05-19 01:51:26 -07:00
if (gArtLanguageInitialized) {
char* pch = strchr(artFileName, '\\');
if (pch == NULL) {
pch = artFileName;
2022-05-19 01:51:26 -07:00
}
char localizedPath[COMPAT_MAX_PATH];
2022-12-08 12:05:50 -08:00
snprintf(localizedPath, sizeof(localizedPath), "art\\%s\\%s", gArtLanguage, pch);
2022-05-19 01:51:26 -07:00
if (artRead(localizedPath, data) == 0) {
2022-05-19 01:51:26 -07:00
loaded = true;
}
}
if (!loaded) {
if (artRead(artFileName, data) == 0) {
2022-05-19 01:51:26 -07:00
loaded = true;
}
}
if (loaded) {
2023-01-19 09:27:22 -08:00
*sizePtr = artGetDataSize((Art*)data);
2022-05-19 01:51:26 -07:00
result = 0;
}
}
return result;
}
// 0x419C80
2022-06-18 23:02:26 -07:00
static void artCacheFreeImpl(void* ptr)
2022-05-19 01:51:26 -07:00
{
internal_free(ptr);
}
// 0x419C88
int buildFid(int objectType, int frmId, int animType, int a3, int rotation)
2022-05-19 01:51:26 -07:00
{
int v7, v8, v9, v10;
v10 = rotation;
if (objectType != OBJ_TYPE_CRITTER) {
goto zero;
}
if (animType == ANIM_FIRE_DANCE || animType < ANIM_FALL_BACK || animType > ANIM_FALL_FRONT_BLOOD) {
2022-05-19 01:51:26 -07:00
goto zero;
}
2022-08-01 09:47:09 -07:00
v7 = ((a3 << 12) & 0xF000) | ((animType << 16) & 0xFF0000) | 0x1000000;
v8 = ((rotation << 28) & 0x70000000) | v7;
v9 = frmId & 0xFFF;
2022-05-19 01:51:26 -07:00
if (artExists(v9 | v8) != 0) {
goto out;
}
if (objectType == rotation) {
goto zero;
}
v10 = objectType;
if (artExists(v9 | v7 | 0x10000000) != 0) {
goto out;
}
zero:
v10 = 0;
out:
2022-08-01 09:47:09 -07:00
return ((v10 << 28) & 0x70000000) | (objectType << 24) | ((animType << 16) & 0xFF0000) | ((a3 << 12) & 0xF000) | (frmId & 0xFFF);
2022-05-19 01:51:26 -07:00
}
// 0x419D60
2023-01-19 09:27:22 -08:00
static int artReadFrameData(unsigned char* data, File* stream, int count, int* paddingPtr)
2022-05-19 01:51:26 -07:00
{
unsigned char* ptr = data;
2023-01-19 09:27:22 -08:00
int padding = 0;
2022-05-19 01:51:26 -07:00
for (int index = 0; index < count; index++) {
ArtFrame* frame = (ArtFrame*)ptr;
if (fileReadInt16(stream, &(frame->width)) == -1) return -1;
if (fileReadInt16(stream, &(frame->height)) == -1) return -1;
if (fileReadInt32(stream, &(frame->size)) == -1) return -1;
if (fileReadInt16(stream, &(frame->x)) == -1) return -1;
if (fileReadInt16(stream, &(frame->y)) == -1) return -1;
if (fileRead(ptr + sizeof(ArtFrame), frame->size, 1, stream) != 1) return -1;
ptr += sizeof(ArtFrame) + frame->size;
2023-01-19 09:27:22 -08:00
ptr += paddingForSize(frame->size);
padding += paddingForSize(frame->size);
2022-05-19 01:51:26 -07:00
}
2023-01-19 09:27:22 -08:00
*paddingPtr = padding;
2022-05-19 01:51:26 -07:00
return 0;
}
// 0x419E1C
2022-06-18 23:02:26 -07:00
static int artReadHeader(Art* art, File* stream)
2022-05-19 01:51:26 -07:00
{
if (fileReadInt32(stream, &(art->field_0)) == -1) return -1;
if (fileReadInt16(stream, &(art->framesPerSecond)) == -1) return -1;
if (fileReadInt16(stream, &(art->actionFrame)) == -1) return -1;
if (fileReadInt16(stream, &(art->frameCount)) == -1) return -1;
if (fileReadInt16List(stream, art->xOffsets, ROTATION_COUNT) == -1) return -1;
if (fileReadInt16List(stream, art->yOffsets, ROTATION_COUNT) == -1) return -1;
if (fileReadInt32List(stream, art->dataOffsets, ROTATION_COUNT) == -1) return -1;
2023-01-19 09:27:22 -08:00
if (fileReadInt32(stream, &(art->dataSize)) == -1) return -1;
2022-05-19 01:51:26 -07:00
return 0;
}
2023-01-21 04:24:18 -08:00
// NOTE: Original function was slightly different, but never used. Basically
// it's a memory allocating variant of `artRead` (which reads data into given
// buffer). This function is useful to load custom `frm` files since `Art` now
// needs more memory then it's on-disk size (due to memory padding).
//
// 0x419EC0
Art* artLoad(const char* path)
{
File* stream = fileOpen(path, "rb");
if (stream == nullptr) {
return nullptr;
}
Art header;
if (artReadHeader(&header, stream) != 0) {
fileClose(stream);
return nullptr;
}
fileClose(stream);
unsigned char* data = reinterpret_cast<unsigned char*>(internal_malloc(artGetDataSize(&header)));
if (data == nullptr) {
return nullptr;
}
if (artRead(path, data) != 0) {
internal_free(data);
return nullptr;
}
return reinterpret_cast<Art*>(data);
}
2022-05-19 01:51:26 -07:00
// 0x419FC0
int artRead(const char* path, unsigned char* data)
{
File* stream = fileOpen(path, "rb");
if (stream == NULL) {
return -2;
}
Art* art = (Art*)data;
if (artReadHeader(art, stream) != 0) {
fileClose(stream);
return -3;
}
2023-01-19 09:27:22 -08:00
int currentPadding = paddingForSize(sizeof(Art));
int previousPadding = 0;
2022-05-19 01:51:26 -07:00
for (int index = 0; index < ROTATION_COUNT; index++) {
2023-01-19 09:27:22 -08:00
art->padding[index] = currentPadding;
2022-05-19 01:51:26 -07:00
if (index == 0 || art->dataOffsets[index - 1] != art->dataOffsets[index]) {
2023-01-19 09:27:22 -08:00
art->padding[index] += previousPadding;
currentPadding += previousPadding;
if (artReadFrameData(data + sizeof(Art) + art->dataOffsets[index] + art->padding[index], stream, art->frameCount, &previousPadding) != 0) {
2022-05-19 01:51:26 -07:00
fileClose(stream);
return -5;
}
}
}
fileClose(stream);
return 0;
}
// NOTE: Unused.
//
// 0x41A070
int artWriteFrameData(unsigned char* data, File* stream, int count)
{
unsigned char* ptr = data;
for (int index = 0; index < count; index++) {
ArtFrame* frame = (ArtFrame*)ptr;
if (fileWriteInt16(stream, frame->width) == -1) return -1;
if (fileWriteInt16(stream, frame->height) == -1) return -1;
if (fileWriteInt32(stream, frame->size) == -1) return -1;
if (fileWriteInt16(stream, frame->x) == -1) return -1;
if (fileWriteInt16(stream, frame->y) == -1) return -1;
if (fileWrite(ptr + sizeof(ArtFrame), frame->size, 1, stream) != 1) return -1;
ptr += sizeof(ArtFrame) + frame->size;
2023-01-19 09:27:22 -08:00
ptr += paddingForSize(frame->size);
}
return 0;
}
// NOTE: Unused.
//
// 0x41A138
int artWriteHeader(Art* art, File* stream)
{
if (fileWriteInt32(stream, art->field_0) == -1) return -1;
if (fileWriteInt16(stream, art->framesPerSecond) == -1) return -1;
if (fileWriteInt16(stream, art->actionFrame) == -1) return -1;
if (fileWriteInt16(stream, art->frameCount) == -1) return -1;
if (fileWriteInt16List(stream, art->xOffsets, ROTATION_COUNT) == -1) return -1;
if (fileWriteInt16List(stream, art->yOffsets, ROTATION_COUNT) == -1) return -1;
if (fileWriteInt32List(stream, art->dataOffsets, ROTATION_COUNT) == -1) return -1;
2023-01-19 09:27:22 -08:00
if (fileWriteInt32(stream, art->dataSize) == -1) return -1;
return 0;
}
// NOTE: Unused.
//
// 0x41A1E8
int artWrite(const char* path, unsigned char* data)
{
if (data == NULL) {
return -1;
}
File* stream = fileOpen(path, "wb");
if (stream == NULL) {
return -1;
}
Art* art = (Art*)data;
if (artWriteHeader(art, stream) == -1) {
fileClose(stream);
return -1;
}
for (int index = 0; index < ROTATION_COUNT; index++) {
if (index == 0 || art->dataOffsets[index - 1] != art->dataOffsets[index]) {
2023-01-19 09:27:22 -08:00
if (artWriteFrameData(data + sizeof(Art) + art->dataOffsets[index] + art->padding[index], stream, art->frameCount) != 0) {
fileClose(stream);
return -1;
}
}
}
fileClose(stream);
return 0;
}
2022-09-23 05:43:44 -07:00
2023-01-19 09:27:22 -08:00
static int artGetDataSize(Art* art)
{
int dataSize = sizeof(*art) + art->dataSize;
for (int index = 0; index < ROTATION_COUNT; index++) {
if (index == 0 || art->dataOffsets[index - 1] != art->dataOffsets[index]) {
// Assume worst case - every frame is unaligned and need
// max padding.
dataSize += (sizeof(int) - 1) * art->frameCount;
}
}
return dataSize;
}
static int paddingForSize(int size)
{
return (sizeof(int) - size % sizeof(int)) % sizeof(int);
}
2022-09-24 06:14:54 -07:00
FrmImage::FrmImage()
{
_key = nullptr;
_data = nullptr;
_width = 0;
_height = 0;
}
FrmImage::~FrmImage()
{
unlock();
}
bool FrmImage::lock(unsigned int fid)
{
if (isLocked()) {
return false;
}
_data = artLockFrameDataReturningSize(fid, &_key, &_width, &_height);
if (!_data) {
return false;
}
return true;
}
void FrmImage::unlock()
{
if (isLocked()) {
artUnlock(_key);
_key = nullptr;
_data = nullptr;
_width = 0;
_height = 0;
}
}
2022-09-23 05:43:44 -07:00
} // namespace fallout