IW4OF integration (#772)

Co-authored-by: Louvenarde <louve@louve.systems>
Co-authored-by: Roxanne <roxanne@thegamebakers.com>
Co-authored-by: FutureRave <edoardo.sanguineti222@gmail.com>
This commit is contained in:
Louve 2023-02-15 12:58:31 +01:00 committed by GitHub
parent ffd7a59f5e
commit f409b881e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 431 additions and 3299 deletions

89
.gitmodules vendored
View File

@ -1,43 +1,46 @@
[submodule "deps/zlib"]
path = deps/zlib
url = https://github.com/madler/zlib.git
branch = develop
[submodule "deps/libtommath"]
path = deps/libtommath
url = https://github.com/libtom/libtommath.git
branch = develop
[submodule "deps/libtomcrypt"]
path = deps/libtomcrypt
url = https://github.com/libtom/libtomcrypt.git
branch = develop
[submodule "deps/pdcurses"]
path = deps/pdcurses
url = https://github.com/wmcbrine/PDCurses.git
branch = master
[submodule "deps/mongoose"]
path = deps/mongoose
url = https://github.com/cesanta/mongoose.git
branch = 7.9
[submodule "deps/protobuf"]
path = deps/protobuf
url = https://github.com/google/protobuf.git
branch = 3.20.x
[submodule "deps/udis86"]
path = deps/udis86
url = https://github.com/vmt/udis86.git
[submodule "deps/dxsdk"]
path = deps/dxsdk
url = https://github.com/devKlausS/dxsdk.git
[submodule "deps/GSL"]
path = deps/GSL
url = https://github.com/microsoft/GSL.git
[submodule "deps/nlohmannjson"]
path = deps/json
url = https://github.com/nlohmann/json.git
branch = v3.11.2
[submodule "deps/discord-rpc"]
path = deps/discord-rpc
url = https://github.com/discord/discord-rpc.git
[submodule "deps/rapidjson"]
path = deps/rapidjson
url = https://github.com/Tencent/rapidjson.git
[submodule "deps/zlib"]
path = deps/zlib
url = https://github.com/madler/zlib.git
branch = develop
[submodule "deps/libtommath"]
path = deps/libtommath
url = https://github.com/libtom/libtommath.git
branch = develop
[submodule "deps/libtomcrypt"]
path = deps/libtomcrypt
url = https://github.com/libtom/libtomcrypt.git
branch = develop
[submodule "deps/pdcurses"]
path = deps/pdcurses
url = https://github.com/wmcbrine/PDCurses.git
branch = master
[submodule "deps/mongoose"]
path = deps/mongoose
url = https://github.com/cesanta/mongoose.git
branch = 7.9
[submodule "deps/protobuf"]
path = deps/protobuf
url = https://github.com/google/protobuf.git
branch = 3.20.x
[submodule "deps/udis86"]
path = deps/udis86
url = https://github.com/vmt/udis86.git
[submodule "deps/dxsdk"]
path = deps/dxsdk
url = https://github.com/devKlausS/dxsdk.git
[submodule "deps/GSL"]
path = deps/GSL
url = https://github.com/microsoft/GSL.git
[submodule "deps/nlohmannjson"]
path = deps/json
url = https://github.com/nlohmann/json.git
branch = v3.11.2
[submodule "deps/discord-rpc"]
path = deps/discord-rpc
url = https://github.com/discord/discord-rpc.git
[submodule "deps/rapidjson"]
path = deps/rapidjson
url = https://github.com/Tencent/rapidjson.git
[submodule "deps/iw4-open-formats"]
path = deps/iw4-open-formats
url = https://github.com/XLabsProject/iw4-open-formats.git

1
deps/iw4-open-formats vendored Submodule

@ -0,0 +1 @@
Subproject commit 619302b5c30f372f3b22576d4553f5af637a6b10

44
deps/premake/iw4-open-formats.lua vendored Normal file
View File

@ -0,0 +1,44 @@
iw4_open_formats = {
source = path.join(dependencies.basePath, "iw4-open-formats"),
}
function iw4_open_formats.import()
links "iw4-open-formats"
iw4_open_formats.includes()
end
function iw4_open_formats.includes()
includedirs {
path.join(iw4_open_formats.source, "include")
}
end
function iw4_open_formats.project()
project "iw4-open-formats"
language "C++"
iw4_open_formats.includes()
pchheader "std_include.hpp"
pchsource (path.join(iw4_open_formats.source, "src/iw4-of/std_include.cpp"))
files {
path.join(iw4_open_formats.source, "src/iw4-of/**.hpp"),
path.join(iw4_open_formats.source, "src/iw4-of/**.cpp"),
}
includedirs {
path.join(iw4_open_formats.source, "src/iw4-of"),
path.join(iw4_open_formats.source, "include"),
}
libtomcrypt.includes()
libtommath.includes()
rapidjson.includes()
zlib.includes()
kind "StaticLib"
end
table.insert(dependencies, iw4_open_formats)

View File

@ -7,52 +7,7 @@ namespace Assets
{
void IComWorld::load(Game::XAssetHeader* header, const std::string& _name, Components::ZoneBuilder::Zone* builder)
{
std::string name = _name;
Utils::String::Replace(name, "maps/mp/", "");
Utils::String::Replace(name, ".d3dbsp", "");
Components::FileSystem::File mapFile(std::format("comworld/{}.iw4xComWorld", name));
if (mapFile.exists())
{
Utils::Stream::Reader reader(builder->getAllocator(), mapFile.getBuffer());
__int64 magic = reader.read<__int64>();
if (std::memcmp(&magic, "IW4xComW", 8))
{
Components::Logger::Error(Game::ERR_FATAL, "Reading comworld '{}' failed, header is invalid!", name);
}
int version = reader.read<int>();
if (version != IW4X_COMMAP_VERSION)
{
Components::Logger::Error(Game::ERR_FATAL, "Reading comworld '{}' failed, expected version is {}, but it was {}!", name, IW4X_COMMAP_VERSION, version);
}
Game::ComWorld* asset = reader.readObject<Game::ComWorld>();
header->comWorld = asset;
if (asset->name)
{
asset->name = reader.readCString();
}
if (asset->primaryLights)
{
asset->primaryLights = reader.readArray<Game::ComPrimaryLight>(asset->primaryLightCount);
for (unsigned int i = 0; i < asset->primaryLightCount; ++i)
{
Game::ComPrimaryLight* light = &asset->primaryLights[i];
if (light->defName)
{
light->defName = reader.readCString();
Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_LIGHT_DEF, light->defName, builder);
}
}
}
}
header->comWorld = builder->getIW4OfApi()->read<Game::ComWorld>(Game::XAssetType::ASSET_TYPE_COMWORLD, _name);
}
void IComWorld::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)

View File

@ -1,14 +1,12 @@
#include <STDInclude.hpp>
#include "IFxEffectDef.hpp"
#define IW4X_FX_VERSION 1
namespace Assets
{
void IFxEffectDef::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
if (!header->data) this->loadEfx(header, name, builder); // Check if we have an editor fx
if (!header->data) this->loadBinary(header, name, builder); // Check if we need to import a new one into the game
if (!header->data) this->loadFromIW4OF(header, name, builder); // Check if we need to import a new one into the game
if (!header->data /*&& !builder->isPrimaryAsset()*/) this->loadNative(header, name, builder); // Check if there is a native one
}
@ -62,139 +60,9 @@ namespace Assets
}
}
void IFxEffectDef::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
void IFxEffectDef::loadFromIW4OF(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
Components::FileSystem::File fxFile(std::format("fx/{}.iw4xFx", name));
if (fxFile.exists())
{
Utils::Stream::Reader buffer(builder->getAllocator(), fxFile.getBuffer());
__int64 magic = buffer.read<__int64>();
if (std::memcmp(&magic, "IW4xFx ", 8))
{
Components::Logger::Error(Game::ERR_FATAL, "Reading fx '{}' failed, header is invalid!", name);
}
int version = buffer.read<int>();
if (version != IW4X_FX_VERSION)
{
Components::Logger::Error(Game::ERR_FATAL, "Reading fx '{}' failed, expected version is {}, but it was {}!", name, IW4X_FX_VERSION, version);
}
Game::FxEffectDef* asset = buffer.readObject<Game::FxEffectDef>();
header->fx = asset;
if (asset->name)
{
asset->name = buffer.readCString();
}
if (asset->elemDefs)
{
asset->elemDefs = buffer.readArray<Game::FxElemDef>(asset->elemDefCountEmission + asset->elemDefCountLooping + asset->elemDefCountOneShot);
for (int i = 0; i < (asset->elemDefCountEmission + asset->elemDefCountLooping + asset->elemDefCountOneShot); ++i)
{
Game::FxElemDef* elemDef = &asset->elemDefs[i];
if (elemDef->velSamples)
{
elemDef->velSamples = buffer.readArray<Game::FxElemVelStateSample>(elemDef->velIntervalCount + 1);
}
if (elemDef->visSamples)
{
elemDef->visSamples = buffer.readArray<Game::FxElemVisStateSample>(elemDef->visStateIntervalCount + 1);
}
// Save_FxElemDefVisuals
{
if (elemDef->elemType == Game::FX_ELEM_TYPE_DECAL)
{
if (elemDef->visuals.markArray)
{
elemDef->visuals.markArray = buffer.readArray<Game::FxElemMarkVisuals>(elemDef->visualCount);
for (char j = 0; j < elemDef->visualCount; ++j)
{
if (elemDef->visuals.markArray[j].materials[0])
{
elemDef->visuals.markArray[j].materials[0] = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, buffer.readString(), builder).material;
}
if (elemDef->visuals.markArray[j].materials[1])
{
elemDef->visuals.markArray[j].materials[1] = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, buffer.readString(), builder).material;
}
}
}
}
else if (elemDef->visualCount > 1)
{
if (elemDef->visuals.array)
{
elemDef->visuals.array = buffer.readArray<Game::FxElemVisuals>(elemDef->visualCount);
for (char j = 0; j < elemDef->visualCount; ++j)
{
this->loadFxElemVisuals(&elemDef->visuals.array[j], elemDef->elemType, builder, &buffer);
}
}
}
else
{
this->loadFxElemVisuals(&elemDef->visuals.instance, elemDef->elemType, builder, &buffer);
}
}
if (elemDef->effectOnImpact.handle)
{
elemDef->effectOnImpact.handle = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_FX, buffer.readString(), builder).fx;
}
if (elemDef->effectOnDeath.handle)
{
elemDef->effectOnDeath.handle = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_FX, buffer.readString(), builder).fx;
}
if (elemDef->effectEmitted.handle)
{
elemDef->effectEmitted.handle = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_FX, buffer.readString(), builder).fx;
}
// Save_FxElemExtendedDefPtr
{
if (elemDef->elemType == Game::FX_ELEM_TYPE_TRAIL)
{
// Save_FxTrailDef
{
if (elemDef->extended.trailDef)
{
Game::FxTrailDef* trailDef = buffer.readObject<Game::FxTrailDef>();
elemDef->extended.trailDef = trailDef;
if (trailDef->verts)
{
trailDef->verts = buffer.readArray<Game::FxTrailVertex>(trailDef->vertCount);
}
if (trailDef->inds)
{
trailDef->inds = buffer.readArray<unsigned short>(trailDef->indCount);
}
}
}
}
else if (elemDef->extended.trailDef)
{
Components::Logger::Error(Game::ERR_FATAL, "Fx element of type {} has traildef, that's impossible?\n", elemDef->elemType);
}
}
}
}
}
header->fx = builder->getIW4OfApi()->read<Game::FxEffectDef>(Game::XAssetType::ASSET_TYPE_FX, name);
}
void IFxEffectDef::loadEfx(Game::XAssetHeader* /*header*/, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/)
@ -270,7 +138,7 @@ namespace Assets
// TODO: Convert editor fx to real fx
}
#else
(name);
(void)name;
#endif
}

View File

@ -17,7 +17,7 @@ namespace Assets
void loadEfx(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
void loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
void loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
void loadFromIW4OF(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
void loadFxElemVisuals(Game::FxElemVisuals* visuals, char elemType, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader);
};

View File

@ -187,6 +187,19 @@ namespace Assets
}
void IFxWorld::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
if (!header->fxWorld) loadFromDisk(header, name, builder);
if (!header->fxWorld) generate(header, name, builder);
assert(header->fxWorld);
}
void IFxWorld::loadFromDisk(Game::XAssetHeader* header, const std::string& _name, Components::ZoneBuilder::Zone* builder)
{
header->fxWorld = builder->getIW4OfApi()->read<Game::FxWorld>(Game::XAssetType::ASSET_TYPE_FXWORLD, _name);
}
void IFxWorld::generate(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
Game::FxWorld* map = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_FXWORLD, name.data()).fxWorld;
if (map) return;
@ -194,7 +207,7 @@ namespace Assets
// Generate
map = builder->getAllocator()->allocate<Game::FxWorld>();
map->name = builder->getAllocator()->duplicateString(name);
// No glass for you!
ZeroMemory(&map->glassSys, sizeof(map->glassSys));

View File

@ -10,5 +10,7 @@ namespace Assets
void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override;
void loadFromDisk(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
void generate(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
};
}

View File

@ -80,115 +80,6 @@ namespace Assets
void IGameWorldMp::load(Game::XAssetHeader* header, const std::string& _name, Components::ZoneBuilder::Zone* builder)
{
std::string name = _name;
Utils::String::Replace(name, "maps/mp/", "");
Utils::String::Replace(name, ".d3dbsp", "");
Components::FileSystem::File gameWorld(std::format("gameworld/{}.iw4x.json", name));
if (gameWorld.exists())
{
nlohmann::json gameWorldJson;
try
{
gameWorldJson = nlohmann::json::parse(gameWorld.getBuffer());
}
catch (const std::exception& e)
{
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid JSON for gameworld {}! {}", name, e.what());
return;
}
auto* asset = builder->getAllocator()->allocate<Game::GameWorldMp>();
if (!gameWorldJson.is_object())
{
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid GameWorldMp json for {}\n", name);
return;
}
auto version = gameWorldJson["version"].is_number() ? gameWorldJson["version"].get<int>() : 0;
if (version != IW4X_GAMEWORLD_VERSION)
{
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid GameWorld json version for {}, expected {} and got {}\n", name, IW4X_GAMEWORLD_VERSION, version);
return;
}
if (!gameWorldJson["name"].is_string())
{
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Missing gameworld name! on {}\n", name);
return;
}
asset->name = builder->getAllocator()->duplicateString(gameWorldJson["name"].get<std::string>());
auto glassData = builder->getAllocator()->allocate<Game::G_GlassData>();
if (gameWorldJson["glassData"].is_object())
{
auto jsonGlassData = gameWorldJson["glassData"];
try
{
glassData->damageToDestroy = jsonGlassData["damageToDestroy"].get<unsigned short>();
glassData->damageToWeaken = jsonGlassData["damageToWeaken"].get<unsigned short>();
if (jsonGlassData["glassNames"].is_array())
{
nlohmann::json::array_t glassNames = jsonGlassData["glassNames"];
glassData->glassNameCount = glassNames.size();
glassData->glassNames = builder->getAllocator()->allocateArray<Game::G_GlassName>(glassData->glassNameCount);
for (size_t i = 0; i < glassData->glassNameCount; i++)
{
auto jsonGlassName = glassNames[i];
glassData->glassNames[i].nameStr = builder->getAllocator()->duplicateString(jsonGlassName["nameStr"]);
glassData->glassNames[i].name = jsonGlassName["name"].get<unsigned short>();
if (jsonGlassName["piecesIndices"].is_array())
{
nlohmann::json::array_t jsonPiecesIndices = jsonGlassName["piecesIndices"];
glassData->glassNames[i].pieceCount = static_cast<unsigned short>(jsonPiecesIndices.size());
for (size_t j = 0; j < glassData->glassNames[i].pieceCount; j++)
{
glassData->glassNames[i].pieceIndices[j] = jsonPiecesIndices[j].get<unsigned short>();
}
}
}
}
if (gameWorldJson["glassPieces"].is_array())
{
nlohmann::json::array_t glassPieces = gameWorldJson["glassPieces"];
glassData->pieceCount = glassPieces.size();
glassData->glassPieces = builder->getAllocator()->allocateArray<Game::G_GlassPiece>(glassData->pieceCount);
for (size_t i = 0; i < glassData->pieceCount; i++)
{
glassData->glassPieces[i].collapseTime = glassPieces[i]["collapseTime"].get<unsigned short>();
glassData->glassPieces[i].damageTaken = glassPieces[i]["damageTaken"].get<unsigned short>();
glassData->glassPieces[i].lastStateChangeTime = glassPieces[i]["lastStateChangeTime"].get<int>();
glassData->glassPieces[i].impactDir = glassPieces[i]["impactDir"].get<char>();
nlohmann::json::array_t jsonPos = glassPieces[i]["impactPos"];
glassData->glassPieces[i].impactPos[0] = jsonPos[0].get<char>();
glassData->glassPieces[i].impactPos[1] = jsonPos[1].get<char>();
}
}
}
catch (const nlohmann::json::exception& e)
{
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Malformed GameWorldMp json for {} ({})\n", name, e.what());
return;
}
}
asset->g_glassData = glassData;
header->gameWorldMp = asset;
}
header->gameWorldMp = builder->getIW4OfApi()->read<Game::GameWorldMp>(Game::XAssetType::ASSET_TYPE_GAMEWORLD_MP, _name);
}
}

View File

@ -1,150 +1,11 @@
#include <STDInclude.hpp>
#include "IGfxImage.hpp"
#define IW4X_IMG_VERSION "0"
namespace Assets
{
void IGfxImage::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
Game::GfxImage* image = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_IMAGE, name.data()).image;
if (image && name[0] != '*') return;
image = builder->getAllocator()->allocate<Game::GfxImage>();
if (!image)
{
Components::Logger::Error(Game::ERR_FATAL, "Failed to allocate GfxImage structure!");
return;
}
image->name = builder->getAllocator()->duplicateString(name);
image->semantic = Game::TextureSemantic::TS_COLOR_MAP;
const char* tempName = image->name;
if (tempName[0] == '*') tempName++;
Components::FileSystem::File imageFile(std::format("images/{}.iw4xImage", tempName));
if (imageFile.exists())
{
Utils::Stream::Reader reader(builder->getAllocator(), imageFile.getBuffer());
__int64 magic = reader.read<__int64>();
if (std::memcmp(&magic, "IW4xImg" IW4X_IMG_VERSION, 8))
{
Components::Logger::Error(Game::ERR_FATAL, "Reading image '{}' failed, header is invalid!", name);
}
image->mapType = reader.read<char>();
image->semantic = reader.read<Game::TextureSemantic>();
image->category = reader.read<char>();
int dataLength = reader.read<int>();
image->cardMemory.platform[0] = dataLength;
image->cardMemory.platform[1] = dataLength;
Game::GfxImageLoadDefIW3 loadDef;
image->texture.loadDef = reinterpret_cast<Game::GfxImageLoadDef*>(reader.readArray<char>(dataLength + 16));
std::memcpy(&loadDef, image->texture.loadDef, sizeof(loadDef));
image->texture.loadDef->levelCount = loadDef.levelCount;
image->texture.loadDef->flags = loadDef.flags;
image->texture.loadDef->format = loadDef.format;
image->texture.loadDef->resourceSize = loadDef.resourceSize;
ZeroMemory(image->texture.loadDef->pad, 3);
if (image->texture.loadDef->resourceSize != dataLength)
{
Components::Logger::Error(Game::ERR_FATAL, "Resource size doesn't match the data length ({})!\n", name);
}
image->width = loadDef.dimensions[0];
image->height = loadDef.dimensions[1];
image->depth = loadDef.dimensions[2];
image->delayLoadPixels = true;
header->image = image;
}
else if (name[0] != '*')
{
char nameBuffer[MAX_PATH]{};
Components::Materials::FormatImagePath(nameBuffer, sizeof(nameBuffer), 0, 0, name.data());
Components::FileSystem::File iwi(nameBuffer);
if (!iwi.exists())
{
Components::Logger::Error(Game::ERR_FATAL, "Loading image '{}' failed!", iwi.getName());
return;
}
auto iwiBuffer = iwi.getBuffer();
const Game::GfxImageFileHeader* iwiHeader = reinterpret_cast<const Game::GfxImageFileHeader*>(iwiBuffer.data());
if (std::memcmp(iwiHeader->tag, "IWi", 3) && iwiHeader->version == 8)
{
Components::Logger::Error(Game::ERR_FATAL, "Image is not a valid IWi!");
return;
}
image->mapType = Game::MAPTYPE_2D;
image->cardMemory.platform[0] = iwiHeader->fileSizeForPicmip[0] - 32;
image->cardMemory.platform[1] = iwiHeader->fileSizeForPicmip[0] - 32;
image->texture.loadDef = builder->getAllocator()->allocate<Game::GfxImageLoadDef>();
if (!image->texture.loadDef)
{
Components::Logger::Error(Game::ERR_FATAL, "Failed to allocate GfxImageLoadDef structure!");
return;
}
image->texture.loadDef->flags = iwiHeader->flags;
image->texture.loadDef->levelCount = 0;
image->width = iwiHeader->dimensions[0];
image->height = iwiHeader->dimensions[1];
image->depth = iwiHeader->dimensions[2];
switch (iwiHeader->format)
{
case Game::IMG_FORMAT_BITMAP_RGBA:
{
image->texture.loadDef->format = 21;
break;
}
case Game::IMG_FORMAT_BITMAP_RGB:
{
image->texture.loadDef->format = 20;
break;
}
case Game::IMG_FORMAT_DXT1:
{
image->texture.loadDef->format = 0x31545844;
break;
}
case Game::IMG_FORMAT_DXT3:
{
image->texture.loadDef->format = 0x33545844;
break;
}
case Game::IMG_FORMAT_DXT5:
{
image->texture.loadDef->format = 0x35545844;
break;
}
default:
{
break;
}
}
header->image = image;
}
header->image = builder->getIW4OfApi()->read<Game::GfxImage>(Game::XAssetType::ASSET_TYPE_IMAGE, name);
}
void IGfxImage::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)

View File

@ -1,44 +1,11 @@
#include <STDInclude.hpp>
#include "IGfxLightDef.hpp"
#define IW4X_LIGHT_VERSION "0"
namespace Assets
{
void IGfxLightDef::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
Components::FileSystem::File mapFile(std::format("lights/{}.iw4xLight", name));
if (mapFile.exists())
{
Utils::Stream::Reader reader(builder->getAllocator(), mapFile.getBuffer());
char* magic = reader.readArray<char>(7);
if (std::memcmp(magic, "IW4xLit", 7))
{
Components::Logger::Error(Game::ERR_FATAL, "Reading light '{}' failed, header is invalid!", name);
}
std::string version;
version.push_back(reader.read<char>());
if (version != IW4X_LIGHT_VERSION)
{
Components::Logger::Error(Game::ERR_FATAL, "Reading light '{}' failed, expected version is {}, but it was {}!", name, IW4X_LIGHT_VERSION, version);
}
Game::GfxLightDef* asset = reader.readObject<Game::GfxLightDef>();
header->lightDef = asset;
if (asset->name)
{
asset->name = reader.readCString();
}
if (asset->attenuation.image)
{
asset->attenuation.image = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader.readString(), builder).image;
}
}
header->lightDef = builder->getIW4OfApi()->read<Game::GfxLightDef>(Game::XAssetType::ASSET_TYPE_LIGHT_DEF, name);
}
void IGfxLightDef::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)

View File

@ -1,450 +1,16 @@
#include <STDInclude.hpp>
#include "IGfxWorld.hpp"
#define IW4X_GFXMAP_VERSION 1
// The xmodel vehicle_small_hatch_green_destructible_mp causes EXTREME lag
// when placed in the world, for reasons unknown.
//
// Something happens with the SModelSurfIterator which makes it load garbage
// as an XSurface in the middle of otherwise valid surfaces. This bug is very
// easy to reproduce with an empty map and just this car in the middle
//
// As of know we do not know why the iterator corruption occurs or what causes
// it. It doesn't seem linked to the SModel, nor to the materials or techsets,
// nor to the sortkeys, nor to the tilemode, boneinfo, and so on. So for now
// and to make it work for majority of users, we just swap the car. (no, using
// the identical car from iw4's favela_escape doesn't work either!)
//
// Two other models have this problem: ch_apartment_9story_noentry_02 and
// ch_apartment_5story_noentry_01
// But these exist in mp_vacant in slightly different versions, and can be
// swapped safely by deleting the two .iw4XModel files and requiring mp_vacant
// or a minimal zone containing just these two models.
//
#define SWAP_GREEN_VEHICLE_XMODEL 1
namespace Assets
{
void IGfxWorld::loadGfxWorldDpvsStatic(Game::GfxWorld* world, Game::GfxWorldDpvsStatic* asset, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader)
{
if (asset->sortedSurfIndex)
{
asset->sortedSurfIndex = reader->readArray<unsigned short>(asset->staticSurfaceCount + asset->staticSurfaceCountNoDecal);
}
if (asset->smodelInsts)
{
asset->smodelInsts = reader->readArray<Game::GfxStaticModelInst>(asset->smodelCount);
}
if (asset->surfaces)
{
asset->surfaces = reader->readArray<Game::GfxSurface>(world->surfaceCount);
for (unsigned int i = 0; i < world->surfaceCount; ++i)
{
Game::GfxSurface* surface = &asset->surfaces[i];
if (surface->material)
{
world->dpvs.surfaces[i].material = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader->readString(), builder).material;
}
}
}
if (asset->surfacesBounds)
{
asset->surfacesBounds = reader->readArray<Game::GfxSurfaceBounds>(world->surfaceCount);
}
if (asset->smodelDrawInsts)
{
asset->smodelDrawInsts = reader->readArray<Game::GfxStaticModelDrawInst>(asset->smodelCount);
for (unsigned int i = 0; i < asset->smodelCount; ++i)
{
Game::GfxStaticModelDrawInst* model = &asset->smodelDrawInsts[i];
if (model->model)
{
auto name = reader->readString();
model->model = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_XMODEL, name, builder).model;
assert(model->model);
}
}
}
}
void IGfxWorld::loadGfxWorldDraw(Game::GfxWorldDraw* asset, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader)
{
if (asset->reflectionProbes)
{
asset->reflectionProbes = reader->readArray<Game::GfxImage*>(asset->reflectionProbeCount);
for (unsigned int i = 0; i < asset->reflectionProbeCount; ++i)
{
asset->reflectionProbes[i] = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader->readString(), builder).image;
}
}
if (asset->reflectionProbeOrigins)
{
asset->reflectionProbeOrigins = reader->readArray<Game::GfxReflectionProbe>(asset->reflectionProbeCount);
}
if (asset->lightmaps)
{
asset->lightmaps = reader->readArray<Game::GfxLightmapArray>(asset->lightmapCount);
for (int i = 0; i < asset->lightmapCount; ++i)
{
Game::GfxLightmapArray* lightmapArray = &asset->lightmaps[i];
if (lightmapArray->primary)
{
lightmapArray->primary = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader->readString(), builder).image;
}
if (lightmapArray->secondary)
{
lightmapArray->secondary = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader->readString(), builder).image;
}
}
}
if (asset->lightmapOverridePrimary)
{
asset->lightmapOverridePrimary = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader->readString(), builder).image;
}
if (asset->lightmapOverrideSecondary)
{
asset->lightmapOverrideSecondary = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader->readString(), builder).image;
}
// saveGfxWorldVertexData
{
if (asset->vd.vertices)
{
asset->vd.vertices = reader->readArray<Game::GfxWorldVertex>(asset->vertexCount);
}
}
// saveGfxWorldVertexLayerData
{
if (asset->vld.data)
{
// no align for char
asset->vld.data = reader->readArray<char>(asset->vertexLayerDataSize);
}
}
if (asset->indices)
{
asset->indices = reader->readArray<unsigned short>(asset->indexCount);
}
}
void IGfxWorld::load(Game::XAssetHeader* header, const std::string& _name, Components::ZoneBuilder::Zone* builder)
{
std::string name = _name;
Utils::String::Replace(name, "maps/mp/", "");
Utils::String::Replace(name, ".d3dbsp", "");
Components::FileSystem::File mapFile(std::format("gfxworld/{}.iw4xGfxWorld", name));
if (mapFile.exists())
{
Utils::Stream::Reader reader(builder->getAllocator(), mapFile.getBuffer());
__int64 magic = reader.read<__int64>();
if (std::memcmp(&magic, "IW4xGfxW", 8))
{
Components::Logger::Error(Game::ERR_FATAL, "Reading gfxworld '{}' failed, header is invalid!", name);
}
int version = reader.read<int>();
if (version != IW4X_GFXMAP_VERSION)
{
Components::Logger::Error(Game::ERR_FATAL, "Reading gfxworld '{}' failed, expected version is {}, but it was {}!", name, IW4X_GFXMAP_VERSION, version);
}
Game::GfxWorld* asset = reader.readObject<Game::GfxWorld>();
header->gfxWorld = asset;
if (asset->name)
{
asset->name = reader.readCString();
}
if (asset->baseName)
{
asset->baseName = reader.readCString();
}
if (asset->skies)
{
asset->skies = reader.readArray<Game::GfxSky>(asset->skyCount);
for (int i = 0; i < asset->skyCount; ++i)
{
Game::GfxSky* sky = &asset->skies[i];
if (sky->skyStartSurfs)
{
sky->skyStartSurfs = reader.readArray<int>(sky->skySurfCount);
}
if (sky->skyImage)
{
sky->skyImage = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader.readString(), builder).image;
}
}
}
// GfxWorldDpvsPlanes
{
if (asset->dpvsPlanes.planes)
{
void* oldPtr = asset->dpvsPlanes.planes;
asset->dpvsPlanes.planes = reader.readArray<Game::cplane_s>(asset->planeCount);
if (builder->getAllocator()->isPointerMapped(oldPtr))
{
asset->dpvsPlanes.planes = builder->getAllocator()->getPointer<Game::cplane_s>(oldPtr);
}
else
{
builder->getAllocator()->mapPointer(oldPtr, asset->dpvsPlanes.planes);
Components::Logger::Print("GfxWorld dpvs planes not mapped. This shouldn't happen. Make sure to load the ClipMap first!\n");
}
}
if (asset->dpvsPlanes.nodes)
{
asset->dpvsPlanes.nodes = reader.readArray<unsigned short>(asset->nodeCount);
}
}
int cellCount = asset->dpvsPlanes.cellCount;
if (asset->aabbTreeCounts)
{
asset->aabbTreeCounts = reader.readArray<Game::GfxCellTreeCount>(cellCount);
}
if (asset->aabbTrees)
{
asset->aabbTrees = reader.readArray<Game::GfxCellTree>(cellCount);
for (int i = 0; i < cellCount; ++i)
{
Game::GfxCellTree* cellTree = &asset->aabbTrees[i];
if (cellTree->aabbTree)
{
cellTree->aabbTree = reader.readArray<Game::GfxAabbTree>(asset->aabbTreeCounts[i].aabbTreeCount);
for (int j = 0; j < asset->aabbTreeCounts[i].aabbTreeCount; ++j)
{
Game::GfxAabbTree* aabbTree = &cellTree->aabbTree[j];
if (aabbTree->smodelIndexes)
{
unsigned short* oldPointer = aabbTree->smodelIndexes;
if(builder->getAllocator()->isPointerMapped(oldPointer))
{
// We still have to read it
reader.readArray<unsigned short>(aabbTree->smodelIndexCount);
aabbTree->smodelIndexes = builder->getAllocator()->getPointer<unsigned short>(oldPointer);
}
else
{
aabbTree->smodelIndexes = reader.readArray<unsigned short>(aabbTree->smodelIndexCount);
for (unsigned short k = 0; k < aabbTree->smodelIndexCount; ++k)
{
builder->getAllocator()->mapPointer(&oldPointer[k], &aabbTree->smodelIndexes[k]);
}
}
}
}
}
}
}
if (asset->cells)
{
asset->cells = reader.readArray<Game::GfxCell>(cellCount);
for (int i = 0; i < cellCount; ++i)
{
Game::GfxCell* cell = &asset->cells[i];
if (cell->portals)
{
cell->portals = reader.readArray<Game::GfxPortal>(cell->portalCount);
for (int j = 0; j < cell->portalCount; ++j)
{
Game::GfxPortal* portal = &cell->portals[j];
if (portal->vertices)
{
portal->vertices = reader.readArray<Game::vec3_t>(portal->vertexCount);
}
}
}
if (cell->reflectionProbes)
{
cell->reflectionProbes = reader.readArray<char>(cell->reflectionProbeCount);
}
}
}
this->loadGfxWorldDraw(&asset->draw, builder, &reader);
// GfxLightGrid
{
if (asset->lightGrid.rowDataStart)
{
asset->lightGrid.rowDataStart = reader.readArray<unsigned short>((asset->lightGrid.maxs[asset->lightGrid.rowAxis] - asset->lightGrid.mins[asset->lightGrid.rowAxis]) + 1);
}
if (asset->lightGrid.rawRowData)
{
asset->lightGrid.rawRowData = reader.readArray<char>(asset->lightGrid.rawRowDataSize);
}
if (asset->lightGrid.entries)
{
asset->lightGrid.entries = reader.readArray<Game::GfxLightGridEntry>(asset->lightGrid.entryCount);
}
if (asset->lightGrid.colors)
{
asset->lightGrid.colors = reader.readArray<Game::GfxLightGridColors>(asset->lightGrid.colorCount);
}
}
if (asset->models)
{
asset->models = reader.readArray<Game::GfxBrushModel>(asset->modelCount);
}
if (asset->materialMemory)
{
asset->materialMemory = reader.readArray<Game::MaterialMemory>(asset->materialMemoryCount);
for (int i = 0; i < asset->materialMemoryCount; ++i)
{
Game::MaterialMemory* materialMemory = &asset->materialMemory[i];
if (materialMemory->material)
{
materialMemory->material = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader.readString(), builder).material;
}
}
}
if (asset->sun.spriteMaterial)
{
asset->sun.spriteMaterial = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader.readString(), builder).material;
}
if (asset->sun.flareMaterial)
{
asset->sun.flareMaterial = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader.readString(), builder).material;
}
if (asset->outdoorImage)
{
asset->outdoorImage = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader.readString(), builder).image;
}
if (asset->primaryLightCount > 0)
{
Utils::Stream::ClearPointer(&asset->primaryLightEntityShadowVis);
}
if (asset->dpvsDyn.dynEntClientCount[0] > 0)
{
Utils::Stream::ClearPointer(&asset->sceneDynModel);
Utils::Stream::ClearPointer(&asset->primaryLightDynEntShadowVis[0]);
Utils::Stream::ClearPointer(&asset->nonSunPrimaryLightForModelDynEnt);
}
if (asset->dpvsDyn.dynEntClientCount[1] > 0)
{
Utils::Stream::ClearPointer(&asset->sceneDynBrush);
Utils::Stream::ClearPointer(&asset->primaryLightDynEntShadowVis[1]);
}
if (asset->shadowGeom)
{
asset->shadowGeom = reader.readArray<Game::GfxShadowGeometry>(asset->primaryLightCount);
for (unsigned int i = 0; i < asset->primaryLightCount; ++i)
{
Game::GfxShadowGeometry* shadowGeometry = &asset->shadowGeom[i];
if (shadowGeometry->sortedSurfIndex)
{
shadowGeometry->sortedSurfIndex = reader.readArray<unsigned short>(shadowGeometry->surfaceCount);
}
if (shadowGeometry->smodelIndex)
{
shadowGeometry->smodelIndex = reader.readArray<unsigned short>(shadowGeometry->smodelCount);
}
}
}
if (asset->lightRegion)
{
asset->lightRegion = reader.readArray<Game::GfxLightRegion>(asset->primaryLightCount);
for (unsigned int i = 0; i < asset->primaryLightCount; ++i)
{
Game::GfxLightRegion* lightRegion = &asset->lightRegion[i];
if (lightRegion->hulls)
{
lightRegion->hulls = reader.readArray<Game::GfxLightRegionHull>(lightRegion->hullCount);
for (unsigned int j = 0; j < lightRegion->hullCount; ++j)
{
Game::GfxLightRegionHull* lightRegionHull = &lightRegion->hulls[j];
if (lightRegionHull->axis)
{
lightRegionHull->axis = reader.readArray<Game::GfxLightRegionAxis>(lightRegionHull->axisCount);
}
}
}
}
}
this->loadGfxWorldDpvsStatic(asset, &asset->dpvs, builder, &reader);
// Obsolete, IW3 has no support for that
if (asset->heroOnlyLights)
{
asset->heroOnlyLights = reader.readArray<Game::GfxHeroOnlyLight>(asset->heroOnlyLightCount);
}
}
header->gfxWorld = builder->getIW4OfApi()->read<Game::GfxWorld>(Game::XAssetType::ASSET_TYPE_GFXWORLD, _name);
}
void IGfxWorld::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{
Game::GfxWorld* asset = header.gfxWorld;
auto* asset = header.gfxWorld;
if (asset->draw.reflectionProbes)
{
for (unsigned int i = 0; i < asset->draw.reflectionProbeCount; ++i)
@ -455,7 +21,7 @@ namespace Assets
if (asset->draw.lightmaps)
{
for (int i = 0; i < asset->draw.lightmapCount; ++i)
for (auto i = 0; i < asset->draw.lightmapCount; ++i)
{
if (asset->draw.lightmaps[i].primary)
{
@ -599,7 +165,7 @@ namespace Assets
{
buffer->align(Utils::Stream::ALIGN_4);
Game::GfxImage** imageDest = buffer->dest<Game::GfxImage*>();
auto** imageDest = buffer->dest<Game::GfxImage*>();
buffer->saveArray(asset->reflectionProbes, asset->reflectionProbeCount);
for (unsigned int i = 0; i < asset->reflectionProbeCount; ++i)
@ -648,13 +214,13 @@ namespace Assets
buffer->align(Utils::Stream::ALIGN_4);
Game::GfxLightmapArray* lightmapArrayDestTable = buffer->dest<Game::GfxLightmapArray>();
auto* lightmapArrayDestTable = buffer->dest<Game::GfxLightmapArray>();
buffer->saveArray(asset->lightmaps, asset->lightmapCount);
for (int i = 0; i < asset->lightmapCount; ++i)
{
Game::GfxLightmapArray* lightmapArrayDest = &lightmapArrayDestTable[i];
Game::GfxLightmapArray* lightmapArray = &asset->lightmaps[i];
auto* lightmapArrayDest = &lightmapArrayDestTable[i];
auto* lightmapArray = &asset->lightmaps[i];
if (lightmapArray->primary)
{
@ -854,13 +420,13 @@ namespace Assets
SaveLogEnter("GfxSurface");
buffer->align(Utils::Stream::ALIGN_4);
Game::GfxSurface* destSurfaceTable = buffer->dest<Game::GfxSurface>();
auto* destSurfaceTable = buffer->dest<Game::GfxSurface>();
buffer->saveArray(asset->surfaces, world->surfaceCount);
for (unsigned int i = 0; i < world->surfaceCount; ++i)
{
Game::GfxSurface* surface = &asset->surfaces[i];
Game::GfxSurface* destSurface = &destSurfaceTable[i];
auto* surface = &asset->surfaces[i];
auto* destSurface = &destSurfaceTable[i];
if (surface->material)
{
@ -890,13 +456,13 @@ namespace Assets
SaveLogEnter("GfxStaticModelDrawInst");
buffer->align(Utils::Stream::ALIGN_4);
Game::GfxStaticModelDrawInst* destModelTable = buffer->dest<Game::GfxStaticModelDrawInst>();
auto* destModelTable = buffer->dest<Game::GfxStaticModelDrawInst>();
buffer->saveArray(asset->smodelDrawInsts, asset->smodelCount);
for (unsigned int i = 0; i < asset->smodelCount; ++i)
{
Game::GfxStaticModelDrawInst* model = &asset->smodelDrawInsts[i];
Game::GfxStaticModelDrawInst* destModel = &destModelTable[i];
auto* model = &asset->smodelDrawInsts[i];
auto* destModel = &destModelTable[i];
if (model->model)
{
@ -986,8 +552,8 @@ namespace Assets
Utils::Stream* buffer = builder->getBuffer();
SaveLogEnter("GfxWorld");
Game::GfxWorld* asset = header.gfxWorld;
Game::GfxWorld* dest = buffer->dest<Game::GfxWorld>();
auto* asset = header.gfxWorld;
auto* dest = buffer->dest<Game::GfxWorld>();
buffer->save(asset);
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);
@ -1012,13 +578,13 @@ namespace Assets
SaveLogEnter("GfxSky");
buffer->align(Utils::Stream::ALIGN_4);
Game::GfxSky* destSkyTable = buffer->dest<Game::GfxSky>();
auto* destSkyTable = buffer->dest<Game::GfxSky>();
buffer->saveArray(asset->skies, asset->skyCount);
for (int i = 0; i < asset->skyCount; ++i)
{
Game::GfxSky* destSky = &destSkyTable[i];
Game::GfxSky* sky = &asset->skies[i];
auto* destSky = &destSkyTable[i];
auto* sky = &asset->skies[i];
if (sky->skyStartSurfs)
{
@ -1059,13 +625,13 @@ namespace Assets
SaveLogEnter("GfxCellTree");
buffer->align(Utils::Stream::ALIGN_128);
Game::GfxCellTree* destCellTreeTable = buffer->dest<Game::GfxCellTree>();
auto* destCellTreeTable = buffer->dest<Game::GfxCellTree>();
buffer->saveArray(asset->aabbTrees, cellCount);
for (int i = 0; i < cellCount; ++i)
{
Game::GfxCellTree* destCellTree = &destCellTreeTable[i];
Game::GfxCellTree* cellTree = &asset->aabbTrees[i];
auto* destCellTree = &destCellTreeTable[i];
auto* cellTree = &asset->aabbTrees[i];
if (cellTree->aabbTree)
{
@ -1073,7 +639,7 @@ namespace Assets
SaveLogEnter("GfxAabbTree");
buffer->align(Utils::Stream::ALIGN_4);
Game::GfxAabbTree* destAabbTreeTable = buffer->dest<Game::GfxAabbTree>();
auto* destAabbTreeTable = buffer->dest<Game::GfxAabbTree>();
buffer->saveArray(cellTree->aabbTree, asset->aabbTreeCounts[i].aabbTreeCount);
// ok this one is based on some assumptions because the actual count is this
@ -1083,8 +649,8 @@ namespace Assets
for (int j = 0; j < asset->aabbTreeCounts[i].aabbTreeCount; ++j)
{
Game::GfxAabbTree* destAabbTree = &destAabbTreeTable[j];
Game::GfxAabbTree* aabbTree = &cellTree->aabbTree[j];
auto* destAabbTree = &destAabbTreeTable[j];
auto* aabbTree = &cellTree->aabbTree[j];
if (aabbTree->smodelIndexes)
{
@ -1122,13 +688,13 @@ namespace Assets
SaveLogEnter("GfxCell");
buffer->align(Utils::Stream::ALIGN_4);
Game::GfxCell* destCellTable = buffer->dest<Game::GfxCell>();
auto* destCellTable = buffer->dest<Game::GfxCell>();
buffer->saveArray(asset->cells, cellCount);
for (int i = 0; i < cellCount; ++i)
{
Game::GfxCell* destCell = &destCellTable[i];
Game::GfxCell* cell = &asset->cells[i];
auto* destCell = &destCellTable[i];
auto* cell = &asset->cells[i];
if (cell->portals)
{
@ -1136,13 +702,13 @@ namespace Assets
SaveLogEnter("GfxPortal");
buffer->align(Utils::Stream::ALIGN_4);
Game::GfxPortal* destPortalTable = buffer->dest<Game::GfxPortal>();
auto* destPortalTable = buffer->dest<Game::GfxPortal>();
buffer->saveArray(cell->portals, cell->portalCount);
for (int j = 0; j < cell->portalCount; ++j)
{
Game::GfxPortal* destPortal = &destPortalTable[j];
Game::GfxPortal* portal = &cell->portals[j];
auto* destPortal = &destPortalTable[j];
auto* portal = &cell->portals[j];
if (portal->vertices)
{
@ -1189,13 +755,13 @@ namespace Assets
SaveLogEnter("MaterialMemory");
buffer->align(Utils::Stream::ALIGN_4);
Game::MaterialMemory* destMaterialMemoryTable = buffer->dest<Game::MaterialMemory>();
auto* destMaterialMemoryTable = buffer->dest<Game::MaterialMemory>();
buffer->saveArray(asset->materialMemory, asset->materialMemoryCount);
for (int i = 0; i < asset->materialMemoryCount; ++i)
{
Game::MaterialMemory* destMaterialMemory = &destMaterialMemoryTable[i];
Game::MaterialMemory* materialMemory = &asset->materialMemory[i];
auto* destMaterialMemory = &destMaterialMemoryTable[i];
auto* materialMemory = &asset->materialMemory[i];
if (materialMemory->material)
{
@ -1284,13 +850,13 @@ namespace Assets
SaveLogEnter("GfxShadowGeometry");
buffer->align(Utils::Stream::ALIGN_4);
Game::GfxShadowGeometry* destShadowGeometryTable = buffer->dest<Game::GfxShadowGeometry>();
auto* destShadowGeometryTable = buffer->dest<Game::GfxShadowGeometry>();
buffer->saveArray(asset->shadowGeom, asset->primaryLightCount);
for (unsigned int i = 0; i < asset->primaryLightCount; ++i)
{
Game::GfxShadowGeometry* destShadowGeometry = &destShadowGeometryTable[i];
Game::GfxShadowGeometry* shadowGeometry = &asset->shadowGeom[i];
auto* destShadowGeometry = &destShadowGeometryTable[i];
auto* shadowGeometry = &asset->shadowGeom[i];
if (shadowGeometry->sortedSurfIndex)
{
@ -1317,13 +883,13 @@ namespace Assets
SaveLogEnter("GfxLightRegion");
buffer->align(Utils::Stream::ALIGN_4);
Game::GfxLightRegion* destLightRegionTable = buffer->dest<Game::GfxLightRegion>();
auto* destLightRegionTable = buffer->dest<Game::GfxLightRegion>();
buffer->saveArray(asset->lightRegion, asset->primaryLightCount);
for (unsigned int i = 0; i < asset->primaryLightCount; ++i)
{
Game::GfxLightRegion* destLightRegion = &destLightRegionTable[i];
Game::GfxLightRegion* lightRegion = &asset->lightRegion[i];
auto* destLightRegion = &destLightRegionTable[i];
auto* lightRegion = &asset->lightRegion[i];
if (lightRegion->hulls)
{
@ -1331,13 +897,13 @@ namespace Assets
SaveLogEnter("GfxLightRegionHull");
buffer->align(Utils::Stream::ALIGN_4);
Game::GfxLightRegionHull* destLightRegionHullTable = buffer->dest<Game::GfxLightRegionHull>();
auto* destLightRegionHullTable = buffer->dest<Game::GfxLightRegionHull>();
buffer->saveArray(lightRegion->hulls, lightRegion->hullCount);
for (unsigned int j = 0; j < lightRegion->hullCount; ++j)
{
Game::GfxLightRegionHull* destLightRegionHull = &destLightRegionHullTable[j];
Game::GfxLightRegionHull* lightRegionHull = &lightRegion->hulls[j];
auto* destLightRegionHull = &destLightRegionHullTable[j];
auto* lightRegionHull = &lightRegion->hulls[j];
if (lightRegionHull->axis)
{
@ -1373,7 +939,6 @@ namespace Assets
Utils::Stream::ClearPointer(&dest->heroOnlyLights);
}
//buffer->setPointerAssertion(false);
buffer->popBlock();
SaveLogExit();
}

View File

@ -18,8 +18,5 @@ namespace Assets
void savesunflare_t(Game::sunflare_t* asset, Game::sunflare_t* dest, Components::ZoneBuilder::Zone* builder);
void saveGfxWorldDpvsStatic(Game::GfxWorld* world, Game::GfxWorldDpvsStatic* asset, Game::GfxWorldDpvsStatic* dest, int planeCount, Components::ZoneBuilder::Zone* builder);
void saveGfxWorldDpvsDynamic(Game::GfxWorldDpvsDynamic* asset, Game::GfxWorldDpvsDynamic* dest, int cellCount, Components::ZoneBuilder::Zone* builder);
void loadGfxWorldDraw(Game::GfxWorldDraw* asset, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader);
void loadGfxWorldDpvsStatic(Game::GfxWorld* world, Game::GfxWorldDpvsStatic* asset, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader);
};
}

View File

@ -5,106 +5,16 @@ namespace Assets
{
void ILoadedSound::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
Components::FileSystem::File soundFile(std::format("loaded_sound/{}", name));
if (!soundFile.exists())
{
header->loadSnd = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).loadSnd;
return;
}
Game::LoadedSound* sound = builder->getAllocator()->allocate<Game::LoadedSound>();
if (!sound)
{
Components::Logger::Print("Error allocating memory for sound structure!\n");
return;
}
Game::LoadedSound* reference = nullptr;
if (!reference) reference = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_LOADED_SOUND, "weapons/c4_detpack/c4_drop_dirt1.wav").loadSnd;
std::memcpy(sound, reference, sizeof(Game::LoadedSound));
sound->sound.data = nullptr;
Utils::Stream::Reader reader(builder->getAllocator(), soundFile.getBuffer());
unsigned int chunkIDBuffer = reader.read<unsigned int>();
if (chunkIDBuffer != 0x46464952) // RIFF
{
Components::Logger::Error(Game::ERR_FATAL, "Reading sound '{}' failed, header is invalid!", name);
return;
}
unsigned int chunkSize = reader.read<unsigned int>();
unsigned int format = reader.read<unsigned int>();
if (format != 0x45564157) // WAVE
{
Components::Logger::Error(Game::ERR_FATAL, "Reading sound '{}' failed, header is invalid!", name);
return;
}
while (!sound->sound.data && !reader.end())
{
chunkIDBuffer = reader.read<unsigned int>();
chunkSize = reader.read<unsigned int>();
switch (chunkIDBuffer)
{
case 0x20746D66: // fmt
if (chunkSize >= 16)
{
sound->sound.info.format = reader.read<short>();
if (sound->sound.info.format != 1 && sound->sound.info.format != 17)
{
Components::Logger::Error(Game::ERR_FATAL, "Reading sound '{}' failed, invalid format!", name);
return;
}
sound->sound.info.channels = reader.read<short>();
sound->sound.info.rate = reader.read<int>();
sound->sound.info.samples = reader.read<int>();
sound->sound.info.block_size = reader.read<short>();
sound->sound.info.bits = reader.read<short>();
// skip any extra parameters
if (chunkSize > 16)
{
reader.seekRelative(chunkSize - 16);
}
}
break;
case 0x61746164: // data
sound->sound.info.data_len = chunkSize;
sound->sound.data = reader.readArray<char>(chunkSize);
break;
default:
if (chunkSize > 0)
{
reader.seekRelative(chunkSize);
}
break;
}
}
if (!sound->sound.info.data_ptr)
{
Components::Logger::Error(Game::ERR_FATAL, "Reading sound '{}' failed, invalid format!", name);
return;
}
sound->name = builder->getAllocator()->duplicateString(name);
header->loadSnd = sound;
header->loadSnd = builder->getIW4OfApi()->read<Game::LoadedSound>(Game::XAssetType::ASSET_TYPE_LOADED_SOUND, name);
}
void ILoadedSound::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{
AssertSize(Game::LoadedSound, 44);
Utils::Stream* buffer = builder->getBuffer();
Game::LoadedSound* asset = header.loadSnd;
Game::LoadedSound* dest = buffer->dest<Game::LoadedSound>();
auto* buffer = builder->getBuffer();
auto* asset = header.loadSnd;
auto* dest = buffer->dest<Game::LoadedSound>();
buffer->save(asset);
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);

View File

@ -5,55 +5,7 @@ namespace Assets
{
void IMapEnts::load(Game::XAssetHeader* header, const std::string& _name, Components::ZoneBuilder::Zone* builder)
{
std::string name = _name;
Utils::String::Replace(name, "maps/", "");
Utils::String::Replace(name, "mp/", "");
Utils::String::Replace(name, ".d3dbsp", "");
Components::FileSystem::File ents(std::format("mapents/{}.ents", name));
if (ents.exists())
{
Game::MapEnts* entites = builder->getAllocator()->allocate<Game::MapEnts>();
Game::MapEnts* orgEnts = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).mapEnts;
// TODO: Get rid of that
if (!orgEnts)
{
orgEnts = Components::AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_MAP_ENTS, "maps/iw4_credits.d3dbsp").mapEnts;
if (!orgEnts)
{
Game::DB_EnumXAssets(Game::XAssetType::ASSET_TYPE_MAP_ENTS, [](Game::XAssetHeader header, void* mapEnts)
{
if (!*reinterpret_cast<void**>(mapEnts))
{
*reinterpret_cast<Game::MapEnts**>(mapEnts) = header.mapEnts;
}
}, &orgEnts, false);
}
}
if (orgEnts)
{
std::memcpy(entites, orgEnts, sizeof Game::MapEnts);
}
else
{
entites->stageCount = 1;
entites->stages = builder->getAllocator()->allocate<Game::Stage>();
entites->stages[0].name = "stage 0";
entites->stages[0].triggerIndex = 0x400;
entites->stages[0].sunPrimaryLightIndex = 0x1;
}
std::string entityString = ents.getBuffer();
entites->name = builder->getAllocator()->duplicateString(std::format("maps/mp/{}.d3dbsp", name));
entites->entityString = builder->getAllocator()->duplicateString(entityString);
entites->numEntityChars = entityString.size() + 1;
header->mapEnts = entites;
}
header->mapEnts = builder->getIW4OfApi()->read<Game::MapEnts>(Game::XAssetType::ASSET_TYPE_MAP_ENTS, _name);
}
void IMapEnts::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)

View File

@ -1,730 +1,18 @@
#include <STDInclude.hpp>
#include "IMaterial.hpp"
#include <Utils/Json.hpp>
#define IW4X_MAT_BIN_VERSION "1"
#define IW4X_MAT_JSON_VERSION 1
namespace Assets
{
const std::unordered_map<std::string, std::string> techSetCorrespondance =
{
{"effect", "effect_blend"},
{"effect", "effect_blend"},
{"effect_nofog", "effect_blend_nofog"},
{"effect_zfeather", "effect_zfeather_blend"},
{"effect_zfeather_falloff", "effect_zfeather_falloff_add"},
{"effect_zfeather_nofog", "effect_zfeather_add_nofog"},
{"wc_unlit_add", "wc_unlit_add_lin"},
{"wc_unlit_distfalloff", "wc_unlit_distfalloff_replace"},
{"wc_unlit_multiply", "wc_unlit_multiply_lin"},
{"wc_unlit_falloff_add", "wc_unlit_falloff_add_lin"},
{"wc_unlit", "wc_unlit_replace_lin"},
{"wc_unlit_alphatest", "wc_unlit_blend_lin"},
{"wc_unlit_blend", "wc_unlit_blend_lin"},
{"wc_unlit_replace", "wc_unlit_replace_lin"},
{"wc_unlit_nofog", "wc_unlit_replace_lin_nofog_nocast" },
{"mc_unlit_replace", "mc_unlit_replace_lin"},
{"mc_unlit_nofog", "mc_unlit_blend_nofog_ua"},
{"mc_unlit", "mc_unlit_replace_lin_nocast"},
{"mc_unlit_alphatest", "mc_unlit_blend_lin"},
{"mc_effect_nofog", "mc_effect_blend_nofog"},
{"mc_effect_falloff_add_nofog", "mc_effect_falloff_add_nofog_eyeoffset"},
};
void IMaterial::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
if (!header->data) this->loadJson(header, name, builder); // Check if we want to load a material from disk
if (!header->data) this->loadBinary(header, name, builder); // Check if we want to load a material from disk (binary format)
if (!header->data) this->loadFromDisk(header, name, builder); // Check if we want to load a material from disk
if (!header->data) this->loadNative(header, name, builder); // Check if there is a native one
assert(header->data);
}
void IMaterial::loadJson(Game::XAssetHeader* header, const std::string& name, [[maybe_unused]] Components::ZoneBuilder::Zone* builder)
void IMaterial::loadFromDisk(Game::XAssetHeader* header, const std::string& name, [[maybe_unused]] Components::ZoneBuilder::Zone* builder)
{
Components::FileSystem::File materialInfo(std::format("materials/{}.iw4x.json", name));
if (!materialInfo.exists()) return;
Game::Material* asset = builder->getAllocator()->allocate<Game::Material>();
nlohmann::json materialJson;
try
{
materialJson = nlohmann::json::parse(materialInfo.getBuffer());
}
catch (const std::exception& e)
{
Components::Logger::Print("Invalid material json for {} (broken json {})\n", name, e.what());
}
if (!materialJson.is_object())
{
Components::Logger::Print("Invalid material json for {} (Is it zonebuilder format?)\n", name);
return;
}
if (materialJson["version"].get<int>() != IW4X_MAT_JSON_VERSION)
{
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid material json version for {}, expected {} and got {}\n", name, IW4X_MAT_JSON_VERSION, materialJson["version"].get<std::string>());
return;
}
try
{
asset->info.name = builder->getAllocator()->duplicateString(materialJson["name"].get<std::string>());
asset->info.gameFlags = static_cast<char>(Utils::Json::ReadFlags(materialJson["gameFlags"].get<std::string>(), sizeof(char)));
asset->info.sortKey = materialJson["sortKey"].get<char>();
// * We do techset later * //
asset->info.textureAtlasRowCount = materialJson["textureAtlasRowCount"].get<unsigned char>();
asset->info.textureAtlasColumnCount = materialJson["textureAtlasColumnCount"].get<unsigned char>();
asset->info.surfaceTypeBits = static_cast<unsigned int>(Utils::Json::ReadFlags(materialJson["surfaceTypeBits"].get<std::string>(), sizeof(int)));
asset->info.hashIndex = materialJson["hashIndex"].get<unsigned short>();
asset->cameraRegion = materialJson["cameraRegion"].get<char>();
}
catch (const nlohmann::json::exception& e)
{
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid material json for {} (broken json {})\n", name, e.what());
return;
}
if (materialJson["gfxDrawSurface"].is_object())
{
asset->info.drawSurf.fields.customIndex = materialJson["gfxDrawSurface"]["customIndex"].get<long long>();
asset->info.drawSurf.fields.hasGfxEntIndex = materialJson["gfxDrawSurface"]["hasGfxEntIndex"].get<long long>();
asset->info.drawSurf.fields.materialSortedIndex = materialJson["gfxDrawSurface"]["materialSortedIndex"].get<long long>();
asset->info.drawSurf.fields.objectId = materialJson["gfxDrawSurface"]["objectId"].get<long long>();
asset->info.drawSurf.fields.prepass = materialJson["gfxDrawSurface"]["prepass"].get<long long>();
asset->info.drawSurf.fields.primarySortKey = materialJson["gfxDrawSurface"]["primarySortKey"].get<long long>();
asset->info.drawSurf.fields.reflectionProbeIndex = materialJson["gfxDrawSurface"]["reflectionProbeIndex"].get<long long>();
asset->info.drawSurf.fields.sceneLightIndex = materialJson["gfxDrawSurface"]["sceneLightIndex"].get<long long>();
asset->info.drawSurf.fields.surfType = materialJson["gfxDrawSurface"]["surfType"].get<long long>();
asset->info.drawSurf.fields.unused = materialJson["gfxDrawSurface"]["unused"].get<long long>();
asset->info.drawSurf.fields.useHeroLighting = materialJson["gfxDrawSurface"]["useHeroLighting"].get<long long>();
}
asset->stateFlags = static_cast<char>(Utils::Json::ReadFlags(materialJson["stateFlags"].get<std::string>(), sizeof(char)));
if (materialJson["textureTable"].is_array())
{
nlohmann::json::array_t textureTable = materialJson["textureTable"];
asset->textureCount = static_cast<unsigned char>(textureTable.size());
asset->textureTable = builder->getAllocator()->allocateArray<Game::MaterialTextureDef>(asset->textureCount);
for (size_t i = 0; i < textureTable.size(); i++)
{
auto& textureJson = textureTable[i];
if (textureJson.is_object())
{
Game::MaterialTextureDef* textureDef = &asset->textureTable[i];
textureDef->semantic = textureJson["semantic"].get<Game::TextureSemantic>();
textureDef->samplerState = textureJson["samplerState"].get<char>();
textureDef->nameStart = textureJson["nameStart"].get<char>();
textureDef->nameEnd = textureJson["nameEnd"].get<char>();
textureDef->nameHash = textureJson["nameHash"].get<unsigned int>();
if (textureDef->semantic == Game::TextureSemantic::TS_WATER_MAP)
{
Game::water_t* water = builder->getAllocator()->allocate<Game::water_t>();
if (textureJson["water"].is_object())
{
auto& waterJson = textureJson["water"];
if (waterJson["image"].is_string())
{
auto imageName = waterJson["image"].get<std::string>();
water->image = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, imageName, builder).image;
}
water->amplitude = waterJson["amplitude"].get<float>();
water->M = waterJson["M"].get<int>();
water->N = waterJson["N"].get<int>();
water->Lx = waterJson["Lx"].get<float>();
water->Lz = waterJson["Lz"].get<float>();
water->gravity = waterJson["gravity"].get<float>();
water->windvel = waterJson["windvel"].get<float>();
auto winddir = waterJson["winddir"].get<std::vector<float>>();
if (winddir.size() == 2)
{
std::copy(winddir.begin(), winddir.end(), water->winddir);
}
auto codeConstant = waterJson["codeConstant"].get<std::vector<float>>();
if (codeConstant.size() == 4)
{
std::copy(codeConstant.begin(), codeConstant.end(), water->codeConstant);
}
/// H0
[[maybe_unused]] auto idealSize = water->M * water->N * sizeof(Game::complex_s);
auto h064 = waterJson["H0"].get<std::string>();
auto predictedSize = static_cast<unsigned long>(std::ceilf((h064.size() / 4.f) * 3.f));
assert(predictedSize >= idealSize);
auto h0 = reinterpret_cast<Game::complex_s*>(builder->getAllocator()->allocate(predictedSize));
[[maybe_unused]] auto h0Result = base64_decode(
h064.data(),
h064.size(),
reinterpret_cast<unsigned char*>(h0),
&predictedSize
);
assert(h0Result == CRYPT_OK);
water->H0 = h0;
/// WTerm
auto wTerm64 = waterJson["wTerm"].get<std::string>();
auto predictedWTermSize = static_cast<unsigned long>(std::ceilf((wTerm64.size() / 4.f) * 3.f));
auto wTerm = reinterpret_cast<float*>(builder->getAllocator()->allocate(predictedWTermSize));
[[maybe_unused]] auto wTermResult = base64_decode(
wTerm64.data(),
wTerm64.size(),
reinterpret_cast<unsigned char*>(wTerm),
&predictedWTermSize
);
assert(wTermResult == CRYPT_OK);
water->wTerm = wTerm;
}
textureDef->u.water = water;
}
else
{
textureDef->u.image = nullptr;
if (textureJson["image"].is_string())
{
textureDef->u.image = Components::AssetHandler::FindAssetForZone
(
Game::XAssetType::ASSET_TYPE_IMAGE,
textureJson["image"].get<std::string>(),
builder
).image;
}
}
}
}
}
// Statebits
if (materialJson["stateBitsEntry"].is_array())
{
nlohmann::json::array_t stateBitsEntry = materialJson["stateBitsEntry"];
for (size_t i = 0; i < std::min(stateBitsEntry.size(), 48u); i++)
{
asset->stateBitsEntry[i] = stateBitsEntry[i].get<char>();
}
}
if (materialJson["stateBitsTable"].is_array())
{
nlohmann::json::array_t array = materialJson["stateBitsTable"];
asset->stateBitsCount = static_cast<unsigned char>(array.size());
asset->stateBitsTable = builder->getAllocator()->allocateArray<Game::GfxStateBits>(array.size());
size_t statebitTableIndex = 0;
for (auto& jsonStateBitEntry : array)
{
auto stateBit = &asset->stateBitsTable[statebitTableIndex++];
unsigned int loadbits0 = 0;
unsigned int loadbits1 = 0;
#define READ_INT_LB_FROM_JSON(x) unsigned int x = jsonStateBitEntry[#x].get<unsigned int>()
#define READ_BOOL_LB_FROM_JSON(x) bool x = jsonStateBitEntry[#x].get<bool>()
READ_INT_LB_FROM_JSON(srcBlendRgb);
READ_INT_LB_FROM_JSON(dstBlendRgb);
READ_INT_LB_FROM_JSON(blendOpRgb);
READ_INT_LB_FROM_JSON(srcBlendAlpha);
READ_INT_LB_FROM_JSON(dstBlendAlpha);
READ_INT_LB_FROM_JSON(blendOpAlpha);
READ_INT_LB_FROM_JSON(depthTest);
READ_INT_LB_FROM_JSON(polygonOffset);
const auto alphaTest = jsonStateBitEntry["alphaTest"].get<std::string>();
const auto cullFace = jsonStateBitEntry["cullFace"].get<std::string>();
READ_BOOL_LB_FROM_JSON(colorWriteRgb);
READ_BOOL_LB_FROM_JSON(colorWriteAlpha);
READ_BOOL_LB_FROM_JSON(polymodeLine);
READ_BOOL_LB_FROM_JSON(gammaWrite);
READ_BOOL_LB_FROM_JSON(depthWrite);
READ_BOOL_LB_FROM_JSON(stencilFrontEnabled);
READ_BOOL_LB_FROM_JSON(stencilBackEnabled);
READ_INT_LB_FROM_JSON(stencilFrontPass);
READ_INT_LB_FROM_JSON(stencilFrontFail);
READ_INT_LB_FROM_JSON(stencilFrontZFail);
READ_INT_LB_FROM_JSON(stencilFrontFunc);
READ_INT_LB_FROM_JSON(stencilBackPass);
READ_INT_LB_FROM_JSON(stencilBackFail);
READ_INT_LB_FROM_JSON(stencilBackZFail);
READ_INT_LB_FROM_JSON(stencilBackFunc);
loadbits0 |= srcBlendRgb << Game::GFXS0_SRCBLEND_RGB_SHIFT;
loadbits0 |= dstBlendRgb << Game::GFXS0_DSTBLEND_RGB_SHIFT;
loadbits0 |= blendOpRgb << Game::GFXS0_BLENDOP_RGB_SHIFT;
loadbits0 |= srcBlendAlpha << Game::GFXS0_SRCBLEND_ALPHA_SHIFT;
loadbits0 |= dstBlendAlpha << Game::GFXS0_DSTBLEND_ALPHA_SHIFT;
loadbits0 |= blendOpAlpha << Game::GFXS0_BLENDOP_ALPHA_SHIFT;
if (depthTest == -1)
{
loadbits1 |= Game::GFXS1_DEPTHTEST_DISABLE;
}
else
{
loadbits1 |= depthTest << Game::GFXS1_DEPTHTEST_SHIFT;
}
loadbits1 |= polygonOffset << Game::GFXS1_POLYGON_OFFSET_SHIFT;
if (alphaTest == "disable")
{
loadbits0 |= Game::GFXS0_ATEST_DISABLE;
}
else if (alphaTest == ">0")
{
loadbits0 |= Game::GFXS0_ATEST_GT_0;
}
else if (alphaTest == "<128")
{
loadbits0 |= Game::GFXS0_ATEST_LT_128;
}
else if (alphaTest == ">=128")
{
loadbits0 |= Game::GFXS0_ATEST_GE_128;
}
else
{
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid alphatest loadbit0 '{}' in material {}\n", alphaTest, name);
return;
}
if (cullFace == "none")
{
loadbits0 |= Game::GFXS0_CULL_NONE;
}
else if (cullFace == "back")
{
loadbits0 |= Game::GFXS0_CULL_BACK;
}
else if (cullFace == "front")
{
loadbits0 |= Game::GFXS0_CULL_FRONT;
}
else
{
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid cullFace loadbit0 '{}' in material {}\n", cullFace, name);
return;
}
if (gammaWrite)
{
loadbits0 |= Game::GFXS0_GAMMAWRITE;
}
if (colorWriteAlpha)
{
loadbits0 |= Game::GFXS0_COLORWRITE_ALPHA;
}
if (colorWriteRgb)
{
loadbits0 |= Game::GFXS0_COLORWRITE_RGB;
}
if (polymodeLine)
{
loadbits0 |= Game::GFXS0_POLYMODE_LINE;
}
if (depthWrite)
{
loadbits1 |= Game::GFXS1_DEPTHWRITE;
}
if (stencilFrontEnabled)
{
loadbits1 |= Game::GFXS1_STENCIL_FRONT_ENABLE;
}
if (stencilBackEnabled)
{
loadbits1 |= Game::GFXS1_STENCIL_BACK_ENABLE;
}
loadbits1 |= stencilFrontPass << Game::GFXS1_STENCIL_FRONT_PASS_SHIFT;
loadbits1 |= stencilFrontFail << Game::GFXS1_STENCIL_FRONT_FAIL_SHIFT;
loadbits1 |= stencilFrontZFail << Game::GFXS1_STENCIL_FRONT_ZFAIL_SHIFT;
loadbits1 |= stencilFrontFunc << Game::GFXS1_STENCIL_FRONT_FUNC_SHIFT;
loadbits1 |= stencilBackPass << Game::GFXS1_STENCIL_BACK_PASS_SHIFT;
loadbits1 |= stencilBackFail << Game::GFXS1_STENCIL_BACK_FAIL_SHIFT;
loadbits1 |= stencilBackZFail << Game::GFXS1_STENCIL_BACK_ZFAIL_SHIFT;
loadbits1 |= stencilBackFunc << Game::GFXS1_STENCIL_BACK_FUNC_SHIFT;
stateBit->loadBits[0] = loadbits0;
stateBit->loadBits[1] = loadbits1;
}
// Constant table
if (materialJson["constantTable"].is_array())
{
nlohmann::json::array_t constants = materialJson["constantTable"];
asset->constantCount = static_cast<char>(constants.size());
auto table = builder->getAllocator()->allocateArray<Game::MaterialConstantDef>(asset->constantCount);
for (size_t constantIndex = 0; constantIndex < asset->constantCount; constantIndex++)
{
auto& constant = constants[constantIndex];
auto entry = &table[constantIndex];
auto litVec = constant["literal"].get<std::vector<float>>();
std::copy(litVec.begin(), litVec.end(), entry->literal);
auto constantName = constant["name"].get<std::string>();
std::copy(constantName.begin(), constantName.end(), entry->name);
entry->nameHash = constant["nameHash"].get<unsigned int>();
}
asset->constantTable = table;
}
if (materialJson["techniqueSet"].is_string())
{
const std::string techsetName = materialJson["techniqueSet"].get<std::string>();
asset->techniqueSet = findWorkingTechset(techsetName, asset, builder);
if (asset->techniqueSet == nullptr)
{
assert(false);
}
}
header->material = asset;
}
}
Game::MaterialTechniqueSet* IMaterial::findWorkingTechset(std::string techsetName, [[maybe_unused]] Game::Material* material, Components::ZoneBuilder::Zone* builder) const
{
Game::MaterialTechniqueSet* techset;
// Pass 1: Identical techset (1:1)
techset = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET, techsetName, builder).techniqueSet;
if (techset != nullptr)
{
return techset;
}
// We do no more cause we use CoD4 techset and they should always be present
// If one day we want to go back to mw2 fallback we can add extra steps here!
return nullptr;
}
void IMaterial::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
static const char* techsetSuffix[] =
{
"_lin",
"_add_lin",
"_replace",
"_eyeoffset",
"_blend",
"_blend_nofog",
"_add",
"_nofog",
"_nocast",
"_add_lin_nofog",
};
Components::FileSystem::File materialFile(std::format("materials/{}.iw4xMaterial", name));
if (!materialFile.exists()) return;
Utils::Stream::Reader reader(builder->getAllocator(), materialFile.getBuffer());
char* magic = reader.readArray<char>(7);
if (std::memcmp(magic, "IW4xMat", 7) != 0)
{
Components::Logger::Error(Game::ERR_FATAL, "Reading material '{}' failed, header is invalid!", name);
}
std::string version;
version.push_back(reader.read<char>());
if (version != IW4X_MAT_BIN_VERSION)
{
Components::Logger::Error(Game::ERR_FATAL, "Reading material '{}' failed, expected version is {}, but it was {}!", name, IW4X_MAT_BIN_VERSION, version);
}
auto* asset = reader.readObject<Game::Material>();
if (asset->info.name)
{
asset->info.name = reader.readCString();
}
if (asset->techniqueSet)
{
std::string techsetName = reader.readString();
if (!techsetName.empty() && techsetName.front() == ',') techsetName.erase(techsetName.begin());
asset->techniqueSet = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET, techsetName, builder).techniqueSet;
if (!asset->techniqueSet)
{
// Workaround for effect techsets having _nofog suffix
std::string suffix;
if (Utils::String::StartsWith(techsetName, "effect_") && Utils::String::EndsWith(techsetName, "_nofog"))
{
suffix = "_nofog";
Utils::String::Replace(techsetName, suffix, "");
}
for (int i = 0; i < ARRAYSIZE(techsetSuffix); ++i)
{
Game::MaterialTechniqueSet* techsetPtr = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET, (techsetName + techsetSuffix[i] + suffix), builder).techniqueSet;
if (techsetPtr)
{
asset->techniqueSet = techsetPtr;
if (asset->techniqueSet->name[0] == ',') continue; // Try to find a better one
Components::Logger::Print("Techset '{}' has been mapped to '{}'\n", techsetName, asset->techniqueSet->name);
break;
}
}
}
else
{
Components::Logger::Print("Techset {} exists with the same name in iw4, and was mapped 1:1 with {}\n", techsetName, asset->techniqueSet->name);
}
if (!asset->techniqueSet)
{
Components::Logger::Error(Game::ERR_FATAL, "Missing techset: '{}' not found", techsetName);
}
}
if (asset->textureTable)
{
asset->textureTable = reader.readArray<Game::MaterialTextureDef>(asset->textureCount);
for (char i = 0; i < asset->textureCount; ++i)
{
Game::MaterialTextureDef* textureDef = &asset->textureTable[i];
if (textureDef->semantic == Game::TextureSemantic::TS_WATER_MAP)
{
if (textureDef->u.water)
{
Game::water_t* water = reader.readObject<Game::water_t>();
textureDef->u.water = water;
// Save_water_t
if (water->H0)
{
water->H0 = reader.readArray<Game::complex_s>(water->M * water->N);
}
if (water->wTerm)
{
water->wTerm = reader.readArray<float>(water->M * water->N);
}
if (water->image)
{
water->image = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader.readString(), builder).image;
}
}
}
else if (textureDef->u.image)
{
textureDef->u.image = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader.readString(), builder).image;
}
}
}
if (asset->constantTable)
{
asset->constantTable = reader.readArray<Game::MaterialConstantDef>(asset->constantCount);
}
if (asset->stateBitsTable)
{
asset->stateBitsTable = reader.readArray<Game::GfxStateBits>(asset->stateBitsCount);
}
header->material = asset;
static thread_local bool replacementFound;
replacementFound = false;
// Find correct sortkey by comparing techsets
Game::DB_EnumXAssetEntries(Game::XAssetType::ASSET_TYPE_MATERIAL, [asset](Game::XAssetEntry* entry)
{
if (!replacementFound)
{
Game::XAssetHeader header = entry->asset.header;
const char* name = asset->techniqueSet->name;
if (name[0] == ',') ++name;
if (std::string(name) == header.material->techniqueSet->name)
{
asset->info.sortKey = header.material->info.sortKey;
// This is temp, as nobody has time to fix materials
asset->stateBitsCount = header.material->stateBitsCount;
asset->stateBitsTable = header.material->stateBitsTable;
std::memcpy(asset->stateBitsEntry, header.material->stateBitsEntry, ARRAYSIZE(asset->stateBitsEntry));
asset->constantCount = header.material->constantCount;
asset->constantTable = header.material->constantTable;
Components::Logger::Print("For {}, copied constants & statebits from {}\n", asset->info.name, header.material->info.name);
replacementFound = true;
}
}
}, false);
if (!replacementFound)
{
auto techsetMatches = [](Game::Material* m1, Game::Material* m2)
{
Game::MaterialTechniqueSet* t1 = m1->techniqueSet;
Game::MaterialTechniqueSet* t2 = m2->techniqueSet;
if (!t1 || !t2) return false;
if (t1->remappedTechniqueSet && t2->remappedTechniqueSet && std::string(t1->remappedTechniqueSet->name) == t2->remappedTechniqueSet->name) return true;
for (int i = 0; i < ARRAYSIZE(t1->techniques); ++i)
{
if (!t1->techniques[i] && !t2->techniques[i]) continue;;
if (!t1->techniques[i] || !t2->techniques[i]) return false;
// Apparently, this is really not that important
//if (t1->techniques[i]->flags != t2->techniques[i]->flags) return false;
}
return true;
};
Game::DB_EnumXAssetEntries(Game::XAssetType::ASSET_TYPE_MATERIAL, [asset, techsetMatches](Game::XAssetEntry* entry)
{
if (!replacementFound)
{
Game::XAssetHeader header = entry->asset.header;
if (techsetMatches(header.material, asset))
{
Components::Logger::Print("Material {} with techset {} has been mapped to {}\n", asset->info.name, asset->techniqueSet->name, header.material->techniqueSet->name);
asset->info.sortKey = header.material->info.sortKey;
replacementFound = true;
}
}
}, false);
}
if (!replacementFound && asset->techniqueSet)
{
Components::Logger::Print("No replacement found for material {} with techset {}\n", asset->info.name, asset->techniqueSet->name);
std::string techName = asset->techniqueSet->name;
if (const auto itr = techSetCorrespondance.find(techName); itr != techSetCorrespondance.end())
{
auto& iw4TechSetName = itr->second;
auto* iw4TechSet = Game::DB_FindXAssetEntry(Game::ASSET_TYPE_TECHNIQUE_SET, iw4TechSetName.data());
if (iw4TechSet)
{
Game::DB_EnumXAssetEntries(Game::XAssetType::ASSET_TYPE_MATERIAL, [asset, iw4TechSet](Game::XAssetEntry* entry)
{
if (!replacementFound)
{
Game::XAssetHeader header = entry->asset.header;
Components::Logger::Print("Material {} with techset {} has been mapped to {} (last chance!), taking the sort key of material {}\n",
asset->info.name, asset->techniqueSet->name, header.material->techniqueSet->name, header.material->info.name);
// Yeah this has a tendency to fuck up a LOT of transparent materials
if (header.material->techniqueSet == iw4TechSet->asset.header.techniqueSet && std::string(header.material->info.name).find("icon") != std::string::npos)
{
Components::Logger::Print("Material {} with techset {} has been mapped to {} (last chance!), taking the sort key of material {}\n",
asset->info.name, asset->techniqueSet->name, header.material->techniqueSet->name, header.material->info.name);
asset->info.sortKey = header.material->info.sortKey;
asset->techniqueSet = iw4TechSet->asset.header.techniqueSet;
// this is terrible!
asset->stateBitsCount = header.material->stateBitsCount;
asset->stateBitsTable = header.material->stateBitsTable;
std::memcpy(asset->stateBitsEntry, header.material->stateBitsEntry, 48);
asset->constantCount = header.material->constantCount;
asset->constantTable = header.material->constantTable;
replacementFound = true;
}
}
}, false);
if (!replacementFound)
{
Components::Logger::Print("Could not find any loaded material with techset {} (in replacement of {}), so I cannot set the sortkey for material {}\n", iw4TechSetName, asset->techniqueSet->name, asset->info.name);
}
}
else
{
Components::Logger::Print("Could not find any loaded techset with iw4 name {} for iw3 techset {}\n", iw4TechSetName, asset->techniqueSet->name);
}
}
else
{
Components::Logger::Print("Could not match iw3 techset {} with any of the techsets I know! This is a critical error, there's a good chance the map will not be playable.\n", techName);
}
}
if (!reader.end())
{
Components::Logger::Error(Game::ERR_FATAL, "Material data left!");
}
/*char baseIndex = 0;
for (char i = 0; i < asset->stateBitsCount; ++i)
{
auto stateBits = asset->stateBitsTable[i];
if (stateBits.loadBits[0] == 0x18128812 &&
stateBits.loadBits[1] == 0xD) // Seems to be like a default stateBit causing a 'generic' initialization
{
baseIndex = i;
break;
}
}
for (int i = 0; i < 48; ++i)
{
if (!asset->techniqueSet->techniques[i] && asset->stateBitsEntry[i] != -1)
{
asset->stateBitsEntry[i] = -1;
}
if (asset->techniqueSet->techniques[i] && asset->stateBitsEntry[i] == -1)
{
asset->stateBitsEntry[i] = baseIndex;
}
}*/
header->material = builder->getIW4OfApi()->read<Game::Material>(Game::XAssetType::ASSET_TYPE_MATERIAL, name);
}
void IMaterial::loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/)

View File

@ -10,11 +10,7 @@ namespace Assets
void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override;
void loadJson(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
void loadFromDisk(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
void loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
void loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
private:
Game::MaterialTechniqueSet* findWorkingTechset(const std::string techsetName, Game::Material* material, Components::ZoneBuilder::Zone* builder) const;
};
}

View File

@ -1,8 +1,6 @@
#include <STDInclude.hpp>
#include "IMaterialPixelShader.hpp"
#define GFX_RENDERER_SHADER_SM3 0
namespace Assets
{
@ -19,21 +17,7 @@ namespace Assets
void IMaterialPixelShader::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
Components::FileSystem::File psFile(std::format("ps/{}.cso", name));
if (!psFile.exists()) return;
auto buff = psFile.getBuffer();
auto programSize = buff.size() / 4;
Game::MaterialPixelShader* asset = builder->getAllocator()->allocate<Game::MaterialPixelShader>();
asset->name = builder->getAllocator()->duplicateString(name);
asset->prog.loadDef.loadForRenderer = GFX_RENDERER_SHADER_SM3;
asset->prog.loadDef.programSize = static_cast<unsigned short>(programSize);
asset->prog.loadDef.program = builder->getAllocator()->allocateArray<unsigned int>(programSize);
memcpy_s(asset->prog.loadDef.program, buff.size(), buff.data(), buff.size());
header->pixelShader = asset;
header->pixelShader = builder->getIW4OfApi()->read<Game::MaterialPixelShader>(Game::XAssetType::ASSET_TYPE_PIXELSHADER, name);
}
void IMaterialPixelShader::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)

View File

@ -11,6 +11,8 @@ namespace Assets
{
if (!header->data) this->loadFromDisk(header, name, builder); // Check if we need to import a new one into the game
if (!header->data) this->loadNative(header, name, builder); // Check if there is a native one
assert(header->data);
}
void IMaterialTechniqueSet::loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/)
@ -18,198 +20,9 @@ namespace Assets
header->techniqueSet = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).techniqueSet;
}
void IMaterialTechniqueSet::loadTechniqueFromDisk(Game::MaterialTechnique** tech, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
AssertSize(Game::MaterialPass, 20);
Components::FileSystem::File techFile(std::format("techniques/{}.iw4x.json", name));
if (!techFile.exists())
{
*tech = nullptr;
Components::Logger::Warning(Game::CON_CHANNEL_DONT_FILTER, "Missing technique '{}'\n", name);
return;
}
nlohmann::json technique;
try
{
technique = nlohmann::json::parse(techFile.getBuffer());
}
catch (std::exception& e)
{
Components::Logger::Error(Game::ERR_FATAL, "Reading techset '{}' failed, file is messed up! {}", name, e.what());
}
int version = technique["version"].get<int>();
if (version != IW4X_TECHSET_VERSION)
{
Components::Logger::Error(Game::ERR_FATAL,
"Reading technique '{}' failed, expected version is {}, but it was {}!", name, IW4X_TECHSET_VERSION, version);
}
unsigned short flags = static_cast<unsigned short>(Utils::Json::ReadFlags(technique["flags"].get<std::string>(), sizeof(short)));
if (technique["passArray"].is_array())
{
nlohmann::json::array_t passArray = technique["passArray"];
Game::MaterialTechnique* asset = (Game::MaterialTechnique*)builder->getAllocator()->allocateArray<unsigned char>(sizeof(Game::MaterialTechnique) + (sizeof(Game::MaterialPass) * (passArray.size() - 1)));
asset->name = builder->getAllocator()->duplicateString(name);
asset->flags = flags;
asset->passCount = static_cast<unsigned short>(passArray.size());
Game::MaterialPass* passes = builder->getAllocator()->allocateArray<Game::MaterialPass>(asset->passCount);
std::memcpy(asset->passArray, passes, sizeof(Game::MaterialPass) * asset->passCount);
for (unsigned short i = 0; i < asset->passCount; i++)
{
Game::MaterialPass* pass = &asset->passArray[i];
auto jsonPass = passArray[i];
if (jsonPass["vertexDeclaration"].is_string())
{
auto declName = jsonPass["vertexDeclaration"].get<std::string>();
pass->vertexDecl = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_VERTEXDECL, declName, builder).vertexDecl;
}
if (jsonPass["vertexShader"].is_string())
{
auto vsName = jsonPass["vertexShader"].get<std::string>();
pass->vertexShader = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_VERTEXSHADER, vsName, builder).vertexShader;
}
if (jsonPass["pixelShader"].is_string())
{
auto psName = jsonPass["pixelShader"].get<std::string>();
pass->pixelShader = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_PIXELSHADER, psName, builder).pixelShader;
}
pass->perPrimArgCount = jsonPass["perPrimArgCount"].get<char>();
pass->perObjArgCount = jsonPass["perObjArgCount"].get<char>();
pass->stableArgCount = jsonPass["stableArgCount"].get<char>();
pass->customSamplerFlags = jsonPass["customSamplerFlags"].get<char>();
if (jsonPass["arguments"].is_array())
{
nlohmann::json::array_t jsonAguments = jsonPass["arguments"];
pass->args = builder->getAllocator()->allocateArray<Game::MaterialShaderArgument>(jsonAguments.size());
for (size_t j = 0; j < jsonAguments.size(); j++)
{
auto jsonArgument = jsonAguments[j];
Game::MaterialShaderArgument* argument = &pass->args[j];
argument->type = jsonArgument["type"].get<Game::MaterialShaderArgumentType>();
argument->dest = jsonArgument["dest"].get<unsigned short>();
if (argument->type == Game::MaterialShaderArgumentType::MTL_ARG_LITERAL_VERTEX_CONST ||
argument->type == Game::MaterialShaderArgumentType::MTL_ARG_LITERAL_PIXEL_CONST)
{
argument->u.literalConst = builder->getAllocator()->allocateArray<float>(4);
auto literals = jsonArgument["literals"].get<std::vector<float>>();
std::copy(literals.begin(), literals.end(), argument->u.literalConst);
}
else if (argument->type == Game::MaterialShaderArgumentType::MTL_ARG_CODE_VERTEX_CONST ||
argument->type == Game::MaterialShaderArgumentType::MTL_ARG_CODE_PIXEL_CONST)
{
if (jsonArgument["codeConst"].is_object())
{
auto codeConst = jsonArgument["codeConst"];
argument->u.codeConst.index = codeConst["index"].get<unsigned short>();
argument->u.codeConst.firstRow = codeConst["firstRow"].get<unsigned char>();
argument->u.codeConst.rowCount = codeConst["rowCount"].get<unsigned char>();
}
}
else if (argument->type == Game::MaterialShaderArgumentType::MTL_ARG_MATERIAL_PIXEL_SAMPLER ||
argument->type == Game::MaterialShaderArgumentType::MTL_ARG_MATERIAL_VERTEX_CONST ||
argument->type == Game::MaterialShaderArgumentType::MTL_ARG_MATERIAL_PIXEL_CONST)
{
argument->u.nameHash = jsonArgument["nameHash"].get<unsigned int>();
}
else if (argument->type == Game::MaterialShaderArgumentType::MTL_ARG_CODE_PIXEL_SAMPLER)
{
argument->u.codeSampler = jsonArgument["codeSampler"].get<unsigned int>();
}
}
}
}
*tech = asset;
}
}
void IMaterialTechniqueSet::loadFromDisk(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
Components::FileSystem::File tsFile(std::format("techsets/{}.iw4x.json", name));
if (!tsFile.exists()) return;
nlohmann::json techset;
try
{
techset = nlohmann::json::parse(tsFile.getBuffer());
}
catch (std::exception& e)
{
Components::Logger::Error(Game::ERR_FATAL, "Reading techset '{}' failed, file is messed up! {}", name, e.what());
}
auto version = techset["version"].get<int>();
if (version != IW4X_TECHSET_VERSION)
{
Components::Logger::Error(Game::ERR_FATAL, "Reading techset '{}' failed, expected version is {}, but it was {}!",
name, IW4X_TECHSET_VERSION, version);
}
Game::MaterialTechniqueSet* asset = builder->getAllocator()->allocate<Game::MaterialTechniqueSet>();
if (asset == nullptr)
{
Components::Logger::Error(Game::ERR_FATAL, "Reading techset '{}' failed, allocation failed!", name);
return;
}
if (techset["name"].is_string())
{
asset->name = builder->getAllocator()->duplicateString(techset["name"].get<std::string>());
}
asset->hasBeenUploaded = techset["hasBeenUploaded"].get<bool>();
asset->worldVertFormat = techset["worldVertFormat"].get<char>();
if (techset["remappedTechniqueSet"].is_string())
{
auto remapped = techset["remappedTechniqueSet"].get<std::string>();
if (remapped != asset->name)
{
builder->loadAssetByName(Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET, remapped, false);
}
}
if (techset["techniques"].is_object())
{
for (int i = 0; i < Game::TECHNIQUE_COUNT; i++)
{
auto technique = techset["techniques"].at(std::to_string(i));
if (technique.is_string())
{
this->loadTechniqueFromDisk(&asset->techniques[i], technique.get<std::string>(), builder);
}
}
}
header->techniqueSet = asset;
header->techniqueSet = builder->getIW4OfApi()->read<Game::MaterialTechniqueSet>(Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET, name);
}
void IMaterialTechniqueSet::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)

View File

@ -18,32 +18,7 @@ namespace Assets
void IMaterialVertexDeclaration::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
Components::FileSystem::File declFile(std::format("decl/{}.iw4xDECL", name));
if (!declFile.exists()) return;
Utils::Stream::Reader reader(builder->getAllocator(), declFile.getBuffer());
char* magic = reader.readArray<char>(8);
if (std::memcmp(magic, "IW4xDECL", 8))
{
Components::Logger::Error(Game::ERR_FATAL, "Reading vertex declaration '{}' failed, header is invalid!", name);
}
auto version = reader.read<char>();
if (version != IW4X_TECHSET_VERSION)
{
Components::Logger::Error(Game::ERR_FATAL, "Reading vertex declaration '{}' failed, expected version is {}, but it was {:d}!",
name, IW4X_TECHSET_VERSION, version);
}
Game::MaterialVertexDeclaration* asset = reader.readObject<Game::MaterialVertexDeclaration>();
if (asset->name)
{
asset->name = reader.readCString();
}
header->vertexDecl = asset;
header->vertexDecl = builder->getIW4OfApi()->read<Game::MaterialVertexDeclaration>(Game::XAssetType::ASSET_TYPE_VERTEXDECL, name);
}
void IMaterialVertexDeclaration::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)

View File

@ -18,20 +18,7 @@ namespace Assets
void IMaterialVertexShader::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
Components::FileSystem::File vsFile(std::format("vs/{}.cso", name));
if (!vsFile.exists()) return;
auto buff = vsFile.getBuffer();
auto programSize = buff.size() / 4;
Game::MaterialVertexShader* asset = builder->getAllocator()->allocate<Game::MaterialVertexShader>();
asset->name = builder->getAllocator()->duplicateString(name);
asset->prog.loadDef.loadForRenderer = GFX_RENDERER_SHADER_SM3;
asset->prog.loadDef.programSize = static_cast<unsigned short>(programSize);
asset->prog.loadDef.program = builder->getAllocator()->allocateArray<unsigned int>(programSize);
memcpy_s(asset->prog.loadDef.program, buff.size(), buff.data(), buff.size());
header->vertexShader = asset;
header->vertexShader = builder->getIW4OfApi()->read<Game::MaterialVertexShader>(Game::XAssetType::ASSET_TYPE_VERTEXSHADER, name);
}
void IMaterialVertexShader::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)

View File

@ -7,9 +7,9 @@ namespace Assets
{
AssertSize(Game::PhysPreset, 44);
Utils::Stream* buffer = builder->getBuffer();
Game::PhysPreset* asset = header.physPreset;
Game::PhysPreset* dest = buffer->dest<Game::PhysPreset>();
auto* buffer = builder->getBuffer();
auto* asset = header.physPreset;
auto* dest = buffer->dest<Game::PhysPreset>();
buffer->save(asset);
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);
@ -28,4 +28,14 @@ namespace Assets
buffer->popBlock();
}
void IPhysPreset::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
loadFromDisk(header, name, builder);
}
void IPhysPreset::loadFromDisk(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
header->physPreset = builder->getIW4OfApi()->read<Game::PhysPreset>(Game::XAssetType::ASSET_TYPE_PHYSPRESET, name);
}
}

View File

@ -8,5 +8,7 @@ namespace Assets
Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_PHYSPRESET; }
void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override;
void loadFromDisk(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
};
}

View File

@ -7,38 +7,7 @@ namespace Assets
{
void IRawFile::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
Components::FileSystem::File rawFile(name);
if (!rawFile.exists())
{
return;
}
auto* asset = builder->getAllocator()->allocate<Game::RawFile>();
if (!asset)
{
return;
}
asset->name = builder->getAllocator()->duplicateString(name);
asset->len = static_cast<int>(rawFile.getBuffer().size());
const auto compressedData = Utils::Compression::ZLib::Compress(rawFile.getBuffer());
// Only save the compressed buffer if we gained space
if (compressedData.size() < rawFile.getBuffer().size())
{
asset->buffer = builder->getAllocator()->allocateArray<char>(compressedData.size());
std::memcpy(const_cast<char*>(asset->buffer), compressedData.data(), compressedData.size());
asset->compressedLen = static_cast<int>(compressedData.size());
}
else
{
asset->buffer = builder->getAllocator()->allocateArray<char>(rawFile.getBuffer().size() + 1);
std::memcpy(const_cast<char*>(asset->buffer), rawFile.getBuffer().data(), rawFile.getBuffer().size());
asset->compressedLen = 0;
}
header->rawfile = asset;
header->rawfile = builder->getIW4OfApi()->read<Game::RawFile>(Game::XAssetType::ASSET_TYPE_RAWFILE, name);
}
void IRawFile::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)

View File

@ -7,9 +7,9 @@ namespace Assets
{
AssertSize(Game::SndCurve, 136);
Utils::Stream* buffer = builder->getBuffer();
Game::SndCurve* asset = header.sndCurve;
Game::SndCurve* dest = buffer->dest<Game::SndCurve>();
auto* buffer = builder->getBuffer();
auto* asset = header.sndCurve;
auto* dest = buffer->dest<Game::SndCurve>();
buffer->save(asset);
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);
@ -22,4 +22,9 @@ namespace Assets
buffer->popBlock();
}
void ISndCurve::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
header->sndCurve = builder->getIW4OfApi()->read<Game::SndCurve>(Game::XAssetType::ASSET_TYPE_SOUND_CURVE, name);
}
}

View File

@ -8,5 +8,6 @@ namespace Assets
Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_SOUND_CURVE; }
void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override;
};
}

View File

@ -1,108 +1,11 @@
#include <STDInclude.hpp>
#include "IXAnimParts.hpp"
#define IW4X_ANIM_VERSION 1
namespace Assets
{
void IXAnimParts::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
Components::FileSystem::File animFile(std::format("xanim/{}.iw4xAnim", name));
if (animFile.exists())
{
Utils::Stream::Reader reader(builder->getAllocator(), animFile.getBuffer());
__int64 magic = reader.read<__int64>();
if (std::memcmp(&magic, "IW4xAnim", 8))
{
Components::Logger::Error(Game::ERR_FATAL, "Reading animation '{}' failed, header is invalid!", name);
}
int version = reader.read<int>();
if (version != IW4X_ANIM_VERSION)
{
Components::Logger::Error(Game::ERR_FATAL, "Reading animation '{}' failed, expected version is {}, but it was {}!", name, IW4X_ANIM_VERSION, version);
}
Game::XAnimParts* xanim = reader.readArray<Game::XAnimParts>();
if (xanim)
{
if (xanim->name)
{
xanim->name = reader.readCString();
}
if (xanim->names)
{
xanim->names = builder->getAllocator()->allocateArray<unsigned short>(xanim->boneCount[Game::PART_TYPE_ALL]);
for (int i = 0; i < xanim->boneCount[Game::PART_TYPE_ALL]; ++i)
{
xanim->names[i] = static_cast<std::uint16_t>(Game::SL_GetString(reader.readCString(), 0));
}
}
if (xanim->notify)
{
xanim->notify = reader.readArray<Game::XAnimNotifyInfo>(xanim->notifyCount);
for (int i = 0; i < xanim->notifyCount; ++i)
{
xanim->notify[i].name = static_cast<std::uint16_t>(Game::SL_GetString(reader.readCString(), 0));
}
}
if (xanim->dataByte)
{
xanim->dataByte = reader.readArray<char>(xanim->dataByteCount);
}
if (xanim->dataShort)
{
xanim->dataShort = reader.readArray<short>(xanim->dataShortCount);
}
if (xanim->dataInt)
{
xanim->dataInt = reader.readArray<int>(xanim->dataIntCount);
}
if (xanim->randomDataByte)
{
xanim->randomDataByte = reader.readArray<char>(xanim->randomDataByteCount);
}
if (xanim->randomDataShort)
{
xanim->randomDataShort = reader.readArray<short>(xanim->randomDataShortCount);
}
if (xanim->randomDataInt)
{
xanim->randomDataInt = reader.readArray<int>(xanim->randomDataIntCount);
}
if (xanim->indices.data)
{
if (xanim->numframes < 256)
{
xanim->indices._1 = reader.readArray<char>(xanim->indexCount);
}
else
{
xanim->indices._2 = reader.readArray<unsigned short>(xanim->indexCount);
}
}
if (!reader.end())
{
Components::Logger::Error(Game::ERR_FATAL, "Reading animation '{}' failed, remaining raw data found!", name);
}
header->parts = xanim;
}
}
header->parts = builder->getIW4OfApi()->read<Game::XAnimParts>(Game::XAssetType::ASSET_TYPE_XANIMPARTS, name);
}
void IXAnimParts::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)

View File

@ -1,298 +1,30 @@
#include <STDInclude.hpp>
#include "IXModel.hpp"
#define IW4X_MODEL_VERSION 8
namespace Assets
{
void IXModel::loadXSurfaceCollisionTree(Game::XSurfaceCollisionTree* entry, Utils::Stream::Reader* reader)
{
if (entry->nodes)
{
entry->nodes = reader->readArrayOnce<Game::XSurfaceCollisionNode>(entry->nodeCount);
}
if (entry->leafs)
{
entry->leafs = reader->readArrayOnce<Game::XSurfaceCollisionLeaf>(entry->leafCount);
}
}
void IXModel::loadXSurface(Game::XSurface* surf, Utils::Stream::Reader* reader, [[maybe_unused]] Components::ZoneBuilder::Zone* builder)
{
if (surf->vertInfo.vertsBlend)
{
surf->vertInfo.vertsBlend = reader->readArrayOnce<unsigned short>(surf->vertInfo.vertCount[0] + (surf->vertInfo.vertCount[1] * 3) + (surf->vertInfo.vertCount[2] * 5) + (surf->vertInfo.vertCount[3] * 7));
}
// Access vertex block
if (surf->verts0)
{
surf->verts0 = reader->readArrayOnce<Game::GfxPackedVertex>(surf->vertCount);
}
// Save_XRigidVertListArray
if (surf->vertList)
{
surf->vertList = reader->readArrayOnce<Game::XRigidVertList>(surf->vertListCount);
for (unsigned int i = 0; i < surf->vertListCount; ++i)
{
Game::XRigidVertList* rigidVertList = &surf->vertList[i];
if (rigidVertList->collisionTree)
{
rigidVertList->collisionTree = reader->readObject<Game::XSurfaceCollisionTree>();
this->loadXSurfaceCollisionTree(rigidVertList->collisionTree, reader);
}
}
}
// Access index block
if (surf->triIndices)
{
surf->triIndices = reader->readArrayOnce<unsigned short>(surf->triCount * 3);
}
}
void IXModel::loadXModelSurfs(Game::XModelSurfs* asset, Utils::Stream::Reader* reader, Components::ZoneBuilder::Zone* builder)
{
if (asset->name)
{
asset->name = reader->readCString();
}
if (asset->surfs)
{
asset->surfs = reader->readArrayOnce<Game::XSurface>(asset->numsurfs);
for (int i = 0; i < asset->numsurfs; ++i)
{
this->loadXSurface(&asset->surfs[i], reader, builder);
}
}
}
void IXModel::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
Components::FileSystem::File modelFile(std::format("xmodel/{}.iw4xModel", name));
header->model = builder->getIW4OfApi()->read<Game::XModel>(Game::XAssetType::ASSET_TYPE_XMODEL, name);
if (!builder->isPrimaryAsset() && (!Components::ZoneBuilder::ZBPreferDiskAssets.get<bool>() || !modelFile.exists()))
if (header->model)
{
header->model = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).model;
if (header->model) return;
}
if (modelFile.exists())
{
Utils::Stream::Reader reader(builder->getAllocator(), modelFile.getBuffer());
__int64 magic = reader.read<__int64>();
if (std::memcmp(&magic, "IW4xModl", 8))
// ???
if (header->model->physCollmap)
{
Components::Logger::Error(Game::ERR_FATAL, "Reading model '{}' failed, header is invalid!", name);
Components::AssetHandler::StoreTemporaryAsset(Game::XAssetType::ASSET_TYPE_PHYSCOLLMAP, { header->model->physCollmap });
}
int version = reader.read<int>();
if (version != IW4X_MODEL_VERSION)
if (header->model->physPreset)
{
Components::Logger::Error(Game::ERR_FATAL, "Reading model '{}' failed, expected version is {}, but it was {}!", name, IW4X_MODEL_VERSION, version);
Components::AssetHandler::StoreTemporaryAsset(Game::XAssetType::ASSET_TYPE_PHYSPRESET, { header->model->physPreset });
}
Game::XModel* asset = reader.readObject<Game::XModel>();
if (asset->name)
for (size_t i = 0; i < header->model->numLods; i++)
{
asset->name = reader.readCString();
const auto& info = header->model->lodInfo[i];
Components::AssetHandler::StoreTemporaryAsset(Game::XAssetType::ASSET_TYPE_XMODEL_SURFS, { info.modelSurfs });
}
if (asset->boneNames)
{
asset->boneNames = builder->getAllocator()->allocateArray<unsigned short>(asset->numBones);
for (char i = 0; i < asset->numBones; ++i)
{
asset->boneNames[i] = static_cast<std::uint16_t>(Game::SL_GetString(reader.readCString(), 0));
}
}
if (asset->parentList)
{
asset->parentList = reader.readArrayOnce<unsigned char>(asset->numBones - asset->numRootBones);
}
if (asset->quats)
{
asset->quats = reader.readArrayOnce<short>((asset->numBones - asset->numRootBones) * 4);
}
if (asset->trans)
{
asset->trans = reader.readArrayOnce<float>((asset->numBones - asset->numRootBones) * 3);
}
if (asset->partClassification)
{
asset->partClassification = reader.readArrayOnce<unsigned char>(asset->numBones);
}
if (asset->baseMat)
{
asset->baseMat = reader.readArrayOnce<Game::DObjAnimMat>(asset->numBones);
}
if (asset->materialHandles)
{
asset->materialHandles = reader.readArray<Game::Material*>(asset->numsurfs);
for (unsigned char i = 0; i < asset->numsurfs; ++i)
{
if (asset->materialHandles[i])
{
asset->materialHandles[i] = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader.readString(), builder).material;
}
}
}
// Save_XModelLodInfoArray
{
for (unsigned int i = 0; i < 4; ++i)
{
if (asset->lodInfo[i].modelSurfs)
{
asset->lodInfo[i].modelSurfs = reader.readObject<Game::XModelSurfs>();
this->loadXModelSurfs(asset->lodInfo[i].modelSurfs, &reader, builder);
Components::AssetHandler::StoreTemporaryAsset(Game::XAssetType::ASSET_TYPE_XMODEL_SURFS, { asset->lodInfo[i].modelSurfs });
asset->lodInfo[i].surfs = asset->lodInfo[i].modelSurfs->surfs;
// Zero that for now, it breaks the models.
// TODO: Figure out how that can be converted
asset->lodInfo[i].smcBaseIndexPlusOne = 0;
asset->lodInfo[i].smcSubIndexMask = 0;
asset->lodInfo[i].smcBucket = 0;
}
}
}
// Save_XModelCollSurfArray
if (asset->collSurfs)
{
asset->collSurfs = reader.readArray<Game::XModelCollSurf_s>(asset->numCollSurfs);
for (int i = 0; i < asset->numCollSurfs; ++i)
{
Game::XModelCollSurf_s* collSurf = &asset->collSurfs[i];
if (collSurf->collTris)
{
collSurf->collTris = reader.readArray<Game::XModelCollTri_s>(collSurf->numCollTris);
}
}
}
if (asset->boneInfo)
{
asset->boneInfo = reader.readArray<Game::XBoneInfo>(asset->numBones);
}
if (asset->physPreset)
{
asset->physPreset = reader.readObject<Game::PhysPreset>();
if (asset->physPreset->name)
{
asset->physPreset->name = reader.readCString();
}
if (asset->physPreset->sndAliasPrefix)
{
asset->physPreset->sndAliasPrefix = reader.readCString();
}
// This is an experiment, ak74 fails though
if (asset->name == "weapon_ak74u"s)
{
asset->physPreset = nullptr;
}
else
{
Game::PhysPreset* preset = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_PHYSPRESET, asset->physPreset->name, builder).physPreset;
if (preset)
{
asset->physPreset = preset;
}
else
{
Components::AssetHandler::StoreTemporaryAsset(Game::XAssetType::ASSET_TYPE_PHYSPRESET, { asset->physPreset });
}
}
}
if (asset->physCollmap)
{
Game::PhysCollmap* collmap = reader.readObject<Game::PhysCollmap>();
asset->physCollmap = collmap;
if (collmap->name)
{
collmap->name = reader.readCString();
}
if (collmap->geoms)
{
collmap->geoms = reader.readArray<Game::PhysGeomInfo>(collmap->count);
for (unsigned int i = 0; i < collmap->count; ++i)
{
Game::PhysGeomInfo* geom = &collmap->geoms[i];
if (geom->brushWrapper)
{
Game::BrushWrapper* brush = reader.readObject<Game::BrushWrapper>();
geom->brushWrapper = brush;
{
if (brush->brush.sides)
{
brush->brush.sides = reader.readArray<Game::cbrushside_t>(brush->brush.numsides);
for (unsigned short j = 0; j < brush->brush.numsides; ++j)
{
Game::cbrushside_t* side = &brush->brush.sides[j];
// TODO: Add pointer support
if (side->plane)
{
side->plane = reader.readObject<Game::cplane_s>();
}
}
}
if (brush->brush.baseAdjacentSide)
{
brush->brush.baseAdjacentSide = reader.readArray<char>(brush->totalEdgeCount);
}
}
// TODO: Add pointer support
if (brush->planes)
{
brush->planes = reader.readArray<Game::cplane_s>(brush->brush.numsides);
}
}
}
Components::AssetHandler::StoreTemporaryAsset(Game::XAssetType::ASSET_TYPE_PHYSCOLLMAP, { asset->physCollmap });
// asset->physCollmap = nullptr;
}
}
if (!reader.end())
{
Components::Logger::Error(Game::ERR_FATAL, "Reading model '{}' failed, remaining raw data found!", name);
}
header->model = asset;
}
}

View File

@ -10,11 +10,5 @@ namespace Assets
void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override;
private:
std::map<void*, void*> triIndicies;
void loadXModelSurfs(Game::XModelSurfs* asset, Utils::Stream::Reader* reader, Components::ZoneBuilder::Zone* builder);
void loadXSurface(Game::XSurface* surf, Utils::Stream::Reader* reader, Components::ZoneBuilder::Zone* builder);
void loadXSurfaceCollisionTree(Game::XSurfaceCollisionTree* entry, Utils::Stream::Reader* reader);
};
}

View File

@ -1,8 +1,6 @@
#include <STDInclude.hpp>
#include "IclipMap_t.hpp"
#define IW4X_CLIPMAP_VERSION 2
namespace Assets
{
void IclipMap_t::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
@ -37,7 +35,7 @@ namespace Assets
buffer->align(Utils::Stream::ALIGN_4);
// not sure if this is needed but both brushside and brushedge need it and it can't hurt
for (int i = 0; i < asset->planeCount; ++i)
for (size_t i = 0; i < asset->planeCount; ++i)
{
builder->storePointer(&asset->planes[i]);
buffer->save(&asset->planes[i]);
@ -192,7 +190,13 @@ namespace Assets
SaveLogEnter("cLeafBrush_t");
buffer->align(Utils::Stream::ALIGN_2);
buffer->saveArray(asset->leafbrushes, asset->numLeafBrushes);
for (size_t i = 0; i < asset->numLeafBrushes; i++)
{
builder->storePointer(&asset->leafbrushes[i]);
buffer->saveObject(asset->leafbrushes[i]);
}
Utils::Stream::ClearPointer(&dest->leafbrushes);
SaveLogExit();
@ -213,6 +217,9 @@ namespace Assets
{
if (node[i].data.leaf.brushes)
{
assert(node[i].data.leaf.brushes >= asset->leafbrushes);
assert(node[i].data.leaf.brushes < asset->leafbrushes + sizeof(unsigned short) * asset->numLeafBrushes);
if (builder->hasPointer(node[i].data.leaf.brushes))
{
node[i].data.leaf.brushes = builder->getPointer(node[i].data.leaf.brushes);
@ -278,7 +285,7 @@ namespace Assets
buffer->align(Utils::Stream::ALIGN_4);
for (int i = 0; i < asset->borderCount; ++i)
for (size_t i = 0; i < asset->borderCount; ++i)
{
builder->storePointer(&asset->borders[i]);
buffer->save(&asset->borders[i]);
@ -564,381 +571,13 @@ namespace Assets
}
}
}
builder->loadAsset(Game::XAssetType::ASSET_TYPE_MAP_ENTS, asset);
}
void IclipMap_t::load(Game::XAssetHeader* header, const std::string& _name, Components::ZoneBuilder::Zone* builder)
{
std::string name = _name;
Utils::String::Replace(name, "maps/mp/", "");
Utils::String::Replace(name, ".d3dbsp", "");
Components::FileSystem::File clipFile(std::format("clipmap/{}.iw4xClipMap", name));
if (!clipFile.exists())
{
return;
}
Game::clipMap_t* clipMap = builder->getAllocator()->allocate<Game::clipMap_t>();
if (!clipMap)
{
Components::Logger::Print("Error allocating memory for clipMap_t structure!\n");
return;
}
Game::clipMap_t* orgClipMap = nullptr;
Game::DB_EnumXAssets(Game::XAssetType::ASSET_TYPE_CLIPMAP_MP, [](Game::XAssetHeader header, void* clipMap)
{
if (!*reinterpret_cast<void**>(clipMap))
{
*reinterpret_cast<Game::clipMap_t**>(clipMap) = header.clipMap;
}
}, &orgClipMap, false);
if (orgClipMap) std::memcpy(clipMap, orgClipMap, sizeof Game::clipMap_t);
Utils::Stream::Reader reader(builder->getAllocator(), clipFile.getBuffer());
__int64 magic = reader.read<__int64>();
if (std::memcmp(&magic, "IW4xClip", 8))
{
Components::Logger::Error(Game::ERR_FATAL, "Reading clipMap_t '{}' failed, header is invalid!", name);
}
int version = reader.read<int>();
if (version > IW4X_CLIPMAP_VERSION)
{
Components::Logger::Error(Game::ERR_FATAL, "Reading clipmap '{}' failed, expected version is {}, but it was {}!", name, IW4X_CLIPMAP_VERSION, version);
}
clipMap->name = reader.readCString();
clipMap->planeCount = reader.read<int>();
clipMap->numStaticModels = reader.read<int>();
clipMap->numMaterials = reader.read<int>();
clipMap->numBrushSides = reader.read<int>();
clipMap->numBrushEdges = reader.read<int>();
clipMap->numNodes = reader.read<int>();
clipMap->numLeafs = reader.read<int>();
clipMap->leafbrushNodesCount = reader.read<int>();
clipMap->numLeafBrushes = reader.read<int>();
clipMap->numLeafSurfaces = reader.read<int>();
clipMap->vertCount = reader.read<int>();
clipMap->triCount = reader.read<int>();
clipMap->borderCount = reader.read<int>();
clipMap->partitionCount = reader.read<int>();
clipMap->aabbTreeCount = reader.read<int>();
clipMap->numSubModels = reader.read<int>();
clipMap->numBrushes = reader.read<short>();
clipMap->dynEntCount[0] = reader.read<unsigned __int16>();
clipMap->dynEntCount[1] = reader.read<unsigned __int16>();
if (clipMap->planeCount)
{
void* oldPtr = reader.read<void*>();
clipMap->planes = reader.readArray<Game::cplane_s>(clipMap->planeCount);
if (builder->getAllocator()->isPointerMapped(oldPtr))
{
clipMap->planes = builder->getAllocator()->getPointer<Game::cplane_s>(oldPtr);
Components::Logger::Print("ClipMap dpvs planes already mapped. This shouldn't happen. Make sure to load the ClipMap before the GfxWorld!\n");
}
else
{
builder->getAllocator()->mapPointer(oldPtr, clipMap->planes);
}
}
if (clipMap->numStaticModels)
{
clipMap->staticModelList = builder->getAllocator()->allocateArray<Game::cStaticModel_s>(clipMap->numStaticModels);
for (unsigned int i = 0; i < clipMap->numStaticModels; ++i)
{
std::string modelName = reader.readString();
if (modelName != "NONE"s)
{
clipMap->staticModelList[i].xmodel = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_XMODEL, modelName, builder).model;
}
float* buf = reader.readArray<float>(18);
memcpy(&clipMap->staticModelList[i].origin, buf, sizeof(float) * 18);
}
}
if (clipMap->numMaterials)
{
clipMap->materials = builder->getAllocator()->allocateArray<Game::ClipMaterial>(clipMap->numMaterials);
for (unsigned int j = 0; j < clipMap->numMaterials; ++j)
{
clipMap->materials[j].name = reader.readArray<char>(64);
clipMap->materials[j].surfaceFlags = reader.read<int>();
clipMap->materials[j].contents = reader.read<int>();
}
}
if (clipMap->numBrushSides)
{
clipMap->brushsides = builder->getAllocator()->allocateArray<Game::cbrushside_t>(clipMap->numBrushSides);
for (unsigned int i = 0; i < clipMap->numBrushSides; ++i)
{
int planeIndex = reader.read<int>();
if (planeIndex < 0 || planeIndex >= clipMap->planeCount)
{
Components::Logger::Error(Game::ERR_FATAL, "invalid plane index");
return;
}
clipMap->brushsides[i].plane = &clipMap->planes[planeIndex];
clipMap->brushsides[i].materialNum = static_cast<unsigned short>(reader.read<int>()); // materialNum
clipMap->brushsides[i].firstAdjacentSideOffset = static_cast<char>(reader.read<short>()); // firstAdjacentSide
clipMap->brushsides[i].edgeCount = reader.read<char>(); // edgeCount
}
}
if (clipMap->numBrushEdges)
{
clipMap->brushEdges = reader.readArray<char>(clipMap->numBrushEdges);
}
if (clipMap->numNodes)
{
clipMap->nodes = builder->getAllocator()->allocateArray<Game::cNode_t>(clipMap->numNodes);
for (unsigned int i = 0; i < clipMap->numNodes; ++i)
{
int planeIndex = reader.read<int>();
if (planeIndex < 0 || planeIndex >= clipMap->planeCount)
{
Components::Logger::Error(Game::ERR_FATAL, "invalid plane index\n");
return;
}
clipMap->nodes[i].plane = &clipMap->planes[planeIndex];
clipMap->nodes[i].children[0] = reader.read<short>();
clipMap->nodes[i].children[1] = reader.read<short>();
}
}
if (clipMap->numLeafs)
{
clipMap->leafs = reader.readArray<Game::cLeaf_t>(clipMap->numLeafs);
}
if (clipMap->leafbrushNodesCount)
{
clipMap->leafbrushNodes = builder->getAllocator()->allocateArray<Game::cLeafBrushNode_s>(clipMap->leafbrushNodesCount);
for (unsigned int i = 0; i < clipMap->leafbrushNodesCount; ++i)
{
clipMap->leafbrushNodes[i] = reader.read<Game::cLeafBrushNode_s>();
if (clipMap->leafbrushNodes[i].leafBrushCount > 0)
{
clipMap->leafbrushNodes[i].data.leaf.brushes = reader.readArray<unsigned short>(clipMap->leafbrushNodes[i].leafBrushCount);
}
}
}
if (clipMap->numLeafBrushes)
{
clipMap->leafbrushes = reader.readArray<unsigned short>(clipMap->numLeafBrushes);
}
if (clipMap->numLeafSurfaces)
{
clipMap->leafsurfaces = reader.readArray<unsigned int>(clipMap->numLeafSurfaces);
}
if (clipMap->vertCount)
{
clipMap->verts = reader.readArray<Game::vec3_t>(clipMap->vertCount);
}
if (clipMap->triCount)
{
clipMap->triIndices = reader.readArray<unsigned short>(clipMap->triCount * 3);
clipMap->triEdgeIsWalkable = reader.readArray<char>(4 * ((3 * clipMap->triCount + 31) >> 5));
}
if (clipMap->borderCount)
{
clipMap->borders = reader.readArray<Game::CollisionBorder>(clipMap->borderCount);
}
if (clipMap->partitionCount)
{
clipMap->partitions = builder->getAllocator()->allocateArray<Game::CollisionPartition>(clipMap->partitionCount);
for (int i = 0; i < clipMap->partitionCount; ++i)
{
clipMap->partitions[i].triCount = reader.read<char>();
clipMap->partitions[i].borderCount = reader.read<char>();
clipMap->partitions[i].firstTri = reader.read<int>();
if (clipMap->partitions[i].borderCount > 0)
{
int index = reader.read<int>();
if (index < 0 || index > clipMap->borderCount)
{
Components::Logger::Error(Game::ERR_FATAL, "invalid border index\n");
return;
}
clipMap->partitions[i].borders = &clipMap->borders[index];
}
}
}
if (clipMap->aabbTreeCount)
{
clipMap->aabbTrees = reader.readArray<Game::CollisionAabbTree>(clipMap->aabbTreeCount);
}
if (clipMap->numSubModels)
{
clipMap->cmodels = reader.readArray<Game::cmodel_t>(clipMap->numSubModels);
}
if (clipMap->numBrushes)
{
clipMap->brushes = builder->getAllocator()->allocateArray<Game::cbrush_t>(clipMap->numBrushes);
memset(clipMap->brushes, 0, sizeof(Game::cbrush_t) * clipMap->numBrushes);
for (int i = 0; i < clipMap->numBrushes; ++i)
{
clipMap->brushes[i].numsides = reader.read<unsigned int>() & 0xFFFF; // todo: check for overflow here
if (clipMap->brushes[i].numsides > 0)
{
auto index = reader.read<unsigned int>();
if (index < 0 || index > clipMap->numBrushSides)
{
Components::Logger::Error(Game::ERR_FATAL, "invalid side index\n");
return;
}
clipMap->brushes[i].sides = &clipMap->brushsides[index];
}
else
{
clipMap->brushes[i].sides = nullptr;
}
auto index = reader.read<unsigned int>();
if (index > clipMap->numBrushEdges)
{
Components::Logger::Error(Game::ERR_FATAL, "invalid edge index\n");
return;
}
clipMap->brushes[i].baseAdjacentSide = &clipMap->brushEdges[index];
char* tmp = reader.readArray<char>(12);
memcpy(&clipMap->brushes[i].axialMaterialNum, tmp, 12);
//todo check for overflow
for (int r = 0; r < 2; ++r)
{
for (int c = 0; c < 3; ++c)
{
clipMap->brushes[i].firstAdjacentSideOffsets[r][c] = reader.read<short>() & 0xFF;
}
}
tmp = reader.readArray<char>(6);
memcpy(&clipMap->brushes[i].edgeCount, tmp, 6);
}
clipMap->brushBounds = reader.readArray<Game::Bounds>(clipMap->numBrushes);
clipMap->brushContents = reader.readArray<int>(clipMap->numBrushes);
}
for (int x = 0; x < 2; ++x)
{
if (clipMap->dynEntCount[x])
{
clipMap->dynEntDefList[x] = builder->getAllocator()->allocateArray<Game::DynEntityDef>(clipMap->dynEntCount[x]);
for (int i = 0; i < clipMap->dynEntCount[x]; ++i)
{
clipMap->dynEntDefList[x][i].type = reader.read<Game::DynEntityType>();
clipMap->dynEntDefList[x][i].pose = reader.read<Game::GfxPlacement>();
std::string tempName = reader.readString();
if (tempName != "NONE"s)
{
clipMap->dynEntDefList[x][i].xModel = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_XMODEL, tempName, builder).model;
}
clipMap->dynEntDefList[x][i].brushModel = reader.read<short>();
clipMap->dynEntDefList[x][i].physicsBrushModel = reader.read<short>();
tempName = reader.readString();
if (tempName != "NONE"s)
{
clipMap->dynEntDefList[x][i].destroyFx = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_FX, tempName, builder).fx;
}
tempName = reader.readString();
if (tempName != "NONE"s)
{
clipMap->dynEntDefList[x][i].physPreset = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_PHYSPRESET, tempName, builder).physPreset;
}
clipMap->dynEntDefList[x][i].health = reader.read<int>();
clipMap->dynEntDefList[x][i].mass = reader.read<Game::PhysMass>();
clipMap->dynEntDefList[x][i].contents = reader.read<int>();
}
}
}
clipMap->smodelNodeCount = reader.read<unsigned short>();
clipMap->smodelNodes = reader.readArray<Game::SModelAabbNode>(clipMap->smodelNodeCount);
clipMap->mapEnts = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MAP_ENTS, std::format("maps/mp/{}.d3dbsp", name), builder).mapEnts;
// add triggers to mapEnts
if (version >= 2) {
if (clipMap->numSubModels > 0) {
clipMap->mapEnts->trigger.count = clipMap->numSubModels;
clipMap->mapEnts->trigger.hullCount = clipMap->numSubModels;
Game::TriggerHull* hulls = builder->getAllocator()->allocateArray<Game::TriggerHull>(clipMap->mapEnts->trigger.hullCount);
Game::TriggerModel* models = builder->getAllocator()->allocateArray<Game::TriggerModel>(clipMap->mapEnts->trigger.count);
for (unsigned int i = 0; i < clipMap->numSubModels; ++i)
{
models[i] = reader.read<Game::TriggerModel>();
hulls[i] = reader.read<Game::TriggerHull>();
}
size_t slabCount = reader.read<size_t>();
clipMap->mapEnts->trigger.slabCount = slabCount;
Game::TriggerSlab* slabs = builder->getAllocator()->allocateArray<Game::TriggerSlab>(clipMap->mapEnts->trigger.slabCount);
for (unsigned int i = 0; i < clipMap->mapEnts->trigger.slabCount; i++) {
slabs[i] = reader.read<Game::TriggerSlab>();
}
clipMap->mapEnts->trigger.models = &models[0];
clipMap->mapEnts->trigger.hulls = &hulls[0];
clipMap->mapEnts->trigger.slabs = &slabs[0];
}
}
clipMap->checksum = reader.read<int>();
// This mustn't be null and has to have at least 1 'valid' entry.
if (!clipMap->smodelNodeCount || !clipMap->smodelNodes)
{
clipMap->smodelNodeCount = 1;
clipMap->smodelNodes = builder->getAllocator()->allocateArray<Game::SModelAabbNode>(clipMap->smodelNodeCount);
clipMap->smodelNodes[0].bounds.halfSize[0] = -131072.000f;
clipMap->smodelNodes[0].bounds.halfSize[1] = -131072.000f;
clipMap->smodelNodes[0].bounds.halfSize[2] = -131072.000f;
}
// These mustn't be null, but they don't need to be valid.
for (int i = 0; i < 2 && clipMap->dynEntCount[i]; ++i)
{
Utils::Stream::ClearPointer(&clipMap->dynEntPoseList[i]);
Utils::Stream::ClearPointer(&clipMap->dynEntClientList[i]);
Utils::Stream::ClearPointer(&clipMap->dynEntCollList[i]);
}
if (!reader.end())
{
Components::Logger::Error(Game::ERR_FATAL, "Clipmap data left!");
}
header->clipMap = clipMap;
header->clipMap = builder->getIW4OfApi()->read<Game::clipMap_t>(Game::XAssetType::ASSET_TYPE_CLIPMAP_MP, _name);
assert(header->data);
}
}

View File

@ -10,6 +10,7 @@ namespace Assets
void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override;
static void dump(Game::XAssetHeader /*header*/);
private:
class SModelQuadtree

View File

@ -7,351 +7,7 @@ namespace Assets
{
void Isnd_alias_list_t::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
Components::FileSystem::File aliasFile(std::format("sounds/{}.json", name));
if (!aliasFile.exists())
{
header->sound = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).sound;
return;
}
auto* aliasList = builder->getAllocator()->allocate<Game::snd_alias_list_t>();
if (!aliasList)
{
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Failed to allocate memory for sound alias structure!\n");
return;
}
nlohmann::json infoData;
try
{
infoData = nlohmann::json::parse(aliasFile.getBuffer());
}
catch (const nlohmann::json::parse_error& ex)
{
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Json Parse Error: {}\n", ex.what());
return;
}
nlohmann::json aliasesContainer = infoData["head"];
nlohmann::json::array_t aliases = aliasesContainer;
aliasList->count = aliases.size();
// Allocate
aliasList->head = builder->getAllocator()->allocateArray<Game::snd_alias_t>(aliasList->count);
if (!aliasList->head)
{
Components::Logger::Print("Error allocating memory for sound alias structure!\n");
return;
}
aliasList->aliasName = builder->getAllocator()->duplicateString(name);
for (size_t i = 0; i < aliasList->count; i++)
{
nlohmann::json head = aliasesContainer[i];
if (!infoData.is_object())
{
Components::Logger::Error(Game::ERR_FATAL, "Failed to load sound {}!", name);
return;
}
aliasList->head->soundFile = builder->getAllocator()->allocate<Game::SoundFile>();
if (!aliasList->head->soundFile)
{
Components::Logger::Print("Error allocating memory for sound alias structure!\n");
return;
}
Game::snd_alias_t* alias = aliasList->head;
// try and parse everything and if it fails then fail for the whole file
auto type = head["type"];
auto subtitle = head["subtitle"];
auto secondaryAliasName = head["secondaryAliasName"];
auto chainAliasName = head["chainAliasName"];
auto soundFile = head["soundFile"];
auto sequence = head["sequence"];
auto volMin = head["volMin"];
auto volMax = head["volMax"];
auto pitchMin = head["pitchMin"];
auto pitchMax = head["pitchMax"];
auto distMin = head["distMin"];
auto distMax = head["distMax"];
auto flags = head["flags"];
auto slavePercentage = head["slavePercentage"];
auto probability = head["probability"];
auto lfePercentage = head["lfePercentage"];
auto centerPercentage = head["centerPercentage"];
auto startDelay = head["startDelay"];
auto volumeFalloffCurve = head["volumeFalloffCurve"];
auto envelopMin = head["envelopMin"];
auto envelopMax = head["envelopMax"];
auto envelopPercentage = head["envelopPercentage"];
auto speakerMap = head["speakerMap"];
auto aliasName = head["aliasName"];
// Fix casing
if (soundFile.is_null())
{
soundFile = head["soundfile"];
Components::Logger::Print("Fixed casing on {}\n", name);
}
if (type.is_null() || soundFile.is_null())
{
Components::Logger::Print("Type is {}\n", type.dump());
Components::Logger::Print("SoundFile is {}\n", soundFile.dump());
Components::Logger::Error(Game::ERR_FATAL, "Failed to parse sound {}! Each alias must have at least a type and a soundFile\n", name);
return;
}
#define CHECK(x, type) (x.is_##type##() || x.is_null())
// TODO: actually support all of those properties
if (!CHECK(type, number))
{
Components::Logger::Print("{} is not number but {} ({})\n", "type", Utils::Json::TypeToString(type.type()), type.dump());
}
if (!CHECK(subtitle, string))
{
Components::Logger::Print("{} is not string but {} ({})\n", "subtitle", Utils::Json::TypeToString(subtitle.type()), subtitle.dump());
}
if (!CHECK(aliasName, string))
{
Components::Logger::Print("{} is not string but {} ({})\n", "aliasName", Utils::Json::TypeToString(aliasName.type()), aliasName.dump());
}
if (!CHECK(secondaryAliasName, string))
{
Components::Logger::Print("{} is not string but {} ({})\n", "secondaryAliasName", Utils::Json::TypeToString(secondaryAliasName.type()), secondaryAliasName.dump());
}
if (!CHECK(chainAliasName, string))
{
Components::Logger::Print("{} is not string but {} ({})\n", "chainAliasName", Utils::Json::TypeToString(chainAliasName.type()), chainAliasName.dump());
}
if (!CHECK(soundFile, string))
{
Components::Logger::Print("{} is not string but {} ({})\n", "soundFile", Utils::Json::TypeToString(soundFile.type()), soundFile.dump());
}
if (!CHECK(sequence, number))
{
Components::Logger::Print("{} is not number but {} ({})\n", "sequence", Utils::Json::TypeToString(sequence.type()), sequence.dump());
}
if (!CHECK(volMin, number))
{
Components::Logger::Print("{} is not number but {} ({})\n", "volMin", Utils::Json::TypeToString(volMin.type()), volMin.dump());
}
if (!CHECK(volMax, number))
{
Components::Logger::Print("{} is not number but {} ({})\n", "volMax", Utils::Json::TypeToString(volMax.type()), volMax.dump());
}
if (!CHECK(pitchMin, number))
{
Components::Logger::Print("{} is not number but {} ({})\n", "pitchMin", Utils::Json::TypeToString(pitchMin.type()), pitchMin.dump());
}
if (!CHECK(pitchMax, number))
{
Components::Logger::Print("{} is not number but {} ()\n", "pitchMax", Utils::Json::TypeToString(pitchMax.type()), pitchMax.dump());
}
if (!CHECK(probability, number))
{
Components::Logger::Print("{} is not number but {} ({}))\n", "probability", Utils::Json::TypeToString(probability.type()), probability.dump());
}
if (!CHECK(lfePercentage, number))
{
Components::Logger::Print("{} is not number but {} ({})\n", "lfePercentage", Utils::Json::TypeToString(lfePercentage.type()), lfePercentage.dump());
}
if (!CHECK(centerPercentage, number))
{
Components::Logger::Print("{} is not number but {} ({})\n", "centerPercentage", Utils::Json::TypeToString(centerPercentage.type()), centerPercentage.dump());
}
if (!CHECK(startDelay, number))
{
Components::Logger::Print("{} is not number but {} ({})\n", "startDelay", Utils::Json::TypeToString(startDelay.type()), startDelay.dump());
}
if (!CHECK(volumeFalloffCurve, string))
{
Components::Logger::Print("{}s is not string but {} ({})\n", "volumeFalloffCurve", Utils::Json::TypeToString(volumeFalloffCurve.type()), volumeFalloffCurve.dump());
}
if (!CHECK(envelopMin, number))
{
Components::Logger::Print("{} is not number but {} ({})\n", "envelopMin", Utils::Json::TypeToString(envelopMin.type()), envelopMin.dump());
}
if (!CHECK(envelopMax, number))
{
Components::Logger::Print("{} is not number but {} ({})\n", "envelopMax", Utils::Json::TypeToString(envelopMax.type()), envelopMax.dump());
}
if (!CHECK(envelopPercentage, number))
{
Components::Logger::Print("{} is not number but {} ({})\n", "envelopPercentage", Utils::Json::TypeToString(envelopPercentage.type()), envelopPercentage.dump());
}
if (!CHECK(speakerMap, object))
{
Components::Logger::Print("{} is not object but {} ({})\n", "speakerMap", Utils::Json::TypeToString(speakerMap.type()), speakerMap.dump());
}
if (CHECK(type, number) && CHECK(aliasName, string) && CHECK(subtitle, string) && CHECK(secondaryAliasName, string) && CHECK(chainAliasName, string) &&
CHECK(soundFile, string) && CHECK(sequence, number) && CHECK(volMin, number) && CHECK(volMax, number) && CHECK(pitchMin, number) &&
CHECK(pitchMax, number) && CHECK(distMin, number) && CHECK(distMax, number) && CHECK(flags, number) && CHECK(slavePercentage, number) &&
CHECK(probability, number) && CHECK(lfePercentage, number) && CHECK(centerPercentage, number) && CHECK(startDelay, number) &&
CHECK(volumeFalloffCurve, string) && CHECK(envelopMin, number) && CHECK(envelopMax, number) && CHECK(envelopPercentage, number) &&
CHECK(speakerMap, object))
{
alias->soundFile->exists = true;
alias->aliasName = builder->getAllocator()->duplicateString(aliasName.get<std::string>());
if (subtitle.is_string())
{
alias->subtitle = builder->getAllocator()->duplicateString(subtitle.get<std::string>());
}
if (secondaryAliasName.is_string())
{
alias->secondaryAliasName = builder->getAllocator()->duplicateString(secondaryAliasName.get<std::string>());
}
if (chainAliasName.is_string())
{
alias->chainAliasName = builder->getAllocator()->duplicateString(chainAliasName.get<std::string>());
}
alias->sequence = sequence.get<int>();
alias->volMin = volMin.get<float>();
alias->volMax = volMax.get<float>();
alias->pitchMin = pitchMin.get<float>();
alias->pitchMax = pitchMax.get<float>();
alias->distMin = distMin.get<float>();
alias->distMax = distMax.get<float>();
alias->flags = flags.get<int>();
alias->___u15.slavePercentage = slavePercentage.get<float>();
alias->probability = probability.get<float>();
alias->lfePercentage = lfePercentage.get<float>();
alias->centerPercentage = centerPercentage.get<float>();
alias->startDelay = startDelay.get<int>();
alias->envelopMin = envelopMin.get<float>();
alias->envelopMax = envelopMax.get<float>();
alias->envelopPercentage = envelopPercentage.get<float>();
// Speaker map object
if (!speakerMap.is_null())
{
alias->speakerMap = builder->getAllocator()->allocate<Game::SpeakerMap>();
if (!alias->speakerMap)
{
Components::Logger::Print("Error allocating memory for speakermap in sound alias{}!\n", alias->aliasName);
return;
}
alias->speakerMap->name = builder->getAllocator()->duplicateString(speakerMap["name"].get<std::string>());
alias->speakerMap->isDefault = speakerMap["isDefault"].get<bool>();
if (speakerMap["channelMaps"].is_array())
{
nlohmann::json::array_t channelMaps = speakerMap["channelMaps"];
assert(channelMaps.size() <= 4);
// channelMapIndex should never exceed 1
for (size_t channelMapIndex = 0; channelMapIndex < 2; channelMapIndex++)
{
// subChannelIndex should never exceed 1
for (size_t subChannelIndex = 0; subChannelIndex < 2; subChannelIndex++)
{
nlohmann::json channelMap = channelMaps[channelMapIndex * 2 + subChannelIndex]; // 0-3
nlohmann::json::array_t speakers = channelMap["speakers"];
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakerCount = speakers.size();
for (size_t speakerIndex = 0; speakerIndex < alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakerCount; speakerIndex++)
{
auto speaker = speakers[speakerIndex];
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].levels[0] = speaker["levels0"].get<float>();
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].levels[1] = speaker["levels1"].get<float>();
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].numLevels = speaker["numLevels"].get<int>();
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].speaker = speaker["speaker"].get<int>();
}
}
}
}
}
if (volumeFalloffCurve.is_string())
{
auto fallOffCurve = volumeFalloffCurve.get<std::string>();
if (fallOffCurve.empty())
{
fallOffCurve = "$default";
}
auto curve = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_SOUND_CURVE, fallOffCurve, builder).sndCurve;
alias->volumeFalloffCurve = curve;
}
if (static_cast<Game::snd_alias_type_t>(type.get<int>()) == Game::snd_alias_type_t::SAT_LOADED) // Loaded
{
alias->soundFile->type = Game::SAT_LOADED;
alias->soundFile->u.loadSnd = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_LOADED_SOUND, soundFile.get<std::string>(), builder).loadSnd;
}
else if (static_cast<Game::snd_alias_type_t>(type.get<int>()) == Game::snd_alias_type_t::SAT_STREAMED) // Streamed
{
alias->soundFile->type = Game::SAT_STREAMED;
std::string streamedFile = soundFile.get<std::string>();
std::string directory = ""s;
int split = streamedFile.find_last_of('/');
if (split >= 0)
{
directory = streamedFile.substr(0, split);
streamedFile = streamedFile.substr(split+1);
}
alias->soundFile->u.streamSnd.filename.info.raw.dir = builder->getAllocator()->duplicateString(directory.c_str());
alias->soundFile->u.streamSnd.filename.info.raw.name = builder->getAllocator()->duplicateString(streamedFile.c_str());
}
else
{
Components::Logger::Error(Game::ERR_FATAL, "Failed to parse sound {}! Invalid sound type {}\n", name, type.get<std::string>());
return;
}
aliasList->head[i] = *alias;
}
else
{
Components::Logger::Error(Game::ERR_FATAL, "Failed to parse sound {}!\n", name);
return;
}
}
header->sound = aliasList;
#undef CHECK
header->sound = builder->getIW4OfApi()->read<Game::snd_alias_list_t>(Game::XAssetType::ASSET_TYPE_SOUND, name);
}
void Isnd_alias_list_t::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
@ -392,8 +48,16 @@ namespace Assets
if (asset->aliasName)
{
buffer->saveString(builder->getAssetName(this->getType(), asset->aliasName));
Utils::Stream::ClearPointer(&dest->aliasName);
if (builder->hasPointer(asset->aliasName))
{
dest->aliasName = builder->getPointer(asset->aliasName);
}
else
{
builder->storePointer(asset->aliasName);
buffer->saveString(asset->aliasName);
Utils::Stream::ClearPointer(&dest->aliasName);
}
}
if (asset->head)
@ -419,8 +83,16 @@ namespace Assets
if (alias->aliasName)
{
buffer->saveString(alias->aliasName);
Utils::Stream::ClearPointer(&destAlias->aliasName);
if (builder->hasPointer(alias->aliasName))
{
destAlias->aliasName = builder->getPointer(alias->aliasName);
}
else
{
builder->storePointer(alias->aliasName);
buffer->saveString(alias->aliasName);
Utils::Stream::ClearPointer(&destAlias->aliasName);
}
}
if (alias->subtitle)
@ -467,7 +139,7 @@ namespace Assets
{
if (alias->soundFile->type == Game::snd_alias_type_t::SAT_LOADED)
{
destSoundFile->u.loadSnd = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_LOADED_SOUND, alias->soundFile->u.loadSnd).loadSnd;
destSoundFile->u.loadSnd = builder->saveSubAsset(Game::ASSET_TYPE_LOADED_SOUND, alias->soundFile->u.loadSnd).loadSnd;
}
else
{
@ -494,7 +166,7 @@ namespace Assets
if (alias->volumeFalloffCurve)
{
destAlias->volumeFalloffCurve = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_SOUND_CURVE, alias->volumeFalloffCurve).sndCurve;
destAlias->volumeFalloffCurve = builder->saveSubAsset(Game::ASSET_TYPE_SOUND_CURVE, alias->volumeFalloffCurve).sndCurve;
}
if (alias->speakerMap)

View File

@ -159,10 +159,11 @@ namespace Components
// show error
pushad
push eax
push ebx
push edx
call R_TextureFromCodeError
add esp, 8
add esp, 0xC
popad
// go back

View File

@ -19,8 +19,6 @@ namespace Components
volatile bool ZoneBuilder::CommandThreadTerminate = false;
std::thread ZoneBuilder::CommandThread;
Dvar::Var ZoneBuilder::ZBPreferDiskAssets;
ZoneBuilder::Zone::Zone(const std::string& name) : indexStart(0), externalSize(0),
// Reserve 100MB by default.
// That's totally fine, as the dedi doesn't load images and therefore doesn't need much memory.
@ -28,8 +26,12 @@ namespace Components
// Side note: if you need a fastfile larger than 100MB, you're doing it wrong-
// Well, decompressed maps can get way larger than 100MB, so let's increase that.
buffer(0xC800000),
zoneName(name), dataMap("zone_source/" + name + ".csv"), branding{nullptr}, assetDepth(0)
zoneName(name),
dataMap("zone_source/" + name + ".csv"),
branding{nullptr},
assetDepth(0)
{
this->initializeIW4OfApi();
}
ZoneBuilder::Zone::~Zone()
@ -98,6 +100,11 @@ namespace Components
return &this->memAllocator;
}
iw4of::api* ZoneBuilder::Zone::getIW4OfApi()
{
return &iw4ofApi;
}
void ZoneBuilder::Zone::Zone::build()
{
if (!this->dataMap.isValid())
@ -750,6 +757,49 @@ namespace Components
return header;
}
void ZoneBuilder::Zone::initializeIW4OfApi()
{
iw4of::params_t params;
params.find_other_asset = [this](int type, const std::string& name) -> void*
{
return AssetHandler::FindAssetForZone(static_cast<Game::XAssetType>(type), name, this).data;
};
params.fs_read_file = [](const std::string& filename) -> std::string
{
auto file = FileSystem::File(filename);
if (file.exists())
{
return file.getBuffer();
}
return {};
};
params.store_in_string_table = [](const std::string& text) -> unsigned int
{
return Game::SL_GetString(text.data(), 0);
};
params.print = [](iw4of::params_t::print_type t, const std::string& message) -> void
{
switch (t)
{
case iw4of::params_t::P_ERR:
Logger::PrintError(Game::CON_CHANNEL_ERROR, "{}", message);
break;
case iw4of::params_t::P_WARN:
Logger::Print("{}", message);
break;
}
};
params.work_directory = (*Game::fs_basepath)->current.string;
this->iw4ofApi = iw4of::api{ params };
}
int ZoneBuilder::StoreTexture(Game::GfxImageLoadDef **loadDef, Game::GfxImage *image)
{
size_t size = 16 + (*loadDef)->resourceSize;
@ -1590,9 +1640,6 @@ namespace Components
Logger::Print("{}\n", nlohmann::json(images).dump());
Logger::Print("------------------- END IWI DUMP -------------------\n");
});
// True by default, but can be put to zero for backward compatibility if needed
ZoneBuilder::ZBPreferDiskAssets = Dvar::Register<bool>("zb_prefer_disk_assets", true, Game::DVAR_NONE, "Should ZoneBuilder prefer in-memory assets (requirements) or disk assets when both are present?");
}
}

View File

@ -1,5 +1,7 @@
#pragma once
#include <api.hpp>
#define XFILE_MAGIC_UNSIGNED 0x3030317566665749
#define XFILE_VERSION 276
@ -38,6 +40,7 @@ namespace Components
Utils::Stream* getBuffer();
Utils::Memory::Allocator* getAllocator();
iw4of::api* getIW4OfApi();
bool hasPointer(const void* pointer);
void storePointer(const void* pointer);
@ -87,11 +90,14 @@ namespace Components
void addBranding();
void initializeIW4OfApi();
uint32_t safeGetPointer(const void* pointer);
int indexStart;
unsigned int externalSize;
Utils::Stream buffer;
iw4of::api iw4ofApi;
std::string zoneName;
Utils::CSV dataMap;
@ -131,7 +137,6 @@ namespace Components
static std::vector<std::pair<Game::XAssetType, std::string>> EndAssetTrace();
static Game::XAssetHeader GetEmptyAssetIfCommon(Game::XAssetType type, const std::string& name, Zone* builder);
static Dvar::Var ZBPreferDiskAssets;
private:
static int StoreTexture(Game::GfxImageLoadDef **loadDef, Game::GfxImage *image);

View File

@ -882,8 +882,8 @@ namespace Game
{
float normal[3];
float dist;
char type;
char pad[3];
unsigned char type;
unsigned char pad[3];
};
struct cbrushside_t
@ -896,13 +896,13 @@ namespace Game
struct cbrush_t
{
unsigned __int16 numsides;
unsigned __int16 glassPieceIndex;
unsigned short numsides;
unsigned short glassPieceIndex;
cbrushside_t* sides;
char* baseAdjacentSide;
__int16 axialMaterialNum[2][3];
char firstAdjacentSideOffsets[2][3];
char edgeCount[2][3];
unsigned char* baseAdjacentSide;
unsigned short axialMaterialNum[2][3];
unsigned char firstAdjacentSideOffsets[2][3];
unsigned char edgeCount[2][3];
};
struct BrushWrapper
@ -2614,6 +2614,13 @@ namespace Game
SASYS_COUNT = 0x3,
};
struct SoundFile
{
char type;
char exists;
SoundFileRef u;
};
struct MSSSpeakerLevels
{
int speaker;
@ -2634,11 +2641,24 @@ namespace Game
MSSChannelMap channelMaps[2][2];
};
struct SoundFile
union SoundAliasFlags
{
char type;
char exists;
SoundFileRef u;
#pragma warning(push)
#pragma warning(disable: 4201)
struct
{
unsigned int looping : 1; // & 1 / 0x1 / 0000 0000 0000 0001
unsigned int isMaster : 1; // & 2 / 0x2 / 0000 0000 0000 0010
unsigned int isSlave : 1; // & 4 / 0x4 / 0000 0000 0000 0100
unsigned int fullDryLevel : 1; // & 8 / 0x8 / 0000 0000 0000 1000
unsigned int noWetLevel : 1; // & 16 / 0x10 / 0000 0000 0001 0000
unsigned int unknown : 1; // & 32 / 0x20 / 0000 0000 0010 0000
unsigned int unk_is3D : 1; // & 64 / 0x40 / 0000 0000 0100 0000 // CONFIRMED IW4 IW5
unsigned int type : 2; // & 384 / 0x180 / 0000 0001 1000 0000 // CONFIRMED IW4 IW5
unsigned int channel : 6; // & 32256 / 0x7E00 / 0111 1110 0000 0000 // CONFIRMED IW4 IW5
};
#pragma warning(pop)
unsigned int intValue;
};
struct SndCurve
@ -2664,7 +2684,7 @@ namespace Game
float distMin;
float distMax;
float velocityMin;
int flags;
SoundAliasFlags flags;
union
{
float slavePercentage;
@ -2817,7 +2837,7 @@ namespace Game
struct cLeafBrushNode_s
{
char axis;
unsigned char axis;
__int16 leafBrushCount;
int contents;
cLeafBrushNodeData_t data;
@ -2834,9 +2854,9 @@ namespace Game
struct CollisionPartition
{
char triCount;
char borderCount;
char firstVertSegment;
unsigned char triCount;
unsigned char borderCount;
unsigned char firstVertSegment;
int firstTri;
CollisionBorder* borders;
};
@ -3173,7 +3193,7 @@ namespace Game
{
const char* name;
int isInUse;
int planeCount;
unsigned int planeCount;
cplane_s* planes;
unsigned int numStaticModels;
cStaticModel_s* staticModelList;
@ -3182,7 +3202,7 @@ namespace Game
unsigned int numBrushSides;
cbrushside_t* brushsides;
unsigned int numBrushEdges;
char* brushEdges;
unsigned char* brushEdges;
unsigned int numNodes;
cNode_t* nodes;
unsigned int numLeafs;
@ -3194,15 +3214,15 @@ namespace Game
unsigned int numLeafSurfaces;
unsigned int* leafsurfaces;
unsigned int vertCount;
float(*verts)[3];
int triCount;
vec3_t* verts;
unsigned int triCount;
unsigned __int16* triIndices;
char* triEdgeIsWalkable;
int borderCount;
unsigned char* triEdgeIsWalkable;
unsigned int borderCount;
CollisionBorder* borders;
int partitionCount;
CollisionPartition* partitions;
int aabbTreeCount;
unsigned int aabbTreeCount;
CollisionAabbTree* aabbTrees;
unsigned int numSubModels;
cmodel_t* cmodels;
@ -3574,9 +3594,9 @@ namespace Game
float texCoordOrigin[2];
unsigned int supportMask;
float areaX2;
char defIndex;
char vertCount;
char fanDataCount;
unsigned char defIndex;
unsigned char vertCount;
unsigned char fanDataCount;
char pad[1];
};

View File

@ -62,4 +62,12 @@ namespace Utils::Json
return input.to_ulong();
}
Game::Bounds ReadBounds(const nlohmann::json& value)
{
Game::Bounds bounds{};
CopyArray(bounds.midPoint, value["midPoint"]);
CopyArray(bounds.halfSize, value["halfSize"]);
return bounds;
}
}

View File

@ -1,8 +1,25 @@
#pragma once
#include <json.hpp>
namespace Utils::Json
{
std::string TypeToString(nlohmann::json::value_t type);
unsigned long ReadFlags(std::string binaryFlags, size_t size);
unsigned long ReadFlags(const std::string binaryFlags, size_t size);
Game::Bounds ReadBounds(const nlohmann::json& value);
template <typename T> void CopyArray(T* destination, const nlohmann::json& json_member, size_t count = 0)
{
if (count == 0)
{
count = json_member.size();
}
for (size_t i = 0; i < count; i++)
{
destination[i] = json_member[i].get<T>();
}
}
}

View File

@ -7,7 +7,7 @@ namespace Utils::Maths
return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
}
void VectorSubtract(float va[3], float vb[3], float out[3])
void VectorSubtract(const float va[3], const float vb[3], float out[3])
{
out[0] = va[0] - vb[0];
out[1] = va[1] - vb[1];
@ -35,7 +35,7 @@ namespace Utils::Maths
out[2] = v[2] * scale;
}
float Vec3SqrDistance(float v1[3], float v2[3])
float Vec3SqrDistance(const float v1[3], const float v2[3])
{
float out[3];

View File

@ -7,9 +7,9 @@ namespace Utils::Maths
constexpr auto VectorNegate(float x[3]) { x[0] = -x[0]; x[1] = -x[1]; x[2] = -x[2]; }
float DotProduct(float v1[3], float v2[3]);
void VectorSubtract(float va[3], float vb[3], float out[3]);
void VectorSubtract(const float va[3], const float vb[3], float out[3]);
void VectorAdd(float va[3], float vb[3], float out[3]);
void VectorCopy(float in[3], float out[3]);
void VectorScale(float v[3], float scale, float out[3]);
float Vec3SqrDistance(float v1[3], float v2[3]);
float Vec3SqrDistance(const float v1[3], const float v2[3]);
}

View File

@ -31,12 +31,11 @@ namespace Utils
void* Stream::Reader::read(size_t size, std::size_t count)
{
size_t bytes = size * count;
auto bytes = size * count;
if ((this->position_ + bytes) <= this->buffer_.size())
{
void* buffer = this->allocator_->allocate(bytes);
auto* buffer = this->allocator_->allocate(bytes);
std::memcpy(buffer, this->buffer_.data() + this->position_, bytes);
this->position_ += bytes;
@ -84,7 +83,7 @@ namespace Utils
bool Stream::Reader::hasPointer(void* pointer) const
{
return this->pointerMap_.find(pointer) != this->pointerMap_.end();
return this->pointerMap_.contains(pointer);
}
Stream::Stream() : ptrAssertion(false), criticalSectionState(0)
@ -159,7 +158,7 @@ namespace Utils
return this->at();
}
auto data = this->data();
auto* data = this->data();
if (this->isCriticalSection() && this->length() + (size * count) > this->capacity())
{

View File

@ -12,6 +12,9 @@
namespace Utils
{
constexpr auto POINTER = 255;
constexpr auto FOLLOWING = 254;
class Stream
{
private:
@ -42,9 +45,6 @@ namespace Utils
template <typename T> T* readArrayOnce(std::size_t count = 1)
{
constexpr auto POINTER = 255;
constexpr auto FOLLOWING = 254;
auto b = static_cast<unsigned char>(readByte());
switch (b)
{
@ -65,6 +65,7 @@ namespace Utils
auto filePosition = position_;
auto data = readArray<T>(count);
allocator_->mapPointer(reinterpret_cast<void*>(filePosition), data);
return data;
}
default:
@ -123,11 +124,13 @@ namespace Utils
Stream(size_t size);
~Stream();
std::unordered_map<void*, size_t> dataPointers;
[[nodiscard]] std::size_t length() const;
[[nodiscard]] std::size_t capacity() const;
char* save(const void * _str, std::size_t size, std::size_t count = 1);
char* save(Game::XFILE_BLOCK_TYPES stream, const void * _str, std::size_t size, std::size_t count);
char* save(const void * str, std::size_t size, std::size_t count = 1);
char* save(Game::XFILE_BLOCK_TYPES stream, const void * str, std::size_t size, std::size_t count);
char* save(Game::XFILE_BLOCK_TYPES stream, int value, std::size_t count);
template <typename T> char* save(T* object)
@ -135,6 +138,38 @@ namespace Utils
return saveArray<T>(object, 1);
}
template <typename T> char* saveObject(T value)
{
return saveArray(&value, 1);
}
template <typename T> void saveArrayIfNotExisting(T* data, size_t count)
{
if (const auto itr = dataPointers.find(data); itr != dataPointers.end())
{
saveByte(POINTER);
saveObject(itr->second);
}
else
{
saveByte(FOLLOWING);
dataPointers.insert_or_assign(reinterpret_cast<void*>(data), length());
saveArray(data, count);
}
}
char* save(int value, size_t count = 1)
{
auto ret = this->length();
for (size_t i = 0; i < count; ++i)
{
this->save(&value, 4, 1);
}
return this->data() + ret;
}
template <typename T> char* saveArray(T* array, std::size_t count)
{
return save(array, sizeof(T), count);