From d8d736f0eb4e397622ba2306e874f72f15b84388 Mon Sep 17 00:00:00 2001 From: vladislavbelov Date: Tue, 12 Apr 2022 17:39:05 +0000 Subject: [PATCH] Allows tex clients decide how to handle invalid textures instead of assertions. Fixes #6436 This was SVN commit r26783. --- source/graphics/TextureConverter.cpp | 6 ++-- source/graphics/TextureManager.cpp | 19 ++++++++++-- source/lib/tex/tex.cpp | 22 ++++++++------ source/lib/tex/tex_codec.cpp | 4 +-- source/lib/tex/tex_dds.cpp | 32 +++++++++++--------- source/lib/tex/tex_png.cpp | 10 +++--- source/ps/Util.cpp | 5 ++- source/ps/Util.h | 6 +++- source/renderer/SkyManager.cpp | 8 +++-- source/soundmanager/scripting/SoundGroup.cpp | 2 +- 10 files changed, 72 insertions(+), 42 deletions(-) diff --git a/source/graphics/TextureConverter.cpp b/source/graphics/TextureConverter.cpp index 9ad1246af5..2c08e0edd9 100644 --- a/source/graphics/TextureConverter.cpp +++ b/source/graphics/TextureConverter.cpp @@ -29,6 +29,7 @@ #include "ps/CStr.h" #include "ps/Profiler2.h" #include "ps/Threading.h" +#include "ps/Util.h" #include "ps/XML/Xeromyces.h" #if CONFIG2_NVTT @@ -342,9 +343,10 @@ bool CTextureConverter::ConvertTexture(const CTexturePtr& texture, const VfsPath } Tex tex; - if (tex.decode(file, fileSize) < 0) + const Status decodeStatus = tex.decode(file, fileSize); + if (decodeStatus != INFO::OK) { - LOGERROR("Failed to decode texture \"%s\"", src.string8()); + LOGERROR("Failed to decode texture \"%s\" %s", src.string8(), GetStatusAsString(decodeStatus).c_str()); return false; } diff --git a/source/graphics/TextureManager.cpp b/source/graphics/TextureManager.cpp index 8bca789531..32fdca13e0 100644 --- a/source/graphics/TextureManager.cpp +++ b/source/graphics/TextureManager.cpp @@ -33,6 +33,7 @@ #include "ps/ConfigDB.h" #include "ps/Filesystem.h" #include "ps/Profile.h" +#include "ps/Util.h" #include "ps/VideoMode.h" #include "renderer/backend/gl/Device.h" #include "renderer/Renderer.h" @@ -401,12 +402,24 @@ public: std::shared_ptr fileData; size_t fileSize; + const Status loadStatus = g_VFS->LoadFile(path, fileData, fileSize); + if (loadStatus != INFO::OK) + { + LOGERROR("Texture failed to load; \"%s\" %s", + texture->m_Properties.m_Path.string8(), GetStatusAsString(loadStatus).c_str()); + texture->ResetBackendTexture( + nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture()); + texture->m_TextureData.reset(); + return; + } + texture->m_TextureData = std::make_unique(); Tex& textureData = *texture->m_TextureData; - if (g_VFS->LoadFile(path, fileData, fileSize) != INFO::OK || - textureData.decode(fileData, fileSize) != INFO::OK) + const Status decodeStatus = textureData.decode(fileData, fileSize); + if (decodeStatus != INFO::OK) { - LOGERROR("Texture failed to load; \"%s\"", texture->m_Properties.m_Path.string8()); + LOGERROR("Texture failed to decode; \"%s\" %s", + texture->m_Properties.m_Path.string8(), GetStatusAsString(decodeStatus).c_str()); texture->ResetBackendTexture( nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture()); texture->m_TextureData.reset(); diff --git a/source/lib/tex/tex.cpp b/source/lib/tex/tex.cpp index 569fad4975..dc0c98eeb0 100644 --- a/source/lib/tex/tex.cpp +++ b/source/lib/tex/tex.cpp @@ -34,7 +34,9 @@ #include #include -static const StatusDefinition texStatusDefinitions[] = { +static const StatusDefinition texStatusDefinitions[] = +{ + { ERR::TEX_UNKNOWN_FORMAT, L"Unknown texture format" }, { ERR::TEX_FMT_INVALID, L"Invalid/unsupported texture format" }, { ERR::TEX_INVALID_COLOR_TYPE, L"Invalid color type" }, { ERR::TEX_NOT_8BIT_PRECISION, L"Not 8-bit channel precision" }, @@ -55,7 +57,7 @@ STATUS_ADD_DEFINITIONS(texStatusDefinitions); Status Tex::validate() const { if(m_Flags & TEX_UNDEFINED_FLAGS) - WARN_RETURN(ERR::_1); + return ERR::_1; // pixel data (only check validity if the image is still in memory). if(m_Data) @@ -64,23 +66,23 @@ Status Tex::validate() const // possible causes: texture file header is invalid, // or file wasn't loaded completely. if(m_DataSize < m_Ofs + m_Width*m_Height*m_Bpp/8) - WARN_RETURN(ERR::_2); + return ERR::_2; } // bits per pixel // (we don't bother checking all values; a sanity check is enough) if(m_Bpp % 4 || m_Bpp > 32) - WARN_RETURN(ERR::_3); + return ERR::_3; // flags // .. DXT value const size_t dxt = m_Flags & TEX_DXT; if(dxt != 0 && dxt != 1 && dxt != DXT1A && dxt != 3 && dxt != 5) - WARN_RETURN(ERR::_4); + return ERR::_4; // .. orientation const size_t orientation = m_Flags & TEX_ORIENTATION; if(orientation == (TEX_BOTTOM_UP|TEX_TOP_DOWN)) - WARN_RETURN(ERR::_5); + return ERR::_5; return INFO::OK; } @@ -718,10 +720,10 @@ Status Tex::decode(const std::shared_ptr& Data, size_t DataSize) // make sure the entire header is available const size_t min_hdr_size = c->hdr_size(0); if(DataSize < min_hdr_size) - WARN_RETURN(ERR::TEX_INCOMPLETE_HEADER); + return ERR::TEX_INCOMPLETE_HEADER; const size_t hdr_size = c->hdr_size(Data.get()); if(DataSize < hdr_size) - WARN_RETURN(ERR::TEX_INCOMPLETE_HEADER); + return ERR::TEX_INCOMPLETE_HEADER; m_Data = Data; m_DataSize = DataSize; @@ -731,9 +733,9 @@ Status Tex::decode(const std::shared_ptr& Data, size_t DataSize) // sanity checks if(!m_Width || !m_Height || m_Bpp > 32) - WARN_RETURN(ERR::TEX_FMT_INVALID); + return ERR::TEX_FMT_INVALID; if(m_DataSize < m_Ofs + img_size()) - WARN_RETURN(ERR::TEX_INVALID_SIZE); + return ERR::TEX_INVALID_SIZE; flip_to_global_orientation(this); diff --git a/source/lib/tex/tex_codec.cpp b/source/lib/tex/tex_codec.cpp index 089fad0a1b..255e5002e1 100644 --- a/source/lib/tex/tex_codec.cpp +++ b/source/lib/tex/tex_codec.cpp @@ -67,7 +67,7 @@ Status tex_codec_for_header(const u8* file, size_t file_size, const ITexCodec** { // we guarantee at least 4 bytes for is_hdr to look at if(file_size < 4) - WARN_RETURN(ERR::TEX_INCOMPLETE_HEADER); + return ERR::TEX_INCOMPLETE_HEADER; for(int i = 0; i < codecs_len; ++i) { @@ -78,7 +78,7 @@ Status tex_codec_for_header(const u8* file, size_t file_size, const ITexCodec** } } - WARN_RETURN(ERR::TEX_UNKNOWN_FORMAT); + return ERR::TEX_UNKNOWN_FORMAT; } Status tex_codec_transform(Tex* t, size_t transforms) diff --git a/source/lib/tex/tex_dds.cpp b/source/lib/tex/tex_dds.cpp index 2c8e993fa1..b61153e5e3 100644 --- a/source/lib/tex/tex_dds.cpp +++ b/source/lib/tex/tex_dds.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -397,7 +397,7 @@ static Status decode_pf(const DDS_PIXELFORMAT* pf, size_t& bpp, size_t& flags) // check struct size if(read_le32(&pf->dwSize) != sizeof(DDS_PIXELFORMAT)) - WARN_RETURN(ERR::TEX_INVALID_SIZE); + return ERR::TEX_INVALID_SIZE; // determine type const size_t pf_flags = (size_t)read_le32(&pf->dwFlags); @@ -432,7 +432,7 @@ static Status decode_pf(const DDS_PIXELFORMAT* pf, size_t& bpp, size_t& flags) // DDS is storing images in a format that requires no processing, // we do not allow any weird orderings that require runtime work. // instead, the artists must export with the correct settings. - WARN_RETURN(ERR::TEX_FMT_INVALID); + return ERR::TEX_FMT_INVALID; } RETURN_STATUS_IF_ERR(tex_validate_plain_format(bpp, (int)flags)); @@ -446,10 +446,10 @@ static Status decode_pf(const DDS_PIXELFORMAT* pf, size_t& bpp, size_t& flags) bpp = pf_bpp; if(pf_bpp != 8) - WARN_RETURN(ERR::TEX_FMT_INVALID); + return ERR::TEX_FMT_INVALID; if(pf_a_mask != 0xFF) - WARN_RETURN(ERR::TEX_FMT_INVALID); + return ERR::TEX_FMT_INVALID; flags |= TEX_GREY; RETURN_STATUS_IF_ERR(tex_validate_plain_format(bpp, (int)flags)); @@ -480,12 +480,12 @@ static Status decode_pf(const DDS_PIXELFORMAT* pf, size_t& bpp, size_t& flags) break; default: - WARN_RETURN(ERR::TEX_FMT_INVALID); + return ERR::TEX_FMT_INVALID; } } // .. neither uncompressed nor compressed - invalid else - WARN_RETURN(ERR::TEX_FMT_INVALID); + return ERR::TEX_FMT_INVALID; return INFO::OK; } @@ -499,7 +499,7 @@ static Status decode_sd(const DDS_HEADER* sd, size_t& w, size_t& h, size_t& bpp, { // check header size if(read_le32(&sd->dwSize) != sizeof(*sd)) - WARN_RETURN(ERR::CORRUPTED); + return ERR::CORRUPTED; // flags (indicate which fields are valid) const size_t sd_flags = (size_t)read_le32(&sd->dwFlags); @@ -507,7 +507,7 @@ static Status decode_sd(const DDS_HEADER* sd, size_t& w, size_t& h, size_t& bpp, // note: we can't guess dimensions - the image may not be square. const size_t sd_req_flags = DDSD_CAPS|DDSD_HEIGHT|DDSD_WIDTH|DDSD_PIXELFORMAT; if((sd_flags & sd_req_flags) != sd_req_flags) - WARN_RETURN(ERR::TEX_INCOMPLETE_HEADER); + return ERR::TEX_INCOMPLETE_HEADER; // image dimensions h = (size_t)read_le32(&sd->dwHeight); @@ -537,7 +537,7 @@ static Status decode_sd(const DDS_HEADER* sd, size_t& w, size_t& h, size_t& bpp, if(sd_flags & DDSD_PITCH) { if(sd_pitch_or_size != Align<4>(pitch)) - DEBUG_WARN_ERR(ERR::CORRUPTED); + return ERR::CORRUPTED; } if(sd_flags & DDSD_LINEARSIZE) { @@ -545,7 +545,7 @@ static Status decode_sd(const DDS_HEADER* sd, size_t& w, size_t& h, size_t& bpp, // so allow values close to that as well const ssize_t totalSize = ssize_t(pitch*stored_h*1.333333f); if(sd_pitch_or_size != pitch*stored_h && std::abs(ssize_t(sd_pitch_or_size)-totalSize) > 64) - DEBUG_WARN_ERR(ERR::CORRUPTED); + return ERR::CORRUPTED; } // note: both flags set would be invalid; no need to check for that, // though, since one of the above tests would fail. @@ -559,7 +559,7 @@ static Status decode_sd(const DDS_HEADER* sd, size_t& w, size_t& h, size_t& bpp, // mipmap chain is incomplete // note: DDS includes the base level in its count, hence +1. if(mipmap_count != ceil_log2(std::max(w,h))+1) - WARN_RETURN(ERR::TEX_FMT_INVALID); + return ERR::TEX_FMT_INVALID; flags |= TEX_MIPMAPS; } } @@ -569,17 +569,19 @@ static Status decode_sd(const DDS_HEADER* sd, size_t& w, size_t& h, size_t& bpp, { const size_t depth = (size_t)read_le32(&sd->dwDepth); if(depth) - WARN_RETURN(ERR::NOT_SUPPORTED); + return ERR::NOT_SUPPORTED; } // check caps // .. this is supposed to be set, but don't bail if not (pointless) - ENSURE(sd->dwCaps & DDSCAPS_TEXTURE); + if (!(sd->dwCaps & DDSCAPS_TEXTURE)) + return ERR::CORRUPTED; // .. sanity check: warn if mipmap flag not set (don't bail if not // because we've already made the decision). const bool mipmap_cap = (sd->dwCaps & DDSCAPS_MIPMAP) != 0; const bool mipmap_flag = (flags & TEX_MIPMAPS) != 0; - ENSURE(mipmap_cap == mipmap_flag); + if (mipmap_cap != mipmap_flag) + return ERR::CORRUPTED; // note: we do not check for cubemaps and volume textures (not supported) // because the file may still have useful data we can read. diff --git a/source/lib/tex/tex_png.cpp b/source/lib/tex/tex_png.cpp index bd5ce75508..b5756dcbc6 100644 --- a/source/lib/tex/tex_png.cpp +++ b/source/lib/tex/tex_png.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -271,7 +271,7 @@ TIMER_ADD_CLIENT(tc_png_decode); // limitation: palette images aren't supported Status TexCodecPng::decode(u8* RESTRICT data, size_t size, Tex* RESTRICT t) const { -TIMER_ACCRUE(tc_png_decode); + TIMER_ACCRUE(tc_png_decode); png_infop info_ptr = 0; @@ -279,19 +279,19 @@ TIMER_ACCRUE(tc_png_decode); // warning handler to filter out useless messages png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, user_warning_fn); if(!png_ptr) - WARN_RETURN(ERR::FAIL); + return ERR::FAIL; info_ptr = png_create_info_struct(png_ptr); if(!info_ptr) { png_destroy_read_struct(&png_ptr, &info_ptr, 0); - WARN_RETURN(ERR::NO_MEM); + return ERR::NO_MEM; } // setup error handling if(setjmp(png_jmpbuf(png_ptr))) { // libpng longjmps here after an error png_destroy_read_struct(&png_ptr, &info_ptr, 0); - WARN_RETURN(ERR::FAIL); + return ERR::FAIL; } MemoryStream stream(data, size); diff --git a/source/ps/Util.cpp b/source/ps/Util.cpp index e5b174da64..5c9e87590d 100644 --- a/source/ps/Util.cpp +++ b/source/ps/Util.cpp @@ -144,7 +144,10 @@ const wchar_t* ErrorString(int err) // TODO: load from language file } - +CStr GetStatusAsString(Status status) +{ + return utf8_from_wstring(ErrorString(status)); +} // write the specified texture to disk. // note: cannot be made const because the image may have to be diff --git a/source/ps/Util.h b/source/ps/Util.h index 0097f7fc90..94355adc95 100644 --- a/source/ps/Util.h +++ b/source/ps/Util.h @@ -18,8 +18,10 @@ #ifndef PS_UTIL_H #define PS_UTIL_H -#include "lib/os_path.h" #include "lib/file/vfs/vfs_path.h" +#include "lib/os_path.h" +#include "lib/status.h" +#include "ps/CStr.h" class Tex; @@ -27,6 +29,8 @@ void WriteSystemInfo(); const wchar_t* ErrorString(int err); +CStr GetStatusAsString(Status status); + OsPath createDateIndexSubdirectory(const OsPath& parentDir); Status tex_write(Tex* t, const VfsPath& filename); diff --git a/source/renderer/SkyManager.cpp b/source/renderer/SkyManager.cpp index bddb6e1899..a83317d155 100644 --- a/source/renderer/SkyManager.cpp +++ b/source/renderer/SkyManager.cpp @@ -95,8 +95,12 @@ void SkyManager::LoadAndUploadSkyTexturesIfNeeded( } } - textures[i].decode(file, fileSize); - textures[i].transform_to((textures[i].m_Flags | TEX_BOTTOM_UP | TEX_ALPHA) & ~(TEX_DXT | TEX_MIPMAPS)); + if (textures[i].decode(file, fileSize) != INFO::OK || + textures[i].transform_to((textures[i].m_Flags | TEX_BOTTOM_UP | TEX_ALPHA) & ~(TEX_DXT | TEX_MIPMAPS)) != INFO::OK) + { + LOGERROR("Error creating sky cubemap '%s', can't decode file: '%s'.", m_SkySet.ToUTF8().c_str(), path.string8().c_str()); + return; + } if (!is_pow2(textures[i].m_Width) || !is_pow2(textures[i].m_Height)) { diff --git a/source/soundmanager/scripting/SoundGroup.cpp b/source/soundmanager/scripting/SoundGroup.cpp index 116ee2f51a..cef2c4899f 100644 --- a/source/soundmanager/scripting/SoundGroup.cpp +++ b/source/soundmanager/scripting/SoundGroup.cpp @@ -253,7 +253,7 @@ static void HandleError(const std::wstring& message, const VfsPath& pathname, St if (err == ERR::AGAIN) return; - LOGERROR("%s: pathname=%s, error=%s", utf8_from_wstring(message), pathname.string8(), utf8_from_wstring(ErrorString(err))); + LOGERROR("%s: pathname=%s, error=%s", utf8_from_wstring(message), pathname.string8(), GetStatusAsString(err).c_str()); } void CSoundGroup::PlayNext(const CVector3D& position, entity_id_t source)